summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeL10n.txt6
-rw-r--r--src/COPYING-DOCS397
-rw-r--r--src/DBSCHEMA.ODSbin0 -> 15850 bytes
-rw-r--r--src/DESIGN18
-rw-r--r--src/Makefile.am25
-rw-r--r--src/NEWS.0.9.0397
-rw-r--r--src/NEWS.0.9.1167
-rw-r--r--src/NEWS.0.9.2158
-rw-r--r--src/NEWS.0.9.3142
-rw-r--r--src/NEWS.0.9.4236
-rw-r--r--src/NEWS.0.9.5103
-rw-r--r--src/configure.in.bot133
-rw-r--r--src/configure.in.in387
-rw-r--r--src/digikam/Makefile.am170
-rw-r--r--src/digikam/album.cpp543
-rw-r--r--src/digikam/album.h455
-rw-r--r--src/digikam/albumdb.cpp1599
-rw-r--r--src/digikam/albumdb.h615
-rw-r--r--src/digikam/albumdb_sqlite2.cpp144
-rw-r--r--src/digikam/albumdb_sqlite2.h85
-rw-r--r--src/digikam/albumfiletip.cpp588
-rw-r--r--src/digikam/albumfiletip.h72
-rw-r--r--src/digikam/albumfolderview.cpp1636
-rw-r--r--src/digikam/albumfolderview.h132
-rw-r--r--src/digikam/albumhistory.cpp337
-rw-r--r--src/digikam/albumhistory.h83
-rw-r--r--src/digikam/albumicongroupitem.cpp168
-rw-r--r--src/digikam/albumicongroupitem.h59
-rw-r--r--src/digikam/albumiconitem.cpp375
-rw-r--r--src/digikam/albumiconitem.h76
-rw-r--r--src/digikam/albumiconview.cpp2341
-rw-r--r--src/digikam/albumiconview.h217
-rw-r--r--src/digikam/albumiconviewfilter.cpp208
-rw-r--r--src/digikam/albumiconviewfilter.h78
-rw-r--r--src/digikam/albuminfo.h112
-rw-r--r--src/digikam/albumitemhandler.cpp45
-rw-r--r--src/digikam/albumitemhandler.h60
-rw-r--r--src/digikam/albumlister.cpp640
-rw-r--r--src/digikam/albumlister.h159
-rw-r--r--src/digikam/albummanager.cpp1678
-rw-r--r--src/digikam/albummanager.h472
-rw-r--r--src/digikam/albumpropsedit.cpp376
-rw-r--r--src/digikam/albumpropsedit.h88
-rw-r--r--src/digikam/albumsettings.cpp1142
-rw-r--r--src/digikam/albumsettings.h283
-rw-r--r--src/digikam/albumthumbnailloader.cpp491
-rw-r--r--src/digikam/albumthumbnailloader.h178
-rw-r--r--src/digikam/albumwidgetstack.cpp282
-rw-r--r--src/digikam/albumwidgetstack.h116
-rw-r--r--src/digikam/cameralist.cpp276
-rw-r--r--src/digikam/cameralist.h84
-rw-r--r--src/digikam/cameratype.cpp191
-rw-r--r--src/digikam/cameratype.h81
-rw-r--r--src/digikam/constants.h37
-rw-r--r--src/digikam/daboutdata.h293
-rw-r--r--src/digikam/datefolderview.cpp482
-rw-r--r--src/digikam/datefolderview.h88
-rw-r--r--src/digikam/dcopiface.cpp52
-rw-r--r--src/digikam/dcopiface.h96
-rw-r--r--src/digikam/digikam.desktop159
-rw-r--r--src/digikam/digikam_export.h45
-rw-r--r--src/digikam/digikamapp.cpp2094
-rw-r--r--src/digikam/digikamapp.h181
-rw-r--r--src/digikam/digikamappprivate.h269
-rw-r--r--src/digikam/digikamfirstrun.cpp183
-rw-r--r--src/digikam/digikamfirstrun.h66
-rw-r--r--src/digikam/digikamui.rc147
-rw-r--r--src/digikam/digikamview.cpp1570
-rw-r--r--src/digikam/digikamview.h194
-rw-r--r--src/digikam/dio.cpp262
-rw-r--r--src/digikam/dio.h54
-rw-r--r--src/digikam/dio_p.h56
-rw-r--r--src/digikam/dragobjects.cpp374
-rw-r--r--src/digikam/dragobjects.h205
-rw-r--r--src/digikam/firstrun.cpp100
-rw-r--r--src/digikam/firstrun.h68
-rw-r--r--src/digikam/folderitem.cpp269
-rw-r--r--src/digikam/folderitem.h91
-rw-r--r--src/digikam/folderview.cpp510
-rw-r--r--src/digikam/folderview.h136
-rw-r--r--src/digikam/icongroupitem.cpp307
-rw-r--r--src/digikam/icongroupitem.h91
-rw-r--r--src/digikam/iconitem.cpp171
-rw-r--r--src/digikam/iconitem.h87
-rw-r--r--src/digikam/iconview.cpp1969
-rw-r--r--src/digikam/iconview.h170
-rw-r--r--src/digikam/imageattributeswatch.cpp89
-rw-r--r--src/digikam/imageattributeswatch.h97
-rw-r--r--src/digikam/imageinfo.cpp347
-rw-r--r--src/digikam/imageinfo.h280
-rw-r--r--src/digikam/imagepreviewview.cpp686
-rw-r--r--src/digikam/imagepreviewview.h115
-rw-r--r--src/digikam/kdateedit.cpp378
-rw-r--r--src/digikam/kdateedit.h150
-rw-r--r--src/digikam/kdatepickerpopup.cpp160
-rw-r--r--src/digikam/kdatepickerpopup.h129
-rw-r--r--src/digikam/kdatetimeedit.cpp74
-rw-r--r--src/digikam/kdatetimeedit.h98
-rw-r--r--src/digikam/kipiinterface.cpp724
-rw-r--r--src/digikam/kipiinterface.h187
-rw-r--r--src/digikam/main.cpp138
-rw-r--r--src/digikam/mediaplayerview.cpp252
-rw-r--r--src/digikam/mediaplayerview.h72
-rw-r--r--src/digikam/metadatahub.cpp858
-rw-r--r--src/digikam/metadatahub.h362
-rw-r--r--src/digikam/mimefilter.cpp88
-rw-r--r--src/digikam/mimefilter.h70
-rw-r--r--src/digikam/monthwidget.cpp423
-rw-r--r--src/digikam/monthwidget.h76
-rw-r--r--src/digikam/pixmapmanager.cpp252
-rw-r--r--src/digikam/pixmapmanager.h82
-rw-r--r--src/digikam/ratingfilter.cpp205
-rw-r--r--src/digikam/ratingfilter.h75
-rw-r--r--src/digikam/ratingpopupmenu.cpp82
-rw-r--r--src/digikam/ratingpopupmenu.h49
-rw-r--r--src/digikam/ratingwidget.cpp195
-rw-r--r--src/digikam/ratingwidget.h72
-rw-r--r--src/digikam/scanlib.cpp541
-rw-r--r--src/digikam/scanlib.h172
-rw-r--r--src/digikam/searchadvanceddialog.cpp664
-rw-r--r--src/digikam/searchadvanceddialog.h90
-rw-r--r--src/digikam/searchfolderview.cpp488
-rw-r--r--src/digikam/searchfolderview.h87
-rw-r--r--src/digikam/searchquickdialog.cpp200
-rw-r--r--src/digikam/searchquickdialog.h87
-rw-r--r--src/digikam/searchresultsitem.cpp87
-rw-r--r--src/digikam/searchresultsitem.h57
-rw-r--r--src/digikam/searchresultsview.cpp211
-rw-r--r--src/digikam/searchresultsview.h82
-rw-r--r--src/digikam/searchwidgets.cpp602
-rw-r--r--src/digikam/searchwidgets.h391
-rw-r--r--src/digikam/syncjob.cpp288
-rw-r--r--src/digikam/syncjob.h108
-rw-r--r--src/digikam/tageditdlg.cpp406
-rw-r--r--src/digikam/tageditdlg.h85
-rw-r--r--src/digikam/tagfilterview.cpp1429
-rw-r--r--src/digikam/tagfilterview.h118
-rw-r--r--src/digikam/tagfolderview.cpp1041
-rw-r--r--src/digikam/tagfolderview.h107
-rw-r--r--src/digikam/tagspopupmenu.cpp366
-rw-r--r--src/digikam/tagspopupmenu.h78
-rw-r--r--src/digikam/thumbnailsize.h90
-rw-r--r--src/digikam/timelinefolderview.cpp308
-rw-r--r--src/digikam/timelinefolderview.h81
-rw-r--r--src/digikam/timelineview.cpp645
-rw-r--r--src/digikam/timelineview.h86
-rw-r--r--src/digikam/timelinewidget.cpp1718
-rw-r--r--src/digikam/timelinewidget.h154
-rw-r--r--src/digikam/upgradedb_sqlite2tosqlite3.cpp609
-rw-r--r--src/digikam/upgradedb_sqlite2tosqlite3.h38
-rw-r--r--src/digikam/welcomepageview.cpp193
-rw-r--r--src/digikam/welcomepageview.h64
-rw-r--r--src/imageplugins/Makefile.am5
-rw-r--r--src/imageplugins/adjustcurves/Makefile.am33
-rw-r--r--src/imageplugins/adjustcurves/adjustcurves.cpp677
-rw-r--r--src/imageplugins/adjustcurves/adjustcurves.h143
-rw-r--r--src/imageplugins/adjustcurves/adjustcurvestool.cpp659
-rw-r--r--src/imageplugins/adjustcurves/adjustcurvestool.h145
-rw-r--r--src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves.desktop52
-rw-r--r--src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves_ui.rc20
-rw-r--r--src/imageplugins/adjustcurves/imageplugin_adjustcurves.cpp70
-rw-r--r--src/imageplugins/adjustcurves/imageplugin_adjustcurves.h56
-rw-r--r--src/imageplugins/adjustlevels/Makefile.am34
-rw-r--r--src/imageplugins/adjustlevels/adjustlevels.cpp913
-rw-r--r--src/imageplugins/adjustlevels/adjustlevels.h153
-rw-r--r--src/imageplugins/adjustlevels/adjustlevelstool.cpp901
-rw-r--r--src/imageplugins/adjustlevels/adjustlevelstool.h160
-rw-r--r--src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels.desktop52
-rw-r--r--src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels_ui.rc20
-rw-r--r--src/imageplugins/adjustlevels/imageplugin_adjustlevels.cpp71
-rw-r--r--src/imageplugins/adjustlevels/imageplugin_adjustlevels.h56
-rw-r--r--src/imageplugins/antivignetting/Makefile.am34
-rw-r--r--src/imageplugins/antivignetting/antivignetting.cpp149
-rw-r--r--src/imageplugins/antivignetting/antivignetting.h62
-rw-r--r--src/imageplugins/antivignetting/antivignettingtool.cpp378
-rw-r--r--src/imageplugins/antivignetting/antivignettingtool.h92
-rw-r--r--src/imageplugins/antivignetting/digikamimageplugin_antivignetting.desktop50
-rw-r--r--src/imageplugins/antivignetting/digikamimageplugin_antivignetting_ui.rc20
-rw-r--r--src/imageplugins/antivignetting/imageeffect_antivignetting.cpp380
-rw-r--r--src/imageplugins/antivignetting/imageeffect_antivignetting.h79
-rw-r--r--src/imageplugins/antivignetting/imageplugin_antivignetting.cpp70
-rw-r--r--src/imageplugins/antivignetting/imageplugin_antivignetting.h57
-rw-r--r--src/imageplugins/blurfx/Makefile.am34
-rw-r--r--src/imageplugins/blurfx/blurfx.cpp1445
-rw-r--r--src/imageplugins/blurfx/blurfx.h191
-rw-r--r--src/imageplugins/blurfx/blurfxtool.cpp402
-rw-r--r--src/imageplugins/blurfx/blurfxtool.h93
-rw-r--r--src/imageplugins/blurfx/digikamimageplugin_blurfx.desktop49
-rw-r--r--src/imageplugins/blurfx/digikamimageplugin_blurfx_ui.rc20
-rw-r--r--src/imageplugins/blurfx/imageeffect_blurfx.cpp388
-rw-r--r--src/imageplugins/blurfx/imageeffect_blurfx.h80
-rw-r--r--src/imageplugins/blurfx/imageplugin_blurfx.cpp70
-rw-r--r--src/imageplugins/blurfx/imageplugin_blurfx.h56
-rw-r--r--src/imageplugins/border/Makefile.am34
-rw-r--r--src/imageplugins/border/border.cpp393
-rw-r--r--src/imageplugins/border/border.h151
-rw-r--r--src/imageplugins/border/bordertool.cpp668
-rw-r--r--src/imageplugins/border/bordertool.h123
-rw-r--r--src/imageplugins/border/digikamimageplugin_border.desktop50
-rw-r--r--src/imageplugins/border/digikamimageplugin_border_ui.rc20
-rw-r--r--src/imageplugins/border/imageeffect_border.cpp667
-rw-r--r--src/imageplugins/border/imageeffect_border.h109
-rw-r--r--src/imageplugins/border/imageplugin_border.cpp71
-rw-r--r--src/imageplugins/border/imageplugin_border.h57
-rw-r--r--src/imageplugins/border/patterns/Makefile.am5
-rw-r--r--src/imageplugins/border/patterns/chalk-pattern.pngbin0 -> 18994 bytes
-rw-r--r--src/imageplugins/border/patterns/craters-pattern.pngbin0 -> 17350 bytes
-rw-r--r--src/imageplugins/border/patterns/dried-pattern.pngbin0 -> 20634 bytes
-rw-r--r--src/imageplugins/border/patterns/granit-pattern.pngbin0 -> 20766 bytes
-rw-r--r--src/imageplugins/border/patterns/ice-pattern.pngbin0 -> 18371 bytes
-rw-r--r--src/imageplugins/border/patterns/leaf-pattern.pngbin0 -> 63395 bytes
-rw-r--r--src/imageplugins/border/patterns/marble-pattern.pngbin0 -> 14715 bytes
-rw-r--r--src/imageplugins/border/patterns/paper-pattern.pngbin0 -> 21785 bytes
-rw-r--r--src/imageplugins/border/patterns/parque-pattern.pngbin0 -> 9835 bytes
-rw-r--r--src/imageplugins/border/patterns/pine-pattern.pngbin0 -> 2868 bytes
-rw-r--r--src/imageplugins/border/patterns/pink-pattern.pngbin0 -> 14947 bytes
-rw-r--r--src/imageplugins/border/patterns/rain-pattern.pngbin0 -> 15876 bytes
-rw-r--r--src/imageplugins/border/patterns/rock-pattern.pngbin0 -> 39841 bytes
-rw-r--r--src/imageplugins/border/patterns/stone-pattern.pngbin0 -> 57784 bytes
-rw-r--r--src/imageplugins/border/patterns/wall-pattern.pngbin0 -> 19335 bytes
-rw-r--r--src/imageplugins/border/patterns/wood-pattern.pngbin0 -> 11893 bytes
-rw-r--r--src/imageplugins/channelmixer/Makefile.am34
-rw-r--r--src/imageplugins/channelmixer/channelmixer.cpp784
-rw-r--r--src/imageplugins/channelmixer/channelmixer.h133
-rw-r--r--src/imageplugins/channelmixer/channelmixertool.cpp774
-rw-r--r--src/imageplugins/channelmixer/channelmixertool.h138
-rw-r--r--src/imageplugins/channelmixer/digikamimageplugin_channelmixer.desktop51
-rw-r--r--src/imageplugins/channelmixer/digikamimageplugin_channelmixer_ui.rc20
-rw-r--r--src/imageplugins/channelmixer/imageplugin_channelmixer.cpp71
-rw-r--r--src/imageplugins/channelmixer/imageplugin_channelmixer.h56
-rw-r--r--src/imageplugins/charcoal/Makefile.am34
-rw-r--r--src/imageplugins/charcoal/charcoal.cpp249
-rw-r--r--src/imageplugins/charcoal/charcoal.h56
-rw-r--r--src/imageplugins/charcoal/charcoaltool.cpp202
-rw-r--r--src/imageplugins/charcoal/charcoaltool.h82
-rw-r--r--src/imageplugins/charcoal/digikamimageplugin_charcoal.desktop50
-rw-r--r--src/imageplugins/charcoal/digikamimageplugin_charcoal_ui.rc20
-rw-r--r--src/imageplugins/charcoal/imageeffect_charcoal.cpp193
-rw-r--r--src/imageplugins/charcoal/imageeffect_charcoal.h69
-rw-r--r--src/imageplugins/charcoal/imageplugin_charcoal.cpp72
-rw-r--r--src/imageplugins/charcoal/imageplugin_charcoal.h57
-rw-r--r--src/imageplugins/colorfx/Makefile.am34
-rw-r--r--src/imageplugins/colorfx/colorfxtool.cpp699
-rw-r--r--src/imageplugins/colorfx/colorfxtool.h136
-rw-r--r--src/imageplugins/colorfx/digikamimageplugin_colorfx.desktop40
-rw-r--r--src/imageplugins/colorfx/digikamimageplugin_colorfx_ui.rc20
-rw-r--r--src/imageplugins/colorfx/imageeffect_colorfx.cpp690
-rw-r--r--src/imageplugins/colorfx/imageeffect_colorfx.h131
-rw-r--r--src/imageplugins/colorfx/imageplugin_colorfx.cpp73
-rw-r--r--src/imageplugins/colorfx/imageplugin_colorfx.h57
-rw-r--r--src/imageplugins/coreplugin/Makefile.am52
-rw-r--r--src/imageplugins/coreplugin/autocorrectiontool.cpp438
-rw-r--r--src/imageplugins/coreplugin/autocorrectiontool.h128
-rw-r--r--src/imageplugins/coreplugin/bcgtool.cpp366
-rw-r--r--src/imageplugins/coreplugin/bcgtool.h115
-rw-r--r--src/imageplugins/coreplugin/blurtool.cpp169
-rw-r--r--src/imageplugins/coreplugin/blurtool.h81
-rw-r--r--src/imageplugins/coreplugin/bwsepiatool.cpp1177
-rw-r--r--src/imageplugins/coreplugin/bwsepiatool.h193
-rw-r--r--src/imageplugins/coreplugin/digikamimageplugin_core.desktop47
-rw-r--r--src/imageplugins/coreplugin/digikamimageplugin_core_ui.rc56
-rw-r--r--src/imageplugins/coreplugin/hsl/Makefile.am26
-rw-r--r--src/imageplugins/coreplugin/hsl/hsltool.cpp453
-rw-r--r--src/imageplugins/coreplugin/hsl/hsltool.h126
-rw-r--r--src/imageplugins/coreplugin/hsl/hspreviewwidget.cpp126
-rw-r--r--src/imageplugins/coreplugin/hsl/hspreviewwidget.h64
-rw-r--r--src/imageplugins/coreplugin/hsl/imageeffect_hsl.cpp428
-rw-r--r--src/imageplugins/coreplugin/hsl/imageeffect_hsl.h116
-rw-r--r--src/imageplugins/coreplugin/iccprooftool.cpp1310
-rw-r--r--src/imageplugins/coreplugin/iccprooftool.h209
-rw-r--r--src/imageplugins/coreplugin/imageeffect_autocorrection.cpp431
-rw-r--r--src/imageplugins/coreplugin/imageeffect_autocorrection.h128
-rw-r--r--src/imageplugins/coreplugin/imageeffect_bcg.cpp350
-rw-r--r--src/imageplugins/coreplugin/imageeffect_bcg.h110
-rw-r--r--src/imageplugins/coreplugin/imageeffect_blur.cpp147
-rw-r--r--src/imageplugins/coreplugin/imageeffect_blur.h68
-rw-r--r--src/imageplugins/coreplugin/imageeffect_bwsepia.cpp1183
-rw-r--r--src/imageplugins/coreplugin/imageeffect_bwsepia.h195
-rw-r--r--src/imageplugins/coreplugin/imageeffect_iccproof.cpp1284
-rw-r--r--src/imageplugins/coreplugin/imageeffect_iccproof.h204
-rw-r--r--src/imageplugins/coreplugin/imageeffect_redeye.cpp574
-rw-r--r--src/imageplugins/coreplugin/imageeffect_redeye.h153
-rw-r--r--src/imageplugins/coreplugin/imageeffect_rgb.cpp419
-rw-r--r--src/imageplugins/coreplugin/imageeffect_rgb.h113
-rw-r--r--src/imageplugins/coreplugin/imageplugin_core.cpp295
-rw-r--r--src/imageplugins/coreplugin/imageplugin_core.h85
-rw-r--r--src/imageplugins/coreplugin/ratiocrop/Makefile.am26
-rw-r--r--src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.cpp799
-rw-r--r--src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.h132
-rw-r--r--src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.cpp1422
-rw-r--r--src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.h176
-rw-r--r--src/imageplugins/coreplugin/ratiocrop/ratiocroptool.cpp853
-rw-r--r--src/imageplugins/coreplugin/ratiocrop/ratiocroptool.h135
-rw-r--r--src/imageplugins/coreplugin/redeyetool.cpp587
-rw-r--r--src/imageplugins/coreplugin/redeyetool.h157
-rw-r--r--src/imageplugins/coreplugin/rgbtool.cpp440
-rw-r--r--src/imageplugins/coreplugin/rgbtool.h119
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/Makefile.am32
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/LICENCE12
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/Makefile.am7
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/README2
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/abort_.c10
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/blaswrap.h158
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/clapack.h5079
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/close.c80
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dgemm.c313
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dger.c143
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dgesv.c117
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetf2.c157
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrf.c197
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrs.c159
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dlaswp.c143
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dscal.c62
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dswap.c81
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/dtrsm.c404
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/endfile.c102
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/err.c240
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/f2c.h223
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/fio.h96
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.c470
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.h86
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/fmtlib.c40
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/fp.h28
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/idamax.c61
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/ieeeck.c150
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/ilaenv.c610
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/lsame.c101
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/open.c256
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/s_cmp.c40
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/s_copy.c47
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/s_stop.c37
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/sfe.c29
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/sig_die.c41
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/util.c39
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/wref.c266
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/wrtfmt.c321
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/wsfe.c69
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/clapack/xerbla.c58
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.cpp696
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.h102
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/matrix.cpp663
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/matrix.h129
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/refocus.cpp199
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/refocus.h67
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/sharpentool.cpp741
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/sharpentool.h111
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/unsharp.cpp127
-rw-r--r--src/imageplugins/coreplugin/sharpnesseditor/unsharp.h58
-rw-r--r--src/imageplugins/distortionfx/Makefile.am34
-rw-r--r--src/imageplugins/distortionfx/digikamimageplugin_distortionfx.desktop50
-rw-r--r--src/imageplugins/distortionfx/digikamimageplugin_distortionfx_ui.rc20
-rw-r--r--src/imageplugins/distortionfx/distortionfx.cpp869
-rw-r--r--src/imageplugins/distortionfx/distortionfx.h149
-rw-r--r--src/imageplugins/distortionfx/distortionfxtool.cpp398
-rw-r--r--src/imageplugins/distortionfx/distortionfxtool.h96
-rw-r--r--src/imageplugins/distortionfx/imageeffect_distortionfx.cpp378
-rw-r--r--src/imageplugins/distortionfx/imageeffect_distortionfx.h82
-rw-r--r--src/imageplugins/distortionfx/imageplugin_distortionfx.cpp72
-rw-r--r--src/imageplugins/distortionfx/imageplugin_distortionfx.h59
-rw-r--r--src/imageplugins/emboss/Makefile.am34
-rw-r--r--src/imageplugins/emboss/digikamimageplugin_emboss.desktop51
-rw-r--r--src/imageplugins/emboss/digikamimageplugin_emboss_ui.rc20
-rw-r--r--src/imageplugins/emboss/emboss.cpp127
-rw-r--r--src/imageplugins/emboss/emboss.h75
-rw-r--r--src/imageplugins/emboss/embosstool.cpp167
-rw-r--r--src/imageplugins/emboss/embosstool.h82
-rw-r--r--src/imageplugins/emboss/imageeffect_emboss.cpp170
-rw-r--r--src/imageplugins/emboss/imageeffect_emboss.h69
-rw-r--r--src/imageplugins/emboss/imageplugin_emboss.cpp72
-rw-r--r--src/imageplugins/emboss/imageplugin_emboss.h57
-rw-r--r--src/imageplugins/filmgrain/Makefile.am34
-rw-r--r--src/imageplugins/filmgrain/digikamimageplugin_filmgrain.desktop52
-rw-r--r--src/imageplugins/filmgrain/digikamimageplugin_filmgrain_ui.rc20
-rw-r--r--src/imageplugins/filmgrain/filmgrain.cpp202
-rw-r--r--src/imageplugins/filmgrain/filmgrain.h58
-rw-r--r--src/imageplugins/filmgrain/filmgraintool.cpp195
-rw-r--r--src/imageplugins/filmgrain/filmgraintool.h83
-rw-r--r--src/imageplugins/filmgrain/imageeffect_filmgrain.cpp195
-rw-r--r--src/imageplugins/filmgrain/imageeffect_filmgrain.h74
-rw-r--r--src/imageplugins/filmgrain/imageplugin_filmgrain.cpp71
-rw-r--r--src/imageplugins/filmgrain/imageplugin_filmgrain.h57
-rw-r--r--src/imageplugins/freerotation/Makefile.am34
-rw-r--r--src/imageplugins/freerotation/digikamimageplugin_freerotation.desktop51
-rw-r--r--src/imageplugins/freerotation/digikamimageplugin_freerotation_ui.rc20
-rw-r--r--src/imageplugins/freerotation/freerotation.cpp263
-rw-r--r--src/imageplugins/freerotation/freerotation.h94
-rw-r--r--src/imageplugins/freerotation/freerotationtool.cpp318
-rw-r--r--src/imageplugins/freerotation/freerotationtool.h97
-rw-r--r--src/imageplugins/freerotation/imageeffect_freerotation.cpp308
-rw-r--r--src/imageplugins/freerotation/imageeffect_freerotation.h83
-rw-r--r--src/imageplugins/freerotation/imageplugin_freerotation.cpp70
-rw-r--r--src/imageplugins/freerotation/imageplugin_freerotation.h57
-rw-r--r--src/imageplugins/hotpixels/Makefile.am36
-rw-r--r--src/imageplugins/hotpixels/TODO4
-rw-r--r--src/imageplugins/hotpixels/blackframelistview.cpp176
-rw-r--r--src/imageplugins/hotpixels/blackframelistview.h127
-rw-r--r--src/imageplugins/hotpixels/blackframeparser.cpp211
-rw-r--r--src/imageplugins/hotpixels/blackframeparser.h102
-rw-r--r--src/imageplugins/hotpixels/digikamimageplugin_hotpixels.desktop50
-rw-r--r--src/imageplugins/hotpixels/digikamimageplugin_hotpixels_ui.rc20
-rw-r--r--src/imageplugins/hotpixels/hotpixel.h74
-rw-r--r--src/imageplugins/hotpixels/hotpixelfixer.cpp302
-rw-r--r--src/imageplugins/hotpixels/hotpixelfixer.h98
-rw-r--r--src/imageplugins/hotpixels/hotpixelstool.cpp276
-rw-r--r--src/imageplugins/hotpixels/hotpixelstool.h113
-rw-r--r--src/imageplugins/hotpixels/imageeffect_hotpixels.cpp279
-rw-r--r--src/imageplugins/hotpixels/imageeffect_hotpixels.h104
-rw-r--r--src/imageplugins/hotpixels/imageplugin_hotpixels.cpp72
-rw-r--r--src/imageplugins/hotpixels/imageplugin_hotpixels.h56
-rw-r--r--src/imageplugins/hotpixels/weights.cpp283
-rw-r--r--src/imageplugins/hotpixels/weights.h90
-rw-r--r--src/imageplugins/infrared/Makefile.am34
-rw-r--r--src/imageplugins/infrared/digikamimageplugin_infrared.desktop52
-rw-r--r--src/imageplugins/infrared/digikamimageplugin_infrared_ui.rc19
-rw-r--r--src/imageplugins/infrared/imageeffect_infrared.cpp227
-rw-r--r--src/imageplugins/infrared/imageeffect_infrared.h76
-rw-r--r--src/imageplugins/infrared/imageplugin_infrared.cpp71
-rw-r--r--src/imageplugins/infrared/imageplugin_infrared.h57
-rw-r--r--src/imageplugins/infrared/infrared.cpp367
-rw-r--r--src/imageplugins/infrared/infrared.h60
-rw-r--r--src/imageplugins/infrared/infraredtool.cpp225
-rw-r--r--src/imageplugins/infrared/infraredtool.h86
-rw-r--r--src/imageplugins/inpainting/Makefile.am34
-rw-r--r--src/imageplugins/inpainting/digikamimageplugin_inpainting.desktop49
-rw-r--r--src/imageplugins/inpainting/digikamimageplugin_inpainting_ui.rc20
-rw-r--r--src/imageplugins/inpainting/imageeffect_inpainting.cpp466
-rw-r--r--src/imageplugins/inpainting/imageeffect_inpainting.h116
-rw-r--r--src/imageplugins/inpainting/imageplugin_inpainting.cpp97
-rw-r--r--src/imageplugins/inpainting/imageplugin_inpainting.h57
-rw-r--r--src/imageplugins/inpainting/inpaintingtool.cpp430
-rw-r--r--src/imageplugins/inpainting/inpaintingtool.h133
-rw-r--r--src/imageplugins/inserttext/Makefile.am34
-rw-r--r--src/imageplugins/inserttext/digikamimageplugin_inserttext.desktop50
-rw-r--r--src/imageplugins/inserttext/digikamimageplugin_inserttext_ui.rc20
-rw-r--r--src/imageplugins/inserttext/fontchooserwidget.cpp738
-rw-r--r--src/imageplugins/inserttext/fontchooserwidget.h172
-rw-r--r--src/imageplugins/inserttext/imageeffect_inserttext.cpp348
-rw-r--r--src/imageplugins/inserttext/imageeffect_inserttext.h103
-rw-r--r--src/imageplugins/inserttext/imageplugin_inserttext.cpp70
-rw-r--r--src/imageplugins/inserttext/imageplugin_inserttext.h56
-rw-r--r--src/imageplugins/inserttext/inserttexttool.cpp336
-rw-r--r--src/imageplugins/inserttext/inserttexttool.h111
-rw-r--r--src/imageplugins/inserttext/inserttextwidget.cpp622
-rw-r--r--src/imageplugins/inserttext/inserttextwidget.h156
-rw-r--r--src/imageplugins/lensdistortion/Makefile.am34
-rw-r--r--src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion.desktop50
-rw-r--r--src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion_ui.rc20
-rw-r--r--src/imageplugins/lensdistortion/imageeffect_lensdistortion.cpp317
-rw-r--r--src/imageplugins/lensdistortion/imageeffect_lensdistortion.h82
-rw-r--r--src/imageplugins/lensdistortion/imageplugin_lensdistortion.cpp69
-rw-r--r--src/imageplugins/lensdistortion/imageplugin_lensdistortion.h56
-rw-r--r--src/imageplugins/lensdistortion/lensdistortion.cpp135
-rw-r--r--src/imageplugins/lensdistortion/lensdistortion.h63
-rw-r--r--src/imageplugins/lensdistortion/lensdistortiontool.cpp326
-rw-r--r--src/imageplugins/lensdistortion/lensdistortiontool.h92
-rw-r--r--src/imageplugins/lensdistortion/pixelaccess.cpp314
-rw-r--r--src/imageplugins/lensdistortion/pixelaccess.h93
-rw-r--r--src/imageplugins/noisereduction/Makefile.am34
-rw-r--r--src/imageplugins/noisereduction/digikamimageplugin_noisereduction.desktop53
-rw-r--r--src/imageplugins/noisereduction/digikamimageplugin_noisereduction_ui.rc20
-rw-r--r--src/imageplugins/noisereduction/imageeffect_noisereduction.cpp553
-rw-r--r--src/imageplugins/noisereduction/imageeffect_noisereduction.h82
-rw-r--r--src/imageplugins/noisereduction/imageplugin_noisereduction.cpp70
-rw-r--r--src/imageplugins/noisereduction/imageplugin_noisereduction.h56
-rw-r--r--src/imageplugins/noisereduction/noisereduction.cpp809
-rw-r--r--src/imageplugins/noisereduction/noisereduction.h257
-rw-r--r--src/imageplugins/noisereduction/noisereductiontool.cpp536
-rw-r--r--src/imageplugins/noisereduction/noisereductiontool.h92
-rw-r--r--src/imageplugins/oilpaint/Makefile.am34
-rw-r--r--src/imageplugins/oilpaint/digikamimageplugin_oilpaint.desktop51
-rw-r--r--src/imageplugins/oilpaint/digikamimageplugin_oilpaint_ui.rc20
-rw-r--r--src/imageplugins/oilpaint/imageeffect_oilpaint.cpp201
-rw-r--r--src/imageplugins/oilpaint/imageeffect_oilpaint.h69
-rw-r--r--src/imageplugins/oilpaint/imageplugin_oilpaint.cpp70
-rw-r--r--src/imageplugins/oilpaint/imageplugin_oilpaint.h56
-rw-r--r--src/imageplugins/oilpaint/oilpaint.cpp203
-rw-r--r--src/imageplugins/oilpaint/oilpaint.h72
-rw-r--r--src/imageplugins/oilpaint/oilpainttool.cpp208
-rw-r--r--src/imageplugins/oilpaint/oilpainttool.h82
-rw-r--r--src/imageplugins/perspective/Makefile.am35
-rw-r--r--src/imageplugins/perspective/digikamimageplugin_perspective.desktop52
-rw-r--r--src/imageplugins/perspective/digikamimageplugin_perspective_ui.rc20
-rw-r--r--src/imageplugins/perspective/imageeffect_perspective.cpp252
-rw-r--r--src/imageplugins/perspective/imageeffect_perspective.h89
-rw-r--r--src/imageplugins/perspective/imageplugin_perspective.cpp69
-rw-r--r--src/imageplugins/perspective/imageplugin_perspective.h56
-rw-r--r--src/imageplugins/perspective/matrix.cpp177
-rw-r--r--src/imageplugins/perspective/matrix.h106
-rw-r--r--src/imageplugins/perspective/perspectivetool.cpp244
-rw-r--r--src/imageplugins/perspective/perspectivetool.h100
-rw-r--r--src/imageplugins/perspective/perspectivewidget.cpp839
-rw-r--r--src/imageplugins/perspective/perspectivewidget.h172
-rw-r--r--src/imageplugins/perspective/triangle.cpp65
-rw-r--r--src/imageplugins/perspective/triangle.h57
-rw-r--r--src/imageplugins/raindrop/Makefile.am34
-rw-r--r--src/imageplugins/raindrop/digikamimageplugin_raindrop.desktop52
-rw-r--r--src/imageplugins/raindrop/digikamimageplugin_raindrop_ui.rc20
-rw-r--r--src/imageplugins/raindrop/imageeffect_raindrop.cpp259
-rw-r--r--src/imageplugins/raindrop/imageeffect_raindrop.h70
-rw-r--r--src/imageplugins/raindrop/imageplugin_raindrop.cpp69
-rw-r--r--src/imageplugins/raindrop/imageplugin_raindrop.h56
-rw-r--r--src/imageplugins/raindrop/raindrop.cpp457
-rw-r--r--src/imageplugins/raindrop/raindrop.h105
-rw-r--r--src/imageplugins/raindrop/raindroptool.cpp254
-rw-r--r--src/imageplugins/raindrop/raindroptool.h82
-rw-r--r--src/imageplugins/restoration/Makefile.am35
-rw-r--r--src/imageplugins/restoration/digikamimageplugin_restoration.desktop52
-rw-r--r--src/imageplugins/restoration/digikamimageplugin_restoration_ui.rc20
-rw-r--r--src/imageplugins/restoration/imageeffect_restoration.cpp349
-rw-r--r--src/imageplugins/restoration/imageeffect_restoration.h94
-rw-r--r--src/imageplugins/restoration/imageplugin_restoration.cpp71
-rw-r--r--src/imageplugins/restoration/imageplugin_restoration.h57
-rw-r--r--src/imageplugins/restoration/restorationtool.cpp356
-rw-r--r--src/imageplugins/restoration/restorationtool.h100
-rw-r--r--src/imageplugins/sheartool/Makefile.am33
-rw-r--r--src/imageplugins/sheartool/digikamimageplugin_sheartool.desktop53
-rw-r--r--src/imageplugins/sheartool/digikamimageplugin_sheartool_ui.rc20
-rw-r--r--src/imageplugins/sheartool/imageeffect_sheartool.cpp322
-rw-r--r--src/imageplugins/sheartool/imageeffect_sheartool.h82
-rw-r--r--src/imageplugins/sheartool/imageplugin_sheartool.cpp69
-rw-r--r--src/imageplugins/sheartool/imageplugin_sheartool.h56
-rw-r--r--src/imageplugins/sheartool/shear.cpp185
-rw-r--r--src/imageplugins/sheartool/shear.h84
-rw-r--r--src/imageplugins/sheartool/sheartool.cpp331
-rw-r--r--src/imageplugins/sheartool/sheartool.h96
-rw-r--r--src/imageplugins/superimpose/Makefile.am35
-rw-r--r--src/imageplugins/superimpose/digikamimageplugin_superimpose.desktop52
-rw-r--r--src/imageplugins/superimpose/digikamimageplugin_superimpose_ui.rc20
-rw-r--r--src/imageplugins/superimpose/dirselectwidget.cpp182
-rw-r--r--src/imageplugins/superimpose/dirselectwidget.h78
-rw-r--r--src/imageplugins/superimpose/imageeffect_superimpose.cpp280
-rw-r--r--src/imageplugins/superimpose/imageeffect_superimpose.h87
-rw-r--r--src/imageplugins/superimpose/imageplugin_superimpose.cpp71
-rw-r--r--src/imageplugins/superimpose/imageplugin_superimpose.h58
-rw-r--r--src/imageplugins/superimpose/superimpose.cpp70
-rw-r--r--src/imageplugins/superimpose/superimpose.h67
-rw-r--r--src/imageplugins/superimpose/superimposetool.cpp271
-rw-r--r--src/imageplugins/superimpose/superimposetool.h90
-rw-r--r--src/imageplugins/superimpose/superimposewidget.cpp329
-rw-r--r--src/imageplugins/superimpose/superimposewidget.h118
-rw-r--r--src/imageplugins/texture/Makefile.am35
-rw-r--r--src/imageplugins/texture/digikamimageplugin_texture.desktop52
-rw-r--r--src/imageplugins/texture/digikamimageplugin_texture_ui.rc20
-rw-r--r--src/imageplugins/texture/imageeffect_texture.cpp291
-rw-r--r--src/imageplugins/texture/imageeffect_texture.h100
-rw-r--r--src/imageplugins/texture/imageplugin_texture.cpp70
-rw-r--r--src/imageplugins/texture/imageplugin_texture.h56
-rw-r--r--src/imageplugins/texture/patterns/Makefile.am5
-rw-r--r--src/imageplugins/texture/patterns/bluejean-texture.pngbin0 -> 18748 bytes
-rw-r--r--src/imageplugins/texture/patterns/bricks-texture.pngbin0 -> 7888 bytes
-rw-r--r--src/imageplugins/texture/patterns/bricks2-texture.pngbin0 -> 8867 bytes
-rw-r--r--src/imageplugins/texture/patterns/burlap-texture.pngbin0 -> 7960 bytes
-rw-r--r--src/imageplugins/texture/patterns/canvas-texture.pngbin0 -> 33037 bytes
-rw-r--r--src/imageplugins/texture/patterns/cellwood-texture.pngbin0 -> 55637 bytes
-rw-r--r--src/imageplugins/texture/patterns/fabric-texture.pngbin0 -> 14189 bytes
-rw-r--r--src/imageplugins/texture/patterns/marble-texture.pngbin0 -> 19218 bytes
-rw-r--r--src/imageplugins/texture/patterns/marble2-texture.pngbin0 -> 6551 bytes
-rw-r--r--src/imageplugins/texture/patterns/metalwire-texture.pngbin0 -> 90343 bytes
-rw-r--r--src/imageplugins/texture/patterns/modern-texture.pngbin0 -> 85181 bytes
-rw-r--r--src/imageplugins/texture/patterns/moss-texture.pngbin0 -> 19894 bytes
-rw-r--r--src/imageplugins/texture/patterns/paper-texture.pngbin0 -> 4435 bytes
-rw-r--r--src/imageplugins/texture/patterns/paper2-texture.pngbin0 -> 182714 bytes
-rw-r--r--src/imageplugins/texture/patterns/stone-texture.pngbin0 -> 13774 bytes
-rw-r--r--src/imageplugins/texture/patterns/wall-texture.pngbin0 -> 69570 bytes
-rw-r--r--src/imageplugins/texture/texture.cpp181
-rw-r--r--src/imageplugins/texture/texture.h62
-rw-r--r--src/imageplugins/texture/texturetool.cpp294
-rw-r--r--src/imageplugins/texture/texturetool.h112
-rw-r--r--src/imageplugins/whitebalance/Makefile.am33
-rw-r--r--src/imageplugins/whitebalance/digikamimageplugin_whitebalance.desktop53
-rw-r--r--src/imageplugins/whitebalance/digikamimageplugin_whitebalance_ui.rc20
-rw-r--r--src/imageplugins/whitebalance/imageeffect_whitebalance.cpp842
-rw-r--r--src/imageplugins/whitebalance/imageeffect_whitebalance.h168
-rw-r--r--src/imageplugins/whitebalance/imageplugin_whitebalance.cpp71
-rw-r--r--src/imageplugins/whitebalance/imageplugin_whitebalance.h57
-rw-r--r--src/imageplugins/whitebalance/whitebalancetool.cpp850
-rw-r--r--src/imageplugins/whitebalance/whitebalancetool.h174
-rw-r--r--src/libs/Makefile.am9
-rw-r--r--src/libs/curves/Makefile.am16
-rw-r--r--src/libs/curves/imagecurves.cpp768
-rw-r--r--src/libs/curves/imagecurves.h111
-rw-r--r--src/libs/dialogs/Makefile.am33
-rw-r--r--src/libs/dialogs/ctrlpaneldlg.cpp445
-rw-r--r--src/libs/dialogs/ctrlpaneldlg.h110
-rw-r--r--src/libs/dialogs/deletedialog.cpp309
-rw-r--r--src/libs/dialogs/deletedialog.h140
-rw-r--r--src/libs/dialogs/deletedialogbase.ui188
-rw-r--r--src/libs/dialogs/dprogressdlg.cpp224
-rw-r--r--src/libs/dialogs/dprogressdlg.h79
-rw-r--r--src/libs/dialogs/iccprofileinfodlg.cpp60
-rw-r--r--src/libs/dialogs/iccprofileinfodlg.h58
-rw-r--r--src/libs/dialogs/imagedialog.cpp366
-rw-r--r--src/libs/dialogs/imagedialog.h100
-rw-r--r--src/libs/dialogs/imagedlgbase.cpp261
-rw-r--r--src/libs/dialogs/imagedlgbase.h98
-rw-r--r--src/libs/dialogs/imageguidedlg.cpp597
-rw-r--r--src/libs/dialogs/imageguidedlg.h123
-rw-r--r--src/libs/dialogs/rawcameradlg.cpp178
-rw-r--r--src/libs/dialogs/rawcameradlg.h61
-rw-r--r--src/libs/dimg/Makefile.am27
-rw-r--r--src/libs/dimg/README95
-rw-r--r--src/libs/dimg/dcolor.cpp281
-rw-r--r--src/libs/dimg/dcolor.h152
-rw-r--r--src/libs/dimg/dcolorblend.h179
-rw-r--r--src/libs/dimg/dcolorcomposer.cpp436
-rw-r--r--src/libs/dimg/dcolorcomposer.h133
-rw-r--r--src/libs/dimg/dcolorpixelaccess.h78
-rw-r--r--src/libs/dimg/ddebug.cpp92
-rw-r--r--src/libs/dimg/ddebug.h73
-rw-r--r--src/libs/dimg/dimg.cpp1700
-rw-r--r--src/libs/dimg/dimg.h353
-rw-r--r--src/libs/dimg/dimgprivate.h81
-rw-r--r--src/libs/dimg/dimgscale.cpp2127
-rw-r--r--src/libs/dimg/drawdecoding.h128
-rw-r--r--src/libs/dimg/exposurecontainer.h65
-rw-r--r--src/libs/dimg/filters/Makefile.am21
-rw-r--r--src/libs/dimg/filters/bcgmodifier.cpp208
-rw-r--r--src/libs/dimg/filters/bcgmodifier.h73
-rw-r--r--src/libs/dimg/filters/colormodifier.cpp287
-rw-r--r--src/libs/dimg/filters/colormodifier.h63
-rw-r--r--src/libs/dimg/filters/dimggaussianblur.cpp327
-rw-r--r--src/libs/dimg/filters/dimggaussianblur.h97
-rw-r--r--src/libs/dimg/filters/dimgimagefilters.cpp977
-rw-r--r--src/libs/dimg/filters/dimgimagefilters.h133
-rw-r--r--src/libs/dimg/filters/dimgsharpen.cpp243
-rw-r--r--src/libs/dimg/filters/dimgsharpen.h69
-rw-r--r--src/libs/dimg/filters/dimgthreadedfilter.cpp170
-rw-r--r--src/libs/dimg/filters/dimgthreadedfilter.h153
-rw-r--r--src/libs/dimg/filters/hslmodifier.cpp240
-rw-r--r--src/libs/dimg/filters/hslmodifier.h58
-rw-r--r--src/libs/dimg/filters/icctransform.cpp831
-rw-r--r--src/libs/dimg/filters/icctransform.h94
-rw-r--r--src/libs/dimg/loaders/Makefile.am21
-rw-r--r--src/libs/dimg/loaders/README42
-rw-r--r--src/libs/dimg/loaders/dimgloader.cpp200
-rw-r--r--src/libs/dimg/loaders/dimgloader.h97
-rw-r--r--src/libs/dimg/loaders/dimgloaderobserver.h67
-rw-r--r--src/libs/dimg/loaders/iccjpeg.c270
-rw-r--r--src/libs/dimg/loaders/iccjpeg.h101
-rw-r--r--src/libs/dimg/loaders/jp2kloader.cpp715
-rw-r--r--src/libs/dimg/loaders/jp2kloader.h69
-rw-r--r--src/libs/dimg/loaders/jp2ksettings.cpp139
-rw-r--r--src/libs/dimg/loaders/jp2ksettings.h67
-rw-r--r--src/libs/dimg/loaders/jpegloader.cpp676
-rw-r--r--src/libs/dimg/loaders/jpegloader.h80
-rw-r--r--src/libs/dimg/loaders/jpegsettings.cpp154
-rw-r--r--src/libs/dimg/loaders/jpegsettings.h63
-rw-r--r--src/libs/dimg/loaders/pngloader.cpp993
-rw-r--r--src/libs/dimg/loaders/pngloader.h73
-rw-r--r--src/libs/dimg/loaders/pngsettings.cpp102
-rw-r--r--src/libs/dimg/loaders/pngsettings.h60
-rw-r--r--src/libs/dimg/loaders/ppmloader.cpp178
-rw-r--r--src/libs/dimg/loaders/ppmloader.h59
-rw-r--r--src/libs/dimg/loaders/qimageloader.cpp126
-rw-r--r--src/libs/dimg/loaders/qimageloader.h57
-rw-r--r--src/libs/dimg/loaders/rawloader.cpp371
-rw-r--r--src/libs/dimg/loaders/rawloader.h86
-rw-r--r--src/libs/dimg/loaders/tiffloader.cpp806
-rw-r--r--src/libs/dimg/loaders/tiffloader.h76
-rw-r--r--src/libs/dimg/loaders/tiffsettings.cpp94
-rw-r--r--src/libs/dimg/loaders/tiffsettings.h60
-rw-r--r--src/libs/dmetadata/Makefile.am18
-rw-r--r--src/libs/dmetadata/dmetadata.cpp544
-rw-r--r--src/libs/dmetadata/dmetadata.h86
-rw-r--r--src/libs/dmetadata/photoinfocontainer.h82
-rw-r--r--src/libs/greycstoration/CImg.h36837
-rw-r--r--src/libs/greycstoration/LICENSE.txt505
-rw-r--r--src/libs/greycstoration/Makefile.am20
-rw-r--r--src/libs/greycstoration/greycstoration.h481
-rw-r--r--src/libs/greycstoration/greycstorationiface.cpp473
-rw-r--r--src/libs/greycstoration/greycstorationiface.h89
-rw-r--r--src/libs/greycstoration/greycstorationsettings.h144
-rw-r--r--src/libs/greycstoration/greycstorationwidget.cpp377
-rw-r--r--src/libs/greycstoration/greycstorationwidget.h70
-rw-r--r--src/libs/histogram/Makefile.am16
-rw-r--r--src/libs/histogram/imagehistogram.cpp548
-rw-r--r--src/libs/histogram/imagehistogram.h113
-rw-r--r--src/libs/imageproperties/Makefile.am51
-rw-r--r--src/libs/imageproperties/cameraitempropertiestab.cpp555
-rw-r--r--src/libs/imageproperties/cameraitempropertiestab.h69
-rw-r--r--src/libs/imageproperties/imagedescedittab.cpp1763
-rw-r--r--src/libs/imageproperties/imagedescedittab.h145
-rw-r--r--src/libs/imageproperties/imagepropertiescolorstab.cpp799
-rw-r--r--src/libs/imageproperties/imagepropertiescolorstab.h114
-rw-r--r--src/libs/imageproperties/imagepropertiesmetadatatab.cpp201
-rw-r--r--src/libs/imageproperties/imagepropertiesmetadatatab.h68
-rw-r--r--src/libs/imageproperties/imagepropertiessidebar.cpp150
-rw-r--r--src/libs/imageproperties/imagepropertiessidebar.h93
-rw-r--r--src/libs/imageproperties/imagepropertiessidebarcamgui.cpp209
-rw-r--r--src/libs/imageproperties/imagepropertiessidebarcamgui.h87
-rw-r--r--src/libs/imageproperties/imagepropertiessidebardb.cpp368
-rw-r--r--src/libs/imageproperties/imagepropertiessidebardb.h117
-rw-r--r--src/libs/imageproperties/imagepropertiestab.cpp601
-rw-r--r--src/libs/imageproperties/imagepropertiestab.h66
-rw-r--r--src/libs/imageproperties/navigatebartab.cpp146
-rw-r--r--src/libs/imageproperties/navigatebartab.h85
-rw-r--r--src/libs/imageproperties/navigatebarwidget.cpp112
-rw-r--r--src/libs/imageproperties/navigatebarwidget.h70
-rw-r--r--src/libs/imageproperties/talbumlistview.cpp528
-rw-r--r--src/libs/imageproperties/talbumlistview.h109
-rw-r--r--src/libs/jpegutils/Makefile.am22
-rw-r--r--src/libs/jpegutils/jinclude.h90
-rw-r--r--src/libs/jpegutils/jpegint.h811
-rw-r--r--src/libs/jpegutils/jpegutils.cpp532
-rw-r--r--src/libs/jpegutils/jpegutils.h44
-rw-r--r--src/libs/jpegutils/libjpeg62.README385
-rw-r--r--src/libs/jpegutils/transupp.cpp2527
-rw-r--r--src/libs/jpegutils/transupp.h373
-rw-r--r--src/libs/levels/Makefile.am17
-rw-r--r--src/libs/levels/imagelevels.cpp715
-rw-r--r--src/libs/levels/imagelevels.h105
-rw-r--r--src/libs/lprof/Makefile.am15
-rw-r--r--src/libs/lprof/cmshull.cpp1480
-rw-r--r--src/libs/lprof/cmslm.cpp288
-rw-r--r--src/libs/lprof/cmslnr.cpp560
-rw-r--r--src/libs/lprof/cmsmatn.cpp323
-rw-r--r--src/libs/lprof/cmsmkmsh.cpp346
-rw-r--r--src/libs/lprof/cmsmntr.cpp371
-rw-r--r--src/libs/lprof/cmsoutl.cpp284
-rw-r--r--src/libs/lprof/cmspcoll.cpp1045
-rw-r--r--src/libs/lprof/cmsprf.cpp439
-rw-r--r--src/libs/lprof/cmsreg.cpp558
-rw-r--r--src/libs/lprof/cmsscn.cpp422
-rw-r--r--src/libs/lprof/cmssheet.cpp1746
-rw-r--r--src/libs/lprof/lcmsprf.h485
-rw-r--r--src/libs/sqlite2/Makefile.am43
-rw-r--r--src/libs/sqlite2/README2
-rw-r--r--src/libs/sqlite2/attach.c311
-rw-r--r--src/libs/sqlite2/auth.c219
-rw-r--r--src/libs/sqlite2/btree.c3584
-rw-r--r--src/libs/sqlite2/btree.h156
-rw-r--r--src/libs/sqlite2/btree_rb.c1488
-rw-r--r--src/libs/sqlite2/build.c2156
-rw-r--r--src/libs/sqlite2/copy.c110
-rw-r--r--src/libs/sqlite2/date.c875
-rw-r--r--src/libs/sqlite2/delete.c393
-rw-r--r--src/libs/sqlite2/encode.c257
-rw-r--r--src/libs/sqlite2/expr.c1662
-rw-r--r--src/libs/sqlite2/func.c658
-rw-r--r--src/libs/sqlite2/hash.c356
-rw-r--r--src/libs/sqlite2/hash.h109
-rw-r--r--src/libs/sqlite2/insert.c919
-rw-r--r--src/libs/sqlite2/main.c1143
-rw-r--r--src/libs/sqlite2/opcodes.c140
-rw-r--r--src/libs/sqlite2/opcodes.h138
-rw-r--r--src/libs/sqlite2/os.c1850
-rw-r--r--src/libs/sqlite2/os.h191
-rw-r--r--src/libs/sqlite2/pager.c2220
-rw-r--r--src/libs/sqlite2/pager.h107
-rw-r--r--src/libs/sqlite2/parse.c4035
-rw-r--r--src/libs/sqlite2/parse.h130
-rw-r--r--src/libs/sqlite2/pragma.c712
-rw-r--r--src/libs/sqlite2/printf.c858
-rw-r--r--src/libs/sqlite2/random.c97
-rw-r--r--src/libs/sqlite2/select.c2434
-rw-r--r--src/libs/sqlite2/shell.c1354
-rw-r--r--src/libs/sqlite2/sqlite.h872
-rw-r--r--src/libs/sqlite2/sqliteInt.h1274
-rw-r--r--src/libs/sqlite2/table.c203
-rw-r--r--src/libs/sqlite2/tokenize.c679
-rw-r--r--src/libs/sqlite2/trigger.c764
-rw-r--r--src/libs/sqlite2/update.c459
-rw-r--r--src/libs/sqlite2/util.c1134
-rw-r--r--src/libs/sqlite2/vacuum.c305
-rw-r--r--src/libs/sqlite2/vdbe.c4921
-rw-r--r--src/libs/sqlite2/vdbe.h112
-rw-r--r--src/libs/sqlite2/vdbeInt.h303
-rw-r--r--src/libs/sqlite2/vdbeaux.c1061
-rw-r--r--src/libs/sqlite2/where.c1235
-rw-r--r--src/libs/sqlite3/Makefile.am9
-rw-r--r--src/libs/sqlite3/sqlite3.c86994
-rw-r--r--src/libs/sqlite3/sqlite3.h5638
-rw-r--r--src/libs/themeengine/Makefile.am12
-rw-r--r--src/libs/themeengine/texture.cpp495
-rw-r--r--src/libs/themeengine/texture.h83
-rw-r--r--src/libs/themeengine/theme.cpp181
-rw-r--r--src/libs/themeengine/theme.h112
-rw-r--r--src/libs/themeengine/themeengine.cpp1132
-rw-r--r--src/libs/themeengine/themeengine.h107
-rw-r--r--src/libs/threadimageio/Makefile.am23
-rw-r--r--src/libs/threadimageio/loadingcache.cpp256
-rw-r--r--src/libs/threadimageio/loadingcache.h135
-rw-r--r--src/libs/threadimageio/loadingcacheinterface.cpp73
-rw-r--r--src/libs/threadimageio/loadingcacheinterface.h56
-rw-r--r--src/libs/threadimageio/loadingdescription.cpp152
-rw-r--r--src/libs/threadimageio/loadingdescription.h135
-rw-r--r--src/libs/threadimageio/loadsavetask.cpp424
-rw-r--r--src/libs/threadimageio/loadsavetask.h360
-rw-r--r--src/libs/threadimageio/loadsavethread.cpp331
-rw-r--r--src/libs/threadimageio/loadsavethread.h184
-rw-r--r--src/libs/threadimageio/managedloadsavethread.cpp362
-rw-r--r--src/libs/threadimageio/managedloadsavethread.h134
-rw-r--r--src/libs/threadimageio/previewloadthread.cpp52
-rw-r--r--src/libs/threadimageio/previewloadthread.h57
-rw-r--r--src/libs/threadimageio/previewtask.cpp258
-rw-r--r--src/libs/threadimageio/previewtask.h57
-rw-r--r--src/libs/threadimageio/sharedloadsavethread.cpp65
-rw-r--r--src/libs/threadimageio/sharedloadsavethread.h43
-rw-r--r--src/libs/thumbbar/Makefile.am18
-rw-r--r--src/libs/thumbbar/thumbbar.cpp1138
-rw-r--r--src/libs/thumbbar/thumbbar.h239
-rw-r--r--src/libs/thumbbar/thumbnailjob.cpp318
-rw-r--r--src/libs/thumbbar/thumbnailjob.h88
-rw-r--r--src/libs/whitebalance/Makefile.am16
-rw-r--r--src/libs/whitebalance/blackbody.h539
-rw-r--r--src/libs/whitebalance/whitebalance.cpp382
-rw-r--r--src/libs/whitebalance/whitebalance.h72
-rw-r--r--src/libs/widgets/Makefile.am12
-rw-r--r--src/libs/widgets/common/Makefile.am25
-rw-r--r--src/libs/widgets/common/colorgradientwidget.cpp161
-rw-r--r--src/libs/widgets/common/colorgradientwidget.h73
-rw-r--r--src/libs/widgets/common/curveswidget.cpp838
-rw-r--r--src/libs/widgets/common/curveswidget.h132
-rw-r--r--src/libs/widgets/common/dcursortracker.cpp109
-rw-r--r--src/libs/widgets/common/dcursortracker.h76
-rw-r--r--src/libs/widgets/common/dlogoaction.cpp96
-rw-r--r--src/libs/widgets/common/dlogoaction.h56
-rw-r--r--src/libs/widgets/common/dpopupmenu.cpp197
-rw-r--r--src/libs/widgets/common/dpopupmenu.h83
-rw-r--r--src/libs/widgets/common/filesaveoptionsbox.cpp182
-rw-r--r--src/libs/widgets/common/filesaveoptionsbox.h72
-rw-r--r--src/libs/widgets/common/histogramwidget.cpp1089
-rw-r--r--src/libs/widgets/common/histogramwidget.h177
-rw-r--r--src/libs/widgets/common/paniconwidget.cpp324
-rw-r--r--src/libs/widgets/common/paniconwidget.h120
-rw-r--r--src/libs/widgets/common/previewwidget.cpp640
-rw-r--r--src/libs/widgets/common/previewwidget.h131
-rw-r--r--src/libs/widgets/common/searchtextbar.cpp260
-rw-r--r--src/libs/widgets/common/searchtextbar.h111
-rw-r--r--src/libs/widgets/common/sidebar.cpp363
-rw-r--r--src/libs/widgets/common/sidebar.h178
-rw-r--r--src/libs/widgets/common/splashscreen.cpp160
-rw-r--r--src/libs/widgets/common/splashscreen.h74
-rw-r--r--src/libs/widgets/common/squeezedcombobox.cpp198
-rw-r--r--src/libs/widgets/common/squeezedcombobox.h164
-rw-r--r--src/libs/widgets/common/statusled.cpp84
-rw-r--r--src/libs/widgets/common/statusled.h72
-rw-r--r--src/libs/widgets/common/statusnavigatebar.cpp172
-rw-r--r--src/libs/widgets/common/statusnavigatebar.h80
-rw-r--r--src/libs/widgets/common/statusprogressbar.cpp171
-rw-r--r--src/libs/widgets/common/statusprogressbar.h87
-rw-r--r--src/libs/widgets/common/statuszoombar.cpp208
-rw-r--r--src/libs/widgets/common/statuszoombar.h100
-rw-r--r--src/libs/widgets/iccprofiles/Makefile.am23
-rw-r--r--src/libs/widgets/iccprofiles/cietonguewidget.cpp816
-rw-r--r--src/libs/widgets/iccprofiles/cietonguewidget.h115
-rw-r--r--src/libs/widgets/iccprofiles/iccpreviewwidget.cpp83
-rw-r--r--src/libs/widgets/iccprofiles/iccpreviewwidget.h71
-rw-r--r--src/libs/widgets/iccprofiles/iccprofilewidget.cpp448
-rw-r--r--src/libs/widgets/iccprofiles/iccprofilewidget.h79
-rw-r--r--src/libs/widgets/imageplugins/Makefile.am22
-rw-r--r--src/libs/widgets/imageplugins/imageguidewidget.cpp625
-rw-r--r--src/libs/widgets/imageplugins/imageguidewidget.h132
-rw-r--r--src/libs/widgets/imageplugins/imagepanelwidget.cpp335
-rw-r--r--src/libs/widgets/imageplugins/imagepanelwidget.h117
-rw-r--r--src/libs/widgets/imageplugins/imagepaniconwidget.cpp198
-rw-r--r--src/libs/widgets/imageplugins/imagepaniconwidget.h68
-rw-r--r--src/libs/widgets/imageplugins/imagepannelwidget.cpp477
-rw-r--r--src/libs/widgets/imageplugins/imagepannelwidget.h123
-rw-r--r--src/libs/widgets/imageplugins/imageregionwidget.cpp473
-rw-r--r--src/libs/widgets/imageplugins/imageregionwidget.h115
-rw-r--r--src/libs/widgets/imageplugins/imagewidget.cpp347
-rw-r--r--src/libs/widgets/imageplugins/imagewidget.h106
-rw-r--r--src/libs/widgets/imageplugins/listboxpreviewitem.cpp62
-rw-r--r--src/libs/widgets/imageplugins/listboxpreviewitem.h80
-rw-r--r--src/libs/widgets/metadata/Makefile.am17
-rw-r--r--src/libs/widgets/metadata/exifwidget.cpp185
-rw-r--r--src/libs/widgets/metadata/exifwidget.h73
-rw-r--r--src/libs/widgets/metadata/gpswidget.cpp340
-rw-r--r--src/libs/widgets/metadata/gpswidget.h95
-rw-r--r--src/libs/widgets/metadata/iptcwidget.cpp167
-rw-r--r--src/libs/widgets/metadata/iptcwidget.h69
-rw-r--r--src/libs/widgets/metadata/makernotewidget.cpp210
-rw-r--r--src/libs/widgets/metadata/makernotewidget.h70
-rw-r--r--src/libs/widgets/metadata/mdkeylistviewitem.cpp94
-rw-r--r--src/libs/widgets/metadata/mdkeylistviewitem.h63
-rw-r--r--src/libs/widgets/metadata/metadatalistview.cpp283
-rw-r--r--src/libs/widgets/metadata/metadatalistview.h85
-rw-r--r--src/libs/widgets/metadata/metadatalistviewitem.cpp75
-rw-r--r--src/libs/widgets/metadata/metadatalistviewitem.h59
-rw-r--r--src/libs/widgets/metadata/metadatawidget.cpp454
-rw-r--r--src/libs/widgets/metadata/metadatawidget.h120
-rw-r--r--src/libs/widgets/metadata/worldmapwidget.cpp211
-rw-r--r--src/libs/widgets/metadata/worldmapwidget.h73
-rw-r--r--src/showfoto/Makefile.am42
-rw-r--r--src/showfoto/main.cpp95
-rw-r--r--src/showfoto/setup/Makefile.am14
-rw-r--r--src/showfoto/setup/setup.cpp153
-rw-r--r--src/showfoto/setup/setup.h78
-rw-r--r--src/showfoto/setup/setupeditor.cpp246
-rw-r--r--src/showfoto/setup/setupeditor.h63
-rw-r--r--src/showfoto/setup/setuptooltip.cpp228
-rw-r--r--src/showfoto/setup/setuptooltip.h59
-rw-r--r--src/showfoto/showfoto.cpp1240
-rw-r--r--src/showfoto/showfoto.desktop151
-rw-r--r--src/showfoto/showfoto.h135
-rw-r--r--src/showfoto/showfotoui.rc131
-rw-r--r--src/tdeioslave/Makefile.am72
-rw-r--r--src/tdeioslave/digikamalbums.cpp1969
-rw-r--r--src/tdeioslave/digikamalbums.h108
-rw-r--r--src/tdeioslave/digikamalbums.protocol45
-rw-r--r--src/tdeioslave/digikamdates.cpp313
-rw-r--r--src/tdeioslave/digikamdates.h54
-rw-r--r--src/tdeioslave/digikamdates.protocol44
-rw-r--r--src/tdeioslave/digikamsearch.cpp734
-rw-r--r--src/tdeioslave/digikamsearch.h100
-rw-r--r--src/tdeioslave/digikamsearch.protocol38
-rw-r--r--src/tdeioslave/digikamtags.cpp325
-rw-r--r--src/tdeioslave/digikamtags.h60
-rw-r--r--src/tdeioslave/digikamtags.protocol44
-rw-r--r--src/tdeioslave/digikamthumbnail.cpp635
-rw-r--r--src/tdeioslave/digikamthumbnail.h76
-rw-r--r--src/tdeioslave/digikamthumbnail.protocol38
-rw-r--r--src/tdeioslave/sqlitedb.cpp197
-rw-r--r--src/tdeioslave/sqlitedb.h58
-rw-r--r--src/themedesigner/Makefile.am19
-rw-r--r--src/themedesigner/main.cpp75
-rw-r--r--src/themedesigner/mainwindow.cpp585
-rw-r--r--src/themedesigner/mainwindow.h125
-rw-r--r--src/themedesigner/themedicongroupitem.cpp105
-rw-r--r--src/themedesigner/themedicongroupitem.h54
-rw-r--r--src/themedesigner/themediconitem.cpp195
-rw-r--r--src/themedesigner/themediconitem.h48
-rw-r--r--src/themedesigner/themediconview.cpp304
-rw-r--r--src/themedesigner/themediconview.h86
-rw-r--r--src/tips267
-rw-r--r--src/utilities/Makefile.am1
-rw-r--r--src/utilities/batch/Makefile.am20
-rw-r--r--src/utilities/batch/batchalbumssyncmetadata.cpp183
-rw-r--r--src/utilities/batch/batchalbumssyncmetadata.h82
-rw-r--r--src/utilities/batch/batchsyncmetadata.cpp166
-rw-r--r--src/utilities/batch/batchsyncmetadata.h89
-rw-r--r--src/utilities/batch/batchthumbsgenerator.cpp233
-rw-r--r--src/utilities/batch/batchthumbsgenerator.h83
-rw-r--r--src/utilities/batch/imageinfoalbumsjob.cpp125
-rw-r--r--src/utilities/batch/imageinfoalbumsjob.h80
-rw-r--r--src/utilities/batch/imageinfojob.cpp163
-rw-r--r--src/utilities/batch/imageinfojob.h78
-rw-r--r--src/utilities/cameragui/Makefile.am30
-rw-r--r--src/utilities/cameragui/albumselectdialog.cpp417
-rw-r--r--src/utilities/cameragui/albumselectdialog.h80
-rw-r--r--src/utilities/cameragui/animwidget.cpp131
-rw-r--r--src/utilities/cameragui/animwidget.h66
-rw-r--r--src/utilities/cameragui/cameracontroller.cpp1227
-rw-r--r--src/utilities/cameragui/cameracontroller.h111
-rw-r--r--src/utilities/cameragui/camerafolderdialog.cpp138
-rw-r--r--src/utilities/cameragui/camerafolderdialog.h68
-rw-r--r--src/utilities/cameragui/camerafolderitem.cpp108
-rw-r--r--src/utilities/cameragui/camerafolderitem.h69
-rw-r--r--src/utilities/cameragui/camerafolderview.cpp169
-rw-r--r--src/utilities/cameragui/camerafolderview.h83
-rw-r--r--src/utilities/cameragui/cameraiconitem.cpp302
-rw-r--r--src/utilities/cameragui/cameraiconitem.h81
-rw-r--r--src/utilities/cameragui/cameraiconview.cpp900
-rw-r--r--src/utilities/cameragui/cameraiconview.h141
-rw-r--r--src/utilities/cameragui/camerainfodialog.cpp85
-rw-r--r--src/utilities/cameragui/camerainfodialog.h50
-rw-r--r--src/utilities/cameragui/cameraui.cpp1734
-rw-r--r--src/utilities/cameragui/cameraui.h155
-rw-r--r--src/utilities/cameragui/dkcamera.cpp113
-rw-r--r--src/utilities/cameragui/dkcamera.h97
-rw-r--r--src/utilities/cameragui/downloadsettingscontainer.h79
-rw-r--r--src/utilities/cameragui/freespacewidget.cpp252
-rw-r--r--src/utilities/cameragui/freespacewidget.h75
-rw-r--r--src/utilities/cameragui/gpcamera.cpp1223
-rw-r--r--src/utilities/cameragui/gpcamera.h107
-rw-r--r--src/utilities/cameragui/gpiteminfo.cpp68
-rw-r--r--src/utilities/cameragui/gpiteminfo.h80
-rw-r--r--src/utilities/cameragui/mtqueue.h116
-rw-r--r--src/utilities/cameragui/renamecustomizer.cpp532
-rw-r--r--src/utilities/cameragui/renamecustomizer.h91
-rw-r--r--src/utilities/cameragui/umscamera.cpp519
-rw-r--r--src/utilities/cameragui/umscamera.h78
-rw-r--r--src/utilities/hotplug/Makefile.am7
-rw-r--r--src/utilities/hotplug/configure.in.in7
-rwxr-xr-xsrc/utilities/hotplug/digikam-camera40
-rw-r--r--src/utilities/hotplug/digikam-download.desktop.in27
-rw-r--r--src/utilities/hotplug/digikam-gphoto2-camera.desktop.in27
-rw-r--r--src/utilities/hotplug/digikam-mount-and-download.desktop.in27
-rw-r--r--src/utilities/imageeditor/Makefile.am1
-rw-r--r--src/utilities/imageeditor/canvas/Makefile.am28
-rw-r--r--src/utilities/imageeditor/canvas/canvas.cpp1421
-rw-r--r--src/utilities/imageeditor/canvas/canvas.h209
-rw-r--r--src/utilities/imageeditor/canvas/colorcorrectiondlg.cpp186
-rw-r--r--src/utilities/imageeditor/canvas/colorcorrectiondlg.h72
-rw-r--r--src/utilities/imageeditor/canvas/dimginterface.cpp1270
-rw-r--r--src/utilities/imageeditor/canvas/dimginterface.h200
-rw-r--r--src/utilities/imageeditor/canvas/iccsettingscontainer.h82
-rw-r--r--src/utilities/imageeditor/canvas/imageplugin.cpp68
-rw-r--r--src/utilities/imageeditor/canvas/imageplugin.h70
-rw-r--r--src/utilities/imageeditor/canvas/imagepluginloader.cpp278
-rw-r--r--src/utilities/imageeditor/canvas/imagepluginloader.h79
-rw-r--r--src/utilities/imageeditor/canvas/iofilesettingscontainer.h84
-rw-r--r--src/utilities/imageeditor/canvas/undoaction.cpp185
-rw-r--r--src/utilities/imageeditor/canvas/undoaction.h144
-rw-r--r--src/utilities/imageeditor/canvas/undocache.cpp178
-rw-r--r--src/utilities/imageeditor/canvas/undocache.h62
-rw-r--r--src/utilities/imageeditor/canvas/undomanager.cpp253
-rw-r--r--src/utilities/imageeditor/canvas/undomanager.h76
-rw-r--r--src/utilities/imageeditor/editor/Makefile.am51
-rw-r--r--src/utilities/imageeditor/editor/digikamimageplugin.desktop39
-rw-r--r--src/utilities/imageeditor/editor/digikamimagewindowui.rc125
-rw-r--r--src/utilities/imageeditor/editor/editorstackview.cpp233
-rw-r--r--src/utilities/imageeditor/editor/editorstackview.h97
-rw-r--r--src/utilities/imageeditor/editor/editortool.cpp436
-rw-r--r--src/utilities/imageeditor/editor/editortool.h164
-rw-r--r--src/utilities/imageeditor/editor/editortooliface.cpp147
-rw-r--r--src/utilities/imageeditor/editor/editortooliface.h76
-rw-r--r--src/utilities/imageeditor/editor/editortoolsettings.cpp331
-rw-r--r--src/utilities/imageeditor/editor/editortoolsettings.h110
-rw-r--r--src/utilities/imageeditor/editor/editorwindow.cpp1932
-rw-r--r--src/utilities/imageeditor/editor/editorwindow.h263
-rw-r--r--src/utilities/imageeditor/editor/editorwindowprivate.h143
-rw-r--r--src/utilities/imageeditor/editor/imageiface.cpp444
-rw-r--r--src/utilities/imageeditor/editor/imageiface.h198
-rw-r--r--src/utilities/imageeditor/editor/imagewindow.cpp1263
-rw-r--r--src/utilities/imageeditor/editor/imagewindow.h155
-rw-r--r--src/utilities/imageeditor/editor/savingcontextcontainer.h89
-rw-r--r--src/utilities/imageeditor/rawimport/Makefile.am27
-rw-r--r--src/utilities/imageeditor/rawimport/rawimport.cpp223
-rw-r--r--src/utilities/imageeditor/rawimport/rawimport.h88
-rw-r--r--src/utilities/imageeditor/rawimport/rawpostprocessing.cpp137
-rw-r--r--src/utilities/imageeditor/rawimport/rawpostprocessing.h63
-rw-r--r--src/utilities/imageeditor/rawimport/rawpreview.cpp336
-rw-r--r--src/utilities/imageeditor/rawimport/rawpreview.h108
-rw-r--r--src/utilities/imageeditor/rawimport/rawsettingsbox.cpp741
-rw-r--r--src/utilities/imageeditor/rawimport/rawsettingsbox.h91
-rw-r--r--src/utilities/imageeditor/tools/Makefile.am19
-rw-r--r--src/utilities/imageeditor/tools/imageprint.cpp814
-rw-r--r--src/utilities/imageeditor/tools/imageprint.h122
-rw-r--r--src/utilities/imageeditor/tools/imageresize.cpp650
-rw-r--r--src/utilities/imageeditor/tools/imageresize.h82
-rw-r--r--src/utilities/lighttable/Makefile.am28
-rw-r--r--src/utilities/lighttable/lighttablebar.cpp881
-rw-r--r--src/utilities/lighttable/lighttablebar.h153
-rw-r--r--src/utilities/lighttable/lighttablepreview.cpp777
-rw-r--r--src/utilities/lighttable/lighttablepreview.h125
-rw-r--r--src/utilities/lighttable/lighttableview.cpp446
-rw-r--r--src/utilities/lighttable/lighttableview.h137
-rw-r--r--src/utilities/lighttable/lighttablewindow.cpp1676
-rw-r--r--src/utilities/lighttable/lighttablewindow.h162
-rw-r--r--src/utilities/lighttable/lighttablewindowprivate.h158
-rw-r--r--src/utilities/lighttable/lighttablewindowui.rc83
-rw-r--r--src/utilities/scripts/Makefile.am4
-rw-r--r--src/utilities/scripts/digitaglinktree568
-rw-r--r--src/utilities/scripts/digitaglinktree.1182
-rw-r--r--src/utilities/setup/Makefile.am29
-rw-r--r--src/utilities/setup/cameraselection.cpp494
-rw-r--r--src/utilities/setup/cameraselection.h88
-rw-r--r--src/utilities/setup/setup.cpp266
-rw-r--r--src/utilities/setup/setup.h81
-rw-r--r--src/utilities/setup/setupcamera.cpp315
-rw-r--r--src/utilities/setup/setupcamera.h73
-rw-r--r--src/utilities/setup/setupcollections.cpp218
-rw-r--r--src/utilities/setup/setupcollections.h66
-rw-r--r--src/utilities/setup/setupdcraw.cpp150
-rw-r--r--src/utilities/setup/setupdcraw.h67
-rw-r--r--src/utilities/setup/setupeditor.cpp176
-rw-r--r--src/utilities/setup/setupeditor.h63
-rw-r--r--src/utilities/setup/setupgeneral.cpp313
-rw-r--r--src/utilities/setup/setupgeneral.h67
-rw-r--r--src/utilities/setup/setupicc.cpp727
-rw-r--r--src/utilities/setup/setupicc.h86
-rw-r--r--src/utilities/setup/setupidentity.cpp217
-rw-r--r--src/utilities/setup/setupidentity.h60
-rw-r--r--src/utilities/setup/setupiofiles.cpp137
-rw-r--r--src/utilities/setup/setupiofiles.h63
-rw-r--r--src/utilities/setup/setuplighttable.cpp133
-rw-r--r--src/utilities/setup/setuplighttable.h59
-rw-r--r--src/utilities/setup/setupmetadata.cpp238
-rw-r--r--src/utilities/setup/setupmetadata.h67
-rw-r--r--src/utilities/setup/setupmime.cpp280
-rw-r--r--src/utilities/setup/setupmime.h66
-rw-r--r--src/utilities/setup/setupmisc.cpp124
-rw-r--r--src/utilities/setup/setupmisc.h58
-rw-r--r--src/utilities/setup/setupplugins.cpp104
-rw-r--r--src/utilities/setup/setupplugins.h56
-rw-r--r--src/utilities/setup/setupslideshow.cpp165
-rw-r--r--src/utilities/setup/setupslideshow.h64
-rw-r--r--src/utilities/setup/setuptooltip.cpp272
-rw-r--r--src/utilities/setup/setuptooltip.h59
-rw-r--r--src/utilities/slideshow/Makefile.am17
-rw-r--r--src/utilities/slideshow/slideshow.cpp679
-rw-r--r--src/utilities/slideshow/slideshow.h91
-rw-r--r--src/utilities/slideshow/slideshowsettings.h125
-rw-r--r--src/utilities/slideshow/toolbar.cpp217
-rw-r--r--src/utilities/slideshow/toolbar.h85
1087 files changed, 410276 insertions, 0 deletions
diff --git a/src/CMakeL10n.txt b/src/CMakeL10n.txt
new file mode 100644
index 00000000..b326219c
--- /dev/null
+++ b/src/CMakeL10n.txt
@@ -0,0 +1,6 @@
+##### create translation templates ##############
+
+tde_l10n_create_template(
+ CATALOG "messages/digikam"
+ SOURCES "." "tips"
+)
diff --git a/src/COPYING-DOCS b/src/COPYING-DOCS
new file mode 100644
index 00000000..4a0fe1c8
--- /dev/null
+++ b/src/COPYING-DOCS
@@ -0,0 +1,397 @@
+ GNU Free Documentation License
+ Version 1.2, November 2002
+
+
+ Copyright (C) 2000,2001,2002 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+0. PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document "free" in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of "copyleft", which means that derivative
+works of the document must themselves be free in the same sense. It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does. But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book. We recommend this License
+principally for works whose purpose is instruction or reference.
+
+
+1. APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License. Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein. The "Document", below,
+refers to any such manual or work. Any member of the public is a
+licensee, and is addressed as "you". You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A "Modified Version" of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A "Secondary Section" is a named appendix or a front-matter section of
+the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall subject
+(or to related matters) and contains nothing that could fall directly
+within that overall subject. (Thus, if the Document is in part a
+textbook of mathematics, a Secondary Section may not explain any
+mathematics.) The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The "Invariant Sections" are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License. If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant. The Document may contain zero
+Invariant Sections. If the Document does not identify any Invariant
+Sections then there are none.
+
+The "Cover Texts" are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License. A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A "Transparent" copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters. A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text. A copy that is not "Transparent" is called "Opaque".
+
+Examples of suitable formats for Transparent copies include plain
+ASCII without markup, Texinfo input format, LaTeX input format, SGML
+or XML using a publicly available DTD, and standard-conforming simple
+HTML, PostScript or PDF designed for human modification. Examples of
+transparent image formats include PNG, XCF and JPG. Opaque formats
+include proprietary formats that can be read and edited only by
+proprietary word processors, SGML or XML for which the DTD and/or
+processing tools are not generally available, and the
+machine-generated HTML, PostScript or PDF produced by some word
+processors for output purposes only.
+
+The "Title Page" means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page. For works in
+formats which do not have any title page as such, "Title Page" means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+A section "Entitled XYZ" means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language. (Here XYZ stands for a
+specific section name mentioned below, such as "Acknowledgements",
+"Dedications", "Endorsements", or "History".) To "Preserve the Title"
+of such a section when you modify the Document means that it remains a
+section "Entitled XYZ" according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document. These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+
+2. VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no other
+conditions whatsoever to those of this License. You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute. However, you may accept
+compensation in exchange for copies. If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+
+3. COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover. Both covers must also clearly and legibly identify
+you as the publisher of these copies. The front cover must present
+the full title with all words of the title equally prominent and
+visible. You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to give
+them a chance to provide you with an updated version of the Document.
+
+
+4. MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it. In addition, you must do these things in the Modified Version:
+
+A. Use in the Title Page (and on the covers, if any) a title distinct
+ from that of the Document, and from those of previous versions
+ (which should, if there were any, be listed in the History section
+ of the Document). You may use the same title as a previous version
+ if the original publisher of that version gives permission.
+B. List on the Title Page, as authors, one or more persons or entities
+ responsible for authorship of the modifications in the Modified
+ Version, together with at least five of the principal authors of the
+ Document (all of its principal authors, if it has fewer than five),
+ unless they release you from this requirement.
+C. State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.
+D. Preserve all the copyright notices of the Document.
+E. Add an appropriate copyright notice for your modifications
+ adjacent to the other copyright notices.
+F. Include, immediately after the copyright notices, a license notice
+ giving the public permission to use the Modified Version under the
+ terms of this License, in the form shown in the Addendum below.
+G. Preserve in that license notice the full lists of Invariant Sections
+ and required Cover Texts given in the Document's license notice.
+H. Include an unaltered copy of this License.
+I. Preserve the section Entitled "History", Preserve its Title, and add
+ to it an item stating at least the title, year, new authors, and
+ publisher of the Modified Version as given on the Title Page. If
+ there is no section Entitled "History" in the Document, create one
+ stating the title, year, authors, and publisher of the Document as
+ given on its Title Page, then add an item describing the Modified
+ Version as stated in the previous sentence.
+J. Preserve the network location, if any, given in the Document for
+ public access to a Transparent copy of the Document, and likewise
+ the network locations given in the Document for previous versions
+ it was based on. These may be placed in the "History" section.
+ You may omit a network location for a work that was published at
+ least four years before the Document itself, or if the original
+ publisher of the version it refers to gives permission.
+K. For any section Entitled "Acknowledgements" or "Dedications",
+ Preserve the Title of the section, and preserve in the section all
+ the substance and tone of each of the contributor acknowledgements
+ and/or dedications given therein.
+L. Preserve all the Invariant Sections of the Document,
+ unaltered in their text and in their titles. Section numbers
+ or the equivalent are not considered part of the section titles.
+M. Delete any section Entitled "Endorsements". Such a section
+ may not be included in the Modified Version.
+N. Do not retitle any existing section to be Entitled "Endorsements"
+ or to conflict in title with any Invariant Section.
+O. Preserve any Warranty Disclaimers.
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant. To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled "Endorsements", provided it contains
+nothing but endorsements of your Modified Version by various
+parties--for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version. Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity. If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+
+5. COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy. If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled "History"
+in the various original documents, forming one section Entitled
+"History"; likewise combine any sections Entitled "Acknowledgements",
+and any sections Entitled "Dedications". You must delete all sections
+Entitled "Endorsements".
+
+
+6. COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other documents
+released under this License, and replace the individual copies of this
+License in the various documents with a single copy that is included in
+the collection, provided that you follow the rules of this License for
+verbatim copying of each of the documents in all other respects.
+
+You may extract a single document from such a collection, and distribute
+it individually under this License, provided you insert a copy of this
+License into the extracted document, and follow this License in all
+other respects regarding verbatim copying of that document.
+
+
+7. AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an "aggregate" if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included in an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+
+8. TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections. You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers. In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled "Acknowledgements",
+"Dedications", or "History", the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+
+9. TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document except
+as expressly provided for under this License. Any other attempt to
+copy, modify, sublicense or distribute the Document is void, and will
+automatically terminate your rights under this License. However,
+parties who have received copies, or rights, from you under this
+License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+
+10. FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions
+of the GNU Free Documentation License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns. See
+http://www.gnu.org/copyleft/.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation. If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation.
+
+
+ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+ Copyright (c) YEAR YOUR NAME.
+ Permission is granted to copy, distribute and/or modify this document
+ under the terms of the GNU Free Documentation License, Version 1.2
+ or any later version published by the Free Software Foundation;
+ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+ A copy of the license is included in the section entitled "GNU
+ Free Documentation License".
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the "with...Texts." line with this:
+
+ with the Invariant Sections being LIST THEIR TITLES, with the
+ Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
+
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
diff --git a/src/DBSCHEMA.ODS b/src/DBSCHEMA.ODS
new file mode 100644
index 00000000..4e016a4b
--- /dev/null
+++ b/src/DBSCHEMA.ODS
Binary files differ
diff --git a/src/DESIGN b/src/DESIGN
new file mode 100644
index 00000000..9b16c519
--- /dev/null
+++ b/src/DESIGN
@@ -0,0 +1,18 @@
+This file is ment to help people get started hacking on digiKam. It will get
+you up to speed on a couple of structures used. We only started to document
+just before digiKam 0.8, so don't expect to much, but whenever you hack some
+please update this file as well.
+
+scanlib
+Scanlib is a library that takes care of scanning the filesystem for new files
+and adds them in the database and checking for missing info in the database so
+that it can be included: if date is empty, it adds the exif or modification
+date (in that order) and the comment to database. If the file is not present
+in the database, make sure to add the file to the database and insert the date
+and comments.
+
+pixmapmanager
+Since there are date based folders, the number of pixmaps which could be
+kept in memory could potentially become too large. The pixmapmanager
+maintains a fixed size cache of thumbnails and loads pixmaps on demand.
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..4741808b
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,25 @@
+COMPILE_FIRST = libs
+
+SUBDIRS = libs utilities digikam showfoto tdeioslave imageplugins themedesigner
+
+EXTRA_DIST = AUTHORS COPYING ChangeLog INSTALL README TODO digikam.lsm
+
+AUTOMAKE_OPTIONS = foreign
+
+MAINTAINERCLEANFILES = subdirs configure.in acinclude.m4 configure.files
+
+
+tip_DATA = tips
+tipdir = $(kde_datadir)/digikam
+
+messages: rc.cpp
+ $(EXTRACTRC) `find . -name "*.ui"` >> rc.cpp
+ $(EXTRACTRC) `find . -name "*.rc"` >> rc.cpp
+ $(PREPARETIPS) > tips.cpp
+ LIST=`find . -name \*.h -o -name \*.hh -o -name \*.H -o -name \*.hxx -o -name \*.hpp -o -name \*.cpp -o -name \*.cc -o -name \*.cxx -o -name \*.ecpp -o -name \*.C`; \
+ if test -n "$$LIST"; then \
+ $(XGETTEXT) $$LIST -o $(podir)/digikam.pot; \
+ fi
+ rm -f tips.cpp
+
+include ../admin/Doxyfile.am
diff --git a/src/NEWS.0.9.0 b/src/NEWS.0.9.0
new file mode 100644
index 00000000..ad8e1cb1
--- /dev/null
+++ b/src/NEWS.0.9.0
@@ -0,0 +1,397 @@
+----------------------------------------------------------------------------------------------------
+digiKam and DigikamImagePlugins 0.9.0 - Release date: 2006-18-12
+
+NEW FEATURES :
+
+*** 0.9.0-beta1 *******************************************************
+
+General : Sidebars are used at all to display metadata, comments & tags, and file properties.
+General : Removed imlib2 and libkexif depencies. Add Exiv2 and LCMS depencies.
+General : New API in digiKam core named DImg to manage image data in 8 and 16 bits colors depth.
+General : New API in digiKam core named DMetadata to manage image metadata.
+General : Ported all image filter algorithms to new DImg API. Support of 16 bits images.
+General : New metadata viewers based on Exiv2 : Exif viewer, MakerNote viewer, IPTC viewer,
+ and GPS locator.
+General : New ICC profiles profile viewer based on LCMS.
+General : New powerful widget to display CIE color space used by ICC profiles.
+General : New implementation to load/save images file using a separate thread.
+General : Tags/Rating/DateTime/Comments/PhotographersID/Copyrights are now stored in Exif
+ and/or IPTC metadata tags.
+General : digiKam comments are now store in JPEG file to JFIF section, Exif UserComments tags
+ and Caption Iptc tags.
+General : New fast preview pictures mode embedded into main interface.
+
+Image Editor : Image Editor support 16 bits color depth images (RAW, TIFF, PNG, PNM).
+Image Editor : Complete rewrite of image editor/showfoto gui implementation. Implementation
+ is now common and is more easy to maintain!
+Image Editor : Add a progress bar to Image editor/showfoto about IO image files access.
+Image Editor : Support of ICC color profiles management using LCMS into Image editor/showfoto.
+Image Editor : digiKam setup is now available from Image Editor menu.
+Image Editor : TIFF and JPEG Exif thumbnail are recomputed at saving pictures.
+Image Editor : Iptc preview image are recomputed at saving pictures.
+
+Image Plugins : New powerfull Noise Reduction algorithm based on dcamnoise2 implementation.
+Image Plugins : New powerfull Black & White converter tool using curves adjustements.
+Image Plugins : New plugin to experiment ICC profiles to perform color corrections into images.
+Image Plugins : New preview rendering modes in all color correction image plugins dialogs.
+Image Plugins : Redesign/polishing of all image plugins dialogs. Histogram are available at all
+ when it's necessary. All dialogs used in DigikamImagePlugins are common in digiKam
+ core now.
+Image Plugins : New zooming capabilities in image plugins dialogs.
+Image Plugins : Ported all core image plugins to new DImg API. Support of 16 bits images.
+Image Plugins : Ported all DigikamImagePlugins tools to new DImg API. Support of 16 bits images.
+
+Setup : Advanced settings to load RAW files in Image Editor using external dcraw instance.
+Setup : Setup implementation are now common between Showfoto and digiKam.
+Setup : New Identity setup page to set default strings informations about Photographer and
+ copyright used when IPTC tags are updated.
+
+CameraGUI : New option to set metadata DateTime/PhotographerId/Copyright Exif and/or IPTC tags
+ on the fly.
+CameraGUI : JPEG Exif thumbnail and Iptc image preview are recomputed during Exif Auto-rotation.
+CameraGUI : digiKam theme support.
+
+Showfoto : Image properties sidebar support
+Showfoto : All images from a folder can be loaded at the same time.
+Showfoto : Add 16 bits/color/pixel support (DImg API).
+Showfoto : Thumbbar moved to the left by default in vertical mode. Image properties side bar to the
+ right to make a consistant GUI with digiKam main interface.
+Showfoto : Thumbbar can be used to the bottom in horizontal mode.
+Showfoto : New Exif Auto-rotate option like in digiKam ImageEditor.
+Showfoto : New Set Orientation Exif flag to normal after Rotate/Flip like in digiKam ImageEditor.
+
+*** 0.9.0-beta2 *******************************************************
+
+CameraGUI : Custom prefix index depand of current images selection in the camera icon items
+CameraGUI : New option to show camera summary and documentations of Gphoto2 drivers.
+CameraGUI : Option to upload pictures to camera using copy&paste or drag&drop.
+CameraGUI : New option to start number sequence index with a custom value.
+CameraGUI : New option to add postfix string to target download file name.
+CameraGUI : New option to add camera name to target download file name.
+CameraGUI : New option to increase/decrease the thumbnails size.
+CameraGUI : New option to lock/unlock pictures from camera to prevent unwanted deletion.
+CameraGUI : New option to convert JPEG files to lossless file format during download.
+CameraGUI : New option to set date format of auto-created albums.
+CameraGUI : New option to auto-create files extension-based sub-albums.
+
+Image Plugins : "Add Border" tool now respect aspect ratio of original image to render the decorative border.
+Image Plugins : "Add Border" tool now use a border size depending of a percent value of original image size.
+
+General : New dcraw version detection at startup.
+General : New option to force the refresh of Album contents.
+General : Usability improvements of albums/pictures deletion.
+General : New option to process a batch creation of all albums items thumbnails.
+General : New option to "Select All", "Deselect", or "Invert Selection" of Tags in Tags Filter View and
+ Comments and Tags side bar tabs.
+General : New option to set image as tag thumbnail on Tag Filters View using D&D.
+General : New option to set a matching condition to use between tags in Tag Filters View
+ (OR or AND condition).
+
+Setup : The new batch thumbnail generator can be started when the Exif Auto-rotate option
+ has been changed.
+Setup : New Album Items Tool Tip setup tab to set witch information to show over the images
+ using the pop-up window.
+Setup : New color sheme theme 'Digikasa' from Sergio Di Rio Mare.
+
+*** 0.9.0-beta3 *******************************************************
+
+Setup : New dcraw options set compatible with dcraw release >= 8.16.
+
+General : Automatic image Tags creation/set using IPTC Keywords contents.
+General : New kipi plugin to set pictures GPS informations using a GPS device (available with
+ next kipi-plugins 0.1.3).
+General : Removing external dcraw depency. Now digiKam include a full supported version of
+ dcraw program in core.
+
+CameraGUI : New advanced config dialog to set date/time format used to rename camera pictures
+ file name during download.
+
+*** 0.9.0-rc1 **********************************************************
+
+General : dcraw implementation updated to version 8.41.
+General : New kipi plugin to edit EXIF and IPTC metadata (available with next kipi-plugins 0.1.3).
+
+*** 0.9.0-rc2 **********************************************************
+
+General : Exiv2 depency fixed to 0.12 release.
+General : Fix broken compilation using "./configure -enable-final" option.
+
+ImageEditor : Fixed and improved Color Management View workflow.
+
+*** 0.9.0-final *********************************************************
+
+General : About final release packaging, improve support of "./configure --enable-final"
+ option everywhere.
+
+
+----------------------------------------------------------------------------------------------------
+digiKam and DigikamImagePlugins BUGFIX FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+*** 0.9.0-beta1 *******************************************************
+
+001 ==> 87823 : Displaying images with ICC color profile when possible.
+002 ==> 91811 : Find pictures by meta tag info, date.
+003 ==> 91812 : Viewer/editor for IPTC meta data.
+004 ==> 96459 : EXIF for RAW camera images.
+005 ==> 116248 : Ask user which plugins should be enabled on first startup.
+006 ==> 115441 : Resize EXIF-rotated images doesn't work.
+007 ==> 115460 : Opening and closing right pane with tag filter also changes width of left pane and vv.
+008 ==> 124688 : digiKam loading time is big because of : WARNING: Failed to find parent for Tag.
+009 ==> 109253 : digiKam does not store comments in image EXIF tags.
+010 ==> 115764 : Modify EXIF creation date.
+011 ==> 121487 : "Tip of the day" text gets cut off.
+012 ==> 120052 : Redraw problem with whatthis info of an image show and start scrolling with the mouse.
+013 ==> 120053 : Whatthis info not closed when albumview is scrolled up or down with cursor keys.
+014 ==> 123522 : Minolta's RAW files (mrw) aren't show in the view window.
+015 ==> 121905 : Albums not displayed correctly and digiKam crashes with St9bad_alloc.
+016 ==> 113997 : Option to store tags in image.
+017 ==> 123742 : Preview-pictures seem to be handled differently by digiKam and Konqueror.
+018 ==> 121308 : Mass storage and PTP usability.
+019 ==> 121306 : Add copyright/license during download.
+020 ==> 111560 : Be able to locate photos on a map.
+021 ==> 122747 : Will not build with GCC 4.1.
+022 ==> 103176 : 16bit/channel framework for digiKam.
+023 ==> 109096 : Provide quick access to image comment.
+024 ==> 109817 : Feature request for image properties dialog in image editor.
+025 ==> 109992 : Preview in comments editor resizeable.
+026 ==> 113103 : Show digiKam comments into digiKam image editor.
+027 ==> 124939 : Improve import picture nomenclature.
+028 ==> 113915 : Tags are lost after "Save As..."ing.
+029 ==> 103245 : Image Effects are inaccessible from image editor.
+030 ==> 125589 : Saving user preferences not possible.
+031 ==> 103201 : Easy transport of albums, including tags, comments, etc.
+032 ==> 119234 : Window views not updated when tag name changed in "Tag Filters".
+033 ==> 103255 : Wish: *add* (not edit) EXIF headers like date, comment etc.
+034 ==> 121800 : Can't setup camera correctly.
+035 ==> 121784 : Accessing the camera in mass storage mode just lists jpegs and no other filetypes.
+036 ==> 121371 : Missing display of EXIF infos in "Simple view".
+037 ==> 123646 : Rating image with a keyboard shortcut.
+038 ==> 119946 : Thumbnails not correctly rotated according to exif information.
+039 ==> 106103 : Should support adding GPS EXIF header to images.
+040 ==> 110598 : Synchronize digiKam comments to existing embedded comments.
+041 ==> 118501 : Exifs lost when modifying the images.
+042 ==> 122264 : Exif data fail to show up in recent SVN.
+043 ==> 103489 : Sort and re-order EXIF information dialogs.
+044 ==> 105670 : Option to print EXIF data with images.
+045 ==> 109319 : Full EXIF info - exiftags style.
+046 ==> 120963 : Optional album date change.
+047 ==> 121370 : Can't save images sometimes after some modifications.
+048 ==> 121646 : digiKam on PPC has problem identifying JPEG and tries to use dcraw with them.
+049 ==> 120479 : Search problem for not tagged or commented images.
+050 ==> 120775 : Search doesn't find pictures without rating.
+051 ==> 112063 : When viewing image in fullscreen there is an ugly frame at the border of the screen.
+052 ==> 120736 : Many imageplugins going busy for ever when not using rubber to change a value.
+053 ==> 116154 : Show progress bar while saving large files.
+054 ==> 120521 : Untranslatable message in tagfilterview.cpp.
+055 ==> 119742 : Wish list -include move to trash in right click menu.
+056 ==> 119201 : Sometimes digiKam fails to show newly downloaded photos.
+057 ==> 117401 : digiKam's 'my albums' and 'my tags' does no longer remember it's state when restarted.
+058 ==> 116520 : Slideshow should work for search results as well.
+059 ==> 119073 : Wish list rescan option.
+060 ==> 118543 : Enable-nfs-hack no longer works.
+061 ==> 113807 : Is it possible to have the tags of the contextual menus "Assign Tag" and
+ "Remove Tag" sorted ?
+062 ==> 116343 : "Not Tagged" has disappeared from the Tags Filter view when changing album library path.
+063 ==> 117225 : digiKam requires at least libpng >= 1.2.7.
+064 ==> 115423 : Thumbnails view jumps to top when new photos are added.
+065 ==> 125732 : ICC settings get reset when disabled and re-enabled.
+066 ==> 119741 : Restore image editor if already open.
+067 ==> 122374 : Ignores read-only permission during saving.
+068 ==> 121367 : Add properties tab to digiKam and (especially) Image Editor.
+069 ==> 125926 : Directories with a '#' in their name are not properly detected when created outside digiKam.
+070 ==> 125733 : Enabling 'Always apply ICM profiles' can lead to suprising results.
+071 ==> 126326 : Camera download: auto-rotated images loose EXIF info when 'No EXIF information found.'
+ is written to console.
+072 ==> 121242 : mimelnk/x-image-raw.desktop conflicts with tdegraphics-3.5.x.
+073 ==> 127374 : Unsharp mask: max. radius way too small.
+074 ==> 116485 : Mimimize button missing on "Compaq Flash Reader" camera window.
+075 ==> 127577 : Raw display too dark and not rotated.
+076 ==> 127634 : Communication between digiKam and its tdeioslaves broken if size_t != off_t.
+077 ==> 126335 : Autoration of photos may confuse user because of changes made he is not aware of.
+078 ==> 127759 : No scroll bar in color management/select profile.
+079 ==> 114211 : Main Interface: image comments encoding unreadable after moving an album.
+080 ==> 120241 : Main Interface: utf8 display and edit.
+081 ==> 127905 : Wish: make comment field resizable.
+082 ==> 127946 : Typo: the word Toogle should be Toggle.
+083 ==> 117115 : Automatically rotate/flip using camera-provided information (EXIF) modifies image contents
+084 ==> 128069 : Crash when moving an album to another album.
+085 ==> 125779 : Use deflate compression for tiffs.
+086 ==> 127972 : digiKam does not *add* EXIF:DateTimeOriginal when modifying date
+087 ==> 127697 : Camera gui always puts *.JPG and *.NEF in the configure - digiKam dialog/File
+ Mime Types/ Image Files.
+088 ==> 128373 : "Makernote - Simple" should show ISO.
+089 ==> 113915 : Tags are lost after "Save As..."ing.
+090 ==> 128283 : Thumbnail generation fails with raw images.
+091 ==> 126112 : Do not open new window when camera is connected.
+092 ==> 127846 : digiKam crash when 2nd gphoto camera dialog is closed.
+093 ==> 93569 : Easier connection to USB disc cameras.
+094 ==> 124952 : digiKam "Mount and Download" Problems.
+095 ==> 129134 : Typo in missing files warning dialog "loose" should be "lose".
+096 ==> 128914 : Differientiating view and edit / call for a integrated viewer.
+097 ==> 128669 : Use embedded thumbnail for viewing RAW files.
+098 ==> 127991 : digiKam image editor and raws (nef).
+099 ==> 129450 : Cannot exit preview mode within empty album.
+100 ==> 129610 : Unsupported initialization of CameraList object.
+101 ==> 127584 : Minimum width of sidebar too large.
+102 ==> 127763 : Color management, profile selection crashes.
+103 ==> 126199 : Nikon D70 comments in jpegs are all shown as "charset="Ascii".
+104 ==> 130381 : Automatic colorbalance and camera color balance checkboxes swapped.
+105 ==> 117248 : Opening default app when camera inserted return KIOExec error.
+106 ==> 130883 : Overexposure indicator in color management is saved as part of the image.
+107 ==> 130798 : Editor saves some jpeg's 30% smaller than original,
+ even with jpeg compression option set at 100.
+108 ==> 130525 : Saving large (>5M) jpg's result in corrupt file.
+109 ==> 130920 : ICC, profiles, metadata, abbreviations.
+110 ==> 124199 : digiKam crashed when I right-clicked in the (empty) "My Albums" view.
+111 ==> 127826 : Album comment with no wordwrap.
+
+*** 0.9.0-beta2 *******************************************************
+
+112 ==> 121691 : Problems with Downloading images from Camera.
+113 ==> 127614 : Focus steal when typing custom prefix in renaming options sidebar.
+114 ==> 131034 : Display a mini-review of the photo currently transfered, i.e better visual feedback.
+115 ==> 124425 : Start index number counting on selected images instead of all images.
+116 ==> 127272 : Crash when opening image in Image Editor.
+117 ==> 96186 : Upload to camera not possible.
+118 ==> 107724 : Handle EXIF Information-Wizzard missing.
+119 ==> 130996 : More control over jpeg options.
+120 ==> 121526 : Download Images from camera.
+121 ==> 124060 : Use same menu mnemonic for Filter as Krita (and Photoshop, and ...).
+122 ==> 131301 : Crash after add second image.
+123 ==> 109820 : Utility script to export tag information of images into the filesystem.
+124 ==> 117375 : Change file name without affecting extension.
+125 ==> 131603 : Orientation of RAW-images (especially Canons *.cr2).
+126 ==> 131532 : Minolta exception code can break EXIF rotation.
+127 ==> 131550 : digiKam/showfoto can't show jpeg image under PowerPC.
+128 ==> 131549 : Endianess problem under Linux-PowerPC (with png images at least).
+129 ==> 132011 : Add search criteria to take sub-tags into account.
+130 ==> 131920 : Can't create preview folders with unicode characters in the name.
+131 ==> 132113 : Resize dialog limits image width/height to 4 digits.
+132 ==> 132081 : Critical: ShowFoto silently aborts saving image when closed.
+133 ==> 118535 : Add Border: use percent to designate border size.
+134 ==> 131686 : Crash when viewing/rollover of Sony Alpha 100 raw (.arw) images.
+135 ==> 132582 : Some pictures make digiKam crash (sample included).
+136 ==> 122693 : Improvement proposals for the "add border" plugin.
+137 ==> 124183 : Option to convert images when importing them from the camera.
+138 ==> 128673 : Add keyboard shortcut to refresh album view or auto-refresh when pictures are renamed.
+139 ==> 126427 : In "rename file" dialog, the 2nd picture is (and can't) not displayed.
+140 ==> 131558 : Camera UI: renaming dialogue can't handle UTF-8 filenames.
+141 ==> 119075 : Wish list new folder date option.
+142 ==> 128817 : Configure timestamp naming format for directory names.
+143 ==> 132660 : Shortcuts for ratings do not work as of 0.9.x beta.
+144 ==> 126874 : digiKam does not support <shift+del>.
+145 ==> 132957 : Crash using dcop action: album_forward.
+146 ==> 130547 : Automatically download RAW-images into separate folder below the JPG-images.
+147 ==> 128296 : Icon selector for tags wont let user select 'other icons'.
+148 ==> 115161 : Image comments/tags: 'Recent tags' looks like a button but funktons like a menu.
+149 ==> 121423 : Rename file loses Album thumbnail.
+150 ==> 120308 : Batch creation of thumbnails.
+151 ==> 120074 : New tag doesn't appear.
+152 ==> 120075 : Can't restore system icons for tag.
+153 ==> 133209 : Shift selection is not working in download screen.
+154 ==> 131552 : Typo inconsisted use of ICC.
+155 ==> 131553 : Typo indent must be intent?
+156 ==> 118526 : Make it possible to remove an album's thumbnail.
+157 ==> 120050 : Assigning an image as icon for a tag entry in 'Tag View' does not update
+ the icons of this tag in 'Tag Filters'.
+158 ==> 129365 : Keyword-List view still uses old description after renaming keyword.
+159 ==> 115154 : Tags filter misses 'deselect all Tags' in context menu.
+160 ==> 133525 : digiKam doesn't compile (KDE forces -fno-exception, digiKam needs it).
+161 ==> 115160 : 'Tag Filters' should use 'and' not 'or' or maybe better label 'Show Tags'.
+162 ==> 120056 : Allow to assign via drag and drop tag thumbnails in 'Tag Filters'.
+163 ==> 132694 : RAW Thumbnailing very slower and resource intensive.
+
+*** 0.9.0-beta3 *******************************************************
+
+164 ==> 134013 : Tag menu extremly slow.
+165 ==> 122653 : File-dialogue claims that pictures are not on the local-storage, yet they are.
+166 ==> 134091 : dcraw -n option not valid for version > 8.15.
+167 ==> 134224 : Prefix for image filename in camera dialog not working.
+168 ==> 133359 : Google maps support to show satellite images of the photos.
+169 ==> 131347 : Comments modified in digiKam Image Editor are not saved.
+170 ==> 134351 : Error while make install.
+171 ==> 132841 : Tag filtering works only a the second click on the tag filter list.
+172 ==> 133359 : Google maps support to show satellite images of the photos.
+173 ==> 134924 : Patch to allow compile with LDFLAGS="-Wl, --as-needed".
+174 ==> 134841 : Weird behaviour of identity setup.
+175 ==> 131382 : All thumbnails of album destroyed when using Tag Filters.
+176 ==> 134869 : High CPU usage while displaying ICC Profile.
+177 ==> 134761 : A rotated RAW image get saved straight with an inconsistent Exif orientation.
+178 ==> 135236 : Right-click menu rename function cuts to the first period (not the extention one).
+179 ==> 135307 : After deleting a file, user comments entered for pictures apply to the wrong picture.
+180 ==> 135145 : Raw image converter fails on my raw files (cr2, crw, dng).
+181 ==> 125727 : digiKam open with of raw file only shows application for octet-stream.
+182 ==> 135430 : Typo automaticly should be automatically in raw image converter.
+183 ==> 135060 : Folders without pictures in it cannot be assigned icons.
+
+*** 0.9.0-rc1 **********************************************************
+
+184 ==> 103255 : Add (not edit) EXIF headers like date, comment etc.
+185 ==> 91812 : Viewer/editor for IPTC meta data.
+186 ==> 136138 : Set as album thumbnail doesn't change the icon immediately.
+187 ==> 135851 : Wish to view IPTC in different order.
+188 ==> 136162 : ISO Slider label is incorrectly labeled as "sensibility", should be sensitivity.
+189 ==> 133026 : Crashes on systems using hyperthreading.
+190 ==> 136260 : Awkward management of metadata and digiKam-tags and comments.
+191 ==> 136769 : digiKam crashes when resetting Album icon (with no album selected).
+192 ==> 136749 : Tags are not kept with images when album is moved in album hierarchy.
+193 ==> 123623 : digiKam freezes or is very slow.
+194 ==> 96993 : Ability to view next next image in folder in showfoto.
+195 ==> 136643 : Showfoto can open CRW, but not shown in file dialogue.
+196 ==> 137063 : Keyboard shortcut for 'paste' action not working.
+197 ==> 137461 : Typo croping must be cropping.
+198 ==> 137282 : Comments are lost when copying or moving an image to another album.
+199 ==> 132805 : Crash when assinging keywords.
+200 ==> 137204 : Crash when applying IPTC data.
+201 ==> 135407 : Reproducible crash selecting photos from collection.
+202 ==> 136086 : Crash when saving any type of files.
+203 ==> 137612 : Showfoto doesn't refresh the photo preview when deleting an image.
+
+*** 0.9.0-rc2 **********************************************************
+
+204 ==> 115125 : White Balance tool: remaining endianess issue on PowerPC.
+205 ==> 137495 : showfoto crashes when doing any modification to a loaded directory.
+206 ==> 137845 : Album Header cut off (squeezed) when entering two or more lines
+ in Album comment.
+207 ==> 137886 : When a tag is moved in the left panel it's position is not updated
+ in the right panel.
+208 ==> 135834 : Lowercasing camera filenames only works for first imported file.
+209 ==> 134391 : digiKam camera gui dialog crashes if there is a filename without extension.
+210 ==> 131947 : digiKam complains about invalid ICC profiles path.
+211 ==> 130176 : Typos in digiKam.po and one plugin.
+212 ==> 138252 : Display is not updates when switching color managed view on/off.
+213 ==> 138253 : Keyboard shortcut for turning color managed display on/off.
+
+*** 0.9.0-final *********************************************************
+
+214 ==> 137461 : Typo croping must be cropping.
+215 ==> 135477 : View mode which shows the comment of an image.
+216 ==> 137770 : digiKam doesn't keep original unix rights when modifying comments/tags/rating.
+217 ==> 133091 : Changing date/time with numblock changes also the orientation.
+218 ==> 137993 : Importing photos into albums results in time/date file override with current one.
+219 ==> 138620 : Saving an image destroy another picture.
+220 ==> 119205 : Drag & Drop of image into librarypath root dir ==> images are invisible.
+221 ==> 121651 : Export menu not available in context menu from album.
+222 ==> 131601 : Need preview pane (large) in main digikam window.
+223 ==> 133276 : Make changing EXIF date of multiple files easier.
+224 ==> 133590 : Usability: walking through photos using image View (F3).
+225 ==> 122746 : Images are not shown anymore. black screen.
+226 ==> 132470 : Plugins not available unless logged in as superuser.
+227 ==> 113801 : Little problem with image files extensions.
+228 ==> 127112 : Tools -> 'Gamma Adjustment...' fails silently when kgamma is not installed.
+229 ==> 136258 : User comments in the EXIF/IPTC-data aren't carried over to the "Comments".
+230 ==> 136932 : Access to a specific jpg photo crashed digiKam.
+231 ==> 135442 : Add 'rename' entry to album RMB menu.
+232 ==> 138540 : Images files are updated (but not modified) when setting new
+ metadatas albeit they are unset.
+233 ==> 136256 : Tags are not filled from the IPTC-keywords.
+234 ==> 136062 : Charset problem saving EXIF comments (JPEG files).
+235 ==> 133567 : Crash when testing tags in digikam image editor (saveWithExiv2).
+236 ==> 138300 : Crash on startup with one user's collection.
+237 ==> 138747 : Crash on Add Camera.
+238 ==> 138715 : Crash when quickly switching to previous/next image.
+239 ==> 138616 : Cannot compile: cannot verify exiv2 version.
+240 ==> 134999 : Crash in exiv2 when searching for new images.
+241 ==> 136771 : Image editor crashes when using undo.
+
+----------------------------------------------------------------------------------------------------
diff --git a/src/NEWS.0.9.1 b/src/NEWS.0.9.1
new file mode 100644
index 00000000..957162db
--- /dev/null
+++ b/src/NEWS.0.9.1
@@ -0,0 +1,167 @@
+----------------------------------------------------------------------------------------------------
+digiKam and DigikamImagePlugins 0.9.1 - Release date: 2007-03-04
+
+NEW FEATURES :
+
+General : New native JPEG2000 image loader using Jasper library witch
+ can support 16 bits color depth pictures.
+General : New optimized layout to show Comments & Tags side bar contents.
+General : Tags View from Comments & Tags side bar support drag & drop.
+General : New Batch tool to sync all images metadata (EXIF/IPTC) with
+ digiKam database content.
+General : Add a status bar to Album Gui with a progress bar, text bar,
+ and navigate bar.
+General : New native SlideShow tool witch use the Image Preview feature.
+ RAW files can be slided very fast.
+
+AlbumGUI : Improvement of pop-up menu of Tags Filter View and Comment & Tags
+ about auto selection/deselection of parents/childs tags in Tags treeview.
+AlbumGUI : Main Root album show a Welcome page for new users (like Kmail
+ or Konqueror).
+AlbumGUI : Video files can be previewed using an embedded instance of
+ default KDE media player.
+AlbumGUI : Preview picture mode use a memory cache to speed-up loading.
+AlbumGUI : Preview picture mode pride a context pop-up menu.
+AlbumGUI : Comments & Tags now support multiple selection of items to
+ apply properties.
+AlbumGUI : New option to set the action to do when user right click on
+ picture thumb: Load on Image Editor or Show an embeded preview.
+AlbumGUI : Prefer Exif DateTimeOriginal for sorting images
+ (DateTimeDigitized and DateTime only used as fallback)
+
+Image Plugins : All tools remember settings between sessions.
+Image Plugins : All tools render properly preview of image using Color Managed View.
+Image Plugins : All tools use the same keyboard shortcuts than PhotoShop.
+Image Plugins : New option in all Color corrections Tools to show under-exposed
+ and over-exposed areas of corrected picture before to apply corrections.
+Image Plugins : Brightness/Contrast/Gamma : settings value excursion are the same
+ than Photoshop.
+Image Plugins : Add Border Tool : add new option to preserve aspect ratio. Border Width
+ can be set in pixels or in percents accordinly with this setting.
+Image Plugins : Perspective Tool : add a grid and vertical/horizontal guide lines.
+Image Plugins : Ratio-crop Tool : usability improvements from Jaromir Malenko.
+Image Plugins : Auto Color Correction Tool : add new filter to perform
+ auto-exposure corrections.
+Image Plugins : all plugins : capabilty to remember settings between plugin
+ sessions to store configuration in a settings file.
+
+Image Editor : Add new advanced options to keep ratio and alignment about print pictures.
+Image Editor : Add 2 new advanced options to show under-exposed or over-exposed pixels.
+Image Editor : Remove View->Histogram menu option. We have a better histogram
+ available in sidebar.
+Image Editor : Color profiles are tested now to avoid invalid files.
+Image Editor : JPEG/PNG/TIFF/JPEG2000 file save settings are now available in
+ File Save dialog.
+Image Editor : JPEG file save settings include a red warning about this lossy
+ compression image format with a link to wikipedia to aware users.
+
+CameraGUI : New option to set metadata DateTime/PhotographerId/Copyright Exif and/or IPTC tags
+ on the fly.
+CameraGUI : JPEG Exif thumbnail and Iptc image preview are recomputed during Exif Auto-rotation.
+CameraGUI : digiKam theme support.
+
+AlbumGUI : Improvement of pop-up menu of Tags Filter View and Comment & Tags about
+ auto selection/deselection of parents/childs tags in Tags treeview.
+AlbumGUI : Main Root album show a Welcome page for new users (like Kmail or Konqueror).
+AlbumGUI : Video files can be previewed using an embedded instance of default KDE
+ media player.
+AlbumGUI : Preview picture mode use a memory cache to speed-up loading.
+AlbumGUI : Preview picture mode pride a context pop-up menu.
+AlbumGUI : Comments & Tags now support multiple selection of items to apply properties.
+AlbumGUI : New option to set the action to do when user right click on picture thumb:
+ Load on Image Editor or Show an embeded preview.
+AlbumGUI : Prefer Exif DateTimeOriginal for sorting images (DateTimeDigitized
+ and DateTime only used as fallback)
+
+Image Plugins : All tools remember settings between sessions.
+Image Plugins : All tools render properly preview of image using Color Managed View.
+Image Plugins : All tools use the same keyboard shortcuts than PhotoShop.
+Image Plugins : New option in all Color corrections Tools to show under-exposed and
+ over-exposed areas of corrected picture before to apply corrections.
+Image Plugins : Brightness/Contrast/Gamma : settings value excursion are the same
+ than Photoshop.
+Image Plugins : Add Border Tool : add new option to preserve aspect ratio. Border Width can be set
+ in pixels or in percents accordinly with this setting.
+Image Plugins : Perspective Tool : add a grid and vertical/horizontal guide lines.
+Image Plugins : Ratio-crop Tool : usability improvements from Jaromir Malenko.
+Image Plugins : Auto Color Correction Tool : add new filter to perform auto-exposure corrections.
+
+Image Editor : Add new advanced options to keep ratio and alignment about print pictures.
+Image Editor : Add 2 new advanced options to show under-exposed or over-exposed pixels.
+Image Editor : Remove View->Histogram menu option. We have a better histogram available in sidebar.
+Image Editor : Color profiles are tested now to avoid invalid files.
+Image Editor : JPEG/PNG/TIFF/JPEG2000 file save settings are now available in File Save dialog.
+Image Editor : JPEG file save settings include a red warning about this lossy compression image
+ format with a link to wikipedia to aware users.
+
+General : Native slideshow tool : new option to slide an album tree in recursive mode.
+General : Native slideshow tool : add capability to print photograph informations during slide.
+General : Native slideshow tool : preaparing slideshow is now cancelable.
+
+
+----------------------------------------------------------------------------
+digiKam and DigikamImagePlugins BUGFIX FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 115157 : Usability: Image comments/tags dialog: hard to find/see all
+ already selected tags (and to 'de'select them).
+002 ==> 132309 : Wish: enhance video support in digiKam.
+003 ==> 115153 : Assigns tags not only to selected images with drag and drop.
+004 ==> 131743 : Comments and tag edit widgets should retain focus when changing image.
+005 ==> 140412 : Comments/Tags sidebar steels focus.
+006 ==> 137491 : Editor image cache not flush when an image is modified outside digiKam.
+007 ==> 138949 : Save/overwrite fails: wrong dialog.
+008 ==> 139197 : Crash during import of raw imagefiles (DNG).
+009 ==> 139313 : UTF-8 image comments are mangled when input from image editor.
+010 ==> 136903 : If partition gets full during camera/card copy there is no
+ warning and empty files are created.
+011 ==> 133955 : Rating search: less, equal, and larger comparison.
+012 ==> 139547 : Tag hierarchy automatic fill.
+013 ==> 138816 : gpcamera.cpp compile failure CameraList camList.
+014 ==> 93542 : Improvements to digiKam print interface.
+015 ==> 139658 : Tiff generated by photoshop is either ignored by digiKam image browser,
+ or kills digiKam when regenerating the database.
+016 ==> 102032 : Wish: rating system for photos, show/hide/sort by rating.
+017 ==> 111446 : Bad behaviour when out of disk space.
+018 ==> 133516 : Tags set in Comments/Tags end up on the wrong picture.
+019 ==> 140136 : Aspect ratio crop and selection widget improvements.
+020 ==> 131600 : Set tag for multiple selection of images.
+021 ==> 137545 : Helper lines / cross wanted.
+023 ==> 131170 : Add grid/auxiliary lines for perspective correction tool.
+024 ==> 138158 : Allow to edit multiple image tags/comments at same time.
+025 ==> 140234 : Thumbnail click default action.
+026 ==> 135141 : Border size in pixel.
+027 ==> 137696 : Border extent should not follow image aspect ratio.
+028 ==> 138444 : More clear/consistent color management terminology.
+029 ==> 140176 : Showfoto crashes when selecting a filter.
+030 ==> 130237 : Typos in digiKam plugin files.
+031 ==> 138925 : External modules config dialog lacks a select all button.
+032 ==> 140038 : digiKam Image Editor crash on right click after changing toolbar.
+033 ==> 139766 : Crash when displaying EXIF metadata with Unicode comment
+034 ==> 130017 : Wish: batch operation to save existing comments in the files.
+035 ==> 140320 : View menu should better fit after Edit.
+036 ==> 127617 : "Toggle Fullscreen" should be located in "View" menu instead
+ of "Settings" menu.
+038 ==> 139264 : digiKam sorts pictures with wrong exif tag.
+039 ==> 140417 : Crash digiKam when delete image.
+040 ==> 110514 : Enhanced selection, refactored histogram.
+041 ==> 140933 : Typo: Album Library Path.
+042 ==> 141190 : digikam crash when invalid ICM file at color profile directory
+ and you enter into PREFERENCES->COLOR SPACE PROFILES.
+043 ==> 123649 : JPEG/PNG quality settings at the "save" dialog.
+044 ==> 116518 : Use KIPI plugin for displaying slideshows.
+045 ==> 140304 : Start slideshow from the current image.
+046 ==> 140303 : slideshow must be relocated to View menu from tools menu.
+047 ==> 137503 : Tags are not written to image file automatically.
+048 ==> 136254 : Editing tags does not change IPTC-keywords.
+049 ==> 135655 : Proper full screen mode (no menu, toolbar, statusbar, sidebars).
+050 ==> 142088 : Build fails when libkexiv2 is in $PREFIX, but not in $TDEDIR or standard include directories
+051 ==> 140227 : remove or modify D&D tags menu from icon view area (improvements, not finished)
+052 ==> 142109 : Turn under/over exposure display on/off by click on corresponding icons
+053 ==> 141663 : Keep getting prompted to Apply Comments
+054 ==> 139024 : Camera GUI new items selection doesn't work
+055 ==> 139547 : Tag hierarchy automatic fill
+056 ==> 141626 : Bugs in german translation
+057 ==> 141786 : Confirmation dialog during rename to existing file does n...
+058 ==> 141924 : Tags preview not updated in last image of album
+059 ==> 141934 : Incorrect capitalized string: "back to album"
+060 ==> 141961 : Long comments truncated in slideshow
diff --git a/src/NEWS.0.9.2 b/src/NEWS.0.9.2
new file mode 100644
index 00000000..77fbbabc
--- /dev/null
+++ b/src/NEWS.0.9.2
@@ -0,0 +1,158 @@
+digiKam 0.9.2-final - Release date: 2007-06-13
+
+NEW FEATURES:
+
+
+digiKam BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+062 ==> 144590 : English labels need cleanup in digiKam.
+063 ==> 146436 : digiKam try to include Exiv2 library headers.
+064 ==> 146464 : Light Table does not deal with colour management.
+065 ==> 142133 : Typo English documentation docbook.
+066 ==> 146744 : Tag that contains '&' is not displayed correctly in menus.
+
+**********************************************************************************************
+
+digiKam 0.9.2-beta3 - Release date: 2007-06-03
+
+NEW FEATURES:
+
+General : Light Table and Preview Mode can work with full image size instead a reduced one.
+
+----------------------------------------------------------------------------
+
+digiKam BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+059 ==> 145198 : Light-table should also work with the full image.
+060 ==> 146072 : Slideshow shows black screen.
+061 ==> 146184 : Showfoto no filename specified .
+
+**********************************************************************************************
+
+
+digiKam 0.9.2-beta2 - Release date: 2007-05-28
+
+NEW FEATURES:
+
+General : digiKam has a new powerful tool to compare similar images side by side:
+ the Light Table. Demo and screenshots:
+ http://www.digikam.org/?q=node/221
+ http://www.digikam.org/?q=node/222
+
+----------------------------------------------------------------------------
+
+digiKam BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+040 ==> 135048 : Easily compare similar images using a light-table.
+041 ==> 145159 : Improvements to the light-table.
+042 ==> 145204 : Small issues with the light-table.
+043 ==> 145227 : Change ratings via short-cuts in the light-table.
+044 ==> 145236 : Small wishes for the light-table.
+045 ==> 145237 : Small wishes for the light-table (2).
+046 ==> 145170 : Always allow zooming in/out in imageeditor.
+047 ==> 145078 : Ctrl-Y untied from Redo.
+048 ==> 145083 : Space and Shift-Space isn't used for navigation between images.
+049 ==> 145077 : Ctrl-W and Ctrl-Q shortcuts not tied to proper actions:fixed
+050 ==> 145558 : In menu view: window size/original size: ctrl-shift-z obsolete?
+051 ==> 144640 : CTRL-P does nothing in Album GUI: fixed to print action
+052 ==> 144643 : Ctrl-Shift-A does not deselect in Album GUI: fixed
+053 ==> 144644 : Ctrl-0 does not set zoom to 100% in Preview mode: Alt+Ctrl+0
+054 ==> 144650 : Shift-Space doesn't work as PageUp.
+055 ==> 145079 : Ctrl-A, Ctrl-Shift-A don't peform proper selection actions.
+056 ==> 145627 : Showfoto /path/to/directory doesn't work, while the "open dir" feature exists.
+057 ==> 146012 : Dragging an image over a tag in "tag filters" panel crashes digiKam.
+058 ==> 146032 : Panning doesn't work in Light Table.
+
+
+**********************************************************************************************
+
+digiKam 0.9.2-beta1 - Release date: 2007-05-04
+
+NEW FEATURES:
+
+General : DigikamImagePlugins have been merged into digiKam. It more simple to release
+ on package for all. Image plugins translations are hosted to digikam.po file
+ instead a .po file for each tool. All tools still available like plugins at
+ the same place than image plugin core.
+General : New depency to libkdcraw shared library used to decode RAW file.
+ This library is shared between digiKam and kipi-plugins. The internal
+ dcraw version used is 8.60. digiKam support now all recent digital
+ camera RAW files released at PMA 2007.
+General : Make size of icons used in sidebars configurable in order to allow more
+ entries to be presented.
+General : Make size of icons used in album icon view more configurable using a slider
+ in status bar.
+General : Removing direct Exiv2 library depency. libkexiv2 interface is used everywhere
+ instead.
+
+Album GUI : Add Zoom/Scrooling functions with preview mode.
+
+Image Editor : Usability improvement : a new pan tool is available on the right bottom
+ corner of canvas to naviguate over large pictures.
+Image Editor : Usability issue : Blowup and Resize tools have been merged.
+Image Editor : Usability issue : Unsharp Mask, Refocus, and Sharpen tools have been merged
+ to a new Sharpness Editor.
+Image Editor : Usability issue : Reorganize menu structure
+Image Editor : Usability issue : persistant selection in all zoom mode.
+Image Editor : Add new option to fit on current selection.
+
+Image Plugins : Red Eyes Correction tool have been completly re-writen. There is
+ a preview of effect and the capability to taint the eye pupil with
+ a customized color. The new eye pupil can be blured to smooth the
+ result.
+Image Plugins : Solarize plugin is now a "Color Effects" pack including Solarize, Velvia (new
+ plugin), Neon, and Edge effects.
+Image Plugins : Black & White converter now support a lots of B&W analog camera film
+ types (Agfa, Ilford, Kodak). A new 'strength' setting can simulate the
+ amount of Lens filters effect.
+Image Plugins : Update internal CImg library to 1.1.9. The Greycstoration algorithm used
+ by Restoration, Inpainting and Blowup plugins is faster and optimized.
+
+Showfoto : The thumbbar is now resizable. The thumbnails contents can be redimensionned in live.
+Showfoto : The thumbbar items can show a full configurable tool tip like digiKam album icon
+ items tool tip.
+
+----------------------------------------------------------------------------
+
+digiKam BUGFIX FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 142443 : Red eye correction should change eye colour to an alternate colour.
+002 ==> 138744 : Dcraw 8.45 supports Pentax K10D.
+003 ==> 142427 : Rotate image tool too complicated.
+004 ==> 141439 : A velvia similar plugin. AKA vivid saturation.
+005 ==> 127583 : keywords, copyright, photographer info not saved to IPTC tags.
+006 ==> 142571 : Auto-exposure result is different from the preview.
+007 ==> 127377 : Restoration filter (CIMG) does not function properly.
+008 ==> 131446 : Using inpainting plugin crashes digiKam.
+009 ==> 103244 : Usability: Merge multiple similar menus into one.
+010 ==> 139790 : Image Editor: the center of the photo is moving when zooming in or out.
+011 ==> 106508 : Please change scaling behavior.
+012 ==> 137236 : Disable autozoom when picture fits in window.
+013 ==> 103645 : Zoom in with rectangle tool selection.
+014 ==> 126127 : Enlarge small images when autozoom is activated.
+015 ==> 104439 : Use left mouse button to scroll image.
+016 ==> 137391 : Image navigation in image editor using the mouse.
+017 ==> 102029 : Configurable size of icons in sidebars.
+018 ==> 102029 : No/small icons in album tree.
+019 ==> 131155 : Smooth increasing/decreasing size of thumbnails like in iPhoto.
+020 ==> 89365 : Reorganize menu structure in Image Editor.
+021 ==> 134037 : Respect current sort order when passing list to KIPI plugin.
+022 ==> 139466 : Remove configuration of digikam image plugins.
+023 ==> 119418 : "set as album thumbnail" should work for different albums too.
+024 ==> 125916 : Problem with opening 16bit TIFF.
+025 ==> 143578 : New Pan Tool crash.
+026 ==> 118539 : Lossless image-editor for JPEGs.
+027 ==> 133913 : sRGB profile white point may be incorrect.
+028 ==> 116148 : Auto-scrolling when selecting large area.
+029 ==> 134498 : Date stamp display option for photos in full-screen mode.
+030 ==> 130525 : Saving large (>5M) jpg's result in corrupt file.
+031 ==> 132047 : Faster display of images and/or prefetch wished for.
+032 ==> 140131 : No zoom in image preview.
+033 ==> 89365 : More standard menu structure.
+034 ==> 144214 : The plural form of "child" is "children", not "childs".
+035 ==> 124487 : No way to pause a slide show.
+036 ==> 128975 : "Correct Exif Orientation Tag" does not change the mtime
+ of the image file.
+037 ==> 139814 : The window of digiKam exceed the screen if the resolution is 800x600.
+038 ==> 144481 : Vertical window size cannot be reduced to VGA resolution.
+039 ==> 136254 : Editing tags does not change IPTC-keywords.
diff --git a/src/NEWS.0.9.3 b/src/NEWS.0.9.3
new file mode 100644
index 00000000..6652db29
--- /dev/null
+++ b/src/NEWS.0.9.3
@@ -0,0 +1,142 @@
+
+**********************************************************************************************
+
+digiKam 0.9.3 - Release date: 2007-12-23
+
+NEW FEATURES:
+
+AlbumGUI
+
+Drag&Drop : D&D from anywhere into album tree. When the albumview is flattened
+ (Include Album Sub-tree) images can be d&ded across horizontal separations.
+ That one satisfies wish #142774.
+
+Goto : A new quick navigation feature has been added: it is now possible (by RMB context menu)
+ to GOTO any other view with pertinent context, e.g. from date view right-click on an
+ image and GOTO the Album or Tag view that this image is associated with.
+ Try any other combination.
+ That covers wish #128231.
+
+AlbumGUI : Always force to show mediaplayer widget to preview video in embeded mode.
+
+**********************************************************************************************
+
+digiKam 0.9.3-RC1 - Release date: 2007-12-08
+
+NEW FEATURES:
+
+AlbumGUI : Add new Filter Bar on bottom of Albums/Tags/Searches/TagsFilter view filter contents
+ based on string.
+
+digiKam BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+052 ==> 152522 : Support for *.3gp and *.mp4 video files in digiKam (already supported by KDE video players).
+053 ==> 128231 : A way to view pictures recursively in albums and sub-albums at the same time would be cool.
+054 ==> 133191 : Quick search/filter for albums like in amarok.
+055 ==> 146364 : Incremental search for tags in all tag fields.
+056 ==> 152843 : Live filter does not work if no text is displayed under thumbnails.
+057 ==> 148629 : "Adjust Exif orientation tag" does not continue after error.
+058 ==> 144165 : Numeric values in Histogram -> Statistics frame should be right-aligned.
+059 ==> 148037 : Tiff images are not displayed with correct colours.
+060 ==> 152961 : Cannot remove album from tree.
+061 ==> 121314 : More options to display the album tree.
+062 ==> 141085 : Some strings are untranslated in digiKam.
+063 ==> 151403 : Crashed trying to save a image downloaded from the camera.
+064 ==> 148772 : Histogram should refresh after editor save.
+
+**********************************************************************************************
+
+digiKam 0.9.3-beta3 - Release date: 2007-11-20
+
+NEW FEATURES:
+
+AlbumGUI : Add Text pattern widget on status bar to filter Album contents based on items
+ name, comments, and tags strings.
+ImageEditor : New black & white Green color tone filter.
+LightTable : Several usability improvements.
+
+digiKam BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+047 ==> 150296 : Images in LightTable are not compared in right order
+048 ==> 151956 : Red eye enhance feature no longer works.
+049 ==> 110136 : Filter textbox in album tree like in amaroks collection list.
+050 ==> 152192 : Resize really bad qualitatively.
+051 ==> 150801 : Thumbnail and image view does not update after editing image.
+
+**********************************************************************************************
+
+digiKam 0.9.3-beta2 - Release date: 2007-11-01
+
+NEW FEATURES:
+
+General : Updated internal CImg library to last stable 1.2.4 (released at 2007/09/26).
+AlbumGUI : Add Rating filter widget on status bar.
+AlbumGUI : Add MimeType filter widget on status bar.
+AlbumGUI : Add tool to navigate from album, tag and date view to any other view.
+
+digiKam BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+040 ==> 147533 : New rating filter for the statusbar.
+041 ==> 131963 : Add a file type filter tab to the right.
+042 ==> 148993 : Filter images by rating in album view.
+043 ==> 151357 : Ratings can exceed 5 stars.
+044 ==> 144815 : Scroll left-pane to the selected album/date on start-up.
+045 ==> 96894 : Easier navigation between albums, tags and collections.
+046 ==> 147426 : Search for non-voted pics.
+
+**********************************************************************************************
+
+digiKam 0.9.3-beta1 - Release date: 2007-10-08
+
+NEW FEATURES:
+
+General : Updated internal CImg library to last stable 1.2.3 (released at 2007/08/24).
+General : Color scheme theme are now XML based (instead X11 format).
+General : Camera interface is now used to import new pictures in collection.
+CameraGUI : New options to Download pictures and Delete it from camera at the same time.
+CameraGUI : Support of Drag & Drop to download files from camera gui to album gui.
+CameraGUI : Add new Bargarph to indicate statistics about free space available on Album
+ Library Path.
+
+digiKam BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 120450 : Strange helper lines behavior in ratio crop tool.
+002 ==> 147248 : Image info doesn't follow image in the editor.
+003 ==> 147147 : digiKam does not apply tags to some selected images.
+004 ==> 143200 : Renaming like crasy with F2 crash digiKam.
+005 ==> 147347 : Showfoto crashes when opening twice multiple files.
+006 ==> 147269 : digiKam finds but fails to use libkdcraw.
+007 ==> 147263 : Some icons are missing.
+008 ==> 146636 : Adjust levels: helper line for sliders.
+009 ==> 147671 : Portability problem in configure script.
+010 ==> 147670 : Compilation problems on NetBSD in greycstoration.
+011 ==> 147362 : Tool-tip for zoom indicator is below the screen if window is maximised.
+012 ==> 145017 : Deleting an image from within digiKam does not update the digiKam database.
+013 ==> 148925 : Light Table thumb bar not updated when deleting images.
+014 ==> 148971 : Awful menu entry "digiKamThemeDesigner".
+015 ==> 148930 : digiKam-0.9.2 does not compile with lcms-1.17.
+016 ==> 141774 : Autorotate does not work however kipi rotate works.
+017 ==> 103350 : Original image is silently overwritten when saving.
+018 ==> 144431 : Add option in Camera Download Dialog.
+019 ==> 131407 : Use camera GUI also for import of images from different locations.
+020 ==> 143934 : Cannot download all photos from camera.
+021 ==> 147119 : Can't link against static libjasper, configure reports jasper is missing.
+022 ==> 147439 : It is too easy to delete a search.
+023 ==> 147687 : Error when downloading and converting images.
+024 ==> 137590 : Be able to modifiy the extension of images in the interface.
+025 ==> 139024 : Camera GUI new items selection doesn't work.
+026 ==> 139519 : digiKam silently fails to import when out of disc space.
+027 ==> 149469 : Excessive trash confirmation dialogs after album is deleted.
+028 ==> 148648 : Color managed previews not working in all plugins.
+029 ==> 126427 : In "rename file" dialog, the 2nd picture is (and can't) not displayed.
+030 ==> 144336 : Selecting pictures on the camera lets the scroll-bar jump back to the top of the list.
+031 ==> 136927 : Failed to download file DCP_4321.jpg. Do you want to continue? and when continue is
+ clicked the same warning comes for the next image and so on.
+032 ==> 146083 : Bugs in drag and drop.
+033 ==> 147854 : Put images into an emptied light-table.
+034 ==> 149578 : libjpeg JPEG subsampling setting is not user-controlable.
+035 ==> 149685 : Go to next photo after current photo deletion (vs. to previous photo).
+036 ==> 148233 : Adding texture generates black image.
+037 ==> 147311 : Ligth Table RAW images does not rotate as def. by exif info.
+038 ==> 146773 : Metadata sources preference when sorting images.
+039 ==> 140133 : Metadata Edit Picture Comments Syncs to IPTC and EXIF when option is de-selected.
diff --git a/src/NEWS.0.9.4 b/src/NEWS.0.9.4
new file mode 100644
index 00000000..a7ea38bf
--- /dev/null
+++ b/src/NEWS.0.9.4
@@ -0,0 +1,236 @@
+**********************************************************************************************
+digiKam 0.9.4 - Release date: 2008-07-16
+
+NEW FEATURES:
+
+General : for packaging, an external libsqlite3 dependency can be used instead internal version.
+ (see B.K.O #160966)
+General : Updated internal CImg library to last stable 1.2.9 (released at 2008/06/26).
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 164392 : Can't compile digikam with missing libkdcraw.
+002 ==> 160617 : Same image gets opened to both sides of view when draggin image to right side.
+003 ==> 165823 : Digikam crashes if last picture is removed from lighttable.
+004 ==> 165921 : Translation bug: Missing german translation.
+005 ==> 130906 : Allow user to click text of tags to tag an image (not checkbox only).
+006 ==> 126871 : Click on parent folder in tree should generate a reaction.
+007 ==> 162812 : Sharpen slider enhancement refresh previews continuously.
+008 ==> 166274 : Rename files on download from camera.
+009 ==> 166472 : Thumbnail bar gone in image editor when switching back from fullscreen.
+010 ==> 166540 : Showfoto always prints filename.
+
+**********************************************************************************************
+digiKam 0.9.4-RC2 - Release date: 2008-07-06
+
+NEW FEATURES:
+
+General : external libsqlite3 dependency removed. sqlite3 source code is now included in digiKam core.
+ (see B.K.O #160966)
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 164301 : Filter Albums sub-tree names too, when using quickfilter on icon view.
+002 ==> 164482 : Place filename of image in klipper/paste buffer from thumbnail right mouse button context menu.
+003 ==> 164536 : Add adjust mid range color levels.
+004 ==> 164615 : Image detail info about geometry (X-pixel / Y-pixel) missed.
+005 ==> 149654 : Fullscreen icon changes image's zoom to 100% (should fit to screen).
+006 ==> 162688 : Digikam RC5, can't see scroll bar sliders in dark theme.
+007 ==> 161212 : Switch to full screen resets X11.
+008 ==> 164432 : Compilation fails with gcc 3.
+
+**********************************************************************************************
+digiKam 0.9.4-RC1 - Release date: 2008-06-17
+
+NEW FEATURES:
+
+General : external libsqlite3 dependency removed. sqlite3 source code is now included in digiKam core.
+ (see B.K.O #160966)
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 162496 : When Digikam saves file as JPEG - JPG options are not always presented.
+002 ==> 160740 : Handbook says that libgphoto2 is required but does not say whether it should be compiled with exif support.
+003 ==> 148400 : Problem loading 16bit Grayscale TIFF image.
+004 ==> 160925 : digiKam crash, I don't know why, but I have the bugreport.
+005 ==> 162691 : 0.9.4 beta5 compile error imagehistogram.cpp.
+006 ==> 160966 : Some searches don't work properly with the new sqlite3-3.5.8.
+007 ==> 162245 : Assigned tag list not updated correctly when "show assigned tags" filter is active.
+008 ==> 158866 : Advanced Search on Tags a mess.
+009 ==> 162814 : digiKam crashes when using light table (page down).
+010 ==> 163151 : Red eye correction doesn't work fine.
+011 ==> 163227 : Searching for tag names in simple and advanced search doesn't work.
+012 ==> 163474 : Config UI using external media.
+013 ==> 156420 : Canon EOS 400D, no thumbnails displayed then digikam segfaults.
+014 ==> 163118 : digikam-0.9.4_beta5 compilation hangs with gcc 4.3.
+
+**********************************************************************************************
+
+digiKam 0.9.4-beta5 - Release date: 2008-05-25
+
+NEW FEATURES:
+
+General : English words review in whole GUI by Oliver Doer
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 162132 : Crash while saving tiff (possibly caused by present alpha channel).
+002 ==> 155046 : light-table useability, possible improvements.
+003 ==> 161085 : Zoom steps in image editor
+004 ==> 162247 : The photos thumbnails list would be better if in the end of an album/date/tag start the next.
+005 ==> 160523 : Crash when saving picture as new nameafter resizing.
+006 ==> 162428 : debug info needed ?
+007 ==> 147597 : Typos in the digiKam KDE3 PO file.
+
+**********************************************************************************************
+
+digiKam 0.9.4-beta4 - Release date: 2008-04-27
+
+NEW FEATURES:
+
+General : Updated internal CImg library to last stable 1.2.8 (released at 2008/04/18).
+General : New search text filter for all metadata sidebar tabs.
+General : New dialog to list all RAW camera supported. A search camera model tool is
+ available to find easy a camera from list.
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 121309 : Camera list sorted by brand.
+002 ==> 137836 : Usability: Configure camera GUI add and auto-detect camera.
+003 ==> 160323 : Changing the album name from sth to #sth and then to zsth causes the album to be deleted along with the files.
+004 ==> 145083 : Space and Shift-Space isn't used for navigation between images.
+005 ==> 158696 : digiKam image viewer crashes when "Save As" is clicked.
+006 ==> 156564 : Light table crashes when choosing channel.
+007 ==> 147466 : digiKam crashes on termination if an audio file played.
+008 ==> 146973 : Crashes when clicking on preview image.
+009 ==> 148976 : digiKam Deleted 2.3 g of Pictures.
+010 ==> 145252 : Umask settings used for album directory, not for image files.
+011 ==> 125775 : digiKam way to save and delete "Searches".
+012 ==> 150908 : Canon g3 not recognized works version 0.9.1.
+013 ==> 152092 : Showfoto crashes ( signal 11 ) on exit.
+014 ==> 160840 : Wrong filtering on Album-Subtree-View.
+015 ==> 160846 : Blurred preview of pics.
+016 ==> 157314 : Zoom-slider has no steps.
+017 ==> 161047 : Shift and wheel mouse doesn't work.
+018 ==> 161087 : Conflicting directions of mouse scroll when zooming.
+019 ==> 161084 : Not properly updates status bar info.
+
+**********************************************************************************************
+
+digiKam 0.9.4-beta3 - Release date: 2008-04-07
+
+NEW FEATURES:
+
+AlbumGUI : Auto-completion in all search text filter.
+
+General : Creation of tags simplified everywhere. Multiple Tags hierarchy can be
+ created at the same time. Tags creation dialog re-designed.
+
+Showfoto : New open image file dialog with photo thumbnail/information.
+
+ImageEditor : New saveas image file dialog with photo thumbnail/information.
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 159806 : Cannot display Nikon d3 raw files.
+002 ==> 148935 : digiKam hot pixel plugin can not read canon RAW files.
+003 ==> 158776 : Confirm tag delete if tag assigned to photos.
+004 ==> 148520 : Change of rating causes high CPU load.
+005 ==> 140227 : Remove or modify D&D tags menu from icon view area.
+006 ==> 154421 : Live search doesn't search filenames.
+007 ==> 118209 : digiKam hotplug script kde user detection problem.
+008 ==> 138766 : JPEG Rotations: Give a warning when doing Lossy rotations (and offer to do lossless when applicable).
+009 ==> 156007 : Canon Raw+Jpg Locks up download image window during thumbnail retrieval.
+010 ==> 114465 : Simpler entry of tags.
+011 ==> 159236 : Corrupted file when downloading from CF card via digikam.
+
+**********************************************************************************************
+
+digiKam 0.9.4-beta2 - Release date: 2008-03-23
+
+NEW FEATURES:
+
+ImageEditor : Raw files can be decoded in 16 bits color depth without to use Color Management.
+ An auto-gamma and auto-white balance is processed using the same method than dcraw
+ with 8 bits color depth RAW image decoding. This usefull to to have a speed-up RAW
+ workflow with suitable images.
+Showfoto : Added support of color theme schemes.
+Showfoto : Added 2 options to setup images ordering with "File/Open Folder" action.
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 146393 : No gamma adjustment when opening RAW file.
+002 ==> 158377 : digiKam duplicates downloaded images while overwriting existing ones.
+003 ==> 151122 : Opening Albums Has Become Very Slow.
+004 ==> 145252 : Umask settings used for album directory, not for image files
+005 ==> 144253 : Failed to update old Database to new Database format.
+006 ==> 159467 : Misleading error message with path change in digikamrc file.
+007 ==> 157237 : Connect the data when using the left tabs.
+008 ==> 157309 : digiKam mouse scroll direction on lighttable.
+009 ==> 158398 : Olympus raw images shown with wrong orientation.
+010 ==> 152257 : Crash on saving images after editing.
+011 ==> 153730 : Too many file format options when saving raw image from editor in digiKam.
+012 ==> 151157 : Right and left keys goes to first picture and last picture without shortcuts.
+013 ==> 151451 : Application crashed using edit.
+014 ==> 139657 : Blueish tint in low saturation images converted to CMYK.
+015 ==> 147600 : Showfoto Open folder - files shown in reverse order.
+016 ==> 149851 : Showfoto asks if you want to save changes when deleting photo, if changes have been made.
+017 ==> 138378 : Showfoto opens file browser window to last the folder used upon startup.
+018 ==> 148964 : images are blur only inside showFoto.
+019 ==> 146938 : Themes Don't Apply To All Panels.
+020 ==> 135378 : Single-click on picture open it in editor.
+021 ==> 142469 : Splash screen to be the very first thing.
+022 ==> 147619 : Crash when viewing video.
+023 ==> 136583 : Album icon does not show preview-image after selecting given image as preview.
+024 ==> 129357 : Thumbnails not rotated for (D200)-NEF.
+025 ==> 132047 : Faster display of images and/or prefetch wished for.
+026 ==> 150259 : media-gfx/digikam-0.9.2 error/typo in showfoto.desktop.
+027 ==> 150674 : Turn of live update of preview when moving points in curves dialog or in histogram.
+028 ==> 141703 : Show file creation date in tooltip.
+029 ==> 128377 : Adjust levels: histograms don't match, bad "auto level" function.
+030 ==> 141755 : Sidebar: Hide unused tabs, Add tooltips.
+031 ==> 146068 : Mixer does not work for green and blue selected as main channel and monochrome mode.
+032 ==> 135931 : Reorganize Batch Processing tools from 'Image' and 'Tools' menus.
+033 ==> 159664 : Background color of live/quick filter not updated on folder/date change.
+
+**********************************************************************************************
+
+digiKam 0.9.4-beta1 - Release date: 2008-03-09
+
+NEW FEATURES:
+
+General : Color theme scheme are now supported everywhere in graphical interfaces.
+General : Color theme scheme can be changed from Editor and LightTable.
+General : Updated internal CImg library to last stable 1.2.7 (released at 2008/01/23).
+General : Add capability to display count of items in all Album, Date, Tags, and
+ Tags Filter Folder View.
+ The number of items contained in virtual or physical albums can be
+ displayed on the right of album name. If a tree branch is collapsed,
+ parents album sum-up the number of items from all undisplayed children albums.
+ Count of items is performed in background by digiKam TDEIO-Slaves.
+ A new option from Setup/Album dialog page can toggle on/off this feature.
+
+AlbumGUI : Add a new tool to perform Date search around whole albums collection: Time-Line.
+AlbumGUI : In Calendar View, selecting Year album show all pictures relevant.
+AlbumGUI : Add a new status-bar indicator to report album icon view filtering status.
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 96388 : Show number of images in the album.
+002 ==> 155271 : Configure suggests wrong parameter for libjasper.
+003 ==> 155105 : Broken png image present in albums folder causes digikam to crash when starting.
+004 ==> 146760 : Providing a Timeline-View for quickly narrowing down the date of photos.
+005 ==> 146635 : Ratio crop doesn't remember orientation.
+006 ==> 144337 : There should be no "empty folders".
+007 ==> 128293 : Aspect ratio crop does not respect aspect ratio.
+008 ==> 157149 : digiKam crash at startup.
+009 ==> 141037 : Search using Tag Name does not work
+010 ==> 158174 : Precise aspect ratio crop feature
+011 ==> 142055 : Which whitebalance is used
+012 ==> 158558 : Delete Function in Tag Filters panel needs to make sure that the tag is
+ unselected when the tag is deleted.
+013 ==> 120309 : Change screen backgroundcolor of image tools.
+014 ==> 153775 : Download from camera: Renaming because of already existing file does not work.
+015 ==> 154346 : Can't rename in digiKam.
+016 ==> 154625 : Image-files are not renamed before they are saved to disk.
+017 ==> 154746 : Album selection window's size is wrong.
diff --git a/src/NEWS.0.9.5 b/src/NEWS.0.9.5
new file mode 100644
index 00000000..290a77f4
--- /dev/null
+++ b/src/NEWS.0.9.5
@@ -0,0 +1,103 @@
+**********************************************************************************************
+digiKam 0.9.5 - Release date: 2009-03-15
+
+NEW FEATURES:
+
+General : Internal CImg library updated to 1.3.0.
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 169037 : Crash when tagging photos.
+002 ==> 173314 : digiKam crashes while exiting the photo-editing mode in KDE 3.5.10.
+003 ==> 185279 : Raw pictures from Panasonic LX2 have totally wrong colours in digikam show modus.
+
+**********************************************************************************************
+digiKam 0.9.5-beta3 - Release date: 2009-01-27
+
+NEW FEATURES:
+
+Image Editor : New composition guide based on Diagonal Rules.
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 176477 : Files disappear while importing.
+002 ==> 179134 : Compile-error with libkdcraw > 0.1.5.
+003 ==> 162535 : Startup is extremely slow when famd is running.
+004 ==> 179413 : Support restoring blown out highlights.
+005 ==> 157313 : Option to reposition lighttable slide-list.
+006 ==> 180671 : Scanner import does not work.
+007 ==> 175387 : digikam-doc 0.9.4 sf.net tarball is outdated.
+008 ==> 181712 : Ubuntu 8.10 - digiKam finds not the correct images.
+
+**********************************************************************************************
+digiKam 0.9.5-beta2 - Release date: 2008-12-14
+
+NEW FEATURES:
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 164573 : Better support for small screens.
+002 ==> 175970 : digitaglinktree merges tags with same name in different subfolders.
+003 ==> 108760 : Use collection image (or part of) as Tag/Album icon.
+004 ==> 144078 : very slow avi startup.
+005 ==> 146258 : Moving an album into waste basket didn't remove.
+006 ==> 149165 : cannot edit anymore - sqlite lock?
+007 ==> 146025 : Wrong image name when using info from EXIF.
+008 ==> 167056 : Updating tags is slow when thumbnails are visible.
+009 ==> 141960 : Problems with photos without EXIV data when updating me.
+010 ==> 129379 : Renamed Album is shown multiple times although there is only on related picture directory.
+011 ==> 171247 : Album creation error when name start by a number.
+012 ==> 150906 : digiKam unable to connect to Panasonic LUMIX DMC-TZ3.
+013 ==> 148812 : "Auto Rotate/Flip &Using Exif Orientation" fails with some images.
+014 ==> 150342 : Camera image window keeps scrolling to the currently downloaded picture.
+015 ==> 147475 : digiKam Slideshow - Pause button does not stay sticky / work.
+016 ==> 148899 : Image Editor does not get the focus after clicking on an image.
+017 ==> 148596 : Empty entries in the "back" drop-down when changing month (date view).
+018 ==> 161387 : Unable to import photos with digiKam even though it's detected.
+019 ==> 165229 : Thumbnail complete update does not work reliably, even for jpgs.
+
+**********************************************************************************************
+digiKam 0.9.5-beta1 - Release date: 2008-11-05
+
+NEW FEATURES:
+
+
+Image Editor : All image plugin tool settings provide default buttons to reset values.
+Image Editor : New Raw import tool to handle Raw pictures with customized decoding settings.
+Image Editor : All image plugin dialogs are removed. All tools are embedded in editor window.
+
+General : libkdcraw dependency updated to 0.1.5
+General : TIFF/PNG/JPEG2000 metadata can be edited, changed (require Exiv2 >= 0.18).
+
+BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org):
+
+001 ==> 166867 : digiKam crashes when starting.
+002 ==> 167026 : Crash on startup Directory ImageSubIfd0 not valid.
+003 ==> 146870 : Don't reduce size of image when rotating.
+004 ==> 167528 : Remove hotlinking URL from tips.
+005 ==> 167343 : Albums view corrupted and unusable.
+006 ==> 146033 : When switching with Page* image jumps.
+007 ==> 127242 : Flashing 'histogram calculation in progress' is a bit annoying.
+008 ==> 150457 : More RAW controls in color management pop-up please.
+009 ==> 155074 : Edit Image should allow chance to adjust RAW conversion parameters.
+010 ==> 155076 : RAW Conversion UI Needs to be more generic.
+011 ==> 142975 : Better support for RAW photo handling.
+012 ==> 147136 : When selecting pictures from folder showfoto got closed.
+013 ==> 168780 : Loose the raw import.
+014 ==> 160564 : No refresh of the number of pictures assigned to a tag after removing the tag from some pictures.
+015 ==> 158144 : Showfoto crashes in settings window.
+016 ==> 162845 : 'Ctrl+F6' Conflict with KDE global shortcut.
+017 ==> 159523 : digiKam crashes while downloading images from Olympus MJU 810/Stylus 810 camera no xD card.
+018 ==> 161369 : Quick filter indicator lamp is not working properly in recursive image folder view mode.
+019 ==> 147314 : Renaming like crazy with F2 slows down my system.
+020 ==> 164622 : Crash using timeline when switching from month to week to month view.
+021 ==> 141951 : Blank empty context right-click menus.
+022 ==> 116886 : Proposal for adding a new destripe/denoise technique.
+023 ==> 163602 : Race condition during image download from Camera/ USB device: image corruption and/or loss.
+024 ==> 165857 : Unreliable import when photo root is on network share.
+025 ==> 147435 : Resize slider in sharpness dialog doesn't work correct.
+026 ==> 168844 : Make the selection rectangle draggable over the image (not only resizable).
+027 ==> 147151 : Compile error: multiple definition of `jpeg_suppress_tables'.
+028 ==> 170758 : High load when tagging.
+029 ==> 168003 : Drag&dropping a photo to a folder in Dolphin begins copying the whole system:/media.
+030 ==> 142457 : Temp files not cleaned up after crashes.
diff --git a/src/configure.in.bot b/src/configure.in.bot
new file mode 100644
index 00000000..8a34d97b
--- /dev/null
+++ b/src/configure.in.bot
@@ -0,0 +1,133 @@
+dnl Put here things to be done at the very end - telling users
+dnl about additional packages to install.
+
+echo ""
+echo "-- digiKam configure results -------------------"
+
+if test "x$included_sqlite3" = "xno"; then
+ if test "x$have_sqlite3" != "xyes"; then
+ echo "-- using internal libsqlite3...... NO"
+ echo "-- libsqlite3 library found....... NO"
+ echo ""
+ echo "digiKam have been set to be compiled using shared libsqlite3 library."
+ echo "digiKam needs libsqlite3 library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libsqlite3 website is http://www.sqlite.org"
+ echo ""
+ all_tests=bad
+ else
+ echo "-- using internal libsqlite3...... NO"
+ echo "-- libsqlite3 library found....... YES"
+ fi
+else
+ echo "-- using internal libsqlite3...... YES"
+fi
+
+if test "x$have_libgphoto2" != "xyes"; then
+ echo "-- libgphoto2 library found....... NO"
+ echo ""
+ echo "digiKam needs libgphoto2 library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libgphoto2 website is http://www.gphoto.org"
+ echo ""
+ all_tests=bad
+else
+ echo "-- libgphoto2 library found....... YES"
+fi
+
+if test "x$have_tiff" != "xyes"; then
+ echo "-- libtiff library found.......... NO"
+ echo ""
+ echo "digiKam needs libtiff library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libtiff website is http://www.remotesensing.org/libtiff"
+ echo ""
+ all_tests=bad
+else
+ echo "-- libtiff library found..... .... YES"
+fi
+
+if test "x$have_png" != "xyes"; then
+ echo "-- libpng library found........... NO"
+ echo ""
+ echo "digiKam needs libpng library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libpng website is http://www.libpng.org/pub/png/libpng.html"
+ echo ""
+ all_tests=bad
+else
+ echo "-- libpng library found........... YES"
+fi
+
+if test "x$have_jasper" != "xyes"; then
+ echo "-- libjasper library found........ NO"
+ echo ""
+ echo "digiKam needs libjasper library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libjasper website is http://www.ece.uvic.ca/~mdadams/jasper"
+ echo "Important note: libjasper has to be configured with --enable-shared=yes"
+ echo "as otherwise the required dynamic libraries are not created."
+ echo ""
+ all_tests=bad
+else
+ echo "-- libjasper library found........ YES"
+fi
+
+if test "x$have_lcms" != "xyes"; then
+ echo "-- liblcms library found.......... NO"
+ echo ""
+ echo "digiKam needs liblcms library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "liblcms website is http://www.littlecms.com"
+ echo ""
+ all_tests=bad
+else
+ echo "-- liblcms library found.......... YES"
+fi
+
+if test "x$have_libkipi" != "xyes"; then
+ echo "-- libkipi library found.......... NO"
+ echo ""
+ echo "digiKam needs libkipi library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libkipi website is http://www.kipi-plugins.org"
+ echo ""
+ all_tests=bad
+else
+ echo "-- libkipi library found.......... YES"
+fi
+
+if test "x$have_libkexiv2" != "xyes"; then
+ echo "-- libkexiv2 library found........ NO"
+ echo ""
+ echo "digiKam needs libkexiv2 library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libkexiv2 website is http://www.kipi-plugins.org"
+ echo ""
+ all_tests=bad
+else
+ echo "-- libkexiv2 library found........ YES"
+fi
+
+if test "x$have_libkdcraw" != "xyes"; then
+ echo "-- libkdcraw library found........ NO"
+ echo ""
+ echo "digiKam needs libkdcraw library development package."
+ echo "You need to install the right version first."
+ echo "Look depencies description from README for details."
+ echo "libkdcraw website is at http://www.kipi-plugins.org"
+ echo ""
+ all_tests=bad
+else
+ echo "-- libkdcraw library found........ YES"
+fi
+
+echo "------------------------------------------------"
diff --git a/src/configure.in.in b/src/configure.in.in
new file mode 100644
index 00000000..7095137f
--- /dev/null
+++ b/src/configure.in.in
@@ -0,0 +1,387 @@
+#MIN_CONFIG(3)
+
+# -----------------------------------------------------------------
+#
+# enable hidden visibility only if kde >= 3.3.2 and tdelibs has
+# been compiled with visibility enabled
+#
+# -----------------------------------------------------------------
+
+AC_LANG_PUSH(C++)
+digikam_save_cppflags=$CPPFLAGS
+CPPFLAGS="$CPPFLAGS $all_includes"
+AC_MSG_CHECKING([if hidden visibility should be enabled])
+AC_COMPILE_IFELSE(
+ [
+ #include <tdeversion.h>
+ #include <tdemacros.h>
+ int other_func( void )
+ {
+ #if KDE_IS_VERSION(3,3,2)
+ #else
+ iam dying;
+ #endif
+ #ifdef __TDE_HAVE_GCC_VISIBILITY
+ #else
+ no, iam really dead;
+ #endif
+ return 0;
+ }
+ ],
+ [ AC_MSG_RESULT([yes])
+ digikam_enable_hidden_visibility="yes" ],
+ [ AC_MSG_RESULT([no]) ]
+)
+CPPFLAGS=$digikam_save_cppflags
+AC_LANG_POP(C++)
+
+if test "x$digikam_enable_hidden_visibility" = "xyes"; then
+ KDE_ENABLE_HIDDEN_VISIBILITY
+fi
+
+# -----------------------------------------------------------------
+#
+# pkg config check
+#
+# -----------------------------------------------------------------
+
+AC_ARG_VAR(PKGCONFIGFOUND, [Path to pkg-config])
+AC_CHECK_PROG(PKGCONFIGFOUND, pkg-config,[yes])
+
+# -----------------------------------------------------------------
+#
+# sqlite2 type check
+#
+# -----------------------------------------------------------------
+
+KDE_CHECK_TYPES
+
+# -------------------------------------------------------
+#
+# Check endianness
+#
+# -------------------------------------------------------
+
+AC_LANG_SAVE
+AC_LANG_C
+AC_C_BIGENDIAN
+AC_LANG_RESTORE
+
+# -----------------------------------------------------------------
+#
+# Check for liblcms
+#
+# -----------------------------------------------------------------
+
+have_lcms_header='no'
+KDE_CHECK_HEADER(lcms/lcms.h,have_lcms_header='yes',,)
+if test "$have_lcms_header" = 'yes'
+then
+ AC_DEFINE(LCMS_HEADER, <lcms/lcms.h>, [The correct header])
+else
+ # Alternative! Debian does it this way...
+ KDE_CHECK_HEADER(lcms.h,have_lcms_header='yes',,)
+ if test "$have_lcms_header" = 'yes'
+ then
+ AC_DEFINE(LCMS_HEADER, <lcms.h>, [The correct header])
+ fi
+fi
+
+LCMS_LIBS=''
+have_lcms='no'
+if test "$have_lcms_header" = 'yes'
+then
+ saved_cflags="$CFLAGS"
+ saved_ldflags="$LDFLAGS"
+ saved_libs=$LIBS
+ LIBS="$LIBS -llcms"
+ CFLAGS="$CFLAGS $all_includes"
+ LDFLAGS="$LDFLAGS $all_libraries"
+ AC_TRY_LINK([
+#define inline __inline /* gcc is in ansi mode */
+#include LCMS_HEADER
+#if LCMS_VERSION < 112
+choke!
+#endif
+], [
+cmsOpenProfileFromFile("foo", "r");
+],
+ [LCMS_LIBS='-llcms'; have_lcms='yes'])
+ LIBS=$saved_libs
+ CFLAGS=$saved_cflags
+ LDFLAGS=$saved_ldflags
+fi
+
+if test -z "$LCMS_LIBS"; then
+ DO_NOT_COMPILE="$DO_NOT_COMPILE digikam"
+fi
+
+AC_SUBST(LCMS_LIBS)
+
+#------------------------------------------------------------------
+#
+# Check for libgphoto2
+#
+#------------------------------------------------------------------
+
+KDE_PKG_CHECK_MODULES(GPHOTO2, libgphoto2 >= 2.5,
+ [have_libgphoto2=yes; have_gphoto25=yes], have_libgphoto2=no)
+if test "x$have_libgphoto2" = "xno"; then
+ KDE_PKG_CHECK_MODULES(GPHOTO2, libgphoto2,
+ have_libgphoto2=yes, have_libgphoto2=no)
+fi
+if test "x$have_libgphoto2" = "xno"; then
+ AC_PATH_PROG(GPHOTO_CONFIG,gphoto2-config)
+ AC_PATH_PROG(GPHOTO_PORT_CONFIG,gphoto2-port-config)
+ if test -n "${GPHOTO_CONFIG}"; then
+ GPHOTO_VERSION="`$GPHOTO_CONFIG --version`"
+ case "${GPHOTO_VERSION}" in "libgphoto2 2.5"*) CXXFLAGS="$CXXFLAGS -DHAVE_GPHOTO25";; esac
+ GPHOTO_CFLAGS="`$GPHOTO_CONFIG --cflags`"
+ AC_SUBST(GPHOTO_CFLAGS)
+ LIB_GPHOTO="`$GPHOTO_CONFIG --libs` `$GPHOTO_PORT_CONFIG --libs`"
+ AC_SUBST(LIB_GPHOTO)
+ have_libgphoto2=yes
+ else
+ AC_MSG_WARN([gPhoto2 not found.])
+ DO_NOT_COMPILE="digikam $DO_NOT_COMPILE"
+ fi
+else
+ if test "x$have_gphoto25" = "xyes"; then
+ GPHOTO_CFLAGS="$GPHOTO2_CFLAGS -DHAVE_GPHOTO25"
+ else
+ GPHOTO_CFLAGS="$GPHOTO2_CFLAGS"
+ fi
+ LIB_GPHOTO="$GPHOTO2_LIBS"
+ AC_SUBST(GPHOTO_CFLAGS)
+ AC_SUBST(LIB_GPHOTO)
+fi
+
+#------------------------------------------------------------------
+#
+# Check for libkipi
+#
+#------------------------------------------------------------------
+
+if test "$PKGCONFIGFOUND" = "yes" ; then
+ have_libkipi=no
+
+ KDE_PKG_CHECK_MODULES(LIBKIPI, libkipi >= 0.1.5,
+ have_libkipi=yes, have_libkipi=no)
+
+ if test "x$have_libkipi" = "xno"; then
+ LIBKIPI_CFLAGS=""
+ LIBKIPI_LIBS=""
+ AC_MSG_RESULT([not found])
+ else
+ AC_MSG_RESULT([found])
+ fi
+else
+ LIBKIPI_CFLAGS=""
+ LIBKIPI_LIBS=""
+ AC_MSG_RESULT([not found])
+fi
+AC_SUBST(LIBKIPI_CFLAGS)
+AC_SUBST(LIBKIPI_LIBS)
+
+if test "x$have_libkipi" != "xyes"; then
+ DO_NOT_COMPILE="$DO_NOT_COMPILE digikam"
+fi
+
+# --------------------------------------------------------------------
+#
+# Check for libkexiv2
+#
+# --------------------------------------------------------------------
+
+if test "$PKGCONFIGFOUND" = "yes" ; then
+ have_libkexiv2=no
+
+ KDE_PKG_CHECK_MODULES(LIBKEXIV2, libkexiv2 >= 0.1.6,
+ have_libkexiv2=yes, have_libkexiv2=no)
+
+ if test "x$have_libkexiv2" = "xno"; then
+ LIBKEXIV2_CFLAGS=""
+ LIBKEXIV2_LIBS=""
+ AC_MSG_RESULT([not found])
+ else
+ AC_MSG_RESULT([found])
+ fi
+else
+ LIBKEXIV2_CFLAGS=""
+ LIBKEXIV2_LIBS=""
+ AC_MSG_RESULT([not found])
+fi
+AC_SUBST(LIBKEXIV2_CFLAGS)
+AC_SUBST(LIBKEXIV2_LIBS)
+
+if test "x$have_libkexiv2" != "xyes"; then
+ DO_NOT_COMPILE="$DO_NOT_COMPILE digikam"
+fi
+
+# --------------------------------------------------------------------
+#
+# Check for libkdcraw
+#
+# --------------------------------------------------------------------
+
+if test "$PKGCONFIGFOUND" = "yes" ; then
+ have_libkdcraw=no
+
+ KDE_PKG_CHECK_MODULES(LIBKDCRAW, libkdcraw >= 0.1.5,
+ have_libkdcraw=yes, have_libkdcraw=no)
+
+ if test "x$have_libkdcraw" = "xno"; then
+ LIBKDCRAW_CFLAGS=""
+ LIBKDCRAW_LIBS=""
+ AC_MSG_RESULT([not found])
+ else
+ AC_MSG_RESULT([found])
+ fi
+else
+ LIBKDCRAW_CFLAGS=""
+ LIBKDCRAW_LIBS=""
+ AC_MSG_RESULT([not found])
+fi
+AC_SUBST(LIBKDCRAW_CFLAGS)
+AC_SUBST(LIBKDCRAW_LIBS)
+
+if test "x$have_libkdcraw" != "xyes"; then
+ DO_NOT_COMPILE="$DO_NOT_COMPILE digikam"
+fi
+
+#------------------------------------------------------------------
+#
+# Check for libtiff
+#
+#------------------------------------------------------------------
+
+have_tiff=no
+KDE_CHECK_LIB(tiff, TIFFWriteScanline,
+ have_tiff=yes,
+ AC_MSG_WARN([TIFF library not found]),
+ -ljpeg -lz -lm)
+
+if test "x$have_tiff" = "xyes"; then
+ KDE_CHECK_HEADER(tiffio.h, have_tiff=yes, have_tiff=no)
+fi
+
+if test "x$have_tiff" != "xyes"; then
+ AC_WARN([TIFF library not found, digiKam will not be compiled.])
+ DO_NOT_COMPILE="digikam $DO_NOT_COMPILE"
+else
+ LIB_TIFF="-ltiff"
+ AC_SUBST(LIB_TIFF)
+fi
+
+#------------------------------------------------------------------
+#
+# Check for libpng (with png_set_add_alpha() function)
+#
+#------------------------------------------------------------------
+
+have_png=no
+KDE_CHECK_LIB(png, png_set_add_alpha,
+ have_png=yes,
+ AC_MSG_WARN([digiKam requires libpng >= 1.2.7]),
+ -lpng -lz -lm)
+
+if test "x$have_png" != "xyes"; then
+ AC_WARN([digiKam requires libpng >= 1.2.7; digiKam will not be compiled.])
+ DO_NOT_COMPILE="digikam $DO_NOT_COMPILE"
+else
+ LIB_PNG="-lpng"
+ AC_SUBST(LIB_PNG)
+fi
+
+#------------------------------------------------------------------
+#
+# Check for libjasper (JPEG2000)
+#
+#------------------------------------------------------------------
+
+have_jasper=no
+KDE_CHECK_LIB(jasper, jas_init,
+ have_jasper=yes,
+ AC_MSG_WARN([digiKam requires libjasper >= 1.7.0]),
+ -ljasper)
+
+if test "x$have_jasper" != "xyes"; then
+ AC_WARN([digiKam requires libjasper >= 1.7.0; digiKam will not be compiled.])
+ DO_NOT_COMPILE="digikam $DO_NOT_COMPILE"
+else
+ LIB_JASPER="-ljasper"
+ AC_SUBST(LIB_JASPER)
+fi
+
+#------------------------------------------------------------------
+#
+# Check for local/shared sqlite3
+#
+#------------------------------------------------------------------
+
+LIB_SQLITE3=""
+
+AC_ARG_WITH(included-sqlite3,
+ AC_HELP_STRING([--without-included-sqlite3],[build digiKam using system sqlite3 library]),
+ [included_sqlite3=$withval],
+ [included_sqlite3=yes]
+)
+
+if test x$included_sqlite3 = xno; then
+ if test x$PKGCONFIGFOUND = xyes; then
+ PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.5, have_sqlite3=yes,have_sqlite3=no)
+
+ if test x$have_sqlite3 = xyes; then
+ ## AC_DEFINE(HAVE_SQLITE3, 1, [have SQLite3 database library])
+ LIB_SQLITE3=`pkg-config --libs sqlite3`
+ else
+ # We don't support not having sqlite3 anymore
+ DO_NOT_COMPILE="digikam $DO_NOT_COMPILE"
+ fi
+ fi
+fi
+
+AC_SUBST(LIB_SQLITE3)
+AM_CONDITIONAL(with_included_sqlite3, [test x$included_sqlite3 = xyes])
+
+###############################################################################
+# END SQLITE CHECK
+###############################################################################
+
+
+#------------------------------------------------------------------
+#
+# NFS is Evil (sqlite makes use of file locking for allowing
+# multiple processes to access the database. but on many
+# nfs implementations, this file locking is horribly broken and
+# can end up locking the app or not allowing access to the app.
+# since we use tdeioslaves which access the db too)
+#
+#------------------------------------------------------------------
+
+AC_ARG_ENABLE(nfs-hack,
+ AC_HELP_STRING([--enable-nfs-hack],
+[Enable a hack for album libraries on a nfs mount,
+ which causes the database to be saved in
+ $HOMEDIR/.trinity/share/apps/digikam/directoryname.db [default=disable]]),
+ [enable_nfs_hack=$enableval],
+ [enable_nfs_hack=no]
+)
+
+if test "x$enable_nfs_hack" = "xyes"; then
+ AC_DEFINE(NFS_HACK, 1, [NFS hack enabled])
+ AC_MSG_NOTICE([NFS hack enabled. Make sure you know what you are doing])
+fi
+
+#------------------------------------------------------------------
+#
+# get the gcc version
+#
+# CImg.h version 1.2.8 do not compile fine with gcc 4.3.x
+# See B.K.O #163118: digikam-0.9.4_beta5 compilation hangs with gcc 4.3
+# Using -fno-tree-pre is work around this problem.
+#
+#------------------------------------------------------------------
+
+KDE_CHECK_COMPILER_FLAG(fno-tree-pre,[CXXFLAGS="-fno-tree-pre $CXXFLAGS"])
+
diff --git a/src/digikam/Makefile.am b/src/digikam/Makefile.am
new file mode 100644
index 00000000..6f0ac90f
--- /dev/null
+++ b/src/digikam/Makefile.am
@@ -0,0 +1,170 @@
+METASOURCES = AUTO
+
+if with_included_sqlite3
+ LIB_SQLITE3_LOCAL = $(top_builddir)/src/libs/sqlite3/libsqlite3.la
+ SQLITE3_INCLUDES = -I$(top_srcdir)/src/libs/sqlite3
+endif
+
+INCLUDES = -I$(top_srcdir)/src/libs/sqlite2 \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/thumbbar \
+ -I$(top_srcdir)/src/libs/jpegutils \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/imageproperties \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_builddir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/utilities/cameragui \
+ -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/utilities/setup \
+ -I$(top_srcdir)/src/utilities/slideshow \
+ -I$(top_srcdir)/src/utilities/batch \
+ -I$(top_srcdir)/src/utilities/lighttable \
+ $(SQLITE3_INCLUDES) \
+ $(LIBKEXIV2_CFLAGS) \
+ $(LIBKIPI_CFLAGS) \
+ $(LIBKDCRAW_CFLAGS) \
+ $(GPHOTO_CFLAGS) \
+ $(all_includes)
+
+
+# -- shared digiKam library rules -----------------------------------------------
+
+lib_LTLIBRARIES = libdigikam.la
+
+# NOTE from Gilles (30-11-06): kdateedit.cpp and metadatahub.cpp must be placed on the top
+# of source file list to unbreak compilation with './configure -enable-final' option.
+# I suspect a problem with X11 header included into albumfolderview.cpp witch redefine 'enum' type.
+libdigikam_la_SOURCES = kdateedit.cpp \
+ metadatahub.cpp \
+ digikamapp.cpp \
+ album.cpp \
+ albumdb.cpp \
+ albumdb_sqlite2.cpp \
+ albumicongroupitem.cpp \
+ albumiconitem.cpp \
+ albumiconview.cpp \
+ albumiconviewfilter.cpp \
+ albumitemhandler.cpp \
+ albumfiletip.cpp \
+ albumfolderview.cpp \
+ albumhistory.cpp \
+ albumlister.cpp \
+ albummanager.cpp \
+ albumpropsedit.cpp \
+ albumsettings.cpp \
+ albumthumbnailloader.cpp \
+ albumwidgetstack.cpp \
+ cameralist.cpp \
+ cameratype.cpp \
+ datefolderview.cpp \
+ dcopiface.cpp \
+ dcopiface.skel \
+ digikamfirstrun.cpp \
+ digikamview.cpp \
+ dio.cpp \
+ dragobjects.cpp \
+ firstrun.cpp \
+ folderitem.cpp \
+ folderview.cpp \
+ iconview.cpp \
+ icongroupitem.cpp \
+ iconitem.cpp \
+ imageattributeswatch.cpp \
+ imageinfo.cpp \
+ imagepreviewview.cpp \
+ kdatetimeedit.cpp \
+ kdatepickerpopup.cpp \
+ kipiinterface.cpp \
+ mediaplayerview.cpp \
+ mimefilter.cpp \
+ monthwidget.cpp \
+ pixmapmanager.cpp \
+ ratingfilter.cpp \
+ ratingpopupmenu.cpp \
+ ratingwidget.cpp \
+ scanlib.cpp \
+ searchadvanceddialog.cpp \
+ searchfolderview.cpp \
+ searchquickdialog.cpp \
+ searchresultsview.cpp \
+ searchresultsitem.cpp \
+ searchwidgets.cpp \
+ syncjob.cpp \
+ tageditdlg.cpp \
+ tagfilterview.cpp \
+ tagfolderview.cpp \
+ tagspopupmenu.cpp \
+ timelinefolderview.cpp \
+ timelineview.cpp \
+ timelinewidget.cpp \
+ upgradedb_sqlite2tosqlite3.cpp \
+ welcomepageview.cpp
+
+# NOTE: if local libsqlite3 is used LIB_SQLITE3 is null.
+# if shared libsqlite3 is used LIB_SQLITE3_LOCAL is null.
+libdigikam_la_LIBADD = $(LIB_SQLITE3) \
+ $(LIB_SQLITE3_LOCAL) \
+ $(LIB_TQT) \
+ $(LIB_TDEPARTS) \
+ $(LIB_TDEIO) \
+ $(LIB_TDEABC) \
+ $(LIB_TDEHTML) \
+ $(LIBKIPI_LIBS) \
+ $(LIBKEXIV2_LIBS) \
+ $(LIB_TDEUTILS) \
+ $(top_builddir)/src/libs/sqlite2/libsqlite2.la \
+ $(top_builddir)/src/libs/thumbbar/libthumbbar.la \
+ $(top_builddir)/src/libs/themeengine/libthemeengine.la \
+ $(top_builddir)/src/libs/widgets/libwidgets.la \
+ $(top_builddir)/src/libs/dialogs/libdialog.la \
+ $(top_builddir)/src/libs/jpegutils/libjpegutils.la \
+ $(top_builddir)/src/libs/dimg/libdimg.la \
+ $(top_builddir)/src/libs/imageproperties/libimagepropertiesdigikam.la \
+ $(top_builddir)/src/libs/threadimageio/libthreadimageio.la \
+ $(top_builddir)/src/libs/greycstoration/libgreycstoration.la \
+ $(top_builddir)/src/utilities/batch/libbatch.la \
+ $(top_builddir)/src/utilities/slideshow/libslideshow.la \
+ $(top_builddir)/src/utilities/cameragui/libcameragui.la \
+ $(top_builddir)/src/utilities/imageeditor/canvas/libdimgcanvas.la \
+ $(top_builddir)/src/utilities/imageeditor/editor/libdimgeditor.la \
+ $(top_builddir)/src/utilities/setup/libsetup.la \
+ $(top_builddir)/src/utilities/lighttable/liblighttable.la
+
+libdigikam_la_LDFLAGS = $(LIBKEXIV2_LIBS) $(all_libraries) $(KDE_RPATH) -no-undefined
+
+# -- main digiKam binary target file rules ----------------------------------------------
+
+bin_PROGRAMS = digikam
+
+digikam_SOURCES = main.cpp
+
+digikam_LDADD = $(LIB_TQT) \
+ $(LIB_TDECORE) \
+ $(LIB_TDEUI) \
+ $(LIBSOCKET) \
+ $(LIB_TDEFILE) \
+ $(LIB_TDEPARTS) \
+ $(LIB_TDEUTILS) \
+ libdigikam.la
+
+digikam_LDFLAGS = $(LIBKIPI_LIBS) $(LIBKDCRAW_LIBS) $(LIBKEXIV2_LIBS) $(KDE_RPATH) $(all_libraries) $(LIB_TDEUTILS)
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamui.rc
+
+dummy_digikam.cpp:
+ echo > dummy_digikam.cpp
+
+xdg_apps_DATA = digikam.desktop
+
+include_HEADERS = digikam_export.h
+
+include ../../admin/Doxyfile.am
+noinst_HEADERS = dcopiface.h
diff --git a/src/digikam/album.cpp b/src/digikam/album.cpp
new file mode 100644
index 00000000..a6e3a295
--- /dev/null
+++ b/src/digikam/album.cpp
@@ -0,0 +1,543 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-15
+ * Description : digiKam album types
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albummanager.h"
+#include "albumdb.h"
+#include "album.h"
+
+namespace Digikam
+{
+
+Album::Album(Album::Type type, int id, bool root)
+{
+ m_parent = 0;
+ m_next = 0;
+ m_prev = 0;
+ m_firstChild = 0;
+ m_lastChild = 0;
+ m_clearing = false;
+ m_type = type;
+ m_id = id;
+ m_root = root;
+}
+
+Album::~Album()
+{
+ if (m_parent)
+ m_parent->removeChild(this);
+ clear();
+}
+
+void Album::setParent(Album* parent)
+{
+ if (parent)
+ {
+ m_parent = parent;
+ parent->insertChild(this);
+ }
+}
+
+Album* Album::parent() const
+{
+ return m_parent;
+}
+
+Album* Album::firstChild() const
+{
+ return m_firstChild;
+}
+
+Album* Album::lastChild() const
+{
+ return m_lastChild;
+}
+
+Album* Album::next() const
+{
+ return m_next;
+}
+
+Album* Album::prev() const
+{
+ return m_prev;
+}
+
+void Album::insertChild(Album* child)
+{
+ if (!child)
+ return;
+
+ if (!m_firstChild)
+ {
+ m_firstChild = child;
+ m_lastChild = child;
+ child->m_next = 0;
+ child->m_prev = 0;
+ }
+ else
+ {
+ m_lastChild->m_next = child;
+ child->m_prev = m_lastChild;
+ child->m_next = 0;
+ m_lastChild = child;
+ }
+}
+
+void Album::removeChild(Album* child)
+{
+ if (!child || m_clearing)
+ return;
+
+ if (child == m_firstChild)
+ {
+ m_firstChild = m_firstChild->m_next;
+ if (m_firstChild)
+ m_firstChild->m_prev = 0;
+ else
+ m_firstChild = m_lastChild = 0;
+ }
+ else if (child == m_lastChild)
+ {
+ m_lastChild = m_lastChild->m_prev;
+ if (m_lastChild)
+ m_lastChild->m_next = 0;
+ else
+ m_firstChild = m_lastChild = 0;
+ }
+ else
+ {
+ Album* c = child;
+ if (c->m_prev)
+ c->m_prev->m_next = c->m_next;
+ if (c->m_next)
+ c->m_next->m_prev = c->m_prev;
+ }
+}
+
+void Album::clear()
+{
+ m_clearing = true;
+
+ Album* child = m_firstChild;
+ Album* nextChild;
+ while (child)
+ {
+ nextChild = child->m_next;
+ delete child;
+ child = nextChild;
+ }
+
+ m_firstChild = 0;
+ m_lastChild = 0;
+ m_clearing = false;
+}
+
+int Album::globalID() const
+{
+ switch (m_type)
+ {
+ case (PHYSICAL):
+ return 10000 + m_id;
+ case(TAG):
+ return 20000 + m_id;
+ case(DATE):
+ return 30000 + m_id;
+ case(SEARCH):
+ return 40000 + m_id;
+ default:
+ DError() << "Unknown album type" << endl;
+ return -1;
+ }
+}
+
+int Album::id() const
+{
+ return m_id;
+}
+
+void Album::setTitle(const TQString& title)
+{
+ m_title = title;
+}
+
+TQString Album::title() const
+{
+ return m_title;
+}
+
+Album::Type Album::type() const
+{
+ return m_type;
+}
+
+void Album::setExtraData(const void* key, void* value)
+{
+ m_extraMap.replace(key, value);
+}
+
+void Album::removeExtraData(const void* key)
+{
+ m_extraMap.remove(key);
+}
+
+void* Album::extraData(const void* key) const
+{
+ typedef TQMap<const void*, void*> Map;
+ Map::const_iterator it = m_extraMap.find(key);
+ if (it == m_extraMap.end())
+ return 0;
+
+ return it.data();
+}
+
+bool Album::isRoot() const
+{
+ return m_root;
+}
+
+bool Album::isAncestorOf(Album* album) const
+{
+ bool val = false;
+ Album* a = album;
+ while (a && !a->isRoot())
+ {
+ if (a == this)
+ {
+ val = true;
+ break;
+ }
+ a = a->parent();
+ }
+ return val;
+}
+
+// ------------------------------------------------------------------------------
+
+PAlbum::PAlbum(const TQString& title, int id, bool root)
+ : Album(Album::PHYSICAL, id, root)
+{
+ setTitle(title);
+ m_caption = "";
+ m_collection = "";
+ m_date = TQDate::currentDate();
+}
+
+PAlbum::~PAlbum()
+{
+}
+
+void PAlbum::setCaption(const TQString& caption)
+{
+ m_caption = caption;
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ db->setAlbumCaption(id(), m_caption);
+}
+
+void PAlbum::setCollection(const TQString& collection)
+{
+ m_collection = collection;
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ db->setAlbumCollection(id(), m_collection);
+}
+
+void PAlbum::setDate(const TQDate& date)
+{
+ m_date = date;
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ db->setAlbumDate(id(), m_date);
+}
+
+TQString PAlbum::caption() const
+{
+ return m_caption;
+}
+
+TQString PAlbum::collection() const
+{
+ return m_collection;
+}
+
+TQDate PAlbum::date() const
+{
+ return m_date;
+}
+
+TQString PAlbum::url() const
+{
+ TQString u("");
+ if (isRoot())
+ {
+ return "/";
+ }
+ else if (parent())
+ {
+ u = ((PAlbum*)parent())->url();
+ if (!u.endsWith("/"))
+ u += '/';
+ }
+ u += title();
+ return u;
+}
+
+KURL PAlbum::kurl() const
+{
+ KURL u;
+ u.setProtocol("digikamalbums");
+ u.setUser(AlbumManager::instance()->getLibraryPath());
+ // add an empty host. KURLDrag will eat away the user
+ // attribute if a host is not present. probably a URL
+ // specification
+ u.setHost(" ");
+ u.setPath(url());
+ return u;
+}
+
+TQString PAlbum::prettyURL() const
+{
+ TQString u = i18n("My Albums") + url();
+ return u;
+}
+
+TQString PAlbum::icon() const
+{
+ return m_icon;
+}
+
+KURL PAlbum::iconKURL() const
+{
+ KURL u;
+ u.setPath( m_icon );
+ return u;
+}
+
+TQString PAlbum::folderPath() const
+{
+ KURL u(AlbumManager::instance()->getLibraryPath());
+ u.addPath(url());
+ return u.path();
+}
+
+// --------------------------------------------------------------------------
+
+TAlbum::TAlbum(const TQString& title, int id, bool root)
+ : Album(Album::TAG, id, root)
+{
+ setTitle(title);
+}
+
+TAlbum::~TAlbum()
+{
+}
+
+TQString TAlbum::tagPath(bool leadingSlash) const
+{
+ if (isRoot())
+ return leadingSlash ? "/" : "";
+
+ TQString u;
+
+ if (parent())
+ {
+ u = ((TAlbum*)parent())->tagPath(leadingSlash);
+ if (!parent()->isRoot())
+ u += '/';
+ }
+
+ u += title();
+
+ return u;
+}
+
+TQString TAlbum::prettyURL() const
+{
+ TQString u = i18n("My Tags") + tagPath(true);
+ return u;
+}
+
+KURL TAlbum::kurl() const
+{
+ KURL url;
+ url.setProtocol("digikamtags");
+
+ if (isRoot())
+ {
+ url.setPath("/");
+ }
+ else if (parent())
+ {
+ TAlbum *p = static_cast<TAlbum*>(parent());
+ url.setPath(p->kurl().path(1));
+ url.addPath(TQString::number(id()));
+ }
+ else
+ {
+ url = KURL();
+ }
+ return url;
+}
+
+
+TQString TAlbum::icon() const
+{
+ return m_icon;
+}
+
+// --------------------------------------------------------------------------
+
+int DAlbum::m_uniqueID = 0;
+
+DAlbum::DAlbum(const TQDate& date, bool root, Range range)
+ : Album(Album::DATE, root ? 0 : ++m_uniqueID, root)
+{
+ m_date = date;
+ m_range = range;
+
+ // Set the name of the date album
+ TQString dateTitle;
+
+ if (m_range == Month)
+ dateTitle = m_date.toString("MMMM yyyy");
+ else
+ dateTitle = m_date.toString("yyyy");
+
+ setTitle(dateTitle);
+}
+
+DAlbum::~DAlbum()
+{
+}
+
+TQDate DAlbum::date() const
+{
+ return m_date;
+}
+
+DAlbum::Range DAlbum::range() const
+{
+ return m_range;
+}
+
+KURL DAlbum::kurl() const
+{
+ TQDate endDate;
+ if (m_range == Month)
+ endDate = m_date.addMonths(1);
+ else
+ endDate = m_date.addYears(1);
+
+ KURL u;
+ u.setProtocol("digikamdates");
+ u.setPath(TQString("/%1/%2/%3/%4")
+ .arg(m_date.year())
+ .arg(m_date.month())
+ .arg(endDate.year())
+ .arg(endDate.month()));
+
+ return u;
+}
+
+// --------------------------------------------------------------------------
+
+SAlbum::SAlbum(int id, const KURL& url, bool simple, bool root)
+ : Album(Album::SEARCH, id, root),
+ m_kurl(url), m_simple(simple)
+{
+ setTitle(url.queryItem("name"));
+}
+
+SAlbum::~SAlbum()
+{
+}
+
+KURL SAlbum::kurl() const
+{
+ return m_kurl;
+}
+
+bool SAlbum::isSimple() const
+{
+ return m_simple;
+}
+
+// --------------------------------------------------------------------------
+
+AlbumIterator::AlbumIterator(Album *album)
+{
+ m_root = album;
+ m_current = album ? album->firstChild() : 0;
+}
+
+AlbumIterator::~AlbumIterator()
+{
+}
+
+AlbumIterator& AlbumIterator::operator++()
+{
+ if (!m_current)
+ return *this;
+
+ Album *album = m_current->firstChild();
+ if ( !album )
+ {
+ while ( (album = m_current->next()) == 0 )
+ {
+ m_current = m_current->parent();
+
+ if ( m_current == m_root )
+ {
+ // we have reached the root.
+ // that means no more children
+ m_current = 0;
+ break;
+ }
+
+ if ( m_current == 0 )
+ break;
+ }
+ }
+
+ m_current = album;
+ return *this;
+}
+
+Album* AlbumIterator::operator*()
+{
+ return m_current;
+}
+
+Album* AlbumIterator::current() const
+{
+ return m_current;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/album.h b/src/digikam/album.h
new file mode 100644
index 00000000..ed088185
--- /dev/null
+++ b/src/digikam/album.h
@@ -0,0 +1,455 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-15
+ * Description : digiKam album types
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file album.h */
+
+#ifndef ALBUM_H
+#define ALBUM_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqdatetime.h>
+#include <tqmap.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+namespace Digikam
+{
+
+/**
+ * \class Album
+ * \brief Abstract base class for all album types
+ *
+ * A class which provides an abstraction for a type Album. This class is meant to
+ * be derived and every time a new Album Type is defined add a enum corresponding
+ * to that to Album::Type
+ *
+ * This class provides a means of building a tree representation for
+ * Albums @see Album::setParent().
+ */
+
+class Album
+{
+public:
+
+ enum Type
+ {
+ PHYSICAL=0, /**< PHYSICAL: A physical album type @see PAlbum */
+ TAG, /**< TAG: A tag album type @see TAlbum */
+ DATE, /**< DATE: A date album type @see DAlbum */
+ SEARCH /**< SEARCH: A search album type @see SAlbum */
+ };
+
+ /**
+ * Destructor
+ *
+ * this will also recursively delete all child Albums
+ */
+ virtual ~Album();
+
+ /**
+ * Delete all child albums and also remove any associated extra data
+ */
+ void clear();
+
+ /**
+ * @return the parent album for this album
+ */
+ Album* parent() const;
+
+ /**
+ * @return the first child of this album or 0 if no children
+ */
+ Album* firstChild() const;
+
+ /**
+ * @return the last child of this album or 0 if no children
+ */
+ Album* lastChild() const;
+
+ /**
+ * @return the next sibling of this album of this album or 0
+ * if no next sibling
+ * @see AlbumIterator
+ */
+ Album* next() const;
+
+ /**
+ * @return the previous sibling of this album of this album or 0 if no
+ * previous sibling
+ * @see AlbumIterator
+ */
+ Album* prev() const;
+
+ /**
+ * @return the type of album
+ * @see Type
+ */
+ Type type() const;
+
+ /**
+ * Each album has a @p ID uniquely identifying it in the set of Albums of
+ * a Type
+ *
+ * \note The @p ID for a root Album is always 0
+ *
+ * @return the @p ID of the album
+ * @see globalID()
+ */
+ int id() const;
+
+ /**
+ * An album ID is only unique among the set of all Albums of its Type.
+ * This is a global Identifier which will uniquely identifying the Album
+ * among all Albums
+ *
+ * \note If you are adding a new Album Type make sure to update
+ * this implementation.
+ *
+ * You can always get the @p ID of the album using something like
+ *
+ * \code
+ * int albumID = rootAlbum->globalID() - album->globalID();
+ * \endcode
+ *
+ * @return the @p globalID of the album
+ * @see id()
+ */
+ int globalID() const;
+
+ /**
+ * @return the @p title aka name of the album
+ */
+ TQString title() const;
+
+ /**
+ * @return the kde url of the album
+ */
+ virtual KURL kurl() const = 0;
+
+ /**
+ * @return true is the album is a Root Album
+ */
+ bool isRoot() const;
+
+ /**
+ * @return true if the @p album is in the parent hierarchy
+ *
+ * @param album Album to check whether it belongs in the child
+ * hierarchy
+ */
+ bool isAncestorOf(Album* album) const;
+
+ /**
+ * This allows to associate some "extra" data to a Album. As one
+ * Album can be used by several objects (often views) which all need
+ * to add some data, you have to use a key to reference your extra data
+ * within the Album.
+ *
+ * That way a Album can hold and provide access to all those views
+ * separately.
+ *
+ * for eg,
+ *
+ * \code
+ * album->setExtraData( this, searchFolderItem );
+ * \endcode
+ *
+ * and can later access the searchFolderItem by doing
+ *
+ * \code
+ * SearchFolderItem *item = static_cast<SearchFolderItem*>(album->extraData(this));
+ * \endcode
+ *
+ * Note: you have to remove and destroy the data you associated yourself
+ * when you don't need it anymore!
+ *
+ * @param key the key of the extra data
+ * @param value the value of the extra data
+ * @see extraData
+ * @see removeExtraData
+ */
+ void setExtraData(const void* key, void *value);
+
+ /**
+ * Remove the associated extra data associated with @p key
+ *
+ * @param key the key of the extra data
+ * @see setExtraData
+ * @see extraData
+ */
+ void removeExtraData(const void* key);
+
+ /**
+ * Retrieve the associated extra data associated with @p key
+ *
+ * @param key the key of the extra data
+ * @see setExtraData
+ * @see extraData
+ */
+ void* extraData(const void* key) const;
+
+protected:
+
+ /**
+ * Constructor
+ */
+ Album(Album::Type type, int id, bool root);
+
+ /**
+ * @internal use only
+ *
+ * Set a new title for the album
+ *
+ * @param title new title for the album
+ */
+ void setTitle(const TQString& title);
+
+ /**
+ * @internal use only
+ *
+ * Set the parent of the album
+ *
+ * @param parent set the parent album of album to @p parent
+ */
+ void setParent(Album* parent);
+
+ /**
+ * @internal use only
+ *
+ * Insert an Album as a child for this album
+ *
+ * @param child the Album to add as child
+ */
+ void insertChild(Album* child);
+
+ /**
+ * @internal use only
+ *
+ * Remove a Album from the children list for this album
+ *
+ * @param child the Album to remove
+ */
+ void removeChild(Album* child);
+
+private:
+
+ /**
+ * Disable copy and default constructor
+ */
+ Album();
+ Album(const Album&);
+ Album& operator==(const Album&);
+
+private:
+
+ Type m_type;
+ int m_id;
+ bool m_root;
+ TQString m_title;
+
+ Album* m_parent;
+ Album* m_firstChild;
+ Album* m_lastChild;
+ Album* m_next;
+ Album* m_prev;
+ bool m_clearing;
+
+ TQMap<const void*, void*> m_extraMap;
+
+ friend class AlbumManager;
+};
+
+/**
+ * \class PAlbum
+ *
+ * A Physical Album representation
+ */
+
+class PAlbum : public Album
+{
+public:
+
+ PAlbum(const TQString& title, int id, bool root=false);
+ ~PAlbum();
+
+ void setCaption(const TQString& caption);
+ void setCollection(const TQString& collection);
+ void setDate(const TQDate& date);
+
+ TQString caption() const;
+ TQString collection() const;
+ TQDate date() const;
+ TQString url() const;
+ TQString prettyURL() const;
+ TQString folderPath() const;
+ KURL kurl() const;
+ TQString icon() const;
+ KURL iconKURL() const;
+
+private:
+
+ TQString m_collection;
+ TQString m_caption;
+ TQDate m_date;
+ TQString m_icon;
+
+ friend class AlbumManager;
+};
+
+/**
+ * \class TAlbum
+ *
+ * A Tag Album representation
+ */
+
+class TAlbum : public Album
+{
+public:
+
+ TAlbum(const TQString& title, int id, bool root=false);
+ ~TAlbum();
+
+ /**
+ * @return The tag path, e.g. "/People/Friend/John" if leadingSlash is true,
+ "People/Friend/John" if leadingSlash if false.
+ * The root TAlbum returns "/" resp. "".
+ */
+ TQString tagPath(bool leadingSlash = true) const;
+ KURL kurl() const;
+ TQString prettyURL() const;
+ TQString icon() const;
+
+private:
+
+ TQString m_icon;
+ int m_pid;
+
+ friend class AlbumManager;
+};
+
+/**
+ * \class DAlbum
+ *
+ * A Date Album representation
+ */
+
+class DAlbum : public Album
+{
+public:
+
+ enum Range
+ {
+ Month = 0,
+ Year
+ };
+
+ DAlbum(const TQDate& date, bool root=false, Range range=Month);
+ ~DAlbum();
+
+ TQDate date() const;
+ Range range() const;
+ KURL kurl() const;
+
+private:
+
+ static int m_uniqueID;
+
+ Range m_range;
+
+ TQDate m_date;
+
+ friend class AlbumManager;
+};
+
+/**
+ * \class SAlbum
+ *
+ * A Search Album representation
+ */
+
+class SAlbum : public Album
+{
+public:
+
+ SAlbum(int id, const KURL& url, bool simple, bool root=false);
+ ~SAlbum();
+
+ KURL kurl() const;
+ bool isSimple() const;
+
+private:
+
+ KURL m_kurl;
+ bool m_simple;
+
+ friend class AlbumManager;
+};
+
+/**
+ * \class AlbumIterator
+ *
+ * Iterate over all children of this Album.
+ * \note It will not include the specified album
+ *
+ * Example usage:
+ * \code
+ * AlbumIterator it(album);
+ * while ( it.current() )
+ * {
+ * DDebug() << "Album: " << it.current()->title() << endl;
+ * ++it;
+ * }
+ * \endcode
+ *
+ * \warning Do not delete albums using this iterator.
+ */
+
+class AlbumIterator
+{
+public:
+
+ AlbumIterator(Album *album);
+ ~AlbumIterator();
+
+ AlbumIterator& operator++();
+ Album* operator*();
+ Album* current() const;
+
+private:
+
+ AlbumIterator() {}
+ AlbumIterator(const AlbumIterator&) {}
+ AlbumIterator& operator=(const AlbumIterator&){ return *this; }
+
+ Album* m_current;
+ Album* m_root;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUM_H */
diff --git a/src/digikam/albumdb.cpp b/src/digikam/albumdb.cpp
new file mode 100644
index 00000000..9b08d5c4
--- /dev/null
+++ b/src/digikam/albumdb.cpp
@@ -0,0 +1,1599 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-18
+ * Description : database album interface.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+/** @file albumdb.cpp */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include "sqlite3.h"
+#include <sys/time.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+#include <cstdlib>
+#include <ctime>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+#include <tqdir.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albummanager.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albumsettings.h"
+
+namespace Digikam
+{
+
+typedef struct sqlite3_stmt sqlite3_stmt;
+typedef struct sqlite3 sqleet3; // hehe.
+
+class AlbumDBPriv
+{
+
+public:
+
+ AlbumDBPriv()
+ {
+ valid = false;
+ dataBase = 0;
+ }
+
+ bool valid;
+
+ sqleet3 *dataBase;
+
+ IntList recentlyAssignedTags;
+};
+
+AlbumDB::AlbumDB()
+{
+ d = new AlbumDBPriv;
+}
+
+AlbumDB::~AlbumDB()
+{
+ if (d->dataBase)
+ {
+ sqlite3_close(d->dataBase);
+ }
+
+ delete d;
+}
+
+bool AlbumDB::isValid() const
+{
+ return d->valid;
+}
+
+void AlbumDB::setDBPath(const TQString& path)
+{
+ if (d->dataBase)
+ {
+ sqlite3_close(d->dataBase);
+ d->dataBase = 0;
+ }
+
+ d->valid = false;
+
+ sqlite3_open(TQFile::encodeName(path), &d->dataBase);
+ if (d->dataBase == 0)
+ {
+ DWarning() << "Cannot open database: "
+ << sqlite3_errmsg(d->dataBase)
+ << endl;
+ }
+ else
+ {
+ initDB();
+ }
+}
+
+void AlbumDB::initDB()
+{
+ d->valid = false;
+
+ // Check if we have the required tables
+
+ TQStringList values;
+
+ if (!execSql( TQString("SELECT name FROM sqlite_master"
+ " WHERE type='table'"
+ " ORDER BY name;"),
+ &values ))
+ {
+ return;
+ }
+
+ if (!values.contains("Albums"))
+ {
+ if (!execSql( TQString("CREATE TABLE Albums\n"
+ " (id INTEGER PRIMARY KEY,\n"
+ " url TEXT NOT NULL UNIQUE,\n"
+ " date DATE NOT NULL,\n"
+ " caption TEXT,\n"
+ " collection TEXT,\n"
+ " icon INTEGER);") ))
+ {
+ return;
+ }
+
+ if (!execSql( TQString("CREATE TABLE Tags\n"
+ " (id INTEGER PRIMARY KEY,\n"
+ " pid INTEGER,\n"
+ " name TEXT NOT NULL,\n"
+ " icon INTEGER,\n"
+ " iconkde TEXT,\n"
+ " UNIQUE (name, pid));") ))
+ {
+ return;
+ }
+
+ if (!execSql( TQString("CREATE TABLE TagsTree\n"
+ " (id INTEGER NOT NULL,\n"
+ " pid INTEGER NOT NULL,\n"
+ " UNIQUE (id, pid));") ))
+ {
+ return;
+ }
+
+ if (!execSql( TQString("CREATE TABLE Images\n"
+ " (id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL,\n"
+ " dirid INTEGER NOT NULL,\n"
+ " caption TEXT,\n"
+ " datetime DATETIME,\n"
+ " UNIQUE (name, dirid));") ))
+ {
+ return;
+ }
+
+
+ if (!execSql( TQString("CREATE TABLE ImageTags\n"
+ " (imageid INTEGER NOT NULL,\n"
+ " tagid INTEGER NOT NULL,\n"
+ " UNIQUE (imageid, tagid));") ))
+ {
+ return;
+ }
+
+ if (!execSql( TQString("CREATE TABLE ImageProperties\n"
+ " (imageid INTEGER NOT NULL,\n"
+ " property TEXT NOT NULL,\n"
+ " value TEXT NOT NULL,\n"
+ " UNIQUE (imageid, property));") ))
+ {
+ return;
+ }
+
+ if ( !execSql( TQString( "CREATE TABLE Searches \n"
+ " (id INTEGER PRIMARY KEY, \n"
+ " name TEXT NOT NULL UNIQUE, \n"
+ " url TEXT NOT NULL);" ) ) )
+ {
+ return;
+ }
+
+ if (!execSql( TQString("CREATE TABLE Settings \n"
+ "(keyword TEXT NOT NULL UNIQUE,\n"
+ " value TEXT);") ))
+ return;
+ else
+ setSetting("DBVersion","1");
+
+ // TODO: see which more indices are needed
+ // create indices
+ execSql("CREATE INDEX dir_index ON Images (dirid);");
+ execSql("CREATE INDEX tag_index ON ImageTags (tagid);");
+
+ // create triggers
+
+ // trigger: delete from Images/ImageTags/ImageProperties
+ // if Album has been deleted
+ execSql("CREATE TRIGGER delete_album DELETE ON Albums\n"
+ "BEGIN\n"
+ " DELETE FROM ImageTags\n"
+ " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n"
+ " DELETE From ImageProperties\n"
+ " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n"
+ " DELETE FROM Images\n"
+ " WHERE dirid = OLD.id;\n"
+ "END;");
+
+ // trigger: delete from ImageTags/ImageProperties
+ // if Image has been deleted
+ execSql("CREATE TRIGGER delete_image DELETE ON Images\n"
+ "BEGIN\n"
+ " DELETE FROM ImageTags\n"
+ " WHERE imageid=OLD.id;\n"
+ " DELETE From ImageProperties\n "
+ " WHERE imageid=OLD.id;\n"
+ " UPDATE Albums SET icon=null \n "
+ " WHERE icon=OLD.id;\n"
+ " UPDATE Tags SET icon=null \n "
+ " WHERE icon=OLD.id;\n"
+ "END;");
+
+ // trigger: delete from ImageTags if Tag has been deleted
+ execSql("CREATE TRIGGER delete_tag DELETE ON Tags\n"
+ "BEGIN\n"
+ " DELETE FROM ImageTags WHERE tagid=OLD.id;\n"
+ "END;");
+
+ // trigger: insert into TagsTree if Tag has been added
+ execSql("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n"
+ "BEGIN\n"
+ " INSERT INTO TagsTree\n"
+ " SELECT NEW.id, NEW.pid\n"
+ " UNION\n"
+ " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n"
+ "END;");
+
+ // trigger: delete from TagsTree if Tag has been deleted
+ execSql("CREATE TRIGGER delete_tagstree DELETE ON Tags\n"
+ "BEGIN\n"
+ " DELETE FROM Tags\n"
+ " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n"
+ " DELETE FROM TagsTree\n"
+ " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n"
+ " DELETE FROM TagsTree\n"
+ " WHERE id=OLD.id;\n"
+ "END;");
+
+ // trigger: delete from TagsTree if Tag has been deleted
+ execSql("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n"
+ "BEGIN\n"
+ " DELETE FROM TagsTree\n"
+ " WHERE\n"
+ " ((id = OLD.id)\n"
+ " OR\n"
+ " id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n"
+ " AND\n"
+ " pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n"
+ " INSERT INTO TagsTree\n"
+ " SELECT NEW.id, NEW.pid\n"
+ " UNION\n"
+ " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n"
+ " UNION\n"
+ " SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n"
+ " UNION\n"
+ " SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n"
+ " WHERE\n"
+ " A.pid = NEW.id AND B.id = NEW.pid;\n"
+ "END;");
+ }
+
+ d->valid = true;
+}
+
+AlbumInfo::List AlbumDB::scanAlbums()
+{
+ AlbumInfo::List aList;
+
+ TQString basePath(AlbumManager::instance()->getLibraryPath());
+
+ TQStringList values;
+ execSql( "SELECT A.id, A.url, A.date, A.caption, A.collection, B.url, I.name \n "
+ "FROM Albums AS A \n "
+ " LEFT OUTER JOIN Images AS I ON A.icon=I.id \n"
+ " LEFT OUTER JOIN Albums AS B ON B.id=I.dirid;", &values);
+
+ TQString iconAlbumUrl, iconName;
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ AlbumInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.url = *it;
+ ++it;
+ info.date = TQDate::fromString(*it, TQt::ISODate);
+ ++it;
+ info.caption = *it;
+ ++it;
+ info.collection = *it;
+ ++it;
+ iconAlbumUrl = *it;
+ ++it;
+ iconName = *it;
+ ++it;
+
+ if (!iconName.isEmpty())
+ {
+ info.icon = basePath + iconAlbumUrl + '/' + iconName;
+ }
+
+ aList.append(info);
+ }
+
+ return aList;
+}
+
+TagInfo::List AlbumDB::scanTags()
+{
+ TagInfo::List tList;
+
+ TQString basePath(AlbumManager::instance()->getLibraryPath());
+
+ TQStringList values;
+ execSql( "SELECT T.id, T.pid, T.name, A.url, I.name, T.iconkde \n "
+ "FROM Tags AS T LEFT OUTER JOIN Images AS I ON I.id=T.icon \n "
+ " LEFT OUTER JOIN Albums AS A ON A.id=I.dirid; ", &values );
+
+ TQString iconName, iconKDE, albumURL;
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ TagInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.pid = (*it).toInt();
+ ++it;
+ info.name = *it;
+ ++it;
+ albumURL = *it;
+ ++it;
+ iconName = *it;
+ ++it;
+ iconKDE = *it;
+ ++it;
+
+ if ( albumURL.isEmpty() )
+ {
+ info.icon = iconKDE;
+ }
+ else
+ {
+ info.icon = basePath + albumURL + '/' + iconName;
+ }
+
+ tList.append(info);
+ }
+
+ return tList;
+}
+
+SearchInfo::List AlbumDB::scanSearches()
+{
+ SearchInfo::List searchList;
+
+ TQStringList values;
+ execSql( "SELECT id, name, url FROM Searches;", &values);
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ SearchInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.name = (*it);
+ ++it;
+ info.url = (*it);
+ ++it;
+
+ searchList.append(info);
+ }
+
+ return searchList;
+}
+
+void AlbumDB::beginTransaction()
+{
+ execSql( "BEGIN TRANSACTION;" );
+}
+
+void AlbumDB::commitTransaction()
+{
+ execSql( "COMMIT TRANSACTION;" );
+}
+
+int AlbumDB::addAlbum(const TQString& url, const TQString& caption,
+ const TQDate& date, const TQString& collection)
+{
+ if (!d->dataBase)
+ return -1;
+
+ execSql( TQString("REPLACE INTO Albums (url, date, caption, collection) "
+ "VALUES('%1', '%2', '%3', '%4');")
+ .arg(escapeString(url),
+ date.toString(TQt::ISODate),
+ escapeString(caption),
+ escapeString(collection)));
+
+ int id = sqlite3_last_insert_rowid(d->dataBase);
+ return id;
+}
+
+void AlbumDB::setAlbumCaption(int albumID, const TQString& caption)
+{
+ execSql( TQString("UPDATE Albums SET caption='%1' WHERE id=%2;")
+ .arg(escapeString(caption),
+ TQString::number(albumID) ));
+}
+
+void AlbumDB::setAlbumCollection(int albumID, const TQString& collection)
+{
+ execSql( TQString("UPDATE Albums SET collection='%1' WHERE id=%2;")
+ .arg(escapeString(collection),
+ TQString::number(albumID)) );
+}
+
+void AlbumDB::setAlbumDate(int albumID, const TQDate& date)
+{
+ execSql( TQString("UPDATE Albums SET date='%1' WHERE id=%2;")
+ .arg(date.toString(TQt::ISODate))
+ .arg(albumID) );
+}
+
+void AlbumDB::setAlbumIcon(int albumID, TQ_LLONG iconID)
+{
+ execSql( TQString("UPDATE Albums SET icon=%1 WHERE id=%2;")
+ .arg(iconID)
+ .arg(albumID) );
+}
+
+
+TQString AlbumDB::getAlbumIcon(int albumID)
+{
+ TQStringList values;
+ execSql( TQString("SELECT B.url, I.name \n "
+ "FROM Albums AS A \n "
+ " LEFT OUTER JOIN Images AS I ON I.id=A.icon \n "
+ " LEFT OUTER JOIN Albums AS B ON B.id=I.dirid \n "
+ "WHERE A.id=%1;")
+ .arg(albumID), &values );
+ if (values.isEmpty())
+ return TQString();
+
+ TQStringList::iterator it = values.begin();
+ TQString url = *it;
+ ++it;
+ TQString icon = *it;
+ if (icon.isEmpty())
+ return TQString();
+
+ TQString basePath(AlbumManager::instance()->getLibraryPath());
+ basePath += url;
+ basePath += '/' + icon;
+
+ return basePath;
+}
+
+void AlbumDB::deleteAlbum(int albumID)
+{
+ execSql( TQString("DELETE FROM Albums WHERE id=%1")
+ .arg(albumID) );
+}
+
+int AlbumDB::addTag(int parentTagID, const TQString& name, const TQString& iconKDE,
+ TQ_LLONG iconID)
+{
+ if (!d->dataBase)
+ return -1;
+
+ if (!execSql( TQString("INSERT INTO Tags (pid, name) "
+ "VALUES( %1, '%2')")
+ .arg(parentTagID)
+ .arg(escapeString(name))))
+ {
+ return -1;
+ }
+
+ int id = sqlite3_last_insert_rowid(d->dataBase);
+
+ if (!iconKDE.isEmpty())
+ {
+ execSql( TQString("UPDATE Tags SET iconkde='%1' WHERE id=%2;")
+ .arg(escapeString(iconKDE),
+ TQString::number(id)));
+ }
+ else
+ {
+ execSql( TQString("UPDATE Tags SET icon=%1 WHERE id=%2;")
+ .arg(iconID)
+ .arg(id));
+ }
+
+ return id;
+}
+
+void AlbumDB::deleteTag(int tagID)
+{
+ execSql( TQString("DELETE FROM Tags WHERE id=%1")
+ .arg(tagID) );
+}
+
+void AlbumDB::setTagIcon(int tagID, const TQString& iconKDE, TQ_LLONG iconID)
+{
+ if (!iconKDE.isEmpty())
+ {
+ execSql( TQString("UPDATE Tags SET iconkde='%1', icon=0 WHERE id=%2;")
+ .arg(escapeString(iconKDE),
+ TQString::number(tagID)));
+ }
+ else
+ {
+ execSql( TQString("UPDATE Tags SET icon=%1 WHERE id=%2;")
+ .arg(iconID)
+ .arg(tagID));
+ }
+}
+
+TQString AlbumDB::getTagIcon(int tagID)
+{
+ TQStringList values;
+ execSql( TQString("SELECT A.url, I.name, T.iconkde \n "
+ "FROM Tags AS T \n "
+ " LEFT OUTER JOIN Images AS I ON I.id=T.icon \n "
+ " LEFT OUTER JOIN Albums AS A ON A.id=I.dirid \n "
+ "WHERE T.id=%1;")
+ .arg(tagID), &values );
+
+ if (values.isEmpty())
+ return TQString();
+
+ TQString basePath(AlbumManager::instance()->getLibraryPath());
+
+ TQString iconName, iconKDE, albumURL, icon;
+
+ TQStringList::iterator it = values.begin();
+
+ albumURL = *it;
+ ++it;
+ iconName = *it;
+ ++it;
+ iconKDE = *it;
+ ++it;
+
+ if ( albumURL.isEmpty() )
+ {
+ icon = iconKDE;
+ }
+ else
+ {
+ icon = basePath + albumURL + '/' + iconName;
+ }
+
+ return icon;
+}
+
+void AlbumDB::setTagParentID(int tagID, int newParentTagID)
+{
+ execSql( TQString("UPDATE Tags SET pid=%1 WHERE id=%2;")
+ .arg(newParentTagID)
+ .arg(tagID) );
+}
+
+int AlbumDB::addSearch(const TQString& name, const KURL& url)
+{
+ if (!d->dataBase)
+ return -1;
+
+ TQString str("INSERT INTO Searches (name, url) \n"
+ "VALUES('$$@@$$', '$$##$$');");
+ str.replace("$$@@$$", escapeString(name));
+ str.replace("$$##$$", escapeString(url.url()));
+
+ if (!execSql(str))
+ {
+ return -1;
+ }
+
+ return sqlite3_last_insert_rowid(d->dataBase);
+}
+
+void AlbumDB::updateSearch(int searchID, const TQString& name,
+ const KURL& url)
+{
+ TQString str = TQString("UPDATE Searches SET name='$$@@$$', url='$$##$$' \n"
+ "WHERE id=%1")
+ .arg(searchID);
+ str.replace("$$@@$$", escapeString(name));
+ str.replace("$$##$$", escapeString(url.url()));
+
+ execSql(str);
+}
+
+void AlbumDB::deleteSearch(int searchID)
+{
+ execSql( TQString("DELETE FROM Searches WHERE id=%1")
+ .arg(searchID) );
+}
+
+void AlbumDB::setSetting(const TQString& keyword,
+ const TQString& value )
+{
+ execSql( TQString("REPLACE into Settings VALUES ('%1','%2');")
+ .arg(escapeString(keyword),
+ escapeString(value) ));
+}
+
+TQString AlbumDB::getSetting(const TQString& keyword)
+{
+ TQStringList values;
+ execSql( TQString("SELECT value FROM Settings "
+ "WHERE keyword='%1';")
+ .arg(escapeString(keyword)), &values );
+
+ if (values.isEmpty())
+ return TQString();
+ else
+ return values[0];
+}
+
+bool AlbumDB::execSql(const TQString& sql, TQStringList* const values,
+ const bool debug)
+{
+ if ( debug )
+ DDebug() << "SQL-query: " << sql << endl;
+
+ if ( !d->dataBase )
+ {
+ DWarning() << k_funcinfo << "SQLite pointer == NULL"
+ << endl;
+ return false;
+ }
+
+ const char* tail;
+ sqlite3_stmt* stmt;
+ int error;
+
+ //compile SQL program to virtual machine
+ error = sqlite3_prepare(d->dataBase, sql.utf8(), -1, &stmt, &tail);
+ if ( error != SQLITE_OK )
+ {
+ DWarning() << k_funcinfo
+ << "sqlite_compile error: "
+ << sqlite3_errmsg(d->dataBase)
+ << " on query: "
+ << sql << endl;
+ return false;
+ }
+
+ int cols = sqlite3_column_count(stmt);
+
+ while ( true )
+ {
+ error = sqlite3_step( stmt );
+
+ if ( error == SQLITE_DONE || error == SQLITE_ERROR )
+ break;
+
+ //iterate over columns
+ for ( int i = 0; values && i < cols; i++ )
+ {
+ *values << TQString::fromUtf8( (const char*)sqlite3_column_text( stmt, i ) );
+ }
+ }
+
+ sqlite3_finalize( stmt );
+
+ if ( error != SQLITE_DONE )
+ {
+ DWarning() << "sqlite_step error: "
+ << sqlite3_errmsg( d->dataBase )
+ << " on query: "
+ << sql << endl;
+ return false;
+ }
+
+ return true;
+}
+
+TQString AlbumDB::escapeString(TQString str) const
+{
+ str.replace( "'", "''" );
+ return str;
+}
+
+TQString AlbumDB::getItemCaption(TQ_LLONG imageID)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT caption FROM Images "
+ "WHERE id=%1;")
+ .arg(imageID),
+ &values );
+
+ if (!values.isEmpty())
+ return values[0];
+ else
+ return TQString();
+}
+
+TQString AlbumDB::getItemCaption(int albumID, const TQString& name)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT caption FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(name)),
+ &values );
+
+ if (!values.isEmpty())
+ return values[0];
+ else
+ return TQString();
+}
+
+TQDateTime AlbumDB::getItemDate(TQ_LLONG imageID)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT datetime FROM Images "
+ "WHERE id=%1;")
+ .arg(imageID),
+ &values );
+
+ if (values.isEmpty())
+ return TQDateTime();
+ else
+ return TQDateTime::fromString(values[0], TQt::ISODate);
+}
+
+TQDateTime AlbumDB::getItemDate(int albumID, const TQString& name)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT datetime FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(name)),
+ &values );
+
+ if (values.isEmpty())
+ return TQDateTime();
+ else
+ return TQDateTime::fromString(values[0], TQt::ISODate);
+}
+
+TQ_LLONG AlbumDB::getImageId(int albumID, const TQString& name)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT id FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(name)),
+ &values );
+
+ if (values.isEmpty())
+ return -1;
+ else
+ return (values[0]).toLongLong();
+}
+
+TQStringList AlbumDB::getItemTagNames(TQ_LLONG imageID)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT name FROM Tags \n "
+ "WHERE id IN (SELECT tagid FROM ImageTags \n "
+ " WHERE imageid=%1) \n "
+ "ORDER BY name;")
+ .arg(imageID),
+ &values );
+
+ return values;
+}
+
+IntList AlbumDB::getItemTagIDs(TQ_LLONG imageID)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT tagid FROM ImageTags \n "
+ "WHERE imageID=%1;")
+ .arg(imageID),
+ &values );
+
+ IntList ids;
+
+ if (values.isEmpty())
+ return ids;
+
+ for (TQStringList::iterator it=values.begin(); it != values.end(); ++it)
+ {
+ ids << (*it).toInt();
+ }
+ return ids;
+}
+
+bool AlbumDB::hasTags(const LLongList& imageIDList)
+{
+ IntList ids;
+
+ if (imageIDList.isEmpty())
+ return false;
+
+ TQStringList values;
+
+ TQString sql = TQString("SELECT count(tagid) FROM ImageTags "
+ "WHERE imageid=%1 ")
+ .arg(imageIDList.first());
+
+ LLongList::const_iterator iter = imageIDList.begin();
+ ++iter;
+
+ while (iter != imageIDList.end())
+ {
+ sql += TQString(" OR imageid=%2 ")
+ .arg(*iter);
+ ++iter;
+ }
+
+ sql += TQString(";");
+ execSql( sql, &values );
+
+ if (values[0] == "0")
+ return false;
+ else
+ return true;
+}
+
+IntList AlbumDB::getItemCommonTagIDs(const LLongList& imageIDList)
+{
+ IntList ids;
+
+ if (imageIDList.isEmpty())
+ return ids;
+
+ TQStringList values;
+
+ TQString sql = TQString("SELECT DISTINCT tagid FROM ImageTags "
+ "WHERE imageid=%1 ")
+ .arg(imageIDList.first());
+
+ LLongList::const_iterator iter = imageIDList.begin();
+ ++iter;
+
+ while (iter != imageIDList.end())
+ {
+ sql += TQString(" OR imageid=%2 ")
+ .arg(*iter);
+ ++iter;
+ }
+
+ sql += TQString(";");
+ execSql( sql, &values );
+
+ if (values.isEmpty())
+ return ids;
+
+ for (TQStringList::iterator it=values.begin(); it != values.end(); ++it)
+ {
+ ids << (*it).toInt();
+ }
+ return ids;
+}
+
+void AlbumDB::setItemCaption(TQ_LLONG imageID,const TQString& caption)
+{
+ TQStringList values;
+
+ execSql( TQString("UPDATE Images SET caption='%1' "
+ "WHERE id=%2;")
+ .arg(escapeString(caption),
+ TQString::number(imageID) ));
+}
+
+void AlbumDB::setItemCaption(int albumID, const TQString& name, const TQString& caption)
+{
+ TQStringList values;
+
+ execSql( TQString("UPDATE Images SET caption='%1' "
+ "WHERE dirid=%2 AND name='%3';")
+ .arg(escapeString(caption),
+ TQString::number(albumID),
+ escapeString(name)) );
+}
+
+void AlbumDB::addItemTag(TQ_LLONG imageID, int tagID)
+{
+ execSql( TQString("REPLACE INTO ImageTags (imageid, tagid) "
+ "VALUES(%1, %2);")
+ .arg(imageID)
+ .arg(tagID) );
+
+ if (!d->recentlyAssignedTags.contains(tagID))
+ {
+ d->recentlyAssignedTags.push_front(tagID);
+ if (d->recentlyAssignedTags.size() > 10)
+ d->recentlyAssignedTags.pop_back();
+ }
+}
+
+void AlbumDB::addItemTag(int albumID, const TQString& name, int tagID)
+{
+ execSql( TQString("REPLACE INTO ImageTags (imageid, tagid) \n "
+ "(SELECT id, %1 FROM Images \n "
+ " WHERE dirid=%2 AND name='%3');")
+ .arg(tagID)
+ .arg(albumID)
+ .arg(escapeString(name)) );
+}
+
+IntList AlbumDB::getRecentlyAssignedTags() const
+{
+ return d->recentlyAssignedTags;
+}
+
+void AlbumDB::removeItemTag(TQ_LLONG imageID, int tagID)
+{
+ execSql( TQString("DELETE FROM ImageTags "
+ "WHERE imageID=%1 AND tagid=%2;")
+ .arg(imageID)
+ .arg(tagID) );
+}
+
+void AlbumDB::removeItemAllTags(TQ_LLONG imageID)
+{
+ execSql( TQString("DELETE FROM ImageTags "
+ "WHERE imageID=%1;")
+ .arg(imageID) );
+}
+
+TQStringList AlbumDB::getItemNamesInAlbum(int albumID, bool recurssive)
+{
+ TQStringList values;
+
+ if (recurssive)
+ {
+ KURL url(getAlbumURL(albumID));
+ execSql( TQString("SELECT Images.name "
+ "FROM Images "
+ "WHERE Images.dirid "
+ "IN (SELECT DISTINCT id "
+ "FROM Albums "
+ "WHERE url='%1' OR url LIKE '\%%2\%')")
+ .arg(escapeString(url.path())).arg(escapeString(url.path(1))), &values);
+ }
+ else
+ {
+ execSql( TQString("SELECT Images.name "
+ "FROM Images "
+ "WHERE Images.dirid=%1")
+ .arg(albumID), &values );
+ }
+ return values;
+}
+
+TQStringList AlbumDB::getAllItemURLsWithoutDate()
+{
+ TQStringList values;
+ execSql( TQString("SELECT Albums.url||'/'||Images.name "
+ "FROM Images, Albums "
+ "WHERE Images.dirid=Albums.Id "
+ "AND (Images.datetime is null or "
+ " Images.datetime == '');"),
+ &values );
+
+ TQString libraryPath = AlbumManager::instance()->getLibraryPath() + '/';
+ for (TQStringList::iterator it = values.begin(); it != values.end();
+ ++it)
+ {
+ *it = libraryPath + *it;
+ }
+
+ return values;
+}
+
+int AlbumDB::getOrCreateAlbumId(const TQString& folder)
+{
+ TQStringList values;
+ execSql( TQString("SELECT id FROM Albums WHERE url ='%1';")
+ .arg( escapeString(folder) ), &values);
+
+ int albumID;
+ if (values.isEmpty())
+ {
+ execSql( TQString ("INSERT INTO Albums (url, date) "
+ "VALUES ('%1','%2')")
+ .arg(escapeString(folder),
+ TQDateTime::currentDateTime().toString(TQt::ISODate)) );
+ albumID = sqlite3_last_insert_rowid(d->dataBase);
+ } else
+ albumID = values[0].toInt();
+
+ return albumID;
+}
+
+TQ_LLONG AlbumDB::addItem(int albumID,
+ const TQString& name,
+ const TQDateTime& datetime,
+ const TQString& comment,
+ int rating,
+ const TQStringList &keywordsList)
+{
+ execSql ( TQString ("REPLACE INTO Images "
+ "( caption , datetime, name, dirid ) "
+ " VALUES ('%1','%2','%3',%4) " )
+ .arg(escapeString(comment),
+ datetime.toString(TQt::ISODate),
+ escapeString(name),
+ TQString::number(albumID)) );
+
+ TQ_LLONG item = sqlite3_last_insert_rowid(d->dataBase);
+
+ // Set Rating value to item in database.
+
+ if ( item != -1 && rating != -1 )
+ setItemRating(item, rating);
+
+ // Set existing tags in database or create new tags if not exist.
+
+ if ( item != -1 && !keywordsList.isEmpty() )
+ {
+ IntList tagIDs = getTagsFromTagPaths(keywordsList);
+ for (IntList::iterator it = tagIDs.begin(); it != tagIDs.end(); ++it)
+ {
+ addItemTag(item, *it);
+ }
+ }
+
+ return item;
+}
+
+IntList AlbumDB::getTagsFromTagPaths(const TQStringList &keywordsList, bool create)
+{
+ if (keywordsList.isEmpty())
+ return IntList();
+
+ IntList tagIDs;
+
+ TQStringList keywordsList2Create;
+
+ // Create a list of the tags currently in database
+
+ TagInfo::List currentTagsList;
+
+ TQStringList values;
+ execSql( "SELECT id, pid, name FROM Tags;", &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ TagInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.pid = (*it).toInt();
+ ++it;
+ info.name = *it;
+ ++it;
+ currentTagsList.append(info);
+ }
+
+ // For every tag in keywordsList, scan taglist to check if tag already exists.
+
+ for (TQStringList::const_iterator kwd = keywordsList.begin();
+ kwd != keywordsList.end(); ++kwd )
+ {
+ // split full tag "url" into list of single tag names
+ TQStringList tagHierarchy = TQStringList::split('/', *kwd);
+ if (tagHierarchy.isEmpty())
+ continue;
+
+ // last entry in list is the actual tag name
+ bool foundTag = false;
+ TQString tagName = tagHierarchy.back();
+ tagHierarchy.pop_back();
+
+ for (TagInfo::List::iterator tag = currentTagsList.begin();
+ tag != currentTagsList.end(); ++tag )
+ {
+ // There might be multiple tags with the same name, but in different
+ // hierarchies. We must check them all until we find the correct hierarchy
+ if ((*tag).name == tagName)
+ {
+ int parentID = (*tag).pid;
+
+ // Check hierarchy, from bottom to top
+ bool foundParentTag = true;
+ TQStringList::iterator parentTagName = tagHierarchy.end();
+
+ while (foundParentTag && parentTagName != tagHierarchy.begin())
+ {
+ --parentTagName;
+
+ foundParentTag = false;
+
+ for (TagInfo::List::iterator parentTag = currentTagsList.begin();
+ parentTag != currentTagsList.end(); ++parentTag )
+ {
+ // check if name is the same, and if ID is identical
+ // to the parent ID we got from the child tag
+ if ( (*parentTag).id == parentID &&
+ (*parentTag).name == (*parentTagName) )
+ {
+ parentID = (*parentTag).pid;
+ foundParentTag = true;
+ break;
+ }
+ }
+
+ // If we traversed the list without a match,
+ // foundParentTag will be false, the while loop breaks.
+ }
+
+ // If we managed to traverse the full hierarchy,
+ // we have our tag.
+ if (foundParentTag)
+ {
+ // add to result list
+ tagIDs.append((*tag).id);
+ foundTag = true;
+ break;
+ }
+ }
+ }
+
+ if (!foundTag)
+ keywordsList2Create.append(*kwd);
+ }
+
+ // If tags do not exist in database, create them.
+
+ if (create && !keywordsList2Create.isEmpty())
+ {
+ for (TQStringList::iterator kwd = keywordsList2Create.begin();
+ kwd != keywordsList2Create.end(); ++kwd )
+ {
+ // split full tag "url" into list of single tag names
+ TQStringList tagHierarchy = TQStringList::split('/', *kwd);
+
+ if (tagHierarchy.isEmpty())
+ continue;
+
+ int parentTagID = 0;
+ int tagID = 0;
+ bool parentTagExisted = true;
+
+ // Traverse hierarchy from top to bottom
+ for (TQStringList::iterator tagName = tagHierarchy.begin();
+ tagName != tagHierarchy.end(); ++tagName)
+ {
+ tagID = 0;
+
+ // if the parent tag did not exist, we need not check if the child exists
+ if (parentTagExisted)
+ {
+ for (TagInfo::List::iterator tag = currentTagsList.begin();
+ tag != currentTagsList.end(); ++tag )
+ {
+ // find the tag with tag name according to tagHierarchy,
+ // and parent ID identical to the ID of the tag we found in
+ // the previous run.
+ if ((*tag).name == (*tagName) && (*tag).pid == parentTagID)
+ {
+ tagID = (*tag).id;
+ break;
+ }
+ }
+ }
+
+ if (tagID != 0)
+ {
+ // tag already found in DB
+ parentTagID = tagID;
+ continue;
+ }
+
+ // Tag does not yet exist in DB, add it
+ tagID = addTag(parentTagID, (*tagName), TQString(), 0);
+
+ if (tagID == -1)
+ {
+ // Something is wrong in database. Abort.
+ break;
+ }
+
+ // append to our list of existing tags (for following keywords)
+ TagInfo info;
+ info.id = tagID;
+ info.pid = parentTagID;
+ info.name = (*tagName);
+ currentTagsList.append(info);
+
+ parentTagID = tagID;
+ parentTagExisted = false;
+ }
+
+ // add to result list
+ tagIDs.append(tagID);
+ }
+ }
+
+ return tagIDs;
+}
+
+int AlbumDB::getItemAlbum(TQ_LLONG imageID)
+{
+ TQStringList values;
+
+ execSql ( TQString ("SELECT dirid FROM Images "
+ "WHERE id=%1;")
+ .arg(imageID),
+ &values);
+
+ if (!values.isEmpty())
+ return values.first().toInt();
+ else
+ return 1;
+}
+
+TQString AlbumDB::getItemName(TQ_LLONG imageID)
+{
+ TQStringList values;
+
+ execSql ( TQString ("SELECT name FROM Images "
+ "WHERE id=%1;")
+ .arg(imageID),
+ &values);
+
+ if (!values.isEmpty())
+ return values.first();
+ else
+ return TQString();
+}
+
+bool AlbumDB::setItemDate(TQ_LLONG imageID,
+ const TQDateTime& datetime)
+{
+ execSql ( TQString ("UPDATE Images SET datetime='%1'"
+ "WHERE id=%2;")
+ .arg(datetime.toString(TQt::ISODate),
+ TQString::number(imageID)) );
+
+ return true;
+}
+
+bool AlbumDB::setItemDate(int albumID, const TQString& name,
+ const TQDateTime& datetime)
+{
+ execSql ( TQString ("UPDATE Images SET datetime='%1'"
+ "WHERE dirid=%2 AND name='%3';")
+ .arg(datetime.toString(TQt::ISODate),
+ TQString::number(albumID),
+ escapeString(name)) );
+
+ return true;
+}
+
+void AlbumDB::setItemRating(TQ_LLONG imageID, int rating)
+{
+ execSql ( TQString ("REPLACE INTO ImageProperties "
+ "(imageid, property, value) "
+ "VALUES(%1, '%2', '%3');")
+ .arg(imageID)
+ .arg("Rating")
+ .arg(rating) );
+}
+
+int AlbumDB::getItemRating(TQ_LLONG imageID)
+{
+ TQStringList values;
+
+ execSql( TQString("SELECT value FROM ImageProperties "
+ "WHERE imageid=%1 and property='%2';")
+ .arg(imageID)
+ .arg("Rating"),
+ &values);
+
+ if (!values.isEmpty())
+ return values[0].toInt();
+ else
+ return 0;
+}
+
+TQStringList AlbumDB::getItemURLsInAlbum(int albumID)
+{
+ TQStringList values;
+
+ TQString basePath(AlbumManager::instance()->getLibraryPath());
+
+ AlbumSettings::ImageSortOrder order = AlbumSettings::instance()->getImageSortOrder();
+
+ TQString sqlString;
+ switch(order)
+ {
+ case AlbumSettings::ByIName:
+ sqlString = TQString("SELECT Albums.url||'/'||Images.name FROM Images, Albums "
+ "WHERE Albums.id=%1 AND Albums.id=Images.dirid "
+ "ORDER BY Images.name COLLATE NOCASE;")
+ .arg(albumID);
+ break;
+ case AlbumSettings::ByIPath:
+ // Dont collate on the path - this is to maintain the same behaviour
+ // that happens when sort order is "By Path"
+ sqlString = TQString("SELECT Albums.url||'/'||Images.name FROM Images, Albums "
+ "WHERE Albums.id=%1 AND Albums.id=Images.dirid "
+ "ORDER BY Albums.url,Images.name;")
+ .arg(albumID);
+ break;
+ case AlbumSettings::ByIDate:
+ sqlString = TQString("SELECT Albums.url||'/'||Images.name FROM Images, Albums "
+ "WHERE Albums.id=%1 AND Albums.id=Images.dirid "
+ "ORDER BY Images.datetime;")
+ .arg(albumID);
+ break;
+ case AlbumSettings::ByIRating:
+ sqlString = TQString("SELECT Albums.url||'/'||Images.name FROM Images, Albums, ImageProperties "
+ "WHERE Albums.id=%1 AND Albums.id=Images.dirid "
+ "AND Images.id = ImageProperties.imageid "
+ "AND ImageProperties.property='Rating' "
+ "ORDER BY ImageProperties.value DESC;")
+ .arg(albumID);
+ break;
+ default:
+ sqlString = TQString("SELECT Albums.url||'/'||Images.name FROM Images, Albums "
+ "WHERE Albums.id=%1 AND Albums.id=Images.dirid;")
+ .arg(albumID);
+ break;
+ }
+ execSql( sqlString, &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end(); ++it)
+ {
+ *it = basePath + *it;
+ }
+
+ return values;
+}
+
+LLongList AlbumDB::getItemIDsInAlbum(int albumID)
+{
+ LLongList itemIDs;
+
+ TQStringList itemNames = getItemNamesInAlbum(albumID);
+
+ for (TQStringList::iterator it = itemNames.begin(); it != itemNames.end(); ++it)
+ {
+ TQ_LLONG id = getImageId(albumID, *it);
+ itemIDs.append(id);
+ }
+
+ return itemIDs;
+}
+
+TQStringList AlbumDB::getItemURLsInTag(int tagID, bool recursive)
+{
+ TQStringList values;
+
+ TQString basePath(AlbumManager::instance()->getLibraryPath());
+
+ TQString imagesIdClause;
+ if (recursive)
+ imagesIdClause = TQString("SELECT imageid FROM ImageTags "
+ " WHERE tagid=%1 "
+ " OR tagid IN (SELECT id FROM TagsTree WHERE pid=%2)")
+ .arg(tagID).arg(tagID);
+ else
+ imagesIdClause = TQString("SELECT imageid FROM ImageTags WHERE tagid=%1").arg(tagID);
+
+ execSql( TQString("SELECT Albums.url||'/'||Images.name FROM Images, Albums "
+ "WHERE Images.id IN (%1) "
+ "AND Albums.id=Images.dirid;")
+ .arg(imagesIdClause), &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end(); ++it)
+ {
+ *it = basePath + *it;
+ }
+
+ return values;
+}
+
+LLongList AlbumDB::getItemIDsInTag(int tagID, bool recursive)
+{
+ LLongList itemIDs;
+ TQStringList values;
+
+ if (recursive)
+ execSql( TQString("SELECT imageid FROM ImageTags "
+ " WHERE tagid=%1 "
+ " OR tagid IN (SELECT id FROM TagsTree WHERE pid=%2)")
+ .arg(tagID).arg(tagID), &values );
+ else
+ execSql( TQString("SELECT imageid FROM ImageTags WHERE tagid=%1;")
+ .arg(tagID), &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end(); ++it)
+ {
+ itemIDs << (*it).toLong();
+ }
+
+ return itemIDs;
+}
+
+TQString AlbumDB::getAlbumURL(int albumID)
+{
+ TQStringList values;
+ execSql( TQString("SELECT url from Albums where id=%1")
+ .arg( albumID), &values);
+ return values[0];
+}
+
+TQDate AlbumDB::getAlbumLowestDate(int albumID)
+{
+ TQStringList values;
+ execSql( TQString("SELECT MIN(datetime) FROM Images "
+ "WHERE dirid=%1 GROUP BY dirid")
+ .arg( albumID ), &values);
+ TQDate itemDate = TQDate::fromString( values[0], TQt::ISODate );
+ return itemDate;
+}
+
+TQDate AlbumDB::getAlbumHighestDate(int albumID)
+{
+ TQStringList values;
+ execSql( TQString("SELECT MAX(datetime) FROM Images "
+ "WHERE dirid=%1 GROUP BY dirid")
+ .arg( albumID ), &values);
+ TQDate itemDate = TQDate::fromString( values[0], TQt::ISODate );
+ return itemDate;
+}
+
+TQDate AlbumDB::getAlbumAverageDate(int albumID)
+{
+ TQStringList values;
+ execSql( TQString("SELECT datetime FROM Images WHERE dirid=%1")
+ .arg( albumID ), &values);
+
+ int differenceInSecs = 0;
+ int amountOfImages = 0;
+ TQDateTime baseDateTime;
+
+ for (TQStringList::iterator it = values.begin(); it != values.end(); ++it)
+ {
+ TQDateTime itemDateTime = TQDateTime::fromString( *it, TQt::ISODate );
+ if (itemDateTime.isValid())
+ {
+ ++amountOfImages;
+ if ( baseDateTime.isNull() )
+ baseDateTime=itemDateTime;
+ else
+ differenceInSecs += itemDateTime.secsTo( baseDateTime );
+ }
+ }
+
+ if ( amountOfImages > 0 )
+ {
+ TQDateTime averageDateTime;
+ averageDateTime.setTime_t( baseDateTime.toTime_t() -
+ (int)( differenceInSecs/amountOfImages ) );
+ return ( averageDateTime.date() );
+ }
+ else
+ return TQDate();
+}
+
+void AlbumDB::deleteItem(int albumID, const TQString& file)
+{
+ execSql( TQString("DELETE FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(file)) );
+}
+
+void AlbumDB::setAlbumURL(int albumID, const TQString& url)
+{
+ TQString u = escapeString(url);
+
+ // first delete any stale albums left behind
+ execSql( TQString("DELETE FROM Albums WHERE url = '%1'")
+ .arg(u) );
+
+ // now update the album url
+ execSql( TQString("UPDATE Albums SET url = '%1' WHERE id = %2;")
+ .arg(u, TQString::number(albumID) ));
+}
+
+void AlbumDB::setTagName(int tagID, const TQString& name)
+{
+ execSql( TQString("UPDATE Tags SET name='%1' WHERE id=%2;")
+ .arg(escapeString(name), TQString::number(tagID) ));
+}
+
+void AlbumDB::moveItem(int srcAlbumID, const TQString& srcName,
+ int dstAlbumID, const TQString& dstName)
+{
+
+ // first delete any stale database entries if any
+ deleteItem(dstAlbumID, dstName);
+
+ execSql( TQString("UPDATE Images SET dirid=%1, name='%2' "
+ "WHERE dirid=%3 AND name='%4';")
+ .arg(TQString::number(dstAlbumID), escapeString(dstName),
+ TQString::number(srcAlbumID), escapeString(srcName)) );
+}
+
+int AlbumDB::copyItem(int srcAlbumID, const TQString& srcName,
+ int dstAlbumID, const TQString& dstName)
+{
+ // check for src == dest
+ if (srcAlbumID == dstAlbumID && srcName == dstName)
+ return -1;
+
+ // find id of src image
+ TQStringList values;
+ execSql( TQString("SELECT id FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(TQString::number(srcAlbumID), escapeString(srcName)),
+ &values);
+
+ if (values.isEmpty())
+ return -1;
+
+ int srcId = values[0].toInt();
+
+ // first delete any stale database entries if any
+ deleteItem(dstAlbumID, dstName);
+
+ // copy entry in Images table
+ execSql( TQString("INSERT INTO Images (dirid, name, caption, datetime) "
+ "SELECT %1, '%2', caption, datetime FROM Images "
+ "WHERE id=%3;")
+ .arg(TQString::number(dstAlbumID), escapeString(dstName),
+ TQString::number(srcId)) );
+
+ int dstId = sqlite3_last_insert_rowid(d->dataBase);
+
+ // copy tags
+ execSql( TQString("INSERT INTO ImageTags (imageid, tagid) "
+ "SELECT %1, tagid FROM ImageTags "
+ "WHERE imageid=%2;")
+ .arg(TQString::number(dstId), TQString::number(srcId)) );
+
+ // copy properties (rating)
+ execSql( TQString("INSERT INTO ImageProperties (imageid, property, value) "
+ "SELECT %1, property, value FROM ImageProperties "
+ "WHERE imageid=%2;")
+ .arg(TQString::number(dstId), TQString::number(srcId)) );
+
+ return dstId;
+}
+
+TQ_LLONG AlbumDB::lastInsertedRow()
+{
+ return sqlite3_last_insert_rowid(d->dataBase);
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/albumdb.h b/src/digikam/albumdb.h
new file mode 100644
index 00000000..9115684a
--- /dev/null
+++ b/src/digikam/albumdb.h
@@ -0,0 +1,615 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-18
+ * Description : database album interface.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+/** @file albumdb.h */
+
+#ifndef ALBUMDB_H
+#define ALBUMDB_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqvaluelist.h>
+#include <tqstringlist.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "albuminfo.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+typedef TQValueList<int> IntList;
+typedef TQValueList<TQ_LLONG> LLongList;
+
+class AlbumDBPriv;
+
+/**
+ * This class is responsible for the communication
+ * with the sqlite database.
+ */
+class DIGIKAM_EXPORT AlbumDB
+{
+public:
+
+ /**
+ * Constructor
+ */
+ AlbumDB();
+
+ /**
+ * Destructor
+ */
+ ~AlbumDB();
+
+ /**
+ * Check if the database interface is initialized properly.
+ * @return true if it's ready to use, else false.
+ */
+ bool isValid() const;
+
+ /**
+ * Makes a connection to the database and makes sure all tables
+ * are available.
+ * @param path The database to open
+ */
+ void setDBPath(const TQString& path);
+
+ /**
+ * Returns all albums and their attributes in the database
+ * @return a list of albums and their attributes
+ */
+ AlbumInfo::List scanAlbums();
+
+ /**
+ * Returns all tags and their attributes in the database
+ * @return a list of tags and their attributes
+ */
+ TagInfo::List scanTags();
+
+ /**
+ * Returns all searches from the database
+ * @return a list of searches from the database
+ */
+ SearchInfo::List scanSearches();
+
+ /**
+ * Add a new album to the database with the given attributes
+ * @param url url of the album
+ * @param caption the album caption
+ * @param date the date for the album
+ * @param collection the album collection
+ * @return the id of the album added or -1 if it failed
+ */
+ int addAlbum(const TQString& url, const TQString& caption,
+ const TQDate& date, const TQString& collection);
+
+ /**
+ * Set a new url for the album. This will not affect the url
+ * of the subalbums.
+ * @param albumID the id of the album
+ * @param url the new url for the album
+ */
+ void setAlbumURL(int albumID, const TQString& url);
+
+ /**
+ * Set a caption for the album.
+ * @param albumID the id of the album
+ * @param caption the new caption for the album
+ */
+ void setAlbumCaption(int albumID, const TQString& caption);
+
+ /**
+ * Set a collection for the album.
+ * @param albumID the id of the album
+ * @param collection the new collection for the album
+ */
+ void setAlbumCollection(int albumID, const TQString& collection);
+
+ /**
+ * Set a date for the album.
+ * @param albumID the id of the album
+ * @param date the date for the album
+ */
+ void setAlbumDate(int albumID, const TQDate& date);
+
+ /**
+ * Set the icon for the album.
+ * @param albumID the id of the album
+ * @param iconID the id of the icon file
+ */
+ void setAlbumIcon(int albumID, TQ_LLONG iconID);
+
+ /**
+ * Get the fullpath for the album icon file
+ * @param albumID the id of the album
+ */
+ TQString getAlbumIcon(int albumID);
+
+ /**
+ * Deletes an album from the database. This will not delete the
+ * subalbums of the album.
+ * @param albumID the id of the album
+ */
+ void deleteAlbum(int albumID);
+
+ /**
+ * Adds a new tag to the database with given name, icon and parent id.
+ * @param parentTagID the id of the tag which will become the new tags parent
+ * @param name the name of the tag
+ * @param iconKDE the name of the icon file (this is filename which kde
+ * iconloader can load up)
+ * @param iconID the id of the icon file
+ * Note: if the iconKDE parameter is empty, then the iconID parameter is used
+ * @return the id of the tag added or -1 if it failed
+ */
+ int addTag(int parentTagID, const TQString& name,
+ const TQString& iconKDE, TQ_LLONG iconID);
+
+ /**
+ * Set a new name for the tag.
+ * @param tagID the id of the tag
+ * @param name the new name for the tag
+ */
+ void setTagName(int tagID, const TQString& name);
+
+ /**
+ * Set the icon for the tag.
+ * @param tagID the id of the tag
+ * @param iconKDE the filename for the kde icon file
+ * @param iconID the id of the icon file
+ * Note: Only one of the iconKDE or iconID parameters is used.
+ * if the iconKDE parameter is empty, then the iconID parameter is used
+ */
+ void setTagIcon(int tagID, const TQString& iconKDE, TQ_LLONG iconID);
+
+ /**
+ * Get the icon for the tag.
+ * @param tagID the id of the tag
+ * @return the path for the icon file. this could be either a simple filename
+ * which can be loaded by kiconloader or an absolute filepath to the file
+ */
+ TQString getTagIcon(int tagID);
+
+ /**
+ * Set the parent tagid for the tag. This is equivalent to reparenting
+ * the tag
+ * @param tagID the id of the tag
+ * @param newParentTagID the new parentid for the tag
+ */
+ void setTagParentID(int tagID, int newParentTagID);
+
+ /**
+ * Deletes a tag from the database. This will not delete the
+ * subtags of the tag.
+ * @param tagID the id of the tag
+ */
+ void deleteTag(int tagID);
+
+ /**
+ * Add a new search to the database with the given attributes
+ * @param name name of the search
+ * @param url url of the search
+ * @return the id of the album added or -1 if it failed
+ */
+ int addSearch(const TQString& name, const KURL& url);
+
+ /**
+ * Updates Search with new attributes
+ * @param searchID the id of the search
+ * @param name name of the search
+ * @param url url of the search
+ */
+ void updateSearch(int searchID, const TQString& name, const KURL& url);
+
+ /**
+ * Delete a search from the database.
+ * @param searchID the id of the search
+ */
+ void deleteSearch(int searchID);
+
+ void beginTransaction();
+ void commitTransaction();
+
+ /**
+ * This adds a keyword-value combination to the database Settings table
+ * if the keyword already exists, the value will be replaced with the new
+ * value.
+ * @param keyword The keyword
+ * @param value The value
+ */
+ void setSetting(const TQString& keyword, const TQString& value);
+
+ /**
+ * This function returns the value which is stored in the database
+ * (table Settings).
+ * @param keyword The keyword for which the value has to be returned.
+ * @return The values which belongs to the keyword.
+ */
+ TQString getSetting(const TQString& keyword);
+
+ /**
+ * This is simple function to put a new Item in the database,
+ * without checking if it already exists, but since albumID+name
+ * has to be unique, it will simply replace the datetime and comment
+ * for an already existing item.
+ * @param albumID The albumID where the file is located.
+ * @param name The filename
+ * @param datetime The datetime to be stored. Should try to let that be
+ * the exif-datetime, but if not available the modification date.
+ * @param comment The user comment as found in the exif-headers of the
+ * file.
+ * @param rating The user rating as found in the iptc-headers of the
+ * file.
+ * @param keywords The user keywords as found in the iptc-headers of the
+ * file.
+ * @return the id of item added or -1 if it fails
+ */
+ TQ_LLONG addItem(int albumID, const TQString& name,
+ const TQDateTime& datetime,
+ const TQString& comment,
+ int rating,
+ const TQStringList& keywordsList);
+
+ /**
+ * Find the album of an item
+ * @param imageID The ID of the item
+ * @return The ID of the PAlbum of the item, or -1 if not found
+ */
+ int getItemAlbum(TQ_LLONG imageID);
+
+ /**
+ * Retrieve the name of the item
+ * @param imageID The ID of the item
+ * @return The name of the item, or a null string if not found
+ */
+ TQString getItemName(TQ_LLONG imageID);
+
+ /**
+ * Update the date of a item to supplied date
+ * @param imageID The ID of the item
+ * @param datetime The datetime to be stored. Should try to let that be
+ * the exif-datetime, but if not available the modification date.
+ * @return It will always return true. Maybe that will change.
+ */
+ bool setItemDate(TQ_LLONG imageID, const TQDateTime& datetime);
+
+ /**
+ * Update the date of a item to supplied date
+ * @param albumID The albumID where the file is located.
+ * @param name The filename
+ * @param datetime The datetime to be stored. Should try to let that be
+ * the exif-datetime, but if not available the modification date.
+ * @return It will always return true. Maybe that will change.
+ */
+ bool setItemDate(int albumID, const TQString& name,
+ const TQDateTime& datetime);
+
+ /**
+ * Set the caption for the item
+ * @param imageID the id of the item
+ * @param caption the caption for the item
+ */
+ void setItemCaption(TQ_LLONG imageID, const TQString& caption);
+
+ /**
+ * Set the caption for the item
+ * @param albumID the albumID of the item
+ * @param name the name of the item
+ * @param caption the caption for the item
+ */
+ void setItemCaption(int albumID, const TQString& name, const TQString& caption);
+
+ /**
+ * Add a tag for the item
+ * @param imageID the ID of the item
+ * @param tagID the tagID for the tag
+ */
+ void addItemTag(TQ_LLONG imageID, int tagID);
+
+ /**
+ * Add a tag for the item
+ * @param albumID the albumID of the item
+ * @param name the name of the item
+ * @param tagID the tagID for the tag
+ */
+ void addItemTag(int albumID, const TQString& name, int tagID);
+
+ /**
+ * Add tags for the item, create tags with the given paths if they do not yet exist
+ * @param tagPaths a list of tag paths
+ * @param create create new tags if necessary
+ * @returns a list of albumIDs of the tags in tagPaths
+ */
+ IntList getTagsFromTagPaths(const TQStringList &tagPaths, bool create = true);
+
+ /**
+ * Get a list of recently assigned tags (only last 6 tags are listed)
+ * @return the list of recently assigned tags
+ */
+ IntList getRecentlyAssignedTags() const;
+
+ /**
+ * Get the caption for the item
+ * @param imageID the id of the item
+ * @return the caption for the item
+ */
+ TQString getItemCaption(TQ_LLONG imageID);
+
+ /**
+ * Get the caption for the item
+ * @param albumID the albumID of the item
+ * @param name the name of the item
+ * @return the caption for the item
+ */
+ TQString getItemCaption(int albumID, const TQString& name);
+
+ /**
+ * Get the datetime for the item
+ * @param imageID the ID of the item
+ * @return the datetime for the item
+ */
+ TQDateTime getItemDate(TQ_LLONG imageID);
+
+ /**
+ * Get the datetime for the item
+ * @param albumID the albumID of the item
+ * @param name the name of the item
+ * @return the datetime for the item
+ */
+ TQDateTime getItemDate(int albumID, const TQString& name);
+
+ /**
+ * Get the imageId of the item
+ * @param albumId the albumID of the item
+ * @param name the name of the item
+ * @return the ImageId for the item
+ */
+ TQ_LLONG getImageId(int albumID, const TQString& name);
+
+ /**
+ * Get a list of names of all the tags for the item
+ * @param imageID the ID of the item
+ * @return the list of names of all tags for the item
+ */
+ TQStringList getItemTagNames(TQ_LLONG imageID);
+
+ /**
+ * Get a list of IDs of all the tags for the item
+ * @param imageID the ID of the item
+ * @return the list of IDs of all tags for the item
+ */
+ IntList getItemTagIDs(TQ_LLONG imageID);
+
+ /**
+ * Given a set of items (identified by their IDs),
+ * this will see if any of the items has a tag.
+ * @param imageIDList a list of IDs of the items
+ * @return true if at least one of the items has a tag
+ */
+ bool hasTags(const LLongList& imageIDList);
+
+ /**
+ * Given a set of items (identified by their IDs),
+ * get a list of ID of all common tags
+ * @param imageIDList a list of IDs of the items
+ * @return the list of common IDs of the given items
+ */
+ IntList getItemCommonTagIDs(const LLongList& imageIDList);
+
+ /**
+ * Remove a specific tag for the item
+ * @param imageID the ID of the item
+ * @param tagID the tagID for the tag
+ */
+ void removeItemTag(TQ_LLONG imageID, int tagID);
+
+ /**
+ * Update the rating of a item to supplied value
+ * @param imageID The ID of the item
+ * @param rating The rating value to be stored.
+ */
+ void setItemRating(TQ_LLONG imageID, int rating);
+
+ /**
+ * Get the item rating
+ * @param imageID the ID of the item
+ * @return the rating for the item
+ */
+ int getItemRating(TQ_LLONG imageID);
+
+ /**
+ * Remove all tags for the item
+ * @param imageID the ID of the item
+ */
+ void removeItemAllTags(TQ_LLONG imageID);
+
+ /**
+ * Deletes an item from the database.
+ * @param albumID The id of the album.
+ * @param file The filename of the file to delete.
+ */
+ void deleteItem(int albumID, const TQString& file);
+
+ /**
+ * This can be used to find out the albumID for a given
+ * folder. If it does not exist, it will be created and the
+ * new albumID will be returned.
+ * @param folder The folder for which you want the albumID
+ * @return It returns the albumID for that folder.
+ */
+ int getOrCreateAlbumId(const TQString& folder);
+
+ /**
+ * Returns all items for a given albumid. This is used to
+ * verify if all items on disk are consistent with the database
+ * in the scanlib class.
+ * @param albumID The albumID for which you want all items.
+ * @param recursive perform a recursive folder hierarchy parsing
+ * @return It returns a TQStringList with the filenames.
+ */
+ TQStringList getItemNamesInAlbum(int albumID, bool recurssive=false);
+
+ /**
+ * Given a albumID, get a list of the url of all items in the album
+ * @param albumID the id of the album
+ * @return a list of urls for the items in the album. The urls are the
+ * absolute path of the items
+ */
+ TQStringList getItemURLsInAlbum(int albumID);
+
+ /**
+ * Given a albumID, get a list of Ids of all items in the album
+ * @param albumID the id of the album
+ * @return a list of Ids for the items in the album.
+ */
+ LLongList getItemIDsInAlbum(int albumID);
+
+ /**
+ * Returns all items in the database without a date. This is used
+ * in the scanlib class which tries to find out the date of the
+ * items, so the database holds the date for each item. This was
+ * not the case untill the 0.8.0 release.
+ * @return The path (starting from albumPath and including the
+ * the filename of all items.
+ */
+ TQStringList getAllItemURLsWithoutDate();
+
+ /**
+ * Given a tagid, get a list of the url of all items in the tag
+ * @param tagID the id of the tag
+ * @param recursive perform a recursive folder hierarchy parsing
+ * @return a list of urls for the items in the tag. The urls are the
+ * absolute path of the items
+ */
+ TQStringList getItemURLsInTag(int tagID, bool recursive = false);
+
+ /**
+ * Given a tagID, get a list of Ids of all items in the tag
+ * @param tagID the id of the tag
+ * @param recursive perform a recursive folder hierarchy parsing
+ * @return a list of Ids for the items in the tag.
+ */
+ LLongList getItemIDsInTag(int tagID, bool recursive = false);
+
+ /**
+ * Given an albumid, this returns the url for that albumdb
+ * @param albumID the id of the albumdb
+ * @return the url of the albumdb
+ */
+ TQString getAlbumURL(int albumID);
+
+ /**
+ * Returns the lowest/oldest date of all images for that album.
+ * @param albumID the id of the album to calculate
+ * @return the date.
+ */
+ TQDate getAlbumLowestDate(int albumID);
+
+ /**
+ * Returns the highest/newest date of all images for that album.
+ * @param albumID the id of the album to calculate
+ * @return the date.
+ */
+ TQDate getAlbumHighestDate(int albumID);
+
+ /**
+ * Returns the average date of all images for that album.
+ * @param albumID the id of the album to calculate
+ * @return the date.
+ */
+ TQDate getAlbumAverageDate(int albumID);
+
+ /**
+ * Move the attributes of an item to a different item. Useful when
+ * say a file is renamed
+ * @param srcAlbumID the id of the source album
+ * @param dstAlbumID the id of the destination album
+ * @param srcName the name of the source file
+ * @param dstName the name of the destination file
+ */
+ void moveItem(int srcAlbumID, const TQString& srcName,
+ int dstAlbumID, const TQString& dstName);
+
+ /**
+ * Copy the attributes of an item to a different item. Useful when
+ * say a file is copied.
+ * The operation fails (returns -1) of src and dest are identical.
+ * @param srcAlbumID the id of the source album
+ * @param dstAlbumID the id of the destination album
+ * @param srcName the name of the source file
+ * @param dstName the name of the destination file
+ * @return the id of item added or -1 if it fails
+ */
+ int copyItem(int srcAlbumID, const TQString& srcName,
+ int dstAlbumID, const TQString& dstName);
+
+ /**
+ * This will execute a given SQL statement to the database.
+ * @param sql The SQL statement
+ * @param values This will be filled with the result of the SQL statement
+ * @param debug If true, it will output the SQL statement
+ * @return It will return if the execution of the statement was succesfull
+ */
+ bool execSql(const TQString& sql, TQStringList* const values = 0,
+ const bool debug = false);
+
+ /**
+ * To be used only if you are sure of what you are doing
+ * @return the last inserted row in one the db table.
+ */
+ TQ_LLONG lastInsertedRow();
+
+private:
+
+ /**
+ * Checks the available tables and creates them if they are not
+ * available.
+ */
+ void initDB();
+
+ /**
+ * Escapes text fields. This is needed for all queries to the database
+ * which happens with an argument which is a text field. It makes sure
+ * a ' is replaced with '', as this is needed for sqlite.
+ * @param str String to escape
+ * @return The escaped string
+ */
+ TQString escapeString(TQString str) const;
+
+private:
+
+ AlbumDBPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMDB_H */
diff --git a/src/digikam/albumdb_sqlite2.cpp b/src/digikam/albumdb_sqlite2.cpp
new file mode 100644
index 00000000..35bb9935
--- /dev/null
+++ b/src/digikam/albumdb_sqlite2.cpp
@@ -0,0 +1,144 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-18
+ * Description : SQlite version 2 database interface.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sqlite.h>
+#include <sys/time.h>
+}
+
+// C++ includes.
+
+#include <ctime>
+#include <cstdio>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqfile.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albumdb_sqlite2.h"
+
+namespace Digikam
+{
+
+typedef struct sqlite_vm sqlite_vm;
+
+AlbumDB_Sqlite2::AlbumDB_Sqlite2()
+{
+ m_db = 0;
+ m_valid = false;
+}
+
+AlbumDB_Sqlite2::~AlbumDB_Sqlite2()
+{
+ if (m_db) {
+ sqlite_close(m_db);
+ }
+}
+
+void AlbumDB_Sqlite2::setDBPath(const TQString& path)
+{
+ if (m_db) {
+ sqlite_close(m_db);
+ m_db = 0;
+ m_valid = false;
+ }
+
+ char *errMsg = 0;
+ m_db = sqlite_open(TQFile::encodeName(path), 0, &errMsg);
+ if (m_db == 0)
+ {
+ DWarning() << k_funcinfo << "Cannot open database: "
+ << errMsg << endl;
+ free(errMsg);
+ return;
+ }
+
+ TQStringList values;
+ execSql("SELECT * FROM sqlite_master", &values);
+ m_valid = values.contains("Albums");
+}
+
+bool AlbumDB_Sqlite2::execSql(const TQString& sql, TQStringList* const values,
+ const bool debug)
+{
+ if ( debug )
+ DDebug() << "SQL-query: " << sql << endl;
+
+ if ( !m_db ) {
+ DWarning() << k_funcinfo << "SQLite pointer == NULL"
+ << endl;
+ return false;
+ }
+
+ const char* tail;
+ sqlite_vm* vm;
+ char* errorStr;
+ int error;
+
+ //compile SQL program to virtual machine
+ error = sqlite_compile( m_db, sql.local8Bit(), &tail, &vm, &errorStr );
+
+ if ( error != SQLITE_OK ) {
+ DWarning() << k_funcinfo << "sqlite_compile error: "
+ << errorStr
+ << " on query: " << sql << endl;
+ sqlite_freemem( errorStr );
+ return false;
+ }
+
+ int number;
+ const char** value;
+ const char** colName;
+ //execute virtual machine by iterating over rows
+ while ( true ) {
+ error = sqlite_step( vm, &number, &value, &colName );
+ if ( error == SQLITE_DONE || error == SQLITE_ERROR )
+ break;
+ //iterate over columns
+ for ( int i = 0; values && i < number; i++ ) {
+ *values << TQString::fromLocal8Bit( value [i] );
+ }
+ }
+
+ //deallocate vm resources
+ sqlite_finalize( vm, &errorStr );
+
+ if ( error != SQLITE_DONE ) {
+ DWarning() << k_funcinfo << "sqlite_step error: "
+ << errorStr
+ << " on query: " << sql << endl;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/albumdb_sqlite2.h b/src/digikam/albumdb_sqlite2.h
new file mode 100644
index 00000000..339289ba
--- /dev/null
+++ b/src/digikam/albumdb_sqlite2.h
@@ -0,0 +1,85 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-18
+ * Description : SQlite version 2 database interface.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMDB_SQLITE2_H
+#define ALBUMDB_SQLITE2_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqvaluelist.h>
+#include <tqstringlist.h>
+#include <tqdatetime.h>
+
+namespace Digikam
+{
+
+typedef struct sqlite sqleet2; // hehe.
+
+typedef TQValueList<int> IntList;
+/**
+ * This class is responsible for the communication
+ * with the sqlite database.
+ */
+class AlbumDB_Sqlite2
+{
+public:
+
+ /**
+ * Constructor
+ */
+ AlbumDB_Sqlite2();
+
+ /**
+ * Destructor
+ */
+ ~AlbumDB_Sqlite2();
+
+ /**
+ * Makes a connection to the database and makes sure all tables
+ * are available.
+ * @param path The database to open
+ */
+ void setDBPath(const TQString& path);
+
+ /**
+ * This will execute a given SQL statement to the database.
+ * @param sql The SQL statement
+ * @param values This will be filled with the result of the SQL statement
+ * @param debug If true, it will output the SQL statement
+ * @return It will return if the execution of the statement was succesfull
+ */
+ bool execSql(const TQString& sql, TQStringList* const values = 0,
+ const bool debug = false);
+
+ bool isValid() const { return m_valid; }
+
+private:
+
+ sqleet2* m_db;
+ bool m_valid;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMDB_SQLITE2_H */
diff --git a/src/digikam/albumfiletip.cpp b/src/digikam/albumfiletip.cpp
new file mode 100644
index 00000000..1950eab6
--- /dev/null
+++ b/src/digikam/albumfiletip.cpp
@@ -0,0 +1,588 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-19
+ * Description : Album item file tip adapted from tdefiletip
+ * (konqueror - konq_iconviewwidget.cc)
+ *
+ * Copyright (C) 1998-1999 by Torben Weis <weis@kde.org>
+ * Copyright (C) 2000-2002 by David Faure <david@mandrakesoft.com>
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtooltip.h>
+#include <tqfileinfo.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpixmap.h>
+#include <tqdatetime.h>
+#include <tqstylesheet.h>
+#include <tqpainter.h>
+#include <tqapplication.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdefileitem.h>
+#include <kmimetype.h>
+#include <tdeglobalsettings.h>
+#include <tdeglobal.h>
+#include <tdeversion.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "albumiconview.h"
+#include "albumiconitem.h"
+#include "albumsettings.h"
+#include "album.h"
+#include "albumfiletip.h"
+
+namespace Digikam
+{
+
+class AlbumFileTipPriv
+{
+public:
+
+ AlbumFileTipPriv() :
+ maxStringLen(30), tipBorder(5)
+ {
+ corner = 0;
+ label = 0;
+ view = 0;
+ iconItem = 0;
+ }
+
+ const uint maxStringLen;
+ const uint tipBorder;
+
+ int corner;
+
+ TQLabel *label;
+
+ TQPixmap corners[4];
+
+ AlbumIconView *view;
+
+ AlbumIconItem *iconItem;
+};
+
+AlbumFileTip::AlbumFileTip(AlbumIconView* view)
+ : TQFrame(0, 0, WStyle_Customize | WStyle_NoBorder | WStyle_Tool |
+ WStyle_StaysOnTop | WX11BypassWM)
+{
+ d = new AlbumFileTipPriv;
+ d->view = view;
+ hide();
+
+ setPalette(TQToolTip::palette());
+ setFrameStyle(TQFrame::Plain | TQFrame::Box);
+ setLineWidth(1);
+
+ TQVBoxLayout *layout = new TQVBoxLayout(this, d->tipBorder+1, 0);
+
+ d->label = new TQLabel(this);
+ d->label->setMargin(0);
+ d->label->setAlignment(TQt::AlignHCenter | TQt::AlignVCenter);
+
+ layout->addWidget(d->label);
+ layout->setResizeMode(TQLayout::Fixed);
+
+ renderArrows();
+}
+
+AlbumFileTip::~AlbumFileTip()
+{
+ delete d;
+}
+
+void AlbumFileTip::setIconItem(AlbumIconItem* iconItem)
+{
+ d->iconItem = iconItem;
+
+ if (!d->iconItem ||
+ !AlbumSettings::instance()->showToolTipsIsValid())
+ {
+ hide();
+ }
+ else
+ {
+ updateText();
+ reposition();
+ if (isHidden())
+ show();
+ }
+}
+
+void AlbumFileTip::reposition()
+{
+ if (!d->iconItem)
+ return;
+
+ TQRect rect = d->iconItem->clickToOpenRect();
+ rect.moveTopLeft(d->view->contentsToViewport(rect.topLeft()));
+ rect.moveTopLeft(d->view->viewport()->mapToGlobal(rect.topLeft()));
+
+ TQPoint pos = rect.center();
+ // d->corner:
+ // 0: upperleft
+ // 1: upperright
+ // 2: lowerleft
+ // 3: lowerright
+
+ d->corner = 0;
+ // should the tooltip be shown to the left or to the right of the ivi ?
+
+#if KDE_IS_VERSION(3,2,0)
+ TQRect desk = TDEGlobalSettings::desktopGeometry(rect.center());
+#else
+ TQRect desk = TQApplication::desktop()->screenGeometry(
+ TQApplication::desktop()->screenNumber(rect.center()) );
+#endif
+
+ if (rect.center().x() + width() > desk.right())
+ {
+ // to the left
+ if (pos.x() - width() < 0)
+ {
+ pos.setX(0);
+ d->corner = 4;
+ }
+ else
+ {
+ pos.setX( pos.x() - width() );
+ d->corner = 1;
+ }
+ }
+
+ // should the tooltip be shown above or below the ivi ?
+ if (rect.bottom() + height() > desk.bottom())
+ {
+ // above
+ pos.setY( rect.top() - height() - 5);
+ d->corner += 2;
+ }
+ else
+ {
+ pos.setY( rect.bottom() + 5 );
+ }
+
+ move( pos );
+}
+
+void AlbumFileTip::renderArrows()
+{
+ int w = d->tipBorder;
+
+ // -- left top arrow -------------------------------------
+
+ TQPixmap& pix0 = d->corners[0];
+ pix0.resize(w, w);
+ pix0.fill(colorGroup().background());
+
+ TQPainter p0(&pix0);
+ p0.setPen(TQPen(TQt::black, 1));
+
+ for (int j=0; j<w; j++)
+ p0.drawLine(0, j, w-j-1, j);
+
+ p0.end();
+
+ // -- right top arrow ------------------------------------
+
+ TQPixmap& pix1 = d->corners[1];
+ pix1.resize(w, w);
+ pix1.fill(colorGroup().background());
+
+ TQPainter p1(&pix1);
+ p1.setPen(TQPen(TQt::black, 1));
+
+ for (int j=0; j<w; j++)
+ p1.drawLine(j, j, w-1, j);
+
+ p1.end();
+
+ // -- left bottom arrow ----------------------------------
+
+ TQPixmap& pix2 = d->corners[2];
+ pix2.resize(w, w);
+ pix2.fill(colorGroup().background());
+
+ TQPainter p2(&pix2);
+ p2.setPen(TQPen(TQt::black, 1));
+
+ for (int j=0; j<w; j++)
+ p2.drawLine(0, j, j, j);
+
+ p2.end();
+
+ // -- right bottom arrow ---------------------------------
+
+ TQPixmap& pix3 = d->corners[3];
+ pix3.resize(w, w);
+ pix3.fill(colorGroup().background());
+
+ TQPainter p3(&pix3);
+ p3.setPen(TQPen(TQt::black, 1));
+
+ for (int j=0; j<w; j++)
+ p3.drawLine(w-j-1, j, w-1, j);
+
+ p3.end();
+}
+
+bool AlbumFileTip::event(TQEvent *e)
+{
+ switch ( e->type() )
+ {
+ case TQEvent::Leave:
+ case TQEvent::MouseButtonPress:
+ case TQEvent::MouseButtonRelease:
+ case TQEvent::FocusIn:
+ case TQEvent::FocusOut:
+ case TQEvent::Wheel:
+ hide();
+ default:
+ break;
+ }
+
+ return TQFrame::event(e);
+}
+
+void AlbumFileTip::resizeEvent(TQResizeEvent* e)
+{
+ TQFrame::resizeEvent(e);
+ reposition();
+}
+
+void AlbumFileTip::drawContents(TQPainter *p)
+{
+ if (d->corner >= 4)
+ {
+ TQFrame::drawContents( p );
+ return;
+ }
+
+ TQPixmap &pix = d->corners[d->corner];
+
+ switch ( d->corner )
+ {
+ case 0:
+ p->drawPixmap( 3, 3, pix );
+ break;
+ case 1:
+ p->drawPixmap( width() - pix.width() - 3, 3, pix );
+ break;
+ case 2:
+ p->drawPixmap( 3, height() - pix.height() - 3, pix );
+ break;
+ case 3:
+ p->drawPixmap( width() - pix.width() - 3, height() - pix.height() - 3, pix );
+ break;
+ }
+
+ TQFrame::drawContents(p);
+}
+
+void AlbumFileTip::updateText()
+{
+ TQString tip, str;
+ TQString unavailable(i18n("unavailable"));
+
+ TQString headBeg("<tr bgcolor=\"orange\"><td colspan=\"2\">"
+ "<nobr><font size=\"-1\" color=\"black\"><b>");
+ TQString headEnd("</b></font></nobr></td></tr>");
+
+ TQString cellBeg("<tr><td><nobr><font size=\"-1\" color=\"black\">");
+ TQString cellMid("</font></nobr></td>"
+ "<td><nobr><font size=\"-1\" color=\"black\">");
+ TQString cellEnd("</font></nobr></td></tr>");
+
+ TQString cellSpecBeg("<tr><td><nobr><font size=\"-1\" color=\"black\">");
+ TQString cellSpecMid("</font></nobr></td>"
+ "<td><nobr><font size=\"-1\" color=\"steelblue\"><i>");
+ TQString cellSpecEnd("</i></font></nobr></td></tr>");
+
+ tip = "<table cellspacing=\"0\" cellpadding=\"0\" width=\"250\" border=\"0\">";
+
+ AlbumSettings* settings = AlbumSettings::instance();
+ const ImageInfo* info = d->iconItem->imageInfo();
+ TQFileInfo fileInfo(info->kurl().path());
+ KFileItem fi(KFileItem::Unknown, KFileItem::Unknown, info->kurl());
+ DMetadata metaData(info->kurl().path());
+
+ // -- File properties ----------------------------------------------
+
+ if (settings->getToolTipsShowFileName() ||
+ settings->getToolTipsShowFileDate() ||
+ settings->getToolTipsShowFileSize() ||
+ settings->getToolTipsShowImageType() ||
+ settings->getToolTipsShowImageDim())
+ {
+ tip += headBeg + i18n("File Properties") + headEnd;
+
+ if (settings->getToolTipsShowFileName())
+ {
+ tip += cellBeg + i18n("Name:") + cellMid;
+ tip += info->kurl().fileName() + cellEnd;
+ }
+
+ if (settings->getToolTipsShowFileDate())
+ {
+ TQDateTime modifiedDate = fileInfo.lastModified();
+ str = TDEGlobal::locale()->formatDateTime(modifiedDate, true, true);
+ tip += cellBeg + i18n("Modified:") + cellMid + str + cellEnd;
+ }
+
+ if (settings->getToolTipsShowFileSize())
+ {
+ tip += cellBeg + i18n("Size:") + cellMid;
+ str = i18n("%1 (%2)").arg(TDEIO::convertSize(fi.size()))
+ .arg(TDEGlobal::locale()->formatNumber(fi.size(), 0));
+ tip += str + cellEnd;
+ }
+
+ TQSize dims;
+#if KDCRAW_VERSION < 0x000106
+ TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
+#endif
+ TQString ext = fileInfo.extension(false).upper();
+
+ if (!ext.isEmpty() && rawFilesExt.upper().contains(ext))
+ {
+ str = i18n("RAW Image");
+ dims = metaData.getImageDimensions();
+ }
+ else
+ {
+ str = fi.mimeComment();
+
+ KFileMetaInfo meta = fi.metaInfo();
+ if (meta.isValid())
+ {
+ if (meta.containsGroup("Jpeg EXIF Data"))
+ dims = meta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
+ else if (meta.containsGroup("General"))
+ dims = meta.group("General").item("Dimensions").value().toSize();
+ else if (meta.containsGroup("Technical"))
+ dims = meta.group("Technical").item("Dimensions").value().toSize();
+ }
+ }
+
+ if (settings->getToolTipsShowImageType())
+ {
+ tip += cellBeg + i18n("Type:") + cellMid + str + cellEnd;
+ }
+
+ if (settings->getToolTipsShowImageDim())
+ {
+ TQString mpixels;
+ mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2);
+ str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)")
+ .arg(dims.width()).arg(dims.height()).arg(mpixels);
+ tip += cellBeg + i18n("Dimensions:") + cellMid + str + cellEnd;
+ }
+ }
+
+ // -- Photograph Info ----------------------------------------------------
+ // NOTE: If something is changed here, please updated imageproperties section too.
+
+ if (settings->getToolTipsShowPhotoMake() ||
+ settings->getToolTipsShowPhotoDate() ||
+ settings->getToolTipsShowPhotoFocal() ||
+ settings->getToolTipsShowPhotoExpo() ||
+ settings->getToolTipsShowPhotoMode() ||
+ settings->getToolTipsShowPhotoFlash() ||
+ settings->getToolTipsShowPhotoWB())
+ {
+ PhotoInfoContainer photoInfo = metaData.getPhotographInformations();
+
+ if (!photoInfo.isEmpty())
+ {
+ TQString metaStr;
+ tip += headBeg + i18n("Photograph Properties") + headEnd;
+
+ if (settings->getToolTipsShowPhotoMake())
+ {
+ str = TQString("%1 / %2").arg(photoInfo.make.isEmpty() ? unavailable : photoInfo.make)
+ .arg(photoInfo.model.isEmpty() ? unavailable : photoInfo.model);
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ metaStr += cellBeg + i18n("Make/Model:") + cellMid + TQStyleSheet::escape( str ) + cellEnd;
+ }
+
+ if (settings->getToolTipsShowPhotoDate())
+ {
+ if (photoInfo.dateTime.isValid())
+ {
+ str = TDEGlobal::locale()->formatDateTime(photoInfo.dateTime, true, true);
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ metaStr += cellBeg + i18n("Created:") + cellMid + TQStyleSheet::escape( str ) + cellEnd;
+ }
+ else
+ metaStr += cellBeg + i18n("Created:") + cellMid + TQStyleSheet::escape( unavailable ) + cellEnd;
+ }
+
+ if (settings->getToolTipsShowPhotoFocal())
+ {
+ str = photoInfo.aperture.isEmpty() ? unavailable : photoInfo.aperture;
+
+ if (photoInfo.focalLength35mm.isEmpty())
+ str += TQString(" / %1").arg(photoInfo.focalLength.isEmpty() ? unavailable : photoInfo.focalLength);
+ else
+ str += TQString(" / %1").arg(i18n("%1 (35mm: %2)").arg(photoInfo.focalLength).arg(photoInfo.focalLength35mm));
+
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ metaStr += cellBeg + i18n("Aperture/Focal:") + cellMid + TQStyleSheet::escape( str ) + cellEnd;
+ }
+
+ if (settings->getToolTipsShowPhotoExpo())
+ {
+ str = TQString("%1 / %2").arg(photoInfo.exposureTime.isEmpty() ? unavailable : photoInfo.exposureTime)
+ .arg(photoInfo.sensitivity.isEmpty() ? unavailable : i18n("%1 ISO").arg(photoInfo.sensitivity));
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ metaStr += cellBeg + i18n("Exposure/Sensitivity:") + cellMid + TQStyleSheet::escape( str ) + cellEnd;
+ }
+
+ if (settings->getToolTipsShowPhotoMode())
+ {
+
+ if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ str = unavailable;
+ else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ str = photoInfo.exposureMode;
+ else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty())
+ str = photoInfo.exposureProgram;
+ else
+ str = TQString("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram);
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ metaStr += cellBeg + i18n("Mode/Program:") + cellMid + TQStyleSheet::escape( str ) + cellEnd;
+ }
+
+ if (settings->getToolTipsShowPhotoFlash())
+ {
+ str = photoInfo.flash.isEmpty() ? unavailable : photoInfo.flash;
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ metaStr += cellBeg + i18n("Flash:") + cellMid + TQStyleSheet::escape( str ) + cellEnd;
+ }
+
+ if (settings->getToolTipsShowPhotoWB())
+ {
+ str = photoInfo.whiteBalance.isEmpty() ? unavailable : photoInfo.whiteBalance;
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ metaStr += cellBeg + i18n("White Balance:") + cellMid + TQStyleSheet::escape( str ) + cellEnd;
+ }
+
+ tip += metaStr;
+ }
+ }
+
+ // -- digiKam properties ------------------------------------------
+
+ if (settings->getToolTipsShowAlbumName() ||
+ settings->getToolTipsShowComments() ||
+ settings->getToolTipsShowTags() ||
+ settings->getToolTipsShowRating())
+ {
+ tip += headBeg + i18n("digiKam Properties") + headEnd;
+
+ if (settings->getToolTipsShowAlbumName())
+ {
+ PAlbum* album = info->album();
+ if (album)
+ tip += cellSpecBeg + i18n("Album:") + cellSpecMid + album->url().remove(0, 1) + cellSpecEnd;
+ }
+
+ if (settings->getToolTipsShowComments())
+ {
+ str = info->caption();
+ if (str.isEmpty()) str = TQString("---");
+ tip += cellSpecBeg + i18n("Caption:") + cellSpecMid + breakString(str) + cellSpecEnd;
+ }
+
+ if (settings->getToolTipsShowTags())
+ {
+ TQStringList tagPaths = info->tagPaths(false);
+
+ str = tagPaths.join(", ");
+ if (str.isEmpty()) str = TQString("---");
+ if (str.length() > d->maxStringLen) str = str.left(d->maxStringLen-3) + "...";
+ tip += cellSpecBeg + i18n("Tags:") + cellSpecMid + str + cellSpecEnd;
+ }
+
+ if (settings->getToolTipsShowRating())
+ {
+ str.fill( 'X', info->rating() );
+ if (str.isEmpty()) str = TQString("---");
+ tip += cellSpecBeg + i18n("Rating:") + cellSpecMid + str + cellSpecEnd;
+ }
+ }
+
+ tip += "</table>";
+
+ d->label->setText(tip);
+}
+
+TQString AlbumFileTip::breakString(const TQString& input)
+{
+ TQString str = input.simplifyWhiteSpace();
+ str = TQStyleSheet::escape(str);
+ uint maxLen = d->maxStringLen;
+
+ if (str.length() <= maxLen)
+ return str;
+
+ TQString br;
+
+ uint i = 0;
+ uint count = 0;
+
+ while (i < str.length())
+ {
+ if (count >= maxLen && str[i].isSpace())
+ {
+ count = 0;
+ br.append("<br>");
+ }
+ else
+ {
+ br.append(str[i]);
+ }
+
+ i++;
+ count++;
+ }
+
+ return br;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumfiletip.h b/src/digikam/albumfiletip.h
new file mode 100644
index 00000000..6ec70aa9
--- /dev/null
+++ b/src/digikam/albumfiletip.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-19
+ * Description : Album item file tip adapted from tdefiletip
+ * (konqueror - konq_iconviewwidget.cc)
+ *
+ * Copyright (C) 1998-1999 by Torben Weis <weis@kde.org>
+ * Copyright (C) 2000-2002 by David Faure <david@mandrakesoft.com>
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMFILETIP_H
+#define ALBUMFILETIP_H
+
+// TQt includes.
+
+#include <tqframe.h>
+#include <tqstring.h>
+
+namespace Digikam
+{
+
+class AlbumIconView;
+class AlbumIconItem;
+class AlbumFileTipPriv;
+
+class AlbumFileTip : public TQFrame
+{
+public:
+
+ AlbumFileTip(AlbumIconView* view);
+ ~AlbumFileTip();
+
+ void setIconItem(AlbumIconItem* iconItem);
+
+protected:
+
+ bool event(TQEvent *e);
+ void resizeEvent(TQResizeEvent* e);
+ void drawContents(TQPainter *p);
+
+private:
+
+ void reposition();
+ void renderArrows();
+ void updateText();
+ TQString breakString(const TQString& str);
+
+private:
+
+ AlbumFileTipPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMFILETIP_H */
diff --git a/src/digikam/albumfolderview.cpp b/src/digikam/albumfolderview.cpp
new file mode 100644
index 00000000..6e9048ab
--- /dev/null
+++ b/src/digikam/albumfolderview.cpp
@@ -0,0 +1,1636 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-06
+ * Description : Albums folder view.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqguardedptr.h>
+#include <tqdir.h>
+#include <tdepopupmenu.h>
+#include <tqcursor.h>
+#include <tqdatastream.h>
+#include <tqvaluelist.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kcalendarsystem.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdemessagebox.h>
+#include <tdeaction.h>
+#include <tdefiledialog.h>
+
+#include <tdeversion.h>
+#if KDE_IS_VERSION(3,2,0)
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// Local includes.
+
+#include "ddebug.h"
+#include "digikamapp.h"
+#include "albumlister.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albumpropsedit.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "thumbnailjob.h"
+#include "thumbnailsize.h"
+#include "folderitem.h"
+#include "cameraui.h"
+#include "dio.h"
+#include "dragobjects.h"
+#include "albumthumbnailloader.h"
+#include "deletedialog.h"
+#include "albumfolderview.h"
+#include "albumfolderview.moc"
+
+// X11 C Ansi includes.
+
+extern "C"
+{
+#include <X11/Xlib.h>
+}
+
+namespace Digikam
+{
+
+class AlbumFolderViewItem : public FolderItem
+{
+
+public:
+
+ AlbumFolderViewItem(TQListView *parent, PAlbum *album);
+ AlbumFolderViewItem(TQListViewItem *parent, PAlbum *album);
+
+ // special group item (collection/dates)
+ AlbumFolderViewItem(TQListViewItem* parent, const TQString& name,
+ int year, int month);
+
+ PAlbum* album() const;
+ int id() const;
+ bool isGroupItem() const;
+ int compare(TQListViewItem *i, int col, bool ascending) const;
+ void refresh();
+ void setOpen(bool o);
+ void setCount(int count);
+ int count();
+
+private:
+
+ bool m_groupItem;
+
+ int m_count;
+ int m_year;
+ int m_month;
+
+ PAlbum *m_album;
+};
+
+AlbumFolderViewItem::AlbumFolderViewItem(TQListView *parent, PAlbum *album)
+ : FolderItem(parent, album->title())
+{
+ setDragEnabled(true);
+ m_album = album;
+ m_groupItem = false;
+ m_count = 0;
+}
+
+AlbumFolderViewItem::AlbumFolderViewItem(TQListViewItem *parent, PAlbum *album)
+ : FolderItem(parent, album->title())
+{
+ setDragEnabled(true);
+ m_album = album;
+ m_groupItem = false;
+ m_count = 0;
+}
+
+// special group item (collection/dates)
+AlbumFolderViewItem::AlbumFolderViewItem(TQListViewItem* parent, const TQString& name,
+ int year, int month)
+ : FolderItem(parent, name, true)
+{
+ m_album = 0;
+ m_year = year;
+ m_month = month;
+ m_groupItem = true;
+ m_count = 0;
+}
+
+void AlbumFolderViewItem::refresh()
+{
+ if (!m_album) return;
+
+ if (AlbumSettings::instance()->getShowFolderTreeViewItemsCount() &&
+ dynamic_cast<AlbumFolderViewItem*>(parent()))
+ {
+ if (isOpen())
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(m_count));
+ else
+ {
+ int countRecursive = m_count;
+ AlbumIterator it(m_album);
+ while ( it.current() )
+ {
+ AlbumFolderViewItem *item = (AlbumFolderViewItem*)it.current()->extraData(listView());
+ if (item)
+ countRecursive += item->count();
+ ++it;
+ }
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(countRecursive));
+ }
+ }
+ else
+ {
+ setText(0, m_album->title());
+ }
+}
+
+void AlbumFolderViewItem::setOpen(bool o)
+{
+ TQListViewItem::setOpen(o);
+ refresh();
+}
+
+PAlbum* AlbumFolderViewItem::album() const
+{
+ return m_album;
+}
+
+int AlbumFolderViewItem::id() const
+{
+ if (m_groupItem)
+ {
+ if (m_year != 0 && m_month != 0)
+ {
+ return (m_year*(-100) + m_month*(-1));
+ }
+ else
+ {
+ return ( - (AlbumSettings::instance()->getAlbumCollectionNames()
+ .findIndex(text(0)) ) );
+ }
+ }
+ else
+ {
+ return m_album ? m_album->id() : 0;
+ }
+}
+
+bool AlbumFolderViewItem::isGroupItem() const
+{
+ return m_groupItem;
+}
+
+int AlbumFolderViewItem::compare(TQListViewItem *i, int col, bool ascending) const
+{
+ if (!m_groupItem || m_year == 0 || m_month == 0)
+ return TQListViewItem::compare(i, col, ascending);
+
+ AlbumFolderViewItem* thatItem = dynamic_cast<AlbumFolderViewItem*>(i);
+ if (!thatItem)
+ return 0;
+
+ int myWeight = m_year*100 + m_month;
+ int hisWeight = thatItem->m_year*100 + thatItem->m_month;
+
+ if (myWeight == hisWeight)
+ return 0;
+ else if (myWeight > hisWeight)
+ return 1;
+ else
+ return -1;
+}
+
+void AlbumFolderViewItem::setCount(int count)
+{
+ m_count = count;
+ refresh();
+}
+
+int AlbumFolderViewItem::count()
+{
+ return m_count;
+}
+
+// -----------------------------------------------------------------------------
+
+class AlbumFolderViewPriv
+{
+public:
+
+ AlbumFolderViewPriv()
+ {
+ albumMan = 0;
+ iconThumbJob = 0;
+ }
+
+ AlbumManager *albumMan;
+ ThumbnailJob *iconThumbJob;
+ TQValueList<AlbumFolderViewItem*> groupItems;
+};
+
+AlbumFolderView::AlbumFolderView(TQWidget *parent)
+ : FolderView(parent, "AlbumFolderView")
+{
+ d = new AlbumFolderViewPriv();
+ d->albumMan = AlbumManager::instance();
+ d->iconThumbJob = 0;
+
+ addColumn(i18n("My Albums"));
+ setResizeMode(TQListView::LastColumn);
+ setRootIsDecorated(false);
+ setAllColumnsShowFocus(true);
+
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(slotAlbumsCleared()));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumIconChanged(Album*)),
+ this, TQ_SLOT(slotAlbumIconChanged(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumRenamed(Album*)),
+ this, TQ_SLOT(slotAlbumRenamed(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalPAlbumsDirty(const TQMap<int, int>&)),
+ this, TQ_SLOT(slotRefresh(const TQMap<int, int>&)));
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+
+ connect(loader, TQ_SIGNAL(signalThumbnail(Album *, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnailFromIcon(Album *, const TQPixmap&)));
+
+ connect(loader, TQ_SIGNAL(signalFailed(Album *)),
+ this, TQ_SLOT(slotThumbnailLost(Album *)));
+
+ connect(loader, TQ_SIGNAL(signalReloadThumbnails()),
+ this, TQ_SLOT(slotReloadThumbnails()));
+
+ connect(this, TQ_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
+ this, TQ_SLOT(slotContextMenu(TQListViewItem*, const TQPoint&, int)));
+
+ connect(this, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+}
+
+AlbumFolderView::~AlbumFolderView()
+{
+ if (d->iconThumbJob)
+ d->iconThumbJob->kill();
+
+ saveViewState();
+ delete d;
+}
+
+void AlbumFolderView::slotTextFolderFilterChanged(const TQString& filter)
+{
+ if (filter.isEmpty())
+ {
+ collapseView();
+ return;
+ }
+
+ TQString search = filter.lower();
+
+ bool atleastOneMatch = false;
+
+ AlbumList pList = d->albumMan->allPAlbums();
+ for (AlbumList::iterator it = pList.begin(); it != pList.end(); ++it)
+ {
+ PAlbum* palbum = (PAlbum*)(*it);
+
+ // don't touch the root Album
+ if (palbum->isRoot())
+ continue;
+
+ bool match = palbum->title().lower().contains(search);
+ bool doesExpand = false;
+ if (!match)
+ {
+ // check if any of the parents match the search
+ Album* parent = palbum->parent();
+ while (parent && !parent->isRoot())
+ {
+ if (parent->title().lower().contains(search))
+ {
+ match = true;
+ break;
+ }
+
+ parent = parent->parent();
+ }
+ }
+
+ if (!match)
+ {
+ // check if any of the children match the search
+ AlbumIterator it(palbum);
+ while (it.current())
+ {
+ if ((*it)->title().lower().contains(search))
+ {
+ match = true;
+ doesExpand = true;
+ break;
+ }
+ ++it;
+ }
+ }
+
+ AlbumFolderViewItem* viewItem = (AlbumFolderViewItem*) palbum->extraData(this);
+
+ if (match)
+ {
+ atleastOneMatch = true;
+
+ if (viewItem)
+ {
+ viewItem->setVisible(true);
+ viewItem->setOpen(doesExpand);
+ }
+ }
+ else
+ {
+ if (viewItem)
+ {
+ viewItem->setVisible(false);
+ viewItem->setOpen(false);
+ }
+ }
+ }
+
+ emit signalTextFolderFilterMatch(atleastOneMatch);
+}
+
+void AlbumFolderView::slotAlbumAdded(Album *album)
+{
+ if(!album)
+ return;
+
+ PAlbum *palbum = dynamic_cast<PAlbum*>(album);
+ if(!palbum)
+ return;
+
+ bool failed;
+ AlbumFolderViewItem* parent = findParent(palbum, failed);
+ if (failed)
+ {
+ DWarning() << k_funcinfo << " Failed to find Album parent "
+ << palbum->url() << endl;
+ return;
+ }
+
+ AlbumFolderViewItem *item;
+ if (!parent)
+ {
+ // root album
+ item = new AlbumFolderViewItem(this, palbum);
+ palbum->setExtraData(this, item);
+ item->setOpen(true);
+ }
+ else
+ {
+ item = new AlbumFolderViewItem(parent, palbum);
+ palbum->setExtraData(this, item);
+ }
+
+ setAlbumThumbnail(palbum);
+}
+
+void AlbumFolderView::slotAlbumDeleted(Album *album)
+{
+ if(!album)
+ return;
+
+ PAlbum* palbum = dynamic_cast<PAlbum*>(album);
+ if(!palbum)
+ return;
+
+ if(!palbum->icon().isEmpty() && d->iconThumbJob)
+ d->iconThumbJob->removeItem(palbum->icon());
+
+ AlbumFolderViewItem* item = (AlbumFolderViewItem*) palbum->extraData(this);
+ if(item)
+ {
+ AlbumFolderViewItem *itemParent = dynamic_cast<AlbumFolderViewItem*>(item->parent());
+
+ if(itemParent)
+ itemParent->takeItem(item);
+ else
+ takeItem(item);
+
+ delete item;
+ clearEmptyGroupItems();
+ }
+}
+
+void AlbumFolderView::slotAlbumRenamed(Album *album)
+{
+ PAlbum* palbum = dynamic_cast<PAlbum*>(album);
+ if(!palbum)
+ return;
+
+ AlbumFolderViewItem* item = (AlbumFolderViewItem*) palbum->extraData(this);
+ if(item)
+ item->refresh();
+ if (item->parent())
+ item->parent()->sort();
+}
+
+void AlbumFolderView::slotAlbumsCleared()
+{
+ d->groupItems.clear();
+ clear();
+}
+
+void AlbumFolderView::setAlbumThumbnail(PAlbum *album)
+{
+ if(!album)
+ return;
+
+ AlbumFolderViewItem* item = (AlbumFolderViewItem*) album->extraData(this);
+
+ if(!item)
+ return;
+
+ // Either, getThumbnail returns true and loads an icon asynchronously.
+ // Then, for the time being, we set the standard icon.
+ // Or, no icon is associated with the album, then we set the standard icon anyway.
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ item->setPixmap(0, loader->getStandardAlbumIcon(album));
+ loader->getAlbumThumbnail(album);
+}
+
+void AlbumFolderView::setCurrentAlbum(Album *album)
+{
+ if(!album) return;
+
+ AlbumFolderViewItem* item = (AlbumFolderViewItem*) album->extraData(this);
+ if(!item) return;
+
+ setCurrentItem(item);
+ ensureItemVisible(item);
+}
+
+void AlbumFolderView::slotGotThumbnailFromIcon(Album *album,
+ const TQPixmap& thumbnail)
+{
+ if(!album || album->type() != Album::PHYSICAL)
+ return;
+
+ AlbumFolderViewItem* item = (AlbumFolderViewItem*)album->extraData(this);
+
+ if(!item)
+ return;
+
+ item->setPixmap(0, thumbnail);
+}
+
+void AlbumFolderView::slotThumbnailLost(Album *)
+{
+ // we already set the standard icon before loading
+}
+
+void AlbumFolderView::slotReloadThumbnails()
+{
+ AlbumList tList = d->albumMan->allPAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ PAlbum* album = (PAlbum*)(*it);
+ setAlbumThumbnail(album);
+ }
+}
+
+void AlbumFolderView::slotAlbumIconChanged(Album* album)
+{
+ if(!album || album->type() != Album::PHYSICAL)
+ return;
+
+ setAlbumThumbnail((PAlbum*)album);
+}
+
+void AlbumFolderView::slotSelectionChanged()
+{
+ if (!active())
+ return;
+
+ TQListViewItem* selItem = 0;
+ TQListViewItemIterator it(this);
+ while(it.current())
+ {
+ if(it.current()->isSelected())
+ {
+ selItem = it.current();
+ break;
+ }
+ ++it;
+ }
+
+ if(!selItem)
+ {
+ d->albumMan->setCurrentAlbum(0);
+ return;
+ }
+
+ AlbumFolderViewItem *albumitem = dynamic_cast<AlbumFolderViewItem*>(selItem);
+ if(!albumitem)
+ {
+ d->albumMan->setCurrentAlbum(0);
+ return;
+ }
+
+ d->albumMan->setCurrentAlbum(albumitem->album());
+}
+
+void AlbumFolderView::slotContextMenu(TQListViewItem *listitem, const TQPoint &, int)
+{
+ TDEActionMenu menuImport(i18n("Import"));
+ TDEActionMenu menuExport(i18n("Export"));
+ TDEActionMenu menuKIPIBatch(i18n("Batch Process"));
+
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("My Albums"));
+ popmenu.insertItem(SmallIcon("albumfolder-new"), i18n("New Album..."), 10);
+
+ AlbumFolderViewItem *item = dynamic_cast<AlbumFolderViewItem*>(listitem);
+ if (item && !item->album())
+ {
+ // if collection/date return
+ return;
+ }
+
+ // Root folder only shows "New Album..."
+ if(item && item->parent())
+ {
+ popmenu.insertItem(SmallIcon("pencil"), i18n("Rename..."), 14);
+ popmenu.insertItem(SmallIcon("albumfolder-properties"), i18n("Album Properties..."), 11);
+ popmenu.insertItem(SmallIcon("reload_page"), i18n("Reset Album Icon"), 13);
+ popmenu.insertSeparator();
+
+ // Add KIPI Albums plugins Actions
+ TDEAction *action;
+ const TQPtrList<TDEAction>& albumActions = DigikamApp::getinstance()->menuAlbumActions();
+ if(!albumActions.isEmpty())
+ {
+ TQPtrListIterator<TDEAction> it(albumActions);
+ while((action = it.current()))
+ {
+ action->plug(&popmenu);
+ ++it;
+ }
+ }
+
+ // Add All Import Actions
+ const TQPtrList<TDEAction> importActions = DigikamApp::getinstance()->menuImportActions();
+ if(!importActions.isEmpty())
+ {
+ TQPtrListIterator<TDEAction> it3(importActions);
+ while((action = it3.current()))
+ {
+ menuImport.insert(action);
+ ++it3;
+ }
+ menuImport.plug(&popmenu);
+ }
+
+ // Add All Export Actions
+ const TQPtrList<TDEAction> exportActions = DigikamApp::getinstance()->menuExportActions();
+ if(!exportActions.isEmpty())
+ {
+ TQPtrListIterator<TDEAction> it4(exportActions);
+ while((action = it4.current()))
+ {
+ menuExport.insert(action);
+ ++it4;
+ }
+ menuExport.plug(&popmenu);
+ }
+
+ // Add KIPI Batch processes plugins Actions
+ const TQPtrList<TDEAction>& batchActions = DigikamApp::getinstance()->menuBatchActions();
+ if(!batchActions.isEmpty())
+ {
+ TQPtrListIterator<TDEAction> it2(batchActions);
+ while((action = it2.current()))
+ {
+ menuKIPIBatch.insert(action);
+ ++it2;
+ }
+ menuKIPIBatch.plug(&popmenu);
+ }
+
+ if(!albumActions.isEmpty() || !batchActions.isEmpty() ||
+ !importActions.isEmpty())
+ {
+ popmenu.insertSeparator(-1);
+ }
+
+ if(AlbumSettings::instance()->getUseTrash())
+ {
+ popmenu.insertItem(SmallIcon("edittrash"),
+ i18n("Move Album to Trash"), 12);
+ }
+ else
+ {
+ popmenu.insertItem(SmallIcon("edit-delete"),
+ i18n("Delete Album"), 12);
+ }
+ }
+
+ switch(popmenu.exec((TQCursor::pos())))
+ {
+ case 10:
+ {
+ albumNew(item);
+ break;
+ }
+ case 11:
+ {
+ albumEdit(item);
+ break;
+ }
+ case 12:
+ {
+ albumDelete(item);
+ break;
+ }
+ case 13:
+ {
+ TQString err;
+ d->albumMan->updatePAlbumIcon(item->album(), 0, err);
+ break;
+ }
+ case 14:
+ {
+ albumRename(item);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void AlbumFolderView::albumNew()
+{
+ AlbumFolderViewItem *item = dynamic_cast<AlbumFolderViewItem*>(selectedItem());
+ if (!item)
+ {
+ item = dynamic_cast<AlbumFolderViewItem*>(firstChild());
+ }
+
+ if (!item)
+ return;
+
+ albumNew(item);
+}
+
+void AlbumFolderView::albumNew(AlbumFolderViewItem *item)
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if(!settings)
+ {
+ DWarning() << "AlbumFolderView: Couldn't get Album Settings" << endl;
+ return;
+ }
+
+ TQDir libraryDir(settings->getAlbumLibraryPath());
+ if(!libraryDir.exists())
+ {
+ KMessageBox::error(0,
+ i18n("The album library has not been set correctly.\n"
+ "Select \"Configure Digikam\" from the Settings "
+ "menu and choose a folder to use for the album "
+ "library."));
+ return;
+ }
+
+ PAlbum *parent;
+
+ if(!item)
+ parent = d->albumMan->findPAlbum(0);
+ else
+ parent = item->album();
+
+ if (!parent)
+ return;
+
+ TQString title;
+ TQString comments;
+ TQString collection;
+ TQDate date;
+ TQStringList albumCollections;
+
+ if(!AlbumPropsEdit::createNew(parent, title, comments, date, collection,
+ albumCollections))
+ return;
+
+ TQStringList oldAlbumCollections(AlbumSettings::instance()->getAlbumCollectionNames());
+ if(albumCollections != oldAlbumCollections)
+ {
+ AlbumSettings::instance()->setAlbumCollectionNames(albumCollections);
+ resort();
+ }
+
+ TQString errMsg;
+ PAlbum* album = d->albumMan->createPAlbum(parent, title, comments,
+ date, collection, errMsg);
+ if (!album)
+ {
+ KMessageBox::error(0, errMsg);
+ return;
+ }
+
+ // by this time the signalAlbumAdded has been fired and the appropriate
+ // AlbumFolderViewItem has been created. Now make this folderviewitem visible
+ AlbumFolderViewItem* newItem = (AlbumFolderViewItem*)album->extraData(this);
+ if (newItem)
+ {
+ if(item)
+ item->setOpen(true);
+
+ ensureItemVisible(newItem);
+ }
+}
+
+void AlbumFolderView::albumDelete()
+{
+ AlbumFolderViewItem *item = dynamic_cast<AlbumFolderViewItem*>(selectedItem());
+ if(!item)
+ return;
+
+ albumDelete(item);
+}
+
+void AlbumFolderView::albumDelete(AlbumFolderViewItem *item)
+{
+ PAlbum *album = item->album();
+
+ if(!album || album->isRoot())
+ return;
+
+ // find subalbums
+ KURL::List childrenList;
+ addAlbumChildrenToList(childrenList, album);
+
+ DeleteDialog dialog(this);
+
+ // All subalbums will be presented in the list as well
+ if (!dialog.confirmDeleteList(childrenList,
+ childrenList.size() == 1 ?
+ DeleteDialogMode::Albums : DeleteDialogMode::Subalbums,
+ DeleteDialogMode::UserPreference))
+ return;
+
+ bool useTrash = !dialog.shouldDelete();
+
+ // Currently trash tdeioslave can handle only full paths.
+ // pass full folder path to the trashing job
+ KURL u;
+ u.setProtocol("file");
+ u.setPath(album->folderPath());
+ TDEIO::Job* job = DIO::del(u, useTrash);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job *)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job *)));
+}
+
+void AlbumFolderView::addAlbumChildrenToList(KURL::List &list, Album *album)
+{
+ // simple recursive helper function
+ if (album)
+ {
+ list.append(album->kurl());
+ AlbumIterator it(album);
+ while(it.current())
+ {
+ addAlbumChildrenToList(list, *it);
+ ++it;
+ }
+ }
+}
+
+void AlbumFolderView::slotDIOResult(TDEIO::Job* job)
+{
+ if (job->error())
+ job->showErrorDialog(this);
+}
+
+void AlbumFolderView::albumRename()
+{
+ AlbumFolderViewItem *item = dynamic_cast<AlbumFolderViewItem*>(selectedItem());
+ if(!item)
+ return;
+
+ albumRename(item);
+}
+
+void AlbumFolderView::albumRename(AlbumFolderViewItem* item)
+{
+ PAlbum *album = item->album();
+
+ if (!album)
+ return;
+
+ TQString oldTitle(album->title());
+ bool ok;
+
+#if KDE_IS_VERSION(3,2,0)
+ TQString title = KInputDialog::getText(i18n("Rename Album (%1)").arg(oldTitle),
+ i18n("Enter new album name:"),
+ oldTitle, &ok, this);
+#else
+ TQString title = KLineEditDlg::getText(i18n("Rename Item (%1)").arg(oldTitle),
+ i18n("Enter new album name:"),
+ oldTitle, &ok, this);
+#endif
+
+ if (!ok)
+ return;
+
+ if(title != oldTitle)
+ {
+ TQString errMsg;
+ if (!d->albumMan->renamePAlbum(album, title, errMsg))
+ KMessageBox::error(0, errMsg);
+ }
+
+ emit signalAlbumModified();
+}
+
+void AlbumFolderView::albumEdit()
+{
+ AlbumFolderViewItem *item = dynamic_cast<AlbumFolderViewItem*>(selectedItem());
+ if(!item)
+ return;
+
+ albumEdit(item);
+}
+
+void AlbumFolderView::albumEdit(AlbumFolderViewItem* item)
+{
+ PAlbum *album = item->album();
+
+ if (!album)
+ return;
+
+ TQString oldTitle(album->title());
+ TQString oldComments(album->caption());
+ TQString oldCollection(album->collection());
+ TQDate oldDate(album->date());
+ TQStringList oldAlbumCollections(AlbumSettings::instance()->getAlbumCollectionNames());
+
+ TQString title, comments, collection;
+ TQDate date;
+ TQStringList albumCollections;
+
+ if(AlbumPropsEdit::editProps(album, title, comments, date,
+ collection, albumCollections))
+ {
+ if(comments != oldComments)
+ album->setCaption(comments);
+
+ if(date != oldDate && date.isValid())
+ album->setDate(date);
+
+ if(collection != oldCollection)
+ album->setCollection(collection);
+
+ AlbumSettings::instance()->setAlbumCollectionNames(albumCollections);
+ resort();
+
+ // Do this last : so that if anything else changed we can
+ // successfuly save to the db with the old name
+
+ if(title != oldTitle)
+ {
+ TQString errMsg;
+ if (!d->albumMan->renamePAlbum(album, title, errMsg))
+ KMessageBox::error(0, errMsg);
+ }
+
+ emit signalAlbumModified();
+ }
+}
+
+TQDragObject* AlbumFolderView::dragObject()
+{
+ AlbumFolderViewItem *item = dynamic_cast<AlbumFolderViewItem*>(dragItem());
+ if(!item)
+ return 0;
+
+ PAlbum *album = item->album();
+ if(album->isRoot())
+ return 0;
+
+ AlbumDrag *a = new AlbumDrag(album->kurl(), album->id(), this);
+ if(!a)
+ return 0;
+ a->setPixmap(*item->pixmap(0));
+
+ return a;
+}
+
+bool AlbumFolderView::acceptDrop(const TQDropEvent *e) const
+{
+ TQPoint vp = contentsToViewport(e->pos());
+ AlbumFolderViewItem *itemDrop = dynamic_cast<AlbumFolderViewItem*>(itemAt(vp));
+ AlbumFolderViewItem *itemDrag = dynamic_cast<AlbumFolderViewItem*>(dragItem());
+
+ if(AlbumDrag::canDecode(e))
+ {
+ switch(AlbumSettings::instance()->getAlbumSortOrder())
+ {
+ case(AlbumSettings::ByFolder):
+ {
+ // Allow dragging at the root, to move the album at the root
+ if(!itemDrop)
+ return true;
+
+ // Dragging an item on itself makes no sense
+ if(itemDrag == itemDrop)
+ return false;
+
+ // Dragging a parent on its child makes no sense
+ if(itemDrag && itemDrag->album()->isAncestorOf(itemDrop->album()))
+ return false;
+
+ return true;
+ }
+ case (AlbumSettings::ByCollection):
+ {
+ if (!itemDrop)
+ return false;
+
+ // Only allow dragging onto Collection
+ if (itemDrop->isGroupItem())
+ return true;
+
+ return false;
+ }
+ default:
+ {
+ return false;
+ }
+ }
+ }
+
+ if(itemDrop && !itemDrop->parent())
+ {
+ // Do not allow drop images on album root
+ return false;
+ }
+
+ if (itemDrop && itemDrop->isGroupItem())
+ {
+ // do not allow drop on a group item
+ return false;
+ }
+
+ if(ItemDrag::canDecode(e))
+ {
+ return true;
+ }
+
+ if (CameraItemListDrag::canDecode(e))
+ {
+ return true;
+ }
+
+ if(TQUriDrag::canDecode(e))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void AlbumFolderView::contentsDropEvent(TQDropEvent *e)
+{
+ FolderView::contentsDropEvent(e);
+
+ if(!acceptDrop(e))
+ return;
+
+ TQPoint vp = contentsToViewport(e->pos());
+ AlbumFolderViewItem *itemDrop = dynamic_cast<AlbumFolderViewItem*>(itemAt(vp));
+
+ if(AlbumDrag::canDecode(e))
+ {
+ AlbumFolderViewItem *itemDrag = dynamic_cast<AlbumFolderViewItem*>(dragItem());
+ if(!itemDrag)
+ return;
+
+ if (AlbumSettings::instance()->getAlbumSortOrder()
+ == AlbumSettings::ByFolder)
+ {
+ // TODO: Copy?
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Albums"));
+ popMenu.insertItem(SmallIcon("goto"), i18n("&Move Here"), 10);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("C&ancel"), 20);
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+
+ if(id == 10)
+ {
+ PAlbum *album = itemDrag->album();
+ PAlbum *destAlbum;
+ if(!itemDrop)
+ {
+ // move dragItem to the root
+ destAlbum = d->albumMan->findPAlbum(0);
+ }
+ else
+ {
+ // move dragItem below dropItem
+ destAlbum = itemDrop->album();
+ }
+ TDEIO::Job* job = DIO::move(album->kurl(), destAlbum->kurl());
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ }
+ }
+ else if (AlbumSettings::instance()->getAlbumSortOrder()
+ == AlbumSettings::ByCollection)
+ {
+ if (!itemDrop)
+ return;
+
+ if (itemDrop->isGroupItem())
+ {
+ PAlbum *album = itemDrag->album();
+ if (!album)
+ return;
+
+ album->setCollection(itemDrop->text(0));
+ resort();
+ }
+ }
+
+ return;
+ }
+
+ if (ItemDrag::canDecode(e))
+ {
+ if (!itemDrop)
+ return;
+
+ PAlbum *destAlbum = itemDrop->album();
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ if (!ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
+ return;
+
+ if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
+ return;
+
+ // Check if items dropped come from outside current album.
+ // This can be the case with reccursive content album mode.
+ KURL::List extUrls;
+ ImageInfoList extImgInfList;
+ for (TQValueList<int>::iterator it = imageIDs.begin(); it != imageIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ if (info->albumID() != destAlbum->id())
+ {
+ extUrls.append(info->kurlForKIO());
+ extImgInfList.append(info);
+ }
+ }
+
+ int id = 0;
+ char keys_return[32];
+ XQueryKeymap(x11Display(), keys_return);
+ int key_1 = XKeysymToKeycode(x11Display(), 0xFFE3);
+ int key_2 = XKeysymToKeycode(x11Display(), 0xFFE4);
+ int key_3 = XKeysymToKeycode(x11Display(), 0xFFE1);
+ int key_4 = XKeysymToKeycode(x11Display(), 0xFFE2);
+
+ if(extUrls.isEmpty())
+ {
+ // Setting the dropped image as the album thumbnail
+ // If the ctrl key is pressed, when dropping the image, the
+ // thumbnail is set without a popup menu
+ if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 12;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Albums"));
+ popMenu.insertItem(i18n("Set as Album Thumbnail"), 12);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ if(id == 12)
+ {
+ TQString errMsg;
+ d->albumMan->updatePAlbumIcon(destAlbum, imageIDs.first(), errMsg);
+ }
+ return;
+ }
+
+ // If shift key is pressed while dragging, move the drag object without
+ // displaying popup menu -> move
+ if (((keys_return[key_3 / 8]) && (1 << (key_3 % 8))) ||
+ ((keys_return[key_4 / 8]) && (1 << (key_4 % 8))))
+ {
+ id = 10;
+ }
+ // If ctrl key is pressed while dragging, copy the drag object without
+ // displaying popup menu -> copy
+ else if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 11;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Albums"));
+ popMenu.insertItem( SmallIcon("goto"), i18n("&Move Here"), 10 );
+ popMenu.insertItem( SmallIcon("edit-copy"), i18n("&Copy Here"), 11 );
+ if (imageIDs.count() == 1)
+ popMenu.insertItem(i18n("Set as Album Thumbnail"), 12);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ switch(id)
+ {
+ case 10:
+ {
+ TDEIO::Job* job = DIO::move(extUrls, destAlbum->kurl());
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+
+ // In recurssive album contents mode, we need to force AlbumLister to take a care about
+ // moved items. This will have no incidence in normal mode.
+ ImageInfo* item;
+ for (ImageInfoListIterator it(extImgInfList); (item = it.current()) ; ++it)
+ {
+ AlbumLister::instance()->invalidateItem(item);
+ }
+ break;
+ }
+ case 11:
+ {
+ TDEIO::Job* job = DIO::copy(extUrls, destAlbum->kurl());
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ break;
+ }
+ case 12:
+ {
+ TQString errMsg;
+ d->albumMan->updatePAlbumIcon(destAlbum, imageIDs.first(), errMsg);
+ }
+ default:
+ break;
+ }
+
+ return;
+ }
+
+ // -- DnD from Camera GUI ------------------------------------------------
+
+ if(CameraItemListDrag::canDecode(e))
+ {
+ Album *album = dynamic_cast<Album*>(itemDrop->album());
+ if (!album) return;
+
+ CameraUI *ui = dynamic_cast<CameraUI*>(e->source());
+ if (ui)
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Albums"));
+ popMenu.insertItem(SmallIcon("go-down"), i18n("Download from camera"), 10);
+ popMenu.insertItem(SmallIcon("go-down"), i18n("Download && Delete from camera"), 11);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("&Cancel"));
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+ switch(id)
+ {
+ case 10: // Download from camera
+ {
+ ui->slotDownload(true, false, album);
+ break;
+ }
+ case 11: // Download and Delete from camera
+ {
+ ui->slotDownload(true, true, album);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ // -- DnD from an external source ----------------------------------------
+
+ if(TQUriDrag::canDecode(e))
+ {
+ PAlbum* destAlbum = 0;
+
+ if (itemDrop)
+ destAlbum = itemDrop->album();
+ else
+ destAlbum = d->albumMan->findPAlbum(0);
+
+ // B.K.O #119205: do not handle root album.
+ if (destAlbum->isRoot())
+ return;
+
+ KURL destURL(destAlbum->kurl());
+
+ KURL::List srcURLs;
+ KURLDrag::decode(e, srcURLs);
+
+ char keys_return[32];
+ XQueryKeymap(x11Display(), keys_return);
+ int id = 0;
+
+ int key_1 = XKeysymToKeycode(x11Display(), 0xFFE3);
+ int key_2 = XKeysymToKeycode(x11Display(), 0xFFE4);
+ int key_3 = XKeysymToKeycode(x11Display(), 0xFFE1);
+ int key_4 = XKeysymToKeycode(x11Display(), 0xFFE2);
+ // If shift key is pressed while dropping, move the drag object without
+ // displaying popup menu -> move
+ if(((keys_return[key_3 / 8]) && (1 << (key_3 % 8))) ||
+ ((keys_return[key_4 / 8]) && (1 << (key_4 % 8))))
+ {
+ id = 10;
+ }
+ // If ctrl key is pressed while dropping, copy the drag object without
+ // displaying popup menu -> copy
+ else if(((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 11;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Albums"));
+ popMenu.insertItem( SmallIcon("goto"), i18n("&Move Here"), 10 );
+ popMenu.insertItem( SmallIcon("edit-copy"), i18n("&Copy Here"), 11 );
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ switch(id)
+ {
+ case 10:
+ {
+ TDEIO::Job* job = DIO::move(srcURLs, destAlbum->kurl());
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ break;
+ }
+ case 11:
+ {
+ TDEIO::Job* job = DIO::copy(srcURLs, destAlbum->kurl());
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ break;
+ }
+ default:
+ break;
+ }
+
+ return;
+ }
+}
+
+void AlbumFolderView::albumImportFolder()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ TQDir libraryDir(settings->getAlbumLibraryPath());
+ if(!libraryDir.exists())
+ {
+ KMessageBox::error(0,
+ i18n("The album library has not been set correctly.\n"
+ "Select \"Configure digiKam\" from the Settings "
+ "menu and choose a folder to use for the album "
+ "library."));
+ return;
+ }
+
+ PAlbum* parent = 0;
+ if(selectedItem())
+ {
+ AlbumFolderViewItem *folderItem = dynamic_cast<AlbumFolderViewItem*>(selectedItem());
+ Album *album = folderItem->album();
+ if (album && album->type() == Album::PHYSICAL)
+ {
+ parent = dynamic_cast<PAlbum*>(album);
+ }
+ }
+ if(!parent)
+ parent = dynamic_cast<PAlbum*>(d->albumMan->findPAlbum(0));
+
+ TQString libraryPath = parent->folderPath();
+
+ KFileDialog dlg(TQString(), "inode/directory", this, "importFolder", true);
+ dlg.setCaption(i18n("Select folders to import"));
+ dlg.setMode(KFile::Directory | KFile::Files);
+ if(dlg.exec() != TQDialog::Accepted)
+ return;
+
+ KURL::List urls = dlg.selectedURLs();
+ if(urls.empty())
+ return;
+
+ TDEIO::Job* job = DIO::copy(urls, parent->kurl());
+ connect(job, TQ_SIGNAL(result(TDEIO::Job *)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job *)));
+}
+
+void AlbumFolderView::selectItem(int id)
+{
+ PAlbum* album = d->albumMan->findPAlbum(id);
+ if(!album)
+ return;
+
+ AlbumFolderViewItem *item = (AlbumFolderViewItem*)album->extraData(this);
+ if(item)
+ {
+ setSelected(item, true);
+ ensureItemVisible(item);
+ }
+}
+
+AlbumFolderViewItem* AlbumFolderView::findParent(PAlbum* album, bool& failed)
+{
+ if (album->isRoot())
+ {
+ failed = false;
+ return 0;
+ }
+
+ switch(AlbumSettings::instance()->getAlbumSortOrder())
+ {
+ case(AlbumSettings::ByFolder):
+ {
+ return findParentByFolder(album, failed);
+ }
+ case(AlbumSettings::ByCollection):
+ {
+ return findParentByCollection(album, failed);
+ }
+ case(AlbumSettings::ByDate):
+ {
+ return findParentByDate(album, failed);
+ }
+ }
+
+ failed = true;
+ return 0;
+}
+
+AlbumFolderViewItem* AlbumFolderView::findParentByFolder(PAlbum* album, bool& failed)
+{
+ AlbumFolderViewItem* parent =
+ (AlbumFolderViewItem*) album->parent()->extraData(this);
+ if (!parent)
+ {
+ failed = true;
+ return 0;
+ }
+
+ failed = false;
+ return parent;
+}
+
+AlbumFolderViewItem* AlbumFolderView::findParentByCollection(PAlbum* album, bool& failed)
+{
+ TQStringList collectionList = AlbumSettings::instance()->getAlbumCollectionNames();
+ TQString collection = album->collection();
+
+ if (collection.isEmpty() || !collectionList.contains(collection))
+ collection = i18n("Uncategorized Albums");
+
+ AlbumFolderViewItem* parent = 0;
+
+ for (TQValueList<AlbumFolderViewItem*>::iterator it=d->groupItems.begin();
+ it != d->groupItems.end(); ++it)
+ {
+ AlbumFolderViewItem* groupItem = *it;
+ if (groupItem->text(0) == collection)
+ {
+ parent = groupItem;
+ break;
+ }
+ }
+
+ // Need to create a new parent item
+ if (!parent)
+ {
+ parent = new AlbumFolderViewItem(firstChild(), collection, 0, 0);
+ d->groupItems.append(parent);
+ }
+
+ failed = false;
+ return parent;
+}
+
+AlbumFolderViewItem* AlbumFolderView::findParentByDate(PAlbum* album, bool& failed)
+{
+ TQDate date = album->date();
+
+ TQString timeString = TQString::number(date.year()) + ", " +
+ TDEGlobal::locale()->calendar()->monthName(date, false);
+
+ AlbumFolderViewItem* parent = 0;
+
+ for (TQValueList<AlbumFolderViewItem*>::iterator it=d->groupItems.begin();
+ it != d->groupItems.end(); ++it)
+ {
+ AlbumFolderViewItem* groupItem = *it;
+ if (groupItem->text(0) == timeString)
+ {
+ parent = groupItem;
+ break;
+ }
+ }
+
+ // Need to create a new parent item
+ if (!parent)
+ {
+ parent = new AlbumFolderViewItem(firstChild(), timeString,
+ date.year(), date.month());
+ d->groupItems.append(parent);
+ }
+
+ failed = false;
+ return parent;
+}
+
+void AlbumFolderView::resort()
+{
+ AlbumFolderViewItem* prevSelectedItem = dynamic_cast<AlbumFolderViewItem*>(selectedItem());
+ if (prevSelectedItem && prevSelectedItem->isGroupItem())
+ prevSelectedItem = 0;
+
+ AlbumList pList(d->albumMan->allPAlbums());
+ for (AlbumList::iterator it = pList.begin(); it != pList.end(); ++it)
+ {
+ PAlbum *album = (PAlbum*)(*it);
+ if (!album->isRoot() && album->extraData(this))
+ {
+ reparentItem(static_cast<AlbumFolderViewItem*>(album->extraData(this)));
+ }
+ }
+
+ // Clear any groupitems which have been left empty
+ clearEmptyGroupItems();
+
+ if (prevSelectedItem)
+ {
+ ensureItemVisible(prevSelectedItem);
+ setSelected(prevSelectedItem, true);
+ }
+}
+
+void AlbumFolderView::reparentItem(AlbumFolderViewItem* folderItem)
+{
+ if (!folderItem)
+ return;
+
+ PAlbum* album = folderItem->album();
+ if (!album || album->isRoot())
+ return;
+
+ AlbumFolderViewItem* oldParent = dynamic_cast<AlbumFolderViewItem*>(folderItem->parent());
+
+ bool failed;
+ AlbumFolderViewItem* newParent = findParent(album, failed);
+ if (failed)
+ return;
+
+ if (oldParent == newParent)
+ return;
+
+ if (oldParent)
+ oldParent->removeItem(folderItem);
+ else
+ removeItem(folderItem);
+
+ // insert into new parent
+ if (newParent)
+ newParent->insertItem(folderItem);
+ else
+ insertItem(folderItem);
+}
+
+void AlbumFolderView::clearEmptyGroupItems()
+{
+ TQValueList<AlbumFolderViewItem*> deleteItems;
+
+ for (TQValueList<AlbumFolderViewItem*>::iterator it=d->groupItems.begin();
+ it != d->groupItems.end(); ++it)
+ {
+ AlbumFolderViewItem* groupItem = *it;
+
+ if (!groupItem->firstChild())
+ {
+ deleteItems.append(groupItem);
+ }
+ }
+
+ for (TQValueList<AlbumFolderViewItem*>::iterator it=deleteItems.begin();
+ it != deleteItems.end(); ++it)
+ {
+ d->groupItems.remove(*it);
+ delete *it;
+ }
+}
+
+void AlbumFolderView::refresh()
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ AlbumFolderViewItem* item = dynamic_cast<AlbumFolderViewItem*>(*it);
+ if (item)
+ item->refresh();
+ ++it;
+ }
+}
+
+void AlbumFolderView::slotRefresh(const TQMap<int, int>& albumsStatMap)
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ AlbumFolderViewItem* item = dynamic_cast<AlbumFolderViewItem*>(*it);
+ if (item)
+ {
+ if (item->album())
+ {
+ int id = item->id();
+ TQMap<int, int>::const_iterator it2 = albumsStatMap.find(id);
+ if ( it2 != albumsStatMap.end() )
+ item->setCount(it2.data());
+ }
+ }
+ ++it;
+ }
+
+ refresh();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumfolderview.h b/src/digikam/albumfolderview.h
new file mode 100644
index 00000000..b02e57a9
--- /dev/null
+++ b/src/digikam/albumfolderview.h
@@ -0,0 +1,132 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-06
+ * Description : Albums folder view.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file albumfoldeview.h */
+
+#ifndef _ALBUMFOLDERVIEW_H_
+#define _ALBUMFOLDERVIEW_H_
+
+// KDE includes.
+
+#include <tdeio/job.h>
+
+// Local includes.
+
+#include "folderview.h"
+
+class TQPixmap;
+
+class KURL;
+
+namespace Digikam
+{
+
+class Album;
+class PAlbum;
+class AlbumFolderViewItem;
+class AlbumFolderViewPriv;
+
+class AlbumFolderView : public FolderView
+{
+ TQ_OBJECT
+
+public:
+
+ AlbumFolderView(TQWidget *parent);
+ ~AlbumFolderView();
+
+ void albumImportFolder();
+ void resort();
+
+ void albumNew();
+ void albumDelete();
+ void albumEdit();
+ void albumRename();
+
+ void setAlbumThumbnail(PAlbum *album);
+
+ void setCurrentAlbum(Album *album);
+ void refresh();
+
+signals:
+
+ void signalAlbumModified();
+ void signalTextFolderFilterMatch(bool);
+
+public slots:
+
+ void slotTextFolderFilterChanged(const TQString&);
+
+private slots:
+
+ void slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail);
+ void slotThumbnailLost(Album *album);
+ void slotReloadThumbnails();
+ void slotSelectionChanged();
+
+ void slotAlbumAdded(Album *);
+ void slotAlbumDeleted(Album *album);
+ void slotAlbumsCleared();
+ void slotAlbumIconChanged(Album* album);
+ void slotAlbumRenamed(Album *album);
+
+ void slotContextMenu(TQListViewItem*, const TQPoint&, int);
+
+ void slotDIOResult(TDEIO::Job* job);
+
+ void slotRefresh(const TQMap<int, int>&);
+
+protected:
+
+ void contentsDropEvent(TQDropEvent *e);
+ TQDragObject* dragObject();
+ bool acceptDrop(const TQDropEvent *e) const;
+
+ void selectItem(int id);
+
+private:
+
+ void albumNew(AlbumFolderViewItem *item);
+ void albumEdit(AlbumFolderViewItem *item);
+ void albumRename(AlbumFolderViewItem *item);
+ void albumDelete(AlbumFolderViewItem *item);
+
+ void addAlbumChildrenToList(KURL::List &list, Album *album);
+
+ AlbumFolderViewItem* findParent(PAlbum* album, bool& failed);
+ AlbumFolderViewItem* findParentByFolder(PAlbum* album, bool& failed);
+ AlbumFolderViewItem* findParentByCollection(PAlbum* album, bool& failed);
+ AlbumFolderViewItem* findParentByDate(PAlbum* album, bool& failed);
+
+ void reparentItem(AlbumFolderViewItem* folderItem);
+ void clearEmptyGroupItems();
+
+private:
+
+ AlbumFolderViewPriv *d;
+};
+
+} // namespace Digikam
+
+#endif // _ALBUMFOLDEVIEW_H_
diff --git a/src/digikam/albumhistory.cpp b/src/digikam/albumhistory.cpp
new file mode 100644
index 00000000..174c879e
--- /dev/null
+++ b/src/digikam/albumhistory.cpp
@@ -0,0 +1,337 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : albums history manager.
+ *
+ * Copyright (C) 2004 by Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albumhistory.h"
+#include "albumhistory.moc"
+
+namespace Digikam
+{
+
+/**
+ * Stores an album along with the sidebar view, where the album
+ * is selected
+ */
+class HistoryItem
+{
+public:
+
+ HistoryItem()
+ {
+ album = 0;
+ widget = 0;
+ };
+
+ HistoryItem(Album *a, TQWidget *w)
+ {
+ album = a;
+ widget = w;
+ };
+
+ bool operator==(const HistoryItem& item)
+ {
+ return (album == item.album) && (widget == item.widget);
+ }
+
+ Album *album;
+ TQWidget *widget;
+};
+
+AlbumHistory::AlbumHistory()
+{
+ m_backwardStack = new AlbumStack;
+ m_forwardStack = new AlbumStack;
+ m_moving = false;
+}
+
+AlbumHistory::~AlbumHistory()
+{
+ clearHistory();
+
+ delete m_backwardStack;
+ delete m_forwardStack;
+}
+
+void AlbumHistory::clearHistory()
+{
+ AlbumStack::iterator iter = m_backwardStack->begin();
+ AlbumStack::iterator end = m_backwardStack->end();
+ for(; iter != end; ++iter)
+ delete *iter;
+ m_backwardStack->clear();
+
+ iter = m_forwardStack->begin();
+ end = m_forwardStack->end();
+ for(; iter != end; ++iter)
+ delete *iter;
+ m_forwardStack->clear();
+
+ m_moving = false;
+}
+
+void AlbumHistory::addAlbum(Album *album, TQWidget *widget)
+{
+ if(!album || !widget || m_moving)
+ {
+ m_moving = false;
+ return;
+ }
+
+ HistoryItem *item = new HistoryItem(album, widget);
+
+ // Same album as before in the history
+ if(!m_backwardStack->isEmpty() &&
+ *m_backwardStack->last() == *item)
+ {
+ delete item;
+ return;
+ }
+
+ m_backwardStack->push_back(item);
+
+ // The forward stack has to be cleared, if backward stack was changed
+ if(!m_forwardStack->isEmpty())
+ {
+ AlbumStack::iterator iter = m_forwardStack->begin();
+ for(; iter != m_forwardStack->end(); ++iter)
+ {
+ delete *iter;
+ }
+ m_forwardStack->clear();
+ }
+}
+
+void AlbumHistory::deleteAlbum(Album *album)
+{
+ if(!album || m_backwardStack->isEmpty())
+ return;
+
+ // Search all HistoryItems, with album and delete them
+ AlbumStack::iterator iter = m_backwardStack->begin();
+ AlbumStack::iterator end = m_backwardStack->end();
+ while(iter != end)
+ {
+ if((*iter)->album == album)
+ {
+ delete *iter;
+ iter = m_backwardStack->remove(iter);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+ iter = m_forwardStack->begin();
+ end = m_forwardStack->end();
+ while(iter != end)
+ {
+ if((*iter)->album == album)
+ {
+ delete *iter;
+ iter = m_forwardStack->remove(iter);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+
+ if(m_backwardStack->isEmpty() && m_forwardStack->isEmpty())
+ return;
+
+ // If backwardStack is empty, then there is no current album.
+ // So make the first album of the forwardStack the current one.
+ if(m_backwardStack->isEmpty())
+ forward();
+
+ // After the album is deleted from the history it has to be ensured,
+ // that neigboring albums are different
+ AlbumStack::iterator lhs = m_backwardStack->begin();
+ AlbumStack::iterator rhs = lhs;
+ ++rhs;
+ while(rhs != m_backwardStack->end())
+ {
+ if(*lhs == *rhs)
+ {
+ rhs = m_backwardStack->remove(rhs);
+ }
+ else
+ {
+ ++lhs;
+ rhs = lhs;
+ ++rhs;
+ }
+ }
+
+ rhs = m_forwardStack->begin();
+ while(rhs != m_forwardStack->end())
+ {
+ if(*lhs == *rhs)
+ {
+ rhs = m_forwardStack->remove(rhs);
+ }
+ else
+ {
+ if(lhs == m_backwardStack->fromLast())
+ {
+ lhs = m_forwardStack->begin();
+ }
+ else
+ {
+ ++lhs;
+ rhs = lhs;
+ }
+ ++rhs;
+ }
+ }
+
+ if(m_backwardStack->isEmpty() && !m_forwardStack->isEmpty())
+ forward();
+}
+
+void AlbumHistory::getBackwardHistory(TQStringList &list) const
+{
+ if(m_backwardStack->isEmpty())
+ return;
+
+ AlbumStack::const_iterator iter = m_backwardStack->begin();
+ for(; iter != m_backwardStack->fromLast(); ++iter)
+ {
+ list.push_front((*iter)->album->title());
+ }
+}
+
+void AlbumHistory::getForwardHistory(TQStringList &list) const
+{
+ if(m_forwardStack->isEmpty())
+ return;
+
+ AlbumStack::const_iterator iter;
+ for(iter = m_forwardStack->begin(); iter != m_forwardStack->end(); ++iter)
+ {
+ list.append((*iter)->album->title());
+ }
+}
+
+void AlbumHistory::back(Album **album, TQWidget **widget, unsigned int steps)
+{
+ *album = 0;
+ *widget = 0;
+
+ if(m_backwardStack->count() <= 1 || steps > m_backwardStack->count())
+ return; // Only the current album available
+
+ while(steps)
+ {
+ m_forwardStack->push_front(m_backwardStack->last());
+ m_backwardStack->erase(m_backwardStack->fromLast());
+ --steps;
+ }
+ m_moving = true;
+
+ HistoryItem *item = getCurrentAlbum();
+ if(item)
+ {
+ *album = item->album;
+ *widget = item->widget;
+ }
+}
+
+void AlbumHistory::forward(Album **album, TQWidget **widget, unsigned int steps)
+{
+ *album = 0;
+ *widget = 0;
+
+ if(m_forwardStack->isEmpty() || steps > m_forwardStack->count())
+ return;
+
+ forward(steps);
+
+ HistoryItem *item = getCurrentAlbum();
+ if(item)
+ {
+ *album = item->album;
+ *widget = item->widget;
+ }
+}
+
+void AlbumHistory::forward(unsigned int steps)
+{
+ if(m_forwardStack->isEmpty() || steps > m_forwardStack->count())
+ return;
+
+ while(steps)
+ {
+ m_backwardStack->push_back(m_forwardStack->first());
+ m_forwardStack->erase(m_forwardStack->begin());
+ --steps;
+ }
+ m_moving = true;
+}
+
+HistoryItem* AlbumHistory::getCurrentAlbum() const
+{
+ if(m_backwardStack->isEmpty())
+ return 0;
+
+ return m_backwardStack->last();
+}
+
+void AlbumHistory::getCurrentAlbum(Album **album, TQWidget **widget) const
+{
+ *album = 0;
+ *widget = 0;
+
+ if(m_backwardStack->isEmpty())
+ return;
+
+ HistoryItem *item = m_backwardStack->last();
+ if(item)
+ {
+ *album = item->album;
+ *widget = item->widget;
+ }
+}
+
+bool AlbumHistory::isForwardEmpty() const
+{
+ return m_forwardStack->isEmpty();
+}
+
+bool AlbumHistory::isBackwardEmpty() const
+{
+ // the last album of the backwardStack is the currently shown
+ // album, and therfore not really a previous album
+ return (m_backwardStack->count() <= 1) ? true : false;
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/albumhistory.h b/src/digikam/albumhistory.h
new file mode 100644
index 00000000..585dad84
--- /dev/null
+++ b/src/digikam/albumhistory.h
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : Albums history manager.
+ *
+ * Copyright (C) 2004 by Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMHISTORY_H
+#define ALBUMHISTORY_H
+
+/** @file albumhistory.h */
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqvaluelist.h>
+#include <tqstringlist.h>
+
+namespace Digikam
+{
+
+class Album;
+class HistoryItem;
+
+/**
+ * Manages the history of the last visited albums.
+ *
+ * The user is able to navigate through the albums, he has
+ * opened during a session.
+ */
+class AlbumHistory : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ AlbumHistory();
+ ~AlbumHistory();
+
+ void addAlbum(Album *album, TQWidget *widget = 0);
+ void deleteAlbum(Album *album);
+ void clearHistory();
+ void back(Album **album, TQWidget **widget, unsigned int steps=1);
+ void forward(Album **album, TQWidget **widget, unsigned int steps=1);
+ void getCurrentAlbum(Album **album, TQWidget **widget) const;
+
+ void getBackwardHistory(TQStringList &list) const;
+ void getForwardHistory(TQStringList &list) const;
+
+ bool isForwardEmpty() const;
+ bool isBackwardEmpty() const;
+
+private:
+
+ HistoryItem* getCurrentAlbum() const;
+ void forward(unsigned int steps=1);
+
+ typedef TQValueList<HistoryItem*> AlbumStack;
+
+ AlbumStack *m_backwardStack;
+ AlbumStack *m_forwardStack;
+ bool m_moving;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMHISTORY_H */
diff --git a/src/digikam/albumicongroupitem.cpp b/src/digikam/albumicongroupitem.cpp
new file mode 100644
index 00000000..07562f8b
--- /dev/null
+++ b/src/digikam/albumicongroupitem.cpp
@@ -0,0 +1,168 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-25
+ * Description : implementation to render album icons group item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqpainter.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kcalendarsystem.h>
+
+// Local includes.
+
+#include "albummanager.h"
+#include "album.h"
+#include "themeengine.h"
+#include "albumsettings.h"
+#include "albumiconview.h"
+#include "albumicongroupitem.h"
+
+namespace Digikam
+{
+
+AlbumIconGroupItem::AlbumIconGroupItem(AlbumIconView* view, int albumID)
+ : IconGroupItem(view), m_albumID(albumID), m_view(view)
+{
+}
+
+AlbumIconGroupItem::~AlbumIconGroupItem()
+{
+}
+
+int AlbumIconGroupItem::compare(IconGroupItem* group)
+{
+ AlbumIconGroupItem* agroup = (AlbumIconGroupItem*)group;
+
+ PAlbum* mine = AlbumManager::instance()->findPAlbum(m_albumID);
+ PAlbum* his = AlbumManager::instance()->findPAlbum(agroup->m_albumID);
+
+ if (!mine || !his)
+ return 0;
+
+ const AlbumSettings *settings = m_view->settings();
+
+ switch (settings->getImageSortOrder())
+ {
+ case(AlbumSettings::ByIName):
+ case(AlbumSettings::ByISize):
+ case(AlbumSettings::ByIPath):
+ case(AlbumSettings::ByIRating):
+ {
+ return mine->url().localeAwareCompare(his->url());
+ }
+ case(AlbumSettings::ByIDate):
+ {
+ if (mine->date() < his->date())
+ return -1;
+ else if (mine->date() > his->date())
+ return 1;
+ else
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+void AlbumIconGroupItem::paintBanner()
+{
+ AlbumManager* man = AlbumManager::instance();
+ PAlbum* album = man->findPAlbum(m_albumID);
+
+ TQString dateAndComments;
+ TQString prettyURL;
+
+ if (album)
+ {
+ TQDate date = album->date();
+
+ dateAndComments = i18n("%1 %2 - 1 Item", "%1 %2 - %n Items", count())
+ .arg(TDEGlobal::locale()->calendar()->monthName(date, false))
+ .arg(TDEGlobal::locale()->calendar()->year(date));
+
+ if (!album->caption().isEmpty())
+ {
+ TQString caption = album->caption();
+ dateAndComments += " - " + caption.replace("\n", " ");
+ }
+
+ prettyURL = album->prettyURL();
+ }
+
+ TQRect r(0, 0, rect().width(), rect().height());
+
+ TQPixmap pix(m_view->bannerPixmap());
+
+ TQFont fn(m_view->font());
+ fn.setBold(true);
+ int fnSize = fn.pointSize();
+ bool usePointSize;
+ if (fnSize > 0)
+ {
+ fn.setPointSize(fnSize+2);
+ usePointSize = true;
+ }
+ else
+ {
+ fnSize = fn.pixelSize();
+ fn.setPixelSize(fnSize+2);
+ usePointSize = false;
+ }
+
+ TQPainter p(&pix);
+ p.setPen(ThemeEngine::instance()->textSelColor());
+ p.setFont(fn);
+
+ TQRect tr;
+ p.drawText(5, 5, r.width(), r.height(),
+ TQt::AlignLeft | TQt::AlignTop, prettyURL,
+ -1, &tr);
+
+ r.setY(tr.height() + 2);
+
+ if (usePointSize)
+ fn.setPointSize(m_view->font().pointSize());
+ else
+ fn.setPixelSize(m_view->font().pixelSize());
+
+ fn.setBold(false);
+ p.setFont(fn);
+
+ p.drawText(5, r.y(), r.width(), r.height(),
+ TQt::AlignLeft | TQt::AlignVCenter, dateAndComments);
+
+ p.end();
+
+ r = rect();
+ r = TQRect(iconView()->contentsToViewport(TQPoint(r.x(), r.y())),
+ TQSize(r.width(), r.height()));
+
+ bitBlt(iconView()->viewport(), r.x(), r.y(), &pix,
+ 0, 0, r.width(), r.height());
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumicongroupitem.h b/src/digikam/albumicongroupitem.h
new file mode 100644
index 00000000..ce2f5c7e
--- /dev/null
+++ b/src/digikam/albumicongroupitem.h
@@ -0,0 +1,59 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-25
+ * Description : implementation to render album icons group item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMICONGROUPITEM_H
+#define ALBUMICONGROUPITEM_H
+
+// Local includes.
+
+#include "icongroupitem.h"
+
+namespace Digikam
+{
+
+class AlbumIconView;
+
+class AlbumIconGroupItem : public IconGroupItem
+{
+public:
+
+ AlbumIconGroupItem(AlbumIconView* view, int albumID);
+ ~AlbumIconGroupItem();
+
+ int albumID() const { return m_albumID; }
+
+ virtual int compare(IconGroupItem* group);
+
+protected:
+
+ void paintBanner();
+
+private:
+
+ int m_albumID;
+ AlbumIconView* m_view;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMICONGROUPITEM_H */
diff --git a/src/digikam/albumiconitem.cpp b/src/digikam/albumiconitem.cpp
new file mode 100644
index 00000000..8c97f084
--- /dev/null
+++ b/src/digikam/albumiconitem.cpp
@@ -0,0 +1,375 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-04-25
+ * Description : implementation to render album icon item.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpalette.h>
+#include <tqstring.h>
+#include <tqpen.h>
+#include <tqfontmetrics.h>
+#include <tqfont.h>
+#include <tqdatetime.h>
+#include <tqstringlist.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <tdeio/global.h>
+
+// Local includes.
+
+#include "themeengine.h"
+#include "thumbnailsize.h"
+#include "imageinfo.h"
+#include "albumsettings.h"
+#include "icongroupitem.h"
+#include "pixmapmanager.h"
+#include "albumiconview.h"
+#include "albumiconitem.h"
+
+namespace Digikam
+{
+
+class AlbumIconItemPriv
+{
+public:
+
+ AlbumIconItemPriv()
+ {
+ dirty = true;
+ info = 0;
+ view = 0;
+ }
+
+ bool dirty;
+
+ TQRect tightPixmapRect;
+
+ ImageInfo *info;
+
+ AlbumIconView *view;
+};
+
+static void dateToString(const TQDateTime& datetime, TQString& str)
+{
+ str = TDEGlobal::locale()->formatDateTime(datetime, true, false);
+}
+
+AlbumIconItem::AlbumIconItem(IconGroupItem* parent, ImageInfo* info)
+ : IconItem(parent)
+{
+ d = new AlbumIconItemPriv;
+ d->view = (AlbumIconView*) parent->iconView();
+ d->info = info;
+}
+
+AlbumIconItem::~AlbumIconItem()
+{
+ delete d;
+}
+
+TQString AlbumIconItem::squeezedText(TQPainter* p, int width, const TQString& text)
+{
+ TQString fullText(text);
+ fullText.replace("\n"," ");
+ TQFontMetrics fm(p->fontMetrics());
+ int textWidth = fm.width(fullText);
+
+ if (textWidth > width)
+ {
+ // start with the dots only
+ TQString squeezedText = "...";
+ int squeezedWidth = fm.width(squeezedText);
+
+ // estimate how many letters we can add to the dots on both sides
+ int letters = fullText.length() * (width - squeezedWidth) / textWidth;
+ if (width < squeezedWidth) letters=1;
+ squeezedText = fullText.left(letters) + "...";
+ squeezedWidth = fm.width(squeezedText);
+
+ if (squeezedWidth < width)
+ {
+ // we estimated too short
+ // add letters while text < label
+ do
+ {
+ letters++;
+ squeezedText = fullText.left(letters) + "...";
+ squeezedWidth = fm.width(squeezedText);
+ }
+ while (squeezedWidth < width);
+
+ letters--;
+ squeezedText = fullText.left(letters) + "...";
+ }
+ else if (squeezedWidth > width)
+ {
+ // we estimated too long
+ // remove letters while text > label
+ do
+ {
+ letters--;
+ squeezedText = fullText.left(letters) + "...";
+ squeezedWidth = fm.width(squeezedText);
+ }
+ while (letters && squeezedWidth > width);
+ }
+
+ if (letters >= 5)
+ {
+ return squeezedText;
+ }
+ }
+
+ return fullText;
+}
+
+bool AlbumIconItem::isDirty()
+{
+ return d->dirty;
+}
+
+ImageInfo* AlbumIconItem::imageInfo() const
+{
+ return d->info;
+}
+
+int AlbumIconItem::compare(IconItem *item)
+{
+ const AlbumSettings *settings = d->view->settings();
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem*>(item);
+
+ switch (settings->getImageSortOrder())
+ {
+ case(AlbumSettings::ByIName):
+ {
+ return d->info->name().localeAwareCompare(iconItem->d->info->name());
+ }
+ case(AlbumSettings::ByIPath):
+ {
+ return d->info->kurl().path().compare(iconItem->d->info->kurl().path());
+ }
+ case(AlbumSettings::ByIDate):
+ {
+ if (d->info->dateTime() < iconItem->d->info->dateTime())
+ return -1;
+ else if (d->info->dateTime() > iconItem->d->info->dateTime())
+ return 1;
+ else
+ return 0;
+ }
+ case(AlbumSettings::ByISize):
+ {
+ int mysize(d->info->fileSize());
+ int hissize(iconItem->d->info->fileSize());
+ if (mysize < hissize)
+ return -1;
+ else if (mysize > hissize)
+ return 1;
+ else
+ return 0;
+ }
+ case(AlbumSettings::ByIRating):
+ {
+ int myrating(d->info->rating());
+ int hisrating(iconItem->d->info->rating());
+ if (myrating < hisrating)
+ return 1;
+ else if (myrating > hisrating)
+ return -1;
+ else
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+TQRect AlbumIconItem::clickToOpenRect()
+{
+ if (d->tightPixmapRect.isNull())
+ return rect();
+
+ TQRect pixmapRect = d->tightPixmapRect;
+ TQRect r = rect();
+
+ pixmapRect.moveBy(r.x(), r.y());
+ return pixmapRect;
+}
+
+void AlbumIconItem::paintItem()
+{
+ TQPixmap pix;
+ TQRect r;
+ const AlbumSettings *settings = d->view->settings();
+
+ if (isSelected())
+ pix = *(d->view->itemBaseSelPixmap());
+ else
+ pix = *(d->view->itemBaseRegPixmap());
+
+ ThemeEngine* te = ThemeEngine::instance();
+
+ TQPainter p(&pix);
+ p.setPen(isSelected() ? te->textSelColor() : te->textRegColor());
+
+
+ d->dirty = true;
+
+ TQPixmap *thumbnail = d->view->pixmapManager()->find(d->info->kurl());
+ if (thumbnail)
+ {
+ r = d->view->itemPixmapRect();
+ p.drawPixmap(r.x() + (r.width()-thumbnail->width())/2,
+ r.y() + (r.height()-thumbnail->height())/2,
+ *thumbnail);
+ d->tightPixmapRect.setRect(r.x() + (r.width()-thumbnail->width())/2,
+ r.y() + (r.height()-thumbnail->height())/2,
+ thumbnail->width(), thumbnail->height());
+ d->dirty = false;
+ }
+
+ if (settings->getIconShowRating())
+ {
+ r = d->view->itemRatingRect();
+ TQPixmap ratingPixmap = d->view->ratingPixmap();
+
+ int rating = d->info->rating();
+
+ int x, w;
+ x = r.x() + (r.width() - rating * ratingPixmap.width())/2;
+ w = rating * ratingPixmap.width();
+
+ p.drawTiledPixmap(x, r.y(), w, r.height(), ratingPixmap);
+ }
+
+ if (settings->getIconShowName())
+ {
+ r = d->view->itemNameRect();
+ p.setFont(d->view->itemFontReg());
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(),
+ d->info->name()));
+ }
+
+ p.setFont(d->view->itemFontCom());
+
+ if (settings->getIconShowComments())
+ {
+ TQString comments = d->info->caption();
+
+ r = d->view->itemCommentsRect();
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(), comments));
+ }
+
+ p.setFont(d->view->itemFontXtra());
+
+ if (settings->getIconShowDate())
+ {
+ TQDateTime date(d->info->dateTime());
+
+ r = d->view->itemDateRect();
+ p.setFont(d->view->itemFontXtra());
+ TQString str;
+ dateToString(date, str);
+ str = i18n("created : %1").arg(str);
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(), str));
+ }
+
+ if (settings->getIconShowModDate())
+ {
+ TQDateTime date(d->info->modDateTime());
+
+ r = d->view->itemModDateRect();
+ p.setFont(d->view->itemFontXtra());
+ TQString str;
+ dateToString(date, str);
+ str = i18n("modified : %1").arg(str);
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(), str));
+ }
+
+ if (settings->getIconShowResolution())
+ {
+ TQSize dims = d->info->dimensions();
+ if (dims.isValid())
+ {
+ TQString mpixels, resolution;
+ mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2);
+ resolution = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)")
+ .arg(dims.width()).arg(dims.height()).arg(mpixels);
+ r = d->view->itemResolutionRect();
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(), resolution));
+ }
+ }
+
+ if (settings->getIconShowSize())
+ {
+ r = d->view->itemSizeRect();
+ p.drawText(r, TQt::AlignCenter,
+ squeezedText(&p, r.width(),
+ TDEIO::convertSize(d->info->fileSize())));
+ }
+
+ p.setFont(d->view->itemFontCom());
+ p.setPen(isSelected() ? te->textSpecialSelColor() : te->textSpecialRegColor());
+
+ if (settings->getIconShowTags())
+ {
+ TQString tags = d->info->tagNames().join(", ");
+
+ r = d->view->itemTagRect();
+ p.drawText(r, TQt::AlignCenter,
+ squeezedText(&p, r.width(), tags));
+ }
+
+ if (this == d->view->currentItem())
+ {
+ p.setPen(TQPen(isSelected() ? te->textSelColor() : te->textRegColor(),
+ 0, TQt::DotLine));
+ p.drawRect(1, 1, pix.width()-2, pix.height()-2);
+ }
+
+ p.end();
+
+ r = rect();
+ r = TQRect(d->view->contentsToViewport(TQPoint(r.x(), r.y())),
+ TQSize(r.width(), r.height()));
+
+ bitBlt(d->view->viewport(), r.x(), r.y(), &pix,
+ 0, 0, r.width(), r.height());
+}
+
+TQRect AlbumIconItem::thumbnailRect() const
+{
+ TQRect pixmapRect = d->view->itemPixmapRect();
+ TQRect r = rect();
+
+ pixmapRect.moveBy(r.x(), r.y());
+ return pixmapRect;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumiconitem.h b/src/digikam/albumiconitem.h
new file mode 100644
index 00000000..1f743b60
--- /dev/null
+++ b/src/digikam/albumiconitem.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-04-25
+ * Description : implementation to render album icon item.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMICONITEM_H
+#define ALBUMICONITEM_H
+
+// TQt includes.
+
+#include <tqrect.h>
+
+// Local includes.
+
+#include "iconitem.h"
+
+class TQPainter;
+class TQString;
+
+namespace Digikam
+{
+
+class ImageInfo;
+class AlbumIconView;
+class AlbumIconItemPriv;
+
+class AlbumIconItem : public IconItem
+{
+
+public:
+
+ AlbumIconItem(IconGroupItem* parent, ImageInfo* info);
+ ~AlbumIconItem();
+
+ ImageInfo* imageInfo() const;
+
+ TQRect thumbnailRect() const;
+
+ bool isDirty();
+
+ static TQString squeezedText(TQPainter* p, int width, const TQString& text);
+
+ virtual int compare(IconItem *item);
+ virtual TQRect clickToOpenRect();
+
+protected:
+
+ virtual void paintItem();
+
+private:
+
+ AlbumIconItemPriv *d;
+};
+
+} // namespace Digikam
+
+#endif // ALBUMICONITEM_H
diff --git a/src/digikam/albumiconview.cpp b/src/digikam/albumiconview.cpp
new file mode 100644
index 00000000..88d6a3d1
--- /dev/null
+++ b/src/digikam/albumiconview.cpp
@@ -0,0 +1,2341 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2002-16-10
+ * Description : album icon view
+ *
+ * Copyright (C) 2002-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2002-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqimage.h>
+#include <tqstring.h>
+#include <tqstringlist.h>
+#include <tqevent.h>
+#include <tqpainter.h>
+#include <tqpoint.h>
+#include <tqpopupmenu.h>
+#include <tqdatetime.h>
+#include <tqfileinfo.h>
+#include <tqfile.h>
+#include <tqdragobject.h>
+#include <tqcursor.h>
+#include <tqvaluevector.h>
+#include <tqptrlist.h>
+#include <tqintdict.h>
+#include <tqdict.h>
+#include <tqdatastream.h>
+#include <tqtimer.h>
+#include <tqclipboard.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kurl.h>
+#include <kurldrag.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <tdemessagebox.h>
+#include <kiconloader.h>
+#include <kpropsdlg.h>
+#include <ktrader.h>
+#include <kservice.h>
+#include <krun.h>
+#include <tdeaction.h>
+#include <kstandarddirs.h>
+#include <kiconeffect.h>
+#include <kdebug.h>
+#include <tdeversion.h>
+
+#if KDE_IS_VERSION(3,2,0)
+#include <kcalendarsystem.h>
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// LibKipi includes.
+
+#include <libkipi/pluginloader.h>
+#include <libkipi/plugin.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "constants.h"
+#include "ddebug.h"
+#include "album.h"
+#include "albummanager.h"
+#include "dio.h"
+#include "albumlister.h"
+#include "albumfiletip.h"
+#include "albumsettings.h"
+#include "imagewindow.h"
+#include "thumbnailsize.h"
+#include "themeengine.h"
+#include "dpopupmenu.h"
+#include "tagspopupmenu.h"
+#include "ratingpopupmenu.h"
+#include "pixmapmanager.h"
+#include "cameraui.h"
+#include "dragobjects.h"
+#include "dmetadata.h"
+#include "albumdb.h"
+#include "imageattributeswatch.h"
+#include "deletedialog.h"
+#include "albumiconitem.h"
+#include "albumicongroupitem.h"
+#include "loadingcacheinterface.h"
+#include "lighttablewindow.h"
+#include "statusprogressbar.h"
+#include "metadatahub.h"
+#include "albumiconview.h"
+#include "albumiconview.moc"
+
+namespace Digikam
+{
+
+class AlbumIconViewPrivate
+{
+public:
+
+ void init()
+ {
+ imageLister = 0;
+ currentAlbum = 0;
+ albumSettings = 0;
+ pixMan = 0;
+ toolTip = 0;
+ }
+
+ TQString albumTitle;
+ TQString albumDate;
+ TQString albumComments;
+
+ TQRect itemRect;
+ TQRect itemRatingRect;
+ TQRect itemDateRect;
+ TQRect itemModDateRect;
+ TQRect itemPixmapRect;
+ TQRect itemNameRect;
+ TQRect itemCommentsRect;
+ TQRect itemResolutionRect;
+ TQRect itemSizeRect;
+ TQRect itemTagRect;
+ TQRect bannerRect;
+
+ TQPixmap itemRegPixmap;
+ TQPixmap itemSelPixmap;
+ TQPixmap bannerPixmap;
+ TQPixmap ratingPixmap;
+
+ TQFont fnReg;
+ TQFont fnCom;
+ TQFont fnXtra;
+
+ TQDict<AlbumIconItem> itemDict;
+
+ KURL itemUrlToFind;
+
+ AlbumLister *imageLister;
+ Album *currentAlbum;
+ const AlbumSettings *albumSettings;
+ TQIntDict<AlbumIconGroupItem> albumDict;
+ PixmapManager *pixMan;
+
+ ThumbnailSize thumbSize;
+
+ AlbumFileTip *toolTip;
+};
+
+AlbumIconView::AlbumIconView(TQWidget* parent)
+ : IconView(parent)
+{
+ d = new AlbumIconViewPrivate;
+ d->init();
+ d->imageLister = AlbumLister::instance();
+ d->pixMan = new PixmapManager(this);
+ d->toolTip = new AlbumFileTip(this);
+
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+
+ // -- Load rating Pixmap ------------------------------------------
+
+ TDEGlobal::dirs()->addResourceType("digikam_rating", TDEGlobal::dirs()->kde_default("data")
+ + "digikam/data");
+ TQString ratingPixPath = TDEGlobal::dirs()->findResourceDir("digikam_rating", "rating.png");
+ ratingPixPath += "/rating.png";
+ d->ratingPixmap = TQPixmap(ratingPixPath);
+
+ TQPainter painter(&d->ratingPixmap);
+ painter.fillRect(0, 0, d->ratingPixmap.width(), d->ratingPixmap.height(),
+ ThemeEngine::instance()->textSpecialRegColor());
+ painter.end();
+
+ // -- ImageLister connections -------------------------------------
+
+ connect(d->imageLister, TQ_SIGNAL(signalNewFilteredItems(const ImageInfoList&)),
+ this, TQ_SLOT(slotImageListerNewItems(const ImageInfoList&)));
+
+ connect(d->imageLister, TQ_SIGNAL(signalDeleteFilteredItem(ImageInfo*)),
+ this, TQ_SLOT(slotImageListerDeleteItem(ImageInfo*)) );
+
+ connect(d->imageLister, TQ_SIGNAL(signalClear()),
+ this, TQ_SLOT(slotImageListerClear()));
+
+ // -- Icon connections --------------------------------------------
+
+ connect(this, TQ_SIGNAL(signalDoubleClicked(IconItem*)),
+ this, TQ_SLOT(slotDoubleClicked(IconItem*)));
+
+ connect(this, TQ_SIGNAL(signalReturnPressed(IconItem*)),
+ this, TQ_SLOT(slotDoubleClicked(IconItem*)));
+
+ connect(this, TQ_SIGNAL(signalRightButtonClicked(IconItem*, const TQPoint &)),
+ this, TQ_SLOT(slotRightButtonClicked(IconItem*, const TQPoint &)));
+
+ connect(this, TQ_SIGNAL(signalRightButtonClicked(const TQPoint &)),
+ this, TQ_SLOT(slotRightButtonClicked(const TQPoint &)));
+
+ connect(this, TQ_SIGNAL(signalSelectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+
+ connect(this, TQ_SIGNAL(signalShowToolTip(IconItem*)),
+ this, TQ_SLOT(slotShowToolTip(IconItem*)));
+
+ // -- ThemeEngine connections ---------------------------------------
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ TQ_SLOT(slotThemeChanged()));
+
+ // -- Pixmap manager connections ------------------------------------
+
+ connect(d->pixMan, TQ_SIGNAL(signalPixmap(const KURL&)),
+ TQ_SLOT(slotGotThumbnail(const KURL&)));
+
+ // -- ImageAttributesWatch connections ------------------------------
+
+ ImageAttributesWatch *watch = ImageAttributesWatch::instance();
+
+ connect(watch, TQ_SIGNAL(signalImageTagsChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageAttributesChanged(TQ_LLONG)));
+
+ connect(watch, TQ_SIGNAL(signalImagesChanged(int)),
+ this, TQ_SLOT(slotAlbumImagesChanged(int)));
+
+ connect(watch, TQ_SIGNAL(signalImageRatingChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageAttributesChanged(TQ_LLONG)));
+
+ connect(watch, TQ_SIGNAL(signalImageDateChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageAttributesChanged(TQ_LLONG)));
+
+ connect(watch, TQ_SIGNAL(signalImageCaptionChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageAttributesChanged(TQ_LLONG)));
+}
+
+AlbumIconView::~AlbumIconView()
+{
+ delete d->pixMan;
+ delete d->toolTip;
+ delete d;
+}
+
+void AlbumIconView::applySettings(const AlbumSettings* settings)
+{
+ if (!settings)
+ return;
+
+ d->albumSettings = settings;
+
+ d->imageLister->setNamesFilter(d->albumSettings->getAllFileFilter());
+
+ d->thumbSize = (ThumbnailSize::Size)d->albumSettings->getDefaultIconSize();
+
+ setEnableToolTips(d->albumSettings->getShowToolTips());
+
+ updateBannerRectPixmap();
+ updateItemRectsPixmap();
+
+ d->imageLister->stop();
+ clear();
+
+ d->pixMan->setThumbnailSize(d->thumbSize.size());
+
+ if (d->currentAlbum)
+ {
+ d->imageLister->openAlbum(d->currentAlbum);
+ }
+}
+
+void AlbumIconView::setThumbnailSize(const ThumbnailSize& thumbSize)
+{
+ if ( d->thumbSize != thumbSize)
+ {
+ d->thumbSize = thumbSize;
+ d->pixMan->setThumbnailSize(d->thumbSize.size());
+
+ updateBannerRectPixmap();
+ updateItemRectsPixmap();
+
+ IconItem *currentIconItem = currentItem();
+ triggerRearrangement();
+ setStoredVisibleItem(currentIconItem);
+ }
+}
+
+void AlbumIconView::setAlbum(Album* album)
+{
+ if (!album)
+ {
+ d->currentAlbum = 0;
+ d->imageLister->stop();
+ clear();
+
+ return;
+ }
+
+ if (d->currentAlbum == album)
+ return;
+
+ d->imageLister->stop();
+ clear();
+
+ d->currentAlbum = album;
+ d->imageLister->openAlbum(d->currentAlbum);
+
+ updateBannerRectPixmap();
+ updateItemRectsPixmap();
+}
+
+void AlbumIconView::setAlbumItemToFind(const KURL& url)
+{
+ d->itemUrlToFind = url;
+}
+
+void AlbumIconView::refreshIcon(AlbumIconItem* item)
+{
+ if (!item)
+ return;
+
+ emit signalSelectionChanged();
+}
+
+void AlbumIconView::clear(bool update)
+{
+ emit signalCleared();
+
+ d->pixMan->clear();
+ d->itemDict.clear();
+ d->albumDict.clear();
+
+ IconView::clear(update);
+
+ emit signalSelectionChanged();
+}
+
+void AlbumIconView::slotImageListerNewItems(const ImageInfoList& itemList)
+{
+ if (!d->currentAlbum || d->currentAlbum->isRoot())
+ return;
+
+ ImageInfo* item;
+ for (ImageInfoListIterator it(itemList); (item = it.current()); ++it)
+ {
+ KURL url( item->kurl() );
+ url.cleanPath();
+
+ if (AlbumIconItem *oldItem = d->itemDict.find(url.url()))
+ {
+ slotImageListerDeleteItem(oldItem->imageInfo());
+ }
+
+ AlbumIconGroupItem* group = d->albumDict.find(item->albumID());
+ if (!group)
+ {
+ group = new AlbumIconGroupItem(this, item->albumID());
+ d->albumDict.insert(item->albumID(), group);
+ }
+
+ if (!item->album())
+ {
+ DWarning() << "No album for item: " << item->name()
+ << ", albumID: " << item->albumID() << endl;
+ continue;
+ }
+
+ AlbumIconItem* iconItem = new AlbumIconItem(group, item);
+ item->setViewItem(iconItem);
+
+ d->itemDict.insert(url.url(), iconItem);
+ }
+
+ // Make the icon, specified by d->itemUrlToFind, the current one
+ // in the album icon view and make it visible.
+ // This is for example used after a "Go To",
+ // e.g. from tags (or date) view to folder view.
+ // Note that AlbumIconView::slotImageListerNewItems may
+ // be called several times after another, because images get
+ // listed in packages of 200.
+ // Therefore the item might not always be available in the very
+ // first call when there are sufficiently many images.
+ // Also, because of this, we cannot reset the item which is to be found,
+ // i.e. something like d->itemUrlToFind = 0, after the item was found,
+ // as then the visibility of this item is lost in a subsequent call.
+ if (!d->itemUrlToFind.isEmpty())
+ {
+ AlbumIconItem* icon = findItem(d->itemUrlToFind.url());
+ if (icon)
+ {
+ clearSelection();
+ updateContents();
+ setCurrentItem(icon);
+ ensureItemVisible(icon);
+
+ // make the item really visible
+ // (the previous ensureItemVisible does not work)
+ setStoredVisibleItem(icon);
+ triggerRearrangement();
+ }
+ }
+
+ emit signalItemsAdded();
+}
+
+void AlbumIconView::slotImageListerDeleteItem(ImageInfo* item)
+{
+ if (!item->getViewItem())
+ return;
+
+ AlbumIconItem* iconItem = static_cast<AlbumIconItem*>(item->getViewItem());
+
+ KURL url(item->kurl());
+ url.cleanPath();
+
+ AlbumIconItem *oldItem = d->itemDict[url.url()];
+
+ if( oldItem &&
+ (oldItem->imageInfo()->id() != iconItem->imageInfo()->id()))
+ {
+ return;
+ }
+
+ //d->pixMan->remove(item->kurl());
+
+ emit signalItemDeleted(iconItem);
+
+ delete iconItem;
+ item->setViewItem(0);
+
+ d->itemDict.remove(url.url());
+
+ IconGroupItem* group = firstGroup();
+ IconGroupItem* tmp;
+
+ while (group)
+ {
+ tmp = group->nextGroup();
+
+ if (group->count() == 0)
+ {
+ d->albumDict.remove(((AlbumIconGroupItem*)group)->albumID());
+ delete group;
+ }
+
+ group = tmp;
+ }
+}
+
+void AlbumIconView::slotImageListerClear()
+{
+ clear();
+}
+
+void AlbumIconView::slotDoubleClicked(IconItem *item)
+{
+ if (!item) return;
+
+ if (d->albumSettings->getItemRightClickAction() == AlbumSettings::ShowPreview)
+ {
+ // icon effect takes too much time
+ //TDEIconEffect::visualActivate(viewport(), contentsRectToViewport(item->rect()));
+ signalPreviewItem(static_cast<AlbumIconItem *>(item));
+ }
+ else
+ {
+ TDEIconEffect::visualActivate(viewport(), contentsRectToViewport(item->rect()));
+ slotDisplayItem(static_cast<AlbumIconItem *>(item));
+ }
+}
+
+void AlbumIconView::slotRightButtonClicked(const TQPoint& pos)
+{
+ if (!d->currentAlbum)
+ return;
+
+ if (d->currentAlbum->isRoot() ||
+ ( d->currentAlbum->type() != Album::PHYSICAL
+ && d->currentAlbum->type() != Album::TAG))
+ {
+ return;
+ }
+
+ TQPopupMenu popmenu(this);
+ TDEAction *paste = KStdAction::paste(this, TQ_SLOT(slotPaste()), 0);
+ TQMimeSource *data = kapp->clipboard()->data(TQClipboard::Clipboard);
+
+ if(!data || !TQUriDrag::canDecode(data))
+ {
+ paste->setEnabled(false);
+ }
+
+ paste->plug(&popmenu);
+ popmenu.exec(pos);
+ delete paste;
+}
+
+void AlbumIconView::slotRightButtonClicked(IconItem *item, const TQPoint& pos)
+{
+ if (!item)
+ return;
+
+ AlbumIconItem* iconItem = static_cast<AlbumIconItem *>(item);
+
+ // --------------------------------------------------------
+
+ KMimeType::Ptr mimePtr = KMimeType::findByURL(iconItem->imageInfo()->kurl(), 0, true, true);
+
+ TQValueVector<KService::Ptr> serviceVector;
+ TDETrader::OfferList offers = TDETrader::self()->query(mimePtr->name(), "Type == 'Application'");
+
+ TQPopupMenu openWithMenu;
+
+ TDETrader::OfferList::Iterator iter;
+ KService::Ptr ptr;
+ int index = 100;
+
+ for( iter = offers.begin(); iter != offers.end(); ++iter )
+ {
+ ptr = *iter;
+ openWithMenu.insertItem( ptr->pixmap(TDEIcon::Small), ptr->name(), index++);
+ serviceVector.push_back(ptr);
+ }
+
+ // Obtain a list of all selected images.
+ // This is needed both for the goto tags submenu here and also
+ // for the "move to trash" and further actions below.
+ TQValueList<TQ_LLONG> selectedImageIDs;
+
+ for (IconItem *it = firstItem(); it; it=it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *selItem = static_cast<AlbumIconItem *>(it);
+ selectedImageIDs.append(selItem->imageInfo()->id());
+ }
+ }
+
+ // --------------------------------------------------------
+ // Provide Goto folder and/or date pop-up menu
+ TQPopupMenu gotoMenu;
+
+ gotoMenu.insertItem(SmallIcon("folder_image"), i18n("Album"), 20);
+ gotoMenu.insertItem(SmallIcon("date"), i18n("Date"), 21);
+
+ TagsPopupMenu* gotoTagsPopup = new TagsPopupMenu(selectedImageIDs, 1000, TagsPopupMenu::DISPLAY);
+ int gotoTagId = gotoMenu.insertItem(SmallIcon("tag"), i18n("Tag"), gotoTagsPopup);
+
+ // Disable the goto Tag popup menu, if there are no tags at all.
+ AlbumManager* man = AlbumManager::instance();
+ if (!man->albumDB()->hasTags(selectedImageIDs))
+ gotoMenu.setItemEnabled(gotoTagId, false);
+
+ connect(gotoTagsPopup, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotGotoTag(int)));
+
+ if (d->currentAlbum->type() == Album::PHYSICAL )
+ {
+ // If the currently selected album is the same as album to
+ // which the image belongs, then disable the "Go To" Album.
+ // (Note that in recursive album view these can be different).
+ if (iconItem->imageInfo()->albumID() == d->currentAlbum->id())
+ gotoMenu.setItemEnabled(20, false);
+ }
+ else if (d->currentAlbum->type() == Album::DATE )
+ {
+ gotoMenu.setItemEnabled(21, false);
+ }
+
+ // --------------------------------------------------------
+
+ DPopupMenu popmenu(this);
+ popmenu.insertItem(SmallIcon("viewimage"), i18n("View..."), 18);
+ popmenu.insertItem(SmallIcon("editimage"), i18n("Edit..."), 10);
+ popmenu.insertItem(SmallIcon("lighttableadd"), i18n("Add to Light Table"), 19);
+ // Note that the numbers 18, 10, 19 are used below in
+ // the switch(id) for popmenu.exec(pos);
+ // For the goto menu such a number is not needed,
+ // because only the above 20 and 21 of the goto popup are used,
+ // but it has to be provided.
+ popmenu.insertItem(SmallIcon("goto"), i18n("Go To"), &gotoMenu, 12);
+ // If there is more than one image selected, disable the goto menu entry.
+ if (selectedImageIDs.count() > 1)
+ {
+ popmenu.setItemEnabled(12, false);
+ }
+
+ popmenu.insertItem(i18n("Open With"), &openWithMenu, 11);
+
+ // Merge in the KIPI plugins actions ----------------------------
+
+ KIPI::PluginLoader* kipiPluginLoader = KIPI::PluginLoader::instance();
+ KIPI::PluginLoader::PluginList pluginList = kipiPluginLoader->pluginList();
+
+ for (KIPI::PluginLoader::PluginList::const_iterator it = pluginList.begin();
+ it != pluginList.end(); ++it)
+ {
+ KIPI::Plugin* plugin = (*it)->plugin();
+
+ if (plugin && (*it)->name() == "JPEGLossless")
+ {
+ DDebug() << "Found JPEGLossless plugin" << endl;
+
+ TDEActionPtrList actionList = plugin->actions();
+
+ for (TDEActionPtrList::const_iterator iter = actionList.begin();
+ iter != actionList.end(); ++iter)
+ {
+ TDEAction* action = *iter;
+
+ if (TQString::fromLatin1(action->name())
+ == TQString::fromLatin1("jpeglossless_rotate"))
+ {
+ action->plug(&popmenu);
+ }
+ }
+ }
+ }
+
+ // --------------------------------------------------------
+
+ popmenu.insertItem(SmallIcon("pencil"), i18n("Rename..."), 15);
+ popmenu.insertSeparator();
+
+ // --------------------------------------------------------
+
+ if (d->currentAlbum)
+ {
+ if (d->currentAlbum->type() == Album::PHYSICAL )
+ {
+ popmenu.insertItem(i18n("Set as Album Thumbnail"), 17);
+ popmenu.insertSeparator();
+ }
+ else if (d->currentAlbum->type() == Album::TAG )
+ {
+ popmenu.insertItem(i18n("Set as Tag Thumbnail"), 17);
+ popmenu.insertSeparator();
+ }
+ }
+
+ // --------------------------------------------------------
+
+ TDEAction *copy = KStdAction::copy(this, TQ_SLOT(slotCopy()), 0);
+ TDEAction *paste = KStdAction::paste(this, TQ_SLOT(slotPaste()), 0);
+ TQMimeSource *data = kapp->clipboard()->data(TQClipboard::Clipboard);
+ if(!data || !TQUriDrag::canDecode(data))
+ {
+ paste->setEnabled(false);
+ }
+ copy->plug(&popmenu);
+ paste->plug(&popmenu);
+
+ popmenu.insertSeparator();
+
+ // --------------------------------------------------------
+
+ popmenu.insertItem(SmallIcon("edittrash"),
+ i18n("Move to Trash", "Move %n Files to Trash" , selectedImageIDs.count() ), 16);
+
+ popmenu.insertSeparator();
+
+ // Bulk assignment/removal of tags --------------------------
+
+ TagsPopupMenu* assignTagsPopup = new TagsPopupMenu(selectedImageIDs, 1000, TagsPopupMenu::ASSIGN);
+ TagsPopupMenu* removeTagsPopup = new TagsPopupMenu(selectedImageIDs, 1000, TagsPopupMenu::REMOVE);
+
+ connect(assignTagsPopup, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotAssignTag(int)));
+
+ connect(removeTagsPopup, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotRemoveTag(int)));
+
+ popmenu.insertItem(i18n("Assign Tag"), assignTagsPopup);
+
+ int removeTagId = popmenu.insertItem(i18n("Remove Tag"), removeTagsPopup);
+
+ // Performance: Only check for tags if there are <250 images selected
+ // Also disable the remove Tag popup menu, if there are no tags at all.
+ if (selectedImageIDs.count() > 250 ||
+ !man->albumDB()->hasTags(selectedImageIDs))
+ popmenu.setItemEnabled(removeTagId, false);
+
+ popmenu.insertSeparator();
+
+ // Assign Star Rating -------------------------------------------
+
+ RatingPopupMenu ratingMenu;
+
+ connect(&ratingMenu, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotAssignRating(int)));
+
+ popmenu.insertItem(i18n("Assign Rating"), &ratingMenu);
+
+ // --------------------------------------------------------
+
+ int id = popmenu.exec(pos);
+
+ switch(id)
+ {
+ case 10:
+ {
+ slotDisplayItem(iconItem);
+ break;
+ }
+
+ case 15:
+ {
+ slotRename(iconItem);
+ break;
+ }
+
+ case 16:
+ {
+ slotDeleteSelectedItems();
+ break;
+ }
+
+ case 17:
+ {
+ slotSetAlbumThumbnail(iconItem);
+ break;
+ }
+
+ case 18:
+ {
+ signalPreviewItem(iconItem);
+ break;
+ }
+
+ case 19:
+ {
+ // add images to existing images in the light table
+ insertSelectionToLightTable(true);
+ break;
+ }
+
+ case 20: // goto album
+ {
+ // send a signal to the parent widget (digikamview.cpp)
+ emit signalGotoAlbumAndItem(iconItem);
+ break;
+ }
+
+ case 21: // goto date
+ {
+ // send a signal to the parent widget (digikamview.cpp)
+ emit signalGotoDateAndItem(iconItem);
+ break;
+ }
+
+
+ default:
+ break;
+ }
+
+ //---------------------------------------------------------------
+
+ if (id >= 100 && id < 1000)
+ {
+ KService::Ptr imageServicePtr = serviceVector[id-100];
+ KURL::List urlList;
+ for (IconItem *it = firstItem(); it; it=it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *selItem = static_cast<AlbumIconItem *>(it);
+ urlList.append(selItem->imageInfo()->kurl());
+ }
+ }
+ if (urlList.count())
+ KRun::run(*imageServicePtr, urlList);
+ }
+
+ serviceVector.clear();
+ delete assignTagsPopup;
+ delete removeTagsPopup;
+ delete copy;
+ delete paste;
+}
+
+void AlbumIconView::slotCopy()
+{
+ if (!d->currentAlbum)
+ return;
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ for (IconItem *it = firstItem(); it; it=it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *albumItem = static_cast<AlbumIconItem *>(it);
+ urls.append(albumItem->imageInfo()->kurl());
+ kioURLs.append(albumItem->imageInfo()->kurlForKIO());
+ imageIDs.append(albumItem->imageInfo()->id());
+ }
+ }
+ albumIDs.append(d->currentAlbum->id());
+
+ if (urls.isEmpty())
+ return;
+
+ TQDragObject* drag = 0;
+
+ drag = new ItemDrag(urls, kioURLs, albumIDs, imageIDs, this);
+ kapp->clipboard()->setData(drag);
+}
+
+void AlbumIconView::slotPaste()
+{
+ TQMimeSource *data = kapp->clipboard()->data(TQClipboard::Clipboard);
+ if(!data)
+ return;
+
+ Album *album = 0;
+
+ // Check if we working on grouped items view.
+ if (groupCount() > 1)
+ {
+ AlbumIconGroupItem *grp = dynamic_cast<AlbumIconGroupItem*>(findGroup(TQCursor::pos()));
+ if (grp)
+ {
+ if(d->currentAlbum->type() == Album::PHYSICAL)
+ album = dynamic_cast<Album*>(AlbumManager::instance()->findPAlbum(grp->albumID()));
+ else if(d->currentAlbum->type() == Album::TAG)
+ album = dynamic_cast<Album*>(AlbumManager::instance()->findTAlbum(grp->albumID()));
+ }
+ }
+ if (!album)
+ album = d->currentAlbum;
+
+ if(d->currentAlbum->type() == Album::PHYSICAL && TQUriDrag::canDecode(data))
+ {
+ PAlbum* palbum = (PAlbum*)album;
+
+ // B.K.O #119205: do not handle root album.
+ if (palbum->isRoot())
+ return;
+
+ KURL destURL(palbum->kurl());
+
+ KURL::List srcURLs;
+ KURLDrag::decode(data, srcURLs);
+
+ TDEIO::Job* job = DIO::copy(srcURLs, destURL);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ }
+ else if(d->currentAlbum->type() == Album::TAG && ItemDrag::canDecode(data))
+ {
+ TAlbum* talbum = (TAlbum*)album;
+
+ // B.K.O #119205: do not handle root album.
+ if (talbum->isRoot())
+ return;
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ if (!ItemDrag::decode(data, urls, kioURLs, albumIDs, imageIDs))
+ return;
+
+ if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
+ return;
+
+ TQPtrList<ImageInfo> list;
+ for (TQValueList<int>::const_iterator it = imageIDs.begin();
+ it != imageIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ list.append(info);
+ }
+
+ changeTagOnImageInfos(list, TQValueList<int>() << talbum->id(), true, true);
+ }
+}
+
+void AlbumIconView::slotSetAlbumThumbnail(AlbumIconItem *iconItem)
+{
+ if(!d->currentAlbum)
+ return;
+
+ if(d->currentAlbum->type() == Album::PHYSICAL)
+ {
+ PAlbum *album = static_cast<PAlbum*>(d->currentAlbum);
+
+ TQString err;
+ AlbumManager::instance()->updatePAlbumIcon( album,
+ iconItem->imageInfo()->id(),
+ err );
+ }
+ else if (d->currentAlbum->type() == Album::TAG)
+ {
+ TAlbum *album = static_cast<TAlbum*>(d->currentAlbum);
+
+ TQString err;
+ AlbumManager::instance()->updateTAlbumIcon( album,
+ TQString(),
+ iconItem->imageInfo()->id(),
+ err );
+ }
+}
+
+void AlbumIconView::slotRename(AlbumIconItem* item)
+{
+ if (!item)
+ return;
+
+ // Create a copy of the item. After entering the event loop
+ // in the dialog, we cannot be sure about the item's status.
+ ImageInfo renameInfo(*item->imageInfo());
+
+ TQFileInfo fi(item->imageInfo()->name());
+ TQString ext = TQString(".") + fi.extension(false);
+ TQString name = fi.fileName();
+ name.truncate(fi.fileName().length() - ext.length());
+
+ bool ok;
+
+#if KDE_IS_VERSION(3,2,0)
+ TQString newName = KInputDialog::getText(i18n("Rename Item (%1)").arg(fi.fileName()),
+ i18n("Enter new name (without extension):"),
+ name, &ok, this);
+#else
+ TQString newName = KLineEditDlg::getText(i18n("Rename Item (%1)").arg(fi.fileName()),
+ i18n("Enter new name (without extension):"),
+ name, &ok, this);
+#endif
+
+ if (!ok)
+ return;
+
+ KURL oldURL = renameInfo.kurlForKIO();
+ KURL newURL = oldURL;
+ newURL.setFileName(newName + ext);
+
+ TDEIO::CopyJob* job = DIO::rename(oldURL, newURL);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ connect(job, TQ_SIGNAL(copyingDone(TDEIO::Job *, const KURL &, const KURL &, bool, bool)),
+ this, TQ_SLOT(slotRenamed(TDEIO::Job*, const KURL &, const KURL&)));
+
+ // The AlbumManager KDirWatch will trigger a DIO::scan.
+ // When this is completed, DIO will call AlbumLister::instance()->refresh().
+ // Usually the AlbumLister will ignore changes to already listed items.
+ // So the renamed item need explicitly be invalidated.
+ d->imageLister->invalidateItem(&renameInfo);
+}
+
+void AlbumIconView::slotRenamed(TDEIO::Job*, const KURL &, const KURL&newURL)
+{
+ // reconstruct file path from digikamalbums:// URL
+ KURL fileURL;
+ fileURL.setPath(newURL.user());
+ fileURL.addPath(newURL.path());
+
+ // refresh thumbnail
+ d->pixMan->remove(fileURL);
+ // clean LoadingCache as well - be pragmatic, do it here.
+ LoadingCacheInterface::cleanFromCache(fileURL.path());
+}
+
+void AlbumIconView::slotDeleteSelectedItems(bool deletePermanently)
+{
+ KURL::List urlList;
+ KURL::List kioUrlList;
+
+ for (IconItem *it = firstItem(); it; it=it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
+ urlList.append(iconItem->imageInfo()->kurl());
+ kioUrlList.append(iconItem->imageInfo()->kurlForKIO());
+ }
+ }
+
+ if (urlList.count() <= 0)
+ return;
+
+ DeleteDialog dialog(this);
+
+ if (!dialog.confirmDeleteList(urlList,
+ DeleteDialogMode::Files,
+ deletePermanently ?
+ DeleteDialogMode::NoChoiceDeletePermanently :
+ DeleteDialogMode::NoChoiceTrash))
+ return;
+
+ bool useTrash = !dialog.shouldDelete();
+
+ // trash does not like non-local URLs, put is not implemented
+ TDEIO::Job* job = DIO::del(useTrash ? urlList : kioUrlList, useTrash);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+
+ // The AlbumManager KDirWatch will trigger a DIO::scan.
+ // When this is completed, DIO will call AlbumLister::instance()->refresh().
+}
+
+void AlbumIconView::slotDeleteSelectedItemsDirectly(bool useTrash)
+{
+ // This method deletes the selected items directly, without confirmation.
+ // It is not used in the default setup.
+
+ KURL::List kioUrlList;
+ KURL::List urlList;
+
+ for (IconItem *it = firstItem(); it; it=it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
+ kioUrlList.append(iconItem->imageInfo()->kurlForKIO());
+ urlList.append(iconItem->imageInfo()->kurl());
+ }
+ }
+
+ if (kioUrlList.count() <= 0)
+ return;
+
+ // trash does not like non-local URLs, put is not implemented
+ TDEIO::Job* job = DIO::del(useTrash ? urlList : kioUrlList , useTrash);
+
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+}
+
+void AlbumIconView::slotFilesModified()
+{
+ d->imageLister->refresh();
+}
+
+void AlbumIconView::slotFilesModified(const KURL& url)
+{
+ refreshItems(url);
+}
+
+void AlbumIconView::slotImageWindowURLChanged(const KURL &url)
+{
+ IconItem* item = findItem(url.url());
+ if (item)
+ setCurrentItem(item);
+}
+
+void AlbumIconView::slotDisplayItem(AlbumIconItem *item)
+{
+ if (!item) return;
+
+ AlbumSettings *settings = AlbumSettings::instance();
+
+ if (!settings) return;
+
+ TQString currentFileExtension = item->imageInfo()->name().section( '.', -1 );
+ TQString imagefilter = settings->getImageFileFilter().lower() +
+ settings->getImageFileFilter().upper();
+
+#if KDCRAW_VERSION < 0x000106
+ if (KDcrawIface::DcrawBinary::instance()->versionIsRight())
+ {
+ // add raw files only if dcraw is available
+ imagefilter += settings->getRawFileFilter().lower() +
+ settings->getRawFileFilter().upper();
+ }
+#else
+ // add raw files only if dcraw is available
+ imagefilter += settings->getRawFileFilter().lower() +
+ settings->getRawFileFilter().upper();
+#endif
+
+ // If the current item is not an image file.
+ if ( !imagefilter.contains(currentFileExtension) )
+ {
+ KMimeType::Ptr mimePtr = KMimeType::findByURL(item->imageInfo()->kurl(),
+ 0, true, true);
+ TDETrader::OfferList offers = TDETrader::self()->query(mimePtr->name(),
+ "Type == 'Application'");
+
+ if (offers.isEmpty())
+ return;
+
+ KService::Ptr ptr = offers.first();
+ // Run the dedicated app to show the item.
+ KRun::run(*ptr, item->imageInfo()->kurl());
+ return;
+ }
+
+ // Run Digikam ImageEditor with all image files in the current Album.
+
+ ImageInfoList imageInfoList;
+ ImageInfo *currentImageInfo = 0;
+
+ for (IconItem *it = firstItem() ; it ; it = it->nextItem())
+ {
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
+ TQString fileExtension = iconItem->imageInfo()->kurl().fileName().section( '.', -1 );
+
+ if ( imagefilter.find(fileExtension) != -1 )
+ {
+ ImageInfo *info = new ImageInfo(*iconItem->imageInfo());
+ info->setViewItem(0);
+ imageInfoList.append(info);
+ if (iconItem == item)
+ currentImageInfo = info;
+ }
+ }
+
+ ImageWindow *imview = ImageWindow::imagewindow();
+
+ imview->disconnect(this);
+
+ connect(imview, TQ_SIGNAL(signalFileAdded(const KURL&)),
+ this, TQ_SLOT(slotFilesModified()));
+
+ connect(imview, TQ_SIGNAL(signalFileModified(const KURL&)),
+ this, TQ_SLOT(slotFilesModified(const KURL&)));
+
+ connect(imview, TQ_SIGNAL(signalFileDeleted(const KURL&)),
+ this, TQ_SLOT(slotFilesModified()));
+
+ connect(imview, TQ_SIGNAL(signalURLChanged(const KURL&)),
+ this, TQ_SLOT(slotImageWindowURLChanged(const KURL &)));
+
+ imview->loadImageInfos(imageInfoList,
+ currentImageInfo,
+ d->currentAlbum ? i18n("Album \"%1\"").arg(d->currentAlbum->title()) : TQString(),
+ true);
+
+ if (imview->isHidden())
+ imview->show();
+
+ imview->raise();
+ imview->setFocus();
+}
+
+void AlbumIconView::insertSelectionToLightTable(bool addTo)
+{
+ // Run Light Table with all selected image files in the current Album.
+ // If addTo is false, the light table will be emptied before adding
+ // the images.
+ ImageInfoList imageInfoList;
+
+ for (IconItem *it = firstItem() ; it ; it = it->nextItem())
+ {
+ if ((*it).isSelected())
+ {
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
+ ImageInfo *info = new ImageInfo(*iconItem->imageInfo());
+ info->setViewItem(0);
+ imageInfoList.append(info);
+ }
+ }
+
+ insertToLightTable(imageInfoList, imageInfoList.first(), addTo);
+}
+
+void AlbumIconView::insertToLightTable(const ImageInfoList& list, ImageInfo* current, bool addTo)
+{
+ LightTableWindow *ltview = LightTableWindow::lightTableWindow();
+
+ ltview->disconnect(this);
+
+ connect(ltview, TQ_SIGNAL(signalFileDeleted(const KURL&)),
+ this, TQ_SLOT(slotFilesModified()));
+
+ connect(this, TQ_SIGNAL(signalItemsUpdated(const KURL::List&)),
+ ltview, TQ_SLOT(slotItemsUpdated(const KURL::List&)));
+
+ if (ltview->isHidden())
+ ltview->show();
+
+ ltview->raise();
+ ltview->setFocus();
+ // If addTo is false, the light table will be emptied before adding
+ // the images.
+ ltview->loadImageInfos(list, current, addTo);
+ ltview->setLeftRightItems(list, addTo);
+}
+
+// ------------------------------------------------------------------------------
+
+AlbumIconItem* AlbumIconView::firstSelectedItem() const
+{
+ AlbumIconItem *iconItem = 0;
+ for (IconItem *it = firstItem(); it; it = it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ iconItem = static_cast<AlbumIconItem *>(it);
+ break;
+ }
+ }
+
+ return iconItem;
+}
+
+const AlbumSettings* AlbumIconView::settings() const
+{
+ return d->albumSettings;
+}
+
+ThumbnailSize AlbumIconView::thumbnailSize() const
+{
+ return d->thumbSize;
+}
+
+void AlbumIconView::resizeEvent(TQResizeEvent *e)
+{
+ IconView::resizeEvent(e);
+
+ if (d->bannerRect.width() != frameRect().width())
+ updateBannerRectPixmap();
+}
+
+// -- DnD ---------------------------------------------------
+
+void AlbumIconView::startDrag()
+{
+ if (!d->currentAlbum)
+ return;
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ for (IconItem *it = firstItem(); it; it=it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *albumItem = static_cast<AlbumIconItem *>(it);
+ urls.append(albumItem->imageInfo()->kurl());
+ kioURLs.append(albumItem->imageInfo()->kurlForKIO());
+ imageIDs.append(albumItem->imageInfo()->id());
+ }
+ }
+ albumIDs.append(d->currentAlbum->id());
+
+ if (urls.isEmpty())
+ return;
+
+ TQPixmap icon(DesktopIcon("image-x-generic", 48));
+ int w = icon.width();
+ int h = icon.height();
+
+ TQPixmap pix(w+4,h+4);
+ TQString text(TQString::number(urls.count()));
+
+ TQPainter p(&pix);
+ p.fillRect(0, 0, w+4, h+4, TQColor(TQt::white));
+ p.setPen(TQPen(TQt::black, 1));
+ p.drawRect(0, 0, w+4, h+4);
+ p.drawPixmap(2, 2, icon);
+ TQRect r = p.boundingRect(2,2,w,h,TQt::AlignLeft|TQt::AlignTop,text);
+ r.setWidth(TQMAX(r.width(),r.height()));
+ r.setHeight(TQMAX(r.width(),r.height()));
+ p.fillRect(r, TQColor(0,80,0));
+ p.setPen(TQt::white);
+ TQFont f(font());
+ f.setBold(true);
+ p.setFont(f);
+ p.drawText(r, TQt::AlignCenter, text);
+ p.end();
+
+ TQDragObject* drag = 0;
+
+ drag = new ItemDrag(urls, kioURLs, albumIDs, imageIDs, this);
+ if (drag)
+ {
+ drag->setPixmap(pix);
+ drag->drag();
+ }
+}
+
+void AlbumIconView::contentsDragMoveEvent(TQDragMoveEvent *event)
+{
+ if (!d->currentAlbum || (AlbumDrag::canDecode(event) ||
+ !TQUriDrag::canDecode(event) &&
+ !CameraDragObject::canDecode(event) &&
+ !TagListDrag::canDecode(event) &&
+ !TagDrag::canDecode(event) &&
+ !CameraItemListDrag::canDecode(event) &&
+ !ItemDrag::canDecode(event)))
+ {
+ event->ignore();
+ return;
+ }
+ event->accept();
+}
+
+void AlbumIconView::contentsDropEvent(TQDropEvent *event)
+{
+ if (!d->currentAlbum || (AlbumDrag::canDecode(event) ||
+ !TQUriDrag::canDecode(event) &&
+ !CameraDragObject::canDecode(event) &&
+ !TagListDrag::canDecode(event) &&
+ !TagDrag::canDecode(event) &&
+ !CameraItemListDrag::canDecode(event) &&
+ !ItemDrag::canDecode(event)))
+ {
+ event->ignore();
+ return;
+ }
+
+ Album *album = 0;
+
+ // Check if we working on grouped items view.
+ if (groupCount() > 1)
+ {
+ AlbumIconGroupItem *grp = dynamic_cast<AlbumIconGroupItem*>(findGroup(TQCursor::pos()));
+ if (grp)
+ {
+ if(d->currentAlbum->type() == Album::PHYSICAL)
+ album = dynamic_cast<Album*>(AlbumManager::instance()->findPAlbum(grp->albumID()));
+ else if(d->currentAlbum->type() == Album::TAG)
+ album = dynamic_cast<Album*>(AlbumManager::instance()->findTAlbum(grp->albumID()));
+ }
+ }
+ if (!album)
+ album = d->currentAlbum;
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ if (ItemDrag::decode(event, urls, kioURLs, albumIDs, imageIDs))
+ {
+ // Drag & drop inside of digiKam
+
+ // Check if items dropped come from outside current album.
+ KURL::List extUrls;
+ ImageInfoList extImgInfList;
+ for (TQValueList<int>::iterator it = imageIDs.begin(); it != imageIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ if (info->albumID() != album->id())
+ {
+ extUrls.append(info->kurlForKIO());
+ extImgInfList.append(info);
+ }
+ }
+
+ if(extUrls.isEmpty())
+ {
+ event->ignore();
+ return;
+ }
+ else if (album->type() == Album::PHYSICAL)
+ {
+ PAlbum* palbum = (PAlbum*)album;
+ KURL destURL(palbum->kurl());
+
+ KURL::List srcURLs;
+ KURLDrag::decode(event, srcURLs);
+
+ TQPopupMenu popMenu(this);
+ popMenu.insertItem( SmallIcon("goto"), i18n("&Move Here"), 10 );
+ popMenu.insertItem( SmallIcon("edit-copy"), i18n("&Copy Here"), 11 );
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+ switch(id)
+ {
+ case 10:
+ {
+ TDEIO::Job* job = DIO::move(srcURLs, destURL);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ break;
+ }
+ case 11:
+ {
+ TDEIO::Job* job = DIO::copy(srcURLs, destURL);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ else if (TQUriDrag::canDecode(event) && album->type() == Album::PHYSICAL)
+ {
+ // Drag & drop outside of digiKam
+ PAlbum* palbum = (PAlbum*)album;
+ KURL destURL(palbum->kurl());
+
+ KURL::List srcURLs;
+ KURLDrag::decode(event, srcURLs);
+
+ TQPopupMenu popMenu(this);
+ popMenu.insertItem( SmallIcon("goto"), i18n("&Move Here"), 10 );
+ popMenu.insertItem( SmallIcon("edit-copy"), i18n("&Copy Here"), 11 );
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+ switch(id)
+ {
+ case 10:
+ {
+ TDEIO::Job* job = DIO::move(srcURLs, destURL);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ break;
+ }
+ case 11:
+ {
+ TDEIO::Job* job = DIO::copy(srcURLs, destURL);
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDIOResult(TDEIO::Job*)));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ else if(TagDrag::canDecode(event))
+ {
+ TQByteArray ba = event->encodedData("digikam/tag-id");
+ TQDataStream ds(ba, IO_ReadOnly);
+ int tagID;
+ ds >> tagID;
+
+ AlbumManager* man = AlbumManager::instance();
+ TAlbum* talbum = man->findTAlbum(tagID);
+
+ if (talbum)
+ {
+ TQPopupMenu popMenu(this);
+
+ bool moreItemsSelected = false;
+ bool itemDropped = false;
+
+ AlbumIconItem *albumItem = findItem(event->pos());
+ if (albumItem)
+ itemDropped = true;
+
+ for (IconItem *it = firstItem(); it; it = it->nextItem())
+ {
+ if (it->isSelected() && it != albumItem)
+ {
+ moreItemsSelected = true;
+ break;
+ }
+ }
+
+ if (moreItemsSelected)
+ popMenu.insertItem(SmallIcon("tag"),
+ i18n("Assign '%1' to &Selected Items").arg(talbum->tagPath().mid(1)), 10);
+
+ if (itemDropped)
+ popMenu.insertItem(SmallIcon("tag"),
+ i18n("Assign '%1' to &This Item").arg(talbum->tagPath().mid(1)), 12);
+
+ popMenu.insertItem(SmallIcon("tag"),
+ i18n("Assign '%1' to &All Items").arg(talbum->tagPath().mid(1)), 11);
+
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("&Cancel"));
+
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+ switch(id)
+ {
+ case 10: // Selected Items
+ {
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image tags. Please wait..."));
+
+ // always give a copy of the image infos (the "true"). Else there were crashes reported.
+ changeTagOnImageInfos(selectedImageInfos(true), TQValueList<int>() << tagID, true, true);
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ break;
+ }
+ case 11: // All Items
+ {
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image tags. Please wait..."));
+
+ changeTagOnImageInfos(allImageInfos(true), TQValueList<int>() << tagID, true, true);
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ break;
+ }
+ case 12: // Dropped Item only.
+ {
+ AlbumIconItem *albumItem = findItem(event->pos());
+ if (albumItem)
+ {
+ TQPtrList<ImageInfo> infos;
+ infos.append(albumItem->imageInfo());
+ changeTagOnImageInfos(infos, TQValueList<int>() << tagID, true, false);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ else if(TagListDrag::canDecode(event))
+ {
+ TQByteArray ba = event->encodedData("digikam/taglist");
+ TQDataStream ds(ba, IO_ReadOnly);
+ TQValueList<int> tagIDs;
+ ds >> tagIDs;
+
+ TQPopupMenu popMenu(this);
+
+ bool moreItemsSelected = false;
+ bool itemDropped = false;
+
+ AlbumIconItem *albumItem = findItem(event->pos());
+ if (albumItem)
+ itemDropped = true;
+
+ for (IconItem *it = firstItem(); it; it = it->nextItem())
+ {
+ if (it->isSelected() && it != albumItem)
+ {
+ moreItemsSelected = true;
+ break;
+ }
+ }
+
+ if (moreItemsSelected)
+ popMenu.insertItem(SmallIcon("tag"), i18n("Assign Tags to &Selected Items"), 10);
+
+ if (itemDropped)
+ popMenu.insertItem(SmallIcon("tag"), i18n("Assign Tags to &This Item"), 12);
+
+ popMenu.insertItem(SmallIcon("tag"), i18n("Assign Tags to &All Items"), 11);
+
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("&Cancel"));
+
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+ switch(id)
+ {
+ case 10: // Selected Items
+ {
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image tags. Please wait..."));
+
+ changeTagOnImageInfos(selectedImageInfos(true), tagIDs, true, true);
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ break;
+ }
+ case 11: // All Items
+ {
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image tags. Please wait..."));
+
+ changeTagOnImageInfos(allImageInfos(true), tagIDs, true, true);
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ break;
+ }
+ case 12: // Dropped item only.
+ {
+ AlbumIconItem *albumItem = findItem(event->pos());
+ if (albumItem)
+ {
+ TQPtrList<ImageInfo> infos;
+ infos.append(albumItem->imageInfo());
+ changeTagOnImageInfos(infos, tagIDs, true, false);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ else if(CameraItemListDrag::canDecode(event))
+ {
+ CameraUI *ui = dynamic_cast<CameraUI*>(event->source());
+ if (ui)
+ {
+ TQPopupMenu popMenu(this);
+ popMenu.insertItem(SmallIcon("go-down"), i18n("Download from camera"), 10);
+ popMenu.insertItem(SmallIcon("go-down"), i18n("Download && Delete from camera"), 11);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("&Cancel"));
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+ switch(id)
+ {
+ case 10: // Download from camera
+ {
+ ui->slotDownload(true, false, album);
+ break;
+ }
+ case 11: // Download and Delete from camera
+ {
+ ui->slotDownload(true, true, album);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ else
+ {
+ event->ignore();
+ }
+}
+
+void AlbumIconView::changeTagOnImageInfos(const TQPtrList<ImageInfo> &list, const TQValueList<int> &tagIDs, bool addOrRemove, bool progress)
+{
+ float cnt = list.count();
+ int i = 0;
+
+ d->imageLister->blockSignals(true);
+ AlbumManager::instance()->albumDB()->beginTransaction();
+ for (TQPtrList<ImageInfo>::const_iterator it = list.begin(); it != list.end(); ++it)
+ {
+ MetadataHub hub;
+
+ hub.load(*it);
+
+ for (TQValueList<int>::const_iterator tagIt = tagIDs.begin(); tagIt != tagIDs.end(); ++tagIt)
+ {
+ hub.setTag(*tagIt, addOrRemove);
+ }
+
+ hub.write(*it, MetadataHub::PartialWrite);
+ hub.write((*it)->filePath(), MetadataHub::FullWriteIfChanged);
+
+ if (progress)
+ {
+ emit signalProgressValue((int)((i++/cnt)*100.0));
+ kapp->processEvents();
+ }
+ }
+ d->imageLister->blockSignals(false);
+ AlbumManager::instance()->albumDB()->commitTransaction();
+
+ if (d->currentAlbum && d->currentAlbum->type() == Album::TAG)
+ {
+ d->imageLister->refresh();
+ }
+ updateContents();
+}
+
+bool AlbumIconView::acceptToolTip(IconItem *item, const TQPoint &mousePos)
+{
+ AlbumIconItem *iconItem = dynamic_cast<AlbumIconItem*>(item);
+
+ if (iconItem && iconItem->clickToOpenRect().contains(mousePos))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void AlbumIconView::slotShowToolTip(IconItem* item)
+{
+ d->toolTip->setIconItem(dynamic_cast<AlbumIconItem*>(item));
+}
+
+KURL::List AlbumIconView::allItems()
+{
+ KURL::List itemList;
+
+ for (IconItem *it = firstItem(); it; it = it->nextItem())
+ {
+ AlbumIconItem *item = (AlbumIconItem*) it;
+ itemList.append(item->imageInfo()->kurl());
+ }
+
+ return itemList;
+}
+
+KURL::List AlbumIconView::selectedItems()
+{
+ KURL::List itemList;
+
+ for (IconItem *it = firstItem(); it; it = it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *item = (AlbumIconItem*) it;
+ itemList.append(item->imageInfo()->kurl());
+ }
+ }
+
+ return itemList;
+}
+
+TQPtrList<ImageInfo> AlbumIconView::allImageInfos(bool copy) const
+{
+ // Returns the list of ImageInfos of all items,
+ // with the extra feature that the currentItem is the first in the list.
+ TQPtrList<ImageInfo> list;
+ for (IconItem *it = firstItem(); it; it = it->nextItem())
+ {
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
+ ImageInfo *info = iconItem->imageInfo();
+ if (copy)
+ info = new ImageInfo(*info);
+
+ if (iconItem == currentItem())
+ list.prepend(info);
+ else
+ list.append(info);
+ }
+ return list;
+}
+
+TQPtrList<ImageInfo> AlbumIconView::selectedImageInfos(bool copy) const
+{
+ // Returns the list of ImageInfos of currently selected items,
+ // with the extra feature that the currentItem is the first in the list.
+ TQPtrList<ImageInfo> list;
+ for (IconItem *it = firstItem(); it; it = it->nextItem())
+ {
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
+ if (it->isSelected())
+ {
+ ImageInfo *info = iconItem->imageInfo();
+ if (copy)
+ info = new ImageInfo(*info);
+
+ if (iconItem == currentItem())
+ list.prepend(info);
+ else
+ list.append(info);
+ }
+ }
+ return list;
+}
+
+void AlbumIconView::refresh()
+{
+ d->imageLister->stop();
+ clear();
+
+ d->imageLister->openAlbum(d->currentAlbum);
+}
+
+void AlbumIconView::refreshItems(const KURL::List& urlList)
+{
+ if (!d->currentAlbum || urlList.empty())
+ return;
+
+ // we do two things here:
+ // 1. refresh the imageinfo for the file
+ // 2. refresh the thumbnails
+
+ for (KURL::List::const_iterator it = urlList.begin();
+ it != urlList.end(); ++it)
+ {
+ AlbumIconItem* iconItem = findItem((*it).url());
+ if (!iconItem)
+ continue;
+
+ iconItem->imageInfo()->refresh();
+ d->pixMan->remove(iconItem->imageInfo()->kurl());
+ // clean LoadingCache as well - be pragmatic, do it here.
+ LoadingCacheInterface::cleanFromCache((*it).path());
+ }
+
+ emit signalItemsUpdated(urlList);
+
+ // trigger a delayed rearrangement, in case we need to resort items
+ triggerRearrangement();
+}
+
+void AlbumIconView::slotGotThumbnail(const KURL& url)
+{
+ AlbumIconItem* iconItem = findItem(url.url());
+ if (!iconItem)
+ return;
+
+ iconItem->repaint();
+}
+
+void AlbumIconView::slotSelectionChanged()
+{
+ if (firstSelectedItem())
+ emitItemsSelected(true);
+ else
+ emitItemsSelected(false);
+}
+
+void AlbumIconView::slotSetExifOrientation( int orientation )
+{
+ KURL::List urlList;
+ int i = 0;
+
+ for (IconItem *it = firstItem(); it; it=it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
+ urlList.append(iconItem->imageInfo()->kurl());
+ }
+ }
+
+ if (urlList.count() <= 0) return;
+
+ TQStringList faildItems;
+ KURL::List::Iterator it;
+ float cnt = (float)urlList.count();
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Revising Exif Orientation tags. Please wait..."));
+
+ for( it = urlList.begin(); it != urlList.end(); ++it )
+ {
+ DDebug() << "Setting Exif Orientation tag to " << orientation << endl;
+
+ DMetadata metadata((*it).path());
+ DMetadata::ImageOrientation o = (DMetadata::ImageOrientation)orientation;
+ metadata.setImageOrientation(o);
+
+ if (!metadata.applyChanges())
+ {
+ faildItems.append((*it).filename());
+ }
+ else
+ {
+ ImageAttributesWatch::instance()->fileMetadataChanged((*it));
+ }
+
+ emit signalProgressValue((int)((i++/cnt)*100.0));
+ kapp->processEvents();
+ }
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+
+ if (!faildItems.isEmpty())
+ {
+ if (faildItems.count() == 1)
+ {
+ KMessageBox::error(0, i18n("Failed to revise Exif orientation for file %1.")
+ .arg(faildItems[0]));
+
+ }
+ else
+ {
+ KMessageBox::errorList(0, i18n("Failed to revise Exif orientation these files:"),
+ faildItems);
+ }
+ }
+
+ refreshItems(urlList);
+}
+
+TQRect AlbumIconView::itemRect() const
+{
+ return d->itemRect;
+}
+
+TQRect AlbumIconView::itemRatingRect() const
+{
+ return d->itemRatingRect;
+}
+
+TQRect AlbumIconView::itemDateRect() const
+{
+ return d->itemDateRect;
+}
+
+TQRect AlbumIconView::itemModDateRect() const
+{
+ return d->itemModDateRect;
+}
+
+TQRect AlbumIconView::itemPixmapRect() const
+{
+ return d->itemPixmapRect;
+}
+
+TQRect AlbumIconView::itemNameRect() const
+{
+ return d->itemNameRect;
+}
+
+TQRect AlbumIconView::itemCommentsRect() const
+{
+ return d->itemCommentsRect;
+}
+
+TQRect AlbumIconView::itemResolutionRect() const
+{
+ return d->itemResolutionRect;
+}
+
+TQRect AlbumIconView::itemTagRect() const
+{
+ return d->itemTagRect;
+}
+
+TQRect AlbumIconView::itemSizeRect() const
+{
+ return d->itemSizeRect;
+}
+
+TQRect AlbumIconView::bannerRect() const
+{
+ return d->bannerRect;
+}
+
+TQPixmap* AlbumIconView::itemBaseRegPixmap() const
+{
+ return &d->itemRegPixmap;
+}
+
+TQPixmap* AlbumIconView::itemBaseSelPixmap() const
+{
+ return &d->itemSelPixmap;
+}
+
+TQPixmap AlbumIconView::bannerPixmap() const
+{
+ return d->bannerPixmap;
+}
+
+TQPixmap AlbumIconView::ratingPixmap() const
+{
+ return d->ratingPixmap;
+}
+
+TQFont AlbumIconView::itemFontReg() const
+{
+ return d->fnReg;
+}
+
+TQFont AlbumIconView::itemFontCom() const
+{
+ return d->fnCom;
+}
+
+TQFont AlbumIconView::itemFontXtra() const
+{
+ return d->fnXtra;
+}
+
+void AlbumIconView::updateBannerRectPixmap()
+{
+ d->bannerRect = TQRect(0, 0, 0, 0);
+
+ // Title --------------------------------------------------------
+ TQFont fn(font());
+ int fnSize = fn.pointSize();
+ bool usePointSize;
+ if (fnSize > 0)
+ {
+ fn.setPointSize(fnSize+2);
+ usePointSize = true;
+ }
+ else
+ {
+ fnSize = fn.pixelSize();
+ fn.setPixelSize(fnSize+2);
+ usePointSize = false;
+ }
+
+ fn.setBold(true);
+ TQFontMetrics fm(fn);
+ TQRect tr = fm.boundingRect(0, 0, frameRect().width(),
+ 0xFFFFFFFF, TQt::AlignLeft | TQt::AlignVCenter,
+ "XXX");
+ d->bannerRect.setHeight(tr.height());
+
+ if (usePointSize)
+ fn.setPointSize(font().pointSize());
+ else
+ fn.setPixelSize(font().pixelSize());
+
+ fn.setBold(false);
+ fm = TQFontMetrics(fn);
+
+ tr = fm.boundingRect(0, 0, frameRect().width(),
+ 0xFFFFFFFF, TQt::AlignLeft | TQt::AlignVCenter,
+ "XXX");
+
+ d->bannerRect.setHeight(d->bannerRect.height() + tr.height() + 10);
+ d->bannerRect.setWidth(frameRect().width());
+
+ d->bannerPixmap = ThemeEngine::instance()->bannerPixmap(d->bannerRect.width(),
+ d->bannerRect.height());
+}
+
+void AlbumIconView::updateItemRectsPixmap()
+{
+ d->itemRect = TQRect(0,0,0,0);
+ d->itemRatingRect = TQRect(0,0,0,0);
+ d->itemDateRect = TQRect(0,0,0,0);
+ d->itemModDateRect = TQRect(0,0,0,0);
+ d->itemPixmapRect = TQRect(0,0,0,0);
+ d->itemNameRect = TQRect(0,0,0,0);
+ d->itemCommentsRect = TQRect(0,0,0,0);
+ d->itemResolutionRect = TQRect(0,0,0,0);
+ d->itemSizeRect = TQRect(0,0,0,0);
+ d->itemTagRect = TQRect(0,0,0,0);
+
+ d->fnReg = font();
+ d->fnCom = font();
+ d->fnXtra = font();
+ d->fnCom.setItalic(true);
+
+ int fnSz = d->fnReg.pointSize();
+ if (fnSz > 0)
+ {
+ d->fnCom.setPointSize(fnSz-1);
+ d->fnXtra.setPointSize(fnSz-2);
+ }
+ else
+ {
+ fnSz = d->fnReg.pixelSize();
+ d->fnCom.setPixelSize(fnSz-1);
+ d->fnXtra.setPixelSize(fnSz-2);
+ }
+
+ int margin = 5;
+ int w = d->thumbSize.size() + 2*margin;
+
+ TQFontMetrics fm(d->fnReg);
+ TQRect oneRowRegRect = fm.boundingRect(0, 0, w, 0xFFFFFFFF,
+ TQt::AlignTop | TQt::AlignHCenter,
+ "XXXXXXXXX");
+ fm = TQFontMetrics(d->fnCom);
+ TQRect oneRowComRect = fm.boundingRect(0, 0, w, 0xFFFFFFFF,
+ TQt::AlignTop | TQt::AlignHCenter,
+ "XXXXXXXXX");
+ fm = TQFontMetrics(d->fnXtra);
+ TQRect oneRowXtraRect = fm.boundingRect(0, 0, w, 0xFFFFFFFF,
+ TQt::AlignTop | TQt::AlignHCenter,
+ "XXXXXXXXX");
+
+ int y = margin;
+
+ d->itemPixmapRect = TQRect(margin, y, w, d->thumbSize.size()+margin);
+ y = d->itemPixmapRect.bottom();
+
+ if (d->albumSettings->getIconShowRating())
+ {
+ d->itemRatingRect = TQRect(margin, y, w, d->ratingPixmap.height());
+ y = d->itemRatingRect.bottom();
+ }
+
+ if (d->albumSettings->getIconShowName())
+ {
+ d->itemNameRect = TQRect(margin, y, w, oneRowRegRect.height());
+ y = d->itemNameRect.bottom();
+ }
+
+ if (d->albumSettings->getIconShowComments())
+ {
+ d->itemCommentsRect = TQRect(margin, y, w, oneRowComRect.height());
+ y = d->itemCommentsRect.bottom();
+ }
+
+
+ if (d->albumSettings->getIconShowDate())
+ {
+ d->itemDateRect = TQRect(margin, y, w, oneRowXtraRect.height());
+ y = d->itemDateRect.bottom();
+ }
+
+ if (d->albumSettings->getIconShowModDate())
+ {
+ d->itemModDateRect = TQRect(margin, y, w, oneRowXtraRect.height());
+ y = d->itemModDateRect.bottom();
+ }
+
+ if (d->albumSettings->getIconShowResolution())
+ {
+ d->itemResolutionRect = TQRect(margin, y, w, oneRowXtraRect.height());
+ y = d->itemResolutionRect.bottom() ;
+ }
+
+ if (d->albumSettings->getIconShowSize())
+ {
+ d->itemSizeRect = TQRect(margin, y, w, oneRowXtraRect.height());
+ y = d->itemSizeRect.bottom();
+ }
+
+ if (d->albumSettings->getIconShowTags())
+ {
+ d->itemTagRect = TQRect(margin, y, w, oneRowComRect.height());
+ y = d->itemTagRect.bottom();
+ }
+
+ d->itemRect = TQRect(0, 0, w+2*margin, y+margin);
+
+ d->itemRegPixmap = ThemeEngine::instance()->thumbRegPixmap(d->itemRect.width(),
+ d->itemRect.height());
+
+ d->itemSelPixmap = ThemeEngine::instance()->thumbSelPixmap(d->itemRect.width(),
+ d->itemRect.height());
+}
+
+void AlbumIconView::slotThemeChanged()
+{
+ TQPainter painter(&d->ratingPixmap);
+ painter.fillRect(0, 0, d->ratingPixmap.width(), d->ratingPixmap.height(),
+ ThemeEngine::instance()->textSpecialRegColor());
+ painter.end();
+
+ updateBannerRectPixmap();
+ updateItemRectsPixmap();
+
+ viewport()->update();
+}
+
+AlbumIconItem* AlbumIconView::findItem(const TQPoint& pos)
+{
+ return dynamic_cast<AlbumIconItem*>(IconView::findItem(pos));
+}
+
+AlbumIconItem* AlbumIconView::findItem(const TQString& url) const
+{
+ return d->itemDict.find(url);
+}
+
+AlbumIconItem* AlbumIconView::nextItemToThumbnail() const
+{
+ TQRect r(contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ IconItem *fItem = findFirstVisibleItem(r);
+ IconItem *lItem = findLastVisibleItem(r);
+ if (!fItem || !lItem)
+ return 0;
+
+ AlbumIconItem* firstItem = static_cast<AlbumIconItem*>(fItem);
+ AlbumIconItem* lastItem = static_cast<AlbumIconItem*>(lItem);
+ AlbumIconItem* item = firstItem;
+ while (item)
+ {
+ if (item->isDirty())
+ return item;
+ if (item == lastItem)
+ break;
+ item = (AlbumIconItem*)item->nextItem();
+ }
+
+ return 0;
+}
+
+PixmapManager* AlbumIconView::pixmapManager() const
+{
+ return d->pixMan;
+}
+
+void AlbumIconView::slotAlbumModified()
+{
+ d->imageLister->stop();
+ clear();
+
+ d->imageLister->openAlbum(d->currentAlbum);
+
+ updateBannerRectPixmap();
+ updateItemRectsPixmap();
+}
+
+void AlbumIconView::slotGotoTag(int tagID)
+{
+ // send a signal to the parent widget (digikamview.cpp) to change
+ // to Tag view and the corresponding item
+
+ emit signalGotoTagAndItem(tagID);
+}
+
+void AlbumIconView::slotAssignTag(int tagID)
+{
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image tags. Please wait..."));
+
+ changeTagOnImageInfos(selectedImageInfos(true), TQValueList<int>() << tagID, true, true);
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+}
+
+void AlbumIconView::slotRemoveTag(int tagID)
+{
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Removing image tags. Please wait..."));
+
+ changeTagOnImageInfos(selectedImageInfos(true), TQValueList<int>() << tagID, false, true);
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+}
+
+void AlbumIconView::slotAssignRating(int rating)
+{
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image ratings. Please wait..."));
+
+ int i = 0;
+ float cnt = (float)countSelected();
+ rating = TQMIN(RatingMax, TQMAX(RatingMin, rating));
+
+ MetadataHub hub;
+ d->imageLister->blockSignals(true);
+ AlbumManager::instance()->albumDB()->beginTransaction();
+ for (IconItem *it = firstItem() ; it ; it = it->nextItem())
+ {
+ if (it->isSelected())
+ {
+ AlbumIconItem *albumItem = dynamic_cast<AlbumIconItem*>(it);
+ if (albumItem)
+ {
+ ImageInfo* info = albumItem->imageInfo();
+
+ hub.load(info);
+ hub.setRating(rating);
+ hub.write(info, MetadataHub::PartialWrite);
+ hub.write(info->filePath(), MetadataHub::FullWriteIfChanged);
+
+ emit signalProgressValue((int)((i++/cnt)*100.0));
+ kapp->processEvents();
+ }
+ }
+ }
+ d->imageLister->blockSignals(false);
+ AlbumManager::instance()->albumDB()->commitTransaction();
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ updateContents();
+}
+
+void AlbumIconView::slotAssignRatingNoStar()
+{
+ slotAssignRating(0);
+}
+
+void AlbumIconView::slotAssignRatingOneStar()
+{
+ slotAssignRating(1);
+}
+
+void AlbumIconView::slotAssignRatingTwoStar()
+{
+ slotAssignRating(2);
+}
+
+void AlbumIconView::slotAssignRatingThreeStar()
+{
+ slotAssignRating(3);
+}
+
+void AlbumIconView::slotAssignRatingFourStar()
+{
+ slotAssignRating(4);
+}
+
+void AlbumIconView::slotAssignRatingFiveStar()
+{
+ slotAssignRating(5);
+}
+
+void AlbumIconView::slotDIOResult(TDEIO::Job* job)
+{
+ if (job->error())
+ job->showErrorDialog(this);
+}
+
+void AlbumIconView::slotImageAttributesChanged(TQ_LLONG imageId)
+{
+ AlbumIconItem *firstItem = static_cast<AlbumIconItem *>(findFirstVisibleItem());
+ AlbumIconItem *lastItem = static_cast<AlbumIconItem *>(findLastVisibleItem());
+ for (AlbumIconItem *item = firstItem; item;
+ item = static_cast<AlbumIconItem *>(item->nextItem()))
+ {
+ if (item->imageInfo()->id() == imageId)
+ {
+ updateContents();
+ return;
+ }
+ if (item == lastItem)
+ break;
+ }
+}
+
+void AlbumIconView::slotAlbumImagesChanged(int /*albumId*/)
+{
+ updateContents();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumiconview.h b/src/digikam/albumiconview.h
new file mode 100644
index 00000000..0239a390
--- /dev/null
+++ b/src/digikam/albumiconview.h
@@ -0,0 +1,217 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2002-16-10
+ * Description : album icon view
+ *
+ * Copyright (C) 2002-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2002-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMICONVIEW_H
+#define ALBUMICONVIEW_H
+
+// KDE includes.
+
+#include <tqrect.h>
+#include <tqfont.h>
+
+// Local includes.
+
+#include "iconview.h"
+#include "imageinfo.h"
+#include "albumitemhandler.h"
+
+class TQResizeEvent;
+class TQDragMoveEvent;
+class TQDropEvent;
+class TQPoint;
+class TQString;
+class TQPixmap;
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace Digikam
+{
+
+class AlbumIconItem;
+class AlbumSettings;
+class ThumbnailSize;
+class Album;
+class PixmapManager;
+class AlbumIconViewPrivate;
+
+class AlbumIconView : public IconView,
+ public AlbumItemHandler
+{
+ TQ_OBJECT
+
+public:
+
+ AlbumIconView(TQWidget* parent);
+ ~AlbumIconView();
+
+ void setAlbum(Album* album);
+
+ /** set the Url of item to select in Album View when all items will be reloaded
+ by setAlbum()*/
+ void setAlbumItemToFind(const KURL& url);
+
+ void setThumbnailSize(const ThumbnailSize& thumbSize);
+ ThumbnailSize thumbnailSize() const;
+
+ void applySettings(const AlbumSettings* settings);
+ const AlbumSettings* settings() const;
+
+ void refreshIcon(AlbumIconItem* item);
+
+ AlbumIconItem* firstSelectedItem() const;
+
+ KURL::List allItems();
+ KURL::List selectedItems();
+
+ TQPtrList<ImageInfo> allImageInfos(bool copy) const;
+ TQPtrList<ImageInfo> selectedImageInfos(bool copy) const;
+
+ void refresh();
+ void refreshItems(const KURL::List& itemList);
+
+ TQRect itemRect() const;
+ TQRect itemRatingRect() const;
+ TQRect itemDateRect() const;
+ TQRect itemModDateRect() const;
+ TQRect itemPixmapRect() const;
+ TQRect itemNameRect() const;
+ TQRect itemCommentsRect() const;
+ TQRect itemResolutionRect() const;
+ TQRect itemSizeRect() const;
+ TQRect itemTagRect() const;
+ TQRect bannerRect() const;
+
+ TQPixmap* itemBaseRegPixmap() const;
+ TQPixmap* itemBaseSelPixmap() const;
+ TQPixmap bannerPixmap() const;
+ TQPixmap ratingPixmap() const;
+
+ TQFont itemFontReg() const;
+ TQFont itemFontCom() const;
+ TQFont itemFontXtra() const;
+
+ void clear(bool update=true);
+
+ AlbumIconItem* findItem(const TQPoint& pos);
+ AlbumIconItem* findItem(const TQString& url) const;
+ AlbumIconItem* nextItemToThumbnail() const;
+ PixmapManager* pixmapManager() const;
+
+ void insertSelectionToLightTable(bool addTo=false);
+ void insertToLightTable(const ImageInfoList& list, ImageInfo* current, bool addTo=false);
+
+signals:
+
+ void signalPreviewItem(AlbumIconItem*);
+ void signalItemsAdded();
+ void signalItemDeleted(AlbumIconItem*);
+ void signalCleared();
+ void signalProgressBarMode(int, const TQString&);
+ void signalProgressValue(int);
+ void signalItemsUpdated(const KURL::List&);
+ void signalGotoAlbumAndItem(AlbumIconItem *);
+ void signalGotoDateAndItem(AlbumIconItem *);
+ void signalGotoTagAndItem(int);
+
+public slots:
+
+ void slotSetExifOrientation(int orientation);
+ void slotRename(AlbumIconItem* item);
+ void slotDeleteSelectedItems(bool deletePermanently = false);
+ void slotDeleteSelectedItemsDirectly(bool useTrash);
+ void slotDisplayItem(AlbumIconItem *item=0);
+ void slotAlbumModified();
+ void slotSetAlbumThumbnail(AlbumIconItem *iconItem);
+ void slotCopy();
+ void slotPaste();
+
+ void slotAssignRating(int rating);
+ void slotAssignRatingNoStar();
+ void slotAssignRatingOneStar();
+ void slotAssignRatingTwoStar();
+ void slotAssignRatingThreeStar();
+ void slotAssignRatingFourStar();
+ void slotAssignRatingFiveStar();
+
+protected:
+
+ void resizeEvent(TQResizeEvent* e);
+
+ // DnD
+ void startDrag();
+ void contentsDragMoveEvent(TQDragMoveEvent *e);
+ void contentsDropEvent(TQDropEvent *e);
+
+ bool acceptToolTip(IconItem *item, const TQPoint &mousePos);
+
+private slots:
+
+ void slotImageListerNewItems(const ImageInfoList& itemList);
+ void slotImageListerDeleteItem(ImageInfo* item);
+ void slotImageListerClear();
+
+ void slotDoubleClicked(IconItem *item);
+ void slotRightButtonClicked(const TQPoint& pos);
+ void slotRightButtonClicked(IconItem *item, const TQPoint& pos);
+
+ void slotGotThumbnail(const KURL& url);
+ void slotSelectionChanged();
+
+ void slotFilesModified();
+ void slotFilesModified(const KURL& url);
+ void slotImageWindowURLChanged(const KURL &url);
+
+ void slotShowToolTip(IconItem* item);
+
+ void slotThemeChanged();
+
+ void slotGotoTag(int tagID);
+
+ void slotAssignTag(int tagID);
+ void slotRemoveTag(int tagID);
+
+ void slotDIOResult(TDEIO::Job* job);
+ void slotRenamed(TDEIO::Job*, const KURL &, const KURL&);
+
+ void slotImageAttributesChanged(TQ_LLONG imageId);
+ void slotAlbumImagesChanged(int albumId);
+
+private:
+
+ void updateBannerRectPixmap();
+ void updateItemRectsPixmap();
+ void changeTagOnImageInfos(const TQPtrList<ImageInfo> &list, const TQValueList<int> &tagIDs, bool addOrRemove, bool progress);
+
+private:
+
+ AlbumIconViewPrivate *d;
+};
+
+} // namespace Digikam
+
+#endif // ALBUMICONVIEW_H
diff --git a/src/digikam/albumiconviewfilter.cpp b/src/digikam/albumiconviewfilter.cpp
new file mode 100644
index 00000000..85a7c5fd
--- /dev/null
+++ b/src/digikam/albumiconviewfilter.cpp
@@ -0,0 +1,208 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-11-27
+ * Description : a bar to filter album contents
+ *
+ * Copyright (C) 2007-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "statusled.h"
+#include "albumsettings.h"
+#include "searchtextbar.h"
+#include "ratingfilter.h"
+#include "mimefilter.h"
+#include "albumiconviewfilter.h"
+#include "albumiconviewfilter.moc"
+
+namespace Digikam
+{
+
+class AlbumIconViewFilterPriv
+{
+public:
+
+ AlbumIconViewFilterPriv()
+ {
+ textFilter = 0;
+ mimeFilter = 0;
+ ratingFilter = 0;
+ led = 0;
+ }
+
+ StatusLed *led;
+
+ SearchTextBar *textFilter;
+
+ MimeFilter *mimeFilter;
+
+ RatingFilter *ratingFilter;
+};
+
+AlbumIconViewFilter::AlbumIconViewFilter(TQWidget* parent)
+ : TQHBox(parent)
+{
+ d = new AlbumIconViewFilterPriv;
+
+ d->led = new StatusLed(this);
+ d->led->installEventFilter(this);
+ d->led->setLedColor(StatusLed::Gray);
+ TQWhatsThis::add(d->led, i18n("This LED indicates the global image filter status, "
+ "encompassing all status-bar filters and all tag filters from the right sidebar.\n\n"
+ "GRAY: no filter is active, all items are visible.\n"
+ "RED: filtering is on, but no items match.\n"
+ "GREEN: filter(s) matches at least one item.\n\n"
+ "Any mouse button click will reset all filters."));
+
+ d->textFilter = new SearchTextBar(this, "AlbumIconViewFilterTextFilter");
+ d->textFilter->setEnableTextQueryCompletion(true);
+ TQToolTip::add(d->textFilter, i18n("Text quick filter (search)"));
+ TQWhatsThis::add(d->textFilter, i18n("Enter search patterns to quickly filter this view on file names, "
+ "captions (comments), and tags"));
+
+ d->mimeFilter = new MimeFilter(this);
+ d->ratingFilter = new RatingFilter(this);
+
+ setSpacing(KDialog::spacingHint());
+ setMargin(0);
+
+ connect(d->ratingFilter, TQ_SIGNAL(signalRatingFilterChanged(int, AlbumLister::RatingCondition)),
+ this, TQ_SLOT(slotRatingFilterChanged(int, AlbumLister::RatingCondition)));
+
+ connect(d->mimeFilter, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotMimeTypeFilterChanged(int)));
+
+ connect(d->textFilter, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ this, TQ_SLOT(slotTextFilterChanged(const TQString&)));
+
+ connect(AlbumLister::instance(), TQ_SIGNAL(signalItemsTextFilterMatch(bool)),
+ d->textFilter, TQ_SLOT(slotSearchResult(bool)));
+
+ connect(AlbumLister::instance(), TQ_SIGNAL(signalItemsFilterMatch(bool)),
+ this, TQ_SLOT(slotItemsFilterMatch(bool)));
+}
+
+AlbumIconViewFilter::~AlbumIconViewFilter()
+{
+ delete d;
+}
+
+void AlbumIconViewFilter::readSettings()
+{
+ AlbumSettings *settings = AlbumSettings::instance();
+ d->ratingFilter->setRatingFilterCondition((Digikam::AlbumLister::RatingCondition)
+ (settings->getRatingFilterCond()));
+ /*
+ Bug 181705: always enable filters
+ d->ratingFilter->setEnabled(settings->getIconShowRating());
+ d->textFilter->setEnabled(settings->getIconShowName() ||
+ settings->getIconShowComments() ||
+ settings->getIconShowTags());
+ */
+}
+
+void AlbumIconViewFilter::saveSettings()
+{
+ AlbumSettings::instance()->setRatingFilterCond(d->ratingFilter->ratingFilterCondition());
+}
+
+void AlbumIconViewFilter::slotRatingFilterChanged(int rating, AlbumLister::RatingCondition cond)
+{
+ AlbumLister::instance()->setRatingFilter(rating, cond);
+}
+
+void AlbumIconViewFilter::slotMimeTypeFilterChanged(int mimeTypeFilter)
+{
+ AlbumLister::instance()->setMimeTypeFilter(mimeTypeFilter);
+}
+
+void AlbumIconViewFilter::slotTextFilterChanged(const TQString& text)
+{
+ AlbumLister::instance()->setTextFilter(text);
+}
+
+void AlbumIconViewFilter::slotItemsFilterMatch(bool match)
+{
+ TQStringList filtersList;
+ TQString message;
+
+ if (!d->textFilter->text().isEmpty())
+ filtersList.append(i18n("<br><nobr><i>Text</i></nobr>"));
+
+ if (d->mimeFilter->mimeFilter() != MimeFilter::AllFiles)
+ filtersList.append(i18n("<br><nobr><i>Mime Type</i></nobr>"));
+
+ if (d->ratingFilter->rating() != 0 || d->ratingFilter->ratingFilterCondition() != AlbumLister::GreaterEqualCondition)
+ filtersList.append(i18n("<br/><nobr><i>Rating</i></nobr>"));
+
+ if (AlbumLister::instance()->tagFiltersIsActive())
+ filtersList.append(i18n("<br><nobr><i>Tags</i></nobr>"));
+
+ if (filtersList.count() > 1)
+ message = i18n("<nobr><b>Active filters:</b></nobr>");
+ else
+ message = i18n("<nobr><b>Active filter:</b></nobr>");
+
+ message.append(filtersList.join(TQString()));
+
+ if (filtersList.isEmpty())
+ {
+ TQToolTip::add(d->led, i18n("No active filter"));
+ d->led->setLedColor(StatusLed::Gray);
+ }
+ else
+ {
+ TQToolTip::add(d->led, message);
+ d->led->setLedColor(match ? StatusLed::Green : StatusLed::Red);
+ }
+}
+
+bool AlbumIconViewFilter::eventFilter(TQObject *object, TQEvent *e)
+{
+ TQWidget *widget = static_cast<TQWidget*>(object);
+
+ if (e->type() == TQEvent::MouseButtonRelease)
+ {
+ TQMouseEvent* event = static_cast<TQMouseEvent*>(e);
+ if ( widget->rect().contains(event->pos()) && d->led->ledColor() != StatusLed::Gray)
+ {
+ // Reset all filters settings.
+ d->textFilter->setText(TQString());
+ d->ratingFilter->setRating(0);
+ d->ratingFilter->setRatingFilterCondition(AlbumLister::GreaterEqualCondition);
+ d->mimeFilter->setMimeFilter(MimeFilter::AllFiles);
+ emit signalResetTagFilters();
+ }
+ }
+
+ return false;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumiconviewfilter.h b/src/digikam/albumiconviewfilter.h
new file mode 100644
index 00000000..8fdbaa64
--- /dev/null
+++ b/src/digikam/albumiconviewfilter.h
@@ -0,0 +1,78 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-11-27
+ * Description : a bar to filter album contents
+ *
+ * Copyright (C) 2007-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMICONVIEWFILTER_H
+#define ALBUMICONVIEWFILTER_H
+
+// TQt includes.
+
+#include "tqhbox.h"
+#include "tqstring.h"
+
+// Local includes.
+
+#include "albumlister.h"
+
+class TQEvent;
+class TQObject;
+
+namespace Digikam
+{
+
+class AlbumIconViewFilterPriv;
+
+class AlbumIconViewFilter : public TQHBox
+{
+ TQ_OBJECT
+
+public:
+
+ AlbumIconViewFilter(TQWidget* parent);
+ ~AlbumIconViewFilter();
+
+ void readSettings();
+ void saveSettings();
+
+signals:
+
+ void signalResetTagFilters();
+
+private slots:
+
+ void slotRatingFilterChanged(int, AlbumLister::RatingCondition);
+ void slotMimeTypeFilterChanged(int);
+ void slotTextFilterChanged(const TQString&);
+ void slotItemsFilterMatch(bool);
+
+private:
+
+ bool eventFilter(TQObject *object, TQEvent *e);
+
+private:
+
+ AlbumIconViewFilterPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // ALBUMICONVIEWFILTER_H
diff --git a/src/digikam/albuminfo.h b/src/digikam/albuminfo.h
new file mode 100644
index 00000000..17e191e3
--- /dev/null
+++ b/src/digikam/albuminfo.h
@@ -0,0 +1,112 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : Album informations container.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMINFO_H
+#define ALBUMINFO_H
+
+/** @file albuminfo.h */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqvaluelist.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+namespace Digikam
+{
+
+/**
+ * \class AlbumInfo
+ * A container class for transporting album information
+ * from the database to AlbumManager
+ */
+class AlbumInfo
+{
+public:
+
+ typedef TQValueList<AlbumInfo> List;
+
+ int id;
+ TQString url;
+ TQString caption;
+ TQString collection;
+ TQDate date;
+ TQString icon;
+
+ /**
+ * needed for sorting
+ */
+ bool operator<(const AlbumInfo& info)
+ {
+ return url < info.url;
+ }
+};
+
+/**
+ * \class TagInfo
+ * A container class for transporting tag information
+ * from the database to AlbumManager
+ */
+class TagInfo
+{
+public:
+
+ typedef TQValueList<TagInfo> List;
+
+ int id;
+ int pid;
+ TQString name;
+ TQString icon;
+};
+
+/**
+ * \class SearchInfo
+ * A container class for transporting search information
+ * from the database to AlbumManager
+ */
+class SearchInfo
+{
+public:
+
+ typedef TQValueList<SearchInfo> List;
+
+ int id;
+ TQString name;
+ KURL url;
+
+ /**
+ * needed for sorting
+ */
+ bool operator<(const SearchInfo& info)
+ {
+ return id < info.id;
+ }
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMINFO_H */
diff --git a/src/digikam/albumitemhandler.cpp b/src/digikam/albumitemhandler.cpp
new file mode 100644
index 00000000..60dc8c1b
--- /dev/null
+++ b/src/digikam/albumitemhandler.cpp
@@ -0,0 +1,45 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-10-15
+ * Description : album item handler.
+ *
+ * Copyright (C) 2003 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "albummanager.h"
+#include "albumitemhandler.h"
+
+namespace Digikam
+{
+
+AlbumItemHandler::AlbumItemHandler()
+{
+}
+
+AlbumItemHandler::~AlbumItemHandler()
+{
+}
+
+void AlbumItemHandler::emitItemsSelected(bool val)
+{
+ AlbumManager::instance()->emitAlbumItemsSelected(val);
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumitemhandler.h b/src/digikam/albumitemhandler.h
new file mode 100644
index 00000000..e78042f5
--- /dev/null
+++ b/src/digikam/albumitemhandler.h
@@ -0,0 +1,60 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-09-26
+ * Description : album item handler.
+ *
+ * Copyright (C) 2003 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMITEMHANDLER_H
+#define ALBUMITEMHANDLER_H
+
+// KDE includes.
+
+#include <kurl.h>
+
+namespace Digikam
+{
+
+/*!
+ AlbumItemHandler
+ An abstract class which returns the selected items and all items in
+ the current album. All views which handle album items should derive
+ from this.
+*/
+
+class AlbumItemHandler
+{
+public:
+
+ AlbumItemHandler();
+ virtual ~AlbumItemHandler();
+
+ virtual KURL::List allItems() = 0;
+ virtual KURL::List selectedItems() = 0;
+ virtual void refresh() = 0;
+ virtual void refreshItems(const KURL::List& items) = 0;
+
+protected:
+
+ void emitItemsSelected(bool val);
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMITEMHANDLER_H */
diff --git a/src/digikam/albumlister.cpp b/src/digikam/albumlister.cpp
new file mode 100644
index 00000000..63648773
--- /dev/null
+++ b/src/digikam/albumlister.cpp
@@ -0,0 +1,640 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-26
+ * Description : Albums lister.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2007-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/time.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+#include <ctime>
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqcstring.h>
+#include <tqdatastream.h>
+#include <tqfileinfo.h>
+#include <tqdir.h>
+#include <tqmap.h>
+#include <tqpair.h>
+#include <tqvaluelist.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <tdeio/job.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "mimefilter.h"
+#include "album.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "albumlister.h"
+#include "albumlister.moc"
+
+namespace Digikam
+{
+
+class AlbumListerPriv
+{
+public:
+
+ AlbumListerPriv()
+ {
+ untaggedFilter = false;
+ ratingFilter = 0;
+ filterTimer = 0;
+ job = 0;
+ currAlbum = 0;
+ namesFilter = "*";
+ mimeTypeFilter = MimeFilter::AllFiles;
+ ratingCond = AlbumLister::GreaterEqualCondition;
+ matchingCond = AlbumLister::OrCondition;
+ recurseAlbums = false;
+ recurseTags = false;
+ }
+
+ bool untaggedFilter;
+
+ int ratingFilter;
+ int recurseAlbums;
+ int recurseTags;
+
+ TQString namesFilter;
+ TQString textFilter;
+
+ TQMap<TQ_LLONG, ImageInfo*> itemMap;
+ TQMap<int, int> invalidatedItems;
+ TQMap<TQDateTime, bool> dayFilter;
+
+ TQValueList<int> tagFilter;
+
+ TQTimer *filterTimer;
+
+ TDEIO::TransferJob *job;
+
+ ImageInfoList itemList;
+
+ Album *currAlbum;
+
+ MimeFilter::TypeMimeFilter mimeTypeFilter;
+
+ AlbumLister::MatchingCondition matchingCond;
+
+ AlbumLister::RatingCondition ratingCond;
+};
+
+AlbumLister* AlbumLister::m_instance = 0;
+
+AlbumLister* AlbumLister::instance()
+{
+ if (!m_instance)
+ new AlbumLister();
+
+ return m_instance;
+}
+
+AlbumLister::AlbumLister()
+{
+ m_instance = this;
+
+ d = new AlbumListerPriv;
+ d->itemList.setAutoDelete(true);
+ d->filterTimer = new TQTimer(this);
+
+ connect(d->filterTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotFilterItems()));
+}
+
+AlbumLister::~AlbumLister()
+{
+ delete d->filterTimer;
+ delete d;
+ m_instance = 0;
+}
+
+void AlbumLister::openAlbum(Album *album)
+{
+ d->currAlbum = album;
+ d->filterTimer->stop();
+ emit signalClear();
+ d->itemList.clear();
+ d->itemMap.clear();
+
+ if (d->job)
+ {
+ d->job->kill();
+ d->job = 0;
+ }
+
+ if (!album)
+ return;
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << AlbumManager::instance()->getLibraryPath();
+ ds << album->kurl();
+ ds << d->namesFilter;
+ ds << AlbumSettings::instance()->getIconShowResolution();
+ ds << d->recurseAlbums;
+ ds << d->recurseTags;
+
+ // Protocol = digikamalbums -> tdeio_digikamalbums
+ d->job = new TDEIO::TransferJob(album->kurl(), TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(), false);
+
+ connect(d->job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotResult(TDEIO::Job*)));
+
+ connect(d->job, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
+ this, TQ_SLOT(slotData(TDEIO::Job*, const TQByteArray&)));
+}
+
+void AlbumLister::refresh()
+{
+ if (!d->currAlbum)
+ return;
+
+ d->filterTimer->stop();
+
+ if (d->job)
+ {
+ d->job->kill();
+ d->job = 0;
+ }
+
+ d->itemMap.clear();
+ ImageInfo* item;
+ for (ImageInfoListIterator it(d->itemList); (item = it.current()); ++it)
+ {
+ d->itemMap.insert(item->id(), item);
+ }
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << AlbumManager::instance()->getLibraryPath();
+ ds << d->currAlbum->kurl();
+ ds << d->namesFilter;
+ ds << AlbumSettings::instance()->getIconShowResolution();
+ ds << d->recurseAlbums;
+ ds << d->recurseTags;
+
+ d->job = new TDEIO::TransferJob(d->currAlbum->kurl(), TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(), false);
+
+ connect(d->job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotResult(TDEIO::Job*)));
+
+ connect(d->job, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
+ this, TQ_SLOT(slotData(TDEIO::Job*, const TQByteArray&)));
+}
+
+void AlbumLister::setDayFilter(const TQValueList<TQDateTime>& days)
+{
+ d->dayFilter.clear();
+
+ for (TQValueList<TQDateTime>::const_iterator it = days.begin(); it != days.end(); ++it)
+ d->dayFilter.insert(*it, true);
+
+ d->filterTimer->start(100, true);
+}
+
+bool AlbumLister::tagFiltersIsActive()
+{
+ if (!d->tagFilter.isEmpty() || d->untaggedFilter)
+ return true;
+
+ return false;
+}
+
+void AlbumLister::setTagFilter(const TQValueList<int>& tags, const MatchingCondition& matchingCond,
+ bool showUnTagged)
+{
+ d->tagFilter = tags;
+ d->matchingCond = matchingCond;
+ d->untaggedFilter = showUnTagged;
+ d->filterTimer->start(100, true);
+}
+
+void AlbumLister::setRatingFilter(int rating, const RatingCondition& ratingCond)
+{
+ d->ratingFilter = rating;
+ d->ratingCond = ratingCond;
+ d->filterTimer->start(100, true);
+}
+
+void AlbumLister::setMimeTypeFilter(int mimeTypeFilter)
+{
+ d->mimeTypeFilter = (MimeFilter::TypeMimeFilter)mimeTypeFilter;
+ d->filterTimer->start(100, true);
+}
+
+void AlbumLister::setTextFilter(const TQString& text)
+{
+ d->textFilter = text;
+ d->filterTimer->start(100, true);
+}
+
+void AlbumLister::setRecurseAlbums(bool recursive)
+{
+ d->recurseAlbums = recursive;
+ refresh();
+}
+
+void AlbumLister::setRecurseTags(bool recursive)
+{
+ d->recurseTags = recursive;
+ refresh();
+}
+
+bool AlbumLister::matchesFilter(const ImageInfo* info, bool &foundText)
+{
+ if (d->dayFilter.isEmpty() && d->tagFilter.isEmpty() && d->textFilter.isEmpty() &&
+ !d->untaggedFilter && d->ratingFilter==-1)
+ return true;
+
+ bool match = false;
+
+ if (!d->tagFilter.isEmpty())
+ {
+ TQValueList<int> tagIDs = info->tagIDs();
+ TQValueList<int>::iterator it;
+
+ if (d->matchingCond == OrCondition)
+ {
+ for (it = d->tagFilter.begin(); it != d->tagFilter.end(); ++it)
+ {
+ if (tagIDs.contains(*it))
+ {
+ match = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // AND matching condition...
+
+ for (it = d->tagFilter.begin(); it != d->tagFilter.end(); ++it)
+ {
+ if (!tagIDs.contains(*it))
+ break;
+ }
+
+ if (it == d->tagFilter.end())
+ match = true;
+ }
+
+ match |= (d->untaggedFilter && tagIDs.isEmpty());
+ }
+ else if (d->untaggedFilter)
+ {
+ match = info->tagIDs().isEmpty();
+ }
+ else
+ {
+ match = true;
+ }
+
+ if (!d->dayFilter.isEmpty())
+ {
+ match &= d->dayFilter.contains(TQDateTime(info->dateTime().date(), TQTime()));
+ }
+
+ //-- Filter by rating ---------------------------------------------------------
+
+ if (d->ratingFilter >= 0)
+ {
+ if (d->ratingCond == GreaterEqualCondition)
+ {
+ // If the rating is not >=, i.e it is <, then it does not match.
+ if (info->rating() < d->ratingFilter)
+ {
+ match = false;
+ }
+ }
+ else if (d->ratingCond == EqualCondition)
+ {
+ // If the rating is not =, i.e it is !=, then it does not match.
+ if (info->rating() != d->ratingFilter)
+ {
+ match = false;
+ }
+ }
+ else
+ {
+ // If the rating is not <=, i.e it is >, then it does not match.
+ if (info->rating() > d->ratingFilter)
+ {
+ match = false;
+ }
+ }
+ }
+
+ // -- Filter by mime type -----------------------------------------------------
+
+ TQFileInfo fi(info->filePath());
+ TQString mimeType = fi.extension(false).upper();
+
+ switch(d->mimeTypeFilter)
+ {
+ case MimeFilter::ImageFiles:
+ {
+ TQString imageFilesExt(AlbumSettings::instance()->getImageFileFilter());
+ imageFilesExt.append(AlbumSettings::instance()->getRawFileFilter());
+ if (!imageFilesExt.upper().contains(mimeType))
+ match = false;
+ break;
+ }
+ case MimeFilter::JPGFiles:
+ {
+ if (mimeType != TQString("JPG") && mimeType != TQString("JPE") &&
+ mimeType != TQString("JPEG"))
+ match = false;
+ break;
+ }
+ case MimeFilter::PNGFiles:
+ {
+ if (mimeType != TQString("PNG"))
+ match = false;
+ break;
+ }
+ case MimeFilter::TIFFiles:
+ {
+ if (mimeType != TQString("TIF") && mimeType != TQString("TIFF"))
+ match = false;
+ break;
+ }
+ case MimeFilter::NoRAWFiles:
+ {
+ TQString rawFilesExt(AlbumSettings::instance()->getRawFileFilter());
+ if (rawFilesExt.upper().contains(mimeType))
+ match = false;
+ break;
+ }
+ case MimeFilter::RAWFiles:
+ {
+ TQString rawFilesExt(AlbumSettings::instance()->getRawFileFilter());
+ if (!rawFilesExt.upper().contains(mimeType))
+ match = false;
+ break;
+ }
+ case MimeFilter::MoviesFiles:
+ {
+ TQString moviesFilesExt(AlbumSettings::instance()->getMovieFileFilter());
+ if (!moviesFilesExt.upper().contains(mimeType))
+ match = false;
+ break;
+ }
+ case MimeFilter::AudioFiles:
+ {
+ TQString audioFilesExt(AlbumSettings::instance()->getAudioFileFilter());
+ if (!audioFilesExt.upper().contains(mimeType))
+ match = false;
+ break;
+ }
+ default: // All Files: do nothing...
+ break;
+ }
+
+ //-- Filter by text -----------------------------------------------------------
+
+ if (!d->textFilter.isEmpty())
+ {
+ foundText = false;
+ if (info->name().lower().contains(d->textFilter.lower()))
+ {
+ foundText = true;
+ }
+ if (info->caption().lower().contains(d->textFilter.lower()))
+ foundText = true;
+ TQStringList tags = info->tagNames();
+ for (TQStringList::const_iterator it = tags.constBegin() ; it != tags.constEnd() ; ++it)
+ {
+ if ((*it).lower().contains(d->textFilter.lower()))
+ foundText = true;
+ }
+ // check for folder names
+ PAlbum* palbum = AlbumManager::instance()->findPAlbum(info->albumID());
+ if ((palbum && palbum->title().lower().contains(d->textFilter.lower())))
+ {
+ foundText = true;
+ }
+ match &= foundText;
+ }
+
+ return match;
+}
+
+void AlbumLister::stop()
+{
+ d->currAlbum = 0;
+ d->filterTimer->stop();
+ emit signalClear();
+
+ d->itemList.clear();
+ d->itemMap.clear();
+
+ if (d->job)
+ {
+ d->job->kill();
+ d->job = 0;
+ }
+}
+
+void AlbumLister::setNamesFilter(const TQString& namesFilter)
+{
+ d->namesFilter = namesFilter;
+}
+
+void AlbumLister::invalidateItem(const ImageInfo *item)
+{
+ d->invalidatedItems.insert(item->id(), item->id());
+}
+
+void AlbumLister::slotFilterItems()
+{
+ if (d->job)
+ {
+ d->filterTimer->start(100, true);
+ return;
+ }
+
+ TQPtrList<ImageInfo> newFilteredItemsList;
+ TQPtrList<ImageInfo> deleteFilteredItemsList;
+ ImageInfo *item = 0;
+ bool matchForText = false;
+ bool match = false;
+
+ for (ImageInfoListIterator it(d->itemList);
+ (item = it.current()); ++it)
+ {
+ bool foundText = false;
+ if (matchesFilter(item, foundText))
+ {
+ match = true;
+ if (!item->getViewItem())
+ newFilteredItemsList.append(item);
+ }
+ else
+ {
+ if (item->getViewItem())
+ deleteFilteredItemsList.append(item);
+ }
+
+ if (foundText)
+ matchForText = true;
+ }
+
+ // This takes linear time - and deleting seems to take longer. Set wait cursor for large numbers.
+ bool setCursor = (3*deleteFilteredItemsList.count() + newFilteredItemsList.count()) > 1500;
+ if (setCursor)
+ kapp->setOverrideCursor(KCursor::waitCursor());
+
+ emit signalItemsTextFilterMatch(matchForText);
+ emit signalItemsFilterMatch(match);
+
+ if (!deleteFilteredItemsList.isEmpty())
+ {
+ for (ImageInfo *info=deleteFilteredItemsList.first(); info; info = deleteFilteredItemsList.next())
+ emit signalDeleteFilteredItem(info);
+ }
+ if (!newFilteredItemsList.isEmpty())
+ {
+ emit signalNewFilteredItems(newFilteredItemsList);
+ }
+
+ if (setCursor)
+ kapp->restoreOverrideCursor();
+}
+
+void AlbumLister::slotResult(TDEIO::Job* job)
+{
+ d->job = 0;
+
+ if (job->error())
+ {
+ DWarning() << "Failed to list url: " << job->errorString() << endl;
+ d->itemMap.clear();
+ d->invalidatedItems.clear();
+ return;
+ }
+
+ typedef TQMap<TQ_LLONG, ImageInfo*> ImMap;
+
+ for (ImMap::iterator it = d->itemMap.begin();
+ it != d->itemMap.end(); ++it)
+ {
+ emit signalDeleteItem(it.data());
+ emit signalDeleteFilteredItem(it.data());
+ d->itemList.remove(it.data());
+ }
+
+ d->itemMap.clear();
+ d->invalidatedItems.clear();
+
+ emit signalCompleted();
+}
+
+void AlbumLister::slotData(TDEIO::Job*, const TQByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ TQ_LLONG imageID;
+ int albumID;
+ TQString name;
+ TQString date;
+ size_t size;
+ TQSize dims;
+
+ ImageInfoList newItemsList;
+ ImageInfoList newFilteredItemsList;
+
+ TQDataStream ds(data, IO_ReadOnly);
+
+ while (!ds.atEnd())
+ {
+ bool foundText = false;
+
+ ds >> imageID;
+ ds >> albumID;
+ ds >> name;
+ ds >> date;
+ ds >> size;
+ ds >> dims;
+
+ if (d->itemMap.contains(imageID))
+ {
+ ImageInfo* info = d->itemMap[imageID];
+ d->itemMap.remove(imageID);
+
+ if (d->invalidatedItems.contains(imageID))
+ {
+ emit signalDeleteItem(info);
+ emit signalDeleteFilteredItem(info);
+ d->itemList.remove(info);
+ }
+ else
+ {
+ if (!matchesFilter(info, foundText))
+ {
+ emit signalDeleteFilteredItem(info);
+ }
+ continue;
+ }
+ }
+
+ ImageInfo* info = new ImageInfo(imageID, albumID, name,
+ TQDateTime::fromString(date, TQt::ISODate),
+ size, dims);
+
+ if (matchesFilter(info, foundText))
+ newFilteredItemsList.append(info);
+
+ newItemsList.append(info);
+ d->itemList.append(info);
+ }
+
+ if (!newFilteredItemsList.isEmpty())
+ emit signalNewFilteredItems(newFilteredItemsList);
+
+ if (!newItemsList.isEmpty())
+ emit signalNewItems(newItemsList);
+
+ slotFilterItems();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumlister.h b/src/digikam/albumlister.h
new file mode 100644
index 00000000..7c14e4c1
--- /dev/null
+++ b/src/digikam/albumlister.h
@@ -0,0 +1,159 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-26
+ * Description : Albums lister.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2007-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMLISTER_H
+#define ALBUMLISTER_H
+
+/** @file albumlister.h */
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqcstring.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace Digikam
+{
+
+class Album;
+class AlbumListerPriv;
+
+/**
+ * Manages imageinfo
+ *
+ * does listing of imageinfo for the current album and controls the lifetime
+ * of the imageinfo. tdeioslaves are used for listing the imageinfo
+ * corresponding to an album. Similar to the albummanager, frontend entities need
+ * to connect to the AlbumLister for notifications of new Images, deletion of
+ * Images or refreshing of currently listed Image.
+ */
+class AlbumLister : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ /** @enum MatchingCondition
+ * Possible logical matching condition used to sort tags id.
+ */
+ enum MatchingCondition
+ {
+ OrCondition = 0,
+ AndCondition
+ };
+
+ /** @enum RatingCondition
+ * Possible conditions used to filter rating: >=, =, <=.
+ */
+ enum RatingCondition
+ {
+ GreaterEqualCondition = 0,
+ EqualCondition,
+ LessEqualCondition
+ };
+
+public:
+
+ static AlbumLister* instance();
+
+ ~AlbumLister();
+
+ /**
+ * Opens an album to lists its items
+ */
+ void openAlbum(Album *album);
+ void stop();
+
+ /**
+ * Reread an albums item list
+ */
+ void refresh();
+
+ void setNamesFilter(const TQString& namesFilter);
+
+ void setDayFilter(const TQValueList<TQDateTime>& days);
+
+ void setTagFilter(const TQValueList<int>& tags, const MatchingCondition& matchingCond,
+ bool showUnTagged=false);
+
+ void setRatingFilter(int rating, const RatingCondition& ratingCond);
+
+ void setMimeTypeFilter(int mimeTypeFilter);
+
+ void setTextFilter(const TQString& text);
+
+ void setRecurseAlbums(bool recursive);
+ void setRecurseTags(bool recursive);
+
+ /**
+ * Trigger a recreation of the given ImageInfo object
+ * for the next refresh.
+ */
+ void invalidateItem(const ImageInfo *item);
+
+ bool tagFiltersIsActive();
+
+signals:
+
+ void signalNewItems(const ImageInfoList& items);
+ void signalDeleteItem(ImageInfo* item);
+ void signalNewFilteredItems(const ImageInfoList& items);
+ void signalDeleteFilteredItem(ImageInfo* item);
+ void signalClear();
+ void signalCompleted();
+ void signalItemsTextFilterMatch(bool);
+ void signalItemsFilterMatch(bool);
+
+private slots:
+
+ void slotFilterItems();
+
+ void slotResult(TDEIO::Job* job);
+ void slotData(TDEIO::Job* job, const TQByteArray& data);
+
+private:
+
+ AlbumLister();
+ bool matchesFilter(const ImageInfo* info, bool& foundText);
+
+private:
+
+ AlbumListerPriv *d;
+
+ static AlbumLister *m_instance;
+
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMLISTER_H */
diff --git a/src/digikam/albummanager.cpp b/src/digikam/albummanager.cpp
new file mode 100644
index 00000000..847ce99e
--- /dev/null
+++ b/src/digikam/albummanager.cpp
@@ -0,0 +1,1678 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-15
+ * Description : Albums manager interface.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#include <config.h>
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <clocale>
+#include <cstdlib>
+#include <cstdio>
+#include <cerrno>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqdir.h>
+#include <tqdict.h>
+#include <tqintdict.h>
+#include <tqcstring.h>
+#include <tqtextcodec.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeversion.h>
+#include <tdemessagebox.h>
+#include <kstandarddirs.h>
+#include <tdeio/netaccess.h>
+#include <tdeio/global.h>
+#include <tdeio/job.h>
+#include <kdirwatch.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albumitemhandler.h"
+#include "dio.h"
+#include "albumsettings.h"
+#include "scanlib.h"
+#include "splashscreen.h"
+#include "upgradedb_sqlite2tosqlite3.h"
+#include "albummanager.h"
+#include "albummanager.moc"
+
+namespace Digikam
+{
+
+typedef TQDict<PAlbum> PAlbumDict;
+typedef TQIntDict<Album> AlbumIntDict;
+typedef TQValueList<TQDateTime> DDateList;
+
+class AlbumManagerPriv
+{
+
+public:
+
+ AlbumManagerPriv()
+ {
+ db = 0;
+ dateListJob = 0;
+ albumListJob = 0;
+ tagListJob = 0;
+ rootPAlbum = 0;
+ rootTAlbum = 0;
+ rootDAlbum = 0;
+ rootSAlbum = 0;
+ itemHandler = 0;
+ currentAlbum = 0;
+ dirWatch = 0;
+ changed = false;
+ }
+
+ bool changed;
+
+ TQString libraryPath;
+ TQStringList dirtyAlbums;
+
+ DDateList dbPathModificationDateList;
+
+ KDirWatch *dirWatch;
+
+ TDEIO::TransferJob *albumListJob;
+ TDEIO::TransferJob *dateListJob;
+ TDEIO::TransferJob *tagListJob;
+
+ PAlbum *rootPAlbum;
+ TAlbum *rootTAlbum;
+ DAlbum *rootDAlbum;
+ SAlbum *rootSAlbum;
+
+ PAlbumDict pAlbumDict;
+ AlbumIntDict albumIntDict;
+
+ Album *currentAlbum;
+ AlbumDB *db;
+ AlbumItemHandler *itemHandler;
+
+ TQValueList<TQDateTime> buildDirectoryModList(const TQFileInfo &dbFile)
+ {
+ // retrieve modification dates of all files in the database-file dir
+ TQValueList<TQDateTime> modList;
+ const TQFileInfoList *fileInfoList = dbFile.dir().entryInfoList(TQDir::Files | TQDir::Dirs );
+
+ // build list
+ TQFileInfoListIterator it(*fileInfoList);
+ TQFileInfo *fi;
+
+ while ( (fi = it.current()) != 0 )
+ {
+ if ( fi->fileName() != dbFile.fileName())
+ {
+ modList << fi->lastModified();
+ }
+ ++it;
+ }
+
+ return modList;
+ }
+
+};
+
+AlbumManager* AlbumManager::m_instance = 0;
+
+AlbumManager* AlbumManager::instance()
+{
+ return m_instance;
+}
+
+AlbumManager::AlbumManager()
+{
+ m_instance = this;
+
+ d = new AlbumManagerPriv;
+ d->db = new AlbumDB;
+}
+
+AlbumManager::~AlbumManager()
+{
+ if (d->dateListJob)
+ {
+ d->dateListJob->kill();
+ d->dateListJob = 0;
+ }
+
+ if (d->albumListJob)
+ {
+ d->albumListJob->kill();
+ d->albumListJob = 0;
+ }
+
+ if (d->tagListJob)
+ {
+ d->tagListJob->kill();
+ d->tagListJob = 0;
+ }
+
+ delete d->rootPAlbum;
+ delete d->rootTAlbum;
+ delete d->rootDAlbum;
+ delete d->rootSAlbum;
+
+ delete d->dirWatch;
+
+ delete d->db;
+ delete d;
+
+ m_instance = 0;
+}
+
+AlbumDB* AlbumManager::albumDB()
+{
+ return d->db;
+}
+
+void AlbumManager::setLibraryPath(const TQString& path, SplashScreen *splash)
+{
+ TQString cleanPath = TQDir::cleanDirPath(path);
+
+ if (cleanPath == d->libraryPath)
+ return;
+
+ d->changed = true;
+
+ if (d->dateListJob)
+ {
+ d->dateListJob->kill();
+ d->dateListJob = 0;
+ }
+
+ if (d->albumListJob)
+ {
+ d->albumListJob->kill();
+ d->albumListJob = 0;
+ }
+
+ if (d->tagListJob)
+ {
+ d->tagListJob->kill();
+ d->tagListJob = 0;
+ }
+
+ delete d->dirWatch;
+ d->dirWatch = 0;
+ d->dirtyAlbums.clear();
+
+ d->currentAlbum = 0;
+ emit signalAlbumCurrentChanged(0);
+ emit signalAlbumsCleared();
+
+ d->pAlbumDict.clear();
+ d->albumIntDict.clear();
+
+ delete d->rootPAlbum;
+ delete d->rootTAlbum;
+ delete d->rootDAlbum;
+
+ d->rootPAlbum = 0;
+ d->rootTAlbum = 0;
+ d->rootDAlbum = 0;
+ d->rootSAlbum = 0;
+
+ d->libraryPath = cleanPath;
+
+ TQString dbPath = cleanPath + "/digikam3.db";
+
+#ifdef NFS_HACK
+ dbPath = locateLocal("appdata", TDEIO::encodeFileName(TQDir::cleanDirPath(dbPath)));
+#endif
+
+ d->db->setDBPath(dbPath);
+
+ // -- Locale Checking ---------------------------------------------------------
+
+ TQString currLocale(TQTextCodec::codecForLocale()->name());
+ TQString dbLocale = d->db->getSetting("Locale");
+
+ // guilty until proven innocent
+ bool localeChanged = true;
+
+ if (dbLocale.isNull())
+ {
+ DDebug() << "No locale found in database" << endl;
+
+ // Copy an existing locale from the settings file (used < 0.8)
+ // to the database.
+ TDEConfig* config = TDEGlobal::config();
+ config->setGroup("General Settings");
+ if (config->hasKey("Locale"))
+ {
+ DDebug() << "Locale found in configfile" << endl;
+ dbLocale = config->readEntry("Locale");
+
+ // this hack is necessary, as we used to store the entire
+ // locale info LC_ALL (for eg: en_US.UTF-8) earlier,
+ // we now save only the encoding (UTF-8)
+
+ TQString oldConfigLocale = ::setlocale(0, 0);
+
+ if (oldConfigLocale == dbLocale)
+ {
+ dbLocale = currLocale;
+ localeChanged = false;
+ d->db->setSetting("Locale", dbLocale);
+ }
+ }
+ else
+ {
+ DDebug() << "No locale found in config file" << endl;
+ dbLocale = currLocale;
+
+ localeChanged = false;
+ d->db->setSetting("Locale",dbLocale);
+ }
+ }
+ else
+ {
+ if (dbLocale == currLocale)
+ localeChanged = false;
+ }
+
+ if (localeChanged)
+ {
+ // TODO it would be better to replace all yes/no confirmation dialogs with ones that has custom
+ // buttons that denote the actions directly, i.e.: ["Ignore and Continue"] ["Adjust locale"]
+ int result =
+ KMessageBox::warningYesNo(0,
+ i18n("Your locale has changed since this album "
+ "was last opened.\n"
+ "Old Locale : %1, New Locale : %2\n"
+ "This can cause unexpected problems. "
+ "If you are sure that you want to "
+ "continue, click 'Yes' to work with this album. "
+ "Otherwise, click 'No' and correct your "
+ "locale setting before restarting digiKam")
+ .arg(dbLocale)
+ .arg(currLocale));
+ if (result != KMessageBox::Yes)
+ exit(0);
+
+ d->db->setSetting("Locale",currLocale);
+ }
+
+ // -- Check if we need to upgrade 0.7.x db to 0.8 db ---------------------
+
+ if (!upgradeDB_Sqlite2ToSqlite3(d->libraryPath))
+ {
+ KMessageBox::error(0, i18n("Failed to update the old Database to the new Database format\n"
+ "This error can happen if the Album Path '%1' does not exist or is write-protected.\n"
+ "If you have moved your photo collection, you need to adjust the 'Album Path' in digikam's configuration file.")
+ .arg(d->libraryPath));
+ exit(0);
+ }
+
+ // set an initial modification list to filter out KDirWatch signals
+ // caused by database operations
+ TQFileInfo dbFile(dbPath);
+ d->dbPathModificationDateList = d->buildDirectoryModList(dbFile);
+
+ // -- Check if we need to do scanning -------------------------------------
+
+ TDEConfig* config = TDEGlobal::config();
+ config->setGroup("General Settings");
+ if (config->readBoolEntry("Scan At Start", true) ||
+ d->db->getSetting("Scanned").isEmpty())
+ {
+ ScanLib sLib(splash);
+ sLib.startScan();
+ }
+}
+
+TQString AlbumManager::getLibraryPath() const
+{
+ return d->libraryPath;
+}
+
+void AlbumManager::startScan()
+{
+ if (!d->changed)
+ return;
+ d->changed = false;
+
+ d->dirWatch = new KDirWatch(this);
+ connect(d->dirWatch, TQ_SIGNAL(dirty(const TQString&)),
+ this, TQ_SLOT(slotDirty(const TQString&)));
+
+ KDirWatch::Method m = d->dirWatch->internalMethod();
+ TQString mName("FAM");
+ if (m == KDirWatch::DNotify)
+ mName = TQString("DNotify");
+ else if (m == KDirWatch::Stat)
+ mName = TQString("Stat");
+ else if (m == KDirWatch::INotify)
+ mName = TQString("INotify");
+ DDebug() << "KDirWatch method = " << mName << endl;
+
+ d->dirWatch->addDir(d->libraryPath);
+
+ d->rootPAlbum = new PAlbum(i18n("My Albums"), 0, true);
+ insertPAlbum(d->rootPAlbum);
+
+ d->rootTAlbum = new TAlbum(i18n("My Tags"), 0, true);
+ insertTAlbum(d->rootTAlbum);
+
+ d->rootSAlbum = new SAlbum(0, KURL(), true, true);
+
+ d->rootDAlbum = new DAlbum(TQDate(), true);
+
+ refresh();
+
+ emit signalAllAlbumsLoaded();
+}
+
+void AlbumManager::refresh()
+{
+ scanPAlbums();
+ scanTAlbums();
+ scanSAlbums();
+ scanDAlbums();
+
+ if (!d->dirtyAlbums.empty())
+ {
+ KURL u;
+ u.setProtocol("digikamalbums");
+ u.setPath(d->dirtyAlbums.first());
+ d->dirtyAlbums.pop_front();
+
+ DIO::scan(u);
+ }
+}
+
+void AlbumManager::scanPAlbums()
+{
+ // first insert all the current PAlbums into a map for quick lookup
+ typedef TQMap<TQString, PAlbum*> AlbumMap;
+ AlbumMap aMap;
+
+ AlbumIterator it(d->rootPAlbum);
+ while (it.current())
+ {
+ PAlbum* a = (PAlbum*)(*it);
+ aMap.insert(a->url(), a);
+ ++it;
+ }
+
+ // scan db and get a list of all albums
+ AlbumInfo::List aList = d->db->scanAlbums();
+ qHeapSort(aList);
+
+ AlbumInfo::List newAlbumList;
+
+ // go through all the Albums and see which ones are already present
+ for (AlbumInfo::List::iterator it = aList.begin(); it != aList.end(); ++it)
+ {
+ AlbumInfo info = *it;
+ info.url = TQDir::cleanDirPath(info.url);
+
+ if (!aMap.contains(info.url))
+ {
+ newAlbumList.append(info);
+ }
+ else
+ {
+ aMap.remove(info.url);
+ }
+ }
+
+ // now aMap contains all the deleted albums and
+ // newAlbumList contains all the new albums
+
+ // first inform all frontends of the deleted albums
+ for (AlbumMap::iterator it = aMap.begin(); it != aMap.end(); ++it)
+ {
+ // the albums have to be removed with children being removed first.
+ // removePAlbum takes care of that.
+ // So never delete the PAlbum using it.data(). instead check if the
+ // PAlbum is still in the Album Dict before trying to remove it.
+
+ // this might look like there is memory leak here, since removePAlbum
+ // doesn't delete albums and looks like child Albums don't get deleted.
+ // But when the parent album gets deleted, the children are also deleted.
+
+ PAlbum* album = d->pAlbumDict.find(it.key());
+ if (!album)
+ continue;
+
+ removePAlbum(album);
+ delete album;
+ }
+
+ qHeapSort(newAlbumList);
+ for (AlbumInfo::List::iterator it = newAlbumList.begin(); it != newAlbumList.end(); ++it)
+ {
+ AlbumInfo info = *it;
+ if (info.url.isEmpty() || info.url == "/")
+ continue;
+
+ // Despite its name info.url is a TQString.
+ // setPath takes care for escaping characters that are valid for files but not for URLs ('#')
+ KURL u;
+ u.setPath(info.url);
+ TQString name = u.fileName();
+ // Get its parent
+ TQString purl = u.upURL().path(-1);
+
+ PAlbum* parent = d->pAlbumDict.find(purl);
+ if (!parent)
+ {
+ DWarning() << k_funcinfo << "Could not find parent with url: "
+ << purl << " for: " << info.url << endl;
+ continue;
+ }
+
+ // Create the new album
+ PAlbum* album = new PAlbum(name, info.id, false);
+ album->m_caption = info.caption;
+ album->m_collection = info.collection;
+ album->m_date = info.date;
+ album->m_icon = info.icon;
+
+ album->setParent(parent);
+ d->dirWatch->addDir(album->folderPath());
+
+ insertPAlbum(album);
+ }
+
+ if (!AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
+ return;
+
+ // List albums using tdeioslave
+
+ if (d->albumListJob)
+ {
+ d->albumListJob->kill();
+ d->albumListJob = 0;
+ }
+
+ KURL u;
+ u.setProtocol("digikamalbums");
+ u.setPath("/");
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << d->libraryPath;
+ ds << KURL();
+ ds << AlbumSettings::instance()->getAllFileFilter();
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+
+ d->albumListJob = new TDEIO::TransferJob(u, TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(), false);
+ d->albumListJob->addMetaData("folders", "yes");
+
+ connect(d->albumListJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotAlbumsJobResult(TDEIO::Job*)));
+
+ connect(d->albumListJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
+ this, TQ_SLOT(slotAlbumsJobData(TDEIO::Job*, const TQByteArray&)));
+}
+
+void AlbumManager::scanTAlbums()
+{
+ // list TAlbums directly from the db
+ // first insert all the current TAlbums into a map for quick lookup
+ typedef TQMap<int, TAlbum*> TagMap;
+ TagMap tmap;
+
+ tmap.insert(0, d->rootTAlbum);
+
+ AlbumIterator it(d->rootTAlbum);
+ while (it.current())
+ {
+ TAlbum* t = (TAlbum*)(*it);
+ tmap.insert(t->id(), t);
+ ++it;
+ }
+
+ // Retrieve the list of tags from the database
+ TagInfo::List tList = d->db->scanTags();
+
+ // sort the list. needed because we want the tags can be read in any order,
+ // but we want to make sure that we are ensure to find the parent TAlbum
+ // for a new TAlbum
+
+ {
+ TQIntDict<TAlbum> tagDict;
+ tagDict.setAutoDelete(false);
+
+ // insert items into a dict for quick lookup
+ for (TagInfo::List::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TagInfo info = *it;
+ TAlbum* album = new TAlbum(info.name, info.id);
+ album->m_icon = info.icon;
+ album->m_pid = info.pid;
+ tagDict.insert(info.id, album);
+ }
+ tList.clear();
+
+ // also add root tag
+ TAlbum* rootTag = new TAlbum("root", 0, true);
+ tagDict.insert(0, rootTag);
+
+ // build tree
+ TQIntDictIterator<TAlbum> iter(tagDict);
+ for ( ; iter.current(); ++iter )
+ {
+ TAlbum* album = iter.current();
+ if (album->m_id == 0)
+ continue;
+
+ TAlbum* parent = tagDict.find(album->m_pid);
+ if (parent)
+ {
+ album->setParent(parent);
+ }
+ else
+ {
+ DWarning() << "Failed to find parent tag for tag "
+ << iter.current()->m_title
+ << " with pid "
+ << iter.current()->m_pid << endl;
+ }
+ }
+
+ // now insert the items into the list. becomes sorted
+ AlbumIterator it(rootTag);
+ while (it.current())
+ {
+ TAlbum* album = (TAlbum*)it.current();
+ TagInfo info;
+ info.id = album->m_id;
+ info.pid = album->m_pid;
+ info.name = album->m_title;
+ info.icon = album->m_icon;
+ tList.append(info);
+ ++it;
+ }
+
+ // this will also delete all child albums
+ delete rootTag;
+ }
+
+ for (TagInfo::List::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TagInfo info = *it;
+
+ // check if we have already added this tag
+ if (tmap.contains(info.id))
+ continue;
+
+ // Its a new album. Find the parent of the album
+ TagMap::iterator iter = tmap.find(info.pid);
+ if (iter == tmap.end())
+ {
+ DWarning() << "Failed to find parent tag for tag "
+ << info.name
+ << " with pid "
+ << info.pid << endl;
+ continue;
+ }
+
+ TAlbum* parent = iter.data();
+
+ // Create the new TAlbum
+ TAlbum* album = new TAlbum(info.name, info.id, false);
+ album->m_icon = info.icon;
+ album->setParent(parent);
+ insertTAlbum(album);
+
+ // also insert it in the map we are doing lookup of parent tags
+ tmap.insert(info.id, album);
+ }
+
+ if (!AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
+ return;
+
+ // List tags using tdeioslave
+
+ if (d->tagListJob)
+ {
+ d->tagListJob->kill();
+ d->tagListJob = 0;
+ }
+
+ KURL u;
+ u.setProtocol("digikamtags");
+ u.setPath("/");
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << d->libraryPath;
+ ds << KURL();
+ ds << AlbumSettings::instance()->getAllFileFilter();
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+
+ d->tagListJob = new TDEIO::TransferJob(u, TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(), false);
+ d->tagListJob->addMetaData("folders", "yes");
+
+ connect(d->tagListJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotTagsJobResult(TDEIO::Job*)));
+
+ connect(d->tagListJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
+ this, TQ_SLOT(slotTagsJobData(TDEIO::Job*, const TQByteArray&)));
+}
+
+void AlbumManager::scanSAlbums()
+{
+ // list SAlbums directly from the db
+ // first insert all the current SAlbums into a map for quick lookup
+ typedef TQMap<int, SAlbum*> SearchMap;
+ SearchMap sMap;
+
+ AlbumIterator it(d->rootSAlbum);
+ while (it.current())
+ {
+ SAlbum* t = (SAlbum*)(*it);
+ sMap.insert(t->id(), t);
+ ++it;
+ }
+
+ // Retrieve the list of searches from the database
+ SearchInfo::List sList = d->db->scanSearches();
+
+ for (SearchInfo::List::iterator it = sList.begin(); it != sList.end(); ++it)
+ {
+ SearchInfo info = *it;
+
+ // check if we have already added this search
+ if (sMap.contains(info.id))
+ continue;
+
+ bool simple = (info.url.queryItem("1.key") == TQString::fromLatin1("keyword"));
+
+ // Its a new album.
+ SAlbum* album = new SAlbum(info.id, info.url, simple, false);
+ album->setParent(d->rootSAlbum);
+ d->albumIntDict.insert(album->globalID(), album);
+ emit signalAlbumAdded(album);
+ }
+}
+
+void AlbumManager::scanDAlbums()
+{
+ // List dates using tdeioslave
+
+ if (d->dateListJob)
+ {
+ d->dateListJob->kill();
+ d->dateListJob = 0;
+ }
+
+ KURL u;
+ u.setProtocol("digikamdates");
+ u.setPath("/");
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << d->libraryPath;
+ ds << KURL();
+ ds << AlbumSettings::instance()->getAllFileFilter();
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+
+ d->dateListJob = new TDEIO::TransferJob(u, TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(), false);
+ d->dateListJob->addMetaData("folders", "yes");
+
+ connect(d->dateListJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDatesJobResult(TDEIO::Job*)));
+
+ connect(d->dateListJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
+ this, TQ_SLOT(slotDatesJobData(TDEIO::Job*, const TQByteArray&)));
+}
+
+AlbumList AlbumManager::allPAlbums() const
+{
+ AlbumList list;
+ if (d->rootPAlbum)
+ list.append(d->rootPAlbum);
+
+ AlbumIterator it(d->rootPAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+AlbumList AlbumManager::allTAlbums() const
+{
+ AlbumList list;
+ if (d->rootTAlbum)
+ list.append(d->rootTAlbum);
+
+ AlbumIterator it(d->rootTAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+AlbumList AlbumManager::allSAlbums() const
+{
+ AlbumList list;
+ if (d->rootSAlbum)
+ list.append(d->rootSAlbum);
+
+ AlbumIterator it(d->rootSAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+AlbumList AlbumManager::allDAlbums() const
+{
+ AlbumList list;
+ if (d->rootDAlbum)
+ list.append(d->rootDAlbum);
+
+ AlbumIterator it(d->rootDAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+void AlbumManager::setCurrentAlbum(Album *album)
+{
+ d->currentAlbum = album;
+ emit signalAlbumCurrentChanged(album);
+}
+
+Album* AlbumManager::currentAlbum() const
+{
+ return d->currentAlbum;
+}
+
+PAlbum* AlbumManager::findPAlbum(const KURL& url) const
+{
+ TQString path = url.path();
+ path.remove(d->libraryPath);
+ path = TQDir::cleanDirPath(path);
+
+ return d->pAlbumDict.find(path);
+}
+
+PAlbum* AlbumManager::findPAlbum(int id) const
+{
+ if (!d->rootPAlbum)
+ return 0;
+
+ int gid = d->rootPAlbum->globalID() + id;
+
+ return (PAlbum*)(d->albumIntDict.find(gid));
+}
+
+TAlbum* AlbumManager::findTAlbum(int id) const
+{
+ if (!d->rootTAlbum)
+ return 0;
+
+ int gid = d->rootTAlbum->globalID() + id;
+
+ return (TAlbum*)(d->albumIntDict.find(gid));
+}
+
+SAlbum* AlbumManager::findSAlbum(int id) const
+{
+ if (!d->rootTAlbum)
+ return 0;
+
+ int gid = d->rootSAlbum->globalID() + id;
+
+ return (SAlbum*)(d->albumIntDict.find(gid));
+}
+
+DAlbum* AlbumManager::findDAlbum(int id) const
+{
+ if (!d->rootDAlbum)
+ return 0;
+
+ int gid = d->rootDAlbum->globalID() + id;
+
+ return (DAlbum*)(d->albumIntDict.find(gid));
+}
+
+Album* AlbumManager::findAlbum(int gid) const
+{
+ return d->albumIntDict.find(gid);
+}
+
+TAlbum* AlbumManager::findTAlbum(const TQString &tagPath) const
+{
+ // handle gracefully with or without leading slash
+ bool withLeadingSlash = tagPath.startsWith("/");
+ AlbumIterator it(d->rootTAlbum);
+ while (it.current())
+ {
+ TAlbum *talbum = static_cast<TAlbum *>(*it);
+ if (talbum->tagPath(withLeadingSlash) == tagPath)
+ return talbum;
+ ++it;
+ }
+ return 0;
+
+}
+
+
+PAlbum* AlbumManager::createPAlbum(PAlbum* parent,
+ const TQString& name,
+ const TQString& caption,
+ const TQDate& date,
+ const TQString& collection,
+ TQString& errMsg)
+{
+ if (!parent)
+ {
+ errMsg = i18n("No parent found for album.");
+ return 0;
+ }
+
+ // sanity checks
+ if (name.isEmpty())
+ {
+ errMsg = i18n("Album name cannot be empty.");
+ return 0;
+ }
+
+ if (name.contains("/"))
+ {
+ errMsg = i18n("Album name cannot contain '/'.");
+ return 0;
+ }
+
+ // first check if we have another album with the same name
+ Album *child = parent->m_firstChild;
+ while (child)
+ {
+ if (child->title() == name)
+ {
+ errMsg = i18n("An existing album has the same name.");
+ return 0;
+ }
+ child = child->m_next;
+ }
+
+ TQString path = parent->folderPath();
+ path += '/' + name;
+ path = TQDir::cleanDirPath(path);
+
+ // make the directory synchronously, so that we can add the
+ // album info to the database directly
+ if (::mkdir(TQFile::encodeName(path), 0777) != 0)
+ {
+ if (errno == EEXIST)
+ errMsg = i18n("Another file or folder with same name exists");
+ else if (errno == EACCES)
+ errMsg = i18n("Access denied to path");
+ else if (errno == ENOSPC)
+ errMsg = i18n("Disk is full");
+ else
+ errMsg = i18n("Unknown error"); // being lazy
+
+ return 0;
+ }
+
+ // Now insert the album properties into the database
+ path = path.remove(0, d->libraryPath.length());
+ if (!path.startsWith("/"))
+ path.prepend("/");
+
+ int id = d->db->addAlbum(path, caption, date, collection);
+ if (id == -1)
+ {
+ errMsg = i18n("Failed to add album to database");
+ return 0;
+ }
+
+ PAlbum *album = new PAlbum(name, id, false);
+ album->m_caption = caption;
+ album->m_collection = collection;
+ album->m_date = date;
+
+ album->setParent(parent);
+
+ d->dirWatch->addDir(album->folderPath());
+
+ insertPAlbum(album);
+
+ return album;
+}
+
+bool AlbumManager::renamePAlbum(PAlbum* album, const TQString& newName,
+ TQString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootPAlbum)
+ {
+ errMsg = i18n("Cannot rename root album");
+ return false;
+ }
+
+ if (newName.contains("/"))
+ {
+ errMsg = i18n("Album name cannot contain '/'");
+ return false;
+ }
+
+ // first check if we have another sibling with the same name
+ Album *sibling = album->m_parent->m_firstChild;
+ while (sibling)
+ {
+ if (sibling->title() == newName)
+ {
+ errMsg = i18n("Another album with same name exists\n"
+ "Please choose another name");
+ return false;
+ }
+ sibling = sibling->m_next;
+ }
+
+ TQString oldURL = album->url();
+
+ KURL u = KURL::fromPathOrURL(album->folderPath()).upURL();
+ u.addPath(newName);
+ u.cleanPath();
+
+ if (::rename(TQFile::encodeName(album->folderPath()),
+ TQFile::encodeName(u.path(-1))) != 0)
+ {
+ errMsg = i18n("Failed to rename Album");
+ return false;
+ }
+
+ // now rename the album and subalbums in the database
+
+ // all we need to do is set the title of the album which is being
+ // renamed correctly and all the sub albums will automatically get
+ // their url set correctly
+
+ album->setTitle(newName);
+ d->db->setAlbumURL(album->id(), album->url());
+
+ Album* subAlbum = 0;
+ AlbumIterator it(album);
+ while ((subAlbum = it.current()) != 0)
+ {
+ d->db->setAlbumURL(subAlbum->id(), ((PAlbum*)subAlbum)->url());
+ ++it;
+ }
+
+ // Update AlbumDict. basically clear it and rebuild from scratch
+ {
+ d->pAlbumDict.clear();
+ d->pAlbumDict.insert(d->rootPAlbum->url(), d->rootPAlbum);
+ AlbumIterator it(d->rootPAlbum);
+ PAlbum* subAlbum = 0;
+ while ((subAlbum = (PAlbum*)it.current()) != 0)
+ {
+ d->pAlbumDict.insert(subAlbum->url(), subAlbum);
+ ++it;
+ }
+ }
+
+ emit signalAlbumRenamed(album);
+
+ return true;
+}
+
+bool AlbumManager::updatePAlbumIcon(PAlbum *album, TQ_LLONG iconID, TQString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootPAlbum)
+ {
+ errMsg = i18n("Cannot edit root album");
+ return false;
+ }
+
+ d->db->setAlbumIcon(album->id(), iconID);
+ album->m_icon = d->db->getAlbumIcon(album->id());
+
+ emit signalAlbumIconChanged(album);
+
+ return true;
+}
+
+TAlbum* AlbumManager::createTAlbum(TAlbum* parent, const TQString& name,
+ const TQString& iconkde, TQString& errMsg)
+{
+ if (!parent)
+ {
+ errMsg = i18n("No parent found for tag");
+ return 0;
+ }
+
+ // sanity checks
+ if (name.isEmpty())
+ {
+ errMsg = i18n("Tag name cannot be empty");
+ return 0;
+ }
+
+ if (name.contains("/"))
+ {
+ errMsg = i18n("Tag name cannot contain '/'");
+ return 0;
+ }
+
+ // first check if we have another album with the same name
+ Album *child = parent->m_firstChild;
+ while (child)
+ {
+ if (child->title() == name)
+ {
+ errMsg = i18n("Tag name already exists");
+ return 0;
+ }
+ child = child->m_next;
+ }
+
+ int id = d->db->addTag(parent->id(), name, iconkde, 0);
+ if (id == -1)
+ {
+ errMsg = i18n("Failed to add tag to database");
+ return 0;
+ }
+
+ TAlbum *album = new TAlbum(name, id, false);
+ album->m_icon = iconkde;
+ album->setParent(parent);
+
+ insertTAlbum(album);
+
+ return album;
+}
+
+AlbumList AlbumManager::findOrCreateTAlbums(const TQStringList &tagPaths)
+{
+ IntList tagIDs;
+
+ // find tag ids for tag paths in list, create if they don't exist
+ tagIDs = d->db->getTagsFromTagPaths(tagPaths);
+
+ // create TAlbum objects for the newly created tags
+ scanTAlbums();
+
+ AlbumList resultList;
+
+ for (IntList::iterator it = tagIDs.begin(); it != tagIDs.end(); ++it)
+ {
+ resultList.append(findTAlbum(*it));
+ }
+
+ return resultList;
+}
+
+bool AlbumManager::deleteTAlbum(TAlbum* album, TQString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot delete Root Tag");
+ return false;
+ }
+
+ d->db->deleteTag(album->id());
+
+ Album* subAlbum = 0;
+ AlbumIterator it(album);
+ while ((subAlbum = it.current()) != 0)
+ {
+ d->db->deleteTag(subAlbum->id());
+ ++it;
+ }
+
+ removeTAlbum(album);
+
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+
+ return true;
+}
+
+bool AlbumManager::renameTAlbum(TAlbum* album, const TQString& name,
+ TQString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot edit root tag");
+ return false;
+ }
+
+ if (name.contains("/"))
+ {
+ errMsg = i18n("Tag name cannot contain '/'");
+ return false;
+ }
+
+ // first check if we have another sibling with the same name
+ Album *sibling = album->m_parent->m_firstChild;
+ while (sibling)
+ {
+ if (sibling->title() == name)
+ {
+ errMsg = i18n("Another tag with same name exists\n"
+ "Please choose another name");
+ return false;
+ }
+ sibling = sibling->m_next;
+ }
+
+ d->db->setTagName(album->id(), name);
+ album->setTitle(name);
+ emit signalAlbumRenamed(album);
+
+ return true;
+}
+
+bool AlbumManager::moveTAlbum(TAlbum* album, TAlbum *newParent, TQString &errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot move root tag");
+ return false;
+ }
+
+ d->db->setTagParentID(album->id(), newParent->id());
+ album->parent()->removeChild(album);
+ album->setParent(newParent);
+
+ emit signalTAlbumMoved(album, newParent);
+
+ return true;
+}
+
+bool AlbumManager::updateTAlbumIcon(TAlbum* album, const TQString& iconKDE,
+ TQ_LLONG iconID, TQString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such tag");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot edit root tag");
+ return false;
+ }
+
+ d->db->setTagIcon(album->id(), iconKDE, iconID);
+ album->m_icon = d->db->getTagIcon(album->id());
+
+ emit signalAlbumIconChanged(album);
+
+ return true;
+}
+
+SAlbum* AlbumManager::createSAlbum(const KURL& url, bool simple)
+{
+ TQString name = url.queryItem("name");
+
+ // first iterate through all the search albums and see if there's an existing
+ // SAlbum with same name. (Remember, SAlbums are arranged in a flat list)
+ for (Album* album = d->rootSAlbum->firstChild(); album; album = album->next())
+ {
+ if (album->title() == name)
+ {
+ SAlbum* sa = (SAlbum*)album;
+ sa->m_kurl = url;
+ d->db->updateSearch(sa->id(), url.queryItem("name"), url);
+ return sa;
+ }
+ }
+
+ int id = d->db->addSearch(url.queryItem("name"), url);
+ if (id == -1)
+ return 0;
+
+ SAlbum* album = new SAlbum(id, url, simple, false);
+ album->setTitle(url.queryItem("name"));
+ album->setParent(d->rootSAlbum);
+
+ d->albumIntDict.insert(album->globalID(), album);
+ emit signalAlbumAdded(album);
+
+ return album;
+}
+
+bool AlbumManager::updateSAlbum(SAlbum* album, const KURL& newURL)
+{
+ if (!album)
+ return false;
+
+ d->db->updateSearch(album->id(), newURL.queryItem("name"), newURL);
+
+ TQString oldName = album->title();
+
+ album->m_kurl = newURL;
+ album->setTitle(newURL.queryItem("name"));
+ if (oldName != album->title())
+ emit signalAlbumRenamed(album);
+
+ return true;
+}
+
+bool AlbumManager::deleteSAlbum(SAlbum* album)
+{
+ if (!album)
+ return false;
+
+ emit signalAlbumDeleted(album);
+
+ d->db->deleteSearch(album->id());
+
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+
+ return true;
+}
+
+void AlbumManager::insertPAlbum(PAlbum *album)
+{
+ if (!album)
+ return;
+
+ d->pAlbumDict.insert(album->url(), album);
+ d->albumIntDict.insert(album->globalID(), album);
+
+ emit signalAlbumAdded(album);
+}
+
+void AlbumManager::removePAlbum(PAlbum *album)
+{
+ if (!album)
+ return;
+
+ // remove all children of this album
+ Album* child = album->m_firstChild;
+ while (child)
+ {
+ Album *next = child->m_next;
+ removePAlbum((PAlbum*)child);
+ child = next;
+ }
+
+ d->pAlbumDict.remove(album->url());
+ d->albumIntDict.remove(album->globalID());
+
+ d->dirtyAlbums.remove(album->url());
+ d->dirWatch->removeDir(album->folderPath());
+
+ if (album == d->currentAlbum)
+ {
+ d->currentAlbum = 0;
+ emit signalAlbumCurrentChanged(0);
+ }
+
+ emit signalAlbumDeleted(album);
+}
+
+void AlbumManager::insertTAlbum(TAlbum *album)
+{
+ if (!album)
+ return;
+
+ d->albumIntDict.insert(album->globalID(), album);
+
+ emit signalAlbumAdded(album);
+}
+
+void AlbumManager::removeTAlbum(TAlbum *album)
+{
+ if (!album)
+ return;
+
+ // remove all children of this album
+ Album* child = album->m_firstChild;
+ while (child)
+ {
+ Album *next = child->m_next;
+ removeTAlbum((TAlbum*)child);
+ child = next;
+ }
+
+ d->albumIntDict.remove(album->globalID());
+
+ if (album == d->currentAlbum)
+ {
+ d->currentAlbum = 0;
+ emit signalAlbumCurrentChanged(0);
+ }
+
+ emit signalAlbumDeleted(album);
+}
+
+void AlbumManager::emitAlbumItemsSelected(bool val)
+{
+ emit signalAlbumItemsSelected(val);
+}
+
+void AlbumManager::setItemHandler(AlbumItemHandler *handler)
+{
+ d->itemHandler = handler;
+}
+
+AlbumItemHandler* AlbumManager::getItemHandler()
+{
+ return d->itemHandler;
+}
+
+void AlbumManager::refreshItemHandler(const KURL::List& itemList)
+{
+ if (itemList.empty())
+ d->itemHandler->refresh();
+ else
+ d->itemHandler->refreshItems(itemList);
+}
+
+void AlbumManager::slotAlbumsJobResult(TDEIO::Job* job)
+{
+ d->albumListJob = 0;
+
+ if (job->error())
+ {
+ DWarning() << k_funcinfo << "Failed to list albums" << endl;
+ return;
+ }
+}
+
+void AlbumManager::slotAlbumsJobData(TDEIO::Job*, const TQByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ TQMap<int, int> albumsStatMap;
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> albumsStatMap;
+
+ emit signalPAlbumsDirty(albumsStatMap);
+}
+
+void AlbumManager::slotTagsJobResult(TDEIO::Job* job)
+{
+ d->tagListJob = 0;
+
+ if (job->error())
+ {
+ DWarning() << k_funcinfo << "Failed to list tags" << endl;
+ return;
+ }
+}
+
+void AlbumManager::slotTagsJobData(TDEIO::Job*, const TQByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ TQMap<int, int> tagsStatMap;
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> tagsStatMap;
+
+ emit signalTAlbumsDirty(tagsStatMap);
+}
+
+void AlbumManager::slotDatesJobResult(TDEIO::Job* job)
+{
+ d->dateListJob = 0;
+
+ if (job->error())
+ {
+ DWarning() << k_funcinfo << "Failed to list dates" << endl;
+ return;
+ }
+
+ emit signalAllDAlbumsLoaded();
+}
+
+void AlbumManager::slotDatesJobData(TDEIO::Job*, const TQByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ // insert all the DAlbums into a qmap for quick access
+ TQMap<TQDate, DAlbum*> mAlbumMap;
+ TQMap<int, DAlbum*> yAlbumMap;
+
+ AlbumIterator it(d->rootDAlbum);
+ while (it.current())
+ {
+ DAlbum* a = (DAlbum*)(*it);
+ if (a->range() == DAlbum::Month)
+ mAlbumMap.insert(a->date(), a);
+ else
+ yAlbumMap.insert(a->date().year(), a);
+ ++it;
+ }
+
+ TQMap<TQDateTime, int> datesStatMap;
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> datesStatMap;
+
+ TQMap<YearMonth, int> yearMonthMap;
+ for ( TQMap<TQDateTime, int>::iterator it = datesStatMap.begin();
+ it != datesStatMap.end(); ++it )
+ {
+ TQMap<YearMonth, int>::iterator it2 = yearMonthMap.find(YearMonth(it.key().date().year(), it.key().date().month()));
+ if ( it2 == yearMonthMap.end() )
+ {
+ yearMonthMap.insert( YearMonth(it.key().date().year(), it.key().date().month()), it.data() );
+ }
+ else
+ {
+ yearMonthMap.replace( YearMonth(it.key().date().year(), it.key().date().month()), it2.data() + it.data() );
+ }
+ }
+
+ int year, month;
+ for ( TQMap<YearMonth, int>::iterator it = yearMonthMap.begin();
+ it != yearMonthMap.end(); ++it )
+ {
+ year = it.key().first;
+ month = it.key().second;
+
+ TQDate md(year, month, 1);
+
+ // Do we already have this Month album
+ if (mAlbumMap.contains(md))
+ {
+ // already there. remove Month album from map
+ mAlbumMap.remove(md);
+
+ if (yAlbumMap.contains(year))
+ {
+ // already there. remove from map
+ yAlbumMap.remove(year);
+ }
+
+ continue;
+ }
+
+ // Check if Year Album already exist.
+ DAlbum *yAlbum = 0;
+ AlbumIterator it2(d->rootDAlbum);
+ while (it2.current())
+ {
+ DAlbum* a = (DAlbum*)(*it2);
+ if (a->date() == TQDate(year, 1, 1) && a->range() == DAlbum::Year)
+ {
+ yAlbum = a;
+ break;
+ }
+ ++it2;
+ }
+
+ // If no, create Year album.
+ if (!yAlbum)
+ {
+ yAlbum = new DAlbum(TQDate(year, 1, 1), false, DAlbum::Year);
+ yAlbum->setParent(d->rootDAlbum);
+ d->albumIntDict.insert(yAlbum->globalID(), yAlbum);
+ emit signalAlbumAdded(yAlbum);
+ }
+
+ // Create Month album
+ DAlbum *mAlbum = new DAlbum(md);
+ mAlbum->setParent(yAlbum);
+ d->albumIntDict.insert(mAlbum->globalID(), mAlbum);
+ emit signalAlbumAdded(mAlbum);
+ }
+
+ // Now the items contained in the maps are the ones which
+ // have been deleted.
+ for (TQMap<TQDate, DAlbum*>::iterator it = mAlbumMap.begin();
+ it != mAlbumMap.end(); ++it)
+ {
+ DAlbum* album = it.data();
+ emit signalAlbumDeleted(album);
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+ }
+
+ for (TQMap<int, DAlbum*>::iterator it = yAlbumMap.begin();
+ it != yAlbumMap.end(); ++it)
+ {
+ DAlbum* album = it.data();
+ emit signalAlbumDeleted(album);
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+ }
+
+ emit signalDAlbumsDirty(yearMonthMap);
+ emit signalDatesMapDirty(datesStatMap);
+}
+
+void AlbumManager::slotDirty(const TQString& path)
+{
+ DDebug() << "Noticed file change in directory " << path << endl;
+ TQString url = TQDir::cleanDirPath(path);
+ url = TQDir::cleanDirPath(url.remove(d->libraryPath));
+
+ if (url.isEmpty())
+ url = "/";
+
+ if (d->dirtyAlbums.contains(url))
+ return;
+
+ // is the signal for the directory containing the database file?
+ if (url == "/")
+ {
+ // retrieve modification dates
+ TQFileInfo dbFile(d->libraryPath);
+ TQValueList<TQDateTime> modList = d->buildDirectoryModList(dbFile);
+
+ // check for equality
+ if (modList == d->dbPathModificationDateList)
+ {
+ DDebug() << "Filtering out db-file-triggered dir watch signal" << endl;
+ // we can skip the signal
+ return;
+ }
+
+ // set new list
+ d->dbPathModificationDateList = modList;
+ }
+
+ d->dirtyAlbums.append(url);
+
+ if (DIO::running())
+ return;
+
+ KURL u;
+ u.setProtocol("digikamalbums");
+ u.setPath(d->dirtyAlbums.first());
+ d->dirtyAlbums.pop_front();
+
+ DIO::scan(u);
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albummanager.h b/src/digikam/albummanager.h
new file mode 100644
index 00000000..3244f5a2
--- /dev/null
+++ b/src/digikam/albummanager.h
@@ -0,0 +1,472 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-15
+ * Description : Albums manager interface.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file albummanager.h */
+
+#ifndef ALBUMMANAGER_H
+#define ALBUMMANAGER_H
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqobject.h>
+#include <tqstring.h>
+#include <tqvaluelist.h>
+#include <tqmap.h>
+#include <tqptrlist.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQDate;
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace Digikam
+{
+
+class Album;
+class PAlbum;
+class TAlbum;
+class DAlbum;
+class SAlbum;
+class AlbumDB;
+class AlbumItemHandler;
+class SplashScreen;
+class AlbumManagerPriv;
+
+typedef TQValueList<Album*> AlbumList;
+typedef TQPair<int, int> YearMonth;
+
+/**
+ * \class AlbumManager
+ *
+ * There are two primary managers which manage the listing and
+ * lifetime of Album and ImageInfo: AlbumManager and AlbumLister
+ *
+ * AlbumManager manages albums: does listing of albums and controls the lifetime of it.
+ * For PAlbums and TAlbums, the listing is done by reading the db directly and
+ * building the hierarchy of the albums. For DAlbums, since the listing takes
+ * time, the work is delegated to a tdeioslave. Interested frontend entities can
+ * connect to the albummanager to receive notifications of new Albums, when
+ * Albums are deleted and when the current album is changed.
+ *
+ * Additional operations are provided for: creating/deleting/rename Albums,
+ * updating icons and moving Albums.
+ *
+ */
+class DIGIKAM_EXPORT AlbumManager : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ */
+ AlbumManager();
+
+ /**
+ * Destructor
+ */
+ ~AlbumManager();
+
+ /**
+ * A convenience function to get the instance of the AlbumManager
+ */
+ static AlbumManager* instance();
+
+ /**
+ * returns a pointer to the current AlbumDB
+ */
+ AlbumDB* albumDB();
+
+ /** @name Library path And Scanning
+ */
+ //@{
+ /**
+ * Set the @p libraryPath to the given path
+ *
+ * If the libraryPath is the same as the current path, nothing happens.
+ * Otherwise the currently listed albums are cleared. The albums in the new
+ * library path are not listed till you call startScan()
+ * @param path the new libraryPath
+ * @see startScan
+ */
+ void setLibraryPath(const TQString& path, SplashScreen *splash=0);
+
+ /**
+ * @return the current libraryPath
+ */
+ TQString getLibraryPath() const;
+
+ /**
+ * starts scanning the libraryPath and listing the albums. If the
+ * libraryPath has not changed since the lastscan, then nothing happens
+ * @see setLibraryPath
+ * @see refresh
+ */
+ void startScan();
+
+ /**
+ * This is similar to startScan, except that it assumes you have run
+ * startScan at least once. It checks the database to see if any new albums
+ * have been added and updates them accordingly. Use this when a change in the
+ * filesystem is detected (but the album library path hasn't changed)
+ * @see startScan
+ */
+ void refresh();
+ //@}
+
+ /** @name List of Albums and current Album
+ */
+ //@{
+ /**
+ * @return a list of all PAlbums
+ */
+ AlbumList allPAlbums() const;
+
+ /**
+ * @return a list of all TAlbums
+ */
+ AlbumList allTAlbums() const;
+
+ /**
+ * @return a list of all SAlbums
+ */
+ AlbumList allSAlbums() const;
+
+ /**
+ * @return a list of all SAlbums
+ */
+ AlbumList allDAlbums() const;
+
+ /**
+ * set the current album to @p album. Call this from views which show
+ * listing of albums. This also causes it to fire the signal
+ * signalAlbumCurrentChanged()
+ */
+ void setCurrentAlbum(Album *album);
+
+ /**
+ * @returns the current albumm
+ */
+ Album* currentAlbum() const;
+ //@}
+
+ /** @name Finding Albums
+ */
+ //@{
+ /**
+ * Given a complete file url (kde url with file protocol), it will try to find
+ * a PAlbum corresponding to it.
+ * \warning This should not be used, unless really necessary
+ * @return PAlbum correspoding to supplied @p url
+ * @param url the url we need to check
+ */
+ PAlbum* findPAlbum(const KURL& url) const;
+
+ /**
+ * @return a PAlbum with given ID
+ * @param id the id for the PAlbum
+ */
+ PAlbum* findPAlbum(int id) const;
+
+ /**
+ * @return a TAlbum with given ID
+ * @param id the id for the TAlbum
+ */
+ TAlbum* findTAlbum(int id) const;
+
+ /**
+ * @return a SAlbum with given ID
+ * @param id the id for the SAlbum
+ */
+ SAlbum* findSAlbum(int id) const;
+
+ /**
+ * @return a DAlbum with given ID
+ * @param id the id for the DAlbum
+ */
+ DAlbum* findDAlbum(int id) const;
+
+ /**
+ * @return a Album with the given globalID
+ * @param gid the global id for the album
+ */
+ Album* findAlbum(int gid) const;
+
+ /**
+ * @return a TAlbum with given tag path, or 0 if not found
+ * @param tagPath the tag path ("People/Friend/John")
+ */
+ TAlbum* findTAlbum(const TQString &tagPath) const;
+ //@}
+
+ /** @name Operations on PAlbum
+ */
+ //@{
+ /**
+ * Create a new PAlbum with supplied properties as a child of the parent
+ * This is equivalent to creating a new folder on the disk with supplied
+ * name in the parent's folder path. Also the supplied attributes are written
+ * out to the database
+ * \note the signalAlbumAdded will be fired before this function returns. Its
+ * recommended to connect to that signal to get notification of new album added
+ * @return the newly created PAlbum or 0 if it fails
+ * @param parent the parent album under which to create the new Album
+ * @param name the name of the new album
+ * @param caption the caption for the new album
+ * @param date the date for the new album
+ * @param collection the collection for the new album
+ * @param errMsg this will contain the error message describing why the
+ * operation failed
+ */
+ PAlbum* createPAlbum(PAlbum* parent, const TQString& name,
+ const TQString& caption, const TQDate& date,
+ const TQString& collection,
+ TQString& errMsg);
+
+ /**
+ * Renames a PAlbum. This is equivalent to actually renaming the corresponding
+ * folder on the disk.
+ * @return true if the operation succeeds, false otherwise
+ * @param album the Album which should be renamed
+ * @param newName the new name for the album
+ * @param errMsg this will contain the error message describing why the
+ * operation failed
+ */
+ bool renamePAlbum(PAlbum* album, const TQString& newName,
+ TQString& errMsg);
+
+ /**
+ * Update the icon for an album. The @p icon is the name (and not full path)
+ * of the file in the album
+ * @return true if the operation succeeds, false otherwise
+ * @param album the album for which icon should be changed
+ * @param iconID the filename of the new icon
+ * @param errMsg if the operation fails, this will contain the error message
+ * describing why the operation failed
+ */
+ bool updatePAlbumIcon(PAlbum *album, TQ_LLONG iconID, TQString& errMsg);
+ //@}
+
+ /** @name Operations on TAlbum
+ */
+ //@{
+ /**
+ * Create a new TAlbum with supplied properties as a child of the parent
+ * The tag is added to the database
+ * \note the signalAlbumAdded will be fired before this function returns. Its
+ * recommended to connect to that signal to get notification of new album added
+ * @return the newly created TAlbum or 0 if it fails
+ * @param parent the parent album under which to create the new Album
+ * @param name the name of the new album
+ * @param iconkde the iconkde for the new album (this is a filename which
+ * kde iconloader can load up
+ * @param errMsg this will contain the error message describing why the
+ * operation failed
+ */
+ TAlbum* createTAlbum(TAlbum* parent, const TQString& name,
+ const TQString& iconkde, TQString& errMsg);
+
+ /**
+ * A list of tag paths is supplied.
+ * If no corresponding TAlbum exists, a new one will be created.
+ * @param tagPath A list of tag paths
+ * @returns A list of all TAlbums for the list (already existing or newly created)
+ */
+ AlbumList findOrCreateTAlbums(const TQStringList &tagPaths);
+
+ /**
+ * Delete a TAlbum.
+ * The tag is removed from the database
+ * \note the signalAlbumDeleted will be fired before this function returns. Its
+ * recommended to connect to that signal to get notification of album deletes
+ * @return true if the operation succeeds or false otherwise
+ * @param album the TAlbum to delete
+ * @param errMsg this will contain the error message describing why the
+ * operation failed
+ */
+ bool deleteTAlbum(TAlbum* album, TQString& errMsg);
+
+ /**
+ * Renames a TAlbum.
+ * This updates the tag name in the database
+ * @return true if the operation succeeds, false otherwise
+ * @param album the Album which should be renamed
+ * @param name the new name for the album
+ * @param errMsg this will contain the error message describing why the
+ * operation failed
+ */
+ bool renameTAlbum(TAlbum* album, const TQString& name, TQString& errMsg);
+
+ /**
+ * Move a TAlbum to a new parent.
+ * This updates the tag parent ID in the database
+ * @return true if the operation succeeds, false otherwise
+ * @param album the Album which should be moved
+ * @param newParent the Parent Album to which album should be moved
+ * @param errMsg this will contain the error message describing why the
+ * operation failed
+ */
+ bool moveTAlbum(TAlbum* album, TAlbum *newParent, TQString &errMsg);
+
+ /**
+ * Update the icon for a TAlbum.
+ * @return true if the operation succeeds, false otherwise
+ * @param album the album for which icon should be changed
+ * @param iconKDE a simple filename which can be loaded by TDEIconLoader
+ * @param iconID id of the icon image file
+ * @param errMsg this will contain the error message describing why the
+ * operation failed
+ * \note if iconKDE is not empty then iconID is used. So if you want to set
+ * the icon to a file which can be loaded by TDEIconLoader, pass it in as
+ * iconKDE. otherwise pass a null TQString to iconKDE and set iconID
+ */
+ bool updateTAlbumIcon(TAlbum* album, const TQString& iconKDE,
+ TQ_LLONG iconID, TQString& errMsg);
+ //@}
+
+ /** @name Operations on SAlbum
+ */
+ //@{
+ /**
+ * Create a new SAlbum with supplied url. If an existing SAlbum with same name
+ * exists this function will return a pointer to that album, instead of creating
+ * a new one. A newly created search album is added to the database. For an
+ * existing SAlbum, the url is updated and written out to the database
+ * \note the signalAlbumAdded will be fired before this function returns. Its
+ * recommended to connect to that signal to get notification of new album added
+ * @return the newly created SAlbum or an existing SAlbum with same name
+ * @param url the url of the album
+ * @param simple indicates whether the Search album is of simple type or
+ * extended type
+ */
+ SAlbum* createSAlbum(const KURL& url, bool simple);
+
+ /**
+ * Update the url for a SAlbum
+ * @return true if the operation succeeds, false otherwise
+ * @param album the album to update
+ * @param newURL the new url of the album
+ */
+ bool updateSAlbum(SAlbum* album, const KURL& newURL);
+
+ /**
+ * Delete a SAlbum from the database
+ * \note the signalAlbumDeleted will be fired before this function returns. Its
+ * recommended to connect to that signal to get notification of album deletes
+ * @return true if the operation succeeds, false otherwise
+ * @param album the album to delete
+ */
+ bool deleteSAlbum(SAlbum* album);
+ //@}
+
+ void setItemHandler(AlbumItemHandler *handler);
+ AlbumItemHandler* getItemHandler();
+ void refreshItemHandler(const KURL::List& itemList=KURL::List());
+ void emitAlbumItemsSelected(bool val);
+
+signals:
+
+ void signalAlbumAdded(Album* album);
+ void signalAlbumDeleted(Album* album);
+ void signalAlbumItemsSelected(bool selected);
+ void signalAlbumsCleared();
+ void signalAlbumCurrentChanged(Album* album);
+ void signalAllAlbumsLoaded();
+ void signalAllDAlbumsLoaded();
+ void signalAlbumIconChanged(Album* album);
+ void signalAlbumRenamed(Album* album);
+ void signalTAlbumMoved(TAlbum* album, TAlbum* newParent);
+ void signalPAlbumDirty(PAlbum* album);
+ void signalPAlbumsDirty(const TQMap<int, int>&);
+ void signalTAlbumsDirty(const TQMap<int, int>&);
+ void signalDAlbumsDirty(const TQMap<YearMonth, int>&);
+ void signalDatesMapDirty(const TQMap<TQDateTime, int>&);
+
+private slots:
+
+ void slotDatesJobResult(TDEIO::Job* job);
+ void slotDatesJobData(TDEIO::Job* job, const TQByteArray& data);
+ void slotAlbumsJobResult(TDEIO::Job* job);
+ void slotAlbumsJobData(TDEIO::Job* job, const TQByteArray& data);
+ void slotTagsJobResult(TDEIO::Job* job);
+ void slotTagsJobData(TDEIO::Job* job, const TQByteArray& data);
+ void slotDirty(const TQString& path);
+
+private:
+
+ void insertPAlbum(PAlbum *album);
+ void removePAlbum(PAlbum *album);
+ void insertTAlbum(TAlbum *album);
+ void removeTAlbum(TAlbum *album);
+
+ /**
+ * Scan albums directly from database and creates new PAlbums
+ * It only create those PAlbums which haven't already been
+ * created
+ */
+ void scanPAlbums();
+
+ /**
+ * Scan tags directly from database and creates new TAlbums
+ * It only create those TAlbums which haven't already been
+ * created
+ */
+ void scanTAlbums();
+
+ /**
+ * Scan searches directly from database and creates new SAlbums
+ * It only create those SAlbums which haven't already been
+ * created
+ */
+ void scanSAlbums();
+
+ /**
+ * Makes use of a TDEIO::Job to list dates from the database
+ * @see slotResult
+ * @see slotData
+ */
+ void scanDAlbums();
+
+private:
+
+ static AlbumManager *m_instance;
+
+ AlbumManagerPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMMANAGER_H */
diff --git a/src/digikam/albumpropsedit.cpp b/src/digikam/albumpropsedit.cpp
new file mode 100644
index 00000000..703d8c4a
--- /dev/null
+++ b/src/digikam/albumpropsedit.cpp
@@ -0,0 +1,376 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-03-09
+ * Description : Album properties dialog.
+ *
+ * Copyright (C) 2003-2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqdatetime.h>
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbox.h>
+#include <tqheader.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqlistview.h>
+#include <tqpushbutton.h>
+#include <tqregexp.h>
+#include <tqvalidator.h>
+
+// KDE includes.
+
+#include <kdatepicker.h>
+#include <ktextedit.h>
+#include <klineedit.h>
+#include <tdelocale.h>
+#include <kurl.h>
+#include <tdemessagebox.h>
+#include <kcursor.h>
+
+#include <tdeversion.h>
+#if KDE_IS_VERSION(3,2,0)
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// Local includes.
+
+#include "album.h"
+#include "albumdb.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "albumpropsedit.h"
+#include "albumpropsedit.moc"
+
+namespace Digikam
+{
+
+class AlbumPropsEditPriv
+{
+
+public:
+
+ AlbumPropsEditPriv()
+ {
+ titleEdit = 0;
+ collectionCombo = 0;
+ commentsEdit = 0;
+ datePicker = 0;
+ album = 0;
+ }
+
+ TQStringList albumCollections;
+
+ TQComboBox *collectionCombo;
+
+ KLineEdit *titleEdit;
+
+ KTextEdit *commentsEdit;
+
+ KDatePicker *datePicker;
+
+ PAlbum *album;
+};
+
+AlbumPropsEdit::AlbumPropsEdit(PAlbum* album, bool create)
+ : KDialogBase( Plain, create ? i18n("New Album") : i18n("Edit Album"),
+ Help|Ok|Cancel, Ok,
+ 0, 0, true, true )
+{
+ d = new AlbumPropsEditPriv;
+ d->album = album;
+ setHelp("albumpropsedit.anchor", "digikam");
+
+ TQGridLayout *topLayout = new TQGridLayout( plainPage(), 2, 6,
+ 0, spacingHint() );
+
+ TQLabel *topLabel = new TQLabel( plainPage() );
+ if (create)
+ {
+ topLabel->setText( i18n( "<qt><b>Create new Album in \"<i>%1</i>\"</b></qt>")
+ .arg(album->title()));
+ }
+ else
+ {
+ topLabel->setText( i18n( "<qt><b>\"<i>%1</i>\" Album Properties</b></qt>")
+ .arg(album->title()));
+ }
+ topLabel->setAlignment(TQt::AlignAuto | TQt::AlignVCenter | TQt::SingleLine);
+ topLayout->addMultiCellWidget( topLabel, 0, 0, 0, 1 );
+
+ // --------------------------------------------------------
+
+ TQFrame *topLine = new TQFrame( plainPage() );
+ topLine->setFrameShape( TQFrame::HLine );
+ topLine->setFrameShadow( TQFrame::Sunken );
+ topLayout->addMultiCellWidget( topLine, 1, 1, 0, 1 );
+
+ // --------------------------------------------------------
+
+ TQLabel *titleLabel = new TQLabel( plainPage( ) );
+ titleLabel->setText( i18n( "&Title:" ) );
+ topLayout->addWidget( titleLabel, 2, 0 );
+
+ d->titleEdit = new KLineEdit( plainPage( ) );
+ topLayout->addWidget( d->titleEdit, 2, 1 );
+ titleLabel->setBuddy( d->titleEdit );
+
+ TQRegExp titleRx("[^/]+");
+ TQValidator *titleValidator = new TQRegExpValidator(titleRx, this);
+ d->titleEdit->setValidator(titleValidator);
+
+ TQLabel *collectionLabel = new TQLabel( plainPage( ) );
+ collectionLabel->setText( i18n( "Co&llection:" ) );
+ topLayout->addWidget( collectionLabel, 3, 0 );
+
+ d->collectionCombo = new TQComboBox( plainPage( ) );
+ d->collectionCombo->setEditable(true);
+ topLayout->addWidget( d->collectionCombo, 3, 1 );
+ collectionLabel->setBuddy( d->collectionCombo );
+
+ TQLabel *commentsLabel = new TQLabel( plainPage( ) );
+ commentsLabel->setText( i18n( "Ca&ption:" ) );
+ topLayout->addWidget( commentsLabel, 4, 0, TQt::AlignAuto|TQt::AlignTop );
+
+ d->commentsEdit = new KTextEdit( plainPage( ) );
+ topLayout->addWidget( d->commentsEdit, 4, 1 );
+ commentsLabel->setBuddy( d->commentsEdit );
+ d->commentsEdit->setCheckSpellingEnabled(true);
+ d->commentsEdit->setWordWrap(TQTextEdit::WidgetWidth);
+ d->commentsEdit->setWrapPolicy(TQTextEdit::AtWhiteSpace);
+
+ TQLabel *dateLabel = new TQLabel( plainPage( ) );
+ dateLabel->setText( i18n( "Album &date:" ) );
+ topLayout->addWidget( dateLabel, 5, 0, TQt::AlignAuto|TQt::AlignTop );
+
+ d->datePicker = new KDatePicker( plainPage( ) );
+ topLayout->addWidget( d->datePicker, 5, 1 );
+ dateLabel->setBuddy( d->datePicker );
+
+ TQHBox *buttonRow = new TQHBox( plainPage( ) );
+ TQPushButton *dateLowButton = new TQPushButton(
+ i18n("Selects the date of the oldest image",
+ "&Oldest" ), buttonRow );
+ TQPushButton *dateAvgButton = new TQPushButton(
+ i18n("Calculates the average date",
+ "&Average" ), buttonRow );
+ TQPushButton *dateHighButton = new TQPushButton(
+ i18n("Selects the date of the newest image",
+ "Newest" ), buttonRow );
+
+ topLayout->addWidget( buttonRow, 6, 1);
+
+ setTabOrder(d->titleEdit, d->collectionCombo);
+ setTabOrder(d->collectionCombo, d->commentsEdit);
+ setTabOrder(d->commentsEdit, d->datePicker);
+ d->commentsEdit->setTabChangesFocus(true);
+ d->titleEdit->selectAll();
+ d->titleEdit->setFocus();
+
+ // Initialize ---------------------------------------------
+
+ AlbumSettings *settings = AlbumSettings::instance();
+ if (settings)
+ {
+ d->collectionCombo->insertItem( TQString() );
+ TQStringList collections = settings->getAlbumCollectionNames();
+ d->collectionCombo->insertStringList( collections );
+ int collectionIndex = collections.findIndex( album->collection() );
+
+ if ( collectionIndex != -1 )
+ {
+ // + 1 because of the empty item
+ d->collectionCombo->setCurrentItem(collectionIndex + 1);
+ }
+ }
+
+ if (create)
+ {
+ d->titleEdit->setText( i18n("New Album") );
+ d->datePicker->setDate( TQDate::currentDate() );
+ }
+ else
+ {
+ d->titleEdit->setText( album->title() );
+ d->commentsEdit->setText( album->caption() );
+ d->datePicker->setDate( album->date() );
+ }
+
+ // -- slots connections -------------------------------------------
+
+ connect(d->titleEdit, TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotTitleChanged(const TQString&)));
+
+ connect(dateLowButton, TQ_SIGNAL( clicked() ),
+ this, TQ_SLOT( slotDateLowButtonClicked()));
+
+ connect(dateAvgButton, TQ_SIGNAL( clicked() ),
+ this, TQ_SLOT( slotDateAverageButtonClicked()));
+
+ connect(dateHighButton, TQ_SIGNAL( clicked() ),
+ this, TQ_SLOT( slotDateHighButtonClicked()));
+
+ adjustSize();
+}
+
+AlbumPropsEdit::~AlbumPropsEdit()
+{
+ delete d;
+}
+
+TQString AlbumPropsEdit::title() const
+{
+ return d->titleEdit->text();
+}
+
+TQString AlbumPropsEdit::comments() const
+{
+ return d->commentsEdit->text();
+}
+
+TQDate AlbumPropsEdit::date() const
+{
+ return d->datePicker->date();
+}
+
+TQString AlbumPropsEdit::collection() const
+{
+ TQString name = d->collectionCombo->currentText();
+
+ if (name.isEmpty())
+ {
+ name = i18n( "Uncategorized Album" );
+ }
+
+ return name;
+}
+
+TQStringList AlbumPropsEdit::albumCollections() const
+{
+ TQStringList collections;
+ AlbumSettings *settings = AlbumSettings::instance();
+ if (settings)
+ {
+ collections = settings->getAlbumCollectionNames();
+ }
+
+ TQString currentCollection = d->collectionCombo->currentText();
+ if ( collections.findIndex( currentCollection ) == -1 )
+ {
+ collections.append(currentCollection);
+ }
+
+ collections.sort();
+ return collections;
+}
+
+bool AlbumPropsEdit::editProps(PAlbum *album, TQString& title,
+ TQString& comments, TQDate& date, TQString& collection,
+ TQStringList& albumCollections)
+{
+ AlbumPropsEdit dlg(album);
+
+ bool ok = dlg.exec() == TQDialog::Accepted;
+
+ title = dlg.title();
+ comments = dlg.comments();
+ date = dlg.date();
+ collection = dlg.collection();
+ albumCollections = dlg.albumCollections();
+
+ return ok;
+}
+
+bool AlbumPropsEdit::createNew(PAlbum *parent,
+ TQString& title,
+ TQString& comments,
+ TQDate& date,
+ TQString& collection,
+ TQStringList& albumCollections)
+{
+ AlbumPropsEdit dlg(parent, true);
+
+ bool ok = dlg.exec() == TQDialog::Accepted;
+
+ title = dlg.title();
+ comments = dlg.comments();
+ date = dlg.date();
+ collection = dlg.collection();
+ albumCollections = dlg.albumCollections();
+
+ return ok;
+}
+
+void AlbumPropsEdit::slotTitleChanged(const TQString& newtitle)
+{
+ enableButtonOK(!newtitle.isEmpty());
+}
+
+void AlbumPropsEdit::slotDateLowButtonClicked()
+{
+ setCursor( KCursor::waitCursor() );
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQDate avDate = db->getAlbumLowestDate( d->album->id() );
+ setCursor( KCursor::arrowCursor() );
+
+ if ( avDate.isValid() )
+ d->datePicker->setDate( avDate );
+}
+
+void AlbumPropsEdit::slotDateHighButtonClicked()
+{
+ setCursor( KCursor::waitCursor() );
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQDate avDate = db->getAlbumHighestDate( d->album->id() );
+ setCursor( KCursor::arrowCursor() );
+
+ if ( avDate.isValid() )
+ d->datePicker->setDate( avDate );
+}
+
+void AlbumPropsEdit::slotDateAverageButtonClicked()
+{
+ setCursor( KCursor::waitCursor() );
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQDate avDate = db->getAlbumAverageDate( d->album->id() );
+ setCursor( KCursor::arrowCursor() );
+
+ if ( avDate.isValid() )
+ d->datePicker->setDate( avDate );
+ else
+ KMessageBox::error( plainPage( ),
+ i18n( "Could not calculate an average."),
+ i18n( "Could Not Calculate Average" ) );
+}
+
+} // namespace Digikam
+
+
diff --git a/src/digikam/albumpropsedit.h b/src/digikam/albumpropsedit.h
new file mode 100644
index 00000000..cd8f7564
--- /dev/null
+++ b/src/digikam/albumpropsedit.h
@@ -0,0 +1,88 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-03-09
+ * Description : Album properties dialog.
+ *
+ * Copyright (C) 2003-2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMPROPSEDIT_H
+#define ALBUMPROPSEDIT_H
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqstring.h>
+#include <tqstringlist.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+namespace Digikam
+{
+
+class PAlbum;
+class AlbumPropsEditPriv;
+
+class AlbumPropsEdit : public KDialogBase
+{
+ TQ_OBJECT
+
+public:
+
+ AlbumPropsEdit(PAlbum* album, bool create=false);
+ ~AlbumPropsEdit();
+
+ TQString title() const;
+ TQString comments() const;
+ TQDate date() const;
+ TQString collection() const;
+ TQStringList albumCollections() const;
+
+ static bool editProps(PAlbum *album,
+ TQString& title,
+ TQString& comments,
+ TQDate& date,
+ TQString& collection,
+ TQStringList& albumCollections);
+
+ static bool createNew(PAlbum *parent,
+ TQString& title,
+ TQString& comments,
+ TQDate& date,
+ TQString& collection,
+ TQStringList& albumCollections);
+
+private slots:
+
+ void slotTitleChanged(const TQString& newtitle);
+ void slotDateLowButtonClicked();
+ void slotDateAverageButtonClicked();
+ void slotDateHighButtonClicked();
+
+private:
+
+ AlbumPropsEditPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMPROPSEDIT_H */
diff --git a/src/digikam/albumsettings.cpp b/src/digikam/albumsettings.cpp
new file mode 100644
index 00000000..6b7d4315
--- /dev/null
+++ b/src/digikam/albumsettings.cpp
@@ -0,0 +1,1142 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-16-10
+ * Description : albums settings interface
+ *
+ * Copyright (C) 2003-2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <kdebug.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "thumbnailsize.h"
+#include "mimefilter.h"
+#include "albumlister.h"
+#include "albumsettings.h"
+
+namespace Digikam
+{
+
+class AlbumSettingsPrivate
+{
+
+public:
+
+ bool showSplash;
+ bool useTrash;
+ bool showTrashDeleteDialog;
+ bool sidebarApplyDirectly;
+ bool scanAtStart;
+ bool recursiveAlbums;
+ bool recursiveTags;
+
+ bool iconShowName;
+ bool iconShowSize;
+ bool iconShowDate;
+ bool iconShowModDate;
+ bool iconShowComments;
+ bool iconShowResolution;
+ bool iconShowTags;
+ bool iconShowRating;
+
+ bool showToolTips;
+ bool tooltipShowFileName;
+ bool tooltipShowFileDate;
+ bool tooltipShowFileSize;
+ bool tooltipShowImageType;
+ bool tooltipShowImageDim;
+ bool tooltipShowPhotoMake;
+ bool tooltipShowPhotoDate;
+ bool tooltipShowPhotoFocal;
+ bool tooltipShowPhotoExpo;
+ bool tooltipShowPhotoMode;
+ bool tooltipShowPhotoFlash;
+ bool tooltipShowPhotoWb;
+ bool tooltipShowAlbumName;
+ bool tooltipShowComments;
+ bool tooltipShowTags;
+ bool tooltipShowRating;
+
+ bool exifRotate;
+ bool exifSetOrientation;
+
+ bool saveIptcTags;
+ bool saveIptcPhotographerId;
+ bool saveIptcCredits;
+
+ bool saveComments;
+ bool saveDateTime;
+ bool saveRating;
+
+ bool previewLoadFullImageSize;
+
+ bool showFolderTreeViewItemsCount;
+
+ int thumbnailSize;
+ int treeThumbnailSize;
+ int ratingFilterCond;
+
+ TQString currentTheme;
+ TQString albumLibraryPath;
+ TQString imageFilefilter;
+ TQString movieFilefilter;
+ TQString audioFilefilter;
+ TQString rawFilefilter;
+ TQString defaultImageFilefilter;
+ TQString defaultMovieFilefilter;
+ TQString defaultAudioFilefilter;
+ TQString defaultRawFilefilter;
+
+ TQString author;
+ TQString authorTitle;
+ TQString credit;
+ TQString source;
+ TQString copyright;
+
+ TQStringList albumCollectionNames;
+
+ TDEConfig *config;
+
+ AlbumSettings::AlbumSortOrder albumSortOrder;
+ AlbumSettings::ImageSortOrder imageSortOrder;
+ AlbumSettings::ItemRightClickAction itemRightClickAction;
+};
+
+
+AlbumSettings* AlbumSettings::m_instance = 0;
+
+AlbumSettings* AlbumSettings::instance()
+{
+ return m_instance;
+}
+
+AlbumSettings::AlbumSettings()
+{
+ d = new AlbumSettingsPrivate;
+ d->config = kapp->config();
+ m_instance = this;
+ init();
+}
+
+AlbumSettings::~AlbumSettings()
+{
+ delete d;
+ m_instance = 0;
+}
+
+void AlbumSettings::init()
+{
+ d->albumCollectionNames.clear();
+ d->albumCollectionNames.append(i18n("Family"));
+ d->albumCollectionNames.append(i18n("Travel"));
+ d->albumCollectionNames.append(i18n("Holidays"));
+ d->albumCollectionNames.append(i18n("Friends"));
+ d->albumCollectionNames.append(i18n("Nature"));
+ d->albumCollectionNames.append(i18n("Party"));
+ d->albumCollectionNames.append(i18n("Todo"));
+ d->albumCollectionNames.append(i18n("Miscellaneous"));
+ d->albumCollectionNames.sort();
+
+ d->albumSortOrder = AlbumSettings::ByFolder;
+ d->imageSortOrder = AlbumSettings::ByIName;
+ d->itemRightClickAction = AlbumSettings::ShowPreview;
+
+ d->defaultImageFilefilter = "*.jpg *.jpeg *.jpe " // JPEG
+ "*.jp2 *.jpx *.jpc *.pgx " // JPEG-2000
+ "*.tif *.tiff " // TIFF
+ "*.png *.gif *.bmp *.xpm *.ppm *.pnm *.xcf *.pcx";
+
+ d->defaultMovieFilefilter = "*.mpeg *.mpg *.mpo *.mpe " // MPEG
+ "*.avi *.mov *.wmf *.asf *.mp4 *.3gp";
+
+ d->defaultAudioFilefilter = "*.ogg *.mp3 *.wma *.wav";
+
+ // RAW files estentions supported by dcraw program and
+ // defines to digikam/libs/dcraw/rawfiles.h
+#if KDCRAW_VERSION < 0x000106
+ d->defaultRawFilefilter = TQString(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ d->defaultRawFilefilter = TQString(KDcrawIface::KDcraw::rawFiles());
+#endif
+
+ d->imageFilefilter = d->defaultImageFilefilter;
+ d->movieFilefilter = d->defaultMovieFilefilter;
+ d->audioFilefilter = d->defaultAudioFilefilter;
+ d->rawFilefilter = d->defaultRawFilefilter;
+
+ d->thumbnailSize = ThumbnailSize::Medium;
+ d->treeThumbnailSize = 22;
+
+ d->ratingFilterCond = AlbumLister::GreaterEqualCondition;
+
+ d->showToolTips = true;
+ d->showSplash = true;
+ d->useTrash = true;
+ d->showTrashDeleteDialog = true;
+ d->sidebarApplyDirectly = false;
+
+ d->iconShowName = false;
+ d->iconShowSize = false;
+ d->iconShowDate = true;
+ d->iconShowModDate = true;
+ d->iconShowComments = true;
+ d->iconShowResolution = false;
+ d->iconShowTags = true;
+ d->iconShowRating = true;
+
+ d->tooltipShowFileName = true;
+ d->tooltipShowFileDate = false;
+ d->tooltipShowFileSize = false;
+ d->tooltipShowImageType = false;
+ d->tooltipShowImageDim = true;
+ d->tooltipShowPhotoMake = true;
+ d->tooltipShowPhotoDate = true;
+ d->tooltipShowPhotoFocal = true;
+ d->tooltipShowPhotoExpo = true;
+ d->tooltipShowPhotoMode = true;
+ d->tooltipShowPhotoFlash = false;
+ d->tooltipShowPhotoWb = false;
+ d->tooltipShowAlbumName = false;
+ d->tooltipShowComments = true;
+ d->tooltipShowTags = true;
+ d->tooltipShowRating = true;
+
+ d->exifRotate = true;
+ d->exifSetOrientation = true;
+
+ d->saveIptcTags = false;
+ d->saveIptcPhotographerId = false;
+ d->saveIptcCredits = false;
+
+ d->saveComments = false;
+ d->saveDateTime = false;
+ d->saveRating = false;
+
+ d->previewLoadFullImageSize = false;
+
+ d->recursiveAlbums = false;
+ d->recursiveTags = true;
+
+ d->showFolderTreeViewItemsCount = false;
+}
+
+void AlbumSettings::readSettings()
+{
+ TDEConfig* config = d->config;
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("Album Settings");
+
+ d->albumLibraryPath = config->readPathEntry("Album Path", TQString());
+
+ TQStringList collectionList = config->readListEntry("Album Collections");
+ if (!collectionList.isEmpty())
+ {
+ collectionList.sort();
+ d->albumCollectionNames = collectionList;
+ }
+
+ d->albumSortOrder = AlbumSettings::AlbumSortOrder(config->readNumEntry("Album Sort Order",
+ (int)AlbumSettings::ByFolder));
+
+ d->imageSortOrder = AlbumSettings::ImageSortOrder(config->readNumEntry("Image Sort Order",
+ (int)AlbumSettings::ByIName));
+
+ d->itemRightClickAction = AlbumSettings::ItemRightClickAction(config->readNumEntry(
+ "Item Right Click Action",
+ (int)AlbumSettings::ShowPreview));
+
+ d->imageFilefilter = config->readEntry("File Filter", d->imageFilefilter);
+ d->movieFilefilter = config->readEntry("Movie File Filter", d->movieFilefilter);
+ d->audioFilefilter = config->readEntry("Audio File Filter", d->audioFilefilter);
+ d->rawFilefilter = config->readEntry("Raw File Filter", d->rawFilefilter);
+ d->thumbnailSize = config->readNumEntry("Default Icon Size", ThumbnailSize::Medium);
+ d->treeThumbnailSize = config->readNumEntry("Default Tree Icon Size", 22);
+ d->currentTheme = config->readEntry("Theme", i18n("Default"));
+
+ d->ratingFilterCond = config->readNumEntry("Rating Filter Condition",
+ AlbumLister::GreaterEqualCondition);
+
+ d->iconShowName = config->readBoolEntry("Icon Show Name", false);
+ d->iconShowResolution = config->readBoolEntry("Icon Show Resolution", false);
+ d->iconShowSize = config->readBoolEntry("Icon Show Size", false);
+ d->iconShowDate = config->readBoolEntry("Icon Show Date", true);
+ d->iconShowModDate = config->readBoolEntry("Icon Show Modification Date", true);
+ d->iconShowComments = config->readBoolEntry("Icon Show Comments", true);
+ d->iconShowTags = config->readBoolEntry("Icon Show Tags", true);
+ d->iconShowRating = config->readBoolEntry("Icon Show Rating", true);
+
+ d->showToolTips = config->readBoolEntry("Show ToolTips", false);
+ d->tooltipShowFileName = config->readBoolEntry("ToolTips Show File Name", true);
+ d->tooltipShowFileDate = config->readBoolEntry("ToolTips Show File Date", false);
+ d->tooltipShowFileSize = config->readBoolEntry("ToolTips Show File Size", false);
+ d->tooltipShowImageType = config->readBoolEntry("ToolTips Show Image Type", false);
+ d->tooltipShowImageDim = config->readBoolEntry("ToolTips Show Image Dim", true);
+ d->tooltipShowPhotoMake = config->readBoolEntry("ToolTips Show Photo Make", true);
+ d->tooltipShowPhotoDate = config->readBoolEntry("ToolTips Show Photo Date", true);
+ d->tooltipShowPhotoFocal = config->readBoolEntry("ToolTips Show Photo Focal", true);
+ d->tooltipShowPhotoExpo = config->readBoolEntry("ToolTips Show Photo Expo", true);
+ d->tooltipShowPhotoMode = config->readBoolEntry("ToolTips Show Photo Mode", true);
+ d->tooltipShowPhotoFlash = config->readBoolEntry("ToolTips Show Photo Flash", false);
+ d->tooltipShowPhotoWb = config->readBoolEntry("ToolTips Show Photo WB", false);
+ d->tooltipShowAlbumName = config->readBoolEntry("ToolTips Show Album Name", false);
+ d->tooltipShowComments = config->readBoolEntry("ToolTips Show Comments", true);
+ d->tooltipShowTags = config->readBoolEntry("ToolTips Show Tags", true);
+ d->tooltipShowRating = config->readBoolEntry("ToolTips Show Rating", true);
+
+ d->previewLoadFullImageSize = config->readBoolEntry("Preview Load Full Image Size", false);
+
+ d->recursiveAlbums = config->readBoolEntry("Recursive Albums", false);
+ d->recursiveTags = config->readBoolEntry("Recursive Tags", true);
+
+ d->showFolderTreeViewItemsCount = config->readBoolEntry("Show Folder Tree View Items Count", false);
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("EXIF Settings");
+
+ d->exifRotate = config->readBoolEntry("EXIF Rotate", true);
+ d->exifSetOrientation = config->readBoolEntry("EXIF Set Orientation", true);
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("Metadata Settings");
+
+ d->saveIptcTags = config->readBoolEntry("Save IPTC Tags", false);
+ d->saveIptcPhotographerId = config->readBoolEntry("Save IPTC Photographer ID", false);
+ d->saveIptcCredits = config->readBoolEntry("Save IPTC Credits", false);
+
+ d->saveComments = config->readBoolEntry("Save EXIF Comments", false);
+ d->saveDateTime = config->readBoolEntry("Save Date Time", false);
+ d->saveRating = config->readBoolEntry("Save Rating", false);
+
+ d->author = config->readEntry("IPTC Author", TQString());
+ d->authorTitle = config->readEntry("IPTC Author Title", TQString());
+ d->credit = config->readEntry("IPTC Credit", TQString());
+ d->source = config->readEntry("IPTC Source", TQString());
+ d->copyright = config->readEntry("IPTC Copyright", TQString());
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("General Settings");
+
+ d->showSplash = config->readBoolEntry("Show Splash", true);
+ d->useTrash = config->readBoolEntry("Use Trash", true);
+ d->showTrashDeleteDialog = config->readBoolEntry("Show Trash Delete Dialog", true);
+ d->sidebarApplyDirectly = config->readBoolEntry("Apply Sidebar Changes Directly", false);
+ d->scanAtStart = config->readBoolEntry("Scan At Start", true);
+}
+
+void AlbumSettings::saveSettings()
+{
+ TDEConfig* config = d->config;
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("Album Settings");
+
+ config->writePathEntry("Album Path", d->albumLibraryPath);
+ config->writeEntry("Album Collections", d->albumCollectionNames);
+ config->writeEntry("Album Sort Order", (int)d->albumSortOrder);
+ config->writeEntry("Image Sort Order", (int)d->imageSortOrder);
+ config->writeEntry("Item Right Click Action", (int)d->itemRightClickAction);
+ config->writeEntry("File Filter", d->imageFilefilter);
+ config->writeEntry("Movie File Filter", d->movieFilefilter);
+ config->writeEntry("Audio File Filter", d->audioFilefilter);
+ config->writeEntry("Raw File Filter", d->rawFilefilter);
+ config->writeEntry("Default Icon Size", TQString::number(d->thumbnailSize));
+ config->writeEntry("Default Tree Icon Size", TQString::number(d->treeThumbnailSize));
+ config->writeEntry("Rating Filter Condition", d->ratingFilterCond);
+ config->writeEntry("Theme", d->currentTheme);
+
+ config->writeEntry("Icon Show Name", d->iconShowName);
+ config->writeEntry("Icon Show Resolution", d->iconShowResolution);
+ config->writeEntry("Icon Show Size", d->iconShowSize);
+ config->writeEntry("Icon Show Date", d->iconShowDate);
+ config->writeEntry("Icon Show Modification Date", d->iconShowModDate);
+ config->writeEntry("Icon Show Comments", d->iconShowComments);
+ config->writeEntry("Icon Show Tags", d->iconShowTags);
+ config->writeEntry("Icon Show Rating", d->iconShowRating);
+
+ config->writeEntry("Show ToolTips", d->showToolTips);
+ config->writeEntry("ToolTips Show File Name", d->tooltipShowFileName);
+ config->writeEntry("ToolTips Show File Date", d->tooltipShowFileDate);
+ config->writeEntry("ToolTips Show File Size", d->tooltipShowFileSize);
+ config->writeEntry("ToolTips Show Image Type", d->tooltipShowImageType);
+ config->writeEntry("ToolTips Show Image Dim", d->tooltipShowImageDim);
+ config->writeEntry("ToolTips Show Photo Make", d->tooltipShowPhotoMake);
+ config->writeEntry("ToolTips Show Photo Date", d->tooltipShowPhotoDate);
+ config->writeEntry("ToolTips Show Photo Focal", d->tooltipShowPhotoFocal);
+ config->writeEntry("ToolTips Show Photo Expo", d->tooltipShowPhotoExpo);
+ config->writeEntry("ToolTips Show Photo Mode", d->tooltipShowPhotoMode);
+ config->writeEntry("ToolTips Show Photo Flash", d->tooltipShowPhotoFlash);
+ config->writeEntry("ToolTips Show Photo WB", d->tooltipShowPhotoWb);
+ config->writeEntry("ToolTips Show Album Name", d->tooltipShowAlbumName);
+ config->writeEntry("ToolTips Show Comments", d->tooltipShowComments);
+ config->writeEntry("ToolTips Show Tags", d->tooltipShowTags);
+ config->writeEntry("ToolTips Show Rating", d->tooltipShowRating);
+
+ config->writeEntry("Preview Load Full Image Size", d->previewLoadFullImageSize);
+
+ config->writeEntry("Recursive Albums", d->recursiveAlbums);
+ config->writeEntry("Recursive Tags", d->recursiveTags);
+
+ config->writeEntry("Show Folder Tree View Items Count", d->showFolderTreeViewItemsCount);
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("EXIF Settings");
+
+ config->writeEntry("EXIF Rotate", d->exifRotate);
+ config->writeEntry("EXIF Set Orientation", d->exifSetOrientation);
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("Metadata Settings");
+
+ config->writeEntry("Save IPTC Tags", d->saveIptcTags);
+ config->writeEntry("Save IPTC Photographer ID", d->saveIptcPhotographerId);
+ config->writeEntry("Save IPTC Credits", d->saveIptcCredits);
+
+ config->writeEntry("Save EXIF Comments", d->saveComments);
+ config->writeEntry("Save Date Time", d->saveDateTime);
+ config->writeEntry("Save Rating", d->saveRating);
+
+ config->writeEntry("IPTC Author", d->author);
+ config->writeEntry("IPTC Author Title", d->authorTitle);
+ config->writeEntry("IPTC Credit", d->credit);
+ config->writeEntry("IPTC Source", d->source);
+ config->writeEntry("IPTC Copyright", d->copyright);
+
+ // ---------------------------------------------------------------------
+
+ config->setGroup("General Settings");
+
+ config->writeEntry("Show Splash", d->showSplash);
+ config->writeEntry("Use Trash", d->useTrash);
+ config->writeEntry("Show Trash Delete Dialog", d->showTrashDeleteDialog);
+ config->writeEntry("Apply Sidebar Changes Directly", d->sidebarApplyDirectly);
+ config->writeEntry("Scan At Start", d->scanAtStart);
+
+ config->sync();
+}
+
+void AlbumSettings::setAlbumLibraryPath(const TQString& path)
+{
+ d->albumLibraryPath = path;
+}
+
+TQString AlbumSettings::getAlbumLibraryPath() const
+{
+ return d->albumLibraryPath;
+}
+
+void AlbumSettings::setShowSplashScreen(bool val)
+{
+ d->showSplash = val;
+}
+
+bool AlbumSettings::getShowSplashScreen() const
+{
+ return d->showSplash;
+}
+
+void AlbumSettings::setScanAtStart(bool val)
+{
+ d->scanAtStart = val;
+}
+
+bool AlbumSettings::getScanAtStart() const
+{
+ return d->scanAtStart;
+}
+
+void AlbumSettings::setAlbumCollectionNames(const TQStringList& list)
+{
+ d->albumCollectionNames = list;
+}
+
+TQStringList AlbumSettings::getAlbumCollectionNames()
+{
+ return d->albumCollectionNames;
+}
+
+bool AlbumSettings::addAlbumCollectionName(const TQString& name)
+{
+ if (d->albumCollectionNames.contains(name))
+ return false;
+
+ d->albumCollectionNames.append(name);
+ return true;
+}
+
+bool AlbumSettings::delAlbumCollectionName(const TQString& name)
+{
+ uint count = d->albumCollectionNames.remove(name);
+ return (count > 0) ? true : false;
+}
+
+void AlbumSettings::setAlbumSortOrder(const AlbumSettings::AlbumSortOrder order)
+{
+ d->albumSortOrder = order;
+}
+
+AlbumSettings::AlbumSortOrder AlbumSettings::getAlbumSortOrder() const
+{
+ return d->albumSortOrder;
+}
+
+void AlbumSettings::setImageSortOrder(const ImageSortOrder order)
+{
+ d->imageSortOrder = order;
+}
+
+AlbumSettings::ImageSortOrder AlbumSettings::getImageSortOrder() const
+{
+ return d->imageSortOrder;
+}
+
+void AlbumSettings::setItemRightClickAction(const ItemRightClickAction action)
+{
+ d->itemRightClickAction = action;
+}
+
+AlbumSettings::ItemRightClickAction AlbumSettings::getItemRightClickAction() const
+{
+ return d->itemRightClickAction;
+}
+
+void AlbumSettings::setImageFileFilter(const TQString& filter)
+{
+ d->imageFilefilter = filter;
+}
+
+TQString AlbumSettings::getImageFileFilter() const
+{
+ return d->imageFilefilter;
+}
+
+void AlbumSettings::setMovieFileFilter(const TQString& filter)
+{
+ d->movieFilefilter = filter;
+}
+
+TQString AlbumSettings::getMovieFileFilter() const
+{
+ return d->movieFilefilter;
+}
+
+void AlbumSettings::setAudioFileFilter(const TQString& filter)
+{
+ d->audioFilefilter = filter;
+}
+
+TQString AlbumSettings::getAudioFileFilter() const
+{
+ return d->audioFilefilter;
+}
+
+void AlbumSettings::setRawFileFilter(const TQString& filter)
+{
+ d->rawFilefilter = filter;
+}
+
+TQString AlbumSettings::getRawFileFilter() const
+{
+ return d->rawFilefilter;
+}
+
+bool AlbumSettings::addImageFileExtension(const TQString& newExt)
+{
+ if ( TQStringList::split(" ", d->imageFilefilter).contains(newExt) ||
+ TQStringList::split(" ", d->movieFilefilter).contains(newExt) ||
+ TQStringList::split(" ", d->audioFilefilter).contains(newExt) ||
+ TQStringList::split(" ", d->rawFilefilter ).contains(newExt) )
+ return false;
+
+ d->imageFilefilter = d->imageFilefilter + ' ' + newExt;
+ return true;
+}
+
+TQString AlbumSettings::getAllFileFilter() const
+{
+ return d->imageFilefilter + ' '
+ + d->movieFilefilter + ' '
+ + d->audioFilefilter + ' '
+ + d->rawFilefilter;
+}
+
+void AlbumSettings::setDefaultIconSize(int val)
+{
+ d->thumbnailSize = val;
+}
+
+int AlbumSettings::getDefaultIconSize() const
+{
+ return d->thumbnailSize;
+}
+
+void AlbumSettings::setDefaultTreeIconSize(int val)
+{
+ d->treeThumbnailSize = val;
+}
+
+int AlbumSettings::getDefaultTreeIconSize() const
+{
+ return ((d->treeThumbnailSize < 8) || (d->treeThumbnailSize > 48)) ? 48 : d->treeThumbnailSize;
+}
+
+void AlbumSettings::setRatingFilterCond(int val)
+{
+ d->ratingFilterCond = val;
+}
+
+int AlbumSettings::getRatingFilterCond() const
+{
+ return d->ratingFilterCond;
+}
+
+void AlbumSettings::setIconShowName(bool val)
+{
+ d->iconShowName = val;
+}
+
+bool AlbumSettings::getIconShowName() const
+{
+ return d->iconShowName;
+}
+
+void AlbumSettings::setIconShowSize(bool val)
+{
+ d->iconShowSize = val;
+}
+
+bool AlbumSettings::getIconShowSize() const
+{
+ return d->iconShowSize;
+}
+
+void AlbumSettings::setIconShowComments(bool val)
+{
+ d->iconShowComments = val;
+}
+
+bool AlbumSettings::getIconShowComments() const
+{
+ return d->iconShowComments;
+}
+
+void AlbumSettings::setIconShowResolution(bool val)
+{
+ d->iconShowResolution = val;
+}
+
+bool AlbumSettings::getIconShowResolution() const
+{
+ return d->iconShowResolution;
+}
+
+void AlbumSettings::setIconShowTags(bool val)
+{
+ d->iconShowTags = val;
+}
+
+bool AlbumSettings::getIconShowTags() const
+{
+ return d->iconShowTags;
+}
+
+void AlbumSettings::setIconShowDate(bool val)
+{
+ d->iconShowDate = val;
+}
+
+bool AlbumSettings::getIconShowDate() const
+{
+ return d->iconShowDate;
+}
+
+void AlbumSettings::setIconShowModDate(bool val)
+{
+ d->iconShowModDate = val;
+}
+
+bool AlbumSettings::getIconShowModDate() const
+{
+ return d->iconShowModDate;
+}
+
+void AlbumSettings::setIconShowRating(bool val)
+{
+ d->iconShowRating = val;
+}
+
+bool AlbumSettings::getIconShowRating() const
+{
+ return d->iconShowRating;
+}
+
+void AlbumSettings::setExifRotate(bool val)
+{
+ d->exifRotate = val;
+}
+
+bool AlbumSettings::getExifRotate() const
+{
+ return d->exifRotate;
+}
+
+void AlbumSettings::setExifSetOrientation(bool val)
+{
+ d->exifSetOrientation = val;
+}
+
+bool AlbumSettings::getExifSetOrientation() const
+{
+ return d->exifSetOrientation;
+}
+
+void AlbumSettings::setSaveIptcTags(bool val)
+{
+ d->saveIptcTags = val;
+}
+
+bool AlbumSettings::getSaveIptcTags() const
+{
+ return d->saveIptcTags;
+}
+
+void AlbumSettings::setSaveIptcPhotographerId(bool val)
+{
+ d->saveIptcPhotographerId = val;
+}
+
+bool AlbumSettings::getSaveIptcPhotographerId() const
+{
+ return d->saveIptcPhotographerId;
+}
+
+void AlbumSettings::setSaveIptcCredits(bool val)
+{
+ d->saveIptcCredits = val;
+}
+
+bool AlbumSettings::getSaveIptcCredits() const
+{
+ return d->saveIptcCredits;
+}
+
+void AlbumSettings::setIptcAuthor(const TQString& author)
+{
+ d->author = author;
+}
+
+TQString AlbumSettings::getIptcAuthor() const
+{
+ return d->author;
+}
+
+void AlbumSettings::setIptcAuthorTitle(const TQString& authorTitle)
+{
+ d->authorTitle = authorTitle;
+}
+
+TQString AlbumSettings::getIptcAuthorTitle() const
+{
+ return d->authorTitle;
+}
+
+void AlbumSettings::setIptcCredit(const TQString& credit)
+{
+ d->credit = credit;
+}
+
+TQString AlbumSettings::getIptcCredit() const
+{
+ return d->credit;
+}
+
+void AlbumSettings::setIptcSource(const TQString& source)
+{
+ d->source = source;
+}
+
+TQString AlbumSettings::getIptcSource() const
+{
+ return d->source;
+}
+
+void AlbumSettings::setIptcCopyright(const TQString& copyright)
+{
+ d->copyright = copyright;
+}
+
+TQString AlbumSettings::getIptcCopyright() const
+{
+ return d->copyright;
+}
+
+void AlbumSettings::setSaveComments(bool val)
+{
+ d->saveComments = val;
+}
+
+bool AlbumSettings::getSaveComments() const
+{
+ return d->saveComments;
+}
+
+void AlbumSettings::setSaveDateTime(bool val)
+{
+ d->saveDateTime = val;
+}
+
+bool AlbumSettings::getSaveDateTime() const
+{
+ return d->saveDateTime;
+}
+
+bool AlbumSettings::getSaveRating() const
+{
+ return d->saveRating;
+}
+
+void AlbumSettings::setSaveRating(bool val)
+{
+ d->saveRating = val;
+}
+
+void AlbumSettings::setShowToolTips(bool val)
+{
+ d->showToolTips = val;
+}
+
+bool AlbumSettings::getShowToolTips() const
+{
+ return d->showToolTips;
+}
+
+void AlbumSettings::setToolTipsShowFileName(bool val)
+{
+ d->tooltipShowFileName = val;
+}
+
+bool AlbumSettings::getToolTipsShowFileName() const
+{
+ return d->tooltipShowFileName;
+}
+
+void AlbumSettings::setToolTipsShowFileDate(bool val)
+{
+ d->tooltipShowFileDate = val;
+}
+
+bool AlbumSettings::getToolTipsShowFileDate() const
+{
+ return d->tooltipShowFileDate;
+}
+
+void AlbumSettings::setToolTipsShowFileSize(bool val)
+{
+ d->tooltipShowFileSize = val;
+}
+
+bool AlbumSettings::getToolTipsShowFileSize() const
+{
+ return d->tooltipShowFileSize;
+}
+
+void AlbumSettings::setToolTipsShowImageType(bool val)
+{
+ d->tooltipShowImageType = val;
+}
+
+bool AlbumSettings::getToolTipsShowImageType() const
+{
+ return d->tooltipShowImageType;
+}
+
+void AlbumSettings::setToolTipsShowImageDim(bool val)
+{
+ d->tooltipShowImageDim = val;
+}
+
+bool AlbumSettings::getToolTipsShowImageDim() const
+{
+ return d->tooltipShowImageDim;
+}
+
+void AlbumSettings::setToolTipsShowPhotoMake(bool val)
+{
+ d->tooltipShowPhotoMake = val;
+}
+
+bool AlbumSettings::getToolTipsShowPhotoMake() const
+{
+ return d->tooltipShowPhotoMake;
+}
+
+void AlbumSettings::setToolTipsShowPhotoDate(bool val)
+{
+ d->tooltipShowPhotoDate = val;
+}
+
+bool AlbumSettings::getToolTipsShowPhotoDate() const
+{
+ return d->tooltipShowPhotoDate;
+}
+
+void AlbumSettings::setToolTipsShowPhotoFocal(bool val)
+{
+ d->tooltipShowPhotoFocal = val;
+}
+
+bool AlbumSettings::getToolTipsShowPhotoFocal() const
+{
+ return d->tooltipShowPhotoFocal;
+}
+
+void AlbumSettings::setToolTipsShowPhotoExpo(bool val)
+{
+ d->tooltipShowPhotoExpo = val;
+}
+
+bool AlbumSettings::getToolTipsShowPhotoExpo() const
+{
+ return d->tooltipShowPhotoExpo;
+}
+
+void AlbumSettings::setToolTipsShowPhotoMode(bool val)
+{
+ d->tooltipShowPhotoMode = val;
+}
+
+bool AlbumSettings::getToolTipsShowPhotoMode() const
+{
+ return d->tooltipShowPhotoMode;
+}
+
+void AlbumSettings::setToolTipsShowPhotoFlash(bool val)
+{
+ d->tooltipShowPhotoFlash = val;
+}
+
+bool AlbumSettings::getToolTipsShowPhotoFlash() const
+{
+ return d->tooltipShowPhotoFlash;
+}
+
+void AlbumSettings::setToolTipsShowPhotoWB(bool val)
+{
+ d->tooltipShowPhotoWb = val;
+}
+
+bool AlbumSettings::getToolTipsShowPhotoWB() const
+{
+ return d->tooltipShowPhotoWb;
+}
+
+void AlbumSettings::setToolTipsShowAlbumName(bool val)
+{
+ d->tooltipShowAlbumName = val;
+}
+
+bool AlbumSettings::getToolTipsShowAlbumName() const
+{
+ return d->tooltipShowAlbumName;
+}
+
+void AlbumSettings::setToolTipsShowComments(bool val)
+{
+ d->tooltipShowComments = val;
+}
+
+bool AlbumSettings::getToolTipsShowComments() const
+{
+ return d->tooltipShowComments;
+}
+
+void AlbumSettings::setToolTipsShowTags(bool val)
+{
+ d->tooltipShowTags = val;
+}
+
+bool AlbumSettings::getToolTipsShowTags() const
+{
+ return d->tooltipShowTags;
+}
+
+void AlbumSettings::setToolTipsShowRating(bool val)
+{
+ d->tooltipShowRating = val;
+}
+
+bool AlbumSettings::getToolTipsShowRating() const
+{
+ return d->tooltipShowRating;
+}
+
+void AlbumSettings::setCurrentTheme(const TQString& theme)
+{
+ d->currentTheme = theme;
+}
+
+TQString AlbumSettings::getCurrentTheme() const
+{
+ return d->currentTheme;
+}
+
+void AlbumSettings::setUseTrash(bool val)
+{
+ d->useTrash = val;
+}
+
+bool AlbumSettings::getUseTrash() const
+{
+ return d->useTrash;
+}
+
+void AlbumSettings::setShowTrashDeleteDialog(bool val)
+{
+ d->showTrashDeleteDialog = val;
+}
+
+bool AlbumSettings::getShowTrashDeleteDialog() const
+{
+ return d->showTrashDeleteDialog;
+}
+
+void AlbumSettings::setApplySidebarChangesDirectly(bool val)
+{
+ d->sidebarApplyDirectly= val;
+}
+
+bool AlbumSettings::getApplySidebarChangesDirectly() const
+{
+ return d->sidebarApplyDirectly;
+}
+
+bool AlbumSettings::showToolTipsIsValid() const
+{
+ if (d->showToolTips)
+ {
+ if (d->tooltipShowFileName ||
+ d->tooltipShowFileDate ||
+ d->tooltipShowFileSize ||
+ d->tooltipShowImageType ||
+ d->tooltipShowImageDim ||
+ d->tooltipShowPhotoMake ||
+ d->tooltipShowPhotoDate ||
+ d->tooltipShowPhotoFocal ||
+ d->tooltipShowPhotoExpo ||
+ d->tooltipShowPhotoMode ||
+ d->tooltipShowPhotoFlash ||
+ d->tooltipShowPhotoWb ||
+ d->tooltipShowAlbumName ||
+ d->tooltipShowComments ||
+ d->tooltipShowTags ||
+ d->tooltipShowRating)
+ return true;
+ }
+
+ return false;
+}
+
+TQString AlbumSettings::getDefaultImageFileFilter() const
+{
+ return d->defaultImageFilefilter;
+}
+
+TQString AlbumSettings::getDefaultMovieFileFilter() const
+{
+ return d->defaultMovieFilefilter;
+}
+
+TQString AlbumSettings::getDefaultAudioFileFilter() const
+{
+ return d->defaultAudioFilefilter;
+}
+
+TQString AlbumSettings::getDefaultRawFileFilter() const
+{
+ return d->defaultRawFilefilter;
+}
+
+void AlbumSettings::setPreviewLoadFullImageSize(bool val)
+{
+ d->previewLoadFullImageSize = val;
+}
+
+bool AlbumSettings::getPreviewLoadFullImageSize() const
+{
+ return d->previewLoadFullImageSize;
+}
+
+void AlbumSettings::setRecurseAlbums(bool val)
+{
+ d->recursiveAlbums = val;
+}
+
+bool AlbumSettings::getRecurseAlbums() const
+{
+ return d->recursiveAlbums;
+}
+
+void AlbumSettings::setRecurseTags(bool val)
+{
+ d->recursiveTags = val;
+}
+
+bool AlbumSettings::getRecurseTags() const
+{
+ return d->recursiveTags;
+}
+
+void AlbumSettings::setShowFolderTreeViewItemsCount(bool val)
+{
+ d->showFolderTreeViewItemsCount = val;
+}
+
+bool AlbumSettings::getShowFolderTreeViewItemsCount() const
+{
+ return d->showFolderTreeViewItemsCount;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumsettings.h b/src/digikam/albumsettings.h
new file mode 100644
index 00000000..c3e3dcb7
--- /dev/null
+++ b/src/digikam/albumsettings.h
@@ -0,0 +1,283 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-16-10
+ * Description : albums settings interface
+ *
+ * Copyright (C) 2003-2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMSETTINGS_H
+#define ALBUMSETTINGS_H
+
+// TQt includes.
+
+#include <tqstringlist.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class AlbumSettingsPrivate;
+
+class DIGIKAM_EXPORT AlbumSettings
+{
+public:
+
+ enum AlbumSortOrder
+ {
+ ByFolder = 0,
+ ByCollection,
+ ByDate
+ };
+
+ enum ImageSortOrder
+ {
+ ByIName = 0,
+ ByIPath,
+ ByIDate,
+ ByISize,
+ ByIRating
+ };
+
+ enum ItemRightClickAction
+ {
+ ShowPreview = 0,
+ StartEditor
+ };
+
+ AlbumSettings();
+ ~AlbumSettings();
+
+ void readSettings();
+ void saveSettings();
+
+ bool showToolTipsIsValid() const;
+
+ void setAlbumLibraryPath(const TQString& path);
+ TQString getAlbumLibraryPath() const;
+
+ void setShowSplashScreen(bool val);
+ bool getShowSplashScreen() const;
+
+ void setScanAtStart(bool val);
+ bool getScanAtStart() const;
+
+ void setAlbumCollectionNames(const TQStringList& list);
+ TQStringList getAlbumCollectionNames();
+
+ bool addAlbumCollectionName(const TQString& name);
+ bool delAlbumCollectionName(const TQString& name);
+
+ void setAlbumSortOrder(const AlbumSortOrder order);
+ AlbumSortOrder getAlbumSortOrder() const;
+
+ void setImageSortOrder(const ImageSortOrder order);
+ ImageSortOrder getImageSortOrder() const;
+
+ void setItemRightClickAction(const ItemRightClickAction action);
+ ItemRightClickAction getItemRightClickAction() const;
+
+ void setImageFileFilter(const TQString& filter);
+ TQString getImageFileFilter() const;
+
+ void setMovieFileFilter(const TQString& filter);
+ TQString getMovieFileFilter() const;
+
+ void setAudioFileFilter(const TQString& filter);
+ TQString getAudioFileFilter() const;
+
+ void setRawFileFilter(const TQString& filter);
+ TQString getRawFileFilter() const;
+
+ bool addImageFileExtension(const TQString& ext);
+ TQString getAllFileFilter() const;
+
+ void setDefaultIconSize(int val);
+ int getDefaultIconSize() const;
+
+ void setDefaultTreeIconSize(int val);
+ int getDefaultTreeIconSize() const;
+
+ void setRatingFilterCond(int val);
+ int getRatingFilterCond() const;
+
+ void setIconShowName(bool val);
+ bool getIconShowName() const;
+
+ void setIconShowSize(bool val);
+ bool getIconShowSize() const;
+
+ void setIconShowComments(bool val);
+ bool getIconShowComments() const;
+
+ void setIconShowResolution(bool val);
+ bool getIconShowResolution() const;
+
+ void setIconShowTags(bool val);
+ bool getIconShowTags() const;
+
+ void setIconShowDate(bool val);
+ bool getIconShowDate() const;
+
+ void setIconShowModDate(bool val);
+ bool getIconShowModDate() const;
+
+ void setIconShowRating(bool val);
+ bool getIconShowRating() const;
+
+ void setExifRotate(bool val);
+ bool getExifRotate() const;
+
+ void setExifSetOrientation(bool val);
+ bool getExifSetOrientation() const;
+
+ void setSaveIptcTags(bool val);
+ bool getSaveIptcTags() const;
+
+ void setSaveIptcPhotographerId(bool val);
+ bool getSaveIptcPhotographerId() const;
+
+ void setSaveIptcCredits(bool val);
+ bool getSaveIptcCredits() const;
+
+ void setIptcAuthor(const TQString& author);
+ TQString getIptcAuthor() const;
+
+ void setIptcAuthorTitle(const TQString& authorTitle);
+ TQString getIptcAuthorTitle() const;
+
+ void setIptcCredit(const TQString& credit);
+ TQString getIptcCredit() const;
+
+ void setIptcSource(const TQString& source);
+ TQString getIptcSource() const;
+
+ void setIptcCopyright(const TQString& copyright);
+ TQString getIptcCopyright() const;
+
+ void setSaveComments(bool val);
+ bool getSaveComments() const;
+
+ void setSaveDateTime(bool val);
+ bool getSaveDateTime() const;
+
+ void setSaveRating(bool val);
+ bool getSaveRating() const;
+
+ void setShowToolTips(bool val);
+ bool getShowToolTips() const;
+
+ void setToolTipsShowFileName(bool val);
+ bool getToolTipsShowFileName() const;
+
+ void setToolTipsShowFileDate(bool val);
+ bool getToolTipsShowFileDate() const;
+
+ void setToolTipsShowFileSize(bool val);
+ bool getToolTipsShowFileSize() const;
+
+ void setToolTipsShowImageType(bool val);
+ bool getToolTipsShowImageType() const;
+
+ void setToolTipsShowImageDim(bool val);
+ bool getToolTipsShowImageDim() const;
+
+ void setToolTipsShowPhotoMake(bool val);
+ bool getToolTipsShowPhotoMake() const;
+
+ void setToolTipsShowPhotoDate(bool val);
+ bool getToolTipsShowPhotoDate() const;
+
+ void setToolTipsShowPhotoFocal(bool val);
+ bool getToolTipsShowPhotoFocal() const;
+
+ void setToolTipsShowPhotoExpo(bool val);
+ bool getToolTipsShowPhotoExpo() const;
+
+ void setToolTipsShowPhotoMode(bool val);
+ bool getToolTipsShowPhotoMode() const;
+
+ void setToolTipsShowPhotoFlash(bool val);
+ bool getToolTipsShowPhotoFlash() const;
+
+ void setToolTipsShowPhotoWB(bool val);
+ bool getToolTipsShowPhotoWB() const;
+
+ void setToolTipsShowAlbumName(bool val);
+ bool getToolTipsShowAlbumName() const;
+
+ void setToolTipsShowComments(bool val);
+ bool getToolTipsShowComments() const;
+
+ void setToolTipsShowTags(bool val);
+ bool getToolTipsShowTags() const;
+
+ void setToolTipsShowRating(bool val);
+ bool getToolTipsShowRating() const;
+
+ void setCurrentTheme(const TQString& theme);
+ TQString getCurrentTheme() const;
+
+ void setUseTrash(bool val);
+ bool getUseTrash() const;
+
+ void setShowTrashDeleteDialog(bool val);
+ bool getShowTrashDeleteDialog() const;
+
+ void setApplySidebarChangesDirectly(bool val);
+ bool getApplySidebarChangesDirectly() const;
+
+ TQString getDefaultImageFileFilter() const;
+ TQString getDefaultMovieFileFilter() const;
+ TQString getDefaultAudioFileFilter() const;
+ TQString getDefaultRawFileFilter() const;
+
+ void setPreviewLoadFullImageSize(bool val);
+ bool getPreviewLoadFullImageSize() const;
+
+ void setShowFolderTreeViewItemsCount(bool val);
+ bool getShowFolderTreeViewItemsCount() const;
+
+ void setRecurseAlbums(bool val);
+ bool getRecurseAlbums() const;
+
+ void setRecurseTags(bool val);
+ bool getRecurseTags() const;
+
+ static AlbumSettings *instance();
+
+private:
+
+ void init();
+
+private:
+
+ static AlbumSettings* m_instance;
+
+ AlbumSettingsPrivate* d;
+};
+
+} // namespace Digikam
+
+#endif // ALBUMSETTINGS_H
diff --git a/src/digikam/albumthumbnailloader.cpp b/src/digikam/albumthumbnailloader.cpp
new file mode 100644
index 00000000..1fe2065b
--- /dev/null
+++ b/src/digikam/albumthumbnailloader.cpp
@@ -0,0 +1,491 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-04-14
+ * Description : Load and cache tag thumbnails
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C includes.
+
+#include <math.h>
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqpainter.h>
+#include <tqvaluelist.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "thumbnailjob.h"
+#include "thumbnailsize.h"
+#include "album.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "albumthumbnailloader.h"
+#include "albumthumbnailloader.moc"
+
+namespace Digikam
+{
+
+typedef TQMap<KURL, TQValueList<int> > UrlAlbumMap;
+typedef TQMap<int, TQPixmap> TagThumbnailMap;
+
+class AlbumThumbnailLoaderPrivate
+{
+public:
+ AlbumThumbnailLoaderPrivate()
+ {
+ iconSize = AlbumSettings::instance()->getDefaultTreeIconSize();
+ minBlendSize = 20;
+ iconAlbumThumbJob = 0;
+ iconTagThumbJob = 0;
+ //cache = new TQCache<TQPixmap>(101, 211);
+ }
+
+ int iconSize;
+ int minBlendSize;
+
+ ThumbnailJob *iconTagThumbJob;
+
+ ThumbnailJob *iconAlbumThumbJob;
+
+ UrlAlbumMap urlAlbumMap;
+
+ TagThumbnailMap tagThumbnailMap;
+
+ //TQCache<TQPixmap> *cache;
+};
+
+class AlbumThumbnailLoaderEvent : public TQCustomEvent
+{
+public:
+ AlbumThumbnailLoaderEvent(int albumID, const TQPixmap &thumbnail)
+ : TQCustomEvent(TQEvent::User),
+ albumID(albumID), thumbnail(thumbnail)
+ {};
+
+ int albumID;
+ TQPixmap thumbnail;
+};
+
+AlbumThumbnailLoader *AlbumThumbnailLoader::m_instance = 0;
+
+AlbumThumbnailLoader *AlbumThumbnailLoader::instance()
+{
+ if (!m_instance)
+ m_instance = new AlbumThumbnailLoader;
+ return m_instance;
+}
+
+void AlbumThumbnailLoader::cleanUp()
+{
+ delete m_instance;
+}
+
+AlbumThumbnailLoader::AlbumThumbnailLoader()
+{
+ d = new AlbumThumbnailLoaderPrivate;
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumIconChanged(Album*)),
+ this, TQ_SLOT(slotIconChanged(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotIconChanged(Album*)));
+}
+
+
+AlbumThumbnailLoader::~AlbumThumbnailLoader()
+{
+ if (d->iconTagThumbJob)
+ d->iconTagThumbJob->kill();
+
+ if (d->iconAlbumThumbJob)
+ d->iconAlbumThumbJob->kill();
+ //delete d->cache;
+
+ delete d;
+
+ m_instance = 0;
+}
+
+TQPixmap AlbumThumbnailLoader::getStandardTagIcon(RelativeSize relativeSize)
+{
+ return loadIcon("tag", computeIconSize(relativeSize));
+}
+
+TQPixmap AlbumThumbnailLoader::getStandardTagRootIcon(RelativeSize relativeSize)
+{
+ return loadIcon("tag-folder", computeIconSize(relativeSize));
+}
+
+TQPixmap AlbumThumbnailLoader::getStandardTagIcon(TAlbum *album, RelativeSize relativeSize)
+{
+ if (album->isRoot())
+ return getStandardTagRootIcon(relativeSize);
+ else
+ return getStandardTagIcon(relativeSize);
+}
+
+TQPixmap AlbumThumbnailLoader::getStandardAlbumIcon(RelativeSize relativeSize)
+{
+ return loadIcon("folder", computeIconSize(relativeSize));
+}
+
+TQPixmap AlbumThumbnailLoader::getStandardAlbumRootIcon(RelativeSize relativeSize)
+{
+ return loadIcon("folder_image", computeIconSize(relativeSize));
+}
+
+TQPixmap AlbumThumbnailLoader::getStandardAlbumIcon(PAlbum *album, RelativeSize relativeSize)
+{
+ if (album->isRoot())
+ return getStandardAlbumRootIcon(relativeSize);
+ else
+ return getStandardAlbumIcon(relativeSize);
+}
+
+int AlbumThumbnailLoader::computeIconSize(RelativeSize relativeSize)
+{
+ if (relativeSize == SmallerSize)
+ {
+ // when size was 32 smaller was 20. Scale.
+ return lround(20.0 / 32.0 * (double)d->iconSize);
+ }
+ return d->iconSize;
+}
+
+TQRect AlbumThumbnailLoader::computeBlendRect(int iconSize)
+{
+ // when drawing a 20x20 thumbnail in a 32x32 icon, starting point was (6,9). Scale.
+ double largerSize = iconSize;
+ double x = 6.0 / 32.0 * largerSize;
+ double y = 9.0 / 32.0 * largerSize;
+ double size = 20.0 / 32.0 * largerSize;
+ return TQRect(lround(x), lround(y), lround(size), lround(size));
+}
+
+TQPixmap AlbumThumbnailLoader::loadIcon(const TQString &name, int size)
+{
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+ return iconLoader->loadIcon(name, TDEIcon::NoGroup,
+ size, TDEIcon::DefaultState,
+ 0, true);
+}
+
+bool AlbumThumbnailLoader::getTagThumbnail(TAlbum *album, TQPixmap &icon)
+{
+ int size = computeIconSize(SmallerSize);
+ /*
+ if (size >= d->minBlendSize)
+ {
+ TQRect rect = computeBlendRect(size);
+ size = rect.width();
+ }
+ */
+
+ if(!album->icon().isEmpty())
+ {
+ if(album->icon().startsWith("/"))
+ {
+ KURL iconKURL;
+ iconKURL.setPath(album->icon());
+ addURL(album, iconKURL);
+ icon = TQPixmap();
+ return true;
+ }
+ else
+ {
+ icon = loadIcon(album->icon(), size);
+ return false;
+ }
+ }
+ else
+ {
+ icon = TQPixmap();
+ return false;
+ }
+}
+
+bool AlbumThumbnailLoader::getAlbumThumbnail(PAlbum *album)
+{
+ if(!album->icon().isEmpty() && d->iconSize > d->minBlendSize)
+ {
+ addURL(album, album->iconKURL());
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void AlbumThumbnailLoader::addURL(Album *album, const KURL &url)
+{
+ /*
+ TQPixmap* pix = d->cache->find(album->iconKURL().path());
+ if (pix)
+ return pix;
+ */
+
+ // First check cached thumbnails.
+ // At startup, this is not relevant, as the views will add their requests in a row.
+ // This is to speed up context menu and IE imagedescedit
+ TagThumbnailMap::iterator ttit = d->tagThumbnailMap.find(album->globalID());
+ if (ttit != d->tagThumbnailMap.end())
+ {
+ // It is not necessary to return cached icon asynchronously - they could be
+ // returned by getTagThumbnail already - but this would make the API
+ // less elegant, it feels much better this way.
+ TQApplication::postEvent(this, new AlbumThumbnailLoaderEvent(album->globalID(), *ttit));
+ return;
+ }
+
+ // Check if the URL has already been added (ThumbnailJob will _not_ check this)
+ UrlAlbumMap::iterator it = d->urlAlbumMap.find(url);
+
+ if (it == d->urlAlbumMap.end())
+ {
+ // use two IOslaves so that tag and album thumbnails are loaded
+ // in parallel and not first album, then tag thumbnails
+ if (album->type() == Album::TAG)
+ {
+ if(!d->iconTagThumbJob)
+ {
+ d->iconTagThumbJob = new ThumbnailJob(url,
+ d->iconSize,
+ true,
+ AlbumSettings::instance()->getExifRotate());
+ connect(d->iconTagThumbJob,
+ TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ TQ_SLOT(slotGotThumbnailFromIcon(const KURL&, const TQPixmap&)));
+ connect(d->iconTagThumbJob,
+ TQ_SIGNAL(signalFailed(const KURL&)),
+ TQ_SLOT(slotThumbnailLost(const KURL&)));
+ }
+ else
+ {
+ d->iconTagThumbJob->addItem(url);
+ }
+ }
+ else
+ {
+ if(!d->iconAlbumThumbJob)
+ {
+ d->iconAlbumThumbJob = new ThumbnailJob(url,
+ d->iconSize,
+ true,
+ AlbumSettings::instance()->getExifRotate());
+ connect(d->iconAlbumThumbJob,
+ TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ TQ_SLOT(slotGotThumbnailFromIcon(const KURL&, const TQPixmap&)));
+ connect(d->iconAlbumThumbJob,
+ TQ_SIGNAL(signalFailed(const KURL&)),
+ TQ_SLOT(slotThumbnailLost(const KURL&)));
+ }
+ else
+ {
+ d->iconAlbumThumbJob->addItem(url);
+ }
+ }
+
+ // insert new entry to map, add album globalID
+ TQValueList<int> &list = d->urlAlbumMap[url];
+ list.remove(album->globalID());
+ list.push_back(album->globalID());
+ }
+ else
+ {
+ // only add album global ID to list which is already inserted in map
+ (*it).remove(album->globalID());
+ (*it).push_back(album->globalID());
+ }
+}
+
+void AlbumThumbnailLoader::setThumbnailSize(int size)
+{
+ if (d->iconSize == size)
+ return;
+
+ d->iconSize = size;
+
+ // clear task list
+ d->urlAlbumMap.clear();
+ // clear cached thumbnails
+ d->tagThumbnailMap.clear();
+
+ if (d->iconAlbumThumbJob)
+ {
+ d->iconAlbumThumbJob->kill();
+ d->iconAlbumThumbJob= 0;
+ }
+ if (d->iconTagThumbJob)
+ {
+ d->iconTagThumbJob->kill();
+ d->iconTagThumbJob= 0;
+ }
+
+ emit signalReloadThumbnails();
+}
+
+int AlbumThumbnailLoader::thumbnailSize() const
+{
+ return d->iconSize;
+}
+
+void AlbumThumbnailLoader::slotGotThumbnailFromIcon(const KURL &url, const TQPixmap &thumbnail)
+{
+ // We need to find all albums for which the given url has been requested,
+ // and emit a signal for each album.
+
+ UrlAlbumMap::iterator it = d->urlAlbumMap.find(url);
+
+ if (it != d->urlAlbumMap.end())
+ {
+ TQPixmap tagThumbnail;
+
+ AlbumManager *manager = AlbumManager::instance();
+ for (TQValueList<int>::iterator vit = (*it).begin(); vit != (*it).end(); ++vit)
+ {
+ // look up with global id
+ Album *album = manager->findAlbum(*vit);
+ if (album)
+ {
+ if (album->type() == Album::TAG)
+ {
+ // create tag thumbnail if needed
+ if (tagThumbnail.isNull())
+ {
+ tagThumbnail = createTagThumbnail(thumbnail);
+ d->tagThumbnailMap.insert(album->globalID(), tagThumbnail);
+ }
+
+ emit signalThumbnail(album, tagThumbnail);
+ }
+ else
+ {
+ emit signalThumbnail(album, thumbnail);
+ }
+ }
+ }
+
+ d->urlAlbumMap.remove(it);
+ }
+
+}
+
+void AlbumThumbnailLoader::customEvent(TQCustomEvent *e)
+{
+ // for cached thumbnails
+
+ AlbumThumbnailLoaderEvent *atle = (AlbumThumbnailLoaderEvent *)e;
+ AlbumManager *manager = AlbumManager::instance();
+ Album *album = manager->findAlbum(atle->albumID);
+ if (album)
+ {
+ if (atle->thumbnail.isNull())
+ emit signalFailed(album);
+ else
+ emit signalThumbnail(album, atle->thumbnail);
+ }
+}
+
+void AlbumThumbnailLoader::slotIconChanged(Album* album)
+{
+ if(!album || album->type() != Album::TAG)
+ return;
+
+ d->tagThumbnailMap.remove(album->globalID());
+}
+
+TQPixmap AlbumThumbnailLoader::createTagThumbnail(const TQPixmap &albumThumbnail)
+{
+ // tag thumbnails are cropped
+
+ TQPixmap tagThumbnail;
+ int thumbSize = TQMAX(albumThumbnail.width(), albumThumbnail.height());
+
+ if(!albumThumbnail.isNull() && thumbSize >= d->minBlendSize)
+ {
+ TQRect rect = computeBlendRect(thumbSize);
+ int w1 = albumThumbnail.width();
+ int w2 = rect.width();
+ int h1 = albumThumbnail.height();
+ int h2 = rect.height();
+ tagThumbnail.resize(w2,h2);
+ bitBlt(&tagThumbnail, 0, 0, &albumThumbnail, (w1-w2)/2, (h1-h2)/2, w2, h2);
+ }
+ else
+ {
+ tagThumbnail = albumThumbnail;
+ }
+
+ return tagThumbnail;
+}
+
+void AlbumThumbnailLoader::slotThumbnailLost(const KURL &url)
+{
+ // Same code as above, only different signal
+
+ UrlAlbumMap::iterator it = d->urlAlbumMap.find(url);
+
+ if (it != d->urlAlbumMap.end())
+ {
+ AlbumManager *manager = AlbumManager::instance();
+ for (TQValueList<int>::iterator vit = (*it).begin(); vit != (*it).end(); ++vit)
+ {
+ Album *album = manager->findAlbum(*vit);
+ if (album)
+ emit signalFailed(album);
+ }
+
+ d->urlAlbumMap.remove(it);
+ }
+}
+
+TQPixmap AlbumThumbnailLoader::blendIcons(TQPixmap dstIcon, const TQPixmap &tagIcon)
+{
+ int dstIconSize = TQMAX(dstIcon.width(), dstIcon.height());
+
+ if (dstIconSize >= d->minBlendSize)
+ {
+ if(!tagIcon.isNull())
+ {
+ TQRect rect = computeBlendRect(dstIconSize);
+ TQPainter p(&dstIcon);
+ p.drawPixmap(rect.x(), rect.y(), tagIcon, 0, 0, rect.width(), rect.height());
+ p.end();
+ }
+ return dstIcon;
+ }
+ else
+ {
+ return tagIcon;
+ }
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumthumbnailloader.h b/src/digikam/albumthumbnailloader.h
new file mode 100644
index 00000000..9d08c5af
--- /dev/null
+++ b/src/digikam/albumthumbnailloader.h
@@ -0,0 +1,178 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-04-14
+ * Description : Load and cache tag thumbnails
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef TAGTHUMBNAILLOADER_H
+#define TAGTHUMBNAILLOADER_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+class TQCustomEvent;
+
+namespace Digikam
+{
+
+class Album;
+class TAlbum;
+class PAlbum;
+class AlbumThumbnailLoaderPrivate;
+
+class AlbumThumbnailLoader : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ static AlbumThumbnailLoader *instance();
+ static void cleanUp();
+
+ /**
+ * Change the size of the thumbnails.
+ * If the size differs from the current size,
+ * signalReloadThumbnails will be emitted.
+ */
+ void setThumbnailSize(int size);
+ /**
+ * Get the current default icon size
+ */
+ int thumbnailSize() const;
+
+ /**
+ * Album thumbnail size is configurable via the settings menu.
+ * Some widgets use smaller icons than other widgets.
+ * These widgets do not need to know the currently set icon size from
+ * the setup and calculate a smaller size, but can simply request
+ * a relatively smaller icon.
+ * Depending on the user-chosen icon size, this size may in fact not
+ * be smaller than the normal size.
+ */
+ enum RelativeSize
+ {
+ NormalSize,
+ SmallerSize
+ };
+
+ /**
+ * Request thumbnail for given album.
+ * The thumbnail will be loaded
+ * and returned asynchronously by the signals.
+ * If no thumbnail is associated with given album,
+ * no action will be taken, and false is returned.
+ *
+ */
+ bool getAlbumThumbnail(PAlbum *album);
+
+ /**
+ * Behaves similar to the above method.
+ * Tag thumbnails will be processed as appropriate.
+ * Tags may have associated an icon that is loaded
+ * synchronously by the system icon loader.
+ * In this case, icon is set to this icon, and false is returned.
+ * If no icon is associated with the tag, icon is set to null,
+ * and false is returned.
+ * If a custom icon is associated with the tag,
+ * it is loaded asynchronously, icon is set to null,
+ * and true is returned.
+ * Tag thumbnails are always smaller than album thumbnails -
+ * as small as an album thumbnail with SmallerSize.
+ * They are supposed to be blended into the standard tag icon
+ * obtained below, or used as is when SmallerSize is requested anyway.
+ * @return Returns true if icon is loaded asynchronously.
+ */
+ bool getTagThumbnail(TAlbum *album, TQPixmap &icon);
+
+ /**
+ * Return standard tag and album icons.
+ * The third methods check if album is the root,
+ * and returns the standard icon or the root standard icon.
+ */
+ TQPixmap getStandardTagIcon(RelativeSize size = NormalSize);
+ TQPixmap getStandardTagRootIcon(RelativeSize size = NormalSize);
+ TQPixmap getStandardTagIcon(TAlbum *album, RelativeSize size = NormalSize);
+
+ TQPixmap getStandardAlbumIcon(RelativeSize size = NormalSize);
+ TQPixmap getStandardAlbumRootIcon(RelativeSize size = NormalSize);
+ TQPixmap getStandardAlbumIcon(PAlbum *album, RelativeSize size = NormalSize);
+
+ /**
+ * Blend tagIcon centered on dstIcon, where dstIcon is a standard
+ * icon of variable size and tagIcon is 12 pixels smaller.
+ * If height(dstIcon) < minBlendSize we return tagIcon verbatim.
+ */
+ TQPixmap blendIcons(TQPixmap dstIcon, const TQPixmap &tagIcon);
+
+
+signals:
+
+ /**
+ * This signal is emitted as soon as a thumbnail has become available
+ * for given album.
+ * This class is a singleton, so any object connected to this
+ * signal might not actually have requested a thumbnail for given url
+ */
+ void signalThumbnail(Album *album, const TQPixmap&);
+
+ /** This signal is emitted if thumbnail generation for given album failed.
+ * Same considerations as above.
+ */
+ void signalFailed(Album *album);
+
+ /**
+ * Indicates that all album and tag thumbnails need to be reloaded.
+ * This is usually because the icon size has changed in the setup.
+ */
+ void signalReloadThumbnails();
+
+protected slots:
+
+ void slotGotThumbnailFromIcon(const KURL&, const TQPixmap&);
+ void slotThumbnailLost(const KURL&);
+ void slotIconChanged(Album* album);
+
+protected:
+
+ void customEvent(TQCustomEvent *e);
+
+private:
+
+ AlbumThumbnailLoader();
+ ~AlbumThumbnailLoader();
+ AlbumThumbnailLoaderPrivate *d;
+ static AlbumThumbnailLoader *m_instance;
+
+ void addURL(Album *album, const KURL &url);
+ TQPixmap loadIcon(const TQString &name, int size = 0);
+ TQPixmap createTagThumbnail(const TQPixmap &albumThumbnail);
+ int computeIconSize(RelativeSize size);
+ TQRect computeBlendRect(int iconSize);
+};
+
+} // namespace Digikam
+
+#endif // TAGTHUMBNAILLOADER_H
diff --git a/src/digikam/albumwidgetstack.cpp b/src/digikam/albumwidgetstack.cpp
new file mode 100644
index 00000000..79b19599
--- /dev/null
+++ b/src/digikam/albumwidgetstack.cpp
@@ -0,0 +1,282 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-13
+ * Description : A widget stack to embedded album content view
+ * or the current image preview.
+ *
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <tdehtmlview.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "albumiconview.h"
+#include "imagepreviewview.h"
+#include "welcomepageview.h"
+#include "mediaplayerview.h"
+#include "albumwidgetstack.h"
+#include "albumwidgetstack.moc"
+
+namespace Digikam
+{
+
+class AlbumWidgetStackPriv
+{
+
+public:
+
+ AlbumWidgetStackPriv()
+ {
+ albumIconView = 0;
+ imagePreviewView = 0;
+ welcomePageView = 0;
+ mediaPlayerView = 0;
+ }
+
+ AlbumIconView *albumIconView;
+
+ ImagePreviewView *imagePreviewView;
+
+ WelcomePageView *welcomePageView;
+
+ MediaPlayerView *mediaPlayerView;
+};
+
+AlbumWidgetStack::AlbumWidgetStack(TQWidget *parent)
+ : TQWidgetStack(parent, 0, TQt::WDestructiveClose)
+{
+ d = new AlbumWidgetStackPriv;
+
+ d->albumIconView = new AlbumIconView(this);
+ d->imagePreviewView = new ImagePreviewView(this);
+ d->welcomePageView = new WelcomePageView(this);
+ d->mediaPlayerView = new MediaPlayerView(this);
+
+ addWidget(d->albumIconView, PreviewAlbumMode);
+ addWidget(d->imagePreviewView, PreviewImageMode);
+ addWidget(d->welcomePageView->view(), WelcomePageMode);
+ addWidget(d->mediaPlayerView, MediaPlayerMode);
+
+ setPreviewMode(PreviewAlbumMode);
+
+ // -----------------------------------------------------------------
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SIGNAL(signalNextItem()));
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SIGNAL(signalPrevItem()));
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalEditItem()),
+ this, TQ_SIGNAL(signalEditItem()));
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalDeleteItem()),
+ this, TQ_SIGNAL(signalDeleteItem()));
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalBack2Album()),
+ this, TQ_SIGNAL(signalBack2Album()));
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalSlideShow()),
+ this, TQ_SIGNAL(signalSlideShow()));
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SLOT(slotZoomFactorChanged(double)));
+
+ connect(d->imagePreviewView, TQ_SIGNAL(signalInsert2LightTable()),
+ this, TQ_SIGNAL(signalInsert2LightTable()));
+}
+
+AlbumWidgetStack::~AlbumWidgetStack()
+{
+ delete d;
+}
+
+void AlbumWidgetStack::slotEscapePreview()
+{
+ if (previewMode() == MediaPlayerMode)
+ d->mediaPlayerView->escapePreview();
+}
+
+AlbumIconView* AlbumWidgetStack::albumIconView()
+{
+ return d->albumIconView;
+}
+
+ImagePreviewView* AlbumWidgetStack::imagePreviewView()
+{
+ return d->imagePreviewView;
+}
+
+void AlbumWidgetStack::setPreviewItem(ImageInfo* info, ImageInfo *previous, ImageInfo *next)
+{
+ if (!info)
+ {
+ if (previewMode() == MediaPlayerMode)
+ d->mediaPlayerView->setMediaPlayerFromUrl(KURL());
+ else if (previewMode() == PreviewImageMode)
+ {
+ d->imagePreviewView->setImageInfo();
+ }
+ }
+ else
+ {
+ AlbumSettings *settings = AlbumSettings::instance();
+ TQString currentFileExtension = TQFileInfo(info->kurl().path()).extension(false);
+ TQString mediaplayerfilter = settings->getMovieFileFilter().lower() +
+ settings->getMovieFileFilter().upper() +
+ settings->getAudioFileFilter().lower() +
+ settings->getAudioFileFilter().upper();
+ if (mediaplayerfilter.contains(currentFileExtension) )
+ {
+ setPreviewMode(MediaPlayerMode);
+ d->mediaPlayerView->setMediaPlayerFromUrl(info->kurl());
+ }
+ else
+ {
+ // Stop media player if running...
+ if (previewMode() == MediaPlayerMode)
+ setPreviewItem();
+
+ d->imagePreviewView->setImageInfo(info, previous, next);
+
+ // NOTE: No need to toggle imediatly in PreviewImageMode here,
+ // because we will receive a signal for that when the image preview will be loaded.
+ // This will prevent a flicker effect with the old image preview loaded in stack.
+ }
+ }
+}
+
+int AlbumWidgetStack::previewMode(void)
+{
+ return id(visibleWidget());
+}
+
+void AlbumWidgetStack::setPreviewMode(int mode)
+{
+ if (mode != PreviewAlbumMode && mode != PreviewImageMode &&
+ mode != WelcomePageMode && mode != MediaPlayerMode)
+ return;
+
+ if (mode == PreviewAlbumMode || mode == WelcomePageMode)
+ {
+ d->albumIconView->setFocus();
+ setPreviewItem();
+ raiseWidget(mode);
+ emit signalToggledToPreviewMode(false);
+ }
+ else
+ {
+ raiseWidget(mode);
+ }
+}
+
+void AlbumWidgetStack::previewLoaded()
+{
+ emit signalToggledToPreviewMode(true);
+}
+
+void AlbumWidgetStack::slotZoomFactorChanged(double z)
+{
+ if (previewMode() == PreviewImageMode)
+ emit signalZoomFactorChanged(z);
+}
+
+void AlbumWidgetStack::slotItemsUpdated(const KURL::List& list)
+{
+ // If item are updated from Icon View, and if we are in Preview Mode,
+ // We will check if the current item preview need to be reloaded.
+
+ if (previewMode() == PreviewAlbumMode ||
+ previewMode() == WelcomePageMode ||
+ previewMode() == MediaPlayerMode) // What we can do with media player ?
+ return;
+
+ if (list.contains(imagePreviewView()->getImageInfo()->kurl()))
+ d->imagePreviewView->reload();
+}
+
+void AlbumWidgetStack::increaseZoom()
+{
+ d->imagePreviewView->slotIncreaseZoom();
+}
+
+void AlbumWidgetStack::decreaseZoom()
+{
+ d->imagePreviewView->slotDecreaseZoom();
+}
+
+void AlbumWidgetStack::zoomTo100Percents()
+{
+ d->imagePreviewView->setZoomFactor(1.0);
+}
+
+void AlbumWidgetStack::fitToWindow()
+{
+ d->imagePreviewView->fitToWindow();
+}
+
+void AlbumWidgetStack::toggleFitToWindowOr100()
+{
+ d->imagePreviewView->toggleFitToWindowOr100();
+}
+
+bool AlbumWidgetStack::maxZoom()
+{
+ return d->imagePreviewView->maxZoom();
+}
+
+bool AlbumWidgetStack::minZoom()
+{
+ return d->imagePreviewView->minZoom();
+}
+
+void AlbumWidgetStack::setZoomFactor(double z)
+{
+ d->imagePreviewView->setZoomFactor(z);
+}
+
+void AlbumWidgetStack::setZoomFactorSnapped(double z)
+{
+ d->imagePreviewView->setZoomFactorSnapped(z);
+}
+
+double AlbumWidgetStack::zoomFactor()
+{
+ return d->imagePreviewView->zoomFactor();
+}
+
+double AlbumWidgetStack::zoomMin()
+{
+ return d->imagePreviewView->zoomMin();
+}
+
+double AlbumWidgetStack::zoomMax()
+{
+ return d->imagePreviewView->zoomMax();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/albumwidgetstack.h b/src/digikam/albumwidgetstack.h
new file mode 100644
index 00000000..c8a6f6ac
--- /dev/null
+++ b/src/digikam/albumwidgetstack.h
@@ -0,0 +1,116 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-13
+ * Description : A widget stack to embedded album content view
+ * or the current image preview.
+ *
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMWIDGETSTACK_H
+#define ALBUMWIDGETSTACK_H
+
+// KDE includes.
+
+#include <tqwidgetstack.h>
+#include <tqcstring.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class KURL;
+
+namespace Digikam
+{
+
+class ImageInfo;
+class AlbumIconView;
+class ImagePreviewView;
+class AlbumWidgetStackPriv;
+
+class DIGIKAM_EXPORT AlbumWidgetStack : public TQWidgetStack
+{
+TQ_OBJECT
+
+public:
+
+ enum AlbumWidgetStackMode
+ {
+ PreviewAlbumMode=0,
+ PreviewImageMode,
+ WelcomePageMode,
+ MediaPlayerMode
+ };
+
+public:
+
+ AlbumWidgetStack(TQWidget *parent=0);
+ ~AlbumWidgetStack();
+
+ AlbumIconView *albumIconView();
+ ImagePreviewView *imagePreviewView();
+
+ void setPreviewItem(ImageInfo* info=0, ImageInfo *previous=0, ImageInfo *next=0);
+ int previewMode(void);
+ void setPreviewMode(int mode);
+ void previewLoaded();
+
+ void increaseZoom();
+ void decreaseZoom();
+ void fitToWindow();
+ void toggleFitToWindowOr100();
+ void zoomTo100Percents();
+ bool maxZoom();
+ bool minZoom();
+ void setZoomFactor(double z);
+ void setZoomFactorSnapped(double z);
+ double zoomFactor();
+ double zoomMin();
+ double zoomMax();
+
+signals:
+
+ void signalNextItem();
+ void signalPrevItem();
+ void signalEditItem();
+ void signalDeleteItem();
+ void signalToggledToPreviewMode(bool);
+ void signalBack2Album();
+ void signalSlideShow();
+ void signalZoomFactorChanged(double);
+ void signalInsert2LightTable();
+
+public slots:
+
+ void slotEscapePreview();
+ void slotItemsUpdated(const KURL::List&);
+
+private slots:
+
+ void slotZoomFactorChanged(double);
+
+private:
+
+ AlbumWidgetStackPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMWIDGETSTACK_H */
diff --git a/src/digikam/cameralist.cpp b/src/digikam/cameralist.cpp
new file mode 100644
index 00000000..6a380e0d
--- /dev/null
+++ b/src/digikam/cameralist.cpp
@@ -0,0 +1,276 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-03
+ * Description : Cameras list container
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqstring.h>
+#include <tqfile.h>
+#include <tqdom.h>
+#include <tqtextstream.h>
+
+// KDE includes.
+
+#include <tdemessagebox.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "gpcamera.h"
+#include "cameratype.h"
+#include "cameralist.h"
+#include "cameralist.moc"
+
+namespace Digikam
+{
+
+CameraList* CameraList::m_instance = 0;
+
+CameraList* CameraList::instance()
+{
+ return m_instance;
+}
+
+class CameraListPrivate
+{
+public:
+
+ bool modified;
+
+ TQPtrList<CameraType> clist;
+ TQString file;
+};
+
+CameraList::CameraList(TQObject *parent, const TQString& file)
+ : TQObject(parent)
+{
+ d = new CameraListPrivate;
+ d->clist.setAutoDelete(true);
+ d->file = file;
+ d->modified = false;
+ m_instance = this;
+}
+
+CameraList::~CameraList()
+{
+ save();
+
+ d->clist.clear();
+ delete d;
+
+ m_instance = 0;
+}
+
+bool CameraList::load()
+{
+ d->modified = false;
+
+ TQFile cfile(d->file);
+
+ if (!cfile.open(IO_ReadOnly))
+ return false;
+
+ TQDomDocument doc("cameralist");
+ if (!doc.setContent(&cfile))
+ return false;
+
+ TQDomElement docElem = doc.documentElement();
+ if (docElem.tagName()!="cameralist")
+ return false;
+
+ for (TQDomNode n = docElem.firstChild();
+ !n.isNull(); n = n.nextSibling())
+ {
+ TQDomElement e = n.toElement();
+ if (e.isNull()) continue;
+ if (e.tagName() != "item") continue;
+
+ TQString title = e.attribute("title");
+ TQString model = e.attribute("model");
+ TQString port = e.attribute("port");
+ TQString path = e.attribute("path");
+ TQDateTime lastAccess = TQDateTime::currentDateTime();
+
+ if (!e.attribute("lastaccess").isEmpty())
+ lastAccess = TQDateTime::fromString(e.attribute("lastaccess"), TQt::ISODate);
+
+ CameraType *ctype = new CameraType(title, model, port, path, lastAccess);
+ insertPrivate(ctype);
+ }
+
+ return true;
+}
+
+bool CameraList::save()
+{
+ // If not modified don't save the file
+ if (!d->modified)
+ return true;
+
+ TQDomDocument doc("cameralist");
+ doc.setContent(TQString("<!DOCTYPE XMLCameraList><cameralist version=\"1.1\" client=\"digikam\"/>"));
+
+ TQDomElement docElem=doc.documentElement();
+
+ for (CameraType *ctype = d->clist.first(); ctype;
+ ctype = d->clist.next())
+ {
+ TQDomElement elem = doc.createElement("item");
+ elem.setAttribute("title", ctype->title());
+ elem.setAttribute("model", ctype->model());
+ elem.setAttribute("port", ctype->port());
+ elem.setAttribute("path", ctype->path());
+ elem.setAttribute("lastaccess", ctype->lastAccess().toString(TQt::ISODate));
+ docElem.appendChild(elem);
+ }
+
+ TQFile cfile(d->file);
+ if (!cfile.open(IO_WriteOnly))
+ return false;
+
+ TQTextStream stream(&cfile);
+ stream.setEncoding(TQTextStream::UnicodeUTF8);
+ stream << doc.toString();
+ cfile.close();
+
+ return true;
+}
+
+bool CameraList::changeCameraAccessTime(const TQString& cameraTitle, const TQDateTime& newDate)
+{
+ CameraType* cam = find(cameraTitle);
+ if (cam)
+ {
+ cam->setLastAccess(newDate);
+ d->modified = true;
+ save();
+ return true;
+ }
+
+ return false;
+}
+
+void CameraList::insert(CameraType* ctype)
+{
+ if (!ctype) return;
+
+ d->modified = true;
+ insertPrivate(ctype);
+}
+
+void CameraList::remove(CameraType* ctype)
+{
+ if (!ctype) return;
+
+ d->modified = true;
+ removePrivate(ctype);
+}
+
+void CameraList::insertPrivate(CameraType* ctype)
+{
+ if (!ctype) return;
+ emit signalCameraAdded(ctype);
+ d->clist.append(ctype);
+}
+
+void CameraList::removePrivate(CameraType* ctype)
+{
+ if (!ctype) return;
+ emit signalCameraRemoved(ctype);
+ d->clist.remove(ctype);
+}
+
+TQPtrList<CameraType>* CameraList::cameraList()
+{
+ return &d->clist;
+}
+
+CameraType* CameraList::find(const TQString& title)
+{
+ for (CameraType *ctype = d->clist.first(); ctype;
+ ctype = d->clist.next())
+ {
+ if (ctype->title() == title)
+ return ctype;
+ }
+ return 0;
+}
+
+CameraType* CameraList::autoDetect(bool& retry)
+{
+ retry = false;
+
+ TQString model, port;
+ if (GPCamera::autoDetect(model, port) != 0)
+ {
+ retry = ( KMessageBox::warningYesNo(0, i18n("Failed to auto-detect camera; "
+ "please make sure it is connected "
+ "properly and is turned on. "
+ "Would you like to try again?"))
+ == KMessageBox::Yes );
+ return 0;
+ }
+
+ // check if the camera is already in the list
+ for (CameraType *ctype = d->clist.first(); ctype;
+ ctype = d->clist.next())
+ {
+ // we can get away with checking only the model, as the auto-detection
+ // works only for usb cameras. so the port is always usb:
+ if (ctype->model() == model)
+ return ctype;
+ }
+
+ // looks like a new camera
+
+ // NOTE: libgphoto2 now (2.1.4+) expects port names to be
+ // something like "usb:001,012". but on linux these port numbers
+ // will change every time camera is reconnected. gphoto port funcs
+ // also allow regexp match, so the safe bet is to just pass in
+ // "usb:" and cross your fingers that user doesn't have multiple cameras
+ // connected at the same time (whack them if they do).
+
+ if (port.startsWith("usb:"))
+ port = "usb:";
+
+ CameraType* ctype = new CameraType(model, model, port, "/", TQDateTime::currentDateTime());
+ insert(ctype);
+
+ return ctype;
+}
+
+void CameraList::clear()
+{
+
+ CameraType *ctype = d->clist.first();
+ while (ctype)
+ {
+ remove(ctype);
+ ctype = d->clist.first();
+ }
+}
+
+} // namespace Digikam
+
+
diff --git a/src/digikam/cameralist.h b/src/digikam/cameralist.h
new file mode 100644
index 00000000..65ecbcea
--- /dev/null
+++ b/src/digikam/cameralist.h
@@ -0,0 +1,84 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-03
+ * Description : Cameras list container
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERALIST_H
+#define CAMERALIST_H
+
+// TQt includes.
+
+#include <tqptrlist.h>
+#include <tqobject.h>
+
+class TQString;
+
+namespace Digikam
+{
+
+class CameraType;
+class CameraListPrivate;
+
+class CameraList : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ CameraList(TQObject *parent, const TQString& file);
+ ~CameraList();
+
+ bool load();
+ bool save();
+ void clear();
+
+ void insert(CameraType* ctype);
+ void remove(CameraType* ctype);
+
+ CameraType* find(const TQString& title);
+ CameraType* autoDetect(bool& retry);
+ TQPtrList<CameraType>* cameraList();
+
+ bool changeCameraAccessTime(const TQString& cameraTitle, const TQDateTime& newDate);
+
+ static CameraList* instance();
+
+signals:
+
+ void signalCameraAdded(CameraType*);
+ void signalCameraRemoved(CameraType*);
+
+private:
+
+ void insertPrivate(CameraType* ctype);
+ void removePrivate(CameraType* ctype);
+
+private:
+
+ static CameraList *m_instance;
+ CameraListPrivate *d;
+
+};
+
+} // namespace Digikam
+
+#endif /* CAMERALIST_H */
diff --git a/src/digikam/cameratype.cpp b/src/digikam/cameratype.cpp
new file mode 100644
index 00000000..28d8383a
--- /dev/null
+++ b/src/digikam/cameratype.cpp
@@ -0,0 +1,191 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-29
+ * Description : Camera settings container.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdeaction.h>
+
+// Local includes.
+
+#include "cameraui.h"
+#include "cameratype.h"
+
+namespace Digikam
+{
+
+class CameraTypePrivate
+{
+public:
+
+ CameraTypePrivate()
+ {
+ action = 0;
+ }
+
+ TQString title;
+ TQString model;
+ TQString port;
+ TQString path;
+
+ TQDateTime lastAccess;
+
+ TDEAction *action;
+ bool valid;
+
+ TQGuardedPtr<CameraUI> currentCameraUI;
+};
+
+CameraType::CameraType()
+{
+ d = new CameraTypePrivate;
+ d->valid = false;
+}
+
+CameraType::CameraType(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path,
+ const TQDateTime& lastAccess, TDEAction *action)
+{
+ d = new CameraTypePrivate;
+ d->title = title;
+ d->model = model;
+ d->port = port;
+ d->path = path;
+ d->action = action;
+ d->lastAccess = lastAccess;
+ d->valid = true;
+}
+
+CameraType::CameraType(const CameraType& ctype)
+{
+ d = new CameraTypePrivate;
+ d->title = ctype.d->title;
+ d->model = ctype.d->model;
+ d->port = ctype.d->port;
+ d->path = ctype.d->path;
+ d->action = ctype.d->action;
+ d->lastAccess = ctype.d->lastAccess;
+ d->valid = ctype.d->valid;
+}
+
+CameraType::~CameraType()
+{
+ delete d;
+}
+
+CameraType& CameraType::operator=(const CameraType& ctype)
+{
+ if (this != &ctype)
+ {
+ d->title = ctype.d->title;
+ d->model = ctype.d->model;
+ d->port = ctype.d->port;
+ d->path = ctype.d->path;
+ d->action = ctype.d->action;
+ d->lastAccess = ctype.d->lastAccess;
+ d->valid = ctype.d->valid;
+ }
+ return *this;
+}
+
+void CameraType::setTitle(const TQString& title)
+{
+ d->title = title;
+}
+
+void CameraType::setModel(const TQString& model)
+{
+ d->model = model;
+}
+
+void CameraType::setPort(const TQString& port)
+{
+ d->port = port;
+}
+
+void CameraType::setPath(const TQString& path)
+{
+ d->path = path;
+}
+
+void CameraType::setLastAccess(const TQDateTime& lastAccess)
+{
+ d->lastAccess = lastAccess;
+}
+
+void CameraType::setAction(TDEAction *action)
+{
+ d->action = action;
+}
+
+void CameraType::setValid(bool valid)
+{
+ d->valid = valid;
+}
+
+void CameraType::setCurrentCameraUI(CameraUI *cameraui)
+{
+ d->currentCameraUI = cameraui;
+}
+
+TQString CameraType::title() const
+{
+ return d->title;
+}
+
+TQString CameraType::model() const
+{
+ return d->model;
+}
+
+TQString CameraType::port() const
+{
+ return d->port;
+}
+
+TQString CameraType::path() const
+{
+ return d->path;
+}
+
+TQDateTime CameraType::lastAccess() const
+{
+ return d->lastAccess;
+}
+
+TDEAction* CameraType::action() const
+{
+ return d->action;
+}
+
+bool CameraType::valid() const
+{
+ return d->valid;
+}
+
+CameraUI *CameraType::currentCameraUI() const
+{
+ return d->currentCameraUI;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/cameratype.h b/src/digikam/cameratype.h
new file mode 100644
index 00000000..71ae0766
--- /dev/null
+++ b/src/digikam/cameratype.h
@@ -0,0 +1,81 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-29
+ * Description : Camera settings container.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERATYPE_H
+#define CAMERATYPE_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqdatetime.h>
+#include <tqguardedptr.h>
+
+class TDEAction;
+
+namespace Digikam
+{
+
+class CameraUI;
+class CameraTypePrivate;
+
+class CameraType
+{
+public:
+
+
+ CameraType();
+ CameraType(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path,
+ const TQDateTime& lastAccess, TDEAction* action=0);
+ ~CameraType();
+
+ CameraType(const CameraType& ctype);
+ CameraType& operator=(const CameraType& type);
+
+ void setTitle(const TQString& title);
+ void setModel(const TQString& model);
+ void setPort(const TQString& port);
+ void setPath(const TQString& path);
+ void setLastAccess(const TQDateTime& lastAccess);
+ void setAction(TDEAction *action);
+ void setValid(bool valid);
+ void setCurrentCameraUI(CameraUI *cameraui);
+
+ TQString title() const;
+ TQString model() const;
+ TQString port() const;
+ TQString path() const;
+ TQDateTime lastAccess() const;
+ TDEAction* action() const;
+ bool valid() const;
+ CameraUI *currentCameraUI() const;
+
+private:
+
+ CameraTypePrivate *d;
+};
+
+} // namespace Digikam
+
+#endif /* CAMERATYPE_H */
diff --git a/src/digikam/constants.h b/src/digikam/constants.h
new file mode 100644
index 00000000..63cbcc80
--- /dev/null
+++ b/src/digikam/constants.h
@@ -0,0 +1,37 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-04-03
+ * Description : Constants for Digikam-specific fields
+ *
+ * Copyright (C) 2007 by Brian Remedios <brianremedios at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+namespace Digikam
+{
+
+// Field value limits for all Digikam-specific fields (not EXIF/IPTC fields)
+
+static const int RatingMin = 0;
+static const int RatingMax = 5;
+
+} // namespace Digikam
+
+#endif /* CONSTANTS_H */
diff --git a/src/digikam/daboutdata.h b/src/digikam/daboutdata.h
new file mode 100644
index 00000000..5ff23f42
--- /dev/null
+++ b/src/digikam/daboutdata.h
@@ -0,0 +1,293 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-08
+ * Description : digiKam about data.
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008-2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DABOUT_DATA_H
+#define DABOUT_DATA_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <png.h>
+}
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Libkexiv2 includes.
+
+#include <libkexiv2/kexiv2.h>
+#include <libkexiv2/version.h>
+
+// Libkdcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+
+static const char digikam_version[] = "0.9.6";
+static const char showfoto_version[] = "0.9.6";
+
+namespace Digikam
+{
+static inline TQString libraryInfo()
+{
+#if KDCRAW_VERSION < 0x000106
+ TQString DcrawVer = KDcrawIface::DcrawBinary::internalVersion();
+#else
+ TQString librawVer = KDcrawIface::KDcraw::librawVersion();
+#endif
+
+ TQString Exiv2Ver = KExiv2Iface::KExiv2::Exiv2Version();
+
+ TQString Kexiv2Ver;
+
+#if KEXIV2_VERSION <= 0x000106
+ Kexiv2Ver = TQString(kexiv2_version);
+#else
+ Kexiv2Ver = KExiv2Iface::KExiv2::version();
+#endif
+
+ TQString libInfo =
+ TQString(I18N_NOOP("Using KExiv2 library version %1")).arg(Kexiv2Ver) +
+ TQString("\n") +
+ TQString(I18N_NOOP("Using Exiv2 library version %1")).arg(Exiv2Ver) +
+ TQString("\n") +
+ TQString(I18N_NOOP("Using KDcraw library version %1")).arg(KDcrawIface::KDcraw::version()) +
+ TQString("\n") +
+#if KDCRAW_VERSION < 0x000106
+ TQString(I18N_NOOP("Using Dcraw program version %1")).arg(DcrawVer) +
+#else
+ TQString(I18N_NOOP("Using LibRaw version %1")).arg(librawVer) +
+#endif
+ TQString("\n") +
+ TQString(I18N_NOOP("Using PNG library version %1")).arg(PNG_LIBPNG_VER_STRING);
+
+ return libInfo;
+}
+
+static inline const char* digiKamDescription()
+{
+ return I18N_NOOP("A Photo-Management Application for TDE");
+}
+
+static inline const char* showFotoDescription()
+{
+ return I18N_NOOP("TDE Photo Viewer and Editor");
+}
+
+static inline const char* themeDesignerDescription()
+{
+ return I18N_NOOP("A Color Theme Designer for digiKam");
+}
+
+static inline const char* copyright()
+{
+ return I18N_NOOP("(c) 2002-2009, digiKam developers team");
+}
+
+static inline const char* webProjectUrl()
+{
+ return "http://www.digikam.org";
+}
+
+static inline void authorsRegistration(TDEAboutData& aboutData)
+{
+ aboutData.addAuthor ( "Caulier Gilles",
+ I18N_NOOP("Main developer and coordinator"),
+ "caulier dot gilles at gmail dot com",
+ "http://www.digikam.org/?q=blog/3");
+
+ aboutData.addAuthor ( "Marcel Wiesweg",
+ I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de",
+ "http://www.digikam.org/?q=blog/8");
+
+ aboutData.addAuthor ( "Arnd Baecker",
+ I18N_NOOP("Developer"),
+ "arnd dot baecker at web dot de",
+ "http://www.digikam.org/?q=blog/133");
+
+ aboutData.addAuthor ( "Andi Clemens",
+ I18N_NOOP("Developer"),
+ "andi dot clemens at gmx dot net",
+ "http://www.digikam.org/?q=blog/135");
+
+ aboutData.addAuthor ( "Francisco J. Cruz",
+ I18N_NOOP("Developer"),
+ "fj dot cruz at supercable dot es",
+ "http://www.digikam.org/?q=blog/5");
+
+ aboutData.addAuthor ( "Renchi Raju",
+ I18N_NOOP("Developer (2002-2005)"),
+ "renchi at pooh dot tam dot uiuc dot edu",
+ 0);
+
+ aboutData.addAuthor ( "Joern Ahrens",
+ I18N_NOOP("Developer (2004-2005)"),
+ "kde at jokele dot de",
+ "http://www.digikam.org/?q=blog/1");
+
+ aboutData.addAuthor ( "Tom Albers",
+ I18N_NOOP("Developer (2004-2005)"),
+ "tomalbers at kde dot nl",
+ "http://www.omat.nl/drupal/?q=blog/1");
+
+ aboutData.addAuthor ( "Ralf Holzer (2004)",
+ I18N_NOOP("Developer"),
+ "kde at ralfhoelzer dot com",
+ 0);
+
+ aboutData.addCredit ( "Mikolaj Machowski",
+ I18N_NOOP("Bug reports and patches"),
+ "mikmach at wp dot pl",
+ 0);
+
+ aboutData.addCredit ( "Achim Bohnet",
+ I18N_NOOP("Bug reports and patches"),
+ "ach at mpe dot mpg dot de",
+ 0);
+
+ aboutData.addCredit ( "Luka Renko",
+ I18N_NOOP("Developer"),
+ "lure at kubuntu dot org",
+ 0);
+
+ aboutData.addCredit ( "Angelo Naselli",
+ I18N_NOOP("Developer"),
+ "anaselli at linux dot it",
+ 0);
+
+ aboutData.addCredit ( "Fabien Salvi",
+ I18N_NOOP("Webmaster"),
+ "fabien dot ubuntu at gmail dot com",
+ 0);
+
+ aboutData.addCredit ( "Todd Shoemaker",
+ I18N_NOOP("Developer"),
+ "todd at theshoemakers dot net",
+ 0);
+
+ aboutData.addCredit ( "Gregory Kokanosky",
+ I18N_NOOP("Developer"),
+ "gregory dot kokanosky at free dot fr",
+ 0);
+
+ aboutData.addCredit ( "Rune Laursen",
+ I18N_NOOP("Danish translations"),
+ "runerl at skjoldhoej dot dk",
+ 0);
+
+ aboutData.addCredit ( "Stefano Rivoir",
+ I18N_NOOP("Italian translations"),
+ "s dot rivoir at gts dot it",
+ 0);
+
+ aboutData.addCredit ( "Jan Toenjes",
+ I18N_NOOP("German translations"),
+ "jan dot toenjes at web dot de",
+ 0);
+
+ aboutData.addCredit ( "Oliver Doerr",
+ I18N_NOOP("German translations and beta tester"),
+ "oliver at doerr-privat dot de",
+ 0);
+
+ aboutData.addCredit ( "Quique",
+ I18N_NOOP("Spanish translations"),
+ "quique at sindominio dot net",
+ 0);
+
+ aboutData.addCredit ( "Marcus Meissner",
+ I18N_NOOP("Czech translations"),
+ "marcus at jet dot franken dot de",
+ 0);
+
+ aboutData.addCredit ( "Janos Tamasi",
+ I18N_NOOP("Hungarian translations"),
+ "janusz at vnet dot hu",
+ 0);
+
+ aboutData.addCredit ( "Jasper van der Marel",
+ I18N_NOOP("Dutch translations"),
+ "jasper dot van dot der dot marel at wanadoo dot nl",
+ 0);
+
+ aboutData.addCredit ( "Anna Sawicka",
+ I18N_NOOP("Polish translations"),
+ "ania at kajak dot org dot pl",
+ 0);
+
+ aboutData.addCredit ( "Charles Bouveyron",
+ I18N_NOOP("Beta tester"),
+ "c dot bouveyron at tuxfamily dot org",
+ 0);
+
+ aboutData.addCredit ( "Richard Groult",
+ I18N_NOOP("Plugin contributor and beta tester"),
+ "Richard dot Groult at jalix dot org",
+ 0);
+
+ aboutData.addCredit ( "Richard Taylor",
+ I18N_NOOP("Feedback and patches. Handbook writer"),
+ "rjt-digicam at thegrindstone dot me dot uk",
+ 0);
+
+ aboutData.addCredit ( "Hans Karlsson",
+ I18N_NOOP("digiKam website banner and application icons"),
+ "karlsson dot h at home dot se",
+ 0);
+
+ aboutData.addCredit ( "Aaron Seigo",
+ I18N_NOOP("Various usability fixes and general application polishing"),
+ "aseigo at kde dot org",
+ 0);
+
+ aboutData.addCredit ( "Yves Chaufour",
+ I18N_NOOP("digiKam website, Feedback"),
+ "yves dot chaufour at wanadoo dot fr",
+ 0);
+
+ aboutData.addCredit ( "Tung Nguyen",
+ I18N_NOOP("Bug reports, feedback and icons"),
+ "ntung at free dot fr",
+ 0);
+}
+
+} // namespace Digikam
+
+#endif // DABOUT_DATA_H
diff --git a/src/digikam/datefolderview.cpp b/src/digikam/datefolderview.cpp
new file mode 100644
index 00000000..8e36742e
--- /dev/null
+++ b/src/digikam/datefolderview.cpp
@@ -0,0 +1,482 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-27
+ * Description : a folder view for date albums.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqlistview.h>
+#include <tqfont.h>
+#include <tqpainter.h>
+#include <tqstyle.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+#include <tdeconfig.h>
+
+#include <tdeversion.h>
+#if KDE_IS_VERSION(3,2,0)
+#include <kcalendarsystem.h>
+#endif
+
+// Local includes.
+
+#include "album.h"
+#include "albumdb.h"
+#include "albumsettings.h"
+#include "ddebug.h"
+#include "folderview.h"
+#include "monthwidget.h"
+#include "datefolderview.h"
+#include "datefolderview.moc"
+
+namespace Digikam
+{
+
+class DateFolderItem : public FolderItem
+{
+
+public:
+
+ DateFolderItem(TQListView* parent, DAlbum* album);
+ DateFolderItem(TQListViewItem* parent, DAlbum* album);
+
+ ~DateFolderItem();
+
+ void refresh();
+
+ int compare(TQListViewItem *i, int, bool) const;
+ TQString date() const;
+ TQString name() const;
+
+ DAlbum* album() const;
+
+ int count() const;
+ void setCount(int v);
+
+private:
+
+ int m_count;
+
+ TQString m_name;
+
+ DAlbum *m_album;
+};
+
+DateFolderItem::DateFolderItem(TQListView* parent, DAlbum* album)
+ : FolderItem(parent, TQString(), true)
+{
+ m_count = 0;
+ m_album = album;
+ m_name = TQString::number(album->date().year());
+ setText(0, m_name);
+}
+
+DateFolderItem::DateFolderItem(TQListViewItem* parent, DAlbum* album)
+ : FolderItem(parent, TQString())
+{
+ m_count = 0;
+ m_album = album;
+#if KDE_IS_VERSION(3,2,0)
+ m_name = TDEGlobal::locale()->calendar()->monthName(m_album->date(), false);
+#else
+ m_name = TDEGlobal::locale()->monthName(m_album->date(), false);
+#endif
+ setText(0, m_name);
+}
+
+DateFolderItem::~DateFolderItem()
+{
+}
+
+void DateFolderItem::refresh()
+{
+ if (AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
+ setText(0, TQString("%1 (%2)").arg(m_name).arg(m_count));
+ else
+ setText(0, m_name);
+}
+
+int DateFolderItem::compare(TQListViewItem* i, int , bool ) const
+{
+ if (!i)
+ return 0;
+
+ DateFolderItem* dItem = dynamic_cast<DateFolderItem*>(i);
+ if (m_album->date() == dItem->m_album->date())
+ return 0;
+ else if (m_album->date() > dItem->m_album->date())
+ return 1;
+ else
+ return -1;
+}
+
+TQString DateFolderItem::date() const
+{
+ return m_album->date().toString();
+}
+
+TQString DateFolderItem::name() const
+{
+ return m_name;
+}
+
+DAlbum* DateFolderItem::album() const
+{
+ return m_album;
+}
+
+int DateFolderItem::count() const
+{
+ return m_count;
+}
+
+void DateFolderItem::setCount(int v)
+{
+ m_count = v;
+ refresh();
+}
+
+// -----------------------------------------------------------------
+
+class DateFolderViewPriv
+{
+public:
+
+ DateFolderViewPriv()
+ {
+ active = false;
+ listview = 0;
+ monthview = 0;
+ }
+
+ bool active;
+
+ TQString selected;
+
+ FolderView *listview;
+
+ MonthWidget *monthview;
+};
+
+DateFolderView::DateFolderView(TQWidget* parent)
+ : TQVBox(parent, "DateFolderView")
+{
+ d = new DateFolderViewPriv;
+ d->listview = new FolderView(this, "DateListView");
+ d->monthview = new MonthWidget(this);
+
+ d->listview->addColumn(i18n("My Calendar"));
+ d->listview->setResizeMode(TQListView::LastColumn);
+ d->listview->setRootIsDecorated(true);
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAllDAlbumsLoaded()),
+ this, TQ_SLOT(slotAllDAlbumsLoaded()));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumsCleared()),
+ d->listview, TQ_SLOT(clear()));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalDAlbumsDirty(const TQMap<YearMonth, int>&)),
+ this, TQ_SLOT(slotRefresh(const TQMap<YearMonth, int>&)));
+
+ connect(d->listview, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+}
+
+DateFolderView::~DateFolderView()
+{
+ saveViewState();
+ delete d;
+}
+
+void DateFolderView::setActive(bool val)
+{
+ if (d->active == val)
+ return;
+
+ d->active = val;
+ if (d->active)
+ {
+ slotSelectionChanged();
+ }
+ else
+ {
+ d->monthview->setActive(false);
+ }
+}
+
+void DateFolderView::slotAllDAlbumsLoaded()
+{
+ disconnect(AlbumManager::instance(), TQ_SIGNAL(signalAllDAlbumsLoaded()),
+ this, TQ_SLOT(slotAllDAlbumsLoaded()));
+ loadViewState();
+}
+
+void DateFolderView::slotAlbumAdded(Album* a)
+{
+ if (!a || a->type() != Album::DATE)
+ return;
+
+ DAlbum* album = (DAlbum*)a;
+ TQDate date = album->date();
+
+ if (album->range() == DAlbum::Year)
+ {
+ DateFolderItem* item = new DateFolderItem(d->listview, album);
+ item->setPixmap(0, SmallIcon("date", AlbumSettings::instance()->getDefaultTreeIconSize()));
+ album->setExtraData(this, item);
+ return;
+ }
+
+ TQString yr = TQString::number(date.year());
+ TQListViewItem* parent = findRootItemByYear(yr);
+
+ if (parent)
+ {
+ DateFolderItem* item = new DateFolderItem(parent, album);
+ item->setPixmap(0, SmallIcon("date", AlbumSettings::instance()->getDefaultTreeIconSize()));
+ album->setExtraData(this, item);
+ }
+}
+
+void DateFolderView::slotAlbumDeleted(Album* a)
+{
+ if (!a || a->type() != Album::DATE)
+ return;
+
+ DAlbum* album = (DAlbum*)a;
+
+ DateFolderItem* item = (DateFolderItem*) album->extraData(this);
+ if (item)
+ {
+ delete item;
+ album->removeExtraData(this);
+ }
+}
+
+void DateFolderView::slotSelectionChanged()
+{
+ if (!d->active)
+ return;
+
+ d->monthview->setActive(false);
+
+ TQListViewItem* selItem = 0;
+ TQListViewItemIterator it( d->listview );
+ while (it.current())
+ {
+ if (it.current()->isSelected())
+ {
+ selItem = it.current();
+ break;
+ }
+ ++it;
+ }
+
+ if (!selItem)
+ {
+ AlbumManager::instance()->setCurrentAlbum(0);
+ return;
+ }
+
+ DateFolderItem* dateItem = dynamic_cast<DateFolderItem*>(selItem);
+ if (!dateItem)
+ {
+ AlbumManager::instance()->setCurrentAlbum(0);
+ return;
+ }
+
+ AlbumManager::instance()->setCurrentAlbum(dateItem->album());
+
+ if (dateItem->album()->range() == DAlbum::Month)
+ {
+ TQDate date = dateItem->album()->date();
+ d->monthview->setActive(true);
+ d->monthview->setYearMonth(date.year(), date.month());
+ }
+}
+
+void DateFolderView::loadViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ TQString selected;
+ if(config->hasKey("Last Selected Date"))
+ {
+ selected = config->readEntry("Last Selected Date");
+ }
+
+ TQStringList openFolders;
+ if(config->hasKey("Open Date Folders"))
+ {
+ openFolders = config->readListEntry("Open Date Folders");
+ }
+
+ DateFolderItem *item;
+ TQString id;
+ TQListViewItemIterator it(d->listview);
+ for( ; it.current(); ++it)
+ {
+ item = dynamic_cast<DateFolderItem*>(it.current());
+ id = item->date();
+ if(openFolders.contains(id))
+ d->listview->setOpen(item, true);
+ else
+ d->listview->setOpen(item, false);
+
+ if(id == selected)
+ d->listview->setSelected(item, true);
+ }
+}
+
+void DateFolderView::saveViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ DateFolderItem *item = dynamic_cast<DateFolderItem*>(d->listview->selectedItem());
+ if(item)
+ config->writeEntry("Last Selected Date", item->date());
+
+ TQStringList openFolders;
+ TQListViewItemIterator it(d->listview);
+ item = dynamic_cast<DateFolderItem*>(d->listview->firstChild());
+ while(item)
+ {
+ // Storing the years only, a month cannot be open
+ if(item && d->listview->isOpen(item))
+ openFolders.push_back(item->date());
+ item = dynamic_cast<DateFolderItem*>(item->nextSibling());
+ }
+ config->writeEntry("Open Date Folders", openFolders);
+}
+
+void DateFolderView::gotoDate(const TQDate& dt)
+{
+ DateFolderItem *item = 0;
+ TQDate id;
+
+ TQDate date = TQDate(dt.year(), dt.month(), 1);
+
+ // Find that date in the side-bar list.
+ TQListViewItemIterator it(d->listview);
+ for( ; it.current(); ++it)
+ {
+ item = dynamic_cast<DateFolderItem*>(it.current());
+ if (item->album())
+ {
+ id = item->album()->date();
+ if(id == date)
+ {
+ d->listview->setSelected(item, true);
+ d->listview->ensureItemVisible(item);
+ }
+ }
+ }
+}
+
+void DateFolderView::setSelected(TQListViewItem *item)
+{
+ if(!item)
+ return;
+
+ d->listview->setSelected(item, true);
+ d->listview->ensureItemVisible(item);
+}
+
+TQListViewItem *DateFolderView::findRootItemByYear(const TQString& year)
+{
+ TQListViewItemIterator it(d->listview);
+
+ while (it.current())
+ {
+ DateFolderItem* item = dynamic_cast<DateFolderItem*>(*it);
+ if (item)
+ {
+ if (item->album()->range() == DAlbum::Year && item->name() == year)
+ return item;
+ }
+ ++it;
+ }
+ return 0;
+}
+
+void DateFolderView::refresh()
+{
+ TQListViewItemIterator it(d->listview);
+
+ while (it.current())
+ {
+ DateFolderItem* item = dynamic_cast<DateFolderItem*>(*it);
+ if (item)
+ item->refresh();
+ ++it;
+ }
+}
+
+void DateFolderView::slotRefresh(const TQMap<YearMonth, int>& yearMonthMap)
+{
+ TQListViewItemIterator it(d->listview);
+
+ while (it.current())
+ {
+ DateFolderItem* item = dynamic_cast<DateFolderItem*>(*it);
+ if (item)
+ {
+ TQDate date = item->album()->date();
+
+ if (item->album()->range() == DAlbum::Month)
+ {
+ TQMap<YearMonth, int>::const_iterator it2 = yearMonthMap.find(YearMonth(date.year(), date.month()));
+ if ( it2 != yearMonthMap.end() )
+ item->setCount(it2.data());
+ }
+ else
+ {
+ int count = 0;
+ for ( TQMap<YearMonth, int>::const_iterator it2 = yearMonthMap.begin();
+ it2 != yearMonthMap.end(); ++it2 )
+ {
+ if (it2.key().first == date.year())
+ count += it2.data();
+ }
+ item->setCount(count);
+ }
+ }
+ ++it;
+ }
+}
+
+} // namespace Digikam
diff --git a/src/digikam/datefolderview.h b/src/digikam/datefolderview.h
new file mode 100644
index 00000000..2e467fe3
--- /dev/null
+++ b/src/digikam/datefolderview.h
@@ -0,0 +1,88 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-27
+ * Description : a folder view for date albums.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DATEFOLDERVIEW_H
+#define DATEFOLDERVIEW_H
+
+// TQt includes.
+
+#include <tqvbox.h>
+
+// Local includes.
+
+#include "albummanager.h"
+#include "folderitem.h"
+
+class TQListViewItem;
+
+namespace Digikam
+{
+
+class DateFolderViewPriv;
+class DAlbum;
+
+class DateFolderView : public TQVBox
+{
+ TQ_OBJECT
+
+public:
+
+ DateFolderView(TQWidget* parent);
+ ~DateFolderView();
+
+ void setActive(bool val);
+
+ void setSelected(TQListViewItem *item);
+
+ void gotoDate(const TQDate& dt);
+ void refresh();
+
+private slots:
+
+ void slotAllDAlbumsLoaded();
+ void slotAlbumAdded(Album* album);
+ void slotAlbumDeleted(Album* album);
+ void slotSelectionChanged();
+ void slotRefresh(const TQMap<YearMonth, int>&);
+
+private:
+
+ /**
+ * load the last view state from disk
+ */
+ void loadViewState();
+
+ /**
+ * writes the view state to disk
+ */
+ void saveViewState();
+
+ TQListViewItem *findRootItemByYear(const TQString& year);
+
+ DateFolderViewPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* DATEFOLDERVIEW_H */
diff --git a/src/digikam/dcopiface.cpp b/src/digikam/dcopiface.cpp
new file mode 100644
index 00000000..49f051d9
--- /dev/null
+++ b/src/digikam/dcopiface.cpp
@@ -0,0 +1,52 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-12
+ * Description : a DCOP interface.
+ *
+ * Copyright (C) 2005 by Leonid Zeitlin <lz@europe.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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "dcopiface.h"
+#include "dcopiface.moc"
+
+namespace Digikam
+{
+
+DCOPIface::DCOPIface(TQObject *parent, const char *name)
+ : TQObject(parent, name), DCOPObject(name)
+{
+}
+
+DCOPIface::~DCOPIface()
+{
+}
+
+void DCOPIface::detectCamera()
+{
+ emit signalCameraAutoDetect();
+}
+
+void DCOPIface::downloadFrom( const TQString &folder)
+{
+ emit signalDownloadImages( folder );
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/dcopiface.h b/src/digikam/dcopiface.h
new file mode 100644
index 00000000..9367e6b1
--- /dev/null
+++ b/src/digikam/dcopiface.h
@@ -0,0 +1,96 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-12
+ * Description : a DCROP interface.
+ *
+ * Copyright (C) 2005 by Leonid Zeitlin <lz@europe.com>
+ * Copyright (C) 2006 Tom Albers <tomalbers@kde.nl>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DCOPIFACE_H
+#define DCOPIFACE_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <dcopobject.h>
+
+namespace Digikam
+{
+
+/**
+* This class implements a DCOP interface for DigiKam.
+* At the moment it supports only one method, @ref cameraAutoDetect to open camera dialog
+*
+* @short DCOP interface for DigiKam
+* @author Leonid Zeitlin
+*/
+
+class DCOPIface : public TQObject, public DCOPObject
+{
+ K_DCOP
+ TQ_OBJECT
+
+public:
+
+ /**
+ * Standard constructor.
+ * @param parent Parent object reference, passed to @ref TQObject constructor
+ * @param name Specifis the name of the object, passed to @ref TQObject constructor
+ */
+ DCOPIface(TQObject *parent = 0, const char *name = 0);
+
+ /**
+ * Standard destructor
+ */
+ ~DCOPIface();
+
+signals:
+
+ /**
+ * This signal is emitted when @ref cameraAutoDetect() is called via DCOP
+ */
+ void signalCameraAutoDetect();
+
+ /**
+ * This signal is emitted when @ref downloadFrom() is called via DCOP
+ * @param folder the path passed tp downloadFrom earlier
+ */
+ void signalDownloadImages( const TQString& folder);
+
+
+public:
+
+k_dcop:
+ /**
+ * This method can be called via DCOP to auto-detect attached camera and show DigiKam camera dialog
+ * For example, a hotplug script can call it when a USB camera is attached to the computer
+ */
+ ASYNC detectCamera();
+
+ /**
+ * This method can be called via DCOP to auto-detect attached camera and
+ * show DigiKam camera dialog. For example, a hotplug script can call it
+ * when a USB camera is attached to the computer
+ */
+ ASYNC downloadFrom( const TQString &folder );
+};
+
+} // namespace Digikam
+
+#endif // DCOPIFACE_H
diff --git a/src/digikam/digikam.desktop b/src/digikam/digikam.desktop
new file mode 100644
index 00000000..ba05db76
--- /dev/null
+++ b/src/digikam/digikam.desktop
@@ -0,0 +1,159 @@
+[Desktop Entry]
+Type=Application
+Categories=Qt;TDE;Graphics;
+Exec=digikam -caption "%c" %i
+Icon=digikam
+X-DocPath=digikam/index.html
+Name=DigiKam
+Name[ast]=DigiKam
+Name[be]=DigiKam
+Name[bg]=DigiKam
+Name[bs]=DigiKam
+Name[ca]=DigiKam
+Name[ca@valencia]=DigiKam
+Name[cs]=DigiKam
+Name[da]=DigiKam
+Name[de]=DigiKam
+Name[el]=DigiKam
+Name[en_GB]=DigiKam
+Name[es]=DigiKam
+Name[et]=DigiKam
+Name[eu]=DigiKam
+Name[fi]=DigiKam
+Name[fr]=DigiKam
+Name[ga]=DigiKam
+Name[gl]=DigiKam
+Name[he]=DigiKam
+Name[hne]=डिजीकैम
+Name[hr]=DigiKam
+Name[hu]=DigiKam
+Name[is]=DigiKam
+Name[it]=DigiKam
+Name[ja]=DigiKam
+Name[km]=DigiKam ​
+Name[lt]=DigiKam
+Name[lv]=DigiKam
+Name[mai]=DigiKam
+Name[mr]=डिजिकॅम
+Name[ms]=DigiKam
+Name[nb]=DigiKam
+Name[nds]=DigiKam
+Name[ne]=डिजिक्याम
+Name[nl]=DigiKam
+Name[nn]=DigiKam
+Name[pa]=ਡਿਜਿਕੈਮ
+Name[pl]=DigiKam
+Name[pt]=DigiKam
+Name[pt_BR]=DigiKam
+Name[ro]=DigiKam
+Name[ru]=DigiKam
+Name[sk]=DigiKam
+Name[sl]=DigiKam
+Name[sq]=DigiKam
+Name[sv]=Digikam
+Name[th]=ดิจิแคม
+Name[tr]=DigiKam
+Name[ug]=DigiKam
+Name[uk]=DigiKam
+Name[vi]=DigiKam
+Name[x-test]=xxDigiKamxx
+Name[zh_CN]=DigiKam
+Name[zh_TW]=DigiKam
+GenericName=Photo Management Program
+GenericName[ast]=Programa de xestión de semeyes
+GenericName[bg]=Управление на снимки в KDE
+GenericName[bs]=Program za upravljanje fotografijama
+GenericName[ca]=Programa de gestió de fotografies
+GenericName[ca@valencia]=Programa de gestió de fotografies
+GenericName[cs]=Program pro správu fotografií
+GenericName[da]=Program til fotohåndtering
+GenericName[de]=Fotoverwaltung
+GenericName[el]=Πρόγραμμα διαχείρισης φωτογραφιών
+GenericName[en_GB]=Photo Management Program
+GenericName[es]=Programa de gestión de fotos
+GenericName[et]=Fotohaldur
+GenericName[eu]=Argazkiak kudeatzeko programa
+GenericName[fi]=Valokuva–hallintaohjelma
+GenericName[fr]=Programme de gestion de photos
+GenericName[ga]=Clár Bainisteoireachta Grianghraf
+GenericName[gl]=Xestor de álbums de fotos
+GenericName[hne]=फोटो प्रबंधन प्रोग्राम
+GenericName[hr]=Program za upravljanje fotografijama
+GenericName[hu]=Fényképkezelő program
+GenericName[is]=Ljósmyndameðhöndlunarforrit
+GenericName[it]=Programma di gestione fotografica
+GenericName[ja]=フォト管理プログラム
+GenericName[km]=កម្មវិធី​គ្រប់គ្រង​ទូរស័ព្ទ
+GenericName[lt]=Nuotraukų tvarkymo programa
+GenericName[lv]=Fotogrāfiju pārvaldības programma
+GenericName[mr]=फोटो व्यवस्थापन कार्यक्रम
+GenericName[ms]=Program Pengurusan Gambar
+GenericName[nb]=KDE Fotobehandling
+GenericName[nds]=Fotopleeg-Programm
+GenericName[nl]=Fotobeheerprogramma
+GenericName[nn]=Fotohandsamingsprogram
+GenericName[pa]=ਫੋਟੋ ਪਰਬੰਧ ਪਰੋਗਰਾਮ
+GenericName[pl]=Program do zarządzania zdjęciami
+GenericName[pt]=Programa de Gestão de Fotos
+GenericName[pt_BR]=Programa de gerenciamento de fotos
+GenericName[ro]=Program pentru gestionarea fotografiilor
+GenericName[ru]=Программа управления фотографиями
+GenericName[sk]=Program na správu fotografií
+GenericName[sl]=Program za upravljanje fotografij
+GenericName[sq]=Program Për Menaxhimin e Fotove
+GenericName[sv]=Fotohanteringsprogram
+GenericName[th]=เครื่องมือจัดการภาพถ่าย
+GenericName[tr]=Fotoğraf Yönetim Aracı
+GenericName[ug]=سۈرەت باشقۇرۇش باشقۇرۇش پروگراممىسى
+GenericName[uk]=Програма для керування фотографіями
+GenericName[x-test]=xxPhoto Management Programxx
+GenericName[zh_CN]=照片管理程序
+GenericName[zh_TW]=相片管理程式
+Comment=Manage your photographs like a professional with the power of open source
+Comment[ast]=Xestiona les tos semeyes como un profesional usando software llibre
+Comment[bg]=Обработвайте снимките си професионално с помощта на свободния софтуер
+Comment[bs]=Upravljajte fotografijama kao profesionalac snagom otvorenog izvornog koda
+Comment[ca]=Gestioneu les vostres fotografies com un professional amb la potència del programari lliure
+Comment[ca@valencia]=Gestioneu les vostres fotografies com un professional amb la potència del programari lliure
+Comment[cs]=Spravujte své fotografie jako profesionál pomocí open source
+Comment[da]=Håndtér dine fotografier som en professionel med kraften fra open source
+Comment[de]=Verwalten Sie Ihre Bilder wie ein Profi mit allen Möglichkeiten von Open Source.
+Comment[el]=Διαχειριστείτε τις φωτογραφίες σας όπως οι επαγγελματίες με τη δύναμη του ελεύθερου λογισμικού
+Comment[en_GB]=Manage your photographs like a professional with the power of open source
+Comment[es]=Gestione sus fotos como un profesional usando software libre
+Comment[et]=Fotode haldamine profina avatud lähtekoodiga tarkvara võimsust kasutades
+Comment[eu]=Kudeatu zure argazkiak profesional baten antzera iturburu irekiaren indarrarekin
+Comment[fi]=Ammattilaistasoinen avoimen lähdekoodin ohjelma valokuvien hallintaan
+Comment[fr]=Gérez vos photos comme un professionnel avec la puissance de l'« Open Source »
+Comment[gl]=Xestione as súas fotografías como un profesional coa potencia do software de fontes abertas
+Comment[hne]=ओपन सोर्स के पावर से आप मन अपन फोटो ल प्रोफेसनल जइसन मैनेज कर सकथो
+Comment[hr]=Upravljajte svojim fotografijama kao profesionalac uz moć slobodnog softvera
+Comment[hu]=Kezelje fényképeit profiként a nyílt forrás erejével
+Comment[is]=Sýslaðu með ljósmyndirnar þínar í krafti opins hugbúnaðar
+Comment[it]=Gestisci le tue fotografie come un professionista con la forza del software libero
+Comment[ja]=オープンソースの力であなたの写真をプロのように管理します
+Comment[km]=គ្រប់គ្រង​​រូបថត​របស់​អ្នក​ ដូច​ជា​អ្នក​អាជីព​ដែល​មាន​ថាម​ពល​លើ​កម្មវិធី​ប្រភព​កូដ​ចំហ
+Comment[lt]=Tvarkykite savo nuotraukas kaip profesionalas su atviro kodo jėga
+Comment[lv]=Pārvaldiet savas fotogrāfijas kā profesionālis, izmantojot atvērtā pirmkoda spēku
+Comment[mr]=ओपन सोर्सच्या बळाने तुमचे फोटो व्यावसायिकाप्रमाणे व्यवस्थापीत करा
+Comment[nb]=Håndter dine fotografier som en proff med kraften i åpen programvare
+Comment[nds]=Pleeg Dien Fotos as'n Fachmann. - Mit de Knööv vun Apenborn.
+Comment[nl]=Beheer uw foto's als een professional met de kracht van opensource
+Comment[pa]=ਆਪਣੀਆਂ ਫੋਟੋਆਂ ਨੂੰ ਇੱਕ ਪਰੋਫੈਸ਼ਨਲ ਵਾਂਗ ਓਪਨ ਸੋਰਸ ਦੀ ਮੱਦਦ ਨਾਲ ਸੰਭਾਲੋ
+Comment[pl]=Zarządzaj swoimi zdjęciami jak profesjonalista z mocą open source
+Comment[pt]=Faça a gestão das suas fotografias como um profissional, graças ao 'software' livre
+Comment[pt_BR]=Gerencie suas fotografias como um profissional, com o poder do código aberto
+Comment[ro]=Gestionați-vă fotografiile ca un profesionist profitînd de puterea aplicațiilor cu sursă deschisă
+Comment[ru]=Управляйте своей коллекцией фотографий на профессиональном уровне с помощью свободного программного обеспечения
+Comment[sk]=Spravujte svoje fotografie ako profesionál pomocou open source
+Comment[sl]=Upravljajte s svojimi fotografijami kot profesionalec z močjo odprte kode
+Comment[sq]=Menaxho fotografitë e tua si një profesionist me fuqinë e burimit të hapur
+Comment[sv]=Hantera dina fotografier som ett proffs med kraftfull öppen källkod
+Comment[th]=จัดการภาพถ่ายของคุณดั่งมืออาชีพ ด้วยพลังสร้างสรรค์ของโอเพนซอร์ส
+Comment[tr]=Açık kaynağın gücüyle fotoğraflarınızı bir profesyonel gibi düzenleyin
+Comment[ug]=ئوچۇق مەنبەلىك يۇمشاق دېتاللارنىڭ كۈچىدىن پايدىلىنىپ سۈرەتلىرىڭىزنى كەسپىي خادىملاردەك باشقۇرالايسىز
+Comment[uk]=Керуйте вашими фотографіями як професіонал за допомогою вільного програмного забезпечення
+Comment[x-test]=xxManage your photographs like a professional with the power of open sourcexx
+Comment[zh_CN]=专业照片管理 彰显开源力量
+Comment[zh_TW]=運用開放原始碼的力量像專業人士般管理您的照片
+Terminal=false
diff --git a/src/digikam/digikam_export.h b/src/digikam/digikam_export.h
new file mode 100644
index 00000000..0d221918
--- /dev/null
+++ b/src/digikam/digikam_export.h
@@ -0,0 +1,45 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-31-01
+ * Description : gcc export extension support
+ *
+ * Copyright (c) 2005 Laurent Montel <montel@kde.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef _DIGIKAM_EXPORT_H
+#define _DIGIKAM_EXPORT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef __TDE_HAVE_GCC_VISIBILITY
+#define DIGIKAM_EXPORT __attribute__ ((visibility("default")))
+#define DIGIKAMIMAGEPLUGINS_EXPORT DIGIKAM_EXPORT
+#define DIGIKAMIMAGEEDITOR_EXPORT DIGIKAM_EXPORT
+#define DIGIKAMIMAGEFILTERS_EXPORT DIGIKAM_EXPORT
+#define DIGIKAMIMAGEWIDGET_EXPORT DIGIKAM_EXPORT
+#else
+#define DIGIKAM_EXPORT
+#define DIGIKAMIMAGEPLUGINS_EXPORT
+#define DIGIKAMIMAGEEDITOR_EXPORT
+#define DIGIKAMIMAGEFILTERS_EXPORT
+#define DIGIKAMIMAGEWIDGET_EXPORT
+#endif
+#endif /* _DIGIKAM_EXPORT_H */
+
diff --git a/src/digikam/digikamapp.cpp b/src/digikam/digikamapp.cpp
new file mode 100644
index 00000000..5b12575a
--- /dev/null
+++ b/src/digikam/digikamapp.cpp
@@ -0,0 +1,2094 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2002-16-10
+ * Description : main digiKam interface implementation
+ *
+ * Copyright (C) 2002-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2002-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatastream.h>
+#include <tqlabel.h>
+#include <tqstringlist.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqsignalmapper.h>
+#include <tqdockarea.h>
+#include <tqhbox.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+#include <kurl.h>
+#include <kstdaction.h>
+#include <tdestdaccel.h>
+#include <kkeydialog.h>
+#include <kedittoolbar.h>
+#include <kiconloader.h>
+#include <ktip.h>
+#include <tdeversion.h>
+#include <tdeapplication.h>
+#include <tdemenubar.h>
+#include <tdeglobalsettings.h>
+#include <tdefiledialog.h>
+#include <tdemessagebox.h>
+#include <twin.h>
+#include <kimageio.h>
+#include <dcopref.h>
+
+// libKipi includes.
+
+#include <libkipi/plugin.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dlogoaction.h"
+#include "album.h"
+#include "albumlister.h"
+#include "albumthumbnailloader.h"
+#include "albumiconviewfilter.h"
+#include "cameratype.h"
+#include "cameraui.h"
+#include "setup.h"
+#include "setupplugins.h"
+#include "setupeditor.h"
+#include "setupicc.h"
+#include "rawcameradlg.h"
+#include "lighttablewindow.h"
+#include "imagewindow.h"
+#include "imageinfo.h"
+#include "thumbnailsize.h"
+#include "themeengine.h"
+#include "scanlib.h"
+#include "loadingcacheinterface.h"
+#include "imageattributeswatch.h"
+#include "batchthumbsgenerator.h"
+#include "batchalbumssyncmetadata.h"
+#include "dcopiface.h"
+#include "digikamappprivate.h"
+#include "digikamapp.h"
+#include "digikamapp.moc"
+
+using TDEIO::Job;
+using TDEIO::UDSEntryList;
+using TDEIO::UDSEntry;
+
+namespace Digikam
+{
+
+DigikamApp* DigikamApp::m_instance = 0;
+
+DigikamApp::DigikamApp()
+ : TDEMainWindow( 0, "Digikam" )
+{
+ d = new DigikamAppPriv;
+ m_instance = this;
+ d->config = kapp->config();
+ d->config->setGroup("General Settings");
+
+ if(d->config->readBoolEntry("Show Splash", true) &&
+ !kapp->isRestored())
+ {
+ d->splashScreen = new SplashScreen("digikam-splash.png");
+ d->splashScreen->show();
+ }
+
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Initializing..."));
+
+ // Register image formats (especially for TIFF )
+ KImageIO::registerFormats();
+
+ d->albumSettings = new AlbumSettings();
+ d->albumSettings->readSettings();
+
+ d->albumManager = new Digikam::AlbumManager();
+
+ AlbumLister::instance();
+
+ d->cameraMediaList = new TDEPopupMenu;
+
+ connect(d->cameraMediaList, TQ_SIGNAL( aboutToShow() ),
+ this, TQ_SLOT(slotCameraMediaMenu()));
+
+ d->cameraList = new CameraList(this, locateLocal("appdata", "cameras.xml"));
+
+ connect(d->cameraList, TQ_SIGNAL(signalCameraAdded(CameraType *)),
+ this, TQ_SLOT(slotCameraAdded(CameraType *)));
+
+ connect(d->cameraList, TQ_SIGNAL(signalCameraRemoved(CameraType *)),
+ this, TQ_SLOT(slotCameraRemoved(CameraType *)));
+
+ setupView();
+ setupStatusBar();
+ setupAccelerators();
+ setupActions();
+
+ applyMainWindowSettings(d->config);
+
+ // Check ICC profiles repository availability
+
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Checking ICC repository"));
+
+ d->validIccPath = SetupICC::iccRepositoryIsValid();
+
+#if KDCRAW_VERSION < 0x000106
+ // Check witch dcraw version available
+
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Checking dcraw version"));
+
+ KDcrawIface::DcrawBinary::instance()->checkSystem();
+#endif
+
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Scan Albums"));
+
+ d->albumManager->setLibraryPath(d->albumSettings->getAlbumLibraryPath(), d->splashScreen);
+
+ // Read albums from database
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Reading database"));
+
+ d->albumManager->startScan();
+
+ // Load KIPI Plugins.
+ loadPlugins();
+
+ // Load Themes
+ populateThemes();
+
+ setAutoSaveSettings();
+
+ d->dcopIface = new DCOPIface(this, "camera");
+
+ connect(d->dcopIface, TQ_SIGNAL(signalCameraAutoDetect()),
+ this, TQ_SLOT(slotDcopCameraAutoDetect()));
+
+ connect(d->dcopIface, TQ_SIGNAL(signalDownloadImages( const TQString & )),
+ this, TQ_SLOT(slotDcopDownloadImages(const TQString &)));
+}
+
+DigikamApp::~DigikamApp()
+{
+ ImageAttributesWatch::shutDown();
+
+ // Close and delete image editor instance.
+
+ if (ImageWindow::imagewindowCreated())
+ ImageWindow::imagewindow()->close(true);
+
+ // Close and delete light table instance.
+
+ if (LightTableWindow::lightTableWindowCreated())
+ LightTableWindow::lightTableWindow()->close(true);
+
+ if (d->view)
+ delete d->view;
+
+ d->albumIconViewFilter->saveSettings();
+ d->albumSettings->setRecurseAlbums(d->recurseAlbumsAction->isChecked());
+ d->albumSettings->setRecurseTags(d->recurseTagsAction->isChecked());
+ d->albumSettings->saveSettings();
+ delete d->albumSettings;
+
+ delete d->albumManager;
+ delete AlbumLister::instance();
+
+ ImageAttributesWatch::cleanUp();
+ LoadingCacheInterface::cleanUp();
+#if KDCRAW_VERSION < 0x000106
+ KDcrawIface::DcrawBinary::cleanUp();
+#endif
+ AlbumThumbnailLoader::cleanUp();
+
+ m_instance = 0;
+
+ delete d;
+}
+
+DigikamApp* DigikamApp::getinstance()
+{
+ return m_instance;
+}
+
+void DigikamApp::show()
+{
+ // Remove Splashscreen.
+
+ if(d->splashScreen)
+ {
+ d->splashScreen->finish(this);
+ delete d->splashScreen;
+ d->splashScreen = 0;
+ }
+
+ // Display application window.
+
+ TDEMainWindow::show();
+
+ // Report errors from ICC repository path.
+
+ if(!d->validIccPath)
+ {
+ TQString message = i18n("<qt><p>ICC profiles path seems to be invalid.</p>"
+ "<p>If you want to set it now, select \"Yes\", otherwise "
+ "select \"No\". In this case, \"Color Management\" feature "
+ "will be disabled until you solve this issue</p></qt>");
+
+ if (KMessageBox::warningYesNo(this, message) == KMessageBox::Yes)
+ {
+ if (!setup(true))
+ {
+ d->config->setGroup("Color Management");
+ d->config->writeEntry("EnableCM", false);
+ d->config->sync();
+ }
+ }
+ else
+ {
+ d->config->setGroup("Color Management");
+ d->config->writeEntry("EnableCM", false);
+ d->config->sync();
+ }
+ }
+
+#if KDCRAW_VERSION < 0x000106
+ // Report errors from dcraw detection.
+ KDcrawIface::DcrawBinary::instance()->checkReport();
+#endif
+
+ // Init album icon view zoom factor.
+ slotThumbSizeChanged(d->albumSettings->getDefaultIconSize());
+}
+
+const TQPtrList<TDEAction>& DigikamApp::menuImageActions()
+{
+ return d->kipiImageActions;
+}
+
+const TQPtrList<TDEAction>& DigikamApp::menuBatchActions()
+{
+ return d->kipiBatchActions;
+}
+
+const TQPtrList<TDEAction>& DigikamApp::menuAlbumActions()
+{
+ return d->kipiAlbumActions;
+}
+
+const TQPtrList<TDEAction> DigikamApp::menuImportActions()
+{
+ TQPtrList<TDEAction> importMenu;
+ importMenu = d->kipiFileActionsImport;
+ importMenu.append( d->albumImportAction );
+ importMenu.append( d->addImagesAction );
+ return importMenu;
+}
+
+const TQPtrList<TDEAction> DigikamApp::menuExportActions()
+{
+ return d->kipiFileActionsExport;
+}
+
+void DigikamApp::autoDetect()
+{
+ // Called from main if command line option is set
+
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Auto-detect camera"));
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotCameraAutoDetect()));
+}
+
+void DigikamApp::downloadFrom(const TQString &cameraGuiPath)
+{
+ // Called from main if command line option is set
+
+ if (!cameraGuiPath.isNull())
+ {
+ d->cameraGuiPath = cameraGuiPath;
+
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Opening Download Dialog"));
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotDownloadImages()));
+ }
+}
+
+bool DigikamApp::queryClose()
+{
+ if (ImageWindow::imagewindowCreated())
+ {
+ return ImageWindow::imagewindow()->queryClose();
+ }
+ else
+ return true;
+}
+
+void DigikamApp::setupView()
+{
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Initializing Main View"));
+
+ d->view = new DigikamView(this);
+ setCentralWidget(d->view);
+ d->view->applySettings();
+
+ connect(d->view, TQ_SIGNAL(signalAlbumSelected(bool)),
+ this, TQ_SLOT(slotAlbumSelected(bool)));
+
+ connect(d->view, TQ_SIGNAL(signalTagSelected(bool)),
+ this, TQ_SLOT(slotTagSelected(bool)));
+
+ connect(d->view, TQ_SIGNAL(signalImageSelected(const TQPtrList<ImageInfo>&, bool, bool, const KURL::List&)),
+ this, TQ_SLOT(slotImageSelected(const TQPtrList<ImageInfo>&, bool, bool, const KURL::List&)));
+}
+
+void DigikamApp::setupStatusBar()
+{
+ d->statusProgressBar = new StatusProgressBar(statusBar());
+ d->statusProgressBar->setAlignment(TQt::AlignLeft|TQt::AlignVCenter);
+ d->statusProgressBar->setMaximumHeight(fontMetrics().height()+4);
+ statusBar()->addWidget(d->statusProgressBar, 100, true);
+
+ //------------------------------------------------------------------------------
+
+ d->albumIconViewFilter = new AlbumIconViewFilter(statusBar());
+ d->albumIconViewFilter->setMaximumHeight(fontMetrics().height()+4);
+ statusBar()->addWidget(d->albumIconViewFilter, 100, true);
+
+ //------------------------------------------------------------------------------
+
+ d->statusZoomBar = new StatusZoomBar(statusBar());
+ d->statusZoomBar->setMaximumHeight(fontMetrics().height()+4);
+ statusBar()->addWidget(d->statusZoomBar, 1, true);
+
+ //------------------------------------------------------------------------------
+
+ d->statusNavigateBar = new StatusNavigateBar(statusBar());
+ d->statusNavigateBar->setMaximumHeight(fontMetrics().height()+4);
+ statusBar()->addWidget(d->statusNavigateBar, 1, true);
+
+ //------------------------------------------------------------------------------
+
+ connect(d->statusZoomBar, TQ_SIGNAL(signalZoomMinusClicked()),
+ d->view, TQ_SLOT(slotZoomOut()));
+
+ connect(d->statusZoomBar, TQ_SIGNAL(signalZoomPlusClicked()),
+ d->view, TQ_SLOT(slotZoomIn()));
+
+ connect(d->statusZoomBar, TQ_SIGNAL(signalZoomSliderChanged(int)),
+ this, TQ_SLOT(slotZoomSliderChanged(int)));
+
+ connect(d->view, TQ_SIGNAL(signalThumbSizeChanged(int)),
+ this, TQ_SLOT(slotThumbSizeChanged(int)));
+
+ connect(d->view, TQ_SIGNAL(signalZoomChanged(double, int)),
+ this, TQ_SLOT(slotZoomChanged(double, int)));
+
+ connect(d->view, TQ_SIGNAL(signalTogglePreview(bool)),
+ this, TQ_SLOT(slotTogglePreview(bool)));
+
+ connect(d->albumIconViewFilter, TQ_SIGNAL(signalResetTagFilters()),
+ this, TQ_SIGNAL(signalResetTagFilters()));
+
+ connect(d->statusNavigateBar, TQ_SIGNAL(signalFirstItem()),
+ d->view, TQ_SLOT(slotFirstItem()));
+
+ connect(d->statusNavigateBar, TQ_SIGNAL(signalNextItem()),
+ d->view, TQ_SLOT(slotNextItem()));
+
+ connect(d->statusNavigateBar, TQ_SIGNAL(signalPrevItem()),
+ d->view, TQ_SLOT(slotPrevItem()));
+
+ connect(d->statusNavigateBar, TQ_SIGNAL(signalLastItem()),
+ d->view, TQ_SLOT(slotLastItem()));
+
+ connect(d->statusProgressBar, TQ_SIGNAL(signalCancelButtonPressed()),
+ this, TQ_SIGNAL(signalCancelButtonPressed()));
+}
+
+void DigikamApp::setupAccelerators()
+{
+ d->accelerators = new TDEAccel(this);
+
+ d->accelerators->insert("Exit Preview Mode", i18n("Exit Preview"),
+ i18n("Exit preview mode"),
+ Key_Escape, this, TQ_SIGNAL(signalEscapePressed()),
+ false, true);
+
+ d->accelerators->insert("Next Image Key_Space", i18n("Next Image"),
+ i18n("Next Image"),
+ Key_Space, this, TQ_SIGNAL(signalNextItem()),
+ false, true);
+
+ d->accelerators->insert("Previous Image SHIFT+Key_Space", i18n("Previous Image"),
+ i18n("Previous Image"),
+ SHIFT+Key_Space, this, TQ_SIGNAL(signalPrevItem()),
+ false, true);
+
+ d->accelerators->insert("Previous Image Key_Backspace", i18n("Previous Image"),
+ i18n("Previous Image"),
+ Key_Backspace, this, TQ_SIGNAL(signalPrevItem()),
+ false, true);
+
+ d->accelerators->insert("Next Image Key_Next", i18n("Next Image"),
+ i18n("Next Image"),
+ Key_Next, this, TQ_SIGNAL(signalNextItem()),
+ false, true);
+
+ d->accelerators->insert("Previous Image Key_Prior", i18n("Previous Image"),
+ i18n("Previous Image"),
+ Key_Prior, this, TQ_SIGNAL(signalPrevItem()),
+ false, true);
+
+ d->accelerators->insert("First Image Key_Home", i18n("First Image"),
+ i18n("First Image"),
+ Key_Home, this, TQ_SIGNAL(signalFirstItem()),
+ false, true);
+
+ d->accelerators->insert("Last Image Key_End", i18n("Last Image"),
+ i18n("Last Image"),
+ Key_End, this, TQ_SIGNAL(signalLastItem()),
+ false, true);
+
+ d->accelerators->insert("Copy Album Items Selection CTRL+Key_C", i18n("Copy Album Items Selection"),
+ i18n("Copy Album Items Selection"),
+ CTRL+Key_C, this, TQ_SIGNAL(signalCopyAlbumItemsSelection()),
+ false, true);
+
+ d->accelerators->insert("Paste Album Items Selection CTRL+Key_V", i18n("Paste Album Items Selection"),
+ i18n("Paste Album Items Selection"),
+ CTRL+Key_V, this, TQ_SIGNAL(signalPasteAlbumItemsSelection()),
+ false, true);
+}
+
+void DigikamApp::setupActions()
+{
+ // -----------------------------------------------------------------
+
+ d->cameraMenuAction = new TDEActionMenu(i18n("&Camera"),
+ "digitalcam",
+ actionCollection(),
+ "camera_menu");
+ d->cameraMenuAction->setDelayed(false);
+
+ // -----------------------------------------------------------------
+
+ d->themeMenuAction = new TDESelectAction(i18n("&Themes"), 0, actionCollection(), "theme_menu");
+ connect(d->themeMenuAction, TQ_SIGNAL(activated(const TQString&)),
+ this, TQ_SLOT(slotChangeTheme(const TQString&)));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ // -----------------------------------------------------------------
+
+ d->backwardActionMenu = new TDEToolBarPopupAction(i18n("&Back"),
+ "back",
+ ALT+Key_Left,
+ d->view,
+ TQ_SLOT(slotAlbumHistoryBack()),
+ actionCollection(),
+ "album_back");
+ d->backwardActionMenu->setEnabled(false);
+
+ connect(d->backwardActionMenu->popupMenu(), TQ_SIGNAL(aboutToShow()),
+ this, TQ_SLOT(slotAboutToShowBackwardMenu()));
+
+ connect(d->backwardActionMenu->popupMenu(), TQ_SIGNAL(activated(int)),
+ d->view, TQ_SLOT(slotAlbumHistoryBack(int)));
+
+ d->forwardActionMenu = new TDEToolBarPopupAction(i18n("Forward"),
+ "forward",
+ ALT+Key_Right,
+ d->view,
+ TQ_SLOT(slotAlbumHistoryForward()),
+ actionCollection(),
+ "album_forward");
+ d->forwardActionMenu->setEnabled(false);
+
+ connect(d->forwardActionMenu->popupMenu(), TQ_SIGNAL(aboutToShow()),
+ this, TQ_SLOT(slotAboutToShowForwardMenu()));
+
+ connect(d->forwardActionMenu->popupMenu(), TQ_SIGNAL(activated(int)),
+ d->view, TQ_SLOT(slotAlbumHistoryForward(int)));
+
+ d->newAction = new TDEAction(i18n("&New..."),
+ "albumfolder-new",
+ TDEStdAccel::shortcut(TDEStdAccel::New),
+ d->view,
+ TQ_SLOT(slotNewAlbum()),
+ actionCollection(),
+ "album_new");
+ d->newAction->setWhatsThis(i18n("Creates a new empty Album in the database."));
+
+ d->albumSortAction = new TDESelectAction(i18n("&Sort Albums"),
+ 0,
+ 0,
+ actionCollection(),
+ "album_sort");
+
+ connect(d->albumSortAction, TQ_SIGNAL(activated(int)),
+ d->view, TQ_SLOT(slotSortAlbums(int)));
+
+ // Use same list order as in albumsettings enum
+ TQStringList sortActionList;
+ sortActionList.append(i18n("By Folder"));
+ sortActionList.append(i18n("By Collection"));
+ sortActionList.append(i18n("By Date"));
+ d->albumSortAction->setItems(sortActionList);
+
+ d->recurseAlbumsAction = new TDEToggleAction(i18n("Include Album Sub-Tree"),
+ 0,
+ this,
+ 0,
+ actionCollection(),
+ "albums_recursive");
+ d->recurseAlbumsAction->setWhatsThis(i18n("Activate this option to recursively show all sub-albums below "
+ "the current album."));
+
+ connect(d->recurseAlbumsAction, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotRecurseAlbums(bool)));
+
+ d->recurseTagsAction = new TDEToggleAction(i18n("Include Tag Sub-Tree"),
+ 0,
+ this,
+ 0,
+ actionCollection(),
+ "tags_recursive");
+ d->recurseTagsAction->setWhatsThis(i18n("Activate this option to show all images marked by the given tag "
+ "and its all its sub-tags."));
+
+ connect(d->recurseTagsAction, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotRecurseTags(bool)));
+
+ d->deleteAction = new TDEAction(i18n("Delete"),
+ "edit-delete",
+ 0,
+ d->view,
+ TQ_SLOT(slotDeleteAlbum()),
+ actionCollection(),
+ "album_delete");
+
+ d->addImagesAction = new TDEAction( i18n("Add Images..."),
+ "albumfolder-importimages",
+ CTRL+Key_I,
+ this,
+ TQ_SLOT(slotAlbumAddImages()),
+ actionCollection(),
+ "album_addImages");
+ d->addImagesAction->setWhatsThis(i18n("Adds new items to the current Album."));
+
+ d->albumImportAction = new TDEAction( i18n("Add Folders..."),
+ "albumfolder-importdir",
+ 0,
+ d->view,
+ TQ_SLOT(slotAlbumImportFolder()),
+ actionCollection(),
+ "album_importFolder");
+
+ d->propsEditAction = new TDEAction( i18n("Properties..."),
+ "albumfolder-properties",
+ 0,
+ d->view,
+ TQ_SLOT(slotAlbumPropsEdit()),
+ actionCollection(),
+ "album_propsEdit");
+ d->propsEditAction->setWhatsThis(i18n("Edit Album Properties and Collection information."));
+
+ d->refreshAlbumAction = new TDEAction( i18n("Refresh"),
+ "rebuild",
+ Key_F5,
+ d->view,
+ TQ_SLOT(slotAlbumRefresh()),
+ actionCollection(),
+ "album_refresh");
+ d->refreshAlbumAction->setWhatsThis(i18n("Refresh all album contents"));
+
+ d->syncAlbumMetadataAction = new TDEAction( i18n("Synchronize Images with Database"),
+ "rebuild",
+ 0,
+ d->view,
+ TQ_SLOT(slotAlbumSyncPicturesMetadata()),
+ actionCollection(),
+ "album_syncmetadata");
+ d->syncAlbumMetadataAction->setWhatsThis(i18n("Updates all image metadata of the current "
+ "album with the contents of the digiKam database "
+ "(image metadata will be over-written with data from the database)."));
+
+ d->openInKonquiAction = new TDEAction( i18n("Open in File Manager"),
+ "konqueror",
+ 0,
+ d->view,
+ TQ_SLOT(slotAlbumOpenInKonqui()),
+ actionCollection(),
+ "album_openinkonqui");
+
+ // -----------------------------------------------------------
+
+ d->newTagAction = new TDEAction(i18n("New &Tag..."), "tag-new",
+ 0, d->view, TQ_SLOT(slotNewTag()),
+ actionCollection(), "tag_new");
+
+ d->editTagAction = new TDEAction(i18n("Edit Tag Properties..."), "tag-properties",
+ 0, d->view, TQ_SLOT(slotEditTag()),
+ actionCollection(), "tag_edit");
+
+ d->deleteTagAction = new TDEAction(i18n("Delete Tag"), "tag-delete",
+ 0, d->view, TQ_SLOT(slotDeleteTag()),
+ actionCollection(), "tag_delete");
+
+ // -----------------------------------------------------------
+
+ d->imagePreviewAction = new TDEToggleAction(i18n("View..."),
+ "viewimage",
+ Key_F3,
+ d->view,
+ TQ_SLOT(slotImagePreview()),
+ actionCollection(),
+ "image_view");
+
+ d->imageViewAction = new TDEAction(i18n("Edit..."),
+ "editimage",
+ Key_F4,
+ d->view,
+ TQ_SLOT(slotImageEdit()),
+ actionCollection(),
+ "image_edit");
+ d->imageViewAction->setWhatsThis(i18n("Open the selected item in the image editor."));
+
+ d->imageLightTableAction = new TDEAction(i18n("Place onto Light Table"),
+ "lighttable",
+ CTRL+Key_L,
+ d->view,
+ TQ_SLOT(slotImageLightTable()),
+ actionCollection(),
+ "image_lighttable");
+ d->imageLightTableAction->setWhatsThis(i18n("Place the selected items on the light table thumbbar."));
+
+ d->imageAddLightTableAction = new TDEAction(i18n("Add to Light Table"),
+ "lighttableadd",
+ SHIFT+CTRL+Key_L,
+ d->view,
+ TQ_SLOT(slotImageAddToLightTable()),
+ actionCollection(),
+ "image_add_to_lighttable");
+ d->imageAddLightTableAction->setWhatsThis(i18n("Add selected items to the light table thumbbar."));
+
+ d->imageRenameAction = new TDEAction(i18n("Rename..."),
+ "pencil",
+ Key_F2,
+ d->view,
+ TQ_SLOT(slotImageRename()),
+ actionCollection(),
+ "image_rename");
+ d->imageRenameAction->setWhatsThis(i18n("Change the filename of the currently selected item."));
+
+ // Pop up dialog to ask user whether to move to trash
+ d->imageDeleteAction = new TDEAction(i18n("Delete"),
+ "edittrash",
+ Key_Delete,
+ d->view,
+ TQ_SLOT(slotImageDelete()),
+ actionCollection(),
+ "image_delete");
+
+ // Pop up dialog to ask user whether to permanently delete
+ d->imageDeletePermanentlyAction = new TDEAction(i18n("Delete permanently"),
+ "edit-delete",
+ SHIFT+Key_Delete,
+ d->view,
+ TQ_SLOT(slotImageDeletePermanently()),
+ actionCollection(),
+ "image_delete_permanently");
+
+ // These two actions are hidden, no menu entry, no toolbar entry, no shortcut.
+ // Power users may add them.
+ d->imageDeletePermanentlyDirectlyAction = new TDEAction(i18n("Delete permanently without confirmation"),
+ "edit-delete",
+ 0,
+ d->view,
+ TQ_SLOT(slotImageDeletePermanentlyDirectly()),
+ actionCollection(),
+ "image_delete_permanently_directly");
+
+ d->imageTrashDirectlyAction = new TDEAction(i18n("Move to trash without confirmation"),
+ "edittrash",
+ 0,
+ d->view,
+ TQ_SLOT(slotImageTrashDirectly()),
+ actionCollection(),
+ "image_trash_directly");
+
+ d->imageSortAction = new TDESelectAction(i18n("&Sort Images"),
+ 0,
+ 0,
+ actionCollection(),
+ "image_sort");
+
+ connect(d->imageSortAction, TQ_SIGNAL(activated(int)),
+ d->view, TQ_SLOT(slotSortImages(int)));
+
+ // Use same list order as in albumsettings enum
+ TQStringList sortImagesActionList;
+ sortImagesActionList.append(i18n("By Name"));
+ sortImagesActionList.append(i18n("By Path"));
+ sortImagesActionList.append(i18n("By Date"));
+ sortImagesActionList.append(i18n("By File Size"));
+ sortImagesActionList.append(i18n("By Rating"));
+ d->imageSortAction->setItems(sortImagesActionList);
+
+ // -----------------------------------------------------------------
+
+ TQSignalMapper *exifOrientationMapper = new TQSignalMapper( d->view );
+
+ connect(exifOrientationMapper, TQ_SIGNAL(mapped(int) ),
+ d->view, TQ_SLOT(slotImageExifOrientation(int)));
+
+ d->imageExifOrientationActionMenu = new TDEActionMenu(i18n("Adjust Exif orientation tag"),
+ actionCollection(),
+ "image_set_exif_orientation");
+ d->imageExifOrientationActionMenu->setDelayed(false);
+
+ d->imageSetExifOrientation1Action = new TDEAction(i18n("Normal"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_normal");
+ d->imageSetExifOrientation2Action = new TDEAction(i18n("Flipped Horizontally"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_flipped_horizontal");
+ d->imageSetExifOrientation3Action = new TDEAction(i18n("Rotated Upside Down"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_rotated_upside_down");
+ d->imageSetExifOrientation4Action = new TDEAction(i18n("Flipped Vertically"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_flipped_vertically");
+ d->imageSetExifOrientation5Action = new TDEAction(i18n("Rotated Right / Horiz. Flipped"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_rotated_right_hor_flipped");
+ d->imageSetExifOrientation6Action = new TDEAction(i18n("Rotated Right"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_rotated_right");
+ d->imageSetExifOrientation7Action = new TDEAction(i18n("Rotated Right / Vert. Flipped"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_rotated_right_ver_flipped");
+ d->imageSetExifOrientation8Action = new TDEAction(i18n("Rotated Left"),
+ 0,
+ d->imageExifOrientationActionMenu,
+ 0,
+ actionCollection(),
+ "image_set_exif_orientation_rotated_left");
+
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation1Action);
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation2Action);
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation3Action);
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation4Action);
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation5Action);
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation6Action);
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation7Action);
+ d->imageExifOrientationActionMenu->insert(d->imageSetExifOrientation8Action);
+
+ connect(d->imageSetExifOrientation1Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ connect(d->imageSetExifOrientation2Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ connect(d->imageSetExifOrientation3Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ connect(d->imageSetExifOrientation4Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ connect(d->imageSetExifOrientation5Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ connect(d->imageSetExifOrientation6Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ connect(d->imageSetExifOrientation7Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ connect(d->imageSetExifOrientation8Action, TQ_SIGNAL(activated()),
+ exifOrientationMapper, TQ_SLOT(map()));
+
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation1Action, 1);
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation2Action, 2);
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation3Action, 3);
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation4Action, 4);
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation5Action, 5);
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation6Action, 6);
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation7Action, 7);
+ exifOrientationMapper->setMapping(d->imageSetExifOrientation8Action, 8);
+
+ // -----------------------------------------------------------------
+
+ d->selectAllAction = new TDEAction(i18n("Select All"),
+ 0,
+ CTRL+Key_A,
+ d->view,
+ TQ_SLOT(slotSelectAll()),
+ actionCollection(),
+ "selectAll");
+
+ d->selectNoneAction = new TDEAction(i18n("Select None"),
+ 0,
+ CTRL+SHIFT+Key_A,
+ d->view,
+ TQ_SLOT(slotSelectNone()),
+ actionCollection(),
+ "selectNone");
+
+ d->selectInvertAction = new TDEAction(i18n("Invert Selection"),
+ 0,
+ CTRL+Key_Asterisk,
+ d->view,
+ TQ_SLOT(slotSelectInvert()),
+ actionCollection(),
+ "selectInvert");
+
+ // -----------------------------------------------------------
+
+ d->showMenuBarAction = KStdAction::showMenubar(this, TQ_SLOT(slotShowMenuBar()), actionCollection());
+
+ KStdAction::keyBindings(this, TQ_SLOT(slotEditKeys()), actionCollection());
+ KStdAction::configureToolbars(this, TQ_SLOT(slotConfToolbars()), actionCollection());
+ KStdAction::preferences(this, TQ_SLOT(slotSetup()), actionCollection());
+
+ // -----------------------------------------------------------
+
+ d->zoomPlusAction = new TDEAction(i18n("Zoom In"),
+ "zoom-in",
+ CTRL+Key_Plus,
+ d->view,
+ TQ_SLOT(slotZoomIn()),
+ actionCollection(),
+ "album_zoomin");
+
+ d->zoomMinusAction = new TDEAction(i18n("Zoom Out"),
+ "zoom-out",
+ CTRL+Key_Minus,
+ d->view,
+ TQ_SLOT(slotZoomOut()),
+ actionCollection(),
+ "album_zoomout");
+
+ d->zoomTo100percents = new TDEAction(i18n("Zoom to 100%"),
+ "zoom-original",
+ ALT+CTRL+Key_0, // NOTE: Photoshop 7 use ALT+CTRL+0.
+ d->view,
+ TQ_SLOT(slotZoomTo100Percents()),
+ actionCollection(),
+ "album_zoomto100percents");
+
+ d->zoomFitToWindowAction = new TDEAction(i18n("Fit to &Window"),
+ "view_fit_window",
+ CTRL+SHIFT+Key_E,
+ d->view,
+ TQ_SLOT(slotFitToWindow()),
+ actionCollection(),
+ "album_zoomfit2window");
+
+ // Do not use std KDE action for full screen because action text is too large for app. toolbar.
+ d->fullScreenAction = new TDEToggleAction(i18n("Full Screen"),
+ "view-fullscreen",
+ CTRL+SHIFT+Key_F,
+ this,
+ TQ_SLOT(slotToggleFullScreen()),
+ actionCollection(),
+ "full_screen");
+ d->fullScreenAction->setWhatsThis(i18n("Switch the window to full screen mode"));
+
+ d->slideShowAction = new TDEActionMenu(i18n("Slideshow"), "slideshow",
+ actionCollection(), "slideshow");
+
+ d->slideShowAction->setDelayed(false);
+
+ d->slideShowAllAction = new TDEAction(i18n("All"), 0, Key_F9,
+ d->view, TQ_SLOT(slotSlideShowAll()),
+ actionCollection(), "slideshow_all");
+ d->slideShowAction->insert(d->slideShowAllAction);
+
+ d->slideShowSelectionAction = new TDEAction(i18n("Selection"), 0, ALT+Key_F9,
+ d->view,
+ TQ_SLOT(slotSlideShowSelection()),
+ actionCollection(),
+ "slideshow_selected");
+ d->slideShowAction->insert(d->slideShowSelectionAction);
+
+ d->slideShowRecursiveAction = new TDEAction(i18n("With All Sub-Albums"), 0, SHIFT+Key_F9,
+ d->view,
+ TQ_SLOT(slotSlideShowRecursive()),
+ actionCollection(),
+ "slideshow_recursive");
+ d->slideShowAction->insert(d->slideShowRecursiveAction);
+
+ d->quitAction = KStdAction::quit(this,
+ TQ_SLOT(slotExit()),
+ actionCollection(),
+ "app_exit");
+
+ d->rawCameraListAction = new TDEAction(i18n("Supported RAW Cameras"),
+ "kdcraw",
+ 0,
+ this,
+ TQ_SLOT(slotRawCameraList()),
+ actionCollection(),
+ "help_rawcameralist");
+
+ d->kipiHelpAction = new TDEAction(i18n("Kipi Plugins Handbook"),
+ "kipi",
+ 0,
+ this,
+ TQ_SLOT(slotShowKipiHelp()),
+ actionCollection(),
+ "help_kipi");
+
+ d->tipAction = KStdAction::tipOfDay(this,
+ TQ_SLOT(slotShowTip()),
+ actionCollection(),
+ "help_tipofday");
+
+ d->donateMoneyAction = new TDEAction(i18n("Donate..."),
+ 0,
+ 0,
+ this,
+ TQ_SLOT(slotDonateMoney()),
+ actionCollection(),
+ "help_donatemoney");
+
+ d->contributeAction = new TDEAction(i18n("Contribute..."),
+ 0, 0,
+ this, TQ_SLOT(slotContribute()),
+ actionCollection(),
+ "help_contribute");
+
+ new DLogoAction(actionCollection(), "logo_action" );
+
+ // -- Rating actions ---------------------------------------------------------------
+
+ d->rating0Star = new TDEAction(i18n("Assign Rating \"No Stars\""), CTRL+Key_0,
+ d->view, TQ_SLOT(slotAssignRatingNoStar()),
+ actionCollection(), "ratenostar");
+ d->rating1Star = new TDEAction(i18n("Assign Rating \"One Star\""), CTRL+Key_1,
+ d->view, TQ_SLOT(slotAssignRatingOneStar()),
+ actionCollection(), "rateonestar");
+ d->rating2Star = new TDEAction(i18n("Assign Rating \"Two Stars\""), CTRL+Key_2,
+ d->view, TQ_SLOT(slotAssignRatingTwoStar()),
+ actionCollection(), "ratetwostar");
+ d->rating3Star = new TDEAction(i18n("Assign Rating \"Three Stars\""), CTRL+Key_3,
+ d->view, TQ_SLOT(slotAssignRatingThreeStar()),
+ actionCollection(), "ratethreestar");
+ d->rating4Star = new TDEAction(i18n("Assign Rating \"Four Stars\""), CTRL+Key_4,
+ d->view, TQ_SLOT(slotAssignRatingFourStar()),
+ actionCollection(), "ratefourstar");
+ d->rating5Star = new TDEAction(i18n("Assign Rating \"Five Stars\""), CTRL+Key_5,
+ d->view, TQ_SLOT(slotAssignRatingFiveStar()),
+ actionCollection(), "ratefivestar");
+
+ // -----------------------------------------------------------
+
+ TDEAction* findAction = KStdAction::find(d->view, TQ_SLOT(slotNewQuickSearch()),
+ actionCollection(), "search_quick");
+ findAction->setText(i18n("Search..."));
+ findAction->setIconSet(BarIcon("filefind"));
+
+ TDEAction* advFindAction = KStdAction::find(d->view, TQ_SLOT(slotNewAdvancedSearch()),
+ actionCollection(), "search_advanced");
+ advFindAction->setText(i18n("Advanced Search..."));
+ advFindAction->setShortcut("Ctrl+Alt+F");
+
+ new TDEAction(i18n("Light Table"), "idea", Key_L,
+ d->view, TQ_SLOT(slotLightTable()), actionCollection(),
+ "light_table");
+
+ new TDEAction(i18n("Scan for New Images"), "reload_page", 0,
+ this, TQ_SLOT(slotDatabaseRescan()), actionCollection(),
+ "database_rescan");
+
+ new TDEAction(i18n("Rebuild All Thumbnails..."), "reload_page", 0,
+ this, TQ_SLOT(slotRebuildAllThumbs()), actionCollection(),
+ "thumbs_rebuild");
+
+ new TDEAction(i18n("Update Metadata Database..."), "reload_page", 0,
+ this, TQ_SLOT(slotSyncAllPicturesMetadata()), actionCollection(),
+ "sync_metadata");
+
+ // -----------------------------------------------------------
+
+ // Provides a menu entry that allows showing/hiding the toolbar(s)
+ setStandardToolBarMenuEnabled(true);
+
+ // Provides a menu entry that allows showing/hiding the statusbar
+ createStandardStatusBarAction();
+
+ // Load Cameras -- do this before the createGUI so that the cameras
+ // are plugged into the toolbar at startup
+ if (d->splashScreen)
+ d->splashScreen->message(i18n("Loading cameras"));
+
+ loadCameras();
+
+ createGUI(TQString::fromLatin1( "digikamui.rc" ), false);
+
+ // Initialize Actions ---------------------------------------
+
+ d->deleteAction->setEnabled(false);
+ d->addImagesAction->setEnabled(false);
+ d->propsEditAction->setEnabled(false);
+ d->openInKonquiAction->setEnabled(false);
+
+ d->imageViewAction->setEnabled(false);
+ d->imagePreviewAction->setEnabled(false);
+ d->imageLightTableAction->setEnabled(false);
+ d->imageAddLightTableAction->setEnabled(false);
+ d->imageRenameAction->setEnabled(false);
+ d->imageDeleteAction->setEnabled(false);
+ d->imageExifOrientationActionMenu->setEnabled(false);
+ d->slideShowSelectionAction->setEnabled(false);
+
+ d->albumSortAction->setCurrentItem((int)d->albumSettings->getAlbumSortOrder());
+ d->imageSortAction->setCurrentItem((int)d->albumSettings->getImageSortOrder());
+
+ d->recurseAlbumsAction->setChecked(d->albumSettings->getRecurseAlbums());
+ d->recurseTagsAction->setChecked(d->albumSettings->getRecurseTags());
+ slotRecurseAlbums(d->recurseAlbumsAction->isChecked());
+ slotRecurseTags(d->recurseTagsAction->isChecked());
+
+ // Setting the filter condition also updates the tooltip.
+ // (So `setRating` is called first, as otherwise the filter value is not respected).
+ d->albumIconViewFilter->readSettings();
+}
+
+void DigikamApp::enableZoomPlusAction(bool val)
+{
+ d->zoomPlusAction->setEnabled(val);
+ d->statusZoomBar->setEnableZoomPlus(val);
+}
+
+void DigikamApp::enableZoomMinusAction(bool val)
+{
+ d->zoomMinusAction->setEnabled(val);
+ d->statusZoomBar->setEnableZoomMinus(val);
+}
+
+void DigikamApp::enableAlbumBackwardHistory(bool enable)
+{
+ d->backwardActionMenu->setEnabled(enable);
+}
+
+void DigikamApp::enableAlbumForwardHistory(bool enable)
+{
+ d->forwardActionMenu->setEnabled(enable);
+}
+
+void DigikamApp::slotAboutToShowBackwardMenu()
+{
+ d->backwardActionMenu->popupMenu()->clear();
+ TQStringList titles;
+ d->view->getBackwardHistory(titles);
+ if(!titles.isEmpty())
+ {
+ int id = 1;
+ TQStringList::Iterator iter = titles.begin();
+ for(; iter != titles.end(); ++iter,++id)
+ {
+ d->backwardActionMenu->popupMenu()->insertItem(*iter, id);
+ }
+ }
+}
+
+void DigikamApp::slotAboutToShowForwardMenu()
+{
+ d->forwardActionMenu->popupMenu()->clear();
+ TQStringList titles;
+ d->view->getForwardHistory(titles);
+
+ if(!titles.isEmpty())
+ {
+ int id = 1;
+ TQStringList::Iterator iter = titles.begin();
+ for(; iter != titles.end(); ++iter,++id)
+ {
+ d->forwardActionMenu->popupMenu()->insertItem(*iter, id);
+ }
+ }
+}
+
+void DigikamApp::slotAlbumSelected(bool val)
+{
+ Album *album = d->albumManager->currentAlbum();
+
+ if(album && !val)
+ {
+ // Not a PAlbum is selected
+ d->deleteAction->setEnabled(false);
+ d->addImagesAction->setEnabled(false);
+ d->propsEditAction->setEnabled(false);
+ d->openInKonquiAction->setEnabled(false);
+ d->newAction->setEnabled(false);
+ d->albumImportAction->setEnabled(false);
+ }
+ else if(!album && !val)
+ {
+ // Groupitem selected (Collection/date)
+ d->deleteAction->setEnabled(false);
+ d->addImagesAction->setEnabled(false);
+ d->propsEditAction->setEnabled(false);
+ d->openInKonquiAction->setEnabled(false);
+ d->newAction->setEnabled(false);
+ d->albumImportAction->setEnabled(false);
+
+ TDEAction *action;
+ for (action = d->kipiFileActionsImport.first(); action;
+ action = d->kipiFileActionsImport.next())
+ {
+ action->setEnabled(false);
+ }
+ }
+ else if(album && !album->isRoot() && album->type() == Album::PHYSICAL)
+ {
+ // Normal Album selected
+ d->deleteAction->setEnabled(true);
+ d->addImagesAction->setEnabled(true);
+ d->propsEditAction->setEnabled(true);
+ d->openInKonquiAction->setEnabled(true);
+ d->newAction->setEnabled(true);
+ d->albumImportAction->setEnabled(true);
+
+ TDEAction *action;
+ for (action = d->kipiFileActionsImport.first(); action;
+ action = d->kipiFileActionsImport.next())
+ {
+ action->setEnabled(true);
+ }
+ }
+ else if(album && album->isRoot() && album->type() == Album::PHYSICAL)
+ {
+ // Root Album selected
+ d->deleteAction->setEnabled(false);
+ d->addImagesAction->setEnabled(false);
+ d->propsEditAction->setEnabled(false);
+
+ if(album->type() == Album::PHYSICAL)
+ {
+ d->newAction->setEnabled(true);
+ d->openInKonquiAction->setEnabled(true);
+ d->albumImportAction->setEnabled(true);
+ }
+ else
+ {
+ d->newAction->setEnabled(false);
+ d->openInKonquiAction->setEnabled(false);
+ d->albumImportAction->setEnabled(false);
+ }
+
+ TDEAction *action;
+ for (action = d->kipiFileActionsImport.first(); action;
+ action = d->kipiFileActionsImport.next())
+ {
+ action->setEnabled(false);
+ }
+ }
+}
+
+void DigikamApp::slotTagSelected(bool val)
+{
+ Album *album = d->albumManager->currentAlbum();
+ if (!album) return;
+
+ if(!val)
+ {
+ d->deleteTagAction->setEnabled(false);
+ d->editTagAction->setEnabled(false);
+ }
+ else if(!album->isRoot())
+ {
+ d->deleteTagAction->setEnabled(true);
+ d->editTagAction->setEnabled(true);
+
+ TDEAction *action;
+ for (action = d->kipiFileActionsImport.first(); action;
+ action = d->kipiFileActionsImport.next())
+ {
+ action->setEnabled(false);
+ }
+ }
+ else
+ {
+ d->deleteTagAction->setEnabled(false);
+ d->editTagAction->setEnabled(false);
+
+ TDEAction *action;
+ for (action = d->kipiFileActionsImport.first(); action;
+ action = d->kipiFileActionsImport.next())
+ {
+ action->setEnabled(false);
+ }
+ }
+}
+
+void DigikamApp::slotImageSelected(const TQPtrList<ImageInfo>& list, bool hasPrev, bool hasNext,
+ const KURL::List& listAll)
+{
+ TQPtrList<ImageInfo> selection = list;
+ KURL::List all = listAll;
+ int num_images = listAll.count();
+ bool val = selection.isEmpty() ? false : true;
+ TQString text;
+ int index = 1;
+
+ d->imageViewAction->setEnabled(val);
+ d->imagePreviewAction->setEnabled(val);
+ d->imageLightTableAction->setEnabled(val);
+ d->imageAddLightTableAction->setEnabled(val);
+ d->imageRenameAction->setEnabled(val);
+ d->imageDeleteAction->setEnabled(val);
+ d->imageExifOrientationActionMenu->setEnabled(val);
+ d->slideShowSelectionAction->setEnabled(selection.count() != 0);
+
+ switch (selection.count())
+ {
+ case 0:
+ d->statusProgressBar->setText(i18n("No item selected"));
+ break;
+ case 1:
+ {
+ KURL first = selection.first()->kurl();
+
+ for (KURL::List::iterator it = all.begin();
+ it != all.end(); ++it)
+ {
+ if ((*it) == first)
+ break;
+
+ index++;
+ }
+
+ text = selection.first()->kurl().fileName()
+ + i18n(" (%1 of %2)")
+ .arg(TQString::number(index))
+ .arg(TQString::number(num_images));
+ d->statusProgressBar->setText(text);
+ break;
+ }
+ default:
+ d->statusProgressBar->setText(i18n("%1/%2 items selected")
+ .arg(selection.count()).arg(TQString::number(num_images)));
+
+ break;
+ }
+
+ d->statusNavigateBar->setNavigateBarState(hasPrev, hasNext);
+}
+
+void DigikamApp::slotProgressBarMode(int mode, const TQString& text)
+{
+ d->statusProgressBar->progressBarMode(mode, text);
+}
+
+void DigikamApp::slotProgressValue(int count)
+{
+ d->statusProgressBar->setProgressValue(count);
+}
+
+void DigikamApp::slotExit()
+{
+ if (d->fullScreen)
+ {
+ slotToggleFullScreen();
+ TQTimer::singleShot(0, this, TQ_SLOT(close()));
+ }
+ else
+ close();
+}
+
+TQString DigikamApp::convertToLocalUrl( const TQString& folder )
+{
+ // This function is copied from k3b.
+
+ KURL url( folder );
+ if( !url.isLocalFile() )
+ {
+#if KDE_IS_VERSION(3,4,91)
+ // Support for system:/ and media:/ (c) Stephan Kulow
+ KURL mlu = TDEIO::NetAccess::mostLocalURL( url, 0 );
+ if (mlu.isLocalFile())
+ return mlu.path();
+
+ DWarning() << folder << " mlu " << mlu << endl;
+
+ TQString path = mlu.path();
+
+ if ( mlu.protocol() == "system" && path.startsWith("/media") )
+ path = path.mid(7);
+ else if (mlu.protocol() == "media")
+ path = path.mid(1);
+ else
+ return folder; // nothing to see - go on
+
+ DDebug() << "parsed import path is: " << path << endl;
+ DCOPRef ref("kded", "mediamanager");
+ DCOPReply reply = ref.call("properties", path);
+ if (reply.isValid()) {
+ TQStringList slreply;
+ reply.get(slreply);
+ if ((slreply.count()>=9) && !slreply[9].isEmpty())
+ return slreply[9];
+ else
+ return slreply[6];
+ }
+ else
+ {
+ DWarning() << "dcop call failed\n";
+ }
+
+ return path;
+#else
+#ifndef UDS_LOCAL_PATH
+#define UDS_LOCAL_PATH (72 | TDEIO::UDS_STRING)
+#else
+ using namespace TDEIO;
+#endif
+ TDEIO::UDSEntry e;
+ if( TDEIO::NetAccess::stat( url, e, 0 ) )
+ {
+ const TDEIO::UDSEntry::ConstIterator end = e.end();
+ for( TDEIO::UDSEntry::ConstIterator it = e.begin(); it != end; ++it )
+ {
+ if( (*it).m_uds == UDS_LOCAL_PATH && !(*it).m_str.isEmpty() )
+ return KURL::fromPathOrURL( (*it).m_str ).path();
+ }
+ }
+#endif
+ }
+
+ return url.path();
+}
+
+void DigikamApp::slotDcopDownloadImages( const TQString& folder )
+{
+ if (!folder.isNull())
+ {
+ // activate window when called by media menu and DCOP
+ if (isMinimized())
+ KWin::deIconifyWindow(winId());
+ KWin::activateWindow(winId());
+
+ slotDownloadImages(folder);
+ }
+}
+
+void DigikamApp::slotDcopCameraAutoDetect()
+{
+ // activate window when called by media menu and DCOP
+ if (isMinimized())
+ KWin::deIconifyWindow(winId());
+ KWin::activateWindow(winId());
+
+ slotCameraAutoDetect();
+}
+
+void DigikamApp::slotDownloadImages( const TQString& folder)
+{
+ if (!folder.isNull())
+ {
+ d->cameraGuiPath = folder;
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotDownloadImages()));
+ }
+}
+
+void DigikamApp::slotDownloadImages()
+{
+ if (d->cameraGuiPath.isNull())
+ return;
+
+ // Fetch the contents of the device. This is needed to make sure that the
+ // media:/device gets mounted.
+ TDEIO::ListJob *job = TDEIO::listDir(KURL(d->cameraGuiPath), false, false);
+ TDEIO::NetAccess::synchronousRun(job,0);
+
+ TQString localUrl = convertToLocalUrl(d->cameraGuiPath);
+ DDebug() << "slotDownloadImages: convertToLocalUrl " << d->cameraGuiPath << " to " << localUrl << endl;
+
+ if (localUrl.isNull())
+ return;
+
+ bool alreadyThere = false;
+
+ for (uint i = 0 ; i != actionCollection()->count() ; i++)
+ {
+ if (actionCollection()->action(i)->name() == d->cameraGuiPath)
+ alreadyThere = true;
+ }
+
+ if (!alreadyThere)
+ {
+ TDEAction *cAction = new TDEAction(
+ i18n("Browse %1").arg(KURL(d->cameraGuiPath).prettyURL()),
+ "camera-photo",
+ 0,
+ this,
+ TQ_SLOT(slotDownloadImages()),
+ actionCollection(),
+ d->cameraGuiPath.latin1() );
+
+ d->cameraMenuAction->insert(cAction, 0);
+ }
+
+ // the CameraUI will delete itself when it has finished
+ CameraUI* cgui = new CameraUI(this,
+ i18n("Images found in %1").arg(d->cameraGuiPath),
+ "directory browse","Fixed", localUrl, TQDateTime::currentDateTime());
+ cgui->show();
+
+ connect(cgui, TQ_SIGNAL(signalLastDestination(const KURL&)),
+ d->view, TQ_SLOT(slotSelectAlbum(const KURL&)));
+
+ connect(cgui, TQ_SIGNAL(signalAlbumSettingsChanged()),
+ this, TQ_SLOT(slotSetupChanged()));
+}
+
+void DigikamApp::slotCameraConnect()
+{
+ CameraType* ctype = d->cameraList->find(TQString::fromUtf8(sender()->name()));
+
+ if (ctype)
+ {
+ // check not to open two dialogs for the same camera
+ if (ctype->currentCameraUI() && !ctype->currentCameraUI()->isClosed())
+ {
+ // show and raise dialog
+ if (ctype->currentCameraUI()->isMinimized())
+ KWin::deIconifyWindow(ctype->currentCameraUI()->winId());
+ KWin::activateWindow(ctype->currentCameraUI()->winId());
+ }
+ else
+ {
+ // the CameraUI will delete itself when it has finished
+ CameraUI* cgui = new CameraUI(this, ctype->title(), ctype->model(),
+ ctype->port(), ctype->path(), ctype->lastAccess());
+
+ ctype->setCurrentCameraUI(cgui);
+
+ cgui->show();
+
+ connect(cgui, TQ_SIGNAL(signalLastDestination(const KURL&)),
+ d->view, TQ_SLOT(slotSelectAlbum(const KURL&)));
+
+ connect(cgui, TQ_SIGNAL(signalAlbumSettingsChanged()),
+ this, TQ_SLOT(slotSetupChanged()));
+ }
+ }
+}
+
+void DigikamApp::slotCameraAdded(CameraType *ctype)
+{
+ if (!ctype) return;
+
+ TDEAction *cAction = new TDEAction(ctype->title(), "camera-photo", 0,
+ this, TQ_SLOT(slotCameraConnect()),
+ actionCollection(),
+ ctype->title().utf8());
+ d->cameraMenuAction->insert(cAction, 0);
+ ctype->setAction(cAction);
+}
+
+void DigikamApp::slotCameraMediaMenu()
+{
+ d->mediaItems.clear();
+
+ d->cameraMediaList->clear();
+ d->cameraMediaList->insertItem(i18n("No media devices found"), 0);
+ d->cameraMediaList->setItemEnabled(0, false);
+
+ KURL kurl("media:/");
+ TDEIO::ListJob *job = TDEIO::listDir(kurl, false, false);
+
+ connect( job, TQ_SIGNAL(entries(TDEIO::Job*,const TDEIO::UDSEntryList&)),
+ this, TQ_SLOT(slotCameraMediaMenuEntries(TDEIO::Job*,const TDEIO::UDSEntryList&)) );
+}
+
+void DigikamApp::slotCameraMediaMenuEntries( Job *, const UDSEntryList & list )
+{
+ int i = 0;
+
+ for(TDEIO::UDSEntryList::ConstIterator it = list.begin() ; it != list.end() ; ++it)
+ {
+ TQString name;
+ TQString path;
+
+ for ( UDSEntry::const_iterator et = (*it).begin() ; et != (*it).end() ; ++et )
+ {
+ if ( (*et).m_uds == TDEIO::UDS_NAME)
+ name = ( *et ).m_str;
+ if ( (*et).m_uds == TDEIO::UDS_URL)
+ path = ( *et ).m_str;
+
+ //DDebug() << ( *et ).m_str << endl;
+ }
+
+ if (!name.isEmpty() && !path.isEmpty())
+ {
+ //DDebug() << "slotCameraMediaMenuEntries: Adding " << name << ", path " << path << endl;
+ if (i == 0)
+ d->cameraMediaList->clear();
+
+ d->mediaItems[i] = path;
+
+ d->cameraMediaList->insertItem(name, this, TQ_SLOT(slotDownloadImagesFromMedia(int)), 0, i);
+ d->cameraMediaList->setItemParameter(i, i);
+ i++;
+ }
+ }
+}
+
+void DigikamApp::slotDownloadImagesFromMedia( int id )
+{
+ slotDownloadImages( d->mediaItems[id] );
+}
+
+void DigikamApp::slotCameraRemoved(CameraType *ctype)
+{
+ if (!ctype) return;
+
+ TDEAction *cAction = ctype->action();
+
+ if (cAction)
+ d->cameraMenuAction->remove(cAction);
+}
+
+void DigikamApp::slotCameraAutoDetect()
+{
+ bool retry = false;
+
+ CameraType* ctype = d->cameraList->autoDetect(retry);
+
+ if (!ctype && retry)
+ {
+ TQTimer::singleShot(0, this, TQ_SLOT(slotCameraAutoDetect()));
+ return;
+ }
+
+ if (ctype && ctype->action())
+ {
+ ctype->action()->activate();
+ }
+}
+
+void DigikamApp::slotSetup()
+{
+ setup();
+}
+
+bool DigikamApp::setup(bool iccSetupPage)
+{
+ Setup setup(this, 0, iccSetupPage ? Setup::IccProfiles : Setup::LastPageUsed);
+
+ // To show the number of KIPI plugins in the setup dialog.
+
+ KIPI::PluginLoader::PluginList list = d->kipiPluginLoader->pluginList();
+ setup.kipiPluginsPage()->initPlugins((int)list.count());
+
+ if (setup.exec() != TQDialog::Accepted)
+ return false;
+
+ setup.kipiPluginsPage()->applyPlugins();
+
+ slotSetupChanged();
+
+ return true;
+}
+
+void DigikamApp::slotSetupCamera()
+{
+ Setup setup(this, 0, Setup::Camera);
+
+ // For to show the number of KIPI plugins in the setup dialog.
+
+ KIPI::PluginLoader::PluginList list = d->kipiPluginLoader->pluginList();
+ setup.kipiPluginsPage()->initPlugins((int)list.count());
+
+ if (setup.exec() != TQDialog::Accepted)
+ return;
+
+ setup.kipiPluginsPage()->applyPlugins();
+
+ slotSetupChanged();
+}
+
+void DigikamApp::slotSetupChanged()
+{
+ // raw loading options might have changed
+ LoadingCacheInterface::cleanCache();
+
+ if(d->albumSettings->getAlbumLibraryPath() != d->albumManager->getLibraryPath())
+ d->view->clearHistory();
+
+ d->albumManager->setLibraryPath(d->albumSettings->getAlbumLibraryPath());
+ d->albumManager->startScan();
+
+ if(d->albumSettings->getShowFolderTreeViewItemsCount())
+ d->albumManager->refresh();
+
+ d->view->applySettings();
+ d->albumIconViewFilter->readSettings();
+
+ AlbumThumbnailLoader::instance()->setThumbnailSize(d->albumSettings->getDefaultTreeIconSize());
+
+ if (ImageWindow::imagewindowCreated())
+ ImageWindow::imagewindow()->applySettings();
+
+ if (LightTableWindow::lightTableWindowCreated())
+ LightTableWindow::lightTableWindow()->applySettings();
+
+ d->config->sync();
+}
+
+void DigikamApp::slotEditKeys()
+{
+ KKeyDialog* dialog = new KKeyDialog();
+ dialog->insert( actionCollection(), i18n( "General" ) );
+
+ KIPI::PluginLoader::PluginList list = d->kipiPluginLoader->pluginList();
+
+ for( KIPI::PluginLoader::PluginList::Iterator it = list.begin() ; it != list.end() ; ++it )
+ {
+ KIPI::Plugin* plugin = (*it)->plugin();
+
+ if ( plugin )
+ dialog->insert( plugin->actionCollection(), (*it)->comment() );
+ }
+
+ dialog->configure();
+ delete dialog;
+}
+
+void DigikamApp::slotConfToolbars()
+{
+ saveMainWindowSettings(TDEGlobal::config());
+ KEditToolbar *dlg = new KEditToolbar(actionCollection(), "digikamui.rc");
+
+ if(dlg->exec())
+ {
+ createGUI(TQString::fromLatin1( "digikamui.rc" ), false);
+ applyMainWindowSettings(TDEGlobal::config());
+ plugActionList( TQString::fromLatin1("file_actions_import"), d->kipiFileActionsImport );
+ plugActionList( TQString::fromLatin1("image_actions"), d->kipiImageActions );
+ plugActionList( TQString::fromLatin1("tool_actions"), d->kipiToolsActions );
+ plugActionList( TQString::fromLatin1("batch_actions"), d->kipiBatchActions );
+ plugActionList( TQString::fromLatin1("album_actions"), d->kipiAlbumActions );
+ plugActionList( TQString::fromLatin1("file_actions_export"), d->kipiFileActionsExport );
+ }
+
+ delete dlg;
+}
+
+void DigikamApp::slotToggleFullScreen()
+{
+ if (d->fullScreen)
+ {
+ setWindowState( windowState() & ~WindowFullScreen );
+ menuBar()->show();
+ statusBar()->show();
+ topDock()->show();
+ bottomDock()->show();
+ leftDock()->show();
+ rightDock()->show();
+ d->view->showSideBars();
+
+ d->fullScreen = false;
+ }
+ else
+ {
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ bool fullScreenHideToolBar = config->readBoolEntry("FullScreen Hide ToolBar", false);
+
+ menuBar()->hide();
+ statusBar()->hide();
+ if (fullScreenHideToolBar)
+ topDock()->hide();
+ bottomDock()->hide();
+ leftDock()->hide();
+ rightDock()->hide();
+ d->view->hideSideBars();
+
+ showFullScreen();
+ d->fullScreen = true;
+ }
+}
+
+void DigikamApp::slotShowTip()
+{
+#if KDE_IS_VERSION(3,2,0)
+ TQStringList tipsFiles;
+ tipsFiles.append("digikam/tips");
+
+ tipsFiles.append("kipi/tips");
+
+ KTipDialog::showMultiTip(this, tipsFiles, true);
+#else
+ KTipDialog::showTip(this, "digikam/tips", true);
+#endif
+}
+
+void DigikamApp::slotShowKipiHelp()
+{
+ TDEApplication::kApplication()->invokeHelp( TQString(), "kipi-plugins" );
+}
+
+void DigikamApp::slotRawCameraList()
+{
+ RawCameraDlg dlg(this);
+ dlg.exec();
+}
+
+void DigikamApp::loadPlugins()
+{
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Loading Kipi Plugins"));
+
+ TQStringList ignores;
+ d->kipiInterface = new DigikamKipiInterface( this, "Digikam_KIPI_interface" );
+
+ ignores.append( "HelloWorld" );
+ ignores.append( "KameraKlient" );
+
+ d->kipiPluginLoader = new KIPI::PluginLoader( ignores, d->kipiInterface );
+
+ connect( d->kipiPluginLoader, TQ_SIGNAL( replug() ),
+ this, TQ_SLOT( slotKipiPluginPlug() ) );
+
+ d->kipiPluginLoader->loadPlugins();
+
+ d->kipiInterface->slotCurrentAlbumChanged(d->albumManager->currentAlbum());
+
+ // Setting the initial menu options after all plugins have been loaded
+ d->view->slotAlbumSelected(d->albumManager->currentAlbum());
+
+ d->imagePluginsLoader = new ImagePluginLoader(this, d->splashScreen);
+}
+
+void DigikamApp::slotKipiPluginPlug()
+{
+ unplugActionList( TQString::fromLatin1("file_actions_export") );
+ unplugActionList( TQString::fromLatin1("file_actions_import") );
+ unplugActionList( TQString::fromLatin1("image_actions") );
+ unplugActionList( TQString::fromLatin1("tool_actions") );
+ unplugActionList( TQString::fromLatin1("batch_actions") );
+ unplugActionList( TQString::fromLatin1("album_actions") );
+
+ d->kipiImageActions.clear();
+ d->kipiFileActionsExport.clear();
+ d->kipiFileActionsImport.clear();
+ d->kipiToolsActions.clear();
+ d->kipiBatchActions.clear();
+ d->kipiAlbumActions.clear();
+
+ KIPI::PluginLoader::PluginList list = d->kipiPluginLoader->pluginList();
+
+ int cpt = 0;
+
+ for( KIPI::PluginLoader::PluginList::Iterator it = list.begin() ; it != list.end() ; ++it )
+ {
+ KIPI::Plugin* plugin = (*it)->plugin();
+
+ if ( !plugin || !(*it)->shouldLoad() )
+ continue;
+
+ ++cpt;
+
+ plugin->setup( this );
+ TQPtrList<TDEAction>* popup = 0;
+
+ // Plugin category identification using TDEAction method based.
+
+ TDEActionPtrList actions = plugin->actions();
+
+ // List of obsolete kipi-plugins to not load.
+ TQStringList pluginActionsDisabled;
+ pluginActionsDisabled << TQString("raw_converter_single"); // Obsolete Since 0.9.5 and new Raw Import tool.
+
+ for( TDEActionPtrList::Iterator it2 = actions.begin(); it2 != actions.end(); ++it2 )
+ {
+ if ( plugin->category(*it2) == KIPI::IMAGESPLUGIN )
+ popup = &d->kipiImageActions;
+
+ else if ( plugin->category(*it2) == KIPI::EXPORTPLUGIN )
+ popup = &d->kipiFileActionsExport;
+
+ else if ( plugin->category(*it2) == KIPI::IMPORTPLUGIN )
+ popup = &d->kipiFileActionsImport;
+
+ else if ( plugin->category(*it2) == KIPI::TOOLSPLUGIN )
+ popup = &d->kipiToolsActions;
+
+ else if ( plugin->category(*it2) == KIPI::BATCHPLUGIN )
+ popup = &d->kipiBatchActions;
+
+ else if ( plugin->category(*it2) == KIPI::COLLECTIONSPLUGIN )
+ popup = &d->kipiAlbumActions;
+
+ TQString actionName((*it2)->name());
+
+ // Plug the KIPI plugins actions in according with the TDEAction method.
+
+ if (popup)
+ {
+ if (!pluginActionsDisabled.contains(actionName))
+ popup->append( *it2 );
+ else
+ DDebug() << "Plugin '" << actionName << "' disabled." << endl;
+ }
+ else
+ {
+ DDebug() << "No menu found for plugin '" << actionName << "' !!!" << endl;
+ }
+ }
+
+ plugin->actionCollection()->readShortcutSettings();
+ }
+
+ // Create GUI menu in according with plugins.
+
+ plugActionList( TQString::fromLatin1("file_actions_export"), d->kipiFileActionsExport );
+ plugActionList( TQString::fromLatin1("file_actions_import"), d->kipiFileActionsImport );
+ plugActionList( TQString::fromLatin1("image_actions"), d->kipiImageActions );
+ plugActionList( TQString::fromLatin1("tool_actions"), d->kipiToolsActions );
+ plugActionList( TQString::fromLatin1("batch_actions"), d->kipiBatchActions );
+ plugActionList( TQString::fromLatin1("album_actions"), d->kipiAlbumActions );
+}
+
+void DigikamApp::loadCameras()
+{
+ d->cameraList->load();
+
+ d->cameraMenuAction->popupMenu()->insertSeparator();
+
+ d->cameraMenuAction->popupMenu()->insertItem(i18n("Browse Media"), d->cameraMediaList);
+
+ d->cameraMenuAction->popupMenu()->insertSeparator();
+
+ d->cameraMenuAction->insert(new TDEAction(i18n("Add Camera..."), 0,
+ this, TQ_SLOT(slotSetupCamera()),
+ actionCollection(),
+ "camera_add"));
+}
+
+void DigikamApp::populateThemes()
+{
+ if(d->splashScreen)
+ d->splashScreen->message(i18n("Loading themes"));
+
+ ThemeEngine::instance()->scanThemes();
+ d->themeMenuAction->setItems(ThemeEngine::instance()->themeNames());
+ slotThemeChanged();
+ ThemeEngine::instance()->slotChangeTheme(d->themeMenuAction->currentText());
+}
+
+void DigikamApp::slotChangeTheme(const TQString& theme)
+{
+ d->albumSettings->setCurrentTheme(theme);
+ ThemeEngine::instance()->slotChangeTheme(theme);
+}
+
+void DigikamApp::slotThemeChanged()
+{
+ TQStringList themes(ThemeEngine::instance()->themeNames());
+ int index = themes.findIndex(d->albumSettings->getCurrentTheme());
+ if (index == -1)
+ index = themes.findIndex(i18n("Default"));
+
+ d->themeMenuAction->setCurrentItem(index);
+}
+
+void DigikamApp::slotDatabaseRescan()
+{
+ ScanLib sLib;
+ sLib.startScan();
+
+ d->view->refreshView();
+
+ if (ImageWindow::imagewindowCreated())
+ ImageWindow::imagewindow()->refreshView();
+
+ if (LightTableWindow::lightTableWindowCreated())
+ LightTableWindow::lightTableWindow()->refreshView();
+}
+
+void DigikamApp::slotRebuildAllThumbs()
+{
+ TQString msg = i18n("Rebuilding all image thumbnails can take some time.\n"
+ "Do you want to continue?");
+ int result = KMessageBox::warningContinueCancel(this, msg);
+ if (result != KMessageBox::Continue)
+ return;
+
+ BatchThumbsGenerator *thumbsGenerator = new BatchThumbsGenerator(this);
+
+ connect(thumbsGenerator, TQ_SIGNAL(signalRebuildAllThumbsDone()),
+ this, TQ_SLOT(slotRebuildAllThumbsDone()));
+
+ thumbsGenerator->exec();
+}
+
+void DigikamApp::slotRebuildAllThumbsDone()
+{
+ d->view->applySettings();
+}
+
+void DigikamApp::slotSyncAllPicturesMetadata()
+{
+ TQString msg = i18n("Updating the metadata database can take some time. \nDo you want to continue?");
+ int result = KMessageBox::warningContinueCancel(this, msg);
+ if (result != KMessageBox::Continue)
+ return;
+
+ BatchAlbumsSyncMetadata *syncMetadata = new BatchAlbumsSyncMetadata(this);
+
+ connect(syncMetadata, TQ_SIGNAL(signalComplete()),
+ this, TQ_SLOT(slotSyncAllPicturesMetadataDone()));
+
+ syncMetadata->exec();
+}
+
+void DigikamApp::slotSyncAllPicturesMetadataDone()
+{
+ d->view->applySettings();
+}
+
+void DigikamApp::slotDonateMoney()
+{
+ TDEApplication::kApplication()->invokeBrowser("http://www.digikam.org/?q=donation");
+}
+
+void DigikamApp::slotContribute()
+{
+ TDEApplication::kApplication()->invokeBrowser("http://www.digikam.org/?q=contrib");
+}
+
+void DigikamApp::slotRecurseAlbums(bool checked)
+{
+ AlbumLister::instance()->setRecurseAlbums(checked);
+}
+
+void DigikamApp::slotRecurseTags(bool checked)
+{
+ AlbumLister::instance()->setRecurseTags(checked);
+}
+
+void DigikamApp::slotZoomSliderChanged(int size)
+{
+ d->view->setThumbSize(size);
+}
+
+void DigikamApp::slotThumbSizeChanged(int size)
+{
+ d->statusZoomBar->setZoomSliderValue(size);
+ d->statusZoomBar->setZoomTrackerText(i18n("Size: %1").arg(size));
+}
+
+void DigikamApp::slotZoomChanged(double zoom, int size)
+{
+ d->statusZoomBar->setZoomSliderValue(size);
+ d->statusZoomBar->setZoomTrackerText(i18n("zoom: %1%").arg((int)(zoom*100.0)));
+}
+
+void DigikamApp::slotTogglePreview(bool t)
+{
+ // NOTE: if 't' is true, we are in Preview Mode, else we are in AlbumView Mode
+
+ // This is require if ESC is pressed to go out of Preview Mode.
+ // imagePreviewAction is handled by F3 key only.
+ d->imagePreviewAction->setChecked(t);
+
+ // Here, we will toggle some menu actions depending of current Mode.
+
+ // Select menu.
+ d->selectAllAction->setEnabled(!t);
+ d->selectNoneAction->setEnabled(!t);
+ d->selectInvertAction->setEnabled(!t);
+
+ // View menu
+ d->albumSortAction->setEnabled(!t);
+ d->imageSortAction->setEnabled(!t);
+ d->zoomTo100percents->setEnabled(t);
+ d->zoomFitToWindowAction->setEnabled(t);
+}
+
+void DigikamApp::slotAlbumAddImages()
+{
+ TQString path = KFileDialog::getExistingDirectory(TDEGlobalSettings::documentPath(), this,
+ i18n("Select folder to parse"));
+
+ if(path.isEmpty())
+ return;
+
+ // The folder contents will be parsed by Camera interface in "Directory Browse" mode.
+ downloadFrom(path);
+}
+
+void DigikamApp::slotShowMenuBar()
+{
+ if (menuBar()->isVisible())
+ menuBar()->hide();
+ else
+ menuBar()->show();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/digikamapp.h b/src/digikam/digikamapp.h
new file mode 100644
index 00000000..899603d6
--- /dev/null
+++ b/src/digikam/digikamapp.h
@@ -0,0 +1,181 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2002-16-10
+ * Description : main digiKam interface implementation
+ *
+ * Copyright (C) 2002-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2002-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIGIKAMAPP_H
+#define DIGIKAMAPP_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdemainwindow.h>
+#include <tdeio/global.h>
+#include <tdeio/netaccess.h>
+
+// Local includes.
+
+#include "albumlister.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImageInfo;
+class CameraType;
+class DigikamAppPriv;
+
+class DIGIKAM_EXPORT DigikamApp : public TDEMainWindow
+{
+ TQ_OBJECT
+
+public:
+
+ DigikamApp();
+ ~DigikamApp();
+
+ virtual void show();
+
+ static DigikamApp* getinstance();
+
+ // KIPI Actions collections access.
+ const TQPtrList<TDEAction>& menuImageActions();
+ const TQPtrList<TDEAction>& menuBatchActions();
+ const TQPtrList<TDEAction>& menuAlbumActions();
+
+ const TQPtrList<TDEAction> menuImportActions();
+ const TQPtrList<TDEAction> menuExportActions();
+
+ void autoDetect();
+ void downloadFrom(const TQString &cameraGuiPath);
+ void enableZoomPlusAction(bool val);
+ void enableZoomMinusAction(bool val);
+ void enableAlbumBackwardHistory(bool enable);
+ void enableAlbumForwardHistory(bool enable);
+
+signals:
+
+ void signalEscapePressed();
+ void signalNextItem();
+ void signalPrevItem();
+ void signalFirstItem();
+ void signalLastItem();
+ void signalCopyAlbumItemsSelection();
+ void signalPasteAlbumItemsSelection();
+ void signalCancelButtonPressed();
+ void signalResetTagFilters();
+
+protected:
+
+ bool queryClose();
+
+protected slots:
+
+ void slotCameraMediaMenuEntries( TDEIO::Job *, const TDEIO::UDSEntryList & );
+
+private:
+
+ bool setup(bool iccSetupPage=false);
+ void setupView();
+ void setupStatusBar();
+ void setupActions();
+ void setupAccelerators();
+ void loadPlugins();
+ void loadCameras();
+ void populateThemes();
+
+private slots:
+
+ void slotAlbumAddImages();
+ void slotAlbumSelected(bool val);
+ void slotTagSelected(bool val);
+ void slotImageSelected(const TQPtrList<ImageInfo>&, bool, bool, const KURL::List&);
+ void slotExit();
+ void slotShowTip();
+ void slotShowKipiHelp();
+ void slotDonateMoney();
+ void slotContribute();
+ void slotRawCameraList();
+
+ void slotRecurseAlbums(bool);
+ void slotRecurseTags(bool);
+
+ void slotAboutToShowForwardMenu();
+ void slotAboutToShowBackwardMenu();
+
+ void slotSetup();
+ void slotSetupCamera();
+ void slotSetupChanged();
+
+ void slotKipiPluginPlug();
+
+ TQString convertToLocalUrl( const TQString& folder );
+ void slotDownloadImages( const TQString& folder );
+ void slotDownloadImages();
+ void slotCameraConnect();
+ void slotCameraMediaMenu();
+ void slotDownloadImagesFromMedia( int id );
+ void slotCameraAdded(CameraType *ctype);
+ void slotCameraRemoved(CameraType *ctype);
+ void slotCameraAutoDetect();
+ void slotDcopDownloadImages(const TQString& folder);
+ void slotDcopCameraAutoDetect();
+ void slotEditKeys();
+ void slotConfToolbars();
+ void slotShowMenuBar();
+ void slotToggleFullScreen();
+
+ void slotDatabaseRescan();
+ void slotRebuildAllThumbs();
+ void slotRebuildAllThumbsDone();
+ void slotSyncAllPicturesMetadata();
+ void slotSyncAllPicturesMetadataDone();
+
+ void slotChangeTheme(const TQString& theme);
+ void slotThemeChanged();
+
+ void slotProgressBarMode(int, const TQString&);
+ void slotProgressValue(int);
+
+ void slotZoomSliderChanged(int);
+ void slotThumbSizeChanged(int);
+ void slotZoomChanged(double, int);
+ void slotTogglePreview(bool);
+
+private:
+
+ DigikamAppPriv *d;
+
+ static DigikamApp *m_instance;
+};
+
+} // namespace Digikam
+
+#endif // DIGIKAMAPP_H
diff --git a/src/digikam/digikamappprivate.h b/src/digikam/digikamappprivate.h
new file mode 100644
index 00000000..cf5e43a9
--- /dev/null
+++ b/src/digikam/digikamappprivate.h
@@ -0,0 +1,269 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-31-01
+ * Description : main digiKam interface implementation
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcstring.h>
+#include <tqstring.h>
+#include <tqmap.h>
+#include <tqtoolbutton.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdeaction.h>
+#include <tdeaccel.h>
+#include <tdepopupmenu.h>
+#include <kstatusbar.h>
+
+// libKipi includes.
+
+#include <libkipi/pluginloader.h>
+
+// Local includes.
+
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "cameralist.h"
+#include "imagepluginloader.h"
+#include "splashscreen.h"
+#include "kipiinterface.h"
+#include "statuszoombar.h"
+#include "statusprogressbar.h"
+#include "statusnavigatebar.h"
+#include "digikamview.h"
+
+class TDEToolBarPopupAction;
+class TDEToggleAction;
+class TDEActionMenu;
+class TDESelectAction;
+
+namespace Digikam
+{
+
+class DCOPIface;
+class SearchTextBar;
+class AlbumIconViewFilter;
+
+class DigikamAppPriv
+{
+public:
+
+ DigikamAppPriv()
+ {
+ fullScreen = false;
+ validIccPath = true;
+ cameraMediaList = 0;
+ accelerators = 0;
+ config = 0;
+ newAction = 0;
+ deleteAction = 0;
+ imageDeletePermanentlyAction = 0;
+ imageDeletePermanentlyDirectlyAction = 0;
+ imageTrashDirectlyAction = 0;
+ albumSortAction = 0;
+ recurseAlbumsAction = 0;
+ recurseTagsAction = 0;
+ backwardActionMenu = 0;
+ forwardActionMenu = 0;
+ addImagesAction = 0;
+ propsEditAction = 0;
+ albumImportAction = 0;
+ openInKonquiAction = 0;
+ refreshAlbumAction = 0;
+ syncAlbumMetadataAction = 0;
+ newTagAction = 0;
+ deleteTagAction = 0;
+ editTagAction = 0;
+ imagePreviewAction = 0;
+ imageViewAction = 0;
+ imageLightTableAction = 0;
+ imageAddLightTableAction = 0;
+ imageSetExifOrientation1Action = 0;
+ imageSetExifOrientation2Action = 0;
+ imageSetExifOrientation3Action = 0;
+ imageSetExifOrientation4Action = 0;
+ imageSetExifOrientation5Action = 0;
+ imageSetExifOrientation6Action = 0;
+ imageSetExifOrientation7Action = 0;
+ imageSetExifOrientation8Action = 0;
+ imageRenameAction = 0;
+ imageDeleteAction = 0;
+ imageSortAction = 0;
+ imageExifOrientationActionMenu = 0;
+ selectAllAction = 0;
+ selectNoneAction = 0;
+ selectInvertAction = 0;
+ fullScreenAction = 0;
+ slideShowAction = 0;
+ slideShowAllAction = 0;
+ slideShowSelectionAction = 0;
+ slideShowRecursiveAction = 0;
+ rating0Star = 0;
+ rating1Star = 0;
+ rating2Star = 0;
+ rating3Star = 0;
+ rating4Star = 0;
+ rating5Star = 0;
+ quitAction = 0;
+ tipAction = 0;
+ rawCameraListAction = 0;
+ kipiHelpAction = 0;
+ donateMoneyAction = 0;
+ cameraMenuAction = 0;
+ themeMenuAction = 0;
+ albumSettings = 0;
+ albumManager = 0;
+ dcopIface = 0;
+ imagePluginsLoader = 0;
+ kipiInterface = 0;
+ cameraList = 0;
+ statusProgressBar = 0;
+ statusNavigateBar = 0;
+ statusZoomBar = 0;
+ kipiPluginLoader = 0;
+ view = 0;
+ splashScreen = 0;
+ zoomTo100percents = 0;
+ zoomFitToWindowAction = 0;
+ zoomPlusAction = 0;
+ zoomMinusAction = 0;
+ albumIconViewFilter = 0;
+ contributeAction = 0;
+ showMenuBarAction = 0;
+ }
+
+ bool fullScreen;
+ bool validIccPath;
+
+ // KIPI plugins support
+ TQPtrList<TDEAction> kipiFileActionsExport;
+ TQPtrList<TDEAction> kipiFileActionsImport;
+ TQPtrList<TDEAction> kipiImageActions;
+ TQPtrList<TDEAction> kipiToolsActions;
+ TQPtrList<TDEAction> kipiBatchActions;
+ TQPtrList<TDEAction> kipiAlbumActions;
+
+ TQMap<int, TQString> mediaItems;
+
+ TQString cameraGuiPath;
+
+ TDEPopupMenu *cameraMediaList;
+
+ TDEAccel *accelerators;
+
+ TDEConfig *config;
+
+ // Album Actions
+ TDEAction *newAction;
+ TDEAction *deleteAction;
+ TDEAction *imageDeletePermanentlyAction;
+ TDEAction *imageDeletePermanentlyDirectlyAction;
+ TDEAction *imageTrashDirectlyAction;
+ TDEToolBarPopupAction *backwardActionMenu;
+ TDEToolBarPopupAction *forwardActionMenu;
+
+ TDEAction *addImagesAction;
+ TDEAction *propsEditAction;
+ TDEAction *albumImportAction;
+ TDEAction *openInKonquiAction;
+ TDEAction *refreshAlbumAction;
+ TDEAction *syncAlbumMetadataAction;
+
+ // Tag Actions
+ TDEAction *newTagAction;
+ TDEAction *deleteTagAction;
+ TDEAction *editTagAction;
+
+ // Image Actions
+ TDEToggleAction *imagePreviewAction;
+ TDEAction *imageLightTableAction;
+ TDEAction *imageAddLightTableAction;
+ TDEAction *imageViewAction;
+ TDEAction *imageSetExifOrientation1Action;
+ TDEAction *imageSetExifOrientation2Action;
+ TDEAction *imageSetExifOrientation3Action;
+ TDEAction *imageSetExifOrientation4Action;
+ TDEAction *imageSetExifOrientation5Action;
+ TDEAction *imageSetExifOrientation6Action;
+ TDEAction *imageSetExifOrientation7Action;
+ TDEAction *imageSetExifOrientation8Action;
+ TDEAction *imageRenameAction;
+ TDEAction *imageDeleteAction;
+ TDEActionMenu *imageExifOrientationActionMenu;
+
+ // Selection Actions
+ TDEAction *selectAllAction;
+ TDEAction *selectNoneAction;
+ TDEAction *selectInvertAction;
+
+ // View Actions
+ TDEToggleAction *fullScreenAction;
+ TDEToggleAction *showMenuBarAction;
+ TDEActionMenu *slideShowAction;
+ TDEAction *slideShowAllAction;
+ TDEAction *slideShowSelectionAction;
+ TDEAction *slideShowRecursiveAction;
+ TDESelectAction *imageSortAction;
+ TDESelectAction *albumSortAction;
+ TDEToggleAction *recurseAlbumsAction;
+ TDEToggleAction *recurseTagsAction;
+ TDEAction *zoomPlusAction;
+ TDEAction *zoomMinusAction;
+ TDEAction *zoomFitToWindowAction;
+ TDEAction *zoomTo100percents;
+
+ TDEAction *rating0Star;
+ TDEAction *rating1Star;
+ TDEAction *rating2Star;
+ TDEAction *rating3Star;
+ TDEAction *rating4Star;
+ TDEAction *rating5Star;
+
+ // Application Actions
+ TDEAction *rawCameraListAction;
+ TDEAction *quitAction;
+ TDEAction *tipAction;
+ TDEAction *kipiHelpAction;
+ TDEAction *donateMoneyAction;
+ TDEAction *contributeAction;
+ TDEActionMenu *cameraMenuAction;
+ TDESelectAction *themeMenuAction;
+
+ AlbumSettings *albumSettings;
+ AlbumManager *albumManager;
+ AlbumIconViewFilter *albumIconViewFilter;
+ SplashScreen *splashScreen;
+ DCOPIface *dcopIface;
+ ImagePluginLoader *imagePluginsLoader;
+ DigikamKipiInterface *kipiInterface;
+ DigikamView *view;
+ CameraList *cameraList;
+ StatusZoomBar *statusZoomBar;
+ StatusProgressBar *statusProgressBar;
+ StatusNavigateBar *statusNavigateBar;
+ KIPI::PluginLoader *kipiPluginLoader;
+};
+
+} // namespace Digikam
diff --git a/src/digikam/digikamfirstrun.cpp b/src/digikam/digikamfirstrun.cpp
new file mode 100644
index 00000000..e7a95c01
--- /dev/null
+++ b/src/digikam/digikamfirstrun.cpp
@@ -0,0 +1,183 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-01
+ * Description : dialog displayed at the first digiKam run
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <iostream>
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqlineedit.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqstring.h>
+#include <tqdir.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdefiledialog.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+#include <kurl.h>
+#include <kurlrequester.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "firstrun.h"
+#include "digikamfirstrun.h"
+#include "digikamfirstrun.moc"
+
+namespace Digikam
+{
+
+using namespace std;
+
+DigikamFirstRun::DigikamFirstRun(TDEConfig* config, TQWidget* parent,
+ const char* name, bool modal, WFlags fl)
+ : KDialogBase(parent, name, modal, i18n( "Album Library Path" ),
+ Help|Ok|Cancel, Ok, true )
+
+{
+ setHelp("firstrundialog.anchor", "digikam");
+ setWFlags(fl);
+
+ m_config = config;
+ m_ui = new FirstRunWidget(this);
+ setMainWidget(m_ui);
+ m_ui->m_path->setURL(TQDir::homeDirPath() +
+ i18n("This is a path name so you should "
+ "include the slash in the translation","/Pictures"));
+ m_ui->m_path->setMode(KFile::Directory | KFile::LocalOnly);
+
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ m_ui->m_pixLabel->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup,
+ 128, TDEIcon::DefaultState, 0, true));
+ m_ui->setMinimumSize(450, m_ui->sizeHint().height());
+}
+
+DigikamFirstRun::~DigikamFirstRun()
+{
+}
+
+void DigikamFirstRun::slotOk()
+{
+ TQString albumLibraryFolder = m_ui->m_path->url();
+
+ if (albumLibraryFolder.isEmpty())
+ {
+ KMessageBox::sorry(this, i18n("You must select a folder for digiKam to "
+ "use as the Album Library folder."));
+ return;
+ }
+
+ if (!albumLibraryFolder.startsWith("/"))
+ {
+ albumLibraryFolder.prepend(TQDir::homeDirPath());
+ }
+
+ if (KURL(albumLibraryFolder).equals(KURL(TQDir::homeDirPath()), true))
+ {
+ KMessageBox::sorry(this, i18n("digiKam cannot use your home folder as "
+ "the Album Library folder."));
+ return;
+ }
+
+ TQDir targetPath(albumLibraryFolder);
+
+ if (!targetPath.exists())
+ {
+ int rc = KMessageBox::questionYesNo(this,
+ i18n("<qt>The folder you selected does not exist: "
+
+ "<p><b>%1</b></p>"
+ "Would you like digiKam to create it?</qt>")
+ .arg(albumLibraryFolder),
+ i18n("Create Folder?"));
+
+ if (rc == KMessageBox::No)
+ {
+ return;
+ }
+
+ if (!targetPath.mkdir(albumLibraryFolder))
+ {
+ KMessageBox::sorry(this,
+ i18n("<qt>digiKam could not create the folder shown below. "
+ "Please select a different location."
+ "<p><b>%1</b></p></qt>").arg(albumLibraryFolder),
+ i18n("Create Folder Failed"));
+ return;
+ }
+ }
+
+ TQFileInfo path(albumLibraryFolder);
+
+ if (!path.isWritable())
+ {
+ KMessageBox::information(this, i18n("No write access for this path.\n"
+ "Warning: the comment and tag features "
+ "will not work."));
+ return;
+ }
+
+ m_config->setGroup("General Settings");
+ m_config->writeEntry("Version", digikam_version);
+
+ m_config->setGroup("Album Settings");
+ m_config->writePathEntry("Album Path", albumLibraryFolder);
+ m_config->sync();
+
+ KDialogBase::accept();
+
+ TQString ErrorMsg, URL;
+
+ if (kapp->startServiceByDesktopName("digikam", URL , &ErrorMsg) > 0)
+ {
+ DError() << ErrorMsg << endl;
+ KMessageBox::sorry(this, i18n("Cannot restart digiKam automatically.\n"
+ "Please restart digiKam manually."));
+ }
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/digikamfirstrun.h b/src/digikam/digikamfirstrun.h
new file mode 100644
index 00000000..e8921f5b
--- /dev/null
+++ b/src/digikam/digikamfirstrun.h
@@ -0,0 +1,66 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-01
+ * Description : dialog displayed at the first digiKam run
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIGIKAMFIRSTRUN_H
+#define DIGIKAMFIRSTRUN_H
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TDEConfig;
+class KURLRequester;
+
+namespace Digikam
+{
+
+class FirstRunWidget;
+
+class DIGIKAM_EXPORT DigikamFirstRun : public KDialogBase
+{
+ TQ_OBJECT
+
+public:
+
+ DigikamFirstRun(TDEConfig* config, TQWidget* parent = 0, const char* name = 0,
+ bool modal = true, WFlags fl = WDestructiveClose);
+ ~DigikamFirstRun();
+
+protected slots:
+
+ void slotOk();
+
+private:
+
+ TDEConfig *m_config;
+ FirstRunWidget *m_ui;
+};
+
+} // namespace Digikam
+
+#endif // DIGIKAMFIRSTRUN_H
diff --git a/src/digikam/digikamui.rc b/src/digikam/digikamui.rc
new file mode 100644
index 00000000..e4763abe
--- /dev/null
+++ b/src/digikam/digikamui.rc
@@ -0,0 +1,147 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="38" name="digikam" >
+
+ <MenuBar>
+
+ <Menu name="Album" >
+ <text>&amp;Album</text>
+ <Action name="album_back" />
+ <Action name="album_forward" />
+ <Separator/>
+ <Action name="album_new" />
+ <Separator/>
+ <Action name="album_propsEdit" />
+ <Action name="album_refresh" />
+ <Action name="album_syncmetadata" />
+ <Action name="album_openinkonqui" />
+ <Separator/>
+ <Action name="album_remove" />
+ <Action name="album_delete" />
+ <Separator/>
+ <Action name="app_exit" />
+ </Menu>
+
+ <Menu name="Tag" >
+ <text>T&amp;ag</text>
+ <Action name="tag_new" />
+ <Action name="tag_edit" />
+ <Separator/>
+ <Action name="tag_delete" />
+ </Menu>
+
+ <Menu name="Image" >
+ <text>&amp;Image</text>
+ <Action name="image_view" />
+ <Action name="image_edit" />
+ <Action name="image_lighttable" />
+ <Action name="image_add_to_lighttable" />
+ <Action name="image_set_exif_orientation"/>
+ <Separator />
+ <ActionList name="image_actions"/>
+ <Separator />
+ <Action name="image_rename" />
+ <Action name="image_delete" />
+ </Menu>
+
+ <Menu name="Edit" >
+ <text>&amp;Edit</text>
+ <Action name="selectAll" />
+ <Action name="selectNone" />
+ <Action name="selectInvert" />
+ </Menu>
+
+ <Menu name="View" >
+ <text>&amp;View</text>
+ <Action name="full_screen" />
+ <Action name="slideshow" />
+ <Separator/>
+ <Action name="album_zoomin" />
+ <Action name="album_zoomout" />
+ <Action name="album_zoomto100percents" />
+ <Action name="album_zoomfit2window" />
+ <Separator />
+ <Action name="album_sort" />
+ <Action name="image_sort" />
+ <Action name="albums_recursive" />
+ <Action name="tags_recursive" />
+ </Menu>
+
+ <Menu name="Tools"><text>&amp;Tools</text>
+ <Action name="search_quick" />
+ <Action name="search_advanced" />
+ <Separator/>
+ <Action name="light_table" />
+ <Separator/>
+ <Action name="database_rescan" />
+ <Action name="thumbs_rebuild" />
+ <Action name="sync_metadata" />
+ <Separator/>
+ <ActionList name="album_actions"/>
+ <Separator/>
+ <ActionList name="tool_actions"/>
+ </Menu>
+
+ <Menu name="Batch"><text>&amp;Batch</text>
+ <ActionList name="batch_actions"/>
+ </Menu>
+
+ <Menu name="Import"><text>&amp;Import</text>
+ <Action name="camera_menu" />
+ <Separator/>
+ <Action name="album_addImages" />
+ <Action name="album_importFolder" />
+ <Separator/>
+ <ActionList name="file_actions_import"/>
+ </Menu>
+
+ <Menu name="Export"><text>&amp;Export</text>
+ <ActionList name="file_actions_export"/>
+ </Menu>
+
+ <Menu name="settings" noMerge="1"><Text>&amp;Settings</Text>
+ <Merge name="StandardToolBarMenuHandler" />
+ <Action name="options_show_menubar"/>
+ <Action name="options_show_statusbar"/>
+ <Action name="options_show_toolbar"/>
+ <Separator/>
+ <Action name="theme_menu" />
+ <Action name="options_configure_keybinding"/>
+ <Action name="options_configure_toolbars"/>
+ <Action name="options_configure"/>
+ </Menu>
+
+ <Menu name="help"><Text>&amp;Help</Text>
+ <Action name="help_rawcameralist"/>
+ <Action name="help_kipi"/>
+ <Action name="help_tipofday"/>
+ <Separator/>
+ <Action name="help_donatemoney"/>
+ <Action name="help_contribute" />
+ </Menu>
+
+ <Merge/>
+
+ </MenuBar>
+
+ <ToolBar noMerge="1" name="ToolBar" >
+ <text>Main Toolbar</text>
+ <Action name="album_back" />
+ <Action name="album_forward" />
+ <Action name="album_new" />
+ <Action name="album_addImages" />
+ <Action name="album_propsEdit" />
+ <Separator/>
+ <Action name="search_quick" />
+ <Separator/>
+ <Action name="image_view" />
+ <Action name="image_edit" />
+ <Action name="image_rename" />
+ <Separator/>
+ <Action name="full_screen" />
+ <Action name="slideshow" />
+ <WeakSeparator/>
+ <Action name="logo_action" />
+ </ToolBar>
+ <ActionProperties/>
+
+</kpartgui>
diff --git a/src/digikam/digikamview.cpp b/src/digikam/digikamview.cpp
new file mode 100644
index 00000000..a8fb42fb
--- /dev/null
+++ b/src/digikam/digikamview.cpp
@@ -0,0 +1,1570 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2002-16-10
+ * Description : implementation of album view interface.
+ *
+ * Copyright (C) 2002-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2002-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvbox.h>
+#include <tqstring.h>
+#include <tqstringlist.h>
+#include <tqstrlist.h>
+#include <tqfileinfo.h>
+#include <tqdir.h>
+#include <tqlabel.h>
+#include <tqimage.h>
+#include <tqevent.h>
+#include <tqeventloop.h>
+#include <tqapplication.h>
+#include <tqsplitter.h>
+#include <tqtimer.h>
+#include <tqlistview.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <kpushbutton.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kdialogbase.h>
+#include <krun.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rawfiles.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "albummanager.h"
+#include "album.h"
+#include "albumwidgetstack.h"
+#include "albumfolderview.h"
+#include "albumiconview.h"
+#include "albumiconitem.h"
+#include "albumsettings.h"
+#include "albumhistory.h"
+#include "batchsyncmetadata.h"
+#include "slideshow.h"
+#include "sidebar.h"
+#include "imagepropertiessidebardb.h"
+#include "imageinfoalbumsjob.h"
+#include "imagepreviewview.h"
+#include "datefolderview.h"
+#include "tagfolderview.h"
+#include "searchfolderview.h"
+#include "searchtextbar.h"
+#include "statusprogressbar.h"
+#include "tagfilterview.h"
+#include "timelineview.h"
+#include "timelinefolderview.h"
+#include "thumbnailsize.h"
+#include "dio.h"
+#include "digikamapp.h"
+#include "digikamview.h"
+#include "digikamview.moc"
+
+namespace Digikam
+{
+
+class DigikamViewPriv
+{
+public:
+
+ DigikamViewPriv()
+ {
+ folderBox = 0;
+ tagBox = 0;
+ searchBox = 0;
+ tagFilterBox = 0;
+ folderSearchBar = 0;
+ tagSearchBar = 0;
+ searchSearchBar = 0;
+ tagFilterSearchBar = 0;
+ splitter = 0;
+ parent = 0;
+ iconView = 0;
+ folderView = 0;
+ albumManager = 0;
+ albumHistory = 0;
+ leftSideBar = 0;
+ rightSideBar = 0;
+ dateFolderView = 0;
+ timeLineView = 0;
+ tagFolderView = 0;
+ searchFolderView = 0;
+ tagFilterView = 0;
+ albumWidgetStack = 0;
+ selectionTimer = 0;
+ thumbSizeTimer = 0;
+ needDispatchSelection = false;
+ cancelSlideShow = false;
+ thumbSize = ThumbnailSize::Medium;
+ }
+
+ bool needDispatchSelection;
+ bool cancelSlideShow;
+
+ int initialAlbumID;
+ int thumbSize;
+
+ TQSplitter *splitter;
+
+ TQTimer *selectionTimer;
+ TQTimer *thumbSizeTimer;
+
+ TQVBox *folderBox;
+ TQVBox *tagBox;
+ TQVBox *searchBox;
+ TQVBox *tagFilterBox;
+
+ SearchTextBar *folderSearchBar;
+ SearchTextBar *tagSearchBar;
+ SearchTextBar *searchSearchBar;
+ SearchTextBar *tagFilterSearchBar;
+
+ DigikamApp *parent;
+
+ AlbumIconView *iconView;
+ AlbumFolderView *folderView;
+ AlbumManager *albumManager;
+ AlbumHistory *albumHistory;
+ AlbumWidgetStack *albumWidgetStack;
+
+ Sidebar *leftSideBar;
+ ImagePropertiesSideBarDB *rightSideBar;
+
+ DateFolderView *dateFolderView;
+ TimeLineView *timeLineView;
+ TagFolderView *tagFolderView;
+ SearchFolderView *searchFolderView;
+ TagFilterView *tagFilterView;
+};
+
+DigikamView::DigikamView(TQWidget *parent)
+ : TQHBox(parent)
+{
+ d = new DigikamViewPriv;
+ d->parent = static_cast<DigikamApp *>(parent);
+ d->albumManager = AlbumManager::instance();
+ d->leftSideBar = new Sidebar(this, "Digikam Left Sidebar", Sidebar::Left);
+
+ d->splitter = new TQSplitter(this);
+ d->splitter->setFrameStyle( TQFrame::NoFrame );
+ d->splitter->setFrameShadow( TQFrame::Plain );
+ d->splitter->setFrameShape( TQFrame::NoFrame );
+ d->splitter->setOpaqueResize(false);
+
+ d->leftSideBar->setSplitter(d->splitter);
+ d->albumWidgetStack = new AlbumWidgetStack(d->splitter);
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+ d->albumWidgetStack->setSizePolicy(rightSzPolicy);
+ d->iconView = d->albumWidgetStack->albumIconView();
+
+ d->rightSideBar = new ImagePropertiesSideBarDB(this, "Digikam Right Sidebar", d->splitter,
+ Sidebar::Right, true);
+
+ // To the left.
+
+ // Folders sidebar tab contents.
+ d->folderBox = new TQVBox(this);
+ d->folderView = new AlbumFolderView(d->folderBox);
+ d->folderSearchBar = new SearchTextBar(d->folderBox, "DigikamViewFolderSearchBar");
+ d->folderBox->setSpacing(KDialog::spacingHint());
+ d->folderBox->setMargin(0);
+
+ // Tags sidebar tab contents.
+ d->tagBox = new TQVBox(this);
+ d->tagFolderView = new TagFolderView(d->tagBox);
+ d->tagSearchBar = new SearchTextBar(d->tagBox, "DigikamViewTagSearchBar");
+ d->tagBox->setSpacing(KDialog::spacingHint());
+ d->tagBox->setMargin(0);
+
+ // Search sidebar tab contents.
+ d->searchBox = new TQVBox(this);
+ d->searchFolderView = new SearchFolderView(d->searchBox);
+ d->searchSearchBar = new SearchTextBar(d->searchBox, "DigikamViewSearchSearchBar");
+ d->searchBox->setSpacing(KDialog::spacingHint());
+ d->searchBox->setMargin(0);
+
+ d->dateFolderView = new DateFolderView(this);
+ d->timeLineView = new TimeLineView(this);
+
+ d->leftSideBar->appendTab(d->folderBox, SmallIcon("folder_image"), i18n("Albums"));
+ d->leftSideBar->appendTab(d->dateFolderView, SmallIcon("date"), i18n("Calendar"));
+ d->leftSideBar->appendTab(d->tagBox, SmallIcon("tag"), i18n("Tags"));
+ d->leftSideBar->appendTab(d->timeLineView, SmallIcon("clock"), i18n("Timeline"));
+ d->leftSideBar->appendTab(d->searchBox, SmallIcon("edit-find"), i18n("Searches"));
+
+ // To the right.
+
+ // Tags Filter sidebar tab contents.
+ d->tagFilterBox = new TQVBox(this);
+ d->tagFilterView = new TagFilterView(d->tagFilterBox);
+ d->tagFilterSearchBar = new SearchTextBar(d->tagFilterBox, "DigikamViewTagFilterSearchBar");
+ d->tagFilterBox->setSpacing(KDialog::spacingHint());
+ d->tagFilterBox->setMargin(0);
+
+ d->rightSideBar->appendTab(d->tagFilterBox, SmallIcon("tag-assigned"), i18n("Tag Filters"));
+
+ d->selectionTimer = new TQTimer(this);
+
+ setupConnections();
+
+ d->albumManager->setItemHandler(d->iconView);
+ d->albumHistory = new AlbumHistory();
+}
+
+DigikamView::~DigikamView()
+{
+ if (d->thumbSizeTimer)
+ delete d->thumbSizeTimer;
+
+ saveViewState();
+
+ delete d->albumHistory;
+ d->albumManager->setItemHandler(0);
+ delete d;
+}
+
+void DigikamView::applySettings()
+{
+ AlbumSettings *settings = AlbumSettings::instance();
+ d->iconView->applySettings(settings);
+ d->albumWidgetStack->imagePreviewView()->setLoadFullImageSize(settings->getPreviewLoadFullImageSize());
+ refreshView();
+}
+
+void DigikamView::refreshView()
+{
+ d->folderView->refresh();
+ d->dateFolderView->refresh();
+ d->tagFolderView->refresh();
+ d->tagFilterView->refresh();
+ d->rightSideBar->refreshTagsView();
+}
+
+void DigikamView::setupConnections()
+{
+ // -- DigikamApp connections ----------------------------------
+
+ connect(d->parent, TQ_SIGNAL(signalEscapePressed()),
+ this, TQ_SLOT(slotEscapePreview()));
+
+ connect(d->parent, TQ_SIGNAL(signalEscapePressed()),
+ d->albumWidgetStack, TQ_SLOT(slotEscapePreview()));
+
+ connect(d->parent, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SLOT(slotNextItem()));
+
+ connect(d->parent, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SLOT(slotPrevItem()));
+
+ connect(d->parent, TQ_SIGNAL(signalFirstItem()),
+ this, TQ_SLOT(slotFirstItem()));
+
+ connect(d->parent, TQ_SIGNAL(signalLastItem()),
+ this, TQ_SLOT(slotLastItem()));
+
+ connect(d->parent, TQ_SIGNAL(signalCopyAlbumItemsSelection()),
+ d->iconView, TQ_SLOT(slotCopy()));
+
+ connect(d->parent, TQ_SIGNAL(signalPasteAlbumItemsSelection()),
+ d->iconView, TQ_SLOT(slotPaste()));
+
+ connect(this, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ d->parent, TQ_SLOT(slotProgressBarMode(int, const TQString&)));
+
+ connect(this, TQ_SIGNAL(signalProgressValue(int)),
+ d->parent, TQ_SLOT(slotProgressValue(int)));
+
+ connect(d->parent, TQ_SIGNAL(signalCancelButtonPressed()),
+ this, TQ_SLOT(slotCancelSlideShow()));
+
+ // -- AlbumManager connections --------------------------------
+
+ connect(d->albumManager, TQ_SIGNAL(signalAlbumCurrentChanged(Album*)),
+ this, TQ_SLOT(slotAlbumSelected(Album*)));
+
+ connect(d->albumManager, TQ_SIGNAL(signalAllAlbumsLoaded()),
+ this, TQ_SLOT(slotAllAlbumsLoaded()));
+
+ connect(d->albumManager, TQ_SIGNAL(signalAlbumItemsSelected(bool) ),
+ this, TQ_SLOT(slotImageSelected()));
+
+ connect(d->albumManager, TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(d->albumManager, TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(d->albumManager, TQ_SIGNAL(signalAlbumRenamed(Album*)),
+ this, TQ_SLOT(slotAlbumRenamed(Album*)));
+
+ connect(d->albumManager, TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(slotAlbumsCleared()));
+
+ // -- IconView Connections -------------------------------------
+
+ connect(d->iconView, TQ_SIGNAL(signalItemsUpdated(const KURL::List&)),
+ d->albumWidgetStack, TQ_SLOT(slotItemsUpdated(const KURL::List&)));
+
+ connect(d->iconView, TQ_SIGNAL(signalItemsAdded()),
+ this, TQ_SLOT(slotImageSelected()));
+
+ connect(d->iconView, TQ_SIGNAL(signalItemsAdded()),
+ this, TQ_SLOT(slotAlbumHighlight()));
+
+ connect(d->iconView, TQ_SIGNAL(signalPreviewItem(AlbumIconItem*)),
+ this, TQ_SLOT(slotTogglePreviewMode(AlbumIconItem*)));
+
+ //connect(d->iconView, TQ_SIGNAL(signalItemDeleted(AlbumIconItem*)),
+ // this, TQ_SIGNAL(signalNoCurrentItem()));
+
+ connect(d->iconView, TQ_SIGNAL(signalGotoAlbumAndItem(AlbumIconItem *)),
+ this, TQ_SLOT(slotGotoAlbumAndItem(AlbumIconItem *)));
+
+ connect(d->iconView, TQ_SIGNAL(signalGotoDateAndItem(AlbumIconItem *)),
+ this, TQ_SLOT(slotGotoDateAndItem(AlbumIconItem *)));
+
+ connect(d->iconView, TQ_SIGNAL(signalGotoTagAndItem(int)),
+ this, TQ_SLOT(slotGotoTagAndItem(int)));
+
+ connect(d->folderView, TQ_SIGNAL(signalAlbumModified()),
+ d->iconView, TQ_SLOT(slotAlbumModified()));
+
+ connect(d->iconView, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ d->parent, TQ_SLOT(slotProgressBarMode(int, const TQString&)));
+
+ connect(d->iconView, TQ_SIGNAL(signalProgressValue(int)),
+ d->parent, TQ_SLOT(slotProgressValue(int)));
+
+ // -- Sidebar Connections -------------------------------------
+
+ connect(d->leftSideBar, TQ_SIGNAL(signalChangedTab(TQWidget*)),
+ this, TQ_SLOT(slotLeftSidebarChangedTab(TQWidget*)));
+
+ connect(d->rightSideBar, TQ_SIGNAL(signalFirstItem()),
+ this, TQ_SLOT(slotFirstItem()));
+
+ connect(d->rightSideBar, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SLOT(slotNextItem()));
+
+ connect(d->rightSideBar, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SLOT(slotPrevItem()));
+
+ connect(d->rightSideBar, TQ_SIGNAL(signalLastItem()),
+ this, TQ_SLOT(slotLastItem()));
+
+ connect(this, TQ_SIGNAL(signalNoCurrentItem()),
+ d->rightSideBar, TQ_SLOT(slotNoCurrentItem()));
+
+ connect(d->rightSideBar, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ d->parent, TQ_SLOT(slotProgressBarMode(int, const TQString&)));
+
+ connect(d->rightSideBar, TQ_SIGNAL(signalProgressValue(int)),
+ d->parent, TQ_SLOT(slotProgressValue(int)));
+
+ connect(d->tagFilterView, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ d->parent, TQ_SLOT(slotProgressBarMode(int, const TQString&)));
+
+ connect(d->tagFilterView, TQ_SIGNAL(signalProgressValue(int)),
+ d->parent, TQ_SLOT(slotProgressValue(int)));
+
+ connect(d->tagFolderView, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ d->parent, TQ_SLOT(slotProgressBarMode(int, const TQString&)));
+
+ connect(d->tagFolderView, TQ_SIGNAL(signalProgressValue(int)),
+ d->parent, TQ_SLOT(slotProgressValue(int)));
+
+ connect(d->parent, TQ_SIGNAL(signalResetTagFilters()),
+ d->tagFilterView, TQ_SLOT(slotResetTagFilters()));
+
+ // -- Filter Bars Connections ---------------------------------
+
+ connect(d->folderSearchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ d->folderView, TQ_SLOT(slotTextFolderFilterChanged(const TQString&)));
+
+ connect(d->tagSearchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ d->tagFolderView, TQ_SLOT(slotTextTagFilterChanged(const TQString&)));
+
+ connect(d->searchSearchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ d->searchFolderView, TQ_SLOT(slotTextSearchFilterChanged(const TQString&)));
+
+ connect(d->tagFilterSearchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ d->tagFilterView, TQ_SLOT(slotTextTagFilterChanged(const TQString&)));
+
+ connect(d->folderView, TQ_SIGNAL(signalTextFolderFilterMatch(bool)),
+ d->folderSearchBar, TQ_SLOT(slotSearchResult(bool)));
+
+ connect(d->tagFolderView, TQ_SIGNAL(signalTextTagFilterMatch(bool)),
+ d->tagSearchBar, TQ_SLOT(slotSearchResult(bool)));
+
+ connect(d->searchFolderView, TQ_SIGNAL(signalTextSearchFilterMatch(bool)),
+ d->searchSearchBar, TQ_SLOT(slotSearchResult(bool)));
+
+ connect(d->tagFilterView, TQ_SIGNAL(signalTextTagFilterMatch(bool)),
+ d->tagFilterSearchBar, TQ_SLOT(slotSearchResult(bool)));
+
+ // -- Preview image widget Connections ------------------------
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SLOT(slotNextItem()));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SLOT(slotPrevItem()));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalEditItem()),
+ this, TQ_SLOT(slotImageEdit()));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalDeleteItem()),
+ this, TQ_SLOT(slotImageDelete()));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalToggledToPreviewMode(bool)),
+ this, TQ_SLOT(slotToggledToPreviewMode(bool)));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalBack2Album()),
+ this, TQ_SLOT(slotEscapePreview()));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalSlideShow()),
+ this, TQ_SLOT(slotSlideShowAll()));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SLOT(slotZoomFactorChanged(double)));
+
+ connect(d->albumWidgetStack, TQ_SIGNAL(signalInsert2LightTable()),
+ this, TQ_SLOT(slotImageAddToLightTable()));
+
+ // -- Selection timer ---------------
+
+ connect(d->selectionTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotDispatchImageSelected()));
+}
+
+void DigikamView::loadViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("MainWindow");
+
+ if(config->hasKey("SplitterSizes"))
+ d->splitter->setSizes(config->readIntListEntry("SplitterSizes"));
+
+ d->initialAlbumID = config->readNumEntry("InitialAlbumID", 0);
+}
+
+void DigikamView::saveViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("MainWindow");
+ config->writeEntry("SplitterSizes", d->splitter->sizes());
+
+ Album *album = AlbumManager::instance()->currentAlbum();
+ if(album)
+ {
+ config->writeEntry("InitialAlbumID", album->globalID());
+ }
+ else
+ {
+ config->writeEntry("InitialAlbumID", 0);
+ }
+}
+
+void DigikamView::showSideBars()
+{
+ d->leftSideBar->restore();
+ d->rightSideBar->restore();
+}
+
+void DigikamView::hideSideBars()
+{
+ d->leftSideBar->backup();
+ d->rightSideBar->backup();
+}
+
+void DigikamView::slotFirstItem(void)
+{
+ AlbumIconItem *currItem = dynamic_cast<AlbumIconItem*>(d->iconView->firstItem());
+ d->iconView->clearSelection();
+ d->iconView->updateContents();
+ if (currItem)
+ d->iconView->setCurrentItem(currItem);
+}
+
+void DigikamView::slotPrevItem(void)
+{
+ AlbumIconItem *currItem = dynamic_cast<AlbumIconItem*>(d->iconView->currentItem());
+ if (currItem)
+ {
+ if (currItem->prevItem())
+ {
+ d->iconView->clearSelection();
+ d->iconView->updateContents();
+ d->iconView->setCurrentItem(currItem->prevItem());
+ }
+ }
+}
+
+void DigikamView::slotNextItem(void)
+{
+ AlbumIconItem *currItem = dynamic_cast<AlbumIconItem*>(d->iconView->currentItem());
+ if (currItem)
+ {
+ if (currItem->nextItem())
+ {
+ d->iconView->clearSelection();
+ d->iconView->updateContents();
+ d->iconView->setCurrentItem(currItem->nextItem());
+ }
+ }
+}
+
+void DigikamView::slotLastItem(void)
+{
+ AlbumIconItem *currItem = dynamic_cast<AlbumIconItem*>(d->iconView->lastItem());
+ d->iconView->clearSelection();
+ d->iconView->updateContents();
+ if (currItem)
+ d->iconView->setCurrentItem(currItem);
+}
+
+void DigikamView::slotAllAlbumsLoaded()
+{
+ disconnect(d->albumManager, TQ_SIGNAL(signalAllAlbumsLoaded()),
+ this, TQ_SLOT(slotAllAlbumsLoaded()));
+
+ loadViewState();
+ Album *album = d->albumManager->findAlbum(d->initialAlbumID);
+ d->albumManager->setCurrentAlbum(album);
+
+ d->leftSideBar->loadViewState();
+ d->rightSideBar->loadViewState();
+ d->rightSideBar->populateTags();
+
+ slotAlbumSelected(album);
+}
+
+void DigikamView::slotSortAlbums(int order)
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings) return;
+ settings->setAlbumSortOrder( (AlbumSettings::AlbumSortOrder) order);
+ d->folderView->resort();
+}
+
+void DigikamView::slotNewAlbum()
+{
+ d->folderView->albumNew();
+}
+
+void DigikamView::slotDeleteAlbum()
+{
+ d->folderView->albumDelete();
+}
+
+void DigikamView::slotNewTag()
+{
+ d->tagFolderView->tagNew();
+}
+
+void DigikamView::slotDeleteTag()
+{
+ d->tagFolderView->tagDelete();
+}
+
+void DigikamView::slotEditTag()
+{
+ d->tagFolderView->tagEdit();
+}
+
+void DigikamView::slotNewQuickSearch()
+{
+ if (d->leftSideBar->getActiveTab() != d->searchBox)
+ d->leftSideBar->setActiveTab(d->searchBox);
+ d->searchFolderView->quickSearchNew();
+}
+
+void DigikamView::slotNewAdvancedSearch()
+{
+ if (d->leftSideBar->getActiveTab() != d->searchBox)
+ d->leftSideBar->setActiveTab(d->searchBox);
+ d->searchFolderView->extendedSearchNew();
+}
+
+void DigikamView::slotAlbumAdded(Album *album)
+{
+ if (!album->isRoot())
+ {
+ switch (album->type())
+ {
+ case Album::PHYSICAL:
+ {
+ d->folderSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ break;
+ }
+ case Album::TAG:
+ {
+ d->tagSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ d->tagFilterSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ break;
+ }
+ case Album::SEARCH:
+ {
+ d->searchSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ d->timeLineView->searchBar()->lineEdit()->completionObject()->addItem(album->title());
+ break;
+ }
+ default:
+ {
+ // Nothing to do with Album::DATE
+ break;
+ }
+ }
+ }
+}
+
+void DigikamView::slotAlbumDeleted(Album *album)
+{
+ d->albumHistory->deleteAlbum(album);
+
+ // display changed tags
+ if (album->type() == Album::TAG)
+ d->iconView->updateContents();
+
+ /*
+ // For what is this needed?
+ Album *a;
+ TQWidget *widget;
+ d->albumHistory->getCurrentAlbum(&a, &widget);
+
+ changeAlbumFromHistory(a, widget);
+ */
+
+ if (!album->isRoot())
+ {
+ switch (album->type())
+ {
+ case Album::PHYSICAL:
+ {
+ d->folderSearchBar->lineEdit()->completionObject()->removeItem(album->title());
+ break;
+ }
+ case Album::TAG:
+ {
+ d->tagSearchBar->lineEdit()->completionObject()->removeItem(album->title());
+ d->tagFilterSearchBar->lineEdit()->completionObject()->removeItem(album->title());
+ break;
+ }
+ case Album::SEARCH:
+ {
+ d->searchSearchBar->lineEdit()->completionObject()->removeItem(album->title());
+ d->timeLineView->searchBar()->lineEdit()->completionObject()->removeItem(album->title());
+ break;
+ }
+ default:
+ {
+ // Nothing to do with Album::DATE
+ break;
+ }
+ }
+ }
+}
+
+void DigikamView::slotAlbumRenamed(Album *album)
+{
+ // display changed names
+
+ if (album == d->albumManager->currentAlbum())
+ d->iconView->updateContents();
+
+ if (!album->isRoot())
+ {
+ switch (album->type())
+ {
+ case Album::PHYSICAL:
+ {
+ d->folderSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ d->folderView->slotTextFolderFilterChanged(d->folderSearchBar->lineEdit()->text());
+ break;
+ }
+ case Album::TAG:
+ {
+ d->tagSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ d->tagFolderView->slotTextTagFilterChanged(d->tagSearchBar->lineEdit()->text());
+
+ d->tagFilterSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ d->tagFilterView->slotTextTagFilterChanged(d->tagFilterSearchBar->lineEdit()->text());
+ break;
+ }
+ case Album::SEARCH:
+ {
+ d->searchSearchBar->lineEdit()->completionObject()->addItem(album->title());
+ d->searchFolderView->slotTextSearchFilterChanged(d->searchSearchBar->lineEdit()->text());
+
+ d->timeLineView->searchBar()->lineEdit()->completionObject()->addItem(album->title());
+ d->timeLineView->folderView()->slotTextSearchFilterChanged(d->timeLineView->searchBar()->lineEdit()->text());
+ break;
+ }
+ default:
+ {
+ // Nothing to do with Album::DATE
+ break;
+ }
+ }
+ }
+}
+
+void DigikamView::slotAlbumsCleared()
+{
+ d->iconView->clear();
+ emit signalAlbumSelected(false);
+
+ d->folderSearchBar->lineEdit()->completionObject()->clear();
+
+ d->tagSearchBar->lineEdit()->completionObject()->clear();
+ d->tagFilterSearchBar->lineEdit()->completionObject()->clear();
+
+ d->searchSearchBar->lineEdit()->completionObject()->clear();
+ d->timeLineView->searchBar()->lineEdit()->completionObject()->clear();
+}
+
+void DigikamView::slotAlbumHistoryBack(int steps)
+{
+ Album *album;
+ TQWidget *widget;
+
+ d->albumHistory->back(&album, &widget, steps);
+
+ changeAlbumFromHistory(album, widget);
+}
+
+void DigikamView::slotAlbumHistoryForward(int steps)
+{
+ Album *album;
+ TQWidget *widget;
+
+ d->albumHistory->forward(&album, &widget, steps);
+
+ changeAlbumFromHistory(album, widget);
+}
+
+void DigikamView::changeAlbumFromHistory(Album *album, TQWidget *widget)
+{
+ if (album && widget)
+ {
+ TQListViewItem *item = 0;
+
+ // Check if widget is a vbox used to host folderview, tagview or searchview.
+ if (TQVBox *v = dynamic_cast<TQVBox*>(widget))
+ {
+ if (v == d->folderBox)
+ {
+ item = (TQListViewItem*)album->extraData(d->folderView);
+ if(!item) return;
+
+ d->folderView->setSelected(item, true);
+ d->folderView->ensureItemVisible(item);
+ }
+ else if (v == d->tagBox)
+ {
+ item = (TQListViewItem*)album->extraData(d->tagFolderView);
+ if(!item) return;
+
+ d->tagFolderView->setSelected(item, true);
+ d->tagFolderView->ensureItemVisible(item);
+ }
+ else if (v == d->searchBox)
+ {
+ item = (TQListViewItem*)album->extraData(d->searchFolderView);
+ if(!item) return;
+
+ d->searchFolderView->setSelected(item, true);
+ d->searchFolderView->ensureItemVisible(item);
+ }
+ }
+ else if (DateFolderView *v = dynamic_cast<DateFolderView*>(widget))
+ {
+ item = (TQListViewItem*)album->extraData(v);
+ if(!item) return;
+ v->setSelected(item);
+ }
+ else if (TimeLineView *v = dynamic_cast<TimeLineView*>(widget))
+ {
+ item = (TQListViewItem*)album->extraData(v->folderView());
+ if(!item) return;
+
+ v->folderView()->setSelected(item, true);
+ v->folderView()->ensureItemVisible(item);
+ }
+
+ d->leftSideBar->setActiveTab(widget);
+
+ d->parent->enableAlbumBackwardHistory(!d->albumHistory->isBackwardEmpty());
+ d->parent->enableAlbumForwardHistory(!d->albumHistory->isForwardEmpty());
+ }
+}
+
+void DigikamView::clearHistory()
+{
+ d->albumHistory->clearHistory();
+ d->parent->enableAlbumBackwardHistory(false);
+ d->parent->enableAlbumForwardHistory(false);
+}
+
+void DigikamView::getBackwardHistory(TQStringList &titles)
+{
+ d->albumHistory->getBackwardHistory(titles);
+}
+
+void DigikamView::getForwardHistory(TQStringList &titles)
+{
+ d->albumHistory->getForwardHistory(titles);
+}
+
+void DigikamView::slotSelectAlbum(const KURL &)
+{
+ /* TODO
+ if (url.isEmpty())
+ return;
+
+ Album *album = d->albumManager->findPAlbum(url);
+ if(album && album->getViewItem())
+ {
+ AlbumFolderItem_Deprecated *item;
+ item = static_cast<AlbumFolderItem_Deprecated*>(album->getViewItem());
+ mFolderView_Deprecated->setSelected(item);
+ d->parent->enableAlbumBackwardHistory(!d->albumHistory->isBackwardEmpty());
+ d->parent->enableAlbumForwardHistory(!d->albumHistory->isForwardEmpty());
+ }
+ */
+}
+
+void DigikamView::slotGotoAlbumAndItem(AlbumIconItem* iconItem)
+{
+ KURL url( iconItem->imageInfo()->kurl() );
+ url.cleanPath();
+
+ emit signalNoCurrentItem();
+
+ Album* album = iconItem->imageInfo()->album();
+
+ // Change the current album in list view.
+ d->folderView->setCurrentAlbum(album);
+
+ // Change to (physical) Album view.
+ // Note, that this also opens the side bar if it is closed; this is
+ // considered as a feature, because it highlights that the view was changed.
+ d->leftSideBar->setActiveTab(d->folderBox);
+
+ // Set the activate item url to find in the Album View after
+ // all items have be reloaded.
+ d->iconView->setAlbumItemToFind(url);
+
+ // And finally toggle album manager to handle album history and
+ // reload all items.
+ d->albumManager->setCurrentAlbum(album);
+}
+
+void DigikamView::slotGotoDateAndItem(AlbumIconItem* iconItem)
+{
+ KURL url( iconItem->imageInfo()->kurl() );
+ url.cleanPath();
+ TQDate date = iconItem->imageInfo()->dateTime().date();
+
+ emit signalNoCurrentItem();
+
+ // Change to Date Album view.
+ // Note, that this also opens the side bar if it is closed; this is
+ // considered as a feature, because it highlights that the view was changed.
+ d->leftSideBar->setActiveTab(d->dateFolderView);
+
+ // Set the activate item url to find in the Album View after
+ // all items have be reloaded.
+ d->iconView->setAlbumItemToFind(url);
+
+ // Change the year and month of the iconItem (day is unused).
+ d->dateFolderView->gotoDate(date);
+}
+
+void DigikamView::slotGotoTagAndItem(int tagID)
+{
+ // FIXME: Arnd: don't know yet how to get the iconItem passed through ...
+ // FIXME: then we would know how to use the following ...
+ // KURL url( iconItem->imageInfo()->kurl() );
+ // url.cleanPath();
+
+ emit signalNoCurrentItem();
+
+ // Change to Tag Folder view.
+ // Note, that this also opens the side bar if it is closed; this is
+ // considered as a feature, because it highlights that the view was changed.
+ d->leftSideBar->setActiveTab(d->tagBox);
+
+ // Set the current tag in the tag folder view.
+ d->tagFolderView->selectItem(tagID);
+
+ // Set the activate item url to find in the Tag View after
+ // all items have be reloaded.
+ // FIXME: see above
+ // d->iconView->setAlbumItemToFind(url);
+}
+
+void DigikamView::slotAlbumSelected(Album* album)
+{
+ emit signalNoCurrentItem();
+
+ if (!album)
+ {
+ d->iconView->setAlbum(0);
+ emit signalAlbumSelected(false);
+ emit signalTagSelected(false);
+ return;
+ }
+
+ if (album->type() == Album::PHYSICAL)
+ {
+ emit signalAlbumSelected(true);
+ emit signalTagSelected(false);
+ }
+ else if (album->type() == Album::TAG)
+ {
+ emit signalAlbumSelected(false);
+ emit signalTagSelected(true);
+ }
+
+ d->albumHistory->addAlbum(album, d->leftSideBar->getActiveTab());
+ d->parent->enableAlbumBackwardHistory(!d->albumHistory->isBackwardEmpty());
+ d->parent->enableAlbumForwardHistory(!d->albumHistory->isForwardEmpty());
+
+ d->iconView->setAlbum(album);
+ if (album->isRoot())
+ d->albumWidgetStack->setPreviewMode(AlbumWidgetStack::WelcomePageMode);
+ else
+ d->albumWidgetStack->setPreviewMode(AlbumWidgetStack::PreviewAlbumMode);
+}
+
+void DigikamView::slotAlbumOpenInKonqui()
+{
+ Album *album = d->albumManager->currentAlbum();
+ if (!album || album->type() != Album::PHYSICAL)
+ return;
+
+ PAlbum* palbum = dynamic_cast<PAlbum*>(album);
+
+ new KRun(palbum->folderPath()); // KRun will delete itself.
+}
+
+void DigikamView::slotAlbumRefresh()
+{
+ d->iconView->refreshItems(d->iconView->allItems());
+}
+
+void DigikamView::slotImageSelected()
+{
+ // delay to slotDispatchImageSelected
+ d->needDispatchSelection = true;
+ d->selectionTimer->start(75, true);
+}
+
+void DigikamView::slotDispatchImageSelected()
+{
+ if (d->needDispatchSelection)
+ {
+ // the list of copies of ImageInfos of currently selected items, currentItem first
+ TQPtrList<ImageInfo> list = d->iconView->selectedImageInfos(true);
+ // no copy needed for this one, as this list is just used for counting
+ // the total number of images
+ KURL::List listAll = d->iconView->allItems();
+
+ if (list.isEmpty())
+ {
+ d->albumWidgetStack->setPreviewItem();
+ emit signalImageSelected(list, false, false, listAll);
+ emit signalNoCurrentItem();
+ }
+ else
+ {
+ d->rightSideBar->itemChanged(list);
+
+ AlbumIconItem *selectedItem = d->iconView->firstSelectedItem();
+ ImageInfo *previousInfo=0, *nextInfo=0;
+ if (selectedItem->prevItem())
+ previousInfo = static_cast<AlbumIconItem*>(selectedItem->prevItem())->imageInfo();
+ if (selectedItem->nextItem())
+ nextInfo = static_cast<AlbumIconItem*>(selectedItem->nextItem())->imageInfo();
+
+ // we fed a list of copies
+ d->rightSideBar->takeImageInfoOwnership(true);
+
+ if (!d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ d->albumWidgetStack->setPreviewItem(list.first(), previousInfo, nextInfo);
+ emit signalImageSelected(list, previousInfo, nextInfo, listAll);
+ }
+
+ d->needDispatchSelection = false;
+ }
+}
+
+void DigikamView::setThumbSize(int size)
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewImageMode)
+ {
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->albumWidgetStack->zoomMin();
+ double zmax = d->albumWidgetStack->zoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ double z = a*size+b;
+ d->albumWidgetStack->setZoomFactorSnapped(z);
+ }
+ else if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ {
+ if (size > ThumbnailSize::Huge)
+ d->thumbSize = ThumbnailSize::Huge;
+ else if (size < ThumbnailSize::Small)
+ d->thumbSize = ThumbnailSize::Small;
+ else
+ d->thumbSize = size;
+
+ emit signalThumbSizeChanged(d->thumbSize);
+
+ if (d->thumbSizeTimer)
+ {
+ d->thumbSizeTimer->stop();
+ delete d->thumbSizeTimer;
+ }
+
+ d->thumbSizeTimer = new TQTimer( this );
+ connect(d->thumbSizeTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotThumbSizeEffect()) );
+ d->thumbSizeTimer->start(300, true);
+ }
+}
+
+void DigikamView::slotThumbSizeEffect()
+{
+ emit signalNoCurrentItem();
+
+ d->iconView->setThumbnailSize(d->thumbSize);
+ toggleZoomActions();
+
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings)
+ return;
+ settings->setDefaultIconSize(d->thumbSize);
+}
+
+void DigikamView::toggleZoomActions()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewImageMode)
+ {
+ d->parent->enableZoomMinusAction(true);
+ d->parent->enableZoomPlusAction(true);
+
+ if (d->albumWidgetStack->maxZoom())
+ d->parent->enableZoomPlusAction(false);
+
+ if (d->albumWidgetStack->minZoom())
+ d->parent->enableZoomMinusAction(false);
+ }
+ else if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ {
+ d->parent->enableZoomMinusAction(true);
+ d->parent->enableZoomPlusAction(true);
+
+ if (d->thumbSize >= ThumbnailSize::Huge)
+ d->parent->enableZoomPlusAction(false);
+
+ if (d->thumbSize <= ThumbnailSize::Small)
+ d->parent->enableZoomMinusAction(false);
+ }
+}
+
+void DigikamView::slotZoomIn()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ {
+ setThumbSize(d->thumbSize + ThumbnailSize::Step);
+ toggleZoomActions();
+ emit signalThumbSizeChanged(d->thumbSize);
+ }
+ else if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewImageMode)
+ {
+ d->albumWidgetStack->increaseZoom();
+ }
+}
+
+void DigikamView::slotZoomOut()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ {
+ setThumbSize(d->thumbSize - ThumbnailSize::Step);
+ toggleZoomActions();
+ emit signalThumbSizeChanged(d->thumbSize);
+ }
+ else if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewImageMode)
+ {
+ d->albumWidgetStack->decreaseZoom();
+ }
+}
+
+void DigikamView::slotZoomTo100Percents()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewImageMode)
+ {
+ d->albumWidgetStack->toggleFitToWindowOr100();
+ }
+}
+
+void DigikamView::slotFitToWindow()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewImageMode)
+ {
+ d->albumWidgetStack->fitToWindow();
+ }
+}
+
+void DigikamView::slotZoomFactorChanged(double zoom)
+{
+ toggleZoomActions();
+
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->albumWidgetStack->zoomMin();
+ double zmax = d->albumWidgetStack->zoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ int size = (int)((zoom - b) /a);
+
+ emit signalZoomChanged(zoom, size);
+}
+
+void DigikamView::slotAlbumPropsEdit()
+{
+ d->folderView->albumEdit();
+}
+
+void DigikamView::slotAlbumSyncPicturesMetadata()
+{
+ Album *album = d->albumManager->currentAlbum();
+ if (!album)
+ return;
+
+ BatchSyncMetadata *syncMetadata = new BatchSyncMetadata(this, album);
+
+ connect(syncMetadata, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ d->parent, TQ_SLOT(slotProgressBarMode(int, const TQString&)));
+
+ connect(syncMetadata, TQ_SIGNAL(signalProgressValue(int)),
+ d->parent, TQ_SLOT(slotProgressValue(int)));
+
+ connect(syncMetadata, TQ_SIGNAL(signalComplete()),
+ this, TQ_SLOT(slotAlbumSyncPicturesMetadataDone()));
+
+ connect(d->parent, TQ_SIGNAL(signalCancelButtonPressed()),
+ syncMetadata, TQ_SLOT(slotAbort()));
+
+ syncMetadata->parseAlbum();
+}
+
+void DigikamView::slotAlbumSyncPicturesMetadataDone()
+{
+ applySettings();
+}
+
+void DigikamView::slotAlbumImportFolder()
+{
+ d->folderView->albumImportFolder();
+}
+
+void DigikamView::slotAlbumHighlight()
+{
+ // TODO:
+ // Don't know what this is supposed to do.
+ // Perhaps some flashing or other eye kandy
+ /*
+ Album *album = d->albumManager->currentAlbum();
+ if (!album || !album->type() == Album::PHYSICAL)
+ return;
+
+ d->folderView->setAlbumThumbnail(dynamic_cast<PAlbum*>(album));
+ */
+}
+
+void DigikamView::slotEscapePreview()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode ||
+ d->albumWidgetStack->previewMode() == AlbumWidgetStack::WelcomePageMode)
+ return;
+
+ AlbumIconItem *currItem = dynamic_cast<AlbumIconItem*>(d->iconView->currentItem());
+ slotTogglePreviewMode(currItem);
+}
+
+void DigikamView::slotImagePreview()
+{
+ AlbumIconItem *currItem = dynamic_cast<AlbumIconItem*>(d->iconView->currentItem());
+ if (currItem)
+ slotTogglePreviewMode(currItem);
+}
+
+// This method toggle between AlbumView and ImagePreview Modes, depending of context.
+void DigikamView::slotTogglePreviewMode(AlbumIconItem *iconItem)
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode && iconItem)
+ {
+ // We will go to ImagePreview Mode.
+ ImageInfo *previousInfo=0, *nextInfo=0;
+
+ if (iconItem->prevItem())
+ previousInfo = static_cast<AlbumIconItem*>(iconItem->prevItem())->imageInfo();
+
+ if (iconItem->nextItem())
+ nextInfo = static_cast<AlbumIconItem*>(iconItem->nextItem())->imageInfo();
+
+ d->albumWidgetStack->setPreviewItem(iconItem->imageInfo(), previousInfo, nextInfo);
+ }
+ else
+ {
+ // We go back to AlbumView Mode.
+ d->albumWidgetStack->setPreviewMode( AlbumWidgetStack::PreviewAlbumMode );
+ }
+}
+
+void DigikamView::slotToggledToPreviewMode(bool b)
+{
+ toggleZoomActions();
+
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ emit signalThumbSizeChanged(d->iconView->thumbnailSize().size());
+ else if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewImageMode)
+ slotZoomFactorChanged(d->albumWidgetStack->zoomFactor());
+
+ emit signalTogglePreview(b);
+}
+
+void DigikamView::slotImageEdit()
+{
+ AlbumIconItem *currItem = dynamic_cast<AlbumIconItem*>(d->iconView->currentItem());
+ if (currItem)
+ imageEdit(currItem);
+}
+
+void DigikamView::imageEdit(AlbumIconItem *iconItem)
+{
+ AlbumIconItem *item;
+
+ if (!iconItem)
+ {
+ item = d->iconView->firstSelectedItem();
+ if (!item) return;
+ }
+ else
+ {
+ item = iconItem;
+ }
+
+ d->iconView->slotDisplayItem(item);
+}
+
+void DigikamView::slotImageExifOrientation(int orientation)
+{
+ d->iconView->slotSetExifOrientation(orientation);
+}
+
+void DigikamView::slotLightTable()
+{
+ ImageInfoList empty;
+ d->iconView->insertToLightTable(empty, 0, true);
+}
+
+void DigikamView::slotImageLightTable()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ {
+ // put images into an emptied light table
+ d->iconView->insertSelectionToLightTable(false);
+ }
+ else
+ {
+ ImageInfoList list;
+ ImageInfo *info = d->albumWidgetStack->imagePreviewView()->getImageInfo();
+ list.append(info);
+ // put images into an emptied light table
+ d->iconView->insertToLightTable(list, info, false);
+ }
+}
+
+void DigikamView::slotImageAddToLightTable()
+{
+ if (d->albumWidgetStack->previewMode() == AlbumWidgetStack::PreviewAlbumMode)
+ {
+ // add images to the existing images in the light table
+ d->iconView->insertSelectionToLightTable(true);
+ }
+ else
+ {
+ ImageInfoList list;
+ ImageInfo *info = d->albumWidgetStack->imagePreviewView()->getImageInfo();
+ list.append(info);
+ // add images to the existing images in the light table
+ d->iconView->insertToLightTable(list, info, true);
+ }
+}
+
+void DigikamView::slotImageRename(AlbumIconItem *iconItem)
+{
+ AlbumIconItem *item;
+
+ if (!iconItem)
+ {
+ item = d->iconView->firstSelectedItem();
+ if (!item) return;
+ }
+ else
+ {
+ item = iconItem;
+ }
+
+ d->iconView->slotRename(item);
+}
+
+void DigikamView::slotImageDelete()
+{
+ d->iconView->slotDeleteSelectedItems(false);
+}
+
+void DigikamView::slotImageDeletePermanently()
+{
+ d->iconView->slotDeleteSelectedItems(true);
+}
+
+void DigikamView::slotImageDeletePermanentlyDirectly()
+{
+ d->iconView->slotDeleteSelectedItemsDirectly(false);
+}
+
+void DigikamView::slotImageTrashDirectly()
+{
+ d->iconView->slotDeleteSelectedItemsDirectly(true);
+}
+
+void DigikamView::slotSelectAll()
+{
+ d->iconView->selectAll();
+}
+
+void DigikamView::slotSelectNone()
+{
+ d->iconView->clearSelection();
+}
+
+void DigikamView::slotSelectInvert()
+{
+ d->iconView->invertSelection();
+}
+
+void DigikamView::slotSortImages(int order)
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings)
+ return;
+ settings->setImageSortOrder((AlbumSettings::ImageSortOrder) order);
+ d->iconView->slotRearrange();
+}
+
+void DigikamView::slotLeftSidebarChangedTab(TQWidget* w)
+{
+ // setActive means that selection changes are propagated, nothing more.
+ // Additionally, when it is set to true, the selectionChanged signal will be emitted.
+ // So this is the place which causes the behavior that when the left sidebar
+ // tab is changed, the current album is changed as well.
+ d->dateFolderView->setActive(w == d->dateFolderView);
+ d->folderView->setActive(w == d->folderBox);
+ d->tagFolderView->setActive(w == d->tagBox);
+ d->searchFolderView->setActive(w == d->searchBox);
+ d->timeLineView->setActive(w == d->timeLineView);
+}
+
+void DigikamView::slotAssignRating(int rating)
+{
+ d->iconView->slotAssignRating(rating);
+}
+
+void DigikamView::slotAssignRatingNoStar()
+{
+ d->iconView->slotAssignRating(0);
+}
+
+void DigikamView::slotAssignRatingOneStar()
+{
+ d->iconView->slotAssignRating(1);
+}
+
+void DigikamView::slotAssignRatingTwoStar()
+{
+ d->iconView->slotAssignRating(2);
+}
+
+void DigikamView::slotAssignRatingThreeStar()
+{
+ d->iconView->slotAssignRating(3);
+}
+
+void DigikamView::slotAssignRatingFourStar()
+{
+ d->iconView->slotAssignRating(4);
+}
+
+void DigikamView::slotAssignRatingFiveStar()
+{
+ d->iconView->slotAssignRating(5);
+}
+
+void DigikamView::slotSlideShowAll()
+{
+ ImageInfoList infoList;
+ AlbumIconItem* item = dynamic_cast<AlbumIconItem*>(d->iconView->firstItem());
+ while (item)
+ {
+ infoList.append(item->imageInfo());
+ item = dynamic_cast<AlbumIconItem*>(item->nextItem());
+ }
+
+ slideShow(infoList);
+}
+
+void DigikamView::slotSlideShowSelection()
+{
+ ImageInfoList infoList;
+ AlbumIconItem* item = dynamic_cast<AlbumIconItem*>(d->iconView->firstItem());
+ while (item)
+ {
+ if (item->isSelected())
+ infoList.append(item->imageInfo());
+ item = dynamic_cast<AlbumIconItem*>(item->nextItem());
+ }
+
+ slideShow(infoList);
+}
+
+void DigikamView::slotSlideShowRecursive()
+{
+ Album *album = AlbumManager::instance()->currentAlbum();
+ if(album)
+ {
+ AlbumList albumList;
+ albumList.append(album);
+ AlbumIterator it(album);
+ while (it.current())
+ {
+ albumList.append(*it);
+ ++it;
+ }
+
+ ImageInfoAlbumsJob *job = new ImageInfoAlbumsJob;
+ connect(job, TQ_SIGNAL(signalCompleted(const ImageInfoList&)),
+ this, TQ_SLOT(slotItemsInfoFromAlbums(const ImageInfoList&)));
+ job->allItemsFromAlbums(albumList);
+ }
+}
+
+void DigikamView::slotItemsInfoFromAlbums(const ImageInfoList& infoList)
+{
+ ImageInfoList list = infoList;
+ slideShow(list);
+}
+
+void DigikamView::slideShow(ImageInfoList &infoList)
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ bool startWithCurrent = config->readBoolEntry("SlideShowStartCurrent", false);
+
+ int i = 0;
+ float cnt = (float)infoList.count();
+ emit signalProgressBarMode(StatusProgressBar::CancelProgressBarMode,
+ i18n("Preparing slideshow of %1 images. Please wait...")
+ .arg(infoList.count()));
+
+ DMetadata meta;
+ SlideShowSettings settings;
+ settings.exifRotate = AlbumSettings::instance()->getExifRotate();
+ settings.delay = config->readNumEntry("SlideShowDelay", 5) * 1000;
+ settings.printName = config->readBoolEntry("SlideShowPrintName", true);
+ settings.printDate = config->readBoolEntry("SlideShowPrintDate", false);
+ settings.printApertureFocal = config->readBoolEntry("SlideShowPrintApertureFocal", false);
+ settings.printExpoSensitivity = config->readBoolEntry("SlideShowPrintExpoSensitivity", false);
+ settings.printMakeModel = config->readBoolEntry("SlideShowPrintMakeModel", false);
+ settings.printComment = config->readBoolEntry("SlideShowPrintComment", false);
+ settings.loop = config->readBoolEntry("SlideShowLoop", false);
+
+ d->cancelSlideShow = false;
+ for (ImageInfoList::iterator it = infoList.begin() ;
+ !d->cancelSlideShow && (it != infoList.end()) ; ++it)
+ {
+ ImageInfo *info = *it;
+ settings.fileList.append(info->kurl());
+ SlidePictureInfo pictInfo;
+ pictInfo.comment = info->caption();
+
+ // Perform optimizations: only read pictures metadata if necessary.
+ if (settings.printApertureFocal || settings.printExpoSensitivity || settings.printMakeModel)
+ {
+ meta.load(info->kurl().path());
+ pictInfo.photoInfo = meta.getPhotographInformations();
+ }
+
+ // In case of dateTime extraction from metadata failed
+ pictInfo.photoInfo.dateTime = info->dateTime();
+ settings.pictInfoMap.insert(info->kurl(), pictInfo);
+
+ emit signalProgressValue((int)((i++/cnt)*100.0));
+ kapp->eventLoop()->processEvents(TQEventLoop::AllEvents & ~TQEventLoop::WaitForMore);
+ }
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+
+ if (!d->cancelSlideShow)
+ {
+ SlideShow *slide = new SlideShow(settings);
+ if (startWithCurrent)
+ {
+ AlbumIconItem* current = dynamic_cast<AlbumIconItem*>(d->iconView->currentItem());
+ if (current)
+ slide->setCurrent(current->imageInfo()->kurl());
+ }
+
+ slide->show();
+ }
+}
+
+void DigikamView::slotCancelSlideShow()
+{
+ d->cancelSlideShow = true;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/digikamview.h b/src/digikam/digikamview.h
new file mode 100644
index 00000000..cf929074
--- /dev/null
+++ b/src/digikam/digikamview.h
@@ -0,0 +1,194 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2002-16-10
+ * Description : implementation of album view interface.
+ *
+ * Copyright (C) 2002-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2002-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIGIKAMVIEW_H
+#define DIGIKAMVIEW_H
+
+// TQt includes.
+
+#include <tqhbox.h>
+#include <tqstringlist.h>
+#include <tqmap.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+
+class KURL;
+
+namespace Digikam
+{
+class AlbumIconItem;
+class AlbumSettings;
+class Album;
+class DigikamViewPriv;
+
+class DigikamView : public TQHBox
+{
+ TQ_OBJECT
+
+public:
+
+ DigikamView(TQWidget *parent);
+ ~DigikamView();
+
+ void applySettings();
+ void refreshView();
+ void clearHistory();
+ void getForwardHistory(TQStringList &titles);
+ void getBackwardHistory(TQStringList &titles);
+ void showSideBars();
+ void hideSideBars();
+ void setThumbSize(int size);
+
+signals:
+
+ void signalAlbumSelected(bool val);
+ void signalTagSelected(bool val);
+ void signalImageSelected(const TQPtrList<ImageInfo>& list, bool, bool,
+ const KURL::List& listAll);
+ void signalNoCurrentItem();
+ void signalProgressBarMode(int, const TQString&);
+ void signalProgressValue(int);
+ void signalThumbSizeChanged(int);
+ void signalZoomChanged(double, int);
+ void signalTogglePreview(bool);
+ void signalGotoAlbumAndItem(AlbumIconItem *);
+ void signalGotoDateAndItem(AlbumIconItem *);
+ void signalGotoTagAndItem(int tagID);
+ void signalChangedTab(TQWidget*);
+
+public slots:
+
+ // View Action slots
+ void slotZoomIn();
+ void slotZoomOut();
+ void slotZoomTo100Percents();
+ void slotFitToWindow();
+ void slotSlideShowAll();
+ void slotSlideShowSelection();
+ void slotSlideShowRecursive();
+
+ // Album action slots
+ void slotNewAlbum();
+ void slotSortAlbums(int order);
+ void slotDeleteAlbum();
+ void slotSelectAlbum(const KURL &url);
+ void slotAlbumPropsEdit();
+ void slotAlbumOpenInKonqui();
+ void slotAlbumRefresh();
+ void slotAlbumImportFolder();
+ void slotAlbumHistoryBack(int steps=1);
+ void slotAlbumHistoryForward(int steps=1);
+ void slotAlbumAdded(Album *album);
+ void slotAlbumDeleted(Album *album);
+ void slotAlbumRenamed(Album *album);
+ void slotAlbumSyncPicturesMetadata();
+ void slotAlbumSyncPicturesMetadataDone();
+ void slotAlbumSelected(Album* album);
+ void slotGotoAlbumAndItem(AlbumIconItem* iconItem);
+ void slotGotoDateAndItem(AlbumIconItem* iconItem);
+ void slotGotoTagAndItem(int tagID);
+
+ // Tag action slots
+ void slotNewTag();
+ void slotDeleteTag();
+ void slotEditTag();
+
+ // Search action slots
+ void slotNewQuickSearch();
+ void slotNewAdvancedSearch();
+
+ // Image action slots
+ void slotImageLightTable();
+ void slotImageAddToLightTable();
+ void slotImagePreview();
+ void slotImageEdit();
+ void slotImageExifOrientation(int orientation);
+ void slotImageRename(AlbumIconItem* iconItem=0);
+ void slotImageDelete();
+ void slotImageDeletePermanently();
+ void slotImageDeletePermanentlyDirectly();
+ void slotImageTrashDirectly();
+ void slotSelectAll();
+ void slotSelectNone();
+ void slotSelectInvert();
+ void slotSortImages(int order);
+
+ // Image Rating slots
+ void slotAssignRating(int rating);
+ void slotAssignRatingNoStar();
+ void slotAssignRatingOneStar();
+ void slotAssignRatingTwoStar();
+ void slotAssignRatingThreeStar();
+ void slotAssignRatingFourStar();
+ void slotAssignRatingFiveStar();
+
+ // Tools action slots.
+ void slotLightTable();
+
+private:
+
+ void toggleZoomActions();
+ void setupConnections();
+ void loadViewState();
+ void saveViewState();
+ void changeAlbumFromHistory(Album *album, TQWidget *widget);
+ void imageEdit(AlbumIconItem* iconItem=0);
+ void slideShow(ImageInfoList &infoList);
+
+private slots:
+
+ void slotAllAlbumsLoaded();
+
+ void slotAlbumsCleared();
+ void slotAlbumHighlight();
+
+ void slotImageSelected();
+ void slotTogglePreviewMode(AlbumIconItem *iconItem=0);
+ void slotDispatchImageSelected();
+ void slotItemsInfoFromAlbums(const ImageInfoList&);
+
+ void slotLeftSidebarChangedTab(TQWidget* w);
+
+ void slotFirstItem(void);
+ void slotPrevItem(void);
+ void slotNextItem(void);
+ void slotLastItem(void);
+
+ void slotToggledToPreviewMode(bool);
+ void slotEscapePreview();
+ void slotCancelSlideShow();
+
+ void slotThumbSizeEffect();
+ void slotZoomFactorChanged(double);
+
+private:
+
+ DigikamViewPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // DIGIKAMVIEW_H
diff --git a/src/digikam/dio.cpp b/src/digikam/dio.cpp
new file mode 100644
index 00000000..f9639fcf
--- /dev/null
+++ b/src/digikam/dio.cpp
@@ -0,0 +1,262 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-17
+ * Description : low level files management interface.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqcstring.h>
+#include <tqdatastream.h>
+
+// KDE includes.
+
+#include <kprotocolinfo.h>
+#include <tdeglobalsettings.h>
+#include <tdeio/renamedlg.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albumsettings.h"
+#include "albummanager.h"
+#include "albumlister.h"
+#include "albumdb.h"
+#include "album.h"
+#include "dio.h"
+#include "dio_p.h"
+#include "dio_p.moc"
+
+namespace DIO
+{
+
+TDEIO::Job* copy(const KURL& src, const KURL& dest)
+{
+ TDEIO::Job* job = TDEIO::copy(src, dest, true);
+ new Watch(job);
+
+ return job;
+}
+
+TDEIO::Job* copy(const KURL::List& srcList, const KURL& dest)
+{
+ TDEIO::Job* job = TDEIO::copy(srcList, dest, true);
+ new Watch(job);
+
+ return job;
+}
+
+TDEIO::Job* move(const KURL& src, const KURL& dest)
+{
+ TDEIO::Job* job = TDEIO::move(src, dest, true);
+ new Watch(job);
+
+ return job;
+}
+
+TDEIO::Job* move(const KURL::List& srcList, const KURL& dest)
+{
+ TDEIO::Job* job = TDEIO::move(srcList, dest, true);
+ new Watch(job);
+
+ return job;
+}
+
+TDEIO::Job* del(const KURL& src, bool useTrash)
+{
+ TDEIO::Job* job = 0;
+
+ if (useTrash)
+ {
+ KURL dest("trash:/");
+
+ if (!KProtocolInfo::isKnownProtocol(dest))
+ {
+ dest = TDEGlobalSettings::trashPath();
+ }
+
+ job = TDEIO::move( src, dest );
+ }
+ else
+ {
+ job = TDEIO::del(src);
+ }
+
+ new Watch(job);
+ return job;
+}
+
+TDEIO::Job* del(const KURL::List& srcList, bool useTrash)
+{
+ TDEIO::Job* job = 0;
+
+ if (useTrash)
+ {
+ KURL dest("trash:/");
+
+ if (!KProtocolInfo::isKnownProtocol(dest))
+ {
+ dest = TDEGlobalSettings::trashPath();
+ }
+
+ job = TDEIO::move( srcList, dest );
+ }
+ else
+ {
+ job = TDEIO::del(srcList);
+ }
+
+ new Watch(job);
+ return job;
+}
+
+TDEIO::CopyJob *rename(const KURL& src, const KURL& dest)
+{
+ TDEIO::CopyJob * job = TDEIO::move(src, dest, false);
+ new Watch(job);
+
+ return job;
+
+ /*
+ KURL srcdir;
+ srcdir.setDirectory(src.directory());
+ KURL dstdir;
+ dstdir.setDirectory(dest.directory());
+ Digikam::PAlbum* srcAlbum = Digikam::AlbumManager::instance()->findPAlbum(srcdir);
+ Digikam::PAlbum* dstAlbum = Digikam::AlbumManager::instance()->findPAlbum(dstdir);
+ if (!srcAlbum || !dstAlbum)
+ {
+ DWarning() << "Source Album " << src.directory() << " not found" << endl;
+ return false;
+ }
+
+ TQString srcPath = Digikam::AlbumManager::instance()->getLibraryPath() + src.path();
+ TQString dstPath = Digikam::AlbumManager::instance()->getLibraryPath() + dest.path();
+ TQString newDstPath;
+
+ bool overwrite = false;
+
+ struct stat stbuf;
+ while (::stat(TQFile::encodeName(dstPath), &stbuf) == 0)
+ {
+ TDEIO::RenameDlg_Result result =
+ TDEIO::open_RenameDlg(i18n("Rename File"), srcPath, dstPath,
+ TDEIO::RenameDlg_Mode(TDEIO::M_SINGLE |
+ TDEIO::M_OVERWRITE),
+ newDstPath);
+
+ dstPath = newDstPath;
+
+ switch (result)
+ {
+ case TDEIO::R_CANCEL:
+ {
+ return false;
+ }
+ case TDEIO::R_OVERWRITE:
+ {
+ overwrite = true;
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (overwrite)
+ break;
+ }
+
+ Digikam::AlbumDB* db = Digikam::AlbumManager::instance()->albumDB();
+ if (::rename(TQFile::encodeName(srcPath), TQFile::encodeName(dstPath)) == 0)
+ {
+ db->moveItem(srcAlbum->id(), src.fileName(),
+ dstAlbum->id(), KURL(dstPath).fileName());
+ return true;
+ }
+
+ KMessageBox::error(0, i18n("Failed to rename file\n%1")
+ .arg(src.fileName()), i18n("Rename Failed"));
+ return false;
+ */
+}
+
+TDEIO::Job* scan(const KURL& albumURL)
+{
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << Digikam::AlbumManager::instance()->getLibraryPath();
+ ds << albumURL;
+ ds << TQString();
+ ds << 0;
+ ds << 0;
+ ds << 0;
+
+ // extra parameter: trigger scan
+ ds << 1;
+
+ TDEIO::Job* job = new TDEIO::TransferJob(albumURL,
+ TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(),
+ false);
+ new Watch(job);
+
+ return job;
+}
+
+bool running()
+{
+ return (Watch::m_runCount != 0);
+}
+
+Watch::Watch(TDEIO::Job* job)
+{
+ m_runCount++;
+ connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotDone(TDEIO::Job*)));
+}
+
+void Watch::slotDone(TDEIO::Job*)
+{
+ Digikam::AlbumManager::instance()->refresh();
+ Digikam::AlbumLister::instance()->refresh();
+ m_runCount--;
+
+ delete this;
+}
+
+uint Watch::m_runCount = 0;
+
+} // namespace DIO
diff --git a/src/digikam/dio.h b/src/digikam/dio.h
new file mode 100644
index 00000000..a7c96d77
--- /dev/null
+++ b/src/digikam/dio.h
@@ -0,0 +1,54 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-17
+ * Description : low level files management interface.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIO_H
+#define DIO_H
+
+// KDE includes.
+
+#include <tdeio/job.h>
+
+namespace DIO
+{
+
+ TDEIO::Job* copy(const KURL& src, const KURL& dest);
+
+ TDEIO::Job* copy(const KURL::List& srcList, const KURL& dest);
+
+ TDEIO::Job* move(const KURL& src, const KURL& dest);
+
+ TDEIO::Job* move(const KURL::List& srcList, const KURL& dest);
+
+ TDEIO::Job* del(const KURL& src, bool useTrash = true);
+
+ TDEIO::Job* del(const KURL::List& srcList, bool useTrash = true);
+
+ TDEIO::CopyJob* rename(const KURL& src, const KURL& dest);
+
+ TDEIO::Job* scan(const KURL& albumURL);
+
+ bool running();
+
+} // namespace DIO
+
+#endif /* DIO_H */
diff --git a/src/digikam/dio_p.h b/src/digikam/dio_p.h
new file mode 100644
index 00000000..660cad04
--- /dev/null
+++ b/src/digikam/dio_p.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-17
+ * Description : TQt Object to follow low level files management.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIO_P_H
+#define DIO_P_H
+
+// TQt includes.
+
+#include <tqobject.h>
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace DIO
+{
+
+class Watch : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ Watch(TDEIO::Job* job);
+
+ static uint m_runCount;
+
+private slots:
+
+ void slotDone(TDEIO::Job* job);
+};
+
+} // namespace DIO
+
+#endif /* DIO_P_H */
diff --git a/src/digikam/dragobjects.cpp b/src/digikam/dragobjects.cpp
new file mode 100644
index 00000000..14a3545f
--- /dev/null
+++ b/src/digikam/dragobjects.cpp
@@ -0,0 +1,374 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-26
+ * Description : Drag object info container
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dragobjects.h"
+
+namespace Digikam
+{
+
+ItemDrag::ItemDrag(const KURL::List &urls,
+ const KURL::List &kioURLs,
+ const TQValueList<int>& albumIDs,
+ const TQValueList<int>& imageIDs,
+ TQWidget* dragSource, const char* name)
+ : KURLDrag(urls, dragSource, name),
+ m_kioURLs(kioURLs),
+ m_albumIDs(albumIDs), m_imageIDs(imageIDs)
+{
+}
+
+bool ItemDrag::canDecode(const TQMimeSource* e)
+{
+ return e->provides("digikam/item-ids") ||
+ e->provides("digikam/album-ids") ||
+ e->provides("digikam/image-ids") ||
+ e->provides("digikam/digikamalbums");
+}
+
+bool ItemDrag::decode(const TQMimeSource* e, KURL::List &urls, KURL::List &kioURLs,
+ TQValueList<int>& albumIDs, TQValueList<int>& imageIDs)
+{
+ urls.clear();
+ kioURLs.clear();
+ albumIDs.clear();
+ imageIDs.clear();
+
+ if (KURLDrag::decode(e, urls))
+ {
+ TQByteArray albumarray = e->encodedData("digikam/album-ids");
+ TQByteArray imagearray = e->encodedData("digikam/image-ids");
+ TQByteArray kioarray = e->encodedData("digikam/digikamalbums");
+
+ if (albumarray.size() && imagearray.size() && kioarray.size())
+ {
+ int id;
+
+ TQDataStream dsAlbums(albumarray, IO_ReadOnly);
+ while (!dsAlbums.atEnd())
+ {
+ dsAlbums >> id;
+ albumIDs.append(id);
+ }
+
+ TQDataStream dsImages(imagearray, IO_ReadOnly);
+ while (!dsImages.atEnd())
+ {
+ dsImages >> id;
+ imageIDs.append(id);
+ }
+
+ KURL u;
+ TQDataStream dsKio(kioarray, IO_ReadOnly);
+ while (!dsKio.atEnd())
+ {
+ dsKio >> u;
+ kioURLs.append(u);
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+TQByteArray ItemDrag::encodedData(const char* mime) const
+{
+ TQCString mimetype(mime);
+
+ if (mimetype == "digikam/album-ids")
+ {
+ TQByteArray byteArray;
+ TQDataStream ds(byteArray, IO_WriteOnly);
+
+ TQValueList<int>::const_iterator it;
+ for (it = m_albumIDs.begin(); it != m_albumIDs.end(); ++it)
+ {
+ ds << (*it);
+ }
+
+ return byteArray;
+ }
+ else if (mimetype == "digikam/image-ids")
+ {
+ TQByteArray byteArray;
+ TQDataStream ds(byteArray, IO_WriteOnly);
+
+ TQValueList<int>::const_iterator it;
+ for (it = m_imageIDs.begin(); it != m_imageIDs.end(); ++it)
+ {
+ ds << (*it);
+ }
+
+ return byteArray;
+ }
+ else if (mimetype == "digikam/digikamalbums")
+ {
+ TQByteArray byteArray;
+ TQDataStream ds(byteArray, IO_WriteOnly);
+
+ KURL::List::const_iterator it;
+ for (it = m_kioURLs.begin(); it != m_kioURLs.end(); ++it)
+ {
+ ds << (*it);
+ }
+
+ return byteArray;
+ }
+ else
+ {
+ return KURLDrag::encodedData(mime);
+ }
+}
+
+const char* ItemDrag::format(int i) const
+{
+ if (i == 0)
+ return "text/uri-list";
+ else if (i == 1)
+ return "digikam/item-ids";
+ else if (i == 2)
+ return "digikam/album-ids";
+ else if (i == 3)
+ return "digikam/image-ids";
+ else if (i == 4)
+ return "digikam/digikamalbums";
+ else
+ return 0;
+}
+
+// ------------------------------------------------------------------------
+
+TagDrag::TagDrag( int albumid, TQWidget *dragSource,
+ const char *name ) :
+ TQDragObject( dragSource, name )
+{
+ mAlbumID = albumid;
+}
+
+bool TagDrag::canDecode( const TQMimeSource* e )
+{
+ return e->provides("digikam/tag-id");
+}
+
+const char* TagDrag::format( int i ) const
+{
+ if ( i == 0 )
+ return "digikam/tag-id";
+
+ return 0;
+}
+
+TQByteArray TagDrag::encodedData( const char* ) const
+{
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << mAlbumID;
+ return ba;
+}
+
+// ------------------------------------------------------------------------
+
+AlbumDrag::AlbumDrag(const KURL &url, int albumid,
+ TQWidget *dragSource,
+ const char *name) :
+ KURLDrag(url, dragSource, name)
+{
+ mAlbumID = albumid;
+}
+
+bool AlbumDrag::canDecode( const TQMimeSource* e )
+{
+ return e->provides("digikam/album-id");
+}
+
+const char* AlbumDrag::format( int i ) const
+{
+ if (i == 0)
+ return "text/uri-list";
+ else if ( i == 1 )
+ return "digikam/album-id";
+
+ return 0;
+}
+
+TQByteArray AlbumDrag::encodedData(const char *mime) const
+{
+ TQCString mimetype( mime );
+ if(mimetype == "digikam/album-id")
+ {
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << mAlbumID;
+ return ba;
+ }
+ else
+ {
+ return KURLDrag::encodedData(mime);
+ }
+}
+
+bool AlbumDrag::decode(const TQMimeSource* e, KURL::List &urls,
+ int &albumID)
+{
+ urls.clear();
+ albumID = -1;
+
+ if(KURLDrag::decode(e, urls))
+ {
+ TQByteArray ba = e->encodedData("digikam/album-id");
+ if (ba.size())
+ {
+ TQDataStream ds(ba, IO_ReadOnly);
+ if(!ds.atEnd())
+ {
+ ds >> albumID;
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// ------------------------------------------------------------------------
+
+TagListDrag::TagListDrag(const TQValueList<int>& tagIDs, TQWidget *dragSource,
+ const char *name)
+ : TQDragObject( dragSource, name )
+{
+ m_tagIDs = tagIDs;
+}
+
+bool TagListDrag::canDecode(const TQMimeSource* e)
+{
+ return e->provides("digikam/taglist");
+}
+
+TQByteArray TagListDrag::encodedData(const char*) const
+{
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << m_tagIDs;
+ return ba;
+}
+
+const char* TagListDrag::format(int i) const
+{
+ if ( i == 0 )
+ return "digikam/taglist";
+
+ return 0;
+}
+
+// ------------------------------------------------------------------------
+
+CameraItemListDrag::CameraItemListDrag(const TQStringList& cameraItemPaths,
+ TQWidget *dragSource,
+ const char *name)
+ : TQDragObject( dragSource, name )
+{
+ m_cameraItemPaths = cameraItemPaths;
+}
+
+bool CameraItemListDrag::canDecode(const TQMimeSource* e)
+{
+ return e->provides("digikam/cameraItemlist");
+}
+
+TQByteArray CameraItemListDrag::encodedData(const char*) const
+{
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << m_cameraItemPaths;
+ return ba;
+}
+
+const char* CameraItemListDrag::format(int i) const
+{
+ if ( i == 0 )
+ return "digikam/cameraItemlist";
+
+ return 0;
+}
+
+// ------------------------------------------------------------------------
+
+CameraDragObject::CameraDragObject(const CameraType& ctype, TQWidget *dragSource)
+ : TQStoredDrag("camera/unknown", dragSource)
+{
+ setCameraType(ctype);
+}
+
+CameraDragObject::~CameraDragObject()
+{
+}
+
+void CameraDragObject::setCameraType(const CameraType& ctype)
+{
+ TQByteArray byteArray;
+ TQDataStream ds(byteArray, IO_WriteOnly);
+
+ ds << ctype.title();
+ ds << ctype.model();
+ ds << ctype.port();
+ ds << ctype.path();
+ ds << ctype.lastAccess();
+
+ setEncodedData(byteArray);
+}
+
+bool CameraDragObject::canDecode(const TQMimeSource* e)
+{
+ return e->provides("camera/unknown");
+}
+
+bool CameraDragObject::decode(const TQMimeSource* e, CameraType& ctype)
+{
+ TQByteArray payload = e->encodedData("camera/unknown");
+ if (payload.size())
+ {
+ TQString title, model, port, path;
+ TQDateTime lastAccess;
+
+ TQDataStream ds(payload, IO_ReadOnly);
+ ds >> title;
+ ds >> model;
+ ds >> port;
+ ds >> path;
+ ds >> lastAccess;
+
+ ctype = CameraType(title, model, port, path, lastAccess);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/dragobjects.h b/src/digikam/dragobjects.h
new file mode 100644
index 00000000..35a70f7a
--- /dev/null
+++ b/src/digikam/dragobjects.h
@@ -0,0 +1,205 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-26
+ * Description : Drag object info container.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2005 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DRAGOBJECTS_H
+#define DRAGOBJECTS_H
+
+// TQt includes.
+
+#include <tqdragobject.h>
+#include <tqstringlist.h>
+#include <tqvaluelist.h>
+
+// KDE includes.
+
+#include <kurldrag.h>
+
+// Local includes.
+
+#include "cameratype.h"
+
+class TQWidget;
+
+namespace Digikam
+{
+
+/**
+ * Provides a drag object for a list of KURLs with its related database IDs.
+ *
+ * Images can be moved through ItemDrag. It is possible to move them on
+ * another application which understands KURLDrag, like konqueror, to
+ * copy the images. Digikam can use the IDs, if ItemDrag is dropped
+ * on digikam itself.
+ * The kioURLs are internally used with the digikamalbums tdeioslave.
+ * The "normal" KURLDrag urls are used for external drops (k3b, gimp, ...)
+ */
+class ItemDrag : public KURLDrag
+{
+public:
+
+ ItemDrag(const KURL::List& urls, const KURL::List& kioURLs,
+ const TQValueList<int>& albumIDs,
+ const TQValueList<int>& imageIDs,
+ TQWidget* dragSource=0, const char* name=0);
+
+ static bool canDecode(const TQMimeSource* e);
+ static bool decode(const TQMimeSource* e,
+ KURL::List &urls,
+ KURL::List &kioURLs,
+ TQValueList<int>& albumIDs,
+ TQValueList<int>& imageIDs);
+
+protected:
+
+ virtual const char* format(int i) const;
+ virtual TQByteArray encodedData(const char* mime) const;
+
+private:
+
+ KURL::List m_kioURLs;
+ TQValueList<int> m_albumIDs;
+ TQValueList<int> m_imageIDs;
+};
+
+
+/**
+ * Provides a drag object for a tag
+ *
+ * When a tag is moved through drag'n'drop an object of this class
+ * is created.
+ */
+class TagDrag : public TQDragObject
+{
+public:
+
+ TagDrag(int albumid, TQWidget *dragSource = 0, const char *name = 0);
+ static bool canDecode(const TQMimeSource* e);
+
+protected:
+
+ TQByteArray encodedData( const char* ) const;
+ const char* format(int i) const;
+
+private:
+ int mAlbumID;
+};
+
+/**
+ * Provides a drag object for an album
+ *
+ * When an album is moved through drag'n'drop an object of this class
+ * is created.
+ */
+class AlbumDrag : public KURLDrag
+{
+public:
+ AlbumDrag(const KURL &url, int albumid,
+ TQWidget *dragSource = 0, const char *name = 0);
+ static bool canDecode(const TQMimeSource* e);
+ static bool decode(const TQMimeSource* e, KURL::List &urls,
+ int &albumID);
+protected:
+
+ TQByteArray encodedData(const char*) const;
+ const char* format(int i) const;
+
+private:
+ int mAlbumID;
+};
+
+/**
+ * Provides a drag object for a list of tags
+ *
+ * When a tag is moved through drag'n'drop an object of this class
+ * is created.
+ */
+class TagListDrag : public TQDragObject
+{
+public:
+
+ TagListDrag(const TQValueList<int>& tagIDs, TQWidget *dragSource = 0,
+ const char *name = 0);
+ static bool canDecode(const TQMimeSource* e);
+
+protected:
+
+ TQByteArray encodedData( const char* ) const;
+ const char* format(int i) const;
+
+private:
+
+ TQValueList<int> m_tagIDs;
+};
+
+/**
+ * Provides a drag object for a list of camera items
+ *
+ * When a camera item is moved through drag'n'drop an object of this class
+ * is created.
+ */
+class CameraItemListDrag : public TQDragObject
+{
+public:
+
+ CameraItemListDrag(const TQStringList& cameraItemPaths, TQWidget *dragSource = 0,
+ const char *name = 0);
+ static bool canDecode(const TQMimeSource* e);
+
+protected:
+
+ TQByteArray encodedData( const char* ) const;
+ const char* format(int i) const;
+
+private:
+
+ TQStringList m_cameraItemPaths;
+};
+
+/**
+ * Provides a drag object for a camera object
+ *
+ * When a camera object is moved through drag'n'drop an object of this class
+ * is created.
+ */
+class CameraDragObject : public TQStoredDrag
+{
+
+public:
+
+ CameraDragObject(const CameraType& ctype, TQWidget* dragSource=0);
+ ~CameraDragObject();
+
+ static bool canDecode(const TQMimeSource* e);
+ static bool decode(const TQMimeSource* e, CameraType& ctype);
+
+private:
+
+ void setCameraType(const CameraType& ctype);
+
+};
+
+} // namespace Digikam
+
+#endif /* DRAGOBJECTS_H */
diff --git a/src/digikam/firstrun.cpp b/src/digikam/firstrun.cpp
new file mode 100644
index 00000000..43613034
--- /dev/null
+++ b/src/digikam/firstrun.cpp
@@ -0,0 +1,100 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-04-25
+ * Description : a widget to use in first run dialog
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvariant.h>
+#include <tqlabel.h>
+#include <tqframe.h>
+#include <kurlrequester.h>
+#include <tqlayout.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <kdialog.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "firstrun.h"
+#include "firstrun.moc"
+
+namespace Digikam
+{
+
+FirstRunWidget::FirstRunWidget( TQWidget* parent )
+ : TQWidget( parent )
+{
+ setName( "FirstRunWidget" );
+ TQVBoxLayout *vlayout = new TQVBoxLayout( this, 0, 6 );
+
+ m_textLabel2 = new TQLabel( this );
+ vlayout->addWidget( m_textLabel2 );
+
+ TQFrame *line1 = new TQFrame( this );
+ line1->setFrameShape( TQFrame::HLine );
+ line1->setFrameShadow( TQFrame::Sunken );
+ line1->setFrameShape( TQFrame::HLine );
+ vlayout->addWidget( line1 );
+
+ TQGridLayout *grid = new TQGridLayout( 0, 1, 1, 0, 6 );
+
+ m_pixLabel = new TQLabel( this );
+ m_pixLabel->setAlignment( int( TQLabel::AlignTop ) );
+ grid->addMultiCellWidget( m_pixLabel, 0, 1, 0, 0 );
+
+ m_path = new KURLRequester( this );
+ m_path->setShowLocalProtocol( true );
+
+ grid->addWidget( m_path, 1, 1 );
+
+ m_textLabel1 = new TQLabel( this );
+ m_textLabel1->setAlignment( int( TQLabel::WordBreak | TQLabel::AlignVCenter ) );
+ grid->addWidget( m_textLabel1, 0, 1 );
+
+ vlayout->addLayout( grid );
+ vlayout->addItem( new TQSpacerItem( 16, 16, TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding ) );
+
+ languageChange();
+ resize( TQSize(479, 149).expandedTo(minimumSizeHint()) );
+ clearWState( WState_Polished );
+}
+
+FirstRunWidget::~FirstRunWidget()
+{
+}
+
+void FirstRunWidget::languageChange()
+{
+ m_textLabel2->setText( i18n( "<b>Albums Library Folder</b>" ) );
+ m_pixLabel->setText( TQString() );
+ m_textLabel1->setText( i18n( "<p>digiKam will store the photo albums which you create in a "
+ "common <b>Albums Library Folder</b>. "
+ "Below, please select which folder you would like "
+ "digiKam to use as the common Albums Library Folder.</p>"
+ "<p><b>Do not use a mount path hosted by a remote computer.</b></p>") );
+}
+
+} // namespace Digikam
diff --git a/src/digikam/firstrun.h b/src/digikam/firstrun.h
new file mode 100644
index 00000000..e3803993
--- /dev/null
+++ b/src/digikam/firstrun.h
@@ -0,0 +1,68 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-04-25
+ * Description : a widget to use in first run dialog
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIGIKAMFIRSTFIRSTRUNWIDGET_H
+#define DIGIKAMFIRSTFIRSTRUNWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQLabel;
+
+class KURLRequester;
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT FirstRunWidget : public TQWidget
+{
+ TQ_OBJECT
+
+public:
+
+ FirstRunWidget( TQWidget* parent = 0 );
+ ~FirstRunWidget();
+
+public:
+
+ TQLabel *m_pixLabel;
+ KURLRequester *m_path;
+
+protected slots:
+
+ virtual void languageChange();
+
+private:
+
+ TQLabel *m_textLabel1;
+ TQLabel *m_textLabel2;
+};
+
+} // namespace Digikam
+
+#endif // DIGIKAMFIRSTFIRSTRUNWIDGET_H
diff --git a/src/digikam/folderitem.cpp b/src/digikam/folderitem.cpp
new file mode 100644
index 00000000..45b875f8
--- /dev/null
+++ b/src/digikam/folderitem.cpp
@@ -0,0 +1,269 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-31
+ * Description : implementation of item folder
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfont.h>
+#include <tqfontmetrics.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqstyle.h>
+
+// Local includes.
+
+#include "thumbnailsize.h"
+#include "albumsettings.h"
+#include "folderview.h"
+#include "folderitem.h"
+
+namespace Digikam
+{
+
+FolderItem::FolderItem(TQListView* parent, const TQString& text, bool special)
+ : TQListViewItem(parent, text)
+{
+ m_special = special;
+ m_focus = false;
+}
+
+FolderItem::FolderItem(TQListViewItem* parent, const TQString& text, bool special)
+ : TQListViewItem(parent, text)
+{
+ m_special = special;
+ m_focus = false;
+}
+
+FolderItem::~FolderItem()
+{
+}
+
+void FolderItem::setFocus(bool b)
+{
+ m_focus = b;
+}
+
+bool FolderItem::focus() const
+{
+ return m_focus;
+}
+
+void FolderItem::paintCell(TQPainter* p, const TQColorGroup& cg, int column, int width, int)
+{
+ FolderView *fv = dynamic_cast<FolderView*>(listView());
+ if (!fv)
+ return;
+
+ TQFontMetrics fm(p->fontMetrics());
+
+ TQString t = text(column);
+ int margin = fv->itemMargin();
+ int r = margin;
+ const TQPixmap* icon = pixmap(column);
+
+ if (isSelected())
+ {
+ p->drawPixmap(0, 0, fv->itemBasePixmapSelected());
+ p->setPen(cg.highlightedText());
+ }
+ else
+ {
+ p->drawPixmap(0, 0, fv->itemBasePixmapRegular());
+ p->setPen(cg.text());
+ }
+
+ if (icon)
+ {
+ int xo = r;
+ int yo = (height() - icon->height())/2;
+
+ p->drawPixmap( xo, yo, *icon );
+
+ r += icon->width() + 5 + fv->itemMargin();
+ }
+
+ if (m_special)
+ {
+ TQFont f(p->font());
+ f.setItalic(true);
+ p->setFont(f);
+
+ p->setPen(isSelected() ? cg.color(TQColorGroup::LinkVisited) :
+ cg.color(TQColorGroup::Link));
+ }
+
+ TQRect br;
+ p->drawText(r, 0, width-margin-r, height(), TQt::AlignLeft|TQt::AlignVCenter, t, -1, &br);
+
+ if (m_special)
+ {
+ p->drawLine(br.right() + 2, height()/2, fv->width(), height()/2);
+ }
+
+ if (m_focus)
+ {
+ p->setPen(cg.link());
+ TQRect r = fv->itemRect(this);
+ p->drawRect(0, 0, r.width(), r.height());
+ }
+}
+
+void FolderItem::setup()
+{
+ widthChanged();
+
+ FolderView *fv = dynamic_cast<FolderView*>(listView());
+ int h = fv->itemHeight();
+ if (h % 2 > 0)
+ h++;
+
+ setHeight(h);
+}
+
+int FolderItem::id() const
+{
+ return 0;
+}
+
+// ------------------------------------------------------------------------------------
+
+FolderCheckListItem::FolderCheckListItem(TQListView* parent, const TQString& text,
+ TQCheckListItem::Type tt)
+ : TQCheckListItem(parent, text, tt)
+{
+ m_focus = false;
+}
+
+FolderCheckListItem::FolderCheckListItem(TQListViewItem* parent, const TQString& text,
+ TQCheckListItem::Type tt)
+ : TQCheckListItem(parent, text, tt)
+{
+ m_focus = false;
+}
+
+FolderCheckListItem::~FolderCheckListItem()
+{
+}
+
+void FolderCheckListItem::setFocus(bool b)
+{
+ m_focus = b;
+}
+
+bool FolderCheckListItem::focus() const
+{
+ return m_focus;
+}
+
+void FolderCheckListItem::paintCell(TQPainter* p, const TQColorGroup& cg, int column, int width, int)
+{
+ FolderView *fv = dynamic_cast<FolderView*>(listView());
+ if (!fv)
+ return;
+
+ TQFontMetrics fm(p->fontMetrics());
+
+ TQString t = text(column);
+ int margin = fv->itemMargin();
+ int r = margin;
+ const TQPixmap* icon = pixmap(column);
+
+ int styleflags = TQStyle::Style_Default;
+ switch (state())
+ {
+ case(TQCheckListItem::Off):
+ styleflags |= TQStyle::Style_Off;
+ break;
+ case(TQCheckListItem::NoChange):
+ styleflags |= TQStyle::Style_NoChange;
+ break;
+ case(TQCheckListItem::On):
+ styleflags |= TQStyle::Style_On;
+ break;
+ }
+
+ if (isSelected())
+ styleflags |= TQStyle::Style_Selected;
+
+ if (isEnabled() && fv->isEnabled())
+ styleflags |= TQStyle::Style_Enabled;
+
+ if ((type() == TQCheckListItem::CheckBox) ||
+ (type() == TQCheckListItem::CheckBoxController))
+ {
+ int boxsize = fv->style().pixelMetric(TQStyle::PM_CheckListButtonSize, fv);
+ int x = 3;
+ int y = (height() - boxsize)/2 + margin;
+ r += boxsize + 4;
+
+ p->fillRect(0, 0, r, height(), cg.base());
+
+ fv->style().drawPrimitive(TQStyle::PE_CheckListIndicator, p,
+ TQRect(x, y, boxsize, height()),
+ cg, styleflags, TQStyleOption(this));
+ }
+
+ if (isSelected())
+ {
+ p->drawPixmap(r, 0, fv->itemBasePixmapSelected());
+ p->setPen(cg.highlightedText());
+ }
+ else
+ {
+ p->drawPixmap(r, 0, fv->itemBasePixmapRegular());
+ p->setPen(cg.text());
+ }
+
+ if (icon)
+ {
+ int xo = r;
+ int yo = (height() - icon->height())/2;
+
+ p->drawPixmap( xo, yo, *icon );
+
+ r += icon->width() + fv->itemMargin();
+ }
+
+ p->drawText(r, 0, width-margin-r, height(), TQt::AlignLeft|TQt::AlignVCenter, t);
+
+ if (m_focus)
+ {
+ p->setPen(cg.link());
+ TQRect r = fv->itemRect(this);
+ p->drawRect(0, 0, r.width(), r.height());
+ }
+}
+
+void FolderCheckListItem::setup()
+{
+ widthChanged();
+
+ FolderView *fv = dynamic_cast<FolderView*>(listView());
+ int h = fv->itemHeight();
+ if (h % 2 > 0)
+ h++;
+
+ setHeight(h);
+}
+
+} // namespace Digikam
diff --git a/src/digikam/folderitem.h b/src/digikam/folderitem.h
new file mode 100644
index 00000000..e99189a8
--- /dev/null
+++ b/src/digikam/folderitem.h
@@ -0,0 +1,91 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-31
+ * Description : implementation of item folder.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef FOLDERITEM_H
+#define FOLDERITEM_H
+
+// TQt includes.
+
+#include <tqlistview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT FolderItem : public TQListViewItem
+{
+public:
+
+ FolderItem(TQListView* parent, const TQString& text, bool special=false);
+ FolderItem(TQListViewItem* parent, const TQString& text, bool special=false);
+
+ virtual ~FolderItem();
+
+ virtual int id() const;
+
+ void setFocus(bool b);
+ bool focus() const;
+
+protected:
+
+ void paintCell(TQPainter* p, const TQColorGroup & cg, int column, int width, int align);
+ void setup();
+
+private:
+
+ bool m_focus;
+ bool m_special;
+};
+
+// ------------------------------------------------------------------------------------
+
+class FolderCheckListItem : public TQCheckListItem
+{
+public:
+
+ FolderCheckListItem(TQListView* parent, const TQString& text,
+ TQCheckListItem::Type tt);
+ FolderCheckListItem(TQListViewItem* parent, const TQString& text,
+ TQCheckListItem::Type tt);
+ virtual ~FolderCheckListItem();
+
+ void setFocus(bool b);
+ bool focus() const;
+
+protected:
+
+ void paintCell(TQPainter* p, const TQColorGroup & cg, int column, int width, int align);
+ void setup();
+
+private:
+
+ bool m_focus;
+};
+
+} // namespace Digikam
+
+#endif /* FOLDERITEM_H */
diff --git a/src/digikam/folderview.cpp b/src/digikam/folderview.cpp
new file mode 100644
index 00000000..0e2d515d
--- /dev/null
+++ b/src/digikam/folderview.cpp
@@ -0,0 +1,510 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-06
+ * Description : implementation of folder view.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqstyle.h>
+#include <tqvaluelist.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "albumthumbnailloader.h"
+#include "ddebug.h"
+#include "dragobjects.h"
+#include "folderitem.h"
+#include "themeengine.h"
+#include "folderview.h"
+#include "folderview.moc"
+
+namespace Digikam
+{
+
+class FolderViewPriv
+{
+public:
+
+ FolderViewPriv()
+ {
+ active = false;
+ dragItem = 0;
+ oldHighlightItem = 0;
+ }
+
+ bool active;
+
+ int itemHeight;
+
+ TQPixmap itemRegPix;
+ TQPixmap itemSelPix;
+
+ TQPoint dragStartPos;
+
+ TQListViewItem *dragItem;
+ TQListViewItem *oldHighlightItem;
+};
+
+//-----------------------------------------------------------------------------
+
+FolderView::FolderView(TQWidget *parent, const char *name)
+ : TQListView(parent, name)
+{
+
+ d = new FolderViewPriv;
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAllAlbumsLoaded()),
+ this, TQ_SLOT(slotAllAlbumsLoaded()));
+
+ connect(AlbumThumbnailLoader::instance(), TQ_SIGNAL(signalReloadThumbnails()),
+ this, TQ_SLOT(slotIconSizeChanged()));
+
+ setColumnAlignment(0, TQt::AlignLeft|TQt::AlignVCenter);
+ setShowSortIndicator(true);
+ fontChange(font());
+}
+
+FolderView::~FolderView()
+{
+ delete d;
+}
+
+void FolderView::setActive(bool val)
+{
+ d->active = val;
+
+ if (d->active)
+ slotSelectionChanged();
+}
+
+bool FolderView::active() const
+{
+ return d->active;
+}
+
+int FolderView::itemHeight() const
+{
+ return d->itemHeight;
+}
+
+TQRect FolderView::itemRect(TQListViewItem *item) const
+{
+ if(!item)
+ return TQRect();
+
+ TQRect r = TQListView::itemRect(item);
+ r.setLeft(r.left()+(item->depth()+(rootIsDecorated() ? 1 : 0))*treeStepSize());
+ return r;
+}
+
+TQPixmap FolderView::itemBasePixmapRegular() const
+{
+ return d->itemRegPix;
+}
+
+TQPixmap FolderView::itemBasePixmapSelected() const
+{
+ return d->itemSelPix;
+}
+
+void FolderView::resizeEvent(TQResizeEvent* e)
+{
+ TQListView::resizeEvent(e);
+
+ int w = frameRect().width();
+ int h = itemHeight();
+ if (d->itemRegPix.width() != w ||
+ d->itemRegPix.height() != h)
+ {
+ slotThemeChanged();
+ }
+}
+
+void FolderView::fontChange(const TQFont& oldFont)
+{
+ // this is bad, since the settings value might not always be the _real_ height of the thumbnail.
+ // (e.g. when it is blended, as for the tags)
+ d->itemHeight = TQMAX(AlbumThumbnailLoader::instance()->thumbnailSize() + 2*itemMargin(), fontMetrics().height());
+ TQListView::fontChange(oldFont);
+ slotThemeChanged();
+}
+
+void FolderView::slotIconSizeChanged()
+{
+ d->itemHeight = TQMAX(AlbumThumbnailLoader::instance()->thumbnailSize() + 2*itemMargin(), fontMetrics().height());
+ slotThemeChanged();
+}
+
+void FolderView::contentsMouseMoveEvent(TQMouseEvent *e)
+{
+ TQListView::contentsMouseMoveEvent(e);
+
+ if(e->state() == NoButton)
+ {
+ if(TDEGlobalSettings::changeCursorOverIcon())
+ {
+ TQPoint vp = contentsToViewport(e->pos());
+ TQListViewItem *item = itemAt(vp);
+ if (mouseInItemRect(item, vp.x()))
+ setCursor(KCursor::handCursor());
+ else
+ unsetCursor();
+ }
+ return;
+ }
+
+ if(d->dragItem &&
+ (d->dragStartPos - e->pos()).manhattanLength() > TQApplication::startDragDistance())
+ {
+ TQPoint vp = contentsToViewport(e->pos());
+ TQListViewItem *item = itemAt(vp);
+ if(!item)
+ {
+ d->dragItem = 0;
+ return;
+ }
+ }
+}
+
+void FolderView::contentsMousePressEvent(TQMouseEvent *e)
+{
+ TQPoint vp = contentsToViewport(e->pos());
+ TQListViewItem *item = itemAt(vp);
+
+ // With Check Box item, we will toggle on/off item using middle mouse button.
+ // See B.K.O #130906
+ FolderCheckListItem *citem = dynamic_cast<FolderCheckListItem*>(item);
+ if(citem && e->button() == MidButton && mouseInItemRect(item, e->pos().x()))
+ {
+ TQListView::contentsMousePressEvent(e);
+ citem->setOn(!citem->isOn());
+ return;
+ }
+
+ TQListView::contentsMousePressEvent(e);
+
+ if(item && e->button() == LeftButton)
+ {
+ // Prepare D&D if necessary
+ d->dragStartPos = e->pos();
+ d->dragItem = item;
+ return;
+ }
+}
+
+void FolderView::contentsMouseReleaseEvent(TQMouseEvent *e)
+{
+ TQPoint vp = contentsToViewport(e->pos());
+ TQListViewItem *item = itemAt(vp);
+
+ TQListView::contentsMouseReleaseEvent(e);
+
+ if(item && e->button() == LeftButton)
+ {
+ // See B.K.O #126871: collapse/expand treeview using left mouse button single click.
+ if (mouseInItemRect(item, e->pos().x()))
+ item->setOpen(!item->isOpen());
+ }
+
+ d->dragItem = 0;
+}
+
+void FolderView::startDrag()
+{
+ TQDragObject *o = dragObject();
+ if(o)
+ o->drag();
+}
+
+TQListViewItem* FolderView::dragItem() const
+{
+ return d->dragItem;
+}
+
+void FolderView::contentsDragEnterEvent(TQDragEnterEvent *e)
+{
+ TQListView::contentsDragEnterEvent(e);
+
+ e->accept(acceptDrop(e));
+}
+
+void FolderView::contentsDragLeaveEvent(TQDragLeaveEvent * e)
+{
+ TQListView::contentsDragLeaveEvent(e);
+
+ if(d->oldHighlightItem)
+ {
+ FolderItem *fitem = dynamic_cast<FolderItem*>(d->oldHighlightItem);
+ if (fitem)
+ fitem->setFocus(false);
+ else
+ {
+ FolderCheckListItem *citem = dynamic_cast<FolderCheckListItem*>(d->oldHighlightItem);
+ if (citem)
+ citem->setFocus(false);
+ }
+ d->oldHighlightItem->repaint();
+ d->oldHighlightItem = 0;
+ }
+}
+
+void FolderView::contentsDragMoveEvent(TQDragMoveEvent *e)
+{
+ TQListView::contentsDragMoveEvent(e);
+
+ TQPoint vp = contentsToViewport(e->pos());
+ TQListViewItem *item = itemAt(vp);
+ if(item)
+ {
+ if(d->oldHighlightItem)
+ {
+ FolderItem *fitem = dynamic_cast<FolderItem*>(d->oldHighlightItem);
+ if (fitem)
+ fitem->setFocus(false);
+ else
+ {
+ FolderCheckListItem *citem = dynamic_cast<FolderCheckListItem*>(d->oldHighlightItem);
+ if (citem)
+ citem->setFocus(false);
+ }
+ d->oldHighlightItem->repaint();
+ }
+
+ FolderItem *fitem = dynamic_cast<FolderItem*>(item);
+ if (fitem)
+ fitem->setFocus(true);
+ else
+ {
+ FolderCheckListItem *citem = dynamic_cast<FolderCheckListItem*>(item);
+ if (citem)
+ citem->setFocus(true);
+ }
+ d->oldHighlightItem = item;
+ item->repaint();
+ }
+ e->accept(acceptDrop(e));
+}
+
+void FolderView::contentsDropEvent(TQDropEvent *e)
+{
+ TQListView::contentsDropEvent(e);
+
+ if(d->oldHighlightItem)
+ {
+ FolderItem *fitem = dynamic_cast<FolderItem*>(d->oldHighlightItem);
+ if (fitem)
+ fitem->setFocus(false);
+ else
+ {
+ FolderCheckListItem *citem = dynamic_cast<FolderCheckListItem*>(d->oldHighlightItem);
+ if (citem)
+ citem->setFocus(false);
+ }
+ d->oldHighlightItem->repaint();
+ d->oldHighlightItem = 0;
+ }
+}
+
+bool FolderView::acceptDrop(const TQDropEvent *) const
+{
+ return false;
+}
+
+bool FolderView::mouseInItemRect(TQListViewItem* item, int x) const
+{
+ if (!item)
+ return false;
+
+ x += contentsX();
+
+ int offset = treeStepSize()*(item->depth() + (rootIsDecorated() ? 1 : 0));
+ offset += itemMargin();
+ int width = item->width(fontMetrics(), this, 0);
+
+ int boxsize = 0;
+ FolderCheckListItem* citem = dynamic_cast<FolderCheckListItem*>(item);
+ if (citem &&
+ ((citem->type() == TQCheckListItem::CheckBox) || (citem->type() == TQCheckListItem::CheckBoxController)))
+ boxsize = style().pixelMetric(TQStyle::PM_CheckListButtonSize, this);
+
+ return (x > (offset + boxsize) && x < (offset + boxsize + width));
+}
+
+void FolderView::slotThemeChanged()
+{
+ int w = frameRect().width();
+ int h = itemHeight();
+
+ d->itemRegPix = ThemeEngine::instance()->listRegPixmap(w, h);
+ d->itemSelPix = ThemeEngine::instance()->listSelPixmap(w, h);
+
+ viewport()->update();
+}
+
+void FolderView::slotAllAlbumsLoaded()
+{
+ disconnect(AlbumManager::instance(), TQ_SIGNAL(signalAllAlbumsLoaded()),
+ this, TQ_SLOT(slotAllAlbumsLoaded()));
+ loadViewState();
+}
+
+void FolderView::loadViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ int selectedItem = config->readNumEntry("LastSelectedItem", 0);
+
+ TQValueList<int> openFolders;
+ if(config->hasKey("OpenFolders"))
+ {
+ openFolders = config->readIntListEntry("OpenFolders");
+ }
+
+ FolderItem *item = 0;
+ FolderItem *foundItem = 0;
+ TQListViewItemIterator it(this->lastItem());
+
+ for( ; it.current(); --it)
+ {
+ item = dynamic_cast<FolderItem*>(it.current());
+ if(!item)
+ continue;
+
+ // Start the album root always open
+ if(openFolders.contains(item->id()) || item->id() == 0)
+ setOpen(item, true);
+ else
+ setOpen(item, false);
+
+ if(item->id() == selectedItem)
+ {
+ // Save the found selected item so that it can be made visible.
+ foundItem = item;
+ }
+ }
+
+ // Important note: this cannot be done inside the previous loop
+ // because opening folders prevents the visibility.
+ // Fixes bug #144815.
+ // (Looks a bit like a bug in TQt to me ...)
+ if (foundItem)
+ {
+ setSelected(foundItem, true);
+ ensureItemVisible(foundItem);
+ }
+}
+
+void FolderView::saveViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ FolderItem *item = dynamic_cast<FolderItem*>(selectedItem());
+ if(item)
+ config->writeEntry("LastSelectedItem", item->id());
+ else
+ config->writeEntry("LastSelectedItem", 0);
+
+ TQValueList<int> openFolders;
+ TQListViewItemIterator it(this);
+ for( ; it.current(); ++it)
+ {
+ item = dynamic_cast<FolderItem*>(it.current());
+ if(item && isOpen(item))
+ openFolders.push_back(item->id());
+ }
+ config->writeEntry("OpenFolders", openFolders);
+}
+
+void FolderView::slotSelectionChanged()
+{
+ TQListView::selectionChanged();
+}
+
+void FolderView::selectItem(int)
+{
+}
+
+void FolderView::collapseView(CollapseMode mode)
+{
+ // collapse the whole list first
+ TQListViewItemIterator iter(this);
+ while (iter.current())
+ {
+ iter.current()->setOpen(false);
+ iter.current()->setVisible(true);
+ iter++;
+ }
+
+ // handle special cases
+ switch (mode)
+ {
+ case OmitRoot:
+ {
+ firstChild()->setOpen(true);
+ break;
+ }
+ case RestoreCurrentAlbum:
+ {
+ TQListViewItemIterator iter(this);
+ FolderItem* restoredItem = 0;
+
+ while (iter.current())
+ {
+ FolderItem* curItem = dynamic_cast<FolderItem*>(iter.current());
+
+ if (curItem)
+ {
+ if (curItem->id() == AlbumManager::instance()->currentAlbum()->id())
+ {
+ curItem->setOpen(true);
+ restoredItem = curItem;
+ break;
+ }
+ }
+ iter++;
+ }
+ if (restoredItem)
+ ensureItemVisible(restoredItem);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+} // namespace Digikam
diff --git a/src/digikam/folderview.h b/src/digikam/folderview.h
new file mode 100644
index 00000000..e32737ce
--- /dev/null
+++ b/src/digikam/folderview.h
@@ -0,0 +1,136 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-28
+ * Description : implementation of folder view.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file foldeview.h */
+
+#ifndef _FOLDERVIEW_H_
+#define _FOLDERVIEW_H_
+
+// TQt includes.
+
+#include <tqlistview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "album.h"
+
+namespace Digikam
+{
+
+class FolderViewPriv;
+class FolderItem;
+
+/**
+ * \class FolderView
+ * \brief Base class for a tree view
+ */
+
+
+class DIGIKAM_EXPORT FolderView : public TQListView
+{
+ TQ_OBJECT
+
+public:
+
+ enum CollapseMode
+ {
+ /*
+ * Collapse the folder view and re-open the current viewed album (default mode)
+ * In this mode, all root items are collapsed, then the one containing
+ * the currently selected album is expand again.
+ * This mode will make sure that the selected album is visible in the folder tree.
+ */
+ RestoreCurrentAlbum,
+ /*
+ * Collapse the folder view but omit the root item.
+ * In this mode all items in the folder view are collapsed,
+ * and the first root item is expanded again (My Tags / My Albums etc)
+ */
+ OmitRoot
+ };
+
+ FolderView(TQWidget *parent, const char *name = "FolderView");
+ virtual ~FolderView();
+
+ void setActive(bool val);
+ bool active() const;
+
+ int itemHeight() const;
+ TQRect itemRect(TQListViewItem *item) const;
+ TQPixmap itemBasePixmapRegular() const;
+ TQPixmap itemBasePixmapSelected() const;
+
+ virtual void collapseView(CollapseMode mode = RestoreCurrentAlbum);
+
+protected:
+
+ void contentsMousePressEvent(TQMouseEvent *e);
+ void contentsMouseReleaseEvent(TQMouseEvent *e);
+ void contentsMouseMoveEvent(TQMouseEvent *e);
+ void contentsDragEnterEvent(TQDragEnterEvent *e);
+ void contentsDragMoveEvent(TQDragMoveEvent *e);
+ void contentsDragLeaveEvent(TQDragLeaveEvent * e);
+ void contentsDropEvent(TQDropEvent *e);
+
+ virtual bool acceptDrop(const TQDropEvent *e) const;
+
+ void startDrag();
+ TQListViewItem* dragItem() const;
+
+ void resizeEvent(TQResizeEvent* e);
+ void fontChange(const TQFont& oldFont);
+
+ virtual void selectItem(int id);
+
+ /**
+ * load the last state from the view from disk
+ */
+ virtual void loadViewState();
+
+ /**
+ * writes the views state to disk
+ */
+ virtual void saveViewState();
+
+protected slots:
+
+ virtual void slotSelectionChanged();
+ virtual void slotAllAlbumsLoaded();
+
+private slots:
+
+ void slotThemeChanged();
+ void slotIconSizeChanged();
+
+private:
+
+ bool mouseInItemRect(TQListViewItem* item, int x) const;
+
+ FolderViewPriv *d;
+};
+
+} // namespace Digikam
+
+#endif // _FOLDERVIEW_H
diff --git a/src/digikam/icongroupitem.cpp b/src/digikam/icongroupitem.cpp
new file mode 100644
index 00000000..e16588fe
--- /dev/null
+++ b/src/digikam/icongroupitem.cpp
@@ -0,0 +1,307 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-24
+ * Description : icons group item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqpalette.h>
+
+// Local includes.
+
+#include "iconview.h"
+#include "iconitem.h"
+#include "icongroupitem.h"
+
+namespace Digikam
+{
+
+class IconGroupItemPriv
+{
+public:
+
+ IconGroupItemPriv()
+ {
+ view = 0;
+ firstItem = 0;
+ lastItem = 0;
+ y = 0;
+ count = 0;
+ clearing = false;
+ }
+
+ IconView* view;
+
+ IconItem* firstItem;
+ IconItem* lastItem;
+
+ int y;
+ int count;
+ bool clearing;
+
+ struct SortableItem
+ {
+ IconItem *item;
+ };
+};
+
+IconGroupItem::IconGroupItem(IconView* parent)
+{
+ d = new IconGroupItemPriv;
+ d->view = parent;
+ m_next = 0;
+ m_prev = 0;
+
+ parent->insertGroup(this);
+}
+
+IconGroupItem::~IconGroupItem()
+{
+ clear(false);
+ d->view->takeGroup(this);
+ delete d;
+}
+
+IconView* IconGroupItem::iconView() const
+{
+ return d->view;
+}
+
+IconGroupItem* IconGroupItem::nextGroup() const
+{
+ return m_next;
+}
+
+IconGroupItem* IconGroupItem::prevGroup() const
+{
+ return m_prev;
+}
+
+TQRect IconGroupItem::rect() const
+{
+ TQRect r = d->view->bannerRect();
+ r.moveBy(0, d->y);
+ return r;
+}
+
+int IconGroupItem::y() const
+{
+ return d->y;
+}
+
+IconItem* IconGroupItem::firstItem() const
+{
+ return d->firstItem;
+}
+
+IconItem* IconGroupItem::lastItem() const
+{
+ return d->lastItem;
+}
+
+void IconGroupItem::insertItem(IconItem* item)
+{
+ if (!item)
+ return;
+
+ if (!d->firstItem)
+ {
+ d->firstItem = item;
+ d->lastItem = item;
+ item->m_prev = 0;
+ item->m_next = 0;
+ }
+ else
+ {
+ d->lastItem->m_next = item;
+ item->m_prev = d->lastItem;
+ item->m_next = 0;
+ d->lastItem = item;
+ }
+
+ d->count++;
+ d->view->insertItem(item);
+}
+
+void IconGroupItem::takeItem(IconItem* item)
+{
+ if (!item)
+ return;
+
+ // take item triggers update
+ d->view->takeItem(item);
+ d->count--;
+
+ if (item == d->firstItem)
+ {
+ d->firstItem = d->firstItem->m_next;
+ if (d->firstItem)
+ d->firstItem->m_prev = 0;
+ else
+ d->firstItem = d->lastItem = 0;
+ }
+ else if (item == d->lastItem)
+ {
+ d->lastItem = d->lastItem->m_prev;
+ if ( d->lastItem )
+ d->lastItem->m_next = 0;
+ else
+ d->firstItem = d->lastItem = 0;
+ }
+ else
+ {
+ IconItem *i = item;
+ if (i)
+ {
+ if (i->m_prev )
+ i->m_prev->m_next = i->m_next;
+ if ( i->m_next )
+ i->m_next->m_prev = i->m_prev;
+ }
+ }
+}
+
+int IconGroupItem::count() const
+{
+ return d->count;
+}
+
+int IconGroupItem::index(IconItem* item) const
+{
+ if ( !item )
+ return -1;
+
+ if ( item == d->firstItem )
+ return 0;
+ else if ( item == d->lastItem )
+ return d->count - 1;
+ else
+ {
+ IconItem *i = d->firstItem;
+ int j = 0;
+ while ( i && i != item )
+ {
+ i = i->m_next;
+ ++j;
+ }
+
+ return i ? j : -1;
+ }
+}
+
+void IconGroupItem::clear(bool update)
+{
+ d->clearing = true;
+
+ IconItem *item = d->firstItem;
+ while (item)
+ {
+ IconItem *tmp = item->m_next;
+ delete item;
+ item = tmp;
+ }
+
+ d->firstItem = 0;
+ d->lastItem = 0;
+ d->count = 0;
+
+ if (update)
+ d->view->triggerRearrangement();
+
+ d->clearing = false;
+}
+
+void IconGroupItem::sort()
+{
+ IconGroupItemPriv::SortableItem *items
+ = new IconGroupItemPriv::SortableItem[ count() ];
+
+ IconItem *item = d->firstItem;
+ int i = 0;
+ for ( ; item; item = item->m_next )
+ items[ i++ ].item = item;
+
+ qsort( items, count(), sizeof( IconGroupItemPriv::SortableItem ), cmpItems );
+
+ IconItem *prev = 0;
+ item = 0;
+ for ( i = 0; i < (int)count(); ++i ) {
+ item = items[ i ].item;
+ if ( item ) {
+ item->m_prev = prev;
+ if ( item->m_prev )
+ item->m_prev->m_next = item;
+ item->m_next = 0;
+ }
+ if ( i == 0 )
+ d->firstItem = item;
+ if ( i == (int)count() - 1 )
+ d->lastItem = item;
+ prev = item;
+ }
+
+ delete [] items;
+}
+
+bool IconGroupItem::move(int y)
+{
+ if (d->y == y)
+ return false;
+
+ d->y = y;
+ return true;
+}
+
+void IconGroupItem::paintBanner()
+{
+ TQRect r(rect());
+ TQPixmap pix(r.width(), r.height());
+ pix.fill(d->view->colorGroup().base());
+
+ r = TQRect(d->view->contentsToViewport(TQPoint(r.x(), r.y())),
+ TQSize(r.width(), r.height()));
+
+ bitBlt(d->view->viewport(), r.x(), r.y(), &pix,
+ 0, 0, r.width(), r.height());
+}
+
+int IconGroupItem::compare(IconGroupItem*)
+{
+ return 0;
+}
+
+int IconGroupItem::cmpItems(const void *n1, const void *n2)
+{
+ if ( !n1 || !n2 )
+ return 0;
+
+ IconGroupItemPriv::SortableItem *i1 = (IconGroupItemPriv::SortableItem *)n1;
+ IconGroupItemPriv::SortableItem *i2 = (IconGroupItemPriv::SortableItem *)n2;
+
+ return i1->item->compare( i2->item );
+}
+
+} // namespace Digikam
diff --git a/src/digikam/icongroupitem.h b/src/digikam/icongroupitem.h
new file mode 100644
index 00000000..8a960a1f
--- /dev/null
+++ b/src/digikam/icongroupitem.h
@@ -0,0 +1,91 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-24
+ * Description : icons group item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICONGROUPITEM_H
+#define ICONGROUPITEM_H
+
+// TQt includes.
+
+#include <tqrect.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class IconView;
+class IconItem;
+class IconGroupItemPriv;
+
+class DIGIKAM_EXPORT IconGroupItem
+{
+ friend class IconView;
+
+public:
+
+ IconGroupItem(IconView* parent);
+ virtual ~IconGroupItem();
+
+ IconView* iconView() const;
+
+ IconGroupItem* nextGroup() const;
+ IconGroupItem* prevGroup() const;
+
+ TQRect rect() const;
+ int y() const;
+ bool move(int y);
+
+ IconItem* firstItem() const;
+ IconItem* lastItem() const;
+
+ int count() const;
+ int index(IconItem* item) const;
+
+ void clear(bool update=true);
+ void sort();
+
+ void insertItem(IconItem* item);
+ void takeItem(IconItem* item);
+
+ virtual int compare(IconGroupItem *group);
+
+protected:
+
+ virtual void paintBanner();
+
+private:
+
+ static int cmpItems(const void *n1, const void *n2);
+
+private:
+
+ IconGroupItemPriv *d;
+ IconGroupItem *m_next;
+ IconGroupItem *m_prev;
+};
+
+} // namespace Digikam
+
+#endif /* ICONGROUPITEM_H */
diff --git a/src/digikam/iconitem.cpp b/src/digikam/iconitem.cpp
new file mode 100644
index 00000000..798b96ae
--- /dev/null
+++ b/src/digikam/iconitem.cpp
@@ -0,0 +1,171 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-24
+ * Description : icon item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqpainter.h>
+
+// Local includes.
+
+#include "icongroupitem.h"
+#include "iconview.h"
+#include "iconitem.h"
+
+namespace Digikam
+{
+
+IconItem::IconItem(IconGroupItem* parent)
+ : m_group(parent)
+{
+ m_next = 0;
+ m_prev = 0;
+ m_x = 0;
+ m_y = 0;
+ m_selected = false;
+
+ m_group->insertItem(this);
+}
+
+IconItem::~IconItem()
+{
+ m_group->takeItem(this);
+}
+
+IconItem* IconItem::nextItem() const
+{
+ if (m_next)
+ return m_next;
+
+ if (m_group->nextGroup())
+ return m_group->nextGroup()->firstItem();
+
+ return 0;
+}
+
+IconItem* IconItem::prevItem() const
+{
+ if (m_prev)
+ return m_prev;
+
+ if (m_group->prevGroup())
+ return m_group->prevGroup()->lastItem();
+
+ return 0;
+}
+
+int IconItem::x() const
+{
+ return m_x;
+}
+
+int IconItem::y() const
+{
+ return m_y;
+}
+
+TQRect IconItem::rect() const
+{
+ IconView* view = m_group->iconView();
+ TQRect r(view->itemRect());
+ r.moveTopLeft(TQPoint(m_x, m_y));
+ return r;
+}
+
+TQRect IconItem::clickToOpenRect()
+{
+ return rect();
+}
+
+bool IconItem::move(int x, int y)
+{
+ if (m_x == x && m_y == y)
+ return false;
+
+ m_x = x; m_y = y;
+ return true;
+}
+
+void IconItem::setSelected(bool val, bool cb)
+{
+ IconView* view = m_group->iconView();
+
+ if (cb)
+ {
+ view->blockSignals(true);
+ view->clearSelection();
+ view->blockSignals(false);
+ }
+
+ m_selected = val;
+ view->selectItem(this, val);
+ view->updateContents(rect());
+}
+
+bool IconItem::isSelected() const
+{
+ return m_selected;
+}
+
+void IconItem::repaint(bool force)
+{
+ if (force)
+ m_group->iconView()->repaintContents(rect());
+ else
+ m_group->iconView()->updateContents(rect());
+}
+
+IconView* IconItem::iconView() const
+{
+ return m_group->iconView();
+}
+
+int IconItem::compare(IconItem* /*item*/)
+{
+ return 0;
+}
+
+void IconItem::paintItem()
+{
+ IconView* view = m_group->iconView();
+
+ TQRect r(rect());
+ TQPixmap pix(r.width(), r.height());
+ pix.fill(m_selected ? TQt::blue : TQt::gray);
+
+ if (this == iconView()->currentItem())
+ {
+ TQPainter p(&pix);
+ p.setPen(TQPen(m_selected ? TQt::white : TQt::black, 1, TQt::DotLine));
+ p.drawRect(2, 2, r.width()-4, r.width()-4);
+ p.end();
+ }
+
+ r = TQRect(view->contentsToViewport(TQPoint(r.x(), r.y())),
+ TQSize(r.width(), r.height()));
+
+ bitBlt(view->viewport(), r.x(), r.y(), &pix,
+ 0, 0, r.width(), r.height());
+}
+
+} // namespace Digikam
diff --git a/src/digikam/iconitem.h b/src/digikam/iconitem.h
new file mode 100644
index 00000000..aa67e209
--- /dev/null
+++ b/src/digikam/iconitem.h
@@ -0,0 +1,87 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-24
+ * Description : icon item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICONITEM_H
+#define ICONITEM_H
+
+// TQt includes.
+
+#include <tqrect.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class IconGroupItem;
+class IconView;
+
+class DIGIKAM_EXPORT IconItem
+{
+ friend class IconView;
+ friend class IconGroupItem;
+
+public:
+
+ IconItem(IconGroupItem* parent);
+ virtual ~IconItem();
+
+ IconItem* nextItem() const;
+ IconItem* prevItem() const;
+
+ int x() const;
+ int y() const;
+ TQRect rect() const;
+
+ bool move(int x, int y);
+
+ void setSelected(bool val, bool cb=true);
+ bool isSelected() const;
+
+ void repaint(bool force=true);
+
+ IconView* iconView() const;
+
+ virtual int compare(IconItem *item);
+ virtual TQRect clickToOpenRect();
+
+protected:
+
+ virtual void paintItem();
+
+private:
+
+ IconGroupItem *m_group;
+ IconItem *m_next;
+ IconItem *m_prev;
+ int m_x;
+ int m_y;
+ bool m_selected;
+};
+
+} // namespace Digikam
+
+#endif /* ICONITEM_H */
diff --git a/src/digikam/iconview.cpp b/src/digikam/iconview.cpp
new file mode 100644
index 00000000..2dac3da0
--- /dev/null
+++ b/src/digikam/iconview.cpp
@@ -0,0 +1,1969 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-24
+ * Description : icons view.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define RECT_EXTENSION 300
+
+// C++ includes.
+
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqtimer.h>
+#include <tqpainter.h>
+#include <tqvaluelist.h>
+#include <tqptrdict.h>
+#include <tqstyle.h>
+#include <tqapplication.h>
+#include <tqdrawutil.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "iconitem.h"
+#include "icongroupitem.h"
+#include "iconview.h"
+#include "iconview.moc"
+
+namespace Digikam
+{
+
+class IconViewPriv
+{
+public:
+
+ IconViewPriv()
+ {
+ firstGroup = 0;
+ lastGroup = 0;
+ currItem = 0;
+ anchorItem = 0;
+ clearing = false;
+ spacing = 10;
+
+ rubber = 0;
+ dragging = false;
+ pressedMoved = false;
+
+ firstContainer = 0;
+ lastContainer = 0;
+
+ showTips = false;
+ toolTipItem = 0;
+ toolTipTimer = 0;
+ rearrangeTimer = 0;
+ rearrangeTimerInterval = 0;
+ storedVisibleItem = 0;
+ needEmitSelectionChanged = false;
+ }
+
+ bool clearing;
+ bool showTips;
+ bool pressedMoved;
+ bool dragging;
+ bool needEmitSelectionChanged; // store for slotRearrange
+
+ int spacing;
+
+ TQPtrDict<IconItem> selectedItems;
+ TQPtrDict<IconItem> prevSelectedItems;
+
+ TQRect* rubber;
+
+ TQPoint dragStartPos;
+
+ TQTimer* rearrangeTimer;
+ TQTimer* toolTipTimer;
+
+ IconItem* toolTipItem;
+ IconItem* currItem;
+ IconItem* anchorItem;
+ IconItem* storedVisibleItem; // store position for slotRearrange
+
+ IconGroupItem* firstGroup;
+ IconGroupItem* lastGroup;
+
+ int rearrangeTimerInterval;
+
+ struct ItemContainer
+ {
+ ItemContainer(ItemContainer *p, ItemContainer *n, const TQRect &r)
+ : prev(p), next(n), rect(r)
+ {
+ if (prev)
+ prev->next = this;
+ if (next)
+ next->prev = this;
+ }
+
+ ItemContainer *prev, *next;
+ TQRect rect;
+ TQValueList<IconItem*> items;
+ } *firstContainer, *lastContainer;
+
+ struct SortableItem
+ {
+ IconGroupItem *group;
+ };
+};
+
+IconView::IconView(TQWidget* parent, const char* name)
+ : TQScrollView(parent, name, TQt::WStaticContents|TQt::WNoAutoErase)
+{
+ viewport()->setBackgroundMode(TQt::NoBackground);
+ viewport()->setFocusProxy(this);
+ viewport()->setFocusPolicy(TQWidget::WheelFocus);
+ viewport()->setMouseTracking(true);
+
+ d = new IconViewPriv;
+ d->rearrangeTimer = new TQTimer(this);
+ d->toolTipTimer = new TQTimer(this);
+
+ connect(d->rearrangeTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotRearrange()));
+
+ connect(d->toolTipTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotToolTip()));
+
+ setEnableToolTips(true);
+}
+
+IconView::~IconView()
+{
+ clear(false);
+
+ delete d->rearrangeTimer;
+ delete d->toolTipTimer;
+ delete d->rubber;
+ delete d;
+}
+
+IconGroupItem* IconView::firstGroup() const
+{
+ return d->firstGroup;
+}
+
+IconGroupItem* IconView::lastGroup() const
+{
+ return d->lastGroup;
+}
+
+IconItem* IconView::firstItem() const
+{
+ if (!d->firstGroup)
+ return 0;
+
+ return d->firstGroup->firstItem();
+}
+
+IconItem* IconView::lastItem() const
+{
+ if (!d->lastGroup)
+ return 0;
+
+ return d->lastGroup->lastItem();
+}
+
+IconItem* IconView::currentItem() const
+{
+ return d->currItem;
+}
+
+void IconView::setCurrentItem(IconItem* item)
+{
+ d->currItem = item;
+ d->anchorItem = d->currItem;
+
+ if (d->currItem)
+ {
+ d->currItem->setSelected(true, true);
+ ensureItemVisible(d->currItem);
+ }
+}
+
+IconItem* IconView::findItem(const TQPoint& pos)
+{
+ IconViewPriv::ItemContainer *c = d->firstContainer;
+ for (; c; c = c->next)
+ {
+ if ( c->rect.contains(pos) )
+ {
+ for (TQValueList<IconItem*>::iterator it = c->items.begin();
+ it != c->items.end(); ++it)
+ {
+ IconItem* item = *it;
+ if (item->rect().contains(pos))
+ return item;
+ }
+ }
+ }
+
+ return 0;
+}
+
+IconGroupItem* IconView::findGroup(const TQPoint& pos)
+{
+ TQPoint p = viewportToContents(viewport()->mapFromGlobal(pos));
+ for (IconGroupItem* group = d->firstGroup; group; group = group->nextGroup())
+ {
+ TQRect rect = group->rect();
+ int bottom;
+ if (group == d->lastGroup)
+ bottom = contentsHeight();
+ else
+ bottom = group->nextGroup()->rect().top();
+
+ rect.setBottom(bottom);
+
+ if ( rect.contains(p) )
+ {
+ return group;
+ }
+ }
+
+ return 0;
+}
+
+int IconView::count() const
+{
+ int c = 0;
+ for (IconGroupItem* group = d->firstGroup; group; group = group->nextGroup())
+ {
+ c += group->count();
+ }
+
+ return c;
+}
+
+
+int IconView::countSelected() const
+{
+ int c = 0;
+ for (IconGroupItem* group = d->firstGroup; group; group = group->nextGroup())
+ {
+ for (IconItem *it = group->firstItem(); it; it = it->nextItem())
+ if (it->isSelected())
+ c++;
+ }
+
+ return c;
+}
+
+int IconView::groupCount() const
+{
+ int c = 0;
+ for (IconGroupItem* group = d->firstGroup; group; group = group->nextGroup())
+ {
+ c++;
+ }
+
+ return c;
+}
+
+void IconView::clear(bool update)
+{
+ d->clearing = true;
+
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+
+ deleteContainers();
+
+ d->selectedItems.clear();
+
+ IconGroupItem *group = d->firstGroup;
+ while (group)
+ {
+ IconGroupItem *tmp = group->m_next;
+ delete group;
+ group = tmp;
+ }
+
+ d->firstGroup = 0;
+ d->lastGroup = 0;
+ d->currItem = 0;
+ d->anchorItem = 0;
+
+ viewport()->setUpdatesEnabled(false);
+ resizeContents(0, 0);
+ setContentsPos(0, 0);
+ viewport()->setUpdatesEnabled(true);
+
+ if (update)
+ updateContents();
+
+ d->clearing = false;
+
+ emit signalSelectionChanged();
+}
+
+void IconView::clearSelection()
+{
+ bool wasBlocked = signalsBlocked();;
+
+ if (!wasBlocked)
+ blockSignals(true);
+
+ TQPtrDict<IconItem> selItems = d->selectedItems;
+ TQPtrDictIterator<IconItem> it( selItems );
+ for ( ; it.current(); ++it )
+ it.current()->setSelected(false, false);
+
+ d->selectedItems.clear();
+
+ if (!wasBlocked)
+ blockSignals(false);
+
+ emit signalSelectionChanged();
+}
+
+void IconView::selectAll()
+{
+ bool wasBlocked = signalsBlocked();
+
+ if (!wasBlocked)
+ blockSignals(true);
+
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ if (!item->isSelected())
+ {
+ item->setSelected(true, false);
+ }
+ }
+
+ if (!wasBlocked)
+ blockSignals(false);
+
+ emit signalSelectionChanged();
+}
+
+void IconView::invertSelection()
+{
+ bool wasBlocked = signalsBlocked();
+
+ if (!wasBlocked)
+ blockSignals(true);
+
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ if (!item->isSelected())
+ {
+ item->setSelected(true, false);
+ }
+ else
+ {
+ item->setSelected(false, false);
+ }
+ }
+
+ if (!wasBlocked)
+ blockSignals(false);
+
+ emit signalSelectionChanged();
+}
+
+void IconView::selectItem(IconItem* item, bool select)
+{
+ if (!item)
+ return;
+
+ if (select)
+ {
+ d->selectedItems.replace(item, item);
+ }
+ else
+ {
+ d->selectedItems.remove(item);
+ }
+
+ emit signalSelectionChanged();
+}
+
+void IconView::setStoredVisibleItem(IconItem *item)
+{
+ d->storedVisibleItem = item;
+}
+
+void IconView::insertGroup(IconGroupItem* group)
+{
+ if (!group)
+ return;
+
+ if (!d->firstGroup)
+ {
+ d->firstGroup = group;
+ d->lastGroup = group;
+ group->m_prev = 0;
+ group->m_next = 0;
+ }
+ else
+ {
+ d->lastGroup->m_next = group;
+ group->m_prev = d->lastGroup;
+ group->m_next = 0;
+ d->lastGroup = group;
+ }
+
+ d->storedVisibleItem = findFirstVisibleItem();
+ startRearrangeTimer();
+}
+
+void IconView::takeGroup(IconGroupItem* group)
+{
+ if (!group)
+ return;
+
+ // this is only to find an alternative visible item if all visible items
+ // are removed
+ IconGroupItem *alternativeVisibleGroup = 0;
+ d->storedVisibleItem = 0;
+
+ if (group == d->firstGroup)
+ {
+ d->firstGroup = d->firstGroup->m_next;
+ if (d->firstGroup)
+ d->firstGroup->m_prev = 0;
+ else
+ d->firstGroup = d->lastGroup = 0;
+ alternativeVisibleGroup = d->firstGroup;
+ }
+ else if (group == d->lastGroup)
+ {
+ d->lastGroup = d->lastGroup->m_prev;
+ if ( d->lastGroup )
+ d->lastGroup->m_next = 0;
+ else
+ d->firstGroup = d->lastGroup = 0;
+ alternativeVisibleGroup = d->lastGroup->m_prev;
+ }
+ else
+ {
+ IconGroupItem *i = group;
+ if (i)
+ {
+ if (i->m_prev )
+ i->m_prev->m_next = i->m_next;
+ if ( i->m_next )
+ i->m_next->m_prev = i->m_prev;
+
+ if (i->m_prev)
+ alternativeVisibleGroup = i->m_prev;
+ else
+ alternativeVisibleGroup = i->m_next;
+ }
+ }
+
+ if (!d->clearing)
+ {
+ d->storedVisibleItem = findFirstVisibleItem();
+ if (!d->storedVisibleItem && alternativeVisibleGroup)
+ {
+ // find an alternative visible item
+ d->storedVisibleItem = alternativeVisibleGroup->lastItem();
+ }
+ startRearrangeTimer();
+ }
+}
+
+void IconView::insertItem(IconItem* item)
+{
+ if (!item)
+ return;
+
+ d->storedVisibleItem = findFirstVisibleItem();
+ startRearrangeTimer();
+}
+
+void IconView::takeItem(IconItem* item)
+{
+ if (!item)
+ return;
+
+ // First remove item from any containers holding it
+ IconViewPriv::ItemContainer *tmp = d->firstContainer;
+ while (tmp)
+ {
+ tmp->items.remove(item);
+ tmp = tmp->next;
+ }
+
+ // Remove from selected item list
+ d->selectedItems.remove(item);
+ // See bug 161084
+ if (d->selectedItems.count() || item->isSelected())
+ d->needEmitSelectionChanged = true;
+
+ if (d->toolTipItem == item)
+ {
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+ }
+
+ // if it is current item, change the current item
+ if (d->currItem == item)
+ {
+ d->currItem = item->nextItem();
+ if (!d->currItem)
+ d->currItem = item->prevItem();
+ // defer calling d->currItem->setSelected (and emitting the signals) to slotRearrange
+ }
+
+ d->anchorItem = d->currItem;
+
+ if (!d->clearing)
+ {
+ d->storedVisibleItem = findFirstVisibleItem();
+ if (d->storedVisibleItem == item)
+ d->storedVisibleItem = d->currItem;
+ startRearrangeTimer();
+ }
+}
+
+void IconView::triggerRearrangement()
+{
+ d->storedVisibleItem = findFirstVisibleItem();
+ startRearrangeTimer();
+}
+
+void IconView::setDelayedRearrangement(bool delayed)
+{
+ // if it is known that e.g. several items will be added or deleted in the next time,
+ // but not from the same event queue thread stack location, it may be desirable to delay
+ // the rearrangeTimer a bit
+ if (delayed)
+ d->rearrangeTimerInterval = 50;
+ else
+ d->rearrangeTimerInterval = 0;
+}
+
+void IconView::startRearrangeTimer()
+{
+ // We want to reduce the number of updates, but not remove all updates
+ if (!d->rearrangeTimer->isActive())
+ d->rearrangeTimer->start(d->rearrangeTimerInterval, true);
+}
+
+void IconView::sort()
+{
+ // first sort the groups
+ for (IconGroupItem* group = d->firstGroup; group;
+ group = group->nextGroup())
+ {
+ group->sort();
+ }
+
+ int gcount = groupCount();
+
+ // then sort the groups themselves
+ IconViewPriv::SortableItem *groups = new IconViewPriv::SortableItem[ gcount ];
+
+ IconGroupItem *group = d->firstGroup;
+ int i = 0;
+
+ for ( ; group; group = group->m_next )
+ groups[ i++ ].group = group;
+
+ qsort( groups, gcount, sizeof( IconViewPriv::SortableItem ), cmpItems );
+
+ IconGroupItem *prev = 0;
+ group = 0;
+
+ for ( i = 0; i < (int)gcount; ++i )
+ {
+ group = groups[ i ].group;
+ if ( group )
+ {
+ group->m_prev = prev;
+
+ if ( group->m_prev )
+ group->m_prev->m_next = group;
+
+ group->m_next = 0;
+ }
+
+ if ( i == 0 )
+ d->firstGroup = group;
+
+ if ( i == (int)gcount - 1 )
+ d->lastGroup = group;
+ prev = group;
+ }
+
+ delete [] groups;
+}
+
+void IconView::slotRearrange()
+{
+ sort();
+ arrangeItems();
+
+ // ensure there is a current item
+ if (!d->currItem)
+ {
+ // set the currItem to first item
+ if (d->firstGroup)
+ d->currItem = d->firstGroup->firstItem();
+ }
+ d->anchorItem = d->currItem;
+
+ // ensure there is a selection
+ if (d->selectedItems.isEmpty() && d->currItem)
+ {
+ d->currItem->setSelected(true, true);
+ }
+ else if (d->needEmitSelectionChanged)
+ {
+ emit signalSelectionChanged();
+ }
+ d->needEmitSelectionChanged = false;
+
+ // set first visible item if they where stored before update was triggered
+ if (d->storedVisibleItem)
+ {
+ ensureItemVisible(d->storedVisibleItem);
+ // reset to 0
+ d->storedVisibleItem = 0;
+ }
+ else
+ {
+ ensureItemVisible(d->currItem);
+ }
+
+ viewport()->update();
+}
+
+bool IconView::arrangeItems()
+{
+ int y = 0;
+ int itemW = itemRect().width();
+ int itemH = itemRect().height();
+ int maxW = 0;
+
+ int numItemsPerRow = visibleWidth()/(itemW + d->spacing);
+
+ bool changed = false;
+
+ IconGroupItem* group = d->firstGroup;
+ IconItem* item = 0;
+ while (group)
+ {
+ changed = group->move(y) || changed;
+ y += group->rect().height() + d->spacing;
+
+ item = group->firstItem();
+
+ int col = 0;
+ int x = d->spacing;
+ while (item)
+ {
+ changed = item->move(x, y) || changed;
+ x += itemW + d->spacing;
+ col++;
+
+ if (col >= numItemsPerRow)
+ {
+ x = d->spacing;
+ y += itemH + d->spacing;
+ col = 0;
+ }
+
+ maxW = TQMAX(maxW, x + itemW);
+
+ item = item->m_next;
+ }
+
+ if (col != 0)
+ {
+ y += itemH + d->spacing;
+ }
+
+ y += d->spacing;
+
+ group = group->m_next;
+ }
+
+ viewport()->setUpdatesEnabled(false);
+ resizeContents( maxW, y );
+ viewport()->setUpdatesEnabled(true);
+
+ rebuildContainers();
+
+ return changed;
+}
+
+TQRect IconView::itemRect() const
+{
+ return TQRect(0, 0, 100, 100);
+}
+
+TQRect IconView::bannerRect() const
+{
+ return TQRect(0, 0, visibleWidth(), 0);
+}
+
+void IconView::viewportPaintEvent(TQPaintEvent* pe)
+{
+ TQRect r(pe->rect());
+ TQRegion paintRegion(pe->region());
+
+ TQPainter painter(viewport());
+ painter.setClipRegion(paintRegion);
+
+ // paint any group banners which intersect this paintevent rect
+ for (IconGroupItem* group = d->firstGroup; group; group = group->nextGroup())
+ {
+ TQRect br(contentsRectToViewport(group->rect()));
+ if (r.intersects(br))
+ {
+ group->paintBanner();
+ paintRegion -= TQRegion(br);
+ }
+ }
+
+ // now paint any items which intersect
+ for (IconViewPriv::ItemContainer* c = d->firstContainer; c;
+ c = c->next)
+ {
+ TQRect cr(contentsRectToViewport(c->rect));
+
+ if (r.intersects(cr))
+ {
+
+ for (TQValueList<IconItem*>::iterator it = c->items.begin();
+ it != c->items.end(); ++it)
+ {
+ IconItem* item = *it;
+ TQRect ir(contentsRectToViewport(item->rect()));
+ if (r.intersects(ir))
+ {
+ item->paintItem();
+ paintRegion -= TQRegion(ir);
+ }
+ }
+ }
+ }
+
+ painter.setClipRegion(paintRegion);
+ painter.fillRect(r, colorGroup().base());
+ painter.end();
+}
+
+TQRect IconView::contentsRectToViewport(const TQRect& r) const
+{
+ TQRect vr = TQRect(contentsToViewport(TQPoint(r.x(), r.y())), r.size());
+ return vr;
+}
+
+void IconView::resizeEvent(TQResizeEvent* e)
+{
+ TQScrollView::resizeEvent(e);
+ triggerRearrangement();
+}
+
+void IconView::rebuildContainers()
+{
+ deleteContainers();
+
+ IconItem *item = 0;
+ appendContainer();
+
+ if (d->firstGroup)
+ item = d->firstGroup->firstItem();
+
+ IconViewPriv::ItemContainer* c = d->lastContainer;
+ while (item)
+ {
+ if (c->rect.contains(item->rect()))
+ {
+ c->items.append(item);
+ item = item->nextItem();
+ }
+ else if (c->rect.intersects(item->rect()))
+ {
+ c->items.append( item );
+ c = c->next;
+
+ if (!c)
+ {
+ appendContainer();
+ c = d->lastContainer;
+ }
+
+ c->items.append(item);
+ item = item->nextItem();
+ c = c->prev;
+ }
+ else
+ {
+ if (item->y() < c->rect.y() && c->prev)
+ {
+ c = c->prev;
+ continue;
+ }
+
+ c = c->next;
+ if (!c)
+ {
+ appendContainer();
+ c = d->lastContainer;
+ }
+ }
+ }
+}
+
+void IconView::appendContainer()
+{
+ TQSize s( INT_MAX - 1, RECT_EXTENSION );
+
+ if (!d->firstContainer)
+ {
+ d->firstContainer =
+ new IconViewPriv::ItemContainer(0, 0, TQRect(TQPoint(0, 0), s));
+ d->lastContainer = d->firstContainer;
+ }
+ else
+ {
+ d->lastContainer = new IconViewPriv::ItemContainer(
+ d->lastContainer, 0, TQRect(d->lastContainer->rect.bottomLeft(), s));
+ }
+}
+
+void IconView::deleteContainers()
+{
+ IconViewPriv::ItemContainer *c = d->firstContainer;
+ IconViewPriv::ItemContainer *tmp;
+
+ while (c)
+ {
+ tmp = c->next;
+ delete c;
+ c = tmp;
+ }
+
+ d->firstContainer = d->lastContainer = 0;
+}
+
+void IconView::leaveEvent(TQEvent *e)
+{
+ // hide tooltip
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+
+ // if the mouse leaves the widget we are not dragging
+ // anymore
+ d->dragging = false;
+
+ TQScrollView::leaveEvent(e);
+}
+
+void IconView::focusOutEvent(TQFocusEvent* e)
+{
+ // hide tooltip
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+
+ TQScrollView::focusOutEvent(e);
+}
+
+bool IconView::acceptToolTip(IconItem*, const TQPoint&)
+{
+ return true;
+}
+
+void IconView::contentsMousePressEvent(TQMouseEvent* e)
+{
+ d->pressedMoved = false;
+
+ // hide tooltip
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+
+ // Delete any existing rubber -------------------------------
+ if ( d->rubber )
+ {
+ TQPainter p;
+ p.begin(viewport());
+ p.setRasterOp(NotROP);
+ p.setPen(TQPen(color0, 1));
+ p.setBrush(NoBrush);
+
+ drawRubber(&p);
+ p.end();
+ delete d->rubber;
+ d->rubber = 0;
+ }
+
+ if (e->button() == TQt::RightButton)
+ {
+ IconItem* item = findItem(e->pos());
+ if (item)
+ {
+ IconItem* prevCurrItem = d->currItem;
+ d->currItem = item;
+ d->anchorItem = item;
+ if (prevCurrItem)
+ prevCurrItem->repaint();
+
+ if (!item->isSelected())
+ item->setSelected(true, true);
+ item->repaint();
+
+ emit signalRightButtonClicked(item, e->globalPos());
+ }
+ else
+ {
+ clearSelection();
+ emit signalRightButtonClicked(e->globalPos());
+ }
+ return;
+ }
+
+ IconItem *item = findItem(e->pos());
+ if (item)
+ {
+ if (e->state() & TQt::ControlButton)
+ {
+ item->setSelected(!item->isSelected(), false);
+ }
+ else if (e->state() & TQt::ShiftButton)
+ {
+ blockSignals(true);
+
+ if (d->currItem)
+ {
+ clearSelection();
+
+ // select all items from/upto the current item
+ bool bwdSelect = false;
+
+ // find if the current item is before the clicked item
+ for (IconItem* it = item->prevItem(); it; it = it->prevItem())
+ {
+ if (it == d->currItem)
+ {
+ bwdSelect = true;
+ break;
+ }
+ }
+
+ if (bwdSelect)
+ {
+ for (IconItem* it = item; it; it = it->prevItem())
+ {
+ it->setSelected(true, false);
+ if (it == d->currItem)
+ break;
+ }
+ }
+ else
+ {
+ for (IconItem* it = item; it; it = it->nextItem())
+ {
+ it->setSelected(true, false);
+ if (it == d->currItem)
+ break;
+ }
+ }
+ }
+ else
+ {
+ item->setSelected(true, false);
+ }
+
+ blockSignals(false);
+
+ emit signalSelectionChanged();
+ }
+ else
+ {
+ if (!item->isSelected())
+ item->setSelected(true, true);
+ }
+
+ IconItem* prevCurrItem = d->currItem;
+ d->currItem = item;
+ d->anchorItem = item;
+
+ if (prevCurrItem)
+ prevCurrItem->repaint();
+
+ d->currItem->repaint();
+
+ d->dragging = true;
+ d->dragStartPos = e->pos();
+
+ return;
+ }
+
+ // Press outside any item.
+ if (!(e->state() & TQt::ControlButton))
+ {
+ // unselect all if the ctrl button is not pressed
+ clearSelection();
+ }
+ else
+ {
+ // ctrl is pressed. make sure our current selection is not lost
+ d->prevSelectedItems.clear();
+ TQPtrDictIterator<IconItem> it( d->selectedItems );
+
+ for ( ; it.current(); ++it )
+ {
+ d->prevSelectedItems.insert(it.current(), it.current());
+ }
+ }
+
+ d->rubber = new TQRect( e->x(), e->y(), 0, 0 );
+
+ TQPainter p;
+ p.begin( viewport() );
+ p.setRasterOp( NotROP );
+ p.setPen( TQPen( color0, 1 ) );
+ p.setBrush( NoBrush );
+ drawRubber( &p );
+ p.end();
+}
+
+void IconView::drawRubber(TQPainter* p)
+{
+ if ( !p || !d->rubber )
+ return;
+
+ TQRect r(d->rubber->normalize());
+
+ r = contentsRectToViewport(r);
+
+ TQPoint pnt(r.x(), r.y());
+
+ style().drawPrimitive(TQStyle::PE_FocusRect, p,
+ TQRect( pnt.x(), pnt.y(),
+ r.width(), r.height() ),
+ colorGroup(), TQStyle::Style_Default,
+ TQStyleOption(colorGroup().base()));
+}
+
+void IconView::contentsMouseMoveEvent(TQMouseEvent* e)
+{
+ if (e->state() == NoButton)
+ {
+ IconItem* item = findItem(e->pos());
+
+ if(d->showTips)
+ {
+ if (!isActiveWindow())
+ {
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+ return;
+ }
+
+ if (item != d->toolTipItem)
+ {
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+
+ if(acceptToolTip(item, e->pos()))
+ {
+ d->toolTipItem = item;
+ d->toolTipTimer->start(500, true);
+ }
+ }
+
+ if(item == d->toolTipItem && !acceptToolTip(item, e->pos()))
+ {
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+ }
+ }
+
+ if (TDEGlobalSettings::changeCursorOverIcon())
+ {
+ if (item && item->clickToOpenRect().contains(e->pos()))
+ setCursor(KCursor::handCursor());
+ else
+ unsetCursor();
+ }
+ return;
+ }
+
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+
+ if (d->dragging && (e->state() & TQt::LeftButton))
+ {
+ if ( (d->dragStartPos - e->pos()).manhattanLength()
+ > TQApplication::startDragDistance() )
+ {
+ startDrag();
+ }
+ return;
+ }
+
+ if (!d->rubber)
+ return;
+
+ TQRect oldRubber = TQRect(*d->rubber);
+
+ d->rubber->setRight( e->pos().x() );
+ d->rubber->setBottom( e->pos().y() );
+
+ TQRect nr = d->rubber->normalize();
+ TQRect rubberUnion = nr.unite(oldRubber.normalize());
+
+ bool changed = false;
+
+ TQRegion paintRegion;
+ viewport()->setUpdatesEnabled(false);
+ blockSignals(true);
+
+ IconViewPriv::ItemContainer *c = d->firstContainer;
+ for (; c; c = c->next)
+ {
+ if ( rubberUnion.intersects(c->rect) )
+ {
+ for (TQValueList<IconItem*>::iterator it = c->items.begin();
+ it != c->items.end(); ++it)
+ {
+ IconItem* item = *it;
+ if (nr.intersects(item->rect()))
+ {
+ if (!item->isSelected())
+ {
+ item->setSelected(true, false);
+ changed = true;
+ paintRegion += TQRect(item->rect());
+ }
+ }
+ else
+ {
+ if (item->isSelected() && !d->prevSelectedItems.find(item))
+ {
+ item->setSelected(false, false);
+ changed = true;
+ paintRegion += TQRect(item->rect());
+ }
+ }
+ }
+ }
+ }
+
+ blockSignals(false);
+ viewport()->setUpdatesEnabled(true);
+
+ TQRect r = *d->rubber;
+ *d->rubber = oldRubber;
+
+ TQPainter p;
+ p.begin( viewport() );
+ p.setRasterOp( NotROP );
+ p.setPen( TQPen( color0, 1 ) );
+ p.setBrush( NoBrush );
+ drawRubber( &p );
+ p.end();
+
+ if (changed)
+ {
+ paintRegion.translate(-contentsX(), -contentsY());
+ viewport()->repaint(paintRegion);
+ }
+
+ ensureVisible(e->pos().x(), e->pos().y());
+
+ *d->rubber = r;
+
+ p.begin(viewport());
+ p.setRasterOp(NotROP);
+ p.setPen(TQPen(color0, 1));
+ p.setBrush(NoBrush);
+ drawRubber(&p);
+ p.end();
+
+ d->pressedMoved = true;
+
+ if (changed)
+ emit signalSelectionChanged();
+}
+
+void IconView::contentsMouseReleaseEvent(TQMouseEvent* e)
+{
+ d->dragging = false;
+ d->prevSelectedItems.clear();
+
+ if (d->rubber)
+ {
+ TQPainter p;
+ p.begin( viewport() );
+ p.setRasterOp( NotROP );
+ p.setPen( TQPen( color0, 1 ) );
+ p.setBrush( NoBrush );
+
+ drawRubber( &p );
+ p.end();
+
+ delete d->rubber;
+ d->rubber = 0;
+ }
+
+ if (e->state() == TQt::LeftButton)
+ {
+ if (d->pressedMoved)
+ {
+ emit signalSelectionChanged();
+ d->pressedMoved = false;
+ return;
+ }
+
+ // click on item
+ IconItem *item = findItem(e->pos());
+ if (item)
+ {
+ IconItem* prevCurrItem = d->currItem;
+ item->setSelected(true, true);
+ d->currItem = item;
+ d->anchorItem = item;
+ if (prevCurrItem)
+ prevCurrItem->repaint();
+ if (TDEGlobalSettings::singleClick())
+ {
+ if (item->clickToOpenRect().contains(e->pos()))
+ {
+ itemClickedToOpen(item);
+ }
+ }
+ }
+ }
+}
+
+void IconView::contentsWheelEvent(TQWheelEvent* e)
+{
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+ viewport()->update();
+
+ TQScrollView::contentsWheelEvent(e);
+}
+
+void IconView::contentsMouseDoubleClickEvent(TQMouseEvent *e)
+{
+ if (TDEGlobalSettings::singleClick())
+ return;
+
+ IconItem *item = findItem(e->pos());
+ if (item)
+ {
+ itemClickedToOpen(item);
+ }
+}
+
+void IconView::keyPressEvent(TQKeyEvent* e)
+{
+ bool handled = false;
+
+ if (!firstItem())
+ return;
+
+ switch ( e->key() )
+ {
+ case Key_Home:
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = firstItem();
+ d->anchorItem = d->currItem;
+ if (tmp)
+ tmp->repaint();
+
+ firstItem()->setSelected(true, true);
+ ensureItemVisible(firstItem());
+ handled = true;
+ break;
+ }
+
+ case Key_End:
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = lastItem();
+ d->anchorItem = d->currItem;
+ if (tmp)
+ tmp->repaint();
+
+ lastItem()->setSelected(true, true);
+ ensureItemVisible(lastItem());
+ handled = true;
+ break;
+ }
+
+ case Key_Enter:
+ case Key_Return:
+ {
+ if (d->currItem)
+ {
+ emit signalReturnPressed(d->currItem);
+ handled = true;
+ }
+ break;
+ }
+
+ case Key_Right:
+ {
+ IconItem *item = 0;
+
+ if (d->currItem)
+ {
+ if (d->currItem->nextItem())
+ {
+ if (e->state() & TQt::ControlButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = d->currItem->nextItem();
+ d->anchorItem = d->currItem;
+ tmp->repaint();
+ d->currItem->repaint();
+
+ item = d->currItem;
+ }
+ else if (e->state() & TQt::ShiftButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = d->currItem->nextItem();
+ tmp->repaint();
+
+ // if the anchor is behind us, move forward preserving
+ // the previously selected item. otherwise unselect the
+ // previously selected item
+ if (!anchorIsBehind())
+ tmp->setSelected(false, false);
+
+ d->currItem->setSelected(true, false);
+
+ item = d->currItem;
+ }
+ else
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = d->currItem->nextItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ tmp->repaint();
+
+ item = d->currItem;
+ }
+ }
+ }
+ else
+ {
+ d->currItem = firstItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ item = d->currItem;
+ }
+
+ ensureItemVisible(item);
+ handled = true;
+ break;
+ }
+
+ case Key_Left:
+ {
+ IconItem *item = 0;
+
+ if (d->currItem)
+ {
+ if (d->currItem->prevItem())
+ {
+ if (e->state() & TQt::ControlButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = d->currItem->prevItem();
+ d->anchorItem = d->currItem;
+ tmp->repaint();
+ d->currItem->repaint();
+
+ item = d->currItem;
+ }
+ else if (e->state() & TQt::ShiftButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = d->currItem->prevItem();
+ tmp->repaint();
+
+ // if the anchor is ahead of us, move forward preserving
+ // the previously selected item. otherwise unselect the
+ // previously selected item
+ if (anchorIsBehind())
+ tmp->setSelected(false, false);
+
+ d->currItem->setSelected(true, false);
+
+ item = d->currItem;
+ }
+ else
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = d->currItem->prevItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ tmp->repaint();
+
+ item = d->currItem;
+ }
+ }
+ }
+ else
+ {
+ d->currItem = firstItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ item = d->currItem;
+ }
+
+ ensureItemVisible(item);
+ handled = true;
+ break;
+ }
+
+ case Key_Up:
+ {
+ IconItem *item = 0;
+
+ if (d->currItem)
+ {
+ int x = d->currItem->x() + itemRect().width()/2;
+ int y = d->currItem->y() - d->spacing*2;
+
+ IconItem *it = 0;
+
+ while (!it && y > 0)
+ {
+ it = findItem(TQPoint(x,y));
+ y -= d->spacing * 2;
+ }
+
+ if (it)
+ {
+ if (e->state() & TQt::ControlButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = it;
+ d->anchorItem = it;
+ tmp->repaint();
+ d->currItem->repaint();
+
+ item = d->currItem;
+ }
+ else if (e->state() & TQt::ShiftButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = it;
+ tmp->repaint();
+
+ clearSelection();
+ if (anchorIsBehind())
+ {
+ for (IconItem* i = d->currItem; i; i = i->prevItem())
+ {
+ i->setSelected(true, false);
+ if (i == d->anchorItem)
+ break;
+ }
+ }
+ else
+ {
+ for (IconItem* i = d->currItem; i; i = i->nextItem())
+ {
+ i->setSelected(true, false);
+ if (i == d->anchorItem)
+ break;
+ }
+ }
+
+ item = d->currItem;
+ }
+ else
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = it;
+ d->anchorItem = it;
+ d->currItem->setSelected(true, true);
+ tmp->repaint();
+
+ item = d->currItem;
+ }
+ }
+ }
+ else
+ {
+ d->currItem = firstItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ item = d->currItem;
+ }
+
+ ensureItemVisible(item);
+ handled = true;
+ break;
+ }
+
+ case Key_Down:
+ {
+ IconItem *item = 0;
+
+ if (d->currItem)
+ {
+ int x = d->currItem->x() + itemRect().width()/2;
+ int y = d->currItem->y() + itemRect().height() + d->spacing*2;
+
+ IconItem *it = 0;
+
+ while (!it && y < contentsHeight())
+ {
+ it = findItem(TQPoint(x,y));
+ y += d->spacing * 2;
+ }
+
+ if (it)
+ {
+ if (e->state() & TQt::ControlButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = it;
+ d->anchorItem = it;
+ tmp->repaint();
+ d->currItem->repaint();
+
+ item = d->currItem;
+ }
+ else if (e->state() & TQt::ShiftButton)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = it;
+ tmp->repaint();
+
+ clearSelection();
+ if (anchorIsBehind())
+ {
+ for (IconItem* i = d->currItem; i; i = i->prevItem())
+ {
+ i->setSelected(true, false);
+ if (i == d->anchorItem)
+ break;
+ }
+ }
+ else
+ {
+ for (IconItem* i = d->currItem; i; i = i->nextItem())
+ {
+ i->setSelected(true, false);
+ if (i == d->anchorItem)
+ break;
+ }
+ }
+
+ item = d->currItem;
+ }
+ else
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = it;
+ d->anchorItem = it;
+ d->currItem->setSelected(true, true);
+ tmp->repaint();
+
+ item = d->currItem;
+ }
+ }
+ }
+ else
+ {
+ d->currItem = firstItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ item = d->currItem;
+ }
+
+ ensureItemVisible(item);
+ handled = true;
+ break;
+ }
+
+ case Key_Next:
+ {
+ IconItem *item = 0;
+
+ if (d->currItem)
+ {
+ TQRect r( 0, d->currItem->y() + visibleHeight(),
+ contentsWidth(), visibleHeight() );
+ IconItem *ni = findFirstVisibleItem(r, false);
+
+ if (!ni)
+ {
+ r = TQRect( 0, d->currItem->y() + itemRect().height(),
+ contentsWidth(), contentsHeight() );
+ ni = findLastVisibleItem(r, false);
+ }
+
+ if (ni)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = ni;
+ d->anchorItem = ni;
+ item = ni;
+ tmp->repaint();
+ d->currItem->setSelected(true, true);
+ }
+ }
+ else
+ {
+ d->currItem = firstItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ item = d->currItem;
+ }
+
+ ensureItemVisible(item);
+ handled = true;
+ break;
+ }
+
+ case Key_Prior:
+ {
+ IconItem *item = 0;
+
+ if (d->currItem)
+ {
+ TQRect r(0, d->currItem->y() - visibleHeight(),
+ contentsWidth(), visibleHeight() );
+
+ IconItem *ni = findFirstVisibleItem(r, false);
+
+ if (!ni)
+ {
+ r = TQRect( 0, 0, contentsWidth(), d->currItem->y() );
+ ni = findFirstVisibleItem(r, false);
+ }
+
+ if (ni)
+ {
+ IconItem* tmp = d->currItem;
+ d->currItem = ni;
+ d->anchorItem = ni;
+ item = ni;
+ tmp->repaint();
+ d->currItem->setSelected(true, true);
+ }
+ }
+ else
+ {
+ d->currItem = firstItem();
+ d->anchorItem = d->currItem;
+ d->currItem->setSelected(true, true);
+ item = d->currItem;
+ }
+
+ ensureItemVisible(item);
+ handled = true;
+ break;
+ }
+
+ // Key_Space is used as a global shortcut in DigikamApp.
+ // Ctrl+Space comes through, Shift+Space is filtered out.
+ case Key_Space:
+ {
+ if (d->currItem)
+ {
+ if ( (e->state() & TQt::ControlButton) || (e->state() & TQt::ShiftButton) )
+ {
+ d->currItem->setSelected(!d->currItem->isSelected(), false);
+ }
+ else
+ {
+ if (!d->currItem->isSelected())
+ d->currItem->setSelected(true, true);
+ }
+ handled = true;
+ }
+ break;
+ }
+
+ case Key_Menu:
+ {
+ if (d->currItem)
+ {
+ if (!d->currItem->isSelected())
+ d->currItem->setSelected(true, false);
+
+ ensureItemVisible(d->currItem);
+
+ TQRect r(itemRect());
+ int w = r.width();
+ int h = r.height();
+ TQPoint p(d->currItem->x() + w / 2, d->currItem->y() + h / 2);
+
+ emit signalRightButtonClicked(d->currItem, mapToGlobal(contentsToViewport(p)));
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!handled)
+ {
+ e->ignore();
+ }
+ else
+ {
+ emit signalSelectionChanged();
+ viewport()->update();
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+ }
+}
+
+bool IconView::anchorIsBehind() const
+{
+ if (!d->anchorItem || !d->currItem)
+ return false;
+
+ for (IconItem* it = d->anchorItem; it; it = it->nextItem())
+ {
+ if (it == d->currItem)
+ return true;
+ }
+
+ return false;
+}
+
+
+void IconView::startDrag()
+{
+}
+
+void IconView::ensureItemVisible(IconItem *item)
+{
+ if ( !item )
+ return;
+
+ if ( item->y() == firstItem()->y() )
+ {
+ TQRect r(itemRect());
+ int w = r.width();
+ ensureVisible( item->x() + w / 2, 0, w/2+1, 0 );
+ }
+ else
+ {
+ TQRect r(itemRect());
+ int w = r.width();
+ int h = r.height();
+ ensureVisible( item->x() + w / 2, item->y() + h / 2,
+ w / 2 + 1, h / 2 + 1 );
+ }
+}
+
+IconItem* IconView::findFirstVisibleItem(bool useThumbnailRect) const
+{
+ TQRect r(contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ return findFirstVisibleItem(r, useThumbnailRect);
+}
+
+IconItem* IconView::findLastVisibleItem(bool useThumbnailRect) const
+{
+ TQRect r(contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ return findLastVisibleItem(r, useThumbnailRect);
+}
+
+IconItem* IconView::findFirstVisibleItem(const TQRect& r, bool useThumbnailRect) const
+{
+ IconViewPriv::ItemContainer *c = d->firstContainer;
+ bool alreadyIntersected = false;
+ IconItem* i = 0;
+ for ( ; c; c = c->next )
+ {
+ if ( c->rect.intersects( r ) )
+ {
+ alreadyIntersected = true;
+ for (TQValueList<IconItem*>::iterator it = c->items.begin();
+ it != c->items.end(); ++it)
+ {
+ IconItem *item = *it;
+
+ // if useThumbnailRect, we only check for the clickToOpenRect, which is the thumbnail,
+ // otherwise, we take the whole item rect
+ if ( r.intersects( useThumbnailRect ? item->clickToOpenRect() : item->rect() ) )
+ {
+ if ( !i )
+ {
+ i = item;
+ }
+ else
+ {
+ TQRect r2 = item->rect();
+ TQRect r3 = i->rect();
+ if ( r2.y() < r3.y() )
+ i = item;
+ else if ( r2.y() == r3.y() &&
+ r2.x() < r3.x() )
+ i = item;
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( alreadyIntersected )
+ break;
+ }
+ }
+
+ return i;
+}
+
+IconItem* IconView::findLastVisibleItem(const TQRect& r, bool useThumbnailRect) const
+{
+ IconViewPriv::ItemContainer *c = d->firstContainer;
+ IconItem *i = 0;
+ bool alreadyIntersected = false;
+ for ( ; c; c = c->next )
+ {
+ if ( c->rect.intersects( r ) )
+ {
+ alreadyIntersected = true;
+ for (TQValueList<IconItem*>::iterator it = c->items.begin();
+ it != c->items.end(); ++it)
+ {
+ IconItem *item = *it;
+
+ if ( r.intersects( useThumbnailRect ? item->clickToOpenRect() : item->rect() ) )
+ {
+ if ( !i )
+ {
+ i = item;
+ }
+ else
+ {
+ TQRect r2 = item->rect();
+ TQRect r3 = i->rect();
+ if ( r2.y() > r3.y() )
+ i = item;
+ else if ( r2.y() == r3.y() &&
+ r2.x() > r3.x() )
+ i = item;
+ }
+ }
+ }
+ }
+ else {
+ if ( alreadyIntersected )
+ break;
+ }
+ }
+
+ return i;
+}
+
+void IconView::drawFrameRaised(TQPainter* p)
+{
+ TQRect r = frameRect();
+ int lwidth = lineWidth();
+
+ const TQColorGroup & g = colorGroup();
+
+ qDrawShadeRect( p, r, g, false, lwidth,
+ midLineWidth() );
+}
+
+void IconView::drawFrameSunken(TQPainter* p)
+{
+ TQRect r = frameRect();
+ int lwidth = lineWidth();
+
+ const TQColorGroup & g = colorGroup();
+
+ qDrawShadeRect( p, r, g, true, lwidth,
+ midLineWidth() );
+}
+
+void IconView::setEnableToolTips(bool val)
+{
+ d->showTips = val;
+ if (!val)
+ {
+ d->toolTipItem = 0;
+ d->toolTipTimer->stop();
+ slotToolTip();
+ }
+}
+
+void IconView::slotToolTip()
+{
+ emit signalShowToolTip(d->toolTipItem);
+}
+
+void IconView::itemClickedToOpen(IconItem* item)
+{
+ if (!item)
+ return;
+
+ IconItem* prevCurrItem = d->currItem;
+ d->currItem = item;
+ d->anchorItem = item;
+
+ if (prevCurrItem)
+ prevCurrItem->repaint();
+
+ item->setSelected(true);
+ emit signalDoubleClicked(item);
+}
+
+int IconView::cmpItems(const void *n1, const void *n2)
+{
+ if ( !n1 || !n2 )
+ return 0;
+
+ IconViewPriv::SortableItem *i1 = (IconViewPriv::SortableItem *)n1;
+ IconViewPriv::SortableItem *i2 = (IconViewPriv::SortableItem *)n2;
+
+ return i1->group->compare( i2->group );
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/iconview.h b/src/digikam/iconview.h
new file mode 100644
index 00000000..59a4b921
--- /dev/null
+++ b/src/digikam/iconview.h
@@ -0,0 +1,170 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-24
+ * Description : icon view.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICONVIEW_H
+#define ICONVIEW_H
+
+// TQt includes.
+
+#include <tqscrollview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQPainter;
+class TQMouseEvent;
+class TQPaintEvent;
+class TQDropEvent;
+class TQPoint;
+
+namespace Digikam
+{
+
+class IconItem;
+class IconGroupItem;
+class IconViewPriv;
+
+class DIGIKAM_EXPORT IconView : public TQScrollView
+{
+ TQ_OBJECT
+
+public:
+
+ IconView(TQWidget* parent=0, const char* name=0);
+ virtual ~IconView();
+
+ IconGroupItem* firstGroup() const;
+ IconGroupItem* lastGroup() const;
+ IconGroupItem* findGroup(const TQPoint& pos);
+
+ IconItem* firstItem() const;
+ IconItem* lastItem() const;
+ IconItem* currentItem() const;
+ IconItem* findItem(const TQPoint& pos);
+
+ void setCurrentItem(IconItem* item);
+
+ int count() const;
+ int countSelected() const;
+ int groupCount() const;
+
+ virtual void clear(bool update=true);
+ void sort();
+
+ void clearSelection();
+ void selectAll();
+ void invertSelection();
+
+ void selectItem(IconItem* item, bool select);
+
+ /** Define the item which is visible after changing an album
+ (applies both to physical and virtual albums, like tags and date view). */
+ void setStoredVisibleItem(IconItem *item);
+
+ void triggerRearrangement();
+
+ void insertGroup(IconGroupItem* group);
+ void takeGroup(IconGroupItem* group);
+
+ void insertItem(IconItem* item);
+ void takeItem(IconItem* item);
+
+ void ensureItemVisible(IconItem *item);
+ IconItem* findFirstVisibleItem(const TQRect& r, bool useThumbnailRect = true) const;
+ IconItem* findLastVisibleItem(const TQRect& r, bool useThumbnailRect = true) const;
+ IconItem* findFirstVisibleItem(bool useThumbnailRect = true) const;
+ IconItem* findLastVisibleItem(bool useThumbnailRect = true) const;
+
+ virtual TQRect itemRect() const;
+ virtual TQRect bannerRect() const;
+
+ TQRect contentsRectToViewport(const TQRect& r) const;
+
+ void setEnableToolTips(bool val);
+
+ void setDelayedRearrangement(bool delayed);
+
+protected:
+
+ virtual void viewportPaintEvent(TQPaintEvent* pe);
+ virtual void resizeEvent(TQResizeEvent* e);
+ virtual void contentsMousePressEvent(TQMouseEvent* e);
+ virtual void contentsMouseMoveEvent(TQMouseEvent* e);
+ virtual void contentsMouseReleaseEvent(TQMouseEvent* e);
+ virtual void contentsMouseDoubleClickEvent(TQMouseEvent *e);
+ virtual void contentsWheelEvent(TQWheelEvent* e);
+ virtual void leaveEvent(TQEvent *e);
+ virtual void focusOutEvent(TQFocusEvent* e);
+ virtual void keyPressEvent(TQKeyEvent* e);
+
+ virtual void startDrag();
+
+ void drawFrameRaised(TQPainter* p);
+ void drawFrameSunken(TQPainter* p);
+
+ virtual bool acceptToolTip(IconItem* , const TQPoint&);
+
+private:
+
+ bool arrangeItems();
+ void rebuildContainers();
+ void appendContainer();
+ void deleteContainers();
+
+ void drawRubber(TQPainter* p);
+
+ void itemClickedToOpen(IconItem* item);
+
+ bool anchorIsBehind() const;
+
+ void startRearrangeTimer();
+
+ static int cmpItems(const void *n1, const void *n2);
+
+signals:
+
+ void signalSelectionChanged();
+ void signalRightButtonClicked(IconItem* item, const TQPoint& pos);
+ void signalRightButtonClicked(const TQPoint& pos);
+ void signalDoubleClicked(IconItem* item);
+ void signalReturnPressed(IconItem* item);
+ void signalShowToolTip(IconItem* item);
+
+public slots:
+
+ void slotRearrange();
+
+private slots:
+
+ void slotToolTip();
+
+private:
+
+ IconViewPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* ICONVIEW_H */
diff --git a/src/digikam/imageattributeswatch.cpp b/src/digikam/imageattributeswatch.cpp
new file mode 100644
index 00000000..a91e4dea
--- /dev/null
+++ b/src/digikam/imageattributeswatch.cpp
@@ -0,0 +1,89 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-05-04
+ * Description : Watch image attributes
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "imageattributeswatch.h"
+#include "imageattributeswatch.moc"
+
+namespace Digikam
+{
+
+ImageAttributesWatch *ImageAttributesWatch::m_instance = 0;
+
+ImageAttributesWatch::~ImageAttributesWatch()
+{
+ m_instance = 0;
+}
+
+void ImageAttributesWatch::cleanUp()
+{
+ delete m_instance;
+ m_instance = 0;
+}
+
+void ImageAttributesWatch::shutDown()
+{
+ if (m_instance)
+ m_instance->disconnect(0, 0, 0);
+}
+
+ImageAttributesWatch *ImageAttributesWatch::instance()
+{
+ if (!m_instance)
+ m_instance = new ImageAttributesWatch;
+ return m_instance;
+}
+
+void ImageAttributesWatch::imageTagsChanged(TQ_LLONG imageId)
+{
+ emit signalImageTagsChanged(imageId);
+}
+
+void ImageAttributesWatch::imagesChanged(int albumId)
+{
+ emit signalImagesChanged(albumId);
+}
+
+void ImageAttributesWatch::imageRatingChanged(TQ_LLONG imageId)
+{
+ emit signalImageRatingChanged(imageId);
+}
+
+void ImageAttributesWatch::imageDateChanged(TQ_LLONG imageId)
+{
+ emit signalImageDateChanged(imageId);
+}
+
+void ImageAttributesWatch::imageCaptionChanged(TQ_LLONG imageId)
+{
+ emit signalImageCaptionChanged(imageId);
+}
+
+void ImageAttributesWatch::fileMetadataChanged(const KURL &url)
+{
+ emit signalFileMetadataChanged(url);
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/imageattributeswatch.h b/src/digikam/imageattributeswatch.h
new file mode 100644
index 00000000..7d377609
--- /dev/null
+++ b/src/digikam/imageattributeswatch.h
@@ -0,0 +1,97 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-05-04
+ * Description : Watch image attributes
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEATTRIBUTESWATCH_H
+#define IMAGEATTRIBUTESWATCH_H
+
+// TQt includes.
+
+#include <tqobject.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+namespace Digikam
+{
+
+class ImageAttributesWatch : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ static ImageAttributesWatch *instance();
+ static void cleanUp();
+ static void shutDown();
+
+ void imageTagsChanged(TQ_LLONG imageId);
+ void imagesChanged(int albumId);
+
+ void imageRatingChanged(TQ_LLONG imageId);
+ void imageDateChanged(TQ_LLONG imageId);
+ void imageCaptionChanged(TQ_LLONG imageId);
+
+ void fileMetadataChanged(const KURL &url);
+
+signals:
+
+ /** Indicates that tags have been assigned or removed
+ for image with given imageId.
+ There is no guarantee that the tags were actually changed.
+ This signal, the signal below, or both may be sent.
+ */
+ void signalImageTagsChanged(TQ_LLONG imageId);
+
+ /**
+ Indicates that images in the given album id may have changed their tags.
+ This signal, the signal above, or both may be sent.
+ */
+ void signalImagesChanged(int albumId);
+
+ /** These signals indicated that the rating, data or caption
+ of the image with given imageId was set.
+ There is no guarantee that it actually changed.
+ */
+ void signalImageRatingChanged(TQ_LLONG imageId);
+ void signalImageDateChanged(TQ_LLONG imageId);
+ void signalImageCaptionChanged(TQ_LLONG imageId);
+
+ /**
+ Indicates that the metadata if the given file
+ has been changed (a write operation on the file on disk).
+ Usually, the database is updated accordingly, so then this
+ signal is sent in combination with one or more of the above signals.
+ */
+ void signalFileMetadataChanged(const KURL &url);
+
+protected:
+
+ ~ImageAttributesWatch();
+
+ static ImageAttributesWatch *m_instance;
+};
+
+} // namespace Digikam
+
+#endif // IMAGEATTRIBUTESWATCH_H
diff --git a/src/digikam/imageinfo.cpp b/src/digikam/imageinfo.cpp
new file mode 100644
index 00000000..f32d3062
--- /dev/null
+++ b/src/digikam/imageinfo.cpp
@@ -0,0 +1,347 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : image informations container.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+/** @file imageinfo.cpp */
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albummanager.h"
+#include "dio.h"
+#include "imageinfo.h"
+#include "imageattributeswatch.h"
+
+namespace Digikam
+{
+
+AlbumManager* ImageInfo::m_man = 0;
+
+ImageInfo::ImageInfo()
+ : m_ID(-1), m_albumID(-1), m_size(0), m_viewitem(0)
+{
+}
+
+ImageInfo::ImageInfo(TQ_LLONG ID, int albumID, const TQString& name,
+ const TQDateTime& datetime, size_t size,
+ const TQSize& dims)
+ : m_ID(ID), m_albumID(albumID), m_name(name), m_datetime(datetime),
+ m_size(size), m_dims(dims), m_viewitem(0)
+{
+ if (!m_man)
+ {
+ m_man = AlbumManager::instance();
+ }
+}
+
+ImageInfo::ImageInfo(TQ_LLONG ID)
+ : m_ID(ID), m_size(0), m_viewitem(0)
+{
+ if (!m_man)
+ {
+ m_man = AlbumManager::instance();
+ }
+ AlbumDB* db = m_man->albumDB();
+
+ // retrieve these now, the rest on demand
+ m_albumID = db->getItemAlbum(m_ID);
+ m_name = db->getItemName(m_ID);
+}
+
+ImageInfo::~ImageInfo()
+{
+}
+
+bool ImageInfo::isNull() const
+{
+ return m_ID != -1;
+}
+
+TQString ImageInfo::name() const
+{
+ return m_name;
+}
+
+void ImageInfo::setName(const TQString& newName)
+{
+ /*
+ KURL src = kurlForKIO();
+ KURL dst = src.upURL();
+ dst.addPath(newName);
+
+ if (!DIO::renameFile(src, dst))
+ return false;
+
+ PAlbum* a = album();
+ if (!a)
+ {
+ DWarning() << "No album found for ID: " << m_albumID << endl;
+ return false;
+ }
+ */
+
+ m_name = newName;
+}
+
+size_t ImageInfo::fileSize() const
+{
+ if (m_size == 0)
+ {
+ TQFileInfo info(filePath());
+ m_size = info.size();
+ }
+ return m_size;
+}
+
+TQDateTime ImageInfo::dateTime() const
+{
+ if (!m_datetime.isValid())
+ {
+ AlbumDB* db = m_man->albumDB();
+ m_datetime = db->getItemDate(m_ID);
+ }
+ return m_datetime;
+}
+
+TQDateTime ImageInfo::modDateTime() const
+{
+ if (!m_modDatetime.isValid())
+ {
+ TQFileInfo fileInfo(filePath());
+ m_modDatetime = fileInfo.lastModified();
+ }
+
+ return m_modDatetime;
+}
+
+TQSize ImageInfo::dimensions() const
+{
+ return m_dims;
+}
+
+TQ_LLONG ImageInfo::id() const
+{
+ return m_ID;
+}
+
+int ImageInfo::albumID() const
+{
+ return m_albumID;
+}
+
+PAlbum* ImageInfo::album() const
+{
+ return m_man->findPAlbum(m_albumID);
+}
+
+KURL ImageInfo::kurl() const
+{
+ PAlbum* a = album();
+ if (!a)
+ {
+ DWarning() << "No album found for ID: " << m_albumID << endl;
+ return KURL();
+ }
+
+ KURL u(m_man->getLibraryPath());
+ u.addPath(a->url());
+ u.addPath(m_name);
+ return u;
+}
+
+TQString ImageInfo::filePath() const
+{
+ PAlbum* a = album();
+ if (!a)
+ {
+ DWarning() << "No album found for ID: " << m_albumID << endl;
+ return TQString();
+ }
+
+ TQString path = m_man->getLibraryPath();
+ path += a->url() + '/' + m_name;
+ return path;
+}
+
+KURL ImageInfo::kurlForKIO() const
+{
+ PAlbum* a = album();
+ if (!a)
+ {
+ DWarning() << "No album found for ID: " << m_albumID << endl;
+ return KURL();
+ }
+
+ KURL u(a->kurl());
+ u.addPath(m_name);
+ return u;
+}
+
+void ImageInfo::setViewItem(void *d)
+{
+ m_viewitem = d;
+}
+
+void* ImageInfo::getViewItem() const
+{
+ return m_viewitem;
+}
+
+void ImageInfo::setDateTime(const TQDateTime& dateTime)
+{
+ if (dateTime.isValid())
+ {
+ AlbumDB* db = m_man->albumDB();
+ db->setItemDate(m_ID, dateTime);
+ m_datetime = dateTime;
+ ImageAttributesWatch::instance()->imageDateChanged(m_ID);
+ }
+}
+
+void ImageInfo::setCaption(const TQString& caption)
+{
+ AlbumDB* db = m_man->albumDB();
+ db->setItemCaption(m_ID, caption);
+ ImageAttributesWatch::instance()->imageCaptionChanged(m_ID);
+}
+
+TQString ImageInfo::caption() const
+{
+ AlbumDB* db = m_man->albumDB();
+ return db->getItemCaption(m_ID);
+}
+
+TQStringList ImageInfo::tagNames() const
+{
+ AlbumDB* db = m_man->albumDB();
+ return db->getItemTagNames(m_ID);
+}
+
+TQStringList ImageInfo::tagPaths(bool leadingSlash) const
+{
+ TQStringList tagPaths;
+
+ AlbumDB* db = m_man->albumDB();
+ IntList tagIDs = db->getItemTagIDs(m_ID);
+ for (IntList::iterator it = tagIDs.begin(); it != tagIDs.end(); ++it)
+ {
+ TAlbum* ta = m_man->findTAlbum(*it);
+ if (ta)
+ {
+ tagPaths.append(ta->tagPath(leadingSlash));
+ }
+ }
+
+ return tagPaths;
+}
+
+TQValueList<int> ImageInfo::tagIDs() const
+{
+ AlbumDB* db = m_man->albumDB();
+ return db->getItemTagIDs(m_ID);
+}
+
+void ImageInfo::setTag(int tagID)
+{
+ AlbumDB* db = m_man->albumDB();
+ db->addItemTag(m_ID, tagID);
+ ImageAttributesWatch::instance()->imageTagsChanged(m_ID);
+}
+
+void ImageInfo::removeTag(int tagID)
+{
+ AlbumDB* db = m_man->albumDB();
+ db->removeItemTag(m_ID, tagID);
+ ImageAttributesWatch::instance()->imageTagsChanged(m_ID);
+}
+
+void ImageInfo::removeAllTags()
+{
+ AlbumDB *db = m_man->albumDB();
+ db->removeItemAllTags(m_ID);
+ ImageAttributesWatch::instance()->imageTagsChanged(m_ID);
+}
+
+void ImageInfo::addTagPaths(const TQStringList &tagPaths)
+{
+ AlbumDB *db = m_man->albumDB();
+ AlbumList list = m_man->findOrCreateTAlbums(tagPaths);
+ for (AlbumList::iterator it = list.begin(); it != list.end(); ++it)
+ db->addItemTag(m_ID, (*it)->id());
+ ImageAttributesWatch::instance()->imageTagsChanged(m_ID);
+}
+
+
+int ImageInfo::rating() const
+{
+ AlbumDB* db = m_man->albumDB();
+ return db->getItemRating(m_ID);
+}
+
+void ImageInfo::setRating(int value)
+{
+ AlbumDB* db = m_man->albumDB();
+ db->setItemRating(m_ID, value);
+ ImageAttributesWatch::instance()->imageRatingChanged(m_ID);
+}
+
+ImageInfo ImageInfo::copyItem(PAlbum *dstAlbum, const TQString &dstFileName)
+{
+ DDebug() << "ImageInfo::copyItem " << m_albumID << " " << m_name << " to "
+ << dstAlbum->id() << " " << dstFileName << endl;
+
+ if (dstAlbum->id() == m_albumID && dstFileName == m_name)
+ return (*this);
+
+ AlbumDB* db = m_man->albumDB();
+ int id = db->copyItem(m_albumID, m_name, dstAlbum->id(), dstFileName);
+
+ if (id == -1)
+ return ImageInfo();
+
+ ImageInfo info;
+ info.m_ID = id;
+ info.m_albumID = dstAlbum->id();
+ info.m_name = dstFileName;
+ // set size and datetime
+ info.refresh();
+ // m_dims is not set, m_viewItem is left 0
+ return info;
+}
+
+void ImageInfo::refresh()
+{
+ m_datetime = m_man->albumDB()->getItemDate(m_ID);
+
+ TQFileInfo fileInfo(filePath());
+ m_size = fileInfo.size();
+ m_modDatetime = fileInfo.lastModified();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/imageinfo.h b/src/digikam/imageinfo.h
new file mode 100644
index 00000000..3427b729
--- /dev/null
+++ b/src/digikam/imageinfo.h
@@ -0,0 +1,280 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : image informations container.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+/** @file imageinfo.h */
+
+#ifndef IMAGEINFO_H
+#define IMAGEINFO_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqdatetime.h>
+#include <tqptrlist.h>
+#include <tqsize.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+namespace Digikam
+{
+
+class PAlbum;
+class AlbumManager;
+
+/**
+ * This is an abstraction of a file entity on the disk.
+ *
+ * This is an abstraction of a file entity on the disk and is uniquely
+ * identified by an Album identifier (PAlbum in which its located) and its
+ * name. Additional methods are provided for accessing/modifying:
+ * - datetime
+ * - caption
+ * - filesize
+ * - tags
+ * - url (ImageInfo::kurl()) -> Note: accessing this will give you the
+ * standard KDE file representation.
+ * (for eg: file:///home/johndoe/Pictures/ItalyPictures/Rome-2004/file001.jpeg)
+ */
+class ImageInfo
+{
+public:
+
+ /**
+ * Constructor
+ * Creates a null image info
+ */
+ ImageInfo();
+
+ /**
+ * Constructor
+ * @param ID unique ID for this image
+ * @param albumID id of the PAlbum to which this item belongs
+ * @param name name of the image
+ * @param datetime datetime of the image
+ * @param size filesize of the image
+ * @param dims dimensions of the image
+ */
+ ImageInfo(TQ_LLONG ID, int albumID, const TQString& name,
+ const TQDateTime& datetime, size_t size,
+ const TQSize& dims=TQSize());
+
+ /**
+ * Constructor
+ * @param ID unique ID for this image
+ */
+ ImageInfo(TQ_LLONG ID);
+
+ /**
+ * Destructor
+ */
+ ~ImageInfo();
+
+ /**
+ * Returns if this objects contains valid data
+ */
+ bool isNull() const;
+
+ /**
+ * @return the name of the image
+ */
+ TQString name() const;
+
+ /**
+ * Set a new name for the image.
+ * @param newName new name for the image
+ */
+ void setName(const TQString& newName);
+
+ /**
+ * @return the datetime of the image
+ */
+ TQDateTime dateTime() const;
+
+ /**
+ * @return the modification datetime of the image
+ */
+ TQDateTime modDateTime() const;
+
+ /**
+ * @return the filesize of the image
+ */
+ size_t fileSize() const;
+
+ /**
+ * @return the dimensions of the image (valid only if dimensions
+ * have been requested)
+ */
+ TQSize dimensions() const;
+
+ /**
+ * @return the standard KDE url with file protocol. The path for
+ * the url is the absolute path of the image.
+ * DEPRECATED: don't use this. if you need only the file path, then
+ * use filePath(). And if you need the digikam "proper" kurl, use
+ * kurlForKIO(). At some point kurl and kurlForKIO() will be merged
+ * together
+ */
+ KURL kurl() const;
+
+ /**
+ * @return the absolute file path of the image
+ */
+ TQString filePath() const;
+
+ /**
+ * @return the kurl for TDEIO or rather DIO. Use this instead of kurl()
+ * for metadata preserving file IO operations. Also, this method needs
+ * to be merged with kurl()
+ */
+ KURL kurlForKIO() const;
+
+ /**
+ * @return the unique image id for this item
+ */
+ TQ_LLONG id() const;
+
+ /**
+ * @return the id of the PAlbum to which this item belongs
+ */
+ int albumID() const;
+
+ /**
+ * @return the PAlbum to which this item belongs
+ */
+ PAlbum* album() const;
+
+ /**
+ * @return the caption for this item
+ */
+ TQString caption() const;
+
+ /**
+ * Set the caption (writes it to database)
+ * @param caption the new caption for this item
+ */
+ void setCaption(const TQString& caption);
+
+ /**
+ * Set the date and time (write it to database)
+ * @param dateTime the new date and time.
+ */
+ void setDateTime(const TQDateTime& dateTime);
+
+ /**
+ * @return a list of names of all tags assigned to this item
+ * @see tagPaths
+ */
+ TQStringList tagNames() const;
+
+ /**
+ * @return a list of complete path of all tags assigned to this item. The
+ * complete path for a tag is a '/' separated string of its hierarchy
+ * @see tagPaths
+ */
+ TQStringList tagPaths(bool leadingSlash = true) const;
+
+ /**
+ * @return a list of IDs of tags assigned to this item
+ * @see tagNames
+ * @see tagPaths
+ * @see Album::id()
+ */
+ TQValueList<int> tagIDs() const;
+
+ /**
+ * Adds a tag to the item (writes it to database)
+ * @param tagID the ID of the tag to add
+ */
+ void setTag(int tagID);
+
+ /**
+ * Adds tags in the list to the item.
+ * Tags are created if they do not yet exist
+ */
+ void addTagPaths(const TQStringList &tagPaths);
+
+ /**
+ * Remove a tag from the item (removes it from database)
+ * @param tagID the ID of the tag to remove
+ */
+ void removeTag(int tagID);
+
+ /**
+ * Remove all tags from the item (removes it from database)
+ */
+ void removeAllTags();
+
+ int rating() const;
+
+ void setRating(int value);
+
+ /**
+ * Copy database information of this item to a newly created item
+ * @param dstAlbum destination album
+ * @param dstFileName new filename
+ * @return an ImageInfo object of the new item
+ */
+ ImageInfo copyItem(PAlbum *dstAlbum, const TQString &dstFileName);
+
+ /**
+ * Assign a viewitem for this item. This is useful when a view has a
+ * corresponding viewitem for this item and wants to access the viewitem, given
+ * this item
+ * @see getViewItem()
+ */
+ void setViewItem(void *d);
+
+ /**
+ * Returns the viewitem associated with this item a viewitem for this item.
+ * @see setViewItem()
+ */
+ void* getViewItem() const;
+
+ /**
+ * refresh the properties of the imageinfo. it reads the database
+ * again for getting the updated date and stats the file to get
+ * the updated size
+ */
+ void refresh();
+
+private:
+
+ TQ_LLONG m_ID;
+ int m_albumID;
+ TQString m_name;
+ mutable TQDateTime m_datetime;
+ mutable TQDateTime m_modDatetime;
+ mutable size_t m_size;
+ TQSize m_dims;
+ void* m_viewitem;
+ static AlbumManager* m_man;
+};
+
+typedef TQPtrList<ImageInfo> ImageInfoList;
+typedef TQPtrListIterator<ImageInfo> ImageInfoListIterator;
+
+} // namespace Digikam
+
+#endif /* IMAGEINFO_H */
diff --git a/src/digikam/imagepreviewview.cpp b/src/digikam/imagepreviewview.cpp
new file mode 100644
index 00000000..f636f685
--- /dev/null
+++ b/src/digikam/imagepreviewview.cpp
@@ -0,0 +1,686 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-21-12
+ * Description : a embedded view to show the image preview widget.
+ *
+ * Copyright (C) 2006-2009 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqcursor.h>
+#include <tqstring.h>
+#include <tqvaluevector.h>
+#include <tqfileinfo.h>
+#include <tqtoolbutton.h>
+#include <tqtooltip.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+#include <tdelocale.h>
+#include <kservice.h>
+#include <krun.h>
+#include <ktrader.h>
+#include <kmimetype.h>
+#include <kiconloader.h>
+#include <kcursor.h>
+#include <kdatetbl.h>
+#include <kiconloader.h>
+#include <kprocess.h>
+#include <tdeapplication.h>
+
+// LibKipi includes.
+
+#include <libkipi/pluginloader.h>
+#include <libkipi/plugin.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "ddebug.h"
+#include "albumdb.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "albumwidgetstack.h"
+#include "imageinfo.h"
+#include "dmetadata.h"
+#include "dpopupmenu.h"
+#include "metadatahub.h"
+#include "paniconwidget.h"
+#include "previewloadthread.h"
+#include "loadingdescription.h"
+#include "tagspopupmenu.h"
+#include "ratingpopupmenu.h"
+#include "themeengine.h"
+#include "imagepreviewview.h"
+#include "imagepreviewview.moc"
+
+namespace Digikam
+{
+
+class ImagePreviewViewPriv
+{
+public:
+
+ ImagePreviewViewPriv()
+ {
+ panIconPopup = 0;
+ panIconWidget = 0;
+ cornerButton = 0;
+ previewThread = 0;
+ previewPreloadThread = 0;
+ imageInfo = 0;
+ parent = 0;
+ hasPrev = false;
+ hasNext = false;
+ loadFullImageSize = false;
+ currentFitWindowZoom = 0;
+ previewSize = 1024;
+ }
+
+ bool hasPrev;
+ bool hasNext;
+ bool loadFullImageSize;
+
+ int previewSize;
+
+ double currentFitWindowZoom;
+
+ TQString path;
+ TQString nextPath;
+ TQString previousPath;
+
+ TQToolButton *cornerButton;
+
+ TDEPopupFrame *panIconPopup;
+
+ PanIconWidget *panIconWidget;
+
+ DImg preview;
+
+ ImageInfo *imageInfo;
+
+ PreviewLoadThread *previewThread;
+ PreviewLoadThread *previewPreloadThread;
+
+ AlbumWidgetStack *parent;
+};
+
+ImagePreviewView::ImagePreviewView(AlbumWidgetStack *parent)
+ : PreviewWidget(parent)
+{
+ d = new ImagePreviewViewPriv;
+ d->parent = parent;
+
+ // get preview size from screen size, but limit from VGA to WQXGA
+ d->previewSize = TQMAX(TDEApplication::desktop()->height(),
+ TDEApplication::desktop()->width());
+ if (d->previewSize < 640)
+ d->previewSize = 640;
+ if (d->previewSize > 2560)
+ d->previewSize = 2560;
+
+ setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);
+
+ d->cornerButton = new TQToolButton(this);
+ d->cornerButton->setIconSet(SmallIcon("move"));
+ d->cornerButton->hide();
+ TQToolTip::add(d->cornerButton, i18n("Pan the image to a region"));
+ setCornerWidget(d->cornerButton);
+
+ // ------------------------------------------------------------
+
+ connect(d->cornerButton, TQ_SIGNAL(pressed()),
+ this, TQ_SLOT(slotCornerButtonPressed()));
+
+ connect(this, TQ_SIGNAL(signalShowNextImage()),
+ this, TQ_SIGNAL(signalNextItem()));
+
+ connect(this, TQ_SIGNAL(signalShowPrevImage()),
+ this, TQ_SIGNAL(signalPrevItem()));
+
+ connect(this, TQ_SIGNAL(signalRightButtonClicked()),
+ this, TQ_SLOT(slotContextMenu()));
+
+ connect(this, TQ_SIGNAL(signalLeftButtonClicked()),
+ this, TQ_SIGNAL(signalBack2Album()));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ // ------------------------------------------------------------
+
+ slotReset();
+}
+
+ImagePreviewView::~ImagePreviewView()
+{
+ delete d->previewThread;
+ delete d->previewPreloadThread;
+ delete d;
+}
+
+void ImagePreviewView::setLoadFullImageSize(bool b)
+{
+ d->loadFullImageSize = b;
+ reload();
+}
+
+void ImagePreviewView::setImage(const DImg& image)
+{
+ d->preview = image;
+
+ updateZoomAndSize(true);
+
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+}
+
+DImg& ImagePreviewView::getImage() const
+{
+ return d->preview;
+}
+
+void ImagePreviewView::reload()
+{
+ // cache is cleaned from AlbumIconView::refreshItems
+ setImagePath(d->path);
+}
+
+void ImagePreviewView::setPreviousNextPaths(const TQString& previous, const TQString &next)
+{
+ d->nextPath = next;
+ d->previousPath = previous;
+}
+
+void ImagePreviewView::setImagePath(const TQString& path)
+{
+ setCursor( KCursor::waitCursor() );
+
+ d->path = path;
+ d->nextPath = TQString();
+ d->previousPath = TQString();
+
+ if (d->path.isEmpty())
+ {
+ slotReset();
+ unsetCursor();
+ return;
+ }
+
+ if (!d->previewThread)
+ {
+ d->previewThread = new PreviewLoadThread();
+ connect(d->previewThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
+ this, TQ_SLOT(slotGotImagePreview(const LoadingDescription &, const DImg&)));
+ }
+ if (!d->previewPreloadThread)
+ {
+ d->previewPreloadThread = new PreviewLoadThread();
+ connect(d->previewPreloadThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
+ this, TQ_SLOT(slotNextPreload()));
+ }
+
+ if (d->loadFullImageSize)
+ d->previewThread->loadHighQuality(LoadingDescription(path, 0, AlbumSettings::instance()->getExifRotate()));
+ else
+ d->previewThread->load(LoadingDescription(path, d->previewSize, AlbumSettings::instance()->getExifRotate()));
+}
+
+void ImagePreviewView::slotGotImagePreview(const LoadingDescription &description, const DImg& preview)
+{
+ if (description.filePath != d->path)
+ return;
+
+ if (preview.isNull())
+ {
+ d->parent->setPreviewMode(AlbumWidgetStack::PreviewImageMode);
+ TQPixmap pix(visibleWidth(), visibleHeight());
+ pix.fill(ThemeEngine::instance()->baseColor());
+ TQPainter p(&pix);
+ TQFileInfo info(d->path);
+ p.setPen(TQPen(ThemeEngine::instance()->textRegColor()));
+ p.drawText(0, 0, pix.width(), pix.height(),
+ TQt::AlignCenter|TQt::WordBreak,
+ i18n("Cannot display preview for\n\"%1\"")
+ .arg(info.fileName()));
+ p.end();
+ // three copies - but the image is small
+ setImage(DImg(pix.convertToImage()));
+ d->parent->previewLoaded();
+ emit signalPreviewLoaded(false);
+ }
+ else
+ {
+ DImg img(preview);
+ if (AlbumSettings::instance()->getExifRotate())
+ d->previewThread->exifRotate(img, description.filePath);
+ d->parent->setPreviewMode(AlbumWidgetStack::PreviewImageMode);
+ setImage(img);
+ d->parent->previewLoaded();
+ emit signalPreviewLoaded(true);
+ }
+
+ unsetCursor();
+ slotNextPreload();
+}
+
+void ImagePreviewView::slotNextPreload()
+{
+ TQString loadPath;
+ if (!d->nextPath.isNull())
+ {
+ loadPath = d->nextPath;
+ d->nextPath = TQString();
+ }
+ else if (!d->previousPath.isNull())
+ {
+ loadPath = d->previousPath;
+ d->previousPath = TQString();
+ }
+ else
+ return;
+
+ if (d->loadFullImageSize)
+ d->previewThread->loadHighQuality(LoadingDescription(loadPath, 0,
+ AlbumSettings::instance()->getExifRotate()));
+ else
+ d->previewPreloadThread->load(LoadingDescription(loadPath, d->previewSize,
+ AlbumSettings::instance()->getExifRotate()));
+}
+
+void ImagePreviewView::setImageInfo(ImageInfo* info, ImageInfo *previous, ImageInfo *next)
+{
+ d->imageInfo = info;
+ d->hasPrev = previous;
+ d->hasNext = next;
+
+ if (d->imageInfo)
+ setImagePath(info->filePath());
+ else
+ setImagePath();
+
+ setPreviousNextPaths(previous ? previous->filePath() : TQString(),
+ next ? next->filePath() : TQString());
+}
+
+ImageInfo* ImagePreviewView::getImageInfo() const
+{
+ return d->imageInfo;
+}
+
+void ImagePreviewView::slotContextMenu()
+{
+ RatingPopupMenu *ratingMenu = 0;
+ TagsPopupMenu *assignTagsMenu = 0;
+ TagsPopupMenu *removeTagsMenu = 0;
+
+ if (!d->imageInfo)
+ return;
+
+ //-- Open With Actions ------------------------------------
+
+ KURL url(d->imageInfo->kurl().path());
+ KMimeType::Ptr mimePtr = KMimeType::findByURL(url, 0, true, true);
+
+ TQValueVector<KService::Ptr> serviceVector;
+ TDETrader::OfferList offers = TDETrader::self()->query(mimePtr->name(), "Type == 'Application'");
+
+ TQPopupMenu openWithMenu;
+
+ TDETrader::OfferList::Iterator iter;
+ KService::Ptr ptr;
+ int index = 100;
+
+ for( iter = offers.begin(); iter != offers.end(); ++iter )
+ {
+ ptr = *iter;
+ openWithMenu.insertItem( ptr->pixmap(TDEIcon::Small), ptr->name(), index++);
+ serviceVector.push_back(ptr);
+ }
+
+ //-- Navigate actions -------------------------------------------
+
+ DPopupMenu popmenu(this);
+ popmenu.insertItem(SmallIcon("back"), i18n("Back"), 10);
+ if (!d->hasPrev) popmenu.setItemEnabled(10, false);
+
+ popmenu.insertItem(SmallIcon("forward"), i18n("Forward"), 11);
+ if (!d->hasNext) popmenu.setItemEnabled(11, false);
+
+ popmenu.insertItem(SmallIcon("folder_image"), i18n("Back to Album"), 15);
+
+ //-- Edit actions -----------------------------------------------
+
+ popmenu.insertSeparator();
+ popmenu.insertItem(SmallIcon("slideshow"), i18n("SlideShow"), 16);
+ popmenu.insertItem(SmallIcon("editimage"), i18n("Edit..."), 12);
+ popmenu.insertItem(SmallIcon("lighttableadd"), i18n("Add to Light Table"), 17);
+ popmenu.insertItem(i18n("Open With"), &openWithMenu, 13);
+
+ // Merge in the KIPI plugins actions ----------------------------
+
+ KIPI::PluginLoader* kipiPluginLoader = KIPI::PluginLoader::instance();
+ KIPI::PluginLoader::PluginList pluginList = kipiPluginLoader->pluginList();
+
+ for (KIPI::PluginLoader::PluginList::const_iterator it = pluginList.begin();
+ it != pluginList.end(); ++it)
+ {
+ KIPI::Plugin* plugin = (*it)->plugin();
+
+ if (plugin && (*it)->name() == "JPEGLossless")
+ {
+ DDebug() << "Found JPEGLossless plugin" << endl;
+
+ TDEActionPtrList actionList = plugin->actions();
+
+ for (TDEActionPtrList::const_iterator iter = actionList.begin();
+ iter != actionList.end(); ++iter)
+ {
+ TDEAction* action = *iter;
+
+ if (TQString::fromLatin1(action->name())
+ == TQString::fromLatin1("jpeglossless_rotate"))
+ {
+ action->plug(&popmenu);
+ }
+ }
+ }
+ }
+
+ //-- Trash action -------------------------------------------
+
+ popmenu.insertSeparator();
+ popmenu.insertItem(SmallIcon("edittrash"), i18n("Move to Trash"), 14);
+
+ // Bulk assignment/removal of tags --------------------------
+
+ TQ_LLONG id = d->imageInfo->id();
+ TQValueList<TQ_LLONG> idList;
+ idList.append(id);
+
+ assignTagsMenu = new TagsPopupMenu(idList, 1000, TagsPopupMenu::ASSIGN);
+ removeTagsMenu = new TagsPopupMenu(idList, 2000, TagsPopupMenu::REMOVE);
+
+ popmenu.insertSeparator();
+
+ popmenu.insertItem(i18n("Assign Tag"), assignTagsMenu);
+ int i = popmenu.insertItem(i18n("Remove Tag"), removeTagsMenu);
+
+ connect(assignTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotAssignTag(int)));
+
+ connect(removeTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotRemoveTag(int)));
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ if (!db->hasTags( idList ))
+ popmenu.setItemEnabled(i, false);
+
+ popmenu.insertSeparator();
+
+ // Assign Star Rating -------------------------------------------
+
+ ratingMenu = new RatingPopupMenu();
+
+ connect(ratingMenu, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotAssignRating(int)));
+
+ popmenu.insertItem(i18n("Assign Rating"), ratingMenu);
+
+ // --------------------------------------------------------
+
+ int idm = popmenu.exec(TQCursor::pos());
+
+ switch(idm)
+ {
+ case 10: // Back
+ {
+ emit signalPrevItem();
+ break;
+ }
+
+ case 11: // Forward
+ {
+ emit signalNextItem();
+ break;
+ }
+
+ case 12: // Edit...
+ {
+ emit signalEditItem();
+ break;
+ }
+
+ case 14: // Move to trash
+ {
+ emit signalDeleteItem();
+ break;
+ }
+
+ case 15: // Back to album
+ {
+ emit signalBack2Album();
+ break;
+ }
+
+ case 16: // SlideShow
+ {
+ emit signalSlideShow();
+ break;
+ }
+
+ case 17: // Place onto Light Table
+ {
+ emit signalInsert2LightTable();
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ // Open With...
+ if (idm >= 100 && idm < 1000)
+ {
+ KService::Ptr imageServicePtr = serviceVector[idm-100];
+ KRun::run(*imageServicePtr, url);
+ }
+
+ serviceVector.clear();
+ delete assignTagsMenu;
+ delete removeTagsMenu;
+ delete ratingMenu;
+}
+
+void ImagePreviewView::slotAssignTag(int tagID)
+{
+ if (d->imageInfo)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfo);
+ hub.setTag(tagID, true);
+ hub.write(d->imageInfo, MetadataHub::PartialWrite);
+ hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void ImagePreviewView::slotRemoveTag(int tagID)
+{
+ if (d->imageInfo)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfo);
+ hub.setTag(tagID, false);
+ hub.write(d->imageInfo, MetadataHub::PartialWrite);
+ hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void ImagePreviewView::slotAssignRating(int rating)
+{
+ rating = TQMIN(5, TQMAX(0, rating));
+ if (d->imageInfo)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfo);
+ hub.setRating(rating);
+ hub.write(d->imageInfo, MetadataHub::PartialWrite);
+ hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void ImagePreviewView::slotThemeChanged()
+{
+ setBackgroundColor(ThemeEngine::instance()->baseColor());
+}
+
+void ImagePreviewView::slotCornerButtonPressed()
+{
+ if (d->panIconPopup)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ }
+
+ d->panIconPopup = new TDEPopupFrame(this);
+ PanIconWidget *pan = new PanIconWidget(d->panIconPopup);
+ pan->setImage(180, 120, getImage());
+ d->panIconPopup->setMainWidget(pan);
+
+ TQRect r((int)(contentsX() / zoomFactor()), (int)(contentsY() / zoomFactor()),
+ (int)(visibleWidth() / zoomFactor()), (int)(visibleHeight() / zoomFactor()));
+ pan->setRegionSelection(r);
+ pan->setMouseFocus();
+
+ connect(pan, TQ_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
+ this, TQ_SLOT(slotPanIconSelectionMoved(const TQRect&, bool)));
+
+ connect(pan, TQ_SIGNAL(signalHiden()),
+ this, TQ_SLOT(slotPanIconHiden()));
+
+ TQPoint g = mapToGlobal(viewport()->pos());
+ g.setX(g.x()+ viewport()->size().width());
+ g.setY(g.y()+ viewport()->size().height());
+ d->panIconPopup->popup(TQPoint(g.x() - d->panIconPopup->width(),
+ g.y() - d->panIconPopup->height()));
+
+ pan->setCursorToLocalRegionSelectionCenter();
+}
+
+void ImagePreviewView::slotPanIconHiden()
+{
+ d->cornerButton->blockSignals(true);
+ d->cornerButton->animateClick();
+ d->cornerButton->blockSignals(false);
+}
+
+void ImagePreviewView::slotPanIconSelectionMoved(const TQRect& r, bool b)
+{
+ setContentsPos((int)(r.x()*zoomFactor()), (int)(r.y()*zoomFactor()));
+
+ if (b)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ slotPanIconHiden();
+ }
+}
+
+void ImagePreviewView::zoomFactorChanged(double zoom)
+{
+ updateScrollBars();
+
+ if (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())
+ d->cornerButton->show();
+ else
+ d->cornerButton->hide();
+
+ PreviewWidget::zoomFactorChanged(zoom);
+}
+
+void ImagePreviewView::resizeEvent(TQResizeEvent* e)
+{
+ if (!e) return;
+
+ TQScrollView::resizeEvent(e);
+
+ if (!d->imageInfo)
+ d->cornerButton->hide();
+
+ updateZoomAndSize(false);
+}
+
+void ImagePreviewView::updateZoomAndSize(bool alwaysFitToWindow)
+{
+ // Set zoom for fit-in-window as minimum, but dont scale up images
+ // that are smaller than the available space, only scale down.
+ double zoom = calcAutoZoomFactor(ZoomInOnly);
+ setZoomMin(zoom);
+ setZoomMax(zoom*12.0);
+
+ // Is currently the zoom factor set to fit to window? Then set it again to fit the new size.
+ if (zoomFactor() < zoom || alwaysFitToWindow || zoomFactor() == d->currentFitWindowZoom)
+ {
+ setZoomFactor(zoom);
+ }
+
+ // store which zoom factor means it is fit to window
+ d->currentFitWindowZoom = zoom;
+
+ updateContentsSize();
+}
+
+int ImagePreviewView::previewWidth()
+{
+ return d->preview.width();
+}
+
+int ImagePreviewView::previewHeight()
+{
+ return d->preview.height();
+}
+
+bool ImagePreviewView::previewIsNull()
+{
+ return d->preview.isNull();
+}
+
+void ImagePreviewView::resetPreview()
+{
+ d->preview = DImg();
+ d->path = TQString();
+ d->imageInfo = 0;
+
+ updateZoomAndSize(true);
+ emit signalPreviewLoaded(false);
+}
+
+void ImagePreviewView::paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh)
+{
+ DImg img = d->preview.smoothScaleSection(sx, sy, sw, sh, tileSize(), tileSize());
+ TQPixmap pix2 = img.convertToPixmap();
+ bitBlt(pix, 0, 0, &pix2, 0, 0);
+}
+
+} // NameSpace Digikam
diff --git a/src/digikam/imagepreviewview.h b/src/digikam/imagepreviewview.h
new file mode 100644
index 00000000..f0058806
--- /dev/null
+++ b/src/digikam/imagepreviewview.h
@@ -0,0 +1,115 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-21-12
+ * Description : a embedded view to show the image preview widget.
+ *
+ * Copyright (C) 2006-2009 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPREVIEWVIEW_H
+#define IMAGEPREVIEWVIEW_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqimage.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "previewwidget.h"
+#include "digikam_export.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+
+class AlbumWidgetStack;
+class LoadingDescription;
+class ImageInfo;
+class ImagePreviewViewPriv;
+
+class DIGIKAM_EXPORT ImagePreviewView : public PreviewWidget
+{
+
+TQ_OBJECT
+
+public:
+
+ ImagePreviewView(AlbumWidgetStack *parent=0);
+ ~ImagePreviewView();
+
+ void setLoadFullImageSize(bool b);
+
+ void setImage(const DImg& image);
+ DImg& getImage() const;
+
+ void setImageInfo(ImageInfo* info=0, ImageInfo *previous=0, ImageInfo *next=0);
+ ImageInfo* getImageInfo() const;
+
+ void reload();
+ void setImagePath(const TQString& path=TQString());
+ void setPreviousNextPaths(const TQString& previous, const TQString &next);
+
+signals:
+
+ void signalNextItem();
+ void signalPrevItem();
+ void signalDeleteItem();
+ void signalEditItem();
+ void signalPreviewLoaded(bool success);
+ void signalBack2Album();
+ void signalSlideShow();
+ void signalInsert2LightTable();
+
+protected:
+
+ void resizeEvent(TQResizeEvent* e);
+
+private slots:
+
+ void slotGotImagePreview(const LoadingDescription &loadingDescription, const DImg &image);
+ void slotNextPreload();
+ void slotContextMenu();
+ void slotAssignTag(int tagID);
+ void slotRemoveTag(int tagID);
+ void slotAssignRating(int rating);
+ void slotThemeChanged();
+ void slotCornerButtonPressed();
+ void slotPanIconSelectionMoved(const TQRect&, bool);
+ void slotPanIconHiden();
+
+private:
+
+ int previewWidth();
+ int previewHeight();
+ bool previewIsNull();
+ void resetPreview();
+ void zoomFactorChanged(double zoom);
+ void updateZoomAndSize(bool alwaysFitToWindow);
+ inline void paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh);
+
+private:
+
+ ImagePreviewViewPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEPREVIEWVIEW_H */
diff --git a/src/digikam/kdateedit.cpp b/src/digikam/kdateedit.cpp
new file mode 100644
index 00000000..1cf4ec42
--- /dev/null
+++ b/src/digikam/kdateedit.cpp
@@ -0,0 +1,378 @@
+/*
+ This file is part of libtdepim.
+
+ 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
+*/
+
+// TQt includes.
+
+#include <tqapplication.h>
+#include <tqlineedit.h>
+#include <tqlistbox.h>
+#include <tqvalidator.h>
+
+// KDE includes.
+
+#include <kcalendarsystem.h>
+#include <tdeglobal.h>
+#include <tdeglobalsettings.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "kdateedit.h"
+#include "kdateedit.moc"
+
+namespace Digikam
+{
+
+class DateValidator : public TQValidator
+{
+ public:
+ DateValidator( const TQStringList &keywords, TQWidget* parent, const char* name = 0 )
+ : TQValidator( parent, name ), mKeywords( keywords )
+ {}
+
+ virtual State validate( TQString &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;
+ TDEGlobal::locale()->readDate( str, &ok );
+ if ( ok )
+ return Acceptable;
+ else
+ return Intermediate;
+ }
+
+ private:
+ TQStringList mKeywords;
+};
+
+KDateEdit::KDateEdit( TQWidget *parent, const char *name )
+ : TQComboBox( true, parent, name ),
+ mReadOnly( false ),
+ mDiscardNextMousePress( false )
+{
+ // need at least one entry for popup to work
+ setMaxCount( 1 );
+
+ mDate = TQDate::currentDate();
+ TQString today = TDEGlobal::locale()->formatDate( mDate, true );
+
+ insertItem( today );
+ setCurrentItem( 0 );
+ changeItem( today, 0 );
+ setMinimumSize( sizeHint() );
+
+ connect( lineEdit(), TQ_SIGNAL( returnPressed() ),
+ this, TQ_SLOT( lineEnterPressed() ) );
+ connect( this, TQ_SIGNAL( textChanged( const TQString& ) ),
+ TQ_SLOT( slotTextChanged( const TQString& ) ) );
+
+ mPopup = new KDatePickerPopup( KDatePickerPopup::DatePicker | KDatePickerPopup::Words );
+ mPopup->hide();
+ mPopup->installEventFilter( this );
+
+ connect( mPopup, TQ_SIGNAL( dateChanged( TQDate ) ),
+ TQ_SLOT( dateSelected( TQDate ) ) );
+
+ // 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 TQDate& date )
+{
+ assignDate( date );
+ updateView();
+}
+
+TQDate 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;
+
+ TQRect desk = TDEGlobalSettings::desktopGeometry( this );
+
+ TQPoint popupPoint = mapToGlobal( TQPoint( 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( TQDate::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.
+ TQDate date = parseDate();
+ assignDate( date );
+ updateView();
+ // Now, simulate an Enter to unpress it
+ TQListBox *lb = listBox();
+ if (lb) {
+ lb->setCurrentItem(0);
+ TQKeyEvent* keyEvent = new TQKeyEvent(TQEvent::KeyPress, TQt::Key_Enter, 0, 0);
+ TQApplication::postEvent(lb, keyEvent);
+ }
+}
+
+void KDateEdit::dateSelected( TQDate date )
+{
+ if (assignDate( date ) ) {
+ updateView();
+ emit dateChanged( date );
+
+ if ( date.isValid() ) {
+ mPopup->hide();
+ }
+ }
+}
+
+void KDateEdit::dateEntered( TQDate date )
+{
+ if (assignDate( date ) ) {
+ updateView();
+ emit dateChanged( date );
+ }
+}
+
+void KDateEdit::lineEnterPressed()
+{
+ bool replaced = false;
+
+ TQDate date = parseDate( &replaced );
+
+ if (assignDate( date ) ) {
+ if ( replaced )
+ updateView();
+
+ emit dateChanged( date );
+ }
+}
+
+TQDate KDateEdit::parseDate( bool *replaced ) const
+{
+ TQString text = currentText();
+ TQDate result;
+
+ if ( replaced )
+ (*replaced) = false;
+
+ if ( text.isEmpty() )
+ result = TQDate();
+ else if ( mKeywordMap.contains( text.lower() ) ) {
+ TQDate today = TQDate::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 = TDEGlobal::locale()->readDate( text );
+ }
+
+ return result;
+}
+
+bool KDateEdit::eventFilter( TQObject *object, TQEvent *event )
+{
+ if ( object == lineEdit() ) {
+ // We only process the focus out event if the text has changed
+ // since we got focus
+ if ( (event->type() == TQEvent::FocusOut) && mTextChanged ) {
+ lineEnterPressed();
+ mTextChanged = false;
+ } else if ( event->type() == TQEvent::KeyPress ) {
+ // Up and down arrow keys step the date
+ TQKeyEvent* keyEvent = (TQKeyEvent*)event;
+
+ if ( keyEvent->key() == TQt::Key_Return ) {
+ lineEnterPressed();
+ return true;
+ }
+
+ int step = 0;
+ if ( keyEvent->key() == TQt::Key_Up )
+ step = 1;
+ else if ( keyEvent->key() == TQt::Key_Down )
+ step = -1;
+ if ( step && !mReadOnly ) {
+ TQDate 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 TQEvent::MouseButtonDblClick:
+ case TQEvent::MouseButtonPress: {
+ TQMouseEvent *mouseEvent = (TQMouseEvent*)event;
+ if ( !mPopup->rect().contains( mouseEvent->pos() ) ) {
+ TQPoint globalPos = mPopup->mapToGlobal( mouseEvent->pos() );
+ if ( TQApplication::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( TQMouseEvent *event )
+{
+ if ( event->button() == TQt::LeftButton && mDiscardNextMousePress ) {
+ mDiscardNextMousePress = false;
+ return;
+ }
+
+ TQComboBox::mousePressEvent( event );
+}
+
+void KDateEdit::slotTextChanged( const TQString& )
+{
+ TQDate 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 );
+
+ TQString dayName;
+ for ( int i = 1; i <= 7; ++i ) {
+ dayName = TDEGlobal::locale()->calendar()->weekDayName( i ).lower();
+ mKeywordMap.insert( dayName, i + 100 );
+ }
+}
+
+bool KDateEdit::assignDate( const TQDate& date )
+{
+ mDate = date;
+ mTextChanged = false;
+ return true;
+}
+
+void KDateEdit::updateView()
+{
+ TQString dateString;
+ if ( mDate.isValid() )
+ dateString = TDEGlobal::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 );
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/kdateedit.h b/src/digikam/kdateedit.h
new file mode 100644
index 00000000..92444823
--- /dev/null
+++ b/src/digikam/kdateedit.h
@@ -0,0 +1,150 @@
+/*
+ This file is part of libtdepim.
+
+ 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
+
+// TQt includes.
+
+#include <tqcombobox.h>
+#include <tqdatetime.h>
+#include <tqmap.h>
+
+// KDE includes.
+
+#include "kdatepickerpopup.h"
+
+namespace Digikam
+{
+
+/**
+ 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 TQComboBox
+{
+ TQ_OBJECT
+
+ public:
+
+ KDateEdit( TQWidget *parent = 0, const char *name = 0 );
+ virtual ~KDateEdit();
+
+ /**
+ @return The date entered. This date could be invalid,
+ you have to check validity yourself.
+ */
+ TQDate 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 TQDate &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 TQDate &date );
+
+ protected slots:
+
+ void lineEnterPressed();
+ void slotTextChanged( const TQString& );
+ void dateEntered( TQDate );
+ void dateSelected( TQDate );
+
+ protected:
+
+ virtual bool eventFilter( TQObject*, TQEvent* );
+ virtual void mousePressEvent( TQMouseEvent* );
+
+ /**
+ 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
+ TQDate::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 TQDate &date );
+
+ /**
+ Fills the keyword map. Reimplement it if you want additional
+ keywords.
+ */
+ void setupKeywords();
+
+ private:
+
+ TQDate parseDate( bool* = 0 ) const;
+ void updateView();
+
+ KDatePickerPopup *mPopup;
+
+ TQDate mDate;
+ bool mReadOnly;
+ bool mTextChanged;
+ bool mDiscardNextMousePress;
+
+ TQMap<TQString, int> mKeywordMap;
+};
+
+} // namespace Digikam
+
+#endif // KDATEEDIT_H
diff --git a/src/digikam/kdatepickerpopup.cpp b/src/digikam/kdatepickerpopup.cpp
new file mode 100644
index 00000000..b4cb8bb2
--- /dev/null
+++ b/src/digikam/kdatepickerpopup.cpp
@@ -0,0 +1,160 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-04-21
+ * Description : a menu widget to pick a date.
+ * this widget come from libtdepim.
+ *
+ * Copyright (C) 2004 Bram Schoenmakers <bramschoenmakers@kde.nl>
+ * Copyright (C) 2006 Mikolaj Machowski <mikmach@wp.pl>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqpopupmenu.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "kdatepickerpopup.h"
+#include "kdatepickerpopup.moc"
+
+namespace Digikam
+{
+
+KDatePickerPopup::KDatePickerPopup(int items, const TQDate &date, TQWidget *parent,
+ const char *name)
+ : TQPopupMenu( parent, name )
+{
+ mItems = items;
+ mDatePicker = new KDatePicker( this );
+ mDatePicker->setCloseButton( false );
+
+ connect( mDatePicker, TQ_SIGNAL( dateEntered( TQDate ) ),
+ this, TQ_SLOT( slotDateChanged( TQDate ) ) );
+
+ connect( mDatePicker, TQ_SIGNAL( dateSelected( TQDate ) ),
+ this, TQ_SLOT( slotDateChanged( TQDate ) ) );
+
+ 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, TQ_SLOT( slotToday() ) );
+ insertItem( i18n("Y&esterday"), this, TQ_SLOT( slotYesterday() ) );
+ insertItem( i18n("Last &Monday"), this, TQ_SLOT( slotPrevMonday() ) );
+ insertItem( i18n("Last &Friday"), this, TQ_SLOT( slotPrevFriday() ) );
+ insertItem( i18n("Last &Week"), this, TQ_SLOT( slotPrevWeek() ) );
+ insertItem( i18n("Last M&onth"), this, TQ_SLOT( slotPrevMonth() ) );
+
+ if ( mItems & NoDate )
+ insertSeparator();
+ }
+
+ if ( mItems & NoDate )
+ insertItem( i18n("No Date"), this, TQ_SLOT( slotNoDate() ) );
+}
+
+KDatePicker *KDatePickerPopup::datePicker() const
+{
+ return mDatePicker;
+}
+
+void KDatePickerPopup::setDate( const TQDate &date )
+{
+ mDatePicker->setDate( date );
+}
+
+#if 0
+void KDatePickerPopup::setItems( int items )
+{
+ mItems = items;
+ buildMenu();
+}
+#endif
+
+void KDatePickerPopup::slotDateChanged( TQDate date )
+{
+ emit dateChanged( date );
+ hide();
+}
+
+void KDatePickerPopup::slotToday()
+{
+ emit dateChanged( TQDate::currentDate() );
+}
+
+void KDatePickerPopup::slotYesterday()
+{
+ emit dateChanged( TQDate::currentDate().addDays( -1 ) );
+}
+
+void KDatePickerPopup::slotPrevFriday()
+{
+ TQDate date = TQDate::currentDate();
+ int day = date.dayOfWeek();
+ if ( day < 6 )
+ date = date.addDays( 5 - 7 - day );
+ else
+ date = date.addDays( 5 - day );
+
+ emit dateChanged( date );
+}
+
+void KDatePickerPopup::slotPrevMonday()
+{
+ TQDate date = TQDate::currentDate();
+ emit dateChanged( date.addDays( 1 - date.dayOfWeek() ) );
+}
+
+void KDatePickerPopup::slotNoDate()
+{
+ emit dateChanged( TQDate() );
+}
+
+void KDatePickerPopup::slotPrevWeek()
+{
+ emit dateChanged( TQDate::currentDate().addDays( -7 ) );
+}
+
+void KDatePickerPopup::slotPrevMonth()
+{
+ emit dateChanged( TQDate::currentDate().addMonths( -1 ) );
+}
+
+} // namespace Digikam
+
+
diff --git a/src/digikam/kdatepickerpopup.h b/src/digikam/kdatepickerpopup.h
new file mode 100644
index 00000000..e343594e
--- /dev/null
+++ b/src/digikam/kdatepickerpopup.h
@@ -0,0 +1,129 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-04-21
+ * Description : a menu widget to pick a date.
+ * this widget come from libtdepim.
+ *
+ * Copyright (C) 2004 Bram Schoenmakers <bramschoenmakers@kde.nl>
+ * Copyright (C) 2006 Mikolaj Machowski <mikmach@wp.pl>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef KDATEPICKERPOPUP_H
+#define KDATEPICKERPOPUP_H
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqpopupmenu.h>
+
+// KDE includes.
+
+#include <kdatepicker.h>
+
+namespace Digikam
+{
+
+/**
+ @short This menu helps the user to select a past date quickly.
+
+ This menu helps the user to select a past date quickly. It offers various ways of selecting, e.g. with a KDatePicker or with words like "Yesterday".
+
+ The available items are:
+
+ @li NoDate: A menu-item with "No Date". If chosen, the datepicker will emit a null TQDate.
+ @li DatePicker: Show a KDatePicker-widget.
+ @li Words: Show items like "Today", "Yesterday" or "Previous Week".
+
+ When supplying multiple items, separate each item with a bitwise OR.
+
+ @author Bram Schoenmakers <bram_s@softhome.net>, Mikolaj Machowski <mikmach@wp.pl>
+*/
+class KDatePickerPopup: public TQPopupMenu
+{
+ TQ_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 TQDate &date = TQDate::currentDate(),
+ TQWidget *parent = 0, const char *name = 0);
+
+ /**
+ @return A pointer to the private variable mDatePicker, an instance of
+ KDatePicker.
+ */
+ KDatePicker *datePicker() const;
+
+ void setDate( const TQDate &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 ( TQDate );
+
+protected slots:
+
+ void slotDateChanged(TQDate);
+ void slotToday();
+ void slotYesterday();
+ void slotPrevMonday();
+ void slotPrevFriday();
+ void slotPrevWeek();
+ void slotPrevMonth();
+ void slotNoDate();
+
+private:
+
+ void buildMenu();
+
+private:
+
+ int mItems;
+
+ KDatePicker *mDatePicker;
+};
+
+} // namespace Digikam
+
+#endif // KDATEPICKERPOPUP_H
diff --git a/src/digikam/kdatetimeedit.cpp b/src/digikam/kdatetimeedit.cpp
new file mode 100644
index 00000000..e78c5cac
--- /dev/null
+++ b/src/digikam/kdatetimeedit.cpp
@@ -0,0 +1,74 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a widget to edit time stamp.
+ *
+ * Copyright (C) 2005 Tom Albers <tomalbers@kde.nl>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatetimeedit.h>
+
+// KDE includes.
+
+#include "kdateedit.h"
+#include "kdatetimeedit.h"
+#include "kdatetimeedit.moc"
+
+namespace Digikam
+{
+
+KDateTimeEdit::KDateTimeEdit(TQWidget *parent, const char *name)
+ : TQHBox(parent, name)
+{
+ m_datePopUp = new KDateEdit(this, "datepopup");
+ m_timePopUp = new TQTimeEdit(TQTime::currentTime(), this);
+
+ connect(m_datePopUp, TQ_SIGNAL(dateChanged(const TQDate& )),
+ TQ_SLOT(slotDateTimeChanged()) );
+ connect(m_timePopUp, TQ_SIGNAL(valueChanged(const TQTime& )),
+ TQ_SLOT(slotDateTimeChanged()) );
+}
+
+KDateTimeEdit::~KDateTimeEdit()
+{
+ delete m_datePopUp;
+ m_datePopUp = 0;
+
+ delete m_timePopUp;
+ m_timePopUp = 0;
+}
+
+TQDateTime KDateTimeEdit::dateTime()
+{
+ return TQDateTime(m_datePopUp->date(), m_timePopUp->time());
+}
+
+void KDateTimeEdit::setDateTime(const TQDateTime dateTime)
+{
+ m_datePopUp->setDate(dateTime.date());
+ m_timePopUp->setTime(dateTime.time());
+}
+
+void KDateTimeEdit::slotDateTimeChanged()
+{
+ emit dateTimeChanged(dateTime());
+}
+
+} // namespace Digikam
diff --git a/src/digikam/kdatetimeedit.h b/src/digikam/kdatetimeedit.h
new file mode 100644
index 00000000..ac0575c6
--- /dev/null
+++ b/src/digikam/kdatetimeedit.h
@@ -0,0 +1,98 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a widget to edit time stamp.
+ *
+ * Copyright (C) 2005 Tom Albers <tomalbers@kde.nl>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+/** @file kdatetimeedit.h **/
+
+#ifndef KDATETIMEEDIT_H
+#define KDATETIMEEDIT_H
+
+// TQt includes.
+
+#include <tqhbox.h>
+
+class TQTimeEdit;
+
+namespace Digikam
+{
+
+class KDateEdit;
+
+/**
+ * @class KDateTimeEdit
+ * This class is basically the same as the KDateTime class
+ * with the exception that a TQTimeEdit is placed directly
+ * besides it.
+ *
+ * @image html kdatetimeedit.png "This is how it looks"
+ * @author Tom Albers
+ */
+class KDateTimeEdit : public TQHBox
+{
+ TQ_OBJECT
+
+public:
+
+ /**
+ * constructor
+ * @param parent the parent widget
+ * @param name the name of the widget
+ */
+ KDateTimeEdit(TQWidget *parent, const char *name);
+
+ /**
+ * destructor
+ */
+ ~KDateTimeEdit();
+
+ /**
+ * returns the date and time
+ * @return a TQDateTime with the currently chosen date and time
+ */
+ TQDateTime dateTime();
+
+ /**
+ * Sets the date and the time of this widget.
+ */
+ void setDateTime(const TQDateTime dateTime);
+
+signals:
+
+ /**
+ * This signal is emitted whenever the user modifies the date or time.
+ * The passed date and time can be invalid.
+ */
+ void dateTimeChanged( const TQDateTime &dateTime );
+
+private:
+
+ KDateEdit* m_datePopUp;
+ TQTimeEdit* m_timePopUp;
+
+private slots:
+
+ void slotDateTimeChanged();
+};
+
+} // namespace Digikam
+
+#endif // KDATETIMEEDIT_H
diff --git a/src/digikam/kipiinterface.cpp b/src/digikam/kipiinterface.cpp
new file mode 100644
index 00000000..aaf012e3
--- /dev/null
+++ b/src/digikam/kipiinterface.cpp
@@ -0,0 +1,724 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-02
+ * Description : digiKam kipi library interface.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2005 by Ralf Holzer <ralf at well.com>
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// C Ansi includes
+
+extern "C"
+{
+#include <sys/types.h>
+#include <utime.h>
+}
+
+// TQt includes.
+
+#include <tqdir.h>
+#include <tqdatetime.h>
+#include <tqfileinfo.h>
+#include <tqfile.h>
+#include <tqregexp.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdefilemetainfo.h>
+#include <tdeio/netaccess.h>
+
+// libKipi includes.
+
+#include <libkipi/version.h>
+
+// Local includes.
+
+#include "constants.h"
+#include "ddebug.h"
+#include "albummanager.h"
+#include "albumitemhandler.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albumsettings.h"
+#include "dmetadata.h"
+#include "imageattributeswatch.h"
+#include "kipiinterface.h"
+#include "kipiinterface.moc"
+
+namespace Digikam
+{
+
+//-- Image Info ------------------------------------------------------------------
+
+DigikamImageInfo::DigikamImageInfo( KIPI::Interface* interface, const KURL& url )
+ : KIPI::ImageInfoShared( interface, url ), palbum_(0)
+{
+}
+
+DigikamImageInfo::~DigikamImageInfo()
+{
+}
+
+PAlbum* DigikamImageInfo::parentAlbum()
+{
+ if (!palbum_)
+ {
+ KURL u(_url.directory());
+ palbum_ = AlbumManager::instance()->findPAlbum(u);
+ }
+ return palbum_;
+}
+
+TQString DigikamImageInfo::title()
+{
+ return _url.fileName();
+}
+
+TQString DigikamImageInfo::description()
+{
+ PAlbum* p = parentAlbum();
+
+ if (p)
+ {
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ return db->getItemCaption(p->id(), _url.fileName());
+ }
+
+ return TQString();
+}
+
+void DigikamImageInfo::setTitle( const TQString& newName )
+{
+ // Here we get informed that a plugin has renamed the file
+
+ PAlbum* p = parentAlbum();
+
+ if ( p && !newName.isEmpty() )
+ {
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ db->moveItem(p->id(), _url.fileName(), p->id(), newName);
+ _url = _url.upURL();
+ _url.addPath(newName);
+ }
+}
+
+void DigikamImageInfo::setDescription( const TQString& description )
+{
+ PAlbum* p = parentAlbum();
+
+ if ( p )
+ {
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQ_LLONG imageId = db->getImageId(p->id(), _url.filename());
+ db->setItemCaption(imageId, description);
+ ImageAttributesWatch::instance()->imageCaptionChanged(imageId);
+
+ // See B.K.O #140133. Do not set here metadata comments of picture.
+ // Only the database comments must be set.
+ }
+}
+
+TQDateTime DigikamImageInfo::time( KIPI::TimeSpec /*spec*/ )
+{
+ PAlbum* p = parentAlbum();
+
+ if (p)
+ {
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ return db->getItemDate(p->id(), _url.fileName());
+ }
+
+ return TQDateTime();
+}
+
+void DigikamImageInfo::setTime(const TQDateTime& time, KIPI::TimeSpec)
+{
+ if ( !time.isValid() )
+ {
+ DWarning() << k_funcinfo << "Invalid datetime specified" << endl;
+ return;
+ }
+
+ PAlbum* p = parentAlbum();
+
+ if ( p )
+ {
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQ_LLONG imageId = db->getImageId(p->id(), _url.filename());
+ db->setItemDate(imageId, time);
+ ImageAttributesWatch::instance()->imageDateChanged(imageId);
+ AlbumManager::instance()->refreshItemHandler( _url );
+ }
+}
+
+void DigikamImageInfo::cloneData( ImageInfoShared* other )
+{
+ setDescription( other->description() );
+ setTime( other->time(KIPI::FromInfo), KIPI::FromInfo );
+ addAttributes( other->attributes() );
+}
+
+TQStringVariantMap DigikamImageInfo::attributes()
+{
+ TQStringVariantMap res;
+
+ PAlbum* p = parentAlbum();
+ if (p)
+ {
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQ_LLONG imageId = db->getImageId(p->id(), _url.filename());
+
+ // Get digiKam Tags list of picture.
+ TQStringList tags = db->getItemTagNames(imageId);
+ res["tags"] = tags;
+
+ // Get digiKam Rating of picture.
+ int rating = db->getItemRating(imageId);
+ res["rating"] = rating;
+
+ // TODO: add here future picture attributes stored by digiKam database
+ }
+ return res;
+}
+
+void DigikamImageInfo::addAttributes(const TQStringVariantMap& res)
+{
+ PAlbum* p = parentAlbum();
+ if (p)
+ {
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQ_LLONG imageId = db->getImageId(p->id(), _url.filename());
+ TQStringVariantMap attributes = res;
+
+ // Set digiKam Tags list of picture.
+ if (attributes.find("tags") != attributes.end())
+ {
+ TQStringList tags = attributes["tags"].asStringList();
+ //TODO
+ }
+
+ // Set digiKam Rating of picture.
+ if (attributes.find("rating") != attributes.end())
+ {
+ int rating = attributes["rating"].asInt();
+ if (rating >= RatingMin && rating <= RatingMax)
+ db->setItemRating(imageId, rating);
+ }
+
+ // TODO: add here future picture attributes stored by digiKam database
+ }
+
+ // To update sidebar content. Some kipi-plugins use this way to refresh sidebar
+ // using an empty TQMap().
+ ImageAttributesWatch::instance()->fileMetadataChanged(_url);
+}
+
+void DigikamImageInfo::clearAttributes()
+{
+ // TODO ! This will used for the futures tags digiKam features.
+}
+
+int DigikamImageInfo::angle()
+{
+ AlbumSettings *settings = AlbumSettings::instance();
+ if (settings->getExifRotate())
+ {
+ DMetadata metadata(_url.path());
+ DMetadata::ImageOrientation orientation = metadata.getImageOrientation();
+
+ switch (orientation)
+ {
+ case DMetadata::ORIENTATION_ROT_180:
+ return 180;
+ case DMetadata::ORIENTATION_ROT_90:
+ case DMetadata::ORIENTATION_ROT_90_HFLIP:
+ case DMetadata::ORIENTATION_ROT_90_VFLIP:
+ return 90;
+ case DMetadata::ORIENTATION_ROT_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+void DigikamImageInfo::setAngle( int )
+{
+ // TODO : add here a DMetadata call (thru Exiv2) to set Exif orientation tag.
+}
+
+//-- Image Collection ------------------------------------------------------------
+
+DigikamImageCollection::DigikamImageCollection( Type tp, Album* album, const TQString& filter )
+ : tp_( tp ), album_(album), imgFilter_(filter)
+{
+ if (!album)
+ {
+ DWarning() << k_funcinfo << "This should not happen. No album specified" << endl;
+ }
+}
+
+DigikamImageCollection::~DigikamImageCollection()
+{
+}
+
+TQString DigikamImageCollection::name()
+{
+ if ( album_->type() == Album::TAG )
+ {
+ return i18n("Tag: %1").arg(album_->title());
+ }
+ else
+ return album_->title();
+}
+
+TQString DigikamImageCollection::category()
+{
+ if ( album_->type() == Album::PHYSICAL )
+ {
+ PAlbum *p = dynamic_cast<PAlbum*>(album_);
+ return p->collection();
+ }
+ else if ( album_->type() == Album::TAG )
+ {
+ TAlbum *p = dynamic_cast<TAlbum*>(album_);
+ return i18n("Tag: %1").arg(p->tagPath());
+ }
+ else
+ return TQString();
+}
+
+TQDate DigikamImageCollection::date()
+{
+ if ( album_->type() == Album::PHYSICAL )
+ {
+ PAlbum *p = dynamic_cast<PAlbum*>(album_);
+ return p->date();
+ }
+ else
+ return TQDate();
+}
+
+TQString DigikamImageCollection::comment()
+{
+ if ( album_->type() == Album::PHYSICAL )
+ {
+ PAlbum *p = dynamic_cast<PAlbum*>(album_);
+ return p->caption();
+ }
+ else
+ return TQString();
+}
+
+static TQValueList<TQRegExp> makeFilterList( const TQString &filter )
+{
+ TQValueList<TQRegExp> regExps;
+ if ( filter.isEmpty() )
+ return regExps;
+
+ TQChar sep( ';' );
+ int i = filter.find( sep, 0 );
+
+ if ( i == -1 && filter.find( ' ', 0 ) != -1 )
+ sep = TQChar( ' ' );
+
+ TQStringList list = TQStringList::split( sep, filter );
+ TQStringList::Iterator it = list.begin();
+
+ while ( it != list.end() )
+ {
+ regExps << TQRegExp( (*it).stripWhiteSpace(), false, true );
+ ++it;
+ }
+
+ return regExps;
+}
+
+static bool matchFilterList( const TQValueList<TQRegExp>& filters, const TQString &fileName )
+{
+ TQValueList<TQRegExp>::ConstIterator rit = filters.begin();
+
+ while ( rit != filters.end() )
+ {
+ if ( (*rit).exactMatch(fileName) )
+ return true;
+ ++rit;
+ }
+
+ return false;
+}
+
+KURL::List DigikamImageCollection::images()
+{
+ switch ( tp_ )
+ {
+ case AllItems:
+ {
+ if (album_->type() == Album::PHYSICAL)
+ {
+ return imagesFromPAlbum(dynamic_cast<PAlbum*>(album_));
+ }
+ else if (album_->type() == Album::TAG)
+ {
+ return imagesFromTAlbum(dynamic_cast<TAlbum*>(album_));
+ }
+ else if (album_->type() == Album::DATE ||
+ album_->type() == Album::SEARCH)
+ {
+ AlbumItemHandler* handler = AlbumManager::instance()->getItemHandler();
+
+ if (handler)
+ {
+ return handler->allItems();
+ }
+
+ return KURL::List();
+ }
+ else
+ {
+ DWarning() << k_funcinfo << "Unknown album type" << endl;
+ return KURL::List();
+ }
+
+ break;
+ }
+ case SelectedItems:
+ {
+ AlbumItemHandler* handler = AlbumManager::instance()->getItemHandler();
+
+ if (handler)
+ {
+ return handler->selectedItems();
+ }
+
+ return KURL::List();
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ // We should never reach here
+ return KURL::List();
+}
+
+/** get the images from the Physical album in database and return the items found */
+
+KURL::List DigikamImageCollection::imagesFromPAlbum(PAlbum* album) const
+{
+ // get the images from the database and return the items found
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+
+ db->beginTransaction();
+
+ TQStringList urls = db->getItemURLsInAlbum(album->id());
+
+ db->commitTransaction();
+
+ KURL::List urlList;
+
+ TQValueList<TQRegExp> regex = makeFilterList(imgFilter_);
+
+ for (TQStringList::iterator it = urls.begin(); it != urls.end(); ++it)
+ {
+ if (matchFilterList(regex, *it))
+ urlList.append(*it);
+ }
+
+ return urlList;
+}
+
+/** get the images from the Tags album in database and return the items found */
+
+KURL::List DigikamImageCollection::imagesFromTAlbum(TAlbum* album) const
+{
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+
+ db->beginTransaction();
+
+ TQStringList urls = db->getItemURLsInTag(album->id());
+
+ db->commitTransaction();
+
+ KURL::List urlList;
+
+ TQValueList<TQRegExp> regex = makeFilterList(imgFilter_);
+
+ for (TQStringList::iterator it = urls.begin(); it != urls.end(); ++it)
+ {
+ if (matchFilterList(regex, *it))
+ urlList.append(*it);
+ }
+
+ return urlList;
+}
+
+KURL DigikamImageCollection::path()
+{
+ if (album_->type() == Album::PHYSICAL)
+ {
+ PAlbum *p = dynamic_cast<PAlbum*>(album_);
+ KURL url;
+ url.setPath(p->folderPath());
+ return url;
+ }
+ else
+ {
+ DWarning() << k_funcinfo << "Requesting kurl from a virtual album" << endl;
+ return TQString();
+ }
+}
+
+KURL DigikamImageCollection::uploadPath()
+{
+ if (album_->type() == Album::PHYSICAL)
+ {
+ PAlbum *p = dynamic_cast<PAlbum*>(album_);
+ KURL url;
+ url.setPath(p->folderPath());
+ return url;
+ }
+ else
+ {
+ DWarning() << k_funcinfo << "Requesting kurl from a virtual album" << endl;
+ return KURL();
+ }
+}
+
+KURL DigikamImageCollection::uploadRoot()
+{
+ return KURL(AlbumManager::instance()->getLibraryPath() + '/');
+}
+
+TQString DigikamImageCollection::uploadRootName()
+{
+ return i18n("My Albums");
+}
+
+bool DigikamImageCollection::isDirectory()
+{
+ if (album_->type() == Album::PHYSICAL)
+ return true;
+ else
+ return false;
+}
+
+bool DigikamImageCollection::operator==(ImageCollectionShared& imgCollection)
+{
+ DigikamImageCollection* thatCollection = static_cast<DigikamImageCollection*>(&imgCollection);
+ return (album_ == thatCollection->album_);
+}
+
+//-- LibKipi interface -----------------------------------------------------------
+
+DigikamKipiInterface::DigikamKipiInterface( TQObject *parent, const char *name)
+ : KIPI::Interface( parent, name )
+{
+ albumManager_ = AlbumManager::instance();
+ albumDB_ = albumManager_->albumDB();
+
+ connect( albumManager_, TQ_SIGNAL( signalAlbumItemsSelected( bool ) ),
+ this, TQ_SLOT( slotSelectionChanged( bool ) ) );
+
+ connect( albumManager_, TQ_SIGNAL( signalAlbumCurrentChanged(Album*) ),
+ this, TQ_SLOT( slotCurrentAlbumChanged(Album*) ) );
+}
+
+DigikamKipiInterface::~DigikamKipiInterface()
+{
+}
+
+KIPI::ImageCollection DigikamKipiInterface::currentAlbum()
+{
+ Album* currAlbum = albumManager_->currentAlbum();
+ if ( currAlbum )
+ {
+ return KIPI::ImageCollection(
+ new DigikamImageCollection( DigikamImageCollection::AllItems,
+ currAlbum, fileExtensions() ) );
+ }
+ else
+ {
+ return KIPI::ImageCollection(0);
+ }
+}
+
+KIPI::ImageCollection DigikamKipiInterface::currentSelection()
+{
+ Album* currAlbum = albumManager_->currentAlbum();
+ if ( currAlbum )
+ {
+ return KIPI::ImageCollection(
+ new DigikamImageCollection( DigikamImageCollection::SelectedItems,
+ currAlbum, fileExtensions() ) );
+ }
+ else
+ {
+ return KIPI::ImageCollection(0);
+ }
+}
+
+TQValueList<KIPI::ImageCollection> DigikamKipiInterface::allAlbums()
+{
+ TQValueList<KIPI::ImageCollection> result;
+
+ TQString fileFilter(fileExtensions());
+
+ AlbumList palbumList = albumManager_->allPAlbums();
+ for ( AlbumList::Iterator it = palbumList.begin();
+ it != palbumList.end(); ++it )
+ {
+ // don't add the root album
+ if ((*it)->isRoot())
+ continue;
+
+ DigikamImageCollection* col = new DigikamImageCollection( DigikamImageCollection::AllItems,
+ *it, fileFilter );
+ result.append( KIPI::ImageCollection( col ) );
+ }
+
+ AlbumList talbumList = albumManager_->allTAlbums();
+ for ( AlbumList::Iterator it = talbumList.begin();
+ it != talbumList.end(); ++it )
+ {
+ // don't add the root album
+ if ((*it)->isRoot())
+ continue;
+
+ DigikamImageCollection* col = new DigikamImageCollection( DigikamImageCollection::AllItems,
+ *it, fileFilter );
+ result.append( KIPI::ImageCollection( col ) );
+ }
+
+ return result;
+}
+
+KIPI::ImageInfo DigikamKipiInterface::info( const KURL& url )
+{
+ return KIPI::ImageInfo( new DigikamImageInfo( this, url ) );
+}
+
+void DigikamKipiInterface::refreshImages( const KURL::List& urls )
+{
+ KURL::List ulist = urls;
+
+ // Re-scan metadata from pictures. This way will update Metadata sidebar and database.
+ for ( KURL::List::Iterator it = ulist.begin() ; it != ulist.end() ; ++it )
+ ImageAttributesWatch::instance()->fileMetadataChanged(*it);
+
+ // Refresh preview.
+ albumManager_->refreshItemHandler(urls);
+}
+
+int DigikamKipiInterface::features() const
+{
+ return (
+
+// HostSupportsTags feature require libkipi version > 1.4.0
+#if KIPI_VERSION>0x000104
+ KIPI::HostSupportsTags |
+#endif
+ KIPI::ImagesHasComments | KIPI::AcceptNewImages |
+ KIPI::AlbumsHaveComments | KIPI::ImageTitlesWritable |
+ KIPI::ImagesHasTime | KIPI::AlbumsHaveCategory |
+ KIPI::AlbumsHaveCreationDate | KIPI::AlbumsUseFirstImagePreview
+ );
+}
+
+bool DigikamKipiInterface::addImage( const KURL& url, TQString& errmsg )
+{
+ // Nota : All copy/move operations are processed by the plugins.
+
+ if ( url.isValid() == false )
+ {
+ errmsg = i18n("Target URL %1 is not valid.").arg(url.path());
+ return false;
+ }
+
+ PAlbum *targetAlbum = albumManager_->findPAlbum(url.directory());
+
+ if ( !targetAlbum )
+ {
+ errmsg = i18n("Target album is not in the album library.");
+ return false;
+ }
+
+ albumManager_->refreshItemHandler( url );
+
+ return true;
+}
+
+void DigikamKipiInterface::delImage( const KURL& url )
+{
+ KURL rootURL(albumManager_->getLibraryPath());
+ if ( !rootURL.isParentOf(url) )
+ {
+ DWarning() << k_funcinfo << "URL not in the album library" << endl;
+ }
+
+ // Is there a PAlbum for this url
+
+ PAlbum *palbum = albumManager_->findPAlbum( KURL(url.directory()) );
+
+ if ( palbum )
+ {
+ // delete the item from the database
+ albumDB_->deleteItem( palbum->id(), url.fileName() );
+ }
+ else
+ {
+ DWarning() << k_funcinfo << "Cannot find Parent album in album library" << endl;
+ }
+}
+
+void DigikamKipiInterface::slotSelectionChanged( bool b )
+{
+ emit selectionChanged( b );
+}
+
+void DigikamKipiInterface::slotCurrentAlbumChanged( Album *album )
+{
+ emit currentAlbumChanged( album != 0 );
+}
+
+TQString DigikamKipiInterface::fileExtensions()
+{
+ // do not save this into a local variable, as this
+ // might change in the main app
+
+ AlbumSettings* s = AlbumSettings::instance();
+ return (s->getImageFileFilter() + ' ' +
+ s->getMovieFileFilter() + ' ' +
+ s->getAudioFileFilter() + ' ' +
+ s->getRawFileFilter());
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/kipiinterface.h b/src/digikam/kipiinterface.h
new file mode 100644
index 00000000..b2974ece
--- /dev/null
+++ b/src/digikam/kipiinterface.h
@@ -0,0 +1,187 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-02
+ * Description : digiKam kipi library interface.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2005 by Ralf Holzer <ralf at well.com>
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIGIKAM_KIPIINTERFACE_H
+#define DIGIKAM_KIPIINTERFACE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// TQt includes.
+
+#include <tqvaluelist.h>
+#include <tqstring.h>
+#include <tqmap.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <tdeio/job.h>
+
+// libKipi includes.
+
+#include <libkipi/interface.h>
+#include <libkipi/imagecollection.h>
+#include <libkipi/imageinfo.h>
+#include <libkipi/imageinfoshared.h>
+#include <libkipi/imagecollectionshared.h>
+
+class TQDateTime;
+
+namespace KIPI
+{
+class Interface;
+class ImageCollection;
+class ImageInfo;
+}
+
+namespace Digikam
+{
+
+class AlbumManager;
+class Album;
+class PAlbum;
+class TAlbum;
+class AlbumDB;
+class AlbumSettings;
+
+/** DigikamImageInfo: class to get/set image information/properties in a digiKam album. */
+
+class DigikamImageInfo : public KIPI::ImageInfoShared
+{
+public:
+
+ DigikamImageInfo( KIPI::Interface* interface, const KURL& url );
+ ~DigikamImageInfo();
+
+ virtual TQString title();
+ virtual void setTitle( const TQString& );
+
+ virtual TQString description();
+ virtual void setDescription( const TQString& );
+
+ virtual void cloneData( ImageInfoShared* other );
+
+ virtual TQDateTime time( KIPI::TimeSpec spec );
+ virtual void setTime( const TQDateTime& time, KIPI::TimeSpec spec = KIPI::FromInfo );
+
+ virtual TQStringVariantMap attributes();
+ virtual void addAttributes(const TQStringVariantMap& res);
+ virtual void clearAttributes();
+
+ virtual int angle();
+ virtual void setAngle( int angle );
+
+private:
+
+ PAlbum *parentAlbum();
+
+private:
+
+ PAlbum *palbum_;
+};
+
+
+/** DigikamImageCollection: class to get/set image collection information/properties in a digiKam
+ album database. */
+
+class DigikamImageCollection : public KIPI::ImageCollectionShared
+{
+
+public:
+
+ enum Type
+ {
+ AllItems,
+ SelectedItems
+ };
+
+public:
+
+ DigikamImageCollection( Type tp, Album *album, const TQString& filter );
+ ~DigikamImageCollection();
+
+ virtual TQString name();
+ virtual TQString comment();
+ virtual TQString category();
+ virtual TQDate date();
+ virtual KURL::List images();
+ virtual KURL path();
+ virtual KURL uploadPath();
+ virtual KURL uploadRoot();
+ virtual TQString uploadRootName();
+ virtual bool isDirectory();
+ virtual bool operator==(ImageCollectionShared&);
+
+private:
+
+ KURL::List imagesFromPAlbum(PAlbum* album) const;
+ KURL::List imagesFromTAlbum(TAlbum* album) const;
+
+private:
+
+ Type tp_;
+ Album *album_;
+ TQString imgFilter_;
+};
+
+
+/** DigikamKipiInterface: class to interface digiKam with kipi library. */
+
+class DigikamKipiInterface : public KIPI::Interface
+{
+ TQ_OBJECT
+
+public:
+
+ DigikamKipiInterface( TQObject *parent, const char *name=0);
+ ~DigikamKipiInterface();
+
+ virtual KIPI::ImageCollection currentAlbum();
+ virtual KIPI::ImageCollection currentSelection();
+ virtual TQValueList<KIPI::ImageCollection> allAlbums();
+ virtual KIPI::ImageInfo info( const KURL& );
+ virtual bool addImage( const KURL&, TQString& errmsg );
+ virtual void delImage( const KURL& );
+ virtual void refreshImages( const KURL::List& urls );
+ virtual int features() const;
+ virtual TQString fileExtensions();
+
+public slots:
+
+ void slotSelectionChanged( bool b );
+ void slotCurrentAlbumChanged( Album *palbum );
+
+private:
+
+ AlbumManager *albumManager_;
+ AlbumDB *albumDB_;
+};
+
+} // namespace Digikam
+
+#endif // DIGIKAM_KIPIINTERFACE_H
+
diff --git a/src/digikam/main.cpp b/src/digikam/main.cpp
new file mode 100644
index 00000000..77609197
--- /dev/null
+++ b/src/digikam/main.cpp
@@ -0,0 +1,138 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2002-07-28
+ * Description : main program from digiKam
+ *
+ * Copyright (C) 2002-2006 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ * Copyright (C) 2002-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqstringlist.h>
+#include <tqfileinfo.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <tdecmdlineargs.h>
+#include <tdeapplication.h>
+#include <tdeaboutdata.h>
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeglobal.h>
+#include <ktip.h>
+#include <tdeversion.h>
+#include <tdemessagebox.h>
+
+// KIPI includes.
+
+#include <libkipi/version.h>
+#include <libkipi/interface.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "albumdb.h"
+#include "digikamapp.h"
+#include "digikamfirstrun.h"
+
+static TDECmdLineOptions options[] =
+{
+ { "detect-camera", I18N_NOOP("Automatically detect and open camera"), 0 },
+ { "download-from <path>", I18N_NOOP("Open camera dialog at <path>"), 0 },
+ TDECmdLineLastOption
+};
+
+int main(int argc, char *argv[])
+{
+ TQString libInfo = Digikam::libraryInfo();
+
+ TQString description = Digikam::digiKamDescription();
+
+ TDEAboutData aboutData( "digikam",
+ I18N_NOOP("digiKam"),
+ digikam_version,
+ description.latin1(),
+ TDEAboutData::License_GPL,
+ Digikam::copyright(),
+ 0,
+ Digikam::webProjectUrl());
+
+ aboutData.setOtherText(libInfo.latin1());
+
+ Digikam::authorsRegistration(aboutData);
+
+ TDECmdLineArgs::init( argc, argv, &aboutData );
+ TDECmdLineArgs::addCmdLineOptions( options );
+
+ TDEApplication app;
+
+ TDEConfig* config = TDEGlobal::config();
+ config->setGroup("General Settings");
+ TQString version = config->readEntry("Version");
+
+ config->setGroup("Album Settings");
+ TQString albumPath = config->readPathEntry("Album Path");
+ TQFileInfo dirInfo(albumPath);
+
+ // version 0.6 was the version when the new Albums Library
+ // storage was implemented
+
+ if (version.startsWith("0.5") ||
+ !dirInfo.exists() ||
+ !dirInfo.isDir())
+ {
+ // Run the first run
+ Digikam::DigikamFirstRun *firstRun = new Digikam::DigikamFirstRun(config);
+ app.setMainWidget(firstRun);
+ firstRun->show();
+ return app.exec();
+ }
+
+ Digikam::DigikamApp *digikam = new Digikam::DigikamApp();
+
+ app.setMainWidget(digikam);
+ digikam->show();
+
+ TDECmdLineArgs* args = TDECmdLineArgs::parsedArgs();
+ if (args && args->isSet("detect-camera"))
+ digikam->autoDetect();
+ else if (args && args->isSet("download-from"))
+ digikam->downloadFrom(args->getOption("download-from"));
+
+#if KDE_IS_VERSION(3,2,0)
+ TQStringList tipsFiles;
+ tipsFiles.append("digikam/tips");
+ tipsFiles.append("kipi/tips");
+
+ TDEGlobal::locale()->insertCatalogue("kipiplugins");
+ TDEGlobal::locale()->insertCatalogue("libkdcraw");
+
+ KTipDialog::showMultiTip(0, tipsFiles, false);
+#else
+ KTipDialog::showTip(0, "digikam/tips", false);
+#endif
+
+ return app.exec();
+}
diff --git a/src/digikam/mediaplayerview.cpp b/src/digikam/mediaplayerview.cpp
new file mode 100644
index 00000000..97ef26aa
--- /dev/null
+++ b/src/digikam/mediaplayerview.cpp
@@ -0,0 +1,252 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-20-12
+ * Description : a view to embed a KPart media player.
+ *
+ * Copyright (C) 2006-2007 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqstring.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+
+// KDE includes.
+
+#include <tdeparts/componentfactory.h>
+#include <kmimetype.h>
+#include <kuserprofile.h>
+#include <kdialogbase.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "themeengine.h"
+#include "mediaplayerview.h"
+#include "mediaplayerview.moc"
+
+namespace Digikam
+{
+
+class MediaPlayerViewPriv
+{
+
+public:
+
+ enum MediaPlayerViewMode
+ {
+ ErrorView=0,
+ PlayerView
+ };
+
+public:
+
+ MediaPlayerViewPriv()
+ {
+ mediaPlayerPart = 0;
+ grid = 0;
+ errorView = 0;
+ mediaPlayerView = 0;
+ }
+
+ TQFrame *errorView;
+
+ TQFrame *mediaPlayerView;
+
+ TQGridLayout *grid;
+
+ KParts::ReadOnlyPart *mediaPlayerPart;
+};
+
+MediaPlayerView::MediaPlayerView(TQWidget *parent)
+ : TQWidgetStack(parent, 0, TQt::WDestructiveClose)
+{
+ d = new MediaPlayerViewPriv;
+
+ // --------------------------------------------------------------------------
+
+ d->errorView = new TQFrame(this);
+ TQLabel *errorMsg = new TQLabel(i18n("No media player available..."), d->errorView);
+ TQGridLayout *grid = new TQGridLayout(d->errorView, 2, 2,
+ KDialogBase::marginHint(), KDialogBase::spacingHint());
+
+ errorMsg->setAlignment(TQt::AlignCenter);
+ d->errorView->setFrameStyle(TQFrame::GroupBoxPanel|TQFrame::Plain);
+ d->errorView->setMargin(0);
+ d->errorView->setLineWidth(1);
+
+ grid->addMultiCellWidget(errorMsg, 1, 1, 0, 2);
+ grid->setColStretch(0, 10),
+ grid->setColStretch(2, 10),
+ grid->setRowStretch(0, 10),
+ grid->setRowStretch(2, 10),
+
+ addWidget(d->errorView, MediaPlayerViewPriv::ErrorView);
+
+ // --------------------------------------------------------------------------
+
+ d->mediaPlayerView = new TQFrame(this);
+ d->grid = new TQGridLayout(d->mediaPlayerView, 2, 2,
+ KDialogBase::marginHint(), KDialogBase::spacingHint());
+
+ d->mediaPlayerView->setFrameStyle(TQFrame::GroupBoxPanel|TQFrame::Plain);
+ d->mediaPlayerView->setMargin(0);
+ d->mediaPlayerView->setLineWidth(1);
+
+ d->grid->setColStretch(0, 10),
+ d->grid->setColStretch(2, 10),
+ d->grid->setRowStretch(0, 10),
+
+ addWidget(d->mediaPlayerView, MediaPlayerViewPriv::PlayerView);
+ setPreviewMode(MediaPlayerViewPriv::PlayerView);
+
+ // --------------------------------------------------------------------------
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+}
+
+MediaPlayerView::~MediaPlayerView()
+{
+ if (d->mediaPlayerPart)
+ {
+ d->mediaPlayerPart->closeURL();
+ delete d->mediaPlayerPart;
+ d->mediaPlayerPart = 0;
+ }
+
+ delete d;
+}
+
+void MediaPlayerView::setMediaPlayerFromUrl(const KURL& url)
+{
+ if (url.isEmpty())
+ {
+ if (d->mediaPlayerPart)
+ {
+ d->mediaPlayerPart->closeURL();
+ delete d->mediaPlayerPart;
+ d->mediaPlayerPart = 0;
+ }
+ return;
+ }
+
+ KMimeType::Ptr mimePtr = KMimeType::findByURL(url, 0, true, true);
+ KServiceTypeProfile::OfferList services = KServiceTypeProfile::offers(mimePtr->name(),
+ TQString::fromLatin1("KParts/ReadOnlyPart"));
+
+ DDebug() << "Search a KPart to preview " << url.fileName() << " (" << mimePtr->name() << ") " << endl;
+
+ if (d->mediaPlayerPart)
+ {
+ d->mediaPlayerPart->closeURL();
+ delete d->mediaPlayerPart;
+ d->mediaPlayerPart = 0;
+ }
+
+ TQWidget *mediaPlayerWidget = 0;
+
+ for( KServiceTypeProfile::OfferList::Iterator it = services.begin(); it != services.end(); ++it )
+ {
+ // Ask for a part for this mime type
+ KService::Ptr service = (*it).service();
+
+ if (!service.data())
+ {
+ DWarning() << "Couldn't find a KPart for media" << endl;
+ continue;
+ }
+
+ TQString library = service->library();
+ if ( library.isNull() )
+ {
+ DWarning() << "The library returned from the service was null, "
+ << "indicating we could play media."
+ << endl;
+ continue;
+ }
+
+ DDebug() << "Find KPart library " << library << endl;
+ int error = 0;
+ d->mediaPlayerPart = KParts::ComponentFactory::createPartInstanceFromService
+ <KParts::ReadOnlyPart>(service, d->mediaPlayerView, 0, d->mediaPlayerView,
+ 0, TQStringList(), &error);
+ if (!d->mediaPlayerPart)
+ {
+ DWarning() << "Failed to instantiate KPart from library " << library
+ << " error=" << error << endl;
+ continue;
+ }
+
+ mediaPlayerWidget = d->mediaPlayerPart->widget();
+ if ( !mediaPlayerWidget )
+ {
+ DWarning() << "Failed to get KPart widget from library " << library << endl;
+ continue;
+ }
+
+ mediaPlayerWidget->show();
+ break;
+ }
+
+ if (!mediaPlayerWidget)
+ {
+ setPreviewMode(MediaPlayerViewPriv::ErrorView);
+ return;
+ }
+
+ d->grid->addMultiCellWidget(mediaPlayerWidget, 0, 0, 0, 2);
+ mediaPlayerWidget->setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);
+ d->mediaPlayerPart->openURL(url);
+ setPreviewMode(MediaPlayerViewPriv::PlayerView);
+}
+
+void MediaPlayerView::escapePreview()
+{
+ if (d->mediaPlayerPart)
+ {
+ d->mediaPlayerPart->closeURL();
+ delete d->mediaPlayerPart;
+ d->mediaPlayerPart = 0;
+ }
+}
+
+void MediaPlayerView::slotThemeChanged()
+{
+ d->errorView->setPaletteBackgroundColor(ThemeEngine::instance()->baseColor());
+ d->mediaPlayerView->setPaletteBackgroundColor(ThemeEngine::instance()->baseColor());
+}
+
+int MediaPlayerView::previewMode(void)
+{
+ return id(visibleWidget());
+}
+
+void MediaPlayerView::setPreviewMode(int mode)
+{
+ if (mode != MediaPlayerViewPriv::ErrorView && mode != MediaPlayerViewPriv::PlayerView)
+ return;
+
+ raiseWidget(mode);
+}
+
+} // NameSpace Digikam
+
diff --git a/src/digikam/mediaplayerview.h b/src/digikam/mediaplayerview.h
new file mode 100644
index 00000000..08399175
--- /dev/null
+++ b/src/digikam/mediaplayerview.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-20-12
+ * Description : a view to embed a KPart media player.
+ *
+ * Copyright (C) 2006-2007 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MEDIAPLAYERVIEW_H
+#define MEDIAPLAYERVIEW_H
+
+// TQt includes.
+
+#include <tqwidgetstack.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class MediaPlayerViewPriv;
+
+class DIGIKAM_EXPORT MediaPlayerView : public TQWidgetStack
+{
+TQ_OBJECT
+
+public:
+
+ MediaPlayerView(TQWidget *parent=0);
+ ~MediaPlayerView();
+
+ void setMediaPlayerFromUrl(const KURL& url);
+ void escapePreview();
+
+private slots:
+
+ void slotThemeChanged();
+
+private:
+
+ int previewMode(void);
+ void setPreviewMode(int mode);
+
+private:
+
+ MediaPlayerViewPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* MEDIAPLAYERVIEW_H */
diff --git a/src/digikam/metadatahub.cpp b/src/digikam/metadatahub.cpp
new file mode 100644
index 00000000..a1ab4d3c
--- /dev/null
+++ b/src/digikam/metadatahub.cpp
@@ -0,0 +1,858 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-05
+ * Description : Metadata handling
+ *
+ * Copyright (C) 2007-2009 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2007-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imageinfo.h"
+#include "album.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "imageattributeswatch.h"
+#include "metadatahub.h"
+
+namespace Digikam
+{
+
+class MetadataHubPriv
+{
+public:
+
+ MetadataHubPriv()
+ {
+ dateTimeStatus = MetadataHub::MetadataInvalid;
+ ratingStatus = MetadataHub::MetadataInvalid;
+ commentStatus = MetadataHub::MetadataInvalid;
+
+ rating = -1;
+ highestRating = -1;
+
+ count = 0;
+
+ dbmode = MetadataHub::ManagedTags;
+
+ dateTimeChanged = false;
+ commentChanged = false;
+ ratingChanged = false;
+ tagsChanged = false;
+ }
+
+ MetadataHub::Status dateTimeStatus;
+ MetadataHub::Status commentStatus;
+ MetadataHub::Status ratingStatus;
+
+ TQDateTime dateTime;
+ TQDateTime lastDateTime;
+ TQString comment;
+ int rating;
+ int highestRating;
+
+ int count;
+
+ TQMap<TAlbum *, MetadataHub::TagStatus> tags;
+ TQStringList tagList;
+
+ MetadataHub::DatabaseMode dbmode;
+
+ bool dateTimeChanged;
+ bool commentChanged;
+ bool ratingChanged;
+ bool tagsChanged;
+
+ template <class T> void loadWithInterval(const T &data, T &storage, T &highestStorage, MetadataHub::Status &status);
+ template <class T> void loadSingleValue(const T &data, T &storage, MetadataHub::Status &status);
+};
+
+MetadataWriteSettings::MetadataWriteSettings()
+{
+ saveComments = false;
+ saveDateTime = false;
+ saveRating = false;
+ saveIptcTags = false;
+ saveIptcPhotographerId = false;
+ saveIptcCredits = false;
+}
+
+MetadataWriteSettings::MetadataWriteSettings(AlbumSettings *albumSettings)
+{
+ saveComments = albumSettings->getSaveComments();
+ saveDateTime = albumSettings->getSaveDateTime();
+ saveRating = albumSettings->getSaveRating();
+ saveIptcTags = albumSettings->getSaveIptcTags();
+ saveIptcPhotographerId = albumSettings->getSaveIptcPhotographerId();
+ saveIptcCredits = albumSettings->getSaveIptcCredits();
+
+ iptcAuthor = albumSettings->getIptcAuthor();
+ iptcAuthorTitle = albumSettings->getIptcAuthorTitle();
+ iptcCredit = albumSettings->getIptcCredit();
+ iptcSource = albumSettings->getIptcSource();
+ iptcCopyright = albumSettings->getIptcCopyright();
+}
+
+MetadataHub::MetadataHub(DatabaseMode dbmode)
+{
+ d = new MetadataHubPriv;
+ d->dbmode = dbmode;
+}
+
+MetadataHub::~MetadataHub()
+{
+ delete d;
+}
+
+MetadataHub::MetadataHub(const MetadataHub &other)
+{
+ d = new MetadataHubPriv(*other.d);
+}
+
+MetadataHub &MetadataHub::operator=(const MetadataHub &other)
+{
+ (*d) = (*other.d);
+ return *this;
+}
+
+void MetadataHub::reset()
+{
+ (*d) = MetadataHubPriv();
+}
+
+// --------------------------------------------------
+
+void MetadataHub::load(ImageInfo *info)
+{
+ d->count++;
+
+ load(info->dateTime(), info->caption(), info->rating());
+
+ AlbumManager *man = AlbumManager::instance();
+ TQValueList<int> tagIDs = info->tagIDs();
+ TQValueList<TAlbum *> loadedTags;
+
+ if (d->dbmode == ManagedTags)
+ {
+ TQValueList<TAlbum *> loadedTags;
+ for (TQValueList<int>::iterator it = tagIDs.begin(); it != tagIDs.end(); ++it)
+ {
+ TAlbum *album = man->findTAlbum(*it);
+ if (!album)
+ {
+ DWarning() << k_funcinfo << "Tag id " << *it << " not found in database." << endl;
+ continue;
+ }
+ loadedTags.append(album);
+ }
+
+ loadTags(loadedTags);
+ }
+ else
+ {
+ loadTags(info->tagPaths(false));
+ }
+}
+
+void MetadataHub::load(const DMetadata &metadata)
+{
+ d->count++;
+
+ TQString comment;
+ TQStringList keywords;
+ TQDateTime datetime;
+ int rating;
+
+ // Try to get comments from image :
+ // In first, from standard JPEG comments, or
+ // In second, from EXIF comments tag, or
+ // In third, from IPTC comments tag.
+
+ comment = metadata.getImageComment();
+
+ // Try to get date and time from image :
+ // In first, from EXIF date & time tags, or
+ // In second, from IPTC date & time tags.
+
+ datetime = metadata.getImageDateTime();
+
+ // Try to get image rating from IPTC Urgency tag
+ // else use file system time stamp.
+ rating = metadata.getImageRating();
+
+ if ( !datetime.isValid() )
+ {
+ TQFileInfo info( metadata.getFilePath() );
+ datetime = info.lastModified();
+ }
+
+ load(datetime, comment, rating);
+
+ // Try to get image tags from IPTC keywords tags.
+
+ if (d->dbmode == ManagedTags)
+ {
+ AlbumManager *man = AlbumManager::instance();
+ TQStringList tagPaths = metadata.getImageKeywords();
+ TQValueList<TAlbum *> loadedTags;
+
+ for (TQStringList::iterator it = tagPaths.begin(); it != tagPaths.end(); ++it)
+ {
+ TAlbum *album = man->findTAlbum(*it);
+ if (!album)
+ {
+ DWarning() << k_funcinfo << "Tag id " << *it << " not found in database. Use NewTagsImport mode?" << endl;
+ continue;
+ }
+ loadedTags.append(album);
+ }
+
+ loadTags(loadedTags);
+ }
+ else
+ {
+ loadTags(metadata.getImageKeywords());
+ }
+}
+
+bool MetadataHub::load(const TQString &filePath)
+{
+ DMetadata metadata;
+ bool success = metadata.load(filePath);
+ load(metadata); // increments count
+ return success;
+}
+
+// private common code to merge tags
+void MetadataHub::loadTags(const TQValueList<TAlbum *> &loadedTags)
+{
+ // get copy of tags
+ TQValueList<TAlbum *> previousTags = d->tags.keys();
+
+ // first go through all tags contained in this set
+ for (TQValueList<TAlbum *>::const_iterator it = loadedTags.begin(); it != loadedTags.end(); ++it)
+ {
+ // that is a reference
+ TagStatus &status = d->tags[*it];
+ // if it was not contained in the list, the default constructor will mark it as invalid
+ if (status == MetadataInvalid)
+ {
+ if (d->count == 1)
+ // there were no previous sets that could have contained the set
+ status = TagStatus(MetadataAvailable, true);
+ else
+ // previous sets did not contain the tag, we do => disjoint
+ status = TagStatus(MetadataDisjoint, true);
+ }
+ else if (status == TagStatus(MetadataAvailable, false))
+ {
+ // set to explicitly not contained, but we contain it => disjoint
+ status = TagStatus(MetadataDisjoint, true);
+ }
+ // else if mapIt.data() == MetadataAvailable, true: all right, we contain it too
+ // else if mapIt.data() == MetadataDisjoint: it's already disjoint
+
+ // remove from the list to signal that this tag has been handled
+ previousTags.remove(*it);
+ }
+
+ // Those tags which had been set as MetadataAvailable before,
+ // but are not contained in this set, have to be set to MetadataDisjoint
+ for (TQValueList<TAlbum *>::iterator it = previousTags.begin(); it != previousTags.end(); ++it)
+ {
+ TQMap<TAlbum *, TagStatus>::iterator mapIt = d->tags.find(*it);
+ if (mapIt != d->tags.end() && mapIt.data() == TagStatus(MetadataAvailable, true))
+ {
+ mapIt.data() = TagStatus(MetadataDisjoint, true);
+ }
+ }
+}
+
+// private code to merge tags with d->tagList
+void MetadataHub::loadTags(const TQStringList &loadedTagPaths)
+{
+ if (d->count == 1)
+ {
+ d->tagList = loadedTagPaths;
+ }
+ else
+ {
+ // a simple intersection
+ TQStringList toBeAdded;
+ for (TQStringList::iterator it = d->tagList.begin(); it != d->tagList.end(); ++it)
+ {
+ TQStringList::const_iterator newTagListIt = loadedTagPaths.find(*it);
+ if (newTagListIt == loadedTagPaths.end())
+ {
+ // it's not in the loadedTagPaths list. Remove it from intersection list.
+ it = d->tagList.remove(it);
+ }
+ // else, it is in both lists, so no need to change d->tagList, it's already added.
+ }
+ }
+}
+
+// private common code to load dateTime, comment, rating
+void MetadataHub::load(const TQDateTime &dateTime, const TQString &comment, int rating)
+{
+ if (dateTime.isValid())
+ {
+ d->loadWithInterval<TQDateTime>(dateTime, d->dateTime, d->lastDateTime, d->dateTimeStatus);
+ }
+
+ d->loadWithInterval<int>(rating, d->rating, d->highestRating, d->ratingStatus);
+
+ d->loadSingleValue<TQString>(comment, d->comment, d->commentStatus);
+}
+
+// template method to share code for dateTime and rating
+template <class T> void MetadataHubPriv::loadWithInterval(const T &data, T &storage, T &highestStorage, MetadataHub::Status &status)
+{
+ switch (status)
+ {
+ case MetadataHub::MetadataInvalid:
+ storage = data;
+ status = MetadataHub::MetadataAvailable;
+ break;
+ case MetadataHub::MetadataAvailable:
+ // we have two values. If they are equal, status is unchanged
+ if (data == storage)
+ break;
+ // they are not equal. We need to enter the disjoint state.
+ status = MetadataHub::MetadataDisjoint;
+ if (data > storage)
+ {
+ highestStorage = data;
+ }
+ else
+ {
+ highestStorage = storage;
+ storage = data;
+ }
+ break;
+ case MetadataHub::MetadataDisjoint:
+ // smaller value is stored in storage
+ if (data < storage)
+ storage = data;
+ else if (highestStorage < data)
+ highestStorage = data;
+ break;
+ }
+}
+
+// template method used by comment
+template <class T> void MetadataHubPriv::loadSingleValue(const T &data, T &storage, MetadataHub::Status &status)
+{
+ switch (status)
+ {
+ case MetadataHub::MetadataInvalid:
+ storage = data;
+ status = MetadataHub::MetadataAvailable;
+ break;
+ case MetadataHub::MetadataAvailable:
+ // we have two values. If they are equal, status is unchanged
+ if (data == storage)
+ break;
+ // they are not equal. We need to enter the disjoint state.
+ status = MetadataHub::MetadataDisjoint;
+ break;
+ case MetadataHub::MetadataDisjoint:
+ break;
+ }
+}
+
+// --------------------------------------------------
+
+bool MetadataHub::write(ImageInfo *info, WriteMode writeMode)
+{
+ bool changed = false;
+
+ // find out in advance if we have something to write - needed for FullWriteIfChanged mode
+ bool saveComment = d->commentStatus == MetadataAvailable;
+ bool saveDateTime = d->dateTimeStatus == MetadataAvailable;
+ bool saveRating = d->ratingStatus == MetadataAvailable;
+ bool saveTags = false;
+ for (TQMap<TAlbum *, TagStatus>::iterator it = d->tags.begin(); it != d->tags.end(); ++it)
+ {
+ if (it.data() == MetadataAvailable)
+ {
+ saveTags = true;
+ break;
+ }
+ }
+
+ bool writeAllFields;
+ if (writeMode == FullWrite)
+ writeAllFields = true;
+ else if (writeMode == FullWriteIfChanged)
+ writeAllFields = (
+ (saveComment && d->commentChanged) ||
+ (saveDateTime && d->dateTimeChanged) ||
+ (saveRating && d->ratingChanged) ||
+ (saveTags && d->tagsChanged)
+ );
+ else // PartialWrite
+ writeAllFields = false;
+
+ if (saveComment && (writeAllFields || d->commentChanged))
+ {
+ info->setCaption(d->comment);
+ changed = true;
+ }
+ if (saveDateTime && (writeAllFields || d->dateTimeChanged))
+ {
+ info->setDateTime(d->dateTime);
+ changed = true;
+ }
+ if (saveRating && (writeAllFields || d->ratingChanged))
+ {
+ info->setRating(d->rating);
+ changed = true;
+ }
+
+ if (writeAllFields || d->tagsChanged)
+ {
+ if (d->dbmode == ManagedTags)
+ {
+ for (TQMap<TAlbum *, TagStatus>::iterator it = d->tags.begin(); it != d->tags.end(); ++it)
+ {
+ if (it.data() == MetadataAvailable)
+ {
+ if (it.data().hasTag)
+ info->setTag(it.key()->id());
+ else
+ info->removeTag(it.key()->id());
+ changed = true;
+ }
+ }
+ }
+ else
+ {
+ // tags not yet contained in database will be created
+ info->addTagPaths(d->tagList);
+ changed = changed || !d->tagList.isEmpty();
+ }
+ }
+ return changed;
+}
+
+bool MetadataHub::write(DMetadata &metadata, WriteMode writeMode, const MetadataWriteSettings &settings)
+{
+ bool dirty = false;
+
+ // find out in advance if we have something to write - needed for FullWriteIfChanged mode
+ bool saveComment = (settings.saveComments && d->commentStatus == MetadataAvailable);
+ bool saveDateTime = (settings.saveDateTime && d->dateTimeStatus == MetadataAvailable);
+ bool saveRating = (settings.saveRating && d->ratingStatus == MetadataAvailable);
+ bool saveTags = false;
+ if (settings.saveIptcTags)
+ {
+ saveTags = false;
+ // find at least one tag to write
+ for (TQMap<TAlbum *, TagStatus>::iterator it = d->tags.begin(); it != d->tags.end(); ++it)
+ {
+ if (it.data() == MetadataAvailable)
+ {
+ saveTags = true;
+ break;
+ }
+ }
+ }
+
+ bool writeAllFields;
+ if (writeMode == FullWrite)
+ writeAllFields = true;
+ else if (writeMode == FullWriteIfChanged)
+ writeAllFields = (
+ (saveComment && d->commentChanged) ||
+ (saveDateTime && d->dateTimeChanged) ||
+ (saveRating && d->ratingChanged) ||
+ (saveTags && d->tagsChanged)
+ );
+ else // PartialWrite
+ writeAllFields = false;
+
+ if (saveComment && (writeAllFields || d->commentChanged))
+ {
+ // Store comments in image as JFIF comments, Exif comments, and Iptc Comments.
+ dirty |= metadata.setImageComment(d->comment);
+ }
+
+ if (saveDateTime && (writeAllFields || d->dateTimeChanged))
+ {
+ // Store Image Date & Time as Exif and Iptc tags.
+ dirty |= metadata.setImageDateTime(d->dateTime, false);
+ }
+
+ if (saveRating && (writeAllFields || d->ratingChanged))
+ {
+ // Store Image rating as Iptc tag.
+ dirty |= metadata.setImageRating(d->rating);
+ }
+
+ if (saveTags && (writeAllFields || d->tagsChanged))
+ {
+ // Store tag paths as Iptc keywords tags.
+
+ // DatabaseMode == ManagedTags is assumed.
+ // To fix this constraint (not needed currently), an oldKeywords parameter is needed
+
+ // create list of keywords to be added and to be removed
+ TQStringList newKeywords, oldKeywords;
+ for (TQMap<TAlbum *, TagStatus>::iterator it = d->tags.begin(); it != d->tags.end(); ++it)
+ {
+ // it is important that MetadataDisjoint keywords are not touched
+ if (it.data() == MetadataAvailable)
+ {
+ // This works for single and multiple selection.
+ // In both situations, tags which had originally been loaded
+ // have explicitly been removed with setTag.
+ if (it.data().hasTag)
+ newKeywords.append(it.key()->tagPath(false));
+ else
+ oldKeywords.append(it.key()->tagPath(false));
+ }
+ }
+ // NOTE: See B.K.O #175321 : we remove all old keyword from IPTC and XMP before to
+ // synchronize metadata, else contents is not coherent.
+ dirty |= metadata.setImageKeywords(metadata.getImageKeywords(), newKeywords);
+ }
+
+ if (settings.saveIptcPhotographerId && writeAllFields)
+ {
+ // Store Photograph identity into the Iptc tags.
+ dirty |= metadata.setImagePhotographerId(settings.iptcAuthor,
+ settings.iptcAuthorTitle);
+ }
+
+ if (settings.saveIptcCredits && writeAllFields)
+ {
+ // Store Photograph identity into the Iptc tags.
+ dirty |= metadata.setImageCredits(settings.iptcCredit,
+ settings.iptcSource,
+ settings.iptcCopyright);
+ }
+
+ return dirty;
+}
+
+bool MetadataHub::write(const TQString &filePath, WriteMode writeMode, const MetadataWriteSettings &settings)
+{
+ // if no DMetadata object is needed at all, don't construct one -
+ // important optimization if writing to file is turned off in setup!
+ if (!needWriteMetadata(writeMode, settings))
+ return false;
+
+ DMetadata metadata(filePath);
+ if (write(metadata, writeMode, settings))
+ {
+ bool success = metadata.applyChanges();
+ ImageAttributesWatch::instance()->fileMetadataChanged(filePath);
+ return success;
+ }
+ return false;
+}
+
+bool MetadataHub::write(DImg &image, WriteMode writeMode, const MetadataWriteSettings &settings)
+{
+ // if no DMetadata object is needed at all, don't construct one
+ if (!needWriteMetadata(writeMode, settings))
+ return false;
+
+ // See DImgLoader::readMetadata() and saveMetadata()
+ DMetadata metadata;
+ metadata.setComments(image.getComments());
+ metadata.setExif(image.getExif());
+ metadata.setIptc(image.getIptc());
+
+ if (write(metadata, writeMode, settings))
+ {
+ // Do not insert null data into metaData map:
+ // Even if byte array is null, if there is a key in the map, it will
+ // be interpreted as "There was data, so write it again to the file".
+ if (!metadata.getComments().isNull())
+ image.setComments(metadata.getComments());
+ if (!metadata.getExif().isNull())
+ image.setExif(metadata.getExif());
+ if (!metadata.getIptc().isNull())
+ image.setIptc(metadata.getIptc());
+ return true;
+ }
+ return false;
+}
+
+bool MetadataHub::needWriteMetadata(WriteMode writeMode, const MetadataWriteSettings &settings) const
+{
+ // This is the same logic as in write(DMetadata) but without actually writing.
+ // Adapt if the method above changes
+
+ bool saveComment = (settings.saveComments && d->commentStatus == MetadataAvailable);
+ bool saveDateTime = (settings.saveDateTime && d->dateTimeStatus == MetadataAvailable);
+ bool saveRating = (settings.saveRating && d->ratingStatus == MetadataAvailable);
+ bool saveTags = false;
+ if (settings.saveIptcTags)
+ {
+ saveTags = false;
+ // find at least one tag to write
+ for (TQMap<TAlbum *, TagStatus>::iterator it = d->tags.begin(); it != d->tags.end(); ++it)
+ {
+ if (it.data() == MetadataAvailable)
+ {
+ saveTags = true;
+ break;
+ }
+ }
+ }
+
+ bool writeAllFields;
+ if (writeMode == FullWrite)
+ writeAllFields = true;
+ else if (writeMode == FullWriteIfChanged)
+ writeAllFields = (
+ (saveComment && d->commentChanged) ||
+ (saveDateTime && d->dateTimeChanged) ||
+ (saveRating && d->ratingChanged) ||
+ (saveTags && d->tagsChanged)
+ );
+ else // PartialWrite
+ writeAllFields = false;
+
+ return (
+ (saveComment && (writeAllFields || d->commentChanged)) ||
+ (saveDateTime && (writeAllFields || d->dateTimeChanged)) ||
+ (saveRating && (writeAllFields || d->ratingChanged)) ||
+ (saveTags && (writeAllFields || d->tagsChanged)) ||
+ (settings.saveIptcPhotographerId && writeAllFields) ||
+ (settings.saveIptcCredits && writeAllFields)
+ );
+}
+
+MetadataWriteSettings MetadataHub::defaultWriteSettings()
+{
+ if (AlbumSettings::instance())
+ return MetadataWriteSettings(AlbumSettings::instance());
+ else
+ // is this check necessary?
+ return MetadataWriteSettings();
+}
+
+// --------------------------------------------------
+
+MetadataHub::Status MetadataHub::dateTimeStatus() const
+{
+ return d->dateTimeStatus;
+}
+
+MetadataHub::Status MetadataHub::commentStatus() const
+{
+ return d->commentStatus;
+}
+
+MetadataHub::Status MetadataHub::ratingStatus() const
+{
+ return d->ratingStatus;
+}
+
+MetadataHub::TagStatus MetadataHub::tagStatus(int albumId) const
+{
+ if (d->dbmode == NewTagsImport)
+ return TagStatus(MetadataInvalid);
+ return tagStatus(AlbumManager::instance()->findTAlbum(albumId));
+}
+
+MetadataHub::TagStatus MetadataHub::tagStatus(const TQString &tagPath) const
+{
+ if (d->dbmode == NewTagsImport)
+ return TagStatus(MetadataInvalid);
+ return tagStatus(AlbumManager::instance()->findTAlbum(tagPath));
+}
+
+MetadataHub::TagStatus MetadataHub::tagStatus(TAlbum *album) const
+{
+ if (!album)
+ return TagStatus(MetadataInvalid);
+ TQMap<TAlbum *, TagStatus>::iterator mapIt = d->tags.find(album);
+ if (mapIt == d->tags.end())
+ return TagStatus(MetadataInvalid);
+ return mapIt.data();
+}
+
+
+bool MetadataHub::dateTimeChanged() const
+{
+ return d->dateTimeChanged;
+}
+
+bool MetadataHub::commentChanged() const
+{
+ return d->commentChanged;
+}
+
+bool MetadataHub::ratingChanged() const
+{
+ return d->ratingChanged;
+}
+
+bool MetadataHub::tagsChanged() const
+{
+ return d->tagsChanged;
+}
+
+TQDateTime MetadataHub::dateTime() const
+{
+ return d->dateTime;
+}
+
+TQString MetadataHub::comment() const
+{
+ return d->comment;
+}
+
+int MetadataHub::rating() const
+{
+ return d->rating;
+}
+
+void MetadataHub::dateTimeInterval(TQDateTime &lowest, TQDateTime &highest) const
+{
+ switch (d->dateTimeStatus)
+ {
+ case MetadataInvalid:
+ lowest = highest = TQDateTime();
+ break;
+ case MetadataAvailable:
+ lowest = highest = d->dateTime;
+ break;
+ case MetadataDisjoint:
+ lowest = d->dateTime;
+ highest = d->lastDateTime;
+ break;
+ }
+}
+
+void MetadataHub::ratingInterval(int &lowest, int &highest) const
+{
+ switch (d->ratingStatus)
+ {
+ case MetadataInvalid:
+ lowest = highest = -1;
+ break;
+ case MetadataAvailable:
+ lowest = highest = d->rating;
+ break;
+ case MetadataDisjoint:
+ lowest = d->rating;
+ highest = d->highestRating;
+ break;
+ }
+}
+
+TQStringList MetadataHub::keywords() const
+{
+ if (d->dbmode == NewTagsImport)
+ return d->tagList;
+ else
+ {
+ TQStringList tagList;
+ for (TQMap<TAlbum *, TagStatus>::iterator it = d->tags.begin(); it != d->tags.end(); ++it)
+ {
+ if (it.data() == TagStatus(MetadataAvailable, true))
+ tagList.append(it.key()->tagPath(false));
+ }
+ return tagList;
+ }
+}
+
+TQMap<TAlbum *, MetadataHub::TagStatus> MetadataHub::tags() const
+{
+ // DatabaseMode == ManagedTags is assumed
+ return d->tags;
+}
+
+TQMap<int, MetadataHub::TagStatus> MetadataHub::tagIDs() const
+{
+ // DatabaseMode == ManagedTags is assumed
+ TQMap<int, TagStatus> intmap;
+ for (TQMap<TAlbum *, TagStatus>::iterator it = d->tags.begin(); it != d->tags.end(); ++it)
+ {
+ intmap.insert(it.key()->id(), it.data());
+ }
+ return intmap;
+}
+
+
+// --------------------------------------------------
+
+void MetadataHub::setDateTime(const TQDateTime &dateTime, Status status)
+{
+ d->dateTimeStatus = status;
+ d->dateTime = dateTime;
+ d->dateTimeChanged = true;
+}
+
+void MetadataHub::setComment(const TQString &comment, Status status)
+{
+ d->commentStatus = status;
+ d->comment = comment;
+ d->commentChanged = true;
+}
+
+void MetadataHub::setRating(int rating, Status status)
+{
+ d->ratingStatus = status;
+ d->rating = rating;
+ d->ratingChanged = true;
+}
+
+void MetadataHub::setTag(TAlbum *tag, bool hasTag, Status status)
+{
+ // DatabaseMode == ManagedTags is assumed
+ d->tags[tag] = TagStatus(status, hasTag);
+ d->tagsChanged = true;
+}
+
+void MetadataHub::setTag(int albumID, bool hasTag, Status status)
+{
+ // DatabaseMode == ManagedTags is assumed
+ TAlbum *album = AlbumManager::instance()->findTAlbum(albumID);
+ if (!album)
+ {
+ DWarning() << k_funcinfo << "Tag ID " << albumID << " not found in database." << endl;
+ return;
+ }
+ setTag(album, hasTag, status);
+}
+
+void MetadataHub::resetChanged()
+{
+ d->dateTimeChanged = false;
+ d->commentChanged = false;
+ d->ratingChanged = false;
+ d->tagsChanged = false;
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/metadatahub.h b/src/digikam/metadatahub.h
new file mode 100644
index 00000000..decb148e
--- /dev/null
+++ b/src/digikam/metadatahub.h
@@ -0,0 +1,362 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-05
+ * Description : Metadata handling
+ *
+ * Copyright (C) 2007-2009 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2007-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef METADATAHUB_H
+#define METADATAHUB_H
+
+// TQt includes.
+
+#include <tqstringlist.h>
+#include <tqdatetime.h>
+#include <tqmap.h>
+#include <tqvaluelist.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "dmetadata.h"
+#include "dimg.h"
+
+namespace Digikam
+{
+
+class AlbumSettings;
+class ImageInfo;
+class TAlbum;
+class MetadataHubPriv;
+
+class MetadataWriteSettings
+{
+ /**
+ The class MetadataWriteSettings encapsulates all metadata related settings that are available
+ from the AlbumSettings.
+ This allows supply changed arguments to MetadataHub without changing the global settings
+ */
+public:
+
+ /**
+ Constructs a MetadataWriteSettings object with all boolean values set to false,
+ all TQString values set to TQString()
+ */
+ MetadataWriteSettings();
+
+ /**
+ Constructs a MetadataWriteSettings object from the given AlbumSettings object
+ */
+ MetadataWriteSettings(AlbumSettings *albumsettings);
+
+ bool saveComments;
+ bool saveDateTime;
+ bool saveRating;
+ bool saveIptcTags;
+ bool saveIptcPhotographerId;
+ bool saveIptcCredits;
+
+ TQString iptcAuthor;
+ TQString iptcAuthorTitle;
+ TQString iptcCredit;
+ TQString iptcSource;
+ TQString iptcCopyright;
+};
+
+class MetadataHub
+{
+public:
+
+ /**
+ The status enum describes the result of joining several metadata sets.
+ If only one set has been added, the status is always MetadataAvailable.
+ If no set has been added, the status is always MetadataInvalid
+ */
+ enum Status
+ {
+ MetadataInvalid, /// not yet filled with any value
+ MetadataAvailable, /// only one data set has been added, or a common value is available
+ MetadataDisjoint /// No common value is available. For rating and dates, the interval is available.
+ };
+
+ /**
+ Describes the complete status of a Tag: The metadata status, and the fact if it has the tag or not.
+ */
+ class TagStatus
+ {
+ public:
+ TagStatus(Status status, bool hasTag = false) : status(status), hasTag(hasTag) {};
+ TagStatus() : status(MetadataInvalid), hasTag(false) {};
+
+ Status status;
+ bool hasTag;
+
+ bool operator==(TagStatus otherstatus)
+ {
+ return otherstatus.status == status &&
+ otherstatus.hasTag == hasTag;
+ }
+ bool operator==(Status otherstatus) { return otherstatus == status; }
+ };
+
+ enum DatabaseMode
+ {
+ /**
+ Use this mode if
+ - the album manager is not available and/or
+ - metadata sets may contain tags that are not available from the AlbumManager
+ This situation occurs if new tags are imported from IPTC keywords.
+ This means that the album manager is not accessed, all methods depending on TAlbum*
+ (tags(), tagIDs(), setTag()) shall not be used.
+ The method write(ImageInfo*) will create not yet existing tags in the database.
+ */
+ NewTagsImport,
+ /**
+ Use this mode if all tags are available from the AlbumManager.
+ This situation occurs if you load from ImageInfo objects.
+ All methods can be used.
+ */
+ ManagedTags
+ };
+
+ enum WriteMode
+ {
+ /**
+ Write all available information
+ */
+ FullWrite,
+ /**
+ Do a full write if and only if
+ - metadata fields changed
+ - the changed fields shall be written according to write settings
+ "Changed" in this context means changed by one of the set... methods,
+ the load() methods are ignored for this attribute.
+ This mode allows to avoid write operations when e.g. the user does not want
+ keywords to be written and only changes keywords.
+ */
+ FullWriteIfChanged,
+ /**
+ Write only the changed parts.
+ Metadata fields which cannot be changed from MetadataHub (photographer ID etc.)
+ will never be written
+ */
+ PartialWrite
+ };
+
+ /**
+ Constructs a MetadataHub.
+ @param dbmode Determines if the database may be accessed or not. See the enum description above.
+ */
+ MetadataHub(DatabaseMode dbmode = ManagedTags);
+ ~MetadataHub();
+ MetadataHub &operator=(const MetadataHub &);
+ MetadataHub(const MetadataHub &);
+
+ void reset();
+
+ // --------------------------------------------------
+
+ /**
+ Add metadata information contained in the ImageInfo object.
+ This method (or in combination with the other load methods)
+ can be called multiple times on the same MetadataHub object.
+ In this case, the metadata will be combined.
+ */
+ void load(ImageInfo *info);
+
+ /**
+ Add metadata information from the DMetadata object
+ */
+ void load(const DMetadata &metadata);
+
+ /**
+ Load metadata information from the given file.
+ (Uses DMetadata, TQFileInfo)
+ @returns True if the metadata could be loaded
+ */
+ bool load(const TQString &filePath);
+
+ // --------------------------------------------------
+
+ /**
+ Applies the set of metadata contained in this MetadataHub
+ to the given ImageInfo object.
+ @return Returns true if the info object has been changed
+ */
+ bool write(ImageInfo *info, WriteMode writeMode = FullWrite);
+
+ /**
+ Applies the set of metadata contained in this MetadataHub
+ to the given DMetadata object.
+ The MetadataWriteSettings determine whether data is actually
+ set or not.
+ The following metadata fields may be set (depending on settings):
+ - Comment
+ - Date
+ - Rating
+ - Tags
+ - Photographer ID (data from settings)
+ - Credits (data from settings)
+
+ The data fields taken from this MetadataHub object are only set if
+ their status is MetadataAvailable.
+ If the status is MetadataInvalid or MetadataDisjoint, the respective
+ metadata field is not touched.
+ @return Returns true if the metadata object has been touched
+ */
+ bool write(DMetadata &metadata, WriteMode writeMode = FullWrite,
+ const MetadataWriteSettings &settings = defaultWriteSettings());
+
+ /**
+ Constructs a DMetadata object for given filePath,
+ calls the above method, writes the changes out to the file,
+ and notifies the ImageAttributesWatch.
+ @return Returns if the file has been touched
+ */
+ bool write(const TQString &filePath, WriteMode writeMode = FullWrite,
+ const MetadataWriteSettings &settings = defaultWriteSettings());
+
+ /**
+ Constructs a DMetadata object from the metadata stored in the given DImg object,
+ calls the above method, and changes the stored metadata in the DImg object.
+ @return Returns if the DImg object has been touched
+ */
+ bool write(DImg &image, WriteMode writeMode = FullWrite,
+ const MetadataWriteSettings &settings = defaultWriteSettings());
+
+ /**
+ Constructs a MetadataWriteSettings object from the global AlbumSettings object.
+ */
+ static MetadataWriteSettings defaultWriteSettings();
+
+ // --------------------------------------------------
+
+ Status dateTimeStatus() const;
+ Status commentStatus() const;
+ Status ratingStatus() const;
+
+ TagStatus tagStatus(TAlbum *album) const;
+ TagStatus tagStatus(int albumId) const;
+ TagStatus tagStatus(const TQString &tagPath) const;
+
+ /**
+ Returns if the metadata field has been changed
+ with the corresponding set... method
+ */
+ bool dateTimeChanged() const;
+ bool commentChanged() const;
+ bool ratingChanged() const;
+ bool tagsChanged() const;
+
+ /**
+ Returns the dateTime.
+ If status is MetadataDisjoint, the earliest date is returned.
+ (see dateTimeInterval())
+ If status is MetadataInvalid, an invalid date is returned.
+ */
+ TQDateTime dateTime() const;
+ /**
+ Returns the dateTime.
+ If status is MetadataDisjoint, the first loaded comment is returned.
+ If status is MetadataInvalid, TQString() is returned.
+ */
+ TQString comment() const;
+ /**
+ Returns the rating.
+ If status is MetadataDisjoint, the lowest rating is returned.
+ (see ratingInterval())
+ If status is MetadataInvalid, -1 is returned.
+ */
+ int rating() const;
+
+ /**
+ Returns the earliest and latest date.
+ If status is MetadataAvailable, the values are the same.
+ If status is MetadataInvalid, invalid dates are returned.
+ */
+ void dateTimeInterval(TQDateTime &lowest, TQDateTime &highest) const;
+ /**
+ Returns the lowest and highest rating.
+ If status is MetadataAvailable, the values are the same.
+ If status is MetadataInvalid, -1 is returned.
+ */
+ void ratingInterval(int &lowest, int &highest) const;
+
+ /**
+ Returns a TQStringList with all tags with status MetadataAvailable.
+ (i.e., the intersection of tags from all loaded metadata sets)
+ */
+ TQStringList keywords() const;
+
+ /**
+ Returns a map with the status for each tag.
+ Tags not contained in the list are considered to have the status MetadataInvalid,
+ that means no loaded metadata set contained this tag.
+ If a tag in the map has the status MetadataAvailable and it has the tag,
+ all loaded sets contained the tag.
+ If a tag in the map has the status MetadataAvailable and it does not have the tag,
+ no loaded sets contains this tag (has been explicitly set so)
+ If a tag in the map has the status MetadataDisjoint, some but not all loaded
+ sets contained the tag. The hasTag value is true then.
+ If MapMode (set in constructor) is false, returns an empty map.
+ */
+ TQMap<TAlbum *, TagStatus> tags() const;
+ /**
+ Similar to the method above.
+ This method is less efficient internally.
+ */
+ TQMap<int, TagStatus> tagIDs() const;
+
+ // --------------------------------------------------
+
+ /**
+ Set dateTime to the given value, and the dateTime status to MetadataAvailable
+ */
+ void setDateTime(const TQDateTime &dateTime, Status status = MetadataAvailable);
+ void setComment(const TQString &comment, Status status = MetadataAvailable);
+ void setRating(int rating, Status status = MetadataAvailable);
+ void setTag(TAlbum *tag, bool hasTag, Status status = MetadataAvailable);
+ void setTag(int albumID, bool hasTag, Status status = MetadataAvailable);
+
+ /**
+ Resets the information that metadata fields have been changed with one of the
+ set... methods (see commentChanged, dateTimeChanged etc.)
+ */
+ void resetChanged();
+
+private:
+
+ void load(const TQDateTime &dateTime, const TQString &comment, int rating);
+ void loadTags(const TQValueList<TAlbum *> &loadedTags);
+ void loadTags(const TQStringList &loadedTagPaths);
+ bool needWriteMetadata(WriteMode writeMode, const MetadataWriteSettings &settings) const;
+
+private:
+
+ MetadataHubPriv *d;
+};
+
+} // namespace Digikam
+
+#endif // METADATAHUB_H
+
diff --git a/src/digikam/mimefilter.cpp b/src/digikam/mimefilter.cpp
new file mode 100644
index 00000000..80b3b501
--- /dev/null
+++ b/src/digikam/mimefilter.cpp
@@ -0,0 +1,88 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-10-22
+ * Description : a widget to filter album contents by type mime
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeglobal.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "mimefilter.h"
+#include "mimefilter.moc"
+
+namespace Digikam
+{
+
+class MimeFilterPriv
+{
+public:
+
+ MimeFilterPriv()
+ {
+ }
+};
+
+MimeFilter::MimeFilter(TQWidget* parent)
+ : TQComboBox(parent)
+{
+ d = new MimeFilterPriv;
+ insertItem( i18n("All Files"), AllFiles );
+ insertItem( i18n("Image Files"), ImageFiles );
+ insertItem( i18n("No RAW Files"), NoRAWFiles );
+ insertItem( i18n("JPEG Files"), JPGFiles );
+ insertItem( i18n("PNG Files"), PNGFiles );
+ insertItem( i18n("TIFF Files"), TIFFiles );
+ insertItem( i18n("RAW Files"), RAWFiles );
+ insertItem( i18n("Movie Files"), MoviesFiles );
+ insertItem( i18n("Audio Files"), AudioFiles );
+
+ TQToolTip::add(this, i18n("Filter for file type"));
+ TQWhatsThis::add(this, i18n("Select the file types (mime types) you want to show"));
+
+ setMimeFilter(AllFiles);
+}
+
+MimeFilter::~MimeFilter()
+{
+ delete d;
+}
+
+void MimeFilter::setMimeFilter(int filter)
+{
+ setCurrentItem(filter);
+ emit activated(filter);
+}
+
+int MimeFilter::mimeFilter()
+{
+ return currentItem();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/mimefilter.h b/src/digikam/mimefilter.h
new file mode 100644
index 00000000..bddb5609
--- /dev/null
+++ b/src/digikam/mimefilter.h
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-10-22
+ * Description : a widget to filter album contents by type mime
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MIMEFILTER_H
+#define MIMEFILTER_H
+
+// TQt includes.
+
+#include <tqcombobox.h>
+
+namespace Digikam
+{
+
+class MimeFilterPriv;
+
+class MimeFilter : public TQComboBox
+{
+ TQ_OBJECT
+
+public:
+
+ enum TypeMimeFilter
+ {
+ AllFiles = 0,
+ ImageFiles,
+ NoRAWFiles,
+ JPGFiles,
+ PNGFiles,
+ TIFFiles,
+ RAWFiles,
+ MoviesFiles,
+ AudioFiles
+ };
+
+public:
+
+ MimeFilter(TQWidget* parent);
+ ~MimeFilter();
+
+ void setMimeFilter(int filter);
+ int mimeFilter();
+
+private:
+
+ MimeFilterPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // MIMEFILTER_H
diff --git a/src/digikam/monthwidget.cpp b/src/digikam/monthwidget.cpp
new file mode 100644
index 00000000..665318fd
--- /dev/null
+++ b/src/digikam/monthwidget.cpp
@@ -0,0 +1,423 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-02
+ * Description : a widget to perform month selection.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqfontmetrics.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpalette.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kcalendarsystem.h>
+#include <tdeversion.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+#include "albumlister.h"
+#include "monthwidget.h"
+#include "monthwidget.moc"
+
+namespace Digikam
+{
+
+class MonthWidgetPriv
+{
+public:
+
+ struct Month
+ {
+ bool active;
+ bool selected;
+
+ int day;
+ int numImages;
+ };
+
+ MonthWidgetPriv()
+ {
+ active = true;
+ }
+
+ bool active;
+
+ int year;
+ int month;
+ int width;
+ int height;
+ int currw;
+ int currh;
+
+ struct Month days[42];
+};
+
+MonthWidget::MonthWidget(TQWidget* parent)
+ : TQFrame(parent, 0, TQt::WNoAutoErase)
+{
+ d = new MonthWidgetPriv;
+ init();
+
+ TQDate date = TQDate::currentDate();
+ setYearMonth(date.year(), date.month());
+
+ setActive(false);
+}
+
+MonthWidget::~MonthWidget()
+{
+ delete d;
+}
+
+void MonthWidget::init()
+{
+ TQFont fn(font());
+ fn.setBold(true);
+ fn.setPointSize(fn.pointSize()+1);
+ TQFontMetrics fm(fn);
+ TQRect r(fm.boundingRect("XX"));
+ r.setWidth(r.width() + 2);
+ r.setHeight(r.height() + 4);
+ d->width = r.width();
+ d->height = r.height();
+
+ setMinimumWidth(d->width * 8);
+ setMinimumHeight(d->height * 9);
+}
+
+void MonthWidget::setYearMonth(int year, int month)
+{
+ d->year = year;
+ d->month = month;
+
+ for (int i=0; i<42; i++)
+ {
+ d->days[i].active = false;
+ d->days[i].selected = false;
+ d->days[i].day = -1;
+ d->days[i].numImages = 0;
+ }
+
+ TQDate date(year, month, 1);
+ int s = date.dayOfWeek();
+
+ for (int i=s; i<(s+date.daysInMonth()); i++)
+ {
+ d->days[i-1].day = i-s+1;
+ }
+
+ update();
+}
+
+TQSize MonthWidget::sizeHint() const
+{
+ return TQSize(d->width * 8, d->height * 9);
+}
+
+void MonthWidget::resizeEvent(TQResizeEvent *e)
+{
+ TQWidget::resizeEvent(e);
+
+ d->currw = contentsRect().width()/8;
+ d->currh = contentsRect().height()/9;
+}
+
+void MonthWidget::drawContents(TQPainter *)
+{
+ TQRect cr(contentsRect());
+
+ TQPixmap pix(cr.width(), cr.height());
+
+ TQColorGroup cg = colorGroup();
+
+ TQFont fnBold(font());
+ TQFont fnOrig(font());
+ fnBold.setBold(true);
+ fnOrig.setBold(false);
+
+ TQPainter p(&pix);
+ p.fillRect(0, 0, cr.width(), cr.height(), cg.background());
+
+ TQRect r(0, 0, d->currw, d->currh);
+ TQRect rsmall;
+
+ int sx, sy;
+ int index = 0;
+ bool weekvisible;
+ for (int j=3; j<9; j++)
+ {
+ sy = d->currh * j;
+ weekvisible = false;
+
+ for (int i=1; i<8; i++)
+ {
+ sx = d->currw * i;
+ r.moveTopLeft(TQPoint(sx,sy));
+ rsmall = TQRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
+ if (d->days[index].day != -1)
+ {
+ if (d->days[index].selected)
+ {
+ p.fillRect(r, cg.highlight());
+ p.setPen(cg.highlightedText());
+
+ if (d->days[index].active)
+ {
+ p.setFont(fnBold);
+ }
+ else
+ {
+ p.setFont(fnOrig);
+ }
+ }
+ else
+ {
+ if (d->days[index].active)
+ {
+ p.setPen(cg.text());
+ p.setFont(fnBold);
+ }
+ else
+ {
+ p.setPen(cg.mid());
+ p.setFont(fnOrig);
+ }
+ }
+
+ p.drawText(rsmall, TQt::AlignVCenter|TQt::AlignHCenter,
+ TQString::number(d->days[index].day));
+
+ if(!weekvisible)
+ {
+ int weeknr = TDEGlobal::locale()->calendar()->weekNumber(TQDate(d->year,
+ d->month, d->days[index].day));
+ p.setPen(d->active ? TQt::black : TQt::gray);
+ p.setFont(fnBold);
+ p.fillRect(1, sy, d->currw-1, d->currh-1, TQColor(210,210,210));
+ p.drawText(1, sy, d->currw-1, d->currh-1, TQt::AlignVCenter|TQt::AlignHCenter,
+ TQString::number(weeknr));
+ weekvisible = true;
+ }
+
+ }
+
+ index++;
+ }
+ }
+
+ p.setPen(d->active ? TQt::black : TQt::gray);
+
+ p.setFont(fnBold);
+
+ sy = 2*d->currh;
+ for (int i=1; i<8; i++)
+ {
+ sx = d->currw * i;
+ r.moveTopLeft(TQPoint(sx+1,sy+1));
+ rsmall = r;
+ rsmall.setWidth(r.width() - 2);
+ rsmall.setHeight(r.height() - 2);
+#if KDE_IS_VERSION(3,2,0)
+ p.drawText(rsmall, TQt::AlignVCenter|TQt::AlignHCenter,
+ TDEGlobal::locale()->calendar()->weekDayName(i, true)
+ .remove(2,1));
+#else
+ p.drawText(rsmall, TQt::AlignVCenter|TQt::AlignHCenter,
+ TDEGlobal::locale()->weekDayName(i, true).remove(2,1));
+#endif
+ index++;
+ }
+
+ r = TQRect(0, 0, cr.width(), 2*d->currh);
+
+ fnBold.setPointSize(fnBold.pointSize()+2);
+ p.setFont(fnBold);
+
+#if KDE_IS_VERSION(3,2,0)
+ p.drawText(r, TQt::AlignCenter,
+ TQString("%1 %2")
+ .arg(TDEGlobal::locale()->calendar()->monthName(d->month, false))
+ .arg(TDEGlobal::locale()->calendar()->year(TQDate(d->year,d->month,1))));
+#else
+ p.drawText(r, TQt::AlignCenter,
+ TQString("%1 %2")
+ .arg(TDEGlobal::locale()->monthName(d->month))
+ .arg(TQString::number(d->year)));
+#endif
+
+ p.end();
+
+ bitBlt(this, cr.x(), cr.y(), &pix);
+}
+
+void MonthWidget::mousePressEvent(TQMouseEvent *e)
+{
+ int firstSelected = 0, lastSelected = 0;
+ if (e->state() != TQt::ControlButton)
+ {
+ for (int i=0; i<42; i++)
+ {
+ if (d->days[i].selected)
+ {
+ if (firstSelected==0)
+ firstSelected = i;
+ lastSelected =i;
+ }
+ d->days[i].selected = false;
+ }
+ }
+
+ TQRect r1(0, d->currh*3, d->currw, d->currh*6);
+ TQRect r2(d->currw, d->currh*3, d->currw*7, d->currh*6);
+ TQRect r3(d->currw, d->currh*2, d->currw*7, d->currh);
+
+ // Click on a weekday
+ if( r3.contains(e->pos()))
+ {
+ int j = (e->pos().x() - d->currw)/d->currw;
+ for (int i=0; i<6; i++)
+ {
+ d->days[i*7+j].selected = !d->days[i*7+j].selected;
+ }
+ }
+ // Click on a week
+ else if (r1.contains(e->pos()))
+ {
+ int j = (e->pos().y() - 3*d->currh)/d->currh;
+ for (int i=0; i<7; i++)
+ {
+ d->days[j*7+i].selected = !d->days[j*7+i].selected;
+ }
+ }
+ // Click on a day.
+ else if (r2.contains(e->pos()))
+ {
+ int i, j;
+ i = (e->pos().x() - d->currw)/d->currw;
+ j = (e->pos().y() - 3*d->currh)/d->currh;
+ if (e->state() == TQt::ShiftButton)
+ {
+ int endSelection = j*7+i;
+ if (endSelection > firstSelected)
+ for (int i2=firstSelected ; i2 <= endSelection; i2++)
+ d->days[i2].selected = true;
+ else if (endSelection < firstSelected)
+ for (int i2=lastSelected ; i2 >= endSelection; i2--)
+ d->days[i2].selected = true;
+ }
+ else
+ d->days[j*7+i].selected = !d->days[j*7+i].selected;
+ }
+
+ TQValueList<TQDateTime> filterDays;
+ for (int i=0; i<42; i++)
+ {
+ if (d->days[i].selected && d->days[i].day != -1)
+ filterDays.append(TQDateTime(TQDate(d->year, d->month, d->days[i].day), TQTime()));
+ }
+
+ AlbumLister::instance()->setDayFilter(filterDays);
+
+ update();
+}
+
+void MonthWidget::setActive(bool val)
+{
+ if (d->active == val)
+ return;
+
+ d->active = val;
+
+ if (d->active)
+ {
+ connect(AlbumLister::instance(), TQ_SIGNAL(signalNewItems(const ImageInfoList&)),
+ this, TQ_SLOT(slotAddItems(const ImageInfoList&)));
+
+ connect(AlbumLister::instance(), TQ_SIGNAL(signalDeleteItem(ImageInfo*)),
+ this, TQ_SLOT(slotDeleteItem(ImageInfo*)));
+ }
+ else
+ {
+ TQDate date = TQDate::currentDate();
+ setYearMonth(date.year(), date.month());
+ AlbumLister::instance()->setDayFilter(TQValueList<TQDateTime>());
+
+ disconnect(AlbumLister::instance());
+ }
+}
+
+void MonthWidget::slotAddItems(const ImageInfoList& items)
+{
+ if (!d->active)
+ return;
+
+ ImageInfo* item;
+ for (ImageInfoListIterator it(items); (item = it.current()); ++it)
+ {
+ TQDateTime dt = item->dateTime();
+
+ for (int i=0; i<42; i++)
+ {
+ if (d->days[i].day == dt.date().day())
+ {
+ d->days[i].active = true;
+ d->days[i].numImages++;
+ break;
+ }
+ }
+ }
+
+ update();
+}
+
+void MonthWidget::slotDeleteItem(ImageInfo* item)
+{
+ if (!d->active || !item)
+ return;
+
+ TQDateTime dt = item->dateTime();
+
+ for (int i=0; i<42; i++)
+ {
+ if (d->days[i].day == dt.date().day())
+ {
+ d->days[i].numImages--;
+ if (d->days[i].numImages <= 0)
+ {
+ d->days[i].active = false;
+ d->days[i].numImages = 0;
+ }
+
+ break;
+ }
+ }
+
+ update();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/monthwidget.h b/src/digikam/monthwidget.h
new file mode 100644
index 00000000..70988c51
--- /dev/null
+++ b/src/digikam/monthwidget.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-02
+ * Description : a widget to perform month selection.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MONTHWIDGET_H
+#define MONTHWIDGET_H
+
+// TQt includes.
+
+#include <tqframe.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+
+namespace Digikam
+{
+class MonthWidgetPriv;
+
+class MonthWidget : public TQFrame
+{
+ TQ_OBJECT
+
+public:
+
+ MonthWidget(TQWidget* parent);
+ ~MonthWidget();
+
+ void setYearMonth(int year, int month);
+ TQSize sizeHint() const;
+
+ void setActive(bool val);
+
+protected:
+
+ void resizeEvent(TQResizeEvent *e);
+ void drawContents(TQPainter *p);
+ void mousePressEvent(TQMouseEvent *e);
+
+private:
+
+ void init();
+
+private slots:
+
+ void slotAddItems(const ImageInfoList& items);
+ void slotDeleteItem(ImageInfo* item);
+
+private:
+
+ MonthWidgetPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* MONTHWIDGET_H */
diff --git a/src/digikam/pixmapmanager.cpp b/src/digikam/pixmapmanager.cpp
new file mode 100644
index 00000000..3da11b5a
--- /dev/null
+++ b/src/digikam/pixmapmanager.cpp
@@ -0,0 +1,252 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-14
+ * Description : a pixmap manager for album icon view.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+}
+
+// TQt includes.
+
+#include <tqcache.h>
+#include <tqguardedptr.h>
+#include <tqpixmap.h>
+#include <tqdir.h>
+#include <tqfile.h>
+#include <tqtimer.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <kmdcodec.h>
+#include <kiconloader.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "thumbnailjob.h"
+#include "albumiconview.h"
+#include "albumiconitem.h"
+#include "albumsettings.h"
+#include "pixmapmanager.h"
+#include "pixmapmanager.moc"
+
+/** @file pixmapmanager.cpp */
+
+namespace Digikam
+{
+
+class PixmapManagerPriv
+{
+
+public:
+
+ PixmapManagerPriv()
+ {
+ size = 0;
+ cache = 0;
+ view = 0;
+ timer = 0;
+ thumbJob = 0;
+ }
+
+ int size;
+
+ TQCache<TQPixmap> *cache;
+ TQGuardedPtr<ThumbnailJob> thumbJob;
+ TQTimer *timer;
+ TQString thumbCacheDir;
+
+ AlbumIconView *view;
+};
+
+PixmapManager::PixmapManager(AlbumIconView* view)
+{
+ d = new PixmapManagerPriv;
+ d->view = view;
+ d->cache = new TQCache<TQPixmap>(101, 211);
+ d->cache->setAutoDelete(true);
+ d->thumbCacheDir = TQDir::homeDirPath() + "/.thumbnails/";
+
+ d->timer = new TQTimer(this);
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotCompleted()));
+}
+
+PixmapManager::~PixmapManager()
+{
+ delete d->timer;
+
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ }
+
+ delete d->cache;
+ delete d;
+}
+
+void PixmapManager::setThumbnailSize(int size)
+{
+ if (d->size == size)
+ return;
+
+ d->size = size;
+ d->cache->clear();
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+}
+
+TQPixmap* PixmapManager::find(const KURL& url)
+{
+ TQPixmap* pix = d->cache->find(url.path());
+ if (pix)
+ return pix;
+
+ if (d->thumbJob.isNull())
+ {
+ d->thumbJob = new ThumbnailJob(url, d->size, true, AlbumSettings::instance()->getExifRotate());
+
+ connect(d->thumbJob, TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnail(const KURL&, const TQPixmap&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalFailed(const KURL&)),
+ this, TQ_SLOT(slotFailedThumbnail(const KURL&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalCompleted()),
+ this, TQ_SLOT(slotCompleted()));
+ }
+
+ return 0;
+}
+
+void PixmapManager::remove(const KURL& url)
+{
+ d->cache->remove(url.path());
+
+ if (!d->thumbJob.isNull())
+ d->thumbJob->removeItem(url);
+
+ // remove the items from the thumbnail cache directory as well.
+ TQString uri = "file://" + TQDir::cleanDirPath(url.path());
+ KMD5 md5(TQFile::encodeName(uri).data());
+ uri = md5.hexDigest();
+
+ TQString smallThumbPath = d->thumbCacheDir + "normal/" + uri + ".png";
+ TQString bigThumbPath = d->thumbCacheDir + "large/" + uri + ".png";
+
+ ::unlink(TQFile::encodeName(smallThumbPath));
+ ::unlink(TQFile::encodeName(bigThumbPath));
+}
+
+void PixmapManager::clear()
+{
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ d->cache->clear();
+}
+
+void PixmapManager::slotGotThumbnail(const KURL& url, const TQPixmap& pix)
+{
+ d->cache->remove(url.path());
+ TQPixmap* thumb = new TQPixmap(pix);
+ d->cache->insert(url.path(), thumb);
+ emit signalPixmap(url);
+}
+
+void PixmapManager::slotFailedThumbnail(const KURL& url)
+{
+ TQImage img;
+ TQString ext = TQFileInfo(url.path()).extension(false);
+
+ // Wrapper around mime type of item to get the right icon.
+
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (settings)
+ {
+ if (settings->getImageFileFilter().upper().contains(ext.upper()) ||
+ settings->getRawFileFilter().upper().contains(ext.upper()))
+ {
+ img = DesktopIcon("image-x-generic", TDEIcon::SizeEnormous).convertToImage();
+ }
+ else if (settings->getMovieFileFilter().upper().contains(ext.upper()))
+ {
+ img = DesktopIcon("video-x-generic", TDEIcon::SizeEnormous).convertToImage();
+ }
+ else if (settings->getAudioFileFilter().upper().contains(ext.upper()))
+ {
+ img = DesktopIcon("audio-x-generic", TDEIcon::SizeEnormous).convertToImage();
+ }
+ }
+
+ if (img.isNull())
+ img = DesktopIcon("file_broken", TDEIcon::SizeEnormous).convertToImage();
+
+ // Resize icon to the right size depending of current settings.
+
+ TQSize size(img.size());
+ size.scale(d->size, d->size, TQSize::ScaleMin);
+ if (size.width() < img.width() && size.height() < img.height())
+ {
+ // only scale down
+ // do not scale up, looks bad
+ img = img.smoothScale(size);
+ }
+
+ d->cache->remove(url.path());
+ TQPixmap* thumb = new TQPixmap(img);
+ d->cache->insert(url.path(), thumb);
+ emit signalPixmap(url);
+}
+
+void PixmapManager::slotCompleted()
+{
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ AlbumIconItem* item = d->view->nextItemToThumbnail();
+ if (!item)
+ return;
+
+ find(item->imageInfo()->kurl());
+}
+
+int PixmapManager::cacheSize() const
+{
+ return d->cache->maxCost();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/pixmapmanager.h b/src/digikam/pixmapmanager.h
new file mode 100644
index 00000000..62da7fa1
--- /dev/null
+++ b/src/digikam/pixmapmanager.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-14
+ * Description : a pixmap manager for album icon view.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PIXMAPMANAGER_H
+#define PIXMAPMANAGER_H
+
+// TQt includes.
+
+#include <tqobject.h>
+
+/** @file pixmapmanager.h */
+
+class TQPixmap;
+
+class KURL;
+
+namespace Digikam
+{
+
+class AlbumIconView;
+class PixmapManagerPriv;
+
+/**
+ * Since there are date based folders, the number of pixmaps which
+ * could be kept in memory could potentially become too large. The
+ * pixmapmanager maintains a fixed size cache of thumbnails and loads
+ * pixmaps on demand.
+ */
+class PixmapManager : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ PixmapManager(AlbumIconView* view);
+ ~PixmapManager();
+
+ TQPixmap* find(const KURL& path);
+ void remove(const KURL& path);
+ void clear();
+ void setThumbnailSize(int size);
+ int cacheSize() const;
+
+signals:
+
+ void signalPixmap(const KURL& url);
+
+private slots:
+
+ void slotGotThumbnail(const KURL& url, const TQPixmap& pix);
+ void slotFailedThumbnail(const KURL& url);
+ void slotCompleted();
+
+private:
+
+ PixmapManagerPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* PIXMAPMANAGER_H */
diff --git a/src/digikam/ratingfilter.cpp b/src/digikam/ratingfilter.cpp
new file mode 100644
index 00000000..1cfa0bbf
--- /dev/null
+++ b/src/digikam/ratingfilter.cpp
@@ -0,0 +1,205 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-10-09
+ * Description : a widget to filter album contents by rating
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpalette.h>
+#include <tqpixmap.h>
+#include <tqcursor.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+#include <tdepopupmenu.h>
+
+// Local includes.
+
+#include "constants.h"
+#include "ddebug.h"
+#include "dcursortracker.h"
+#include "themeengine.h"
+#include "ratingfilter.h"
+#include "ratingfilter.moc"
+
+namespace Digikam
+{
+
+class RatingFilterPriv
+{
+public:
+
+ RatingFilterPriv()
+ {
+ dirty = false;
+ ratingTracker = 0;
+ filterCond = AlbumLister::GreaterEqualCondition;
+ }
+
+ bool dirty;
+
+ DTipTracker *ratingTracker;
+
+ AlbumLister::RatingCondition filterCond;
+};
+
+RatingFilter::RatingFilter(TQWidget* parent)
+ : RatingWidget(parent)
+{
+ d = new RatingFilterPriv;
+
+ d->ratingTracker = new DTipTracker("", this);
+ updateRatingTooltip();
+ setMouseTracking(true);
+
+ TQWhatsThis::add(this, i18n("Select the rating value used to filter "
+ "albums contents. Use contextual pop-up menu to "
+ "set rating filter condition."));
+
+ // To dispatch signal from parent widget with filter condition.
+ connect(this, TQ_SIGNAL(signalRatingChanged(int)),
+ this, TQ_SLOT(slotRatingChanged()));
+}
+
+RatingFilter::~RatingFilter()
+{
+ delete d->ratingTracker;
+ delete d;
+}
+
+void RatingFilter::slotRatingChanged()
+{
+ emit signalRatingFilterChanged(rating(), d->filterCond);
+}
+
+void RatingFilter::setRatingFilterCondition(AlbumLister::RatingCondition cond)
+{
+ d->filterCond = cond;
+ updateRatingTooltip();
+ slotRatingChanged();
+}
+
+AlbumLister::RatingCondition RatingFilter::ratingFilterCondition()
+{
+ return d->filterCond;
+}
+
+void RatingFilter::mouseMoveEvent(TQMouseEvent* e)
+{
+ // This method have been re-implemented to display and update the famous TipTracker contents.
+
+ if ( d->dirty )
+ {
+ int pos = e->x() / regPixmapWidth() +1;
+
+ if (rating() != pos)
+ setRating(pos);
+
+ updateRatingTooltip();
+ }
+}
+
+void RatingFilter::mousePressEvent(TQMouseEvent* e)
+{
+ // This method must be re-implemented to handle witch mousse button is pressed
+ // and show the rating filter settings pop-up menu with right mouse button.
+ // NOTE: Left and Middle Mouse buttons continue to change rating filter value.
+
+ d->dirty = false;
+
+ if ( e->button() == TQt::LeftButton || e->button() == TQt::MidButton )
+ {
+ d->dirty = true;
+ int pos = e->x() / regPixmapWidth() +1;
+
+ if (rating() == pos)
+ setRating(rating()-1);
+ else
+ setRating(pos);
+ updateRatingTooltip();
+ }
+ else if (e->button() == TQt::RightButton)
+ {
+ // Show pop-up menu about Rating Filter condition settings
+
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("Rating Filter"));
+ popmenu.setCheckable(true);
+ popmenu.insertItem(i18n("Greater Equal Condition"), AlbumLister::GreaterEqualCondition);
+ popmenu.insertItem(i18n("Equal Condition"), AlbumLister::EqualCondition);
+ popmenu.insertItem(i18n("Less Equal Condition"), AlbumLister::LessEqualCondition);
+ popmenu.setItemChecked(d->filterCond, true);
+
+ int choice = popmenu.exec((TQCursor::pos()));
+ switch(choice)
+ {
+ case AlbumLister::GreaterEqualCondition:
+ case AlbumLister::EqualCondition:
+ case AlbumLister::LessEqualCondition:
+ {
+ setRatingFilterCondition((AlbumLister::RatingCondition)choice);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+void RatingFilter::mouseReleaseEvent(TQMouseEvent*)
+{
+ d->dirty = false;
+}
+
+void RatingFilter::updateRatingTooltip()
+{
+ // Adapt tip message with rating filter condition settings.
+
+ switch(d->filterCond)
+ {
+ case AlbumLister::GreaterEqualCondition:
+ {
+ d->ratingTracker->setText(i18n("Rating >= %1").arg(rating()));
+ break;
+ }
+ case AlbumLister::EqualCondition:
+ {
+ d->ratingTracker->setText(i18n("Rating = %1").arg(rating()));
+ break;
+ }
+ case AlbumLister::LessEqualCondition:
+ {
+ d->ratingTracker->setText(i18n("Rating <= %1").arg(rating()));
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+} // namespace Digikam
diff --git a/src/digikam/ratingfilter.h b/src/digikam/ratingfilter.h
new file mode 100644
index 00000000..ac527e49
--- /dev/null
+++ b/src/digikam/ratingfilter.h
@@ -0,0 +1,75 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-10-09
+ * Description : a widget to filter album contents by rating
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef RATINGFILTER_H
+#define RATINGFILTER_H
+
+// Local includes.
+
+#include "albumlister.h"
+#include "ratingwidget.h"
+
+namespace Digikam
+{
+
+class RatingFilterPriv;
+
+class RatingFilter : public RatingWidget
+{
+ TQ_OBJECT
+
+public:
+
+ RatingFilter(TQWidget* parent);
+ ~RatingFilter();
+
+ void setRatingFilterCondition(AlbumLister::RatingCondition cond);
+ AlbumLister::RatingCondition ratingFilterCondition();
+
+signals:
+
+ void signalRatingFilterChanged(int, AlbumLister::RatingCondition);
+
+protected:
+
+ void mousePressEvent(TQMouseEvent*);
+ void mouseMoveEvent(TQMouseEvent*);
+ void mouseReleaseEvent(TQMouseEvent*);
+
+private:
+
+ void updateRatingTooltip();
+
+private slots:
+
+ void slotRatingChanged();
+
+private:
+
+ RatingFilterPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // RATINGWIDGET_H
diff --git a/src/digikam/ratingpopupmenu.cpp b/src/digikam/ratingpopupmenu.cpp
new file mode 100644
index 00000000..a1d17bab
--- /dev/null
+++ b/src/digikam/ratingpopupmenu.cpp
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-02-02
+ * Description : a pop-up menu to show stars rating selection.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqbitmap.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "constants.h"
+#include "themeengine.h"
+#include "ratingpopupmenu.h"
+#include "ratingpopupmenu.moc"
+
+namespace Digikam
+{
+
+RatingPopupMenu::RatingPopupMenu(TQWidget* parent)
+ : TQPopupMenu(parent)
+{
+ TDEGlobal::dirs()->addResourceType("digikam_rating", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString ratingPixPath = TDEGlobal::dirs()->findResourceDir("digikam_rating", "rating.png");
+ ratingPixPath += "/rating.png";
+
+ insertItem(i18n("None"), 0);
+
+ TQBitmap starbm(ratingPixPath);
+ TQBitmap clearbm(starbm.width(), starbm.height(), true);
+
+ for (int i = 1 ; i <= RatingMax ; i++)
+ {
+ TQPixmap pix(starbm.width() * 5, starbm.height());
+ pix.fill(ThemeEngine::instance()->textSpecialRegColor());
+ TQBitmap mask(starbm.width() * 5, starbm.height());
+ TQPainter painter(&mask);
+ painter.drawTiledPixmap(0, 0,
+ i*starbm.width(), pix.height(),
+ starbm);
+ painter.drawTiledPixmap(i*starbm.width(), 0,
+ 5*starbm.width()-i*starbm.width(), pix.height(),
+ clearbm);
+ painter.end();
+ pix.setMask(mask);
+ insertItem(pix, i);
+ }
+}
+
+RatingPopupMenu::~RatingPopupMenu()
+{
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/ratingpopupmenu.h b/src/digikam/ratingpopupmenu.h
new file mode 100644
index 00000000..14b2fef4
--- /dev/null
+++ b/src/digikam/ratingpopupmenu.h
@@ -0,0 +1,49 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-02-02
+ * Description : a pop-up menu to show stars rating selection.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RATING_POPUP_MENU_H
+#define RATING_POPUP_MENU_H
+
+// TQt includes.
+
+#include <tqpopupmenu.h>
+
+namespace Digikam
+{
+
+class RatingPopupMenuPriv;
+
+class RatingPopupMenu : public TQPopupMenu
+{
+ TQ_OBJECT
+
+public:
+
+ RatingPopupMenu(TQWidget* parent=0);
+ ~RatingPopupMenu();
+
+};
+
+} // namespace Digikam
+
+#endif // RATING_POPUP_MENU_H
diff --git a/src/digikam/ratingwidget.cpp b/src/digikam/ratingwidget.cpp
new file mode 100644
index 00000000..3371a2c6
--- /dev/null
+++ b/src/digikam/ratingwidget.cpp
@@ -0,0 +1,195 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-08-15
+ * Description : a widget to draw stars rating
+ *
+ * Copyright (C) 2005 by Owen Hirst <n8rider@sbcglobal.net>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpalette.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "constants.h"
+#include "themeengine.h"
+#include "ratingwidget.h"
+#include "ratingwidget.moc"
+
+namespace Digikam
+{
+
+class RatingWidgetPriv
+{
+public:
+
+ RatingWidgetPriv()
+ {
+ rating = 0;
+ }
+
+ int rating;
+
+ TQString ratingPixPath;
+
+ TQPixmap disPixmap;
+ TQPixmap selPixmap;
+ TQPixmap regPixmap;
+};
+
+RatingWidget::RatingWidget(TQWidget* parent)
+ : TQWidget(parent)
+{
+ d = new RatingWidgetPriv;
+
+ TDEGlobal::dirs()->addResourceType("digikam_rating",
+ TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ d->ratingPixPath = TDEGlobal::dirs()->findResourceDir("digikam_rating", "rating.png");
+ d->ratingPixPath.append("/rating.png");
+
+ slotThemeChanged();
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+}
+
+RatingWidget::~RatingWidget()
+{
+ delete d;
+}
+
+void RatingWidget::setRating(int val)
+{
+ if (val < RatingMin || val > RatingMax) return;
+
+ d->rating = val;
+ emit signalRatingChanged(d->rating);
+ update();
+}
+
+int RatingWidget::rating() const
+{
+ return d->rating;
+}
+
+int RatingWidget::regPixmapWidth() const
+{
+ return d->regPixmap.width();
+}
+
+void RatingWidget::mouseMoveEvent(TQMouseEvent* e)
+{
+ int pos = e->x() / d->regPixmap.width() +1;
+
+ if (d->rating != pos)
+ {
+ if (pos > RatingMax) // B.K.O.: # 151357
+ pos = RatingMax;
+ if (pos < RatingMin)
+ pos = RatingMin;
+ d->rating = pos;
+ emit signalRatingChanged(d->rating);
+ update();
+ }
+}
+
+void RatingWidget::mousePressEvent(TQMouseEvent* e)
+{
+ int pos = e->x() / d->regPixmap.width() +1;
+
+ if (d->rating == pos)
+ {
+ d->rating--;
+ }
+ else
+ {
+ d->rating = pos;
+ }
+
+ emit signalRatingChanged(d->rating);
+
+ update();
+}
+
+void RatingWidget::paintEvent(TQPaintEvent*)
+{
+ TQPainter p(this);
+ int x = 0;
+
+ // Widget is disable : drawing grayed frame.
+ if (!isEnabled())
+ {
+ for (int i=0; i<RatingMax; i++)
+ {
+ p.drawPixmap(x, 0, d->disPixmap);
+ x += d->disPixmap.width();
+ }
+ }
+ else
+ {
+ for (int i=0; i<d->rating; i++)
+ {
+ p.drawPixmap(x, 0, d->selPixmap);
+ x += d->selPixmap.width();
+ }
+
+ for (int i=d->rating; i<RatingMax; i++)
+ {
+ p.drawPixmap(x, 0, d->regPixmap);
+ x += d->regPixmap.width();
+ }
+ }
+
+ p.end();
+}
+
+void RatingWidget::slotThemeChanged()
+{
+ d->regPixmap = TQPixmap(d->ratingPixPath);
+ d->selPixmap = d->regPixmap;
+ d->disPixmap = d->regPixmap;
+
+ TQPainter painter(&d->regPixmap);
+ painter.fillRect(0, 0, d->regPixmap.width(), d->regPixmap.height(),
+ colorGroup().dark());
+ painter.end();
+
+ TQPainter painter2(&d->selPixmap);
+ painter2.fillRect(0, 0, d->selPixmap.width(), d->selPixmap.height(),
+ ThemeEngine::instance()->textSpecialRegColor());
+ painter2.end();
+
+ TQPainter painter3(&d->disPixmap);
+ painter3.fillRect(0, 0, d->disPixmap.width(), d->disPixmap.height(),
+ palette().disabled().foreground());
+ painter3.end();
+
+ setFixedSize(TQSize(d->regPixmap.width()*5, d->regPixmap.height()));
+ update();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/ratingwidget.h b/src/digikam/ratingwidget.h
new file mode 100644
index 00000000..a6a6ab57
--- /dev/null
+++ b/src/digikam/ratingwidget.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-08-15
+ * Description : a widget to draw stars rating
+ *
+ * Copyright (C) 2005 by Owen Hirst <n8rider@sbcglobal.net>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RATINGWIDGET_H
+#define RATINGWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class RatingWidgetPriv;
+
+class RatingWidget : public TQWidget
+{
+ TQ_OBJECT
+
+public:
+
+ RatingWidget(TQWidget* parent);
+ virtual ~RatingWidget();
+
+ void setRating(int val);
+ int rating() const;
+
+signals:
+
+ void signalRatingChanged(int);
+
+protected:
+
+ int regPixmapWidth() const;
+
+ virtual void mousePressEvent(TQMouseEvent*);
+ virtual void mouseMoveEvent(TQMouseEvent*);
+ virtual void paintEvent(TQPaintEvent*);
+
+private slots:
+
+ void slotThemeChanged();
+
+private:
+
+ RatingWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // RATINGWIDGET_H
diff --git a/src/digikam/scanlib.cpp b/src/digikam/scanlib.cpp
new file mode 100644
index 00000000..71ea425c
--- /dev/null
+++ b/src/digikam/scanlib.cpp
@@ -0,0 +1,541 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description : scan pictures interface.
+ *
+ * Copyright (C) 2005-2006 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/time.h>
+}
+
+// C++ includes.
+
+#include <ctime>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqapplication.h>
+#include <tqstring.h>
+#include <tqstringlist.h>
+#include <tqmap.h>
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqwhatsthis.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kprogress.h>
+#include <tdemessagebox.h>
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dprogressdlg.h"
+#include "dmetadata.h"
+#include "albumdb.h"
+#include "albummanager.h"
+#include "splashscreen.h"
+#include "scanlib.h"
+
+/** @file scanlib.cpp*/
+
+namespace Digikam
+{
+
+ScanLib::ScanLib(SplashScreen *splash)
+{
+ m_splash = splash;
+ m_progressBar = new DProgressDlg(0);
+ m_progressBar->setInitialSize(TQSize(500, 100), true);
+ m_progressBar->setActionListVSBarVisible(false);
+ TQWhatsThis::add( m_progressBar, i18n("This shows the progress of the "
+ "scan. During the scan, all files on disk are registered in a "
+ "database. This is required for sorting by exif-date, and also speeds up "
+ "the overall performance of digiKam.") );
+
+ // these two lines prevent the dialog to be shown in
+ // findFoldersWhichDoNotExist() method.
+ m_progressBar->progressBar()->setTotalSteps(1);
+ m_progressBar->progressBar()->setProgress(1);
+}
+
+ScanLib::~ScanLib()
+{
+ delete m_progressBar;
+}
+
+void ScanLib::startScan()
+{
+ struct timeval tv1, tv2;
+ TQPixmap pix = TDEApplication::kApplication()->iconLoader()->loadIcon(
+ "system-run", TDEIcon::NoGroup, 32);
+
+ TQString message = i18n("Finding non-existent Albums");
+ if (m_splash) m_splash->message(message);
+ else m_progressBar->addedAction(pix, message);
+ gettimeofday(&tv1, 0);
+ findFoldersWhichDoNotExist();
+ gettimeofday(&tv2, 0);
+ timing(message, tv1, tv2);
+
+ message = i18n("Finding items not in database");
+ if (m_splash) m_splash->message(message);
+ else m_progressBar->addedAction(pix, message);
+ gettimeofday(&tv1, 0);
+ findMissingItems();
+ gettimeofday(&tv2, 0);
+ timing(message, tv1, tv2);
+
+ message = i18n("Updating items without a date");
+ if (m_splash) m_splash->message(message);
+ else m_progressBar->addedAction(pix, message);
+ gettimeofday(&tv1, 0);
+ updateItemsWithoutDate();
+ gettimeofday(&tv2, 0);
+ timing(message, tv1, tv2);
+
+ deleteStaleEntries();
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ db->setSetting("Scanned", TQDateTime::currentDateTime().toString(TQt::ISODate));
+}
+
+void ScanLib::findFoldersWhichDoNotExist()
+{
+ TQMap<TQString, int> toBeDeleted;
+ TQString basePath(AlbumManager::instance()->getLibraryPath());
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ AlbumInfo::List aList = db->scanAlbums();
+
+ for (AlbumInfo::List::iterator it = aList.begin(); it != aList.end(); ++it)
+ {
+ AlbumInfo info = *it;
+ info.url = TQDir::cleanDirPath(info.url);
+ TQFileInfo fi(basePath + info.url);
+ if (!fi.exists() || !fi.isDir())
+ {
+ toBeDeleted[info.url] = info.id;
+ }
+ }
+
+ kapp->processEvents();
+
+ if (!toBeDeleted.isEmpty())
+ {
+ int rc = KMessageBox::warningYesNoList(0,
+ i18n("<p>There is an album in the database which does not appear to "
+ "be on disk. This album should be removed from the database, "
+ "however you may lose information because all images "
+ "associated with this album will be removed from the database "
+ "as well.<p>"
+ "digiKam cannot continue without removing the items "
+ "from the database because all views depend on the information "
+ "in the database. Do you want them to be removed from the "
+ "database?",
+ "<p>There are %n albums in the database which do not appear to "
+ "be on disk. These albums should be removed from the database, "
+ "however you may lose information because all images "
+ "associated with these albums will be removed from the database "
+ "as well.<p>"
+ "digiKam cannot continue without removing the items "
+ "from the database because all views depend on the information "
+ "in the database. Do you want them to be removed from the "
+ "database?",
+ toBeDeleted.count()),
+ toBeDeleted.keys(),
+ i18n("Albums are Missing"));
+
+ if (rc != KMessageBox::Yes)
+ exit(0);
+
+ TQMapIterator<TQString,int> it;
+ for (it = toBeDeleted.begin() ; it != toBeDeleted.end(); ++it)
+ {
+ DDebug() << "Removing Album: " << it.key() << endl;
+ db->deleteAlbum( it.data() );
+ }
+ }
+}
+
+void ScanLib::findMissingItems(const TQString &path)
+{
+ allFiles(path);
+}
+
+void ScanLib::findMissingItems()
+{
+ TQString albumPath = AlbumManager::instance()->getLibraryPath();
+ albumPath = TQDir::cleanDirPath(albumPath);
+
+ m_progressBar->setAllowCancel( false );
+ m_progressBar->showCancelButton (false );
+ m_progressBar->progressBar()->setProgress( 0 );
+ m_progressBar->setLabel(i18n("Scanning items, please wait..."));
+ m_progressBar->progressBar()->setTotalSteps( countItemsInFolder( albumPath ) );
+ if (!m_splash) m_progressBar->show();
+ kapp->processEvents();
+
+ TQDir dir(albumPath);
+ TQStringList fileList(dir.entryList(TQDir::Dirs));
+ TQPixmap pix = TDEApplication::kApplication()->iconLoader()->loadIcon(
+ "folder_image", TDEIcon::NoGroup, 32);
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ db->beginTransaction();
+
+ for (TQStringList::iterator it = fileList.begin(); it != fileList.end(); ++it)
+ {
+ if ((*it) == "." || (*it) == "..")
+ continue;
+
+ TQString path = albumPath + '/' + (*it);
+ allFiles(path);
+ m_progressBar->addedAction(pix, path);
+ }
+ db->commitTransaction();
+
+ m_progressBar->hide();
+ kapp->processEvents();
+}
+
+void ScanLib::updateItemsWithoutDate()
+{
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ TQStringList urls = db->getAllItemURLsWithoutDate();
+
+ if (urls.isEmpty())
+ {
+ m_progressBar->progressBar()->setTotalSteps(1);
+ m_progressBar->progressBar()->setProgress(1);
+ m_progressBar->hide();
+ return;
+ }
+
+ m_progressBar->setAllowCancel( false );
+ m_progressBar->showCancelButton (false );
+ m_progressBar->progressBar()->setProgress(0);
+ m_progressBar->progressBar()->setTotalSteps(urls.count());
+ m_progressBar->setLabel(i18n("Updating items, please wait..."));
+ m_progressBar->show();
+ kapp->processEvents();
+
+ TQString basePath = AlbumManager::instance()->getLibraryPath();
+ basePath = TQDir::cleanDirPath(basePath);
+
+ db->beginTransaction();
+
+ int counter=0;
+ for (TQStringList::iterator it = urls.begin(); it != urls.end(); ++it)
+ {
+ m_progressBar->progressBar()->advance(1);
+ ++counter;
+ if ( counter % 30 == 0 )
+ {
+ kapp->processEvents();
+ }
+
+ TQFileInfo fi(*it);
+ TQString albumURL = fi.dirPath();
+ albumURL = TQDir::cleanDirPath(albumURL.remove(basePath));
+
+ int albumID = db->getOrCreateAlbumId(albumURL);
+
+ if (albumID <= 0)
+ {
+ DWarning() << "Album ID == -1: " << albumURL << endl;
+ }
+
+ if (fi.exists())
+ {
+ updateItemDate(albumURL, fi.fileName(), albumID);
+ }
+ else
+ {
+ TQPair<TQString, int> fileID = qMakePair(fi.fileName(),albumID);
+
+ if (m_filesToBeDeleted.findIndex(fileID) == -1)
+ {
+ m_filesToBeDeleted.append(fileID);
+ }
+ }
+ }
+
+ db->commitTransaction();
+
+ m_progressBar->hide();
+ kapp->processEvents();
+}
+
+int ScanLib::countItemsInFolder(const TQString& directory)
+{
+ int items = 0;
+
+ TQDir dir( directory );
+ if ( !dir.exists() or !dir.isReadable() )
+ return 0;
+
+ const TQFileInfoList *list = dir.entryInfoList();
+ TQFileInfoListIterator it( *list );
+ TQFileInfo *fi;
+
+ items += list->count();
+
+ while ( (fi = it.current()) != 0 )
+ {
+ if ( fi->isDir() &&
+ fi->fileName() != "." &&
+ fi->fileName() != "..")
+ {
+ items += countItemsInFolder( fi->filePath() );
+ }
+
+ ++it;
+ }
+
+ return items;
+}
+
+void ScanLib::allFiles(const TQString& directory)
+{
+ TQDir dir( directory );
+ if ( !dir.exists() or !dir.isReadable() )
+ {
+ DWarning() << "Folder does not exist or is not readable: "
+ << directory << endl;
+ return;
+ }
+
+ TQString basePath = AlbumManager::instance()->getLibraryPath();
+ basePath = TQDir::cleanDirPath(basePath);
+
+ TQString albumURL = directory;
+ albumURL = TQDir::cleanDirPath(albumURL.remove(basePath));
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+
+ int albumID = db->getOrCreateAlbumId(albumURL);
+
+ if (albumID <= 0)
+ {
+ DWarning() << "Album ID == -1: " << albumURL << endl;
+ }
+
+ TQStringList filesInAlbum = db->getItemNamesInAlbum( albumID );
+ TQMap<TQString, bool> filesFoundInDB;
+
+ for (TQStringList::iterator it = filesInAlbum.begin();
+ it != filesInAlbum.end(); ++it)
+ {
+ filesFoundInDB.insert(*it, true);
+ }
+
+ const TQFileInfoList *list = dir.entryInfoList();
+ if (!list)
+ return;
+
+ TQFileInfoListIterator it( *list );
+ TQFileInfo *fi;
+
+ m_progressBar->progressBar()->advance(list->count());
+ kapp->processEvents();
+
+ while ( (fi = it.current()) != 0 )
+ {
+ if ( fi->isFile())
+ {
+ if (filesFoundInDB.contains(fi->fileName()) )
+ {
+ filesFoundInDB.erase(fi->fileName());
+ }
+ else
+ {
+ storeItemInDatabase(albumURL, fi->fileName(), albumID);
+ }
+ }
+ else if ( fi->isDir() && fi->fileName() != "." && fi->fileName() != "..")
+ {
+ allFiles( fi->filePath() );
+ }
+
+ ++it;
+ }
+
+ // Removing items from the db which we did not see on disk.
+ if (!filesFoundInDB.isEmpty())
+ {
+ TQMapIterator<TQString,bool> it;
+ for (it = filesFoundInDB.begin(); it != filesFoundInDB.end(); ++it)
+ {
+ if (m_filesToBeDeleted.findIndex(qMakePair(it.key(),albumID)) == -1)
+ {
+ m_filesToBeDeleted.append(qMakePair(it.key(),albumID));
+ }
+ }
+ }
+}
+
+void ScanLib::storeItemInDatabase(const TQString& albumURL,
+ const TQString& filename,
+ int albumID)
+{
+ // Do not store items found in the root of the albumdb
+ if (albumURL.isEmpty())
+ return;
+
+ TQString comment;
+ TQStringList keywords;
+ TQDateTime datetime;
+ int rating;
+
+ TQString filePath( AlbumManager::instance()->getLibraryPath());
+ filePath += albumURL + '/' + filename;
+
+ DMetadata metadata(filePath);
+
+ // Try to get comments from image :
+ // In first, from standard JPEG comments, or
+ // In second, from EXIF comments tag, or
+ // In third, from IPTC comments tag.
+
+ comment = metadata.getImageComment();
+
+ // Try to get date and time from image :
+ // In first, from EXIF date & time tags, or
+ // In second, from IPTC date & time tags.
+
+ datetime = metadata.getImageDateTime();
+
+ // Try to get image rating from IPTC Urgency tag
+ // else use file system time stamp.
+ rating = metadata.getImageRating();
+
+ if ( !datetime.isValid() )
+ {
+ TQFileInfo info( filePath );
+ datetime = info.lastModified();
+ }
+
+ // Try to get image tags from IPTC keywords tags.
+
+ keywords = metadata.getImageKeywords();
+
+ AlbumDB* dbstore = AlbumManager::instance()->albumDB();
+ dbstore->addItem(albumID, filename, datetime, comment, rating, keywords);
+}
+
+void ScanLib::updateItemDate(const TQString& albumURL,
+ const TQString& filename,
+ int albumID)
+{
+ TQDateTime datetime;
+
+ TQString filePath( AlbumManager::instance()->getLibraryPath());
+ filePath += albumURL + '/' + filename;
+
+ DMetadata metadata(filePath);
+
+ // Trying to get date and time from image :
+ // In first, from EXIF date & time tags, or
+ // In second, from IPTC date & time tags.
+
+ datetime = metadata.getImageDateTime();
+
+ if ( !datetime.isValid() )
+ {
+ TQFileInfo info( filePath );
+ datetime = info.lastModified();
+ }
+
+ AlbumDB* dbstore = AlbumManager::instance()->albumDB();
+ dbstore->setItemDate(albumID, filename, datetime);
+}
+
+void ScanLib::deleteStaleEntries()
+{
+ TQStringList listToBeDeleted;
+ TQValueList< TQPair<TQString,int> >::iterator it;
+
+ for (it = m_filesToBeDeleted.begin() ; it != m_filesToBeDeleted.end(); ++it)
+ {
+ AlbumDB* dbstore = AlbumManager::instance()->albumDB();
+ TQString location = " (" + dbstore->getAlbumURL((*it).second) + ')';
+
+ listToBeDeleted.append((*it).first + location);
+ }
+
+ if ( !m_filesToBeDeleted.isEmpty() )
+ {
+ int rc = KMessageBox::warningYesNoList(0,
+ i18n("<p>There is an item in the database which does not "
+ "appear to be on disk or is located in the root album of "
+ "the path. This file should be removed from the "
+ "database, however you may lose information.<p>"
+ "digiKam cannot continue without removing the item from "
+ "the database because all views depend on the information "
+ "in the database. Do you want it to be removed from the "
+ "database?",
+ "<p>There are %n items in the database which do not "
+ "appear to be on disk or are located in the root album of "
+ "the path. These files should be removed from the "
+ "database, however you may lose information.<p>"
+ "digiKam cannot continue without removing these items from "
+ "the database because all views depend on the information "
+ "in the database. Do you want them to be removed from the "
+ "database?",
+ listToBeDeleted.count()),
+ listToBeDeleted,
+ i18n("Files are Missing"));
+
+ if (rc != KMessageBox::Yes)
+ exit(0);
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ db->beginTransaction();
+ for (it = m_filesToBeDeleted.begin() ; it != m_filesToBeDeleted.end();
+ ++it)
+ {
+ DDebug() << "Removing: " << (*it).first << " in "
+ << (*it).second << endl;
+ db->deleteItem( (*it).second, (*it).first );
+ }
+ db->commitTransaction();
+ }
+}
+
+void ScanLib::timing(const TQString& text, struct timeval tv1, struct timeval tv2)
+{
+ DDebug() << "ScanLib: "
+ << text + ": "
+ << (((tv2.tv_sec-tv1.tv_sec)*1000000 +
+ (tv2.tv_usec-tv1.tv_usec))/1000)
+ << " ms" << endl;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/scanlib.h b/src/digikam/scanlib.h
new file mode 100644
index 00000000..5e58b29b
--- /dev/null
+++ b/src/digikam/scanlib.h
@@ -0,0 +1,172 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description : scan pictures interface.
+ *
+ * Copyright (C) 2005-2006 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SCANLIB_H
+#define SCANLIB_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqmap.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+/** @file scanlib.h */
+
+namespace Digikam
+{
+
+class DProgressDlg;
+class SplashScreen;
+
+/**
+ * Class which is responsible for keeping the database in sync
+ * with the disk. Scanlib is a library that takes care of scanning the
+ * filesystem for new files and adds them in the database and checking
+ * for missing info in the database so that it can be included: if date
+ * is empty, it adds the exif or modification date (in that order) and
+ * the comment to database. If the file is not present in the database,
+ * make sure to add the file to the database and insert the date and
+ * comments.
+ */
+class DIGIKAM_EXPORT ScanLib
+{
+public:
+ /**
+ * Constructor
+ */
+ ScanLib(SplashScreen *splash=0);
+
+ /**
+ * Destructor
+ */
+ ~ScanLib();
+
+ /**
+ * This will execute findFoldersWhichDoNotExist(),
+ * findMissingItems() and updateItemsWithoutDate()
+ * and deletes all items from the database after confirmation.
+ */
+ void startScan();
+
+ /**
+ * This checks if all albums in the database still existing
+ * on the disk
+ */
+ void findFoldersWhichDoNotExist();
+
+ /**
+ * This calls allFiles with the albumPath.
+ */
+ void findMissingItems();
+
+
+ /**
+ * This calls allFiles with a given path.
+ * @param path the path to scan.
+ */
+ void findMissingItems(const TQString &path);
+
+ /**
+ * This queries the db for items that have no date
+ * for each item found, storeItemInDatabase is called.
+ */
+ void updateItemsWithoutDate();
+
+private:
+ /**
+ * This counts all existing files recursively, starting from
+ * directory.
+ * @param directory The path to the start searching from
+ * @return The amount of items
+ */
+ int countItemsInFolder(const TQString& directory);
+
+ /**
+ * This checks all existing files recursively, starting from
+ * directory. Calls storeItemInDatabase to store the found items
+ * which are not in the database.
+ * @param directory The path to the start searching from
+ */
+ void allFiles(const TQString& directory);
+
+ /**
+ * This fetches the exif-date or the modification date when
+ * the exif-date is not available and retrieves the JFIF-comment
+ * and calls AlbumDB::setItemDateComment to store the info in
+ * the db.
+ * @param albumURL The album path (relative to the
+ * album library Path)
+ * @param filename The filename of the item to store
+ * @param albumID The albumID as used in the database
+ */
+ void storeItemInDatabase(const TQString& albumURL,
+ const TQString& filename,
+ int albumID);
+
+ /**
+ * This fetches the exif-date or the modification date when
+ * the exif-date is not available and calls AlbumDB::setItemDate
+ * to store the info in
+ * the db.
+ * @param albumURL The album path (relative to the album library Path)
+ * @param filename The filename of the item to store
+ * @param albumID The albumID as used in the database
+ */
+ void updateItemDate(const TQString& albumURL,
+ const TQString& filename,
+ int albumID);
+
+ /**
+ * This will delete all items stored in m_filesToBeDeleted
+ */
+ void deleteStaleEntries();
+
+ /**
+ * Member variable so we can update the progress bar everywhere
+ */
+ DProgressDlg *m_progressBar;
+
+ SplashScreen *m_splash;
+
+ /**
+ * Member to store stale filesystem
+ */
+ TQValueList< TQPair<TQString,int> > m_filesToBeDeleted;
+
+ /**
+ * This is used to print out some timings.
+ */
+ void timing(const TQString& text, struct timeval tv1, struct timeval tv2);
+};
+
+} // namespace Digikam
+
+#endif /* SCANLIB_H */
diff --git a/src/digikam/searchadvanceddialog.cpp b/src/digikam/searchadvanceddialog.cpp
new file mode 100644
index 00000000..cb40b87b
--- /dev/null
+++ b/src/digikam/searchadvanceddialog.cpp
@@ -0,0 +1,664 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description : a dialog to perform advanced search in albums
+ *
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file searchadvanceddialog.cpp */
+
+// TQt includes.
+
+#include <tqvbox.h>
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqcombobox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqtimer.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <klineedit.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "searchwidgets.h"
+#include "searchresultsview.h"
+#include "searchadvanceddialog.h"
+#include "searchadvanceddialog.moc"
+
+namespace Digikam
+{
+
+class SearchAdvancedDialogPriv
+{
+public:
+
+ SearchAdvancedDialogPriv()
+ {
+ timer = 0;
+ title = 0;
+ optionsCombo = 0;
+ resultsView = 0;
+ ungroupButton = 0;
+ groupButton = 0;
+ delButton = 0;
+ addButton = 0;
+ rulesBox = 0;
+ }
+
+ TQVGroupBox *rulesBox;
+
+ TQPushButton *addButton;
+ TQPushButton *delButton;
+ TQPushButton *groupButton;
+ TQPushButton *ungroupButton;
+
+ TQComboBox *optionsCombo;
+
+ TQValueList<SearchAdvancedBase*> baseList;
+
+ TQTimer *timer;
+
+ KLineEdit *title;
+
+ SearchResultsView *resultsView;
+};
+
+SearchAdvancedDialog::SearchAdvancedDialog(TQWidget* parent, KURL& url)
+ : KDialogBase(parent, 0, true, i18n("Advanced Search"),
+ Help|Ok|Cancel, Ok, true), m_url(url)
+{
+ d = new SearchAdvancedDialogPriv;
+ d->timer = new TQTimer(this);
+ setHelp("advancedsearchtool.anchor", "digikam");
+
+ TQWidget *page = new TQWidget( this );
+ setMainWidget(page);
+
+ resize(configDialogSize("AdvancedSearch Dialog"));
+
+ // ----------------------------------------------------------------
+ // two columns, one for the rules, one for the preview.
+
+ TQHBoxLayout* hbox = new TQHBoxLayout( page );
+ TQVBoxLayout* leftSide = new TQVBoxLayout();
+ d->resultsView = new SearchResultsView(page);
+ d->resultsView->setMinimumSize(TQSize(200, 200));
+ TQWhatsThis::add(d->resultsView, i18n("<p>Here you can review the images found "
+ "using the current search settings."));
+ hbox->addLayout(leftSide, 10);
+ hbox->setSpacing(spacingHint());
+ hbox->addWidget(d->resultsView, 5);
+
+ // ----------------------------------------------------------------
+ // Box for all the rules
+
+ d->rulesBox = new TQVGroupBox(i18n("Search Rules"), page);
+ TQWhatsThis::add(d->rulesBox, i18n("<p>Here you can review the search rules used to filter image-"
+ "searching in album library."));
+ d->rulesBox->layout()->setSpacing( spacingHint() );
+ d->rulesBox->layout()->setMargin( spacingHint() );
+ d->rulesBox->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Expanding );
+ d->rulesBox->layout()->setAlignment( TQt::AlignTop );
+
+ // ----------------------------------------------------------------
+ // Box for the add/delete
+
+ TQGroupBox *groupbox1 = new TQGroupBox( i18n("Add/Delete Option"), page, "groupbox1" );
+ TQWhatsThis::add(groupbox1, i18n("<p>You can edit the search rules "
+ "by adding/removing criteria."));
+
+ groupbox1->setColumnLayout(0, TQt::Vertical );
+ groupbox1->layout()->setSpacing( KDialog::spacingHint() );
+ groupbox1->layout()->setMargin( KDialog::marginHint() );
+ d->optionsCombo = new TQComboBox(groupbox1);
+ d->optionsCombo->insertItem(i18n("As well as"), 0);
+ d->optionsCombo->insertItem(i18n("Or"), 1);
+ d->optionsCombo->setEnabled(false);
+
+ d->addButton = new TQPushButton(i18n("&Add"), groupbox1);
+ d->delButton = new TQPushButton(i18n("&Del"), groupbox1);
+ d->addButton->setIconSet(SmallIcon("add"));
+ d->delButton->setIconSet(SmallIcon("remove"));
+
+ TQHBoxLayout* box1 = new TQHBoxLayout(groupbox1->layout());
+ box1->addWidget(d->optionsCombo);
+ box1->addWidget(d->addButton);
+ box1->addStretch(10);
+ box1->addWidget(d->delButton);
+
+ // ----------------------------------------------------------------
+ // Box for the group/ungroup
+
+ TQGroupBox *groupbox2 = new TQGroupBox( i18n("Group/Ungroup Options"), page, "groupbox2" );
+ TQWhatsThis::add(groupbox1, i18n("<p>You can group or ungroup any search criteria "
+ "from the Search Rule set."));
+ groupbox2->setColumnLayout(0, TQt::Vertical);
+ groupbox2->layout()->setSpacing( KDialog::spacingHint() );
+ groupbox2->layout()->setMargin( KDialog::marginHint() );
+ d->groupButton = new TQPushButton(i18n("&Group"), groupbox2);
+ d->ungroupButton = new TQPushButton(i18n("&Ungroup"), groupbox2);
+
+ TQHBoxLayout* box2 = new TQHBoxLayout(groupbox2->layout());
+ box2->addWidget(d->groupButton);
+ box2->addStretch(10);
+ box2->addWidget(d->ungroupButton);
+
+ // ----------------------------------------------------------------
+ // box for saving the search.
+
+ TQGroupBox *groupbox3 = new TQGroupBox( page, "groupbox3");
+ groupbox3->setColumnLayout(0, TQt::Vertical );
+ groupbox3->layout()->setSpacing( KDialog::spacingHint() );
+ groupbox3->setFrameStyle( TQFrame::NoFrame );
+ TQLabel* label = new TQLabel(i18n("&Save search as: "), groupbox3);
+ d->title = new KLineEdit(groupbox3, "searchTitle");
+ TQWhatsThis::add(d->title, i18n("<p>Enter the name used to save the current search in "
+ "\"My Searches\" view"));
+
+ TQHBoxLayout* box3 = new TQHBoxLayout(groupbox3->layout());
+ box3->addWidget(label);
+ box3->addWidget(d->title);
+ label->setBuddy(d->title);
+
+ // ----------------------------------------------------------------
+
+ leftSide->addWidget( d->rulesBox );
+ leftSide->addStretch(10); // Push the rulesbox to top and the buttons down.
+ leftSide->addWidget(groupbox1);
+ leftSide->addWidget(groupbox2);
+ leftSide->addWidget(groupbox3);
+
+ // ----------------------------------------------------------------
+
+ if ( url.isEmpty() )
+ {
+ d->title->setText(i18n("Last Search"));
+ slotAddRule();
+ }
+ else
+ {
+ d->title->setText(url.queryItem("name"));
+ fillWidgets( url );
+ }
+
+ slotChangeButtonStates();
+ d->timer->start(0, true);
+
+ // ----------------------------------------------------------------
+
+ connect(d->addButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAddRule()));
+
+ connect(d->delButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotDelRules()));
+
+ connect(d->groupButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotGroupRules()));
+
+ connect(d->ungroupButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotUnGroupRules()));
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotTimeOut()));
+
+ connect(d->title, TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotChangeButtonStates()));
+}
+
+SearchAdvancedDialog::~SearchAdvancedDialog()
+{
+ saveDialogSize("AdvancedSearch Dialog");
+ delete d->timer;
+ delete d;
+}
+
+void SearchAdvancedDialog::slotAddRule()
+{
+ SearchAdvancedBase::Option type = SearchAdvancedBase::NONE;
+ if ( !d->baseList.isEmpty() )
+ {
+ if (d->optionsCombo->currentItem() == 0 )
+ type = SearchAdvancedBase::AND;
+ else
+ type = SearchAdvancedBase::OR;
+ }
+
+ SearchAdvancedRule* rule = new SearchAdvancedRule( d->rulesBox, type );
+ d->baseList.append(rule);
+
+ connect( rule, TQ_SIGNAL( signalBaseItemToggled() ),
+ this, TQ_SLOT( slotChangeButtonStates() ) );
+
+ connect( rule, TQ_SIGNAL( signalPropertyChanged() ),
+ this, TQ_SLOT(slotPropertyChanged()));
+
+ slotChangeButtonStates();
+ slotPropertyChanged();
+}
+
+void SearchAdvancedDialog::slotDelRules()
+{
+ if (d->baseList.isEmpty())
+ return;
+
+ typedef TQValueList<SearchAdvancedBase*> BaseList;
+
+ BaseList itemsToRemove;
+
+ for (BaseList::iterator it = d->baseList.begin();
+ it != d->baseList.end(); ++it)
+ {
+ SearchAdvancedBase* base = *it;
+ if (base->isChecked())
+ {
+ itemsToRemove.append(base);
+ }
+ }
+
+ for (BaseList::iterator it = itemsToRemove.begin();
+ it != itemsToRemove.end(); ++it)
+ {
+ SearchAdvancedBase* base = (SearchAdvancedBase*) *it;
+ d->baseList.remove(base);
+ delete base;
+ }
+
+ BaseList::iterator it = d->baseList.begin();
+ if (it != d->baseList.end())
+ (*it)->removeOption();
+
+ slotChangeButtonStates();
+ slotPropertyChanged();
+ if (d->baseList.isEmpty())
+ {
+ d->optionsCombo->setEnabled(false);
+ d->addButton->setEnabled(true);
+ enableButtonOK( false );
+ }
+}
+
+void SearchAdvancedDialog::slotGroupRules()
+{
+ typedef TQValueList<SearchAdvancedBase*> BaseList;
+
+ BaseList itemsToGroup;
+ BaseList groupsToUnGroupAndGroup;
+
+ for (BaseList::iterator it = d->baseList.begin();
+ it != d->baseList.end(); ++it)
+ {
+ SearchAdvancedBase* base = *it;
+ if ( base->isChecked() )
+ {
+ itemsToGroup.append( base );
+ if ( base->type() == SearchAdvancedBase::GROUP)
+ groupsToUnGroupAndGroup.append( base );
+ }
+ }
+
+ // ungroup every found group so it can be regrouped later on.
+ for (BaseList::iterator it = groupsToUnGroupAndGroup.begin();
+ it != groupsToUnGroupAndGroup.end(); ++it)
+ {
+ SearchAdvancedGroup* group = (SearchAdvancedGroup*)*it;
+ BaseList::iterator itemsToGroupPos = itemsToGroup.find(group);
+ BaseList::iterator itPos = d->baseList.find(group);
+ TQValueList<SearchAdvancedRule*> childRules = group->childRules();
+ for (TQValueList<SearchAdvancedRule*>::iterator iter = childRules.begin();
+ iter != childRules.end(); ++iter)
+ {
+ d->baseList.insert(itPos, *iter);
+ itemsToGroup.insert(itemsToGroupPos, *iter);
+ }
+ group->removeRules();
+ d->baseList.remove(group);
+ itemsToGroup.remove(group);
+ delete group;
+ }
+
+ // if there is only one or no item return
+ if (itemsToGroup.size() < 2)
+ return;
+
+ BaseList::iterator it = itemsToGroup.begin();
+ SearchAdvancedRule* rule = (SearchAdvancedRule*)(*it);
+
+ SearchAdvancedGroup* group = new SearchAdvancedGroup(d->rulesBox);
+ BaseList::iterator itPos = d->baseList.find(rule);
+ d->baseList.insert(itPos, group);
+
+ for (BaseList::iterator it = itemsToGroup.begin();
+ it != itemsToGroup.end(); ++it)
+ {
+ SearchAdvancedBase* base = *it;
+ if (base->type() == SearchAdvancedBase::RULE)
+ {
+ SearchAdvancedRule* rule = (SearchAdvancedRule*)base;
+ group->addRule(rule);
+ d->baseList.remove(rule);
+ }
+ }
+
+ for (BaseList::iterator it = d->baseList.begin();
+ it != d->baseList.end(); ++it)
+ {
+ d->rulesBox->layout()->remove((*it)->widget());
+ d->rulesBox->layout()->add((*it)->widget());
+ }
+
+ connect( group, TQ_SIGNAL( signalBaseItemToggled() ),
+ this, TQ_SLOT( slotChangeButtonStates() ) );
+
+ slotChangeButtonStates();
+ slotPropertyChanged();
+}
+
+void SearchAdvancedDialog::slotUnGroupRules()
+{
+ typedef TQValueList<SearchAdvancedBase*> BaseList;
+ typedef TQValueList<SearchAdvancedGroup*> GroupList;
+
+ GroupList itemsToUnGroup;
+
+ for (BaseList::iterator it = d->baseList.begin();
+ it != d->baseList.end(); ++it)
+ {
+ SearchAdvancedBase* base = *it;
+ if (base->type() == SearchAdvancedBase::GROUP &&
+ base->isChecked())
+ {
+ itemsToUnGroup.append((SearchAdvancedGroup*)base);
+ }
+ }
+
+ if (itemsToUnGroup.isEmpty())
+ return;
+
+ for (GroupList::iterator it = itemsToUnGroup.begin();
+ it != itemsToUnGroup.end(); ++it)
+ {
+ SearchAdvancedGroup *group = *it;
+ TQValueList<SearchAdvancedRule*> childRules = group->childRules();
+
+ BaseList::iterator itPos = d->baseList.find(group);
+
+ for (TQValueList<SearchAdvancedRule*>::iterator iter = childRules.begin();
+ iter != childRules.end(); ++iter)
+ {
+ d->baseList.insert(itPos, *iter);
+ }
+
+ group->removeRules();
+ d->baseList.remove(group);
+ delete group;
+ }
+
+ for (BaseList::iterator it = d->baseList.begin();
+ it != d->baseList.end(); ++it)
+ {
+ d->rulesBox->layout()->remove((*it)->widget());
+ d->rulesBox->layout()->add((*it)->widget());
+ }
+
+ slotChangeButtonStates();
+ slotPropertyChanged();
+}
+
+void SearchAdvancedDialog::slotPropertyChanged()
+{
+ d->timer->start(500, true);
+}
+
+void SearchAdvancedDialog::slotOk()
+{
+ // calculate the latest url and name.
+ slotTimeOut();
+
+ // Since it's not possible to check the state of the ok button,
+ // check the state of the add button.
+ if ( d->addButton->isEnabled() )
+ KDialogBase::slotOk();
+}
+
+void SearchAdvancedDialog::slotTimeOut()
+{
+ if (d->baseList.isEmpty())
+ return;
+
+ typedef TQValueList<SearchAdvancedBase*> BaseList;
+
+ TQString grouping;
+ int count = 0;
+ bool emptyVal = false;
+
+ KURL url;
+ url.setProtocol("digikamsearch");
+
+ for (BaseList::iterator it = d->baseList.begin();
+ it != d->baseList.end(); ++it)
+ {
+ SearchAdvancedBase* base = *it;
+ if (base->type() == SearchAdvancedBase::RULE)
+ {
+ SearchAdvancedRule* rule = (SearchAdvancedRule*)base;
+ TQString val = rule->urlValue();
+ if ( !val.isEmpty() )
+ {
+ if (rule->option() != SearchAdvancedBase::NONE &&
+ !count == 0 )
+ grouping += (rule->option() == SearchAdvancedBase::AND) ?
+ " AND " : " OR ";
+ grouping += TQString::number(++count);
+ url.addQueryItem( TQString::number(count) + ".key", rule->urlKey());
+ url.addQueryItem( TQString::number(count) + ".op", rule->urlOperator());
+ url.addQueryItem( TQString::number(count) + ".val", val);
+ }
+ else
+ emptyVal = true;
+ }
+ else
+ {
+ SearchAdvancedGroup* group = (SearchAdvancedGroup*)base;
+
+ TQString tempGrouping;
+ int curCount = count;
+
+ TQValueList<SearchAdvancedRule*> childRules = group->childRules();
+ for (TQValueList<SearchAdvancedRule*>::iterator iter =
+ childRules.begin();
+ iter != childRules.end(); ++iter)
+ {
+ SearchAdvancedRule* rule = (SearchAdvancedRule*)(*iter);
+ TQString val = rule->urlValue();
+ if ( !val.isEmpty() )
+ {
+ if (rule->option() != SearchAdvancedBase::NONE &&
+ !count == 0 )
+ tempGrouping += (rule->option() == SearchAdvancedBase::AND) ?
+ " AND " : " OR ";
+ tempGrouping += TQString::number(++count);
+ url.addQueryItem( TQString::number(count) + ".key", rule->urlKey());
+ url.addQueryItem( TQString::number(count) + ".op", rule->urlOperator());
+ url.addQueryItem( TQString::number(count) + ".val", val);
+ }
+ else
+ emptyVal = true;
+ }
+
+ if (!tempGrouping.isEmpty())
+ {
+ if (group->option() != SearchAdvancedBase::NONE &&
+ !curCount == 0 )
+ grouping += (group->option() == SearchAdvancedBase::AND) ?
+ " AND " : " OR ";
+ grouping += " ( " + tempGrouping + " ) ";
+ }
+ }
+ }
+
+ url.setPath(grouping);
+ url.addQueryItem("name", d->title->text());
+ url.addQueryItem("count", TQString::number(count));
+ m_url = url;
+ if (!count == 0)
+ d->resultsView->openURL( url );
+ DDebug() << url << endl;
+
+ if (!d->baseList.isEmpty())
+ {
+ if (!d->title->text().isEmpty())
+ enableButtonOK( !emptyVal );
+ d->addButton->setEnabled( !emptyVal );
+ d->optionsCombo->setEnabled( !emptyVal );
+ }
+}
+
+void SearchAdvancedDialog::slotChangeButtonStates()
+{
+ bool group = false;
+ int counter = 0;
+
+ typedef TQValueList<SearchAdvancedBase*> BaseList;
+ for (BaseList::iterator it = d->baseList.begin();
+ it != d->baseList.end(); ++it)
+ {
+ SearchAdvancedBase* base = *it;
+ if (base->isChecked())
+ {
+ ++counter;
+ if (base->type() == SearchAdvancedBase::GROUP)
+ group = true;
+ }
+ }
+
+ d->ungroupButton->setEnabled( group );
+
+ if ( counter == 0)
+ {
+ d->delButton->setEnabled(false);
+ d->groupButton->setEnabled(false);
+ }
+ else if ( counter == 1)
+ {
+ if (d->baseList.count() > 1)
+ d->delButton->setEnabled(true);
+ d->groupButton->setEnabled(false);
+ }
+ else if ( counter > 1 )
+ {
+ d->delButton->setEnabled(true);
+ d->groupButton->setEnabled(true);
+ }
+
+ enableButtonOK( !d->title->text().isEmpty() );
+}
+
+void SearchAdvancedDialog::fillWidgets( const KURL& url )
+{
+ int count = url.queryItem("count").toInt();
+ if (count <= 0)
+ return;
+
+ TQMap<int, KURL> rulesMap;
+
+ for (int i=1; i<=count; i++)
+ {
+ KURL newRule;
+
+ TQString key = url.queryItem(TQString::number(i) + ".key");
+ TQString op = url.queryItem(TQString::number(i) + ".op");
+ TQString val = url.queryItem(TQString::number(i) + ".val");
+
+ newRule.setPath("1");
+ newRule.addQueryItem("1.key",key);
+ newRule.addQueryItem("1.op",op);
+ newRule.addQueryItem("1.val",val);
+
+ rulesMap.insert(i, newRule);
+ }
+
+ TQStringList strList = TQStringList::split(" ", url.path());
+
+ SearchAdvancedGroup* group = 0;
+ bool groupingActive = false;
+ SearchAdvancedBase::Option type = SearchAdvancedBase::NONE;
+
+ for ( TQStringList::Iterator it = strList.begin(); it != strList.end(); ++it )
+ {
+ bool ok;
+ int num = (*it).toInt(&ok);
+ if (ok)
+ {
+ SearchAdvancedRule* rule = new SearchAdvancedRule( d->rulesBox, type );
+ rule->setValues( rulesMap[num] );
+
+ connect( rule, TQ_SIGNAL( signalBaseItemToggled() ),
+ this, TQ_SLOT( slotChangeButtonStates() ) );
+
+ connect( rule, TQ_SIGNAL( signalPropertyChanged() ),
+ this, TQ_SLOT(slotPropertyChanged()));
+
+ if (groupingActive)
+ group->addRule(rule);
+ else
+ d->baseList.append(rule);
+ }
+ else if (*it == "OR")
+ {
+ type = SearchAdvancedRule::OR;
+ }
+ else if (*it == "AND")
+ {
+ type = SearchAdvancedRule::AND;
+ }
+ else if (*it == "(")
+ {
+ group = new SearchAdvancedGroup(d->rulesBox);
+ d->baseList.append(group);
+
+ connect( group, TQ_SIGNAL( signalBaseItemToggled() ),
+ this, TQ_SLOT( slotChangeButtonStates() ) );
+
+ groupingActive = true;
+ }
+ else if (*it == ")")
+ {
+ groupingActive = false;
+ }
+ else
+ {
+ DDebug() << "IGNORED:" << *it << endl;
+ }
+ }
+
+ enableButtonOK( true );
+}
+
+} // namespace Digikam
diff --git a/src/digikam/searchadvanceddialog.h b/src/digikam/searchadvanceddialog.h
new file mode 100644
index 00000000..e4f233bd
--- /dev/null
+++ b/src/digikam/searchadvanceddialog.h
@@ -0,0 +1,90 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description : a dialog to perform advanced search in albums
+ *
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+/** @file searchadvanceddialog.h */
+
+#ifndef SEARCHADVANCEDDIALOG_H
+#define SEARCHADVANCEDDIALOG_H
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+class KURL;
+
+namespace Digikam
+{
+
+class SearchAdvancedDialogPriv;
+
+/** @class SearchAdvancedDialog
+ *
+ * This is the dialog for the advanced search
+ * @author Tom Albers
+ * @author Renchi Raju
+ * @author Gilles Caulier
+ *
+ */
+class SearchAdvancedDialog : public KDialogBase
+{
+ TQ_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ * @param parent parent window
+ * @param url holds the url for the search
+ */
+ SearchAdvancedDialog(TQWidget* parent, KURL& url);
+
+ /**
+ * Destructor
+ */
+ ~SearchAdvancedDialog();
+
+private slots:
+
+ void fillWidgets(const KURL& url);
+ void slotAddRule();
+ void slotDelRules();
+ void slotGroupRules();
+ void slotUnGroupRules();
+ void slotChangeButtonStates();
+ void slotTimeOut();
+ void slotPropertyChanged();
+ void slotOk();
+
+private:
+
+ KURL& m_url;
+
+ SearchAdvancedDialogPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* SEARCHADVANCEDDIALOG_H */
diff --git a/src/digikam/searchfolderview.cpp b/src/digikam/searchfolderview.cpp
new file mode 100644
index 00000000..c302d700
--- /dev/null
+++ b/src/digikam/searchfolderview.cpp
@@ -0,0 +1,488 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-21
+ * Description : Searches folder view
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfont.h>
+#include <tqpainter.h>
+#include <tqstyle.h>
+#include <tqcursor.h>
+
+// KDE includes.
+
+#include <tdepopupmenu.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+#include <tdeversion.h>
+#include <tdemessagebox.h>
+
+#if KDE_IS_VERSION(3,2,0)
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// Local includes.
+
+#include "album.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "searchquickdialog.h"
+#include "searchadvanceddialog.h"
+#include "folderitem.h"
+#include "searchfolderview.h"
+#include "searchfolderview.moc"
+
+namespace Digikam
+{
+
+class SearchFolderItem : public FolderItem
+{
+
+public:
+
+ SearchFolderItem(TQListView* parent, SAlbum* album)
+ : FolderItem(parent, album->title()),
+ m_album(album)
+ {
+ m_album->setExtraData(parent, this);
+ }
+
+ ~SearchFolderItem()
+ {
+ m_album->removeExtraData(listView());
+ }
+
+ int compare(TQListViewItem* i, int , bool ) const
+ {
+ if (!i)
+ return 0;
+
+ if (text(0) == i18n("Last Search"))
+ return -1;
+
+ return text(0).localeAwareCompare(i->text(0));
+ }
+
+ int id() const
+ {
+ return m_album ? m_album->id() : 0;
+ }
+
+ SAlbum* album() const
+ {
+ return m_album;
+ }
+
+private:
+
+ SAlbum *m_album;
+};
+
+SearchFolderView::SearchFolderView(TQWidget* parent)
+ : FolderView(parent, "SearchFolderView")
+{
+ addColumn(i18n("My Searches"));
+ setResizeMode(TQListView::LastColumn);
+ setRootIsDecorated(false);
+
+ m_lastAddedItem = 0;
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(clear()));
+
+ connect(this, TQ_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
+ this, TQ_SLOT(slotContextMenu(TQListViewItem*, const TQPoint&, int)));
+
+ connect(this, TQ_SIGNAL(doubleClicked(TQListViewItem*, const TQPoint&, int)),
+ this, TQ_SLOT(slotDoubleClicked(TQListViewItem*, const TQPoint&, int)));
+
+ connect(this, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+}
+
+SearchFolderView::~SearchFolderView()
+{
+ saveViewState();
+}
+
+void SearchFolderView::slotTextSearchFilterChanged(const TQString& filter)
+{
+ TQString search = filter.lower();
+
+ bool atleastOneMatch = false;
+
+ AlbumList sList = AlbumManager::instance()->allSAlbums();
+ for (AlbumList::iterator it = sList.begin(); it != sList.end(); ++it)
+ {
+ SAlbum* salbum = (SAlbum*)(*it);
+ SearchFolderItem* viewItem = (SearchFolderItem*) salbum->extraData(this);
+
+ // Check if a special url query exist to identify a SAlbum dedicaced to Date Search
+ // used with TimeLine.
+ KURL url = salbum->kurl();
+ TQString type = url.queryItem("type");
+
+ if (salbum->title().lower().contains(search) &&
+ type != TQString("datesearch"))
+ {
+ atleastOneMatch = true;
+
+ if (viewItem)
+ viewItem->setVisible(true);
+ }
+ else
+ {
+ if (viewItem)
+ {
+ viewItem->setVisible(false);
+ }
+ }
+ }
+
+ emit signalTextSearchFilterMatch(atleastOneMatch);
+}
+
+void SearchFolderView::quickSearchNew()
+{
+ KURL url;
+ SearchQuickDialog dlg(this, url);
+
+ if (dlg.exec() != KDialogBase::Accepted)
+ return;
+
+ // Check if there is not already an album with that namespace
+ // and return if user aborts the dialog.
+ if ( ! checkName( url ) )
+ return;
+
+ SAlbum* album = AlbumManager::instance()->createSAlbum(url, true);
+
+ if (album)
+ {
+ SearchFolderItem* searchItem = (SearchFolderItem*)(album->extraData(this));
+ if (searchItem)
+ {
+ clearSelection();
+ setSelected(searchItem, true);
+ slotSelectionChanged();
+ }
+ }
+}
+
+void SearchFolderView::extendedSearchNew()
+{
+ KURL url;
+ SearchAdvancedDialog dlg(this, url);
+
+ if (dlg.exec() != KDialogBase::Accepted)
+ return;
+
+ // Check if there is not already an album with that name
+ // and return if user aborts the dialog.
+ if ( ! checkName( url ) )
+ return;
+
+ SAlbum* album = AlbumManager::instance()->createSAlbum(url, false);
+
+ if (album)
+ {
+ SearchFolderItem* searchItem = (SearchFolderItem*)(album->extraData(this));
+ if (searchItem)
+ {
+ clearSelection();
+ setSelected(searchItem, true);
+ slotSelectionChanged();
+ }
+ }
+}
+
+bool SearchFolderView::checkName( KURL& url )
+{
+ TQString albumTitle = url.queryItem("name");
+ AlbumManager* aManager = AlbumManager::instance();
+ AlbumList aList = aManager->allSAlbums();
+ bool checked = checkAlbum( albumTitle );
+
+ while ( !checked)
+ {
+ TQString label = i18n( "Search name already exists."
+ "\nPlease enter a new name:" );
+ bool ok;
+#if KDE_IS_VERSION(3,2,0)
+ TQString newTitle = KInputDialog::getText( i18n("Name exists"), label,
+ albumTitle, &ok, this );
+#else
+ TQString newTitle = KLineEditDlg::getText( i18n("Name exists"), label,
+ albumTitle, ok, this );
+#endif
+ if (!ok)
+ return false;
+
+ albumTitle=newTitle;
+ checked = checkAlbum( albumTitle );
+ }
+
+ url.removeQueryItem( "name" );
+ url.addQueryItem( "name", albumTitle );
+ return true;
+}
+
+bool SearchFolderView::checkAlbum( const TQString& name ) const
+{
+
+ AlbumManager* aManager = AlbumManager::instance();
+ AlbumList aList = aManager->allSAlbums();
+
+ for ( AlbumList::Iterator it = aList.begin();
+ it != aList.end(); ++it )
+ {
+ SAlbum *album = (SAlbum*)(*it);
+ if ( album->title() == name )
+ return false;
+ }
+ return true;
+}
+
+void SearchFolderView::quickSearchEdit(SAlbum* album)
+{
+ if (!album)
+ return;
+
+ KURL url = album->kurl();
+ SearchQuickDialog dlg(this, url);
+
+ if (dlg.exec() != KDialogBase::Accepted)
+ return;
+
+ AlbumManager::instance()->updateSAlbum(album, url);
+
+ ((SearchFolderItem*)album->extraData(this))->setText(0, album->title());
+
+ clearSelection();
+ setSelected((SearchFolderItem*)(album->extraData(this)), true);
+}
+
+void SearchFolderView::extendedSearchEdit(SAlbum* album)
+{
+ if (!album)
+ return;
+
+ KURL url = album->kurl();
+ SearchAdvancedDialog dlg(this, url);
+
+ if (dlg.exec() != KDialogBase::Accepted)
+ return;
+
+ AlbumManager::instance()->updateSAlbum(album, url);
+
+ ((SearchFolderItem*)album->extraData(this))->setText(0, album->title());
+
+ clearSelection();
+ setSelected((SearchFolderItem*)(album->extraData(this)), true);
+}
+
+void SearchFolderView::searchDelete(SAlbum* album)
+{
+ if (!album)
+ return;
+
+ // Make sure that a complicated search is not deleted accidentally
+ int result = KMessageBox::warningYesNo(this, i18n("Are you sure you want to "
+ "delete the selected search "
+ "\"%1\"?")
+ .arg(album->title()),
+ i18n("Delete Search?"),
+ i18n("Delete"),
+ KStdGuiItem::cancel());
+
+ if (result != KMessageBox::Yes)
+ return;
+
+ AlbumManager::instance()->deleteSAlbum(album);
+}
+
+void SearchFolderView::slotAlbumAdded(Album* a)
+{
+ if (!a || a->type() != Album::SEARCH)
+ return;
+
+ SAlbum* album = (SAlbum*)a;
+
+ // Check if a special url query exist to identify a SAlbum dedicaced to Date Search
+ // used with TimeLine. In this case, SAlbum is not displayed here, but in TimeLineFolderView.
+ KURL url = album->kurl();
+ TQString type = url.queryItem("type");
+ if (type == TQString("datesearch")) return;
+
+ SearchFolderItem* item = new SearchFolderItem(this, album);
+ item->setPixmap(0, SmallIcon("edit-find", AlbumSettings::instance()->getDefaultTreeIconSize()));
+ m_lastAddedItem = item;
+}
+
+void SearchFolderView::slotAlbumDeleted(Album* a)
+{
+ if (!a || a->type() != Album::SEARCH)
+ return;
+
+ SAlbum* album = (SAlbum*)a;
+
+ SearchFolderItem* item = (SearchFolderItem*) album->extraData(this);
+ if (item)
+ delete item;
+}
+
+void SearchFolderView::slotSelectionChanged()
+{
+ if (!active())
+ return;
+
+ TQListViewItem* selItem = 0;
+
+ TQListViewItemIterator it( this );
+ while (it.current())
+ {
+ if (it.current()->isSelected())
+ {
+ selItem = it.current();
+ break;
+ }
+ ++it;
+ }
+
+ if (!selItem)
+ {
+ AlbumManager::instance()->setCurrentAlbum(0);
+ return;
+ }
+
+ SearchFolderItem* searchItem = dynamic_cast<SearchFolderItem*>(selItem);
+
+ if (!searchItem || !searchItem->album())
+ AlbumManager::instance()->setCurrentAlbum(0);
+ else
+ AlbumManager::instance()->setCurrentAlbum(searchItem->album());
+}
+
+void SearchFolderView::slotContextMenu(TQListViewItem* item, const TQPoint&, int)
+{
+ if (!item)
+ {
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("My Searches"));
+ popmenu.insertItem(SmallIcon("filefind"), i18n("New Simple Search..."), 10);
+ popmenu.insertItem(SmallIcon("edit-find"), i18n("New Advanced Search..."), 11);
+
+ switch (popmenu.exec(TQCursor::pos()))
+ {
+ case 10:
+ {
+ quickSearchNew();
+ break;
+ }
+ case 11:
+ {
+ extendedSearchNew();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ else
+ {
+ SearchFolderItem* sItem = dynamic_cast<SearchFolderItem*>(item);
+
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("My Searches"));
+ popmenu.insertItem(SmallIcon("filefind"), i18n("Edit Search..."), 10);
+
+ if ( sItem->album()->isSimple() )
+ popmenu.insertItem(SmallIcon("edit-find"), i18n("Edit as Advanced Search..."), 11);
+
+ popmenu.insertSeparator(-1);
+ popmenu.insertItem(SmallIcon("edit-delete"), i18n("Delete Search"), 12);
+
+ switch (popmenu.exec(TQCursor::pos()))
+ {
+ case 10:
+ {
+ if (sItem->album()->isSimple())
+ quickSearchEdit(sItem->album());
+ else
+ extendedSearchEdit(sItem->album());
+ break;
+ }
+ case 11:
+ {
+ extendedSearchEdit(sItem->album());
+ break;
+ }
+ case 12:
+ {
+ searchDelete(sItem->album());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+void SearchFolderView::slotDoubleClicked(TQListViewItem* item, const TQPoint&, int)
+{
+ if (!item)
+ return;
+
+ SearchFolderItem* sItem = dynamic_cast<SearchFolderItem*>(item);
+
+ if (sItem->album()->isSimple())
+ quickSearchEdit(sItem->album());
+ else
+ extendedSearchEdit(sItem->album());
+}
+
+void SearchFolderView::selectItem(int id)
+{
+ SAlbum *album = AlbumManager::instance()->findSAlbum(id);
+ if(!album)
+ return;
+
+ SearchFolderItem *item = (SearchFolderItem*)album->extraData(this);
+ if(item)
+ {
+ setSelected(item, true);
+ ensureItemVisible(item);
+ }
+}
+
+} // namespace Digikam
diff --git a/src/digikam/searchfolderview.h b/src/digikam/searchfolderview.h
new file mode 100644
index 00000000..8dab4ca0
--- /dev/null
+++ b/src/digikam/searchfolderview.h
@@ -0,0 +1,87 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-21
+ * Description : Searches folder view
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SEARCHFOLDERVIEW_H
+#define SEARCHFOLDERVIEW_H
+
+// Local includes.
+
+#include "folderview.h"
+
+namespace Digikam
+{
+
+class SAlbum;
+class SearchFolderItem;
+
+class SearchFolderView : public FolderView
+{
+ TQ_OBJECT
+
+public:
+
+ SearchFolderView(TQWidget* parent);
+ ~SearchFolderView();
+
+ void quickSearchNew();
+ void extendedSearchNew();
+
+ void quickSearchEdit(SAlbum* album);
+ void extendedSearchEdit(SAlbum* album);
+
+ void searchDelete(SAlbum* album);
+
+signals:
+
+ void signalTextSearchFilterMatch(bool);
+
+public slots:
+
+ void slotTextSearchFilterChanged(const TQString&);
+
+private slots:
+
+ void slotAlbumAdded(Album* album);
+ void slotAlbumDeleted(Album* album);
+ void slotSelectionChanged();
+ void slotContextMenu(TQListViewItem*, const TQPoint&, int);
+ void slotDoubleClicked(TQListViewItem*, const TQPoint&, int);
+
+protected:
+
+ void selectItem(int id);
+
+private:
+
+ bool checkName( KURL& url );
+ bool checkAlbum( const TQString& name ) const;
+
+private:
+
+ SearchFolderItem* m_lastAddedItem;
+};
+
+} // namespace Digikam
+
+#endif /* SEARCHFOLDERVIEW_H */
diff --git a/src/digikam/searchquickdialog.cpp b/src/digikam/searchquickdialog.cpp
new file mode 100644
index 00000000..9cca5dfc
--- /dev/null
+++ b/src/digikam/searchquickdialog.cpp
@@ -0,0 +1,200 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-19
+ * Description : a dialog to perform simple search in albums
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file searchquickdialog.cpp */
+
+// TQt includes.
+
+#include <tqtimer.h>
+#include <tqlayout.h>
+#include <tqstringlist.h>
+#include <tqdatetime.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <klineedit.h>
+#include <tdelocale.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "searchtextbar.h"
+#include "searchresultsview.h"
+#include "searchquickdialog.h"
+#include "searchquickdialog.moc"
+
+namespace Digikam
+{
+
+class SearchQuickDialogPriv
+{
+public:
+
+ SearchQuickDialogPriv()
+ {
+ timer = 0;
+ searchEdit = 0;
+ nameEdit = 0;
+ resultsView = 0;
+ }
+
+ TQTimer *timer;
+
+ KLineEdit *nameEdit;
+
+ SearchTextBar *searchEdit;
+
+ SearchResultsView *resultsView;
+};
+
+SearchQuickDialog::SearchQuickDialog(TQWidget* parent, KURL& url)
+ : KDialogBase(Plain, i18n("Quick Search"), Help|Ok|Cancel, Ok,
+ parent, 0, true, true), m_url(url)
+{
+ d = new SearchQuickDialogPriv;
+ d->timer = new TQTimer(this);
+ setHelp("quicksearchtool.anchor", "digikam");
+
+ TQGridLayout* grid = new TQGridLayout(plainPage(), 2, 2, 0, spacingHint());
+
+ TQLabel *label1 = new TQLabel("<b>" + i18n("Search:") + "</b>", plainPage());
+ d->searchEdit = new SearchTextBar(plainPage(), "SearchQuickDialogSearchEdit", i18n("Enter here your search criteria"));
+ TQWhatsThis::add( d->searchEdit, i18n("<p>Enter your search criteria to find items in the album library"));
+
+ d->resultsView = new SearchResultsView(plainPage());
+ d->resultsView->setMinimumSize(320, 200);
+ TQWhatsThis::add( d->resultsView, i18n("<p>Here you can see the items found in album library, "
+ "using the current search criteria"));
+
+ TQLabel *label2 = new TQLabel(i18n("Save search as:"), plainPage());
+ d->nameEdit = new KLineEdit(plainPage());
+ d->nameEdit->setText(i18n("Last Search"));
+ TQWhatsThis::add( d->nameEdit, i18n("<p>Enter the name of the current search to save in the "
+ "\"My Searches\" view"));
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 0);
+ grid->addMultiCellWidget(d->searchEdit, 0, 0, 1, 2);
+ grid->addMultiCellWidget(d->resultsView, 1, 1, 0, 2);
+ grid->addMultiCellWidget(label2, 2, 2, 0, 1);
+ grid->addMultiCellWidget(d->nameEdit, 2, 2, 2, 2);
+
+ connect(d->searchEdit, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ this, TQ_SLOT(slotSearchChanged(const TQString&)));
+
+ connect(d->resultsView, TQ_SIGNAL(signalSearchResultsMatch(bool)),
+ d->searchEdit, TQ_SLOT(slotSearchResult(bool)));
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotTimeOut()));
+
+ enableButtonOK(false);
+ resize(configDialogSize("QuickSearch Dialog"));
+
+ // check if we are being passed a valid url
+ if (m_url.isValid())
+ {
+ int count = m_url.queryItem("count").toInt();
+ if (count > 0)
+ {
+ TQStringList strList;
+
+ for (int i=1; i<=count; i++)
+ {
+ TQString val = m_url.queryItem(TQString::number(i) + ".val");
+ if (!strList.contains(val))
+ {
+ strList.append(val);
+ }
+ }
+
+ d->searchEdit->setText(strList.join(" "));
+ d->nameEdit->setText(url.queryItem("name"));
+ d->timer->start(0, true);
+ }
+ }
+}
+
+SearchQuickDialog::~SearchQuickDialog()
+{
+ saveDialogSize(("QuickSearch Dialog"));
+ delete d->timer;
+ delete d;
+}
+
+void SearchQuickDialog::slotTimeOut()
+{
+ if (d->searchEdit->text().isEmpty())
+ {
+ d->resultsView->clear();
+ enableButtonOK(false);
+ return;
+ }
+
+ enableButtonOK(true);
+
+ KURL url;
+ url.setProtocol("digikamsearch");
+
+ TQString path, num;
+ int count = 0;
+
+ TQStringList textList = TQStringList::split(' ', d->searchEdit->text());
+ for (TQStringList::iterator it = textList.begin(); it != textList.end(); ++it)
+ {
+ if (count != 0)
+ path += " AND ";
+
+ path += TQString(" %1 ").arg(count + 1);
+
+ num = TQString::number(++count);
+ url.addQueryItem(num + ".key", "keyword");
+ url.addQueryItem(num + ".op", "like");
+ url.addQueryItem(num + ".val", *it);
+ }
+
+ url.setPath(path);
+ url.addQueryItem("name", "Live Search");
+ url.addQueryItem("count", num);
+
+ m_url = url;
+ d->resultsView->openURL(url);
+}
+
+void SearchQuickDialog::slotSearchChanged(const TQString&)
+{
+ d->timer->start(500, true);
+}
+
+void SearchQuickDialog::hideEvent(TQHideEvent* e)
+{
+ m_url.removeQueryItem("name");
+ m_url.addQueryItem("name", d->nameEdit->text().isEmpty() ?
+ i18n("Last Search") : d->nameEdit->text());
+ KDialogBase::hideEvent(e);
+}
+
+} // namespace Digikam
diff --git a/src/digikam/searchquickdialog.h b/src/digikam/searchquickdialog.h
new file mode 100644
index 00000000..3412283e
--- /dev/null
+++ b/src/digikam/searchquickdialog.h
@@ -0,0 +1,87 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-19
+ * Description : a dialog to perform simple search in albums
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file searchquickdialog.h */
+
+#ifndef SEARCHQUICKDIALOG_H
+#define SEARCHQUICKDIALOG_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+class KURL;
+
+namespace Digikam
+{
+
+class SearchQuickDialogPriv;
+
+/** @class SearchQuickDialog
+ *
+ * This is the dialog for the quick search
+ * @author Renchi Raju
+ *
+ */
+class SearchQuickDialog : public KDialogBase
+{
+ TQ_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ * @param parent parent window
+ * @param url holds the url for the search
+ */
+ SearchQuickDialog(TQWidget* parent, KURL& url);
+ /**
+ * Destructor
+ */
+ ~SearchQuickDialog();
+
+protected:
+
+ void hideEvent(TQHideEvent* e);
+
+private slots:
+
+ void slotTimeOut();
+ void slotSearchChanged(const TQString&);
+
+private:
+
+ KURL& m_url;
+
+ SearchQuickDialogPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif /* SEARCHQUICKDIALOG_H */
diff --git a/src/digikam/searchresultsitem.cpp b/src/digikam/searchresultsitem.cpp
new file mode 100644
index 00000000..d3f2ab13
--- /dev/null
+++ b/src/digikam/searchresultsitem.cpp
@@ -0,0 +1,87 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-20
+ * Description : search results item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+
+// Local includes.
+
+#include "searchresultsitem.h"
+
+namespace Digikam
+{
+
+TQPixmap* SearchResultsItem::m_basePixmap = 0;
+
+SearchResultsItem::SearchResultsItem(TQIconView* view, const TQString& path)
+ : TQIconViewItem(view), m_path(path)
+{
+ if (!m_basePixmap)
+ {
+ m_basePixmap = new TQPixmap(128, 128);
+ m_basePixmap->fill(view->colorGroup().base());
+
+ TQPainter p(m_basePixmap);
+ p.setPen(TQt::lightGray);
+ p.drawRect(0, 0, 128, 128);
+ p.end();
+ }
+
+ setPixmap(*m_basePixmap);
+ calcRect();
+ m_marked = true;
+}
+
+SearchResultsItem::~SearchResultsItem()
+{
+
+}
+
+void SearchResultsItem::calcRect(const TQString&)
+{
+ TQRect r(0,0,0,0);
+ setTextRect(r);
+ setPixmapRect(r);
+ setItemRect(TQRect(x(),y(),130,130));
+}
+
+void SearchResultsItem::paintItem(TQPainter* p, const TQColorGroup&)
+{
+ TQRect r(rect());
+ p->drawPixmap(r.x() + (width()-pixmap()->width())/2 ,
+ r.y() + (height()-pixmap()->height())/2,
+ *pixmap());
+}
+
+void SearchResultsItem::paintFocus(TQPainter* p, const TQColorGroup&)
+{
+ TQRect r(rect());
+ p->save();
+ p->setPen(TQPen(TQt::darkGray, 0, TQt::DotLine));
+ p->drawRect(rect());
+ p->restore();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/searchresultsitem.h b/src/digikam/searchresultsitem.h
new file mode 100644
index 00000000..39d6562d
--- /dev/null
+++ b/src/digikam/searchresultsitem.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-20
+ * Description : search results item.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ * ============================================================ */
+
+#ifndef SEARCHRESULTSITEM_H
+#define SEARCHRESULTSITEM_H
+
+// TQt includes.
+
+#include <tqiconview.h>
+
+namespace Digikam
+{
+
+class SearchResultsItem : public TQIconViewItem
+{
+ friend class SearchResultsView;
+
+public:
+
+ SearchResultsItem(TQIconView* view, const TQString& path);
+ ~SearchResultsItem();
+
+protected:
+
+ void calcRect(const TQString& text = TQString());
+ void paintItem (TQPainter * p, const TQColorGroup & cg);
+ void paintFocus (TQPainter * p, const TQColorGroup & cg);
+
+private:
+
+ static TQPixmap* m_basePixmap;
+ bool m_marked;
+ TQString m_path;
+};
+
+} // namespace Digikam
+
+#endif /* SEARCHRESULTSITEM_H */
diff --git a/src/digikam/searchresultsview.cpp b/src/digikam/searchresultsview.cpp
new file mode 100644
index 00000000..968b14a8
--- /dev/null
+++ b/src/digikam/searchresultsview.cpp
@@ -0,0 +1,211 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-20
+ * Description : search results view.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatastream.h>
+#include <tqdict.h>
+#include <tqguardedptr.h>
+
+// KDE includes.
+
+#include <tdeio/job.h>
+#include <tdefileitem.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "thumbnailjob.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "searchresultsitem.h"
+#include "searchresultsview.h"
+#include "searchresultsview.moc"
+
+namespace Digikam
+{
+
+class SearchResultsViewPriv
+{
+public:
+
+ SearchResultsViewPriv()
+ {
+ listJob = 0;
+ thumbJob = 0;
+ }
+
+ TQString libraryPath;
+ TQString filter;
+
+ TQDict<TQIconViewItem> itemDict;
+
+ TQGuardedPtr<ThumbnailJob> thumbJob;
+
+ TDEIO::TransferJob* listJob;
+};
+
+SearchResultsView::SearchResultsView(TQWidget* parent)
+ : TQIconView(parent)
+{
+ d = new SearchResultsViewPriv;
+ d->libraryPath = AlbumManager::instance()->getLibraryPath();
+ d->filter = AlbumSettings::instance()->getAllFileFilter();
+
+ setAutoArrange(true);
+ setResizeMode(TQIconView::Adjust);
+}
+
+SearchResultsView::~SearchResultsView()
+{
+ if (!d->thumbJob.isNull())
+ d->thumbJob->kill();
+ if (d->listJob)
+ d->listJob->kill();
+
+ delete d;
+}
+
+void SearchResultsView::openURL(const KURL& url)
+{
+ if (d->listJob)
+ d->listJob->kill();
+ d->listJob = 0;
+
+ if (!d->thumbJob.isNull())
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << d->libraryPath;
+ ds << url;
+ ds << d->filter;
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+ ds << 2; // miniListing (Use 1 for full listing)
+
+ d->listJob = new TDEIO::TransferJob(url, TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(), false);
+
+ connect(d->listJob, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotResult(TDEIO::Job*)));
+
+ connect(d->listJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
+ this, TQ_SLOT(slotData(TDEIO::Job*, const TQByteArray&)));
+}
+
+void SearchResultsView::clear()
+{
+ if (d->listJob)
+ d->listJob->kill();
+ d->listJob = 0;
+
+ if (!d->thumbJob.isNull())
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+
+ d->itemDict.clear();
+ TQIconView::clear();
+}
+
+void SearchResultsView::slotData(TDEIO::Job*, const TQByteArray &data)
+{
+ for (TQIconViewItem* item = firstItem(); item; item = item->nextItem())
+ ((SearchResultsItem*)item)->m_marked = false;
+
+ KURL::List ulist;
+
+ TQString path;
+ TQDataStream ds(data, IO_ReadOnly);
+ while (!ds.atEnd())
+ {
+ ds >> path;
+
+ SearchResultsItem* existingItem = (SearchResultsItem*) d->itemDict.find(path);
+ if (existingItem)
+ {
+ existingItem->m_marked = true;
+ continue;
+ }
+
+ SearchResultsItem* item = new SearchResultsItem(this, path);
+ d->itemDict.insert(path, item);
+
+ ulist.append(KURL(path));
+ }
+
+ SearchResultsItem* item = (SearchResultsItem*)firstItem();
+ TQIconViewItem* nextItem;
+ while (item)
+ {
+ nextItem = item->nextItem();
+ if (!item->m_marked)
+ {
+ d->itemDict.remove(item->m_path);
+ delete item;
+ }
+ item = (SearchResultsItem*)nextItem;
+ }
+ arrangeItemsInGrid();
+
+ bool match = !ulist.isEmpty();
+
+ emit signalSearchResultsMatch(match);
+
+ if (match)
+ {
+ d->thumbJob = new ThumbnailJob(ulist, 128, true, true);
+
+ connect(d->thumbJob, TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnail(const KURL&, const TQPixmap&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalFailed(const KURL&)),
+ this, TQ_SLOT(slotFailedThumbnail(const KURL&)));
+ }
+}
+
+void SearchResultsView::slotResult(TDEIO::Job *job)
+{
+ if (job->error())
+ job->showErrorDialog(this);
+ d->listJob = 0;
+}
+
+void SearchResultsView::slotGotThumbnail(const KURL& url, const TQPixmap& pix)
+{
+ TQIconViewItem* i = d->itemDict.find(url.path());
+ if (i)
+ i->setPixmap(pix);
+
+ d->thumbJob = 0;
+}
+
+void SearchResultsView::slotFailedThumbnail(const KURL&)
+{
+ d->thumbJob = 0;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/searchresultsview.h b/src/digikam/searchresultsview.h
new file mode 100644
index 00000000..a3846a28
--- /dev/null
+++ b/src/digikam/searchresultsview.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-20
+ * Description : search results view.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SEARCHRESULTSVIEW_H
+#define SEARCHRESULTSVIEW_H
+
+// TQt includes.
+
+#include <tqcstring.h>
+#include <tqiconview.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+class TQPixmap;
+
+class KFileItem;
+
+namespace TDEIO
+{
+class TransferJob;
+class Job;
+}
+
+namespace Digikam
+{
+
+class SearchResultsViewPriv;
+
+class SearchResultsView : public TQIconView
+{
+ TQ_OBJECT
+
+public:
+
+ SearchResultsView(TQWidget* parent);
+ ~SearchResultsView();
+
+ void openURL(const KURL& url);
+ void clear();
+
+signals:
+
+ void signalSearchResultsMatch(bool);
+
+private slots:
+
+ void slotData(TDEIO::Job *job, const TQByteArray &data);
+ void slotResult(TDEIO::Job *job);
+ void slotGotThumbnail(const KURL& url, const TQPixmap& pix);
+ void slotFailedThumbnail(const KURL& url);
+
+private:
+
+ SearchResultsViewPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* SEARCHRESULTSVIEW_H */
diff --git a/src/digikam/searchwidgets.cpp b/src/digikam/searchwidgets.cpp
new file mode 100644
index 00000000..c930d80c
--- /dev/null
+++ b/src/digikam/searchwidgets.cpp
@@ -0,0 +1,602 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description : search widgets collection.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file searchwidgets.cpp */
+
+// TQt includes.
+
+#include <tqhbox.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqlineedit.h>
+#include <tqgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqlayout.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kurl.h>
+#include <kdialog.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albuminfo.h"
+#include "albummanager.h"
+#include "ratingwidget.h"
+#include "squeezedcombobox.h"
+#include "kdateedit.h"
+#include "searchwidgets.h"
+#include "searchwidgets.moc"
+
+namespace Digikam
+{
+
+static const int RuleKeyTableCount = 11;
+static const int RuleOpTableCount = 18;
+
+static struct
+{
+ const char *keyText;
+ TQString key;
+ SearchAdvancedRule::valueWidgetTypes cat;
+}
+RuleKeyTable[] =
+{
+ { I18N_NOOP("Album"), "album", SearchAdvancedRule::ALBUMS },
+ { I18N_NOOP("Album Name"), "albumname", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Album Caption"), "albumcaption", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Album Collection"), "albumcollection", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Tag"), "tag", SearchAdvancedRule::TAGS },
+ { I18N_NOOP("Tag Name"), "tagname", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Image Name"), "imagename", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Image Date"), "imagedate", SearchAdvancedRule::DATE },
+ { I18N_NOOP("Image Caption"), "imagecaption", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Keyword"), "keyword", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Rating"), "rating", SearchAdvancedRule::RATING },
+};
+
+static struct
+{
+ const char *keyText;
+ TQString key;
+ SearchAdvancedRule::valueWidgetTypes cat;
+}
+RuleOpTable[] =
+{
+ { I18N_NOOP("Contains"), "LIKE", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Does Not Contain"), "NLIKE", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Equals"), "EQ", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Does Not Equal"), "NE", SearchAdvancedRule::LINEEDIT },
+ { I18N_NOOP("Equals"), "EQ", SearchAdvancedRule::ALBUMS },
+ { I18N_NOOP("Does Not Equal"), "NE", SearchAdvancedRule::ALBUMS },
+ { I18N_NOOP("Contains"), "LIKE", SearchAdvancedRule::ALBUMS },
+ { I18N_NOOP("Does Not Contain"), "NLIKE", SearchAdvancedRule::ALBUMS },
+ { I18N_NOOP("Equals"), "EQ", SearchAdvancedRule::TAGS },
+ { I18N_NOOP("Does Not Equal"), "NE", SearchAdvancedRule::TAGS },
+ { I18N_NOOP("Contains"), "LIKE", SearchAdvancedRule::TAGS },
+ { I18N_NOOP("Does Not Contain"), "NLIKE", SearchAdvancedRule::TAGS },
+ { I18N_NOOP("After"), "GT", SearchAdvancedRule::DATE },
+ { I18N_NOOP("Before"), "LT", SearchAdvancedRule::DATE },
+ { I18N_NOOP("Equals"), "EQ", SearchAdvancedRule::DATE },
+ { I18N_NOOP("At least"), "GTE", SearchAdvancedRule::RATING },
+ { I18N_NOOP("At most"), "LTE", SearchAdvancedRule::RATING },
+ { I18N_NOOP("Equals"), "EQ", SearchAdvancedRule::RATING },
+};
+
+SearchRuleLabel::SearchRuleLabel(const TQString& text, TQWidget *parent,
+ const char *name, WFlags f )
+ : TQLabel(text, parent, name, f)
+{
+}
+
+void SearchRuleLabel::mouseDoubleClickEvent( TQMouseEvent * e )
+{
+ emit signalDoubleClick( e );
+}
+
+SearchAdvancedRule::SearchAdvancedRule(TQWidget* parent, SearchAdvancedRule::Option option)
+ : SearchAdvancedBase(SearchAdvancedBase::RULE)
+{
+ m_box = new TQVBox(parent);
+ m_box->layout()->setSpacing( KDialog::spacingHint() );
+ m_box->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );
+
+ m_optionsBox = 0;
+ m_option = option;
+ if (option != NONE)
+ {
+ m_optionsBox = new TQHBox( m_box );
+ m_label = new SearchRuleLabel( option == AND ?
+ i18n("As well as") : i18n("Or"),
+ m_optionsBox);
+ TQFrame* hline = new TQFrame( m_optionsBox );
+ hline->setFrameStyle( TQFrame::HLine|TQFrame::Sunken );
+ m_label->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum );
+ hline->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );
+
+ connect( m_label, TQ_SIGNAL( signalDoubleClick( TQMouseEvent* ) ),
+ this, TQ_SLOT( slotLabelDoubleClick() ));
+ }
+
+ m_hbox = new TQWidget( m_box );
+ m_hbox->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum );
+
+ m_key = new TQComboBox( m_hbox, "key" );
+ m_key->setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Minimum );
+ for (int i=0; i< RuleKeyTableCount; i++)
+ m_key->insertItem( i18n(RuleKeyTable[i].keyText), i );
+
+ m_operator = new TQComboBox( m_hbox );
+ m_operator->setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Minimum );
+ // prepopulate the operator widget to get optimal size
+ for (int i=0; i< RuleOpTableCount; i++)
+ m_operator->insertItem( i18n(RuleOpTable[i].keyText), i );
+ m_operator->adjustSize();
+
+ m_valueBox = new TQHBox( m_hbox );
+ m_widgetType = NOWIDGET;
+
+ slotKeyChanged( 0 );
+ m_check = new TQCheckBox( m_hbox );
+
+ m_hboxLayout = new TQHBoxLayout( m_hbox );
+ m_hboxLayout->setSpacing( KDialog::spacingHint() );
+ m_hboxLayout->addWidget( m_key );
+ m_hboxLayout->addWidget( m_operator );
+ m_hboxLayout->addWidget( m_valueBox );
+ m_hboxLayout->addWidget( m_check, 0, TQt::AlignRight );
+
+ m_box->show();
+
+ connect( m_key, TQ_SIGNAL( activated(int) ),
+ this, TQ_SLOT(slotKeyChanged(int)));
+ connect( m_key, TQ_SIGNAL( activated(int) ),
+ this, TQ_SIGNAL( signalPropertyChanged() ));
+ connect( m_operator, TQ_SIGNAL( activated(int) ),
+ this, TQ_SIGNAL( signalPropertyChanged() ));
+ connect( m_check, TQ_SIGNAL( toggled( bool ) ),
+ this, TQ_SIGNAL( signalBaseItemToggled() ));
+}
+
+void SearchAdvancedRule::setValues(const KURL& url)
+{
+ if (url.isEmpty())
+ return;
+
+ // set the key widget
+ for (int i=0; i< RuleKeyTableCount; i++)
+ {
+ if (RuleKeyTable[i].key == url.queryItem("1.key"))
+ {
+ m_key->setCurrentText( i18n(RuleKeyTable[i].keyText) );
+ }
+ }
+
+ // set the operator and the last widget
+ slotKeyChanged( m_key->currentItem() );
+ for (int i=0; i< RuleOpTableCount; i++)
+ {
+ if ( RuleOpTable[i].key == url.queryItem("1.op") &&
+ RuleOpTable[i].cat == m_widgetType )
+ {
+ m_operator->setCurrentText( i18n(RuleOpTable[i].keyText) );
+ }
+ }
+
+ // Set the value for the last widget.
+ TQString value = url.queryItem("1.val");
+ if (m_widgetType == LINEEDIT)
+ m_lineEdit->setText( value );
+
+ if (m_widgetType == DATE)
+ m_dateEdit->setDate( TQDate::fromString( value, TQt::ISODate) );
+
+ if (m_widgetType == RATING)
+ {
+ bool ok;
+ int num = value.toInt(&ok);
+ if (ok)
+ m_ratingWidget->setRating( num );
+ }
+
+ if (m_widgetType == TAGS || m_widgetType == ALBUMS)
+ {
+ bool ok;
+ int num = value.toInt(&ok);
+ if (ok)
+ {
+ TQMapIterator<int,int> it;
+ for (it = m_itemsIndexIDMap.begin() ; it != m_itemsIndexIDMap.end(); ++it)
+ {
+ if (it.data() == num)
+ m_valueCombo->setCurrentItem( it.key() );
+ }
+ }
+ }
+}
+
+SearchAdvancedRule::~SearchAdvancedRule()
+{
+ delete m_box;
+}
+
+void SearchAdvancedRule::slotLabelDoubleClick()
+{
+ if (m_option == AND)
+ {
+ m_option=OR;
+ m_label->setText( i18n("Or") );
+ }
+ else
+ {
+ m_option=AND;
+ m_label->setText( i18n("As well as") );
+ }
+ emit signalPropertyChanged();
+}
+
+void SearchAdvancedRule::slotKeyChanged(int id)
+{
+ TQString currentOperator = m_operator->currentText();
+ valueWidgetTypes currentType = m_widgetType;
+
+ // we need to save the current size of the operator combobox
+ // otherise clear() will shrink it
+ TQSize curSize = m_operator->size();
+ m_operator->clear();
+ m_widgetType = RuleKeyTable[id].cat;
+
+ for (int i=0; i< RuleOpTableCount; i++)
+ {
+ if ( RuleOpTable[i].cat == m_widgetType )
+ {
+ m_operator->insertItem( i18n(RuleOpTable[i].keyText) );
+
+ if ( currentOperator == RuleOpTable[i].key )
+ m_operator->setCurrentText( currentOperator );
+ }
+ }
+ m_operator->setFixedSize(curSize);
+ setValueWidget( currentType, m_widgetType );
+}
+
+void SearchAdvancedRule::setValueWidget(valueWidgetTypes oldType, valueWidgetTypes newType)
+{
+ // this map is used to sort album and tag list combobox
+ typedef TQMap<TQString, int> SortedList;
+
+ if (oldType == newType)
+ return;
+
+ if (m_lineEdit && oldType == LINEEDIT)
+ delete m_lineEdit;
+
+ if (m_dateEdit && oldType == DATE)
+ delete m_dateEdit;
+
+ if (m_ratingWidget && oldType == RATING)
+ delete m_ratingWidget;
+
+ if (m_valueCombo && (oldType == ALBUMS || oldType == TAGS))
+ delete m_valueCombo;
+
+ if (newType == DATE)
+ {
+ m_dateEdit = new KDateEdit( m_valueBox,"datepicker");
+ m_dateEdit->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum );
+ m_dateEdit->show();
+
+ connect( m_dateEdit, TQ_SIGNAL( dateChanged(const TQDate& ) ),
+ this, TQ_SIGNAL(signalPropertyChanged()));
+ }
+ else if (newType == LINEEDIT)
+ {
+ m_lineEdit = new TQLineEdit( m_valueBox, "lineedit" );
+ m_lineEdit->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );
+ m_lineEdit->show();
+
+ connect( m_lineEdit, TQ_SIGNAL ( textChanged(const TQString&) ),
+ this, TQ_SIGNAL(signalPropertyChanged()));
+
+ }
+ else if (newType == ALBUMS)
+ {
+ m_valueCombo = new SqueezedComboBox( m_valueBox, "albumscombo" );
+ m_valueCombo->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );
+
+ AlbumManager* aManager = AlbumManager::instance();
+ AlbumList aList = aManager->allPAlbums();
+
+ m_itemsIndexIDMap.clear();
+
+ // First we need to sort the album list.
+ // We create a map with the album url as key, so that it is
+ // automatically sorted.
+ SortedList sAList;
+
+ for ( AlbumList::Iterator it = aList.begin();
+ it != aList.end(); ++it )
+ {
+ PAlbum *album = (PAlbum*)(*it);
+ if ( !album->isRoot() )
+ {
+ sAList.insert(album->url().remove(0,1), album->id());
+ }
+ }
+
+ // Now we can iterate over the sorted list and fill the combobox
+ int index = 0;
+ for ( SortedList::Iterator it = sAList.begin();
+ it != sAList.end(); ++it )
+ {
+ m_valueCombo->insertSqueezedItem( it.key(), index );
+ m_itemsIndexIDMap.insert(index, it.data());
+ index++;
+ }
+
+ m_valueCombo->show();
+
+ connect( m_valueCombo, TQ_SIGNAL( activated(int) ),
+ this, TQ_SIGNAL( signalPropertyChanged() ));
+ }
+ else if (newType == TAGS)
+ {
+ m_valueCombo = new SqueezedComboBox( m_valueBox, "tagscombo" );
+ m_valueCombo->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );
+
+ AlbumManager* aManager = AlbumManager::instance();
+ AlbumList tList = aManager->allTAlbums();
+
+ m_itemsIndexIDMap.clear();
+
+ // First we need to sort the tags.
+ // We create a map with the album tagPath as key, so that it is
+ // automatically sorted.
+ SortedList sTList;
+
+ for ( AlbumList::Iterator it = tList.begin();
+ it != tList.end(); ++it )
+ {
+ TAlbum *album = (TAlbum*)(*it);
+ if ( !album->isRoot() )
+ {
+ sTList.insert(album->tagPath(false), album->id());
+ }
+ }
+
+ // Now we can iterate over the sorted list and fill the combobox
+ int index = 0;
+ for (SortedList::Iterator it = sTList.begin();
+ it != sTList.end(); ++it)
+ {
+ m_valueCombo->insertSqueezedItem( it.key(), index );
+ m_itemsIndexIDMap.insert( index, it.data() );
+ ++index;
+ }
+
+ m_valueCombo->show();
+
+ connect( m_valueCombo, TQ_SIGNAL( activated(int) ),
+ this, TQ_SIGNAL( signalPropertyChanged() ));
+ }
+ else if (newType == RATING)
+ {
+ m_ratingWidget = new RatingWidget( m_valueBox );
+ m_ratingWidget->show();
+
+ connect( m_ratingWidget, TQ_SIGNAL( signalRatingChanged(int) ),
+ this, TQ_SIGNAL( signalPropertyChanged() ));
+ }
+}
+
+TQString SearchAdvancedRule::urlKey() const
+{
+ return RuleKeyTable[m_key->currentItem()].key;
+}
+
+TQString SearchAdvancedRule::urlOperator() const
+{
+ TQString string;
+
+ int countItems = 0;
+ for (int i=0; i< RuleOpTableCount; i++)
+ {
+ if ( RuleOpTable[i].cat == m_widgetType )
+ {
+ if ( countItems == m_operator->currentItem() )
+ string = RuleOpTable[i].key;
+ ++countItems;
+ }
+ }
+
+ return string;
+}
+
+TQString SearchAdvancedRule::urlValue() const
+{
+ TQString string;
+
+ if (m_widgetType == LINEEDIT)
+ string = m_lineEdit->text() ;
+
+ else if (m_widgetType == DATE)
+ string = m_dateEdit->date().toString(TQt::ISODate) ;
+
+ else if (m_widgetType == TAGS || m_widgetType == ALBUMS)
+ string = TQString::number(m_itemsIndexIDMap[ m_valueCombo->currentItem() ]);
+
+ else if (m_widgetType == RATING)
+ string = TQString::number(m_ratingWidget->rating()) ;
+
+ return string;
+}
+
+TQWidget* SearchAdvancedRule::widget() const
+{
+ return m_box;
+}
+
+bool SearchAdvancedRule::isChecked() const
+{
+ return (m_check && m_check->isChecked());
+}
+
+void SearchAdvancedRule::addOption(Option option)
+{
+ if (option == NONE)
+ {
+ removeOption();
+ return;
+ }
+
+ m_box->layout()->remove(m_hbox);
+
+ m_optionsBox = new TQHBox(m_box);
+ new TQLabel(option == AND ? i18n("As well as") : i18n("Or"), m_optionsBox);
+ TQFrame* hline = new TQFrame(m_optionsBox);
+ hline->setFrameStyle(TQFrame::HLine|TQFrame::Sunken);
+ hline->setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Minimum);
+ m_optionsBox->show();
+
+ m_box->layout()->add(m_hbox);
+ m_option = option;
+}
+
+void SearchAdvancedRule::removeOption()
+{
+ m_option = NONE;
+ delete m_optionsBox;
+ m_optionsBox = 0;
+}
+
+void SearchAdvancedRule::addCheck()
+{
+ m_check = new TQCheckBox(m_hbox);
+ m_check->setSizePolicy(TQSizePolicy::Minimum, TQSizePolicy::Minimum);
+ m_hboxLayout->addWidget( m_check, 0, TQt::AlignRight );
+ m_check->show();
+
+ connect( m_check, TQ_SIGNAL( toggled( bool ) ),
+ this, TQ_SIGNAL( signalBaseItemToggled() ));
+}
+
+void SearchAdvancedRule::removeCheck()
+{
+ delete m_check;
+ m_check = 0;
+}
+
+SearchAdvancedGroup::SearchAdvancedGroup(TQWidget* parent)
+ : SearchAdvancedBase(SearchAdvancedBase::GROUP)
+{
+ m_box = new TQHBox(parent);
+ m_box->layout()->setSpacing(KDialog::spacingHint());
+ m_groupbox = new TQVGroupBox(m_box);
+ m_check = new TQCheckBox(m_box);
+ m_option = SearchAdvancedRule::NONE;
+ m_box->show();
+
+ connect( m_check, TQ_SIGNAL( toggled( bool ) ),
+ this, TQ_SIGNAL( signalBaseItemToggled() ));
+}
+
+SearchAdvancedGroup::~SearchAdvancedGroup()
+{
+ delete m_box;
+}
+
+TQWidget* SearchAdvancedGroup::widget() const
+{
+ return m_box;
+}
+
+bool SearchAdvancedGroup::isChecked() const
+{
+ return m_check->isChecked();
+}
+
+void SearchAdvancedGroup::addRule(SearchAdvancedRule* rule)
+{
+ if (m_childRules.isEmpty() && rule->option() != SearchAdvancedRule::NONE)
+ {
+ // this is the first rule being inserted in this group.
+ // get its option and remove its option
+ addOption(rule->option());
+ rule->removeOption();
+ }
+
+ rule->removeCheck();
+
+ m_childRules.append(rule);
+ rule->widget()->reparent(m_groupbox, TQPoint(0,0));
+ rule->widget()->show();
+}
+
+void SearchAdvancedGroup::removeRules()
+{
+ typedef TQValueList<SearchAdvancedRule*> RuleList;
+
+ for (RuleList::iterator it = m_childRules.begin();
+ it != m_childRules.end(); ++it)
+ {
+ SearchAdvancedRule* rule = (SearchAdvancedRule*)(*it);
+ if (it == m_childRules.begin())
+ {
+ rule->addOption(m_option);
+ }
+ rule->addCheck();
+
+ rule->widget()->reparent((TQWidget*)m_box->parent(), TQPoint(0,0));
+ rule->widget()->show();
+ }
+
+ m_childRules.clear();
+ removeOption();
+}
+
+TQValueList<SearchAdvancedRule*> SearchAdvancedGroup::childRules() const
+{
+ return m_childRules;
+}
+
+void SearchAdvancedGroup::addOption(Option option)
+{
+ m_option = option;
+ m_groupbox->setTitle(m_option == SearchAdvancedRule::AND ? i18n("As well as") : i18n("Or"));
+}
+
+void SearchAdvancedGroup::removeOption()
+{
+ m_option = NONE;
+ m_groupbox->setTitle("");
+}
+
+} // namespace Digikam
diff --git a/src/digikam/searchwidgets.h b/src/digikam/searchwidgets.h
new file mode 100644
index 00000000..75ca51ce
--- /dev/null
+++ b/src/digikam/searchwidgets.h
@@ -0,0 +1,391 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description :
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file searchwidgets.h */
+
+#ifndef SEARCHWIDGETS_H
+#define SEARCHWIDGETS_H
+
+class TQHBox;
+class TQVBox;
+class TQCheckBox;
+class TQComboBox;
+class TQLineEdit;
+class TQLabel;
+class TQVGroupBox;
+class TQLabel;
+
+class KURL;
+class KDateEdit;
+
+namespace Digikam
+{
+
+class RatingWidget;
+class SqueezedComboBox;
+
+/** @class SearchRuleLabel
+ *
+ * This class inherits everything from TQLabel, and adds one
+ * signal to it, when double clicked on it.
+ */
+class SearchRuleLabel: public TQLabel
+{
+ TQ_OBJECT
+
+public:
+
+ /** Constructor. See for more info the TQLabel clas.
+ * @param text Text of the label
+ * @param parent The parent widget
+ * @param name The name
+ * @param f WFlags
+ */
+ SearchRuleLabel(const TQString & text,
+ TQWidget * parent,
+ const char * name=0,
+ WFlags f=0 );
+
+signals:
+
+ /**
+ * Signal which gets emitted when a double clicked
+ * event occurs
+ * @param e the mouse event received
+ */
+ void signalDoubleClick( TQMouseEvent * e );
+
+private:
+
+ void mouseDoubleClickEvent( TQMouseEvent * e );
+};
+
+/** @class SearchAdvancedBase
+ *
+ * This class is a common class for SearchAdvancedRule and
+ * SearchAdvancedGroup. It contains the basic functionality
+ * for the advanced search dialog.
+ * @author Renchi Raju
+ * @author Tom Albers
+ *
+ */
+class SearchAdvancedBase : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ /** @enum Type
+ * Possible types. A Base item can either be a Rule on its own
+ * or hold a group of rules.
+ */
+ enum Type
+ {
+ RULE = 0,
+ GROUP
+ };
+
+ /** @enum Option
+ * Possible Options. A Rule or a group of rules can have a relation
+ * to the previous rule or group of rules. None can be used for the
+ * first rule or group.
+ */
+ enum Option
+ {
+ NONE = 0,
+ AND,
+ OR
+ };
+
+ /**
+ * Constructor
+ * @param type Determines which type of base item to be created.
+ */
+ SearchAdvancedBase(Type type): m_type(type) {}
+
+ /**
+ * Destructor
+ */
+ virtual ~SearchAdvancedBase() {}
+
+ /**
+ * Returns the type of the base item
+ * @return The type which is a enum, see above
+ */
+ Type type() const { return m_type; }
+
+ /**
+ * Returns a pointer to the widget holding the base item
+ * @return Pointer to the widget
+ */
+ virtual TQWidget* widget() const = 0;
+
+ /**
+ * Determines if the base item is checked or not
+ * @return true if the base item is checked and false if not
+ */
+ virtual bool isChecked() const = 0;
+
+ /**
+ * Returns the option which is accociated with the base item
+ * @return The Option which is a enum, see above
+ */
+ Option option() const { return m_option; }
+
+ /**
+ * Sets the option of the base item
+ * @param option The enum of the option to be set
+ */
+ virtual void addOption(Option option) = 0;
+
+ /**
+ * Removes the option of the base item
+ */
+ virtual void removeOption() = 0;
+
+signals:
+ /**
+ * This signal is emitted when a rule or group is checked or unchecked
+ * This is used to determine the state of the buttons of the dialog
+ */
+ void signalBaseItemToggled();
+
+ /**
+ * This signal is emitted when there is a change in the rule.
+ * This is used in the dialog to recalculate the url to pass to the
+ * preview widget
+ */
+ void signalPropertyChanged();
+
+protected:
+
+ enum Option m_option;
+ enum Type m_type;
+};
+
+class SearchAdvancedGroup;
+
+/** @class SearchAdvancedRule
+ * This inherits SearchAdvancedBase and is one rule in the search dialog
+ * it contains all widgets to create a rule
+ */
+class SearchAdvancedRule : public SearchAdvancedBase
+{
+ TQ_OBJECT
+
+public:
+ /**
+ * Constructor
+ * @param parent The parent
+ * @param option Holds the Option of the rule, see the enum.
+ *
+ */
+ SearchAdvancedRule(TQWidget* parent, Option option);
+
+ /**
+ * destructor
+ */
+ ~SearchAdvancedRule();
+
+ /**
+ * Returns a pointer to the widget holding the rule
+ * @return Pointer to the widget
+ */
+ TQWidget* widget() const;
+
+ /**
+ * Determines if the rule is checked or not
+ * @return True if the rule is checked, false if not
+ */
+ bool isChecked() const;
+
+ /**
+ * Sets the values of the rule.
+ * @param url The url which sets defaults for the rule.
+ */
+ void setValues(const KURL& url);
+
+ /**
+ * Sets the option of the rule, so this holds the
+ * relation ship to the previous rule or group
+ * @param option the enum of the option to be set
+ */
+ void addOption(Option option);
+
+ /**
+ * Removes the option of the rule
+ */
+ void removeOption();
+
+ /**
+ * This adds the checkbox at the end of the rule. This is needed
+ * for example when a group of rules get ungrouped.
+ */
+ void addCheck();
+
+ /**
+ * This removes the checkbox at the end of the rule, this is
+ * used if the rule is a part of a group. In a group the group
+ * has the checkbox, not the individual rules.
+ */
+ void removeCheck();
+
+ /**
+ * Gives back the value of the key part of the rule, which can
+ * be used to build the correct url for the tdeio
+ * @return The value of the key part of the rule
+ */
+ TQString urlKey() const;
+
+ /**
+ * Gives back the value of the operator part of the rule, which can
+ * be used to build the correct url for the tdeio
+ * @return The value of the operator part of the rule
+ */
+ TQString urlOperator() const;
+
+ /**
+ * Gives back the value of the value part of the rule, which can
+ * be used to build the correct url for the tdeio
+ * @return The value of the value part of the rule
+ */
+ TQString urlValue() const;
+
+ enum valueWidgetTypes
+ {
+ NOWIDGET = 0,
+ LINEEDIT,
+ DATE,
+ ALBUMS,
+ TAGS,
+ RATING
+ };
+
+private:
+
+ void setValueWidget(valueWidgetTypes oldType, valueWidgetTypes newType);
+
+private slots:
+
+ void slotKeyChanged(int);
+ void slotLabelDoubleClick();
+
+private:
+
+ TQLabel* m_label;
+
+ TQVBox* m_box;
+ TQWidget* m_hbox;
+ TQHBoxLayout* m_hboxLayout;
+
+ TQHBox* m_valueBox;
+
+ TQCheckBox* m_check;
+
+ TQComboBox* m_key;
+ TQComboBox* m_operator;
+
+ TQLineEdit* m_lineEdit;
+ KDateEdit* m_dateEdit;
+ SqueezedComboBox* m_valueCombo;
+ RatingWidget* m_ratingWidget;
+
+ TQMap<int, int> m_itemsIndexIDMap;
+
+ TQHBox* m_optionsBox;
+
+ enum valueWidgetTypes m_widgetType;
+};
+
+/** @class SearchAdvancedGroup
+ * This inherits SearchAdvancedBase and is a group of rules
+ * in the search dialog
+ */
+class SearchAdvancedGroup : public SearchAdvancedBase
+{
+
+public:
+ /**
+ * Constructor
+ * @param parent the parent
+ */
+ SearchAdvancedGroup(TQWidget* parent);
+
+ /**
+ * Destructor
+ */
+ ~SearchAdvancedGroup();
+
+ /**
+ * Returns a pointer to the widget holding the group
+ * @return pointer to the widget
+ */
+ TQWidget* widget() const;
+
+ /**
+ * determines if the rule is checked or not
+ * @return bool, which indicated if the base item is checked
+ */
+ bool isChecked() const;
+
+ /**
+ * sets the option of the group
+ * @param option the enum of the option to be set
+ */
+ void addOption(Option option);
+
+ /**
+ * removes the option
+ */
+ void removeOption();
+
+ /**
+ * adds a rule to group
+ * @param rule the pointer to the rule to be added to the group
+ */
+ void addRule(SearchAdvancedRule* rule);
+
+ /**
+ * removes all rules from the group
+ */
+ void removeRules();
+
+ /**
+ * gives back a list of pointers to the rules inside the group
+ * @return a list of pointers to the childeren in the group
+ */
+ TQValueList<SearchAdvancedRule*> childRules() const;
+
+private:
+
+ TQHBox* m_box;
+ TQVGroupBox* m_groupbox;
+ TQCheckBox* m_check;
+ TQValueList<SearchAdvancedRule*> m_childRules;
+};
+
+} // namespace Digikam
+
+#endif /* SEARCHWIDGETS_H */
diff --git a/src/digikam/syncjob.cpp b/src/digikam/syncjob.cpp
new file mode 100644
index 00000000..b350aabc
--- /dev/null
+++ b/src/digikam/syncjob.cpp
@@ -0,0 +1,288 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-10-04
+ * Description : sync IO jobs.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * Concept copied from tdelibs/tdeio/tdeio/netaccess.h/cpp
+ * This file is part of the KDE libraries
+ * Copyright (C) 1997 Torben Weis (weis@kde.org)
+ * Copyright (C) 1998 Matthias Ettrich (ettrich@kde.org)
+ * Copyright (C) 1999 David Faure (faure@kde.org)
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqapplication.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <tdeio/job.h>
+#include <kprotocolinfo.h>
+#include <tdeglobalsettings.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albumsettings.h"
+#include "thumbnailjob.h"
+#include "thumbnailsize.h"
+#include "albumthumbnailloader.h"
+#include "album.h"
+#include "syncjob.h"
+#include "syncjob.moc"
+
+void tqt_enter_modal( TQWidget *widget );
+void tqt_leave_modal( TQWidget *widget );
+
+namespace Digikam
+{
+
+TQString* SyncJob::lastErrorMsg_ = 0;
+int SyncJob::lastErrorCode_ = 0;
+
+bool SyncJob::del(const KURL::List& urls, bool useTrash)
+{
+ SyncJob sj;
+
+ if (useTrash)
+ return sj.trashPriv(urls);
+ else
+ return sj.delPriv(urls);
+}
+
+bool SyncJob::file_move(const KURL &src, const KURL &dest)
+{
+ SyncJob sj;
+ return sj.fileMovePriv(src, dest);
+}
+
+TQPixmap SyncJob::getTagThumbnail(TAlbum *album)
+{
+ SyncJob sj;
+ return sj.getTagThumbnailPriv(album);
+}
+
+TQPixmap SyncJob::getTagThumbnail(const TQString &name, int size)
+{
+ SyncJob sj;
+ return sj.getTagThumbnailPriv(name, size);
+}
+
+SyncJob::SyncJob()
+{
+ thumbnail_ = 0;
+ album_ = 0;
+}
+
+SyncJob::~SyncJob()
+{
+ if (thumbnail_)
+ delete thumbnail_;
+}
+
+bool SyncJob::delPriv(const KURL::List& urls)
+{
+ success_ = true;
+
+ TDEIO::Job* job = TDEIO::del( urls );
+ connect( job, TQ_SIGNAL(result( TDEIO::Job* )),
+ TQ_SLOT(slotResult( TDEIO::Job*)) );
+
+ enter_loop();
+ return success_;
+}
+
+bool SyncJob::trashPriv(const KURL::List& urls)
+{
+ success_ = true;
+ KURL dest("trash:/");
+
+ if (!KProtocolInfo::isKnownProtocol(dest))
+ {
+ dest = TDEGlobalSettings::trashPath();
+ }
+
+ TDEIO::Job* job = TDEIO::move( urls, dest );
+ connect( job, TQ_SIGNAL(result( TDEIO::Job* )),
+ TQ_SLOT(slotResult( TDEIO::Job*)) );
+
+ enter_loop();
+ return success_;
+}
+
+bool SyncJob::fileMovePriv(const KURL &src, const KURL &dest)
+{
+ success_ = true;
+
+ TDEIO::FileCopyJob* job = TDEIO::file_move(src, dest, -1,
+ true, false, false);
+ connect( job, TQ_SIGNAL(result( TDEIO::Job* )),
+ TQ_SLOT(slotResult( TDEIO::Job*)) );
+
+ enter_loop();
+ return success_;
+}
+
+void SyncJob::enter_loop()
+{
+ TQWidget dummy(0,0,WType_Dialog | WShowModal);
+ dummy.setFocusPolicy( TQWidget::NoFocus );
+ tqt_enter_modal(&dummy);
+ tqApp->enter_loop();
+ tqt_leave_modal(&dummy);
+}
+
+void SyncJob::slotResult( TDEIO::Job * job )
+{
+ lastErrorCode_ = job->error();
+ success_ = !(lastErrorCode_);
+ if ( !success_ )
+ {
+ if ( !lastErrorMsg_ )
+ lastErrorMsg_ = new TQString;
+ *lastErrorMsg_ = job->errorString();
+ }
+ tqApp->exit_loop();
+}
+
+TQPixmap SyncJob::getTagThumbnailPriv(TAlbum *album)
+{
+ if (thumbnail_)
+ delete thumbnail_;
+ thumbnail_ = new TQPixmap;
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+
+ if (!loader->getTagThumbnail(album, *thumbnail_))
+ {
+ if (thumbnail_->isNull())
+ {
+ return loader->getStandardTagIcon(album);
+ }
+ else
+ {
+ return loader->blendIcons(loader->getStandardTagIcon(), *thumbnail_);
+ }
+ }
+ else
+ {
+ connect(loader, TQ_SIGNAL(signalThumbnail(Album *, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnailFromIcon(Album *, const TQPixmap&)));
+
+ connect(loader, TQ_SIGNAL(signalFailed(Album *)),
+ this, TQ_SLOT(slotLoadThumbnailFailed(Album *)));
+
+ album_ = album;
+ enter_loop();
+ }
+ return *thumbnail_;
+}
+
+void SyncJob::slotLoadThumbnailFailed(Album *album)
+{
+ // TODO: setting _lastError*
+ if (album == album_)
+ {
+ tqApp->exit_loop();
+ }
+}
+
+void SyncJob::slotGotThumbnailFromIcon(Album *album, const TQPixmap& pix)
+{
+ if (album == album_)
+ {
+ *thumbnail_ = pix;
+ tqApp->exit_loop();
+ }
+}
+
+TQPixmap SyncJob::getTagThumbnailPriv(const TQString &name, int size)
+{
+ thumbnailSize_ = size;
+ if (thumbnail_)
+ delete thumbnail_;
+ thumbnail_ = new TQPixmap;
+
+ if(name.startsWith("/"))
+ {
+ ThumbnailJob *job = new ThumbnailJob(name,
+ ThumbnailSize::Tiny,
+ false,
+ AlbumSettings::instance()->getExifRotate());
+ connect(job,
+ TQ_SIGNAL(signalThumbnail(const KURL&,
+ const TQPixmap&)),
+ TQ_SLOT(slotGotThumbnailFromIcon(const KURL&,
+ const TQPixmap&)));
+ connect(job,
+ TQ_SIGNAL(signalFailed(const KURL&)),
+ TQ_SLOT(slotLoadThumbnailFailed()));
+
+ enter_loop();
+ job->kill();
+ }
+ else
+ {
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+ *thumbnail_ = iconLoader->loadIcon(name, TDEIcon::NoGroup, thumbnailSize_,
+ TDEIcon::DefaultState, 0, true);
+ }
+ return *thumbnail_;
+}
+
+void SyncJob::slotLoadThumbnailFailed()
+{
+ // TODO: setting _lastError*
+ tqApp->exit_loop();
+}
+
+void SyncJob::slotGotThumbnailFromIcon(const KURL&, const TQPixmap& pix)
+{
+ if(!pix.isNull() && (thumbnailSize_ < ThumbnailSize::Tiny))
+ {
+ int w1 = pix.width();
+ int w2 = thumbnailSize_;
+ int h1 = pix.height();
+ int h2 = thumbnailSize_;
+ thumbnail_->resize(w2,h2);
+ bitBlt(thumbnail_, 0, 0, &pix, (w1-w2)/2, (h1-h2)/2, w2, h2);
+ }
+ else
+ {
+ *thumbnail_ = pix;
+ }
+ tqApp->exit_loop();
+}
+
+TQString SyncJob::lastErrorMsg()
+{
+ return (lastErrorMsg_ ? *lastErrorMsg_ : TQString());
+}
+
+int SyncJob::lastErrorCode()
+{
+ return lastErrorCode_;
+}
+
+} // namespace Digikam
+
diff --git a/src/digikam/syncjob.h b/src/digikam/syncjob.h
new file mode 100644
index 00000000..fca82613
--- /dev/null
+++ b/src/digikam/syncjob.h
@@ -0,0 +1,108 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-10-04
+ * Description : sync IO jobs.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * Concept copied from tdelibs/tdeio/tdeio/netaccess.h/cpp
+ * This file is part of the KDE libraries
+ * Copyright (C) 1997 Torben Weis (weis@kde.org)
+ * Copyright (C) 1998 Matthias Ettrich (ettrich@kde.org)
+ * Copyright (C) 1999 David Faure (faure@kde.org)
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef SYNCJOB_H
+#define SYNCJOB_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+class TQString;
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace Digikam
+{
+
+class Album;
+class TAlbum;
+
+class SyncJob : public TQObject
+{
+ TQ_OBJECT
+
+public:
+
+ /* this will delete the urls. */
+ static bool del(const KURL::List& urls, bool useTrash);
+
+ /* remove this when we move dependency upto kde 3.2 */
+ static bool file_move(const KURL &src, const KURL &dest);
+
+ /* Load the image or icon for the tag thumbnail */
+ static TQPixmap getTagThumbnail(TAlbum *album);
+ static TQPixmap getTagThumbnail(const TQString &name, int size);
+
+ static TQString lastErrorMsg();
+ static int lastErrorCode();
+
+private:
+
+ SyncJob();
+ ~SyncJob();
+
+ bool delPriv(const KURL::List& urls);
+ bool trashPriv(const KURL::List& urls);
+
+ bool fileMovePriv(const KURL &src, const KURL &dest);
+
+ TQPixmap getTagThumbnailPriv(TAlbum *album);
+ TQPixmap getTagThumbnailPriv(const TQString &name, int size);
+
+ void enter_loop();
+
+ static int lastErrorCode_;
+ static TQString* lastErrorMsg_;
+ bool success_;
+
+ TQPixmap *thumbnail_;
+ Album *album_;
+ int thumbnailSize_;
+
+private slots:
+
+ void slotResult( TDEIO::Job * job );
+ void slotGotThumbnailFromIcon(Album *album, const TQPixmap& pix);
+ void slotLoadThumbnailFailed(Album *album);
+ void slotGotThumbnailFromIcon(const KURL& url, const TQPixmap& pix);
+ void slotLoadThumbnailFailed();
+};
+
+} // namespace Digikam
+
+#endif /* SYNCJOB_H */
diff --git a/src/digikam/tageditdlg.cpp b/src/digikam/tageditdlg.cpp
new file mode 100644
index 00000000..d77a0169
--- /dev/null
+++ b/src/digikam/tageditdlg.cpp
@@ -0,0 +1,406 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-01
+ * Description : dialog to edit and create digiKam Tags
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kicondialog.h>
+#include <tdeapplication.h>
+#include <tdeversion.h>
+#include <kiconloader.h>
+#include <kseparator.h>
+#include <tdelistview.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "searchtextbar.h"
+#include "syncjob.h"
+#include "tageditdlg.h"
+#include "tageditdlg.moc"
+
+namespace Digikam
+{
+
+class TagsListCreationErrorDialog : public KDialogBase
+{
+
+public:
+
+ TagsListCreationErrorDialog(TQWidget* parent, const TQMap<TQString, TQString>& errMap);
+ ~TagsListCreationErrorDialog(){};
+};
+
+// ------------------------------------------------------------------------------
+
+class TagEditDlgPriv
+{
+public:
+
+ TagEditDlgPriv()
+ {
+ titleEdit = 0;
+ iconButton = 0;
+ resetIconButton = 0;
+ mainRootAlbum = 0;
+ topLabel = 0;
+ create = false;
+ }
+
+ bool create;
+
+ TQLabel *topLabel;
+
+ TQString icon;
+
+ TQPushButton *iconButton;
+ TQPushButton *resetIconButton;
+
+ TAlbum *mainRootAlbum;
+ SearchTextBar *titleEdit;
+};
+
+TagEditDlg::TagEditDlg(TQWidget *parent, TAlbum* album, bool create)
+ : KDialogBase(parent, 0, true, 0, Help|Ok|Cancel, Ok, true)
+{
+ d = new TagEditDlgPriv;
+ d->mainRootAlbum = album;
+ d->create = create;
+
+ setHelp("tagscreation.anchor", "digikam");
+ if (d->create) setCaption(i18n("New Tag"));
+ else setCaption(i18n("Edit Tag"));
+
+ TQWidget *page = makeMainWidget();
+ TQGridLayout* grid = new TQGridLayout(page, 5, 4, 0, spacingHint());
+
+ // --------------------------------------------------------
+
+ TQLabel *logo = new TQLabel(page);
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 96, TDEIcon::DefaultState, 0, true));
+
+ d->topLabel = new TQLabel(page);
+ d->topLabel->setAlignment(TQt::AlignAuto | TQt::AlignVCenter | TQt::SingleLine);
+
+ KSeparator *line = new KSeparator (Horizontal, page);
+
+ // --------------------------------------------------------
+
+ TQLabel *titleLabel = new TQLabel(page);
+ titleLabel->setText(i18n("&Title:"));
+
+ d->titleEdit = new SearchTextBar(page, "TagEditDlgTitleEdit", i18n("Enter tag name here..."));
+ titleLabel->setBuddy(d->titleEdit);
+
+ TQLabel *tipLabel = new TQLabel(page);
+ tipLabel->setTextFormat(TQt::RichText);
+ tipLabel->setText(i18n("<qt><p>To create new tags, you can use the following rules:</p>"
+ "<p><ul><li>'/' can be used to create a tags hierarchy.<br>"
+ "Ex.: <i>\"Country/City/Paris\"</i></li>"
+ "<li>',' can be used to create more than one tags hierarchy at the same time.<br>"
+ "Ex.: <i>\"City/Paris, Monument/Notre-Dame\"</i></li>"
+ "<li>If a tag hierarchy starts with '/', root tag album is used as parent.</li></ul></p></qt>"
+ ));
+
+ if (d->create)
+ {
+ AlbumList tList = AlbumManager::instance()->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum *tag = dynamic_cast<TAlbum*>(*it);
+ d->titleEdit->lineEdit()->completionObject()->addItem(tag->tagPath());
+ }
+ }
+ else
+ {
+ d->titleEdit->setText(d->mainRootAlbum->title());
+ tipLabel->hide();
+ }
+
+ TQLabel *iconTextLabel = new TQLabel(page);
+ iconTextLabel->setText(i18n("&Icon:"));
+
+ d->iconButton = new TQPushButton(page);
+ d->iconButton->setFixedSize(40, 40);
+ iconTextLabel->setBuddy(d->iconButton);
+
+ // In create mode, by default assign the icon of the parent (if not root) to this new tag.
+ if (d->create && !d->mainRootAlbum->isRoot())
+ d->icon = d->mainRootAlbum->icon();
+ else
+ d->icon = d->mainRootAlbum->icon();
+
+ d->iconButton->setIconSet(SyncJob::getTagThumbnail(d->icon, 20));
+
+ d->resetIconButton = new TQPushButton(SmallIcon("reload_page"), i18n("Reset"), page);
+ if (d->create) d->resetIconButton->hide();
+
+ // --------------------------------------------------------
+
+ grid->addMultiCellWidget(logo, 0, 3, 0, 0);
+ grid->addMultiCellWidget(d->topLabel, 0, 0, 1, 4);
+ grid->addMultiCellWidget(line, 1, 1, 1, 4);
+ grid->addMultiCellWidget(tipLabel, 2, 2, 1, 4);
+ grid->addMultiCellWidget(titleLabel, 3, 3, 1, 1);
+ grid->addMultiCellWidget(d->titleEdit, 3, 3, 2, 4);
+ grid->addMultiCellWidget(iconTextLabel, 4, 4, 1, 1);
+ grid->addMultiCellWidget(d->iconButton, 4, 4, 2, 2);
+ grid->addMultiCellWidget(d->resetIconButton, 4, 4, 3, 3);
+ grid->setColStretch(4, 10);
+ grid->setRowStretch(5, 10);
+
+ // --------------------------------------------------------
+
+ connect(d->iconButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotIconChanged()));
+
+ connect(d->resetIconButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotIconResetClicked()));
+
+ connect(d->titleEdit->lineEdit(), TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotTitleChanged(const TQString&)));
+
+ // --------------------------------------------------------
+
+ slotTitleChanged(d->titleEdit->text());
+ d->titleEdit->lineEdit()->setFocus();
+ adjustSize();
+}
+
+TagEditDlg::~TagEditDlg()
+{
+ delete d;
+}
+
+TQString TagEditDlg::title() const
+{
+ return d->titleEdit->text();
+}
+
+TQString TagEditDlg::icon() const
+{
+ return d->icon;
+}
+
+void TagEditDlg::slotIconResetClicked()
+{
+ d->icon = TQString("tag");
+ d->iconButton->setIconSet(SyncJob::getTagThumbnail(d->icon, 20));
+}
+
+void TagEditDlg::slotIconChanged()
+{
+#if KDE_IS_VERSION(3,3,0)
+ TDEIconDialog dlg(this);
+ dlg.setup(TDEIcon::NoGroup, TDEIcon::Application, false, 20, false, false, false);
+ TQString icon = dlg.openDialog();
+#else
+ TQString icon = TDEIconDialog::getIcon(TDEIcon::NoGroup, TDEIcon::Application, false, 20);
+ if (icon.startsWith("/"))
+ return;
+#endif
+
+ if (icon.isEmpty() || icon == d->icon)
+ return;
+
+ d->icon = icon;
+ d->iconButton->setIconSet(SyncJob::getTagThumbnail(d->icon, 20));
+}
+
+void TagEditDlg::slotTitleChanged(const TQString& newtitle)
+{
+ TQString tagName = d->mainRootAlbum->tagPath();
+ if (tagName.endsWith("/") && !d->mainRootAlbum->isRoot())
+ tagName.truncate(tagName.length()-1);
+
+ if (d->create)
+ {
+ if (d->titleEdit->text().startsWith("/"))
+ {
+ d->topLabel->setText(i18n("<qt><b>Create New Tag</b></qt>"));
+ }
+ else
+ {
+ d->topLabel->setText(i18n("<qt><b>Create New Tag in<br>"
+ "<i>\"%1\"</i></b></qt>").arg(tagName));
+ }
+ }
+ else
+ {
+ d->topLabel->setText(i18n("<qt><b>Properties of Tag<br>"
+ "<i>\"%1\"</i></b></qt>").arg(tagName));
+ }
+
+ enableButtonOK(!newtitle.isEmpty());
+}
+
+bool TagEditDlg::tagEdit(TQWidget *parent, TAlbum* album, TQString& title, TQString& icon)
+{
+ TagEditDlg dlg(parent, album);
+
+ bool valRet = dlg.exec();
+ if (valRet == TQDialog::Accepted)
+ {
+ title = dlg.title();
+ icon = dlg.icon();
+ }
+
+ return valRet;
+}
+
+bool TagEditDlg::tagCreate(TQWidget *parent, TAlbum* album, TQString& title, TQString& icon)
+{
+ TagEditDlg dlg(parent, album, true);
+
+ bool valRet = dlg.exec();
+ if (valRet == TQDialog::Accepted)
+ {
+ title = dlg.title();
+ icon = dlg.icon();
+ }
+
+ return valRet;
+}
+
+AlbumList TagEditDlg::createTAlbum(TAlbum *mainRootAlbum, const TQString& tagStr, const TQString& icon,
+ TQMap<TQString, TQString>& errMap)
+{
+ errMap.clear();
+ AlbumList createdTagsList;
+
+ // Check if new tags are include in a list of tags hierarchy separated by ','.
+ // Ex: /Country/France/people,/City/France/Paris
+
+ TQStringList tagsHierarchies = TQStringList::split(",", tagStr);
+ if (tagsHierarchies.isEmpty())
+ return createdTagsList;
+
+ for (TQStringList::const_iterator it = tagsHierarchies.begin(); it != tagsHierarchies.end(); ++it)
+ {
+ TQString hierarchy = (*it).stripWhiteSpace();
+ if (!hierarchy.isEmpty())
+ {
+ // Check if new tags is a hierarchy of tags separated by '/'.
+
+ TAlbum *root = 0;
+
+ if (hierarchy.startsWith("/") || !mainRootAlbum)
+ root = AlbumManager::instance()->findTAlbum(0);
+ else
+ root = mainRootAlbum;
+
+ TQStringList tagsList = TQStringList::split("/", hierarchy);
+ DDebug() << tagsList << endl;
+
+ if (!tagsList.isEmpty())
+ {
+ for (TQStringList::iterator it2 = tagsList.begin(); it2 != tagsList.end(); ++it2)
+ {
+ TQString tagPath, errMsg;
+ TQString tag = (*it2).stripWhiteSpace();
+ if (root->isRoot())
+ tagPath = TQString("/%1").arg(tag);
+ else
+ tagPath = TQString("%1/%2").arg(root->tagPath()).arg(tag);
+
+ DDebug() << tag << " :: " << tagPath << endl;
+
+ if (!tag.isEmpty())
+ {
+ // Tag already exist ?
+ TAlbum* album = AlbumManager::instance()->findTAlbum(tagPath);
+ if (!album)
+ {
+ root = AlbumManager::instance()->createTAlbum(root, tag, icon, errMsg);
+ }
+ else
+ {
+ root = album;
+ if (*it2 == tagsList.last())
+ errMap.insert(tagPath, i18n("Tag name already exists"));
+ }
+
+ if (root)
+ createdTagsList.append(root);
+ }
+
+ // Sanity check if tag creation failed.
+ if (!root)
+ {
+ errMap.insert(tagPath, errMsg);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return createdTagsList;
+}
+
+void TagEditDlg::showtagsListCreationError(TQWidget* parent, const TQMap<TQString, TQString>& errMap)
+{
+ if (!errMap.isEmpty())
+ {
+ TagsListCreationErrorDialog dlg(parent, errMap);
+ dlg.exec();
+ }
+}
+
+// ------------------------------------------------------------------------------
+
+TagsListCreationErrorDialog::TagsListCreationErrorDialog(TQWidget* parent, const TQMap<TQString, TQString>& errMap)
+ : KDialogBase(parent, 0, true, 0, Help|Ok, Ok, false)
+{
+ setHelp("tagscreation.anchor", "digikam");
+ setCaption(i18n("Tag creation Error"));
+
+ TQWidget* box = makeMainWidget();
+ TQVBoxLayout* vLay = new TQVBoxLayout(box);
+
+ TQLabel *label = new TQLabel(i18n("Error been occured during Tag creation:"), box);
+ TDEListView *listView = new TDEListView(box);
+ listView->addColumn(i18n("Tag Path"));
+ listView->addColumn(i18n("Error"));
+ listView->setResizeMode(TQListView::LastColumn);
+
+ vLay->addWidget(label);
+ vLay->addWidget(listView);
+ vLay->setMargin(0);
+ vLay->setSpacing(0);
+
+ for (TQMap<TQString, TQString>::const_iterator it = errMap.begin() ; it != errMap.end() ; ++it)
+ new TDEListViewItem(listView, it.key(), it.data());
+
+ adjustSize();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/tageditdlg.h b/src/digikam/tageditdlg.h
new file mode 100644
index 00000000..a1bfcfa2
--- /dev/null
+++ b/src/digikam/tageditdlg.h
@@ -0,0 +1,85 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-01
+ * Description : dialog to edit and create digiKam Tags
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TAGEDITDLG_H
+#define TAGEDITDLG_H
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "albummanager.h"
+
+namespace Digikam
+{
+class TagEditDlgPriv;
+
+class TagEditDlg : public KDialogBase
+{
+ TQ_OBJECT
+
+public:
+
+ TagEditDlg(TQWidget *parent, TAlbum* album, bool create=false);
+ ~TagEditDlg();
+
+ TQString title() const;
+ TQString icon() const;
+
+ static bool tagEdit(TQWidget *parent, TAlbum* album, TQString& title, TQString& icon);
+ static bool tagCreate(TQWidget *parent, TAlbum* album, TQString& title, TQString& icon);
+
+ /** Create a list of new Tag album using a list of tags hierarchies separated by ",".
+ A hierarchy of tags is a string path of tags name separated by "/".
+ If a hierarchy start by "/" or if mainRootAlbum is null, it will be created from
+ root tag album, else it will be created from mainRootAlbum as parent album.
+ 'errMap' is Map of TAlbum path and error message if tag creation failed.
+ Return the list of created Albums.
+ */
+ static AlbumList createTAlbum(TAlbum *mainRootAlbum, const TQString& tagStr, const TQString& icon,
+ TQMap<TQString, TQString>& errMap);
+
+ static void showtagsListCreationError(TQWidget* parent, const TQMap<TQString, TQString>& errMap);
+
+private slots:
+
+ void slotIconChanged();
+ void slotIconResetClicked();
+ void slotTitleChanged(const TQString& newtitle);
+
+private:
+
+ TagEditDlgPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* TAGEDITDLG_H */
diff --git a/src/digikam/tagfilterview.cpp b/src/digikam/tagfilterview.cpp
new file mode 100644
index 00000000..2bebc2b8
--- /dev/null
+++ b/src/digikam/tagfilterview.cpp
@@ -0,0 +1,1429 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-05
+ * Description : tags filter view
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqheader.h>
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqtimer.h>
+#include <tqcursor.h>
+
+// KDE includes.
+
+#include <tdeabc/stdaddressbook.h>
+#include <tdepopupmenu.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kiconloader.h>
+#include <tdeglobalsettings.h>
+#include <kcursor.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "albumdb.h"
+#include "album.h"
+#include "albumlister.h"
+#include "albumthumbnailloader.h"
+#include "syncjob.h"
+#include "dragobjects.h"
+#include "folderitem.h"
+#include "imageattributeswatch.h"
+#include "imageinfo.h"
+#include "metadatahub.h"
+#include "tageditdlg.h"
+#include "statusprogressbar.h"
+#include "tagfilterview.h"
+#include "tagfilterview.moc"
+
+// X11 includes.
+
+extern "C"
+{
+#include <X11/Xlib.h>
+}
+
+namespace Digikam
+{
+
+class TagFilterViewItem : public FolderCheckListItem
+{
+
+public:
+
+ TagFilterViewItem(TQListView* parent, TAlbum* tag, bool untagged=false);
+ TagFilterViewItem(TQListViewItem* parent, TAlbum* tag);
+
+ TAlbum* album() const;
+ int id() const;
+ bool untagged() const;
+ void refresh();
+ void setOpen(bool o);
+ void setCount(int count);
+ int count();
+ int compare(TQListViewItem* i, int column, bool ascending) const;
+
+private:
+
+ void stateChange(bool val);
+ void paintCell(TQPainter* p, const TQColorGroup & cg, int column, int width, int align);
+
+private:
+
+ bool m_untagged;
+
+ int m_count;
+
+ TAlbum *m_album;
+};
+
+TagFilterViewItem::TagFilterViewItem(TQListView* parent, TAlbum* album, bool untagged)
+ : FolderCheckListItem(parent, album ? album->title() : i18n("Not Tagged"),
+ TQCheckListItem::CheckBox/*Controller*/)
+{
+ m_album = album;
+ m_untagged = untagged;
+ m_count = 0;
+ setDragEnabled(!untagged);
+
+ if (m_album)
+ m_album->setExtraData(listView(), this);
+}
+
+TagFilterViewItem::TagFilterViewItem(TQListViewItem* parent, TAlbum* album)
+ : FolderCheckListItem(parent, album->title(),
+ TQCheckListItem::CheckBox/*Controller*/)
+{
+ m_album = album;
+ m_untagged = false;
+ m_count = 0;
+ setDragEnabled(true);
+
+ if (m_album)
+ m_album->setExtraData(listView(), this);
+}
+
+void TagFilterViewItem::refresh()
+{
+ if (!m_album) return;
+
+ if (AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
+ {
+ if (isOpen())
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(m_count));
+ else
+ {
+ int countRecursive = m_count;
+ AlbumIterator it(m_album);
+ while ( it.current() )
+ {
+ TagFilterViewItem *item = (TagFilterViewItem*)it.current()->extraData(listView());
+ if (item)
+ countRecursive += item->count();
+ ++it;
+ }
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(countRecursive));
+ }
+ }
+ else
+ {
+ setText(0, m_album->title());
+ }
+}
+
+void TagFilterViewItem::stateChange(bool val)
+{
+ TQCheckListItem::stateChange(val);
+
+ /* NOTE G.Caulier 2007/01/08: this code is now disable because TagFilterViewItem
+ have been changed from TQCheckListItem::CheckBoxController
+ to TQCheckListItem::CheckBox.
+
+ // All TagFilterViewItems are CheckBoxControllers. If they have no children,
+ // they should be of type CheckBox, but that is not possible with our way of adding items.
+ // When clicked, children-less items first change to the NoChange state, and a second
+ // click is necessary to set them to On and make the filter take effect.
+ // So set them to On if the condition is met.
+ if (!firstChild() && state() == NoChange)
+ {
+ setState(On);
+ }
+ */
+
+ ((TagFilterView*)listView())->stateChanged(this);
+}
+
+int TagFilterViewItem::compare(TQListViewItem* i, int column, bool ascending) const
+{
+ if (m_untagged)
+ return 1;
+
+ TagFilterViewItem* dItem = dynamic_cast<TagFilterViewItem*>(i);
+ if (!dItem)
+ return 0;
+
+ if (dItem && dItem->m_untagged)
+ return -1;
+
+ return TQListViewItem::compare(i, column, ascending);
+}
+
+void TagFilterViewItem::paintCell(TQPainter* p, const TQColorGroup & cg, int column, int width, int align)
+{
+ if (!m_untagged)
+ {
+ FolderCheckListItem::paintCell(p, cg, column, width, align);
+ return;
+ }
+
+ TQFont f(listView()->font());
+ f.setBold(true);
+ f.setItalic(true);
+ p->setFont(f);
+
+ TQColorGroup mcg(cg);
+ mcg.setColor(TQColorGroup::Text, TQt::darkRed);
+
+ FolderCheckListItem::paintCell(p, mcg, column, width, align);
+}
+
+void TagFilterViewItem::setOpen(bool o)
+{
+ TQListViewItem::setOpen(o);
+ refresh();
+}
+
+TAlbum* TagFilterViewItem::album() const
+{
+ return m_album;
+}
+
+int TagFilterViewItem::id() const
+{
+ return m_album ? m_album->id() : 0;
+}
+
+void TagFilterViewItem::setCount(int count)
+{
+ m_count = count;
+ refresh();
+}
+
+int TagFilterViewItem::count()
+{
+ return m_count;
+}
+
+bool TagFilterViewItem::untagged() const
+{
+ return m_untagged;
+}
+
+// ---------------------------------------------------------------------
+
+class TagFilterViewPrivate
+{
+
+public:
+
+ TagFilterViewPrivate()
+ {
+ ABCMenu = 0;
+ timer = 0;
+ toggleAutoTags = TagFilterView::NoToggleAuto;
+ matchingCond = AlbumLister::OrCondition;
+ }
+
+ TQTimer *timer;
+
+ TQPopupMenu *ABCMenu;
+
+ TagFilterView::ToggleAutoTags toggleAutoTags;
+
+ AlbumLister::MatchingCondition matchingCond;
+};
+
+TagFilterView::TagFilterView(TQWidget* parent)
+ : FolderView(parent, "TagFilterView")
+{
+ d = new TagFilterViewPrivate;
+ d->timer = new TQTimer(this);
+
+ addColumn(i18n("Tag Filters"));
+ setResizeMode(TQListView::LastColumn);
+ setRootIsDecorated(true);
+
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+
+ TagFilterViewItem* notTaggedItem = new TagFilterViewItem(this, 0, true);
+ notTaggedItem->setPixmap(0, AlbumThumbnailLoader::instance()->getStandardTagIcon());
+
+ // ------------------------------------------------------------------------
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalTAlbumsDirty(const TQMap<int, int>&)),
+ this, TQ_SLOT(slotRefresh(const TQMap<int, int>&)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotTagAdded(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotTagDeleted(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumRenamed(Album*)),
+ this, TQ_SLOT(slotTagRenamed(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(slotClear()));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumIconChanged(Album*)),
+ this, TQ_SLOT(slotAlbumIconChanged(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalTAlbumMoved(TAlbum*, TAlbum*)),
+ this, TQ_SLOT(slotTagMoved(TAlbum*, TAlbum*)));
+
+ // ------------------------------------------------------------------------
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+
+ connect(loader, TQ_SIGNAL(signalThumbnail(Album *, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnailFromIcon(Album *, const TQPixmap&)));
+
+ connect(loader, TQ_SIGNAL(signalFailed(Album *)),
+ this, TQ_SLOT(slotThumbnailLost(Album *)));
+
+ connect(loader, TQ_SIGNAL(signalReloadThumbnails()),
+ this, TQ_SLOT(slotReloadThumbnails()));
+
+ connect(this, TQ_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
+ this, TQ_SLOT(slotContextMenu(TQListViewItem*, const TQPoint&, int)));
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotTimeOut()));
+
+ // ------------------------------------------------------------------------
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("Tag Filters View");
+ d->matchingCond = (AlbumLister::MatchingCondition)(config->readNumEntry("Matching Condition",
+ AlbumLister::OrCondition));
+
+ d->toggleAutoTags = (ToggleAutoTags)(config->readNumEntry("Toggle Auto Tags", NoToggleAuto));
+}
+
+TagFilterView::~TagFilterView()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Tag Filters View");
+ config->writeEntry("Matching Condition", (int)(d->matchingCond));
+ config->writeEntry("Toggle Auto Tags", (int)(d->toggleAutoTags));
+ config->sync();
+
+ saveViewState();
+
+ delete d->timer;
+ delete d;
+}
+
+void TagFilterView::slotTextTagFilterChanged(const TQString& filter)
+{
+ if (filter.isEmpty())
+ {
+ collapseView();
+ return;
+ }
+
+ TQString search = filter.lower();
+
+ bool atleastOneMatch = false;
+
+ AlbumList tList = AlbumManager::instance()->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum* talbum = (TAlbum*)(*it);
+
+ // don't touch the root Album
+ if (talbum->isRoot())
+ continue;
+
+ bool match = talbum->title().lower().contains(search);
+ bool doesExpand = false;
+ if (!match)
+ {
+ // check if any of the parents match the search
+ Album* parent = talbum->parent();
+ while (parent && !parent->isRoot())
+ {
+ if (parent->title().lower().contains(search))
+ {
+ match = true;
+ break;
+ }
+
+ parent = parent->parent();
+ }
+ }
+
+ if (!match)
+ {
+ // check if any of the children match the search
+ AlbumIterator it(talbum);
+ while (it.current())
+ {
+ if ((*it)->title().lower().contains(search))
+ {
+ match = true;
+ doesExpand = true;
+ break;
+ }
+ ++it;
+ }
+ }
+
+ TagFilterViewItem* viewItem = (TagFilterViewItem*) talbum->extraData(this);
+
+ if (match)
+ {
+ atleastOneMatch = true;
+
+ if (viewItem)
+ {
+ viewItem->setVisible(true);
+ viewItem->setOpen(doesExpand);
+ }
+ }
+ else
+ {
+ if (viewItem)
+ {
+ viewItem->setVisible(false);
+ viewItem->setOpen(false);
+ }
+ }
+ }
+
+ emit signalTextTagFilterMatch(atleastOneMatch);
+}
+
+void TagFilterView::stateChanged(TagFilterViewItem* item)
+{
+ ToggleAutoTags oldAutoTags = d->toggleAutoTags;
+
+ switch(d->toggleAutoTags)
+ {
+ case Children:
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(item, item->isOn());
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ case Parents:
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleParentTags(item, item->isOn());
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ case ChildrenAndParents:
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(item, item->isOn());
+ toggleParentTags(item, item->isOn());
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ default:
+ break;
+ }
+
+ triggerChange();
+}
+
+void TagFilterView::triggerChange()
+{
+ d->timer->start(50, true);
+}
+
+TQDragObject* TagFilterView::dragObject()
+{
+ TagFilterViewItem *item = dynamic_cast<TagFilterViewItem*>(dragItem());
+ if(!item)
+ return 0;
+
+ TagDrag *t = new TagDrag(item->id(), this);
+ t->setPixmap(*item->pixmap(0));
+
+ return t;
+}
+
+bool TagFilterView::acceptDrop(const TQDropEvent *e) const
+{
+ TQPoint vp = contentsToViewport(e->pos());
+ TagFilterViewItem *itemDrop = dynamic_cast<TagFilterViewItem*>(itemAt(vp));
+ TagFilterViewItem *itemDrag = dynamic_cast<TagFilterViewItem*>(dragItem());
+
+ if(TagDrag::canDecode(e) || TagListDrag::canDecode(e))
+ {
+ // Allow dragging at the root, to move the tag to the root
+ if(!itemDrop)
+ return true;
+
+ // Do not allow dragging at the "Not Tagged" item.
+ if (itemDrop->untagged())
+ return false;
+
+ // Dragging an item on itself makes no sense
+ if(itemDrag == itemDrop)
+ return false;
+
+ // Dragging a parent on its child makes no sense
+ if(itemDrag && itemDrag->album()->isAncestorOf(itemDrop->album()))
+ return false;
+
+ return true;
+ }
+
+ if (ItemDrag::canDecode(e) && itemDrop && !itemDrop->untagged())
+ {
+ TAlbum *tag = itemDrop->album();
+
+ if (tag)
+ {
+ if (tag->parent())
+ {
+ // Only other possibility is image items being dropped
+ // And allow this only if there is a Tag to be dropped
+ // on and also the Tag is not root or "Not Tagged" item.
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void TagFilterView::contentsDropEvent(TQDropEvent *e)
+{
+ FolderView::contentsDropEvent(e);
+
+ if (!acceptDrop(e))
+ return;
+
+ TQPoint vp = contentsToViewport(e->pos());
+ TagFilterViewItem *itemDrop = dynamic_cast<TagFilterViewItem*>(itemAt(vp));
+
+ if (!itemDrop || itemDrop->untagged())
+ return;
+
+ if(TagDrag::canDecode(e))
+ {
+ TQByteArray ba = e->encodedData("digikam/tag-id");
+ TQDataStream ds(ba, IO_ReadOnly);
+ int tagID;
+ ds >> tagID;
+
+ AlbumManager* man = AlbumManager::instance();
+ TAlbum* talbum = man->findTAlbum(tagID);
+
+ if(!talbum)
+ return;
+
+ if (talbum == itemDrop->album())
+ return;
+
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("Tag Filters"));
+ popMenu.insertItem(SmallIcon("goto"), i18n("&Move Here"), 10);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("C&ancel"), 20);
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+
+ if(id == 10)
+ {
+ TAlbum *newParentTag = 0;
+
+ if (!itemDrop)
+ {
+ // move dragItem to the root
+ newParentTag = AlbumManager::instance()->findTAlbum(0);
+ }
+ else
+ {
+ // move dragItem as child of dropItem
+ newParentTag = itemDrop->album();
+ }
+
+ TQString errMsg;
+ if (!AlbumManager::instance()->moveTAlbum(talbum, newParentTag, errMsg))
+ {
+ KMessageBox::error(this, errMsg);
+ }
+
+ if(itemDrop && !itemDrop->isOpen())
+ itemDrop->setOpen(true);
+ }
+
+ return;
+ }
+
+ if (ItemDrag::canDecode(e))
+ {
+ TAlbum *destAlbum = itemDrop->album();
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ if (!ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
+ return;
+
+ if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
+ return;
+
+ int id = 0;
+ char keys_return[32];
+ XQueryKeymap(x11Display(), keys_return);
+ int key_1 = XKeysymToKeycode(x11Display(), 0xFFE3);
+ int key_2 = XKeysymToKeycode(x11Display(), 0xFFE4);
+
+ // If a ctrl key is pressed while dropping the drag object,
+ // the tag is assigned to the images without showing a
+ // popup menu.
+ if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 10;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("Tag Filters"));
+ popMenu.insertItem(SmallIcon("tag"), i18n("Assign Tag '%1' to Items")
+ .arg(destAlbum->prettyURL()), 10) ;
+ popMenu.insertItem(i18n("Set as Tag Thumbnail"), 11);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("C&ancel"));
+
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ if (id == 10)
+ {
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image tags. Please wait..."));
+
+ AlbumLister::instance()->blockSignals(true);
+ AlbumManager::instance()->albumDB()->beginTransaction();
+ int i=0;
+ for (TQValueList<int>::const_iterator it = imageIDs.begin();
+ it != imageIDs.end(); ++it)
+ {
+ // create temporary ImageInfo object
+ ImageInfo info(*it);
+
+ MetadataHub hub;
+ hub.load(&info);
+ hub.setTag(destAlbum, true);
+ hub.write(&info, MetadataHub::PartialWrite);
+ hub.write(info.filePath(), MetadataHub::FullWriteIfChanged);
+
+ emit signalProgressValue((int)((i++/(float)imageIDs.count())*100.0));
+ kapp->processEvents();
+ }
+ AlbumLister::instance()->blockSignals(false);
+ AlbumManager::instance()->albumDB()->commitTransaction();
+
+ ImageAttributesWatch::instance()->imagesChanged(destAlbum->id());
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ }
+ else if(id == 11)
+ {
+ TQString errMsg;
+ AlbumManager::instance()->updateTAlbumIcon(destAlbum, TQString(),
+ imageIDs.first(), errMsg);
+ }
+ }
+}
+
+void TagFilterView::slotTagAdded(Album* album)
+{
+ if (!album || album->isRoot())
+ return;
+
+ TAlbum* tag = dynamic_cast<TAlbum*>(album);
+ if (!tag)
+ return;
+
+ if (tag->parent()->isRoot())
+ {
+ new TagFilterViewItem(this, tag);
+ }
+ else
+ {
+ TagFilterViewItem* parent = (TagFilterViewItem*)(tag->parent()->extraData(this));
+ if (!parent)
+ {
+ DWarning() << k_funcinfo << " Failed to find parent for Tag "
+ << tag->tagPath() << endl;
+ return;
+ }
+
+ new TagFilterViewItem(parent, tag);
+ }
+
+ setTagThumbnail(tag);
+}
+
+void TagFilterView::slotTagRenamed(Album* album)
+{
+ if (!album)
+ return;
+
+ TAlbum* tag = dynamic_cast<TAlbum*>(album);
+ if (!tag)
+ return;
+
+ TagFilterViewItem* item = (TagFilterViewItem*)(tag->extraData(this));
+ if (item)
+ item->refresh();
+}
+
+void TagFilterView::slotTagMoved(TAlbum* tag, TAlbum* newParent)
+{
+ if (!tag || !newParent)
+ return;
+
+ TagFilterViewItem* item = (TagFilterViewItem*)(tag->extraData(this));
+ if (!item)
+ return;
+
+ if (item->parent())
+ {
+ TQListViewItem* oldPItem = item->parent();
+ oldPItem->takeItem(item);
+
+ TagFilterViewItem* newPItem = (TagFilterViewItem*)(newParent->extraData(this));
+ if (newPItem)
+ newPItem->insertItem(item);
+ else
+ insertItem(item);
+ }
+ else
+ {
+ takeItem(item);
+
+ TagFilterViewItem* newPItem = (TagFilterViewItem*)(newParent->extraData(this));
+
+ if (newPItem)
+ newPItem->insertItem(item);
+ else
+ insertItem(item);
+ }
+}
+
+void TagFilterView::slotTagDeleted(Album* album)
+{
+ if (!album || album->isRoot())
+ return;
+
+ TAlbum* tag = dynamic_cast<TAlbum*>(album);
+ if (!tag)
+ return;
+
+ TagFilterViewItem* item = (TagFilterViewItem*)(album->extraData(this));
+ if (!item)
+ return;
+
+ // NOTE: see B.K.O #158558: unselected tag filter and all childrens before to delete it.
+ toggleChildTags(item, false);
+ item->setOn(false);
+
+ album->removeExtraData(this);
+ delete item;
+}
+
+void TagFilterView::setTagThumbnail(TAlbum *album)
+{
+ if(!album)
+ return;
+
+ TagFilterViewItem* item = (TagFilterViewItem*) album->extraData(this);
+
+ if(!item)
+ return;
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ TQPixmap icon;
+ if (!loader->getTagThumbnail(album, icon))
+ {
+ if (icon.isNull())
+ {
+ item->setPixmap(0, loader->getStandardTagIcon(album));
+ }
+ else
+ {
+ TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), icon);
+ item->setPixmap(0, blendedIcon);
+ }
+ }
+ else
+ {
+ // for the time being, set standard icon
+ item->setPixmap(0, loader->getStandardTagIcon(album));
+ }
+}
+
+void TagFilterView::slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail)
+{
+ if(!album || album->type() != Album::TAG)
+ return;
+
+ TagFilterViewItem* item = (TagFilterViewItem*)album->extraData(this);
+
+ if(!item)
+ return;
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), thumbnail);
+ item->setPixmap(0, blendedIcon);
+}
+
+void TagFilterView::slotThumbnailLost(Album *)
+{
+ // we already set the standard icon before loading
+}
+
+void TagFilterView::slotReloadThumbnails()
+{
+ AlbumList tList = AlbumManager::instance()->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum* tag = (TAlbum*)(*it);
+ setTagThumbnail(tag);
+ }
+}
+
+void TagFilterView::slotAlbumIconChanged(Album* album)
+{
+ if(!album || album->type() != Album::TAG)
+ return;
+
+ setTagThumbnail((TAlbum *)album);
+}
+
+void TagFilterView::slotClear()
+{
+ clear();
+
+ TagFilterViewItem* notTaggedItem = new TagFilterViewItem(this, 0, true);
+ notTaggedItem->setPixmap(0, AlbumThumbnailLoader::instance()->getStandardTagIcon());
+}
+
+void TagFilterView::slotTimeOut()
+{
+ TQValueList<int> filterTags;
+
+ bool showUnTagged = false;
+
+ TQListViewItemIterator it(this, TQListViewItemIterator::Checked);
+ while (it.current())
+ {
+ TagFilterViewItem* item = (TagFilterViewItem*)it.current();
+ if (item->album())
+ filterTags.append(item->album()->id());
+ else if (item->untagged())
+ showUnTagged = true;
+ ++it;
+ }
+
+ AlbumLister::instance()->setTagFilter(filterTags, d->matchingCond, showUnTagged);
+}
+
+void TagFilterView::slotContextMenu(TQListViewItem* it, const TQPoint&, int)
+{
+ TagFilterViewItem *item = dynamic_cast<TagFilterViewItem*>(it);
+ if (item && item->untagged())
+ return;
+
+ d->ABCMenu = new TQPopupMenu;
+
+ connect(d->ABCMenu, TQ_SIGNAL( aboutToShow() ),
+ this, TQ_SLOT( slotABCContextMenu() ));
+
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("Tag Filters"));
+ popmenu.insertItem(SmallIcon("tag-new"), i18n("New Tag..."), 10);
+ popmenu.insertItem(SmallIcon("tag-addressbook"), i18n("Create Tag From AddressBook"), d->ABCMenu);
+
+ if (item)
+ {
+ popmenu.insertItem(SmallIcon("tag-properties"), i18n("Edit Tag Properties..."), 11);
+ popmenu.insertItem(SmallIcon("tag-reset"), i18n("Reset Tag Icon"), 13);
+ popmenu.insertSeparator(-1);
+ popmenu.insertItem(SmallIcon("tag-delete"), i18n("Delete Tag"), 12);
+ }
+
+ popmenu.insertSeparator(-1);
+
+ TQPopupMenu selectTagsMenu;
+ selectTagsMenu.insertItem(i18n("All Tags"), 14);
+ if (item)
+ {
+ selectTagsMenu.insertSeparator(-1);
+ selectTagsMenu.insertItem(i18n("Children"), 17);
+ selectTagsMenu.insertItem(i18n("Parents"), 19);
+ }
+ popmenu.insertItem(i18n("Select"), &selectTagsMenu);
+
+ TQPopupMenu deselectTagsMenu;
+ deselectTagsMenu.insertItem(i18n("All Tags"), 15);
+ if (item)
+ {
+ deselectTagsMenu.insertSeparator(-1);
+ deselectTagsMenu.insertItem(i18n("Children"), 18);
+ deselectTagsMenu.insertItem(i18n("Parents"), 20);
+ }
+ popmenu.insertItem(i18n("Deselect"), &deselectTagsMenu);
+
+ popmenu.insertItem(i18n("Invert Selection"), 16);
+ popmenu.insertSeparator(-1);
+
+ TQPopupMenu toggleAutoMenu;
+ toggleAutoMenu.setCheckable(true);
+ toggleAutoMenu.insertItem(i18n("None"), 21);
+ toggleAutoMenu.insertSeparator(-1);
+ toggleAutoMenu.insertItem(i18n("Children"), 22);
+ toggleAutoMenu.insertItem(i18n("Parents"), 23);
+ toggleAutoMenu.insertItem(i18n("Both"), 24);
+ toggleAutoMenu.setItemChecked(21 + d->toggleAutoTags, true);
+ popmenu.insertItem(i18n("Toggle Auto"), &toggleAutoMenu);
+
+ TQPopupMenu matchingCongMenu;
+ matchingCongMenu.setCheckable(true);
+ matchingCongMenu.insertItem(i18n("Or Between Tags"), 25);
+ matchingCongMenu.insertItem(i18n("And Between Tags"), 26);
+ matchingCongMenu.setItemChecked((d->matchingCond == AlbumLister::OrCondition) ? 25 : 26, true);
+ popmenu.insertItem(i18n("Matching Condition"), &matchingCongMenu);
+
+ ToggleAutoTags oldAutoTags = d->toggleAutoTags;
+
+ int choice = popmenu.exec((TQCursor::pos()));
+ switch( choice )
+ {
+ case 10: // New Tag.
+ {
+ tagNew(item);
+ break;
+ }
+ case 11: // Edit Tag Properties.
+ {
+ tagEdit(item);
+ break;
+ }
+ case 12: // Delete Tag.
+ {
+ tagDelete(item);
+ break;
+ }
+ case 13: // Reset Tag Icon.
+ {
+ TQString errMsg;
+ AlbumManager::instance()->updateTAlbumIcon(item->album(), TQString("tag"), 0, errMsg);
+ break;
+ }
+ case 14: // Select All Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ TQListViewItemIterator it(this, TQListViewItemIterator::NotChecked);
+ while (it.current())
+ {
+ TagFilterViewItem* item = (TagFilterViewItem*)it.current();
+
+ // Ignore "Not Tagged" tag filter.
+ if (!item->untagged())
+ item->setOn(true);
+
+ ++it;
+ }
+ triggerChange();
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 15: // Deselect All Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ TQListViewItemIterator it(this, TQListViewItemIterator::Checked);
+ while (it.current())
+ {
+ TagFilterViewItem* item = (TagFilterViewItem*)it.current();
+
+ // Ignore "Not Tagged" tag filter.
+ if (!item->untagged())
+ item->setOn(false);
+
+ ++it;
+ }
+ triggerChange();
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 16: // Invert All Tags Selection.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ TQListViewItemIterator it(this);
+ while (it.current())
+ {
+ TagFilterViewItem* item = (TagFilterViewItem*)it.current();
+
+ // Ignore "Not Tagged" tag filter.
+ if (!item->untagged())
+ item->setOn(!item->isOn());
+
+ ++it;
+ }
+ triggerChange();
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 17: // Select Child Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(item, true);
+ TagFilterViewItem *tItem = (TagFilterViewItem*)item->album()->extraData(this);
+ tItem->setOn(true);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 18: // Deselect Child Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(item, false);
+ TagFilterViewItem *tItem = (TagFilterViewItem*)item->album()->extraData(this);
+ tItem->setOn(false);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 19: // Select Parent Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleParentTags(item, true);
+ TagFilterViewItem *tItem = (TagFilterViewItem*)item->album()->extraData(this);
+ tItem->setOn(true);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 20: // Deselect Parent Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleParentTags(item, false);
+ TagFilterViewItem *tItem = (TagFilterViewItem*)item->album()->extraData(this);
+ tItem->setOn(false);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 21: // No toggle auto tags.
+ {
+ d->toggleAutoTags = NoToggleAuto;
+ break;
+ }
+ case 22: // Toggle auto Children tags.
+ {
+ d->toggleAutoTags = Children;
+ break;
+ }
+ case 23: // Toggle auto Parents tags.
+ {
+ d->toggleAutoTags = Parents;
+ break;
+ }
+ case 24: // Toggle auto Children and Parents tags.
+ {
+ d->toggleAutoTags = ChildrenAndParents;
+ break;
+ }
+ case 25: // Or Between Tags.
+ {
+ d->matchingCond = AlbumLister::OrCondition;
+ triggerChange();
+ break;
+ }
+ case 26: // And Between Tags.
+ {
+ d->matchingCond = AlbumLister::AndCondition;
+ triggerChange();
+ break;
+ }
+ default:
+ break;
+ }
+
+ if ( choice > 100 )
+ {
+ tagNew(item, d->ABCMenu->text( choice ), "tag-people" );
+ }
+
+ delete d->ABCMenu;
+ d->ABCMenu = 0;
+}
+
+void TagFilterView::slotABCContextMenu()
+{
+ d->ABCMenu->clear();
+
+ int counter = 100;
+ TDEABC::AddressBook* ab = TDEABC::StdAddressBook::self();
+ TQStringList names;
+ for ( TDEABC::AddressBook::Iterator it = ab->begin(); it != ab->end(); ++it )
+ {
+ names.push_back(it->formattedName());
+ }
+
+ qHeapSort(names);
+
+ for ( TQStringList::Iterator it = names.begin(); it != names.end(); ++it )
+ {
+ TQString name = *it;
+ if ( !name.isNull() )
+ d->ABCMenu->insertItem( name, ++counter );
+ }
+
+ if (counter == 100)
+ {
+ d->ABCMenu->insertItem( i18n("No AddressBook entries found"), ++counter );
+ d->ABCMenu->setItemEnabled( counter, false );
+ }
+}
+
+void TagFilterView::tagNew(TagFilterViewItem* item, const TQString& _title, const TQString& _icon)
+{
+ TAlbum *parent;
+ TQString title = _title;
+ TQString icon = _icon;
+ AlbumManager *man = AlbumManager::instance();
+
+ if (!item)
+ parent = man->findTAlbum(0);
+ else
+ parent = item->album();
+
+ if (title.isNull())
+ {
+ if (!TagEditDlg::tagCreate(kapp->activeWindow(), parent, title, icon))
+ return;
+ }
+
+ TQMap<TQString, TQString> errMap;
+ AlbumList tList = TagEditDlg::createTAlbum(parent, title, icon, errMap);
+ TagEditDlg::showtagsListCreationError(kapp->activeWindow(), errMap);
+
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TagFilterViewItem* item = (TagFilterViewItem*)(*it)->extraData(this);
+ if (item)
+ {
+ clearSelection();
+ setSelected(item, true);
+ setCurrentItem(item);
+ ensureItemVisible(item);
+ }
+ }
+}
+
+void TagFilterView::tagEdit(TagFilterViewItem* item)
+{
+ if (!item)
+ return;
+
+ TAlbum *tag = item->album();
+ if (!tag)
+ return;
+
+ TQString title, icon;
+ if (!TagEditDlg::tagEdit(kapp->activeWindow(), tag, title, icon))
+ {
+ return;
+ }
+
+ AlbumManager* man = AlbumManager::instance();
+
+ if (tag->title() != title)
+ {
+ TQString errMsg;
+ if(!man->renameTAlbum(tag, title, errMsg))
+ KMessageBox::error(0, errMsg);
+ else
+ item->refresh();
+ }
+
+ if (tag->icon() != icon)
+ {
+ TQString errMsg;
+ if (!man->updateTAlbumIcon(tag, icon, 0, errMsg))
+ KMessageBox::error(0, errMsg);
+ else
+ setTagThumbnail(tag);
+ }
+}
+
+void TagFilterView::tagDelete(TagFilterViewItem* item)
+{
+ if (!item)
+ return;
+
+ TAlbum *tag = item->album();
+ if (!tag || tag->isRoot())
+ return;
+
+ // find number of subtags
+ int children = 0;
+ AlbumIterator iter(tag);
+ while(iter.current())
+ {
+ children++;
+ ++iter;
+ }
+
+ AlbumManager* man = AlbumManager::instance();
+
+ if (children)
+ {
+ int result = KMessageBox::warningContinueCancel(this,
+ i18n("Tag '%1' has one subtag. "
+ "Deleting this will also delete "
+ "the subtag. "
+ "Do you want to continue?",
+ "Tag '%1' has %n subtags. "
+ "Deleting this will also delete "
+ "the subtags. "
+ "Do you want to continue?",
+ children).arg(tag->title()));
+
+ if(result != KMessageBox::Continue)
+ return;
+ }
+
+ TQString message;
+ LLongList assignedItems = man->albumDB()->getItemIDsInTag(tag->id());
+ if (!assignedItems.isEmpty())
+ {
+ message = i18n("Tag '%1' is assigned to one item. "
+ "Do you want to continue?",
+ "Tag '%1' is assigned to %n items. "
+ "Do you want to continue?",
+ assignedItems.count()).arg(tag->title());
+ }
+ else
+ {
+ message = i18n("Delete '%1' tag?").arg(tag->title());
+ }
+
+ int result = KMessageBox::warningContinueCancel(0, message,
+ i18n("Delete Tag"),
+ KGuiItem(i18n("Delete"),
+ "edit-delete"));
+
+ if (result == KMessageBox::Continue)
+ {
+ TQString errMsg;
+ if (!man->deleteTAlbum(tag, errMsg))
+ KMessageBox::error(0, errMsg);
+ }
+}
+
+void TagFilterView::toggleChildTags(TagFilterViewItem* tItem, bool b)
+{
+ if (!tItem)
+ return;
+
+ TAlbum *album = tItem->album();
+ if (!album)
+ return;
+
+ AlbumIterator it(album);
+ while ( it.current() )
+ {
+ TAlbum *ta = (TAlbum*)it.current();
+ TagFilterViewItem *item = (TagFilterViewItem*)ta->extraData(this);
+ if (item)
+ {
+ if (item->isVisible())
+ item->setOn(b);
+ }
+ ++it;
+ }
+}
+
+void TagFilterView::toggleParentTags(TagFilterViewItem* tItem, bool b)
+{
+ if (!tItem)
+ return;
+
+ TAlbum *album = tItem->album();
+ if (!album)
+ return;
+
+ TQListViewItemIterator it(this);
+ while (it.current())
+ {
+ TagFilterViewItem* item = dynamic_cast<TagFilterViewItem*>(it.current());
+ if (item->isVisible())
+ {
+ Album *a = dynamic_cast<Album*>(item->album());
+ if (a)
+ {
+ if (a == album->parent())
+ {
+ item->setOn(b);
+ toggleParentTags(item, b);
+ }
+ }
+ }
+ ++it;
+ }
+}
+
+void TagFilterView::refresh()
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ TagFilterViewItem* item = dynamic_cast<TagFilterViewItem*>(*it);
+ if (item)
+ item->refresh();
+ ++it;
+ }
+}
+
+void TagFilterView::slotRefresh(const TQMap<int, int>& tagsStatMap)
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ TagFilterViewItem* item = dynamic_cast<TagFilterViewItem*>(*it);
+ if (item)
+ {
+ if (item->album())
+ {
+ int id = item->id();
+ TQMap<int, int>::const_iterator it2 = tagsStatMap.find(id);
+ if ( it2 != tagsStatMap.end() )
+ item->setCount(it2.data());
+ }
+ }
+ ++it;
+ }
+
+ refresh();
+}
+
+void TagFilterView::slotResetTagFilters()
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ TagFilterViewItem* item = dynamic_cast<TagFilterViewItem*>(*it);
+ if (item && item->isOn())
+ item->setOn(false);
+ ++it;
+ }
+}
+
+void TagFilterView::loadViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ int selectedItem = config->readNumEntry("LastSelectedItem", 0);
+
+ TQValueList<int> openFolders;
+ if(config->hasKey("OpenFolders"))
+ {
+ openFolders = config->readIntListEntry("OpenFolders");
+ }
+
+ TagFilterViewItem *item = 0;
+ TagFilterViewItem *foundItem = 0;
+ TQListViewItemIterator it(this->lastItem());
+
+ for( ; it.current(); --it)
+ {
+ item = dynamic_cast<TagFilterViewItem*>(it.current());
+ if(!item)
+ continue;
+
+ // Start the album root always open
+ if(openFolders.contains(item->id()) || item->id() == 0)
+ setOpen(item, true);
+ else
+ setOpen(item, false);
+
+ if(item->id() == selectedItem)
+ {
+ // Save the found selected item so that it can be made visible.
+ foundItem = item;
+ }
+ }
+
+ // Important note: this cannot be done inside the previous loop
+ // because opening folders prevents the visibility.
+ // Fixes bug #144815.
+ // (Looks a bit like a bug in TQt to me ...)
+ if (foundItem)
+ {
+ setSelected(foundItem, true);
+ ensureItemVisible(foundItem);
+ }
+}
+
+void TagFilterView::saveViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ TagFilterViewItem *item = dynamic_cast<TagFilterViewItem*>(selectedItem());
+ if(item)
+ config->writeEntry("LastSelectedItem", item->id());
+ else
+ config->writeEntry("LastSelectedItem", 0);
+
+ TQValueList<int> openFolders;
+ TQListViewItemIterator it(this);
+ for( ; it.current(); ++it)
+ {
+ item = dynamic_cast<TagFilterViewItem*>(it.current());
+ if(item && isOpen(item))
+ openFolders.push_back(item->id());
+ }
+ config->writeEntry("OpenFolders", openFolders);
+}
+
+} // namespace Digikam
diff --git a/src/digikam/tagfilterview.h b/src/digikam/tagfilterview.h
new file mode 100644
index 00000000..0cd977e1
--- /dev/null
+++ b/src/digikam/tagfilterview.h
@@ -0,0 +1,118 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-05
+ * Description : tags filter view
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TAGFILTERVIEW_H
+#define TAGFILTERVIEW_H
+
+// Local includes.
+
+#include "folderview.h"
+
+namespace Digikam
+{
+
+class Album;
+class TagFilterViewItem;
+class TagFilterViewPrivate;
+
+class TagFilterView : public FolderView
+{
+ TQ_OBJECT
+
+public:
+
+ enum ToggleAutoTags
+ {
+ NoToggleAuto = 0,
+ Children,
+ Parents,
+ ChildrenAndParents
+ };
+
+public:
+
+ TagFilterView(TQWidget* parent);
+ ~TagFilterView();
+
+ void stateChanged(TagFilterViewItem*);
+ void refresh();
+
+signals:
+
+ void signalProgressBarMode(int, const TQString&);
+ void signalProgressValue(int);
+ void signalTextTagFilterMatch(bool);
+
+public slots:
+
+ void slotTextTagFilterChanged(const TQString&);
+
+ /** Reset all active tag filters */
+ void slotResetTagFilters();
+
+protected:
+
+ bool acceptDrop(const TQDropEvent *e) const;
+ void contentsDropEvent(TQDropEvent *e);
+ TQDragObject* dragObject();
+
+private slots:
+
+ void slotTagAdded(Album* album);
+ void slotTagMoved(TAlbum* tag, TAlbum* newParent);
+ void slotTagRenamed(Album* album);
+ void slotTagDeleted(Album* album);
+ void slotClear();
+ void slotAlbumIconChanged(Album* album);
+ void slotTimeOut();
+ void slotContextMenu(TQListViewItem*, const TQPoint&, int);
+ void slotABCContextMenu();
+ void slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail);
+ void slotThumbnailLost(Album *album);
+ void slotReloadThumbnails();
+ void slotRefresh(const TQMap<int, int>&);
+
+private:
+
+ void triggerChange();
+ void tagNew(TagFilterViewItem* item, const TQString& _title=TQString(),
+ const TQString& _icon=TQString());
+ void tagEdit(TagFilterViewItem* item);
+ void tagDelete(TagFilterViewItem* item);
+ void setTagThumbnail(TAlbum *album);
+ void toggleChildTags(TagFilterViewItem* tItem, bool b);
+ void toggleParentTags(TagFilterViewItem* tItem, bool b);
+
+ void loadViewState();
+ void saveViewState();
+
+private:
+
+ TagFilterViewPrivate *d;
+};
+
+} // namespace Digikam
+
+#endif /* TAGFILTERVIEW_H */
diff --git a/src/digikam/tagfolderview.cpp b/src/digikam/tagfolderview.cpp
new file mode 100644
index 00000000..c5d54331
--- /dev/null
+++ b/src/digikam/tagfolderview.cpp
@@ -0,0 +1,1041 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-22
+ * Descritpion : tags folder view.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqcursor.h>
+#include <tqlistview.h>
+
+// KDE includes.
+
+#include <tdepopupmenu.h>
+#include <tdelocale.h>
+#include <tdeabc/stdaddressbook.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "albumlister.h"
+#include "syncjob.h"
+#include "tageditdlg.h"
+#include "dragobjects.h"
+#include "folderitem.h"
+#include "dio.h"
+#include "imageattributeswatch.h"
+#include "imageinfo.h"
+#include "metadatahub.h"
+#include "albumthumbnailloader.h"
+#include "statusprogressbar.h"
+#include "tagfolderview.h"
+#include "tagfolderview.moc"
+
+// X11 includes.
+
+extern "C"
+{
+#include <X11/Xlib.h>
+}
+
+namespace Digikam
+{
+
+class TagFolderViewItem : public FolderItem
+{
+
+public:
+
+ TagFolderViewItem(TQListView *parent, TAlbum *album);
+ TagFolderViewItem(TQListViewItem *parent, TAlbum *album);
+
+ TAlbum* album() const;
+ int id() const;
+ void refresh();
+ void setOpen(bool o);
+ void setCount(int count);
+ int count();
+
+private:
+
+ int m_count;
+
+ TAlbum *m_album;
+};
+
+TagFolderViewItem::TagFolderViewItem(TQListView *parent, TAlbum *album)
+ : FolderItem(parent, album->title())
+{
+ setDragEnabled(true);
+ m_album = album;
+ m_count = 0;
+}
+
+TagFolderViewItem::TagFolderViewItem(TQListViewItem *parent, TAlbum *album)
+ : FolderItem(parent, album->title())
+{
+ setDragEnabled(true);
+ m_album = album;
+ m_count = 0;
+}
+
+void TagFolderViewItem::refresh()
+{
+ if (!m_album) return;
+
+ if (AlbumSettings::instance()->getShowFolderTreeViewItemsCount() &&
+ dynamic_cast<TagFolderViewItem*>(parent()))
+ {
+ if (isOpen())
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(m_count));
+ else
+ {
+ int countRecursive = m_count;
+ AlbumIterator it(m_album);
+ while ( it.current() )
+ {
+ TagFolderViewItem *item = (TagFolderViewItem*)it.current()->extraData(listView());
+ if (item)
+ countRecursive += item->count();
+ ++it;
+ }
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(countRecursive));
+ }
+ }
+ else
+ {
+ setText(0, m_album->title());
+ }
+}
+
+void TagFolderViewItem::setOpen(bool o)
+{
+ TQListViewItem::setOpen(o);
+ refresh();
+}
+
+TAlbum* TagFolderViewItem::album() const
+{
+ return m_album;
+}
+
+int TagFolderViewItem::id() const
+{
+ return m_album ? m_album->id() : 0;
+}
+
+void TagFolderViewItem::setCount(int count)
+{
+ m_count = count;
+ refresh();
+}
+
+int TagFolderViewItem::count()
+{
+ return m_count;
+}
+
+//-----------------------------------------------------------------------------
+
+class TagFolderViewPriv
+{
+
+public:
+
+ TagFolderViewPriv()
+ {
+ ABCMenu = 0;
+ albumMan = 0;
+ }
+
+ TQPopupMenu *ABCMenu;
+
+ AlbumManager *albumMan;
+};
+
+TagFolderView::TagFolderView(TQWidget *parent)
+ : FolderView(parent, "TagFolderView")
+{
+ d = new TagFolderViewPriv();
+ d->albumMan = AlbumManager::instance();
+
+ addColumn(i18n("My Tags"));
+ setResizeMode(TQListView::LastColumn);
+ setRootIsDecorated(false);
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+
+ // ------------------------------------------------------------------------
+
+ connect(d->albumMan, TQ_SIGNAL(signalTAlbumsDirty(const TQMap<int, int>&)),
+ this, TQ_SLOT(slotRefresh(const TQMap<int, int>&)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumRenamed(Album*)),
+ this, TQ_SLOT(slotAlbumRenamed(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(slotAlbumsCleared()));
+
+ connect(d->albumMan, TQ_SIGNAL(signalAlbumIconChanged(Album*)),
+ this, TQ_SLOT(slotAlbumIconChanged(Album*)));
+
+ connect(d->albumMan, TQ_SIGNAL(signalTAlbumMoved(TAlbum*, TAlbum*)),
+ this, TQ_SLOT(slotAlbumMoved(TAlbum*, TAlbum*)));
+
+ // ------------------------------------------------------------------------
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+
+ connect(loader, TQ_SIGNAL(signalThumbnail(Album *, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnailFromIcon(Album *, const TQPixmap&)));
+
+ connect(loader, TQ_SIGNAL(signalFailed(Album *)),
+ this, TQ_SLOT(slotThumbnailLost(Album *)));
+
+ connect(loader, TQ_SIGNAL(signalReloadThumbnails()),
+ this, TQ_SLOT(slotReloadThumbnails()));
+
+ connect(this, TQ_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
+ TQ_SLOT(slotContextMenu(TQListViewItem*, const TQPoint&, int)));
+
+ connect(this, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+}
+
+TagFolderView::~TagFolderView()
+{
+ saveViewState();
+ delete d;
+}
+
+void TagFolderView::slotTextTagFilterChanged(const TQString& filter)
+{
+ if (filter.isEmpty())
+ {
+ collapseView();
+ return;
+ }
+
+ TQString search = filter.lower();
+
+ bool atleastOneMatch = false;
+
+ AlbumList tList = d->albumMan->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum* talbum = (TAlbum*)(*it);
+
+ // don't touch the root Album
+ if (talbum->isRoot())
+ continue;
+
+ bool match = talbum->title().lower().contains(search);
+ bool doesExpand = false;
+ if (!match)
+ {
+ // check if any of the parents match the search
+ Album* parent = talbum->parent();
+ while (parent && !parent->isRoot())
+ {
+ if (parent->title().lower().contains(search))
+ {
+ match = true;
+ break;
+ }
+
+ parent = parent->parent();
+ }
+ }
+
+ if (!match)
+ {
+ // check if any of the children match the search
+ AlbumIterator it(talbum);
+ while (it.current())
+ {
+ if ((*it)->title().lower().contains(search))
+ {
+ match = true;
+ doesExpand = true;
+ break;
+ }
+ ++it;
+ }
+ }
+
+ TagFolderViewItem* viewItem = (TagFolderViewItem*) talbum->extraData(this);
+
+ if (match)
+ {
+ atleastOneMatch = true;
+
+ if (viewItem)
+ {
+ viewItem->setVisible(true);
+ viewItem->setOpen(doesExpand);
+ }
+ }
+ else
+ {
+ if (viewItem)
+ {
+ viewItem->setVisible(false);
+ viewItem->setOpen(false);
+ }
+ }
+ }
+
+ emit signalTextTagFilterMatch(atleastOneMatch);
+}
+
+void TagFolderView::slotAlbumAdded(Album *album)
+{
+ if(!album)
+ return;
+
+ TAlbum *tag = dynamic_cast<TAlbum*>(album);
+ if(!tag)
+ return;
+
+ TagFolderViewItem *item;
+
+ if(tag->isRoot())
+ {
+ item = new TagFolderViewItem(this, tag);
+ tag->setExtraData(this, item);
+ // Toplevel tags are all children of root, and should always be visible - set root to open
+ item->setOpen(true);
+ }
+ else
+ {
+ TagFolderViewItem *parent =
+ (TagFolderViewItem*)tag->parent()->extraData(this);
+
+ if (!parent)
+ {
+ DWarning() << k_funcinfo << " Failed to find parent for Tag "
+ << tag->title() << endl;
+ return;
+ }
+
+ item = new TagFolderViewItem(parent, tag);
+ tag->setExtraData(this, item);
+ }
+
+ setTagThumbnail(tag);
+}
+
+void TagFolderView::slotAlbumDeleted(Album *album)
+{
+ if(!album)
+ return;
+
+ TAlbum *tag = dynamic_cast<TAlbum*>(album);
+ if(!tag)
+ return;
+
+ TagFolderViewItem *item = (TagFolderViewItem*)album->extraData(this);
+ if(item)
+ {
+ TagFolderViewItem *itemParent = dynamic_cast<TagFolderViewItem*>(item->parent());
+
+ if(itemParent)
+ itemParent->takeItem(item);
+ else
+ takeItem(item);
+
+ delete item;
+ }
+}
+
+void TagFolderView::slotAlbumsCleared()
+{
+ clear();
+}
+
+void TagFolderView::slotAlbumMoved(TAlbum* tag, TAlbum* newParent)
+{
+ if (!tag || !newParent)
+ return;
+
+ TagFolderViewItem* item = (TagFolderViewItem*)tag->extraData(this);
+ if (!item)
+ return;
+
+ if (item->parent())
+ {
+ TQListViewItem* oldPItem = item->parent();
+ oldPItem->takeItem(item);
+ }
+ else
+ {
+ takeItem(item);
+ }
+
+ TagFolderViewItem* newPItem = (TagFolderViewItem*)newParent->extraData(this);
+ if (newPItem)
+ newPItem->insertItem(item);
+ else
+ insertItem(item);
+}
+
+void TagFolderView::slotAlbumRenamed(Album* album)
+{
+ if (!album)
+ return;
+
+ TAlbum* tag = dynamic_cast<TAlbum*>(album);
+ if (!tag)
+ return;
+
+ TagFolderViewItem* item = (TagFolderViewItem*)(tag->extraData(this));
+ if (item)
+ item->refresh();
+}
+
+void TagFolderView::setTagThumbnail(TAlbum *album)
+{
+ if(!album)
+ return;
+
+ TagFolderViewItem* item = (TagFolderViewItem*) album->extraData(this);
+
+ if(!item)
+ return;
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ TQPixmap icon;
+ if (!loader->getTagThumbnail(album, icon))
+ {
+ if (icon.isNull())
+ {
+ item->setPixmap(0, loader->getStandardTagIcon(album));
+ }
+ else
+ {
+ TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), icon);
+ item->setPixmap(0, blendedIcon);
+ }
+ }
+ else
+ {
+ // for the time being, set standard icon
+ item->setPixmap(0, loader->getStandardTagIcon(album));
+ }
+}
+
+void TagFolderView::slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail)
+{
+ if(!album || album->type() != Album::TAG)
+ return;
+
+ TagFolderViewItem* item = (TagFolderViewItem*)album->extraData(this);
+
+ if(!item)
+ return;
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), thumbnail);
+ item->setPixmap(0, blendedIcon);
+}
+
+void TagFolderView::slotThumbnailLost(Album *)
+{
+ // we already set the standard icon before loading
+}
+
+void TagFolderView::slotReloadThumbnails()
+{
+ AlbumList tList = d->albumMan->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum* tag = (TAlbum*)(*it);
+ setTagThumbnail(tag);
+ }
+}
+
+void TagFolderView::slotAlbumIconChanged(Album* album)
+{
+ if(!album || album->type() != Album::TAG)
+ return;
+
+ setTagThumbnail((TAlbum *)album);
+}
+
+void TagFolderView::slotSelectionChanged()
+{
+ if (!active())
+ return;
+
+ TQListViewItem* selItem = 0;
+ TQListViewItemIterator it(this);
+ while (it.current())
+ {
+ if (it.current()->isSelected())
+ {
+ selItem = it.current();
+ break;
+ }
+ ++it;
+ }
+
+ if (!selItem)
+ {
+ d->albumMan->setCurrentAlbum(0);
+ return;
+ }
+
+ TagFolderViewItem *tagitem = dynamic_cast<TagFolderViewItem*>(selItem);
+ if(!tagitem)
+ {
+ d->albumMan->setCurrentAlbum(0);
+ return;
+ }
+
+ d->albumMan->setCurrentAlbum(tagitem->album());
+}
+
+void TagFolderView::slotContextMenu(TQListViewItem *item, const TQPoint &, int)
+{
+ d->ABCMenu = new TQPopupMenu;
+
+ connect( d->ABCMenu, TQ_SIGNAL( aboutToShow() ),
+ this, TQ_SLOT( slotABCContextMenu() ) );
+
+ TagFolderViewItem *tag = dynamic_cast<TagFolderViewItem*>(item);
+
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("My Tags"));
+ popmenu.insertItem(SmallIcon("tag-new"), i18n("New Tag..."), 10);
+ popmenu.insertItem(SmallIcon("tag-addressbook"), i18n("Create Tag From AddressBook"), d->ABCMenu);
+
+ if(tag && tag->parent())
+ {
+ popmenu.insertItem(SmallIcon("tag-properties"), i18n("Edit Tag Properties..."), 11);
+ popmenu.insertItem(SmallIcon("tag-reset"), i18n("Reset Tag Icon"), 13);
+ popmenu.insertSeparator(-1);
+ popmenu.insertItem(SmallIcon("tag-delete"), i18n("Delete Tag"), 12);
+ }
+
+ int choice = popmenu.exec((TQCursor::pos()));
+ switch( choice )
+ {
+ case 10:
+ {
+ tagNew(tag);
+ break;
+ }
+ case 11:
+ {
+ tagEdit(tag);
+ break;
+ }
+ case 12:
+ {
+ tagDelete(tag);
+ break;
+ }
+ case 13:
+ {
+ TQString errMsg;
+ d->albumMan->updateTAlbumIcon(tag->album(), TQString("tag"), 0, errMsg);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if ( choice > 100 )
+ {
+ tagNew( tag, d->ABCMenu->text( choice ), "tag-people" );
+ }
+
+ delete d->ABCMenu;
+ d->ABCMenu = 0;
+}
+
+void TagFolderView::slotABCContextMenu()
+{
+ d->ABCMenu->clear();
+
+ int counter = 100;
+ TDEABC::AddressBook* ab = TDEABC::StdAddressBook::self();
+ TQStringList names;
+ for ( TDEABC::AddressBook::Iterator it = ab->begin(); it != ab->end(); ++it )
+ {
+ names.push_back(it->formattedName());
+ }
+
+ qHeapSort(names);
+
+ for ( TQStringList::Iterator it = names.begin(); it != names.end(); ++it )
+ {
+ TQString name = *it;
+ if ( !name.isNull() )
+ d->ABCMenu->insertItem( name, ++counter );
+ }
+
+ if (counter == 100)
+ {
+ d->ABCMenu->insertItem( i18n("No AddressBook entries found"), ++counter );
+ d->ABCMenu->setItemEnabled( counter, false );
+ }
+}
+
+void TagFolderView::tagNew()
+{
+ TagFolderViewItem *item = dynamic_cast<TagFolderViewItem*>(selectedItem());
+ tagNew(item);
+}
+
+void TagFolderView::tagNew( TagFolderViewItem *item, const TQString& _title, const TQString& _icon )
+{
+ TQString title = _title;
+ TQString icon = _icon;
+ TAlbum *parent;
+
+ if(!item)
+ parent = d->albumMan->findTAlbum(0);
+ else
+ parent = item->album();
+
+ if (title.isNull())
+ {
+ if(!TagEditDlg::tagCreate(kapp->activeWindow(), parent, title, icon))
+ return;
+ }
+
+ TQMap<TQString, TQString> errMap;
+ AlbumList tList = TagEditDlg::createTAlbum(parent, title, icon, errMap);
+ TagEditDlg::showtagsListCreationError(kapp->activeWindow(), errMap);
+
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TagFolderViewItem* item = (TagFolderViewItem*)(*it)->extraData(this);
+ if (item)
+ ensureItemVisible(item);
+ }
+}
+
+void TagFolderView::tagEdit()
+{
+ TagFolderViewItem *item = dynamic_cast<TagFolderViewItem*>(selectedItem());
+ tagEdit(item);
+}
+
+void TagFolderView::tagEdit(TagFolderViewItem *item)
+{
+ if(!item)
+ return;
+
+ TAlbum *tag = item->album();
+ if(!tag)
+ return;
+
+ TQString title, icon;
+ if(!TagEditDlg::tagEdit(kapp->activeWindow(), tag, title, icon))
+ return;
+
+ if(tag->title() != title)
+ {
+ TQString errMsg;
+ if(!d->albumMan->renameTAlbum(tag, title, errMsg))
+ KMessageBox::error(0, errMsg);
+ else
+ item->refresh();
+ }
+
+ if(tag->icon() != icon)
+ {
+ TQString errMsg;
+ if (!d->albumMan->updateTAlbumIcon(tag, icon, 0, errMsg))
+ KMessageBox::error(0, errMsg);
+ else
+ setTagThumbnail(tag);
+ }
+}
+
+void TagFolderView::tagDelete()
+{
+ TagFolderViewItem *item = dynamic_cast<TagFolderViewItem*>(selectedItem());
+ tagDelete(item);
+}
+
+void TagFolderView::tagDelete(TagFolderViewItem *item)
+{
+ if(!item)
+ return;
+
+ TAlbum *tag = item->album();
+ if (!tag || tag->isRoot())
+ return;
+
+ // find number of subtags
+ int children = 0;
+ AlbumIterator iter(tag);
+ while(iter.current())
+ {
+ children++;
+ ++iter;
+ }
+
+ if(children)
+ {
+ int result = KMessageBox::warningContinueCancel(this,
+ i18n("Tag '%1' has one subtag. "
+ "Deleting this will also delete "
+ "the subtag. "
+ "Do you want to continue?",
+ "Tag '%1' has %n subtags. "
+ "Deleting this will also delete "
+ "the subtags. "
+ "Do you want to continue?",
+ children).arg(tag->title()));
+
+ if(result != KMessageBox::Continue)
+ return;
+ }
+
+ TQString message;
+ LLongList assignedItems = d->albumMan->albumDB()->getItemIDsInTag(tag->id());
+ if (!assignedItems.isEmpty())
+ {
+ message = i18n("Tag '%1' is assigned to one item. "
+ "Do you want to continue?",
+ "Tag '%1' is assigned to %n items. "
+ "Do you want to continue?",
+ assignedItems.count()).arg(tag->title());
+ }
+ else
+ {
+ message = i18n("Delete '%1' tag?").arg(tag->title());
+ }
+
+ int result = KMessageBox::warningContinueCancel(0, message,
+ i18n("Delete Tag"),
+ KGuiItem(i18n("Delete"),
+ "edit-delete"));
+
+ if(result == KMessageBox::Continue)
+ {
+ TQString errMsg;
+ if (!d->albumMan->deleteTAlbum(tag, errMsg))
+ KMessageBox::error(0, errMsg);
+ }
+}
+
+TQDragObject* TagFolderView::dragObject()
+{
+ TagFolderViewItem *item = dynamic_cast<TagFolderViewItem*>(dragItem());
+ if(!item)
+ return 0;
+
+ if(!item->parent())
+ return 0;
+
+ TagDrag *t = new TagDrag(item->album()->id(), this);
+ t->setPixmap(*item->pixmap(0));
+
+ return t;
+}
+
+bool TagFolderView::acceptDrop(const TQDropEvent *e) const
+{
+ TQPoint vp = contentsToViewport(e->pos());
+ TagFolderViewItem *itemDrop = dynamic_cast<TagFolderViewItem*>(itemAt(vp));
+ TagFolderViewItem *itemDrag = dynamic_cast<TagFolderViewItem*>(dragItem());
+
+ if(TagDrag::canDecode(e) || TagListDrag::canDecode(e))
+ {
+ // Allow dragging at the root, to move the tag to the root
+ if(!itemDrop)
+ return true;
+
+ // Dragging an item on itself makes no sense
+ if(itemDrag == itemDrop)
+ return false;
+
+ // Dragging a parent on its child makes no sense
+ if(itemDrag && itemDrag->album()->isAncestorOf(itemDrop->album()))
+ return false;
+
+ return true;
+ }
+
+ if (ItemDrag::canDecode(e) && itemDrop && itemDrop->parent())
+ {
+ // Only other possibility is image items being dropped
+ // And allow this only if there is a Tag to be dropped
+ // on and also the Tag is not root.
+ return true;
+ }
+
+ return false;
+}
+
+void TagFolderView::contentsDropEvent(TQDropEvent *e)
+{
+ FolderView::contentsDropEvent(e);
+
+ if(!acceptDrop(e))
+ return;
+
+ TQPoint vp = contentsToViewport(e->pos());
+ TagFolderViewItem *itemDrop = dynamic_cast<TagFolderViewItem*>(itemAt(vp));
+
+ if (!itemDrop)
+ return;
+
+ if(TagDrag::canDecode(e))
+ {
+ TQByteArray ba = e->encodedData("digikam/tag-id");
+ TQDataStream ds(ba, IO_ReadOnly);
+ int tagID;
+ ds >> tagID;
+
+ TAlbum* talbum = d->albumMan->findTAlbum(tagID);
+
+ if(!talbum)
+ return;
+
+ if (talbum == itemDrop->album())
+ return;
+
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Tags"));
+ popMenu.insertItem(SmallIcon("goto"), i18n("&Move Here"), 10);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("C&ancel"), 20);
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+
+ if(id == 10)
+ {
+ TAlbum *newParentTag = 0;
+
+ if (!itemDrop)
+ {
+ // move dragItem to the root
+ newParentTag = d->albumMan->findTAlbum(0);
+ }
+ else
+ {
+ // move dragItem as child of dropItem
+ newParentTag = itemDrop->album();
+ }
+
+ TQString errMsg;
+ if (!d->albumMan->moveTAlbum(talbum, newParentTag, errMsg))
+ {
+ KMessageBox::error(this, errMsg);
+ }
+
+ if(itemDrop && !itemDrop->isOpen())
+ itemDrop->setOpen(true);
+ }
+
+ return;
+ }
+
+ if (ItemDrag::canDecode(e))
+ {
+ TAlbum *destAlbum = itemDrop->album();
+ TAlbum *srcAlbum;
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ if (!ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
+ return;
+
+ if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
+ return;
+
+ // all the albumids will be the same
+ int albumID = albumIDs.first();
+ srcAlbum = d->albumMan->findTAlbum(albumID);
+ if (!srcAlbum)
+ {
+ DWarning() << "Could not find source album of drag"
+ << endl;
+ return;
+ }
+
+ int id = 0;
+ char keys_return[32];
+ XQueryKeymap(x11Display(), keys_return);
+ int key_1 = XKeysymToKeycode(x11Display(), 0xFFE3);
+ int key_2 = XKeysymToKeycode(x11Display(), 0xFFE4);
+
+ if(srcAlbum == destAlbum)
+ {
+ // Setting the dropped image as the album thumbnail
+ // If the ctrl key is pressed, when dropping the image, the
+ // thumbnail is set without a popup menu
+ if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 12;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Tags"));
+ popMenu.insertItem(i18n("Set as Tag Thumbnail"), 12);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ if(id == 12)
+ {
+ TQString errMsg;
+ d->albumMan->updateTAlbumIcon(destAlbum, TQString(), imageIDs.first(), errMsg);
+ }
+ return;
+ }
+
+ // If a ctrl key is pressed while dropping the drag object,
+ // the tag is assigned to the images without showing a
+ // popup menu.
+ if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 10;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("My Tags"));
+ popMenu.insertItem( SmallIcon("tag"), i18n("Assign Tag '%1' to Items")
+ .arg(destAlbum->prettyURL()), 10) ;
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ if (id == 10)
+ {
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assigning image tags. Please wait..."));
+
+ AlbumLister::instance()->blockSignals(true);
+ d->albumMan->albumDB()->beginTransaction();
+ int i=0;
+ for (TQValueList<int>::const_iterator it = imageIDs.begin();
+ it != imageIDs.end(); ++it)
+ {
+ // create temporary ImageInfo object
+ ImageInfo info(*it);
+
+ MetadataHub hub;
+ hub.load(&info);
+ hub.setTag(destAlbum, true);
+ hub.write(&info, MetadataHub::PartialWrite);
+ hub.write(info.filePath(), MetadataHub::FullWriteIfChanged);
+
+ emit signalProgressValue((int)((i++/(float)imageIDs.count())*100.0));
+ kapp->processEvents();
+ }
+ AlbumLister::instance()->blockSignals(false);
+ d->albumMan->albumDB()->commitTransaction();
+
+ ImageAttributesWatch::instance()->imagesChanged(destAlbum->id());
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ }
+ }
+}
+
+void TagFolderView::selectItem(int id)
+{
+ TAlbum* tag = d->albumMan->findTAlbum(id);
+ if(!tag)
+ return;
+
+ TagFolderViewItem *item =
+ (TagFolderViewItem*)tag->extraData(this);
+ if(item)
+ {
+ setSelected(item, true);
+ ensureItemVisible(item);
+ }
+}
+
+void TagFolderView::refresh()
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ TagFolderViewItem* item = dynamic_cast<TagFolderViewItem*>(*it);
+ if (item)
+ item->refresh();
+ ++it;
+ }
+}
+
+void TagFolderView::slotRefresh(const TQMap<int, int>& tagsStatMap)
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ TagFolderViewItem* item = dynamic_cast<TagFolderViewItem*>(*it);
+ if (item)
+ {
+ if (item->album())
+ {
+ int id = item->id();
+ TQMap<int, int>::const_iterator it2 = tagsStatMap.find(id);
+ if ( it2 != tagsStatMap.end() )
+ item->setCount(it2.data());
+ }
+ }
+ ++it;
+ }
+
+ refresh();
+}
+
+} // namespace Digikam
diff --git a/src/digikam/tagfolderview.h b/src/digikam/tagfolderview.h
new file mode 100644
index 00000000..27523e9d
--- /dev/null
+++ b/src/digikam/tagfolderview.h
@@ -0,0 +1,107 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-22
+ * Description : tags folder view.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file tagfoldeview.h */
+
+#ifndef _TAGFOLDERVIEW_H_
+#define _TAGFOLDERVIEW_H_
+
+// Local includes.
+
+#include "folderview.h"
+
+class TQDropEvent;
+
+namespace Digikam
+{
+
+class Album;
+class TAlbum;
+class TagFolderViewItem;
+class TagFolderViewPriv;
+
+class TagFolderView : public FolderView
+{
+ TQ_OBJECT
+
+public:
+
+ TagFolderView(TQWidget *parent);
+ ~TagFolderView();
+
+ void tagNew();
+ void tagEdit();
+ void tagDelete();
+
+ void selectItem(int id);
+ void refresh();
+
+ signals:
+
+ void signalProgressBarMode(int, const TQString&);
+ void signalProgressValue(int);
+ void signalTextTagFilterMatch(bool);
+
+public slots:
+
+ void slotTextTagFilterChanged(const TQString&);
+
+protected:
+
+ void contentsDropEvent(TQDropEvent *e);
+ TQDragObject* dragObject();
+ bool acceptDrop(const TQDropEvent *e) const;
+
+private slots:
+
+ void slotAlbumAdded(Album*);
+ void slotSelectionChanged();
+ void slotAlbumDeleted(Album*);
+ void slotAlbumRenamed(Album*);
+ void slotAlbumsCleared();
+ void slotAlbumIconChanged(Album* album);
+ void slotAlbumMoved(TAlbum* tag, TAlbum* newParent);
+ void slotContextMenu(TQListViewItem*, const TQPoint&, int);
+ void slotABCContextMenu();
+ void slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail);
+ void slotThumbnailLost(Album *album);
+ void slotReloadThumbnails();
+ void slotRefresh(const TQMap<int, int>&);
+
+private:
+
+ void tagNew(TagFolderViewItem *item, const TQString& _title=TQString(),
+ const TQString& _icon=TQString() );
+ void tagEdit(TagFolderViewItem *item);
+ void tagDelete(TagFolderViewItem *item);
+ void setTagThumbnail(TAlbum *album);
+
+private:
+
+ TagFolderViewPriv *d;
+};
+
+} // namespace Digikam
+
+#endif // _TAGFOLDEVIEW_H_
diff --git a/src/digikam/tagspopupmenu.cpp b/src/digikam/tagspopupmenu.cpp
new file mode 100644
index 00000000..8b9e4644
--- /dev/null
+++ b/src/digikam/tagspopupmenu.cpp
@@ -0,0 +1,366 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-07
+ * Description : a pop-up menu implementation to display a
+ * hierarchical view of digiKam tags.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define ADDTAGID 10000
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqstring.h>
+#include <tqpainter.h>
+#include <tqstyle.h>
+#include <tqvaluelist.h>
+#include <tqpixmap.h>
+#include <tqvaluevector.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albumiconview.h"
+#include "albumiconitem.h"
+#include "albummanager.h"
+#include "albumdb.h"
+#include "album.h"
+#include "syncjob.h"
+#include "tageditdlg.h"
+#include "tagspopupmenu.h"
+#include "tagspopupmenu.moc"
+
+namespace Digikam
+{
+
+class TagsPopupCheckedMenuItem : public TQCustomMenuItem
+{
+public:
+
+ TagsPopupCheckedMenuItem(TQPopupMenu* popup, const TQString& txt, const TQPixmap& pix)
+ : TQCustomMenuItem(), m_popup(popup), m_txt(txt), m_pix(pix)
+ {
+ }
+
+ virtual TQSize sizeHint()
+ {
+ TQFont fn = m_popup->font();
+ TQFontMetrics fm(fn);
+ int w = fm.width(m_txt) + 5 + kapp->style().pixelMetric(TQStyle::PM_IndicatorWidth, 0);
+ int h = TQMAX(fm.height(), m_pix.height());
+ return TQSize( w, h );
+ }
+
+ virtual void paint(TQPainter* p, const TQColorGroup& cg, bool act, bool enabled,
+ int x, int y, int w, int h )
+ {
+ p->save();
+ p->setPen(act ? cg.highlightedText() : cg.highlight());
+ p->drawText(x, y, w, h, TQt::AlignLeft|TQt::AlignVCenter, m_txt);
+ p->restore();
+
+ if (!m_pix.isNull())
+ {
+ TQRect pixRect(x/2 - m_pix.width()/2, y, m_pix.width(), m_pix.height());
+ p->drawPixmap( pixRect.topLeft(), m_pix );
+ }
+
+ int checkWidth = kapp->style().pixelMetric(TQStyle::PM_IndicatorWidth, 0);
+ int checkHeight = kapp->style().pixelMetric(TQStyle::PM_IndicatorHeight, 0);
+
+ TQStyle::SFlags flags = TQStyle::Style_Default;
+ flags |= TQStyle::Style_On;
+ if (enabled)
+ flags |= TQStyle::Style_Enabled;
+ if (act)
+ flags |= TQStyle::Style_Active;
+
+ TQFont fn = m_popup->font();
+ TQFontMetrics fm(fn);
+ TQRect r(x + 5 + fm.width(m_txt), y + (h/2-checkHeight/2), checkWidth, checkHeight);
+ kapp->style().drawPrimitive(TQStyle::PE_CheckMark, p, r, cg, flags);
+ }
+
+private:
+
+ TQPopupMenu *m_popup;
+
+ TQString m_txt;
+
+ TQPixmap m_pix;
+};
+
+// ------------------------------------------------------------------------
+
+class TagsPopupMenuPriv
+{
+public:
+
+ TagsPopupMenuPriv(){}
+
+ int addToID;
+
+ TQPixmap addTagPix;
+
+ TQValueList<int> assignedTags;
+ TQValueList<TQ_LLONG> selectedImageIDs;
+
+ TagsPopupMenu::Mode mode;
+};
+
+TagsPopupMenu::TagsPopupMenu(const TQValueList<TQ_LLONG>& selectedImageIDs, int addToID, Mode mode)
+ : TQPopupMenu(0)
+{
+ d = new TagsPopupMenuPriv;
+ d->selectedImageIDs = selectedImageIDs;
+ d->addToID = addToID;
+ d->mode = mode;
+
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+ d->addTagPix = iconLoader->loadIcon("tag",
+ TDEIcon::NoGroup,
+ TDEIcon::SizeSmall,
+ TDEIcon::DefaultState,
+ 0, true);
+
+ connect(this, TQ_SIGNAL(aboutToShow()),
+ this, TQ_SLOT(slotAboutToShow()));
+
+ connect(this, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotActivated(int)));
+}
+
+TagsPopupMenu::~TagsPopupMenu()
+{
+ delete d;
+}
+
+void TagsPopupMenu::clearPopup()
+{
+ d->assignedTags.clear();
+ clear();
+}
+
+TQPopupMenu* TagsPopupMenu::buildSubMenu(int tagid)
+{
+ AlbumManager* man = AlbumManager::instance();
+ TAlbum* album = man->findTAlbum(tagid);
+ if (!album)
+ return 0;
+
+ TQPopupMenu* popup = new TQPopupMenu(this);
+
+ connect(popup, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotActivated(int)));
+
+ if (d->mode == ASSIGN)
+ {
+ popup->insertItem(d->addTagPix, i18n("Add New Tag..."), ADDTAGID + album->id());
+ popup->insertSeparator();
+
+ TQPixmap pix = SyncJob::getTagThumbnail(album);
+ if ((d->mode == ASSIGN) && (d->assignedTags.contains(album->id())))
+ {
+ popup->insertItem(new TagsPopupCheckedMenuItem(popup, album->title(), pix),
+ d->addToID + album->id());
+ }
+ else
+ {
+ popup->insertItem(pix, album->title(), d->addToID + album->id());
+ }
+
+ if (album->firstChild())
+ {
+ popup->insertSeparator();
+ }
+ }
+ else
+ {
+ if (!album->isRoot())
+ {
+ TQPixmap pix = SyncJob::getTagThumbnail(album);
+ popup->insertItem(pix, album->title(), d->addToID + album->id());
+ popup->insertSeparator();
+ }
+ }
+
+ iterateAndBuildMenu(popup, album);
+
+ return popup;
+}
+
+void TagsPopupMenu::slotAboutToShow()
+{
+ clearPopup();
+
+ AlbumManager* man = AlbumManager::instance();
+
+ if (d->mode == REMOVE || d->mode == DISPLAY)
+ {
+ if (d->selectedImageIDs.isEmpty())
+ return;
+
+ d->assignedTags = man->albumDB()->getItemCommonTagIDs(d->selectedImageIDs);
+
+ if (d->assignedTags.isEmpty())
+ return;
+
+ // also add the parents of the assigned tags
+ IntList tList;
+ for (IntList::iterator it = d->assignedTags.begin();
+ it != d->assignedTags.end(); ++it)
+ {
+ TAlbum* album = man->findTAlbum(*it);
+ if (album)
+ {
+ Album* a = album->parent();
+ while (a)
+ {
+ tList.append(a->id());
+ a = a->parent();
+ }
+ }
+ }
+
+ for (IntList::iterator it = tList.begin();
+ it != tList.end(); ++it)
+ {
+ d->assignedTags.append(*it);
+ }
+ }
+ else if (d->mode == ASSIGN)
+ {
+ if (d->selectedImageIDs.count() == 1)
+ {
+ d->assignedTags = man->albumDB()->getItemCommonTagIDs(d->selectedImageIDs);
+ }
+ }
+
+ TAlbum* album = man->findTAlbum(0);
+ if (!album)
+ return;
+
+ if (d->mode == ASSIGN)
+ {
+ insertItem(d->addTagPix, i18n("Add New Tag..."), ADDTAGID);
+ if (album->firstChild())
+ {
+ insertSeparator();
+ }
+ }
+
+ iterateAndBuildMenu(this, album);
+}
+
+// for qHeapSort
+typedef TQPair<TQString, Album*> TagsMenuSortType;
+bool operator<(const TagsMenuSortType &lhs, const TagsMenuSortType &rhs)
+{
+ return lhs.first < rhs.first;
+}
+
+void TagsPopupMenu::iterateAndBuildMenu(TQPopupMenu *menu, TAlbum *album)
+{
+ TQValueVector<TagsMenuSortType> sortedTags;
+
+ for (Album* a = album->firstChild(); a; a = a->next())
+ {
+ sortedTags.push_back(qMakePair(a->title(), a));
+ }
+
+ qHeapSort(sortedTags);
+
+ for (TQValueVector<TagsMenuSortType>::Iterator i = sortedTags.begin(); i != sortedTags.end(); ++i)
+ {
+ Album *a = i->second;
+
+ if (d->mode == REMOVE || d->mode == DISPLAY)
+ {
+ IntList::iterator it = tqFind(d->assignedTags.begin(),
+ d->assignedTags.end(), a->id());
+ if (it == d->assignedTags.end())
+ continue;
+ }
+
+ TQPixmap pix = SyncJob::getTagThumbnail((TAlbum*)a);
+ TQString t = a->title();
+ t.replace('&',"&&");
+
+ if (a->firstChild())
+ {
+ menu->insertItem(pix, t, buildSubMenu(a->id()));
+ }
+ else
+ {
+ if ((d->mode == ASSIGN) && (d->assignedTags.contains(a->id())))
+ {
+ menu->insertItem(new TagsPopupCheckedMenuItem(this, a->title(), pix),
+ d->addToID + a->id());
+ }
+ else
+ {
+ menu->insertItem(pix, t, d->addToID + a->id());
+ }
+ }
+ }
+}
+
+void TagsPopupMenu::slotActivated(int id)
+{
+ if (id >= ADDTAGID)
+ {
+ int tagID = id - ADDTAGID;
+
+ AlbumManager* man = AlbumManager::instance();
+ TAlbum* parent = man->findTAlbum(tagID);
+ if (!parent)
+ {
+ DWarning() << "Failed to find album with id "
+ << tagID << endl;
+ return;
+ }
+
+ TQString title, icon;
+ if (!TagEditDlg::tagCreate(kapp->activeWindow(), parent, title, icon))
+ return;
+
+ TQMap<TQString, TQString> errMap;
+ AlbumList tList = TagEditDlg::createTAlbum(parent, title, icon, errMap);
+ TagEditDlg::showtagsListCreationError(kapp->activeWindow(), errMap);
+
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ emit signalTagActivated((*it)->id());
+ }
+ else
+ {
+ int tagID = id - d->addToID;
+ emit signalTagActivated(tagID);
+ }
+}
+
+} // namespace Digikam
diff --git a/src/digikam/tagspopupmenu.h b/src/digikam/tagspopupmenu.h
new file mode 100644
index 00000000..5e746410
--- /dev/null
+++ b/src/digikam/tagspopupmenu.h
@@ -0,0 +1,78 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-07
+ * Description : a pop-up menu implementation to display a
+ * hierarchical view of digiKam tags.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TAGSPOPUPMENU_H
+#define TAGSPOPUPMENU_H
+
+// TQt includes.
+
+#include <tqpopupmenu.h>
+
+namespace Digikam
+{
+
+class TAlbum;
+class TagsPopupMenuPriv;
+
+class TagsPopupMenu : public TQPopupMenu
+{
+ TQ_OBJECT
+
+public:
+
+ enum Mode
+ {
+ ASSIGN = 0,
+ REMOVE,
+ DISPLAY // Used by "GoTo Tag" feature
+ };
+
+ TagsPopupMenu(const TQValueList<TQ_LLONG>& selectedImageIDs, int addToID, Mode mode);
+ ~TagsPopupMenu();
+
+signals:
+
+ void signalTagActivated(int id);
+
+private slots:
+
+ void slotAboutToShow();
+ void slotActivated(int id);
+
+private:
+
+ void clearPopup();
+ TQPopupMenu* buildSubMenu(int tagid);
+ void iterateAndBuildMenu(TQPopupMenu *menu, TAlbum *album);
+ bool showThisTag(int tagid);
+
+private:
+
+ TagsPopupMenuPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* TAGSPOPUPMENU_H */
diff --git a/src/digikam/thumbnailsize.h b/src/digikam/thumbnailsize.h
new file mode 100644
index 00000000..a665913e
--- /dev/null
+++ b/src/digikam/thumbnailsize.h
@@ -0,0 +1,90 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-07
+ * Description : thumbnails size interface
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef THUMBNAILSIZE_H
+#define THUMBNAILSIZE_H
+
+namespace Digikam
+{
+
+class ThumbnailSize
+{
+
+public:
+
+ enum Size
+ {
+ Step = 8,
+ Tiny = 32,
+ Small = 64,
+ Medium = 96,
+ Large = 160,
+ Huge = 256
+ };
+
+ ThumbnailSize()
+ {
+ m_Size = Medium;
+ }
+
+ ThumbnailSize(int size)
+ {
+ m_Size = size;
+ }
+
+ ~ThumbnailSize()
+ {
+ }
+
+ ThumbnailSize(const ThumbnailSize& thumbsize)
+ {
+ if (this != &thumbsize)
+ m_Size = thumbsize.m_Size;
+ }
+
+ ThumbnailSize& operator=(const ThumbnailSize& thumbsize)
+ {
+ if (this != &thumbsize)
+ m_Size = thumbsize.m_Size;
+ return *this;
+ }
+
+ bool operator!=(const ThumbnailSize& thumbsize)
+ {
+ return m_Size != thumbsize.m_Size;
+ }
+
+ int size() const
+ {
+ return m_Size;
+ }
+
+private:
+
+ int m_Size;
+
+};
+
+} // namespace Digikam
+
+#endif // THUMBNAILSIZE_H
diff --git a/src/digikam/timelinefolderview.cpp b/src/digikam/timelinefolderview.cpp
new file mode 100644
index 00000000..0168b8cb
--- /dev/null
+++ b/src/digikam/timelinefolderview.cpp
@@ -0,0 +1,308 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-01-14
+ * Description : Searches dates folder view used by timeline
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfont.h>
+#include <tqpainter.h>
+#include <tqstyle.h>
+#include <tqcursor.h>
+
+// KDE includes.
+
+#include <tdepopupmenu.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "album.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "folderitem.h"
+#include "timelinefolderview.h"
+#include "timelinefolderview.moc"
+
+namespace Digikam
+{
+
+class TimeLineFolderItem : public FolderItem
+{
+
+public:
+
+ TimeLineFolderItem(TQListView* parent, SAlbum* album)
+ : FolderItem(parent, album->title()),
+ m_album(album)
+ {
+ m_album->setExtraData(parent, this);
+ }
+
+ ~TimeLineFolderItem()
+ {
+ m_album->removeExtraData(listView());
+ }
+
+ int compare(TQListViewItem* i, int , bool ) const
+ {
+ if (!i)
+ return 0;
+
+ return text(0).localeAwareCompare(i->text(0));
+ }
+
+ int id() const
+ {
+ return m_album ? m_album->id() : 0;
+ }
+
+ SAlbum* album() const
+ {
+ return m_album;
+ }
+
+private:
+
+ SAlbum *m_album;
+};
+
+TimeLineFolderView::TimeLineFolderView(TQWidget* parent)
+ : FolderView(parent, "TimeLineFolderView")
+{
+ m_currentTimeLineSearchName = TQString("_Current_Time_Line_Search_");
+ addColumn(i18n("My Date Searches"));
+ setResizeMode(TQListView::LastColumn);
+ setRootIsDecorated(false);
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(clear()));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumRenamed(Album*)),
+ this, TQ_SLOT(slotAlbumRenamed(Album*)));
+
+ connect(this, TQ_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
+ this, TQ_SLOT(slotContextMenu(TQListViewItem*, const TQPoint&, int)));
+
+ connect(this, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+}
+
+TimeLineFolderView::~TimeLineFolderView()
+{
+ saveViewState();
+}
+
+TQString TimeLineFolderView::currentTimeLineSearchName() const
+{
+ return m_currentTimeLineSearchName;
+}
+
+void TimeLineFolderView::slotTextSearchFilterChanged(const TQString& filter)
+{
+ TQString search = filter.lower();
+
+ bool atleastOneMatch = false;
+
+ AlbumList sList = AlbumManager::instance()->allSAlbums();
+ for (AlbumList::iterator it = sList.begin(); it != sList.end(); ++it)
+ {
+ SAlbum* salbum = (SAlbum*)(*it);
+ TimeLineFolderItem* viewItem = (TimeLineFolderItem*) salbum->extraData(this);
+
+ // Check if a special url query exist to identify a SAlbum dedicaced to Date Search
+ // used with TimeLine.
+ KURL url = salbum->kurl();
+ TQString type = url.queryItem("type");
+
+ if (salbum->title().lower().contains(search) &&
+ type == TQString("datesearch") &&
+ salbum->title() != currentTimeLineSearchName())
+ {
+ atleastOneMatch = true;
+
+ if (viewItem)
+ viewItem->setVisible(true);
+ }
+ else
+ {
+ if (viewItem)
+ {
+ viewItem->setVisible(false);
+ }
+ }
+ }
+
+ emit signalTextSearchFilterMatch(atleastOneMatch);
+}
+
+void TimeLineFolderView::searchDelete(SAlbum* album)
+{
+ if (!album)
+ return;
+
+ // Make sure that a complicated search is not deleted accidentally
+ int result = KMessageBox::warningYesNo(this, i18n("Are you sure you want to "
+ "delete the selected Date Search "
+ "\"%1\"?")
+ .arg(album->title()),
+ i18n("Delete Date Search?"),
+ i18n("Delete"),
+ KStdGuiItem::cancel());
+
+ if (result != KMessageBox::Yes)
+ return;
+
+ AlbumManager::instance()->deleteSAlbum(album);
+}
+
+void TimeLineFolderView::slotAlbumAdded(Album* a)
+{
+ if (!a || a->type() != Album::SEARCH)
+ return;
+
+ SAlbum *salbum = dynamic_cast<SAlbum*>(a);
+ if (!salbum) return;
+
+ // Check if a special url query exist to identify a SAlbum dedicaced to Date Search
+ KURL url = salbum->kurl();
+ TQMap<TQString, TQString> queries = url.queryItems();
+ if (queries.isEmpty()) return;
+
+ TQString type = url.queryItem("type");
+ if (type != TQString("datesearch")) return;
+
+ // We will ignore the internal Dates Search Album used to perform selection from timeline.
+ TQString name = url.queryItem("name");
+ if (name == currentTimeLineSearchName()) return;
+
+ TimeLineFolderItem* item = new TimeLineFolderItem(this, salbum);
+ item->setPixmap(0, SmallIcon("edit-find", AlbumSettings::instance()->getDefaultTreeIconSize()));
+}
+
+void TimeLineFolderView::slotAlbumDeleted(Album* a)
+{
+ if (!a || a->type() != Album::SEARCH)
+ return;
+
+ SAlbum* album = (SAlbum*)a;
+
+ TimeLineFolderItem* item = (TimeLineFolderItem*) album->extraData(this);
+ if (item)
+ delete item;
+}
+
+void TimeLineFolderView::slotAlbumRenamed(Album* album)
+{
+ if (!album)
+ return;
+
+ SAlbum* salbum = dynamic_cast<SAlbum*>(album);
+ if (!salbum)
+ return;
+
+ TimeLineFolderItem* item = (TimeLineFolderItem*)(salbum->extraData(this));
+ if (item)
+ item->setText(0, item->album()->title());
+}
+
+void TimeLineFolderView::slotSelectionChanged()
+{
+ TQListViewItem* selItem = 0;
+
+ TQListViewItemIterator it( this );
+ while (it.current())
+ {
+ if (it.current()->isSelected())
+ {
+ selItem = it.current();
+ break;
+ }
+ ++it;
+ }
+
+ if (!selItem)
+ {
+ emit signalAlbumSelected(0);
+ return;
+ }
+
+ TimeLineFolderItem* searchItem = dynamic_cast<TimeLineFolderItem*>(selItem);
+
+ if (!searchItem || !searchItem->album())
+ emit signalAlbumSelected(0);
+ else
+ emit signalAlbumSelected(searchItem->album());
+}
+
+void TimeLineFolderView::slotContextMenu(TQListViewItem* item, const TQPoint&, int)
+{
+ if (!item) return;
+
+ TimeLineFolderItem* sItem = dynamic_cast<TimeLineFolderItem*>(item);
+
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("My Date Searches"));
+ popmenu.insertItem(SmallIcon("pencil"), i18n("Rename..."), 10);
+ popmenu.insertItem(SmallIcon("edit-delete"), i18n("Delete"), 11);
+
+ switch (popmenu.exec(TQCursor::pos()))
+ {
+ case 10:
+ {
+ emit signalRenameAlbum(sItem->album());
+ break;
+ }
+ case 11:
+ {
+ searchDelete(sItem->album());
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void TimeLineFolderView::selectItem(int id)
+{
+ SAlbum *album = AlbumManager::instance()->findSAlbum(id);
+ if(!album)
+ return;
+
+ TimeLineFolderItem *item = (TimeLineFolderItem*)album->extraData(this);
+ if(item)
+ {
+ setSelected(item, true);
+ ensureItemVisible(item);
+ }
+}
+
+} // namespace Digikam
diff --git a/src/digikam/timelinefolderview.h b/src/digikam/timelinefolderview.h
new file mode 100644
index 00000000..2e8a8a86
--- /dev/null
+++ b/src/digikam/timelinefolderview.h
@@ -0,0 +1,81 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-01-14
+ * Description : Searches dates folder view used by timeline
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TIMELINEFOLDERVIEW_H
+#define TIMELINEFOLDERVIEW_H
+
+// Local includes.
+
+#include "folderview.h"
+
+namespace Digikam
+{
+
+class SAlbum;
+class TimeLineFolderItem;
+
+class TimeLineFolderView : public FolderView
+{
+ TQ_OBJECT
+
+public:
+
+ TimeLineFolderView(TQWidget* parent);
+ ~TimeLineFolderView();
+
+ void searchDelete(SAlbum* album);
+ TQString currentTimeLineSearchName() const;
+
+signals:
+
+ void signalTextSearchFilterMatch(bool);
+ void signalAlbumSelected(SAlbum*);
+ void signalRenameAlbum(SAlbum*);
+
+public slots:
+
+ void slotTextSearchFilterChanged(const TQString&);
+
+private slots:
+
+ void slotAlbumAdded(Album* album);
+ void slotAlbumDeleted(Album* album);
+ void slotAlbumRenamed(Album* album);
+ void slotSelectionChanged();
+ void slotContextMenu(TQListViewItem*, const TQPoint&, int);
+
+protected:
+
+ void selectItem(int id);
+
+private:
+
+ // Used to store in database the name of search performed by
+ // current selection from timeline.
+ TQString m_currentTimeLineSearchName;
+};
+
+} // namespace Digikam
+
+#endif /* TIMELINEFOLDERVIEW_H */
diff --git a/src/digikam/timelineview.cpp b/src/digikam/timelineview.cpp
new file mode 100644
index 00000000..ae8a6bc2
--- /dev/null
+++ b/src/digikam/timelineview.cpp
@@ -0,0 +1,645 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-12-08
+ * Description : Time line sidebar tab contents.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtimer.h>
+#include <tqframe.h>
+#include <tqhbox.h>
+#include <tqlayout.h>
+#include <tqcombobox.h>
+#include <tqpushbutton.h>
+#include <tqhbuttongroup.h>
+#include <tqmap.h>
+#include <tqscrollbar.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <kdialog.h>
+#include <klineedit.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <ksqueezedtextlabel.h>
+#include <kstandarddirs.h>
+#include <tdeversion.h>
+#include <tdemessagebox.h>
+
+#if KDE_IS_VERSION(3,2,0)
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// Local includes.
+
+#include "album.h"
+#include "albummanager.h"
+#include "ddebug.h"
+#include "searchtextbar.h"
+#include "timelinefolderview.h"
+#include "timelinewidget.h"
+#include "timelineview.h"
+#include "timelineview.moc"
+
+namespace Digikam
+{
+
+class TimeLineViewPriv
+{
+
+public:
+
+ TimeLineViewPriv()
+ {
+ timeUnitCB = 0;
+ scaleBG = 0;
+ cursorDateLabel = 0;
+ cursorCountLabel = 0;
+ timeLineWidget = 0;
+ timer = 0;
+ resetButton = 0;
+ saveButton = 0;
+ scrollBar = 0;
+ timeLineFolderView = 0;
+ nameEdit = 0;
+ searchDateBar = 0;
+ }
+
+ TQScrollBar *scrollBar;
+
+ TQTimer *timer;
+
+ TQComboBox *timeUnitCB;
+
+ TQHButtonGroup *scaleBG;
+
+ TQPushButton *resetButton;
+ TQPushButton *saveButton;
+
+ TQLabel *cursorCountLabel;
+
+ KLineEdit *nameEdit;
+
+ KSqueezedTextLabel *cursorDateLabel;
+
+ SearchTextBar *searchDateBar;
+
+ TimeLineWidget *timeLineWidget;
+
+ TimeLineFolderView *timeLineFolderView;
+};
+
+TimeLineView::TimeLineView(TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new TimeLineViewPriv;
+ d->timer = new TQTimer(this);
+
+ TQVBoxLayout *vlay = new TQVBoxLayout(this);
+ TQFrame *panel = new TQFrame(this);
+ panel->setFrameStyle(TQFrame::StyledPanel | TQFrame::Sunken);
+ panel->setLineWidth(1);
+
+ TQGridLayout *grid = new TQGridLayout(panel, 4, 3);
+
+ // ---------------------------------------------------------------
+
+ TQWidget *hbox1 = new TQWidget(panel);
+ TQHBoxLayout *hlay = new TQHBoxLayout(hbox1);
+
+ TQLabel *label1 = new TQLabel(i18n("Time Unit:"), hbox1);
+ d->timeUnitCB = new TQComboBox(false, hbox1);
+ d->timeUnitCB->insertItem(i18n("Day"), TimeLineWidget::Day);
+ d->timeUnitCB->insertItem(i18n("Week"), TimeLineWidget::Week);
+ d->timeUnitCB->insertItem(i18n("Month"), TimeLineWidget::Month);
+ d->timeUnitCB->insertItem(i18n("Year"), TimeLineWidget::Year);
+ d->timeUnitCB->setCurrentItem((int)TimeLineWidget::Month);
+ d->timeUnitCB->setFocusPolicy(TQWidget::NoFocus);
+ TQWhatsThis::add(d->timeUnitCB, i18n("<p>Select the histogram time unit here.<p>"
+ "You can change the graph decade to zoom in or zoom out over time."));
+
+ d->scaleBG = new TQHButtonGroup(hbox1);
+ d->scaleBG->setExclusive(true);
+ d->scaleBG->setFrameShape(TQFrame::NoFrame);
+ d->scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add(d->scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the date count's maximal values are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal values are big; "
+ "if it is used, all values (small and large) will be visible on the "
+ "graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( d->scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ d->scaleBG->insert(linHistoButton, TimeLineWidget::LinScale);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( d->scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ d->scaleBG->insert(logHistoButton, TimeLineWidget::LogScale);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ hlay->setMargin(0);
+ hlay->setSpacing(KDialog::spacingHint());
+ hlay->addWidget(label1);
+ hlay->addWidget(d->timeUnitCB);
+ hlay->addItem(new TQSpacerItem(10, 10, TQSizePolicy::Expanding, TQSizePolicy::Minimum));
+ hlay->addWidget(d->scaleBG);
+
+ // ---------------------------------------------------------------
+
+ d->timeLineWidget = new TimeLineWidget(panel);
+ d->scrollBar = new TQScrollBar(panel);
+ d->scrollBar->setOrientation(TQt::Horizontal);
+ d->scrollBar->setMinValue(0);
+ d->scrollBar->setLineStep(1);
+
+ d->cursorDateLabel = new KSqueezedTextLabel(0, panel);
+ d->cursorCountLabel = new TQLabel(panel);
+ d->cursorCountLabel->setAlignment(TQt::AlignRight);
+
+ // ---------------------------------------------------------------
+
+ TQHBox *hbox2 = new TQHBox(panel);
+ hbox2->setMargin(0);
+ hbox2->setSpacing(KDialog::spacingHint());
+
+ d->resetButton = new TQPushButton(hbox2);
+ d->resetButton->setPixmap(SmallIcon("reload_page"));
+ TQToolTip::add(d->resetButton, i18n("Clear current selection"));
+ TQWhatsThis::add(d->resetButton, i18n("<p>If you press this button, current "
+ "dates selection from time-line will be "
+ "clear."));
+ d->nameEdit = new KLineEdit(hbox2);
+ TQWhatsThis::add(d->nameEdit, i18n("<p>Enter the name of the current dates search to save in the "
+ "\"My Date Searches\" view"));
+
+ d->saveButton = new TQPushButton(hbox2);
+ d->saveButton->setPixmap(SmallIcon("document-save"));
+ d->saveButton->setEnabled(false);
+ TQToolTip::add(d->saveButton, i18n("Save current selection to a new virtual Album"));
+ TQWhatsThis::add(d->saveButton, i18n("<p>If you press this button, current "
+ "dates selection from time-line will be "
+ "saved to a new search virtual Album using name "
+ "set on the left side."));
+
+ // ---------------------------------------------------------------
+
+ grid->addMultiCellWidget(hbox1, 0, 0, 0, 3);
+ grid->addMultiCellWidget(d->cursorDateLabel, 1, 1, 0, 2);
+ grid->addMultiCellWidget(d->cursorCountLabel, 1, 1, 3, 3);
+ grid->addMultiCellWidget(d->timeLineWidget, 2, 2, 0, 3);
+ grid->addMultiCellWidget(d->scrollBar, 3, 3, 0, 3);
+ grid->addMultiCellWidget(hbox2, 4, 4, 0, 3);
+ grid->setColStretch(2, 10);
+ grid->setMargin(KDialog::spacingHint());
+ grid->setSpacing(KDialog::spacingHint());
+
+ // ---------------------------------------------------------------
+
+ d->timeLineFolderView = new TimeLineFolderView(this);
+ d->searchDateBar = new SearchTextBar(this, "TimeLineViewSearchDateBar");
+
+ vlay->addWidget(panel);
+ vlay->addWidget(d->timeLineFolderView);
+ vlay->addItem(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::Minimum));
+ vlay->addWidget(d->searchDateBar);
+ vlay->setMargin(0);
+ vlay->setSpacing(0);
+
+ // ---------------------------------------------------------------
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalDatesMapDirty(const TQMap<TQDateTime, int>&)),
+ d->timeLineWidget, TQ_SLOT(slotDatesMap(const TQMap<TQDateTime, int>&)));
+
+ connect(d->timeLineFolderView, TQ_SIGNAL(signalAlbumSelected(SAlbum*)),
+ this, TQ_SLOT(slotAlbumSelected(SAlbum*)));
+
+ connect(d->timeLineFolderView, TQ_SIGNAL(signalRenameAlbum(SAlbum*)),
+ this, TQ_SLOT(slotRenameAlbum(SAlbum*)));
+
+ connect(d->timeLineFolderView, TQ_SIGNAL(signalTextSearchFilterMatch(bool)),
+ d->searchDateBar, TQ_SLOT(slotSearchResult(bool)));
+
+ connect(d->searchDateBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ d->timeLineFolderView, TQ_SLOT(slotTextSearchFilterChanged(const TQString&)));
+
+ connect(d->timeUnitCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotTimeUnitChanged(int)));
+
+ connect(d->scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(d->timeLineWidget, TQ_SIGNAL(signalDateMapChanged()),
+ this, TQ_SLOT(slotInit()));
+
+ connect(d->timeLineWidget, TQ_SIGNAL(signalCursorPositionChanged()),
+ this, TQ_SLOT(slotCursorPositionChanged()));
+
+ connect(d->timeLineWidget, TQ_SIGNAL(signalSelectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+
+ connect(d->timeLineWidget, TQ_SIGNAL(signalRefDateTimeChanged()),
+ this, TQ_SLOT(slotRefDateTimeChanged()));
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotUpdateCurrentDateSearchAlbum()));
+
+ connect(d->resetButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetSelection()));
+
+ connect(d->saveButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotSaveSelection()));
+
+ connect(d->scrollBar, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotScrollBarValueChanged(int)));
+
+ connect(d->nameEdit, TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotCheckAboutSelection()));
+
+ connect(d->nameEdit, TQ_SIGNAL(returnPressed(const TQString&)),
+ d->saveButton, TQ_SLOT(animateClick()));
+}
+
+TimeLineView::~TimeLineView()
+{
+ writeConfig();
+ delete d->timer;
+ delete d;
+}
+
+TimeLineFolderView* TimeLineView::folderView() const
+{
+ return d->timeLineFolderView;
+}
+
+SearchTextBar* TimeLineView::searchBar() const
+{
+ return d->searchDateBar;
+}
+
+void TimeLineView::slotInit()
+{
+ // Date Maps are loaded from AlbumManager to TimeLineWidget after than GUI is initialized.
+ // AlbumManager query Date TDEIO slave to stats items from database and it can take a while.
+ // We waiting than TimeLineWidget is ready before to set last config from users.
+
+ readConfig();
+
+ disconnect(d->timeLineWidget, TQ_SIGNAL(signalDateMapChanged()),
+ this, TQ_SLOT(slotInit()));
+
+ connect(d->timeLineWidget, TQ_SIGNAL(signalDateMapChanged()),
+ this, TQ_SLOT(slotCursorPositionChanged()));
+}
+
+void TimeLineView::readConfig()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("TimeLine SideBar");
+
+ d->timeUnitCB->setCurrentItem(config->readNumEntry("Histogram TimeUnit", TimeLineWidget::Month));
+ slotTimeUnitChanged(d->timeUnitCB->currentItem());
+
+ d->scaleBG->setButton(config->readNumEntry("Histogram Scale", TimeLineWidget::LinScale));
+ slotScaleChanged(d->scaleBG->selectedId());
+
+ TQDateTime now = TQDateTime::currentDateTime();
+ d->timeLineWidget->setCursorDateTime(config->readDateTimeEntry("Cursor Position", &now));
+ d->timeLineWidget->setCurrentIndex(d->timeLineWidget->indexForCursorDateTime());
+}
+
+void TimeLineView::writeConfig()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("TimeLine SideBar");
+ config->writeEntry("Histogram TimeUnit", d->timeUnitCB->currentItem());
+ config->writeEntry("Histogram Scale", d->scaleBG->selectedId());
+ config->writeEntry("Cursor Position", d->timeLineWidget->cursorDateTime());
+ config->sync();
+}
+
+void TimeLineView::setActive(bool val)
+{
+ if (d->timeLineFolderView->selectedItem())
+ {
+ d->timeLineFolderView->setActive(val);
+ }
+ else if (val)
+ {
+ int totalCount = 0;
+ DateRangeList list = d->timeLineWidget->selectedDateRange(totalCount);
+ if (list.isEmpty())
+ {
+ AlbumManager::instance()->setCurrentAlbum(0);
+ }
+ else
+ {
+ AlbumList sList = AlbumManager::instance()->allSAlbums();
+ for (AlbumList::iterator it = sList.begin(); it != sList.end(); ++it)
+ {
+ SAlbum* salbum = (SAlbum*)(*it);
+ if (salbum->title() == d->timeLineFolderView->currentTimeLineSearchName())
+ AlbumManager::instance()->setCurrentAlbum(salbum);
+ }
+ }
+ }
+}
+
+void TimeLineView::slotRefDateTimeChanged()
+{
+ d->scrollBar->blockSignals(true);
+ d->scrollBar->setMaxValue(d->timeLineWidget->totalIndex()-1);
+ d->scrollBar->setValue(d->timeLineWidget->indexForRefDateTime()-1);
+ d->scrollBar->blockSignals(false);
+}
+
+void TimeLineView::slotTimeUnitChanged(int mode)
+{
+ d->timeLineWidget->setTimeUnit((TimeLineWidget::TimeUnit)mode);
+}
+
+void TimeLineView::slotScrollBarValueChanged(int val)
+{
+ d->timeLineWidget->setCurrentIndex(val);
+}
+
+void TimeLineView::slotScaleChanged(int mode)
+{
+ d->timeLineWidget->setScaleMode((TimeLineWidget::ScaleMode)mode);
+}
+
+void TimeLineView::slotCursorPositionChanged()
+{
+ TQString txt;
+ int val = d->timeLineWidget->cursorInfo(txt);
+ d->cursorDateLabel->setText(txt);
+ d->cursorCountLabel->setText(TQString::number(val));
+}
+
+void TimeLineView::slotSelectionChanged()
+{
+ d->timer->start(100, true);
+}
+
+/** Called from d->timer event.*/
+void TimeLineView::slotUpdateCurrentDateSearchAlbum()
+{
+ slotCheckAboutSelection();
+ createNewDateSearchAlbum(d->timeLineFolderView->currentTimeLineSearchName());
+}
+
+void TimeLineView::slotSaveSelection()
+{
+ TQString name = d->nameEdit->text();
+ if (!checkName(name))
+ return;
+ createNewDateSearchAlbum(name);
+}
+
+void TimeLineView::createNewDateSearchAlbum(const TQString& name)
+{
+ int totalCount = 0;
+ TQDateTime start, end;
+ DateRangeList list = d->timeLineWidget->selectedDateRange(totalCount);
+
+ if (list.isEmpty())
+ {
+ AlbumManager::instance()->setCurrentAlbum(0);
+ return;
+ }
+
+ d->timeLineFolderView->blockSignals(true);
+ d->timeLineFolderView->clearSelection();
+ d->timeLineFolderView->blockSignals(false);
+
+ // We will make now the Url for digiKam Search TDEIO-Slave
+
+ KURL url;
+ url.setProtocol("digikamsearch");
+
+ int grp = list.count();
+ TQString path("1 AND 2");
+
+ if (grp > 1 )
+ {
+ for (int i = 1 ; i < grp; i++)
+ {
+ path.append(" OR ");
+ path.append(TQString("%1 AND %2").arg(i*2+1).arg(i*2+2));
+ }
+ }
+ url.setPath(path);
+
+ int i = 0;
+ DateRangeList::iterator it;
+ for (it = list.begin() ; it != list.end(); ++it)
+ {
+ start = (*it).first;
+ end = (*it).second;
+ url.addQueryItem(TQString("%1.key").arg(i*2+1), TQString("imagedate"));
+ url.addQueryItem(TQString("%1.op").arg(i*2+1), TQString("GT"));
+ url.addQueryItem(TQString("%1.val").arg(i*2+1), start.date().toString(TQt::ISODate));
+ url.addQueryItem(TQString("%1.key").arg(i*2+2), TQString("imagedate"));
+ url.addQueryItem(TQString("%1.op").arg(i*2+2), TQString("LT"));
+ url.addQueryItem(TQString("%1.val").arg(i*2+2), end.date().toString(TQt::ISODate));
+ i++;
+ }
+
+ url.addQueryItem("name", name);
+ url.addQueryItem("count", TQString::number(grp*2));
+ url.addQueryItem("type", TQString("datesearch"));
+
+ //DDebug() << url << endl;
+
+ SAlbum* album = AlbumManager::instance()->createSAlbum(url, false);
+ AlbumManager::instance()->setCurrentAlbum(album);
+}
+
+void TimeLineView::slotAlbumSelected(SAlbum* salbum)
+{
+ if (!salbum)
+ {
+ slotResetSelection();
+ return;
+ }
+
+ // Date Search url for TDEIO-Slave is something like that :
+ // digikamsearch:1 AND 2 OR 3 AND 4 OR 5 AND 6?
+ // 1.key=imagedate&1.op=GT&1.val=2006-02-06&
+ // 2.key=imagedate&2.op=LT&2.val=2006-02-07&
+ // 3.key=imagedate&3.op=GT&3.val=2006-02-10&
+ // 4.key=imagedate&4.op=LT&4.val=2006-02-11&
+ // 5.key=imagedate&5.op=GT&5.val=2006-02-12&
+ // 6.key=imagedate&6.op=LT&6.val=2006-02-13&
+ // name=TimeLineSelection&
+ // count=6
+ // type=datesearch
+
+ // Check if a special url query exist to identify a SAlbum dedicaced to Date Search
+ KURL url = salbum->kurl();
+ TQMap<TQString, TQString> queries = url.queryItems();
+ if (queries.isEmpty()) return;
+
+ TQString type = url.queryItem("type");
+ if (type != TQString("datesearch")) return;
+
+ bool ok = false;
+ int count = url.queryItem("count").toInt(&ok);
+ if (!ok || count <= 0) return;
+
+ //DDebug() << url << endl;
+
+ TQMap<TQString, TQString>::iterator it2;
+ TQString key;
+ TQDateTime start, end;
+ DateRangeList list;
+ for (int i = 1 ; i <= count ; i+=2)
+ {
+ key = TQString("%1.val").arg(TQString::number(i));
+ it2 = queries.find(key);
+ if (it2 != queries.end())
+ start = TQDateTime(TQDate::fromString(it2.data(), TQt::ISODate));
+
+ //DDebug() << key << " :: " << it2.data() << endl;
+
+ key = TQString("%1.val").arg(TQString::number(i+1));
+ it2 = queries.find(key);
+ if (it2 != queries.end())
+ end = TQDateTime(TQDate::fromString(it2.data(), TQt::ISODate));
+
+ //DDebug() << key << " :: " << it2.data() << endl;
+
+ list.append(DateRange(start, end));
+ }
+
+ /*
+ DateRangeList::iterator it3;
+ for (it3 = list.begin() ; it3 != list.end(); ++it3)
+ DDebug() << (*it3).first.date().toString(TQt::ISODate) << " :: "
+ << (*it3).second.date().toString(TQt::ISODate) << endl;
+ */
+
+ d->timeLineWidget->setSelectedDateRange(list);
+ AlbumManager::instance()->setCurrentAlbum(salbum);
+}
+
+void TimeLineView::slotResetSelection()
+{
+ d->timeLineWidget->slotResetSelection();
+ slotCheckAboutSelection();
+ AlbumManager::instance()->setCurrentAlbum(0);
+}
+
+bool TimeLineView::checkName(TQString& name)
+{
+ bool checked = checkAlbum(name);
+
+ while (!checked)
+ {
+ TQString label = i18n( "Search name already exists.\n"
+ "Please enter a new name:" );
+ bool ok;
+#if KDE_IS_VERSION(3,2,0)
+ TQString newTitle = KInputDialog::getText(i18n("Name exists"), label, name, &ok, this);
+#else
+ TQString newTitle = KLineEditDlg::getText(i18n("Name exists"), label, name, ok, this);
+#endif
+ if (!ok) return false;
+
+ name = newTitle;
+ checked = checkAlbum(name);
+ }
+
+ return true;
+}
+
+bool TimeLineView::checkAlbum(const TQString& name) const
+{
+ AlbumList list = AlbumManager::instance()->allSAlbums();
+
+ for (AlbumList::Iterator it = list.begin() ; it != list.end() ; ++it)
+ {
+ SAlbum *album = (SAlbum*)(*it);
+ if ( album->title() == name )
+ return false;
+ }
+ return true;
+}
+
+void TimeLineView::slotCheckAboutSelection()
+{
+ int totalCount = 0;
+ DateRangeList list = d->timeLineWidget->selectedDateRange(totalCount);
+ if (!list.isEmpty())
+ {
+ d->nameEdit->setEnabled(true);
+
+ if (!d->nameEdit->text().isEmpty())
+ d->saveButton->setEnabled(true);
+ }
+ else
+ {
+ d->nameEdit->setEnabled(false);
+ d->saveButton->setEnabled(false);
+ }
+}
+
+void TimeLineView::slotRenameAlbum(SAlbum* salbum)
+{
+ if (!salbum) return;
+
+ TQString oldName(salbum->title());
+ bool ok;
+
+#if KDE_IS_VERSION(3,2,0)
+ TQString name = KInputDialog::getText(i18n("Rename Album (%1)").arg(oldName),
+ i18n("Enter new album name:"),
+ oldName, &ok, this);
+#else
+ TQString name = KLineEditDlg::getText(i18n("Rename Album (%1)").arg(oldName),
+ i18n("Enter new album name:"),
+ oldName, &ok, this);
+#endif
+
+ if (!ok || name == oldName || name.isEmpty()) return;
+
+ if (!checkName(name)) return;
+
+ KURL url = salbum->kurl();
+ url.removeQueryItem("name");
+ url.addQueryItem("name", name);
+ AlbumManager::instance()->updateSAlbum(salbum, url);
+}
+
+} // NameSpace Digikam
diff --git a/src/digikam/timelineview.h b/src/digikam/timelineview.h
new file mode 100644
index 00000000..fd673662
--- /dev/null
+++ b/src/digikam/timelineview.h
@@ -0,0 +1,86 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-12-08
+ * Description : Time line sidebar tab contents.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TIMELINEVIEW_H
+#define TIMELINEVIEW_H
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqwidget.h>
+#include <tqstring.h>
+
+namespace Digikam
+{
+
+class SAlbum;
+class SearchTextBar;
+class TimeLineFolderView;
+class TimeLineViewPriv;
+
+class TimeLineView : public TQWidget
+{
+ TQ_OBJECT
+
+public:
+
+ TimeLineView(TQWidget *parent=0);
+ ~TimeLineView();
+
+ TimeLineFolderView *folderView() const;
+ SearchTextBar* searchBar() const;
+
+ void setActive(bool val);
+
+private:
+
+ void readConfig();
+ void writeConfig();
+ void createNewDateSearchAlbum(const TQString& name);
+ bool checkName(TQString& name);
+ bool checkAlbum(const TQString& name) const;
+
+private slots:
+
+ void slotInit();
+ void slotScrollBarValueChanged(int);
+ void slotRefDateTimeChanged();
+ void slotScaleChanged(int);
+ void slotTimeUnitChanged(int);
+ void slotCursorPositionChanged();
+ void slotSelectionChanged();
+ void slotResetSelection();
+ void slotSaveSelection();
+ void slotUpdateCurrentDateSearchAlbum();
+ void slotAlbumSelected(SAlbum*);
+ void slotCheckAboutSelection();
+ void slotRenameAlbum(SAlbum*);
+
+private:
+
+ TimeLineViewPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* TIMELINEVIEW_H */
diff --git a/src/digikam/timelinewidget.cpp b/src/digikam/timelinewidget.cpp
new file mode 100644
index 00000000..b9033e41
--- /dev/null
+++ b/src/digikam/timelinewidget.cpp
@@ -0,0 +1,1718 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-12-08
+ * Description : a widget to display date and time statistics of pictures
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpen.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kcalendarsystem.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "themeengine.h"
+#include "timelinewidget.h"
+#include "timelinewidget.moc"
+
+namespace Digikam
+{
+
+class TimeLineWidgetPriv
+{
+
+public :
+
+ typedef TQPair<int, int> YearRefPair; // Year + a reference association (Month or week or day)
+ typedef TQPair<int, TimeLineWidget::SelectionMode> StatPair; // Statistic value + selection status.
+
+public:
+
+ TimeLineWidgetPriv()
+ {
+ validMouseEvent = false;
+ selMouseEvent = false;
+ maxCountByDay = 1;
+ maxCountByWeek = 1;
+ maxCountByMonth = 1;
+ maxCountByYear = 1;
+ topMargin = 3;
+ bottomMargin = 20;
+ barWidth = 20;
+ startPos = 96;
+ nbItems = 10;
+ timeUnit = TimeLineWidget::Month;
+ scaleMode = TimeLineWidget::LinScale;
+ calendar = TDEGlobal::locale()->calendar();
+ }
+
+ bool validMouseEvent; // Current mouse enter event is valid to set cursor position or selection.
+ bool selMouseEvent; // Current mouse enter event is about to make a selection.
+
+ int maxCountByDay;
+ int maxCountByWeek;
+ int maxCountByMonth;
+ int maxCountByYear;
+ int topMargin;
+ int bottomMargin;
+ int barWidth;
+ int nbItems;
+ int startPos;
+
+ TQDateTime refDateTime; // Reference date-time used to draw histogram from middle of widget.
+ TQDateTime cursorDateTime; // Current date-time used to draw focus cursor.
+ TQDateTime minDateTime; // Higher date on histogram.
+ TQDateTime maxDateTime; // Lower date on histogram.
+ TQDateTime selStartDateTime;
+ TQDateTime selMinDateTime; // Lower date available on histogram.
+ TQDateTime selMaxDateTime; // Higher date available on histogram.
+
+ TQPixmap pixmap; // Used for widget double buffering.
+
+ TQMap<YearRefPair, StatPair> dayStatMap; // Store Days count statistics.
+ TQMap<YearRefPair, StatPair> weekStatMap; // Store Weeks count statistics.
+ TQMap<YearRefPair, StatPair> monthStatMap; // Store Month count statistics.
+ TQMap<int, StatPair> yearStatMap; // Store Years count statistics.
+
+ const KCalendarSystem *calendar;
+
+ TimeLineWidget::TimeUnit timeUnit;
+ TimeLineWidget::ScaleMode scaleMode;
+};
+
+TimeLineWidget::TimeLineWidget(TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new TimeLineWidgetPriv;
+ setBackgroundMode(TQt::NoBackground);
+ setMouseTracking(true);
+ setMinimumWidth(256);
+ setMinimumHeight(192);
+
+ TQDateTime ref = TQDateTime::currentDateTime();
+ setCursorDateTime(ref);
+ setRefDateTime(ref);
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+}
+
+TimeLineWidget::~TimeLineWidget()
+{
+ delete d;
+}
+
+void TimeLineWidget::setTimeUnit(TimeUnit timeUnit)
+{
+ d->timeUnit = timeUnit;
+ setCursorDateTime(cursorDateTime());
+ setRefDateTime(cursorDateTime());
+}
+
+TimeLineWidget::TimeUnit TimeLineWidget::timeUnit() const
+{
+ return d->timeUnit;
+}
+
+void TimeLineWidget::setScaleMode(ScaleMode scaleMode)
+{
+ d->scaleMode = scaleMode;
+ updatePixmap();
+ update();
+}
+
+TimeLineWidget::ScaleMode TimeLineWidget::scaleMode() const
+{
+ return d->scaleMode;
+}
+
+int TimeLineWidget::totalIndex()
+{
+ if (d->minDateTime.isNull() || d->maxDateTime.isNull())
+ return 0;
+
+ int i = 0;
+ TQDateTime dt = d->minDateTime;
+
+ do
+ {
+ dt = nextDateTime(dt);
+ i++;
+ }
+ while(dt < d->maxDateTime);
+
+ return i;
+}
+
+int TimeLineWidget::indexForDateTime(const TQDateTime& date)
+{
+ if (d->minDateTime.isNull() || d->maxDateTime.isNull() || date.isNull())
+ return 0;
+
+ int i = 0;
+ TQDateTime dt = d->minDateTime;
+
+ do
+ {
+ dt = nextDateTime(dt);
+ i++;
+ }
+ while(dt < date);
+
+ return i;
+}
+
+int TimeLineWidget::indexForRefDateTime()
+{
+ return (indexForDateTime(d->refDateTime));
+}
+
+int TimeLineWidget::indexForCursorDateTime()
+{
+ return (indexForDateTime(d->cursorDateTime));
+}
+
+void TimeLineWidget::setCurrentIndex(int index)
+{
+ if (d->minDateTime.isNull() || d->maxDateTime.isNull())
+ return;
+
+ int i = 0;
+ TQDateTime dt = d->minDateTime;
+
+ do
+ {
+ dt = nextDateTime(dt);
+ i++;
+ }
+ while(i <= index);
+
+ setRefDateTime(dt);
+}
+
+void TimeLineWidget::setCursorDateTime(const TQDateTime& dateTime)
+{
+ TQDateTime dt = dateTime;
+ dt.setTime(TQTime(0, 0, 0, 0));
+
+ switch(d->timeUnit)
+ {
+ case Week:
+ {
+ // Go to the first day of week.
+ int weekYear = 0;
+ int weekNb = d->calendar->weekNumber(dt.date(), &weekYear);
+ dt = firstDayOfWeek(weekYear, weekNb);
+ break;
+ }
+ case Month:
+ {
+ // Go to the first day of month.
+ dt.setDate(TQDate(dt.date().year(), dt.date().month(), 1));
+ break;
+ }
+ case Year:
+ {
+ // Go to the first day of year.
+ dt.setDate(TQDate(dt.date().year(), 1, 1));
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (d->cursorDateTime == dt)
+ return;
+
+ d->cursorDateTime = dt;
+
+ emit signalCursorPositionChanged();
+}
+
+TQDateTime TimeLineWidget::cursorDateTime() const
+{
+ return d->cursorDateTime;
+}
+
+int TimeLineWidget::cursorInfo(TQString& infoDate)
+{
+ SelectionMode selected;
+ TQDateTime dt = cursorDateTime();
+
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ infoDate = TDEGlobal::locale()->formatDate(dt.date());
+ break;
+ }
+ case Week:
+ {
+ infoDate = i18n("Week #%1 - %2 %3")
+ .arg(d->calendar->weekNumber(dt.date()))
+ .arg(d->calendar->monthName(dt.date()))
+ .arg(d->calendar->yearString(dt.date(), false));
+ break;
+ }
+ case Month:
+ {
+ infoDate = TQString("%1 %2")
+ .arg(d->calendar->monthName(dt.date()))
+ .arg(d->calendar->yearString(dt.date(), false));
+ break;
+ }
+ case Year:
+ {
+ infoDate = d->calendar->yearString(dt.date(), false);
+ break;
+ }
+ }
+
+ return statForDateTime(dt, selected);
+}
+
+void TimeLineWidget::setRefDateTime(const TQDateTime& dateTime)
+{
+ TQDateTime dt = dateTime;
+ dt.setTime(TQTime(0, 0, 0, 0));
+
+ switch(d->timeUnit)
+ {
+ case Week:
+ {
+ // Go to the first day of week.
+ int dayWeekOffset = (-1) * (d->calendar->dayOfWeek(dt.date()) - 1);
+ dt = dt.addDays(dayWeekOffset);
+ break;
+ }
+ case Month:
+ {
+ // Go to the first day of month.
+ dt.setDate(TQDate(dt.date().year(), dt.date().month(), 1));
+ break;
+ }
+ case Year:
+ {
+ // Go to the first day of year.
+ dt.setDate(TQDate(dt.date().year(), 1, 1));
+ break;
+ }
+ default:
+ break;
+ }
+
+ d->refDateTime = dt;
+ updatePixmap();
+ update();
+ emit signalRefDateTimeChanged();
+}
+
+void TimeLineWidget::slotResetSelection()
+{
+ resetSelection();
+ updatePixmap();
+ update();
+}
+
+void TimeLineWidget::resetSelection()
+{
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
+
+ for (it = d->dayStatMap.begin() ; it != d->dayStatMap.end(); ++it)
+ it.data().second = Unselected;
+
+ for (it = d->weekStatMap.begin() ; it != d->weekStatMap.end(); ++it)
+ it.data().second = Unselected;
+
+ for (it = d->monthStatMap.begin() ; it != d->monthStatMap.end(); ++it)
+ it.data().second = Unselected;
+
+ TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it2;
+
+ for (it2 = d->yearStatMap.begin() ; it2 != d->yearStatMap.end(); ++it2)
+ it2.data().second = Unselected;
+}
+
+void TimeLineWidget::setSelectedDateRange(const DateRangeList& list)
+{
+ if (list.isEmpty())
+ return;
+
+ resetSelection();
+
+ TQDateTime start, end, dt;
+ DateRangeList::const_iterator it;
+
+ for (it = list.begin() ; it != list.end(); ++it)
+ {
+ start = (*it).first;
+ end = (*it).second;
+ if (end > start)
+ {
+ dt = start;
+ do
+ {
+ setDateTimeSelected(dt, Selected);
+ dt = dt.addDays(1);
+ }
+ while (dt < end);
+ }
+ }
+
+ updatePixmap();
+ update();
+}
+
+DateRangeList TimeLineWidget::selectedDateRange(int& totalCount)
+{
+ // We will parse all selections done on Days stats map.
+
+ DateRangeList list;
+ totalCount = 0;
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it3;
+ TQDateTime sdt, edt;
+ TQDate date;
+
+ for (it3 = d->dayStatMap.begin() ; it3 != d->dayStatMap.end(); ++it3)
+ {
+ if (it3.data().second == Selected)
+ {
+ date = TQDate(it3.key().first, 1, 1);
+ date = date.addDays(it3.key().second-1);
+ sdt = TQDateTime(date);
+ edt = sdt.addDays(1);
+ list.append(DateRange(sdt, edt));
+ totalCount += it3.data().first;
+ }
+ }
+
+ DateRangeList::iterator it, it2;
+
+ /*
+ for (it = list.begin() ; it != list.end(); ++it)
+ DDebug() << (*it).first.date().toString(TQt::ISODate) << " :: "
+ << (*it).second.date().toString(TQt::ISODate) << endl;
+
+ DDebug() << "Total Count of Items = " << totalCount << endl;
+ */
+
+ // Group contiguous date ranges to optimize query on database.
+
+ DateRangeList list2;
+ TQDateTime first, second, first2, second2;
+
+ for (it = list.begin() ; it != list.end(); ++it)
+ {
+ first = (*it).first;
+ second = (*it).second;
+ it2 = it;
+ do
+ {
+ ++it2;
+ if (it2 != list.end())
+ {
+ first2 = (*it2).first;
+ second2 = (*it2).second;
+
+ if (first2 == second)
+ {
+ second = second2;
+ ++it;
+ }
+ else
+ break;
+ }
+ }
+ while(it2 != list.end());
+
+ list2.append(DateRange(first, second));
+ }
+
+ /*
+ for (it = list2.begin() ; it != list2.end(); ++it)
+ DDebug() << (*it).first.date().toString(TQt::ISODate) << " :: "
+ << (*it).second.date().toString(TQt::ISODate) << endl;
+ */
+
+ return list2;
+}
+
+void TimeLineWidget::slotDatesMap(const TQMap<TQDateTime, int>& datesStatMap)
+{
+ // Clear all counts in all stats maps before to update it. Do not clear selections.
+
+ TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it_iP;
+ for ( it_iP = d->yearStatMap.begin() ; it_iP != d->yearStatMap.end(); ++it_iP )
+ it_iP.data().first = 0;
+
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it_YP;
+ for ( it_YP = d->monthStatMap.begin() ; it_YP != d->monthStatMap.end(); ++it_YP )
+ it_YP.data().first = 0;
+
+ for ( it_YP = d->weekStatMap.begin() ; it_YP != d->weekStatMap.end(); ++it_YP )
+ it_YP.data().first = 0;
+
+ for ( it_YP = d->dayStatMap.begin() ; it_YP != d->dayStatMap.end(); ++it_YP )
+ it_YP.data().first = 0;
+
+ // Parse all new Date stamp and store histogram stats relevant in maps.
+
+ int count;
+ TQMap<TQDateTime, int>::const_iterator it;
+ if (datesStatMap.isEmpty())
+ {
+ d->minDateTime = TQDateTime();
+ d->maxDateTime = TQDateTime();
+ }
+ else
+ {
+ d->minDateTime = datesStatMap.begin().key();
+ d->maxDateTime = datesStatMap.begin().key();
+ }
+
+ for ( it = datesStatMap.begin(); it != datesStatMap.end(); ++it )
+ {
+ if (it.key() > d->maxDateTime)
+ d->maxDateTime = it.key();
+
+ if (it.key() < d->minDateTime)
+ d->minDateTime = it.key();
+
+ int year = it.key().date().year();
+ int month = it.key().date().month();
+ int day = d->calendar->dayOfYear(it.key().date());
+ int yearForWeek = year; // Used with week shared between 2 years decade (Dec/Jan).
+ int week = d->calendar->weekNumber(it.key().date(), &yearForWeek);
+
+ // Stats Years values.
+
+ it_iP = d->yearStatMap.find(year);
+ if ( it_iP == d->yearStatMap.end() )
+ {
+ count = it.data();
+ d->yearStatMap.insert( year, TimeLineWidgetPriv::StatPair(count, Unselected) );
+ }
+ else
+ {
+ count = it_iP.data().first + it.data();
+ d->yearStatMap.replace( year, TimeLineWidgetPriv::StatPair(count, it_iP.data().second) );
+ }
+
+ if (d->maxCountByYear < count)
+ d->maxCountByYear = count;
+
+ // Stats Months values.
+
+ it_YP = d->monthStatMap.find(TimeLineWidgetPriv::YearRefPair(year, month));
+ if ( it_YP == d->monthStatMap.end() )
+ {
+ count = it.data();
+ d->monthStatMap.insert( TimeLineWidgetPriv::YearRefPair(year, month),
+ TimeLineWidgetPriv::StatPair(count, Unselected) );
+ }
+ else
+ {
+ count = it_YP.data().first + it.data();
+ d->monthStatMap.replace( TimeLineWidgetPriv::YearRefPair(year, month),
+ TimeLineWidgetPriv::StatPair(count, it_YP.data().second) );
+ }
+
+ if (d->maxCountByMonth < count)
+ d->maxCountByMonth = count;
+
+ // Stats Weeks values.
+
+ it_YP = d->weekStatMap.find(TimeLineWidgetPriv::YearRefPair(yearForWeek, week));
+ if ( it_YP == d->weekStatMap.end() )
+ {
+ count = it.data();
+ d->weekStatMap.insert( TimeLineWidgetPriv::YearRefPair(yearForWeek, week),
+ TimeLineWidgetPriv::StatPair(count, Unselected) );
+ }
+ else
+ {
+ count = it_YP.data().first + it.data();
+ d->weekStatMap.replace( TimeLineWidgetPriv::YearRefPair(yearForWeek, week),
+ TimeLineWidgetPriv::StatPair(count, it_YP.data().second) );
+ }
+
+ if (d->maxCountByWeek < count)
+ d->maxCountByWeek = count;
+
+ // Stats Days values.
+
+ it_YP = d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
+ if ( it_YP == d->dayStatMap.end() )
+ {
+ count = it.data();
+ d->dayStatMap.insert( TimeLineWidgetPriv::YearRefPair(year, day),
+ TimeLineWidgetPriv::StatPair(count, Unselected) );
+ }
+ else
+ {
+ count = it_YP.data().first + it.data();
+ d->dayStatMap.replace( TimeLineWidgetPriv::YearRefPair(year, day),
+ TimeLineWidgetPriv::StatPair(count, it_YP.data().second) );
+ }
+
+ if (d->maxCountByDay < count)
+ d->maxCountByDay = count;
+ }
+
+ if (!datesStatMap.isEmpty())
+ {
+ d->maxDateTime.setTime(TQTime(0, 0, 0, 0));
+ d->minDateTime.setTime(TQTime(0, 0, 0, 0));
+ }
+ else
+ {
+ d->maxDateTime = d->refDateTime;
+ d->minDateTime = d->refDateTime;
+ }
+
+ updatePixmap();
+ update();
+ emit signalDateMapChanged();
+}
+
+void TimeLineWidget::updatePixmap()
+{
+ // Drawing background and image.
+ d->pixmap = TQPixmap(size());
+ d->pixmap.fill(palette().active().background());
+
+ TQPainter p(&d->pixmap);
+
+ d->bottomMargin = (int)(p.fontMetrics().height()*1.5);
+ d->barWidth = p.fontMetrics().width("00");
+ d->nbItems = (int)((width() / 2.0) / (float)d->barWidth);
+ d->startPos = (int)((width() / 2.0) - ((float)(d->barWidth) / 2.0));
+ int dim = height() - d->bottomMargin - d->topMargin;
+ TQDateTime ref = d->refDateTime;
+ ref.setTime(TQTime(0, 0, 0, 0));
+ double max, logVal;
+ int val, top;
+ SelectionMode sel;
+ TQRect focusRect, selRect, barRect;
+ TQBrush selBrush;
+ TQColor dateColor, subDateColor;
+
+ // Date histogram drawing is divided in 2 parts. The current date-time
+ // is placed on the center of the view and all dates on right are computed,
+ // and in second time, all dates on the left.
+
+ // Draw all dates on the right of ref. date-time.
+
+ for (int i = 0 ; i < d->nbItems ; i++)
+ {
+ val = statForDateTime(ref, sel);
+ max = (double)maxCount();
+
+ if (d->scaleMode == TimeLineWidget::LogScale)
+ {
+ if (max > 0.0) max = log(max);
+ else max = 1.0;
+
+ if (val <= 0) logVal = 0;
+ else logVal = log(val);
+
+ top = lround(dim + d->topMargin - ((logVal * dim) / max));
+
+ if (top < 0) val = 0;
+ }
+ else
+ {
+ top = lround(dim + d->topMargin - ((val * dim) / max));
+ }
+
+ barRect.setTop(top);
+ barRect.setLeft(d->startPos + i*d->barWidth);
+ barRect.setBottom(height() - d->bottomMargin);
+ barRect.setRight(d->startPos + (i+1)*d->barWidth);
+
+ if (ref == d->cursorDateTime)
+ focusRect = barRect;
+
+ if (ref > d->maxDateTime)
+ dateColor = palette().active().mid();
+ else
+ dateColor = palette().active().foreground();
+
+ p.setPen(palette().active().foreground());
+ p.fillRect(barRect, TQBrush(ThemeEngine::instance()->textSpecialRegColor()));
+ p.drawRect(barRect);
+ p.drawLine(barRect.right(), barRect.bottom(), barRect.right(), barRect.bottom()+3);
+ p.drawLine(barRect.left(), barRect.bottom(), barRect.left(), barRect.bottom()+3);
+
+ if (val)
+ {
+ if (sel)
+ subDateColor = palette().active().highlightedText();
+ else
+ subDateColor = palette().active().foreground();
+ }
+ else
+ subDateColor = palette().active().mid();
+
+ if (sel == Selected || sel == FuzzySelection)
+ {
+ selBrush.setColor(ThemeEngine::instance()->thumbSelColor());
+ selBrush.setStyle(TQBrush::SolidPattern);
+ if (sel == FuzzySelection)
+ selBrush.setStyle(TQBrush::Dense4Pattern);
+
+ selRect.setTop(height() - d->bottomMargin + 1);
+ selRect.setLeft(d->startPos + i*d->barWidth);
+ selRect.setBottom(height() - d->bottomMargin/2);
+ selRect.setRight(d->startPos + (i+1)*d->barWidth);
+ p.fillRect(selRect, selBrush);
+ }
+
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ {
+ p.save();
+ TQFont fnt = p.font();
+ fnt.setPointSize(fnt.pointSize()-4);
+ p.setFont(fnt);
+ p.setPen(subDateColor);
+ TQString txt = TQString(d->calendar->weekDayName(ref.date(), true)[0]);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
+ barRect.bottom()+br.height(), txt);
+ p.restore();
+ }
+
+ if (d->calendar->dayOfWeek(ref.date()) == 1)
+ {
+ p.setPen(dateColor);
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ break;
+ }
+ case Week:
+ {
+ int week = d->calendar->weekNumber(ref.date());
+ {
+ p.save();
+ TQFont fnt = p.font();
+ fnt.setPointSize(fnt.pointSize()-4);
+ p.setFont(fnt);
+ p.setPen(subDateColor);
+ TQString txt = TQString::number(week);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
+ barRect.bottom()+br.height(), txt);
+ p.restore();
+ }
+
+ p.setPen(dateColor);
+ if (week == 1 || week == 10 || week == 20 || week == 30 || week == 40 || week == 50)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ if (week != 50)
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ else if (week == 6 || week == 16 || week == 26 || week == 36 || week == 46)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/4);
+ }
+ break;
+ }
+ case Month:
+ {
+ {
+ p.save();
+ TQFont fnt = p.font();
+ fnt.setPointSize(fnt.pointSize()-4);
+ p.setFont(fnt);
+ p.setPen(subDateColor) ;
+ TQString txt = TQString(d->calendar->monthName(ref.date(), true)[0]);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
+ barRect.bottom()+br.height(), txt);
+ p.restore();
+ }
+
+ p.setPen(dateColor);
+ if (ref.date().month() == 1)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TQString::number(ref.date().year());
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ else if (ref.date().month() == 7)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/4);
+ }
+ break;
+ }
+ case Year:
+ {
+ p.setPen(dateColor);
+ if (ref.date().year() % 10 == 0)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TQString::number(ref.date().year());
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ else if (ref.date().year() % 5 == 0)
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/4);
+ break;
+ }
+ }
+
+ ref = nextDateTime(ref);
+ }
+
+ // Draw all dates on the left of ref. date-time.
+
+ ref = d->refDateTime;
+ ref.setTime(TQTime(0, 0, 0, 0));
+ ref = prevDateTime(ref);
+
+ for (int i = 0 ; i < d->nbItems-1 ; i++)
+ {
+ val = statForDateTime(ref, sel);
+ max = (double)maxCount();
+
+ if (d->scaleMode == TimeLineWidget::LogScale)
+ {
+ if (max > 0.0) max = log(max);
+ else max = 1.0;
+
+ if (val <= 0) logVal = 0;
+ else logVal = log(val);
+
+ top = lround(dim + d->topMargin - ((logVal * dim) / max));
+
+ if (top < 0) val = 0;
+ }
+ else
+ {
+ top = lround(dim + d->topMargin - ((val * dim) / max));
+ }
+
+ barRect.setTop(top);
+ barRect.setRight(d->startPos - i*d->barWidth);
+ barRect.setBottom(height() - d->bottomMargin);
+ barRect.setLeft(d->startPos - (i+1)*d->barWidth);
+
+ if (ref == d->cursorDateTime)
+ focusRect = barRect;
+
+ if (ref < d->minDateTime)
+ dateColor = palette().active().mid();
+ else
+ dateColor = palette().active().foreground();
+
+ p.setPen(palette().active().foreground());
+ p.fillRect(barRect, TQBrush(ThemeEngine::instance()->textSpecialRegColor()));
+ p.drawRect(barRect);
+ p.drawLine(barRect.right(), barRect.bottom(), barRect.right(), barRect.bottom()+3);
+ p.drawLine(barRect.left(), barRect.bottom(), barRect.left(), barRect.bottom()+3);
+
+ if (val)
+ {
+ if (sel)
+ subDateColor = palette().active().highlightedText();
+ else
+ subDateColor = palette().active().foreground();
+ }
+ else
+ subDateColor = palette().active().mid();
+
+ if (sel == Selected || sel == FuzzySelection)
+ {
+ selBrush.setColor(ThemeEngine::instance()->thumbSelColor());
+ selBrush.setStyle(TQBrush::SolidPattern);
+ if (sel == FuzzySelection)
+ selBrush.setStyle(TQBrush::Dense4Pattern);
+
+ selRect.setTop(height() - d->bottomMargin + 1);
+ selRect.setLeft(d->startPos - (i+1)*d->barWidth);
+ selRect.setBottom(height() - d->bottomMargin/2);
+ selRect.setRight(d->startPos - i*d->barWidth);
+ p.fillRect(selRect, selBrush);
+ }
+
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ {
+ p.save();
+ TQFont fnt = p.font();
+ fnt.setPointSize(fnt.pointSize()-4);
+ p.setFont(fnt);
+ p.setPen(subDateColor) ;
+ TQString txt = TQString(d->calendar->weekDayName(ref.date(), true)[0]);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
+ barRect.bottom()+br.height(), txt);
+ p.restore();
+ }
+
+ if (d->calendar->dayOfWeek(ref.date()) == 1)
+ {
+ p.setPen(dateColor);
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ break;
+ }
+ case Week:
+ {
+ int week = d->calendar->weekNumber(ref.date());
+ {
+ p.save();
+ TQFont fnt = p.font();
+ fnt.setPointSize(fnt.pointSize()-4);
+ p.setFont(fnt);
+ p.setPen(subDateColor) ;
+ TQString txt = TQString::number(week);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
+ barRect.bottom()+br.height(), txt);
+ p.restore();
+ }
+
+ p.setPen(dateColor);
+ if (week == 1 || week == 10 || week == 20 || week == 30 || week == 40 || week == 50)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ if (week != 50)
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ else if (week == 6 || week == 16 || week == 26 || week == 36 || week == 46)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/4);
+ }
+ break;
+ }
+ case Month:
+ {
+ {
+ p.save();
+ TQFont fnt = p.font();
+ fnt.setPointSize(fnt.pointSize()-4);
+ p.setFont(fnt);
+ p.setPen(subDateColor) ;
+ TQString txt = TQString(d->calendar->monthName(ref.date(), true)[0]);
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
+ barRect.bottom()+br.height(), txt);
+ p.restore();
+ }
+
+ p.setPen(dateColor);
+ if (ref.date().month() == 1)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TQString::number(ref.date().year());
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ else if (ref.date().month() == 7)
+ {
+ p.drawLine(barRect.right(), barRect.bottom(),
+ barRect.right(), barRect.bottom()+d->bottomMargin/4);
+ }
+ break;
+ }
+ case Year:
+ {
+ p.setPen(dateColor);
+ if (ref.date().year() % 10 == 0)
+ {
+ p.drawLine(barRect.left(), barRect.bottom(),
+ barRect.left(), barRect.bottom()+d->bottomMargin/2);
+ TQString txt = TQString::number(ref.date().year());
+ TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
+ p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
+ }
+ else if (ref.date().year() % 5 == 0)
+ p.drawLine(barRect.right(), barRect.bottom(),
+ barRect.right(), barRect.bottom()+d->bottomMargin/4);
+ break;
+ }
+ }
+
+ ref = prevDateTime(ref);
+ }
+
+ // Draw cursor rectangle over current date-time.
+ if (focusRect.isValid())
+ {
+ focusRect.setTop(d->topMargin);
+ TQPoint p1(focusRect.left(), height() - d->bottomMargin);
+ TQPoint p2(focusRect.right(), height() - d->bottomMargin);
+ focusRect.setBottom(focusRect.bottom() + d->bottomMargin/2);
+
+ p.setPen(palette().active().shadow());
+ p.drawLine(p1.x(), p1.y()+1, p2.x(), p2.y()+1);
+ p.drawRect(focusRect);
+
+ focusRect.addCoords(-1,-1, 1, 1);
+ p.setPen(ThemeEngine::instance()->textSelColor());
+ p.drawRect(focusRect);
+ p.drawLine(p1.x()-1, p1.y(), p2.x()+1, p2.y());
+
+ focusRect.addCoords(-1,-1, 1, 1);
+ p.drawRect(focusRect);
+ p.drawLine(p1.x()-1, p1.y()-1, p2.x()+1, p2.y()-1);
+
+ focusRect.addCoords(-1,-1, 1, 1);
+ p.setPen(palette().active().shadow());
+ p.drawRect(focusRect);
+ p.drawLine(p1.x(), p1.y()-2, p2.x(), p2.y()-2);
+ }
+ p.end();
+}
+
+TQDateTime TimeLineWidget::prevDateTime(const TQDateTime& dt)
+{
+ TQDateTime prev;
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ prev = dt.addDays(-1);
+ break;
+ }
+ case Week:
+ {
+ prev = dt.addDays(-7);
+ break;
+ }
+ case Month:
+ {
+ prev = dt.addMonths(-1);
+ break;
+ }
+ case Year:
+ {
+ prev = dt.addYears(-1);
+ break;
+ }
+ }
+ return prev;
+}
+
+TQDateTime TimeLineWidget::nextDateTime(const TQDateTime& dt)
+{
+ TQDateTime next;
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ next = dt.addDays(1);
+ break;
+ }
+ case Week:
+ {
+ next = dt.addDays(7);
+ break;
+ }
+ case Month:
+ {
+ next = dt.addMonths(1);
+ break;
+ }
+ case Year:
+ {
+ next = dt.addYears(1);
+ break;
+ }
+ }
+ return next;
+}
+
+int TimeLineWidget::maxCount()
+{
+ int max = 1;
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ max = d->maxCountByDay;
+ break;
+ }
+ case Week:
+ {
+ max = d->maxCountByWeek;
+ break;
+ }
+ case Month:
+ {
+ max = d->maxCountByMonth;
+ break;
+ }
+ case Year:
+ {
+ max = d->maxCountByYear;
+ break;
+ }
+ }
+ return max;
+}
+
+int TimeLineWidget::statForDateTime(const TQDateTime& dt, SelectionMode& selected)
+{
+ int count = 0;
+ int year = dt.date().year();
+ int month = dt.date().month();
+ int day = d->calendar->dayOfYear(dt.date());
+ int yearForWeek; // Used with week shared between 2 years decade (Dec/Jan).
+ int week = d->calendar->weekNumber(dt.date(), &yearForWeek);
+ selected = Unselected;
+
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it =
+ d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
+ if ( it != d->dayStatMap.end() )
+ {
+ count = it.data().first;
+ selected = it.data().second;
+ }
+ break;
+ }
+ case Week:
+ {
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it =
+ d->weekStatMap.find(TimeLineWidgetPriv::YearRefPair(yearForWeek, week));
+ if ( it != d->weekStatMap.end() )
+ {
+ count = it.data().first;
+ selected = it.data().second;
+ }
+ break;
+ }
+ case Month:
+ {
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it =
+ d->monthStatMap.find(TimeLineWidgetPriv::YearRefPair(year, month));
+ if ( it != d->monthStatMap.end() )
+ {
+ count = it.data().first;
+ selected = it.data().second;
+ }
+ break;
+ }
+ case Year:
+ {
+ TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it = d->yearStatMap.find(year);
+ if ( it != d->yearStatMap.end() )
+ {
+ count = it.data().first;
+ selected = it.data().second;
+ }
+ break;
+ }
+ }
+
+ return count;
+}
+
+void TimeLineWidget::setDateTimeSelected(const TQDateTime& dt, SelectionMode selected)
+{
+ int year = dt.date().year();
+ int month = dt.date().month();
+ int yearForWeek; // Used with week shared between 2 years decade (Dec/Jan).
+ int week = d->calendar->weekNumber(dt.date(), &yearForWeek);
+
+ TQDateTime dts, dte;
+
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ dts = dt;
+ dte = dts.addDays(1);
+ setDaysRangeSelection(dts, dte, selected);
+ break;
+ }
+ case Week:
+ {
+ dts = firstDayOfWeek(yearForWeek, week);
+ dte = dts.addDays(7);
+ setDaysRangeSelection(dts, dte, selected);
+ updateWeekSelection(dts, dte);
+ break;
+ }
+ case Month:
+ {
+ dts = TQDateTime(TQDate(year, month, 1));
+ dte = dts.addDays(d->calendar->daysInMonth(dts.date()));
+ setDaysRangeSelection(dts, dte, selected);
+ updateMonthSelection(dts, dte);
+ break;
+ }
+ case Year:
+ {
+ dts = TQDateTime(TQDate(year, 1, 1));
+ dte = dts.addDays(d->calendar->daysInYear(dts.date()));
+ setDaysRangeSelection(dts, dte, selected);
+ updateYearSelection(dts, dte);
+ break;
+ }
+ }
+}
+
+void TimeLineWidget::updateWeekSelection(const TQDateTime dts, const TQDateTime dte)
+{
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
+ TQDateTime dtsWeek, dteWeek, dt;
+ int week;
+ int yearForWeek; // Used with week shared between 2 years decade (Dec/Jan).
+ dt = dts;
+ do
+ {
+ yearForWeek = dt.date().year();
+ week = d->calendar->weekNumber(dt.date(), &yearForWeek);
+
+ dtsWeek = firstDayOfWeek(yearForWeek, week);
+ dteWeek = dtsWeek.addDays(7);
+ it = d->weekStatMap.find(TimeLineWidgetPriv::YearRefPair(yearForWeek, week));
+ if ( it != d->weekStatMap.end() )
+ it.data().second = checkSelectionForDaysRange(dtsWeek, dteWeek);
+
+ dt = dt.addDays(7);
+ }
+ while (dt <= dte);
+}
+
+void TimeLineWidget::updateMonthSelection(const TQDateTime dts, const TQDateTime dte)
+{
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
+ TQDateTime dtsMonth, dteMonth, dt;
+ int year, month;
+ dt = dts;
+ do
+ {
+ year = dt.date().year();
+ month = dt.date().month();
+
+ dtsMonth = TQDateTime(TQDate(year, month, 1));
+ dteMonth = dtsMonth.addDays(d->calendar->daysInMonth(dtsMonth.date()));
+ it = d->monthStatMap.find(TimeLineWidgetPriv::YearRefPair(year, month));
+ if ( it != d->monthStatMap.end() )
+ it.data().second = checkSelectionForDaysRange(dtsMonth, dteMonth);
+
+ dt = dteMonth;
+ }
+ while (dt <= dte);
+}
+
+void TimeLineWidget::updateYearSelection(const TQDateTime dts, const TQDateTime dte)
+{
+ TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it;
+ TQDateTime dtsYear, dteYear, dt;
+ int year;
+ dt = dts;
+ do
+ {
+ year = dt.date().year();
+
+ dtsYear = TQDateTime(TQDate(year, 1, 1));
+ dteYear = dtsYear.addDays(d->calendar->daysInYear(dtsYear.date()));
+ it = d->yearStatMap.find(year);
+ if ( it != d->yearStatMap.end() )
+ it.data().second = checkSelectionForDaysRange(dtsYear, dteYear);
+
+ dt = dteYear;
+ }
+ while (dt <= dte);
+}
+
+void TimeLineWidget::updateAllSelection()
+{
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
+ TQDateTime dts, dte;
+ TQDate date;
+
+ for (it = d->dayStatMap.begin() ; it != d->dayStatMap.end(); ++it)
+ {
+ if (it.data().second == Selected)
+ {
+ date = TQDate(it.key().first, 1, 1);
+ date = date.addDays(it.key().second-1);
+ dts = TQDateTime(date);
+ dte = dts.addDays(1);
+ updateWeekSelection(dts, dte);
+ updateMonthSelection(dts, dte);
+ updateYearSelection(dts, dte);
+ }
+ }
+}
+
+void TimeLineWidget::setDaysRangeSelection(const TQDateTime dts, const TQDateTime dte, SelectionMode selected)
+{
+ int year, day;
+ TQDateTime dt = dts;
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
+
+ do
+ {
+ year = dt.date().year();
+ day = d->calendar->dayOfYear(dt.date());
+ it = d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
+ if ( it != d->dayStatMap.end() )
+ it.data().second = selected;
+
+ dt = dt.addDays(1);
+ }
+ while(dt < dte);
+}
+
+TimeLineWidget::SelectionMode TimeLineWidget::checkSelectionForDaysRange(const TQDateTime dts, const TQDateTime dte)
+{
+ int year, day;
+ int items = 0;
+ int itemsFuz = 0;
+ int itemsSel = 0;
+ TQDateTime dt = dts;
+ TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
+
+ do
+ {
+ year = dt.date().year();
+ day = d->calendar->dayOfYear(dt.date());
+
+ it = d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
+ if ( it != d->dayStatMap.end() )
+ {
+ items++;
+
+ if (it.data().second != Unselected)
+ {
+ if (it.data().second == FuzzySelection)
+ itemsFuz++;
+ else
+ itemsSel++;
+ }
+ }
+ dt = dt.addDays(1);
+ }
+ while (dt < dte);
+
+ if (items == 0)
+ return Unselected;
+
+ if (itemsFuz == 0 && itemsSel == 0)
+ return Unselected;
+
+ if (itemsFuz > 0)
+ return FuzzySelection;
+
+ if (items > itemsSel)
+ return FuzzySelection;
+
+ return Selected;
+}
+
+void TimeLineWidget::paintEvent(TQPaintEvent*)
+{
+ bitBlt(this, 0, 0, &d->pixmap);
+}
+
+void TimeLineWidget::resizeEvent(TQResizeEvent*)
+{
+ updatePixmap();
+}
+
+void TimeLineWidget::slotBackward()
+{
+ TQDateTime ref = d->refDateTime;
+
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ for (int i = 0; i < 7; i++)
+ ref = prevDateTime(ref);
+ break;
+ }
+ case Week:
+ {
+ for (int i = 0; i < 4; i++)
+ ref = prevDateTime(ref);
+ break;
+ }
+ case Month:
+ {
+ for (int i = 0; i < 12; i++)
+ ref = prevDateTime(ref);
+ break;
+ }
+ case Year:
+ {
+ for (int i = 0; i < 5; i++)
+ ref = prevDateTime(ref);
+ break;
+ }
+ }
+
+ if (ref < d->minDateTime)
+ ref = d->minDateTime;
+
+ setRefDateTime(ref);
+}
+
+void TimeLineWidget::slotPrevious()
+{
+ if (d->refDateTime <= d->minDateTime)
+ return;
+ TQDateTime ref = prevDateTime(d->refDateTime);
+ setRefDateTime(ref);
+}
+
+void TimeLineWidget::slotNext()
+{
+ if (d->refDateTime >= d->maxDateTime)
+ return;
+ TQDateTime ref = nextDateTime(d->refDateTime);
+ setRefDateTime(ref);
+}
+
+void TimeLineWidget::slotForward()
+{
+ TQDateTime ref = d->refDateTime;
+
+ switch(d->timeUnit)
+ {
+ case Day:
+ {
+ for (int i = 0; i < 7; i++)
+ ref = nextDateTime(ref);
+ break;
+ }
+ case Week:
+ {
+ for (int i = 0; i < 4; i++)
+ ref = nextDateTime(ref);
+ break;
+ }
+ case Month:
+ {
+ for (int i = 0; i < 12; i++)
+ ref = nextDateTime(ref);
+ break;
+ }
+ case Year:
+ {
+ for (int i = 0; i < 5; i++)
+ ref = nextDateTime(ref);
+ break;
+ }
+ }
+
+ if (ref > d->maxDateTime)
+ ref = d->maxDateTime;
+
+ setRefDateTime(ref);
+}
+
+void TimeLineWidget::wheelEvent(TQWheelEvent* e)
+{
+ if (e->delta() < 0)
+ {
+ if (e->state() & TQt::ShiftButton)
+ slotForward();
+ else
+ slotNext();
+ }
+
+ if (e->delta() > 0)
+ {
+ if (e->state() & TQt::ShiftButton)
+ slotBackward();
+ else
+ slotPrevious();
+ }
+}
+
+void TimeLineWidget::mousePressEvent(TQMouseEvent *e)
+{
+ if (e->button() == TQt::LeftButton)
+ {
+ TQPoint pt(e->x(), e->y());
+
+ bool ctrlPressed = e->state() & TQt::ControlButton;
+ TQDateTime ref = dateTimeForPoint(pt, d->selMouseEvent);
+ d->selStartDateTime = TQDateTime();
+ if (d->selMouseEvent)
+ {
+ if (!ctrlPressed)
+ resetSelection();
+
+ d->selStartDateTime = ref;
+ d->selMinDateTime = ref;
+ d->selMaxDateTime = ref;
+ setDateTimeSelected(ref, Selected);
+ }
+
+ if (!ref.isNull())
+ setCursorDateTime(ref);
+
+ d->validMouseEvent = true;
+ updatePixmap();
+ update();
+ }
+}
+
+void TimeLineWidget::mouseMoveEvent(TQMouseEvent *e)
+{
+ if (d->validMouseEvent == true)
+ {
+ TQPoint pt(e->x(), e->y());
+
+ bool sel;
+ TQDateTime selEndDateTime = dateTimeForPoint(pt, sel);
+ setCursorDateTime(selEndDateTime);
+
+ // Clamp start and end date-time of current contiguous selection.
+
+ if (!selEndDateTime.isNull() && !d->selStartDateTime.isNull())
+ {
+ if (selEndDateTime > d->selStartDateTime &&
+ selEndDateTime > d->selMaxDateTime)
+ {
+ d->selMaxDateTime = selEndDateTime;
+ }
+ else if (selEndDateTime < d->selStartDateTime &&
+ selEndDateTime < d->selMinDateTime)
+ {
+ d->selMinDateTime = selEndDateTime;
+ }
+
+ TQDateTime dt = d->selMinDateTime;
+ do
+ {
+ setDateTimeSelected(dt, Unselected);
+ dt = nextDateTime(dt);
+ }
+ while(dt <= d->selMaxDateTime);
+ }
+
+ // Now perform selections on Date Maps.
+
+ if (d->selMouseEvent)
+ {
+ if (!d->selStartDateTime.isNull() && !selEndDateTime.isNull())
+ {
+ TQDateTime dt = d->selStartDateTime;
+ if (selEndDateTime > d->selStartDateTime)
+ {
+ do
+ {
+ setDateTimeSelected(dt, Selected);
+ dt = nextDateTime(dt);
+ }
+ while(dt <= selEndDateTime);
+ }
+ else
+ {
+ do
+ {
+ setDateTimeSelected(dt, Selected);
+ dt = prevDateTime(dt);
+ }
+ while(dt >= selEndDateTime);
+ }
+ }
+ }
+
+ updatePixmap();
+ update();
+ }
+}
+
+void TimeLineWidget::mouseReleaseEvent(TQMouseEvent*)
+{
+ d->validMouseEvent = false;
+
+ // Only dispatch changes about selection when user release mouse selection
+ // to prevent multiple queries on database.
+ if (d->selMouseEvent)
+ {
+ updateAllSelection();
+ emit signalSelectionChanged();
+ }
+
+ d->selMouseEvent = false;
+}
+
+TQDateTime TimeLineWidget::dateTimeForPoint(const TQPoint& pt, bool &isOnSelectionArea)
+{
+ TQRect barRect, selRect;
+ isOnSelectionArea = false;
+
+ // Check on the right of reference date.
+
+ TQDateTime ref = d->refDateTime;
+ ref.setTime(TQTime(0, 0, 0, 0));
+
+ TQRect deskRect = TDEGlobalSettings::desktopGeometry(this);
+ int items = deskRect.width() / d->barWidth;
+
+ for (int i = 0 ; i < items ; i++)
+ {
+ barRect.setTop(0);
+ barRect.setLeft(d->startPos + i*d->barWidth);
+ barRect.setBottom(height() - d->bottomMargin + 1);
+ barRect.setRight(d->startPos + (i+1)*d->barWidth);
+
+ selRect.setTop(height() - d->bottomMargin + 1);
+ selRect.setLeft(d->startPos + i*d->barWidth);
+ selRect.setBottom(height());
+ selRect.setRight(d->startPos + (i+1)*d->barWidth);
+
+ if (selRect.contains(pt))
+ isOnSelectionArea = true;
+
+ if (barRect.contains(pt) || selRect.contains(pt))
+ {
+ if (i >= d->nbItems)
+ {
+ // Point is outside visible widget area. We scrolling widget contents.
+ slotNext();
+ }
+
+ return ref;
+ }
+
+ ref = nextDateTime(ref);
+ }
+
+ // Check on the left of reference date.
+
+ ref = d->refDateTime;
+ ref.setTime(TQTime(0, 0, 0, 0));
+ ref = prevDateTime(ref);
+
+ for (int i = 0 ; i < items ; i++)
+ {
+ barRect.setTop(0);
+ barRect.setRight(d->startPos - i*d->barWidth);
+ barRect.setBottom(height() - d->bottomMargin + 1);
+ barRect.setLeft(d->startPos - (i+1)*d->barWidth);
+
+ selRect.setTop(height() - d->bottomMargin + 1);
+ selRect.setLeft(d->startPos - (i+1)*d->barWidth);
+ selRect.setBottom(height());
+ selRect.setRight(d->startPos - i*d->barWidth);
+
+ if (selRect.contains(pt))
+ isOnSelectionArea = true;
+
+ if (barRect.contains(pt) || selRect.contains(pt))
+ {
+ if (i >= d->nbItems-1)
+ {
+ // Point is outside visible widget area. We scrolling widget contents.
+ slotPrevious();
+ }
+
+ return ref;
+ }
+
+ ref = prevDateTime(ref);
+ }
+
+ return TQDateTime();
+}
+
+TQDateTime TimeLineWidget::firstDayOfWeek(int year, int weekNumber)
+{
+ // Search the first day of first week of year.
+ // We start to scan from 1st december of year-1 because
+ // first week of year OR last week of year-1 can be shared
+ // between year-1 and year.
+ TQDateTime d1(TQDate(year-1, 12, 1));
+ TQDateTime dt = d1;
+ int weekYear = 0;
+ int weekNum = 0;
+
+ do
+ {
+ dt = dt.addDays(1);
+ weekNum = d->calendar->weekNumber(dt.date(), &weekYear);
+ }
+ while(weekNum != 1 && weekYear != year);
+
+ dt = dt.addDays((weekNumber-1)*7);
+
+/*
+ DDebug() << "Year= " << year << " Week= " << weekNumber
+ << " 1st day= " << dt << endl;
+*/
+
+ return dt;
+}
+
+void TimeLineWidget::slotThemeChanged()
+{
+ updatePixmap();
+ update();
+}
+
+} // NameSpace Digikam
diff --git a/src/digikam/timelinewidget.h b/src/digikam/timelinewidget.h
new file mode 100644
index 00000000..33aeb7ea
--- /dev/null
+++ b/src/digikam/timelinewidget.h
@@ -0,0 +1,154 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-12-08
+ * Description : a widget to display date and time statistics of pictures
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TIMELINEWIDGET_H
+#define TIMELINEWIDGET_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqwidget.h>
+#include <tqdatetime.h>
+
+// Local includes.
+
+#include "ddebug.h"
+
+namespace Digikam
+{
+
+typedef TQPair<TQDateTime, TQDateTime> DateRange; // Range of a contigue dates selection <start date, end date>.
+typedef TQValueList<DateRange> DateRangeList; // List of dates range selected.
+
+class TimeLineWidgetPriv;
+
+class TimeLineWidget : public TQWidget
+{
+TQ_OBJECT
+
+public:
+
+ enum TimeUnit
+ {
+ Day = 0,
+ Week,
+ Month,
+ Year
+ };
+
+ enum SelectionMode
+ {
+ Unselected=0, // No selection.
+ FuzzySelection, // Partially selected.
+ Selected // Fully selected.
+ };
+
+ enum ScaleMode
+ {
+ LinScale=0, // Linear scale.
+ LogScale // Logarithmic scale.
+ };
+
+public:
+
+ TimeLineWidget(TQWidget *parent=0);
+ ~TimeLineWidget();
+
+ void setTimeUnit(TimeUnit timeUnit);
+ TimeUnit timeUnit() const;
+
+ void setScaleMode(ScaleMode scaleMode);
+ ScaleMode scaleMode() const;
+
+ void setCursorDateTime(const TQDateTime& dateTime);
+ TQDateTime cursorDateTime() const;
+ int cursorInfo(TQString& infoDate);
+
+ /** Return a list of Date-Range based on selection performed on days-map */
+ DateRangeList selectedDateRange(int& totalCount);
+ void setSelectedDateRange(const DateRangeList& list);
+
+ int totalIndex();
+ int indexForRefDateTime();
+ int indexForCursorDateTime();
+ void setCurrentIndex(int index);
+
+signals:
+
+ void signalCursorPositionChanged();
+ void signalSelectionChanged();
+ void signalRefDateTimeChanged();
+ void signalDateMapChanged();
+
+public slots:
+
+ void slotDatesMap(const TQMap<TQDateTime, int>&);
+ void slotPrevious();
+ void slotNext();
+ void slotBackward();
+ void slotForward();
+ void slotResetSelection();
+
+private slots:
+
+ void slotThemeChanged();
+
+private:
+
+ TQDateTime prevDateTime(const TQDateTime& dt);
+ TQDateTime nextDateTime(const TQDateTime& dt);
+
+ int maxCount();
+ int indexForDateTime(const TQDateTime& date);
+ int statForDateTime(const TQDateTime& dt, SelectionMode& selected);
+ void setRefDateTime(const TQDateTime& dateTime);
+
+ void updatePixmap();
+ void paintEvent(TQPaintEvent*);
+ void resizeEvent(TQResizeEvent*);
+ void wheelEvent(TQWheelEvent*);
+
+ void mousePressEvent(TQMouseEvent*);
+ void mouseMoveEvent(TQMouseEvent*);
+ void mouseReleaseEvent(TQMouseEvent*);
+
+ TQDateTime dateTimeForPoint(const TQPoint& pt, bool &isOnSelectionArea);
+ TQDateTime firstDayOfWeek(int year, int weekNumber);
+
+ void resetSelection();
+ void setDateTimeSelected(const TQDateTime& dt, SelectionMode selected);
+ void setDaysRangeSelection(const TQDateTime dts, const TQDateTime dte, SelectionMode selected);
+ SelectionMode checkSelectionForDaysRange(const TQDateTime dts, const TQDateTime dte);
+ void updateWeekSelection(const TQDateTime dts, const TQDateTime dte);
+ void updateMonthSelection(const TQDateTime dts, const TQDateTime dte);
+ void updateYearSelection(const TQDateTime dts, const TQDateTime dte);
+ void updateAllSelection();
+
+private:
+
+ TimeLineWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif // TIMELINEWIDGET_H
diff --git a/src/digikam/upgradedb_sqlite2tosqlite3.cpp b/src/digikam/upgradedb_sqlite2tosqlite3.cpp
new file mode 100644
index 00000000..b9156349
--- /dev/null
+++ b/src/digikam/upgradedb_sqlite2tosqlite3.cpp
@@ -0,0 +1,609 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-05
+ * Description : SQlite 2 to SQlite 3 interface.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqpair.h>
+#include <tqdir.h>
+#include <tqstringlist.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <tdeio/global.h>
+#include <iostream>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albumdb.h"
+#include "albumdb_sqlite2.h"
+#include "upgradedb_sqlite2tosqlite3.h"
+
+namespace Digikam
+{
+
+struct _Album
+{
+ int id;
+ TQString url;
+ TQString date;
+ TQString caption;
+ TQString collection;
+ TQString icon;
+};
+
+struct _Tag
+{
+ int id;
+ int pid;
+ TQString name;
+ TQString icon;
+};
+
+static TQString escapeString(TQString str)
+{
+ str.replace( "'", "''" );
+ return str;
+}
+
+TQ_LLONG findOrAddImage(AlbumDB* db, int dirid, const TQString& name,
+ const TQString& caption)
+{
+ TQStringList values;
+
+ db->execSql(TQString("SELECT id FROM Images WHERE dirid=%1 AND name='%2'")
+ .arg(dirid)
+ .arg(escapeString(name)), &values);
+
+ if (!values.isEmpty())
+ {
+ return values.first().toLongLong();
+ }
+
+ db->execSql(TQString("INSERT INTO Images (dirid, name, caption) \n "
+ "VALUES(%1, '%2', '%3');")
+ .arg(dirid)
+ .arg(escapeString(name))
+ .arg(escapeString(caption)), &values);
+
+ return db->lastInsertedRow();
+}
+
+
+bool upgradeDB_Sqlite2ToSqlite3(const TQString& _libraryPath)
+{
+ TQString libraryPath = TQDir::cleanDirPath(_libraryPath);
+
+ TQString newDB= libraryPath + "/digikam3.db";
+
+#ifdef NFS_HACK
+ newDB = locateLocal("appdata", TDEIO::encodeFileName(TQDir::cleanDirPath(newDB)));
+ DDebug() << "NFS: " << newDB << endl;
+#endif
+
+ AlbumDB db3;
+ db3.setDBPath(newDB);
+ if (!db3.isValid())
+ {
+ DWarning() << "Failed to open new Album Database" << endl;
+ DWarning() << "The directory <" << libraryPath << "> may not exist or is write-protected" << endl;
+ return false;
+ }
+
+ if (db3.getSetting("UpgradedFromSqlite2") == "yes")
+ return true;
+
+ TQString dbPath = libraryPath + "/digikam.db";
+
+#ifdef NFS_HACK
+ dbPath = locateLocal("appdata", TDEIO::encodeFileName(TQDir::cleanDirPath(dbPath)));
+ DDebug() << "From NFS: " << dbPath << endl;
+#endif
+
+ TQFileInfo fi(dbPath);
+
+ if (!fi.exists())
+ {
+ DDebug() << "No old database present. Not upgrading" << endl;
+ db3.setSetting("UpgradedFromSqlite2", "yes");
+ return true;
+ }
+
+ AlbumDB_Sqlite2 db2;
+ db2.setDBPath( dbPath );
+ if (!db2.isValid())
+ {
+ DDebug() << "Failed to initialize Old Album Database" << endl;
+ return false;
+ }
+
+ // delete entries from sqlite3 database
+ db3.execSql("DELETE FROM Albums;");
+ db3.execSql("DELETE FROM Tags;");
+ db3.execSql("DELETE FROM TagsTree;");
+ db3.execSql("DELETE FROM Images;");
+ db3.execSql("DELETE FROM ImageTags;");
+ db3.execSql("DELETE FROM ImageProperties;");
+
+ TQStringList values;
+
+ // update albums -------------------------------------------------
+
+ values.clear();
+ db2.execSql("SELECT id, url, date, caption, collection, icon FROM Albums;",
+ &values);
+
+ typedef TQValueList<_Album> AlbumList;
+ AlbumList albumList;
+
+ typedef TQMap<TQString, int> AlbumMap;
+ AlbumMap albumMap;
+
+ db3.beginTransaction();
+ for (TQStringList::iterator it=values.begin(); it!=values.end();)
+ {
+ _Album album;
+
+ album.id = (*it).toInt();
+ ++it;
+ album.url = (*it);
+ ++it;
+ album.date = (*it);
+ ++it;
+ album.caption = (*it);
+ ++it;
+ album.collection = (*it);
+ ++it;
+ album.icon = (*it);
+ ++it;
+
+ albumList.append(album);
+ albumMap.insert(album.url, album.id);
+
+ db3.execSql(TQString("INSERT INTO Albums (id, url, date, caption, collection) "
+ "VALUES(%1, '%2', '%3', '%4', '%5');")
+ .arg(album.id)
+ .arg(escapeString(album.url))
+ .arg(escapeString(album.date))
+ .arg(escapeString(album.caption))
+ .arg(escapeString(album.collection)));
+ }
+ db3.commitTransaction();
+
+ // update tags -------------------------------------------------
+
+ values.clear();
+ db2.execSql("SELECT id, pid, name, icon FROM Tags;",
+ &values);
+
+ typedef TQValueList<_Tag> TagList;
+ TagList tagList;
+
+ db3.beginTransaction();
+ for (TQStringList::iterator it=values.begin(); it!=values.end();)
+ {
+ _Tag tag;
+
+ tag.id = (*it).toInt();
+ ++it;
+ tag.pid = (*it).toInt();
+ ++it;
+ tag.name = (*it);
+ ++it;
+ tag.icon = (*it);
+ ++it;
+
+ tagList.append(tag);
+
+ db3.execSql(TQString("INSERT INTO Tags (id, pid, name) "
+ "VALUES(%1, %2, '%3');")
+ .arg(tag.id)
+ .arg(tag.pid)
+ .arg(escapeString(tag.name)));
+ }
+ db3.commitTransaction();
+
+ // update images -------------------------------------------------
+
+ values.clear();
+ db2.execSql("SELECT dirid, name, caption FROM Images;",
+ &values);
+
+ db3.beginTransaction();
+ for (TQStringList::iterator it=values.begin(); it!=values.end();)
+ {
+ int dirid = (*it).toInt();
+ ++it;
+ TQString name = (*it);
+ ++it;
+ TQString caption = (*it);
+ ++it;
+
+ findOrAddImage(&db3, dirid, name, caption);
+ }
+ db3.commitTransaction();
+
+ // update imagetags -----------------------------------------------
+
+ values.clear();
+ db2.execSql("SELECT dirid, name, tagid FROM ImageTags;",
+ &values);
+ db3.beginTransaction();
+ for (TQStringList::iterator it=values.begin(); it!=values.end();)
+ {
+ int dirid = (*it).toInt();
+ ++it;
+
+ TQString name = (*it);
+ ++it;
+
+ int tagid = (*it).toInt();
+ ++it;
+
+ TQ_LLONG imageid = findOrAddImage(&db3, dirid, name, TQString());
+
+ db3.execSql(TQString("INSERT INTO ImageTags VALUES( %1, %2 )")
+ .arg(imageid).arg(tagid));
+ }
+ db3.commitTransaction();
+
+ // update album icons -------------------------------------------------
+
+ db3.beginTransaction();
+ for (AlbumList::iterator it = albumList.begin(); it != albumList.end();
+ ++it)
+ {
+ _Album album = *it;
+
+ if (album.icon.isEmpty())
+ continue;
+
+ TQ_LLONG imageid = findOrAddImage(&db3, album.id, album.icon, TQString());
+
+ db3.execSql(TQString("UPDATE Albums SET icon=%1 WHERE id=%2")
+ .arg(imageid)
+ .arg(album.id));
+ }
+ db3.commitTransaction();
+
+ // -- update tag icons ---------------------------------------------------
+
+ db3.beginTransaction();
+ for (TagList::iterator it = tagList.begin(); it != tagList.end(); ++it)
+ {
+ _Tag tag = *it;
+
+ if (tag.icon.isEmpty())
+ continue;
+
+ TQFileInfo fi(tag.icon);
+ if (fi.isRelative())
+ {
+ db3.execSql(TQString("UPDATE Tags SET iconkde='%1' WHERE id=%2")
+ .arg(escapeString(tag.icon))
+ .arg(tag.id));
+ continue;
+ }
+
+ tag.icon = TQDir::cleanDirPath(tag.icon);
+ fi.setFile(tag.icon.remove(libraryPath));
+
+ TQString url = fi.dirPath(true);
+ TQString name = fi.fileName();
+
+ AlbumMap::iterator it1 = albumMap.find(url);
+ if (it1 == albumMap.end())
+ {
+ DDebug() << "Could not find album with url: " << url << endl;
+ DDebug() << "Most likely an external directory. Rejecting." << endl;
+ continue;
+ }
+
+ int dirid = it1.data();
+
+ TQ_LLONG imageid = findOrAddImage(&db3, dirid, name, TQString());;
+
+ db3.execSql(TQString("UPDATE Tags SET icon=%1 WHERE id=%2")
+ .arg(imageid)
+ .arg(tag.id));
+
+ }
+ db3.commitTransaction();
+
+ // -- Remove invalid entries ----------------------------------------
+ db3.execSql("DELETE FROM Images WHERE dirid=-1");
+
+ // -- update setting entry ------------------------------------------
+ db3.setSetting("UpgradedFromSqlite2", "yes");
+
+ DDebug() << "Successfully upgraded database to sqlite3 " << endl;
+
+ // -- Check for db consistency ----------------------------------------
+
+ std::cout << "Checking database consistency" << std::endl;
+
+
+ std::cout << "Checking Albums..................";
+ values.clear();
+ db2.execSql("SELECT id, url, date, caption, collection FROM Albums;", &values);
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ _Album album;
+ album.id = (*it).toInt();
+ ++it;
+ album.url = (*it);
+ ++it;
+ album.date = (*it);
+ ++it;
+ album.caption = (*it);
+ ++it;
+ album.collection = (*it);
+ ++it;
+
+ TQStringList list;
+ db3.execSql(TQString("SELECT id FROM Albums WHERE \n"
+ " id=%1 AND \n"
+ " url='%2' AND \n"
+ " date='%3' AND \n"
+ " caption='%4' AND \n"
+ " collection='%5';")
+ .arg(album.id)
+ .arg(escapeString(album.url))
+ .arg(escapeString(album.date))
+ .arg(escapeString(album.caption))
+ .arg(escapeString(album.collection)), &list, false);
+ if (list.size() != 1)
+ {
+ std::cerr << "Failed" << std::endl;
+ DWarning() << "" << endl;
+ DWarning() << "Consistency check failed for Album: "
+ << album.url << endl;
+ return false;
+ }
+ }
+ std::cout << "(" << values.count()/5 << " Albums) " << "OK" << std::endl;
+
+
+ std::cout << "Checking Tags....................";
+ values.clear();
+ db2.execSql("SELECT id, pid, name FROM Tags;", &values);
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ int id = (*it).toInt();
+ ++it;
+ int pid = (*it).toInt();
+ ++it;
+ TQString name = (*it);
+ ++it;
+
+ TQStringList list;
+ db3.execSql(TQString("SELECT id FROM Tags WHERE \n"
+ " id=%1 AND \n"
+ " pid=%2 AND \n"
+ " name='%3';")
+ .arg(id)
+ .arg(pid)
+ .arg(escapeString(name)),
+ &list, false);
+ if (list.size() != 1)
+ {
+ std::cerr << "Failed" << std::endl;
+ DWarning() << "" << endl;
+ DWarning() << "Consistency check failed for Tag: "
+ << name << endl;
+ return false;
+ }
+ }
+ std::cout << "(" << values.count()/3 << " Tags) " << "OK" << std::endl;
+
+
+ std::cout << "Checking Images..................";
+ values.clear();
+ db2.execSql("SELECT Albums.url, Images.name, Images.caption "
+ "FROM Images, Albums WHERE Albums.id=Images.dirid;", &values);
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ TQString url = (*it);
+ ++it;
+ TQString name = (*it);
+ ++it;
+ TQString caption = (*it);
+ ++it;
+
+ TQStringList list;
+ db3.execSql(TQString("SELECT Images.id FROM Images, Albums WHERE \n "
+ "Albums.url = '%1' AND \n "
+ "Images.dirid = Albums.id AND \n "
+ "Images.name = '%2' AND \n "
+ "Images.caption = '%3';")
+ .arg(escapeString(url))
+ .arg(escapeString(name))
+ .arg(escapeString(caption)),
+ &list, false);
+ if (list.size() != 1)
+ {
+ std::cerr << "Failed" << std::endl;
+ DWarning() << "" << endl;
+ DWarning() << "Consistency check failed for Image: "
+ << url << ", " << name << ", " << caption << endl;
+ return false;
+ }
+ }
+ std::cout << "(" << values.count()/3 << " Images) " << "OK" << std::endl;
+
+
+ std::cout << "Checking ImageTags...............";
+ values.clear();
+ db2.execSql("SELECT Albums.url, ImageTags.name, ImageTags.tagid "
+ "FROM ImageTags, Albums WHERE \n "
+ " Albums.id=ImageTags.dirid;", &values);
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ TQString url = (*it);
+ ++it;
+ TQString name = (*it);
+ ++it;
+ int tagid = (*it).toInt();
+ ++it;
+
+ TQStringList list;
+ db3.execSql(TQString("SELECT Images.id FROM Albums, Images, ImageTags WHERE \n "
+ "Albums.url = '%1' AND \n "
+ "Images.dirid = Albums.id AND \n "
+ "Images.name = '%2' AND \n "
+ "ImageTags.imageid = Images.id AND \n "
+ "ImageTags.tagid = %3;")
+ .arg(escapeString(url))
+ .arg(escapeString(name))
+ .arg(tagid),
+ &list, false);
+ if (list.size() != 1)
+ {
+ std::cerr << "Failed" << std::endl;
+ DWarning() << "" << endl;
+ DWarning() << "Consistency check failed for ImageTag: "
+ << url << ", " << name << ", " << tagid << endl;
+ return false;
+ }
+ }
+ std::cout << "(" << values.count()/3 << " ImageTags) " << "OK" << std::endl;
+
+ std::cout << "Checking Album icons ...............";
+ values.clear();
+ db2.execSql("SELECT url, icon FROM Albums;", &values);
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ TQString url = (*it);
+ ++it;
+ TQString icon = (*it);
+ ++it;
+
+ if (icon.isEmpty())
+ continue;
+
+ TQStringList list;
+ db3.execSql(TQString("SELECT Images.id FROM Images, Albums WHERE \n "
+ "Albums.url = '%1' AND \n "
+ "Images.id = Albums.icon AND \n "
+ "Images.name = '%2';")
+ .arg(escapeString(url))
+ .arg(escapeString(icon)), &list);
+
+ if (list.size() != 1)
+ {
+ std::cerr << "Failed" << std::endl;
+ DWarning() << "" << endl;
+ DWarning() << "Consistency check failed for Album Icon: "
+ << url << ", " << icon << endl;
+
+ return false;
+ }
+ }
+ std::cout << "(" << values.count()/2 << " Album Icons) " << "OK" << std::endl;
+
+
+ std::cout << "Checking Tag icons ...............";
+ values.clear();
+ db2.execSql("SELECT id, icon FROM Tags;", &values);
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ int id = (*it).toInt();
+ ++it;
+ TQString icon = (*it);
+ ++it;
+
+ if (icon.isEmpty())
+ continue;
+
+ if (!icon.startsWith("/"))
+ {
+ TQStringList list;
+ db3.execSql(TQString("SELECT id FROM Tags WHERE \n "
+ "id = %1 AND \n "
+ "iconkde = '%2';")
+ .arg(id)
+ .arg(escapeString(icon)), &list);
+
+ if (list.size() != 1)
+ {
+ std::cerr << "Failed" << std::endl;
+ DWarning() << "" << endl;
+ DWarning() << "Consistency check failed for Tag Icon: "
+ << id << ", " << icon << endl;
+
+ return false;
+ }
+ }
+ else
+ {
+ icon = TQDir::cleanDirPath(icon);
+ TQFileInfo fi(icon.remove(libraryPath));
+
+ TQString url = fi.dirPath(true);
+ TQString name = fi.fileName();
+
+ TQStringList list;
+
+ list.clear();
+ db3.execSql(TQString("SELECT id FROM Albums WHERE url='%1'")
+ .arg(escapeString(url)), &list);
+ if (list.isEmpty())
+ {
+ DWarning() << "Tag icon not in Album Library Path, Rejecting " << endl;
+ DWarning() << "(" << icon << ")" << endl;
+ continue;
+ }
+
+ list.clear();
+ db3.execSql(TQString("SELECT Images.id FROM Images, Tags WHERE \n "
+ " Images.dirid=(SELECT id FROM Albums WHERE url='%1') AND \n "
+ " Images.name='%2' AND \n "
+ " Tags.id=%3 AND \n "
+ " Tags.icon=Images.id")
+ .arg(escapeString(url))
+ .arg(escapeString(name))
+ .arg(id), &list);
+ if (list.size() != 1)
+ {
+ std::cerr << "Failed." << std::endl;
+ DWarning() << "" << endl;
+ DWarning() << "Consistency check failed for Tag Icon: "
+ << id << ", " << icon << endl;
+
+ return false;
+ }
+
+ }
+ }
+ std::cout << "(" << values.count()/2 << " Tag Icons) " << "OK" << std::endl;
+
+ std::cout << "" << std::endl;
+ std::cout << "All Tests: A-OK" << std::endl;
+
+ return true;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/upgradedb_sqlite2tosqlite3.h b/src/digikam/upgradedb_sqlite2tosqlite3.h
new file mode 100644
index 00000000..8d94fa78
--- /dev/null
+++ b/src/digikam/upgradedb_sqlite2tosqlite3.h
@@ -0,0 +1,38 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-05
+ * Description : SQlite 2 to SQlite 3 interface
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef UPGRADEDB_SQLITE2TOSQLITE3_H
+#define UPGRADEDB_SQLITE2TOSQLITE3_H
+
+#include "digikam_export.h"
+
+class TQString;
+
+namespace Digikam
+{
+
+extern bool DIGIKAM_EXPORT upgradeDB_Sqlite2ToSqlite3(const TQString& libraryPath);
+
+} // namespace Digikam
+
+#endif /* UPGRADEDB_SQLITE2TOSQLITE3_H */
diff --git a/src/digikam/welcomepageview.cpp b/src/digikam/welcomepageview.cpp
new file mode 100644
index 00000000..6327c947
--- /dev/null
+++ b/src/digikam/welcomepageview.cpp
@@ -0,0 +1,193 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-12-20
+ * Description : a widget to display a welcome page
+ * on root album.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqfile.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kcursor.h>
+#include <tdehtml_part.h>
+#include <tdehtmlview.h>
+#include <tdeapplication.h>
+#include <kurl.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "welcomepageview.h"
+#include "welcomepageview.moc"
+
+namespace Digikam
+{
+
+WelcomePageView::WelcomePageView(TQWidget* parent)
+ : TDEHTMLPart(parent)
+{
+ widget()->setFocusPolicy(TQWidget::WheelFocus);
+ // Let's better be paranoid and disable plugins (it defaults to enabled):
+ setPluginsEnabled(false);
+ setJScriptEnabled(false); // just make this explicit.
+ setJavaEnabled(false); // just make this explicit.
+ setMetaRefreshEnabled(false);
+ setURLCursor(KCursor::handCursor());
+
+ TQString fontSize = TQString::number(12);
+ TQString appTitle = i18n("digiKam");
+ TQString catchPhrase = TQString(); // Not enough space for a catch phrase at default window size.
+ TQString quickDescription = TQString(digiKamDescription());
+ TQString locationHtml = locate("data", "digikam/about/main.html");
+ TQString locationCss = locate("data", "digikam/about/kde_infopage.css");
+ TQString locationRtl = locate("data", "digikam/about/kde_infopage_rtl.css" );
+ TQString rtl = kapp->reverseLayout() ? TQString("@import \"%1\";" ).arg(locationRtl)
+ : TQString();
+
+ begin(KURL(locationHtml));
+
+ TQString content = fileToString(locationHtml);
+ content = content.arg(locationCss) // %1
+ .arg(rtl) // %2
+ .arg(fontSize) // %3
+ .arg(appTitle) // %4
+ .arg(catchPhrase) // %5
+ .arg(quickDescription) // %6
+ .arg(infoPage()); // %7
+
+ write(content);
+ end();
+ show();
+
+ connect(browserExtension(), TQ_SIGNAL(openURLRequest(const KURL &, const KParts::URLArgs &)),
+ this, TQ_SLOT(slotUrlOpen(const KURL &)));
+}
+
+WelcomePageView::~WelcomePageView()
+{
+}
+
+void WelcomePageView::slotUrlOpen(const KURL &url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url.url());
+}
+
+TQString WelcomePageView::infoPage()
+{
+ TQString info =
+ i18n(
+ "%1: digiKam version; "
+ "%2: help:// URL; "
+ "%3: homepage URL; "
+ "%4: prior digiKam version; "
+ "%5: prior KDE version; "
+ "%6: generated list of new features; "
+ "%7: First-time user text (only shown on first start); "
+ "%8: generated list of important changes; "
+ "--- end of comment ---",
+ "<h2 style='margin-top: 0px;'>Welcome to digiKam</h2><p>"
+ "digiKam is a photo management program for the Trinity Desktop Environment. "
+ "It is designed to import, organize, and export your digital photographs on your computer."
+ "</p><p>You are currently in the Album view mode of digiKam. The Albums are the real "
+ "containers where your files are stored, they are identical with the folders "
+ "on disk.</p>\n"
+ "digiKam has many powerful features\n"
+ "%8\n<p>" // important changes
+ "Some of the features of digiKam include</p>\n"
+ "<ul>\n%5</ul>\n"
+ "%6\n"
+ "<p>We hope you will enjoy digiKam.</p>\n"
+ "<p>Thank you,</p>\n"
+ "<p style='margin-bottom: 0px'>&nbsp; &nbsp; The digiKam Team</p>");
+
+ TQStringList newFeatures;
+ newFeatures << i18n("16-bit/color/pixel image support");
+ newFeatures << i18n("Full color management support");
+ newFeatures << i18n("Native JPEG-2000 support");
+ newFeatures << i18n("Makernote and IPTC metadata support");
+ newFeatures << i18n("Photograph geolocation");
+ newFeatures << i18n("Extensive Sidebars");
+ newFeatures << i18n("Advanced RAW image decoding settings");
+ newFeatures << i18n("Fast RAW preview");
+ newFeatures << i18n("RAW Metadata support");
+ newFeatures << i18n("Camera Interface used as generic import tool");
+ newFeatures << i18n("New advanced camera download options");
+ newFeatures << i18n("New advanced tag management");
+ newFeatures << i18n("New zooming/panning support in preview mode");
+ newFeatures << i18n("New Light Table provides easy comparison for similar images");
+ newFeatures << i18n("New text, mime-type, and rating filters to search contents on icon view");
+ newFeatures << i18n("New options to easy navigate between albums, tags and collections");
+ newFeatures << i18n("New options to recursively show the contents of sub-folders");
+ newFeatures << i18n("New text filter to search contents on folder views");
+ newFeatures << i18n("New options to count of items on all folder views");
+ newFeatures << i18n("New tool to perform dates search around whole albums collection: Time-Line");
+ newFeatures << i18n("New tool to import RAW files in editor with customized decoding settings");
+
+ TQString featureItems;
+ for ( uint i = 0 ; i < newFeatures.count() ; i++ )
+ featureItems += i18n("<li>%1</li>\n").arg( newFeatures[i] );
+
+ info = info.arg( featureItems );
+
+ // Add first-time user text (only shown on first start).
+ info = info.arg( TQString() );
+
+ // Generated list of important changes
+ info = info.arg( TQString() );
+
+ return info;
+}
+
+TQCString WelcomePageView::fileToString(const TQString &aFileName)
+{
+ TQCString result;
+ TQFileInfo info(aFileName);
+ unsigned int readLen;
+ unsigned int len = info.size();
+ TQFile file(aFileName);
+
+ if (aFileName.isEmpty() || len <= 0 ||
+ !info.exists() || info.isDir() || !info.isReadable() ||
+ !file.open(IO_Raw|IO_ReadOnly))
+ return TQCString();
+
+ result.resize(len + 2);
+ readLen = file.readBlock(result.data(), len);
+ if (1 && result[len-1]!='\n')
+ {
+ result[len++] = '\n';
+ readLen++;
+ }
+ result[len] = '\0';
+
+ if (readLen < len)
+ return TQCString();
+
+ return result;
+}
+
+} // namespace Digikam
diff --git a/src/digikam/welcomepageview.h b/src/digikam/welcomepageview.h
new file mode 100644
index 00000000..b69d82a2
--- /dev/null
+++ b/src/digikam/welcomepageview.h
@@ -0,0 +1,64 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-12-20
+ * Description : a view to display a welcome page
+ * on root album.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef WELCOMEPAGEVIEW_H
+#define WELCOMEPAGEVIEW_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdehtml_part.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT WelcomePageView : public TDEHTMLPart
+{
+ TQ_OBJECT
+
+public:
+
+ WelcomePageView(TQWidget* parent);
+ ~WelcomePageView();
+
+private:
+
+ TQCString fileToString(const TQString &aFileName);
+ TQString infoPage();
+
+private slots:
+
+ void slotUrlOpen(const KURL &);
+};
+
+} // namespace Digikam
+
+#endif /* WELCOMEPAGEVIEW_H */
diff --git a/src/imageplugins/Makefile.am b/src/imageplugins/Makefile.am
new file mode 100644
index 00000000..3e04b062
--- /dev/null
+++ b/src/imageplugins/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = coreplugin adjustcurves adjustlevels antivignetting blurfx border \
+ channelmixer charcoal colorfx distortionfx emboss filmgrain \
+ freerotation hotpixels infrared inpainting inserttext lensdistortion \
+ noisereduction oilpaint perspective raindrop restoration sheartool \
+ superimpose texture whitebalance
diff --git a/src/imageplugins/adjustcurves/Makefile.am b/src/imageplugins/adjustcurves/Makefile.am
new file mode 100644
index 00000000..f798a7af
--- /dev/null
+++ b/src/imageplugins/adjustcurves/Makefile.am
@@ -0,0 +1,33 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_adjustcurves_la_SOURCES = imageplugin_adjustcurves.cpp \
+ adjustcurvestool.cpp
+
+digikamimageplugin_adjustcurves_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_adjustcurves_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_adjustcurves.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_adjustcurves.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_adjustcurves_ui.rc
diff --git a/src/imageplugins/adjustcurves/adjustcurves.cpp b/src/imageplugins/adjustcurves/adjustcurves.cpp
new file mode 100644
index 00000000..ace9c9b3
--- /dev/null
+++ b/src/imageplugins/adjustcurves/adjustcurves.cpp
@@ -0,0 +1,677 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image histogram adjust curves.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpainter.h>
+#include <tqcombobox.h>
+#include <tqspinbox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+#include <tqhbuttongroup.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <knuminput.h>
+#include <tdemessagebox.h>
+#include <tdeselect.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "imagecurves.h"
+#include "histogramwidget.h"
+#include "curveswidget.h"
+#include "colorgradientwidget.h"
+#include "dimgimagefilters.h"
+#include "adjustcurves.h"
+#include "adjustcurves.moc"
+
+namespace DigikamAdjustCurvesImagesPlugin
+{
+
+AdjustCurveDialog::AdjustCurveDialog(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Adjust Color Curves"), "adjustcurves", true, false)
+{
+ m_destinationPreviewData = 0L;
+
+ Digikam::ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ m_originalImage = Digikam::DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+
+ m_histoSegments = m_originalImage.sixteenBit() ? 65535 : 255;
+ m_curves = new Digikam::ImageCurves(m_originalImage.sixteenBit());
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Adjust Color Curves"),
+ digikam_version,
+ I18N_NOOP("An image-histogram-curves adjustment plugin for digiKam."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new Digikam::ImageWidget("adjustcurves Tool Dialog", plainPage(),
+ i18n("<p>This is the image's curve-adjustments preview. "
+ "You can pick a spot on the image "
+ "to see the corresponding level in the histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* grid = new TQGridLayout( gboxSettings, 5, 5, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ m_channelCB->insertItem( i18n("Alpha") );
+ m_channelCB->setCurrentText( i18n("Luminosity") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"
+ "<b>Alpha</b>: display the alpha image-channel values. "
+ "This channel corresponds to the transparency value and "
+ "is supported by some image formats, such as PNG or TIF."));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::CurvesWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::CurvesWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setButton(Digikam::CurvesWidget::LogScaleHistogram);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ grid->addMultiCellWidget(label1, 0, 0, 1, 1);
+ grid->addMultiCellWidget(m_channelCB, 0, 0, 2, 2);
+ grid->addMultiCellLayout(l1, 0, 0, 4, 5);
+
+ // -------------------------------------------------------------
+
+ TQWidget *curveBox = new TQWidget(gboxSettings);
+ TQGridLayout* gl = new TQGridLayout(curveBox, 4, 2, 0);
+
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, curveBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "curves settings changes."));
+
+ m_vGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Vertical, 10, curveBox );
+ m_vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+
+ TQLabel *spacev = new TQLabel(curveBox);
+ spacev->setFixedWidth(1);
+
+ m_curvesWidget = new Digikam::CurvesWidget(256, 256, m_originalImage.bits(), m_originalImage.width(),
+ m_originalImage.height(), m_originalImage.sixteenBit(),
+ m_curves, curveBox);
+ TQWhatsThis::add( m_curvesWidget, i18n("<p>This is the curve drawing of the selected channel from "
+ "original image"));
+
+ TQLabel *spaceh = new TQLabel(curveBox);
+ spaceh->setFixedHeight(1);
+
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, curveBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gl->addMultiCellWidget(m_histogramWidget, 0, 0, 2, 2);
+ gl->addMultiCellWidget(m_vGradient, 2, 2, 0, 0);
+ gl->addMultiCellWidget(spacev, 2, 2, 1, 1);
+ gl->addMultiCellWidget(m_curvesWidget, 2, 2, 2, 2);
+ gl->addMultiCellWidget(spaceh, 3, 3, 2, 2);
+ gl->addMultiCellWidget(m_hGradient, 4, 4, 2, 2);
+ gl->setRowSpacing(1, spacingHint());
+
+ grid->addMultiCellWidget(curveBox, 2, 3, 0, 5);
+
+ // -------------------------------------------------------------
+
+ m_curveType = new TQHButtonGroup(gboxSettings);
+ m_curveFree = new TQPushButton(m_curveType);
+ m_curveType->insert(m_curveFree, FreeDrawing);
+ TDEGlobal::dirs()->addResourceType("curvefree", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("curvefree", "curvefree.png");
+ m_curveFree->setPixmap( TQPixmap( directory + "curvefree.png" ) );
+ m_curveFree->setToggleButton(true);
+ TQToolTip::add( m_curveFree, i18n( "Curve free mode" ) );
+ TQWhatsThis::add( m_curveFree, i18n("<p>With this button, you can draw your curve free-hand with the mouse."));
+ m_curveSmooth = new TQPushButton(m_curveType);
+ m_curveType->insert(m_curveSmooth, SmoothDrawing);
+ TDEGlobal::dirs()->addResourceType("curvemooth", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("curvemooth", "curvemooth.png");
+ m_curveSmooth->setPixmap( TQPixmap( directory + "curvemooth.png" ) );
+ m_curveSmooth->setToggleButton(true);
+ TQToolTip::add( m_curveSmooth, i18n( "Curve smooth mode" ) );
+ TQWhatsThis::add( m_curveSmooth, i18n("<p>With this button, you constrains the curve type to a smooth line with tension."));
+ m_curveType->setExclusive(true);
+ m_curveType->setButton(SmoothDrawing);
+ m_curveType->setFrameShape(TQFrame::NoFrame);
+
+ // -------------------------------------------------------------
+
+ m_pickerColorButtonGroup = new TQHButtonGroup(gboxSettings);
+ m_pickBlack = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickBlack, BlackTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-black", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-black", "color-picker-black.png");
+ m_pickBlack->setPixmap( TQPixmap( directory + "color-picker-black.png" ) );
+ m_pickBlack->setToggleButton(true);
+ TQToolTip::add( m_pickBlack, i18n( "All channels shadow tone color picker" ) );
+ TQWhatsThis::add( m_pickBlack, i18n("<p>With this button, you can pick the color from original image used to set <b>Shadow Tone</b> "
+ "smooth curves point on Red, Green, Blue, and Luminosity channels."));
+ m_pickGray = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickGray, GrayTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-grey", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-grey", "color-picker-grey.png");
+ m_pickGray->setPixmap( TQPixmap( directory + "color-picker-grey.png" ) );
+ m_pickGray->setToggleButton(true);
+ TQToolTip::add( m_pickGray, i18n( "All channels middle tone color picker" ) );
+ TQWhatsThis::add( m_pickGray, i18n("<p>With this button, you can pick the color from original image used to set <b>Middle Tone</b> "
+ "smooth curves point on Red, Green, Blue, and Luminosity channels."));
+ m_pickWhite = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickWhite, WhiteTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-white", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-white", "color-picker-white.png");
+ m_pickWhite->setPixmap( TQPixmap( directory + "color-picker-white.png" ) );
+ m_pickWhite->setToggleButton(true);
+ TQToolTip::add( m_pickWhite, i18n( "All channels highlight tone color picker" ) );
+ TQWhatsThis::add( m_pickWhite, i18n("<p>With this button, you can pick the color from original image used to set <b>Highlight Tone</b> "
+ "smooth curves point on Red, Green, Blue, and Luminosity channels."));
+ m_pickerColorButtonGroup->setExclusive(true);
+ m_pickerColorButtonGroup->setFrameShape(TQFrame::NoFrame);
+
+ // -------------------------------------------------------------
+
+ m_resetButton = new TQPushButton(i18n("&Reset"), gboxSettings);
+ m_resetButton->setPixmap( SmallIcon("reload_page", 18) );
+ TQToolTip::add( m_resetButton, i18n( "Reset current channel curves' values." ) );
+ TQWhatsThis::add( m_resetButton, i18n("<p>If you press this button, all curves' values "
+ "from the current selected channel "
+ "will be reset to the default values."));
+
+ TQHBoxLayout* l3 = new TQHBoxLayout();
+ l3->addWidget(m_curveType);
+ l3->addWidget(m_pickerColorButtonGroup);
+ l3->addWidget(m_resetButton);
+ l3->addStretch(10);
+
+ grid->addMultiCellLayout(l3, 4, 4, 1, 5);
+
+ // -------------------------------------------------------------
+
+
+ grid->setRowStretch(5, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_curvesWidget, TQ_SIGNAL(signalCurvesChanged()),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotSpotColorChanged( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // ComboBox slots.
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_curveType, TQ_SIGNAL(clicked(int)),
+ this, TQ_SLOT(slotCurveTypeChanged(int)));
+
+ // -------------------------------------------------------------
+ // Bouttons slots.
+
+ connect(m_resetButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetCurrentChannel()));
+
+ connect(m_pickerColorButtonGroup, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotPickerColorButtonActived()));
+}
+
+AdjustCurveDialog::~AdjustCurveDialog()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_curvesWidget;
+ delete m_previewWidget;
+ delete m_curves;
+}
+
+void AdjustCurveDialog::slotPickerColorButtonActived()
+{
+ // Save previous rendering mode and toggle to original image.
+ m_currentPreviewMode = m_previewWidget->getRenderingPreviewMode();
+ m_previewWidget->setRenderingPreviewMode(Digikam::ImageGuideWidget::PreviewOriginalImage);
+}
+
+void AdjustCurveDialog::slotSpotColorChanged(const Digikam::DColor &color)
+{
+ Digikam::DColor sc = color;
+
+ if ( m_pickBlack->isOn() )
+ {
+ // Black tonal curves point.
+ m_curves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 1,
+ TQPoint(TQMAX(TQMAX(sc.red(), sc.green()), sc.blue()), 42*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::RedChannel, 1, TQPoint(sc.red(), 42*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::GreenChannel, 1, TQPoint(sc.green(), 42*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::BlueChannel, 1, TQPoint(sc.blue(), 42*m_histoSegments/256));
+ m_pickBlack->setOn(false);
+ }
+ else if ( m_pickGray->isOn() )
+ {
+ // Gray tonal curves point.
+ m_curves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 8,
+ TQPoint(TQMAX(TQMAX(sc.red(), sc.green()), sc.blue()), 128*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::RedChannel, 8, TQPoint(sc.red(), 128*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::GreenChannel, 8, TQPoint(sc.green(), 128*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::BlueChannel, 8, TQPoint(sc.blue(), 128*m_histoSegments/256));
+ m_pickGray->setOn(false);
+ }
+ else if ( m_pickWhite->isOn() )
+ {
+ // White tonal curves point.
+ m_curves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 15,
+ TQPoint(TQMAX(TQMAX(sc.red(), sc.green()), sc.blue()), 213*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::RedChannel, 15, TQPoint(sc.red(), 213*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::GreenChannel, 15, TQPoint(sc.green(), 213*m_histoSegments/256));
+ m_curves->setCurvePoint(Digikam::ImageHistogram::BlueChannel, 15, TQPoint(sc.blue(), 213*m_histoSegments/256));
+ m_pickWhite->setOn(false);
+ }
+ else
+ {
+ m_curvesWidget->setCurveGuide(color);
+ return;
+ }
+
+ // Calculate Red, green, blue curves.
+
+ for (int i = Digikam::ImageHistogram::ValueChannel ; i <= Digikam::ImageHistogram::BlueChannel ; i++)
+ m_curves->curvesCalculateCurve(i);
+
+ m_curvesWidget->repaint(false);
+
+ // restore previous rendering mode.
+ m_previewWidget->setRenderingPreviewMode(m_currentPreviewMode);
+
+ slotEffect();
+}
+
+void AdjustCurveDialog::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void AdjustCurveDialog::slotResetCurrentChannel()
+{
+ m_curves->curvesChannelReset(m_channelCB->currentItem());
+
+ m_curvesWidget->reset();
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void AdjustCurveDialog::slotEffect()
+{
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_curves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_curves->curvesLutProcess(orgData, m_destinationPreviewData, w, h);
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] orgData;
+}
+
+void AdjustCurveDialog::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ // Create the new empty destination image data space.
+ uchar* desData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_curves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_curves->curvesLutProcess(orgData, desData, w, h);
+
+ iface->putOriginalImage(i18n("Adjust Curve"), desData);
+ kapp->restoreOverrideCursor();
+
+ delete [] orgData;
+ delete [] desData;
+ accept();
+}
+
+void AdjustCurveDialog::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_curvesWidget->m_channelType = Digikam::CurvesWidget::ValueHistogram;
+ m_vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_curvesWidget->m_channelType = Digikam::CurvesWidget::RedChannelHistogram;
+ m_vGradient->setColors( TQColor( "red" ), TQColor( "black" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_curvesWidget->m_channelType = Digikam::CurvesWidget::GreenChannelHistogram;
+ m_vGradient->setColors( TQColor( "green" ), TQColor( "black" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_curvesWidget->m_channelType = Digikam::CurvesWidget::BlueChannelHistogram;
+ m_vGradient->setColors( TQColor( "blue" ), TQColor( "black" ) );
+ break;
+
+ case AlphaChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::AlphaChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_curvesWidget->m_channelType = Digikam::CurvesWidget::AlphaChannelHistogram;
+ m_vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+ break;
+ }
+
+ m_curveType->setButton(m_curves->getCurveType(channel));
+
+ m_curvesWidget->repaint(false);
+ m_histogramWidget->repaint(false);
+}
+
+void AdjustCurveDialog::slotScaleChanged(int scale)
+{
+ m_curvesWidget->m_scaleType = scale;
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+ m_curvesWidget->repaint(false);
+}
+
+void AdjustCurveDialog::slotCurveTypeChanged(int type)
+{
+ switch(type)
+ {
+ case SmoothDrawing:
+ {
+ m_curves->setCurveType(m_curvesWidget->m_channelType, Digikam::ImageCurves::CURVE_SMOOTH);
+ m_pickerColorButtonGroup->setEnabled(true);
+ break;
+ }
+
+ case FreeDrawing:
+ {
+ m_curves->setCurveType(m_curvesWidget->m_channelType, Digikam::ImageCurves::CURVE_FREE);
+ m_pickerColorButtonGroup->setEnabled(false);
+ break;
+ }
+ }
+
+ m_curvesWidget->curveTypeChanged();
+}
+
+void AdjustCurveDialog::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustcurves Tool Dialog");
+
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+
+ m_curvesWidget->reset();
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ m_curves->curvesChannelReset(i);
+ m_curves->setCurveType(i, (Digikam::ImageCurves::CurveType)config->readNumEntry(TQString("CurveTypeChannel%1").arg(i),
+ Digikam::ImageCurves::CURVE_SMOOTH));
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p = config->readPointEntry(TQString("CurveAjustmentChannel%1Point%2").arg(i).arg(j), &disable);
+
+ if (m_originalImage.sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curves->setCurvePoint(i, j, p);
+ }
+
+ m_curves->curvesCalculateCurve(i);
+ }
+
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void AdjustCurveDialog::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustcurves Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ config->writeEntry(TQString("CurveTypeChannel%1").arg(i), m_curves->getCurveType(i));
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint p = m_curves->getCurvePoint(i, j);
+
+ if (m_originalImage.sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+
+ config->writeEntry(TQString("CurveAjustmentChannel%1Point%2").arg(i).arg(j), p);
+ }
+ }
+
+ config->sync();
+}
+
+void AdjustCurveDialog::resetValues()
+{
+ for (int channel = 0 ; channel < 5 ; channel++)
+ m_curves->curvesChannelReset(channel);
+
+ m_curvesWidget->reset();
+ m_histogramWidget->reset();
+}
+
+// Load all settings.
+void AdjustCurveDialog::slotUser3()
+{
+ KURL loadCurvesFile;
+
+ loadCurvesFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Select Gimp Curves File to Load")) );
+ if( loadCurvesFile.isEmpty() )
+ return;
+
+ if ( m_curves->loadCurvesFromGimpCurvesFile( loadCurvesFile ) == false )
+ {
+ KMessageBox::error(this, i18n("Cannot load from the Gimp curves text file."));
+ return;
+ }
+
+ // Refresh the current curves config.
+ slotChannelChanged(m_channelCB->currentItem());
+ slotEffect();
+}
+
+// Save all settings.
+void AdjustCurveDialog::slotUser2()
+{
+ KURL saveCurvesFile;
+
+ saveCurvesFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Gimp Curves File to Save")) );
+ if( saveCurvesFile.isEmpty() )
+ return;
+
+ if ( m_curves->saveCurvesToGimpCurvesFile( saveCurvesFile ) == false )
+ {
+ KMessageBox::error(this, i18n("Cannot save to the Gimp curves text file."));
+ return;
+ }
+
+ // Refresh the current curves config.
+ slotChannelChanged(m_channelCB->currentItem());
+}
+
+} // NameSpace DigikamAdjustCurvesImagesPlugin
+
diff --git a/src/imageplugins/adjustcurves/adjustcurves.h b/src/imageplugins/adjustcurves/adjustcurves.h
new file mode 100644
index 00000000..f4cad2f2
--- /dev/null
+++ b/src/imageplugins/adjustcurves/adjustcurves.h
@@ -0,0 +1,143 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image histogram adjust curves.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ADJUSTCURVES_H
+#define ADJUSTCURVES_H
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+#include "dimg.h"
+
+// Local includes.
+
+class TQComboBox;
+class TQPushButton;
+class TQHButtonGroup;
+
+namespace Digikam
+{
+class CurvesWidget;
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class ImageCurves;
+}
+
+namespace DigikamAdjustCurvesImagesPlugin
+{
+
+class AdjustCurveDialog : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ AdjustCurveDialog(TQWidget *parent);
+ ~AdjustCurveDialog();
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void slotEffect();
+ void slotResetCurrentChannel();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotCurveTypeChanged(int type);
+ void slotSpotColorChanged(const Digikam::DColor &color);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+ void slotPickerColorButtonActived();
+
+private:
+
+ enum ColorPicker
+ {
+ BlackTonal=0,
+ GrayTonal,
+ WhiteTonal
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel,
+ AlphaChannel
+ };
+
+ enum CurvesDrawingType
+ {
+ SmoothDrawing=0,
+ FreeDrawing
+ };
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ uchar *m_destinationPreviewData;
+
+ int m_histoSegments;
+ int m_currentPreviewMode;
+
+ TQComboBox *m_channelCB;
+
+ TQPushButton *m_resetButton;
+ TQPushButton *m_pickBlack;
+ TQPushButton *m_pickGray;
+ TQPushButton *m_pickWhite;
+ TQPushButton *m_curveFree;
+ TQPushButton *m_curveSmooth;
+
+ TQHButtonGroup *m_pickerColorButtonGroup;
+ TQHButtonGroup *m_scaleBG;
+ TQHButtonGroup *m_curveType;
+
+ Digikam::CurvesWidget *m_curvesWidget;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+ Digikam::ColorGradientWidget *m_vGradient;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ImageCurves *m_curves;
+ Digikam::DImg m_originalImage;
+};
+
+} // NameSpace DigikamAdjustCurvesImagesPlugin
+
+#endif /* ADJUSTCURVES_H */
diff --git a/src/imageplugins/adjustcurves/adjustcurvestool.cpp b/src/imageplugins/adjustcurves/adjustcurvestool.cpp
new file mode 100644
index 00000000..7bfd00f7
--- /dev/null
+++ b/src/imageplugins/adjustcurves/adjustcurvestool.cpp
@@ -0,0 +1,659 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image histogram adjust curves.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpainter.h>
+#include <tqcombobox.h>
+#include <tqspinbox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+#include <tqhbuttongroup.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <knuminput.h>
+#include <tdemessagebox.h>
+#include <tdeselect.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <kpushbutton.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "imagecurves.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "curveswidget.h"
+#include "colorgradientwidget.h"
+#include "dimgimagefilters.h"
+#include "adjustcurvestool.h"
+#include "adjustcurvestool.moc"
+
+using namespace Digikam;
+
+namespace DigikamAdjustCurvesImagesPlugin
+{
+
+AdjustCurvesTool::AdjustCurvesTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ m_destinationPreviewData = 0;
+
+ ImageIface iface(0, 0);
+ m_originalImage = iface.getOriginalImg();
+
+ m_histoSegments = m_originalImage->sixteenBit() ? 65535 : 255;
+
+ setName("adjustcurves");
+ setToolName(i18n("Adjust Curves"));
+ setToolIcon(SmallIcon("adjustcurves"));
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImageWidget("adjustcurves Tool", 0,
+ i18n("<p>This is the image's curve-adjustments preview. "
+ "You can pick a spot on the image "
+ "to see the corresponding level in the histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 5, 5);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), m_gboxSettings->plainPage());
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, m_gboxSettings->plainPage() );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ m_channelCB->insertItem( i18n("Alpha") );
+ m_channelCB->setCurrentText( i18n("Luminosity") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"
+ "<b>Alpha</b>: display the alpha image-channel values. "
+ "This channel corresponds to the transparency value and "
+ "is supported by some image formats, such as PNG or TIF."));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, CurvesWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, CurvesWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setButton(CurvesWidget::LogScaleHistogram);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQWidget *curveBox = new TQWidget(m_gboxSettings->plainPage());
+ TQGridLayout* gl = new TQGridLayout(curveBox, 4, 2, 0);
+
+ m_histogramWidget = new HistogramWidget(256, 140, curveBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "curves settings changes."));
+
+ m_vGradient = new ColorGradientWidget( ColorGradientWidget::Vertical, 10, curveBox );
+ m_vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+
+ TQLabel *spacev = new TQLabel(curveBox);
+ spacev->setFixedWidth(1);
+
+ m_curvesWidget = new CurvesWidget(256, 256, m_originalImage->bits(), m_originalImage->width(),
+ m_originalImage->height(), m_originalImage->sixteenBit(),
+ curveBox);
+ TQWhatsThis::add( m_curvesWidget, i18n("<p>This is the curve drawing of the selected channel from "
+ "original image"));
+
+ TQLabel *spaceh = new TQLabel(curveBox);
+ spaceh->setFixedHeight(1);
+
+ m_hGradient = new ColorGradientWidget( ColorGradientWidget::Horizontal, 10, curveBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gl->addMultiCellWidget(m_histogramWidget, 0, 0, 2, 2);
+ gl->addMultiCellWidget(m_vGradient, 2, 2, 0, 0);
+ gl->addMultiCellWidget(spacev, 2, 2, 1, 1);
+ gl->addMultiCellWidget(m_curvesWidget, 2, 2, 2, 2);
+ gl->addMultiCellWidget(spaceh, 3, 3, 2, 2);
+ gl->addMultiCellWidget(m_hGradient, 4, 4, 2, 2);
+ gl->setRowSpacing(1, m_gboxSettings->spacingHint());
+
+ // -------------------------------------------------------------
+
+ m_curveType = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_curveFree = new TQPushButton(m_curveType);
+ m_curveType->insert(m_curveFree, FreeDrawing);
+ TDEGlobal::dirs()->addResourceType("curvefree", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("curvefree", "curvefree.png");
+ m_curveFree->setPixmap( TQPixmap( directory + "curvefree.png" ) );
+ m_curveFree->setToggleButton(true);
+ TQToolTip::add( m_curveFree, i18n( "Curve free mode" ) );
+ TQWhatsThis::add( m_curveFree, i18n("<p>With this button, you can draw your curve free-hand with the mouse."));
+ m_curveSmooth = new TQPushButton(m_curveType);
+ m_curveType->insert(m_curveSmooth, SmoothDrawing);
+ TDEGlobal::dirs()->addResourceType("curvemooth", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("curvemooth", "curvemooth.png");
+ m_curveSmooth->setPixmap( TQPixmap( directory + "curvemooth.png" ) );
+ m_curveSmooth->setToggleButton(true);
+ TQToolTip::add( m_curveSmooth, i18n( "Curve smooth mode" ) );
+ TQWhatsThis::add( m_curveSmooth, i18n("<p>With this button, you constrains the curve type to a smooth line with tension."));
+ m_curveType->setExclusive(true);
+ m_curveType->setButton(SmoothDrawing);
+ m_curveType->setFrameShape(TQFrame::NoFrame);
+
+ // -------------------------------------------------------------
+
+ m_pickerColorButtonGroup = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_pickBlack = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickBlack, BlackTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-black", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-black", "color-picker-black.png");
+ m_pickBlack->setPixmap( TQPixmap( directory + "color-picker-black.png" ) );
+ m_pickBlack->setToggleButton(true);
+ TQToolTip::add( m_pickBlack, i18n( "All channels shadow tone color picker" ) );
+ TQWhatsThis::add( m_pickBlack, i18n("<p>With this button, you can pick the color from original image used to set <b>Shadow Tone</b> "
+ "smooth curves point on Red, Green, Blue, and Luminosity channels."));
+ m_pickGray = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickGray, GrayTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-grey", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-grey", "color-picker-grey.png");
+ m_pickGray->setPixmap( TQPixmap( directory + "color-picker-grey.png" ) );
+ m_pickGray->setToggleButton(true);
+ TQToolTip::add( m_pickGray, i18n( "All channels middle tone color picker" ) );
+ TQWhatsThis::add( m_pickGray, i18n("<p>With this button, you can pick the color from original image used to set <b>Middle Tone</b> "
+ "smooth curves point on Red, Green, Blue, and Luminosity channels."));
+ m_pickWhite = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickWhite, WhiteTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-white", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-white", "color-picker-white.png");
+ m_pickWhite->setPixmap( TQPixmap( directory + "color-picker-white.png" ) );
+ m_pickWhite->setToggleButton(true);
+ TQToolTip::add( m_pickWhite, i18n( "All channels highlight tone color picker" ) );
+ TQWhatsThis::add( m_pickWhite, i18n("<p>With this button, you can pick the color from original image used to set <b>Highlight Tone</b> "
+ "smooth curves point on Red, Green, Blue, and Luminosity channels."));
+ m_pickerColorButtonGroup->setExclusive(true);
+ m_pickerColorButtonGroup->setFrameShape(TQFrame::NoFrame);
+
+ // -------------------------------------------------------------
+
+ m_resetButton = new TQPushButton(i18n("&Reset"), m_gboxSettings->plainPage());
+ m_resetButton->setPixmap( SmallIcon("reload_page", 18) );
+ TQToolTip::add( m_resetButton, i18n( "Reset current channel curves' values." ) );
+ TQWhatsThis::add( m_resetButton, i18n("<p>If you press this button, all curves' values "
+ "from the current selected channel "
+ "will be reset to the default values."));
+
+ TQHBoxLayout* l3 = new TQHBoxLayout();
+ l3->addWidget(m_curveType);
+ l3->addWidget(m_pickerColorButtonGroup);
+ l3->addWidget(m_resetButton);
+ l3->addStretch(10);
+
+ grid->addMultiCellLayout(l1, 0, 0, 1, 5);
+ grid->addMultiCellWidget(curveBox, 1, 3, 0, 5);
+ grid->addMultiCellLayout(l3, 4, 4, 1, 5);
+ grid->setMargin(0);
+ grid->setSpacing(m_gboxSettings->spacingHint());
+ grid->setRowStretch(5, 10);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_curvesWidget, TQ_SIGNAL(signalCurvesChanged()),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotSpotColorChanged(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromTarget(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // ComboBox slots.
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_curveType, TQ_SIGNAL(clicked(int)),
+ this, TQ_SLOT(slotCurveTypeChanged(int)));
+
+ // -------------------------------------------------------------
+ // Buttons slots.
+
+ connect(m_resetButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetCurrentChannel()));
+
+ connect(m_pickerColorButtonGroup, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotPickerColorButtonActived()));
+}
+
+AdjustCurvesTool::~AdjustCurvesTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void AdjustCurvesTool::slotPickerColorButtonActived()
+{
+ // Save previous rendering mode and toggle to original image.
+ m_currentPreviewMode = m_previewWidget->getRenderingPreviewMode();
+ m_previewWidget->setRenderingPreviewMode(ImageGuideWidget::PreviewOriginalImage);
+}
+
+void AdjustCurvesTool::slotSpotColorChanged(const DColor &color)
+{
+ DColor sc = color;
+
+ if ( m_pickBlack->isOn() )
+ {
+ // Black tonal curves point.
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, 1,
+ TQPoint(TQMAX(TQMAX(sc.red(), sc.green()), sc.blue()), 42*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::RedChannel, 1, TQPoint(sc.red(), 42*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::GreenChannel, 1, TQPoint(sc.green(), 42*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::BlueChannel, 1, TQPoint(sc.blue(), 42*m_histoSegments/256));
+ m_pickBlack->setOn(false);
+ }
+ else if ( m_pickGray->isOn() )
+ {
+ // Gray tonal curves point.
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, 8,
+ TQPoint(TQMAX(TQMAX(sc.red(), sc.green()), sc.blue()), 128*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::RedChannel, 8, TQPoint(sc.red(), 128*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::GreenChannel, 8, TQPoint(sc.green(), 128*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::BlueChannel, 8, TQPoint(sc.blue(), 128*m_histoSegments/256));
+ m_pickGray->setOn(false);
+ }
+ else if ( m_pickWhite->isOn() )
+ {
+ // White tonal curves point.
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, 15,
+ TQPoint(TQMAX(TQMAX(sc.red(), sc.green()), sc.blue()), 213*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::RedChannel, 15, TQPoint(sc.red(), 213*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::GreenChannel, 15, TQPoint(sc.green(), 213*m_histoSegments/256));
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::BlueChannel, 15, TQPoint(sc.blue(), 213*m_histoSegments/256));
+ m_pickWhite->setOn(false);
+ }
+ else
+ {
+ m_curvesWidget->setCurveGuide(color);
+ return;
+ }
+
+ // Calculate Red, green, blue curves.
+
+ for (int i = ImageHistogram::ValueChannel ; i <= ImageHistogram::BlueChannel ; i++)
+ m_curvesWidget->curves()->curvesCalculateCurve(i);
+
+ m_curvesWidget->repaint(false);
+
+ // restore previous rendering mode.
+ m_previewWidget->setRenderingPreviewMode(m_currentPreviewMode);
+
+ slotEffect();
+}
+
+void AdjustCurvesTool::slotColorSelectedFromTarget( const DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void AdjustCurvesTool::slotResetCurrentChannel()
+{
+ m_curvesWidget->curves()->curvesChannelReset(m_channelCB->currentItem());
+
+ m_curvesWidget->repaint();
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void AdjustCurvesTool::slotEffect()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_curvesWidget->curves()->curvesLutSetup(ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_curvesWidget->curves()->curvesLutProcess(orgData, m_destinationPreviewData, w, h);
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] orgData;
+}
+
+void AdjustCurvesTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ // Create the new empty destination image data space.
+ uchar* desData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_curvesWidget->curves()->curvesLutSetup(ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_curvesWidget->curves()->curvesLutProcess(orgData, desData, w, h);
+
+ iface->putOriginalImage(i18n("Adjust Curves"), desData);
+ kapp->restoreOverrideCursor();
+
+ delete [] orgData;
+ delete [] desData;
+}
+
+void AdjustCurvesTool::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_curvesWidget->m_channelType = CurvesWidget::ValueHistogram;
+ m_vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_curvesWidget->m_channelType = CurvesWidget::RedChannelHistogram;
+ m_vGradient->setColors( TQColor( "red" ), TQColor( "black" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_curvesWidget->m_channelType = CurvesWidget::GreenChannelHistogram;
+ m_vGradient->setColors( TQColor( "green" ), TQColor( "black" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_curvesWidget->m_channelType = CurvesWidget::BlueChannelHistogram;
+ m_vGradient->setColors( TQColor( "blue" ), TQColor( "black" ) );
+ break;
+
+ case AlphaChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::AlphaChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_curvesWidget->m_channelType = CurvesWidget::AlphaChannelHistogram;
+ m_vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+ break;
+ }
+
+ m_curveType->setButton(m_curvesWidget->curves()->getCurveType(channel));
+
+ m_curvesWidget->repaint(false);
+ m_histogramWidget->repaint(false);
+}
+
+void AdjustCurvesTool::slotScaleChanged(int scale)
+{
+ m_curvesWidget->m_scaleType = scale;
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+ m_curvesWidget->repaint(false);
+}
+
+void AdjustCurvesTool::slotCurveTypeChanged(int type)
+{
+ switch(type)
+ {
+ case SmoothDrawing:
+ {
+ m_curvesWidget->curves()->setCurveType(m_curvesWidget->m_channelType, ImageCurves::CURVE_SMOOTH);
+ m_pickerColorButtonGroup->setEnabled(true);
+ break;
+ }
+
+ case FreeDrawing:
+ {
+ m_curvesWidget->curves()->setCurveType(m_curvesWidget->m_channelType, ImageCurves::CURVE_FREE);
+ m_pickerColorButtonGroup->setEnabled(false);
+ break;
+ }
+ }
+
+ m_curvesWidget->curveTypeChanged();
+}
+
+void AdjustCurvesTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustcurves Tool");
+
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+
+ m_curvesWidget->reset();
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ m_curvesWidget->curves()->curvesChannelReset(i);
+ m_curvesWidget->curves()->setCurveType(i, (ImageCurves::CurveType)config->readNumEntry(TQString("CurveTypeChannel%1").arg(i),
+ ImageCurves::CURVE_SMOOTH));
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p = config->readPointEntry(TQString("CurveAjustmentChannel%1Point%2").arg(i).arg(j), &disable);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curvesWidget->curves()->setCurvePoint(i, j, p);
+ }
+
+ m_curvesWidget->curves()->curvesCalculateCurve(i);
+ }
+
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+ slotEffect();
+}
+
+void AdjustCurvesTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustcurves Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ config->writeEntry(TQString("CurveTypeChannel%1").arg(i), m_curvesWidget->curves()->getCurveType(i));
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint p = m_curvesWidget->curves()->getCurvePoint(i, j);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+
+ config->writeEntry(TQString("CurveAjustmentChannel%1Point%2").arg(i).arg(j), p);
+ }
+ }
+
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void AdjustCurvesTool::slotResetSettings()
+{
+ for (int channel = 0 ; channel < 5 ; channel++)
+ m_curvesWidget->curves()->curvesChannelReset(channel);
+
+ m_curvesWidget->reset();
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void AdjustCurvesTool::slotLoadSettings()
+{
+ KURL loadCurvesFile;
+
+ loadCurvesFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Select Gimp Curves File to Load")) );
+ if( loadCurvesFile.isEmpty() )
+ return;
+
+ if ( m_curvesWidget->curves()->loadCurvesFromGimpCurvesFile( loadCurvesFile ) == false )
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load from the Gimp curves text file."));
+ return;
+ }
+
+ // Refresh the current curves config.
+ slotChannelChanged(m_channelCB->currentItem());
+ slotEffect();
+}
+
+void AdjustCurvesTool::slotSaveAsSettings()
+{
+ KURL saveCurvesFile;
+
+ saveCurvesFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Gimp Curves File to Save")) );
+ if( saveCurvesFile.isEmpty() )
+ return;
+
+ if ( m_curvesWidget->curves()->saveCurvesToGimpCurvesFile( saveCurvesFile ) == false )
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save to the Gimp curves text file."));
+ return;
+ }
+
+ // Refresh the current curves config.
+ slotChannelChanged(m_channelCB->currentItem());
+}
+
+} // NameSpace DigikamAdjustCurvesImagesPlugin
diff --git a/src/imageplugins/adjustcurves/adjustcurvestool.h b/src/imageplugins/adjustcurves/adjustcurvestool.h
new file mode 100644
index 00000000..4a380530
--- /dev/null
+++ b/src/imageplugins/adjustcurves/adjustcurvestool.h
@@ -0,0 +1,145 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image histogram adjust curves.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ADJUSTCURVESTOOL_H
+#define ADJUSTCURVESTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+// Local includes.
+
+class TQComboBox;
+class TQPushButton;
+class TQHButtonGroup;
+
+namespace Digikam
+{
+class CurvesWidget;
+class HistogramWidget;
+class ColorGradientWidget;
+class EditorToolSettings;
+class ImageWidget;
+class DImg;
+class DColor;
+}
+
+namespace DigikamAdjustCurvesImagesPlugin
+{
+
+class AdjustCurvesTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ AdjustCurvesTool(TQObject *parent);
+ ~AdjustCurvesTool();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+
+private slots:
+
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+ void slotEffect();
+ void slotResetSettings();
+ void slotResetCurrentChannel();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotCurveTypeChanged(int type);
+ void slotSpotColorChanged(const Digikam::DColor& color);
+ void slotColorSelectedFromTarget(const Digikam::DColor& color);
+ void slotPickerColorButtonActived();
+
+private:
+
+ enum ColorPicker
+ {
+ BlackTonal=0,
+ GrayTonal,
+ WhiteTonal
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel,
+ AlphaChannel
+ };
+
+ enum CurvesDrawingType
+ {
+ SmoothDrawing=0,
+ FreeDrawing
+ };
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ uchar *m_destinationPreviewData;
+
+ int m_histoSegments;
+ int m_currentPreviewMode;
+
+ TQComboBox *m_channelCB;
+
+ TQPushButton *m_resetButton;
+ TQPushButton *m_pickBlack;
+ TQPushButton *m_pickGray;
+ TQPushButton *m_pickWhite;
+ TQPushButton *m_curveFree;
+ TQPushButton *m_curveSmooth;
+
+ TQHButtonGroup *m_pickerColorButtonGroup;
+ TQHButtonGroup *m_scaleBG;
+ TQHButtonGroup *m_curveType;
+
+ Digikam::CurvesWidget *m_curvesWidget;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+ Digikam::ColorGradientWidget *m_vGradient;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+
+ Digikam::DImg *m_originalImage;
+};
+
+} // NameSpace DigikamAdjustCurvesImagesPlugin
+
+#endif /* ADJUSTCURVESTOOL_H */
diff --git a/src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves.desktop b/src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves.desktop
new file mode 100644
index 00000000..377c2a33
--- /dev/null
+++ b/src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_AdjustCurves
+Name[bg]=Приставка за снимки - Настройка на кривите
+Name[da]=Billedplugin_Kurvejustering
+Name[el]=ΠρόσθετοΕικόνας_ΠροσαρμογήΚαμπύλων
+Name[fi]=TasonsäätöKäyrä
+Name[hr]=Podešavanje krivulja
+Name[it]=PluginImmagini_RegolaCurve
+Name[nl]=Afbeeldingsplugin_CurvesAanpassen
+Name[sr]=Подешавање кривих
+Name[sr@Latn]=Podešavanje krivih
+Name[sv]=Insticksprogram för justering av kurvor
+Name[tr]=ResimEklentisi_EğriAyarla
+Name[xx]=xxImagePlugin_AdjustCurvesxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+
+Comment=Image histogram adjust curves plugin for digiKam
+Comment[bg]=Приставка на digiKam за настройка кривите на хистограмите на снимки
+Comment[ca]=Connector pel digiKam d'ajust de les corbes de l'histograma d'imatges
+Comment[da]=Plugin til histogramkurvejustering i Digikam
+Comment[de]=digiKam-Modul zur Justierung der Farbkurven
+Comment[el]=Πρόσθετο προσαρμογής των καμπύλων του ιστογράμματος εικόνας για το digiKam
+Comment[es]=Histograma de imágenes, plugin de ajuste de curvas para digiKam
+Comment[et]=DigiKami pildi histogrammi kõverate kohendamise plugin
+Comment[fa]=وصلۀ منحنیهای تنظیم سابقه‌نمای تصویر برای digiKam
+Comment[fi]=Muokkaa värikanavien raja-arvoja
+Comment[fr]=Module externe pour ajuster les courbes de l'histogramme dans digiKam
+Comment[gl]=Un plugin de digiKam para o axuste das curvas do histograma da imaxe
+Comment[hr]=digiKam dodatak za histogramsko podešavanje krivulja
+Comment[is]=Íforrit fyrir digiKam sem breytir ferlum (curves) í stuðlariti myndar
+Comment[it]=Plugin di regolazione delle curve degli istogrammi delle immagini per digiKam
+Comment[ja]=digiKam カーブ補正プラグイン
+Comment[nds]=digiKam-Moduul för't Topassen vun Histogramm-Klöörbagens
+Comment[nl]=Digikam-plugin voor curvesaanpassing van afbeeldingshistogram
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਚਿੱਤਰ ਹਿਸਟੋਗਰਾਮ ਅਨੁਕੂਲ ਚਾਪ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam dostosowująca krzywe i histogram dla obrazu
+Comment[pt]=Um 'plugin' do digiKam de ajuste de curvas do histograma da imagem
+Comment[pt_BR]=Plugin de ajuste de curvas do histograma da imagem
+Comment[ru]=Модуль digiKam подстройки кривых гистограммы изображения
+Comment[sk]=digiKam plugin histogramu kriviek úprav obrázku
+Comment[sr]=digiKam-ов прикључак за подешавање кривих хистограма слике
+Comment[sr@Latn]=digiKam-ov priključak za podešavanje krivih histograma slike
+Comment[sv]=Digikam insticksprogram för justering av kurvor i bildhistogram
+Comment[tr]=digiKam için resim histogram eğrileri ayarlama eklentisi
+Comment[uk]=Втулок коригування кривих гістограми зображень для digiKam
+Comment[vi]=Phần bổ sung biểu đồ tần xuất điều chỉnh đường cong ảnh cho digiKam
+Comment[xx]=xxImage histogram adjust curves plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_adjustcurves
+author=Caulier Gilles, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves_ui.rc b/src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves_ui.rc
new file mode 100644
index 00000000..c87c2c1e
--- /dev/null
+++ b/src/imageplugins/adjustcurves/digikamimageplugin_adjustcurves_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_adjustcurves" >
+
+ <MenuBar>
+
+ <Menu name="Color" ><text>&amp;Color</text>
+ <Action name="imageplugin_adjustcurves" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_adjustcurves" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/adjustcurves/imageplugin_adjustcurves.cpp b/src/imageplugins/adjustcurves/imageplugin_adjustcurves.cpp
new file mode 100644
index 00000000..11b6e53f
--- /dev/null
+++ b/src/imageplugins/adjustcurves/imageplugin_adjustcurves.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image histogram adjust curves.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "adjustcurvestool.h"
+#include "imageplugin_adjustcurves.h"
+#include "imageplugin_adjustcurves.moc"
+
+using namespace DigikamAdjustCurvesImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY( digikamimageplugin_adjustcurves,
+ KGenericFactory<ImagePlugin_AdjustCurves>("digikamimageplugin_adjustcurves"));
+
+ImagePlugin_AdjustCurves::ImagePlugin_AdjustCurves(TQObject *parent, const char*, const TQStringList&)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_AdjustCurves")
+{
+ m_curvesAction = new TDEAction(i18n("Curves Adjust..."), "adjustcurves",
+ CTRL+SHIFT+Key_M, // NOTE: Photoshop 7 use CTRL+M (but it's used in KDE to toogle menu bar).
+ this, TQ_SLOT(slotCurvesAdjust()),
+ actionCollection(), "imageplugin_adjustcurves");
+
+ setXMLFile("digikamimageplugin_adjustcurves_ui.rc");
+
+ DDebug() << "ImagePlugin_AdjustCurves plugin loaded" << endl;
+}
+
+ImagePlugin_AdjustCurves::~ImagePlugin_AdjustCurves()
+{
+}
+
+void ImagePlugin_AdjustCurves::setEnabledActions(bool enable)
+{
+ m_curvesAction->setEnabled(enable);
+}
+
+void ImagePlugin_AdjustCurves::slotCurvesAdjust()
+{
+ AdjustCurvesTool *curves = new AdjustCurvesTool(this);
+ loadTool(curves);
+}
diff --git a/src/imageplugins/adjustcurves/imageplugin_adjustcurves.h b/src/imageplugins/adjustcurves/imageplugin_adjustcurves.h
new file mode 100644
index 00000000..f8e43578
--- /dev/null
+++ b/src/imageplugins/adjustcurves/imageplugin_adjustcurves.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image histogram adjust curves.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_ADJUSTCURVES_H
+#define IMAGEPLUGIN_ADJUSTCURVES_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_AdjustCurves : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_AdjustCurves(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_AdjustCurves();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotCurvesAdjust();
+
+private:
+
+ TDEAction *m_curvesAction;
+};
+
+#endif /* IMAGEPLUGIN_ADJUSTCURVES_H */
diff --git a/src/imageplugins/adjustlevels/Makefile.am b/src/imageplugins/adjustlevels/Makefile.am
new file mode 100644
index 00000000..2b04f4cc
--- /dev/null
+++ b/src/imageplugins/adjustlevels/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_adjustlevels_la_SOURCES = imageplugin_adjustlevels.cpp \
+ adjustlevelstool.cpp
+
+digikamimageplugin_adjustlevels_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_adjustlevels_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_adjustlevels.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_adjustlevels.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_adjustlevels_ui.rc
+
diff --git a/src/imageplugins/adjustlevels/adjustlevels.cpp b/src/imageplugins/adjustlevels/adjustlevels.cpp
new file mode 100644
index 00000000..72d5e46b
--- /dev/null
+++ b/src/imageplugins/adjustlevels/adjustlevels.cpp
@@ -0,0 +1,913 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-20
+ * Description : image histogram adjust levels.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpainter.h>
+#include <tqcombobox.h>
+#include <tqspinbox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+#include <tqhbuttongroup.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <knuminput.h>
+#include <tdemessagebox.h>
+#include <tdeselect.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "imagelevels.h"
+#include "histogramwidget.h"
+#include "dimgimagefilters.h"
+#include "adjustlevels.h"
+#include "adjustlevels.moc"
+
+namespace DigikamAdjustLevelsImagesPlugin
+{
+
+AdjustLevelDialog::AdjustLevelDialog(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Adjust Color Levels"),
+ "adjustlevels", true, false)
+{
+ m_destinationPreviewData = 0L;
+
+ Digikam::ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ m_originalImage = Digikam::DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+
+ m_histoSegments = m_originalImage.sixteenBit() ? 65535 : 255;
+ m_levels = new Digikam::ImageLevels(m_originalImage.sixteenBit());
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Adjust Color Levels"),
+ digikam_version,
+ I18N_NOOP("An image-histogram-levels adjustment plugin for digiKam."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new Digikam::ImageWidget("adjustlevels Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the image's "
+ "level-adjustments preview. You can pick a spot on the image "
+ "to see the corresponding level in the histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* grid = new TQGridLayout(gboxSettings, 16, 8, spacingHint(), 0);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ m_channelCB->insertItem( i18n("Alpha") );
+ m_channelCB->setCurrentText( i18n("Luminosity") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Here select the histogram channel to display:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"
+ "<b>Alpha</b>: display the alpha image-channel values. "
+ "This channel corresponds to the transparency value and "
+ "is supported by some image formats, such as PNG or TIF."));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Here select the histogram scale.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "The Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, gboxSettings, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing of the "
+ "selected image channel. This one is re-computed at any levels "
+ "settings changes."));
+
+ m_levelsHistogramWidget = new Digikam::HistogramWidget(256, 140, m_originalImage.bits(), m_originalImage.width(),
+ m_originalImage.height(), m_originalImage.sixteenBit(), gboxSettings, false);
+ TQWhatsThis::add( m_levelsHistogramWidget, i18n("<p>This is the histogram drawing of the selected channel "
+ "from original image"));
+
+ // -------------------------------------------------------------
+
+ m_hGradientMinInput = new KGradientSelector( TDESelector::Horizontal, gboxSettings );
+ m_hGradientMinInput->setFixedHeight( 20 );
+ m_hGradientMinInput->setMinValue(0);
+ m_hGradientMinInput->setMaxValue(m_histoSegments);
+ TQWhatsThis::add( m_hGradientMinInput, i18n("<p>Select the minimal intensity input value of the histogram."));
+ TQToolTip::add( m_hGradientMinInput, i18n( "Minimal intensity input." ) );
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMinInput->installEventFilter(this);
+
+ m_hGradientMaxInput = new KGradientSelector( TDESelector::Horizontal, gboxSettings );
+ m_hGradientMaxInput->setFixedHeight( 20 );
+ m_hGradientMaxInput->setMinValue(0);
+ m_hGradientMaxInput->setMaxValue(m_histoSegments);
+ TQWhatsThis::add( m_hGradientMaxInput, i18n("<p>Select the maximal intensity input value of the histogram."));
+ TQToolTip::add( m_hGradientMaxInput, i18n( "Maximal intensity input." ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxInput->installEventFilter(this);
+
+ m_minInput = new TQSpinBox(0, m_histoSegments, 1, gboxSettings);
+ m_minInput->setValue(0);
+ TQWhatsThis::add( m_minInput, i18n("<p>Select the minimal intensity input value of the histogram."));
+ TQToolTip::add( m_minInput, i18n( "Minimal intensity input." ) );
+
+ m_gammaInput = new KDoubleNumInput(gboxSettings);
+ m_gammaInput->setPrecision(2);
+ m_gammaInput->setRange(0.1, 3.0, 0.01);
+ m_gammaInput->setValue(1.0);
+ TQToolTip::add( m_gammaInput, i18n( "Gamma input value." ) );
+ TQWhatsThis::add( m_gammaInput, i18n("<p>Select the gamma input value."));
+
+ m_maxInput = new TQSpinBox(0, m_histoSegments, 1, gboxSettings);
+ m_maxInput->setValue(m_histoSegments);
+ TQToolTip::add( m_maxInput, i18n( "Maximal intensity input." ) );
+ TQWhatsThis::add( m_maxInput, i18n("<p>Select the maximal intensity input value of the histogram."));
+
+ m_hGradientMinOutput = new KGradientSelector( TDESelector::Horizontal, gboxSettings );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ TQWhatsThis::add( m_hGradientMinOutput, i18n("<p>Select the minimal intensity output value of the histogram."));
+ TQToolTip::add( m_hGradientMinOutput, i18n( "Minimal intensity output." ) );
+ m_hGradientMinOutput->setFixedHeight( 20 );
+ m_hGradientMinOutput->setMinValue(0);
+ m_hGradientMinOutput->setMaxValue(m_histoSegments);
+ m_hGradientMinOutput->installEventFilter(this);
+
+ m_hGradientMaxOutput = new KGradientSelector( TDESelector::Horizontal, gboxSettings );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ TQWhatsThis::add( m_hGradientMaxOutput, i18n("<p>Select the maximal intensity output value of the histogram."));
+ TQToolTip::add( m_hGradientMaxOutput, i18n( "Maximal intensity output." ) );
+ m_hGradientMaxOutput->setFixedHeight( 20 );
+ m_hGradientMaxOutput->setMinValue(0);
+ m_hGradientMaxOutput->setMaxValue(m_histoSegments);
+ m_hGradientMaxOutput->installEventFilter(this);
+
+ m_minOutput = new TQSpinBox(0, m_histoSegments, 1, gboxSettings);
+ m_minOutput->setValue(0);
+ TQToolTip::add( m_minOutput, i18n( "Minimal intensity output." ) );
+ TQWhatsThis::add( m_minOutput, i18n("<p>Select the minimal intensity output value of the histogram."));
+
+ m_maxOutput = new TQSpinBox(0, m_histoSegments, 1, gboxSettings);
+ m_maxOutput->setValue(m_histoSegments);
+ TQToolTip::add( m_maxOutput, i18n( "Maximal intensity output." ) );
+ TQWhatsThis::add( m_maxOutput, i18n("<p>Select the maximal intensity output value of the histogram."));
+
+ // -------------------------------------------------------------
+
+ m_pickerColorButtonGroup = new TQHButtonGroup(gboxSettings);
+ m_pickBlack = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickBlack, BlackTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-black", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-black", "color-picker-black.png");
+ m_pickBlack->setPixmap( TQPixmap( directory + "color-picker-black.png" ) );
+ m_pickBlack->setToggleButton(true);
+ TQToolTip::add( m_pickBlack, i18n( "All channels shadow tone color picker" ) );
+ TQWhatsThis::add( m_pickBlack, i18n("<p>With this button, you can pick the color from original image used to set <b>Shadow Tone</b> "
+ "levels input on Red, Green, Blue, and Luminosity channels."));
+ m_pickGray = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickGray, GrayTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-gray", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-gray", "color-picker-gray.png");
+ m_pickGray->setPixmap( TQPixmap( directory + "color-picker-gray.png" ) );
+ m_pickGray->setToggleButton(true);
+ TQToolTip::add( m_pickGray, i18n( "All channels middle tone color picker" ) );
+ TQWhatsThis::add( m_pickGray, i18n("<p>With this button, you can pick the color from original image used to set <b>Middle Tone</b> "
+ "levels input on Red, Green, Blue, and Luminosity channels."));
+ m_pickWhite = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickWhite, WhiteTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-white", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-white", "color-picker-white.png");
+ m_pickWhite->setPixmap( TQPixmap( directory + "color-picker-white.png" ) );
+ m_pickWhite->setToggleButton(true);
+ TQToolTip::add( m_pickWhite, i18n( "All channels highlight tone color picker" ) );
+ TQWhatsThis::add( m_pickWhite, i18n("<p>With this button, you can pick the color from original image used to set <b>Highlight Tone</b> "
+ "levels input on Red, Green, Blue, and Luminosity channels."));
+ m_pickerColorButtonGroup->setExclusive(true);
+ m_pickerColorButtonGroup->setFrameShape(TQFrame::NoFrame);
+
+ m_autoButton = new TQPushButton(gboxSettings);
+ m_autoButton->setPixmap(kapp->iconLoader()->loadIcon("system-run", (TDEIcon::Group)TDEIcon::Toolbar)); TQToolTip::add( m_autoButton, i18n( "Adjust all levels automatically." ) );
+ TQWhatsThis::add( m_autoButton, i18n("<p>If you press this button, all channel levels will be adjusted "
+ "automatically."));
+
+ m_resetButton = new TQPushButton(i18n("&Reset"), gboxSettings);
+ m_resetButton->setPixmap(kapp->iconLoader()->loadIcon("reload_page", (TDEIcon::Group)TDEIcon::Toolbar));
+ TQToolTip::add( m_resetButton, i18n( "Reset current channel levels' values." ) );
+ TQWhatsThis::add( m_resetButton, i18n("<p>If you press this button, all levels' values "
+ "from the current selected channel "
+ "will be reset to the default values."));
+
+ TQLabel *space = new TQLabel(gboxSettings);
+ space->setFixedWidth(spacingHint());
+
+ TQHBoxLayout* l3 = new TQHBoxLayout();
+ l3->addWidget(m_pickerColorButtonGroup);
+ l3->addWidget(m_autoButton);
+ l3->addWidget(space);
+ l3->addWidget(m_resetButton);
+ l3->addStretch(10);
+
+ // -------------------------------------------------------------
+
+ grid->addMultiCellLayout(l1, 0, 0, 0, 6);
+ grid->setRowSpacing(1, spacingHint());
+ grid->addMultiCellWidget(m_histogramWidget, 2, 2, 1, 5);
+ grid->setRowSpacing(3, spacingHint());
+ grid->addMultiCellWidget(m_levelsHistogramWidget, 4, 4, 1, 5);
+ grid->addMultiCellWidget(m_hGradientMinInput, 5, 5, 0, 6);
+ grid->addMultiCellWidget(m_minInput, 5, 5, 8, 8);
+ grid->setRowSpacing(6, spacingHint());
+ grid->addMultiCellWidget(m_hGradientMaxInput, 7, 7, 0, 6);
+ grid->addMultiCellWidget(m_maxInput, 7, 7, 8, 8);
+ grid->setRowSpacing(8, spacingHint());
+ grid->addMultiCellWidget(m_gammaInput, 9, 9, 0, 8);
+ grid->setRowSpacing(10, spacingHint());
+ grid->addMultiCellWidget(m_hGradientMinOutput, 11, 11, 0, 6);
+ grid->addMultiCellWidget(m_minOutput, 11, 11, 8, 8);
+ grid->setRowSpacing(12, spacingHint());
+ grid->addMultiCellWidget(m_hGradientMaxOutput, 13, 13, 0, 6);
+ grid->addMultiCellWidget(m_maxOutput, 13, 13, 8, 8);
+ grid->setRowSpacing(14, spacingHint());
+ grid->addMultiCellLayout(l3, 15, 15, 0, 8);
+ grid->setRowStretch(16, 10);
+ grid->setColStretch(2, 10);
+ grid->setColSpacing(0, 5);
+ grid->setColSpacing(6, 5);
+ grid->setColSpacing(7, spacingHint());
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+ // Channels and scale selection slots.
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotSpotColorChanged( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // Color sliders and spinbox slots.
+
+ connect(m_hGradientMinInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMinInputSpinBox(int)));
+
+ connect(m_minInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ connect(m_gammaInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotGammaInputchanged(double)));
+
+ connect(m_hGradientMaxInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMaxInputSpinBox(int)));
+
+ connect(m_maxInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ connect(m_hGradientMinOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMinOutputSpinBox(int)));
+
+ connect(m_minOutput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ connect(m_hGradientMaxOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMaxOutputSpinBox(int)));
+
+ connect(m_maxOutput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ // -------------------------------------------------------------
+ // Bouttons slots.
+
+ connect(m_autoButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAutoLevels()));
+
+ connect(m_resetButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetCurrentChannel()));
+
+ connect(m_pickerColorButtonGroup, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotPickerColorButtonActived()));
+
+}
+
+AdjustLevelDialog::~AdjustLevelDialog()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_levelsHistogramWidget;
+ delete m_levels;
+}
+
+void AdjustLevelDialog::slotPickerColorButtonActived()
+{
+ // Save previous rendering mode and toggle to original image.
+ m_currentPreviewMode = m_previewWidget->getRenderingPreviewMode();
+ m_previewWidget->setRenderingPreviewMode(Digikam::ImageGuideWidget::PreviewOriginalImage);
+}
+
+void AdjustLevelDialog::slotSpotColorChanged(const Digikam::DColor &color)
+{
+ if ( m_pickBlack->isOn() )
+ {
+ // Black tonal levels point.
+ m_levels->levelsBlackToneAdjustByColors(m_channelCB->currentItem(), color);
+ m_pickBlack->setOn(false);
+ }
+ else if ( m_pickGray->isOn() )
+ {
+ // Gray tonal levels point.
+ m_levels->levelsGrayToneAdjustByColors(m_channelCB->currentItem(), color);
+ m_pickGray->setOn(false);
+ }
+ else if ( m_pickWhite->isOn() )
+ {
+ // White tonal levels point.
+ m_levels->levelsWhiteToneAdjustByColors(m_channelCB->currentItem(), color);
+ m_pickWhite->setOn(false);
+ }
+ else
+ {
+ m_levelsHistogramWidget->setHistogramGuideByColor(color);
+ return;
+ }
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+
+ // restore previous rendering mode.
+ m_previewWidget->setRenderingPreviewMode(m_currentPreviewMode);
+
+ slotEffect();
+}
+
+void AdjustLevelDialog::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void AdjustLevelDialog::slotGammaInputchanged(double val)
+{
+ blockSignals(true);
+ m_levels->setLevelGammaValue(m_channelCB->currentItem(), val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelDialog::slotAdjustMinInputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val < m_hGradientMaxInput->value() )
+ val = m_hGradientMaxInput->value();
+
+ m_minInput->setValue(m_histoSegments - val);
+ m_hGradientMinInput->setValue( val );
+ m_levels->setLevelLowInputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelDialog::slotAdjustMaxInputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val > m_hGradientMinInput->value() )
+ val = m_hGradientMinInput->value();
+
+ m_maxInput->setValue(m_histoSegments - val);
+ m_hGradientMaxInput->setValue( val );
+ m_levels->setLevelHighInputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelDialog::slotAdjustMinOutputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val < m_hGradientMaxOutput->value() )
+ val = m_hGradientMaxOutput->value();
+
+ m_minOutput->setValue(m_histoSegments - val);
+ m_hGradientMinOutput->setValue( val );
+ m_levels->setLevelLowOutputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelDialog::slotAdjustMaxOutputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val > m_hGradientMinOutput->value() )
+ val = m_hGradientMinOutput->value();
+
+ m_maxOutput->setValue(m_histoSegments - val);
+ m_hGradientMaxOutput->setValue( val );
+ m_levels->setLevelHighOutputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelDialog::slotAdjustSliders()
+{
+ adjustSliders(m_minInput->value(), m_gammaInput->value(),
+ m_maxInput->value(), m_minOutput->value(),
+ m_maxOutput->value());
+}
+
+void AdjustLevelDialog::adjustSliders(int minIn, double gamIn, int maxIn, int minOut, int maxOut)
+{
+ m_hGradientMinInput->setValue(m_histoSegments - minIn);
+ m_hGradientMaxInput->setValue(m_histoSegments - maxIn);
+ m_gammaInput->setValue(gamIn);
+ m_hGradientMinOutput->setValue(m_histoSegments - minOut);
+ m_hGradientMaxOutput->setValue(m_histoSegments - maxOut);
+}
+
+void AdjustLevelDialog::slotResetCurrentChannel()
+{
+ m_levels->levelsChannelReset(m_channelCB->currentItem());
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+ m_levelsHistogramWidget->reset();
+
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void AdjustLevelDialog::slotAutoLevels()
+{
+ // Calculate Auto levels.
+ m_levels->levelsAuto(m_levelsHistogramWidget->m_imageHistogram);
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+
+ slotEffect();
+}
+
+void AdjustLevelDialog::slotEffect()
+{
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_levels->levelsLutSetup(Digikam::ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_levels->levelsLutProcess(orgData, m_destinationPreviewData, w, h);
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ delete [] orgData;
+}
+
+void AdjustLevelDialog::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ // Create the new empty destination image data space.
+ uchar* desData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_levels->levelsLutSetup(Digikam::ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_levels->levelsLutProcess(orgData, desData, w, h);
+
+ iface->putOriginalImage(i18n("Adjust Level"), desData);
+ kapp->restoreOverrideCursor();
+
+ delete [] orgData;
+ delete [] desData;
+ accept();
+}
+
+void AdjustLevelDialog::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_levelsHistogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+
+ case AlphaChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::AlphaChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = Digikam::HistogramWidget::AlphaChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+ }
+
+ adjustSliders(m_levels->getLevelLowInputValue(channel),
+ m_levels->getLevelGammaValue(channel),
+ m_levels->getLevelHighInputValue(channel),
+ m_levels->getLevelLowOutputValue(channel),
+ m_levels->getLevelHighOutputValue(channel));
+
+ m_levelsHistogramWidget->repaint(false);
+ m_histogramWidget->repaint(false);
+}
+
+void AdjustLevelDialog::slotScaleChanged(int scale)
+{
+ m_levelsHistogramWidget->m_scaleType = scale;
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+ m_levelsHistogramWidget->repaint(false);
+}
+
+void AdjustLevelDialog::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustlevels Tool Dialog");
+
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ bool sb = m_originalImage.sixteenBit();
+ int max = sb ? 65535 : 255;
+ double gamma = config->readDoubleNumEntry(TQString("GammaChannel%1").arg(i), 1.0);
+ int lowInput = config->readNumEntry(TQString("LowInputChannel%1").arg(i), 0);
+ int lowOutput = config->readNumEntry(TQString("LowOutputChannel%1").arg(i), 0);
+ int highInput = config->readNumEntry(TQString("HighInputChannel%1").arg(i), max);
+ int highOutput = config->readNumEntry(TQString("HighOutputChannel%1").arg(i), max);
+
+ m_levels->setLevelGammaValue(i, gamma);
+ m_levels->setLevelLowInputValue(i, sb ? lowInput*255 : lowInput);
+ m_levels->setLevelHighInputValue(i, sb ? highInput*255 : highInput);
+ m_levels->setLevelLowOutputValue(i, sb ? lowOutput*255 : lowOutput);
+ m_levels->setLevelHighOutputValue(i, sb ? highOutput*255 : highOutput);
+ }
+
+ m_levelsHistogramWidget->reset();
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+
+ // This is mandatory here to set spinbox values because slot connections
+ // can be not set completely at plugin startup.
+ m_minInput->setValue(m_levels->getLevelLowInputValue(m_channelCB->currentItem()));
+ m_minOutput->setValue(m_levels->getLevelLowOutputValue(m_channelCB->currentItem()));
+ m_maxInput->setValue(m_levels->getLevelHighInputValue(m_channelCB->currentItem()));
+ m_maxOutput->setValue(m_levels->getLevelHighOutputValue(m_channelCB->currentItem()));
+}
+
+void AdjustLevelDialog::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustlevels Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ bool sb = m_originalImage.sixteenBit();
+ double gamma = m_levels->getLevelGammaValue(i);
+ int lowInput = m_levels->getLevelLowInputValue(i);
+ int lowOutput = m_levels->getLevelLowOutputValue(i);
+ int highInput = m_levels->getLevelHighInputValue(i);
+ int highOutput = m_levels->getLevelHighOutputValue(i);
+
+ config->writeEntry(TQString("GammaChannel%1").arg(i), gamma);
+ config->writeEntry(TQString("LowInputChannel%1").arg(i), sb ? lowInput/255 : lowInput);
+ config->writeEntry(TQString("LowOutputChannel%1").arg(i), sb ? lowOutput/255 : lowOutput);
+ config->writeEntry(TQString("HighInputChannel%1").arg(i), sb ? highInput/255 : highInput);
+ config->writeEntry(TQString("HighOutputChannel%1").arg(i), sb ? highOutput/255 : highOutput);
+ }
+
+ config->sync();
+}
+
+void AdjustLevelDialog::resetValues()
+{
+ for (int channel = 0 ; channel < 5 ; ++channel)
+ m_levels->levelsChannelReset(channel);
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+ m_levelsHistogramWidget->reset();
+ m_histogramWidget->reset();
+}
+
+// Load all settings.
+void AdjustLevelDialog::slotUser3()
+{
+ KURL loadLevelsFile;
+
+ loadLevelsFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Select Gimp Levels File to Load")) );
+ if( loadLevelsFile.isEmpty() )
+ return;
+
+ if ( m_levels->loadLevelsFromGimpLevelsFile( loadLevelsFile ) == false )
+ {
+ KMessageBox::error(this, i18n("Cannot load from the Gimp levels text file."));
+ return;
+ }
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+}
+
+// Save all settings.
+void AdjustLevelDialog::slotUser2()
+{
+ KURL saveLevelsFile;
+
+ saveLevelsFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Gimp Levels File to Save")) );
+ if( saveLevelsFile.isEmpty() )
+ return;
+
+ if ( m_levels->saveLevelsToGimpLevelsFile( saveLevelsFile ) == false )
+ {
+ KMessageBox::error(this, i18n("Cannot save to the Gimp levels text file."));
+ return;
+ }
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+}
+
+// See B.K.O #146636: use event filter with all level slider to display a
+// guide over level histogram.
+bool AdjustLevelDialog::eventFilter(TQObject *obj, TQEvent *ev)
+{
+ if ( obj == m_hGradientMinInput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_minInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_minInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ m_levelsHistogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if ( obj == m_hGradientMaxInput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_maxInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_maxInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ m_levelsHistogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if ( obj == m_hGradientMinOutput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_minOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_minOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ m_levelsHistogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if ( obj == m_hGradientMaxOutput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_maxOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_maxOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowHistogramGuide(int)));
+
+ m_levelsHistogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // pass the event on to the parent class
+ return KDialogBase::eventFilter(obj, ev);
+ }
+}
+
+void AdjustLevelDialog::slotShowHistogramGuide(int v)
+{
+ Digikam::DColor color(v, v, v, v, m_originalImage.sixteenBit());
+ m_levelsHistogramWidget->setHistogramGuideByColor(color);
+}
+
+} // NameSpace DigikamAdjustLevelsImagesPlugin
diff --git a/src/imageplugins/adjustlevels/adjustlevels.h b/src/imageplugins/adjustlevels/adjustlevels.h
new file mode 100644
index 00000000..1b1ad9be
--- /dev/null
+++ b/src/imageplugins/adjustlevels/adjustlevels.h
@@ -0,0 +1,153 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-20
+ * Description : image histogram adjust levels.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ADJUSTLEVELS_H
+#define ADJUSTLEVELS_H
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+#include "dimg.h"
+
+class TQComboBox;
+class TQSpinBox;
+class TQPushButton;
+class TQHButtonGroup;
+
+class KDoubleSpinBox;
+class KGradientSelector;
+class KDoubleNumInput;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ImageWidget;
+class ImageLevels;
+}
+
+namespace DigikamAdjustLevelsImagesPlugin
+{
+
+class AdjustLevelDialog : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ AdjustLevelDialog(TQWidget *parent);
+ ~AdjustLevelDialog();
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+ void adjustSliders(int minIn, double gamIn, int maxIn, int minOut, int maxOut);
+ bool eventFilter(TQObject *o, TQEvent *e);
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void slotEffect();
+ void slotResetCurrentChannel();
+ void slotAutoLevels();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotAdjustSliders();
+ void slotGammaInputchanged(double val);
+ void slotAdjustMinInputSpinBox(int val);
+ void slotAdjustMaxInputSpinBox(int val);
+ void slotAdjustMinOutputSpinBox(int val);
+ void slotAdjustMaxOutputSpinBox(int val);
+ void slotSpotColorChanged(const Digikam::DColor &color);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+ void slotPickerColorButtonActived();
+ void slotShowHistogramGuide(int v);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel,
+ AlphaChannel
+ };
+
+ enum ColorPicker
+ {
+ BlackTonal=0,
+ GrayTonal,
+ WhiteTonal
+ };
+
+ uchar *m_destinationPreviewData;
+
+ int m_histoSegments;
+ int m_currentPreviewMode;
+
+ TQComboBox *m_channelCB;
+
+ TQSpinBox *m_minInput;
+ TQSpinBox *m_maxInput;
+ TQSpinBox *m_minOutput;
+ TQSpinBox *m_maxOutput;
+
+ TQPushButton *m_autoButton;
+ TQPushButton *m_resetButton;
+ TQPushButton *m_pickBlack;
+ TQPushButton *m_pickGray;
+ TQPushButton *m_pickWhite;
+
+ TQHButtonGroup *m_pickerColorButtonGroup;
+ TQHButtonGroup *m_scaleBG;
+
+ KDoubleNumInput *m_gammaInput;
+
+ KGradientSelector *m_hGradientMinInput;
+ KGradientSelector *m_hGradientMaxInput;
+ KGradientSelector *m_hGradientMinOutput;
+ KGradientSelector *m_hGradientMaxOutput;
+
+ Digikam::HistogramWidget *m_levelsHistogramWidget;
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ImageLevels *m_levels;
+ Digikam::DImg m_originalImage;
+};
+
+} // NameSpace DigikamAdjustLevelsImagesPlugin
+
+#endif /* ADJUSTLEVELS_H */
diff --git a/src/imageplugins/adjustlevels/adjustlevelstool.cpp b/src/imageplugins/adjustlevels/adjustlevelstool.cpp
new file mode 100644
index 00000000..28066774
--- /dev/null
+++ b/src/imageplugins/adjustlevels/adjustlevelstool.cpp
@@ -0,0 +1,901 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-20
+ * Description : image histogram adjust levels.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqlayout.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <tdepopupmenu.h>
+#include <tdeselect.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "editortoolsettings.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "imagelevels.h"
+#include "histogramwidget.h"
+#include "dimgimagefilters.h"
+#include "adjustlevelstool.h"
+#include "adjustlevelstool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamAdjustLevelsImagesPlugin
+{
+
+AdjustLevelsTool::AdjustLevelsTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ m_destinationPreviewData = 0;
+
+ ImageIface iface(0, 0);
+ m_originalImage = iface.getOriginalImg();
+
+ m_histoSegments = m_originalImage->sixteenBit() ? 65535 : 255;
+ m_levels = new ImageLevels(m_originalImage->sixteenBit());
+
+ setName("adjustlevels");
+ setToolName(i18n("Adjust Levels"));
+ setToolIcon(SmallIcon("adjustlevels"));
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImageWidget("adjustlevels Tool", 0,
+ i18n("<p>Here you can see the image's "
+ "level-adjustments preview. You can pick a spot on the image "
+ "to see the corresponding level in the histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 20, 6);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), m_gboxSettings->plainPage());
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, m_gboxSettings->plainPage() );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ m_channelCB->insertItem( i18n("Alpha") );
+ m_channelCB->setCurrentText( i18n("Luminosity") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Here select the histogram channel to display:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"
+ "<b>Alpha</b>: display the alpha image-channel values. "
+ "This channel corresponds to the transparency value and "
+ "is supported by some image formats, such as PNG or TIF."));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Here select the histogram scale.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "The Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ m_histogramWidget = new HistogramWidget(256, 140, m_gboxSettings->plainPage(), false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing of the "
+ "selected image channel. This one is re-computed at any levels "
+ "settings changes."));
+
+ m_levelsHistogramWidget = new HistogramWidget(256, 140, m_originalImage->bits(), m_originalImage->width(),
+ m_originalImage->height(), m_originalImage->sixteenBit(), m_gboxSettings, false);
+ TQWhatsThis::add( m_levelsHistogramWidget, i18n("<p>This is the histogram drawing of the selected channel "
+ "from original image"));
+
+ // -------------------------------------------------------------
+
+ m_hGradientMinInput = new KGradientSelector( TDESelector::Horizontal, m_gboxSettings->plainPage() );
+ m_hGradientMinInput->setFixedHeight( 20 );
+ m_hGradientMinInput->setMinValue(0);
+ m_hGradientMinInput->setMaxValue(m_histoSegments);
+ TQWhatsThis::add( m_hGradientMinInput, i18n("<p>Select the minimal intensity input value of the histogram."));
+ TQToolTip::add( m_hGradientMinInput, i18n( "Minimal intensity input." ) );
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMinInput->installEventFilter(this);
+
+ m_hGradientMaxInput = new KGradientSelector( TDESelector::Horizontal, m_gboxSettings->plainPage() );
+ m_hGradientMaxInput->setFixedHeight( 20 );
+ m_hGradientMaxInput->setMinValue(0);
+ m_hGradientMaxInput->setMaxValue(m_histoSegments);
+ TQWhatsThis::add( m_hGradientMaxInput, i18n("<p>Select the maximal intensity input value of the histogram."));
+ TQToolTip::add( m_hGradientMaxInput, i18n( "Maximal intensity input." ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxInput->installEventFilter(this);
+
+ m_minInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_minInput->input()->setRange(0, m_histoSegments, 1, false);
+ m_minInput->setDefaultValue(0);
+ TQWhatsThis::add( m_minInput, i18n("<p>Select the minimal intensity input value of the histogram."));
+ TQToolTip::add( m_minInput, i18n( "Minimal intensity input." ) );
+
+ m_gammaInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_gammaInput->setPrecision(2);
+ m_gammaInput->setRange(0.1, 3.0, 0.01, true);
+ m_gammaInput->setDefaultValue(1.0);
+ TQToolTip::add( m_gammaInput, i18n( "Gamma input value." ) );
+ TQWhatsThis::add( m_gammaInput, i18n("<p>Select the gamma input value."));
+
+ m_maxInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_maxInput->input()->setRange(0, m_histoSegments, 1, false);
+ m_maxInput->setDefaultValue(m_histoSegments);
+ TQToolTip::add( m_maxInput, i18n( "Maximal intensity input." ) );
+ TQWhatsThis::add( m_maxInput, i18n("<p>Select the maximal intensity input value of the histogram."));
+
+ m_hGradientMinOutput = new KGradientSelector( TDESelector::Horizontal, m_gboxSettings->plainPage() );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ TQWhatsThis::add( m_hGradientMinOutput, i18n("<p>Select the minimal intensity output value of the histogram."));
+ TQToolTip::add( m_hGradientMinOutput, i18n( "Minimal intensity output." ) );
+ m_hGradientMinOutput->setFixedHeight( 20 );
+ m_hGradientMinOutput->setMinValue(0);
+ m_hGradientMinOutput->setMaxValue(m_histoSegments);
+ m_hGradientMinOutput->installEventFilter(this);
+
+ m_hGradientMaxOutput = new KGradientSelector( TDESelector::Horizontal, m_gboxSettings->plainPage() );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ TQWhatsThis::add( m_hGradientMaxOutput, i18n("<p>Select the maximal intensity output value of the histogram."));
+ TQToolTip::add( m_hGradientMaxOutput, i18n( "Maximal intensity output." ) );
+ m_hGradientMaxOutput->setFixedHeight( 20 );
+ m_hGradientMaxOutput->setMinValue(0);
+ m_hGradientMaxOutput->setMaxValue(m_histoSegments);
+ m_hGradientMaxOutput->installEventFilter(this);
+
+ m_minOutput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_minOutput->input()->setRange(0, m_histoSegments, 1, false);
+ m_minOutput->setDefaultValue(0);
+ TQToolTip::add( m_minOutput, i18n( "Minimal intensity output." ) );
+ TQWhatsThis::add( m_minOutput, i18n("<p>Select the minimal intensity output value of the histogram."));
+
+ m_maxOutput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_maxOutput->input()->setRange(0, m_histoSegments, 1, false);
+ m_maxOutput->setDefaultValue(m_histoSegments);
+ TQToolTip::add( m_maxOutput, i18n( "Maximal intensity output." ) );
+ TQWhatsThis::add( m_maxOutput, i18n("<p>Select the maximal intensity output value of the histogram."));
+
+ // -------------------------------------------------------------
+
+ m_pickerColorButtonGroup = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_pickBlack = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickBlack, BlackTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-black", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-black", "color-picker-black.png");
+ m_pickBlack->setPixmap( TQPixmap( directory + "color-picker-black.png" ) );
+ m_pickBlack->setToggleButton(true);
+ TQToolTip::add( m_pickBlack, i18n( "All channels shadow tone color picker" ) );
+ TQWhatsThis::add( m_pickBlack, i18n("<p>With this button, you can pick the color from original image used to set <b>Shadow Tone</b> "
+ "levels input on Red, Green, Blue, and Luminosity channels."));
+ m_pickGray = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickGray, GrayTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-gray", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-grey", "color-picker-grey.png");
+ m_pickGray->setPixmap( TQPixmap( directory + "color-picker-grey.png" ) );
+ m_pickGray->setToggleButton(true);
+ TQToolTip::add( m_pickGray, i18n( "All channels middle tone color picker" ) );
+ TQWhatsThis::add( m_pickGray, i18n("<p>With this button, you can pick the color from original image used to set <b>Middle Tone</b> "
+ "levels input on Red, Green, Blue, and Luminosity channels."));
+ m_pickWhite = new TQPushButton(m_pickerColorButtonGroup);
+ m_pickerColorButtonGroup->insert(m_pickWhite, WhiteTonal);
+ TDEGlobal::dirs()->addResourceType("color-picker-white", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-white", "color-picker-white.png");
+ m_pickWhite->setPixmap( TQPixmap( directory + "color-picker-white.png" ) );
+ m_pickWhite->setToggleButton(true);
+ TQToolTip::add( m_pickWhite, i18n( "All channels highlight tone color picker" ) );
+ TQWhatsThis::add( m_pickWhite, i18n("<p>With this button, you can pick the color from original image used to set <b>Highlight Tone</b> "
+ "levels input on Red, Green, Blue, and Luminosity channels."));
+ m_pickerColorButtonGroup->setExclusive(true);
+ m_pickerColorButtonGroup->setFrameShape(TQFrame::NoFrame);
+
+ m_autoButton = new TQPushButton(m_gboxSettings->plainPage());
+ m_autoButton->setPixmap(kapp->iconLoader()->loadIcon("system-run", (TDEIcon::Group)TDEIcon::Toolbar)); TQToolTip::add( m_autoButton, i18n( "Adjust all levels automatically." ) );
+ TQWhatsThis::add( m_autoButton, i18n("<p>If you press this button, all channel levels will be adjusted "
+ "automatically."));
+
+ m_resetButton = new TQPushButton(i18n("&Reset"), m_gboxSettings->plainPage());
+ m_resetButton->setPixmap(kapp->iconLoader()->loadIcon("reload_page", (TDEIcon::Group)TDEIcon::Toolbar));
+ TQToolTip::add( m_resetButton, i18n( "Reset current channel levels' values." ) );
+ TQWhatsThis::add( m_resetButton, i18n("<p>If you press this button, all levels' values "
+ "from the current selected channel "
+ "will be reset to the default values."));
+
+ TQLabel *space = new TQLabel(m_gboxSettings->plainPage());
+ space->setFixedWidth(m_gboxSettings->spacingHint());
+
+ TQHBoxLayout* l3 = new TQHBoxLayout();
+ l3->addWidget(m_pickerColorButtonGroup);
+ l3->addWidget(m_autoButton);
+ l3->addWidget(space);
+ l3->addWidget(m_resetButton);
+ l3->addStretch(10);
+
+ // -------------------------------------------------------------
+
+ grid->addMultiCellLayout(l1, 0, 0, 0, 6);
+ grid->addMultiCellWidget(m_histogramWidget, 2, 2, 1, 5);
+ grid->addMultiCellWidget(m_levelsHistogramWidget, 4, 4, 1, 5);
+ grid->addMultiCellWidget(m_hGradientMinInput, 5, 5, 0, 6);
+ grid->addMultiCellWidget(m_hGradientMaxInput, 7, 7, 0, 6);
+ grid->addMultiCellWidget(m_minInput, 9, 9, 1, 1);
+ grid->addMultiCellWidget(m_maxInput, 9, 9, 5, 5);
+ grid->addMultiCellWidget(m_gammaInput, 11, 11, 0, 6);
+ grid->addMultiCellWidget(m_hGradientMinOutput, 13, 13, 0, 6);
+ grid->addMultiCellWidget(m_hGradientMaxOutput, 15, 15, 0, 6);
+ grid->addMultiCellWidget(m_minOutput, 17, 17, 1, 1);
+ grid->addMultiCellWidget(m_maxOutput, 17, 17, 5, 5);
+ grid->addMultiCellLayout(l3, 19, 19, 0, 6);
+ grid->setRowStretch(20, 10);
+ grid->setColStretch(3, 10);
+ grid->setMargin(0);
+ grid->setSpacing(5);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+ // Channels and scale selection slots.
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotSpotColorChanged(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromTarget(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // Color sliders and spinbox slots.
+
+ connect(m_hGradientMinInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMinInputSpinBox(int)));
+
+ connect(m_minInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ connect(m_gammaInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotGammaInputchanged(double)));
+
+ connect(m_hGradientMaxInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMaxInputSpinBox(int)));
+
+ connect(m_maxInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ connect(m_hGradientMinOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMinOutputSpinBox(int)));
+
+ connect(m_minOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ connect(m_hGradientMaxOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustMaxOutputSpinBox(int)));
+
+ connect(m_maxOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotAdjustSliders()));
+
+ // -------------------------------------------------------------
+ // Bouttons slots.
+
+ connect(m_autoButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAutoLevels()));
+
+ connect(m_resetButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetCurrentChannel()));
+
+ connect(m_pickerColorButtonGroup, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotPickerColorButtonActived()));
+}
+
+AdjustLevelsTool::~AdjustLevelsTool()
+{
+ delete [] m_destinationPreviewData;
+ delete m_levels;
+}
+
+void AdjustLevelsTool::slotPickerColorButtonActived()
+{
+ // Save previous rendering mode and toggle to original image.
+ m_currentPreviewMode = m_previewWidget->getRenderingPreviewMode();
+ m_previewWidget->setRenderingPreviewMode(ImageGuideWidget::PreviewOriginalImage);
+}
+
+void AdjustLevelsTool::slotSpotColorChanged(const DColor& color)
+{
+ if ( m_pickBlack->isOn() )
+ {
+ // Black tonal levels point.
+ m_levels->levelsBlackToneAdjustByColors(m_channelCB->currentItem(), color);
+ m_pickBlack->setOn(false);
+ }
+ else if ( m_pickGray->isOn() )
+ {
+ // Gray tonal levels point.
+ m_levels->levelsGrayToneAdjustByColors(m_channelCB->currentItem(), color);
+ m_pickGray->setOn(false);
+ }
+ else if ( m_pickWhite->isOn() )
+ {
+ // White tonal levels point.
+ m_levels->levelsWhiteToneAdjustByColors(m_channelCB->currentItem(), color);
+ m_pickWhite->setOn(false);
+ }
+ else
+ {
+ m_levelsHistogramWidget->setHistogramGuideByColor(color);
+ return;
+ }
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+
+ // restore previous rendering mode.
+ m_previewWidget->setRenderingPreviewMode(m_currentPreviewMode);
+
+ slotEffect();
+}
+
+void AdjustLevelsTool::slotColorSelectedFromTarget(const DColor& color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void AdjustLevelsTool::slotGammaInputchanged(double val)
+{
+ blockSignals(true);
+ m_levels->setLevelGammaValue(m_channelCB->currentItem(), val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelsTool::slotAdjustMinInputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val < m_hGradientMaxInput->value() )
+ val = m_hGradientMaxInput->value();
+
+ m_minInput->setValue(m_histoSegments - val);
+ m_hGradientMinInput->setValue( val );
+ m_levels->setLevelLowInputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelsTool::slotAdjustMaxInputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val > m_hGradientMinInput->value() )
+ val = m_hGradientMinInput->value();
+
+ m_maxInput->setValue(m_histoSegments - val);
+ m_hGradientMaxInput->setValue( val );
+ m_levels->setLevelHighInputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelsTool::slotAdjustMinOutputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val < m_hGradientMaxOutput->value() )
+ val = m_hGradientMaxOutput->value();
+
+ m_minOutput->setValue(m_histoSegments - val);
+ m_hGradientMinOutput->setValue( val );
+ m_levels->setLevelLowOutputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelsTool::slotAdjustMaxOutputSpinBox(int val)
+{
+ blockSignals(true);
+
+ if ( val > m_hGradientMinOutput->value() )
+ val = m_hGradientMinOutput->value();
+
+ m_maxOutput->setValue(m_histoSegments - val);
+ m_hGradientMaxOutput->setValue( val );
+ m_levels->setLevelHighOutputValue(m_channelCB->currentItem(), m_histoSegments - val);
+ blockSignals(false);
+ slotTimer();
+}
+
+void AdjustLevelsTool::slotAdjustSliders()
+{
+ adjustSliders(m_minInput->value(), m_gammaInput->value(),
+ m_maxInput->value(), m_minOutput->value(),
+ m_maxOutput->value());
+}
+
+void AdjustLevelsTool::adjustSliders(int minIn, double gamIn, int maxIn, int minOut, int maxOut)
+{
+ m_hGradientMinInput->setValue(m_histoSegments - minIn);
+ m_hGradientMaxInput->setValue(m_histoSegments - maxIn);
+ m_gammaInput->setValue(gamIn);
+ m_hGradientMinOutput->setValue(m_histoSegments - minOut);
+ m_hGradientMaxOutput->setValue(m_histoSegments - maxOut);
+}
+
+void AdjustLevelsTool::slotResetCurrentChannel()
+{
+ m_levels->levelsChannelReset(m_channelCB->currentItem());
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+ m_levelsHistogramWidget->reset();
+
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void AdjustLevelsTool::slotAutoLevels()
+{
+ // Calculate Auto levels.
+ m_levels->levelsAuto(m_levelsHistogramWidget->m_imageHistogram);
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+
+ slotEffect();
+}
+
+void AdjustLevelsTool::slotEffect()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_levels->levelsLutSetup(ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_levels->levelsLutProcess(orgData, m_destinationPreviewData, w, h);
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ delete [] orgData;
+}
+
+void AdjustLevelsTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *orgData = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ // Create the new empty destination image data space.
+ uchar* desData = new uchar[w*h*(sb ? 8 : 4)];
+
+ // Calculate the LUT to apply on the image.
+ m_levels->levelsLutSetup(ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ m_levels->levelsLutProcess(orgData, desData, w, h);
+
+ iface->putOriginalImage(i18n("Adjust Level"), desData);
+ kapp->restoreOverrideCursor();
+
+ delete [] orgData;
+ delete [] desData;
+}
+
+void AdjustLevelsTool::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_levelsHistogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+
+ case AlphaChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::AlphaChannelHistogram;
+ m_levelsHistogramWidget->m_channelType = HistogramWidget::AlphaChannelHistogram;
+ m_hGradientMinInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxInput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMinOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ m_hGradientMaxOutput->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+ }
+
+ adjustSliders(m_levels->getLevelLowInputValue(channel),
+ m_levels->getLevelGammaValue(channel),
+ m_levels->getLevelHighInputValue(channel),
+ m_levels->getLevelLowOutputValue(channel),
+ m_levels->getLevelHighOutputValue(channel));
+
+ m_levelsHistogramWidget->repaint(false);
+ m_histogramWidget->repaint(false);
+}
+
+void AdjustLevelsTool::slotScaleChanged(int scale)
+{
+ m_levelsHistogramWidget->m_scaleType = scale;
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+ m_levelsHistogramWidget->repaint(false);
+}
+
+void AdjustLevelsTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustlevels Tool");
+
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ bool sb = m_originalImage->sixteenBit();
+ int max = sb ? 65535 : 255;
+ double gamma = config->readDoubleNumEntry(TQString("GammaChannel%1").arg(i), 1.0);
+ int lowInput = config->readNumEntry(TQString("LowInputChannel%1").arg(i), 0);
+ int lowOutput = config->readNumEntry(TQString("LowOutputChannel%1").arg(i), 0);
+ int highInput = config->readNumEntry(TQString("HighInputChannel%1").arg(i), max);
+ int highOutput = config->readNumEntry(TQString("HighOutputChannel%1").arg(i), max);
+
+ m_levels->setLevelGammaValue(i, gamma);
+ m_levels->setLevelLowInputValue(i, sb ? lowInput*255 : lowInput);
+ m_levels->setLevelHighInputValue(i, sb ? highInput*255 : highInput);
+ m_levels->setLevelLowOutputValue(i, sb ? lowOutput*255 : lowOutput);
+ m_levels->setLevelHighOutputValue(i, sb ? highOutput*255 : highOutput);
+ }
+
+ m_levelsHistogramWidget->reset();
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+
+ // This is mandatory here to set spinbox values because slot connections
+ // can be not set completely at plugin startup.
+ m_minInput->setValue(m_levels->getLevelLowInputValue(m_channelCB->currentItem()));
+ m_minOutput->setValue(m_levels->getLevelLowOutputValue(m_channelCB->currentItem()));
+ m_maxInput->setValue(m_levels->getLevelHighInputValue(m_channelCB->currentItem()));
+ m_maxOutput->setValue(m_levels->getLevelHighOutputValue(m_channelCB->currentItem()));
+}
+
+void AdjustLevelsTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("adjustlevels Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ for (int i = 0 ; i < 5 ; i++)
+ {
+ bool sb = m_originalImage->sixteenBit();
+ double gamma = m_levels->getLevelGammaValue(i);
+ int lowInput = m_levels->getLevelLowInputValue(i);
+ int lowOutput = m_levels->getLevelLowOutputValue(i);
+ int highInput = m_levels->getLevelHighInputValue(i);
+ int highOutput = m_levels->getLevelHighOutputValue(i);
+
+ config->writeEntry(TQString("GammaChannel%1").arg(i), gamma);
+ config->writeEntry(TQString("LowInputChannel%1").arg(i), sb ? lowInput/255 : lowInput);
+ config->writeEntry(TQString("LowOutputChannel%1").arg(i), sb ? lowOutput/255 : lowOutput);
+ config->writeEntry(TQString("HighInputChannel%1").arg(i), sb ? highInput/255 : highInput);
+ config->writeEntry(TQString("HighOutputChannel%1").arg(i), sb ? highOutput/255 : highOutput);
+ }
+
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void AdjustLevelsTool::slotResetSettings()
+{
+ for (int channel = 0 ; channel < 5 ; ++channel)
+ m_levels->levelsChannelReset(channel);
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+ m_levelsHistogramWidget->reset();
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void AdjustLevelsTool::slotLoadSettings()
+{
+ KURL loadLevelsFile;
+
+ loadLevelsFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Select Gimp Levels File to Load")) );
+ if( loadLevelsFile.isEmpty() )
+ return;
+
+ if ( m_levels->loadLevelsFromGimpLevelsFile( loadLevelsFile ) == false )
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load from the Gimp levels text file."));
+ return;
+ }
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+}
+
+void AdjustLevelsTool::slotSaveAsSettings()
+{
+ KURL saveLevelsFile;
+
+ saveLevelsFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Gimp Levels File to Save")) );
+ if( saveLevelsFile.isEmpty() )
+ return;
+
+ if ( m_levels->saveLevelsToGimpLevelsFile( saveLevelsFile ) == false )
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save to the Gimp levels text file."));
+ return;
+ }
+
+ // Refresh the current levels config.
+ slotChannelChanged(m_channelCB->currentItem());
+}
+
+// See B.K.O #146636: use event filter with all level slider to display a
+// guide over level histogram.
+bool AdjustLevelsTool::eventFilter(TQObject *obj, TQEvent *ev)
+{
+ if ( obj == m_hGradientMinInput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_minInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowInputHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_minInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowInputHistogramGuide(int)));
+
+ m_levelsHistogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if ( obj == m_hGradientMaxInput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_maxInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowInputHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_maxInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowInputHistogramGuide(int)));
+
+ m_levelsHistogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if ( obj == m_hGradientMinOutput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_minOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowOutputHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_minOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowOutputHistogramGuide(int)));
+
+ m_histogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if ( obj == m_hGradientMaxOutput )
+ {
+ if ( ev->type() == TQEvent::MouseButtonPress)
+ {
+ connect(m_maxOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowOutputHistogramGuide(int)));
+
+ return false;
+ }
+ if ( ev->type() == TQEvent::MouseButtonRelease)
+ {
+ disconnect(m_maxOutput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotShowOutputHistogramGuide(int)));
+
+ m_histogramWidget->reset();
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // pass the event on to the parent class
+ return EditorTool::eventFilter(obj, ev);
+ }
+}
+
+void AdjustLevelsTool::slotShowInputHistogramGuide(int v)
+{
+ DColor color(v, v, v, v, m_originalImage->sixteenBit());
+ m_levelsHistogramWidget->setHistogramGuideByColor(color);
+}
+
+void AdjustLevelsTool::slotShowOutputHistogramGuide(int v)
+{
+ DColor color(v, v, v, v, m_originalImage->sixteenBit());
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+} // NameSpace DigikamAdjustLevelsImagesPlugin
diff --git a/src/imageplugins/adjustlevels/adjustlevelstool.h b/src/imageplugins/adjustlevels/adjustlevelstool.h
new file mode 100644
index 00000000..9fdadf91
--- /dev/null
+++ b/src/imageplugins/adjustlevels/adjustlevelstool.h
@@ -0,0 +1,160 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-20
+ * Description : image histogram adjust levels.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ADJUSTLEVELSTOOL_H
+#define ADJUSTLEVELSTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQComboBox;
+class TQPushButton;
+class TQHButtonGroup;
+
+class KGradientSelector;
+
+namespace KDcrawIface
+{
+class RDoubleNumInput;
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ImageWidget;
+class ImageLevels;
+class DImg;
+class DColor;
+}
+
+namespace DigikamAdjustLevelsImagesPlugin
+{
+
+class AdjustLevelsTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ AdjustLevelsTool(TQObject *parent);
+ ~AdjustLevelsTool();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+ void adjustSliders(int minIn, double gamIn, int maxIn, int minOut, int maxOut);
+ bool eventFilter(TQObject *o, TQEvent *e);
+
+private slots:
+
+ void slotLoadSettings();
+ void slotSaveAsSettings();
+ void slotEffect();
+ void slotResetSettings();
+ void slotResetCurrentChannel();
+ void slotAutoLevels();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotAdjustSliders();
+ void slotGammaInputchanged(double val);
+ void slotAdjustMinInputSpinBox(int val);
+ void slotAdjustMaxInputSpinBox(int val);
+ void slotAdjustMinOutputSpinBox(int val);
+ void slotAdjustMaxOutputSpinBox(int val);
+ void slotSpotColorChanged(const Digikam::DColor& color);
+ void slotColorSelectedFromTarget(const Digikam::DColor& color);
+ void slotPickerColorButtonActived();
+ void slotShowInputHistogramGuide(int v);
+ void slotShowOutputHistogramGuide(int v);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel,
+ AlphaChannel
+ };
+
+ enum ColorPicker
+ {
+ BlackTonal=0,
+ GrayTonal,
+ WhiteTonal
+ };
+
+ uchar *m_destinationPreviewData;
+
+ int m_histoSegments;
+ int m_currentPreviewMode;
+
+ TQComboBox *m_channelCB;
+
+ TQPushButton *m_autoButton;
+ TQPushButton *m_resetButton;
+ TQPushButton *m_pickBlack;
+ TQPushButton *m_pickGray;
+ TQPushButton *m_pickWhite;
+
+ TQHButtonGroup *m_pickerColorButtonGroup;
+ TQHButtonGroup *m_scaleBG;
+
+ KGradientSelector *m_hGradientMinInput;
+ KGradientSelector *m_hGradientMaxInput;
+ KGradientSelector *m_hGradientMinOutput;
+ KGradientSelector *m_hGradientMaxOutput;
+
+ KDcrawIface::RDoubleNumInput *m_gammaInput;
+
+ KDcrawIface::RIntNumInput *m_minInput;
+ KDcrawIface::RIntNumInput *m_maxInput;
+ KDcrawIface::RIntNumInput *m_minOutput;
+ KDcrawIface::RIntNumInput *m_maxOutput;
+
+ Digikam::HistogramWidget *m_levelsHistogramWidget;
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+
+ Digikam::ImageLevels *m_levels;
+ Digikam::DImg *m_originalImage;
+};
+
+} // NameSpace DigikamAdjustLevelsImagesPlugin
+
+#endif /* ADJUSTLEVELSTOOL_H */
diff --git a/src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels.desktop b/src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels.desktop
new file mode 100644
index 00000000..b601ebbf
--- /dev/null
+++ b/src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_AdjustLevels
+Name[bg]=Приставка за снимки - Настройка на нивата
+Name[da]=Billedplugin_Niveaujustering
+Name[el]=ΠρόσθετοΕικόνας_ΠροσαρμογήΕπιπέδων
+Name[fi]=TasonsäätöHistogrammi
+Name[hr]=Podešavanje razina
+Name[it]=PluginImmagini_RegolaLivelli
+Name[nl]=Afbeeldingsplugin_NiveausAanpassen
+Name[sr]=Подешавање нивоа
+Name[sr@Latn]=Podešavanje nivoa
+Name[sv]=Insticksprogram för nivåjustering
+Name[tr]=ResimEklentisi_DüzeyAyarla
+Name[xx]=xxImagePlugin_AdjustLevelsxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+
+Comment=Image histogram adjust levels plugin for digiKam
+Comment[bg]=Приставка на digiKam за настройка нивата на хистограмите на снимки
+Comment[ca]=Connector pel digiKam d'ajust dels nivells de l'histograma d'imatges
+Comment[da]=Plugin til niveaujustering i Digikam
+Comment[de]=digiKam-Modul zur Justierung der Bildhistogrammwerte
+Comment[el]=Πρόσθετο προσαρμογής των επιπέδων του ιστογράμματος εικόνας για το digiKam
+Comment[es]=Plugin de digiKam para ajustar los niveles de color de una imagen
+Comment[et]=DigiKami pildi histogrammi tasemete kohendamise plugin
+Comment[fa]=وصلۀ سطوح تنظیم سابقه‌نمای تصویر برای digiKam
+Comment[fi]=Muokkaa värikanavien raja-arvoja
+Comment[fr]=Module externe pour ajuster les niveaux de l'histogramme dans digiKam
+Comment[gl]=Un plugin de digiKam para axustar os níveis do histograma
+Comment[hr]=digiKam dodatak za histogramsko podešavanje razina
+Comment[is]=Íforrit fyrir digiKam sem breytir tíðnidreifingu (levels) í stuðlariti myndar
+Comment[it]=Plugin di regolazione dei livelli degli istogrammi delle immagini per digiKam
+Comment[ja]=digiKam レベル補正プラグイン
+Comment[nds]=digiKam-Moduul för't Topassen vun Klöörkanaal-Toonrebeden
+Comment[nl]=Digikam-plugin voor niveauaanpassing van afbeeldingshistogram
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਚਿੱਤਰ ਹਿਸਟੋਗਰਾਮ ਅਨੁਕੂਲ ਪੱਧਰ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam dostosowująca poziomy histogramu
+Comment[pt]=Um 'plugin' do digiKam para ajustar os níveis do histograma do digiKam
+Comment[pt_BR]=Plugin de ajuste de níveis do histograma da imagem
+Comment[ru]=Модуль digiKam подстройки уровней гистограммы изображения
+Comment[sk]=digiKam plugin histogramu úrovní obrázku
+Comment[sr]=digiKam-ов прикључак за подешавање нивоа хистограма слика
+Comment[sr@Latn]=digiKam-ov priključak za podešavanje nivoa histograma slika
+Comment[sv]=Digikam insticksprogram för justering av nivåer i bildhistogram
+Comment[tr]=digiKam için resim histogram düzeyleri ayarlama eklentisi
+Comment[uk]=Втулок коригування рівнів гістограми зображень для digiKam
+Comment[vi]=Phần bổ sung biểu đồ tần xuất điều chỉnh lớp ảnh cho digiKam
+Comment[xx]=xxImage histogram adjust levels plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_adjustlevels
+author=Caulier Gilles, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels_ui.rc b/src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels_ui.rc
new file mode 100644
index 00000000..8036e124
--- /dev/null
+++ b/src/imageplugins/adjustlevels/digikamimageplugin_adjustlevels_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_adjustlevels" >
+
+ <MenuBar>
+
+ <Menu name="Color" ><text>&amp;Color</text>
+ <Action name="imageplugin_adjustlevels" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_adjustlevels" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/adjustlevels/imageplugin_adjustlevels.cpp b/src/imageplugins/adjustlevels/imageplugin_adjustlevels.cpp
new file mode 100644
index 00000000..0f9dcb9e
--- /dev/null
+++ b/src/imageplugins/adjustlevels/imageplugin_adjustlevels.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : image histogram adjust levels.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "adjustlevelstool.h"
+#include "imageplugin_adjustlevels.h"
+#include "imageplugin_adjustlevels.moc"
+
+using namespace DigikamAdjustLevelsImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_adjustlevels,
+ KGenericFactory<ImagePlugin_AdjustLevels>("digikamimageplugin_adjustlevels"))
+
+ImagePlugin_AdjustLevels::ImagePlugin_AdjustLevels(TQObject *parent, const char*,
+ const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_AdjustLevels")
+{
+ m_levelsAction = new TDEAction(i18n("Levels Adjust..."), "adjustlevels",
+ CTRL+Key_L, // NOTE: Photoshop 7 use CTRL+L.
+ this, TQ_SLOT(slotLevelsAdjust()),
+ actionCollection(), "imageplugin_adjustlevels");
+
+ setXMLFile("digikamimageplugin_adjustlevels_ui.rc");
+
+ DDebug() << "ImagePlugin_AdjustLevels plugin loaded" << endl;
+}
+
+ImagePlugin_AdjustLevels::~ImagePlugin_AdjustLevels()
+{
+}
+
+void ImagePlugin_AdjustLevels::setEnabledActions(bool enable)
+{
+ m_levelsAction->setEnabled(enable);
+}
+
+void ImagePlugin_AdjustLevels::slotLevelsAdjust()
+{
+ AdjustLevelsTool *levels = new AdjustLevelsTool(this);
+ loadTool(levels);
+}
diff --git a/src/imageplugins/adjustlevels/imageplugin_adjustlevels.h b/src/imageplugins/adjustlevels/imageplugin_adjustlevels.h
new file mode 100644
index 00000000..16af4f32
--- /dev/null
+++ b/src/imageplugins/adjustlevels/imageplugin_adjustlevels.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : image histogram adjust levels.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_ADJUSTLEVELS_H
+#define IMAGEPLUGIN_ADJUSTLEVELS_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_AdjustLevels : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_AdjustLevels(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_AdjustLevels();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotLevelsAdjust();
+
+private:
+
+ TDEAction *m_levelsAction;
+};
+
+#endif /* IMAGEPLUGIN_ADJUSTLEVELS_H */
diff --git a/src/imageplugins/antivignetting/Makefile.am b/src/imageplugins/antivignetting/Makefile.am
new file mode 100644
index 00000000..54539672
--- /dev/null
+++ b/src/imageplugins/antivignetting/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_antivignetting_la_SOURCES = imageplugin_antivignetting.cpp \
+ antivignettingtool.cpp antivignetting.cpp
+
+digikamimageplugin_antivignetting_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_antivignetting_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_antivignetting.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_antivignetting.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_antivignetting_ui.rc
+
diff --git a/src/imageplugins/antivignetting/antivignetting.cpp b/src/imageplugins/antivignetting/antivignetting.cpp
new file mode 100644
index 00000000..dec0a9ea
--- /dev/null
+++ b/src/imageplugins/antivignetting/antivignetting.cpp
@@ -0,0 +1,149 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Antivignetting threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original AntiVignetting algorithm copyrighted 2003 by
+ * John Walker from 'pnmctrfilt' implementation. See
+ * http://www.fourmilab.ch/netpbm/pnmctrfilt for more
+ * information.
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "antivignetting.h"
+
+namespace DigikamAntiVignettingImagesPlugin
+{
+
+AntiVignetting::AntiVignetting(Digikam::DImg *orgImage, TQObject *parent, double density,
+ double power, double radius, int xshift, int yshift, bool normalize)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "AntiVignetting")
+{
+ m_density = density;
+ m_power = power;
+ m_radius = radius;
+ m_xshift = xshift;
+ m_yshift = yshift;
+ m_normalize = normalize;
+
+ initFilter();
+}
+
+// This method is inspired from John Walker 'pnmctrfilt' algorithm code.
+
+void AntiVignetting::filterImage(void)
+{
+ int progress;
+ int col, row, xd, td, yd, p;
+ int i, xsize, ysize, diagonal, erad, xctr, yctr;
+ double *ldens;
+
+ uchar* NewBits = m_destImage.bits();
+ uchar* data = m_orgImage.bits();
+
+ unsigned short* NewBits16 = (unsigned short*)m_destImage.bits();
+ unsigned short* data16 = (unsigned short*)m_orgImage.bits();
+
+ int Width = m_orgImage.width();
+ int Height = m_orgImage.height();
+
+ // Determine the radius of the filter. This is the half diagonal
+ // measure of the image multiplied by the command line radius factor.
+
+ xsize = (Height + 1) / 2;
+ ysize = (Width + 1) / 2;
+ erad = (int)((sqrt((xsize * xsize) + (ysize * ysize)) + 0.5) * m_radius);
+
+ // Build the in-memory table which maps distance from the
+ // center of the image (as adjusted by the X and Y offset,
+ // if any) to the density of the filter at this remove. This
+ // table needs to be as large as the diagonal from the
+ // (possibly offset) center to the most distant corner
+ // of the image.
+
+ xsize = ((Height + 1) / 2) + abs(m_xshift);
+ ysize = ((Width + 1) / 2) + abs(m_yshift);
+ diagonal = ((int) (sqrt((xsize * xsize) + (ysize * ysize)) + 0.5)) + 1;
+
+ ldens = new double[diagonal];
+
+ for (i = 0 ; !m_cancel && (i < diagonal) ; i++)
+ {
+ if ( i >= erad )
+ ldens[i] = 1;
+ else
+ ldens[i] = (1.0 + (m_density - 1) * pow(1.0 - (((double) i) / (erad - 1)), m_power));
+ }
+
+ xctr = ((Height + 1) / 2) + m_xshift;
+ yctr = ((Width + 1) / 2) + m_yshift;
+
+ for (row = 0 ; !m_cancel && (row < Width) ; row++)
+ {
+ yd = abs(yctr - row);
+
+ for (col = 0 ; !m_cancel && (col < Height) ; col++)
+ {
+ p = (col * Width + row)*4;
+
+ xd = abs(xctr - col);
+ td = (int) (sqrt((xd * xd) + (yd * yd)) + 0.5);
+
+ if (!m_orgImage.sixteenBit()) // 8 bits image
+ {
+ NewBits[ p ] = (uchar)(data[ p ] / ldens[td]);
+ NewBits[p+1] = (uchar)(data[p+1] / ldens[td]);
+ NewBits[p+2] = (uchar)(data[p+2] / ldens[td]);
+ NewBits[p+3] = data[p+3];
+ }
+ else // 16 bits image.
+ {
+ NewBits16[ p ] = (unsigned short)(data16[ p ] / ldens[td]);
+ NewBits16[p+1] = (unsigned short)(data16[p+1] / ldens[td]);
+ NewBits16[p+2] = (unsigned short)(data16[p+2] / ldens[td]);
+ NewBits16[p+3] = data16[p+3];
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int)(((double)row * 100.0) / Width);
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+
+ // Normalize colors for a best rendering.
+ if (m_normalize)
+ {
+ Digikam::DImgImageFilters filters;
+ filters.normalizeImage(m_destImage.bits(), Width, Height, m_destImage.sixteenBit());
+ }
+
+ delete [] ldens;
+}
+
+} // NameSpace DigikamAntiVignettingImagesPlugin
diff --git a/src/imageplugins/antivignetting/antivignetting.h b/src/imageplugins/antivignetting/antivignetting.h
new file mode 100644
index 00000000..f5311da0
--- /dev/null
+++ b/src/imageplugins/antivignetting/antivignetting.h
@@ -0,0 +1,62 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Antivignetting threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ANTIVIGNETTING_H
+#define ANTIVIGNETTING_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamAntiVignettingImagesPlugin
+{
+
+class AntiVignetting : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ AntiVignetting(Digikam::DImg *orgImage, TQObject *parent=0, double density=2.0,
+ double power=1.0, double radius=1.0, int xshift=0, int yshift=0, bool normalize=true);
+
+ ~AntiVignetting(){};
+
+private:
+
+ virtual void filterImage(void);
+
+private:
+
+ bool m_normalize;
+
+ int m_xshift;
+ int m_yshift;
+
+ double m_density;
+ double m_power;
+ double m_radius;
+};
+
+} // NameSpace DigikamAntiVignettingImagesPlugin
+
+#endif /* ANTIVIGNETTING_H */
diff --git a/src/imageplugins/antivignetting/antivignettingtool.cpp b/src/imageplugins/antivignetting/antivignettingtool.cpp
new file mode 100644
index 00000000..d8f211c7
--- /dev/null
+++ b/src/imageplugins/antivignetting/antivignettingtool.cpp
@@ -0,0 +1,378 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-25
+ * Description : a digiKam image plugin to reduce
+ * vignetting on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqtabwidget.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <kseparator.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "bcgmodifier.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "editortoolsettings.h"
+#include "dimgimagefilters.h"
+#include "antivignetting.h"
+#include "antivignettingtool.h"
+#include "antivignettingtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamAntiVignettingImagesPlugin
+{
+
+AntiVignettingTool::AntiVignettingTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("antivignettings");
+ setToolName(i18n("Vignetting Correction"));
+ setToolIcon(SmallIcon("antivignetting"));
+
+ m_previewWidget = new ImageWidget("antivignetting Tool", 0, TQString(),
+ false, ImageGuideWidget::HVGuideMode, false);
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 14, 2);
+
+ m_maskPreviewLabel = new TQLabel( m_gboxSettings->plainPage() );
+ m_maskPreviewLabel->setAlignment ( TQt::AlignHCenter | TQt::AlignVCenter );
+ TQWhatsThis::add( m_maskPreviewLabel, i18n("<p>You can see here a thumbnail preview of the anti-vignetting "
+ "mask applied to the image.") );
+
+ // -------------------------------------------------------------
+
+ TQLabel *label1 = new TQLabel(i18n("Density:"), m_gboxSettings->plainPage());
+
+ m_densityInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_densityInput->setPrecision(1);
+ m_densityInput->setRange(1.0, 20.0, 0.1);
+ m_densityInput->setDefaultValue(2.0);
+ TQWhatsThis::add( m_densityInput, i18n("<p>This value controls the degree of intensity attenuation by the filter "
+ "at its point of maximum density."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Power:"), m_gboxSettings->plainPage());
+
+ m_powerInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_powerInput->setPrecision(1);
+ m_powerInput->setRange(0.1, 2.0, 0.1);
+ m_powerInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_powerInput, i18n("<p>This value is used as the exponent controlling the fall-off in density "
+ "from the center of the filter to the periphery."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Radius:"), m_gboxSettings->plainPage());
+
+ m_radiusInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_radiusInput->setPrecision(1);
+ m_radiusInput->setRange(-100.0, 100.0, 0.1);
+ m_radiusInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_radiusInput, i18n("<p>This value is the radius of the center filter. It is a multiple of the "
+ "half-diagonal measure of the image, at which the density of the filter falls "
+ "to zero."));
+
+ KSeparator *line = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ // -------------------------------------------------------------
+
+ TQLabel *label4 = new TQLabel(i18n("Brightness:"), m_gboxSettings->plainPage());
+
+ m_brightnessInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_brightnessInput->setRange(0, 100, 1);
+ m_brightnessInput->setDefaultValue(0);
+ TQWhatsThis::add( m_brightnessInput, i18n("<p>Set here the brightness re-adjustment of the target image."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label5 = new TQLabel(i18n("Contrast:"), m_gboxSettings->plainPage());
+
+ m_contrastInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_contrastInput->setRange(0, 100, 1);
+ m_contrastInput->setDefaultValue(0);
+ TQWhatsThis::add( m_contrastInput, i18n("<p>Set here the contrast re-adjustment of the target image."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label6 = new TQLabel(i18n("Gamma:"), m_gboxSettings->plainPage());
+
+ m_gammaInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_gammaInput->setPrecision(2);
+ m_gammaInput->setRange(0.1, 3.0, 0.01);
+ m_gammaInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_gammaInput, i18n("<p>Set here the gamma re-adjustment of the target image."));
+
+ grid->addMultiCellWidget(m_maskPreviewLabel, 0, 0, 0, 2);
+ grid->addMultiCellWidget(label1, 1, 1, 0, 2);
+ grid->addMultiCellWidget(m_densityInput, 2, 2, 0, 2);
+ grid->addMultiCellWidget(label2, 3, 3, 0, 2);
+ grid->addMultiCellWidget(m_powerInput, 4, 4, 0, 2);
+ grid->addMultiCellWidget(label3, 5, 5, 0, 2);
+ grid->addMultiCellWidget(m_radiusInput, 6, 6, 0, 2);
+ grid->addMultiCellWidget(line, 7, 7, 0, 2);
+ grid->addMultiCellWidget(label4, 8, 8, 0, 2);
+ grid->addMultiCellWidget(m_brightnessInput, 9, 9, 0, 2);
+ grid->addMultiCellWidget(label5, 10, 10, 0, 2);
+ grid->addMultiCellWidget(m_contrastInput, 11, 11, 0, 2);
+ grid->addMultiCellWidget(label6, 12, 12, 0, 2);
+ grid->addMultiCellWidget(m_gammaInput, 13, 13, 0, 2);
+ grid->setRowStretch(14, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_densityInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_powerInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_radiusInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_brightnessInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_contrastInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gammaInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+AntiVignettingTool::~AntiVignettingTool()
+{
+}
+
+void AntiVignettingTool::renderingFinished()
+{
+ m_densityInput->setEnabled(true);
+ m_powerInput->setEnabled(true);
+ m_radiusInput->setEnabled(true);
+ m_brightnessInput->setEnabled(true);
+ m_contrastInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+}
+
+void AntiVignettingTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("antivignettings Tool");
+
+ m_densityInput->blockSignals(true);
+ m_powerInput->blockSignals(true);
+ m_radiusInput->blockSignals(true);
+ m_brightnessInput->blockSignals(true);
+ m_contrastInput->blockSignals(true);
+ m_gammaInput->blockSignals(true);
+
+ m_densityInput->setValue(config->readDoubleNumEntry("DensityAjustment", m_densityInput->defaultValue()));
+ m_powerInput->setValue(config->readDoubleNumEntry("PowerAjustment", m_powerInput->defaultValue()));
+ m_radiusInput->setValue(config->readDoubleNumEntry("RadiusAjustment", m_radiusInput->defaultValue()));
+ m_brightnessInput->setValue(config->readNumEntry("BrightnessAjustment", m_brightnessInput->defaultValue()));
+ m_contrastInput->setValue(config->readNumEntry("ContrastAjustment", m_contrastInput->defaultValue()));
+ m_gammaInput->setValue(config->readDoubleNumEntry("GammaAjustment", m_gammaInput->defaultValue()));
+
+ m_densityInput->blockSignals(false);
+ m_powerInput->blockSignals(false);
+ m_radiusInput->blockSignals(false);
+ m_brightnessInput->blockSignals(false);
+ m_contrastInput->blockSignals(false);
+ m_gammaInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void AntiVignettingTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("antivignettings Tool");
+ config->writeEntry("DensityAjustment", m_densityInput->value());
+ config->writeEntry("PowerAjustment", m_powerInput->value());
+ config->writeEntry("RadiusAjustment", m_radiusInput->value());
+ config->writeEntry("BrightnessAjustment", m_brightnessInput->value());
+ config->writeEntry("ContrastAjustment", m_contrastInput->value());
+ config->writeEntry("GammaAjustment", m_gammaInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void AntiVignettingTool::slotResetSettings()
+{
+ m_densityInput->blockSignals(true);
+ m_powerInput->blockSignals(true);
+ m_radiusInput->blockSignals(true);
+ m_brightnessInput->blockSignals(true);
+ m_contrastInput->blockSignals(true);
+ m_gammaInput->blockSignals(true);
+
+ m_densityInput->slotReset();
+ m_powerInput->slotReset();
+ m_radiusInput->slotReset();
+ m_brightnessInput->slotReset();
+ m_contrastInput->slotReset();
+ m_gammaInput->slotReset();
+
+ m_densityInput->blockSignals(false);
+ m_powerInput->blockSignals(false);
+ m_radiusInput->blockSignals(false);
+ m_brightnessInput->blockSignals(false);
+ m_contrastInput->blockSignals(false);
+ m_gammaInput->blockSignals(false);
+}
+
+void AntiVignettingTool::prepareEffect()
+{
+ m_densityInput->setEnabled(false);
+ m_powerInput->setEnabled(false);
+ m_radiusInput->setEnabled(false);
+ m_brightnessInput->setEnabled(false);
+ m_contrastInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+
+ double d = m_densityInput->value();
+ double p = m_powerInput->value();
+ double r = m_radiusInput->value();
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int orgWidth = iface->originalWidth();
+ int orgHeight = iface->originalHeight();
+ TQSize ps(orgWidth, orgHeight);
+ ps.scale( TQSize(120, 120), TQSize::ScaleMin );
+
+ // Calc mask preview.
+ DImg preview(ps.width(), ps.height(), false);
+ memset(preview.bits(), 255, preview.numBytes());
+ AntiVignetting maskPreview(&preview, 0L, d, p, r, 0, 0, false);
+ TQPixmap pix = maskPreview.getTargetImage().convertToPixmap();
+ TQPainter pt(&pix);
+ pt.setPen( TQPen(TQt::black, 1) );
+ pt.drawRect( 0, 0, pix.width(), pix.height() );
+ pt.end();
+ m_maskPreviewLabel->setPixmap( pix );
+
+ DImg orgImage(orgWidth, orgHeight, iface->originalSixteenBit(),
+ iface->originalHasAlpha(), data);
+ delete [] data;
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new AntiVignetting(&orgImage, this, d, p, r, 0, 0, true)));
+}
+
+void AntiVignettingTool::prepareFinal()
+{
+ m_densityInput->setEnabled(false);
+ m_powerInput->setEnabled(false);
+ m_radiusInput->setEnabled(false);
+ m_brightnessInput->setEnabled(false);
+ m_contrastInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+
+ double d = m_densityInput->value();
+ double p = m_powerInput->value();
+ double r = m_radiusInput->value();
+
+ ImageIface iface(0, 0);
+
+ uchar *data = iface.getOriginalImage();
+ DImg orgImage(iface.originalWidth(), iface.originalHeight(), iface.originalSixteenBit(),
+ iface.originalHasAlpha(), data);
+ delete [] data;
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new AntiVignetting(&orgImage, this, d, p, r, 0, 0, true)));
+}
+
+void AntiVignettingTool::putPreviewData(void)
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ DImg imDest = filter()->getTargetImage();
+
+ // Adjust Image BCG.
+
+ double b = (double)(m_brightnessInput->value() / 100.0);
+ double c = (double)(m_contrastInput->value() / 100.0) + (double)(1.00);
+ double g = m_gammaInput->value();
+
+ BCGModifier cmod;
+ cmod.setGamma(g);
+ cmod.setBrightness(b);
+ cmod.setContrast(c);
+ cmod.applyBCG(imDest);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+ m_previewWidget->updatePreview();
+}
+
+void AntiVignettingTool::putFinalData(void)
+{
+ ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Vignetting Correction"),
+ filter()->getTargetImage().bits());
+
+ double b = (double)(m_brightnessInput->value() / 100.0);
+ double c = (double)(m_contrastInput->value() / 100.0) + (double)(1.00);
+ double g = m_gammaInput->value();
+
+ // Adjust Image BCG.
+ iface.setOriginalBCG(b, c, g);
+}
+
+} // NameSpace DigikamAntiVignettingImagesPlugin
diff --git a/src/imageplugins/antivignetting/antivignettingtool.h b/src/imageplugins/antivignetting/antivignettingtool.h
new file mode 100644
index 00000000..467a19da
--- /dev/null
+++ b/src/imageplugins/antivignetting/antivignettingtool.h
@@ -0,0 +1,92 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : a digiKam image plugin to reduce
+ * vignetting on an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ANTIVIGNETTINGTOOL_H
+#define ANTIVIGNETTINGTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQLabel;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RDoubleNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImageWidget;
+}
+
+namespace DigikamAntiVignettingImagesPlugin
+{
+
+class AntiVignettingTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ AntiVignettingTool(TQObject *parent);
+ ~AntiVignettingTool();
+
+private slots:
+
+ void slotResetSettings();
+
+private:
+
+ void writeSettings();
+ void readSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_maskPreviewLabel;
+
+ KDcrawIface::RIntNumInput *m_brightnessInput;
+ KDcrawIface::RIntNumInput *m_contrastInput;
+
+ KDcrawIface::RDoubleNumInput *m_gammaInput;
+ KDcrawIface::RDoubleNumInput *m_densityInput;
+ KDcrawIface::RDoubleNumInput *m_powerInput;
+ KDcrawIface::RDoubleNumInput *m_radiusInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamAntiVignettingImagesPlugin
+
+#endif /* ANTIVIGNETTINGTOOL_H */
diff --git a/src/imageplugins/antivignetting/digikamimageplugin_antivignetting.desktop b/src/imageplugins/antivignetting/digikamimageplugin_antivignetting.desktop
new file mode 100644
index 00000000..f70a4752
--- /dev/null
+++ b/src/imageplugins/antivignetting/digikamimageplugin_antivignetting.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=ImagePlugin_AntiVignetting
+Name[bg]=Приставка за снимки - Премахване на винетиране
+Name[da]=Billedplugin_Anti-vignettering
+Name[el]=ΠρόσθετοΕικόνας_Αντισκίασης
+Name[fi]=ReunatummentumienPoisto
+Name[hr]=Uklanjanje vinjeta
+Name[it]=PluginImmagini_Antivignettatura
+Name[nl]=Afbeeldingsplugin_Antivignetting
+Name[sr]=Девињетизација
+Name[sr@Latn]=Devinjetizacija
+Name[sv]=Insticksprogram för antivinjettering
+Name[tr]=ResimEklentisi_KenarKararmalarınıDüzelt
+Name[xx]=xxImagePlugin_AntiVignettingxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Anti Vignetting image effect plugin for digiKam
+Comment[bg]=Приставка на digiKam за намаляване на винетирането на снимки
+Comment[ca]=Connector pel digiKam d'efecte anti-vinyetatge
+Comment[da]=Anti-vignetteringsplugin for Digikam
+Comment[de]=digiKam-Modul zur Reduzierung von Vignettierung im Bild
+Comment[el]=Πρόσθετο εφέ αντισκίασης εικόνας για το digiKam
+Comment[es]=Un plugin para digiKam para eliminar efectos tipo "Vignetting"
+Comment[et]=DigiKami pildi vinjeti kohendamise plugin
+Comment[fa]=وصلۀ جلوۀ تصویر ضد عکس برای digiKam
+Comment[fi]=Keskialueen portaaton tummennus
+Comment[gl]=Un plugin de digiKam para efeitos antiviñeta de imaxe
+Comment[hr]=digiKam dodatak za efekt protiv vinjeta
+Comment[is]=Íforrit fyrir digiKam sem minnkar linsuskyggingu í hornum
+Comment[it]=Plugin per l'effetto delle immagini di antivignettatura per digiKam
+Comment[ja]=digiKam 口径食補正プラグイン
+Comment[nds]=digiKam-Bildeffektmoduul gegen Randschaddens
+Comment[nl]=Digikam-plugin voor anti-vignetting
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਐਂਟੀ ਵੀਜਨਿੰਟ ਚਿੱਤਰ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam usuwająca efekt winietowania
+Comment[pt]=Um 'plugin' do digiKam para efeitos anti-vinheta de imagem
+Comment[pt_BR]=Plugin de efeito anti-Vignetting para o digiKam
+Comment[ru]=Модуль эффекта анти-виньетирования изображения для digiKam
+Comment[sk]=Plugin korekcie vignetácie obrázku pre digiKam
+Comment[sr]=digiKam-ов прикључак за ефекат девињетизације
+Comment[sr@Latn]=digiKam-ov priključak za efekat devinjetizacije
+Comment[sv]=Digikam insticksprogram för antivinjetteringsbildeffekt
+Comment[tr]=digiKam için kenar kararmalarını düzeltme eklentisi
+Comment[uk]=Втулок противіньєткового ефекту зображень для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng chống làm mờ nét ảnh cho digiKam
+Comment[xx]=xxAnti Vignetting image effect plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_antivignetting
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/antivignetting/digikamimageplugin_antivignetting_ui.rc b/src/imageplugins/antivignetting/digikamimageplugin_antivignetting_ui.rc
new file mode 100644
index 00000000..7a54a1f3
--- /dev/null
+++ b/src/imageplugins/antivignetting/digikamimageplugin_antivignetting_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_antivignetting" >
+
+ <MenuBar>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ <Action name="imageplugin_antivignetting" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_antivignetting" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/antivignetting/imageeffect_antivignetting.cpp b/src/imageplugins/antivignetting/imageeffect_antivignetting.cpp
new file mode 100644
index 00000000..7d7a00c6
--- /dev/null
+++ b/src/imageplugins/antivignetting/imageeffect_antivignetting.cpp
@@ -0,0 +1,380 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-25
+ * Description : a digiKam image plugin to reduce
+ * vignetting on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqtabwidget.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+#include <kseparator.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "bcgmodifier.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "dimgimagefilters.h"
+#include "antivignetting.h"
+#include "imageeffect_antivignetting.h"
+#include "imageeffect_antivignetting.moc"
+
+namespace DigikamAntiVignettingImagesPlugin
+{
+
+ImageEffect_AntiVignetting::ImageEffect_AntiVignetting(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Vignetting Correction"),
+ "antivignettings", false, true, false,
+ Digikam::ImageGuideWidget::HVGuideMode, 0, true)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Vignetting Correction"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to reduce image vignetting."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("John Walker", I18N_NOOP("Anti Vignetting algorithm"), 0,
+ "http://www.fourmilab.ch/netpbm/pnmctrfilt");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 13, 2, spacingHint());
+
+ m_maskPreviewLabel = new TQLabel( gboxSettings );
+ m_maskPreviewLabel->setAlignment ( TQt::AlignHCenter | TQt::AlignVCenter );
+ TQWhatsThis::add( m_maskPreviewLabel, i18n("<p>You can see here a thumbnail preview of the anti-vignetting "
+ "mask applied to the image.") );
+ gridSettings->addMultiCellWidget(m_maskPreviewLabel, 0, 0, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label1 = new TQLabel(i18n("Density:"), gboxSettings);
+
+ m_densityInput = new KDoubleNumInput(gboxSettings);
+ m_densityInput->setPrecision(1);
+ m_densityInput->setRange(1.0, 20.0, 0.1, true);
+ TQWhatsThis::add( m_densityInput, i18n("<p>This value controls the degree of intensity attenuation by the filter "
+ "at its point of maximum density."));
+
+ gridSettings->addMultiCellWidget(label1, 1, 1, 0, 2);
+ gridSettings->addMultiCellWidget(m_densityInput, 2, 2, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Power:"), gboxSettings);
+
+ m_powerInput = new KDoubleNumInput(gboxSettings);
+ m_powerInput->setPrecision(1);
+ m_powerInput->setRange(0.1, 2.0, 0.1, true);
+ TQWhatsThis::add( m_powerInput, i18n("<p>This value is used as the exponent controlling the fall-off in density "
+ "from the center of the filter to the periphery."));
+
+ gridSettings->addMultiCellWidget(label2, 3, 3, 0, 2);
+ gridSettings->addMultiCellWidget(m_powerInput, 4, 4, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Radius:"), gboxSettings);
+
+ m_radiusInput = new KDoubleNumInput(gboxSettings);
+ m_radiusInput->setPrecision(1);
+ m_radiusInput->setRange(-100.0, 100.0, 0.1, true);
+ TQWhatsThis::add( m_radiusInput, i18n("<p>This value is the radius of the center filter. It is a multiple of the "
+ "half-diagonal measure of the image, at which the density of the filter falls "
+ "to zero."));
+
+ gridSettings->addMultiCellWidget(label3, 5, 5, 0, 2);
+ gridSettings->addMultiCellWidget(m_radiusInput, 6, 6, 0, 2);
+
+ KSeparator *line = new KSeparator(Horizontal, gboxSettings);
+ gridSettings->addMultiCellWidget(line, 7, 7, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label4 = new TQLabel(i18n("Brightness:"), gboxSettings);
+
+ m_brightnessInput = new KIntNumInput(gboxSettings);
+ m_brightnessInput->setRange(0, 100, 1, true);
+ TQWhatsThis::add( m_brightnessInput, i18n("<p>Set here the brightness re-adjustment of the target image."));
+
+ gridSettings->addMultiCellWidget(label4, 8, 8, 0, 2);
+ gridSettings->addMultiCellWidget(m_brightnessInput, 9, 9, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label5 = new TQLabel(i18n("Contrast:"), gboxSettings);
+
+ m_contrastInput = new KIntNumInput(gboxSettings);
+ m_contrastInput->setRange(0, 100, 1, true);
+ TQWhatsThis::add( m_contrastInput, i18n("<p>Set here the contrast re-adjustment of the target image."));
+
+ gridSettings->addMultiCellWidget(label5, 10, 10, 0, 2);
+ gridSettings->addMultiCellWidget(m_contrastInput, 11, 11, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label6 = new TQLabel(i18n("Gamma:"), gboxSettings);
+
+ m_gammaInput = new KDoubleNumInput(gboxSettings);
+ m_gammaInput->setPrecision(2);
+ m_gammaInput->setRange(0.1, 3.0, 0.01, true);
+ m_gammaInput->setValue(1.0);
+ TQWhatsThis::add( m_gammaInput, i18n("<p>Set here the gamma re-adjustment of the target image."));
+
+ gridSettings->addMultiCellWidget(label6, 12, 12, 0, 2);
+ gridSettings->addMultiCellWidget(m_gammaInput, 13, 13, 0, 2);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_densityInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_powerInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_radiusInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_brightnessInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_contrastInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gammaInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_AntiVignetting::~ImageEffect_AntiVignetting()
+{
+}
+
+void ImageEffect_AntiVignetting::renderingFinished()
+{
+ m_densityInput->setEnabled(true);
+ m_powerInput->setEnabled(true);
+ m_radiusInput->setEnabled(true);
+ m_brightnessInput->setEnabled(true);
+ m_contrastInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+}
+
+void ImageEffect_AntiVignetting::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("antivignettings Tool Dialog");
+
+ m_densityInput->blockSignals(true);
+ m_powerInput->blockSignals(true);
+ m_radiusInput->blockSignals(true);
+ m_brightnessInput->blockSignals(true);
+ m_contrastInput->blockSignals(true);
+ m_gammaInput->blockSignals(true);
+
+ m_densityInput->setValue(config->readDoubleNumEntry("DensityAjustment", 2.0));
+ m_powerInput->setValue(config->readDoubleNumEntry("PowerAjustment", 1.0));
+ m_radiusInput->setValue(config->readDoubleNumEntry("RadiusAjustment", 1.0));
+ m_brightnessInput->setValue(config->readNumEntry("BrightnessAjustment", 0));
+ m_contrastInput->setValue(config->readNumEntry("ContrastAjustment", 0));
+ m_gammaInput->setValue(config->readDoubleNumEntry("GammaAjustment", 1.0));
+
+ m_densityInput->blockSignals(false);
+ m_powerInput->blockSignals(false);
+ m_radiusInput->blockSignals(false);
+ m_brightnessInput->blockSignals(false);
+ m_contrastInput->blockSignals(false);
+ m_gammaInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_AntiVignetting::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("antivignettings Tool Dialog");
+ config->writeEntry("DensityAjustment", m_densityInput->value());
+ config->writeEntry("PowerAjustment", m_powerInput->value());
+ config->writeEntry("RadiusAjustment", m_radiusInput->value());
+ config->writeEntry("BrightnessAjustment", m_brightnessInput->value());
+ config->writeEntry("ContrastAjustment", m_contrastInput->value());
+ config->writeEntry("GammaAjustment", m_gammaInput->value());
+ config->sync();
+}
+
+void ImageEffect_AntiVignetting::resetValues()
+{
+ m_densityInput->blockSignals(true);
+ m_powerInput->blockSignals(true);
+ m_radiusInput->blockSignals(true);
+ m_brightnessInput->blockSignals(true);
+ m_contrastInput->blockSignals(true);
+ m_gammaInput->blockSignals(true);
+
+ m_densityInput->setValue(2.0);
+ m_powerInput->setValue(1.0);
+ m_radiusInput->setValue(1.0);
+ m_brightnessInput->setValue(0);
+ m_contrastInput->setValue(0);
+ m_gammaInput->setValue(1.0);
+
+ m_densityInput->blockSignals(false);
+ m_powerInput->blockSignals(false);
+ m_radiusInput->blockSignals(false);
+ m_brightnessInput->blockSignals(false);
+ m_contrastInput->blockSignals(false);
+ m_gammaInput->blockSignals(false);
+}
+
+void ImageEffect_AntiVignetting::prepareEffect()
+{
+ m_densityInput->setEnabled(false);
+ m_powerInput->setEnabled(false);
+ m_radiusInput->setEnabled(false);
+ m_brightnessInput->setEnabled(false);
+ m_contrastInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+
+ double d = m_densityInput->value();
+ double p = m_powerInput->value();
+ double r = m_radiusInput->value();
+
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int orgWidth = iface->originalWidth();
+ int orgHeight = iface->originalHeight();
+ TQSize ps(orgWidth, orgHeight);
+ ps.scale( TQSize(120, 120), TQSize::ScaleMin );
+
+ // Calc mask preview.
+ Digikam::DImg preview(ps.width(), ps.height(), false);
+ memset(preview.bits(), 255, preview.numBytes());
+ AntiVignetting maskPreview(&preview, 0L, d, p, r, 0, 0, false);
+ TQPixmap pix = maskPreview.getTargetImage().convertToPixmap();
+ TQPainter pt(&pix);
+ pt.setPen( TQPen(TQt::black, 1) );
+ pt.drawRect( 0, 0, pix.width(), pix.height() );
+ pt.end();
+ m_maskPreviewLabel->setPixmap( pix );
+
+ Digikam::DImg orgImage(orgWidth, orgHeight, iface->originalSixteenBit(),
+ iface->originalHasAlpha(), data);
+ delete [] data;
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new AntiVignetting(&orgImage, this, d, p, r, 0, 0, true));
+}
+
+void ImageEffect_AntiVignetting::prepareFinal()
+{
+ m_densityInput->setEnabled(false);
+ m_powerInput->setEnabled(false);
+ m_radiusInput->setEnabled(false);
+ m_brightnessInput->setEnabled(false);
+ m_contrastInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+
+ double d = m_densityInput->value();
+ double p = m_powerInput->value();
+ double r = m_radiusInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+
+ uchar *data = iface.getOriginalImage();
+ Digikam::DImg orgImage(iface.originalWidth(), iface.originalHeight(), iface.originalSixteenBit(),
+ iface.originalHasAlpha(), data);
+ delete [] data;
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new AntiVignetting(&orgImage, this, d, p, r, 0, 0, true));
+}
+
+void ImageEffect_AntiVignetting::putPreviewData(void)
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage();
+
+ // Adjust Image BCG.
+
+ double b = (double)(m_brightnessInput->value() / 100.0);
+ double c = (double)(m_contrastInput->value() / 100.0) + (double)(1.00);
+ double g = m_gammaInput->value();
+
+ Digikam::BCGModifier cmod;
+ cmod.setGamma(g);
+ cmod.setBrightness(b);
+ cmod.setContrast(c);
+ cmod.applyBCG(imDest);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+ m_imagePreviewWidget->updatePreview();
+}
+
+void ImageEffect_AntiVignetting::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Vignetting Correction"),
+ m_threadedFilter->getTargetImage().bits());
+
+ double b = (double)(m_brightnessInput->value() / 100.0);
+ double c = (double)(m_contrastInput->value() / 100.0) + (double)(1.00);
+ double g = m_gammaInput->value();
+
+ // Adjust Image BCG.
+ iface.setOriginalBCG(b, c, g);
+}
+
+} // NameSpace DigikamAntiVignettingImagesPlugin
+
diff --git a/src/imageplugins/antivignetting/imageeffect_antivignetting.h b/src/imageplugins/antivignetting/imageeffect_antivignetting.h
new file mode 100644
index 00000000..3ee6f158
--- /dev/null
+++ b/src/imageplugins/antivignetting/imageeffect_antivignetting.h
@@ -0,0 +1,79 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : a digiKam image plugin to reduce
+ * vignetting on an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_ANTIVIGNETTING_H
+#define IMAGEEFFECT_ANTIVIGNETTING_H
+
+// Digikam includes.
+
+#include "imageguidedlg.h"
+
+class TQLabel;
+
+class KIntNumInput;
+class KDoubleNumInput;
+
+namespace DigikamAntiVignettingImagesPlugin
+{
+
+class ImageEffect_AntiVignetting : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_AntiVignetting(TQWidget *parent);
+ ~ImageEffect_AntiVignetting();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_maskPreviewLabel;
+
+ KIntNumInput *m_brightnessInput;
+ KIntNumInput *m_contrastInput;
+
+ KDoubleNumInput *m_gammaInput;
+ KDoubleNumInput *m_densityInput;
+ KDoubleNumInput *m_powerInput;
+ KDoubleNumInput *m_radiusInput;
+};
+
+} // NameSpace DigikamAntiVignettingImagesPlugin
+
+#endif /* IMAGEEFFECT_ANTIVIGNETTING_H */
diff --git a/src/imageplugins/antivignetting/imageplugin_antivignetting.cpp b/src/imageplugins/antivignetting/imageplugin_antivignetting.cpp
new file mode 100644
index 00000000..239d9842
--- /dev/null
+++ b/src/imageplugins/antivignetting/imageplugin_antivignetting.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-25
+ * Description : a digiKam image plugin to reduce
+ * vignetting on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "antivignettingtool.h"
+#include "imageplugin_antivignetting.h"
+#include "imageplugin_antivignetting.moc"
+
+using namespace DigikamAntiVignettingImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_antivignetting,
+ KGenericFactory<ImagePlugin_AntiVignetting>("digikamimageplugin_antivignetting"));
+
+ImagePlugin_AntiVignetting::ImagePlugin_AntiVignetting(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_AntiVignetting")
+{
+ m_antivignettingAction = new TDEAction(i18n("Vignetting Correction..."), "antivignetting", 0,
+ this, TQ_SLOT(slotAntiVignetting()),
+ actionCollection(), "imageplugin_antivignetting");
+
+ setXMLFile("digikamimageplugin_antivignetting_ui.rc");
+
+ DDebug() << "ImagePlugin_AntiVignetting plugin loaded" << endl;
+}
+
+ImagePlugin_AntiVignetting::~ImagePlugin_AntiVignetting()
+{
+}
+
+void ImagePlugin_AntiVignetting::setEnabledActions(bool enable)
+{
+ m_antivignettingAction->setEnabled(enable);
+}
+
+void ImagePlugin_AntiVignetting::slotAntiVignetting()
+{
+ AntiVignettingTool *tool = new AntiVignettingTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/antivignetting/imageplugin_antivignetting.h b/src/imageplugins/antivignetting/imageplugin_antivignetting.h
new file mode 100644
index 00000000..d8d4eedd
--- /dev/null
+++ b/src/imageplugins/antivignetting/imageplugin_antivignetting.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-25
+ * Description : a digiKam image plugin to reduce
+ * vignetting on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_ANTIVIGNETTING_H
+#define IMAGEPLUGIN_ANTIVIGNETTING_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_AntiVignetting : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_AntiVignetting(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_AntiVignetting();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotAntiVignetting();
+
+private:
+
+ TDEAction *m_antivignettingAction;
+};
+
+#endif /* IMAGEPLUGIN_ANTIVIGNETTING_H */
diff --git a/src/imageplugins/blurfx/Makefile.am b/src/imageplugins/blurfx/Makefile.am
new file mode 100644
index 00000000..51a78977
--- /dev/null
+++ b/src/imageplugins/blurfx/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_blurfx_la_SOURCES = imageplugin_blurfx.cpp \
+ blurfxtool.cpp blurfx.cpp
+
+digikamimageplugin_blurfx_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_blurfx_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_blurfx.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_blurfx.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_blurfx_ui.rc
+
diff --git a/src/imageplugins/blurfx/blurfx.cpp b/src/imageplugins/blurfx/blurfx.cpp
new file mode 100644
index 00000000..ec5ff30e
--- /dev/null
+++ b/src/imageplugins/blurfx/blurfx.cpp
@@ -0,0 +1,1445 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Blur FX threaded image filter.
+ *
+ * Copyright 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Blur algorithms copyrighted 2004 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Represents 1
+#define ANGLE_RATIO 0.017453292519943295769236907685
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+
+// TQt includes.
+
+#include <tqdatetime.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimggaussianblur.h"
+#include "blurfx.h"
+
+namespace DigikamBlurFXImagesPlugin
+{
+
+BlurFX::BlurFX(Digikam::DImg *orgImage, TQObject *parent, int blurFXType, int distance, int level)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "BlurFX")
+{
+ m_blurFXType = blurFXType;
+ m_distance = distance;
+ m_level = level;
+
+ initFilter();
+}
+
+void BlurFX::filterImage(void)
+{
+ int w = m_orgImage.width();
+ int h = m_orgImage.height();
+
+ switch (m_blurFXType)
+ {
+ case ZoomBlur:
+ zoomBlur(&m_orgImage, &m_destImage, w/2, h/2, m_distance);
+ break;
+
+ case RadialBlur:
+ radialBlur(&m_orgImage, &m_destImage, w/2, h/2, m_distance);
+ break;
+
+ case FarBlur:
+ farBlur(&m_orgImage, &m_destImage, m_distance);
+ break;
+
+ case MotionBlur:
+ motionBlur(&m_orgImage, &m_destImage, m_distance, (double)m_level);
+ break;
+
+ case SoftenerBlur:
+ softenerBlur(&m_orgImage, &m_destImage);
+ break;
+
+ case ShakeBlur:
+ shakeBlur(&m_orgImage, &m_destImage, m_distance);
+ break;
+
+ case FocusBlur:
+ focusBlur(&m_orgImage, &m_destImage, w/2, h/2, m_distance, m_level*10);
+ break;
+
+ case SmartBlur:
+ smartBlur(&m_orgImage, &m_destImage, m_distance, m_level);
+ break;
+
+ case FrostGlass:
+ frostGlass(&m_orgImage, &m_destImage, m_distance);
+ break;
+
+ case Mosaic:
+ mosaic(&m_orgImage, &m_destImage, m_distance, m_distance);
+ break;
+ }
+}
+
+/* Function to apply the ZoomBlur effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * X, Y => Center of zoom in the image
+ * Distance => Distance value
+ * pArea => Preview area.
+ *
+ * Theory => Here we have a effect similar to RadialBlur mode Zoom from
+ * Photoshop. The theory is very similar to RadialBlur, but has one
+ * difference. Instead we use pixels with the same radius and
+ * near angles, we take pixels with the same angle but near radius
+ * This radius is always from the center to out of the image, we
+ * calc a proportional radius from the center.
+ */
+void BlurFX::zoomBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int X, int Y, int Distance, TQRect pArea)
+{
+ if (Distance <= 1) return;
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ // We working on full image.
+ int xMin = 0;
+ int xMax = Width;
+ int yMin = 0;
+ int yMax = Height;
+
+ // If we working in preview mode, else we using the preview area.
+ if ( pArea.isValid() )
+ {
+ xMin = pArea.x();
+ xMax = pArea.x() + pArea.width();
+ yMin = pArea.y();
+ yMax = pArea.y() + pArea.height();
+ }
+
+ int h, w, nh, nw, r;
+ int sumR, sumG, sumB, nCount;
+ double lfRadius, lfNewRadius, lfRadMax, lfAngle;
+
+ Digikam::DColor color;
+ int offset;
+
+ lfRadMax = sqrt (Height * Height + Width * Width);
+
+ // number of added pixels
+ nCount = 0;
+
+ // we have reached the main loop
+ for (h = yMin; !m_cancel && (h < yMax); h++)
+ {
+ for (w = xMin; !m_cancel && (w < xMax); w++)
+ {
+ // ...we enter this loop to sum the bits
+
+ // we initialize the variables
+ sumR = sumG = sumB = nCount = 0;
+
+ nw = X - w;
+ nh = Y - h;
+
+ lfRadius = sqrt (nw * nw + nh * nh);
+ lfAngle = atan2 ((double)nh, (double)nw);
+ lfNewRadius = (lfRadius * Distance) / lfRadMax;
+
+ for (r = 0; !m_cancel && (r <= lfNewRadius); r++)
+ {
+ // we need to calc the positions
+ nw = (int)(X - (lfRadius - r) * cos (lfAngle));
+ nh = (int)(Y - (lfRadius - r) * sin (lfAngle));
+
+ if (IsInside(Width, Height, nw, nh))
+ {
+ // read color
+ offset = GetOffset(Width, nw, nh, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+
+ // we sum the bits
+ sumR += color.red();
+ sumG += color.green();
+ sumB += color.blue();
+ nCount++;
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // calculate pointer
+ offset = GetOffset(Width, w, h, bytesDepth);
+ // read color to preserve alpha
+ color.setColor(data + offset, sixteenBit);
+
+ // now, we have to calc the arithmetic average
+ color.setRed (sumR / nCount);
+ color.setGreen(sumG / nCount);
+ color.setBlue (sumB / nCount);
+
+ // write color to destination
+ color.setPixel(pResBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)(h - yMin) * 100.0) / (yMax - yMin));
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the radialBlur effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * X, Y => Center of radial in the image
+ * Distance => Distance value
+ * pArea => Preview area.
+ *
+ * Theory => Similar to RadialBlur from Photoshop, its an amazing effect
+ * Very easy to understand but a little hard to implement.
+ * We have all the image and find the center pixel. Now, we analize
+ * all the pixels and calc the radius from the center and find the
+ * angle. After this, we sum this pixel with others with the same
+ * radius, but different angles. Here I'm using degrees angles.
+ */
+void BlurFX::radialBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int X, int Y, int Distance, TQRect pArea)
+{
+ if (Distance <= 1) return;
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ // We working on full image.
+ int xMin = 0;
+ int xMax = Width;
+ int yMin = 0;
+ int yMax = Height;
+
+ // If we working in preview mode, else we using the preview area.
+ if ( pArea.isValid() )
+ {
+ xMin = pArea.x();
+ xMax = pArea.x() + pArea.width();
+ yMin = pArea.y();
+ yMax = pArea.y() + pArea.height();
+ }
+
+ int sumR, sumG, sumB, nw, nh;
+ double Radius, Angle, AngleRad;
+
+ Digikam::DColor color;
+ int offset;
+
+ double *nMultArray = new double[Distance * 2 + 1];
+
+ for (int i = -Distance; i <= Distance; i++)
+ nMultArray[i + Distance] = i * ANGLE_RATIO;
+
+ // number of added pixels
+ int nCount = 0;
+
+ // we have reached the main loop
+
+ for (int h = yMin; !m_cancel && (h < yMax); h++)
+ {
+ for (int w = xMin; !m_cancel && (w < xMax); w++)
+ {
+ // ...we enter this loop to sum the bits
+
+ // we initialize the variables
+ sumR = sumG = sumB = nCount = 0;
+
+ nw = X - w;
+ nh = Y - h;
+
+ Radius = sqrt (nw * nw + nh * nh);
+ AngleRad = atan2 ((double)nh, (double)nw);
+
+ for (int a = -Distance; !m_cancel && (a <= Distance); a++)
+ {
+ Angle = AngleRad + nMultArray[a + Distance];
+ // we need to calc the positions
+ nw = (int)(X - Radius * cos (Angle));
+ nh = (int)(Y - Radius * sin (Angle));
+
+ if (IsInside(Width, Height, nw, nh))
+ {
+ // read color
+ offset = GetOffset(Width, nw, nh, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+
+ // we sum the bits
+ sumR += color.red();
+ sumG += color.green();
+ sumB += color.blue();
+ nCount++;
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // calculate pointer
+ offset = GetOffset(Width, w, h, bytesDepth);
+ // read color to preserve alpha
+ color.setColor(data + offset, sixteenBit);
+
+ // now, we have to calc the arithmetic average
+ color.setRed (sumR / nCount);
+ color.setGreen(sumG / nCount);
+ color.setBlue (sumB / nCount);
+
+ // write color to destination
+ color.setPixel(pResBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)(h - yMin) * 100.0) / (yMax - yMin));
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ delete [] nMultArray;
+}
+
+/* Function to apply the focusBlur effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * BlurRadius => Radius of blurred image.
+ * BlendRadius => Radius of blending effect.
+ * bInversed => If true, invert focus effect.
+ * pArea => Preview area.
+ *
+ */
+void BlurFX::focusBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ int X, int Y, int BlurRadius, int BlendRadius,
+ bool bInversed, TQRect pArea)
+{
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ // We working on full image.
+ int xMin = 0;
+ int xMax = Width;
+ int yMin = 0;
+ int yMax = Height;
+
+ // If we working in preview mode, else we using the preview area.
+ if ( pArea.isValid() )
+ {
+ xMin = pArea.x();
+ xMax = pArea.x() + pArea.width();
+ yMin = pArea.y();
+ yMax = pArea.y() + pArea.height();
+ }
+
+ if (pArea.isValid())
+ {
+ //UNTESTED (unused)
+
+ // We do not have access to the loop of the Gaussian blur,
+ // so we have to cut the image that we run the effect on.
+ int xMinBlur = xMin - BlurRadius;
+ int xMaxBlur = xMax + BlurRadius;
+ int yMinBlur = yMin - BlurRadius;
+ int yMaxBlur = yMax + BlurRadius;
+ Digikam::DImg areaImage = orgImage->copy(xMinBlur, yMaxBlur, xMaxBlur - xMinBlur, yMaxBlur - yMinBlur);
+
+ Digikam::DImgGaussianBlur(this, *orgImage, *destImage, 10, 75, BlurRadius);
+
+ // I am unsure about differences of 1 pixel
+ destImage->bitBltImage(&areaImage, xMinBlur, yMinBlur);
+ destImage->bitBltImage(orgImage, 0, 0, Width, yMinBlur, 0, 0);
+ destImage->bitBltImage(orgImage, 0, yMinBlur, xMinBlur, yMaxBlur - yMinBlur, 0, yMinBlur);
+ destImage->bitBltImage(orgImage, xMaxBlur + 1, yMinBlur, Width - xMaxBlur - 1, yMaxBlur - yMinBlur, yMaxBlur, yMinBlur);
+ destImage->bitBltImage(orgImage, 0, yMaxBlur + 1, Width, Height - yMaxBlur - 1, 0, yMaxBlur);
+
+ postProgress(80);
+ }
+ else
+ {
+ // copy bits for blurring
+ memcpy(pResBits, data, orgImage->numBytes());
+
+ // Gaussian blur using the BlurRadius parameter.
+ Digikam::DImgGaussianBlur(this, *orgImage, *destImage, 10, 80, BlurRadius);
+ }
+
+ // Blending results.
+
+ int nBlendFactor;
+ double lfRadius;
+ int offset;
+
+ Digikam::DColor colorOrgImage, colorBlurredImage;
+ int alpha;
+ uchar *ptr;
+
+ // get composer for default blending
+ Digikam::DColorComposer *composer = Digikam::DColorComposer::getComposer(Digikam::DColorComposer::PorterDuffNone);
+
+ int nh = 0, nw = 0;
+
+ for (int h = yMin; !m_cancel && (h < yMax); h++)
+ {
+ nh = Y - h;
+
+ for (int w = xMin; !m_cancel && (w < xMax); w++)
+ {
+ nw = X - w;
+
+ lfRadius = sqrt (nh * nh + nw * nw);
+
+ if (sixteenBit)
+ nBlendFactor = LimitValues16 ((int)(65535.0 * lfRadius / (double)BlendRadius));
+ else
+ nBlendFactor = LimitValues8 ((int)(255.0 * lfRadius / (double)BlendRadius));
+
+ // Read color values
+ offset = GetOffset(Width, w, h, bytesDepth);
+ ptr = pResBits + offset;
+ colorOrgImage.setColor(data + offset, sixteenBit);
+ colorBlurredImage.setColor(ptr, sixteenBit);
+
+ // Preserve alpha
+ alpha = colorOrgImage.alpha();
+
+ // In normal mode, the image is focused in the middle
+ // and less focused towards the border.
+ // In inversed mode, the image is more focused towards the edge
+ // and less focused in the middle.
+ // This is achieved by swapping src and dest while blending.
+ if (bInversed)
+ {
+ // set blending alpha value as src alpha. Original value is stored above.
+ colorOrgImage.setAlpha(nBlendFactor);
+ // compose colors, writing to dest - colorBlurredImage
+ composer->compose(colorBlurredImage, colorOrgImage);
+ // restore alpha
+ colorBlurredImage.setAlpha(alpha);
+ // write color to destination
+ colorBlurredImage.setPixel(ptr);
+ }
+ else
+ {
+ // set blending alpha value as src alpha. Original value is stored above.
+ colorBlurredImage.setAlpha(nBlendFactor);
+ // compose colors, writing to dest - colorOrgImage
+ composer->compose(colorOrgImage, colorBlurredImage);
+ // restore alpha
+ colorOrgImage.setAlpha(alpha);
+ // write color to destination
+ colorOrgImage.setPixel(ptr);
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (80.0 + ((double)(h - yMin) * 20.0) / (yMax - yMin));
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ delete composer;
+}
+
+/* Function to apply the farBlur effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Distance => Distance value
+ *
+ * Theory => This is an interesting effect, the blur is applied in that
+ * way: (the value "1" means pixel to be used in a blur calc, ok?)
+ * e.g. With distance = 2
+ * |1|1|1|1|1|
+ * |1|0|0|0|1|
+ * |1|0|C|0|1|
+ * |1|0|0|0|1|
+ * |1|1|1|1|1|
+ * We sum all the pixels with value = 1 and apply at the pixel with*
+ * the position "C".
+ */
+void BlurFX::farBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance)
+{
+ if (Distance < 1) return;
+
+ // we need to create our kernel
+ // e.g. distance = 3, so kernel={3 1 1 2 1 1 3}
+
+ int *nKern = new int[Distance * 2 + 1];
+
+ for (int i = 0; i < Distance * 2 + 1; i++)
+ {
+ // the first element is 3
+ if (i == 0)
+ nKern[i] = 2;
+ // the center element is 2
+ else if (i == Distance)
+ nKern[i] = 3;
+ // the last element is 3
+ else if (i == Distance * 2)
+ nKern[i] = 3;
+ // all other elements will be 1
+ else
+ nKern[i] = 1;
+ }
+
+ // now, we apply a convolution with kernel
+ MakeConvolution(orgImage, destImage, Distance, nKern);
+
+ // we must delete to free memory
+ delete [] nKern;
+}
+
+/* Function to apply the SmartBlur effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Radius => blur matrix radius.
+ * Strenght => Color strenght.
+ *
+ * Theory => Similar to SmartBlur from Photoshop, this function has the
+ * same engine as Blur function, but, in a matrix with n
+ * dimentions, we take only colors that pass by sensibility filter
+ * The result is a clean image, not totally blurred, but a image
+ * with correction between pixels.
+ */
+
+void BlurFX::smartBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Radius, int Strength)
+{
+ if (Radius <= 0) return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int progress;
+ int sumR, sumG, sumB, nCount, w, h, a;
+
+ int StrengthRange = Strength;
+ if (sixteenBit)
+ StrengthRange = (StrengthRange + 1) * 256 - 1;
+
+ Digikam::DColor color, radiusColor, radiusColorBlur;
+ int offset, loopOffset;
+
+ uchar* pBlur = new uchar[orgImage->numBytes()];
+
+ // We need to copy our bits to blur bits
+
+ memcpy (pBlur, data, orgImage->numBytes());
+
+ // we have reached the main loop
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ // we initialize the variables
+ sumR = sumG = sumB = nCount = 0;
+
+ // read color
+ offset = GetOffset(Width, w, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+
+ // ...we enter this loop to sum the bits
+ for (a = -Radius; !m_cancel && (a <= Radius); a++)
+ {
+ // verify if is inside the rect
+ if (IsInside( Width, Height, w + a, h))
+ {
+ // read color
+ loopOffset = GetOffset(Width, w+a, h, bytesDepth);
+ radiusColor.setColor(data + loopOffset, sixteenBit);
+
+ // now, we have to check if is inside the sensibility filter
+ if (IsColorInsideTheRange (color.red(), color.green(), color.blue(),
+ radiusColor.red(), radiusColor.green(), radiusColor.blue(),
+ StrengthRange))
+ {
+ // finally we sum the bits
+ sumR += radiusColor.red();
+ sumG += radiusColor.green();
+ sumB += radiusColor.blue();
+ }
+ else
+ {
+ // finally we sum the bits
+ sumR += color.red();
+ sumG += color.green();
+ sumB += color.blue();
+ }
+
+ // increment counter
+ nCount++;
+ }
+ }
+
+ // now, we have to calc the arithmetic average
+ color.setRed (sumR / nCount);
+ color.setGreen(sumG / nCount);
+ color.setBlue (sumB / nCount);
+
+ // write color to destination
+ color.setPixel(pBlur + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 50.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ // we have reached the second part of main loop
+
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ for (h = 0;!m_cancel && ( h < Height); h++)
+ {
+ // we initialize the variables
+ sumR = sumG = sumB = nCount = 0;
+
+ // read color
+ offset = GetOffset(Width, w, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+
+ // ...we enter this loop to sum the bits
+ for (a = -Radius; !m_cancel && (a <= Radius); a++)
+ {
+ // verify if is inside the rect
+ if (IsInside( Width, Height, w, h + a))
+ {
+ // read color
+ loopOffset = GetOffset(Width, w, h+a, bytesDepth);
+ radiusColor.setColor(data + loopOffset, sixteenBit);
+
+ // now, we have to check if is inside the sensibility filter
+ if (IsColorInsideTheRange (color.red(), color.green(), color.blue(),
+ radiusColor.red(), radiusColor.green(), radiusColor.blue(),
+ StrengthRange))
+ {
+ radiusColorBlur.setColor(pBlur + loopOffset, sixteenBit);
+ // finally we sum the bits
+ sumR += radiusColorBlur.red();
+ sumG += radiusColorBlur.green();
+ sumB += radiusColorBlur.blue();
+ }
+ else
+ {
+ // finally we sum the bits
+ sumR += color.red();
+ sumG += color.green();
+ sumB += color.blue();
+ }
+
+ // increment counter
+ nCount++;
+ }
+ }
+
+ // now, we have to calc the arithmetic average
+ color.setRed (sumR / nCount);
+ color.setGreen(sumG / nCount);
+ color.setBlue (sumB / nCount);
+
+ // write color to destination
+ color.setPixel(pResBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (50.0 + ((double)w * 50.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ // now, we must free memory
+ delete [] pBlur;
+}
+
+/* Function to apply the motionBlur effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Distance => Distance value
+ * Angle => Angle direction (degrees)
+ *
+ * Theory => Similar to MotionBlur from Photoshop, the engine is very
+ * simple to undertand, we take a pixel (duh!), with the angle we
+ * will taking near pixels. After this we blur (add and do a
+ * division).
+ */
+void BlurFX::motionBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance, double Angle)
+{
+ if (Distance == 0) return;
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ Digikam::DColor color;
+ int offset;
+
+ // we try to avoid division by 0 (zero)
+ if (Angle == 0.0) Angle = 360.0;
+
+ int sumR, sumG, sumB, nCount, nw, nh;
+ double nAngX, nAngY;
+
+ // we initialize cos and sin for a best performance
+ nAngX = cos ((2.0 * M_PI) / (360.0 / Angle));
+ nAngY = sin ((2.0 * M_PI) / (360.0 / Angle));
+
+ // total of bits to be taken is given by this formula
+ nCount = Distance * 2 + 1;
+
+ // we will alloc size and calc the possible results
+ int *lpXArray = new int[nCount];
+ int *lpYArray = new int[nCount];
+
+ for (int i = 0; i < nCount; i++)
+ {
+ lpXArray[i] = lround( (double)(i - Distance) * nAngX);
+ lpYArray[i] = lround( (double)(i - Distance) * nAngY);
+ }
+
+ // we have reached the main loop
+
+ for (int h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (int w = 0; !m_cancel && (w < Width); w++)
+ {
+ // we initialize the variables
+ sumR = sumG = sumB = 0;
+
+ // ...we enter this loop to sum the bits
+ for (int a = -Distance; !m_cancel && (a <= Distance); a++)
+ {
+ // we need to calc the positions
+ nw = w + lpXArray[a + Distance];
+ nh = h + lpYArray[a + Distance];
+
+ offset = GetOffsetAdjusted(Width, Height, nw, nh, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+
+ // we sum the bits
+ sumR += color.red();
+ sumG += color.green();
+ sumB += color.blue();
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // calculate pointer
+ offset = GetOffset(Width, w, h, bytesDepth);
+ // read color to preserve alpha
+ color.setColor(data + offset, sixteenBit);
+
+ // now, we have to calc the arithmetic average
+ color.setRed (sumR / nCount);
+ color.setGreen(sumG / nCount);
+ color.setBlue (sumB / nCount);
+
+ // write color to destination
+ color.setPixel(pResBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ delete [] lpXArray;
+ delete [] lpYArray;
+}
+
+/* Function to apply the softenerBlur effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ *
+ * Theory => An interesting blur-like function. In dark tones we apply a
+ * blur with 3x3 dimentions, in light tones, we apply a blur with
+ * 5x5 dimentions. Easy, hun?
+ */
+void BlurFX::softenerBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage)
+{
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int SomaR = 0, SomaG = 0, SomaB = 0;
+ int Gray;
+
+ Digikam::DColor color, colorSoma;
+ int offset, offsetSoma;
+
+ int grayLimit = sixteenBit ? 32767 : 127;
+
+ for (int h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (int w = 0; !m_cancel && (w < Width); w++)
+ {
+ SomaR = SomaG = SomaB = 0;
+
+ offset = GetOffset(Width, w, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+
+ Gray = (color.red() + color.green() + color.blue()) / 3;
+
+ if (Gray > grayLimit)
+ {
+ // 7x7
+ for (int a = -3; !m_cancel && (a <= 3); a++)
+ {
+ for (int b = -3; !m_cancel && (b <= 3); b++)
+ {
+ if ((h + a < 0) || (w + b < 0))
+ offsetSoma = offset;
+ else
+ offsetSoma = GetOffset(Width, (w + Lim_Max (w, b, Width)),
+ (h + Lim_Max (h, a, Height)), bytesDepth);
+ colorSoma.setColor(data + offsetSoma, sixteenBit);
+
+ SomaR += colorSoma.red();
+ SomaG += colorSoma.green();
+ SomaB += colorSoma.blue();
+ }
+ }
+
+ // 7*7 = 49
+ color.setRed (SomaR / 49);
+ color.setGreen(SomaG / 49);
+ color.setBlue (SomaB / 49);
+ color.setPixel(pResBits + offset);
+ }
+ else
+ {
+ // 3x3
+ for (int a = -1; !m_cancel && (a <= 1); a++)
+ {
+ for (int b = -1; !m_cancel && (b <= 1); b++)
+ {
+ if ((h + a < 0) || (w + b < 0))
+ offsetSoma = offset;
+ else
+ offsetSoma = GetOffset(Width, (w + Lim_Max (w, b, Width)),
+ (h + Lim_Max (h, a, Height)), bytesDepth);
+ colorSoma.setColor(data + offsetSoma, sixteenBit);
+
+ SomaR += colorSoma.red();
+ SomaG += colorSoma.green();
+ SomaB += colorSoma.blue();
+ }
+ }
+
+ // 3*3 = 9
+ color.setRed (SomaR / 9);
+ color.setGreen(SomaG / 9);
+ color.setBlue (SomaB / 9);
+ color.setPixel(pResBits + offset);
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the shake blur effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Distance => Distance between layers (from origin)
+ *
+ * Theory => Similar to Fragment effect from Photoshop. We create 4 layers
+ * each one has the same distance from the origin, but have
+ * different positions (top, button, left and right), with these 4
+ * layers, we join all the pixels.
+ */
+void BlurFX::shakeBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance)
+{
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ Digikam::DColor color, colorLayer, color1, color2, color3, color4;
+ int offset, offsetLayer;
+
+ int numBytes = orgImage->numBytes();
+ uchar* Layer1 = new uchar[numBytes];
+ uchar* Layer2 = new uchar[numBytes];
+ uchar* Layer3 = new uchar[numBytes];
+ uchar* Layer4 = new uchar[numBytes];
+
+ int h, w, nw, nh;
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ offsetLayer = GetOffset(Width, w, h, bytesDepth);
+
+ nh = (h + Distance >= Height) ? Height - 1 : h + Distance;
+ offset = GetOffset(Width, w, nh, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+ color.setPixel(Layer1 + offsetLayer);
+
+ nh = (h - Distance < 0) ? 0 : h - Distance;
+ offset = GetOffset(Width, w, nh, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+ color.setPixel(Layer2 + offsetLayer);
+
+ nw = (w + Distance >= Width) ? Width - 1 : w + Distance;
+ offset = GetOffset(Width, nw, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+ color.setPixel(Layer3 + offsetLayer);
+
+ nw = (w - Distance < 0) ? 0 : w - Distance;
+ offset = GetOffset(Width, nw, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+ color.setPixel(Layer4 + offsetLayer);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 50.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ for (int h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (int w = 0; !m_cancel && (w < Width); w++)
+ {
+ offset = GetOffset(Width, w, h, bytesDepth);
+ // read original data to preserve alpha
+ color.setColor(data + offset, sixteenBit);
+ // read colors from all four layers
+ color1.setColor(Layer1 + offset, sixteenBit);
+ color2.setColor(Layer2 + offset, sixteenBit);
+ color3.setColor(Layer3 + offset, sixteenBit);
+ color4.setColor(Layer4 + offset, sixteenBit);
+
+ // set color components of resulting color
+ color.setRed ( (color1.red() + color2.red() + color3.red() + color4.red()) / 4 );
+ color.setGreen( (color1.green() + color2.green() + color3.green() + color4.green()) / 4 );
+ color.setBlue ( (color1.blue() + color2.blue() + color3.blue() + color4.blue()) / 4 );
+
+ color.setPixel(pResBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (50.0 + ((double)h * 50.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ delete [] Layer1;
+ delete [] Layer2;
+ delete [] Layer3;
+ delete [] Layer4;
+}
+
+/* Function to apply the frostGlass effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Frost => Frost value
+ *
+ * Theory => Similar to Diffuse effect, but the random byte is defined
+ * in a matrix. Diffuse uses a random diagonal byte.
+ */
+void BlurFX::frostGlass(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Frost)
+{
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ Frost = (Frost < 1) ? 1 : (Frost > 10) ? 10 : Frost;
+
+ int h, w;
+
+ Digikam::DColor color;
+ int offset;
+
+ // Randomize.
+
+ TQDateTime dt = TQDateTime::currentDateTime();
+ TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
+ uint seed = dt.secsTo(Y2000);
+
+ int range = sixteenBit ? 65535 : 255;
+
+ // it is a huge optimizsation to allocate these here once
+ uchar *IntensityCount = new uchar[range + 1];
+ uint *AverageColorR = new uint[range + 1];
+ uint *AverageColorG = new uint[range + 1];
+ uint *AverageColorB = new uint[range + 1];
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ offset = GetOffset(Width, w, h, bytesDepth);
+ // read color to preserve alpha
+ color.setColor(data + offset, sixteenBit);
+
+ // get random color from surrounding of w|h
+ color = RandomColor (data, Width, Height, sixteenBit, bytesDepth,
+ w, h, Frost, color.alpha(), &seed, range, IntensityCount,
+ AverageColorR, AverageColorG, AverageColorB);
+
+ // write color to destination
+ color.setPixel(pResBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ delete [] IntensityCount;
+ delete [] AverageColorR;
+ delete [] AverageColorG;
+ delete [] AverageColorB;
+}
+
+/* Function to apply the mosaic effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Size => Size of mosaic .
+ *
+ * Theory => Ok, you can find some mosaic effects on PSC, but this one
+ * has a great feature, if you see a mosaic in other code you will
+ * see that the corner pixel doesn't change. The explanation is
+ * simple, the color of the mosaic is the same as the first pixel
+ * get. Here, the color of the mosaic is the same as the mosaic
+ * center pixel.
+ * Now the function scan the rows from the top (like photoshop).
+ */
+void BlurFX::mosaic(Digikam::DImg *orgImage, Digikam::DImg *destImage, int SizeW, int SizeH)
+{
+ int progress;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ // we need to check for valid values
+ if (SizeW < 1) SizeW = 1;
+ if (SizeH < 1) SizeH = 1;
+ if ((SizeW == 1) && (SizeH == 1)) return;
+
+ Digikam::DColor color;
+ int offsetCenter, offset;
+
+ // this loop will never look for transparent colors
+
+ for (int h = 0; !m_cancel && (h < Height); h += SizeH)
+ {
+ for (int w = 0; !m_cancel && (w < Width); w += SizeW)
+ {
+ // we have to find the center pixel for mosaic's rectangle
+
+ offsetCenter = GetOffsetAdjusted(Width, Height, w + (SizeW / 2), h + (SizeH / 2), bytesDepth);
+ color.setColor(data + offsetCenter, sixteenBit);
+
+ // now, we fill the mosaic's rectangle with the center pixel color
+
+ for (int subw = w; !m_cancel && (subw <= w + SizeW); subw++)
+ {
+ for (int subh = h; !m_cancel && (subh <= h + SizeH); subh++)
+ {
+ // if is inside...
+ if (IsInside(Width, Height, subw, subh))
+ {
+ // set color
+ offset = GetOffset(Width, subw, subh, bytesDepth);
+ color.setPixel(pResBits + offset);
+ }
+ }
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to get a color in a matriz with a determined size
+ *
+ * Bits => Bits array
+ * Width => Image width
+ * Height => Image height
+ * X => Position horizontal
+ * Y => Position vertical
+ * Radius => The radius of the matrix to be created
+ *
+ * Theory => This function takes from a distinct matrix a random color
+ */
+Digikam::DColor BlurFX::RandomColor(uchar *Bits, int Width, int Height, bool sixteenBit, int bytesDepth,
+ int X, int Y, int Radius,
+ int alpha, uint *randomSeed, int range, uchar *IntensityCount,
+ uint *AverageColorR, uint *AverageColorG, uint *AverageColorB)
+{
+ Digikam::DColor color;
+ int offset;
+
+ int w, h, counter = 0;
+
+ int I;
+
+ // For 16 bit we have a problem here because this takes 255 times longer,
+ // and the algorithm is really slow for 16 bit, but I think this cannot be avoided.
+ memset(IntensityCount, 0, range );
+ memset(AverageColorR, 0, range );
+ memset(AverageColorG, 0, range );
+ memset(AverageColorB, 0, range );
+
+ for (w = X - Radius; !m_cancel && (w <= X + Radius); w++)
+ {
+ for (h = Y - Radius; !m_cancel && (h <= Y + Radius); h++)
+ {
+ if ((w >= 0) && (w < Width) && (h >= 0) && (h < Height))
+ {
+ offset = GetOffset(Width, w, h, bytesDepth);
+ color.setColor(Bits + offset, sixteenBit);
+ I = GetIntensity (color.red(), color.green(), color.blue());
+ IntensityCount[I]++;
+ counter++;
+
+ if (IntensityCount[I] == 1)
+ {
+ AverageColorR[I] = color.red();
+ AverageColorG[I] = color.green();
+ AverageColorB[I] = color.blue();
+ }
+ else
+ {
+ AverageColorR[I] += color.red();
+ AverageColorG[I] += color.green();
+ AverageColorB[I] += color.blue();
+ }
+ }
+ }
+ }
+
+ // check for m_cancel here before entering the do loop (will crash with SIGFPE otherwise)
+ if (m_cancel)
+ return Digikam::DColor(0, 0, 0, 0, sixteenBit);
+
+ int RandNumber, count, Index, ErrorCount = 0;
+ int J;
+
+ do
+ {
+ RandNumber = abs( (int)((rand_r(randomSeed) + 1) * ((double)counter / (1 + (double) RAND_MAX))) );
+ count = 0;
+ Index = 0;
+
+ do
+ {
+ count += IntensityCount[Index];
+ Index++;
+ }
+ while (count < RandNumber && !m_cancel);
+
+ J = Index - 1;
+ ErrorCount++;
+ }
+ while ((IntensityCount[J] == 0) && (ErrorCount <= counter) && !m_cancel);
+
+ if (m_cancel)
+ return Digikam::DColor(0, 0, 0, 0, sixteenBit);
+
+
+ color.setSixteenBit(sixteenBit);
+ color.setAlpha(alpha);
+
+ if (ErrorCount >= counter)
+ {
+ color.setRed (AverageColorR[J] / counter);
+ color.setGreen(AverageColorG[J] / counter);
+ color.setBlue (AverageColorB[J] / counter);
+ }
+ else
+ {
+ color.setRed (AverageColorR[J] / IntensityCount[J]);
+ color.setGreen(AverageColorG[J] / IntensityCount[J]);
+ color.setBlue (AverageColorB[J] / IntensityCount[J]);
+ }
+
+ return color;
+}
+
+/* Function to simple convolve a unique pixel with a determined radius
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Radius => kernel radius, e.g. rad=1, so array will be 3X3
+ * Kernel => kernel array to apply.
+ *
+ * Theory => I've worked hard here, but I think this is a very smart
+ * way to convolve an array, its very hard to explain how I reach
+ * this, but the trick here its to store the sum used by the
+ * previous pixel, so we sum with the other pixels that wasn't get
+ */
+void BlurFX::MakeConvolution (Digikam::DImg *orgImage, Digikam::DImg *destImage, int Radius, int Kernel[])
+{
+ if (Radius <= 0) return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pOutBits = destImage->bits();
+
+ int progress;
+ int n, h, w;
+
+ int nSumR, nSumG, nSumB, nCount;
+ int nKernelWidth = Radius * 2 + 1;
+ int range = sixteenBit ? 65536 : 256;
+ Digikam::DColor color;
+ int offset;
+
+ uchar* pBlur = new uchar[orgImage->numBytes()];
+
+ // We need to copy our bits to blur bits
+
+ memcpy (pBlur, data, orgImage->numBytes());
+
+ // We need to alloc a 2d array to help us to store the values
+
+ int** arrMult = Alloc2DArray (nKernelWidth, range);
+
+ for (int i = 0; i < nKernelWidth; i++)
+ for (int j = 0; j < range; j++)
+ arrMult[i][j] = j * Kernel[i];
+
+ // Now, we enter in the main loop
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ // initialize the variables
+ nSumR = nSumG = nSumB = nCount = 0;
+
+ // first of all, we need to blur the horizontal lines
+
+ for (n = -Radius; !m_cancel && (n <= Radius); n++)
+ {
+ // if is inside...
+ if (IsInside (Width, Height, w + n, h))
+ {
+ // read color from orgImage
+ offset = GetOffset(Width, w+n, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+
+ // finally, we sum the pixels using a method similar to assigntables
+ nSumR += arrMult[n + Radius][color.red()];
+ nSumG += arrMult[n + Radius][color.green()];
+ nSumB += arrMult[n + Radius][color.blue()];
+
+ // we need to add the kernel value to the counter
+ nCount += Kernel[n + Radius];
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // calculate pointer
+ offset = GetOffset(Width, w, h, bytesDepth);
+ // read color from orgImage to preserve alpha
+ color.setColor(data + offset, sixteenBit);
+
+ // now, we have to calc the arithmetic average
+ if (sixteenBit)
+ {
+ color.setRed (LimitValues16(nSumR / nCount));
+ color.setGreen(LimitValues16(nSumG / nCount));
+ color.setBlue (LimitValues16(nSumB / nCount));
+ }
+ else
+ {
+ color.setRed (LimitValues8(nSumR / nCount));
+ color.setGreen(LimitValues8(nSumG / nCount));
+ color.setBlue (LimitValues8(nSumB / nCount));
+ }
+
+ // write color to blur bits
+ color.setPixel(pBlur + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 50.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ // We enter in the second main loop
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ // initialize the variables
+ nSumR = nSumG = nSumB = nCount = 0;
+
+ // first of all, we need to blur the vertical lines
+ for (n = -Radius; !m_cancel && (n <= Radius); n++)
+ {
+ // if is inside...
+ if (IsInside(Width, Height, w, h + n))
+ {
+ // read color from blur bits
+ offset = GetOffset(Width, w, h+n, bytesDepth);
+ color.setColor(pBlur + offset, sixteenBit);
+
+ // finally, we sum the pixels using a method similar to assigntables
+ nSumR += arrMult[n + Radius][color.red()];
+ nSumG += arrMult[n + Radius][color.green()];
+ nSumB += arrMult[n + Radius][color.blue()];
+
+ // we need to add the kernel value to the counter
+ nCount += Kernel[n + Radius];
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // calculate pointer
+ offset = GetOffset(Width, w, h, bytesDepth);
+ // read color from orgImage to preserve alpha
+ color.setColor(data + offset, sixteenBit);
+
+ // now, we have to calc the arithmetic average
+ if (sixteenBit)
+ {
+ color.setRed (LimitValues16(nSumR / nCount));
+ color.setGreen(LimitValues16(nSumG / nCount));
+ color.setBlue (LimitValues16(nSumB / nCount));
+ }
+ else
+ {
+ color.setRed (LimitValues8(nSumR / nCount));
+ color.setGreen(LimitValues8(nSumG / nCount));
+ color.setBlue (LimitValues8(nSumB / nCount));
+ }
+
+ // write color to destination
+ color.setPixel(pOutBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (50.0 + ((double)w * 50.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ // now, we must free memory
+ Free2DArray (arrMult, nKernelWidth);
+ delete [] pBlur;
+}
+
+} // NameSpace DigikamBlurFXImagesPlugin
diff --git a/src/imageplugins/blurfx/blurfx.h b/src/imageplugins/blurfx/blurfx.h
new file mode 100644
index 00000000..4a4397b4
--- /dev/null
+++ b/src/imageplugins/blurfx/blurfx.h
@@ -0,0 +1,191 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Blur FX threaded image filter.
+ *
+ * Copyright 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Blur algorithms copyrighted 2004 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BLURFX_H
+#define BLURFX_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamBlurFXImagesPlugin
+{
+
+class BlurFX : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ BlurFX(Digikam::DImg *orgImage, TQObject *parent=0, int blurFXType=ZoomBlur,
+ int distance=100, int level=45);
+
+ ~BlurFX(){};
+
+public:
+
+ enum BlurFXTypes
+ {
+ ZoomBlur=0,
+ RadialBlur,
+ FarBlur,
+ MotionBlur,
+ SoftenerBlur,
+ ShakeBlur,
+ FocusBlur,
+ SmartBlur,
+ FrostGlass,
+ Mosaic
+ };
+
+private: // BlurFX filter data.
+
+ int m_blurFXType;
+ int m_distance;
+ int m_level;
+
+private: // BlurFX filter methods.
+
+ virtual void filterImage(void);
+
+ // Backported from ImageProcessing version 1
+ void softenerBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage);
+ void shakeBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance);
+ void frostGlass(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Frost);
+
+ // Backported from ImageProcessing version 2
+ void zoomBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ int X, int Y, int Distance, TQRect pArea=TQRect());
+ void radialBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ int X, int Y, int Distance, TQRect pArea=TQRect());
+ void focusBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ int X, int Y, int BlurRadius, int BlendRadius,
+ bool bInversed=false, TQRect pArea=TQRect());
+ void farBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance);
+ void motionBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance, double Angle=0.0);
+ void smartBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Radius, int Strenght);
+ void mosaic(Digikam::DImg *orgImage, Digikam::DImg *destImage, int SizeW, int SizeH);
+
+private: // Internal filter methods.
+
+ void MakeConvolution(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Radius, int Kernel[]);
+
+ Digikam::DColor RandomColor(uchar *Bits, int Width, int Height, bool sixteenBit, int bytesDepth,
+ int X, int Y, int Radius,
+ int alpha, uint *randomSeed, int range, uchar *IntensityCount,
+ uint *AverageColorR, uint *AverageColorG, uint *AverageColorB);
+
+ // Return the limit defined the max and min values.
+ inline int Lim_Max(int Now, int Up, int Max)
+ {
+ --Max;
+ while (Now > Max - Up) --Up;
+ return (Up);
+ };
+
+ // Return the luminance (Y) component of YIQ color model.
+ inline int GetIntensity (int R, int G, int B)
+ {
+ return (int)(R * 0.3 + G * 0.59 + B * 0.11);
+ };
+
+ // function to allocate a 2d array
+ inline int** Alloc2DArray (int Columns, int Rows)
+ {
+ // First, we declare our future 2d array to be returned
+ int** lpcArray = NULL;
+
+ // Now, we alloc the main pointer with Columns
+ lpcArray = new int*[Columns];
+
+ for (int i = 0; i < Columns; i++)
+ lpcArray[i] = new int[Rows];
+
+ return (lpcArray);
+ }
+
+ // Function to deallocates the 2d array previously created
+ inline void Free2DArray (int** lpcArray, int Columns)
+ {
+ // loop to dealocate the columns
+ for (int i = 0; i < Columns; i++)
+ delete [] lpcArray[i];
+
+ // now, we delete the main pointer
+ delete [] lpcArray;
+ }
+
+ inline bool IsInside (int Width, int Height, int X, int Y)
+ {
+ bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true);
+ bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true);
+ return (bIsWOk && bIsHOk);
+ };
+
+ inline uchar LimitValues8(int ColorValue)
+ {
+ if (ColorValue > 255) ColorValue = 255;
+ if (ColorValue < 0) ColorValue = 0;
+ return ((uchar) ColorValue);
+ };
+
+
+ inline int LimitValues16(int ColorValue)
+ {
+ if (ColorValue > 65535) ColorValue = 65535;
+ if (ColorValue < 0) ColorValue = 0;
+ return ColorValue;
+ };
+
+ inline int GetOffset(int Width, int X, int Y, int bytesDepth)
+ {
+ return (Y * Width * bytesDepth) + (X * bytesDepth);
+ };
+
+ inline int GetOffsetAdjusted(int Width, int Height, int X, int Y, int bytesDepth)
+ {
+ X = (X < 0) ? 0 : ((X >= Width ) ? (Width - 1) : X);
+ Y = (Y < 0) ? 0 : ((Y >= Height) ? (Height - 1) : Y);
+ return GetOffset(Width, X, Y, bytesDepth);
+ };
+
+ inline bool IsColorInsideTheRange (int cR, int cG, int cB,
+ int nR, int nG, int nB,
+ int Range)
+ {
+ if ((nR >= cR - Range) && (nR <= cR + Range))
+ if ((nG >= cG - Range) && (nG <= cG + Range))
+ if ((nB >= cB - Range) && (nB <= cB + Range))
+ return (true);
+
+ return (false);
+ };
+
+};
+
+} // NameSpace DigikamBlurFXImagesPlugin
+
+#endif /* BLURFX_H */
diff --git a/src/imageplugins/blurfx/blurfxtool.cpp b/src/imageplugins/blurfx/blurfxtool.cpp
new file mode 100644
index 00000000..2998dbde
--- /dev/null
+++ b/src/imageplugins/blurfx/blurfxtool.cpp
@@ -0,0 +1,402 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-09
+ * Description : a plugin to apply Blur FX to images
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqslider.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "blurfx.h"
+#include "blurfxtool.h"
+#include "blurfxtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamBlurFXImagesPlugin
+{
+
+BlurFXTool::BlurFXTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("blurfx");
+ setToolName(i18n("Blur FX"));
+ setToolIcon(SmallIcon("blurfx"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 6, 1);
+
+ m_effectTypeLabel = new TQLabel(i18n("Type:"), m_gboxSettings->plainPage());
+
+ m_effectType = new RComboBox(m_gboxSettings->plainPage());
+ m_effectType->insertItem(i18n("Zoom Blur"));
+ m_effectType->insertItem(i18n("Radial Blur"));
+ m_effectType->insertItem(i18n("Far Blur"));
+ m_effectType->insertItem(i18n("Motion Blur"));
+ m_effectType->insertItem(i18n("Softener Blur"));
+ m_effectType->insertItem(i18n("Skake Blur"));
+ m_effectType->insertItem(i18n("Focus Blur"));
+ m_effectType->insertItem(i18n("Smart Blur"));
+ m_effectType->insertItem(i18n("Frost Glass"));
+ m_effectType->insertItem(i18n("Mosaic"));
+ m_effectType->setDefaultItem(BlurFX::ZoomBlur);
+ TQWhatsThis::add( m_effectType, i18n("<p>Select the blurring effect to apply to the image.<p>"
+ "<b>Zoom Blur</b>: blurs the image along radial lines starting from "
+ "a specified center point. This simulates the blur of a zooming camera.<p>"
+ "<b>Radial Blur</b>: blurs the image by rotating the pixels around "
+ "the specified center point. This simulates the blur of a rotating camera.<p>"
+ "<b>Far Blur</b>: blurs the image by using far pixels. This simulates the blur "
+ "of an unfocalized camera lens.<p>"
+ "<b>Motion Blur</b>: blurs the image by moving the pixels horizontally. "
+ "This simulates the blur of a linear moving camera.<p>"
+ "<b>Softener Blur</b>: blurs the image softly in dark tones and hardly in light "
+ "tones. This gives images a dreamy and glossy soft focus effect. It's ideal "
+ "for creating romantic portraits, glamour photographs, or giving images a warm "
+ "and subtle glow.<p>"
+ "<b>Skake Blur</b>: blurs the image by skaking randomly the pixels. "
+ "This simulates the blur of a random moving camera.<p>"
+ "<b>Focus Blur</b>: blurs the image corners to reproduce the astigmatism distortion "
+ "of a lens.<p>"
+ "<b>Smart Blur</b>: finds the edges of color in your image and blurs them without "
+ "muddying the rest of the image.<p>"
+ "<b>Frost Glass</b>: blurs the image by randomly disperse light coming through "
+ "a frosted glass.<p>"
+ "<b>Mosaic</b>: divides the photograph into rectangular cells and then "
+ "recreates it by filling those cells with average pixel value."));
+
+ m_distanceLabel = new TQLabel(i18n("Distance:"), m_gboxSettings->plainPage());
+ m_distanceInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_distanceInput->setRange(0, 100, 1);
+ m_distanceInput->setDefaultValue(3);
+ TQWhatsThis::add( m_distanceInput, i18n("<p>Set here the blur distance in pixels."));
+
+ m_levelLabel = new TQLabel(i18n("Level:"), m_gboxSettings->plainPage());
+ m_levelInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_levelInput->setRange(0, 360, 1);
+ m_levelInput->setDefaultValue(128);
+ TQWhatsThis::add( m_levelInput, i18n("<p>This value controls the level to use with the current effect."));
+
+ grid->addMultiCellWidget(m_effectTypeLabel, 0, 0, 0, 1);
+ grid->addMultiCellWidget(m_effectType, 1, 1, 0, 1);
+ grid->addMultiCellWidget(m_distanceLabel, 2, 2, 0, 1);
+ grid->addMultiCellWidget(m_distanceInput, 3, 3, 0, 1);
+ grid->addMultiCellWidget(m_levelLabel, 4, 4, 0, 1);
+ grid->addMultiCellWidget(m_levelInput, 5, 5, 0, 1);
+ grid->setRowStretch(6, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "blurfx Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_effectType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffectTypeChanged(int)));
+
+ connect(m_distanceInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_levelInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+BlurFXTool::~BlurFXTool()
+{
+}
+
+void BlurFXTool::renderingFinished(void)
+{
+
+ m_effectTypeLabel->setEnabled(true);
+ m_effectType->setEnabled(true);
+ m_distanceInput->setEnabled(true);
+ m_distanceLabel->setEnabled(true);
+
+ switch (m_effectType->currentItem())
+ {
+ case BlurFX::ZoomBlur:
+ case BlurFX::RadialBlur:
+ case BlurFX::FarBlur:
+ case BlurFX::ShakeBlur:
+ case BlurFX::FrostGlass:
+ case BlurFX::Mosaic:
+ break;
+
+ case BlurFX::MotionBlur:
+ case BlurFX::FocusBlur:
+ case BlurFX::SmartBlur:
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ break;
+
+ case BlurFX::SoftenerBlur:
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ break;
+ }
+}
+
+void BlurFXTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("blurfx Tool");
+ m_effectType->blockSignals(true);
+ m_distanceInput->blockSignals(true);
+ m_levelInput->blockSignals(true);
+
+ m_effectType->setCurrentItem(config->readNumEntry("EffectType", m_effectType->defaultItem()));
+ m_distanceInput->setValue(config->readNumEntry("DistanceAjustment", m_distanceInput->defaultValue()));
+ m_levelInput->setValue(config->readNumEntry("LevelAjustment", m_levelInput->defaultValue()));
+
+ m_effectType->blockSignals(false);
+ m_distanceInput->blockSignals(false);
+ m_levelInput->blockSignals(false);
+}
+
+void BlurFXTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("blurfx Tool");
+ config->writeEntry("EffectType", m_effectType->currentItem());
+ config->writeEntry("DistanceAjustment", m_distanceInput->value());
+ config->writeEntry("LevelAjustment", m_levelInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void BlurFXTool::slotResetSettings()
+{
+ m_effectType->blockSignals(true);
+ m_distanceInput->blockSignals(true);
+ m_levelInput->blockSignals(true);
+
+ m_effectType->slotReset();
+ m_distanceInput->slotReset();
+ m_levelInput->slotReset();
+
+ m_effectType->blockSignals(false);
+ m_distanceInput->blockSignals(false);
+ m_levelInput->blockSignals(false);
+
+ slotEffectTypeChanged(m_effectType->defaultItem());
+}
+
+void BlurFXTool::slotEffectTypeChanged(int type)
+{
+ m_distanceInput->setEnabled(true);
+ m_distanceLabel->setEnabled(true);
+
+ m_distanceInput->blockSignals(true);
+ m_levelInput->blockSignals(true);
+
+ m_distanceInput->setRange(0, 200, 1);
+ m_distanceInput->setValue(100);
+ m_levelInput->setRange(0, 360, 1);
+ m_levelInput->setValue(45);
+
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+
+ switch (type)
+ {
+ case BlurFX::ZoomBlur:
+ break;
+
+ case BlurFX::RadialBlur:
+ case BlurFX::FrostGlass:
+ m_distanceInput->setRange(0, 10, 1);
+ m_distanceInput->setValue(3);
+ break;
+
+ case BlurFX::FarBlur:
+ m_distanceInput->setRange(0, 20, 1);
+ m_distanceInput->input()->setMaxValue(20);
+ m_distanceInput->setValue(10);
+ break;
+
+ case BlurFX::MotionBlur:
+ case BlurFX::FocusBlur:
+ m_distanceInput->setRange(0, 100, 1);
+ m_distanceInput->setValue(20);
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ break;
+
+ case BlurFX::SoftenerBlur:
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ break;
+
+ case BlurFX::ShakeBlur:
+ m_distanceInput->setRange(0, 100, 1);
+ m_distanceInput->setValue(20);
+ break;
+
+ case BlurFX::SmartBlur:
+ m_distanceInput->setRange(0, 20, 1);
+ m_distanceInput->setValue(3);
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ m_levelInput->setRange(0, 255, 1);
+ m_levelInput->setValue(128);
+ break;
+
+ case BlurFX::Mosaic:
+ m_distanceInput->setRange(0, 50, 1);
+ m_distanceInput->setValue(3);
+ break;
+ }
+
+ m_distanceInput->blockSignals(false);
+ m_levelInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void BlurFXTool::prepareEffect()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+
+ DImg image;
+
+ switch (m_effectType->currentItem())
+ {
+ case BlurFX::ZoomBlur:
+ case BlurFX::RadialBlur:
+ case BlurFX::FocusBlur:
+ {
+ ImageIface iface(0, 0);
+ image = *iface.getOriginalImg();
+ break;
+ }
+
+ case BlurFX::FarBlur:
+ case BlurFX::MotionBlur:
+ case BlurFX::SoftenerBlur:
+ case BlurFX::ShakeBlur:
+ case BlurFX::SmartBlur:
+ case BlurFX::FrostGlass:
+ case BlurFX::Mosaic:
+ image = m_previewWidget->getOriginalRegionImage();
+ break;
+ }
+
+ int t = m_effectType->currentItem();
+ int d = m_distanceInput->value();
+ int l = m_levelInput->value();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new BlurFX(&image, this, t, d, l)));
+}
+
+void BlurFXTool::prepareFinal()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+
+ int t = m_effectType->currentItem();
+ int d = m_distanceInput->value();
+ int l = m_levelInput->value();
+
+ ImageIface iface(0, 0);
+ setFilter(dynamic_cast<DImgThreadedFilter *>(new BlurFX(iface.getOriginalImg(), this, t, d, l)));
+}
+
+void BlurFXTool::putPreviewData()
+{
+ switch (m_effectType->currentItem())
+ {
+ case BlurFX::ZoomBlur:
+ case BlurFX::RadialBlur:
+ case BlurFX::FocusBlur:
+ {
+ TQRect pRect = m_previewWidget->getOriginalImageRegionToRender();
+ DImg destImg = filter()->getTargetImage().copy(pRect);
+ m_previewWidget->setPreviewImage(destImg);
+ break;
+ }
+ case BlurFX::FarBlur:
+ case BlurFX::MotionBlur:
+ case BlurFX::SoftenerBlur:
+ case BlurFX::ShakeBlur:
+ case BlurFX::SmartBlur:
+ case BlurFX::FrostGlass:
+ case BlurFX::Mosaic:
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+ break;
+ }
+}
+
+void BlurFXTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Blur Effects"), filter()->getTargetImage().bits());
+}
+
+} // NameSpace DigikamBlurFXImagesPlugin
diff --git a/src/imageplugins/blurfx/blurfxtool.h b/src/imageplugins/blurfx/blurfxtool.h
new file mode 100644
index 00000000..9726e65d
--- /dev/null
+++ b/src/imageplugins/blurfx/blurfxtool.h
@@ -0,0 +1,93 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-09
+ * Description : a plugin to apply Blur FX to images
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef BLURFXTOOL_H
+#define BLURFXTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQLabel;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamBlurFXImagesPlugin
+{
+
+class BlurFXTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ BlurFXTool(TQObject *parent);
+ ~BlurFXTool();
+
+private slots:
+
+ void slotEffectTypeChanged(int type);
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_effectTypeLabel;
+ TQLabel *m_distanceLabel;
+ TQLabel *m_levelLabel;
+
+ KDcrawIface::RComboBox *m_effectType;
+
+ KDcrawIface::RIntNumInput *m_distanceInput;
+ KDcrawIface::RIntNumInput *m_levelInput;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamBlurFXImagesPlugin
+
+#endif /* BLURFXTOOL_H */
diff --git a/src/imageplugins/blurfx/digikamimageplugin_blurfx.desktop b/src/imageplugins/blurfx/digikamimageplugin_blurfx.desktop
new file mode 100644
index 00000000..e7968a80
--- /dev/null
+++ b/src/imageplugins/blurfx/digikamimageplugin_blurfx.desktop
@@ -0,0 +1,49 @@
+[Desktop Entry]
+Name=ImagePlugin_BlurFX
+Name[bg]=Приставка за снимки - Ефекти за замъгляване
+Name[el]=ΠρόσθετοΕικόνας_ΕφέΘολώματος
+Name[fi]=Sumennus
+Name[hr]=Zamućenje
+Name[it]=PluginImmagini_EffettiDiSfocatura
+Name[nl]=Afbeeldingsplugin_Vervaageffect
+Name[sr]=Ефекти замућења
+Name[sr@Latn]=Efekti zamućenja
+Name[sv]=Insticksprogram för oskärpeeffekt
+Name[tr]=ResimEklentisi_Bulanıklaştır
+Name[xx]=xxImagePlugin_BlurFXxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Blur special effects plugin for digiKam
+Comment[bg]=Приставка на digiKam с ефекти за замъгляване на снимки
+Comment[ca]=Connector pel digiKam d'efectes especials de difuminat
+Comment[da]=Digikam plugin med specialeffekter for udviskning
+Comment[de]=digiKam-Modul zum Erzeugen von speziellen Unschärfe-Effekten
+Comment[el]=Πρόσθετο ειδικών εφέ θολώματος για το digiKam
+Comment[es]=Plugin para digiKam con efectos especiales de difusión
+Comment[et]=DigiKami spetsiaalsete hägustamisefektide plugin
+Comment[fa]=وصلۀ جلوه‌های ویژۀ محو برای digiKam
+Comment[fi]=Sumennustehosteita
+Comment[gl]=Un plugin de digiKam para efeitos especiais de borrón
+Comment[hr]=digiKam dodatak za efekt zamućenja
+Comment[is]=Íforrit fyrir digiKam sem mýkir eða afskerpir myndir
+Comment[it]=Plugin degli effetti speciali di sfocatura per digiKam
+Comment[ja]=digiKam ぼかし特殊効果プラグイン
+Comment[nds]=digiKam-Moduul för't Opstellen vun Weekteek-Effekten
+Comment[nl]=Digikam-plugin voor vervaageffect
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਬਲੱਰ (ਧੁੰਧਲਾਪਨ) ਖਾਸ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka specjalnych efektów rozmycia do programu digiKam
+Comment[pt]=Um 'plugin' do digiKam para efeitos especiais de borrão
+Comment[pt_BR]=Plugin de efeitos especiais de desfocalização
+Comment[ru]=Модуль специальных эффектов размытия для digiKam
+Comment[sk]=digiKam plugin pre špeciálne efekty rozmazania
+Comment[sr]=Прикључак ефеката замућења за digiKam
+Comment[sr@Latn]=Priključak efekata zamućenja za digiKam
+Comment[sv]=Digikam insticksprogram med specialeffekter för oskärpa
+Comment[tr]=digiKam için bulanıklaştırma eklentisi
+Comment[uk]=Втулок спеціальних ефектів розмивання для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng che mờ cho digiKam
+Comment[xx]=xxBlur special effects plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_blurfx
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/blurfx/digikamimageplugin_blurfx_ui.rc b/src/imageplugins/blurfx/digikamimageplugin_blurfx_ui.rc
new file mode 100644
index 00000000..085b4c68
--- /dev/null
+++ b/src/imageplugins/blurfx/digikamimageplugin_blurfx_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_blurfx" >
+
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_blurfx" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_blurfx" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/blurfx/imageeffect_blurfx.cpp b/src/imageplugins/blurfx/imageeffect_blurfx.cpp
new file mode 100644
index 00000000..62bcd525
--- /dev/null
+++ b/src/imageplugins/blurfx/imageeffect_blurfx.cpp
@@ -0,0 +1,388 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-09
+ * Description : a plugin to apply Blur FX to images
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqslider.h>
+#include <tqimage.h>
+#include <tqcombobox.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <knuminput.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "blurfx.h"
+#include "imageeffect_blurfx.h"
+#include "imageeffect_blurfx.moc"
+
+namespace DigikamBlurFXImagesPlugin
+{
+
+ImageEffect_BlurFX::ImageEffect_BlurFX(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Apply Blurring Special Effect to Photograph"),
+ "blurfx", false, false, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Blur Effects"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to apply blurring special effect "
+ "to an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pieter Z. Voloshyn", I18N_NOOP("Blurring algorithms"),
+ "pieter dot voloshyn at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 5, 1, 0, spacingHint());
+
+ m_effectTypeLabel = new TQLabel(i18n("Type:"), gboxSettings);
+
+ m_effectType = new TQComboBox( false, gboxSettings );
+ m_effectType->insertItem( i18n("Zoom Blur") );
+ m_effectType->insertItem( i18n("Radial Blur") );
+ m_effectType->insertItem( i18n("Far Blur") );
+ m_effectType->insertItem( i18n("Motion Blur") );
+ m_effectType->insertItem( i18n("Softener Blur") );
+ m_effectType->insertItem( i18n("Skake Blur") );
+ m_effectType->insertItem( i18n("Focus Blur") );
+ m_effectType->insertItem( i18n("Smart Blur") );
+ m_effectType->insertItem( i18n("Frost Glass") );
+ m_effectType->insertItem( i18n("Mosaic") );
+ TQWhatsThis::add( m_effectType, i18n("<p>Select the blurring effect to apply to the image.<p>"
+ "<b>Zoom Blur</b>: blurs the image along radial lines starting from "
+ "a specified center point. This simulates the blur of a zooming camera.<p>"
+ "<b>Radial Blur</b>: blurs the image by rotating the pixels around "
+ "the specified center point. This simulates the blur of a rotating camera.<p>"
+ "<b>Far Blur</b>: blurs the image by using far pixels. This simulates the blur "
+ "of an unfocalized camera lens.<p>"
+ "<b>Motion Blur</b>: blurs the image by moving the pixels horizontally. "
+ "This simulates the blur of a linear moving camera.<p>"
+ "<b>Softener Blur</b>: blurs the image softly in dark tones and hardly in light "
+ "tones. This gives images a dreamy and glossy soft focus effect. It's ideal "
+ "for creating romantic portraits, glamour photographs, or giving images a warm "
+ "and subtle glow.<p>"
+ "<b>Skake Blur</b>: blurs the image by skaking randomly the pixels. "
+ "This simulates the blur of a random moving camera.<p>"
+ "<b>Focus Blur</b>: blurs the image corners to reproduce the astigmatism distortion "
+ "of a lens.<p>"
+ "<b>Smart Blur</b>: finds the edges of color in your image and blurs them without "
+ "muddying the rest of the image.<p>"
+ "<b>Frost Glass</b>: blurs the image by randomly disperse light coming through "
+ "a frosted glass.<p>"
+ "<b>Mosaic</b>: divides the photograph into rectangular cells and then "
+ "recreates it by filling those cells with average pixel value."));
+ gridSettings->addMultiCellWidget(m_effectTypeLabel, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_effectType, 1, 1, 0, 1);
+
+ m_distanceLabel = new TQLabel(i18n("Distance:"), gboxSettings);
+ m_distanceInput = new KIntNumInput(gboxSettings);
+ m_distanceInput->setRange(0, 100, 1, true);
+ TQWhatsThis::add( m_distanceInput, i18n("<p>Set here the blur distance in pixels."));
+
+ gridSettings->addMultiCellWidget(m_distanceLabel, 2, 2, 0, 1);
+ gridSettings->addMultiCellWidget(m_distanceInput, 3, 3, 0, 1);
+
+ m_levelLabel = new TQLabel(i18n("Level:"), gboxSettings);
+ m_levelInput = new KIntNumInput(gboxSettings);
+ m_levelInput->setRange(0, 360, 1, true);
+ TQWhatsThis::add( m_levelInput, i18n("<p>This value controls the level to use with the current effect."));
+
+ gridSettings->addMultiCellWidget(m_levelLabel, 4, 4, 0, 1);
+ gridSettings->addMultiCellWidget(m_levelInput, 5, 5, 0, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_effectType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffectTypeChanged(int)));
+
+ connect(m_distanceInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_levelInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_BlurFX::~ImageEffect_BlurFX()
+{
+}
+
+void ImageEffect_BlurFX::renderingFinished(void)
+{
+
+ m_effectTypeLabel->setEnabled(true);
+ m_effectType->setEnabled(true);
+ m_distanceInput->setEnabled(true);
+ m_distanceLabel->setEnabled(true);
+
+ switch (m_effectType->currentItem())
+ {
+ case BlurFX::ZoomBlur:
+ case BlurFX::RadialBlur:
+ case BlurFX::FarBlur:
+ case BlurFX::ShakeBlur:
+ case BlurFX::FrostGlass:
+ case BlurFX::Mosaic:
+ break;
+
+ case BlurFX::MotionBlur:
+ case BlurFX::FocusBlur:
+ case BlurFX::SmartBlur:
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ break;
+
+ case BlurFX::SoftenerBlur:
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ break;
+ }
+}
+
+void ImageEffect_BlurFX::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("blurfx Tool Dialog");
+ m_effectType->blockSignals(true);
+ m_distanceInput->blockSignals(true);
+ m_levelInput->blockSignals(true);
+ m_effectType->setCurrentItem(config->readNumEntry("EffectType", BlurFX::ZoomBlur));
+ m_distanceInput->setValue(config->readNumEntry("DistanceAjustment", 3));
+ m_levelInput->setValue(config->readNumEntry("LevelAjustment", 128));
+ m_effectType->blockSignals(false);
+ m_distanceInput->blockSignals(false);
+ m_levelInput->blockSignals(false);
+}
+
+void ImageEffect_BlurFX::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("blurfx Tool Dialog");
+ config->writeEntry("EffectType", m_effectType->currentItem());
+ config->writeEntry("DistanceAjustment", m_distanceInput->value());
+ config->writeEntry("LevelAjustment", m_levelInput->value());
+ config->sync();
+}
+
+void ImageEffect_BlurFX::resetValues()
+{
+ m_effectType->setCurrentItem(BlurFX::ZoomBlur);
+ slotEffectTypeChanged(BlurFX::ZoomBlur);
+}
+
+void ImageEffect_BlurFX::slotEffectTypeChanged(int type)
+{
+ m_distanceInput->setEnabled(true);
+ m_distanceLabel->setEnabled(true);
+
+ m_distanceInput->blockSignals(true);
+ m_levelInput->blockSignals(true);
+ m_distanceInput->setRange(0, 200, 1, true);
+ m_distanceInput->setValue(100);
+ m_levelInput->setRange(0, 360, 1, true);
+ m_levelInput->setValue(45);
+
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+
+ switch (type)
+ {
+ case BlurFX::ZoomBlur:
+ break;
+
+ case BlurFX::RadialBlur:
+ case BlurFX::FrostGlass:
+ m_distanceInput->setRange(0, 10, 1, true);
+ m_distanceInput->setValue(3);
+ break;
+
+ case BlurFX::FarBlur:
+ m_distanceInput->setRange(0, 20, 1, true);
+ m_distanceInput->setMaxValue(20);
+ m_distanceInput->setValue(10);
+ break;
+
+ case BlurFX::MotionBlur:
+ case BlurFX::FocusBlur:
+ m_distanceInput->setRange(0, 100, 1, true);
+ m_distanceInput->setValue(20);
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ break;
+
+ case BlurFX::SoftenerBlur:
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ break;
+
+ case BlurFX::ShakeBlur:
+ m_distanceInput->setRange(0, 100, 1, true);
+ m_distanceInput->setValue(20);
+ break;
+
+ case BlurFX::SmartBlur:
+ m_distanceInput->setRange(0, 20, 1, true);
+ m_distanceInput->setValue(3);
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ m_levelInput->setRange(0, 255, 1, true);
+ m_levelInput->setValue(128);
+ break;
+
+ case BlurFX::Mosaic:
+ m_distanceInput->setRange(0, 50, 1, true);
+ m_distanceInput->setValue(3);
+ break;
+ }
+
+ m_distanceInput->blockSignals(false);
+ m_levelInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_BlurFX::prepareEffect()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+
+ Digikam::DImg image;
+
+ switch (m_effectType->currentItem())
+ {
+ case BlurFX::ZoomBlur:
+ case BlurFX::RadialBlur:
+ case BlurFX::FocusBlur:
+ {
+ Digikam::ImageIface iface(0, 0);
+ image = *iface.getOriginalImg();
+ break;
+ }
+
+ case BlurFX::FarBlur:
+ case BlurFX::MotionBlur:
+ case BlurFX::SoftenerBlur:
+ case BlurFX::ShakeBlur:
+ case BlurFX::SmartBlur:
+ case BlurFX::FrostGlass:
+ case BlurFX::Mosaic:
+ image = m_imagePreviewWidget->getOriginalRegionImage();
+ break;
+ }
+
+ int t = m_effectType->currentItem();
+ int d = m_distanceInput->value();
+ int l = m_levelInput->value();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new BlurFX(&image, this, t, d, l));
+}
+
+void ImageEffect_BlurFX::prepareFinal()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_distanceInput->setEnabled(false);
+ m_distanceLabel->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+
+ int t = m_effectType->currentItem();
+ int d = m_distanceInput->value();
+ int l = m_levelInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new BlurFX(iface.getOriginalImg(), this, t, d, l));
+}
+
+void ImageEffect_BlurFX::putPreviewData(void)
+{
+ switch (m_effectType->currentItem())
+ {
+ case BlurFX::ZoomBlur:
+ case BlurFX::RadialBlur:
+ case BlurFX::FocusBlur:
+ {
+ TQRect pRect = m_imagePreviewWidget->getOriginalImageRegionToRender();
+ Digikam::DImg destImg = m_threadedFilter->getTargetImage().copy(pRect);
+ m_imagePreviewWidget->setPreviewImage(destImg);
+ break;
+ }
+ case BlurFX::FarBlur:
+ case BlurFX::MotionBlur:
+ case BlurFX::SoftenerBlur:
+ case BlurFX::ShakeBlur:
+ case BlurFX::SmartBlur:
+ case BlurFX::FrostGlass:
+ case BlurFX::Mosaic:
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+ break;
+ }
+}
+
+void ImageEffect_BlurFX::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Blur Effects"),
+ m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamBlurFXImagesPlugin
+
diff --git a/src/imageplugins/blurfx/imageeffect_blurfx.h b/src/imageplugins/blurfx/imageeffect_blurfx.h
new file mode 100644
index 00000000..e2517d4f
--- /dev/null
+++ b/src/imageplugins/blurfx/imageeffect_blurfx.h
@@ -0,0 +1,80 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-09
+ * Description : a plugin to apply Blur FX to images
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_BLURFX_H
+#define IMAGEEFFECT_BLURFX_H
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+class TQComboBox;
+class TQLabel;
+
+class KIntNumInput;
+
+namespace DigikamBlurFXImagesPlugin
+{
+
+class ImageEffect_BlurFX : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_BlurFX(TQWidget *parent);
+ ~ImageEffect_BlurFX();
+
+private slots:
+
+ void slotEffectTypeChanged(int type);
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQComboBox *m_effectType;
+
+ TQLabel *m_effectTypeLabel;
+ TQLabel *m_distanceLabel;
+ TQLabel *m_levelLabel;
+
+ KIntNumInput *m_distanceInput;
+ KIntNumInput *m_levelInput;
+};
+
+} // NameSpace DigikamBlurFXImagesPlugin
+
+#endif /* IMAGEEFFECT_BLURFX_H */
diff --git a/src/imageplugins/blurfx/imageplugin_blurfx.cpp b/src/imageplugins/blurfx/imageplugin_blurfx.cpp
new file mode 100644
index 00000000..3bbd05e0
--- /dev/null
+++ b/src/imageplugins/blurfx/imageplugin_blurfx.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-09
+ * Description : a plugin to apply Blur FX to images
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "blurfxtool.h"
+#include "imageplugin_blurfx.h"
+#include "imageplugin_blurfx.moc"
+
+using namespace DigikamBlurFXImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_blurfx,
+ KGenericFactory<ImagePlugin_BlurFX>("digikamimageplugin_blurfx"));
+
+ImagePlugin_BlurFX::ImagePlugin_BlurFX(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_BlurFX")
+{
+ m_blurfxAction = new TDEAction(i18n("Blur Effects..."), "blurfx", 0,
+ this, TQ_SLOT(slotBlurFX()),
+ actionCollection(), "imageplugin_blurfx");
+
+ setXMLFile( "digikamimageplugin_blurfx_ui.rc" );
+
+ DDebug() << "ImagePlugin_BlurFX plugin loaded" << endl;
+}
+
+ImagePlugin_BlurFX::~ImagePlugin_BlurFX()
+{
+}
+
+void ImagePlugin_BlurFX::setEnabledActions(bool enable)
+{
+ m_blurfxAction->setEnabled(enable);
+}
+
+void ImagePlugin_BlurFX::slotBlurFX()
+{
+ BlurFXTool *tool = new BlurFXTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/blurfx/imageplugin_blurfx.h b/src/imageplugins/blurfx/imageplugin_blurfx.h
new file mode 100644
index 00000000..13df6534
--- /dev/null
+++ b/src/imageplugins/blurfx/imageplugin_blurfx.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-09
+ * Description : a plugin to apply Blur FX to images
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_BLURFX_H
+#define IMAGEPLUGIN_BLURFX_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_BlurFX : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_BlurFX(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_BlurFX();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotBlurFX();
+
+private:
+
+ TDEAction *m_blurfxAction;
+};
+
+#endif /* IMAGEPLUGIN_BLURFX_H */
diff --git a/src/imageplugins/border/Makefile.am b/src/imageplugins/border/Makefile.am
new file mode 100644
index 00000000..385acfab
--- /dev/null
+++ b/src/imageplugins/border/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+SUBDIRS = patterns
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_border_la_SOURCES = imageplugin_border.cpp \
+ bordertool.cpp border.cpp
+
+digikamimageplugin_border_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_border_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_border.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_border.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_border_ui.rc
diff --git a/src/imageplugins/border/border.cpp b/src/imageplugins/border/border.cpp
new file mode 100644
index 00000000..00726f58
--- /dev/null
+++ b/src/imageplugins/border/border.cpp
@@ -0,0 +1,393 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : border threaded image filter.
+ *
+ * Copyright 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqpoint.h>
+#include <tqregion.h>
+#include <tqpointarray.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "ddebug.h"
+#include "border.h"
+
+namespace DigikamBorderImagesPlugin
+{
+
+Border::Border(Digikam::DImg *image, TQObject *parent, int orgWidth, int orgHeight,
+ TQString borderPath, int borderType, float borderPercent,
+ Digikam::DColor solidColor,
+ Digikam::DColor niepceBorderColor,
+ Digikam::DColor niepceLineColor,
+ Digikam::DColor bevelUpperLeftColor,
+ Digikam::DColor bevelLowerRightColor,
+ Digikam::DColor decorativeFirstColor,
+ Digikam::DColor decorativeSecondColor)
+ : Digikam::DImgThreadedFilter(image, parent, "Border")
+{
+ m_orgWidth = orgWidth;
+ m_orgHeight = orgHeight;
+ m_orgRatio = (float)m_orgWidth / (float)m_orgHeight;
+ m_borderType = borderType;
+ m_borderPath = borderPath;
+ int size = (image->width() > image->height()) ? image->height() : image->width();
+ m_borderMainWidth = (int)(size * borderPercent);
+ m_border2ndWidth = (int)(size * 0.005);
+
+ // Clamp internal border with to 1 pixel to be visible with small image.
+ if (m_border2ndWidth < 1) m_border2ndWidth = 1;
+
+ m_solidColor = solidColor;
+ m_niepceBorderColor = niepceBorderColor;
+ m_niepceLineColor = niepceLineColor;
+ m_bevelUpperLeftColor = bevelUpperLeftColor;
+ m_bevelLowerRightColor = bevelLowerRightColor;
+ m_decorativeFirstColor = decorativeFirstColor;
+ m_decorativeSecondColor = decorativeSecondColor;
+
+ m_preserveAspectRatio = true;
+
+ initFilter();
+}
+
+Border::Border(Digikam::DImg *orgImage, TQObject *parent, int orgWidth, int orgHeight,
+ TQString borderPath, int borderType,
+ int borderWidth1, int borderWidth2, int borderWidth3, int borderWidth4,
+ Digikam::DColor solidColor,
+ Digikam::DColor niepceBorderColor,
+ Digikam::DColor niepceLineColor,
+ Digikam::DColor bevelUpperLeftColor,
+ Digikam::DColor bevelLowerRightColor,
+ Digikam::DColor decorativeFirstColor,
+ Digikam::DColor decorativeSecondColor)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "Border")
+{
+ m_orgWidth = orgWidth;
+ m_orgHeight = orgHeight;
+
+ m_borderType = borderType;
+ m_borderWidth1 = borderWidth1;
+ m_borderWidth2 = borderWidth2;
+ m_borderWidth3 = borderWidth3;
+ m_borderWidth4 = borderWidth4;
+
+ m_solidColor = solidColor;
+ m_niepceBorderColor = niepceBorderColor;
+ m_niepceLineColor = niepceLineColor;
+ m_bevelUpperLeftColor = bevelUpperLeftColor;
+ m_bevelLowerRightColor = bevelLowerRightColor;
+ m_decorativeFirstColor = decorativeFirstColor;
+ m_decorativeSecondColor = decorativeSecondColor;
+
+ m_borderPath = borderPath;
+
+ m_preserveAspectRatio = false;
+
+ initFilter();
+}
+
+void Border::filterImage(void)
+{
+ switch (m_borderType)
+ {
+ case SolidBorder:
+ if (m_preserveAspectRatio)
+ solid(m_orgImage, m_destImage, m_solidColor, m_borderMainWidth);
+ else
+ solid2(m_orgImage, m_destImage, m_solidColor, m_borderWidth1);
+ break;
+
+ case NiepceBorder:
+ if (m_preserveAspectRatio)
+ niepce(m_orgImage, m_destImage, m_niepceBorderColor, m_borderMainWidth,
+ m_niepceLineColor, m_border2ndWidth);
+ else
+ niepce2(m_orgImage, m_destImage, m_niepceBorderColor, m_borderWidth1,
+ m_niepceLineColor, m_borderWidth4);
+ break;
+
+ case BeveledBorder:
+ if (m_preserveAspectRatio)
+ bevel(m_orgImage, m_destImage, m_bevelUpperLeftColor,
+ m_bevelLowerRightColor, m_borderMainWidth);
+ else
+ bevel2(m_orgImage, m_destImage, m_bevelUpperLeftColor,
+ m_bevelLowerRightColor, m_borderWidth1);
+ break;
+
+ case PineBorder:
+ case WoodBorder:
+ case PaperBorder:
+ case ParqueBorder:
+ case IceBorder:
+ case LeafBorder:
+ case MarbleBorder:
+ case RainBorder:
+ case CratersBorder:
+ case DriedBorder:
+ case PinkBorder:
+ case StoneBorder:
+ case ChalkBorder:
+ case GraniteBorder:
+ case RockBorder:
+ case WallBorder:
+ if (m_preserveAspectRatio)
+ pattern(m_orgImage, m_destImage, m_borderMainWidth,
+ m_decorativeFirstColor, m_decorativeSecondColor,
+ m_border2ndWidth, m_border2ndWidth);
+ else
+ pattern2(m_orgImage, m_destImage, m_borderWidth1,
+ m_decorativeFirstColor, m_decorativeSecondColor,
+ m_borderWidth2, m_borderWidth2);
+ break;
+ }
+}
+
+// -- Methods to preserve aspect ratio of image ------------------------------------------
+
+void Border::solid(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg, int borderWidth)
+{
+ if (m_orgWidth > m_orgHeight)
+ {
+ int height = src.height() + borderWidth*2;
+ dest = Digikam::DImg((int)(height*m_orgRatio), height, src.sixteenBit(), src.hasAlpha());
+ dest.fill(fg);
+ dest.bitBltImage(&src, (dest.width()-src.width())/2, borderWidth);
+ }
+ else
+ {
+ int width = src.width() + borderWidth*2;
+ dest = Digikam::DImg(width, (int)(width/m_orgRatio), src.sixteenBit(), src.hasAlpha());
+ dest.fill(fg);
+ dest.bitBltImage(&src, borderWidth, (dest.height()-src.height())/2);
+ }
+}
+
+void Border::niepce(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg,
+ int borderWidth, const Digikam::DColor &bg, int lineWidth)
+{
+ Digikam::DImg tmp;
+ solid(src, tmp, bg, lineWidth);
+ solid(tmp, dest, fg, borderWidth);
+}
+
+void Border::bevel(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &topColor,
+ const Digikam::DColor &btmColor, int borderWidth)
+{
+ int width, height;
+
+ if (m_orgWidth > m_orgHeight)
+ {
+ height = src.height() + borderWidth*2;
+ width = (int)(height*m_orgRatio);
+ }
+ else
+ {
+ width = src.width() + borderWidth*2;
+ height = (int)(width/m_orgRatio);
+ }
+
+ dest = Digikam::DImg(width, height, src.sixteenBit(), src.hasAlpha());
+ dest.fill(topColor);
+
+ TQPointArray btTriangle(3);
+ btTriangle.setPoint(0, width, 0);
+ btTriangle.setPoint(1, 0, height);
+ btTriangle.setPoint(2, width, height);
+ TQRegion btRegion(btTriangle);
+
+ for(int x=0 ; x < width ; x++)
+ {
+ for(int y=0 ; y < height ; y++)
+ {
+ if (btRegion.contains(TQPoint(x, y)))
+ dest.setPixelColor(x, y, btmColor);
+ }
+ }
+
+ if (m_orgWidth > m_orgHeight)
+ {
+ dest.bitBltImage(&src, (dest.width()-src.width())/2, borderWidth);
+ }
+ else
+ {
+ dest.bitBltImage(&src, borderWidth, (dest.height()-src.height())/2);
+ }
+}
+
+void Border::pattern(Digikam::DImg &src, Digikam::DImg &dest, int borderWidth,
+ const Digikam::DColor &firstColor, const Digikam::DColor &secondColor,
+ int firstWidth, int secondWidth)
+{
+ // Original image with the first solid border around.
+ Digikam::DImg tmp;
+ solid(src, tmp, firstColor, firstWidth);
+
+ // Border tiled image using pattern with second solid border around.
+ int width, height;
+
+ if (m_orgWidth > m_orgHeight)
+ {
+ height = tmp.height() + borderWidth*2;
+ width = (int)(height*m_orgRatio);
+ }
+ else
+ {
+ width = tmp.width() + borderWidth*2;
+ height = (int)(width/m_orgRatio);
+ }
+
+ Digikam::DImg tmp2(width, height, tmp.sixteenBit(), tmp.hasAlpha());
+ DDebug() << "Border File:" << m_borderPath << endl;
+ Digikam::DImg border(m_borderPath);
+ if ( border.isNull() )
+ return;
+
+ border.convertToDepthOfImage(&tmp2);
+
+ for (int x = 0 ; x < width ; x+=border.width())
+ for (int y = 0 ; y < height ; y+=border.height())
+ tmp2.bitBltImage(&border, x, y);
+
+ solid(tmp2, dest, secondColor, secondWidth);
+
+ // Merge both images to one.
+ if (m_orgWidth > m_orgHeight)
+ {
+ dest.bitBltImage(&tmp, (dest.width()-tmp.width())/2, borderWidth);
+ }
+ else
+ {
+ dest.bitBltImage(&tmp, borderWidth, (dest.height()-tmp.height())/2);
+ }
+}
+
+// -- Methods to not-preserve aspect ratio of image ------------------------------------------
+
+
+void Border::solid2(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg, int borderWidth)
+{
+ dest = Digikam::DImg(src.width() + borderWidth*2, src.height() + borderWidth*2,
+ src.sixteenBit(), src.hasAlpha());
+ dest.fill(fg);
+ dest.bitBltImage(&src, borderWidth, borderWidth);
+}
+
+void Border::niepce2(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg, int borderWidth,
+ const Digikam::DColor &bg, int lineWidth)
+{
+ Digikam::DImg tmp;
+ solid2(src, tmp, bg, lineWidth);
+ solid2(tmp, dest, fg, borderWidth);
+}
+
+void Border::bevel2(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &topColor,
+ const Digikam::DColor &btmColor, int borderWidth)
+{
+ int x, y;
+ int wc;
+
+ dest = Digikam::DImg(src.width() + borderWidth*2,
+ src.height() + borderWidth*2,
+ src.sixteenBit(), src.hasAlpha());
+
+ // top
+
+ for(y=0, wc = (int)dest.width()-1; y < borderWidth; ++y, --wc)
+ {
+ for(x=0; x < wc; ++x)
+ dest.setPixelColor(x, y, topColor);
+
+ for(;x < (int)dest.width(); ++x)
+ dest.setPixelColor(x, y, btmColor);
+ }
+
+ // left and right
+
+ for(; y < (int)dest.height()-borderWidth; ++y)
+ {
+ for(x=0; x < borderWidth; ++x)
+ dest.setPixelColor(x, y, topColor);
+
+ for(x = (int)dest.width()-1; x > (int)dest.width()-borderWidth-1; --x)
+ dest.setPixelColor(x, y, btmColor);
+ }
+
+ // bottom
+
+ for(wc = borderWidth; y < (int)dest.height(); ++y, --wc)
+ {
+ for(x=0; x < wc; ++x)
+ dest.setPixelColor(x, y, topColor);
+
+ for(; x < (int)dest.width(); ++x)
+ dest.setPixelColor(x, y, btmColor);
+ }
+
+ dest.bitBltImage(&src, borderWidth, borderWidth);
+}
+
+void Border::pattern2(Digikam::DImg &src, Digikam::DImg &dest, int borderWidth,
+ const Digikam::DColor &firstColor, const Digikam::DColor &secondColor,
+ int firstWidth, int secondWidth)
+{
+ // Border tile.
+
+ int w = m_orgWidth + borderWidth*2;
+ int h = m_orgHeight + borderWidth*2;
+
+ DDebug() << "Border File:" << m_borderPath << endl;
+ Digikam::DImg border(m_borderPath);
+ if ( border.isNull() )
+ return;
+
+ Digikam::DImg borderImg(w, h, src.sixteenBit(), src.hasAlpha());
+ border.convertToDepthOfImage(&borderImg);
+
+ for (int x = 0 ; x < w ; x+=border.width())
+ for (int y = 0 ; y < h ; y+=border.height())
+ borderImg.bitBltImage(&border, x, y);
+
+ // First line around the pattern tile.
+ Digikam::DImg tmp = borderImg.smoothScale(src.width() + borderWidth*2,
+ src.height() + borderWidth*2 );
+
+ solid2(tmp, dest, firstColor, firstWidth);
+
+ // Second line around original image.
+ tmp.reset();
+ solid2(src, tmp, secondColor, secondWidth);
+
+ // Copy original image.
+ dest.bitBltImage(&tmp, borderWidth, borderWidth);
+}
+
+} // NameSpace DigikamBorderImagesPlugin
diff --git a/src/imageplugins/border/border.h b/src/imageplugins/border/border.h
new file mode 100644
index 00000000..b213e27a
--- /dev/null
+++ b/src/imageplugins/border/border.h
@@ -0,0 +1,151 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : border threaded image filter.
+ *
+ * Copyright 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef BORDER_H
+#define BORDER_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqcolor.h>
+#include <tqimage.h>
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamBorderImagesPlugin
+{
+
+class Border : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ enum BorderTypes
+ {
+ SolidBorder=0,
+ NiepceBorder,
+ BeveledBorder,
+ PineBorder,
+ WoodBorder,
+ PaperBorder,
+ ParqueBorder,
+ IceBorder,
+ LeafBorder,
+ MarbleBorder,
+ RainBorder,
+ CratersBorder,
+ DriedBorder,
+ PinkBorder,
+ StoneBorder,
+ ChalkBorder,
+ GraniteBorder,
+ RockBorder,
+ WallBorder
+ };
+
+public:
+
+ /** Constructor using settings to preserve aspect ratio of image. */
+ Border(Digikam::DImg *orgImage, TQObject *parent=0, int orgWidth=0, int orgHeight=0,
+ TQString borderPath=TQString(), int borderType=SolidBorder, float borderPercent=0.1,
+ Digikam::DColor solidColor = Digikam::DColor(),
+ Digikam::DColor niepceBorderColor = Digikam::DColor(),
+ Digikam::DColor niepceLineColor = Digikam::DColor(),
+ Digikam::DColor bevelUpperLeftColor = Digikam::DColor(),
+ Digikam::DColor bevelLowerRightColor = Digikam::DColor(),
+ Digikam::DColor decorativeFirstColor = Digikam::DColor(),
+ Digikam::DColor decorativeSecondColor = Digikam::DColor());
+
+ /** Constructor using settings to not-preserve aspect ratio of image. */
+ Border(Digikam::DImg *orgImage, TQObject *parent=0, int orgWidth=0, int orgHeight=0,
+ TQString borderPath=TQString(), int borderType=SolidBorder,
+ int borderWidth1=100, int borderWidth2=20, int borderWidth3=20, int borderWidth4=10,
+ Digikam::DColor solidColor = Digikam::DColor(),
+ Digikam::DColor niepceBorderColor = Digikam::DColor(),
+ Digikam::DColor niepceLineColor = Digikam::DColor(),
+ Digikam::DColor bevelUpperLeftColor = Digikam::DColor(),
+ Digikam::DColor bevelLowerRightColor = Digikam::DColor(),
+ Digikam::DColor decorativeFirstColor = Digikam::DColor(),
+ Digikam::DColor decorativeSecondColor = Digikam::DColor());
+
+ ~Border(){};
+
+private:
+
+ virtual void filterImage(void);
+
+
+ /** Methods to preserve aspect ratio of image. */
+ void solid(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg, int borderWidth);
+ void niepce(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg, int borderWidth,
+ const Digikam::DColor &bg, int lineWidth);
+ void bevel(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &topColor,
+ const Digikam::DColor &btmColor, int borderWidth);
+ void pattern(Digikam::DImg &src, Digikam::DImg &dest, int borderWidth, const Digikam::DColor &firstColor,
+ const Digikam::DColor &secondColor, int firstWidth, int secondWidth);
+
+ /** Methods to not-preserve aspect ratio of image. */
+ void solid2(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg, int borderWidth);
+ void niepce2(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &fg, int borderWidth,
+ const Digikam::DColor &bg, int lineWidth);
+ void bevel2(Digikam::DImg &src, Digikam::DImg &dest, const Digikam::DColor &topColor,
+ const Digikam::DColor &btmColor, int borderWidth);
+ void pattern2(Digikam::DImg &src, Digikam::DImg &dest, int borderWidth, const Digikam::DColor &firstColor,
+ const Digikam::DColor &secondColor, int firstWidth, int secondWidth);
+
+private:
+
+ bool m_preserveAspectRatio;
+
+ int m_orgWidth;
+ int m_orgHeight;
+
+ int m_borderType;
+
+ int m_borderWidth1;
+ int m_borderWidth2;
+ int m_borderWidth3;
+ int m_borderWidth4;
+
+ int m_borderMainWidth;
+ int m_border2ndWidth;
+
+ float m_orgRatio;
+
+ TQString m_borderPath;
+
+ Digikam::DColor m_solidColor;
+ Digikam::DColor m_niepceBorderColor;
+ Digikam::DColor m_niepceLineColor;
+ Digikam::DColor m_bevelUpperLeftColor;
+ Digikam::DColor m_bevelLowerRightColor;
+ Digikam::DColor m_decorativeFirstColor;
+ Digikam::DColor m_decorativeSecondColor;
+};
+
+} // NameSpace DigikamBorderImagesPlugin
+
+#endif /* BORDER_H */
diff --git a/src/imageplugins/border/bordertool.cpp b/src/imageplugins/border/bordertool.cpp
new file mode 100644
index 00000000..160f5b0a
--- /dev/null
+++ b/src/imageplugins/border/bordertool.cpp
@@ -0,0 +1,668 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-20
+ * Description : a digiKam image plugin to add a border
+ * around an image.
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <kcolorbutton.h>
+#include <tdeconfig.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kseparator.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "editortoolsettings.h"
+#include "border.h"
+#include "bordertool.h"
+#include "bordertool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamBorderImagesPlugin
+{
+
+BorderTool::BorderTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("border");
+ setToolName(i18n("Add Border"));
+ setToolIcon(SmallIcon("bordertool"));
+
+ m_previewWidget = new ImageWidget("bordertool Tool", 0, TQString(),
+ false, ImageGuideWidget::HVGuideMode, false);
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 11, 2);
+
+ TQLabel *label1 = new TQLabel(i18n("Type:"), m_gboxSettings->plainPage());
+
+ m_borderType = new RComboBox(m_gboxSettings->plainPage());
+ m_borderType->insertItem( i18n("Solid") );
+ // Niepce is Real name. This is the first guy in the world to have built a camera.
+ m_borderType->insertItem( "Niepce" );
+ m_borderType->insertItem( i18n("Beveled") );
+ m_borderType->insertItem( i18n("Decorative Pine") );
+ m_borderType->insertItem( i18n("Decorative Wood") );
+ m_borderType->insertItem( i18n("Decorative Paper") );
+ m_borderType->insertItem( i18n("Decorative Parquet") );
+ m_borderType->insertItem( i18n("Decorative Ice") );
+ m_borderType->insertItem( i18n("Decorative Leaf") );
+ m_borderType->insertItem( i18n("Decorative Marble") );
+ m_borderType->insertItem( i18n("Decorative Rain") );
+ m_borderType->insertItem( i18n("Decorative Craters") );
+ m_borderType->insertItem( i18n("Decorative Dried") );
+ m_borderType->insertItem( i18n("Decorative Pink") );
+ m_borderType->insertItem( i18n("Decorative Stone") );
+ m_borderType->insertItem( i18n("Decorative Chalk") );
+ m_borderType->insertItem( i18n("Decorative Granite") );
+ m_borderType->insertItem( i18n("Decorative Rock") );
+ m_borderType->insertItem( i18n("Decorative Wall") );
+ m_borderType->setDefaultItem(Border::SolidBorder);
+ TQWhatsThis::add( m_borderType, i18n("<p>Select the border type to add around the image."));
+
+ KSeparator *line1 = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ // -------------------------------------------------------------------
+
+ m_preserveAspectRatio = new TQCheckBox(m_gboxSettings->plainPage());
+ m_preserveAspectRatio->setText(i18n("Preserve Aspect Ratio"));
+ TQWhatsThis::add(m_preserveAspectRatio, i18n("Enable this option if you want to preserve the aspect "
+ "ratio of the image. If enabled, the border width will be "
+ "in percent of the image size, else the border width will "
+ "in pixels."));
+
+ m_labelBorderPercent = new TQLabel(i18n("Width (%):"), m_gboxSettings->plainPage());
+ m_borderPercent = new RIntNumInput(m_gboxSettings->plainPage());
+ m_borderPercent->setRange(1, 50, 1);
+ m_borderPercent->setDefaultValue(10);
+ TQWhatsThis::add(m_borderPercent, i18n("<p>Set here the border width in percent of the image size."));
+
+ m_labelBorderWidth = new TQLabel(i18n("Width (pixels):"), m_gboxSettings->plainPage());
+ m_borderWidth = new RIntNumInput(m_gboxSettings->plainPage());
+ m_borderWidth->setDefaultValue(100);
+ TQWhatsThis::add(m_borderWidth, i18n("<p>Set here the border width in pixels to add around the image."));
+
+ ImageIface iface(0, 0);
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+
+ if (w > h)
+ m_borderWidth->setRange(1, h/2, 1);
+ else
+ m_borderWidth->setRange(1, w/2, 1);
+
+ KSeparator *line2 = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ // -------------------------------------------------------------------
+
+ m_labelForeground = new TQLabel(m_gboxSettings->plainPage());
+ m_firstColorButton = new KColorButton( TQColor( 192, 192, 192 ), m_gboxSettings->plainPage() );
+ m_labelBackground = new TQLabel(m_gboxSettings->plainPage());
+ m_secondColorButton = new KColorButton( TQColor( 128, 128, 128 ), m_gboxSettings->plainPage() );
+
+ // -------------------------------------------------------------------
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 2);
+ grid->addMultiCellWidget(m_borderType, 1, 1, 0, 2);
+ grid->addMultiCellWidget(line1, 2, 2, 0, 2);
+ grid->addMultiCellWidget(m_preserveAspectRatio, 3, 3, 0, 2);
+ grid->addMultiCellWidget(m_labelBorderPercent, 4, 4, 0, 2);
+ grid->addMultiCellWidget(m_borderPercent, 5, 5, 0, 2);
+ grid->addMultiCellWidget(m_labelBorderWidth, 6, 6, 0, 2);
+ grid->addMultiCellWidget(m_borderWidth, 7, 7, 0, 2);
+ grid->addMultiCellWidget(line2, 8, 8, 0, 2);
+ grid->addMultiCellWidget(m_labelForeground, 9, 9, 0, 0);
+ grid->addMultiCellWidget(m_firstColorButton, 9, 9, 1, 2);
+ grid->addMultiCellWidget(m_labelBackground, 10, 10, 0, 0);
+ grid->addMultiCellWidget(m_secondColorButton, 10, 10, 1, 2);
+ grid->setRowStretch(11, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_preserveAspectRatio, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotPreserveAspectRatioToggled(bool)));
+
+ connect(m_borderType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotBorderTypeChanged(int)));
+
+ connect(m_borderPercent, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_borderWidth, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_firstColorButton, TQ_SIGNAL(changed(const TQColor &)),
+ this, TQ_SLOT(slotColorForegroundChanged(const TQColor &)));
+
+ connect(m_secondColorButton, TQ_SIGNAL(changed(const TQColor &)),
+ this, TQ_SLOT(slotColorBackgroundChanged(const TQColor &)));
+}
+
+BorderTool::~BorderTool()
+{
+}
+
+void BorderTool::readSettings()
+{
+ m_borderType->blockSignals(true);
+ m_borderPercent->blockSignals(true);
+ m_borderWidth->blockSignals(true);
+ m_firstColorButton->blockSignals(true);
+ m_secondColorButton->blockSignals(true);
+ m_preserveAspectRatio->blockSignals(true);
+
+ TDEConfig *config = kapp->config();
+ config->setGroup("border Tool");
+
+ m_borderType->setCurrentItem(config->readNumEntry("Border Type", m_borderType->defaultItem()));
+ m_borderPercent->setValue(config->readNumEntry("Border Percent", m_borderPercent->defaultValue()));
+ m_borderWidth->setValue(config->readNumEntry("Border Width", m_borderWidth->defaultValue()));
+ m_preserveAspectRatio->setChecked(config->readBoolEntry("Preserve Aspect Ratio", true));
+
+ TQColor black(0, 0, 0);
+ TQColor white(255, 255, 255);
+ TQColor gray1(192, 192, 192);
+ TQColor gray2(128, 128, 128);
+
+ m_solidColor = config->readColorEntry("Solid Color", &black);
+ m_niepceBorderColor = config->readColorEntry("Niepce Border Color", &white);
+ m_niepceLineColor = config->readColorEntry("Niepce Line Color", &black);
+ m_bevelUpperLeftColor = config->readColorEntry("Bevel Upper Left Color", &gray1);
+ m_bevelLowerRightColor = config->readColorEntry("Bevel Lower Right Color", &gray2);
+ m_decorativeFirstColor = config->readColorEntry("Decorative First Color", &black);
+ m_decorativeSecondColor = config->readColorEntry("Decorative Second Color", &black);
+
+ m_borderType->blockSignals(false);
+ m_borderPercent->blockSignals(false);
+ m_borderWidth->blockSignals(false);
+ m_firstColorButton->blockSignals(false);
+ m_secondColorButton->blockSignals(false);
+ m_preserveAspectRatio->blockSignals(false);
+
+ slotBorderTypeChanged(m_borderType->currentItem());
+}
+
+void BorderTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("border Tool");
+
+ config->writeEntry("Border Type", m_borderType->currentItem());
+ config->writeEntry("Border Percent", m_borderPercent->value());
+ config->writeEntry("Border Width", m_borderWidth->value());
+ config->writeEntry("Preserve Aspect Ratio", m_preserveAspectRatio->isChecked());
+
+ config->writeEntry("Solid Color", m_solidColor);
+ config->writeEntry("Niepce Border Color", m_niepceBorderColor);
+ config->writeEntry("Niepce Line Color", m_niepceLineColor);
+ config->writeEntry("Bevel Upper Left Color", m_bevelUpperLeftColor);
+ config->writeEntry("Bevel Lower Right Color", m_bevelLowerRightColor);
+ config->writeEntry("Decorative First Color", m_decorativeFirstColor);
+ config->writeEntry("Decorative Second Color", m_decorativeSecondColor);
+
+ m_previewWidget->writeSettings();
+
+ config->sync();
+}
+
+void BorderTool::slotResetSettings()
+{
+ m_borderType->blockSignals(true);
+ m_borderPercent->blockSignals(true);
+ m_borderWidth->blockSignals(true);
+ m_firstColorButton->blockSignals(true);
+ m_secondColorButton->blockSignals(true);
+ m_preserveAspectRatio->blockSignals(true);
+
+ m_borderType->slotReset();
+ m_borderPercent->slotReset();
+ m_borderWidth->slotReset();
+ m_preserveAspectRatio->setChecked(true);
+
+ m_solidColor = TQColor(0, 0, 0);
+ m_niepceBorderColor = TQColor(255, 255, 255);
+ m_niepceLineColor = TQColor(0, 0, 0);
+ m_bevelUpperLeftColor = TQColor(192, 192, 192);
+ m_bevelLowerRightColor = TQColor(128, 128, 128);
+ m_decorativeFirstColor = TQColor(0, 0, 0);
+ m_decorativeSecondColor = TQColor(0, 0, 0);
+
+ m_borderType->blockSignals(false);
+ m_borderPercent->blockSignals(false);
+ m_borderWidth->blockSignals(false);
+ m_firstColorButton->blockSignals(false);
+ m_secondColorButton->blockSignals(false);
+ m_preserveAspectRatio->blockSignals(false);
+
+ slotBorderTypeChanged(Border::SolidBorder);
+}
+
+void BorderTool::renderingFinished()
+{
+ m_preserveAspectRatio->setEnabled(true);
+ m_borderType->setEnabled(true);
+ m_borderPercent->setEnabled(true);
+ m_borderWidth->setEnabled(true);
+ m_firstColorButton->setEnabled(true);
+ m_secondColorButton->setEnabled(true);
+ toggleBorderSlider(m_preserveAspectRatio->isChecked());
+}
+
+void BorderTool::slotColorForegroundChanged(const TQColor &color)
+{
+ switch (m_borderType->currentItem())
+ {
+ case Border::SolidBorder:
+ m_solidColor = color;
+ break;
+
+ case Border::NiepceBorder:
+ m_niepceBorderColor = color;
+ break;
+
+ case Border::BeveledBorder:
+ m_bevelUpperLeftColor = color;
+ break;
+
+ case Border::PineBorder:
+ case Border::WoodBorder:
+ case Border::PaperBorder:
+ case Border::ParqueBorder:
+ case Border::IceBorder:
+ case Border::LeafBorder:
+ case Border::MarbleBorder:
+ case Border::RainBorder:
+ case Border::CratersBorder:
+ case Border::DriedBorder:
+ case Border::PinkBorder:
+ case Border::StoneBorder:
+ case Border::ChalkBorder:
+ case Border::GraniteBorder:
+ case Border::RockBorder:
+ case Border::WallBorder:
+ m_decorativeFirstColor = color;
+ break;
+ }
+
+ slotEffect();
+}
+
+void BorderTool::slotColorBackgroundChanged(const TQColor &color)
+{
+ switch (m_borderType->currentItem())
+ {
+ case Border::SolidBorder:
+ m_solidColor = color;
+ break;
+
+ case Border::NiepceBorder:
+ m_niepceLineColor = color;
+ break;
+
+ case Border::BeveledBorder:
+ m_bevelLowerRightColor = color;
+ break;
+
+ case Border::PineBorder:
+ case Border::WoodBorder:
+ case Border::PaperBorder:
+ case Border::ParqueBorder:
+ case Border::IceBorder:
+ case Border::LeafBorder:
+ case Border::MarbleBorder:
+ case Border::RainBorder:
+ case Border::CratersBorder:
+ case Border::DriedBorder:
+ case Border::PinkBorder:
+ case Border::StoneBorder:
+ case Border::ChalkBorder:
+ case Border::GraniteBorder:
+ case Border::RockBorder:
+ case Border::WallBorder:
+ m_decorativeSecondColor = color;
+ break;
+ }
+
+ slotEffect();
+}
+
+void BorderTool::slotBorderTypeChanged(int borderType)
+{
+ m_labelForeground->setText(i18n("First:"));
+ m_labelBackground->setText(i18n("Second:"));
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the foreground color of the border."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the Background color of the border."));
+ m_firstColorButton->setEnabled(true);
+ m_secondColorButton->setEnabled(true);
+ m_labelForeground->setEnabled(true);
+ m_labelBackground->setEnabled(true);
+ m_borderPercent->setEnabled(true);
+
+ switch (borderType)
+ {
+ case Border::SolidBorder:
+ m_firstColorButton->setColor( m_solidColor );
+ m_secondColorButton->setEnabled(false);
+ m_labelBackground->setEnabled(false);
+ break;
+
+ case Border::NiepceBorder:
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the color of the main border."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the color of the line."));
+ m_firstColorButton->setColor( m_niepceBorderColor );
+ m_secondColorButton->setColor( m_niepceLineColor );
+ break;
+
+ case Border::BeveledBorder:
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the color of the upper left area."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the color of the lower right area."));
+ m_firstColorButton->setColor( m_bevelUpperLeftColor );
+ m_secondColorButton->setColor( m_bevelLowerRightColor );
+ break;
+
+ case Border::PineBorder:
+ case Border::WoodBorder:
+ case Border::PaperBorder:
+ case Border::ParqueBorder:
+ case Border::IceBorder:
+ case Border::LeafBorder:
+ case Border::MarbleBorder:
+ case Border::RainBorder:
+ case Border::CratersBorder:
+ case Border::DriedBorder:
+ case Border::PinkBorder:
+ case Border::StoneBorder:
+ case Border::ChalkBorder:
+ case Border::GraniteBorder:
+ case Border::RockBorder:
+ case Border::WallBorder:
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the color of the first line."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the color of the second line."));
+ m_firstColorButton->setColor( m_decorativeFirstColor );
+ m_secondColorButton->setColor( m_decorativeSecondColor );
+ break;
+ }
+
+ slotEffect();
+}
+
+void BorderTool::prepareEffect()
+{
+ m_borderType->setEnabled(false);
+ m_borderPercent->setEnabled(false);
+ m_borderWidth->setEnabled(false);
+ m_firstColorButton->setEnabled(false);
+ m_secondColorButton->setEnabled(false);
+ m_preserveAspectRatio->setEnabled(false);
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ int orgWidth = iface->originalWidth();
+ int orgHeight = iface->originalHeight();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sixteenBit = iface->previewSixteenBit();
+ uchar *data = iface->getPreviewImage();
+ DImg previewImage(w, h, sixteenBit,
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ int borderType = m_borderType->currentItem();
+ float ratio = (float)w/(float)orgWidth;
+ int borderWidth = (int)((float)m_borderWidth->value()*ratio);
+ TQString border = getBorderPath( m_borderType->currentItem() );
+
+ if (m_preserveAspectRatio->isChecked())
+ {
+ setFilter(dynamic_cast<DImgThreadedFilter*>(
+ new Border(&previewImage, this, orgWidth, orgHeight,
+ border, borderType, m_borderPercent->value()/100.0,
+ DColor(m_solidColor, sixteenBit),
+ DColor(m_niepceBorderColor, sixteenBit),
+ DColor(m_niepceLineColor, sixteenBit),
+ DColor(m_bevelUpperLeftColor, sixteenBit),
+ DColor(m_bevelLowerRightColor, sixteenBit),
+ DColor(m_decorativeFirstColor, sixteenBit),
+ DColor(m_decorativeSecondColor, sixteenBit))));
+ }
+ else
+ {
+ setFilter(dynamic_cast<DImgThreadedFilter*>(
+ new Border(&previewImage, this, orgWidth, orgHeight,
+ border, borderType, borderWidth,
+ (int)(20.0*ratio), (int)(20.0*ratio), 3,
+ DColor(m_solidColor, sixteenBit),
+ DColor(m_niepceBorderColor, sixteenBit),
+ DColor(m_niepceLineColor, sixteenBit),
+ DColor(m_bevelUpperLeftColor, sixteenBit),
+ DColor(m_bevelLowerRightColor, sixteenBit),
+ DColor(m_decorativeFirstColor, sixteenBit),
+ DColor(m_decorativeSecondColor, sixteenBit))));
+ }
+}
+
+void BorderTool::prepareFinal()
+{
+ m_borderType->setEnabled(false);
+ m_borderPercent->setEnabled(false);
+ m_borderWidth->setEnabled(false);
+ m_firstColorButton->setEnabled(false);
+ m_secondColorButton->setEnabled(false);
+
+ int borderType = m_borderType->currentItem();
+ int borderWidth = m_borderWidth->value();
+ float borderRatio = m_borderPercent->value()/100.0;
+ TQString border = getBorderPath( m_borderType->currentItem() );
+
+ ImageIface iface(0, 0);
+ int orgWidth = iface.originalWidth();
+ int orgHeight = iface.originalHeight();
+ bool sixteenBit = iface.previewSixteenBit();
+ uchar *data = iface.getOriginalImage();
+ DImg orgImage(orgWidth, orgHeight, sixteenBit,
+ iface.originalHasAlpha(), data);
+ delete [] data;
+
+ if (m_preserveAspectRatio->isChecked())
+ {
+ setFilter(dynamic_cast<DImgThreadedFilter*>(
+ new Border(&orgImage, this, orgWidth, orgHeight,
+ border, borderType, borderRatio,
+ DColor(m_solidColor, sixteenBit),
+ DColor(m_niepceBorderColor, sixteenBit),
+ DColor(m_niepceLineColor, sixteenBit),
+ DColor(m_bevelUpperLeftColor, sixteenBit),
+ DColor(m_bevelLowerRightColor, sixteenBit),
+ DColor(m_decorativeFirstColor, sixteenBit),
+ DColor(m_decorativeSecondColor, sixteenBit))));
+ }
+ else
+ {
+ setFilter(dynamic_cast<DImgThreadedFilter*>(
+ new Border(&orgImage, this, orgWidth, orgHeight,
+ border, borderType, borderWidth, 15, 15, 10,
+ DColor(m_solidColor, sixteenBit),
+ DColor(m_niepceBorderColor, sixteenBit),
+ DColor(m_niepceLineColor, sixteenBit),
+ DColor(m_bevelUpperLeftColor, sixteenBit),
+ DColor(m_bevelLowerRightColor, sixteenBit),
+ DColor(m_decorativeFirstColor, sixteenBit),
+ DColor(m_decorativeSecondColor, sixteenBit))));
+ }
+}
+
+void BorderTool::putPreviewData()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+
+ DImg imTemp = filter()->getTargetImage().smoothScale(w, h, TQSize::ScaleMin);
+ DImg imDest( w, h, filter()->getTargetImage().sixteenBit(),
+ filter()->getTargetImage().hasAlpha() );
+
+ imDest.fill( DColor(m_previewWidget->paletteBackgroundColor().rgb(),
+ filter()->getTargetImage().sixteenBit()) );
+
+ imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2);
+
+ iface->putPreviewImage(imDest.bits());
+ m_previewWidget->updatePreview();
+}
+
+void BorderTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+
+ DImg targetImage = filter()->getTargetImage();
+ iface.putOriginalImage(i18n("Add Border"),
+ targetImage.bits(),
+ targetImage.width(), targetImage.height());
+}
+
+TQString BorderTool::getBorderPath(int border)
+{
+ TQString pattern;
+
+ switch (border)
+ {
+ case Border::PineBorder:
+ pattern = "pine-pattern";
+ break;
+
+ case Border::WoodBorder:
+ pattern = "wood-pattern";
+ break;
+
+ case Border::PaperBorder:
+ pattern = "paper-pattern";
+ break;
+
+ case Border::ParqueBorder:
+ pattern = "parque-pattern";
+ break;
+
+ case Border::IceBorder:
+ pattern = "ice-pattern";
+ break;
+
+ case Border::LeafBorder:
+ pattern = "leaf-pattern";
+ break;
+
+ case Border::MarbleBorder:
+ pattern = "marble-pattern";
+ break;
+
+ case Border::RainBorder:
+ pattern = "rain-pattern";
+ break;
+
+ case Border::CratersBorder:
+ pattern = "craters-pattern";
+ break;
+
+ case Border::DriedBorder:
+ pattern = "dried-pattern";
+ break;
+
+ case Border::PinkBorder:
+ pattern = "pink-pattern";
+ break;
+
+ case Border::StoneBorder:
+ pattern = "stone-pattern";
+ break;
+
+ case Border::ChalkBorder:
+ pattern = "chalk-pattern";
+ break;
+
+ case Border::GraniteBorder:
+ pattern = "granit-pattern";
+ break;
+
+ case Border::RockBorder:
+ pattern = "rock-pattern";
+ break;
+
+ case Border::WallBorder:
+ pattern = "wall-pattern";
+ break;
+
+ default:
+ return TQString();
+ }
+
+ TDEGlobal::dirs()->addResourceType(pattern.ascii(), TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ return (TDEGlobal::dirs()->findResourceDir(pattern.ascii(), pattern + ".png") + pattern + ".png" );
+}
+
+void BorderTool::slotPreserveAspectRatioToggled(bool b)
+{
+ toggleBorderSlider(b);
+ slotTimer();
+}
+
+void BorderTool::toggleBorderSlider(bool b)
+{
+ m_borderPercent->setEnabled(b);
+ m_borderWidth->setEnabled(!b);
+ m_labelBorderPercent->setEnabled(b);
+ m_labelBorderWidth->setEnabled(!b);
+}
+
+} // NameSpace DigikamBorderImagesPlugin
diff --git a/src/imageplugins/border/bordertool.h b/src/imageplugins/border/bordertool.h
new file mode 100644
index 00000000..a8ec5ccf
--- /dev/null
+++ b/src/imageplugins/border/bordertool.h
@@ -0,0 +1,123 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-20
+ * Description : a digiKam image plugin to add a border
+ * around an image.
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef BORDERTOOL_H
+#define BORDERTOOL_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQLabel;
+class TQCheckBox;
+class TQColor;
+
+class KColorButton;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImageWidget;
+}
+
+namespace DigikamBorderImagesPlugin
+{
+
+class BorderTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ BorderTool(TQObject *parent);
+ ~BorderTool();
+
+private:
+
+ TQString getBorderPath(int border);
+
+private slots:
+
+ void slotPreserveAspectRatioToggled(bool);
+ void slotBorderTypeChanged(int borderType);
+ void slotColorForegroundChanged(const TQColor &color);
+ void slotColorBackgroundChanged(const TQColor &color);
+ void slotResetSettings();
+
+private:
+
+ void writeSettings();
+ void readSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+ void toggleBorderSlider(bool b);
+
+private:
+
+ TQLabel *m_labelBorderPercent;
+ TQLabel *m_labelBorderWidth;
+ TQLabel *m_labelForeground;
+ TQLabel *m_labelBackground;
+
+ TQCheckBox *m_preserveAspectRatio;
+
+ TQColor m_solidColor;
+ TQColor m_niepceBorderColor;
+ TQColor m_niepceLineColor;
+ TQColor m_bevelUpperLeftColor;
+ TQColor m_bevelLowerRightColor;
+ TQColor m_decorativeFirstColor;
+ TQColor m_decorativeSecondColor;
+
+ KDcrawIface::RComboBox *m_borderType;
+
+ KDcrawIface::RIntNumInput *m_borderPercent;
+ KDcrawIface::RIntNumInput *m_borderWidth;
+
+ KColorButton *m_firstColorButton;
+ KColorButton *m_secondColorButton;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamBorderImagesPlugin
+
+#endif /* BORDERTOOL_H */
diff --git a/src/imageplugins/border/digikamimageplugin_border.desktop b/src/imageplugins/border/digikamimageplugin_border.desktop
new file mode 100644
index 00000000..45f32a7e
--- /dev/null
+++ b/src/imageplugins/border/digikamimageplugin_border.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=ImagePlugin_Border
+Name[bg]=Приставка за снимки - �амки
+Name[da]=Billedplugin_Indramning
+Name[el]=ΠρόσθετοΕικόνας_Περίγραμμα
+Name[fi]=Reunus
+Name[hr]=Obrub
+Name[it]=PluginImmagini_Bordo
+Name[nl]=Afbeeldingsplugin_Rand
+Name[sr]=Оквир
+Name[sr@Latn]=Okvir
+Name[sv]=Insticksprogram för bildkanter
+Name[tr]=ResimEklentisi_Çerçeve
+Name[xx]=xxImagePlugin_Borderxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Add border to image plugin for digiKam
+Comment[bg]=Приставка на digiKam за добавяне на рамки на снимки
+Comment[ca]=Connector pel digiKam per afegir una vora a la imatge
+Comment[da]=Plugin til indramning af billeder i Digikam
+Comment[de]=digiKam-Modul zum Hinzufügen eines Rahmens zu einem Bild
+Comment[el]=Πρόσθετο προσθήκης περιγράμματος σε εικόνα για το digiKam
+Comment[es]=Plugin de digiKam para añadir bordes a la imagen
+Comment[et]=DigiKami pildile raami lisamise plugin
+Comment[fa]=افزودن لبه به وصلۀ تصویر برای digiKam
+Comment[fi]=Lisää kuvaan reunuksen
+Comment[gl]=Un plugin de digiKam para engadir un contorno á imaxe
+Comment[hr]=digiKam dodatak za dodavanje obruba
+Comment[is]=Íforrit fyrir digiKam sem gerir ramma á myndir
+Comment[it]=Plugin per l'aggiunta di bordi a un'immagire per digiKam
+Comment[ja]=digikam 画像装飾プラグイン
+Comment[nds]=digiKam-Moduul för't Tofögen vun Ränners rund en Bild
+Comment[nl]=Digikam-plugin voor het toevoegen van randen
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਹਾਸ਼ੀਆ ਜੋੜਨ ਲਈ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam dodająca ramkę do obrazu
+Comment[pt]=Um 'plugin' do digiKam para adicionar um contorno à imagem
+Comment[pt_BR]=Plugin de adicionar borda na imagem
+Comment[ru]=Модуль digiKam наложения рамки на изображение
+Comment[sk]=digiKam plugin pridávajúci okraj obrázku
+Comment[sr]=Прикључак за digiKam који додаје оквир на слику
+Comment[sr@Latn]=Priključak za digiKam koji dodaje okvir na sliku
+Comment[sv]=Digikam insticksprogram för tillägg av kanter
+Comment[tr]=digiKam için resme çerçeve eklenme eklentisi
+Comment[uk]=Втулок digiKam для додавання рамки навколо зображення
+Comment[vi]=Phần bổ sung thêm viền trên ảnh cho digiKam
+Comment[xx]=xxAdd border to image plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_border
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/border/digikamimageplugin_border_ui.rc b/src/imageplugins/border/digikamimageplugin_border_ui.rc
new file mode 100644
index 00000000..8cce0581
--- /dev/null
+++ b/src/imageplugins/border/digikamimageplugin_border_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="6" name="digikamimageplugin_border" >
+
+ <MenuBar>
+
+ <Menu name="Decorate" ><text>&amp;Decorate</text>
+ <Action name="imageplugin_border" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_border" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/border/imageeffect_border.cpp b/src/imageplugins/border/imageeffect_border.cpp
new file mode 100644
index 00000000..9f3dd6c3
--- /dev/null
+++ b/src/imageplugins/border/imageeffect_border.cpp
@@ -0,0 +1,667 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-20
+ * Description : a digiKam image plugin to add a border
+ * around an image.
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqcombobox.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <kseparator.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+#include <knuminput.h>
+#include <kcolorbutton.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "border.h"
+#include "imageeffect_border.h"
+#include "imageeffect_border.moc"
+
+namespace DigikamBorderImagesPlugin
+{
+
+ImageEffect_Border::ImageEffect_Border(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Add Border Around Photograph"),
+ "border", false, false, false,
+ Digikam::ImageGuideWidget::HVGuideMode)
+{
+ // No need Abort button action.
+ showButton(User1, false);
+
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Add Border"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to add a border around an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings, 10, 2, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Type:"), gboxSettings);
+
+ m_borderType = new TQComboBox( false, gboxSettings );
+ m_borderType->insertItem( i18n("Solid") );
+ // Niepce is Real name. This is the first guy in the world to have built a camera.
+ m_borderType->insertItem( "Niepce" );
+ m_borderType->insertItem( i18n("Beveled") );
+ m_borderType->insertItem( i18n("Decorative Pine") );
+ m_borderType->insertItem( i18n("Decorative Wood") );
+ m_borderType->insertItem( i18n("Decorative Paper") );
+ m_borderType->insertItem( i18n("Decorative Parquet") );
+ m_borderType->insertItem( i18n("Decorative Ice") );
+ m_borderType->insertItem( i18n("Decorative Leaf") );
+ m_borderType->insertItem( i18n("Decorative Marble") );
+ m_borderType->insertItem( i18n("Decorative Rain") );
+ m_borderType->insertItem( i18n("Decorative Craters") );
+ m_borderType->insertItem( i18n("Decorative Dried") );
+ m_borderType->insertItem( i18n("Decorative Pink") );
+ m_borderType->insertItem( i18n("Decorative Stone") );
+ m_borderType->insertItem( i18n("Decorative Chalk") );
+ m_borderType->insertItem( i18n("Decorative Granite") );
+ m_borderType->insertItem( i18n("Decorative Rock") );
+ m_borderType->insertItem( i18n("Decorative Wall") );
+ TQWhatsThis::add( m_borderType, i18n("<p>Select the border type to add around the image."));
+
+ KSeparator *line1 = new KSeparator(Horizontal, gboxSettings);
+
+ // -------------------------------------------------------------------
+
+ m_preserveAspectRatio = new TQCheckBox(gboxSettings);
+ m_preserveAspectRatio->setText(i18n("Preserve Aspect Ratio"));
+ TQWhatsThis::add(m_preserveAspectRatio, i18n("Enable this option if you want to preserve the aspect "
+ "ratio of the image. If enabled, the border width will be "
+ "in percent of the image size, else the border width will "
+ "in pixels."));
+
+ m_labelBorderPercent = new TQLabel(i18n("Width (%):"), gboxSettings);
+ m_borderPercent = new KIntNumInput(gboxSettings);
+ m_borderPercent->setRange(1, 50, 1, true);
+ TQWhatsThis::add(m_borderPercent, i18n("<p>Set here the border width in percent of the image size."));
+
+ m_labelBorderWidth = new TQLabel(i18n("Width (pixels):"), gboxSettings);
+ m_borderWidth = new KIntNumInput(gboxSettings);
+ TQWhatsThis::add(m_borderWidth, i18n("<p>Set here the border width in pixels to add around the image."));
+
+ Digikam::ImageIface iface(0, 0);
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+
+ if (w > h)
+ m_borderWidth->setRange(1, h/2, 1, true);
+ else
+ m_borderWidth->setRange(1, w/2, 1, true);
+
+ KSeparator *line2 = new KSeparator(Horizontal, gboxSettings);
+
+ // -------------------------------------------------------------------
+
+ m_labelForeground = new TQLabel(gboxSettings);
+ m_firstColorButton = new KColorButton( TQColor( 192, 192, 192 ), gboxSettings );
+ m_labelBackground = new TQLabel(gboxSettings);
+ m_secondColorButton = new KColorButton( TQColor( 128, 128, 128 ), gboxSettings );
+
+ // -------------------------------------------------------------------
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 2);
+ gridSettings->addMultiCellWidget(m_borderType, 1, 1, 0, 2);
+ gridSettings->addMultiCellWidget(line1, 2, 2, 0, 2);
+ gridSettings->addMultiCellWidget(m_preserveAspectRatio, 3, 3, 0, 2);
+ gridSettings->addMultiCellWidget(m_labelBorderPercent, 4, 4, 0, 2);
+ gridSettings->addMultiCellWidget(m_borderPercent, 5, 5, 0, 2);
+ gridSettings->addMultiCellWidget(m_labelBorderWidth, 6, 6, 0, 2);
+ gridSettings->addMultiCellWidget(m_borderWidth, 7, 7, 0, 2);
+ gridSettings->addMultiCellWidget(line2, 8, 8, 0, 2);
+ gridSettings->addMultiCellWidget(m_labelForeground, 9, 9, 0, 0);
+ gridSettings->addMultiCellWidget(m_firstColorButton, 9, 9, 1, 2);
+ gridSettings->addMultiCellWidget(m_labelBackground, 10, 10, 0, 0);
+ gridSettings->addMultiCellWidget(m_secondColorButton, 10, 10, 1, 2);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_preserveAspectRatio, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotPreserveAspectRatioToggled(bool)));
+
+ connect(m_borderType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotBorderTypeChanged(int)));
+
+ connect(m_borderPercent, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_borderWidth, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_firstColorButton, TQ_SIGNAL(changed(const TQColor &)),
+ this, TQ_SLOT(slotColorForegroundChanged(const TQColor &)));
+
+ connect(m_secondColorButton, TQ_SIGNAL(changed(const TQColor &)),
+ this, TQ_SLOT(slotColorBackgroundChanged(const TQColor &)));
+}
+
+ImageEffect_Border::~ImageEffect_Border()
+{
+}
+
+void ImageEffect_Border::readUserSettings(void)
+{
+ m_borderType->blockSignals(true);
+ m_borderPercent->blockSignals(true);
+ m_borderWidth->blockSignals(true);
+ m_firstColorButton->blockSignals(true);
+ m_secondColorButton->blockSignals(true);
+ m_preserveAspectRatio->blockSignals(true);
+
+ TDEConfig *config = kapp->config();
+ config->setGroup("border Tool Dialog");
+
+ m_borderType->setCurrentItem(config->readNumEntry("Border Type", Border::SolidBorder));
+ m_borderPercent->setValue(config->readNumEntry("Border Percent", 10) );
+ m_borderWidth->setValue(config->readNumEntry("Border Width", 100) );
+ m_preserveAspectRatio->setChecked(config->readBoolEntry("Preserve Aspect Ratio", true) );
+
+ TQColor black(0, 0, 0);
+ TQColor white(255, 255, 255);
+ TQColor gray1(192, 192, 192);
+ TQColor gray2(128, 128, 128);
+
+ m_solidColor = config->readColorEntry("Solid Color", &black);
+ m_niepceBorderColor = config->readColorEntry("Niepce Border Color", &white);
+ m_niepceLineColor = config->readColorEntry("Niepce Line Color", &black);
+ m_bevelUpperLeftColor = config->readColorEntry("Bevel Upper Left Color", &gray1);
+ m_bevelLowerRightColor = config->readColorEntry("Bevel Lower Right Color", &gray2);
+ m_decorativeFirstColor = config->readColorEntry("Decorative First Color", &black);
+ m_decorativeSecondColor = config->readColorEntry("Decorative Second Color", &black);
+
+ m_borderType->blockSignals(false);
+ m_borderPercent->blockSignals(false);
+ m_borderWidth->blockSignals(false);
+ m_firstColorButton->blockSignals(false);
+ m_secondColorButton->blockSignals(false);
+ m_preserveAspectRatio->blockSignals(false);
+
+ slotBorderTypeChanged(m_borderType->currentItem());
+}
+
+void ImageEffect_Border::writeUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("border Tool Dialog");
+
+ config->writeEntry("Border Type", m_borderType->currentItem());
+ config->writeEntry("Border Percent", m_borderPercent->value());
+ config->writeEntry("Border Width", m_borderWidth->value());
+ config->writeEntry("Preserve Aspect Ratio", m_preserveAspectRatio->isChecked());
+
+ config->writeEntry("Solid Color", m_solidColor);
+ config->writeEntry("Niepce Border Color", m_niepceBorderColor);
+ config->writeEntry("Niepce Line Color", m_niepceLineColor);
+ config->writeEntry("Bevel Upper Left Color", m_bevelUpperLeftColor);
+ config->writeEntry("Bevel Lower Right Color", m_bevelLowerRightColor);
+ config->writeEntry("Decorative First Color", m_decorativeFirstColor);
+ config->writeEntry("Decorative Second Color", m_decorativeSecondColor);
+
+ config->sync();
+}
+
+void ImageEffect_Border::resetValues()
+{
+ m_borderType->blockSignals(true);
+ m_borderPercent->blockSignals(true);
+ m_borderWidth->blockSignals(true);
+ m_firstColorButton->blockSignals(true);
+ m_secondColorButton->blockSignals(true);
+ m_preserveAspectRatio->blockSignals(true);
+
+ m_borderType->setCurrentItem(Border::SolidBorder);
+ m_borderPercent->setValue(10);
+ m_borderWidth->setValue(100);
+ m_preserveAspectRatio->setChecked(true);
+
+ m_solidColor = TQColor(0, 0, 0);
+ m_niepceBorderColor = TQColor(255, 255, 255);
+ m_niepceLineColor = TQColor(0, 0, 0);
+ m_bevelUpperLeftColor = TQColor(192, 192, 192);
+ m_bevelLowerRightColor = TQColor(128, 128, 128);
+ m_decorativeFirstColor = TQColor(0, 0, 0);
+ m_decorativeSecondColor = TQColor(0, 0, 0);
+
+ m_borderType->blockSignals(false);
+ m_borderPercent->blockSignals(false);
+ m_borderWidth->blockSignals(false);
+ m_firstColorButton->blockSignals(false);
+ m_secondColorButton->blockSignals(false);
+ m_preserveAspectRatio->blockSignals(false);
+
+ slotBorderTypeChanged(Border::SolidBorder);
+}
+
+void ImageEffect_Border::renderingFinished()
+{
+ m_preserveAspectRatio->setEnabled(true);
+ m_borderType->setEnabled(true);
+ m_borderPercent->setEnabled(true);
+ m_borderWidth->setEnabled(true);
+ m_firstColorButton->setEnabled(true);
+ m_secondColorButton->setEnabled(true);
+ toggleBorderSlider(m_preserveAspectRatio->isChecked());
+}
+
+void ImageEffect_Border::slotColorForegroundChanged(const TQColor &color)
+{
+ switch (m_borderType->currentItem())
+ {
+ case Border::SolidBorder:
+ m_solidColor = color;
+ break;
+
+ case Border::NiepceBorder:
+ m_niepceBorderColor = color;
+ break;
+
+ case Border::BeveledBorder:
+ m_bevelUpperLeftColor = color;
+ break;
+
+ case Border::PineBorder:
+ case Border::WoodBorder:
+ case Border::PaperBorder:
+ case Border::ParqueBorder:
+ case Border::IceBorder:
+ case Border::LeafBorder:
+ case Border::MarbleBorder:
+ case Border::RainBorder:
+ case Border::CratersBorder:
+ case Border::DriedBorder:
+ case Border::PinkBorder:
+ case Border::StoneBorder:
+ case Border::ChalkBorder:
+ case Border::GraniteBorder:
+ case Border::RockBorder:
+ case Border::WallBorder:
+ m_decorativeFirstColor = color;
+ break;
+ }
+
+ slotEffect();
+}
+
+void ImageEffect_Border::slotColorBackgroundChanged(const TQColor &color)
+{
+ switch (m_borderType->currentItem())
+ {
+ case Border::SolidBorder:
+ m_solidColor = color;
+ break;
+
+ case Border::NiepceBorder:
+ m_niepceLineColor = color;
+ break;
+
+ case Border::BeveledBorder:
+ m_bevelLowerRightColor = color;
+ break;
+
+ case Border::PineBorder:
+ case Border::WoodBorder:
+ case Border::PaperBorder:
+ case Border::ParqueBorder:
+ case Border::IceBorder:
+ case Border::LeafBorder:
+ case Border::MarbleBorder:
+ case Border::RainBorder:
+ case Border::CratersBorder:
+ case Border::DriedBorder:
+ case Border::PinkBorder:
+ case Border::StoneBorder:
+ case Border::ChalkBorder:
+ case Border::GraniteBorder:
+ case Border::RockBorder:
+ case Border::WallBorder:
+ m_decorativeSecondColor = color;
+ break;
+ }
+
+ slotEffect();
+}
+
+void ImageEffect_Border::slotBorderTypeChanged(int borderType)
+{
+ m_labelForeground->setText(i18n("First:"));
+ m_labelBackground->setText(i18n("Second:"));
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the foreground color of the border."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the Background color of the border."));
+ m_firstColorButton->setEnabled(true);
+ m_secondColorButton->setEnabled(true);
+ m_labelForeground->setEnabled(true);
+ m_labelBackground->setEnabled(true);
+ m_borderPercent->setEnabled(true);
+
+ switch (borderType)
+ {
+ case Border::SolidBorder:
+ m_firstColorButton->setColor( m_solidColor );
+ m_secondColorButton->setEnabled(false);
+ m_labelBackground->setEnabled(false);
+ break;
+
+ case Border::NiepceBorder:
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the color of the main border."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the color of the line."));
+ m_firstColorButton->setColor( m_niepceBorderColor );
+ m_secondColorButton->setColor( m_niepceLineColor );
+ break;
+
+ case Border::BeveledBorder:
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the color of the upper left area."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the color of the lower right area."));
+ m_firstColorButton->setColor( m_bevelUpperLeftColor );
+ m_secondColorButton->setColor( m_bevelLowerRightColor );
+ break;
+
+ case Border::PineBorder:
+ case Border::WoodBorder:
+ case Border::PaperBorder:
+ case Border::ParqueBorder:
+ case Border::IceBorder:
+ case Border::LeafBorder:
+ case Border::MarbleBorder:
+ case Border::RainBorder:
+ case Border::CratersBorder:
+ case Border::DriedBorder:
+ case Border::PinkBorder:
+ case Border::StoneBorder:
+ case Border::ChalkBorder:
+ case Border::GraniteBorder:
+ case Border::RockBorder:
+ case Border::WallBorder:
+ TQWhatsThis::add( m_firstColorButton, i18n("<p>Set here the color of the first line."));
+ TQWhatsThis::add( m_secondColorButton, i18n("<p>Set here the color of the second line."));
+ m_firstColorButton->setColor( m_decorativeFirstColor );
+ m_secondColorButton->setColor( m_decorativeSecondColor );
+ break;
+ }
+
+ slotEffect();
+}
+
+void ImageEffect_Border::prepareEffect()
+{
+ m_borderType->setEnabled(false);
+ m_borderPercent->setEnabled(false);
+ m_borderWidth->setEnabled(false);
+ m_firstColorButton->setEnabled(false);
+ m_secondColorButton->setEnabled(false);
+ m_preserveAspectRatio->setEnabled(false);
+
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ int orgWidth = iface->originalWidth();
+ int orgHeight = iface->originalHeight();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sixteenBit = iface->previewSixteenBit();
+ uchar *data = iface->getPreviewImage();
+ Digikam::DImg previewImage(w, h, sixteenBit,
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ int borderType = m_borderType->currentItem();
+ float ratio = (float)w/(float)orgWidth;
+ int borderWidth = (int)((float)m_borderWidth->value()*ratio);
+ TQString border = getBorderPath( m_borderType->currentItem() );
+
+ if (m_preserveAspectRatio->isChecked())
+ {
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Border(&previewImage, this, orgWidth, orgHeight,
+ border, borderType, m_borderPercent->value()/100.0,
+ Digikam::DColor(m_solidColor, sixteenBit),
+ Digikam::DColor(m_niepceBorderColor, sixteenBit),
+ Digikam::DColor(m_niepceLineColor, sixteenBit),
+ Digikam::DColor(m_bevelUpperLeftColor, sixteenBit),
+ Digikam::DColor(m_bevelLowerRightColor, sixteenBit),
+ Digikam::DColor(m_decorativeFirstColor, sixteenBit),
+ Digikam::DColor(m_decorativeSecondColor, sixteenBit)) );
+ }
+ else
+ {
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Border(&previewImage, this, orgWidth, orgHeight,
+ border, borderType, borderWidth,
+ (int)(20.0*ratio), (int)(20.0*ratio), 3,
+ Digikam::DColor(m_solidColor, sixteenBit),
+ Digikam::DColor(m_niepceBorderColor, sixteenBit),
+ Digikam::DColor(m_niepceLineColor, sixteenBit),
+ Digikam::DColor(m_bevelUpperLeftColor, sixteenBit),
+ Digikam::DColor(m_bevelLowerRightColor, sixteenBit),
+ Digikam::DColor(m_decorativeFirstColor, sixteenBit),
+ Digikam::DColor(m_decorativeSecondColor, sixteenBit)) );
+ }
+}
+
+void ImageEffect_Border::prepareFinal()
+{
+ m_borderType->setEnabled(false);
+ m_borderPercent->setEnabled(false);
+ m_borderWidth->setEnabled(false);
+ m_firstColorButton->setEnabled(false);
+ m_secondColorButton->setEnabled(false);
+
+ int borderType = m_borderType->currentItem();
+ int borderWidth = m_borderWidth->value();
+ float borderRatio = m_borderPercent->value()/100.0;
+ TQString border = getBorderPath( m_borderType->currentItem() );
+
+ Digikam::ImageIface iface(0, 0);
+ int orgWidth = iface.originalWidth();
+ int orgHeight = iface.originalHeight();
+ bool sixteenBit = iface.previewSixteenBit();
+ uchar *data = iface.getOriginalImage();
+ Digikam::DImg orgImage(orgWidth, orgHeight, sixteenBit,
+ iface.originalHasAlpha(), data);
+ delete [] data;
+
+ if (m_preserveAspectRatio->isChecked())
+ {
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Border(&orgImage, this, orgWidth, orgHeight,
+ border, borderType, borderRatio,
+ Digikam::DColor(m_solidColor, sixteenBit),
+ Digikam::DColor(m_niepceBorderColor, sixteenBit),
+ Digikam::DColor(m_niepceLineColor, sixteenBit),
+ Digikam::DColor(m_bevelUpperLeftColor, sixteenBit),
+ Digikam::DColor(m_bevelLowerRightColor, sixteenBit),
+ Digikam::DColor(m_decorativeFirstColor, sixteenBit),
+ Digikam::DColor(m_decorativeSecondColor, sixteenBit)) );
+ }
+ else
+ {
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Border(&orgImage, this, orgWidth, orgHeight,
+ border, borderType, borderWidth, 15, 15, 10,
+ Digikam::DColor(m_solidColor, sixteenBit),
+ Digikam::DColor(m_niepceBorderColor, sixteenBit),
+ Digikam::DColor(m_niepceLineColor, sixteenBit),
+ Digikam::DColor(m_bevelUpperLeftColor, sixteenBit),
+ Digikam::DColor(m_bevelLowerRightColor, sixteenBit),
+ Digikam::DColor(m_decorativeFirstColor, sixteenBit),
+ Digikam::DColor(m_decorativeSecondColor, sixteenBit)) );
+ }
+}
+
+void ImageEffect_Border::putPreviewData(void)
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+
+ Digikam::DImg imTemp = m_threadedFilter->getTargetImage().smoothScale(w, h, TQSize::ScaleMin);
+ Digikam::DImg imDest( w, h, m_threadedFilter->getTargetImage().sixteenBit(),
+ m_threadedFilter->getTargetImage().hasAlpha() );
+
+ imDest.fill( Digikam::DColor(paletteBackgroundColor().rgb(),
+ m_threadedFilter->getTargetImage().sixteenBit()) );
+
+ imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2);
+
+ iface->putPreviewImage(imDest.bits());
+ m_imagePreviewWidget->updatePreview();
+}
+
+void ImageEffect_Border::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ Digikam::DImg targetImage = m_threadedFilter->getTargetImage();
+ iface.putOriginalImage(i18n("Add Border"),
+ targetImage.bits(),
+ targetImage.width(), targetImage.height());
+}
+
+TQString ImageEffect_Border::getBorderPath(int border)
+{
+ TQString pattern;
+
+ switch (border)
+ {
+ case Border::PineBorder:
+ pattern = "pine-pattern";
+ break;
+
+ case Border::WoodBorder:
+ pattern = "wood-pattern";
+ break;
+
+ case Border::PaperBorder:
+ pattern = "paper-pattern";
+ break;
+
+ case Border::ParqueBorder:
+ pattern = "parque-pattern";
+ break;
+
+ case Border::IceBorder:
+ pattern = "ice-pattern";
+ break;
+
+ case Border::LeafBorder:
+ pattern = "leaf-pattern";
+ break;
+
+ case Border::MarbleBorder:
+ pattern = "marble-pattern";
+ break;
+
+ case Border::RainBorder:
+ pattern = "rain-pattern";
+ break;
+
+ case Border::CratersBorder:
+ pattern = "craters-pattern";
+ break;
+
+ case Border::DriedBorder:
+ pattern = "dried-pattern";
+ break;
+
+ case Border::PinkBorder:
+ pattern = "pink-pattern";
+ break;
+
+ case Border::StoneBorder:
+ pattern = "stone-pattern";
+ break;
+
+ case Border::ChalkBorder:
+ pattern = "chalk-pattern";
+ break;
+
+ case Border::GraniteBorder:
+ pattern = "granit-pattern";
+ break;
+
+ case Border::RockBorder:
+ pattern = "rock-pattern";
+ break;
+
+ case Border::WallBorder:
+ pattern = "wall-pattern";
+ break;
+
+ default:
+ return TQString();
+ }
+
+ TDEGlobal::dirs()->addResourceType(pattern.ascii(), TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ return (TDEGlobal::dirs()->findResourceDir(pattern.ascii(), pattern + ".png") + pattern + ".png" );
+}
+
+void ImageEffect_Border::slotPreserveAspectRatioToggled(bool b)
+{
+ toggleBorderSlider(b);
+ slotTimer();
+}
+
+void ImageEffect_Border::toggleBorderSlider(bool b)
+{
+ m_borderPercent->setEnabled(b);
+ m_borderWidth->setEnabled(!b);
+ m_labelBorderPercent->setEnabled(b);
+ m_labelBorderWidth->setEnabled(!b);
+}
+
+} // NameSpace DigikamBorderImagesPlugin
+
diff --git a/src/imageplugins/border/imageeffect_border.h b/src/imageplugins/border/imageeffect_border.h
new file mode 100644
index 00000000..787a76a0
--- /dev/null
+++ b/src/imageplugins/border/imageeffect_border.h
@@ -0,0 +1,109 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-20
+ * Description : a digiKam image plugin to add a border
+ * around an image.
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_BORDER_H
+#define IMAGEEFFECT_BORDER_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "imageguidedlg.h"
+
+class TQComboBox;
+class TQLabel;
+class TQCheckBox;
+class TQColor;
+
+class KIntNumInput;
+class KColorButton;
+
+namespace DigikamBorderImagesPlugin
+{
+
+class ImageEffect_Border : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Border(TQWidget *parent);
+ ~ImageEffect_Border();
+
+private:
+
+ TQString getBorderPath(int border);
+
+private slots:
+
+ void slotPreserveAspectRatioToggled(bool);
+ void slotBorderTypeChanged(int borderType);
+ void slotColorForegroundChanged(const TQColor &color);
+ void slotColorBackgroundChanged(const TQColor &color);
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+ void toggleBorderSlider(bool b);
+
+private:
+
+ TQLabel *m_labelBorderPercent;
+ TQLabel *m_labelBorderWidth;
+ TQLabel *m_labelForeground;
+ TQLabel *m_labelBackground;
+
+ TQComboBox *m_borderType;
+
+ TQCheckBox *m_preserveAspectRatio;
+
+ TQColor m_solidColor;
+ TQColor m_niepceBorderColor;
+ TQColor m_niepceLineColor;
+ TQColor m_bevelUpperLeftColor;
+ TQColor m_bevelLowerRightColor;
+ TQColor m_decorativeFirstColor;
+ TQColor m_decorativeSecondColor;
+
+ KIntNumInput *m_borderPercent;
+ KIntNumInput *m_borderWidth;
+
+ KColorButton *m_firstColorButton;
+ KColorButton *m_secondColorButton;
+};
+
+} // NameSpace DigikamBorderImagesPlugin
+
+#endif /* IMAGEEFFECT_BORDER_H */
diff --git a/src/imageplugins/border/imageplugin_border.cpp b/src/imageplugins/border/imageplugin_border.cpp
new file mode 100644
index 00000000..f0c5a236
--- /dev/null
+++ b/src/imageplugins/border/imageplugin_border.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-20
+ * Description : a digiKam image plugin to add a border
+ * around an image.
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "bordertool.h"
+#include "imageplugin_border.h"
+#include "imageplugin_border.moc"
+
+using namespace DigikamBorderImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_border,
+ KGenericFactory<ImagePlugin_Border>("digikamimageplugin_border"));
+
+ImagePlugin_Border::ImagePlugin_Border(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_Border")
+{
+ m_borderAction = new TDEAction(i18n("Add Border..."), "bordertool",
+ 0,
+ this, TQ_SLOT(slotBorder()),
+ actionCollection(), "imageplugin_border");
+
+ setXMLFile("digikamimageplugin_border_ui.rc");
+
+ DDebug() << "ImagePlugin_Border plugin loaded" << endl;
+}
+
+ImagePlugin_Border::~ImagePlugin_Border()
+{
+}
+
+void ImagePlugin_Border::setEnabledActions(bool enable)
+{
+ m_borderAction->setEnabled(enable);
+}
+
+void ImagePlugin_Border::slotBorder()
+{
+ BorderTool *tool = new BorderTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/border/imageplugin_border.h b/src/imageplugins/border/imageplugin_border.h
new file mode 100644
index 00000000..e5b3d959
--- /dev/null
+++ b/src/imageplugins/border/imageplugin_border.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-20
+ * Description : a digiKam image plugin to add a border
+ * around an image.
+ *
+ * Copyright 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_BORDER_H
+#define IMAGEPLUGIN_BORDER_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Border : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Border(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_Border();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotBorder();
+
+private:
+
+ TDEAction *m_borderAction;
+};
+
+#endif /* IMAGEPLUGIN_BORDER_H */
diff --git a/src/imageplugins/border/patterns/Makefile.am b/src/imageplugins/border/patterns/Makefile.am
new file mode 100644
index 00000000..31bb4b2d
--- /dev/null
+++ b/src/imageplugins/border/patterns/Makefile.am
@@ -0,0 +1,5 @@
+borderpicsdir = $(kde_datadir)/digikam/data
+borderpics_DATA = pine-pattern.png wood-pattern.png paper-pattern.png parque-pattern.png \
+ ice-pattern.png leaf-pattern.png marble-pattern.png rain-pattern.png \
+ craters-pattern.png dried-pattern.png pink-pattern.png stone-pattern.png \
+ chalk-pattern.png granit-pattern.png rock-pattern.png wall-pattern.png
diff --git a/src/imageplugins/border/patterns/chalk-pattern.png b/src/imageplugins/border/patterns/chalk-pattern.png
new file mode 100644
index 00000000..31c34f62
--- /dev/null
+++ b/src/imageplugins/border/patterns/chalk-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/craters-pattern.png b/src/imageplugins/border/patterns/craters-pattern.png
new file mode 100644
index 00000000..fe8fe792
--- /dev/null
+++ b/src/imageplugins/border/patterns/craters-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/dried-pattern.png b/src/imageplugins/border/patterns/dried-pattern.png
new file mode 100644
index 00000000..fca948ff
--- /dev/null
+++ b/src/imageplugins/border/patterns/dried-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/granit-pattern.png b/src/imageplugins/border/patterns/granit-pattern.png
new file mode 100644
index 00000000..eccbb354
--- /dev/null
+++ b/src/imageplugins/border/patterns/granit-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/ice-pattern.png b/src/imageplugins/border/patterns/ice-pattern.png
new file mode 100644
index 00000000..30a67265
--- /dev/null
+++ b/src/imageplugins/border/patterns/ice-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/leaf-pattern.png b/src/imageplugins/border/patterns/leaf-pattern.png
new file mode 100644
index 00000000..b602fceb
--- /dev/null
+++ b/src/imageplugins/border/patterns/leaf-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/marble-pattern.png b/src/imageplugins/border/patterns/marble-pattern.png
new file mode 100644
index 00000000..6346bf1f
--- /dev/null
+++ b/src/imageplugins/border/patterns/marble-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/paper-pattern.png b/src/imageplugins/border/patterns/paper-pattern.png
new file mode 100644
index 00000000..43da3b2f
--- /dev/null
+++ b/src/imageplugins/border/patterns/paper-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/parque-pattern.png b/src/imageplugins/border/patterns/parque-pattern.png
new file mode 100644
index 00000000..2aa5e96f
--- /dev/null
+++ b/src/imageplugins/border/patterns/parque-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/pine-pattern.png b/src/imageplugins/border/patterns/pine-pattern.png
new file mode 100644
index 00000000..4ae7a687
--- /dev/null
+++ b/src/imageplugins/border/patterns/pine-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/pink-pattern.png b/src/imageplugins/border/patterns/pink-pattern.png
new file mode 100644
index 00000000..d0649c85
--- /dev/null
+++ b/src/imageplugins/border/patterns/pink-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/rain-pattern.png b/src/imageplugins/border/patterns/rain-pattern.png
new file mode 100644
index 00000000..a282c442
--- /dev/null
+++ b/src/imageplugins/border/patterns/rain-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/rock-pattern.png b/src/imageplugins/border/patterns/rock-pattern.png
new file mode 100644
index 00000000..04dc8b0e
--- /dev/null
+++ b/src/imageplugins/border/patterns/rock-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/stone-pattern.png b/src/imageplugins/border/patterns/stone-pattern.png
new file mode 100644
index 00000000..98da7c2c
--- /dev/null
+++ b/src/imageplugins/border/patterns/stone-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/wall-pattern.png b/src/imageplugins/border/patterns/wall-pattern.png
new file mode 100644
index 00000000..3c101597
--- /dev/null
+++ b/src/imageplugins/border/patterns/wall-pattern.png
Binary files differ
diff --git a/src/imageplugins/border/patterns/wood-pattern.png b/src/imageplugins/border/patterns/wood-pattern.png
new file mode 100644
index 00000000..bfe4c724
--- /dev/null
+++ b/src/imageplugins/border/patterns/wood-pattern.png
Binary files differ
diff --git a/src/imageplugins/channelmixer/Makefile.am b/src/imageplugins/channelmixer/Makefile.am
new file mode 100644
index 00000000..61eaab34
--- /dev/null
+++ b/src/imageplugins/channelmixer/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_channelmixer_la_SOURCES = imageplugin_channelmixer.cpp \
+ channelmixertool.cpp
+
+digikamimageplugin_channelmixer_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_channelmixer_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_channelmixer.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_channelmixer.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_channelmixer_ui.rc
+
diff --git a/src/imageplugins/channelmixer/channelmixer.cpp b/src/imageplugins/channelmixer/channelmixer.cpp
new file mode 100644
index 00000000..849cc1e8
--- /dev/null
+++ b/src/imageplugins/channelmixer/channelmixer.cpp
@@ -0,0 +1,784 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-26
+ * Description : image channels mixer.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Load and save mixer gains methods inspired from
+ * Gimp 2.2.3 and copyrighted 2002 by Martin Guldahl
+ * <mguldahl at xmission dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+#include <cstring>
+#include <cstdlib>
+#include <cerrno>
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqlabel.h>
+#include <tqpainter.h>
+#include <tqcombobox.h>
+#include <tqspinbox.h>
+#include <tqvbox.h>
+#include <tqwhatsthis.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+#include <tqcheckbox.h>
+#include <tqfile.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <knuminput.h>
+#include <tdemessagebox.h>
+#include <tdeselect.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "version.h"
+#include "dimg.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "dimgimagefilters.h"
+#include "channelmixer.h"
+#include "channelmixer.moc"
+
+namespace DigikamChannelMixerImagesPlugin
+{
+
+ChannelMixerDialog::ChannelMixerDialog(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Color Channel Mixer"),
+ "channelmixer", true, false)
+{
+ m_destinationPreviewData = 0L;
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Color Channel Mixer"),
+ digikam_version,
+ I18N_NOOP("An image color channel mixer plugin for digiKam."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new Digikam::ImageWidget("channelmixer Tool Dialog", plainPage(),
+ i18n("<p>You can see here the image's color channels' "
+ "gains adjustments preview. You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* grid = new TQGridLayout( gboxSettings, 9, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ m_channelCB->setCurrentText( i18n("Red") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the color channel to mix here:<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ grid->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "mixer settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+
+ grid->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQLabel *redLabel = new TQLabel(i18n("Red:"), gboxSettings);
+ m_redGain = new KDoubleNumInput(gboxSettings);
+ m_redGain->setPrecision(0);
+ m_redGain->setRange(-200.0, 200.0, 1, true);
+ TQWhatsThis::add( m_redGain, i18n("<p>Select the red color gain in percent for the current channel here."));
+
+ TQLabel *blueLabel = new TQLabel(i18n("Blue:"), gboxSettings);
+ m_greenGain = new KDoubleNumInput(gboxSettings);
+ m_greenGain->setPrecision(0);
+ m_greenGain->setRange(-200.0, 200.0, 1, true);
+ TQWhatsThis::add( m_greenGain, i18n("<p>Select the green color gain in percent for the current channel here."));
+
+ TQLabel *greenLabel = new TQLabel(i18n("Green:"), gboxSettings);
+ m_blueGain = new KDoubleNumInput(gboxSettings);
+ m_blueGain->setPrecision(0);
+ m_blueGain->setRange(-200.0, 200.0, 1, true);
+ TQWhatsThis::add( m_blueGain, i18n("<p>Select the blue color gain in percent for the current channel here."));
+
+ m_resetButton = new TQPushButton(i18n("&Reset"), gboxSettings);
+ TQWhatsThis::add( m_resetButton, i18n("Reset color channels' gains settings from the currently selected channel."));
+
+ grid->addMultiCellWidget(redLabel, 3, 3, 0, 0);
+ grid->addMultiCellWidget(greenLabel, 4, 4, 0, 0);
+ grid->addMultiCellWidget(blueLabel, 5, 5, 0, 0);
+ grid->addMultiCellWidget(m_redGain, 3, 3, 1, 4);
+ grid->addMultiCellWidget(m_greenGain, 4, 4, 1, 4);
+ grid->addMultiCellWidget(m_blueGain, 5, 5, 1, 4);
+ grid->addMultiCellWidget(m_resetButton, 6, 6, 0, 1);
+
+ // -------------------------------------------------------------
+
+ m_monochrome = new TQCheckBox( i18n("Monochrome"), gboxSettings);
+ TQWhatsThis::add( m_monochrome, i18n("<p>Enable this option if you want the image rendered in monochrome mode. "
+ "In this mode, the histogram will display only luminosity values."));
+
+ m_preserveLuminosity = new TQCheckBox( i18n("Preserve luminosity"), gboxSettings);
+ TQWhatsThis::add( m_preserveLuminosity, i18n("<p>Enable this option is you want preserve the image luminosity."));
+
+ grid->addMultiCellWidget(m_monochrome, 7, 7, 0, 4);
+ grid->addMultiCellWidget(m_preserveLuminosity, 8, 8, 0, 4);
+ grid->setRowStretch(9, 10);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+ // Channels and scale selection slots.
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // Gains settings slots.
+
+ connect(m_redGain, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotGainsChanged()));
+
+ connect(m_greenGain, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotGainsChanged()));
+
+ connect(m_blueGain, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotGainsChanged()));
+
+ connect(m_preserveLuminosity, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_monochrome, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotMonochromeActived(bool)));
+
+ // -------------------------------------------------------------
+ // Bouttons slots.
+
+ connect(m_resetButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetCurrentChannel()));
+}
+
+ChannelMixerDialog::~ChannelMixerDialog()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+}
+
+void ChannelMixerDialog::slotResetCurrentChannel()
+{
+ switch( m_channelCB->currentItem() )
+ {
+ case GreenChannelGains: // Green.
+ m_greenRedGain = 0.0;
+ m_greenGreenGain = 1.0;
+ m_greenBlueGain = 0.0;
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_blueRedGain = 0.0;
+ m_blueGreenGain = 0.0;
+ m_blueBlueGain = 1.0;
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_blackRedGain = 1.0;
+ m_blackGreenGain = 0.0;
+ m_blackBlueGain = 0.0;
+ }
+ else
+ {
+ m_redRedGain = 1.0;
+ m_redGreenGain = 0.0;
+ m_redBlueGain = 0.0;
+ }
+ break;
+ }
+
+ adjustSliders();
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void ChannelMixerDialog::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ChannelMixerDialog::slotGainsChanged()
+{
+ switch( m_channelCB->currentItem() )
+ {
+ case GreenChannelGains: // Green.
+ m_greenRedGain = m_redGain->value() / 100.0;
+ m_greenGreenGain = m_greenGain->value() / 100.0;
+ m_greenBlueGain = m_blueGain->value() / 100.0;
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_blueRedGain = m_redGain->value() / 100.0;
+ m_blueGreenGain = m_greenGain->value() / 100.0;
+ m_blueBlueGain = m_blueGain->value() / 100.0;
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_blackRedGain = m_redGain->value() / 100.0;
+ m_blackGreenGain = m_greenGain->value() / 100.0;
+ m_blackBlueGain = m_blueGain->value() / 100.0;
+ }
+ else
+ {
+ m_redRedGain = m_redGain->value() / 100.0;
+ m_redGreenGain = m_greenGain->value() / 100.0;
+ m_redBlueGain = m_blueGain->value() / 100.0;
+ }
+ break;
+ }
+
+ slotTimer();
+}
+
+void ChannelMixerDialog::adjustSliders(void)
+{
+ m_redGain->blockSignals(true);
+ m_greenGain->blockSignals(true);
+ m_blueGain->blockSignals(true);
+
+ switch( m_channelCB->currentItem() )
+ {
+ case GreenChannelGains: // Green.
+ m_redGain->setValue(m_greenRedGain * 100.0);
+ m_greenGain->setValue(m_greenGreenGain * 100.0);
+ m_blueGain->setValue(m_greenBlueGain * 100.0);
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_redGain->setValue(m_blueRedGain * 100.0);
+ m_greenGain->setValue(m_blueGreenGain * 100.0);
+ m_blueGain->setValue(m_blueBlueGain * 100.0);
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_redGain->setValue(m_blackRedGain * 100.0);
+ m_greenGain->setValue(m_blackGreenGain * 100.0);
+ m_blueGain->setValue(m_blackBlueGain * 100.0);
+ }
+ else
+ {
+ m_redGain->setValue(m_redRedGain * 100.0);
+ m_greenGain->setValue(m_redGreenGain * 100.0);
+ m_blueGain->setValue(m_redBlueGain * 100.0);
+ }
+ break;
+ }
+
+ m_redGain->blockSignals(false);
+ m_greenGain->blockSignals(false);
+ m_blueGain->blockSignals(false);
+}
+
+void ChannelMixerDialog::slotMonochromeActived(bool mono)
+{
+ m_channelCB->setEnabled(!mono);
+ m_channelCB->setCurrentItem(RedChannelGains); // Red for monochrome.
+ slotChannelChanged(RedChannelGains); // Monochrome => display luminosity histogram value.
+}
+
+void ChannelMixerDialog::slotEffect()
+{
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+ Digikam::DImgImageFilters filter;
+
+ if (m_monochrome->isChecked())
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_blackRedGain, m_blackGreenGain, m_blackBlueGain, // Red channel gains.
+ 0.0, 1.0, 0.0, // Green channel gains (not used).
+ 0.0, 0.0, 1.0); // Blue channel gains (not used).
+ }
+ else
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_redRedGain, m_redGreenGain, m_redBlueGain, // Red channel gains.
+ m_greenRedGain, m_greenGreenGain, m_greenBlueGain, // Green channel gains.
+ m_blueRedGain, m_blueGreenGain, m_blueBlueGain); // Blue channel gains.
+ }
+
+ iface->putPreviewImage(data);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ memcpy (m_destinationPreviewData, data, w*h*(sb ? 8 : 4));
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] data;
+}
+
+void ChannelMixerDialog::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ Digikam::DImgImageFilters filter;
+
+ if (m_monochrome->isChecked())
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_blackRedGain, m_blackGreenGain, m_blackBlueGain, // Red channel gains.
+ 0.0, 1.0, 0.0, // Green channel gains (not used).
+ 0.0, 0.0, 1.0); // Blue channel gains (not used).
+ }
+ else
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_redRedGain, m_redGreenGain, m_redBlueGain, // Red channel gains.
+ m_greenRedGain, m_greenGreenGain, m_greenBlueGain, // Green channel gains.
+ m_blueRedGain, m_blueGreenGain, m_blueBlueGain); // Blue channel gains.
+ }
+
+ iface->putOriginalImage(i18n("Channel Mixer"), data);
+ delete [] data;
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+void ChannelMixerDialog::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case GreenChannelGains: // Green.
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ }
+ else
+ {
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ }
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+ adjustSliders();
+ slotEffect();
+}
+
+void ChannelMixerDialog::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ChannelMixerDialog::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("channelmixer Tool Dialog");
+
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+
+ m_monochrome->setChecked(config->readBoolEntry("Monochrome", false));
+ m_preserveLuminosity->setChecked(config->readNumEntry("PreserveLuminosity", false));
+
+ m_redRedGain = config->readDoubleNumEntry("RedRedGain", 1.0);
+ m_redGreenGain = config->readDoubleNumEntry("RedGreenGain", 0.0);
+ m_redBlueGain = config->readDoubleNumEntry("RedBlueGain", 0.0);
+
+ m_greenRedGain = config->readDoubleNumEntry("GreenRedGain", 0.0);
+ m_greenGreenGain = config->readDoubleNumEntry("GreenGreenGain", 1.0);
+ m_greenBlueGain = config->readDoubleNumEntry("GreenBlueGain", 0.0);
+
+ m_blueRedGain = config->readDoubleNumEntry("BlueRedGain", 0.0);
+ m_blueGreenGain = config->readDoubleNumEntry("BlueGreenGain", 0.0);
+ m_blueBlueGain = config->readDoubleNumEntry("BlueBlueGain", 1.0);
+
+ m_blackRedGain = config->readDoubleNumEntry("BlackRedGain", 1.0);
+ m_blackGreenGain = config->readDoubleNumEntry("BlackGreenGain", 0.0);
+ m_blackBlueGain = config->readDoubleNumEntry("BlackBlueGain", 0.0);
+
+ adjustSliders();
+ m_histogramWidget->reset();
+
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ChannelMixerDialog::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("channelmixer Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ config->writeEntry("Monochrome", m_monochrome->isChecked());
+ config->writeEntry("PreserveLuminosity", m_preserveLuminosity->isChecked());
+
+ config->writeEntry("RedRedGain", m_redRedGain);
+ config->writeEntry("RedGreenGain", m_redGreenGain);
+ config->writeEntry("RedBlueGain", m_redBlueGain);
+
+ config->writeEntry("GreenRedGain", m_greenRedGain);
+ config->writeEntry("GreenGreenGain", m_greenGreenGain);
+ config->writeEntry("GreenBlueGain", m_greenBlueGain);
+
+ config->writeEntry("BlueRedGain", m_blueRedGain);
+ config->writeEntry("BlueGreenGain", m_blueGreenGain);
+ config->writeEntry("BlueBlueGain", m_blueBlueGain);
+
+ config->writeEntry("BlackRedGain", m_blackRedGain);
+ config->writeEntry("BlackGreenGain", m_blackGreenGain);
+ config->writeEntry("BlackBlueGain", m_blackBlueGain);
+
+ config->sync();
+}
+
+void ChannelMixerDialog::resetValues()
+{
+ m_monochrome->blockSignals(true);
+ m_preserveLuminosity->blockSignals(true);
+
+ m_redRedGain = 1.0;
+ m_redGreenGain = 0.0;
+ m_redBlueGain = 0.0;
+
+ m_greenRedGain = 0.0;
+ m_greenGreenGain = 1.0;
+ m_greenBlueGain = 0.0;
+
+ m_blueRedGain = 0.0;
+ m_blueGreenGain = 0.0;
+ m_blueBlueGain = 1.0;
+
+ m_blackRedGain = 1.0;
+ m_blackGreenGain = 0.0;
+ m_blackBlueGain = 0.0;
+
+ adjustSliders();
+
+ m_monochrome->blockSignals(false);
+ m_preserveLuminosity->blockSignals(false);
+ m_channelCB->setEnabled(true);
+
+ m_histogramWidget->reset();
+
+ slotChannelChanged(RedChannelGains);
+}
+
+// Load all gains.
+void ChannelMixerDialog::slotUser3()
+{
+ KURL loadGainsFileUrl;
+ FILE *fp = 0L;
+ int currentOutputChannel;
+ bool monochrome;
+
+ loadGainsFileUrl = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Select Gimp Gains Mixer File to Load")) );
+ if( loadGainsFileUrl.isEmpty() )
+ return;
+
+ fp = fopen(TQFile::encodeName(loadGainsFileUrl.path()), "r");
+
+ if ( fp )
+ {
+ char buf1[1024];
+ char buf2[1024];
+ char buf3[1024];
+
+ buf1[0] = '\0';
+
+ fgets(buf1, 1023, fp);
+
+ fscanf (fp, "%*s %s", buf1);
+
+ // Get the current output channel in dialog.
+
+ if (strcmp (buf1, "RED") == 0)
+ currentOutputChannel = RedChannelGains;
+ else if (strcmp (buf1, "GREEN") == 0)
+ currentOutputChannel = GreenChannelGains;
+ else if (strcmp (buf1, "BLUE") == 0)
+ currentOutputChannel = BlueChannelGains;
+
+ fscanf (fp, "%*s %s", buf1); // preview flag, preserved for compatibility
+
+ fscanf (fp, "%*s %s", buf1);
+
+ if (strcmp (buf1, "true") == 0)
+ monochrome = true;
+ else
+ monochrome = false;
+
+ fscanf (fp, "%*s %s", buf1);
+
+ if (strcmp (buf1, "true") == 0)
+ m_preserveLuminosity->setChecked(true);
+ else
+ m_preserveLuminosity->setChecked(false);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_redRedGain = atof(buf1);
+ m_redGreenGain = atof(buf2);
+ m_redBlueGain = atof(buf3);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_greenRedGain = atof(buf1);
+ m_greenGreenGain = atof(buf2);
+ m_greenBlueGain = atof(buf3);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_blueRedGain = atof(buf1);
+ m_blueGreenGain = atof(buf2);
+ m_blueBlueGain = atof(buf3);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_blackRedGain = atof(buf1);
+ m_blackGreenGain = atof(buf2);
+ m_blackBlueGain = atof(buf3);
+
+ fclose(fp);
+
+ // Refresh settings.
+ m_monochrome->setChecked(monochrome);
+ m_channelCB->setCurrentItem(currentOutputChannel);
+ slotChannelChanged(currentOutputChannel);
+ }
+ else
+ {
+ KMessageBox::error(this, i18n("Cannot load settings from the Gains Mixer text file."));
+ return;
+ }
+}
+
+// Save all gains.
+void ChannelMixerDialog::slotUser2()
+{
+ KURL saveGainsFileUrl;
+ FILE *fp = 0L;
+
+ saveGainsFileUrl = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Gimp Gains Mixer File to Save")) );
+ if( saveGainsFileUrl.isEmpty() )
+ return;
+
+ fp = fopen(TQFile::encodeName(saveGainsFileUrl.path()), "w");
+
+ if ( fp )
+ {
+ const char *str = 0L;
+ char buf1[256];
+ char buf2[256];
+ char buf3[256];
+
+ switch ( m_channelCB->currentItem() )
+ {
+ case RedChannelGains:
+ str = "RED";
+ break;
+ case GreenChannelGains:
+ str = "GREEN";
+ break;
+ case BlueChannelGains:
+ str = "BLUE";
+ break;
+ default:
+ DWarning() << "Unknown Color channel gains" << endl;
+ break;
+ }
+
+ fprintf (fp, "# Channel Mixer Configuration File\n");
+
+ fprintf (fp, "CHANNEL: %s\n", str);
+ fprintf (fp, "PREVIEW: %s\n", "true"); // preserved for compatibility
+ fprintf (fp, "MONOCHROME: %s\n",
+ m_monochrome->isChecked() ? "true" : "false");
+ fprintf (fp, "PRESERVE_LUMINOSITY: %s\n",
+ m_preserveLuminosity->isChecked() ? "true" : "false");
+
+ sprintf (buf1, "%5.3f", m_redRedGain);
+ sprintf (buf2, "%5.3f", m_redGreenGain);
+ sprintf (buf3, "%5.3f", m_redBlueGain);
+ fprintf (fp, "RED: %s %s %s\n", buf1, buf2,buf3);
+
+ sprintf (buf1, "%5.3f", m_greenRedGain);
+ sprintf (buf2, "%5.3f", m_greenGreenGain);
+ sprintf (buf3, "%5.3f", m_greenBlueGain);
+ fprintf (fp, "GREEN: %s %s %s\n", buf1, buf2,buf3);
+
+ sprintf (buf1, "%5.3f", m_blueRedGain);
+ sprintf (buf2, "%5.3f", m_blueGreenGain);
+ sprintf (buf3, "%5.3f", m_blueBlueGain);
+ fprintf (fp, "BLUE: %s %s %s\n", buf1, buf2,buf3);
+
+ sprintf (buf1, "%5.3f", m_blackRedGain);
+ sprintf (buf2, "%5.3f", m_blackGreenGain);
+ sprintf (buf3, "%5.3f", m_blackBlueGain);
+ fprintf (fp, "BLACK: %s %s %s\n", buf1, buf2,buf3);
+
+ fclose (fp);
+ }
+ else
+ {
+ KMessageBox::error(this, i18n("Cannot save settings to the Gains Mixer text file."));
+ return;
+ }
+}
+
+} // NameSpace DigikamChannelMixerImagesPlugin
+
diff --git a/src/imageplugins/channelmixer/channelmixer.h b/src/imageplugins/channelmixer/channelmixer.h
new file mode 100644
index 00000000..9b4396d0
--- /dev/null
+++ b/src/imageplugins/channelmixer/channelmixer.h
@@ -0,0 +1,133 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-26
+ * Description : image channels mixer.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CHANNELMIXER_H
+#define CHANNELMIXER_H
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+
+class TQCheckBox;
+class TQComboBox;
+class TQPushButton;
+class TQHButtonGroup;
+
+class KDoubleNumInput;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamChannelMixerImagesPlugin
+{
+
+class ChannelMixerDialog : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ChannelMixerDialog(TQWidget *parent);
+ ~ChannelMixerDialog();
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+ void adjustSliders();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void slotResetCurrentChannel();
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotGainsChanged();
+ void slotMonochromeActived(bool mono);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+
+private:
+
+ enum ColorChannelGains
+ {
+ RedChannelGains=0,
+ GreenChannelGains,
+ BlueChannelGains
+ };
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+private:
+
+ uchar *m_destinationPreviewData;
+
+ double m_redRedGain;
+ double m_redGreenGain;
+ double m_redBlueGain;
+ double m_greenRedGain;
+ double m_greenGreenGain;
+ double m_greenBlueGain;
+ double m_blueRedGain;
+ double m_blueGreenGain;
+ double m_blueBlueGain;
+ double m_blackRedGain;
+ double m_blackGreenGain;
+ double m_blackBlueGain;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KDoubleNumInput *m_redGain;
+ KDoubleNumInput *m_greenGain;
+ KDoubleNumInput *m_blueGain;
+
+ TQPushButton *m_resetButton;
+
+ TQCheckBox *m_preserveLuminosity;
+ TQCheckBox *m_monochrome;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ImageWidget *m_previewWidget;
+};
+
+} // NameSpace DigikamChannelMixerImagesPlugin
+
+#endif /* CHANNELMIXER_H */
diff --git a/src/imageplugins/channelmixer/channelmixertool.cpp b/src/imageplugins/channelmixer/channelmixertool.cpp
new file mode 100644
index 00000000..c8215178
--- /dev/null
+++ b/src/imageplugins/channelmixer/channelmixertool.cpp
@@ -0,0 +1,774 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-26
+ * Description : image channels mixer.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+#include <cstring>
+#include <cstdlib>
+#include <cerrno>
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqfile.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpainter.h>
+#include <tqpushbutton.h>
+#include <tqspinbox.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <tdepopupmenu.h>
+#include <tdeselect.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "dimg.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "dimgimagefilters.h"
+#include "editortoolsettings.h"
+#include "channelmixertool.h"
+#include "channelmixertool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamChannelMixerImagesPlugin
+{
+
+ChannelMixerTool::ChannelMixerTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ m_destinationPreviewData = 0;
+
+ setName("channelmixer");
+ setToolName(i18n("Channel Mixer"));
+ setToolIcon(SmallIcon("channelmixer"));
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImageWidget("channelmixer Tool", 0,
+ i18n("<p>You can see here the image's color channels' "
+ "gains adjustments preview. You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 9, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), m_gboxSettings->plainPage());
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, m_gboxSettings->plainPage() );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ m_channelCB->setCurrentText( i18n("Red") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the color channel to mix here:<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(m_gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "mixer settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget( ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+
+ // -------------------------------------------------------------
+
+ TQLabel *redLabel = new TQLabel(i18n("Red:"), m_gboxSettings->plainPage());
+ m_redGain = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_redGain->setPrecision(0);
+ m_redGain->setRange(-200.0, 200.0, 1);
+ m_redGain->setDefaultValue(0);
+ TQWhatsThis::add( m_redGain, i18n("<p>Select the red color gain in percent for the current channel here."));
+
+ TQLabel *blueLabel = new TQLabel(i18n("Blue:"), m_gboxSettings->plainPage());
+ m_greenGain = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_greenGain->setPrecision(0);
+ m_greenGain->setRange(-200.0, 200.0, 1);
+ m_greenGain->setDefaultValue(0);
+ TQWhatsThis::add( m_greenGain, i18n("<p>Select the green color gain in percent for the current channel here."));
+
+ TQLabel *greenLabel = new TQLabel(i18n("Green:"), m_gboxSettings->plainPage());
+ m_blueGain = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_blueGain->setPrecision(0);
+ m_blueGain->setRange(-200.0, 200.0, 1);
+ m_blueGain->setDefaultValue(0);
+ TQWhatsThis::add( m_blueGain, i18n("<p>Select the blue color gain in percent for the current channel here."));
+
+ m_resetButton = new TQPushButton(i18n("&Reset"), m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_resetButton, i18n("Reset color channels' gains settings from the currently selected channel."));
+
+ // -------------------------------------------------------------
+
+ m_monochrome = new TQCheckBox( i18n("Monochrome"), m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_monochrome, i18n("<p>Enable this option if you want the image rendered in monochrome mode. "
+ "In this mode, the histogram will display only luminosity values."));
+
+ m_preserveLuminosity = new TQCheckBox( i18n("Preserve luminosity"), m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_preserveLuminosity, i18n("<p>Enable this option is you want preserve the image luminosity."));
+
+ grid->addMultiCellLayout(l1, 0, 0, 0, 4);
+ grid->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+ grid->addMultiCellWidget(redLabel, 3, 3, 0, 0);
+ grid->addMultiCellWidget(greenLabel, 4, 4, 0, 0);
+ grid->addMultiCellWidget(blueLabel, 5, 5, 0, 0);
+ grid->addMultiCellWidget(m_redGain, 3, 3, 1, 4);
+ grid->addMultiCellWidget(m_greenGain, 4, 4, 1, 4);
+ grid->addMultiCellWidget(m_blueGain, 5, 5, 1, 4);
+ grid->addMultiCellWidget(m_resetButton, 6, 6, 0, 1);
+ grid->addMultiCellWidget(m_monochrome, 7, 7, 0, 4);
+ grid->addMultiCellWidget(m_preserveLuminosity, 8, 8, 0, 4);
+ grid->setRowStretch(9, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+ // Channels and scale selection slots.
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromTarget(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // Gains settings slots.
+
+ connect(m_redGain, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotGainsChanged()));
+
+ connect(m_greenGain, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotGainsChanged()));
+
+ connect(m_blueGain, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotGainsChanged()));
+
+ connect(m_preserveLuminosity, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_monochrome, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotMonochromeActived(bool)));
+
+ // -------------------------------------------------------------
+ // Bouttons slots.
+
+ connect(m_resetButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetCurrentChannel()));
+}
+
+ChannelMixerTool::~ChannelMixerTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void ChannelMixerTool::slotResetCurrentChannel()
+{
+ switch( m_channelCB->currentItem() )
+ {
+ case GreenChannelGains: // Green.
+ m_greenRedGain = 0.0;
+ m_greenGreenGain = 1.0;
+ m_greenBlueGain = 0.0;
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_blueRedGain = 0.0;
+ m_blueGreenGain = 0.0;
+ m_blueBlueGain = 1.0;
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_blackRedGain = 1.0;
+ m_blackGreenGain = 0.0;
+ m_blackBlueGain = 0.0;
+ }
+ else
+ {
+ m_redRedGain = 1.0;
+ m_redGreenGain = 0.0;
+ m_redBlueGain = 0.0;
+ }
+ break;
+ }
+
+ adjustSliders();
+ slotEffect();
+ m_histogramWidget->reset();
+}
+
+void ChannelMixerTool::slotColorSelectedFromTarget(const DColor& color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ChannelMixerTool::slotGainsChanged()
+{
+ switch( m_channelCB->currentItem() )
+ {
+ case GreenChannelGains: // Green.
+ m_greenRedGain = m_redGain->value() / 100.0;
+ m_greenGreenGain = m_greenGain->value() / 100.0;
+ m_greenBlueGain = m_blueGain->value() / 100.0;
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_blueRedGain = m_redGain->value() / 100.0;
+ m_blueGreenGain = m_greenGain->value() / 100.0;
+ m_blueBlueGain = m_blueGain->value() / 100.0;
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_blackRedGain = m_redGain->value() / 100.0;
+ m_blackGreenGain = m_greenGain->value() / 100.0;
+ m_blackBlueGain = m_blueGain->value() / 100.0;
+ }
+ else
+ {
+ m_redRedGain = m_redGain->value() / 100.0;
+ m_redGreenGain = m_greenGain->value() / 100.0;
+ m_redBlueGain = m_blueGain->value() / 100.0;
+ }
+ break;
+ }
+
+ slotTimer();
+}
+
+void ChannelMixerTool::adjustSliders()
+{
+ m_redGain->blockSignals(true);
+ m_greenGain->blockSignals(true);
+ m_blueGain->blockSignals(true);
+
+ switch( m_channelCB->currentItem() )
+ {
+ case GreenChannelGains: // Green.
+ m_redGain->setValue(m_greenRedGain * 100.0);
+ m_greenGain->setValue(m_greenGreenGain * 100.0);
+ m_blueGain->setValue(m_greenBlueGain * 100.0);
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_redGain->setValue(m_blueRedGain * 100.0);
+ m_greenGain->setValue(m_blueGreenGain * 100.0);
+ m_blueGain->setValue(m_blueBlueGain * 100.0);
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_redGain->setValue(m_blackRedGain * 100.0);
+ m_greenGain->setValue(m_blackGreenGain * 100.0);
+ m_blueGain->setValue(m_blackBlueGain * 100.0);
+ }
+ else
+ {
+ m_redGain->setValue(m_redRedGain * 100.0);
+ m_greenGain->setValue(m_redGreenGain * 100.0);
+ m_blueGain->setValue(m_redBlueGain * 100.0);
+ }
+ break;
+ }
+
+ m_redGain->blockSignals(false);
+ m_greenGain->blockSignals(false);
+ m_blueGain->blockSignals(false);
+}
+
+void ChannelMixerTool::slotMonochromeActived(bool mono)
+{
+ m_channelCB->setEnabled(!mono);
+ m_channelCB->setCurrentItem(RedChannelGains); // Red for monochrome.
+ slotChannelChanged(RedChannelGains); // Monochrome => display luminosity histogram value.
+}
+
+void ChannelMixerTool::slotEffect()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+ DImgImageFilters filter;
+
+ if (m_monochrome->isChecked())
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_blackRedGain, m_blackGreenGain, m_blackBlueGain, // Red channel gains.
+ 0.0, 1.0, 0.0, // Green channel gains (not used).
+ 0.0, 0.0, 1.0); // Blue channel gains (not used).
+ }
+ else
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_redRedGain, m_redGreenGain, m_redBlueGain, // Red channel gains.
+ m_greenRedGain, m_greenGreenGain, m_greenBlueGain, // Green channel gains.
+ m_blueRedGain, m_blueGreenGain, m_blueBlueGain); // Blue channel gains.
+ }
+
+ iface->putPreviewImage(data);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ memcpy (m_destinationPreviewData, data, w*h*(sb ? 8 : 4));
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] data;
+}
+
+void ChannelMixerTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ DImgImageFilters filter;
+
+ if (m_monochrome->isChecked())
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_blackRedGain, m_blackGreenGain, m_blackBlueGain, // Red channel gains.
+ 0.0, 1.0, 0.0, // Green channel gains (not used).
+ 0.0, 0.0, 1.0); // Blue channel gains (not used).
+ }
+ else
+ {
+ filter.channelMixerImage(data, w, h, sb, // Image data.
+ m_preserveLuminosity->isChecked(), // Preserve luminosity.
+ m_monochrome->isChecked(), // Monochrome.
+ m_redRedGain, m_redGreenGain, m_redBlueGain, // Red channel gains.
+ m_greenRedGain, m_greenGreenGain, m_greenBlueGain, // Green channel gains.
+ m_blueRedGain, m_blueGreenGain, m_blueBlueGain); // Blue channel gains.
+ }
+
+ iface->putOriginalImage(i18n("Channel Mixer"), data);
+ delete [] data;
+ kapp->restoreOverrideCursor();
+}
+
+void ChannelMixerTool::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case GreenChannelGains: // Green.
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannelGains: // Blue.
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+
+ default: // Red or monochrome.
+ if ( m_monochrome->isChecked() )
+ {
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ }
+ else
+ {
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ }
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+ adjustSliders();
+ slotEffect();
+}
+
+void ChannelMixerTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ChannelMixerTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("channelmixer Tool");
+
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+
+ m_monochrome->setChecked(config->readBoolEntry("Monochrome", false));
+ m_preserveLuminosity->setChecked(config->readNumEntry("PreserveLuminosity", false));
+
+ m_redRedGain = config->readDoubleNumEntry("RedRedGain", 1.0);
+ m_redGreenGain = config->readDoubleNumEntry("RedGreenGain", 0.0);
+ m_redBlueGain = config->readDoubleNumEntry("RedBlueGain", 0.0);
+
+ m_greenRedGain = config->readDoubleNumEntry("GreenRedGain", 0.0);
+ m_greenGreenGain = config->readDoubleNumEntry("GreenGreenGain", 1.0);
+ m_greenBlueGain = config->readDoubleNumEntry("GreenBlueGain", 0.0);
+
+ m_blueRedGain = config->readDoubleNumEntry("BlueRedGain", 0.0);
+ m_blueGreenGain = config->readDoubleNumEntry("BlueGreenGain", 0.0);
+ m_blueBlueGain = config->readDoubleNumEntry("BlueBlueGain", 1.0);
+
+ m_blackRedGain = config->readDoubleNumEntry("BlackRedGain", 1.0);
+ m_blackGreenGain = config->readDoubleNumEntry("BlackGreenGain", 0.0);
+ m_blackBlueGain = config->readDoubleNumEntry("BlackBlueGain", 0.0);
+
+ adjustSliders();
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ChannelMixerTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("channelmixer Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ config->writeEntry("Monochrome", m_monochrome->isChecked());
+ config->writeEntry("PreserveLuminosity", m_preserveLuminosity->isChecked());
+
+ config->writeEntry("RedRedGain", m_redRedGain);
+ config->writeEntry("RedGreenGain", m_redGreenGain);
+ config->writeEntry("RedBlueGain", m_redBlueGain);
+
+ config->writeEntry("GreenRedGain", m_greenRedGain);
+ config->writeEntry("GreenGreenGain", m_greenGreenGain);
+ config->writeEntry("GreenBlueGain", m_greenBlueGain);
+
+ config->writeEntry("BlueRedGain", m_blueRedGain);
+ config->writeEntry("BlueGreenGain", m_blueGreenGain);
+ config->writeEntry("BlueBlueGain", m_blueBlueGain);
+
+ config->writeEntry("BlackRedGain", m_blackRedGain);
+ config->writeEntry("BlackGreenGain", m_blackGreenGain);
+ config->writeEntry("BlackBlueGain", m_blackBlueGain);
+
+ m_previewWidget->writeSettings();
+
+ config->sync();
+}
+
+void ChannelMixerTool::slotResetSettings()
+{
+ m_monochrome->blockSignals(true);
+ m_preserveLuminosity->blockSignals(true);
+
+ m_redRedGain = 1.0;
+ m_redGreenGain = 0.0;
+ m_redBlueGain = 0.0;
+
+ m_greenRedGain = 0.0;
+ m_greenGreenGain = 1.0;
+ m_greenBlueGain = 0.0;
+
+ m_blueRedGain = 0.0;
+ m_blueGreenGain = 0.0;
+ m_blueBlueGain = 1.0;
+
+ m_blackRedGain = 1.0;
+ m_blackGreenGain = 0.0;
+ m_blackBlueGain = 0.0;
+
+ adjustSliders();
+
+ m_monochrome->blockSignals(false);
+ m_preserveLuminosity->blockSignals(false);
+ m_channelCB->setEnabled(true);
+
+ m_histogramWidget->reset();
+
+ slotChannelChanged(RedChannelGains);
+}
+
+void ChannelMixerTool::slotLoadSettings()
+{
+ KURL loadGainsFileUrl;
+ FILE *fp = 0L;
+ int currentOutputChannel;
+ bool monochrome;
+
+ loadGainsFileUrl = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Select Gimp Gains Mixer File to Load")) );
+ if( loadGainsFileUrl.isEmpty() )
+ return;
+
+ fp = fopen(TQFile::encodeName(loadGainsFileUrl.path()), "r");
+
+ if ( fp )
+ {
+ char buf1[1024];
+ char buf2[1024];
+ char buf3[1024];
+
+ buf1[0] = '\0';
+
+ fgets(buf1, 1023, fp);
+
+ fscanf (fp, "%*s %s", buf1);
+
+ // Get the current output channel in dialog.
+
+ if (strcmp (buf1, "RED") == 0)
+ currentOutputChannel = RedChannelGains;
+ else if (strcmp (buf1, "GREEN") == 0)
+ currentOutputChannel = GreenChannelGains;
+ else if (strcmp (buf1, "BLUE") == 0)
+ currentOutputChannel = BlueChannelGains;
+
+ fscanf (fp, "%*s %s", buf1); // preview flag, preserved for compatibility
+
+ fscanf (fp, "%*s %s", buf1);
+
+ if (strcmp (buf1, "true") == 0)
+ monochrome = true;
+ else
+ monochrome = false;
+
+ fscanf (fp, "%*s %s", buf1);
+
+ if (strcmp (buf1, "true") == 0)
+ m_preserveLuminosity->setChecked(true);
+ else
+ m_preserveLuminosity->setChecked(false);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_redRedGain = atof(buf1);
+ m_redGreenGain = atof(buf2);
+ m_redBlueGain = atof(buf3);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_greenRedGain = atof(buf1);
+ m_greenGreenGain = atof(buf2);
+ m_greenBlueGain = atof(buf3);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_blueRedGain = atof(buf1);
+ m_blueGreenGain = atof(buf2);
+ m_blueBlueGain = atof(buf3);
+
+ fscanf (fp, "%*s %s %s %s", buf1, buf2, buf3);
+ m_blackRedGain = atof(buf1);
+ m_blackGreenGain = atof(buf2);
+ m_blackBlueGain = atof(buf3);
+
+ fclose(fp);
+
+ // Refresh settings.
+ m_monochrome->setChecked(monochrome);
+ m_channelCB->setCurrentItem(currentOutputChannel);
+ slotChannelChanged(currentOutputChannel);
+ }
+ else
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load settings from the Gains Mixer text file."));
+ return;
+ }
+}
+
+void ChannelMixerTool::slotSaveAsSettings()
+{
+ KURL saveGainsFileUrl;
+ FILE *fp = 0L;
+
+ saveGainsFileUrl = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Gimp Gains Mixer File to Save")) );
+ if( saveGainsFileUrl.isEmpty() )
+ return;
+
+ fp = fopen(TQFile::encodeName(saveGainsFileUrl.path()), "w");
+
+ if ( fp )
+ {
+ const char *str = 0L;
+ char buf1[256];
+ char buf2[256];
+ char buf3[256];
+
+ switch ( m_channelCB->currentItem() )
+ {
+ case RedChannelGains:
+ str = "RED";
+ break;
+ case GreenChannelGains:
+ str = "GREEN";
+ break;
+ case BlueChannelGains:
+ str = "BLUE";
+ break;
+ default:
+ DWarning() << "Unknown Color channel gains" << endl;
+ break;
+ }
+
+ fprintf (fp, "# Channel Mixer Configuration File\n");
+
+ fprintf (fp, "CHANNEL: %s\n", str);
+ fprintf (fp, "PREVIEW: %s\n", "true"); // preserved for compatibility
+ fprintf (fp, "MONOCHROME: %s\n",
+ m_monochrome->isChecked() ? "true" : "false");
+ fprintf (fp, "PRESERVE_LUMINOSITY: %s\n",
+ m_preserveLuminosity->isChecked() ? "true" : "false");
+
+ sprintf (buf1, "%5.3f", m_redRedGain);
+ sprintf (buf2, "%5.3f", m_redGreenGain);
+ sprintf (buf3, "%5.3f", m_redBlueGain);
+ fprintf (fp, "RED: %s %s %s\n", buf1, buf2,buf3);
+
+ sprintf (buf1, "%5.3f", m_greenRedGain);
+ sprintf (buf2, "%5.3f", m_greenGreenGain);
+ sprintf (buf3, "%5.3f", m_greenBlueGain);
+ fprintf (fp, "GREEN: %s %s %s\n", buf1, buf2,buf3);
+
+ sprintf (buf1, "%5.3f", m_blueRedGain);
+ sprintf (buf2, "%5.3f", m_blueGreenGain);
+ sprintf (buf3, "%5.3f", m_blueBlueGain);
+ fprintf (fp, "BLUE: %s %s %s\n", buf1, buf2,buf3);
+
+ sprintf (buf1, "%5.3f", m_blackRedGain);
+ sprintf (buf2, "%5.3f", m_blackGreenGain);
+ sprintf (buf3, "%5.3f", m_blackBlueGain);
+ fprintf (fp, "BLACK: %s %s %s\n", buf1, buf2,buf3);
+
+ fclose (fp);
+ }
+ else
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save settings to the Gains Mixer text file."));
+ return;
+ }
+}
+
+} // NameSpace DigikamChannelMixerImagesPlugin
diff --git a/src/imageplugins/channelmixer/channelmixertool.h b/src/imageplugins/channelmixer/channelmixertool.h
new file mode 100644
index 00000000..c15a30cd
--- /dev/null
+++ b/src/imageplugins/channelmixer/channelmixertool.h
@@ -0,0 +1,138 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-26
+ * Description : image channels mixer.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CHANNELMIXERTOOL_H
+#define CHANNELMIXERTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQCheckBox;
+class TQComboBox;
+class TQPushButton;
+class TQHButtonGroup;
+
+namespace KDcrawIface
+{
+class RDoubleNumInput;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamChannelMixerImagesPlugin
+{
+
+class ChannelMixerTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ ChannelMixerTool(TQObject *parent);
+ ~ChannelMixerTool();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+ void adjustSliders();
+
+private slots:
+
+ void slotLoadSettings();
+ void slotSaveAsSettings();
+ void slotResetCurrentChannel();
+ void slotEffect();
+ void slotResetSettings();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotGainsChanged();
+ void slotMonochromeActived(bool mono);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+
+private:
+
+ enum ColorChannelGains
+ {
+ RedChannelGains=0,
+ GreenChannelGains,
+ BlueChannelGains
+ };
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+private:
+
+ uchar *m_destinationPreviewData;
+
+ double m_redRedGain;
+ double m_redGreenGain;
+ double m_redBlueGain;
+ double m_greenRedGain;
+ double m_greenGreenGain;
+ double m_greenBlueGain;
+ double m_blueRedGain;
+ double m_blueGreenGain;
+ double m_blueBlueGain;
+ double m_blackRedGain;
+ double m_blackGreenGain;
+ double m_blackBlueGain;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KDcrawIface::RDoubleNumInput *m_redGain;
+ KDcrawIface::RDoubleNumInput *m_greenGain;
+ KDcrawIface::RDoubleNumInput *m_blueGain;
+
+ TQPushButton *m_resetButton;
+
+ TQCheckBox *m_preserveLuminosity;
+ TQCheckBox *m_monochrome;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamChannelMixerImagesPlugin
+
+#endif /* CHANNELMIXERTOOL_H */
diff --git a/src/imageplugins/channelmixer/digikamimageplugin_channelmixer.desktop b/src/imageplugins/channelmixer/digikamimageplugin_channelmixer.desktop
new file mode 100644
index 00000000..a855fffe
--- /dev/null
+++ b/src/imageplugins/channelmixer/digikamimageplugin_channelmixer.desktop
@@ -0,0 +1,51 @@
+[Desktop Entry]
+Name=ImagePlugin_ChannelMixer
+Name[bg]=Приставка за снимки - Смесител на канали
+Name[el]=ΠρόσθετοΕικόνας_ΑνάμειξηΚαναλιών
+Name[fi]=KanavatasapainonSäätö
+Name[hr]=Miješanje kanala
+Name[it]=PluginImmagini_MixerDeiCanali
+Name[nl]=Afbeeldingsplugin_Kanaalmixer
+Name[sr]=Миксета канала
+Name[sr@Latn]=Mikseta kanala
+Name[sv]=Insticksprogram för kanalblandning
+Name[tr]=ResimEklentisi_KanalKarıştırıcı
+Name[xx]=xxImagePlugin_ChannelMixerxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+
+Comment=Image color channels mixer plugin for digiKam
+Comment[bg]=Приставка на digiKam със смесител на цветови канали
+Comment[ca]=Connector pel digiKam per mesclar els canals de color de la imatge
+Comment[da]=Digikam plugin til at blande billedfarvekanaler
+Comment[de]=digiKam-Modul zum Mischen von Farbkanälen eines Bildes
+Comment[el]=Πρόσθετο μείκτη καναλιών χρώματος εικόνας για το digiKam
+Comment[en_GB]=Image colour channels mixer plugin for digiKam
+Comment[es]=Plugin para digiKam para la mezcla de los canales de color de imagen
+Comment[et]=DigiKami pildi värvikanalite mikseri plugin
+Comment[fa]=وصلۀ مخلوط‌کن مجراهای رنگ تصویر برای digiKam
+Comment[fi]=Värikanavien sekoitussuhteiden muokkain
+Comment[gl]=Un plugin de digiKam para misturar os canais de cores
+Comment[hr]=digiKam dodatak za miješanje kanala boja
+Comment[is]=Íforrit fyrir digiKam sem blandar litrásum mynda
+Comment[it]=Plugin di mixer dei canali dei colori per digiKam
+Comment[ja]=digiKam カラーチャンネルミキサープラグイン
+Comment[nds]=digiKam-Mischermoduul för de Bild-Klöörkanaals
+Comment[nl]=Digikam-plugin voor kanaalmixer
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਚਿੱਤਰ ਰੰਗ ਚੈਨਲ ਮਿਕਸਰ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam mieszająca kanały kolorów
+Comment[pt]=Um 'plugin' do digiKam para misturar os canais de cores
+Comment[pt_BR]=Plugin do mixer de canais de cor da imagem
+Comment[ru]=Модуль микширования цветовых каналов для digiKam
+Comment[sk]=digiKam plugin pre miešanie farebných kanálov obrázku
+Comment[sr]=Прикључак миксете канала боја за digiKam
+Comment[sr@Latn]=Priključak miksete kanala boja za digiKam
+Comment[sv]=Digikam insticksprogram för att blanda bildfärgkanaler
+Comment[tr]=digiKam için resim kanalları karıştıcı eklentisi
+Comment[uk]=Втулок змішування каналів кольорів для digiKam
+Comment[vi]=Phần bổ sung hoà kênh màu ảnh cho digiKam
+Comment[xx]=xxImage color channels mixer plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_channelmixer
+author=Caulier Gilles, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/channelmixer/digikamimageplugin_channelmixer_ui.rc b/src/imageplugins/channelmixer/digikamimageplugin_channelmixer_ui.rc
new file mode 100644
index 00000000..1a004c96
--- /dev/null
+++ b/src/imageplugins/channelmixer/digikamimageplugin_channelmixer_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_channelmixer" >
+
+ <MenuBar>
+
+ <Menu name="Color"><text>&amp;Color</text>
+ <Action name="imageplugin_channelmixer" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_channelmixer" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/channelmixer/imageplugin_channelmixer.cpp b/src/imageplugins/channelmixer/imageplugin_channelmixer.cpp
new file mode 100644
index 00000000..19fe21a9
--- /dev/null
+++ b/src/imageplugins/channelmixer/imageplugin_channelmixer.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-26
+ * Description : image channels mixer.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "channelmixertool.h"
+#include "imageplugin_channelmixer.h"
+#include "imageplugin_channelmixer.moc"
+
+using namespace DigikamChannelMixerImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_channelmixer,
+ KGenericFactory<ImagePlugin_ChannelMixer>("digikamimageplugin_channelmixer"))
+
+ImagePlugin_ChannelMixer::ImagePlugin_ChannelMixer(TQObject *parent, const char*,
+ const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_ChannelMixer")
+{
+ m_channelMixerAction = new TDEAction(i18n("Channel Mixer..."), "channelmixer",
+ CTRL+Key_H,
+ this, TQ_SLOT(slotChannelMixer()),
+ actionCollection(), "imageplugin_channelmixer");
+
+ setXMLFile("digikamimageplugin_channelmixer_ui.rc");
+
+ DDebug() << "ImagePlugin_ChannelMixer plugin loaded" << endl;
+}
+
+ImagePlugin_ChannelMixer::~ImagePlugin_ChannelMixer()
+{
+}
+
+void ImagePlugin_ChannelMixer::setEnabledActions(bool enable)
+{
+ m_channelMixerAction->setEnabled(enable);
+}
+
+void ImagePlugin_ChannelMixer::slotChannelMixer()
+{
+ ChannelMixerTool *cm = new ChannelMixerTool(this);
+ loadTool(cm);
+}
diff --git a/src/imageplugins/channelmixer/imageplugin_channelmixer.h b/src/imageplugins/channelmixer/imageplugin_channelmixer.h
new file mode 100644
index 00000000..444006ab
--- /dev/null
+++ b/src/imageplugins/channelmixer/imageplugin_channelmixer.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-26
+ * Description : image channels mixer.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_CHANNELMIXER_H
+#define IMAGEPLUGIN_CHANNELMIXER_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_ChannelMixer : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_ChannelMixer(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_ChannelMixer();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotChannelMixer();
+
+private:
+
+ TDEAction *m_channelMixerAction;
+};
+
+#endif /* IMAGEPLUGIN_CHANNELMIXER_H */
diff --git a/src/imageplugins/charcoal/Makefile.am b/src/imageplugins/charcoal/Makefile.am
new file mode 100644
index 00000000..3d851deb
--- /dev/null
+++ b/src/imageplugins/charcoal/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_charcoal_la_SOURCES = imageplugin_charcoal.cpp \
+ charcoaltool.cpp charcoal.cpp
+
+digikamimageplugin_charcoal_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_charcoal_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_charcoal.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_charcoal.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_charcoal_ui.rc
+
diff --git a/src/imageplugins/charcoal/charcoal.cpp b/src/imageplugins/charcoal/charcoal.cpp
new file mode 100644
index 00000000..a4c54a3c
--- /dev/null
+++ b/src/imageplugins/charcoal/charcoal.cpp
@@ -0,0 +1,249 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Charcoal threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Charcoal algorithm copyright 2002
+ * by Daniel M. Duley <mosfet@kde.org> from KImageEffect API.
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#define SQ2PI 2.50662827463100024161235523934010416269302368164062
+#define Epsilon 1.0e-12
+
+// C++ includes.
+
+#include <cmath>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimggaussianblur.h"
+#include "dimgimagefilters.h"
+#include "charcoal.h"
+
+namespace DigikamCharcoalImagesPlugin
+{
+
+Charcoal::Charcoal(Digikam::DImg *orgImage, TQObject *parent, double pencil, double smooth)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "Charcoal")
+{
+ m_pencil = pencil;
+ m_smooth = smooth;
+
+ initFilter();
+}
+
+void Charcoal::filterImage(void)
+{
+ if (m_orgImage.isNull())
+ {
+ DWarning() << k_funcinfo << "No image data available!"
+ << endl;
+ return;
+ }
+
+ if (m_pencil <= 0.0)
+ {
+ m_destImage = m_orgImage;
+ return;
+ }
+
+ // -- Applying Edge effect -----------------------------------------------
+
+ long i=0;
+ int kernelWidth = getOptimalKernelWidth(m_pencil, m_smooth);
+
+ if((int)m_orgImage.width() < kernelWidth)
+ {
+ DWarning() << k_funcinfo << "Image is smaller than radius!"
+ << endl;
+ return;
+ }
+
+ double *kernel = new double[kernelWidth*kernelWidth];
+
+ if(!kernel)
+ {
+ DWarning() << k_funcinfo << "Unable to allocate memory!"
+ << endl;
+ return;
+ }
+
+ for(i = 0 ; i < (kernelWidth*kernelWidth) ; i++)
+ kernel[i]=(-1.0);
+
+ kernel[i/2]=kernelWidth*kernelWidth-1.0;
+ convolveImage(kernelWidth, kernel);
+ delete [] kernel;
+
+ // -- Applying Gaussian blur effect ---------------------------------------
+
+ Digikam::DImgGaussianBlur(this, m_destImage, m_destImage, 50, 60, (int)(m_smooth/10.0));
+
+ if (m_cancel)
+ return;
+
+ // -- Applying strech contrast color effect -------------------------------
+
+ Digikam::DImgImageFilters().stretchContrastImage(m_destImage.bits(), m_destImage.width(),
+ m_destImage.height(), m_destImage.sixteenBit());
+ postProgress( 70 );
+ if (m_cancel)
+ return;
+
+ // -- Inverting image color -----------------------------------------------
+
+ Digikam::DImgImageFilters().invertImage(m_destImage.bits(), m_destImage.width(),
+ m_destImage.height(), m_destImage.sixteenBit());
+ postProgress( 80 );
+ if (m_cancel)
+ return;
+
+ // -- Convert to neutral black & white ------------------------------------
+
+ Digikam::DImgImageFilters().channelMixerImage(
+ m_destImage.bits(), m_destImage.width(),
+ m_destImage.height(), m_destImage.sixteenBit(), // Image data.
+ true, // Preserve luminosity.
+ true, // Monochrome.
+ 0.3, 0.59 , 0.11, // Red channel gains.
+ 0.0, 1.0, 0.0, // Green channel gains (not used).
+ 0.0, 0.0, 1.0); // Blue channel gains (not used).
+ postProgress( 90 );
+ if (m_cancel)
+ return;
+}
+
+bool Charcoal::convolveImage(const unsigned int order, const double *kernel)
+{
+ uint x, y;
+ int mx, my, sx, sy, mcx, mcy, progress;
+ long kernelWidth, i;
+ double red, green, blue, alpha, normalize=0.0;
+ double *k=0;
+ Digikam::DColor color;
+
+ kernelWidth = order;
+
+ if((kernelWidth % 2) == 0)
+ {
+ DWarning() << k_funcinfo << "Kernel width must be an odd number!"
+ << endl;
+ return(false);
+ }
+
+ double *normal_kernel = new double[kernelWidth*kernelWidth];
+
+ if(!normal_kernel)
+ {
+ DWarning() << k_funcinfo << "Unable to allocate memory!"
+ << endl;
+ return(false);
+ }
+
+ for(i=0 ; i < (kernelWidth*kernelWidth) ; i++)
+ normalize += kernel[i];
+
+ if(fabs(normalize) <= Epsilon)
+ normalize=1.0;
+
+ normalize = 1.0/normalize;
+
+ for(i=0 ; i < (kernelWidth*kernelWidth) ; i++)
+ normal_kernel[i] = normalize*kernel[i];
+
+ double maxClamp = m_destImage.sixteenBit() ? 16777215.0 : 65535.0;
+
+ for(y=0 ; !m_cancel && (y < m_destImage.height()) ; y++)
+ {
+ sy = y-(kernelWidth/2);
+
+ for(x=0 ; !m_cancel && (x < m_destImage.width()) ; x++)
+ {
+ k = normal_kernel;
+ red = green = blue = alpha = 0;
+ sy = y-(kernelWidth/2);
+
+ for(mcy=0 ; !m_cancel && (mcy < kernelWidth) ; mcy++, sy++)
+ {
+ my = sy < 0 ? 0 : sy > (int)m_destImage.height()-1 ? m_destImage.height()-1 : sy;
+ sx = x+(-kernelWidth/2);
+
+ for(mcx=0 ; !m_cancel && (mcx < kernelWidth) ; mcx++, sx++)
+ {
+ mx = sx < 0 ? 0 : sx > (int)m_destImage.width()-1 ? m_destImage.width()-1 : sx;
+ color = m_orgImage.getPixelColor(mx, my);
+ red += (*k)*(color.red() * 257.0);
+ green += (*k)*(color.green() * 257.0);
+ blue += (*k)*(color.blue() * 257.0);
+ alpha += (*k)*(color.alpha() * 257.0);
+ k++;
+ }
+ }
+
+ red = red < 0.0 ? 0.0 : red > maxClamp ? maxClamp : red+0.5;
+ green = green < 0.0 ? 0.0 : green > maxClamp ? maxClamp : green+0.5;
+ blue = blue < 0.0 ? 0.0 : blue > maxClamp ? maxClamp : blue+0.5;
+ alpha = alpha < 0.0 ? 0.0 : alpha > maxClamp ? maxClamp : alpha+0.5;
+
+ m_destImage.setPixelColor(x, y, Digikam::DColor((int)(red / 257UL), (int)(green / 257UL),
+ (int)(blue / 257UL), (int)(alpha / 257UL),
+ m_destImage.sixteenBit()));
+ }
+
+ progress = (int)(((double)y * 50.0) / m_destImage.height());
+ if ( progress%5 == 0 )
+ postProgress( progress );
+ }
+
+ delete [] normal_kernel;
+ return(true);
+}
+
+int Charcoal::getOptimalKernelWidth(double radius, double sigma)
+{
+ double normalize, value;
+ long kernelWidth;
+ long u;
+
+ if(radius > 0.0)
+ return((int)(2.0*ceil(radius)+1.0));
+
+ for(kernelWidth=5; ;)
+ {
+ normalize=0.0;
+
+ for(u=(-kernelWidth/2) ; u <= (kernelWidth/2) ; u++)
+ normalize += exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma);
+
+ u = kernelWidth/2;
+ value = exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma)/normalize;
+
+ if((long)(65535*value) <= 0)
+ break;
+
+ kernelWidth+=2;
+ }
+
+ return((int)kernelWidth-2);
+}
+
+} // NameSpace DigikamCharcoalImagesPlugin
diff --git a/src/imageplugins/charcoal/charcoal.h b/src/imageplugins/charcoal/charcoal.h
new file mode 100644
index 00000000..2088ccc4
--- /dev/null
+++ b/src/imageplugins/charcoal/charcoal.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Charcoal threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CHARCOAL_H
+#define CHARCOAL_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamCharcoalImagesPlugin
+{
+
+class Charcoal : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ Charcoal(Digikam::DImg *orgImage, TQObject *parent=0, double pencil=5.0, double smooth=10.0);
+ ~Charcoal(){};
+
+private:
+
+ void filterImage(void);
+ bool convolveImage(const unsigned int order, const double *kernel);
+ int getOptimalKernelWidth(double radius, double sigma);
+
+private:
+
+ double m_pencil;
+ double m_smooth;
+};
+
+} // NameSpace DigikamCharcoalImagesPlugin
+
+#endif /* CHARCOAL_H */
diff --git a/src/imageplugins/charcoal/charcoaltool.cpp b/src/imageplugins/charcoal/charcoaltool.cpp
new file mode 100644
index 00000000..75ee22df
--- /dev/null
+++ b/src/imageplugins/charcoal/charcoaltool.cpp
@@ -0,0 +1,202 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digikam image editor plugin to
+ * simulate charcoal drawing.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "charcoal.h"
+#include "charcoaltool.h"
+#include "charcoaltool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamCharcoalImagesPlugin
+{
+
+CharcoalTool::CharcoalTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("charcoal");
+ setToolName(i18n("Charcoal"));
+ setToolIcon(SmallIcon("charcoaltool"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 4, 1);
+ TQLabel *label1 = new TQLabel(i18n("Pencil size:"), m_gboxSettings->plainPage());
+
+ m_pencilInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_pencilInput->setRange(1, 100, 1);
+ m_pencilInput->setDefaultValue(5);
+ TQWhatsThis::add( m_pencilInput, i18n("<p>Set here the charcoal pencil size used to simulate the drawing."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Smooth:"), m_gboxSettings->plainPage());
+
+ m_smoothInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_smoothInput->setRange(1, 100, 1);
+ m_smoothInput->setDefaultValue(10);
+ TQWhatsThis::add( m_smoothInput, i18n("<p>This value controls the smoothing effect of the pencil "
+ "under the canvas."));
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 1);
+ grid->addMultiCellWidget(m_pencilInput, 1, 1, 0, 1);
+ grid->addMultiCellWidget(label2, 2, 2, 0, 1);
+ grid->addMultiCellWidget(m_smoothInput, 3, 3, 0, 1);
+ grid->setRowStretch(4, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "charcoal Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_pencilInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_smoothInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+CharcoalTool::~CharcoalTool()
+{
+}
+
+void CharcoalTool::renderingFinished()
+{
+ m_pencilInput->setEnabled(true);
+ m_smoothInput->setEnabled(true);
+}
+
+void CharcoalTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("charcoal Tool");
+ m_pencilInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+
+ m_pencilInput->setValue(config->readNumEntry("PencilAjustment", m_pencilInput->defaultValue()));
+ m_smoothInput->setValue(config->readNumEntry("SmoothAjustment", m_smoothInput->defaultValue()));
+
+ m_pencilInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void CharcoalTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("charcoal Tool");
+ config->writeEntry("PencilAjustment", m_pencilInput->value());
+ config->writeEntry("SmoothAjustment", m_smoothInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void CharcoalTool::slotResetSettings()
+{
+ m_pencilInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+
+ m_pencilInput->slotReset();
+ m_smoothInput->slotReset();
+
+ m_pencilInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void CharcoalTool::prepareEffect()
+{
+ m_pencilInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ double pencil = (double)m_pencilInput->value()/10.0;
+ double smooth = (double)m_smoothInput->value();
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Charcoal(&image, this, pencil, smooth)));
+}
+
+void CharcoalTool::prepareFinal()
+{
+ m_pencilInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ double pencil = (double)m_pencilInput->value()/10.0;
+ double smooth = (double)m_smoothInput->value();
+
+ ImageIface iface(0, 0);
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Charcoal(iface.getOriginalImg(), this, pencil, smooth)));
+}
+
+void CharcoalTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void CharcoalTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Charcoal"), filter()->getTargetImage().bits());
+}
+
+} // NameSpace DigikamCharcoalImagesPlugin
diff --git a/src/imageplugins/charcoal/charcoaltool.h b/src/imageplugins/charcoal/charcoaltool.h
new file mode 100644
index 00000000..5833dd06
--- /dev/null
+++ b/src/imageplugins/charcoal/charcoaltool.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digikam image editor plugin to
+ * simulate charcoal drawing.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CHARCOALTOOL_H
+#define CHARCOALTOOL_H
+
+// Local includes.
+
+#include "editortool.h"
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamCharcoalImagesPlugin
+{
+
+class CharcoalTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ CharcoalTool(TQObject* parent);
+ ~CharcoalTool();
+
+private slots:
+
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KDcrawIface::RIntNumInput *m_pencilInput;
+ KDcrawIface::RIntNumInput *m_smoothInput;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamCharcoalImagesPlugin
+
+#endif /* CHARCOALTOOL_H */
diff --git a/src/imageplugins/charcoal/digikamimageplugin_charcoal.desktop b/src/imageplugins/charcoal/digikamimageplugin_charcoal.desktop
new file mode 100644
index 00000000..e8957bbf
--- /dev/null
+++ b/src/imageplugins/charcoal/digikamimageplugin_charcoal.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=ImagePlugin_Charcoal
+Name[bg]=Приставка за снимки - Въглен
+Name[da]=Billedplugin_Kultegning
+Name[el]=ΠρόσθετοΕικόνας_Κάρβουνο
+Name[fi]=Hiilipiirros
+Name[hr]=Crtež ugljenom
+Name[it]=PluginImmagini_Carboncino
+Name[nl]=Afbeeldingsplugin_Houtskool
+Name[sr]=Угљен
+Name[sr@Latn]=Ugljen
+Name[sv]=Insticksprogram för kolteckning
+Name[tr]=ResimEklentisi_Karakalem
+Name[xx]=xxImagePlugin_Charcoalxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Charcoal drawing image effect plugin for digiKam
+Comment[bg]=Приставка на digiKam за наподобяване на рисуване с въглен върху снимки
+Comment[ca]=Connector pel digiKam d'efecte d'imatge de dibuix al carbonet
+Comment[da]=Plugin til kultegningseffekt på billeder i Digikam
+Comment[de]=digiKam-Modul zum Erzeugen eines Kohlezeichnung-Effekts
+Comment[el]=Πρόσθετο εφέ σχεδίασης με κάρβουνο για το digiKam
+Comment[es]=Plugin para digiKam con efectos de dibujo a carboncillo
+Comment[et]=DigiKami söejoonistuse pildiefektiplugin
+Comment[fa]=وصلۀ جلوۀ تصویر ترسیم Charcoal برای digiKam
+Comment[fi]=Jäljittelee hiiliviivapiirrosta
+Comment[gl]=Un plugin de digiKam para o efeito de imaxe debuxada ao carbón
+Comment[hr]=digiKam dodatak za efekt crtanja ugljenom
+Comment[is]=Íforrit fyrir digiKam sem líkir eftir viðarkolateikningu
+Comment[it]=Plugin di effetto di disegno dell'immagine con carboncino per digiKam
+Comment[ja]=digiKam 木炭画効果プラグイン
+Comment[nds]=digiKam-Moduul för Kahlteken-Effekten
+Comment[nl]=Digikam-plugin voor houtskooltekeningen
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਚਾਰਕੋਲ ਡਰਾਇੰਗ ਚਿੱਤਰ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam imitująca szkice węglem
+Comment[pt]=Um 'plugin' do digiKam para o efeito de imagem de desenho a carvão
+Comment[pt_BR]=Plugin de efeito de carvão para o digiKam
+Comment[ru]=Модуль изображения картинки углем для digiKam
+Comment[sk]=digiKam plugin pre efekt kreslenia kriedou
+Comment[sr]=Прикључак ефекта цртежа угљеном за digiKam
+Comment[sr@Latn]=Priključak efekta crteža ugljenom za digiKam
+Comment[sv]=Digikam insticksprogram för kolteckningsbildeffekt
+Comment[tr]=digiKam için karakalem resim etkisi eklentisi
+Comment[uk]=Втулок створення ефекту малювання вугільним олівцем для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng vẽ ảnh than gỗ cho digiKam
+Comment[xx]=xxCharcoal drawing image effect plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_charcoal
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/charcoal/digikamimageplugin_charcoal_ui.rc b/src/imageplugins/charcoal/digikamimageplugin_charcoal_ui.rc
new file mode 100644
index 00000000..92ab4752
--- /dev/null
+++ b/src/imageplugins/charcoal/digikamimageplugin_charcoal_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="6" name="digikamimageplugin_charcoal" >
+
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_charcoal" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_charcoal" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/charcoal/imageeffect_charcoal.cpp b/src/imageplugins/charcoal/imageeffect_charcoal.cpp
new file mode 100644
index 00000000..67a0269d
--- /dev/null
+++ b/src/imageplugins/charcoal/imageeffect_charcoal.cpp
@@ -0,0 +1,193 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digikam image editor plugin to
+ * simulate charcoal drawing.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <knuminput.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "charcoal.h"
+#include "imageeffect_charcoal.h"
+#include "imageeffect_charcoal.moc"
+
+namespace DigikamCharcoalImagesPlugin
+{
+
+ImageEffect_Charcoal::ImageEffect_Charcoal(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Charcoal Drawing"),
+ "charcoal", false, false, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Charcoal Drawing"),
+ digikam_version,
+ I18N_NOOP("A digiKam charcoal drawing image effect plugin."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 3, 1, 0, spacingHint());
+ TQLabel *label1 = new TQLabel(i18n("Pencil size:"), gboxSettings);
+
+ m_pencilInput = new KIntNumInput(gboxSettings);
+ m_pencilInput->setRange(1, 100, 1, true);
+ m_pencilInput->setValue(5);
+ TQWhatsThis::add( m_pencilInput, i18n("<p>Set here the charcoal pencil size used to simulate the drawing."));
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_pencilInput, 1, 1, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Smooth:"), gboxSettings);
+
+ m_smoothInput = new KIntNumInput(gboxSettings);
+ m_smoothInput->setRange(1, 100, 1, true);
+ m_smoothInput->setValue(10);
+ TQWhatsThis::add( m_smoothInput, i18n("<p>This value controls the smoothing effect of the pencil "
+ "under the canvas."));
+
+ gridSettings->addMultiCellWidget(label2, 2, 2, 0, 1);
+ gridSettings->addMultiCellWidget(m_smoothInput, 3, 3, 0, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_pencilInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_smoothInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_Charcoal::~ImageEffect_Charcoal()
+{
+}
+
+void ImageEffect_Charcoal::renderingFinished()
+{
+ m_pencilInput->setEnabled(true);
+ m_smoothInput->setEnabled(true);
+}
+
+void ImageEffect_Charcoal::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("charcoal Tool Dialog");
+ m_pencilInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+ m_pencilInput->setValue(config->readNumEntry("PencilAjustment", 5));
+ m_smoothInput->setValue(config->readNumEntry("SmoothAjustment", 10));
+ m_pencilInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void ImageEffect_Charcoal::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("charcoal Tool Dialog");
+ config->writeEntry("PencilAjustment", m_pencilInput->value());
+ config->writeEntry("SmoothAjustment", m_smoothInput->value());
+ config->sync();
+}
+
+void ImageEffect_Charcoal::resetValues()
+{
+ m_pencilInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+ m_pencilInput->setValue(5);
+ m_smoothInput->setValue(10);
+ m_pencilInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void ImageEffect_Charcoal::prepareEffect()
+{
+ m_pencilInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ double pencil = (double)m_pencilInput->value()/10.0;
+ double smooth = (double)m_smoothInput->value();
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new Charcoal(&image, this, pencil, smooth));
+}
+
+void ImageEffect_Charcoal::prepareFinal()
+{
+ m_pencilInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ double pencil = (double)m_pencilInput->value()/10.0;
+ double smooth = (double)m_smoothInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new Charcoal(iface.getOriginalImg(),
+ this, pencil, smooth));
+}
+
+void ImageEffect_Charcoal::putPreviewData(void)
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_Charcoal::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Charcoal"), m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamCharcoalImagesPlugin
+
diff --git a/src/imageplugins/charcoal/imageeffect_charcoal.h b/src/imageplugins/charcoal/imageeffect_charcoal.h
new file mode 100644
index 00000000..31916ecc
--- /dev/null
+++ b/src/imageplugins/charcoal/imageeffect_charcoal.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digikam image editor plugin to
+ * simulate charcoal drawing.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_CHARCOAL_H
+#define IMAGEEFFECT_CHARCOAL_H
+
+// Local includes.
+
+#include "ctrlpaneldlg.h"
+
+class KIntNumInput;
+
+namespace DigikamCharcoalImagesPlugin
+{
+
+class ImageEffect_Charcoal : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Charcoal(TQWidget* parent);
+ ~ImageEffect_Charcoal();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KIntNumInput *m_pencilInput;
+ KIntNumInput *m_smoothInput;
+};
+
+} // NameSpace DigikamCharcoalImagesPlugin
+
+#endif /* IMAGEEFFECT_CHARCOAL_H */
diff --git a/src/imageplugins/charcoal/imageplugin_charcoal.cpp b/src/imageplugins/charcoal/imageplugin_charcoal.cpp
new file mode 100644
index 00000000..ae28e7ca
--- /dev/null
+++ b/src/imageplugins/charcoal/imageplugin_charcoal.cpp
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digikam image editor plugin to
+ * simulate charcoal drawing.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "charcoaltool.h"
+#include "imageplugin_charcoal.h"
+#include "imageplugin_charcoal.moc"
+
+using namespace DigikamCharcoalImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_charcoal,
+ KGenericFactory<ImagePlugin_Charcoal>("digikamimageplugin_charcoal"));
+
+ImagePlugin_Charcoal::ImagePlugin_Charcoal(TQObject *parent, const char*,
+ const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_Charcoal")
+{
+ m_charcoalAction = new TDEAction(i18n("Charcoal Drawing..."), "charcoaltool", 0,
+ this, TQ_SLOT(slotCharcoal()),
+ actionCollection(), "imageplugin_charcoal");
+
+ setXMLFile( "digikamimageplugin_charcoal_ui.rc" );
+
+ DDebug() << "ImagePlugin_Charcoal plugin loaded" << endl;
+}
+
+ImagePlugin_Charcoal::~ImagePlugin_Charcoal()
+{
+}
+
+void ImagePlugin_Charcoal::setEnabledActions(bool enable)
+{
+ m_charcoalAction->setEnabled(enable);
+}
+
+void ImagePlugin_Charcoal::slotCharcoal()
+{
+ CharcoalTool *tool = new CharcoalTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/charcoal/imageplugin_charcoal.h b/src/imageplugins/charcoal/imageplugin_charcoal.h
new file mode 100644
index 00000000..c642087a
--- /dev/null
+++ b/src/imageplugins/charcoal/imageplugin_charcoal.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digikam image editor plugin to
+ * simulate charcoal drawing.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_CHARCOAL_H
+#define IMAGEPLUGIN_CHARCOAL_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Charcoal : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Charcoal(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_Charcoal();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotCharcoal();
+
+private:
+
+ TDEAction *m_charcoalAction;
+};
+
+#endif /* IMAGEPLUGIN_CHARCOAL_H */
diff --git a/src/imageplugins/colorfx/Makefile.am b/src/imageplugins/colorfx/Makefile.am
new file mode 100644
index 00000000..a0d2e4fa
--- /dev/null
+++ b/src/imageplugins/colorfx/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_colorfx_la_SOURCES = imageplugin_colorfx.cpp \
+ colorfxtool.cpp
+
+digikamimageplugin_colorfx_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_colorfx_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_colorfx.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_colorfx.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_colorfx_ui.rc
+
diff --git a/src/imageplugins/colorfx/colorfxtool.cpp b/src/imageplugins/colorfx/colorfxtool.cpp
new file mode 100644
index 00000000..dd16e7cf
--- /dev/null
+++ b/src/imageplugins/colorfx/colorfxtool.cpp
@@ -0,0 +1,699 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : a digiKam image plugin for to apply a color
+ * effect to an image.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcombobox.h>
+#include <tqframe.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "colorgradientwidget.h"
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "imagecurves.h"
+#include "imagehistogram.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "colorfxtool.h"
+#include "colorfxtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamColorFXImagesPlugin
+{
+
+ColorFXTool::ColorFXTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("coloreffects");
+ setToolName(i18n("Color Effects"));
+ setToolIcon(SmallIcon("colorfx"));
+
+ m_destinationPreviewData = 0;
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImageWidget("coloreffects Tool", 0,
+ i18n("<p>This is the color effects preview"));
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ EditorToolSettings *gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings->plainPage(), 9, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings->plainPage());
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, gboxSettings->plainPage());
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap(TQPixmap(directory + "histogram-lin.png"));
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget( ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ // -------------------------------------------------------------
+
+ m_effectTypeLabel = new TQLabel(i18n("Type:"), gboxSettings->plainPage());
+
+ m_effectType = new RComboBox(gboxSettings->plainPage());
+ m_effectType->insertItem(i18n("Solarize"));
+ m_effectType->insertItem(i18n("Vivid"));
+ m_effectType->insertItem(i18n("Neon"));
+ m_effectType->insertItem(i18n("Find Edges"));
+ m_effectType->setDefaultItem(Solarize);
+ TQWhatsThis::add( m_effectType, i18n("<p>Select the effect type to apply to the image here.<p>"
+ "<b>Solarize</b>: simulates solarization of photograph.<p>"
+ "<b>Vivid</b>: simulates the Velvia(tm) slide film colors.<p>"
+ "<b>Neon</b>: coloring the edges in a photograph to "
+ "reproduce a fluorescent light effect.<p>"
+ "<b>Find Edges</b>: detects the edges in a photograph "
+ "and their strength."
+ ));
+
+ m_levelLabel = new TQLabel(i18n("Level:"), gboxSettings->plainPage());
+ m_levelInput = new RIntNumInput(gboxSettings->plainPage());
+ m_levelInput->setRange(0, 100, 1);
+ m_levelInput->setDefaultValue(0);
+ TQWhatsThis::add( m_levelInput, i18n("<p>Set here the level of the effect."));
+
+ m_iterationLabel = new TQLabel(i18n("Iteration:"), gboxSettings->plainPage());
+ m_iterationInput = new RIntNumInput(gboxSettings->plainPage());
+ m_iterationInput->setRange(0, 100, 1);
+ m_iterationInput->setDefaultValue(0);
+ TQWhatsThis::add( m_iterationInput, i18n("<p>This value controls the number of iterations "
+ "to use with the Neon and Find Edges effects."));
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+ gridSettings->addMultiCellWidget(m_effectTypeLabel, 3, 3, 0, 4);
+ gridSettings->addMultiCellWidget(m_effectType, 4, 4, 0, 4);
+ gridSettings->addMultiCellWidget(m_levelLabel, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_levelInput, 6, 6, 0, 4);
+ gridSettings->addMultiCellWidget(m_iterationLabel, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_iterationInput, 8, 8, 0, 4);
+ gridSettings->setRowStretch(9, 10);
+
+ setToolSettings(gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const DColor & )));
+
+ connect(m_levelInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_iterationInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_effectType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffectTypeChanged(int)));
+}
+
+ColorFXTool::~ColorFXTool()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void ColorFXTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("coloreffect Tool");
+ m_effectType->setCurrentItem(config->readNumEntry("EffectType", m_effectType->defaultItem()));
+ m_levelInput->setValue(config->readNumEntry("LevelAjustment", m_levelInput->defaultValue()));
+ m_iterationInput->setValue(config->readNumEntry("IterationAjustment", m_iterationInput->defaultValue()));
+ slotEffectTypeChanged(m_effectType->currentItem()); //check for enable/disable of iteration
+
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ColorFXTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("coloreffect Tool");
+ config->writeEntry("EffectType", m_effectType->currentItem());
+ config->writeEntry("LevelAjustment", m_levelInput->value());
+ config->writeEntry("IterationAjustment", m_iterationInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void ColorFXTool::slotResetSettings()
+{
+ m_levelInput->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+ m_effectType->blockSignals(true);
+
+ m_levelInput->slotReset();
+ m_iterationInput->slotReset();
+ m_effectType->slotReset();
+
+ m_levelInput->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+ m_effectType->blockSignals(false);
+
+ slotEffect();
+}
+
+void ColorFXTool::slotChannelChanged(int channel)
+{
+ switch (channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("red"));
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("green"));
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("blue"));
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ColorFXTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ColorFXTool::slotColorSelectedFromTarget(const DColor &color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ColorFXTool::slotEffectTypeChanged(int type)
+{
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+
+ m_levelInput->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+ m_levelInput->setRange(0, 100, 1);
+ m_levelInput->setValue(25);
+
+ switch (type)
+ {
+ case Solarize:
+ m_levelInput->setRange(0, 100, 1);
+ m_levelInput->setValue(0);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+ break;
+
+ case Vivid:
+ m_levelInput->setRange(0, 50, 1);
+ m_levelInput->setValue(5);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+ break;
+
+ case Neon:
+ case FindEdges:
+ m_levelInput->setRange(0, 5, 1);
+ m_levelInput->setValue(3);
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+ m_iterationInput->setRange(0, 5, 1);
+ m_iterationInput->setValue(2);
+ break;
+ }
+
+ m_levelInput->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ColorFXTool::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ colorEffect(m_destinationPreviewData, w, h, sb);
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void ColorFXTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ colorEffect(data, w, h, sb);
+ TQString name;
+
+ switch (m_effectType->currentItem())
+ {
+ case Solarize:
+ name = i18n("ColorFX");
+ break;
+
+ case Vivid:
+ name = i18n("Vivid");
+ break;
+
+ case Neon:
+ name = i18n("Neon");
+ break;
+
+ case FindEdges:
+ name = i18n("Find Edges");
+ break;
+ }
+
+ iface->putOriginalImage(name, data);
+ delete[] data;
+ }
+
+ kapp->restoreOverrideCursor();
+}
+
+void ColorFXTool::colorEffect(uchar *data, int w, int h, bool sb)
+{
+ switch (m_effectType->currentItem())
+ {
+ case Solarize:
+ solarize(m_levelInput->value(), data, w, h, sb);
+ break;
+
+ case Vivid:
+ vivid(m_levelInput->value(), data, w, h, sb);
+ break;
+
+ case Neon:
+ neon(data, w, h, sb, m_levelInput->value(), m_iterationInput->value());
+ break;
+
+ case FindEdges:
+ findEdges(data, w, h, sb, m_levelInput->value(), m_iterationInput->value());
+ break;
+ }
+}
+
+void ColorFXTool::solarize(int factor, uchar *data, int w, int h, bool sb)
+{
+ bool stretch = true;
+
+ if (!sb) // 8 bits image.
+ {
+ uint threshold = (uint)((100-factor)*(255+1)/100);
+ threshold = TQMAX(1, threshold);
+ uchar *ptr = data;
+ uchar a, r, g, b;
+
+ for (int x=0 ; x < w*h ; x++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ a = ptr[3];
+
+ if (stretch)
+ {
+ r = (r > threshold) ? (255-r)*255/(255-threshold) : r*255/threshold;
+ g = (g > threshold) ? (255-g)*255/(255-threshold) : g*255/threshold;
+ b = (b > threshold) ? (255-b)*255/(255-threshold) : b*255/threshold;
+ }
+ else
+ {
+ if (r > threshold)
+ r = (255-r);
+ if (g > threshold)
+ g = (255-g);
+ if (b > threshold)
+ b = (255-b);
+ }
+
+ ptr[0] = b;
+ ptr[1] = g;
+ ptr[2] = r;
+ ptr[3] = a;
+
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ uint threshold = (uint)((100-factor)*(65535+1)/100);
+ threshold = TQMAX(1, threshold);
+ unsigned short *ptr = (unsigned short *)data;
+ unsigned short a, r, g, b;
+
+ for (int x=0 ; x < w*h ; x++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ a = ptr[3];
+
+ if (stretch)
+ {
+ r = (r > threshold) ? (65535-r)*65535/(65535-threshold) : r*65535/threshold;
+ g = (g > threshold) ? (65535-g)*65535/(65535-threshold) : g*65535/threshold;
+ b = (b > threshold) ? (65535-b)*65535/(65535-threshold) : b*65535/threshold;
+ }
+ else
+ {
+ if (r > threshold)
+ r = (65535-r);
+ if (g > threshold)
+ g = (65535-g);
+ if (b > threshold)
+ b = (65535-b);
+ }
+
+ ptr[0] = b;
+ ptr[1] = g;
+ ptr[2] = r;
+ ptr[3] = a;
+
+ ptr += 4;
+ }
+ }
+}
+
+void ColorFXTool::vivid(int factor, uchar *data, int w, int h, bool sb)
+{
+ float amount = factor/100.0;
+
+ DImgImageFilters filter;
+
+ // Apply Channel Mixer adjustments.
+
+ filter.channelMixerImage(
+ data, w, h, sb, // Image data.
+ true, // Preserve Luminosity
+ false, // Disable Black & White mode.
+ 1.0 + amount + amount, (-1.0)*amount, (-1.0)*amount, // Red Gains.
+ (-1.0)*amount, 1.0 + amount + amount, (-1.0)*amount, // Green Gains.
+ (-1.0)*amount, (-1.0)*amount, 1.0 + amount + amount // Blue Gains.
+ );
+
+ // Allocate the destination image data.
+
+ uchar *dest = new uchar[w*h*(sb ? 8 : 4)];
+
+ // And now apply the curve correction.
+
+ ImageCurves Curves(sb);
+
+ if (!sb) // 8 bits image.
+ {
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 5, TQPoint(63, 60));
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 10, TQPoint(191, 194));
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 16, TQPoint(255, 255));
+ }
+ else // 16 bits image.
+ {
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 5, TQPoint(16128, 15360));
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 10, TQPoint(48896, 49664));
+ Curves.setCurvePoint(ImageHistogram::ValueChannel, 16, TQPoint(65535, 65535));
+ }
+
+ Curves.curvesCalculateCurve(ImageHistogram::AlphaChannel); // Calculate cure on all channels.
+ Curves.curvesLutSetup(ImageHistogram::AlphaChannel); // ... and apply it on all channels
+ Curves.curvesLutProcess(data, dest, w, h);
+
+ memcpy(data, dest, w*h*(sb ? 8 : 4));
+ delete [] dest;
+}
+
+/* Function to apply the Neon effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Intensity => Intensity value
+ * BW => Border Width
+ *
+ * Theory => Wow, this is a great effect, you've never seen a Neon effect
+ * like this on PSC. Is very similar to Growing Edges (photoshop)
+ * Some pictures will be very interesting
+ */
+void ColorFXTool::neon(uchar *data, int w, int h, bool sb, int Intensity, int BW)
+{
+ neonFindEdges(data, w, h, sb, true, Intensity, BW);
+}
+
+/* Function to apply the Find Edges effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Intensity => Intensity value
+ * BW => Border Width
+ *
+ * Theory => Wow, another Photoshop filter (FindEdges). Do you understand
+ * Neon effect ? This is the same engine, but is inversed with
+ * 255 - color.
+ */
+void ColorFXTool::findEdges(uchar *data, int w, int h, bool sb, int Intensity, int BW)
+{
+ neonFindEdges(data, w, h, sb, false, Intensity, BW);
+}
+
+// Implementation of neon and FindEdges. They share 99% of their code.
+void ColorFXTool::neonFindEdges(uchar *data, int w, int h, bool sb, bool neon, int Intensity, int BW)
+{
+ int Width = w;
+ int Height = h;
+ bool sixteenBit = sb;
+ int bytesDepth = sb ? 8 : 4;
+ uchar* pResBits = new uchar[Width*Height*bytesDepth];
+
+ Intensity = (Intensity < 0) ? 0 : (Intensity > 5) ? 5 : Intensity;
+ BW = (BW < 1) ? 1 : (BW > 5) ? 5 : BW;
+
+ uchar *ptr, *ptr1, *ptr2;
+
+ // these must be uint, we need full 2^32 range for 16 bit
+ uint color_1, color_2, colorPoint, colorOther1, colorOther2;
+
+ // initial copy
+ memcpy (pResBits, data, Width*Height*bytesDepth);
+
+ double intensityFactor = sqrt( 1 << Intensity );
+
+ for (int h = 0; h < Height; h++)
+ {
+ for (int w = 0; w < Width; w++)
+ {
+ ptr = pResBits + getOffset(Width, w, h, bytesDepth);
+ ptr1 = pResBits + getOffset(Width, w + Lim_Max (w, BW, Width), h, bytesDepth);
+ ptr2 = pResBits + getOffset(Width, w, h + Lim_Max (h, BW, Height), bytesDepth);
+
+ if (sixteenBit)
+ {
+ for (int k = 0; k <= 2; k++)
+ {
+ colorPoint = ((unsigned short *)ptr)[k];
+ colorOther1 = ((unsigned short *)ptr1)[k];
+ colorOther2 = ((unsigned short *)ptr2)[k];
+ color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
+ color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
+
+ // old algorithm was
+ // sqrt ((color_1 + color_2) << Intensity)
+ // As (a << I) = a * (1 << I) = a * (2^I), and we can split the square root
+
+ if (neon)
+ ((unsigned short *)ptr)[k] = CLAMP065535 ((int)( sqrt((double)color_1 + color_2) * intensityFactor ));
+ else
+ ((unsigned short *)ptr)[k] = 65535 - CLAMP065535 ((int)( sqrt((double)color_1 + color_2) * intensityFactor ));
+ }
+ }
+ else
+ {
+ for (int k = 0; k <= 2; k++)
+ {
+ colorPoint = ptr[k];
+ colorOther1 = ptr1[k];
+ colorOther2 = ptr2[k];
+ color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
+ color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
+
+ if (neon)
+ ptr[k] = CLAMP0255 ((int)( sqrt((double)color_1 + color_2) * intensityFactor ));
+ else
+ ptr[k] = 255 - CLAMP0255 ((int)( sqrt((double)color_1 + color_2) * intensityFactor ));
+ }
+ }
+ }
+ }
+
+ memcpy (data, pResBits, Width*Height*bytesDepth);
+ delete [] pResBits;
+}
+
+int ColorFXTool::getOffset(int Width, int X, int Y, int bytesDepth)
+{
+ return (Y * Width * bytesDepth) + (X * bytesDepth);
+}
+
+inline int ColorFXTool::Lim_Max(int Now, int Up, int Max)
+{
+ --Max;
+ while (Now > Max - Up) --Up;
+ return (Up);
+}
+
+} // NameSpace DigikamColorFXImagesPlugin
+
diff --git a/src/imageplugins/colorfx/colorfxtool.h b/src/imageplugins/colorfx/colorfxtool.h
new file mode 100644
index 00000000..6040f53e
--- /dev/null
+++ b/src/imageplugins/colorfx/colorfxtool.h
@@ -0,0 +1,136 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : a digiKam image plugin for to apply a color
+ * effect to an image.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef COLORFXTOOL_H
+#define COLORFXTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQHButtonGroup;
+class TQComboBox;
+class TQLabel;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class ImageWidget;
+class ColorGradientWidget;
+class HistogramWidget;
+class DColor;
+}
+
+namespace DigikamColorFXImagesPlugin
+{
+
+class ColorFXTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ ColorFXTool(TQObject *parent);
+ ~ColorFXTool();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+ void colorEffect(uchar *data, int w, int h, bool sb);
+ void solarize(int factor, uchar *data, int w, int h, bool sb);
+ void vivid(int factor, uchar *data, int w, int h, bool sb);
+ void neon(uchar *data, int w, int h, bool sb, int Intensity, int BW);
+ void findEdges(uchar *data, int w, int h, bool sb, int Intensity, int BW);
+ void neonFindEdges(uchar *data, int w, int h, bool sb, bool neon, int Intensity, int BW);
+
+ inline int getOffset(int Width, int X, int Y, int bytesDepth);
+ inline int Lim_Max(int Now, int Up, int Max);
+
+private slots:
+
+ void slotEffectTypeChanged(int type);
+ void slotEffect();
+ void slotResetSettings();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum ColorFXTypes
+ {
+ Solarize=0,
+ Vivid,
+ Neon,
+ FindEdges
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQLabel *m_effectTypeLabel;
+ TQLabel *m_levelLabel;
+ TQLabel *m_iterationLabel;
+
+ KDcrawIface::RIntNumInput *m_levelInput;
+ KDcrawIface::RIntNumInput *m_iterationInput;
+
+ KDcrawIface::RComboBox *m_effectType;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+};
+
+} // NameSpace DigikamColorFXImagesPlugin
+
+#endif /* COLORFXTOOL_H */
diff --git a/src/imageplugins/colorfx/digikamimageplugin_colorfx.desktop b/src/imageplugins/colorfx/digikamimageplugin_colorfx.desktop
new file mode 100644
index 00000000..7350f6d9
--- /dev/null
+++ b/src/imageplugins/colorfx/digikamimageplugin_colorfx.desktop
@@ -0,0 +1,40 @@
+[Desktop Entry]
+Name=ImagePlugin_ColorFx
+Name[fi]=Väriefektit
+Name[it]=PluginImmagini_EffettiDiColore
+Name[nl]=Afbeeldingsplugin_Kleureffecten
+Name[sr]=Ефекти боје
+Name[sr@Latn]=Efekti boje
+Name[sv]=Insticksprogram med färgeffekter
+Name[tr]=ResimEklentisi_RenkFx
+Name[xx]=xxImagePlugin_ColorFxxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Color special effects plugin for digiKam
+Comment[ca]=Connector pel digiKam d'efectes especials de color
+Comment[da]=Digikam plugin med specialeffekter for farve
+Comment[de]=digiKam-Modul zum Erzeugen von speziellen Farbeffekten
+Comment[el]=Πρόσθετο ειδικών εφέ χρώματος για το digiKam
+Comment[es]=Plugin para digiKam con efectos especiales de color
+Comment[et]=DigiKami värvieriefektide plugin
+Comment[fi]=Erikoisia väritehosteita
+Comment[is]=Íforrit fyrir digiKam sem litmeðhöndlar sérstaklega myndir
+Comment[it]=Plugin degli effetti speciali dei colori per digiKam
+Comment[ja]=digiKam 色特殊効果プラグイン
+Comment[nds]=digiKam-Moduul för Klöör-Effekten
+Comment[nl]=Digikam-plugin voor kleureffecten
+Comment[pl]=Wtyczka specjalnych efektów koloru do programu digiKam
+Comment[pt]=Um 'plugin' do digiKam para efeitos especiais de cores
+Comment[pt_BR]=Plugin de efeitos especiais de Cor
+Comment[sk]=digiKam plugin pre špeciálne farebné efekty
+Comment[sr]=Прикључак посебних ефеката боје за digiKam
+Comment[sr@Latn]=Priključak posebnih efekata boje za digiKam
+Comment[sv]=Digikam insticksprogram med specialeffekter för färg
+Comment[tr]=digiKam için özel renk eklentisi
+Comment[uk]=Втулок спецефектів кольорів для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng màu sắc cho digiKam
+Comment[xx]=xxColor special effects plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_colorfx
+author=Caulier Gilles, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/colorfx/digikamimageplugin_colorfx_ui.rc b/src/imageplugins/colorfx/digikamimageplugin_colorfx_ui.rc
new file mode 100644
index 00000000..443a00bd
--- /dev/null
+++ b/src/imageplugins/colorfx/digikamimageplugin_colorfx_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="7" name="digikamimageplugin_colorfx" >
+
+ <MenuBar>
+
+ <Menu name="Color" ><text>&amp;Color</text>
+ <Action name="imageplugin_colorfx" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_colorfx" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/colorfx/imageeffect_colorfx.cpp b/src/imageplugins/colorfx/imageeffect_colorfx.cpp
new file mode 100644
index 00000000..a4ab6fe7
--- /dev/null
+++ b/src/imageplugins/colorfx/imageeffect_colorfx.cpp
@@ -0,0 +1,690 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : a digiKam image plugin for to apply a color
+ * effect to an image.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqcombobox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqvbox.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <knuminput.h>
+#include <tdelocale.h>
+#include <kcursor.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagecurves.h"
+#include "imagehistogram.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "imageeffect_colorfx.h"
+#include "imageeffect_colorfx.moc"
+
+namespace DigikamColorFXImagesPlugin
+{
+
+ImageEffect_ColorFX::ImageEffect_ColorFX(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent,
+ i18n("Apply Color Special Effects to Photograph"),
+ "coloreffect", false, false)
+{
+ m_destinationPreviewData = 0;
+
+ // About data and help button.
+
+ TDEAboutData *about = new TDEAboutData("digikam",
+ I18N_NOOP("Color Effects"),
+ digikam_version,
+ I18N_NOOP("A digiKam plugin to apply special color effects to an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2005, Renchi Raju\n(c) 2006-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Renchi Raju", I18N_NOOP("Original Author"),
+ "renchi@pooh.tam.uiuc.edu");
+
+ about->addAuthor("Caulier Gilles", I18N_NOOP("Maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new Digikam::ImageWidget("coloreffect Tool Dialog", plainPage(),
+ i18n("<p>This is the color effect preview"));
+
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 9, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ m_effectTypeLabel = new TQLabel(i18n("Type:"), gboxSettings);
+
+ m_effectType = new TQComboBox( false, gboxSettings );
+ m_effectType->insertItem( i18n("Solarize") );
+ m_effectType->insertItem( i18n("Vivid") );
+ m_effectType->insertItem( i18n("Neon") );
+ m_effectType->insertItem( i18n("Find Edges") );
+ TQWhatsThis::add( m_effectType, i18n("<p>Select the effect type to apply to the image here.<p>"
+ "<b>Solarize</b>: simulates solarization of photograph.<p>"
+ "<b>Vivid</b>: simulates the Velvia(tm) slide film colors.<p>"
+ "<b>Neon</b>: coloring the edges in a photograph to "
+ "reproduce a fluorescent light effect.<p>"
+ "<b>Find Edges</b>: detects the edges in a photograph "
+ "and their strength."
+ ));
+ gridSettings->addMultiCellWidget(m_effectTypeLabel, 3, 3, 0, 4);
+ gridSettings->addMultiCellWidget(m_effectType, 4, 4, 0, 4);
+
+ m_levelLabel = new TQLabel(i18n("Level:"), gboxSettings);
+ m_levelInput = new KIntNumInput(gboxSettings);
+ m_levelInput->setRange(0, 100, 1, true);
+ TQWhatsThis::add( m_levelInput, i18n("<p>Set here the level of the effect."));
+
+ gridSettings->addMultiCellWidget(m_levelLabel, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_levelInput, 6, 6, 0, 4);
+
+ m_iterationLabel = new TQLabel(i18n("Iteration:"), gboxSettings);
+ m_iterationInput = new KIntNumInput(gboxSettings);
+ m_iterationInput->setRange(0, 100, 1, true);
+ TQWhatsThis::add( m_iterationInput, i18n("<p>This value controls the number of iterations "
+ "to use with the Neon and Find Edges effects."));
+
+ gridSettings->addMultiCellWidget(m_iterationLabel, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_iterationInput, 8, 8, 0, 4);
+
+ gridSettings->setRowStretch(9, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_levelInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_iterationInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_effectType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffectTypeChanged(int)));
+}
+
+ImageEffect_ColorFX::~ImageEffect_ColorFX()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_previewWidget;
+}
+
+void ImageEffect_ColorFX::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("coloreffect Tool Dialog");
+ m_effectType->setCurrentItem(config->readNumEntry("EffectType", ColorFX));
+ m_levelInput->setValue(config->readNumEntry("LevelAjustment", 0));
+ m_iterationInput->setValue(config->readNumEntry("IterationAjustment", 3));
+ slotEffectTypeChanged(m_effectType->currentItem()); //check for enable/disable of iteration
+}
+
+void ImageEffect_ColorFX::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("coloreffect Tool Dialog");
+ config->writeEntry("EffectType", m_effectType->currentItem());
+ config->writeEntry("LevelAjustment", m_levelInput->value());
+ config->writeEntry("IterationAjustment", m_iterationInput->value());
+ config->sync();
+}
+
+void ImageEffect_ColorFX::resetValues()
+{
+ m_levelInput->setValue(0);
+}
+
+void ImageEffect_ColorFX::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_ColorFX::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_ColorFX::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_ColorFX::slotEffectTypeChanged(int type)
+{
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+
+ m_levelInput->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+ m_levelInput->setRange(0, 100, 1, true);
+ m_levelInput->setValue(25);
+
+ switch (type)
+ {
+ case ColorFX:
+ m_levelInput->setRange(0, 100, 1, true);
+ m_levelInput->setValue(0);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+ break;
+
+ case Vivid:
+ m_levelInput->setRange(0, 50, 1, true);
+ m_levelInput->setValue(5);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+ break;
+
+ case Neon:
+ case FindEdges:
+ m_levelInput->setRange(0, 5, 1, true);
+ m_levelInput->setValue(3);
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+ m_iterationInput->setRange(0, 5, 1, true);
+ m_iterationInput->setValue(2);
+ break;
+ }
+
+ m_levelInput->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_ColorFX::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ colorEffect(m_destinationPreviewData, w, h, sb);
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void ImageEffect_ColorFX::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ colorEffect(data, w, h, sb);
+ TQString name;
+
+ switch (m_effectType->currentItem())
+ {
+ case ColorFX:
+ name = i18n("ColorFX");
+ break;
+
+ case Vivid:
+ name = i18n("Vivid");
+ break;
+
+ case Neon:
+ name = i18n("Neon");
+ break;
+
+ case FindEdges:
+ name = i18n("Find Edges");
+ break;
+ }
+
+ iface->putOriginalImage(name, data);
+ delete [] data;
+ }
+
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+void ImageEffect_ColorFX::colorEffect(uchar *data, int w, int h, bool sb)
+{
+ switch (m_effectType->currentItem())
+ {
+ case ColorFX:
+ solarize(m_levelInput->value(), data, w, h, sb);
+ break;
+
+ case Vivid:
+ vivid(m_levelInput->value(), data, w, h, sb);
+ break;
+
+ case Neon:
+ neon(data, w, h, sb, m_levelInput->value(), m_iterationInput->value());
+ break;
+
+ case FindEdges:
+ findEdges(data, w, h, sb, m_levelInput->value(), m_iterationInput->value());
+ break;
+ }
+}
+
+void ImageEffect_ColorFX::solarize(int factor, uchar *data, int w, int h, bool sb)
+{
+ bool stretch = true;
+
+ if (!sb) // 8 bits image.
+ {
+ uint threshold = (uint)((100-factor)*(255+1)/100);
+ threshold = TQMAX(1, threshold);
+ uchar *ptr = data;
+ uchar a, r, g, b;
+
+ for (int x=0 ; x < w*h ; x++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ a = ptr[3];
+
+ if (stretch)
+ {
+ r = (r > threshold) ? (255-r)*255/(255-threshold) : r*255/threshold;
+ g = (g > threshold) ? (255-g)*255/(255-threshold) : g*255/threshold;
+ b = (b > threshold) ? (255-b)*255/(255-threshold) : b*255/threshold;
+ }
+ else
+ {
+ if (r > threshold)
+ r = (255-r);
+ if (g > threshold)
+ g = (255-g);
+ if (b > threshold)
+ b = (255-b);
+ }
+
+ ptr[0] = b;
+ ptr[1] = g;
+ ptr[2] = r;
+ ptr[3] = a;
+
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ uint threshold = (uint)((100-factor)*(65535+1)/100);
+ threshold = TQMAX(1, threshold);
+ unsigned short *ptr = (unsigned short *)data;
+ unsigned short a, r, g, b;
+
+ for (int x=0 ; x < w*h ; x++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ a = ptr[3];
+
+ if (stretch)
+ {
+ r = (r > threshold) ? (65535-r)*65535/(65535-threshold) : r*65535/threshold;
+ g = (g > threshold) ? (65535-g)*65535/(65535-threshold) : g*65535/threshold;
+ b = (b > threshold) ? (65535-b)*65535/(65535-threshold) : b*65535/threshold;
+ }
+ else
+ {
+ if (r > threshold)
+ r = (65535-r);
+ if (g > threshold)
+ g = (65535-g);
+ if (b > threshold)
+ b = (65535-b);
+ }
+
+ ptr[0] = b;
+ ptr[1] = g;
+ ptr[2] = r;
+ ptr[3] = a;
+
+ ptr += 4;
+ }
+ }
+}
+
+void ImageEffect_ColorFX::vivid(int factor, uchar *data, int w, int h, bool sb)
+{
+ float amount = factor/100.0;
+
+ Digikam::DImgImageFilters filter;
+
+ // Apply Channel Mixer adjustments.
+
+ filter.channelMixerImage(
+ data, w, h, sb, // Image data.
+ true, // Preserve Luminosity
+ false, // Disable Black & White mode.
+ 1.0 + amount + amount, (-1.0)*amount, (-1.0)*amount, // Red Gains.
+ (-1.0)*amount, 1.0 + amount + amount, (-1.0)*amount, // Green Gains.
+ (-1.0)*amount, (-1.0)*amount, 1.0 + amount + amount // Blue Gains.
+ );
+
+ // Allocate the destination image data.
+
+ uchar *dest = new uchar[w*h*(sb ? 8 : 4)];
+
+ // And now apply the curve correction.
+
+ Digikam::ImageCurves Curves(sb);
+
+ if (!sb) // 8 bits image.
+ {
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 5, TQPoint(63, 60));
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 10, TQPoint(191, 194));
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(255, 255));
+ }
+ else // 16 bits image.
+ {
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 5, TQPoint(16128, 15360));
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 10, TQPoint(48896, 49664));
+ Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(65535, 65535));
+ }
+
+ Curves.curvesCalculateCurve(Digikam::ImageHistogram::AlphaChannel); // Calculate cure on all channels.
+ Curves.curvesLutSetup(Digikam::ImageHistogram::AlphaChannel); // ... and apply it on all channels
+ Curves.curvesLutProcess(data, dest, w, h);
+
+ memcpy(data, dest, w*h*(sb ? 8 : 4));
+ delete [] dest;
+}
+
+/* Function to apply the Neon effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Intensity => Intensity value
+ * BW => Border Width
+ *
+ * Theory => Wow, this is a great effect, you've never seen a Neon effect
+ * like this on PSC. Is very similar to Growing Edges (photoshop)
+ * Some pictures will be very interesting
+ */
+void ImageEffect_ColorFX::neon(uchar *data, int w, int h, bool sb, int Intensity, int BW)
+{
+ neonFindEdges(data, w, h, sb, true, Intensity, BW);
+}
+
+/* Function to apply the Find Edges effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Intensity => Intensity value
+ * BW => Border Width
+ *
+ * Theory => Wow, another Photoshop filter (FindEdges). Do you understand
+ * Neon effect ? This is the same engine, but is inversed with
+ * 255 - color.
+ */
+void ImageEffect_ColorFX::findEdges(uchar *data, int w, int h, bool sb, int Intensity, int BW)
+{
+ neonFindEdges(data, w, h, sb, false, Intensity, BW);
+}
+
+// Implementation of neon and FindEdges. They share 99% of their code.
+void ImageEffect_ColorFX::neonFindEdges(uchar *data, int w, int h, bool sb, bool neon, int Intensity, int BW)
+{
+ int Width = w;
+ int Height = h;
+ bool sixteenBit = sb;
+ int bytesDepth = sb ? 8 : 4;
+ uchar* pResBits = new uchar[Width*Height*bytesDepth];
+
+ Intensity = (Intensity < 0) ? 0 : (Intensity > 5) ? 5 : Intensity;
+ BW = (BW < 1) ? 1 : (BW > 5) ? 5 : BW;
+
+ uchar *ptr, *ptr1, *ptr2;
+
+ // these must be uint, we need full 2^32 range for 16 bit
+ uint color_1, color_2, colorPoint, colorOther1, colorOther2;
+
+ // initial copy
+ memcpy (pResBits, data, Width*Height*bytesDepth);
+
+ double intensityFactor = sqrt( 1 << Intensity );
+
+ for (int h = 0; h < Height; h++)
+ {
+ for (int w = 0; w < Width; w++)
+ {
+ ptr = pResBits + getOffset(Width, w, h, bytesDepth);
+ ptr1 = pResBits + getOffset(Width, w + Lim_Max (w, BW, Width), h, bytesDepth);
+ ptr2 = pResBits + getOffset(Width, w, h + Lim_Max (h, BW, Height), bytesDepth);
+
+ if (sixteenBit)
+ {
+ for (int k = 0; k <= 2; k++)
+ {
+ colorPoint = ((unsigned short *)ptr)[k];
+ colorOther1 = ((unsigned short *)ptr1)[k];
+ colorOther2 = ((unsigned short *)ptr2)[k];
+ color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
+ color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
+
+ // old algorithm was
+ // sqrt ((color_1 + color_2) << Intensity)
+ // As (a << I) = a * (1 << I) = a * (2^I), and we can split the square root
+
+ if (neon)
+ ((unsigned short *)ptr)[k] = CLAMP065535 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
+ else
+ ((unsigned short *)ptr)[k] = 65535 - CLAMP065535 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
+ }
+ }
+ else
+ {
+ for (int k = 0; k <= 2; k++)
+ {
+ colorPoint = ptr[k];
+ colorOther1 = ptr1[k];
+ colorOther2 = ptr2[k];
+ color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
+ color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
+
+ if (neon)
+ ptr[k] = CLAMP0255 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
+ else
+ ptr[k] = 255 - CLAMP0255 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
+ }
+ }
+ }
+ }
+
+ memcpy (data, pResBits, Width*Height*bytesDepth);
+ delete [] pResBits;
+}
+
+int ImageEffect_ColorFX::getOffset(int Width, int X, int Y, int bytesDepth)
+{
+ return (Y * Width * bytesDepth) + (X * bytesDepth);
+}
+
+inline int ImageEffect_ColorFX::Lim_Max(int Now, int Up, int Max)
+{
+ --Max;
+ while (Now > Max - Up) --Up;
+ return (Up);
+}
+
+} // NameSpace DigikamColorFXImagesPlugin
+
diff --git a/src/imageplugins/colorfx/imageeffect_colorfx.h b/src/imageplugins/colorfx/imageeffect_colorfx.h
new file mode 100644
index 00000000..d6d7d105
--- /dev/null
+++ b/src/imageplugins/colorfx/imageeffect_colorfx.h
@@ -0,0 +1,131 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : a digiKam image plugin for to apply a color
+ * effect to an image.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_COLORFX_H
+#define IMAGEEFFECT_COLORFX_H
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+
+class TQHButtonGroup;
+class TQComboBox;
+class TQLabel;
+
+class KIntNumInput;
+
+namespace Digikam
+{
+class ImageWidget;
+class ColorGradientWidget;
+class HistogramWidget;
+class DColor;
+}
+
+namespace DigikamColorFXImagesPlugin
+{
+
+class ImageEffect_ColorFX : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_ColorFX(TQWidget *parent);
+ ~ImageEffect_ColorFX();
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+ void colorEffect(uchar *data, int w, int h, bool sb);
+ void solarize(int factor, uchar *data, int w, int h, bool sb);
+ void vivid(int factor, uchar *data, int w, int h, bool sb);
+ void neon(uchar *data, int w, int h, bool sb, int Intensity, int BW);
+ void findEdges(uchar *data, int w, int h, bool sb, int Intensity, int BW);
+ void neonFindEdges(uchar *data, int w, int h, bool sb, bool neon, int Intensity, int BW);
+
+ inline int getOffset(int Width, int X, int Y, int bytesDepth);
+ inline int Lim_Max(int Now, int Up, int Max);
+
+private slots:
+
+ void slotEffectTypeChanged(int type);
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum ColorFXTypes
+ {
+ ColorFX=0,
+ Vivid,
+ Neon,
+ FindEdges
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+ TQComboBox *m_effectType;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQLabel *m_effectTypeLabel;
+ TQLabel *m_levelLabel;
+ TQLabel *m_iterationLabel;
+
+ KIntNumInput *m_levelInput;
+ KIntNumInput *m_iterationInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+};
+
+} // NameSpace DigikamColorFXImagesPlugin
+
+#endif /* IMAGEEFFECT_COLORFX_H */
diff --git a/src/imageplugins/colorfx/imageplugin_colorfx.cpp b/src/imageplugins/colorfx/imageplugin_colorfx.cpp
new file mode 100644
index 00000000..f4725028
--- /dev/null
+++ b/src/imageplugins/colorfx/imageplugin_colorfx.cpp
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : a digiKam image plugin for to apply a color
+ * effect to an image.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "colorfxtool.h"
+#include "imageplugin_colorfx.h"
+#include "imageplugin_colorfx.moc"
+
+using namespace DigikamColorFXImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_colorfx,
+ KGenericFactory<ImagePlugin_ColorFX>("digikamimageplugin_colorfx"));
+
+ImagePlugin_ColorFX::ImagePlugin_ColorFX(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_ColorFX")
+{
+ m_solarizeAction = new TDEAction(i18n("Color Effects..."), "colorfx", 0,
+ this, TQ_SLOT(slotColorFX()),
+ actionCollection(), "imageplugin_colorfx");
+
+ setXMLFile( "digikamimageplugin_colorfx_ui.rc" );
+
+ DDebug() << "ImagePlugin_ColorFX plugin loaded" << endl;
+}
+
+ImagePlugin_ColorFX::~ImagePlugin_ColorFX()
+{
+}
+
+void ImagePlugin_ColorFX::setEnabledActions(bool enable)
+{
+ m_solarizeAction->setEnabled(enable);
+}
+
+void ImagePlugin_ColorFX::slotColorFX()
+{
+ ColorFXTool *colorfx = new ColorFXTool(this);
+ loadTool(colorfx);
+}
+
diff --git a/src/imageplugins/colorfx/imageplugin_colorfx.h b/src/imageplugins/colorfx/imageplugin_colorfx.h
new file mode 100644
index 00000000..d7aaa83f
--- /dev/null
+++ b/src/imageplugins/colorfx/imageplugin_colorfx.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : a digiKam image plugin for to apply a color
+ * effect to an image.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_COLORFX_H
+#define IMAGEPLUGIN_COLORFX_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_ColorFX : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_ColorFX(TQObject *parent, const char* name, const TQStringList &args);
+ ~ImagePlugin_ColorFX();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotColorFX();
+
+private:
+
+ TDEAction *m_solarizeAction;
+};
+
+#endif /* IMAGEPLUGIN_COLORFX_H */
diff --git a/src/imageplugins/coreplugin/Makefile.am b/src/imageplugins/coreplugin/Makefile.am
new file mode 100644
index 00000000..b36d6f36
--- /dev/null
+++ b/src/imageplugins/coreplugin/Makefile.am
@@ -0,0 +1,52 @@
+SUBDIRS = sharpnesseditor hsl ratiocrop
+COMPILE_FIRST = sharpnesseditor hsl ratiocrop
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/imageplugins/coreplugin/sharpnesseditor \
+ -I$(top_srcdir)/src/imageplugins/coreplugin/hsl \
+ -I$(top_srcdir)/src/imageplugins/coreplugin/ratiocrop \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_core_la_SOURCES = imageplugin_core.cpp bwsepiatool.cpp \
+ autocorrectiontool.cpp \
+ rgbtool.cpp \
+ redeyetool.cpp blurtool.cpp \
+ iccprooftool.cpp bcgtool.cpp
+
+noinst_HEADERS = autocorrectiontool.h blurtool.h \
+ rgbtool.h bcgtool.h \
+ bwsepiatool.h iccprooftool.h redeyetool.h
+
+digikamimageplugin_core_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/imageplugins/coreplugin/sharpnesseditor/libsharpnesseditor.la \
+ $(top_builddir)/src/imageplugins/coreplugin/hsl/libhsl.la \
+ $(top_builddir)/src/imageplugins/coreplugin/ratiocrop/libratiocrop.la \
+ $(top_builddir)/src/digikam/libdigikam.la \
+ $(top_builddir)/src/utilities/imageeditor/editor/libdimgeditor.la \
+ $(top_builddir)/src/libs/curves/libcurves.la \
+ $(top_builddir)/src/libs/widgets/common/libcommonwidgets.la \
+ $(top_builddir)/src/libs/widgets/imageplugins/libimagepluginswidgets.la
+
+digikamimageplugin_core_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio -lkexiv2 -ltdeutils
+
+kde_services_DATA = digikamimageplugin_core.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_core.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_core_ui.rc
diff --git a/src/imageplugins/coreplugin/autocorrectiontool.cpp b/src/imageplugins/coreplugin/autocorrectiontool.cpp
new file mode 100644
index 00000000..32f00b44
--- /dev/null
+++ b/src/imageplugins/coreplugin/autocorrectiontool.cpp
@@ -0,0 +1,438 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-31
+ * Description : Auto-Color correction tool.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqlistbox.h>
+#include <tqpushbutton.h>
+#include <tqradiobutton.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// Digikam includes.
+
+#include "colorgradientwidget.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "listboxpreviewitem.h"
+#include "whitebalance.h"
+
+// Local includes.
+
+#include "autocorrectiontool.h"
+#include "autocorrectiontool.moc"
+
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+AutoCorrectionTool::AutoCorrectionTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("autocorrection");
+ setToolName(i18n("Auto-Correction"));
+ setToolIcon(SmallIcon("autocorrection"));
+ setToolHelp("autocolorcorrectiontool.anchor");
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImageWidget("autocorrection Tool", 0,
+ i18n("<p>Here you can see the auto-color correction tool "
+ "preview. You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ ImageIface iface(0, 0);
+ m_thumbnailImage = iface.getOriginalImg()->smoothScale(128, 128, TQSize::ScaleMin);
+ m_destinationPreviewData = 0;
+
+ EditorToolSettings *gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings->plainPage(), 2, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings->plainPage());
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings->plainPage() );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox);
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ // -------------------------------------------------------------
+
+ m_correctionTools = new TQListBox(gboxSettings->plainPage());
+ m_correctionTools->setColumnMode(1);
+ m_correctionTools->setVariableWidth(false);
+ m_correctionTools->setVariableHeight(false);
+ ListBoxWhatsThis* whatsThis = new ListBoxWhatsThis(m_correctionTools);
+
+ TQPixmap pix = getThumbnailForEffect(AutoLevelsCorrection);
+ ListBoxPreviewItem *item = new ListBoxPreviewItem(pix, i18n("Auto Levels"));
+ whatsThis->add( item, i18n("<b>Auto Levels</b>:"
+ "<p>This option maximizes the tonal range in the Red, "
+ "Green, and Blue channels. It searches the image shadow and highlight "
+ "limit values and adjusts the Red, Green, and Blue channels "
+ "to a full histogram range.</p>"));
+ m_correctionTools->insertItem(item, AutoLevelsCorrection);
+
+ pix = getThumbnailForEffect(NormalizeCorrection);
+ item = new ListBoxPreviewItem(pix, i18n("Normalize"));
+ whatsThis->add( item, i18n("<b>Normalize</b>:"
+ "<p>This option scales brightness values across the active "
+ "image so that the darkest point becomes black, and the "
+ "brightest point becomes as bright as possible without "
+ "altering its hue. This is often a \"magic fix\" for "
+ "images that are dim or washed out.</p>"));
+ m_correctionTools->insertItem(item, NormalizeCorrection);
+
+ pix = getThumbnailForEffect(EqualizeCorrection);
+ item = new ListBoxPreviewItem(pix, i18n("Equalize"));
+ whatsThis->add( item, i18n("<b>Equalize</b>:"
+ "<p>This option adjusts the brightness of colors across the "
+ "active image so that the histogram for the value channel "
+ "is as nearly as possible flat, that is, so that each possible "
+ "brightness value appears at about the same number of pixels "
+ "as each other value. Sometimes Equalize works wonderfully at "
+ "enhancing the contrasts in an image. Other times it gives "
+ "garbage. It is a very powerful operation, which can either work "
+ "miracles on an image or destroy it.</p>"));
+ m_correctionTools->insertItem(item, EqualizeCorrection);
+
+ pix = getThumbnailForEffect(StretchContrastCorrection);
+ item = new ListBoxPreviewItem(pix, i18n("Stretch Contrast"));
+ whatsThis->add( item, i18n("<b>Stretch Contrast</b>:"
+ "<p>This option enhances the contrast and brightness "
+ "of the RGB values of an image by stretching the lowest "
+ "and highest values to their fullest range, adjusting "
+ "everything in between.</p>"));
+ m_correctionTools->insertItem(item, StretchContrastCorrection);
+
+ pix = getThumbnailForEffect(AutoExposureCorrection);
+ item = new ListBoxPreviewItem(pix, i18n("Auto Exposure"));
+ whatsThis->add( item, i18n("<b>Auto Exposure</b>:"
+ "<p>This option enhances the contrast and brightness "
+ "of the RGB values of an image to calculate optimal "
+ "exposition and black level using image histogram "
+ "properties.</p>"));
+ m_correctionTools->insertItem(item, AutoExposureCorrection);
+
+ // -------------------------------------------------------------
+
+ m_correctionTools->setFocus();
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+ gridSettings->addMultiCellWidget(histoBox, 1, 1, 0, 4);
+ gridSettings->addMultiCellWidget(m_correctionTools, 2, 2, 0, 4);
+ gridSettings->setRowStretch(2, 10);
+ gridSettings->setSpacing(gboxSettings->spacingHint());
+ gridSettings->setMargin(gboxSettings->spacingHint());
+
+ setToolSettings(gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget(const DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromTarget(const DColor&)));
+
+ connect(m_correctionTools, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+}
+
+AutoCorrectionTool::~AutoCorrectionTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void AutoCorrectionTool::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void AutoCorrectionTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void AutoCorrectionTool::slotColorSelectedFromTarget(const DColor& color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void AutoCorrectionTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("autocorrection Tool");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ m_correctionTools->setCurrentItem(config->readNumEntry("Auto Correction Filter", AutoLevelsCorrection));
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void AutoCorrectionTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("autocorrection Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("Auto Correction Filter", m_correctionTools->currentItem());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void AutoCorrectionTool::slotResetSettings()
+{
+ m_correctionTools->blockSignals(true);
+ m_correctionTools->setCurrentItem(AutoLevelsCorrection);
+ m_correctionTools->blockSignals(false);
+
+ slotEffect();
+}
+
+void AutoCorrectionTool::slotEffect()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ autoCorrection(m_destinationPreviewData, w, h, sb, m_correctionTools->currentItem());
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+TQPixmap AutoCorrectionTool::getThumbnailForEffect(AutoCorrectionType type)
+{
+ DImg thumb = m_thumbnailImage.copy();
+ autoCorrection(thumb.bits(), thumb.width(), thumb.height(), thumb.sixteenBit(), type);
+ return (thumb.convertToPixmap());
+}
+
+
+void AutoCorrectionTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ int type = m_correctionTools->currentItem();
+ autoCorrection(data, w, h, sb, type);
+ TQString name;
+
+ switch (type)
+ {
+ case AutoLevelsCorrection:
+ name = i18n("Auto Levels");
+ break;
+
+ case NormalizeCorrection:
+ name = i18n("Normalize");
+ break;
+
+ case EqualizeCorrection:
+ name = i18n("Equalize");
+ break;
+
+ case StretchContrastCorrection:
+ name = i18n("Stretch Contrast");
+ break;
+
+ case AutoExposureCorrection:
+ name = i18n("Auto Exposure");
+ break;
+ }
+
+ iface->putOriginalImage(name, data);
+ delete [] data;
+ }
+
+ kapp->restoreOverrideCursor();
+}
+
+void AutoCorrectionTool::autoCorrection(uchar *data, int w, int h, bool sb, int type)
+{
+ DImgImageFilters filter;
+
+ switch (type)
+ {
+ case AutoLevelsCorrection:
+ filter.autoLevelsCorrectionImage(data, w, h, sb);
+ break;
+
+ case NormalizeCorrection:
+ filter.normalizeImage(data, w, h, sb);
+ break;
+
+ case EqualizeCorrection:
+ filter.equalizeImage(data, w, h, sb);
+ break;
+
+ case StretchContrastCorrection:
+ filter.stretchContrastImage(data, w, h, sb);
+ break;
+
+ case AutoExposureCorrection:
+ WhiteBalance wbFilter(sb);
+ double blackLevel;
+ double exposureLevel;
+ wbFilter.autoExposureAdjustement(data, w, h, sb, blackLevel, exposureLevel);
+ wbFilter.whiteBalance(data, w, h, sb, blackLevel, exposureLevel);
+ break;
+ }
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/autocorrectiontool.h b/src/imageplugins/coreplugin/autocorrectiontool.h
new file mode 100644
index 00000000..73a388f2
--- /dev/null
+++ b/src/imageplugins/coreplugin/autocorrectiontool.h
@@ -0,0 +1,128 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-31
+ * Description : Auto-Color correction tool.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef AUTOCORRECTIONTOOL_H
+#define AUTOCORRECTIONTOOL_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQHButtonGroup;
+class TQComboBox;
+class TQListBox;
+class TQButtonGroup;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class DImg;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class AutoCorrectionTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ AutoCorrectionTool(TQObject *parent);
+ ~AutoCorrectionTool();
+
+protected:
+
+ void finalRendering();
+
+private slots:
+
+ void slotEffect();
+ void slotResetSettings();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+
+private:
+
+ enum AutoCorrectionType
+ {
+ AutoLevelsCorrection=0,
+ NormalizeCorrection,
+ EqualizeCorrection,
+ StretchContrastCorrection,
+ AutoExposureCorrection
+ };
+
+private:
+
+ void readSettings();
+ void writeSettings();
+
+ void autoCorrection(uchar *data, int w, int h, bool sb, int type);
+ TQPixmap getThumbnailForEffect(AutoCorrectionType type);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQListBox *m_correctionTools;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::DImg m_thumbnailImage;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* AUTOCORRECTIONTOOL_H */
diff --git a/src/imageplugins/coreplugin/bcgtool.cpp b/src/imageplugins/coreplugin/bcgtool.cpp
new file mode 100644
index 00000000..17ecf838
--- /dev/null
+++ b/src/imageplugins/coreplugin/bcgtool.cpp
@@ -0,0 +1,366 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-05
+ * Description : digiKam image editor to adjust Brightness,
+ Contrast, and Gamma of picture.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Digikam includes.
+
+#include "bcgmodifier.h"
+#include "colorgradientwidget.h"
+#include "dimg.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+
+// Local includes.
+
+#include "bcgtool.h"
+#include "bcgtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+BCGTool::BCGTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("bcgadjust");
+ setToolName(i18n("Brightness / Contrast / Gamma"));
+ setToolIcon(SmallIcon("contrast"));
+ setToolHelp("bcgadjusttool.anchor");
+
+ m_destinationPreviewData = 0;
+
+ m_previewWidget = new ImageWidget("bcgadjust Tool", 0,
+ i18n("<p>Here you can see the image "
+ "brightness-contrast-gamma adjustments preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout(m_gboxSettings->plainPage(), 9, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), m_gboxSettings->plainPage());
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, m_gboxSettings->plainPage());
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap(TQPixmap(directory + "histogram-lin.png"));
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(m_gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox);
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Brightness:"), m_gboxSettings->plainPage());
+ m_bInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_bInput->setRange(-100, 100, 1);
+ m_bInput->setDefaultValue(0);
+ TQWhatsThis::add( m_bInput, i18n("<p>Set here the brightness adjustment of the image."));
+
+ TQLabel *label3 = new TQLabel(i18n("Contrast:"), m_gboxSettings->plainPage());
+ m_cInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_cInput->setRange(-100, 100, 1);
+ m_cInput->setDefaultValue(0);
+ TQWhatsThis::add( m_cInput, i18n("<p>Set here the contrast adjustment of the image."));
+
+ TQLabel *label4 = new TQLabel(i18n("Gamma:"), m_gboxSettings->plainPage());
+ m_gInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_gInput->setPrecision(2);
+ m_gInput->setRange(0.1, 3.0, 0.01);
+ m_gInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_gInput, i18n("<p>Set here the gamma adjustment of the image."));
+
+ // -------------------------------------------------------------
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+ gridSettings->addMultiCellWidget(label2, 3, 3, 0, 4);
+ gridSettings->addMultiCellWidget(m_bInput, 4, 4, 0, 4);
+ gridSettings->addMultiCellWidget(label3, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_cInput, 6, 6, 0, 4);
+ gridSettings->addMultiCellWidget(label4, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_gInput, 8, 8, 0, 4);
+ gridSettings->setRowStretch(9, 10);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_bInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_cInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings->enableButton(EditorToolSettings::Ok, false);
+}
+
+BCGTool::~BCGTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void BCGTool::slotChannelChanged(int channel)
+{
+ switch (channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("red"));
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("green"));
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("blue"));
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void BCGTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void BCGTool::slotColorSelectedFromTarget(const DColor &color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void BCGTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("bcgadjust Tool");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ m_bInput->setValue(config->readNumEntry("BrightnessAjustment", m_bInput->defaultValue()));
+ m_cInput->setValue(config->readNumEntry("ContrastAjustment", m_cInput->defaultValue()));
+ m_gInput->setValue(config->readDoubleNumEntry("GammaAjustment", m_gInput->defaultValue()));
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void BCGTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("bcgadjust Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("BrightnessAjustment", m_bInput->value());
+ config->writeEntry("ContrastAjustment", m_cInput->value());
+ config->writeEntry("GammaAjustment", m_gInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void BCGTool::slotResetSettings()
+{
+ m_bInput->blockSignals(true);
+ m_cInput->blockSignals(true);
+ m_gInput->blockSignals(true);
+
+ m_bInput->slotReset();
+ m_cInput->slotReset();
+ m_gInput->slotReset();
+
+ m_bInput->blockSignals(false);
+ m_cInput->blockSignals(false);
+ m_gInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void BCGTool::slotEffect()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+
+ double b = (double) m_bInput->value() / 250.0;
+ double c = (double) (m_cInput->value() / 100.0) + 1.00;
+ double g = m_gInput->value();
+
+ m_gboxSettings->enableButton(EditorToolSettings::Ok,
+ ( b != 0.0 || c != 1.0 || g != 1.0 ));
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ DImg preview(w, h, sb, a, m_destinationPreviewData);
+ BCGModifier cmod;
+ cmod.setGamma(g);
+ cmod.setBrightness(b);
+ cmod.setContrast(c);
+ cmod.applyBCG(preview);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void BCGTool::finalRendering()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+ ImageIface* iface = m_previewWidget->imageIface();
+
+ double b = (double) m_bInput->value() / 250.0;
+ double c = (double) (m_cInput->value() / 100.0) + 1.00;
+ double g = m_gInput->value();
+
+ iface->setOriginalBCG(b, c, g);
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/bcgtool.h b/src/imageplugins/coreplugin/bcgtool.h
new file mode 100644
index 00000000..4a1e9d21
--- /dev/null
+++ b/src/imageplugins/coreplugin/bcgtool.h
@@ -0,0 +1,115 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-05
+ * Description : digiKam image editor to adjust Brightness,
+ Contrast, and Gamma of picture.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BCGTOOL_H
+#define BCGTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQCheckBox;
+class TQComboBox;
+class TQHButtonGroup;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RDoubleNumInput;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class BCGTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ BCGTool(TQObject *parent);
+ ~BCGTool();
+
+private slots:
+
+ void slotEffect();
+ void slotResetSettings();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KDcrawIface::RIntNumInput *m_bInput;
+ KDcrawIface::RIntNumInput *m_cInput;
+ KDcrawIface::RDoubleNumInput *m_gInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* BCGTOOL_H */
diff --git a/src/imageplugins/coreplugin/blurtool.cpp b/src/imageplugins/coreplugin/blurtool.cpp
new file mode 100644
index 00000000..85e1dc19
--- /dev/null
+++ b/src/imageplugins/coreplugin/blurtool.cpp
@@ -0,0 +1,169 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to blur an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+// Digikam includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "dimggaussianblur.h"
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "blurtool.h"
+#include "blurtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+BlurTool::BlurTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("gaussianblur");
+ setToolName(i18n("Blur"));
+ setToolIcon(SmallIcon("blurimage"));
+ setToolHelp("blursharpentool.anchor");
+
+ // ---------------------------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 2, 1);
+ TQLabel *label = new TQLabel(i18n("Smoothness:"), m_gboxSettings->plainPage());
+
+ m_radiusInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_radiusInput->setRange(0, 100, 1);
+ m_radiusInput->setDefaultValue(0);
+ TQWhatsThis::add(m_radiusInput, i18n("<p>A smoothness of 0 has no effect, "
+ "1 and above determine the Gaussian blur matrix radius "
+ "that determines how much to blur the image."));
+
+ grid->addMultiCellWidget(label, 0, 0, 0, 1);
+ grid->addMultiCellWidget(m_radiusInput, 1, 1, 0, 1);
+ grid->setRowStretch(2, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "gaussianblur Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+}
+
+BlurTool::~BlurTool()
+{
+}
+
+void BlurTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("gaussianblur Tool");
+ m_radiusInput->setValue(config->readNumEntry("RadiusAjustment", m_radiusInput->defaultValue()));
+}
+
+void BlurTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("gaussianblur Tool");
+ config->writeEntry("RadiusAjustment", m_radiusInput->value());
+ config->sync();
+}
+
+void BlurTool::slotResetSettings()
+{
+ m_radiusInput->blockSignals(true);
+ m_radiusInput->slotReset();
+ m_radiusInput->blockSignals(false);
+}
+
+void BlurTool::prepareEffect()
+{
+ m_radiusInput->setEnabled(false);
+
+ DImg img = m_previewWidget->getOriginalRegionImage();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DImgGaussianBlur(&img, this, m_radiusInput->value())));
+}
+
+void BlurTool::prepareFinal()
+{
+ m_radiusInput->setEnabled(false);
+
+ ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ DImg orgImage = DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DImgGaussianBlur(&orgImage, this, m_radiusInput->value())));
+}
+
+void BlurTool::putPreviewData()
+{
+ DImg imDest = filter()->getTargetImage();
+ m_previewWidget->setPreviewImage(imDest);
+}
+
+void BlurTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ DImg imDest = filter()->getTargetImage();
+ iface.putOriginalImage(i18n("Gaussian Blur"), imDest.bits());
+}
+
+void BlurTool::renderingFinished()
+{
+ m_radiusInput->setEnabled(true);
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/blurtool.h b/src/imageplugins/coreplugin/blurtool.h
new file mode 100644
index 00000000..545f4aa2
--- /dev/null
+++ b/src/imageplugins/coreplugin/blurtool.h
@@ -0,0 +1,81 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to blur an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_BLUR_H
+#define IMAGEEFFECT_BLUR_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class BlurTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ BlurTool(TQObject *parent);
+ ~BlurTool();
+
+private slots:
+
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KDcrawIface::RIntNumInput *m_radiusInput;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_BLUR_H */
diff --git a/src/imageplugins/coreplugin/bwsepiatool.cpp b/src/imageplugins/coreplugin/bwsepiatool.cpp
new file mode 100644
index 00000000..06c30462
--- /dev/null
+++ b/src/imageplugins/coreplugin/bwsepiatool.cpp
@@ -0,0 +1,1177 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : Black and White conversion tool.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqfile.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqintdict.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqlistbox.h>
+#include <tqpushbutton.h>
+#include <tqtextstream.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <kstandarddirs.h>
+#include <ktabwidget.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Digikam includes.
+
+#include "bcgmodifier.h"
+#include "colorgradientwidget.h"
+#include "curveswidget.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "imagecurves.h"
+#include "imagehistogram.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "listboxpreviewitem.h"
+
+// Local includes.
+
+#include "bwsepiatool.h"
+#include "bwsepiatool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+class PreviewPixmapFactory : public TQObject
+{
+public:
+
+ PreviewPixmapFactory(BWSepiaTool* bwSepia);
+
+ void invalidate() { m_previewPixmapMap.clear(); }
+
+ const TQPixmap* pixmap(int id);
+
+private:
+
+ TQPixmap makePixmap(int id);
+
+ TQIntDict<TQPixmap> m_previewPixmapMap;
+ BWSepiaTool *m_bwSepia;
+};
+
+PreviewPixmapFactory::PreviewPixmapFactory(BWSepiaTool* bwSepia)
+ : TQObject(bwSepia), m_bwSepia(bwSepia)
+{
+ m_previewPixmapMap.setAutoDelete(true);
+}
+
+const TQPixmap* PreviewPixmapFactory::pixmap(int id)
+{
+ if (m_previewPixmapMap.find(id) == 0)
+ {
+ TQPixmap pix = makePixmap(id);
+ m_previewPixmapMap.insert(id, new TQPixmap(pix));
+ }
+
+ TQPixmap* res = m_previewPixmapMap[id];
+
+ return res;
+}
+
+TQPixmap PreviewPixmapFactory::makePixmap(int id)
+{
+ return m_bwSepia->getThumbnailForEffect(id);
+}
+
+// -----------------------------------------------------------------------------------
+
+class ListBoxBWPreviewItem : public ListBoxPreviewItem
+{
+
+public:
+
+ ListBoxBWPreviewItem(TQListBox *listbox, const TQString &text,
+ PreviewPixmapFactory* factory, int id)
+ : ListBoxPreviewItem(listbox, TQPixmap(), text)
+ {
+ m_previewPixmapFactory = factory;
+ m_id = id;
+ };
+
+ virtual const TQPixmap* pixmap() const;
+
+private:
+
+ int m_id;
+ PreviewPixmapFactory* m_previewPixmapFactory;
+};
+
+const TQPixmap* ListBoxBWPreviewItem::pixmap() const
+{
+ return m_previewPixmapFactory->pixmap(m_id);
+}
+
+// -----------------------------------------------------------------------------------
+
+BWSepiaTool::BWSepiaTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("convertbw");
+ setToolName(i18n("Black && White"));
+ setToolIcon(SmallIcon("bwtonal"));
+ setToolHelp("blackandwhitetool.anchor");
+
+ m_destinationPreviewData = 0;
+
+ ImageIface iface(0, 0);
+ m_originalImage = iface.getOriginalImg();
+ m_thumbnailImage = m_originalImage->smoothScale(128, 128, TQSize::ScaleMin);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImageWidget("convertbw Tool", 0,
+ i18n("<p>Here you can see the black and white conversion tool preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ EditorToolSettings *gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings->plainPage(), 4, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings->plainPage());
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, gboxSettings->plainPage());
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap(TQPixmap(directory + "histogram-lin.png"));
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox);
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ m_tab = new KTabWidget(gboxSettings->plainPage());
+
+ m_bwFilm = new TQListBox(m_tab);
+ m_bwFilm->setColumnMode(1);
+ m_bwFilm->setVariableWidth(false);
+ m_bwFilm->setVariableHeight(false);
+ ListBoxWhatsThis* whatsThis2 = new ListBoxWhatsThis(m_bwFilm);
+ m_previewPixmapFactory = new PreviewPixmapFactory(this);
+
+ int type = BWGeneric;
+
+ ListBoxBWPreviewItem *item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Generic"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Generic</b>:"
+ "<p>Simulate a generic black and white film</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa 200X"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa 200X</b>:"
+ "<p>Simulate the Agfa 200X black and white film at 200 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa Pan 25"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa Pan 25</b>:"
+ "<p>Simulate the Agfa Pan black and white film at 25 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa Pan 100"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa Pan 100</b>:"
+ "<p>Simulate the Agfa Pan black and white film at 100 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa Pan 400"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa Pan 400</b>:"
+ "<p>Simulate the Agfa Pan black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford Delta 100"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford Delta 100</b>:"
+ "<p>Simulate the Ilford Delta black and white film at 100 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford Delta 400"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford Delta 400</b>:"
+ "<p>Simulate the Ilford Delta black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford Delta 400 Pro 3200"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford Delta 400 Pro 3200</b>:"
+ "<p>Simulate the Ilford Delta 400 Pro black and white film at 3200 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford FP4 Plus"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford FP4 Plus</b>:"
+ "<p>Simulate the Ilford FP4 Plus black and white film at 125 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford HP5 Plus"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford HP5 Plus</b>:"
+ "<p>Simulate the Ilford HP5 Plus black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford PanF Plus"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford PanF Plus</b>:"
+ "<p>Simulate the Ilford PanF Plus black and white film at 50 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford XP2 Super"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford XP2 Super</b>:"
+ "<p>Simulate the Ilford XP2 Super black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Kodak Tmax 100"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Kodak Tmax 100</b>:"
+ "<p>Simulate the Kodak Tmax black and white film at 100 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Kodak Tmax 400"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Kodak Tmax 400</b>:"
+ "<p>Simulate the Kodak Tmax black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Kodak TriX"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Kodak TriX</b>:"
+ "<p>Simulate the Kodak TriX black and white film at 400 ISO</p>"));
+
+ // -------------------------------------------------------------
+
+ TQVBox *vbox = new TQVBox(m_tab);
+
+ m_bwFilters = new TQListBox(vbox);
+ m_bwFilters->setColumnMode(1);
+ m_bwFilters->setVariableWidth(false);
+ m_bwFilters->setVariableHeight(false);
+ ListBoxWhatsThis* whatsThis = new ListBoxWhatsThis(m_bwFilters);
+
+ type = BWNoFilter;
+
+ item = new ListBoxBWPreviewItem(m_bwFilters,
+ i18n("No Lens Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>No Lens Filter</b>:"
+ "<p>Do not apply a lens filter when rendering the image.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Green Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Green Filter</b>:"
+ "<p>Simulate black and white film exposure using a green filter. "
+ "This is usefule for all scenic shoots, especially portraits "
+ "photographed against the sky.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Orange Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Orange Filter</b>:"
+ "<p>Simulate black and white film exposure using an orange filter. "
+ "This will enhance landscapes, marine scenes and aerial "
+ "photography.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Red Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Red Filter</b>:"
+ "<p>Simulate black and white film exposure using a red filter. "
+ "This creates dramatic sky effects, and simulates moonlight scenes "
+ "in the daytime.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Yellow Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Yellow Filter</b>:"
+ "<p>Simulate black and white film exposure using a yellow filter. "
+ "This has the most natural tonal correction, and improves contrast. Ideal for "
+ "landscapes.</p>"));
+
+ m_strengthInput = new RIntNumInput(vbox);
+ m_strengthInput->input()->setLabel(i18n("Strength:"), AlignLeft | AlignVCenter);
+ m_strengthInput->setRange(1, 5, 1);
+ m_strengthInput->setDefaultValue(1);
+ TQWhatsThis::add(m_strengthInput, i18n("<p>Here, set the strength adjustment of the lens filter."));
+
+ // -------------------------------------------------------------
+
+ m_bwTone = new TQListBox(m_tab);
+ m_bwTone->setColumnMode(1);
+ m_bwTone->setVariableWidth(false);
+ m_bwTone->setVariableHeight(false);
+ ListBoxWhatsThis* whatsThis3 = new ListBoxWhatsThis(m_bwTone);
+
+ type = BWNoTone;
+
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("No Tone Filter"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>No Tone Filter</b>:"
+ "<p>Do not apply a tone filter to the image.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Sepia Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Sepia Tone</b>:"
+ "<p>Gives a warm highlight and mid-tone while adding a bit of coolness to "
+ "the shadows - very similar to the process of bleaching a print and "
+ "re-developing in a sepia toner.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Brown Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Brown Tone</b>:"
+ "<p>This filter is more neutral than the Sepia Tone "
+ "filter.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Cold Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Cold Tone</b>:"
+ "<p>Start subtle and replicates printing on a cold tone black and white "
+ "paper such as a bromide enlarging "
+ "paper.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Selenium Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Selenium Tone</b>:"
+ "<p>This effect replicates traditional selenium chemical toning done "
+ "in the darkroom.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Platinum Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Platinum Tone</b>:"
+ "<p>This effect replicates traditional platinum chemical toning done "
+ "in the darkroom.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Green Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with greenish tint</b>:"
+ "<p>This effect is also known as Verdante.</p>"));
+
+ // -------------------------------------------------------------
+
+ TQWidget *curveBox = new TQWidget( m_tab );
+ TQGridLayout *gridTab2 = new TQGridLayout(curveBox, 5, 2, 0);
+
+ ColorGradientWidget* vGradient = new ColorGradientWidget(
+ ColorGradientWidget::Vertical,
+ 10, curveBox);
+ vGradient->setColors(TQColor("white"), TQColor("black"));
+
+ TQLabel *spacev = new TQLabel(curveBox);
+ spacev->setFixedWidth(1);
+
+ m_curvesWidget = new CurvesWidget(256, 256, m_originalImage->bits(), m_originalImage->width(),
+ m_originalImage->height(), m_originalImage->sixteenBit(),
+ curveBox);
+ TQWhatsThis::add( m_curvesWidget, i18n("<p>This is the curve adjustment of the image luminosity"));
+
+ TQLabel *spaceh = new TQLabel(curveBox);
+ spaceh->setFixedHeight(1);
+
+ ColorGradientWidget *hGradient = new ColorGradientWidget(
+ ColorGradientWidget::Horizontal,
+ 10, curveBox);
+ hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ m_cInput = new RIntNumInput(curveBox);
+ m_cInput->input()->setLabel(i18n("Contrast:"), AlignLeft | AlignVCenter);
+ m_cInput->setRange(-100, 100, 1);
+ m_cInput->setDefaultValue(0);
+ TQWhatsThis::add( m_cInput, i18n("<p>Set here the contrast adjustment of the image."));
+
+ gridTab2->addMultiCellWidget(vGradient, 0, 0, 0, 0);
+ gridTab2->addMultiCellWidget(spacev, 0, 0, 1, 1);
+ gridTab2->addMultiCellWidget(m_curvesWidget, 0, 0, 2, 2);
+ gridTab2->addMultiCellWidget(spaceh, 1, 1, 2, 2);
+ gridTab2->addMultiCellWidget(hGradient, 2, 2, 2, 2);
+ gridTab2->addMultiCellWidget(m_cInput, 4, 4, 0, 2);
+// gridTab2->setRowSpacing(3);
+ gridTab2->setRowStretch(5, 10);
+
+ // -------------------------------------------------------------
+
+ m_tab->insertTab(m_bwFilm, i18n("Film"), FilmTab);
+ m_tab->insertTab(vbox, i18n("Lens Filters"), BWFiltersTab);
+ m_tab->insertTab(m_bwTone, i18n("Tone"), ToneTab);
+ m_tab->insertTab(curveBox, i18n("Lightness"), LuminosityTab);
+
+ gridSettings->addMultiCellWidget(m_tab, 3, 3, 0, 4);
+ gridSettings->setRowStretch(3, 10);
+ setToolSettings(gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotSpotColorChanged(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_bwFilters, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotFilterSelected(int)));
+
+ connect(m_strengthInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_bwFilm, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_bwTone, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_curvesWidget, TQ_SIGNAL(signalCurvesChanged()),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_cInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+}
+
+BWSepiaTool::~BWSepiaTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void BWSepiaTool::slotFilterSelected(int filter)
+{
+ if (filter == BWNoFilter)
+ m_strengthInput->setEnabled(false);
+ else
+ m_strengthInput->setEnabled(true);
+
+ slotEffect();
+}
+
+TQPixmap BWSepiaTool::getThumbnailForEffect(int type)
+{
+ DImg thumb = m_thumbnailImage.copy();
+ int w = thumb.width();
+ int h = thumb.height();
+ bool sb = thumb.sixteenBit();
+ bool a = thumb.hasAlpha();
+
+ if (type < BWGeneric)
+ {
+ // In Filter view, we will render a preview of the B&W filter with the generic B&W film.
+ blackAndWhiteConversion(thumb.bits(), w, h, sb, type);
+ blackAndWhiteConversion(thumb.bits(), w, h, sb, BWGeneric);
+ }
+ else
+ {
+ // In Film and Tone view, we will render the preview without to use the B&W Filter
+ blackAndWhiteConversion(thumb.bits(), w, h, sb, type);
+ }
+
+ if (m_curvesWidget->curves()) // in case we're called before the creator is done
+ {
+ uchar *targetData = new uchar[w*h*(sb ? 8 : 4)];
+ m_curvesWidget->curves()->curvesLutSetup(ImageHistogram::AlphaChannel);
+ m_curvesWidget->curves()->curvesLutProcess(thumb.bits(), targetData, w, h);
+
+ DImg preview(w, h, sb, a, targetData);
+ BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(preview);
+
+ thumb.putImageData(preview.bits());
+
+ delete [] targetData;
+ }
+ return (thumb.convertToPixmap());
+}
+
+void BWSepiaTool::slotChannelChanged(int channel)
+{
+ switch (channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("red"));
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("green"));
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("blue"));
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void BWSepiaTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+ m_curvesWidget->m_scaleType = scale;
+ m_curvesWidget->repaint(false);
+}
+
+void BWSepiaTool::slotSpotColorChanged(const DColor &color)
+{
+ m_curvesWidget->setCurveGuide(color);
+}
+
+void BWSepiaTool::slotColorSelectedFromTarget(const DColor &color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void BWSepiaTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("convertbw Tool");
+
+ m_tab->setCurrentPage(config->readNumEntry("Settings Tab", BWFiltersTab));
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ m_bwFilters->setCurrentItem(config->readNumEntry("BW Filter", 0));
+ m_bwFilm->setCurrentItem(config->readNumEntry("BW Film", 0));
+ m_bwTone->setCurrentItem(config->readNumEntry("BW Tone", 0));
+ m_cInput->setValue(config->readNumEntry("ContrastAjustment", m_cInput->defaultValue()));
+ m_strengthInput->setValue(config->readNumEntry("StrengthAjustment", m_strengthInput->defaultValue()));
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesChannelReset(i);
+
+ m_curvesWidget->curves()->setCurveType(m_curvesWidget->m_channelType, ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p = config->readPointEntry(TQString("CurveAjustmentPoint%1").arg(j), &disable);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, j, p);
+ }
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesCalculateCurve(i);
+
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+ slotFilterSelected(m_bwFilters->currentItem());
+}
+
+void BWSepiaTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("convertbw Tool");
+ config->writeEntry("Settings Tab", m_tab->currentPageIndex());
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("BW Filter", m_bwFilters->currentItem());
+ config->writeEntry("BW Film", m_bwFilm->currentItem());
+ config->writeEntry("BW Tone", m_bwTone->currentItem());
+ config->writeEntry("ContrastAjustment", m_cInput->value());
+ config->writeEntry("StrengthAjustment", m_strengthInput->value());
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint p = m_curvesWidget->curves()->getCurvePoint(ImageHistogram::ValueChannel, j);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+
+ config->writeEntry(TQString("CurveAjustmentPoint%1").arg(j), p);
+ }
+
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void BWSepiaTool::slotResetSettings()
+{
+ m_bwFilm->blockSignals(true);
+ m_bwFilters->blockSignals(true);
+ m_bwTone->blockSignals(true);
+ m_cInput->blockSignals(true);
+ m_strengthInput->blockSignals(true);
+
+ m_bwFilm->setCurrentItem(0);
+ m_bwFilm->setSelected(0, true);
+
+ m_bwFilters->setCurrentItem(0);
+ m_bwFilters->setSelected(0, true);
+
+ m_bwTone->setCurrentItem(0);
+ m_bwTone->setSelected(0, true);
+
+ m_cInput->slotReset();
+ m_strengthInput->slotReset();
+
+ for (int channel = 0; channel < 5; channel++)
+ m_curvesWidget->curves()->curvesChannelReset(channel);
+
+ m_curvesWidget->reset();
+
+ m_bwFilm->blockSignals(false);
+ m_bwFilters->blockSignals(false);
+ m_bwTone->blockSignals(false);
+ m_cInput->blockSignals(false);
+ m_strengthInput->blockSignals(false);
+
+ m_histogramWidget->reset();
+ m_previewPixmapFactory->invalidate();
+ m_bwFilters->triggerUpdate(false);
+ m_bwTone->triggerUpdate(false);
+
+ slotEffect();
+}
+
+void BWSepiaTool::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ m_histogramWidget->stopHistogramComputation();
+
+ delete [] m_destinationPreviewData;
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ // Apply black and white filter.
+
+ blackAndWhiteConversion(m_destinationPreviewData, w, h, sb, m_bwFilters->currentItem());
+
+ // Apply black and white film type.
+
+ blackAndWhiteConversion(m_destinationPreviewData, w, h, sb, m_bwFilm->currentItem() + BWGeneric);
+
+ // Apply color tone filter.
+
+ blackAndWhiteConversion(m_destinationPreviewData, w, h, sb, m_bwTone->currentItem() + BWNoTone);
+
+ // Calculate and apply the curve on image.
+
+ uchar *targetData = new uchar[w*h*(sb ? 8 : 4)];
+ m_curvesWidget->curves()->curvesLutSetup(ImageHistogram::AlphaChannel);
+ m_curvesWidget->curves()->curvesLutProcess(m_destinationPreviewData, targetData, w, h);
+
+ // Adjust contrast.
+
+ DImg preview(w, h, sb, a, targetData);
+ BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(preview);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] targetData;
+
+ kapp->restoreOverrideCursor();
+}
+
+void BWSepiaTool::finalRendering()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ // Apply black and white filter.
+
+ blackAndWhiteConversion(data, w, h, sb, m_bwFilters->currentItem());
+
+ // Apply black and white film type.
+
+ blackAndWhiteConversion(data, w, h, sb, m_bwFilm->currentItem() + BWGeneric);
+
+ // Apply color tone filter.
+
+ blackAndWhiteConversion(data, w, h, sb, m_bwTone->currentItem() + BWNoTone);
+
+ // Calculate and apply the curve on image.
+
+ uchar *targetData = new uchar[w*h*(sb ? 8 : 4)];
+ m_curvesWidget->curves()->curvesLutSetup(ImageHistogram::AlphaChannel);
+ m_curvesWidget->curves()->curvesLutProcess(data, targetData, w, h);
+
+ // Adjust contrast.
+
+ DImg img(w, h, sb, a, targetData);
+ BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(img);
+
+ iface->putOriginalImage(i18n("Convert to Black && White"), img.bits());
+
+ delete [] data;
+ delete [] targetData;
+ }
+
+ kapp->restoreOverrideCursor();
+}
+
+void BWSepiaTool::blackAndWhiteConversion(uchar *data, int w, int h, bool sb, int type)
+{
+ // Value to multiply RGB 8 bits component of mask used by changeTonality() method.
+ int mul = sb ? 255 : 1;
+ DImgImageFilters filter;
+ double strength = 1.0 + ((double)m_strengthInput->value() - 1.0) * (1.0 / 3.0);
+
+ switch (type)
+ {
+ case BWNoFilter:
+ m_redAttn = 0.0;
+ m_greenAttn = 0.0;
+ m_blueAttn = 0.0;
+ break;
+
+ case BWGreenFilter:
+ m_redAttn = -0.20 * strength;
+ m_greenAttn = +0.11 * strength;
+ m_blueAttn = +0.09 * strength;
+ break;
+
+ case BWOrangeFilter:
+ m_redAttn = +0.48 * strength;
+ m_greenAttn = -0.37 * strength;
+ m_blueAttn = -0.11 * strength;
+ break;
+
+ case BWRedFilter:
+ m_redAttn = +0.60 * strength;
+ m_greenAttn = -0.49 * strength;
+ m_blueAttn = -0.11 * strength;
+ break;
+
+ case BWYellowFilter:
+ m_redAttn = +0.30 * strength;
+ m_greenAttn = -0.31 * strength;
+ m_blueAttn = +0.01 * strength;
+ break;
+
+ // --------------------------------------------------------------------------------
+
+ case BWGeneric:
+ case BWNoTone:
+ m_redMult = 0.24;
+ m_greenMult = 0.68;
+ m_blueMult = 0.08;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfa200X:
+ m_redMult = 0.18;
+ m_greenMult = 0.41;
+ m_blueMult = 0.41;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfapan25:
+ m_redMult = 0.25;
+ m_greenMult = 0.39;
+ m_blueMult = 0.36;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfapan100:
+ m_redMult = 0.21;
+ m_greenMult = 0.40;
+ m_blueMult = 0.39;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfapan400:
+ m_redMult = 0.20;
+ m_greenMult = 0.41;
+ m_blueMult = 0.39;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordDelta100:
+ m_redMult = 0.21;
+ m_greenMult = 0.42;
+ m_blueMult = 0.37;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordDelta400:
+ m_redMult = 0.22;
+ m_greenMult = 0.42;
+ m_blueMult = 0.36;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordDelta400Pro3200:
+ m_redMult = 0.31;
+ m_greenMult = 0.36;
+ m_blueMult = 0.33;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordFP4:
+ m_redMult = 0.28;
+ m_greenMult = 0.41;
+ m_blueMult = 0.31;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordHP5:
+ m_redMult = 0.23;
+ m_greenMult = 0.37;
+ m_blueMult = 0.40;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordPanF:
+ m_redMult = 0.33;
+ m_greenMult = 0.36;
+ m_blueMult = 0.31;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordXP2Super:
+ m_redMult = 0.21;
+ m_greenMult = 0.42;
+ m_blueMult = 0.37;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWKodakTmax100:
+ m_redMult = 0.24;
+ m_greenMult = 0.37;
+ m_blueMult = 0.39;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWKodakTmax400:
+ m_redMult = 0.27;
+ m_greenMult = 0.36;
+ m_blueMult = 0.37;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWKodakTriX:
+ m_redMult = 0.25;
+ m_greenMult = 0.35;
+ m_blueMult = 0.40;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ // --------------------------------------------------------------------------------
+
+ case BWSepiaTone:
+ filter.changeTonality(data, w, h, sb, 162*mul, 132*mul, 101*mul);
+ break;
+
+ case BWBrownTone:
+ filter.changeTonality(data, w, h, sb, 129*mul, 115*mul, 104*mul);
+ break;
+
+ case BWColdTone:
+ filter.changeTonality(data, w, h, sb, 102*mul, 109*mul, 128*mul);
+ break;
+
+ case BWSeleniumTone:
+ filter.changeTonality(data, w, h, sb, 122*mul, 115*mul, 122*mul);
+ break;
+
+ case BWPlatinumTone:
+ filter.changeTonality(data, w, h, sb, 115*mul, 110*mul, 106*mul);
+ break;
+
+ case BWGreenTone:
+ filter.changeTonality(data, w, h, sb, 108*mul, 116*mul, 100*mul);
+ break;
+
+ }
+}
+
+//-- Load all settings from file --------------------------------------
+
+void BWSepiaTool::slotLoadSettings()
+{
+ KURL loadFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Black & White Settings File to Load")) );
+ if( loadFile.isEmpty() )
+ return;
+
+ TQFile file(loadFile.path());
+
+ if (file.open(IO_ReadOnly))
+ {
+ TQTextStream stream(&file);
+
+ if (stream.readLine() != "# Black & White Configuration File")
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("\"%1\" is not a Black & White settings text file.")
+ .arg(loadFile.fileName()));
+ file.close();
+ return;
+ }
+
+ m_bwFilters->blockSignals(true);
+ m_bwTone->blockSignals(true);
+ m_cInput->blockSignals(true);
+
+ m_bwFilters->setCurrentItem(stream.readLine().toInt());
+ m_bwTone->setCurrentItem(stream.readLine().toInt());
+ m_cInput->setValue(stream.readLine().toInt());
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesChannelReset(i);
+
+ m_curvesWidget->curves()->setCurveType(m_curvesWidget->m_channelType, ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0; j < 17; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p;
+ p.setX(stream.readLine().toInt());
+ p.setY(stream.readLine().toInt());
+
+ if (m_originalImage->sixteenBit() && p != disable)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, j, p);
+ }
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesCalculateCurve(i);
+
+ m_bwFilters->blockSignals(false);
+ m_bwTone->blockSignals(false);
+ m_cInput->blockSignals(false);
+
+ m_histogramWidget->reset();
+ m_previewPixmapFactory->invalidate();
+ m_bwFilters->triggerUpdate(false);
+ m_bwTone->triggerUpdate(false);
+
+ slotEffect();
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("Cannot load settings from the Black & White text file."));
+
+ file.close();
+}
+
+//-- Save all settings to file ---------------------------------------
+
+void BWSepiaTool::slotSaveAsSettings()
+{
+ KURL saveFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Black & White Settings File to Save")));
+ if( saveFile.isEmpty() )
+ return;
+
+ TQFile file(saveFile.path());
+
+ if (file.open(IO_WriteOnly))
+ {
+ TQTextStream stream(&file);
+ stream << "# Black & White Configuration File\n";
+ stream << m_bwFilters->currentItem() << "\n";
+ stream << m_bwTone->currentItem() << "\n";
+ stream << m_cInput->value() << "\n";
+
+ for (int j = 0; j < 17; j++)
+ {
+ TQPoint p = m_curvesWidget->curves()->getCurvePoint(ImageHistogram::ValueChannel, j);
+ if (m_originalImage->sixteenBit())
+ {
+ p.setX(p.x() / 255);
+ p.setY(p.y() / 255);
+ }
+ stream << p.x() << "\n";
+ stream << p.y() << "\n";
+ }
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("Cannot save settings to the Black & White text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/bwsepiatool.h b/src/imageplugins/coreplugin/bwsepiatool.h
new file mode 100644
index 00000000..7145f567
--- /dev/null
+++ b/src/imageplugins/coreplugin/bwsepiatool.h
@@ -0,0 +1,193 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : Black and White conversion tool.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef BWSEPIATOOL_H
+#define BWSEPIATOOL_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQHButtonGroup;
+class TQComboBox;
+class TQButtonGroup;
+class TQListBox;
+
+class KTabWidget;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class DImg;
+class CurvesWidget;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class PreviewPixmapFactory;
+
+class BWSepiaTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ BWSepiaTool(TQObject *parent);
+ ~BWSepiaTool();
+
+ friend class PreviewPixmapFactory;
+
+protected:
+
+ TQPixmap getThumbnailForEffect(int type);
+ void finalRendering();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void blackAndWhiteConversion(uchar *data, int w, int h, bool sb, int type);
+
+private slots:
+
+ void slotResetSettings();
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotSpotColorChanged(const Digikam::DColor &color);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+ void slotFilterSelected(int filter);
+
+private:
+
+ enum BlackWhiteConversionType
+ {
+ BWNoFilter=0, // B&W filter to the front of lens.
+ BWGreenFilter,
+ BWOrangeFilter,
+ BWRedFilter,
+ BWYellowFilter,
+
+ BWGeneric, // B&W film simulation.
+ BWAgfa200X,
+ BWAgfapan25,
+ BWAgfapan100,
+ BWAgfapan400,
+ BWIlfordDelta100,
+ BWIlfordDelta400,
+ BWIlfordDelta400Pro3200,
+ BWIlfordFP4,
+ BWIlfordHP5,
+ BWIlfordPanF,
+ BWIlfordXP2Super,
+ BWKodakTmax100,
+ BWKodakTmax400,
+ BWKodakTriX,
+
+ BWNoTone, // Chemical color tone filter.
+ BWSepiaTone,
+ BWBrownTone,
+ BWColdTone,
+ BWSeleniumTone,
+ BWPlatinumTone,
+ BWGreenTone
+ };
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum SettingsTab
+ {
+ FilmTab=0,
+ BWFiltersTab,
+ ToneTab,
+ LuminosityTab
+ };
+
+ // Color filter attenuation in percents.
+ double m_redAttn, m_greenAttn, m_blueAttn;
+
+ // Channel mixer color multiplier.
+ double m_redMult, m_greenMult, m_blueMult;
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQListBox *m_bwFilters;
+ TQListBox *m_bwFilm;
+ TQListBox *m_bwTone;
+
+ KDcrawIface::RIntNumInput *m_cInput;
+ KDcrawIface::RIntNumInput *m_strengthInput;
+
+ KTabWidget *m_tab;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::CurvesWidget *m_curvesWidget;
+
+ Digikam::DImg *m_originalImage;
+ Digikam::DImg m_thumbnailImage;
+
+ PreviewPixmapFactory *m_previewPixmapFactory;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* BWSEPIATOOL_H */
diff --git a/src/imageplugins/coreplugin/digikamimageplugin_core.desktop b/src/imageplugins/coreplugin/digikamimageplugin_core.desktop
new file mode 100644
index 00000000..8f8b3d10
--- /dev/null
+++ b/src/imageplugins/coreplugin/digikamimageplugin_core.desktop
@@ -0,0 +1,47 @@
+[Desktop Entry]
+Name=ImagePlugin_Core
+Name[el]=ΠρόσθετοΕικόνας_Πυρήνας
+Name[fi]=Liitännäisten ydin
+Name[hr]=Jezgra
+Name[it]=PluginImmagini_Nocciolo
+Name[nl]=Afbeeldingsplugin_Kern
+Name[sr]=Језгро прикључака
+Name[sr@Latn]=Jezgro priključaka
+Name[sv]=Insticksprogram med bildkärna
+Name[tr]=ResimEklentisi_Çekirdek
+Name[xx]=xxImagePlugin_Corexx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=digiKam Core Image Plugin
+Comment[bg]=Основна приставка за снимки на digiKam
+Comment[ca]=Connector d'imatges bàsic del digiKam
+Comment[da]=Grundlæggende Digikam billed-plugin
+Comment[de]=digiKam-Kernmodul
+Comment[el]=Πρόσθετο εικόνων πυρήνα digiKam
+Comment[es]=Plugin fundamental de digiKam de imágenes
+Comment[et]=DigiKami peamine pildiplugin
+Comment[fa]=وصلۀ تصویر هستۀ digiKam
+Comment[fi]=digiKamin liitännäisten ydin
+Comment[gl]=Plugin de Imaxe Básico de digiKam
+Comment[hr]=digiKam dodatak za slike
+Comment[it]=Plugin delle immagini centrale di digiKam
+Comment[ja]=digiKam コア画像プラグイン
+Comment[nds]=Karn-Bildmoduul vun digiKam
+Comment[nl]=Digikam kernafbeeldingsplugin
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਕੋਰ ਚਿੱਤਰ ਪਲੱਗਇਨ
+Comment[pl]=Podstawowa wtyczka obrazu digiKama
+Comment[pt]='Plugin' de Imagem Básico do digiKam
+Comment[pt_BR]=Plugin digiKam para Centralização da imagem
+Comment[ru]=Модуль digiKam основная работа с изображением
+Comment[sk]=Základný obrázkový plugin digiKamu
+Comment[sr]=Језгро digiKam-ових сликовних прикључака
+Comment[sr@Latn]=Jezgro digiKam-ovih slikovnih priključaka
+Comment[sv]=Insticksprogram med bildkärna för Digikam
+Comment[tr]=digiKam Çekirdek Resim Eklentisi
+Comment[uk]=Втулок основи зображення для digiKam
+Comment[vi]=Phần bổ sung ảnh lõi digiKam
+Comment[xx]=xxdigiKam Core Image Pluginxx
+
+X-TDE-Library=digikamimageplugin_core
+author=Caulier Gilles, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/coreplugin/digikamimageplugin_core_ui.rc b/src/imageplugins/coreplugin/digikamimageplugin_core_ui.rc
new file mode 100644
index 00000000..cb1148bd
--- /dev/null
+++ b/src/imageplugins/coreplugin/digikamimageplugin_core_ui.rc
@@ -0,0 +1,56 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="7" name="digikamimageplugin_core" >
+
+ <MenuBar>
+
+ <Menu name="Color"><text>&amp;Color</text>
+ <Action name="implugcore_autocorrection" />
+ <Action name="imageplugin_whitebalance" />
+ <Action name="implugcore_bcg" />
+ <Action name="implugcore_hsl" />
+ <Action name="imageplugin_colorfx" />
+ <Action name="implugcore_rgb" />
+ <Separator />
+ <Action name="imageplugin_adjustcurves" />
+ <Action name="imageplugin_adjustlevels" />
+ <Action name="implugcore_invert" />
+ <Action name="implugcore_blackwhite" />
+ <Menu name="Depth"><text>&amp;Depth</text>
+ <Action name="implugcore_convertto8bits" />
+ <Action name="implugcore_convertto16bits" />
+ </Menu>
+ <Separator />
+ <Action name="implugcore_colormanagement" />
+ <Separator />
+ </Menu>
+ <Menu name="Enhance"><text>Enh&amp;ance</text>
+ <Action name="implugcore_sharpen" />
+ <Action name="implugcore_blur" />
+ <Separator />
+ <Action name="implugcore_redeye" />
+ </Menu>
+
+ <Menu name="Transform" ><text>Tra&amp;nsform</text>
+ <Action name="implugcore_ratiocrop" />
+ </Menu>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_infrared" />
+ <Action name="imageplugin_filmgrain" />
+ <Action name="imageplugin_oilpaint" />
+ <Action name="imageplugin_charcoal" />
+ <Action name="imageplugin_emboss" />
+ <Action name="imageplugin_distortionfx" />
+ <Action name="imageplugin_blurfx" />
+ <Action name="imageplugin_raindrop" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties/>
+
+</kpartgui>
diff --git a/src/imageplugins/coreplugin/hsl/Makefile.am b/src/imageplugins/coreplugin/hsl/Makefile.am
new file mode 100644
index 00000000..4465d44c
--- /dev/null
+++ b/src/imageplugins/coreplugin/hsl/Makefile.am
@@ -0,0 +1,26 @@
+noinst_LTLIBRARIES = libhsl.la
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+libhsl_la_SOURCES = hsltool.cpp hspreviewwidget.cpp
+
+libhsl_la_LDFLAGS = $(all_libraries)
+
+noinst_HEADERS = hsltool.h hspreviewwidget.h
+
diff --git a/src/imageplugins/coreplugin/hsl/hsltool.cpp b/src/imageplugins/coreplugin/hsl/hsltool.cpp
new file mode 100644
index 00000000..5fa3d746
--- /dev/null
+++ b/src/imageplugins/coreplugin/hsl/hsltool.cpp
@@ -0,0 +1,453 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-16
+ * Description : digiKam image editor to adjust Hue, Saturation,
+ * and Lightness of picture.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kcolordialog.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Digikam includes.
+
+#include "colorgradientwidget.h"
+#include "dimg.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "hslmodifier.h"
+#include "hspreviewwidget.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+
+// Local includes.
+
+#include "hsltool.h"
+#include "hsltool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+HSLTool::HSLTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("adjusthsl");
+ setToolName(i18n("Hue / Saturation / Lightness"));
+ setToolIcon(SmallIcon("adjusthsl"));
+ setToolHelp("hsladjusttool.anchor");
+
+ m_destinationPreviewData = 0;
+
+ ImageIface iface(0, 0);
+ m_originalImage = iface.getOriginalImg();
+
+ m_previewWidget = new ImageWidget("hsladjust Tool", 0,
+ i18n("<p>Here you can see the image "
+ "Hue/Saturation/Lightness adjustments preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout(m_gboxSettings->plainPage(), 11, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), m_gboxSettings->plainPage());
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, m_gboxSettings->plainPage());
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap(TQPixmap(directory + "histogram-lin.png"));
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(m_gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox);
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ m_HSSelector = new KHSSelector(m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_HSSelector, i18n("<p>Select the hue and saturation adjustments of the image here."));
+ m_HSSelector->setMinimumSize(256, 142);
+ gridSettings->addMultiCellWidget(m_HSSelector, 3, 3, 0, 4);
+
+ m_HSPreview = new HSPreviewWidget(m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_HSPreview, i18n("<p>You can see here a color preview of the hue and "
+ "saturation adjustments."));
+ m_HSPreview->setMinimumSize(256, 15);
+ gridSettings->addMultiCellWidget(m_HSPreview, 4, 4, 0, 4);
+
+ TQLabel *label2 = new TQLabel(i18n("Hue:"), m_gboxSettings->plainPage());
+ m_hInput = new RDoubleNumInput(m_gboxSettings);
+ m_hInput->setPrecision(0);
+ m_hInput->setRange(-180.0, 180.0, 1.0);
+ m_hInput->setDefaultValue(0.0);
+ TQWhatsThis::add( m_hInput, i18n("<p>Set here the hue adjustment of the image."));
+ gridSettings->addMultiCellWidget(label2, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_hInput, 6, 6, 0, 4);
+
+ TQLabel *label3 = new TQLabel(i18n("Saturation:"), m_gboxSettings->plainPage());
+ m_sInput = new RDoubleNumInput(m_gboxSettings);
+ m_sInput->setPrecision(2);
+ m_sInput->setRange(-100.0, 100.0, 0.01);
+ m_sInput->setDefaultValue(0.0);
+ TQWhatsThis::add( m_sInput, i18n("<p>Set here the saturation adjustment of the image."));
+ gridSettings->addMultiCellWidget(label3, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_sInput, 8, 8, 0, 4);
+
+ TQLabel *label4 = new TQLabel(i18n("Lightness:"), m_gboxSettings->plainPage());
+ m_lInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_lInput->setPrecision(2);
+ m_lInput->setRange(-100.0, 100.0, 0.01);
+ m_lInput->setDefaultValue(0.0);
+ TQWhatsThis::add( m_lInput, i18n("<p>Set here the lightness adjustment of the image."));
+ gridSettings->addMultiCellWidget(label4, 9, 9, 0, 4);
+ gridSettings->addMultiCellWidget(m_lInput, 10, 10, 0, 4);
+
+ gridSettings->setRowStretch(11, 10);
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_HSSelector, TQ_SIGNAL(valueChanged(int, int)),
+ this, TQ_SLOT(slotHSChanged(int, int)));
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_hInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_hInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotHChanged(double)));
+
+ connect(m_sInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_sInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotSChanged(double)));
+
+ connect(m_lInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings->enableButton(EditorToolSettings::Ok, false);
+}
+
+HSLTool::~HSLTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void HSLTool::slotChannelChanged(int channel)
+{
+ switch (channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("red"));
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("green"));
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("blue"));
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void HSLTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void HSLTool::slotColorSelectedFromTarget( const DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void HSLTool::slotHSChanged(int h, int s)
+{
+ double hue = double(h);
+ if (h >= 180 && h <= 359)
+ hue = double(h) - 359.0;
+
+ double sat = ((double) s * (200.0 / 255.0)) - 100.0;
+
+ m_hInput->blockSignals(true);
+ m_sInput->blockSignals(true);
+
+ m_hInput->setValue(hue);
+ m_sInput->setValue(sat);
+
+ m_hInput->blockSignals(false);
+ m_sInput->blockSignals(false);
+
+ slotTimer();
+}
+
+void HSLTool::slotHChanged(double h)
+{
+ int hue = int(h);
+ if (h >= -180 && h < 0)
+ hue = int(h) + 359;
+
+ m_HSSelector->blockSignals(true);
+ m_HSSelector->setXValue(hue);
+ m_HSSelector->blockSignals(false);
+}
+
+void HSLTool::slotSChanged(double s)
+{
+ int sat = (int) ((s + 100.0) * (255.0 / 200.0));
+
+ m_HSSelector->blockSignals(true);
+ m_HSSelector->setYValue(sat);
+ m_HSSelector->blockSignals(false);
+}
+
+void HSLTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("hsladjust Tool");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ m_hInput->setValue(config->readDoubleNumEntry("HueAjustment", m_hInput->defaultValue()));
+ m_sInput->setValue(config->readDoubleNumEntry("SaturationAjustment", m_sInput->defaultValue()));
+ m_lInput->setValue(config->readDoubleNumEntry("LighnessAjustment", m_lInput->defaultValue()));
+ slotHChanged(m_hInput->value());
+ slotSChanged(m_sInput->value());
+
+ m_histogramWidget->reset();
+
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void HSLTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("hsladjust Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("HueAjustment", m_hInput->value());
+ config->writeEntry("SaturationAjustment", m_sInput->value());
+ config->writeEntry("LighnessAjustment", m_lInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void HSLTool::slotResetSettings()
+{
+ m_hInput->blockSignals(true);
+ m_sInput->blockSignals(true);
+ m_lInput->blockSignals(true);
+
+ m_hInput->slotReset();
+ m_sInput->slotReset();
+ m_lInput->slotReset();
+
+ slotHChanged(0.0);
+ slotSChanged(0.0);
+
+ slotEffect();
+
+ m_hInput->blockSignals(false);
+ m_sInput->blockSignals(false);
+ m_lInput->blockSignals(false);
+}
+
+void HSLTool::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ double hu = m_hInput->value();
+ double sa = m_sInput->value();
+ double lu = m_lInput->value();
+
+ m_gboxSettings->enableButton(EditorToolSettings::Ok,
+ ( hu != 0.0 || sa != 0.0 || lu != 0.0));
+
+ m_HSPreview->setHS(hu, sa);
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ DImg preview(w, h, sb, a, m_destinationPreviewData);
+ HSLModifier cmod;
+ cmod.setHue(hu);
+ cmod.setSaturation(sa);
+ cmod.setLightness(lu);
+ cmod.applyHSL(preview);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void HSLTool::finalRendering()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+
+ double hu = m_hInput->value();
+ double sa = m_sInput->value();
+ double lu = m_lInput->value();
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+ DImg original(w, h, sb, a, data);
+ delete [] data;
+
+ HSLModifier cmod;
+ cmod.setHue(hu);
+ cmod.setSaturation(sa);
+ cmod.setLightness(lu);
+ cmod.applyHSL(original);
+
+ iface->putOriginalImage(i18n("HSL Adjustments"), original.bits());
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/hsl/hsltool.h b/src/imageplugins/coreplugin/hsl/hsltool.h
new file mode 100644
index 00000000..751bacf3
--- /dev/null
+++ b/src/imageplugins/coreplugin/hsl/hsltool.h
@@ -0,0 +1,126 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-16
+ * Description : digiKam image editor to adjust Hue, Saturation,
+ * and Lightness of picture.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef HSLTOOL_H
+#define HSLTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQComboBox;
+class TQHButtonGroup;
+
+class KHSSelector;
+
+namespace KDcrawIface
+{
+class RDoubleNumInput;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class DImg;
+class EditorToolSettings;
+}
+
+namespace DigikamImagesPluginCore
+{
+class HSPreviewWidget;
+
+class HSLTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ HSLTool(TQObject *parent);
+ ~HSLTool();
+
+private slots:
+
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+ void slotHSChanged(int h, int s);
+ void slotHChanged(double h);
+ void slotSChanged(double s);
+ void slotResetSettings();
+
+private:
+
+ void writeSettings();
+ void readSettings();
+ void finalRendering();
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KDcrawIface::RDoubleNumInput *m_hInput;
+ KDcrawIface::RDoubleNumInput *m_sInput;
+ KDcrawIface::RDoubleNumInput *m_lInput;
+
+ KHSSelector *m_HSSelector;
+
+ HSPreviewWidget *m_HSPreview;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::DImg *m_originalImage;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* HSLTOOL_H */
diff --git a/src/imageplugins/coreplugin/hsl/hspreviewwidget.cpp b/src/imageplugins/coreplugin/hsl/hspreviewwidget.cpp
new file mode 100644
index 00000000..3841ca38
--- /dev/null
+++ b/src/imageplugins/coreplugin/hsl/hspreviewwidget.cpp
@@ -0,0 +1,126 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-08
+ * Description : Hue/Saturation preview widget
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdrawutil.h>
+#include <tqimage.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kimageeffect.h>
+
+// Local includes.
+
+#include "hslmodifier.h"
+#include "dimg.h"
+#include "hspreviewwidget.h"
+#include "hspreviewwidget.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+class HSPreviewWidgetPrivate
+{
+
+public:
+
+ HSPreviewWidgetPrivate()
+ {
+ hue = 0.0;
+ sat = 0.0;
+ }
+
+ int xBorder;
+
+ double hue;
+ double sat;
+
+ TQPixmap pixmap;
+};
+
+HSPreviewWidget::HSPreviewWidget(TQWidget *parent, int xBorder)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new HSPreviewWidgetPrivate;
+ d->xBorder = xBorder;
+}
+
+HSPreviewWidget::~HSPreviewWidget()
+{
+ delete d;
+}
+
+void HSPreviewWidget::setHS(double hue, double sat)
+{
+ d->hue = hue;
+ d->sat = sat;
+ updatePixmap();
+ update();
+}
+
+void HSPreviewWidget::resizeEvent( TQResizeEvent * )
+{
+ updatePixmap();
+}
+
+void HSPreviewWidget::paintEvent( TQPaintEvent * )
+{
+ bitBlt(this, 0+d->xBorder, 0, &d->pixmap);
+}
+
+void HSPreviewWidget::updatePixmap()
+{
+ int xSize = width()-2*d->xBorder;
+ int ySize = height();
+
+ Digikam::DImg image(xSize, ySize, false, false, 0, false);
+ TQColor col;
+ uint *p;
+
+ for ( int s = ySize-1; s >= 0; s-- )
+ {
+ p = (uint *)image.scanLine(ySize - s - 1);
+
+ for( int h = 0 ; h < xSize ; h++ )
+ {
+ col.setHsv( 359*h/(xSize-1), 255, 192 );
+ *p = col.rgb();
+ p++;
+ }
+ }
+
+ Digikam::HSLModifier cmod;
+ cmod.setHue(d->hue);
+ cmod.setSaturation(d->sat);
+ cmod.setLightness(0.0);
+ cmod.applyHSL(image);
+
+ d->pixmap = image.convertToPixmap();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/hsl/hspreviewwidget.h b/src/imageplugins/coreplugin/hsl/hspreviewwidget.h
new file mode 100644
index 00000000..3744907c
--- /dev/null
+++ b/src/imageplugins/coreplugin/hsl/hspreviewwidget.h
@@ -0,0 +1,64 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-08
+ * Description : Hue/Saturation preview widget
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef HSPREVIEWWIDGET_H
+#define HSPREVIEWWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace DigikamImagesPluginCore
+{
+
+class HSPreviewWidgetPrivate;
+
+class HSPreviewWidget : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ HSPreviewWidget(TQWidget *parent=0, int xBorder=0);
+ ~HSPreviewWidget();
+
+ void setHS(double hue, double sat);
+
+protected:
+
+ void resizeEvent( TQResizeEvent * );
+ void paintEvent( TQPaintEvent * );
+
+private:
+
+ void updatePixmap();
+
+private:
+
+ HSPreviewWidgetPrivate *d;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* HSPREVIEWWIDGET_H */
diff --git a/src/imageplugins/coreplugin/hsl/imageeffect_hsl.cpp b/src/imageplugins/coreplugin/hsl/imageeffect_hsl.cpp
new file mode 100644
index 00000000..e179d36f
--- /dev/null
+++ b/src/imageplugins/coreplugin/hsl/imageeffect_hsl.cpp
@@ -0,0 +1,428 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-16
+ * Description : digiKam image editor to adjust Hue, Saturation,
+ * and Lightness of picture.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqvbox.h>
+#include <tqhbuttongroup.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <knuminput.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kstandarddirs.h>
+#include <kcolordialog.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "hslmodifier.h"
+#include "dimg.h"
+
+// Local includes.
+
+#include "hspreviewwidget.h"
+#include "imageeffect_hsl.h"
+#include "imageeffect_hsl.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_HSL::ImageEffect_HSL(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Hue/Saturation/Lightness"), "hsladjust", false)
+{
+ m_destinationPreviewData = 0L;
+ setHelp("hsladjusttool.anchor", "digikam");
+
+ m_previewWidget = new Digikam::ImageWidget("hsladjust Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the image "
+ "Hue/Saturation/Lightness adjustments preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings, 11, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ m_HSSelector = new KHSSelector(gboxSettings);
+ TQWhatsThis::add( m_HSSelector, i18n("<p>Select the hue and saturation adjustments of the image here."));
+ m_HSSelector->setMinimumSize(256, 142);
+ gridSettings->addMultiCellWidget(m_HSSelector, 3, 3, 0, 4);
+
+ m_HSPreview = new HSPreviewWidget(gboxSettings, spacingHint());
+ TQWhatsThis::add( m_HSPreview, i18n("<p>You can see here a color preview of the hue and "
+ "saturation adjustments."));
+ m_HSPreview->setMinimumSize(256, 15);
+ gridSettings->addMultiCellWidget(m_HSPreview, 4, 4, 0, 4);
+
+ TQLabel *label2 = new TQLabel(i18n("Hue:"), gboxSettings);
+ m_hInput = new KDoubleNumInput(gboxSettings);
+ m_hInput->setPrecision(0);
+ m_hInput->setRange(-180.0, 180.0, 1.0, true);
+ m_hInput->setValue(0.0);
+ TQWhatsThis::add( m_hInput, i18n("<p>Set here the hue adjustment of the image."));
+ gridSettings->addMultiCellWidget(label2, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_hInput, 6, 6, 0, 4);
+
+ TQLabel *label3 = new TQLabel(i18n("Saturation:"), gboxSettings);
+ m_sInput = new KDoubleNumInput(gboxSettings);
+ m_sInput->setPrecision(2);
+ m_sInput->setRange(-100.0, 100.0, 0.01, true);
+ m_sInput->setValue(0.0);
+ TQWhatsThis::add( m_sInput, i18n("<p>Set here the saturation adjustment of the image."));
+ gridSettings->addMultiCellWidget(label3, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_sInput, 8, 8, 0, 4);
+
+ TQLabel *label4 = new TQLabel(i18n("Lightness:"), gboxSettings);
+ m_lInput = new KDoubleNumInput(gboxSettings);
+ m_lInput->setPrecision(2);
+ m_lInput->setRange(-100.0, 100.0, 0.01, true);
+ m_lInput->setValue(0.0);
+ TQWhatsThis::add( m_lInput, i18n("<p>Set here the lightness adjustment of the image."));
+ gridSettings->addMultiCellWidget(label4, 9, 9, 0, 4);
+ gridSettings->addMultiCellWidget(m_lInput, 10, 10, 0, 4);
+
+ gridSettings->setRowStretch(11, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_HSSelector, TQ_SIGNAL(valueChanged(int, int)),
+ this, TQ_SLOT(slotHSChanged(int, int)));
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_hInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_hInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotHChanged(double)));
+
+ connect(m_sInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_sInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotSChanged(double)));
+
+ connect(m_lInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+
+ enableButtonOK( false );
+}
+
+ImageEffect_HSL::~ImageEffect_HSL()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_previewWidget;
+}
+
+void ImageEffect_HSL::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_HSL::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_HSL::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_HSL::slotHSChanged(int h, int s)
+{
+ double hue = double(h);
+ if (h >= 180 && h <= 359)
+ hue = double(h) - 359.0;
+
+ double sat = ((double)s * (200.0/255.0)) - 100.0;
+
+ m_hInput->blockSignals(true);
+ m_sInput->blockSignals(true);
+ m_hInput->setValue(hue);
+ m_sInput->setValue(sat);
+ m_hInput->blockSignals(false);
+ m_sInput->blockSignals(false);
+ slotTimer();
+}
+
+void ImageEffect_HSL::slotHChanged(double h)
+{
+ int hue = int(h);
+ if (h >= -180 && h < 0)
+ hue = int(h) + 359;
+
+ m_HSSelector->blockSignals(true);
+ m_HSSelector->setXValue(hue);
+ m_HSSelector->blockSignals(false);
+}
+
+void ImageEffect_HSL::slotSChanged(double s)
+{
+ int sat = (int)((s + 100.0) * (255.0/200.0));
+
+ m_HSSelector->blockSignals(true);
+ m_HSSelector->setYValue(sat);
+ m_HSSelector->blockSignals(false);
+}
+
+void ImageEffect_HSL::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("hsladjust Tool Dialog");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+ m_hInput->setValue(config->readDoubleNumEntry("HueAjustment", 0.0));
+ m_sInput->setValue(config->readDoubleNumEntry("SaturationAjustment", 0.0));
+ m_lInput->setValue(config->readDoubleNumEntry("LighnessAjustment", 0.0));
+ slotHChanged(m_hInput->value());
+ slotSChanged(m_sInput->value());
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ImageEffect_HSL::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("hsladjust Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("HueAjustment", m_hInput->value());
+ config->writeEntry("SaturationAjustment", m_sInput->value());
+ config->writeEntry("LighnessAjustment", m_lInput->value());
+ config->sync();
+}
+
+void ImageEffect_HSL::resetValues()
+{
+ m_hInput->blockSignals(true);
+ m_sInput->blockSignals(true);
+ m_lInput->blockSignals(true);
+ m_hInput->setValue(0.0);
+ m_sInput->setValue(0.0);
+ m_lInput->setValue(0.0);
+ slotHChanged(0.0);
+ slotSChanged(0.0);
+ m_hInput->blockSignals(false);
+ m_sInput->blockSignals(false);
+ m_lInput->blockSignals(false);
+}
+
+void ImageEffect_HSL::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ double hu = m_hInput->value();
+ double sa = m_sInput->value();
+ double lu = m_lInput->value();
+
+ enableButtonOK( hu != 0.0 || sa != 0.0 || lu != 0.0);
+
+ m_HSPreview->setHS(hu, sa);
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ Digikam::DImg preview(w, h, sb, a, m_destinationPreviewData);
+ Digikam::HSLModifier cmod;
+ cmod.setHue(hu);
+ cmod.setSaturation(sa);
+ cmod.setLightness(lu);
+ cmod.applyHSL(preview);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void ImageEffect_HSL::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ double hu = m_hInput->value();
+ double sa = m_sInput->value();
+ double lu = m_lInput->value();
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+ Digikam::DImg original(w, h, sb, a, data);
+ delete [] data;
+
+ Digikam::HSLModifier cmod;
+ cmod.setHue(hu);
+ cmod.setSaturation(sa);
+ cmod.setLightness(lu);
+ cmod.applyHSL(original);
+
+ iface->putOriginalImage(i18n("HSL Adjustments"), original.bits());
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/hsl/imageeffect_hsl.h b/src/imageplugins/coreplugin/hsl/imageeffect_hsl.h
new file mode 100644
index 00000000..24880f4d
--- /dev/null
+++ b/src/imageplugins/coreplugin/hsl/imageeffect_hsl.h
@@ -0,0 +1,116 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-16
+ * Description : digiKam image editor to adjust Hue, Saturation,
+ * and Lightness of picture.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_HSL_H
+#define IMAGEEFFECT_HSL_H
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQComboBox;
+class TQHButtonGroup;
+
+class KDoubleNumInput;
+class KHSSelector;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamImagesPluginCore
+{
+class HSPreviewWidget;
+
+class ImageEffect_HSL : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_HSL(TQWidget *parent);
+ ~ImageEffect_HSL();
+
+private slots:
+
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+ void slotHSChanged(int h, int s);
+ void slotHChanged(double h);
+ void slotSChanged(double s);
+
+private:
+
+ void writeUserSettings();
+ void readUserSettings();
+ void resetValues();
+ void finalRendering();
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KDoubleNumInput *m_hInput;
+ KDoubleNumInput *m_sInput;
+ KDoubleNumInput *m_lInput;
+
+ KHSSelector *m_HSSelector;
+
+ HSPreviewWidget *m_HSPreview;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_HSL_H */
diff --git a/src/imageplugins/coreplugin/iccprooftool.cpp b/src/imageplugins/coreplugin/iccprooftool.cpp
new file mode 100644
index 00000000..b74adf9a
--- /dev/null
+++ b/src/imageplugins/coreplugin/iccprooftool.cpp
@@ -0,0 +1,1310 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-21
+ * Description : digiKam image editor tool to correct picture
+ * colors using an ICC color profile
+ *
+ * Copyright (C) 2005-2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqfile.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbox.h>
+#include <tqhbuttongroup.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpoint.h>
+#include <tqpushbutton.h>
+#include <tqradiobutton.h>
+#include <tqtextstream.h>
+#include <tqtoolbox.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvbuttongroup.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdefile.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <ksqueezedtextlabel.h>
+#include <kstandarddirs.h>
+#include <ktabwidget.h>
+#include <kurllabel.h>
+#include <kurlrequester.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Digikam includes.
+
+#include "bcgmodifier.h"
+#include "colorgradientwidget.h"
+#include "curveswidget.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "iccpreviewwidget.h"
+#include "iccprofileinfodlg.h"
+#include "icctransform.h"
+#include "imagecurves.h"
+#include "imagehistogram.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+
+// Local includes.
+
+#include "iccprooftool.h"
+#include "iccprooftool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+ICCProofTool::ICCProofTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("colormanagement");
+ setToolName(i18n("Color Management"));
+ setToolIcon(SmallIcon("colormanagement"));
+ setToolHelp("colormanagement.anchor");
+
+ m_destinationPreviewData = 0;
+ m_cmEnabled = true;
+ m_hasICC = false;
+
+ ImageIface iface(0, 0);
+ m_originalImage = iface.getOriginalImg();
+ m_embeddedICC = iface.getEmbeddedICCFromOriginalImage();
+
+ m_previewWidget = new ImageWidget("colormanagement Tool",0,
+ i18n("<p>Here you can see the image preview after "
+ "applying a color profile</p>"));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout *gridSettings = new TQGridLayout(m_gboxSettings->plainPage(), 3, 2);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel: "), m_gboxSettings->plainPage());
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, m_gboxSettings->plainPage());
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red channel values.<p>"
+ "<b>Green</b>: display the green channel values.<p>"
+ "<b>Blue</b>: display the blue channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+ TQWhatsThis::add(m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal values are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal values are big; "
+ "if it is used, all values (small and large) will be visible on the "
+ "graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap(TQPixmap(directory + "histogram-lin.png"));
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(m_gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram "
+ "of the selected image channel. "
+ "This one is updated after setting changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget( ColorGradientWidget::Horizontal, 10,
+ histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 2);
+
+ // -------------------------------------------------------------
+
+ m_toolBoxWidgets = new TQToolBox(m_gboxSettings->plainPage());
+ TQWidget *generalOptions = new TQWidget(m_toolBoxWidgets);
+ TQWidget *inProfiles = new TQWidget(m_toolBoxWidgets);
+ TQWidget *spaceProfiles = new TQWidget(m_toolBoxWidgets);
+ TQWidget *proofProfiles = new TQWidget(m_toolBoxWidgets);
+ TQWidget *lightnessadjust = new TQWidget(m_toolBoxWidgets);
+
+ //---------- "General" Page Setup ----------------------------------
+
+ m_toolBoxWidgets->insertItem(GENERALPAGE, generalOptions,
+ SmallIconSet("misc"), i18n("General Settings"));
+ TQWhatsThis::add(generalOptions, i18n("<p>Here you can set general parameters.</p>"));
+
+ TQGridLayout *zeroPageLayout = new TQGridLayout(generalOptions, 5, 1);
+
+ m_doSoftProofBox = new TQCheckBox(generalOptions);
+ m_doSoftProofBox->setText(i18n("Soft-proofing"));
+ TQWhatsThis::add(m_doSoftProofBox, i18n("<p>Rendering emulation of the device described "
+ "by the \"Proofing\" profile. Useful to preview the final "
+ "result without rendering to physical medium.</p>"));
+
+ m_checkGamutBox = new TQCheckBox(generalOptions);
+ m_checkGamutBox->setText(i18n("Check gamut"));
+ TQWhatsThis::add(m_checkGamutBox, i18n("<p>You can use this option if you want to show "
+ "the colors that are outside the printer's gamut<p>"));
+
+ m_embeddProfileBox = new TQCheckBox(generalOptions);
+ m_embeddProfileBox->setChecked(true);
+ m_embeddProfileBox->setText(i18n("Assign profile"));
+ TQWhatsThis::add(m_embeddProfileBox, i18n("<p>You can use this option to embed "
+ "the selected workspace color profile into the image.</p>"));
+
+ m_BPCBox = new TQCheckBox(generalOptions);
+ m_BPCBox->setText(i18n("Use BPC"));
+ TQWhatsThis::add(m_BPCBox, i18n("<p>The Black Point Compensation (BPC) feature does work in conjunction "
+ "with Relative Colorimetric Intent. Perceptual intent should make no "
+ "difference, since BPC is always on, and in Absolute Colorimetric "
+ "Intent it is always turned off.</p>"
+ "<p>BPC does compensate for a lack of ICC profiles in the dark tone rendering. "
+ "With BPC the dark tones are optimally mapped (no clipping) from original media "
+ "to the destination rendering media, e.g. the combination of paper and ink.</p>"));
+
+ TQLabel *intent = new TQLabel(i18n("Rendering Intent:"), generalOptions);
+ m_renderingIntentsCB = new RComboBox(generalOptions);
+ m_renderingIntentsCB->insertItem("Perceptual");
+ m_renderingIntentsCB->insertItem("Absolute Colorimetric");
+ m_renderingIntentsCB->insertItem("Relative Colorimetric");
+ m_renderingIntentsCB->insertItem("Saturation");
+ m_renderingIntentsCB->setDefaultItem(0);
+ TQWhatsThis::add( m_renderingIntentsCB, i18n("<ul><li>Perceptual intent causes the full gamut "
+ "of the image to be compressed or expanded to fill the gamut of the destination media, "
+ "so that gray balance is preserved but colorimetric accuracy may not be preserved.<br>"
+ "In other words, if certain colors in an image fall outside of the range of colors that "
+ "the output device can render, the image intent will cause all the colors in the image "
+ "to be adjusted so that every color in the image falls within the range that can be "
+ "rendered and so that the relationship between colors is preserved as much as possible.<br>"
+ "This intent is most suitable for display of photographs and images, and is the default "
+ "intent.</li>"
+ "<li> Absolute Colorimetric intent causes any colors that fall outside the range that the "
+ "output device can render to be adjusted to the closest color that can be rendered, while all "
+ "other colors are left unchanged.<br>"
+ "This intent preserves the white point and is most suitable for spot colors (Pantone, "
+ "TruMatch, logo colors, ...).</li>"
+ "<li>Relative Colorimetric intent is defined such that any colors that fall outside the "
+ "range that the output device can render are adjusted to the closest color that can be "
+ "rendered, while all other colors are left unchanged. Proof intent does not preserve "
+ "the white point.</li>"
+ "<li>Saturation intent preserves the saturation of colors in the image at the possible "
+ "expense of hue and lightness.<br>"
+ "Implementation of this intent remains somewhat problematic, and the ICC is still working "
+ "on methods to achieve the desired effects.<br>"
+ "This intent is most suitable for business graphics such as charts, where it is more "
+ "important that the colors be vivid and contrast well with each other rather than a "
+ "specific color.</li></ul>"));
+
+ KURLLabel *lcmsLogoLabel = new KURLLabel(generalOptions);
+ lcmsLogoLabel->setAlignment(AlignTop | AlignRight);
+ lcmsLogoLabel->setText(TQString());
+ lcmsLogoLabel->setURL("http://www.littlecms.com");
+ TDEGlobal::dirs()->addResourceType("logo-lcms", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("logo-lcms", "logo-lcms.png");
+ lcmsLogoLabel->setPixmap(TQPixmap(directory + "logo-lcms.png"));
+ TQToolTip::add(lcmsLogoLabel, i18n("Visit Little CMS project website"));
+
+ zeroPageLayout->addMultiCellWidget(m_doSoftProofBox, 0, 0, 0, 0);
+ zeroPageLayout->addMultiCellWidget(m_checkGamutBox, 1, 1, 0, 0);
+ zeroPageLayout->addMultiCellWidget(m_embeddProfileBox, 2, 2, 0, 0);
+ zeroPageLayout->addMultiCellWidget(lcmsLogoLabel, 0, 2, 1, 1);
+ zeroPageLayout->addMultiCellWidget(m_BPCBox, 3, 3, 0, 0);
+ zeroPageLayout->addMultiCellWidget(intent, 4, 4, 0, 0);
+ zeroPageLayout->addMultiCellWidget(m_renderingIntentsCB, 4, 4, 1, 1);
+ zeroPageLayout->setRowStretch(5, 10);
+
+ //---------- "Input" Page Setup ----------------------------------
+
+ m_toolBoxWidgets->insertItem(INPUTPAGE, inProfiles, SmallIconSet("camera-photo"), i18n("Input Profile"));
+ TQWhatsThis::add(inProfiles, i18n("<p>Set here all parameters relevant of Input Color "
+ "Profiles.</p>"));
+
+ TQGridLayout *firstPageLayout = new TQGridLayout(inProfiles, 4, 2);
+
+ m_inProfileBG = new TQButtonGroup(4, TQt::Vertical, inProfiles);
+ m_inProfileBG->setFrameStyle(TQFrame::NoFrame);
+ m_inProfileBG->setInsideMargin(0);
+
+ m_useEmbeddedProfile = new TQRadioButton(m_inProfileBG);
+ m_useEmbeddedProfile->setText(i18n("Use embedded profile"));
+
+ m_useSRGBDefaultProfile = new TQRadioButton(m_inProfileBG);
+ m_useSRGBDefaultProfile->setText(i18n("Use builtin sRGB profile"));
+ m_useSRGBDefaultProfile->setChecked(true);
+
+ m_useInDefaultProfile = new TQRadioButton(m_inProfileBG);
+ m_useInDefaultProfile->setText(i18n("Use default profile"));
+
+ m_useInSelectedProfile = new TQRadioButton(m_inProfileBG);
+ m_useInSelectedProfile->setText(i18n("Use selected profile"));
+
+ m_inProfilesPath = new KURLRequester(inProfiles);
+ m_inProfilesPath->setMode(KFile::File|KFile::ExistingOnly);
+ m_inProfilesPath->setFilter("*.icc *.icm|"+i18n("ICC Files (*.icc; *.icm)"));
+ KFileDialog *inProfiles_dialog = m_inProfilesPath->fileDialog();
+ m_iccInPreviewWidget = new ICCPreviewWidget(inProfiles_dialog);
+ inProfiles_dialog->setPreviewWidget(m_iccInPreviewWidget);
+
+ TQPushButton *inProfilesInfo = new TQPushButton(i18n("Info..."), inProfiles);
+
+ TQGroupBox *pictureInfo = new TQGroupBox(2, TQt::Horizontal, i18n("Camera information"), inProfiles);
+ new TQLabel(i18n("Make:"), pictureInfo);
+ KSqueezedTextLabel *make = new KSqueezedTextLabel(0, pictureInfo);
+ new TQLabel(i18n("Model:"), pictureInfo);
+ KSqueezedTextLabel *model = new KSqueezedTextLabel(0, pictureInfo);
+ make->setText(iface.getPhotographInformations().make);
+ model->setText(iface.getPhotographInformations().model);
+
+ firstPageLayout->addMultiCellWidget(m_inProfileBG, 0, 1, 0, 0);
+ firstPageLayout->addMultiCellWidget(inProfilesInfo, 0, 0, 2, 2);
+ firstPageLayout->addMultiCellWidget(m_inProfilesPath, 2, 2, 0, 2);
+ firstPageLayout->addMultiCellWidget(pictureInfo, 3, 3, 0, 2);
+ firstPageLayout->setColStretch(1, 10);
+ firstPageLayout->setRowStretch(4, 10);
+
+ //---------- "Workspace" Page Setup ---------------------------------
+
+ m_toolBoxWidgets->insertItem(WORKSPACEPAGE, spaceProfiles,
+ SmallIconSet("input-tablet"), i18n("Workspace Profile"));
+ TQWhatsThis::add(spaceProfiles, i18n("<p>Set here all parameters relevant to Color Workspace "
+ "Profiles.</p>"));
+
+ TQGridLayout *secondPageLayout = new TQGridLayout(spaceProfiles, 3, 2);
+
+ m_spaceProfileBG = new TQButtonGroup(2, TQt::Vertical, spaceProfiles);
+ m_spaceProfileBG->setFrameStyle(TQFrame::NoFrame);
+ m_spaceProfileBG->setInsideMargin(0);
+
+ m_useSpaceDefaultProfile = new TQRadioButton(m_spaceProfileBG);
+ m_useSpaceDefaultProfile->setText(i18n("Use default workspace profile"));
+
+ m_useSpaceSelectedProfile = new TQRadioButton(m_spaceProfileBG);
+ m_useSpaceSelectedProfile->setText(i18n("Use selected profile"));
+
+ m_spaceProfilePath = new KURLRequester(spaceProfiles);
+ m_spaceProfilePath->setMode(KFile::File|KFile::ExistingOnly);
+ m_spaceProfilePath->setFilter("*.icc *.icm|"+i18n("ICC Files (*.icc; *.icm)"));
+ KFileDialog *spaceProfiles_dialog = m_spaceProfilePath->fileDialog();
+ m_iccSpacePreviewWidget = new ICCPreviewWidget(spaceProfiles_dialog);
+ spaceProfiles_dialog->setPreviewWidget(m_iccSpacePreviewWidget);
+
+ TQPushButton *spaceProfilesInfo = new TQPushButton(i18n("Info..."), spaceProfiles);
+
+ secondPageLayout->addMultiCellWidget(m_spaceProfileBG, 0, 1, 0, 0);
+ secondPageLayout->addMultiCellWidget(spaceProfilesInfo, 0, 0, 2, 2);
+ secondPageLayout->addMultiCellWidget(m_spaceProfilePath, 2, 2, 0, 2);
+ secondPageLayout->setColStretch(1, 10);
+ secondPageLayout->setRowStretch(3, 10);
+
+ //---------- "Proofing" Page Setup ---------------------------------
+
+ m_toolBoxWidgets->insertItem(PROOFINGPAGE, proofProfiles,
+ SmallIconSet("printer"), i18n("Proofing Profile"));
+ TQWhatsThis::add(proofProfiles, i18n("<p>Set here all parameters relevant to Proofing Color "
+ "Profiles.</p>"));
+
+ TQGridLayout *thirdPageLayout = new TQGridLayout(proofProfiles, 3, 2);
+
+ m_proofProfileBG = new TQButtonGroup(2, TQt::Vertical, proofProfiles);
+ m_proofProfileBG->setFrameStyle(TQFrame::NoFrame);
+ m_proofProfileBG->setInsideMargin(0);
+
+ m_useProofDefaultProfile = new TQRadioButton(m_proofProfileBG);
+ m_useProofDefaultProfile->setText(i18n("Use default proof profile"));
+
+ m_useProofSelectedProfile = new TQRadioButton(m_proofProfileBG);
+ m_useProofSelectedProfile->setText(i18n("Use selected profile"));
+
+ m_proofProfilePath = new KURLRequester(proofProfiles);
+ m_proofProfilePath->setMode(KFile::File|KFile::ExistingOnly);
+ m_proofProfilePath->setFilter("*.icc *.icm|"+i18n("ICC Files (*.icc; *.icm)"));
+ KFileDialog *proofProfiles_dialog = m_proofProfilePath->fileDialog();
+ m_iccProofPreviewWidget = new ICCPreviewWidget(proofProfiles_dialog);
+ proofProfiles_dialog->setPreviewWidget(m_iccProofPreviewWidget);
+
+ TQPushButton *proofProfilesInfo = new TQPushButton(i18n("Info..."), proofProfiles);
+
+ thirdPageLayout->addMultiCellWidget(m_proofProfileBG, 0, 1, 0, 0);
+ thirdPageLayout->addMultiCellWidget(proofProfilesInfo, 0, 0, 2, 2);
+ thirdPageLayout->addMultiCellWidget(m_proofProfilePath, 2, 2, 0, 2);
+ thirdPageLayout->setColStretch(1, 10);
+ thirdPageLayout->setRowStretch(3, 10);
+
+ //---------- "Lightness" Page Setup ----------------------------------
+
+ m_toolBoxWidgets->insertItem(LIGHTNESSPAGE, lightnessadjust,
+ SmallIconSet("blend"), i18n("Lightness Adjustments"));
+ TQWhatsThis::add(lightnessadjust, i18n("<p>Set here all lightness adjustments to the target image.</p>"));
+
+ TQGridLayout *fourPageLayout = new TQGridLayout( lightnessadjust, 5, 2);
+
+ ColorGradientWidget* vGradient = new ColorGradientWidget(ColorGradientWidget::Vertical,
+ 10, lightnessadjust );
+ vGradient->setColors(TQColor("white"), TQColor("black"));
+
+ TQLabel *spacev = new TQLabel(lightnessadjust);
+ spacev->setFixedWidth(1);
+
+ m_curvesWidget = new CurvesWidget(256, 192, m_originalImage->bits(), m_originalImage->width(),
+ m_originalImage->height(), m_originalImage->sixteenBit(),
+ lightnessadjust);
+ TQWhatsThis::add( m_curvesWidget, i18n("<p>This is the curve adjustment of the image luminosity"));
+
+ TQLabel *spaceh = new TQLabel(lightnessadjust);
+ spaceh->setFixedHeight(1);
+
+ ColorGradientWidget *hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal,
+ 10,
+ lightnessadjust);
+
+ hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ m_cInput = new RIntNumInput(lightnessadjust);
+ m_cInput->input()->setLabel(i18n("Contrast:"), AlignLeft | AlignVCenter);
+ m_cInput->setRange(-100, 100, 1);
+ m_cInput->setDefaultValue(0);
+ TQWhatsThis::add( m_cInput, i18n("<p>Set here the contrast adjustment of the image."));
+
+ fourPageLayout->addMultiCellWidget(vGradient, 0, 0, 0, 0);
+ fourPageLayout->addMultiCellWidget(spacev, 0, 0, 1, 1);
+ fourPageLayout->addMultiCellWidget(m_curvesWidget, 0, 0, 2, 2);
+ fourPageLayout->addMultiCellWidget(spaceh, 1, 1, 2, 2);
+ fourPageLayout->addMultiCellWidget(hGradient, 2, 2, 2, 2);
+ fourPageLayout->addMultiCellWidget(m_cInput, 4, 4, 0, 2);
+// fourPageLayout->setRowSpacing(3);
+ fourPageLayout->setRowStretch(5, 10);
+
+ // -------------------------------------------------------------
+
+ gridSettings->addMultiCellWidget(m_toolBoxWidgets, 3, 3, 0, 2);
+
+ setToolSettings(m_gboxSettings);
+ m_gboxSettings->enableButton(EditorToolSettings::Ok, false);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(lcmsLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processLCMSURL(const TQString&)));
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_curvesWidget, TQ_SIGNAL(signalCurvesChanged()),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_cInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_renderingIntentsCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- Check box options connections -------------------------------------------
+
+ connect(m_doSoftProofBox, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_checkGamutBox, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_BPCBox, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- Button Group ICC profile options connections ----------------------------
+
+ connect(m_inProfileBG, TQ_SIGNAL(released (int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_spaceProfileBG, TQ_SIGNAL(released (int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_proofProfileBG, TQ_SIGNAL(released (int)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- url requester ICC profile connections -----------------------------------
+
+ connect(m_inProfilesPath, TQ_SIGNAL(urlSelected(const TQString&)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_spaceProfilePath, TQ_SIGNAL(urlSelected(const TQString&)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_proofProfilePath, TQ_SIGNAL(urlSelected(const TQString&)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- Image preview widget connections ----------------------------
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotSpotColorChanged( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ //-- ICC profile preview connections -----------------------------
+
+ connect(inProfilesInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotInICCInfo()));
+
+ connect(spaceProfilesInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotSpaceICCInfo()));
+
+ connect(proofProfilesInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotProofICCInfo()));
+}
+
+ICCProofTool::~ICCProofTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void ICCProofTool::readSettings()
+{
+ TQString defaultICCPath = TDEGlobalSettings::documentPath();
+ TDEConfig* config = kapp->config();
+
+ // General settings of digiKam Color Management
+ config->setGroup("Color Management");
+
+ if (!config->readBoolEntry("EnableCM", false))
+ {
+ m_cmEnabled = false;
+ slotToggledWidgets(false);
+ }
+ else
+ {
+ m_inPath = config->readPathEntry("InProfileFile");
+ m_spacePath = config->readPathEntry("WorkProfileFile");
+ m_proofPath = config->readPathEntry("ProofProfileFile");
+
+ if (TQFile::exists(config->readPathEntry("DefaultPath")))
+ {
+ defaultICCPath = config->readPathEntry("DefaultPath");
+ }
+ else
+ {
+ TQString message = i18n("The ICC profiles path seems to be invalid. You won't be able to use the \"Default profile\"\
+ options.<p>Please fix this in the digiKam ICC setup.");
+ slotToggledWidgets( false );
+ KMessageBox::information(kapp->activeWindow(), message);
+ }
+ }
+
+ // Plugin settings.
+ config->setGroup("colormanagement Tool");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ m_toolBoxWidgets->setCurrentIndex(config->readNumEntry("Settings Tab", GENERALPAGE));
+ m_inProfilesPath->setURL(config->readPathEntry("InputProfilePath", defaultICCPath));
+ m_proofProfilePath->setURL(config->readPathEntry("ProofProfilePath", defaultICCPath));
+ m_spaceProfilePath->setURL(config->readPathEntry("SpaceProfilePath", defaultICCPath));
+ m_renderingIntentsCB->setCurrentItem(config->readNumEntry("RenderingIntent", m_renderingIntentsCB->defaultItem()));
+ m_doSoftProofBox->setChecked(config->readBoolEntry("DoSoftProof", false));
+ m_checkGamutBox->setChecked(config->readBoolEntry("CheckGamut", false));
+ m_embeddProfileBox->setChecked(config->readBoolEntry("EmbeddProfile", true));
+ m_BPCBox->setChecked(config->readBoolEntry("BPC", true));
+ m_inProfileBG->setButton(config->readNumEntry("InputProfileMethod", 0));
+ m_spaceProfileBG->setButton(config->readNumEntry("SpaceProfileMethod", 0));
+ m_proofProfileBG->setButton(config->readNumEntry("ProofProfileMethod", 0));
+ m_cInput->setValue(config->readNumEntry("ContrastAjustment", m_cInput->defaultValue()));
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesChannelReset(i);
+
+ m_curvesWidget->curves()->setCurveType(m_curvesWidget->m_channelType, ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p = config->readPointEntry(TQString("CurveAjustmentPoint%1").arg(j), &disable);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, j, p);
+ }
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesCalculateCurve(i);
+
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ICCProofTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("colormanagement Tool");
+ config->writeEntry("Settings Tab", m_toolBoxWidgets->currentIndex());
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writePathEntry("InputProfilePath", m_inProfilesPath->url());
+ config->writePathEntry("ProofProfilePath", m_proofProfilePath->url());
+ config->writePathEntry("SpaceProfilePath", m_spaceProfilePath->url());
+ config->writeEntry("RenderingIntent", m_renderingIntentsCB->currentItem());
+ config->writeEntry("DoSoftProof", m_doSoftProofBox->isChecked());
+ config->writeEntry("CheckGamut", m_checkGamutBox->isChecked());
+ config->writeEntry("EmbeddProfile", m_embeddProfileBox->isChecked());
+ config->writeEntry("BPC", m_BPCBox->isChecked());
+ config->writeEntry("InputProfileMethod", m_inProfileBG->selectedId());
+ config->writeEntry("SpaceProfileMethod", m_spaceProfileBG->selectedId());
+ config->writeEntry("ProofProfileMethod", m_proofProfileBG->selectedId());
+ config->writeEntry("ContrastAjustment", m_cInput->value());
+
+ for (int j = 0; j < 17; j++)
+ {
+ TQPoint p = m_curvesWidget->curves()->getCurvePoint(ImageHistogram::ValueChannel, j);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x() / 255);
+ p.setY(p.y() / 255);
+ }
+
+ config->writeEntry(TQString("CurveAjustmentPoint%1").arg(j), p);
+ }
+
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void ICCProofTool::processLCMSURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void ICCProofTool::slotSpotColorChanged(const DColor &color)
+{
+ m_curvesWidget->setCurveGuide(color);
+}
+
+void ICCProofTool::slotColorSelectedFromTarget( const DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ICCProofTool::slotChannelChanged( int channel )
+{
+ switch (channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("red"));
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("green"));
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("blue"));
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ICCProofTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ICCProofTool::slotResetSettings()
+{
+ m_cInput->blockSignals(true);
+ m_renderingIntentsCB->blockSignals(true);
+
+ m_cInput->slotReset();
+ m_renderingIntentsCB->slotReset();
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesChannelReset(i);
+
+ m_curvesWidget->reset();
+ m_cInput->blockSignals(false);
+ m_renderingIntentsCB->blockSignals(false);
+}
+
+void ICCProofTool::slotEffect()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+ m_gboxSettings->enableButton(EditorToolSettings::Ok, true);
+ m_histogramWidget->stopHistogramComputation();
+
+ IccTransform transform;
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ ImageIface *iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ DImg preview(w, h, sb, a, m_destinationPreviewData);
+
+ TQString tmpInPath = TQString();
+ TQString tmpProofPath = TQString();
+ TQString tmpSpacePath = TQString();
+
+ bool proofCondition = false;
+ bool spaceCondition = false;
+
+ //-- Input profile parameters ------------------
+
+ if (useDefaultInProfile())
+ {
+ tmpInPath = m_inPath;
+ }
+ else if (useSelectedInProfile())
+ {
+ tmpInPath = m_inProfilesPath->url();
+ TQFileInfo info(tmpInPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile())
+ {
+ KMessageBox::information(kapp->activeWindow(),
+ i18n("<p>The selected ICC input profile path seems to be invalid.<p>"
+ "Please check it."));
+ return;
+ }
+ }
+
+ //-- Proof profile parameters ------------------
+
+ if (useDefaultProofProfile())
+ {
+ tmpProofPath = m_proofPath;
+ }
+ else
+ {
+ tmpProofPath = m_proofProfilePath->url();
+ TQFileInfo info(tmpProofPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile())
+ {
+ KMessageBox::information(kapp->activeWindow(),
+ i18n("<p>The selected ICC proof profile path seems to be invalid.<p>"
+ "Please check it."));
+ return;
+ }
+ }
+
+ if (m_doSoftProofBox->isChecked())
+ proofCondition = tmpProofPath.isEmpty();
+
+ //-- Workspace profile parameters --------------
+
+ if (useDefaultSpaceProfile())
+ {
+ tmpSpacePath = m_spacePath;
+ }
+ else
+ {
+ tmpSpacePath = m_spaceProfilePath->url();
+ TQFileInfo info(tmpSpacePath);
+ if (!info.exists() || !info.isReadable() || !info.isFile())
+ {
+ KMessageBox::information(kapp->activeWindow(),
+ i18n("<p>Selected ICC workspace profile path seems to be invalid.<p>"
+ "Please check it."));
+ return;
+ }
+ }
+
+ spaceCondition = tmpSpacePath.isEmpty();
+
+ //-- Perform the color transformations ------------------
+
+ transform.getTransformType(m_doSoftProofBox->isChecked());
+
+ if (m_doSoftProofBox->isChecked())
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles(tmpSpacePath, tmpProofPath, true);
+ }
+ else
+ {
+ transform.setProfiles(tmpInPath, tmpSpacePath, tmpProofPath);
+ }
+ }
+ else
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles(tmpSpacePath);
+ }
+ else
+ {
+ transform.setProfiles(tmpInPath, tmpSpacePath);
+ }
+ }
+
+ if ( proofCondition || spaceCondition )
+ {
+ kapp->restoreOverrideCursor();
+ TQString error = i18n("<p>Your settings are not sufficient.</p>"
+ "<p>To apply a color transform, you need at least two ICC profiles:</p>"
+ "<ul><li>An \"Input\" profile.</li>"
+ "<li>A \"Workspace\" profile.</li></ul>"
+ "<p>If you want to do a \"soft-proof\" transform, in addition to these profiles "
+ "you need a \"Proof\" profile.</p>");
+ KMessageBox::information(kapp->activeWindow(), error);
+ m_gboxSettings->enableButton(EditorToolSettings::Ok, false);
+ }
+ else
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.apply(preview, m_embeddedICC, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+ else
+ {
+ TQByteArray fakeProfile = TQByteArray();
+ transform.apply(preview, fakeProfile, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+
+ //-- Calculate and apply the curve on image after transformation -------------
+
+ DImg preview2(w, h, sb, a, 0, false);
+ m_curvesWidget->curves()->curvesLutSetup(ImageHistogram::AlphaChannel);
+ m_curvesWidget->curves()->curvesLutProcess(preview.bits(), preview2.bits(), w, h);
+
+ //-- Adjust contrast ---------------------------------------------------------
+
+ BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(preview2);
+
+ iface->putPreviewImage(preview2.bits());
+ m_previewWidget->updatePreview();
+
+ //-- Update histogram --------------------------------------------------------
+
+ memcpy(m_destinationPreviewData, preview2.bits(), preview2.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ kapp->restoreOverrideCursor();
+ }
+}
+
+void ICCProofTool::finalRendering()
+{
+ if (!m_doSoftProofBox->isChecked())
+ {
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ ImageIface *iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ IccTransform transform;
+
+ DImg img(w, h, sb, a, data);
+
+ TQString tmpInPath;
+ TQString tmpProofPath;
+ TQString tmpSpacePath;
+ bool proofCondition;
+
+ //-- Input profile parameters ------------------
+
+ if (useDefaultInProfile())
+ {
+ tmpInPath = m_inPath;
+ }
+ else if (useSelectedInProfile())
+ {
+ tmpInPath = m_inProfilesPath->url();
+ TQFileInfo info(tmpInPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile())
+ {
+ KMessageBox::information(kapp->activeWindow(),
+ i18n("<p>Selected ICC input profile path seems "
+ "to be invalid.<p>Please check it."));
+ return;
+ }
+ }
+
+ //-- Proof profile parameters ------------------
+
+ if (useDefaultProofProfile())
+ {
+ tmpProofPath = m_proofPath;
+ }
+ else
+ {
+ tmpProofPath = m_proofProfilePath->url();
+ TQFileInfo info(tmpProofPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile())
+ {
+ KMessageBox::information(kapp->activeWindow(),
+ i18n("<p>The selected ICC proof profile path seems "
+ "to be invalid.<p>Please check it."));
+ return;
+ }
+ }
+
+ if (tmpProofPath.isNull())
+ proofCondition = false;
+
+ //-- Workspace profile parameters --------------
+
+ if (useDefaultSpaceProfile())
+ {
+ tmpSpacePath = m_spacePath;
+ }
+ else
+ {
+ tmpSpacePath = m_spaceProfilePath->url();
+ TQFileInfo info(tmpSpacePath);
+ if (!info.exists() || !info.isReadable() || !info.isFile())
+ {
+ KMessageBox::information(kapp->activeWindow(),
+ i18n("<p>Selected ICC workspace profile path seems "
+ "to be invalid.<p>Please check it."));
+ return;
+ }
+ }
+
+ //-- Perform the color transformations ------------------
+
+ transform.getTransformType(m_doSoftProofBox->isChecked());
+
+ if (m_doSoftProofBox->isChecked())
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles( tmpSpacePath, tmpProofPath, true );
+ }
+ else
+ {
+ transform.setProfiles( tmpInPath, tmpSpacePath, tmpProofPath);
+ }
+ }
+ else
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles( tmpSpacePath );
+ }
+ else
+ {
+ transform.setProfiles( tmpInPath, tmpSpacePath );
+ }
+ }
+
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.apply(img, m_embeddedICC, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+ else
+ {
+ TQByteArray fakeProfile = TQByteArray();
+ transform.apply(img, fakeProfile, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+
+ //-- Embed the workspace profile if necessary --------------------------------
+
+ if (m_embeddProfileBox->isChecked())
+ {
+ iface->setEmbeddedICCToOriginalImage(tmpSpacePath);
+ DDebug() << k_funcinfo << TQFile::encodeName(tmpSpacePath) << endl;
+ }
+
+ //-- Calculate and apply the curve on image after transformation -------------
+
+ DImg img2(w, h, sb, a, 0, false);
+ m_curvesWidget->curves()->curvesLutSetup(ImageHistogram::AlphaChannel);
+ m_curvesWidget->curves()->curvesLutProcess(img.bits(), img2.bits(), w, h);
+
+ //-- Adjust contrast ---------------------------------------------------------
+
+ BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(img2);
+
+ iface->putOriginalImage("Color Management", img2.bits());
+ delete [] data;
+ }
+
+ kapp->restoreOverrideCursor();
+ }
+}
+
+void ICCProofTool::slotToggledWidgets( bool t)
+{
+ m_useInDefaultProfile->setEnabled(t);
+ m_useProofDefaultProfile->setEnabled(t);
+ m_useSpaceDefaultProfile->setEnabled(t);
+}
+
+void ICCProofTool::slotInICCInfo()
+{
+ if (useEmbeddedProfile())
+ {
+ getICCInfo(m_embeddedICC);
+ }
+ else if (useBuiltinProfile())
+ {
+ TQString message = i18n("<p>You have selected the \"Default builtin sRGB profile\"</p>");
+ message.append(i18n("<p>This profile is built on the fly, so there is no relevant information "
+ "about it.</p>"));
+ KMessageBox::information(kapp->activeWindow(), message);
+ }
+ else if (useDefaultInProfile())
+ {
+ getICCInfo(m_inPath);
+ }
+ else if (useSelectedInProfile())
+ {
+ getICCInfo(m_inProfilesPath->url());
+ }
+}
+
+void ICCProofTool::slotProofICCInfo()
+{
+ if (useDefaultProofProfile())
+ {
+ getICCInfo(m_proofPath);
+ }
+ else
+ {
+ getICCInfo(m_proofProfilePath->url());
+ }
+}
+
+void ICCProofTool::slotSpaceICCInfo()
+{
+ if (useDefaultSpaceProfile())
+ {
+ getICCInfo(m_spacePath);
+ }
+ else
+ {
+ getICCInfo(m_spaceProfilePath->url());
+ }
+}
+
+void ICCProofTool::getICCInfo(const TQString& profile)
+{
+ if (profile.isEmpty())
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("Sorry, there is no selected profile"),
+ i18n("Profile Error"));
+ return;
+ }
+
+ ICCProfileInfoDlg infoDlg(kapp->activeWindow(), profile);
+ infoDlg.exec();
+}
+
+void ICCProofTool::getICCInfo(const TQByteArray& profile)
+{
+ if (profile.isNull())
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("Sorry, it seems there is no embedded profile"),
+ i18n("Profile Error"));
+ return;
+ }
+
+ ICCProfileInfoDlg infoDlg(kapp->activeWindow(), TQString(), profile);
+ infoDlg.exec();
+}
+
+void ICCProofTool::slotCMDisabledWarning()
+{
+ if (!m_cmEnabled)
+ {
+ TQString message = i18n("<p>You have not enabled Color Management in the digiKam preferences.</p>");
+ message.append(i18n("<p>\"Use of default profile\" options will be disabled now.</p>"));
+ KMessageBox::information(kapp->activeWindow(), message);
+ slotToggledWidgets(false);
+ }
+}
+
+//-- General Tab ---------------------------
+
+bool ICCProofTool::useBPC()
+{
+ return m_BPCBox->isChecked();
+}
+
+bool ICCProofTool::doProof()
+{
+ return m_doSoftProofBox->isChecked();
+}
+
+bool ICCProofTool::checkGamut()
+{
+ return m_checkGamutBox->isChecked();
+}
+
+bool ICCProofTool::embedProfile()
+{
+ return m_embeddProfileBox->isChecked();
+}
+
+//-- Input Tab ---------------------------
+
+bool ICCProofTool::useEmbeddedProfile()
+{
+ return m_useEmbeddedProfile->isChecked();
+}
+
+bool ICCProofTool::useBuiltinProfile()
+{
+ return m_useSRGBDefaultProfile->isChecked();
+}
+
+bool ICCProofTool::useDefaultInProfile()
+{
+ return m_useInDefaultProfile->isChecked();
+}
+
+bool ICCProofTool::useSelectedInProfile()
+{
+ return m_useInSelectedProfile->isChecked();
+}
+
+//-- Workspace Tab ---------------------------
+
+bool ICCProofTool::useDefaultSpaceProfile()
+{
+ return m_useSpaceDefaultProfile->isChecked();
+}
+
+//-- Proofing Tab ---------------------------
+
+bool ICCProofTool::useDefaultProofProfile()
+{
+ return m_useProofDefaultProfile->isChecked();
+}
+
+//-- Load all settings from file --------------------------------------
+
+void ICCProofTool::slotLoadSettings()
+{
+ KURL loadColorManagementFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString("*"), kapp->activeWindow(),
+ TQString(i18n("Color Management Settings File to Load")));
+ if (loadColorManagementFile.isEmpty())
+ return;
+
+ TQFile file(loadColorManagementFile.path());
+
+ if (file.open(IO_ReadOnly))
+ {
+ TQTextStream stream(&file);
+
+ if (stream.readLine() != "# Color Management Configuration File")
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("\"%1\" is not a Color Management settings text file.")
+ .arg(loadColorManagementFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+
+ m_renderingIntentsCB->setCurrentItem(stream.readLine().toInt());
+ m_doSoftProofBox->setChecked((bool) (stream.readLine().toUInt()));
+ m_checkGamutBox->setChecked((bool) (stream.readLine().toUInt()));
+ m_embeddProfileBox->setChecked((bool) (stream.readLine().toUInt()));
+ m_BPCBox->setChecked((bool) (stream.readLine().toUInt()));
+ m_inProfileBG->setButton(stream.readLine().toInt());
+ m_spaceProfileBG->setButton(stream.readLine().toInt());
+ m_proofProfileBG->setButton(stream.readLine().toInt());
+ m_inProfilesPath->setURL(stream.readLine());
+ m_proofProfilePath->setURL(stream.readLine());
+ m_spaceProfilePath->setURL(stream.readLine());
+ m_cInput->setValue(stream.readLine().toInt());
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesChannelReset(i);
+
+ m_curvesWidget->curves()->setCurveType(m_curvesWidget->m_channelType, ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0; j < 17; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p;
+ p.setX(stream.readLine().toInt());
+ p.setY(stream.readLine().toInt());
+
+ if (m_originalImage->sixteenBit() && p != disable)
+ {
+ p.setX(p.x() * 255);
+ p.setY(p.y() * 255);
+ }
+
+ m_curvesWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, j, p);
+ }
+
+ blockSignals(false);
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curvesWidget->curves()->curvesCalculateCurve(i);
+
+ m_histogramWidget->reset();
+ slotEffect();
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("Cannot load settings from the Color Management text file."));
+
+ file.close();
+}
+
+//-- Save all settings to file ---------------------------------------
+
+void ICCProofTool::slotSaveAsSettings()
+{
+ KURL saveColorManagementFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString(i18n("Color Management Settings File to Save")));
+ if (saveColorManagementFile.isEmpty())
+ return;
+
+ TQFile file(saveColorManagementFile.path());
+
+ if (file.open(IO_WriteOnly))
+ {
+ TQTextStream stream(&file);
+ stream << "# Color Management Configuration File\n";
+ stream << m_renderingIntentsCB->currentItem() << "\n";
+ stream << m_doSoftProofBox->isChecked() << "\n";
+ stream << m_checkGamutBox->isChecked() << "\n";
+ stream << m_embeddProfileBox->isChecked() << "\n";
+ stream << m_BPCBox->isChecked() << "\n";
+ stream << m_inProfileBG->selectedId() << "\n";
+ stream << m_spaceProfileBG->selectedId() << "\n";
+ stream << m_proofProfileBG->selectedId() << "\n";
+ stream << m_inProfilesPath->url() << "\n";
+ stream << m_proofProfilePath->url() << "\n";
+ stream << m_spaceProfilePath->url() << "\n";
+ stream << m_cInput->value() << "\n";
+
+ for (int j = 0; j < 17; j++)
+ {
+ TQPoint p = m_curvesWidget->curves()->getCurvePoint(ImageHistogram::ValueChannel, j);
+ if (m_originalImage->sixteenBit())
+ {
+ p.setX(p.x() / 255);
+ p.setY(p.y() / 255);
+ }
+ stream << p.x() << "\n";
+ stream << p.y() << "\n";
+ }
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("Cannot save settings to the Color Management text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/iccprooftool.h b/src/imageplugins/coreplugin/iccprooftool.h
new file mode 100644
index 00000000..86d93ea9
--- /dev/null
+++ b/src/imageplugins/coreplugin/iccprooftool.h
@@ -0,0 +1,209 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-21
+ * Description : digiKam image editor tool to correct picture
+ * colors using an ICC color profile
+ *
+ * Copyright (C) 2005-2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICCPROOFTOOL_H
+#define ICCPROOFTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQCheckBox;
+class TQComboBox;
+class TQVButtonGroup;
+class TQButtonGroup;
+class TQHButtonGroup;
+class TQRadioButton;
+class TQPushButton;
+class TQToolBox;
+
+class KURLRequester;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class ICCTransform;
+class ImageWidget;
+class HistogramWidget;
+class ColorGradientWidget;
+class DColor;
+class ICCPreviewWidget;
+class CurvesWidget;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ICCProofTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ ICCProofTool(TQObject* parent);
+ ~ICCProofTool();
+
+protected:
+
+ void finalRendering();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+
+ void getICCInfo(const TQString&);
+ void getICCInfo(const TQByteArray&);
+
+ bool useBPC();
+ bool doProof();
+ bool checkGamut();
+ bool embedProfile();
+
+ bool useEmbeddedProfile();
+ bool useBuiltinProfile();
+ bool useDefaultInProfile();
+ bool useSelectedInProfile();
+
+ bool useDefaultSpaceProfile();
+ bool useSelectedSpaceProfile();
+
+ bool useDefaultProofProfile();
+ bool useSelectedProofProfile();
+
+private slots:
+
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+ void slotEffect();
+ void slotResetSettings();
+ void slotChannelChanged(int);
+ void slotScaleChanged(int);
+ void slotSpotColorChanged(const Digikam::DColor &);
+ void slotColorSelectedFromTarget(const Digikam::DColor &);
+ void slotToggledWidgets(bool);
+ void slotInICCInfo();
+ void slotProofICCInfo();
+ void slotSpaceICCInfo();
+ void slotCMDisabledWarning();
+ void processLCMSURL(const TQString&);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear = 0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel = 0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum ICCSettingsTab
+ {
+ GENERALPAGE=0,
+ INPUTPAGE,
+ WORKSPACEPAGE,
+ PROOFINGPAGE,
+ LIGHTNESSPAGE
+ };
+
+ bool m_cmEnabled;
+ bool m_hasICC;
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQCheckBox *m_doSoftProofBox;
+ TQCheckBox *m_checkGamutBox;
+ TQCheckBox *m_embeddProfileBox;
+ TQCheckBox *m_BPCBox;
+
+ TQRadioButton *m_useEmbeddedProfile;
+ TQRadioButton *m_useInDefaultProfile;
+ TQRadioButton *m_useInSelectedProfile;
+ TQRadioButton *m_useProofDefaultProfile;
+ TQRadioButton *m_useProofSelectedProfile;
+ TQRadioButton *m_useSpaceDefaultProfile;
+ TQRadioButton *m_useSpaceSelectedProfile;
+ TQRadioButton *m_useSRGBDefaultProfile;
+
+ TQString m_inPath;
+ TQString m_spacePath;
+ TQString m_proofPath;
+
+ TQButtonGroup *m_optionsBG;
+ TQButtonGroup *m_inProfileBG;
+ TQButtonGroup *m_spaceProfileBG;
+ TQButtonGroup *m_proofProfileBG;
+
+ TQHButtonGroup *m_scaleBG;
+ TQVButtonGroup *m_renderingIntentBG;
+ TQVButtonGroup *m_profilesBG;
+
+ TQByteArray m_embeddedICC;
+
+ TQToolBox *m_toolBoxWidgets;
+
+ KURLRequester *m_inProfilesPath;
+ KURLRequester *m_spaceProfilePath;
+ KURLRequester *m_proofProfilePath;
+
+ KDcrawIface::RIntNumInput *m_cInput;
+
+ KDcrawIface::RComboBox *m_renderingIntentsCB;
+
+ Digikam::DImg *m_originalImage;
+
+ Digikam::CurvesWidget *m_curvesWidget;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ICCPreviewWidget *m_iccInPreviewWidget;
+ Digikam::ICCPreviewWidget *m_iccSpacePreviewWidget;
+ Digikam::ICCPreviewWidget *m_iccProofPreviewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif // ICCPROOFTOOL_H
diff --git a/src/imageplugins/coreplugin/imageeffect_autocorrection.cpp b/src/imageplugins/coreplugin/imageeffect_autocorrection.cpp
new file mode 100644
index 00000000..290b93cb
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_autocorrection.cpp
@@ -0,0 +1,431 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-31
+ * Description : Auto-Color correction tool.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+ // TQt includes.
+
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqradiobutton.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqlistbox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "dimgimagefilters.h"
+#include "whitebalance.h"
+#include "dimg.h"
+#include "listboxpreviewitem.h"
+
+// Local includes.
+
+#include "imageeffect_autocorrection.h"
+#include "imageeffect_autocorrection.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_AutoCorrection::ImageEffect_AutoCorrection(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Auto Color Correction"),
+ "autocorrection", false), m_destinationPreviewData(0L)
+{
+ setHelp("autocolorcorrectiontool.anchor", "digikam");
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new Digikam::ImageWidget("autocorrection Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the auto-color correction tool "
+ "preview. You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ Digikam::ImageIface iface(0, 0);
+ m_thumbnailImage = iface.getOriginalImg()->smoothScale(128, 128, TQSize::ScaleMin);
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 4, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ m_correctionTools = new TQListBox(gboxSettings);
+ m_correctionTools->setColumnMode(1);
+ m_correctionTools->setVariableWidth(false);
+ m_correctionTools->setVariableHeight(false);
+ Digikam::ListBoxWhatsThis* whatsThis = new Digikam::ListBoxWhatsThis(m_correctionTools);
+
+ TQPixmap pix = getThumbnailForEffect(AutoLevelsCorrection);
+ Digikam::ListBoxPreviewItem *item = new Digikam::ListBoxPreviewItem(pix, i18n("Auto Levels"));
+ whatsThis->add( item, i18n("<b>Auto Levels</b>:"
+ "<p>This option maximizes the tonal range in the Red, "
+ "Green, and Blue channels. It searches the image shadow and highlight "
+ "limit values and adjusts the Red, Green, and Blue channels "
+ "to a full histogram range.</p>"));
+ m_correctionTools->insertItem(item, AutoLevelsCorrection);
+
+ pix = getThumbnailForEffect(NormalizeCorrection);
+ item = new Digikam::ListBoxPreviewItem(pix, i18n("Normalize"));
+ whatsThis->add( item, i18n("<b>Normalize</b>:"
+ "<p>This option scales brightness values across the active "
+ "image so that the darkest point becomes black, and the "
+ "brightest point becomes as bright as possible without "
+ "altering its hue. This is often a \"magic fix\" for "
+ "images that are dim or washed out.</p>"));
+ m_correctionTools->insertItem(item, NormalizeCorrection);
+
+ pix = getThumbnailForEffect(EqualizeCorrection);
+ item = new Digikam::ListBoxPreviewItem(pix, i18n("Equalize"));
+ whatsThis->add( item, i18n("<b>Equalize</b>:"
+ "<p>This option adjusts the brightness of colors across the "
+ "active image so that the histogram for the value channel "
+ "is as nearly as possible flat, that is, so that each possible "
+ "brightness value appears at about the same number of pixels "
+ "as each other value. Sometimes Equalize works wonderfully at "
+ "enhancing the contrasts in an image. Other times it gives "
+ "garbage. It is a very powerful operation, which can either work "
+ "miracles on an image or destroy it.</p>"));
+ m_correctionTools->insertItem(item, EqualizeCorrection);
+
+ pix = getThumbnailForEffect(StretchContrastCorrection);
+ item = new Digikam::ListBoxPreviewItem(pix, i18n("Stretch Contrast"));
+ whatsThis->add( item, i18n("<b>Stretch Contrast</b>:"
+ "<p>This option enhances the contrast and brightness "
+ "of the RGB values of an image by stretching the lowest "
+ "and highest values to their fullest range, adjusting "
+ "everything in between.</p>"));
+ m_correctionTools->insertItem(item, StretchContrastCorrection);
+
+ pix = getThumbnailForEffect(AutoExposureCorrection);
+ item = new Digikam::ListBoxPreviewItem(pix, i18n("Auto Exposure"));
+ whatsThis->add( item, i18n("<b>Auto Exposure</b>:"
+ "<p>This option enhances the contrast and brightness "
+ "of the RGB values of an image to calculate optimal "
+ "exposition and black level using image histogram "
+ "properties.</p>"));
+ m_correctionTools->insertItem(item, AutoExposureCorrection);
+
+ // -------------------------------------------------------------
+
+ m_correctionTools->setFocus();
+ gridSettings->addMultiCellWidget(m_correctionTools, 3, 3, 0, 4);
+ gridSettings->setRowStretch(3, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_correctionTools, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+}
+
+ImageEffect_AutoCorrection::~ImageEffect_AutoCorrection()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_previewWidget;
+}
+
+void ImageEffect_AutoCorrection::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_AutoCorrection::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_AutoCorrection::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_AutoCorrection::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("autocorrection Tool Dialog");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+ m_correctionTools->setCurrentItem(config->readNumEntry("Auto Correction Filter", AutoLevelsCorrection));
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ImageEffect_AutoCorrection::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("autocorrection Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("Auto Correction Filter", m_correctionTools->currentItem());
+ config->sync();
+}
+
+void ImageEffect_AutoCorrection::resetValues()
+{
+ m_correctionTools->blockSignals(true);
+ m_correctionTools->setCurrentItem(AutoLevelsCorrection);
+ m_correctionTools->blockSignals(false);
+}
+
+void ImageEffect_AutoCorrection::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ autoCorrection(m_destinationPreviewData, w, h, sb, m_correctionTools->currentItem());
+
+ iface->putPreviewImage(m_destinationPreviewData);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+TQPixmap ImageEffect_AutoCorrection::getThumbnailForEffect(AutoCorrectionType type)
+{
+ Digikam::DImg thumb = m_thumbnailImage.copy();
+ autoCorrection(thumb.bits(), thumb.width(), thumb.height(), thumb.sixteenBit(), type);
+ return (thumb.convertToPixmap());
+}
+
+
+void ImageEffect_AutoCorrection::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ int type = m_correctionTools->currentItem();
+ autoCorrection(data, w, h, sb, type);
+ TQString name;
+
+ switch (type)
+ {
+ case AutoLevelsCorrection:
+ name = i18n("Auto Levels");
+ break;
+
+ case NormalizeCorrection:
+ name = i18n("Normalize");
+ break;
+
+ case EqualizeCorrection:
+ name = i18n("Equalize");
+ break;
+
+ case StretchContrastCorrection:
+ name = i18n("Stretch Contrast");
+ break;
+
+ case AutoExposureCorrection:
+ name = i18n("Auto Exposure");
+ break;
+ }
+
+ iface->putOriginalImage(name, data);
+ delete [] data;
+ }
+
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+void ImageEffect_AutoCorrection::autoCorrection(uchar *data, int w, int h, bool sb, int type)
+{
+ Digikam::DImgImageFilters filter;
+
+ switch (type)
+ {
+ case AutoLevelsCorrection:
+ filter.autoLevelsCorrectionImage(data, w, h, sb);
+ break;
+
+ case NormalizeCorrection:
+ filter.normalizeImage(data, w, h, sb);
+ break;
+
+ case EqualizeCorrection:
+ filter.equalizeImage(data, w, h, sb);
+ break;
+
+ case StretchContrastCorrection:
+ filter.stretchContrastImage(data, w, h, sb);
+ break;
+
+ case AutoExposureCorrection:
+ Digikam::WhiteBalance wbFilter(sb);
+ double blackLevel;
+ double exposureLevel;
+ wbFilter.autoExposureAdjustement(data, w, h, sb, blackLevel, exposureLevel);
+ wbFilter.whiteBalance(data, w, h, sb, blackLevel, exposureLevel);
+ break;
+ }
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/imageeffect_autocorrection.h b/src/imageplugins/coreplugin/imageeffect_autocorrection.h
new file mode 100644
index 00000000..c707ac1b
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_autocorrection.h
@@ -0,0 +1,128 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-31
+ * Description : Auto-Color correction tool.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_AUTOCORRECTION_H
+#define IMAGEEFFECT_AUTOCORRECTION_H
+
+// TQt Includes.
+
+#include <tqstring.h>
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQHButtonGroup;
+class TQComboBox;
+class TQListBox;
+class TQButtonGroup;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class DImg;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageEffect_AutoCorrection : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_AutoCorrection(TQWidget *parent);
+ ~ImageEffect_AutoCorrection();
+
+protected:
+
+ void finalRendering();
+
+private slots:
+
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+
+private:
+
+ enum AutoCorrectionType
+ {
+ AutoLevelsCorrection=0,
+ NormalizeCorrection,
+ EqualizeCorrection,
+ StretchContrastCorrection,
+ AutoExposureCorrection
+ };
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+
+ void autoCorrection(uchar *data, int w, int h, bool sb, int type);
+ TQPixmap getThumbnailForEffect(AutoCorrectionType type);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQListBox *m_correctionTools;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::DImg m_thumbnailImage;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_AUTOCORRECTION_H */
diff --git a/src/imageplugins/coreplugin/imageeffect_bcg.cpp b/src/imageplugins/coreplugin/imageeffect_bcg.cpp
new file mode 100644
index 00000000..9d21115d
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_bcg.cpp
@@ -0,0 +1,350 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-05
+ * Description : digiKam image editor to adjust Brightness,
+ Contrast, and Gamma of picture.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqlabel.h>
+#include <tqvbox.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <knuminput.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kstandarddirs.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "bcgmodifier.h"
+#include "dimg.h"
+
+// Local includes.
+
+#include "imageeffect_bcg.h"
+#include "imageeffect_bcg.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_BCG::ImageEffect_BCG(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Brightness Contrast Gamma Adjustments"),
+ "bcgadjust", false)
+{
+ m_destinationPreviewData = 0L;
+ setHelp("bcgadjusttool.anchor", "digikam");
+
+ m_previewWidget = new Digikam::ImageWidget("bcgadjust Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the image "
+ "brightness-contrast-gamma adjustments preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 9, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Brightness:"), gboxSettings);
+ m_bInput = new KIntNumInput(gboxSettings);
+ m_bInput->setRange(-100, 100, 1, true);
+ m_bInput->setValue(0);
+ TQWhatsThis::add( m_bInput, i18n("<p>Set here the brightness adjustment of the image."));
+ gridSettings->addMultiCellWidget(label2, 3, 3, 0, 4);
+ gridSettings->addMultiCellWidget(m_bInput, 4, 4, 0, 4);
+
+ TQLabel *label3 = new TQLabel(i18n("Contrast:"), gboxSettings);
+ m_cInput = new KIntNumInput(gboxSettings);
+ m_cInput->setRange(-100, 100, 1, true);
+ m_cInput->setValue(0);
+ TQWhatsThis::add( m_cInput, i18n("<p>Set here the contrast adjustment of the image."));
+ gridSettings->addMultiCellWidget(label3, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_cInput, 6, 6, 0, 4);
+
+ TQLabel *label4 = new TQLabel(i18n("Gamma:"), gboxSettings);
+ m_gInput = new KDoubleNumInput(gboxSettings);
+ m_gInput->setPrecision(2);
+ m_gInput->setRange(0.1, 3.0, 0.01, true);
+ m_gInput->setValue(1.0);
+ TQWhatsThis::add( m_gInput, i18n("<p>Set here the gamma adjustment of the image."));
+ gridSettings->addMultiCellWidget(label4, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_gInput, 8, 8, 0, 4);
+
+ gridSettings->setRowStretch(9, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_bInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_cInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+
+ enableButtonOK( false );
+}
+
+ImageEffect_BCG::~ImageEffect_BCG()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_previewWidget;
+}
+
+void ImageEffect_BCG::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_BCG::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_BCG::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_BCG::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("bcgadjust Tool Dialog");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+ m_bInput->setValue(config->readNumEntry("BrightnessAjustment", 0));
+ m_cInput->setValue(config->readNumEntry("ContrastAjustment", 0));
+ m_gInput->setValue(config->readDoubleNumEntry("GammaAjustment", 1.0));
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ImageEffect_BCG::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("bcgadjust Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("BrightnessAjustment", m_bInput->value());
+ config->writeEntry("ContrastAjustment", m_cInput->value());
+ config->writeEntry("GammaAjustment", m_gInput->value());
+ config->sync();
+}
+
+void ImageEffect_BCG::resetValues()
+{
+ m_bInput->blockSignals(true);
+ m_cInput->blockSignals(true);
+ m_gInput->blockSignals(true);
+ m_bInput->setValue(0);
+ m_cInput->setValue(0);
+ m_gInput->setValue(1.0);
+ m_bInput->blockSignals(false);
+ m_cInput->blockSignals(false);
+ m_gInput->blockSignals(false);
+}
+
+void ImageEffect_BCG::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ double b = (double)m_bInput->value()/250.0;
+ double c = (double)(m_cInput->value()/100.0) + 1.00;
+ double g = m_gInput->value();
+
+ enableButtonOK( b != 0.0 || c != 1.0 || g != 1.0 );
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ Digikam::DImg preview(w, h, sb, a, m_destinationPreviewData);
+ Digikam::BCGModifier cmod;
+ cmod.setGamma(g);
+ cmod.setBrightness(b);
+ cmod.setContrast(c);
+ cmod.applyBCG(preview);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void ImageEffect_BCG::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+
+ double b = (double)m_bInput->value()/250.0;
+ double c = (double)(m_cInput->value()/100.0) + 1.00;
+ double g = m_gInput->value();
+
+ iface->setOriginalBCG(b, c, g);
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/imageeffect_bcg.h b/src/imageplugins/coreplugin/imageeffect_bcg.h
new file mode 100644
index 00000000..a9cd020b
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_bcg.h
@@ -0,0 +1,110 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-05
+ * Description : digiKam image editor to adjust Brightness,
+ Contrast, and Gamma of picture.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_BCG_H
+#define IMAGEEFFECT_BCG_H
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQCheckBox;
+class TQComboBox;
+class TQHButtonGroup;
+
+class KIntNumInput;
+class KDoubleNumInput;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageEffect_BCG : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_BCG(TQWidget *parent);
+ ~ImageEffect_BCG();
+
+private slots:
+
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KIntNumInput *m_bInput;
+ KIntNumInput *m_cInput;
+ KDoubleNumInput *m_gInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_BCG_H */
diff --git a/src/imageplugins/coreplugin/imageeffect_blur.cpp b/src/imageplugins/coreplugin/imageeffect_blur.cpp
new file mode 100644
index 00000000..bd23854b
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_blur.cpp
@@ -0,0 +1,147 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to blur an image
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <knuminput.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+// Digikam includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "dimggaussianblur.h"
+
+// Local includes.
+
+#include "imageeffect_blur.h"
+#include "imageeffect_blur.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_Blur::ImageEffect_Blur(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Apply Gaussian Blur on Photograph"),
+ "gaussianblur", false, true, true)
+{
+ setHelp("blursharpentool.anchor", "digikam");
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 1, 1, 0, spacingHint());
+ TQLabel *label = new TQLabel(i18n("Smoothness:"), gboxSettings);
+
+ m_radiusInput = new KIntNumInput(gboxSettings);
+ m_radiusInput->setRange(0, 100, 1, true);
+ m_radiusInput->setValue(0);
+ TQWhatsThis::add( m_radiusInput, i18n("<p>A smoothness of 0 has no effect, "
+ "1 and above determine the Gaussian blur matrix radius "
+ "that determines how much to blur the image."));
+
+ gridSettings->addMultiCellWidget(label, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_radiusInput, 1, 1, 0, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+}
+
+ImageEffect_Blur::~ImageEffect_Blur()
+{
+}
+
+void ImageEffect_Blur::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("gaussianblur Tool Dialog");
+ m_radiusInput->setValue(config->readNumEntry("RadiusAjustment", 0));
+}
+
+void ImageEffect_Blur::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("gaussianblur Tool Dialog");
+ config->writeEntry("RadiusAjustment", m_radiusInput->value());
+ config->sync();
+}
+
+void ImageEffect_Blur::resetValues(void)
+{
+ m_radiusInput->blockSignals(true);
+ m_radiusInput->setValue(0);
+ m_radiusInput->blockSignals(false);
+}
+
+void ImageEffect_Blur::prepareEffect()
+{
+ m_radiusInput->setEnabled(false);
+
+ Digikam::DImg img = m_imagePreviewWidget->getOriginalRegionImage();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new Digikam::DImgGaussianBlur(&img, this, m_radiusInput->value()));
+}
+
+void ImageEffect_Blur::prepareFinal()
+{
+ m_radiusInput->setEnabled(false);
+
+ Digikam::ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ Digikam::DImg orgImage = Digikam::DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new Digikam::DImgGaussianBlur(&orgImage, this, m_radiusInput->value()));
+}
+
+void ImageEffect_Blur::putPreviewData(void)
+{
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage();
+ m_imagePreviewWidget->setPreviewImage(imDest);
+}
+
+void ImageEffect_Blur::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage();
+ iface.putOriginalImage(i18n("Gaussian Blur"), imDest.bits());
+}
+
+void ImageEffect_Blur::renderingFinished(void)
+{
+ m_radiusInput->setEnabled(true);
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/imageeffect_blur.h b/src/imageplugins/coreplugin/imageeffect_blur.h
new file mode 100644
index 00000000..eab7694e
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_blur.h
@@ -0,0 +1,68 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to blur an image
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_BLUR_H
+#define IMAGEEFFECT_BLUR_H
+
+// Digikam include.
+
+#include "ctrlpaneldlg.h"
+
+class KIntNumInput;
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageEffect_Blur : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Blur(TQWidget *parent);
+ ~ImageEffect_Blur();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KIntNumInput *m_radiusInput;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_BLUR_H */
diff --git a/src/imageplugins/coreplugin/imageeffect_bwsepia.cpp b/src/imageplugins/coreplugin/imageeffect_bwsepia.cpp
new file mode 100644
index 00000000..7dcdf0da
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_bwsepia.cpp
@@ -0,0 +1,1183 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : Black and White conversion tool.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+ // TQt includes.
+
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqlistbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqintdict.h>
+#include <tqtextstream.h>
+#include <tqfile.h>
+#include <tqvbox.h>
+
+// KDE includes.
+
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdemessagebox.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+#include <tdeapplication.h>
+#include <knuminput.h>
+#include <ktabwidget.h>
+#include <tdeconfig.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "imagehistogram.h"
+#include "dimgimagefilters.h"
+#include "imagewidget.h"
+#include "imagecurves.h"
+#include "histogramwidget.h"
+#include "curveswidget.h"
+#include "colorgradientwidget.h"
+#include "dimg.h"
+#include "bcgmodifier.h"
+#include "listboxpreviewitem.h"
+
+// Local includes.
+
+#include "imageeffect_bwsepia.h"
+#include "imageeffect_bwsepia.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+class PreviewPixmapFactory : public TQObject
+{
+public:
+
+ PreviewPixmapFactory(ImageEffect_BWSepia* bwSepia);
+
+ void invalidate() { m_previewPixmapMap.clear(); }
+
+ const TQPixmap* pixmap(int id);
+
+private:
+
+ TQPixmap makePixmap(int id);
+
+ TQIntDict<TQPixmap> m_previewPixmapMap;
+ ImageEffect_BWSepia *m_bwSepia;
+};
+
+PreviewPixmapFactory::PreviewPixmapFactory(ImageEffect_BWSepia* bwSepia)
+ : TQObject(bwSepia), m_bwSepia(bwSepia)
+{
+ m_previewPixmapMap.setAutoDelete(true);
+}
+
+const TQPixmap* PreviewPixmapFactory::pixmap(int id)
+{
+ if (m_previewPixmapMap.find(id) == 0)
+ {
+ TQPixmap pix = makePixmap(id);
+ m_previewPixmapMap.insert(id, new TQPixmap(pix));
+ }
+
+ TQPixmap* res = m_previewPixmapMap[id];
+
+ return res;
+}
+
+TQPixmap PreviewPixmapFactory::makePixmap(int id)
+{
+ return m_bwSepia->getThumbnailForEffect(id);
+}
+
+// -----------------------------------------------------------------------------------
+
+class ListBoxBWPreviewItem : public Digikam::ListBoxPreviewItem
+{
+
+public:
+
+ ListBoxBWPreviewItem(TQListBox *listbox, const TQString &text,
+ PreviewPixmapFactory* factory, int id)
+ : ListBoxPreviewItem(listbox, TQPixmap(), text)
+ {
+ m_previewPixmapFactory = factory;
+ m_id = id;
+ };
+
+ virtual const TQPixmap* pixmap() const;
+
+private:
+
+ int m_id;
+ PreviewPixmapFactory* m_previewPixmapFactory;
+};
+
+const TQPixmap* ListBoxBWPreviewItem::pixmap() const
+{
+ return m_previewPixmapFactory->pixmap(m_id);
+}
+
+// -----------------------------------------------------------------------------------
+
+ImageEffect_BWSepia::ImageEffect_BWSepia(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Convert to Black & White"),
+ "convertbw", true, false),
+ m_destinationPreviewData(0L),
+ m_channelCB(0),
+ m_scaleBG(0),
+ m_bwFilters(0),
+ m_bwTone(0),
+ m_cInput(0),
+ m_tab(0),
+ m_previewWidget(0),
+ m_histogramWidget(0),
+ m_curvesWidget(0),
+ m_curves(0),
+ m_originalImage(0),
+ m_previewPixmapFactory(0)
+{
+ setHelp("blackandwhitetool.anchor", "digikam");
+
+ Digikam::ImageIface iface(0, 0);
+ m_originalImage = iface.getOriginalImg();
+ m_thumbnailImage = m_originalImage->smoothScale(128, 128, TQSize::ScaleMin);
+ m_curves = new Digikam::ImageCurves(m_originalImage->sixteenBit());
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new Digikam::ImageWidget("convertbw Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the black and white conversion tool preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 4, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ m_tab = new KTabWidget(gboxSettings);
+
+ m_bwFilm = new TQListBox(m_tab);
+ m_bwFilm->setColumnMode(1);
+ m_bwFilm->setVariableWidth(false);
+ m_bwFilm->setVariableHeight(false);
+ Digikam::ListBoxWhatsThis* whatsThis2 = new Digikam::ListBoxWhatsThis(m_bwFilm);
+ m_previewPixmapFactory = new PreviewPixmapFactory(this);
+
+ int type = BWGeneric;
+
+ ListBoxBWPreviewItem *item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Generic"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Generic</b>:"
+ "<p>Simulate a generic black and white film</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa 200X"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa 200X</b>:"
+ "<p>Simulate the Agfa 200X black and white film at 200 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa Pan 25"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa Pan 25</b>:"
+ "<p>Simulate the Agfa Pan black and white film at 25 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa Pan 100"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa Pan 100</b>:"
+ "<p>Simulate the Agfa Pan black and white film at 100 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Agfa Pan 400"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Agfa Pan 400</b>:"
+ "<p>Simulate the Agfa Pan black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford Delta 100"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford Delta 100</b>:"
+ "<p>Simulate the Ilford Delta black and white film at 100 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford Delta 400"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford Delta 400</b>:"
+ "<p>Simulate the Ilford Delta black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford Delta 400 Pro 3200"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford Delta 400 Pro 3200</b>:"
+ "<p>Simulate the Ilford Delta 400 Pro black and white film at 3200 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford FP4 Plus"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford FP4 Plus</b>:"
+ "<p>Simulate the Ilford FP4 Plus black and white film at 125 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford HP5 Plus"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford HP5 Plus</b>:"
+ "<p>Simulate the Ilford HP5 Plus black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford PanF Plus"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford PanF Plus</b>:"
+ "<p>Simulate the Ilford PanF Plus black and white film at 50 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Ilford XP2 Super"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Ilford XP2 Super</b>:"
+ "<p>Simulate the Ilford XP2 Super black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Kodak Tmax 100"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Kodak Tmax 100</b>:"
+ "<p>Simulate the Kodak Tmax black and white film at 100 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Kodak Tmax 400"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Kodak Tmax 400</b>:"
+ "<p>Simulate the Kodak Tmax black and white film at 400 ISO</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilm, i18n("Kodak TriX"), m_previewPixmapFactory, type);
+ whatsThis2->add( item, i18n("<b>Kodak TriX</b>:"
+ "<p>Simulate the Kodak TriX black and white film at 400 ISO</p>"));
+
+ // -------------------------------------------------------------
+
+ TQVBox *vbox = new TQVBox(m_tab);
+ vbox->setSpacing(spacingHint());
+
+ m_bwFilters = new TQListBox(vbox);
+ m_bwFilters->setColumnMode(1);
+ m_bwFilters->setVariableWidth(false);
+ m_bwFilters->setVariableHeight(false);
+ Digikam::ListBoxWhatsThis* whatsThis = new Digikam::ListBoxWhatsThis(m_bwFilters);
+
+ type = BWNoFilter;
+
+ item = new ListBoxBWPreviewItem(m_bwFilters,
+ i18n("No Lens Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>No Lens Filter</b>:"
+ "<p>Do not apply a lens filter when rendering the image.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Green Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Green Filter</b>:"
+ "<p>Simulate black and white film exposure using a green filter. "
+ "This is usefule for all scenic shoots, especially portraits "
+ "photographed against the sky.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Orange Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Orange Filter</b>:"
+ "<p>Simulate black and white film exposure using an orange filter. "
+ "This will enhance landscapes, marine scenes and aerial "
+ "photography.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Red Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Red Filter</b>:"
+ "<p>Simulate black and white film exposure using a red filter. "
+ "This creates dramatic sky effects, and simulates moonlight scenes "
+ "in the daytime.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwFilters, i18n("Yellow Filter"), m_previewPixmapFactory, type);
+ whatsThis->add( item, i18n("<b>Black & White with Yellow Filter</b>:"
+ "<p>Simulate black and white film exposure using a yellow filter. "
+ "This has the most natural tonal correction, and improves contrast. Ideal for "
+ "landscapes.</p>"));
+
+ m_strengthInput = new KIntNumInput(vbox);
+ m_strengthInput->setLabel(i18n("Strength:"), AlignLeft | AlignVCenter);
+ m_strengthInput->setRange(1, 5, 1, true);
+ m_strengthInput->setValue(1);
+ TQWhatsThis::add(m_strengthInput, i18n("<p>Here, set the strength adjustment of the lens filter."));
+
+ // -------------------------------------------------------------
+
+ m_bwTone = new TQListBox(m_tab);
+ m_bwTone->setColumnMode(1);
+ m_bwTone->setVariableWidth(false);
+ m_bwTone->setVariableHeight(false);
+ Digikam::ListBoxWhatsThis* whatsThis3 = new Digikam::ListBoxWhatsThis(m_bwTone);
+
+ type = BWNoTone;
+
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("No Tone Filter"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>No Tone Filter</b>:"
+ "<p>Do not apply a tone filter to the image.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Sepia Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Sepia Tone</b>:"
+ "<p>Gives a warm highlight and mid-tone while adding a bit of coolness to "
+ "the shadows - very similar to the process of bleaching a print and "
+ "re-developing in a sepia toner.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Brown Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Brown Tone</b>:"
+ "<p>This filter is more neutral than the Sepia Tone "
+ "filter.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Cold Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Cold Tone</b>:"
+ "<p>Start subtle and replicates printing on a cold tone black and white "
+ "paper such as a bromide enlarging "
+ "paper.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Selenium Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Selenium Tone</b>:"
+ "<p>This effect replicates traditional selenium chemical toning done "
+ "in the darkroom.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Platinum Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with Platinum Tone</b>:"
+ "<p>This effect replicates traditional platinum chemical toning done "
+ "in the darkroom.</p>"));
+
+ ++type;
+ item = new ListBoxBWPreviewItem(m_bwTone, i18n("Green Tone"), m_previewPixmapFactory, type);
+ whatsThis3->add( item, i18n("<b>Black & White with greenish tint</b>:"
+ "<p>This effect is also known as Verdante.</p>"));
+
+ // -------------------------------------------------------------
+
+ TQWidget *curveBox = new TQWidget( m_tab );
+ TQGridLayout *gridTab2 = new TQGridLayout(curveBox, 5, 2, spacingHint(), 0);
+
+ Digikam::ColorGradientWidget* vGradient = new Digikam::ColorGradientWidget(
+ Digikam::ColorGradientWidget::Vertical,
+ 10, curveBox );
+ vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+
+ TQLabel *spacev = new TQLabel(curveBox);
+ spacev->setFixedWidth(1);
+
+ m_curvesWidget = new Digikam::CurvesWidget(256, 256, m_originalImage->bits(), m_originalImage->width(),
+ m_originalImage->height(), m_originalImage->sixteenBit(),
+ m_curves, curveBox);
+ TQWhatsThis::add( m_curvesWidget, i18n("<p>This is the curve adjustment of the image luminosity"));
+
+ TQLabel *spaceh = new TQLabel(curveBox);
+ spaceh->setFixedHeight(1);
+
+ Digikam::ColorGradientWidget *hGradient = new Digikam::ColorGradientWidget(
+ Digikam::ColorGradientWidget::Horizontal,
+ 10, curveBox );
+ hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ m_cInput = new KIntNumInput(curveBox);
+ m_cInput->setLabel(i18n("Contrast:"), AlignLeft | AlignVCenter);
+ m_cInput->setRange(-100, 100, 1, true);
+ m_cInput->setValue(0);
+ TQWhatsThis::add( m_cInput, i18n("<p>Set here the contrast adjustment of the image."));
+
+ gridTab2->addMultiCellWidget(vGradient, 0, 0, 0, 0);
+ gridTab2->addMultiCellWidget(spacev, 0, 0, 1, 1);
+ gridTab2->addMultiCellWidget(m_curvesWidget, 0, 0, 2, 2);
+ gridTab2->addMultiCellWidget(spaceh, 1, 1, 2, 2);
+ gridTab2->addMultiCellWidget(hGradient, 2, 2, 2, 2);
+ gridTab2->addMultiCellWidget(m_cInput, 4, 4, 0, 2);
+ gridTab2->setRowSpacing(3, spacingHint());
+ gridTab2->setRowStretch(5, 10);
+
+ // -------------------------------------------------------------
+
+ m_tab->insertTab(m_bwFilm, i18n("Film"), FilmTab);
+ m_tab->insertTab(vbox, i18n("Lens Filters"), BWFiltersTab);
+ m_tab->insertTab(m_bwTone, i18n("Tone"), ToneTab);
+ m_tab->insertTab(curveBox, i18n("Lightness"), LuminosityTab);
+
+ gridSettings->addMultiCellWidget(m_tab, 3, 3, 0, 4);
+ gridSettings->setRowStretch(3, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotSpotColorChanged(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_bwFilters, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotFilterSelected(int)));
+
+ connect(m_strengthInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_bwFilm, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_bwTone, TQ_SIGNAL(highlighted(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_curvesWidget, TQ_SIGNAL(signalCurvesChanged()),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_cInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+}
+
+ImageEffect_BWSepia::~ImageEffect_BWSepia()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_previewWidget;
+ delete m_curvesWidget;
+ delete m_curves;
+}
+
+void ImageEffect_BWSepia::slotFilterSelected(int filter)
+{
+ if (filter == BWNoFilter)
+ m_strengthInput->setEnabled(false);
+ else
+ m_strengthInput->setEnabled(true);
+
+ slotEffect();
+}
+
+TQPixmap ImageEffect_BWSepia::getThumbnailForEffect(int type)
+{
+ Digikam::DImg thumb = m_thumbnailImage.copy();
+ int w = thumb.width();
+ int h = thumb.height();
+ bool sb = thumb.sixteenBit();
+ bool a = thumb.hasAlpha();
+
+ if (type < BWGeneric)
+ {
+ // In Filter view, we will render a preview of the B&W filter with the generic B&W film.
+ blackAndWhiteConversion(thumb.bits(), w, h, sb, type);
+ blackAndWhiteConversion(thumb.bits(), w, h, sb, BWGeneric);
+ }
+ else
+ {
+ // In Film and Tone view, we will render the preview without to use the B&W Filter
+ blackAndWhiteConversion(thumb.bits(), w, h, sb, type);
+ }
+
+ if (m_curves) // in case we're called before the creator is done
+ {
+ uchar *targetData = new uchar[w*h*(sb ? 8 : 4)];
+ m_curves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+ m_curves->curvesLutProcess(thumb.bits(), targetData, w, h);
+
+ Digikam::DImg preview(w, h, sb, a, targetData);
+ Digikam::BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(preview);
+
+ thumb.putImageData(preview.bits());
+
+ delete [] targetData;
+ }
+ return (thumb.convertToPixmap());
+}
+
+void ImageEffect_BWSepia::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_BWSepia::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+ m_curvesWidget->m_scaleType = scale;
+ m_curvesWidget->repaint(false);
+}
+
+void ImageEffect_BWSepia::slotSpotColorChanged(const Digikam::DColor &color)
+{
+ m_curvesWidget->setCurveGuide(color);
+}
+
+void ImageEffect_BWSepia::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_BWSepia::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("convertbw Tool Dialog");
+
+ m_tab->setCurrentPage(config->readNumEntry("Settings Tab", BWFiltersTab));
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+ m_bwFilters->setCurrentItem(config->readNumEntry("BW Filter", 0));
+ m_bwFilm->setCurrentItem(config->readNumEntry("BW Film", 0));
+ m_bwTone->setCurrentItem(config->readNumEntry("BW Tone", 0));
+ m_cInput->setValue(config->readNumEntry("ContrastAjustment", 0));
+ m_strengthInput->setValue(config->readNumEntry("StrengthAjustment", 1));
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesChannelReset(i);
+
+ m_curves->setCurveType(m_curvesWidget->m_channelType, Digikam::ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p = config->readPointEntry(TQString("CurveAjustmentPoint%1").arg(j), &disable);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, j, p);
+ }
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesCalculateCurve(i);
+
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+ slotFilterSelected(m_bwFilters->currentItem());
+}
+
+void ImageEffect_BWSepia::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("convertbw Tool Dialog");
+ config->writeEntry("Settings Tab", m_tab->currentPageIndex());
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("BW Filter", m_bwFilters->currentItem());
+ config->writeEntry("BW Film", m_bwFilm->currentItem());
+ config->writeEntry("BW Tone", m_bwTone->currentItem());
+ config->writeEntry("ContrastAjustment", m_cInput->value());
+ config->writeEntry("StrengthAjustment", m_strengthInput->value());
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint p = m_curves->getCurvePoint(Digikam::ImageHistogram::ValueChannel, j);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+
+ config->writeEntry(TQString("CurveAjustmentPoint%1").arg(j), p);
+ }
+
+ config->sync();
+}
+
+void ImageEffect_BWSepia::resetValues()
+{
+ m_bwFilters->blockSignals(true);
+ m_bwTone->blockSignals(true);
+ m_cInput->blockSignals(true);
+ m_strengthInput->blockSignals(true);
+
+ m_bwFilters->setCurrentItem(0);
+ m_bwFilters->setSelected(0, true);
+
+ m_bwTone->setCurrentItem(0);
+ m_bwTone->setSelected(0, true);
+
+ m_cInput->setValue(0);
+
+ for (int channel = 0 ; channel < 5 ; channel++)
+ m_curves->curvesChannelReset(channel);
+
+ m_curvesWidget->reset();
+
+ m_cInput->blockSignals(false);
+ m_bwTone->blockSignals(false);
+ m_bwFilters->blockSignals(false);
+ m_strengthInput->blockSignals(false);
+
+ m_histogramWidget->reset();
+ m_previewPixmapFactory->invalidate();
+ m_bwFilters->triggerUpdate(false);
+ m_bwTone->triggerUpdate(false);
+}
+
+void ImageEffect_BWSepia::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ m_histogramWidget->stopHistogramComputation();
+
+ delete [] m_destinationPreviewData;
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ // Apply black and white filter.
+
+ blackAndWhiteConversion(m_destinationPreviewData, w, h, sb, m_bwFilters->currentItem());
+
+ // Apply black and white film type.
+
+ blackAndWhiteConversion(m_destinationPreviewData, w, h, sb, m_bwFilm->currentItem() + BWGeneric);
+
+ // Apply color tone filter.
+
+ blackAndWhiteConversion(m_destinationPreviewData, w, h, sb, m_bwTone->currentItem() + BWNoTone);
+
+ // Calculate and apply the curve on image.
+
+ uchar *targetData = new uchar[w*h*(sb ? 8 : 4)];
+ m_curves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+ m_curves->curvesLutProcess(m_destinationPreviewData, targetData, w, h);
+
+ // Adjust contrast.
+
+ Digikam::DImg preview(w, h, sb, a, targetData);
+ Digikam::BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(preview);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] targetData;
+
+ kapp->restoreOverrideCursor();
+}
+
+void ImageEffect_BWSepia::slotTimer()
+{
+ Digikam::ImageDlgBase::slotTimer();
+ if (m_previewPixmapFactory && m_bwFilters && m_bwTone)
+ {
+ m_previewPixmapFactory->invalidate();
+ m_bwFilters->triggerUpdate(false);
+ m_bwTone->triggerUpdate(false);
+ }
+}
+
+void ImageEffect_BWSepia::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ // Apply black and white filter.
+
+ blackAndWhiteConversion(data, w, h, sb, m_bwFilters->currentItem());
+
+ // Apply black and white film type.
+
+ blackAndWhiteConversion(data, w, h, sb, m_bwFilm->currentItem() + BWGeneric);
+
+ // Apply color tone filter.
+
+ blackAndWhiteConversion(data, w, h, sb, m_bwTone->currentItem() + BWNoTone);
+
+ // Calculate and apply the curve on image.
+
+ uchar *targetData = new uchar[w*h*(sb ? 8 : 4)];
+ m_curves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+ m_curves->curvesLutProcess(data, targetData, w, h);
+
+ // Adjust contrast.
+
+ Digikam::DImg img(w, h, sb, a, targetData);
+ Digikam::BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(img);
+
+ iface->putOriginalImage(i18n("Convert to Black && White"), img.bits());
+
+ delete [] data;
+ delete [] targetData;
+ }
+
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+void ImageEffect_BWSepia::blackAndWhiteConversion(uchar *data, int w, int h, bool sb, int type)
+{
+ // Value to multiply RGB 8 bits component of mask used by changeTonality() method.
+ int mul = sb ? 255 : 1;
+ Digikam::DImgImageFilters filter;
+ double strength = 1.0 + ((double)m_strengthInput->value() - 1.0) * (1.0 / 3.0);
+
+ switch (type)
+ {
+ case BWNoFilter:
+ m_redAttn = 0.0;
+ m_greenAttn = 0.0;
+ m_blueAttn = 0.0;
+ break;
+
+ case BWGreenFilter:
+ m_redAttn = -0.20 * strength;
+ m_greenAttn = +0.11 * strength;
+ m_blueAttn = +0.09 * strength;
+ break;
+
+ case BWOrangeFilter:
+ m_redAttn = +0.48 * strength;
+ m_greenAttn = -0.37 * strength;
+ m_blueAttn = -0.11 * strength;
+ break;
+
+ case BWRedFilter:
+ m_redAttn = +0.60 * strength;
+ m_greenAttn = -0.49 * strength;
+ m_blueAttn = -0.11 * strength;
+ break;
+
+ case BWYellowFilter:
+ m_redAttn = +0.30 * strength;
+ m_greenAttn = -0.31 * strength;
+ m_blueAttn = +0.01 * strength;
+ break;
+
+ // --------------------------------------------------------------------------------
+
+ case BWGeneric:
+ case BWNoTone:
+ m_redMult = 0.24;
+ m_greenMult = 0.68;
+ m_blueMult = 0.08;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfa200X:
+ m_redMult = 0.18;
+ m_greenMult = 0.41;
+ m_blueMult = 0.41;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfapan25:
+ m_redMult = 0.25;
+ m_greenMult = 0.39;
+ m_blueMult = 0.36;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfapan100:
+ m_redMult = 0.21;
+ m_greenMult = 0.40;
+ m_blueMult = 0.39;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWAgfapan400:
+ m_redMult = 0.20;
+ m_greenMult = 0.41;
+ m_blueMult = 0.39;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordDelta100:
+ m_redMult = 0.21;
+ m_greenMult = 0.42;
+ m_blueMult = 0.37;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordDelta400:
+ m_redMult = 0.22;
+ m_greenMult = 0.42;
+ m_blueMult = 0.36;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordDelta400Pro3200:
+ m_redMult = 0.31;
+ m_greenMult = 0.36;
+ m_blueMult = 0.33;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordFP4:
+ m_redMult = 0.28;
+ m_greenMult = 0.41;
+ m_blueMult = 0.31;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordHP5:
+ m_redMult = 0.23;
+ m_greenMult = 0.37;
+ m_blueMult = 0.40;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordPanF:
+ m_redMult = 0.33;
+ m_greenMult = 0.36;
+ m_blueMult = 0.31;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWIlfordXP2Super:
+ m_redMult = 0.21;
+ m_greenMult = 0.42;
+ m_blueMult = 0.37;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWKodakTmax100:
+ m_redMult = 0.24;
+ m_greenMult = 0.37;
+ m_blueMult = 0.39;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWKodakTmax400:
+ m_redMult = 0.27;
+ m_greenMult = 0.36;
+ m_blueMult = 0.37;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ case BWKodakTriX:
+ m_redMult = 0.25;
+ m_greenMult = 0.35;
+ m_blueMult = 0.40;
+ filter.channelMixerImage(data, w, h, sb, true, true,
+ m_redMult + m_redMult*m_redAttn, m_greenMult + m_greenMult*m_greenAttn, m_blueMult + m_blueMult*m_blueAttn,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0);
+ break;
+
+ // --------------------------------------------------------------------------------
+
+ case BWSepiaTone:
+ filter.changeTonality(data, w, h, sb, 162*mul, 132*mul, 101*mul);
+ break;
+
+ case BWBrownTone:
+ filter.changeTonality(data, w, h, sb, 129*mul, 115*mul, 104*mul);
+ break;
+
+ case BWColdTone:
+ filter.changeTonality(data, w, h, sb, 102*mul, 109*mul, 128*mul);
+ break;
+
+ case BWSeleniumTone:
+ filter.changeTonality(data, w, h, sb, 122*mul, 115*mul, 122*mul);
+ break;
+
+ case BWPlatinumTone:
+ filter.changeTonality(data, w, h, sb, 115*mul, 110*mul, 106*mul);
+ break;
+
+ case BWGreenTone:
+ filter.changeTonality(data, w, h, sb, 108*mul, 116*mul, 100*mul);
+ break;
+
+ }
+}
+
+//-- Load all settings from file --------------------------------------
+
+void ImageEffect_BWSepia::slotUser3()
+{
+ KURL loadFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Black & White Settings File to Load")) );
+ if( loadFile.isEmpty() )
+ return;
+
+ TQFile file(loadFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+
+ if ( stream.readLine() != "# Black & White Configuration File" )
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a Black & White settings text file.")
+ .arg(loadFile.fileName()));
+ file.close();
+ return;
+ }
+
+ m_bwFilters->blockSignals(true);
+ m_bwTone->blockSignals(true);
+ m_cInput->blockSignals(true);
+
+ m_bwFilters->setCurrentItem(stream.readLine().toInt());
+ m_bwTone->setCurrentItem(stream.readLine().toInt());
+ m_cInput->setValue(stream.readLine().toInt());
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesChannelReset(i);
+
+ m_curves->setCurveType(m_curvesWidget->m_channelType, Digikam::ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p;
+ p.setX( stream.readLine().toInt() );
+ p.setY( stream.readLine().toInt() );
+
+ if (m_originalImage->sixteenBit() && p != disable)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, j, p);
+ }
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesCalculateCurve(i);
+
+ m_bwFilters->blockSignals(false);
+ m_bwTone->blockSignals(false);
+ m_cInput->blockSignals(false);
+
+ m_histogramWidget->reset();
+ m_previewPixmapFactory->invalidate();
+ m_bwFilters->triggerUpdate(false);
+ m_bwTone->triggerUpdate(false);
+
+ slotEffect();
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the Black & White text file."));
+
+ file.close();
+}
+
+//-- Save all settings to file ---------------------------------------
+
+void ImageEffect_BWSepia::slotUser2()
+{
+ KURL saveFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Black & White Settings File to Save")) );
+ if( saveFile.isEmpty() )
+ return;
+
+ TQFile file(saveFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# Black & White Configuration File\n";
+ stream << m_bwFilters->currentItem() << "\n";
+ stream << m_bwTone->currentItem() << "\n";
+ stream << m_cInput->value() << "\n";
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint p = m_curves->getCurvePoint(Digikam::ImageHistogram::ValueChannel, j);
+ if (m_originalImage->sixteenBit())
+ {
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+ stream << p.x() << "\n";
+ stream << p.y() << "\n";
+ }
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the Black & White text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamImagesPluginCore
+
+
diff --git a/src/imageplugins/coreplugin/imageeffect_bwsepia.h b/src/imageplugins/coreplugin/imageeffect_bwsepia.h
new file mode 100644
index 00000000..74b4edf3
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_bwsepia.h
@@ -0,0 +1,195 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : Black and White conversion tool.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef IMAGEEFFECT_BWSEPIA_H
+#define IMAGEEFFECT_BWSEPIA_H
+
+// TQt Includes.
+
+#include <tqstring.h>
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQHButtonGroup;
+class TQComboBox;
+class TQButtonGroup;
+
+class KIntNumInput;
+class KTabWidget;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class DImg;
+class ImageCurves;
+class CurvesWidget;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class PreviewPixmapFactory;
+
+class ImageEffect_BWSepia : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_BWSepia(TQWidget *parent);
+ ~ImageEffect_BWSepia();
+
+ friend class PreviewPixmapFactory;
+
+protected:
+
+ TQPixmap getThumbnailForEffect(int type);
+ void finalRendering();
+
+protected slots:
+
+ virtual void slotTimer();
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void blackAndWhiteConversion(uchar *data, int w, int h, bool sb, int type);
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotSpotColorChanged(const Digikam::DColor &color);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+ void slotFilterSelected(int filter);
+
+private:
+
+ enum BlackWhiteConversionType
+ {
+ BWNoFilter=0, // B&W filter to the front of lens.
+ BWGreenFilter,
+ BWOrangeFilter,
+ BWRedFilter,
+ BWYellowFilter,
+
+ BWGeneric, // B&W film simulation.
+ BWAgfa200X,
+ BWAgfapan25,
+ BWAgfapan100,
+ BWAgfapan400,
+ BWIlfordDelta100,
+ BWIlfordDelta400,
+ BWIlfordDelta400Pro3200,
+ BWIlfordFP4,
+ BWIlfordHP5,
+ BWIlfordPanF,
+ BWIlfordXP2Super,
+ BWKodakTmax100,
+ BWKodakTmax400,
+ BWKodakTriX,
+
+ BWNoTone, // Chemical color tone filter.
+ BWSepiaTone,
+ BWBrownTone,
+ BWColdTone,
+ BWSeleniumTone,
+ BWPlatinumTone,
+ BWGreenTone
+ };
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum SettingsTab
+ {
+ FilmTab=0,
+ BWFiltersTab,
+ ToneTab,
+ LuminosityTab
+ };
+
+ // Color filter attenuation in percents.
+ double m_redAttn, m_greenAttn, m_blueAttn;
+
+ // Channel mixer color multiplier.
+ double m_redMult, m_greenMult, m_blueMult;
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQListBox *m_bwFilters;
+ TQListBox *m_bwFilm;
+ TQListBox *m_bwTone;
+
+ KIntNumInput *m_cInput;
+ KIntNumInput *m_strengthInput;
+
+ KTabWidget *m_tab;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::CurvesWidget *m_curvesWidget;
+
+ Digikam::ImageCurves *m_curves;
+
+ Digikam::DImg *m_originalImage;
+ Digikam::DImg m_thumbnailImage;
+
+ PreviewPixmapFactory *m_previewPixmapFactory;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_BWSEPIA_H */
diff --git a/src/imageplugins/coreplugin/imageeffect_iccproof.cpp b/src/imageplugins/coreplugin/imageeffect_iccproof.cpp
new file mode 100644
index 00000000..79a09983
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_iccproof.cpp
@@ -0,0 +1,1284 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-21
+ * Description : digiKam image editor tool to correct picture
+ * colors using an ICC color profile
+ *
+ * Copyright (C) 2005-2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhbox.h>
+#include <tqhbuttongroup.h>
+#include <tqvbuttongroup.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqpoint.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqradiobutton.h>
+#include <tqfile.h>
+#include <tqtoolbox.h>
+#include <tqtextstream.h>
+
+// KDE includes.
+
+#include <knuminput.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <kstandarddirs.h>
+#include <ktabwidget.h>
+#include <tdeconfig.h>
+#include <kurlrequester.h>
+#include <kurllabel.h>
+#include <tdefiledialog.h>
+#include <tdefile.h>
+#include <tdemessagebox.h>
+#include <tdeglobalsettings.h>
+#include <kiconloader.h>
+#include <ksqueezedtextlabel.h>
+
+// Digikam includes.
+
+#include "ddebug.h"
+#include "bcgmodifier.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "imagecurves.h"
+#include "curveswidget.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "iccpreviewwidget.h"
+#include "icctransform.h"
+#include "iccprofileinfodlg.h"
+
+// Local includes.
+
+#include "imageeffect_iccproof.h"
+#include "imageeffect_iccproof.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_ICCProof::ImageEffect_ICCProof(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent,i18n("Color Management"),
+ "colormanagement", true, false)
+{
+ m_destinationPreviewData = 0;
+ m_cmEnabled = true;
+ m_hasICC = false;
+
+ setHelp("colormanagement.anchor", "digikam");
+
+ Digikam::ImageIface iface(0, 0);
+ m_originalImage = iface.getOriginalImg();
+ m_embeddedICC = iface.getEmbeddedICCFromOriginalImage();
+ m_curves = new Digikam::ImageCurves(m_originalImage->sixteenBit());
+
+ m_previewWidget = new Digikam::ImageWidget("colormanagement Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the image preview after "
+ "applying a color profile</p>"));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout *gridSettings = new TQGridLayout( gboxSettings, 3, 2, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel: "), gboxSettings);
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, gboxSettings);
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red channel values.<p>"
+ "<b>Green</b>: display the green channel values.<p>"
+ "<b>Blue</b>: display the blue channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal values are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal values are big; "
+ "if it is used, all values (small and large) will be visible on the "
+ "graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram "
+ "of the selected image channel. "
+ "This one is updated after setting changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10,
+ histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 2);
+
+ // -------------------------------------------------------------
+
+ m_toolBoxWidgets = new TQToolBox(gboxSettings);
+ TQWidget *generalOptions = new TQWidget(m_toolBoxWidgets);
+ TQWidget *inProfiles = new TQWidget(m_toolBoxWidgets);
+ TQWidget *spaceProfiles = new TQWidget(m_toolBoxWidgets);
+ TQWidget *proofProfiles = new TQWidget(m_toolBoxWidgets);
+ TQWidget *lightnessadjust = new TQWidget(m_toolBoxWidgets);
+
+ //---------- "General" Page Setup ----------------------------------
+
+ m_toolBoxWidgets->insertItem(GENERALPAGE, generalOptions,
+ SmallIconSet("misc"), i18n("General Settings"));
+ TQWhatsThis::add(generalOptions, i18n("<p>Here you can set general parameters.</p>"));
+
+ TQGridLayout *zeroPageLayout = new TQGridLayout(generalOptions, 5, 1, spacingHint());
+
+ m_doSoftProofBox = new TQCheckBox(generalOptions);
+ m_doSoftProofBox->setText(i18n("Soft-proofing"));
+ TQWhatsThis::add(m_doSoftProofBox, i18n("<p>Rendering emulation of the device described "
+ "by the \"Proofing\" profile. Useful to preview the final "
+ "result without rendering to physical medium.</p>"));
+
+ m_checkGamutBox = new TQCheckBox(generalOptions);
+ m_checkGamutBox->setText(i18n("Check gamut"));
+ TQWhatsThis::add(m_checkGamutBox, i18n("<p>You can use this option if you want to show "
+ "the colors that are outside the printer's gamut<p>"));
+
+ m_embeddProfileBox = new TQCheckBox(generalOptions);
+ m_embeddProfileBox->setChecked(true);
+ m_embeddProfileBox->setText(i18n("Assign profile"));
+ TQWhatsThis::add(m_embeddProfileBox, i18n("<p>You can use this option to embed "
+ "the selected workspace color profile into the image.</p>"));
+
+ m_BPCBox = new TQCheckBox(generalOptions);
+ m_BPCBox->setText(i18n("Use BPC"));
+ TQWhatsThis::add(m_BPCBox, i18n("<p>The Black Point Compensation (BPC) feature does work in conjunction "
+ "with Relative Colorimetric Intent. Perceptual intent should make no "
+ "difference, since BPC is always on, and in Absolute Colorimetric "
+ "Intent it is always turned off.</p>"
+ "<p>BPC does compensate for a lack of ICC profiles in the dark tone rendering. "
+ "With BPC the dark tones are optimally mapped (no clipping) from original media "
+ "to the destination rendering media, e.g. the combination of paper and ink.</p>"));
+
+ TQLabel *intent = new TQLabel(i18n("Rendering Intent:"), generalOptions);
+ m_renderingIntentsCB = new TQComboBox(false, generalOptions);
+ m_renderingIntentsCB->insertItem("Perceptual");
+ m_renderingIntentsCB->insertItem("Absolute Colorimetric");
+ m_renderingIntentsCB->insertItem("Relative Colorimetric");
+ m_renderingIntentsCB->insertItem("Saturation");
+ TQWhatsThis::add( m_renderingIntentsCB, i18n("<ul><li>Perceptual intent causes the full gamut "
+ "of the image to be compressed or expanded to fill the gamut of the destination media, "
+ "so that gray balance is preserved but colorimetric accuracy may not be preserved.<br>"
+ "In other words, if certain colors in an image fall outside of the range of colors that "
+ "the output device can render, the image intent will cause all the colors in the image "
+ "to be adjusted so that every color in the image falls within the range that can be "
+ "rendered and so that the relationship between colors is preserved as much as possible.<br>"
+ "This intent is most suitable for display of photographs and images, and is the default "
+ "intent.</li>"
+ "<li> Absolute Colorimetric intent causes any colors that fall outside the range that the "
+ "output device can render to be adjusted to the closest color that can be rendered, while all "
+ "other colors are left unchanged.<br>"
+ "This intent preserves the white point and is most suitable for spot colors (Pantone, "
+ "TruMatch, logo colors, ...).</li>"
+ "<li>Relative Colorimetric intent is defined such that any colors that fall outside the "
+ "range that the output device can render are adjusted to the closest color that can be "
+ "rendered, while all other colors are left unchanged. Proof intent does not preserve "
+ "the white point.</li>"
+ "<li>Saturation intent preserves the saturation of colors in the image at the possible "
+ "expense of hue and lightness.<br>"
+ "Implementation of this intent remains somewhat problematic, and the ICC is still working "
+ "on methods to achieve the desired effects.<br>"
+ "This intent is most suitable for business graphics such as charts, where it is more "
+ "important that the colors be vivid and contrast well with each other rather than a "
+ "specific color.</li></ul>"));
+
+ KURLLabel *lcmsLogoLabel = new KURLLabel(generalOptions);
+ lcmsLogoLabel->setAlignment( AlignTop | AlignRight );
+ lcmsLogoLabel->setText(TQString());
+ lcmsLogoLabel->setURL("http://www.littlecms.com");
+ TDEGlobal::dirs()->addResourceType("logo-lcms", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("logo-lcms", "logo-lcms.png");
+ lcmsLogoLabel->setPixmap( TQPixmap( directory + "logo-lcms.png" ) );
+ TQToolTip::add(lcmsLogoLabel, i18n("Visit Little CMS project website"));
+
+ zeroPageLayout->addMultiCellWidget(m_doSoftProofBox, 0, 0, 0, 0);
+ zeroPageLayout->addMultiCellWidget(m_checkGamutBox, 1, 1, 0, 0);
+ zeroPageLayout->addMultiCellWidget(m_embeddProfileBox, 2, 2, 0, 0);
+ zeroPageLayout->addMultiCellWidget(lcmsLogoLabel, 0, 2, 1, 1);
+ zeroPageLayout->addMultiCellWidget(m_BPCBox, 3, 3, 0, 0);
+ zeroPageLayout->addMultiCellWidget(intent, 4, 4, 0, 0);
+ zeroPageLayout->addMultiCellWidget(m_renderingIntentsCB, 4, 4, 1, 1);
+ zeroPageLayout->setRowStretch(5, 10);
+
+ //---------- "Input" Page Setup ----------------------------------
+
+ m_toolBoxWidgets->insertItem(INPUTPAGE, inProfiles, SmallIconSet("camera-photo"), i18n("Input Profile"));
+ TQWhatsThis::add(inProfiles, i18n("<p>Set here all parameters relevant of Input Color "
+ "Profiles.</p>"));
+
+ TQGridLayout *firstPageLayout = new TQGridLayout(inProfiles, 4, 2, spacingHint());
+
+ m_inProfileBG = new TQButtonGroup(4, TQt::Vertical, inProfiles);
+ m_inProfileBG->setFrameStyle(TQFrame::NoFrame);
+ m_inProfileBG->setInsideMargin(0);
+
+ m_useEmbeddedProfile = new TQRadioButton(m_inProfileBG);
+ m_useEmbeddedProfile->setText(i18n("Use embedded profile"));
+
+ m_useSRGBDefaultProfile = new TQRadioButton(m_inProfileBG);
+ m_useSRGBDefaultProfile->setText(i18n("Use builtin sRGB profile"));
+ m_useSRGBDefaultProfile->setChecked(true);
+
+ m_useInDefaultProfile = new TQRadioButton(m_inProfileBG);
+ m_useInDefaultProfile->setText(i18n("Use default profile"));
+
+ m_useInSelectedProfile = new TQRadioButton(m_inProfileBG);
+ m_useInSelectedProfile->setText(i18n("Use selected profile"));
+
+ m_inProfilesPath = new KURLRequester(inProfiles);
+ m_inProfilesPath->setMode(KFile::File|KFile::ExistingOnly);
+ m_inProfilesPath->setFilter("*.icc *.icm|"+i18n("ICC Files (*.icc; *.icm)"));
+ KFileDialog *inProfiles_dialog = m_inProfilesPath->fileDialog();
+ m_iccInPreviewWidget = new Digikam::ICCPreviewWidget(inProfiles_dialog);
+ inProfiles_dialog->setPreviewWidget(m_iccInPreviewWidget);
+
+ TQPushButton *inProfilesInfo = new TQPushButton(i18n("Info..."), inProfiles);
+
+ TQGroupBox *pictureInfo = new TQGroupBox(2, TQt::Horizontal, i18n("Camera information"), inProfiles);
+ new TQLabel(i18n("Make:"), pictureInfo);
+ KSqueezedTextLabel *make = new KSqueezedTextLabel(0, pictureInfo);
+ new TQLabel(i18n("Model:"), pictureInfo);
+ KSqueezedTextLabel *model = new KSqueezedTextLabel(0, pictureInfo);
+ make->setText(iface.getPhotographInformations().make);
+ model->setText(iface.getPhotographInformations().model);
+
+ firstPageLayout->addMultiCellWidget(m_inProfileBG, 0, 1, 0, 0);
+ firstPageLayout->addMultiCellWidget(inProfilesInfo, 0, 0, 2, 2);
+ firstPageLayout->addMultiCellWidget(m_inProfilesPath, 2, 2, 0, 2);
+ firstPageLayout->addMultiCellWidget(pictureInfo, 3, 3, 0, 2);
+ firstPageLayout->setColStretch(1, 10);
+ firstPageLayout->setRowStretch(4, 10);
+
+ //---------- "Workspace" Page Setup ---------------------------------
+
+ m_toolBoxWidgets->insertItem(WORKSPACEPAGE, spaceProfiles,
+ SmallIconSet("input-tablet"), i18n("Workspace Profile"));
+ TQWhatsThis::add(spaceProfiles, i18n("<p>Set here all parameters relevant to Color Workspace "
+ "Profiles.</p>"));
+
+ TQGridLayout *secondPageLayout = new TQGridLayout(spaceProfiles, 3, 2, spacingHint());
+
+ m_spaceProfileBG = new TQButtonGroup(2, TQt::Vertical, spaceProfiles);
+ m_spaceProfileBG->setFrameStyle(TQFrame::NoFrame);
+ m_spaceProfileBG->setInsideMargin(0);
+
+ m_useSpaceDefaultProfile = new TQRadioButton(m_spaceProfileBG);
+ m_useSpaceDefaultProfile->setText(i18n("Use default workspace profile"));
+
+ m_useSpaceSelectedProfile = new TQRadioButton(m_spaceProfileBG);
+ m_useSpaceSelectedProfile->setText(i18n("Use selected profile"));
+
+ m_spaceProfilePath = new KURLRequester(spaceProfiles);
+ m_spaceProfilePath->setMode(KFile::File|KFile::ExistingOnly);
+ m_spaceProfilePath->setFilter("*.icc *.icm|"+i18n("ICC Files (*.icc; *.icm)"));
+ KFileDialog *spaceProfiles_dialog = m_spaceProfilePath->fileDialog();
+ m_iccSpacePreviewWidget = new Digikam::ICCPreviewWidget(spaceProfiles_dialog);
+ spaceProfiles_dialog->setPreviewWidget(m_iccSpacePreviewWidget);
+
+ TQPushButton *spaceProfilesInfo = new TQPushButton(i18n("Info..."), spaceProfiles);
+
+ secondPageLayout->addMultiCellWidget(m_spaceProfileBG, 0, 1, 0, 0);
+ secondPageLayout->addMultiCellWidget(spaceProfilesInfo, 0, 0, 2, 2);
+ secondPageLayout->addMultiCellWidget(m_spaceProfilePath, 2, 2, 0, 2);
+ secondPageLayout->setColStretch(1, 10);
+ secondPageLayout->setRowStretch(3, 10);
+
+ //---------- "Proofing" Page Setup ---------------------------------
+
+ m_toolBoxWidgets->insertItem(PROOFINGPAGE, proofProfiles,
+ SmallIconSet("printer"), i18n("Proofing Profile"));
+ TQWhatsThis::add(proofProfiles, i18n("<p>Set here all parameters relevant to Proofing Color "
+ "Profiles.</p>"));
+
+ TQGridLayout *thirdPageLayout = new TQGridLayout(proofProfiles, 3, 2,
+ spacingHint(), spacingHint());
+
+ m_proofProfileBG = new TQButtonGroup(2, TQt::Vertical, proofProfiles);
+ m_proofProfileBG->setFrameStyle(TQFrame::NoFrame);
+ m_proofProfileBG->setInsideMargin(0);
+
+ m_useProofDefaultProfile = new TQRadioButton(m_proofProfileBG);
+ m_useProofDefaultProfile->setText(i18n("Use default proof profile"));
+
+ m_useProofSelectedProfile = new TQRadioButton(m_proofProfileBG);
+ m_useProofSelectedProfile->setText(i18n("Use selected profile"));
+
+ m_proofProfilePath = new KURLRequester(proofProfiles);
+ m_proofProfilePath->setMode(KFile::File|KFile::ExistingOnly);
+ m_proofProfilePath->setFilter("*.icc *.icm|"+i18n("ICC Files (*.icc; *.icm)"));
+ KFileDialog *proofProfiles_dialog = m_proofProfilePath->fileDialog();
+ m_iccProofPreviewWidget = new Digikam::ICCPreviewWidget(proofProfiles_dialog);
+ proofProfiles_dialog->setPreviewWidget(m_iccProofPreviewWidget);
+
+ TQPushButton *proofProfilesInfo = new TQPushButton(i18n("Info..."), proofProfiles);
+
+ thirdPageLayout->addMultiCellWidget(m_proofProfileBG, 0, 1, 0, 0);
+ thirdPageLayout->addMultiCellWidget(proofProfilesInfo, 0, 0, 2, 2);
+ thirdPageLayout->addMultiCellWidget(m_proofProfilePath, 2, 2, 0, 2);
+ thirdPageLayout->setColStretch(1, 10);
+ thirdPageLayout->setRowStretch(3, 10);
+
+ //---------- "Lightness" Page Setup ----------------------------------
+
+ m_toolBoxWidgets->insertItem(LIGHTNESSPAGE, lightnessadjust,
+ SmallIconSet("blend"), i18n("Lightness Adjustments"));
+ TQWhatsThis::add(lightnessadjust, i18n("<p>Set here all lightness adjustments to the target image.</p>"));
+
+ TQGridLayout *fourPageLayout = new TQGridLayout( lightnessadjust, 5, 2, spacingHint(), 0);
+
+ Digikam::ColorGradientWidget* vGradient = new Digikam::ColorGradientWidget(
+ Digikam::ColorGradientWidget::Vertical,
+ 10, lightnessadjust );
+ vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+
+ TQLabel *spacev = new TQLabel(lightnessadjust);
+ spacev->setFixedWidth(1);
+
+ m_curvesWidget = new Digikam::CurvesWidget(256, 192, m_originalImage->bits(), m_originalImage->width(),
+ m_originalImage->height(), m_originalImage->sixteenBit(),
+ m_curves, lightnessadjust);
+ TQWhatsThis::add( m_curvesWidget, i18n("<p>This is the curve adjustment of the image luminosity"));
+
+ TQLabel *spaceh = new TQLabel(lightnessadjust);
+ spaceh->setFixedHeight(1);
+
+ Digikam::ColorGradientWidget *hGradient = new Digikam::ColorGradientWidget(
+ Digikam::ColorGradientWidget::Horizontal,
+ 10, lightnessadjust );
+ hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ m_cInput = new KIntNumInput(lightnessadjust);
+ m_cInput->setLabel(i18n("Contrast:"), AlignLeft | AlignVCenter);
+ m_cInput->setRange(-100, 100, 1, true);
+ m_cInput->setValue(0);
+ TQWhatsThis::add( m_cInput, i18n("<p>Set here the contrast adjustment of the image."));
+
+ fourPageLayout->addMultiCellWidget(vGradient, 0, 0, 0, 0);
+ fourPageLayout->addMultiCellWidget(spacev, 0, 0, 1, 1);
+ fourPageLayout->addMultiCellWidget(m_curvesWidget, 0, 0, 2, 2);
+ fourPageLayout->addMultiCellWidget(spaceh, 1, 1, 2, 2);
+ fourPageLayout->addMultiCellWidget(hGradient, 2, 2, 2, 2);
+ fourPageLayout->addMultiCellWidget(m_cInput, 4, 4, 0, 2);
+ fourPageLayout->setRowSpacing(3, spacingHint());
+ fourPageLayout->setRowStretch(5, 10);
+
+ // -------------------------------------------------------------
+
+ gridSettings->addMultiCellWidget(m_toolBoxWidgets, 3, 3, 0, 2);
+ setUserAreaWidget(gboxSettings);
+ enableButtonOK(false);
+
+ // -------------------------------------------------------------
+
+ connect(lcmsLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processLCMSURL(const TQString&)));
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_curvesWidget, TQ_SIGNAL(signalCurvesChanged()),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_cInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_renderingIntentsCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- Check box options connections -------------------------------------------
+
+ connect(m_doSoftProofBox, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_checkGamutBox, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_BPCBox, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- Button Group ICC profile options connections ----------------------------
+
+ connect(m_inProfileBG, TQ_SIGNAL(released (int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_spaceProfileBG, TQ_SIGNAL(released (int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_proofProfileBG, TQ_SIGNAL(released (int)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- url requester ICC profile connections -----------------------------------
+
+ connect(m_inProfilesPath, TQ_SIGNAL(urlSelected(const TQString&)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_spaceProfilePath, TQ_SIGNAL(urlSelected(const TQString&)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_proofProfilePath, TQ_SIGNAL(urlSelected(const TQString&)),
+ this, TQ_SLOT(slotEffect()));
+
+ //-- Image preview widget connections ----------------------------
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotSpotColorChanged( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ //-- ICC profile preview connections -----------------------------
+
+ connect(inProfilesInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotInICCInfo()));
+
+ connect(spaceProfilesInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotSpaceICCInfo()));
+
+ connect(proofProfilesInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotProofICCInfo()));
+}
+
+ImageEffect_ICCProof::~ImageEffect_ICCProof()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ delete [] m_destinationPreviewData;
+ delete m_histogramWidget;
+ delete m_previewWidget;
+ delete m_curvesWidget;
+ delete m_curves;
+}
+
+void ImageEffect_ICCProof::readUserSettings()
+{
+ TQString defaultICCPath = TDEGlobalSettings::documentPath();
+ TDEConfig* config = kapp->config();
+
+ // General settings of digiKam Color Management
+ config->setGroup("Color Management");
+
+ if (!config->readBoolEntry("EnableCM", false))
+ {
+ m_cmEnabled = false;
+ slotToggledWidgets(false);
+ }
+ else
+ {
+ m_inPath = config->readPathEntry("InProfileFile");
+ m_spacePath = config->readPathEntry("WorkProfileFile");
+ m_proofPath = config->readPathEntry("ProofProfileFile");
+
+ if (TQFile::exists(config->readPathEntry("DefaultPath")))
+ {
+ defaultICCPath = config->readPathEntry("DefaultPath");
+ }
+ else
+ {
+ TQString message = i18n("The ICC profiles path seems to be invalid. You won't be able to use the \"Default profile\"\
+ options.<p>Please fix this in the digiKam ICC setup.");
+ slotToggledWidgets( false );
+ KMessageBox::information(this, message);
+ }
+ }
+
+ // Plugin settings.
+ config->setGroup("colormanagement Tool Dialog");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+ m_toolBoxWidgets->setCurrentIndex(config->readNumEntry("Settings Tab", GENERALPAGE));
+ m_inProfilesPath->setURL(config->readPathEntry("InputProfilePath", defaultICCPath));
+ m_proofProfilePath->setURL(config->readPathEntry("ProofProfilePath", defaultICCPath));
+ m_spaceProfilePath->setURL(config->readPathEntry("SpaceProfilePath", defaultICCPath));
+ m_renderingIntentsCB->setCurrentItem(config->readNumEntry("RenderingIntent", 0));
+ m_doSoftProofBox->setChecked(config->readBoolEntry("DoSoftProof", false));
+ m_checkGamutBox->setChecked(config->readBoolEntry("CheckGamut", false));
+ m_embeddProfileBox->setChecked(config->readBoolEntry("EmbeddProfile", true));
+ m_BPCBox->setChecked(config->readBoolEntry("BPC", true));
+ m_inProfileBG->setButton(config->readNumEntry("InputProfileMethod", 0));
+ m_spaceProfileBG->setButton(config->readNumEntry("SpaceProfileMethod", 0));
+ m_proofProfileBG->setButton(config->readNumEntry("ProofProfileMethod", 0));
+ m_cInput->setValue(config->readNumEntry("ContrastAjustment", 0));
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesChannelReset(i);
+
+ m_curves->setCurveType(m_curvesWidget->m_channelType, Digikam::ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p = config->readPointEntry(TQString("CurveAjustmentPoint%1").arg(j), &disable);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, j, p);
+ }
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesCalculateCurve(i);
+
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ImageEffect_ICCProof::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("colormanagement Tool Dialog");
+ config->writeEntry("Settings Tab", m_toolBoxWidgets->currentIndex());
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writePathEntry("InputProfilePath", m_inProfilesPath->url());
+ config->writePathEntry("ProofProfilePath", m_proofProfilePath->url());
+ config->writePathEntry("SpaceProfilePath", m_spaceProfilePath->url());
+ config->writeEntry("RenderingIntent", m_renderingIntentsCB->currentItem());
+ config->writeEntry("DoSoftProof", m_doSoftProofBox->isChecked());
+ config->writeEntry("CheckGamut", m_checkGamutBox->isChecked());
+ config->writeEntry("EmbeddProfile", m_embeddProfileBox->isChecked());
+ config->writeEntry("BPC", m_BPCBox->isChecked());
+ config->writeEntry("InputProfileMethod", m_inProfileBG->selectedId());
+ config->writeEntry("SpaceProfileMethod", m_spaceProfileBG->selectedId());
+ config->writeEntry("ProofProfileMethod", m_proofProfileBG->selectedId());
+ config->writeEntry("ContrastAjustment", m_cInput->value());
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint p = m_curves->getCurvePoint(Digikam::ImageHistogram::ValueChannel, j);
+
+ if (m_originalImage->sixteenBit() && p.x() != -1)
+ {
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+
+ config->writeEntry(TQString("CurveAjustmentPoint%1").arg(j), p);
+ }
+
+ config->sync();
+}
+
+void ImageEffect_ICCProof::processLCMSURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void ImageEffect_ICCProof::slotSpotColorChanged(const Digikam::DColor &color)
+{
+ m_curvesWidget->setCurveGuide(color);
+}
+
+void ImageEffect_ICCProof::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_ICCProof::slotChannelChanged( int channel )
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_ICCProof::slotScaleChanged( int scale )
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_ICCProof::resetValues()
+{
+ m_cInput->blockSignals(true);
+ m_cInput->setValue(0);
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesChannelReset(i);
+
+ m_curvesWidget->reset();
+ m_cInput->blockSignals(false);
+}
+
+void ImageEffect_ICCProof::slotEffect()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+ enableButtonOK(true);
+ m_histogramWidget->stopHistogramComputation();
+
+ Digikam::IccTransform transform;
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ Digikam::ImageIface *iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool a = iface->previewHasAlpha();
+ bool sb = iface->previewSixteenBit();
+
+ Digikam::DImg preview(w, h, sb, a, m_destinationPreviewData);
+
+ TQString tmpInPath = TQString();
+ TQString tmpProofPath = TQString();
+ TQString tmpSpacePath = TQString();
+
+ bool proofCondition = false;
+ bool spaceCondition = false;
+
+ //-- Input profile parameters ------------------
+
+ if (useDefaultInProfile())
+ {
+ tmpInPath = m_inPath;
+ }
+ else if (useSelectedInProfile())
+ {
+ tmpInPath = m_inProfilesPath->url();
+ TQFileInfo info(tmpInPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile() )
+ {
+ KMessageBox::information(this, i18n("<p>The selected ICC input profile path seems to be invalid.<p>"
+ "Please check it."));
+ return;
+ }
+ }
+
+ //-- Proof profile parameters ------------------
+
+ if (useDefaultProofProfile())
+ {
+ tmpProofPath = m_proofPath;
+ }
+ else
+ {
+ tmpProofPath = m_proofProfilePath->url();
+ TQFileInfo info(tmpProofPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile() )
+ {
+ KMessageBox::information(this, i18n("<p>The selected ICC proof profile path seems to be invalid.<p>"
+ "Please check it."));
+ return;
+ }
+ }
+
+ if (m_doSoftProofBox->isChecked())
+ proofCondition = tmpProofPath.isEmpty();
+
+ //-- Workspace profile parameters --------------
+
+ if (useDefaultSpaceProfile())
+ {
+ tmpSpacePath = m_spacePath;
+ }
+ else
+ {
+ tmpSpacePath = m_spaceProfilePath->url();
+ TQFileInfo info(tmpSpacePath);
+ if (!info.exists() || !info.isReadable() || !info.isFile() )
+ {
+ KMessageBox::information(this, i18n("<p>Selected ICC workspace profile path seems to be invalid.<p>"
+ "Please check it."));
+ return;
+ }
+ }
+
+ spaceCondition = tmpSpacePath.isEmpty();
+
+ //-- Perform the color transformations ------------------
+
+ transform.getTransformType(m_doSoftProofBox->isChecked());
+
+ if (m_doSoftProofBox->isChecked())
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles( tmpSpacePath, tmpProofPath, true );
+ }
+ else
+ {
+ transform.setProfiles( tmpInPath, tmpSpacePath, tmpProofPath);
+ }
+ }
+ else
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles( tmpSpacePath );
+ }
+ else
+ {
+ transform.setProfiles( tmpInPath, tmpSpacePath );
+ }
+ }
+
+ if ( proofCondition || spaceCondition )
+ {
+ kapp->restoreOverrideCursor();
+ TQString error = i18n("<p>Your settings are not sufficient.</p>"
+ "<p>To apply a color transform, you need at least two ICC profiles:</p>"
+ "<ul><li>An \"Input\" profile.</li>"
+ "<li>A \"Workspace\" profile.</li></ul>"
+ "<p>If you want to do a \"soft-proof\" transform, in addition to these profiles "
+ "you need a \"Proof\" profile.</p>");
+ KMessageBox::information(this, error);
+ enableButtonOK(false);
+ }
+ else
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.apply(preview, m_embeddedICC, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+ else
+ {
+ TQByteArray fakeProfile = TQByteArray();
+ transform.apply(preview, fakeProfile, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+
+ //-- Calculate and apply the curve on image after transformation -------------
+
+ Digikam::DImg preview2(w, h, sb, a, 0, false);
+ m_curves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+ m_curves->curvesLutProcess(preview.bits(), preview2.bits(), w, h);
+
+ //-- Adjust contrast ---------------------------------------------------------
+
+ Digikam::BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(preview2);
+
+ iface->putPreviewImage(preview2.bits());
+ m_previewWidget->updatePreview();
+
+ //-- Update histogram --------------------------------------------------------
+
+ memcpy(m_destinationPreviewData, preview2.bits(), preview2.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ kapp->restoreOverrideCursor();
+ }
+}
+
+void ImageEffect_ICCProof::finalRendering()
+{
+ if (!m_doSoftProofBox->isChecked())
+ {
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ Digikam::ImageIface *iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+
+ if (data)
+ {
+ Digikam::IccTransform transform;
+
+ Digikam::DImg img(w, h, sb, a, data);
+
+ TQString tmpInPath;
+ TQString tmpProofPath;
+ TQString tmpSpacePath;
+ bool proofCondition;
+
+ //-- Input profile parameters ------------------
+
+ if (useDefaultInProfile())
+ {
+ tmpInPath = m_inPath;
+ }
+ else if (useSelectedInProfile())
+ {
+ tmpInPath = m_inProfilesPath->url();
+ TQFileInfo info(tmpInPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile() )
+ {
+ KMessageBox::information(this, i18n("<p>Selected ICC input profile path seems "
+ "to be invalid.<p>Please check it."));
+ return;
+ }
+ }
+
+ //-- Proof profile parameters ------------------
+
+ if (useDefaultProofProfile())
+ {
+ tmpProofPath = m_proofPath;
+ }
+ else
+ {
+ tmpProofPath = m_proofProfilePath->url();
+ TQFileInfo info(tmpProofPath);
+ if (!info.exists() || !info.isReadable() || !info.isFile() )
+ {
+ KMessageBox::information(this, i18n("<p>The selected ICC proof profile path seems "
+ "to be invalid.<p>Please check it."));
+ return;
+ }
+ }
+
+ if (tmpProofPath.isNull())
+ proofCondition = false;
+
+ //-- Workspace profile parameters --------------
+
+ if (useDefaultSpaceProfile())
+ {
+ tmpSpacePath = m_spacePath;
+ }
+ else
+ {
+ tmpSpacePath = m_spaceProfilePath->url();
+ TQFileInfo info(tmpSpacePath);
+ if (!info.exists() || !info.isReadable() || !info.isFile() )
+ {
+ KMessageBox::information(this, i18n("<p>Selected ICC workspace profile path seems "
+ "to be invalid.<p>Please check it."));
+ return;
+ }
+ }
+
+ //-- Perform the color transformations ------------------
+
+ transform.getTransformType(m_doSoftProofBox->isChecked());
+
+ if (m_doSoftProofBox->isChecked())
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles( tmpSpacePath, tmpProofPath, true );
+ }
+ else
+ {
+ transform.setProfiles( tmpInPath, tmpSpacePath, tmpProofPath);
+ }
+ }
+ else
+ {
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.setProfiles( tmpSpacePath );
+ }
+ else
+ {
+ transform.setProfiles( tmpInPath, tmpSpacePath );
+ }
+ }
+
+ if (m_useEmbeddedProfile->isChecked())
+ {
+ transform.apply(img, m_embeddedICC, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+ else
+ {
+ TQByteArray fakeProfile = TQByteArray();
+ transform.apply(img, fakeProfile, m_renderingIntentsCB->currentItem(), useBPC(),
+ m_checkGamutBox->isChecked(), useBuiltinProfile());
+ }
+
+ //-- Embed the workspace profile if necessary --------------------------------
+
+ if (m_embeddProfileBox->isChecked())
+ {
+ iface->setEmbeddedICCToOriginalImage( tmpSpacePath );
+ DDebug() << k_funcinfo << TQFile::encodeName(tmpSpacePath) << endl;
+ }
+
+ //-- Calculate and apply the curve on image after transformation -------------
+
+ Digikam::DImg img2(w, h, sb, a, 0, false);
+ m_curves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+ m_curves->curvesLutProcess(img.bits(), img2.bits(), w, h);
+
+ //-- Adjust contrast ---------------------------------------------------------
+
+ Digikam::BCGModifier cmod;
+ cmod.setContrast((double)(m_cInput->value()/100.0) + 1.00);
+ cmod.applyBCG(img2);
+
+ iface->putOriginalImage("Color Management", img2.bits());
+ delete [] data;
+ }
+
+ kapp->restoreOverrideCursor();
+ }
+
+ accept();
+}
+
+void ImageEffect_ICCProof::slotToggledWidgets( bool t)
+{
+ m_useInDefaultProfile->setEnabled(t);
+ m_useProofDefaultProfile->setEnabled(t);
+ m_useSpaceDefaultProfile->setEnabled(t);
+}
+
+void ImageEffect_ICCProof::slotInICCInfo()
+{
+ if (useEmbeddedProfile())
+ {
+ getICCInfo(m_embeddedICC);
+ }
+ else if(useBuiltinProfile())
+ {
+ TQString message = i18n("<p>You have selected the \"Default builtin sRGB profile\"</p>");
+ message.append(i18n("<p>This profile is built on the fly, so there is no relevant information "
+ "about it.</p>"));
+ KMessageBox::information(this, message);
+ }
+ else if (useDefaultInProfile())
+ {
+ getICCInfo(m_inPath);
+ }
+ else if (useSelectedInProfile())
+ {
+ getICCInfo(m_inProfilesPath->url());
+ }
+}
+
+void ImageEffect_ICCProof::slotProofICCInfo()
+{
+ if (useDefaultProofProfile())
+ {
+ getICCInfo(m_proofPath);
+ }
+ else
+ {
+ getICCInfo(m_proofProfilePath->url());
+ }
+}
+
+void ImageEffect_ICCProof::slotSpaceICCInfo()
+{
+ if (useDefaultSpaceProfile())
+ {
+ getICCInfo(m_spacePath);
+ }
+ else
+ {
+ getICCInfo(m_spaceProfilePath->url());
+ }
+}
+
+void ImageEffect_ICCProof::getICCInfo(const TQString& profile)
+{
+ if (profile.isEmpty())
+ {
+ KMessageBox::error(this, i18n("Sorry, there is no selected profile"), i18n("Profile Error"));
+ return;
+ }
+
+ Digikam::ICCProfileInfoDlg infoDlg(this, profile);
+ infoDlg.exec();
+}
+
+void ImageEffect_ICCProof::getICCInfo(const TQByteArray& profile)
+{
+ if (profile.isNull())
+ {
+ KMessageBox::error(this, i18n("Sorry, it seems there is no embedded profile"), i18n("Profile Error"));
+ return;
+ }
+
+ Digikam::ICCProfileInfoDlg infoDlg(this, TQString(), profile);
+ infoDlg.exec();
+}
+
+void ImageEffect_ICCProof::slotCMDisabledWarning()
+{
+ if (!m_cmEnabled)
+ {
+ TQString message = i18n("<p>You have not enabled Color Management in the digiKam preferences.</p>");
+ message.append( i18n("<p>\"Use of default profile\" options will be disabled now.</p>"));
+ KMessageBox::information(this, message);
+ slotToggledWidgets(false);
+ }
+}
+
+//-- General Tab ---------------------------
+
+bool ImageEffect_ICCProof::useBPC()
+{
+ return m_BPCBox->isChecked();
+}
+
+bool ImageEffect_ICCProof::doProof()
+{
+ return m_doSoftProofBox->isChecked();
+}
+
+bool ImageEffect_ICCProof::checkGamut()
+{
+ return m_checkGamutBox->isChecked();
+}
+
+bool ImageEffect_ICCProof::embedProfile()
+{
+ return m_embeddProfileBox->isChecked();
+}
+
+//-- Input Tab ---------------------------
+
+bool ImageEffect_ICCProof::useEmbeddedProfile()
+{
+ return m_useEmbeddedProfile->isChecked();
+}
+
+bool ImageEffect_ICCProof::useBuiltinProfile()
+{
+ return m_useSRGBDefaultProfile->isChecked();
+}
+
+bool ImageEffect_ICCProof::useDefaultInProfile()
+{
+ return m_useInDefaultProfile->isChecked();
+}
+
+bool ImageEffect_ICCProof::useSelectedInProfile()
+{
+ return m_useInSelectedProfile->isChecked();
+}
+
+//-- Workspace Tab ---------------------------
+
+bool ImageEffect_ICCProof::useDefaultSpaceProfile()
+{
+ return m_useSpaceDefaultProfile->isChecked();
+}
+
+//-- Proofing Tab ---------------------------
+
+bool ImageEffect_ICCProof::useDefaultProofProfile()
+{
+ return m_useProofDefaultProfile->isChecked();
+}
+
+//-- Load all settings from file --------------------------------------
+
+void ImageEffect_ICCProof::slotUser3()
+{
+ KURL loadColorManagementFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Color Management Settings File to Load")) );
+ if( loadColorManagementFile.isEmpty() )
+ return;
+
+ TQFile file(loadColorManagementFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+
+ if ( stream.readLine() != "# Color Management Configuration File" )
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a Color Management settings text file.")
+ .arg(loadColorManagementFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+
+ m_renderingIntentsCB->setCurrentItem( stream.readLine().toInt() );
+ m_doSoftProofBox->setChecked( (bool)(stream.readLine().toUInt()) );
+ m_checkGamutBox->setChecked( (bool)(stream.readLine().toUInt()) );
+ m_embeddProfileBox->setChecked( (bool)(stream.readLine().toUInt()) );
+ m_BPCBox->setChecked( (bool)(stream.readLine().toUInt()) );
+ m_inProfileBG->setButton( stream.readLine().toInt() );
+ m_spaceProfileBG->setButton( stream.readLine().toInt() );
+ m_proofProfileBG->setButton( stream.readLine().toInt() );
+ m_inProfilesPath->setURL( stream.readLine() );
+ m_proofProfilePath->setURL( stream.readLine() );
+ m_spaceProfilePath->setURL( stream.readLine() );
+ m_cInput->setValue( stream.readLine().toInt() );
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesChannelReset(i);
+
+ m_curves->setCurveType(m_curvesWidget->m_channelType, Digikam::ImageCurves::CURVE_SMOOTH);
+ m_curvesWidget->reset();
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p;
+ p.setX( stream.readLine().toInt() );
+ p.setY( stream.readLine().toInt() );
+
+ if (m_originalImage->sixteenBit() && p != disable)
+ {
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+
+ m_curves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, j, p);
+ }
+
+ blockSignals(false);
+
+ for (int i = 0 ; i < 5 ; i++)
+ m_curves->curvesCalculateCurve(i);
+
+ m_histogramWidget->reset();
+ slotEffect();
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the Color Management text file."));
+
+ file.close();
+}
+
+//-- Save all settings to file ---------------------------------------
+
+void ImageEffect_ICCProof::slotUser2()
+{
+ KURL saveColorManagementFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Color Management Settings File to Save")) );
+ if( saveColorManagementFile.isEmpty() )
+ return;
+
+ TQFile file(saveColorManagementFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# Color Management Configuration File\n";
+ stream << m_renderingIntentsCB->currentItem() << "\n";
+ stream << m_doSoftProofBox->isChecked() << "\n";
+ stream << m_checkGamutBox->isChecked() << "\n";
+ stream << m_embeddProfileBox->isChecked() << "\n";
+ stream << m_BPCBox->isChecked() << "\n";
+ stream << m_inProfileBG->selectedId() << "\n";
+ stream << m_spaceProfileBG->selectedId() << "\n";
+ stream << m_proofProfileBG->selectedId() << "\n";
+ stream << m_inProfilesPath->url() << "\n";
+ stream << m_proofProfilePath->url() << "\n";
+ stream << m_spaceProfilePath->url() << "\n";
+ stream << m_cInput->value() << "\n";
+
+ for (int j = 0 ; j < 17 ; j++)
+ {
+ TQPoint p = m_curves->getCurvePoint(Digikam::ImageHistogram::ValueChannel, j);
+ if (m_originalImage->sixteenBit())
+ {
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+ stream << p.x() << "\n";
+ stream << p.y() << "\n";
+ }
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the Color Management text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/imageeffect_iccproof.h b/src/imageplugins/coreplugin/imageeffect_iccproof.h
new file mode 100644
index 00000000..58112aef
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_iccproof.h
@@ -0,0 +1,204 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-21
+ * Description : digiKam image editor tool to correct picture
+ * colors using an ICC color profile
+ *
+ * Copyright (C) 2005-2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_ICCPROOF_H
+#define IMAGEEFFECT_ICCPROOF_H
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQCheckBox;
+class TQComboBox;
+class TQVButtonGroup;
+class TQButtonGroup;
+class TQHButtonGroup;
+class TQRadioButton;
+class TQPushButton;
+class TQToolBox;
+
+class KURLRequester;
+class KIntNumInput;
+
+namespace Digikam
+{
+class ICCTransform;
+class ImageWidget;
+class HistogramWidget;
+class ColorGradientWidget;
+class DColor;
+class ICCPreviewWidget;
+class ImageCurves;
+class CurvesWidget;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageEffect_ICCProof : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_ICCProof(TQWidget* parent);
+ ~ImageEffect_ICCProof();
+
+protected:
+
+ void finalRendering();
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+
+ void getICCInfo(const TQString&);
+ void getICCInfo(const TQByteArray&);
+
+ bool useBPC();
+ bool doProof();
+ bool checkGamut();
+ bool embedProfile();
+
+ bool useEmbeddedProfile();
+ bool useBuiltinProfile();
+ bool useDefaultInProfile();
+ bool useSelectedInProfile();
+
+ bool useDefaultSpaceProfile();
+ bool useSelectedSpaceProfile();
+
+ bool useDefaultProofProfile();
+ bool useSelectedProofProfile();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void slotEffect();
+ void slotChannelChanged(int);
+ void slotScaleChanged(int);
+ void slotSpotColorChanged(const Digikam::DColor &);
+ void slotColorSelectedFromTarget(const Digikam::DColor &);
+ void slotToggledWidgets(bool);
+ void slotInICCInfo();
+ void slotProofICCInfo();
+ void slotSpaceICCInfo();
+ void slotCMDisabledWarning();
+ void processLCMSURL(const TQString&);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear = 0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel = 0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum ICCSettingsTab
+ {
+ GENERALPAGE=0,
+ INPUTPAGE,
+ WORKSPACEPAGE,
+ PROOFINGPAGE,
+ LIGHTNESSPAGE
+ };
+
+ bool m_cmEnabled;
+ bool m_hasICC;
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+ TQComboBox *m_renderingIntentsCB;
+
+ TQCheckBox *m_doSoftProofBox;
+ TQCheckBox *m_checkGamutBox;
+ TQCheckBox *m_embeddProfileBox;
+ TQCheckBox *m_BPCBox;
+
+ TQRadioButton *m_useEmbeddedProfile;
+ TQRadioButton *m_useInDefaultProfile;
+ TQRadioButton *m_useInSelectedProfile;
+ TQRadioButton *m_useProofDefaultProfile;
+ TQRadioButton *m_useProofSelectedProfile;
+ TQRadioButton *m_useSpaceDefaultProfile;
+ TQRadioButton *m_useSpaceSelectedProfile;
+ TQRadioButton *m_useSRGBDefaultProfile;
+
+ TQString m_inPath;
+ TQString m_spacePath;
+ TQString m_proofPath;
+
+ TQButtonGroup *m_optionsBG;
+ TQButtonGroup *m_inProfileBG;
+ TQButtonGroup *m_spaceProfileBG;
+ TQButtonGroup *m_proofProfileBG;
+
+ TQHButtonGroup *m_scaleBG;
+ TQVButtonGroup *m_renderingIntentBG;
+ TQVButtonGroup *m_profilesBG;
+
+ TQByteArray m_embeddedICC;
+
+ TQToolBox *m_toolBoxWidgets;
+
+ KIntNumInput *m_cInput;
+
+ KURLRequester *m_inProfilesPath;
+ KURLRequester *m_spaceProfilePath;
+ KURLRequester *m_proofProfilePath;
+
+ Digikam::DImg *m_originalImage;
+
+ Digikam::CurvesWidget *m_curvesWidget;
+
+ Digikam::ImageCurves *m_curves;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ICCPreviewWidget *m_iccInPreviewWidget;
+ Digikam::ICCPreviewWidget *m_iccSpacePreviewWidget;
+ Digikam::ICCPreviewWidget *m_iccProofPreviewWidget;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif // IMAGEEFFECT_ICCPROOF_H
diff --git a/src/imageplugins/coreplugin/imageeffect_redeye.cpp b/src/imageplugins/coreplugin/imageeffect_redeye.cpp
new file mode 100644
index 00000000..df3ae2e7
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_redeye.cpp
@@ -0,0 +1,574 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-06
+ * Description : Red eyes correction tool for image editor
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqhbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <kcolordialog.h>
+#include <knuminput.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kstandarddirs.h>
+#include <kcolordialog.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "bcgmodifier.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+
+// Local includes.
+
+#include "imageeffect_redeye.h"
+#include "imageeffect_redeye.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_RedEye::ImageEffect_RedEye(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Red Eye Reduction"), "redeye", false)
+{
+ m_destinationPreviewData = 0;
+ setHelp("redeyecorrectiontool.anchor", "digikam");
+
+ m_previewWidget = new Digikam::ImageWidget("redeye Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the image selection preview with "
+ "red eye reduction applied."),
+ true, Digikam::ImageGuideWidget::PickColorMode, true, true);
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings, 11, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image channel values.<p>"
+ "<b>Green</b>: display the green image channel values.<p>"
+ "<b>Blue</b>: display the blue image channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximum counts are small, you can use the linear scale.<p>"
+ "The logarithmic scale can be used when the maximal counts are big "
+ "to show all values (small and large) on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram "
+ "of the selected image channel. It is "
+ "updated upon setting changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget(Digikam::ColorGradientWidget::Horizontal, 10, histoBox);
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ // -------------------------------------------------------------
+
+ m_thresholdLabel = new TQLabel(i18n("Sensitivity:"), gboxSettings);
+ m_redThreshold = new KIntNumInput(gboxSettings);
+ m_redThreshold->setRange(10, 90, 1, true);
+ m_redThreshold->setValue(20);
+ TQWhatsThis::add(m_redThreshold, i18n("<p>Sets the red color pixels selection threshold. "
+ "Low values will select more red color pixels (agressive correction), high "
+ "values less (mild correction). Use low value if eye have been selected "
+ "exactly. Use high value if other parts of the face are also selected."));
+
+ m_smoothLabel = new TQLabel(i18n("Smooth:"), gboxSettings);
+ m_smoothLevel = new KIntNumInput(gboxSettings);
+ m_smoothLevel->setRange(0, 5, 1, true);
+ m_smoothLevel->setValue(1);
+ TQWhatsThis::add(m_smoothLevel, i18n("<p>Sets the smoothness value when blurring the border "
+ "of the changed pixels. "
+ "This leads to a more naturally looking pupil."));
+
+ TQLabel *label3 = new TQLabel(i18n("Coloring Tint:"), gboxSettings);
+ m_HSSelector = new KHSSelector(gboxSettings);
+ m_VSelector = new KValueSelector(gboxSettings);
+ m_HSSelector->setMinimumSize(200, 142);
+ m_VSelector->setMinimumSize(26, 142);
+ TQWhatsThis::add(m_HSSelector, i18n("<p>Sets a custom color to re-colorize the eyes."));
+
+ TQLabel *label4 = new TQLabel(i18n("Tint Level:"), gboxSettings);
+ m_tintLevel = new KIntNumInput(gboxSettings);
+ m_tintLevel->setRange(1, 200, 1, true);
+ m_tintLevel->setValue(128);
+ TQWhatsThis::add(m_tintLevel, i18n("<p>Set the tint level to adjust the luminosity of "
+ "the new color of the pupil."));
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+ gridSettings->addMultiCellWidget(m_thresholdLabel, 3, 3, 0, 4);
+ gridSettings->addMultiCellWidget(m_redThreshold, 4, 4, 0, 4);
+ gridSettings->addMultiCellWidget(m_smoothLabel, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_smoothLevel, 6, 6, 0, 4);
+ gridSettings->addMultiCellWidget(label3, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_HSSelector, 8, 8, 0, 3);
+ gridSettings->addMultiCellWidget(m_VSelector, 8, 8, 4, 4);
+ gridSettings->addMultiCellWidget(label4, 9, 9, 0, 4);
+ gridSettings->addMultiCellWidget(m_tintLevel, 10, 10, 0, 4);
+ gridSettings->setRowStretch(11, 10);
+ gridSettings->setColStretch(3, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromTarget(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_redThreshold, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_smoothLevel, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_HSSelector, TQ_SIGNAL(valueChanged(int, int)),
+ this, TQ_SLOT(slotHSChanged(int, int)));
+
+ connect(m_VSelector, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_tintLevel, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_RedEye::~ImageEffect_RedEye()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_previewWidget;
+}
+
+void ImageEffect_RedEye::slotHSChanged(int h, int s)
+{
+ m_VSelector->blockSignals(true);
+ m_VSelector->setHue(h);
+ m_VSelector->setSaturation(s);
+ m_VSelector->updateContents();
+ m_VSelector->repaint(false);
+ m_VSelector->blockSignals(false);
+ slotTimer();
+}
+
+void ImageEffect_RedEye::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_RedEye::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_RedEye::slotColorSelectedFromTarget(const Digikam::DColor& color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_RedEye::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("redeye Tool Dialog");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+ m_redThreshold->setValue(config->readNumEntry("RedThreshold", 20));
+ m_smoothLevel->setValue(config->readNumEntry("SmoothLevel", 1));
+ m_HSSelector->setXValue(config->readNumEntry("HueColoringTint", 0));
+ m_HSSelector->setYValue(config->readNumEntry("SatColoringTint", 0));
+ m_VSelector->setValue(config->readNumEntry("ValColoringTint", 0));
+ m_tintLevel->setValue(config->readNumEntry("TintLevel", 128));
+
+ slotHSChanged(m_HSSelector->xValue(), m_HSSelector->yValue());
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ImageEffect_RedEye::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("redeye Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("RedThreshold", m_redThreshold->value());
+ config->writeEntry("SmoothLevel", m_smoothLevel->value());
+ config->writeEntry("HueColoringTint", m_HSSelector->xValue());
+ config->writeEntry("SatColoringTint", m_HSSelector->yValue());
+ config->writeEntry("ValColoringTint", m_VSelector->value());
+ config->writeEntry("TintLevel", m_tintLevel->value());
+ config->sync();
+}
+
+void ImageEffect_RedEye::resetValues()
+{
+ m_redThreshold->blockSignals(true);
+ m_HSSelector->blockSignals(true);
+ m_VSelector->blockSignals(true);
+ m_tintLevel->blockSignals(true);
+
+ m_redThreshold->setValue(20);
+ m_smoothLevel->setValue(1);
+
+ // Black color by default
+ m_HSSelector->setXValue(0);
+ m_HSSelector->setYValue(0);
+ m_VSelector->setValue(0);
+
+ m_tintLevel->setValue(128);
+
+ m_redThreshold->blockSignals(false);
+ m_HSSelector->blockSignals(false);
+ m_VSelector->blockSignals(false);
+ m_tintLevel->blockSignals(false);
+}
+
+void ImageEffect_RedEye::slotEffect()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ // Here, we need to use the real selection image data because we will apply
+ // a Gaussian blur filter on pixels and we cannot use directly the preview scaled image
+ // else the blur radius will not give the same result between preview and final rendering.
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getImageSelection();
+ int w = iface->selectedWidth();
+ int h = iface->selectedHeight();
+ bool sb = iface->originalSixteenBit();
+ bool a = iface->originalHasAlpha();
+ Digikam::DImg selection(w, h, sb, a, m_destinationPreviewData);
+
+ redEyeFilter(selection);
+
+ Digikam::DImg preview = selection.smoothScale(iface->previewWidth(), iface->previewHeight());
+
+ iface->putPreviewImage(preview.bits());
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, selection.bits(), selection.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void ImageEffect_RedEye::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getImageSelection();
+ int w = iface->selectedWidth();
+ int h = iface->selectedHeight();
+ bool sixteenBit = iface->originalSixteenBit();
+ bool hasAlpha = iface->originalHasAlpha();
+ Digikam::DImg selection(w, h, sixteenBit, hasAlpha, data);
+ delete [] data;
+
+ redEyeFilter(selection);
+
+ iface->putImageSelection(i18n("Red Eyes Correction"), selection.bits());
+
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+void ImageEffect_RedEye::redEyeFilter(Digikam::DImg& selection)
+{
+ Digikam::DImg mask(selection.width(), selection.height(), selection.sixteenBit(), true,
+ selection.bits(), true);
+
+ selection = mask.copy();
+ float redThreshold = m_redThreshold->value()/10.0;
+ int hue = m_HSSelector->xValue();
+ int sat = m_HSSelector->yValue();
+ int val = m_VSelector->value();
+ KColor coloring;
+ coloring.setHsv(hue, sat, val);
+
+ struct channel
+ {
+ float red_gain;
+ float green_gain;
+ float blue_gain;
+ };
+
+ channel red_chan, green_chan, blue_chan;
+
+ red_chan.red_gain = 0.1;
+ red_chan.green_gain = 0.6;
+ red_chan.blue_gain = 0.3;
+
+ green_chan.red_gain = 0.0;
+ green_chan.green_gain = 1.0;
+ green_chan.blue_gain = 0.0;
+
+ blue_chan.red_gain = 0.0;
+ blue_chan.green_gain = 0.0;
+ blue_chan.blue_gain = 1.0;
+
+ float red_norm, green_norm, blue_norm;
+ int level = 201 - m_tintLevel->value();
+
+ red_norm = 1.0 / (red_chan.red_gain + red_chan.green_gain + red_chan.blue_gain);
+ green_norm = 1.0 / (green_chan.red_gain + green_chan.green_gain + green_chan.blue_gain);
+ blue_norm = 1.0 / (blue_chan.red_gain + blue_chan.green_gain + blue_chan.blue_gain);
+
+ red_norm *= coloring.red() / level;
+ green_norm *= coloring.green() / level;
+ blue_norm *= coloring.blue() / level;
+
+ // Perform a red color pixels detection in selection image and create a correction mask using an alpha channel.
+
+ if (!selection.sixteenBit()) // 8 bits image.
+ {
+ uchar* ptr = selection.bits();
+ uchar* mptr = mask.bits();
+ uchar r, g, b, r1, g1, b1;
+
+ for (uint i = 0 ; i < selection.width() * selection.height() ; i++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ mptr[3] = 255;
+
+ if (r >= ( redThreshold * g))
+ {
+ r1 = TQMIN(255, (int)(red_norm * (red_chan.red_gain * r +
+ red_chan.green_gain * g +
+ red_chan.blue_gain * b)));
+
+ g1 = TQMIN(255, (int)(green_norm * (green_chan.red_gain * r +
+ green_chan.green_gain * g +
+ green_chan.blue_gain * b)));
+
+ b1 = TQMIN(255, (int)(blue_norm * (blue_chan.red_gain * r +
+ blue_chan.green_gain * g +
+ blue_chan.blue_gain * b)));
+
+ mptr[0] = b1;
+ mptr[1] = g1;
+ mptr[2] = r1;
+ mptr[3] = TQMIN( (int)((r-g) / 150.0 * 255.0), 255);
+ }
+
+ ptr += 4;
+ mptr+= 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short* ptr = (unsigned short*)selection.bits();
+ unsigned short* mptr = (unsigned short*)mask.bits();
+ unsigned short r, g, b, r1, g1, b1;
+
+ for (uint i = 0 ; i < selection.width() * selection.height() ; i++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ mptr[3] = 65535;
+
+ if (r >= ( redThreshold * g))
+ {
+ r1 = TQMIN(65535, (int)(red_norm * (red_chan.red_gain * r +
+ red_chan.green_gain * g +
+ red_chan.blue_gain * b)));
+
+ g1 = TQMIN(65535, (int)(green_norm * (green_chan.red_gain * r +
+ green_chan.green_gain * g +
+ green_chan.blue_gain * b)));
+
+ b1 = TQMIN(65535, (int)(blue_norm * (blue_chan.red_gain * r +
+ blue_chan.green_gain * g +
+ blue_chan.blue_gain * b)));
+
+ mptr[0] = b1;
+ mptr[1] = g1;
+ mptr[2] = r1;
+ mptr[3] = TQMIN( (int)((r-g) / 38400.0 * 65535.0), 65535);;
+ }
+
+ ptr += 4;
+ mptr+= 4;
+ }
+ }
+
+ // Now, we will blur only the transparency pixels from the mask.
+
+ Digikam::DImg mask2 = mask.copy();
+ Digikam::DImgImageFilters filter;
+ filter.gaussianBlurImage(mask2.bits(), mask2.width(), mask2.height(),
+ mask2.sixteenBit(), m_smoothLevel->value());
+
+ if (!selection.sixteenBit()) // 8 bits image.
+ {
+ uchar* mptr = mask.bits();
+ uchar* mptr2 = mask2.bits();
+
+ for (uint i = 0 ; i < mask2.width() * mask2.height() ; i++)
+ {
+ if (mptr2[3] < 255)
+ {
+ mptr[0] = mptr2[0];
+ mptr[1] = mptr2[1];
+ mptr[2] = mptr2[2];
+ mptr[3] = mptr2[3];
+ }
+
+ mptr += 4;
+ mptr2+= 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short* mptr = (unsigned short*)mask.bits();
+ unsigned short* mptr2 = (unsigned short*)mask2.bits();
+
+ for (uint i = 0 ; i < mask2.width() * mask2.height() ; i++)
+ {
+ if (mptr2[3] < 65535)
+ {
+ mptr[0] = mptr2[0];
+ mptr[1] = mptr2[1];
+ mptr[2] = mptr2[2];
+ mptr[3] = mptr2[3];
+ }
+
+ mptr += 4;
+ mptr2+= 4;
+ }
+ }
+
+ // - Perform pixels blending using alpha channel between the mask and the selection.
+
+ Digikam::DColorComposer *composer = Digikam::DColorComposer::getComposer(Digikam::DColorComposer::PorterDuffSrcOver);
+
+ // NOTE: 'mask' is the Source image, 'selection' is the Destination image.
+
+ selection.bitBlendImage(composer, &mask,
+ 0, 0, mask.width(), mask.height(),
+ 0, 0);
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/imageeffect_redeye.h b/src/imageplugins/coreplugin/imageeffect_redeye.h
new file mode 100644
index 00000000..79b517ee
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_redeye.h
@@ -0,0 +1,153 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-06
+ * Description : Red eyes correction tool for image editor
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef IMAGEEFFECT_REDEYE_H
+#define IMAGEEFFECT_REDEYE_H
+
+// KDE includes.
+
+#include <kpassivepopup.h>
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQLabel;
+class TQComboBox;
+class TQHButtonGroup;
+
+class KHSSelector;
+class KValueSelector;
+class KIntNumInput;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class DImg;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class RedEyePassivePopup : public KPassivePopup
+{
+public:
+
+ RedEyePassivePopup(TQWidget* parent)
+ : KPassivePopup(parent), m_parent(parent)
+ {
+ }
+
+protected:
+
+ virtual void positionSelf()
+ {
+ move(m_parent->x() + 30, m_parent->y() + 30);
+ }
+
+private:
+
+ TQWidget* m_parent;
+};
+
+// ----------------------------------------------------------------
+
+class ImageEffect_RedEye : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_RedEye(TQWidget *parent);
+ ~ImageEffect_RedEye();
+
+private slots:
+
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+ void slotHSChanged(int h, int s);
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+ void redEyeFilter(Digikam::DImg& selection);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum RedThresold
+ {
+ Mild=0,
+ Aggressive
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQLabel *m_thresholdLabel;
+ TQLabel *m_smoothLabel;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KIntNumInput *m_tintLevel;
+ KIntNumInput *m_redThreshold;
+ KIntNumInput *m_smoothLevel;
+
+ KHSSelector *m_HSSelector;
+ KValueSelector *m_VSelector;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_REDEYE_H */
diff --git a/src/imageplugins/coreplugin/imageeffect_rgb.cpp b/src/imageplugins/coreplugin/imageeffect_rgb.cpp
new file mode 100644
index 00000000..fcff9c41
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_rgb.cpp
@@ -0,0 +1,419 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-11
+ * Description : digiKam image editor Color Balance tool.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqspinbox.h>
+#include <tqslider.h>
+#include <tqcolor.h>
+#include <tqgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <kstandarddirs.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "colormodifier.h"
+#include "dimg.h"
+
+// Local includes.
+
+#include "imageeffect_rgb.h"
+#include "imageeffect_rgb.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_RGB::ImageEffect_RGB(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Color Balance"), "colorbalance", false)
+{
+ m_destinationPreviewData = 0L;
+ setHelp("colorbalancetool.anchor", "digikam");
+
+ m_previewWidget = new Digikam::ImageWidget("colorbalance Tool Dialog", plainPage(),
+ i18n("<p>Here you can see the image "
+ "color-balance adjustments preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 7, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQLabel *labelLeft = new TQLabel(i18n("Cyan"), gboxSettings);
+ labelLeft->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_rSlider = new TQSlider(-100, 100, 1, 0, TQt::Horizontal, gboxSettings, "m_rSlider");
+ m_rSlider->setTickmarks(TQSlider::Below);
+ m_rSlider->setTickInterval(20);
+ TQWhatsThis::add( m_rSlider, i18n("<p>Set here the cyan/red color adjustment of the image."));
+ TQLabel *labelRight = new TQLabel(i18n("Red"), gboxSettings);
+ labelRight->setAlignment ( TQt::AlignLeft | TQt::AlignVCenter );
+ m_rInput = new TQSpinBox(-100, 100, 1, gboxSettings, "m_rInput");
+
+ gridSettings->addMultiCellWidget(labelLeft, 3, 3, 0, 0);
+ gridSettings->addMultiCellWidget(m_rSlider, 3, 3, 1, 1);
+ gridSettings->addMultiCellWidget(labelRight, 3, 3, 2, 2);
+ gridSettings->addMultiCellWidget(m_rInput, 3, 3, 3, 3);
+
+ // -------------------------------------------------------------
+
+ labelLeft = new TQLabel(i18n("Magenta"), gboxSettings);
+ labelLeft->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_gSlider = new TQSlider(-100, 100, 1, 0, TQt::Horizontal, gboxSettings, "m_gSlider");
+ m_gSlider->setTickmarks(TQSlider::Below);
+ m_gSlider->setTickInterval(20);
+ TQWhatsThis::add( m_gSlider, i18n("<p>Set here the magenta/green color adjustment of the image."));
+ labelRight = new TQLabel(i18n("Green"), gboxSettings);
+ labelRight->setAlignment ( TQt::AlignLeft | TQt::AlignVCenter );
+ m_gInput = new TQSpinBox(-100, 100, 1, gboxSettings, "m_gInput");
+
+ gridSettings->addMultiCellWidget(labelLeft, 4, 4, 0, 0);
+ gridSettings->addMultiCellWidget(m_gSlider, 4, 4, 1, 1);
+ gridSettings->addMultiCellWidget(labelRight, 4, 4, 2, 2);
+ gridSettings->addMultiCellWidget(m_gInput, 4, 4, 3, 3);
+
+ // -------------------------------------------------------------
+
+ labelLeft = new TQLabel(i18n("Yellow"), gboxSettings);
+ labelLeft->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_bSlider = new TQSlider(-100, 100, 1, 0, TQt::Horizontal, gboxSettings, "m_bSlider");
+ m_bSlider->setTickmarks(TQSlider::Below);
+ m_bSlider->setTickInterval(20);
+ TQWhatsThis::add( m_bSlider, i18n("<p>Set here the yellow/blue color adjustment of the image."));
+ labelRight = new TQLabel(i18n("Blue"), gboxSettings);
+ labelRight->setAlignment ( TQt::AlignLeft | TQt::AlignVCenter );
+ m_bInput = new TQSpinBox(-100, 100, 1, gboxSettings, "m_bInput");
+
+ gridSettings->addMultiCellWidget(labelLeft, 5, 5, 0, 0);
+ gridSettings->addMultiCellWidget(m_bSlider, 5, 5, 1, 1);
+ gridSettings->addMultiCellWidget(labelRight, 5, 5, 2, 2);
+ gridSettings->addMultiCellWidget(m_bInput, 5, 5, 3, 3);
+
+ m_rInput->setValue(0);
+ m_gInput->setValue(0);
+ m_bInput->setValue(0);
+
+ gridSettings->setRowStretch(6, 10);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_rSlider, TQ_SIGNAL(valueChanged(int)),
+ m_rInput, TQ_SLOT(setValue(int)));
+ connect(m_rInput, TQ_SIGNAL(valueChanged (int)),
+ m_rSlider, TQ_SLOT(setValue(int)));
+ connect(m_rInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gSlider, TQ_SIGNAL(valueChanged(int)),
+ m_gInput, TQ_SLOT(setValue(int)));
+ connect(m_gInput, TQ_SIGNAL(valueChanged (int)),
+ m_gSlider, TQ_SLOT(setValue(int)));
+ connect(m_gInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_bSlider, TQ_SIGNAL(valueChanged(int)),
+ m_bInput, TQ_SLOT(setValue(int)));
+ connect(m_bInput, TQ_SIGNAL(valueChanged (int)),
+ m_bSlider, TQ_SLOT(setValue(int)));
+ connect(m_bInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+
+ enableButtonOK( false );
+}
+
+ImageEffect_RGB::~ImageEffect_RGB()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+ delete m_previewWidget;
+}
+
+void ImageEffect_RGB::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_RGB::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_RGB::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_RGB::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("colorbalance Tool Dialog");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+ int r = config->readNumEntry("RedAjustment", 0);
+ int g = config->readNumEntry("GreenAjustment", 0);
+ int b = config->readNumEntry("BlueAjustment", 0);
+ adjustSliders(r, g, b);
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ImageEffect_RGB::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("colorbalance Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("RedAjustment", m_rSlider->value());
+ config->writeEntry("GreenAjustment", m_gInput->value());
+ config->writeEntry("BlueAjustment", m_bInput->value());
+ config->sync();
+}
+
+void ImageEffect_RGB::resetValues()
+{
+ adjustSliders(0, 0, 0);
+}
+
+void ImageEffect_RGB::adjustSliders(int r, int g, int b)
+{
+ m_rSlider->blockSignals(true);
+ m_gSlider->blockSignals(true);
+ m_bSlider->blockSignals(true);
+ m_rInput->blockSignals(true);
+ m_gInput->blockSignals(true);
+ m_bInput->blockSignals(true);
+
+ m_rSlider->setValue(r);
+ m_gSlider->setValue(g);
+ m_bSlider->setValue(b);
+ m_rInput->setValue(r);
+ m_gInput->setValue(g);
+ m_bInput->setValue(b);
+
+ m_rSlider->blockSignals(false);
+ m_gSlider->blockSignals(false);
+ m_bSlider->blockSignals(false);
+ m_rInput->blockSignals(false);
+ m_gInput->blockSignals(false);
+ m_bInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_RGB::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ enableButtonOK(m_rInput->value() != 0 ||
+ m_gInput->value() != 0 ||
+ m_bInput->value() != 0);
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool alpha = iface->previewHasAlpha();
+ bool sixteenBit = iface->previewSixteenBit();
+
+ double r = ((double)m_rInput->value() + 100.0)/100.0;
+ double g = ((double)m_gInput->value() + 100.0)/100.0;
+ double b = ((double)m_bInput->value() + 100.0)/100.0;
+ double a = 1.0;
+
+ Digikam::DImg preview(w, h, sixteenBit, alpha, m_destinationPreviewData);
+ Digikam::ColorModifier cmod;
+ cmod.applyColorModifier(preview, r, g, b, a);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sixteenBit, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void ImageEffect_RGB::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ double r = ((double)m_rInput->value() + 100.0)/100.0;
+ double g = ((double)m_gInput->value() + 100.0)/100.0;
+ double b = ((double)m_bInput->value() + 100.0)/100.0;
+ double a = 1.0;
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool alpha = iface->originalHasAlpha();
+ bool sixteenBit = iface->originalSixteenBit();
+ Digikam::DImg original(w, h, sixteenBit, alpha, data);
+ delete [] data;
+
+ Digikam::ColorModifier cmod;
+ cmod.applyColorModifier(original, r, g, b, a);
+
+ iface->putOriginalImage(i18n("Color Balance"), original.bits());
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/imageeffect_rgb.h b/src/imageplugins/coreplugin/imageeffect_rgb.h
new file mode 100644
index 00000000..9f5db52c
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageeffect_rgb.h
@@ -0,0 +1,113 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-11
+ * Description : digiKam image editor Color Balance tool.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_RGB_H
+#define IMAGEEFFECT_RGB_H
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQComboBox;
+class TQHButtonGroup;
+
+class TQSpinBox;
+class TQSlider;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageEffect_RGB : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_RGB(TQWidget *parent);
+ ~ImageEffect_RGB();
+
+private:
+
+ void writeUserSettings();
+ void readUserSettings();
+ void resetValues();
+ void adjustSliders(int r, int g, int b);
+ void finalRendering();
+
+private slots:
+
+ void slotEffect();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQSpinBox *m_rInput;
+ TQSpinBox *m_gInput;
+ TQSpinBox *m_bInput;
+
+ TQSlider *m_rSlider;
+ TQSlider *m_gSlider;
+ TQSlider *m_bSlider;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_RGB_H */
diff --git a/src/imageplugins/coreplugin/imageplugin_core.cpp b/src/imageplugins/coreplugin/imageplugin_core.cpp
new file mode 100644
index 00000000..22c63b34
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageplugin_core.cpp
@@ -0,0 +1,295 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : digiKam image editor plugin core
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#include <config.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdemessagebox.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "imageiface.h"
+#include "rgbtool.h"
+#include "hsltool.h"
+#include "bcgtool.h"
+#include "bwsepiatool.h"
+#include "redeyetool.h"
+#include "blurtool.h"
+#include "sharpentool.h"
+#include "ratiocroptool.h"
+#include "autocorrectiontool.h"
+#include "iccprooftool.h"
+#include "imageplugin_core.h"
+#include "imageplugin_core.moc"
+
+using namespace DigikamImagesPluginCore;
+using namespace Digikam;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_core,
+ KGenericFactory<ImagePlugin_Core>("digikam"));
+
+ImagePlugin_Core::ImagePlugin_Core(TQObject *parent, const char*, const TQStringList&)
+ : ImagePlugin(parent, "ImagePlugin_Core")
+{
+ //-------------------------------
+ // Fix and Colors menu actions
+
+ m_blurAction = new TDEAction(i18n("Blur..."), "blurimage", 0,
+ this, TQ_SLOT(slotBlur()),
+ actionCollection(), "implugcore_blur");
+
+ m_sharpenAction = new TDEAction(i18n("Sharpen..."), "sharpenimage", 0,
+ this, TQ_SLOT(slotSharpen()),
+ actionCollection(), "implugcore_sharpen");
+
+ m_redeyeAction = new TDEAction(i18n("Red Eye..."), "redeyes", 0,
+ this, TQ_SLOT(slotRedEye()),
+ actionCollection(), "implugcore_redeye");
+ m_redeyeAction->setWhatsThis( i18n( "This filter can be used to correct red eyes in a photo. "
+ "Select a region including the eyes to use this option.") );
+
+ m_BCGAction = new TDEAction(i18n("Brightness/Contrast/Gamma..."), "contrast", 0,
+ this, TQ_SLOT(slotBCG()),
+ actionCollection(), "implugcore_bcg");
+
+ m_HSLAction = new TDEAction(i18n("Hue/Saturation/Lightness..."), "adjusthsl",
+ CTRL+Key_U, // NOTE: Photoshop 7 use CTRL+U.
+ this, TQ_SLOT(slotHSL()),
+ actionCollection(), "implugcore_hsl");
+
+ m_RGBAction = new TDEAction(i18n("Color Balance..."), "adjustrgb",
+ CTRL+Key_B, // NOTE: Photoshop 7 use CTRL+B.
+ this, TQ_SLOT(slotRGB()),
+ actionCollection(), "implugcore_rgb");
+
+ m_autoCorrectionAction = new TDEAction(i18n("Auto-Correction..."), "autocorrection",
+ CTRL+SHIFT+Key_B, // NOTE: Photoshop 7 use CTRL+SHIFT+B with 'Auto-Color' option.
+ this, TQ_SLOT(slotAutoCorrection()),
+ actionCollection(), "implugcore_autocorrection");
+
+ m_invertAction = new TDEAction(i18n("Invert"), "invertimage",
+ CTRL+Key_I, // NOTE: Photoshop 7 use CTRL+I.
+ this, TQ_SLOT(slotInvert()),
+ actionCollection(), "implugcore_invert");
+
+ m_convertTo8Bits = new TDEAction(i18n("8 bits"), "depth16to8", 0,
+ this, TQ_SLOT(slotConvertTo8Bits()),
+ actionCollection(), "implugcore_convertto8bits");
+
+ m_convertTo16Bits = new TDEAction(i18n("16 bits"), "depth8to16", 0,
+ this, TQ_SLOT(slotConvertTo16Bits()),
+ actionCollection(), "implugcore_convertto16bits");
+
+ m_colorManagementAction = new TDEAction(i18n("Color Management..."), "colormanagement", 0,
+ this, TQ_SLOT(slotColorManagement()),
+ actionCollection(), "implugcore_colormanagement");
+ //-------------------------------
+ // Filters menu actions.
+
+ m_BWAction = new TDEAction(i18n("Black && White..."), "bwtonal", 0,
+ this, TQ_SLOT(slotBW()),
+ actionCollection(), "implugcore_blackwhite");
+
+ //-------------------------------
+ // Transform menu actions.
+
+ m_aspectRatioCropAction = new TDEAction(i18n("Aspect Ratio Crop..."), "ratiocrop", 0,
+ this, TQ_SLOT(slotRatioCrop()),
+ actionCollection(), "implugcore_ratiocrop");
+
+ //-------------------------------
+ // Init. menu actions.
+
+ setXMLFile("digikamimageplugin_core_ui.rc");
+
+ DDebug() << "ImagePlugin_Core plugin loaded" << endl;
+}
+
+ImagePlugin_Core::~ImagePlugin_Core()
+{
+}
+
+void ImagePlugin_Core::setEnabledSelectionActions(bool)
+{
+}
+
+void ImagePlugin_Core::setEnabledActions(bool enable)
+{
+ m_redeyeAction->setEnabled(enable);
+ m_BCGAction->setEnabled(enable);
+ m_HSLAction->setEnabled(enable);
+ m_RGBAction->setEnabled(enable);
+ m_autoCorrectionAction->setEnabled(enable);
+ m_invertAction->setEnabled(enable);
+ m_BWAction->setEnabled(enable);
+ m_aspectRatioCropAction->setEnabled(enable);
+ m_sharpenAction->setEnabled(enable);
+ m_blurAction->setEnabled(enable);
+ m_colorManagementAction->setEnabled(enable);
+ m_convertTo8Bits->setEnabled(enable);
+ m_convertTo16Bits->setEnabled(enable);
+}
+
+void ImagePlugin_Core::slotBlur()
+{
+ BlurTool *tool = new BlurTool(this);
+ loadTool(tool);
+}
+
+void ImagePlugin_Core::slotSharpen()
+{
+ SharpenTool *tool = new SharpenTool(this);
+ loadTool(tool);
+}
+
+void ImagePlugin_Core::slotBCG()
+{
+ BCGTool *bcg = new BCGTool(this);
+ loadTool(bcg);
+}
+
+void ImagePlugin_Core::slotRGB()
+{
+ RGBTool *rgb = new RGBTool(this);
+ loadTool(rgb);
+}
+
+void ImagePlugin_Core::slotHSL()
+{
+ HSLTool *hsl = new HSLTool(this);
+ loadTool(hsl);
+}
+
+void ImagePlugin_Core::slotAutoCorrection()
+{
+ AutoCorrectionTool *autocorrection = new AutoCorrectionTool(this);
+ loadTool(autocorrection);
+}
+
+void ImagePlugin_Core::slotInvert()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ ImageIface iface(0, 0);
+
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+
+ DImgImageFilters filter;
+ filter.invertImage(data, w, h, sixteenBit);
+ iface.putOriginalImage(i18n("Invert"), data);
+ delete [] data;
+
+ kapp->restoreOverrideCursor();
+}
+
+void ImagePlugin_Core::slotBW()
+{
+ BWSepiaTool *bwsepia = new BWSepiaTool(this);
+ loadTool(bwsepia);
+}
+
+void ImagePlugin_Core::slotRedEye()
+{
+ ImageIface iface(0, 0);
+
+ if (!iface.selectedWidth() || !iface.selectedHeight())
+ {
+ RedEyePassivePopup* popup = new RedEyePassivePopup(kapp->activeWindow());
+ popup->setView(i18n("Red-Eye Correction Tool"),
+ i18n("You need to select a region including the eyes to use "
+ "the red-eye correction tool"));
+ popup->setAutoDelete(true);
+ popup->setTimeout(2500);
+ popup->show();
+ return;
+ }
+
+ RedEyeTool *redeye = new RedEyeTool(this);
+ loadTool(redeye);
+}
+
+void ImagePlugin_Core::slotColorManagement()
+{
+ ICCProofTool *tool = new ICCProofTool(this);
+ loadTool(tool);
+}
+
+void ImagePlugin_Core::slotRatioCrop()
+{
+ RatioCropTool *ratiocrop = new RatioCropTool(this);
+ loadTool(ratiocrop);
+}
+
+void ImagePlugin_Core::slotConvertTo8Bits()
+{
+ ImageIface iface(0, 0);
+
+ if (!iface.originalSixteenBit())
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("This image is already using a depth of 8 bits / color / pixel."));
+ return;
+ }
+ else
+ {
+ if (KMessageBox::warningContinueCancel(
+ kapp->activeWindow(),
+ i18n("Performing this operation will reduce image color quality. "
+ "Do you want to continue?"), TQString(),
+ KStdGuiItem::cont(),
+ TQString("ImagePluginCore16To8Bits")) == KMessageBox::Cancel)
+ return;
+ }
+
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ iface.convertOriginalColorDepth(32);
+ kapp->restoreOverrideCursor();
+}
+
+void ImagePlugin_Core::slotConvertTo16Bits()
+{
+ ImageIface iface(0, 0);
+
+ if (iface.originalSixteenBit())
+ {
+ KMessageBox::error(kapp->activeWindow(), i18n("This image is already using a depth of 16 bits / color / pixel."));
+ return;
+ }
+
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ iface.convertOriginalColorDepth(64);
+ kapp->restoreOverrideCursor();
+}
diff --git a/src/imageplugins/coreplugin/imageplugin_core.h b/src/imageplugins/coreplugin/imageplugin_core.h
new file mode 100644
index 00000000..41168d26
--- /dev/null
+++ b/src/imageplugins/coreplugin/imageplugin_core.h
@@ -0,0 +1,85 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : digiKam image editor plugin core
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_CORE_H
+#define IMAGEPLUGIN_CORE_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Core : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Core(TQObject *parent, const char* name, const TQStringList &args);
+ ~ImagePlugin_Core();
+
+ void setEnabledSelectionActions(bool enable);
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotBlur();
+ void slotSharpen();
+ void slotBCG();
+ void slotRGB();
+ void slotHSL();
+ void slotAutoCorrection();
+ void slotInvert();
+
+ void slotBW();
+
+ void slotRedEye();
+ void slotRatioCrop();
+
+ void slotConvertTo8Bits();
+ void slotConvertTo16Bits();
+
+ void slotColorManagement();
+
+private:
+
+ TDEAction *m_redeyeAction;
+ TDEAction *m_BCGAction;
+ TDEAction *m_HSLAction;
+ TDEAction *m_RGBAction;
+ TDEAction *m_autoCorrectionAction;
+ TDEAction *m_invertAction;
+ TDEAction *m_BWAction;
+ TDEAction *m_aspectRatioCropAction;
+ TDEAction *m_sharpenAction;
+ TDEAction *m_blurAction;
+ TDEAction *m_colorManagementAction;
+ TDEAction *m_convertTo8Bits;
+ TDEAction *m_convertTo16Bits;
+};
+
+#endif /* IMAGEPLUGIN_CORE_H */
diff --git a/src/imageplugins/coreplugin/ratiocrop/Makefile.am b/src/imageplugins/coreplugin/ratiocrop/Makefile.am
new file mode 100644
index 00000000..47ec5b58
--- /dev/null
+++ b/src/imageplugins/coreplugin/ratiocrop/Makefile.am
@@ -0,0 +1,26 @@
+noinst_LTLIBRARIES = libratiocrop.la
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+libratiocrop_la_SOURCES = ratiocroptool.cpp imageselectionwidget.cpp
+
+libratiocrop_la_LDFLAGS = $(all_libraries)
+
+noinst_HEADERS = ratiocroptool.h imageselectionwidget.h
+
diff --git a/src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.cpp b/src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.cpp
new file mode 100644
index 00000000..64bc7a8e
--- /dev/null
+++ b/src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.cpp
@@ -0,0 +1,799 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : digiKam image editor Ratio Crop tool
+ *
+ * Copyright (C) 2007 by Jaromir Malenko <malenko at email dot cz>
+ * Copyright (C) 2008 by Roberto Castagnola <roberto dot castagnola at gmail dot com>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqrect.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcombobox.h>
+#include <tqspinbox.h>
+#include <tqimage.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <knuminput.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kstandarddirs.h>
+#include <kcolorbutton.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "imageselectionwidget.h"
+
+// Local includes.
+
+#include "imageeffect_ratiocrop.h"
+#include "imageeffect_ratiocrop.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_RatioCrop::ImageEffect_RatioCrop(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Aspect Ratio Crop & Composition Guide"),
+ "aspectratiocrop", false)
+{
+ setHelp("ratiocroptool.anchor", "digikam");
+ setButtonWhatsThis ( User1, i18n("<p>Set selection area to the maximum size according "
+ "to the current ratio.") );
+ setButtonText(User1, i18n("&Max. Aspect"));
+ showButton(User1, true);
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(plainPage());
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* l = new TQVBoxLayout(frame, 5, 0);
+ m_imageSelectionWidget = new ImageSelectionWidget(480, 320, frame);
+ l->addWidget(m_imageSelectionWidget);
+ TQWhatsThis::add( m_imageSelectionWidget, i18n("<p>Here you can see the aspect ratio selection preview "
+ "used for cropping. You can use the mouse to move and "
+ "resize the crop area. "
+ "Press and hold the CTRL key to move the opposite corner too. "
+ "Press and hold the SHIFT key to move the closest corner to the "
+ "mouse pointer."));
+ setPreviewAreaWidget(frame);
+
+ m_originalIsLandscape = m_imageSelectionWidget->getOriginalImageWidth() >
+ m_imageSelectionWidget->getOriginalImageHeight();
+
+ // -------------------------------------------------------------
+
+ TQWidget *gbox2 = new TQWidget(plainPage());
+ TQGridLayout *gridBox2 = new TQGridLayout( gbox2, 2, 0);
+
+ TQFrame *cropSelection = new TQFrame( gbox2 );
+ cropSelection->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQGridLayout* grid = new TQGridLayout( cropSelection, 6, 4, spacingHint());
+
+ TQLabel *label = new TQLabel(i18n("Aspect ratio:"), cropSelection);
+ m_ratioCB = new TQComboBox( false, cropSelection );
+ setRatioCBText(ImageSelectionWidget::Landscape);
+ TQWhatsThis::add( m_ratioCB, i18n("<p>Select your constrained aspect ratio for cropping. "
+ "Aspect Ratio Crop tool uses a relative ratio. That means it "
+ "is the same if you use centimeters or inches and it doesn't "
+ "specify the physical size.<p>"
+ "You can see below a correspondence list of traditional photographic "
+ "paper sizes and aspect ratio crop:<p>"
+ "<b>2:3</b>: 10x15cm, 20x30cm, 30x45cm, 4x6\", 8x12\", "
+ "12x18\", 16x24\", 20x30\"<p>"
+ "<b>3:4</b>: 6x8cm, 15x20cm, 18x24cm, 30x40cm, 3.75x5\", 4.5x6\", "
+ "6x8\", 7.5x10\", 9x12\"<p>"
+ "<b>4:5</b>: 20x25cm, 40x50cm, 8x10\", 16x20\"<p>"
+ "<b>5:7</b>: 15x21cm, 30x42cm, 5x7\"<p>"
+ "<b>7:10</b>: 21x30cm, 42x60cm, 3.5x5\"<p>"
+ "The <b>Golden Ratio</b> is 1:1.618. A composition following this rule "
+ "is considered visually harmonious but can be unadapted to print on "
+ "standard photographic paper."));
+
+ m_preciseCrop = new TQCheckBox(i18n("Exact"), cropSelection);
+ TQWhatsThis::add( m_preciseCrop, i18n("<p>Enable this option to force exact aspect ratio crop."));
+
+ m_orientLabel = new TQLabel(i18n("Orientation:"), cropSelection);
+ m_orientCB = new TQComboBox( false, cropSelection );
+ m_orientCB->insertItem( i18n("Landscape") );
+ m_orientCB->insertItem( i18n("Portrait") );
+ TQWhatsThis::add( m_orientCB, i18n("<p>Select constrained aspect ratio orientation."));
+
+ m_autoOrientation = new TQCheckBox(i18n("Auto"), cropSelection);
+ TQWhatsThis::add( m_autoOrientation, i18n("<p>Enable this option to automatically set the orientation."));
+
+ grid->addMultiCellWidget(label, 0, 0, 0, 0);
+ grid->addMultiCellWidget(m_ratioCB, 0, 0, 1, 3);
+ grid->addMultiCellWidget(m_preciseCrop, 0, 0, 4, 4);
+ grid->addMultiCellWidget(m_orientLabel, 2, 2, 0, 0);
+ grid->addMultiCellWidget(m_orientCB, 2, 2, 1, 3);
+ grid->addMultiCellWidget(m_autoOrientation, 2, 2, 4, 4);
+
+ // -------------------------------------------------------------
+
+ m_customLabel1 = new TQLabel(i18n("Custom ratio:"), cropSelection);
+ m_customLabel1->setAlignment(AlignLeft|AlignVCenter);
+ m_customRatioNInput = new KIntSpinBox(1, 10000, 1, 1, 10, cropSelection);
+ TQWhatsThis::add( m_customRatioNInput, i18n("<p>Set here the desired custom aspect numerator value."));
+ m_customLabel2 = new TQLabel(" : ", cropSelection);
+ m_customLabel2->setAlignment(AlignCenter|AlignVCenter);
+ m_customRatioDInput = new KIntSpinBox(1, 10000, 1, 1, 10, cropSelection);
+ TQWhatsThis::add( m_customRatioDInput, i18n("<p>Set here the desired custom aspect denominator value."));
+
+ grid->addMultiCellWidget(m_customLabel1, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_customRatioNInput, 1, 1, 1, 1);
+ grid->addMultiCellWidget(m_customLabel2, 1, 1, 2, 2);
+ grid->addMultiCellWidget(m_customRatioDInput, 1, 1, 3, 3);
+
+ // -------------------------------------------------------------
+
+ m_xInput = new KIntNumInput(cropSelection);
+ TQWhatsThis::add( m_xInput, i18n("<p>Set here the top left selection corner position for cropping."));
+ m_xInput->setLabel(i18n("X:"), AlignLeft|AlignVCenter);
+ m_xInput->setRange(0, m_imageSelectionWidget->getOriginalImageWidth(), 1, true);
+
+ m_widthInput = new KIntNumInput(cropSelection);
+ m_widthInput->setLabel(i18n("Width:"), AlignLeft|AlignVCenter);
+ TQWhatsThis::add( m_widthInput, i18n("<p>Set here the width selection for cropping."));
+ m_widthInput->setRange(m_imageSelectionWidget->getMinWidthRange(),
+ m_imageSelectionWidget->getMaxWidthRange(),
+ m_imageSelectionWidget->getWidthStep(), true);
+
+ m_centerWidth = new TQPushButton(cropSelection);
+ TDEGlobal::dirs()->addResourceType("centerwidth", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("centerwidth", "centerwidth.png");
+ m_centerWidth->setPixmap( TQPixmap( directory + "centerwidth.png" ) );
+ TQWhatsThis::add( m_centerWidth, i18n("<p>Set width position to center."));
+
+ grid->addMultiCellWidget(m_xInput, 3, 3, 0, 3);
+ grid->addMultiCellWidget(m_widthInput, 4, 4, 0, 3);
+ grid->addMultiCellWidget(m_centerWidth, 3, 3, 4, 4);
+
+ // -------------------------------------------------------------
+
+ m_yInput = new KIntNumInput(cropSelection);
+ m_yInput->setLabel(i18n("Y:"), AlignLeft|AlignVCenter);
+ TQWhatsThis::add( m_yInput, i18n("<p>Set here the top left selection corner position for cropping."));
+ m_yInput->setRange(0, m_imageSelectionWidget->getOriginalImageHeight(), 1, true);
+
+ m_heightInput = new KIntNumInput(cropSelection);
+ m_heightInput->setLabel(i18n("Height:"), AlignLeft|AlignVCenter);
+ TQWhatsThis::add( m_heightInput, i18n("<p>Set here the height selection for cropping."));
+ m_heightInput->setRange(m_imageSelectionWidget->getMinHeightRange(),
+ m_imageSelectionWidget->getMaxHeightRange(),
+ m_imageSelectionWidget->getHeightStep(), true);
+
+ m_centerHeight = new TQPushButton(cropSelection);
+ TDEGlobal::dirs()->addResourceType("centerheight", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("centerheight", "centerheight.png");
+ m_centerHeight->setPixmap( TQPixmap( directory + "centerheight.png" ) );
+ TQWhatsThis::add( m_centerHeight, i18n("<p>Set height position to center."));
+
+ grid->addMultiCellWidget(m_yInput, 5, 5, 0, 3);
+ grid->addMultiCellWidget(m_heightInput, 6, 6, 0, 3);
+ grid->addMultiCellWidget(m_centerHeight, 5, 5, 4, 4);
+
+ gridBox2->addMultiCellWidget(cropSelection, 0, 0, 0, 0);
+
+ // -------------------------------------------------------------
+
+ TQFrame* compositionGuide = new TQFrame( gbox2 );
+ TQGridLayout* grid2 = new TQGridLayout( compositionGuide, 7, 2, spacingHint());
+ compositionGuide->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+
+ TQLabel *labelGuideLines = new TQLabel(i18n("Composition guide:"), compositionGuide);
+ m_guideLinesCB = new TQComboBox( false, compositionGuide );
+ m_guideLinesCB->insertItem( i18n("Rules of Thirds") );
+ m_guideLinesCB->insertItem( i18n("Harmonious Triangles") );
+ m_guideLinesCB->insertItem( i18n("Golden Mean") );
+ m_guideLinesCB->insertItem( i18n("None") );
+ m_guideLinesCB->setCurrentText( i18n("None") );
+ TQWhatsThis::add( m_guideLinesCB, i18n("<p>With this option, you can display guide lines "
+ "which help you to compose your photograph."));
+
+ m_goldenSectionBox = new TQCheckBox(i18n("Golden sections"), compositionGuide);
+ TQWhatsThis::add( m_goldenSectionBox, i18n("<p>Enable this option to show golden sections."));
+
+ m_goldenSpiralSectionBox = new TQCheckBox(i18n("Golden spiral sections"), compositionGuide);
+ TQWhatsThis::add( m_goldenSpiralSectionBox, i18n("<p>Enable this option to show golden spiral sections."));
+
+ m_goldenSpiralBox = new TQCheckBox(i18n("Golden spiral"), compositionGuide);
+ TQWhatsThis::add( m_goldenSpiralBox, i18n("<p>Enable this option to show golden spiral guide."));
+
+ m_goldenTriangleBox = new TQCheckBox(i18n("Golden triangles"), compositionGuide);
+ TQWhatsThis::add( m_goldenTriangleBox, i18n("<p>Enable this option to show golden triangles."));
+
+ m_flipHorBox = new TQCheckBox(i18n("Flip horizontally"), compositionGuide);
+ TQWhatsThis::add( m_flipHorBox, i18n("<p>Enable this option to flip horizontally guidelines."));
+
+ m_flipVerBox = new TQCheckBox(i18n("Flip vertically"), compositionGuide);
+ TQWhatsThis::add( m_flipVerBox, i18n("<p>Enable this option to flip vertically guidelines."));
+
+ m_colorGuideLabel = new TQLabel(i18n("Color and width:"), compositionGuide);
+ m_guideColorBt = new KColorButton( TQColor( 250, 250, 255 ), compositionGuide );
+ m_guideSize = new TQSpinBox( 1, 5, 1, compositionGuide);
+ TQWhatsThis::add( m_guideColorBt, i18n("<p>Set here the color used to draw composition guides."));
+ TQWhatsThis::add( m_guideSize, i18n("<p>Set here the width in pixels used to draw composition guides."));
+
+ grid2->addMultiCellWidget(labelGuideLines, 0, 0, 0, 0);
+ grid2->addMultiCellWidget(m_guideLinesCB, 0, 0, 1, 2);
+ grid2->addMultiCellWidget(m_goldenSectionBox, 1, 1, 0, 2);
+ grid2->addMultiCellWidget(m_goldenSpiralSectionBox, 2, 2, 0, 2);
+ grid2->addMultiCellWidget(m_goldenSpiralBox, 3, 3, 0, 2);
+ grid2->addMultiCellWidget(m_goldenTriangleBox, 4, 4, 0, 2);
+ grid2->addMultiCellWidget(m_flipHorBox, 5, 5, 0, 2);
+ grid2->addMultiCellWidget(m_flipVerBox, 6, 6, 0, 2);
+ grid2->addMultiCellWidget(m_colorGuideLabel, 7, 7, 0, 0);
+ grid2->addMultiCellWidget(m_guideColorBt, 7, 7, 1, 1);
+ grid2->addMultiCellWidget(m_guideSize, 7, 7, 2, 2);
+
+ gridBox2->addMultiCellWidget(compositionGuide, 1, 1, 0, 0);
+ gridBox2->setRowStretch(2, 10);
+
+ setUserAreaWidget(gbox2);
+
+ // -------------------------------------------------------------
+
+ connect(m_ratioCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotRatioChanged(int)));
+
+ connect(m_preciseCrop, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotPreciseCropChanged(bool)));
+
+ connect(m_orientCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotOrientChanged(int)));
+
+ connect(m_autoOrientation, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotAutoOrientChanged(bool)));
+
+ connect(m_xInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotXChanged(int)));
+
+ connect(m_yInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotYChanged(int)));
+
+ connect(m_customRatioNInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotCustomNRatioChanged(int)));
+
+ connect(m_customRatioDInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotCustomDRatioChanged(int)));
+
+ connect(m_guideLinesCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotGuideTypeChanged(int)));
+
+ connect(m_goldenSectionBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_goldenSpiralSectionBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_goldenSpiralBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_goldenTriangleBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_flipHorBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_flipVerBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_guideColorBt, TQ_SIGNAL(changed(const TQColor &)),
+ m_imageSelectionWidget, TQ_SLOT(slotChangeGuideColor(const TQColor &)));
+
+ connect(m_guideSize, TQ_SIGNAL(valueChanged(int)),
+ m_imageSelectionWidget, TQ_SLOT(slotChangeGuideSize(int)));
+
+ connect(m_widthInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotWidthChanged(int)));
+
+ connect(m_heightInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotHeightChanged(int)));
+
+ connect(m_imageSelectionWidget, TQ_SIGNAL(signalSelectionChanged(TQRect)),
+ this, TQ_SLOT(slotSelectionChanged(TQRect)));
+
+ connect(m_imageSelectionWidget, TQ_SIGNAL(signalSelectionMoved(TQRect)),
+ this, TQ_SLOT(slotSelectionChanged(TQRect)));
+
+ connect(m_imageSelectionWidget, TQ_SIGNAL(signalSelectionOrientationChanged(int)),
+ this, TQ_SLOT(slotSelectionOrientationChanged(int)));
+
+ connect(m_centerWidth, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotCenterWidth()));
+
+ connect(m_centerHeight, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotCenterHeight()));
+
+ // -------------------------------------------------------------
+
+ // Sets current region selection
+ slotSelectionChanged(m_imageSelectionWidget->getRegionSelection());
+
+ readSettings();
+}
+
+ImageEffect_RatioCrop::~ImageEffect_RatioCrop()
+{
+}
+
+void ImageEffect_RatioCrop::readSettings()
+{
+ TQColor defaultGuideColor(250, 250, 255);
+ TDEConfig *config = kapp->config();
+ config->setGroup("aspectratiocrop Tool Dialog");
+
+ // No guide lines per default.
+ m_guideLinesCB->setCurrentItem( config->readNumEntry("Guide Lines Type",
+ ImageSelectionWidget::GuideNone) );
+ m_goldenSectionBox->setChecked( config->readBoolEntry("Golden Section", true) );
+ m_goldenSpiralSectionBox->setChecked( config->readBoolEntry("Golden Spiral Section", false) );
+ m_goldenSpiralBox->setChecked( config->readBoolEntry("Golden Spiral", false) );
+ m_goldenTriangleBox->setChecked( config->readBoolEntry("Golden Triangle", false) );
+ m_flipHorBox->setChecked( config->readBoolEntry("Golden Flip Horizontal", false) );
+ m_flipVerBox->setChecked( config->readBoolEntry("Golden Flip Vertical", false) );
+ m_guideColorBt->setColor(config->readColorEntry("Guide Color", &defaultGuideColor));
+ m_guideSize->setValue(config->readNumEntry("Guide Width", 1));
+ m_imageSelectionWidget->slotGuideLines(m_guideLinesCB->currentItem());
+ m_imageSelectionWidget->slotChangeGuideColor(m_guideColorBt->color());
+
+ m_preciseCrop->setChecked( config->readBoolEntry("Precise Aspect Ratio Crop", false) );
+ m_imageSelectionWidget->setPreciseCrop( m_preciseCrop->isChecked() );
+
+ if (m_originalIsLandscape)
+ {
+ m_orientCB->setCurrentItem( config->readNumEntry("Hor.Oriented Aspect Ratio Orientation",
+ ImageSelectionWidget::Landscape) );
+
+ m_imageSelectionWidget->setSelectionOrientation(m_orientCB->currentItem());
+
+ m_customRatioNInput->setValue( config->readNumEntry("Hor.Oriented Custom Aspect Ratio Num", 1) );
+ m_customRatioDInput->setValue( config->readNumEntry("Hor.Oriented Custom Aspect Ratio Den", 1) );
+ m_ratioCB->setCurrentItem( config->readNumEntry("Hor.Oriented Aspect Ratio",
+ ImageSelectionWidget::RATIO03X04) );
+
+ applyRatioChanges(m_ratioCB->currentItem());
+
+ // Empty selection so it can be moved w/out size constraint
+ m_widthInput->setValue( 0 );
+ m_heightInput->setValue( 0 );
+
+ m_xInput->setValue( config->readNumEntry("Hor.Oriented Custom Aspect Ratio Xpos", 50) );
+ m_yInput->setValue( config->readNumEntry("Hor.Oriented Custom Aspect Ratio Ypos", 50) );
+
+ m_widthInput->setValue( config->readNumEntry("Hor.Oriented Custom Aspect Ratio Width", 800) );
+ m_heightInput->setValue( config->readNumEntry("Hor.Oriented Custom Aspect Ratio Height", 600) );
+ }
+ else
+ {
+ m_orientCB->setCurrentItem( config->readNumEntry("Ver.Oriented Aspect Ratio Orientation",
+ ImageSelectionWidget::Portrait) );
+
+ m_imageSelectionWidget->setSelectionOrientation(m_orientCB->currentItem());
+
+ m_customRatioNInput->setValue( config->readNumEntry("Ver.Oriented Custom Aspect Ratio Num", 1) );
+ m_customRatioDInput->setValue( config->readNumEntry("Ver.Oriented Custom Aspect Ratio Den", 1) );
+ m_ratioCB->setCurrentItem( config->readNumEntry("Ver.Oriented Aspect Ratio",
+ ImageSelectionWidget::RATIO03X04) );
+
+ applyRatioChanges(m_ratioCB->currentItem());
+
+ // Empty selection so it can be moved w/out size constraint
+ m_widthInput->setValue( 0 );
+ m_heightInput->setValue( 0 );
+
+ m_xInput->setValue( config->readNumEntry("Ver.Oriented Custom Aspect Ratio Xpos", 50) );
+ m_yInput->setValue( config->readNumEntry("Ver.Oriented Custom Aspect Ratio Ypos", 50) );
+
+ m_widthInput->setValue( config->readNumEntry("Ver.Oriented Custom Aspect Ratio Width", 800) );
+ m_heightInput->setValue( config->readNumEntry("Ver.Oriented Custom Aspect Ratio Height", 600) );
+ }
+
+ m_autoOrientation->setChecked( config->readBoolEntry("Auto Orientation", false) );
+ slotAutoOrientChanged( m_autoOrientation->isChecked() );
+}
+
+void ImageEffect_RatioCrop::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("aspectratiocrop Tool Dialog");
+
+ if (m_originalIsLandscape)
+ {
+ config->writeEntry( "Hor.Oriented Aspect Ratio", m_ratioCB->currentItem() );
+ config->writeEntry( "Hor.Oriented Aspect Ratio Orientation", m_orientCB->currentItem() );
+ config->writeEntry( "Hor.Oriented Custom Aspect Ratio Num", m_customRatioNInput->value() );
+ config->writeEntry( "Hor.Oriented Custom Aspect Ratio Den", m_customRatioDInput->value() );
+
+ config->writeEntry( "Hor.Oriented Custom Aspect Ratio Xpos", m_xInput->value() );
+ config->writeEntry( "Hor.Oriented Custom Aspect Ratio Ypos", m_yInput->value() );
+ config->writeEntry( "Hor.Oriented Custom Aspect Ratio Width", m_widthInput->value() );
+ config->writeEntry( "Hor.Oriented Custom Aspect Ratio Height", m_heightInput->value() );
+ }
+ else
+ {
+ config->writeEntry( "Ver.Oriented Aspect Ratio", m_ratioCB->currentItem() );
+ config->writeEntry( "Ver.Oriented Aspect Ratio Orientation", m_orientCB->currentItem() );
+ config->writeEntry( "Ver.Oriented Custom Aspect Ratio Num", m_customRatioNInput->value() );
+ config->writeEntry( "Ver.Oriented Custom Aspect Ratio Den", m_customRatioDInput->value() );
+
+ config->writeEntry( "Ver.Oriented Custom Aspect Ratio Xpos", m_xInput->value() );
+ config->writeEntry( "Ver.Oriented Custom Aspect Ratio Ypos", m_yInput->value() );
+ config->writeEntry( "Ver.Oriented Custom Aspect Ratio Width", m_widthInput->value() );
+ config->writeEntry( "Ver.Oriented Custom Aspect Ratio Height", m_heightInput->value() );
+ }
+
+ config->writeEntry( "Precise Aspect Ratio Crop", m_preciseCrop->isChecked() );
+ config->writeEntry( "Auto Orientation", m_autoOrientation->isChecked() );
+ config->writeEntry( "Guide Lines Type", m_guideLinesCB->currentItem() );
+ config->writeEntry( "Golden Section", m_goldenSectionBox->isChecked() );
+ config->writeEntry( "Golden Spiral Section", m_goldenSpiralSectionBox->isChecked() );
+ config->writeEntry( "Golden Spiral", m_goldenSpiralBox->isChecked() );
+ config->writeEntry( "Golden Triangle", m_goldenTriangleBox->isChecked() );
+ config->writeEntry( "Golden Flip Horizontal", m_flipHorBox->isChecked() );
+ config->writeEntry( "Golden Flip Vertical", m_flipVerBox->isChecked() );
+ config->writeEntry( "Guide Color", m_guideColorBt->color() );
+ config->writeEntry( "Guide Width", m_guideSize->value() );
+ config->sync();
+}
+
+void ImageEffect_RatioCrop::slotDefault()
+{
+ m_imageSelectionWidget->resetSelection();
+}
+
+void ImageEffect_RatioCrop::slotUser1()
+{
+ m_imageSelectionWidget->maxAspectSelection();
+}
+
+void ImageEffect_RatioCrop::slotCenterWidth()
+{
+ m_imageSelectionWidget->setCenterSelection(ImageSelectionWidget::CenterWidth);
+}
+
+void ImageEffect_RatioCrop::slotCenterHeight()
+{
+ m_imageSelectionWidget->setCenterSelection(ImageSelectionWidget::CenterHeight);
+}
+
+void ImageEffect_RatioCrop::slotSelectionChanged(TQRect rect)
+{
+ m_xInput->blockSignals(true);
+ m_yInput->blockSignals(true);
+ m_widthInput->blockSignals(true);
+ m_heightInput->blockSignals(true);
+
+ m_xInput->setRange(0, m_imageSelectionWidget->getOriginalImageWidth() - rect.width(), 1, true);
+ m_yInput->setRange(0, m_imageSelectionWidget->getOriginalImageHeight() - rect.height(), 1, true);
+ m_widthInput->setRange(m_imageSelectionWidget->getMinWidthRange(),
+ m_imageSelectionWidget->getMaxWidthRange(),
+ m_imageSelectionWidget->getWidthStep(), true);
+ m_heightInput->setRange(m_imageSelectionWidget->getMinHeightRange(),
+ m_imageSelectionWidget->getMaxHeightRange(),
+ m_imageSelectionWidget->getHeightStep(), true);
+
+ m_xInput->setValue(rect.x());
+ m_yInput->setValue(rect.y());
+ m_widthInput->setValue(rect.width());
+ m_heightInput->setValue(rect.height());
+
+ enableButtonOK( rect.isValid() );
+ m_preciseCrop->setEnabled(m_imageSelectionWidget->preciseCropAvailable());
+
+ m_xInput->blockSignals(false);
+ m_yInput->blockSignals(false);
+ m_widthInput->blockSignals(false);
+ m_heightInput->blockSignals(false);
+}
+
+void ImageEffect_RatioCrop::setRatioCBText(int orientation)
+{
+ int item = m_ratioCB->currentItem();
+
+ m_ratioCB->blockSignals(true);
+ m_ratioCB->clear();
+ m_ratioCB->insertItem( i18n("Custom") );
+ m_ratioCB->insertItem( "1:1" );
+ if ( orientation == ImageSelectionWidget::Landscape )
+ {
+ m_ratioCB->insertItem( "3:2" );
+ m_ratioCB->insertItem( "4:3" );
+ m_ratioCB->insertItem( "5:4" );
+ m_ratioCB->insertItem( "7:5" );
+ m_ratioCB->insertItem( "10:7" );
+ }
+ else
+ {
+ m_ratioCB->insertItem( "2:3" );
+ m_ratioCB->insertItem( "3:4" );
+ m_ratioCB->insertItem( "4:5" );
+ m_ratioCB->insertItem( "5:7" );
+ m_ratioCB->insertItem( "7:10" );
+ }
+ m_ratioCB->insertItem( i18n("Golden Ratio") );
+ m_ratioCB->insertItem( i18n("None") );
+ m_ratioCB->setCurrentItem( item );
+ m_ratioCB->blockSignals(false);
+}
+
+void ImageEffect_RatioCrop::slotSelectionOrientationChanged(int newOrientation)
+{
+ // Change text for Aspect ratio ComboBox
+
+ setRatioCBText(newOrientation);
+
+ // Change Orientation ComboBox
+
+ m_orientCB->setCurrentItem(newOrientation);
+
+ // Reverse custom values
+
+ if ( ( m_customRatioNInput->value() < m_customRatioDInput->value() &&
+ newOrientation == ImageSelectionWidget::Landscape ) ||
+ ( m_customRatioNInput->value() > m_customRatioDInput->value() &&
+ newOrientation == ImageSelectionWidget::Portrait ) )
+ {
+ m_customRatioNInput->blockSignals(true);
+ m_customRatioDInput->blockSignals(true);
+
+ int tmp = m_customRatioNInput->value();
+ m_customRatioNInput->setValue( m_customRatioDInput->value() );
+ m_customRatioDInput->setValue( tmp );
+
+ m_customRatioNInput->blockSignals(false);
+ m_customRatioDInput->blockSignals(false);
+ }
+}
+
+void ImageEffect_RatioCrop::slotXChanged(int x)
+{
+ m_imageSelectionWidget->setSelectionX(x);
+}
+
+void ImageEffect_RatioCrop::slotYChanged(int y)
+{
+ m_imageSelectionWidget->setSelectionY(y);
+}
+
+void ImageEffect_RatioCrop::slotWidthChanged(int w)
+{
+ m_imageSelectionWidget->setSelectionWidth(w);
+}
+
+void ImageEffect_RatioCrop::slotHeightChanged(int h)
+{
+ m_imageSelectionWidget->setSelectionHeight(h);
+}
+
+void ImageEffect_RatioCrop::slotPreciseCropChanged(bool a)
+{
+ m_imageSelectionWidget->setPreciseCrop(a);
+}
+
+void ImageEffect_RatioCrop::slotOrientChanged(int o)
+{
+ m_imageSelectionWidget->setSelectionOrientation(o);
+
+ // Reset selection area.
+ slotDefault();
+}
+
+void ImageEffect_RatioCrop::slotAutoOrientChanged(bool a)
+{
+ m_orientCB->setEnabled(!a /*|| m_ratioCB->currentItem() == ImageSelectionWidget::RATIONONE*/);
+ m_imageSelectionWidget->setAutoOrientation(a);
+}
+
+void ImageEffect_RatioCrop::slotRatioChanged(int a)
+{
+ applyRatioChanges(a);
+
+ // Reset selection area.
+ slotDefault();
+}
+
+void ImageEffect_RatioCrop::applyRatioChanges(int a)
+{
+ m_imageSelectionWidget->setSelectionAspectRatioType(a);
+
+ if ( a == ImageSelectionWidget::RATIOCUSTOM )
+ {
+ m_customLabel1->setEnabled(true);
+ m_customLabel2->setEnabled(true);
+ m_customRatioNInput->setEnabled(true);
+ m_customRatioDInput->setEnabled(true);
+ m_orientLabel->setEnabled(true);
+ m_orientCB->setEnabled(! m_autoOrientation->isChecked());
+ m_autoOrientation->setEnabled(true);
+ slotCustomRatioChanged();
+ }
+ else if ( a == ImageSelectionWidget::RATIONONE )
+ {
+ m_orientLabel->setEnabled(false);
+ m_orientCB->setEnabled(false);
+ m_autoOrientation->setEnabled(false);
+ m_customLabel1->setEnabled(false);
+ m_customLabel2->setEnabled(false);
+ m_customRatioNInput->setEnabled(false);
+ m_customRatioDInput->setEnabled(false);
+ }
+ else // Pre-config ratio selected.
+ {
+ m_orientLabel->setEnabled(true);
+ m_orientCB->setEnabled(! m_autoOrientation->isChecked());
+ m_autoOrientation->setEnabled(true);
+ m_customLabel1->setEnabled(false);
+ m_customLabel2->setEnabled(false);
+ m_customRatioNInput->setEnabled(false);
+ m_customRatioDInput->setEnabled(false);
+ }
+}
+
+void ImageEffect_RatioCrop::slotGuideTypeChanged(int t)
+{
+ if ( t == ImageSelectionWidget::GuideNone )
+ {
+ m_goldenSectionBox->setEnabled(false);
+ m_goldenSpiralSectionBox->setEnabled(false);
+ m_goldenSpiralBox->setEnabled(false);
+ m_goldenTriangleBox->setEnabled(false);
+ m_flipHorBox->setEnabled(false);
+ m_flipVerBox->setEnabled(false);
+ m_colorGuideLabel->setEnabled(false);
+ m_guideColorBt->setEnabled(false);
+ m_guideSize->setEnabled(false);
+ }
+ else if ( t == ImageSelectionWidget::RulesOfThirds )
+ {
+ m_goldenSectionBox->setEnabled(false);
+ m_goldenSpiralSectionBox->setEnabled(false);
+ m_goldenSpiralBox->setEnabled(false);
+ m_goldenTriangleBox->setEnabled(false);
+ m_flipHorBox->setEnabled(false);
+ m_flipVerBox->setEnabled(false);
+ m_colorGuideLabel->setEnabled(true);
+ m_guideColorBt->setEnabled(true);
+ m_guideSize->setEnabled(true);
+ }
+ else if ( t == ImageSelectionWidget::HarmoniousTriangles )
+ {
+ m_goldenSectionBox->setEnabled(false);
+ m_goldenSpiralSectionBox->setEnabled(false);
+ m_goldenSpiralBox->setEnabled(false);
+ m_goldenTriangleBox->setEnabled(false);
+ m_flipHorBox->setEnabled(true);
+ m_flipVerBox->setEnabled(true);
+ m_colorGuideLabel->setEnabled(true);
+ m_guideColorBt->setEnabled(true);
+ m_guideSize->setEnabled(true);
+ }
+ else
+ {
+ m_goldenSectionBox->setEnabled(true);
+ m_goldenSpiralSectionBox->setEnabled(true);
+ m_goldenSpiralBox->setEnabled(true);
+ m_goldenTriangleBox->setEnabled(true);
+ m_flipHorBox->setEnabled(true);
+ m_flipVerBox->setEnabled(true);
+ m_colorGuideLabel->setEnabled(true);
+ m_guideColorBt->setEnabled(true);
+ m_guideSize->setEnabled(true);
+ }
+
+ m_imageSelectionWidget->setGoldenGuideTypes(m_goldenSectionBox->isChecked(),
+ m_goldenSpiralSectionBox->isChecked(),
+ m_goldenSpiralBox->isChecked(),
+ m_goldenTriangleBox->isChecked(),
+ m_flipHorBox->isChecked(),
+ m_flipVerBox->isChecked());
+ m_imageSelectionWidget->slotGuideLines(t);
+}
+
+void ImageEffect_RatioCrop::slotGoldenGuideTypeChanged()
+{
+ slotGuideTypeChanged(m_guideLinesCB->currentItem());
+}
+
+void ImageEffect_RatioCrop::slotCustomNRatioChanged(int a)
+{
+ if ( ! m_autoOrientation->isChecked() )
+ {
+ if ( ( m_orientCB->currentItem() == ImageSelectionWidget::Portrait &&
+ m_customRatioDInput->value() < a ) ||
+ ( m_orientCB->currentItem() == ImageSelectionWidget::Landscape &&
+ m_customRatioDInput->value() > a ) )
+ {
+ m_customRatioDInput->blockSignals(true);
+ m_customRatioDInput->setValue(a);
+ m_customRatioDInput->blockSignals(false);
+ }
+ }
+
+ slotCustomRatioChanged();
+}
+
+void ImageEffect_RatioCrop::slotCustomDRatioChanged(int a)
+{
+ if ( ! m_autoOrientation->isChecked() )
+ {
+ if ( ( m_orientCB->currentItem() == ImageSelectionWidget::Landscape &&
+ m_customRatioNInput->value() < a ) ||
+ ( m_orientCB->currentItem() == ImageSelectionWidget::Portrait &&
+ m_customRatioNInput->value() > a ) )
+ {
+ m_customRatioNInput->blockSignals(true);
+ m_customRatioNInput->setValue(a);
+ m_customRatioNInput->blockSignals(false);
+ }
+ }
+
+ slotCustomRatioChanged();
+}
+
+void ImageEffect_RatioCrop::slotCustomRatioChanged()
+{
+ m_imageSelectionWidget->setSelectionAspectRatioValue(
+ m_customRatioNInput->value(), m_customRatioDInput->value() );
+
+ // Reset selection area.
+ slotDefault();
+}
+
+void ImageEffect_RatioCrop::slotOk()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ TQRect currentRegion = m_imageSelectionWidget->getRegionSelection();
+ Digikam::ImageIface* iface = m_imageSelectionWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+
+ TQRect normalizedRegion = currentRegion.normalize();
+ if (normalizedRegion.right() > w) normalizedRegion.setRight(w);
+ if (normalizedRegion.bottom() > h) normalizedRegion.setBottom(h);
+
+ Digikam::DImg imOrg(w, h, sb, a, data);
+ delete [] data;
+ imOrg.crop(normalizedRegion);
+
+ iface->putOriginalImage(i18n("Aspect Ratio Crop"), imOrg.bits(), imOrg.width(), imOrg.height());
+
+ kapp->restoreOverrideCursor();
+ writeSettings();
+ accept();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.h b/src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.h
new file mode 100644
index 00000000..ca24eeac
--- /dev/null
+++ b/src/imageplugins/coreplugin/ratiocrop/imageeffect_ratiocrop.h
@@ -0,0 +1,132 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : digiKam image editor Ratio Crop tool
+ *
+ * Copyright (C) 2007 by Jaromir Malenko <malenko at email dot cz>
+ * Copyright (C) 2008 by Roberto Castagnola <roberto dot castagnola at gmail dot com>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_RATIOCROP_H
+#define IMAGEEFFECT_RATIOCROP_H
+
+// Digikam include.
+
+#include "imagedlgbase.h"
+
+class TQLabel;
+class TQComboBox;
+class TQPushButton;
+class TQCheckBox;
+class TQSpinBox;
+
+class KIntNumInput;
+class KIntSpinBox;
+class KColorButton;
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageSelectionWidget;
+
+class ImageEffect_RatioCrop : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_RatioCrop(TQWidget *parent);
+ ~ImageEffect_RatioCrop();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+
+ void applyRatioChanges(int a);
+ void setRatioCBText(int orientation);
+
+private slots:
+
+ void slotUser1();
+ void slotDefault();
+ void slotOk();
+
+ void slotCenterWidth();
+ void slotCenterHeight();
+ void slotXChanged(int x);
+ void slotYChanged(int y);
+ void slotWidthChanged(int w);
+ void slotHeightChanged(int h);
+ void slotCustomRatioChanged();
+ void slotCustomNRatioChanged(int a);
+ void slotCustomDRatioChanged(int a);
+ void slotPreciseCropChanged(bool a);
+ void slotOrientChanged(int o);
+ void slotAutoOrientChanged(bool a);
+ void slotRatioChanged(int a);
+ void slotSelectionChanged(TQRect rect );
+ void slotSelectionOrientationChanged(int);
+ void slotGuideTypeChanged(int t);
+ void slotGoldenGuideTypeChanged();
+
+private:
+
+ bool m_originalIsLandscape;
+
+ TQLabel *m_customLabel1;
+ TQLabel *m_customLabel2;
+ TQLabel *m_orientLabel;
+ TQLabel *m_colorGuideLabel;
+
+ TQComboBox *m_ratioCB;
+ TQComboBox *m_orientCB;
+ TQComboBox *m_guideLinesCB;
+
+ TQPushButton *m_centerWidth;
+ TQPushButton *m_centerHeight;
+
+ TQCheckBox *m_goldenSectionBox;
+ TQCheckBox *m_goldenSpiralSectionBox;
+ TQCheckBox *m_goldenSpiralBox;
+ TQCheckBox *m_goldenTriangleBox;
+ TQCheckBox *m_flipHorBox;
+ TQCheckBox *m_flipVerBox;
+ TQCheckBox *m_autoOrientation;
+ TQCheckBox *m_preciseCrop;
+
+ TQSpinBox *m_guideSize;
+
+ KIntNumInput *m_widthInput;
+ KIntNumInput *m_heightInput;
+ KIntNumInput *m_xInput;
+ KIntNumInput *m_yInput;
+
+ KIntSpinBox *m_customRatioNInput;
+ KIntSpinBox *m_customRatioDInput;
+
+ KColorButton *m_guideColorBt;
+
+ ImageSelectionWidget *m_imageSelectionWidget;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_RATIOCROP_H */
diff --git a/src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.cpp b/src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.cpp
new file mode 100644
index 00000000..17eaf415
--- /dev/null
+++ b/src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.cpp
@@ -0,0 +1,1422 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-09
+ * Description : image selection widget used by ratio crop tool.
+ *
+ * Copyright (C) 2007 by Jaromir Malenko <malenko at email.cz>
+ * Copyright (C) 2008 by Roberto Castagnola <roberto dot castagnola at gmail dot com>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define OPACITY 0.7
+#define RCOL 0xAA
+#define GCOL 0xAA
+#define BCOL 0xAA
+
+#define MINRANGE 0
+
+// Golden number (1+sqrt(5))/2
+#define PHI 1.61803398874989479
+// 1/PHI
+#define INVPHI 0.61803398874989479
+
+// C++ includes.
+
+#include <iostream>
+#include <cstdio>
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqregion.h>
+#include <tqcolor.h>
+#include <tqpainter.h>
+#include <tqbrush.h>
+#include <tqpixmap.h>
+#include <tqimage.h>
+#include <tqpen.h>
+#include <tqpoint.h>
+#include <tqtimer.h>
+#include <tqsizepolicy.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "dimg.h"
+#include "imageselectionwidget.h"
+#include "imageselectionwidget.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageSelectionWidgetPriv
+{
+public:
+
+ enum ResizingMode
+ {
+ ResizingNone = 0,
+ ResizingTopLeft,
+ ResizingTopRight,
+ ResizingBottomLeft,
+ ResizingBottomRight
+ };
+
+ ImageSelectionWidgetPriv()
+ {
+ currentResizing = ResizingNone;
+ iface = 0;
+ pixmap = 0;
+ guideSize = 1;
+ }
+
+ // Golden guide types.
+ bool drawGoldenSection;
+ bool drawGoldenSpiralSection;
+ bool drawGoldenSpiral;
+ bool drawGoldenTriangle;
+
+ // Golden guide translations.
+ bool flipHorGoldenGuide;
+ bool flipVerGoldenGuide;
+
+ bool moving;
+ bool autoOrientation;
+ bool preciseCrop;
+
+ int guideLinesType;
+ int guideSize;
+
+ int currentAspectRatioType;
+ int currentResizing;
+ int currentOrientation;
+
+ float currentWidthRatioValue;
+ float currentHeightRatioValue;
+
+ TQPoint lastPos;
+
+ TQRect rect;
+ TQRect image; // Real image dimension.
+ TQRect regionSelection; // Real size image selection.
+ TQRect localRegionSelection; // Local size selection.
+
+ // Draggable local region selection corners.
+ TQRect localTopLeftCorner;
+ TQRect localBottomLeftCorner;
+ TQRect localTopRightCorner;
+ TQRect localBottomRightCorner;
+
+ TQPixmap *pixmap;
+
+ TQColor guideColor;
+
+ Digikam::DImg preview;
+
+ Digikam::ImageIface *iface;
+};
+
+ImageSelectionWidget::ImageSelectionWidget(int w, int h, TQWidget *parent,
+ int widthRatioValue, int heightRatioValue,
+ int aspectRatioType, int orient, int guideLinesType)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new ImageSelectionWidgetPriv;
+ d->currentAspectRatioType = aspectRatioType;
+ d->currentWidthRatioValue = widthRatioValue;
+ d->currentHeightRatioValue = heightRatioValue;
+ d->currentOrientation = orient;
+ d->guideLinesType = guideLinesType;
+ d->autoOrientation = false;
+ d->preciseCrop = false;
+ d->moving = true;
+ reverseRatioValues();
+
+ setBackgroundMode(TQt::NoBackground);
+ setMinimumSize(w, h);
+ setMouseTracking(true);
+
+ d->iface = new Digikam::ImageIface(w, h);
+ uchar *data = d->iface->getPreviewImage();
+ int width = d->iface->previewWidth();
+ int height = d->iface->previewHeight();
+ bool sixteenBit = d->iface->previewSixteenBit();
+ bool hasAlpha = d->iface->previewHasAlpha();
+ d->preview = Digikam::DImg(width, height, sixteenBit, hasAlpha, data);
+ delete [] data;
+ d->preview.convertToEightBit();
+ d->pixmap = new TQPixmap(w, h);
+
+ d->image = TQRect(0, 0, d->iface->originalWidth(), d->iface->originalHeight());
+ d->rect = TQRect(w/2-d->preview.width()/2, h/2-d->preview.height()/2,
+ d->preview.width(), d->preview.height());
+ updatePixmap();
+ setGoldenGuideTypes(true, false, false, false, false, false);
+}
+
+ImageSelectionWidget::~ImageSelectionWidget()
+{
+ delete d->iface;
+ delete d->pixmap;
+ delete d;
+}
+
+Digikam::ImageIface* ImageSelectionWidget::imageIface()
+{
+ return d->iface;
+}
+
+void ImageSelectionWidget::resizeEvent(TQResizeEvent *e)
+{
+ delete d->pixmap;
+
+ int w = e->size().width();
+ int h = e->size().height();
+
+ uchar *data = d->iface->setPreviewImageSize(w, h);
+ int width = d->iface->previewWidth();
+ int height = d->iface->previewHeight();
+ bool sixteenBit = d->iface->previewSixteenBit();
+ bool hasAlpha = d->iface->previewHasAlpha();
+ d->preview = Digikam::DImg(width, height, sixteenBit, hasAlpha, data);
+ delete [] data;
+ d->preview.convertToEightBit();
+
+ d->pixmap = new TQPixmap(w, h);
+
+ d->rect = TQRect(w/2-d->preview.width()/2, h/2-d->preview.height()/2,
+ d->preview.width(), d->preview.height());
+ updatePixmap();
+}
+
+int ImageSelectionWidget::getOriginalImageWidth()
+{
+ return d->image.width();
+}
+
+int ImageSelectionWidget::getOriginalImageHeight()
+{
+ return d->image.height();
+}
+
+TQRect ImageSelectionWidget::getRegionSelection()
+{
+ return d->regionSelection;
+}
+
+int ImageSelectionWidget::getMinWidthRange()
+{
+ return MINRANGE;
+}
+
+int ImageSelectionWidget::getMinHeightRange()
+{
+ return MINRANGE;
+}
+
+int ImageSelectionWidget::getMaxWidthRange()
+{
+ int maxW = d->image.width() - d->regionSelection.left();
+
+ if (d->currentAspectRatioType != RATIONONE)
+ {
+ // Compute max width taking aspect ratio into account
+ int t = d->currentWidthRatioValue > d->currentHeightRatioValue ? 1 : 0;
+ int h = d->image.height() - d->regionSelection.top();
+ int w = rint( ( h + t ) * d->currentWidthRatioValue /
+ d->currentHeightRatioValue ) - t;
+ if ( w < maxW )
+ maxW = w;
+ }
+
+ // Return max width adjusted if a precise crop is wanted
+ return computePreciseSize(maxW, d->currentWidthRatioValue);
+}
+
+int ImageSelectionWidget::getMaxHeightRange()
+{
+ int maxH = d->image.height() - d->regionSelection.top();
+
+ if (d->currentAspectRatioType != RATIONONE)
+ {
+ // Compute max height taking aspect ratio into account
+ int t = d->currentHeightRatioValue > d->currentWidthRatioValue ? 1 : 0;
+ int w = d->image.width() - d->regionSelection.left();
+ int h = rint( ( w + t ) * d->currentHeightRatioValue /
+ d->currentWidthRatioValue ) - t;
+ if ( h < maxH )
+ maxH = h;
+ }
+
+ // Return max height adjusted if a precise crop is wanted
+ return computePreciseSize(maxH, d->currentHeightRatioValue);
+}
+
+int ImageSelectionWidget::getWidthStep()
+{
+ if ( d->preciseCrop && preciseCropAvailable() )
+ return d->currentWidthRatioValue;
+ else
+ return 1;
+}
+
+int ImageSelectionWidget::getHeightStep()
+{
+ if ( d->preciseCrop && preciseCropAvailable() )
+ return d->currentHeightRatioValue;
+ else
+ return 1;
+}
+
+// Draw a new centered selection with half width (if orientation = Landscape)
+// or with half height (if orientation = Portrait)
+void ImageSelectionWidget::resetSelection()
+{
+ d->regionSelection.setWidth(d->image.width()/2);
+ d->regionSelection.setHeight(d->image.height()/2);
+ applyAspectRatio(d->currentOrientation == Portrait, false);
+
+ setCenterSelection(CenterImage);
+}
+
+void ImageSelectionWidget::setCenterSelection(int centerType)
+{
+ // Adjust selection size if bigger than real image
+ if ( d->regionSelection.height() > d->image.height() )
+ {
+ d->regionSelection.setHeight(d->image.height());
+ applyAspectRatio(true, false);
+ }
+ if ( d->regionSelection.width() > d->image.width() )
+ {
+ d->regionSelection.setWidth(d->image.width());
+ applyAspectRatio(false, false);
+ }
+
+ // Set center point for selection
+ TQPoint center = d->image.center();
+ switch (centerType)
+ {
+ case CenterWidth:
+ center.setY(d->regionSelection.center().y());
+ break;
+
+ case CenterHeight:
+ center.setX(d->regionSelection.center().x());
+ break;
+ }
+ d->regionSelection.moveCenter(center);
+
+ // Repaint
+ updatePixmap();
+ repaint(false);
+ regionSelectionChanged();
+}
+
+// Draw a new centered selection with max size
+void ImageSelectionWidget::maxAspectSelection()
+{
+ d->regionSelection.setWidth(d->image.width());
+ d->regionSelection.setHeight(d->image.height());
+ if ( d->currentAspectRatioType != RATIONONE )
+ applyAspectRatio(d->currentOrientation == Portrait, false);
+
+ setCenterSelection(CenterImage);
+}
+
+void ImageSelectionWidget::setGoldenGuideTypes(bool drawGoldenSection, bool drawGoldenSpiralSection,
+ bool drawGoldenSpiral, bool drawGoldenTriangle,
+ bool flipHorGoldenGuide, bool flipVerGoldenGuide)
+{
+ d->drawGoldenSection = drawGoldenSection;
+ d->drawGoldenSpiralSection = drawGoldenSpiralSection;
+ d->drawGoldenSpiral = drawGoldenSpiral;
+ d->drawGoldenTriangle = drawGoldenTriangle;
+ d->flipHorGoldenGuide = flipHorGoldenGuide;
+ d->flipVerGoldenGuide = flipVerGoldenGuide;
+}
+
+void ImageSelectionWidget::slotGuideLines(int guideLinesType)
+{
+ d->guideLinesType = guideLinesType;
+ updatePixmap();
+ repaint(false);
+}
+
+void ImageSelectionWidget::slotChangeGuideColor(const TQColor &color)
+{
+ d->guideColor = color;
+ updatePixmap();
+ repaint(false);
+}
+
+void ImageSelectionWidget::slotChangeGuideSize(int size)
+{
+ d->guideSize = size;
+ updatePixmap();
+ repaint(false);
+}
+
+void ImageSelectionWidget::setSelectionOrientation(int orient)
+{
+ d->currentOrientation = orient;
+ reverseRatioValues();
+ applyAspectRatio(true);
+ emit signalSelectionOrientationChanged( d->currentOrientation );
+}
+
+void ImageSelectionWidget::setSelectionAspectRatioType(int aspectRatioType)
+{
+ d->currentAspectRatioType = aspectRatioType;
+
+ // Set ratio values
+ switch(aspectRatioType)
+ {
+ case RATIO01X01:
+ d->currentWidthRatioValue = 1.0;
+ d->currentHeightRatioValue = 1.0;
+ break;
+
+ case RATIO03X04:
+ d->currentWidthRatioValue = 4.0;
+ d->currentHeightRatioValue = 3.0;
+ break;
+
+ case RATIO02x03:
+ d->currentWidthRatioValue = 3.0;
+ d->currentHeightRatioValue = 2.0;
+ break;
+
+ case RATIO05x07:
+ d->currentWidthRatioValue = 7.0;
+ d->currentHeightRatioValue = 5.0;
+ break;
+
+ case RATIO07x10:
+ d->currentWidthRatioValue = 10.0;
+ d->currentHeightRatioValue = 7.0;
+ break;
+
+ case RATIO04X05:
+ d->currentWidthRatioValue = 5.0;
+ d->currentHeightRatioValue = 4.0;
+ break;
+
+ case RATIOGOLDEN:
+ d->currentWidthRatioValue = PHI;
+ d->currentHeightRatioValue = 1.0;
+ break;
+ }
+
+ reverseRatioValues();
+ applyAspectRatio(false);
+}
+
+void ImageSelectionWidget::setSelectionAspectRatioValue(int widthRatioValue,
+ int heightRatioValue)
+{
+ int gdc = widthRatioValue;
+
+ // Compute greatest common divisor using Euclidean algorithm
+ for (int tmp, mod = heightRatioValue; mod != 0; mod = tmp % mod)
+ {
+ tmp = gdc;
+ gdc = mod;
+ }
+
+ d->currentWidthRatioValue = widthRatioValue / gdc;
+ d->currentHeightRatioValue = heightRatioValue / gdc;
+ d->currentAspectRatioType = RATIOCUSTOM;
+
+ // Fix orientation
+ if ( d->autoOrientation )
+ {
+ if ( heightRatioValue > widthRatioValue &&
+ d->currentOrientation == Landscape )
+ {
+ d->currentOrientation = Portrait;
+ emit signalSelectionOrientationChanged( d->currentOrientation );
+ }
+ else if ( widthRatioValue > heightRatioValue &&
+ d->currentOrientation == Portrait )
+ {
+ d->currentOrientation = Landscape;
+ emit signalSelectionOrientationChanged( d->currentOrientation );
+ }
+ }
+ else
+ reverseRatioValues();
+
+ applyAspectRatio(false);
+}
+
+void ImageSelectionWidget::reverseRatioValues()
+{
+ // Reverse ratio values if needed
+ if ( ( d->currentWidthRatioValue > d->currentHeightRatioValue &&
+ d->currentOrientation == Portrait ) ||
+ ( d->currentHeightRatioValue > d->currentWidthRatioValue &&
+ d->currentOrientation == Landscape ) )
+ {
+ float tmp = d->currentWidthRatioValue;
+ d->currentWidthRatioValue = d->currentHeightRatioValue;
+ d->currentHeightRatioValue = tmp;
+ }
+}
+
+bool ImageSelectionWidget::preciseCropAvailable()
+{
+ // Define when precise crop feature can be used
+ // No needed when aspect ratio is 1:1
+ switch(d->currentAspectRatioType)
+ {
+ case RATIONONE:
+ case RATIO01X01:
+ case RATIOGOLDEN:
+ return false;
+
+ case RATIOCUSTOM:
+ return ( d->currentWidthRatioValue != d->currentHeightRatioValue );
+
+ default:
+ return true;
+ }
+}
+
+void ImageSelectionWidget::setPreciseCrop(bool precise)
+{
+ d->preciseCrop = precise;
+ applyAspectRatio(false, true);
+ regionSelectionChanged();
+}
+
+void ImageSelectionWidget::setAutoOrientation(bool orientation)
+{
+ d->autoOrientation = orientation;
+}
+
+void ImageSelectionWidget::setSelectionX(int x)
+{
+ d->regionSelection.moveLeft(x);
+ regionSelectionMoved();
+}
+
+void ImageSelectionWidget::setSelectionY(int y)
+{
+ d->regionSelection.moveTop(y);
+ regionSelectionMoved();
+}
+
+void ImageSelectionWidget::setSelectionWidth(int w)
+{
+ d->regionSelection.setWidth(w);
+ applyAspectRatio(false, true);
+
+ regionSelectionChanged();
+}
+
+void ImageSelectionWidget::setSelectionHeight(int h)
+{
+ d->regionSelection.setHeight(h);
+ applyAspectRatio(true, true);
+
+ regionSelectionChanged();
+}
+
+TQPoint ImageSelectionWidget::convertPoint(const TQPoint pm, bool localToReal)
+{
+ return convertPoint(pm.x(), pm.y(), localToReal);
+}
+
+TQPoint ImageSelectionWidget::convertPoint(int x, int y, bool localToReal)
+{
+ int pmX, pmY;
+
+ if (localToReal)
+ {
+ pmX = ( x - d->rect.left() ) * (float)d->image.width() /
+ (float)d->preview.width();
+
+ pmY = ( y - d->rect.top() ) * (float)d->image.height() /
+ (float)d->preview.height();
+ }
+ else
+ {
+ pmX = d->rect.left() + ( x * (float)d->preview.width() /
+ (float)d->image.width() );
+
+ pmY = d->rect.top() + ( y * (float)d->preview.height() /
+ (float)d->image.height() );
+ }
+
+ return TQPoint(pmX, pmY);
+}
+
+int ImageSelectionWidget::computePreciseSize(int size, int step)
+{
+ // Adjust size if precise crop is wanted
+ if ( d->preciseCrop && preciseCropAvailable() )
+ size = int(size / step) * step;
+
+ return size;
+}
+
+void ImageSelectionWidget::applyAspectRatio(bool useHeight, bool repaintWidget)
+{
+ // Save selection area for re-adjustment after changing width and height.
+ TQRect oldRegionSelection = d->regionSelection;
+
+ if ( !useHeight ) // Width changed.
+ {
+ int w = computePreciseSize(d->regionSelection.width(),
+ d->currentWidthRatioValue);
+
+ d->regionSelection.setWidth(w);
+ switch(d->currentAspectRatioType)
+ {
+ case RATIONONE:
+ break;
+
+ default:
+ d->regionSelection.setHeight(rint( w * d->currentHeightRatioValue /
+ d->currentWidthRatioValue ) );
+ break;
+ }
+ }
+ else // Height changed.
+ {
+ int h = computePreciseSize(d->regionSelection.height(),
+ d->currentHeightRatioValue);
+
+ d->regionSelection.setHeight(h);
+ switch(d->currentAspectRatioType)
+ {
+ case RATIONONE:
+ break;
+
+ default:
+ d->regionSelection.setWidth(rint( h * d->currentWidthRatioValue /
+ d->currentHeightRatioValue ) );
+ break;
+ }
+ }
+
+ // If we change selection size by a corner, re-adjust the oposite corner position.
+ switch(d->currentResizing)
+ {
+ case ImageSelectionWidgetPriv::ResizingTopLeft:
+ d->regionSelection.moveBottomRight( oldRegionSelection.bottomRight() );
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingTopRight:
+ d->regionSelection.moveBottomLeft( oldRegionSelection.bottomLeft() );
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomLeft:
+ d->regionSelection.moveTopRight( oldRegionSelection.topRight() );
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomRight:
+ d->regionSelection.moveTopLeft( oldRegionSelection.topLeft() );
+ break;
+ }
+
+ if (repaintWidget)
+ {
+ updatePixmap();
+ repaint(false);
+ }
+}
+
+void ImageSelectionWidget::normalizeRegion()
+{
+ // Perform normalization of selection area.
+
+ if (d->regionSelection.left() < d->image.left())
+ d->regionSelection.moveLeft(d->image.left());
+
+ if (d->regionSelection.top() < d->image.top())
+ d->regionSelection.moveTop(d->image.top());
+
+ if (d->regionSelection.right() > d->image.right())
+ d->regionSelection.moveRight(d->image.right());
+
+ if (d->regionSelection.bottom() > d->image.bottom())
+ d->regionSelection.moveBottom(d->image.bottom());
+}
+
+void ImageSelectionWidget::regionSelectionMoved()
+{
+ normalizeRegion();
+
+ updatePixmap();
+ repaint(false);
+
+ emit signalSelectionMoved( d->regionSelection );
+}
+
+void ImageSelectionWidget::regionSelectionChanged()
+{
+ // Compute the intersection of selection region and image region
+ TQRect cut = d->regionSelection & d->image;
+
+ // Adjust selection size if it was cropped
+ if ( d->regionSelection.width() > cut.width() )
+ {
+ d->regionSelection = cut;
+ applyAspectRatio(false);
+ }
+ if ( d->regionSelection.height() > cut.height() )
+ {
+ d->regionSelection = cut;
+ applyAspectRatio(true);
+ }
+
+ emit signalSelectionChanged( d->regionSelection );
+}
+
+void ImageSelectionWidget::updatePixmap()
+{
+ // Updated local selection region.
+ d->localRegionSelection.setTopLeft(
+ convertPoint(d->regionSelection.topLeft(), false));
+ d->localRegionSelection.setBottomRight(
+ convertPoint(d->regionSelection.bottomRight(), false));
+
+ // Updated dragging corners region.
+ d->localTopLeftCorner.setRect(d->localRegionSelection.left(),
+ d->localRegionSelection.top(), 8, 8);
+ d->localBottomLeftCorner.setRect(d->localRegionSelection.left(),
+ d->localRegionSelection.bottom() - 7, 8, 8);
+ d->localTopRightCorner.setRect(d->localRegionSelection.right() - 7,
+ d->localRegionSelection.top(), 8, 8);
+ d->localBottomRightCorner.setRect(d->localRegionSelection.right() - 7,
+ d->localRegionSelection.bottom() - 7, 8, 8);
+
+ // Drawing background and image.
+ d->pixmap->fill(colorGroup().background());
+
+ if (d->preview.isNull())
+ return;
+
+ // Drawing region outside selection grayed.
+
+ Digikam::DImg image = d->preview.copy();
+
+ uchar* ptr = image.bits();
+ uchar r, g, b;
+
+ for (int y=d->rect.top() ; y <= d->rect.bottom() ; y++)
+ {
+ for (int x=d->rect.left() ; x <= d->rect.right() ; x++)
+ {
+ if (! d->localRegionSelection.contains(x, y, true) )
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+
+ r += (uchar)((RCOL - r) * OPACITY);
+ g += (uchar)((GCOL - g) * OPACITY);
+ b += (uchar)((BCOL - b) * OPACITY);
+
+ ptr[0] = b;
+ ptr[1] = g;
+ ptr[2] = r;
+ }
+
+ ptr+=4;
+ }
+ }
+
+ TQPixmap pix = d->iface->convertToPixmap(image);
+ bitBlt(d->pixmap, d->rect.x(), d->rect.y(), &pix);
+
+ // Stop here if no selection to draw
+ if ( d->regionSelection.isEmpty() )
+ return;
+
+ TQPainter p(d->pixmap);
+
+ // Drawing selection borders.
+
+ p.setPen(TQPen(TQColor(250, 250, 255), 1, TQt::SolidLine));
+ p.drawRect(d->localRegionSelection);
+
+ // Drawing selection corners.
+
+ p.drawRect(d->localTopLeftCorner);
+ p.drawRect(d->localBottomLeftCorner);
+ p.drawRect(d->localTopRightCorner);
+ p.drawRect(d->localBottomRightCorner);
+
+ // Drawing guide lines.
+
+ // Constraint drawing only on local selection region.
+ // This is needed because arcs and incurved lines can draw
+ // outside a little of local selection region.
+ p.setClipping(true);
+ p.setClipRect(d->localRegionSelection);
+
+ switch (d->guideLinesType)
+ {
+ case RulesOfThirds:
+ {
+ int xThird = d->localRegionSelection.width() / 3;
+ int yThird = d->localRegionSelection.height() / 3;
+
+ p.setPen(TQPen(TQt::white, d->guideSize, TQt::SolidLine));
+ p.drawLine( d->localRegionSelection.left() + xThird, d->localRegionSelection.top(),
+ d->localRegionSelection.left() + xThird, d->localRegionSelection.bottom() );
+ p.drawLine( d->localRegionSelection.left() + 2*xThird, d->localRegionSelection.top(),
+ d->localRegionSelection.left() + 2*xThird, d->localRegionSelection.bottom() );
+
+ p.drawLine( d->localRegionSelection.left(), d->localRegionSelection.top() + yThird,
+ d->localRegionSelection.right(), d->localRegionSelection.top() + yThird );
+ p.drawLine( d->localRegionSelection.left(), d->localRegionSelection.top() + 2*yThird,
+ d->localRegionSelection.right(), d->localRegionSelection.top() + 2*yThird );
+
+ p.setPen(TQPen(d->guideColor, d->guideSize, TQt::DotLine));
+ p.drawLine( d->localRegionSelection.left() + xThird, d->localRegionSelection.top(),
+ d->localRegionSelection.left() + xThird, d->localRegionSelection.bottom() );
+ p.drawLine( d->localRegionSelection.left() + 2*xThird, d->localRegionSelection.top(),
+ d->localRegionSelection.left() + 2*xThird, d->localRegionSelection.bottom() );
+
+ p.drawLine( d->localRegionSelection.left(), d->localRegionSelection.top() + yThird,
+ d->localRegionSelection.right(), d->localRegionSelection.top() + yThird );
+ p.drawLine( d->localRegionSelection.left(), d->localRegionSelection.top() + 2*yThird,
+ d->localRegionSelection.right(), d->localRegionSelection.top() + 2*yThird );
+ break;
+ }
+
+ case DiagonalMethod:
+ {
+ // Move coordinates to top, left
+ p.translate(d->localRegionSelection.topLeft().x(), d->localRegionSelection.topLeft().y());
+
+ float w = (float)d->localRegionSelection.width();
+ float h = (float)d->localRegionSelection.height();
+
+ p.setPen(TQPen(TQt::white, d->guideSize, TQt::SolidLine));
+ if (w > h)
+ {
+ p.drawLine( 0, 0, h, h);
+ p.drawLine( 0, h, h, 0);
+ p.drawLine( w-h, 0, w, h);
+ p.drawLine( w-h, h, w, 0);
+
+ }
+ else
+ {
+ p.drawLine( 0, 0, w, w);
+ p.drawLine( 0, w, w, 0);
+ p.drawLine( 0, h-w, w, h);
+ p.drawLine( 0, h, w, h-w);
+ }
+
+ p.setPen(TQPen(d->guideColor, d->guideSize, TQt::DotLine));
+ if (w > h)
+ {
+ p.drawLine( 0, 0, h, h);
+ p.drawLine( 0, h, h, 0);
+ p.drawLine( w-h, 0, w, h);
+ p.drawLine( w-h, h, w, 0);
+
+ }
+ else
+ {
+ p.drawLine( 0, 0, w, w);
+ p.drawLine( 0, w, w, 0);
+ p.drawLine( 0, h-w, w, h);
+ p.drawLine( 0, h, w, h-w);
+ }
+ break;
+ }
+
+ case HarmoniousTriangles:
+ {
+ // Move coordinates to local center selection.
+ p.translate(d->localRegionSelection.center().x(), d->localRegionSelection.center().y());
+
+ // Flip horizontal.
+ if (d->flipHorGoldenGuide)
+ p.scale(-1, 1);
+
+ // Flip verical.
+ if (d->flipVerGoldenGuide)
+ p.scale(1, -1);
+
+ float w = (float)d->localRegionSelection.width();
+ float h = (float)d->localRegionSelection.height();
+ int dst = (int)((h*cos(atan(w/h)) / (cos(atan(h/w)))));
+
+ p.setPen(TQPen(TQt::white, d->guideSize, TQt::SolidLine));
+ p.drawLine( -d->localRegionSelection.width()/2, -d->localRegionSelection.height()/2,
+ d->localRegionSelection.width()/2, d->localRegionSelection.height()/2);
+
+ p.drawLine( -d->localRegionSelection.width()/2 + dst, -d->localRegionSelection.height()/2,
+ -d->localRegionSelection.width()/2, d->localRegionSelection.height()/2);
+
+ p.drawLine( d->localRegionSelection.width()/2, -d->localRegionSelection.height()/2,
+ d->localRegionSelection.width()/2 - dst, d->localRegionSelection.height()/2);
+
+ p.setPen(TQPen(d->guideColor, d->guideSize, TQt::DotLine));
+ p.drawLine( -d->localRegionSelection.width()/2, -d->localRegionSelection.height()/2,
+ d->localRegionSelection.width()/2, d->localRegionSelection.height()/2);
+
+ p.drawLine( -d->localRegionSelection.width()/2 + dst, -d->localRegionSelection.height()/2,
+ -d->localRegionSelection.width()/2, d->localRegionSelection.height()/2);
+
+ p.drawLine( d->localRegionSelection.width()/2, -d->localRegionSelection.height()/2,
+ d->localRegionSelection.width()/2 - dst, d->localRegionSelection.height()/2);
+ break;
+ }
+
+ case GoldenMean:
+ {
+ // Move coordinates to local center selection.
+ p.translate(d->localRegionSelection.center().x(), d->localRegionSelection.center().y());
+
+ // Flip horizontal.
+ if (d->flipHorGoldenGuide)
+ p.scale(-1, 1);
+
+ // Flip vertical.
+ if (d->flipVerGoldenGuide)
+ p.scale(1, -1);
+
+ int w = d->localRegionSelection.width();
+ int h = d->localRegionSelection.height();
+
+ // lengths for the golden mean and half the sizes of the region:
+ int w_g = (int)(w*INVPHI);
+ int h_g = (int)(h*INVPHI);
+ int w_2 = w/2;
+ int h_2 = h/2;
+
+ TQRect R1(-w_2, -h_2, w_g, h);
+ // w - 2*w_2 corrects for one-pixel difference
+ // so that R2.right() is really at the right end of the region
+ TQRect R2(w_g-w_2, h_2-h_g, w-w_g+1-(w - 2*w_2), h_g);
+
+ TQRect R3((int)(w_2 - R2.width()*INVPHI), -h_2,
+ (int)(R2.width()*INVPHI), h - R2.height());
+ TQRect R4(R2.x(), R1.y(), R3.x() - R2.x(),
+ (int)(R3.height()*INVPHI));
+ TQRect R5(R4.x(), R4.bottom(), (int)(R4.width()*INVPHI),
+ R3.height() - R4.height());
+ TQRect R6(R5.x() + R5.width(), R5.bottom() - (int)(R5.height()*INVPHI),
+ R3.x() - R5.right(), (int)(R5.height()*INVPHI));
+ TQRect R7(R6.right() - (int)(R6.width()*INVPHI), R4.bottom(),
+ (int)(R6.width()*INVPHI), R5.height() - R6.height());
+
+ p.setPen(TQPen(TQt::white, d->guideSize, TQt::SolidLine));
+
+ // Drawing Golden sections.
+ if (d->drawGoldenSection)
+ {
+ // horizontal lines:
+ p.drawLine( R1.left(), R2.top(),
+ R2.right(), R2.top());
+
+ p.drawLine( R1.left(), R1.top() + R2.height(),
+ R2.right(), R1.top() + R2.height());
+
+ // vertical lines:
+ p.drawLine( R1.right(), R1.top(),
+ R1.right(), R1.bottom() );
+
+ p.drawLine( R1.left()+R2.width(), R1.top(),
+ R1.left()+R2.width(), R1.bottom() );
+ }
+
+ // Drawing Golden triangle guides.
+ if (d->drawGoldenTriangle)
+ {
+ p.drawLine( R1.left(), R1.bottom(),
+ R2.right(), R1.top() );
+
+ p.drawLine( R1.left(), R1.top(),
+ R2.right() - R1.width(), R1.bottom());
+
+ p.drawLine( R1.left() + R1.width(), R1.top(),
+ R2.right(), R1.bottom() );
+ }
+
+ // Drawing Golden spiral sections.
+ if (d->drawGoldenSpiralSection)
+ {
+ p.drawLine( R1.topRight(), R1.bottomRight() );
+ p.drawLine( R2.topLeft(), R2.topRight() );
+ p.drawLine( R3.topLeft(), R3.bottomLeft() );
+ p.drawLine( R4.bottomLeft(), R4.bottomRight() );
+ p.drawLine( R5.topRight(), R5.bottomRight() );
+ p.drawLine( R6.topLeft(), R6.topRight() );
+ p.drawLine( R7.topLeft(), R7.bottomLeft() );
+ }
+
+ // Drawing Golden Spiral.
+ if (d->drawGoldenSpiral)
+ {
+ p.drawArc ( R1.left(),
+ R1.top() - R1.height(),
+ 2*R1.width(), 2*R1.height(),
+ 180*16, 90*16);
+
+ p.drawArc ( R2.right() - 2*R2.width(),
+ R1.bottom() - 2*R2.height(),
+ 2*R2.width(), 2*R2.height(),
+ 270*16, 90*16);
+
+ p.drawArc ( R2.right() - 2*R3.width(),
+ R3.top(),
+ 2*R3.width(), 2*R3.height(),
+ 0, 90*16);
+
+ p.drawArc ( R4.left(),
+ R4.top(),
+ 2*R4.width(), 2*R4.height(),
+ 90*16, 90*16);
+
+ p.drawArc ( R5.left(),
+ R5.top()-R5.height(),
+ 2*R5.width(), 2*R5.height(),
+ 180*16, 90*16);
+
+ p.drawArc ( R6.left()-R6.width(),
+ R6.top()-R6.height(),
+ 2*R6.width(), 2*R6.height(),
+ 270*16, 90*16);
+
+ p.drawArc ( R7.left()-R7.width(),
+ R7.top(),
+ 2*R7.width(), 2*R7.height(),
+ 0, 90*16);
+ }
+
+ p.setPen(TQPen(d->guideColor, d->guideSize, TQt::DotLine));
+
+ // Drawing Golden sections.
+ if (d->drawGoldenSection)
+ {
+ // horizontal lines:
+ p.drawLine( R1.left(), R2.top(),
+ R2.right(), R2.top());
+
+ p.drawLine( R1.left(), R1.top() + R2.height(),
+ R2.right(), R1.top() + R2.height());
+
+ // vertical lines:
+ p.drawLine( R1.right(), R1.top(),
+ R1.right(), R1.bottom() );
+
+ p.drawLine( R1.left()+R2.width(), R1.top(),
+ R1.left()+R2.width(), R1.bottom() );
+ }
+
+ // Drawing Golden triangle guides.
+ if (d->drawGoldenTriangle)
+ {
+ p.drawLine( R1.left(), R1.bottom(),
+ R2.right(), R1.top() );
+
+ p.drawLine( R1.left(), R1.top(),
+ R2.right() - R1.width(), R1.bottom());
+
+ p.drawLine( R1.left() + R1.width(), R1.top(),
+ R2.right(), R1.bottom() );
+ }
+
+ // Drawing Golden spiral sections.
+ if (d->drawGoldenSpiralSection)
+ {
+ p.drawLine( R1.topRight(), R1.bottomRight() );
+ p.drawLine( R2.topLeft(), R2.topRight() );
+ p.drawLine( R3.topLeft(), R3.bottomLeft() );
+ p.drawLine( R4.bottomLeft(), R4.bottomRight() );
+ p.drawLine( R5.topRight(), R5.bottomRight() );
+ p.drawLine( R6.topLeft(), R6.topRight() );
+ p.drawLine( R7.topLeft(), R7.bottomLeft() );
+ }
+
+ // Drawing Golden Spiral.
+ if (d->drawGoldenSpiral)
+ {
+ p.drawArc ( R1.left(),
+ R1.top() - R1.height(),
+ 2*R1.width(), 2*R1.height(),
+ 180*16, 90*16);
+
+ p.drawArc ( R2.right() - 2*R2.width(),
+ R1.bottom() - 2*R2.height(),
+ 2*R2.width(), 2*R2.height(),
+ 270*16, 90*16);
+
+ p.drawArc ( R2.right() - 2*R3.width(),
+ R3.top(),
+ 2*R3.width(), 2*R3.height(),
+ 0, 90*16);
+
+ p.drawArc ( R4.left(),
+ R4.top(),
+ 2*R4.width(), 2*R4.height(),
+ 90*16, 90*16);
+
+ p.drawArc ( R5.left(),
+ R5.top()-R5.height(),
+ 2*R5.width(), 2*R5.height(),
+ 180*16, 90*16);
+
+ p.drawArc ( R6.left()-R6.width(),
+ R6.top()-R6.height(),
+ 2*R6.width(), 2*R6.height(),
+ 270*16, 90*16);
+
+ p.drawArc ( R7.left()-R7.width(),
+ R7.top(),
+ 2*R7.width(), 2*R7.height(),
+ 0, 90*16);
+ }
+
+ break;
+ }
+ }
+
+ p.setClipping(false);
+
+ p.end();
+}
+
+void ImageSelectionWidget::paintEvent( TQPaintEvent * )
+{
+ bitBlt(this, 0, 0, d->pixmap);
+}
+
+TQPoint ImageSelectionWidget::opposite()
+{
+ TQPoint opp;
+
+ switch(d->currentResizing)
+ {
+ case ImageSelectionWidgetPriv::ResizingTopRight:
+ opp = d->regionSelection.bottomLeft();
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomLeft:
+ opp = d->regionSelection.topRight();
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomRight:
+ opp = d->regionSelection.topLeft();
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingTopLeft:
+ default:
+ opp = d->regionSelection.bottomRight();
+ break;
+ }
+
+ return opp;
+}
+
+float ImageSelectionWidget::distance(TQPoint a, TQPoint b)
+{
+ return sqrt(pow(a.x() - b.x(), 2) + pow(a.y() - b.y(), 2));
+}
+
+void ImageSelectionWidget::setCursorResizing()
+{
+ switch(d->currentResizing)
+ {
+ case ImageSelectionWidgetPriv::ResizingTopLeft:
+ setCursor( KCursor::sizeFDiagCursor() );
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingTopRight:
+ setCursor( KCursor::sizeBDiagCursor() );
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomLeft:
+ setCursor( KCursor::sizeBDiagCursor() );
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomRight:
+ setCursor( KCursor::sizeFDiagCursor() );
+ break;
+ }
+}
+
+void ImageSelectionWidget::placeSelection(TQPoint pm, bool symmetric, TQPoint center)
+{
+ // Set orientation
+ if ( d->autoOrientation )
+ {
+ TQPoint rel = pm - opposite();
+
+ if ( abs(rel.x()) > abs(rel.y()) )
+ {
+ if ( d->currentOrientation == Portrait )
+ {
+ d->currentOrientation = Landscape;
+ reverseRatioValues();
+ emit signalSelectionOrientationChanged( d->currentOrientation );
+ }
+ }
+ else
+ {
+ if ( d->currentOrientation == Landscape )
+ {
+ d->currentOrientation = Portrait;
+ reverseRatioValues();
+ emit signalSelectionOrientationChanged( d->currentOrientation );
+ }
+ }
+ }
+
+ // Place the corner at the mouse
+ // If a symmetric selection is wanted, place opposite corner to
+ // the center, double selection size and move it to old center after
+ // computing aspect ratio.
+ switch(d->currentResizing)
+ {
+ case ImageSelectionWidgetPriv::ResizingTopLeft:
+ // Place corners to the proper position
+ d->regionSelection.setTopLeft(pm);
+ if ( symmetric )
+ d->regionSelection.setBottomRight(center);
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingTopRight:
+ d->regionSelection.setTopRight(pm);
+ if ( symmetric )
+ d->regionSelection.setBottomLeft(center);
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomLeft:
+ d->regionSelection.setBottomLeft(pm);
+ if ( symmetric )
+ d->regionSelection.setTopRight(center);
+ break;
+
+ case ImageSelectionWidgetPriv::ResizingBottomRight:
+ d->regionSelection.setBottomRight(pm);
+ if ( symmetric )
+ d->regionSelection.setTopLeft(center);
+ break;
+ }
+
+ if ( symmetric )
+ d->regionSelection.setSize(d->regionSelection.size()*2);
+ applyAspectRatio(d->currentOrientation == Portrait, false);
+ if ( symmetric )
+ d->regionSelection.moveCenter(center);
+
+ // Repaint
+ updatePixmap();
+ repaint(false);
+}
+
+void ImageSelectionWidget::mousePressEvent ( TQMouseEvent * e )
+{
+ if ( e->button() == TQt::LeftButton )
+ {
+ TQPoint pm = TQPoint(e->x(), e->y());
+ TQPoint pmVirtual = convertPoint(pm);
+ d->moving = false;
+
+ if ( (e->state() & TQt::ShiftButton) == TQt::ShiftButton )
+ {
+ bool symmetric = (e->state() & TQt::ControlButton ) == TQt::ControlButton;
+ TQPoint center = d->regionSelection.center();
+
+ // Find the closest corner
+
+ TQPoint points[] = { d->regionSelection.topLeft(),
+ d->regionSelection.topRight(),
+ d->regionSelection.bottomLeft(),
+ d->regionSelection.bottomRight() };
+ int resizings[] = { ImageSelectionWidgetPriv::ResizingTopLeft,
+ ImageSelectionWidgetPriv::ResizingTopRight,
+ ImageSelectionWidgetPriv::ResizingBottomLeft,
+ ImageSelectionWidgetPriv::ResizingBottomRight };
+ float dist = -1;
+ for (int i = 0 ; i < 4 ; i++)
+ {
+ TQPoint point = points[i];
+ float dist2 = distance(pmVirtual, point);
+ if (dist2 < dist || d->currentResizing == ImageSelectionWidgetPriv::ResizingNone)
+ {
+ dist = dist2;
+ d->currentResizing = resizings[i];
+ }
+ }
+
+ setCursorResizing();
+
+ placeSelection(pmVirtual, symmetric, center);
+ }
+ else
+ {
+ if ( d->localTopLeftCorner.contains( pm ) )
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingTopLeft;
+ else if ( d->localTopRightCorner.contains( pm ) )
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingTopRight;
+ else if ( d->localBottomLeftCorner.contains( pm ) )
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingBottomLeft;
+ else if ( d->localBottomRightCorner.contains( pm ) )
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingBottomRight;
+ else
+ {
+ d->lastPos = pmVirtual;
+ setCursor( KCursor::sizeAllCursor() );
+
+ if (d->regionSelection.contains( pmVirtual ) )
+ {
+ d->moving = true;
+ }
+ else
+ {
+ d->regionSelection.moveCenter( pmVirtual );
+ normalizeRegion();
+ updatePixmap();
+ repaint(false);
+ }
+ }
+ }
+ }
+}
+
+void ImageSelectionWidget::mouseReleaseEvent ( TQMouseEvent * )
+{
+ if ( d->currentResizing != ImageSelectionWidgetPriv::ResizingNone )
+ {
+ setCursor( KCursor::arrowCursor() );
+ regionSelectionChanged();
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingNone;
+ }
+ else if ( d->regionSelection.contains( d->lastPos ) )
+ {
+ setCursor( KCursor::handCursor() );
+ regionSelectionMoved();
+ }
+ else
+ {
+ setCursor( KCursor::arrowCursor() );
+ regionSelectionMoved();
+ }
+}
+
+void ImageSelectionWidget::mouseMoveEvent ( TQMouseEvent * e )
+{
+ if ( ( e->state() & TQt::LeftButton ) == TQt::LeftButton )
+ {
+ if ( d->moving )
+ {
+ setCursor( KCursor::sizeAllCursor() );
+ TQPoint newPos = convertPoint(e->x(), e->y());
+
+ d->regionSelection.moveBy( newPos.x() - d->lastPos.x(),
+ newPos.y() - d->lastPos.y() );
+
+ d->lastPos = newPos;
+
+ normalizeRegion();
+
+ updatePixmap();
+ repaint(false);
+ }
+ else
+ {
+ TQPoint pmVirtual = convertPoint(e->x(), e->y());
+
+ if ( d->currentResizing == ImageSelectionWidgetPriv::ResizingNone )
+ {
+ d->regionSelection.setTopLeft( pmVirtual );
+ d->regionSelection.setBottomRight( pmVirtual );
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingTopLeft; // set to anything
+ }
+
+ TQPoint center = d->regionSelection.center();
+ bool symmetric = (e->state() & TQt::ControlButton ) == TQt::ControlButton;
+
+ // Change resizing mode
+
+ TQPoint opp = symmetric ? center : opposite();
+ TQPoint dir = pmVirtual - opp;
+
+ if ( dir.x() > 0 && dir.y() > 0 && d->currentResizing != ImageSelectionWidgetPriv::ResizingBottomRight)
+ {
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingBottomRight;
+ d->regionSelection.setTopLeft( opp );
+ setCursor( KCursor::sizeFDiagCursor() );
+ }
+ else if ( dir.x() > 0 && dir.y() < 0 && d->currentResizing != ImageSelectionWidgetPriv::ResizingTopRight)
+ {
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingTopRight;
+ d->regionSelection.setBottomLeft( opp );
+ setCursor( KCursor::sizeBDiagCursor() );
+ }
+ else if ( dir.x() < 0 && dir.y() > 0 && d->currentResizing != ImageSelectionWidgetPriv::ResizingBottomLeft)
+ {
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingBottomLeft;
+ d->regionSelection.setTopRight( opp );
+ setCursor( KCursor::sizeBDiagCursor() );
+ }
+ else if ( dir.x() < 0 && dir.y() < 0 && d->currentResizing != ImageSelectionWidgetPriv::ResizingTopLeft)
+ {
+ d->currentResizing = ImageSelectionWidgetPriv::ResizingTopLeft;
+ d->regionSelection.setBottomRight( opp );
+ setCursor( KCursor::sizeFDiagCursor() );
+ }
+ else
+ {
+ if ( dir.x() == 0 && dir.y() == 0 )
+ setCursor( KCursor::sizeAllCursor() );
+ else if ( dir.x() == 0 )
+ setCursor( KCursor::sizeHorCursor() );
+ else if ( dir.y() == 0 )
+ setCursor( KCursor::sizeVerCursor() );
+ }
+
+ placeSelection(pmVirtual, symmetric, center);
+ }
+ }
+ else
+ {
+ if ( d->localTopLeftCorner.contains( e->x(), e->y() ) ||
+ d->localBottomRightCorner.contains( e->x(), e->y() ) )
+ setCursor( KCursor::sizeFDiagCursor() );
+ else if ( d->localTopRightCorner.contains( e->x(), e->y() ) ||
+ d->localBottomLeftCorner.contains( e->x(), e->y() ) )
+ setCursor( KCursor::sizeBDiagCursor() );
+ else if ( d->localRegionSelection.contains( e->x(), e->y() ) )
+ setCursor( KCursor::handCursor() );
+ else
+ setCursor( KCursor::arrowCursor() );
+ }
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.h b/src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.h
new file mode 100644
index 00000000..0d2bd4fd
--- /dev/null
+++ b/src/imageplugins/coreplugin/ratiocrop/imageselectionwidget.h
@@ -0,0 +1,176 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-09
+ * Description : image selection widget used by ratio crop tool.
+ *
+ * Copyright (C) 2007 by Jaromir Malenko <malenko at email.cz>
+ * Copyright (C) 2008 by Roberto Castagnola <roberto dot castagnola at gmail dot com>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGESELECTIONWIDGET_H
+#define IMAGESELECTIONWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqrect.h>
+#include <tqcolor.h>
+
+namespace Digikam
+{
+class ImageIface;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageSelectionWidgetPriv;
+
+class ImageSelectionWidget : public TQWidget
+{
+
+TQ_OBJECT
+
+
+public:
+
+ enum RatioAspect // Constrained Aspect Ratio list.
+ {
+ RATIOCUSTOM=0, // Custom aspect ratio.
+ RATIO01X01, // 1:1
+ RATIO02x03, // 2:3
+ RATIO03X04, // 3:4
+ RATIO04X05, // 4:5
+ RATIO05x07, // 5:7
+ RATIO07x10, // 7:10
+ RATIOGOLDEN, // Golden ratio : 1:1.618
+ RATIONONE // No aspect ratio.
+ };
+
+ enum Orient
+ {
+ Landscape = 0,
+ Portrait
+ };
+
+ enum CenterType
+ {
+ CenterWidth = 0, // Center selection to the center of image width.
+ CenterHeight, // Center selection to the center of image height.
+ CenterImage // Center selection to the center of image.
+ };
+
+ // Proportion : Golden Ratio and Rule of Thirds. More information at this url:
+ // http://photoinf.com/General/Robert_Berdan/Composition_and_the_Elements_of_Visual_Design.htm
+
+ enum GuideLineType
+ {
+ RulesOfThirds = 0, // Line guides position to 1/3 width and height.
+ DiagonalMethod, // Diagonal Method to improve composition.
+ HarmoniousTriangles, // Harmonious Triangle to improve composition.
+ GoldenMean, // Guides tools using Phi ratio (1.618).
+ GuideNone // No guide line.
+ };
+
+public:
+
+ ImageSelectionWidget(int width, int height, TQWidget *parent=0,
+ int widthRatioValue=1, int heightRatioValue=1,
+ int aspectRatio=RATIO01X01, int orient=Landscape,
+ int guideLinesType=GuideNone);
+ ~ImageSelectionWidget();
+
+ void setCenterSelection(int centerType=CenterImage);
+ void setSelectionX(int x);
+ void setSelectionY(int y);
+ void setSelectionWidth(int w);
+ void setSelectionHeight(int h);
+ void setSelectionOrientation(int orient);
+ void setPreciseCrop(bool precise);
+ void setAutoOrientation(bool orientation);
+ void setSelectionAspectRatioType(int aspectRatioType);
+ void setSelectionAspectRatioValue(int widthRatioValue, int heightRatioValue);
+ void setGoldenGuideTypes(bool drawGoldenSection, bool drawGoldenSpiralSection,
+ bool drawGoldenSpiral, bool drawGoldenTriangle,
+ bool flipHorGoldenGuide, bool flipVerGoldenGuide);
+
+ int getOriginalImageWidth();
+ int getOriginalImageHeight();
+ TQRect getRegionSelection();
+
+ int getMinWidthRange();
+ int getMinHeightRange();
+ int getMaxWidthRange();
+ int getMaxHeightRange();
+ int getWidthStep();
+ int getHeightStep();
+
+ bool preciseCropAvailable();
+
+ void resetSelection();
+ void maxAspectSelection();
+
+ Digikam::ImageIface* imageIface();
+
+public slots:
+
+ void slotGuideLines(int guideLinesType);
+ void slotChangeGuideColor(const TQColor &color);
+ void slotChangeGuideSize(int size);
+
+signals:
+
+ void signalSelectionMoved( TQRect rect );
+ void signalSelectionChanged( TQRect rect );
+ void signalSelectionOrientationChanged( int newOrientation );
+
+protected:
+
+ void paintEvent( TQPaintEvent *e );
+ void mousePressEvent ( TQMouseEvent * e );
+ void mouseReleaseEvent ( TQMouseEvent * e );
+ void mouseMoveEvent ( TQMouseEvent * e );
+ void resizeEvent(TQResizeEvent * e);
+
+private:
+
+ // Recalculate the target selection position and emit 'signalSelectionMoved'.
+ void regionSelectionMoved();
+
+ void regionSelectionChanged();
+ TQPoint convertPoint(const TQPoint pm, bool localToReal=true);
+ TQPoint convertPoint(int x, int y, bool localToReal=true);
+ void normalizeRegion();
+ void reverseRatioValues();
+ int computePreciseSize(int size, int step);
+ void applyAspectRatio(bool useHeight, bool repaintWidget=true);
+ void updatePixmap();
+ TQPoint opposite();
+ float distance(TQPoint a, TQPoint b);
+ void placeSelection(TQPoint pm, bool symetric, TQPoint center);
+ void setCursorResizing();
+
+private:
+
+ ImageSelectionWidgetPriv* d;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGESELECTIONWIDGET_H */
diff --git a/src/imageplugins/coreplugin/ratiocrop/ratiocroptool.cpp b/src/imageplugins/coreplugin/ratiocrop/ratiocroptool.cpp
new file mode 100644
index 00000000..ed0e670e
--- /dev/null
+++ b/src/imageplugins/coreplugin/ratiocrop/ratiocroptool.cpp
@@ -0,0 +1,853 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : digiKam image editor Ratio Crop tool
+ *
+ * Copyright (C) 2007 by Jaromir Malenko <malenko at email dot cz>
+ * Copyright (C) 2008 by Roberto Castagnola <roberto dot castagnola at gmail dot com>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqframe.h>
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqrect.h>
+#include <tqspinbox.h>
+#include <tqtimer.h>
+#include <tqtoolbutton.h>
+#include <tqtooltip.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kcolorbutton.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kpushbutton.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Digikam includes.
+
+#include "editortoolsettings.h"
+#include "imageiface.h"
+#include "imageselectionwidget.h"
+
+// Local includes.
+
+#include "ratiocroptool.h"
+#include "ratiocroptool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+RatioCropTool::RatioCropTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("aspectratiocrop");
+ setToolName(i18n("Aspect Ratio Crop"));
+ setToolIcon(SmallIcon("ratiocrop"));
+ setToolHelp("ratiocroptool.anchor");
+
+ // -------------------------------------------------------------
+
+ m_imageSelectionWidget = new ImageSelectionWidget(480, 320);
+ TQWhatsThis::add(m_imageSelectionWidget,
+ i18n("<p>Here you can see the aspect ratio selection preview "
+ "used for cropping. You can use the mouse to move and "
+ "resize the crop area. "
+ "Press and hold the CTRL key to move the opposite corner too. "
+ "Press and hold the SHIFT key to move the closest corner to the "
+ "mouse pointer."));
+
+ m_originalIsLandscape = ((m_imageSelectionWidget->getOriginalImageWidth()) >
+ (m_imageSelectionWidget->getOriginalImageHeight()));
+
+ setToolView(m_imageSelectionWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Try|
+ EditorToolSettings::Cancel);
+
+ // -------------------------------------------------------------
+
+ // need to set the button to a KStdGuiItem that has no icon
+ m_gboxSettings->button(EditorToolSettings::Try)->setGuiItem(KStdGuiItem::Test);
+ // now we can set the correct text for the button
+ m_gboxSettings->button(EditorToolSettings::Try)->setText(i18n("Max. Aspect"));
+
+ TQToolTip::add(m_gboxSettings->button(EditorToolSettings::Try),
+ i18n("<p>Set selection area to the maximum size according "
+ "to the current ratio."));
+
+ // -------------------------------------------------------------
+
+ TQGridLayout *gboxLayout = new TQGridLayout(m_gboxSettings->plainPage(), 3, 2);
+
+ TQFrame *cropSelection = new TQFrame(m_gboxSettings->plainPage());
+ cropSelection->setFrameStyle(TQFrame::Panel | TQFrame::Sunken);
+
+ TQGridLayout* grid = new TQGridLayout(cropSelection, 7, 5);
+
+ TQLabel *label = new TQLabel(i18n("Ratio:"), cropSelection);
+ m_ratioCB = new RComboBox(cropSelection);
+ m_ratioCB->setDefaultItem(ImageSelectionWidget::RATIO03X04);
+ setRatioCBText(ImageSelectionWidget::Landscape);
+ TQWhatsThis::add( m_ratioCB, i18n("<p>Select your constrained aspect ratio for cropping. "
+ "Aspect Ratio Crop tool uses a relative ratio. That means it "
+ "is the same if you use centimeters or inches and it doesn't "
+ "specify the physical size.<p>"
+ "You can see below a correspondence list of traditional photographic "
+ "paper sizes and aspect ratio crop:<p>"
+ "<b>2:3</b>: 10x15cm, 20x30cm, 30x45cm, 4x6\", 8x12\", "
+ "12x18\", 16x24\", 20x30\"<p>"
+ "<b>3:4</b>: 6x8cm, 15x20cm, 18x24cm, 30x40cm, 3.75x5\", 4.5x6\", "
+ "6x8\", 7.5x10\", 9x12\"<p>"
+ "<b>4:5</b>: 20x25cm, 40x50cm, 8x10\", 16x20\"<p>"
+ "<b>5:7</b>: 15x21cm, 30x42cm, 5x7\"<p>"
+ "<b>7:10</b>: 21x30cm, 42x60cm, 3.5x5\"<p>"
+ "The <b>Golden Ratio</b> is 1:1.618. A composition following this rule "
+ "is considered visually harmonious but can be unadapted to print on "
+ "standard photographic paper."));
+
+ m_preciseCrop = new TQCheckBox(i18n("Exact"), cropSelection);
+ TQWhatsThis::add( m_preciseCrop, i18n("<p>Enable this option to force exact aspect ratio crop."));
+
+ m_orientLabel = new TQLabel(i18n("Orientation:"), cropSelection);
+ m_orientCB = new RComboBox(cropSelection);
+ m_orientCB->insertItem( i18n("Landscape"));
+ m_orientCB->insertItem( i18n("Portrait"));
+ m_orientCB->setDefaultItem(ImageSelectionWidget::Landscape);
+ TQWhatsThis::add( m_orientCB, i18n("<p>Select constrained aspect ratio orientation."));
+
+ m_autoOrientation = new TQCheckBox(i18n("Auto"), cropSelection);
+ TQWhatsThis::add( m_autoOrientation, i18n("<p>Enable this option to automatically set the orientation."));
+
+ // -------------------------------------------------------------
+
+ m_customLabel1 = new TQLabel(i18n("Custom:"), cropSelection);
+ m_customLabel1->setAlignment(AlignLeft|AlignVCenter);
+ m_customRatioNInput = new RIntNumInput(cropSelection);
+ m_customRatioNInput->input()->setRange(1, 10000, 1, false);
+ m_customRatioNInput->setDefaultValue(1);
+ TQWhatsThis::add( m_customRatioNInput, i18n("<p>Set here the desired custom aspect numerator value."));
+
+ m_customLabel2 = new TQLabel(" : ", cropSelection);
+ m_customLabel2->setAlignment(AlignCenter|AlignVCenter);
+ m_customRatioDInput = new RIntNumInput(cropSelection);
+ m_customRatioDInput->input()->setRange(1, 10000, 1, false);
+ m_customRatioDInput->setDefaultValue(1);
+ TQWhatsThis::add( m_customRatioDInput, i18n("<p>Set here the desired custom aspect denominator value."));
+
+ // -------------------------------------------------------------
+
+ m_xInput = new RIntNumInput(cropSelection);
+ m_xInput->input()->setLabel(i18n("X:"), AlignLeft|AlignVCenter);
+ m_xInput->setRange(0, m_imageSelectionWidget->getOriginalImageWidth(), 1);
+ m_xInput->setDefaultValue(50);
+ TQWhatsThis::add( m_xInput, i18n("<p>Set here the top left selection corner position for cropping."));
+
+ m_widthInput = new RIntNumInput(cropSelection);
+ m_widthInput->input()->setLabel(i18n("Width:"), AlignLeft|AlignVCenter);
+ m_widthInput->setRange(m_imageSelectionWidget->getMinWidthRange(),
+ m_imageSelectionWidget->getMaxWidthRange(),
+ m_imageSelectionWidget->getWidthStep());
+ m_widthInput->setDefaultValue(800);
+ TQWhatsThis::add( m_widthInput, i18n("<p>Set here the width selection for cropping."));
+
+ m_centerWidth = new TQToolButton(cropSelection);
+ TDEGlobal::dirs()->addResourceType("centerwidth", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("centerwidth", "centerwidth.png");
+ m_centerWidth->setPixmap(TQPixmap(directory + "centerwidth.png"));
+ TQWhatsThis::add(m_centerWidth, i18n("<p>Set width position to center."));
+
+ // -------------------------------------------------------------
+
+ m_yInput = new RIntNumInput(cropSelection);
+ m_yInput->input()->setLabel(i18n("Y:"), AlignLeft | AlignVCenter);
+ m_yInput->setRange(0, m_imageSelectionWidget->getOriginalImageHeight(), 1);
+ m_yInput->setDefaultValue(50);
+ TQWhatsThis::add(m_yInput, i18n("<p>Set here the top left selection corner position for cropping."));
+
+ m_heightInput = new RIntNumInput(cropSelection);
+ m_heightInput->input()->setLabel(i18n("Height:"), AlignLeft | AlignVCenter);
+ m_heightInput->setRange(m_imageSelectionWidget->getMinHeightRange(),
+ m_imageSelectionWidget->getMaxHeightRange(),
+ m_imageSelectionWidget->getHeightStep());
+ m_heightInput->setDefaultValue(600);
+ TQWhatsThis::add( m_heightInput, i18n("<p>Set here the height selection for cropping."));
+
+ m_centerHeight = new TQToolButton(cropSelection);
+ TDEGlobal::dirs()->addResourceType("centerheight", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("centerheight", "centerheight.png");
+ m_centerHeight->setPixmap(TQPixmap(directory + "centerheight.png"));
+ TQWhatsThis::add(m_centerHeight, i18n("<p>Set height position to center."));
+
+ grid->addMultiCellWidget(label, 0, 0, 0, 0);
+ grid->addMultiCellWidget(m_ratioCB, 0, 0, 1, 3);
+ grid->addMultiCellWidget(m_preciseCrop, 0, 0, 4, 4);
+ grid->addMultiCellWidget(m_customLabel1, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_customRatioNInput, 1, 1, 1, 1);
+ grid->addMultiCellWidget(m_customLabel2, 1, 1, 2, 2);
+ grid->addMultiCellWidget(m_customRatioDInput, 1, 1, 3, 3);
+ grid->addMultiCellWidget(m_orientLabel, 2, 2, 0, 0);
+ grid->addMultiCellWidget(m_orientCB, 2, 2, 1, 3);
+ grid->addMultiCellWidget(m_autoOrientation, 2, 2, 4, 4);
+ grid->addMultiCellWidget(m_xInput, 3, 3, 0, 3);
+ grid->addMultiCellWidget(m_widthInput, 4, 4, 0, 3);
+ grid->addMultiCellWidget(m_centerWidth, 4, 4, 4, 4);
+ grid->addMultiCellWidget(m_yInput, 5, 5, 0, 3);
+ grid->addMultiCellWidget(m_heightInput, 6, 6, 0, 3);
+ grid->addMultiCellWidget(m_centerHeight, 6, 6, 4, 4);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ // -------------------------------------------------------------
+
+ TQFrame* compositionGuide = new TQFrame(m_gboxSettings->plainPage());
+ TQGridLayout* grid2 = new TQGridLayout(compositionGuide, 8, 3);
+ compositionGuide->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+
+ TQLabel *labelGuideLines = new TQLabel(i18n("Composition guide:"), compositionGuide);
+ m_guideLinesCB = new RComboBox(compositionGuide);
+ m_guideLinesCB->insertItem( i18n("Rules of Thirds"));
+ m_guideLinesCB->insertItem( i18n("Diagonal Method"));
+ m_guideLinesCB->insertItem( i18n("Harmonious Triangles"));
+ m_guideLinesCB->insertItem( i18n("Golden Mean"));
+ m_guideLinesCB->insertItem( i18n("None"));
+ m_guideLinesCB->setDefaultItem(ImageSelectionWidget::GuideNone);
+ TQWhatsThis::add( m_guideLinesCB, i18n("<p>With this option, you can display guide lines "
+ "which help you to compose your photograph."));
+
+ m_goldenSectionBox = new TQCheckBox(i18n("Golden sections"), compositionGuide);
+ TQWhatsThis::add( m_goldenSectionBox, i18n("<p>Enable this option to show golden sections."));
+
+ m_goldenSpiralSectionBox = new TQCheckBox(i18n("Golden spiral sections"), compositionGuide);
+ TQWhatsThis::add( m_goldenSpiralSectionBox, i18n("<p>Enable this option to show golden spiral sections."));
+
+ m_goldenSpiralBox = new TQCheckBox(i18n("Golden spiral"), compositionGuide);
+ TQWhatsThis::add( m_goldenSpiralBox, i18n("<p>Enable this option to show golden spiral guide."));
+
+ m_goldenTriangleBox = new TQCheckBox(i18n("Golden triangles"), compositionGuide);
+ TQWhatsThis::add( m_goldenTriangleBox, i18n("<p>Enable this option to show golden triangles."));
+
+ m_flipHorBox = new TQCheckBox(i18n("Flip horizontally"), compositionGuide);
+ TQWhatsThis::add( m_flipHorBox, i18n("<p>Enable this option to flip horizontally guidelines."));
+
+ m_flipVerBox = new TQCheckBox(i18n("Flip vertically"), compositionGuide);
+ TQWhatsThis::add( m_flipVerBox, i18n("<p>Enable this option to flip vertically guidelines."));
+
+ m_colorGuideLabel = new TQLabel(i18n("Color and width:"), compositionGuide);
+ m_guideColorBt = new KColorButton(TQColor(250, 250, 255), compositionGuide);
+ m_guideSize = new RIntNumInput(compositionGuide);
+ m_guideSize->input()->setRange(1, 5, 1, false);
+ m_guideSize->setDefaultValue(1);
+ TQWhatsThis::add( m_guideColorBt, i18n("<p>Set here the color used to draw composition guides."));
+ TQWhatsThis::add( m_guideSize, i18n("<p>Set here the width in pixels used to draw composition guides."));
+
+ grid2->addMultiCellWidget(labelGuideLines, 0, 0, 0, 0);
+ grid2->addMultiCellWidget(m_guideLinesCB, 0, 0, 1, 2);
+ grid2->addMultiCellWidget(m_goldenSectionBox, 1, 1, 0, 2);
+ grid2->addMultiCellWidget(m_goldenSpiralSectionBox, 2, 2, 0, 2);
+ grid2->addMultiCellWidget(m_goldenSpiralBox, 3, 3, 0, 2);
+ grid2->addMultiCellWidget(m_goldenTriangleBox, 4, 4, 0, 2);
+ grid2->addMultiCellWidget(m_flipHorBox, 5, 5, 0, 2);
+ grid2->addMultiCellWidget(m_flipVerBox, 6, 6, 0, 2);
+ grid2->addMultiCellWidget(m_colorGuideLabel, 7, 7, 0, 0);
+ grid2->addMultiCellWidget(m_guideColorBt, 7, 7, 1, 1);
+ grid2->addMultiCellWidget(m_guideSize, 7, 7, 2, 2);
+ grid2->setMargin(m_gboxSettings->spacingHint());
+ grid2->setSpacing(m_gboxSettings->spacingHint());
+
+
+ // -------------------------------------------------------------
+
+ gboxLayout->addMultiCellWidget(cropSelection, 0, 0, 0, 1);
+ gboxLayout->addMultiCellWidget(compositionGuide, 1, 1, 0, 1);
+ gboxLayout->setRowStretch(2, 10);
+ gboxLayout->setMargin(m_gboxSettings->spacingHint());
+ gboxLayout->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_ratioCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotRatioChanged(int)));
+
+ connect(m_preciseCrop, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotPreciseCropChanged(bool)));
+
+ connect(m_orientCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotOrientChanged(int)));
+
+ connect(m_autoOrientation, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotAutoOrientChanged(bool)));
+
+ connect(m_xInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotXChanged(int)));
+
+ connect(m_yInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotYChanged(int)));
+
+ connect(m_customRatioNInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotCustomNRatioChanged(int)));
+
+ connect(m_customRatioDInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotCustomDRatioChanged(int)));
+
+ connect(m_guideLinesCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotGuideTypeChanged(int)));
+
+ connect(m_goldenSectionBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_goldenSpiralSectionBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_goldenSpiralBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_goldenTriangleBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_flipHorBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_flipVerBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotGoldenGuideTypeChanged()));
+
+ connect(m_guideColorBt, TQ_SIGNAL(changed(const TQColor&)),
+ m_imageSelectionWidget, TQ_SLOT(slotChangeGuideColor(const TQColor&)));
+
+ connect(m_guideSize, TQ_SIGNAL(valueChanged(int)),
+ m_imageSelectionWidget, TQ_SLOT(slotChangeGuideSize(int)));
+
+ connect(m_widthInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotWidthChanged(int)));
+
+ connect(m_heightInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotHeightChanged(int)));
+
+ connect(m_imageSelectionWidget, TQ_SIGNAL(signalSelectionChanged(TQRect)),
+ this, TQ_SLOT(slotSelectionChanged(TQRect)));
+
+ connect(m_imageSelectionWidget, TQ_SIGNAL(signalSelectionMoved(TQRect)),
+ this, TQ_SLOT(slotSelectionChanged(TQRect)));
+
+ connect(m_imageSelectionWidget, TQ_SIGNAL(signalSelectionOrientationChanged(int)),
+ this, TQ_SLOT(slotSelectionOrientationChanged(int)));
+
+ connect(m_centerWidth, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotCenterWidth()));
+
+ connect(m_centerHeight, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotCenterHeight()));
+
+ // we need to disconnect the standard connection of the Try button first
+ disconnect(m_gboxSettings, TQ_SIGNAL(signalTryClicked()),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_gboxSettings, TQ_SIGNAL(signalTryClicked()),
+ this, TQ_SLOT(slotMaxAspectRatio()));
+
+ // -------------------------------------------------------------
+
+ // Sets current region selection
+ slotSelectionChanged(m_imageSelectionWidget->getRegionSelection());
+}
+
+RatioCropTool::~RatioCropTool()
+{
+}
+
+void RatioCropTool::readSettings()
+{
+ TQColor defaultGuideColor(250, 250, 255);
+ TDEConfig *config = kapp->config();
+ config->setGroup("aspectratiocrop Tool");
+
+ // No guide lines per default.
+ m_guideLinesCB->setCurrentItem(config->readNumEntry("Guide Lines Type", ImageSelectionWidget::GuideNone));
+ m_goldenSectionBox->setChecked(config->readBoolEntry("Golden Section", true));
+ m_goldenSpiralSectionBox->setChecked(config->readBoolEntry("Golden Spiral Section", false));
+ m_goldenSpiralBox->setChecked(config->readBoolEntry("Golden Spiral", false));
+ m_goldenTriangleBox->setChecked(config->readBoolEntry("Golden Triangle", false));
+ m_flipHorBox->setChecked(config->readBoolEntry("Golden Flip Horizontal", false));
+ m_flipVerBox->setChecked(config->readBoolEntry("Golden Flip Vertical", false));
+ m_guideColorBt->setColor(config->readColorEntry("Guide Color", &defaultGuideColor));
+ m_guideSize->setValue(config->readNumEntry("Guide Width", m_guideSize->defaultValue()));
+ m_imageSelectionWidget->slotGuideLines(m_guideLinesCB->currentItem());
+ m_imageSelectionWidget->slotChangeGuideColor(m_guideColorBt->color());
+
+ m_preciseCrop->setChecked(config->readBoolEntry("Precise Aspect Ratio Crop", false));
+ m_imageSelectionWidget->setPreciseCrop(m_preciseCrop->isChecked());
+
+ // Empty selection so it can be moved w/out size constraint
+ m_widthInput->setValue(0);
+ m_heightInput->setValue(0);
+
+ m_xInput->setValue(config->readNumEntry("Hor.Oriented Custom Aspect Ratio Xpos",
+ m_xInput->defaultValue()));
+ m_yInput->setValue(config->readNumEntry("Hor.Oriented Custom Aspect Ratio Ypos",
+ m_yInput->defaultValue()));
+
+ m_widthInput->setValue(config->readNumEntry("Hor.Oriented Custom Aspect Ratio Width",
+ m_widthInput->defaultValue()));
+ m_heightInput->setValue(config->readNumEntry("Hor.Oriented Custom Aspect Ratio Height",
+ m_heightInput->defaultValue()));
+
+ m_imageSelectionWidget->setSelectionOrientation(m_orientCB->currentItem());
+
+ m_customRatioNInput->setValue(config->readNumEntry("Hor.Oriented Custom Aspect Ratio Num",
+ m_customRatioNInput->defaultValue()));
+ m_customRatioDInput->setValue(config->readNumEntry("Hor.Oriented Custom Aspect Ratio Den",
+ m_customRatioDInput->defaultValue()));
+
+ m_ratioCB->setCurrentItem(config->readNumEntry("Hor.Oriented Aspect Ratio",
+ m_ratioCB->defaultItem()));
+
+ if (m_originalIsLandscape)
+ {
+ m_orientCB->setCurrentItem(config->readNumEntry("Hor.Oriented Aspect Ratio Orientation",
+ ImageSelectionWidget::Landscape));
+ m_orientCB->setDefaultItem(ImageSelectionWidget::Landscape);
+ }
+ else
+ {
+ m_orientCB->setCurrentItem(config->readNumEntry("Ver.Oriented Aspect Ratio Orientation",
+ ImageSelectionWidget::Portrait));
+ m_orientCB->setDefaultItem(ImageSelectionWidget::Portrait);
+ }
+
+ applyRatioChanges(m_ratioCB->currentItem());
+
+ m_autoOrientation->setChecked( config->readBoolEntry("Auto Orientation", false) );
+ slotAutoOrientChanged( m_autoOrientation->isChecked() );
+}
+
+void RatioCropTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("aspectratiocrop Tool");
+
+ if (m_originalIsLandscape)
+ {
+ config->writeEntry("Hor.Oriented Aspect Ratio", m_ratioCB->currentItem());
+ config->writeEntry("Hor.Oriented Aspect Ratio Orientation", m_orientCB->currentItem());
+ config->writeEntry("Hor.Oriented Custom Aspect Ratio Num", m_customRatioNInput->value());
+ config->writeEntry("Hor.Oriented Custom Aspect Ratio Den", m_customRatioDInput->value());
+
+ config->writeEntry("Hor.Oriented Custom Aspect Ratio Xpos", m_xInput->value());
+ config->writeEntry("Hor.Oriented Custom Aspect Ratio Ypos", m_yInput->value());
+ config->writeEntry("Hor.Oriented Custom Aspect Ratio Width", m_widthInput->value());
+ config->writeEntry("Hor.Oriented Custom Aspect Ratio Height", m_heightInput->value());
+ }
+ else
+ {
+ config->writeEntry("Ver.Oriented Aspect Ratio", m_ratioCB->currentItem());
+ config->writeEntry("Ver.Oriented Aspect Ratio Orientation", m_orientCB->currentItem());
+ config->writeEntry("Ver.Oriented Custom Aspect Ratio Num", m_customRatioNInput->value());
+ config->writeEntry("Ver.Oriented Custom Aspect Ratio Den", m_customRatioDInput->value());
+
+ config->writeEntry("Ver.Oriented Custom Aspect Ratio Xpos", m_xInput->value());
+ config->writeEntry("Ver.Oriented Custom Aspect Ratio Ypos", m_yInput->value());
+ config->writeEntry("Ver.Oriented Custom Aspect Ratio Width", m_widthInput->value());
+ config->writeEntry("Ver.Oriented Custom Aspect Ratio Height", m_heightInput->value());
+ }
+
+ config->writeEntry("Precise Aspect Ratio Crop", m_preciseCrop->isChecked());
+ config->writeEntry("Auto Orientation", m_autoOrientation->isChecked());
+ config->writeEntry("Guide Lines Type", m_guideLinesCB->currentItem());
+ config->writeEntry("Golden Section", m_goldenSectionBox->isChecked());
+ config->writeEntry("Golden Spiral Section", m_goldenSpiralSectionBox->isChecked());
+ config->writeEntry("Golden Spiral", m_goldenSpiralBox->isChecked());
+ config->writeEntry("Golden Triangle", m_goldenTriangleBox->isChecked());
+ config->writeEntry("Golden Flip Horizontal", m_flipHorBox->isChecked());
+ config->writeEntry("Golden Flip Vertical", m_flipVerBox->isChecked());
+ config->writeEntry("Guide Color", m_guideColorBt->color());
+ config->writeEntry("Guide Width", m_guideSize->value());
+ config->sync();
+}
+
+void RatioCropTool::slotResetSettings()
+{
+ m_imageSelectionWidget->resetSelection();
+}
+
+void RatioCropTool::slotMaxAspectRatio()
+{
+ m_imageSelectionWidget->maxAspectSelection();
+}
+
+void RatioCropTool::slotCenterWidth()
+{
+ m_imageSelectionWidget->setCenterSelection(ImageSelectionWidget::CenterWidth);
+}
+
+void RatioCropTool::slotCenterHeight()
+{
+ m_imageSelectionWidget->setCenterSelection(ImageSelectionWidget::CenterHeight);
+}
+
+void RatioCropTool::slotSelectionChanged(TQRect rect)
+{
+ m_xInput->blockSignals(true);
+ m_yInput->blockSignals(true);
+ m_widthInput->blockSignals(true);
+ m_heightInput->blockSignals(true);
+
+ m_xInput->setRange(0, m_imageSelectionWidget->getOriginalImageWidth() - rect.width(), 1);
+ m_yInput->setRange(0, m_imageSelectionWidget->getOriginalImageHeight() - rect.height(), 1);
+
+ m_widthInput->setRange(m_imageSelectionWidget->getMinWidthRange(),
+ m_imageSelectionWidget->getMaxWidthRange(),
+ m_imageSelectionWidget->getWidthStep());
+
+ m_heightInput->setRange(m_imageSelectionWidget->getMinHeightRange(),
+ m_imageSelectionWidget->getMaxHeightRange(),
+ m_imageSelectionWidget->getHeightStep());
+
+ m_xInput->setValue(rect.x());
+ m_yInput->setValue(rect.y());
+ m_widthInput->setValue(rect.width());
+ m_heightInput->setValue(rect.height());
+
+ m_gboxSettings->enableButton(EditorToolSettings::Ok, rect.isValid());
+ m_preciseCrop->setEnabled(m_imageSelectionWidget->preciseCropAvailable());
+
+ m_xInput->blockSignals(false);
+ m_yInput->blockSignals(false);
+ m_widthInput->blockSignals(false);
+ m_heightInput->blockSignals(false);
+}
+
+void RatioCropTool::setRatioCBText(int orientation)
+{
+ int item = m_ratioCB->currentItem();
+
+ m_ratioCB->blockSignals(true);
+ m_ratioCB->combo()->clear();
+ m_ratioCB->insertItem(i18n("Custom"));
+ m_ratioCB->insertItem("1:1");
+ if (orientation == ImageSelectionWidget::Landscape)
+ {
+ m_ratioCB->insertItem("3:2");
+ m_ratioCB->insertItem("4:3");
+ m_ratioCB->insertItem("5:4");
+ m_ratioCB->insertItem("7:5");
+ m_ratioCB->insertItem("10:7");
+ }
+ else
+ {
+ m_ratioCB->insertItem("2:3");
+ m_ratioCB->insertItem("3:4");
+ m_ratioCB->insertItem("4:5");
+ m_ratioCB->insertItem("5:7");
+ m_ratioCB->insertItem("7:10");
+ }
+ m_ratioCB->insertItem(i18n("Golden Ratio"));
+ m_ratioCB->insertItem(i18n("None"));
+ m_ratioCB->setCurrentItem(item);
+ m_ratioCB->blockSignals(false);
+}
+
+void RatioCropTool::slotSelectionOrientationChanged(int newOrientation)
+{
+ // Change text for Aspect ratio ComboBox
+
+ setRatioCBText(newOrientation);
+
+ // Change Orientation ComboBox
+
+ m_orientCB->setCurrentItem(newOrientation);
+
+ // Reverse custom values
+
+ if ( ( m_customRatioNInput->value() < m_customRatioDInput->value() &&
+ newOrientation == ImageSelectionWidget::Landscape) ||
+ ( m_customRatioNInput->value() > m_customRatioDInput->value() &&
+ newOrientation == ImageSelectionWidget::Portrait))
+ {
+ m_customRatioNInput->blockSignals(true);
+ m_customRatioDInput->blockSignals(true);
+
+ int tmp = m_customRatioNInput->value();
+ m_customRatioNInput->setValue(m_customRatioDInput->value());
+ m_customRatioDInput->setValue(tmp);
+
+ m_customRatioNInput->blockSignals(false);
+ m_customRatioDInput->blockSignals(false);
+ }
+}
+
+void RatioCropTool::slotXChanged(int x)
+{
+ m_imageSelectionWidget->setSelectionX(x);
+}
+
+void RatioCropTool::slotYChanged(int y)
+{
+ m_imageSelectionWidget->setSelectionY(y);
+}
+
+void RatioCropTool::slotWidthChanged(int w)
+{
+ m_imageSelectionWidget->setSelectionWidth(w);
+}
+
+void RatioCropTool::slotHeightChanged(int h)
+{
+ m_imageSelectionWidget->setSelectionHeight(h);
+}
+
+void RatioCropTool::slotPreciseCropChanged(bool a)
+{
+ m_imageSelectionWidget->setPreciseCrop(a);
+}
+
+void RatioCropTool::slotOrientChanged(int o)
+{
+ m_imageSelectionWidget->setSelectionOrientation(o);
+
+ // Reset selection area.
+ slotResetSettings();
+}
+
+void RatioCropTool::slotAutoOrientChanged(bool a)
+{
+ m_orientCB->setEnabled(!a /*|| m_ratioCB->currentItem() == ImageSelectionWidget::RATIONONE*/);
+ m_imageSelectionWidget->setAutoOrientation(a);
+}
+
+void RatioCropTool::slotRatioChanged(int a)
+{
+ applyRatioChanges(a);
+
+ // Reset selection area.
+ slotResetSettings();
+}
+
+void RatioCropTool::applyRatioChanges(int a)
+{
+ m_imageSelectionWidget->setSelectionAspectRatioType(a);
+
+ if (a == ImageSelectionWidget::RATIOCUSTOM)
+ {
+ m_customLabel1->setEnabled(true);
+ m_customLabel2->setEnabled(true);
+ m_customRatioNInput->setEnabled(true);
+ m_customRatioDInput->setEnabled(true);
+ m_orientLabel->setEnabled(true);
+ m_orientCB->setEnabled(!m_autoOrientation->isChecked());
+ m_autoOrientation->setEnabled(true);
+ slotCustomRatioChanged();
+ }
+ else if (a == ImageSelectionWidget::RATIONONE)
+ {
+ m_orientLabel->setEnabled(false);
+ m_orientCB->setEnabled(false);
+ m_autoOrientation->setEnabled(false);
+ m_customLabel1->setEnabled(false);
+ m_customLabel2->setEnabled(false);
+ m_customRatioNInput->setEnabled(false);
+ m_customRatioDInput->setEnabled(false);
+ }
+ else // Pre-config ratio selected.
+ {
+ m_orientLabel->setEnabled(true);
+ m_orientCB->setEnabled(!m_autoOrientation->isChecked());
+ m_autoOrientation->setEnabled(true);
+ m_customLabel1->setEnabled(false);
+ m_customLabel2->setEnabled(false);
+ m_customRatioNInput->setEnabled(false);
+ m_customRatioDInput->setEnabled(false);
+ }
+}
+
+void RatioCropTool::slotGuideTypeChanged(int t)
+{
+ if (t == ImageSelectionWidget::GuideNone)
+ {
+ m_goldenSectionBox->setEnabled(false);
+ m_goldenSpiralSectionBox->setEnabled(false);
+ m_goldenSpiralBox->setEnabled(false);
+ m_goldenTriangleBox->setEnabled(false);
+ m_flipHorBox->setEnabled(false);
+ m_flipVerBox->setEnabled(false);
+ m_colorGuideLabel->setEnabled(false);
+ m_guideColorBt->setEnabled(false);
+ m_guideSize->setEnabled(false);
+ }
+ else if (t == ImageSelectionWidget::RulesOfThirds)
+ {
+ m_goldenSectionBox->setEnabled(false);
+ m_goldenSpiralSectionBox->setEnabled(false);
+ m_goldenSpiralBox->setEnabled(false);
+ m_goldenTriangleBox->setEnabled(false);
+ m_flipHorBox->setEnabled(false);
+ m_flipVerBox->setEnabled(false);
+ m_colorGuideLabel->setEnabled(true);
+ m_guideColorBt->setEnabled(true);
+ m_guideSize->setEnabled(true);
+ }
+ else if (t == ImageSelectionWidget::DiagonalMethod)
+ {
+ m_goldenSectionBox->setEnabled(false);
+ m_goldenSpiralSectionBox->setEnabled(false);
+ m_goldenSpiralBox->setEnabled(false);
+ m_goldenTriangleBox->setEnabled(false);
+ m_flipHorBox->setEnabled(false);
+ m_flipVerBox->setEnabled(false);
+ m_colorGuideLabel->setEnabled(true);
+ m_guideColorBt->setEnabled(true);
+ m_guideSize->setEnabled(true);
+ }
+ else if (t == ImageSelectionWidget::HarmoniousTriangles)
+ {
+ m_goldenSectionBox->setEnabled(false);
+ m_goldenSpiralSectionBox->setEnabled(false);
+ m_goldenSpiralBox->setEnabled(false);
+ m_goldenTriangleBox->setEnabled(false);
+ m_flipHorBox->setEnabled(true);
+ m_flipVerBox->setEnabled(true);
+ m_colorGuideLabel->setEnabled(true);
+ m_guideColorBt->setEnabled(true);
+ m_guideSize->setEnabled(true);
+ }
+ else
+ {
+ m_goldenSectionBox->setEnabled(true);
+ m_goldenSpiralSectionBox->setEnabled(true);
+ m_goldenSpiralBox->setEnabled(true);
+ m_goldenTriangleBox->setEnabled(true);
+ m_flipHorBox->setEnabled(true);
+ m_flipVerBox->setEnabled(true);
+ m_colorGuideLabel->setEnabled(true);
+ m_guideColorBt->setEnabled(true);
+ m_guideSize->setEnabled(true);
+ }
+
+ m_imageSelectionWidget->setGoldenGuideTypes(m_goldenSectionBox->isChecked(),
+ m_goldenSpiralSectionBox->isChecked(),
+ m_goldenSpiralBox->isChecked(),
+ m_goldenTriangleBox->isChecked(),
+ m_flipHorBox->isChecked(),
+ m_flipVerBox->isChecked());
+ m_imageSelectionWidget->slotGuideLines(t);
+}
+
+void RatioCropTool::slotGoldenGuideTypeChanged()
+{
+ slotGuideTypeChanged(m_guideLinesCB->currentItem());
+}
+
+void RatioCropTool::slotCustomNRatioChanged(int a)
+{
+ if ( ! m_autoOrientation->isChecked() )
+ {
+ if ( ( m_orientCB->currentItem() == ImageSelectionWidget::Portrait &&
+ m_customRatioDInput->value() < a) ||
+ ( m_orientCB->currentItem() == ImageSelectionWidget::Landscape &&
+ m_customRatioDInput->value() > a))
+ {
+ m_customRatioDInput->blockSignals(true);
+ m_customRatioDInput->setValue(a);
+ m_customRatioDInput->blockSignals(false);
+ }
+ }
+
+ slotCustomRatioChanged();
+}
+
+void RatioCropTool::slotCustomDRatioChanged(int a)
+{
+ if ( ! m_autoOrientation->isChecked() )
+ {
+ if ( ( m_orientCB->currentItem() == ImageSelectionWidget::Landscape &&
+ m_customRatioNInput->value() < a) ||
+ ( m_orientCB->currentItem() == ImageSelectionWidget::Portrait &&
+ m_customRatioNInput->value() > a))
+ {
+ m_customRatioNInput->blockSignals(true);
+ m_customRatioNInput->setValue(a);
+ m_customRatioNInput->blockSignals(false);
+ }
+ }
+
+ slotCustomRatioChanged();
+}
+
+void RatioCropTool::slotCustomRatioChanged()
+{
+ m_imageSelectionWidget->setSelectionAspectRatioValue(m_customRatioNInput->value(),
+ m_customRatioDInput->value());
+
+ // Reset selection area.
+ slotResetSettings();
+}
+
+void RatioCropTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ TQRect currentRegion = m_imageSelectionWidget->getRegionSelection();
+ ImageIface* iface = m_imageSelectionWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool a = iface->originalHasAlpha();
+ bool sb = iface->originalSixteenBit();
+
+ TQRect normalizedRegion = currentRegion.normalize();
+ if (normalizedRegion.right() > w)
+ normalizedRegion.setRight(w);
+
+ if (normalizedRegion.bottom() > h)
+ normalizedRegion.setBottom(h);
+
+ DImg imOrg(w, h, sb, a, data);
+ delete [] data;
+ imOrg.crop(normalizedRegion);
+
+ iface->putOriginalImage(i18n("Aspect Ratio Crop"), imOrg.bits(), imOrg.width(), imOrg.height());
+
+ kapp->restoreOverrideCursor();
+ writeSettings();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/ratiocrop/ratiocroptool.h b/src/imageplugins/coreplugin/ratiocrop/ratiocroptool.h
new file mode 100644
index 00000000..833677da
--- /dev/null
+++ b/src/imageplugins/coreplugin/ratiocrop/ratiocroptool.h
@@ -0,0 +1,135 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-06
+ * Description : digiKam image editor Ratio Crop tool
+ *
+ * Copyright (C) 2007 by Jaromir Malenko <malenko at email dot cz>
+ * Copyright (C) 2008 by Roberto Castagnola <roberto dot castagnola at gmail dot com>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RATIOCROPTOOL_H
+#define RATIOCROPTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQCheckBox;
+class TQLabel;
+class TQToolButton;
+
+class KColorButton;
+
+namespace KDcrawIface
+{
+class RComboBox;
+class RIntNumInput;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageSelectionWidget;
+
+class RatioCropTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ RatioCropTool(TQObject *parent);
+ ~RatioCropTool();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+
+ void applyRatioChanges(int a);
+ void setRatioCBText(int orientation);
+
+private slots:
+
+ void slotMaxAspectRatio();
+ void slotResetSettings();
+
+ void slotCenterWidth();
+ void slotCenterHeight();
+ void slotXChanged(int x);
+ void slotYChanged(int y);
+ void slotWidthChanged(int w);
+ void slotHeightChanged(int h);
+ void slotCustomRatioChanged();
+ void slotCustomNRatioChanged(int a);
+ void slotCustomDRatioChanged(int a);
+ void slotPreciseCropChanged(bool a);
+ void slotOrientChanged(int o);
+ void slotAutoOrientChanged(bool a);
+ void slotRatioChanged(int a);
+ void slotSelectionChanged(TQRect rect );
+ void slotSelectionOrientationChanged(int);
+ void slotGuideTypeChanged(int t);
+ void slotGoldenGuideTypeChanged();
+
+private:
+
+ bool m_originalIsLandscape;
+
+ TQLabel *m_customLabel1;
+ TQLabel *m_customLabel2;
+ TQLabel *m_orientLabel;
+ TQLabel *m_colorGuideLabel;
+
+
+ TQToolButton *m_centerWidth;
+ TQToolButton *m_centerHeight;
+
+ TQCheckBox *m_goldenSectionBox;
+ TQCheckBox *m_goldenSpiralSectionBox;
+ TQCheckBox *m_goldenSpiralBox;
+ TQCheckBox *m_goldenTriangleBox;
+ TQCheckBox *m_flipHorBox;
+ TQCheckBox *m_flipVerBox;
+ TQCheckBox *m_autoOrientation;
+ TQCheckBox *m_preciseCrop;
+
+ KDcrawIface::RComboBox *m_guideLinesCB;
+ KDcrawIface::RComboBox *m_orientCB;
+ KDcrawIface::RComboBox *m_ratioCB;
+
+ KDcrawIface::RIntNumInput *m_customRatioDInput;
+ KDcrawIface::RIntNumInput *m_customRatioNInput;
+ KDcrawIface::RIntNumInput *m_guideSize;
+ KDcrawIface::RIntNumInput *m_heightInput;
+ KDcrawIface::RIntNumInput *m_widthInput;
+ KDcrawIface::RIntNumInput *m_xInput;
+ KDcrawIface::RIntNumInput *m_yInput;
+
+ KColorButton *m_guideColorBt;
+
+ ImageSelectionWidget *m_imageSelectionWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* RATIOCROPTOOL_H */
diff --git a/src/imageplugins/coreplugin/redeyetool.cpp b/src/imageplugins/coreplugin/redeyetool.cpp
new file mode 100644
index 00000000..5b5af7e7
--- /dev/null
+++ b/src/imageplugins/coreplugin/redeyetool.cpp
@@ -0,0 +1,587 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-06
+ * Description : Red eyes correction tool for image editor
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqhbox.h>
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kcolordialog.h>
+#include <kcolordialog.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// Digikam includes.
+
+#include "bcgmodifier.h"
+#include "colorgradientwidget.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "redeyetool.h"
+#include "redeyetool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+RedEyeTool::RedEyeTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("redeye");
+ setToolName(i18n("Red Eye"));
+ setToolIcon(SmallIcon("redeyes"));
+ setToolHelp("redeyecorrectiontool.anchor");
+
+ m_destinationPreviewData = 0;
+
+ m_previewWidget = new ImageWidget("redeye Tool", 0,
+ i18n("<p>Here you can see the image selection preview with "
+ "red eye reduction applied."),
+ true, ImageGuideWidget::PickColorMode, true, true);
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ EditorToolSettings *gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings->plainPage(), 11, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings->plainPage());
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, gboxSettings->plainPage());
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image channel values.<p>"
+ "<b>Green</b>: display the green image channel values.<p>"
+ "<b>Blue</b>: display the blue image channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximum counts are small, you can use the linear scale.<p>"
+ "The logarithmic scale can be used when the maximal counts are big "
+ "to show all values (small and large) on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap(TQPixmap(directory + "histogram-lin.png"));
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram "
+ "of the selected image channel. It is "
+ "updated upon setting changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox);
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ // -------------------------------------------------------------
+
+ m_thresholdLabel = new TQLabel(i18n("Sensitivity:"), gboxSettings->plainPage());
+ m_redThreshold = new RIntNumInput(gboxSettings->plainPage());
+ m_redThreshold->setRange(10, 90, 1);
+ m_redThreshold->setDefaultValue(20);
+ TQWhatsThis::add(m_redThreshold, i18n("<p>Sets the red color pixels selection threshold. "
+ "Low values will select more red color pixels (agressive correction), high "
+ "values less (mild correction). Use low value if eye have been selected "
+ "exactly. Use high value if other parts of the face are also selected."));
+
+ m_smoothLabel = new TQLabel(i18n("Smooth:"), gboxSettings->plainPage());
+ m_smoothLevel = new RIntNumInput(gboxSettings->plainPage());
+ m_smoothLevel->setRange(0, 5, 1);
+ m_smoothLevel->setDefaultValue(1);
+ TQWhatsThis::add(m_smoothLevel, i18n("<p>Sets the smoothness value when blurring the border "
+ "of the changed pixels. "
+ "This leads to a more naturally looking pupil."));
+
+ TQLabel *label3 = new TQLabel(i18n("Coloring Tint:"), gboxSettings->plainPage());
+ m_HSSelector = new KHSSelector(gboxSettings->plainPage());
+ m_VSelector = new KValueSelector(gboxSettings->plainPage());
+ m_HSSelector->setMinimumSize(200, 142);
+ m_VSelector->setMinimumSize(26, 142);
+ TQWhatsThis::add(m_HSSelector, i18n("<p>Sets a custom color to re-colorize the eyes."));
+
+ TQLabel *label4 = new TQLabel(i18n("Tint Level:"), gboxSettings->plainPage());
+ m_tintLevel = new RIntNumInput(gboxSettings->plainPage());
+ m_tintLevel->setRange(1, 200, 1);
+ m_tintLevel->setDefaultValue(128);
+ TQWhatsThis::add(m_tintLevel, i18n("<p>Set the tint level to adjust the luminosity of "
+ "the new color of the pupil."));
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+ gridSettings->addMultiCellWidget(m_thresholdLabel, 3, 3, 0, 4);
+ gridSettings->addMultiCellWidget(m_redThreshold, 4, 4, 0, 4);
+ gridSettings->addMultiCellWidget(m_smoothLabel, 5, 5, 0, 4);
+ gridSettings->addMultiCellWidget(m_smoothLevel, 6, 6, 0, 4);
+ gridSettings->addMultiCellWidget(label3, 7, 7, 0, 4);
+ gridSettings->addMultiCellWidget(m_HSSelector, 8, 8, 0, 3);
+ gridSettings->addMultiCellWidget(m_VSelector, 8, 8, 4, 4);
+ gridSettings->addMultiCellWidget(label4, 9, 9, 0, 4);
+ gridSettings->addMultiCellWidget(m_tintLevel, 10, 10, 0, 4);
+ gridSettings->setRowStretch(11, 10);
+ gridSettings->setColStretch(3, 10);
+
+ setToolSettings(gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromTarget(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_redThreshold, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_smoothLevel, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_HSSelector, TQ_SIGNAL(valueChanged(int, int)),
+ this, TQ_SLOT(slotHSChanged(int, int)));
+
+ connect(m_VSelector, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_tintLevel, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+RedEyeTool::~RedEyeTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void RedEyeTool::slotHSChanged(int h, int s)
+{
+ m_VSelector->blockSignals(true);
+ m_VSelector->setHue(h);
+ m_VSelector->setSaturation(s);
+ m_VSelector->updateContents();
+ m_VSelector->repaint(false);
+ m_VSelector->blockSignals(false);
+ slotTimer();
+}
+
+void RedEyeTool::slotChannelChanged(int channel)
+{
+ switch (channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("red"));
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("green"));
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("blue"));
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void RedEyeTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void RedEyeTool::slotColorSelectedFromTarget(const DColor& color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void RedEyeTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("redeye Tool");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ m_redThreshold->setValue(config->readNumEntry("RedThreshold", m_redThreshold->defaultValue()));
+ m_smoothLevel->setValue(config->readNumEntry("SmoothLevel", m_smoothLevel->defaultValue()));
+ m_HSSelector->setXValue(config->readNumEntry("HueColoringTint", 0));
+ m_HSSelector->setYValue(config->readNumEntry("SatColoringTint", 0));
+ m_VSelector->setValue(config->readNumEntry("ValColoringTint", 0));
+ m_tintLevel->setValue(config->readNumEntry("TintLevel", m_tintLevel->defaultValue()));
+
+ slotHSChanged(m_HSSelector->xValue(), m_HSSelector->yValue());
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void RedEyeTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("redeye Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("RedThreshold", m_redThreshold->value());
+ config->writeEntry("SmoothLevel", m_smoothLevel->value());
+ config->writeEntry("HueColoringTint", m_HSSelector->xValue());
+ config->writeEntry("SatColoringTint", m_HSSelector->yValue());
+ config->writeEntry("ValColoringTint", m_VSelector->value());
+ config->writeEntry("TintLevel", m_tintLevel->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void RedEyeTool::slotResetSettings()
+{
+ m_redThreshold->blockSignals(true);
+ m_HSSelector->blockSignals(true);
+ m_VSelector->blockSignals(true);
+ m_tintLevel->blockSignals(true);
+
+ m_redThreshold->slotReset();
+ m_smoothLevel->slotReset();
+
+ // Black color by default
+ m_HSSelector->setXValue(0);
+ m_HSSelector->setYValue(0);
+ m_VSelector->setValue(0);
+
+ m_tintLevel->slotReset();
+
+ m_redThreshold->blockSignals(false);
+ m_HSSelector->blockSignals(false);
+ m_VSelector->blockSignals(false);
+ m_tintLevel->blockSignals(false);
+}
+
+void RedEyeTool::slotEffect()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ // Here, we need to use the real selection image data because we will apply
+ // a Gaussian blur filter on pixels and we cannot use directly the preview scaled image
+ // else the blur radius will not give the same result between preview and final rendering.
+ ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getImageSelection();
+ int w = iface->selectedWidth();
+ int h = iface->selectedHeight();
+ bool sb = iface->originalSixteenBit();
+ bool a = iface->originalHasAlpha();
+ DImg selection(w, h, sb, a, m_destinationPreviewData);
+
+ redEyeFilter(selection);
+
+ DImg preview = selection.smoothScale(iface->previewWidth(), iface->previewHeight());
+
+ iface->putPreviewImage(preview.bits());
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, selection.bits(), selection.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void RedEyeTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getImageSelection();
+ int w = iface->selectedWidth();
+ int h = iface->selectedHeight();
+ bool sixteenBit = iface->originalSixteenBit();
+ bool hasAlpha = iface->originalHasAlpha();
+ DImg selection(w, h, sixteenBit, hasAlpha, data);
+ delete [] data;
+
+ redEyeFilter(selection);
+
+ iface->putImageSelection(i18n("Red Eyes Correction"), selection.bits());
+
+ kapp->restoreOverrideCursor();
+}
+
+void RedEyeTool::redEyeFilter(DImg& selection)
+{
+ DImg mask(selection.width(), selection.height(), selection.sixteenBit(), true,
+ selection.bits(), true);
+
+ selection = mask.copy();
+ float redThreshold = m_redThreshold->value()/10.0;
+ int hue = m_HSSelector->xValue();
+ int sat = m_HSSelector->yValue();
+ int val = m_VSelector->value();
+ KColor coloring;
+ coloring.setHsv(hue, sat, val);
+
+ struct channel
+ {
+ float red_gain;
+ float green_gain;
+ float blue_gain;
+ };
+
+ channel red_chan, green_chan, blue_chan;
+
+ red_chan.red_gain = 0.1;
+ red_chan.green_gain = 0.6;
+ red_chan.blue_gain = 0.3;
+
+ green_chan.red_gain = 0.0;
+ green_chan.green_gain = 1.0;
+ green_chan.blue_gain = 0.0;
+
+ blue_chan.red_gain = 0.0;
+ blue_chan.green_gain = 0.0;
+ blue_chan.blue_gain = 1.0;
+
+ float red_norm, green_norm, blue_norm;
+ int level = 201 - m_tintLevel->value();
+
+ red_norm = 1.0 / (red_chan.red_gain + red_chan.green_gain + red_chan.blue_gain);
+ green_norm = 1.0 / (green_chan.red_gain + green_chan.green_gain + green_chan.blue_gain);
+ blue_norm = 1.0 / (blue_chan.red_gain + blue_chan.green_gain + blue_chan.blue_gain);
+
+ red_norm *= coloring.red() / level;
+ green_norm *= coloring.green() / level;
+ blue_norm *= coloring.blue() / level;
+
+ // Perform a red color pixels detection in selection image and create a correction mask using an alpha channel.
+
+ if (!selection.sixteenBit()) // 8 bits image.
+ {
+ uchar* ptr = selection.bits();
+ uchar* mptr = mask.bits();
+ uchar r, g, b, r1, g1, b1;
+
+ for (uint i = 0 ; i < selection.width() * selection.height() ; i++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ mptr[3] = 255;
+
+ if (r >= ( redThreshold * g))
+ {
+ r1 = TQMIN(255, (int)(red_norm * (red_chan.red_gain * r +
+ red_chan.green_gain * g +
+ red_chan.blue_gain * b)));
+
+ g1 = TQMIN(255, (int)(green_norm * (green_chan.red_gain * r +
+ green_chan.green_gain * g +
+ green_chan.blue_gain * b)));
+
+ b1 = TQMIN(255, (int)(blue_norm * (blue_chan.red_gain * r +
+ blue_chan.green_gain * g +
+ blue_chan.blue_gain * b)));
+
+ mptr[0] = b1;
+ mptr[1] = g1;
+ mptr[2] = r1;
+ mptr[3] = TQMIN( (int)((r-g) / 150.0 * 255.0), 255);
+ }
+
+ ptr += 4;
+ mptr+= 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short* ptr = (unsigned short*)selection.bits();
+ unsigned short* mptr = (unsigned short*)mask.bits();
+ unsigned short r, g, b, r1, g1, b1;
+
+ for (uint i = 0 ; i < selection.width() * selection.height() ; i++)
+ {
+ b = ptr[0];
+ g = ptr[1];
+ r = ptr[2];
+ mptr[3] = 65535;
+
+ if (r >= ( redThreshold * g))
+ {
+ r1 = TQMIN(65535, (int)(red_norm * (red_chan.red_gain * r +
+ red_chan.green_gain * g +
+ red_chan.blue_gain * b)));
+
+ g1 = TQMIN(65535, (int)(green_norm * (green_chan.red_gain * r +
+ green_chan.green_gain * g +
+ green_chan.blue_gain * b)));
+
+ b1 = TQMIN(65535, (int)(blue_norm * (blue_chan.red_gain * r +
+ blue_chan.green_gain * g +
+ blue_chan.blue_gain * b)));
+
+ mptr[0] = b1;
+ mptr[1] = g1;
+ mptr[2] = r1;
+ mptr[3] = TQMIN( (int)((r-g) / 38400.0 * 65535.0), 65535);;
+ }
+
+ ptr += 4;
+ mptr+= 4;
+ }
+ }
+
+ // Now, we will blur only the transparency pixels from the mask.
+
+ DImg mask2 = mask.copy();
+ DImgImageFilters filter;
+ filter.gaussianBlurImage(mask2.bits(), mask2.width(), mask2.height(),
+ mask2.sixteenBit(), m_smoothLevel->value());
+
+ if (!selection.sixteenBit()) // 8 bits image.
+ {
+ uchar* mptr = mask.bits();
+ uchar* mptr2 = mask2.bits();
+
+ for (uint i = 0 ; i < mask2.width() * mask2.height() ; i++)
+ {
+ if (mptr2[3] < 255)
+ {
+ mptr[0] = mptr2[0];
+ mptr[1] = mptr2[1];
+ mptr[2] = mptr2[2];
+ mptr[3] = mptr2[3];
+ }
+
+ mptr += 4;
+ mptr2+= 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short* mptr = (unsigned short*)mask.bits();
+ unsigned short* mptr2 = (unsigned short*)mask2.bits();
+
+ for (uint i = 0 ; i < mask2.width() * mask2.height() ; i++)
+ {
+ if (mptr2[3] < 65535)
+ {
+ mptr[0] = mptr2[0];
+ mptr[1] = mptr2[1];
+ mptr[2] = mptr2[2];
+ mptr[3] = mptr2[3];
+ }
+
+ mptr += 4;
+ mptr2+= 4;
+ }
+ }
+
+ // - Perform pixels blending using alpha channel between the mask and the selection.
+
+ DColorComposer *composer = DColorComposer::getComposer(DColorComposer::PorterDuffSrcOver);
+
+ // NOTE: 'mask' is the Source image, 'selection' is the Destination image.
+
+ selection.bitBlendImage(composer, &mask,
+ 0, 0, mask.width(), mask.height(),
+ 0, 0);
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/redeyetool.h b/src/imageplugins/coreplugin/redeyetool.h
new file mode 100644
index 00000000..eb614fd3
--- /dev/null
+++ b/src/imageplugins/coreplugin/redeyetool.h
@@ -0,0 +1,157 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-06
+ * Description : Red eyes correction tool for image editor
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef REDEYETOOL_H
+#define REDEYETOOL_H
+
+// KDE includes.
+
+#include <kpassivepopup.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQLabel;
+class TQComboBox;
+class TQHButtonGroup;
+
+class KHSSelector;
+class KValueSelector;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class DImg;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class RedEyePassivePopup : public KPassivePopup
+{
+public:
+
+ RedEyePassivePopup(TQWidget* parent)
+ : KPassivePopup(parent), m_parent(parent)
+ {
+ }
+
+protected:
+
+ virtual void positionSelf()
+ {
+ move(m_parent->x() + 30, m_parent->y() + 30);
+ }
+
+private:
+
+ TQWidget* m_parent;
+};
+
+// ----------------------------------------------------------------
+
+class RedEyeTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ RedEyeTool(TQObject *parent);
+ ~RedEyeTool();
+
+private slots:
+
+ void slotEffect();
+ void slotResetSettings();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+ void slotHSChanged(int h, int s);
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+ void redEyeFilter(Digikam::DImg& selection);
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum RedThresold
+ {
+ Mild=0,
+ Aggressive
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQLabel *m_thresholdLabel;
+ TQLabel *m_smoothLabel;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KDcrawIface::RIntNumInput *m_tintLevel;
+ KDcrawIface::RIntNumInput *m_redThreshold;
+ KDcrawIface::RIntNumInput *m_smoothLevel;
+
+ KHSSelector *m_HSSelector;
+ KValueSelector *m_VSelector;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* REDEYETOOL_H */
diff --git a/src/imageplugins/coreplugin/rgbtool.cpp b/src/imageplugins/coreplugin/rgbtool.cpp
new file mode 100644
index 00000000..e27e396e
--- /dev/null
+++ b/src/imageplugins/coreplugin/rgbtool.cpp
@@ -0,0 +1,440 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-11
+ * Description : digiKam image editor Color Balance tool.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqframe.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqslider.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Digikam includes.
+
+#include "colorgradientwidget.h"
+#include "colormodifier.h"
+#include "dimg.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+
+// Local includes.
+
+#include "rgbtool.h"
+#include "rgbtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+RGBTool::RGBTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("colorbalance");
+ setToolName(i18n("Color Balance"));
+ setToolIcon(SmallIcon("adjustrgb"));
+
+ m_destinationPreviewData = 0;
+
+ m_previewWidget = new ImageWidget("colorbalance Tool", 0,
+ i18n("<p>Here you can see the image "
+ "color-balance adjustments preview. "
+ "You can pick color on image "
+ "to see the color level corresponding on histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout(m_gboxSettings->plainPage(), 7, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), m_gboxSettings->plainPage());
+ label1->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_channelCB = new TQComboBox(false, m_gboxSettings->plainPage());
+ m_channelCB->insertItem(i18n("Luminosity"));
+ m_channelCB->insertItem(i18n("Red"));
+ m_channelCB->insertItem(i18n("Green"));
+ m_channelCB->insertItem(i18n("Blue"));
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin(0);
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(linHistoButton, i18n("<p>Linear"));
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap(TQPixmap(directory + "histogram-lin.png"));
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton(m_scaleBG);
+ TQToolTip::add(logHistoButton, i18n("<p>Logarithmic"));
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap(TQPixmap(directory + "histogram-log.png"));
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(m_gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox);
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQLabel *labelLeft = new TQLabel(i18n("Cyan"), m_gboxSettings->plainPage());
+ labelLeft->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_rSlider = new TQSlider(-100, 100, 1, 0, TQt::Horizontal, m_gboxSettings->plainPage(), "m_rSlider");
+ m_rSlider->setTickmarks(TQSlider::Below);
+ m_rSlider->setTickInterval(20);
+ TQWhatsThis::add( m_rSlider, i18n("<p>Set here the cyan/red color adjustment of the image."));
+ TQLabel *labelRight = new TQLabel(i18n("Red"), m_gboxSettings->plainPage());
+ labelRight->setAlignment ( TQt::AlignLeft | TQt::AlignVCenter );
+ m_rInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_rInput->setDefaultValue(0);
+ m_rInput->input()->setRange(-100, 100, 1, false);
+
+ gridSettings->addMultiCellWidget(labelLeft, 3, 3, 0, 0);
+ gridSettings->addMultiCellWidget(m_rSlider, 3, 3, 1, 1);
+ gridSettings->addMultiCellWidget(labelRight, 3, 3, 2, 2);
+ gridSettings->addMultiCellWidget(m_rInput, 3, 3, 3, 3);
+
+ // -------------------------------------------------------------
+
+ labelLeft = new TQLabel(i18n("Magenta"), m_gboxSettings->plainPage());
+ labelLeft->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+ m_gSlider = new TQSlider(-100, 100, 1, 0, TQt::Horizontal, m_gboxSettings->plainPage(), "m_gSlider");
+ m_gSlider->setTickmarks(TQSlider::Below);
+ m_gSlider->setTickInterval(20);
+ TQWhatsThis::add( m_gSlider, i18n("<p>Set here the magenta/green color adjustment of the image."));
+ labelRight = new TQLabel(i18n("Green"), m_gboxSettings->plainPage());
+ labelRight->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ m_gInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_gInput->setDefaultValue(0);
+ m_gInput->input()->setRange(-100, 100, 1, false);
+
+ gridSettings->addMultiCellWidget(labelLeft, 4, 4, 0, 0);
+ gridSettings->addMultiCellWidget(m_gSlider, 4, 4, 1, 1);
+ gridSettings->addMultiCellWidget(labelRight, 4, 4, 2, 2);
+ gridSettings->addMultiCellWidget(m_gInput, 4, 4, 3, 3);
+
+ // -------------------------------------------------------------
+
+ labelLeft = new TQLabel(i18n("Yellow"), m_gboxSettings->plainPage());
+ labelLeft->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_bSlider = new TQSlider(-100, 100, 1, 0, TQt::Horizontal, m_gboxSettings->plainPage(), "m_bSlider");
+ m_bSlider->setTickmarks(TQSlider::Below);
+ m_bSlider->setTickInterval(20);
+ TQWhatsThis::add( m_bSlider, i18n("<p>Set here the yellow/blue color adjustment of the image."));
+ labelRight = new TQLabel(i18n("Blue"), m_gboxSettings->plainPage());
+ labelRight->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ m_bInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_bInput->setDefaultValue(0);
+ m_bInput->input()->setRange(-100, 100, 1, false);
+
+ gridSettings->addMultiCellWidget(labelLeft, 5, 5, 0, 0);
+ gridSettings->addMultiCellWidget(m_bSlider, 5, 5, 1, 1);
+ gridSettings->addMultiCellWidget(labelRight, 5, 5, 2, 2);
+ gridSettings->addMultiCellWidget(m_bInput, 5, 5, 3, 3);
+
+ m_rInput->setValue(0);
+ m_gInput->setValue(0);
+ m_bInput->setValue(0);
+
+ gridSettings->setRowStretch(6, 10);
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_rSlider, TQ_SIGNAL(valueChanged(int)),
+ m_rInput, TQ_SLOT(setValue(int)));
+ connect(m_rInput, TQ_SIGNAL(valueChanged (int)),
+ m_rSlider, TQ_SLOT(setValue(int)));
+ connect(m_rInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gSlider, TQ_SIGNAL(valueChanged(int)),
+ m_gInput, TQ_SLOT(setValue(int)));
+ connect(m_gInput, TQ_SIGNAL(valueChanged (int)),
+ m_gSlider, TQ_SLOT(setValue(int)));
+ connect(m_gInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_bSlider, TQ_SIGNAL(valueChanged(int)),
+ m_bInput, TQ_SLOT(setValue(int)));
+ connect(m_bInput, TQ_SIGNAL(valueChanged (int)),
+ m_bSlider, TQ_SLOT(setValue(int)));
+ connect(m_bInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings->enableButton(EditorToolSettings::Ok, false);
+}
+
+RGBTool::~RGBTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void RGBTool::slotChannelChanged(int channel)
+{
+ switch (channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("white"));
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("red"));
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("green"));
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors(TQColor("black"), TQColor("blue"));
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void RGBTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void RGBTool::slotColorSelectedFromTarget(const DColor &color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void RGBTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("colorbalance Tool");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ int r = config->readNumEntry("RedAjustment", m_rInput->defaultValue());
+ int g = config->readNumEntry("GreenAjustment", m_gInput->defaultValue());
+ int b = config->readNumEntry("BlueAjustment", m_bInput->defaultValue());
+ adjustSliders(r, g, b);
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void RGBTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("colorbalance Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+ config->writeEntry("RedAjustment", m_rSlider->value());
+ config->writeEntry("GreenAjustment", m_gInput->value());
+ config->writeEntry("BlueAjustment", m_bInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void RGBTool::slotResetSettings()
+{
+ int r = m_rInput->defaultValue();
+ int g = m_gInput->defaultValue();
+ int b = m_bInput->defaultValue();
+
+ adjustSliders(r, g, b);
+}
+
+void RGBTool::adjustSliders(int r, int g, int b)
+{
+ m_rSlider->blockSignals(true);
+ m_gSlider->blockSignals(true);
+ m_bSlider->blockSignals(true);
+ m_rInput->blockSignals(true);
+ m_gInput->blockSignals(true);
+ m_bInput->blockSignals(true);
+
+ m_rSlider->setValue(r);
+ m_gSlider->setValue(g);
+ m_bSlider->setValue(b);
+ m_rInput->setValue(r);
+ m_gInput->setValue(g);
+ m_bInput->setValue(b);
+
+ m_rSlider->blockSignals(false);
+ m_gSlider->blockSignals(false);
+ m_bSlider->blockSignals(false);
+ m_rInput->blockSignals(false);
+ m_gInput->blockSignals(false);
+ m_bInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void RGBTool::slotEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ m_gboxSettings->enableButton(EditorToolSettings::Ok,
+ (m_rInput->value() != 0 ||
+ m_gInput->value() != 0 ||
+ m_bInput->value() != 0));
+
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ m_destinationPreviewData = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool alpha = iface->previewHasAlpha();
+ bool sixteenBit = iface->previewSixteenBit();
+
+ double r = ((double) m_rInput->value() + 100.0) / 100.0;
+ double g = ((double) m_gInput->value() + 100.0) / 100.0;
+ double b = ((double) m_bInput->value() + 100.0) / 100.0;
+ double a = 1.0;
+
+ DImg preview(w, h, sixteenBit, alpha, m_destinationPreviewData);
+ ColorModifier cmod;
+ cmod.applyColorModifier(preview, r, g, b, a);
+ iface->putPreviewImage(preview.bits());
+
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+
+ memcpy(m_destinationPreviewData, preview.bits(), preview.numBytes());
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sixteenBit, 0, 0, 0, false);
+
+ kapp->restoreOverrideCursor();
+}
+
+void RGBTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ double r = ((double) m_rInput->value() + 100.0) / 100.0;
+ double g = ((double) m_gInput->value() + 100.0) / 100.0;
+ double b = ((double) m_bInput->value() + 100.0) / 100.0;
+ double a = 1.0;
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool alpha = iface->originalHasAlpha();
+ bool sixteenBit = iface->originalSixteenBit();
+ DImg original(w, h, sixteenBit, alpha, data);
+ delete [] data;
+
+ ColorModifier cmod;
+ cmod.applyColorModifier(original, r, g, b, a);
+
+ iface->putOriginalImage(i18n("Color Balance"), original.bits());
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/rgbtool.h b/src/imageplugins/coreplugin/rgbtool.h
new file mode 100644
index 00000000..dadb6a0e
--- /dev/null
+++ b/src/imageplugins/coreplugin/rgbtool.h
@@ -0,0 +1,119 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-11
+ * Description : digiKam image editor Color Balance tool.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RGBTOOL_H
+#define RGBTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQComboBox;
+class TQHButtonGroup;
+
+class TQSlider;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class RGBTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ RGBTool(TQObject *parent);
+ ~RGBTool();
+
+private:
+
+ void writeSettings();
+ void readSettings();
+ void adjustSliders(int r, int g, int b);
+ void finalRendering();
+
+private slots:
+
+ void slotEffect();
+ void slotResetSettings();
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorSelectedFromTarget( const Digikam::DColor &color );
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ uchar *m_destinationPreviewData;
+
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ KDcrawIface::RIntNumInput *m_rInput;
+ KDcrawIface::RIntNumInput *m_gInput;
+ KDcrawIface::RIntNumInput *m_bInput;
+
+ TQSlider *m_rSlider;
+ TQSlider *m_gSlider;
+ TQSlider *m_bSlider;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* RGBTOOL_H */
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/Makefile.am b/src/imageplugins/coreplugin/sharpnesseditor/Makefile.am
new file mode 100644
index 00000000..c02087ee
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/Makefile.am
@@ -0,0 +1,32 @@
+SUBDIRS = clapack
+COMPILE_FIRST = clapack
+
+noinst_LTLIBRARIES = libsharpnesseditor.la
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/imageplugins/coreplugin/sharpnesseditor/clapack \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+libsharpnesseditor_la_LIBADD = $(top_builddir)/src/imageplugins/coreplugin/sharpnesseditor/clapack/liblapack.la
+
+libsharpnesseditor_la_SOURCES = sharpentool.cpp unsharp.cpp matrix.cpp refocus.cpp
+
+libsharpnesseditor_la_LDFLAGS = $(all_libraries)
+
+noinst_HEADERS = sharpentool.h unsharp.h matrix.h refocus.h
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/LICENCE b/src/imageplugins/coreplugin/sharpnesseditor/clapack/LICENCE
new file mode 100644
index 00000000..a338f860
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/LICENCE
@@ -0,0 +1,12 @@
+REDISTRIBUTABLE
+
+LAPACK is a freely-available software package. It is available from netlib via anonymous ftp and the World Wide Web.
+Thus, it can be included in commercial software packages (and has been). We only ask that proper credit be given to the authors.
+
+Like all software, it is copyrighted. It is not trademarked, but we do ask the following:
+
+If you modify the source for these routines we ask that you change the name of the routine
+and comment the changes made to the original.
+
+We will gladly answer any questions regarding the software. If a modification is done, however,
+it is the responsibility of the person who modified the routine to provide support.
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/Makefile.am b/src/imageplugins/coreplugin/sharpnesseditor/clapack/Makefile.am
new file mode 100644
index 00000000..bf478556
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/Makefile.am
@@ -0,0 +1,7 @@
+noinst_LTLIBRARIES = liblapack.la
+
+liblapack_la_CFLAGS = -w
+
+noinst_HEADERS = blaswrap.h clapack.h f2c.h fio.h fmt.h fp.h
+
+liblapack_la_SOURCES = abort_.c dgesv.c dlaswp.c endfile.c idamax.c open.c sig_die.c wref.c close.c dgetf2.c dscal.c err.c ieeeck.c s_cmp.c s_stop.c wrtfmt.c dgemm.c dgetrf.c dswap.c fmt.c ilaenv.c s_copy.c wsfe.c dger.c dgetrs.c dtrsm.c fmtlib.c lsame.c sfe.c util.c xerbla.c
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/README b/src/imageplugins/coreplugin/sharpnesseditor/clapack/README
new file mode 100644
index 00000000..530f73ee
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/README
@@ -0,0 +1,2 @@
+The sources in this directory were copied from the CLAPACK
+distribution (see http://www.netlib.org/clapack).
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/abort_.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/abort_.c
new file mode 100644
index 00000000..67278e93
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/abort_.c
@@ -0,0 +1,10 @@
+#include "stdio.h"
+#include "f2c.h"
+
+extern void sig_die(char*,int);
+
+int abort_(void)
+{
+sig_die("Fortran abort routine called", 1);
+return 0; /* not reached */
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/blaswrap.h b/src/imageplugins/coreplugin/sharpnesseditor/clapack/blaswrap.h
new file mode 100644
index 00000000..84c08d30
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/blaswrap.h
@@ -0,0 +1,158 @@
+/* CLAPACK 3.0 BLAS wrapper macros
+ * Feb 5, 2000
+ */
+
+#ifndef __BLASWRAP_H
+#define __BLASWRAP_H
+
+#ifndef NO_BLAS_WRAP
+
+/* BLAS1 routines */
+#define srotg_ f2c_srotg
+#define drotg_ f2c_drotg
+#define srotmg_ f2c_srotmg
+#define drotmg_ f2c_drotmg
+#define srot_ f2c_srot
+#define drot_ f2c_drot
+#define srotm_ f2c_srotm
+#define drotm_ f2c_drotm
+#define sswap_ f2c_sswap
+#define dswap_ f2c_dswap
+#define cswap_ f2c_cswap
+#define zswap_ f2c_zswap
+#define sscal_ f2c_sscal
+#define dscal_ f2c_dscal
+#define cscal_ f2c_cscal
+#define zscal_ f2c_zscal
+#define csscal_ f2c_csscal
+#define zdscal_ f2c_zdscal
+#define scopy_ f2c_scopy
+#define dcopy_ f2c_dcopy
+#define ccopy_ f2c_ccopy
+#define zcopy_ f2c_zcopy
+#define saxpy_ f2c_saxpy
+#define daxpy_ f2c_daxpy
+#define caxpy_ f2c_caxpy
+#define zaxpy_ f2c_zaxpy
+#define sdot_ f2c_sdot
+#define ddot_ f2c_ddot
+#define cdotu_ f2c_cdotu
+#define zdotu_ f2c_zdotu
+#define cdotc_ f2c_cdotc
+#define zdotc_ f2c_zdotc
+#define snrm2_ f2c_snrm2
+#define dnrm2_ f2c_dnrm2
+#define scnrm2_ f2c_scnrm2
+#define dznrm2_ f2c_dznrm2
+#define sasum_ f2c_sasum
+#define dasum_ f2c_dasum
+#define scasum_ f2c_scasum
+#define dzasum_ f2c_dzasum
+#define isamax_ f2c_isamax
+#define idamax_ f2c_idamax
+#define icamax_ f2c_icamax
+#define izamax_ f2c_izamax
+
+/* BLAS2 routines */
+#define sgemv_ f2c_sgemv
+#define dgemv_ f2c_dgemv
+#define cgemv_ f2c_cgemv
+#define zgemv_ f2c_zgemv
+#define sgbmv_ f2c_sgbmv
+#define dgbmv_ f2c_dgbmv
+#define cgbmv_ f2c_cgbmv
+#define zgbmv_ f2c_zgbmv
+#define chemv_ f2c_chemv
+#define zhemv_ f2c_zhemv
+#define chbmv_ f2c_chbmv
+#define zhbmv_ f2c_zhbmv
+#define chpmv_ f2c_chpmv
+#define zhpmv_ f2c_zhpmv
+#define ssymv_ f2c_ssymv
+#define dsymv_ f2c_dsymv
+#define ssbmv_ f2c_ssbmv
+#define dsbmv_ f2c_dsbmv
+#define sspmv_ f2c_sspmv
+#define dspmv_ f2c_dspmv
+#define strmv_ f2c_strmv
+#define dtrmv_ f2c_dtrmv
+#define ctrmv_ f2c_ctrmv
+#define ztrmv_ f2c_ztrmv
+#define stbmv_ f2c_stbmv
+#define dtbmv_ f2c_dtbmv
+#define ctbmv_ f2c_ctbmv
+#define ztbmv_ f2c_ztbmv
+#define stpmv_ f2c_stpmv
+#define dtpmv_ f2c_dtpmv
+#define ctpmv_ f2c_ctpmv
+#define ztpmv_ f2c_ztpmv
+#define strsv_ f2c_strsv
+#define dtrsv_ f2c_dtrsv
+#define ctrsv_ f2c_ctrsv
+#define ztrsv_ f2c_ztrsv
+#define stbsv_ f2c_stbsv
+#define dtbsv_ f2c_dtbsv
+#define ctbsv_ f2c_ctbsv
+#define ztbsv_ f2c_ztbsv
+#define stpsv_ f2c_stpsv
+#define dtpsv_ f2c_dtpsv
+#define ctpsv_ f2c_ctpsv
+#define ztpsv_ f2c_ztpsv
+#define sger_ f2c_sger
+#define dger_ f2c_dger
+#define cgeru_ f2c_cgeru
+#define zgeru_ f2c_zgeru
+#define cgerc_ f2c_cgerc
+#define zgerc_ f2c_zgerc
+#define cher_ f2c_cher
+#define zher_ f2c_zher
+#define chpr_ f2c_chpr
+#define zhpr_ f2c_zhpr
+#define cher2_ f2c_cher2
+#define zher2_ f2c_zher2
+#define chpr2_ f2c_chpr2
+#define zhpr2_ f2c_zhpr2
+#define ssyr_ f2c_ssyr
+#define dsyr_ f2c_dsyr
+#define sspr_ f2c_sspr
+#define dspr_ f2c_dspr
+#define ssyr2_ f2c_ssyr2
+#define dsyr2_ f2c_dsyr2
+#define sspr2_ f2c_sspr2
+#define dspr2_ f2c_dspr2
+
+/* BLAS3 routines */
+#define sgemm_ f2c_sgemm
+#define dgemm_ f2c_dgemm
+#define cgemm_ f2c_cgemm
+#define zgemm_ f2c_zgemm
+#define ssymm_ f2c_ssymm
+#define dsymm_ f2c_dsymm
+#define csymm_ f2c_csymm
+#define zsymm_ f2c_zsymm
+#define chemm_ f2c_chemm
+#define zhemm_ f2c_zhemm
+#define ssyrk_ f2c_ssyrk
+#define dsyrk_ f2c_dsyrk
+#define csyrk_ f2c_csyrk
+#define zsyrk_ f2c_zsyrk
+#define cherk_ f2c_cherk
+#define zherk_ f2c_zherk
+#define ssyr2k_ f2c_ssyr2k
+#define dsyr2k_ f2c_dsyr2k
+#define csyr2k_ f2c_csyr2k
+#define zsyr2k_ f2c_zsyr2k
+#define cher2k_ f2c_cher2k
+#define zher2k_ f2c_zher2k
+#define strmm_ f2c_strmm
+#define dtrmm_ f2c_dtrmm
+#define ctrmm_ f2c_ctrmm
+#define ztrmm_ f2c_ztrmm
+#define strsm_ f2c_strsm
+#define dtrsm_ f2c_dtrsm
+#define ctrsm_ f2c_ctrsm
+#define ztrsm_ f2c_ztrsm
+
+#endif /* NO_BLAS_WRAP */
+
+#endif /* __BLASWRAP_H */
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/clapack.h b/src/imageplugins/coreplugin/sharpnesseditor/clapack/clapack.h
new file mode 100644
index 00000000..cad9a4c2
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/clapack.h
@@ -0,0 +1,5079 @@
+#ifndef __CLAPACK_H
+#define __CLAPACK_H
+
+/* Subroutine */ int cbdsqr_(char *uplo, integer *n, integer *ncvt, integer *
+ nru, integer *ncc, real *d__, real *e, complex *vt, integer *ldvt,
+ complex *u, integer *ldu, complex *c__, integer *ldc, real *rwork,
+ integer *info);
+
+/* Subroutine */ int cgbbrd_(char *vect, integer *m, integer *n, integer *ncc,
+ integer *kl, integer *ku, complex *ab, integer *ldab, real *d__,
+ real *e, complex *q, integer *ldq, complex *pt, integer *ldpt,
+ complex *c__, integer *ldc, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgbcon_(char *norm, integer *n, integer *kl, integer *ku,
+ complex *ab, integer *ldab, integer *ipiv, real *anorm, real *rcond,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgbequ_(integer *m, integer *n, integer *kl, integer *ku,
+ complex *ab, integer *ldab, real *r__, real *c__, real *rowcnd, real
+ *colcnd, real *amax, integer *info);
+
+/* Subroutine */ int cgbrfs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, complex *ab, integer *ldab, complex *afb, integer *
+ ldafb, integer *ipiv, complex *b, integer *ldb, complex *x, integer *
+ ldx, real *ferr, real *berr, complex *work, real *rwork, integer *
+ info);
+
+/* Subroutine */ int cgbsv_(integer *n, integer *kl, integer *ku, integer *
+ nrhs, complex *ab, integer *ldab, integer *ipiv, complex *b, integer *
+ ldb, integer *info);
+
+/* Subroutine */ int cgbsvx_(char *fact, char *trans, integer *n, integer *kl,
+ integer *ku, integer *nrhs, complex *ab, integer *ldab, complex *afb,
+ integer *ldafb, integer *ipiv, char *equed, real *r__, real *c__,
+ complex *b, integer *ldb, complex *x, integer *ldx, real *rcond, real
+ *ferr, real *berr, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgbtf2_(integer *m, integer *n, integer *kl, integer *ku,
+ complex *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int cgbtrf_(integer *m, integer *n, integer *kl, integer *ku,
+ complex *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int cgbtrs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, complex *ab, integer *ldab, integer *ipiv, complex
+ *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cgebak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, real *scale, integer *m, complex *v, integer *ldv,
+ integer *info);
+
+/* Subroutine */ int cgebal_(char *job, integer *n, complex *a, integer *lda,
+ integer *ilo, integer *ihi, real *scale, integer *info);
+
+/* Subroutine */ int cgebd2_(integer *m, integer *n, complex *a, integer *lda,
+ real *d__, real *e, complex *tauq, complex *taup, complex *work,
+ integer *info);
+
+/* Subroutine */ int cgebrd_(integer *m, integer *n, complex *a, integer *lda,
+ real *d__, real *e, complex *tauq, complex *taup, complex *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int cgecon_(char *norm, integer *n, complex *a, integer *lda,
+ real *anorm, real *rcond, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgeequ_(integer *m, integer *n, complex *a, integer *lda,
+ real *r__, real *c__, real *rowcnd, real *colcnd, real *amax,
+ integer *info);
+
+/* Subroutine */ int cgees_(char *jobvs, char *sort, L_fp select, integer *n,
+ complex *a, integer *lda, integer *sdim, complex *w, complex *vs,
+ integer *ldvs, complex *work, integer *lwork, real *rwork, logical *
+ bwork, integer *info);
+
+/* Subroutine */ int cgeesx_(char *jobvs, char *sort, L_fp select, char *
+ sense, integer *n, complex *a, integer *lda, integer *sdim, complex *
+ w, complex *vs, integer *ldvs, real *rconde, real *rcondv, complex *
+ work, integer *lwork, real *rwork, logical *bwork, integer *info);
+
+/* Subroutine */ int cgeev_(char *jobvl, char *jobvr, integer *n, complex *a,
+ integer *lda, complex *w, complex *vl, integer *ldvl, complex *vr,
+ integer *ldvr, complex *work, integer *lwork, real *rwork, integer *
+ info);
+
+/* Subroutine */ int cgeevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, complex *a, integer *lda, complex *w, complex *vl,
+ integer *ldvl, complex *vr, integer *ldvr, integer *ilo, integer *ihi,
+ real *scale, real *abnrm, real *rconde, real *rcondv, complex *work,
+ integer *lwork, real *rwork, integer *info);
+
+/* Subroutine */ int cgegs_(char *jobvsl, char *jobvsr, integer *n, complex *
+ a, integer *lda, complex *b, integer *ldb, complex *alpha, complex *
+ beta, complex *vsl, integer *ldvsl, complex *vsr, integer *ldvsr,
+ complex *work, integer *lwork, real *rwork, integer *info);
+
+/* Subroutine */ int cgegv_(char *jobvl, char *jobvr, integer *n, complex *a,
+ integer *lda, complex *b, integer *ldb, complex *alpha, complex *beta,
+ complex *vl, integer *ldvl, complex *vr, integer *ldvr, complex *
+ work, integer *lwork, real *rwork, integer *info);
+
+/* Subroutine */ int cgehd2_(integer *n, integer *ilo, integer *ihi, complex *
+ a, integer *lda, complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cgehrd_(integer *n, integer *ilo, integer *ihi, complex *
+ a, integer *lda, complex *tau, complex *work, integer *lwork, integer
+ *info);
+
+/* Subroutine */ int cgelq2_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cgelqf_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cgels_(char *trans, integer *m, integer *n, integer *
+ nrhs, complex *a, integer *lda, complex *b, integer *ldb, complex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int cgelsx_(integer *m, integer *n, integer *nrhs, complex *
+ a, integer *lda, complex *b, integer *ldb, integer *jpvt, real *rcond,
+ integer *rank, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgelsy_(integer *m, integer *n, integer *nrhs, complex *
+ a, integer *lda, complex *b, integer *ldb, integer *jpvt, real *rcond,
+ integer *rank, complex *work, integer *lwork, real *rwork, integer *
+ info);
+
+/* Subroutine */ int cgeql2_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cgeqlf_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cgeqp3_(integer *m, integer *n, complex *a, integer *lda,
+ integer *jpvt, complex *tau, complex *work, integer *lwork, real *
+ rwork, integer *info);
+
+/* Subroutine */ int cgeqpf_(integer *m, integer *n, complex *a, integer *lda,
+ integer *jpvt, complex *tau, complex *work, real *rwork, integer *
+ info);
+
+/* Subroutine */ int cgeqr2_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cgeqrf_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cgerfs_(char *trans, integer *n, integer *nrhs, complex *
+ a, integer *lda, complex *af, integer *ldaf, integer *ipiv, complex *
+ b, integer *ldb, complex *x, integer *ldx, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgerq2_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cgerqf_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cgesc2_(integer *n, complex *a, integer *lda, complex *
+ rhs, integer *ipiv, integer *jpiv, real *scale);
+
+/* Subroutine */ int cgesv_(integer *n, integer *nrhs, complex *a, integer *
+ lda, integer *ipiv, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cgesvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, complex *a, integer *lda, complex *af, integer *ldaf, integer *
+ ipiv, char *equed, real *r__, real *c__, complex *b, integer *ldb,
+ complex *x, integer *ldx, real *rcond, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgetc2_(integer *n, complex *a, integer *lda, integer *
+ ipiv, integer *jpiv, integer *info);
+
+/* Subroutine */ int cgetf2_(integer *m, integer *n, complex *a, integer *lda,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int cgetrf_(integer *m, integer *n, complex *a, integer *lda,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int cgetri_(integer *n, complex *a, integer *lda, integer *
+ ipiv, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cgetrs_(char *trans, integer *n, integer *nrhs, complex *
+ a, integer *lda, integer *ipiv, complex *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int cggbak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, real *lscale, real *rscale, integer *m, complex *v,
+ integer *ldv, integer *info);
+
+/* Subroutine */ int cggbal_(char *job, integer *n, complex *a, integer *lda,
+ complex *b, integer *ldb, integer *ilo, integer *ihi, real *lscale,
+ real *rscale, real *work, integer *info);
+
+/* Subroutine */ int cgges_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ selctg, integer *n, complex *a, integer *lda, complex *b, integer *
+ ldb, integer *sdim, complex *alpha, complex *beta, complex *vsl,
+ integer *ldvsl, complex *vsr, integer *ldvsr, complex *work, integer *
+ lwork, real *rwork, logical *bwork, integer *info);
+
+/* Subroutine */ int cggesx_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ selctg, char *sense, integer *n, complex *a, integer *lda, complex *b,
+ integer *ldb, integer *sdim, complex *alpha, complex *beta, complex *
+ vsl, integer *ldvsl, complex *vsr, integer *ldvsr, real *rconde, real
+ *rcondv, complex *work, integer *lwork, real *rwork, integer *iwork,
+ integer *liwork, logical *bwork, integer *info);
+
+/* Subroutine */ int cggev_(char *jobvl, char *jobvr, integer *n, complex *a,
+ integer *lda, complex *b, integer *ldb, complex *alpha, complex *beta,
+ complex *vl, integer *ldvl, complex *vr, integer *ldvr, complex *
+ work, integer *lwork, real *rwork, integer *info);
+
+/* Subroutine */ int cggevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, complex *a, integer *lda, complex *b, integer *ldb,
+ complex *alpha, complex *beta, complex *vl, integer *ldvl, complex *
+ vr, integer *ldvr, integer *ilo, integer *ihi, real *lscale, real *
+ rscale, real *abnrm, real *bbnrm, real *rconde, real *rcondv, complex
+ *work, integer *lwork, real *rwork, integer *iwork, logical *bwork,
+ integer *info);
+
+/* Subroutine */ int cggglm_(integer *n, integer *m, integer *p, complex *a,
+ integer *lda, complex *b, integer *ldb, complex *d__, complex *x,
+ complex *y, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cgghrd_(char *compq, char *compz, integer *n, integer *
+ ilo, integer *ihi, complex *a, integer *lda, complex *b, integer *ldb,
+ complex *q, integer *ldq, complex *z__, integer *ldz, integer *info);
+
+/* Subroutine */ int cgglse_(integer *m, integer *n, integer *p, complex *a,
+ integer *lda, complex *b, integer *ldb, complex *c__, complex *d__,
+ complex *x, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cggqrf_(integer *n, integer *m, integer *p, complex *a,
+ integer *lda, complex *taua, complex *b, integer *ldb, complex *taub,
+ complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cggrqf_(integer *m, integer *p, integer *n, complex *a,
+ integer *lda, complex *taua, complex *b, integer *ldb, complex *taub,
+ complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cggsvd_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *n, integer *p, integer *k, integer *l, complex *a, integer *
+ lda, complex *b, integer *ldb, real *alpha, real *beta, complex *u,
+ integer *ldu, complex *v, integer *ldv, complex *q, integer *ldq,
+ complex *work, real *rwork, integer *iwork, integer *info);
+
+/* Subroutine */ int cggsvp_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, complex *a, integer *lda, complex *b, integer
+ *ldb, real *tola, real *tolb, integer *k, integer *l, complex *u,
+ integer *ldu, complex *v, integer *ldv, complex *q, integer *ldq,
+ integer *iwork, real *rwork, complex *tau, complex *work, integer *
+ info);
+
+/* Subroutine */ int cgtcon_(char *norm, integer *n, complex *dl, complex *
+ d__, complex *du, complex *du2, integer *ipiv, real *anorm, real *
+ rcond, complex *work, integer *info);
+
+/* Subroutine */ int cgtrfs_(char *trans, integer *n, integer *nrhs, complex *
+ dl, complex *d__, complex *du, complex *dlf, complex *df, complex *
+ duf, complex *du2, integer *ipiv, complex *b, integer *ldb, complex *
+ x, integer *ldx, real *ferr, real *berr, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int cgtsv_(integer *n, integer *nrhs, complex *dl, complex *
+ d__, complex *du, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cgtsvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, complex *dl, complex *d__, complex *du, complex *dlf, complex *
+ df, complex *duf, complex *du2, integer *ipiv, complex *b, integer *
+ ldb, complex *x, integer *ldx, real *rcond, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cgttrf_(integer *n, complex *dl, complex *d__, complex *
+ du, complex *du2, integer *ipiv, integer *info);
+
+/* Subroutine */ int cgttrs_(char *trans, integer *n, integer *nrhs, complex *
+ dl, complex *d__, complex *du, complex *du2, integer *ipiv, complex *
+ b, integer *ldb, integer *info);
+
+/* Subroutine */ int cgtts2_(integer *itrans, integer *n, integer *nrhs,
+ complex *dl, complex *d__, complex *du, complex *du2, integer *ipiv,
+ complex *b, integer *ldb);
+
+/* Subroutine */ int chbev_(char *jobz, char *uplo, integer *n, integer *kd,
+ complex *ab, integer *ldab, real *w, complex *z__, integer *ldz,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int chbevd_(char *jobz, char *uplo, integer *n, integer *kd,
+ complex *ab, integer *ldab, real *w, complex *z__, integer *ldz,
+ complex *work, integer *lwork, real *rwork, integer *lrwork, integer *
+ iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int chbevx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *kd, complex *ab, integer *ldab, complex *q, integer *ldq,
+ real *vl, real *vu, integer *il, integer *iu, real *abstol, integer *
+ m, real *w, complex *z__, integer *ldz, complex *work, real *rwork,
+ integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int chbgst_(char *vect, char *uplo, integer *n, integer *ka,
+ integer *kb, complex *ab, integer *ldab, complex *bb, integer *ldbb,
+ complex *x, integer *ldx, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int chbgv_(char *jobz, char *uplo, integer *n, integer *ka,
+ integer *kb, complex *ab, integer *ldab, complex *bb, integer *ldbb,
+ real *w, complex *z__, integer *ldz, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int chbgvx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *ka, integer *kb, complex *ab, integer *ldab, complex *bb,
+ integer *ldbb, complex *q, integer *ldq, real *vl, real *vu, integer *
+ il, integer *iu, real *abstol, integer *m, real *w, complex *z__,
+ integer *ldz, complex *work, real *rwork, integer *iwork, integer *
+ ifail, integer *info);
+
+/* Subroutine */ int chbtrd_(char *vect, char *uplo, integer *n, integer *kd,
+ complex *ab, integer *ldab, real *d__, real *e, complex *q, integer *
+ ldq, complex *work, integer *info);
+
+/* Subroutine */ int checon_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, real *anorm, real *rcond, complex *work, integer *
+ info);
+
+/* Subroutine */ int cheev_(char *jobz, char *uplo, integer *n, complex *a,
+ integer *lda, real *w, complex *work, integer *lwork, real *rwork,
+ integer *info);
+
+/* Subroutine */ int cheevd_(char *jobz, char *uplo, integer *n, complex *a,
+ integer *lda, real *w, complex *work, integer *lwork, real *rwork,
+ integer *lrwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int cheevr_(char *jobz, char *range, char *uplo, integer *n,
+ complex *a, integer *lda, real *vl, real *vu, integer *il, integer *
+ iu, real *abstol, integer *m, real *w, complex *z__, integer *ldz,
+ integer *isuppz, complex *work, integer *lwork, real *rwork, integer *
+ lrwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int cheevx_(char *jobz, char *range, char *uplo, integer *n,
+ complex *a, integer *lda, real *vl, real *vu, integer *il, integer *
+ iu, real *abstol, integer *m, real *w, complex *z__, integer *ldz,
+ complex *work, integer *lwork, real *rwork, integer *iwork, integer *
+ ifail, integer *info);
+
+/* Subroutine */ int chegs2_(integer *itype, char *uplo, integer *n, complex *
+ a, integer *lda, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int chegst_(integer *itype, char *uplo, integer *n, complex *
+ a, integer *lda, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int chegv_(integer *itype, char *jobz, char *uplo, integer *
+ n, complex *a, integer *lda, complex *b, integer *ldb, real *w,
+ complex *work, integer *lwork, real *rwork, integer *info);
+
+/* Subroutine */ int chegvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, complex *a, integer *lda, complex *b, integer *ldb, real *w,
+ complex *work, integer *lwork, real *rwork, integer *lrwork, integer *
+ iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int chegvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, complex *a, integer *lda, complex *b, integer *ldb,
+ real *vl, real *vu, integer *il, integer *iu, real *abstol, integer *
+ m, real *w, complex *z__, integer *ldz, complex *work, integer *lwork,
+ real *rwork, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int cherfs_(char *uplo, integer *n, integer *nrhs, complex *
+ a, integer *lda, complex *af, integer *ldaf, integer *ipiv, complex *
+ b, integer *ldb, complex *x, integer *ldx, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int chesv_(char *uplo, integer *n, integer *nrhs, complex *a,
+ integer *lda, integer *ipiv, complex *b, integer *ldb, complex *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int chesvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, complex *a, integer *lda, complex *af, integer *ldaf, integer *
+ ipiv, complex *b, integer *ldb, complex *x, integer *ldx, real *rcond,
+ real *ferr, real *berr, complex *work, integer *lwork, real *rwork,
+ integer *info);
+
+/* Subroutine */ int chetf2_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int chetrd_(char *uplo, integer *n, complex *a, integer *lda,
+ real *d__, real *e, complex *tau, complex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int chetrf_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int chetri_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, complex *work, integer *info);
+
+/* Subroutine */ int chetrs_(char *uplo, integer *n, integer *nrhs, complex *
+ a, integer *lda, integer *ipiv, complex *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int chgeqz_(char *job, char *compq, char *compz, integer *n,
+ integer *ilo, integer *ihi, complex *a, integer *lda, complex *b,
+ integer *ldb, complex *alpha, complex *beta, complex *q, integer *ldq,
+ complex *z__, integer *ldz, complex *work, integer *lwork, real *
+ rwork, integer *info);
+
+/* Subroutine */ int chpcon_(char *uplo, integer *n, complex *ap, integer *
+ ipiv, real *anorm, real *rcond, complex *work, integer *info);
+
+/* Subroutine */ int chpev_(char *jobz, char *uplo, integer *n, complex *ap,
+ real *w, complex *z__, integer *ldz, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int chpevd_(char *jobz, char *uplo, integer *n, complex *ap,
+ real *w, complex *z__, integer *ldz, complex *work, integer *lwork,
+ real *rwork, integer *lrwork, integer *iwork, integer *liwork,
+ integer *info);
+
+/* Subroutine */ int chpevx_(char *jobz, char *range, char *uplo, integer *n,
+ complex *ap, real *vl, real *vu, integer *il, integer *iu, real *
+ abstol, integer *m, real *w, complex *z__, integer *ldz, complex *
+ work, real *rwork, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int chpgst_(integer *itype, char *uplo, integer *n, complex *
+ ap, complex *bp, integer *info);
+
+/* Subroutine */ int chpgv_(integer *itype, char *jobz, char *uplo, integer *
+ n, complex *ap, complex *bp, real *w, complex *z__, integer *ldz,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int chpgvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, complex *ap, complex *bp, real *w, complex *z__, integer *ldz,
+ complex *work, integer *lwork, real *rwork, integer *lrwork, integer *
+ iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int chpgvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, complex *ap, complex *bp, real *vl, real *vu,
+ integer *il, integer *iu, real *abstol, integer *m, real *w, complex *
+ z__, integer *ldz, complex *work, real *rwork, integer *iwork,
+ integer *ifail, integer *info);
+
+/* Subroutine */ int chprfs_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, complex *afp, integer *ipiv, complex *b, integer *ldb, complex *x,
+ integer *ldx, real *ferr, real *berr, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int chpsv_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, integer *ipiv, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int chpsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, complex *ap, complex *afp, integer *ipiv, complex *b, integer *
+ ldb, complex *x, integer *ldx, real *rcond, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int chptrd_(char *uplo, integer *n, complex *ap, real *d__,
+ real *e, complex *tau, integer *info);
+
+/* Subroutine */ int chptrf_(char *uplo, integer *n, complex *ap, integer *
+ ipiv, integer *info);
+
+/* Subroutine */ int chptri_(char *uplo, integer *n, complex *ap, integer *
+ ipiv, complex *work, integer *info);
+
+/* Subroutine */ int chptrs_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, integer *ipiv, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int chsein_(char *side, char *eigsrc, char *initv, logical *
+ select, integer *n, complex *h__, integer *ldh, complex *w, complex *
+ vl, integer *ldvl, complex *vr, integer *ldvr, integer *mm, integer *
+ m, complex *work, real *rwork, integer *ifaill, integer *ifailr,
+ integer *info);
+
+/* Subroutine */ int chseqr_(char *job, char *compz, integer *n, integer *ilo,
+ integer *ihi, complex *h__, integer *ldh, complex *w, complex *z__,
+ integer *ldz, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int clabrd_(integer *m, integer *n, integer *nb, complex *a,
+ integer *lda, real *d__, real *e, complex *tauq, complex *taup,
+ complex *x, integer *ldx, complex *y, integer *ldy);
+
+/* Subroutine */ int clacgv_(integer *n, complex *x, integer *incx);
+
+/* Subroutine */ int clacon_(integer *n, complex *v, complex *x, real *est,
+ integer *kase);
+
+/* Subroutine */ int clacp2_(char *uplo, integer *m, integer *n, real *a,
+ integer *lda, complex *b, integer *ldb);
+
+/* Subroutine */ int clacpy_(char *uplo, integer *m, integer *n, complex *a,
+ integer *lda, complex *b, integer *ldb);
+
+/* Subroutine */ int clacrm_(integer *m, integer *n, complex *a, integer *lda,
+ real *b, integer *ldb, complex *c__, integer *ldc, real *rwork);
+
+/* Subroutine */ int clacrt_(integer *n, complex *cx, integer *incx, complex *
+ cy, integer *incy, complex *c__, complex *s);
+
+/* Subroutine */ int claed0_(integer *qsiz, integer *n, real *d__, real *e,
+ complex *q, integer *ldq, complex *qstore, integer *ldqs, real *rwork,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int claed7_(integer *n, integer *cutpnt, integer *qsiz,
+ integer *tlvls, integer *curlvl, integer *curpbm, real *d__, complex *
+ q, integer *ldq, real *rho, integer *indxq, real *qstore, integer *
+ qptr, integer *prmptr, integer *perm, integer *givptr, integer *
+ givcol, real *givnum, complex *work, real *rwork, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int claed8_(integer *k, integer *n, integer *qsiz, complex *
+ q, integer *ldq, real *d__, real *rho, integer *cutpnt, real *z__,
+ real *dlamda, complex *q2, integer *ldq2, real *w, integer *indxp,
+ integer *indx, integer *indxq, integer *perm, integer *givptr,
+ integer *givcol, real *givnum, integer *info);
+
+/* Subroutine */ int claein_(logical *rightv, logical *noinit, integer *n,
+ complex *h__, integer *ldh, complex *w, complex *v, complex *b,
+ integer *ldb, real *rwork, real *eps3, real *smlnum, integer *info);
+
+/* Subroutine */ int claesy_(complex *a, complex *b, complex *c__, complex *
+ rt1, complex *rt2, complex *evscal, complex *cs1, complex *sn1);
+
+/* Subroutine */ int claev2_(complex *a, complex *b, complex *c__, real *rt1,
+ real *rt2, real *cs1, complex *sn1);
+
+/* Subroutine */ int clags2_(logical *upper, real *a1, complex *a2, real *a3,
+ real *b1, complex *b2, real *b3, real *csu, complex *snu, real *csv,
+ complex *snv, real *csq, complex *snq);
+
+/* Subroutine */ int clagtm_(char *trans, integer *n, integer *nrhs, real *
+ alpha, complex *dl, complex *d__, complex *du, complex *x, integer *
+ ldx, real *beta, complex *b, integer *ldb);
+
+/* Subroutine */ int clahef_(char *uplo, integer *n, integer *nb, integer *kb,
+ complex *a, integer *lda, integer *ipiv, complex *w, integer *ldw,
+ integer *info);
+
+/* Subroutine */ int clahqr_(logical *wantt, logical *wantz, integer *n,
+ integer *ilo, integer *ihi, complex *h__, integer *ldh, complex *w,
+ integer *iloz, integer *ihiz, complex *z__, integer *ldz, integer *
+ info);
+
+/* Subroutine */ int clahrd_(integer *n, integer *k, integer *nb, complex *a,
+ integer *lda, complex *tau, complex *t, integer *ldt, complex *y,
+ integer *ldy);
+
+/* Subroutine */ int claic1_(integer *job, integer *j, complex *x, real *sest,
+ complex *w, complex *gamma, real *sestpr, complex *s, complex *c__);
+
+/* Subroutine */ int clals0_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, integer *nrhs, complex *b, integer *ldb, complex *bx,
+ integer *ldbx, integer *perm, integer *givptr, integer *givcol,
+ integer *ldgcol, real *givnum, integer *ldgnum, real *poles, real *
+ difl, real *difr, real *z__, integer *k, real *c__, real *s, real *
+ rwork, integer *info);
+
+/* Subroutine */ int clalsa_(integer *icompq, integer *smlsiz, integer *n,
+ integer *nrhs, complex *b, integer *ldb, complex *bx, integer *ldbx,
+ real *u, integer *ldu, real *vt, integer *k, real *difl, real *difr,
+ real *z__, real *poles, integer *givptr, integer *givcol, integer *
+ ldgcol, integer *perm, real *givnum, real *c__, real *s, real *rwork,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int clapll_(integer *n, complex *x, integer *incx, complex *
+ y, integer *incy, real *ssmin);
+
+/* Subroutine */ int clapmt_(logical *forwrd, integer *m, integer *n, complex
+ *x, integer *ldx, integer *k);
+
+/* Subroutine */ int claqgb_(integer *m, integer *n, integer *kl, integer *ku,
+ complex *ab, integer *ldab, real *r__, real *c__, real *rowcnd, real
+ *colcnd, real *amax, char *equed);
+
+/* Subroutine */ int claqge_(integer *m, integer *n, complex *a, integer *lda,
+ real *r__, real *c__, real *rowcnd, real *colcnd, real *amax, char *
+ equed);
+
+/* Subroutine */ int claqhb_(char *uplo, integer *n, integer *kd, complex *ab,
+ integer *ldab, real *s, real *scond, real *amax, char *equed);
+
+/* Subroutine */ int claqhe_(char *uplo, integer *n, complex *a, integer *lda,
+ real *s, real *scond, real *amax, char *equed);
+
+/* Subroutine */ int claqhp_(char *uplo, integer *n, complex *ap, real *s,
+ real *scond, real *amax, char *equed);
+
+/* Subroutine */ int claqp2_(integer *m, integer *n, integer *offset, complex
+ *a, integer *lda, integer *jpvt, complex *tau, real *vn1, real *vn2,
+ complex *work);
+
+/* Subroutine */ int claqps_(integer *m, integer *n, integer *offset, integer
+ *nb, integer *kb, complex *a, integer *lda, integer *jpvt, complex *
+ tau, real *vn1, real *vn2, complex *auxv, complex *f, integer *ldf);
+
+/* Subroutine */ int claqsb_(char *uplo, integer *n, integer *kd, complex *ab,
+ integer *ldab, real *s, real *scond, real *amax, char *equed);
+
+/* Subroutine */ int claqsp_(char *uplo, integer *n, complex *ap, real *s,
+ real *scond, real *amax, char *equed);
+
+/* Subroutine */ int claqsy_(char *uplo, integer *n, complex *a, integer *lda,
+ real *s, real *scond, real *amax, char *equed);
+
+/* Subroutine */ int clar1v_(integer *n, integer *b1, integer *bn, real *
+ sigma, real *d__, real *l, real *ld, real *lld, real *gersch, complex
+ *z__, real *ztz, real *mingma, integer *r__, integer *isuppz, real *
+ work);
+
+/* Subroutine */ int clar2v_(integer *n, complex *x, complex *y, complex *z__,
+ integer *incx, real *c__, complex *s, integer *incc);
+
+/* Subroutine */ int clarcm_(integer *m, integer *n, real *a, integer *lda,
+ complex *b, integer *ldb, complex *c__, integer *ldc, real *rwork);
+
+/* Subroutine */ int clarf_(char *side, integer *m, integer *n, complex *v,
+ integer *incv, complex *tau, complex *c__, integer *ldc, complex *
+ work);
+
+/* Subroutine */ int clarfb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, complex *v, integer *ldv,
+ complex *t, integer *ldt, complex *c__, integer *ldc, complex *work,
+ integer *ldwork);
+
+/* Subroutine */ int clarfg_(integer *n, complex *alpha, complex *x, integer *
+ incx, complex *tau);
+
+/* Subroutine */ int clarft_(char *direct, char *storev, integer *n, integer *
+ k, complex *v, integer *ldv, complex *tau, complex *t, integer *ldt);
+
+/* Subroutine */ int clarfx_(char *side, integer *m, integer *n, complex *v,
+ complex *tau, complex *c__, integer *ldc, complex *work);
+
+/* Subroutine */ int clargv_(integer *n, complex *x, integer *incx, complex *
+ y, integer *incy, real *c__, integer *incc);
+
+/* Subroutine */ int clarnv_(integer *idist, integer *iseed, integer *n,
+ complex *x);
+
+/* Subroutine */ int clarrv_(integer *n, real *d__, real *l, integer *isplit,
+ integer *m, real *w, integer *iblock, real *gersch, real *tol,
+ complex *z__, integer *ldz, integer *isuppz, real *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int clartg_(complex *f, complex *g, real *cs, complex *sn,
+ complex *r__);
+
+/* Subroutine */ int clartv_(integer *n, complex *x, integer *incx, complex *
+ y, integer *incy, real *c__, complex *s, integer *incc);
+
+/* Subroutine */ int clarz_(char *side, integer *m, integer *n, integer *l,
+ complex *v, integer *incv, complex *tau, complex *c__, integer *ldc,
+ complex *work);
+
+/* Subroutine */ int clarzb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, integer *l, complex *v,
+ integer *ldv, complex *t, integer *ldt, complex *c__, integer *ldc,
+ complex *work, integer *ldwork);
+
+/* Subroutine */ int clarzt_(char *direct, char *storev, integer *n, integer *
+ k, complex *v, integer *ldv, complex *tau, complex *t, integer *ldt);
+
+/* Subroutine */ int clascl_(char *type__, integer *kl, integer *ku, real *
+ cfrom, real *cto, integer *m, integer *n, complex *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int claset_(char *uplo, integer *m, integer *n, complex *
+ alpha, complex *beta, complex *a, integer *lda);
+
+/* Subroutine */ int clasr_(char *side, char *pivot, char *direct, integer *m,
+ integer *n, real *c__, real *s, complex *a, integer *lda);
+
+/* Subroutine */ int classq_(integer *n, complex *x, integer *incx, real *
+ scale, real *sumsq);
+
+/* Subroutine */ int claswp_(integer *n, complex *a, integer *lda, integer *
+ k1, integer *k2, integer *ipiv, integer *incx);
+
+/* Subroutine */ int clasyf_(char *uplo, integer *n, integer *nb, integer *kb,
+ complex *a, integer *lda, integer *ipiv, complex *w, integer *ldw,
+ integer *info);
+
+/* Subroutine */ int clatbs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, integer *kd, complex *ab, integer *ldab, complex *
+ x, real *scale, real *cnorm, integer *info);
+
+/* Subroutine */ int clatdf_(integer *ijob, integer *n, complex *z__, integer
+ *ldz, complex *rhs, real *rdsum, real *rdscal, integer *ipiv, integer
+ *jpiv);
+
+/* Subroutine */ int clatps_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, complex *ap, complex *x, real *scale, real *cnorm,
+ integer *info);
+
+/* Subroutine */ int clatrd_(char *uplo, integer *n, integer *nb, complex *a,
+ integer *lda, real *e, complex *tau, complex *w, integer *ldw);
+
+/* Subroutine */ int clatrs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, complex *a, integer *lda, complex *x, real *scale,
+ real *cnorm, integer *info);
+
+/* Subroutine */ int clatrz_(integer *m, integer *n, integer *l, complex *a,
+ integer *lda, complex *tau, complex *work);
+
+/* Subroutine */ int clatzm_(char *side, integer *m, integer *n, complex *v,
+ integer *incv, complex *tau, complex *c1, complex *c2, integer *ldc,
+ complex *work);
+
+/* Subroutine */ int clauu2_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int clauum_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int cpbcon_(char *uplo, integer *n, integer *kd, complex *ab,
+ integer *ldab, real *anorm, real *rcond, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int cpbequ_(char *uplo, integer *n, integer *kd, complex *ab,
+ integer *ldab, real *s, real *scond, real *amax, integer *info);
+
+/* Subroutine */ int cpbrfs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, complex *ab, integer *ldab, complex *afb, integer *ldafb,
+ complex *b, integer *ldb, complex *x, integer *ldx, real *ferr, real *
+ berr, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cpbstf_(char *uplo, integer *n, integer *kd, complex *ab,
+ integer *ldab, integer *info);
+
+/* Subroutine */ int cpbsv_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, complex *ab, integer *ldab, complex *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int cpbsvx_(char *fact, char *uplo, integer *n, integer *kd,
+ integer *nrhs, complex *ab, integer *ldab, complex *afb, integer *
+ ldafb, char *equed, real *s, complex *b, integer *ldb, complex *x,
+ integer *ldx, real *rcond, real *ferr, real *berr, complex *work,
+ real *rwork, integer *info);
+
+/* Subroutine */ int cpbtf2_(char *uplo, integer *n, integer *kd, complex *ab,
+ integer *ldab, integer *info);
+
+/* Subroutine */ int cpbtrf_(char *uplo, integer *n, integer *kd, complex *ab,
+ integer *ldab, integer *info);
+
+/* Subroutine */ int cpbtrs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, complex *ab, integer *ldab, complex *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int cpocon_(char *uplo, integer *n, complex *a, integer *lda,
+ real *anorm, real *rcond, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cpoequ_(integer *n, complex *a, integer *lda, real *s,
+ real *scond, real *amax, integer *info);
+
+/* Subroutine */ int cporfs_(char *uplo, integer *n, integer *nrhs, complex *
+ a, integer *lda, complex *af, integer *ldaf, complex *b, integer *ldb,
+ complex *x, integer *ldx, real *ferr, real *berr, complex *work,
+ real *rwork, integer *info);
+
+/* Subroutine */ int cposv_(char *uplo, integer *n, integer *nrhs, complex *a,
+ integer *lda, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cposvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, complex *a, integer *lda, complex *af, integer *ldaf, char *
+ equed, real *s, complex *b, integer *ldb, complex *x, integer *ldx,
+ real *rcond, real *ferr, real *berr, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int cpotf2_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int cpotrf_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int cpotri_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int cpotrs_(char *uplo, integer *n, integer *nrhs, complex *
+ a, integer *lda, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cppcon_(char *uplo, integer *n, complex *ap, real *anorm,
+ real *rcond, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cppequ_(char *uplo, integer *n, complex *ap, real *s,
+ real *scond, real *amax, integer *info);
+
+/* Subroutine */ int cpprfs_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, complex *afp, complex *b, integer *ldb, complex *x, integer *ldx,
+ real *ferr, real *berr, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cppsv_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cppsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, complex *ap, complex *afp, char *equed, real *s, complex *b,
+ integer *ldb, complex *x, integer *ldx, real *rcond, real *ferr, real
+ *berr, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int cpptrf_(char *uplo, integer *n, complex *ap, integer *
+ info);
+
+/* Subroutine */ int cpptri_(char *uplo, integer *n, complex *ap, integer *
+ info);
+
+/* Subroutine */ int cpptrs_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cptcon_(integer *n, real *d__, complex *e, real *anorm,
+ real *rcond, real *rwork, integer *info);
+
+/* Subroutine */ int cptrfs_(char *uplo, integer *n, integer *nrhs, real *d__,
+ complex *e, real *df, complex *ef, complex *b, integer *ldb, complex
+ *x, integer *ldx, real *ferr, real *berr, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int cptsv_(integer *n, integer *nrhs, real *d__, complex *e,
+ complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cptsvx_(char *fact, integer *n, integer *nrhs, real *d__,
+ complex *e, real *df, complex *ef, complex *b, integer *ldb, complex
+ *x, integer *ldx, real *rcond, real *ferr, real *berr, complex *work,
+ real *rwork, integer *info);
+
+/* Subroutine */ int cpttrf_(integer *n, real *d__, complex *e, integer *info);
+
+/* Subroutine */ int cpttrs_(char *uplo, integer *n, integer *nrhs, real *d__,
+ complex *e, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cptts2_(integer *iuplo, integer *n, integer *nrhs, real *
+ d__, complex *e, complex *b, integer *ldb);
+
+/* Subroutine */ int crot_(integer *n, complex *cx, integer *incx, complex *
+ cy, integer *incy, real *c__, complex *s);
+
+/* Subroutine */ int cspcon_(char *uplo, integer *n, complex *ap, integer *
+ ipiv, real *anorm, real *rcond, complex *work, integer *info);
+
+/* Subroutine */ int cspmv_(char *uplo, integer *n, complex *alpha, complex *
+ ap, complex *x, integer *incx, complex *beta, complex *y, integer *
+ incy);
+
+/* Subroutine */ int cspr_(char *uplo, integer *n, complex *alpha, complex *x,
+ integer *incx, complex *ap);
+
+/* Subroutine */ int csprfs_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, complex *afp, integer *ipiv, complex *b, integer *ldb, complex *x,
+ integer *ldx, real *ferr, real *berr, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int cspsv_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, integer *ipiv, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int cspsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, complex *ap, complex *afp, integer *ipiv, complex *b, integer *
+ ldb, complex *x, integer *ldx, real *rcond, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int csptrf_(char *uplo, integer *n, complex *ap, integer *
+ ipiv, integer *info);
+
+/* Subroutine */ int csptri_(char *uplo, integer *n, complex *ap, integer *
+ ipiv, complex *work, integer *info);
+
+/* Subroutine */ int csptrs_(char *uplo, integer *n, integer *nrhs, complex *
+ ap, integer *ipiv, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int csrot_(integer *n, complex *cx, integer *incx, complex *
+ cy, integer *incy, real *c__, real *s);
+
+/* Subroutine */ int csrscl_(integer *n, real *sa, complex *sx, integer *incx);
+
+/* Subroutine */ int cstedc_(char *compz, integer *n, real *d__, real *e,
+ complex *z__, integer *ldz, complex *work, integer *lwork, real *
+ rwork, integer *lrwork, integer *iwork, integer *liwork, integer *
+ info);
+
+/* Subroutine */ int cstein_(integer *n, real *d__, real *e, integer *m, real
+ *w, integer *iblock, integer *isplit, complex *z__, integer *ldz,
+ real *work, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int csteqr_(char *compz, integer *n, real *d__, real *e,
+ complex *z__, integer *ldz, real *work, integer *info);
+
+/* Subroutine */ int csycon_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, real *anorm, real *rcond, complex *work, integer *
+ info);
+
+/* Subroutine */ int csymv_(char *uplo, integer *n, complex *alpha, complex *
+ a, integer *lda, complex *x, integer *incx, complex *beta, complex *y,
+ integer *incy);
+
+/* Subroutine */ int csyr_(char *uplo, integer *n, complex *alpha, complex *x,
+ integer *incx, complex *a, integer *lda);
+
+/* Subroutine */ int csyrfs_(char *uplo, integer *n, integer *nrhs, complex *
+ a, integer *lda, complex *af, integer *ldaf, integer *ipiv, complex *
+ b, integer *ldb, complex *x, integer *ldx, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int csysv_(char *uplo, integer *n, integer *nrhs, complex *a,
+ integer *lda, integer *ipiv, complex *b, integer *ldb, complex *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int csysvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, complex *a, integer *lda, complex *af, integer *ldaf, integer *
+ ipiv, complex *b, integer *ldb, complex *x, integer *ldx, real *rcond,
+ real *ferr, real *berr, complex *work, integer *lwork, real *rwork,
+ integer *info);
+
+/* Subroutine */ int csytf2_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int csytrf_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int csytri_(char *uplo, integer *n, complex *a, integer *lda,
+ integer *ipiv, complex *work, integer *info);
+
+/* Subroutine */ int csytrs_(char *uplo, integer *n, integer *nrhs, complex *
+ a, integer *lda, integer *ipiv, complex *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int ctbcon_(char *norm, char *uplo, char *diag, integer *n,
+ integer *kd, complex *ab, integer *ldab, real *rcond, complex *work,
+ real *rwork, integer *info);
+
+/* Subroutine */ int ctbrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, complex *ab, integer *ldab, complex *b,
+ integer *ldb, complex *x, integer *ldx, real *ferr, real *berr,
+ complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int ctbtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, complex *ab, integer *ldab, complex *b,
+ integer *ldb, integer *info);
+
+/* Subroutine */ int ctgevc_(char *side, char *howmny, logical *select,
+ integer *n, complex *a, integer *lda, complex *b, integer *ldb,
+ complex *vl, integer *ldvl, complex *vr, integer *ldvr, integer *mm,
+ integer *m, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int ctgex2_(logical *wantq, logical *wantz, integer *n,
+ complex *a, integer *lda, complex *b, integer *ldb, complex *q,
+ integer *ldq, complex *z__, integer *ldz, integer *j1, integer *info);
+
+/* Subroutine */ int ctgexc_(logical *wantq, logical *wantz, integer *n,
+ complex *a, integer *lda, complex *b, integer *ldb, complex *q,
+ integer *ldq, complex *z__, integer *ldz, integer *ifst, integer *
+ ilst, integer *info);
+
+/* Subroutine */ int ctgsen_(integer *ijob, logical *wantq, logical *wantz,
+ logical *select, integer *n, complex *a, integer *lda, complex *b,
+ integer *ldb, complex *alpha, complex *beta, complex *q, integer *ldq,
+ complex *z__, integer *ldz, integer *m, real *pl, real *pr, real *
+ dif, complex *work, integer *lwork, integer *iwork, integer *liwork,
+ integer *info);
+
+/* Subroutine */ int ctgsja_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, integer *k, integer *l, complex *a, integer *
+ lda, complex *b, integer *ldb, real *tola, real *tolb, real *alpha,
+ real *beta, complex *u, integer *ldu, complex *v, integer *ldv,
+ complex *q, integer *ldq, complex *work, integer *ncycle, integer *
+ info);
+
+/* Subroutine */ int ctgsna_(char *job, char *howmny, logical *select,
+ integer *n, complex *a, integer *lda, complex *b, integer *ldb,
+ complex *vl, integer *ldvl, complex *vr, integer *ldvr, real *s, real
+ *dif, integer *mm, integer *m, complex *work, integer *lwork, integer
+ *iwork, integer *info);
+
+/* Subroutine */ int ctgsy2_(char *trans, integer *ijob, integer *m, integer *
+ n, complex *a, integer *lda, complex *b, integer *ldb, complex *c__,
+ integer *ldc, complex *d__, integer *ldd, complex *e, integer *lde,
+ complex *f, integer *ldf, real *scale, real *rdsum, real *rdscal,
+ integer *info);
+
+/* Subroutine */ int ctgsyl_(char *trans, integer *ijob, integer *m, integer *
+ n, complex *a, integer *lda, complex *b, integer *ldb, complex *c__,
+ integer *ldc, complex *d__, integer *ldd, complex *e, integer *lde,
+ complex *f, integer *ldf, real *scale, real *dif, complex *work,
+ integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int ctpcon_(char *norm, char *uplo, char *diag, integer *n,
+ complex *ap, real *rcond, complex *work, real *rwork, integer *info);
+
+/* Subroutine */ int ctprfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, complex *ap, complex *b, integer *ldb, complex *x,
+ integer *ldx, real *ferr, real *berr, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int ctptri_(char *uplo, char *diag, integer *n, complex *ap,
+ integer *info);
+
+/* Subroutine */ int ctptrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, complex *ap, complex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int ctrcon_(char *norm, char *uplo, char *diag, integer *n,
+ complex *a, integer *lda, real *rcond, complex *work, real *rwork,
+ integer *info);
+
+/* Subroutine */ int ctrevc_(char *side, char *howmny, logical *select,
+ integer *n, complex *t, integer *ldt, complex *vl, integer *ldvl,
+ complex *vr, integer *ldvr, integer *mm, integer *m, complex *work,
+ real *rwork, integer *info);
+
+/* Subroutine */ int ctrexc_(char *compq, integer *n, complex *t, integer *
+ ldt, complex *q, integer *ldq, integer *ifst, integer *ilst, integer *
+ info);
+
+/* Subroutine */ int ctrrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, complex *a, integer *lda, complex *b, integer *ldb,
+ complex *x, integer *ldx, real *ferr, real *berr, complex *work, real
+ *rwork, integer *info);
+
+/* Subroutine */ int ctrsen_(char *job, char *compq, logical *select, integer
+ *n, complex *t, integer *ldt, complex *q, integer *ldq, complex *w,
+ integer *m, real *s, real *sep, complex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int ctrsna_(char *job, char *howmny, logical *select,
+ integer *n, complex *t, integer *ldt, complex *vl, integer *ldvl,
+ complex *vr, integer *ldvr, real *s, real *sep, integer *mm, integer *
+ m, complex *work, integer *ldwork, real *rwork, integer *info);
+
+/* Subroutine */ int ctrsyl_(char *trana, char *tranb, integer *isgn, integer
+ *m, integer *n, complex *a, integer *lda, complex *b, integer *ldb,
+ complex *c__, integer *ldc, real *scale, integer *info);
+
+/* Subroutine */ int ctrti2_(char *uplo, char *diag, integer *n, complex *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int ctrtri_(char *uplo, char *diag, integer *n, complex *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int ctrtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, complex *a, integer *lda, complex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int ctzrqf_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, integer *info);
+
+/* Subroutine */ int ctzrzf_(integer *m, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cung2l_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cung2r_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cungbr_(char *vect, integer *m, integer *n, integer *k,
+ complex *a, integer *lda, complex *tau, complex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int cunghr_(integer *n, integer *ilo, integer *ihi, complex *
+ a, integer *lda, complex *tau, complex *work, integer *lwork, integer
+ *info);
+
+/* Subroutine */ int cungl2_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cunglq_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int cungql_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int cungqr_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int cungr2_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *info);
+
+/* Subroutine */ int cungrq_(integer *m, integer *n, integer *k, complex *a,
+ integer *lda, complex *tau, complex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int cungtr_(char *uplo, integer *n, complex *a, integer *lda,
+ complex *tau, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cunm2l_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *info);
+
+/* Subroutine */ int cunm2r_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *info);
+
+/* Subroutine */ int cunmbr_(char *vect, char *side, char *trans, integer *m,
+ integer *n, integer *k, complex *a, integer *lda, complex *tau,
+ complex *c__, integer *ldc, complex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int cunmhr_(char *side, char *trans, integer *m, integer *n,
+ integer *ilo, integer *ihi, complex *a, integer *lda, complex *tau,
+ complex *c__, integer *ldc, complex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int cunml2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *info);
+
+/* Subroutine */ int cunmlq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cunmql_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cunmqr_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cunmr2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *info);
+
+/* Subroutine */ int cunmr3_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, complex *a, integer *lda, complex *tau,
+ complex *c__, integer *ldc, complex *work, integer *info);
+
+/* Subroutine */ int cunmrq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cunmrz_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, complex *a, integer *lda, complex *tau,
+ complex *c__, integer *ldc, complex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int cunmtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, complex *a, integer *lda, complex *tau, complex *c__,
+ integer *ldc, complex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int cupgtr_(char *uplo, integer *n, complex *ap, complex *
+ tau, complex *q, integer *ldq, complex *work, integer *info);
+
+/* Subroutine */ int cupmtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, complex *ap, complex *tau, complex *c__, integer *ldc,
+ complex *work, integer *info);
+
+/* Subroutine */ int dbdsdc_(char *uplo, char *compq, integer *n, doublereal *
+ d__, doublereal *e, doublereal *u, integer *ldu, doublereal *vt,
+ integer *ldvt, doublereal *q, integer *iq, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dbdsqr_(char *uplo, integer *n, integer *ncvt, integer *
+ nru, integer *ncc, doublereal *d__, doublereal *e, doublereal *vt,
+ integer *ldvt, doublereal *u, integer *ldu, doublereal *c__, integer *
+ ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int ddisna_(char *job, integer *m, integer *n, doublereal *
+ d__, doublereal *sep, integer *info);
+
+/* Subroutine */ int dgbbrd_(char *vect, integer *m, integer *n, integer *ncc,
+ integer *kl, integer *ku, doublereal *ab, integer *ldab, doublereal *
+ d__, doublereal *e, doublereal *q, integer *ldq, doublereal *pt,
+ integer *ldpt, doublereal *c__, integer *ldc, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dgbcon_(char *norm, integer *n, integer *kl, integer *ku,
+ doublereal *ab, integer *ldab, integer *ipiv, doublereal *anorm,
+ doublereal *rcond, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dgbequ_(integer *m, integer *n, integer *kl, integer *ku,
+ doublereal *ab, integer *ldab, doublereal *r__, doublereal *c__,
+ doublereal *rowcnd, doublereal *colcnd, doublereal *amax, integer *
+ info);
+
+/* Subroutine */ int dgbrfs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, doublereal *ab, integer *ldab, doublereal *afb,
+ integer *ldafb, integer *ipiv, doublereal *b, integer *ldb,
+ doublereal *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dgbsv_(integer *n, integer *kl, integer *ku, integer *
+ nrhs, doublereal *ab, integer *ldab, integer *ipiv, doublereal *b,
+ integer *ldb, integer *info);
+
+/* Subroutine */ int dgbsvx_(char *fact, char *trans, integer *n, integer *kl,
+ integer *ku, integer *nrhs, doublereal *ab, integer *ldab,
+ doublereal *afb, integer *ldafb, integer *ipiv, char *equed,
+ doublereal *r__, doublereal *c__, doublereal *b, integer *ldb,
+ doublereal *x, integer *ldx, doublereal *rcond, doublereal *ferr,
+ doublereal *berr, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dgbtf2_(integer *m, integer *n, integer *kl, integer *ku,
+ doublereal *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int dgbtrf_(integer *m, integer *n, integer *kl, integer *ku,
+ doublereal *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int dgbtrs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, doublereal *ab, integer *ldab, integer *ipiv,
+ doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dgebak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, doublereal *scale, integer *m, doublereal *v, integer *
+ ldv, integer *info);
+
+/* Subroutine */ int dgebal_(char *job, integer *n, doublereal *a, integer *
+ lda, integer *ilo, integer *ihi, doublereal *scale, integer *info);
+
+/* Subroutine */ int dgebd2_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *d__, doublereal *e, doublereal *tauq, doublereal *
+ taup, doublereal *work, integer *info);
+
+/* Subroutine */ int dgebrd_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *d__, doublereal *e, doublereal *tauq, doublereal *
+ taup, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dgecon_(char *norm, integer *n, doublereal *a, integer *
+ lda, doublereal *anorm, doublereal *rcond, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dgeequ_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *r__, doublereal *c__, doublereal *rowcnd, doublereal
+ *colcnd, doublereal *amax, integer *info);
+
+/* Subroutine */ int dgees_(char *jobvs, char *sort, L_fp select, integer *n,
+ doublereal *a, integer *lda, integer *sdim, doublereal *wr,
+ doublereal *wi, doublereal *vs, integer *ldvs, doublereal *work,
+ integer *lwork, logical *bwork, integer *info);
+
+/* Subroutine */ int dgeesx_(char *jobvs, char *sort, L_fp select, char *
+ sense, integer *n, doublereal *a, integer *lda, integer *sdim,
+ doublereal *wr, doublereal *wi, doublereal *vs, integer *ldvs,
+ doublereal *rconde, doublereal *rcondv, doublereal *work, integer *
+ lwork, integer *iwork, integer *liwork, logical *bwork, integer *info);
+
+/* Subroutine */ int dgeev_(char *jobvl, char *jobvr, integer *n, doublereal *
+ a, integer *lda, doublereal *wr, doublereal *wi, doublereal *vl,
+ integer *ldvl, doublereal *vr, integer *ldvr, doublereal *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int dgeevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, doublereal *a, integer *lda, doublereal *wr,
+ doublereal *wi, doublereal *vl, integer *ldvl, doublereal *vr,
+ integer *ldvr, integer *ilo, integer *ihi, doublereal *scale,
+ doublereal *abnrm, doublereal *rconde, doublereal *rcondv, doublereal
+ *work, integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int dgegs_(char *jobvsl, char *jobvsr, integer *n,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, doublereal *
+ alphar, doublereal *alphai, doublereal *beta, doublereal *vsl,
+ integer *ldvsl, doublereal *vsr, integer *ldvsr, doublereal *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int dgegv_(char *jobvl, char *jobvr, integer *n, doublereal *
+ a, integer *lda, doublereal *b, integer *ldb, doublereal *alphar,
+ doublereal *alphai, doublereal *beta, doublereal *vl, integer *ldvl,
+ doublereal *vr, integer *ldvr, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dgehd2_(integer *n, integer *ilo, integer *ihi,
+ doublereal *a, integer *lda, doublereal *tau, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dgehrd_(integer *n, integer *ilo, integer *ihi,
+ doublereal *a, integer *lda, doublereal *tau, doublereal *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int dgelq2_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dgelqf_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dgels_(char *trans, integer *m, integer *n, integer *
+ nrhs, doublereal *a, integer *lda, doublereal *b, integer *ldb,
+ doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dgelsd_(integer *m, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, doublereal *
+ s, doublereal *rcond, integer *rank, doublereal *work, integer *lwork,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int dgelss_(integer *m, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, doublereal *
+ s, doublereal *rcond, integer *rank, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dgelsx_(integer *m, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, integer *
+ jpvt, doublereal *rcond, integer *rank, doublereal *work, integer *
+ info);
+
+/* Subroutine */ int dgelsy_(integer *m, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, integer *
+ jpvt, doublereal *rcond, integer *rank, doublereal *work, integer *
+ lwork, integer *info);
+
+/* Subroutine */ int dgeql2_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dgeqlf_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dgeqp3_(integer *m, integer *n, doublereal *a, integer *
+ lda, integer *jpvt, doublereal *tau, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dgeqpf_(integer *m, integer *n, doublereal *a, integer *
+ lda, integer *jpvt, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dgeqr2_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dgeqrf_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dgerfs_(char *trans, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *af, integer *ldaf, integer *
+ ipiv, doublereal *b, integer *ldb, doublereal *x, integer *ldx,
+ doublereal *ferr, doublereal *berr, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dgerq2_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dgerqf_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dgesc2_(integer *n, doublereal *a, integer *lda,
+ doublereal *rhs, integer *ipiv, integer *jpiv, doublereal *scale);
+
+/* Subroutine */ int dgesdd_(char *jobz, integer *m, integer *n, doublereal *
+ a, integer *lda, doublereal *s, doublereal *u, integer *ldu,
+ doublereal *vt, integer *ldvt, doublereal *work, integer *lwork,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int dgesv_(integer *n, integer *nrhs, doublereal *a, integer
+ *lda, integer *ipiv, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dgesvd_(char *jobu, char *jobvt, integer *m, integer *n,
+ doublereal *a, integer *lda, doublereal *s, doublereal *u, integer *
+ ldu, doublereal *vt, integer *ldvt, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dgesvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, doublereal *a, integer *lda, doublereal *af, integer *ldaf,
+ integer *ipiv, char *equed, doublereal *r__, doublereal *c__,
+ doublereal *b, integer *ldb, doublereal *x, integer *ldx, doublereal *
+ rcond, doublereal *ferr, doublereal *berr, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dgetc2_(integer *n, doublereal *a, integer *lda, integer
+ *ipiv, integer *jpiv, integer *info);
+
+/* Subroutine */ int dgetf2_(integer *m, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, integer *info);
+
+/* Subroutine */ int dgetrf_(integer *m, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, integer *info);
+
+/* Subroutine */ int dgetri_(integer *n, doublereal *a, integer *lda, integer
+ *ipiv, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dgetrs_(char *trans, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, integer *ipiv, doublereal *b, integer *
+ ldb, integer *info);
+
+/* Subroutine */ int dggbak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, doublereal *lscale, doublereal *rscale, integer *m,
+ doublereal *v, integer *ldv, integer *info);
+
+/* Subroutine */ int dggbal_(char *job, integer *n, doublereal *a, integer *
+ lda, doublereal *b, integer *ldb, integer *ilo, integer *ihi,
+ doublereal *lscale, doublereal *rscale, doublereal *work, integer *
+ info);
+
+/* Subroutine */ int dgges_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ delctg, integer *n, doublereal *a, integer *lda, doublereal *b,
+ integer *ldb, integer *sdim, doublereal *alphar, doublereal *alphai,
+ doublereal *beta, doublereal *vsl, integer *ldvsl, doublereal *vsr,
+ integer *ldvsr, doublereal *work, integer *lwork, logical *bwork,
+ integer *info);
+
+/* Subroutine */ int dggesx_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ delctg, char *sense, integer *n, doublereal *a, integer *lda,
+ doublereal *b, integer *ldb, integer *sdim, doublereal *alphar,
+ doublereal *alphai, doublereal *beta, doublereal *vsl, integer *ldvsl,
+ doublereal *vsr, integer *ldvsr, doublereal *rconde, doublereal *
+ rcondv, doublereal *work, integer *lwork, integer *iwork, integer *
+ liwork, logical *bwork, integer *info);
+
+/* Subroutine */ int dggev_(char *jobvl, char *jobvr, integer *n, doublereal *
+ a, integer *lda, doublereal *b, integer *ldb, doublereal *alphar,
+ doublereal *alphai, doublereal *beta, doublereal *vl, integer *ldvl,
+ doublereal *vr, integer *ldvr, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dggevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, doublereal *a, integer *lda, doublereal *b,
+ integer *ldb, doublereal *alphar, doublereal *alphai, doublereal *
+ beta, doublereal *vl, integer *ldvl, doublereal *vr, integer *ldvr,
+ integer *ilo, integer *ihi, doublereal *lscale, doublereal *rscale,
+ doublereal *abnrm, doublereal *bbnrm, doublereal *rconde, doublereal *
+ rcondv, doublereal *work, integer *lwork, integer *iwork, logical *
+ bwork, integer *info);
+
+/* Subroutine */ int dggglm_(integer *n, integer *m, integer *p, doublereal *
+ a, integer *lda, doublereal *b, integer *ldb, doublereal *d__,
+ doublereal *x, doublereal *y, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dgghrd_(char *compq, char *compz, integer *n, integer *
+ ilo, integer *ihi, doublereal *a, integer *lda, doublereal *b,
+ integer *ldb, doublereal *q, integer *ldq, doublereal *z__, integer *
+ ldz, integer *info);
+
+/* Subroutine */ int dgglse_(integer *m, integer *n, integer *p, doublereal *
+ a, integer *lda, doublereal *b, integer *ldb, doublereal *c__,
+ doublereal *d__, doublereal *x, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dggqrf_(integer *n, integer *m, integer *p, doublereal *
+ a, integer *lda, doublereal *taua, doublereal *b, integer *ldb,
+ doublereal *taub, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dggrqf_(integer *m, integer *p, integer *n, doublereal *
+ a, integer *lda, doublereal *taua, doublereal *b, integer *ldb,
+ doublereal *taub, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dggsvd_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *n, integer *p, integer *k, integer *l, doublereal *a,
+ integer *lda, doublereal *b, integer *ldb, doublereal *alpha,
+ doublereal *beta, doublereal *u, integer *ldu, doublereal *v, integer
+ *ldv, doublereal *q, integer *ldq, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dggsvp_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, doublereal *a, integer *lda, doublereal *b,
+ integer *ldb, doublereal *tola, doublereal *tolb, integer *k, integer
+ *l, doublereal *u, integer *ldu, doublereal *v, integer *ldv,
+ doublereal *q, integer *ldq, integer *iwork, doublereal *tau,
+ doublereal *work, integer *info);
+
+/* Subroutine */ int dgtcon_(char *norm, integer *n, doublereal *dl,
+ doublereal *d__, doublereal *du, doublereal *du2, integer *ipiv,
+ doublereal *anorm, doublereal *rcond, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dgtrfs_(char *trans, integer *n, integer *nrhs,
+ doublereal *dl, doublereal *d__, doublereal *du, doublereal *dlf,
+ doublereal *df, doublereal *duf, doublereal *du2, integer *ipiv,
+ doublereal *b, integer *ldb, doublereal *x, integer *ldx, doublereal *
+ ferr, doublereal *berr, doublereal *work, integer *iwork, integer *
+ info);
+
+/* Subroutine */ int dgtsv_(integer *n, integer *nrhs, doublereal *dl,
+ doublereal *d__, doublereal *du, doublereal *b, integer *ldb, integer
+ *info);
+
+/* Subroutine */ int dgtsvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, doublereal *dl, doublereal *d__, doublereal *du, doublereal *
+ dlf, doublereal *df, doublereal *duf, doublereal *du2, integer *ipiv,
+ doublereal *b, integer *ldb, doublereal *x, integer *ldx, doublereal *
+ rcond, doublereal *ferr, doublereal *berr, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dgttrf_(integer *n, doublereal *dl, doublereal *d__,
+ doublereal *du, doublereal *du2, integer *ipiv, integer *info);
+
+/* Subroutine */ int dgttrs_(char *trans, integer *n, integer *nrhs,
+ doublereal *dl, doublereal *d__, doublereal *du, doublereal *du2,
+ integer *ipiv, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dgtts2_(integer *itrans, integer *n, integer *nrhs,
+ doublereal *dl, doublereal *d__, doublereal *du, doublereal *du2,
+ integer *ipiv, doublereal *b, integer *ldb);
+
+/* Subroutine */ int dhgeqz_(char *job, char *compq, char *compz, integer *n,
+ integer *ilo, integer *ihi, doublereal *a, integer *lda, doublereal *
+ b, integer *ldb, doublereal *alphar, doublereal *alphai, doublereal *
+ beta, doublereal *q, integer *ldq, doublereal *z__, integer *ldz,
+ doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dhsein_(char *side, char *eigsrc, char *initv, logical *
+ select, integer *n, doublereal *h__, integer *ldh, doublereal *wr,
+ doublereal *wi, doublereal *vl, integer *ldvl, doublereal *vr,
+ integer *ldvr, integer *mm, integer *m, doublereal *work, integer *
+ ifaill, integer *ifailr, integer *info);
+
+/* Subroutine */ int dhseqr_(char *job, char *compz, integer *n, integer *ilo,
+ integer *ihi, doublereal *h__, integer *ldh, doublereal *wr,
+ doublereal *wi, doublereal *z__, integer *ldz, doublereal *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int dlabad_(doublereal *small, doublereal *large);
+
+/* Subroutine */ int dlabrd_(integer *m, integer *n, integer *nb, doublereal *
+ a, integer *lda, doublereal *d__, doublereal *e, doublereal *tauq,
+ doublereal *taup, doublereal *x, integer *ldx, doublereal *y, integer
+ *ldy);
+
+/* Subroutine */ int dlacon_(integer *n, doublereal *v, doublereal *x,
+ integer *isgn, doublereal *est, integer *kase);
+
+/* Subroutine */ int dlacpy_(char *uplo, integer *m, integer *n, doublereal *
+ a, integer *lda, doublereal *b, integer *ldb);
+
+/* Subroutine */ int dladiv_(doublereal *a, doublereal *b, doublereal *c__,
+ doublereal *d__, doublereal *p, doublereal *q);
+
+/* Subroutine */ int dlae2_(doublereal *a, doublereal *b, doublereal *c__,
+ doublereal *rt1, doublereal *rt2);
+
+/* Subroutine */ int dlaebz_(integer *ijob, integer *nitmax, integer *n,
+ integer *mmax, integer *minp, integer *nbmin, doublereal *abstol,
+ doublereal *reltol, doublereal *pivmin, doublereal *d__, doublereal *
+ e, doublereal *e2, integer *nval, doublereal *ab, doublereal *c__,
+ integer *mout, integer *nab, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dlaed0_(integer *icompq, integer *qsiz, integer *n,
+ doublereal *d__, doublereal *e, doublereal *q, integer *ldq,
+ doublereal *qstore, integer *ldqs, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dlaed1_(integer *n, doublereal *d__, doublereal *q,
+ integer *ldq, integer *indxq, doublereal *rho, integer *cutpnt,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dlaed2_(integer *k, integer *n, integer *n1, doublereal *
+ d__, doublereal *q, integer *ldq, integer *indxq, doublereal *rho,
+ doublereal *z__, doublereal *dlamda, doublereal *w, doublereal *q2,
+ integer *indx, integer *indxc, integer *indxp, integer *coltyp,
+ integer *info);
+
+/* Subroutine */ int dlaed3_(integer *k, integer *n, integer *n1, doublereal *
+ d__, doublereal *q, integer *ldq, doublereal *rho, doublereal *dlamda,
+ doublereal *q2, integer *indx, integer *ctot, doublereal *w,
+ doublereal *s, integer *info);
+
+/* Subroutine */ int dlaed4_(integer *n, integer *i__, doublereal *d__,
+ doublereal *z__, doublereal *delta, doublereal *rho, doublereal *dlam,
+ integer *info);
+
+/* Subroutine */ int dlaed5_(integer *i__, doublereal *d__, doublereal *z__,
+ doublereal *delta, doublereal *rho, doublereal *dlam);
+
+/* Subroutine */ int dlaed6_(integer *kniter, logical *orgati, doublereal *
+ rho, doublereal *d__, doublereal *z__, doublereal *finit, doublereal *
+ tau, integer *info);
+
+/* Subroutine */ int dlaed7_(integer *icompq, integer *n, integer *qsiz,
+ integer *tlvls, integer *curlvl, integer *curpbm, doublereal *d__,
+ doublereal *q, integer *ldq, integer *indxq, doublereal *rho, integer
+ *cutpnt, doublereal *qstore, integer *qptr, integer *prmptr, integer *
+ perm, integer *givptr, integer *givcol, doublereal *givnum,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dlaed8_(integer *icompq, integer *k, integer *n, integer
+ *qsiz, doublereal *d__, doublereal *q, integer *ldq, integer *indxq,
+ doublereal *rho, integer *cutpnt, doublereal *z__, doublereal *dlamda,
+ doublereal *q2, integer *ldq2, doublereal *w, integer *perm, integer
+ *givptr, integer *givcol, doublereal *givnum, integer *indxp, integer
+ *indx, integer *info);
+
+/* Subroutine */ int dlaed9_(integer *k, integer *kstart, integer *kstop,
+ integer *n, doublereal *d__, doublereal *q, integer *ldq, doublereal *
+ rho, doublereal *dlamda, doublereal *w, doublereal *s, integer *lds,
+ integer *info);
+
+/* Subroutine */ int dlaeda_(integer *n, integer *tlvls, integer *curlvl,
+ integer *curpbm, integer *prmptr, integer *perm, integer *givptr,
+ integer *givcol, doublereal *givnum, doublereal *q, integer *qptr,
+ doublereal *z__, doublereal *ztemp, integer *info);
+
+/* Subroutine */ int dlaein_(logical *rightv, logical *noinit, integer *n,
+ doublereal *h__, integer *ldh, doublereal *wr, doublereal *wi,
+ doublereal *vr, doublereal *vi, doublereal *b, integer *ldb,
+ doublereal *work, doublereal *eps3, doublereal *smlnum, doublereal *
+ bignum, integer *info);
+
+/* Subroutine */ int dlaev2_(doublereal *a, doublereal *b, doublereal *c__,
+ doublereal *rt1, doublereal *rt2, doublereal *cs1, doublereal *sn1);
+
+/* Subroutine */ int dlaexc_(logical *wantq, integer *n, doublereal *t,
+ integer *ldt, doublereal *q, integer *ldq, integer *j1, integer *n1,
+ integer *n2, doublereal *work, integer *info);
+
+/* Subroutine */ int dlag2_(doublereal *a, integer *lda, doublereal *b,
+ integer *ldb, doublereal *safmin, doublereal *scale1, doublereal *
+ scale2, doublereal *wr1, doublereal *wr2, doublereal *wi);
+
+/* Subroutine */ int dlags2_(logical *upper, doublereal *a1, doublereal *a2,
+ doublereal *a3, doublereal *b1, doublereal *b2, doublereal *b3,
+ doublereal *csu, doublereal *snu, doublereal *csv, doublereal *snv,
+ doublereal *csq, doublereal *snq);
+
+/* Subroutine */ int dlagtf_(integer *n, doublereal *a, doublereal *lambda,
+ doublereal *b, doublereal *c__, doublereal *tol, doublereal *d__,
+ integer *in, integer *info);
+
+/* Subroutine */ int dlagtm_(char *trans, integer *n, integer *nrhs,
+ doublereal *alpha, doublereal *dl, doublereal *d__, doublereal *du,
+ doublereal *x, integer *ldx, doublereal *beta, doublereal *b, integer
+ *ldb);
+
+/* Subroutine */ int dlagts_(integer *job, integer *n, doublereal *a,
+ doublereal *b, doublereal *c__, doublereal *d__, integer *in,
+ doublereal *y, doublereal *tol, integer *info);
+
+/* Subroutine */ int dlagv2_(doublereal *a, integer *lda, doublereal *b,
+ integer *ldb, doublereal *alphar, doublereal *alphai, doublereal *
+ beta, doublereal *csl, doublereal *snl, doublereal *csr, doublereal *
+ snr);
+
+/* Subroutine */ int dlahqr_(logical *wantt, logical *wantz, integer *n,
+ integer *ilo, integer *ihi, doublereal *h__, integer *ldh, doublereal
+ *wr, doublereal *wi, integer *iloz, integer *ihiz, doublereal *z__,
+ integer *ldz, integer *info);
+
+/* Subroutine */ int dlahrd_(integer *n, integer *k, integer *nb, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *t, integer *ldt,
+ doublereal *y, integer *ldy);
+
+/* Subroutine */ int dlaic1_(integer *job, integer *j, doublereal *x,
+ doublereal *sest, doublereal *w, doublereal *gamma, doublereal *
+ sestpr, doublereal *s, doublereal *c__);
+
+/* Subroutine */ int dlaln2_(logical *ltrans, integer *na, integer *nw,
+ doublereal *smin, doublereal *ca, doublereal *a, integer *lda,
+ doublereal *d1, doublereal *d2, doublereal *b, integer *ldb,
+ doublereal *wr, doublereal *wi, doublereal *x, integer *ldx,
+ doublereal *scale, doublereal *xnorm, integer *info);
+
+/* Subroutine */ int dlals0_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, integer *nrhs, doublereal *b, integer *ldb, doublereal
+ *bx, integer *ldbx, integer *perm, integer *givptr, integer *givcol,
+ integer *ldgcol, doublereal *givnum, integer *ldgnum, doublereal *
+ poles, doublereal *difl, doublereal *difr, doublereal *z__, integer *
+ k, doublereal *c__, doublereal *s, doublereal *work, integer *info);
+
+/* Subroutine */ int dlalsa_(integer *icompq, integer *smlsiz, integer *n,
+ integer *nrhs, doublereal *b, integer *ldb, doublereal *bx, integer *
+ ldbx, doublereal *u, integer *ldu, doublereal *vt, integer *k,
+ doublereal *difl, doublereal *difr, doublereal *z__, doublereal *
+ poles, integer *givptr, integer *givcol, integer *ldgcol, integer *
+ perm, doublereal *givnum, doublereal *c__, doublereal *s, doublereal *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int dlalsd_(char *uplo, integer *smlsiz, integer *n, integer
+ *nrhs, doublereal *d__, doublereal *e, doublereal *b, integer *ldb,
+ doublereal *rcond, integer *rank, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dlamc1_(integer *beta, integer *t, logical *rnd, logical
+ *ieee1);
+
+/* Subroutine */ int dlamc2_(integer *beta, integer *t, logical *rnd,
+ doublereal *eps, integer *emin, doublereal *rmin, integer *emax,
+ doublereal *rmax);
+
+/* Subroutine */ int dlamc4_(integer *emin, doublereal *start, integer *base);
+
+/* Subroutine */ int dlamc5_(integer *beta, integer *p, integer *emin,
+ logical *ieee, integer *emax, doublereal *rmax);
+
+/* Subroutine */ int dlamrg_(integer *n1, integer *n2, doublereal *a, integer
+ *dtrd1, integer *dtrd2, integer *index);
+
+/* Subroutine */ int dlanv2_(doublereal *a, doublereal *b, doublereal *c__,
+ doublereal *d__, doublereal *rt1r, doublereal *rt1i, doublereal *rt2r,
+ doublereal *rt2i, doublereal *cs, doublereal *sn);
+
+/* Subroutine */ int dlapll_(integer *n, doublereal *x, integer *incx,
+ doublereal *y, integer *incy, doublereal *ssmin);
+
+/* Subroutine */ int dlapmt_(logical *forwrd, integer *m, integer *n,
+ doublereal *x, integer *ldx, integer *k);
+
+/* Subroutine */ int dlaqgb_(integer *m, integer *n, integer *kl, integer *ku,
+ doublereal *ab, integer *ldab, doublereal *r__, doublereal *c__,
+ doublereal *rowcnd, doublereal *colcnd, doublereal *amax, char *equed);
+
+/* Subroutine */ int dlaqge_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *r__, doublereal *c__, doublereal *rowcnd, doublereal
+ *colcnd, doublereal *amax, char *equed);
+
+/* Subroutine */ int dlaqp2_(integer *m, integer *n, integer *offset,
+ doublereal *a, integer *lda, integer *jpvt, doublereal *tau,
+ doublereal *vn1, doublereal *vn2, doublereal *work);
+
+/* Subroutine */ int dlaqps_(integer *m, integer *n, integer *offset, integer
+ *nb, integer *kb, doublereal *a, integer *lda, integer *jpvt,
+ doublereal *tau, doublereal *vn1, doublereal *vn2, doublereal *auxv,
+ doublereal *f, integer *ldf);
+
+/* Subroutine */ int dlaqsb_(char *uplo, integer *n, integer *kd, doublereal *
+ ab, integer *ldab, doublereal *s, doublereal *scond, doublereal *amax,
+ char *equed);
+
+/* Subroutine */ int dlaqsp_(char *uplo, integer *n, doublereal *ap,
+ doublereal *s, doublereal *scond, doublereal *amax, char *equed);
+
+/* Subroutine */ int dlaqsy_(char *uplo, integer *n, doublereal *a, integer *
+ lda, doublereal *s, doublereal *scond, doublereal *amax, char *equed);
+
+/* Subroutine */ int dlaqtr_(logical *ltran, logical *lreal, integer *n,
+ doublereal *t, integer *ldt, doublereal *b, doublereal *w, doublereal
+ *scale, doublereal *x, doublereal *work, integer *info);
+
+/* Subroutine */ int dlar1v_(integer *n, integer *b1, integer *bn, doublereal
+ *sigma, doublereal *d__, doublereal *l, doublereal *ld, doublereal *
+ lld, doublereal *gersch, doublereal *z__, doublereal *ztz, doublereal
+ *mingma, integer *r__, integer *isuppz, doublereal *work);
+
+/* Subroutine */ int dlar2v_(integer *n, doublereal *x, doublereal *y,
+ doublereal *z__, integer *incx, doublereal *c__, doublereal *s,
+ integer *incc);
+
+/* Subroutine */ int dlarf_(char *side, integer *m, integer *n, doublereal *v,
+ integer *incv, doublereal *tau, doublereal *c__, integer *ldc,
+ doublereal *work);
+
+/* Subroutine */ int dlarfb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, doublereal *v, integer *
+ ldv, doublereal *t, integer *ldt, doublereal *c__, integer *ldc,
+ doublereal *work, integer *ldwork);
+
+/* Subroutine */ int dlarfg_(integer *n, doublereal *alpha, doublereal *x,
+ integer *incx, doublereal *tau);
+
+/* Subroutine */ int dlarft_(char *direct, char *storev, integer *n, integer *
+ k, doublereal *v, integer *ldv, doublereal *tau, doublereal *t,
+ integer *ldt);
+
+/* Subroutine */ int dlarfx_(char *side, integer *m, integer *n, doublereal *
+ v, doublereal *tau, doublereal *c__, integer *ldc, doublereal *work);
+
+/* Subroutine */ int dlargv_(integer *n, doublereal *x, integer *incx,
+ doublereal *y, integer *incy, doublereal *c__, integer *incc);
+
+/* Subroutine */ int dlarnv_(integer *idist, integer *iseed, integer *n,
+ doublereal *x);
+
+/* Subroutine */ int dlarrb_(integer *n, doublereal *d__, doublereal *l,
+ doublereal *ld, doublereal *lld, integer *ifirst, integer *ilast,
+ doublereal *sigma, doublereal *reltol, doublereal *w, doublereal *
+ wgap, doublereal *werr, doublereal *work, integer *iwork, integer *
+ info);
+
+/* Subroutine */ int dlarre_(integer *n, doublereal *d__, doublereal *e,
+ doublereal *tol, integer *nsplit, integer *isplit, integer *m,
+ doublereal *w, doublereal *woff, doublereal *gersch, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dlarrf_(integer *n, doublereal *d__, doublereal *l,
+ doublereal *ld, doublereal *lld, integer *ifirst, integer *ilast,
+ doublereal *w, doublereal *dplus, doublereal *lplus, doublereal *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int dlarrv_(integer *n, doublereal *d__, doublereal *l,
+ integer *isplit, integer *m, doublereal *w, integer *iblock,
+ doublereal *gersch, doublereal *tol, doublereal *z__, integer *ldz,
+ integer *isuppz, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dlartg_(doublereal *f, doublereal *g, doublereal *cs,
+ doublereal *sn, doublereal *r__);
+
+/* Subroutine */ int dlartv_(integer *n, doublereal *x, integer *incx,
+ doublereal *y, integer *incy, doublereal *c__, doublereal *s, integer
+ *incc);
+
+/* Subroutine */ int dlaruv_(integer *iseed, integer *n, doublereal *x);
+
+/* Subroutine */ int dlarz_(char *side, integer *m, integer *n, integer *l,
+ doublereal *v, integer *incv, doublereal *tau, doublereal *c__,
+ integer *ldc, doublereal *work);
+
+/* Subroutine */ int dlarzb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, integer *l, doublereal *v,
+ integer *ldv, doublereal *t, integer *ldt, doublereal *c__, integer *
+ ldc, doublereal *work, integer *ldwork);
+
+/* Subroutine */ int dlarzt_(char *direct, char *storev, integer *n, integer *
+ k, doublereal *v, integer *ldv, doublereal *tau, doublereal *t,
+ integer *ldt);
+
+/* Subroutine */ int dlas2_(doublereal *f, doublereal *g, doublereal *h__,
+ doublereal *ssmin, doublereal *ssmax);
+
+/* Subroutine */ int dlascl_(char *type__, integer *kl, integer *ku,
+ doublereal *cfrom, doublereal *cto, integer *m, integer *n,
+ doublereal *a, integer *lda, integer *info);
+
+/* Subroutine */ int dlasd0_(integer *n, integer *sqre, doublereal *d__,
+ doublereal *e, doublereal *u, integer *ldu, doublereal *vt, integer *
+ ldvt, integer *smlsiz, integer *iwork, doublereal *work, integer *
+ info);
+
+/* Subroutine */ int dlasd1_(integer *nl, integer *nr, integer *sqre,
+ doublereal *d__, doublereal *alpha, doublereal *beta, doublereal *u,
+ integer *ldu, doublereal *vt, integer *ldvt, integer *idxq, integer *
+ iwork, doublereal *work, integer *info);
+
+/* Subroutine */ int dlasd2_(integer *nl, integer *nr, integer *sqre, integer
+ *k, doublereal *d__, doublereal *z__, doublereal *alpha, doublereal *
+ beta, doublereal *u, integer *ldu, doublereal *vt, integer *ldvt,
+ doublereal *dsigma, doublereal *u2, integer *ldu2, doublereal *vt2,
+ integer *ldvt2, integer *idxp, integer *idx, integer *idxc, integer *
+ idxq, integer *coltyp, integer *info);
+
+/* Subroutine */ int dlasd3_(integer *nl, integer *nr, integer *sqre, integer
+ *k, doublereal *d__, doublereal *q, integer *ldq, doublereal *dsigma,
+ doublereal *u, integer *ldu, doublereal *u2, integer *ldu2,
+ doublereal *vt, integer *ldvt, doublereal *vt2, integer *ldvt2,
+ integer *idxc, integer *ctot, doublereal *z__, integer *info);
+
+/* Subroutine */ int dlasd4_(integer *n, integer *i__, doublereal *d__,
+ doublereal *z__, doublereal *delta, doublereal *rho, doublereal *
+ sigma, doublereal *work, integer *info);
+
+/* Subroutine */ int dlasd5_(integer *i__, doublereal *d__, doublereal *z__,
+ doublereal *delta, doublereal *rho, doublereal *dsigma, doublereal *
+ work);
+
+/* Subroutine */ int dlasd6_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, doublereal *d__, doublereal *vf, doublereal *vl,
+ doublereal *alpha, doublereal *beta, integer *idxq, integer *perm,
+ integer *givptr, integer *givcol, integer *ldgcol, doublereal *givnum,
+ integer *ldgnum, doublereal *poles, doublereal *difl, doublereal *
+ difr, doublereal *z__, integer *k, doublereal *c__, doublereal *s,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dlasd7_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, integer *k, doublereal *d__, doublereal *z__,
+ doublereal *zw, doublereal *vf, doublereal *vfw, doublereal *vl,
+ doublereal *vlw, doublereal *alpha, doublereal *beta, doublereal *
+ dsigma, integer *idx, integer *idxp, integer *idxq, integer *perm,
+ integer *givptr, integer *givcol, integer *ldgcol, doublereal *givnum,
+ integer *ldgnum, doublereal *c__, doublereal *s, integer *info);
+
+/* Subroutine */ int dlasd8_(integer *icompq, integer *k, doublereal *d__,
+ doublereal *z__, doublereal *vf, doublereal *vl, doublereal *difl,
+ doublereal *difr, integer *lddifr, doublereal *dsigma, doublereal *
+ work, integer *info);
+
+/* Subroutine */ int dlasd9_(integer *icompq, integer *ldu, integer *k,
+ doublereal *d__, doublereal *z__, doublereal *vf, doublereal *vl,
+ doublereal *difl, doublereal *difr, doublereal *dsigma, doublereal *
+ work, integer *info);
+
+/* Subroutine */ int dlasda_(integer *icompq, integer *smlsiz, integer *n,
+ integer *sqre, doublereal *d__, doublereal *e, doublereal *u, integer
+ *ldu, doublereal *vt, integer *k, doublereal *difl, doublereal *difr,
+ doublereal *z__, doublereal *poles, integer *givptr, integer *givcol,
+ integer *ldgcol, integer *perm, doublereal *givnum, doublereal *c__,
+ doublereal *s, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dlasdq_(char *uplo, integer *sqre, integer *n, integer *
+ ncvt, integer *nru, integer *ncc, doublereal *d__, doublereal *e,
+ doublereal *vt, integer *ldvt, doublereal *u, integer *ldu,
+ doublereal *c__, integer *ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int dlasdt_(integer *n, integer *lvl, integer *nd, integer *
+ inode, integer *ndiml, integer *ndimr, integer *msub);
+
+/* Subroutine */ int dlaset_(char *uplo, integer *m, integer *n, doublereal *
+ alpha, doublereal *beta, doublereal *a, integer *lda);
+
+/* Subroutine */ int dlasq1_(integer *n, doublereal *d__, doublereal *e,
+ doublereal *work, integer *info);
+
+/* Subroutine */ int dlasq2_(integer *n, doublereal *z__, integer *info);
+
+/* Subroutine */ int dlasq3_(integer *i0, integer *n0, doublereal *z__,
+ integer *pp, doublereal *dmin__, doublereal *sigma, doublereal *desig,
+ doublereal *qmax, integer *nfail, integer *iter, integer *ndiv,
+ logical *ieee);
+
+/* Subroutine */ int dlasq4_(integer *i0, integer *n0, doublereal *z__,
+ integer *pp, integer *n0in, doublereal *dmin__, doublereal *dmin1,
+ doublereal *dmin2, doublereal *dn, doublereal *dn1, doublereal *dn2,
+ doublereal *tau, integer *ttype);
+
+/* Subroutine */ int dlasq5_(integer *i0, integer *n0, doublereal *z__,
+ integer *pp, doublereal *tau, doublereal *dmin__, doublereal *dmin1,
+ doublereal *dmin2, doublereal *dn, doublereal *dnm1, doublereal *dnm2,
+ logical *ieee);
+
+/* Subroutine */ int dlasq6_(integer *i0, integer *n0, doublereal *z__,
+ integer *pp, doublereal *dmin__, doublereal *dmin1, doublereal *dmin2,
+ doublereal *dn, doublereal *dnm1, doublereal *dnm2);
+
+/* Subroutine */ int dlasr_(char *side, char *pivot, char *direct, integer *m,
+ integer *n, doublereal *c__, doublereal *s, doublereal *a, integer *
+ lda);
+
+/* Subroutine */ int dlasrt_(char *id, integer *n, doublereal *d__, integer *
+ info);
+
+/* Subroutine */ int dlassq_(integer *n, doublereal *x, integer *incx,
+ doublereal *scale, doublereal *sumsq);
+
+/* Subroutine */ int dlasv2_(doublereal *f, doublereal *g, doublereal *h__,
+ doublereal *ssmin, doublereal *ssmax, doublereal *snr, doublereal *
+ csr, doublereal *snl, doublereal *csl);
+
+/* Subroutine */ int dlaswp_(integer *n, doublereal *a, integer *lda, integer
+ *k1, integer *k2, integer *ipiv, integer *incx);
+
+/* Subroutine */ int dlasy2_(logical *ltranl, logical *ltranr, integer *isgn,
+ integer *n1, integer *n2, doublereal *tl, integer *ldtl, doublereal *
+ tr, integer *ldtr, doublereal *b, integer *ldb, doublereal *scale,
+ doublereal *x, integer *ldx, doublereal *xnorm, integer *info);
+
+/* Subroutine */ int dlasyf_(char *uplo, integer *n, integer *nb, integer *kb,
+ doublereal *a, integer *lda, integer *ipiv, doublereal *w, integer *
+ ldw, integer *info);
+
+/* Subroutine */ int dlatbs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, integer *kd, doublereal *ab, integer *ldab,
+ doublereal *x, doublereal *scale, doublereal *cnorm, integer *info);
+
+/* Subroutine */ int dlatdf_(integer *ijob, integer *n, doublereal *z__,
+ integer *ldz, doublereal *rhs, doublereal *rdsum, doublereal *rdscal,
+ integer *ipiv, integer *jpiv);
+
+/* Subroutine */ int dlatps_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, doublereal *ap, doublereal *x, doublereal *scale,
+ doublereal *cnorm, integer *info);
+
+/* Subroutine */ int dlatrd_(char *uplo, integer *n, integer *nb, doublereal *
+ a, integer *lda, doublereal *e, doublereal *tau, doublereal *w,
+ integer *ldw);
+
+/* Subroutine */ int dlatrs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, doublereal *a, integer *lda, doublereal *x,
+ doublereal *scale, doublereal *cnorm, integer *info);
+
+/* Subroutine */ int dlatrz_(integer *m, integer *n, integer *l, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work);
+
+/* Subroutine */ int dlatzm_(char *side, integer *m, integer *n, doublereal *
+ v, integer *incv, doublereal *tau, doublereal *c1, doublereal *c2,
+ integer *ldc, doublereal *work);
+
+/* Subroutine */ int dlauu2_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *info);
+
+/* Subroutine */ int dlauum_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *info);
+
+/* Subroutine */ int dopgtr_(char *uplo, integer *n, doublereal *ap,
+ doublereal *tau, doublereal *q, integer *ldq, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dopmtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, doublereal *ap, doublereal *tau, doublereal *c__, integer
+ *ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int dorg2l_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dorg2r_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dorgbr_(char *vect, integer *m, integer *n, integer *k,
+ doublereal *a, integer *lda, doublereal *tau, doublereal *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int dorghr_(integer *n, integer *ilo, integer *ihi,
+ doublereal *a, integer *lda, doublereal *tau, doublereal *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int dorgl2_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dorglq_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dorgql_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dorgqr_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dorgr2_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *info);
+
+/* Subroutine */ int dorgrq_(integer *m, integer *n, integer *k, doublereal *
+ a, integer *lda, doublereal *tau, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dorgtr_(char *uplo, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dorm2l_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int dorm2r_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int dormbr_(char *vect, char *side, char *trans, integer *m,
+ integer *n, integer *k, doublereal *a, integer *lda, doublereal *tau,
+ doublereal *c__, integer *ldc, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dormhr_(char *side, char *trans, integer *m, integer *n,
+ integer *ilo, integer *ihi, doublereal *a, integer *lda, doublereal *
+ tau, doublereal *c__, integer *ldc, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dorml2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int dormlq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dormql_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dormqr_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dormr2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int dormr3_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, doublereal *a, integer *lda, doublereal *tau,
+ doublereal *c__, integer *ldc, doublereal *work, integer *info);
+
+/* Subroutine */ int dormrq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dormrz_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, doublereal *a, integer *lda, doublereal *tau,
+ doublereal *c__, integer *ldc, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dormtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, doublereal *a, integer *lda, doublereal *tau, doublereal *
+ c__, integer *ldc, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dpbcon_(char *uplo, integer *n, integer *kd, doublereal *
+ ab, integer *ldab, doublereal *anorm, doublereal *rcond, doublereal *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int dpbequ_(char *uplo, integer *n, integer *kd, doublereal *
+ ab, integer *ldab, doublereal *s, doublereal *scond, doublereal *amax,
+ integer *info);
+
+/* Subroutine */ int dpbrfs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, doublereal *ab, integer *ldab, doublereal *afb, integer *ldafb,
+ doublereal *b, integer *ldb, doublereal *x, integer *ldx, doublereal *
+ ferr, doublereal *berr, doublereal *work, integer *iwork, integer *
+ info);
+
+/* Subroutine */ int dpbstf_(char *uplo, integer *n, integer *kd, doublereal *
+ ab, integer *ldab, integer *info);
+
+/* Subroutine */ int dpbsv_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, doublereal *ab, integer *ldab, doublereal *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int dpbsvx_(char *fact, char *uplo, integer *n, integer *kd,
+ integer *nrhs, doublereal *ab, integer *ldab, doublereal *afb,
+ integer *ldafb, char *equed, doublereal *s, doublereal *b, integer *
+ ldb, doublereal *x, integer *ldx, doublereal *rcond, doublereal *ferr,
+ doublereal *berr, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dpbtf2_(char *uplo, integer *n, integer *kd, doublereal *
+ ab, integer *ldab, integer *info);
+
+/* Subroutine */ int dpbtrf_(char *uplo, integer *n, integer *kd, doublereal *
+ ab, integer *ldab, integer *info);
+
+/* Subroutine */ int dpbtrs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, doublereal *ab, integer *ldab, doublereal *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int dpocon_(char *uplo, integer *n, doublereal *a, integer *
+ lda, doublereal *anorm, doublereal *rcond, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dpoequ_(integer *n, doublereal *a, integer *lda,
+ doublereal *s, doublereal *scond, doublereal *amax, integer *info);
+
+/* Subroutine */ int dporfs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *af, integer *ldaf,
+ doublereal *b, integer *ldb, doublereal *x, integer *ldx, doublereal *
+ ferr, doublereal *berr, doublereal *work, integer *iwork, integer *
+ info);
+
+/* Subroutine */ int dposv_(char *uplo, integer *n, integer *nrhs, doublereal
+ *a, integer *lda, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dposvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublereal *a, integer *lda, doublereal *af, integer *ldaf,
+ char *equed, doublereal *s, doublereal *b, integer *ldb, doublereal *
+ x, integer *ldx, doublereal *rcond, doublereal *ferr, doublereal *
+ berr, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dpotf2_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *info);
+
+/* Subroutine */ int dpotrf_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *info);
+
+/* Subroutine */ int dpotri_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *info);
+
+/* Subroutine */ int dpotrs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int dppcon_(char *uplo, integer *n, doublereal *ap,
+ doublereal *anorm, doublereal *rcond, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dppequ_(char *uplo, integer *n, doublereal *ap,
+ doublereal *s, doublereal *scond, doublereal *amax, integer *info);
+
+/* Subroutine */ int dpprfs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *ap, doublereal *afp, doublereal *b, integer *ldb,
+ doublereal *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dppsv_(char *uplo, integer *n, integer *nrhs, doublereal
+ *ap, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dppsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublereal *ap, doublereal *afp, char *equed, doublereal *s,
+ doublereal *b, integer *ldb, doublereal *x, integer *ldx, doublereal *
+ rcond, doublereal *ferr, doublereal *berr, doublereal *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dpptrf_(char *uplo, integer *n, doublereal *ap, integer *
+ info);
+
+/* Subroutine */ int dpptri_(char *uplo, integer *n, doublereal *ap, integer *
+ info);
+
+/* Subroutine */ int dpptrs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *ap, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dptcon_(integer *n, doublereal *d__, doublereal *e,
+ doublereal *anorm, doublereal *rcond, doublereal *work, integer *info);
+
+/* Subroutine */ int dpteqr_(char *compz, integer *n, doublereal *d__,
+ doublereal *e, doublereal *z__, integer *ldz, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dptrfs_(integer *n, integer *nrhs, doublereal *d__,
+ doublereal *e, doublereal *df, doublereal *ef, doublereal *b, integer
+ *ldb, doublereal *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublereal *work, integer *info);
+
+/* Subroutine */ int dptsv_(integer *n, integer *nrhs, doublereal *d__,
+ doublereal *e, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dptsvx_(char *fact, integer *n, integer *nrhs,
+ doublereal *d__, doublereal *e, doublereal *df, doublereal *ef,
+ doublereal *b, integer *ldb, doublereal *x, integer *ldx, doublereal *
+ rcond, doublereal *ferr, doublereal *berr, doublereal *work, integer *
+ info);
+
+/* Subroutine */ int dpttrf_(integer *n, doublereal *d__, doublereal *e,
+ integer *info);
+
+/* Subroutine */ int dpttrs_(integer *n, integer *nrhs, doublereal *d__,
+ doublereal *e, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dptts2_(integer *n, integer *nrhs, doublereal *d__,
+ doublereal *e, doublereal *b, integer *ldb);
+
+/* Subroutine */ int drscl_(integer *n, doublereal *sa, doublereal *sx,
+ integer *incx);
+
+/* Subroutine */ int dsbev_(char *jobz, char *uplo, integer *n, integer *kd,
+ doublereal *ab, integer *ldab, doublereal *w, doublereal *z__,
+ integer *ldz, doublereal *work, integer *info);
+
+/* Subroutine */ int dsbevd_(char *jobz, char *uplo, integer *n, integer *kd,
+ doublereal *ab, integer *ldab, doublereal *w, doublereal *z__,
+ integer *ldz, doublereal *work, integer *lwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int dsbevx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *kd, doublereal *ab, integer *ldab, doublereal *q, integer *
+ ldq, doublereal *vl, doublereal *vu, integer *il, integer *iu,
+ doublereal *abstol, integer *m, doublereal *w, doublereal *z__,
+ integer *ldz, doublereal *work, integer *iwork, integer *ifail,
+ integer *info);
+
+/* Subroutine */ int dsbgst_(char *vect, char *uplo, integer *n, integer *ka,
+ integer *kb, doublereal *ab, integer *ldab, doublereal *bb, integer *
+ ldbb, doublereal *x, integer *ldx, doublereal *work, integer *info);
+
+/* Subroutine */ int dsbgv_(char *jobz, char *uplo, integer *n, integer *ka,
+ integer *kb, doublereal *ab, integer *ldab, doublereal *bb, integer *
+ ldbb, doublereal *w, doublereal *z__, integer *ldz, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dsbgvd_(char *jobz, char *uplo, integer *n, integer *ka,
+ integer *kb, doublereal *ab, integer *ldab, doublereal *bb, integer *
+ ldbb, doublereal *w, doublereal *z__, integer *ldz, doublereal *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dsbgvx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *ka, integer *kb, doublereal *ab, integer *ldab, doublereal *
+ bb, integer *ldbb, doublereal *q, integer *ldq, doublereal *vl,
+ doublereal *vu, integer *il, integer *iu, doublereal *abstol, integer
+ *m, doublereal *w, doublereal *z__, integer *ldz, doublereal *work,
+ integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int dsbtrd_(char *vect, char *uplo, integer *n, integer *kd,
+ doublereal *ab, integer *ldab, doublereal *d__, doublereal *e,
+ doublereal *q, integer *ldq, doublereal *work, integer *info);
+
+/* Subroutine */ int dspcon_(char *uplo, integer *n, doublereal *ap, integer *
+ ipiv, doublereal *anorm, doublereal *rcond, doublereal *work, integer
+ *iwork, integer *info);
+
+/* Subroutine */ int dspev_(char *jobz, char *uplo, integer *n, doublereal *
+ ap, doublereal *w, doublereal *z__, integer *ldz, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dspevd_(char *jobz, char *uplo, integer *n, doublereal *
+ ap, doublereal *w, doublereal *z__, integer *ldz, doublereal *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dspevx_(char *jobz, char *range, char *uplo, integer *n,
+ doublereal *ap, doublereal *vl, doublereal *vu, integer *il, integer *
+ iu, doublereal *abstol, integer *m, doublereal *w, doublereal *z__,
+ integer *ldz, doublereal *work, integer *iwork, integer *ifail,
+ integer *info);
+
+/* Subroutine */ int dspgst_(integer *itype, char *uplo, integer *n,
+ doublereal *ap, doublereal *bp, integer *info);
+
+/* Subroutine */ int dspgv_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublereal *ap, doublereal *bp, doublereal *w, doublereal *z__,
+ integer *ldz, doublereal *work, integer *info);
+
+/* Subroutine */ int dspgvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublereal *ap, doublereal *bp, doublereal *w, doublereal *z__,
+ integer *ldz, doublereal *work, integer *lwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int dspgvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, doublereal *ap, doublereal *bp, doublereal *vl,
+ doublereal *vu, integer *il, integer *iu, doublereal *abstol, integer
+ *m, doublereal *w, doublereal *z__, integer *ldz, doublereal *work,
+ integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int dsprfs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *ap, doublereal *afp, integer *ipiv, doublereal *b,
+ integer *ldb, doublereal *x, integer *ldx, doublereal *ferr,
+ doublereal *berr, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dspsv_(char *uplo, integer *n, integer *nrhs, doublereal
+ *ap, integer *ipiv, doublereal *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dspsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublereal *ap, doublereal *afp, integer *ipiv, doublereal *b,
+ integer *ldb, doublereal *x, integer *ldx, doublereal *rcond,
+ doublereal *ferr, doublereal *berr, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dsptrd_(char *uplo, integer *n, doublereal *ap,
+ doublereal *d__, doublereal *e, doublereal *tau, integer *info);
+
+/* Subroutine */ int dsptrf_(char *uplo, integer *n, doublereal *ap, integer *
+ ipiv, integer *info);
+
+/* Subroutine */ int dsptri_(char *uplo, integer *n, doublereal *ap, integer *
+ ipiv, doublereal *work, integer *info);
+
+/* Subroutine */ int dsptrs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *ap, integer *ipiv, doublereal *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int dstebz_(char *range, char *order, integer *n, doublereal
+ *vl, doublereal *vu, integer *il, integer *iu, doublereal *abstol,
+ doublereal *d__, doublereal *e, integer *m, integer *nsplit,
+ doublereal *w, integer *iblock, integer *isplit, doublereal *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int dstedc_(char *compz, integer *n, doublereal *d__,
+ doublereal *e, doublereal *z__, integer *ldz, doublereal *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dstegr_(char *jobz, char *range, integer *n, doublereal *
+ d__, doublereal *e, doublereal *vl, doublereal *vu, integer *il,
+ integer *iu, doublereal *abstol, integer *m, doublereal *w,
+ doublereal *z__, integer *ldz, integer *isuppz, doublereal *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dstein_(integer *n, doublereal *d__, doublereal *e,
+ integer *m, doublereal *w, integer *iblock, integer *isplit,
+ doublereal *z__, integer *ldz, doublereal *work, integer *iwork,
+ integer *ifail, integer *info);
+
+/* Subroutine */ int dsteqr_(char *compz, integer *n, doublereal *d__,
+ doublereal *e, doublereal *z__, integer *ldz, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dsterf_(integer *n, doublereal *d__, doublereal *e,
+ integer *info);
+
+/* Subroutine */ int dstev_(char *jobz, integer *n, doublereal *d__,
+ doublereal *e, doublereal *z__, integer *ldz, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int dstevd_(char *jobz, integer *n, doublereal *d__,
+ doublereal *e, doublereal *z__, integer *ldz, doublereal *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dstevr_(char *jobz, char *range, integer *n, doublereal *
+ d__, doublereal *e, doublereal *vl, doublereal *vu, integer *il,
+ integer *iu, doublereal *abstol, integer *m, doublereal *w,
+ doublereal *z__, integer *ldz, integer *isuppz, doublereal *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dstevx_(char *jobz, char *range, integer *n, doublereal *
+ d__, doublereal *e, doublereal *vl, doublereal *vu, integer *il,
+ integer *iu, doublereal *abstol, integer *m, doublereal *w,
+ doublereal *z__, integer *ldz, doublereal *work, integer *iwork,
+ integer *ifail, integer *info);
+
+/* Subroutine */ int dsycon_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, doublereal *anorm, doublereal *rcond, doublereal *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int dsyev_(char *jobz, char *uplo, integer *n, doublereal *a,
+ integer *lda, doublereal *w, doublereal *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int dsyevd_(char *jobz, char *uplo, integer *n, doublereal *
+ a, integer *lda, doublereal *w, doublereal *work, integer *lwork,
+ integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dsyevr_(char *jobz, char *range, char *uplo, integer *n,
+ doublereal *a, integer *lda, doublereal *vl, doublereal *vu, integer *
+ il, integer *iu, doublereal *abstol, integer *m, doublereal *w,
+ doublereal *z__, integer *ldz, integer *isuppz, doublereal *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int dsyevx_(char *jobz, char *range, char *uplo, integer *n,
+ doublereal *a, integer *lda, doublereal *vl, doublereal *vu, integer *
+ il, integer *iu, doublereal *abstol, integer *m, doublereal *w,
+ doublereal *z__, integer *ldz, doublereal *work, integer *lwork,
+ integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int dsygs2_(integer *itype, char *uplo, integer *n,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int dsygst_(integer *itype, char *uplo, integer *n,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int dsygv_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublereal *a, integer *lda, doublereal *b, integer *ldb,
+ doublereal *w, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dsygvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublereal *a, integer *lda, doublereal *b, integer *ldb,
+ doublereal *w, doublereal *work, integer *lwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int dsygvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, doublereal *a, integer *lda, doublereal *b, integer
+ *ldb, doublereal *vl, doublereal *vu, integer *il, integer *iu,
+ doublereal *abstol, integer *m, doublereal *w, doublereal *z__,
+ integer *ldz, doublereal *work, integer *lwork, integer *iwork,
+ integer *ifail, integer *info);
+
+/* Subroutine */ int dsyrfs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, doublereal *af, integer *ldaf, integer *
+ ipiv, doublereal *b, integer *ldb, doublereal *x, integer *ldx,
+ doublereal *ferr, doublereal *berr, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dsysv_(char *uplo, integer *n, integer *nrhs, doublereal
+ *a, integer *lda, integer *ipiv, doublereal *b, integer *ldb,
+ doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dsysvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublereal *a, integer *lda, doublereal *af, integer *ldaf,
+ integer *ipiv, doublereal *b, integer *ldb, doublereal *x, integer *
+ ldx, doublereal *rcond, doublereal *ferr, doublereal *berr,
+ doublereal *work, integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int dsytd2_(char *uplo, integer *n, doublereal *a, integer *
+ lda, doublereal *d__, doublereal *e, doublereal *tau, integer *info);
+
+/* Subroutine */ int dsytf2_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, integer *info);
+
+/* Subroutine */ int dsytrd_(char *uplo, integer *n, doublereal *a, integer *
+ lda, doublereal *d__, doublereal *e, doublereal *tau, doublereal *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int dsytrf_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dsytri_(char *uplo, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, doublereal *work, integer *info);
+
+/* Subroutine */ int dsytrs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, integer *ipiv, doublereal *b, integer *
+ ldb, integer *info);
+
+/* Subroutine */ int dtbcon_(char *norm, char *uplo, char *diag, integer *n,
+ integer *kd, doublereal *ab, integer *ldab, doublereal *rcond,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dtbrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, doublereal *ab, integer *ldab, doublereal
+ *b, integer *ldb, doublereal *x, integer *ldx, doublereal *ferr,
+ doublereal *berr, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dtbtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, doublereal *ab, integer *ldab, doublereal
+ *b, integer *ldb, integer *info);
+
+/* Subroutine */ int dtgevc_(char *side, char *howmny, logical *select,
+ integer *n, doublereal *a, integer *lda, doublereal *b, integer *ldb,
+ doublereal *vl, integer *ldvl, doublereal *vr, integer *ldvr, integer
+ *mm, integer *m, doublereal *work, integer *info);
+
+/* Subroutine */ int dtgex2_(logical *wantq, logical *wantz, integer *n,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, doublereal *
+ q, integer *ldq, doublereal *z__, integer *ldz, integer *j1, integer *
+ n1, integer *n2, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dtgexc_(logical *wantq, logical *wantz, integer *n,
+ doublereal *a, integer *lda, doublereal *b, integer *ldb, doublereal *
+ q, integer *ldq, doublereal *z__, integer *ldz, integer *ifst,
+ integer *ilst, doublereal *work, integer *lwork, integer *info);
+
+/* Subroutine */ int dtgsen_(integer *ijob, logical *wantq, logical *wantz,
+ logical *select, integer *n, doublereal *a, integer *lda, doublereal *
+ b, integer *ldb, doublereal *alphar, doublereal *alphai, doublereal *
+ beta, doublereal *q, integer *ldq, doublereal *z__, integer *ldz,
+ integer *m, doublereal *pl, doublereal *pr, doublereal *dif,
+ doublereal *work, integer *lwork, integer *iwork, integer *liwork,
+ integer *info);
+
+/* Subroutine */ int dtgsja_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, integer *k, integer *l, doublereal *a,
+ integer *lda, doublereal *b, integer *ldb, doublereal *tola,
+ doublereal *tolb, doublereal *alpha, doublereal *beta, doublereal *u,
+ integer *ldu, doublereal *v, integer *ldv, doublereal *q, integer *
+ ldq, doublereal *work, integer *ncycle, integer *info);
+
+/* Subroutine */ int dtgsna_(char *job, char *howmny, logical *select,
+ integer *n, doublereal *a, integer *lda, doublereal *b, integer *ldb,
+ doublereal *vl, integer *ldvl, doublereal *vr, integer *ldvr,
+ doublereal *s, doublereal *dif, integer *mm, integer *m, doublereal *
+ work, integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int dtgsy2_(char *trans, integer *ijob, integer *m, integer *
+ n, doublereal *a, integer *lda, doublereal *b, integer *ldb,
+ doublereal *c__, integer *ldc, doublereal *d__, integer *ldd,
+ doublereal *e, integer *lde, doublereal *f, integer *ldf, doublereal *
+ scale, doublereal *rdsum, doublereal *rdscal, integer *iwork, integer
+ *pq, integer *info);
+
+/* Subroutine */ int dtgsyl_(char *trans, integer *ijob, integer *m, integer *
+ n, doublereal *a, integer *lda, doublereal *b, integer *ldb,
+ doublereal *c__, integer *ldc, doublereal *d__, integer *ldd,
+ doublereal *e, integer *lde, doublereal *f, integer *ldf, doublereal *
+ scale, doublereal *dif, doublereal *work, integer *lwork, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dtpcon_(char *norm, char *uplo, char *diag, integer *n,
+ doublereal *ap, doublereal *rcond, doublereal *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int dtprfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublereal *ap, doublereal *b, integer *ldb,
+ doublereal *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dtptri_(char *uplo, char *diag, integer *n, doublereal *
+ ap, integer *info);
+
+/* Subroutine */ int dtptrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublereal *ap, doublereal *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int dtrcon_(char *norm, char *uplo, char *diag, integer *n,
+ doublereal *a, integer *lda, doublereal *rcond, doublereal *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int dtrevc_(char *side, char *howmny, logical *select,
+ integer *n, doublereal *t, integer *ldt, doublereal *vl, integer *
+ ldvl, doublereal *vr, integer *ldvr, integer *mm, integer *m,
+ doublereal *work, integer *info);
+
+/* Subroutine */ int dtrexc_(char *compq, integer *n, doublereal *t, integer *
+ ldt, doublereal *q, integer *ldq, integer *ifst, integer *ilst,
+ doublereal *work, integer *info);
+
+/* Subroutine */ int dtrrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublereal *a, integer *lda, doublereal *b, integer *
+ ldb, doublereal *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int dtrsen_(char *job, char *compq, logical *select, integer
+ *n, doublereal *t, integer *ldt, doublereal *q, integer *ldq,
+ doublereal *wr, doublereal *wi, integer *m, doublereal *s, doublereal
+ *sep, doublereal *work, integer *lwork, integer *iwork, integer *
+ liwork, integer *info);
+
+/* Subroutine */ int dtrsna_(char *job, char *howmny, logical *select,
+ integer *n, doublereal *t, integer *ldt, doublereal *vl, integer *
+ ldvl, doublereal *vr, integer *ldvr, doublereal *s, doublereal *sep,
+ integer *mm, integer *m, doublereal *work, integer *ldwork, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int dtrsyl_(char *trana, char *tranb, integer *isgn, integer
+ *m, integer *n, doublereal *a, integer *lda, doublereal *b, integer *
+ ldb, doublereal *c__, integer *ldc, doublereal *scale, integer *info);
+
+/* Subroutine */ int dtrti2_(char *uplo, char *diag, integer *n, doublereal *
+ a, integer *lda, integer *info);
+
+/* Subroutine */ int dtrtri_(char *uplo, char *diag, integer *n, doublereal *
+ a, integer *lda, integer *info);
+
+/* Subroutine */ int dtrtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublereal *a, integer *lda, doublereal *b, integer *
+ ldb, integer *info);
+
+/* Subroutine */ int dtzrqf_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, integer *info);
+
+/* Subroutine */ int dtzrzf_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublereal *tau, doublereal *work, integer *lwork, integer *info);
+
+integer icmax1_(integer *n, complex *cx, integer *incx);
+
+integer ieeeck_(integer *ispec, real *zero, real *one);
+
+integer ilaenv_(integer *ispec, char *name__, char *opts, integer *n1,
+ integer *n2, integer *n3, integer *n4, ftnlen name_len, ftnlen
+ opts_len);
+
+integer izmax1_(integer *n, doublecomplex *cx, integer *incx);
+
+/* Subroutine */ int sbdsdc_(char *uplo, char *compq, integer *n, real *d__,
+ real *e, real *u, integer *ldu, real *vt, integer *ldvt, real *q,
+ integer *iq, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sbdsqr_(char *uplo, integer *n, integer *ncvt, integer *
+ nru, integer *ncc, real *d__, real *e, real *vt, integer *ldvt, real *
+ u, integer *ldu, real *c__, integer *ldc, real *work, integer *info);
+
+/* Subroutine */ int sdisna_(char *job, integer *m, integer *n, real *d__,
+ real *sep, integer *info);
+
+/* Subroutine */ int sgbbrd_(char *vect, integer *m, integer *n, integer *ncc,
+ integer *kl, integer *ku, real *ab, integer *ldab, real *d__, real *
+ e, real *q, integer *ldq, real *pt, integer *ldpt, real *c__, integer
+ *ldc, real *work, integer *info);
+
+/* Subroutine */ int sgbcon_(char *norm, integer *n, integer *kl, integer *ku,
+ real *ab, integer *ldab, integer *ipiv, real *anorm, real *rcond,
+ real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sgbequ_(integer *m, integer *n, integer *kl, integer *ku,
+ real *ab, integer *ldab, real *r__, real *c__, real *rowcnd, real *
+ colcnd, real *amax, integer *info);
+
+/* Subroutine */ int sgbrfs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, real *ab, integer *ldab, real *afb, integer *ldafb,
+ integer *ipiv, real *b, integer *ldb, real *x, integer *ldx, real *
+ ferr, real *berr, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sgbsv_(integer *n, integer *kl, integer *ku, integer *
+ nrhs, real *ab, integer *ldab, integer *ipiv, real *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int sgbsvx_(char *fact, char *trans, integer *n, integer *kl,
+ integer *ku, integer *nrhs, real *ab, integer *ldab, real *afb,
+ integer *ldafb, integer *ipiv, char *equed, real *r__, real *c__,
+ real *b, integer *ldb, real *x, integer *ldx, real *rcond, real *ferr,
+ real *berr, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sgbtf2_(integer *m, integer *n, integer *kl, integer *ku,
+ real *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int sgbtrf_(integer *m, integer *n, integer *kl, integer *ku,
+ real *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int sgbtrs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, real *ab, integer *ldab, integer *ipiv, real *b,
+ integer *ldb, integer *info);
+
+/* Subroutine */ int sgebak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, real *scale, integer *m, real *v, integer *ldv, integer
+ *info);
+
+/* Subroutine */ int sgebal_(char *job, integer *n, real *a, integer *lda,
+ integer *ilo, integer *ihi, real *scale, integer *info);
+
+/* Subroutine */ int sgebd2_(integer *m, integer *n, real *a, integer *lda,
+ real *d__, real *e, real *tauq, real *taup, real *work, integer *info);
+
+/* Subroutine */ int sgebrd_(integer *m, integer *n, real *a, integer *lda,
+ real *d__, real *e, real *tauq, real *taup, real *work, integer *
+ lwork, integer *info);
+
+/* Subroutine */ int sgecon_(char *norm, integer *n, real *a, integer *lda,
+ real *anorm, real *rcond, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sgeequ_(integer *m, integer *n, real *a, integer *lda,
+ real *r__, real *c__, real *rowcnd, real *colcnd, real *amax, integer
+ *info);
+
+/* Subroutine */ int sgees_(char *jobvs, char *sort, L_fp select, integer *n,
+ real *a, integer *lda, integer *sdim, real *wr, real *wi, real *vs,
+ integer *ldvs, real *work, integer *lwork, logical *bwork, integer *
+ info);
+
+/* Subroutine */ int sgeesx_(char *jobvs, char *sort, L_fp select, char *
+ sense, integer *n, real *a, integer *lda, integer *sdim, real *wr,
+ real *wi, real *vs, integer *ldvs, real *rconde, real *rcondv, real *
+ work, integer *lwork, integer *iwork, integer *liwork, logical *bwork,
+ integer *info);
+
+/* Subroutine */ int sgeev_(char *jobvl, char *jobvr, integer *n, real *a,
+ integer *lda, real *wr, real *wi, real *vl, integer *ldvl, real *vr,
+ integer *ldvr, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgeevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, real *a, integer *lda, real *wr, real *wi, real *
+ vl, integer *ldvl, real *vr, integer *ldvr, integer *ilo, integer *
+ ihi, real *scale, real *abnrm, real *rconde, real *rcondv, real *work,
+ integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int sgegs_(char *jobvsl, char *jobvsr, integer *n, real *a,
+ integer *lda, real *b, integer *ldb, real *alphar, real *alphai, real
+ *beta, real *vsl, integer *ldvsl, real *vsr, integer *ldvsr, real *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgegv_(char *jobvl, char *jobvr, integer *n, real *a,
+ integer *lda, real *b, integer *ldb, real *alphar, real *alphai, real
+ *beta, real *vl, integer *ldvl, real *vr, integer *ldvr, real *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int sgehd2_(integer *n, integer *ilo, integer *ihi, real *a,
+ integer *lda, real *tau, real *work, integer *info);
+
+/* Subroutine */ int sgehrd_(integer *n, integer *ilo, integer *ihi, real *a,
+ integer *lda, real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgelq2_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *info);
+
+/* Subroutine */ int sgelqf_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgels_(char *trans, integer *m, integer *n, integer *
+ nrhs, real *a, integer *lda, real *b, integer *ldb, real *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int sgelsd_(integer *m, integer *n, integer *nrhs, real *a,
+ integer *lda, real *b, integer *ldb, real *s, real *rcond, integer *
+ rank, real *work, integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int sgelss_(integer *m, integer *n, integer *nrhs, real *a,
+ integer *lda, real *b, integer *ldb, real *s, real *rcond, integer *
+ rank, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgelsx_(integer *m, integer *n, integer *nrhs, real *a,
+ integer *lda, real *b, integer *ldb, integer *jpvt, real *rcond,
+ integer *rank, real *work, integer *info);
+
+/* Subroutine */ int sgelsy_(integer *m, integer *n, integer *nrhs, real *a,
+ integer *lda, real *b, integer *ldb, integer *jpvt, real *rcond,
+ integer *rank, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgeql2_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *info);
+
+/* Subroutine */ int sgeqlf_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgeqp3_(integer *m, integer *n, real *a, integer *lda,
+ integer *jpvt, real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgeqpf_(integer *m, integer *n, real *a, integer *lda,
+ integer *jpvt, real *tau, real *work, integer *info);
+
+/* Subroutine */ int sgeqr2_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *info);
+
+/* Subroutine */ int sgeqrf_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgerfs_(char *trans, integer *n, integer *nrhs, real *a,
+ integer *lda, real *af, integer *ldaf, integer *ipiv, real *b,
+ integer *ldb, real *x, integer *ldx, real *ferr, real *berr, real *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int sgerq2_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *info);
+
+/* Subroutine */ int sgerqf_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgesc2_(integer *n, real *a, integer *lda, real *rhs,
+ integer *ipiv, integer *jpiv, real *scale);
+
+/* Subroutine */ int sgesdd_(char *jobz, integer *m, integer *n, real *a,
+ integer *lda, real *s, real *u, integer *ldu, real *vt, integer *ldvt,
+ real *work, integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int sgesv_(integer *n, integer *nrhs, real *a, integer *lda,
+ integer *ipiv, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sgesvd_(char *jobu, char *jobvt, integer *m, integer *n,
+ real *a, integer *lda, real *s, real *u, integer *ldu, real *vt,
+ integer *ldvt, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgesvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, real *a, integer *lda, real *af, integer *ldaf, integer *ipiv,
+ char *equed, real *r__, real *c__, real *b, integer *ldb, real *x,
+ integer *ldx, real *rcond, real *ferr, real *berr, real *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int sgetc2_(integer *n, real *a, integer *lda, integer *ipiv,
+ integer *jpiv, integer *info);
+
+/* Subroutine */ int sgetf2_(integer *m, integer *n, real *a, integer *lda,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int sgetrf_(integer *m, integer *n, real *a, integer *lda,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int sgetri_(integer *n, real *a, integer *lda, integer *ipiv,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgetrs_(char *trans, integer *n, integer *nrhs, real *a,
+ integer *lda, integer *ipiv, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sggbak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, real *lscale, real *rscale, integer *m, real *v,
+ integer *ldv, integer *info);
+
+/* Subroutine */ int sggbal_(char *job, integer *n, real *a, integer *lda,
+ real *b, integer *ldb, integer *ilo, integer *ihi, real *lscale, real
+ *rscale, real *work, integer *info);
+
+/* Subroutine */ int sgges_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ selctg, integer *n, real *a, integer *lda, real *b, integer *ldb,
+ integer *sdim, real *alphar, real *alphai, real *beta, real *vsl,
+ integer *ldvsl, real *vsr, integer *ldvsr, real *work, integer *lwork,
+ logical *bwork, integer *info);
+
+/* Subroutine */ int sggesx_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ selctg, char *sense, integer *n, real *a, integer *lda, real *b,
+ integer *ldb, integer *sdim, real *alphar, real *alphai, real *beta,
+ real *vsl, integer *ldvsl, real *vsr, integer *ldvsr, real *rconde,
+ real *rcondv, real *work, integer *lwork, integer *iwork, integer *
+ liwork, logical *bwork, integer *info);
+
+/* Subroutine */ int sggev_(char *jobvl, char *jobvr, integer *n, real *a,
+ integer *lda, real *b, integer *ldb, real *alphar, real *alphai, real
+ *beta, real *vl, integer *ldvl, real *vr, integer *ldvr, real *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int sggevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, real *a, integer *lda, real *b, integer *ldb, real
+ *alphar, real *alphai, real *beta, real *vl, integer *ldvl, real *vr,
+ integer *ldvr, integer *ilo, integer *ihi, real *lscale, real *rscale,
+ real *abnrm, real *bbnrm, real *rconde, real *rcondv, real *work,
+ integer *lwork, integer *iwork, logical *bwork, integer *info);
+
+/* Subroutine */ int sggglm_(integer *n, integer *m, integer *p, real *a,
+ integer *lda, real *b, integer *ldb, real *d__, real *x, real *y,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sgghrd_(char *compq, char *compz, integer *n, integer *
+ ilo, integer *ihi, real *a, integer *lda, real *b, integer *ldb, real
+ *q, integer *ldq, real *z__, integer *ldz, integer *info);
+
+/* Subroutine */ int sgglse_(integer *m, integer *n, integer *p, real *a,
+ integer *lda, real *b, integer *ldb, real *c__, real *d__, real *x,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sggqrf_(integer *n, integer *m, integer *p, real *a,
+ integer *lda, real *taua, real *b, integer *ldb, real *taub, real *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int sggrqf_(integer *m, integer *p, integer *n, real *a,
+ integer *lda, real *taua, real *b, integer *ldb, real *taub, real *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int sggsvd_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *n, integer *p, integer *k, integer *l, real *a, integer *lda,
+ real *b, integer *ldb, real *alpha, real *beta, real *u, integer *
+ ldu, real *v, integer *ldv, real *q, integer *ldq, real *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int sggsvp_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, real *a, integer *lda, real *b, integer *ldb,
+ real *tola, real *tolb, integer *k, integer *l, real *u, integer *ldu,
+ real *v, integer *ldv, real *q, integer *ldq, integer *iwork, real *
+ tau, real *work, integer *info);
+
+/* Subroutine */ int sgtcon_(char *norm, integer *n, real *dl, real *d__,
+ real *du, real *du2, integer *ipiv, real *anorm, real *rcond, real *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int sgtrfs_(char *trans, integer *n, integer *nrhs, real *dl,
+ real *d__, real *du, real *dlf, real *df, real *duf, real *du2,
+ integer *ipiv, real *b, integer *ldb, real *x, integer *ldx, real *
+ ferr, real *berr, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sgtsv_(integer *n, integer *nrhs, real *dl, real *d__,
+ real *du, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sgtsvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, real *dl, real *d__, real *du, real *dlf, real *df, real *duf,
+ real *du2, integer *ipiv, real *b, integer *ldb, real *x, integer *
+ ldx, real *rcond, real *ferr, real *berr, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int sgttrf_(integer *n, real *dl, real *d__, real *du, real *
+ du2, integer *ipiv, integer *info);
+
+/* Subroutine */ int sgttrs_(char *trans, integer *n, integer *nrhs, real *dl,
+ real *d__, real *du, real *du2, integer *ipiv, real *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int sgtts2_(integer *itrans, integer *n, integer *nrhs, real
+ *dl, real *d__, real *du, real *du2, integer *ipiv, real *b, integer *
+ ldb);
+
+/* Subroutine */ int shgeqz_(char *job, char *compq, char *compz, integer *n,
+ integer *ilo, integer *ihi, real *a, integer *lda, real *b, integer *
+ ldb, real *alphar, real *alphai, real *beta, real *q, integer *ldq,
+ real *z__, integer *ldz, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int shsein_(char *side, char *eigsrc, char *initv, logical *
+ select, integer *n, real *h__, integer *ldh, real *wr, real *wi, real
+ *vl, integer *ldvl, real *vr, integer *ldvr, integer *mm, integer *m,
+ real *work, integer *ifaill, integer *ifailr, integer *info);
+
+/* Subroutine */ int shseqr_(char *job, char *compz, integer *n, integer *ilo,
+ integer *ihi, real *h__, integer *ldh, real *wr, real *wi, real *z__,
+ integer *ldz, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int slabad_(real *small, real *large);
+
+/* Subroutine */ int slabrd_(integer *m, integer *n, integer *nb, real *a,
+ integer *lda, real *d__, real *e, real *tauq, real *taup, real *x,
+ integer *ldx, real *y, integer *ldy);
+
+/* Subroutine */ int slacon_(integer *n, real *v, real *x, integer *isgn,
+ real *est, integer *kase);
+
+/* Subroutine */ int slacpy_(char *uplo, integer *m, integer *n, real *a,
+ integer *lda, real *b, integer *ldb);
+
+/* Subroutine */ int sladiv_(real *a, real *b, real *c__, real *d__, real *p,
+ real *q);
+
+/* Subroutine */ int slae2_(real *a, real *b, real *c__, real *rt1, real *rt2);
+
+/* Subroutine */ int slaebz_(integer *ijob, integer *nitmax, integer *n,
+ integer *mmax, integer *minp, integer *nbmin, real *abstol, real *
+ reltol, real *pivmin, real *d__, real *e, real *e2, integer *nval,
+ real *ab, real *c__, integer *mout, integer *nab, real *work, integer
+ *iwork, integer *info);
+
+/* Subroutine */ int slaed0_(integer *icompq, integer *qsiz, integer *n, real
+ *d__, real *e, real *q, integer *ldq, real *qstore, integer *ldqs,
+ real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int slaed1_(integer *n, real *d__, real *q, integer *ldq,
+ integer *indxq, real *rho, integer *cutpnt, real *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int slaed2_(integer *k, integer *n, integer *n1, real *d__,
+ real *q, integer *ldq, integer *indxq, real *rho, real *z__, real *
+ dlamda, real *w, real *q2, integer *indx, integer *indxc, integer *
+ indxp, integer *coltyp, integer *info);
+
+/* Subroutine */ int slaed3_(integer *k, integer *n, integer *n1, real *d__,
+ real *q, integer *ldq, real *rho, real *dlamda, real *q2, integer *
+ indx, integer *ctot, real *w, real *s, integer *info);
+
+/* Subroutine */ int slaed4_(integer *n, integer *i__, real *d__, real *z__,
+ real *delta, real *rho, real *dlam, integer *info);
+
+/* Subroutine */ int slaed5_(integer *i__, real *d__, real *z__, real *delta,
+ real *rho, real *dlam);
+
+/* Subroutine */ int slaed6_(integer *kniter, logical *orgati, real *rho,
+ real *d__, real *z__, real *finit, real *tau, integer *info);
+
+/* Subroutine */ int slaed7_(integer *icompq, integer *n, integer *qsiz,
+ integer *tlvls, integer *curlvl, integer *curpbm, real *d__, real *q,
+ integer *ldq, integer *indxq, real *rho, integer *cutpnt, real *
+ qstore, integer *qptr, integer *prmptr, integer *perm, integer *
+ givptr, integer *givcol, real *givnum, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int slaed8_(integer *icompq, integer *k, integer *n, integer
+ *qsiz, real *d__, real *q, integer *ldq, integer *indxq, real *rho,
+ integer *cutpnt, real *z__, real *dlamda, real *q2, integer *ldq2,
+ real *w, integer *perm, integer *givptr, integer *givcol, real *
+ givnum, integer *indxp, integer *indx, integer *info);
+
+/* Subroutine */ int slaed9_(integer *k, integer *kstart, integer *kstop,
+ integer *n, real *d__, real *q, integer *ldq, real *rho, real *dlamda,
+ real *w, real *s, integer *lds, integer *info);
+
+/* Subroutine */ int slaeda_(integer *n, integer *tlvls, integer *curlvl,
+ integer *curpbm, integer *prmptr, integer *perm, integer *givptr,
+ integer *givcol, real *givnum, real *q, integer *qptr, real *z__,
+ real *ztemp, integer *info);
+
+/* Subroutine */ int slaein_(logical *rightv, logical *noinit, integer *n,
+ real *h__, integer *ldh, real *wr, real *wi, real *vr, real *vi, real
+ *b, integer *ldb, real *work, real *eps3, real *smlnum, real *bignum,
+ integer *info);
+
+/* Subroutine */ int slaev2_(real *a, real *b, real *c__, real *rt1, real *
+ rt2, real *cs1, real *sn1);
+
+/* Subroutine */ int slaexc_(logical *wantq, integer *n, real *t, integer *
+ ldt, real *q, integer *ldq, integer *j1, integer *n1, integer *n2,
+ real *work, integer *info);
+
+/* Subroutine */ int slag2_(real *a, integer *lda, real *b, integer *ldb,
+ real *safmin, real *scale1, real *scale2, real *wr1, real *wr2, real *
+ wi);
+
+/* Subroutine */ int slags2_(logical *upper, real *a1, real *a2, real *a3,
+ real *b1, real *b2, real *b3, real *csu, real *snu, real *csv, real *
+ snv, real *csq, real *snq);
+
+/* Subroutine */ int slagtf_(integer *n, real *a, real *lambda, real *b, real
+ *c__, real *tol, real *d__, integer *in, integer *info);
+
+/* Subroutine */ int slagtm_(char *trans, integer *n, integer *nrhs, real *
+ alpha, real *dl, real *d__, real *du, real *x, integer *ldx, real *
+ beta, real *b, integer *ldb);
+
+/* Subroutine */ int slagts_(integer *job, integer *n, real *a, real *b, real
+ *c__, real *d__, integer *in, real *y, real *tol, integer *info);
+
+/* Subroutine */ int slagv2_(real *a, integer *lda, real *b, integer *ldb,
+ real *alphar, real *alphai, real *beta, real *csl, real *snl, real *
+ csr, real *snr);
+
+/* Subroutine */ int slahqr_(logical *wantt, logical *wantz, integer *n,
+ integer *ilo, integer *ihi, real *h__, integer *ldh, real *wr, real *
+ wi, integer *iloz, integer *ihiz, real *z__, integer *ldz, integer *
+ info);
+
+/* Subroutine */ int slahrd_(integer *n, integer *k, integer *nb, real *a,
+ integer *lda, real *tau, real *t, integer *ldt, real *y, integer *ldy);
+
+/* Subroutine */ int slaic1_(integer *job, integer *j, real *x, real *sest,
+ real *w, real *gamma, real *sestpr, real *s, real *c__);
+
+/* Subroutine */ int slaln2_(logical *ltrans, integer *na, integer *nw, real *
+ smin, real *ca, real *a, integer *lda, real *d1, real *d2, real *b,
+ integer *ldb, real *wr, real *wi, real *x, integer *ldx, real *scale,
+ real *xnorm, integer *info);
+
+/* Subroutine */ int slals0_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, integer *nrhs, real *b, integer *ldb, real *bx,
+ integer *ldbx, integer *perm, integer *givptr, integer *givcol,
+ integer *ldgcol, real *givnum, integer *ldgnum, real *poles, real *
+ difl, real *difr, real *z__, integer *k, real *c__, real *s, real *
+ work, integer *info);
+
+/* Subroutine */ int slalsa_(integer *icompq, integer *smlsiz, integer *n,
+ integer *nrhs, real *b, integer *ldb, real *bx, integer *ldbx, real *
+ u, integer *ldu, real *vt, integer *k, real *difl, real *difr, real *
+ z__, real *poles, integer *givptr, integer *givcol, integer *ldgcol,
+ integer *perm, real *givnum, real *c__, real *s, real *work, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int slalsd_(char *uplo, integer *smlsiz, integer *n, integer
+ *nrhs, real *d__, real *e, real *b, integer *ldb, real *rcond,
+ integer *rank, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int slamc1_(integer *beta, integer *t, logical *rnd, logical
+ *ieee1);
+
+/* Subroutine */ int slamc2_(integer *beta, integer *t, logical *rnd, real *
+ eps, integer *emin, real *rmin, integer *emax, real *rmax);
+
+/* Subroutine */ int slamc4_(integer *emin, real *start, integer *base);
+
+/* Subroutine */ int slamc5_(integer *beta, integer *p, integer *emin,
+ logical *ieee, integer *emax, real *rmax);
+
+/* Subroutine */ int slamrg_(integer *n1, integer *n2, real *a, integer *
+ strd1, integer *strd2, integer *index);
+
+/* Subroutine */ int slanv2_(real *a, real *b, real *c__, real *d__, real *
+ rt1r, real *rt1i, real *rt2r, real *rt2i, real *cs, real *sn);
+
+/* Subroutine */ int slapll_(integer *n, real *x, integer *incx, real *y,
+ integer *incy, real *ssmin);
+
+/* Subroutine */ int slapmt_(logical *forwrd, integer *m, integer *n, real *x,
+ integer *ldx, integer *k);
+
+/* Subroutine */ int slaqgb_(integer *m, integer *n, integer *kl, integer *ku,
+ real *ab, integer *ldab, real *r__, real *c__, real *rowcnd, real *
+ colcnd, real *amax, char *equed);
+
+/* Subroutine */ int slaqge_(integer *m, integer *n, real *a, integer *lda,
+ real *r__, real *c__, real *rowcnd, real *colcnd, real *amax, char *
+ equed);
+
+/* Subroutine */ int slaqp2_(integer *m, integer *n, integer *offset, real *a,
+ integer *lda, integer *jpvt, real *tau, real *vn1, real *vn2, real *
+ work);
+
+/* Subroutine */ int slaqps_(integer *m, integer *n, integer *offset, integer
+ *nb, integer *kb, real *a, integer *lda, integer *jpvt, real *tau,
+ real *vn1, real *vn2, real *auxv, real *f, integer *ldf);
+
+/* Subroutine */ int slaqsb_(char *uplo, integer *n, integer *kd, real *ab,
+ integer *ldab, real *s, real *scond, real *amax, char *equed);
+
+/* Subroutine */ int slaqsp_(char *uplo, integer *n, real *ap, real *s, real *
+ scond, real *amax, char *equed);
+
+/* Subroutine */ int slaqsy_(char *uplo, integer *n, real *a, integer *lda,
+ real *s, real *scond, real *amax, char *equed);
+
+/* Subroutine */ int slaqtr_(logical *ltran, logical *lreal, integer *n, real
+ *t, integer *ldt, real *b, real *w, real *scale, real *x, real *work,
+ integer *info);
+
+/* Subroutine */ int slar1v_(integer *n, integer *b1, integer *bn, real *
+ sigma, real *d__, real *l, real *ld, real *lld, real *gersch, real *
+ z__, real *ztz, real *mingma, integer *r__, integer *isuppz, real *
+ work);
+
+/* Subroutine */ int slar2v_(integer *n, real *x, real *y, real *z__, integer
+ *incx, real *c__, real *s, integer *incc);
+
+/* Subroutine */ int slarf_(char *side, integer *m, integer *n, real *v,
+ integer *incv, real *tau, real *c__, integer *ldc, real *work);
+
+/* Subroutine */ int slarfb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, real *v, integer *ldv,
+ real *t, integer *ldt, real *c__, integer *ldc, real *work, integer *
+ ldwork);
+
+/* Subroutine */ int slarfg_(integer *n, real *alpha, real *x, integer *incx,
+ real *tau);
+
+/* Subroutine */ int slarft_(char *direct, char *storev, integer *n, integer *
+ k, real *v, integer *ldv, real *tau, real *t, integer *ldt);
+
+/* Subroutine */ int slarfx_(char *side, integer *m, integer *n, real *v,
+ real *tau, real *c__, integer *ldc, real *work);
+
+/* Subroutine */ int slargv_(integer *n, real *x, integer *incx, real *y,
+ integer *incy, real *c__, integer *incc);
+
+/* Subroutine */ int slarnv_(integer *idist, integer *iseed, integer *n, real
+ *x);
+
+/* Subroutine */ int slarrb_(integer *n, real *d__, real *l, real *ld, real *
+ lld, integer *ifirst, integer *ilast, real *sigma, real *reltol, real
+ *w, real *wgap, real *werr, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int slarre_(integer *n, real *d__, real *e, real *tol,
+ integer *nsplit, integer *isplit, integer *m, real *w, real *woff,
+ real *gersch, real *work, integer *info);
+
+/* Subroutine */ int slarrf_(integer *n, real *d__, real *l, real *ld, real *
+ lld, integer *ifirst, integer *ilast, real *w, real *dplus, real *
+ lplus, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int slarrv_(integer *n, real *d__, real *l, integer *isplit,
+ integer *m, real *w, integer *iblock, real *gersch, real *tol, real *
+ z__, integer *ldz, integer *isuppz, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int slartg_(real *f, real *g, real *cs, real *sn, real *r__);
+
+/* Subroutine */ int slartv_(integer *n, real *x, integer *incx, real *y,
+ integer *incy, real *c__, real *s, integer *incc);
+
+/* Subroutine */ int slaruv_(integer *iseed, integer *n, real *x);
+
+/* Subroutine */ int slarz_(char *side, integer *m, integer *n, integer *l,
+ real *v, integer *incv, real *tau, real *c__, integer *ldc, real *
+ work);
+
+/* Subroutine */ int slarzb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, integer *l, real *v,
+ integer *ldv, real *t, integer *ldt, real *c__, integer *ldc, real *
+ work, integer *ldwork);
+
+/* Subroutine */ int slarzt_(char *direct, char *storev, integer *n, integer *
+ k, real *v, integer *ldv, real *tau, real *t, integer *ldt);
+
+/* Subroutine */ int slas2_(real *f, real *g, real *h__, real *ssmin, real *
+ ssmax);
+
+/* Subroutine */ int slascl_(char *type__, integer *kl, integer *ku, real *
+ cfrom, real *cto, integer *m, integer *n, real *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int slasd0_(integer *n, integer *sqre, real *d__, real *e,
+ real *u, integer *ldu, real *vt, integer *ldvt, integer *smlsiz,
+ integer *iwork, real *work, integer *info);
+
+/* Subroutine */ int slasd1_(integer *nl, integer *nr, integer *sqre, real *
+ d__, real *alpha, real *beta, real *u, integer *ldu, real *vt,
+ integer *ldvt, integer *idxq, integer *iwork, real *work, integer *
+ info);
+
+/* Subroutine */ int slasd2_(integer *nl, integer *nr, integer *sqre, integer
+ *k, real *d__, real *z__, real *alpha, real *beta, real *u, integer *
+ ldu, real *vt, integer *ldvt, real *dsigma, real *u2, integer *ldu2,
+ real *vt2, integer *ldvt2, integer *idxp, integer *idx, integer *idxc,
+ integer *idxq, integer *coltyp, integer *info);
+
+/* Subroutine */ int slasd3_(integer *nl, integer *nr, integer *sqre, integer
+ *k, real *d__, real *q, integer *ldq, real *dsigma, real *u, integer *
+ ldu, real *u2, integer *ldu2, real *vt, integer *ldvt, real *vt2,
+ integer *ldvt2, integer *idxc, integer *ctot, real *z__, integer *
+ info);
+
+/* Subroutine */ int slasd4_(integer *n, integer *i__, real *d__, real *z__,
+ real *delta, real *rho, real *sigma, real *work, integer *info);
+
+/* Subroutine */ int slasd5_(integer *i__, real *d__, real *z__, real *delta,
+ real *rho, real *dsigma, real *work);
+
+/* Subroutine */ int slasd6_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, real *d__, real *vf, real *vl, real *alpha, real *beta,
+ integer *idxq, integer *perm, integer *givptr, integer *givcol,
+ integer *ldgcol, real *givnum, integer *ldgnum, real *poles, real *
+ difl, real *difr, real *z__, integer *k, real *c__, real *s, real *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int slasd7_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, integer *k, real *d__, real *z__, real *zw, real *vf,
+ real *vfw, real *vl, real *vlw, real *alpha, real *beta, real *dsigma,
+ integer *idx, integer *idxp, integer *idxq, integer *perm, integer *
+ givptr, integer *givcol, integer *ldgcol, real *givnum, integer *
+ ldgnum, real *c__, real *s, integer *info);
+
+/* Subroutine */ int slasd8_(integer *icompq, integer *k, real *d__, real *
+ z__, real *vf, real *vl, real *difl, real *difr, integer *lddifr,
+ real *dsigma, real *work, integer *info);
+
+/* Subroutine */ int slasd9_(integer *icompq, integer *ldu, integer *k, real *
+ d__, real *z__, real *vf, real *vl, real *difl, real *difr, real *
+ dsigma, real *work, integer *info);
+
+/* Subroutine */ int slasda_(integer *icompq, integer *smlsiz, integer *n,
+ integer *sqre, real *d__, real *e, real *u, integer *ldu, real *vt,
+ integer *k, real *difl, real *difr, real *z__, real *poles, integer *
+ givptr, integer *givcol, integer *ldgcol, integer *perm, real *givnum,
+ real *c__, real *s, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int slasdq_(char *uplo, integer *sqre, integer *n, integer *
+ ncvt, integer *nru, integer *ncc, real *d__, real *e, real *vt,
+ integer *ldvt, real *u, integer *ldu, real *c__, integer *ldc, real *
+ work, integer *info);
+
+/* Subroutine */ int slasdt_(integer *n, integer *lvl, integer *nd, integer *
+ inode, integer *ndiml, integer *ndimr, integer *msub);
+
+/* Subroutine */ int slaset_(char *uplo, integer *m, integer *n, real *alpha,
+ real *beta, real *a, integer *lda);
+
+/* Subroutine */ int slasq1_(integer *n, real *d__, real *e, real *work,
+ integer *info);
+
+/* Subroutine */ int slasq2_(integer *n, real *z__, integer *info);
+
+/* Subroutine */ int slasq3_(integer *i0, integer *n0, real *z__, integer *pp,
+ real *dmin__, real *sigma, real *desig, real *qmax, integer *nfail,
+ integer *iter, integer *ndiv, logical *ieee);
+
+/* Subroutine */ int slasq4_(integer *i0, integer *n0, real *z__, integer *pp,
+ integer *n0in, real *dmin__, real *dmin1, real *dmin2, real *dn,
+ real *dn1, real *dn2, real *tau, integer *ttype);
+
+/* Subroutine */ int slasq5_(integer *i0, integer *n0, real *z__, integer *pp,
+ real *tau, real *dmin__, real *dmin1, real *dmin2, real *dn, real *
+ dnm1, real *dnm2, logical *ieee);
+
+/* Subroutine */ int slasq6_(integer *i0, integer *n0, real *z__, integer *pp,
+ real *dmin__, real *dmin1, real *dmin2, real *dn, real *dnm1, real *
+ dnm2);
+
+/* Subroutine */ int slasr_(char *side, char *pivot, char *direct, integer *m,
+ integer *n, real *c__, real *s, real *a, integer *lda);
+
+/* Subroutine */ int slasrt_(char *id, integer *n, real *d__, integer *info);
+
+/* Subroutine */ int slassq_(integer *n, real *x, integer *incx, real *scale,
+ real *sumsq);
+
+/* Subroutine */ int slasv2_(real *f, real *g, real *h__, real *ssmin, real *
+ ssmax, real *snr, real *csr, real *snl, real *csl);
+
+/* Subroutine */ int slaswp_(integer *n, real *a, integer *lda, integer *k1,
+ integer *k2, integer *ipiv, integer *incx);
+
+/* Subroutine */ int slasy2_(logical *ltranl, logical *ltranr, integer *isgn,
+ integer *n1, integer *n2, real *tl, integer *ldtl, real *tr, integer *
+ ldtr, real *b, integer *ldb, real *scale, real *x, integer *ldx, real
+ *xnorm, integer *info);
+
+/* Subroutine */ int slasyf_(char *uplo, integer *n, integer *nb, integer *kb,
+ real *a, integer *lda, integer *ipiv, real *w, integer *ldw, integer
+ *info);
+
+/* Subroutine */ int slatbs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, integer *kd, real *ab, integer *ldab, real *x,
+ real *scale, real *cnorm, integer *info);
+
+/* Subroutine */ int slatdf_(integer *ijob, integer *n, real *z__, integer *
+ ldz, real *rhs, real *rdsum, real *rdscal, integer *ipiv, integer *
+ jpiv);
+
+/* Subroutine */ int slatps_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, real *ap, real *x, real *scale, real *cnorm,
+ integer *info);
+
+/* Subroutine */ int slatrd_(char *uplo, integer *n, integer *nb, real *a,
+ integer *lda, real *e, real *tau, real *w, integer *ldw);
+
+/* Subroutine */ int slatrs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, real *a, integer *lda, real *x, real *scale, real
+ *cnorm, integer *info);
+
+/* Subroutine */ int slatrz_(integer *m, integer *n, integer *l, real *a,
+ integer *lda, real *tau, real *work);
+
+/* Subroutine */ int slatzm_(char *side, integer *m, integer *n, real *v,
+ integer *incv, real *tau, real *c1, real *c2, integer *ldc, real *
+ work);
+
+/* Subroutine */ int slauu2_(char *uplo, integer *n, real *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int slauum_(char *uplo, integer *n, real *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int sopgtr_(char *uplo, integer *n, real *ap, real *tau,
+ real *q, integer *ldq, real *work, integer *info);
+
+/* Subroutine */ int sopmtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, real *ap, real *tau, real *c__, integer *ldc, real *work,
+ integer *info);
+
+/* Subroutine */ int sorg2l_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *info);
+
+/* Subroutine */ int sorg2r_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *info);
+
+/* Subroutine */ int sorgbr_(char *vect, integer *m, integer *n, integer *k,
+ real *a, integer *lda, real *tau, real *work, integer *lwork, integer
+ *info);
+
+/* Subroutine */ int sorghr_(integer *n, integer *ilo, integer *ihi, real *a,
+ integer *lda, real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sorgl2_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *info);
+
+/* Subroutine */ int sorglq_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sorgql_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sorgqr_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sorgr2_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *info);
+
+/* Subroutine */ int sorgrq_(integer *m, integer *n, integer *k, real *a,
+ integer *lda, real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sorgtr_(char *uplo, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sorm2l_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *info);
+
+/* Subroutine */ int sorm2r_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *info);
+
+/* Subroutine */ int sormbr_(char *vect, char *side, char *trans, integer *m,
+ integer *n, integer *k, real *a, integer *lda, real *tau, real *c__,
+ integer *ldc, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sormhr_(char *side, char *trans, integer *m, integer *n,
+ integer *ilo, integer *ihi, real *a, integer *lda, real *tau, real *
+ c__, integer *ldc, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sorml2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *info);
+
+/* Subroutine */ int sormlq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sormql_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sormqr_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sormr2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *info);
+
+/* Subroutine */ int sormr3_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, real *a, integer *lda, real *tau, real *c__,
+ integer *ldc, real *work, integer *info);
+
+/* Subroutine */ int sormrq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sormrz_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, real *a, integer *lda, real *tau, real *c__,
+ integer *ldc, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int sormtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, real *a, integer *lda, real *tau, real *c__, integer *ldc,
+ real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int spbcon_(char *uplo, integer *n, integer *kd, real *ab,
+ integer *ldab, real *anorm, real *rcond, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int spbequ_(char *uplo, integer *n, integer *kd, real *ab,
+ integer *ldab, real *s, real *scond, real *amax, integer *info);
+
+/* Subroutine */ int spbrfs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, real *ab, integer *ldab, real *afb, integer *ldafb, real *b,
+ integer *ldb, real *x, integer *ldx, real *ferr, real *berr, real *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int spbstf_(char *uplo, integer *n, integer *kd, real *ab,
+ integer *ldab, integer *info);
+
+/* Subroutine */ int spbsv_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, real *ab, integer *ldab, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int spbsvx_(char *fact, char *uplo, integer *n, integer *kd,
+ integer *nrhs, real *ab, integer *ldab, real *afb, integer *ldafb,
+ char *equed, real *s, real *b, integer *ldb, real *x, integer *ldx,
+ real *rcond, real *ferr, real *berr, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int spbtf2_(char *uplo, integer *n, integer *kd, real *ab,
+ integer *ldab, integer *info);
+
+/* Subroutine */ int spbtrf_(char *uplo, integer *n, integer *kd, real *ab,
+ integer *ldab, integer *info);
+
+/* Subroutine */ int spbtrs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, real *ab, integer *ldab, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int spocon_(char *uplo, integer *n, real *a, integer *lda,
+ real *anorm, real *rcond, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int spoequ_(integer *n, real *a, integer *lda, real *s, real
+ *scond, real *amax, integer *info);
+
+/* Subroutine */ int sporfs_(char *uplo, integer *n, integer *nrhs, real *a,
+ integer *lda, real *af, integer *ldaf, real *b, integer *ldb, real *x,
+ integer *ldx, real *ferr, real *berr, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int sposv_(char *uplo, integer *n, integer *nrhs, real *a,
+ integer *lda, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sposvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, real *a, integer *lda, real *af, integer *ldaf, char *equed,
+ real *s, real *b, integer *ldb, real *x, integer *ldx, real *rcond,
+ real *ferr, real *berr, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int spotf2_(char *uplo, integer *n, real *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int spotrf_(char *uplo, integer *n, real *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int spotri_(char *uplo, integer *n, real *a, integer *lda,
+ integer *info);
+
+/* Subroutine */ int spotrs_(char *uplo, integer *n, integer *nrhs, real *a,
+ integer *lda, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sppcon_(char *uplo, integer *n, real *ap, real *anorm,
+ real *rcond, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sppequ_(char *uplo, integer *n, real *ap, real *s, real *
+ scond, real *amax, integer *info);
+
+/* Subroutine */ int spprfs_(char *uplo, integer *n, integer *nrhs, real *ap,
+ real *afp, real *b, integer *ldb, real *x, integer *ldx, real *ferr,
+ real *berr, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sppsv_(char *uplo, integer *n, integer *nrhs, real *ap,
+ real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sppsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, real *ap, real *afp, char *equed, real *s, real *b, integer *
+ ldb, real *x, integer *ldx, real *rcond, real *ferr, real *berr, real
+ *work, integer *iwork, integer *info);
+
+/* Subroutine */ int spptrf_(char *uplo, integer *n, real *ap, integer *info);
+
+/* Subroutine */ int spptri_(char *uplo, integer *n, real *ap, integer *info);
+
+/* Subroutine */ int spptrs_(char *uplo, integer *n, integer *nrhs, real *ap,
+ real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sptcon_(integer *n, real *d__, real *e, real *anorm,
+ real *rcond, real *work, integer *info);
+
+/* Subroutine */ int spteqr_(char *compz, integer *n, real *d__, real *e,
+ real *z__, integer *ldz, real *work, integer *info);
+
+/* Subroutine */ int sptrfs_(integer *n, integer *nrhs, real *d__, real *e,
+ real *df, real *ef, real *b, integer *ldb, real *x, integer *ldx,
+ real *ferr, real *berr, real *work, integer *info);
+
+/* Subroutine */ int sptsv_(integer *n, integer *nrhs, real *d__, real *e,
+ real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sptsvx_(char *fact, integer *n, integer *nrhs, real *d__,
+ real *e, real *df, real *ef, real *b, integer *ldb, real *x, integer
+ *ldx, real *rcond, real *ferr, real *berr, real *work, integer *info);
+
+/* Subroutine */ int spttrf_(integer *n, real *d__, real *e, integer *info);
+
+/* Subroutine */ int spttrs_(integer *n, integer *nrhs, real *d__, real *e,
+ real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sptts2_(integer *n, integer *nrhs, real *d__, real *e,
+ real *b, integer *ldb);
+
+/* Subroutine */ int srscl_(integer *n, real *sa, real *sx, integer *incx);
+
+/* Subroutine */ int ssbev_(char *jobz, char *uplo, integer *n, integer *kd,
+ real *ab, integer *ldab, real *w, real *z__, integer *ldz, real *work,
+ integer *info);
+
+/* Subroutine */ int ssbevd_(char *jobz, char *uplo, integer *n, integer *kd,
+ real *ab, integer *ldab, real *w, real *z__, integer *ldz, real *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int ssbevx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *kd, real *ab, integer *ldab, real *q, integer *ldq, real *vl,
+ real *vu, integer *il, integer *iu, real *abstol, integer *m, real *
+ w, real *z__, integer *ldz, real *work, integer *iwork, integer *
+ ifail, integer *info);
+
+/* Subroutine */ int ssbgst_(char *vect, char *uplo, integer *n, integer *ka,
+ integer *kb, real *ab, integer *ldab, real *bb, integer *ldbb, real *
+ x, integer *ldx, real *work, integer *info);
+
+/* Subroutine */ int ssbgv_(char *jobz, char *uplo, integer *n, integer *ka,
+ integer *kb, real *ab, integer *ldab, real *bb, integer *ldbb, real *
+ w, real *z__, integer *ldz, real *work, integer *info);
+
+/* Subroutine */ int ssbgvd_(char *jobz, char *uplo, integer *n, integer *ka,
+ integer *kb, real *ab, integer *ldab, real *bb, integer *ldbb, real *
+ w, real *z__, integer *ldz, real *work, integer *lwork, integer *
+ iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int ssbgvx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *ka, integer *kb, real *ab, integer *ldab, real *bb, integer *
+ ldbb, real *q, integer *ldq, real *vl, real *vu, integer *il, integer
+ *iu, real *abstol, integer *m, real *w, real *z__, integer *ldz, real
+ *work, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int ssbtrd_(char *vect, char *uplo, integer *n, integer *kd,
+ real *ab, integer *ldab, real *d__, real *e, real *q, integer *ldq,
+ real *work, integer *info);
+
+/* Subroutine */ int sspcon_(char *uplo, integer *n, real *ap, integer *ipiv,
+ real *anorm, real *rcond, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sspev_(char *jobz, char *uplo, integer *n, real *ap,
+ real *w, real *z__, integer *ldz, real *work, integer *info);
+
+/* Subroutine */ int sspevd_(char *jobz, char *uplo, integer *n, real *ap,
+ real *w, real *z__, integer *ldz, real *work, integer *lwork, integer
+ *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int sspevx_(char *jobz, char *range, char *uplo, integer *n,
+ real *ap, real *vl, real *vu, integer *il, integer *iu, real *abstol,
+ integer *m, real *w, real *z__, integer *ldz, real *work, integer *
+ iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int sspgst_(integer *itype, char *uplo, integer *n, real *ap,
+ real *bp, integer *info);
+
+/* Subroutine */ int sspgv_(integer *itype, char *jobz, char *uplo, integer *
+ n, real *ap, real *bp, real *w, real *z__, integer *ldz, real *work,
+ integer *info);
+
+/* Subroutine */ int sspgvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, real *ap, real *bp, real *w, real *z__, integer *ldz, real *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int sspgvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, real *ap, real *bp, real *vl, real *vu, integer *il,
+ integer *iu, real *abstol, integer *m, real *w, real *z__, integer *
+ ldz, real *work, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int ssprfs_(char *uplo, integer *n, integer *nrhs, real *ap,
+ real *afp, integer *ipiv, real *b, integer *ldb, real *x, integer *
+ ldx, real *ferr, real *berr, real *work, integer *iwork, integer *
+ info);
+
+/* Subroutine */ int sspsv_(char *uplo, integer *n, integer *nrhs, real *ap,
+ integer *ipiv, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sspsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, real *ap, real *afp, integer *ipiv, real *b, integer *ldb, real
+ *x, integer *ldx, real *rcond, real *ferr, real *berr, real *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int ssptrd_(char *uplo, integer *n, real *ap, real *d__,
+ real *e, real *tau, integer *info);
+
+/* Subroutine */ int ssptrf_(char *uplo, integer *n, real *ap, integer *ipiv,
+ integer *info);
+
+/* Subroutine */ int ssptri_(char *uplo, integer *n, real *ap, integer *ipiv,
+ real *work, integer *info);
+
+/* Subroutine */ int ssptrs_(char *uplo, integer *n, integer *nrhs, real *ap,
+ integer *ipiv, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int sstebz_(char *range, char *order, integer *n, real *vl,
+ real *vu, integer *il, integer *iu, real *abstol, real *d__, real *e,
+ integer *m, integer *nsplit, real *w, integer *iblock, integer *
+ isplit, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int sstedc_(char *compz, integer *n, real *d__, real *e,
+ real *z__, integer *ldz, real *work, integer *lwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int sstegr_(char *jobz, char *range, integer *n, real *d__,
+ real *e, real *vl, real *vu, integer *il, integer *iu, real *abstol,
+ integer *m, real *w, real *z__, integer *ldz, integer *isuppz, real *
+ work, integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int sstein_(integer *n, real *d__, real *e, integer *m, real
+ *w, integer *iblock, integer *isplit, real *z__, integer *ldz, real *
+ work, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int ssteqr_(char *compz, integer *n, real *d__, real *e,
+ real *z__, integer *ldz, real *work, integer *info);
+
+/* Subroutine */ int ssterf_(integer *n, real *d__, real *e, integer *info);
+
+/* Subroutine */ int sstev_(char *jobz, integer *n, real *d__, real *e, real *
+ z__, integer *ldz, real *work, integer *info);
+
+/* Subroutine */ int sstevd_(char *jobz, integer *n, real *d__, real *e, real
+ *z__, integer *ldz, real *work, integer *lwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int sstevr_(char *jobz, char *range, integer *n, real *d__,
+ real *e, real *vl, real *vu, integer *il, integer *iu, real *abstol,
+ integer *m, real *w, real *z__, integer *ldz, integer *isuppz, real *
+ work, integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int sstevx_(char *jobz, char *range, integer *n, real *d__,
+ real *e, real *vl, real *vu, integer *il, integer *iu, real *abstol,
+ integer *m, real *w, real *z__, integer *ldz, real *work, integer *
+ iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int ssycon_(char *uplo, integer *n, real *a, integer *lda,
+ integer *ipiv, real *anorm, real *rcond, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int ssyev_(char *jobz, char *uplo, integer *n, real *a,
+ integer *lda, real *w, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int ssyevd_(char *jobz, char *uplo, integer *n, real *a,
+ integer *lda, real *w, real *work, integer *lwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int ssyevr_(char *jobz, char *range, char *uplo, integer *n,
+ real *a, integer *lda, real *vl, real *vu, integer *il, integer *iu,
+ real *abstol, integer *m, real *w, real *z__, integer *ldz, integer *
+ isuppz, real *work, integer *lwork, integer *iwork, integer *liwork,
+ integer *info);
+
+/* Subroutine */ int ssyevx_(char *jobz, char *range, char *uplo, integer *n,
+ real *a, integer *lda, real *vl, real *vu, integer *il, integer *iu,
+ real *abstol, integer *m, real *w, real *z__, integer *ldz, real *
+ work, integer *lwork, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int ssygs2_(integer *itype, char *uplo, integer *n, real *a,
+ integer *lda, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int ssygst_(integer *itype, char *uplo, integer *n, real *a,
+ integer *lda, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int ssygv_(integer *itype, char *jobz, char *uplo, integer *
+ n, real *a, integer *lda, real *b, integer *ldb, real *w, real *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int ssygvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, real *a, integer *lda, real *b, integer *ldb, real *w, real *work,
+ integer *lwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int ssygvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, real *a, integer *lda, real *b, integer *ldb, real *
+ vl, real *vu, integer *il, integer *iu, real *abstol, integer *m,
+ real *w, real *z__, integer *ldz, real *work, integer *lwork, integer
+ *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int ssyrfs_(char *uplo, integer *n, integer *nrhs, real *a,
+ integer *lda, real *af, integer *ldaf, integer *ipiv, real *b,
+ integer *ldb, real *x, integer *ldx, real *ferr, real *berr, real *
+ work, integer *iwork, integer *info);
+
+/* Subroutine */ int ssysv_(char *uplo, integer *n, integer *nrhs, real *a,
+ integer *lda, integer *ipiv, real *b, integer *ldb, real *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int ssysvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, real *a, integer *lda, real *af, integer *ldaf, integer *ipiv,
+ real *b, integer *ldb, real *x, integer *ldx, real *rcond, real *ferr,
+ real *berr, real *work, integer *lwork, integer *iwork, integer *
+ info);
+
+/* Subroutine */ int ssytd2_(char *uplo, integer *n, real *a, integer *lda,
+ real *d__, real *e, real *tau, integer *info);
+
+/* Subroutine */ int ssytf2_(char *uplo, integer *n, real *a, integer *lda,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int ssytrd_(char *uplo, integer *n, real *a, integer *lda,
+ real *d__, real *e, real *tau, real *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int ssytrf_(char *uplo, integer *n, real *a, integer *lda,
+ integer *ipiv, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int ssytri_(char *uplo, integer *n, real *a, integer *lda,
+ integer *ipiv, real *work, integer *info);
+
+/* Subroutine */ int ssytrs_(char *uplo, integer *n, integer *nrhs, real *a,
+ integer *lda, integer *ipiv, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int stbcon_(char *norm, char *uplo, char *diag, integer *n,
+ integer *kd, real *ab, integer *ldab, real *rcond, real *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int stbrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, real *ab, integer *ldab, real *b, integer
+ *ldb, real *x, integer *ldx, real *ferr, real *berr, real *work,
+ integer *iwork, integer *info);
+
+/* Subroutine */ int stbtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, real *ab, integer *ldab, real *b, integer
+ *ldb, integer *info);
+
+/* Subroutine */ int stgevc_(char *side, char *howmny, logical *select,
+ integer *n, real *a, integer *lda, real *b, integer *ldb, real *vl,
+ integer *ldvl, real *vr, integer *ldvr, integer *mm, integer *m, real
+ *work, integer *info);
+
+/* Subroutine */ int stgex2_(logical *wantq, logical *wantz, integer *n, real
+ *a, integer *lda, real *b, integer *ldb, real *q, integer *ldq, real *
+ z__, integer *ldz, integer *j1, integer *n1, integer *n2, real *work,
+ integer *lwork, integer *info);
+
+/* Subroutine */ int stgexc_(logical *wantq, logical *wantz, integer *n, real
+ *a, integer *lda, real *b, integer *ldb, real *q, integer *ldq, real *
+ z__, integer *ldz, integer *ifst, integer *ilst, real *work, integer *
+ lwork, integer *info);
+
+/* Subroutine */ int stgsen_(integer *ijob, logical *wantq, logical *wantz,
+ logical *select, integer *n, real *a, integer *lda, real *b, integer *
+ ldb, real *alphar, real *alphai, real *beta, real *q, integer *ldq,
+ real *z__, integer *ldz, integer *m, real *pl, real *pr, real *dif,
+ real *work, integer *lwork, integer *iwork, integer *liwork, integer *
+ info);
+
+/* Subroutine */ int stgsja_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, integer *k, integer *l, real *a, integer *lda,
+ real *b, integer *ldb, real *tola, real *tolb, real *alpha, real *
+ beta, real *u, integer *ldu, real *v, integer *ldv, real *q, integer *
+ ldq, real *work, integer *ncycle, integer *info);
+
+/* Subroutine */ int stgsna_(char *job, char *howmny, logical *select,
+ integer *n, real *a, integer *lda, real *b, integer *ldb, real *vl,
+ integer *ldvl, real *vr, integer *ldvr, real *s, real *dif, integer *
+ mm, integer *m, real *work, integer *lwork, integer *iwork, integer *
+ info);
+
+/* Subroutine */ int stgsy2_(char *trans, integer *ijob, integer *m, integer *
+ n, real *a, integer *lda, real *b, integer *ldb, real *c__, integer *
+ ldc, real *d__, integer *ldd, real *e, integer *lde, real *f, integer
+ *ldf, real *scale, real *rdsum, real *rdscal, integer *iwork, integer
+ *pq, integer *info);
+
+/* Subroutine */ int stgsyl_(char *trans, integer *ijob, integer *m, integer *
+ n, real *a, integer *lda, real *b, integer *ldb, real *c__, integer *
+ ldc, real *d__, integer *ldd, real *e, integer *lde, real *f, integer
+ *ldf, real *scale, real *dif, real *work, integer *lwork, integer *
+ iwork, integer *info);
+
+/* Subroutine */ int stpcon_(char *norm, char *uplo, char *diag, integer *n,
+ real *ap, real *rcond, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int stprfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, real *ap, real *b, integer *ldb, real *x, integer *ldx,
+ real *ferr, real *berr, real *work, integer *iwork, integer *info);
+
+/* Subroutine */ int stptri_(char *uplo, char *diag, integer *n, real *ap,
+ integer *info);
+
+/* Subroutine */ int stptrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, real *ap, real *b, integer *ldb, integer *info);
+
+/* Subroutine */ int strcon_(char *norm, char *uplo, char *diag, integer *n,
+ real *a, integer *lda, real *rcond, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int strevc_(char *side, char *howmny, logical *select,
+ integer *n, real *t, integer *ldt, real *vl, integer *ldvl, real *vr,
+ integer *ldvr, integer *mm, integer *m, real *work, integer *info);
+
+/* Subroutine */ int strexc_(char *compq, integer *n, real *t, integer *ldt,
+ real *q, integer *ldq, integer *ifst, integer *ilst, real *work,
+ integer *info);
+
+/* Subroutine */ int strrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, real *a, integer *lda, real *b, integer *ldb, real *x,
+ integer *ldx, real *ferr, real *berr, real *work, integer *iwork,
+ integer *info);
+
+/* Subroutine */ int strsen_(char *job, char *compq, logical *select, integer
+ *n, real *t, integer *ldt, real *q, integer *ldq, real *wr, real *wi,
+ integer *m, real *s, real *sep, real *work, integer *lwork, integer *
+ iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int strsna_(char *job, char *howmny, logical *select,
+ integer *n, real *t, integer *ldt, real *vl, integer *ldvl, real *vr,
+ integer *ldvr, real *s, real *sep, integer *mm, integer *m, real *
+ work, integer *ldwork, integer *iwork, integer *info);
+
+/* Subroutine */ int strsyl_(char *trana, char *tranb, integer *isgn, integer
+ *m, integer *n, real *a, integer *lda, real *b, integer *ldb, real *
+ c__, integer *ldc, real *scale, integer *info);
+
+/* Subroutine */ int strti2_(char *uplo, char *diag, integer *n, real *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int strtri_(char *uplo, char *diag, integer *n, real *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int strtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, real *a, integer *lda, real *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int stzrqf_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, integer *info);
+
+/* Subroutine */ int stzrzf_(integer *m, integer *n, real *a, integer *lda,
+ real *tau, real *work, integer *lwork, integer *info);
+
+/* Subroutine */ int xerbla_(char *srname, integer *info);
+
+/* Subroutine */ int zbdsqr_(char *uplo, integer *n, integer *ncvt, integer *
+ nru, integer *ncc, doublereal *d__, doublereal *e, doublecomplex *vt,
+ integer *ldvt, doublecomplex *u, integer *ldu, doublecomplex *c__,
+ integer *ldc, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zdrot_(integer *n, doublecomplex *cx, integer *incx,
+ doublecomplex *cy, integer *incy, doublereal *c__, doublereal *s);
+
+/* Subroutine */ int zdrscl_(integer *n, doublereal *sa, doublecomplex *sx,
+ integer *incx);
+
+/* Subroutine */ int zgbbrd_(char *vect, integer *m, integer *n, integer *ncc,
+ integer *kl, integer *ku, doublecomplex *ab, integer *ldab,
+ doublereal *d__, doublereal *e, doublecomplex *q, integer *ldq,
+ doublecomplex *pt, integer *ldpt, doublecomplex *c__, integer *ldc,
+ doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgbcon_(char *norm, integer *n, integer *kl, integer *ku,
+ doublecomplex *ab, integer *ldab, integer *ipiv, doublereal *anorm,
+ doublereal *rcond, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zgbequ_(integer *m, integer *n, integer *kl, integer *ku,
+ doublecomplex *ab, integer *ldab, doublereal *r__, doublereal *c__,
+ doublereal *rowcnd, doublereal *colcnd, doublereal *amax, integer *
+ info);
+
+/* Subroutine */ int zgbrfs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, doublecomplex *ab, integer *ldab, doublecomplex *
+ afb, integer *ldafb, integer *ipiv, doublecomplex *b, integer *ldb,
+ doublecomplex *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgbsv_(integer *n, integer *kl, integer *ku, integer *
+ nrhs, doublecomplex *ab, integer *ldab, integer *ipiv, doublecomplex *
+ b, integer *ldb, integer *info);
+
+/* Subroutine */ int zgbsvx_(char *fact, char *trans, integer *n, integer *kl,
+ integer *ku, integer *nrhs, doublecomplex *ab, integer *ldab,
+ doublecomplex *afb, integer *ldafb, integer *ipiv, char *equed,
+ doublereal *r__, doublereal *c__, doublecomplex *b, integer *ldb,
+ doublecomplex *x, integer *ldx, doublereal *rcond, doublereal *ferr,
+ doublereal *berr, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zgbtf2_(integer *m, integer *n, integer *kl, integer *ku,
+ doublecomplex *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int zgbtrf_(integer *m, integer *n, integer *kl, integer *ku,
+ doublecomplex *ab, integer *ldab, integer *ipiv, integer *info);
+
+/* Subroutine */ int zgbtrs_(char *trans, integer *n, integer *kl, integer *
+ ku, integer *nrhs, doublecomplex *ab, integer *ldab, integer *ipiv,
+ doublecomplex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int zgebak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, doublereal *scale, integer *m, doublecomplex *v,
+ integer *ldv, integer *info);
+
+/* Subroutine */ int zgebal_(char *job, integer *n, doublecomplex *a, integer
+ *lda, integer *ilo, integer *ihi, doublereal *scale, integer *info);
+
+/* Subroutine */ int zgebd2_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublereal *d__, doublereal *e, doublecomplex *tauq,
+ doublecomplex *taup, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zgebrd_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublereal *d__, doublereal *e, doublecomplex *tauq,
+ doublecomplex *taup, doublecomplex *work, integer *lwork, integer *
+ info);
+
+/* Subroutine */ int zgecon_(char *norm, integer *n, doublecomplex *a,
+ integer *lda, doublereal *anorm, doublereal *rcond, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgeequ_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublereal *r__, doublereal *c__, doublereal *rowcnd,
+ doublereal *colcnd, doublereal *amax, integer *info);
+
+/* Subroutine */ int zgees_(char *jobvs, char *sort, L_fp select, integer *n,
+ doublecomplex *a, integer *lda, integer *sdim, doublecomplex *w,
+ doublecomplex *vs, integer *ldvs, doublecomplex *work, integer *lwork,
+ doublereal *rwork, logical *bwork, integer *info);
+
+/* Subroutine */ int zgeesx_(char *jobvs, char *sort, L_fp select, char *
+ sense, integer *n, doublecomplex *a, integer *lda, integer *sdim,
+ doublecomplex *w, doublecomplex *vs, integer *ldvs, doublereal *
+ rconde, doublereal *rcondv, doublecomplex *work, integer *lwork,
+ doublereal *rwork, logical *bwork, integer *info);
+
+/* Subroutine */ int zgeev_(char *jobvl, char *jobvr, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *w, doublecomplex *vl,
+ integer *ldvl, doublecomplex *vr, integer *ldvr, doublecomplex *work,
+ integer *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgeevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, doublecomplex *a, integer *lda, doublecomplex *w,
+ doublecomplex *vl, integer *ldvl, doublecomplex *vr, integer *ldvr,
+ integer *ilo, integer *ihi, doublereal *scale, doublereal *abnrm,
+ doublereal *rconde, doublereal *rcondv, doublecomplex *work, integer *
+ lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgegs_(char *jobvsl, char *jobvsr, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *alpha, doublecomplex *beta, doublecomplex *vsl,
+ integer *ldvsl, doublecomplex *vsr, integer *ldvsr, doublecomplex *
+ work, integer *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgegv_(char *jobvl, char *jobvr, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *alpha, doublecomplex *beta, doublecomplex *vl, integer
+ *ldvl, doublecomplex *vr, integer *ldvr, doublecomplex *work, integer
+ *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgehd2_(integer *n, integer *ilo, integer *ihi,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zgehrd_(integer *n, integer *ilo, integer *ihi,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zgelq2_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zgelqf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zgels_(char *trans, integer *m, integer *n, integer *
+ nrhs, doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int zgelsx_(integer *m, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ integer *jpvt, doublereal *rcond, integer *rank, doublecomplex *work,
+ doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgelsy_(integer *m, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ integer *jpvt, doublereal *rcond, integer *rank, doublecomplex *work,
+ integer *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgeql2_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zgeqlf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zgeqp3_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, integer *jpvt, doublecomplex *tau, doublecomplex *work,
+ integer *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgeqpf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, integer *jpvt, doublecomplex *tau, doublecomplex *work,
+ doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgeqr2_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zgeqrf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zgerfs_(char *trans, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *af, integer *ldaf,
+ integer *ipiv, doublecomplex *b, integer *ldb, doublecomplex *x,
+ integer *ldx, doublereal *ferr, doublereal *berr, doublecomplex *work,
+ doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgerq2_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zgerqf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zgesc2_(integer *n, doublecomplex *a, integer *lda,
+ doublecomplex *rhs, integer *ipiv, integer *jpiv, doublereal *scale);
+
+/* Subroutine */ int zgesv_(integer *n, integer *nrhs, doublecomplex *a,
+ integer *lda, integer *ipiv, doublecomplex *b, integer *ldb, integer *
+ info);
+
+/* Subroutine */ int zgesvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, doublecomplex *a, integer *lda, doublecomplex *af, integer *
+ ldaf, integer *ipiv, char *equed, doublereal *r__, doublereal *c__,
+ doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *rcond, doublereal *ferr, doublereal *berr, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgetc2_(integer *n, doublecomplex *a, integer *lda,
+ integer *ipiv, integer *jpiv, integer *info);
+
+/* Subroutine */ int zgetf2_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, integer *info);
+
+/* Subroutine */ int zgetrf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, integer *info);
+
+/* Subroutine */ int zgetri_(integer *n, doublecomplex *a, integer *lda,
+ integer *ipiv, doublecomplex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int zgetrs_(char *trans, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, integer *ipiv, doublecomplex *b,
+ integer *ldb, integer *info);
+
+/* Subroutine */ int zggbak_(char *job, char *side, integer *n, integer *ilo,
+ integer *ihi, doublereal *lscale, doublereal *rscale, integer *m,
+ doublecomplex *v, integer *ldv, integer *info);
+
+/* Subroutine */ int zggbal_(char *job, integer *n, doublecomplex *a, integer
+ *lda, doublecomplex *b, integer *ldb, integer *ilo, integer *ihi,
+ doublereal *lscale, doublereal *rscale, doublereal *work, integer *
+ info);
+
+/* Subroutine */ int zgges_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ delctg, integer *n, doublecomplex *a, integer *lda, doublecomplex *b,
+ integer *ldb, integer *sdim, doublecomplex *alpha, doublecomplex *
+ beta, doublecomplex *vsl, integer *ldvsl, doublecomplex *vsr, integer
+ *ldvsr, doublecomplex *work, integer *lwork, doublereal *rwork,
+ logical *bwork, integer *info);
+
+/* Subroutine */ int zggesx_(char *jobvsl, char *jobvsr, char *sort, L_fp
+ delctg, char *sense, integer *n, doublecomplex *a, integer *lda,
+ doublecomplex *b, integer *ldb, integer *sdim, doublecomplex *alpha,
+ doublecomplex *beta, doublecomplex *vsl, integer *ldvsl,
+ doublecomplex *vsr, integer *ldvsr, doublereal *rconde, doublereal *
+ rcondv, doublecomplex *work, integer *lwork, doublereal *rwork,
+ integer *iwork, integer *liwork, logical *bwork, integer *info);
+
+/* Subroutine */ int zggev_(char *jobvl, char *jobvr, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *alpha, doublecomplex *beta, doublecomplex *vl, integer
+ *ldvl, doublecomplex *vr, integer *ldvr, doublecomplex *work, integer
+ *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zggevx_(char *balanc, char *jobvl, char *jobvr, char *
+ sense, integer *n, doublecomplex *a, integer *lda, doublecomplex *b,
+ integer *ldb, doublecomplex *alpha, doublecomplex *beta,
+ doublecomplex *vl, integer *ldvl, doublecomplex *vr, integer *ldvr,
+ integer *ilo, integer *ihi, doublereal *lscale, doublereal *rscale,
+ doublereal *abnrm, doublereal *bbnrm, doublereal *rconde, doublereal *
+ rcondv, doublecomplex *work, integer *lwork, doublereal *rwork,
+ integer *iwork, logical *bwork, integer *info);
+
+/* Subroutine */ int zggglm_(integer *n, integer *m, integer *p,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *d__, doublecomplex *x, doublecomplex *y, doublecomplex
+ *work, integer *lwork, integer *info);
+
+/* Subroutine */ int zgghrd_(char *compq, char *compz, integer *n, integer *
+ ilo, integer *ihi, doublecomplex *a, integer *lda, doublecomplex *b,
+ integer *ldb, doublecomplex *q, integer *ldq, doublecomplex *z__,
+ integer *ldz, integer *info);
+
+/* Subroutine */ int zgglse_(integer *m, integer *n, integer *p,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *c__, doublecomplex *d__, doublecomplex *x,
+ doublecomplex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int zggqrf_(integer *n, integer *m, integer *p,
+ doublecomplex *a, integer *lda, doublecomplex *taua, doublecomplex *b,
+ integer *ldb, doublecomplex *taub, doublecomplex *work, integer *
+ lwork, integer *info);
+
+/* Subroutine */ int zggrqf_(integer *m, integer *p, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *taua, doublecomplex *b,
+ integer *ldb, doublecomplex *taub, doublecomplex *work, integer *
+ lwork, integer *info);
+
+/* Subroutine */ int zggsvd_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *n, integer *p, integer *k, integer *l, doublecomplex *a,
+ integer *lda, doublecomplex *b, integer *ldb, doublereal *alpha,
+ doublereal *beta, doublecomplex *u, integer *ldu, doublecomplex *v,
+ integer *ldv, doublecomplex *q, integer *ldq, doublecomplex *work,
+ doublereal *rwork, integer *iwork, integer *info);
+
+/* Subroutine */ int zggsvp_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, doublecomplex *a, integer *lda, doublecomplex
+ *b, integer *ldb, doublereal *tola, doublereal *tolb, integer *k,
+ integer *l, doublecomplex *u, integer *ldu, doublecomplex *v, integer
+ *ldv, doublecomplex *q, integer *ldq, integer *iwork, doublereal *
+ rwork, doublecomplex *tau, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zgtcon_(char *norm, integer *n, doublecomplex *dl,
+ doublecomplex *d__, doublecomplex *du, doublecomplex *du2, integer *
+ ipiv, doublereal *anorm, doublereal *rcond, doublecomplex *work,
+ integer *info);
+
+/* Subroutine */ int zgtrfs_(char *trans, integer *n, integer *nrhs,
+ doublecomplex *dl, doublecomplex *d__, doublecomplex *du,
+ doublecomplex *dlf, doublecomplex *df, doublecomplex *duf,
+ doublecomplex *du2, integer *ipiv, doublecomplex *b, integer *ldb,
+ doublecomplex *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zgtsv_(integer *n, integer *nrhs, doublecomplex *dl,
+ doublecomplex *d__, doublecomplex *du, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zgtsvx_(char *fact, char *trans, integer *n, integer *
+ nrhs, doublecomplex *dl, doublecomplex *d__, doublecomplex *du,
+ doublecomplex *dlf, doublecomplex *df, doublecomplex *duf,
+ doublecomplex *du2, integer *ipiv, doublecomplex *b, integer *ldb,
+ doublecomplex *x, integer *ldx, doublereal *rcond, doublereal *ferr,
+ doublereal *berr, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zgttrf_(integer *n, doublecomplex *dl, doublecomplex *
+ d__, doublecomplex *du, doublecomplex *du2, integer *ipiv, integer *
+ info);
+
+/* Subroutine */ int zgttrs_(char *trans, integer *n, integer *nrhs,
+ doublecomplex *dl, doublecomplex *d__, doublecomplex *du,
+ doublecomplex *du2, integer *ipiv, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zgtts2_(integer *itrans, integer *n, integer *nrhs,
+ doublecomplex *dl, doublecomplex *d__, doublecomplex *du,
+ doublecomplex *du2, integer *ipiv, doublecomplex *b, integer *ldb);
+
+/* Subroutine */ int zhbev_(char *jobz, char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, doublereal *w, doublecomplex *z__,
+ integer *ldz, doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zhbevd_(char *jobz, char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, doublereal *w, doublecomplex *z__,
+ integer *ldz, doublecomplex *work, integer *lwork, doublereal *rwork,
+ integer *lrwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int zhbevx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *kd, doublecomplex *ab, integer *ldab, doublecomplex *q,
+ integer *ldq, doublereal *vl, doublereal *vu, integer *il, integer *
+ iu, doublereal *abstol, integer *m, doublereal *w, doublecomplex *z__,
+ integer *ldz, doublecomplex *work, doublereal *rwork, integer *iwork,
+ integer *ifail, integer *info);
+
+/* Subroutine */ int zhbgst_(char *vect, char *uplo, integer *n, integer *ka,
+ integer *kb, doublecomplex *ab, integer *ldab, doublecomplex *bb,
+ integer *ldbb, doublecomplex *x, integer *ldx, doublecomplex *work,
+ doublereal *rwork, integer *info);
+
+/* Subroutine */ int zhbgv_(char *jobz, char *uplo, integer *n, integer *ka,
+ integer *kb, doublecomplex *ab, integer *ldab, doublecomplex *bb,
+ integer *ldbb, doublereal *w, doublecomplex *z__, integer *ldz,
+ doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zhbgvx_(char *jobz, char *range, char *uplo, integer *n,
+ integer *ka, integer *kb, doublecomplex *ab, integer *ldab,
+ doublecomplex *bb, integer *ldbb, doublecomplex *q, integer *ldq,
+ doublereal *vl, doublereal *vu, integer *il, integer *iu, doublereal *
+ abstol, integer *m, doublereal *w, doublecomplex *z__, integer *ldz,
+ doublecomplex *work, doublereal *rwork, integer *iwork, integer *
+ ifail, integer *info);
+
+/* Subroutine */ int zhbtrd_(char *vect, char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, doublereal *d__, doublereal *e,
+ doublecomplex *q, integer *ldq, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zhecon_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, doublereal *anorm, doublereal *rcond,
+ doublecomplex *work, integer *info);
+
+/* Subroutine */ int zheev_(char *jobz, char *uplo, integer *n, doublecomplex
+ *a, integer *lda, doublereal *w, doublecomplex *work, integer *lwork,
+ doublereal *rwork, integer *info);
+
+/* Subroutine */ int zheevd_(char *jobz, char *uplo, integer *n,
+ doublecomplex *a, integer *lda, doublereal *w, doublecomplex *work,
+ integer *lwork, doublereal *rwork, integer *lrwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int zheevr_(char *jobz, char *range, char *uplo, integer *n,
+ doublecomplex *a, integer *lda, doublereal *vl, doublereal *vu,
+ integer *il, integer *iu, doublereal *abstol, integer *m, doublereal *
+ w, doublecomplex *z__, integer *ldz, integer *isuppz, doublecomplex *
+ work, integer *lwork, doublereal *rwork, integer *lrwork, integer *
+ iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int zheevx_(char *jobz, char *range, char *uplo, integer *n,
+ doublecomplex *a, integer *lda, doublereal *vl, doublereal *vu,
+ integer *il, integer *iu, doublereal *abstol, integer *m, doublereal *
+ w, doublecomplex *z__, integer *ldz, doublecomplex *work, integer *
+ lwork, doublereal *rwork, integer *iwork, integer *ifail, integer *
+ info);
+
+/* Subroutine */ int zhegs2_(integer *itype, char *uplo, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zhegst_(integer *itype, char *uplo, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zhegv_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublereal *w, doublecomplex *work, integer *lwork, doublereal *rwork,
+ integer *info);
+
+/* Subroutine */ int zhegvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublereal *w, doublecomplex *work, integer *lwork, doublereal *rwork,
+ integer *lrwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int zhegvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, doublecomplex *a, integer *lda, doublecomplex *b,
+ integer *ldb, doublereal *vl, doublereal *vu, integer *il, integer *
+ iu, doublereal *abstol, integer *m, doublereal *w, doublecomplex *z__,
+ integer *ldz, doublecomplex *work, integer *lwork, doublereal *rwork,
+ integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int zherfs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *af, integer *ldaf,
+ integer *ipiv, doublecomplex *b, integer *ldb, doublecomplex *x,
+ integer *ldx, doublereal *ferr, doublereal *berr, doublecomplex *work,
+ doublereal *rwork, integer *info);
+
+/* Subroutine */ int zhesv_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, integer *ipiv, doublecomplex *b,
+ integer *ldb, doublecomplex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int zhesvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublecomplex *a, integer *lda, doublecomplex *af, integer *
+ ldaf, integer *ipiv, doublecomplex *b, integer *ldb, doublecomplex *x,
+ integer *ldx, doublereal *rcond, doublereal *ferr, doublereal *berr,
+ doublecomplex *work, integer *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zhetf2_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, integer *info);
+
+/* Subroutine */ int zhetrd_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, doublereal *d__, doublereal *e, doublecomplex *tau,
+ doublecomplex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int zhetrf_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zhetri_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zhetrs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, integer *ipiv, doublecomplex *b,
+ integer *ldb, integer *info);
+
+/* Subroutine */ int zhgeqz_(char *job, char *compq, char *compz, integer *n,
+ integer *ilo, integer *ihi, doublecomplex *a, integer *lda,
+ doublecomplex *b, integer *ldb, doublecomplex *alpha, doublecomplex *
+ beta, doublecomplex *q, integer *ldq, doublecomplex *z__, integer *
+ ldz, doublecomplex *work, integer *lwork, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zhpcon_(char *uplo, integer *n, doublecomplex *ap,
+ integer *ipiv, doublereal *anorm, doublereal *rcond, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zhpev_(char *jobz, char *uplo, integer *n, doublecomplex
+ *ap, doublereal *w, doublecomplex *z__, integer *ldz, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zhpevd_(char *jobz, char *uplo, integer *n,
+ doublecomplex *ap, doublereal *w, doublecomplex *z__, integer *ldz,
+ doublecomplex *work, integer *lwork, doublereal *rwork, integer *
+ lrwork, integer *iwork, integer *liwork, integer *info);
+
+/* Subroutine */ int zhpevx_(char *jobz, char *range, char *uplo, integer *n,
+ doublecomplex *ap, doublereal *vl, doublereal *vu, integer *il,
+ integer *iu, doublereal *abstol, integer *m, doublereal *w,
+ doublecomplex *z__, integer *ldz, doublecomplex *work, doublereal *
+ rwork, integer *iwork, integer *ifail, integer *info);
+
+/* Subroutine */ int zhpgst_(integer *itype, char *uplo, integer *n,
+ doublecomplex *ap, doublecomplex *bp, integer *info);
+
+/* Subroutine */ int zhpgv_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublecomplex *ap, doublecomplex *bp, doublereal *w, doublecomplex
+ *z__, integer *ldz, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zhpgvd_(integer *itype, char *jobz, char *uplo, integer *
+ n, doublecomplex *ap, doublecomplex *bp, doublereal *w, doublecomplex
+ *z__, integer *ldz, doublecomplex *work, integer *lwork, doublereal *
+ rwork, integer *lrwork, integer *iwork, integer *liwork, integer *
+ info);
+
+/* Subroutine */ int zhpgvx_(integer *itype, char *jobz, char *range, char *
+ uplo, integer *n, doublecomplex *ap, doublecomplex *bp, doublereal *
+ vl, doublereal *vu, integer *il, integer *iu, doublereal *abstol,
+ integer *m, doublereal *w, doublecomplex *z__, integer *ldz,
+ doublecomplex *work, doublereal *rwork, integer *iwork, integer *
+ ifail, integer *info);
+
+/* Subroutine */ int zhprfs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, doublecomplex *afp, integer *ipiv, doublecomplex *
+ b, integer *ldb, doublecomplex *x, integer *ldx, doublereal *ferr,
+ doublereal *berr, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zhpsv_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, integer *ipiv, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zhpsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublecomplex *ap, doublecomplex *afp, integer *ipiv,
+ doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *rcond, doublereal *ferr, doublereal *berr, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zhptrd_(char *uplo, integer *n, doublecomplex *ap,
+ doublereal *d__, doublereal *e, doublecomplex *tau, integer *info);
+
+/* Subroutine */ int zhptrf_(char *uplo, integer *n, doublecomplex *ap,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int zhptri_(char *uplo, integer *n, doublecomplex *ap,
+ integer *ipiv, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zhptrs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, integer *ipiv, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zhsein_(char *side, char *eigsrc, char *initv, logical *
+ select, integer *n, doublecomplex *h__, integer *ldh, doublecomplex *
+ w, doublecomplex *vl, integer *ldvl, doublecomplex *vr, integer *ldvr,
+ integer *mm, integer *m, doublecomplex *work, doublereal *rwork,
+ integer *ifaill, integer *ifailr, integer *info);
+
+/* Subroutine */ int zhseqr_(char *job, char *compz, integer *n, integer *ilo,
+ integer *ihi, doublecomplex *h__, integer *ldh, doublecomplex *w,
+ doublecomplex *z__, integer *ldz, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zlabrd_(integer *m, integer *n, integer *nb,
+ doublecomplex *a, integer *lda, doublereal *d__, doublereal *e,
+ doublecomplex *tauq, doublecomplex *taup, doublecomplex *x, integer *
+ ldx, doublecomplex *y, integer *ldy);
+
+/* Subroutine */ int zlacgv_(integer *n, doublecomplex *x, integer *incx);
+
+/* Subroutine */ int zlacon_(integer *n, doublecomplex *v, doublecomplex *x,
+ doublereal *est, integer *kase);
+
+/* Subroutine */ int zlacp2_(char *uplo, integer *m, integer *n, doublereal *
+ a, integer *lda, doublecomplex *b, integer *ldb);
+
+/* Subroutine */ int zlacpy_(char *uplo, integer *m, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb);
+
+/* Subroutine */ int zlacrm_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublereal *b, integer *ldb, doublecomplex *c__,
+ integer *ldc, doublereal *rwork);
+
+/* Subroutine */ int zlacrt_(integer *n, doublecomplex *cx, integer *incx,
+ doublecomplex *cy, integer *incy, doublecomplex *c__, doublecomplex *
+ s);
+
+/* Subroutine */ int zlaed0_(integer *qsiz, integer *n, doublereal *d__,
+ doublereal *e, doublecomplex *q, integer *ldq, doublecomplex *qstore,
+ integer *ldqs, doublereal *rwork, integer *iwork, integer *info);
+
+/* Subroutine */ int zlaed7_(integer *n, integer *cutpnt, integer *qsiz,
+ integer *tlvls, integer *curlvl, integer *curpbm, doublereal *d__,
+ doublecomplex *q, integer *ldq, doublereal *rho, integer *indxq,
+ doublereal *qstore, integer *qptr, integer *prmptr, integer *perm,
+ integer *givptr, integer *givcol, doublereal *givnum, doublecomplex *
+ work, doublereal *rwork, integer *iwork, integer *info);
+
+/* Subroutine */ int zlaed8_(integer *k, integer *n, integer *qsiz,
+ doublecomplex *q, integer *ldq, doublereal *d__, doublereal *rho,
+ integer *cutpnt, doublereal *z__, doublereal *dlamda, doublecomplex *
+ q2, integer *ldq2, doublereal *w, integer *indxp, integer *indx,
+ integer *indxq, integer *perm, integer *givptr, integer *givcol,
+ doublereal *givnum, integer *info);
+
+/* Subroutine */ int zlaein_(logical *rightv, logical *noinit, integer *n,
+ doublecomplex *h__, integer *ldh, doublecomplex *w, doublecomplex *v,
+ doublecomplex *b, integer *ldb, doublereal *rwork, doublereal *eps3,
+ doublereal *smlnum, integer *info);
+
+/* Subroutine */ int zlaesy_(doublecomplex *a, doublecomplex *b,
+ doublecomplex *c__, doublecomplex *rt1, doublecomplex *rt2,
+ doublecomplex *evscal, doublecomplex *cs1, doublecomplex *sn1);
+
+/* Subroutine */ int zlaev2_(doublecomplex *a, doublecomplex *b,
+ doublecomplex *c__, doublereal *rt1, doublereal *rt2, doublereal *cs1,
+ doublecomplex *sn1);
+
+/* Subroutine */ int zlags2_(logical *upper, doublereal *a1, doublecomplex *
+ a2, doublereal *a3, doublereal *b1, doublecomplex *b2, doublereal *b3,
+ doublereal *csu, doublecomplex *snu, doublereal *csv, doublecomplex *
+ snv, doublereal *csq, doublecomplex *snq);
+
+/* Subroutine */ int zlagtm_(char *trans, integer *n, integer *nrhs,
+ doublereal *alpha, doublecomplex *dl, doublecomplex *d__,
+ doublecomplex *du, doublecomplex *x, integer *ldx, doublereal *beta,
+ doublecomplex *b, integer *ldb);
+
+/* Subroutine */ int zlahef_(char *uplo, integer *n, integer *nb, integer *kb,
+ doublecomplex *a, integer *lda, integer *ipiv, doublecomplex *w,
+ integer *ldw, integer *info);
+
+/* Subroutine */ int zlahqr_(logical *wantt, logical *wantz, integer *n,
+ integer *ilo, integer *ihi, doublecomplex *h__, integer *ldh,
+ doublecomplex *w, integer *iloz, integer *ihiz, doublecomplex *z__,
+ integer *ldz, integer *info);
+
+/* Subroutine */ int zlahrd_(integer *n, integer *k, integer *nb,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *t,
+ integer *ldt, doublecomplex *y, integer *ldy);
+
+/* Subroutine */ int zlaic1_(integer *job, integer *j, doublecomplex *x,
+ doublereal *sest, doublecomplex *w, doublecomplex *gamma, doublereal *
+ sestpr, doublecomplex *s, doublecomplex *c__);
+
+/* Subroutine */ int zlals0_(integer *icompq, integer *nl, integer *nr,
+ integer *sqre, integer *nrhs, doublecomplex *b, integer *ldb,
+ doublecomplex *bx, integer *ldbx, integer *perm, integer *givptr,
+ integer *givcol, integer *ldgcol, doublereal *givnum, integer *ldgnum,
+ doublereal *poles, doublereal *difl, doublereal *difr, doublereal *
+ z__, integer *k, doublereal *c__, doublereal *s, doublereal *rwork,
+ integer *info);
+
+/* Subroutine */ int zlalsa_(integer *icompq, integer *smlsiz, integer *n,
+ integer *nrhs, doublecomplex *b, integer *ldb, doublecomplex *bx,
+ integer *ldbx, doublereal *u, integer *ldu, doublereal *vt, integer *
+ k, doublereal *difl, doublereal *difr, doublereal *z__, doublereal *
+ poles, integer *givptr, integer *givcol, integer *ldgcol, integer *
+ perm, doublereal *givnum, doublereal *c__, doublereal *s, doublereal *
+ rwork, integer *iwork, integer *info);
+
+/* Subroutine */ int zlapll_(integer *n, doublecomplex *x, integer *incx,
+ doublecomplex *y, integer *incy, doublereal *ssmin);
+
+/* Subroutine */ int zlapmt_(logical *forwrd, integer *m, integer *n,
+ doublecomplex *x, integer *ldx, integer *k);
+
+/* Subroutine */ int zlaqgb_(integer *m, integer *n, integer *kl, integer *ku,
+ doublecomplex *ab, integer *ldab, doublereal *r__, doublereal *c__,
+ doublereal *rowcnd, doublereal *colcnd, doublereal *amax, char *equed);
+
+/* Subroutine */ int zlaqge_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublereal *r__, doublereal *c__, doublereal *rowcnd,
+ doublereal *colcnd, doublereal *amax, char *equed);
+
+/* Subroutine */ int zlaqhb_(char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, doublereal *s, doublereal *scond,
+ doublereal *amax, char *equed);
+
+/* Subroutine */ int zlaqhe_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, doublereal *s, doublereal *scond, doublereal *amax,
+ char *equed);
+
+/* Subroutine */ int zlaqhp_(char *uplo, integer *n, doublecomplex *ap,
+ doublereal *s, doublereal *scond, doublereal *amax, char *equed);
+
+/* Subroutine */ int zlaqp2_(integer *m, integer *n, integer *offset,
+ doublecomplex *a, integer *lda, integer *jpvt, doublecomplex *tau,
+ doublereal *vn1, doublereal *vn2, doublecomplex *work);
+
+/* Subroutine */ int zlaqps_(integer *m, integer *n, integer *offset, integer
+ *nb, integer *kb, doublecomplex *a, integer *lda, integer *jpvt,
+ doublecomplex *tau, doublereal *vn1, doublereal *vn2, doublecomplex *
+ auxv, doublecomplex *f, integer *ldf);
+
+/* Subroutine */ int zlaqsb_(char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, doublereal *s, doublereal *scond,
+ doublereal *amax, char *equed);
+
+/* Subroutine */ int zlaqsp_(char *uplo, integer *n, doublecomplex *ap,
+ doublereal *s, doublereal *scond, doublereal *amax, char *equed);
+
+/* Subroutine */ int zlaqsy_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, doublereal *s, doublereal *scond, doublereal *amax,
+ char *equed);
+
+/* Subroutine */ int zlar1v_(integer *n, integer *b1, integer *bn, doublereal
+ *sigma, doublereal *d__, doublereal *l, doublereal *ld, doublereal *
+ lld, doublereal *gersch, doublecomplex *z__, doublereal *ztz,
+ doublereal *mingma, integer *r__, integer *isuppz, doublereal *work);
+
+/* Subroutine */ int zlar2v_(integer *n, doublecomplex *x, doublecomplex *y,
+ doublecomplex *z__, integer *incx, doublereal *c__, doublecomplex *s,
+ integer *incc);
+
+/* Subroutine */ int zlarcm_(integer *m, integer *n, doublereal *a, integer *
+ lda, doublecomplex *b, integer *ldb, doublecomplex *c__, integer *ldc,
+ doublereal *rwork);
+
+/* Subroutine */ int zlarf_(char *side, integer *m, integer *n, doublecomplex
+ *v, integer *incv, doublecomplex *tau, doublecomplex *c__, integer *
+ ldc, doublecomplex *work);
+
+/* Subroutine */ int zlarfb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, doublecomplex *v, integer
+ *ldv, doublecomplex *t, integer *ldt, doublecomplex *c__, integer *
+ ldc, doublecomplex *work, integer *ldwork);
+
+/* Subroutine */ int zlarfg_(integer *n, doublecomplex *alpha, doublecomplex *
+ x, integer *incx, doublecomplex *tau);
+
+/* Subroutine */ int zlarft_(char *direct, char *storev, integer *n, integer *
+ k, doublecomplex *v, integer *ldv, doublecomplex *tau, doublecomplex *
+ t, integer *ldt);
+
+/* Subroutine */ int zlarfx_(char *side, integer *m, integer *n,
+ doublecomplex *v, doublecomplex *tau, doublecomplex *c__, integer *
+ ldc, doublecomplex *work);
+
+/* Subroutine */ int zlargv_(integer *n, doublecomplex *x, integer *incx,
+ doublecomplex *y, integer *incy, doublereal *c__, integer *incc);
+
+/* Subroutine */ int zlarnv_(integer *idist, integer *iseed, integer *n,
+ doublecomplex *x);
+
+/* Subroutine */ int zlarrv_(integer *n, doublereal *d__, doublereal *l,
+ integer *isplit, integer *m, doublereal *w, integer *iblock,
+ doublereal *gersch, doublereal *tol, doublecomplex *z__, integer *ldz,
+ integer *isuppz, doublereal *work, integer *iwork, integer *info);
+
+/* Subroutine */ int zlartg_(doublecomplex *f, doublecomplex *g, doublereal *
+ cs, doublecomplex *sn, doublecomplex *r__);
+
+/* Subroutine */ int zlartv_(integer *n, doublecomplex *x, integer *incx,
+ doublecomplex *y, integer *incy, doublereal *c__, doublecomplex *s,
+ integer *incc);
+
+/* Subroutine */ int zlarz_(char *side, integer *m, integer *n, integer *l,
+ doublecomplex *v, integer *incv, doublecomplex *tau, doublecomplex *
+ c__, integer *ldc, doublecomplex *work);
+
+/* Subroutine */ int zlarzb_(char *side, char *trans, char *direct, char *
+ storev, integer *m, integer *n, integer *k, integer *l, doublecomplex
+ *v, integer *ldv, doublecomplex *t, integer *ldt, doublecomplex *c__,
+ integer *ldc, doublecomplex *work, integer *ldwork);
+
+/* Subroutine */ int zlarzt_(char *direct, char *storev, integer *n, integer *
+ k, doublecomplex *v, integer *ldv, doublecomplex *tau, doublecomplex *
+ t, integer *ldt);
+
+/* Subroutine */ int zlascl_(char *type__, integer *kl, integer *ku,
+ doublereal *cfrom, doublereal *cto, integer *m, integer *n,
+ doublecomplex *a, integer *lda, integer *info);
+
+/* Subroutine */ int zlaset_(char *uplo, integer *m, integer *n,
+ doublecomplex *alpha, doublecomplex *beta, doublecomplex *a, integer *
+ lda);
+
+/* Subroutine */ int zlasr_(char *side, char *pivot, char *direct, integer *m,
+ integer *n, doublereal *c__, doublereal *s, doublecomplex *a,
+ integer *lda);
+
+/* Subroutine */ int zlassq_(integer *n, doublecomplex *x, integer *incx,
+ doublereal *scale, doublereal *sumsq);
+
+/* Subroutine */ int zlaswp_(integer *n, doublecomplex *a, integer *lda,
+ integer *k1, integer *k2, integer *ipiv, integer *incx);
+
+/* Subroutine */ int zlasyf_(char *uplo, integer *n, integer *nb, integer *kb,
+ doublecomplex *a, integer *lda, integer *ipiv, doublecomplex *w,
+ integer *ldw, integer *info);
+
+/* Subroutine */ int zlatbs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, integer *kd, doublecomplex *ab, integer *ldab,
+ doublecomplex *x, doublereal *scale, doublereal *cnorm, integer *info);
+
+/* Subroutine */ int zlatdf_(integer *ijob, integer *n, doublecomplex *z__,
+ integer *ldz, doublecomplex *rhs, doublereal *rdsum, doublereal *
+ rdscal, integer *ipiv, integer *jpiv);
+
+/* Subroutine */ int zlatps_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, doublecomplex *ap, doublecomplex *x, doublereal *
+ scale, doublereal *cnorm, integer *info);
+
+/* Subroutine */ int zlatrd_(char *uplo, integer *n, integer *nb,
+ doublecomplex *a, integer *lda, doublereal *e, doublecomplex *tau,
+ doublecomplex *w, integer *ldw);
+
+/* Subroutine */ int zlatrs_(char *uplo, char *trans, char *diag, char *
+ normin, integer *n, doublecomplex *a, integer *lda, doublecomplex *x,
+ doublereal *scale, doublereal *cnorm, integer *info);
+
+/* Subroutine */ int zlatrz_(integer *m, integer *n, integer *l,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work);
+
+/* Subroutine */ int zlatzm_(char *side, integer *m, integer *n,
+ doublecomplex *v, integer *incv, doublecomplex *tau, doublecomplex *
+ c1, doublecomplex *c2, integer *ldc, doublecomplex *work);
+
+/* Subroutine */ int zlauu2_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int zlauum_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int zpbcon_(char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, doublereal *anorm, doublereal *
+ rcond, doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zpbequ_(char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, doublereal *s, doublereal *scond,
+ doublereal *amax, integer *info);
+
+/* Subroutine */ int zpbrfs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, doublecomplex *ab, integer *ldab, doublecomplex *afb, integer *
+ ldafb, doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *ferr, doublereal *berr, doublecomplex *work, doublereal *
+ rwork, integer *info);
+
+/* Subroutine */ int zpbstf_(char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, integer *info);
+
+/* Subroutine */ int zpbsv_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, doublecomplex *ab, integer *ldab, doublecomplex *b, integer *
+ ldb, integer *info);
+
+/* Subroutine */ int zpbsvx_(char *fact, char *uplo, integer *n, integer *kd,
+ integer *nrhs, doublecomplex *ab, integer *ldab, doublecomplex *afb,
+ integer *ldafb, char *equed, doublereal *s, doublecomplex *b, integer
+ *ldb, doublecomplex *x, integer *ldx, doublereal *rcond, doublereal *
+ ferr, doublereal *berr, doublecomplex *work, doublereal *rwork,
+ integer *info);
+
+/* Subroutine */ int zpbtf2_(char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, integer *info);
+
+/* Subroutine */ int zpbtrf_(char *uplo, integer *n, integer *kd,
+ doublecomplex *ab, integer *ldab, integer *info);
+
+/* Subroutine */ int zpbtrs_(char *uplo, integer *n, integer *kd, integer *
+ nrhs, doublecomplex *ab, integer *ldab, doublecomplex *b, integer *
+ ldb, integer *info);
+
+/* Subroutine */ int zpocon_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, doublereal *anorm, doublereal *rcond, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zpoequ_(integer *n, doublecomplex *a, integer *lda,
+ doublereal *s, doublereal *scond, doublereal *amax, integer *info);
+
+/* Subroutine */ int zporfs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *af, integer *ldaf,
+ doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *ferr, doublereal *berr, doublecomplex *work, doublereal *
+ rwork, integer *info);
+
+/* Subroutine */ int zposv_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zposvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublecomplex *a, integer *lda, doublecomplex *af, integer *
+ ldaf, char *equed, doublereal *s, doublecomplex *b, integer *ldb,
+ doublecomplex *x, integer *ldx, doublereal *rcond, doublereal *ferr,
+ doublereal *berr, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zpotf2_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int zpotrf_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int zpotri_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *info);
+
+/* Subroutine */ int zpotrs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zppcon_(char *uplo, integer *n, doublecomplex *ap,
+ doublereal *anorm, doublereal *rcond, doublecomplex *work, doublereal
+ *rwork, integer *info);
+
+/* Subroutine */ int zppequ_(char *uplo, integer *n, doublecomplex *ap,
+ doublereal *s, doublereal *scond, doublereal *amax, integer *info);
+
+/* Subroutine */ int zpprfs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, doublecomplex *afp, doublecomplex *b, integer *ldb,
+ doublecomplex *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zppsv_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, doublecomplex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int zppsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublecomplex *ap, doublecomplex *afp, char *equed, doublereal *
+ s, doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *rcond, doublereal *ferr, doublereal *berr, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zpptrf_(char *uplo, integer *n, doublecomplex *ap,
+ integer *info);
+
+/* Subroutine */ int zpptri_(char *uplo, integer *n, doublecomplex *ap,
+ integer *info);
+
+/* Subroutine */ int zpptrs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, doublecomplex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int zptcon_(integer *n, doublereal *d__, doublecomplex *e,
+ doublereal *anorm, doublereal *rcond, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zptrfs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *d__, doublecomplex *e, doublereal *df, doublecomplex *ef,
+ doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *ferr, doublereal *berr, doublecomplex *work, doublereal *
+ rwork, integer *info);
+
+/* Subroutine */ int zptsv_(integer *n, integer *nrhs, doublereal *d__,
+ doublecomplex *e, doublecomplex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int zptsvx_(char *fact, integer *n, integer *nrhs,
+ doublereal *d__, doublecomplex *e, doublereal *df, doublecomplex *ef,
+ doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *rcond, doublereal *ferr, doublereal *berr, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zpttrf_(integer *n, doublereal *d__, doublecomplex *e,
+ integer *info);
+
+/* Subroutine */ int zpttrs_(char *uplo, integer *n, integer *nrhs,
+ doublereal *d__, doublecomplex *e, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zptts2_(integer *iuplo, integer *n, integer *nrhs,
+ doublereal *d__, doublecomplex *e, doublecomplex *b, integer *ldb);
+
+/* Subroutine */ int zrot_(integer *n, doublecomplex *cx, integer *incx,
+ doublecomplex *cy, integer *incy, doublereal *c__, doublecomplex *s);
+
+/* Subroutine */ int zspcon_(char *uplo, integer *n, doublecomplex *ap,
+ integer *ipiv, doublereal *anorm, doublereal *rcond, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zspmv_(char *uplo, integer *n, doublecomplex *alpha,
+ doublecomplex *ap, doublecomplex *x, integer *incx, doublecomplex *
+ beta, doublecomplex *y, integer *incy);
+
+/* Subroutine */ int zspr_(char *uplo, integer *n, doublecomplex *alpha,
+ doublecomplex *x, integer *incx, doublecomplex *ap);
+
+/* Subroutine */ int zsprfs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, doublecomplex *afp, integer *ipiv, doublecomplex *
+ b, integer *ldb, doublecomplex *x, integer *ldx, doublereal *ferr,
+ doublereal *berr, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int zspsv_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, integer *ipiv, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zspsvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublecomplex *ap, doublecomplex *afp, integer *ipiv,
+ doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *rcond, doublereal *ferr, doublereal *berr, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zsptrf_(char *uplo, integer *n, doublecomplex *ap,
+ integer *ipiv, integer *info);
+
+/* Subroutine */ int zsptri_(char *uplo, integer *n, doublecomplex *ap,
+ integer *ipiv, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zsptrs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *ap, integer *ipiv, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int zstedc_(char *compz, integer *n, doublereal *d__,
+ doublereal *e, doublecomplex *z__, integer *ldz, doublecomplex *work,
+ integer *lwork, doublereal *rwork, integer *lrwork, integer *iwork,
+ integer *liwork, integer *info);
+
+/* Subroutine */ int zstein_(integer *n, doublereal *d__, doublereal *e,
+ integer *m, doublereal *w, integer *iblock, integer *isplit,
+ doublecomplex *z__, integer *ldz, doublereal *work, integer *iwork,
+ integer *ifail, integer *info);
+
+/* Subroutine */ int zsteqr_(char *compz, integer *n, doublereal *d__,
+ doublereal *e, doublecomplex *z__, integer *ldz, doublereal *work,
+ integer *info);
+
+/* Subroutine */ int zsycon_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, doublereal *anorm, doublereal *rcond,
+ doublecomplex *work, integer *info);
+
+/* Subroutine */ int zsymv_(char *uplo, integer *n, doublecomplex *alpha,
+ doublecomplex *a, integer *lda, doublecomplex *x, integer *incx,
+ doublecomplex *beta, doublecomplex *y, integer *incy);
+
+/* Subroutine */ int zsyr_(char *uplo, integer *n, doublecomplex *alpha,
+ doublecomplex *x, integer *incx, doublecomplex *a, integer *lda);
+
+/* Subroutine */ int zsyrfs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, doublecomplex *af, integer *ldaf,
+ integer *ipiv, doublecomplex *b, integer *ldb, doublecomplex *x,
+ integer *ldx, doublereal *ferr, doublereal *berr, doublecomplex *work,
+ doublereal *rwork, integer *info);
+
+/* Subroutine */ int zsysv_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, integer *ipiv, doublecomplex *b,
+ integer *ldb, doublecomplex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int zsysvx_(char *fact, char *uplo, integer *n, integer *
+ nrhs, doublecomplex *a, integer *lda, doublecomplex *af, integer *
+ ldaf, integer *ipiv, doublecomplex *b, integer *ldb, doublecomplex *x,
+ integer *ldx, doublereal *rcond, doublereal *ferr, doublereal *berr,
+ doublecomplex *work, integer *lwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int zsytf2_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, integer *info);
+
+/* Subroutine */ int zsytrf_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zsytri_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, integer *ipiv, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zsytrs_(char *uplo, integer *n, integer *nrhs,
+ doublecomplex *a, integer *lda, integer *ipiv, doublecomplex *b,
+ integer *ldb, integer *info);
+
+/* Subroutine */ int ztbcon_(char *norm, char *uplo, char *diag, integer *n,
+ integer *kd, doublecomplex *ab, integer *ldab, doublereal *rcond,
+ doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int ztbrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, doublecomplex *ab, integer *ldab,
+ doublecomplex *b, integer *ldb, doublecomplex *x, integer *ldx,
+ doublereal *ferr, doublereal *berr, doublecomplex *work, doublereal *
+ rwork, integer *info);
+
+/* Subroutine */ int ztbtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *kd, integer *nrhs, doublecomplex *ab, integer *ldab,
+ doublecomplex *b, integer *ldb, integer *info);
+
+/* Subroutine */ int ztgevc_(char *side, char *howmny, logical *select,
+ integer *n, doublecomplex *a, integer *lda, doublecomplex *b, integer
+ *ldb, doublecomplex *vl, integer *ldvl, doublecomplex *vr, integer *
+ ldvr, integer *mm, integer *m, doublecomplex *work, doublereal *rwork,
+ integer *info);
+
+/* Subroutine */ int ztgex2_(logical *wantq, logical *wantz, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *q, integer *ldq, doublecomplex *z__, integer *ldz,
+ integer *j1, integer *info);
+
+/* Subroutine */ int ztgexc_(logical *wantq, logical *wantz, integer *n,
+ doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *q, integer *ldq, doublecomplex *z__, integer *ldz,
+ integer *ifst, integer *ilst, integer *info);
+
+/* Subroutine */ int ztgsen_(integer *ijob, logical *wantq, logical *wantz,
+ logical *select, integer *n, doublecomplex *a, integer *lda,
+ doublecomplex *b, integer *ldb, doublecomplex *alpha, doublecomplex *
+ beta, doublecomplex *q, integer *ldq, doublecomplex *z__, integer *
+ ldz, integer *m, doublereal *pl, doublereal *pr, doublereal *dif,
+ doublecomplex *work, integer *lwork, integer *iwork, integer *liwork,
+ integer *info);
+
+/* Subroutine */ int ztgsja_(char *jobu, char *jobv, char *jobq, integer *m,
+ integer *p, integer *n, integer *k, integer *l, doublecomplex *a,
+ integer *lda, doublecomplex *b, integer *ldb, doublereal *tola,
+ doublereal *tolb, doublereal *alpha, doublereal *beta, doublecomplex *
+ u, integer *ldu, doublecomplex *v, integer *ldv, doublecomplex *q,
+ integer *ldq, doublecomplex *work, integer *ncycle, integer *info);
+
+/* Subroutine */ int ztgsna_(char *job, char *howmny, logical *select,
+ integer *n, doublecomplex *a, integer *lda, doublecomplex *b, integer
+ *ldb, doublecomplex *vl, integer *ldvl, doublecomplex *vr, integer *
+ ldvr, doublereal *s, doublereal *dif, integer *mm, integer *m,
+ doublecomplex *work, integer *lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int ztgsy2_(char *trans, integer *ijob, integer *m, integer *
+ n, doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *c__, integer *ldc, doublecomplex *d__, integer *ldd,
+ doublecomplex *e, integer *lde, doublecomplex *f, integer *ldf,
+ doublereal *scale, doublereal *rdsum, doublereal *rdscal, integer *
+ info);
+
+/* Subroutine */ int ztgsyl_(char *trans, integer *ijob, integer *m, integer *
+ n, doublecomplex *a, integer *lda, doublecomplex *b, integer *ldb,
+ doublecomplex *c__, integer *ldc, doublecomplex *d__, integer *ldd,
+ doublecomplex *e, integer *lde, doublecomplex *f, integer *ldf,
+ doublereal *scale, doublereal *dif, doublecomplex *work, integer *
+ lwork, integer *iwork, integer *info);
+
+/* Subroutine */ int ztpcon_(char *norm, char *uplo, char *diag, integer *n,
+ doublecomplex *ap, doublereal *rcond, doublecomplex *work, doublereal
+ *rwork, integer *info);
+
+/* Subroutine */ int ztprfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublecomplex *ap, doublecomplex *b, integer *ldb,
+ doublecomplex *x, integer *ldx, doublereal *ferr, doublereal *berr,
+ doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int ztptri_(char *uplo, char *diag, integer *n,
+ doublecomplex *ap, integer *info);
+
+/* Subroutine */ int ztptrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublecomplex *ap, doublecomplex *b, integer *ldb,
+ integer *info);
+
+/* Subroutine */ int ztrcon_(char *norm, char *uplo, char *diag, integer *n,
+ doublecomplex *a, integer *lda, doublereal *rcond, doublecomplex *
+ work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int ztrevc_(char *side, char *howmny, logical *select,
+ integer *n, doublecomplex *t, integer *ldt, doublecomplex *vl,
+ integer *ldvl, doublecomplex *vr, integer *ldvr, integer *mm, integer
+ *m, doublecomplex *work, doublereal *rwork, integer *info);
+
+/* Subroutine */ int ztrexc_(char *compq, integer *n, doublecomplex *t,
+ integer *ldt, doublecomplex *q, integer *ldq, integer *ifst, integer *
+ ilst, integer *info);
+
+/* Subroutine */ int ztrrfs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublecomplex *a, integer *lda, doublecomplex *b,
+ integer *ldb, doublecomplex *x, integer *ldx, doublereal *ferr,
+ doublereal *berr, doublecomplex *work, doublereal *rwork, integer *
+ info);
+
+/* Subroutine */ int ztrsen_(char *job, char *compq, logical *select, integer
+ *n, doublecomplex *t, integer *ldt, doublecomplex *q, integer *ldq,
+ doublecomplex *w, integer *m, doublereal *s, doublereal *sep,
+ doublecomplex *work, integer *lwork, integer *info);
+
+/* Subroutine */ int ztrsna_(char *job, char *howmny, logical *select,
+ integer *n, doublecomplex *t, integer *ldt, doublecomplex *vl,
+ integer *ldvl, doublecomplex *vr, integer *ldvr, doublereal *s,
+ doublereal *sep, integer *mm, integer *m, doublecomplex *work,
+ integer *ldwork, doublereal *rwork, integer *info);
+
+/* Subroutine */ int ztrsyl_(char *trana, char *tranb, integer *isgn, integer
+ *m, integer *n, doublecomplex *a, integer *lda, doublecomplex *b,
+ integer *ldb, doublecomplex *c__, integer *ldc, doublereal *scale,
+ integer *info);
+
+/* Subroutine */ int ztrti2_(char *uplo, char *diag, integer *n,
+ doublecomplex *a, integer *lda, integer *info);
+
+/* Subroutine */ int ztrtri_(char *uplo, char *diag, integer *n,
+ doublecomplex *a, integer *lda, integer *info);
+
+/* Subroutine */ int ztrtrs_(char *uplo, char *trans, char *diag, integer *n,
+ integer *nrhs, doublecomplex *a, integer *lda, doublecomplex *b,
+ integer *ldb, integer *info);
+
+/* Subroutine */ int ztzrqf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, integer *info);
+
+/* Subroutine */ int ztzrzf_(integer *m, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zung2l_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zung2r_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zungbr_(char *vect, integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zunghr_(integer *n, integer *ilo, integer *ihi,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zungl2_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zunglq_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zungql_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zungqr_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zungr2_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zungrq_(integer *m, integer *n, integer *k,
+ doublecomplex *a, integer *lda, doublecomplex *tau, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zungtr_(char *uplo, integer *n, doublecomplex *a,
+ integer *lda, doublecomplex *tau, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zunm2l_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zunm2r_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zunmbr_(char *vect, char *side, char *trans, integer *m,
+ integer *n, integer *k, doublecomplex *a, integer *lda, doublecomplex
+ *tau, doublecomplex *c__, integer *ldc, doublecomplex *work, integer *
+ lwork, integer *info);
+
+/* Subroutine */ int zunmhr_(char *side, char *trans, integer *m, integer *n,
+ integer *ilo, integer *ihi, doublecomplex *a, integer *lda,
+ doublecomplex *tau, doublecomplex *c__, integer *ldc, doublecomplex *
+ work, integer *lwork, integer *info);
+
+/* Subroutine */ int zunml2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zunmlq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zunmql_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zunmqr_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zunmr2_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *info);
+
+/* Subroutine */ int zunmr3_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, doublecomplex *a, integer *lda, doublecomplex
+ *tau, doublecomplex *c__, integer *ldc, doublecomplex *work, integer *
+ info);
+
+/* Subroutine */ int zunmrq_(char *side, char *trans, integer *m, integer *n,
+ integer *k, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zunmrz_(char *side, char *trans, integer *m, integer *n,
+ integer *k, integer *l, doublecomplex *a, integer *lda, doublecomplex
+ *tau, doublecomplex *c__, integer *ldc, doublecomplex *work, integer *
+ lwork, integer *info);
+
+/* Subroutine */ int zunmtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, doublecomplex *a, integer *lda, doublecomplex *tau,
+ doublecomplex *c__, integer *ldc, doublecomplex *work, integer *lwork,
+ integer *info);
+
+/* Subroutine */ int zupgtr_(char *uplo, integer *n, doublecomplex *ap,
+ doublecomplex *tau, doublecomplex *q, integer *ldq, doublecomplex *
+ work, integer *info);
+
+/* Subroutine */ int zupmtr_(char *side, char *uplo, char *trans, integer *m,
+ integer *n, doublecomplex *ap, doublecomplex *tau, doublecomplex *c__,
+ integer *ldc, doublecomplex *work, integer *info);
+
+#endif /* __CLAPACK_H */
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/close.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/close.c
new file mode 100644
index 00000000..d305ccf8
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/close.c
@@ -0,0 +1,80 @@
+#include "f2c.h"
+#include "fio.h"
+#undef abs
+#undef min
+#undef max
+#include "stdlib.h"
+#ifdef NON_UNIX_STDIO
+#ifndef unlink
+#define unlink remove
+#endif
+#else
+#ifdef MSDOS
+#include "io.h"
+#else
+#ifdef __cplusplus
+extern "C" int unlink(const char*);
+#else
+extern int unlink(const char*);
+#endif
+#endif
+#endif
+
+integer f_clos(cllist *a)
+{ unit *b;
+
+ if(a->cunit >= MXUNIT) return(0);
+ b= &f__units[a->cunit];
+ if(b->ufd==NULL)
+ goto done;
+ if (b->uscrtch == 1)
+ goto Delete;
+ if (!a->csta)
+ goto Keep;
+ switch(*a->csta) {
+ default:
+ Keep:
+ case 'k':
+ case 'K':
+ if(b->uwrt == 1)
+ t_runc((alist *)a);
+ if(b->ufnm) {
+ fclose(b->ufd);
+ free(b->ufnm);
+ }
+ break;
+ case 'd':
+ case 'D':
+ Delete:
+ fclose(b->ufd);
+ if(b->ufnm) {
+ unlink(b->ufnm); /*SYSDEP*/
+ free(b->ufnm);
+ }
+ }
+ b->ufd=NULL;
+ done:
+ b->uend=0;
+ b->ufnm=NULL;
+ return(0);
+ }
+void f_exit(void)
+{ int i;
+ static cllist xx;
+ if (!xx.cerr) {
+ xx.cerr=1;
+ xx.csta=NULL;
+ for(i=0;i<MXUNIT;i++)
+ {
+ xx.cunit=i;
+ (void) f_clos(&xx);
+ }
+ }
+}
+int flush_(void)
+{ int i;
+ for(i=0;i<MXUNIT;i++)
+ if(f__units[i].ufd != NULL && f__units[i].uwrt)
+ fflush(f__units[i].ufd);
+return 0;
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgemm.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgemm.c
new file mode 100644
index 00000000..964a278a
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgemm.c
@@ -0,0 +1,313 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dgemm_(char *transa, char *transb, integer *m, integer *
+ n, integer *k, doublereal *alpha, doublereal *a, integer *lda,
+ doublereal *b, integer *ldb, doublereal *beta, doublereal *c__,
+ integer *ldc)
+{
+ /* System generated locals */
+ integer a_dim1, a_offset, b_dim1, b_offset, c_dim1, c_offset, i__1, i__2,
+ i__3;
+ /* Local variables */
+ static integer info;
+ static logical nota, notb;
+ static doublereal temp;
+ static integer i__, j, l, ncola;
+ extern logical lsame_(char *, char *);
+ static integer nrowa, nrowb;
+ extern /* Subroutine */ int xerbla_(char *, integer *);
+#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1]
+#define b_ref(a_1,a_2) b[(a_2)*b_dim1 + a_1]
+#define c___ref(a_1,a_2) c__[(a_2)*c_dim1 + a_1]
+/* Purpose
+ =======
+ DGEMM performs one of the matrix-matrix operations
+ C := alpha*op( A )*op( B ) + beta*C,
+ where op( X ) is one of
+ op( X ) = X or op( X ) = X',
+ alpha and beta are scalars, and A, B and C are matrices, with op( A )
+ an m by k matrix, op( B ) a k by n matrix and C an m by n matrix.
+ Parameters
+ ==========
+ TRANSA - CHARACTER*1.
+ On entry, TRANSA specifies the form of op( A ) to be used in
+ the matrix multiplication as follows:
+ TRANSA = 'N' or 'n', op( A ) = A.
+ TRANSA = 'T' or 't', op( A ) = A'.
+ TRANSA = 'C' or 'c', op( A ) = A'.
+ Unchanged on exit.
+ TRANSB - CHARACTER*1.
+ On entry, TRANSB specifies the form of op( B ) to be used in
+ the matrix multiplication as follows:
+ TRANSB = 'N' or 'n', op( B ) = B.
+ TRANSB = 'T' or 't', op( B ) = B'.
+ TRANSB = 'C' or 'c', op( B ) = B'.
+ Unchanged on exit.
+ M - INTEGER.
+ On entry, M specifies the number of rows of the matrix
+ op( A ) and of the matrix C. M must be at least zero.
+ Unchanged on exit.
+ N - INTEGER.
+ On entry, N specifies the number of columns of the matrix
+ op( B ) and the number of columns of the matrix C. N must be
+ at least zero.
+ Unchanged on exit.
+ K - INTEGER.
+ On entry, K specifies the number of columns of the matrix
+ op( A ) and the number of rows of the matrix op( B ). K must
+ be at least zero.
+ Unchanged on exit.
+ ALPHA - DOUBLE PRECISION.
+ On entry, ALPHA specifies the scalar alpha.
+ Unchanged on exit.
+ A - DOUBLE PRECISION array of DIMENSION ( LDA, ka ), where ka is
+ k when TRANSA = 'N' or 'n', and is m otherwise.
+ Before entry with TRANSA = 'N' or 'n', the leading m by k
+ part of the array A must contain the matrix A, otherwise
+ the leading k by m part of the array A must contain the
+ matrix A.
+ Unchanged on exit.
+ LDA - INTEGER.
+ On entry, LDA specifies the first dimension of A as declared
+ in the calling (sub) program. When TRANSA = 'N' or 'n' then
+ LDA must be at least max( 1, m ), otherwise LDA must be at
+ least max( 1, k ).
+ Unchanged on exit.
+ B - DOUBLE PRECISION array of DIMENSION ( LDB, kb ), where kb is
+ n when TRANSB = 'N' or 'n', and is k otherwise.
+ Before entry with TRANSB = 'N' or 'n', the leading k by n
+ part of the array B must contain the matrix B, otherwise
+ the leading n by k part of the array B must contain the
+ matrix B.
+ Unchanged on exit.
+ LDB - INTEGER.
+ On entry, LDB specifies the first dimension of B as declared
+ in the calling (sub) program. When TRANSB = 'N' or 'n' then
+ LDB must be at least max( 1, k ), otherwise LDB must be at
+ least max( 1, n ).
+ Unchanged on exit.
+ BETA - DOUBLE PRECISION.
+ On entry, BETA specifies the scalar beta. When BETA is
+ supplied as zero then C need not be set on input.
+ Unchanged on exit.
+ C - DOUBLE PRECISION array of DIMENSION ( LDC, n ).
+ Before entry, the leading m by n part of the array C must
+ contain the matrix C, except when beta is zero, in which
+ case C need not be set on entry.
+ On exit, the array C is overwritten by the m by n matrix
+ ( alpha*op( A )*op( B ) + beta*C ).
+ LDC - INTEGER.
+ On entry, LDC specifies the first dimension of C as declared
+ in the calling (sub) program. LDC must be at least
+ max( 1, m ).
+ Unchanged on exit.
+ Level 3 Blas routine.
+ -- Written on 8-February-1989.
+ Jack Dongarra, Argonne National Laboratory.
+ Iain Duff, AERE Harwell.
+ Jeremy Du Croz, Numerical Algorithms Group Ltd.
+ Sven Hammarling, Numerical Algorithms Group Ltd.
+ Set NOTA and NOTB as true if A and B respectively are not
+ transposed and set NROWA, NCOLA and NROWB as the number of rows
+ and columns of A and the number of rows of B respectively.
+ Parameter adjustments */
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ b_dim1 = *ldb;
+ b_offset = 1 + b_dim1 * 1;
+ b -= b_offset;
+ c_dim1 = *ldc;
+ c_offset = 1 + c_dim1 * 1;
+ c__ -= c_offset;
+ /* Function Body */
+ nota = lsame_(transa, "N");
+ notb = lsame_(transb, "N");
+ if (nota) {
+ nrowa = *m;
+ ncola = *k;
+ } else {
+ nrowa = *k;
+ ncola = *m;
+ }
+ if (notb) {
+ nrowb = *k;
+ } else {
+ nrowb = *n;
+ }
+/* Test the input parameters. */
+ info = 0;
+ if (! nota && ! lsame_(transa, "C") && ! lsame_(
+ transa, "T")) {
+ info = 1;
+ } else if (! notb && ! lsame_(transb, "C") && !
+ lsame_(transb, "T")) {
+ info = 2;
+ } else if (*m < 0) {
+ info = 3;
+ } else if (*n < 0) {
+ info = 4;
+ } else if (*k < 0) {
+ info = 5;
+ } else if (*lda < max(1,nrowa)) {
+ info = 8;
+ } else if (*ldb < max(1,nrowb)) {
+ info = 10;
+ } else if (*ldc < max(1,*m)) {
+ info = 13;
+ }
+ if (info != 0) {
+ xerbla_("DGEMM ", &info);
+ return 0;
+ }
+/* Quick return if possible. */
+ if (*m == 0 || *n == 0 || (*alpha == 0. || *k == 0) && *beta == 1.) {
+ return 0;
+ }
+/* And if alpha.eq.zero. */
+ if (*alpha == 0.) {
+ if (*beta == 0.) {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ c___ref(i__, j) = 0.;
+/* L10: */
+ }
+/* L20: */
+ }
+ } else {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ c___ref(i__, j) = *beta * c___ref(i__, j);
+/* L30: */
+ }
+/* L40: */
+ }
+ }
+ return 0;
+ }
+/* Start the operations. */
+ if (notb) {
+ if (nota) {
+/* Form C := alpha*A*B + beta*C. */
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ if (*beta == 0.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ c___ref(i__, j) = 0.;
+/* L50: */
+ }
+ } else if (*beta != 1.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ c___ref(i__, j) = *beta * c___ref(i__, j);
+/* L60: */
+ }
+ }
+ i__2 = *k;
+ for (l = 1; l <= i__2; ++l) {
+ if (b_ref(l, j) != 0.) {
+ temp = *alpha * b_ref(l, j);
+ i__3 = *m;
+ for (i__ = 1; i__ <= i__3; ++i__) {
+ c___ref(i__, j) = c___ref(i__, j) + temp * a_ref(
+ i__, l);
+/* L70: */
+ }
+ }
+/* L80: */
+ }
+/* L90: */
+ }
+ } else {
+/* Form C := alpha*A'*B + beta*C */
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ temp = 0.;
+ i__3 = *k;
+ for (l = 1; l <= i__3; ++l) {
+ temp += a_ref(l, i__) * b_ref(l, j);
+/* L100: */
+ }
+ if (*beta == 0.) {
+ c___ref(i__, j) = *alpha * temp;
+ } else {
+ c___ref(i__, j) = *alpha * temp + *beta * c___ref(i__,
+ j);
+ }
+/* L110: */
+ }
+/* L120: */
+ }
+ }
+ } else {
+ if (nota) {
+/* Form C := alpha*A*B' + beta*C */
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ if (*beta == 0.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ c___ref(i__, j) = 0.;
+/* L130: */
+ }
+ } else if (*beta != 1.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ c___ref(i__, j) = *beta * c___ref(i__, j);
+/* L140: */
+ }
+ }
+ i__2 = *k;
+ for (l = 1; l <= i__2; ++l) {
+ if (b_ref(j, l) != 0.) {
+ temp = *alpha * b_ref(j, l);
+ i__3 = *m;
+ for (i__ = 1; i__ <= i__3; ++i__) {
+ c___ref(i__, j) = c___ref(i__, j) + temp * a_ref(
+ i__, l);
+/* L150: */
+ }
+ }
+/* L160: */
+ }
+/* L170: */
+ }
+ } else {
+/* Form C := alpha*A'*B' + beta*C */
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ temp = 0.;
+ i__3 = *k;
+ for (l = 1; l <= i__3; ++l) {
+ temp += a_ref(l, i__) * b_ref(j, l);
+/* L180: */
+ }
+ if (*beta == 0.) {
+ c___ref(i__, j) = *alpha * temp;
+ } else {
+ c___ref(i__, j) = *alpha * temp + *beta * c___ref(i__,
+ j);
+ }
+/* L190: */
+ }
+/* L200: */
+ }
+ }
+ }
+ return 0;
+/* End of DGEMM . */
+} /* dgemm_ */
+#undef c___ref
+#undef b_ref
+#undef a_ref
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dger.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dger.c
new file mode 100644
index 00000000..c53835fa
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dger.c
@@ -0,0 +1,143 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dger_(integer *m, integer *n, doublereal *alpha,
+ doublereal *x, integer *incx, doublereal *y, integer *incy,
+ doublereal *a, integer *lda)
+{
+ /* System generated locals */
+ integer a_dim1, a_offset, i__1, i__2;
+ /* Local variables */
+ static integer info;
+ static doublereal temp;
+ static integer i__, j, ix, jy, kx;
+ extern /* Subroutine */ int xerbla_(char *, integer *);
+#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1]
+/* Purpose
+ =======
+ DGER performs the rank 1 operation
+ A := alpha*x*y' + A,
+ where alpha is a scalar, x is an m element vector, y is an n element
+ vector and A is an m by n matrix.
+ Parameters
+ ==========
+ M - INTEGER.
+ On entry, M specifies the number of rows of the matrix A.
+ M must be at least zero.
+ Unchanged on exit.
+ N - INTEGER.
+ On entry, N specifies the number of columns of the matrix A.
+ N must be at least zero.
+ Unchanged on exit.
+ ALPHA - DOUBLE PRECISION.
+ On entry, ALPHA specifies the scalar alpha.
+ Unchanged on exit.
+ X - DOUBLE PRECISION array of dimension at least
+ ( 1 + ( m - 1 )*abs( INCX ) ).
+ Before entry, the incremented array X must contain the m
+ element vector x.
+ Unchanged on exit.
+ INCX - INTEGER.
+ On entry, INCX specifies the increment for the elements of
+ X. INCX must not be zero.
+ Unchanged on exit.
+ Y - DOUBLE PRECISION array of dimension at least
+ ( 1 + ( n - 1 )*abs( INCY ) ).
+ Before entry, the incremented array Y must contain the n
+ element vector y.
+ Unchanged on exit.
+ INCY - INTEGER.
+ On entry, INCY specifies the increment for the elements of
+ Y. INCY must not be zero.
+ Unchanged on exit.
+ A - DOUBLE PRECISION array of DIMENSION ( LDA, n ).
+ Before entry, the leading m by n part of the array A must
+ contain the matrix of coefficients. On exit, A is
+ overwritten by the updated matrix.
+ LDA - INTEGER.
+ On entry, LDA specifies the first dimension of A as declared
+ in the calling (sub) program. LDA must be at least
+ max( 1, m ).
+ Unchanged on exit.
+ Level 2 Blas routine.
+ -- Written on 22-October-1986.
+ Jack Dongarra, Argonne National Lab.
+ Jeremy Du Croz, Nag Central Office.
+ Sven Hammarling, Nag Central Office.
+ Richard Hanson, Sandia National Labs.
+ Test the input parameters.
+ Parameter adjustments */
+ --x;
+ --y;
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ /* Function Body */
+ info = 0;
+ if (*m < 0) {
+ info = 1;
+ } else if (*n < 0) {
+ info = 2;
+ } else if (*incx == 0) {
+ info = 5;
+ } else if (*incy == 0) {
+ info = 7;
+ } else if (*lda < max(1,*m)) {
+ info = 9;
+ }
+ if (info != 0) {
+ xerbla_("DGER ", &info);
+ return 0;
+ }
+/* Quick return if possible. */
+ if (*m == 0 || *n == 0 || *alpha == 0.) {
+ return 0;
+ }
+/* Start the operations. In this version the elements of A are
+ accessed sequentially with one pass through A. */
+ if (*incy > 0) {
+ jy = 1;
+ } else {
+ jy = 1 - (*n - 1) * *incy;
+ }
+ if (*incx == 1) {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ if (y[jy] != 0.) {
+ temp = *alpha * y[jy];
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ a_ref(i__, j) = a_ref(i__, j) + x[i__] * temp;
+/* L10: */
+ }
+ }
+ jy += *incy;
+/* L20: */
+ }
+ } else {
+ if (*incx > 0) {
+ kx = 1;
+ } else {
+ kx = 1 - (*m - 1) * *incx;
+ }
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ if (y[jy] != 0.) {
+ temp = *alpha * y[jy];
+ ix = kx;
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ a_ref(i__, j) = a_ref(i__, j) + x[ix] * temp;
+ ix += *incx;
+/* L30: */
+ }
+ }
+ jy += *incy;
+/* L40: */
+ }
+ }
+ return 0;
+/* End of DGER . */
+} /* dger_ */
+#undef a_ref
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgesv.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgesv.c
new file mode 100644
index 00000000..5c0dc52b
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgesv.c
@@ -0,0 +1,117 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dgesv_(integer *n, integer *nrhs, doublereal *a, integer
+ *lda, integer *ipiv, doublereal *b, integer *ldb, integer *info)
+{
+/* -- LAPACK driver routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ March 31, 1993
+
+
+ Purpose
+ =======
+
+ DGESV computes the solution to a real system of linear equations
+ A * X = B,
+ where A is an N-by-N matrix and X and B are N-by-NRHS matrices.
+
+ The LU decomposition with partial pivoting and row interchanges is
+ used to factor A as
+ A = P * L * U,
+ where P is a permutation matrix, L is unit lower triangular, and U is
+ upper triangular. The factored form of A is then used to solve the
+ system of equations A * X = B.
+
+ Arguments
+ =========
+
+ N (input) INTEGER
+ The number of linear equations, i.e., the order of the
+ matrix A. N >= 0.
+
+ NRHS (input) INTEGER
+ The number of right hand sides, i.e., the number of columns
+ of the matrix B. NRHS >= 0.
+
+ A (input/output) DOUBLE PRECISION array, dimension (LDA,N)
+ On entry, the N-by-N coefficient matrix A.
+ On exit, the factors L and U from the factorization
+ A = P*L*U; the unit diagonal elements of L are not stored.
+
+ LDA (input) INTEGER
+ The leading dimension of the array A. LDA >= max(1,N).
+
+ IPIV (output) INTEGER array, dimension (N)
+ The pivot indices that define the permutation matrix P;
+ row i of the matrix was interchanged with row IPIV(i).
+
+ B (input/output) DOUBLE PRECISION array, dimension (LDB,NRHS)
+ On entry, the N-by-NRHS matrix of right hand side matrix B.
+ On exit, if INFO = 0, the N-by-NRHS solution matrix X.
+
+ LDB (input) INTEGER
+ The leading dimension of the array B. LDB >= max(1,N).
+
+ INFO (output) INTEGER
+ = 0: successful exit
+ < 0: if INFO = -i, the i-th argument had an illegal value
+ > 0: if INFO = i, U(i,i) is exactly zero. The factorization
+ has been completed, but the factor U is exactly
+ singular, so the solution could not be computed.
+
+ =====================================================================
+
+
+ Test the input parameters.
+
+ Parameter adjustments */
+ /* System generated locals */
+ integer a_dim1, a_offset, b_dim1, b_offset, i__1;
+ /* Local variables */
+ extern /* Subroutine */ int dgetrf_(integer *, integer *, doublereal *,
+ integer *, integer *, integer *), xerbla_(char *, integer *), dgetrs_(char *, integer *, integer *, doublereal *,
+ integer *, integer *, doublereal *, integer *, integer *);
+
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ --ipiv;
+ b_dim1 = *ldb;
+ b_offset = 1 + b_dim1 * 1;
+ b -= b_offset;
+
+ /* Function Body */
+ *info = 0;
+ if (*n < 0) {
+ *info = -1;
+ } else if (*nrhs < 0) {
+ *info = -2;
+ } else if (*lda < max(1,*n)) {
+ *info = -4;
+ } else if (*ldb < max(1,*n)) {
+ *info = -7;
+ }
+ if (*info != 0) {
+ i__1 = -(*info);
+ xerbla_("DGESV ", &i__1);
+ return 0;
+ }
+
+/* Compute the LU factorization of A. */
+
+ dgetrf_(n, n, &a[a_offset], lda, &ipiv[1], info);
+ if (*info == 0) {
+
+/* Solve the system A*X = B, overwriting B with X. */
+
+ dgetrs_("No transpose", n, nrhs, &a[a_offset], lda, &ipiv[1], &b[
+ b_offset], ldb, info);
+ }
+ return 0;
+
+/* End of DGESV */
+
+} /* dgesv_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetf2.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetf2.c
new file mode 100644
index 00000000..83bf1872
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetf2.c
@@ -0,0 +1,157 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dgetf2_(integer *m, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, integer *info)
+{
+/* -- LAPACK routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ June 30, 1992
+
+
+ Purpose
+ =======
+
+ DGETF2 computes an LU factorization of a general m-by-n matrix A
+ using partial pivoting with row interchanges.
+
+ The factorization has the form
+ A = P * L * U
+ where P is a permutation matrix, L is lower triangular with unit
+ diagonal elements (lower trapezoidal if m > n), and U is upper
+ triangular (upper trapezoidal if m < n).
+
+ This is the right-looking Level 2 BLAS version of the algorithm.
+
+ Arguments
+ =========
+
+ M (input) INTEGER
+ The number of rows of the matrix A. M >= 0.
+
+ N (input) INTEGER
+ The number of columns of the matrix A. N >= 0.
+
+ A (input/output) DOUBLE PRECISION array, dimension (LDA,N)
+ On entry, the m by n matrix to be factored.
+ On exit, the factors L and U from the factorization
+ A = P*L*U; the unit diagonal elements of L are not stored.
+
+ LDA (input) INTEGER
+ The leading dimension of the array A. LDA >= max(1,M).
+
+ IPIV (output) INTEGER array, dimension (min(M,N))
+ The pivot indices; for 1 <= i <= min(M,N), row i of the
+ matrix was interchanged with row IPIV(i).
+
+ INFO (output) INTEGER
+ = 0: successful exit
+ < 0: if INFO = -k, the k-th argument had an illegal value
+ > 0: if INFO = k, U(k,k) is exactly zero. The factorization
+ has been completed, but the factor U is exactly
+ singular, and division by zero will occur if it is used
+ to solve a system of equations.
+
+ =====================================================================
+
+
+ Test the input parameters.
+
+ Parameter adjustments */
+ /* Table of constant values */
+ static integer c__1 = 1;
+ static doublereal c_b6 = -1.;
+
+ /* System generated locals */
+ integer a_dim1, a_offset, i__1, i__2, i__3;
+ doublereal d__1;
+ /* Local variables */
+ extern /* Subroutine */ int dger_(integer *, integer *, doublereal *,
+ doublereal *, integer *, doublereal *, integer *, doublereal *,
+ integer *);
+ static integer j;
+ extern /* Subroutine */ int dscal_(integer *, doublereal *, doublereal *,
+ integer *), dswap_(integer *, doublereal *, integer *, doublereal
+ *, integer *);
+ static integer jp;
+ extern integer idamax_(integer *, doublereal *, integer *);
+ extern /* Subroutine */ int xerbla_(char *, integer *);
+#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1]
+
+
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ --ipiv;
+
+ /* Function Body */
+ *info = 0;
+ if (*m < 0) {
+ *info = -1;
+ } else if (*n < 0) {
+ *info = -2;
+ } else if (*lda < max(1,*m)) {
+ *info = -4;
+ }
+ if (*info != 0) {
+ i__1 = -(*info);
+ xerbla_("DGETF2", &i__1);
+ return 0;
+ }
+
+/* Quick return if possible */
+
+ if (*m == 0 || *n == 0) {
+ return 0;
+ }
+
+ i__1 = min(*m,*n);
+ for (j = 1; j <= i__1; ++j) {
+
+/* Find pivot and test for singularity. */
+
+ i__2 = *m - j + 1;
+ jp = j - 1 + idamax_(&i__2, &a_ref(j, j), &c__1);
+ ipiv[j] = jp;
+ if (a_ref(jp, j) != 0.) {
+
+/* Apply the interchange to columns 1:N. */
+
+ if (jp != j) {
+ dswap_(n, &a_ref(j, 1), lda, &a_ref(jp, 1), lda);
+ }
+
+/* Compute elements J+1:M of J-th column. */
+
+ if (j < *m) {
+ i__2 = *m - j;
+ d__1 = 1. / a_ref(j, j);
+ dscal_(&i__2, &d__1, &a_ref(j + 1, j), &c__1);
+ }
+
+ } else if (*info == 0) {
+
+ *info = j;
+ }
+
+ if (j < min(*m,*n)) {
+
+/* Update trailing submatrix. */
+
+ i__2 = *m - j;
+ i__3 = *n - j;
+ dger_(&i__2, &i__3, &c_b6, &a_ref(j + 1, j), &c__1, &a_ref(j, j +
+ 1), lda, &a_ref(j + 1, j + 1), lda);
+ }
+/* L10: */
+ }
+ return 0;
+
+/* End of DGETF2 */
+
+} /* dgetf2_ */
+
+#undef a_ref
+
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrf.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrf.c
new file mode 100644
index 00000000..13175f01
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrf.c
@@ -0,0 +1,197 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dgetrf_(integer *m, integer *n, doublereal *a, integer *
+ lda, integer *ipiv, integer *info)
+{
+/* -- LAPACK routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ March 31, 1993
+
+
+ Purpose
+ =======
+
+ DGETRF computes an LU factorization of a general M-by-N matrix A
+ using partial pivoting with row interchanges.
+
+ The factorization has the form
+ A = P * L * U
+ where P is a permutation matrix, L is lower triangular with unit
+ diagonal elements (lower trapezoidal if m > n), and U is upper
+ triangular (upper trapezoidal if m < n).
+
+ This is the right-looking Level 3 BLAS version of the algorithm.
+
+ Arguments
+ =========
+
+ M (input) INTEGER
+ The number of rows of the matrix A. M >= 0.
+
+ N (input) INTEGER
+ The number of columns of the matrix A. N >= 0.
+
+ A (input/output) DOUBLE PRECISION array, dimension (LDA,N)
+ On entry, the M-by-N matrix to be factored.
+ On exit, the factors L and U from the factorization
+ A = P*L*U; the unit diagonal elements of L are not stored.
+
+ LDA (input) INTEGER
+ The leading dimension of the array A. LDA >= max(1,M).
+
+ IPIV (output) INTEGER array, dimension (min(M,N))
+ The pivot indices; for 1 <= i <= min(M,N), row i of the
+ matrix was interchanged with row IPIV(i).
+
+ INFO (output) INTEGER
+ = 0: successful exit
+ < 0: if INFO = -i, the i-th argument had an illegal value
+ > 0: if INFO = i, U(i,i) is exactly zero. The factorization
+ has been completed, but the factor U is exactly
+ singular, and division by zero will occur if it is used
+ to solve a system of equations.
+
+ =====================================================================
+
+
+ Test the input parameters.
+
+ Parameter adjustments */
+ /* Table of constant values */
+ static integer c__1 = 1;
+ static integer c_n1 = -1;
+ static doublereal c_b16 = 1.;
+ static doublereal c_b19 = -1.;
+
+ /* System generated locals */
+ integer a_dim1, a_offset, i__1, i__2, i__3, i__4, i__5;
+ /* Local variables */
+ static integer i__, j;
+ extern /* Subroutine */ int dgemm_(char *, char *, integer *, integer *,
+ integer *, doublereal *, doublereal *, integer *, doublereal *,
+ integer *, doublereal *, doublereal *, integer *);
+ static integer iinfo;
+ extern /* Subroutine */ int dtrsm_(char *, char *, char *, char *,
+ integer *, integer *, doublereal *, doublereal *, integer *,
+ doublereal *, integer *), dgetf2_(
+ integer *, integer *, doublereal *, integer *, integer *, integer
+ *);
+ static integer jb, nb;
+ extern /* Subroutine */ int xerbla_(char *, integer *);
+ extern integer ilaenv_(integer *, char *, char *, integer *, integer *,
+ integer *, integer *, ftnlen, ftnlen);
+ extern /* Subroutine */ int dlaswp_(integer *, doublereal *, integer *,
+ integer *, integer *, integer *, integer *);
+#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1]
+
+
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ --ipiv;
+
+ /* Function Body */
+ *info = 0;
+ if (*m < 0) {
+ *info = -1;
+ } else if (*n < 0) {
+ *info = -2;
+ } else if (*lda < max(1,*m)) {
+ *info = -4;
+ }
+ if (*info != 0) {
+ i__1 = -(*info);
+ xerbla_("DGETRF", &i__1);
+ return 0;
+ }
+
+/* Quick return if possible */
+
+ if (*m == 0 || *n == 0) {
+ return 0;
+ }
+
+/* Determine the block size for this environment. */
+
+ nb = ilaenv_(&c__1, "DGETRF", " ", m, n, &c_n1, &c_n1, (ftnlen)6, (ftnlen)
+ 1);
+ if (nb <= 1 || nb >= min(*m,*n)) {
+
+/* Use unblocked code. */
+
+ dgetf2_(m, n, &a[a_offset], lda, &ipiv[1], info);
+ } else {
+
+/* Use blocked code. */
+
+ i__1 = min(*m,*n);
+ i__2 = nb;
+ for (j = 1; i__2 < 0 ? j >= i__1 : j <= i__1; j += i__2) {
+/* Computing MIN */
+ i__3 = min(*m,*n) - j + 1;
+ jb = min(i__3,nb);
+
+/* Factor diagonal and subdiagonal blocks and test for exact
+ singularity. */
+
+ i__3 = *m - j + 1;
+ dgetf2_(&i__3, &jb, &a_ref(j, j), lda, &ipiv[j], &iinfo);
+
+/* Adjust INFO and the pivot indices. */
+
+ if (*info == 0 && iinfo > 0) {
+ *info = iinfo + j - 1;
+ }
+/* Computing MIN */
+ i__4 = *m, i__5 = j + jb - 1;
+ i__3 = min(i__4,i__5);
+ for (i__ = j; i__ <= i__3; ++i__) {
+ ipiv[i__] = j - 1 + ipiv[i__];
+/* L10: */
+ }
+
+/* Apply interchanges to columns 1:J-1. */
+
+ i__3 = j - 1;
+ i__4 = j + jb - 1;
+ dlaswp_(&i__3, &a[a_offset], lda, &j, &i__4, &ipiv[1], &c__1);
+
+ if (j + jb <= *n) {
+
+/* Apply interchanges to columns J+JB:N. */
+
+ i__3 = *n - j - jb + 1;
+ i__4 = j + jb - 1;
+ dlaswp_(&i__3, &a_ref(1, j + jb), lda, &j, &i__4, &ipiv[1], &
+ c__1);
+
+/* Compute block row of U. */
+
+ i__3 = *n - j - jb + 1;
+ dtrsm_("Left", "Lower", "No transpose", "Unit", &jb, &i__3, &
+ c_b16, &a_ref(j, j), lda, &a_ref(j, j + jb), lda);
+ if (j + jb <= *m) {
+
+/* Update trailing submatrix. */
+
+ i__3 = *m - j - jb + 1;
+ i__4 = *n - j - jb + 1;
+ dgemm_("No transpose", "No transpose", &i__3, &i__4, &jb,
+ &c_b19, &a_ref(j + jb, j), lda, &a_ref(j, j + jb),
+ lda, &c_b16, &a_ref(j + jb, j + jb), lda);
+ }
+ }
+/* L20: */
+ }
+ }
+ return 0;
+
+/* End of DGETRF */
+
+} /* dgetrf_ */
+
+#undef a_ref
+
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrs.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrs.c
new file mode 100644
index 00000000..c4dd0b38
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dgetrs.c
@@ -0,0 +1,159 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dgetrs_(char *trans, integer *n, integer *nrhs,
+ doublereal *a, integer *lda, integer *ipiv, doublereal *b, integer *
+ ldb, integer *info)
+{
+/* -- LAPACK routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ March 31, 1993
+
+
+ Purpose
+ =======
+
+ DGETRS solves a system of linear equations
+ A * X = B or A' * X = B
+ with a general N-by-N matrix A using the LU factorization computed
+ by DGETRF.
+
+ Arguments
+ =========
+
+ TRANS (input) CHARACTER*1
+ Specifies the form of the system of equations:
+ = 'N': A * X = B (No transpose)
+ = 'T': A'* X = B (Transpose)
+ = 'C': A'* X = B (Conjugate transpose = Transpose)
+
+ N (input) INTEGER
+ The order of the matrix A. N >= 0.
+
+ NRHS (input) INTEGER
+ The number of right hand sides, i.e., the number of columns
+ of the matrix B. NRHS >= 0.
+
+ A (input) DOUBLE PRECISION array, dimension (LDA,N)
+ The factors L and U from the factorization A = P*L*U
+ as computed by DGETRF.
+
+ LDA (input) INTEGER
+ The leading dimension of the array A. LDA >= max(1,N).
+
+ IPIV (input) INTEGER array, dimension (N)
+ The pivot indices from DGETRF; for 1<=i<=N, row i of the
+ matrix was interchanged with row IPIV(i).
+
+ B (input/output) DOUBLE PRECISION array, dimension (LDB,NRHS)
+ On entry, the right hand side matrix B.
+ On exit, the solution matrix X.
+
+ LDB (input) INTEGER
+ The leading dimension of the array B. LDB >= max(1,N).
+
+ INFO (output) INTEGER
+ = 0: successful exit
+ < 0: if INFO = -i, the i-th argument had an illegal value
+
+ =====================================================================
+
+
+ Test the input parameters.
+
+ Parameter adjustments */
+ /* Table of constant values */
+ static integer c__1 = 1;
+ static doublereal c_b12 = 1.;
+ static integer c_n1 = -1;
+
+ /* System generated locals */
+ integer a_dim1, a_offset, b_dim1, b_offset, i__1;
+ /* Local variables */
+ extern logical lsame_(char *, char *);
+ extern /* Subroutine */ int dtrsm_(char *, char *, char *, char *,
+ integer *, integer *, doublereal *, doublereal *, integer *,
+ doublereal *, integer *), xerbla_(
+ char *, integer *), dlaswp_(integer *, doublereal *,
+ integer *, integer *, integer *, integer *, integer *);
+ static logical notran;
+
+
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ --ipiv;
+ b_dim1 = *ldb;
+ b_offset = 1 + b_dim1 * 1;
+ b -= b_offset;
+
+ /* Function Body */
+ *info = 0;
+ notran = lsame_(trans, "N");
+ if (! notran && ! lsame_(trans, "T") && ! lsame_(
+ trans, "C")) {
+ *info = -1;
+ } else if (*n < 0) {
+ *info = -2;
+ } else if (*nrhs < 0) {
+ *info = -3;
+ } else if (*lda < max(1,*n)) {
+ *info = -5;
+ } else if (*ldb < max(1,*n)) {
+ *info = -8;
+ }
+ if (*info != 0) {
+ i__1 = -(*info);
+ xerbla_("DGETRS", &i__1);
+ return 0;
+ }
+
+/* Quick return if possible */
+
+ if (*n == 0 || *nrhs == 0) {
+ return 0;
+ }
+
+ if (notran) {
+
+/* Solve A * X = B.
+
+ Apply row interchanges to the right hand sides. */
+
+ dlaswp_(nrhs, &b[b_offset], ldb, &c__1, n, &ipiv[1], &c__1);
+
+/* Solve L*X = B, overwriting B with X. */
+
+ dtrsm_("Left", "Lower", "No transpose", "Unit", n, nrhs, &c_b12, &a[
+ a_offset], lda, &b[b_offset], ldb);
+
+/* Solve U*X = B, overwriting B with X. */
+
+ dtrsm_("Left", "Upper", "No transpose", "Non-unit", n, nrhs, &c_b12, &
+ a[a_offset], lda, &b[b_offset], ldb);
+ } else {
+
+/* Solve A' * X = B.
+
+ Solve U'*X = B, overwriting B with X. */
+
+ dtrsm_("Left", "Upper", "Transpose", "Non-unit", n, nrhs, &c_b12, &a[
+ a_offset], lda, &b[b_offset], ldb);
+
+/* Solve L'*X = B, overwriting B with X. */
+
+ dtrsm_("Left", "Lower", "Transpose", "Unit", n, nrhs, &c_b12, &a[
+ a_offset], lda, &b[b_offset], ldb);
+
+/* Apply row interchanges to the solution vectors. */
+
+ dlaswp_(nrhs, &b[b_offset], ldb, &c__1, n, &ipiv[1], &c_n1);
+ }
+
+ return 0;
+
+/* End of DGETRS */
+
+} /* dgetrs_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dlaswp.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dlaswp.c
new file mode 100644
index 00000000..4dd6fd7b
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dlaswp.c
@@ -0,0 +1,143 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dlaswp_(integer *n, doublereal *a, integer *lda, integer
+ *k1, integer *k2, integer *ipiv, integer *incx)
+{
+/* -- LAPACK auxiliary routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ June 30, 1999
+
+
+ Purpose
+ =======
+
+ DLASWP performs a series of row interchanges on the matrix A.
+ One row interchange is initiated for each of rows K1 through K2 of A.
+
+ Arguments
+ =========
+
+ N (input) INTEGER
+ The number of columns of the matrix A.
+
+ A (input/output) DOUBLE PRECISION array, dimension (LDA,N)
+ On entry, the matrix of column dimension N to which the row
+ interchanges will be applied.
+ On exit, the permuted matrix.
+
+ LDA (input) INTEGER
+ The leading dimension of the array A.
+
+ K1 (input) INTEGER
+ The first element of IPIV for which a row interchange will
+ be done.
+
+ K2 (input) INTEGER
+ The last element of IPIV for which a row interchange will
+ be done.
+
+ IPIV (input) INTEGER array, dimension (M*abs(INCX))
+ The vector of pivot indices. Only the elements in positions
+ K1 through K2 of IPIV are accessed.
+ IPIV(K) = L implies rows K and L are to be interchanged.
+
+ INCX (input) INTEGER
+ The increment between successive values of IPIV. If IPIV
+ is negative, the pivots are applied in reverse order.
+
+ Further Details
+ ===============
+
+ Modified by
+ R. C. Whaley, Computer Science Dept., Univ. of Tenn., Knoxville, USA
+
+ =====================================================================
+
+
+ Interchange row I with row IPIV(I) for each of rows K1 through K2.
+
+ Parameter adjustments */
+ /* System generated locals */
+ integer a_dim1, a_offset, i__1, i__2, i__3, i__4;
+ /* Local variables */
+ static doublereal temp;
+ static integer i__, j, k, i1, i2, n32, ip, ix, ix0, inc;
+#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1]
+
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ --ipiv;
+
+ /* Function Body */
+ if (*incx > 0) {
+ ix0 = *k1;
+ i1 = *k1;
+ i2 = *k2;
+ inc = 1;
+ } else if (*incx < 0) {
+ ix0 = (1 - *k2) * *incx + 1;
+ i1 = *k2;
+ i2 = *k1;
+ inc = -1;
+ } else {
+ return 0;
+ }
+
+ n32 = *n / 32 << 5;
+ if (n32 != 0) {
+ i__1 = n32;
+ for (j = 1; j <= i__1; j += 32) {
+ ix = ix0;
+ i__2 = i2;
+ i__3 = inc;
+ for (i__ = i1; i__3 < 0 ? i__ >= i__2 : i__ <= i__2; i__ += i__3)
+ {
+ ip = ipiv[ix];
+ if (ip != i__) {
+ i__4 = j + 31;
+ for (k = j; k <= i__4; ++k) {
+ temp = a_ref(i__, k);
+ a_ref(i__, k) = a_ref(ip, k);
+ a_ref(ip, k) = temp;
+/* L10: */
+ }
+ }
+ ix += *incx;
+/* L20: */
+ }
+/* L30: */
+ }
+ }
+ if (n32 != *n) {
+ ++n32;
+ ix = ix0;
+ i__1 = i2;
+ i__3 = inc;
+ for (i__ = i1; i__3 < 0 ? i__ >= i__1 : i__ <= i__1; i__ += i__3) {
+ ip = ipiv[ix];
+ if (ip != i__) {
+ i__2 = *n;
+ for (k = n32; k <= i__2; ++k) {
+ temp = a_ref(i__, k);
+ a_ref(i__, k) = a_ref(ip, k);
+ a_ref(ip, k) = temp;
+/* L40: */
+ }
+ }
+ ix += *incx;
+/* L50: */
+ }
+ }
+
+ return 0;
+
+/* End of DLASWP */
+
+} /* dlaswp_ */
+
+#undef a_ref
+
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dscal.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dscal.c
new file mode 100644
index 00000000..e0e6ffb9
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dscal.c
@@ -0,0 +1,62 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dscal_(integer *n, doublereal *da, doublereal *dx,
+ integer *incx)
+{
+ /* System generated locals */
+ integer i__1, i__2;
+ /* Local variables */
+ static integer i__, m, nincx, mp1;
+/* scales a vector by a constant.
+ uses unrolled loops for increment equal to one.
+ jack dongarra, linpack, 3/11/78.
+ modified 3/93 to return if incx .le. 0.
+ modified 12/3/93, array(1) declarations changed to array(*)
+ Parameter adjustments */
+ --dx;
+ /* Function Body */
+ if (*n <= 0 || *incx <= 0) {
+ return 0;
+ }
+ if (*incx == 1) {
+ goto L20;
+ }
+/* code for increment not equal to 1 */
+ nincx = *n * *incx;
+ i__1 = nincx;
+ i__2 = *incx;
+ for (i__ = 1; i__2 < 0 ? i__ >= i__1 : i__ <= i__1; i__ += i__2) {
+ dx[i__] = *da * dx[i__];
+/* L10: */
+ }
+ return 0;
+/* code for increment equal to 1
+ clean-up loop */
+L20:
+ m = *n % 5;
+ if (m == 0) {
+ goto L40;
+ }
+ i__2 = m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ dx[i__] = *da * dx[i__];
+/* L30: */
+ }
+ if (*n < 5) {
+ return 0;
+ }
+L40:
+ mp1 = m + 1;
+ i__2 = *n;
+ for (i__ = mp1; i__ <= i__2; i__ += 5) {
+ dx[i__] = *da * dx[i__];
+ dx[i__ + 1] = *da * dx[i__ + 1];
+ dx[i__ + 2] = *da * dx[i__ + 2];
+ dx[i__ + 3] = *da * dx[i__ + 3];
+ dx[i__ + 4] = *da * dx[i__ + 4];
+/* L50: */
+ }
+ return 0;
+} /* dscal_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dswap.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dswap.c
new file mode 100644
index 00000000..5aacf29b
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dswap.c
@@ -0,0 +1,81 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dswap_(integer *n, doublereal *dx, integer *incx,
+ doublereal *dy, integer *incy)
+{
+ /* System generated locals */
+ integer i__1;
+ /* Local variables */
+ static integer i__, m;
+ static doublereal dtemp;
+ static integer ix, iy, mp1;
+/* interchanges two vectors.
+ uses unrolled loops for increments equal one.
+ jack dongarra, linpack, 3/11/78.
+ modified 12/3/93, array(1) declarations changed to array(*)
+ Parameter adjustments */
+ --dy;
+ --dx;
+ /* Function Body */
+ if (*n <= 0) {
+ return 0;
+ }
+ if (*incx == 1 && *incy == 1) {
+ goto L20;
+ }
+/* code for unequal increments or equal increments not equal
+ to 1 */
+ ix = 1;
+ iy = 1;
+ if (*incx < 0) {
+ ix = (-(*n) + 1) * *incx + 1;
+ }
+ if (*incy < 0) {
+ iy = (-(*n) + 1) * *incy + 1;
+ }
+ i__1 = *n;
+ for (i__ = 1; i__ <= i__1; ++i__) {
+ dtemp = dx[ix];
+ dx[ix] = dy[iy];
+ dy[iy] = dtemp;
+ ix += *incx;
+ iy += *incy;
+/* L10: */
+ }
+ return 0;
+/* code for both increments equal to 1
+ clean-up loop */
+L20:
+ m = *n % 3;
+ if (m == 0) {
+ goto L40;
+ }
+ i__1 = m;
+ for (i__ = 1; i__ <= i__1; ++i__) {
+ dtemp = dx[i__];
+ dx[i__] = dy[i__];
+ dy[i__] = dtemp;
+/* L30: */
+ }
+ if (*n < 3) {
+ return 0;
+ }
+L40:
+ mp1 = m + 1;
+ i__1 = *n;
+ for (i__ = mp1; i__ <= i__1; i__ += 3) {
+ dtemp = dx[i__];
+ dx[i__] = dy[i__];
+ dy[i__] = dtemp;
+ dtemp = dx[i__ + 1];
+ dx[i__ + 1] = dy[i__ + 1];
+ dy[i__ + 1] = dtemp;
+ dtemp = dx[i__ + 2];
+ dx[i__ + 2] = dy[i__ + 2];
+ dy[i__ + 2] = dtemp;
+/* L50: */
+ }
+ return 0;
+} /* dswap_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/dtrsm.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dtrsm.c
new file mode 100644
index 00000000..d178c1ed
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/dtrsm.c
@@ -0,0 +1,404 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int dtrsm_(char *side, char *uplo, char *transa, char *diag,
+ integer *m, integer *n, doublereal *alpha, doublereal *a, integer *
+ lda, doublereal *b, integer *ldb)
+{
+ /* System generated locals */
+ integer a_dim1, a_offset, b_dim1, b_offset, i__1, i__2, i__3;
+ /* Local variables */
+ static integer info;
+ static doublereal temp;
+ static integer i__, j, k;
+ static logical lside;
+ extern logical lsame_(char *, char *);
+ static integer nrowa;
+ static logical upper;
+ extern /* Subroutine */ int xerbla_(char *, integer *);
+ static logical nounit;
+#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1]
+#define b_ref(a_1,a_2) b[(a_2)*b_dim1 + a_1]
+/* Purpose
+ =======
+ DTRSM solves one of the matrix equations
+ op( A )*X = alpha*B, or X*op( A ) = alpha*B,
+ where alpha is a scalar, X and B are m by n matrices, A is a unit, or
+ non-unit, upper or lower triangular matrix and op( A ) is one of
+ op( A ) = A or op( A ) = A'.
+ The matrix X is overwritten on B.
+ Parameters
+ ==========
+ SIDE - CHARACTER*1.
+ On entry, SIDE specifies whether op( A ) appears on the left
+ or right of X as follows:
+ SIDE = 'L' or 'l' op( A )*X = alpha*B.
+ SIDE = 'R' or 'r' X*op( A ) = alpha*B.
+ Unchanged on exit.
+ UPLO - CHARACTER*1.
+ On entry, UPLO specifies whether the matrix A is an upper or
+ lower triangular matrix as follows:
+ UPLO = 'U' or 'u' A is an upper triangular matrix.
+ UPLO = 'L' or 'l' A is a lower triangular matrix.
+ Unchanged on exit.
+ TRANSA - CHARACTER*1.
+ On entry, TRANSA specifies the form of op( A ) to be used in
+ the matrix multiplication as follows:
+ TRANSA = 'N' or 'n' op( A ) = A.
+ TRANSA = 'T' or 't' op( A ) = A'.
+ TRANSA = 'C' or 'c' op( A ) = A'.
+ Unchanged on exit.
+ DIAG - CHARACTER*1.
+ On entry, DIAG specifies whether or not A is unit triangular
+ as follows:
+ DIAG = 'U' or 'u' A is assumed to be unit triangular.
+ DIAG = 'N' or 'n' A is not assumed to be unit
+ triangular.
+ Unchanged on exit.
+ M - INTEGER.
+ On entry, M specifies the number of rows of B. M must be at
+ least zero.
+ Unchanged on exit.
+ N - INTEGER.
+ On entry, N specifies the number of columns of B. N must be
+ at least zero.
+ Unchanged on exit.
+ ALPHA - DOUBLE PRECISION.
+ On entry, ALPHA specifies the scalar alpha. When alpha is
+ zero then A is not referenced and B need not be set before
+ entry.
+ Unchanged on exit.
+ A - DOUBLE PRECISION array of DIMENSION ( LDA, k ), where k is m
+ when SIDE = 'L' or 'l' and is n when SIDE = 'R' or 'r'.
+ Before entry with UPLO = 'U' or 'u', the leading k by k
+ upper triangular part of the array A must contain the upper
+ triangular matrix and the strictly lower triangular part of
+ A is not referenced.
+ Before entry with UPLO = 'L' or 'l', the leading k by k
+ lower triangular part of the array A must contain the lower
+ triangular matrix and the strictly upper triangular part of
+ A is not referenced.
+ Note that when DIAG = 'U' or 'u', the diagonal elements of
+ A are not referenced either, but are assumed to be unity.
+ Unchanged on exit.
+ LDA - INTEGER.
+ On entry, LDA specifies the first dimension of A as declared
+ in the calling (sub) program. When SIDE = 'L' or 'l' then
+ LDA must be at least max( 1, m ), when SIDE = 'R' or 'r'
+ then LDA must be at least max( 1, n ).
+ Unchanged on exit.
+ B - DOUBLE PRECISION array of DIMENSION ( LDB, n ).
+ Before entry, the leading m by n part of the array B must
+ contain the right-hand side matrix B, and on exit is
+ overwritten by the solution matrix X.
+ LDB - INTEGER.
+ On entry, LDB specifies the first dimension of B as declared
+ in the calling (sub) program. LDB must be at least
+ max( 1, m ).
+ Unchanged on exit.
+ Level 3 Blas routine.
+ -- Written on 8-February-1989.
+ Jack Dongarra, Argonne National Laboratory.
+ Iain Duff, AERE Harwell.
+ Jeremy Du Croz, Numerical Algorithms Group Ltd.
+ Sven Hammarling, Numerical Algorithms Group Ltd.
+ Test the input parameters.
+ Parameter adjustments */
+ a_dim1 = *lda;
+ a_offset = 1 + a_dim1 * 1;
+ a -= a_offset;
+ b_dim1 = *ldb;
+ b_offset = 1 + b_dim1 * 1;
+ b -= b_offset;
+ /* Function Body */
+ lside = lsame_(side, "L");
+ if (lside) {
+ nrowa = *m;
+ } else {
+ nrowa = *n;
+ }
+ nounit = lsame_(diag, "N");
+ upper = lsame_(uplo, "U");
+ info = 0;
+ if (! lside && ! lsame_(side, "R")) {
+ info = 1;
+ } else if (! upper && ! lsame_(uplo, "L")) {
+ info = 2;
+ } else if (! lsame_(transa, "N") && ! lsame_(transa,
+ "T") && ! lsame_(transa, "C")) {
+ info = 3;
+ } else if (! lsame_(diag, "U") && ! lsame_(diag,
+ "N")) {
+ info = 4;
+ } else if (*m < 0) {
+ info = 5;
+ } else if (*n < 0) {
+ info = 6;
+ } else if (*lda < max(1,nrowa)) {
+ info = 9;
+ } else if (*ldb < max(1,*m)) {
+ info = 11;
+ }
+ if (info != 0) {
+ xerbla_("DTRSM ", &info);
+ return 0;
+ }
+/* Quick return if possible. */
+ if (*n == 0) {
+ return 0;
+ }
+/* And when alpha.eq.zero. */
+ if (*alpha == 0.) {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = 0.;
+/* L10: */
+ }
+/* L20: */
+ }
+ return 0;
+ }
+/* Start the operations. */
+ if (lside) {
+ if (lsame_(transa, "N")) {
+/* Form B := alpha*inv( A )*B. */
+ if (upper) {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ if (*alpha != 1.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = *alpha * b_ref(i__, j);
+/* L30: */
+ }
+ }
+ for (k = *m; k >= 1; --k) {
+ if (b_ref(k, j) != 0.) {
+ if (nounit) {
+ b_ref(k, j) = b_ref(k, j) / a_ref(k, k);
+ }
+ i__2 = k - 1;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = b_ref(i__, j) - b_ref(k, j) *
+ a_ref(i__, k);
+/* L40: */
+ }
+ }
+/* L50: */
+ }
+/* L60: */
+ }
+ } else {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ if (*alpha != 1.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = *alpha * b_ref(i__, j);
+/* L70: */
+ }
+ }
+ i__2 = *m;
+ for (k = 1; k <= i__2; ++k) {
+ if (b_ref(k, j) != 0.) {
+ if (nounit) {
+ b_ref(k, j) = b_ref(k, j) / a_ref(k, k);
+ }
+ i__3 = *m;
+ for (i__ = k + 1; i__ <= i__3; ++i__) {
+ b_ref(i__, j) = b_ref(i__, j) - b_ref(k, j) *
+ a_ref(i__, k);
+/* L80: */
+ }
+ }
+/* L90: */
+ }
+/* L100: */
+ }
+ }
+ } else {
+/* Form B := alpha*inv( A' )*B. */
+ if (upper) {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ temp = *alpha * b_ref(i__, j);
+ i__3 = i__ - 1;
+ for (k = 1; k <= i__3; ++k) {
+ temp -= a_ref(k, i__) * b_ref(k, j);
+/* L110: */
+ }
+ if (nounit) {
+ temp /= a_ref(i__, i__);
+ }
+ b_ref(i__, j) = temp;
+/* L120: */
+ }
+/* L130: */
+ }
+ } else {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ for (i__ = *m; i__ >= 1; --i__) {
+ temp = *alpha * b_ref(i__, j);
+ i__2 = *m;
+ for (k = i__ + 1; k <= i__2; ++k) {
+ temp -= a_ref(k, i__) * b_ref(k, j);
+/* L140: */
+ }
+ if (nounit) {
+ temp /= a_ref(i__, i__);
+ }
+ b_ref(i__, j) = temp;
+/* L150: */
+ }
+/* L160: */
+ }
+ }
+ }
+ } else {
+ if (lsame_(transa, "N")) {
+/* Form B := alpha*B*inv( A ). */
+ if (upper) {
+ i__1 = *n;
+ for (j = 1; j <= i__1; ++j) {
+ if (*alpha != 1.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = *alpha * b_ref(i__, j);
+/* L170: */
+ }
+ }
+ i__2 = j - 1;
+ for (k = 1; k <= i__2; ++k) {
+ if (a_ref(k, j) != 0.) {
+ i__3 = *m;
+ for (i__ = 1; i__ <= i__3; ++i__) {
+ b_ref(i__, j) = b_ref(i__, j) - a_ref(k, j) *
+ b_ref(i__, k);
+/* L180: */
+ }
+ }
+/* L190: */
+ }
+ if (nounit) {
+ temp = 1. / a_ref(j, j);
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = temp * b_ref(i__, j);
+/* L200: */
+ }
+ }
+/* L210: */
+ }
+ } else {
+ for (j = *n; j >= 1; --j) {
+ if (*alpha != 1.) {
+ i__1 = *m;
+ for (i__ = 1; i__ <= i__1; ++i__) {
+ b_ref(i__, j) = *alpha * b_ref(i__, j);
+/* L220: */
+ }
+ }
+ i__1 = *n;
+ for (k = j + 1; k <= i__1; ++k) {
+ if (a_ref(k, j) != 0.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = b_ref(i__, j) - a_ref(k, j) *
+ b_ref(i__, k);
+/* L230: */
+ }
+ }
+/* L240: */
+ }
+ if (nounit) {
+ temp = 1. / a_ref(j, j);
+ i__1 = *m;
+ for (i__ = 1; i__ <= i__1; ++i__) {
+ b_ref(i__, j) = temp * b_ref(i__, j);
+/* L250: */
+ }
+ }
+/* L260: */
+ }
+ }
+ } else {
+/* Form B := alpha*B*inv( A' ). */
+ if (upper) {
+ for (k = *n; k >= 1; --k) {
+ if (nounit) {
+ temp = 1. / a_ref(k, k);
+ i__1 = *m;
+ for (i__ = 1; i__ <= i__1; ++i__) {
+ b_ref(i__, k) = temp * b_ref(i__, k);
+/* L270: */
+ }
+ }
+ i__1 = k - 1;
+ for (j = 1; j <= i__1; ++j) {
+ if (a_ref(j, k) != 0.) {
+ temp = a_ref(j, k);
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, j) = b_ref(i__, j) - temp * b_ref(
+ i__, k);
+/* L280: */
+ }
+ }
+/* L290: */
+ }
+ if (*alpha != 1.) {
+ i__1 = *m;
+ for (i__ = 1; i__ <= i__1; ++i__) {
+ b_ref(i__, k) = *alpha * b_ref(i__, k);
+/* L300: */
+ }
+ }
+/* L310: */
+ }
+ } else {
+ i__1 = *n;
+ for (k = 1; k <= i__1; ++k) {
+ if (nounit) {
+ temp = 1. / a_ref(k, k);
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, k) = temp * b_ref(i__, k);
+/* L320: */
+ }
+ }
+ i__2 = *n;
+ for (j = k + 1; j <= i__2; ++j) {
+ if (a_ref(j, k) != 0.) {
+ temp = a_ref(j, k);
+ i__3 = *m;
+ for (i__ = 1; i__ <= i__3; ++i__) {
+ b_ref(i__, j) = b_ref(i__, j) - temp * b_ref(
+ i__, k);
+/* L330: */
+ }
+ }
+/* L340: */
+ }
+ if (*alpha != 1.) {
+ i__2 = *m;
+ for (i__ = 1; i__ <= i__2; ++i__) {
+ b_ref(i__, k) = *alpha * b_ref(i__, k);
+/* L350: */
+ }
+ }
+/* L360: */
+ }
+ }
+ }
+ }
+ return 0;
+/* End of DTRSM . */
+} /* dtrsm_ */
+#undef b_ref
+#undef a_ref
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/endfile.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/endfile.c
new file mode 100644
index 00000000..d309fc26
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/endfile.c
@@ -0,0 +1,102 @@
+#include "f2c.h"
+#include "fio.h"
+
+#undef abs
+#undef min
+#undef max
+#include "stdlib.h"
+#include "string.h"
+
+extern char *f__r_mode[], *f__w_mode[];
+
+integer f_end(alist *a)
+{
+ unit *b;
+ FILE *tf;
+
+ if(a->aunit>=MXUNIT || a->aunit<0) err(a->aerr,101,"endfile");
+ b = &f__units[a->aunit];
+ if(b->ufd==NULL) {
+ char nbuf[10];
+ sprintf(nbuf,"fort.%ld",a->aunit);
+ if (tf = fopen(nbuf, f__w_mode[0]))
+ fclose(tf);
+ return(0);
+ }
+ b->uend=1;
+ return(b->useek ? t_runc(a) : 0);
+}
+
+static int copy(FILE *from, long len, FILE *to)
+{
+ int len1;
+ char buf[BUFSIZ];
+
+ while(fread(buf, len1 = len > BUFSIZ ? BUFSIZ : (int)len, 1, from)) {
+ if (!fwrite(buf, len1, 1, to))
+ return 1;
+ if ((len -= len1) <= 0)
+ break;
+ }
+ return 0;
+ }
+
+int t_runc(alist *a)
+{
+ long loc, len;
+ unit *b;
+ FILE *bf, *tf;
+ int rc = 0;
+
+ b = &f__units[a->aunit];
+ if(b->url)
+ return(0); /*don't truncate direct files*/
+ loc=ftell(bf = b->ufd);
+ fseek(bf,0L,SEEK_END);
+ len=ftell(bf);
+ if (loc >= len || b->useek == 0 || b->ufnm == NULL)
+ return(0);
+ fclose(b->ufd);
+ if (!loc) {
+ if (!(bf = fopen(b->ufnm, f__w_mode[b->ufmt])))
+ rc = 1;
+ if (b->uwrt)
+ b->uwrt = 1;
+ goto done;
+ }
+ if (!(bf = fopen(b->ufnm, f__r_mode[0]))
+ || !(tf = tmpfile())) {
+#ifdef NON_UNIX_STDIO
+ bad:
+#endif
+ rc = 1;
+ goto done;
+ }
+ if (copy(bf, loc, tf)) {
+ bad1:
+ rc = 1;
+ goto done1;
+ }
+ if (!(bf = freopen(b->ufnm, f__w_mode[0], bf)))
+ goto bad1;
+ rewind(tf);
+ if (copy(tf, loc, bf))
+ goto bad1;
+ b->urw = 2;
+#ifdef NON_UNIX_STDIO
+ if (b->ufmt) {
+ fclose(bf);
+ if (!(bf = fopen(b->ufnm, f__w_mode[3])))
+ goto bad;
+ fseek(bf,0L,SEEK_END);
+ b->urw = 3;
+ }
+#endif
+done1:
+ fclose(tf);
+done:
+ f__cf = b->ufd = bf;
+ if (rc)
+ err(a->aerr,111,"endfile");
+ return 0;
+ }
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/err.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/err.c
new file mode 100644
index 00000000..90af7cae
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/err.c
@@ -0,0 +1,240 @@
+#ifndef NON_UNIX_STDIO
+#define _INCLUDE_POSIX_SOURCE /* for HP-UX */
+#define _INCLUDE_XOPEN_SOURCE /* for HP-UX */
+#include "sys/types.h"
+#include "sys/stat.h"
+#endif
+#include "f2c.h"
+#undef abs
+#undef min
+#undef max
+#include "stdlib.h"
+#include "fio.h"
+#include "fmt.h" /* for struct syl */
+
+/*global definitions*/
+unit f__units[MXUNIT]; /*unit table*/
+flag f__init; /*0 on entry, 1 after initializations*/
+cilist *f__elist; /*active external io list*/
+icilist *f__svic; /*active internal io list*/
+flag f__reading; /*1 if reading, 0 if writing*/
+flag f__cplus,f__cblank;
+char *f__fmtbuf;
+flag f__external; /*1 if external io, 0 if internal */
+int (*f__getn)(void); /* for formatted input */
+void (*f__putn)(int); /* for formatted output */
+int (*f__doed)(struct syl*, char*, ftnlen),(*f__doned)(struct syl*);
+int (*f__dorevert)(void),(*f__donewrec)(void),(*f__doend)(void);
+flag f__sequential; /*1 if sequential io, 0 if direct*/
+flag f__formatted; /*1 if formatted io, 0 if unformatted*/
+FILE *f__cf; /*current file*/
+unit *f__curunit; /*current unit*/
+int f__recpos; /*place in current record*/
+int f__cursor, f__hiwater, f__scale;
+char *f__icptr;
+
+/*error messages*/
+char *F_err[] =
+{
+ "error in format", /* 100 */
+ "illegal unit number", /* 101 */
+ "formatted io not allowed", /* 102 */
+ "unformatted io not allowed", /* 103 */
+ "direct io not allowed", /* 104 */
+ "sequential io not allowed", /* 105 */
+ "can't backspace file", /* 106 */
+ "null file name", /* 107 */
+ "can't stat file", /* 108 */
+ "unit not connected", /* 109 */
+ "off end of record", /* 110 */
+ "truncation failed in endfile", /* 111 */
+ "incomprehensible list input", /* 112 */
+ "out of free space", /* 113 */
+ "unit not connected", /* 114 */
+ "read unexpected character", /* 115 */
+ "bad logical input field", /* 116 */
+ "bad variable type", /* 117 */
+ "bad namelist name", /* 118 */
+ "variable not in namelist", /* 119 */
+ "no end record", /* 120 */
+ "variable count incorrect", /* 121 */
+ "subscript for scalar variable", /* 122 */
+ "invalid array section", /* 123 */
+ "substring out of bounds", /* 124 */
+ "subscript out of bounds", /* 125 */
+ "can't read file", /* 126 */
+ "can't write file", /* 127 */
+ "'new' file exists", /* 128 */
+ "can't append to file", /* 129 */
+ "non-positive record number" /* 130 */
+};
+#define MAXERR (sizeof(F_err)/sizeof(char *)+100)
+
+int f__canseek(FILE *f) /*SYSDEP*/
+{
+#ifdef NON_UNIX_STDIO
+ return !isatty(fileno(f));
+#else
+ struct stat x;
+
+ if (fstat(fileno(f),&x) < 0)
+ return(0);
+#ifdef S_IFMT
+ switch(x.st_mode & S_IFMT) {
+ case S_IFDIR:
+ case S_IFREG:
+ if(x.st_nlink > 0) /* !pipe */
+ return(1);
+ else
+ return(0);
+ case S_IFCHR:
+ if(isatty(fileno(f)))
+ return(0);
+ return(1);
+#ifdef S_IFBLK
+ case S_IFBLK:
+ return(1);
+#endif
+ }
+#else
+#ifdef S_ISDIR
+ /* POSIX version */
+ if (S_ISREG(x.st_mode) || S_ISDIR(x.st_mode)) {
+ if(x.st_nlink > 0) /* !pipe */
+ return(1);
+ else
+ return(0);
+ }
+ if (S_ISCHR(x.st_mode)) {
+ if(isatty(fileno(f)))
+ return(0);
+ return(1);
+ }
+ if (S_ISBLK(x.st_mode))
+ return(1);
+#else
+ Help! How does fstat work on this system?
+#endif
+#endif
+ return(0); /* who knows what it is? */
+#endif
+}
+
+void f__fatal(int n, char *s)
+{
+ if(n<100 && n>=0) perror(s); /*SYSDEP*/
+ else if(n >= (int)MAXERR || n < -1)
+ { fprintf(stderr,"%s: illegal error number %d\n",s,n);
+ }
+ else if(n == -1) fprintf(stderr,"%s: end of file\n",s);
+ else
+ fprintf(stderr,"%s: %s\n",s,F_err[n-100]);
+ if (f__curunit) {
+ fprintf(stderr,"apparent state: unit %d ",
+ (int)(f__curunit-f__units));
+ fprintf(stderr, f__curunit->ufnm ? "named %s\n" : "(unnamed)\n",
+ f__curunit->ufnm);
+ }
+ else
+ fprintf(stderr,"apparent state: internal I/O\n");
+ if (f__fmtbuf)
+ fprintf(stderr,"last format: %s\n",f__fmtbuf);
+ fprintf(stderr,"lately %s %s %s %s",f__reading?"reading":"writing",
+ f__sequential?"sequential":"direct",f__formatted?"formatted":"unformatted",
+ f__external?"external":"internal");
+ sig_die(" IO", 1);
+}
+/*initialization routine*/
+ VOID
+f_init(Void)
+{ unit *p;
+
+ f__init=1;
+ p= &f__units[0];
+ p->ufd=stderr;
+ p->useek=f__canseek(stderr);
+ p->ufmt=1;
+ p->uwrt=1;
+ p = &f__units[5];
+ p->ufd=stdin;
+ p->useek=f__canseek(stdin);
+ p->ufmt=1;
+ p->uwrt=0;
+ p= &f__units[6];
+ p->ufd=stdout;
+ p->useek=f__canseek(stdout);
+ p->ufmt=1;
+ p->uwrt=1;
+}
+
+int f__nowreading(unit *x)
+{
+ long loc;
+ int ufmt, urw;
+ extern char *f__r_mode[], *f__w_mode[];
+
+ if (x->urw & 1)
+ goto done;
+ if (!x->ufnm)
+ goto cantread;
+ ufmt = x->url ? 0 : x->ufmt;
+ loc = ftell(x->ufd);
+ urw = 3;
+ if (!freopen(x->ufnm, f__w_mode[ufmt|2], x->ufd)) {
+ urw = 1;
+ if(!freopen(x->ufnm, f__r_mode[ufmt], x->ufd)) {
+ cantread:
+ errno = 126;
+ return 1;
+ }
+ }
+ fseek(x->ufd,loc,SEEK_SET);
+ x->urw = urw;
+ done:
+ x->uwrt = 0;
+ return 0;
+}
+
+int f__nowwriting(unit *x)
+{
+ long loc;
+ int ufmt;
+ extern char *f__w_mode[];
+
+ if (x->urw & 2)
+ goto done;
+ if (!x->ufnm)
+ goto cantwrite;
+ ufmt = x->url ? 0 : x->ufmt;
+ if (x->uwrt == 3) { /* just did write, rewind */
+ if (!(f__cf = x->ufd =
+ freopen(x->ufnm,f__w_mode[ufmt],x->ufd)))
+ goto cantwrite;
+ x->urw = 2;
+ }
+ else {
+ loc=ftell(x->ufd);
+ if (!(f__cf = x->ufd =
+ freopen(x->ufnm, f__w_mode[ufmt |= 2], x->ufd)))
+ {
+ x->ufd = NULL;
+ cantwrite:
+ errno = 127;
+ return(1);
+ }
+ x->urw = 3;
+ fseek(x->ufd,loc,SEEK_SET);
+ }
+ done:
+ x->uwrt = 1;
+ return 0;
+}
+
+int err__fl(int f, int m, char *s)
+{
+ if (!f)
+ f__fatal(m, s);
+ if (f__doend)
+ (*f__doend)();
+ return errno = m;
+ }
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/f2c.h b/src/imageplugins/coreplugin/sharpnesseditor/clapack/f2c.h
new file mode 100644
index 00000000..6514cd91
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/f2c.h
@@ -0,0 +1,223 @@
+/* f2c.h -- Standard Fortran to C header file */
+
+/** barf [ba:rf] 2. "He suggested using FORTRAN, and everybody barfed."
+
+ - From The Shogakukan DICTIONARY OF NEW ENGLISH (Second edition) */
+
+#ifndef F2C_INCLUDE
+#define F2C_INCLUDE
+
+typedef long int integer;
+typedef unsigned long uinteger;
+typedef char *address;
+typedef short int shortint;
+typedef float real;
+typedef double doublereal;
+typedef struct { real r, i; } complex;
+typedef struct { doublereal r, i; } doublecomplex;
+typedef long int logical;
+typedef short int shortlogical;
+typedef char logical1;
+typedef char integer1;
+#if 0 /* Adjust for integer*8. */
+typedef long long longint; /* system-dependent */
+typedef unsigned long long ulongint; /* system-dependent */
+#define qbit_clear(a,b) ((a) & ~((ulongint)1 << (b)))
+#define qbit_set(a,b) ((a) | ((ulongint)1 << (b)))
+#endif
+
+#define TRUE_ (1)
+#define FALSE_ (0)
+
+/* Extern is for use with -E */
+#ifndef Extern
+#define Extern extern
+#endif
+
+/* I/O stuff */
+
+#ifdef f2c_i2
+/* for -i2 */
+typedef short flag;
+typedef short ftnlen;
+typedef short ftnint;
+#else
+typedef long int flag;
+typedef long int ftnlen;
+typedef long int ftnint;
+#endif
+
+/*external read, write*/
+typedef struct
+{ flag cierr;
+ ftnint ciunit;
+ flag ciend;
+ char *cifmt;
+ ftnint cirec;
+} cilist;
+
+/*internal read, write*/
+typedef struct
+{ flag icierr;
+ char *iciunit;
+ flag iciend;
+ char *icifmt;
+ ftnint icirlen;
+ ftnint icirnum;
+} icilist;
+
+/*open*/
+typedef struct
+{ flag oerr;
+ ftnint ounit;
+ char *ofnm;
+ ftnlen ofnmlen;
+ char *osta;
+ char *oacc;
+ char *ofm;
+ ftnint orl;
+ char *oblnk;
+} olist;
+
+/*close*/
+typedef struct
+{ flag cerr;
+ ftnint cunit;
+ char *csta;
+} cllist;
+
+/*rewind, backspace, endfile*/
+typedef struct
+{ flag aerr;
+ ftnint aunit;
+} alist;
+
+/* inquire */
+typedef struct
+{ flag inerr;
+ ftnint inunit;
+ char *infile;
+ ftnlen infilen;
+ ftnint *inex; /*parameters in standard's order*/
+ ftnint *inopen;
+ ftnint *innum;
+ ftnint *innamed;
+ char *inname;
+ ftnlen innamlen;
+ char *inacc;
+ ftnlen inacclen;
+ char *inseq;
+ ftnlen inseqlen;
+ char *indir;
+ ftnlen indirlen;
+ char *infmt;
+ ftnlen infmtlen;
+ char *inform;
+ ftnint informlen;
+ char *inunf;
+ ftnlen inunflen;
+ ftnint *inrecl;
+ ftnint *innrec;
+ char *inblank;
+ ftnlen inblanklen;
+} inlist;
+
+#define VOID void
+
+union Multitype { /* for multiple entry points */
+ integer1 g;
+ shortint h;
+ integer i;
+ /* longint j; */
+ real r;
+ doublereal d;
+ complex c;
+ doublecomplex z;
+ };
+
+typedef union Multitype Multitype;
+
+/*typedef long int Long;*/ /* No longer used; formerly in Namelist */
+
+struct Vardesc { /* for Namelist */
+ char *name;
+ char *addr;
+ ftnlen *dims;
+ int type;
+ };
+typedef struct Vardesc Vardesc;
+
+struct Namelist {
+ char *name;
+ Vardesc **vars;
+ int nvars;
+ };
+typedef struct Namelist Namelist;
+
+#define abs(x) ((x) >= 0 ? (x) : -(x))
+#define dabs(x) (doublereal)abs(x)
+#define min(a,b) ((a) <= (b) ? (a) : (b))
+#define max(a,b) ((a) >= (b) ? (a) : (b))
+#define dmin(a,b) (doublereal)min(a,b)
+#define dmax(a,b) (doublereal)max(a,b)
+#define bit_test(a,b) ((a) >> (b) & 1)
+#define bit_clear(a,b) ((a) & ~((uinteger)1 << (b)))
+#define bit_set(a,b) ((a) | ((uinteger)1 << (b)))
+
+/* procedure parameter types for -A and -C++ */
+
+#define F2C_proc_par_types 1
+#ifdef __cplusplus
+typedef int /* Unknown procedure type */ (*U_fp)(...);
+typedef shortint (*J_fp)(...);
+typedef integer (*I_fp)(...);
+typedef real (*R_fp)(...);
+typedef doublereal (*D_fp)(...), (*E_fp)(...);
+typedef /* Complex */ VOID (*C_fp)(...);
+typedef /* Double Complex */ VOID (*Z_fp)(...);
+typedef logical (*L_fp)(...);
+typedef shortlogical (*K_fp)(...);
+typedef /* Character */ VOID (*H_fp)(...);
+typedef /* Subroutine */ int (*S_fp)(...);
+#else
+typedef int /* Unknown procedure type */ (*U_fp)();
+typedef shortint (*J_fp)();
+typedef integer (*I_fp)();
+typedef real (*R_fp)();
+typedef doublereal (*D_fp)(), (*E_fp)();
+typedef /* Complex */ VOID (*C_fp)();
+typedef /* Double Complex */ VOID (*Z_fp)();
+typedef logical (*L_fp)();
+typedef shortlogical (*K_fp)();
+typedef /* Character */ VOID (*H_fp)();
+typedef /* Subroutine */ int (*S_fp)();
+#endif
+/* E_fp is for real functions when -R is not specified */
+typedef VOID C_f; /* complex function */
+typedef VOID H_f; /* character function */
+typedef VOID Z_f; /* double complex function */
+typedef doublereal E_f; /* real function with -R not specified */
+
+/* undef any lower-case symbols that your C compiler predefines, e.g.: */
+
+#ifndef Skip_f2c_Undefs
+#undef cray
+#undef gcos
+#undef mc68010
+#undef mc68020
+#undef mips
+#undef pdp11
+#undef sgi
+#undef sparc
+#undef sun
+#undef sun2
+#undef sun3
+#undef sun4
+#undef u370
+#undef u3b
+#undef u3b2
+#undef u3b5
+#undef unix
+#undef vax
+#endif
+#endif
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/fio.h b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fio.h
new file mode 100644
index 00000000..b1632d50
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fio.h
@@ -0,0 +1,96 @@
+#include "stdio.h"
+#include "errno.h"
+#ifndef NULL
+/* ANSI C */
+#include "stddef.h"
+#endif
+
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+#endif
+
+#ifdef MSDOS
+#ifndef NON_UNIX_STDIO
+#define NON_UNIX_STDIO
+#endif
+#endif
+
+#ifdef UIOLEN_int
+typedef int uiolen;
+#else
+typedef long uiolen;
+#endif
+
+/*units*/
+typedef struct
+{ FILE *ufd; /*0=unconnected*/
+ char *ufnm;
+#ifndef MSDOS
+ long uinode;
+ int udev;
+#endif
+ int url; /*0=sequential*/
+ flag useek; /*true=can backspace, use dir, ...*/
+ flag ufmt;
+ flag urw; /* (1 for can read) | (2 for can write) */
+ flag ublnk;
+ flag uend;
+ flag uwrt; /*last io was write*/
+ flag uscrtch;
+} unit;
+
+extern flag f__init;
+extern cilist *f__elist; /*active external io list*/
+extern flag f__reading,f__external,f__sequential,f__formatted;
+#undef Void
+#define Void void
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern int (*f__getn)(void); /* for formatted input */
+extern void (*f__putn)(int); /* for formatted output */
+extern void x_putc(int);
+extern long f__inode(char*,int*);
+extern void sig_die(char*,int);
+extern void f__fatal(int,char*);
+extern int t_runc(alist*);
+extern int f__nowreading(unit*), f__nowwriting(unit*);
+extern int fk_open(int,int,ftnint);
+extern int en_fio(void);
+extern void f_init(void);
+extern int (*f__donewrec)(void), t_putc(int), x_wSL(void);
+extern void b_char(char*,char*,ftnlen), g_char(char*,ftnlen,char*);
+extern int c_sfe(cilist*), z_rnew(void);
+extern int isatty(int);
+extern int err__fl(int,int,char*);
+extern int xrd_SL(void);
+extern int f__putbuf(int);
+#ifdef __cplusplus
+ }
+#endif
+extern int (*f__doend)(Void);
+extern FILE *f__cf; /*current file*/
+extern unit *f__curunit; /*current unit*/
+extern unit f__units[];
+#define err(f,m,s) {if(f) errno= m; else f__fatal(m,s); return(m);}
+#define errfl(f,m,s) return err__fl((int)f,m,s)
+
+/*Table sizes*/
+#define MXUNIT 100
+
+extern int f__recpos; /*position in current record*/
+extern int f__cursor; /* offset to move to */
+extern int f__hiwater; /* so TL doesn't confuse us */
+
+#define WRITE 1
+#define READ 2
+#define SEQ 3
+#define DIR 4
+#define FMT 5
+#define UNF 6
+#define EXT 7
+#define INT 8
+
+#define buf_end(x) (x->_flag & _IONBF ? x->_ptr : x->_base + BUFSIZ)
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.c
new file mode 100644
index 00000000..a4fcbc38
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.c
@@ -0,0 +1,470 @@
+#include "f2c.h"
+#include "fio.h"
+#include "fmt.h"
+#define skip(s) while(*s==' ') s++
+#ifdef interdata
+#define SYLMX 300
+#endif
+#ifdef pdp11
+#define SYLMX 300
+#endif
+#ifdef vax
+#define SYLMX 300
+#endif
+#ifndef SYLMX
+#define SYLMX 300
+#endif
+#define GLITCH '\2'
+ /* special quote character for stu */
+extern int f__cursor,f__scale;
+extern flag f__cblank,f__cplus; /*blanks in I and compulsory plus*/
+static struct syl f__syl[SYLMX];
+int f__parenlvl,f__pc,f__revloc;
+
+static char *ap_end(char *s)
+{ char quote;
+ quote= *s++;
+ for(;*s;s++)
+ { if(*s!=quote) continue;
+ if(*++s!=quote) return(s);
+ }
+ if(f__elist->cierr) {
+ errno = 100;
+ return(NULL);
+ }
+ f__fatal(100, "bad string");
+ /*NOTREACHED*/ return 0;
+}
+
+static int op_gen(int a, int b, int c, int d)
+{ struct syl *p= &f__syl[f__pc];
+ if(f__pc>=SYLMX)
+ { fprintf(stderr,"format too complicated:\n");
+ sig_die(f__fmtbuf, 1);
+ }
+ p->op=a;
+ p->p1=b;
+ p->p2.i[0]=c;
+ p->p2.i[1]=d;
+ return(f__pc++);
+}
+
+static char *f_list(char*);
+static char *gt_num(char *s, int *n, int n1)
+{ int m=0,f__cnt=0;
+ char c;
+ for(c= *s;;c = *s)
+ { if(c==' ')
+ { s++;
+ continue;
+ }
+ if(c>'9' || c<'0') break;
+ m=10*m+c-'0';
+ f__cnt++;
+ s++;
+ }
+ if(f__cnt==0) {
+ if (!n1)
+ s = 0;
+ *n=n1;
+ }
+ else *n=m;
+ return(s);
+}
+
+static char *f_s(char *s, int curloc)
+{
+ skip(s);
+ if(*s++!='(')
+ {
+ return(NULL);
+ }
+ if(f__parenlvl++ ==1) f__revloc=curloc;
+ if(op_gen(RET1,curloc,0,0)<0 ||
+ (s=f_list(s))==NULL)
+ {
+ return(NULL);
+ }
+ skip(s);
+ return(s);
+}
+
+static int ne_d(char *s, char **p)
+{ int n,x,sign=0;
+ struct syl *sp;
+ switch(*s)
+ {
+ default:
+ return(0);
+ case ':': (void) op_gen(COLON,0,0,0); break;
+ case '$':
+ (void) op_gen(NONL, 0, 0, 0); break;
+ case 'B':
+ case 'b':
+ if(*++s=='z' || *s == 'Z') (void) op_gen(BZ,0,0,0);
+ else (void) op_gen(BN,0,0,0);
+ break;
+ case 'S':
+ case 's':
+ if(*(s+1)=='s' || *(s+1) == 'S')
+ { x=SS;
+ s++;
+ }
+ else if(*(s+1)=='p' || *(s+1) == 'P')
+ { x=SP;
+ s++;
+ }
+ else x=S;
+ (void) op_gen(x,0,0,0);
+ break;
+ case '/': (void) op_gen(SLASH,0,0,0); break;
+ case '-': sign=1;
+ case '+': s++; /*OUTRAGEOUS CODING TRICK*/
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (!(s=gt_num(s,&n,0))) {
+ bad: *p = 0;
+ return 1;
+ }
+ switch(*s)
+ {
+ default:
+ return(0);
+ case 'P':
+ case 'p': if(sign) n= -n; (void) op_gen(P,n,0,0); break;
+ case 'X':
+ case 'x': (void) op_gen(X,n,0,0); break;
+ case 'H':
+ case 'h':
+ sp = &f__syl[op_gen(H,n,0,0)];
+ sp->p2.s = s + 1;
+ s+=n;
+ break;
+ }
+ break;
+ case GLITCH:
+ case '"':
+ case '\'':
+ sp = &f__syl[op_gen(APOS,0,0,0)];
+ sp->p2.s = s;
+ if((*p = ap_end(s)) == NULL)
+ return(0);
+ return(1);
+ case 'T':
+ case 't':
+ if(*(s+1)=='l' || *(s+1) == 'L')
+ { x=TL;
+ s++;
+ }
+ else if(*(s+1)=='r'|| *(s+1) == 'R')
+ { x=TR;
+ s++;
+ }
+ else x=T;
+ if (!(s=gt_num(s+1,&n,0)))
+ goto bad;
+ s--;
+ (void) op_gen(x,n,0,0);
+ break;
+ case 'X':
+ case 'x': (void) op_gen(X,1,0,0); break;
+ case 'P':
+ case 'p': (void) op_gen(P,1,0,0); break;
+ }
+ s++;
+ *p=s;
+ return(1);
+}
+
+static int e_d(char *s, char **p)
+{ int i,im,n,w,d,e,found=0,x=0;
+ char *sv=s;
+ s=gt_num(s,&n,1);
+ (void) op_gen(STACK,n,0,0);
+ switch(*s++)
+ {
+ default: break;
+ case 'E':
+ case 'e': x=1;
+ case 'G':
+ case 'g':
+ found=1;
+ if (!(s=gt_num(s,&w,0))) {
+ bad:
+ *p = 0;
+ return 1;
+ }
+ if(w==0) break;
+ if(*s=='.') {
+ if (!(s=gt_num(s+1,&d,0)))
+ goto bad;
+ }
+ else d=0;
+ if(*s!='E' && *s != 'e')
+ (void) op_gen(x==1?E:G,w,d,0); /* default is Ew.dE2 */
+ else {
+ if (!(s=gt_num(s+1,&e,0)))
+ goto bad;
+ (void) op_gen(x==1?EE:GE,w,d,e);
+ }
+ break;
+ case 'O':
+ case 'o':
+ i = O;
+ im = OM;
+ goto finish_I;
+ case 'Z':
+ case 'z':
+ i = Z;
+ im = ZM;
+ goto finish_I;
+ case 'L':
+ case 'l':
+ found=1;
+ if (!(s=gt_num(s,&w,0)))
+ goto bad;
+ if(w==0) break;
+ (void) op_gen(L,w,0,0);
+ break;
+ case 'A':
+ case 'a':
+ found=1;
+ skip(s);
+ if(*s>='0' && *s<='9')
+ { s=gt_num(s,&w,1);
+ if(w==0) break;
+ (void) op_gen(AW,w,0,0);
+ break;
+ }
+ (void) op_gen(A,0,0,0);
+ break;
+ case 'F':
+ case 'f':
+ if (!(s=gt_num(s,&w,0)))
+ goto bad;
+ found=1;
+ if(w==0) break;
+ if(*s=='.') {
+ if (!(s=gt_num(s+1,&d,0)))
+ goto bad;
+ }
+ else d=0;
+ (void) op_gen(F,w,d,0);
+ break;
+ case 'D':
+ case 'd':
+ found=1;
+ if (!(s=gt_num(s,&w,0)))
+ goto bad;
+ if(w==0) break;
+ if(*s=='.') {
+ if (!(s=gt_num(s+1,&d,0)))
+ goto bad;
+ }
+ else d=0;
+ (void) op_gen(D,w,d,0);
+ break;
+ case 'I':
+ case 'i':
+ i = I;
+ im = IM;
+ finish_I:
+ if (!(s=gt_num(s,&w,0)))
+ goto bad;
+ found=1;
+ if(w==0) break;
+ if(*s!='.')
+ { (void) op_gen(i,w,0,0);
+ break;
+ }
+ if (!(s=gt_num(s+1,&d,0)))
+ goto bad;
+ (void) op_gen(im,w,d,0);
+ break;
+ }
+ if(found==0)
+ { f__pc--; /*unSTACK*/
+ *p=sv;
+ return(0);
+ }
+ *p=s;
+ return(1);
+}
+
+static char *i_tem(char *s)
+{ char *t;
+ int n,curloc;
+ if(*s==')') return(s);
+ if(ne_d(s,&t)) return(t);
+ if(e_d(s,&t)) return(t);
+ s=gt_num(s,&n,1);
+ if((curloc=op_gen(STACK,n,0,0))<0) return(NULL);
+ return(f_s(s,curloc));
+}
+
+static char *f_list(char *s)
+{
+ for(;*s!=0;)
+ { skip(s);
+ if((s=i_tem(s))==NULL) return(NULL);
+ skip(s);
+ if(*s==',') s++;
+ else if(*s==')')
+ { if(--f__parenlvl==0)
+ {
+ (void) op_gen(REVERT,f__revloc,0,0);
+ return(++s);
+ }
+ (void) op_gen(GOTO,0,0,0);
+ return(++s);
+ }
+ }
+ return(NULL);
+}
+
+int pars_f(char *s)
+{
+ f__parenlvl=f__revloc=f__pc=0;
+ if(f_s(s,0) == NULL)
+ {
+ return(-1);
+ }
+ return(0);
+}
+
+#define STKSZ 10
+int f__cnt[STKSZ],f__ret[STKSZ],f__cp,f__rp;
+flag f__workdone, f__nonl;
+
+static int type_f(int n)
+{
+ switch(n)
+ {
+ default:
+ return(n);
+ case RET1:
+ return(RET1);
+ case REVERT: return(REVERT);
+ case GOTO: return(GOTO);
+ case STACK: return(STACK);
+ case X:
+ case SLASH:
+ case APOS: case H:
+ case T: case TL: case TR:
+ return(NED);
+ case F:
+ case I:
+ case IM:
+ case A: case AW:
+ case O: case OM:
+ case L:
+ case E: case EE: case D:
+ case G: case GE:
+ case Z: case ZM:
+ return(ED);
+ }
+}
+
+integer do_fio(ftnint *number, char *ptr, ftnlen len)
+{ struct syl *p;
+ int n,i;
+ for(i=0;i<*number;i++,ptr+=len)
+ {
+loop: switch(type_f((p= &f__syl[f__pc])->op))
+ {
+ default:
+ fprintf(stderr,"unknown code in do_fio: %d\n%s\n",
+ p->op,f__fmtbuf);
+ err(f__elist->cierr,100,"do_fio");
+ case NED:
+ if((*f__doned)(p))
+ { f__pc++;
+ goto loop;
+ }
+ f__pc++;
+ continue;
+ case ED:
+ if(f__cnt[f__cp]<=0)
+ { f__cp--;
+ f__pc++;
+ goto loop;
+ }
+ if(ptr==NULL)
+ return((*f__doend)());
+ f__cnt[f__cp]--;
+ f__workdone=1;
+ if((n=(*f__doed)(p,ptr,len))>0)
+ errfl(f__elist->cierr,errno,"fmt");
+ if(n<0)
+ err(f__elist->ciend,(EOF),"fmt");
+ continue;
+ case STACK:
+ f__cnt[++f__cp]=p->p1;
+ f__pc++;
+ goto loop;
+ case RET1:
+ f__ret[++f__rp]=p->p1;
+ f__pc++;
+ goto loop;
+ case GOTO:
+ if(--f__cnt[f__cp]<=0)
+ { f__cp--;
+ f__rp--;
+ f__pc++;
+ goto loop;
+ }
+ f__pc=1+f__ret[f__rp--];
+ goto loop;
+ case REVERT:
+ f__rp=f__cp=0;
+ f__pc = p->p1;
+ if(ptr==NULL)
+ return((*f__doend)());
+ if(!f__workdone) return(0);
+ if((n=(*f__dorevert)()) != 0) return(n);
+ goto loop;
+ case COLON:
+ if(ptr==NULL)
+ return((*f__doend)());
+ f__pc++;
+ goto loop;
+ case NONL:
+ f__nonl = 1;
+ f__pc++;
+ goto loop;
+ case S:
+ case SS:
+ f__cplus=0;
+ f__pc++;
+ goto loop;
+ case SP:
+ f__cplus = 1;
+ f__pc++;
+ goto loop;
+ case P: f__scale=p->p1;
+ f__pc++;
+ goto loop;
+ case BN:
+ f__cblank=0;
+ f__pc++;
+ goto loop;
+ case BZ:
+ f__cblank=1;
+ f__pc++;
+ goto loop;
+ }
+ }
+ return(0);
+}
+
+int en_fio(Void)
+{ ftnint one=1;
+ return(do_fio(&one,(char *)NULL,(ftnint)0));
+}
+
+ VOID
+fmt_bg(Void)
+{
+ f__workdone=f__cp=f__rp=f__pc=f__cursor=0;
+ f__cnt[0]=f__ret[0]=0;
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.h b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.h
new file mode 100644
index 00000000..b46bc9c5
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmt.h
@@ -0,0 +1,86 @@
+struct syl
+{ int op;
+ int p1;
+ union { int i[2]; char *s;} p2;
+ };
+#define RET1 1
+#define REVERT 2
+#define GOTO 3
+#define X 4
+#define SLASH 5
+#define STACK 6
+#define I 7
+#define ED 8
+#define NED 9
+#define IM 10
+#define APOS 11
+#define H 12
+#define TL 13
+#define TR 14
+#define T 15
+#define COLON 16
+#define S 17
+#define SP 18
+#define SS 19
+#define P 20
+#define BN 21
+#define BZ 22
+#define F 23
+#define E 24
+#define EE 25
+#define D 26
+#define G 27
+#define GE 28
+#define L 29
+#define A 30
+#define AW 31
+#define O 32
+#define NONL 33
+#define OM 34
+#define Z 35
+#define ZM 36
+extern int f__pc,f__parenlvl,f__revloc;
+typedef union
+{ real pf;
+ doublereal pd;
+} ufloat;
+typedef union
+{ short is;
+ signed char ic;
+ integer il;
+#ifdef Allow_TYQUAD
+ longint ili;
+#endif
+} Uint;
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern int (*f__doed)(struct syl*, char*, ftnlen),(*f__doned)(struct syl*);
+extern int (*f__dorevert)(void);
+extern void fmt_bg(void);
+extern int pars_f(char*);
+extern int rd_ed(struct syl*, char*, ftnlen),rd_ned(struct syl*);
+extern int w_ed(struct syl*, char*, ftnlen),w_ned(struct syl*);
+extern int wrt_E(ufloat*, int, int, int, ftnlen);
+extern int wrt_F(ufloat*, int, int, ftnlen);
+extern int wrt_L(Uint*, int, ftnlen);
+#ifdef __cplusplus
+ }
+#endif
+extern flag f__cblank,f__cplus,f__workdone, f__nonl;
+extern char *f__fmtbuf;
+extern int f__scale;
+#define GET(x) if((x=(*f__getn)())<0) return(x)
+#define VAL(x) (x!='\n'?x:' ')
+#define PUT(x) (*f__putn)(x)
+extern int f__cursor;
+
+#undef TYQUAD
+#ifndef Allow_TYQUAD
+#undef longint
+#define longint long
+#else
+#define TYQUAD 14
+#endif
+
+extern char *f__icvt(longint, int*, int*, int);
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmtlib.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmtlib.c
new file mode 100644
index 00000000..8343337b
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fmtlib.c
@@ -0,0 +1,40 @@
+/* @(#)fmtlib.c 1.2 */
+#define MAXINTLENGTH 23
+
+#include "f2c.h"
+#ifndef Allow_TYQUAD
+#undef longint
+#define longint long
+#undef ulongint
+#define ulongint unsigned long
+#endif
+
+char *f__icvt(longint value, int *ndigit, int *sign, int base)
+{
+ static char buf[MAXINTLENGTH+1];
+ int i;
+ ulongint uvalue;
+
+ if(value > 0) {
+ uvalue = value;
+ *sign = 0;
+ }
+ else if (value < 0) {
+ uvalue = -value;
+ *sign = 1;
+ }
+ else {
+ *sign = 0;
+ *ndigit = 1;
+ buf[MAXINTLENGTH-1] = '0';
+ return &buf[MAXINTLENGTH-1];
+ }
+ i = MAXINTLENGTH;
+ do {
+ buf[--i] = (uvalue%base) + '0';
+ uvalue /= base;
+ }
+ while(uvalue > 0);
+ *ndigit = MAXINTLENGTH - i;
+ return &buf[i];
+ }
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/fp.h b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fp.h
new file mode 100644
index 00000000..40743d79
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/fp.h
@@ -0,0 +1,28 @@
+#define FMAX 40
+#define EXPMAXDIGS 8
+#define EXPMAX 99999999
+/* FMAX = max number of nonzero digits passed to atof() */
+/* EXPMAX = 10^EXPMAXDIGS - 1 = largest allowed exponent absolute value */
+
+#ifdef V10 /* Research Tenth-Edition Unix */
+#include "local.h"
+#endif
+
+/* MAXFRACDIGS and MAXINTDIGS are for wrt_F -- bounds (not necessarily
+ tight) on the maximum number of digits to the right and left of
+ * the decimal point.
+ */
+
+#ifdef VAX
+#define MAXFRACDIGS 56
+#define MAXINTDIGS 38
+#else
+#ifdef CRAY
+#define MAXFRACDIGS 9880
+#define MAXINTDIGS 9864
+#else
+/* values that suffice for IEEE double */
+#define MAXFRACDIGS 344
+#define MAXINTDIGS 308
+#endif
+#endif
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/idamax.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/idamax.c
new file mode 100644
index 00000000..2cc54813
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/idamax.c
@@ -0,0 +1,61 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+integer idamax_(integer *n, doublereal *dx, integer *incx)
+{
+ /* System generated locals */
+ integer ret_val, i__1;
+ doublereal d__1;
+ /* Local variables */
+ static doublereal dmax__;
+ static integer i__, ix;
+/* finds the index of element having max. absolute value.
+ jack dongarra, linpack, 3/11/78.
+ modified 3/93 to return if incx .le. 0.
+ modified 12/3/93, array(1) declarations changed to array(*)
+ Parameter adjustments */
+ --dx;
+ /* Function Body */
+ ret_val = 0;
+ if (*n < 1 || *incx <= 0) {
+ return ret_val;
+ }
+ ret_val = 1;
+ if (*n == 1) {
+ return ret_val;
+ }
+ if (*incx == 1) {
+ goto L20;
+ }
+/* code for increment not equal to 1 */
+ ix = 1;
+ dmax__ = abs(dx[1]);
+ ix += *incx;
+ i__1 = *n;
+ for (i__ = 2; i__ <= i__1; ++i__) {
+ if ((d__1 = dx[ix], abs(d__1)) <= dmax__) {
+ goto L5;
+ }
+ ret_val = i__;
+ dmax__ = (d__1 = dx[ix], abs(d__1));
+L5:
+ ix += *incx;
+/* L10: */
+ }
+ return ret_val;
+/* code for increment equal to 1 */
+L20:
+ dmax__ = abs(dx[1]);
+ i__1 = *n;
+ for (i__ = 2; i__ <= i__1; ++i__) {
+ if ((d__1 = dx[i__], abs(d__1)) <= dmax__) {
+ goto L30;
+ }
+ ret_val = i__;
+ dmax__ = (d__1 = dx[i__], abs(d__1));
+L30:
+ ;
+ }
+ return ret_val;
+} /* idamax_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/ieeeck.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/ieeeck.c
new file mode 100644
index 00000000..39256a48
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/ieeeck.c
@@ -0,0 +1,150 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+integer ieeeck_(integer *ispec, real *zero, real *one)
+{
+/* -- LAPACK auxiliary routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ June 30, 1998
+
+
+ Purpose
+ =======
+
+ IEEECK is called from the ILAENV to verify that Infinity and
+ possibly NaN arithmetic is safe (i.e. will not trap).
+
+ Arguments
+ =========
+
+ ISPEC (input) INTEGER
+ Specifies whether to test just for inifinity arithmetic
+ or whether to test for infinity and NaN arithmetic.
+ = 0: Verify infinity arithmetic only.
+ = 1: Verify infinity and NaN arithmetic.
+
+ ZERO (input) REAL
+ Must contain the value 0.0
+ This is passed to prevent the compiler from optimizing
+ away this code.
+
+ ONE (input) REAL
+ Must contain the value 1.0
+ This is passed to prevent the compiler from optimizing
+ away this code.
+
+ RETURN VALUE: INTEGER
+ = 0: Arithmetic failed to produce the correct answers
+ = 1: Arithmetic produced the correct answers */
+ /* System generated locals */
+ integer ret_val;
+ /* Local variables */
+ static real neginf, posinf, negzro, newzro, nan1, nan2, nan3, nan4, nan5,
+ nan6;
+
+
+ ret_val = 1;
+
+ posinf = *one / *zero;
+ if (posinf <= *one) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ neginf = -(*one) / *zero;
+ if (neginf >= *zero) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ negzro = *one / (neginf + *one);
+ if (negzro != *zero) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ neginf = *one / negzro;
+ if (neginf >= *zero) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ newzro = negzro + *zero;
+ if (newzro != *zero) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ posinf = *one / newzro;
+ if (posinf <= *one) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ neginf *= posinf;
+ if (neginf >= *zero) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ posinf *= posinf;
+ if (posinf <= *one) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+
+
+
+/* Return if we were only asked to check infinity arithmetic */
+
+ if (*ispec == 0) {
+ return ret_val;
+ }
+
+ nan1 = posinf + neginf;
+
+ nan2 = posinf / neginf;
+
+ nan3 = posinf / posinf;
+
+ nan4 = posinf * *zero;
+
+ nan5 = neginf * negzro;
+
+ nan6 = nan5 * 0.f;
+
+ if (nan1 == nan1) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ if (nan2 == nan2) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ if (nan3 == nan3) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ if (nan4 == nan4) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ if (nan5 == nan5) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ if (nan6 == nan6) {
+ ret_val = 0;
+ return ret_val;
+ }
+
+ return ret_val;
+} /* ieeeck_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/ilaenv.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/ilaenv.c
new file mode 100644
index 00000000..58299fff
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/ilaenv.c
@@ -0,0 +1,610 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+integer ilaenv_(integer *ispec, char *name__, char *opts, integer *n1,
+ integer *n2, integer *n3, integer *n4, ftnlen name_len, ftnlen
+ opts_len)
+{
+/* -- LAPACK auxiliary routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ June 30, 1999
+
+
+ Purpose
+ =======
+
+ ILAENV is called from the LAPACK routines to choose problem-dependent
+ parameters for the local environment. See ISPEC for a description of
+ the parameters.
+
+ This version provides a set of parameters which should give good,
+ but not optimal, performance on many of the currently available
+ computers. Users are encouraged to modify this subroutine to set
+ the tuning parameters for their particular machine using the option
+ and problem size information in the arguments.
+
+ This routine will not function correctly if it is converted to all
+ lower case. Converting it to all upper case is allowed.
+
+ Arguments
+ =========
+
+ ISPEC (input) INTEGER
+ Specifies the parameter to be returned as the value of
+ ILAENV.
+ = 1: the optimal blocksize; if this value is 1, an unblocked
+ algorithm will give the best performance.
+ = 2: the minimum block size for which the block routine
+ should be used; if the usable block size is less than
+ this value, an unblocked routine should be used.
+ = 3: the crossover point (in a block routine, for N less
+ than this value, an unblocked routine should be used)
+ = 4: the number of shifts, used in the nonsymmetric
+ eigenvalue routines
+ = 5: the minimum column dimension for blocking to be used;
+ rectangular blocks must have dimension at least k by m,
+ where k is given by ILAENV(2,...) and m by ILAENV(5,...)
+ = 6: the crossover point for the SVD (when reducing an m by n
+ matrix to bidiagonal form, if max(m,n)/min(m,n) exceeds
+ this value, a QR factorization is used first to reduce
+ the matrix to a triangular form.)
+ = 7: the number of processors
+ = 8: the crossover point for the multishift QR and QZ methods
+ for nonsymmetric eigenvalue problems.
+ = 9: maximum size of the subproblems at the bottom of the
+ computation tree in the divide-and-conquer algorithm
+ (used by xGELSD and xGESDD)
+ =10: ieee NaN arithmetic can be trusted not to trap
+ =11: infinity arithmetic can be trusted not to trap
+
+ NAME (input) CHARACTER*(*)
+ The name of the calling subroutine, in either upper case or
+ lower case.
+
+ OPTS (input) CHARACTER*(*)
+ The character options to the subroutine NAME, concatenated
+ into a single character string. For example, UPLO = 'U',
+ TRANS = 'T', and DIAG = 'N' for a triangular routine would
+ be specified as OPTS = 'UTN'.
+
+ N1 (input) INTEGER
+ N2 (input) INTEGER
+ N3 (input) INTEGER
+ N4 (input) INTEGER
+ Problem dimensions for the subroutine NAME; these may not all
+ be required.
+
+ (ILAENV) (output) INTEGER
+ >= 0: the value of the parameter specified by ISPEC
+ < 0: if ILAENV = -k, the k-th argument had an illegal value.
+
+ Further Details
+ ===============
+
+ The following conventions have been used when calling ILAENV from the
+ LAPACK routines:
+ 1) OPTS is a concatenation of all of the character options to
+ subroutine NAME, in the same order that they appear in the
+ argument list for NAME, even if they are not used in determining
+ the value of the parameter specified by ISPEC.
+ 2) The problem dimensions N1, N2, N3, N4 are specified in the order
+ that they appear in the argument list for NAME. N1 is used
+ first, N2 second, and so on, and unused problem dimensions are
+ passed a value of -1.
+ 3) The parameter value returned by ILAENV is checked for validity in
+ the calling subroutine. For example, ILAENV is used to retrieve
+ the optimal blocksize for STRTRI as follows:
+
+ NB = ILAENV( 1, 'STRTRI', UPLO // DIAG, N, -1, -1, -1 )
+ IF( NB.LE.1 ) NB = MAX( 1, N )
+
+ ===================================================================== */
+ /* Table of constant values */
+ static integer c__0 = 0;
+ static real c_b162 = 0.f;
+ static real c_b163 = 1.f;
+ static integer c__1 = 1;
+
+ /* System generated locals */
+ integer ret_val;
+ /* Builtin functions
+ Subroutine */ int s_copy(char *, char *, ftnlen, ftnlen);
+ integer s_cmp(char *, char *, ftnlen, ftnlen);
+ /* Local variables */
+ static integer i__;
+ static logical cname, sname;
+ static integer nbmin;
+ static char c1[1], c2[2], c3[3], c4[2];
+ static integer ic, nb;
+ extern integer ieeeck_(integer *, real *, real *);
+ static integer iz, nx;
+ static char subnam[6];
+
+
+
+
+ switch (*ispec) {
+ case 1: goto L100;
+ case 2: goto L100;
+ case 3: goto L100;
+ case 4: goto L400;
+ case 5: goto L500;
+ case 6: goto L600;
+ case 7: goto L700;
+ case 8: goto L800;
+ case 9: goto L900;
+ case 10: goto L1000;
+ case 11: goto L1100;
+ }
+
+/* Invalid value for ISPEC */
+
+ ret_val = -1;
+ return ret_val;
+
+L100:
+
+/* Convert NAME to upper case if the first character is lower case. */
+
+ ret_val = 1;
+ s_copy(subnam, name__, (ftnlen)6, name_len);
+ ic = *(unsigned char *)subnam;
+ iz = 'Z';
+ if (iz == 90 || iz == 122) {
+
+/* ASCII character set */
+
+ if (ic >= 97 && ic <= 122) {
+ *(unsigned char *)subnam = (char) (ic - 32);
+ for (i__ = 2; i__ <= 6; ++i__) {
+ ic = *(unsigned char *)&subnam[i__ - 1];
+ if (ic >= 97 && ic <= 122) {
+ *(unsigned char *)&subnam[i__ - 1] = (char) (ic - 32);
+ }
+/* L10: */
+ }
+ }
+
+ } else if (iz == 233 || iz == 169) {
+
+/* EBCDIC character set */
+
+ if (ic >= 129 && ic <= 137 || ic >= 145 && ic <= 153 || ic >= 162 &&
+ ic <= 169) {
+ *(unsigned char *)subnam = (char) (ic + 64);
+ for (i__ = 2; i__ <= 6; ++i__) {
+ ic = *(unsigned char *)&subnam[i__ - 1];
+ if (ic >= 129 && ic <= 137 || ic >= 145 && ic <= 153 || ic >=
+ 162 && ic <= 169) {
+ *(unsigned char *)&subnam[i__ - 1] = (char) (ic + 64);
+ }
+/* L20: */
+ }
+ }
+
+ } else if (iz == 218 || iz == 250) {
+
+/* Prime machines: ASCII+128 */
+
+ if (ic >= 225 && ic <= 250) {
+ *(unsigned char *)subnam = (char) (ic - 32);
+ for (i__ = 2; i__ <= 6; ++i__) {
+ ic = *(unsigned char *)&subnam[i__ - 1];
+ if (ic >= 225 && ic <= 250) {
+ *(unsigned char *)&subnam[i__ - 1] = (char) (ic - 32);
+ }
+/* L30: */
+ }
+ }
+ }
+
+ *(unsigned char *)c1 = *(unsigned char *)subnam;
+ sname = *(unsigned char *)c1 == 'S' || *(unsigned char *)c1 == 'D';
+ cname = *(unsigned char *)c1 == 'C' || *(unsigned char *)c1 == 'Z';
+ if (! (cname || sname)) {
+ return ret_val;
+ }
+ s_copy(c2, subnam + 1, (ftnlen)2, (ftnlen)2);
+ s_copy(c3, subnam + 3, (ftnlen)3, (ftnlen)3);
+ s_copy(c4, c3 + 1, (ftnlen)2, (ftnlen)2);
+
+ switch (*ispec) {
+ case 1: goto L110;
+ case 2: goto L200;
+ case 3: goto L300;
+ }
+
+L110:
+
+/* ISPEC = 1: block size
+
+ In these examples, separate code is provided for setting NB for
+ real and complex. We assume that NB will take the same value in
+ single or double precision. */
+
+ nb = 1;
+
+ if (s_cmp(c2, "GE", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRF", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 64;
+ } else {
+ nb = 64;
+ }
+ } else if (s_cmp(c3, "QRF", (ftnlen)3, (ftnlen)3) == 0 || s_cmp(c3,
+ "RQF", (ftnlen)3, (ftnlen)3) == 0 || s_cmp(c3, "LQF", (ftnlen)
+ 3, (ftnlen)3) == 0 || s_cmp(c3, "QLF", (ftnlen)3, (ftnlen)3)
+ == 0) {
+ if (sname) {
+ nb = 32;
+ } else {
+ nb = 32;
+ }
+ } else if (s_cmp(c3, "HRD", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 32;
+ } else {
+ nb = 32;
+ }
+ } else if (s_cmp(c3, "BRD", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 32;
+ } else {
+ nb = 32;
+ }
+ } else if (s_cmp(c3, "TRI", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 64;
+ } else {
+ nb = 64;
+ }
+ }
+ } else if (s_cmp(c2, "PO", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRF", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 64;
+ } else {
+ nb = 64;
+ }
+ }
+ } else if (s_cmp(c2, "SY", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRF", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 64;
+ } else {
+ nb = 64;
+ }
+ } else if (sname && s_cmp(c3, "TRD", (ftnlen)3, (ftnlen)3) == 0) {
+ nb = 32;
+ } else if (sname && s_cmp(c3, "GST", (ftnlen)3, (ftnlen)3) == 0) {
+ nb = 64;
+ }
+ } else if (cname && s_cmp(c2, "HE", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRF", (ftnlen)3, (ftnlen)3) == 0) {
+ nb = 64;
+ } else if (s_cmp(c3, "TRD", (ftnlen)3, (ftnlen)3) == 0) {
+ nb = 32;
+ } else if (s_cmp(c3, "GST", (ftnlen)3, (ftnlen)3) == 0) {
+ nb = 64;
+ }
+ } else if (sname && s_cmp(c2, "OR", (ftnlen)2, (ftnlen)2) == 0) {
+ if (*(unsigned char *)c3 == 'G') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nb = 32;
+ }
+ } else if (*(unsigned char *)c3 == 'M') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nb = 32;
+ }
+ }
+ } else if (cname && s_cmp(c2, "UN", (ftnlen)2, (ftnlen)2) == 0) {
+ if (*(unsigned char *)c3 == 'G') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nb = 32;
+ }
+ } else if (*(unsigned char *)c3 == 'M') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nb = 32;
+ }
+ }
+ } else if (s_cmp(c2, "GB", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRF", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ if (*n4 <= 64) {
+ nb = 1;
+ } else {
+ nb = 32;
+ }
+ } else {
+ if (*n4 <= 64) {
+ nb = 1;
+ } else {
+ nb = 32;
+ }
+ }
+ }
+ } else if (s_cmp(c2, "PB", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRF", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ if (*n2 <= 64) {
+ nb = 1;
+ } else {
+ nb = 32;
+ }
+ } else {
+ if (*n2 <= 64) {
+ nb = 1;
+ } else {
+ nb = 32;
+ }
+ }
+ }
+ } else if (s_cmp(c2, "TR", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRI", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 64;
+ } else {
+ nb = 64;
+ }
+ }
+ } else if (s_cmp(c2, "LA", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "UUM", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nb = 64;
+ } else {
+ nb = 64;
+ }
+ }
+ } else if (sname && s_cmp(c2, "ST", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "EBZ", (ftnlen)3, (ftnlen)3) == 0) {
+ nb = 1;
+ }
+ }
+ ret_val = nb;
+ return ret_val;
+
+L200:
+
+/* ISPEC = 2: minimum block size */
+
+ nbmin = 2;
+ if (s_cmp(c2, "GE", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "QRF", (ftnlen)3, (ftnlen)3) == 0 || s_cmp(c3, "RQF", (
+ ftnlen)3, (ftnlen)3) == 0 || s_cmp(c3, "LQF", (ftnlen)3, (
+ ftnlen)3) == 0 || s_cmp(c3, "QLF", (ftnlen)3, (ftnlen)3) == 0)
+ {
+ if (sname) {
+ nbmin = 2;
+ } else {
+ nbmin = 2;
+ }
+ } else if (s_cmp(c3, "HRD", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nbmin = 2;
+ } else {
+ nbmin = 2;
+ }
+ } else if (s_cmp(c3, "BRD", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nbmin = 2;
+ } else {
+ nbmin = 2;
+ }
+ } else if (s_cmp(c3, "TRI", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nbmin = 2;
+ } else {
+ nbmin = 2;
+ }
+ }
+ } else if (s_cmp(c2, "SY", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRF", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nbmin = 8;
+ } else {
+ nbmin = 8;
+ }
+ } else if (sname && s_cmp(c3, "TRD", (ftnlen)3, (ftnlen)3) == 0) {
+ nbmin = 2;
+ }
+ } else if (cname && s_cmp(c2, "HE", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRD", (ftnlen)3, (ftnlen)3) == 0) {
+ nbmin = 2;
+ }
+ } else if (sname && s_cmp(c2, "OR", (ftnlen)2, (ftnlen)2) == 0) {
+ if (*(unsigned char *)c3 == 'G') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nbmin = 2;
+ }
+ } else if (*(unsigned char *)c3 == 'M') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nbmin = 2;
+ }
+ }
+ } else if (cname && s_cmp(c2, "UN", (ftnlen)2, (ftnlen)2) == 0) {
+ if (*(unsigned char *)c3 == 'G') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nbmin = 2;
+ }
+ } else if (*(unsigned char *)c3 == 'M') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nbmin = 2;
+ }
+ }
+ }
+ ret_val = nbmin;
+ return ret_val;
+
+L300:
+
+/* ISPEC = 3: crossover point */
+
+ nx = 0;
+ if (s_cmp(c2, "GE", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "QRF", (ftnlen)3, (ftnlen)3) == 0 || s_cmp(c3, "RQF", (
+ ftnlen)3, (ftnlen)3) == 0 || s_cmp(c3, "LQF", (ftnlen)3, (
+ ftnlen)3) == 0 || s_cmp(c3, "QLF", (ftnlen)3, (ftnlen)3) == 0)
+ {
+ if (sname) {
+ nx = 128;
+ } else {
+ nx = 128;
+ }
+ } else if (s_cmp(c3, "HRD", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nx = 128;
+ } else {
+ nx = 128;
+ }
+ } else if (s_cmp(c3, "BRD", (ftnlen)3, (ftnlen)3) == 0) {
+ if (sname) {
+ nx = 128;
+ } else {
+ nx = 128;
+ }
+ }
+ } else if (s_cmp(c2, "SY", (ftnlen)2, (ftnlen)2) == 0) {
+ if (sname && s_cmp(c3, "TRD", (ftnlen)3, (ftnlen)3) == 0) {
+ nx = 32;
+ }
+ } else if (cname && s_cmp(c2, "HE", (ftnlen)2, (ftnlen)2) == 0) {
+ if (s_cmp(c3, "TRD", (ftnlen)3, (ftnlen)3) == 0) {
+ nx = 32;
+ }
+ } else if (sname && s_cmp(c2, "OR", (ftnlen)2, (ftnlen)2) == 0) {
+ if (*(unsigned char *)c3 == 'G') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nx = 128;
+ }
+ }
+ } else if (cname && s_cmp(c2, "UN", (ftnlen)2, (ftnlen)2) == 0) {
+ if (*(unsigned char *)c3 == 'G') {
+ if (s_cmp(c4, "QR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "RQ",
+ (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "LQ", (ftnlen)2, (
+ ftnlen)2) == 0 || s_cmp(c4, "QL", (ftnlen)2, (ftnlen)2) ==
+ 0 || s_cmp(c4, "HR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(
+ c4, "TR", (ftnlen)2, (ftnlen)2) == 0 || s_cmp(c4, "BR", (
+ ftnlen)2, (ftnlen)2) == 0) {
+ nx = 128;
+ }
+ }
+ }
+ ret_val = nx;
+ return ret_val;
+
+L400:
+
+/* ISPEC = 4: number of shifts (used by xHSEQR) */
+
+ ret_val = 6;
+ return ret_val;
+
+L500:
+
+/* ISPEC = 5: minimum column dimension (not used) */
+
+ ret_val = 2;
+ return ret_val;
+
+L600:
+
+/* ISPEC = 6: crossover point for SVD (used by xGELSS and xGESVD) */
+
+ ret_val = (integer) ((real) min(*n1,*n2) * 1.6f);
+ return ret_val;
+
+L700:
+
+/* ISPEC = 7: number of processors (not used) */
+
+ ret_val = 1;
+ return ret_val;
+
+L800:
+
+/* ISPEC = 8: crossover point for multishift (used by xHSEQR) */
+
+ ret_val = 50;
+ return ret_val;
+
+L900:
+
+/* ISPEC = 9: maximum size of the subproblems at the bottom of the
+ computation tree in the divide-and-conquer algorithm
+ (used by xGELSD and xGESDD) */
+
+ ret_val = 25;
+ return ret_val;
+
+L1000:
+
+/* ISPEC = 10: ieee NaN arithmetic can be trusted not to trap
+
+ ILAENV = 0 */
+ ret_val = 1;
+ if (ret_val == 1) {
+ ret_val = ieeeck_(&c__0, &c_b162, &c_b163);
+ }
+ return ret_val;
+
+L1100:
+
+/* ISPEC = 11: infinity arithmetic can be trusted not to trap
+
+ ILAENV = 0 */
+ ret_val = 1;
+ if (ret_val == 1) {
+ ret_val = ieeeck_(&c__1, &c_b162, &c_b163);
+ }
+ return ret_val;
+
+/* End of ILAENV */
+
+} /* ilaenv_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/lsame.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/lsame.c
new file mode 100644
index 00000000..d9baaa3f
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/lsame.c
@@ -0,0 +1,101 @@
+#include "f2c.h"
+
+logical lsame_(char *ca, char *cb)
+{
+/* -- LAPACK auxiliary routine (version 3.0) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ September 30, 1994
+
+
+ Purpose
+ =======
+
+ LSAME returns .TRUE. if CA is the same letter as CB regardless of
+ case.
+
+ Arguments
+ =========
+
+ CA (input) CHARACTER*1
+ CB (input) CHARACTER*1
+ CA and CB specify the single characters to be compared.
+
+ =====================================================================
+
+
+
+ Test if the characters are equal */
+ /* System generated locals */
+ logical ret_val;
+ /* Local variables */
+ static integer inta, intb, zcode;
+
+
+ ret_val = *(unsigned char *)ca == *(unsigned char *)cb;
+ if (ret_val) {
+ return ret_val;
+ }
+
+/* Now test for equivalence if both characters are alphabetic. */
+
+ zcode = 'Z';
+
+/* Use 'Z' rather than 'A' so that ASCII can be detected on Prime
+ machines, on which ICHAR returns a value with bit 8 set.
+ ICHAR('A') on Prime machines returns 193 which is the same as
+ ICHAR('A') on an EBCDIC machine. */
+
+ inta = *(unsigned char *)ca;
+ intb = *(unsigned char *)cb;
+
+ if (zcode == 90 || zcode == 122) {
+
+/* ASCII is assumed - ZCODE is the ASCII code of either lower o
+r
+ upper case 'Z'. */
+
+ if (inta >= 97 && inta <= 122) {
+ inta += -32;
+ }
+ if (intb >= 97 && intb <= 122) {
+ intb += -32;
+ }
+
+ } else if (zcode == 233 || zcode == 169) {
+
+/* EBCDIC is assumed - ZCODE is the EBCDIC code of either lower
+ or
+ upper case 'Z'. */
+
+ if (inta >= 129 && inta <= 137 || inta >= 145 && inta <= 153 || inta
+ >= 162 && inta <= 169) {
+ inta += 64;
+ }
+ if (intb >= 129 && intb <= 137 || intb >= 145 && intb <= 153 || intb
+ >= 162 && intb <= 169) {
+ intb += 64;
+ }
+
+ } else if (zcode == 218 || zcode == 250) {
+
+/* ASCII is assumed, on Prime machines - ZCODE is the ASCII cod
+e
+ plus 128 of either lower or upper case 'Z'. */
+
+ if (inta >= 225 && inta <= 250) {
+ inta += -32;
+ }
+ if (intb >= 225 && intb <= 250) {
+ intb += -32;
+ }
+ }
+ ret_val = inta == intb;
+
+/* RETURN
+
+ End of LSAME */
+
+ return ret_val;
+} /* lsame_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/open.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/open.c
new file mode 100644
index 00000000..e7810757
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/open.c
@@ -0,0 +1,256 @@
+#include "f2c.h"
+#include "fio.h"
+#include "string.h"
+#ifndef NON_POSIX_STDIO
+#ifdef MSDOS
+#include "io.h"
+#else
+#include "unistd.h" /* for access */
+#endif
+#endif
+
+#undef abs
+#undef min
+#undef max
+#include "stdlib.h"
+extern int f__canseek(FILE*);
+extern integer f_clos(cllist*);
+
+#ifdef NON_ANSI_RW_MODES
+char *f__r_mode[2] = {"r", "r"};
+char *f__w_mode[4] = {"w", "w", "r+w", "r+w"};
+#else
+char *f__r_mode[2] = {"rb", "r"};
+char *f__w_mode[4] = {"wb", "w", "r+b", "r+"};
+#endif
+
+static char f__buf0[400], *f__buf = f__buf0;
+int f__buflen = (int)sizeof(f__buf0);
+
+static void f__bufadj(int n, int c)
+{
+ unsigned int len;
+ char *nbuf, *s, *t, *te;
+
+ if (f__buf == f__buf0)
+ f__buflen = 1024;
+ while(f__buflen <= n)
+ f__buflen <<= 1;
+ len = (unsigned int)f__buflen;
+ if (len != f__buflen || !(nbuf = (char*)malloc(len)))
+ f__fatal(113, "malloc failure");
+ s = nbuf;
+ t = f__buf;
+ te = t + c;
+ while(t < te)
+ *s++ = *t++;
+ if (f__buf != f__buf0)
+ free(f__buf);
+ f__buf = nbuf;
+ }
+
+int f__putbuf(int c)
+{
+ char *s, *se;
+ int n;
+
+ if (f__hiwater > f__recpos)
+ f__recpos = f__hiwater;
+ n = f__recpos + 1;
+ if (n >= f__buflen)
+ f__bufadj(n, f__recpos);
+ s = f__buf;
+ se = s + f__recpos;
+ if (c)
+ *se++ = c;
+ *se = 0;
+ for(;;) {
+ fputs(s, f__cf);
+ s += strlen(s);
+ if (s >= se)
+ break; /* normally happens the first time */
+ putc(*s++, f__cf);
+ }
+ return 0;
+ }
+
+void x_putc(int c)
+{
+ if (f__recpos >= f__buflen)
+ f__bufadj(f__recpos, f__buflen);
+ f__buf[f__recpos++] = c;
+ }
+
+#define opnerr(f,m,s) {if(f) errno= m; else opn_err(m,s,a); return(m);}
+
+static void opn_err(int m, char *s, olist *a)
+{
+ if (a->ofnm) {
+ /* supply file name to error message */
+ if (a->ofnmlen >= f__buflen)
+ f__bufadj((int)a->ofnmlen, 0);
+ g_char(a->ofnm, a->ofnmlen, f__curunit->ufnm = f__buf);
+ }
+ f__fatal(m, s);
+ }
+
+integer f_open(olist *a)
+{ unit *b;
+ integer rv;
+ char buf[256], *s;
+ cllist x;
+ int ufmt;
+ FILE *tf;
+#ifndef NON_UNIX_STDIO
+ int n;
+#endif
+ f__external = 1;
+ if(a->ounit>=MXUNIT || a->ounit<0)
+ err(a->oerr,101,"open")
+ if (!f__init)
+ f_init();
+ f__curunit = b = &f__units[a->ounit];
+ if(b->ufd) {
+ if(a->ofnm==0)
+ {
+ same: if (a->oblnk)
+ b->ublnk = *a->oblnk == 'z' || *a->oblnk == 'Z';
+ return(0);
+ }
+#ifdef NON_UNIX_STDIO
+ if (b->ufnm
+ && strlen(b->ufnm) == a->ofnmlen
+ && !strncmp(b->ufnm, a->ofnm, (unsigned)a->ofnmlen))
+ goto same;
+#else
+ g_char(a->ofnm,a->ofnmlen,buf);
+ if (f__inode(buf,&n) == b->uinode && n == b->udev)
+ goto same;
+#endif
+ x.cunit=a->ounit;
+ x.csta=0;
+ x.cerr=a->oerr;
+ if ((rv = f_clos(&x)) != 0)
+ return rv;
+ }
+ b->url = (int)a->orl;
+ b->ublnk = a->oblnk && (*a->oblnk == 'z' || *a->oblnk == 'Z');
+ if(a->ofm==0)
+ { if(b->url>0) b->ufmt=0;
+ else b->ufmt=1;
+ }
+ else if(*a->ofm=='f' || *a->ofm == 'F') b->ufmt=1;
+ else b->ufmt=0;
+ ufmt = b->ufmt;
+#ifdef url_Adjust
+ if (b->url && !ufmt)
+ url_Adjust(b->url);
+#endif
+ if (a->ofnm) {
+ g_char(a->ofnm,a->ofnmlen,buf);
+ if (!buf[0])
+ opnerr(a->oerr,107,"open")
+ }
+ else
+ sprintf(buf, "fort.%ld", (long)a->ounit);
+ b->uscrtch = 0;
+ b->uend=0;
+ b->uwrt = 0;
+ b->ufd = 0;
+ b->urw = 3;
+ switch(a->osta ? *a->osta : 'u')
+ {
+ case 'o':
+ case 'O':
+#ifdef NON_POSIX_STDIO
+ if (!(tf = fopen(buf,"r")))
+ opnerr(a->oerr,errno,"open")
+ fclose(tf);
+#else
+ if (access(buf,0))
+ opnerr(a->oerr,errno,"open")
+#endif
+ break;
+ case 's':
+ case 'S':
+ b->uscrtch=1;
+#ifdef NON_ANSI_STDIO
+ (void) strcpy(buf,"tmp.FXXXXXX");
+ (void) mktemp(buf);
+ goto replace;
+#else
+ if (!(b->ufd = tmpfile()))
+ opnerr(a->oerr,errno,"open")
+ b->ufnm = 0;
+#ifndef NON_UNIX_STDIO
+ b->uinode = b->udev = -1;
+#endif
+ b->useek = 1;
+ return 0;
+#endif
+
+ case 'n':
+ case 'N':
+#ifdef NON_POSIX_STDIO
+ if ((tf = fopen(buf,"r")) || (tf = fopen(buf,"a"))) {
+ fclose(tf);
+ opnerr(a->oerr,128,"open")
+ }
+#else
+ if (!access(buf,0))
+ opnerr(a->oerr,128,"open")
+#endif
+ /* no break */
+ case 'r': /* Fortran 90 replace option */
+ case 'R':
+#ifdef NON_ANSI_STDIO
+ replace:
+#endif
+ if (tf = fopen(buf,f__w_mode[0]))
+ fclose(tf);
+ }
+
+ b->ufnm=(char *) malloc((unsigned int)(strlen(buf)+1));
+ if(b->ufnm==NULL) opnerr(a->oerr,113,"no space");
+ (void) strcpy(b->ufnm,buf);
+ if ((s = a->oacc) && b->url)
+ ufmt = 0;
+ if(!(tf = fopen(buf, f__w_mode[ufmt|2]))) {
+ if (tf = fopen(buf, f__r_mode[ufmt]))
+ b->urw = 1;
+ else if (tf = fopen(buf, f__w_mode[ufmt])) {
+ b->uwrt = 1;
+ b->urw = 2;
+ }
+ else
+ err(a->oerr, errno, "open");
+ }
+ b->useek = f__canseek(b->ufd = tf);
+#ifndef NON_UNIX_STDIO
+ if((b->uinode = f__inode(buf,&b->udev)) == -1)
+ opnerr(a->oerr,108,"open")
+#endif
+ if(b->useek)
+ if (a->orl)
+ rewind(b->ufd);
+ else if ((s = a->oacc) && (*s == 'a' || *s == 'A')
+ && fseek(b->ufd, 0L, SEEK_END))
+ opnerr(a->oerr,129,"open");
+ return(0);
+}
+
+int fk_open(int seq, int fmt, ftnint n)
+{ char nbuf[10];
+ olist a;
+ (void) sprintf(nbuf,"fort.%ld",(long)n);
+ a.oerr=1;
+ a.ounit=n;
+ a.ofnm=nbuf;
+ a.ofnmlen=strlen(nbuf);
+ a.osta=NULL;
+ a.oacc= seq==SEQ?"s":"d";
+ a.ofm = fmt==FMT?"f":"u";
+ a.orl = seq==DIR?1:0;
+ a.oblnk=NULL;
+ return(f_open(&a));
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_cmp.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_cmp.c
new file mode 100644
index 00000000..cd6a7dc4
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_cmp.c
@@ -0,0 +1,40 @@
+#include "f2c.h"
+
+/* compare two strings */
+
+integer s_cmp(char *a0, char *b0, ftnlen la, ftnlen lb)
+{
+unsigned char *a, *aend, *b, *bend;
+a = (unsigned char *)a0;
+b = (unsigned char *)b0;
+aend = a + la;
+bend = b + lb;
+
+if(la <= lb)
+ {
+ while(a < aend)
+ if(*a != *b)
+ return( *a - *b );
+ else
+ { ++a; ++b; }
+
+ while(b < bend)
+ if(*b != ' ')
+ return( ' ' - *b );
+ else ++b;
+ }
+
+else
+ {
+ while(b < bend)
+ if(*a == *b)
+ { ++a; ++b; }
+ else
+ return( *a - *b );
+ while(a < aend)
+ if(*a != ' ')
+ return(*a - ' ');
+ else ++a;
+ }
+return(0);
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_copy.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_copy.c
new file mode 100644
index 00000000..289c67dd
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_copy.c
@@ -0,0 +1,47 @@
+/* Unless compiled with -DNO_OVERWRITE, this variant of s_copy allows the
+ * target of an assignment to appear on its right-hand side (contrary
+ * to the Fortran 77 Standard, but in accordance with Fortran 90),
+ * as in a(2:5) = a(4:7) .
+ */
+
+#include "f2c.h"
+
+/* assign strings: a = b */
+
+void s_copy(char *a, char *b, ftnlen la, ftnlen lb)
+{
+ char *aend, *bend;
+
+ aend = a + la;
+
+ if(la <= lb)
+#ifndef NO_OVERWRITE
+ if (a <= b || a >= b + la)
+#endif
+ while(a < aend)
+ *a++ = *b++;
+#ifndef NO_OVERWRITE
+ else
+ for(b += la; a < aend; )
+ *--aend = *--b;
+#endif
+
+ else {
+ bend = b + lb;
+#ifndef NO_OVERWRITE
+ if (a <= b || a >= bend)
+#endif
+ while(b < bend)
+ *a++ = *b++;
+#ifndef NO_OVERWRITE
+ else {
+ a += lb;
+ while(b < bend)
+ *--a = *--bend;
+ a += lb;
+ }
+#endif
+ while(a < aend)
+ *a++ = ' ';
+ }
+ }
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_stop.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_stop.c
new file mode 100644
index 00000000..049f71b6
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/s_stop.c
@@ -0,0 +1,37 @@
+#include "stdio.h"
+#include "f2c.h"
+
+#undef abs
+#undef min
+#undef max
+#include "stdlib.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+void f_exit(void);
+
+int s_stop(char *s, ftnlen n)
+{
+int i;
+
+if(n > 0)
+ {
+ fprintf(stderr, "STOP ");
+ for(i = 0; i<n ; ++i)
+ putc(*s++, stderr);
+ fprintf(stderr, " statement executed\n");
+ }
+#ifdef NO_ONEXIT
+f_exit();
+#endif
+exit(0);
+
+/* We cannot avoid (useless) compiler diagnostics here: */
+/* some compilers complain if there is no return statement, */
+/* and others complain that this one cannot be reached. */
+
+return 0; /* NOT REACHED */
+}
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/sfe.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/sfe.c
new file mode 100644
index 00000000..4f8907ea
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/sfe.c
@@ -0,0 +1,29 @@
+/* sequential formatted external common routines*/
+#include "f2c.h"
+#include "fio.h"
+
+extern char *f__fmtbuf;
+
+integer e_rsfe(Void)
+{ int n;
+ n=en_fio();
+ f__fmtbuf=NULL;
+ return(n);
+}
+
+int c_sfe(cilist *a) /* check */
+{ unit *p;
+ f__curunit = p = &f__units[a->ciunit];
+ if(a->ciunit >= MXUNIT || a->ciunit<0)
+ err(a->cierr,101,"startio");
+ if(p->ufd==NULL && fk_open(SEQ,FMT,a->ciunit)) err(a->cierr,114,"sfe")
+ if(!p->ufmt) err(a->cierr,102,"sfe")
+ return(0);
+}
+
+integer e_wsfe(Void)
+{
+ int n = en_fio();
+ f__fmtbuf = NULL;
+ return n;
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/sig_die.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/sig_die.c
new file mode 100644
index 00000000..78335200
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/sig_die.c
@@ -0,0 +1,41 @@
+#include "stdio.h"
+#include "signal.h"
+
+#ifndef SIGIOT
+#ifdef SIGABRT
+#define SIGIOT SIGABRT
+#endif
+#endif
+
+#include "stdlib.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern void f_exit(void);
+
+void sig_die(char *s, int kill)
+{
+ /* print error message, then clear buffers */
+ fprintf(stderr, "%s\n", s);
+
+ if(kill)
+ {
+ fflush(stderr);
+ f_exit();
+ fflush(stderr);
+ /* now get a core */
+#ifdef SIGIOT
+ signal(SIGIOT, SIG_DFL);
+#endif
+ abort();
+ }
+ else {
+#ifdef NO_ONEXIT
+ f_exit();
+#endif
+ exit(1);
+ }
+ }
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/util.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/util.c
new file mode 100644
index 00000000..684cdfc4
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/util.c
@@ -0,0 +1,39 @@
+#ifndef NON_UNIX_STDIO
+#define _INCLUDE_POSIX_SOURCE /* for HP-UX */
+#define _INCLUDE_XOPEN_SOURCE /* for HP-UX */
+#include "sys/types.h"
+#include "sys/stat.h"
+#endif
+#include "f2c.h"
+#include "fio.h"
+
+void g_char(char *a, ftnlen alen, char *b)
+{
+ char *x = a + alen, *y = b + alen;
+
+ for(;; y--) {
+ if (x <= a) {
+ *b = 0;
+ return;
+ }
+ if (*--x != ' ')
+ break;
+ }
+ *y-- = 0;
+ do *y-- = *x;
+ while(x-- > a);
+ }
+
+void b_char(char *a, char *b, ftnlen blen)
+{ int i;
+ for(i=0;i<blen && *a!=0;i++) *b++= *a++;
+ for(;i<blen;i++) *b++=' ';
+}
+#ifndef NON_UNIX_STDIO
+long f__inode(char *a, int *dev)
+{ struct stat x;
+ if(stat(a,&x)<0) return(-1);
+ *dev = x.st_dev;
+ return(x.st_ino);
+}
+#endif
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/wref.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/wref.c
new file mode 100644
index 00000000..636489c3
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/wref.c
@@ -0,0 +1,266 @@
+#include "f2c.h"
+#include "fio.h"
+
+#undef abs
+#undef min
+#undef max
+#include "stdlib.h"
+#include "string.h"
+
+#include "fmt.h"
+#include "fp.h"
+#ifndef VAX
+#include "ctype.h"
+#endif
+
+int wrt_E(ufloat *p, int w, int d, int e, ftnlen len)
+{
+ char buf[FMAX+EXPMAXDIGS+4], *s, *se;
+ int d1, delta, e1, i, sign, signspace;
+ double dd;
+#ifdef WANT_LEAD_0
+ int insert0 = 0;
+#endif
+#ifndef VAX
+ int e0 = e;
+#endif
+
+ if(e <= 0)
+ e = 2;
+ if(f__scale) {
+ if(f__scale >= d + 2 || f__scale <= -d)
+ goto nogood;
+ }
+ if(f__scale <= 0)
+ --d;
+ if (len == sizeof(real))
+ dd = p->pf;
+ else
+ dd = p->pd;
+ if (dd < 0.) {
+ signspace = sign = 1;
+ dd = -dd;
+ }
+ else {
+ sign = 0;
+ signspace = (int)f__cplus;
+#ifndef VAX
+ if (!dd)
+ dd = 0.; /* avoid -0 */
+#endif
+ }
+ delta = w - (2 /* for the . and the d adjustment above */
+ + 2 /* for the E+ */ + signspace + d + e);
+#ifdef WANT_LEAD_0
+ if (f__scale <= 0 && delta > 0) {
+ delta--;
+ insert0 = 1;
+ }
+ else
+#endif
+ if (delta < 0) {
+nogood:
+ while(--w >= 0)
+ PUT('*');
+ return(0);
+ }
+ if (f__scale < 0)
+ d += f__scale;
+ if (d > FMAX) {
+ d1 = d - FMAX;
+ d = FMAX;
+ }
+ else
+ d1 = 0;
+ sprintf(buf,"%#.*E", d, dd);
+#ifndef VAX
+ /* check for NaN, Infinity */
+ if (!isdigit(buf[0])) {
+ switch(buf[0]) {
+ case 'n':
+ case 'N':
+ signspace = 0; /* no sign for NaNs */
+ }
+ delta = w - strlen(buf) - signspace;
+ if (delta < 0)
+ goto nogood;
+ while(--delta >= 0)
+ PUT(' ');
+ if (signspace)
+ PUT(sign ? '-' : '+');
+ for(s = buf; *s; s++)
+ PUT(*s);
+ return 0;
+ }
+#endif
+ se = buf + d + 3;
+#ifdef GOOD_SPRINTF_EXPONENT /* When possible, exponent has 2 digits. */
+ if (f__scale != 1 && dd)
+ sprintf(se, "%+.2d", atoi(se) + 1 - f__scale);
+#else
+ if (dd)
+ sprintf(se, "%+.2d", atoi(se) + 1 - f__scale);
+ else
+ strcpy(se, "+00");
+#endif
+ s = ++se;
+ if (e < 2) {
+ if (*s != '0')
+ goto nogood;
+ }
+#ifndef VAX
+ /* accommodate 3 significant digits in exponent */
+ if (s[2]) {
+#ifdef Pedantic
+ if (!e0 && !s[3])
+ for(s -= 2, e1 = 2; s[0] = s[1]; s++);
+
+ /* Pedantic gives the behavior that Fortran 77 specifies, */
+ /* i.e., requires that E be specified for exponent fields */
+ /* of more than 3 digits. With Pedantic undefined, we get */
+ /* the behavior that Cray displays -- you get a bigger */
+ /* exponent field if it fits. */
+#else
+ if (!e0) {
+ for(s -= 2, e1 = 2; s[0] = s[1]; s++)
+#ifdef CRAY
+ delta--;
+ if ((delta += 4) < 0)
+ goto nogood
+#endif
+ ;
+ }
+#endif
+ else if (e0 >= 0)
+ goto shift;
+ else
+ e1 = e;
+ }
+ else
+ shift:
+#endif
+ for(s += 2, e1 = 2; *s; ++e1, ++s)
+ if (e1 >= e)
+ goto nogood;
+ while(--delta >= 0)
+ PUT(' ');
+ if (signspace)
+ PUT(sign ? '-' : '+');
+ s = buf;
+ i = f__scale;
+ if (f__scale <= 0) {
+#ifdef WANT_LEAD_0
+ if (insert0)
+ PUT('0');
+#endif
+ PUT('.');
+ for(; i < 0; ++i)
+ PUT('0');
+ PUT(*s);
+ s += 2;
+ }
+ else if (f__scale > 1) {
+ PUT(*s);
+ s += 2;
+ while(--i > 0)
+ PUT(*s++);
+ PUT('.');
+ }
+ if (d1) {
+ se -= 2;
+ while(s < se) PUT(*s++);
+ se += 2;
+ do PUT('0'); while(--d1 > 0);
+ }
+ while(s < se)
+ PUT(*s++);
+ if (e < 2)
+ PUT(s[1]);
+ else {
+ while(++e1 <= e)
+ PUT('0');
+ while(*s)
+ PUT(*s++);
+ }
+ return 0;
+ }
+
+int wrt_F(ufloat *p, int w, int d, ftnlen len)
+{
+ int d1, sign, n;
+ double x;
+ char *b, buf[MAXINTDIGS+MAXFRACDIGS+4], *s;
+
+ x= (len==sizeof(real)?p->pf:p->pd);
+ if (d < MAXFRACDIGS)
+ d1 = 0;
+ else {
+ d1 = d - MAXFRACDIGS;
+ d = MAXFRACDIGS;
+ }
+ if (x < 0.)
+ { x = -x; sign = 1; }
+ else {
+ sign = 0;
+#ifndef VAX
+ if (!x)
+ x = 0.;
+#endif
+ }
+
+ if (n = f__scale)
+ if (n > 0)
+ do x *= 10.; while(--n > 0);
+ else
+ do x *= 0.1; while(++n < 0);
+
+#ifdef USE_STRLEN
+ sprintf(b = buf, "%#.*f", d, x);
+ n = strlen(b) + d1;
+#else
+ n = sprintf(b = buf, "%#.*f", d, x) + d1;
+#endif
+
+#ifndef WANT_LEAD_0
+ if (buf[0] == '0' && d)
+ { ++b; --n; }
+#endif
+ if (sign) {
+ /* check for all zeros */
+ for(s = b;;) {
+ while(*s == '0') s++;
+ switch(*s) {
+ case '.':
+ s++; continue;
+ case 0:
+ sign = 0;
+ }
+ break;
+ }
+ }
+ if (sign || f__cplus)
+ ++n;
+ if (n > w) {
+#ifdef WANT_LEAD_0
+ if (buf[0] == '0' && --n == w)
+ ++b;
+ else
+#endif
+ {
+ while(--w >= 0)
+ PUT('*');
+ return 0;
+ }
+ }
+ for(w -= n; --w >= 0; )
+ PUT(' ');
+ if (sign)
+ PUT('-');
+ else if (f__cplus)
+ PUT('+');
+ while(n = *b++)
+ PUT(n);
+ while(--d1 >= 0)
+ PUT('0');
+ return 0;
+ }
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/wrtfmt.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/wrtfmt.c
new file mode 100644
index 00000000..74d1131a
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/wrtfmt.c
@@ -0,0 +1,321 @@
+#include "f2c.h"
+#include "fio.h"
+#include "fmt.h"
+
+extern icilist *f__svic;
+extern char *f__icptr;
+
+ static int
+mv_cur(Void) /* shouldn't use fseek because it insists on calling fflush */
+ /* instead we know too much about stdio */
+{
+ int cursor = f__cursor;
+ f__cursor = 0;
+ if(f__external == 0) {
+ if(cursor < 0) {
+ if(f__hiwater < f__recpos)
+ f__hiwater = f__recpos;
+ f__recpos += cursor;
+ f__icptr += cursor;
+ if(f__recpos < 0)
+ err(f__elist->cierr, 110, "left off");
+ }
+ else if(cursor > 0) {
+ if(f__recpos + cursor >= f__svic->icirlen)
+ err(f__elist->cierr, 110, "recend");
+ if(f__hiwater <= f__recpos)
+ for(; cursor > 0; cursor--)
+ (*f__putn)(' ');
+ else if(f__hiwater <= f__recpos + cursor) {
+ cursor -= f__hiwater - f__recpos;
+ f__icptr += f__hiwater - f__recpos;
+ f__recpos = f__hiwater;
+ for(; cursor > 0; cursor--)
+ (*f__putn)(' ');
+ }
+ else {
+ f__icptr += cursor;
+ f__recpos += cursor;
+ }
+ }
+ return(0);
+ }
+ if (cursor > 0) {
+ if(f__hiwater <= f__recpos)
+ for(;cursor>0;cursor--) (*f__putn)(' ');
+ else if(f__hiwater <= f__recpos + cursor) {
+ cursor -= f__hiwater - f__recpos;
+ f__recpos = f__hiwater;
+ for(; cursor > 0; cursor--)
+ (*f__putn)(' ');
+ }
+ else {
+ f__recpos += cursor;
+ }
+ }
+ else if (cursor < 0)
+ {
+ if(cursor + f__recpos < 0)
+ err(f__elist->cierr,110,"left off");
+ if(f__hiwater < f__recpos)
+ f__hiwater = f__recpos;
+ f__recpos += cursor;
+ }
+ return(0);
+}
+
+static int wrt_Z(Uint *n, int w, int minlen, ftnlen len)
+{
+ char *s, *se;
+ int i, w1;
+ static int one = 1;
+ static char hex[] = "0123456789ABCDEF";
+ s = (char *)n;
+ --len;
+ if (*(char *)&one) {
+ /* little endian */
+ se = s;
+ s += len;
+ i = -1;
+ }
+ else {
+ se = s + len;
+ i = 1;
+ }
+ for(;; s += i)
+ if (s == se || *s)
+ break;
+ w1 = (i*(se-s) << 1) + 1;
+ if (*s & 0xf0)
+ w1++;
+ if (w1 > w)
+ for(i = 0; i < w; i++)
+ (*f__putn)('*');
+ else {
+ if ((minlen -= w1) > 0)
+ w1 += minlen;
+ while(--w >= w1)
+ (*f__putn)(' ');
+ while(--minlen >= 0)
+ (*f__putn)('0');
+ if (!(*s & 0xf0)) {
+ (*f__putn)(hex[*s & 0xf]);
+ if (s == se)
+ return 0;
+ s += i;
+ }
+ for(;; s += i) {
+ (*f__putn)(hex[*s >> 4 & 0xf]);
+ (*f__putn)(hex[*s & 0xf]);
+ if (s == se)
+ break;
+ }
+ }
+ return 0;
+ }
+
+static int wrt_I(Uint *n, int w, ftnlen len, int base)
+{ int ndigit,sign,spare,i;
+ longint x;
+ char *ans;
+ if(len==sizeof(integer)) x=n->il;
+ else if(len == sizeof(char)) x = n->ic;
+#ifdef Allow_TYQUAD
+ else if (len == sizeof(longint)) x = n->ili;
+#endif
+ else x=n->is;
+ ans=f__icvt(x,&ndigit,&sign, base);
+ spare=w-ndigit;
+ if(sign || f__cplus) spare--;
+ if(spare<0)
+ for(i=0;i<w;i++) (*f__putn)('*');
+ else
+ { for(i=0;i<spare;i++) (*f__putn)(' ');
+ if(sign) (*f__putn)('-');
+ else if(f__cplus) (*f__putn)('+');
+ for(i=0;i<ndigit;i++) (*f__putn)(*ans++);
+ }
+ return(0);
+}
+
+static int wrt_IM(Uint *n, int w, int m, ftnlen len, int base)
+{ int ndigit,sign,spare,i,xsign;
+ longint x;
+ char *ans;
+ if(sizeof(integer)==len) x=n->il;
+ else if(len == sizeof(char)) x = n->ic;
+#ifdef Allow_TYQUAD
+ else if (len == sizeof(longint)) x = n->ili;
+#endif
+ else x=n->is;
+ ans=f__icvt(x,&ndigit,&sign, base);
+ if(sign || f__cplus) xsign=1;
+ else xsign=0;
+ if(ndigit+xsign>w || m+xsign>w)
+ { for(i=0;i<w;i++) (*f__putn)('*');
+ return(0);
+ }
+ if(x==0 && m==0)
+ { for(i=0;i<w;i++) (*f__putn)(' ');
+ return(0);
+ }
+ if(ndigit>=m)
+ spare=w-ndigit-xsign;
+ else
+ spare=w-m-xsign;
+ for(i=0;i<spare;i++) (*f__putn)(' ');
+ if(sign) (*f__putn)('-');
+ else if(f__cplus) (*f__putn)('+');
+ for(i=0;i<m-ndigit;i++) (*f__putn)('0');
+ for(i=0;i<ndigit;i++) (*f__putn)(*ans++);
+ return(0);
+}
+
+static int wrt_AP(char *s)
+{ char quote;
+ int i;
+
+ if(f__cursor && (i = mv_cur()))
+ return i;
+ quote = *s++;
+ for(;*s;s++)
+ { if(*s!=quote) (*f__putn)(*s);
+ else if(*++s==quote) (*f__putn)(*s);
+ else return(1);
+ }
+ return(1);
+}
+
+static int wrt_H(int a, char *s)
+{
+ int i;
+
+ if(f__cursor && (i = mv_cur()))
+ return i;
+ while(a--) (*f__putn)(*s++);
+ return(1);
+}
+
+int wrt_L(Uint *n, int len, ftnlen sz)
+{ int i;
+ long x;
+ if(sizeof(long)==sz) x=n->il;
+ else if(sz == sizeof(char)) x = n->ic;
+ else x=n->is;
+ for(i=0;i<len-1;i++)
+ (*f__putn)(' ');
+ if(x) (*f__putn)('T');
+ else (*f__putn)('F');
+ return(0);
+}
+
+static int wrt_A(char *p, ftnlen len)
+{
+ while(len-- > 0) (*f__putn)(*p++);
+ return(0);
+}
+
+static int wrt_AW(char * p, int w, ftnlen len)
+{
+ while(w>len)
+ { w--;
+ (*f__putn)(' ');
+ }
+ while(w-- > 0)
+ (*f__putn)(*p++);
+ return(0);
+}
+
+static int wrt_G(ufloat *p, int w, int d, int e, ftnlen len)
+{ double up = 1,x;
+ int i=0,oldscale,n,j;
+ x = len==sizeof(real)?p->pf:p->pd;
+ if(x < 0 ) x = -x;
+ if(x<.1) {
+ if (x != 0.)
+ return(wrt_E(p,w,d,e,len));
+ i = 1;
+ goto have_i;
+ }
+ for(;i<=d;i++,up*=10)
+ { if(x>=up) continue;
+ have_i:
+ oldscale = f__scale;
+ f__scale = 0;
+ if(e==0) n=4;
+ else n=e+2;
+ i=wrt_F(p,w-n,d-i,len);
+ for(j=0;j<n;j++) (*f__putn)(' ');
+ f__scale=oldscale;
+ return(i);
+ }
+ return(wrt_E(p,w,d,e,len));
+}
+
+int w_ed(struct syl *p, char *ptr, ftnlen len)
+{
+ int i;
+
+ if(f__cursor && (i = mv_cur()))
+ return i;
+ switch(p->op)
+ {
+ default:
+ fprintf(stderr,"w_ed, unexpected code: %d\n", p->op);
+ sig_die(f__fmtbuf, 1);
+ case I: return(wrt_I((Uint *)ptr,p->p1,len, 10));
+ case IM:
+ return(wrt_IM((Uint *)ptr,p->p1,p->p2.i[0],len,10));
+
+ /* O and OM don't work right for character, double, complex, */
+ /* or doublecomplex, and they differ from Fortran 90 in */
+ /* showing a minus sign for negative values. */
+
+ case O: return(wrt_I((Uint *)ptr, p->p1, len, 8));
+ case OM:
+ return(wrt_IM((Uint *)ptr,p->p1,p->p2.i[0],len,8));
+ case L: return(wrt_L((Uint *)ptr,p->p1, len));
+ case A: return(wrt_A(ptr,len));
+ case AW:
+ return(wrt_AW(ptr,p->p1,len));
+ case D:
+ case E:
+ case EE:
+ return(wrt_E((ufloat *)ptr,p->p1,p->p2.i[0],p->p2.i[1],len));
+ case G:
+ case GE:
+ return(wrt_G((ufloat *)ptr,p->p1,p->p2.i[0],p->p2.i[1],len));
+ case F: return(wrt_F((ufloat *)ptr,p->p1,p->p2.i[0],len));
+
+ /* Z and ZM assume 8-bit bytes. */
+
+ case Z: return(wrt_Z((Uint *)ptr,p->p1,0,len));
+ case ZM:
+ return(wrt_Z((Uint *)ptr,p->p1,p->p2.i[0],len));
+ }
+}
+
+int w_ned(struct syl *p)
+{
+ switch(p->op)
+ {
+ default: fprintf(stderr,"w_ned, unexpected code: %d\n", p->op);
+ sig_die(f__fmtbuf, 1);
+ case SLASH:
+ return((*f__donewrec)());
+ case T: f__cursor = p->p1-f__recpos - 1;
+ return(1);
+ case TL: f__cursor -= p->p1;
+ if(f__cursor < -f__recpos) /* TL1000, 1X */
+ f__cursor = -f__recpos;
+ return(1);
+ case TR:
+ case X:
+ f__cursor += p->p1;
+ return(1);
+ case APOS:
+ return(wrt_AP(p->p2.s));
+ case H:
+ return(wrt_H(p->p1,p->p2.s));
+ }
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/wsfe.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/wsfe.c
new file mode 100644
index 00000000..b772df32
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/wsfe.c
@@ -0,0 +1,69 @@
+/*write sequential formatted external*/
+#include "f2c.h"
+#include "fio.h"
+#include "fmt.h"
+extern int f__hiwater;
+
+ int
+x_wSL(Void)
+{
+ int n = f__putbuf('\n');
+ f__hiwater = f__recpos = f__cursor = 0;
+ return(n == 0);
+}
+
+ static int
+xw_end(Void)
+{
+ int n;
+
+ if(f__nonl) {
+ f__putbuf(n = 0);
+ fflush(f__cf);
+ }
+ else
+ n = f__putbuf('\n');
+ f__hiwater = f__recpos = f__cursor = 0;
+ return n;
+}
+
+ static int
+xw_rev(Void)
+{
+ int n = 0;
+ if(f__workdone) {
+ n = f__putbuf('\n');
+ f__workdone = 0;
+ }
+ f__hiwater = f__recpos = f__cursor = 0;
+ return n;
+}
+
+integer s_wsfe(cilist *a) /*start*/
+{ int n;
+ if(!f__init) f_init();
+ f__reading=0;
+ f__sequential=1;
+ f__formatted=1;
+ f__external=1;
+ if(n=c_sfe(a)) return(n);
+ f__elist=a;
+ f__hiwater = f__cursor=f__recpos=0;
+ f__nonl = 0;
+ f__scale=0;
+ f__fmtbuf=a->cifmt;
+ f__cf=f__curunit->ufd;
+ if(pars_f(f__fmtbuf)<0) err(a->cierr,100,"startio");
+ f__putn= x_putc;
+ f__doed= w_ed;
+ f__doned= w_ned;
+ f__doend=xw_end;
+ f__dorevert=xw_rev;
+ f__donewrec=x_wSL;
+ fmt_bg();
+ f__cplus=0;
+ f__cblank=f__curunit->ublnk;
+ if(f__curunit->uwrt != 1 && f__nowwriting(f__curunit))
+ err(a->cierr,errno,"write start");
+ return(0);
+}
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/clapack/xerbla.c b/src/imageplugins/coreplugin/sharpnesseditor/clapack/xerbla.c
new file mode 100644
index 00000000..d8ef512b
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/clapack/xerbla.c
@@ -0,0 +1,58 @@
+#include "blaswrap.h"
+#include "f2c.h"
+
+/* Subroutine */ int xerbla_(char *srname, integer *info)
+{
+/* -- LAPACK auxiliary routine (preliminary version) --
+ Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+ Courant Institute, Argonne National Lab, and Rice University
+ February 29, 1992
+
+
+ Purpose
+ =======
+
+ XERBLA is an error handler for the LAPACK routines.
+ It is called by an LAPACK routine if an input parameter has an
+ invalid value. A message is printed and execution stops.
+
+ Installers may consider modifying the STOP statement in order to
+ call system-specific exception-handling facilities.
+
+ Arguments
+ =========
+
+ SRNAME (input) CHARACTER*6
+ The name of the routine which called XERBLA.
+
+ INFO (input) INTEGER
+ The position of the invalid parameter in the parameter list
+ of the calling routine. */
+ /* Table of constant values */
+ static integer c__1 = 1;
+
+ /* Format strings */
+ static char fmt_9999[] = "(\002 ** On entry to \002,a6,\002 parameter nu"
+ "mber \002,i2,\002 had \002,\002an illegal value\002)";
+ /* Builtin functions */
+ integer s_wsfe(cilist *), do_fio(integer *, char *, ftnlen), e_wsfe(void);
+ /* Subroutine */ int s_stop(char *, ftnlen);
+ /* Fortran I/O blocks */
+ static cilist io___1 = { 0, 6, 0, fmt_9999, 0 };
+
+
+
+
+ s_wsfe(&io___1);
+ do_fio(&c__1, srname, (ftnlen)6);
+ do_fio(&c__1, (char *)&(*info), (ftnlen)sizeof(integer));
+ e_wsfe();
+
+ s_stop("", (ftnlen)0);
+
+
+/* End of XERBLA */
+
+ return 0;
+} /* xerbla_ */
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.cpp b/src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.cpp
new file mode 100644
index 00000000..b2ae9a62
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.cpp
@@ -0,0 +1,696 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to sharp an image
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define MAX_MATRIX_SIZE 25
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcombobox.h>
+#include <tqwidgetstack.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <knuminput.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <kseparator.h>
+#include <tdeconfig.h>
+#include <kurl.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "dimgsharpen.h"
+#include "unsharp.h"
+#include "refocus.h"
+#include "imageeffect_sharpen.h"
+#include "imageeffect_sharpen.moc"
+
+namespace DigikamImagesPluginCore
+{
+
+ImageEffect_Sharpen::ImageEffect_Sharpen(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Sharpening Photograph"), "sharpen",
+ true, true, true)
+{
+ setHelp("blursharpentool.anchor", "digikam");
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 2, 1, 0, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Method:"), gboxSettings);
+
+ m_sharpMethod = new TQComboBox( false, gboxSettings );
+ m_sharpMethod->insertItem( i18n("Simple sharp") );
+ m_sharpMethod->insertItem( i18n("Unsharp mask") );
+ m_sharpMethod->insertItem( i18n("Refocus") );
+ TQWhatsThis::add( m_sharpMethod, i18n("<p>Select the sharpening method to apply to the image."));
+
+ m_stack = new TQWidgetStack(gboxSettings);
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridSettings->addMultiCellWidget(m_sharpMethod, 0, 0, 1, 1);
+ gridSettings->addMultiCellWidget(new KSeparator(gboxSettings), 1, 1, 0, 1);
+ gridSettings->addMultiCellWidget(m_stack, 2, 2, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQWidget *simpleSharpSettings = new TQWidget(m_stack);
+ TQGridLayout* grid1 = new TQGridLayout( simpleSharpSettings, 2, 1, 0, spacingHint());
+
+ TQLabel *label = new TQLabel(i18n("Sharpness:"), simpleSharpSettings);
+ m_radiusInput = new KIntNumInput(simpleSharpSettings);
+ m_radiusInput->setRange(0, 100, 1, true);
+ m_radiusInput->setValue(0);
+ TQWhatsThis::add( m_radiusInput, i18n("<p>A sharpness of 0 has no effect, "
+ "1 and above determine the sharpen matrix radius "
+ "that determines how much to sharpen the image."));
+
+ grid1->addMultiCellWidget(label, 0, 0, 0, 1);
+ grid1->addMultiCellWidget(m_radiusInput, 1, 1, 0, 1);
+ grid1->setRowStretch(2, 10);
+ m_stack->addWidget(simpleSharpSettings, SimpleSharp);
+
+ // -------------------------------------------------------------
+
+ TQWidget *unsharpMaskSettings = new TQWidget(m_stack);
+ TQGridLayout* grid2 = new TQGridLayout( unsharpMaskSettings, 6, 1, 0, spacingHint());
+
+ TQLabel *label2 = new TQLabel(i18n("Radius:"), unsharpMaskSettings);
+ m_radiusInput2 = new KIntNumInput(unsharpMaskSettings);
+ m_radiusInput2->setRange(1, 120, 1, true);
+ TQWhatsThis::add( m_radiusInput2, i18n("<p>Radius value is the gaussian blur matrix radius value "
+ "used to determines how much to blur the image.") );
+
+ TQLabel *label3 = new TQLabel(i18n("Amount:"), unsharpMaskSettings);
+ m_amountInput = new KDoubleNumInput(unsharpMaskSettings);
+ m_amountInput->setPrecision(1);
+ m_amountInput->setRange(0.0, 5.0, 0.1, true);
+ TQWhatsThis::add( m_amountInput, i18n("<p>The value of the difference between the "
+ "original and the blur image that is added back into the original.") );
+
+ TQLabel *label4 = new TQLabel(i18n("Threshold:"), unsharpMaskSettings);
+ m_thresholdInput = new KDoubleNumInput(unsharpMaskSettings);
+ m_thresholdInput->setPrecision(2);
+ m_thresholdInput->setRange(0.0, 1.0, 0.01, true);
+ TQWhatsThis::add( m_thresholdInput, i18n("<p>The threshold, as a fraction of the maximum "
+ "luminosity value, needed to apply the difference amount.") );
+
+ grid2->addMultiCellWidget(label2, 0, 0, 0, 1);
+ grid2->addMultiCellWidget(m_radiusInput2, 1, 1, 0, 1);
+ grid2->addMultiCellWidget(label3, 2, 2, 0, 1);
+ grid2->addMultiCellWidget(m_amountInput, 3, 3, 0, 1);
+ grid2->addMultiCellWidget(label4, 4, 4, 0, 1);
+ grid2->addMultiCellWidget(m_thresholdInput, 5, 5, 0, 1);
+ grid2->setRowStretch(6, 10);
+ m_stack->addWidget(unsharpMaskSettings, UnsharpMask);
+
+ // -------------------------------------------------------------
+
+ TQWidget *refocusSettings = new TQWidget(m_stack);
+ TQGridLayout* grid3 = new TQGridLayout(refocusSettings, 10, 1, 0, spacingHint());
+
+ TQLabel *label5 = new TQLabel(i18n("Circular sharpness:"), refocusSettings);
+ m_radius = new KDoubleNumInput(refocusSettings);
+ m_radius->setPrecision(2);
+ m_radius->setRange(0.0, 5.0, 0.01, true);
+ TQWhatsThis::add( m_radius, i18n("<p>This is the radius of the circular convolution. It is the most important "
+ "parameter for using this plugin. For most images the default value of 1.0 "
+ "should give good results. Select a higher value when your image is very blurred."));
+
+ TQLabel *label6 = new TQLabel(i18n("Correlation:"), refocusSettings);
+ m_correlation = new KDoubleNumInput(refocusSettings);
+ m_correlation->setPrecision(2);
+ m_correlation->setRange(0.0, 1.0, 0.01, true);
+ TQWhatsThis::add( m_correlation, i18n("<p>Increasing the correlation may help to reduce artifacts. The correlation can "
+ "range from 0-1. Useful values are 0.5 and values close to 1, e.g. 0.95 and 0.99. "
+ "Using a high value for the correlation will reduce the sharpening effect of the "
+ "plugin."));
+
+ TQLabel *label7 = new TQLabel(i18n("Noise filter:"), refocusSettings);
+ m_noise = new KDoubleNumInput(refocusSettings);
+ m_noise->setPrecision(3);
+ m_noise->setRange(0.0, 1.0, 0.001, true);
+ TQWhatsThis::add( m_noise, i18n("<p>Increasing the noise filter parameter may help to reduce artifacts. The noise filter "
+ "can range from 0-1 but values higher than 0.1 are rarely helpful. When the noise filter "
+ "value is too low, e.g. 0.0 the image quality will be very poor. A useful value is 0.01. "
+ "Using a high value for the noise filter will reduce the sharpening "
+ "effect of the plugin."));
+
+ TQLabel *label8 = new TQLabel(i18n("Gaussian sharpness:"), refocusSettings);
+ m_gauss = new KDoubleNumInput(refocusSettings);
+ m_gauss->setPrecision(2);
+ m_gauss->setRange(0.0, 1.0, 0.01, true);
+ TQWhatsThis::add( m_gauss, i18n("<p>This is the sharpness for the gaussian convolution. Use this parameter when your "
+ "blurring is of a Gaussian type. In most cases you should set this parameter to 0, because "
+ "it causes nasty artifacts. When you use non-zero values, you will probably have to "
+ "increase the correlation and/or noise filter parameters too."));
+
+ TQLabel *label9 = new TQLabel(i18n("Matrix size:"), refocusSettings);
+ m_matrixSize = new KIntNumInput(refocusSettings);
+ m_matrixSize->setRange(0, MAX_MATRIX_SIZE, 1, true);
+ TQWhatsThis::add( m_matrixSize, i18n("<p>This parameter determines the size of the transformation matrix. "
+ "Increasing the matrix width may give better results, especially when you have "
+ "chosen large values for circular or gaussian sharpness."));
+
+ grid3->addMultiCellWidget(label5, 0, 0, 0, 1);
+ grid3->addMultiCellWidget(m_radius, 1, 1, 0, 1);
+ grid3->addMultiCellWidget(label6, 2, 2, 0, 1);
+ grid3->addMultiCellWidget(m_correlation, 3, 3, 0, 1);
+ grid3->addMultiCellWidget(label7, 4, 4, 0, 1);
+ grid3->addMultiCellWidget(m_noise, 5, 5, 0, 1);
+ grid3->addMultiCellWidget(label8, 6, 6, 0, 1);
+ grid3->addMultiCellWidget(m_gauss, 7, 7, 0, 1);
+ grid3->addMultiCellWidget(label9, 8, 8, 0, 1);
+ grid3->addMultiCellWidget(m_matrixSize, 9, 9, 0, 1);
+ grid3->setRowStretch(10, 10);
+ m_stack->addWidget(refocusSettings, Refocus);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_sharpMethod, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotSharpMethodActived(int)));
+
+ // -------------------------------------------------------------
+
+ // Image creation with dummy borders (mosaic mode) used by Refocus method. It needs to do
+ // it before to apply deconvolution filter on original image border pixels including
+ // on matrix size area. This way limit artifacts on image border.
+
+ Digikam::ImageIface iface(0, 0);
+
+ uchar* data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sb = iface.originalSixteenBit();
+ bool a = iface.originalHasAlpha();
+
+ m_img = Digikam::DImg( w + 4*MAX_MATRIX_SIZE, h + 4*MAX_MATRIX_SIZE, sb, a);
+
+ Digikam::DImg tmp;
+ Digikam::DImg org(w, h, sb, a, data);
+
+ // Copy original.
+ m_img.bitBltImage(&org, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+
+ // Create dummy top border
+ tmp = org.copy(0, 0, w, 2*MAX_MATRIX_SIZE);
+ tmp.flip(Digikam::DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 2*MAX_MATRIX_SIZE, 0);
+
+ // Create dummy bottom border
+ tmp = org.copy(0, h-2*MAX_MATRIX_SIZE, w, 2*MAX_MATRIX_SIZE);
+ tmp.flip(Digikam::DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE+h);
+
+ // Create dummy left border
+ tmp = org.copy(0, 0, 2*MAX_MATRIX_SIZE, h);
+ tmp.flip(Digikam::DImg::HORIZONTAL);
+ m_img.bitBltImage(&tmp, 0, 2*MAX_MATRIX_SIZE);
+
+ // Create dummy right border
+ tmp = org.copy(w-2*MAX_MATRIX_SIZE, 0, 2*MAX_MATRIX_SIZE, h);
+ tmp.flip(Digikam::DImg::HORIZONTAL);
+ m_img.bitBltImage(&tmp, w+2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+
+ // Create dummy top/left corner
+ tmp = org.copy(0, 0, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(Digikam::DImg::HORIZONTAL);
+ tmp.flip(Digikam::DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 0, 0);
+
+ // Create dummy top/right corner
+ tmp = org.copy(w-2*MAX_MATRIX_SIZE, 0, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(Digikam::DImg::HORIZONTAL);
+ tmp.flip(Digikam::DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, w+2*MAX_MATRIX_SIZE, 0);
+
+ // Create dummy bottom/left corner
+ tmp = org.copy(0, h-2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(Digikam::DImg::HORIZONTAL);
+ tmp.flip(Digikam::DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 0, h+2*MAX_MATRIX_SIZE);
+
+ // Create dummy bottom/right corner
+ tmp = org.copy(w-2*MAX_MATRIX_SIZE, h-2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(Digikam::DImg::HORIZONTAL);
+ tmp.flip(Digikam::DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, w+2*MAX_MATRIX_SIZE, h+2*MAX_MATRIX_SIZE);
+
+ delete [] data;
+}
+
+ImageEffect_Sharpen::~ImageEffect_Sharpen()
+{
+}
+
+void ImageEffect_Sharpen::renderingFinished(void)
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->setEnabled(true);
+ enableButton(User2, false);
+ enableButton(User3, false);
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->setEnabled(true);
+ m_amountInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ enableButton(User2, false);
+ enableButton(User3, false);
+ break;
+ }
+
+ case Refocus:
+ {
+ m_matrixSize->setEnabled(true);
+ m_radius->setEnabled(true);
+ m_gauss->setEnabled(true);
+ m_correlation->setEnabled(true);
+ m_noise->setEnabled(true);
+ break;
+ }
+ }
+}
+
+void ImageEffect_Sharpen::slotSharpMethodActived(int w)
+{
+ m_stack->raiseWidget(w);
+ if (w == Refocus)
+ {
+ enableButton(User2, true);
+ enableButton(User3, true);
+ }
+ else
+ {
+ enableButton(User2, false);
+ enableButton(User3, false);
+ }
+}
+
+void ImageEffect_Sharpen::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("sharpen Tool Dialog");
+ m_radiusInput->blockSignals(true);
+ m_radiusInput2->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_thresholdInput->blockSignals(true);
+ m_matrixSize->blockSignals(true);
+ m_radius->blockSignals(true);
+ m_gauss->blockSignals(true);
+ m_correlation->blockSignals(true);
+ m_noise->blockSignals(true);
+ m_sharpMethod->blockSignals(true);
+ m_radiusInput->setValue(config->readNumEntry("SimpleSharpRadiusAjustment", 0));
+ m_radiusInput2->setValue(config->readNumEntry("UnsharpMaskRadiusAjustment", 1));
+ m_amountInput->setValue(config->readDoubleNumEntry("UnsharpMaskAmountAjustment", 1.0));
+ m_thresholdInput->setValue(config->readDoubleNumEntry("UnsharpMaskThresholdAjustment", 0.05));
+ m_matrixSize->setValue(config->readNumEntry("RefocusMatrixSize", 5));
+ m_radius->setValue(config->readDoubleNumEntry("RefocusRadiusAjustment", 1.0));
+ m_gauss->setValue(config->readDoubleNumEntry("RefocusGaussAjustment", 0.0));
+ m_correlation->setValue(config->readDoubleNumEntry("RefocusCorrelationAjustment", 0.5));
+ m_noise->setValue(config->readDoubleNumEntry("RefocusNoiseAjustment", 0.03));
+ m_sharpMethod->setCurrentItem(config->readNumEntry("SharpenMethod", SimpleSharp));
+ m_radiusInput->blockSignals(false);
+ m_radiusInput2->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_thresholdInput->blockSignals(false);
+ m_matrixSize->blockSignals(false);
+ m_radius->blockSignals(false);
+ m_gauss->blockSignals(false);
+ m_correlation->blockSignals(false);
+ m_noise->blockSignals(false);
+ m_sharpMethod->blockSignals(false);
+ slotSharpMethodActived(m_sharpMethod->currentItem());
+}
+
+void ImageEffect_Sharpen::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("sharpen Tool Dialog");
+ config->writeEntry("SimpleSharpRadiusAjustment", m_radiusInput->value());
+ config->writeEntry("UnsharpMaskRadiusAjustment", m_radiusInput2->value());
+ config->writeEntry("UnsharpMaskAmountAjustment", m_amountInput->value());
+ config->writeEntry("UnsharpMaskThresholdAjustment", m_thresholdInput->value());
+ config->writeEntry("RefocusMatrixSize", m_matrixSize->value());
+ config->writeEntry("RefocusRadiusAjustment", m_radius->value());
+ config->writeEntry("RefocusGaussAjustment", m_gauss->value());
+ config->writeEntry("RefocusCorrelationAjustment", m_correlation->value());
+ config->writeEntry("RefocusNoiseAjustment", m_noise->value());
+ config->writeEntry("SharpenMethod", m_sharpMethod->currentItem());
+ config->sync();
+}
+
+void ImageEffect_Sharpen::resetValues(void)
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->blockSignals(true);
+ m_radiusInput->setValue(0);
+ m_radiusInput->blockSignals(false);
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_thresholdInput->blockSignals(true);
+ m_radiusInput2->setValue(1);
+ m_amountInput->setValue(1.0);
+ m_thresholdInput->setValue(0.05);
+ m_radiusInput2->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_thresholdInput->blockSignals(false);
+ break;
+ }
+
+ case Refocus:
+ {
+ m_matrixSize->blockSignals(true);
+ m_radius->blockSignals(true);
+ m_gauss->blockSignals(true);
+ m_correlation->blockSignals(true);
+ m_noise->blockSignals(true);
+ m_matrixSize->setValue(5);
+ m_radius->setValue(1.0);
+ m_gauss->setValue(0.0);
+ m_correlation->setValue(0.5);
+ m_noise->setValue(0.03);
+ m_matrixSize->blockSignals(false);
+ m_radius->blockSignals(false);
+ m_gauss->blockSignals(false);
+ m_correlation->blockSignals(false);
+ m_noise->blockSignals(false);
+ break;
+ }
+ }
+}
+
+void ImageEffect_Sharpen::prepareEffect()
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->setEnabled(false);
+
+ Digikam::DImg img = m_imagePreviewWidget->getOriginalRegionImage();
+
+ double radius = m_radiusInput->value()/10.0;
+ double sigma;
+
+ if (radius < 1.0) sigma = radius;
+ else sigma = sqrt(radius);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new Digikam::DImgSharpen(&img, this, radius, sigma ));
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+
+ Digikam::DImg img = m_imagePreviewWidget->getOriginalRegionImage();
+
+ int r = m_radiusInput2->value();
+ double a = m_amountInput->value();
+ double th = m_thresholdInput->value();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new DigikamImagesPluginCore::UnsharpMask(&img, this, r, a, th));
+ break;
+ }
+
+ case Refocus:
+ {
+ m_matrixSize->setEnabled(false);
+ m_radius->setEnabled(false);
+ m_gauss->setEnabled(false);
+ m_correlation->setEnabled(false);
+ m_noise->setEnabled(false);
+
+ int ms = m_matrixSize->value();
+ double r = m_radius->value();
+ double g = m_gauss->value();
+ double c = m_correlation->value();
+ double n = m_noise->value();
+
+ TQRect area = m_imagePreviewWidget->getOriginalImageRegionToRender();
+ TQRect tmpRect;
+ tmpRect.setLeft(area.left()-2*ms);
+ tmpRect.setRight(area.right()+2*ms);
+ tmpRect.setTop(area.top()-2*ms);
+ tmpRect.setBottom(area.bottom()+2*ms);
+ tmpRect.moveBy(2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ Digikam::DImg imTemp = m_img.copy(tmpRect);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new DigikamImagesPluginCore::Refocus(&imTemp, this, ms, r, g, c, n));
+ break;
+ }
+ }
+}
+
+void ImageEffect_Sharpen::prepareFinal()
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->setEnabled(false);
+
+ double radius = m_radiusInput->value()/10.0;
+ double sigma;
+
+ if (radius < 1.0) sigma = radius;
+ else sigma = sqrt(radius);
+
+ Digikam::ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ Digikam::DImg orgImage = Digikam::DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new Digikam::DImgSharpen(&orgImage, this, radius, sigma ));
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+
+ int r = m_radiusInput2->value();
+ double a = m_amountInput->value();
+ double th = m_thresholdInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ Digikam::DImg orgImage = Digikam::DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new DigikamImagesPluginCore::UnsharpMask(&orgImage, this, r, a, th));
+ break;
+ }
+
+ case Refocus:
+ {
+
+ m_matrixSize->setEnabled(false);
+ m_radius->setEnabled(false);
+ m_gauss->setEnabled(false);
+ m_correlation->setEnabled(false);
+ m_noise->setEnabled(false);
+
+ int ms = m_matrixSize->value();
+ double r = m_radius->value();
+ double g = m_gauss->value();
+ double c = m_correlation->value();
+ double n = m_noise->value();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>
+ (new DigikamImagesPluginCore::Refocus(&m_img, this, ms, r, g, c, n));
+ break;
+ }
+ }
+}
+
+void ImageEffect_Sharpen::putPreviewData(void)
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ case UnsharpMask:
+ {
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage();
+ m_imagePreviewWidget->setPreviewImage(imDest);
+ break;
+ }
+
+ case Refocus:
+ {
+ int ms = m_matrixSize->value();
+ TQRect area = m_imagePreviewWidget->getOriginalImageRegionToRender();
+
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage()
+ .copy(2*ms, 2*ms, area.width(), area.height());
+ m_imagePreviewWidget->setPreviewImage(imDest);
+ break;
+ }
+ }
+}
+
+void ImageEffect_Sharpen::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage();
+
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ iface.putOriginalImage(i18n("Sharpen"), imDest.bits());
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ iface.putOriginalImage(i18n("Unsharp Mask"), imDest.bits());
+ break;
+ }
+
+ case Refocus:
+ {
+ TQRect area = m_imagePreviewWidget->getOriginalImageRegionToRender();
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Refocus"), m_threadedFilter->getTargetImage()
+ .copy(2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE,
+ iface.originalWidth(),
+ iface.originalHeight())
+ .bits());
+ break;
+ }
+ }
+}
+
+void ImageEffect_Sharpen::slotUser3()
+{
+ KURL loadRestorationFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Refocus Settings File to Load")) );
+ if ( loadRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(loadRestorationFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+ if ( stream.readLine() != "# Photograph Refocus Configuration File" )
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a Photograph Refocus settings text file.")
+ .arg(loadRestorationFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+ m_matrixSize->setValue( stream.readLine().toInt() );
+ m_radius->setValue( stream.readLine().toDouble() );
+ m_gauss->setValue( stream.readLine().toDouble() );
+ m_correlation->setValue( stream.readLine().toDouble() );
+ m_noise->setValue( stream.readLine().toDouble() );
+ blockSignals(false);
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the Photograph Refocus text file."));
+
+ file.close();
+}
+
+void ImageEffect_Sharpen::slotUser2()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Refocus Settings File to Save")) );
+ if ( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# Photograph Refocus Configuration File\n";
+ stream << m_matrixSize->value() << "\n";
+ stream << m_radius->value() << "\n";
+ stream << m_gauss->value() << "\n";
+ stream << m_correlation->value() << "\n";
+ stream << m_noise->value() << "\n";
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the Photograph Refocus text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.h b/src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.h
new file mode 100644
index 00000000..fa926383
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/imageeffect_sharpen.h
@@ -0,0 +1,102 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to sharp an image
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_SHARPEN_H
+#define IMAGEEFFECT_SHARPEN_H
+
+// Digikam include.
+
+#include "ctrlpaneldlg.h"
+
+class TQComboBox;
+class TQWidgetStack;
+
+class KIntNumInput;
+class KDoubleNumInput;
+
+namespace Digikam
+{
+ class DImg;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class ImageEffect_Sharpen : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Sharpen(TQWidget *parent);
+ ~ImageEffect_Sharpen();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void readUserSettings();
+ void slotSharpMethodActived(int);
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ enum SharpingMethods
+ {
+ SimpleSharp=0,
+ UnsharpMask,
+ Refocus
+ };
+
+ TQWidgetStack *m_stack;
+
+ TQComboBox *m_sharpMethod;
+
+ KIntNumInput *m_matrixSize;
+ KIntNumInput *m_radiusInput;
+ KIntNumInput *m_radiusInput2;
+
+ KDoubleNumInput *m_radius;
+ KDoubleNumInput *m_gauss;
+ KDoubleNumInput *m_correlation;
+ KDoubleNumInput *m_noise;
+ KDoubleNumInput *m_amountInput;
+ KDoubleNumInput *m_thresholdInput;
+
+ Digikam::DImg m_img;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* IMAGEEFFECT_SHARPEN_H */
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/matrix.cpp b/src/imageplugins/coreplugin/sharpnesseditor/matrix.cpp
new file mode 100644
index 00000000..cfb8afe4
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/matrix.cpp
@@ -0,0 +1,663 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-29
+ * Description : refocus deconvolution matrix implementation.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original implementation from Refocus Gimp plug-in
+ * Copyright (C) 1999-2003 Ernst Lippe
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Uncomment this line to debug matrix computation.
+//#define RF_DEBUG 1
+
+// Square
+#define SQR(x) ((x) * (x))
+
+// C++ includes.
+
+#include <cmath>
+
+extern "C"
+{
+#include "f2c.h"
+#include "clapack.h"
+}
+
+// TQt includes.
+
+#include <tqglobal.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "matrix.h"
+
+namespace DigikamImagesPluginCore
+{
+
+Mat *RefocusMatrix::allocate_matrix (int nrows, int ncols)
+{
+ Mat *result = new Mat;
+ memset (result, 0, sizeof(result));
+
+ result->cols = ncols;
+ result->rows = nrows;
+ result->data = new double [nrows * ncols];
+ memset (result->data, 0, nrows * ncols * sizeof(double));
+
+ return (result);
+}
+
+void RefocusMatrix::finish_matrix (Mat * mat)
+{
+ delete [] mat->data;
+}
+
+void RefocusMatrix::finish_and_free_matrix (Mat * mat)
+{
+ delete [] mat->data;
+ delete mat;
+}
+
+double *RefocusMatrix::mat_eltptr (Mat * mat, const int r, const int c)
+{
+ Q_ASSERT ((r >= 0) && (r < mat->rows));
+ Q_ASSERT ((c >= 0) && (c < mat->rows));
+ return (&(mat->data[mat->rows * c + r]));
+}
+
+double RefocusMatrix::mat_elt (const Mat * mat, const int r, const int c)
+{
+ Q_ASSERT ((r >= 0) && (r < mat->rows));
+ Q_ASSERT ((c >= 0) && (c < mat->rows));
+ return (mat->data[mat->rows * c + r]);
+}
+
+void RefocusMatrix::init_c_mat (CMat * mat, const int radius)
+{
+ mat->radius = radius;
+ mat->row_stride = 2 * radius + 1;
+ mat->data = new double [SQR (mat->row_stride)];
+ memset (mat->data, 0, SQR (mat->row_stride) * sizeof(double));
+ mat->center = mat->data + mat->row_stride * mat->radius + mat->radius;
+}
+
+CMat *RefocusMatrix::allocate_c_mat (const int radius)
+{
+ CMat *result = new CMat;
+ memset(result, 0, sizeof(result));
+ init_c_mat (result, radius);
+ return (result);
+}
+
+void RefocusMatrix::finish_c_mat (CMat * mat)
+{
+ delete [] mat->data;
+ mat->data = NULL;
+}
+
+inline double *RefocusMatrix::c_mat_eltptr (CMat * mat, const int col, const int row)
+{
+ Q_ASSERT ((TQABS (row) <= mat->radius) && (TQABS (col) <= mat->radius));
+ return (mat->center + mat->row_stride * row + col);
+}
+
+inline double RefocusMatrix::c_mat_elt (const CMat * const mat, const int col, const int row)
+{
+ Q_ASSERT ((TQABS (row) <= mat->radius) && (TQABS (col) <= mat->radius));
+ return (mat->center[mat->row_stride * row + col]);
+}
+
+void RefocusMatrix::convolve_mat (CMat * result, const CMat * const mata, const CMat * const matb)
+{
+ int xr, yr, xa, ya;
+
+ for (yr = -result->radius; yr <= result->radius; yr++)
+ {
+ for (xr = -result->radius; xr <= result->radius; xr++)
+ {
+ const int ya_low = TQMAX (-mata->radius, yr - matb->radius);
+ const int ya_high = TQMIN (mata->radius, yr + matb->radius);
+ const int xa_low = TQMAX (-mata->radius, xr - matb->radius);
+ const int xa_high = TQMIN (mata->radius, xr + matb->radius);
+ double val = 0.0;
+
+ for (ya = ya_low; ya <= ya_high; ya++)
+ {
+ for (xa = xa_low; xa <= xa_high; xa++)
+ {
+ val += c_mat_elt (mata, xa, ya) *
+ c_mat_elt (matb, xr - xa, yr - ya);
+ }
+ }
+
+ *c_mat_eltptr (result, xr, yr) = val;
+ }
+ }
+}
+
+void RefocusMatrix::convolve_star_mat (CMat * result, const CMat * const mata, const CMat * const matb)
+{
+ int xr, yr, xa, ya;
+
+ for (yr = -result->radius; yr <= result->radius; yr++)
+ {
+ for (xr = -result->radius; xr <= result->radius; xr++)
+ {
+ const int ya_low = TQMAX (-mata->radius, -matb->radius - yr);
+ const int ya_high = TQMIN (mata->radius, matb->radius - yr);
+ const int xa_low = TQMAX (-mata->radius, -matb->radius - xr);
+ const int xa_high = TQMIN (mata->radius, matb->radius - xr);
+ double val = 0.0;
+
+ for (ya = ya_low; ya <= ya_high; ya++)
+ {
+ for (xa = xa_low; xa <= xa_high; xa++)
+ {
+ val += c_mat_elt (mata, xa, ya) *
+ c_mat_elt (matb, xr + xa, yr + ya);
+ }
+ }
+
+ *c_mat_eltptr (result, xr, yr) = val;
+ }
+ }
+}
+
+void RefocusMatrix::convolve_mat_fun (CMat * result, const CMat * const mata, double (f) (int, int))
+{
+ int xr, yr, xa, ya;
+
+ for (yr = -result->radius; yr <= result->radius; yr++)
+ {
+ for (xr = -result->radius; xr <= result->radius; xr++)
+ {
+ double val = 0.0;
+
+ for (ya = -mata->radius; ya <= mata->radius; ya++)
+ {
+ for (xa = -mata->radius; xa <= mata->radius; xa++)
+ {
+ val += c_mat_elt (mata, xa, ya) * f (xr - xa, yr - ya);
+ }
+ }
+
+ *c_mat_eltptr (result, xr, yr) = val;
+ }
+ }
+}
+
+int RefocusMatrix::as_idx (const int k, const int l, const int m)
+{
+ return ((k + m) * (2 * m + 1) + (l + m));
+}
+
+int RefocusMatrix::as_cidx (const int k, const int l)
+{
+ const int a = TQMAX (TQABS (k), TQABS (l));
+ const int b = TQMIN (TQABS (k), TQABS (l));
+ return ((a * (a + 1)) / 2 + b);
+}
+
+void RefocusMatrix::print_c_mat (const CMat * const mat)
+{
+ int x, y;
+
+ for (y = -mat->radius; y <= mat->radius; y++)
+ {
+ TQString output, num;
+
+ for (x = -mat->radius; x <= mat->radius; x++)
+ {
+ output.append( num.setNum( c_mat_elt (mat, x, y) ) );
+ }
+
+ DDebug() << output << endl;
+ }
+}
+
+void RefocusMatrix::print_matrix (Mat * matrix)
+{
+ int col_idx, row_idx;
+
+ for (row_idx = 0; row_idx < matrix->rows; row_idx++)
+ {
+ TQString output, num;
+
+ for (col_idx = 0; col_idx < matrix->cols; col_idx++)
+ {
+ output.append( num.setNum( mat_elt (matrix, row_idx, col_idx) ) );
+ }
+
+ DDebug() << output << endl;
+ }
+}
+
+Mat *RefocusMatrix::make_s_matrix (CMat * mat, int m, double noise_factor)
+{
+ const int mat_size = SQR (2 * m + 1);
+ Mat *result = allocate_matrix (mat_size, mat_size);
+ int yr, yc, xr, xc;
+
+ for (yr = -m; yr <= m; yr++)
+ {
+ for (xr = -m; xr <= m; xr++)
+ {
+ for (yc = -m; yc <= m; yc++)
+ {
+ for (xc = -m; xc <= m; xc++)
+ {
+ *mat_eltptr (result, as_idx (xr, yr, m),
+ as_idx (xc, yc, m)) =
+ c_mat_elt (mat, xr - xc, yr - yc);
+ if ((xr == xc) && (yr == yc))
+ {
+ *mat_eltptr (result, as_idx (xr, yr, m),
+ as_idx (xc, yc, m)) += noise_factor;
+ }
+ }
+ }
+ }
+ }
+
+ return (result);
+}
+
+Mat *RefocusMatrix::make_s_cmatrix (CMat * mat, int m, double noise_factor)
+{
+ const int mat_size = as_cidx (m + 1, 0);
+ Mat *result = allocate_matrix (mat_size, mat_size);
+ int yr, yc, xr, xc;
+
+ for (yr = 0; yr <= m; yr++)
+ {
+ for (xr = 0; xr <= yr; xr++)
+ {
+ for (yc = -m; yc <= m; yc++)
+ {
+ for (xc = -m; xc <= m; xc++)
+ {
+ *mat_eltptr (result, as_cidx (xr, yr), as_cidx (xc, yc)) +=
+ c_mat_elt (mat, xr - xc, yr - yc);
+ if ((xr == xc) && (yr == yc))
+ {
+ *mat_eltptr (result, as_cidx (xr, yr),
+ as_cidx (xc, yc)) += noise_factor;
+ }
+ }
+ }
+ }
+ }
+
+ return (result);
+}
+
+double RefocusMatrix::correlation (const int x, const int y, const double gamma, const double musq)
+{
+ return (musq + pow (gamma, sqrt (SQR (x) + SQR (y))));
+}
+
+Mat *RefocusMatrix::copy_vec (const CMat * const mat, const int m)
+{
+ Mat *result = allocate_matrix (SQR (2 * m + 1), 1);
+ int x, y, index = 0;
+
+ for (y = -m; y <= m; y++)
+ {
+ for (x = -m; x <= m; x++)
+ {
+ *mat_eltptr (result, index, 0) = c_mat_elt (mat, x, y);
+ index++;
+ }
+ }
+
+ Q_ASSERT (index == SQR (2 * m + 1));
+ return (result);
+}
+
+Mat *RefocusMatrix::copy_cvec (const CMat * const mat, const int m)
+{
+ Mat *result = allocate_matrix (as_cidx (m + 1, 0), 1);
+ int x, y, index = 0;
+
+ for (y = 0; y <= m; y++)
+ {
+ for (x = 0; x <= y; x++)
+ {
+ *mat_eltptr (result, index, 0) = c_mat_elt (mat, x, y);
+ index++;
+ }
+ }
+
+ Q_ASSERT (index == as_cidx (m + 1, 0));
+ return (result);
+}
+
+CMat *RefocusMatrix::copy_cvec2mat (const Mat * const cvec, const int m)
+{
+ CMat *result = allocate_c_mat (m);
+ int x, y;
+
+ for (y = -m; y <= m; y++)
+ {
+ for (x = -m; x <= m; x++)
+ {
+ *c_mat_eltptr (result, x, y) = mat_elt (cvec, as_cidx (x, y), 0);
+ }
+ }
+
+ return (result);
+}
+
+CMat *RefocusMatrix::copy_vec2mat (const Mat * const cvec, const int m)
+{
+ CMat *result = allocate_c_mat (m);
+ int x, y;
+
+ for (y = -m; y <= m; y++)
+ {
+ for (x = -m; x <= m; x++)
+ {
+ *c_mat_eltptr (result, x, y) = mat_elt (cvec, as_idx (x, y, m), 0);
+ }
+ }
+
+ return (result);
+}
+
+CMat *RefocusMatrix::compute_g (const CMat * const convolution, const int m, const double gamma,
+ const double noise_factor, const double musq, const bool symmetric)
+{
+ CMat h_conv_ruv, a, corr;
+ CMat *result;
+ Mat *b;
+ Mat *s;
+ int status;
+
+ init_c_mat (&h_conv_ruv, 3 * m);
+ fill_matrix2 (&corr, 4 * m, correlation, gamma, musq);
+ convolve_mat (&h_conv_ruv, convolution, &corr);
+ init_c_mat (&a, 2 * m);
+ convolve_star_mat (&a, convolution, &h_conv_ruv);
+
+ if (symmetric)
+ {
+ s = make_s_cmatrix (&a, m, noise_factor);
+ b = copy_cvec (&h_conv_ruv, m);
+ }
+ else
+ {
+ s = make_s_matrix (&a, m, noise_factor);
+ b = copy_vec (&h_conv_ruv, m);
+ }
+
+#ifdef RF_DEBUG
+ DDebug() << "Convolution:" << endl;
+ print_c_mat (convolution);
+ DDebug() << "h_conv_ruv:" << endl;
+ print_c_mat (&h_conv_ruv);
+ DDebug() << "Value of s:" << endl;
+ print_matrix (s);
+#endif
+
+ Q_ASSERT (s->cols == s->rows);
+ Q_ASSERT (s->rows == b->rows);
+ status = dgesv (s->rows, 1, s->data, s->rows, b->data, b->rows);
+
+ if (symmetric)
+ {
+ result = copy_cvec2mat (b, m);
+ }
+ else
+ {
+ result = copy_vec2mat (b, m);
+ }
+
+#ifdef RF_DEBUG
+ DDebug() << "Deconvolution:" << endl;
+ print_c_mat (result);
+#endif
+
+ finish_c_mat (&a);
+ finish_c_mat (&h_conv_ruv);
+ finish_c_mat (&corr);
+ finish_and_free_matrix (s);
+ finish_and_free_matrix (b);
+ return (result);
+}
+
+CMat *RefocusMatrix::compute_g_matrix (const CMat * const convolution, const int m,
+ const double gamma, const double noise_factor,
+ const double musq, const bool symmetric)
+{
+#ifdef RF_DEBUG
+ DDebug() << "matrix size: " << m << endl;
+ DDebug() << "correlation: " << gamma << endl;
+ DDebug() << "noise: " << noise_factor << endl;
+#endif
+
+ CMat *g = compute_g (convolution, m, gamma, noise_factor, musq, symmetric);
+ int r, c;
+ double sum = 0.0;
+
+ /* Determine sum of array */
+ for (r = -g->radius; r <= g->radius; r++)
+ {
+ for (c = -g->radius; c <= g->radius; c++)
+ {
+ sum += c_mat_elt (g, r, c);
+ }
+ }
+
+ for (r = -g->radius; r <= g->radius; r++)
+ {
+ for (c = -g->radius; c <= g->radius; c++)
+ {
+ *c_mat_eltptr (g, r, c) /= sum;
+ }
+ }
+
+ return (g);
+}
+
+void RefocusMatrix::fill_matrix (CMat * matrix, const int m,
+ double f (const int, const int, const double),
+ const double fun_arg)
+{
+ int x, y;
+ init_c_mat (matrix, m);
+
+ for (y = -m; y <= m; y++)
+ {
+ for (x = -m; x <= m; x++)
+ {
+ *c_mat_eltptr (matrix, x, y) = f (x, y, fun_arg);
+ }
+ }
+}
+
+void RefocusMatrix::fill_matrix2 (CMat * matrix, const int m,
+ double f (const int, const int, const double, const double),
+ const double fun_arg1, const double fun_arg2)
+{
+ int x, y;
+ init_c_mat (matrix, m);
+
+ for (y = -m; y <= m; y++)
+ {
+ for (x = -m; x <= m; x++)
+ {
+ *c_mat_eltptr (matrix, x, y) = f (x, y, fun_arg1, fun_arg2);
+ }
+ }
+}
+
+void RefocusMatrix::make_gaussian_convolution (const double gradius, CMat * convolution, const int m)
+{
+ int x, y;
+
+#ifdef RF_DEBUG
+ DDebug() << "gauss: " << gradius << endl;
+#endif
+
+ init_c_mat (convolution, m);
+
+ if (SQR (gradius) <= 1 / 3.40282347e38F)
+ {
+ for (y = -m; y <= m; y++)
+ {
+ for (x = -m; x <= m; x++)
+ {
+ *c_mat_eltptr (convolution, x, y) = 0;
+ }
+ }
+
+ *c_mat_eltptr (convolution, 0, 0) = 1;
+ }
+ else
+ {
+ const double alpha = log (2.0) / SQR (gradius);
+
+ for (y = -m; y <= m; y++)
+ {
+ for (x = -m; x <= m; x++)
+ {
+ *c_mat_eltptr (convolution, x, y) =
+ exp (-alpha * (SQR (x) + SQR (y)));
+ }
+ }
+ }
+}
+
+/** Return the integral of sqrt(radius^2 - z^2) for z = 0 to x. */
+
+double RefocusMatrix::circle_integral (const double x, const double radius)
+{
+ if (radius == 0)
+ {
+ // Perhaps some epsilon must be added here.
+ return (0);
+ }
+ else
+ {
+ const double sin = x / radius;
+ const double sq_diff = SQR (radius) - SQR (x);
+ // From a mathematical point of view the following is redundant.
+ // Numerically they are not equivalent!
+
+ if ((sq_diff < 0.0) || (sin < -1.0) || (sin > 1.0))
+ {
+ if (sin < 0)
+ {
+ return (-0.25 * SQR (radius) * M_PI);
+ }
+ else
+ {
+ return (0.25 * SQR (radius) * M_PI);
+ }
+ }
+ else
+ {
+ return (0.5 * x * sqrt (sq_diff) + 0.5 * SQR (radius) * asin (sin));
+ }
+ }
+}
+
+double RefocusMatrix::circle_intensity (const int x, const int y, const double radius)
+{
+ if (radius == 0)
+ {
+ return (((x == 0) && (y == 0)) ? 1 : 0);
+ }
+ else
+ {
+ double xlo = TQABS (x) - 0.5, xhi = TQABS (x) + 0.5,
+ ylo = TQABS (y) - 0.5, yhi = TQABS (y) + 0.5;
+ double symmetry_factor = 1, xc1, xc2;
+
+ if (xlo < 0)
+ {
+ xlo = 0;
+ symmetry_factor *= 2;
+ }
+
+ if (ylo < 0)
+ {
+ ylo = 0;
+ symmetry_factor *= 2;
+ }
+
+ if (SQR (xlo) + SQR (yhi) > SQR (radius))
+ {
+ xc1 = xlo;
+ }
+ else if (SQR (xhi) + SQR (yhi) > SQR (radius))
+ {
+ xc1 = sqrt (SQR (radius) - SQR (yhi));
+ }
+ else
+ {
+ xc1 = xhi;
+ }
+
+ if (SQR (xlo) + SQR (ylo) > SQR (radius))
+ {
+ xc2 = xlo;
+ }
+ else if (SQR (xhi) + SQR (ylo) > SQR (radius))
+ {
+ xc2 = sqrt (SQR (radius) - SQR (ylo));
+ }
+ else
+ {
+ xc2 = xhi;
+ }
+
+ return (((yhi - ylo) * (xc1 - xlo) +
+ circle_integral (xc2, radius) - circle_integral (xc1, radius) -
+ (xc2 - xc1) * ylo) * symmetry_factor / (M_PI * SQR (radius)));
+ }
+}
+
+void RefocusMatrix::make_circle_convolution (const double radius, CMat * convolution, const int m)
+{
+#ifdef RF_DEBUG
+ DDebug() << "radius: " << radius << endl;
+#endif
+
+ fill_matrix (convolution, m, circle_intensity, radius);
+}
+
+int RefocusMatrix::dgesv (const int N, const int NRHS, double *A, const int lda, double *B, const int ldb)
+{
+ int result = 0;
+ integer i_N = N, i_NHRS = NRHS, i_lda = lda, i_ldb = ldb, info;
+ integer *ipiv = new integer[N];
+
+ // Clapack call.
+ dgesv_ (&i_N, &i_NHRS, A, &i_lda, ipiv, B, &i_ldb, &info);
+
+ delete [] ipiv;
+ result = info;
+ return (result);
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/matrix.h b/src/imageplugins/coreplugin/sharpnesseditor/matrix.h
new file mode 100644
index 00000000..6b2f65cb
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/matrix.h
@@ -0,0 +1,129 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-29
+ * Description : refocus deconvolution matrix implementation.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MATRIX_H_INCLUDED
+#define MATRIX_H_INCLUDED
+
+// C ++ includes.
+
+#include <cstdio>
+
+namespace DigikamImagesPluginCore
+{
+
+/**
+* CMat:
+* @radius: Radius of the matrix.
+*
+* Centered matrix. This is a square matrix where
+* the indices range from [-radius, radius].
+* The matrix contains (2 * radius + 1) ** 2 elements.
+*
+**/
+typedef struct
+{
+ int radius; // Radius of the matrix
+ int row_stride; // Size of one row = 2 * radius + 1
+ double *data; // Contents of matrix
+ double *center; // Points to element with index (0, 0)
+}
+CMat;
+
+/**
+* Mat:
+* @rows: Number of rows in the matrix.
+*
+* Normal matrix type. Indices range from
+* [0, rows -1 ] and [0, cols - 1].
+*
+**/
+typedef struct
+{
+ int rows; // Number of rows in the matrix
+ int cols; // Number of columns in the matrix
+ double *data; // Content of the matrix
+}
+Mat;
+
+class RefocusMatrix
+{
+
+public:
+
+ static void fill_matrix (CMat * matrix, const int m, double f (int, int, double), const double fun_arg);
+
+ static void fill_matrix2 (CMat * matrix, const int m,
+ double f (const int, const int, const double, const double),
+ const double fun_arg1, const double fun_arg2);
+
+ static void make_circle_convolution (const double radius, CMat *convolution, const int m);
+
+ static void make_gaussian_convolution (const double alpha, CMat *convolution, const int m);
+
+ static void convolve_star_mat (CMat *result, const CMat *const mata, const CMat* const matb);
+
+ static CMat *compute_g_matrix (const CMat * const convolution, const int m,
+ const double gamma, const double noise_factor,
+ const double musq, const bool symmetric);
+
+ static void finish_matrix (Mat * mat);
+ static void finish_and_free_matrix (Mat * mat);
+ static void init_c_mat (CMat * mat, const int radius);
+ static void finish_c_mat (CMat * mat);
+
+private:
+
+ // Debug methods.
+ static void print_c_mat (const CMat * const mat);
+ static void print_matrix (Mat * matrix);
+
+ static Mat *allocate_matrix (int nrows, int ncols);
+ static double *mat_eltptr (Mat * mat, const int r, const int c);
+ static double mat_elt (const Mat * mat, const int r, const int c);
+ static CMat *allocate_c_mat (const int radius);
+ static inline double *c_mat_eltptr (CMat * mat, const int col, const int row);
+ static inline double c_mat_elt (const CMat * const mat, const int col, const int row);
+ static void convolve_mat (CMat * result, const CMat * const mata, const CMat * const matb);
+ static void convolve_mat_fun (CMat * result, const CMat * const mata, double (f) (int, int));
+ static int as_idx (const int k, const int l, const int m);
+ static int as_cidx (const int k, const int l);
+ static Mat *make_s_matrix (CMat * mat, int m, double noise_factor);
+ static Mat *make_s_cmatrix (CMat * mat, int m, double noise_factor);
+ static double correlation (const int x, const int y, const double gamma, const double musq);
+ static Mat *copy_vec (const CMat * const mat, const int m);
+ static Mat *copy_cvec (const CMat * const mat, const int m);
+ static CMat *copy_cvec2mat (const Mat * const cvec, const int m);
+ static CMat *copy_vec2mat (const Mat * const cvec, const int m);
+ static CMat *compute_g (const CMat * const convolution, const int m, const double gamma,
+ const double noise_factor, const double musq, const bool symmetric);
+ static double circle_integral (const double x, const double radius);
+ static double circle_intensity (const int x, const int y, const double radius);
+
+ // CLapack interface.
+ static int dgesv (const int N, const int NRHS, double *A, const int lda, double *B, const int ldb);
+
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* MATRIX_H_INCLUDED */
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/refocus.cpp b/src/imageplugins/coreplugin/sharpnesseditor/refocus.cpp
new file mode 100644
index 00000000..7e99d663
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/refocus.cpp
@@ -0,0 +1,199 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Refocus threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dcolor.h"
+#include "dimgimagefilters.h"
+#include "matrix.h"
+#include "refocus.h"
+
+namespace DigikamImagesPluginCore
+{
+
+Refocus::Refocus(Digikam::DImg *orgImage, TQObject *parent, int matrixSize, double radius,
+ double gauss, double correlation, double noise)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "Refocus")
+{
+ m_matrixSize = matrixSize;
+ m_radius = radius;
+ m_gauss = gauss;
+ m_correlation = correlation;
+ m_noise = noise;
+ initFilter();
+}
+
+void Refocus::filterImage(void)
+{
+ refocusImage(m_orgImage.bits(), m_orgImage.width(), m_orgImage.height(),
+ m_orgImage.sixteenBit(), m_matrixSize, m_radius, m_gauss,
+ m_correlation, m_noise);
+}
+
+void Refocus::refocusImage(uchar* data, int width, int height, bool sixteenBit,
+ int matrixSize, double radius, double gauss,
+ double correlation, double noise)
+{
+ CMat *matrix=0;
+
+ // Compute matrix
+ DDebug() << "Refocus::Compute matrix..." << endl;
+
+ CMat circle, gaussian, convolution;
+
+ RefocusMatrix::make_gaussian_convolution (gauss, &gaussian, matrixSize);
+ RefocusMatrix::make_circle_convolution (radius, &circle, matrixSize);
+ RefocusMatrix::init_c_mat (&convolution, matrixSize);
+ RefocusMatrix::convolve_star_mat (&convolution, &gaussian, &circle);
+
+ matrix = RefocusMatrix::compute_g_matrix (&convolution, matrixSize, correlation, noise, 0.0, true);
+
+ RefocusMatrix::finish_c_mat (&convolution);
+ RefocusMatrix::finish_c_mat (&gaussian);
+ RefocusMatrix::finish_c_mat (&circle);
+
+ // Apply deconvolution kernel to image.
+ DDebug() << "Refocus::Apply Matrix to image..." << endl;
+ convolveImage(data, m_destImage.bits(), width, height, sixteenBit,
+ matrix->data, 2 * matrixSize + 1);
+
+ // Clean up memory
+ delete matrix;
+}
+
+void Refocus::convolveImage(uchar *orgData, uchar *destData, int width, int height,
+ bool sixteenBit, const double *const matrix, int mat_size)
+{
+ int progress;
+ unsigned short *orgData16 = (unsigned short *)orgData;
+ unsigned short *destData16 = (unsigned short *)destData;
+
+ double valRed, valGreen, valBlue;
+ int x1, y1, x2, y2, index1, index2;
+
+ const int imageSize = width*height;
+ const int mat_offset = mat_size / 2;
+
+ for (y1 = 0; !m_cancel && (y1 < height); y1++)
+ {
+ for (x1 = 0; !m_cancel && (x1 < width); x1++)
+ {
+ valRed = valGreen = valBlue = 0.0;
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue;
+ uchar *ptr;
+
+ for (y2 = 0; !m_cancel && (y2 < mat_size); y2++)
+ {
+ for (x2 = 0; !m_cancel && (x2 < mat_size); x2++)
+ {
+ index1 = width * (y1 + y2 - mat_offset) +
+ x1 + x2 - mat_offset;
+
+ if ( index1 >= 0 && index1 < imageSize )
+ {
+ ptr = &orgData[index1*4];
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ const double matrixValue = matrix[y2 * mat_size + x2];
+ valRed += matrixValue * red;
+ valGreen += matrixValue * green;
+ valBlue += matrixValue * blue;
+ }
+ }
+ }
+
+ index2 = y1 * width + x1;
+
+ if (index2 >= 0 && index2 < imageSize)
+ {
+ // To get Alpha channel value from original (unchanged)
+ memcpy (&destData[index2*4], &orgData[index2*4], 4);
+ ptr = &destData[index2*4];
+
+ // Overwrite RGB values to destination.
+ ptr[0] = (uchar) CLAMP (valBlue, 0, 255);
+ ptr[1] = (uchar) CLAMP (valGreen, 0, 255);
+ ptr[2] = (uchar) CLAMP (valRed, 0, 255);
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue;
+ unsigned short *ptr;
+
+ for (y2 = 0; !m_cancel && (y2 < mat_size); y2++)
+ {
+ for (x2 = 0; !m_cancel && (x2 < mat_size); x2++)
+ {
+ index1 = width * (y1 + y2 - mat_offset) +
+ x1 + x2 - mat_offset;
+
+ if ( index1 >= 0 && index1 < imageSize )
+ {
+ ptr = &orgData16[index1*4];
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ const double matrixValue = matrix[y2 * mat_size + x2];
+ valRed += matrixValue * red;
+ valGreen += matrixValue * green;
+ valBlue += matrixValue * blue;
+ }
+ }
+ }
+
+ index2 = y1 * width + x1;
+
+ if (index2 >= 0 && index2 < imageSize)
+ {
+ // To get Alpha channel value from original (unchanged)
+ memcpy (&destData16[index2*4], &orgData16[index2*4], 8);
+ ptr = &destData16[index2*4];
+
+ // Overwrite RGB values to destination.
+ ptr[0] = (unsigned short) CLAMP (valBlue, 0, 65535);
+ ptr[1] = (unsigned short) CLAMP (valGreen, 0, 65535);
+ ptr[2] = (unsigned short) CLAMP (valRed, 0, 65535);
+ }
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int)(((double)y1 * 100.0) / height);
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+}
+
+} // NameSpace DigikamImagesPluginCore
+
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/refocus.h b/src/imageplugins/coreplugin/sharpnesseditor/refocus.h
new file mode 100644
index 00000000..323b24b9
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/refocus.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Refocus threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef REFOCUS_H
+#define REFOCUS_H
+
+// Local includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamImagesPluginCore
+{
+
+class Refocus : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ Refocus(Digikam::DImg *orgImage, TQObject *parent=0, int matrixSize=5, double radius=0.9,
+ double gauss=0.0, double correlation=0.5, double noise=0.01);
+
+ ~Refocus(){};
+
+private: // Refocus filter methods.
+
+ virtual void filterImage(void);
+
+ void refocusImage(uchar* data, int width, int height, bool sixteenBit,
+ int matrixSize, double radius, double gauss,
+ double correlation, double noise);
+
+ void convolveImage(uchar *orgData, uchar *destData, int width, int height,
+ bool sixteenBit, const double *const matrix, int mat_size);
+
+private: // Refocus filter data.
+
+ int m_matrixSize;
+
+ double m_radius;
+ double m_gauss;
+ double m_correlation;
+ double m_noise;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* REFOCUS_H */
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/sharpentool.cpp b/src/imageplugins/coreplugin/sharpnesseditor/sharpentool.cpp
new file mode 100644
index 00000000..aee5d841
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/sharpentool.cpp
@@ -0,0 +1,741 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to sharp an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define MAX_MATRIX_SIZE 25
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqwidgetstack.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <kseparator.h>
+#include <tdeconfig.h>
+#include <kurl.h>
+#include <kiconloader.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdemessagebox.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "dimgsharpen.h"
+#include "unsharp.h"
+#include "refocus.h"
+#include "sharpentool.h"
+#include "sharpentool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamImagesPluginCore
+{
+
+SharpenTool::SharpenTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("sharpen");
+ setToolName(i18n("Sharpen"));
+ setToolIcon(SmallIcon("sharpenimage"));
+ setToolHelp("blursharpentool.anchor");
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 3, 1);
+
+ TQLabel *label1 = new TQLabel(i18n("Method:"), m_gboxSettings->plainPage());
+
+ m_sharpMethod = new RComboBox(m_gboxSettings->plainPage());
+ m_sharpMethod->insertItem( i18n("Simple sharp") );
+ m_sharpMethod->insertItem( i18n("Unsharp mask") );
+ m_sharpMethod->insertItem( i18n("Refocus") );
+ m_sharpMethod->setDefaultItem(SimpleSharp);
+ TQWhatsThis::add( m_sharpMethod, i18n("<p>Select the sharpening method to apply to the image."));
+
+ m_stack = new TQWidgetStack(m_gboxSettings->plainPage());
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 0);
+ grid->addMultiCellWidget(m_sharpMethod, 0, 0, 1, 1);
+ grid->addMultiCellWidget(new KSeparator(m_gboxSettings->plainPage()), 1, 1, 0, 1);
+ grid->addMultiCellWidget(m_stack, 2, 2, 0, 1);
+ grid->setRowStretch(3, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ // -------------------------------------------------------------
+
+ TQWidget *simpleSharpSettings = new TQWidget(m_stack);
+ TQGridLayout* grid1 = new TQGridLayout( simpleSharpSettings, 2, 1);
+
+ TQLabel *label = new TQLabel(i18n("Sharpness:"), simpleSharpSettings);
+ m_radiusInput = new RIntNumInput(simpleSharpSettings);
+ m_radiusInput->setRange(0, 100, 1);
+ m_radiusInput->setDefaultValue(0);
+ TQWhatsThis::add( m_radiusInput, i18n("<p>A sharpness of 0 has no effect, "
+ "1 and above determine the sharpen matrix radius "
+ "that determines how much to sharpen the image."));
+
+ grid1->addMultiCellWidget(label, 0, 0, 0, 1);
+ grid1->addMultiCellWidget(m_radiusInput, 1, 1, 0, 1);
+ grid1->setRowStretch(2, 10);
+ grid1->setMargin(0);
+ grid1->setSpacing(0);
+
+ m_stack->addWidget(simpleSharpSettings, SimpleSharp);
+
+ // -------------------------------------------------------------
+
+ TQWidget *unsharpMaskSettings = new TQWidget(m_stack);
+ TQGridLayout* grid2 = new TQGridLayout( unsharpMaskSettings, 6, 1);
+
+ TQLabel *label2 = new TQLabel(i18n("Radius:"), unsharpMaskSettings);
+ m_radiusInput2 = new RIntNumInput(unsharpMaskSettings);
+ m_radiusInput2->setRange(1, 120, 1);
+ m_radiusInput2->setDefaultValue(1);
+ TQWhatsThis::add( m_radiusInput2, i18n("<p>Radius value is the gaussian blur matrix radius value "
+ "used to determines how much to blur the image.") );
+
+ TQLabel *label3 = new TQLabel(i18n("Amount:"), unsharpMaskSettings);
+ m_amountInput = new RDoubleNumInput(unsharpMaskSettings);
+ m_amountInput->setPrecision(1);
+ m_amountInput->setRange(0.0, 5.0, 0.1);
+ m_amountInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_amountInput, i18n("<p>The value of the difference between the "
+ "original and the blur image that is added back into the original.") );
+
+ TQLabel *label4 = new TQLabel(i18n("Threshold:"), unsharpMaskSettings);
+ m_thresholdInput = new RDoubleNumInput(unsharpMaskSettings);
+ m_thresholdInput->setPrecision(2);
+ m_thresholdInput->setRange(0.0, 1.0, 0.01);
+ m_thresholdInput->setDefaultValue(0.05);
+ TQWhatsThis::add( m_thresholdInput, i18n("<p>The threshold, as a fraction of the maximum "
+ "luminosity value, needed to apply the difference amount.") );
+
+ grid2->addMultiCellWidget(label2, 0, 0, 0, 1);
+ grid2->addMultiCellWidget(m_radiusInput2, 1, 1, 0, 1);
+ grid2->addMultiCellWidget(label3, 2, 2, 0, 1);
+ grid2->addMultiCellWidget(m_amountInput, 3, 3, 0, 1);
+ grid2->addMultiCellWidget(label4, 4, 4, 0, 1);
+ grid2->addMultiCellWidget(m_thresholdInput, 5, 5, 0, 1);
+ grid2->setRowStretch(6, 10);
+ grid2->setMargin(0);
+ grid2->setSpacing(0);
+
+ m_stack->addWidget(unsharpMaskSettings, UnsharpMask);
+
+ // -------------------------------------------------------------
+
+ TQWidget *refocusSettings = new TQWidget(m_stack);
+ TQGridLayout* grid3 = new TQGridLayout(refocusSettings, 10, 1);
+
+ TQLabel *label5 = new TQLabel(i18n("Circular sharpness:"), refocusSettings);
+ m_radius = new RDoubleNumInput(refocusSettings);
+ m_radius->setPrecision(2);
+ m_radius->setRange(0.0, 5.0, 0.01);
+ m_radius->setDefaultValue(1.0);
+ TQWhatsThis::add( m_radius, i18n("<p>This is the radius of the circular convolution. It is the most important "
+ "parameter for using this plugin. For most images the default value of 1.0 "
+ "should give good results. Select a higher value when your image is very blurred."));
+
+ TQLabel *label6 = new TQLabel(i18n("Correlation:"), refocusSettings);
+ m_correlation = new RDoubleNumInput(refocusSettings);
+ m_correlation->setPrecision(2);
+ m_correlation->setRange(0.0, 1.0, 0.01);
+ m_correlation->setDefaultValue(0.5);
+ TQWhatsThis::add( m_correlation, i18n("<p>Increasing the correlation may help to reduce artifacts. The correlation can "
+ "range from 0-1. Useful values are 0.5 and values close to 1, e.g. 0.95 and 0.99. "
+ "Using a high value for the correlation will reduce the sharpening effect of the "
+ "plugin."));
+
+ TQLabel *label7 = new TQLabel(i18n("Noise filter:"), refocusSettings);
+ m_noise = new RDoubleNumInput(refocusSettings);
+ m_noise->setPrecision(3);
+ m_noise->setRange(0.0, 1.0, 0.001);
+ m_noise->setDefaultValue(0.03);
+ TQWhatsThis::add( m_noise, i18n("<p>Increasing the noise filter parameter may help to reduce artifacts. The noise filter "
+ "can range from 0-1 but values higher than 0.1 are rarely helpful. When the noise filter "
+ "value is too low, e.g. 0.0 the image quality will be very poor. A useful value is 0.01. "
+ "Using a high value for the noise filter will reduce the sharpening "
+ "effect of the plugin."));
+
+ TQLabel *label8 = new TQLabel(i18n("Gaussian sharpness:"), refocusSettings);
+ m_gauss = new RDoubleNumInput(refocusSettings);
+ m_gauss->setPrecision(2);
+ m_gauss->setRange(0.0, 1.0, 0.01);
+ m_gauss->setDefaultValue(0.0);
+ TQWhatsThis::add( m_gauss, i18n("<p>This is the sharpness for the gaussian convolution. Use this parameter when your "
+ "blurring is of a Gaussian type. In most cases you should set this parameter to 0, because "
+ "it causes nasty artifacts. When you use non-zero values, you will probably have to "
+ "increase the correlation and/or noise filter parameters too."));
+
+ TQLabel *label9 = new TQLabel(i18n("Matrix size:"), refocusSettings);
+ m_matrixSize = new RIntNumInput(refocusSettings);
+ m_matrixSize->setRange(0, MAX_MATRIX_SIZE, 1);
+ m_matrixSize->setDefaultValue(5);
+ TQWhatsThis::add( m_matrixSize, i18n("<p>This parameter determines the size of the transformation matrix. "
+ "Increasing the matrix width may give better results, especially when you have "
+ "chosen large values for circular or gaussian sharpness."));
+
+ grid3->addMultiCellWidget(label5, 0, 0, 0, 1);
+ grid3->addMultiCellWidget(m_radius, 1, 1, 0, 1);
+ grid3->addMultiCellWidget(label6, 2, 2, 0, 1);
+ grid3->addMultiCellWidget(m_correlation, 3, 3, 0, 1);
+ grid3->addMultiCellWidget(label7, 4, 4, 0, 1);
+ grid3->addMultiCellWidget(m_noise, 5, 5, 0, 1);
+ grid3->addMultiCellWidget(label8, 6, 6, 0, 1);
+ grid3->addMultiCellWidget(m_gauss, 7, 7, 0, 1);
+ grid3->addMultiCellWidget(label9, 8, 8, 0, 1);
+ grid3->addMultiCellWidget(m_matrixSize, 9, 9, 0, 1);
+ grid3->setRowStretch(10, 10);
+ grid3->setMargin(0);
+ grid3->setSpacing(0);
+
+ m_stack->addWidget(refocusSettings, Refocus);
+
+ setToolSettings(m_gboxSettings);
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "sharpen Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_sharpMethod, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotSharpMethodActived(int)));
+
+ // -------------------------------------------------------------
+
+ // Image creation with dummy borders (mosaic mode) used by Refocus method. It needs to do
+ // it before to apply deconvolution filter on original image border pixels including
+ // on matrix size area. This way limit artifacts on image border.
+
+ ImageIface iface(0, 0);
+
+ uchar* data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sb = iface.originalSixteenBit();
+ bool a = iface.originalHasAlpha();
+
+ m_img = DImg( w + 4*MAX_MATRIX_SIZE, h + 4*MAX_MATRIX_SIZE, sb, a);
+
+ DImg tmp;
+ DImg org(w, h, sb, a, data);
+
+ // Copy original.
+ m_img.bitBltImage(&org, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+
+ // Create dummy top border
+ tmp = org.copy(0, 0, w, 2*MAX_MATRIX_SIZE);
+ tmp.flip(DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 2*MAX_MATRIX_SIZE, 0);
+
+ // Create dummy bottom border
+ tmp = org.copy(0, h-2*MAX_MATRIX_SIZE, w, 2*MAX_MATRIX_SIZE);
+ tmp.flip(DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE+h);
+
+ // Create dummy left border
+ tmp = org.copy(0, 0, 2*MAX_MATRIX_SIZE, h);
+ tmp.flip(DImg::HORIZONTAL);
+ m_img.bitBltImage(&tmp, 0, 2*MAX_MATRIX_SIZE);
+
+ // Create dummy right border
+ tmp = org.copy(w-2*MAX_MATRIX_SIZE, 0, 2*MAX_MATRIX_SIZE, h);
+ tmp.flip(DImg::HORIZONTAL);
+ m_img.bitBltImage(&tmp, w+2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+
+ // Create dummy top/left corner
+ tmp = org.copy(0, 0, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(DImg::HORIZONTAL);
+ tmp.flip(DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 0, 0);
+
+ // Create dummy top/right corner
+ tmp = org.copy(w-2*MAX_MATRIX_SIZE, 0, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(DImg::HORIZONTAL);
+ tmp.flip(DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, w+2*MAX_MATRIX_SIZE, 0);
+
+ // Create dummy bottom/left corner
+ tmp = org.copy(0, h-2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(DImg::HORIZONTAL);
+ tmp.flip(DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, 0, h+2*MAX_MATRIX_SIZE);
+
+ // Create dummy bottom/right corner
+ tmp = org.copy(w-2*MAX_MATRIX_SIZE, h-2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ tmp.flip(DImg::HORIZONTAL);
+ tmp.flip(DImg::VERTICAL);
+ m_img.bitBltImage(&tmp, w+2*MAX_MATRIX_SIZE, h+2*MAX_MATRIX_SIZE);
+
+ delete [] data;
+}
+
+SharpenTool::~SharpenTool()
+{
+}
+
+void SharpenTool::renderingFinished()
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->setEnabled(true);
+ m_gboxSettings->enableButton(EditorToolSettings::Load, false);
+ m_gboxSettings->enableButton(EditorToolSettings::SaveAs, false);
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->setEnabled(true);
+ m_amountInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ m_gboxSettings->enableButton(EditorToolSettings::Load, false);
+ m_gboxSettings->enableButton(EditorToolSettings::SaveAs, false);
+ break;
+ }
+
+ case Refocus:
+ {
+ m_matrixSize->setEnabled(true);
+ m_radius->setEnabled(true);
+ m_gauss->setEnabled(true);
+ m_correlation->setEnabled(true);
+ m_noise->setEnabled(true);
+ break;
+ }
+ }
+}
+
+void SharpenTool::slotSharpMethodActived(int w)
+{
+ m_stack->raiseWidget(w);
+ if (w == Refocus)
+ {
+ m_gboxSettings->enableButton(EditorToolSettings::Load, true);
+ m_gboxSettings->enableButton(EditorToolSettings::SaveAs, true);
+ }
+ else
+ {
+ m_gboxSettings->enableButton(EditorToolSettings::Load, false);
+ m_gboxSettings->enableButton(EditorToolSettings::SaveAs, false);
+ }
+}
+
+void SharpenTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("sharpen Tool");
+ m_radiusInput->blockSignals(true);
+ m_radiusInput2->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_thresholdInput->blockSignals(true);
+ m_matrixSize->blockSignals(true);
+ m_radius->blockSignals(true);
+ m_gauss->blockSignals(true);
+ m_correlation->blockSignals(true);
+ m_noise->blockSignals(true);
+ m_sharpMethod->blockSignals(true);
+
+ m_radiusInput->setValue(config->readNumEntry("SimpleSharpRadiusAjustment", m_radiusInput->defaultValue()));
+ m_radiusInput2->setValue(config->readNumEntry("UnsharpMaskRadiusAjustment", m_radiusInput2->defaultValue()));
+ m_amountInput->setValue(config->readDoubleNumEntry("UnsharpMaskAmountAjustment", m_amountInput->defaultValue()));
+ m_thresholdInput->setValue(config->readDoubleNumEntry("UnsharpMaskThresholdAjustment", m_thresholdInput->defaultValue()));
+ m_matrixSize->setValue(config->readNumEntry("RefocusMatrixSize", m_matrixSize->defaultValue()));
+ m_radius->setValue(config->readDoubleNumEntry("RefocusRadiusAjustment", m_radius->defaultValue()));
+ m_gauss->setValue(config->readDoubleNumEntry("RefocusGaussAjustment", m_gauss->defaultValue()));
+ m_correlation->setValue(config->readDoubleNumEntry("RefocusCorrelationAjustment", m_correlation->defaultValue()));
+ m_noise->setValue(config->readDoubleNumEntry("RefocusNoiseAjustment", m_noise->defaultValue()));
+ m_sharpMethod->setCurrentItem(config->readNumEntry("SharpenMethod", SimpleSharp));
+
+ m_radiusInput->blockSignals(false);
+ m_radiusInput2->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_thresholdInput->blockSignals(false);
+ m_matrixSize->blockSignals(false);
+ m_radius->blockSignals(false);
+ m_gauss->blockSignals(false);
+ m_correlation->blockSignals(false);
+ m_noise->blockSignals(false);
+ m_sharpMethod->blockSignals(false);
+
+ slotSharpMethodActived(m_sharpMethod->currentItem());
+}
+
+void SharpenTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("sharpen Tool");
+ config->writeEntry("SimpleSharpRadiusAjustment", m_radiusInput->value());
+ config->writeEntry("UnsharpMaskRadiusAjustment", m_radiusInput2->value());
+ config->writeEntry("UnsharpMaskAmountAjustment", m_amountInput->value());
+ config->writeEntry("UnsharpMaskThresholdAjustment", m_thresholdInput->value());
+ config->writeEntry("RefocusMatrixSize", m_matrixSize->value());
+ config->writeEntry("RefocusRadiusAjustment", m_radius->value());
+ config->writeEntry("RefocusGaussAjustment", m_gauss->value());
+ config->writeEntry("RefocusCorrelationAjustment", m_correlation->value());
+ config->writeEntry("RefocusNoiseAjustment", m_noise->value());
+ config->writeEntry("SharpenMethod", m_sharpMethod->currentItem());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void SharpenTool::slotResetSettings()
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->blockSignals(true);
+ m_radiusInput->slotReset();
+ m_radiusInput->blockSignals(false);
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_thresholdInput->blockSignals(true);
+
+ m_radiusInput2->slotReset();
+ m_amountInput->slotReset();
+ m_thresholdInput->slotReset();
+
+ m_radiusInput2->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_thresholdInput->blockSignals(false);
+ break;
+ }
+
+ case Refocus:
+ {
+ m_matrixSize->blockSignals(true);
+ m_radius->blockSignals(true);
+ m_gauss->blockSignals(true);
+ m_correlation->blockSignals(true);
+ m_noise->blockSignals(true);
+
+ m_matrixSize->slotReset();
+ m_radius->slotReset();
+ m_gauss->slotReset();
+ m_correlation->slotReset();
+ m_noise->slotReset();
+
+ m_matrixSize->blockSignals(false);
+ m_radius->blockSignals(false);
+ m_gauss->blockSignals(false);
+ m_correlation->blockSignals(false);
+ m_noise->blockSignals(false);
+ break;
+ }
+ }
+}
+
+void SharpenTool::prepareEffect()
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->setEnabled(false);
+
+ DImg img = m_previewWidget->getOriginalRegionImage();
+
+ double radius = m_radiusInput->value()/10.0;
+ double sigma;
+
+ if (radius < 1.0) sigma = radius;
+ else sigma = sqrt(radius);
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DImgSharpen(&img, this, radius, sigma )));
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+
+ DImg img = m_previewWidget->getOriginalRegionImage();
+
+ int r = m_radiusInput2->value();
+ double a = m_amountInput->value();
+ double th = m_thresholdInput->value();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DigikamImagesPluginCore::UnsharpMask(&img, this, r, a, th)));
+ break;
+ }
+
+ case Refocus:
+ {
+ m_matrixSize->setEnabled(false);
+ m_radius->setEnabled(false);
+ m_gauss->setEnabled(false);
+ m_correlation->setEnabled(false);
+ m_noise->setEnabled(false);
+
+ int ms = m_matrixSize->value();
+ double r = m_radius->value();
+ double g = m_gauss->value();
+ double c = m_correlation->value();
+ double n = m_noise->value();
+
+ TQRect area = m_previewWidget->getOriginalImageRegionToRender();
+ TQRect tmpRect;
+ tmpRect.setLeft(area.left()-2*ms);
+ tmpRect.setRight(area.right()+2*ms);
+ tmpRect.setTop(area.top()-2*ms);
+ tmpRect.setBottom(area.bottom()+2*ms);
+ tmpRect.moveBy(2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE);
+ DImg imTemp = m_img.copy(tmpRect);
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DigikamImagesPluginCore::Refocus(&imTemp, this, ms, r, g, c, n)));
+ break;
+ }
+ }
+}
+
+void SharpenTool::prepareFinal()
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ m_radiusInput->setEnabled(false);
+
+ double radius = m_radiusInput->value()/10.0;
+ double sigma;
+
+ if (radius < 1.0) sigma = radius;
+ else sigma = sqrt(radius);
+
+ ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ DImg orgImage = DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DImgSharpen(&orgImage, this, radius, sigma )));
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ m_radiusInput2->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+
+ int r = m_radiusInput2->value();
+ double a = m_amountInput->value();
+ double th = m_thresholdInput->value();
+
+ ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ int w = iface.originalWidth();
+ int h = iface.originalHeight();
+ bool sixteenBit = iface.originalSixteenBit();
+ bool hasAlpha = iface.originalHasAlpha();
+ DImg orgImage = DImg(w, h, sixteenBit, hasAlpha ,data);
+ delete [] data;
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DigikamImagesPluginCore::UnsharpMask(&orgImage, this, r, a, th)));
+ break;
+ }
+
+ case Refocus:
+ {
+
+ m_matrixSize->setEnabled(false);
+ m_radius->setEnabled(false);
+ m_gauss->setEnabled(false);
+ m_correlation->setEnabled(false);
+ m_noise->setEnabled(false);
+
+ int ms = m_matrixSize->value();
+ double r = m_radius->value();
+ double g = m_gauss->value();
+ double c = m_correlation->value();
+ double n = m_noise->value();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new DigikamImagesPluginCore::Refocus(&m_img, this, ms, r, g, c, n)));
+ break;
+ }
+ }
+}
+
+void SharpenTool::putPreviewData()
+{
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ case UnsharpMask:
+ {
+ DImg imDest = filter()->getTargetImage();
+ m_previewWidget->setPreviewImage(imDest);
+ break;
+ }
+
+ case Refocus:
+ {
+ int ms = m_matrixSize->value();
+ TQRect area = m_previewWidget->getOriginalImageRegionToRender();
+
+ DImg imDest = filter()->getTargetImage()
+ .copy(2*ms, 2*ms, area.width(), area.height());
+ m_previewWidget->setPreviewImage(imDest);
+ break;
+ }
+ }
+}
+
+void SharpenTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ DImg imDest = filter()->getTargetImage();
+
+ switch (m_stack->id(m_stack->visibleWidget()))
+ {
+ case SimpleSharp:
+ {
+ iface.putOriginalImage(i18n("Sharpen"), imDest.bits());
+ break;
+ }
+
+ case UnsharpMask:
+ {
+ iface.putOriginalImage(i18n("Unsharp Mask"), imDest.bits());
+ break;
+ }
+
+ case Refocus:
+ {
+ TQRect area = m_previewWidget->getOriginalImageRegionToRender();
+ ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Refocus"), filter()->getTargetImage()
+ .copy(2*MAX_MATRIX_SIZE, 2*MAX_MATRIX_SIZE,
+ iface.originalWidth(),
+ iface.originalHeight())
+ .bits());
+ break;
+ }
+ }
+}
+
+void SharpenTool::slotLoadSettings()
+{
+ KURL loadRestorationFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Refocus Settings File to Load")) );
+ if ( loadRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(loadRestorationFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+ if ( stream.readLine() != "# Photograph Refocus Configuration File" )
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("\"%1\" is not a Photograph Refocus settings text file.")
+ .arg(loadRestorationFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+ m_matrixSize->setValue( stream.readLine().toInt() );
+ m_radius->setValue( stream.readLine().toDouble() );
+ m_gauss->setValue( stream.readLine().toDouble() );
+ m_correlation->setValue( stream.readLine().toDouble() );
+ m_noise->setValue( stream.readLine().toDouble() );
+ blockSignals(false);
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load settings from the Photograph Refocus text file."));
+
+ file.close();
+}
+
+void SharpenTool::slotSaveAsSettings()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Refocus Settings File to Save")) );
+ if ( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# Photograph Refocus Configuration File\n";
+ stream << m_matrixSize->value() << "\n";
+ stream << m_radius->value() << "\n";
+ stream << m_gauss->value() << "\n";
+ stream << m_correlation->value() << "\n";
+ stream << m_noise->value() << "\n";
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save settings to the Photograph Refocus text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/sharpentool.h b/src/imageplugins/coreplugin/sharpnesseditor/sharpentool.h
new file mode 100644
index 00000000..8dbca1c5
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/sharpentool.h
@@ -0,0 +1,111 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tool to sharp an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SHARPENTOOL_H
+#define SHARPENTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQWidgetStack;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RDoubleNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class DImg;
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamImagesPluginCore
+{
+
+class SharpenTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ SharpenTool(TQObject *parent);
+ ~SharpenTool();
+
+private slots:
+
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+ void slotResetSettings();
+ void slotSharpMethodActived(int);
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ enum SharpingMethods
+ {
+ SimpleSharp=0,
+ UnsharpMask,
+ Refocus
+ };
+
+ TQWidgetStack *m_stack;
+
+ KDcrawIface::RComboBox *m_sharpMethod;
+
+ KDcrawIface::RIntNumInput *m_matrixSize;
+ KDcrawIface::RIntNumInput *m_radiusInput;
+ KDcrawIface::RIntNumInput *m_radiusInput2;
+
+ KDcrawIface::RDoubleNumInput *m_radius;
+ KDcrawIface::RDoubleNumInput *m_gauss;
+ KDcrawIface::RDoubleNumInput *m_correlation;
+ KDcrawIface::RDoubleNumInput *m_noise;
+ KDcrawIface::RDoubleNumInput *m_amountInput;
+ KDcrawIface::RDoubleNumInput *m_thresholdInput;
+
+ Digikam::DImg m_img;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* SHARPENTOOL_H */
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/unsharp.cpp b/src/imageplugins/coreplugin/sharpnesseditor/unsharp.cpp
new file mode 100644
index 00000000..fb1b9d21
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/unsharp.cpp
@@ -0,0 +1,127 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Unsharp Mask threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dcolor.h"
+#include "dimgimagefilters.h"
+#include "dimggaussianblur.h"
+#include "unsharp.h"
+
+namespace DigikamImagesPluginCore
+{
+
+UnsharpMask::UnsharpMask(Digikam::DImg *orgImage, TQObject *parent, int radius,
+ double amount, double threshold)
+ : DImgThreadedFilter(orgImage, parent, "UnsharpMask")
+{
+ m_radius = radius;
+ m_amount = amount;
+ m_threshold = threshold;
+ initFilter();
+}
+
+void UnsharpMask::filterImage(void)
+{
+ int progress;
+ int quantum;
+ double quantumThreshold;
+ double value;
+ Digikam::DColor p;
+ Digikam::DColor q;
+
+ if (m_orgImage.isNull())
+ {
+ DWarning() << k_funcinfo << "No image data available!" << endl;
+ return;
+ }
+
+ Digikam::DImgGaussianBlur(this, m_orgImage, m_destImage, 0, 10, (int)(m_radius));
+
+ quantum = m_destImage.sixteenBit() ? 65535 : 255;
+ quantumThreshold = quantum*m_threshold;
+
+ for (uint y = 0 ; !m_cancel && (y < m_destImage.height()) ; y++)
+ {
+ for (uint x = 0 ; !m_cancel && (x < m_destImage.width()) ; x++)
+ {
+ p = m_orgImage.getPixelColor(x, y);
+ q = m_destImage.getPixelColor(x, y);
+
+ // Red channel.
+ value = (double)(p.red())-(double)(q.red());
+
+ if (fabs(2.0*value) < quantumThreshold)
+ value = (double)(p.red());
+ else
+ value = (double)(p.red()) + value*m_amount;
+
+ q.setRed(CLAMP(ROUND(value), 0, quantum));
+
+ // Green Channel.
+ value = (double)(p.green())-(double)(q.green());
+
+ if (fabs(2.0*value) < quantumThreshold)
+ value = (double)(p.green());
+ else
+ value = (double)(p.green()) + value*m_amount;
+
+ q.setGreen(CLAMP(ROUND(value), 0, quantum));
+
+ // Blue Channel.
+ value = (double)(p.blue())-(double)(q.blue());
+
+ if (fabs(2.0*value) < quantumThreshold)
+ value = (double)(p.blue());
+ else
+ value = (double)(p.blue()) + value*m_amount;
+
+ q.setBlue(CLAMP(ROUND(value), 0, quantum));
+
+ // Alpha Channel.
+ value = (double)(p.alpha())-(double)(q.alpha());
+
+ if (fabs(2.0*value) < quantumThreshold)
+ value = (double)(p.alpha());
+ else
+ value = (double)(p.alpha()) + value*m_amount;
+
+ q.setAlpha(CLAMP(ROUND(value), 0, quantum));
+
+ m_destImage.setPixelColor(x, y, q);
+ }
+
+ progress = (int)(10.0 + ((double)y * 90.0) / m_destImage.height());
+ if ( progress%5 == 0 )
+ postProgress( progress );
+ }
+}
+
+} // NameSpace DigikamImagesPluginCore
diff --git a/src/imageplugins/coreplugin/sharpnesseditor/unsharp.h b/src/imageplugins/coreplugin/sharpnesseditor/unsharp.h
new file mode 100644
index 00000000..a1780ab4
--- /dev/null
+++ b/src/imageplugins/coreplugin/sharpnesseditor/unsharp.h
@@ -0,0 +1,58 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Unsharp Mask threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef UNSHARP_MASK_H
+#define UNSHARP_MASK_H
+
+// Local includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamImagesPluginCore
+{
+
+class UnsharpMask : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ UnsharpMask(Digikam::DImg *orgImage, TQObject *parent=0, int radius=1,
+ double amount=1.0, double threshold=0.05);
+
+ ~UnsharpMask(){};
+
+private:
+
+ virtual void filterImage(void);
+
+private:
+
+ int m_radius;
+
+ double m_amount;
+ double m_threshold;
+};
+
+} // NameSpace DigikamImagesPluginCore
+
+#endif /* UNSHARP_MASK_H */
diff --git a/src/imageplugins/distortionfx/Makefile.am b/src/imageplugins/distortionfx/Makefile.am
new file mode 100644
index 00000000..fcb7b74a
--- /dev/null
+++ b/src/imageplugins/distortionfx/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_distortionfx_la_SOURCES = imageplugin_distortionfx.cpp \
+ distortionfxtool.cpp distortionfx.cpp
+
+digikamimageplugin_distortionfx_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_distortionfx_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_distortionfx.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_distortionfx.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_distortionfx_ui.rc
+
diff --git a/src/imageplugins/distortionfx/digikamimageplugin_distortionfx.desktop b/src/imageplugins/distortionfx/digikamimageplugin_distortionfx.desktop
new file mode 100644
index 00000000..8baca4eb
--- /dev/null
+++ b/src/imageplugins/distortionfx/digikamimageplugin_distortionfx.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=ImagePlugin_DistortionFX
+Name[bg]=Приставка за снимки - Изкривяващи ефекти
+Name[da]=Plugin for forvrængningseffekt
+Name[el]=ΠρόσθετοΕικόνας_ΕφέΠαραμόρφωσης
+Name[fi]=Vääristymä
+Name[hr]=Izobličenje
+Name[it]=PluginImmagini_EffettiDiDistorsione
+Name[nl]=Afbeeldingsplugin_Vervormingseffect
+Name[sr]=Ефекти изобличења
+Name[sr@Latn]=Efekti izobličenja
+Name[sv]=Insticksprogram för förvrängningseffekt
+Name[tr]=ResimEklentisi_Bozma
+Name[xx]=xxImagePlugin_DistortionFXxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Distortion special effects plugin for digiKam
+Comment[bg]=Приставка на digiKam с изкривяващи снимките ефекти
+Comment[ca]=Connector pel digiKam d'efectes especials de distorsió
+Comment[da]=Digikam plugin for forvrængningsspecialeffekt
+Comment[de]=digiKam-Modul zum Erzeugen von speziellen Verzerrungseffekten
+Comment[el]=Πρόσθετο ειδικών εφέ παραμόρφωσης για το digiKam
+Comment[es]=Plugin para digiKam con efectos de distorsión especiales
+Comment[et]=DigiKami spetsiaalsete moonutusefektide plugin
+Comment[fa]=وصلۀ جلوه‌های ویژۀ اعواج برای digiKam
+Comment[fi]=Vääristymäerikoistehosteita
+Comment[gl]=Un plugin de digiKam para efeitos especiais de distorsión
+Comment[hr]=digiKam dodatak za efekt izobličavanja
+Comment[is]=Íforrit fyrir digiKam sem afbakar sérstaklega myndir
+Comment[it]=Plugin degli effetti speciali di distorsione per digiKam
+Comment[ja]=digiKam ゆがめ特殊効果プラグイン
+Comment[nds]=digiKam-Moduul för Vertarren-Effekten
+Comment[nl]=Digikam-plugin voor vervormingseffect
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਖਿੰਡਾਉਣ ਖਾਸ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam oferująca efekty zniekształceń
+Comment[pt]=Um 'plugin' do digiKam para efeitos especiais de distorção
+Comment[pt_BR]=Plugin de efeito especial de distorção
+Comment[ru]=Модуль специальных шумовых эффектов для digiKam
+Comment[sk]=digiKam plugin pre špeciálne efekty skreslenia
+Comment[sr]=digiKam-ов прикључак за ефекте изобличења
+Comment[sr@Latn]=digiKam-ov priključak za efekte izobličenja
+Comment[sv]=Digikam insticksprogram för förvrängningsspecialeffekt
+Comment[tr]=Bozma etkileri uygulamak için bir digiKam eklentisi
+Comment[uk]=Втулок спеціальних ефектів спотворення для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng méo mó ảnh cho digiKam
+Comment[xx]=xxDistortion special effects plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_distortionfx
+author=Caulier Gilles, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/distortionfx/digikamimageplugin_distortionfx_ui.rc b/src/imageplugins/distortionfx/digikamimageplugin_distortionfx_ui.rc
new file mode 100644
index 00000000..99b905ad
--- /dev/null
+++ b/src/imageplugins/distortionfx/digikamimageplugin_distortionfx_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_distortionfx" >
+
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_distortionfx" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_distortionfx" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/distortionfx/distortionfx.cpp b/src/imageplugins/distortionfx/distortionfx.cpp
new file mode 100644
index 00000000..d17abd4f
--- /dev/null
+++ b/src/imageplugins/distortionfx/distortionfx.cpp
@@ -0,0 +1,869 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-18
+ * Description : Distortion FX threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Represents 1
+#define ANGLE_RATIO 0.017453292519943295769236907685
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqdatetime.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "distortionfx.h"
+
+namespace DigikamDistortionFXImagesPlugin
+{
+
+DistortionFX::DistortionFX(Digikam::DImg *orgImage, TQObject *parent, int effectType,
+ int level, int iteration, bool antialiasing)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "DistortionFX")
+{
+ m_effectType = effectType;
+ m_level = level;
+ m_iteration = iteration;
+ m_antiAlias = antialiasing;
+
+ initFilter();
+}
+
+void DistortionFX::filterImage(void)
+{
+ int w = m_orgImage.width();
+ int h = m_orgImage.height();
+ int l = m_level;
+ int f = m_iteration;
+
+ switch (m_effectType)
+ {
+ case FishEye:
+ fisheye(&m_orgImage, &m_destImage, (double)(l/5.0), m_antiAlias);
+ break;
+
+ case Twirl:
+ twirl(&m_orgImage, &m_destImage, l, m_antiAlias);
+ break;
+
+ case CilindricalHor:
+ cilindrical(&m_orgImage, &m_destImage, (double)l, true, false, m_antiAlias);
+ break;
+
+ case CilindricalVert:
+ cilindrical(&m_orgImage, &m_destImage, (double)l, false, true, m_antiAlias);
+ break;
+
+ case CilindricalHV:
+ cilindrical(&m_orgImage, &m_destImage, (double)l, true, true, m_antiAlias);
+ break;
+
+ case Caricature:
+ fisheye(&m_orgImage, &m_destImage, (double)(-l/5.0), m_antiAlias);
+ break;
+
+ case MultipleCorners:
+ multipleCorners(&m_orgImage, &m_destImage, l, m_antiAlias);
+ break;
+
+ case WavesHorizontal:
+ waves(&m_orgImage, &m_destImage, l, f, true, true);
+ break;
+
+ case WavesVertical:
+ waves(&m_orgImage, &m_destImage, l, f, true, false);
+ break;
+
+ case BlockWaves1:
+ blockWaves(&m_orgImage, &m_destImage, l, f, false);
+ break;
+
+ case BlockWaves2:
+ blockWaves(&m_orgImage, &m_destImage, l, f, true);
+ break;
+
+ case CircularWaves1:
+ circularWaves(&m_orgImage, &m_destImage, w/2, h/2, (double)l, (double)f, 0.0, false, m_antiAlias);
+ break;
+
+ case CircularWaves2:
+ circularWaves(&m_orgImage, &m_destImage, w/2, h/2, (double)l, (double)f, 25.0, true, m_antiAlias);
+ break;
+
+ case PolarCoordinates:
+ polarCoordinates(&m_orgImage, &m_destImage, true, m_antiAlias);
+ break;
+
+ case UnpolarCoordinates:
+ polarCoordinates(&m_orgImage, &m_destImage, false, m_antiAlias);
+ break;
+
+ case Tile:
+ tile(&m_orgImage, &m_destImage, 200-f, 200-f, l);
+ break;
+ }
+}
+
+/*
+ This code is shared by six methods.
+ Write value of pixel w|h in data to pixel nw|nh in pResBits.
+ Antialias if requested.
+*/
+void DistortionFX::setPixelFromOther(int Width, int Height, bool sixteenBit, int bytesDepth,
+ uchar *data, uchar *pResBits,
+ int w, int h, double nw, double nh, bool AntiAlias)
+{
+ Digikam::DColor color;
+ int offset, offsetOther;
+
+ offset = getOffset(Width, w, h, bytesDepth);
+
+ if (AntiAlias)
+ {
+ uchar *ptr = pResBits + offset;
+ if (sixteenBit)
+ {
+ unsigned short *ptr16 = (unsigned short *)ptr;
+ Digikam::DImgImageFilters().pixelAntiAliasing16((unsigned short *)data, Width, Height, nw, nh,
+ ptr16+3, ptr16+2, ptr16+1, ptr16);
+ }
+ else
+ {
+ Digikam::DImgImageFilters().pixelAntiAliasing(data, Width, Height, nw, nh,
+ ptr+3, ptr+2, ptr+1, ptr);
+ }
+ }
+ else
+ {
+ // we get the position adjusted
+ offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth);
+ // read color
+ color.setColor(data + offsetOther, sixteenBit);
+ // write color to destination
+ color.setPixel(pResBits + offset);
+ }
+}
+
+/* Function to apply the fisheye effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Coeff => Distortion effect coeff. Positive value render 'Fish Eyes' effect,
+ * and negative values render 'Caricature' effect.
+ * Antialias => Smart bluring result.
+ *
+ * Theory => This is a great effect if you take employee photos
+ * Its pure trigonometry. I think if you study hard the code you
+ * understand very well.
+ */
+void DistortionFX::fisheye(Digikam::DImg *orgImage, Digikam::DImg *destImage, double Coeff, bool AntiAlias)
+{
+ if (Coeff == 0.0) return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int h, w;
+ double nh, nw, th, tw;
+
+ int progress;
+ int nHalfW = Width / 2, nHalfH = Height / 2;
+
+ Digikam::DColor color;
+ int offset;
+
+ double lfXScale = 1.0, lfYScale = 1.0;
+ double lfRadius, lfRadMax, lfAngle, lfCoeff, lfCoeffStep = Coeff / 1000.0;
+
+ if (Width > Height)
+ lfYScale = (double)Width / (double)Height;
+ else if (Height > Width)
+ lfXScale = (double)Height / (double)Width;
+
+ lfRadMax = (double)TQMAX(Height, Width) / 2.0;
+ lfCoeff = lfRadMax / log (fabs (lfCoeffStep) * lfRadMax + 1.0);
+
+ // main loop
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ th = lfYScale * (double)(h - nHalfH);
+
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ tw = lfXScale * (double)(w - nHalfW);
+
+ // we find the distance from the center
+ lfRadius = sqrt (th * th + tw * tw);
+
+ if (lfRadius < lfRadMax)
+ {
+ lfAngle = atan2 (th, tw);
+
+ if (Coeff > 0.0)
+ lfRadius = (exp (lfRadius / lfCoeff) - 1.0) / lfCoeffStep;
+ else
+ lfRadius = lfCoeff * log (1.0 + (-1.0 * lfCoeffStep) * lfRadius);
+
+ nw = (double)nHalfW + (lfRadius / lfXScale) * cos (lfAngle);
+ nh = (double)nHalfH + (lfRadius / lfYScale) * sin (lfAngle);
+
+ setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, h, nw, nh, AntiAlias);
+ }
+ else
+ {
+ // copy pixel
+ offset = getOffset(Width, w, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+ color.setPixel(pResBits + offset);
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)(h) * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the twirl effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Twirl => Distance value.
+ * Antialias => Smart bluring result.
+ *
+ * Theory => Take spiral studies, you will understand better, I'm studying
+ * hard on this effect, because it is not too fast.
+ */
+void DistortionFX::twirl(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Twirl, bool AntiAlias)
+{
+ // if twirl value is zero, we do nothing
+
+ if (Twirl == 0)
+ return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int h, w;
+ double tw, th, nh, nw;
+
+ Digikam::DColor color;
+ int offset;
+
+ int progress;
+ int nHalfW = Width / 2, nHalfH = Height / 2;
+
+ double lfXScale = 1.0, lfYScale = 1.0;
+ double lfAngle, lfNewAngle, lfAngleStep, lfAngleSum, lfCurrentRadius, lfRadMax;
+
+ if (Width > Height)
+ lfYScale = (double)Width / (double)Height;
+ else if (Height > Width)
+ lfXScale = (double)Height / (double)Width;
+
+ // the angle step is twirl divided by 10000
+ lfAngleStep = Twirl / 10000.0;
+ // now, we get the minimum radius
+ lfRadMax = (double)TQMAX(Width, Height) / 2.0;
+
+ // main loop
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ th = lfYScale * (double)(h - nHalfH);
+
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ tw = lfXScale * (double)(w - nHalfW);
+
+ // now, we get the distance
+ lfCurrentRadius = sqrt (th * th + tw * tw);
+
+ // if distance is less than maximum radius...
+ if (lfCurrentRadius < lfRadMax)
+ {
+ // we find the angle from the center
+ lfAngle = atan2 (th, tw);
+ // we get the accumuled angle
+ lfAngleSum = lfAngleStep * (-1.0 * (lfCurrentRadius - lfRadMax));
+ // ok, we sum angle with accumuled to find a new angle
+ lfNewAngle = lfAngle + lfAngleSum;
+
+ // now we find the exact position's x and y
+ nw = (double)nHalfW + cos (lfNewAngle) * (lfCurrentRadius / lfXScale);
+ nh = (double)nHalfH + sin (lfNewAngle) * (lfCurrentRadius / lfYScale);
+
+ setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, h, nw, nh, AntiAlias);
+ }
+ else
+ {
+ // copy pixel
+ offset = getOffset(Width, w, h, bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+ color.setPixel(pResBits + offset);
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the Cilindrical effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Coeff => Cilindrical value.
+ * Horizontal => Apply horizontally.
+ * Vertical => Apply vertically.
+ * Antialias => Smart bluring result.
+ *
+ * Theory => This is a great effect, similar to Spherize (Photoshop).
+ * If you understand FishEye, you will understand Cilindrical
+ * FishEye apply a logarithm function using a sphere radius,
+ * Spherize use the same function but in a rectangular
+ * environment.
+ */
+void DistortionFX::cilindrical(Digikam::DImg *orgImage, Digikam::DImg *destImage, double Coeff,
+ bool Horizontal, bool Vertical, bool AntiAlias)
+
+{
+ if ((Coeff == 0.0) || (! (Horizontal || Vertical)))
+ return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int progress;
+
+ int h, w;
+ double nh, nw;
+
+ int nHalfW = Width / 2, nHalfH = Height / 2;
+ double lfCoeffX = 1.0, lfCoeffY = 1.0, lfCoeffStep = Coeff / 1000.0;
+
+ if (Horizontal)
+ lfCoeffX = (double)nHalfW / log (fabs (lfCoeffStep) * nHalfW + 1.0);
+ if (Vertical)
+ lfCoeffY = (double)nHalfH / log (fabs (lfCoeffStep) * nHalfH + 1.0);
+
+ // initial copy
+ memcpy (pResBits, data, orgImage->numBytes());
+
+ // main loop
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ // we find the distance from the center
+ nh = fabs ((double)(h - nHalfH));
+ nw = fabs ((double)(w - nHalfW));
+
+ if (Horizontal)
+ {
+ if (Coeff > 0.0)
+ nw = (exp (nw / lfCoeffX) - 1.0) / lfCoeffStep;
+ else
+ nw = lfCoeffX * log (1.0 + (-1.0 * lfCoeffStep) * nw);
+ }
+
+ if (Vertical)
+ {
+ if (Coeff > 0.0)
+ nh = (exp (nh / lfCoeffY) - 1.0) / lfCoeffStep;
+ else
+ nh = lfCoeffY * log (1.0 + (-1.0 * lfCoeffStep) * nh);
+ }
+
+ nw = (double)nHalfW + ((w >= nHalfW) ? nw : -nw);
+ nh = (double)nHalfH + ((h >= nHalfH) ? nh : -nh);
+
+ setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, h, nw, nh, AntiAlias);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the Multiple Corners effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Factor => nb corners.
+ * Antialias => Smart bluring result.
+ *
+ * Theory => This is an amazing function, you've never seen this before.
+ * I was testing some trigonometric functions, and I saw that if
+ * I multiply the angle by 2, the result is an image like this
+ * If we multiply by 3, we can create the SixCorners effect.
+ */
+void DistortionFX::multipleCorners(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Factor, bool AntiAlias)
+{
+ if (Factor == 0) return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int h, w;
+ double nh, nw;
+ int progress;
+
+ int nHalfW = Width / 2, nHalfH = Height / 2;
+ double lfAngle, lfNewRadius, lfCurrentRadius, lfRadMax;
+
+ lfRadMax = sqrt (Height * Height + Width * Width) / 2.0;
+
+ // main loop
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ // we find the distance from the center
+ nh = nHalfH - h;
+ nw = nHalfW - w;
+
+ // now, we get the distance
+ lfCurrentRadius = sqrt (nh * nh + nw * nw);
+ // we find the angle from the center
+ lfAngle = atan2 (nh, nw) * (double)Factor;
+
+ // ok, we sum angle with accumuled to find a new angle
+ lfNewRadius = lfCurrentRadius * lfCurrentRadius / lfRadMax;
+
+ // now we find the exact position's x and y
+ nw = (double)nHalfW - (cos (lfAngle) * lfNewRadius);
+ nh = (double)nHalfH - (sin (lfAngle) * lfNewRadius);
+
+ setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, h, nw, nh, AntiAlias);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the Polar Coordinates effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Type => if true Polar Coordinate to Polar else inverse.
+ * Antialias => Smart bluring result.
+ *
+ * Theory => Similar to PolarCoordinates from Photoshop. We apply the polar
+ * transformation in a proportional (Height and Width) radius.
+ */
+void DistortionFX::polarCoordinates(Digikam::DImg *orgImage, Digikam::DImg *destImage, bool Type, bool AntiAlias)
+{
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int h, w;
+ double nh, nw, th, tw;
+ int progress;
+
+ int nHalfW = Width / 2, nHalfH = Height / 2;
+ double lfXScale = 1.0, lfYScale = 1.0;
+ double lfAngle, lfRadius, lfRadMax;
+
+ if (Width > Height)
+ lfYScale = (double)Width / (double)Height;
+ else if (Height > Width)
+ lfXScale = (double)Height / (double)Width;
+
+ lfRadMax = (double)TQMAX(Height, Width) / 2.0;
+
+ // main loop
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ th = lfYScale * (double)(h - nHalfH);
+
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ tw = lfXScale * (double)(w - nHalfW);
+
+ if (Type)
+ {
+ // now, we get the distance
+ lfRadius = sqrt (th * th + tw * tw);
+ // we find the angle from the center
+ lfAngle = atan2 (tw, th);
+
+ // now we find the exact position's x and y
+ nh = lfRadius * (double) Height / lfRadMax;
+ nw = lfAngle * (double) Width / (2 * M_PI);
+
+ nw = (double)nHalfW + nw;
+ }
+ else
+ {
+ lfRadius = (double)(h) * lfRadMax / (double)Height;
+ lfAngle = (double)(w) * (2 * M_PI) / (double) Width;
+
+ nw = (double)nHalfW - (lfRadius / lfXScale) * sin (lfAngle);
+ nh = (double)nHalfH - (lfRadius / lfYScale) * cos (lfAngle);
+ }
+
+ setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, h, nw, nh, AntiAlias);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the circular waves effect backported from ImageProcessing version 2
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * X, Y => Position of circle center on the image.
+ * Amplitude => Sinoidal maximum height
+ * Frequency => Frequency value.
+ * Phase => Phase value.
+ * WavesType => If true the amplitude is proportional to radius.
+ * Antialias => Smart bluring result.
+ *
+ * Theory => Similar to Waves effect, but here I apply a senoidal function
+ * with the angle point.
+ */
+void DistortionFX::circularWaves(Digikam::DImg *orgImage, Digikam::DImg *destImage, int X, int Y, double Amplitude,
+ double Frequency, double Phase, bool WavesType, bool AntiAlias)
+{
+ if (Amplitude < 0.0) Amplitude = 0.0;
+ if (Frequency < 0.0) Frequency = 0.0;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int h, w;
+ double nh, nw;
+ int progress;
+
+ double lfRadius, lfRadMax, lfNewAmp = Amplitude;
+ double lfFreqAngle = Frequency * ANGLE_RATIO;
+
+ Phase *= ANGLE_RATIO;
+
+ lfRadMax = sqrt (Height * Height + Width * Width);
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ nw = X - w;
+ nh = Y - h;
+
+ lfRadius = sqrt (nw * nw + nh * nh);
+
+ if (WavesType)
+ lfNewAmp = Amplitude * lfRadius / lfRadMax;
+
+ nw = (double)w + lfNewAmp * sin(lfFreqAngle * lfRadius + Phase);
+ nh = (double)h + lfNewAmp * cos(lfFreqAngle * lfRadius + Phase);
+
+ setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, h, nw, nh, AntiAlias);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the waves effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Amplitude => Sinoidal maximum height.
+ * Frequency => Frequency value.
+ * FillSides => Like a boolean variable.
+ * Direction => Vertical or horizontal flag.
+ *
+ * Theory => This is an amazing effect, very funny, and very simple to
+ * understand. You just need understand how sin and cos works.
+ */
+void DistortionFX::waves(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ int Amplitude, int Frequency,
+ bool FillSides, bool Direction)
+{
+ if (Amplitude < 0) Amplitude = 0;
+ if (Frequency < 0) Frequency = 0;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+
+ int progress;
+ int h, w;
+
+ if (Direction) // Horizontal
+ {
+ int tx;
+
+ for (h = 0; !m_cancel && (h < Height); h++)
+ {
+ tx = lround(Amplitude * sin ((Frequency * 2) * h * (M_PI / 180)));
+ destImage->bitBltImage(orgImage, 0, h, Width, 1, tx, h);
+
+ if (FillSides)
+ {
+ destImage->bitBltImage(orgImage, Width - tx, h, tx, 1, 0, h);
+ destImage->bitBltImage(orgImage, 0, h, Width - (Width - 2 * Amplitude + tx), 1, Width + tx, h);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+ }
+ else
+ {
+ int ty;
+
+ for (w = 0; !m_cancel && (w < Width); w++)
+ {
+ ty = lround(Amplitude * sin ((Frequency * 2) * w * (M_PI / 180)));
+ destImage->bitBltImage(orgImage, w, 0, 1, Height, w, ty);
+
+ if (FillSides)
+ {
+ destImage->bitBltImage(orgImage, w, Height - ty, 1, ty, w, 0);
+ destImage->bitBltImage(orgImage, w, 0, 1, Height - (Height - 2 * Amplitude + ty), w, Height + ty);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)w * 100.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+ }
+}
+
+/* Function to apply the block waves effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * Amplitude => Sinoidal maximum height
+ * Frequency => Frequency value
+ * Mode => The mode to be applied.
+ *
+ * Theory => This is an amazing effect, very funny when amplitude and
+ * frequency are small values.
+ */
+void DistortionFX::blockWaves(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ int Amplitude, int Frequency, bool Mode)
+{
+ if (Amplitude < 0) Amplitude = 0;
+ if (Frequency < 0) Frequency = 0;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* pResBits = destImage->bits();
+
+ int nw, nh, progress;
+ double Radius;
+
+ Digikam::DColor color;
+ int offset, offsetOther;
+
+ int nHalfW = Width / 2, nHalfH = Height / 2;
+
+ for (int w = 0; !m_cancel && (w < Width); w++)
+ {
+ for (int h = 0; !m_cancel && (h < Height); h++)
+ {
+ nw = nHalfW - w;
+ nh = nHalfH - h;
+
+ Radius = sqrt (nw * nw + nh * nh);
+
+ if (Mode)
+ {
+ nw = (int)(w + Amplitude * sin (Frequency * nw * (M_PI / 180)));
+ nh = (int)(h + Amplitude * cos (Frequency * nh * (M_PI / 180)));
+ }
+ else
+ {
+ nw = (int)(w + Amplitude * sin (Frequency * w * (M_PI / 180)));
+ nh = (int)(h + Amplitude * cos (Frequency * h * (M_PI / 180)));
+ }
+
+ offset = getOffset(Width, w, h, bytesDepth);
+ offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth);
+
+ // read color
+ color.setColor(data + offsetOther, sixteenBit);
+ // write color to destination
+ color.setPixel(pResBits + offset);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int) (((double)w * 100.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+/* Function to apply the tile effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * WSize => Tile Width
+ * HSize => Tile Height
+ * Random => Maximum random value
+ *
+ * Theory => Similar to Tile effect from Photoshop and very easy to
+ * understand. We get a rectangular area using WSize and HSize and
+ * replace in a position with a random distance from the original
+ * position.
+ */
+void DistortionFX::tile(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ int WSize, int HSize, int Random)
+{
+ if (WSize < 1) WSize = 1;
+ if (HSize < 1) HSize = 1;
+ if (Random < 1) Random = 1;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+
+ TQDateTime dt = TQDateTime::currentDateTime();
+ TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
+ uint seed = dt.secsTo(Y2000);
+
+ int tx, ty, h, w, progress;
+
+ for (h = 0; !m_cancel && (h < Height); h += HSize)
+ {
+ for (w = 0; !m_cancel && (w < Width); w += WSize)
+ {
+ tx = (int)(rand_r(&seed) % Random) - (Random / 2);
+ ty = (int)(rand_r(&seed) % Random) - (Random / 2);
+ destImage->bitBltImage(orgImage, w, h, WSize, HSize, w + tx, h + ty);
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int)(((double)h * 100.0) / Height);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+// UNUSED
+/* Function to return the maximum radius with a determined angle
+ *
+ * Height => Height of the image
+ * Width => Width of the image
+ * Angle => Angle to analize the maximum radius
+ *
+ * Theory => This function calcule the maximum radius to that angle
+ * so, we can build an oval circunference
+ */
+ /*
+double DistortionFX::maximumRadius(int Height, int Width, double Angle)
+{
+ double MaxRad, MinRad;
+ double Radius, DegAngle = fabs (Angle * 57.295); // Rads -> Degrees
+
+ MinRad = TQMIN (Height, Width) / 2.0; // Gets the minor radius
+ MaxRad = TQMAX (Height, Width) / 2.0; // Gets the major radius
+
+ // Find the quadrant between -PI/2 and PI/2
+ if (DegAngle > 90.0)
+ Radius = proportionalValue (MinRad, MaxRad, (DegAngle * (255.0 / 90.0)));
+ else
+ Radius = proportionalValue (MaxRad, MinRad, ((DegAngle - 90.0) * (255.0 / 90.0)));
+ return (Radius);
+}
+ */
+
+} // NameSpace DigikamDistortionFXImagesPlugin
diff --git a/src/imageplugins/distortionfx/distortionfx.h b/src/imageplugins/distortionfx/distortionfx.h
new file mode 100644
index 00000000..073f4df2
--- /dev/null
+++ b/src/imageplugins/distortionfx/distortionfx.h
@@ -0,0 +1,149 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-18
+ * Description : Distortion FX threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DISTORTION_FX_H
+#define DISTORTION_FX_H
+
+// TQt includes.
+
+#include <tqsize.h>
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamDistortionFXImagesPlugin
+{
+
+class DistortionFX : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ DistortionFX(Digikam::DImg *orgImage, TQObject *parent=0, int effectType=0,
+ int level=0, int iteration=0, bool antialiasing=true);
+
+ ~DistortionFX(){};
+
+public:
+
+ enum DistortionFXTypes
+ {
+ FishEye=0,
+ Twirl,
+ CilindricalHor,
+ CilindricalVert,
+ CilindricalHV,
+ Caricature,
+ MultipleCorners,
+ WavesHorizontal,
+ WavesVertical,
+ BlockWaves1,
+ BlockWaves2,
+ CircularWaves1,
+ CircularWaves2,
+ PolarCoordinates,
+ UnpolarCoordinates,
+ Tile
+ };
+
+private:
+
+ virtual void filterImage(void);
+
+ // Backported from ImageProcessing version 2
+ void fisheye(Digikam::DImg *orgImage, Digikam::DImg *destImage, double Coeff, bool AntiAlias=true);
+ void twirl(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Twirl, bool AntiAlias=true);
+ void cilindrical(Digikam::DImg *orgImage, Digikam::DImg *destImage, double Coeff,
+ bool Horizontal, bool Vertical, bool AntiAlias=true);
+ void multipleCorners(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Factor, bool AntiAlias=true);
+ void polarCoordinates(Digikam::DImg *orgImage, Digikam::DImg *destImage, bool Type, bool AntiAlias=true);
+ void circularWaves(Digikam::DImg *orgImage, Digikam::DImg *destImage, int X, int Y, double Amplitude,
+ double Frequency, double Phase, bool WavesType, bool AntiAlias=true);
+
+ // Backported from ImageProcessing version 1
+ void waves(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Amplitude, int Frequency,
+ bool FillSides, bool Direction);
+ void blockWaves(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Amplitude, int Frequency, bool Mode);
+ void tile(Digikam::DImg *orgImage, Digikam::DImg *destImage, int WSize, int HSize, int Random);
+
+ void setPixelFromOther(int Width, int Height, bool sixteenBit, int bytesDepth,
+ uchar *data, uchar *pResBits,
+ int w, int h, double nw, double nh, bool AntiAlias);
+ /*
+ //UNUSED
+
+ inline double maximumRadius(int Height, int Width, double Angle);
+
+ // This function does the same thing that ShadeColors function but using double variables.
+ inline double proportionalValue (double DestValue, double SrcValue, double Shade)
+ {
+ if (Shade == 0.0) return DestValue;
+ if (Shade == 255.0) return SrcValue;
+ return ((DestValue * (255.0 - Shade) + SrcValue * Shade) / 256.0);
+ };
+ */
+
+ // Return the limit defined the max and min values.
+ inline int Lim_Max(int Now, int Up, int Max)
+ {
+ --Max;
+ while (Now > Max - Up) --Up;
+ return (Up);
+ };
+
+ inline bool isInside (int Width, int Height, int X, int Y)
+ {
+ bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true);
+ bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true);
+ return (bIsWOk && bIsHOk);
+ };
+
+ inline int getOffset(int Width, int X, int Y, int bytesDepth)
+ {
+ return (Y * Width * bytesDepth) + (X * bytesDepth);
+ };
+
+ inline int getOffsetAdjusted(int Width, int Height, int X, int Y, int bytesDepth)
+ {
+ X = (X < 0) ? 0 : ((X >= Width ) ? (Width - 1) : X);
+ Y = (Y < 0) ? 0 : ((Y >= Height) ? (Height - 1) : Y);
+ return getOffset(Width, X, Y, bytesDepth);
+ };
+
+private:
+
+ bool m_antiAlias;
+
+ int m_level;
+ int m_iteration;
+ int m_effectType;
+};
+
+} // NameSpace DigikamDistortionFXImagesPlugin
+
+#endif /* DISTORTION_FX_H */
diff --git a/src/imageplugins/distortionfx/distortionfxtool.cpp b/src/imageplugins/distortionfx/distortionfxtool.cpp
new file mode 100644
index 00000000..45ff0e45
--- /dev/null
+++ b/src/imageplugins/distortionfx/distortionfxtool.cpp
@@ -0,0 +1,398 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-11
+ * Description : a plugin to apply Distortion FX to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqframe.h>
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqspinbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kprogress.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "editortoolsettings.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "distortionfx.h"
+#include "distortionfxtool.h"
+#include "distortionfxtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamDistortionFXImagesPlugin
+{
+
+DistortionFXTool::DistortionFXTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("distortionfx");
+ setToolName(i18n("Distortion Effects"));
+ setToolIcon(SmallIcon("distortionfx"));
+
+ m_previewWidget = new ImageWidget("distortionfx Tool", 0,
+ i18n("<p>This is the preview of the distortion effect "
+ "applied to the photograph."),
+ false, ImageGuideWidget::HVGuideMode);
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel,
+ EditorToolSettings::ColorGuide);
+
+ TQGridLayout* gridSettings = new TQGridLayout(m_gboxSettings->plainPage(), 7, 2);
+
+ m_effectTypeLabel = new TQLabel(i18n("Type:"), m_gboxSettings->plainPage());
+
+ m_effectType = new RComboBox(m_gboxSettings->plainPage());
+ m_effectType->insertItem(i18n("Fish Eyes"));
+ m_effectType->insertItem(i18n("Twirl"));
+ m_effectType->insertItem(i18n("Cylindrical Hor."));
+ m_effectType->insertItem(i18n("Cylindrical Vert."));
+ m_effectType->insertItem(i18n("Cylindrical H/V."));
+ m_effectType->insertItem(i18n("Caricature"));
+ m_effectType->insertItem(i18n("Multiple Corners"));
+ m_effectType->insertItem(i18n("Waves Hor."));
+ m_effectType->insertItem(i18n("Waves Vert."));
+ m_effectType->insertItem(i18n("Block Waves 1"));
+ m_effectType->insertItem(i18n("Block Waves 2"));
+ m_effectType->insertItem(i18n("Circular Waves 1"));
+ m_effectType->insertItem(i18n("Circular Waves 2"));
+ m_effectType->insertItem(i18n("Polar Coordinates"));
+ m_effectType->insertItem(i18n("Unpolar Coordinates"));
+ m_effectType->insertItem(i18n("Tile"));
+ m_effectType->setDefaultItem(DistortionFX::FishEye);
+ TQWhatsThis::add( m_effectType, i18n("<p>Here, select the type of effect to apply to the image.<p>"
+ "<b>Fish Eyes</b>: warps the photograph around a 3D spherical shape to "
+ "reproduce the common photograph 'Fish Eyes' effect.<p>"
+ "<b>Twirl</b>: spins the photograph to produce a Twirl pattern.<p>"
+ "<b>Cylinder Hor.</b>: warps the photograph around a horizontal cylinder.<p>"
+ "<b>Cylinder Vert.</b>: warps the photograph around a vertical cylinder.<p>"
+ "<b>Cylinder H/V.</b>: warps the photograph around 2 cylinders, vertical "
+ "and horizontal.<p>"
+ "<b>Caricature</b>: distorts the photograph with the 'Fish Eyes' effect inverted.<p>"
+ "<b>Multiple Corners</b>: splits the photograph like a multiple corners pattern.<p>"
+ "<b>Waves Horizontal</b>: distorts the photograph with horizontal waves.<p>"
+ "<b>Waves Vertical</b>: distorts the photograph with vertical waves.<p>"
+ "<b>Block Waves 1</b>: divides the image into cells and makes it look as "
+ "if it is being viewed through glass blocks.<p>"
+ "<b>Block Waves 2</b>: like Block Waves 1 but with another version "
+ "of glass blocks distortion.<p>"
+ "<b>Circular Waves 1</b>: distorts the photograph with circular waves.<p>"
+ "<b>Circular Waves 2</b>: another variation of the Circular Waves effect.<p>"
+ "<b>Polar Coordinates</b>: converts the photograph from rectangular "
+ "to polar coordinates.<p>"
+ "<b>Unpolar Coordinates</b>: the Polar Coordinates effect inverted.<p>"
+ "<b>Tile</b>: splits the photograph into square blocks and moves "
+ "them randomly inside the image.<p>"
+ ));
+
+ m_levelLabel = new TQLabel(i18n("Level:"), m_gboxSettings->plainPage());
+ m_levelInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_levelInput->setRange(0, 100, 1);
+ m_levelInput->setDefaultValue(50);
+ TQWhatsThis::add( m_levelInput, i18n("<p>Set here the level of the effect."));
+
+
+ m_iterationLabel = new TQLabel(i18n("Iteration:"), m_gboxSettings->plainPage());
+ m_iterationInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_iterationInput->setRange(0, 100, 1);
+ m_iterationInput->setDefaultValue(10);
+ TQWhatsThis::add( m_iterationInput, i18n("<p>This value controls the iterations to use for Waves, "
+ "Tile, and Neon effects."));
+
+ gridSettings->addMultiCellWidget(m_effectTypeLabel, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_effectType, 1, 1, 0, 1);
+ gridSettings->addMultiCellWidget(m_levelLabel, 2, 2, 0, 1);
+ gridSettings->addMultiCellWidget(m_levelInput, 3, 3, 0, 1);
+ gridSettings->addMultiCellWidget(m_iterationLabel, 4, 4, 0, 1);
+ gridSettings->addMultiCellWidget(m_iterationInput, 5, 5, 0, 1);
+ gridSettings->setRowStretch(6, 10);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_effectType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffectTypeChanged(int)));
+
+ connect(m_levelInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_iterationInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gboxSettings, TQ_SIGNAL(signalColorGuideChanged()),
+ this, TQ_SLOT(slotColorGuideChanged()));
+}
+
+DistortionFXTool::~DistortionFXTool()
+{
+}
+
+void DistortionFXTool::slotColorGuideChanged()
+{
+ m_previewWidget->slotChangeGuideColor(m_gboxSettings->guideColor());
+ m_previewWidget->slotChangeGuideSize(m_gboxSettings->guideSize());
+}
+
+void DistortionFXTool::renderingFinished()
+{
+ m_effectTypeLabel->setEnabled(true);
+ m_effectType->setEnabled(true);
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+
+ switch (m_effectType->currentItem())
+ {
+ case DistortionFX::FishEye:
+ case DistortionFX::Twirl:
+ case DistortionFX::CilindricalHor:
+ case DistortionFX::CilindricalVert:
+ case DistortionFX::CilindricalHV:
+ case DistortionFX::Caricature:
+ case DistortionFX::MultipleCorners:
+ break;
+
+ case DistortionFX::PolarCoordinates:
+ case DistortionFX::UnpolarCoordinates:
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ break;
+
+ case DistortionFX::WavesHorizontal:
+ case DistortionFX::WavesVertical:
+ case DistortionFX::BlockWaves1:
+ case DistortionFX::BlockWaves2:
+ case DistortionFX::CircularWaves1:
+ case DistortionFX::CircularWaves2:
+ case DistortionFX::Tile:
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+ break;
+ }
+}
+
+void DistortionFXTool::readSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("distortionfx Tool");
+
+ m_effectType->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+ m_levelInput->blockSignals(true);
+
+ m_effectType->setCurrentItem(config->readNumEntry("EffectType",
+ m_effectType->defaultItem()));
+ m_iterationInput->setValue(config->readNumEntry("IterationAjustment",
+ m_iterationInput->defaultValue()));
+ m_levelInput->setValue(config->readNumEntry("LevelAjustment",
+ m_levelInput->defaultValue()));
+
+ m_effectType->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+ m_levelInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void DistortionFXTool::writeSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("distortionfx Tool");
+ config->writeEntry("EffectType", m_effectType->currentItem());
+ config->writeEntry("IterationAjustment", m_iterationInput->value());
+ config->writeEntry("LevelAjustment", m_levelInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void DistortionFXTool::slotResetSettings()
+{
+ m_effectType->blockSignals(true);
+ m_levelInput->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+
+ m_levelInput->slotReset();
+ m_iterationInput->slotReset();
+ m_effectType->slotReset();
+ slotEffectTypeChanged(m_effectType->defaultItem());
+
+ m_effectType->blockSignals(false);
+ m_levelInput->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+}
+
+void DistortionFXTool::slotEffectTypeChanged(int type)
+{
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+
+ m_levelInput->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+ m_levelInput->setRange(0, 100, 1);
+ m_levelInput->setValue(25);
+
+ switch (type)
+ {
+ case DistortionFX::Twirl:
+ m_levelInput->setRange(-50, 50, 1);
+ m_levelInput->setValue(10);
+ break;
+
+ case DistortionFX::FishEye:
+ case DistortionFX::CilindricalHor:
+ case DistortionFX::CilindricalVert:
+ case DistortionFX::CilindricalHV:
+ case DistortionFX::Caricature:
+ m_levelInput->setRange(0, 200, 1);
+ m_levelInput->setValue(50);
+ break;
+
+ case DistortionFX::MultipleCorners:
+ m_levelInput->setRange(1, 10, 1);
+ m_levelInput->setValue(4);
+ break;
+
+ case DistortionFX::WavesHorizontal:
+ case DistortionFX::WavesVertical:
+ case DistortionFX::BlockWaves1:
+ case DistortionFX::BlockWaves2:
+ case DistortionFX::CircularWaves1:
+ case DistortionFX::CircularWaves2:
+ case DistortionFX::Tile:
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+ m_iterationInput->setRange(0, 200, 1);
+ m_iterationInput->setValue(10);
+ break;
+
+ case DistortionFX::PolarCoordinates:
+ case DistortionFX::UnpolarCoordinates:
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ break;
+ }
+
+ m_levelInput->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void DistortionFXTool::prepareEffect()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+
+ int l = m_levelInput->value();
+ int f = m_iterationInput->value();
+ int e = m_effectType->currentItem();
+
+ ImageIface* iface = m_previewWidget->imageIface();
+
+ uchar *data = iface->getPreviewImage();
+ DImg image(iface->previewWidth(), iface->previewHeight(), iface->previewSixteenBit(),
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ setFilter(dynamic_cast<DImgThreadedFilter *> (new DistortionFX(&image, this, e, l, f)));
+}
+
+void DistortionFXTool::prepareFinal()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+
+ int l = m_levelInput->value();
+ int f = m_iterationInput->value();
+ int e = m_effectType->currentItem();
+
+ ImageIface iface(0, 0);
+
+ setFilter(dynamic_cast<DImgThreadedFilter *> (new DistortionFX(iface.getOriginalImg(), this, e, l, f)));
+}
+
+void DistortionFXTool::putPreviewData(void)
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+
+ DImg imDest = filter()->getTargetImage()
+ .smoothScale(iface->previewWidth(), iface->previewHeight());
+ iface->putPreviewImage(imDest.bits());
+
+ m_previewWidget->updatePreview();
+}
+
+void DistortionFXTool::putFinalData(void)
+{
+ ImageIface iface(0, 0);
+ DImg targetImage = filter()->getTargetImage();
+ iface.putOriginalImage(i18n("Distortion Effects"),
+ targetImage.bits(),
+ targetImage.width(), targetImage.height());
+}
+
+} // NameSpace DigikamDistortionFXImagesPlugin
+
diff --git a/src/imageplugins/distortionfx/distortionfxtool.h b/src/imageplugins/distortionfx/distortionfxtool.h
new file mode 100644
index 00000000..bcdd6088
--- /dev/null
+++ b/src/imageplugins/distortionfx/distortionfxtool.h
@@ -0,0 +1,96 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-11
+ * Description : a plugin to apply Distortion FX to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DISTORTIONFXTOOL_H
+#define DISTORTIONFXTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQLabel;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class ImageWidget;
+}
+
+namespace DigikamDistortionFXImagesPlugin
+{
+
+class DistortionFXTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ DistortionFXTool(TQObject *parent);
+ ~DistortionFXTool();
+
+private slots:
+
+ void slotEffectTypeChanged(int type);
+ void slotResetSettings();
+ void slotColorGuideChanged();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+
+ TQLabel *m_effectTypeLabel;
+ TQLabel *m_levelLabel;
+ TQLabel *m_iterationLabel;
+
+ KDcrawIface::RComboBox *m_effectType;
+
+ KDcrawIface::RIntNumInput *m_levelInput;
+ KDcrawIface::RIntNumInput *m_iterationInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamDistortionFXImagesPlugin
+
+#endif /* DISTORTIONFXTOOL_H */
diff --git a/src/imageplugins/distortionfx/imageeffect_distortionfx.cpp b/src/imageplugins/distortionfx/imageeffect_distortionfx.cpp
new file mode 100644
index 00000000..695e7749
--- /dev/null
+++ b/src/imageplugins/distortionfx/imageeffect_distortionfx.cpp
@@ -0,0 +1,378 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-11
+ * Description : a plugin to apply Distortion FX to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqimage.h>
+#include <tqspinbox.h>
+#include <tqcombobox.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <kcursor.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <tdeapplication.h>
+#include <knuminput.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "distortionfx.h"
+#include "imageeffect_distortionfx.h"
+#include "imageeffect_distortionfx.moc"
+
+namespace DigikamDistortionFXImagesPlugin
+{
+
+ImageEffect_DistortionFX::ImageEffect_DistortionFX(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Distortion Effects"),
+ "distortionfx", false, true, false,
+ Digikam::ImageGuideWidget::HVGuideMode)
+{
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Distortion Effects"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to apply distortion effects to an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pieter Z. Voloshyn", I18N_NOOP("Distortion algorithms"),
+ "pieter dot voloshyn at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ TQWhatsThis::add( m_imagePreviewWidget, i18n("<p>This is the preview of the distortion effect "
+ "applied to the photograph.") );
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 5, 2, spacingHint());
+
+ m_effectTypeLabel = new TQLabel(i18n("Type:"), gboxSettings);
+
+ m_effectType = new TQComboBox( false, gboxSettings );
+ m_effectType->insertItem( i18n("Fish Eyes") );
+ m_effectType->insertItem( i18n("Twirl") );
+ m_effectType->insertItem( i18n("Cylindrical Hor.") );
+ m_effectType->insertItem( i18n("Cylindrical Vert.") );
+ m_effectType->insertItem( i18n("Cylindrical H/V.") );
+ m_effectType->insertItem( i18n("Caricature") );
+ m_effectType->insertItem( i18n("Multiple Corners") );
+ m_effectType->insertItem( i18n("Waves Hor.") );
+ m_effectType->insertItem( i18n("Waves Vert.") );
+ m_effectType->insertItem( i18n("Block Waves 1") );
+ m_effectType->insertItem( i18n("Block Waves 2") );
+ m_effectType->insertItem( i18n("Circular Waves 1") );
+ m_effectType->insertItem( i18n("Circular Waves 2") );
+ m_effectType->insertItem( i18n("Polar Coordinates") );
+ m_effectType->insertItem( i18n("Unpolar Coordinates") );
+ m_effectType->insertItem( i18n("Tile") );
+ TQWhatsThis::add( m_effectType, i18n("<p>Here, select the type of effect to apply to the image.<p>"
+ "<b>Fish Eyes</b>: warps the photograph around a 3D spherical shape to "
+ "reproduce the common photograph 'Fish Eyes' effect.<p>"
+ "<b>Twirl</b>: spins the photograph to produce a Twirl pattern.<p>"
+ "<b>Cylinder Hor.</b>: warps the photograph around a horizontal cylinder.<p>"
+ "<b>Cylinder Vert.</b>: warps the photograph around a vertical cylinder.<p>"
+ "<b>Cylinder H/V.</b>: warps the photograph around 2 cylinders, vertical "
+ "and horizontal.<p>"
+ "<b>Caricature</b>: distorts the photograph with the 'Fish Eyes' effect inverted.<p>"
+ "<b>Multiple Corners</b>: splits the photograph like a multiple corners pattern.<p>"
+ "<b>WavesQt Horizontal</b>: distorts the photograph with horizontal waves.<p>"
+ "<b>Waves Vertical</b>: distorts the photograph with vertical waves.<p>"
+ "<b>Block Waves 1</b>: divides the image into cells and makes it look as "
+ "if it is being viewed through glass blocks.<p>"
+ "<b>Block Waves 2</b>: like Block Waves 1 but with another version "
+ "of glass blocks distortion.<p>"
+ "<b>Circular Waves 1</b>: distorts the photograph with circular waves.<p>"
+ "<b>Circular Waves 2</b>: another variation of the Circular Waves effect.<p>"
+ "<b>Polar Coordinates</b>: converts the photograph from rectangular "
+ "to polar coordinates.<p>"
+ "<b>Unpolar Coordinates</b>: the Polar Coordinates effect inverted.<p>"
+ "<b>Tile</b>: splits the photograph into square blocks and moves "
+ "them randomly inside the image.<p>"
+ ));
+ gridSettings->addMultiCellWidget(m_effectTypeLabel, 0, 0, 0, 2);
+ gridSettings->addMultiCellWidget(m_effectType, 1, 1, 0, 2);
+
+ m_levelLabel = new TQLabel(i18n("Level:"), gboxSettings);
+ m_levelInput = new KIntNumInput(gboxSettings);
+ m_levelInput->setRange(0, 100, 1, true);
+ TQWhatsThis::add( m_levelInput, i18n("<p>Set here the level of the effect."));
+
+ gridSettings->addMultiCellWidget(m_levelLabel, 2, 2, 0, 2);
+ gridSettings->addMultiCellWidget(m_levelInput, 3, 3, 0, 2);
+
+ m_iterationLabel = new TQLabel(i18n("Iteration:"), gboxSettings);
+ m_iterationInput = new KIntNumInput(gboxSettings);
+ m_iterationInput->setRange(0, 100, 1, true);
+ TQWhatsThis::add( m_iterationInput, i18n("<p>This value controls the iterations to use for Waves, "
+ "Tile, and Neon effects."));
+
+ gridSettings->addMultiCellWidget(m_iterationLabel, 4, 4, 0, 2);
+ gridSettings->addMultiCellWidget(m_iterationInput, 5, 5, 0, 2);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_effectType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffectTypeChanged(int)));
+
+ connect(m_levelInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_iterationInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_DistortionFX::~ImageEffect_DistortionFX()
+{
+}
+
+void ImageEffect_DistortionFX::renderingFinished()
+{
+ m_effectTypeLabel->setEnabled(true);
+ m_effectType->setEnabled(true);
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+
+ switch (m_effectType->currentItem())
+ {
+ case DistortionFX::FishEye:
+ case DistortionFX::Twirl:
+ case DistortionFX::CilindricalHor:
+ case DistortionFX::CilindricalVert:
+ case DistortionFX::CilindricalHV:
+ case DistortionFX::Caricature:
+ case DistortionFX::MultipleCorners:
+ break;
+
+ case DistortionFX::PolarCoordinates:
+ case DistortionFX::UnpolarCoordinates:
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ break;
+
+ case DistortionFX::WavesHorizontal:
+ case DistortionFX::WavesVertical:
+ case DistortionFX::BlockWaves1:
+ case DistortionFX::BlockWaves2:
+ case DistortionFX::CircularWaves1:
+ case DistortionFX::CircularWaves2:
+ case DistortionFX::Tile:
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+ break;
+ }
+}
+
+void ImageEffect_DistortionFX::readUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("distortionfx Tool Dialog");
+
+ m_effectType->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+ m_levelInput->blockSignals(true);
+
+ m_effectType->setCurrentItem(config->readNumEntry("EffectType", DistortionFX::FishEye));
+ m_iterationInput->setValue(config->readNumEntry("IterationAjustment", 10));
+ m_levelInput->setValue(config->readNumEntry("LevelAjustment", 50));
+
+ m_effectType->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+ m_levelInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_DistortionFX::writeUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("distortionfx Tool Dialog");
+ config->writeEntry("EffectType", m_effectType->currentItem());
+ config->writeEntry("IterationAjustment", m_iterationInput->value());
+ config->writeEntry("LevelAjustment", m_levelInput->value());
+ config->sync();
+}
+
+void ImageEffect_DistortionFX::resetValues()
+{
+ m_effectType->blockSignals(true);
+ m_effectType->setCurrentItem(DistortionFX::FishEye);
+ slotEffectTypeChanged(DistortionFX::FishEye);
+ m_effectType->blockSignals(false);
+}
+
+void ImageEffect_DistortionFX::slotEffectTypeChanged(int type)
+{
+ m_levelInput->setEnabled(true);
+ m_levelLabel->setEnabled(true);
+
+ m_levelInput->blockSignals(true);
+ m_iterationInput->blockSignals(true);
+ m_levelInput->setRange(0, 100, 1, true);
+ m_levelInput->setValue(25);
+
+ switch (type)
+ {
+ case DistortionFX::Twirl:
+ m_levelInput->setRange(-50, 50, 1, true);
+ m_levelInput->setValue(10);
+ break;
+
+ case DistortionFX::FishEye:
+ case DistortionFX::CilindricalHor:
+ case DistortionFX::CilindricalVert:
+ case DistortionFX::CilindricalHV:
+ case DistortionFX::Caricature:
+ m_levelInput->setRange(0, 200, 1, true);
+ m_levelInput->setValue(50);
+ break;
+
+ case DistortionFX::MultipleCorners:
+ m_levelInput->setRange(1, 10, 1, true);
+ m_levelInput->setValue(4);
+ break;
+
+ case DistortionFX::WavesHorizontal:
+ case DistortionFX::WavesVertical:
+ case DistortionFX::BlockWaves1:
+ case DistortionFX::BlockWaves2:
+ case DistortionFX::CircularWaves1:
+ case DistortionFX::CircularWaves2:
+ case DistortionFX::Tile:
+ m_iterationInput->setEnabled(true);
+ m_iterationLabel->setEnabled(true);
+ m_iterationInput->setRange(0, 200, 1, true);
+ m_iterationInput->setValue(10);
+ break;
+
+ case DistortionFX::PolarCoordinates:
+ case DistortionFX::UnpolarCoordinates:
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ break;
+ }
+
+ m_levelInput->blockSignals(false);
+ m_iterationInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_DistortionFX::prepareEffect()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+
+ int l = m_levelInput->value();
+ int f = m_iterationInput->value();
+ int e = m_effectType->currentItem();
+
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+
+ uchar *data = iface->getPreviewImage();
+ Digikam::DImg image(iface->previewWidth(), iface->previewHeight(), iface->previewSixteenBit(),
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new DistortionFX(&image, this, e, l, f));
+}
+
+void ImageEffect_DistortionFX::prepareFinal()
+{
+ m_effectTypeLabel->setEnabled(false);
+ m_effectType->setEnabled(false);
+ m_levelInput->setEnabled(false);
+ m_levelLabel->setEnabled(false);
+ m_iterationInput->setEnabled(false);
+ m_iterationLabel->setEnabled(false);
+
+ int l = m_levelInput->value();
+ int f = m_iterationInput->value();
+ int e = m_effectType->currentItem();
+
+ Digikam::ImageIface iface(0, 0);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new DistortionFX(iface.getOriginalImg(), this, e, l, f));
+}
+
+void ImageEffect_DistortionFX::putPreviewData(void)
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage()
+ .smoothScale(iface->previewWidth(), iface->previewHeight());
+ iface->putPreviewImage(imDest.bits());
+
+ m_imagePreviewWidget->updatePreview();
+}
+
+void ImageEffect_DistortionFX::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Distortion Effects"),
+ m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamDistortionFXImagesPlugin
+
diff --git a/src/imageplugins/distortionfx/imageeffect_distortionfx.h b/src/imageplugins/distortionfx/imageeffect_distortionfx.h
new file mode 100644
index 00000000..652b372b
--- /dev/null
+++ b/src/imageplugins/distortionfx/imageeffect_distortionfx.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-11
+ * Description : a plugin to apply Distortion FX to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_DISTORTIONFX_H
+#define IMAGEEFFECT_DISTORTIONFX_H
+
+// Digikam includes.
+
+#include "imageguidedlg.h"
+
+class TQComboBox;
+class TQLabel;
+
+class KIntNumInput;
+
+namespace DigikamDistortionFXImagesPlugin
+{
+
+class ImageEffect_DistortionFX : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_DistortionFX(TQWidget *parent);
+ ~ImageEffect_DistortionFX();
+
+private slots:
+
+ void slotEffectTypeChanged(int type);
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQComboBox *m_effectType;
+
+ TQLabel *m_effectTypeLabel;
+ TQLabel *m_levelLabel;
+ TQLabel *m_iterationLabel;
+
+ KIntNumInput *m_levelInput;
+ KIntNumInput *m_iterationInput;
+};
+
+} // NameSpace DigikamDistortionFXImagesPlugin
+
+#endif /* IMAGEEFFECT_DISTORTIONFX_H */
diff --git a/src/imageplugins/distortionfx/imageplugin_distortionfx.cpp b/src/imageplugins/distortionfx/imageplugin_distortionfx.cpp
new file mode 100644
index 00000000..582e1b99
--- /dev/null
+++ b/src/imageplugins/distortionfx/imageplugin_distortionfx.cpp
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-11
+ * Description : a plugin to apply Distortion FX to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "distortionfxtool.h"
+#include "imageplugin_distortionfx.h"
+#include "imageplugin_distortionfx.moc"
+
+using namespace DigikamDistortionFXImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_distortionfx,
+ KGenericFactory<ImagePlugin_DistortionFX>("digikamimageplugin_distortionfx"));
+
+ImagePlugin_DistortionFX::ImagePlugin_DistortionFX(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_DistortionFX")
+{
+ m_distortionfxAction = new TDEAction(i18n("Distortion Effects..."), "distortionfx", 0,
+ this, TQ_SLOT(slotDistortionFX()),
+ actionCollection(), "imageplugin_distortionfx");
+
+ setXMLFile( "digikamimageplugin_distortionfx_ui.rc" );
+
+ DDebug() << "ImagePlugin_DistortionFX plugin loaded" << endl;
+}
+
+ImagePlugin_DistortionFX::~ImagePlugin_DistortionFX()
+{
+}
+
+void ImagePlugin_DistortionFX::setEnabledActions(bool enable)
+{
+ m_distortionfxAction->setEnabled(enable);
+}
+
+void ImagePlugin_DistortionFX::slotDistortionFX()
+{
+ DistortionFXTool *distortionfx = new DistortionFXTool(this);
+ loadTool(distortionfx);
+}
diff --git a/src/imageplugins/distortionfx/imageplugin_distortionfx.h b/src/imageplugins/distortionfx/imageplugin_distortionfx.h
new file mode 100644
index 00000000..2af33a14
--- /dev/null
+++ b/src/imageplugins/distortionfx/imageplugin_distortionfx.h
@@ -0,0 +1,59 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-11
+ * Description : a plugin to apply Distortion FX to an image.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Distortion algorithms copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_DISTORTIONFX_H
+#define IMAGEPLUGIN_DISTORTIONFX_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_DistortionFX : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_DistortionFX(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_DistortionFX();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotDistortionFX();
+
+private:
+
+ TDEAction *m_distortionfxAction;
+};
+
+#endif /* IMAGEPLUGIN_DISTORTIONFX_H */
diff --git a/src/imageplugins/emboss/Makefile.am b/src/imageplugins/emboss/Makefile.am
new file mode 100644
index 00000000..c595aa30
--- /dev/null
+++ b/src/imageplugins/emboss/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_emboss_la_SOURCES = imageplugin_emboss.cpp \
+ embosstool.cpp emboss.cpp
+
+digikamimageplugin_emboss_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_emboss_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_emboss.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_emboss.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_emboss_ui.rc
+
diff --git a/src/imageplugins/emboss/digikamimageplugin_emboss.desktop b/src/imageplugins/emboss/digikamimageplugin_emboss.desktop
new file mode 100644
index 00000000..414f235b
--- /dev/null
+++ b/src/imageplugins/emboss/digikamimageplugin_emboss.desktop
@@ -0,0 +1,51 @@
+[Desktop Entry]
+Name=ImagePlugin_Emboss
+Name[bg]=Приставка за снимки - Релеф
+Name[da]=Billedplugin_Præg i relief
+Name[el]=ΠρόσθετοΕικόνας_Εμβάθυνση
+Name[fi]=Kohokuvio
+Name[hr]=Reljef
+Name[it]=PluginImmagini_Rilievo
+Name[nl]=Afbeeldingsplugin_Reliëf
+Name[sr]=Рељеф
+Name[sr@Latn]=Reljef
+Name[sv]=Insticksprogram för reliefeffekt
+Name[tr]=ResimEklentisi_Kabart
+Name[xx]=xxImagePlugin_Embossxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Emboss image effect plugin for digiKam
+Comment[bg]=Приставка на digiKam за добавяне на релеф към снимки
+Comment[ca]=Connector pel digiKam d'efecte d'imatge de relleu
+Comment[cs]=Efektový modul pro tvorbu reliéfu pro digiKam
+Comment[da]=Reliefprægnings-plugin for Digikam
+Comment[de]=digiKam-Modul zum Erzeugen eines Gravureffektes
+Comment[el]=Πρόσθετο εφέ εμβάθυνσης για το digiKam
+Comment[es]=Plugin para digiKam con efectos de bajo relieve
+Comment[et]=DigiKami kohrutuse pildiefektiplugin
+Comment[fa]=برجسته کردن وصلۀ جلوۀ تصویر برای digiKam
+Comment[fi]=Kohokuvioefekti kohteiden ääriviivoista
+Comment[gl]=Un plugin de digiKam para dar-lle relevo á imaxe
+Comment[hr]=digiKam dodatak za efekt reljefa
+Comment[is]=Íforrit fyrir digiKam sem framkallar upphleypingaráhrif
+Comment[it]=Plugin di effetto di rilievo delle immagini per digiKam
+Comment[ja]=digiKam エンボス効果プラグイン
+Comment[nds]=digiKam-Moduul för Ingraav-Effekten
+Comment[nl]=Digikam-plugin voor reliëf-afbeeldingen
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਈਮਬੋਸ ਚਿੱਤਰ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam oferująca efekty zniekształceń
+Comment[pt]=Um 'plugin' do digiKam para gravar em relevo a imagem
+Comment[pt_BR]=Plugin de efeito de estampa
+Comment[ru]=Модуль рельефного рисунка для digiKam
+Comment[sk]=digiKam plugin pre efekt reliéfu
+Comment[sr]=digiKam-ов прикључак за ефекат рељефа на сликама
+Comment[sr@Latn]=digiKam-ov priključak za efekat reljefa na slikama
+Comment[sv]=Digikam insticksprogram för reliefbildeffekt
+Comment[tr]=digiKam için resim kabartma etkisi eklentisi
+Comment[uk]=Втулок створення ефекту барельєфу для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng chạm nổi ảnh cho digiKam
+Comment[xx]=xxEmboss image effect plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_emboss
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/emboss/digikamimageplugin_emboss_ui.rc b/src/imageplugins/emboss/digikamimageplugin_emboss_ui.rc
new file mode 100644
index 00000000..1a6943c6
--- /dev/null
+++ b/src/imageplugins/emboss/digikamimageplugin_emboss_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_emboss" >
+
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_emboss" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_emboss" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/emboss/emboss.cpp b/src/imageplugins/emboss/emboss.cpp
new file mode 100644
index 00000000..d4225148
--- /dev/null
+++ b/src/imageplugins/emboss/emboss.cpp
@@ -0,0 +1,127 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Emboss threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Emboss algorithm copyrighted 2004 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "emboss.h"
+
+namespace DigikamEmbossImagesPlugin
+{
+
+Emboss::Emboss(Digikam::DImg *orgImage, TQObject *parent, int depth)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "Emboss")
+{
+ m_depth = depth;
+ initFilter();
+}
+
+void Emboss::filterImage(void)
+{
+ embossImage(&m_orgImage, &m_destImage, m_depth);
+}
+
+// This method have been ported from Pieter Z. Voloshyn algorithm code.
+
+/* Function to apply the Emboss effect
+ *
+ * data => The image data in RGBA mode.
+ * Width => Width of image.
+ * Height => Height of image.
+ * d => Emboss value
+ *
+ * Theory => This is an amazing effect. And the theory is very simple to
+ * understand. You get the diference between the colors and
+ * increase it. After this, get the gray tone
+ */
+
+void Emboss::embossImage(Digikam::DImg *orgImage, Digikam::DImg *destImage, int d)
+{
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ uchar* data = orgImage->bits();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar* Bits = destImage->bits();
+
+ // Initial copy
+ memcpy (Bits, data, destImage->numBytes());
+
+ double Depth = d / 10.0;
+
+ int progress;
+ int red, green, blue, gray;
+ Digikam::DColor color, colorOther;
+ int offset, offsetOther;
+
+ for (int h = 0 ; !m_cancel && (h < Height) ; h++)
+ {
+ for (int w = 0 ; !m_cancel && (w < Width) ; w++)
+ {
+ offset = getOffset(Width, w, h, bytesDepth);
+ offsetOther = getOffset(Width, w + Lim_Max (w, 1, Width), h + Lim_Max (h, 1, Height), bytesDepth);
+
+ color.setColor(Bits + offset, sixteenBit);
+ colorOther.setColor(Bits + offsetOther, sixteenBit);
+
+ if (sixteenBit)
+ {
+ red = abs ((int)((color.red() - colorOther.red()) * Depth + 32768));
+ green = abs ((int)((color.green() - colorOther.green()) * Depth + 32768));
+ blue = abs ((int)((color.blue() - colorOther.blue()) * Depth + 32768));
+
+ gray = CLAMP065535 ((red + green + blue) / 3);
+ }
+ else
+ {
+ red = abs ((int)((color.red() - colorOther.red()) * Depth + 128));
+ green = abs ((int)((color.green() - colorOther.green()) * Depth + 128));
+ blue = abs ((int)((color.blue() - colorOther.blue()) * Depth + 128));
+
+ gray = CLAMP0255 ((red + green + blue) / 3);
+ }
+
+ // Overwrite RGB values to destination. Alpha remains unchanged.
+ color.setRed(gray);
+ color.setGreen(gray);
+ color.setBlue(gray);
+ color.setPixel(Bits + offset);
+ }
+
+ progress = (int) (((double)h * 100.0) / Height);
+ if ( progress%5 == 0 )
+ postProgress( progress );
+ }
+}
+
+} // NameSpace DigikamEmbossImagesPlugin
diff --git a/src/imageplugins/emboss/emboss.h b/src/imageplugins/emboss/emboss.h
new file mode 100644
index 00000000..470755a6
--- /dev/null
+++ b/src/imageplugins/emboss/emboss.h
@@ -0,0 +1,75 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Emboss threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original Emboss algorithm copyrighted 2004 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EMBOSS_H
+#define EMBOSS_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamEmbossImagesPlugin
+{
+
+class Emboss : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ Emboss(Digikam::DImg *orgImage, TQObject *parent=0, int depth=30);
+ ~Emboss(){};
+
+private:
+
+ virtual void filterImage(void);
+
+
+ void embossImage(Digikam::DImg *orgImage, Digikam::DImg *destImage, int d);
+
+ // Function to limit the max and min values defined by the developer.
+
+ inline int Lim_Max (int Now, int Up, int Max)
+ {
+ --Max;
+ while (Now > Max - Up)
+ --Up;
+ return (Up);
+ };
+
+ inline int getOffset(int Width, int X, int Y, int bytesDepth)
+ {
+ return (Y * Width * bytesDepth) + (X * bytesDepth);
+ };
+
+private:
+
+ int m_depth;
+};
+
+} // NameSpace DigikamEmbossImagesPlugin
+
+#endif /* EMBOSS_H */
diff --git a/src/imageplugins/emboss/embosstool.cpp b/src/imageplugins/emboss/embosstool.cpp
new file mode 100644
index 00000000..f16ef4de
--- /dev/null
+++ b/src/imageplugins/emboss/embosstool.cpp
@@ -0,0 +1,167 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin to emboss
+ * an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "emboss.h"
+#include "embosstool.h"
+#include "embosstool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamEmbossImagesPlugin
+{
+
+EmbossTool::EmbossTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("emboss");
+ setToolName(i18n("Emboss"));
+ setToolIcon(SmallIcon("embosstool"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::PanIcon);
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 2, 1);
+ TQLabel *label1 = new TQLabel(i18n("Depth:"), m_gboxSettings->plainPage());
+
+ m_depthInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_depthInput->setRange(10, 300, 1);
+ m_depthInput->setDefaultValue(30);
+ TQWhatsThis::add( m_depthInput, i18n("<p>Set here the depth of the embossing image effect.") );
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 1);
+ grid->addMultiCellWidget(m_depthInput, 1, 1, 0, 1);
+ grid->setRowStretch(2, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "emboss Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_depthInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+EmbossTool::~EmbossTool()
+{
+}
+
+void EmbossTool::renderingFinished()
+{
+ m_depthInput->setEnabled(true);
+}
+
+void EmbossTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("emboss Tool");
+ m_depthInput->blockSignals(true);
+ m_depthInput->setValue(config->readNumEntry("DepthAjustment", m_depthInput->defaultValue()));
+ m_depthInput->blockSignals(false);
+}
+
+void EmbossTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("emboss Tool");
+ config->writeEntry("DepthAjustment", m_depthInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void EmbossTool::slotResetSettings()
+{
+ m_depthInput->blockSignals(true);
+ m_depthInput->slotReset();
+ m_depthInput->blockSignals(false);
+}
+
+void EmbossTool::prepareEffect()
+{
+ m_depthInput->setEnabled(false);
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+ int depth = m_depthInput->value();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Emboss(&image, this, depth)));
+}
+
+void EmbossTool::prepareFinal()
+{
+ m_depthInput->setEnabled(false);
+
+ int depth = m_depthInput->value();
+ ImageIface iface(0, 0);
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Emboss(iface.getOriginalImg(), this, depth)));
+}
+
+void EmbossTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void EmbossTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Emboss"), filter()->getTargetImage().bits());
+}
+
+} // NameSpace DigikamEmbossImagesPlugin
diff --git a/src/imageplugins/emboss/embosstool.h b/src/imageplugins/emboss/embosstool.h
new file mode 100644
index 00000000..224858f7
--- /dev/null
+++ b/src/imageplugins/emboss/embosstool.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin to emboss
+ * an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef EMBOSSTOOL_H
+#define EMBOSSTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamEmbossImagesPlugin
+{
+
+class EmbossTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ EmbossTool(TQObject* parent);
+ ~EmbossTool();
+
+private slots:
+
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KDcrawIface::RIntNumInput *m_depthInput;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamEmbossImagesPlugin
+
+#endif /* EMBOSSTOOL_H */
diff --git a/src/imageplugins/emboss/imageeffect_emboss.cpp b/src/imageplugins/emboss/imageeffect_emboss.cpp
new file mode 100644
index 00000000..b4557c80
--- /dev/null
+++ b/src/imageplugins/emboss/imageeffect_emboss.cpp
@@ -0,0 +1,170 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin to emboss
+ * an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "version.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "emboss.h"
+#include "imageeffect_emboss.h"
+#include "imageeffect_emboss.moc"
+
+namespace DigikamEmbossImagesPlugin
+{
+
+ImageEffect_Emboss::ImageEffect_Emboss(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Emboss Image"), "emboss", false, false, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Emboss Image"),
+ digikam_version,
+ I18N_NOOP("Emboss image effect plugin for digiKam."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2006, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pieter Z. Voloshyn", I18N_NOOP("Emboss algorithm"),
+ "pieter dot voloshyn at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 1, 1, 0, spacingHint());
+ TQLabel *label1 = new TQLabel(i18n("Depth:"), gboxSettings);
+
+ m_depthInput = new KIntNumInput(gboxSettings);
+ m_depthInput->setRange(10, 300, 1, true);
+ TQWhatsThis::add( m_depthInput, i18n("<p>Set here the depth of the embossing image effect.") );
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_depthInput, 1, 1, 0, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_depthInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_Emboss::~ImageEffect_Emboss()
+{
+}
+
+void ImageEffect_Emboss::renderingFinished()
+{
+ m_depthInput->setEnabled(true);
+}
+
+void ImageEffect_Emboss::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("emboss Tool Dialog");
+ m_depthInput->blockSignals(true);
+ m_depthInput->setValue(config->readNumEntry("DepthAjustment", 30));
+ m_depthInput->blockSignals(false);
+}
+
+void ImageEffect_Emboss::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("emboss Tool Dialog");
+ config->writeEntry("DepthAjustment", m_depthInput->value());
+ config->sync();
+}
+
+void ImageEffect_Emboss::resetValues()
+{
+ m_depthInput->blockSignals(true);
+ m_depthInput->setValue(30);
+ m_depthInput->blockSignals(false);
+}
+
+void ImageEffect_Emboss::prepareEffect()
+{
+ m_depthInput->setEnabled(false);
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+
+ int depth = m_depthInput->value();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new Emboss(&image, this, depth));
+}
+
+void ImageEffect_Emboss::prepareFinal()
+{
+ m_depthInput->setEnabled(false);
+
+ int depth = m_depthInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new Emboss(iface.getOriginalImg(), this, depth));
+}
+
+void ImageEffect_Emboss::putPreviewData(void)
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_Emboss::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Emboss"),
+ m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamEmbossImagesPlugin
+
diff --git a/src/imageplugins/emboss/imageeffect_emboss.h b/src/imageplugins/emboss/imageeffect_emboss.h
new file mode 100644
index 00000000..19cf4f77
--- /dev/null
+++ b/src/imageplugins/emboss/imageeffect_emboss.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin to emboss
+ * an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_EMBOSS_H
+#define IMAGEEFFECT_EMBOSS_H
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+class KIntNumInput;
+
+namespace DigikamEmbossImagesPlugin
+{
+
+class ImageEffect_Emboss : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Emboss(TQWidget* parent);
+ ~ImageEffect_Emboss();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KIntNumInput *m_depthInput;
+};
+
+} // NameSpace DigikamEmbossImagesPlugin
+
+#endif /* IMAGEEFFECT_EMBOSS_H */
diff --git a/src/imageplugins/emboss/imageplugin_emboss.cpp b/src/imageplugins/emboss/imageplugin_emboss.cpp
new file mode 100644
index 00000000..6bd616d6
--- /dev/null
+++ b/src/imageplugins/emboss/imageplugin_emboss.cpp
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin to emboss
+ * an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "embosstool.h"
+#include "imageplugin_emboss.h"
+#include "imageplugin_emboss.moc"
+
+using namespace DigikamEmbossImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_emboss,
+ KGenericFactory<ImagePlugin_Emboss>("digikamimageplugin_emboss"));
+
+ImagePlugin_Emboss::ImagePlugin_Emboss(TQObject *parent, const char*,
+ const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_Emboss")
+{
+ m_embossAction = new TDEAction(i18n("Emboss..."), "embosstool", 0,
+ this, TQ_SLOT(slotEmboss()),
+ actionCollection(), "imageplugin_emboss");
+
+ setXMLFile( "digikamimageplugin_emboss_ui.rc" );
+
+ DDebug() << "ImagePlugin_Emboss plugin loaded" << endl;
+}
+
+ImagePlugin_Emboss::~ImagePlugin_Emboss()
+{
+}
+
+void ImagePlugin_Emboss::setEnabledActions(bool enable)
+{
+ m_embossAction->setEnabled(enable);
+}
+
+void ImagePlugin_Emboss::slotEmboss()
+{
+ EmbossTool *tool = new EmbossTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/emboss/imageplugin_emboss.h b/src/imageplugins/emboss/imageplugin_emboss.h
new file mode 100644
index 00000000..1ccac064
--- /dev/null
+++ b/src/imageplugins/emboss/imageplugin_emboss.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin to emboss
+ * an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_EMBOSS_H
+#define IMAGEPLUGIN_EMBOSS_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Emboss : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Emboss(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_Emboss();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotEmboss();
+
+private:
+
+ TDEAction *m_embossAction;
+};
+
+#endif /* IMAGEPLUGIN_EMBOSS_H */
diff --git a/src/imageplugins/filmgrain/Makefile.am b/src/imageplugins/filmgrain/Makefile.am
new file mode 100644
index 00000000..c23c9d8e
--- /dev/null
+++ b/src/imageplugins/filmgrain/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_filmgrain_la_SOURCES = imageplugin_filmgrain.cpp \
+ filmgraintool.cpp filmgrain.cpp
+
+digikamimageplugin_filmgrain_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_filmgrain_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_filmgrain.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_filmgrain.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_filmgrain_ui.rc
+
diff --git a/src/imageplugins/filmgrain/digikamimageplugin_filmgrain.desktop b/src/imageplugins/filmgrain/digikamimageplugin_filmgrain.desktop
new file mode 100644
index 00000000..5ad6582b
--- /dev/null
+++ b/src/imageplugins/filmgrain/digikamimageplugin_filmgrain.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_FilmGrain
+Name[bg]=Приставка за снимки - Филмово зърно
+Name[da]=Billedplugin_Filmkorn
+Name[el]=ΠρόσθετοΕικόνας_ΚόκκοςΦιλμ
+Name[fi]=Filmirakeet
+Name[hr]=Zrnatost
+Name[it]=PluginImmagini_GranaPellicola
+Name[nl]=Afbeeldingsplugin_Filmkorrel
+Name[sr]=Зрнаст филм
+Name[sr@Latn]=Zrnast film
+Name[sv]=Insticksprogram för filmkorn
+Name[tr]=ResimEklentisi_FilmTanecikleri
+Name[xx]=xxImagePlugin_FilmGrainxx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Film grain image effect plugin for digiKam
+Comment[bg]=Приставка на digiKam за наподобяване на филмово зърно
+Comment[ca]=Connector pel digiKam d'efecte d'imatge de gra de pel·lícula
+Comment[cs]=Efektový modul pro tvorbu filmového zrna pro digiKam
+Comment[da]=Plugin til filmkorn-effekt på billeder i Digikam
+Comment[de]=digiKam-Modul zum Erzeugen eines Effektes von körnigem Film
+Comment[el]=Πρόσθετο εφέ κόκκου φιλμ για το digiKam
+Comment[es]=Plugin para digiKam de efectos de imagen tipo grano fino de película
+Comment[et]=DigiKami teralisuse pildiefektiplugin
+Comment[fa]=وصلۀ جلوۀ تصویر خرده فیلم برای digiKam
+Comment[fi]=Filmikuvien rakeisuuden jäljittelijä
+Comment[gl]=Un plugin de digiKam para o efeito de grao de filme
+Comment[hr]=digiKam dodatak za efekt zrnatosti filma
+Comment[is]=Íforrit fyrir digiKam sem líkir eftir filmukornum
+Comment[it]=Plugin per l'effetto di grana della pellicola delle immagini per digiKam
+Comment[ja]=digiKam フィルム粒子効果プラグイン
+Comment[nds]=digiKam-Moduul för Effekten vun körnig Film
+Comment[nl]=Digikam-plugin voor filmkorrel
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਫਿਲਮ ਗਰੇਨ ਚਿੱਤਰ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam oferująca efekty zniekształceń
+Comment[pt]=Um 'plugin' do digiKam para o efeito de grão de filme
+Comment[pt_BR]=Um 'plugin' do digiKam para o efeito de grão de filme
+Comment[ru]=Модуль зернистости изображения для digiKam
+Comment[sk]=digiKam obrázkový plugin pre efekt filmového zrna
+Comment[sr]=digiKam-ов прикључак за ефекат зрнастости филма
+Comment[sr@Latn]=digiKam-ov priključak za efekat zrnastosti filma
+Comment[sv]=Digikam insticksprogram för filmkornsbildeffekt
+Comment[tr]=digiKam için film tanecikleri etkisi eklentisi
+Comment[uk]=Втулок digiKam для створення ефекту фільму на зображеннях
+Comment[vi]=Phần bổ sung hiệu ứng chạm mịn mặt màng ảnh cho digiKam
+Comment[xx]=xxFilm grain image effect plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_filmgrain
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/filmgrain/digikamimageplugin_filmgrain_ui.rc b/src/imageplugins/filmgrain/digikamimageplugin_filmgrain_ui.rc
new file mode 100644
index 00000000..770c2dbd
--- /dev/null
+++ b/src/imageplugins/filmgrain/digikamimageplugin_filmgrain_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_filmgrain" >
+
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_filmgrain" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_filmgrain" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/filmgrain/filmgrain.cpp b/src/imageplugins/filmgrain/filmgrain.cpp
new file mode 100644
index 00000000..57616eb9
--- /dev/null
+++ b/src/imageplugins/filmgrain/filmgrain.cpp
@@ -0,0 +1,202 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : FilmGrain threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqdatetime.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimggaussianblur.h"
+#include "imagecurves.h"
+#include "imagehistogram.h"
+#include "dimgimagefilters.h"
+#include "filmgrain.h"
+
+namespace DigikamFilmGrainImagesPlugin
+{
+
+FilmGrain::FilmGrain(Digikam::DImg *orgImage, TQObject *parent, int sensibility)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "FilmGrain")
+{
+ m_sensibility = sensibility;
+ initFilter();
+}
+
+void FilmGrain::filterImage(void)
+{
+ filmgrainImage(&m_orgImage, m_sensibility);
+}
+
+// This method is based on the Simulate Film grain tutorial from GimpGuru.org web site
+// available at this url : http://www.gimpguru.org/Tutorials/FilmGrain
+
+void FilmGrain::filmgrainImage(Digikam::DImg *orgImage, int Sensibility)
+{
+ // Sensibility: 800..6400
+
+ if (Sensibility <= 0) return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ int bytesDepth = orgImage->bytesDepth();
+ bool sixteenBit = orgImage->sixteenBit();
+ uchar* data = orgImage->bits();
+
+ Digikam::DImg grain(Width, Height, sixteenBit); // Grain blured without curves adjustment.
+ Digikam::DImg mask(Width, Height, sixteenBit); // Grain mask with curves adjustment.
+ uchar* pGrainBits = grain.bits();
+ uchar* pMaskBits = mask.bits();
+ uchar* pOutBits = m_destImage.bits(); // Destination image with merged grain mask and original.
+
+ int Noise, Shade, nRand, component, progress;
+ uchar *ptr;
+ Digikam::DColor blendData, grainData, maskData, outData;
+
+ if (sixteenBit)
+ Noise = (Sensibility / 10 + 1) * 256 - 1;
+ else
+ Noise = Sensibility / 10;
+
+ // This value controls the shading pixel effect between original image and grain mask.
+ if (sixteenBit)
+ Shade = (52 + 1) * 256 - 1;
+ else
+ Shade = 52;
+
+ TQDateTime dt = TQDateTime::currentDateTime();
+ TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
+ uint seed = (uint) dt.secsTo(Y2000);
+
+ // Make gray grain mask.
+
+ grainData.setSixteenBit(sixteenBit);
+
+ for (int x = 0; !m_cancel && x < Width; x++)
+ {
+ for (int y = 0; !m_cancel && y < Height; y++)
+ {
+ ptr = pGrainBits + x*bytesDepth + (y*Width*bytesDepth);
+
+ nRand = (rand_r(&seed) % Noise) - (Noise / 2);
+ if (sixteenBit)
+ component = CLAMP(32768 + nRand, 0, 65535);
+ else
+ component = CLAMP(128 + nRand, 0, 255);
+
+ grainData.setRed (component);
+ grainData.setGreen(component);
+ grainData.setBlue (component);
+ grainData.setAlpha(0);
+
+ grainData.setPixel(ptr);
+ }
+
+ // Update progress bar in dialog.
+ progress = (int) (((double)x * 25.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+
+ // Smooth grain mask using gaussian blur with radius 1.
+ Digikam::DImgGaussianBlur(this, grain, grain, 25, 30, 1);
+
+ // Normally, film grain tends to be most noticeable in the midtones, and much less
+ // so in the shadows and highlights. Adjust histogram curve to adjust grain like this.
+
+ Digikam::ImageCurves *grainCurves = new Digikam::ImageCurves(sixteenBit);
+
+ // We modify only global luminosity of the grain.
+ if (sixteenBit)
+ {
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 8, TQPoint(32768, 32768));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(65535, 0));
+ }
+ else
+ {
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 8, TQPoint(128, 128));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(255, 0));
+ }
+
+ // Calculate curves and lut to apply on grain.
+ grainCurves->curvesCalculateCurve(Digikam::ImageHistogram::ValueChannel);
+ grainCurves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+ grainCurves->curvesLutProcess(pGrainBits, pMaskBits, Width, Height);
+
+ grain.reset();
+ delete grainCurves;
+
+ // Update progress bar in dialog.
+ postProgress( 40 );
+
+ // Merge src image with grain using shade coefficient.
+
+ int alpha;
+ // get composer for default blending
+ Digikam::DColorComposer *composer = Digikam::DColorComposer::getComposer(Digikam::DColorComposer::PorterDuffNone);
+
+ for (int x = 0; !m_cancel && x < Width; x++)
+ {
+ for (int y = 0; !m_cancel && y < Height; y++)
+ {
+ int offset = x*bytesDepth + (y*Width*bytesDepth);
+
+ // read color from orig image
+ blendData.setColor(data + offset, sixteenBit);
+ // read color from mask
+ maskData.setColor(pMaskBits + offset, sixteenBit);
+ // set shade as alpha value - it will be used as source alpha when blending
+ maskData.setAlpha(Shade);
+
+ // compose, write result to blendData.
+ // Preserve alpha, do not blend it (taken from old algorithm - correct?)
+ alpha = blendData.alpha();
+ composer->compose(blendData, maskData);
+ blendData.setAlpha(alpha);
+
+ // write to destination
+ blendData.setPixel(pOutBits + offset);
+ }
+
+ // Update progress bar in dialog.
+ progress = (int) (50.0 + ((double)x * 50.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+
+ delete composer;
+}
+
+} // NameSpace DigikamFilmGrainImagesPlugin
diff --git a/src/imageplugins/filmgrain/filmgrain.h b/src/imageplugins/filmgrain/filmgrain.h
new file mode 100644
index 00000000..42bcf03c
--- /dev/null
+++ b/src/imageplugins/filmgrain/filmgrain.h
@@ -0,0 +1,58 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : FilmGrain threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef FILMGRAIN_H
+#define FILMGRAIN_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamFilmGrainImagesPlugin
+{
+
+class FilmGrain : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ FilmGrain(Digikam::DImg *orgImage, TQObject *parent=0, int sensibility=12);
+
+ ~FilmGrain(){};
+
+private:
+
+ virtual void filterImage(void);
+
+ void filmgrainImage(Digikam::DImg *orgImage, int Sensibility);
+
+private:
+
+ int m_sensibility;
+};
+
+} // NameSpace DigikamFilmGrainImagesPlugin
+
+#endif /* FILMGRAIN_H */
diff --git a/src/imageplugins/filmgrain/filmgraintool.cpp b/src/imageplugins/filmgrain/filmgraintool.cpp
new file mode 100644
index 00000000..5799d814
--- /dev/null
+++ b/src/imageplugins/filmgrain/filmgraintool.cpp
@@ -0,0 +1,195 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin for add film
+ * grain on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlcdnumber.h>
+#include <tqslider.h>
+#include <tqlayout.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "filmgrain.h"
+#include "filmgraintool.h"
+#include "filmgraintool.moc"
+
+using namespace Digikam;
+
+namespace DigikamFilmGrainImagesPlugin
+{
+
+FilmGrainTool::FilmGrainTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("filmgrain");
+ setToolName(i18n("Film Grain"));
+ setToolIcon(SmallIcon("filmgrain"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 2, 1);
+ TQLabel *label1 = new TQLabel(i18n("Sensitivity (ISO):"), m_gboxSettings->plainPage());
+
+ m_sensibilitySlider = new TQSlider(2, 30, 1, 12, TQt::Horizontal, m_gboxSettings->plainPage());
+ m_sensibilitySlider->setTracking(false);
+ m_sensibilitySlider->setTickInterval(1);
+ m_sensibilitySlider->setTickmarks(TQSlider::Below);
+
+ m_sensibilityLCDValue = new TQLCDNumber(4, m_gboxSettings->plainPage());
+ m_sensibilityLCDValue->setSegmentStyle(TQLCDNumber::Flat);
+ m_sensibilityLCDValue->display(TQString::number(2400));
+ TQString whatsThis = i18n("<p>Set here the film ISO-sensitivity to "
+ "use for simulating the film graininess.");
+
+ TQWhatsThis::add(m_sensibilityLCDValue, whatsThis);
+ TQWhatsThis::add(m_sensibilitySlider, whatsThis);
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 1);
+ grid->addMultiCellWidget(m_sensibilitySlider, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_sensibilityLCDValue, 1, 1, 1, 1);
+ grid->setRowStretch(2, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "filmgrain Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()) );
+
+ // this connection is necessary to change the LCD display when
+ // the value is changed by single clicking on the slider
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(sliderMoved(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+}
+
+FilmGrainTool::~FilmGrainTool()
+{
+}
+
+void FilmGrainTool::renderingFinished()
+{
+ m_sensibilitySlider->setEnabled(true);
+}
+
+void FilmGrainTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("filmgrain Tool");
+ m_sensibilitySlider->blockSignals(true);
+ m_sensibilitySlider->setValue(config->readNumEntry("SensitivityAjustment", 12));
+ m_sensibilitySlider->blockSignals(false);
+ slotSliderMoved(m_sensibilitySlider->value());
+}
+
+void FilmGrainTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("filmgrain Tool");
+ config->writeEntry("SensitivityAjustment", m_sensibilitySlider->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void FilmGrainTool::slotResetSettings()
+{
+ m_sensibilitySlider->blockSignals(true);
+ m_sensibilitySlider->setValue(12);
+ m_sensibilitySlider->blockSignals(false);
+}
+
+void FilmGrainTool::slotSliderMoved(int v)
+{
+ m_sensibilityLCDValue->display( TQString::number(400+200*v) );
+}
+
+void FilmGrainTool::prepareEffect()
+{
+ m_sensibilitySlider->setEnabled(false);
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+ int s = 400 + 200 * m_sensibilitySlider->value();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new FilmGrain(&image, this, s)));
+}
+
+void FilmGrainTool::prepareFinal()
+{
+ m_sensibilitySlider->setEnabled(false);
+
+ int s = 400 + 200 * m_sensibilitySlider->value();
+
+ ImageIface iface(0, 0);
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new FilmGrain(iface.getOriginalImg(), this, s)));
+}
+
+void FilmGrainTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void FilmGrainTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Film Grain"), filter()->getTargetImage().bits());
+}
+
+} // NameSpace DigikamFilmGrainImagesPlugin
diff --git a/src/imageplugins/filmgrain/filmgraintool.h b/src/imageplugins/filmgrain/filmgraintool.h
new file mode 100644
index 00000000..1a179b53
--- /dev/null
+++ b/src/imageplugins/filmgrain/filmgraintool.h
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin for add film
+ * grain on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef FILMGRAINTOOL_H
+#define FILMGRAINTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQSlider;
+class TQLCDNumber;
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamFilmGrainImagesPlugin
+{
+
+class FilmGrainTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ FilmGrainTool(TQObject* parent);
+ ~FilmGrainTool();
+
+private slots:
+
+ void slotSliderMoved(int);
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQSlider *m_sensibilitySlider;
+
+ TQLCDNumber *m_sensibilityLCDValue;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamFilmGrainImagesPlugin
+
+#endif /* FILMGRAINTOOL_H */
diff --git a/src/imageplugins/filmgrain/imageeffect_filmgrain.cpp b/src/imageplugins/filmgrain/imageeffect_filmgrain.cpp
new file mode 100644
index 00000000..e008095a
--- /dev/null
+++ b/src/imageplugins/filmgrain/imageeffect_filmgrain.cpp
@@ -0,0 +1,195 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin for add film
+ * grain on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlcdnumber.h>
+#include <tqslider.h>
+#include <tqlayout.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "filmgrain.h"
+#include "imageeffect_filmgrain.h"
+#include "imageeffect_filmgrain.moc"
+
+namespace DigikamFilmGrainImagesPlugin
+{
+
+ImageEffect_FilmGrain::ImageEffect_FilmGrain(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Add Film Grain to Photograph"),
+ "filmgrain", false, false, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Film Grain"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to apply a film grain "
+ "effect to an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 1, 1, 0, spacingHint());
+ TQLabel *label1 = new TQLabel(i18n("Sensitivity (ISO):"), gboxSettings);
+
+ m_sensibilitySlider = new TQSlider(2, 30, 1, 12, TQt::Horizontal, gboxSettings);
+ m_sensibilitySlider->setTracking ( false );
+ m_sensibilitySlider->setTickInterval(1);
+ m_sensibilitySlider->setTickmarks(TQSlider::Below);
+
+ m_sensibilityLCDValue = new TQLCDNumber (4, gboxSettings);
+ m_sensibilityLCDValue->setSegmentStyle ( TQLCDNumber::Flat );
+ m_sensibilityLCDValue->display( TQString::number(2400) );
+ whatsThis = i18n("<p>Set here the film ISO-sensitivity to use for simulating the film graininess.");
+
+ TQWhatsThis::add( m_sensibilityLCDValue, whatsThis);
+ TQWhatsThis::add( m_sensibilitySlider, whatsThis);
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_sensibilitySlider, 1, 1, 0, 0);
+ gridSettings->addMultiCellWidget(m_sensibilityLCDValue, 1, 1, 1, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()) );
+
+ // this connection is necessary to change the LCD display when
+ // the value is changed by single clicking on the slider
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(sliderMoved(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+}
+
+ImageEffect_FilmGrain::~ImageEffect_FilmGrain()
+{
+}
+
+void ImageEffect_FilmGrain::renderingFinished()
+{
+ m_sensibilitySlider->setEnabled(true);
+}
+
+void ImageEffect_FilmGrain::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("filmgrain Tool Dialog");
+ m_sensibilitySlider->blockSignals(true);
+ m_sensibilitySlider->setValue(config->readNumEntry("SensitivityAjustment", 12));
+ m_sensibilitySlider->blockSignals(false);
+ slotSliderMoved(m_sensibilitySlider->value());
+}
+
+void ImageEffect_FilmGrain::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("filmgrain Tool Dialog");
+ config->writeEntry("SensitivityAjustment", m_sensibilitySlider->value());
+ config->sync();
+}
+
+void ImageEffect_FilmGrain::resetValues()
+{
+ m_sensibilitySlider->blockSignals(true);
+ m_sensibilitySlider->setValue(12);
+ m_sensibilitySlider->blockSignals(false);
+}
+
+void ImageEffect_FilmGrain::slotSliderMoved(int v)
+{
+ m_sensibilityLCDValue->display( TQString::number(400+200*v) );
+}
+
+void ImageEffect_FilmGrain::prepareEffect()
+{
+ m_sensibilitySlider->setEnabled(false);
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+ int s = 400 + 200 * m_sensibilitySlider->value();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new FilmGrain(&image, this, s));
+}
+
+void ImageEffect_FilmGrain::prepareFinal()
+{
+ m_sensibilitySlider->setEnabled(false);
+
+ int s = 400 + 200 * m_sensibilitySlider->value();
+
+ Digikam::ImageIface iface(0, 0);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new FilmGrain(iface.getOriginalImg(), this, s));
+}
+
+void ImageEffect_FilmGrain::putPreviewData(void)
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_FilmGrain::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Film Grain"), m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamFilmGrainImagesPlugin
+
diff --git a/src/imageplugins/filmgrain/imageeffect_filmgrain.h b/src/imageplugins/filmgrain/imageeffect_filmgrain.h
new file mode 100644
index 00000000..ba66e0a2
--- /dev/null
+++ b/src/imageplugins/filmgrain/imageeffect_filmgrain.h
@@ -0,0 +1,74 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-26
+ * Description : a digiKam image editor plugin for add film
+ * grain on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef IMAGEEFFECT_FILMGRAIN_H
+#define IMAGEEFFECT_FILMGRAIN_H
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+class TQSlider;
+class TQLCDNumber;
+
+namespace DigikamFilmGrainImagesPlugin
+{
+
+class ImageEffect_FilmGrain : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_FilmGrain(TQWidget* parent);
+ ~ImageEffect_FilmGrain();
+
+private slots:
+
+ void slotSliderMoved(int);
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQSlider *m_sensibilitySlider;
+
+ TQLCDNumber *m_sensibilityLCDValue;
+};
+
+} // NameSpace DigikamFilmGrainImagesPlugin
+
+#endif /* IMAGEEFFECT_FILMGRAIN_H */
diff --git a/src/imageplugins/filmgrain/imageplugin_filmgrain.cpp b/src/imageplugins/filmgrain/imageplugin_filmgrain.cpp
new file mode 100644
index 00000000..efa2c35d
--- /dev/null
+++ b/src/imageplugins/filmgrain/imageplugin_filmgrain.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-10-01
+ * Description : a digiKam image editor plugin for add film
+ * grain on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "filmgraintool.h"
+#include "imageplugin_filmgrain.h"
+#include "imageplugin_filmgrain.moc"
+
+using namespace DigikamFilmGrainImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_filmgrain,
+ KGenericFactory<ImagePlugin_FilmGrain>("digikamimageplugin_filmgrain"));
+
+ImagePlugin_FilmGrain::ImagePlugin_FilmGrain(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_FilmGrain")
+{
+ m_filmgrainAction = new TDEAction(i18n("Add Film Grain..."), "filmgrain", 0,
+ this, TQ_SLOT(slotFilmGrain()),
+ actionCollection(), "imageplugin_filmgrain");
+
+ setXMLFile( "digikamimageplugin_filmgrain_ui.rc" );
+
+ DDebug() << "ImagePlugin_FilmGrain plugin loaded" << endl;
+}
+
+ImagePlugin_FilmGrain::~ImagePlugin_FilmGrain()
+{
+}
+
+void ImagePlugin_FilmGrain::setEnabledActions(bool enable)
+{
+ m_filmgrainAction->setEnabled(enable);
+}
+
+void ImagePlugin_FilmGrain::slotFilmGrain()
+{
+ FilmGrainTool *tool = new FilmGrainTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/filmgrain/imageplugin_filmgrain.h b/src/imageplugins/filmgrain/imageplugin_filmgrain.h
new file mode 100644
index 00000000..495da122
--- /dev/null
+++ b/src/imageplugins/filmgrain/imageplugin_filmgrain.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-10-01
+ * Description : a digiKam image editor plugin for add film
+ * grain on an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_FILMGRAIN_H
+#define IMAGEPLUGIN_FILMGRAIN_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_FilmGrain : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_FilmGrain(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_FilmGrain();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotFilmGrain();
+
+private:
+
+ TDEAction *m_filmgrainAction;
+};
+
+#endif /* IMAGEPLUGIN_FILMGRAIN_H */
diff --git a/src/imageplugins/freerotation/Makefile.am b/src/imageplugins/freerotation/Makefile.am
new file mode 100644
index 00000000..e2b67283
--- /dev/null
+++ b/src/imageplugins/freerotation/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_freerotation_la_SOURCES = imageplugin_freerotation.cpp \
+ freerotationtool.cpp freerotation.cpp
+
+digikamimageplugin_freerotation_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_freerotation_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_freerotation.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_freerotation.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_freerotation_ui.rc
+
diff --git a/src/imageplugins/freerotation/digikamimageplugin_freerotation.desktop b/src/imageplugins/freerotation/digikamimageplugin_freerotation.desktop
new file mode 100644
index 00000000..47659a0d
--- /dev/null
+++ b/src/imageplugins/freerotation/digikamimageplugin_freerotation.desktop
@@ -0,0 +1,51 @@
+[Desktop Entry]
+Name=ImagePlugin_FreeRotation
+Name[bg]=Приставка за снимки - Свободно завъртане
+Name[da]=Billedplugin_Fri rotation
+Name[el]=ΠρόσθετοΕικόνας_ΕλεύθερηΠεριστροφή
+Name[fi]=Kierto
+Name[hr]=Slobodno obrtanje
+Name[it]=PluginImmagini_RotazioneLibera
+Name[nl]=Afbeeldingsplugin_VrijeRotatie
+Name[sr]=Слободно окретање
+Name[sr@Latn]=Slobodno okretanje
+Name[sv]=Insticksprogram för bildrotation
+Name[tr]=ResimEklentisi_SerbestDöndürme
+Name[xx]=xxImagePlugin_FreeRotationxx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Free rotation plugin for digiKam
+Comment[bg]=Приставка на digiKam за свободно завъртане на снимки
+Comment[ca]=Connector pel digiKam de gir lliure
+Comment[da]=Plugin til fri rotation af billeder til Digikam
+Comment[de]=digiKam-Modul zur freien Drehung von Bildern
+Comment[el]=Πρόσθετο ελεύθερης περιστροφής για το digiKam
+Comment[es]=Plugin para digiKam de rotación
+Comment[et]=DigiKami pildi vaba pööramise plugin
+Comment[fa]=وصلۀ چرخش آزاد برای digiKam
+Comment[fi]=Kiertää kuvaa vapaasti määritettävien asteiden verran
+Comment[gl]=Un plugin de digiKam para a rotazón libre da imaxe
+Comment[hr]=digiKam dodatak za slobodno obrtanje
+Comment[is]=Íforrit fyrir digiKam sem leyfir frjálsan snúning mynda
+Comment[it]=Plugin per la rotazione libera per digiKam
+Comment[ja]=digiKam 画像自由回転プラグイン
+Comment[nds]=digiKam-Moduul för't fre'e Dreihen
+Comment[nl]=Digikam-plugin voor vrije rotatie
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਮੁਕਤ ਘੁੰਮਾਉਣ ਵਾਲੀ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam umożliwiająca swobodny obrót obrazu
+Comment[pt]=Um 'plugin' do digiKam para a rotação livre da imagem
+Comment[pt_BR]=Plugin de Rotação Livre
+Comment[ru]=Модуль свободного вращения для digiKam
+Comment[sk]=digiKam plugin voľného otáčania
+Comment[sr]=digiKam-ов прикључак за слободно окретање
+Comment[sr@Latn]=digiKam-ov priključak za slobodno okretanje
+Comment[sv]=Digikam insticksprogram för bildrotation
+Comment[tr]=digiKam için serbest döndürme eklentisi
+Comment[uk]=Втулок вільного обертання для digiKam
+Comment[vi]=Phần bổ sung xoay tự do cho digiKam
+Comment[xx]=xxFree rotation plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_freerotation
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/freerotation/digikamimageplugin_freerotation_ui.rc b/src/imageplugins/freerotation/digikamimageplugin_freerotation_ui.rc
new file mode 100644
index 00000000..fbbf9605
--- /dev/null
+++ b/src/imageplugins/freerotation/digikamimageplugin_freerotation_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="4" name="digikamimageplugin_freerotation" >
+
+ <MenuBar>
+
+ <Menu name="Transform" ><text>Tra&amp;nsform</text>
+ <Action name="imageplugin_freerotation" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_freerotation" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/freerotation/freerotation.cpp b/src/imageplugins/freerotation/freerotation.cpp
new file mode 100644
index 00000000..3f8803d5
--- /dev/null
+++ b/src/imageplugins/freerotation/freerotation.cpp
@@ -0,0 +1,263 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-18
+ * Description : Free rotation threaded image filter.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Degrees to radian convertion coeff (PI/180). To optimize computation.
+#define DEG2RAD 0.017453292519943
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "freerotation.h"
+
+namespace DigikamFreeRotationImagesPlugin
+{
+
+FreeRotation::FreeRotation(Digikam::DImg *orgImage, TQObject *parent, double angle, bool antialiasing,
+ int autoCrop, TQColor backgroundColor, int orgW, int orgH)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "FreeRotation")
+{
+ m_angle = angle;
+ m_orgW = orgW;
+ m_orgH = orgH;
+ m_antiAlias = antialiasing;
+ m_autoCrop = autoCrop;
+ m_backgroundColor = backgroundColor;
+
+ initFilter();
+}
+
+void FreeRotation::filterImage(void)
+{
+ int progress;
+ int w, h, nw, nh, j, i = 0;
+ int nNewHeight, nNewWidth;
+ int nhdx, nhdy, nhsx, nhsy;
+ double lfSin, lfCos, lfx, lfy;
+
+ int nWidth = m_orgImage.width();
+ int nHeight = m_orgImage.height();
+
+ uchar *pBits = m_orgImage.bits();
+ unsigned short *pBits16 = (unsigned short*)m_orgImage.bits();
+
+ // first of all, we need to calcule the sin and cos of the given angle
+
+ lfSin = sin (m_angle * -DEG2RAD);
+ lfCos = cos (m_angle * -DEG2RAD);
+
+ // now, we have to calc the new size for the destination image
+
+ if ((lfSin * lfCos) < 0)
+ {
+ nNewWidth = ROUND (fabs (nWidth * lfCos - nHeight * lfSin));
+ nNewHeight = ROUND (fabs (nWidth * lfSin - nHeight * lfCos));
+ }
+ else
+ {
+ nNewWidth = ROUND (fabs (nWidth * lfCos + nHeight * lfSin));
+ nNewHeight = ROUND (fabs (nWidth * lfSin + nHeight * lfCos));
+ }
+
+ // getting the destination's center position
+
+ nhdx = nNewWidth / 2;
+ nhdy = nNewHeight / 2;
+
+ // getting the source's center position
+
+ nhsx = nWidth / 2;
+ nhsy = nHeight / 2;
+
+ // now, we have to alloc a new image
+
+ bool sixteenBit = m_orgImage.sixteenBit();
+
+ m_destImage = Digikam::DImg(nNewWidth, nNewHeight, sixteenBit, m_orgImage.hasAlpha());
+ m_destImage.fill( Digikam::DColor(m_backgroundColor.rgb(), sixteenBit) );
+
+ uchar *pResBits = m_destImage.bits();
+ unsigned short *pResBits16 = (unsigned short *)m_destImage.bits();
+
+ Digikam::DImgImageFilters filters;
+
+ // main loop
+
+ for (h = 0; !m_cancel && (h < nNewHeight); h++)
+ {
+ nh = h - nhdy;
+
+ for (w = 0; !m_cancel && (w < nNewWidth); w++)
+ {
+ nw = w - nhdx;
+
+ i = setPosition (nNewWidth, w, h);
+
+ lfx = (double)nw * lfCos - (double)nh * lfSin + nhsx;
+ lfy = (double)nw * lfSin + (double)nh * lfCos + nhsy;
+
+ if (isInside (nWidth, nHeight, (int)lfx, (int)lfy))
+ {
+ if (m_antiAlias)
+ {
+ if (!sixteenBit)
+ filters.pixelAntiAliasing(pBits, nWidth, nHeight, lfx, lfy,
+ &pResBits[i+3], &pResBits[i+2],
+ &pResBits[i+1], &pResBits[i]);
+ else
+ filters.pixelAntiAliasing16(pBits16, nWidth, nHeight, lfx, lfy,
+ &pResBits16[i+3], &pResBits16[i+2],
+ &pResBits16[i+1], &pResBits16[i]);
+ }
+ else
+ {
+ j = setPosition (nWidth, (int)lfx, (int)lfy);
+
+ for (int p = 0 ; p < 4 ; p++)
+ {
+ if (!sixteenBit)
+ pResBits[i] = pBits[j];
+ else
+ pResBits16[i] = pBits16[j];
+
+ i++;
+ j++;
+ }
+ }
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int)(((double)h * 100.0) / nNewHeight);
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+
+ // Compute the rotated destination image size using original image dimensions.
+ int W, H;
+ double absAngle = fabs(m_angle);
+
+ if (absAngle < 90.0)
+ {
+ W = (int)(m_orgW * cos(absAngle * DEG2RAD) + m_orgH * sin(absAngle * DEG2RAD));
+ H = (int)(m_orgH * cos(absAngle * DEG2RAD) + m_orgW * sin(absAngle * DEG2RAD));
+ }
+ else
+ {
+ H = (int)(m_orgW * cos((absAngle-90.0) * DEG2RAD) + m_orgH * sin((absAngle-90.0) * DEG2RAD));
+ W = (int)(m_orgH * cos((absAngle-90.0) * DEG2RAD) + m_orgW * sin((absAngle-90.0) * DEG2RAD));
+ }
+
+ // Auto-cropping destination image without black holes around.
+ TQRect autoCrop;
+
+ switch(m_autoCrop)
+ {
+ case WidestArea:
+ {
+ // 'Widest Area' method (by Renchi Raju).
+
+ autoCrop.setX( (int)(nHeight * sin(absAngle * DEG2RAD)) );
+ autoCrop.setY( (int)(nWidth * sin(absAngle * DEG2RAD)) );
+ autoCrop.setWidth( (int)(nNewWidth - 2*nHeight * sin(absAngle * DEG2RAD)) );
+ autoCrop.setHeight( (int)(nNewHeight - 2*nWidth * sin(absAngle * DEG2RAD)) );
+
+ if (!autoCrop.isValid())
+ {
+ m_destImage = Digikam::DImg(m_orgImage.width(), m_orgImage.height(),
+ m_orgImage.sixteenBit(), m_orgImage.hasAlpha());
+ m_destImage.fill( Digikam::DColor(m_backgroundColor.rgb(), sixteenBit) );
+ m_newSize = TQSize();
+ }
+ else
+ {
+ m_destImage = m_destImage.copy(autoCrop);
+ m_newSize.setWidth( (int)(W - 2*m_orgH * sin(absAngle * DEG2RAD)) );
+ m_newSize.setHeight( (int)(H - 2*m_orgW * sin(absAngle * DEG2RAD)) );
+ }
+ break;
+ }
+
+ case LargestArea:
+ {
+ // 'Largest Area' method (by Gerhard Kulzer).
+
+ float gamma = atan((float)nHeight / (float)nWidth);
+
+ if (absAngle < 90.0)
+ {
+ autoCrop.setWidth( (int)((float)nHeight / cos(absAngle*DEG2RAD) /
+ ( tan(gamma) + tan(absAngle*DEG2RAD) )) );
+ autoCrop.setHeight( (int)((float)autoCrop.width() * tan(gamma)) );
+ }
+ else
+ {
+ autoCrop.setHeight( (int)((float)nHeight / cos((absAngle-90.0)*DEG2RAD) /
+ ( tan(gamma) + tan((absAngle-90.0)*DEG2RAD) )) );
+ autoCrop.setWidth( (int)((float)autoCrop.height() * tan(gamma)) );
+ }
+
+ autoCrop.moveCenter( TQPoint(nNewWidth/2, nNewHeight/2));
+
+ if (!autoCrop.isValid())
+ {
+ m_destImage = Digikam::DImg(m_orgImage.width(), m_orgImage.height(),
+ m_orgImage.sixteenBit(), m_orgImage.hasAlpha());
+ m_destImage.fill( Digikam::DColor(m_backgroundColor.rgb(), sixteenBit) );
+ m_newSize = TQSize();
+ }
+ else
+ {
+ m_destImage = m_destImage.copy(autoCrop);
+ gamma = atan((float)m_orgH / (float)m_orgW);
+
+ if (absAngle < 90.0)
+ {
+ m_newSize.setWidth( (int)((float)m_orgH / cos(absAngle*DEG2RAD) /
+ ( tan(gamma) + tan(absAngle*DEG2RAD) )) );
+ m_newSize.setHeight( (int)((float)m_newSize.width() * tan(gamma)) );
+ }
+ else
+ {
+ m_newSize.setHeight( (int)((float)m_orgH / cos((absAngle-90.0)*DEG2RAD) /
+ ( tan(gamma) + tan((absAngle-90.0)*DEG2RAD) )) );
+ m_newSize.setWidth( (int)((float)m_newSize.height() * tan(gamma)) );
+ }
+ }
+ break;
+ }
+ default: // No auto croping.
+ {
+ m_newSize.setWidth( W );
+ m_newSize.setHeight( H );
+ break;
+ }
+ }
+}
+
+} // NameSpace DigikamFreeRotationImagesPlugin
diff --git a/src/imageplugins/freerotation/freerotation.h b/src/imageplugins/freerotation/freerotation.h
new file mode 100644
index 00000000..7dc4cc8c
--- /dev/null
+++ b/src/imageplugins/freerotation/freerotation.h
@@ -0,0 +1,94 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-18
+ * Description : Free rotation threaded image filter.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef FREE_ROTATION_H
+#define FREE_ROTATION_H
+
+// TQt includes.
+
+#include <tqsize.h>
+#include <tqcolor.h>
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamFreeRotationImagesPlugin
+{
+
+class FreeRotation : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ FreeRotation(Digikam::DImg *orgImage, TQObject *parent=0, double angle=0.0,
+ bool antialiasing=true, int autoCrop=NoAutoCrop, TQColor backgroundColor=TQt::black,
+ int orgW=0, int orgH=0);
+
+ ~FreeRotation(){};
+
+ TQSize getNewSize(void){ return m_newSize; };
+
+public:
+
+ enum AutoCropTypes
+ {
+ NoAutoCrop=0,
+ WidestArea,
+ LargestArea
+ };
+
+private:
+
+ virtual void filterImage(void);
+
+ inline int setPosition (int Width, int X, int Y)
+ {
+ return (Y *Width*4 + 4*X);
+ };
+
+ inline bool isInside (int Width, int Height, int X, int Y)
+ {
+ bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true);
+ bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true);
+ return (bIsWOk && bIsHOk);
+ };
+
+private:
+
+ bool m_antiAlias;
+
+ int m_autoCrop;
+ int m_orgW;
+ int m_orgH;
+
+ double m_angle;
+
+ TQSize m_newSize;
+
+ TQColor m_backgroundColor;
+};
+
+} // NameSpace DigikamFreeRotationImagesPlugin
+
+#endif /* FREE_ROTATION_H */
diff --git a/src/imageplugins/freerotation/freerotationtool.cpp b/src/imageplugins/freerotation/freerotationtool.cpp
new file mode 100644
index 00000000..b25f2196
--- /dev/null
+++ b/src/imageplugins/freerotation/freerotationtool.cpp
@@ -0,0 +1,318 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-28
+ * Description : a digiKam image editor plugin to process image
+ * free rotation.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kseparator.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "editortoolsettings.h"
+#include "freerotation.h"
+#include "freerotationtool.h"
+#include "freerotationtool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamFreeRotationImagesPlugin
+{
+
+FreeRotationTool::FreeRotationTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("freerotation");
+ setToolName(i18n("Free Rotation"));
+ setToolIcon(SmallIcon("freerotation"));
+
+ m_previewWidget = new ImageWidget("freerotation Tool", 0,
+ i18n("<p>This is the free rotation operation preview. "
+ "If you move the mouse cursor on this preview, "
+ "a vertical and horizontal dashed line will be drawn "
+ "to guide you in adjusting the free rotation correction. "
+ "Release the left mouse button to freeze the dashed "
+ "line's position."),
+ false, ImageGuideWidget::HVGuideMode);
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQString temp;
+ Digikam::ImageIface iface(0, 0);
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel,
+ EditorToolSettings::ColorGuide);
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 9, 2);
+
+ TQLabel *label1 = new TQLabel(i18n("New width:"), m_gboxSettings->plainPage());
+ m_newWidthLabel = new TQLabel(temp.setNum( iface.originalWidth()) + i18n(" px"), m_gboxSettings->plainPage());
+ m_newWidthLabel->setAlignment( AlignBottom | AlignRight );
+
+ TQLabel *label2 = new TQLabel(i18n("New height:"), m_gboxSettings->plainPage());
+ m_newHeightLabel = new TQLabel(temp.setNum( iface.originalHeight()) + i18n(" px"), m_gboxSettings->plainPage());
+ m_newHeightLabel->setAlignment( AlignBottom | AlignRight );
+
+ KSeparator *line = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ TQLabel *label3 = new TQLabel(i18n("Main angle:"), m_gboxSettings->plainPage());
+ m_angleInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_angleInput->setRange(-180, 180, 1);
+ m_angleInput->setDefaultValue(0);
+ TQWhatsThis::add( m_angleInput, i18n("<p>An angle in degrees by which to rotate the image. "
+ "A positive angle rotates the image clockwise; "
+ "a negative angle rotates it counter-clockwise."));
+
+ TQLabel *label4 = new TQLabel(i18n("Fine angle:"), m_gboxSettings->plainPage());
+ m_fineAngleInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_fineAngleInput->setRange(-5.0, 5.0, 0.01);
+ m_fineAngleInput->setDefaultValue(0);
+ TQWhatsThis::add( m_fineAngleInput, i18n("<p>This value in degrees will be added to main angle value "
+ "to set fine target angle."));
+
+ m_antialiasInput = new TQCheckBox(i18n("Anti-Aliasing"), m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_antialiasInput, i18n("<p>Enable this option to apply the anti-aliasing filter "
+ "to the rotated image. "
+ "In order to smooth the target image, it will be blurred a little."));
+
+ TQLabel *label5 = new TQLabel(i18n("Auto-crop:"), m_gboxSettings->plainPage());
+ m_autoCropCB = new RComboBox(m_gboxSettings->plainPage());
+ m_autoCropCB->insertItem( i18n("None") );
+ m_autoCropCB->insertItem( i18n("Widest Area") );
+ m_autoCropCB->insertItem( i18n("Largest Area") );
+ m_autoCropCB->setDefaultItem(FreeRotation::NoAutoCrop);
+ TQWhatsThis::add( m_autoCropCB, i18n("<p>Select the method to process image auto-cropping "
+ "to remove black frames around a rotated image."));
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 0);
+ grid->addMultiCellWidget(m_newWidthLabel, 0, 0, 1, 2);
+ grid->addMultiCellWidget(label2, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_newHeightLabel, 1, 1, 1, 2);
+ grid->addMultiCellWidget(line, 2, 2, 0, 2);
+ grid->addMultiCellWidget(label3, 3, 3, 0, 2);
+ grid->addMultiCellWidget(m_angleInput, 4, 4, 0, 2);
+ grid->addMultiCellWidget(label4, 5, 5, 0, 2);
+ grid->addMultiCellWidget(m_fineAngleInput, 6, 6, 0, 2);
+ grid->addMultiCellWidget(m_antialiasInput, 7, 7, 0, 2);
+ grid->addMultiCellWidget(label5, 8, 8, 0, 0);
+ grid->addMultiCellWidget(m_autoCropCB, 8, 8, 1, 2);
+ grid->setRowStretch(9, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_angleInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineAngleInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_antialiasInput, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_autoCropCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_gboxSettings, TQ_SIGNAL(signalColorGuideChanged()),
+ this, TQ_SLOT(slotColorGuideChanged()));
+}
+
+FreeRotationTool::~FreeRotationTool()
+{
+}
+
+void FreeRotationTool::slotColorGuideChanged()
+{
+ m_previewWidget->slotChangeGuideColor(m_gboxSettings->guideColor());
+ m_previewWidget->slotChangeGuideSize(m_gboxSettings->guideSize());
+}
+
+void FreeRotationTool::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("freerotation Tool");
+ m_angleInput->setValue(config->readNumEntry("Main Angle", m_angleInput->defaultValue()));
+ m_fineAngleInput->setValue(config->readDoubleNumEntry("Fine Angle", m_fineAngleInput->defaultValue()));
+ m_autoCropCB->setCurrentItem(config->readNumEntry("Auto Crop Type", m_autoCropCB->defaultItem()));
+ m_antialiasInput->setChecked(config->readBoolEntry("Anti Aliasing", true));
+ m_gboxSettings->setGuideColor(config->readColorEntry("Guide Color", &TQt::red));
+ m_gboxSettings->setGuideSize(config->readNumEntry("Guide Width", 1));
+
+ slotColorGuideChanged();
+ slotEffect();
+}
+
+void FreeRotationTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("freerotation Tool");
+ config->writeEntry("Main Angle", m_angleInput->value());
+ config->writeEntry("Fine Angle", m_fineAngleInput->value());
+ config->writeEntry("Auto Crop Type", m_autoCropCB->currentItem());
+ config->writeEntry("Anti Aliasing", m_antialiasInput->isChecked());
+ config->writeEntry("Guide Color", m_gboxSettings->guideColor());
+ config->writeEntry("Guide Width", m_gboxSettings->guideSize());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void FreeRotationTool::slotResetSettings()
+{
+ m_angleInput->blockSignals(true);
+ m_antialiasInput->blockSignals(true);
+ m_autoCropCB->blockSignals(true);
+
+ m_angleInput->slotReset();
+ m_fineAngleInput->slotReset();
+ m_antialiasInput->setChecked(true);
+ m_autoCropCB->slotReset();
+
+ m_angleInput->blockSignals(false);
+ m_antialiasInput->blockSignals(false);
+ m_autoCropCB->blockSignals(false);
+}
+
+void FreeRotationTool::prepareEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ m_angleInput->setEnabled(false);
+ m_fineAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+ m_autoCropCB->setEnabled(false);
+
+ double angle = m_angleInput->value() + m_fineAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ int autocrop = m_autoCropCB->currentItem();
+ TQColor background = toolView()->paletteBackgroundColor().rgb();
+ ImageIface* iface = m_previewWidget->imageIface();
+ int orgW = iface->originalWidth();
+ int orgH = iface->originalHeight();
+
+ uchar *data = iface->getPreviewImage();
+ DImg image(iface->previewWidth(), iface->previewHeight(), iface->previewSixteenBit(),
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new FreeRotation(&image, this, angle, antialiasing,
+ autocrop, background, orgW, orgH)));
+}
+
+void FreeRotationTool::prepareFinal()
+{
+ m_angleInput->setEnabled(false);
+ m_fineAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+ m_autoCropCB->setEnabled(false);
+
+ double angle = m_angleInput->value() + m_fineAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ int autocrop = m_autoCropCB->currentItem();
+ TQColor background = TQt::black;
+
+ ImageIface iface(0, 0);
+ int orgW = iface.originalWidth();
+ int orgH = iface.originalHeight();
+
+ uchar *data = iface.getOriginalImage();
+ DImg orgImage(orgW, orgH, iface.originalSixteenBit(), iface.originalHasAlpha(), data);
+ delete [] data;
+
+ setFilter(dynamic_cast<DImgThreadedFilter *>(new FreeRotation(&orgImage, this, angle, antialiasing,
+ autocrop, background, orgW, orgH)));
+}
+
+void FreeRotationTool::putPreviewData()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+
+ DImg imTemp = filter()->getTargetImage().smoothScale(w, h, TQSize::ScaleMin);
+ DImg imDest( w, h, filter()->getTargetImage().sixteenBit(),
+ filter()->getTargetImage().hasAlpha() );
+
+ imDest.fill(DColor(toolView()->paletteBackgroundColor().rgb(),
+ filter()->getTargetImage().sixteenBit()) );
+ imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+
+ m_previewWidget->updatePreview();
+ TQSize newSize = dynamic_cast<FreeRotation*>(filter())->getNewSize();
+ TQString temp;
+ m_newWidthLabel->setText(temp.setNum( newSize.width()) + i18n(" px") );
+ m_newHeightLabel->setText(temp.setNum( newSize.height()) + i18n(" px") );
+}
+
+void FreeRotationTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ DImg targetImage = filter()->getTargetImage();
+ iface.putOriginalImage(i18n("Free Rotation"),
+ targetImage.bits(),
+ targetImage.width(), targetImage.height());
+}
+
+void FreeRotationTool::renderingFinished()
+{
+ m_angleInput->setEnabled(true);
+ m_fineAngleInput->setEnabled(true);
+ m_antialiasInput->setEnabled(true);
+ m_autoCropCB->setEnabled(true);
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamFreeRotationImagesPlugin
diff --git a/src/imageplugins/freerotation/freerotationtool.h b/src/imageplugins/freerotation/freerotationtool.h
new file mode 100644
index 00000000..fd17f4ff
--- /dev/null
+++ b/src/imageplugins/freerotation/freerotationtool.h
@@ -0,0 +1,97 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-28
+ * Description : a digiKam image editor plugin to process image
+ * free rotation.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef FREEROTATIONTOOL_H
+#define FREEROTATIONTOOL_H
+
+// Local includes.
+
+#include "editortool.h"
+
+class TQFrame;
+class TQLabel;
+class TQCheckBox;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RDoubleNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImageWidget;
+}
+
+namespace DigikamFreeRotationImagesPlugin
+{
+
+class FreeRotationTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ FreeRotationTool(TQObject *parent);
+ ~FreeRotationTool();
+
+private slots:
+
+ void slotResetSettings();
+ void slotColorGuideChanged();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_newWidthLabel;
+ TQLabel *m_newHeightLabel;
+
+ TQCheckBox *m_antialiasInput;
+
+ KDcrawIface::RComboBox *m_autoCropCB;
+
+ KDcrawIface::RIntNumInput *m_angleInput;
+
+ KDcrawIface::RDoubleNumInput *m_fineAngleInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamFreeRotationImagesPlugin
+
+#endif /* FREEROTATIONTOOL_H */
diff --git a/src/imageplugins/freerotation/imageeffect_freerotation.cpp b/src/imageplugins/freerotation/imageeffect_freerotation.cpp
new file mode 100644
index 00000000..bce50f07
--- /dev/null
+++ b/src/imageplugins/freerotation/imageeffect_freerotation.cpp
@@ -0,0 +1,308 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-28
+ * Description : a digiKam image editor plugin to process image
+ * free rotation.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqcheckbox.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqimage.h>
+#include <tqcombobox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+#include <kcursor.h>
+#include <kseparator.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "freerotation.h"
+#include "imageeffect_freerotation.h"
+#include "imageeffect_freerotation.moc"
+
+namespace DigikamFreeRotationImagesPlugin
+{
+
+ImageEffect_FreeRotation::ImageEffect_FreeRotation(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Free Rotation"), "freerotation",
+ false, true, true, Digikam::ImageGuideWidget::HVGuideMode)
+{
+ // No need Abort button action.
+ showButton(User1, false);
+
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Free Rotation"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to process free image "
+ "rotation."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pieter Z. Voloshyn", I18N_NOOP("Free Rotation algorithm"),
+ "pieter dot voloshyn at gmail dot com");
+
+ setAboutData(about);
+
+ TQWhatsThis::add( m_imagePreviewWidget, i18n("<p>This is the free image operation preview. "
+ "If you move the mouse cursor on this preview, "
+ "a vertical and horizontal dashed line will be drawn "
+ "to guide you in adjusting the free rotation correction. "
+ "Release the left mouse button to freeze the dashed "
+ "line's position."));
+
+ // -------------------------------------------------------------
+
+ TQString temp;
+ Digikam::ImageIface iface(0, 0);
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 9, 2, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("New width:"), gboxSettings);
+ m_newWidthLabel = new TQLabel(temp.setNum( iface.originalWidth()) + i18n(" px"), gboxSettings);
+ m_newWidthLabel->setAlignment( AlignBottom | AlignRight );
+
+ TQLabel *label2 = new TQLabel(i18n("New height:"), gboxSettings);
+ m_newHeightLabel = new TQLabel(temp.setNum( iface.originalHeight()) + i18n(" px"), gboxSettings);
+ m_newHeightLabel->setAlignment( AlignBottom | AlignRight );
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridSettings->addMultiCellWidget(m_newWidthLabel, 0, 0, 1, 2);
+ gridSettings->addMultiCellWidget(label2, 1, 1, 0, 0);
+ gridSettings->addMultiCellWidget(m_newHeightLabel, 1, 1, 1, 2);
+
+ KSeparator *line = new KSeparator(Horizontal, gboxSettings);
+ gridSettings->addMultiCellWidget(line, 2, 2, 0, 2);
+
+ TQLabel *label3 = new TQLabel(i18n("Main angle:"), gboxSettings);
+ m_angleInput = new KIntNumInput(gboxSettings);
+ m_angleInput->setRange(-180, 180, 1, true);
+ m_angleInput->setValue(0);
+ TQWhatsThis::add( m_angleInput, i18n("<p>An angle in degrees by which to rotate the image. "
+ "A positive angle rotates the image clockwise; "
+ "a negative angle rotates it counter-clockwise."));
+
+ gridSettings->addMultiCellWidget(label3, 3, 3, 0, 2);
+ gridSettings->addMultiCellWidget(m_angleInput, 4, 4, 0, 2);
+
+ TQLabel *label4 = new TQLabel(i18n("Fine angle:"), gboxSettings);
+ m_fineAngleInput = new KDoubleNumInput(gboxSettings);
+ m_fineAngleInput->setRange(-5.0, 5.0, 0.01, true);
+ m_fineAngleInput->setValue(0);
+ TQWhatsThis::add( m_fineAngleInput, i18n("<p>This value in degrees will be added to main angle value "
+ "to set fine target angle."));
+
+ gridSettings->addMultiCellWidget(label4, 5, 5, 0, 2);
+ gridSettings->addMultiCellWidget(m_fineAngleInput, 6, 6, 0, 2);
+
+ m_antialiasInput = new TQCheckBox(i18n("Anti-Aliasing"), gboxSettings);
+ TQWhatsThis::add( m_antialiasInput, i18n("<p>Enable this option to apply the anti-aliasing filter "
+ "to the rotated image. "
+ "In order to smooth the target image, it will be blurred a little."));
+ gridSettings->addMultiCellWidget(m_antialiasInput, 7, 7, 0, 2);
+
+ TQLabel *label5 = new TQLabel(i18n("Auto-crop:"), gboxSettings);
+ m_autoCropCB = new TQComboBox(false, gboxSettings);
+ m_autoCropCB->insertItem( i18n("None") );
+ m_autoCropCB->insertItem( i18n("Widest Area") );
+ m_autoCropCB->insertItem( i18n("Largest Area") );
+ TQWhatsThis::add( m_autoCropCB, i18n("<p>Select the method to process image auto-cropping "
+ "to remove black frames around a rotated image."));
+ gridSettings->addMultiCellWidget(label5, 8, 8, 0, 0);
+ gridSettings->addMultiCellWidget(m_autoCropCB, 8, 8, 1, 2);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_angleInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineAngleInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_antialiasInput, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_autoCropCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+}
+
+ImageEffect_FreeRotation::~ImageEffect_FreeRotation()
+{
+}
+
+void ImageEffect_FreeRotation::readUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("freerotation Tool Dialog");
+ m_angleInput->setValue(config->readNumEntry("Main Angle", 0));
+ m_fineAngleInput->setValue(config->readDoubleNumEntry("Fine Angle", 0.0));
+ m_autoCropCB->setCurrentItem(config->readNumEntry("Auto Crop Type", FreeRotation::NoAutoCrop));
+ m_antialiasInput->setChecked(config->readBoolEntry("Anti Aliasing", true));
+ slotEffect();
+}
+
+void ImageEffect_FreeRotation::writeUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("freerotation Tool Dialog");
+ config->writeEntry("Main Angle", m_angleInput->value());
+ config->writeEntry("Fine Angle", m_fineAngleInput->value());
+ config->writeEntry( "Auto Crop Type", m_autoCropCB->currentItem() );
+ config->writeEntry( "Anti Aliasing", m_antialiasInput->isChecked() );
+ config->sync();
+}
+
+void ImageEffect_FreeRotation::resetValues()
+{
+ m_angleInput->blockSignals(true);
+ m_antialiasInput->blockSignals(true);
+ m_autoCropCB->blockSignals(true);
+ m_angleInput->setValue(0);
+ m_fineAngleInput->setValue(0.0);
+ m_antialiasInput->setChecked(true);
+ m_autoCropCB->setCurrentItem(FreeRotation::NoAutoCrop);
+ m_angleInput->blockSignals(false);
+ m_antialiasInput->blockSignals(false);
+ m_autoCropCB->blockSignals(false);
+}
+
+void ImageEffect_FreeRotation::prepareEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ m_angleInput->setEnabled(false);
+ m_fineAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+ m_autoCropCB->setEnabled(false);
+
+ double angle = m_angleInput->value() + m_fineAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ int autocrop = m_autoCropCB->currentItem();
+ TQColor background = paletteBackgroundColor().rgb();
+
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ int orgW = iface->originalWidth();
+ int orgH = iface->originalHeight();
+
+ uchar *data = iface->getPreviewImage();
+ Digikam::DImg image(iface->previewWidth(), iface->previewHeight(), iface->previewSixteenBit(),
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new FreeRotation(&image, this, angle, antialiasing, autocrop,
+ background, orgW, orgH));
+}
+
+void ImageEffect_FreeRotation::prepareFinal()
+{
+ m_angleInput->setEnabled(false);
+ m_fineAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+ m_autoCropCB->setEnabled(false);
+
+ double angle = m_angleInput->value() + m_fineAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ int autocrop = m_autoCropCB->currentItem();
+ TQColor background = TQt::black;
+
+ Digikam::ImageIface iface(0, 0);
+ int orgW = iface.originalWidth();
+ int orgH = iface.originalHeight();
+
+ uchar *data = iface.getOriginalImage();
+ Digikam::DImg orgImage(orgW, orgH, iface.originalSixteenBit(),
+ iface.originalHasAlpha(), data);
+ delete [] data;
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new FreeRotation(&orgImage, this, angle, antialiasing, autocrop,
+ background, orgW, orgH));
+}
+
+void ImageEffect_FreeRotation::putPreviewData(void)
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+
+ Digikam::DImg imTemp = m_threadedFilter->getTargetImage().smoothScale(w, h, TQSize::ScaleMin);
+ Digikam::DImg imDest( w, h, m_threadedFilter->getTargetImage().sixteenBit(),
+ m_threadedFilter->getTargetImage().hasAlpha() );
+
+ imDest.fill( Digikam::DColor(paletteBackgroundColor().rgb(),
+ m_threadedFilter->getTargetImage().sixteenBit()) );
+ imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+
+ m_imagePreviewWidget->updatePreview();
+ TQSize newSize = dynamic_cast<FreeRotation *>(m_threadedFilter)->getNewSize();
+ TQString temp;
+ m_newWidthLabel->setText(temp.setNum( newSize.width()) + i18n(" px") );
+ m_newHeightLabel->setText(temp.setNum( newSize.height()) + i18n(" px") );
+}
+
+void ImageEffect_FreeRotation::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ Digikam::DImg targetImage = m_threadedFilter->getTargetImage();
+ iface.putOriginalImage(i18n("Free Rotation"),
+ targetImage.bits(),
+ targetImage.width(), targetImage.height());
+}
+
+void ImageEffect_FreeRotation::renderingFinished()
+{
+ m_angleInput->setEnabled(true);
+ m_fineAngleInput->setEnabled(true);
+ m_antialiasInput->setEnabled(true);
+ m_autoCropCB->setEnabled(true);
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamFreeRotationImagesPlugin
+
diff --git a/src/imageplugins/freerotation/imageeffect_freerotation.h b/src/imageplugins/freerotation/imageeffect_freerotation.h
new file mode 100644
index 00000000..a90be09d
--- /dev/null
+++ b/src/imageplugins/freerotation/imageeffect_freerotation.h
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-28
+ * Description : a digiKam image editor plugin to process image
+ * free rotation.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_FREEROTATION_H
+#define IMAGEEFFECT_FREEROTATION_H
+
+// Local includes.
+
+#include "imageguidedlg.h"
+
+class TQFrame;
+class TQLabel;
+class TQCheckBox;
+class TQComboBox;
+
+class KIntNumInput;
+class KDoubleNumInput;
+
+namespace DigikamFreeRotationImagesPlugin
+{
+
+class ImageEffect_FreeRotation : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_FreeRotation(TQWidget *parent);
+ ~ImageEffect_FreeRotation();
+
+private slots:
+
+ void readUserSettings(void);
+
+protected:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_newWidthLabel;
+ TQLabel *m_newHeightLabel;
+
+ TQCheckBox *m_antialiasInput;
+
+ TQComboBox *m_autoCropCB;
+
+ KIntNumInput *m_angleInput;
+
+ KDoubleNumInput *m_fineAngleInput;
+};
+
+} // NameSpace DigikamFreeRotationImagesPlugin
+
+#endif /* IMAGEEFFECT_FREEROTATION_H */
diff --git a/src/imageplugins/freerotation/imageplugin_freerotation.cpp b/src/imageplugins/freerotation/imageplugin_freerotation.cpp
new file mode 100644
index 00000000..0c04a2ca
--- /dev/null
+++ b/src/imageplugins/freerotation/imageplugin_freerotation.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-28
+ * Description : a digiKam image editor plugin to process image
+ * free rotation.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "freerotationtool.h"
+#include "imageplugin_freerotation.h"
+#include "imageplugin_freerotation.moc"
+
+using namespace DigikamFreeRotationImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_freerotation,
+ KGenericFactory<ImagePlugin_FreeRotation>("digikamimageplugin_freerotation"));
+
+ImagePlugin_FreeRotation::ImagePlugin_FreeRotation(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_FreeRotation")
+{
+ m_freerotationAction = new TDEAction(i18n("Free Rotation..."), "freerotation", 0,
+ this, TQ_SLOT(slotFreeRotation()),
+ actionCollection(), "imageplugin_freerotation");
+
+ setXMLFile("digikamimageplugin_freerotation_ui.rc");
+
+ DDebug() << "ImagePlugin_FreeRotation plugin loaded" << endl;
+}
+
+ImagePlugin_FreeRotation::~ImagePlugin_FreeRotation()
+{
+}
+
+void ImagePlugin_FreeRotation::setEnabledActions(bool enable)
+{
+ m_freerotationAction->setEnabled(enable);
+}
+
+void ImagePlugin_FreeRotation::slotFreeRotation()
+{
+ FreeRotationTool *tool = new FreeRotationTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/freerotation/imageplugin_freerotation.h b/src/imageplugins/freerotation/imageplugin_freerotation.h
new file mode 100644
index 00000000..2a0c2627
--- /dev/null
+++ b/src/imageplugins/freerotation/imageplugin_freerotation.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-28
+ * Description : a digiKam image editor plugin to process image
+ * free rotation.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_FREEROTATION_H
+#define IMAGEPLUGIN_FREEROTATION_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_FreeRotation : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_FreeRotation(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_FreeRotation();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotFreeRotation();
+
+private:
+
+ TDEAction *m_freerotationAction;
+};
+
+#endif /* IMAGEPLUGIN_FREEROTATION_H */
diff --git a/src/imageplugins/hotpixels/Makefile.am b/src/imageplugins/hotpixels/Makefile.am
new file mode 100644
index 00000000..711fe84e
--- /dev/null
+++ b/src/imageplugins/hotpixels/Makefile.am
@@ -0,0 +1,36 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_hotpixels_la_SOURCES = blackframeparser.cpp weights.cpp \
+ hotpixelfixer.cpp imageplugin_hotpixels.cpp \
+ blackframelistview.cpp hotpixelstool.cpp
+
+digikamimageplugin_hotpixels_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_hotpixels_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_hotpixels.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_hotpixels.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_hotpixels_ui.rc
+
diff --git a/src/imageplugins/hotpixels/TODO b/src/imageplugins/hotpixels/TODO
new file mode 100644
index 00000000..880e1a36
--- /dev/null
+++ b/src/imageplugins/hotpixels/TODO
@@ -0,0 +1,4 @@
+- Store black frames. Include the fullsize image to be able to reedit it if necessary.
+- Add a hand hot-pixel editor for the hot pixels on the black frame
+- Use the same hot-pixel editor from the image view, to edit a new black frame with the added data
+
diff --git a/src/imageplugins/hotpixels/blackframelistview.cpp b/src/imageplugins/hotpixels/blackframelistview.cpp
new file mode 100644
index 00000000..1202b094
--- /dev/null
+++ b/src/imageplugins/hotpixels/blackframelistview.cpp
@@ -0,0 +1,176 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-05
+ * Description : a ListView to display black frames
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+#define THUMB_WIDTH 150
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqtooltip.h>
+
+// Local includes.
+
+#include "blackframelistview.h"
+#include "blackframelistview.moc"
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+BlackFrameListView::BlackFrameListView(TQWidget* parent)
+ : TQListView(parent)
+{
+ addColumn(i18n("Preview"));
+ addColumn(i18n("Size"));
+ addColumn(i18n("This is a column which will contain the amount of HotPixels "
+ "found in the black frame file", "HP"));
+ setAllColumnsShowFocus(true);
+ setResizeMode(TQListView::LastColumn);
+ setSelectionMode(TQListView::Single);
+}
+
+// --------------------------------------------------------------------------
+
+BlackFrameListViewItem::BlackFrameListViewItem(BlackFrameListView* parent, const KURL &url)
+ : TQObject(parent), TQListViewItem(parent)
+{
+ m_parent = parent;
+ m_blackFrameURL = url;
+ m_parser = new BlackFrameParser(parent);
+ m_parser->parseBlackFrame(url);
+
+ connect(m_parser, TQ_SIGNAL(parsed(TQValueList<HotPixel>)),
+ this, TQ_SLOT(slotParsed(TQValueList<HotPixel>)));
+
+ connect(this, TQ_SIGNAL(parsed(TQValueList<HotPixel>, const KURL&)),
+ parent, TQ_SLOT(slotParsed(TQValueList<HotPixel>, const KURL&)));
+
+ connect(m_parser, TQ_SIGNAL(signalLoadingProgress(float)),
+ this, TQ_SIGNAL(signalLoadingProgress(float)));
+
+ connect(m_parser, TQ_SIGNAL(signalLoadingComplete()),
+ this, TQ_SIGNAL(signalLoadingComplete()));
+}
+
+void BlackFrameListViewItem::activate()
+{
+ TQToolTip::add( m_parent, m_blackFrameDesc);
+ emit parsed(m_hotPixels, m_blackFrameURL);
+}
+
+TQString BlackFrameListViewItem::text(int column)const
+{
+ switch (column)
+ {
+ case 0:
+ {
+ // First column includes the pixmap
+ break;
+ }
+ case 1:
+ {
+ // The image size.
+ if (!m_imageSize.isEmpty())
+ return (TQString("%1x%2").arg(m_imageSize.width()).arg(m_imageSize.height()));
+ break;
+ }
+ case 2:
+ {
+ // The amount of hot pixels found in the black frame.
+ return (TQString::number(m_hotPixels.count()));
+ break;
+ }
+ }
+
+ return TQString();
+}
+
+void BlackFrameListViewItem::paintCell(TQPainter* p, const TQColorGroup& cg, int column, int width, int align)
+{
+ //Let the normal listview item draw it all for now
+ TQListViewItem::paintCell(p, cg, column, width, align);
+}
+
+void BlackFrameListViewItem::slotParsed(TQValueList<HotPixel> hotPixels)
+{
+ m_hotPixels = hotPixels;
+ m_image = m_parser->image();
+ m_imageSize = m_image.size();
+ m_thumb = thumb(TQSize(THUMB_WIDTH, THUMB_WIDTH/3*2));
+ setPixmap(0, m_thumb);
+
+ m_blackFrameDesc = TQString("<p><b>" + m_blackFrameURL.fileName() + "</b>:<p>");
+ TQValueList <HotPixel>::Iterator end(m_hotPixels.end());
+ for (TQValueList <HotPixel>::Iterator it = m_hotPixels.begin() ; it != end ; ++it)
+ m_blackFrameDesc.append( TQString("[%1,%2] ").arg((*it).x()).arg((*it).y()) );
+
+ emit parsed(m_hotPixels, m_blackFrameURL);
+}
+
+TQPixmap BlackFrameListViewItem::thumb(const TQSize& size)
+{
+ TQPixmap thumb;
+
+ //First scale it down to the size
+ thumb = m_image.smoothScale(size, TQImage::ScaleMin);
+
+ //And draw the hot pixel positions on the thumb
+ TQPainter p(&thumb);
+
+ //Take scaling into account
+ float xRatio, yRatio;
+ float hpThumbX, hpThumbY;
+ TQRect hpRect;
+
+ xRatio = (float)size.width()/(float)m_image.width();
+ yRatio = (float)size.height()/(float)m_image.height();
+
+ //Draw hot pixels one by one
+ TQValueList <HotPixel>::Iterator it;
+ TQValueList <HotPixel>::Iterator end(m_hotPixels.end());
+ for (it=m_hotPixels.begin() ; it!=end ; ++it)
+ {
+ hpRect = (*it).rect;
+ hpThumbX = (hpRect.x()+hpRect.width()/2)*xRatio;
+ hpThumbY = (hpRect.y()+hpRect.height()/2)*yRatio;
+
+ p.setPen(TQPen(TQt::black));
+ p.drawLine((int)hpThumbX, (int)hpThumbY-1, (int)hpThumbX, (int)hpThumbY+1);
+ p.drawLine((int)hpThumbX-1, (int)hpThumbY, (int)hpThumbX+1, (int)hpThumbY);
+ p.setPen(TQPen(TQt::white));
+ p.drawPoint((int)hpThumbX-1, (int)hpThumbY-1);
+ p.drawPoint((int)hpThumbX+1, (int)hpThumbY+1);
+ p.drawPoint((int)hpThumbX-1, (int)hpThumbY+1);
+ p.drawPoint((int)hpThumbX+1, (int)hpThumbY-1);
+ }
+
+ return thumb;
+}
+
+int BlackFrameListViewItem::width(const TQFontMetrics& fm,const TQListView* lv,int c)const
+{
+ if (c==0) return THUMB_WIDTH;
+ else return TQListViewItem::width(fm,lv,c);
+}
+
+} // NameSpace DigikamHotPixelsImagesPlugin
diff --git a/src/imageplugins/hotpixels/blackframelistview.h b/src/imageplugins/hotpixels/blackframelistview.h
new file mode 100644
index 00000000..977d226d
--- /dev/null
+++ b/src/imageplugins/hotpixels/blackframelistview.h
@@ -0,0 +1,127 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-05
+ * Description : a ListView to display black frames
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BLACKFRAMELISTVIEW_H
+#define BLACKFRAMELISTVIEW_H
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqstring.h>
+#include <tqsize.h>
+#include <tqpoint.h>
+#include <tqvaluelist.h>
+#include <tqlistview.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "blackframeparser.h"
+#include "hotpixel.h"
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+class BlackFrameListView : public TQListView
+{
+ TQ_OBJECT
+
+
+public:
+
+ BlackFrameListView(TQWidget* parent=0);
+ ~BlackFrameListView(){};
+
+signals:
+
+ void blackFrameSelected(TQValueList<HotPixel>, const KURL&);
+
+private slots:
+
+ void slotParsed(TQValueList<HotPixel> hotPixels, const KURL& blackFrameURL)
+ {
+ emit blackFrameSelected(hotPixels, blackFrameURL);
+ };
+};
+
+// --------------------------------------------------------------------------
+
+class BlackFrameListViewItem : public TQObject, TQListViewItem
+{
+TQ_OBJECT
+
+
+public:
+
+ BlackFrameListViewItem(BlackFrameListView* parent, const KURL &url);
+ ~BlackFrameListViewItem(){};
+
+ virtual TQString text(int column)const;
+ virtual void paintCell(TQPainter* p, const TQColorGroup& cg, int column, int width, int align);
+ virtual int width(const TQFontMetrics& fm, const TQListView* lv, int c)const;
+
+signals:
+
+ void parsed(TQValueList<HotPixel>, const KURL&);
+ void signalLoadingProgress(float);
+ void signalLoadingComplete();
+
+protected:
+
+ void activate();
+
+private:
+
+ TQPixmap thumb(const TQSize& size);
+
+private slots:
+
+ void slotParsed(TQValueList<HotPixel>);
+
+private:
+
+ // Data contained within each listview item
+ TQImage m_thumb;
+ TQImage m_image;
+
+ TQSize m_imageSize;
+
+ TQValueList <HotPixel> m_hotPixels;
+
+ TQString m_blackFrameDesc;
+
+ KURL m_blackFrameURL;
+
+ BlackFrameParser *m_parser;
+
+ BlackFrameListView *m_parent;
+};
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
+#endif // BLACKFRAMELISTVIEW_H
diff --git a/src/imageplugins/hotpixels/blackframeparser.cpp b/src/imageplugins/hotpixels/blackframeparser.cpp
new file mode 100644
index 00000000..7306f4e6
--- /dev/null
+++ b/src/imageplugins/hotpixels/blackframeparser.cpp
@@ -0,0 +1,211 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : black frames parser
+ *
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot net>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Part of the algorithm for finding the hot pixels was based on
+ * the code of jpegpixi, which was released under the GPL license,
+ * and is Copyright (C) 2003, 2004 Martin Dickopp
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Denominator for relative quantities.
+#define DENOM (DENOM_STQRT * DENOM_STQRT)
+
+// Square root of denominator for relative quantities.
+#define DENOM_STQRT 10000
+
+// Convert relative to absolute numbers. Care must be taken not to overflow integers.
+#define REL_TO_ABS(n,m) \
+ ((((n) / DENOM_STQRT) * (m) + ((n) % DENOM_STQRT) * (m) / DENOM_STQRT) / DENOM_STQRT)
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqstringlist.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeversion.h>
+#include <tdeio/netaccess.h>
+#include <tdeio/job.h>
+
+// Local includes.
+
+#include "blackframeparser.h"
+#include "blackframeparser.moc"
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+BlackFrameParser::BlackFrameParser(TQObject *parent)
+ : TQObject(parent)
+{
+ m_imageLoaderThread = 0;
+}
+
+BlackFrameParser::~BlackFrameParser()
+{
+ delete m_imageLoaderThread;
+}
+
+void BlackFrameParser::parseHotPixels(const TQString &file)
+{
+ parseBlackFrame(KURL(file));
+}
+
+void BlackFrameParser::parseBlackFrame(const KURL &url)
+{
+#if KDE_IS_VERSION(3,2,0)
+ TDEIO::NetAccess::download(url, m_localFile, kapp->activeWindow());
+#else
+ TDEIO::NetAccess::download(url, m_localFile);
+#endif
+
+ if (!m_imageLoaderThread)
+ {
+ m_imageLoaderThread = new LoadSaveThread();
+
+ connect(m_imageLoaderThread, TQ_SIGNAL(signalLoadingProgress(const LoadingDescription&, float)),
+ this, TQ_SLOT(slotLoadingProgress(const LoadingDescription&, float)));
+
+ connect(m_imageLoaderThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription&, const DImg&)),
+ this, TQ_SLOT(slotLoadImageFromUrlComplete(const LoadingDescription&, const DImg&)));
+ }
+
+ LoadingDescription desc = LoadingDescription(m_localFile, DRawDecoding());
+ m_imageLoaderThread->load(desc);
+}
+
+void BlackFrameParser::slotLoadingProgress(const LoadingDescription&, float v)
+{
+ emit signalLoadingProgress(v);
+}
+
+void BlackFrameParser::slotLoadImageFromUrlComplete(const LoadingDescription&, const DImg& img)
+{
+ DImg image(img);
+ m_Image = image.copyTQImage();
+ blackFrameParsing();
+ emit signalLoadingComplete();
+}
+
+void BlackFrameParser::parseBlackFrame(TQImage& img)
+{
+ m_Image = img;
+ blackFrameParsing();
+}
+
+// Parses black frames
+
+void BlackFrameParser::blackFrameParsing()
+{
+ // Now find the hot pixels and store them in a list
+ TQValueList<HotPixel> hpList;
+
+ for (int y=0 ; y < m_Image.height() ; ++y)
+ {
+ for (int x=0 ; x < m_Image.width() ; ++x)
+ {
+ //Get each point in the image
+ TQRgb pixrgb = m_Image.pixel(x,y);
+ TQColor color; color.setRgb(pixrgb);
+
+ // Find maximum component value.
+ int maxValue;
+ int threshold = DENOM/10;
+ const int threshold_value = REL_TO_ABS(threshold, 255);
+ maxValue = (color.red()>color.blue()) ? color.red() : color.blue();
+ if (color.green() > maxValue) maxValue = color.green();
+
+ // If the component is bigger than the threshold, add the point
+ if (maxValue > threshold_value)
+ {
+ HotPixel point;
+ point.rect = TQRect (x, y, 1, 1);
+ //TODO:check this
+ point.luminosity = ((2 * DENOM) / 255 ) * maxValue / 2;
+
+ hpList.append(point);
+ }
+ }
+ }
+
+ //Now join points together into groups
+ consolidatePixels (hpList);
+
+ //And notify
+ emit parsed(hpList);
+}
+
+// Consolidate adjacent points into larger points.
+
+void BlackFrameParser::consolidatePixels (TQValueList<HotPixel>& list)
+{
+ if (list.isEmpty())
+ return;
+
+ /* Consolidate horizontally. */
+
+ TQValueList<HotPixel>::iterator it, prevPointIt;
+
+ prevPointIt = list.begin();
+ it = list.begin();
+ ++it;
+
+ HotPixel tmp;
+ HotPixel point;
+ HotPixel point_below;
+ TQValueList<HotPixel>::iterator end(list.end());
+ for (; it != end; ++it )
+ {
+ while (1)
+ {
+ point = (*it);
+ tmp = point;
+
+ TQValueList<HotPixel>::Iterator point_below_it;
+ point_below_it = list.find (tmp); //find any intersecting hotp below tmp
+ if (point_below_it != list.end())
+ {
+ point_below =* point_below_it;
+ validateAndConsolidate (&point, &point_below);
+
+ point.rect.setX(MIN(point.x(), point_below.x()));
+ point.rect.setWidth(MAX(point.x() + point.width(),
+ point_below.x() + point_below.width()) - point.x());
+ point.rect.setHeight(MAX(point.y() + point.height(),
+ point_below.y() + point_below.height()) - point.y());
+ *it = point;
+ list.remove (point_below_it); //TODO: Check! this could remove it++?
+ }
+ else
+ break;
+ }
+ }
+}
+
+void BlackFrameParser::validateAndConsolidate (HotPixel *a, HotPixel *b)
+{
+ a->luminosity = MAX (a->luminosity, b->luminosity);
+}
+
+} // NameSpace DigikamHotPixelsImagesPlugin
diff --git a/src/imageplugins/hotpixels/blackframeparser.h b/src/imageplugins/hotpixels/blackframeparser.h
new file mode 100644
index 00000000..f13ebdc4
--- /dev/null
+++ b/src/imageplugins/hotpixels/blackframeparser.h
@@ -0,0 +1,102 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : black frames parser
+ *
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot net>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Part of the algorithm for finding the hot pixels was based on
+ * the code of jpegpixi, which was released under the GPL license,
+ * and is Copyright (C) 2003, 2004 Martin Dickopp
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef BLACKFRAMEPARSER_H
+#define BLACKFRAMEPARSER_H
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqobject.h>
+#include <tqvaluelist.h>
+#include <tqstring.h>
+#include <tqrect.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "loadsavethread.h"
+#include "hotpixel.h"
+
+using namespace Digikam;
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+class BlackFrameParser: public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ BlackFrameParser(TQObject *parent);
+ ~BlackFrameParser();
+
+ void parseHotPixels(const TQString &file);
+ void parseBlackFrame(const KURL &url);
+ void parseBlackFrame(TQImage& img);
+ TQImage image(){return m_Image;}
+
+signals:
+
+ void parsed(TQValueList<HotPixel>);
+ void signalLoadingProgress(float);
+ void signalLoadingComplete();
+
+private slots:
+
+ void slotLoadingProgress(const LoadingDescription&, float);
+ void slotLoadImageFromUrlComplete(const LoadingDescription&, const DImg&);
+
+private:
+
+ void blackFrameParsing();
+ void consolidatePixels(TQValueList<HotPixel>& list);
+ void validateAndConsolidate(HotPixel *a, HotPixel *b);
+
+private:
+
+ TQString m_OutputString;
+ TQString m_localFile;
+
+ TQImage m_Image;
+
+ LoadSaveThread *m_imageLoaderThread;
+};
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
+#endif // BLACKFRAMEPARSER_H
diff --git a/src/imageplugins/hotpixels/digikamimageplugin_hotpixels.desktop b/src/imageplugins/hotpixels/digikamimageplugin_hotpixels.desktop
new file mode 100644
index 00000000..5b12fe2c
--- /dev/null
+++ b/src/imageplugins/hotpixels/digikamimageplugin_hotpixels.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=ImagePlugin_HotPixels
+Name[bg]=Приставка за снимки - Горещи пиксели
+Name[el]=ΠρόσθετοΕικόνας_ΈντοναΕικονοστοιχεία
+Name[fi]=KuumatPikselit
+Name[hr]=Vrući pikseli
+Name[it]=PluginImmagini_PixelBruciati
+Name[ms]=ImagePlugin_PikselPanas
+Name[nl]=Afbeeldingsplugin_HotPixels
+Name[sr]=Врући пиксели
+Name[sr@Latn]=Vrući pikseli
+Name[sv]=Insticksprogram för heta bildpunkter
+Name[tr]=ResimEklentisi_Çekirdek
+Name[xx]=xxImagePlugin_HotPixelsxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Hot pixel correction plugin for digiKam
+Comment[ca]=Connector per al digiKam de correcció de píxels cremats
+Comment[da]=Hot pixel rettelsesplugin for Digikam
+Comment[de]=digiKam-Modul zur Korrektur von heißen (defekten) Pixeln
+Comment[el]=Πρόσθετο διόρθωσης έντονων εικονοστοιχείων για το digiKam
+Comment[es]=Plugin para digiKampara corregir los píxeles quemados de la imagen
+Comment[et]=DigiKami kuumade pikslite korrigeerimise plugin
+Comment[fa]=وصلۀ اصلاح تصویردانۀ Hot برای digiKam
+Comment[fi]=Digitaalisen rakeisuushäiriön korjaus
+Comment[gl]=Un plugin de digiKam para corrixir os pixels queimados da imaxe
+Comment[hr]=digiKam dodatak za ispravljanje vrućih piksela
+Comment[is]=Íforrit fyrir digiKam sem fjarlægir skemmda díla (hot pixels)
+Comment[it]=Plugin di correzione dei pixel bruciati per digiKam
+Comment[ja]=digiKam ホットピクセル除去プラグイン
+Comment[ms]=Plugin pembetulan piksel panas untuk digiKam
+Comment[nds]=digiKam-Moduul för't Richten vun hitte Pixels
+Comment[nl]=Digikam-plugin voor het corrigeren van de hotpixels
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਹਾਟ ਪਿਕਸਲ ਸੋਧ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam usuwająca "gorące piksele"
+Comment[pt]=Um 'plugin' do digiKam para corrigir os pixels queimados da imagem
+Comment[pt_BR]=Plugin de Correção de hot pixel para o digiKam
+Comment[ru]=Модуль коррекции ярких пикселей для digiKam
+Comment[sk]=digiKam plugin pre korekciu vypálených pixelov
+Comment[sr]=digiKam-ов прикључак исправку врућих пиксела
+Comment[sr@Latn]=digiKam-ov priključak ispravku vrućih piksela
+Comment[sv]=Digikam insticksprogram för korrigering av heta bildpunkter
+Comment[tr]=digiKam için beyaz dengesini düzeltme eklentisi
+Comment[uk]=Втулок виправлення гарячих пікселів для Digikam
+Comment[vi]=Phần bổ sung sửa điểm ảnh nóng cho digiKam
+Comment[xx]=xxHot pixel correction plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_hotpixels
+author=Unai Garro, ugarro at sourceforge dot net
diff --git a/src/imageplugins/hotpixels/digikamimageplugin_hotpixels_ui.rc b/src/imageplugins/hotpixels/digikamimageplugin_hotpixels_ui.rc
new file mode 100644
index 00000000..b80d7f5a
--- /dev/null
+++ b/src/imageplugins/hotpixels/digikamimageplugin_hotpixels_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="4" name="digikamimageplugin_hotpixels" >
+
+ <MenuBar>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ <Action name="imageplugin_hotpixels" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_hotpixels" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/hotpixels/hotpixel.h b/src/imageplugins/hotpixels/hotpixel.h
new file mode 100644
index 00000000..bceb539f
--- /dev/null
+++ b/src/imageplugins/hotpixels/hotpixel.h
@@ -0,0 +1,74 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : Threaded image filter to fix hot pixels
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef HOTPIXEL_H
+#define HOTPIXEL_H
+
+// TQt includes.
+
+#include <tqrect.h>
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+class HotPixel
+{
+
+public:
+
+ TQRect rect;
+ int luminosity;
+ int y() const {return rect.y(); };
+ int x() const {return rect.x(); };
+ int width()const {return rect.width(); };
+ int height()const {return rect.height();};
+
+ bool operator==(const HotPixel p) const
+ {
+ //we can say they're same hotpixel spot if they
+ //touch(next to) each other horizontally or vertically, not diagonal corners
+ //return (rect.intersects(p.rect));
+ return (rect != p.rect) && (x() + width() >= p.x() && x() <= p.x() + p.width()
+ && y() + height() >= p.y() && y() <= p.y() + p.height())
+ && !diagonal(rect, p.rect);
+ }
+
+private:
+
+ bool diagonal(TQRect r1,TQRect r2) const
+ {
+ //locate next-to positions
+
+ bool top = r1.y() + height()-1 == r2.y()-1; //r1 is on the top of r2
+ bool left = r1.x() + width()-1 == r2.x()-1; //r1 is on the left of r2
+ bool right = r1.x() == r2.x() + r2.width();
+ bool bottom = r1.y() == r2.y() + r2.height();
+
+ return ((top && left) || (top && right) || (bottom && left) || (bottom && right));
+ }
+};
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
+#endif // HOTPIXEL_H
diff --git a/src/imageplugins/hotpixels/hotpixelfixer.cpp b/src/imageplugins/hotpixels/hotpixelfixer.cpp
new file mode 100644
index 00000000..d29b8525
--- /dev/null
+++ b/src/imageplugins/hotpixels/hotpixelfixer.cpp
@@ -0,0 +1,302 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : Threaded image filter to fix hot pixels
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqregexp.h>
+#include <tqstringlist.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "ddebug.h"
+#include "hotpixelfixer.h"
+
+#ifdef HAVE_FLOAT_H
+#if HAVE_FLOAT_H
+# include <float.h>
+#endif
+#endif
+
+#ifndef DBL_MIN
+# define DBL_MIN 1e-37
+#endif
+#ifndef DBL_MAX
+# define DBL_MAX 1e37
+#endif
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+HotPixelFixer::HotPixelFixer(Digikam::DImg *orgImage, TQObject *parent, const TQValueList<HotPixel>& hpList,
+ int interpolationMethod)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "HotPixels")
+{
+ m_hpList = hpList;
+ m_interpolationMethod = interpolationMethod;
+ mWeightList.clear();
+
+ initFilter();
+}
+
+HotPixelFixer::~HotPixelFixer()
+{
+}
+
+void HotPixelFixer::filterImage(void)
+{
+ TQValueList <HotPixel>::ConstIterator it;
+ TQValueList <HotPixel>::ConstIterator end(m_hpList.end());
+ for (it = m_hpList.begin() ; it != end ; ++it)
+ {
+ HotPixel hp = *it;
+ interpolate(m_orgImage, hp, m_interpolationMethod);
+ }
+
+ m_destImage = m_orgImage;
+}
+
+// Interpolates a pixel block
+void HotPixelFixer::interpolate (Digikam::DImg &img, HotPixel &hp, int method)
+{
+ const int xPos = hp.x();
+ const int yPos = hp.y();
+ bool sixtBits = img.sixteenBit();
+
+ // Interpolate pixel.
+ switch (method)
+ {
+ case AVERAGE_INTERPOLATION:
+ {
+ // We implement the bidimendional one first.
+ // TODO: implement the rest of directions (V & H) here
+
+ //case twodim:
+ // {
+ int sum_weight = 0;
+ double vr=0.0,vg=0.0,vb=0.0;
+ int x, y;
+ Digikam::DColor col;
+
+ for (x = xPos; x < xPos+hp.width(); ++x)
+ {
+ if (validPoint(img,TQPoint(x,yPos-1)))
+ {
+ col=img.getPixelColor(x,yPos-1);
+ vr += col.red();
+ vg += col.green();
+ vb += col.blue();
+ ++sum_weight;
+ }
+ if (validPoint(img,TQPoint(x,yPos+hp.height())))
+ {
+ col=img.getPixelColor(x,yPos+hp.height());
+ vr += col.red();
+ vg += col.green();
+ vb += col.blue();
+ ++sum_weight;
+ }
+ }
+
+ for (y = yPos; y < hp.height(); ++y)
+ {
+ if (validPoint(img,TQPoint(xPos-1,y)))
+ {
+ col=img.getPixelColor(xPos,y);
+ vr += col.red();
+ vg += col.green();
+ vb += col.blue();
+ ++sum_weight;
+ }
+ if (validPoint(img,TQPoint(xPos+hp.width(),y)))
+ {
+ col=img.getPixelColor(xPos+hp.width(),y);
+ vr += col.red();
+ vg += col.green();
+ vb += col.blue();
+ ++sum_weight;
+ }
+ }
+
+ if (sum_weight > 0)
+ {
+ vr /= (double)sum_weight;
+ vg /= (double)sum_weight;
+ vb /= (double)sum_weight;
+
+
+ for (x = 0; x < hp.width(); ++x)
+ for (y = 0; y < hp.height(); ++y)
+ if (validPoint(img,TQPoint(xPos+x,yPos+y)))
+ {
+ int alpha=sixtBits ? 65535 : 255;
+ int ir=(int )round(vr),ig=(int) round(vg),ib=(int) round(vb);
+ img.setPixelColor(xPos+x,yPos+y,Digikam::DColor(ir,ig,ib,alpha,sixtBits));
+ }
+ }
+ break;
+ } //Case average
+
+ case LINEAR_INTERPOLATION:
+ //(Bi)linear interpolation.
+ weightPixels (img,hp,LINEAR_INTERPOLATION,TWODIM_DIRECTION,sixtBits ? 65535: 255);
+ break;
+
+ case QUADRATIC_INTERPOLATION:
+ // (Bi)quadratic interpolation.
+ weightPixels (img,hp,QUADRATIC_INTERPOLATION,TWODIM_DIRECTION,sixtBits ? 65535 : 255);
+ break;
+
+ case CUBIC_INTERPOLATION:
+ // (Bi)cubic interpolation.
+ weightPixels (img,hp,CUBIC_INTERPOLATION,TWODIM_DIRECTION,sixtBits ? 65535 : 255);
+ break;
+ } //switch
+}
+
+void HotPixelFixer::weightPixels (Digikam::DImg &img, HotPixel &px, int method, Direction dir,int maxComponent)
+{
+ //TODO: implement direction here too
+
+ for (int iComp = 0; iComp < 3; ++iComp)
+ {
+ // Obtain weight data block.
+
+ Weights w;
+ int polynomeOrder=-1;
+
+ switch (method)
+ {
+ case AVERAGE_INTERPOLATION: // Gilles: to prevent warnings from compiler.
+ break;
+ case LINEAR_INTERPOLATION:
+ polynomeOrder=1;
+ break;
+ case QUADRATIC_INTERPOLATION:
+ polynomeOrder=2;
+ break;
+ case CUBIC_INTERPOLATION:
+ polynomeOrder=3;
+ break;
+ }
+ if (polynomeOrder<0) return;
+
+ // In the one-dimensional case, the width must be 1,
+ // and the size must be stored in height
+
+ w.setWidth(dir == TWODIM_DIRECTION ? px.width() : 1);
+ w.setHeight(dir == HORIZONTAL_DIRECTION ? px.width() : px.height());
+ w.setPolynomeOrder(polynomeOrder);
+ w.setTwoDim(dir == TWODIM_DIRECTION);
+
+ //TODO: check this, it must not recalculate existing calculated weights
+ //for now I don't think it is finding the duplicates fine, so it uses
+ //the previous one always...
+
+ //if (mWeightList.find(w)==mWeightList.end())
+ //{
+ w.calculateWeights();
+
+ // mWeightList.append(w);
+
+ //}
+
+ // Calculate weighted pixel sum.
+ for (int y = 0; y<px.height(); ++y)
+ {
+ for (int x = 0; x < px.width(); ++x)
+ {
+ if (validPoint (img,TQPoint(px.x()+x,px.y()+y)))
+ {
+ double sum_weight = 0.0, v = 0.0;
+ size_t i;
+
+ for (i = 0; i < w.positions().count(); ++i)
+ {
+ // In the one-dimensional case, only the y coordinate is used.
+ const int xx = px.x()+(dir == VERTICAL_DIRECTION ? x :
+ dir== HORIZONTAL_DIRECTION ? w.positions()[i].y() : w.positions()[i].x());
+ const int yy = px.y()+(dir == HORIZONTAL_DIRECTION ? y :
+ w.positions()[i].y());
+
+ if (validPoint (img,TQPoint(xx, yy)))
+ {
+ //TODO: check this. I think it is broken
+ double weight;
+ if (dir==VERTICAL_DIRECTION)
+ {
+ weight = w[i][y][0];
+ }
+ else if (dir==HORIZONTAL_DIRECTION)
+ {
+ weight=w[i][0][x];
+ }
+ else
+ {
+ weight=w[i][y][x];
+ }
+
+ if (iComp==0) v += weight * img.getPixelColor(xx, yy).red();
+ else if (iComp==1) v += weight * img.getPixelColor(xx, yy).green();
+ else v += weight * img.getPixelColor(xx, yy).blue();
+
+ sum_weight += weight;
+ }
+ }
+
+ Digikam::DColor color=img.getPixelColor(px.x()+x,px.y()+y);
+ int component;
+ if (fabs (v) <= DBL_MIN)
+ component=0;
+ else if (sum_weight >= DBL_MIN)
+ {
+ component=(int) (v/sum_weight);
+ //Clamp value
+ if (component<0) component=0;
+ if (component>maxComponent) component=maxComponent;
+ }
+ else if (v >= 0.0)
+ component=maxComponent;
+ else
+ component=0;
+
+ if (iComp==0) color.setRed(component);
+ else if (iComp==1) color.setGreen(component);
+ else color.setBlue(component);
+
+
+ img.setPixelColor(px.x()+x,px.y()+y,color);
+ }
+ }
+ }
+ }
+}
+
+} // NameSpace DigikamHotPixelsImagesPlugin
diff --git a/src/imageplugins/hotpixels/hotpixelfixer.h b/src/imageplugins/hotpixels/hotpixelfixer.h
new file mode 100644
index 00000000..2bf8131c
--- /dev/null
+++ b/src/imageplugins/hotpixels/hotpixelfixer.h
@@ -0,0 +1,98 @@
+/* ============================================================
+ * Authors: Unai Garro <ugarro at users dot sourceforge dot net>
+ * Gilles Caulier <caulier dot gilles at free dot fr>
+ * Date : 2005-03-27
+ * Description : Threaded image filter to fix hot pixels
+ *
+ * Copyright 2005-2007 by Unai Garro and Gilles Caulier
+ *
+ * The algorithm for fixing the hot pixels was based on
+ * the code of jpegpixi, which was released under the GPL license,
+ * and is Copyright (C) 2003, 2004 Martin Dickopp
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================*/
+
+#ifndef HOTPIXELFIXER_H
+#define HOTPIXELFIXER_H
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqobject.h>
+#include <tqvaluelist.h>
+#include <tqrect.h>
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+// Local includes.
+
+#include "hotpixel.h"
+#include "weights.h"
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+class HotPixelFixer : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ enum InterpolationMethod
+ {
+ AVERAGE_INTERPOLATION = 0,
+ LINEAR_INTERPOLATION = 1,
+ QUADRATIC_INTERPOLATION = 2,
+ CUBIC_INTERPOLATION = 3
+ };
+
+ enum Direction
+ {
+ TWODIM_DIRECTION = 0,
+ VERTICAL_DIRECTION = 1,
+ HORIZONTAL_DIRECTION = 2
+ };
+
+public:
+
+ HotPixelFixer(Digikam::DImg *orgImage, TQObject *parent,
+ const TQValueList<HotPixel>& hpList, int interpolationMethod);
+ ~HotPixelFixer();
+
+private:
+
+ virtual void filterImage(void);
+
+ void interpolate (Digikam::DImg &img,HotPixel &hp, int method);
+ void weightPixels (Digikam::DImg &img, HotPixel &px, int method, Direction dir, int maxComponent);
+
+ inline bool validPoint(Digikam::DImg &img, TQPoint p)
+ {
+ return (p.x()>=0 && p.y()>=0 && p.x()<(long) img.width() && p.y()<(long) img.height());
+ };
+
+ TQValueList <Weights> mWeightList;
+
+private:
+
+ int m_interpolationMethod;
+
+ TQValueList<HotPixel> m_hpList;
+};
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
+#endif // HOTPIXELFIXER_H
diff --git a/src/imageplugins/hotpixels/hotpixelstool.cpp b/src/imageplugins/hotpixels/hotpixelstool.cpp
new file mode 100644
index 00000000..fbcc6c9f
--- /dev/null
+++ b/src/imageplugins/hotpixels/hotpixelstool.cpp
@@ -0,0 +1,276 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a digiKam image plugin for fixing dots produced by
+ * hot/stuck/dead pixels from a CCD.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcombobox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+#include <tqpushbutton.h>
+#include <tqpointarray.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <kimageio.h>
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdefiledialog.h>
+#include <kprogress.h>
+#include <kiconloader.h>
+#include <kpushbutton.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortooliface.h"
+#include "editortoolsettings.h"
+#include "imagedialog.h"
+#include "blackframelistview.h"
+#include "hotpixelstool.h"
+#include "hotpixelstool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+HotPixelsTool::HotPixelsTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("hotpixels");
+ setToolName(i18n("Hot Pixels"));
+ setToolIcon(SmallIcon("hotpixels"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Ok|
+ EditorToolSettings::Try|
+ EditorToolSettings::Cancel,
+ EditorToolSettings::PanIcon);
+
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 3, 2);
+
+ TQLabel *filterMethodLabel = new TQLabel(i18n("Filter:"), m_gboxSettings->plainPage());
+ m_filterMethodCombo = new RComboBox(m_gboxSettings->plainPage());
+ m_filterMethodCombo->insertItem(i18n("Average"));
+ m_filterMethodCombo->insertItem(i18n("Linear"));
+ m_filterMethodCombo->insertItem(i18n("Quadratic"));
+ m_filterMethodCombo->insertItem(i18n("Cubic"));
+ m_filterMethodCombo->setDefaultItem(HotPixelFixer::QUADRATIC_INTERPOLATION);
+
+ m_blackFrameButton = new TQPushButton(i18n("Black Frame..."), m_gboxSettings->plainPage());
+ TQWhatsThis::add(m_blackFrameButton, i18n("<p>Use this button to "
+ "add a new black frame file which will be used by the hot pixels removal filter."));
+
+ m_blackFrameListView = new BlackFrameListView(m_gboxSettings->plainPage());
+
+ grid->addMultiCellWidget(filterMethodLabel, 0, 0, 0, 0);
+ grid->addMultiCellWidget(m_filterMethodCombo, 0, 0, 1, 1);
+ grid->addMultiCellWidget(m_blackFrameButton, 0, 0, 2, 2);
+ grid->addMultiCellWidget(m_blackFrameListView, 1, 2, 0, 2);
+ grid->setRowStretch(3, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "hotpixels Tool", m_gboxSettings->panIconView(),
+ 0, ImagePanelWidget::SeparateViewDuplicate);
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_filterMethodCombo, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_blackFrameButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAddBlackFrame()));
+
+ connect(m_blackFrameListView, TQ_SIGNAL(blackFrameSelected(TQValueList<HotPixel>, const KURL&)),
+ this, TQ_SLOT(slotBlackFrame(TQValueList<HotPixel>, const KURL&)));
+}
+
+HotPixelsTool::~HotPixelsTool()
+{
+}
+
+void HotPixelsTool::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("hotpixels Tool");
+ m_blackFrameURL = KURL(config->readEntry("Last Black Frame File", TQString()));
+ m_filterMethodCombo->setCurrentItem(config->readNumEntry("Filter Method",
+ m_filterMethodCombo->defaultItem()));
+
+ if (m_blackFrameURL.isValid())
+ {
+ EditorToolIface::editorToolIface()->setToolStartProgress(i18n("Loading: "));
+ BlackFrameListViewItem *item = new BlackFrameListViewItem(m_blackFrameListView, m_blackFrameURL);
+
+ connect(item, TQ_SIGNAL(signalLoadingProgress(float)),
+ this, TQ_SLOT(slotLoadingProgress(float)));
+
+ connect(item, TQ_SIGNAL(signalLoadingComplete()),
+ this, TQ_SLOT(slotLoadingComplete()));
+ }
+}
+
+void HotPixelsTool::slotLoadingProgress(float v)
+{
+ EditorToolIface::editorToolIface()->setToolProgress((int)(v*100));
+}
+
+void HotPixelsTool::slotLoadingComplete()
+{
+ EditorToolIface::editorToolIface()->setToolStopProgress();
+}
+
+void HotPixelsTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("hotpixels Tool");
+ config->writeEntry("Last Black Frame File", m_blackFrameURL.url());
+ config->writeEntry("Filter Method", m_filterMethodCombo->currentItem());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void HotPixelsTool::slotResetSettings()
+{
+ m_filterMethodCombo->blockSignals(true);
+ m_filterMethodCombo->slotReset();
+ m_filterMethodCombo->blockSignals(false);
+}
+
+void HotPixelsTool::slotAddBlackFrame()
+{
+ KURL url = ImageDialog::getImageURL(kapp->activeWindow(), m_blackFrameURL, i18n("Select Black Frame Image"));
+
+ if (!url.isEmpty())
+ {
+ // Load the selected file and insert into the list.
+
+ m_blackFrameURL = url;
+ m_blackFrameListView->clear();
+ BlackFrameListViewItem *item = new BlackFrameListViewItem(m_blackFrameListView, m_blackFrameURL);
+
+ connect(item, TQ_SIGNAL(signalLoadingProgress(float)),
+ this, TQ_SLOT(slotLoadingProgress(float)));
+
+ connect(item, TQ_SIGNAL(signalLoadingComplete()),
+ this, TQ_SLOT(slotLoadingComplete()));
+ }
+}
+
+void HotPixelsTool::renderingFinished()
+{
+ m_filterMethodCombo->setEnabled(true);
+ m_blackFrameListView->setEnabled(true);
+}
+
+void HotPixelsTool::prepareEffect()
+{
+ m_filterMethodCombo->setEnabled(false);
+ m_blackFrameListView->setEnabled(false);
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+ int interpolationMethod = m_filterMethodCombo->currentItem();
+
+ TQValueList<HotPixel> hotPixelsRegion;
+ TQRect area = m_previewWidget->getOriginalImageRegionToRender();
+ TQValueList<HotPixel>::Iterator end(m_hotPixelsList.end());
+
+ for (TQValueList<HotPixel>::Iterator it = m_hotPixelsList.begin() ; it != end ; ++it )
+ {
+ HotPixel hp = (*it);
+
+ if ( area.contains( hp.rect ) )
+ {
+ hp.rect.moveTopLeft(TQPoint( hp.rect.x()-area.x(), hp.rect.y()-area.y() ));
+ hotPixelsRegion.append(hp);
+ }
+ }
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new HotPixelFixer(&image, this, hotPixelsRegion, interpolationMethod)));
+}
+
+void HotPixelsTool::prepareFinal()
+{
+ m_filterMethodCombo->setEnabled(false);
+ m_blackFrameListView->setEnabled(false);
+
+ int interpolationMethod = m_filterMethodCombo->currentItem();
+
+ ImageIface iface(0, 0);
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new HotPixelFixer(iface.getOriginalImg(), this,m_hotPixelsList,interpolationMethod)));
+}
+
+void HotPixelsTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void HotPixelsTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Hot Pixels Correction"), filter()->getTargetImage().bits());
+}
+
+void HotPixelsTool::slotBlackFrame(TQValueList<HotPixel> hpList, const KURL& blackFrameURL)
+{
+ m_blackFrameURL = blackFrameURL;
+ m_hotPixelsList = hpList;
+
+ TQPointArray pointList(m_hotPixelsList.size());
+ TQValueList <HotPixel>::Iterator it;
+ int i = 0;
+ TQValueList <HotPixel>::Iterator end(m_hotPixelsList.end());
+
+ for (it = m_hotPixelsList.begin() ; it != end ; ++it, i++)
+ pointList.setPoint(i, (*it).rect.center());
+
+ m_previewWidget->setPanIconHighLightPoints(pointList);
+
+ slotEffect();
+}
+
+} // NameSpace DigikamHotPixelsImagesPlugin
diff --git a/src/imageplugins/hotpixels/hotpixelstool.h b/src/imageplugins/hotpixels/hotpixelstool.h
new file mode 100644
index 00000000..727c6387
--- /dev/null
+++ b/src/imageplugins/hotpixels/hotpixelstool.h
@@ -0,0 +1,113 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a digiKam image plugin for fixing dots produced by
+ * hot/stuck/dead pixels from a CCD.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_HOTPIXELS_H
+#define IMAGEEFFECT_HOTPIXELS_H
+
+#define MAX_PIXEL_DEPTH 4
+
+// TQt includes.
+
+#include <tqvaluelist.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+// Local includes.
+
+#include "hotpixelfixer.h"
+
+class TQPushButton;
+
+namespace KDcrawIface
+{
+class RComboBox;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+class BlackFrameListView;
+
+class HotPixelsTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ HotPixelsTool(TQObject *parent);
+ ~HotPixelsTool();
+
+private slots:
+
+ void slotBlackFrame(TQValueList<HotPixel> hpList, const KURL& blackFrameURL);
+ void slotResetSettings();
+ void slotAddBlackFrame();
+ void slotLoadingProgress(float);
+ void slotLoadingComplete();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQPushButton *m_blackFrameButton;
+
+ TQValueList<HotPixel> m_hotPixelsList;
+
+ KURL m_blackFrameURL;
+
+ BlackFrameListView *m_blackFrameListView;
+
+ KDcrawIface::RComboBox *m_filterMethodCombo;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
+#endif /* IMAGEEFFECT_HOTPIXELS_H */
diff --git a/src/imageplugins/hotpixels/imageeffect_hotpixels.cpp b/src/imageplugins/hotpixels/imageeffect_hotpixels.cpp
new file mode 100644
index 00000000..67e9a1b2
--- /dev/null
+++ b/src/imageplugins/hotpixels/imageeffect_hotpixels.cpp
@@ -0,0 +1,279 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a digiKam image plugin for fixing dots produced by
+ * hot/stuck/dead pixels from a CCD.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcombobox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+#include <tqpushbutton.h>
+#include <tqpointarray.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <kimageio.h>
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdefiledialog.h>
+#include <kprogress.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagedialog.h"
+#include "blackframelistview.h"
+#include "imageeffect_hotpixels.h"
+#include "imageeffect_hotpixels.moc"
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+ImageEffect_HotPixels::ImageEffect_HotPixels(TQWidget* parent)
+ : CtrlPanelDlg(parent, i18n("Hot Pixels Correction"),
+ "hotpixels", false, false, false,
+ Digikam::ImagePannelWidget::SeparateViewDuplicate)
+{
+ // No need Abort button action.
+ showButton(User1, false);
+
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Hot Pixels Correction"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin for fixing dots produced by "
+ "hot/stuck/dead pixels from a CCD."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2006, Unai Garro\n(c) 2005-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Unai Garro", I18N_NOOP("Author and maintainer"),
+ "ugarro at sourceforge dot net");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Developer"),
+ "caulier dot gilles at gmail dot com");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings, 3, 2, 0, spacingHint());
+
+ TQLabel *filterMethodLabel = new TQLabel(i18n("Filter:"), gboxSettings);
+ m_filterMethodCombo = new TQComboBox(gboxSettings);
+ m_filterMethodCombo->insertItem(i18n("Average"));
+ m_filterMethodCombo->insertItem(i18n("Linear"));
+ m_filterMethodCombo->insertItem(i18n("Quadratic"));
+ m_filterMethodCombo->insertItem(i18n("Cubic"));
+
+ m_blackFrameButton = new TQPushButton(i18n("Black Frame..."), gboxSettings);
+ setButtonWhatsThis( Apply, i18n("<p>Use this button to add a new black frame file which will "
+ "be used by the hot pixels removal filter.") );
+
+ m_blackFrameListView = new BlackFrameListView(gboxSettings);
+ m_progressBar = new KProgress(100, gboxSettings);
+ m_progressBar->setValue(0);
+ m_progressBar->hide();
+
+ gridSettings->addMultiCellWidget(filterMethodLabel, 0, 0, 0, 0);
+ gridSettings->addMultiCellWidget(m_filterMethodCombo, 0, 0, 1, 1);
+ gridSettings->addMultiCellWidget(m_blackFrameButton, 0, 0, 2, 2);
+ gridSettings->addMultiCellWidget(m_blackFrameListView, 1, 2, 0, 2);
+ gridSettings->addMultiCellWidget(m_progressBar, 3, 3, 0, 2);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_filterMethodCombo, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_blackFrameButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAddBlackFrame()));
+
+ connect(m_blackFrameListView, TQ_SIGNAL(blackFrameSelected(TQValueList<HotPixel>, const KURL&)),
+ this, TQ_SLOT(slotBlackFrame(TQValueList<HotPixel>, const KURL&)));
+}
+
+ImageEffect_HotPixels::~ImageEffect_HotPixels()
+{
+}
+
+void ImageEffect_HotPixels::readUserSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("hotpixels Tool Dialog");
+ m_blackFrameURL = KURL(config->readEntry("Last Black Frame File", TQString()));
+ m_filterMethodCombo->setCurrentItem(config->readNumEntry("Filter Method",
+ HotPixelFixer::QUADRATIC_INTERPOLATION));
+
+ if (m_blackFrameURL.isValid())
+ {
+ BlackFrameListViewItem *item = new BlackFrameListViewItem(m_blackFrameListView, m_blackFrameURL);
+
+ connect(item, TQ_SIGNAL(signalLoadingProgress(float)),
+ this, TQ_SLOT(slotLoadingProgress(float)));
+
+ connect(item, TQ_SIGNAL(signalLoadingComplete()),
+ this, TQ_SLOT(slotLoadingComplete()));
+ }
+}
+
+void ImageEffect_HotPixels::writeUserSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("hotpixels Tool Dialog");
+ config->writeEntry("Last Black Frame File", m_blackFrameURL.url());
+ config->writeEntry("Filter Method", m_filterMethodCombo->currentItem());
+ config->sync();
+}
+
+void ImageEffect_HotPixels::resetValues()
+{
+ m_filterMethodCombo->blockSignals(true);
+ m_filterMethodCombo->setCurrentItem(HotPixelFixer::QUADRATIC_INTERPOLATION);
+ m_filterMethodCombo->blockSignals(false);
+}
+
+void ImageEffect_HotPixels::slotAddBlackFrame()
+{
+ KURL url = Digikam::ImageDialog::getImageURL(this, m_blackFrameURL,
+ i18n("Select Black Frame Image"));
+
+ if (!url.isEmpty())
+ {
+ // Load the selected file and insert into the list.
+
+ m_blackFrameURL = url;
+ m_blackFrameListView->clear();
+ BlackFrameListViewItem *item = new BlackFrameListViewItem(m_blackFrameListView, m_blackFrameURL);
+
+ connect(item, TQ_SIGNAL(signalLoadingProgress(float)),
+ this, TQ_SLOT(slotLoadingProgress(float)));
+
+ connect(item, TQ_SIGNAL(signalLoadingComplete()),
+ this, TQ_SLOT(slotLoadingComplete()));
+ }
+}
+
+void ImageEffect_HotPixels::slotLoadingProgress(float v)
+{
+ m_progressBar->show();
+ m_progressBar->setValue((int)(v*100));
+}
+
+void ImageEffect_HotPixels::slotLoadingComplete()
+{
+ m_progressBar->hide();
+}
+
+void ImageEffect_HotPixels::renderingFinished()
+{
+ m_filterMethodCombo->setEnabled(true);
+ m_blackFrameListView->setEnabled(true);
+ enableButton(Apply, true);
+}
+
+void ImageEffect_HotPixels::prepareEffect()
+{
+ m_filterMethodCombo->setEnabled(false);
+ m_blackFrameListView->setEnabled(false);
+ enableButton(Apply, false);
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+ int interpolationMethod = m_filterMethodCombo->currentItem();
+
+ TQValueList<HotPixel> hotPixelsRegion;
+ TQRect area = m_imagePreviewWidget->getOriginalImageRegionToRender();
+ TQValueList<HotPixel>::Iterator end(m_hotPixelsList.end());
+
+ for (TQValueList<HotPixel>::Iterator it = m_hotPixelsList.begin() ; it != end ; ++it )
+ {
+ HotPixel hp = (*it);
+
+ if ( area.contains( hp.rect ) )
+ {
+ hp.rect.moveTopLeft(TQPoint( hp.rect.x()-area.x(), hp.rect.y()-area.y() ));
+ hotPixelsRegion.append(hp);
+ }
+ }
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new HotPixelFixer(&image, this, hotPixelsRegion, interpolationMethod));
+}
+
+void ImageEffect_HotPixels::prepareFinal()
+{
+ m_filterMethodCombo->setEnabled(false);
+ m_blackFrameListView->setEnabled(false);
+ enableButton(Apply, false);
+
+ int interpolationMethod = m_filterMethodCombo->currentItem();
+
+ Digikam::ImageIface iface(0, 0);
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new HotPixelFixer(iface.getOriginalImg(), this,m_hotPixelsList,interpolationMethod));
+}
+
+void ImageEffect_HotPixels::putPreviewData()
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_HotPixels::putFinalData()
+{
+ Digikam::ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Hot Pixels Correction"), m_threadedFilter->getTargetImage().bits());
+}
+
+void ImageEffect_HotPixels::slotBlackFrame(TQValueList<HotPixel> hpList, const KURL& blackFrameURL)
+{
+ m_blackFrameURL = blackFrameURL;
+ m_hotPixelsList = hpList;
+
+ TQPointArray pointList(m_hotPixelsList.size());
+ TQValueList <HotPixel>::Iterator it;
+ int i = 0;
+ TQValueList <HotPixel>::Iterator end(m_hotPixelsList.end());
+
+ for (it = m_hotPixelsList.begin() ; it != end ; ++it, i++)
+ pointList.setPoint(i, (*it).rect.center());
+
+ m_imagePreviewWidget->setPanIconHighLightPoints(pointList);
+
+ slotEffect();
+}
+
+} // NameSpace DigikamHotPixelsImagesPlugin
diff --git a/src/imageplugins/hotpixels/imageeffect_hotpixels.h b/src/imageplugins/hotpixels/imageeffect_hotpixels.h
new file mode 100644
index 00000000..4a8b0868
--- /dev/null
+++ b/src/imageplugins/hotpixels/imageeffect_hotpixels.h
@@ -0,0 +1,104 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a digiKam image plugin for fixing dots produced by
+ * hot/stuck/dead pixels from a CCD.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_HOTPIXELS_H
+#define IMAGEEFFECT_HOTPIXELS_H
+
+#define MAX_PIXEL_DEPTH 4
+
+// TQt includes.
+
+#include <tqvaluelist.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+// Local includes.
+
+#include "hotpixelfixer.h"
+
+class TQComboBox;
+class TQPushButton;
+
+class KProgress;
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+class BlackFrameListView;
+
+class ImageEffect_HotPixels : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_HotPixels(TQWidget *parent);
+ ~ImageEffect_HotPixels();
+
+private slots:
+
+ void slotLoadingProgress(float v);
+ void slotLoadingComplete();
+
+ void slotBlackFrame(TQValueList<HotPixel> hpList, const KURL& blackFrameURL);
+ void slotAddBlackFrame();
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void abortPreview();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQComboBox *m_filterMethodCombo;
+
+ TQPushButton *m_blackFrameButton;
+
+ TQValueList<HotPixel> m_hotPixelsList;
+
+ KURL m_blackFrameURL;
+
+ KProgress *m_progressBar;
+
+ BlackFrameListView *m_blackFrameListView;
+};
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
+#endif /* IMAGEEFFECT_HOTPIXELS_H */
diff --git a/src/imageplugins/hotpixels/imageplugin_hotpixels.cpp b/src/imageplugins/hotpixels/imageplugin_hotpixels.cpp
new file mode 100644
index 00000000..864b59dc
--- /dev/null
+++ b/src/imageplugins/hotpixels/imageplugin_hotpixels.cpp
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a digiKam image plugin for fixing dots produced by
+ * hot/stuck/dead pixels from a CCD.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "hotpixelstool.h"
+#include "imageplugin_hotpixels.h"
+#include "imageplugin_hotpixels.moc"
+
+using namespace DigikamHotPixelsImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_hotpixels,
+ KGenericFactory<ImagePlugin_HotPixels>("digikamimageplugin_hotpixels"));
+
+ImagePlugin_HotPixels::ImagePlugin_HotPixels(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_HotPixels")
+{
+ m_hotpixelsAction = new TDEAction(i18n("Hot Pixels..."), "hotpixels", 0,
+ this, TQ_SLOT(slotHotPixels()),
+ actionCollection(), "imageplugin_hotpixels");
+
+ setXMLFile("digikamimageplugin_hotpixels_ui.rc");
+
+ DDebug() << "ImagePlugin_HotPixels plugin loaded" << endl;
+}
+
+ImagePlugin_HotPixels::~ImagePlugin_HotPixels()
+{
+}
+
+void ImagePlugin_HotPixels::setEnabledActions(bool enable)
+{
+ m_hotpixelsAction->setEnabled(enable);
+}
+
+void ImagePlugin_HotPixels::slotHotPixels()
+{
+ HotPixelsTool *tool = new HotPixelsTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/hotpixels/imageplugin_hotpixels.h b/src/imageplugins/hotpixels/imageplugin_hotpixels.h
new file mode 100644
index 00000000..c639b830
--- /dev/null
+++ b/src/imageplugins/hotpixels/imageplugin_hotpixels.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a digiKam image plugin for fixing dots produced by
+ * hot/stuck/dead pixels from a CCD.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_HOTPIXELS_H
+#define IMAGEPLUGIN_HOTPIXELS_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_HotPixels : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_HotPixels(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_HotPixels();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotHotPixels();
+
+private:
+
+ TDEAction *m_hotpixelsAction;
+};
+
+#endif /* IMAGEPLUGIN_HOTPIXELS_H */
diff --git a/src/imageplugins/hotpixels/weights.cpp b/src/imageplugins/hotpixels/weights.cpp
new file mode 100644
index 00000000..e0d3f246
--- /dev/null
+++ b/src/imageplugins/hotpixels/weights.cpp
@@ -0,0 +1,283 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a class to calculate filter weights
+ *
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstring>
+
+// Local includes.
+
+#include "weights.h"
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+Weights::Weights(const Weights &w)
+{
+ (*this) = w;
+}
+
+void Weights::operator=(const Weights &w)
+{
+ mHeight=w.height();
+ mWidth=w.width();
+ mPositions=(w.positions());
+ mCoefficientNumber=w.coefficientNumber();
+ mTwoDim=w.twoDim();
+ mPolynomeOrder=w.polynomeOrder();
+
+ // Allocate memory and copy weights
+ // if the original one was calculated
+
+ if (!w.weightMatrices()) return;
+ else
+ {
+ double*** origMatrices=w.weightMatrices();
+ mWeightMatrices = new double**[mPositions.count()]; //allocate mPositions.count() matrices
+
+ for (uint i=0; i<mPositions.count(); i++)
+ {
+ mWeightMatrices[i]=new double*[mHeight]; //allocate mHeight rows on each position
+ for (unsigned int j=0; j<mHeight; j++)
+ {
+ mWeightMatrices[i][j]=new double[mWidth]; //Allocate mWidth columns on each row
+ for (unsigned int k=0; k<mWidth; k++)
+ {
+ mWeightMatrices[i][j][k]=origMatrices[i][j][k];
+ }
+ }
+ }
+ }
+}
+
+void Weights::calculateWeights()
+{
+ mCoefficientNumber = (mTwoDim
+ ? ((size_t)mPolynomeOrder + 1) * ((size_t)mPolynomeOrder + 1)
+ : (size_t)mPolynomeOrder + 1);
+ double *matrix; /* num_coeff * num_coeff */
+ double *vector0; /* mPositions.count() * num_coeff */
+ double *vector1; /* mPositions.count() * num_coeff */
+ size_t ix,iy,i,j;
+ int x, y;
+
+ // Determine coordinates of pixels to be sampled
+
+ if (mTwoDim)
+ {
+
+ int iPolynomeOrder=(int) mPolynomeOrder; //lets avoid signed/unsigned comparison warnings
+ int iHeight = (int) height(); //"
+ int iWidth = (int) width(); //"
+
+ for (y = -iPolynomeOrder; y < iHeight + iPolynomeOrder; ++y)
+ {
+ for (x = -iPolynomeOrder; x < iWidth + iPolynomeOrder; ++x)
+ {
+ if ((x < 0 && y < 0 && -x - y < iPolynomeOrder + 2)
+ || (x < 0 && y >= iHeight && -x + y - iHeight < iPolynomeOrder + 1)
+ || (x >= iWidth && y < 0 && x - y - iWidth < iPolynomeOrder + 1)
+ || (x >= iWidth && y >= iHeight && x + y - iWidth - iHeight < iPolynomeOrder)
+ || (x < 0 && y >= 0 && y < iHeight) || (x >= iWidth && y >= 0 && y < iHeight)
+ || (y < 0 && x >= 0 && x < iWidth ) || (y >= iHeight && x >= 0 && x < iWidth))
+ {
+ TQPoint position(x,y);
+ mPositions.append(position);
+ }
+ }
+ }
+ }
+ else
+ {
+ // In the one-dimensional case, only the y coordinate and y size is used. */
+
+ for (y = -mPolynomeOrder; y < 0; ++y)
+ {
+ TQPoint position(0,y);
+ mPositions.append(position);
+ }
+
+ for (y = (int) height(); y < (int) height() + (int) mPolynomeOrder; ++y)
+ {
+ TQPoint position(0,y);
+ mPositions.append(position);
+ }
+ }
+
+ // Allocate memory.
+
+ matrix = new double[mCoefficientNumber*mCoefficientNumber];
+ vector0 = new double[mPositions.count() * mCoefficientNumber];
+ vector1 = new double[mPositions.count() * mCoefficientNumber];
+
+ // Calculate coefficient matrix and vectors
+
+ for (iy = 0; iy < mCoefficientNumber; ++iy)
+ {
+ for (ix = 0; ix < mCoefficientNumber; ++ix)
+ matrix [iy*mCoefficientNumber+ix] = 0.0;
+
+ for (j = 0; j < mPositions.count(); ++j)
+ {
+ vector0 [iy * mPositions.count() + j] = polyTerm (iy, mPositions [j].x(),
+ mPositions [j].y(), mPolynomeOrder);
+
+ for (ix = 0; ix < mCoefficientNumber; ++ix)
+ matrix [iy * mCoefficientNumber + ix] += (vector0 [iy * mPositions.count() + j]
+ * polyTerm (ix, mPositions [j].x(), mPositions[j].y(), mPolynomeOrder));
+ }
+ }
+
+ // Invert matrix.
+
+ matrixInv (matrix, mCoefficientNumber);
+
+ // Multiply inverse matrix with vector.
+
+ for (iy = 0; iy < mCoefficientNumber; ++iy)
+ for (j = 0; j < mPositions.count(); ++j)
+ {
+ vector1 [iy * mPositions.count() + j] = 0.0;
+
+ for (ix = 0; ix < mCoefficientNumber; ++ix)
+ vector1 [iy * mPositions.count() + j] += matrix [iy * mCoefficientNumber + ix]
+ * vector0 [ix * mPositions.count() + j];
+ }
+
+ // Store weights
+
+ mWeightMatrices = new double**[mPositions.count()]; //allocate mPositions.count() matrices
+
+ for (i=0; i<mPositions.count(); i++)
+ {
+ mWeightMatrices[i] = new double*[mHeight]; //allocate mHeight rows on each position
+ for (j=0; j<mHeight; j++)
+ mWeightMatrices[i][j] = new double[mWidth]; //Allocate mWidth columns on each row
+ }
+
+ for (y = 0; y < (int) mHeight; ++y)
+ {
+ for (x = 0; x < (int) mWidth; ++x)
+ {
+ for (j = 0; j < mPositions.count(); ++j)
+ {
+ mWeightMatrices [j][y][x] = 0.0;
+
+ for (iy = 0; iy < mCoefficientNumber; ++iy)
+ mWeightMatrices [j][y][x] += vector1 [iy * mPositions.count() + j]
+ * polyTerm (iy, x, y, mPolynomeOrder);
+
+ mWeightMatrices [j][y][x] *= (double) mPositions.count();
+ }
+ }
+ }
+
+ delete[] vector1;
+ delete[] vector0;
+ delete[] matrix;
+}
+
+bool Weights::operator==(const Weights& ws) const
+{
+ return (mHeight==ws.height() &&
+ mWidth==ws.width() &&
+ mPolynomeOrder==ws.polynomeOrder() &&
+ mTwoDim==ws.twoDim()
+ );
+}
+
+ //Invert a quadratic matrix.
+void Weights::matrixInv (double *const a, const size_t size)
+{
+ double *const b = new double[size * size];
+ size_t ix, iy, j;
+
+ // Copy matrix to new location.
+
+ memcpy (b, a, sizeof (double) * size * size);
+
+ // Set destination matrix to unit matrix.
+
+ for (iy = 0; iy < size; ++iy)
+ for (ix = 0; ix < size; ++ix)
+ a [iy * size + ix] = ix == iy ? 1.0 : 0.0;
+
+ // Convert matrix to upper triangle form.
+
+ for (iy = 0; iy < size - 1; ++iy)
+ {
+ for (j = iy + 1; j < size; ++j)
+ {
+ const double factor = b [j * size + iy] / b [iy * size + iy];
+
+ for (ix = 0; ix < size; ++ix)
+ {
+ b [j * size + ix] -= factor * b [iy * size + ix];
+ a [j * size + ix] -= factor * a [iy * size + ix];
+ }
+ }
+ }
+
+ // Convert matrix to diagonal form.
+
+ for (iy = size - 1; iy > 0; --iy)
+ {
+ for (j = 0; j < iy; ++j)
+ {
+ const double factor = b [j * size + iy] / b [iy * size + iy];
+
+ for (ix = 0; ix < size; ++ix)
+ a [j * size + ix] -= factor * a [iy * size + ix];
+ }
+ }
+
+ // Convert matrix to unit matrix.
+
+ for (iy = 0; iy < size; ++iy)
+ for (ix = 0; ix < size; ++ix)
+ a [iy * size + ix] /= b [iy * size + iy];
+
+ delete [] b;
+}
+
+// Calculates one term of the polynomial
+double Weights::polyTerm (const size_t i_coeff, const int x, const int y, const int poly_order)
+{
+ const size_t x_power = i_coeff / ((size_t)poly_order + 1);
+ const size_t y_power = i_coeff % ((size_t)poly_order + 1);
+ int result;
+ size_t i;
+
+ result = 1;
+
+ for (i = 0; i < x_power; ++i)
+ result *= x;
+
+ for (i = 0; i < y_power; ++i)
+ result *= y;
+
+ return (double)result;
+}
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
diff --git a/src/imageplugins/hotpixels/weights.h b/src/imageplugins/hotpixels/weights.h
new file mode 100644
index 00000000..b82fde89
--- /dev/null
+++ b/src/imageplugins/hotpixels/weights.h
@@ -0,0 +1,90 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-27
+ * Description : a class to calculate filter weights
+ *
+ * Copyright (C) 2005-2006 by Unai Garro <ugarro at users dot sourceforge dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef WEIGHTS_H
+#define WEIGHTS_H
+
+// TQt includes.
+
+#include <tqrect.h>
+#include <tqvaluelist.h>
+
+namespace DigikamHotPixelsImagesPlugin
+{
+
+class Weights
+{
+public:
+
+ Weights(){};
+ Weights(const Weights &w);
+ void operator=(const Weights &w);
+
+ ~Weights()
+ {
+ if (!mWeightMatrices) return;
+ for (unsigned int i=0; i<mPositions.count(); i++)
+ {
+ for (unsigned int j=0; j<mHeight; j++) delete[] mWeightMatrices[i][j];
+ }
+ }
+
+ unsigned int height() const { return mHeight; };
+ unsigned int polynomeOrder() const { return mPolynomeOrder; };
+ bool twoDim() const { return mTwoDim; };
+ unsigned int width() const { return mWidth; };
+
+ void setHeight(int h) { mHeight=h; };
+ void setPolynomeOrder(int order) { mPolynomeOrder=order; };
+ void setTwoDim(bool td) { mTwoDim=td; };
+ void setWidth(int w) { mWidth=w; };
+
+ void calculateWeights();
+ bool operator==(const Weights& ws) const;
+ double** operator[](int n) const { return mWeightMatrices[n]; };
+ const TQValueList <TQPoint> positions() const { return mPositions; };
+
+protected:
+
+ int coefficientNumber() const { return mCoefficientNumber; };
+
+ double*** weightMatrices() const { return mWeightMatrices; };
+
+private:
+
+ double polyTerm (const size_t i_coeff, const int x, const int y, const int poly_order);
+ void matrixInv (double *const a, const size_t size);
+
+private:
+
+ unsigned int mHeight,mWidth;
+ unsigned int mCoefficientNumber;
+ bool mTwoDim;
+ unsigned int mPolynomeOrder;
+ double *** mWeightMatrices; //Stores a list of weight matrices
+ TQValueList <TQPoint> mPositions;
+};
+
+} // NameSpace DigikamHotPixelsImagesPlugin
+
+#endif // WEIGHTS_H
diff --git a/src/imageplugins/infrared/Makefile.am b/src/imageplugins/infrared/Makefile.am
new file mode 100644
index 00000000..1e9cd294
--- /dev/null
+++ b/src/imageplugins/infrared/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_infrared_la_SOURCES = imageplugin_infrared.cpp \
+ infraredtool.cpp infrared.cpp
+
+digikamimageplugin_infrared_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_infrared_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_infrared.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_infrared.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_infrared_ui.rc
+
diff --git a/src/imageplugins/infrared/digikamimageplugin_infrared.desktop b/src/imageplugins/infrared/digikamimageplugin_infrared.desktop
new file mode 100644
index 00000000..716de474
--- /dev/null
+++ b/src/imageplugins/infrared/digikamimageplugin_infrared.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_Infrared
+Name[bg]=Приставка за снимки - Инфрачервен филм
+Name[da]=Plugin for infrarødt
+Name[el]=ΠρόσθετοΕικόνας_Υπέρυθρο
+Name[fi]=Infrapuna
+Name[hr]=Infracrveno
+Name[it]=PluginImmagini_Infrarosso
+Name[ms]=ImagePlugin_Inframerah
+Name[nl]=Afbeeldingsplugin_Infrarood
+Name[sr]=Инфрацрвено
+Name[sr@Latn]=Infracrveno
+Name[sv]=Insticksprogram för infrarött
+Name[tr]=ResimEklentisi_Kızılötesi
+Name[xx]=xxImagePlugin_Infraredxx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Simulate infrared film plugin for digiKam
+Comment[bg]=Приставка на digiKam за наподобяване на снимка с инфрачервен филм
+Comment[ca]=Connector pel digiKam de simulació de pel·lícula d'infraroigs
+Comment[da]=Digikam plugin til at simulere infrarød film
+Comment[de]=digiKam-Modul für die Simulation eines Infrarot-Filmes
+Comment[el]=Πρόσθετο εξομοίωσης υπέρυθρου φιλμ για το digiKam
+Comment[es]=Plugin para digiKam para simular la película infrarroja
+Comment[et]=DigiKami infrapunafilmi matkimise plugin
+Comment[fa]=شبیه‌سازی وصلۀ فیلم مادون قرمز برای digiKam
+Comment[fi]=Jäljittelee infrapunafilmiä
+Comment[gl]=Un plugin de digiKam para simulazón de infravermellos
+Comment[hr]=digiKam dodatak za oponašanje IC filma
+Comment[is]=Íforrit fyrir digiKam sem líkir eftir innrauðri filmu
+Comment[it]=Plugin di simulazione di pellicola infrarossa per digiKam
+Comment[ja]=digiKam 赤外線フィルム効果プラグイン
+Comment[nds]=digiKam-Moduul för't Simuleren vun Infraroot-Filmen
+Comment[nl]=Digikam-plugin voor infraroodfilm-effect
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਸਿਮੂਲੇਸ਼ਨ ਇੰਫਰਾਰੈੱਡ ਫਿਲਮ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam symulująca efekt podczerwonej kliszy
+Comment[pt]=Um 'plugin' do digiKam para simulação de infravermelhos
+Comment[pt_BR]=Um 'plugin' do digiKam para simulação de infravermelhos
+Comment[ru]=Модуль имитирующий инфракрасное фильм для digiKam
+Comment[sk]=digiKam plugin pre napodobenie infračerveného filmu
+Comment[sr]=digiKam-ов прикључак који симулира инфрацрвени филм
+Comment[sr@Latn]=digiKam-ov priključak koji simulira infracrveni film
+Comment[sv]=Digikam insticksprogram för att simulera infraröd film
+Comment[tr]=digiKam için kızılötesi film benzetme eklentisi
+Comment[uk]=Втулок симуляції інфрачервоного фільму для digiKam
+Comment[vi]=Phần bổ sung mô phỏng phim ảnh hồng ngoại cho digiKam
+Comment[xx]=xxSimulate infrared film plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_infrared
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/infrared/digikamimageplugin_infrared_ui.rc b/src/imageplugins/infrared/digikamimageplugin_infrared_ui.rc
new file mode 100644
index 00000000..1df83fa6
--- /dev/null
+++ b/src/imageplugins/infrared/digikamimageplugin_infrared_ui.rc
@@ -0,0 +1,19 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_infrared" >
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_infrared" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_infrared" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/infrared/imageeffect_infrared.cpp b/src/imageplugins/infrared/imageeffect_infrared.cpp
new file mode 100644
index 00000000..f8069255
--- /dev/null
+++ b/src/imageplugins/infrared/imageeffect_infrared.cpp
@@ -0,0 +1,227 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-22
+ * Description : a digiKam image editor plugin for simulate
+ * infrared film.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlcdnumber.h>
+#include <tqslider.h>
+#include <tqlayout.h>
+#include <tqdatetime.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "infrared.h"
+#include "imageeffect_infrared.h"
+#include "imageeffect_infrared.moc"
+
+namespace DigikamInfraredImagesPlugin
+{
+
+ImageEffect_Infrared::ImageEffect_Infrared(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Simulate Infrared Film to Photograph"),
+ "infrared", false, false, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Infrared Film"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to simulate infrared film."),
+ TDEAboutData::License_GPL,
+ "(c) 2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 2, 1, 0, spacingHint());
+ TQLabel *label1 = new TQLabel(i18n("Sensitivity (ISO):"), gboxSettings);
+
+ m_sensibilitySlider = new TQSlider(1, 25, 1, 1, TQt::Horizontal, gboxSettings);
+ m_sensibilitySlider->setTracking ( false );
+ m_sensibilitySlider->setTickInterval(1);
+ m_sensibilitySlider->setTickmarks(TQSlider::Below);
+
+ m_sensibilityLCDValue = new TQLCDNumber (4, gboxSettings);
+ m_sensibilityLCDValue->setSegmentStyle ( TQLCDNumber::Flat );
+ m_sensibilityLCDValue->display( TQString::number(200) );
+ whatsThis = i18n("<p>Set here the ISO-sensitivity of the simulated infrared film. "
+ "Increasing this value will increase the proportion of green color in the mix. "
+ "It will also increase the halo effect on the hightlights, and the film "
+ "graininess (if that box is checked).</p>"
+ "<p>Note: to simulate an <b>Ilford SFX200</b> infrared film, use a sensitivity excursion of 200 to 800. "
+ "A sensitivity over 800 simulates <b>Kodak HIE</b> high-speed infrared film. This last one creates a more "
+ "dramatic photographic style.</p>");
+
+ TQWhatsThis::add( m_sensibilityLCDValue, whatsThis);
+ TQWhatsThis::add( m_sensibilitySlider, whatsThis);
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_sensibilitySlider, 1, 1, 0, 0);
+ gridSettings->addMultiCellWidget(m_sensibilityLCDValue, 1, 1, 1, 1);
+
+ // -------------------------------------------------------------
+
+ m_addFilmGrain = new TQCheckBox( i18n("Add film grain"), gboxSettings);
+ m_addFilmGrain->setChecked( true );
+ TQWhatsThis::add( m_addFilmGrain, i18n("<p>This option adds infrared film grain to "
+ "the image depending on ISO-sensitivity."));
+ gridSettings->addMultiCellWidget(m_addFilmGrain, 2, 2, 0, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()) );
+
+ // this connection is necessary to change the LCD display when
+ // the value is changed by single clicking on the slider
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(sliderMoved(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+
+ connect( m_addFilmGrain, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()) );
+}
+
+ImageEffect_Infrared::~ImageEffect_Infrared()
+{
+}
+
+void ImageEffect_Infrared::renderingFinished()
+{
+ m_sensibilitySlider->setEnabled(true);
+ m_addFilmGrain->setEnabled(true);
+}
+
+void ImageEffect_Infrared::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("infrared Tool Dialog");
+ m_sensibilitySlider->blockSignals(true);
+ m_addFilmGrain->blockSignals(true);
+ m_sensibilitySlider->setValue(config->readNumEntry("SensitivityAjustment", 1));
+ m_addFilmGrain->setChecked(config->readBoolEntry("AddFilmGrain", false));
+ m_sensibilitySlider->blockSignals(false);
+ m_addFilmGrain->blockSignals(false);
+ slotSliderMoved(m_sensibilitySlider->value());
+}
+
+void ImageEffect_Infrared::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("infrared Tool Dialog");
+ config->writeEntry("SensitivityAjustment", m_sensibilitySlider->value());
+ config->writeEntry("AddFilmGrain", m_addFilmGrain->isChecked());
+ config->sync();
+}
+
+void ImageEffect_Infrared::resetValues()
+{
+ m_sensibilitySlider->blockSignals(true);
+ m_addFilmGrain->blockSignals(true);
+ m_sensibilitySlider->setValue(1);
+ m_addFilmGrain->setChecked(false);
+ m_sensibilitySlider->blockSignals(false);
+ m_addFilmGrain->blockSignals(false);
+}
+
+void ImageEffect_Infrared::slotSliderMoved(int v)
+{
+ m_sensibilityLCDValue->display( TQString::number(100 + 100 * v) );
+}
+
+void ImageEffect_Infrared::prepareEffect()
+{
+ m_addFilmGrain->setEnabled(false);
+ m_sensibilitySlider->setEnabled(false);
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+ int s = 100 + 100 * m_sensibilitySlider->value();
+ bool g = m_addFilmGrain->isChecked();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Infrared(&image, this, s, g));
+}
+
+void ImageEffect_Infrared::prepareFinal()
+{
+ m_addFilmGrain->setEnabled(false);
+ m_sensibilitySlider->setEnabled(false);
+
+ int s = 100 + 100 * m_sensibilitySlider->value();
+ bool g = m_addFilmGrain->isChecked();
+
+ Digikam::ImageIface iface(0, 0);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Infrared(iface.getOriginalImg(), this, s, g));
+}
+
+void ImageEffect_Infrared::putPreviewData(void)
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_Infrared::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Infrared"), m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamInfraredImagesPlugin
+
diff --git a/src/imageplugins/infrared/imageeffect_infrared.h b/src/imageplugins/infrared/imageeffect_infrared.h
new file mode 100644
index 00000000..d6c28237
--- /dev/null
+++ b/src/imageplugins/infrared/imageeffect_infrared.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-22
+ * Description : a digiKam image editor plugin for simulate
+ * infrared film.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_INFRARED_H
+#define IMAGEEFFECT_INFRARED_H
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+class TQSlider;
+class TQLCDNumber;
+class TQCheckBox;
+
+namespace DigikamInfraredImagesPlugin
+{
+
+class ImageEffect_Infrared : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Infrared(TQWidget* parent);
+ ~ImageEffect_Infrared();
+
+private slots:
+
+ void slotSliderMoved(int);
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQCheckBox *m_addFilmGrain;
+
+ TQSlider *m_sensibilitySlider;
+
+ TQLCDNumber *m_sensibilityLCDValue;
+};
+
+} // NameSpace DigikamInfraredImagesPlugin
+
+#endif /* IMAGEEFFECT_INFRARED_H */
diff --git a/src/imageplugins/infrared/imageplugin_infrared.cpp b/src/imageplugins/infrared/imageplugin_infrared.cpp
new file mode 100644
index 00000000..4b845908
--- /dev/null
+++ b/src/imageplugins/infrared/imageplugin_infrared.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-22
+ * Description : a digiKam image editor plugin for simulate
+ * infrared film.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "infraredtool.h"
+#include "imageplugin_infrared.h"
+#include "imageplugin_infrared.moc"
+
+using namespace DigikamInfraredImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_infrared,
+ KGenericFactory<ImagePlugin_Infrared>("digikamimageplugin_infrared"));
+
+ImagePlugin_Infrared::ImagePlugin_Infrared(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_Infrared")
+{
+ m_infraredAction = new TDEAction(i18n("Infrared Film..."), "infrared", 0,
+ this, TQ_SLOT(slotInfrared()),
+ actionCollection(), "imageplugin_infrared");
+
+ setXMLFile( "digikamimageplugin_infrared_ui.rc" );
+
+ DDebug() << "ImagePlugin_Infrared plugin loaded" << endl;
+}
+
+ImagePlugin_Infrared::~ImagePlugin_Infrared()
+{
+}
+
+void ImagePlugin_Infrared::setEnabledActions(bool enable)
+{
+ m_infraredAction->setEnabled(enable);
+}
+
+void ImagePlugin_Infrared::slotInfrared()
+{
+ InfraredTool *tool = new InfraredTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/infrared/imageplugin_infrared.h b/src/imageplugins/infrared/imageplugin_infrared.h
new file mode 100644
index 00000000..7b6fa12a
--- /dev/null
+++ b/src/imageplugins/infrared/imageplugin_infrared.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-22
+ * Description : a digiKam image editor plugin for simulate
+ * infrared film.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_INFRARED_H
+#define IMAGEPLUGIN_INFRARED_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Infrared : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Infrared(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_Infrared();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotInfrared();
+
+private:
+
+ TDEAction *m_infraredAction;
+};
+
+#endif /* IMAGEPLUGIN_INFRARED_H */
diff --git a/src/imageplugins/infrared/infrared.cpp b/src/imageplugins/infrared/infrared.cpp
new file mode 100644
index 00000000..fa2983d5
--- /dev/null
+++ b/src/imageplugins/infrared/infrared.cpp
@@ -0,0 +1,367 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Infrared threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqdatetime.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimggaussianblur.h"
+#include "imagecurves.h"
+#include "imagehistogram.h"
+#include "dimgimagefilters.h"
+#include "infrared.h"
+
+namespace DigikamInfraredImagesPlugin
+{
+
+Infrared::Infrared(Digikam::DImg *orgImage, TQObject *parent, int sensibility, bool grain)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "Infrared")
+{
+ m_sensibility = sensibility;
+ m_grain = grain;
+ initFilter();
+}
+
+void Infrared::filterImage(void)
+{
+ infraredImage(&m_orgImage, m_sensibility, m_grain);
+}
+
+// This method is based on the Simulate Infrared Film tutorial from GimpGuru.org web site
+// available at this url : http://www.gimpguru.org/Tutorials/SimulatedInfrared/
+
+inline static int intMult8(uint a, uint b)
+{
+ uint t = a * b + 0x80;
+ return ((t >> 8) + t) >> 8;
+}
+
+inline static int intMult16(uint a, uint b)
+{
+ uint t = a * b + 0x8000;
+ return ((t >> 16) + t) >> 16;
+}
+
+/* More info about IR film can be seen at this url :
+
+http://www.pauck.de/marco/photo/infrared/comparison_of_films/comparison_of_films.html
+*/
+
+void Infrared::infraredImage(Digikam::DImg *orgImage, int Sensibility, bool Grain)
+{
+ // Sensibility: 200..2600
+
+ if (Sensibility <= 0) return;
+
+ int Width = orgImage->width();
+ int Height = orgImage->height();
+ int bytesDepth = orgImage->bytesDepth();
+ uint numBytes = orgImage->numBytes();
+ bool sixteenBit = orgImage->sixteenBit();
+ uchar* data = orgImage->bits();
+
+ // Infrared film variables depending on Sensibility.
+ // We can reproduce famous Ilford SFX200 infrared film
+ // http://www.ilford.com/html/us_english/prod_html/sfx200/sfx200.html
+ // This film have a sensibility escursion from 200 to 800 ISO.
+ // Over 800 ISO, we reproduce The Kodak HIE hight speed infrared film.
+
+ // Infrared film grain.
+ int Noise = (Sensibility + 3000) / 10;
+ if (sixteenBit)
+ Noise = (Noise + 1) * 256 - 1;
+
+ int blurRadius = (int)((Sensibility / 200.0) + 1.0); // Gaussian blur infrared hightlight effect
+ // [2 to 5].
+ float greenBoost = 2.1 - (Sensibility / 2000.0); // Infrared green color boost [1.7 to 2.0].
+
+ int nRand, offset, progress;
+
+ uchar* pBWBits = 0; // Black and White conversion.
+ uchar* pBWBlurBits = 0; // Black and White with blur.
+ uchar* pGrainBits = 0; // Grain blured without curves adjustment.
+ uchar* pMaskBits = 0; // Grain mask with curves adjustment.
+ uchar* pOverlayBits = 0; // Overlay to merge with original converted in gray scale.
+ uchar* pOutBits = m_destImage.bits(); // Destination image with merged grain mask and original.
+
+ Digikam::DColor bwData, bwBlurData, grainData, maskData, overData, outData;
+
+ //------------------------------------------
+ // 1 - Create GrayScale green boosted image.
+ //------------------------------------------
+
+ // Convert to gray scale with boosting Green channel.
+ // Infrared film increase green color.
+
+ Digikam::DImg BWImage(Width, Height, sixteenBit); // Black and White conversion.
+ pBWBits = BWImage.bits();
+ memcpy (pBWBits, data, numBytes);
+
+ Digikam::DImgImageFilters().channelMixerImage(pBWBits, Width, Height, sixteenBit, // Image data.
+ true, // Preserve luminosity.
+ true, // Monochrome.
+ 0.4, greenBoost, -0.8, // Red channel gains.
+ 0.0, 1.0, 0.0, // Green channel gains (not used).
+ 0.0, 0.0, 1.0); // Blue channel gains (not used).
+ postProgress( 10 );
+ if (m_cancel)
+ {
+ return;
+ }
+
+ // Apply a Gaussian blur to the black and white image.
+ // This way simulate Infrared film dispersion for the highlights.
+
+ Digikam::DImg BWBlurImage(Width, Height, sixteenBit);
+ pBWBlurBits = BWBlurImage.bits();
+
+ Digikam::DImgGaussianBlur(this, BWImage, BWBlurImage, 10, 20, blurRadius);
+
+ if (m_cancel)
+ {
+ return;
+ }
+
+ //-----------------------------------------------------------------
+ // 2 - Create Gaussian blured averlay mask with grain if necessary.
+ //-----------------------------------------------------------------
+
+
+ if (Grain)
+ {
+
+ // Create gray grain mask.
+
+ TQDateTime dt = TQDateTime::currentDateTime();
+ TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
+ uint seed = ((uint) dt.secsTo(Y2000));
+
+ pGrainBits = new uchar[numBytes]; // Grain blured without curves adjustment.
+ uchar *ptr;
+ int component;
+ grainData.setSixteenBit(sixteenBit);
+
+ for (int x = 0; !m_cancel && x < Width; x++)
+ {
+ for (int y = 0; !m_cancel && y < Height; y++)
+ {
+ ptr = pGrainBits + x*bytesDepth + (y*Width*bytesDepth);
+
+ nRand = (rand_r(&seed) % Noise) - (Noise / 2);
+ if (sixteenBit)
+ component = CLAMP(32768 + nRand, 0, 65535);
+ else
+ component = CLAMP(128 + nRand, 0, 255);
+
+ grainData.setRed (component);
+ grainData.setGreen(component);
+ grainData.setBlue (component);
+ grainData.setAlpha(0);
+
+ grainData.setPixel(ptr);
+ }
+
+ // Update progress bar in dialog.
+ progress = (int) (30.0 + ((double)x * 10.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+
+ // Smooth grain mask using gaussian blur.
+
+ Digikam::DImgImageFilters().gaussianBlurImage(pGrainBits, Width, Height, sixteenBit, 1);
+
+ postProgress( 40 );
+ if (m_cancel)
+ {
+ delete [] pGrainBits;
+ return;
+ }
+ }
+
+ postProgress( 50 );
+ if (m_cancel)
+ {
+ delete [] pGrainBits;
+ return;
+ }
+
+ // Normally, film grain tends to be most noticeable in the midtones, and much less
+ // so in the shadows and highlights. Adjust histogram curve to adjust grain like this.
+
+ if (Grain)
+ {
+ Digikam::ImageCurves *grainCurves = new Digikam::ImageCurves(sixteenBit);
+ pMaskBits = new uchar[numBytes]; // Grain mask with curves adjustment.
+
+ // We modify only global luminosity of the grain.
+ if (sixteenBit)
+ {
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 8, TQPoint(32768, 32768));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(65535, 0));
+ }
+ else
+ {
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 8, TQPoint(128, 128));
+ grainCurves->setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(255, 0));
+ }
+
+ // Calculate curves and lut to apply on grain.
+ grainCurves->curvesCalculateCurve(Digikam::ImageHistogram::ValueChannel);
+ grainCurves->curvesLutSetup(Digikam::ImageHistogram::AlphaChannel);
+ grainCurves->curvesLutProcess(pGrainBits, pMaskBits, Width, Height);
+ delete grainCurves;
+
+ // delete it here, not used any more
+ delete [] pGrainBits;
+ pGrainBits = 0;
+ }
+
+ postProgress( 60 );
+ if (m_cancel)
+ {
+ delete [] pGrainBits;
+ delete [] pMaskBits;
+ return;
+ }
+
+ // Merge gray scale image with grain using shade coefficient.
+
+ if (Grain)
+ {
+ pOverlayBits = new uchar[numBytes]; // Overlay to merge with original converted in gray scale.
+
+ // get composer for default blending
+ Digikam::DColorComposer *composer = Digikam::DColorComposer::getComposer(Digikam::DColorComposer::PorterDuffNone);
+ int alpha;
+
+ int Shade = 52; // This value control the shading pixel effect between original image and grain mask.
+ if (sixteenBit)
+ Shade = (Shade + 1) * 256 - 1;
+
+ for (int x = 0; !m_cancel && x < Width; x++)
+ {
+ for (int y = 0; !m_cancel && y < Height; y++)
+ {
+ int offset = x*bytesDepth + (y*Width*bytesDepth);
+
+ // read color from orig image
+ bwBlurData.setColor(pBWBlurBits + offset, sixteenBit);
+ // read color from mask
+ maskData.setColor(pMaskBits + offset, sixteenBit);
+ // set shade as alpha value - it will be used as source alpha when blending
+ maskData.setAlpha(Shade);
+
+ // compose, write result to blendData.
+ // Preserve alpha, do not blend it (taken from old algorithm - correct?)
+ alpha = bwBlurData.alpha();
+ composer->compose(bwBlurData, maskData);
+ bwBlurData.setAlpha(alpha);
+
+ // write to destination
+ bwBlurData.setPixel(pOverlayBits + offset);
+ }
+
+ // Update progress bar in dialog.
+ progress = (int) (70.0 + ((double)x * 10.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+
+ delete composer;
+
+ // delete it here, not used any more
+ BWBlurImage.reset();
+ delete [] pMaskBits;
+ pMaskBits = 0;
+ }
+ else
+ {
+ // save a memcpy
+ pOverlayBits = pBWBlurBits;
+ pBWBlurBits = 0;
+ }
+
+ //------------------------------------------
+ // 3 - Merge Grayscale image & overlay mask.
+ //------------------------------------------
+
+ // Merge overlay and gray scale image using 'Overlay' Gimp method for increase the highlight.
+ // The result is usually a brighter picture.
+ // Overlay mode composite value computation is D = A * (B + (2 * B) * (255 - A)).
+
+ outData.setSixteenBit(sixteenBit);
+ for (int x = 0; !m_cancel && x < Width; x++)
+ {
+ for (int y = 0; !m_cancel && y < Height; y++)
+ {
+ offset = x*bytesDepth + (y*Width*bytesDepth);
+
+ bwData.setColor (pBWBits + offset, sixteenBit);
+ overData.setColor(pOverlayBits + offset, sixteenBit);
+
+ if (sixteenBit)
+ {
+ outData.setRed ( intMult16 (bwData.red(), bwData.red() + intMult16(2 * overData.red(), 65535 - bwData.red()) ) );
+ outData.setGreen( intMult16 (bwData.green(), bwData.green() + intMult16(2 * overData.green(), 65535 - bwData.green()) ) );
+ outData.setBlue ( intMult16 (bwData.blue(), bwData.blue() + intMult16(2 * overData.blue(), 65535 - bwData.blue()) ) );
+ }
+ else
+ {
+ outData.setRed ( intMult8 (bwData.red(), bwData.red() + intMult8(2 * overData.red(), 255 - bwData.red()) ) );
+ outData.setGreen( intMult8 (bwData.green(), bwData.green() + intMult8(2 * overData.green(), 255 - bwData.green()) ) );
+ outData.setBlue ( intMult8 (bwData.blue(), bwData.blue() + intMult8(2 * overData.blue(), 255 - bwData.blue()) ) );
+ }
+ outData.setAlpha( bwData.alpha() );
+ outData.setPixel( pOutBits + offset );
+ }
+
+ // Update progress bar in dialog.
+ progress = (int) (80.0 + ((double)x * 20.0) / Width);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ delete [] pGrainBits;
+ delete [] pMaskBits;
+
+ if (Grain)
+ delete [] pOverlayBits;
+}
+
+} // NameSpace DigikamInfraredImagesPlugin
diff --git a/src/imageplugins/infrared/infrared.h b/src/imageplugins/infrared/infrared.h
new file mode 100644
index 00000000..fc2f89ee
--- /dev/null
+++ b/src/imageplugins/infrared/infrared.h
@@ -0,0 +1,60 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Infrared threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef INFRARED_H
+#define INFRARED_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamInfraredImagesPlugin
+{
+
+class Infrared : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ Infrared(Digikam::DImg *orgImage, TQObject *parent=0, int sensibility=1, bool grain=true);
+
+ ~Infrared(){};
+
+private: // Infrared filter data.
+
+ bool m_grain;
+
+ int m_sensibility;
+
+private: // Infrared filter methods.
+
+ virtual void filterImage(void);
+
+ void infraredImage(Digikam::DImg *orgImage, int Sensibility, bool Grain);
+
+};
+
+} // NameSpace DigikamInfraredImagesPlugin
+
+#endif /* INFRARED_H */
diff --git a/src/imageplugins/infrared/infraredtool.cpp b/src/imageplugins/infrared/infraredtool.cpp
new file mode 100644
index 00000000..f34d1c8a
--- /dev/null
+++ b/src/imageplugins/infrared/infraredtool.cpp
@@ -0,0 +1,225 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-22
+ * Description : a digiKam image editor plugin for simulate
+ * infrared film.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlcdnumber.h>
+#include <tqslider.h>
+#include <tqlayout.h>
+#include <tqdatetime.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "infrared.h"
+#include "infraredtool.h"
+#include "infraredtool.moc"
+
+using namespace Digikam;
+
+namespace DigikamInfraredImagesPlugin
+{
+
+InfraredTool::InfraredTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("infrared");
+ setToolName(i18n("Infrared"));
+ setToolIcon(SmallIcon("infrared"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 3, 1);
+ TQLabel *label1 = new TQLabel(i18n("Sensitivity (ISO):"), m_gboxSettings->plainPage());
+
+ m_sensibilitySlider = new TQSlider(1, 25, 1, 1, TQt::Horizontal, m_gboxSettings->plainPage());
+ m_sensibilitySlider->setTracking(false);
+ m_sensibilitySlider->setTickInterval(1);
+ m_sensibilitySlider->setTickmarks(TQSlider::Below);
+
+ m_sensibilityLCDValue = new TQLCDNumber(4, m_gboxSettings->plainPage());
+ m_sensibilityLCDValue->setSegmentStyle(TQLCDNumber::Flat);
+ m_sensibilityLCDValue->display(TQString::number(200));
+ TQString whatsThis = i18n("<p>Set here the ISO-sensitivity of the simulated infrared film. "
+ "Increasing this value will increase the proportion of green color in the mix. "
+ "It will also increase the halo effect on the hightlights, and the film "
+ "graininess (if that box is checked).</p>"
+ "<p>Note: to simulate an <b>Ilford SFX200</b> infrared film, use a sensitivity excursion of 200 to 800. "
+ "A sensitivity over 800 simulates <b>Kodak HIE</b> high-speed infrared film. This last one creates a more "
+ "dramatic photographic style.</p>");
+
+ TQWhatsThis::add(m_sensibilityLCDValue, whatsThis);
+ TQWhatsThis::add(m_sensibilitySlider, whatsThis);
+
+ // -------------------------------------------------------------
+
+ m_addFilmGrain = new TQCheckBox(i18n("Add film grain"), m_gboxSettings->plainPage());
+ m_addFilmGrain->setChecked(true);
+ TQWhatsThis::add( m_addFilmGrain, i18n("<p>This option adds infrared film grain to "
+ "the image depending on ISO-sensitivity."));
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 1);
+ grid->addMultiCellWidget(m_sensibilitySlider, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_sensibilityLCDValue, 1, 1, 1, 1);
+ grid->addMultiCellWidget(m_addFilmGrain, 2, 2, 0, 1);
+ grid->setRowStretch(3, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "infrared Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()) );
+
+ // this connection is necessary to change the LCD display when
+ // the value is changed by single clicking on the slider
+ connect( m_sensibilitySlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+
+ connect( m_sensibilitySlider, TQ_SIGNAL(sliderMoved(int)),
+ this, TQ_SLOT(slotSliderMoved(int)) );
+
+ connect( m_addFilmGrain, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()) );
+}
+
+InfraredTool::~InfraredTool()
+{
+}
+
+void InfraredTool::renderingFinished()
+{
+ m_sensibilitySlider->setEnabled(true);
+ m_addFilmGrain->setEnabled(true);
+}
+
+void InfraredTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("infrared Tool");
+ m_sensibilitySlider->blockSignals(true);
+ m_addFilmGrain->blockSignals(true);
+ m_sensibilitySlider->setValue(config->readNumEntry("SensitivityAjustment", 1));
+ m_addFilmGrain->setChecked(config->readBoolEntry("AddFilmGrain", false));
+ m_sensibilitySlider->blockSignals(false);
+ m_addFilmGrain->blockSignals(false);
+ slotSliderMoved(m_sensibilitySlider->value());
+}
+
+void InfraredTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("infrared Tool");
+ config->writeEntry("SensitivityAjustment", m_sensibilitySlider->value());
+ config->writeEntry("AddFilmGrain", m_addFilmGrain->isChecked());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void InfraredTool::slotResetSettings()
+{
+ m_sensibilitySlider->blockSignals(true);
+ m_addFilmGrain->blockSignals(true);
+ m_sensibilitySlider->setValue(1);
+ m_addFilmGrain->setChecked(false);
+ m_sensibilitySlider->blockSignals(false);
+ m_addFilmGrain->blockSignals(false);
+}
+
+void InfraredTool::slotSliderMoved(int v)
+{
+ m_sensibilityLCDValue->display( TQString::number(100 + 100 * v) );
+}
+
+void InfraredTool::prepareEffect()
+{
+ m_addFilmGrain->setEnabled(false);
+ m_sensibilitySlider->setEnabled(false);
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+ int s = 100 + 100 * m_sensibilitySlider->value();
+ bool g = m_addFilmGrain->isChecked();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Infrared(&image, this, s, g)));
+}
+
+void InfraredTool::prepareFinal()
+{
+ m_addFilmGrain->setEnabled(false);
+ m_sensibilitySlider->setEnabled(false);
+
+ int s = 100 + 100 * m_sensibilitySlider->value();
+ bool g = m_addFilmGrain->isChecked();
+
+ ImageIface iface(0, 0);
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Infrared(iface.getOriginalImg(), this, s, g)));
+}
+
+void InfraredTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void InfraredTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Infrared"), filter()->getTargetImage().bits());
+}
+
+} // NameSpace DigikamInfraredImagesPlugin
diff --git a/src/imageplugins/infrared/infraredtool.h b/src/imageplugins/infrared/infraredtool.h
new file mode 100644
index 00000000..ddb5144b
--- /dev/null
+++ b/src/imageplugins/infrared/infraredtool.h
@@ -0,0 +1,86 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-22
+ * Description : a digiKam image editor plugin for simulate
+ * infrared film.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef INFRAREDTOOL_H
+#define INFRAREDTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQSlider;
+class TQLCDNumber;
+class TQCheckBox;
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamInfraredImagesPlugin
+{
+
+class InfraredTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ InfraredTool(TQObject* parent);
+ ~InfraredTool();
+
+private slots:
+
+ void slotSliderMoved(int);
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQCheckBox *m_addFilmGrain;
+
+ TQSlider *m_sensibilitySlider;
+
+ TQLCDNumber *m_sensibilityLCDValue;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamInfraredImagesPlugin
+
+#endif /* INFRAREDTOOL_H */
diff --git a/src/imageplugins/inpainting/Makefile.am b/src/imageplugins/inpainting/Makefile.am
new file mode 100644
index 00000000..243253a8
--- /dev/null
+++ b/src/imageplugins/inpainting/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/greycstoration \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_inpainting_la_SOURCES = imageplugin_inpainting.cpp \
+ inpaintingtool.cpp
+
+digikamimageplugin_inpainting_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_inpainting_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -no-undefined -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_inpainting.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_inpainting.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_inpainting_ui.rc
diff --git a/src/imageplugins/inpainting/digikamimageplugin_inpainting.desktop b/src/imageplugins/inpainting/digikamimageplugin_inpainting.desktop
new file mode 100644
index 00000000..09f891a7
--- /dev/null
+++ b/src/imageplugins/inpainting/digikamimageplugin_inpainting.desktop
@@ -0,0 +1,49 @@
+[Desktop Entry]
+Name=ImagePlugin_InPainting
+Name[bg]=Приставка за снимка - Дорисуване
+Name[da]=Plugin til indfyldning
+Name[el]=ΠρόσθετοΕικόνας_Αποζωγραφισμός
+Name[hr]=Bojanje
+Name[it]=PluginImmagini_Reintegrazione
+Name[nl]=Afbeeldingsplugin_Inkleuren
+Name[sr]=Пребојавање
+Name[sr@Latn]=Prebojavanje
+Name[sv]=Insticksprogram för ifyllnad
+Name[tr]=ResimEklentisi_InPainting
+Name[xx]=xxImagePlugin_InPaintingxx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=digiKam plugin to inpaint a photograph
+Comment[bg]=Приставка на digiKam за дорисуване на повредени или липсващи области от снимки
+Comment[ca]=Connector pel digiKam per repintar una fotografia
+Comment[da]=Digikam plugin til at indfylde i et fotografi
+Comment[de]=digiKam-Modul zur Korrektur von Bildfehlern durch Inpainting
+Comment[el]=Πρόσθετο αποζωγραφισμού μιας φωτογραφίας μέσα για το digiKam
+Comment[es]=Plugin para digiKam repintar una parte de una fotografía
+Comment[et]=DigiKami foto objektide kaotamise plugin
+Comment[fa]=وصلۀ digiKam برای کشیدن یک عکس
+Comment[gl]=Un plugin de digiKam para pintar por dentro unha fotografia
+Comment[hr]=digiKam dodatak za bojanje u fotografiji
+Comment[is]=Íforrit fyrir digiKam til að fjarlægja óæskilega hluti úr mynd
+Comment[it]=Plugin di digiKam per reintegrare una fotografia
+Comment[ja]=digiKam 写真修復プラグイン
+Comment[nds]=digiKam-Moduul för de Bildkorrektuur dör Övermalen
+Comment[nl]=Digikam-plugin voor het inkleuren van een foto
+Comment[pa]=ਇੱਕ ਫੋਟੋ-ਗਰਾਫ਼ ਇਨ-ਪੇਂਟ ਲਈ ਡਿਜ਼ੀਕੈਮ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam umożliwiająca zamalowanie fragmentów zdjęcia
+Comment[pt]=Um 'plugin' do digiKam para pintar por dentro uma fotografia
+Comment[pt_BR]=Um 'plugin' do digiKam para pintar por dentro uma fotografia
+Comment[ru]=Модуль подрисовки фотографий для digiKam
+Comment[sk]=digiKam plugin na odstránenie artefaktov
+Comment[sr]=digiKam-ов прикључак за пребојавање фотографије
+Comment[sr@Latn]=digiKam-ov priključak za prebojavanje fotografije
+Comment[sv]=Digikam insticksprogram för att fylla i ett fotografi
+Comment[tr]=TDE Fotoğraf Yöneticisi
+Comment[uk]=Втулок малювання на фотографіях для digiKam
+Comment[vi]=Phần bổ sung sơn vào ảnh chụp cho digiKam
+Comment[xx]=xxdigiKam plugin to inpaint a photographxx
+
+X-TDE-Library=digikamimageplugin_inpainting
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/inpainting/digikamimageplugin_inpainting_ui.rc b/src/imageplugins/inpainting/digikamimageplugin_inpainting_ui.rc
new file mode 100644
index 00000000..a4c13901
--- /dev/null
+++ b/src/imageplugins/inpainting/digikamimageplugin_inpainting_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_inpainting" >
+
+ <MenuBar>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ <Action name="imageplugin_inpainting" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_inpainting" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/inpainting/imageeffect_inpainting.cpp b/src/imageplugins/inpainting/imageeffect_inpainting.cpp
new file mode 100644
index 00000000..d7f2f5fe
--- /dev/null
+++ b/src/imageplugins/inpainting/imageeffect_inpainting.cpp
@@ -0,0 +1,466 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-30
+ * Description : a digiKam image editor plugin to inpaint
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ include.
+
+#include <cstdio>
+#include <cmath>
+#include <cstring>
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqtabwidget.h>
+#include <tqtimer.h>
+#include <tqevent.h>
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqbrush.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <kurllabel.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <tdefiledialog.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <tdemessagebox.h>
+#include <knuminput.h>
+#include <tdeglobalsettings.h>
+#include <kpassivepopup.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "greycstorationsettings.h"
+#include "greycstorationwidget.h"
+#include "greycstorationiface.h"
+#include "imageeffect_inpainting.h"
+#include "imageeffect_inpainting.moc"
+
+namespace DigikamInPaintingImagesPlugin
+{
+
+class InPaintingPassivePopup : public KPassivePopup
+{
+public:
+
+ InPaintingPassivePopup(TQWidget* parent) : KPassivePopup(parent), m_parent(parent) {}
+
+protected:
+
+ virtual void positionSelf() { move(m_parent->x() + 30, m_parent->y() + 30); }
+
+private:
+
+ TQWidget* m_parent;
+};
+
+//------------------------------------------------------------------------------------------
+
+void ImageEffect_InPainting::inPainting(TQWidget* parent)
+{
+ // -- check if we actually have a selection --------------------
+
+ Digikam::ImageIface iface(0, 0);
+
+ int w = iface.selectedWidth();
+ int h = iface.selectedHeight();
+
+ if (!w || !h)
+ {
+ InPaintingPassivePopup* popup = new InPaintingPassivePopup(parent);
+ popup->setView(i18n("Inpainting Photograph Tool"),
+ i18n("You need to select a region to inpaint to use "
+ "this tool"));
+ popup->setAutoDelete(true);
+ popup->setTimeout(2500);
+ popup->show();
+ return;
+ }
+
+ // -- run the dlg ----------------------------------------------
+
+ ImageEffect_InPainting_Dialog dlg(parent);
+ dlg.exec();
+}
+
+//------------------------------------------------------------------------------------------
+
+ImageEffect_InPainting_Dialog::ImageEffect_InPainting_Dialog(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Photograph Inpainting"),
+ "inpainting", true, true, false,
+ Digikam::ImageGuideWidget::HVGuideMode,
+ 0, true, true, true)
+{
+ m_isComputed = false;
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Photograph Inpainting"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to inpaint a photograph."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("David Tschumperle", I18N_NOOP("CImg library"), 0,
+ "http://cimg.sourceforge.net");
+
+ about->addAuthor("Gerhard Kulzer", I18N_NOOP("Feedback and plugin polishing"),
+ "gerhard at kulzer.net");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout(gboxSettings, 2, 1, spacingHint());
+ m_mainTab = new TQTabWidget( gboxSettings );
+
+ TQWidget* firstPage = new TQWidget( m_mainTab );
+ TQGridLayout* grid = new TQGridLayout( firstPage, 2, 2, marginHint(), spacingHint());
+ m_mainTab->addTab( firstPage, i18n("Preset") );
+
+ KURLLabel *cimgLogoLabel = new KURLLabel(firstPage);
+ cimgLogoLabel->setText(TQString());
+ cimgLogoLabel->setURL("http://cimg.sourceforge.net");
+ TDEGlobal::dirs()->addResourceType("logo-cimg", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-cimg", "logo-cimg.png");
+ cimgLogoLabel->setPixmap( TQPixmap( directory + "logo-cimg.png" ) );
+ TQToolTip::add(cimgLogoLabel, i18n("Visit CImg library website"));
+
+ TQLabel *typeLabel = new TQLabel(i18n("Filtering type:"), firstPage);
+ typeLabel->setAlignment ( TQt::AlignRight | TQt::AlignVCenter);
+ m_inpaintingTypeCB = new TQComboBox( false, firstPage );
+ m_inpaintingTypeCB->insertItem( i18n("None") );
+ m_inpaintingTypeCB->insertItem( i18n("Remove Small Artefact") );
+ m_inpaintingTypeCB->insertItem( i18n("Remove Medium Artefact") );
+ m_inpaintingTypeCB->insertItem( i18n("Remove Large Artefact") );
+ TQWhatsThis::add( m_inpaintingTypeCB, i18n("<p>Select the filter preset to use for photograph restoration:<p>"
+ "<b>None</b>: Most common values. Puts settings to default.<p>"
+ "<b>Remove Small Artefact</b>: inpaint small image artefact like image glitch.<p>"
+ "<b>Remove Medium Artefact</b>: inpaint medium image artefact.<p>"
+ "<b>Remove Large Artefact</b>: inpaint image artefact like unwanted object.<p>"));
+
+ grid->addMultiCellWidget(cimgLogoLabel, 0, 0, 1, 1);
+ grid->addMultiCellWidget(typeLabel, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_inpaintingTypeCB, 1, 1, 1, 1);
+ grid->setRowStretch(1, 10);
+
+ // -------------------------------------------------------------
+
+ m_settingsWidget = new Digikam::GreycstorationWidget(m_mainTab);
+
+ gridSettings->addMultiCellWidget(m_mainTab, 0, 0, 1, 1);
+ gridSettings->addMultiCellWidget(new TQLabel(gboxSettings), 1, 1, 1, 1);
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(cimgLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processCImgURL(const TQString&)));
+
+ connect(m_inpaintingTypeCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotResetValues(int)));
+}
+
+ImageEffect_InPainting_Dialog::~ImageEffect_InPainting_Dialog()
+{
+}
+
+void ImageEffect_InPainting_Dialog::renderingFinished()
+{
+ m_mainTab->setEnabled(true);
+}
+
+void ImageEffect_InPainting_Dialog::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("inpainting Tool Dialog");
+
+ Digikam::GreycstorationSettings settings;
+ settings.fastApprox = config->readBoolEntry("FastApprox", true);
+ settings.interp = config->readNumEntry("Interpolation",
+ Digikam::GreycstorationSettings::NearestNeighbor);
+ settings.amplitude = config->readDoubleNumEntry("Amplitude", 20.0);
+ settings.sharpness = config->readDoubleNumEntry("Sharpness", 0.3);
+ settings.anisotropy = config->readDoubleNumEntry("Anisotropy", 1.0);
+ settings.alpha = config->readDoubleNumEntry("Alpha", 0.8);
+ settings.sigma = config->readDoubleNumEntry("Sigma", 2.0);
+ settings.gaussPrec = config->readDoubleNumEntry("GaussPrec", 2.0);
+ settings.dl = config->readDoubleNumEntry("Dl", 0.8);
+ settings.da = config->readDoubleNumEntry("Da", 30.0);
+ settings.nbIter = config->readNumEntry("Iteration", 30);
+ settings.tile = config->readNumEntry("Tile", 512);
+ settings.btile = config->readNumEntry("BTile", 4);
+ m_settingsWidget->setSettings(settings);
+
+ int p = config->readNumEntry("Preset", NoPreset);
+ m_inpaintingTypeCB->setCurrentItem(p);
+ if (p == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+}
+
+void ImageEffect_InPainting_Dialog::writeUserSettings()
+{
+ Digikam::GreycstorationSettings settings = m_settingsWidget->getSettings();
+ TDEConfig* config = kapp->config();
+ config->setGroup("inpainting Tool Dialog");
+ config->writeEntry("Preset", m_inpaintingTypeCB->currentItem());
+ config->writeEntry("FastApprox", settings.fastApprox);
+ config->writeEntry("Interpolation", settings.interp);
+ config->writeEntry("Amplitude", settings.amplitude);
+ config->writeEntry("Sharpness", settings.sharpness);
+ config->writeEntry("Anisotropy", settings.anisotropy);
+ config->writeEntry("Alpha", settings.alpha);
+ config->writeEntry("Sigma", settings.sigma);
+ config->writeEntry("GaussPrec", settings.gaussPrec);
+ config->writeEntry("Dl", settings.dl);
+ config->writeEntry("Da", settings.da);
+ config->writeEntry("Iteration", settings.nbIter);
+ config->writeEntry("Tile", settings.tile);
+ config->writeEntry("BTile", settings.btile);
+ config->sync();
+}
+
+void ImageEffect_InPainting_Dialog::slotResetValues(int i)
+{
+ if (i == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+
+ resetValues();
+}
+
+void ImageEffect_InPainting_Dialog::resetValues()
+{
+ Digikam::GreycstorationSettings settings;
+ settings.setInpaintingDefaultSettings();
+
+ switch(m_inpaintingTypeCB->currentItem())
+ {
+ case RemoveSmallArtefact:
+ // We use default settings here.
+ break;
+
+ case RemoveMediumArtefact:
+ {
+ settings.amplitude = 50.0;
+ settings.nbIter = 50;
+ break;
+ }
+
+ case RemoveLargeArtefact:
+ {
+ settings.amplitude = 100.0;
+ settings.nbIter = 100;
+ break;
+ }
+ }
+
+ m_settingsWidget->setSettings(settings);
+}
+
+void ImageEffect_InPainting_Dialog::processCImgURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void ImageEffect_InPainting_Dialog::prepareEffect()
+{
+ m_mainTab->setEnabled(false);
+
+ Digikam::ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ m_originalImage = Digikam::DImg(iface.originalWidth(), iface.originalHeight(),
+ iface.originalSixteenBit(), iface.originalHasAlpha(), data);
+ delete [] data;
+
+ // Selected area from the image and mask creation:
+ //
+ // We optimize the computation time to use the current selected area in image editor
+ // and to create an inpainting mask with it. Because inpainting is done by interpolation
+ // neighboor pixels which can be located far from the selected area, we need to ajust the
+ // mask size in according with the parameter algorithms, especially 'amplitude'.
+ // Mask size is computed like this :
+ //
+ // (image_size_x + 2*amplitude , image_size_y + 2*amplitude)
+
+
+ TQRect selectionRect = TQRect(iface.selectedXOrg(), iface.selectedYOrg(),
+ iface.selectedWidth(), iface.selectedHeight());
+
+ TQPixmap inPaintingMask(iface.originalWidth(), iface.originalHeight());
+ inPaintingMask.fill(TQt::black);
+ TQPainter p(&inPaintingMask);
+ p.fillRect( selectionRect, TQBrush(TQt::white) );
+ p.end();
+
+ Digikam::GreycstorationSettings settings = m_settingsWidget->getSettings();
+
+ int x1 = (int)(selectionRect.left() - 2*settings.amplitude);
+ int y1 = (int)(selectionRect.top() - 2*settings.amplitude);
+ int x2 = (int)(selectionRect.right() + 2*settings.amplitude);
+ int y2 = (int)(selectionRect.bottom() + 2*settings.amplitude);
+ m_maskRect = TQRect(x1, y1, x2-x1, y2-y1);
+
+ // Mask area normalization.
+ // We need to check if mask area is out of image size else inpainting give strange results.
+
+ if (m_maskRect.left() < 0) m_maskRect.setLeft(0);
+ if (m_maskRect.top() < 0) m_maskRect.setTop(0);
+ if (m_maskRect.right() > iface.originalWidth()) m_maskRect.setRight(iface.originalWidth());
+ if (m_maskRect.bottom() > iface.originalHeight()) m_maskRect.setBottom(iface.originalHeight());
+
+ m_maskImage = inPaintingMask.convertToImage().copy(m_maskRect);
+ m_cropImage = m_originalImage.copy(m_maskRect);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Digikam::GreycstorationIface(
+ &m_cropImage,
+ settings,
+ Digikam::GreycstorationIface::InPainting,
+ 0, 0,
+ m_maskImage, this));
+}
+
+void ImageEffect_InPainting_Dialog::prepareFinal()
+{
+ if (!m_isComputed)
+ {
+ setProgressVisible(true);
+ prepareEffect();
+ }
+ else
+ {
+ putFinalData();
+ kapp->restoreOverrideCursor();
+ accept();
+ }
+}
+
+void ImageEffect_InPainting_Dialog::putPreviewData()
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ Digikam::GreycstorationSettings settings = m_settingsWidget->getSettings();
+
+ m_cropImage = m_threadedFilter->getTargetImage();
+ TQRect cropSel((int)(2*settings.amplitude), (int)(2*settings.amplitude),
+ iface->selectedWidth(), iface->selectedHeight());
+ Digikam::DImg imDest = m_cropImage.copy(cropSel);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+ m_imagePreviewWidget->updatePreview();
+ m_isComputed = true;
+}
+
+void ImageEffect_InPainting_Dialog::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ if (!m_isComputed)
+ m_cropImage = m_threadedFilter->getTargetImage();
+
+ m_originalImage.bitBltImage(&m_cropImage, m_maskRect.left(), m_maskRect.top());
+
+ iface.putOriginalImage(i18n("InPainting"), m_originalImage.bits());
+}
+
+void ImageEffect_InPainting_Dialog::slotUser3()
+{
+ KURL loadInpaintingFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Inpainting Settings File to Load")) );
+ if( loadInpaintingFile.isEmpty() )
+ return;
+
+ TQFile file(loadInpaintingFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ if (!m_settingsWidget->loadSettings(file, TQString("# Photograph Inpainting Configuration File V2")))
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a Photograph Inpainting settings text file.")
+ .arg(loadInpaintingFile.fileName()));
+ file.close();
+ return;
+ }
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the Photograph Inpainting text file."));
+
+ file.close();
+ m_inpaintingTypeCB->blockSignals(true);
+ m_inpaintingTypeCB->setCurrentItem(NoPreset);
+ m_inpaintingTypeCB->blockSignals(false);
+ m_settingsWidget->setEnabled(true);
+}
+
+void ImageEffect_InPainting_Dialog::slotUser2()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Inpainting Settings File to Save")) );
+ if( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ m_settingsWidget->saveSettings(file, TQString("# Photograph Inpainting Configuration File V2"));
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the Photograph Inpainting text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamInPaintingImagesPlugin
+
diff --git a/src/imageplugins/inpainting/imageeffect_inpainting.h b/src/imageplugins/inpainting/imageeffect_inpainting.h
new file mode 100644
index 00000000..542e9504
--- /dev/null
+++ b/src/imageplugins/inpainting/imageeffect_inpainting.h
@@ -0,0 +1,116 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-30
+ * Description : a digiKam image editor plugin to inpaint
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef IMAGEEFFECT_INPAINTING_H
+#define IMAGEEFFECT_INPAINTING_H
+
+// TQt include.
+
+#include <tqimage.h>
+#include <tqrect.h>
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "imageguidedlg.h"
+
+class TQTabWidget;
+class TQComboBox;
+
+namespace Digikam
+{
+class GreycstorationWidget;
+}
+
+namespace DigikamInPaintingImagesPlugin
+{
+
+class ImageEffect_InPainting
+{
+public:
+
+ static void inPainting(TQWidget *parent);
+};
+
+//-----------------------------------------------------------
+
+class ImageEffect_InPainting_Dialog : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_InPainting_Dialog(TQWidget* parent);
+ ~ImageEffect_InPainting_Dialog();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void readUserSettings();
+ void processCImgURL(const TQString&);
+ void slotResetValues(int);
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ enum InPaintingFilteringPreset
+ {
+ NoPreset=0,
+ RemoveSmallArtefact,
+ RemoveMediumArtefact,
+ RemoveLargeArtefact
+ };
+
+ bool m_isComputed;
+
+ TQRect m_maskRect;
+
+ TQImage m_maskImage;
+
+ TQComboBox *m_inpaintingTypeCB;
+
+ TQTabWidget *m_mainTab;
+
+ Digikam::DImg m_originalImage;
+ Digikam::DImg m_cropImage;
+
+ Digikam::GreycstorationWidget *m_settingsWidget;
+};
+
+} // NameSpace DigikamInPaintingImagesPlugin
+
+#endif /* IMAGEEFFECT_INPAINTING_H */
diff --git a/src/imageplugins/inpainting/imageplugin_inpainting.cpp b/src/imageplugins/inpainting/imageplugin_inpainting.cpp
new file mode 100644
index 00000000..2c1e30f3
--- /dev/null
+++ b/src/imageplugins/inpainting/imageplugin_inpainting.cpp
@@ -0,0 +1,97 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-30
+ * Description : a digiKam image editor plugin to inpaint
+ * a photograph
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "inpaintingtool.h"
+#include "imageplugin_inpainting.h"
+#include "imageplugin_inpainting.moc"
+
+using namespace DigikamInPaintingImagesPlugin;
+using namespace Digikam;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_inpainting,
+ KGenericFactory<ImagePlugin_InPainting>("digikamimageplugin_inpainting"));
+
+ImagePlugin_InPainting::ImagePlugin_InPainting(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_InPainting")
+{
+ m_inPaintingAction = new TDEAction(i18n("Inpainting..."), "inpainting",
+ CTRL+Key_E,
+ this, TQ_SLOT(slotInPainting()),
+ actionCollection(), "imageplugin_inpainting");
+
+ m_inPaintingAction->setWhatsThis( i18n( "This filter can be used to inpaint a part in a photo. "
+ "Select a region to inpaint to use this option.") );
+
+ setXMLFile( "digikamimageplugin_inpainting_ui.rc" );
+
+ DDebug() << "ImagePlugin_InPainting plugin loaded" << endl;
+}
+
+ImagePlugin_InPainting::~ImagePlugin_InPainting()
+{
+}
+
+void ImagePlugin_InPainting::setEnabledActions(bool enable)
+{
+ m_inPaintingAction->setEnabled(enable);
+}
+
+void ImagePlugin_InPainting::slotInPainting()
+{
+
+ ImageIface iface(0, 0);
+
+ int w = iface.selectedWidth();
+ int h = iface.selectedHeight();
+
+ if (!w || !h)
+ {
+ InPaintingPassivePopup* popup = new InPaintingPassivePopup(kapp->activeWindow());
+ popup->setView(i18n("Inpainting Photograph Tool"),
+ i18n("You need to select a region to inpaint to use "
+ "this tool"));
+ popup->setAutoDelete(true);
+ popup->setTimeout(2500);
+ popup->show();
+ return;
+ }
+
+ // -- run the dlg ----------------------------------------------
+
+ InPaintingTool *tool = new InPaintingTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/inpainting/imageplugin_inpainting.h b/src/imageplugins/inpainting/imageplugin_inpainting.h
new file mode 100644
index 00000000..fb2b1f5c
--- /dev/null
+++ b/src/imageplugins/inpainting/imageplugin_inpainting.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-30
+ * Description : a digiKam image editor plugin to inpaint
+ * a photograph
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_INPAINTING_H
+#define IMAGEPLUGIN_INPAINTING_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_InPainting : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_InPainting(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_InPainting();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotInPainting();
+
+private:
+
+ TDEAction *m_inPaintingAction;
+};
+
+#endif /* IMAGEPLUGIN_INPAINTING_H */
diff --git a/src/imageplugins/inpainting/inpaintingtool.cpp b/src/imageplugins/inpainting/inpaintingtool.cpp
new file mode 100644
index 00000000..2ada52bf
--- /dev/null
+++ b/src/imageplugins/inpainting/inpaintingtool.cpp
@@ -0,0 +1,430 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-30
+ * Description : a digiKam image editor plugin to inpaint
+ * a photograph
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+#include <cstring>
+
+// TQt includes.
+
+#include <tqbrush.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqevent.h>
+#include <tqfile.h>
+#include <tqframe.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpushbutton.h>
+#include <tqtabwidget.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <tdepopupmenu.h>
+#include <kprogress.h>
+#include <kstandarddirs.h>
+#include <kurllabel.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimgthreadedfilter.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "editortoolsettings.h"
+#include "greycstorationsettings.h"
+#include "greycstorationwidget.h"
+#include "greycstorationiface.h"
+#include "inpaintingtool.h"
+#include "inpaintingtool.moc"
+
+using namespace Digikam;
+
+namespace DigikamInPaintingImagesPlugin
+{
+
+InPaintingTool::InPaintingTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("inpainting");
+ setToolName(i18n("Inpainting"));
+ setToolIcon(SmallIcon("inpainting"));
+
+ m_isComputed = false;
+
+ m_previewWidget = new ImageWidget("inpainting Tool", 0,
+ i18n("<p>Here you can see the image selection preview with "
+ "inpainting applied."),
+ true, ImageGuideWidget::HVGuideMode, false, true);
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Try|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+ TQGridLayout* gridSettings = new TQGridLayout(m_gboxSettings->plainPage(), 2, 1);
+ m_mainTab = new TQTabWidget( m_gboxSettings->plainPage() );
+
+ TQWidget* firstPage = new TQWidget( m_mainTab );
+ TQGridLayout* grid = new TQGridLayout( firstPage, 2, 2);
+ m_mainTab->addTab( firstPage, i18n("Preset") );
+
+ KURLLabel *cimgLogoLabel = new KURLLabel(firstPage);
+ cimgLogoLabel->setText(TQString());
+ cimgLogoLabel->setURL("http://cimg.sourceforge.net");
+ TDEGlobal::dirs()->addResourceType("logo-cimg", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-cimg", "logo-cimg.png");
+ cimgLogoLabel->setPixmap( TQPixmap( directory + "logo-cimg.png" ) );
+ TQToolTip::add(cimgLogoLabel, i18n("Visit CImg library website"));
+
+ TQLabel *typeLabel = new TQLabel(i18n("Filtering type:"), firstPage);
+ typeLabel->setAlignment ( TQt::AlignRight | TQt::AlignVCenter);
+ m_inpaintingTypeCB = new TQComboBox( false, firstPage );
+ m_inpaintingTypeCB->insertItem( i18n("None") );
+ m_inpaintingTypeCB->insertItem( i18n("Remove Small Artefact") );
+ m_inpaintingTypeCB->insertItem( i18n("Remove Medium Artefact") );
+ m_inpaintingTypeCB->insertItem( i18n("Remove Large Artefact") );
+ TQWhatsThis::add( m_inpaintingTypeCB, i18n("<p>Select the filter preset to use for photograph restoration:<p>"
+ "<b>None</b>: Most common values. Puts settings to default.<p>"
+ "<b>Remove Small Artefact</b>: inpaint small image artefact like image glitch.<p>"
+ "<b>Remove Medium Artefact</b>: inpaint medium image artefact.<p>"
+ "<b>Remove Large Artefact</b>: inpaint image artefact like unwanted object.<p>"));
+
+ grid->addMultiCellWidget(cimgLogoLabel, 0, 0, 1, 1);
+ grid->addMultiCellWidget(typeLabel, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_inpaintingTypeCB, 1, 1, 1, 1);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+ grid->setRowStretch(1, 10);
+
+ // -------------------------------------------------------------
+
+ m_settingsWidget = new GreycstorationWidget(m_mainTab);
+
+ gridSettings->addMultiCellWidget(m_mainTab, 0, 0, 1, 1);
+ gridSettings->addMultiCellWidget(new TQLabel(m_gboxSettings->plainPage()), 1, 1, 1, 1);
+ gridSettings->setMargin(m_gboxSettings->spacingHint());
+ gridSettings->setSpacing(m_gboxSettings->spacingHint());
+ gridSettings->setRowStretch(1, 10);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(cimgLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processCImgURL(const TQString&)));
+
+ connect(m_inpaintingTypeCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotResetValues(int)));
+
+ // -------------------------------------------------------------
+
+ GreycstorationSettings defaults;
+ defaults.setInpaintingDefaultSettings();
+ m_settingsWidget->setDefaultSettings(defaults);
+}
+
+InPaintingTool::~InPaintingTool()
+{
+}
+
+void InPaintingTool::renderingFinished()
+{
+ m_mainTab->setEnabled(true);
+}
+
+void InPaintingTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("inpainting Tool");
+
+ GreycstorationSettings settings;
+ GreycstorationSettings defaults;
+ defaults.setInpaintingDefaultSettings();
+
+ settings.fastApprox = config->readBoolEntry("FastApprox", defaults.fastApprox);
+ settings.interp = config->readNumEntry("Interpolation", defaults.interp);
+ settings.amplitude = config->readDoubleNumEntry("Amplitude", defaults.amplitude);
+ settings.sharpness = config->readDoubleNumEntry("Sharpness", defaults.sharpness);
+ settings.anisotropy = config->readDoubleNumEntry("Anisotropy", defaults.anisotropy);
+ settings.alpha = config->readDoubleNumEntry("Alpha", defaults.alpha);
+ settings.sigma = config->readDoubleNumEntry("Sigma", defaults.sigma);
+ settings.gaussPrec = config->readDoubleNumEntry("GaussPrec", defaults.gaussPrec);
+ settings.dl = config->readDoubleNumEntry("Dl", defaults.dl);
+ settings.da = config->readDoubleNumEntry("Da", defaults.da);
+ settings.nbIter = config->readNumEntry("Iteration", defaults.nbIter);
+ settings.tile = config->readNumEntry("Tile", defaults.tile);
+ settings.btile = config->readNumEntry("BTile", defaults.btile);
+ m_settingsWidget->setSettings(settings);
+
+ int p = config->readNumEntry("Preset", NoPreset);
+ m_inpaintingTypeCB->setCurrentItem(p);
+ if (p == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+}
+
+void InPaintingTool::writeSettings()
+{
+ GreycstorationSettings settings = m_settingsWidget->getSettings();
+ TDEConfig* config = kapp->config();
+ config->setGroup("inpainting Tool");
+ config->writeEntry("Preset", m_inpaintingTypeCB->currentItem());
+ config->writeEntry("FastApprox", settings.fastApprox);
+ config->writeEntry("Interpolation", settings.interp);
+ config->writeEntry("Amplitude", settings.amplitude);
+ config->writeEntry("Sharpness", settings.sharpness);
+ config->writeEntry("Anisotropy", settings.anisotropy);
+ config->writeEntry("Alpha", settings.alpha);
+ config->writeEntry("Sigma", settings.sigma);
+ config->writeEntry("GaussPrec", settings.gaussPrec);
+ config->writeEntry("Dl", settings.dl);
+ config->writeEntry("Da", settings.da);
+ config->writeEntry("Iteration", settings.nbIter);
+ config->writeEntry("Tile", settings.tile);
+ config->writeEntry("BTile", settings.btile);
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void InPaintingTool::slotResetValues(int i)
+{
+ if (i == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+
+ slotResetSettings();
+}
+
+void InPaintingTool::slotResetSettings()
+{
+ GreycstorationSettings settings;
+ settings.setInpaintingDefaultSettings();
+
+ switch(m_inpaintingTypeCB->currentItem())
+ {
+ case RemoveSmallArtefact:
+ // We use default settings here.
+ break;
+
+ case RemoveMediumArtefact:
+ {
+ settings.amplitude = 50.0;
+ settings.nbIter = 50;
+ break;
+ }
+
+ case RemoveLargeArtefact:
+ {
+ settings.amplitude = 100.0;
+ settings.nbIter = 100;
+ break;
+ }
+ }
+
+ m_settingsWidget->setSettings(settings);
+}
+
+void InPaintingTool::processCImgURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void InPaintingTool::prepareEffect()
+{
+ m_mainTab->setEnabled(false);
+
+ ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ m_originalImage = DImg(iface.originalWidth(), iface.originalHeight(),
+ iface.originalSixteenBit(), iface.originalHasAlpha(), data);
+ delete [] data;
+
+ // Selected area from the image and mask creation:
+ //
+ // We optimize the computation time to use the current selected area in image editor
+ // and to create an inpainting mask with it. Because inpainting is done by interpolation
+ // neighboor pixels which can be located far from the selected area, we need to ajust the
+ // mask size in according with the parameter algorithms, especially 'amplitude'.
+ // Mask size is computed like this :
+ //
+ // (image_size_x + 2*amplitude , image_size_y + 2*amplitude)
+
+
+ TQRect selectionRect = TQRect(iface.selectedXOrg(), iface.selectedYOrg(),
+ iface.selectedWidth(), iface.selectedHeight());
+
+ TQPixmap inPaintingMask(iface.originalWidth(), iface.originalHeight());
+ inPaintingMask.fill(TQt::black);
+ TQPainter p(&inPaintingMask);
+ p.fillRect( selectionRect, TQBrush(TQt::white) );
+ p.end();
+
+ GreycstorationSettings settings = m_settingsWidget->getSettings();
+
+ int x1 = (int)(selectionRect.left() - 2*settings.amplitude);
+ int y1 = (int)(selectionRect.top() - 2*settings.amplitude);
+ int x2 = (int)(selectionRect.right() + 2*settings.amplitude);
+ int y2 = (int)(selectionRect.bottom() + 2*settings.amplitude);
+ m_maskRect = TQRect(x1, y1, x2-x1, y2-y1);
+
+ // Mask area normalization.
+ // We need to check if mask area is out of image size else inpainting give strange results.
+
+ if (m_maskRect.left() < 0) m_maskRect.setLeft(0);
+ if (m_maskRect.top() < 0) m_maskRect.setTop(0);
+ if (m_maskRect.right() > iface.originalWidth()) m_maskRect.setRight(iface.originalWidth());
+ if (m_maskRect.bottom() > iface.originalHeight()) m_maskRect.setBottom(iface.originalHeight());
+
+ m_maskImage = inPaintingMask.convertToImage().copy(m_maskRect);
+ m_cropImage = m_originalImage.copy(m_maskRect);
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(
+ new GreycstorationIface(
+ &m_cropImage,
+ settings,
+ GreycstorationIface::InPainting,
+ 0, 0,
+ m_maskImage, this)));
+}
+
+void InPaintingTool::prepareFinal()
+{
+ if (!m_isComputed)
+ {
+ prepareEffect();
+ }
+ else
+ {
+ Digikam::DImgThreadedFilter::EventData *eventData = new Digikam::DImgThreadedFilter::EventData();
+ eventData->progress = 100;
+ eventData->starting = false;
+ eventData->success = true;
+ TQApplication::postEvent(this, new TQCustomEvent(TQEvent::User, eventData));
+ }
+}
+
+void InPaintingTool::putPreviewData()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ GreycstorationSettings settings = m_settingsWidget->getSettings();
+
+ m_cropImage = filter()->getTargetImage();
+ TQRect cropSel((int)(2*settings.amplitude), (int)(2*settings.amplitude),
+ iface->selectedWidth(), iface->selectedHeight());
+ DImg imDest = m_cropImage.copy(cropSel);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+ m_previewWidget->updatePreview();
+ m_isComputed = true;
+}
+
+void InPaintingTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+
+ if (!m_isComputed)
+ m_cropImage = filter()->getTargetImage();
+
+ m_originalImage.bitBltImage(&m_cropImage, m_maskRect.left(), m_maskRect.top());
+
+ iface.putOriginalImage(i18n("InPainting"), m_originalImage.bits());
+}
+
+void InPaintingTool::slotLoadSettings()
+{
+ KURL loadInpaintingFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Inpainting Settings File to Load")) );
+ if( loadInpaintingFile.isEmpty() )
+ return;
+
+ TQFile file(loadInpaintingFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ if (!m_settingsWidget->loadSettings(file, TQString("# Photograph Inpainting Configuration File V2")))
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("\"%1\" is not a Photograph Inpainting settings text file.")
+ .arg(loadInpaintingFile.fileName()));
+ file.close();
+ return;
+ }
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load settings from the Photograph Inpainting text file."));
+
+ file.close();
+ m_inpaintingTypeCB->blockSignals(true);
+ m_inpaintingTypeCB->setCurrentItem(NoPreset);
+ m_inpaintingTypeCB->blockSignals(false);
+ m_settingsWidget->setEnabled(true);
+}
+
+void InPaintingTool::slotSaveAsSettings()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Inpainting Settings File to Save")) );
+ if( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ m_settingsWidget->saveSettings(file, TQString("# Photograph Inpainting Configuration File V2"));
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save settings to the Photograph Inpainting text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamInPaintingImagesPlugin
diff --git a/src/imageplugins/inpainting/inpaintingtool.h b/src/imageplugins/inpainting/inpaintingtool.h
new file mode 100644
index 00000000..44202c5d
--- /dev/null
+++ b/src/imageplugins/inpainting/inpaintingtool.h
@@ -0,0 +1,133 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-30
+ * Description : a digiKam image editor plugin to inpaint
+ * a photograph
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef INPAINTINGTOOL_H
+#define INPAINTINGTOOL_H
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqrect.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kpassivepopup.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "editortool.h"
+
+class TQTabWidget;
+class TQComboBox;
+
+namespace Digikam
+{
+class GreycstorationWidget;
+class ImageWidget;
+class EditorToolSettings;
+}
+
+namespace DigikamInPaintingImagesPlugin
+{
+
+class InPaintingPassivePopup : public KPassivePopup
+{
+public:
+
+ InPaintingPassivePopup(TQWidget* parent) : KPassivePopup(parent), m_parent(parent) {}
+
+protected:
+
+ virtual void positionSelf() { move(m_parent->x() + 30, m_parent->y() + 30); }
+
+private:
+
+ TQWidget* m_parent;
+};
+
+//-----------------------------------------------------------
+
+class InPaintingTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ InPaintingTool(TQObject* parent);
+ ~InPaintingTool();
+
+private slots:
+
+ void processCImgURL(const TQString&);
+ void slotResetValues(int);
+ void slotResetSettings();
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ enum InPaintingFilteringPreset
+ {
+ NoPreset=0,
+ RemoveSmallArtefact,
+ RemoveMediumArtefact,
+ RemoveLargeArtefact
+ };
+
+ bool m_isComputed;
+
+ TQRect m_maskRect;
+
+ TQImage m_maskImage;
+
+ TQComboBox *m_inpaintingTypeCB;
+
+ TQTabWidget *m_mainTab;
+
+ Digikam::DImg m_originalImage;
+ Digikam::DImg m_cropImage;
+
+ Digikam::GreycstorationWidget *m_settingsWidget;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamInPaintingImagesPlugin
+
+#endif /* INPAINTINGTOOL_H */
diff --git a/src/imageplugins/inserttext/Makefile.am b/src/imageplugins/inserttext/Makefile.am
new file mode 100644
index 00000000..2753def7
--- /dev/null
+++ b/src/imageplugins/inserttext/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_inserttext_la_SOURCES = imageplugin_inserttext.cpp inserttextwidget.cpp \
+ inserttexttool.cpp fontchooserwidget.cpp
+
+digikamimageplugin_inserttext_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_inserttext_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_inserttext.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_inserttext.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_inserttext_ui.rc
+
diff --git a/src/imageplugins/inserttext/digikamimageplugin_inserttext.desktop b/src/imageplugins/inserttext/digikamimageplugin_inserttext.desktop
new file mode 100644
index 00000000..e9ca6cfd
--- /dev/null
+++ b/src/imageplugins/inserttext/digikamimageplugin_inserttext.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=ImagePlugin_InsertText
+Name[bg]=Приставка за снимки - Добавяне на текст
+Name[da]=Plugin til at indsætte tekst
+Name[el]=ΠρόσθετοΕικόνας_ΕισαγωγήΚειμένου
+Name[fi]=Tekstinlisäys
+Name[hr]=Tekst
+Name[it]=PluginImmagini_InserisciTesto
+Name[nl]=Afbeeldingsplugin_TekstInvoegen
+Name[sr]=Убаци текст
+Name[sr@Latn]=Ubaci tekst
+Name[sv]=Insticksprogram för infoga text
+Name[tr]=ResimEklentisi_MetinEkle
+Name[xx]=xxImagePlugin_InsertTextxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Insert text to image plugin for digiKam
+Comment[bg]=Приставка на digiKam за добавяне на текст към снимки
+Comment[ca]=Connector pel digiKam per inserir text en una imatge
+Comment[da]=Digikam plugin til at indsætte tekst i billeder
+Comment[de]=digiKam-Modul zum Einfügen von Text in ein Bild
+Comment[el]=Πρόσθετο εισαγωγής κειμένου σε εικόνα για το digiKam
+Comment[es]=Plugin de digiKampara insertar texto a una imagen
+Comment[et]=DigiKami pildile teksti lisamise plugin
+Comment[fa]=درج متن در وصلۀ تصویر برای digiKam
+Comment[fi]=Lisää tekstiä kuvaan
+Comment[gl]=Un plugin de digiKam para inserir texto na imaxe
+Comment[hr]=digiKam dodatak za umetanje teksta
+Comment[is]=Íforrit fyrir digiKam sem setur texta inn á mynd
+Comment[it]=Plugin per l'aggiunta di testo nell'immagire per digiKam
+Comment[ja]=digiKam テキスト挿入プラグイン
+Comment[nds]=digiKam-Moduul för't Infögen vun Text in en Bild
+Comment[nl]=Digikam-plugin voor het invoegen van tekst
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਚਿੱਤਰ ਉੱਤੇ ਪਾਠ ਲਿਖਣ ਲਈ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam dodająca tekst do zdjęcia
+Comment[pt]=Um 'plugin' do digiKam para inserir texto na imagem
+Comment[pt_BR]=Plugin de inserção de texto na imagem
+Comment[ru]=Модуль digiKam наложения текста на изображения
+Comment[sk]=digiKam plugin pre vkladanie textu do obrázku
+Comment[sr]=digiKam-ов прикључак за убацивање текста у слику
+Comment[sr@Latn]=digiKam-ov priključak za ubacivanje teksta u sliku
+Comment[sv]=Digikam insticksprogram för att infoga text i bilder
+Comment[tr]=digiKam için resme metin ekleme eklentisi
+Comment[uk]=Втулок для вставляння тексту в зображення для digiKam
+Comment[vi]=Phần bổ sung chèn chữ vào ảnh cho digiKam
+Comment[xx]=xxInsert text to image plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_inserttext
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/inserttext/digikamimageplugin_inserttext_ui.rc b/src/imageplugins/inserttext/digikamimageplugin_inserttext_ui.rc
new file mode 100644
index 00000000..4fa71fcc
--- /dev/null
+++ b/src/imageplugins/inserttext/digikamimageplugin_inserttext_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_inserttext" >
+
+ <MenuBar>
+
+ <Menu name="Decorate" ><text>&amp;Decorate</text>
+ <Action name="imageplugin_inserttext" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_inserttext" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/inserttext/fontchooserwidget.cpp b/src/imageplugins/inserttext/fontchooserwidget.cpp
new file mode 100644
index 00000000..aa76ea8e
--- /dev/null
+++ b/src/imageplugins/inserttext/fontchooserwidget.cpp
@@ -0,0 +1,738 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a simple widget to choose a font based on
+ * KDE FontChooserWidget implementation.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 1999 by Preston Brown <pbrown@kde.org>
+ * Copyright (C) 1997 by Bernd Johannes Wuebben <wuebben@kde.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqfile.h>
+#include <tqfont.h>
+#include <tqfontdatabase.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqscrollbar.h>
+#include <tqspinbox.h>
+#include <tqstringlist.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kcharsets.h>
+#include <tdeconfig.h>
+#include <kdialog.h>
+#include <tdeglobal.h>
+#include <tdeglobalsettings.h>
+#include <tdelistbox.h>
+#include <tdelocale.h>
+#include <knuminput.h>
+#include <kstandarddirs.h>
+#include <tqlineedit.h>
+
+// Local includes.
+
+#include "fontchooserwidget.h"
+#include "fontchooserwidget.moc"
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+class FontChooserWidget::FontChooserWidgetPrivate
+{
+public:
+
+ FontChooserWidgetPrivate()
+ {
+ m_palette.setColor(TQPalette::Active, TQColorGroup::Text, TQt::black);
+ m_palette.setColor(TQPalette::Active, TQColorGroup::Base, TQt::white);
+ }
+
+ TQPalette m_palette;
+};
+
+FontChooserWidget::FontChooserWidget(TQWidget *parent, const char *name,
+ bool onlyFixed, const TQStringList &fontList,
+ int visibleListSize, bool diff,
+ TQButton::ToggleState *sizeIsRelativeState )
+ : TQWidget(parent, name), usingFixed(onlyFixed)
+{
+ charsetsCombo = 0;
+
+ TQString mainWhatsThisText = i18n( "Here you can choose the font to be used." );
+ TQWhatsThis::add( this, mainWhatsThisText );
+
+ d = new FontChooserWidgetPrivate;
+ TQVBoxLayout *topLayout = new TQVBoxLayout( this, 0, KDialog::spacingHint() );
+ int checkBoxGap = KDialog::spacingHint() / 2;
+
+ int row = 0;
+ TQWidget *page = new TQWidget( this );
+ topLayout->addWidget(page);
+ TQGridLayout *gridLayout = new TQGridLayout( page, 4, 3, 0, KDialog::spacingHint() );
+
+ // First, create the labels across the top
+
+ TQHBoxLayout *familyLayout = new TQHBoxLayout();
+ familyLayout->addSpacing( checkBoxGap );
+
+ if (diff)
+ {
+ familyCheckbox = new TQCheckBox(i18n("Font"), page);
+
+ connect(familyCheckbox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(toggled_checkbox()));
+
+ familyLayout->addWidget(familyCheckbox, 0, TQt::AlignLeft);
+ TQString familyCBToolTipText =
+ i18n("Change font family?");
+ TQString familyCBWhatsThisText =
+ i18n("Enable this checkbox to change the font family settings.");
+ TQWhatsThis::add( familyCheckbox, familyCBWhatsThisText );
+ TQToolTip::add( familyCheckbox, familyCBToolTipText );
+ familyLabel = 0;
+ }
+ else
+ {
+ familyCheckbox = 0;
+ familyLabel = new TQLabel( i18n("Font:"), page, "familyLabel" );
+ familyLayout->addWidget(familyLabel, 1, TQt::AlignLeft);
+ }
+
+ gridLayout->addLayout(familyLayout, row, 0 );
+
+ TQHBoxLayout *styleLayout = new TQHBoxLayout();
+
+ if (diff)
+ {
+ styleCheckbox = new TQCheckBox(i18n("Style:"), page);
+
+ connect(styleCheckbox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(toggled_checkbox()));
+
+ styleLayout->addWidget(styleCheckbox, 0, TQt::AlignLeft);
+ TQString styleCBToolTipText =
+ i18n("Change font style?");
+ TQString styleCBWhatsThisText =
+ i18n("Enable this checkbox to change the font style settings.");
+ TQWhatsThis::add( styleCheckbox, styleCBWhatsThisText );
+ TQToolTip::add( styleCheckbox, styleCBToolTipText );
+ styleLabel = 0;
+ }
+ else
+ {
+ styleCheckbox = 0;
+ styleLabel = new TQLabel( i18n("Style:"), page, "styleLabel");
+ styleLayout->addWidget(styleLabel, 1, TQt::AlignLeft);
+ }
+
+ styleLayout->addSpacing( checkBoxGap );
+ gridLayout->addLayout(styleLayout, row, 1 );
+
+ TQHBoxLayout *sizeLayout = new TQHBoxLayout();
+
+ if (diff)
+ {
+ sizeCheckbox = new TQCheckBox(i18n("Size"),page);
+
+ connect(sizeCheckbox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(toggled_checkbox()));
+
+ sizeLayout->addWidget(sizeCheckbox, 0, TQt::AlignLeft);
+ TQString sizeCBToolTipText =
+ i18n("Change font size?");
+ TQString sizeCBWhatsThisText =
+ i18n("Enable this checkbox to change the font size settings.");
+ TQWhatsThis::add( sizeCheckbox, sizeCBWhatsThisText );
+ TQToolTip::add( sizeCheckbox, sizeCBToolTipText );
+ sizeLabel = 0;
+ }
+ else
+ {
+ sizeCheckbox = 0;
+ sizeLabel = new TQLabel( i18n("Size:"), page, "sizeLabel");
+ sizeLayout->addWidget(sizeLabel, 1, TQt::AlignLeft);
+ }
+
+ sizeLayout->addSpacing( checkBoxGap );
+ sizeLayout->addSpacing( checkBoxGap ); // prevent label from eating border
+ gridLayout->addLayout(sizeLayout, row, 2 );
+
+ row ++;
+
+ // Now create the actual boxes that hold the info
+
+ familyListBox = new TDEListBox( page, "familyListBox");
+ familyListBox->setEnabled( !diff );
+ gridLayout->addWidget( familyListBox, row, 0 );
+ TQString fontFamilyWhatsThisText = i18n("Here you can choose the font family to be used." );
+ TQWhatsThis::add( familyListBox, fontFamilyWhatsThisText );
+ TQWhatsThis::add(diff?(TQWidget *) familyCheckbox:(TQWidget *) familyLabel, fontFamilyWhatsThisText );
+
+ connect(familyListBox, TQ_SIGNAL(highlighted(const TQString &)),
+ this, TQ_SLOT(family_chosen_slot(const TQString &)));
+
+ if(!fontList.isEmpty())
+ {
+ familyListBox->insertStringList(fontList);
+ }
+ else
+ {
+ fillFamilyListBox(onlyFixed);
+ }
+
+ familyListBox->setMinimumHeight( minimumListHeight( familyListBox, visibleListSize ) );
+
+ styleListBox = new TDEListBox( page, "styleListBox");
+ styleListBox->setEnabled( !diff );
+ gridLayout->addWidget(styleListBox, row, 1);
+ TQString fontStyleWhatsThisText = i18n("Here you can choose the font style to be used." );
+ TQWhatsThis::add( styleListBox, fontStyleWhatsThisText );
+ TQWhatsThis::add(diff?(TQWidget *)styleCheckbox:(TQWidget *)styleLabel, fontFamilyWhatsThisText );
+ styleListBox->insertItem(i18n("Regular"));
+ styleListBox->insertItem(i18n("Italic"));
+ styleListBox->insertItem(i18n("Bold"));
+ styleListBox->insertItem(i18n("Bold Italic"));
+ styleListBox->setMinimumWidth( minimumListWidth( styleListBox ) );
+ styleListBox->setMinimumHeight( minimumListHeight( styleListBox, visibleListSize ) );
+
+ connect(styleListBox, TQ_SIGNAL(highlighted(const TQString &)),
+ this, TQ_SLOT(style_chosen_slot(const TQString &)));
+
+ sizeListBox = new TDEListBox( page, "sizeListBox");
+ sizeOfFont = new KIntNumInput( page, "sizeOfFont");
+ sizeOfFont->setMinValue(4);
+
+ sizeListBox->setEnabled( !diff );
+ sizeOfFont->setEnabled( !diff );
+
+ if( sizeIsRelativeState )
+ {
+ TQString sizeIsRelativeCBText =
+ i18n("Relative");
+ TQString sizeIsRelativeCBToolTipText =
+ i18n("Font size<br><i>fixed</i> or <i>relative</i><br>to environment");
+ TQString sizeIsRelativeCBWhatsThisText =
+ i18n("Here you can switch between a fixed font size and a font size "
+ "to be calculated dynamically and adjusted to any changing "
+ "environment (e.g. widget dimensions, paper size)." );
+ sizeIsRelativeCheckBox = new TQCheckBox( sizeIsRelativeCBText,
+ page,
+ "sizeIsRelativeCheckBox" );
+ sizeIsRelativeCheckBox->setTristate( diff );
+ TQGridLayout *sizeLayout2 = new TQGridLayout( 3,2, KDialog::spacingHint()/2, "sizeLayout2" );
+ gridLayout->addLayout(sizeLayout2, row, 2);
+ sizeLayout2->setColStretch( 1, 1 ); // to prevent text from eating the right border
+ sizeLayout2->addMultiCellWidget( sizeOfFont, 0, 0, 0, 1);
+ sizeLayout2->addMultiCellWidget(sizeListBox, 1,1, 0,1);
+ sizeLayout2->addWidget(sizeIsRelativeCheckBox, 2, 0, TQt::AlignLeft);
+ TQWhatsThis::add( sizeIsRelativeCheckBox, sizeIsRelativeCBWhatsThisText );
+ TQToolTip::add( sizeIsRelativeCheckBox, sizeIsRelativeCBToolTipText );
+ }
+ else
+ {
+ sizeIsRelativeCheckBox = 0L;
+ TQGridLayout *sizeLayout2 = new TQGridLayout( 2,1, KDialog::spacingHint()/2, "sizeLayout2" );
+ gridLayout->addLayout(sizeLayout2, row, 2);
+ sizeLayout2->addWidget( sizeOfFont, 0, 0);
+ sizeLayout2->addMultiCellWidget(sizeListBox, 1,1, 0,0);
+ }
+
+ TQString fontSizeWhatsThisText = i18n("Here you can choose the font size to be used." );
+ TQWhatsThis::add( sizeListBox, fontSizeWhatsThisText );
+ TQWhatsThis::add( diff?(TQWidget *)sizeCheckbox:(TQWidget *)sizeLabel, fontSizeWhatsThisText );
+
+ fillSizeList();
+ sizeListBox->setMinimumWidth( minimumListWidth(sizeListBox) +
+ sizeListBox->fontMetrics().maxWidth() );
+ sizeListBox->setMinimumHeight( minimumListHeight( sizeListBox, visibleListSize ) );
+
+ connect( sizeOfFont, TQ_SIGNAL( valueChanged(int) ),
+ this, TQ_SLOT(size_value_slot(int)));
+
+ connect( sizeListBox, TQ_SIGNAL(highlighted(const TQString&)),
+ this, TQ_SLOT(size_chosen_slot(const TQString&)) );
+
+ sizeListBox->setSelected(sizeListBox->findItem(TQString::number(10)), true); // default to 10pt.
+
+ row ++;
+
+ row ++;
+
+ TQVBoxLayout *vbox;
+ page = new TQWidget( this );
+ topLayout->addWidget(page);
+ vbox = new TQVBoxLayout( page, 0, KDialog::spacingHint() );
+ TQLabel *label = new TQLabel( i18n("Actual Font"), page );
+ vbox->addWidget( label );
+
+ xlfdEdit = new TQLineEdit( page, "xlfdEdit" );
+ vbox->addWidget( xlfdEdit );
+
+ // lets initialize the display if possible
+ setFont( TDEGlobalSettings::generalFont(), usingFixed );
+
+ // check or uncheck or gray out the "relative" checkbox
+ if( sizeIsRelativeState && sizeIsRelativeCheckBox )
+ setSizeIsRelative( *sizeIsRelativeState );
+
+ TDEConfig *config = TDEGlobal::config();
+ TDEConfigGroupSaver saver(config, TQString::fromLatin1("General"));
+ showXLFDArea(config->readBoolEntry(TQString::fromLatin1("fontSelectorShowXLFD"), false));
+}
+
+FontChooserWidget::~FontChooserWidget()
+{
+ delete d;
+}
+
+int FontChooserWidget::minimumListWidth( const TQListBox *list )
+{
+ int w=0;
+
+ for( uint i=0; i<list->count(); i++ )
+ {
+ int itemWidth = list->item(i)->width(list);
+ w = TQMAX(w,itemWidth);
+ }
+
+ if( w == 0 )
+ {
+ w = 40;
+ }
+
+ w += list->frameWidth() * 2;
+ w += list->verticalScrollBar()->sizeHint().width();
+ return w;
+}
+
+int FontChooserWidget::minimumListHeight( const TQListBox *list, int numVisibleEntry )
+{
+ int w = list->count() > 0 ? list->item(0)->height(list) :
+ list->fontMetrics().lineSpacing();
+
+ if( w < 0 ) { w = 10; }
+ if( numVisibleEntry <= 0 ) { numVisibleEntry = 4; }
+ return ( w * numVisibleEntry + 2 * list->frameWidth() );
+}
+
+void FontChooserWidget::fillSizeList()
+{
+ if(! sizeListBox) return; //assertion.
+
+ static const int c[] =
+ {
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 12, 13, 14, 15,
+ 16, 17, 18, 19,
+ 20, 22, 24, 26,
+ 28, 32, 48, 64,
+ 72, 80, 94, 102,
+ 116, 128, 132, 148,
+ 156, 164, 172, 188,
+ 192, 202, 212, 224,
+ 232, 240, 248, 256,
+ 0
+ };
+
+ for(int i = 0; c[i]; ++i)
+ {
+ sizeListBox->insertItem(TQString::number(c[i]));
+ }
+}
+
+void FontChooserWidget::setColor( const TQColor & col )
+{
+ d->m_palette.setColor( TQPalette::Active, TQColorGroup::Text, col );
+}
+
+TQColor FontChooserWidget::color() const
+{
+ return d->m_palette.color( TQPalette::Active, TQColorGroup::Text );
+}
+
+void FontChooserWidget::setBackgroundColor( const TQColor & col )
+{
+ d->m_palette.setColor( TQPalette::Active, TQColorGroup::Base, col );
+}
+
+TQColor FontChooserWidget::backgroundColor() const
+{
+ return d->m_palette.color( TQPalette::Active, TQColorGroup::Base );
+}
+
+void FontChooserWidget::setSizeIsRelative( TQButton::ToggleState relative )
+{
+ // check or uncheck or gray out the "relative" checkbox
+ if( sizeIsRelativeCheckBox )
+ {
+ if( TQButton::NoChange == relative )
+ sizeIsRelativeCheckBox->setNoChange();
+ else
+ sizeIsRelativeCheckBox->setChecked( TQButton::On == relative );
+ }
+}
+
+TQButton::ToggleState FontChooserWidget::sizeIsRelative() const
+{
+ return sizeIsRelativeCheckBox
+ ? sizeIsRelativeCheckBox->state()
+ : TQButton::NoChange;
+}
+
+TQSize FontChooserWidget::sizeHint( void ) const
+{
+ return minimumSizeHint();
+}
+
+void FontChooserWidget::enableColumn( int column, bool state )
+{
+ if( column & FamilyList )
+ {
+ familyListBox->setEnabled(state);
+ }
+ if( column & StyleList )
+ {
+ styleListBox->setEnabled(state);
+ }
+ if( column & SizeList )
+ {
+ sizeListBox->setEnabled(state);
+ }
+}
+
+void FontChooserWidget::setFont( const TQFont& aFont, bool onlyFixed )
+{
+ selFont = aFont;
+ selectedSize=aFont.pointSize();
+ if (selectedSize == -1)
+ selectedSize = TQFontInfo(aFont).pointSize();
+
+ if( onlyFixed != usingFixed)
+ {
+ usingFixed = onlyFixed;
+ fillFamilyListBox(usingFixed);
+ }
+ setupDisplay();
+ displaySample(selFont);
+}
+
+int FontChooserWidget::fontDiffFlags() {
+ int diffFlags = 0;
+ if (familyCheckbox && styleCheckbox && sizeCheckbox)
+ {
+ diffFlags = (int)(familyCheckbox->isChecked() ? FontDiffFamily : 0)
+ | (int)( styleCheckbox->isChecked() ? FontDiffStyle : 0)
+ | (int)( sizeCheckbox->isChecked() ? FontDiffSize : 0);
+ }
+ return diffFlags;
+}
+
+void FontChooserWidget::toggled_checkbox()
+{
+ familyListBox->setEnabled( familyCheckbox->isChecked() );
+ styleListBox->setEnabled( styleCheckbox->isChecked() );
+ sizeListBox->setEnabled( sizeCheckbox->isChecked() );
+ sizeOfFont->setEnabled( sizeCheckbox->isChecked() );
+}
+
+void FontChooserWidget::family_chosen_slot(const TQString& family)
+{
+ TQFontDatabase dbase;
+ TQStringList styles = TQStringList(dbase.styles(family));
+ styleListBox->clear();
+ currentStyles.clear();
+ TQStringList::Iterator end(styles.end());
+ for ( TQStringList::Iterator it = styles.begin(); it != end; ++it )
+ {
+ TQString style = *it;
+ int pos = style.find("Plain");
+ if(pos >=0) style = style.replace(pos,5,i18n("Regular"));
+ pos = style.find("Normal");
+ if(pos >=0) style = style.replace(pos,6,i18n("Regular"));
+ pos = style.find("Oblique");
+ if(pos >=0) style = style.replace(pos,7,i18n("Italic"));
+ if(!styleListBox->findItem(style))
+ {
+ styleListBox->insertItem(i18n(style.utf8()));
+ currentStyles.insert(i18n(style.utf8()), *it);
+ }
+ }
+ if(styleListBox->count()==0)
+ {
+ styleListBox->insertItem(i18n("Regular"));
+ currentStyles.insert(i18n("Regular"), "Normal");
+ }
+
+ styleListBox->blockSignals(true);
+ TQListBoxItem *item = styleListBox->findItem(selectedStyle);
+ if (item)
+ styleListBox->setSelected(styleListBox->findItem(selectedStyle), true);
+ else
+ styleListBox->setSelected(0, true);
+ styleListBox->blockSignals(false);
+
+ style_chosen_slot(TQString());
+}
+
+void FontChooserWidget::size_chosen_slot(const TQString& size)
+{
+ selectedSize=size.toInt();
+ sizeOfFont->setValue(selectedSize);
+ selFont.setPointSize(selectedSize);
+ emit fontSelected(selFont);
+}
+
+void FontChooserWidget::size_value_slot(int val)
+{
+ selFont.setPointSize(val);
+ emit fontSelected(selFont);
+}
+
+void FontChooserWidget::style_chosen_slot(const TQString& style)
+{
+ TQString currentStyle;
+ if (style.isEmpty())
+ currentStyle = styleListBox->currentText();
+ else
+ currentStyle = style;
+
+ int diff=0; // the difference between the font size requested and what we can show.
+
+ sizeListBox->clear();
+ TQFontDatabase dbase;
+ if(dbase.isSmoothlyScalable(familyListBox->currentText(), currentStyles[currentStyle]))
+ { // is vector font
+ //sampleEdit->setPaletteBackgroundPixmap( VectorPixmap ); // TODO
+ fillSizeList();
+ }
+ else
+ { // is bitmap font.
+ //sampleEdit->setPaletteBackgroundPixmap( BitmapPixmap ); // TODO
+ TQValueList<int> sizes = dbase.smoothSizes(familyListBox->currentText(), currentStyles[currentStyle]);
+ if(sizes.count() > 0)
+ {
+ TQValueList<int>::iterator it;
+ diff=1000;
+ TQValueList<int>::iterator end(sizes.end());
+ for ( it = sizes.begin(); it != end; ++it )
+ {
+ if(*it <= selectedSize || diff > *it - selectedSize) diff = selectedSize - *it;
+ sizeListBox->insertItem(TQString::number(*it));
+ }
+ }
+ else // there are times TQT does not provide the list..
+ fillSizeList();
+ }
+ sizeListBox->blockSignals(true);
+ sizeListBox->setSelected(sizeListBox->findItem(TQString::number(selectedSize)), true);
+ sizeListBox->blockSignals(false);
+ sizeListBox->ensureCurrentVisible();
+
+ selFont = dbase.font(familyListBox->currentText(), currentStyles[currentStyle], selectedSize-diff);
+ emit fontSelected(selFont);
+ if (!style.isEmpty())
+ selectedStyle = style;
+}
+
+void FontChooserWidget::displaySample(const TQFont& font)
+{
+ xlfdEdit->setText(font.rawName());
+ xlfdEdit->setCursorPosition(0);
+}
+
+void FontChooserWidget::setupDisplay()
+{
+ // Calling familyListBox->setCurrentItem() causes the value of selFont
+ // to change, so we save the family, style and size beforehand.
+ TQString family = selFont.family().lower();
+ int style = (selFont.bold() ? 2 : 0) + (selFont.italic() ? 1 : 0);
+ int size = selFont.pointSize();
+ if (size == -1)
+ size = TQFontInfo(selFont).pointSize();
+ TQString sizeStr = TQString::number(size);
+
+ int numEntries, i;
+
+ numEntries = familyListBox->count();
+ for (i = 0; i < numEntries; i++)
+ {
+ if (family == familyListBox->text(i).lower())
+ {
+ familyListBox->setCurrentItem(i);
+ break;
+ }
+ }
+
+ // 1st Fallback
+ if ( (i == numEntries) )
+ {
+ if (family.contains('['))
+ {
+ family = family.left(family.find('[')).stripWhiteSpace();
+ for (i = 0; i < numEntries; i++)
+ {
+ if (family == familyListBox->text(i).lower())
+ {
+ familyListBox->setCurrentItem(i);
+ break;
+ }
+ }
+ }
+ }
+
+ // 2nd Fallback
+ if ( (i == numEntries) )
+ {
+ TQString fallback = family+" [";
+ for (i = 0; i < numEntries; i++)
+ {
+ if (familyListBox->text(i).lower().startsWith(fallback))
+ {
+ familyListBox->setCurrentItem(i);
+ break;
+ }
+ }
+ }
+
+ // 3rd Fallback
+ if ( (i == numEntries) )
+ {
+ for (i = 0; i < numEntries; i++)
+ {
+ if (familyListBox->text(i).lower().startsWith(family))
+ {
+ familyListBox->setCurrentItem(i);
+ break;
+ }
+ }
+ }
+
+ // Fall back in case nothing matched. Otherwise, diff doesn't work
+ if ( i == numEntries )
+ familyListBox->setCurrentItem( 0 );
+
+ styleListBox->setCurrentItem(style);
+
+ numEntries = sizeListBox->count();
+ for (i = 0; i < numEntries; i++)
+ {
+ if (sizeStr == sizeListBox->text(i))
+ {
+ sizeListBox->setCurrentItem(i);
+ break;
+ }
+ }
+
+ sizeOfFont->setValue(size);
+}
+
+void FontChooserWidget::getFontList( TQStringList &list, uint fontListCriteria)
+{
+ TQFontDatabase dbase;
+ TQStringList lstSys(dbase.families());
+
+ // if we have criteria; then check fonts before adding
+ if (fontListCriteria)
+ {
+ TQStringList lstFonts;
+ TQStringList::Iterator end(lstSys.end());
+ for (TQStringList::Iterator it = lstSys.begin(); it != end; ++it)
+ {
+ if ((fontListCriteria & FixedWidthFonts) > 0 && !dbase.isFixedPitch(*it)) continue;
+ if (((fontListCriteria & (SmoothScalableFonts | ScalableFonts)) == ScalableFonts) &&
+ !dbase.isBitmapScalable(*it)) continue;
+ if ((fontListCriteria & SmoothScalableFonts) > 0 && !dbase.isSmoothlyScalable(*it)) continue;
+ lstFonts.append(*it);
+ }
+
+ if((fontListCriteria & FixedWidthFonts) > 0) {
+ // Fallback.. if there are no fixed fonts found, it is probably a
+ // bug in the font server or TQt. In this case, just use 'fixed'
+ if (lstFonts.count() == 0)
+ lstFonts.append("fixed");
+ }
+
+ lstSys = lstFonts;
+ }
+
+ lstSys.sort();
+
+ list = lstSys;
+}
+
+void FontChooserWidget::addFont( TQStringList &list, const char *xfont )
+{
+ const char *ptr = strchr( xfont, '-' );
+ if ( !ptr )
+ return;
+
+ ptr = strchr( ptr + 1, '-' );
+ if ( !ptr )
+ return;
+
+ TQString font = TQString::fromLatin1(ptr + 1);
+
+ int pos;
+ if ( ( pos = font.find( '-' ) ) > 0 ) {
+ font.truncate( pos );
+
+ if ( font.find( TQString::fromLatin1("open look"), 0, false ) >= 0 )
+ return;
+
+ TQStringList::Iterator it = list.begin();
+ TQStringList::Iterator end(list.end());
+ for ( ; it != end; ++it )
+ if ( *it == font )
+ return;
+ list.append( font );
+ }
+}
+
+void FontChooserWidget::fillFamilyListBox(bool onlyFixedFonts)
+{
+ TQStringList fontList;
+ getFontList(fontList, onlyFixedFonts?FixedWidthFonts:0);
+ familyListBox->clear();
+ familyListBox->insertStringList(fontList);
+}
+
+void FontChooserWidget::showXLFDArea(bool show)
+{
+ if( show )
+ {
+ xlfdEdit->parentWidget()->show();
+ }
+ else
+ {
+ xlfdEdit->parentWidget()->hide();
+ }
+}
+
+} // NameSpace DigikamInsertTextImagesPlugin
+
diff --git a/src/imageplugins/inserttext/fontchooserwidget.h b/src/imageplugins/inserttext/fontchooserwidget.h
new file mode 100644
index 00000000..4585c231
--- /dev/null
+++ b/src/imageplugins/inserttext/fontchooserwidget.h
@@ -0,0 +1,172 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a simple widget to choose a font based on
+ * KDE FontChooserWidget implementation.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 1999 by Preston Brown <pbrown@kde.org>
+ * Copyright (C) 1997 by Bernd Johannes Wuebben <wuebben@kde.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef FONT_CHOOSER_WIDGET_H
+#define FONT_CHOOSER_WIDGET_H
+
+#include <tqlineedit.h>
+#include <tqbutton.h>
+
+class TQComboBox;
+class TQCheckBox;
+class TQFont;
+class TQGroupBox;
+class TQLabel;
+class TQStringList;
+
+class TDEListBox;
+class KIntNumInput;
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+class FontChooserWidget : public TQWidget
+{
+ TQ_OBJECT
+
+ TQ_PROPERTY( TQFont font READ font WRITE setFont )
+
+public:
+
+ enum FontColumn
+ {
+ FamilyList=0x01,
+ StyleList=0x02,
+ SizeList=0x04
+ };
+
+ enum FontDiff
+ {
+ FontDiffFamily=0x01,
+ FontDiffStyle=0x02,
+ FontDiffSize=0x04
+ };
+
+ enum FontListCriteria
+ {
+ FixedWidthFonts=0x01,
+ ScalableFonts=0x02,
+ SmoothScalableFonts=0x04
+ };
+
+public:
+
+ FontChooserWidget(TQWidget *parent = 0L, const char *name = 0L,
+ bool onlyFixed = false,
+ const TQStringList &fontList = TQStringList(),
+ int visibleListSize=8,
+ bool diff = false,
+ TQButton::ToggleState *sizeIsRelativeState = 0L );
+
+ ~FontChooserWidget();
+
+ void setFont( const TQFont &font, bool onlyFixed = false );
+ void setColor( const TQColor & col );
+ void setBackgroundColor( const TQColor & col );
+ void setSizeIsRelative( TQButton::ToggleState relative );
+
+ TQFont font() const { return selFont; };
+ TQColor color() const;
+ TQColor backgroundColor() const;
+ static void getFontList( TQStringList &list, uint fontListCriteria);
+ TQButton::ToggleState sizeIsRelative() const;
+ static TQString getXLFD( const TQFont &theFont ) { return theFont.rawName(); };
+
+ int fontDiffFlags();
+ void enableColumn( int column, bool state );
+ virtual TQSize sizeHint( void ) const;
+
+signals:
+
+ void fontSelected( const TQFont &font );
+
+private slots:
+
+ void toggled_checkbox();
+ void family_chosen_slot(const TQString&);
+ void size_chosen_slot(const TQString&);
+ void style_chosen_slot(const TQString&);
+ void displaySample(const TQFont &font);
+ void showXLFDArea(bool);
+ void size_value_slot(int);
+
+private:
+
+ void fillFamilyListBox(bool onlyFixedFonts = false);
+ void fillSizeList();
+
+ void addFont( TQStringList &list, const char *xfont );
+
+ void setupDisplay();
+
+ int minimumListWidth( const TQListBox *list );
+ int minimumListHeight( const TQListBox *list, int numVisibleEntry );
+
+private:
+
+ bool usingFixed;
+ int selectedSize;
+
+ TQMap<TQString, TQString> currentStyles;
+
+ // pointer to an optinally supplied list of fonts to
+ // inserted into the fontdialog font-family combo-box
+ TQStringList fontList;
+
+ TQLineEdit *xlfdEdit;
+
+ TQLabel *familyLabel;
+ TQLabel *styleLabel;
+
+ TQCheckBox *familyCheckbox;
+ TQCheckBox *styleCheckbox;
+ TQCheckBox *sizeCheckbox;
+ TQCheckBox *sizeIsRelativeCheckBox;
+
+ TQComboBox *charsetsCombo;
+
+ TQFont selFont;
+
+ TQString selectedStyle;
+
+ TQLabel *sizeLabel;
+
+ KIntNumInput *sizeOfFont;
+
+ TDEListBox *familyListBox;
+ TDEListBox *styleListBox;
+ TDEListBox *sizeListBox;
+
+private:
+
+ class FontChooserWidgetPrivate;
+ FontChooserWidgetPrivate *d;
+
+};
+
+} // NameSpace DigikamInsertTextImagesPlugin
+
+#endif // FONT_CHOOSER_WIDGET_H
diff --git a/src/imageplugins/inserttext/imageeffect_inserttext.cpp b/src/imageplugins/inserttext/imageeffect_inserttext.cpp
new file mode 100644
index 00000000..3f88644b
--- /dev/null
+++ b/src/imageplugins/inserttext/imageeffect_inserttext.cpp
@@ -0,0 +1,348 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a plugin to insert a text over an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqcombobox.h>
+#include <tqcheckbox.h>
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqbrush.h>
+#include <tqpen.h>
+#include <tqfont.h>
+#include <tqtimer.h>
+#include <tqhbuttongroup.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kcursor.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+#include <kcolorbutton.h>
+#include <ktextedit.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "inserttextwidget.h"
+#include "fontchooserwidget.h"
+#include "imageeffect_inserttext.h"
+#include "imageeffect_inserttext.moc"
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+ImageEffect_InsertText::ImageEffect_InsertText(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Insert Text on Photograph"),
+ "inserttext", false, false)
+{
+ TQString whatsThis;
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Insert Text"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin for inserting text on a photograph."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2006, Gilles Caulier\n"
+ "(c) 2006-2007, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(plainPage());
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* l = new TQVBoxLayout(frame, 5, 0);
+ m_previewWidget = new InsertTextWidget(480, 320, frame);
+ l->addWidget(m_previewWidget);
+ TQWhatsThis::add( m_previewWidget, i18n("<p>This previews the text inserted in the image. "
+ "You can use the mouse to move the text to the right location."));
+ setPreviewAreaWidget(frame);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gbox2 = new TQWidget(plainPage());
+ TQGridLayout *gridBox2 = new TQGridLayout( gbox2, 9, 1, spacingHint());
+
+ m_textEdit = new KTextEdit(gbox2);
+ m_textEdit->setCheckSpellingEnabled(true);
+ m_textEdit->setWordWrap(TQTextEdit::NoWrap);
+ TQWhatsThis::add( m_textEdit, i18n("<p>Here, enter the text you want to insert in your image."));
+ gridBox2->addMultiCellWidget(m_textEdit, 0, 2, 0, 1);
+
+ // -------------------------------------------------------------
+
+ m_fontChooserWidget = new FontChooserWidget(gbox2);
+ TQWhatsThis::add( m_textEdit, i18n("<p>Here you can choose the font to be used."));
+ gridBox2->addMultiCellWidget(m_fontChooserWidget, 3, 3, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TDEIconLoader icon;
+ m_alignButtonGroup = new TQHButtonGroup(gbox2);
+
+ TQPushButton *alignLeft = new TQPushButton( m_alignButtonGroup );
+ m_alignButtonGroup->insert(alignLeft, ALIGN_LEFT);
+ alignLeft->setPixmap( icon.loadIcon( "format-text-direction-ltr", (TDEIcon::Group)TDEIcon::Small ) );
+ alignLeft->setToggleButton(true);
+ TQToolTip::add( alignLeft, i18n( "Align text to the left" ) );
+
+ TQPushButton *alignRight = new TQPushButton( m_alignButtonGroup );
+ m_alignButtonGroup->insert(alignRight, ALIGN_RIGHT);
+ alignRight->setPixmap( icon.loadIcon( "format-text-direction-rtl", (TDEIcon::Group)TDEIcon::Small ) );
+ alignRight->setToggleButton(true);
+ TQToolTip::add( alignRight, i18n( "Align text to the right" ) );
+
+ TQPushButton *alignCenter = new TQPushButton( m_alignButtonGroup );
+ m_alignButtonGroup->insert(alignCenter, ALIGN_CENTER);
+ alignCenter->setPixmap( icon.loadIcon( "text_center", (TDEIcon::Group)TDEIcon::Small ) );
+ alignCenter->setToggleButton(true);
+ TQToolTip::add( alignCenter, i18n( "Align text to center" ) );
+
+ TQPushButton *alignBlock = new TQPushButton( m_alignButtonGroup );
+ m_alignButtonGroup->insert(alignBlock, ALIGN_BLOCK);
+ alignBlock->setPixmap( icon.loadIcon( "text_block", (TDEIcon::Group)TDEIcon::Small ) );
+ alignBlock->setToggleButton(true);
+ TQToolTip::add( alignBlock, i18n( "Align text to a block" ) );
+
+ m_alignButtonGroup->setExclusive(true);
+ m_alignButtonGroup->setFrameShape(TQFrame::NoFrame);
+ gridBox2->addMultiCellWidget(m_alignButtonGroup, 4, 4, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label1 = new TQLabel(i18n("Rotation:"), gbox2);
+ m_textRotation = new TQComboBox( false, gbox2 );
+ m_textRotation->insertItem( i18n("None") );
+ m_textRotation->insertItem( i18n("90 Degrees") );
+ m_textRotation->insertItem( i18n("180 Degrees") );
+ m_textRotation->insertItem( i18n("270 Degrees") );
+ TQWhatsThis::add( m_textRotation, i18n("<p>Select the text rotation to use."));
+ gridBox2->addMultiCellWidget(label1, 5, 5, 0, 0);
+ gridBox2->addMultiCellWidget(m_textRotation, 5, 5, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Color:"), gbox2);
+ m_fontColorButton = new KColorButton( TQt::black, gbox2 );
+ TQWhatsThis::add( m_fontColorButton, i18n("<p>Select the font color to use."));
+ gridBox2->addMultiCellWidget(label2, 6, 6, 0, 0);
+ gridBox2->addMultiCellWidget(m_fontColorButton, 6, 6, 1, 1);
+
+ // -------------------------------------------------------------
+
+ m_borderText = new TQCheckBox( i18n( "Add border"), gbox2 );
+ TQToolTip::add( m_borderText, i18n( "Add a solid border around text using current text color" ) );
+
+ m_transparentText = new TQCheckBox( i18n( "Semi-transparent"), gbox2 );
+ TQToolTip::add( m_transparentText, i18n( "Use semi-transparent text background under image" ) );
+
+ gridBox2->addMultiCellWidget(m_borderText, 7, 7, 0, 1);
+ gridBox2->addMultiCellWidget(m_transparentText, 8, 8, 0, 1);
+ gridBox2->setRowStretch(9, 10);
+
+ setUserAreaWidget(gbox2);
+
+ // -------------------------------------------------------------
+
+ connect(m_fontChooserWidget, TQ_SIGNAL(fontSelected (const TQFont &)),
+ this, TQ_SLOT(slotFontPropertiesChanged(const TQFont &)));
+
+ connect(m_fontColorButton, TQ_SIGNAL(changed(const TQColor &)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_textEdit, TQ_SIGNAL(textChanged()),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_alignButtonGroup, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotAlignModeChanged(int)));
+
+ connect(m_borderText, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_transparentText, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_textRotation, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(this, TQ_SIGNAL(signalUpdatePreview()), TQ_SLOT(slotUpdatePreview()));
+ // -------------------------------------------------------------
+
+ slotUpdatePreview();
+}
+
+ImageEffect_InsertText::~ImageEffect_InsertText()
+{
+}
+
+void ImageEffect_InsertText::readUserSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("inserttext Tool Dialog");
+ TQColor black(0, 0, 0);
+ TQFont defaultFont;
+
+ int orgW = m_previewWidget->imageIface()->originalWidth();
+ int orgH = m_previewWidget->imageIface()->originalHeight();
+
+ if ( orgW > orgH ) m_defaultSizeFont = (int)(orgH / 8.0);
+ else m_defaultSizeFont = (int)(orgW / 8.0);
+
+ defaultFont.setPointSize(m_defaultSizeFont);
+ m_textRotation->setCurrentItem( config->readNumEntry("Text Rotation", 0) );
+ m_fontColorButton->setColor(config->readColorEntry("Font Color", &black));
+ m_textEdit->setText(config->readEntry("Text String", i18n("Enter your text here!")));
+ m_textFont = config->readFontEntry("Font Properties", &defaultFont);
+ m_fontChooserWidget->setFont(m_textFont);
+ m_alignTextMode = config->readNumEntry("Text Alignment", ALIGN_LEFT);
+ m_borderText->setChecked( config->readBoolEntry("Border Text", false) );
+ m_transparentText->setChecked( config->readBoolEntry("Transparent Text", false) );
+ m_previewWidget->setPositionHint( config->readRectEntry("Position Hint") );
+
+ static_cast<TQPushButton*>(m_alignButtonGroup->find(m_alignTextMode))->setOn(true);
+ slotAlignModeChanged(m_alignTextMode);
+}
+
+void ImageEffect_InsertText::writeUserSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("inserttext Tool Dialog");
+
+ config->writeEntry( "Text Rotation", m_textRotation->currentItem() );
+ config->writeEntry( "Font Color", m_fontColorButton->color() );
+ config->writeEntry( "Text String", m_textEdit->text() );
+ config->writeEntry( "Font Properties", m_textFont );
+ config->writeEntry( "Text Alignment", m_alignTextMode );
+ config->writeEntry( "Border Text", m_borderText->isChecked() );
+ config->writeEntry( "Transparent Text", m_transparentText->isChecked() );
+ config->writeEntry( "Position Hint", m_previewWidget->getPositionHint() );
+
+ config->sync();
+}
+
+void ImageEffect_InsertText::resetValues()
+{
+ m_fontColorButton->blockSignals(true);
+ m_alignButtonGroup->blockSignals(true);
+ m_fontChooserWidget->blockSignals(true);
+
+ m_textRotation->setCurrentItem(0); // No rotation.
+ m_fontColorButton->setColor(TQt::black);
+ TQFont defaultFont;
+ m_textFont = defaultFont; // Reset to default TDE font.
+ m_textFont.setPointSize( m_defaultSizeFont );
+ m_fontChooserWidget->setFont(m_textFont);
+ m_borderText->setChecked( false );
+ m_transparentText->setChecked( false );
+ m_previewWidget->resetEdit();
+ static_cast<TQPushButton*>(m_alignButtonGroup->find(ALIGN_LEFT))->setOn(true);
+
+ m_fontChooserWidget->blockSignals(false);
+ m_fontColorButton->blockSignals(false);
+ m_alignButtonGroup->blockSignals(false);
+ slotAlignModeChanged(ALIGN_LEFT);
+}
+
+void ImageEffect_InsertText::slotAlignModeChanged(int mode)
+{
+ m_alignTextMode = mode;
+ m_textEdit->selectAll(true);
+
+ switch (m_alignTextMode)
+ {
+ case ALIGN_LEFT:
+ m_textEdit->setAlignment( TQt::AlignLeft );
+ break;
+
+ case ALIGN_RIGHT:
+ m_textEdit->setAlignment( TQt::AlignRight );
+ break;
+
+ case ALIGN_CENTER:
+ m_textEdit->setAlignment( TQt::AlignHCenter );
+ break;
+
+ case ALIGN_BLOCK:
+ m_textEdit->setAlignment( TQt::AlignJustify );
+ break;
+ }
+
+ m_textEdit->selectAll(false);
+ emit signalUpdatePreview();
+}
+
+void ImageEffect_InsertText::slotFontPropertiesChanged(const TQFont &font)
+{
+ m_textFont = font;
+ emit signalUpdatePreview();
+}
+
+void ImageEffect_InsertText::slotUpdatePreview()
+{
+ m_previewWidget->setText(m_textEdit->text(), m_textFont, m_fontColorButton->color(), m_alignTextMode,
+ m_borderText->isChecked(), m_transparentText->isChecked(),
+ m_textRotation->currentItem());
+}
+
+void ImageEffect_InsertText::finalRendering()
+{
+ accept();
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ Digikam::ImageIface iface(0, 0);
+ Digikam::DImg dest = m_previewWidget->makeInsertText();
+ iface.putOriginalImage(i18n("Insert Text"), dest.bits(), dest.width(), dest.height());
+
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamInsertTextImagesPlugin
+
diff --git a/src/imageplugins/inserttext/imageeffect_inserttext.h b/src/imageplugins/inserttext/imageeffect_inserttext.h
new file mode 100644
index 00000000..2b7c204c
--- /dev/null
+++ b/src/imageplugins/inserttext/imageeffect_inserttext.h
@@ -0,0 +1,103 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a plugin to insert a text over an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_INSERTEXT_H
+#define IMAGEEFFECT_INSERTEXT_H
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqimage.h>
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+
+class TQLabel;
+class TQFont;
+class TQHButtonGroup;
+class TQComboBox;
+class TQCheckBox;
+
+class KTextEdit;
+class KColorButton;
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+class InsertTextWidget;
+class FontChooserWidget;
+
+class ImageEffect_InsertText : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_InsertText(TQWidget *parent);
+ ~ImageEffect_InsertText();
+
+signals:
+
+ void signalUpdatePreview();
+
+private slots:
+
+ void slotFontPropertiesChanged(const TQFont &font);
+ void slotUpdatePreview();
+ void slotAlignModeChanged(int mode);
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+
+private:
+
+ int m_alignTextMode;
+ int m_defaultSizeFont;
+
+ TQComboBox *m_textRotation;
+
+ TQCheckBox *m_borderText;
+ TQCheckBox *m_transparentText;
+
+ TQHButtonGroup *m_alignButtonGroup;
+
+ TQFont m_textFont;
+
+ KColorButton *m_fontColorButton;
+
+ FontChooserWidget *m_fontChooserWidget;
+
+ KTextEdit *m_textEdit;
+
+ InsertTextWidget *m_previewWidget;
+};
+
+} // NameSpace DigikamInsertTextImagesPlugin
+
+#endif /* IMAGEEFFECT_INSERTEXT_H */
diff --git a/src/imageplugins/inserttext/imageplugin_inserttext.cpp b/src/imageplugins/inserttext/imageplugin_inserttext.cpp
new file mode 100644
index 00000000..a7a5ab6b
--- /dev/null
+++ b/src/imageplugins/inserttext/imageplugin_inserttext.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a plugin to insert a text over an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "inserttexttool.h"
+#include "imageplugin_inserttext.h"
+#include "imageplugin_inserttext.moc"
+
+using namespace DigikamInsertTextImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_inserttext,
+ KGenericFactory<ImagePlugin_InsertText>("digikamimageplugin_inserttext"));
+
+ImagePlugin_InsertText::ImagePlugin_InsertText(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_InsertText")
+{
+ m_insertTextAction = new TDEAction(i18n("Insert Text..."), "inserttext",
+ SHIFT+CTRL+Key_T,
+ this, TQ_SLOT(slotInsertText()),
+ actionCollection(), "imageplugin_inserttext");
+
+ setXMLFile("digikamimageplugin_inserttext_ui.rc");
+
+ DDebug() << "ImagePlugin_InsertText plugin loaded" << endl;
+}
+
+ImagePlugin_InsertText::~ImagePlugin_InsertText()
+{
+}
+
+void ImagePlugin_InsertText::setEnabledActions(bool enable)
+{
+ m_insertTextAction->setEnabled(enable);
+}
+
+void ImagePlugin_InsertText::slotInsertText()
+{
+ InsertTextTool *tool = new InsertTextTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/inserttext/imageplugin_inserttext.h b/src/imageplugins/inserttext/imageplugin_inserttext.h
new file mode 100644
index 00000000..427b47c6
--- /dev/null
+++ b/src/imageplugins/inserttext/imageplugin_inserttext.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a plugin to insert a text over an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_INSERTTEXT_H
+#define IMAGEPLUGIN_INSERTTEXT_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_InsertText : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_InsertText(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_InsertText();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotInsertText();
+
+private:
+
+ TDEAction *m_insertTextAction;
+};
+
+#endif /* IMAGEPLUGIN_INSERTTEXT_H */
diff --git a/src/imageplugins/inserttext/inserttexttool.cpp b/src/imageplugins/inserttext/inserttexttool.cpp
new file mode 100644
index 00000000..3c854896
--- /dev/null
+++ b/src/imageplugins/inserttext/inserttexttool.cpp
@@ -0,0 +1,336 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a plugin to insert a text over an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqbrush.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqframe.h>
+#include <tqhbuttongroup.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqpixmap.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <kcolorbutton.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <ktextedit.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "editortoolsettings.h"
+#include "fontchooserwidget.h"
+#include "imageiface.h"
+#include "inserttextwidget.h"
+#include "inserttexttool.h"
+#include "inserttexttool.moc"
+
+using namespace Digikam;
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+InsertTextTool::InsertTextTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("inserttext");
+ setToolName(i18n("Insert Text"));
+ setToolIcon(SmallIcon("inserttext"));
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(0);
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* l = new TQVBoxLayout(frame, 5, 0);
+ m_previewWidget = new InsertTextWidget(480, 320, frame);
+ l->addWidget(m_previewWidget);
+ TQWhatsThis::add(m_previewWidget, i18n("<p>This previews the text inserted in the image. "
+ "You can use the mouse to move the text to the right location."));
+ setToolView(frame);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+ TQGridLayout *grid = new TQGridLayout(m_gboxSettings->plainPage(), 9, 1);
+
+ m_textEdit = new KTextEdit(m_gboxSettings->plainPage());
+ m_textEdit->setCheckSpellingEnabled(true);
+ m_textEdit->setWordWrap(TQTextEdit::NoWrap);
+ TQWhatsThis::add(m_textEdit, i18n("<p>Here, enter the text you want to insert in your image."));
+
+ // -------------------------------------------------------------
+
+ m_fontChooserWidget = new FontChooserWidget(m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_textEdit, i18n("<p>Here you can choose the font to be used."));
+
+ // -------------------------------------------------------------
+
+ TDEIconLoader icon;
+ m_alignButtonGroup = new TQHButtonGroup(m_gboxSettings->plainPage());
+
+ TQPushButton *alignLeft = new TQPushButton(m_alignButtonGroup);
+ m_alignButtonGroup->insert(alignLeft, ALIGN_LEFT);
+ alignLeft->setPixmap(icon.loadIcon("format-text-direction-ltr", (TDEIcon::Group) TDEIcon::Small));
+ alignLeft->setToggleButton(true);
+ TQToolTip::add(alignLeft, i18n("Align text to the left"));
+
+ TQPushButton *alignRight = new TQPushButton(m_alignButtonGroup);
+ m_alignButtonGroup->insert(alignRight, ALIGN_RIGHT);
+ alignRight->setPixmap(icon.loadIcon("format-text-direction-rtl", (TDEIcon::Group) TDEIcon::Small));
+ alignRight->setToggleButton(true);
+ TQToolTip::add(alignRight, i18n("Align text to the right"));
+
+ TQPushButton *alignCenter = new TQPushButton(m_alignButtonGroup);
+ m_alignButtonGroup->insert(alignCenter, ALIGN_CENTER);
+ alignCenter->setPixmap(icon.loadIcon("text_center", (TDEIcon::Group) TDEIcon::Small));
+ alignCenter->setToggleButton(true);
+ TQToolTip::add(alignCenter, i18n("Align text to center"));
+
+ TQPushButton *alignBlock = new TQPushButton(m_alignButtonGroup);
+ m_alignButtonGroup->insert(alignBlock, ALIGN_BLOCK);
+ alignBlock->setPixmap(icon.loadIcon("text_block", (TDEIcon::Group) TDEIcon::Small));
+ alignBlock->setToggleButton(true);
+ TQToolTip::add(alignBlock, i18n("Align text to a block"));
+
+ m_alignButtonGroup->setExclusive(true);
+ m_alignButtonGroup->setFrameShape(TQFrame::NoFrame);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label1 = new TQLabel(i18n("Rotation:"), m_gboxSettings->plainPage());
+ m_textRotation = new TQComboBox(false, m_gboxSettings->plainPage());
+ m_textRotation->insertItem(i18n("None"));
+ m_textRotation->insertItem(i18n("90 Degrees"));
+ m_textRotation->insertItem(i18n("180 Degrees"));
+ m_textRotation->insertItem(i18n("270 Degrees"));
+ TQWhatsThis::add( m_textRotation, i18n("<p>Select the text rotation to use."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Color:"), m_gboxSettings->plainPage());
+ m_fontColorButton = new KColorButton( TQt::black, m_gboxSettings->plainPage() );
+ TQWhatsThis::add( m_fontColorButton, i18n("<p>Select the font color to use."));
+
+ // -------------------------------------------------------------
+
+ m_borderText = new TQCheckBox(i18n("Add border"), m_gboxSettings->plainPage());
+ TQToolTip::add(m_borderText, i18n("Add a solid border around text using current text color"));
+
+ m_transparentText = new TQCheckBox(i18n("Semi-transparent"), m_gboxSettings->plainPage());
+ TQToolTip::add(m_transparentText, i18n("Use semi-transparent text background under image"));
+
+ grid->addMultiCellWidget(m_textEdit, 0, 2, 0, 1);
+ grid->addMultiCellWidget(m_fontChooserWidget, 3, 3, 0, 1);
+ grid->addMultiCellWidget(m_alignButtonGroup, 4, 4, 0, 1);
+ grid->addMultiCellWidget(label1, 5, 5, 0, 0);
+ grid->addMultiCellWidget(m_textRotation, 5, 5, 1, 1);
+ grid->addMultiCellWidget(label2, 6, 6, 0, 0);
+ grid->addMultiCellWidget(m_fontColorButton, 6, 6, 1, 1);
+ grid->addMultiCellWidget(m_borderText, 7, 7, 0, 1);
+ grid->addMultiCellWidget(m_transparentText, 8, 8, 0, 1);
+ grid->setMargin(0);
+ grid->setSpacing(m_gboxSettings->spacingHint());
+ grid->setRowStretch(9, 10);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_fontChooserWidget, TQ_SIGNAL(fontSelected (const TQFont&)),
+ this, TQ_SLOT(slotFontPropertiesChanged(const TQFont&)));
+
+ connect(m_fontColorButton, TQ_SIGNAL(changed(const TQColor&)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_textEdit, TQ_SIGNAL(textChanged()),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_alignButtonGroup, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotAlignModeChanged(int)));
+
+ connect(m_borderText, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_transparentText, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(m_textRotation, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(this, TQ_SIGNAL(signalUpdatePreview()),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ // -------------------------------------------------------------
+
+ slotUpdatePreview();
+}
+
+InsertTextTool::~InsertTextTool()
+{
+}
+
+void InsertTextTool::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("inserttext Tool");
+ TQColor black(0, 0, 0);
+ TQFont defaultFont;
+
+ int orgW = m_previewWidget->imageIface()->originalWidth();
+ int orgH = m_previewWidget->imageIface()->originalHeight();
+
+ if (orgW > orgH) m_defaultSizeFont = (int)(orgH / 8.0);
+ else m_defaultSizeFont = (int)(orgW / 8.0);
+
+ defaultFont.setPointSize(m_defaultSizeFont);
+ m_textRotation->setCurrentItem(config->readNumEntry("Text Rotation", 0));
+ m_fontColorButton->setColor(config->readColorEntry("Font Color", &black));
+ m_textEdit->setText(config->readEntry("Text String", i18n("Enter your text here!")));
+ m_textFont = config->readFontEntry("Font Properties", &defaultFont);
+ m_fontChooserWidget->setFont(m_textFont);
+ m_alignTextMode = config->readNumEntry("Text Alignment", ALIGN_LEFT);
+ m_borderText->setChecked(config->readBoolEntry("Border Text", false));
+ m_transparentText->setChecked(config->readBoolEntry("Transparent Text", false));
+ m_previewWidget->setPositionHint(config->readRectEntry("Position Hint"));
+
+ static_cast<TQPushButton*>(m_alignButtonGroup->find(m_alignTextMode))->setOn(true);
+ slotAlignModeChanged(m_alignTextMode);
+}
+
+void InsertTextTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("inserttext Tool");
+
+ config->writeEntry("Text Rotation", m_textRotation->currentItem());
+ config->writeEntry("Font Color", m_fontColorButton->color());
+ config->writeEntry("Text String", m_textEdit->text());
+ config->writeEntry("Font Properties", m_textFont);
+ config->writeEntry("Text Alignment", m_alignTextMode);
+ config->writeEntry("Border Text", m_borderText->isChecked());
+ config->writeEntry("Transparent Text", m_transparentText->isChecked());
+ config->writeEntry("Position Hint", m_previewWidget->getPositionHint());
+
+ config->sync();
+}
+
+void InsertTextTool::slotResetSettings()
+{
+ m_fontColorButton->blockSignals(true);
+ m_alignButtonGroup->blockSignals(true);
+ m_fontChooserWidget->blockSignals(true);
+
+ m_textRotation->setCurrentItem(0); // No rotation.
+ m_fontColorButton->setColor(TQt::black);
+ TQFont defaultFont;
+ m_textFont = defaultFont; // Reset to default TDE font.
+ m_textFont.setPointSize(m_defaultSizeFont);
+ m_fontChooserWidget->setFont(m_textFont);
+ m_borderText->setChecked(false);
+ m_transparentText->setChecked(false);
+ m_previewWidget->resetEdit();
+ static_cast<TQPushButton*> (m_alignButtonGroup->find(ALIGN_LEFT))->setOn(true);
+
+ m_fontChooserWidget->blockSignals(false);
+ m_fontColorButton->blockSignals(false);
+ m_alignButtonGroup->blockSignals(false);
+ slotAlignModeChanged(ALIGN_LEFT);
+}
+
+void InsertTextTool::slotAlignModeChanged(int mode)
+{
+ m_alignTextMode = mode;
+ m_textEdit->selectAll(true);
+
+ switch (m_alignTextMode)
+ {
+ case ALIGN_LEFT:
+ m_textEdit->setAlignment( TQt::AlignLeft );
+ break;
+
+ case ALIGN_RIGHT:
+ m_textEdit->setAlignment( TQt::AlignRight );
+ break;
+
+ case ALIGN_CENTER:
+ m_textEdit->setAlignment( TQt::AlignHCenter );
+ break;
+
+ case ALIGN_BLOCK:
+ m_textEdit->setAlignment( TQt::AlignJustify );
+ break;
+ }
+
+ m_textEdit->selectAll(false);
+ emit signalUpdatePreview();
+}
+
+void InsertTextTool::slotFontPropertiesChanged(const TQFont& font)
+{
+ m_textFont = font;
+ emit signalUpdatePreview();
+}
+
+void InsertTextTool::slotUpdatePreview()
+{
+ m_previewWidget->setText(m_textEdit->text(), m_textFont, m_fontColorButton->color(), m_alignTextMode,
+ m_borderText->isChecked(), m_transparentText->isChecked(),
+ m_textRotation->currentItem());
+}
+
+void InsertTextTool::finalRendering()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+
+ ImageIface iface(0, 0);
+ DImg dest = m_previewWidget->makeInsertText();
+ iface.putOriginalImage(i18n("Insert Text"), dest.bits(), dest.width(), dest.height());
+
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamInsertTextImagesPlugin
diff --git a/src/imageplugins/inserttext/inserttexttool.h b/src/imageplugins/inserttext/inserttexttool.h
new file mode 100644
index 00000000..c148aa60
--- /dev/null
+++ b/src/imageplugins/inserttext/inserttexttool.h
@@ -0,0 +1,111 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a plugin to insert a text over an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef INSERTEXTTOOL_H
+#define INSERTEXTTOOL_H
+
+// TQt includes.
+
+#include <tqfont.h>
+#include <tqcolor.h>
+#include <tqimage.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQLabel;
+class TQFont;
+class TQHButtonGroup;
+class TQComboBox;
+class TQCheckBox;
+
+class KTextEdit;
+class KColorButton;
+
+namespace Digikam
+{
+class EditorToolSettings;
+}
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+class InsertTextWidget;
+class FontChooserWidget;
+
+class InsertTextTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ InsertTextTool(TQObject *parent);
+ ~InsertTextTool();
+
+signals:
+
+ void signalUpdatePreview();
+
+private slots:
+
+ void slotFontPropertiesChanged(const TQFont& font);
+ void slotUpdatePreview();
+ void slotAlignModeChanged(int mode);
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+
+private:
+
+ int m_alignTextMode;
+ int m_defaultSizeFont;
+
+ TQComboBox *m_textRotation;
+
+ TQCheckBox *m_borderText;
+ TQCheckBox *m_transparentText;
+
+ TQHButtonGroup *m_alignButtonGroup;
+
+ TQFont m_textFont;
+
+ KColorButton *m_fontColorButton;
+
+ KTextEdit *m_textEdit;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+
+ FontChooserWidget *m_fontChooserWidget;
+
+ InsertTextWidget *m_previewWidget;
+};
+
+} // NameSpace DigikamInsertTextImagesPlugin
+
+#endif /* INSERTEXTTOOL_H */
diff --git a/src/imageplugins/inserttext/inserttextwidget.cpp b/src/imageplugins/inserttext/inserttextwidget.cpp
new file mode 100644
index 00000000..dc5b65f4
--- /dev/null
+++ b/src/imageplugins/inserttext/inserttextwidget.cpp
@@ -0,0 +1,622 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a widget to insert a text over an image.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqfont.h>
+#include <tqfontmetrics.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+
+// Local includes.
+
+#include "inserttextwidget.h"
+#include "inserttextwidget.moc"
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+InsertTextWidget::InsertTextWidget(int w, int h, TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ m_currentMoving = false;
+
+ m_iface = new Digikam::ImageIface(w, h);
+ m_data = m_iface->getPreviewImage();
+ m_w = m_iface->previewWidth();
+ m_h = m_iface->previewHeight();
+ m_pixmap = new TQPixmap(w, h);
+ m_pixmap->fill(colorGroup().background());
+
+ setBackgroundMode(TQt::NoBackground);
+ setMinimumSize(w, h);
+ setMouseTracking(true);
+
+ m_rect = TQRect(width()/2-m_w/2, height()/2-m_h/2, m_w, m_h);
+ m_textRect = TQRect();
+
+ m_backgroundColor = TQColor(0xCC, 0xCC, 0xCC);
+ m_transparency = 210;
+}
+
+InsertTextWidget::~InsertTextWidget()
+{
+ delete [] m_data;
+ delete m_iface;
+ delete m_pixmap;
+}
+
+Digikam::ImageIface* InsertTextWidget::imageIface()
+{
+ return m_iface;
+}
+
+void InsertTextWidget::resetEdit()
+{
+ // signal this needs to be filled by makePixmap
+ m_textRect = TQRect();
+ makePixmap();
+ repaint(false);
+}
+
+void InsertTextWidget::setText(TQString text, TQFont font, TQColor color, int alignMode,
+ bool border, bool transparent, int rotation)
+{
+ m_textString = text;
+ m_textColor = color;
+ m_textBorder = border;
+ m_textTransparent = transparent;
+ m_textRotation = rotation;
+
+ switch (alignMode)
+ {
+ case ALIGN_LEFT:
+ m_alignMode = TQt::AlignLeft;
+ break;
+
+ case ALIGN_RIGHT:
+ m_alignMode = TQt::AlignRight;
+ break;
+
+ case ALIGN_CENTER:
+ m_alignMode = TQt::AlignHCenter;
+ break;
+
+ case ALIGN_BLOCK:
+ m_alignMode = TQt::AlignJustify;
+ break;
+ }
+
+ // Center text if top left corner text area is not visible.
+
+ /*
+ if ( m_textFont.pointSize() != font.pointSize() &&
+ !rect().contains( m_textRect.x(), m_textRect.y() ) )
+ {
+ m_textFont = font;
+ resetEdit();
+ return;
+ }
+ */
+
+ m_textFont = font;
+
+ makePixmap();
+ repaint(false);
+}
+
+void InsertTextWidget::setPositionHint(TQRect hint)
+{
+ // interpreted by composeImage
+ m_positionHint = hint;
+ if (m_textRect.isValid())
+ {
+ // invalidate current position so that hint is certainly interpreted
+ m_textRect = TQRect();
+ makePixmap();
+ repaint();
+ }
+}
+
+TQRect InsertTextWidget::getPositionHint()
+{
+ TQRect hint;
+ if (m_textRect.isValid())
+ {
+ // We normalize on the size of the image, but we store as int. Precision loss is no problem.
+ hint.setX( (int) ((float)(m_textRect.x() - m_rect.x()) / (float)m_rect.width() * 10000.0) );
+ hint.setY( (int) ((float)(m_textRect.y() - m_rect.y()) / (float)m_rect.height() * 10000.0) );
+ hint.setWidth( (int) ((float)m_textRect.width() / (float)m_rect.width() * 10000.0) );
+ hint.setHeight( (int) ((float)m_textRect.height() / (float)m_rect.height() * 10000.0) );
+ }
+ return hint;
+}
+
+Digikam::DImg InsertTextWidget::makeInsertText(void)
+{
+ int orgW = m_iface->originalWidth();
+ int orgH = m_iface->originalHeight();
+ float ratioW = (float)orgW/(float)m_w;
+ float ratioH = (float)orgH/(float)m_h;
+
+ int x, y;
+ if (m_textRect.isValid())
+ {
+ // convert from widget to image coordinates, then to original size
+ x = lroundf( (m_textRect.x() - m_rect.x()) * ratioW);
+ y = lroundf( (m_textRect.y() - m_rect.y()) * ratioH);
+ }
+ else
+ {
+ x = -1;
+ y = -1;
+ }
+
+ // Get original image
+ Digikam::DImg image = m_iface->getOriginalImg()->copy();
+
+ int borderWidth = TQMAX(1, lroundf(ratioW));
+ // compose and draw result on image
+ composeImage(&image, 0, x, y,
+ m_textFont, m_textFont.pointSizeFloat(),
+ m_textRotation, m_textColor, m_alignMode, m_textString,
+ m_textTransparent, m_backgroundColor,
+ m_textBorder ? BORDER_NORMAL : BORDER_NONE, borderWidth, borderWidth);
+
+ return image;
+}
+
+void InsertTextWidget::makePixmap(void)
+{
+ int orgW = m_iface->originalWidth();
+ int orgH = m_iface->originalHeight();
+ float ratioW = (float)m_w / (float)orgW;
+ float ratioH = (float)m_h / (float)orgH;
+
+ int x, y;
+ if (m_textRect.isValid())
+ {
+ // convert from widget to image coordinates
+ x = m_textRect.x() - m_rect.x();
+ y = m_textRect.y() - m_rect.y();
+ }
+ else
+ {
+ x = -1;
+ y = -1;
+ }
+
+ // get preview image data
+ uchar *data = m_iface->getPreviewImage();
+ Digikam::DImg image(m_iface->previewWidth(), m_iface->previewHeight(), m_iface->previewSixteenBit(),
+ m_iface->previewHasAlpha(), data);
+ delete [] data;
+
+ // paint pixmap for drawing this widget
+ // First, fill with background color
+ m_pixmap->fill(colorGroup().background());
+ TQPainter p(m_pixmap);
+ // Convert image to pixmap and draw it
+ TQPixmap imagePixmap = image.convertToPixmap();
+ p.drawPixmap(m_rect.x(), m_rect.y(),
+ imagePixmap, 0, 0, imagePixmap.width(), imagePixmap.height());
+
+ // prepare painter for use by compose image
+ p.setClipRect(m_rect);
+ p.translate(m_rect.x(), m_rect.y());
+
+ // compose image and draw result directly on pixmap, with correct offset
+ TQRect textRect = composeImage(&image, &p, x, y,
+ m_textFont, m_textFont.pointSizeFloat() * ((ratioW > ratioH) ? ratioW : ratioH),
+ m_textRotation, m_textColor, m_alignMode, m_textString,
+ m_textTransparent, m_backgroundColor,
+ m_textBorder ? BORDER_NORMAL : BORDER_SUPPORT, 1, 1);
+
+ p.end();
+
+ // store new text rectangle
+ // convert from image to widget coordinates
+ m_textRect.setX(textRect.x() + m_rect.x());
+ m_textRect.setY(textRect.y() + m_rect.y());
+ m_textRect.setSize(textRect.size());
+}
+
+/*
+ Take data from image, draw text at x|y with specified parameters.
+ If destPainter is null, draw to image,
+ if destPainter is not null, draw directly using the painter.
+ Returns modified area of image.
+*/
+TQRect InsertTextWidget::composeImage(Digikam::DImg *image, TQPainter *destPainter,
+ int x, int y,
+ TQFont font, float pointSize, int textRotation, TQColor textColor,
+ int alignMode, const TQString &textString,
+ bool transparentBackground, TQColor backgroundColor,
+ BorderMode borderMode, int borderWidth, int spacing)
+{
+ /*
+ The problem we have to solve is that we have no pixel access to font rendering,
+ we have to let TQt do the drawing. On the other hand we need to support 16 bit, which
+ cannot be done with TQPixmap.
+ The current solution cuts out the text area, lets TQt do its drawing, converts back and blits to original.
+ */
+ Digikam::DColorComposer *composer = Digikam::DColorComposer::getComposer(Digikam::DColorComposer::PorterDuffNone);
+
+ int maxWidth, maxHeight;
+ if (x == -1 && y == -1)
+ {
+ maxWidth = image->width();
+ maxHeight = image->height();
+ }
+ else
+ {
+ maxWidth = image->width() - x;
+ maxHeight = image->height() - y;
+ }
+
+ // find out size of the area that we are drawing to
+ font.setPointSizeFloat(pointSize);
+ TQFontMetrics fontMt( font );
+ TQRect fontRect = fontMt.boundingRect(0, 0, maxWidth, maxHeight, 0, textString);
+
+ int fontWidth, fontHeight;
+
+ switch(textRotation)
+ {
+ case ROTATION_NONE:
+ case ROTATION_180:
+ default:
+ fontWidth = fontRect.width();
+ fontHeight = fontRect.height();
+ break;
+
+ case ROTATION_90:
+ case ROTATION_270:
+ fontWidth = fontRect.height();
+ fontHeight = fontRect.width();
+ break;
+ }
+
+ // x, y == -1 means that we have to find a good initial position for the text here
+ if (x == -1 && y == -1)
+ {
+ // was a valid position hint stored from last use?
+ if (m_positionHint.isValid())
+ {
+ // We assume that people tend to orient text along the edges,
+ // so we do some guessing so that positions such as "in the lower right corner"
+ // will be remembered across different image sizes.
+
+ // get relative positions
+ float fromTop = (float)m_positionHint.top() / 10000.0;
+ float fromBottom = 1.0 - (float)m_positionHint.bottom() / 10000.0;
+ float fromLeft = (float)m_positionHint.left() / 10000.0;
+ float fromRight = 1.0 - (float)m_positionHint.right() / 10000.0;
+
+ // calculate horizontal position
+ if (fromLeft < fromRight)
+ {
+ x = (int)(fromLeft * maxWidth);
+
+ // we are placing from the smaller distance,
+ // so if now the larger distance is actually too small,
+ // fall back to standard placement, nothing to lose.
+ if (x + fontWidth > maxWidth)
+ x = TQMAX( (maxWidth - fontWidth) / 2, 0);
+ }
+ else
+ {
+ x = maxWidth - (int)(fromRight * maxWidth) - fontWidth;
+ if ( x < 0 )
+ x = TQMAX( (maxWidth - fontWidth) / 2, 0);
+ }
+
+ // calculate vertical position
+ if (fromTop < fromBottom)
+ {
+ y = (int)(fromTop * maxHeight);
+ if (y + fontHeight > maxHeight)
+ y = TQMAX( (maxHeight - fontHeight) / 2, 0);
+ }
+ else
+ {
+ y = maxHeight - (int)(fromBottom * maxHeight) - fontHeight;
+ if ( y < 0 )
+ y = TQMAX( (maxHeight - fontHeight) / 2, 0);
+ }
+
+ if (! TQRect(x, y, fontWidth, fontHeight).
+ intersects(TQRect(0, 0, maxWidth, maxHeight)) )
+ {
+ // emergency fallback - nothing is visible
+ x = TQMAX( (maxWidth - fontWidth) / 2, 0);
+ y = TQMAX( (maxHeight - fontHeight) / 2, 0);
+ }
+
+ // invalidate position hint, use only once
+ m_positionHint = TQRect();
+ }
+ else
+ {
+ // use standard position
+ x = TQMAX( (maxWidth - fontWidth) / 2, 0);
+ y = TQMAX( (maxHeight - fontHeight) / 2, 0);
+ }
+ }
+
+ // create a rectangle relative to image
+ TQRect drawRect( x, y, fontWidth + 2 * borderWidth + 2 * spacing, fontHeight + 2 * borderWidth + 2 * spacing);
+
+ // create a rectangle relative to textArea, excluding the border
+ TQRect textAreaBackgroundRect( borderWidth, borderWidth, fontWidth + 2 * spacing, fontHeight + 2 * spacing);
+
+ // create a rectangle relative to textArea, excluding the border and spacing
+ TQRect textAreaTextRect( borderWidth + spacing, borderWidth + spacing, fontWidth, fontHeight );
+
+ // create a rectangle relative to textArea, including the border,
+ // for drawing the rectangle, taking into account that the width of the TQPen goes in and out in equal parts
+ TQRect textAreaDrawRect( borderWidth / 2, borderWidth / 2, fontWidth + borderWidth + 2 * spacing,
+ fontHeight + borderWidth + 2 * spacing );
+
+ // cut out the text area
+ Digikam::DImg textArea = image->copy(drawRect);
+
+ if (textArea.isNull())
+ return TQRect();
+
+ // compose semi-transparent background over textArea
+ if (transparentBackground)
+ {
+ Digikam::DImg transparentLayer(textAreaBackgroundRect.width(), textAreaBackgroundRect.height(), textArea.sixteenBit(), true);
+ Digikam::DColor transparent(backgroundColor);
+ transparent.setAlpha(m_transparency);
+ if (image->sixteenBit())
+ transparent.convertToSixteenBit();
+ transparentLayer.fill(transparent);
+ textArea.bitBlendImage(composer, &transparentLayer, 0, 0, transparentLayer.width(), transparentLayer.height(),
+ textAreaBackgroundRect.x(), textAreaBackgroundRect.y());
+ }
+
+ Digikam::DImg textNotDrawn;
+ if (textArea.sixteenBit())
+ {
+ textNotDrawn = textArea.copy();
+ textNotDrawn.convertToEightBit();
+ }
+ else
+ textNotDrawn = textArea;
+
+ // We have no direct pixel access to font rendering, so now we need to use TQt/X11 for the drawing
+
+ // convert text area to pixmap
+ TQPixmap pixmap = textNotDrawn.convertToPixmap();
+ // paint on pixmap
+ TQPainter p(&pixmap);
+ p.setPen( TQPen(textColor, 1) ) ;
+ p.setFont( font );
+ p.save();
+
+ // translate to origin of text, leaving space for the border
+ p.translate(textAreaTextRect.x(), textAreaTextRect.y());
+
+ switch(textRotation)
+ {
+ case ROTATION_NONE:
+ p.drawText( 0, 0, textAreaTextRect.width(),
+ textAreaTextRect.height(), alignMode, textString );
+ break;
+ case ROTATION_90:
+ p.translate(textAreaTextRect.width(), 0);
+ p.rotate(90.0);
+ p.drawText( 0, 0, textAreaTextRect.height(), textAreaTextRect.width(),
+ alignMode, textString );
+ break;
+ case ROTATION_180:
+ p.translate(textAreaTextRect.width(), textAreaTextRect.height());
+ p.rotate(180.0);
+ p.drawText( 0, 0, textAreaTextRect.width(), textAreaTextRect.height(),
+ alignMode, textString );
+ break;
+ case ROTATION_270:
+ p.translate(0, textAreaTextRect.height());
+ p.rotate(270.0);
+ p.drawText( 0, 0, textAreaTextRect.height(), textAreaTextRect.width(),
+ alignMode, textString );
+ break;
+ }
+
+ p.restore();
+
+ // Drawing rectangle around text.
+
+ if (borderMode == BORDER_NORMAL) // Decorative border using text color.
+ {
+ p.setPen( TQPen(textColor, borderWidth, TQt::SolidLine,
+ TQt::SquareCap, TQt::RoundJoin) ) ;
+ p.drawRect(textAreaDrawRect);
+ }
+ else if (borderMode == BORDER_SUPPORT) // Make simple dot line border to help user.
+ {
+ p.setPen(TQPen(TQt::white, 1, TQt::SolidLine));
+ p.drawRect(textAreaDrawRect);
+ p.setPen(TQPen(TQt::red, 1, TQt::DotLine));
+ p.drawRect(textAreaDrawRect);
+ }
+ p.end();
+
+ if (!destPainter)
+ {
+ // convert to TQImage, then to DImg
+ TQImage pixmapImage = pixmap.convertToImage();
+ Digikam::DImg textDrawn(pixmapImage.width(), pixmapImage.height(), false, true, pixmapImage.bits());
+
+ // This does not work: during the conversion, colors are altered significantly (diffs of 1 to 10 in each component),
+ // so we cannot find out which pixels have actually been touched.
+ /*
+ // Compare the result of drawing with the previous version.
+ // Set all unchanged pixels to transparent
+ Digikam::DColor color, ncolor;
+ uchar *ptr, *nptr;
+ ptr = textDrawn.bits();
+ nptr = textNotDrawn.bits();
+ int bytesDepth = textDrawn.bytesDepth();
+ int numPixels = textDrawn.width() * textDrawn.height();
+ for (int i = 0; i < numPixels; i++, ptr+= bytesDepth, nptr += bytesDepth)
+ {
+ color.setColor(ptr, false);
+ ncolor.setColor(nptr, false);
+ if ( color.red() == ncolor.red() &&
+ color.green() == ncolor.green() &&
+ color.blue() == ncolor.blue())
+ {
+ color.setAlpha(0);
+ color.setPixel(ptr);
+ }
+ }
+ // convert to 16 bit if needed
+ */
+ textDrawn.convertToDepthOfImage(&textArea);
+
+ // now compose to original: only pixels affected by drawing text and border are changed, not whole area
+ textArea.bitBlendImage(composer, &textDrawn, 0, 0, textDrawn.width(), textDrawn.height(), 0, 0);
+
+ // copy result to original image
+ image->bitBltImage(&textArea, drawRect.x(), drawRect.y());
+ }
+ else
+ {
+ destPainter->drawPixmap(drawRect.x(), drawRect.y(), pixmap, 0, 0, pixmap.width(), pixmap.height());
+ }
+
+ delete composer;
+
+ return drawRect;
+}
+
+void InsertTextWidget::paintEvent( TQPaintEvent * )
+{
+ bitBlt(this, 0, 0, m_pixmap);
+}
+
+void InsertTextWidget::resizeEvent(TQResizeEvent * e)
+{
+ blockSignals(true);
+ delete m_pixmap;
+
+ int w = e->size().width();
+ int h = e->size().height();
+
+ int textX = m_textRect.x() - m_rect.x();
+ int textY = m_textRect.y() - m_rect.y();
+ int old_w = m_w;
+ int old_h = m_h;
+ m_data = m_iface->setPreviewImageSize(w, h);
+ m_w = m_iface->previewWidth();
+ m_h = m_iface->previewHeight();
+
+ m_pixmap = new TQPixmap(w, h);
+ m_rect = TQRect(w/2-m_w/2, h/2-m_h/2, m_w, m_h);
+
+ if (m_textRect.isValid())
+ {
+ int textWidth = m_textRect.width();
+ int textHeight = m_textRect.height();
+
+ textX = lroundf( textX * (float)m_w / (float)old_w );
+ textY = lroundf( textY * (float)m_h / (float)old_h );
+ textWidth = lroundf(textWidth * (float)m_w / (float)old_w );
+ textHeight = lroundf(textHeight * (float)m_h / (float)old_h );
+
+ m_textRect.setX(textX + m_rect.x());
+ m_textRect.setY(textY + m_rect.y());
+ m_textRect.setWidth(textWidth);
+ m_textRect.setHeight(textHeight);
+ makePixmap();
+ }
+
+ blockSignals(false);
+}
+
+void InsertTextWidget::mousePressEvent ( TQMouseEvent * e )
+{
+ if ( e->button() == TQt::LeftButton &&
+ m_textRect.contains( e->x(), e->y() ) )
+ {
+ m_xpos = e->x();
+ m_ypos = e->y();
+ setCursor ( KCursor::sizeAllCursor() );
+ m_currentMoving = true;
+ }
+}
+
+void InsertTextWidget::mouseReleaseEvent ( TQMouseEvent * )
+{
+ setCursor ( KCursor::arrowCursor() );
+ m_currentMoving = false;
+}
+
+void InsertTextWidget::mouseMoveEvent ( TQMouseEvent * e )
+{
+ if ( rect().contains( e->x(), e->y() ) )
+ {
+ if ( e->state() == TQt::LeftButton && m_currentMoving )
+ {
+ uint newxpos = e->x();
+ uint newypos = e->y();
+
+ m_textRect.moveBy(newxpos - m_xpos, newypos - m_ypos);
+
+ makePixmap();
+ repaint(false);
+
+ m_xpos = newxpos;
+ m_ypos = newypos;
+ setCursor( KCursor::handCursor() );
+ }
+ else if ( m_textRect.contains( e->x(), e->y() ) )
+ {
+ setCursor ( KCursor::sizeAllCursor() );
+ }
+ else
+ {
+ setCursor ( KCursor::arrowCursor() );
+ }
+ }
+}
+
+} // NameSpace DigikamInsertTextImagesPlugin
diff --git a/src/imageplugins/inserttext/inserttextwidget.h b/src/imageplugins/inserttext/inserttextwidget.h
new file mode 100644
index 00000000..bbce9189
--- /dev/null
+++ b/src/imageplugins/inserttext/inserttextwidget.h
@@ -0,0 +1,156 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-14
+ * Description : a widget to insert a text over an image.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef INSERTTEXTWIDGET_H
+#define INSERTTEXTWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqimage.h>
+#include <tqrect.h>
+#include <tqsize.h>
+#include <tqpixmap.h>
+#include <tqstring.h>
+#include <tqfont.h>
+#include <tqcolor.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+class ImageIface;
+}
+
+namespace DigikamInsertTextImagesPlugin
+{
+
+enum Action
+{
+ ALIGN_LEFT=0,
+ ALIGN_RIGHT,
+ ALIGN_CENTER,
+ ALIGN_BLOCK,
+ BORDER_TEXT,
+ TRANSPARENT_TEXT
+};
+
+enum TextRotation
+{
+ ROTATION_NONE=0,
+ ROTATION_90,
+ ROTATION_180,
+ ROTATION_270
+};
+
+enum BorderMode
+{
+ BORDER_NONE,
+ BORDER_SUPPORT,
+ BORDER_NORMAL
+};
+
+class InsertTextWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ InsertTextWidget(int w, int h, TQWidget *parent=0);
+ ~InsertTextWidget();
+
+ Digikam::ImageIface* imageIface();
+ Digikam::DImg makeInsertText(void);
+
+ void setText(TQString text, TQFont font, TQColor color, int alignMode,
+ bool border, bool transparent, int rotation);
+ void resetEdit(void);
+
+ void setPositionHint(TQRect hint);
+ TQRect getPositionHint();
+
+protected:
+
+ void paintEvent(TQPaintEvent *e);
+ void resizeEvent(TQResizeEvent * e);
+ void mousePressEvent(TQMouseEvent * e);
+ void mouseReleaseEvent(TQMouseEvent * e);
+ void mouseMoveEvent(TQMouseEvent * e);
+
+ void makePixmap(void);
+ TQRect composeImage(Digikam::DImg *image, TQPainter *destPainter,
+ int x, int y,
+ TQFont font, float pointSize, int textRotation, TQColor textColor,
+ int alignMode, const TQString &textString,
+ bool transparentBackground, TQColor backgroundColor,
+ BorderMode borderMode, int borderWidth, int spacing);
+
+private:
+
+ bool m_currentMoving;
+ bool m_textBorder;
+ bool m_textTransparent;
+
+ int m_alignMode;
+ int m_textRotation;
+
+ uchar *m_data;
+ int m_w;
+ int m_h;
+
+ int m_xpos;
+ int m_ypos;
+
+ int m_transparency;
+
+ TQPixmap *m_pixmap;
+
+ TQRect m_rect;
+ TQRect m_textRect;
+
+ TQString m_textString;
+
+ TQFont m_textFont;
+
+ TQColor m_textColor;
+
+ TQColor m_backgroundColor;
+
+ TQRect m_positionHint;
+
+ Digikam::ImageIface *m_iface;
+};
+
+} // NameSpace DigikamInsertTextImagesPlugin
+
+#endif /* INSERTTEXTWIDGET_H */
diff --git a/src/imageplugins/lensdistortion/Makefile.am b/src/imageplugins/lensdistortion/Makefile.am
new file mode 100644
index 00000000..cd157aa1
--- /dev/null
+++ b/src/imageplugins/lensdistortion/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_lensdistortion_la_SOURCES = imageplugin_lensdistortion.cpp \
+ lensdistortiontool.cpp \
+ lensdistortion.cpp pixelaccess.cpp
+
+digikamimageplugin_lensdistortion_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_lensdistortion_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_lensdistortion.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_lensdistortion.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_lensdistortion_ui.rc
diff --git a/src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion.desktop b/src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion.desktop
new file mode 100644
index 00000000..da529559
--- /dev/null
+++ b/src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=ImagePlugin_LensDistortion
+Name[bg]=Приставка за снимки - Аберации от обективи
+Name[da]=Billedplugin_Linseforvrængning
+Name[el]=ΠρόσθετοΕικόνας_ΠαραμόρφωσηΦακών
+Name[fi]=Linssivääristymä
+Name[hr]=Izobličena leća
+Name[it]=PluginImmagini_DistorsioneLenticolare
+Name[nl]=Afbeeldingsplugin_Lensafwijking
+Name[sr]=Изобличење сочива
+Name[sr@Latn]=Izobličenje sočiva
+Name[sv]=Insticksprogram för linsförvrängning
+Name[tr]=ResimEklentisi_MercekÇarpıtması
+Name[xx]=xxImagePlugin_LensDistortionxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Spherical aberration image correction plugin for digiKam
+Comment[bg]=Приставка на digiKam за корекция на аберации от обективи в снимките
+Comment[ca]=Connector pel digiKam per corregir l'aberració esfèrica d'una imatge
+Comment[da]=Plugin til korrigering af sfærisk afvigelse for Digikam
+Comment[de]=digiKam-Modul zur Reduzierung kugelförmiger Verzerrung durch Linsen
+Comment[el]=Πρόσθετο διόρθωσης εικόνας σφαιρικής παρέκκλισης για το digiKam
+Comment[es]=Plugin para digiKam para corrección de aberraciones de imagen esféricas
+Comment[et]=DigiKami sfäärilise aberratsiooni korrigeerimise plugin
+Comment[fa]=وصلۀ اصلاح تصویر انحراف کروی برای digiKam
+Comment[fi]=Palloaberraatio (kalansilmävääristymä)
+Comment[gl]=Un plugin de digiKam para corrixir a aberrazón esférica da imaxe
+Comment[hr]=digiKam dodatak za ispravljanje kružne aberacije
+Comment[is]=Íforrit fyrir digiKam sem minnkar hringskekkingu linsu
+Comment[it]=Plugin di correzione dell'aberrazione sferica delle immagini per digiKam
+Comment[ja]=digiKam 球面収差補正プラグイン
+Comment[nds]=digiKam-Moduul för't Richten vun Kugel-Vertarren
+Comment[nl]=Digikam-plugin voor het corrigeren van bolvormige afwijkingen
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਗੋਲਾ ਐਬੱਰੇਸ਼ਨ ਚਿੱਤਰ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam korygująca aberrację sferyczną
+Comment[pt]=Um 'plugin' do digiKam para corrigir a aberração esférica da imagem
+Comment[pt_BR]=Um 'plugin' do digiKam para corrigir a aberração esférica da imagem
+Comment[ru]=Модуль digiKam коррекции сферических искажений
+Comment[sk]=digiKam plugin na korekciu sférickej aberácie šošovky
+Comment[sr]=digiKam-ов прикључак за исправљање сферног искривљења слике
+Comment[sr@Latn]=digiKam-ov priključak za ispravljanje sfernog iskrivljenja slike
+Comment[sv]=Digikam insticksprogram för korrigering av sfärisk avvikelse i bild
+Comment[tr]=digiKam için resim küresel sapma düzeltme eklentisi
+Comment[uk]=Втулок коректування сферичних спотворень для digiKam
+Comment[vi]=Phần bổ sung sửa quang sai hình cầu ảnh cho digiKam
+Comment[xx]=xxSpherical aberration image correction plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_lensdistortion
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion_ui.rc b/src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion_ui.rc
new file mode 100644
index 00000000..4705467e
--- /dev/null
+++ b/src/imageplugins/lensdistortion/digikamimageplugin_lensdistortion_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_lensdistortion" >
+
+ <MenuBar>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ <Action name="imageplugin_lensdistortion" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_lensdistortion" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/lensdistortion/imageeffect_lensdistortion.cpp b/src/imageplugins/lensdistortion/imageeffect_lensdistortion.cpp
new file mode 100644
index 00000000..89420a9c
--- /dev/null
+++ b/src/imageplugins/lensdistortion/imageeffect_lensdistortion.cpp
@@ -0,0 +1,317 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : a plugin to reduce lens distorsions to an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ include.
+
+#include <cstring>
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqbrush.h>
+#include <tqpen.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "lensdistortion.h"
+#include "imageeffect_lensdistortion.h"
+#include "imageeffect_lensdistortion.moc"
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+ImageEffect_LensDistortion::ImageEffect_LensDistortion(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Lens Distortion Correction"),
+ "lensdistortion", false, true, true,
+ Digikam::ImageGuideWidget::HVGuideMode)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Lens Distortion Correction"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to reduce spherical aberration caused "
+ "by a lens to an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2006, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ about->addAuthor("David Hodson", I18N_NOOP("Lens distortion correction algorithm."),
+ "hodsond at acm dot org");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 8, 1, spacingHint());
+
+ m_maskPreviewLabel = new TQLabel( gboxSettings );
+ m_maskPreviewLabel->setAlignment ( TQt::AlignHCenter | TQt::AlignVCenter );
+ TQWhatsThis::add( m_maskPreviewLabel, i18n("<p>You can see here a thumbnail preview of the distortion correction "
+ "applied to a cross pattern.") );
+ gridSettings->addMultiCellWidget(m_maskPreviewLabel, 0, 0, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label1 = new TQLabel(i18n("Main:"), gboxSettings);
+
+ m_mainInput = new KDoubleNumInput(gboxSettings);
+ m_mainInput->setPrecision(1);
+ m_mainInput->setRange(-100.0, 100.0, 0.1, true);
+ TQWhatsThis::add( m_mainInput, i18n("<p>This value controls the amount of distortion. Negative values correct lens barrel "
+ "distortion, while positive values correct lens pincushion distortion."));
+
+ gridSettings->addMultiCellWidget(label1, 1, 1, 0, 1);
+ gridSettings->addMultiCellWidget(m_mainInput, 2, 2, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Edge:"), gboxSettings);
+
+ m_edgeInput = new KDoubleNumInput(gboxSettings);
+ m_edgeInput->setPrecision(1);
+ m_edgeInput->setRange(-100.0, 100.0, 0.1, true);
+ TQWhatsThis::add( m_edgeInput, i18n("<p>This value controls in the same manner as the Main control, but has more effect "
+ "at the edges of the image than at the center."));
+
+ gridSettings->addMultiCellWidget(label2, 3, 3, 0, 1);
+ gridSettings->addMultiCellWidget(m_edgeInput, 4, 4, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Zoom:"), gboxSettings);
+
+ m_rescaleInput = new KDoubleNumInput(gboxSettings);
+ m_rescaleInput->setPrecision(1);
+ m_rescaleInput->setRange(-100.0, 100.0, 0.1, true);
+ TQWhatsThis::add( m_rescaleInput, i18n("<p>This value rescales the overall image size."));
+
+ gridSettings->addMultiCellWidget(label3, 5, 5, 0, 1);
+ gridSettings->addMultiCellWidget(m_rescaleInput, 6, 6, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label4 = new TQLabel(i18n("Brighten:"), gboxSettings);
+
+ m_brightenInput = new KDoubleNumInput(gboxSettings);
+ m_brightenInput->setPrecision(1);
+ m_brightenInput->setRange(-100.0, 100.0, 0.1, true);
+ TQWhatsThis::add( m_brightenInput, i18n("<p>This value adjusts the brightness in image corners."));
+
+ gridSettings->addMultiCellWidget(label4, 7, 7, 0, 1);
+ gridSettings->addMultiCellWidget(m_brightenInput, 8, 8, 0, 1);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_mainInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_edgeInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_rescaleInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_brightenInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ // -------------------------------------------------------------
+
+ /* Calc transform preview.
+ We would like a checkered area to demonstrate the effect.
+ We do not have any drawing support in DImg, so we let TQt draw.
+ First we create a white TQImage. We convert this to a TQPixmap,
+ on which we can draw. Then we convert back to TQImage,
+ convert the TQImage to a DImg which we only need to create once, here.
+ Later, we apply the effect on a copy and convert the DImg to TQPixmap.
+ Longing for TQt4 where we can paint directly on the TQImage...
+ */
+
+ TQImage preview(120, 120, 32);
+ memset(preview.bits(), 255, preview.numBytes());
+ TQPixmap pix (preview);
+ TQPainter pt(&pix);
+ pt.setPen( TQPen(TQt::black, 1) );
+ pt.fillRect( 0, 0, pix.width(), pix.height(), TQBrush(TQt::black, TQt::CrossPattern) );
+ pt.drawRect( 0, 0, pix.width(), pix.height() );
+ pt.end();
+ TQImage preview2(pix.convertToImage());
+ m_previewRasterImage = Digikam::DImg(preview2.width(), preview2.height(), false, false, preview2.bits());
+}
+
+ImageEffect_LensDistortion::~ImageEffect_LensDistortion()
+{
+}
+
+void ImageEffect_LensDistortion::readUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("lensdistortion Tool Dialog");
+
+ m_mainInput->blockSignals(true);
+ m_edgeInput->blockSignals(true);
+ m_rescaleInput->blockSignals(true);
+ m_brightenInput->blockSignals(true);
+
+ m_mainInput->setValue(config->readDoubleNumEntry("2nd Order Distortion", 0.0));
+ m_edgeInput->setValue(config->readDoubleNumEntry("4th Order Distortion",0.0));
+ m_rescaleInput->setValue(config->readDoubleNumEntry("Zoom Factor", 0.0));
+ m_brightenInput->setValue(config->readDoubleNumEntry("Brighten", 0.0));
+
+ m_mainInput->blockSignals(false);
+ m_edgeInput->blockSignals(false);
+ m_rescaleInput->blockSignals(false);
+ m_brightenInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_LensDistortion::writeUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("lensdistortion Tool Dialog");
+ config->writeEntry("2nd Order Distortion", m_mainInput->value());
+ config->writeEntry("4th Order Distortion", m_edgeInput->value());
+ config->writeEntry("Zoom Factor", m_rescaleInput->value());
+ config->writeEntry("Brighten", m_brightenInput->value());
+ config->sync();
+}
+
+void ImageEffect_LensDistortion::resetValues()
+{
+ m_mainInput->blockSignals(true);
+ m_edgeInput->blockSignals(true);
+ m_rescaleInput->blockSignals(true);
+ m_brightenInput->blockSignals(true);
+
+ m_mainInput->setValue(0.0);
+ m_edgeInput->setValue(0.0);
+ m_rescaleInput->setValue(0.0);
+ m_brightenInput->setValue(0.0);
+
+ m_mainInput->blockSignals(false);
+ m_edgeInput->blockSignals(false);
+ m_rescaleInput->blockSignals(false);
+ m_brightenInput->blockSignals(false);
+}
+
+void ImageEffect_LensDistortion::prepareEffect()
+{
+ m_mainInput->setEnabled(false);
+ m_edgeInput->setEnabled(false);
+ m_rescaleInput->setEnabled(false);
+ m_brightenInput->setEnabled(false);
+
+ double m = m_mainInput->value();
+ double e = m_edgeInput->value();
+ double r = m_rescaleInput->value();
+ double b = m_brightenInput->value();
+
+ LensDistortion transformPreview(&m_previewRasterImage, 0L, m, e, r, b, 0, 0);
+ m_maskPreviewLabel->setPixmap(TQPixmap::TQPixmap(transformPreview.getTargetImage().convertToPixmap()));
+
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new LensDistortion(iface->getOriginalImg(), this, m, e, r, b, 0, 0));
+}
+
+void ImageEffect_LensDistortion::prepareFinal()
+{
+ m_mainInput->setEnabled(false);
+ m_edgeInput->setEnabled(false);
+ m_rescaleInput->setEnabled(false);
+ m_brightenInput->setEnabled(false);
+
+ double m = m_mainInput->value();
+ double e = m_edgeInput->value();
+ double r = m_rescaleInput->value();
+ double b = m_brightenInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new LensDistortion(iface.getOriginalImg(), this, m, e, r, b, 0, 0));
+}
+
+void ImageEffect_LensDistortion::putPreviewData(void)
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage()
+ .smoothScale(iface->previewWidth(), iface->previewHeight());
+ iface->putPreviewImage(imDest.bits());
+
+ m_imagePreviewWidget->updatePreview();
+}
+
+void ImageEffect_LensDistortion::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Lens Distortion"),
+ m_threadedFilter->getTargetImage().bits());
+}
+
+void ImageEffect_LensDistortion::renderingFinished()
+{
+ m_mainInput->setEnabled(true);
+ m_edgeInput->setEnabled(true);
+ m_rescaleInput->setEnabled(true);
+ m_brightenInput->setEnabled(true);
+}
+
+} // NameSpace DigikamLensDistortionImagesPlugin
+
diff --git a/src/imageplugins/lensdistortion/imageeffect_lensdistortion.h b/src/imageplugins/lensdistortion/imageeffect_lensdistortion.h
new file mode 100644
index 00000000..2688a5df
--- /dev/null
+++ b/src/imageplugins/lensdistortion/imageeffect_lensdistortion.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : a plugin to reduce lens distorsions to an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_LENSDISTORTION_H
+#define IMAGEEFFECT_LENSDISTORTION_H
+
+// TQt includes.
+
+#include <tqimage.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "imageguidedlg.h"
+
+class TQLabel;
+
+class KDoubleNumInput;
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+class ImageEffect_LensDistortion : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_LensDistortion(TQWidget *parent);
+ ~ImageEffect_LensDistortion();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_maskPreviewLabel;
+
+ KDoubleNumInput *m_mainInput;
+ KDoubleNumInput *m_edgeInput;
+ KDoubleNumInput *m_rescaleInput;
+ KDoubleNumInput *m_brightenInput;
+
+ Digikam::DImg m_previewRasterImage;
+};
+
+} // NameSpace DigikamLensDistortionImagesPlugin
+
+#endif /* IMAGEEFFECT_LENSDISTORTION_H */
diff --git a/src/imageplugins/lensdistortion/imageplugin_lensdistortion.cpp b/src/imageplugins/lensdistortion/imageplugin_lensdistortion.cpp
new file mode 100644
index 00000000..73403fa7
--- /dev/null
+++ b/src/imageplugins/lensdistortion/imageplugin_lensdistortion.cpp
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : a plugin to reduce lens distorsions to an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "lensdistortiontool.h"
+#include "imageplugin_lensdistortion.h"
+#include "imageplugin_lensdistortion.moc"
+
+using namespace DigikamLensDistortionImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_lensdistortion,
+ KGenericFactory<ImagePlugin_LensDistortion>("digikamimageplugin_lensdistortion"));
+
+ImagePlugin_LensDistortion::ImagePlugin_LensDistortion(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_LensDistortion")
+{
+ m_lensdistortionAction = new TDEAction(i18n("Lens Distortion..."), "lensdistortion", 0,
+ this, TQ_SLOT(slotLensDistortion()),
+ actionCollection(), "imageplugin_lensdistortion");
+
+ setXMLFile("digikamimageplugin_lensdistortion_ui.rc");
+
+ DDebug() << "ImagePlugin_LensDistortion plugin loaded" << endl;
+}
+
+ImagePlugin_LensDistortion::~ImagePlugin_LensDistortion()
+{
+}
+
+void ImagePlugin_LensDistortion::setEnabledActions(bool enable)
+{
+ m_lensdistortionAction->setEnabled(enable);
+}
+
+void ImagePlugin_LensDistortion::slotLensDistortion()
+{
+ LensDistortionTool *tool = new LensDistortionTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/lensdistortion/imageplugin_lensdistortion.h b/src/imageplugins/lensdistortion/imageplugin_lensdistortion.h
new file mode 100644
index 00000000..ceb24756
--- /dev/null
+++ b/src/imageplugins/lensdistortion/imageplugin_lensdistortion.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : a plugin to reduce lens distorsions to an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_LENSDISTORTION_H
+#define IMAGEPLUGIN_LENSDISTORTION_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_LensDistortion : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_LensDistortion(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_LensDistortion();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotLensDistortion();
+
+private:
+
+ TDEAction *m_lensdistortionAction;
+};
+
+#endif /* IMAGEPLUGIN_LENSDISTORTION_H */
diff --git a/src/imageplugins/lensdistortion/lensdistortion.cpp b/src/imageplugins/lensdistortion/lensdistortion.cpp
new file mode 100644
index 00000000..7b18cc93
--- /dev/null
+++ b/src/imageplugins/lensdistortion/lensdistortion.cpp
@@ -0,0 +1,135 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : lens distortion algorithm.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2001-2003 by David Hodson <hodsond@acm.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "dimg.h"
+#include "ddebug.h"
+#include "pixelaccess.h"
+#include "lensdistortion.h"
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+LensDistortion::LensDistortion(Digikam::DImg *orgImage, TQObject *parent, double main,
+ double edge, double rescale, double brighten,
+ int center_x, int center_y)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "LensDistortion")
+{
+ m_main = main;
+ m_edge = edge;
+ m_rescale = rescale;
+ m_brighten = brighten;
+ m_centre_x = center_x;
+ m_centre_y = center_y;
+
+ initFilter();
+}
+
+void LensDistortion::filterImage(void)
+{
+ int Width = m_orgImage.width();
+ int Height = m_orgImage.height();
+ int bytesDepth = m_orgImage.bytesDepth();
+
+ uchar *data = m_destImage.bits();
+
+ // initial copy
+
+ m_destImage.bitBltImage(&m_orgImage, 0, 0);
+
+ // initialize coefficients
+
+ double normallise_radius_sq = 4.0 / (Width * Width + Height * Height);
+ double center_x = Width * (100.0 + m_centre_x) / 200.0;
+ double center_y = Height * (100.0 + m_centre_y) / 200.0;
+ double mult_sq = m_main / 200.0;
+ double mult_qd = m_edge / 200.0;
+ double rescale = pow(2.0, - m_rescale / 100.0);
+ double brighten = - m_brighten / 10.0;
+
+ PixelAccess *pa = new PixelAccess(&m_orgImage);
+
+ /*
+ * start at image (i, j), increment by (step, step)
+ * output goes to dst, which is w x h x d in size
+ * NB: d <= image.bpp
+ */
+
+ // We are working on the full image.
+ int dstWidth = Width;
+ int dstHeight = Height;
+ uchar* dst = (uchar*)data;
+ int step = 1, progress;
+
+ int iLimit, jLimit;
+ double srcX, srcY, mag;
+
+ iLimit = dstWidth * step;
+ jLimit = dstHeight * step;
+
+ for (int dstJ = 0 ; !m_cancel && (dstJ < jLimit) ; dstJ += step)
+ {
+ for (int dstI = 0 ; !m_cancel && (dstI < iLimit) ; dstI += step)
+ {
+ // Get source Coordinates.
+ double radius_sq;
+ double off_x;
+ double off_y;
+ double radius_mult;
+
+ off_x = dstI - center_x;
+ off_y = dstJ - center_y;
+ radius_sq = (off_x * off_x) + (off_y * off_y);
+
+ radius_sq *= normallise_radius_sq;
+
+ radius_mult = radius_sq * mult_sq + radius_sq * radius_sq * mult_qd;
+ mag = radius_mult;
+ radius_mult = rescale * (1.0 + radius_mult);
+
+ srcX = center_x + radius_mult * off_x;
+ srcY = center_y + radius_mult * off_y;
+
+ brighten = 1.0 + mag * brighten;
+ pa->pixelAccessGetCubic(srcX, srcY, brighten, dst);
+ dst += bytesDepth;
+ }
+
+ // Update progress bar in dialog.
+
+ progress = (int) (((double)dstJ * 100.0) / jLimit);
+ if (m_parent && progress%5 == 0)
+ postProgress(progress);
+ }
+
+ delete pa;
+}
+
+} // NameSpace DigikamLensDistortionImagesPlugin
diff --git a/src/imageplugins/lensdistortion/lensdistortion.h b/src/imageplugins/lensdistortion/lensdistortion.h
new file mode 100644
index 00000000..cbc46105
--- /dev/null
+++ b/src/imageplugins/lensdistortion/lensdistortion.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : lens distortion algorithm.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2001-2003 by David Hodson <hodsond@acm.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef LENS_DISTORTION_H
+#define LENS_DISTORTION_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+class LensDistortion : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ LensDistortion(Digikam::DImg *orgImage, TQObject *parent=0, double main=0.0,
+ double edge=0.0, double rescale=0.0, double brighten=0.0,
+ int center_x=0, int center_y=0);
+
+ ~LensDistortion(){};
+
+private:
+
+ virtual void filterImage(void);
+
+private:
+
+ int m_centre_x;
+ int m_centre_y;
+
+ double m_main;
+ double m_edge;
+ double m_rescale;
+ double m_brighten;
+};
+
+} // NameSpace DigikamLensDistortionImagesPlugin
+
+#endif /* LENS_DISTORTION_H */
diff --git a/src/imageplugins/lensdistortion/lensdistortiontool.cpp b/src/imageplugins/lensdistortion/lensdistortiontool.cpp
new file mode 100644
index 00000000..8d33e1df
--- /dev/null
+++ b/src/imageplugins/lensdistortion/lensdistortiontool.cpp
@@ -0,0 +1,326 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : a plugin to reduce lens distorsions to an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstring>
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqbrush.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqpixmap.h>
+#include <tqwhatsthis.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "editortoolsettings.h"
+#include "lensdistortion.h"
+#include "lensdistortiontool.h"
+#include "lensdistortiontool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+LensDistortionTool::LensDistortionTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("lensdistortion");
+ setToolName(i18n("Lens Distortion"));
+ setToolIcon(SmallIcon("lensdistortion"));
+
+ m_previewWidget = new ImageWidget("lensdistortion Tool", 0, TQString(),
+ false, ImageGuideWidget::HVGuideMode);
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel,
+ EditorToolSettings::ColorGuide);
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 9, 1);
+
+ m_maskPreviewLabel = new TQLabel( m_gboxSettings->plainPage() );
+ m_maskPreviewLabel->setAlignment ( TQt::AlignHCenter | TQt::AlignVCenter );
+ TQWhatsThis::add( m_maskPreviewLabel, i18n("<p>You can see here a thumbnail preview of the distortion correction "
+ "applied to a cross pattern.") );
+
+ // -------------------------------------------------------------
+
+ TQLabel *label1 = new TQLabel(i18n("Main:"), m_gboxSettings->plainPage());
+
+ m_mainInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_mainInput->setPrecision(1);
+ m_mainInput->setRange(-100.0, 100.0, 0.1);
+ m_mainInput->setDefaultValue(0.0);
+ TQWhatsThis::add(m_mainInput, i18n("<p>This value controls the amount of distortion. Negative values correct lens barrel "
+ "distortion, while positive values correct lens pincushion distortion."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Edge:"), m_gboxSettings->plainPage());
+
+ m_edgeInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_edgeInput->setPrecision(1);
+ m_edgeInput->setRange(-100.0, 100.0, 0.1);
+ m_edgeInput->setDefaultValue(0.0);
+ TQWhatsThis::add(m_edgeInput, i18n("<p>This value controls in the same manner as the Main control, but has more effect "
+ "at the edges of the image than at the center."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Zoom:"), m_gboxSettings->plainPage());
+
+ m_rescaleInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_rescaleInput->setPrecision(1);
+ m_rescaleInput->setRange(-100.0, 100.0, 0.1);
+ m_rescaleInput->setDefaultValue(0.0);
+ TQWhatsThis::add(m_rescaleInput, i18n("<p>This value rescales the overall image size."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label4 = new TQLabel(i18n("Brighten:"), m_gboxSettings->plainPage());
+
+ m_brightenInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_brightenInput->setPrecision(1);
+ m_brightenInput->setRange(-100.0, 100.0, 0.1);
+ m_brightenInput->setDefaultValue(0.0);
+ TQWhatsThis::add(m_brightenInput, i18n("<p>This value adjusts the brightness in image corners."));
+
+ grid->addMultiCellWidget(m_maskPreviewLabel, 0, 0, 0, 1);
+ grid->addMultiCellWidget(label1, 1, 1, 0, 1);
+ grid->addMultiCellWidget(m_mainInput, 2, 2, 0, 1);
+ grid->addMultiCellWidget(label2, 3, 3, 0, 1);
+ grid->addMultiCellWidget(m_edgeInput, 4, 4, 0, 1);
+ grid->addMultiCellWidget(label3, 5, 5, 0, 1);
+ grid->addMultiCellWidget(m_rescaleInput, 6, 6, 0, 1);
+ grid->addMultiCellWidget(label4, 7, 7, 0, 1);
+ grid->addMultiCellWidget(m_brightenInput, 8, 8, 0, 1);
+ grid->setRowStretch(9, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_mainInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_edgeInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_rescaleInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_brightenInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gboxSettings, TQ_SIGNAL(signalColorGuideChanged()),
+ this, TQ_SLOT(slotColorGuideChanged()));
+
+ // -------------------------------------------------------------
+
+ /* Calc transform preview.
+ We would like a checkered area to demonstrate the effect.
+ We do not have any drawing support in DImg, so we let TQt draw.
+ First we create a white TQImage. We convert this to a TQPixmap,
+ on which we can draw. Then we convert back to TQImage,
+ convert the TQImage to a DImg which we only need to create once, here.
+ Later, we apply the effect on a copy and convert the DImg to TQPixmap.
+ Longing for TQt4 where we can paint directly on the TQImage...
+ */
+
+ TQImage preview(120, 120, 32);
+ memset(preview.bits(), 255, preview.numBytes());
+ TQPixmap pix (preview);
+ TQPainter pt(&pix);
+ pt.setPen( TQPen(TQt::black, 1) );
+ pt.fillRect( 0, 0, pix.width(), pix.height(), TQBrush(TQt::black, TQt::CrossPattern) );
+ pt.drawRect( 0, 0, pix.width(), pix.height() );
+ pt.end();
+ TQImage preview2(pix.convertToImage());
+ m_previewRasterImage = DImg(preview2.width(), preview2.height(), false, false, preview2.bits());
+}
+
+LensDistortionTool::~LensDistortionTool()
+{
+}
+
+void LensDistortionTool::slotColorGuideChanged()
+{
+ m_previewWidget->slotChangeGuideColor(m_gboxSettings->guideColor());
+ m_previewWidget->slotChangeGuideSize(m_gboxSettings->guideSize());
+}
+
+void LensDistortionTool::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("lensdistortion Tool");
+
+ m_mainInput->blockSignals(true);
+ m_edgeInput->blockSignals(true);
+ m_rescaleInput->blockSignals(true);
+ m_brightenInput->blockSignals(true);
+
+ m_mainInput->setValue(config->readDoubleNumEntry("2nd Order Distortion", m_mainInput->defaultValue()));
+ m_edgeInput->setValue(config->readDoubleNumEntry("4th Order Distortion",m_edgeInput->defaultValue()));
+ m_rescaleInput->setValue(config->readDoubleNumEntry("Zoom Factor", m_rescaleInput->defaultValue()));
+ m_brightenInput->setValue(config->readDoubleNumEntry("Brighten", m_brightenInput->defaultValue()));
+ m_gboxSettings->setGuideColor(config->readColorEntry("Guide Color", &TQt::red));
+ m_gboxSettings->setGuideSize(config->readNumEntry("Guide Width", 1));
+
+ m_mainInput->blockSignals(false);
+ m_edgeInput->blockSignals(false);
+ m_rescaleInput->blockSignals(false);
+ m_brightenInput->blockSignals(false);
+
+ slotColorGuideChanged();
+ slotEffect();
+}
+
+void LensDistortionTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("lensdistortion Tool");
+ config->writeEntry("2nd Order Distortion", m_mainInput->value());
+ config->writeEntry("4th Order Distortion", m_edgeInput->value());
+ config->writeEntry("Zoom Factor", m_rescaleInput->value());
+ config->writeEntry("Brighten", m_brightenInput->value());
+ config->writeEntry("Guide Color", m_gboxSettings->guideColor());
+ config->writeEntry("Guide Width", m_gboxSettings->guideSize());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void LensDistortionTool::slotResetSettings()
+{
+ m_mainInput->blockSignals(true);
+ m_edgeInput->blockSignals(true);
+ m_rescaleInput->blockSignals(true);
+ m_brightenInput->blockSignals(true);
+
+ m_mainInput->slotReset();
+ m_edgeInput->slotReset();
+ m_rescaleInput->slotReset();
+ m_brightenInput->slotReset();
+
+ m_mainInput->blockSignals(false);
+ m_edgeInput->blockSignals(false);
+ m_rescaleInput->blockSignals(false);
+ m_brightenInput->blockSignals(false);
+}
+
+void LensDistortionTool::prepareEffect()
+{
+ m_mainInput->setEnabled(false);
+ m_edgeInput->setEnabled(false);
+ m_rescaleInput->setEnabled(false);
+ m_brightenInput->setEnabled(false);
+
+ double m = m_mainInput->value();
+ double e = m_edgeInput->value();
+ double r = m_rescaleInput->value();
+ double b = m_brightenInput->value();
+
+ LensDistortion transformPreview(&m_previewRasterImage, 0L, m, e, r, b, 0, 0);
+ m_maskPreviewLabel->setPixmap(TQPixmap(transformPreview.getTargetImage().convertToPixmap()));
+
+ ImageIface* iface = m_previewWidget->imageIface();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new LensDistortion(iface->getOriginalImg(), this, m, e, r, b, 0, 0)));
+}
+
+void LensDistortionTool::prepareFinal()
+{
+ m_mainInput->setEnabled(false);
+ m_edgeInput->setEnabled(false);
+ m_rescaleInput->setEnabled(false);
+ m_brightenInput->setEnabled(false);
+
+ double m = m_mainInput->value();
+ double e = m_edgeInput->value();
+ double r = m_rescaleInput->value();
+ double b = m_brightenInput->value();
+
+ ImageIface iface(0, 0);
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new LensDistortion(iface.getOriginalImg(), this, m, e, r, b, 0, 0)));
+}
+
+void LensDistortionTool::putPreviewData()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+
+ DImg imDest = filter()->getTargetImage().smoothScale(iface->previewWidth(), iface->previewHeight());
+ iface->putPreviewImage(imDest.bits());
+
+ m_previewWidget->updatePreview();
+}
+
+void LensDistortionTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Lens Distortion"), filter()->getTargetImage().bits());
+}
+
+void LensDistortionTool::renderingFinished()
+{
+ m_mainInput->setEnabled(true);
+ m_edgeInput->setEnabled(true);
+ m_rescaleInput->setEnabled(true);
+ m_brightenInput->setEnabled(true);
+}
+
+} // NameSpace DigikamLensDistortionImagesPlugin
diff --git a/src/imageplugins/lensdistortion/lensdistortiontool.h b/src/imageplugins/lensdistortion/lensdistortiontool.h
new file mode 100644
index 00000000..f3c7b67f
--- /dev/null
+++ b/src/imageplugins/lensdistortion/lensdistortiontool.h
@@ -0,0 +1,92 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : a plugin to reduce lens distorsions to an image.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef LENSDISTORTIONTOOL_H
+#define LENSDISTORTIONTOOL_H
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "editortool.h"
+
+class TQLabel;
+
+namespace KDcrawIface
+{
+class RDoubleNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImageWidget;
+}
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+class LensDistortionTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ LensDistortionTool(TQObject *parent);
+ ~LensDistortionTool();
+
+private slots:
+
+ void slotResetSettings();
+ void slotColorGuideChanged();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_maskPreviewLabel;
+
+ KDcrawIface::RDoubleNumInput *m_mainInput;
+ KDcrawIface::RDoubleNumInput *m_edgeInput;
+ KDcrawIface::RDoubleNumInput *m_rescaleInput;
+ KDcrawIface::RDoubleNumInput *m_brightenInput;
+
+ Digikam::DImg m_previewRasterImage;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamLensDistortionImagesPlugin
+
+#endif /* LENSDISTORTIONTOOL_H */
diff --git a/src/imageplugins/lensdistortion/pixelaccess.cpp b/src/imageplugins/lensdistortion/pixelaccess.cpp
new file mode 100644
index 00000000..a6041f94
--- /dev/null
+++ b/src/imageplugins/lensdistortion/pixelaccess.cpp
@@ -0,0 +1,314 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : acess pixels method for lens distortion algorithm.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstring>
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "pixelaccess.h"
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+PixelAccess::PixelAccess(Digikam::DImg *srcImage)
+{
+ m_image = srcImage;
+
+ m_width = PixelAccessWidth;
+ m_height = PixelAccessHeight;
+
+ m_depth = m_image->bytesDepth();
+ m_imageWidth = m_image->width();
+ m_imageHeight = m_image->height();
+ m_sixteenBit = m_image->sixteenBit();
+
+ for ( int i = 0 ; i < PixelAccessRegions ; i++ )
+ {
+ m_buffer[i] = new Digikam::DImg(m_image->copy(0, 0, m_width, m_height));
+
+ m_tileMinX[i] = 1;
+ m_tileMaxX[i] = m_width - 2;
+ m_tileMinY[i] = 1;
+ m_tileMaxY[i] = m_height - 2;
+ }
+}
+
+PixelAccess::~PixelAccess()
+{
+ for( int i = 0 ; i < PixelAccessRegions ; i++ )
+ delete m_buffer[i];
+}
+
+uchar* PixelAccess::pixelAccessAddress(int i, int j)
+{
+ return m_buffer[0]->bits() + m_depth * (m_width * (j + 1 - m_tileMinY[0]) + (i + 1 - m_tileMinX[0]));
+}
+
+// Swap region[n] with region[0].
+void PixelAccess::pixelAccessSelectRegion(int n)
+{
+ Digikam::DImg *temp;
+ int a, b, c, d;
+ int i;
+
+ temp = m_buffer[n];
+ a = m_tileMinX[n];
+ b = m_tileMaxX[n];
+ c = m_tileMinY[n];
+ d = m_tileMaxY[n];
+
+ for( i = n ; i > 0 ; i--)
+ {
+ m_buffer[i] = m_buffer[i-1];
+ m_tileMinX[i] = m_tileMinX[i-1];
+ m_tileMaxX[i] = m_tileMaxX[i-1];
+ m_tileMinY[i] = m_tileMinY[i-1];
+ m_tileMaxY[i] = m_tileMaxY[i-1];
+ }
+
+ m_buffer[0] = temp;
+ m_tileMinX[0] = a;
+ m_tileMaxX[0] = b;
+ m_tileMinY[0] = c;
+ m_tileMaxY[0] = d;
+}
+
+// Buffer[0] is cleared, should start at [i, j], fill rows that overlap image.
+void PixelAccess::pixelAccessDoEdge(int i, int j)
+{
+ int lineStart, lineEnd;
+ int rowStart, rowEnd;
+ int lineWidth;
+ uchar* line;
+
+ lineStart = i;
+ if (lineStart < 0) lineStart = 0;
+ lineEnd = i + m_width;
+ if (lineEnd > m_imageWidth) lineEnd = m_imageWidth;
+ lineWidth = lineEnd - lineStart;
+
+ if( lineStart >= lineEnd )
+ return;
+
+ rowStart = j;
+ if (rowStart < 0) rowStart = 0;
+ rowEnd = j + m_height;
+ if (rowEnd > m_imageHeight) rowEnd = m_imageHeight;
+
+ for( int y = rowStart ; y < rowEnd ; y++ )
+ {
+ line = pixelAccessAddress(lineStart, y);
+ memcpy(line, m_image->scanLine(y) + lineStart * m_depth, lineWidth * m_depth);
+ }
+}
+
+// Moves buffer[0] so that [x, y] is inside it.
+void PixelAccess::pixelAccessReposition(int xInt, int yInt)
+{
+ int newStartX = xInt - PixelAccessXOffset;
+ int newStartY = yInt - PixelAccessYOffset;
+
+ m_tileMinX[0] = newStartX + 1;
+ m_tileMaxX[0] = newStartX + m_width - 2;
+ m_tileMinY[0] = newStartY + 1;
+ m_tileMaxY[0] = newStartY + m_height - 2;
+
+
+ if ( (newStartX < 0) || ((newStartX + m_width) >= m_imageWidth) ||
+ (newStartY < 0) || ((newStartY + m_height) >= m_imageHeight) )
+ {
+ // some data is off edge of image
+
+ m_buffer[0]->fill(Digikam::DColor(0,0,0,0, m_sixteenBit));
+
+ // This could probably be done by bitBltImage but I did not figure out how,
+ // so leave the working code here. And no, it is not this:
+ //m_buffer[0]->bitBltImage(m_image, newStartX, newStartY, m_width, m_height, 0, 0);
+
+ if ( ((newStartX + m_width) < 0) || (newStartX >= m_imageWidth) ||
+ ((newStartY + m_height) < 0) || (newStartY >= m_imageHeight) )
+ {
+ // totally outside, just leave it.
+ }
+ else
+ {
+ pixelAccessDoEdge(newStartX, newStartY);
+ }
+ }
+ else
+ {
+ m_buffer[0]->bitBltImage(m_image, newStartX, newStartY, m_width, m_height, 0, 0);
+ }
+}
+
+void PixelAccess::pixelAccessGetCubic(double srcX, double srcY, double brighten, uchar* dst)
+{
+ int xInt, yInt;
+ double dx, dy;
+ uchar *corner;
+
+ xInt = (int)floor(srcX);
+ dx = srcX - xInt;
+ yInt = (int)floor(srcY);
+ dy = srcY - yInt;
+
+ // We need 4x4 pixels, xInt-1 to xInt+2 horz, yInt-1 to yInt+2 vert
+ // they're probably in the last place we looked...
+
+ if ((xInt >= m_tileMinX[0]) && (xInt < m_tileMaxX[0]) &&
+ (yInt >= m_tileMinY[0]) && (yInt < m_tileMaxY[0]) )
+ {
+ corner = pixelAccessAddress(xInt - 1, yInt - 1);
+ cubicInterpolate(corner, m_depth * m_width, dst, m_sixteenBit, dx, dy, brighten);
+ return;
+ }
+
+ // Or maybe it was a while back...
+
+ for ( int i = 1 ; i < PixelAccessRegions ; i++)
+ {
+ if ((xInt >= m_tileMinX[i]) && (xInt < m_tileMaxX[i]) &&
+ (yInt >= m_tileMinY[i]) && (yInt < m_tileMaxY[i]) )
+ {
+ // Check here first next time
+
+ pixelAccessSelectRegion(i);
+ corner = pixelAccessAddress(xInt - 1, yInt - 1);
+ cubicInterpolate(corner, m_depth * m_width, dst, m_sixteenBit, dx, dy, brighten);
+ return;
+ }
+ }
+
+ // Nope, recycle an old region.
+
+ pixelAccessSelectRegion(PixelAccessRegions - 1);
+ pixelAccessReposition(xInt, yInt);
+
+ corner = pixelAccessAddress(xInt - 1, yInt - 1);
+ cubicInterpolate(corner, m_depth * m_width, dst, m_sixteenBit, dx, dy, brighten);
+}
+
+/*
+ * Catmull-Rom cubic interpolation
+ *
+ * equally spaced points p0, p1, p2, p3
+ * interpolate 0 <= u < 1 between p1 and p2
+ *
+ * (1 u u^2 u^3) ( 0.0 1.0 0.0 0.0 ) (p0)
+ * ( -0.5 0.0 0.5 0.0 ) (p1)
+ * ( 1.0 -2.5 2.0 -0.5 ) (p2)
+ * ( -0.5 1.5 -1.5 0.5 ) (p3)
+ *
+ */
+void PixelAccess::cubicInterpolate(uchar* src, int rowStride, uchar* dst,
+ bool sixteenBit, double dx, double dy, double brighten)
+{
+ float um1, u, up1, up2;
+ float vm1, v, vp1, vp2;
+ int c;
+ const int numberOfComponents = 4;
+ float verts[4 * numberOfComponents];
+
+ um1 = ((-0.5 * dx + 1.0) * dx - 0.5) * dx;
+ u = (1.5 * dx - 2.5) * dx * dx + 1.0;
+ up1 = ((-1.5 * dx + 2.0) * dx + 0.5) * dx;
+ up2 = (0.5 * dx - 0.5) * dx * dx;
+
+ vm1 = ((-0.5 * dy + 1.0) * dy - 0.5) * dy;
+ v = (1.5 * dy - 2.5) * dy * dy + 1.0;
+ vp1 = ((-1.5 * dy + 2.0) * dy + 0.5) * dy;
+ vp2 = (0.5 * dy - 0.5) * dy * dy;
+
+ if (sixteenBit)
+ {
+ unsigned short *src16 = (unsigned short *)src;
+ unsigned short *dst16 = (unsigned short *)dst;
+
+ // for each component, read the values of 4 pixels into array
+
+ for (c = 0 ; c < 4 * numberOfComponents ; c++)
+ {
+ verts[c] = vm1 * src16[c] + v * src16[c+rowStride] + vp1 * src16[c+rowStride*2] + vp2 * src16[c+rowStride*3];
+ }
+
+ // for each component, compute resulting value from array
+
+ for (c = 0 ; c < numberOfComponents ; c++)
+ {
+ float result;
+ result = um1 * verts[c] + u * verts[c+numberOfComponents]
+ + up1 * verts[c+numberOfComponents*2] + up2 * verts[c+numberOfComponents*3];
+ result *= brighten;
+
+ if (result < 0.0)
+ {
+ dst16[c] = 0;
+ }
+ else if (result > 65535.0)
+ {
+ dst16[c] = 65535;
+ }
+ else
+ {
+ dst16[c] = (uint)result;
+ }
+ }
+ }
+ else
+ {
+ for (c = 0 ; c < 4 * numberOfComponents ; c++)
+ {
+ verts[c] = vm1 * src[c] + v * src[c+rowStride] + vp1 * src[c+rowStride*2] + vp2 * src[c+rowStride*3];
+ }
+
+ for (c = 0 ; c < numberOfComponents ; c++)
+ {
+ float result;
+ result = um1 * verts[c] + u * verts[c+numberOfComponents]
+ + up1 * verts[c+numberOfComponents*2] + up2 * verts[c+numberOfComponents*3];
+ result *= brighten;
+
+ if (result < 0.0)
+ {
+ dst[c] = 0;
+ }
+ else if (result > 255.0)
+ {
+ dst[c] = 255;
+ }
+ else
+ {
+ dst[c] = (uint)result;
+ }
+ }
+ }
+}
+
+} // NameSpace DigikamLensDistortionImagesPlugin
+
diff --git a/src/imageplugins/lensdistortion/pixelaccess.h b/src/imageplugins/lensdistortion/pixelaccess.h
new file mode 100644
index 00000000..734d0779
--- /dev/null
+++ b/src/imageplugins/lensdistortion/pixelaccess.h
@@ -0,0 +1,93 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-27
+ * Description : acess pixels method for lens distortion algorithm.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef PIXEL_ACCESS_H
+#define PIXEL_ACCESS_H
+
+#define PixelAccessRegions 20
+#define PixelAccessWidth 40
+#define PixelAccessHeight 20
+#define PixelAccessXOffset 3
+#define PixelAccessYOffset 3
+
+// Digikam includes.
+
+#include "dimg.h"
+
+namespace DigikamLensDistortionImagesPlugin
+{
+
+ /* PixelAcess class: solving the eternal problem: random, cubic-interpolated,
+ * sub-pixel coordinate access to an image.
+ * Assuming that accesses are at least slightly coherent,
+ * PixelAccess keeps PixelAccessRegions buffers, each containing a
+ * PixelAccessWidth x PixelAccessHeight region of pixels.
+ * Buffer[0] is always checked first, so move the last accessed
+ * region into that position.
+ * When a request arrives which is outside all the regions,
+ * get a new region.
+ * The new region is placed so that the requested pixel is positioned
+ * at [PixelAccessXOffset, PixelAccessYOffset] in the region.
+ */
+
+class PixelAccess
+{
+public:
+
+ PixelAccess(Digikam::DImg *srcImage);
+ ~PixelAccess();
+
+ void pixelAccessGetCubic(double srcX, double srcY, double brighten, uchar* dst);
+
+private:
+
+ Digikam::DImg *m_image;
+
+ //uchar* m_buffer[PixelAccessRegions];
+ Digikam::DImg *m_buffer[PixelAccessRegions];
+
+ int m_width;
+ int m_height;
+ int m_depth;
+ int m_imageWidth;
+ int m_imageHeight;
+ bool m_sixteenBit;
+ int m_tileMinX[PixelAccessRegions];
+ int m_tileMaxX[PixelAccessRegions];
+ int m_tileMinY[PixelAccessRegions];
+ int m_tileMaxY[PixelAccessRegions];
+
+protected:
+
+ inline uchar* pixelAccessAddress(int i, int j);
+ void pixelAccessSelectRegion(int n);
+ void pixelAccessDoEdge(int i, int j);
+ void pixelAccessReposition(int xInt, int yInt);
+ void cubicInterpolate(uchar* src, int rowStride, uchar* dst,
+ bool sixteenBit, double dx, double dy, double brighten);
+};
+
+} // NameSpace DigikamLensDistortionImagesPlugin
+
+#endif /* PIXEL_ACCESS_H */
diff --git a/src/imageplugins/noisereduction/Makefile.am b/src/imageplugins/noisereduction/Makefile.am
new file mode 100644
index 00000000..c1b6ac08
--- /dev/null
+++ b/src/imageplugins/noisereduction/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_noisereduction_la_SOURCES = imageplugin_noisereduction.cpp \
+ noisereductiontool.cpp noisereduction.cpp
+
+digikamimageplugin_noisereduction_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_noisereduction_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_noisereduction.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_noisereduction.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_noisereduction_ui.rc
+
diff --git a/src/imageplugins/noisereduction/digikamimageplugin_noisereduction.desktop b/src/imageplugins/noisereduction/digikamimageplugin_noisereduction.desktop
new file mode 100644
index 00000000..9cf408e3
--- /dev/null
+++ b/src/imageplugins/noisereduction/digikamimageplugin_noisereduction.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Name=ImagePlugin_NoiseReduction
+Name[da]=Plugin for støjreducering
+Name[el]=ΠρόσθετοΕικόνας_ΜείωσηΘορύβου
+Name[fi]=Kohinanpoisto
+Name[hr]=Uklanjanje šuma
+Name[it]=PluginImmagini_RiduzioneDisturbi
+Name[nl]=Afbeeldingsplugin_Ruisreductie
+Name[pt]=ImagePlugin_Restoration
+Name[sr]=Смањење шума
+Name[sr@Latn]=Smanjenje šuma
+Name[sv]=Insticksprogram för brusreducering
+Name[tr]=ResimEklentisi_Onarım
+Name[vi]=ImagePlugin_Restoration
+Name[xx]=xxImagePlugin_NoiseReductionxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Noise Reduction plugin for digiKam
+Comment[bg]=Приставка на digiKam за намаляване шума в снимки
+Comment[ca]=Connector pel digiKam de reducció de soroll
+Comment[da]=Plugin til støjreduktion for DigiKam
+Comment[de]=digiKam-Modul zum Entfernen von Rauschen
+Comment[el]=Πρόσθετο μείωσης θορύβου για το digiKam
+Comment[es]=Plugin para digiKam de reducción de ruido
+Comment[et]=DigiKami müra vähendamise plugin
+Comment[fa]=وصلۀ کاهش نوفه برای digiKam
+Comment[fi]=Vähentää kuvan kohinaa
+Comment[fr]=Module externe pour réduire le bruit numérique dans digiKam
+Comment[gl]=Un plugin de digiKam para a reduzón de ruído
+Comment[hr]=digiKam dodatak za uklanjanje šuma
+Comment[is]=Íforrit fyrir digiKam sem minnkar truflanir (noise) í mynd
+Comment[it]=Plugin di riduzione dei disturbi per digiKam
+Comment[ja]=digiKam ノイズ低減プラグイン
+Comment[ms]=Plugin Pengurangan Bising untuk digiKam
+Comment[nds]=digiKam-Moduul för Ruusminnern
+Comment[nl]=Digikam-plugin voor ruisreductie
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਗੜਬੜ ਘਟਾਉਣ ਲਈ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam zmniejszająca szum
+Comment[pt]=Um 'plugin' do digiKam para a redução de ruído
+Comment[pt_BR]=Plugin de redução de ruidos
+Comment[ru]=Модуль уменьшения шума для digiKam
+Comment[sk]=digiKam plugin na potlačenie šumu
+Comment[sr]=digiKam-ов прикључак за смањење шума
+Comment[sr@Latn]=digiKam-ov priključak za smanjenje šuma
+Comment[sv]=Digikam insticksprogram för brusreducering
+Comment[tr]=digiKam için Parazit Azaltma eklentisi
+Comment[uk]=Втулок зменшення шуму для digiKam
+Comment[vi]=Phần bổ sung giảm nhiễu cho digiKam
+Comment[xx]=xxNoise Reduction plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_noisereduction
+author=Caulier Gilles, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/noisereduction/digikamimageplugin_noisereduction_ui.rc b/src/imageplugins/noisereduction/digikamimageplugin_noisereduction_ui.rc
new file mode 100644
index 00000000..b6a663e3
--- /dev/null
+++ b/src/imageplugins/noisereduction/digikamimageplugin_noisereduction_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_noisereduction" >
+
+ <MenuBar>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ <Action name="imageplugin_noisereduction" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_noisereduction" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/noisereduction/imageeffect_noisereduction.cpp b/src/imageplugins/noisereduction/imageeffect_noisereduction.cpp
new file mode 100644
index 00000000..707b05db
--- /dev/null
+++ b/src/imageplugins/noisereduction/imageeffect_noisereduction.cpp
@@ -0,0 +1,553 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-24
+ * Description : a plugin to reduce CCD noise.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqcheckbox.h>
+#include <tqstring.h>
+#include <tqtabwidget.h>
+#include <tqimage.h>
+#include <tqlayout.h>
+#include <tqfile.h>
+#include <tqtextstream.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <tdemessagebox.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "noisereduction.h"
+#include "imageeffect_noisereduction.h"
+#include "imageeffect_noisereduction.moc"
+
+namespace DigikamNoiseReductionImagesPlugin
+{
+
+ImageEffect_NoiseReduction::ImageEffect_NoiseReduction(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Noise Reduction"),
+ "noisereduction", true, true, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Noise Reduction"),
+ digikam_version,
+ I18N_NOOP("A noise reduction image filter plugin for digiKam."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Peter Heckert", I18N_NOOP("Noise Reduction algorithm. Developer"),
+ "peter dot heckert at arcor dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQTabWidget *mainTab = new TQTabWidget(m_imagePreviewWidget);
+
+ TQWidget* firstPage = new TQWidget( mainTab );
+ TQGridLayout* gridSettings = new TQGridLayout( firstPage, 6, 1, spacingHint());
+ mainTab->addTab( firstPage, i18n("Details") );
+
+ TQLabel *label1 = new TQLabel(i18n("Radius:"), firstPage);
+
+ m_radiusInput = new KDoubleNumInput(firstPage);
+ m_radiusInput->setPrecision(1);
+ m_radiusInput->setRange(0.0, 10.0, 0.1, true);
+ TQWhatsThis::add( m_radiusInput, i18n("<p><b>Radius</b>: this control selects the "
+ "gliding window size used for the filter. Larger values do not increase "
+ "the amount of time needed to filter each pixel in the image but "
+ "can cause blurring. This window moves across the image, and the "
+ "color in it is smoothed to remove imperfections. "
+ "In any case it must be about the same size as the noise granularity "
+ "or somewhat more. If it is set higher than necessary, then it "
+ "can cause unwanted blur."));
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridSettings->addMultiCellWidget(m_radiusInput, 0, 0, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Threshold:"), firstPage);
+
+ m_thresholdInput = new KDoubleNumInput(firstPage);
+ m_thresholdInput->setPrecision(2);
+ m_thresholdInput->setRange(0.0, 1.0, 0.01, true);
+ TQWhatsThis::add( m_thresholdInput, i18n("<p><b>Threshold</b>: use the slider for coarse adjustment, "
+ "and the spin control for fine adjustment to control edge detection sensitivity. "
+ "This value should be set so that edges and details are clearly visible "
+ "and noise is smoothed out. "
+ "Adjustment must be made carefully, because the gap between \"noisy\", "
+ "\"smooth\", and \"blur\" is very small. Adjust it as carefully as you would adjust "
+ "the focus of a camera."));
+
+ gridSettings->addMultiCellWidget(label3, 1, 1, 0, 0);
+ gridSettings->addMultiCellWidget(m_thresholdInput, 1, 1, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label4 = new TQLabel(i18n("Texture:"), firstPage);
+
+ m_textureInput = new KDoubleNumInput(firstPage);
+ m_textureInput->setPrecision(2);
+ m_textureInput->setRange(-0.99, 0.99, 0.01, true);
+ TQWhatsThis::add( m_textureInput, i18n("<p><b>Texture</b>: this control sets the texture accuracy. "
+ "This value can be used, to get more or less texture accuracy. When decreased, "
+ "then noise and texture are blurred out, when increased then texture is "
+ "amplified, but also noise will increase. It has almost no effect on image edges."));
+
+ gridSettings->addMultiCellWidget(label4, 2, 2, 0, 0);
+ gridSettings->addMultiCellWidget(m_textureInput, 2, 2, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label7 = new TQLabel(i18n("Sharpness:"), firstPage); // Filter setting "Lookahead".
+
+ m_sharpnessInput = new KDoubleNumInput(firstPage);
+ m_sharpnessInput->setPrecision(2);
+ m_sharpnessInput->setRange(0.0, 1.0, 0.1, true);
+ TQWhatsThis::add( m_sharpnessInput, i18n("<p><b>Sharpness</b>: "
+ "This value improves the frequency response for the filter. "
+ "When it is too strong then not all noise can be removed, or spike noise may appear. "
+ "Set it near to maximum, if you want to remove very weak noise or JPEG-artifacts, "
+ "without losing detail."));
+
+ gridSettings->addMultiCellWidget(label7, 3, 3, 0, 0);
+ gridSettings->addMultiCellWidget(m_sharpnessInput, 3, 3, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label5 = new TQLabel(i18n("Edge Lookahead:"), firstPage); // Filter setting "Sharp".
+
+ m_lookaheadInput = new KDoubleNumInput(firstPage);
+ m_lookaheadInput->setPrecision(2);
+ m_lookaheadInput->setRange(0.01, 20.0, 0.01, true);
+ TQWhatsThis::add( m_lookaheadInput, i18n("<p><b>Edge</b>: "
+ "This value defines the pixel distance to which the filter looks ahead for edges. "
+ "When this value is increased, then spike noise is erased. "
+ "You can eventually re-adjust the <b>Edge</b> filter, when you have changed this setting. "
+ "When this value is too high, the adaptive filter can no longer accurately track "
+ "image details, and noise or blurring can occur."));
+
+ gridSettings->addMultiCellWidget(label5, 4, 4, 0, 0);
+ gridSettings->addMultiCellWidget(m_lookaheadInput, 4, 4, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label10 = new TQLabel(i18n("Erosion:"), firstPage);
+
+ m_phaseInput = new KDoubleNumInput(firstPage);
+ m_phaseInput->setPrecision(1);
+ m_phaseInput->setRange(0.5, 20.0, 0.5, true);
+ TQWhatsThis::add( m_phaseInput, i18n("<p><b>Erosion</b>: "
+ "Use this to increase edge noise erosion and spike noise erosion "
+ "(noise is removed by erosion)."));
+
+ gridSettings->addMultiCellWidget(label10, 5, 5, 0, 0);
+ gridSettings->addMultiCellWidget(m_phaseInput, 5, 5, 1, 1);
+ gridSettings->setColStretch(1, 10);
+ gridSettings->setRowStretch(6, 10);
+
+ // -------------------------------------------------------------
+
+ TQWidget* secondPage = new TQWidget( mainTab );
+ TQGridLayout* gridSettings2 = new TQGridLayout( secondPage, 4, 1, spacingHint());
+ mainTab->addTab( secondPage, i18n("Advanced") );
+
+ TQLabel *label2 = new TQLabel(i18n("Luminance:"), secondPage);
+
+ m_lumToleranceInput = new KDoubleNumInput(secondPage);
+ m_lumToleranceInput->setPrecision(1);
+ m_lumToleranceInput->setRange(0.0, 1.0, 0.1, true);
+ TQWhatsThis::add( m_lumToleranceInput, i18n("<p><b>Luminance</b>: this control sets the luminance tolerance of the image."
+ "We recommend using either the <b>Color</b> or the <b>Luminance</b> tolerance settings "
+ "to make an image correction, not both at the same time. These settings "
+ "do not influence the main smoothing process controlled by the <b>Details</b> "
+ "settings."));
+
+ gridSettings2->addMultiCellWidget(label2, 0, 0, 0, 0);
+ gridSettings2->addMultiCellWidget(m_lumToleranceInput, 0, 0, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label6 = new TQLabel(i18n("Color:"), secondPage);
+
+ m_csmoothInput = new KDoubleNumInput(secondPage);
+ m_csmoothInput->setPrecision(1);
+ m_csmoothInput->setRange(0.0, 1.0, 0.1, true);
+ TQWhatsThis::add( m_csmoothInput, i18n("<p><b>Color</b>: this control sets the color tolerance of the image. It is "
+ "recommended using either the <b>Color</b> or the <b>Luminance</b> tolerance "
+ "to make image correction, not both at the same time. These settings "
+ "do not influence the main smoothing process controlled by the <b>Details</b> "
+ "settings."));
+
+ gridSettings2->addMultiCellWidget(label6, 1, 1, 0, 0);
+ gridSettings2->addMultiCellWidget(m_csmoothInput, 1, 1, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label8 = new TQLabel(i18n("Gamma:"), secondPage);
+
+ m_gammaInput = new KDoubleNumInput(secondPage);
+ m_gammaInput->setPrecision(1);
+ m_gammaInput->setRange(0.3, 3.0, 0.1, true);
+ TQWhatsThis::add( m_gammaInput, i18n("<p><b>Gamma</b>: this control sets the gamma tolerance of the image. This value "
+ "can be used to increase the tolerance values for darker areas (which commonly "
+ "are noisier). This results in more blur for shadow areas."));
+
+ gridSettings2->addMultiCellWidget(label8, 2, 2, 0, 0);
+ gridSettings2->addMultiCellWidget(m_gammaInput, 2, 2, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label9 = new TQLabel(i18n("Damping:"), secondPage);
+
+ m_dampingInput = new KDoubleNumInput(secondPage);
+ m_dampingInput->setPrecision(1);
+ m_dampingInput->setRange(0.5, 20.0, 0.5, true);
+ TQWhatsThis::add( m_dampingInput, i18n("<p><b>Damping</b>: this control sets the phase-jitter damping adjustment. "
+ "This value defines how fast the adaptive filter-radius reacts to luminance "
+ "variations. If increased, then edges appear smoother; if too high, then blur "
+ "may occur. If at minimum, then noise and phase jitter at the edges can occur. It "
+ "can suppress spike noise when increased, and this is the preferred method to "
+ "remove it."));
+
+ gridSettings2->addMultiCellWidget(label9, 3, 3, 0, 0);
+ gridSettings2->addMultiCellWidget(m_dampingInput, 3, 3, 1, 1);
+ gridSettings2->setColStretch(1, 10);
+ gridSettings2->setRowStretch(4, 10);
+
+ m_imagePreviewWidget->setUserAreaWidget(mainTab);
+
+ // -------------------------------------------------------------
+
+// connect(m_radiusInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_lumToleranceInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_thresholdInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_textureInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_sharpnessInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_csmoothInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_lookaheadInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_gammaInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_dampingInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+//
+// connect(m_phaseInput, TQ_SIGNAL(valueChanged(double)),
+// this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_NoiseReduction::~ImageEffect_NoiseReduction()
+{
+}
+
+void ImageEffect_NoiseReduction::renderingFinished()
+{
+ m_radiusInput->setEnabled(true);
+ m_lumToleranceInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ m_textureInput->setEnabled(true);
+ m_sharpnessInput->setEnabled(true);
+ m_csmoothInput->setEnabled(true);
+ m_lookaheadInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+ m_dampingInput->setEnabled(true);
+ m_phaseInput->setEnabled(true);
+}
+
+void ImageEffect_NoiseReduction::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("noisereduction Tool Dialog");
+ m_radiusInput->setEnabled(true);
+ m_lumToleranceInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ m_textureInput->setEnabled(true);
+ m_sharpnessInput->setEnabled(true);
+ m_csmoothInput->setEnabled(true);
+ m_lookaheadInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+ m_dampingInput->setEnabled(true);
+ m_phaseInput->setEnabled(true);
+
+ m_radiusInput->setValue(config->readDoubleNumEntry("RadiusAjustment", 1.0));
+ m_lumToleranceInput->setValue(config->readDoubleNumEntry("LumToleranceAjustment", 1.0));
+ m_thresholdInput->setValue(config->readDoubleNumEntry("ThresholdAjustment", 0.08));
+ m_textureInput->setValue(config->readDoubleNumEntry("TextureAjustment", 0.0));
+ m_sharpnessInput->setValue(config->readDoubleNumEntry("SharpnessAjustment", 0.25));
+ m_csmoothInput->setValue(config->readDoubleNumEntry("CsmoothAjustment", 1.0));
+ m_lookaheadInput->setValue(config->readDoubleNumEntry("LookAheadAjustment", 2.0));
+ m_gammaInput->setValue(config->readDoubleNumEntry("GammaAjustment", 1.4));
+ m_dampingInput->setValue(config->readDoubleNumEntry("DampingAjustment", 5.0));
+ m_phaseInput->setValue(config->readDoubleNumEntry("PhaseAjustment", 1.0));
+
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+}
+
+void ImageEffect_NoiseReduction::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("noisereduction Tool Dialog");
+ config->writeEntry("RadiusAjustment", m_radiusInput->value());
+ config->writeEntry("LumToleranceAjustment", m_lumToleranceInput->value());
+ config->writeEntry("ThresholdAjustment", m_thresholdInput->value());
+ config->writeEntry("TextureAjustment", m_textureInput->value());
+ config->writeEntry("SharpnessAjustment", m_sharpnessInput->value());
+ config->writeEntry("CsmoothAjustment", m_csmoothInput->value());
+ config->writeEntry("LookAheadAjustment", m_lookaheadInput->value());
+ config->writeEntry("GammaAjustment", m_gammaInput->value());
+ config->writeEntry("DampingAjustment", m_dampingInput->value());
+ config->writeEntry("PhaseAjustment", m_phaseInput->value());
+ config->sync();
+}
+
+void ImageEffect_NoiseReduction::resetValues()
+{
+ m_radiusInput->setEnabled(true);
+ m_lumToleranceInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ m_textureInput->setEnabled(true);
+ m_sharpnessInput->setEnabled(true);
+ m_csmoothInput->setEnabled(true);
+ m_lookaheadInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+ m_dampingInput->setEnabled(true);
+ m_phaseInput->setEnabled(true);
+
+ m_radiusInput->setValue(1.0);
+ m_lumToleranceInput->setValue(1.0);
+ m_thresholdInput->setValue(0.08);
+ m_textureInput->setValue(0.0);
+ m_sharpnessInput->setValue(0.25);
+ m_csmoothInput->setValue(1.0);
+ m_lookaheadInput->setValue(2.0);
+ m_gammaInput->setValue(1.4);
+ m_dampingInput->setValue(5.0);
+ m_phaseInput->setValue(1.0);
+
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+}
+
+void ImageEffect_NoiseReduction::prepareEffect()
+{
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+
+ double r = m_radiusInput->value();
+ double l = m_lumToleranceInput->value();
+ double th = m_thresholdInput->value();
+ double tx = m_textureInput->value();
+ double s = m_sharpnessInput->value();
+ double c = m_csmoothInput->value();
+ double a = m_lookaheadInput->value();
+ double g = m_gammaInput->value();
+ double d = m_dampingInput->value();
+ double p = m_phaseInput->value();
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new NoiseReduction(&image,
+ this, r, l, th, tx, s, c, a, g, d, p));
+}
+
+void ImageEffect_NoiseReduction::prepareFinal()
+{
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+
+ double r = m_radiusInput->value();
+ double l = m_lumToleranceInput->value();
+ double th = m_thresholdInput->value();
+ double tx = m_textureInput->value();
+ double s = m_sharpnessInput->value();
+ double c = m_csmoothInput->value();
+ double a = m_lookaheadInput->value();
+ double g = m_gammaInput->value();
+ double d = m_dampingInput->value();
+ double p = m_phaseInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new NoiseReduction(iface.getOriginalImg(),
+ this, r, l, th, tx, s, c, a, g, d, p));
+}
+
+void ImageEffect_NoiseReduction::putPreviewData(void)
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_NoiseReduction::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Noise Reduction"), m_threadedFilter->getTargetImage().bits());
+}
+
+void ImageEffect_NoiseReduction::slotUser3()
+{
+ KURL loadRestorationFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Noise Reduction Settings File to Load")) );
+ if ( loadRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(loadRestorationFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+ if ( stream.readLine() != "# Photograph Noise Reduction Configuration File" )
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a Photograph Noise Reduction settings text file.")
+ .arg(loadRestorationFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+ m_radiusInput->setValue( stream.readLine().toDouble() );
+ m_lumToleranceInput->setValue( stream.readLine().toDouble() );
+ m_thresholdInput->setValue( stream.readLine().toDouble() );
+ m_textureInput->setValue( stream.readLine().toDouble() );
+ m_sharpnessInput->setValue( stream.readLine().toDouble() );
+ m_csmoothInput->setValue( stream.readLine().toDouble() );
+ m_lookaheadInput->setValue( stream.readLine().toDouble() );
+ m_gammaInput->setValue( stream.readLine().toDouble() );
+ m_dampingInput->setValue( stream.readLine().toDouble() );
+ m_phaseInput->setValue( stream.readLine().toDouble() );
+ blockSignals(false);
+// slotEffect();
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the Photograph Noise Reduction text file."));
+
+ file.close();
+}
+
+void ImageEffect_NoiseReduction::slotUser2()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Noise Reduction Settings File to Save")) );
+ if ( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# Photograph Noise Reduction Configuration File\n";
+ stream << m_radiusInput->value() << "\n";
+ stream << m_lumToleranceInput->value() << "\n";
+ stream << m_thresholdInput->value() << "\n";
+ stream << m_textureInput->value() << "\n";
+ stream << m_sharpnessInput->value() << "\n";
+ stream << m_csmoothInput->value() << "\n";
+ stream << m_lookaheadInput->value() << "\n";
+ stream << m_gammaInput->value() << "\n";
+ stream << m_dampingInput->value() << "\n";
+ stream << m_phaseInput->value() << "\n";
+
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the Photograph Noise Reduction text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamNoiseReductionImagesPlugin
+
diff --git a/src/imageplugins/noisereduction/imageeffect_noisereduction.h b/src/imageplugins/noisereduction/imageeffect_noisereduction.h
new file mode 100644
index 00000000..b7ee1bb6
--- /dev/null
+++ b/src/imageplugins/noisereduction/imageeffect_noisereduction.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-24
+ * Description : a plugin to reduce CCD noise.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_NOISEREDUCTION_H
+#define IMAGEEFFECT_NOISEREDUCTION_H
+
+// Local includes.
+
+#include "ctrlpaneldlg.h"
+
+class KDoubleNumInput;
+
+namespace DigikamNoiseReductionImagesPlugin
+{
+
+class ImageEffect_NoiseReduction : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_NoiseReduction(TQWidget* parent);
+ ~ImageEffect_NoiseReduction();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+
+private:
+
+ KDoubleNumInput *m_radiusInput;
+ KDoubleNumInput *m_lumToleranceInput;
+ KDoubleNumInput *m_thresholdInput;
+ KDoubleNumInput *m_textureInput;
+ KDoubleNumInput *m_sharpnessInput;
+
+ KDoubleNumInput *m_csmoothInput;
+ KDoubleNumInput *m_lookaheadInput;
+ KDoubleNumInput *m_gammaInput;
+ KDoubleNumInput *m_dampingInput;
+ KDoubleNumInput *m_phaseInput;
+};
+
+} // NameSpace DigikamNoiseReductionImagesPlugin
+
+#endif /* IMAGEEFFECT_NOISEREDUCTION_H */
diff --git a/src/imageplugins/noisereduction/imageplugin_noisereduction.cpp b/src/imageplugins/noisereduction/imageplugin_noisereduction.cpp
new file mode 100644
index 00000000..8392933d
--- /dev/null
+++ b/src/imageplugins/noisereduction/imageplugin_noisereduction.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-24
+ * Description : a plugin to reduce CCD noise.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "noisereductiontool.h"
+#include "imageplugin_noisereduction.h"
+#include "imageplugin_noisereduction.moc"
+
+using namespace DigikamNoiseReductionImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_noisereduction,
+ KGenericFactory<ImagePlugin_NoiseReduction>("digikamimageplugin_noisereduction"));
+
+ImagePlugin_NoiseReduction::ImagePlugin_NoiseReduction(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_NoiseReduction")
+{
+ m_noiseReductionAction = new TDEAction(i18n("Noise Reduction..."), "noisereduction", 0,
+ this, TQ_SLOT(slotNoiseReduction()),
+ actionCollection(), "imageplugin_noisereduction");
+
+ setXMLFile("digikamimageplugin_noisereduction_ui.rc");
+
+ DDebug() << "ImagePlugin_NoiseReduction plugin loaded" << endl;
+}
+
+ImagePlugin_NoiseReduction::~ImagePlugin_NoiseReduction()
+{
+}
+
+void ImagePlugin_NoiseReduction::setEnabledActions(bool enable)
+{
+ m_noiseReductionAction->setEnabled(enable);
+}
+
+void ImagePlugin_NoiseReduction::slotNoiseReduction()
+{
+ NoiseReductionTool *tool = new NoiseReductionTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/noisereduction/imageplugin_noisereduction.h b/src/imageplugins/noisereduction/imageplugin_noisereduction.h
new file mode 100644
index 00000000..3cbcb312
--- /dev/null
+++ b/src/imageplugins/noisereduction/imageplugin_noisereduction.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-24
+ * Description : a plugin to reduce CCD noise.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_NOISEREDUCTION_H
+#define IMAGEPLUGIN_NOISEREDUCTION_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_NoiseReduction : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_NoiseReduction(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_NoiseReduction();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotNoiseReduction();
+
+private:
+
+ TDEAction *m_noiseReductionAction;
+};
+
+#endif /* IMAGEPLUGIN_NOISEREDUCTION_H */
diff --git a/src/imageplugins/noisereduction/noisereduction.cpp b/src/imageplugins/noisereduction/noisereduction.cpp
new file mode 100644
index 00000000..b9be4729
--- /dev/null
+++ b/src/imageplugins/noisereduction/noisereduction.cpp
@@ -0,0 +1,809 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Noise Reduction threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Noise Filter algorithm copyright (C) 2005
+ * Peter Heckert <peter dot heckert at arcor dot de>
+ * from dcamnoise2 gimp plugin available at this url :
+ * http://home.arcor.de/peter.heckert/dcamnoise2-0.63.c
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#define IIR1(dest,src) (dest) = (d3 = ((((src) * b + d3) * b3 + d2) * b2 + d1) * b1)
+#define IIR2(dest,src) (dest) = (d2 = ((((src) * b + d2) * b3 + d1) * b2 + d3) * b1)
+#define IIR3(dest,src) (dest) = (d1 = ((((src) * b + d1) * b3 + d3) * b2 + d2) * b1)
+
+#define IIR1A(dest,src) (dest) = fabs(d3 = ((((src) * b + d3) * b3 + d2) * b2 + d1) * b1)
+#define IIR2A(dest,src) (dest) = fabs(d2 = ((((src) * b + d2) * b3 + d1) * b2 + d3) * b1)
+#define IIR3A(dest,src) (dest) = fabs(d1 = ((((src) * b + d1) * b3 + d3) * b2 + d2) * b1)
+
+#define FR 0.212671
+#define FG 0.715160
+#define FB 0.072169
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "noisereduction.h"
+
+namespace DigikamNoiseReductionImagesPlugin
+{
+
+NoiseReduction::NoiseReduction(Digikam::DImg *orgImage, TQObject *parent,
+ double radius, double lsmooth, double effect, double texture, double sharp,
+ double csmooth, double lookahead, double gamma, double damping, double phase)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "NoiseReduction")
+{
+ m_radius = radius; /* default radius default = 1.0 */
+ m_sharp = sharp; /* Sharpness factor default = 0.25 */
+ m_lsmooth = lsmooth; /* Luminance Tolerance default = 1.0 */
+ m_effect = effect; /* Adaptive filter-effect threshold default = 0.08 */
+ m_texture = texture; /* Texture Detail default = 0.0 */
+
+ m_csmooth = csmooth; /* RGB Tolerance default = 1.0 */
+ m_lookahead = lookahead; /* Lookahead default = 2.0 */
+ m_gamma = gamma; /* Filter gamma default = 1.0 */
+ m_damping = damping; /* Phase jitter Damping default = 5.0 */
+ m_phase = phase; /* Area Noise Clip default = 1.0 */
+
+ m_iir.B = 0.0;
+ m_iir.b1 = 0.0;
+ m_iir.b2 = 0.0;
+ m_iir.b3 = 0.0;
+ m_iir.b0 = 0.0;
+ m_iir.r = 0.0;
+ m_iir.q = 0.0;
+ m_iir.p = 0;
+
+ m_clampMax = m_orgImage.sixteenBit() ? 65535 : 255;
+
+ initFilter();
+}
+
+// Remove noise on the region, given a source region, dest.
+// region, width and height of the regions, and corner coordinates of
+// a subregion to act upon. Everything outside the subregion is unaffected.
+
+void NoiseReduction::filterImage(void)
+{
+ int bytes = m_orgImage.bytesDepth(); // Bytes per pixel sample
+ uchar *srcPR = m_orgImage.bits();
+ uchar *destPR = m_destImage.bits();
+ int width = m_orgImage.width();
+ int height = m_orgImage.height();
+
+ int row, col, i, progress;
+ float prob = 0.0;
+
+ int w = (int)((m_radius + m_lookahead + m_damping + m_phase) * 4.0 + 40.0);
+
+ // NOTE: commented from original implementation
+ // if (radius < m_lookahead) w = m_lookahead * 4.0 + 40.0;
+
+ float csmooth = m_csmooth;
+
+ // Raw Filter preview
+
+ if (csmooth >= 0.99) csmooth = 1.0;
+
+ // Allocate and init buffers
+
+ uchar *src = new uchar[ TQMAX (width, height) * bytes ];
+ uchar *dest = new uchar[ TQMAX (width, height) * bytes ];
+ float *data = new float[ TQMAX (width, height) + 2*w ];
+ float *data2 = new float[ TQMAX (width, height) + 2*w ];
+ float *buffer = new float[ TQMAX (width, height) + 2*w ];
+ float *rbuf = new float[ TQMAX (width, height) + 2*w ];
+ float *tbuf = new float[ TQMAX (width, height) + 2*w ];
+
+ memset (src, 0, TQMAX (width, height) * bytes);
+ memset (dest, 0, TQMAX (width, height) * bytes);
+
+ for (i=0 ; i < TQMAX(width,height)+2*w-1 ; i++)
+ data[i] = data2[i] = buffer[i] = rbuf[i] = tbuf[i] = 0.0;
+
+ // Initialize the damping filter coefficients
+
+ iir_init(m_radius);
+
+ // blur the rows
+
+ for (row = 0 ; !m_cancel && (row < height) ; row++)
+ {
+ memcpy(src, srcPR + row*width*bytes, width*bytes);
+ memcpy(dest, src, width*bytes);
+
+ blur_line (data+w, data2+w, buffer+w, rbuf+w, tbuf+w, src, dest, width);
+
+ memcpy(destPR + row*width*bytes, dest, width*bytes);
+
+ progress = (int)(((double)row * 20.0) / height);
+ if ( progress%2 == 0 )
+ postProgress( progress );
+ }
+
+ // blur the cols
+
+ for (col = 0 ; !m_cancel && (col < width) ; col++)
+ {
+ for (int n = 0 ; n < height ; n++)
+ memcpy(src + n*bytes, destPR + (col + width*n)*bytes, bytes);
+
+ for (int n = 0 ; n < height ; n++)
+ memcpy(dest + n*bytes, srcPR + (col + width*n)*bytes, bytes);
+
+ blur_line (data+w, data2+w, buffer+w, rbuf+w, tbuf+w, src, dest, height);
+
+ for (int n = 0 ; n < height ; n++)
+ memcpy(destPR + (col + width*n)*bytes, dest + n*bytes, bytes);
+
+ progress = (int)(20.0 + ((double)col * 20.0) / width);
+ if ( progress%2 == 0 )
+ postProgress( progress );
+ }
+
+ // merge the source and destination (which currently contains
+ // the blurred version) images
+
+ for (row = 0 ; !m_cancel && (row < height) ; row++)
+ {
+ uchar *s = src;
+ uchar *d = dest;
+ unsigned short *s16 = (unsigned short *)src;
+ unsigned short *d16 = (unsigned short *)dest;
+ float value;
+ int u, v;
+
+ // get source row
+
+ memcpy(src, srcPR + row*width*bytes, width*bytes);
+ memcpy(dest, destPR + row*width*bytes, width*bytes);
+
+ // get dest row and combine the two
+
+ float t = m_csmooth;
+ float t2 = m_lsmooth;
+
+ // Values are squared, so that sliders get a nonlinear chracteristic
+ // for better adjustment accuracy when values are small.
+ t*=t;
+ t2*=t2;
+
+ for (u = 0 ; !m_cancel && (u < width) ; u++)
+ {
+ float dpix[3], spix[3];
+ float lum, red, green, blue;
+ float lum2, red2, green2, blue2;
+
+ if (m_orgImage.sixteenBit()) // 16 bits image
+ {
+ red = (float) s16[2]/(float)m_clampMax;
+ green = (float) s16[1]/(float)m_clampMax;
+ blue = (float) s16[0]/(float)m_clampMax;
+ }
+ else // 8 bits image
+ {
+ red = (float) s[2]/(float)m_clampMax;
+ green = (float) s[1]/(float)m_clampMax;
+ blue = (float) s[0]/(float)m_clampMax;
+ }
+
+ spix[2] = red;
+ spix[1] = green;
+ spix[0] = blue;
+
+ lum = (FR*red + FG*green + FB*blue);
+
+ if (m_orgImage.sixteenBit()) // 16 bits image
+ {
+ red2 = (float) d16[2]/(float)m_clampMax;
+ green2 = (float) d16[1]/(float)m_clampMax;
+ blue2 = (float) d16[0]/(float)m_clampMax;
+ }
+ else // 8 bits image
+ {
+ red2 = (float) d[2]/(float)m_clampMax;
+ green2 = (float) d[1]/(float)m_clampMax;
+ blue2 = (float) d[0]/(float)m_clampMax;
+ }
+
+ lum2 = (FR*red2 + FG*green2 + FB*blue2);
+
+ // Calculate luminance error (contrast error) for filtered template.
+ // This error is biggest, where edges are. Edges anyway cannot be filtered.
+ // Therefore we can correct luminance error in edges without increasing noise.
+ // Should be adjusted carefully, or not so carefully if you intentionally want to add noise.
+ // Noise, if not colorized, /can/ look good, so this makes sense.
+
+ float dl = lum - lum2;
+
+ // Multiply dl with first derivative of gamma curve divided by derivative value for midtone 0.5
+ // So bright tones will be corrected more (get more luminance noise and -information) than
+ // darker values because bright parts of image generally are less noisy, this is what we want.
+
+ dl *= pow(lum2/0.5, m_gamma-1.0);
+
+ if (t2 > 0.0)
+ dl *= (1.0 - exp(-dl*dl/(2.0*t2*t2)));
+
+ // NOTE: commented from original implementation
+ // if (dl > p) dl = p;
+ // if (dl < -p) dl = -p;
+
+ dpix[2] = red2 + dl;
+ dpix[1] = green2 + dl;
+ dpix[0] = blue2 + dl;
+
+ for (v = 0 ; !m_cancel && (v < 3) ; v++)
+ {
+ float value = spix[v];
+ float fvalue = dpix[v];
+ float mvalue = (value + fvalue)/2.0;
+ float diff = (value) - (fvalue);
+
+ // Multiply diff with first derivative of gamma curve divided by derivative value for midtone 0.5
+ // So midtones will stay unchanged, darker values get more blur and brighter values get less blur
+ // when we increase gamma.
+
+ diff *= pow(mvalue/0.5, m_gamma-1.0);
+
+ // Calculate noise probability for pixel
+ // TODO : probably it is not probability but an arbitrary curve.
+ // Probably we should provide a GUI-interface for this!!!
+
+ if (t > 0.0)
+ prob = exp(-diff*diff/(2.0*t*t));
+ else
+ prob = 0.0;
+
+ // Allow viewing of raw filter output
+
+ if (t >= 0.99)
+ prob = 1.0;
+
+ dpix[v] = value = fvalue * prob + value * (1.0 - prob);
+ }
+
+ if (m_orgImage.sixteenBit()) // 16 bits image
+ {
+ value = dpix[0]*(float)m_clampMax+0.5;
+ d16[0] = (unsigned short)CLAMP(value, 0, m_clampMax);
+ value = dpix[1]*(float)m_clampMax+0.5;
+ d16[1] = (unsigned short)CLAMP(value, 0, m_clampMax);
+ value = dpix[2]*(float)m_clampMax+0.5;
+ d16[2] = (unsigned short)CLAMP(value, 0, m_clampMax);
+
+ d16 += 4;
+ s16 += 4;
+ }
+ else // 8 bits image
+ {
+ value = dpix[0]*(float)m_clampMax+0.5;
+ d[0] = (uchar)CLAMP(value, 0, m_clampMax);
+ value = dpix[1]*(float)m_clampMax+0.5;
+ d[1] = (uchar)CLAMP(value, 0, m_clampMax);
+ value = dpix[2]*(float)m_clampMax+0.5;
+ d[2] = (uchar)CLAMP(value, 0, m_clampMax);
+
+ d += 4;
+ s += 4;
+ }
+ }
+
+ memcpy(destPR + row*width*bytes, dest, width*bytes);
+
+ progress = (int)(40.0 + ((double)row * 60.0) / height);
+ if ( progress%2 == 0 )
+ postProgress( progress );
+ }
+
+ delete [] data;
+ delete [] data2;
+ delete [] buffer;
+ delete [] rbuf;
+ delete [] tbuf;
+ delete [] dest;
+ delete [] src;
+}
+
+// This function is written as if it is blurring a column at a time,
+// even though it can operate on rows, too. There is no difference
+// in the processing of the lines, at least to the blur_line function.
+// 'len' is the length of src and dest
+
+void NoiseReduction::blur_line(float* const data, float* const data2, float* const buffer,
+ float* rbuf, float* tbuf, const uchar *src, uchar *dest, int len)
+{
+ int b;
+ int row;
+ int idx;
+
+ unsigned short *src16 = (unsigned short *)src;
+ unsigned short *dest16 = (unsigned short *)dest;
+
+ // Calculate radius factors
+
+ for (row = 0, idx = 0 ; !m_cancel && (idx < len) ; row += 4, idx++)
+ {
+ // Color weigths are chosen proportional to Bayer Sensor pixel count
+
+ if (m_orgImage.sixteenBit()) // 16 bits image
+ {
+ data[idx] = (float) dest16[row+2] / (float)m_clampMax * 0.25; // Red color
+ data[idx] += (float) dest16[row+1] / (float)m_clampMax * 0.5; // Green color
+ data[idx] += (float) dest16[row] / (float)m_clampMax * 0.25; // Blue color
+ data[idx] = mypow(data[idx], m_gamma);
+ }
+ else // 8 bits image
+ {
+ data[idx] = (float) dest[row+2] / (float)m_clampMax * 0.25; // Red color
+ data[idx] += (float) dest[row+1] / (float)m_clampMax * 0.5; // Green color
+ data[idx] += (float) dest[row] / (float)m_clampMax * 0.25; // Blue color
+ data[idx] = mypow(data[idx], m_gamma);
+ }
+ }
+
+ filter(data, data2, buffer, rbuf, tbuf, len, -1);
+
+ // Do actual filtering
+
+ for (b = 0 ; !m_cancel && (b < 3) ; b++)
+ {
+ for (row = b, idx = 0 ; !m_cancel && (idx < len) ; row += 4, idx++)
+ {
+ if (m_orgImage.sixteenBit()) // 16 bits image
+ data[idx] = (float)src16[row] / (float)m_clampMax;
+ else // 8 bits image
+ data[idx] = (float)src[row] / (float)m_clampMax;
+ }
+
+ filter(data, data2, buffer, rbuf, tbuf, len, b);
+
+ for (row = b, idx = 0 ; !m_cancel && (idx < len) ; row += 4, idx++)
+ {
+ int value = (int)(data[idx] * (float)m_clampMax + 0.5);
+
+ if (m_orgImage.sixteenBit()) // 16 bits image
+ dest16[row] = (unsigned short)CLAMP( value, 0, m_clampMax);
+ else // 8 bits image
+ dest[row] = (uchar)CLAMP( value, 0, m_clampMax);
+ }
+ }
+}
+
+void NoiseReduction::iir_init(double r)
+{
+ if (m_iir.r == r)
+ return;
+
+ // damping settings;
+ m_iir.r = r;
+
+ double q;
+
+ if ( r >= 2.5)
+ q = 0.98711 * r - 0.96330;
+ else
+ q = 3.97156 - 4.14554 * sqrt(1.0 - 0.26891 * r);
+
+ m_iir.q = q;
+ m_iir.b0 = 1.57825 + ((0.422205 * q + 1.4281) * q + 2.44413) * q;
+ m_iir.b1 = ((1.26661 * q +2.85619) * q + 2.44413) * q / m_iir.b0;
+ m_iir.b2 = - ((1.26661*q +1.4281) * q * q ) / m_iir.b0;
+ m_iir.b3 = 0.422205 * q * q * q / m_iir.b0;
+ m_iir.B = 1.0 - (m_iir.b1 + m_iir.b2 + m_iir.b3);
+}
+
+void NoiseReduction::box_filter(double *src, double *end, double *dest, double radius)
+{
+ int boxwidth = 1;
+ float box = (*src);
+ float fbw = 2.0 * radius;
+
+ if (fbw < 1.0)
+ fbw = 1.0;
+
+ while(boxwidth+2 <= (int) fbw) boxwidth+=2, box += (src[boxwidth/2]) + (src[-boxwidth/2]);
+
+ double frac = (fbw - (double) boxwidth) / 2.0;
+ int bh = boxwidth / 2;
+ int bh1 = boxwidth / 2+1;
+
+ for ( ; src <= end ; src++, dest++)
+ {
+ *dest = (box + frac * ((src[bh1])+(src[-bh1]))) / fbw;
+ box = box - (src[-bh]) + (src[bh1]);
+ }
+}
+
+// Bidirectional IIR-filter, speed optimized
+
+void NoiseReduction::iir_filter(float* const start, float* const end, float* dstart,
+ double radius, const int type)
+{
+ if (!dstart)
+ dstart = start;
+
+ int width;
+ float *src = start;
+ float *dest = dstart;
+ float *dend = dstart + (end - start);
+
+ radius = floor((radius + 0.1) / 0.5) * 0.5;
+
+ // NOTE: commented from original implementation
+ // gfloat boxwidth = radius * 2.0;
+ // gint bw = (gint) boxwidth;
+
+ int ofs = (int)radius;
+ if (ofs < 1) ofs = 1;
+
+ double d1, d2, d3;
+
+ width = end - start + 1;
+
+ if (radius < 0.25)
+ {
+ if ( start != dest )
+ {
+ memcpy(dest, start, width*sizeof(*dest));
+ return;
+ }
+ }
+
+ iir_init(radius);
+
+ const double b1 = m_iir.b1;
+ const double b2 = m_iir.b2 / m_iir.b1;
+ const double b3 = m_iir.b3 / m_iir.b2;
+ const double b = m_iir.B / m_iir.b3;
+
+ switch(type)
+ {
+ case Gaussian:
+
+ d1 = d2 = d3 = *dest;
+ dend -= 6;
+ src--;
+ dest--;
+
+ while (dest < dend)
+ {
+ IIR1(*(++dest), *(++src));
+ IIR2(*(++dest), *(++src));
+ IIR3(*(++dest), *(++src));
+ IIR1(*(++dest), *(++src));
+ IIR2(*(++dest), *(++src));
+ IIR3(*(++dest), *(++src));
+ }
+
+ dend += 6;
+
+ while (1)
+ {
+ if (++dest > dend) break;
+ IIR1(*dest,*(++src));
+ if (++dest > dend) break;
+ IIR2(*dest,*(++src));
+ if (++dest > dend) break;
+ IIR3(*dest,*(++src));
+ }
+
+ d1 = d2 = d3 = dest[-1];
+ dstart += 6;
+
+ while (dest > dstart)
+ {
+ --dest, IIR1(*dest, *dest);
+ --dest, IIR2(*dest, *dest);
+ --dest, IIR3(*dest, *dest);
+ --dest, IIR1(*dest, *dest);
+ --dest, IIR2(*dest, *dest);
+ --dest, IIR3(*dest, *dest);
+ }
+
+ dstart -= 6;
+
+ while (1)
+ {
+ if (--dest < dstart) break;
+ IIR1(*dest, *dest);
+ if (--dest < dstart) break;
+ IIR2(*dest, *dest);
+ if (--dest < dstart) break;
+ IIR3(*dest, *dest);
+ }
+
+ break;
+
+ case SecondDerivative: // rectified and filtered second derivative, source and dest may be equal
+
+ d1 = d2 = d3 = 0.0;
+ dest[0] = dest[ofs] = 0.0;
+ dend -= 6;
+ dest--;
+ src--;
+
+ while (dest < dend)
+ {
+ ++src, IIR1(*(++dest), src[ofs]-src[0]);
+ ++src, IIR2(*(++dest), src[ofs]-src[0]);
+ ++src, IIR3(*(++dest), src[ofs]-src[0]);
+ ++src, IIR1(*(++dest), src[ofs]-src[0]);
+ ++src, IIR2(*(++dest), src[ofs]-src[0]);
+ ++src, IIR3(*(++dest), src[ofs]-src[0]);
+ }
+
+ dend += 6;
+
+ while (1)
+ {
+ if (++dest > dend) break;
+ ++src, IIR1(*dest, src[ofs]-src[0]);
+ if (++dest > dend) break;
+ ++src, IIR2(*dest, src[ofs]-src[0]);
+ if (++dest > dend) break;
+ ++src, IIR3(*dest, src[ofs]-src[0]);
+ }
+
+ d1 = d2 = d3 = 0.0;
+ dest[-1] = dest[-ofs-1] = 0.0;
+ dstart += 6;
+
+ while (dest > dstart)
+ {
+ --dest, IIR1A(*dest, dest[0]-dest[-ofs]);
+ --dest, IIR2A(*dest, dest[0]-dest[-ofs]);
+ --dest, IIR3A(*dest, dest[0]-dest[-ofs]);
+ --dest, IIR1A(*dest, dest[0]-dest[-ofs]);
+ --dest, IIR2A(*dest, dest[0]-dest[-ofs]);
+ --dest, IIR3A(*dest, dest[0]-dest[-ofs]);
+ }
+
+ dstart -= 6;
+
+ while (1)
+ {
+ if (--dest < dstart) break;
+ IIR1A(*dest, dest[0]-dest[-ofs]);
+ if (--dest < dstart) break;
+ IIR2A(*dest, dest[0]-dest[-ofs]);
+ if (--dest < dstart) break;
+ IIR3A(*dest, dest[0]-dest[-ofs]);
+ }
+
+ break;
+ }
+}
+
+// A forward-backward box filter is used here and the radius is adapted to luminance jump.
+// Radius is calculated fron 1st and 2nd derivative of intensity values.
+// (Its not exactly 2nd derivative, but something similar, optimized by experiment)
+// The radius variations are filtered. This reduces spatial phase jitter.
+
+void NoiseReduction::filter(float *buffer, float *data, float *data2, float *rbuf,
+ float */*tbuf*/, int width, int color)
+{
+ float *lp = data;
+ float *rp = data + width-1;
+ float *lp2 = data2;
+ float *blp = buffer;
+ float *brp = buffer + width-1;
+ float *rbuflp = rbuf;
+ float *rbufrp = rbuf + width-1;
+ float fboxwidth = m_radius*2.0;
+ float fradius = m_radius;
+ float *p1, *p2;
+
+ if (fboxwidth < 1.0) fboxwidth = 1.0 ;
+ if (fradius < 0.5) fradius = 0.5;
+
+ int i, pass;
+ int ofs, ofs2;
+ float maxrad;
+ float fbw;
+ float val;
+ double rfact = m_effect*m_effect;
+ double sharp = m_sharp;
+
+ ofs2 = (int)floor(m_damping * 2.0 + 0.1);
+ ofs = (int)floor(m_lookahead * 2.0 + 0.1);
+ int w = (int)(fboxwidth + m_damping + m_lookahead + m_phase + 2.0);
+
+ // Mirror image edges
+
+ for (i=1 ; i <= w ; i++)
+ blp[-i] = blp[i];
+
+ for (i=1 ; i <= w ; i++)
+ brp[i] = brp[-i];
+
+ if (color < 0) // Calc 2nd derivative
+ {
+ // boost high frequency in rbuf
+
+ for (p1 = blp, p2 = rbuflp ; p1 <= brp ; p1++, p2++)
+ {
+ *p2 = (sharp+1.0) * p1[0] - sharp * 0.5 * (p1[-ofs]+p1[ofs]);
+ }
+
+ iir_filter(rbuflp-w, rbufrp+w, blp-w, m_lookahead, SecondDerivative);
+
+ // Mirror image edges
+
+ for (i = 1 ; i <= w ; i++)
+ blp[-i] = blp[i];
+
+ for (i = 1 ; i <= w ; i++)
+ brp[i] = brp[-i];
+
+ // boost high frequency in rbuf
+
+ for (p1 = blp, p2 = rbuflp ; p1 <= brp ; p1++, p2++)
+ {
+ *p2 = ((sharp+1.0) * (p1[0]) - sharp * 0.5 * ((p1[-ofs2])+(p1[ofs2])));
+ }
+
+ // Mirror rbuf edges
+
+ for (i = 1 ; i <= w ; i++)
+ rbuflp[-i] = rbuflp[i];
+
+ for (i = 1 ; i <= w ; i++)
+ rbufrp[i] = rbufrp[-i];
+
+ // Lowpass (gauss) filter rbuf, remove phase jitter
+
+ iir_filter(rbuflp-w+5, rbufrp+w-5, rbuflp-w+5, m_damping, Gaussian);
+
+ for (i = -w+5; i < width-1+w-5 ; i++)
+ {
+ // NOTE: commented from original implementation
+ // val = rbuflp[i];
+
+ val = rbuflp[i]-rfact;
+
+ // Avoid division by zero, clip negative filter overshoot
+
+ if (val < rfact/fradius) val=rfact/fradius;
+
+ val = rfact/val;
+
+ // NOTE: commented from original implementation
+ // val = pow(val/fradius,m_phase)*fradius;
+
+ if (val < 0.5) val = 0.5;
+
+ rbuflp[i] = val*2.0;
+ }
+
+ // Mirror rbuf edges
+
+ for (i=1 ; i <= w ; i++)
+ rbuflp[-i] = rbuflp[i];
+
+ for (i=1 ; i <= w ; i++)
+ rbufrp[i] = rbufrp[-i];
+
+ return;
+ }
+
+ // Calc lowpass filtered input signal
+
+ iir_filter(blp-w+1, brp+w-1, lp2-w+1, m_radius, Gaussian);
+
+ // Subtract low frequency from input signal (aka original image data)
+ // and predistort this signal
+
+ val = m_texture + 1.0;
+
+ for (i = -w+1 ; i <= width-1+w-1 ; i++)
+ {
+ blp[i] = mypow(blp[i] - lp2[i], val);
+ }
+
+ float *src, *dest;
+ val = m_texture + 1.0;
+
+ pass = 2;
+
+ while (pass--)
+ {
+ float sum;
+ int ibw;
+ src = blp;
+ dest = lp;
+ maxrad = 0.0;
+
+ // Mirror left edge
+
+ for (i=1 ; i <= w ; i++)
+ src[-i] = src[i];
+
+ sum = (src[-1] += src[-2]);
+
+ // forward pass
+
+ for (rbuf = rbuflp-(int) m_phase ; rbuf <= rbufrp; src++, dest++, rbuf++)
+ {
+ // NOTE: commented from original implementation
+ //fbw = fabs( rbuf[-ofs2]*ll2+rbuf[-ofs2-1]*rl2);
+
+ fbw = *rbuf;
+
+ if (fbw > (maxrad += 1.0)) fbw = maxrad;
+ else if (fbw < maxrad) maxrad = fbw;
+
+ ibw = (int)fbw;
+ *src = sum += *src;
+ *dest = (sum-src[-ibw]+(src[-ibw]-src[-ibw-1])*(fbw-ibw))/fbw;
+ }
+
+ src = rp;
+ dest = brp;
+ maxrad = 0.0;
+
+ // Mirror right edge
+
+ for (i=1 ; i <= w ; i++)
+ src[i] = src[-i];
+
+ sum = (src[1] += src[2]);
+
+ // backward pass
+
+ for ( rbuf = rbufrp +(int) m_phase ; rbuf >= rbuflp; src--, dest--, rbuf--)
+ {
+ // NOTE: commented from original implementation
+ //fbw = fabs( rbuf[ofs2]*ll2+rbuf[ofs2+1]*rl2);
+
+ fbw = *rbuf;
+
+ if (fbw > (maxrad +=1.0)) fbw = maxrad;
+ else if (fbw < maxrad) maxrad = fbw;
+
+ ibw = (int)fbw;
+
+ *src = sum += *src;
+ *dest = (sum-src[ibw]+(src[ibw]-src[ibw+1])*(fbw-ibw))/fbw;
+ }
+ }
+
+ val = 1.0 / (m_texture + 1.0);
+
+ for (i = -w+1 ; i <= width-1+w-1 ; i++)
+ {
+ // Undo predistortion
+
+ blp[i]= mypow(blp[i],val);
+
+ // Add in low frequency
+
+ blp[i] += lp2[i];
+
+ // NOTE: commented from original implementation
+ // if (blp[i] >= 0.0) blp[i] = pow(blp[i],val);
+ // else blp[i] = 0.0;
+ }
+}
+
+} // NameSpace DigikamNoiseReductionImagesPlugin
diff --git a/src/imageplugins/noisereduction/noisereduction.h b/src/imageplugins/noisereduction/noisereduction.h
new file mode 100644
index 00000000..69e14123
--- /dev/null
+++ b/src/imageplugins/noisereduction/noisereduction.h
@@ -0,0 +1,257 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Noise Reduction threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Noise Filter algorithm copyright (C) 2005
+ * Peter Heckert <peter dot heckert at arcor dot de>
+ * from dcamnoise2 gimp plugin available at this url :
+ * http://home.arcor.de/peter.heckert/dcamnoise2-0.63.c
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef NOISE_REDUCTION_H
+#define NOISE_REDUCTION_H
+
+// C++ includes.
+
+#include <cmath>
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+/**============= NOTICE TO USE THE FILTER ===============================================================
+ *
+ * Let me explain, how the filter works, some understanding is necessary to use it:
+ *
+ * Hint for the novice user:
+ * In most cases only Filter Max Radius, Filter treshold and Texture Detail are needed and the other
+ * params can be left at their default setting.
+ *
+ * Main Filter (Preprocessing)
+ * First, a filtered template is generated, using an adaptive filter.
+ * To see this template, we must set _Luminance tolerance, _Color tolerance to 1.0.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * "Filter max. Radius" is preset to 5.0
+ * This is good for most noise situations.
+ * In any case it must be about the same size as noise granularity ore somewhat more.
+ * If it is set higher than necessary, then it can cause unwanted blur.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * "Filter Threshold" should be set so that edges are clearly visible and noise is smoothed out.
+ * This threshold value is not bound to any intensity value, it is bound to the second derivative of
+ * intensity values.
+ * Simply adjust it and watch the preview. Adjustment must be made carefully, because the gap
+ * between "noisy", "smooth", and "blur" is very small. Adjust it as carefully as you would adjust
+ * the focus of a camera.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * "Lookahead" defines the pixel distance in which the filter looks ahead for luminance variations
+ * Normally the default value should do.
+ * When _Lookahead is increased, then spikenoise is erased.
+ * Eventually readjust Filter treshold, when you changed lookahead.
+ * When the value is to high, then the adaptive filter cannot longer accurately track image details, and
+ * noise can reappear or blur can occur.
+ *
+ * Minimum value is 1.0, this gives best accuracy when blurring very weak noise.
+ *
+ * I never had good success with other values than 2.0.
+ * However, for images with extemely high or low resolution another value possibly is better.
+ * Use it only as a last ressort.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * "Phase Jitter Damping" defines how fast the adaptive filter-radius reacts to luminance variations.
+ * I have preset a value, that should do in most cases.
+ * If increased, then edges appear smoother, if too high, then blur may occur.
+ * If at minimum then noise and phase jitter at edges can occur.
+ * It can suppress Spike noise when increased and this is the preferred method to remove spike noise.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * "Sharpness" does just what it says, it improves sharpness. It improves the frequency response for the filter.
+ * When it is too strong then not all noise can be removed, or spike noise may appear.
+ * Set it near to maximum, if you want to remove weak noise or JPEG-artifacts, without loosing detail.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * "Erosion". The new filter gives better sharpness and this also gives problems
+ * with spike noise. The Erosion param erodes singular spikes and it has a smooth effect to edges, and sharpens
+ * edges by erosion, so noise at edges is eroded.
+ * The effect is dependant from sharpness,phase-jitter damping and lookahead.
+ * Set it to minimum (zero), if you want to remove weak noise or JPEG-artifacts.
+ * When "Erosion" is increased, then also increasing "Phase Jitter Damping" is often useful
+ *
+ * It works nicely. Apart from removing spike noise it has a sharpening and antialiasing effect to edges
+ * (Sharpening occurs by erosion, not by deconvolution)
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * "Texture Detail" can be used, to get more or less texture accuracy.
+ * When decreased, then noise and texture are blurred out, when increased then texture is
+ * amplified, but also noise will increase.
+ * It has almost no effect to image edges, opposed to Filter theshold, which would blur edges, when increased.
+ *
+ * E.g. if Threshold is adjusted in away so that edges are sharp, and there is still too much area noise, then
+ * Texture detail could be used to reduce noise without blurring edges.
+ * (Another way would be to decrease radius and to increase threshold)
+ *
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * The filtered image that is now seen in the preview, is used as template for the following processing steps,
+ * therefore it is important to do this adjustment in first place and to do it as good as possible.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * Combining original image and filtered image, using tolerance thresholds (Postprocessing)
+ * This can give a final touch of sharpness to your image.
+ * It is not necessary to do this, if you want to reduce JPEG-artifacts or weak noise.
+ * It's purpose is to master strong noise without loosing too much sharpness.
+ *
+ * Note, that this all is done in one filter invocation. Preprocessing and postprocessing is done in one run,
+ * but logically and in the algorithm they are different and ordered processes.
+ *
+ *
+ * Adjust _Color tolerance or/and Luminance tolerance, (if necessary) so that you get the final image.
+ * I recommend to use only one, either _Color or _Luminance.
+ * These settings do not influence the main smoothing process. What they really do is this:
+ *
+ * The tolerance values are used as error-thresholds to compare the filtered template with the original
+ * image. The plugin algorithm uses them to combine the filtered template with the original image
+ * so that noise and filter errors (blur) are thrown out.
+ * A filtered pixel, that is too far away from the original pixel will be overridden by original image content.
+ *
+ * Hint:
+ * If you cange other sliders, like lookahead or Texture Detail, then you should set color tolerance and
+ * luminance tolerance to 1.0 (right end), because otherwise the filtered template is partially hidden
+ * and e.g. the effects for the damping filter cant be seen clearly and cant be optimized.
+ *------------------------------------------------------------------------------------------------------------
+ *
+ * _Gamma can be used to increase the tolerance values for darker areas (which commonly are more noisy)
+ * This results in more blur for shadow areas.
+ *
+ * Hint for users of previous versions:
+ * Gamma also influences the main-filter process. While the previous version did not have this feature,
+ * I have reimplemented it, however, the algorithm used is totally new.
+ *
+ *
+ * Keep in mind, how the filter works, then usage should be easy!
+ *
+ *
+ * ================ THEORY AND TECHNIC =======================================================================
+ *
+ * Some interesting things (theoretic and technic)
+ * This plugin bases on the assumption, that noise has no 2-dimensional correlation and therefore
+ * can be removed in a 1-dimensional process.
+ * To remove noise, I use a four-times boxfilter with variable radius.
+ *
+ * The radius is calculated from 2nd derivative of pixeldata.
+ * A gauss filter is used to calculte 2nd derivative.
+ * The filter has some inbuilt features to clip low amplitude noise to clip very high values that would
+ * slow down response time.
+ * The 2nd derivative is lowpassfiltered and then radius is calculated as (Filter Treshold)/2nd_derivative.
+ * The radius modulation data is precalulated and buffered an is used to steer filter radius when
+ * the actual filtering occurs.
+ *
+ * Noise and texture can be further suppressed by nonlinear distortion before adaptive filtering.
+ * To make this possible I subtract low frequency from image data before denoising, so that I get a
+ * bipolar, zerosymmetric image signal.
+ *
+ * The filter works in a /one-dimensional/ way. It is applied to x and then to y axis.
+ *
+ * After filtering a zerodimensional point operator (pixel by pixel comparison) is used, where
+ * filter-errors are thrown out.
+ * This is meant to limit and control filter errors,it can give "final touch" to the image, but it has
+ * nothing to do with the main filter process.
+ *
+ * I do not know if something like this filter already exists.
+ * It is all based on my own ideas and experiments.
+ * Possibly a separable adaptive gauss-filter is a new thing.
+ * Also it is an impossible thing, from a mathemathical point of view ;-)
+ * It is possible only for bandwidth limited images.
+ * Happyly most photographic images are bandwidth limited, or when they are noisy then we want
+ * to limit banwith locally. And this is, what the filter does: It limits bandwidth locally, dependent
+ * from (approximately) 2nd derivative of intensity.
+ *
+ * Because gauss filtering is essentially linear diffusion, and because this filter uses a variable
+ * nonlinear modulated gaussfilter (four box passes are almost gauss) we could say, that this filter
+ * implements a special subclass of nonlinear adaptive diffusion, which is separable, and indeed,
+ * results are very similar to nonlinear diffusion filters.
+ * However, because the filter is separable, it is much faster and needs less memory.
+ */
+
+namespace DigikamNoiseReductionImagesPlugin
+{
+
+class NoiseReduction : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ NoiseReduction(Digikam::DImg *orgImage, TQObject *parent,
+ double radius, double lsmooth, double effect, double texture, double sharp,
+ double csmooth, double lookahead, double gamma, double damping, double phase);
+ ~NoiseReduction(){};
+
+private:
+
+ void filterImage(void);
+
+ void iir_init(double r);
+ void box_filter(double *src, double *end, double *dest, double radius);
+ void iir_filter(float* const start, float* const end, float* dstart, double radius, const int type);
+ void filter(float *buffer, float *data, float *data2, float *rbuf, float *tbuf, int width, int color);
+ void blur_line(float* const data, float* const data2, float* const buffer,
+ float* rbuf, float* tbuf, const uchar *src, uchar *dest, int len);
+
+ inline double mypow(double val, double ex)
+ {
+ if (fabs(val) < 1e-16) return 0.0;
+ if (val > 0.0) return exp(log(val)*ex);
+ return -exp(log(-val)*ex);
+ };
+
+private:
+
+ struct iir_param
+ {
+ double B, b1, b2, b3, b0, r, q;
+ double *p;
+ } m_iir;
+
+ enum IIRFilteringMode
+ {
+ Gaussian=0,
+ SecondDerivative
+ };
+
+ int m_clampMax;
+
+ double m_radius;
+ double m_lsmooth;
+ double m_csmooth;
+ double m_effect;
+ double m_lookahead;
+ double m_gamma;
+ double m_damping;
+ double m_phase;
+ double m_texture;
+ double m_sharp;
+};
+
+} // NameSpace DigikamNoiseReductionImagesPlugin
+
+#endif /* NOISE_REDUCTION_H */
diff --git a/src/imageplugins/noisereduction/noisereductiontool.cpp b/src/imageplugins/noisereduction/noisereductiontool.cpp
new file mode 100644
index 00000000..d1e4908a
--- /dev/null
+++ b/src/imageplugins/noisereduction/noisereductiontool.cpp
@@ -0,0 +1,536 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-24
+ * Description : a plugin to reduce CCD noise.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqfile.h>
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqstring.h>
+#include <tqtabwidget.h>
+#include <tqtextstream.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "noisereduction.h"
+#include "noisereductiontool.h"
+#include "noisereductiontool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamNoiseReductionImagesPlugin
+{
+
+NoiseReductionTool::NoiseReductionTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("noisereduction");
+ setToolName(i18n("Noise Reduction"));
+ setToolIcon(SmallIcon("noisereduction"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 1, 1);
+
+ TQTabWidget *mainTab = new TQTabWidget(m_gboxSettings->plainPage());
+ TQWidget* firstPage = new TQWidget( mainTab );
+ TQGridLayout* grid1 = new TQGridLayout(firstPage, 6, 1);
+
+ TQLabel *label1 = new TQLabel(i18n("Radius:"), firstPage);
+
+ m_radiusInput = new RDoubleNumInput(firstPage);
+ m_radiusInput->setPrecision(1);
+ m_radiusInput->setRange(0.0, 10.0, 0.1);
+ m_radiusInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_radiusInput, i18n("<p><b>Radius</b>: this control selects the "
+ "gliding window size used for the filter. Larger values do not increase "
+ "the amount of time needed to filter each pixel in the image but "
+ "can cause blurring. This window moves across the image, and the "
+ "color in it is smoothed to remove imperfections. "
+ "In any case it must be about the same size as the noise granularity "
+ "or somewhat more. If it is set higher than necessary, then it "
+ "can cause unwanted blur."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Threshold:"), firstPage);
+
+ m_thresholdInput = new RDoubleNumInput(firstPage);
+ m_thresholdInput->setPrecision(2);
+ m_thresholdInput->setRange(0.0, 1.0, 0.01);
+ m_thresholdInput->setDefaultValue(0.08);
+ TQWhatsThis::add( m_thresholdInput, i18n("<p><b>Threshold</b>: use the slider for coarse adjustment, "
+ "and the spin control for fine adjustment to control edge detection sensitivity. "
+ "This value should be set so that edges and details are clearly visible "
+ "and noise is smoothed out. "
+ "Adjustment must be made carefully, because the gap between \"noisy\", "
+ "\"smooth\", and \"blur\" is very small. Adjust it as carefully as you would adjust "
+ "the focus of a camera."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label4 = new TQLabel(i18n("Texture:"), firstPage);
+
+ m_textureInput = new RDoubleNumInput(firstPage);
+ m_textureInput->setPrecision(2);
+ m_textureInput->setRange(-0.99, 0.99, 0.01);
+ m_textureInput->setDefaultValue(0.0);
+ TQWhatsThis::add( m_textureInput, i18n("<p><b>Texture</b>: this control sets the texture accuracy. "
+ "This value can be used, to get more or less texture accuracy. When decreased, "
+ "then noise and texture are blurred out, when increased then texture is "
+ "amplified, but also noise will increase. It has almost no effect on image edges."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label7 = new TQLabel(i18n("Sharpness:"), firstPage); // Filter setting "Lookahead".
+
+ m_sharpnessInput = new RDoubleNumInput(firstPage);
+ m_sharpnessInput->setPrecision(2);
+ m_sharpnessInput->setRange(0.0, 1.0, 0.1);
+ m_sharpnessInput->setDefaultValue(0.25);
+ TQWhatsThis::add( m_sharpnessInput, i18n("<p><b>Sharpness</b>: "
+ "This value improves the frequency response for the filter. "
+ "When it is too strong then not all noise can be removed, or spike noise may appear. "
+ "Set it near to maximum, if you want to remove very weak noise or JPEG-artifacts, "
+ "without losing detail."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label5 = new TQLabel(i18n("Edge Lookahead:"), firstPage); // Filter setting "Sharp".
+
+ m_lookaheadInput = new RDoubleNumInput(firstPage);
+ m_lookaheadInput->setPrecision(2);
+ m_lookaheadInput->setRange(0.01, 20.0, 0.01);
+ m_lookaheadInput->setDefaultValue(2.0);
+ TQWhatsThis::add( m_lookaheadInput, i18n("<p><b>Edge</b>: "
+ "This value defines the pixel distance to which the filter looks ahead for edges. "
+ "When this value is increased, then spike noise is erased. "
+ "You can eventually re-adjust the <b>Edge</b> filter, when you have changed this setting. "
+ "When this value is too high, the adaptive filter can no longer accurately track "
+ "image details, and noise or blurring can occur."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label10 = new TQLabel(i18n("Erosion:"), firstPage);
+
+ m_phaseInput = new RDoubleNumInput(firstPage);
+ m_phaseInput->setPrecision(1);
+ m_phaseInput->setRange(0.5, 20.0, 0.5);
+ m_phaseInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_phaseInput, i18n("<p><b>Erosion</b>: "
+ "Use this to increase edge noise erosion and spike noise erosion "
+ "(noise is removed by erosion)."));
+
+ grid1->addMultiCellWidget(label1, 0, 0, 0, 0);
+ grid1->addMultiCellWidget(m_radiusInput, 0, 0, 1, 1);
+ grid1->addMultiCellWidget(label3, 1, 1, 0, 0);
+ grid1->addMultiCellWidget(m_thresholdInput, 1, 1, 1, 1);
+ grid1->addMultiCellWidget(label4, 2, 2, 0, 0);
+ grid1->addMultiCellWidget(m_textureInput, 2, 2, 1, 1);
+ grid1->addMultiCellWidget(label7, 3, 3, 0, 0);
+ grid1->addMultiCellWidget(m_sharpnessInput, 3, 3, 1, 1);
+ grid1->addMultiCellWidget(label5, 4, 4, 0, 0);
+ grid1->addMultiCellWidget(m_lookaheadInput, 4, 4, 1, 1);
+ grid1->addMultiCellWidget(label10, 5, 5, 0, 0);
+ grid1->addMultiCellWidget(m_phaseInput, 5, 5, 1, 1);
+ grid1->setMargin(m_gboxSettings->spacingHint());
+ grid1->setSpacing(m_gboxSettings->spacingHint());
+ grid1->setColStretch(1, 10);
+ grid1->setRowStretch(6, 10);
+
+ mainTab->addTab( firstPage, i18n("Details") );
+
+ // -------------------------------------------------------------
+
+ TQWidget* secondPage = new TQWidget( mainTab );
+ TQGridLayout* grid2 = new TQGridLayout( secondPage, 4, 1);
+
+ TQLabel *label2 = new TQLabel(i18n("Luminance:"), secondPage);
+
+ m_lumToleranceInput = new RDoubleNumInput(secondPage);
+ m_lumToleranceInput->setPrecision(1);
+ m_lumToleranceInput->setRange(0.0, 1.0, 0.1);
+ m_lumToleranceInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_lumToleranceInput, i18n("<p><b>Luminance</b>: this control sets the luminance tolerance of the image."
+ "We recommend using either the <b>Color</b> or the <b>Luminance</b> tolerance settings "
+ "to make an image correction, not both at the same time. These settings "
+ "do not influence the main smoothing process controlled by the <b>Details</b> "
+ "settings."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label6 = new TQLabel(i18n("Color:"), secondPage);
+
+ m_csmoothInput = new RDoubleNumInput(secondPage);
+ m_csmoothInput->setPrecision(1);
+ m_csmoothInput->setRange(0.0, 1.0, 0.1);
+ m_csmoothInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_csmoothInput, i18n("<p><b>Color</b>: this control sets the color tolerance of the image. It is "
+ "recommended using either the <b>Color</b> or the <b>Luminance</b> tolerance "
+ "to make image correction, not both at the same time. These settings "
+ "do not influence the main smoothing process controlled by the <b>Details</b> "
+ "settings."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label8 = new TQLabel(i18n("Gamma:"), secondPage);
+
+ m_gammaInput = new RDoubleNumInput(secondPage);
+ m_gammaInput->setPrecision(1);
+ m_gammaInput->setRange(0.3, 3.0, 0.1);
+ m_gammaInput->setDefaultValue(1.4);
+ TQWhatsThis::add( m_gammaInput, i18n("<p><b>Gamma</b>: this control sets the gamma tolerance of the image. This value "
+ "can be used to increase the tolerance values for darker areas (which commonly "
+ "are noisier). This results in more blur for shadow areas."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label9 = new TQLabel(i18n("Damping:"), secondPage);
+
+ m_dampingInput = new RDoubleNumInput(secondPage);
+ m_dampingInput->setPrecision(1);
+ m_dampingInput->setRange(0.5, 20.0, 0.5);
+ m_dampingInput->setDefaultValue(5.0);
+ TQWhatsThis::add( m_dampingInput, i18n("<p><b>Damping</b>: this control sets the phase-jitter damping adjustment. "
+ "This value defines how fast the adaptive filter-radius reacts to luminance "
+ "variations. If increased, then edges appear smoother; if too high, then blur "
+ "may occur. If at minimum, then noise and phase jitter at the edges can occur. It "
+ "can suppress spike noise when increased, and this is the preferred method to "
+ "remove it."));
+
+ grid2->addMultiCellWidget(label2, 0, 0, 0, 0);
+ grid2->addMultiCellWidget(m_lumToleranceInput, 0, 0, 1, 1);
+ grid2->addMultiCellWidget(label6, 1, 1, 0, 0);
+ grid2->addMultiCellWidget(m_csmoothInput, 1, 1, 1, 1);
+ grid2->addMultiCellWidget(label8, 2, 2, 0, 0);
+ grid2->addMultiCellWidget(m_gammaInput, 2, 2, 1, 1);
+ grid2->addMultiCellWidget(label9, 3, 3, 0, 0);
+ grid2->addMultiCellWidget(m_dampingInput, 3, 3, 1, 1);
+ grid2->setMargin(m_gboxSettings->spacingHint());
+ grid2->setSpacing(m_gboxSettings->spacingHint());
+ grid2->setColStretch(1, 10);
+ grid2->setRowStretch(4, 10);
+
+ mainTab->addTab( secondPage, i18n("Advanced") );
+
+ grid->addMultiCellWidget(mainTab, 0, 0, 0, 1);
+ grid->setRowStretch(1, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "noisereduction Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+}
+
+NoiseReductionTool::~NoiseReductionTool()
+{
+}
+
+void NoiseReductionTool::renderingFinished()
+{
+ m_radiusInput->setEnabled(true);
+ m_lumToleranceInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ m_textureInput->setEnabled(true);
+ m_sharpnessInput->setEnabled(true);
+ m_csmoothInput->setEnabled(true);
+ m_lookaheadInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+ m_dampingInput->setEnabled(true);
+ m_phaseInput->setEnabled(true);
+}
+
+void NoiseReductionTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("noisereduction Tool");
+
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+
+ m_radiusInput->setValue(config->readDoubleNumEntry("RadiusAjustment", m_radiusInput->defaultValue()));
+ m_lumToleranceInput->setValue(config->readDoubleNumEntry("LumToleranceAjustment", m_lumToleranceInput->defaultValue()));
+ m_thresholdInput->setValue(config->readDoubleNumEntry("ThresholdAjustment", m_thresholdInput->defaultValue()));
+ m_textureInput->setValue(config->readDoubleNumEntry("TextureAjustment", m_textureInput->defaultValue()));
+ m_sharpnessInput->setValue(config->readDoubleNumEntry("SharpnessAjustment", m_sharpnessInput->defaultValue()));
+ m_csmoothInput->setValue(config->readDoubleNumEntry("CsmoothAjustment", m_csmoothInput->defaultValue()));
+ m_lookaheadInput->setValue(config->readDoubleNumEntry("LookAheadAjustment", m_lookaheadInput->defaultValue()));
+ m_gammaInput->setValue(config->readDoubleNumEntry("GammaAjustment", m_gammaInput->defaultValue()));
+ m_dampingInput->setValue(config->readDoubleNumEntry("DampingAjustment", m_dampingInput->defaultValue()));
+ m_phaseInput->setValue(config->readDoubleNumEntry("PhaseAjustment", m_phaseInput->defaultValue()));
+
+ m_radiusInput->setEnabled(true);
+ m_lumToleranceInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ m_textureInput->setEnabled(true);
+ m_sharpnessInput->setEnabled(true);
+ m_csmoothInput->setEnabled(true);
+ m_lookaheadInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+ m_dampingInput->setEnabled(true);
+ m_phaseInput->setEnabled(true);
+}
+
+void NoiseReductionTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("noisereduction Tool");
+ config->writeEntry("RadiusAjustment", m_radiusInput->value());
+ config->writeEntry("LumToleranceAjustment", m_lumToleranceInput->value());
+ config->writeEntry("ThresholdAjustment", m_thresholdInput->value());
+ config->writeEntry("TextureAjustment", m_textureInput->value());
+ config->writeEntry("SharpnessAjustment", m_sharpnessInput->value());
+ config->writeEntry("CsmoothAjustment", m_csmoothInput->value());
+ config->writeEntry("LookAheadAjustment", m_lookaheadInput->value());
+ config->writeEntry("GammaAjustment", m_gammaInput->value());
+ config->writeEntry("DampingAjustment", m_dampingInput->value());
+ config->writeEntry("PhaseAjustment", m_phaseInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void NoiseReductionTool::slotResetSettings()
+{
+ m_radiusInput->setEnabled(true);
+ m_lumToleranceInput->setEnabled(true);
+ m_thresholdInput->setEnabled(true);
+ m_textureInput->setEnabled(true);
+ m_sharpnessInput->setEnabled(true);
+ m_csmoothInput->setEnabled(true);
+ m_lookaheadInput->setEnabled(true);
+ m_gammaInput->setEnabled(true);
+ m_dampingInput->setEnabled(true);
+ m_phaseInput->setEnabled(true);
+
+ m_radiusInput->slotReset();
+ m_lumToleranceInput->slotReset();
+ m_thresholdInput->slotReset();
+ m_textureInput->slotReset();
+ m_sharpnessInput->slotReset();
+ m_csmoothInput->slotReset();
+ m_lookaheadInput->slotReset();
+ m_gammaInput->slotReset();
+ m_dampingInput->slotReset();
+ m_phaseInput->slotReset();
+
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+}
+
+void NoiseReductionTool::prepareEffect()
+{
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+
+ double r = m_radiusInput->value();
+ double l = m_lumToleranceInput->value();
+ double th = m_thresholdInput->value();
+ double tx = m_textureInput->value();
+ double s = m_sharpnessInput->value();
+ double c = m_csmoothInput->value();
+ double a = m_lookaheadInput->value();
+ double g = m_gammaInput->value();
+ double d = m_dampingInput->value();
+ double p = m_phaseInput->value();
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new NoiseReduction(&image, this, r, l, th, tx, s, c, a, g, d, p)));
+}
+
+void NoiseReductionTool::prepareFinal()
+{
+ m_radiusInput->setEnabled(false);
+ m_lumToleranceInput->setEnabled(false);
+ m_thresholdInput->setEnabled(false);
+ m_textureInput->setEnabled(false);
+ m_sharpnessInput->setEnabled(false);
+ m_csmoothInput->setEnabled(false);
+ m_lookaheadInput->setEnabled(false);
+ m_gammaInput->setEnabled(false);
+ m_dampingInput->setEnabled(false);
+ m_phaseInput->setEnabled(false);
+
+ double r = m_radiusInput->value();
+ double l = m_lumToleranceInput->value();
+ double th = m_thresholdInput->value();
+ double tx = m_textureInput->value();
+ double s = m_sharpnessInput->value();
+ double c = m_csmoothInput->value();
+ double a = m_lookaheadInput->value();
+ double g = m_gammaInput->value();
+ double d = m_dampingInput->value();
+ double p = m_phaseInput->value();
+
+ ImageIface iface(0, 0);
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new NoiseReduction(iface.getOriginalImg(), this, r, l, th, tx, s, c, a, g, d, p)));
+}
+
+void NoiseReductionTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void NoiseReductionTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Noise Reduction"), filter()->getTargetImage().bits());
+}
+
+void NoiseReductionTool::slotLoadSettings()
+{
+ KURL loadRestorationFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Noise Reduction Settings File to Load")) );
+ if ( loadRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(loadRestorationFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+ if ( stream.readLine() != "# Photograph Noise Reduction Configuration File" )
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("\"%1\" is not a Photograph Noise Reduction settings text file.")
+ .arg(loadRestorationFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+ m_radiusInput->setValue( stream.readLine().toDouble() );
+ m_lumToleranceInput->setValue( stream.readLine().toDouble() );
+ m_thresholdInput->setValue( stream.readLine().toDouble() );
+ m_textureInput->setValue( stream.readLine().toDouble() );
+ m_sharpnessInput->setValue( stream.readLine().toDouble() );
+ m_csmoothInput->setValue( stream.readLine().toDouble() );
+ m_lookaheadInput->setValue( stream.readLine().toDouble() );
+ m_gammaInput->setValue( stream.readLine().toDouble() );
+ m_dampingInput->setValue( stream.readLine().toDouble() );
+ m_phaseInput->setValue( stream.readLine().toDouble() );
+ blockSignals(false);
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load settings from the Photograph Noise Reduction text file."));
+
+ file.close();
+}
+
+void NoiseReductionTool::slotSaveAsSettings()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Noise Reduction Settings File to Save")) );
+ if ( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# Photograph Noise Reduction Configuration File\n";
+ stream << m_radiusInput->value() << "\n";
+ stream << m_lumToleranceInput->value() << "\n";
+ stream << m_thresholdInput->value() << "\n";
+ stream << m_textureInput->value() << "\n";
+ stream << m_sharpnessInput->value() << "\n";
+ stream << m_csmoothInput->value() << "\n";
+ stream << m_lookaheadInput->value() << "\n";
+ stream << m_gammaInput->value() << "\n";
+ stream << m_dampingInput->value() << "\n";
+ stream << m_phaseInput->value() << "\n";
+
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save settings to the Photograph Noise Reduction text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamNoiseReductionImagesPlugin
diff --git a/src/imageplugins/noisereduction/noisereductiontool.h b/src/imageplugins/noisereduction/noisereductiontool.h
new file mode 100644
index 00000000..8d7d5828
--- /dev/null
+++ b/src/imageplugins/noisereduction/noisereductiontool.h
@@ -0,0 +1,92 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-24
+ * Description : a plugin to reduce CCD noise.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef NOISEREDUCTIONTOOL_H
+#define NOISEREDUCTIONTOOL_H
+
+// Local includes.
+
+#include "editortool.h"
+
+namespace KDcrawIface
+{
+class RDoubleNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamNoiseReductionImagesPlugin
+{
+
+class NoiseReductionTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ NoiseReductionTool(TQObject* parent);
+ ~NoiseReductionTool();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private slots:
+
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+ void slotResetSettings();
+
+private:
+
+ KDcrawIface::RDoubleNumInput *m_radiusInput;
+ KDcrawIface::RDoubleNumInput *m_lumToleranceInput;
+ KDcrawIface::RDoubleNumInput *m_thresholdInput;
+ KDcrawIface::RDoubleNumInput *m_textureInput;
+ KDcrawIface::RDoubleNumInput *m_sharpnessInput;
+
+ KDcrawIface::RDoubleNumInput *m_csmoothInput;
+ KDcrawIface::RDoubleNumInput *m_lookaheadInput;
+ KDcrawIface::RDoubleNumInput *m_gammaInput;
+ KDcrawIface::RDoubleNumInput *m_dampingInput;
+ KDcrawIface::RDoubleNumInput *m_phaseInput;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamNoiseReductionImagesPlugin
+
+#endif /* NOISEREDUCTIONTOOL_H */
diff --git a/src/imageplugins/oilpaint/Makefile.am b/src/imageplugins/oilpaint/Makefile.am
new file mode 100644
index 00000000..f50d7408
--- /dev/null
+++ b/src/imageplugins/oilpaint/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_oilpaint_la_SOURCES = imageplugin_oilpaint.cpp \
+ oilpainttool.cpp oilpaint.cpp
+
+digikamimageplugin_oilpaint_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_oilpaint_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_oilpaint.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_oilpaint.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_oilpaint_ui.rc
+
diff --git a/src/imageplugins/oilpaint/digikamimageplugin_oilpaint.desktop b/src/imageplugins/oilpaint/digikamimageplugin_oilpaint.desktop
new file mode 100644
index 00000000..875c7f16
--- /dev/null
+++ b/src/imageplugins/oilpaint/digikamimageplugin_oilpaint.desktop
@@ -0,0 +1,51 @@
+[Desktop Entry]
+Name=ImagePlugin_OilPaint
+Name[bg]=Приставка за снимки - Маслени бои
+Name[da]=Billedplugin_Oliemaling
+Name[el]=ΠρόσθετοΕικόνας_Ελαιογραφίας
+Name[fi]=Öljyvärimaalaus
+Name[hr]=Uljana slika
+Name[it]=PluginImmagini_PitturaAOlio
+Name[nl]=Afbeeldingsplugin_Olieverf
+Name[sr]=Слика у уљу
+Name[sr@Latn]=Slika u ulju
+Name[sv]=Insticksprogram för oljemålning
+Name[tr]=ResimEklentisi_YağlıBoya
+Name[xx]=xxImagePlugin_OilPaintxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Oil paint image effect plugin for digiKam
+Comment[bg]=Приставка на digiKam за наподобяване на картина с маслени бои
+Comment[ca]=Connector pel digiKam d'efecte de pintura a l'oli
+Comment[da]=Plugin med oliemalingeffekt på billeder i Digikam
+Comment[de]=digiKam-Modul zum Erzeugen eines Ölgemäldeeffektes
+Comment[el]=Πρόσθετο ελαιογραφίας για το digiKam
+Comment[es]=Plugin para digiKam con efectos de pintura al óleo
+Comment[et]=DigiKami õlimaali pildiefektiplugin
+Comment[fa]=وصلۀ جلوۀ تصویر رنگ روغن برای digiKam
+Comment[fi]=Jäljittelee öljyvärimaalausta
+Comment[gl]=Un plugin de digiKam para simular unha pintura ao óleo
+Comment[hr]=digiKam dodatak za efekt uljane slike
+Comment[is]=Íforrit fyrir digiKam sem líkir eftir olíumálun
+Comment[it]=Plugin per l'effetto di pittura a olio delle immagini per digiKam
+Comment[ja]=digiKam 油絵効果プラグイン
+Comment[ms]=Plugin kesan imej cat minyak untuk digiKam
+Comment[nds]=digiKam-Moduul för Öölbildeffekten
+Comment[nl]=Digikam-plugin voor olieverfafbeeldingen
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਤੇਲ ਪੇਂਟ ਚਿੱਤਰ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam zmniejszająca szum
+Comment[pt]=Um 'plugin' do digiKam para simular uma pintura a óleo
+Comment[pt_BR]=Plugin de efeito de condensar cor da imagem
+Comment[ru]=Модуль эффект "маслянной краски" для digiKam
+Comment[sk]=digiKam plugin pre efekt olejomaľby
+Comment[sr]=digiKam-ов прикључак за ефекат слике у уљу
+Comment[sr@Latn]=digiKam-ov priključak za efekat slike u ulju
+Comment[sv]=Digikam insticksprogram för oljemålningsbildeffekt
+Comment[tr]=digiKam için resmi yağlıboyaya benzetme eklentisi
+Comment[uk]=Втулок створення ефекту олійних фарб для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng ảnh sơn dầu cho digiKam
+Comment[xx]=xxOil paint image effect plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_oilpaint
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/oilpaint/digikamimageplugin_oilpaint_ui.rc b/src/imageplugins/oilpaint/digikamimageplugin_oilpaint_ui.rc
new file mode 100644
index 00000000..864c09db
--- /dev/null
+++ b/src/imageplugins/oilpaint/digikamimageplugin_oilpaint_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_oilpaint" >
+
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_oilpaint" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_oilpaint" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/oilpaint/imageeffect_oilpaint.cpp b/src/imageplugins/oilpaint/imageeffect_oilpaint.cpp
new file mode 100644
index 00000000..da07be05
--- /dev/null
+++ b/src/imageplugins/oilpaint/imageeffect_oilpaint.cpp
@@ -0,0 +1,201 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-25
+ * Description : a plugin to simulate Oil Painting
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqimage.h>
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "oilpaint.h"
+#include "imageeffect_oilpaint.h"
+#include "imageeffect_oilpaint.moc"
+
+namespace DigikamOilPaintImagesPlugin
+{
+
+ImageEffect_OilPaint::ImageEffect_OilPaint(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Apply Oil Paint Effect"),
+ "oilpaint", false, false, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Oil Paint"),
+ digikam_version,
+ I18N_NOOP("An oil painting image effect plugin for digiKam."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://wwww.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pieter Z. Voloshyn", I18N_NOOP("Oil paint algorithm"),
+ "pieter dot voloshyn at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 3, 1, 0, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Brush size:"), gboxSettings);
+ m_brushSizeInput = new KIntNumInput(gboxSettings);
+ m_brushSizeInput->setRange(1, 5, 1, true);
+ TQWhatsThis::add( m_brushSizeInput, i18n("<p>Set here the brush size to use for "
+ "simulating the oil painting.") );
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_brushSizeInput, 1, 1, 0, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Smooth:"), gboxSettings);
+ m_smoothInput = new KIntNumInput(gboxSettings);
+ m_smoothInput->setRange(10, 255, 1, true);
+ TQWhatsThis::add( m_smoothInput, i18n("<p>This value controls the smoothing effect "
+ "of the brush under the canvas.") );
+
+ gridSettings->addMultiCellWidget(label2, 2, 2, 0, 1);
+ gridSettings->addMultiCellWidget(m_smoothInput, 3, 3, 0, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_brushSizeInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_smoothInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_OilPaint::~ImageEffect_OilPaint()
+{
+}
+
+void ImageEffect_OilPaint::renderingFinished()
+{
+ m_brushSizeInput->setEnabled(true);
+ m_smoothInput->setEnabled(true);
+}
+
+void ImageEffect_OilPaint::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("oilpaint Tool Dialog");
+ m_brushSizeInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+ m_brushSizeInput->setValue(config->readNumEntry("BrushSize", 1));
+ m_smoothInput->setValue(config->readNumEntry("SmoothAjustment", 30));
+ m_brushSizeInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void ImageEffect_OilPaint::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("oilpaint Tool Dialog");
+ config->writeEntry("BrushSize", m_brushSizeInput->value());
+ config->writeEntry("SmoothAjustment", m_smoothInput->value());
+ config->sync();
+}
+
+void ImageEffect_OilPaint::resetValues()
+{
+ m_brushSizeInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+ m_brushSizeInput->setValue(1);
+ m_smoothInput->setValue(30);
+ m_brushSizeInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void ImageEffect_OilPaint::prepareEffect()
+{
+ m_brushSizeInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+
+ int b = m_brushSizeInput->value();
+ int s = m_smoothInput->value();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new OilPaint(&image, this, b, s));
+}
+
+void ImageEffect_OilPaint::prepareFinal()
+{
+ m_brushSizeInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ int b = m_brushSizeInput->value();
+ int s = m_smoothInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(new OilPaint(iface.getOriginalImg(), this, b, s));
+}
+
+void ImageEffect_OilPaint::putPreviewData(void)
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_OilPaint::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Oil Paint"),
+ m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamOilPaintImagesPlugin
+
diff --git a/src/imageplugins/oilpaint/imageeffect_oilpaint.h b/src/imageplugins/oilpaint/imageeffect_oilpaint.h
new file mode 100644
index 00000000..527cc2ef
--- /dev/null
+++ b/src/imageplugins/oilpaint/imageeffect_oilpaint.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-25
+ * Description : a plugin to simulate Oil Painting
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_OILPAINT_H
+#define IMAGEEFFECT_OILPAINT_H
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+class KIntNumInput;
+
+namespace DigikamOilPaintImagesPlugin
+{
+
+class ImageEffect_OilPaint : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_OilPaint(TQWidget* parent);
+ ~ImageEffect_OilPaint();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KIntNumInput *m_brushSizeInput;
+ KIntNumInput *m_smoothInput;
+};
+
+} // NameSpace DigikamOilPaintImagesPlugin
+
+#endif /* IMAGEEFFECT_OILPAINT_H */
diff --git a/src/imageplugins/oilpaint/imageplugin_oilpaint.cpp b/src/imageplugins/oilpaint/imageplugin_oilpaint.cpp
new file mode 100644
index 00000000..740e89fc
--- /dev/null
+++ b/src/imageplugins/oilpaint/imageplugin_oilpaint.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-25
+ * Description : a plugin to simulate Oil Painting
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "oilpainttool.h"
+#include "imageplugin_oilpaint.h"
+#include "imageplugin_oilpaint.moc"
+
+using namespace DigikamOilPaintImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_oilpaint,
+ KGenericFactory<ImagePlugin_OilPaint>("digikamimageplugin_oilpaint"));
+
+ImagePlugin_OilPaint::ImagePlugin_OilPaint(TQObject *parent, const char*, const TQStringList&)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_OilPaint")
+{
+ m_oilpaintAction = new TDEAction(i18n("Oil Paint..."), "oilpaint", 0,
+ this, TQ_SLOT(slotOilPaint()),
+ actionCollection(), "imageplugin_oilpaint");
+
+ setXMLFile( "digikamimageplugin_oilpaint_ui.rc" );
+
+ DDebug() << "ImagePlugin_OilPaint plugin loaded" << endl;
+}
+
+ImagePlugin_OilPaint::~ImagePlugin_OilPaint()
+{
+}
+
+void ImagePlugin_OilPaint::setEnabledActions(bool enable)
+{
+ m_oilpaintAction->setEnabled(enable);
+}
+
+void ImagePlugin_OilPaint::slotOilPaint()
+{
+ OilPaintTool *tool = new OilPaintTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/oilpaint/imageplugin_oilpaint.h b/src/imageplugins/oilpaint/imageplugin_oilpaint.h
new file mode 100644
index 00000000..032ee065
--- /dev/null
+++ b/src/imageplugins/oilpaint/imageplugin_oilpaint.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-25
+ * Description : a plugin to simulate Oil Painting
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_OILPAINT_H
+#define IMAGEPLUGIN_OILPAINT_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_OilPaint : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_OilPaint(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_OilPaint();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotOilPaint();
+
+private:
+
+ TDEAction *m_oilpaintAction;
+};
+
+#endif /* IMAGEPLUGIN_OILPAINT_H */
diff --git a/src/imageplugins/oilpaint/oilpaint.cpp b/src/imageplugins/oilpaint/oilpaint.cpp
new file mode 100644
index 00000000..be9697fa
--- /dev/null
+++ b/src/imageplugins/oilpaint/oilpaint.cpp
@@ -0,0 +1,203 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Oil Painting threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original OilPaint algorithm copyrighted 2004 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimggaussianblur.h"
+#include "dimgimagefilters.h"
+#include "oilpaint.h"
+
+namespace DigikamOilPaintImagesPlugin
+{
+
+OilPaint::OilPaint(Digikam::DImg *orgImage, TQObject *parent, int brushSize, int smoothness)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "OilPaint")
+{
+ m_brushSize = brushSize;
+ m_smoothness = smoothness;
+ initFilter();
+}
+
+void OilPaint::filterImage(void)
+{
+ oilpaintImage(m_orgImage, m_destImage, m_brushSize, m_smoothness);
+}
+
+// This method have been ported from Pieter Z. Voloshyn algorithm code.
+
+/* Function to apply the OilPaint effect.
+ *
+ * data => The image data in RGBA mode.
+ * w => Width of image.
+ * h => Height of image.
+ * BrushSize => Brush size.
+ * Smoothness => Smooth value.
+ *
+ * Theory => Using MostFrequentColor function we take the main color in
+ * a matrix and simply write at the original position.
+ */
+
+void OilPaint::oilpaintImage(Digikam::DImg &orgImage, Digikam::DImg &destImage, int BrushSize, int Smoothness)
+{
+ int progress;
+ Digikam::DColor mostFrequentColor;
+ int w,h;
+
+ mostFrequentColor.setSixteenBit(orgImage.sixteenBit());
+ w = (int)orgImage.width();
+ h = (int)orgImage.height();
+ uchar *dest = destImage.bits();
+ int bytesDepth = orgImage.bytesDepth();
+ uchar *dptr;
+
+ // Allocate some arrays to be used.
+ // Do this here once for all to save a few million new / delete operations
+ m_intensityCount = new uchar[Smoothness + 1];
+ m_averageColorR = new uint[Smoothness + 1];
+ m_averageColorG = new uint[Smoothness + 1];
+ m_averageColorB = new uint[Smoothness + 1];
+
+ for (int h2 = 0; !m_cancel && (h2 < h); h2++)
+ {
+ for (int w2 = 0; !m_cancel && (w2 < w); w2++)
+ {
+ mostFrequentColor = MostFrequentColor(orgImage, w2, h2, BrushSize, Smoothness);
+ dptr = dest + w2*bytesDepth + (w*h2*bytesDepth);
+ mostFrequentColor.setPixel(dptr);
+ }
+
+ progress = (int) (((double)h2 * 100.0) / h);
+ if ( progress%5 == 0 )
+ postProgress( progress );
+ }
+
+ // free all the arrays
+ delete [] m_intensityCount;
+ delete [] m_averageColorR;
+ delete [] m_averageColorG;
+ delete [] m_averageColorB;
+}
+
+// This method have been ported from Pieter Z. Voloshyn algorithm code.
+
+/* Function to determine the most frequent color in a matrix
+ *
+ * Bits => Bits array
+ * Width => Image width
+ * Height => Image height
+ * X => Position horizontal
+ * Y => Position vertical
+ * Radius => Is the radius of the matrix to be analized
+ * Intensity => Intensity to calcule
+ *
+ * Theory => This function creates a matrix with the analized pixel in
+ * the center of this matrix and find the most frequenty color
+ */
+
+Digikam::DColor OilPaint::MostFrequentColor(Digikam::DImg &src, int X, int Y, int Radius, int Intensity)
+{
+ int i, w, h, I, Width, Height;
+ uint red, green, blue;
+
+ uchar *dest = src.bits();
+ int bytesDepth = src.bytesDepth();
+ uchar *sptr;
+ bool sixteenBit = src.sixteenBit();
+
+ Digikam::DColor mostFrequentColor;
+
+ double Scale = Intensity / (sixteenBit ? 65535.0 : 255.0);
+ Width = (int)src.width();
+ Height = (int)src.height();
+
+ // Erase the array
+ memset(m_intensityCount, 0, (Intensity + 1) * sizeof (uchar));
+
+ for (w = X - Radius; w <= X + Radius; w++)
+ {
+ for (h = Y - Radius; h <= Y + Radius; h++)
+ {
+ // This condition helps to identify when a point doesn't exist
+
+ if ((w >= 0) && (w < Width) && (h >= 0) && (h < Height))
+ {
+ sptr = dest + w*bytesDepth + (Width*h*bytesDepth);
+ Digikam::DColor color(sptr, sixteenBit);
+ red = (uint)color.red();
+ green = (uint)color.green();
+ blue = (uint)color.blue();
+
+ I = lround(GetIntensity (red, green, blue) * Scale);
+ m_intensityCount[I]++;
+
+ if (m_intensityCount[I] == 1)
+ {
+ m_averageColorR[I] = red;
+ m_averageColorG[I] = green;
+ m_averageColorB[I] = blue;
+ }
+ else
+ {
+ m_averageColorR[I] += red;
+ m_averageColorG[I] += green;
+ m_averageColorB[I] += blue;
+ }
+ }
+ }
+ }
+
+ I = 0;
+ int MaxInstance = 0;
+
+ for (i = 0 ; i <= Intensity ; i++)
+ {
+ if (m_intensityCount[i] > MaxInstance)
+ {
+ I = i;
+ MaxInstance = m_intensityCount[i];
+ }
+ }
+
+ // get Alpha channel value from original (unchanged)
+ mostFrequentColor = src.getPixelColor(X, Y);
+
+ // Overwrite RGB values to destination.
+ mostFrequentColor.setRed(m_averageColorR[I] / MaxInstance);
+ mostFrequentColor.setGreen(m_averageColorG[I] / MaxInstance);
+ mostFrequentColor.setBlue(m_averageColorB[I] / MaxInstance);
+
+ return mostFrequentColor;
+}
+
+} // NameSpace DigikamOilPaintImagesPlugin
diff --git a/src/imageplugins/oilpaint/oilpaint.h b/src/imageplugins/oilpaint/oilpaint.h
new file mode 100644
index 00000000..998522a5
--- /dev/null
+++ b/src/imageplugins/oilpaint/oilpaint.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Oil Painting threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef OILPAINT_H
+#define OILPAINT_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamOilPaintImagesPlugin
+{
+
+class OilPaint : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ OilPaint(Digikam::DImg *orgImage, TQObject *parent=0, int brushSize=1, int smoothness=30);
+
+ ~OilPaint(){};
+
+private:
+
+ virtual void filterImage(void);
+
+ void oilpaintImage(Digikam::DImg &orgImage, Digikam::DImg &destImage, int BrushSize, int Smoothness);
+
+ Digikam::DColor MostFrequentColor (Digikam::DImg &src,
+ int X, int Y, int Radius, int Intensity);
+
+ // Function to calculate the color intensity and return the luminance (Y)
+ // component of YIQ color model.
+ inline double GetIntensity(uint Red, uint Green, uint Blue)
+ { return Red * 0.3 + Green * 0.59 + Blue * 0.11; };
+
+private:
+
+ uchar *m_intensityCount;
+
+ int m_brushSize;
+ int m_smoothness;
+
+ uint *m_averageColorR;
+ uint *m_averageColorG;
+ uint *m_averageColorB;
+};
+
+} // NameSpace DigikamOilPaintImagesPlugin
+
+#endif /* OILPAINT_H */
diff --git a/src/imageplugins/oilpaint/oilpainttool.cpp b/src/imageplugins/oilpaint/oilpainttool.cpp
new file mode 100644
index 00000000..25bea302
--- /dev/null
+++ b/src/imageplugins/oilpaint/oilpainttool.cpp
@@ -0,0 +1,208 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-25
+ * Description : a plugin to simulate Oil Painting
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "oilpaint.h"
+#include "oilpainttool.h"
+#include "oilpainttool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamOilPaintImagesPlugin
+{
+
+OilPaintTool::OilPaintTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("oilpaint");
+ setToolName(i18n("Oil Paint"));
+ setToolIcon(SmallIcon("oilpaint"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 4, 1);
+
+ TQLabel *label1 = new TQLabel(i18n("Brush size:"), m_gboxSettings->plainPage());
+ m_brushSizeInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_brushSizeInput->setRange(1, 5, 1);
+ m_brushSizeInput->setDefaultValue(1);
+ TQWhatsThis::add( m_brushSizeInput, i18n("<p>Set here the brush size to use for "
+ "simulating the oil painting.") );
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Smooth:"), m_gboxSettings->plainPage());
+ m_smoothInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_smoothInput->setRange(10, 255, 1);
+ m_smoothInput->setDefaultValue(30);
+ TQWhatsThis::add( m_smoothInput, i18n("<p>This value controls the smoothing effect "
+ "of the brush under the canvas.") );
+
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 1);
+ grid->addMultiCellWidget(m_brushSizeInput, 1, 1, 0, 1);
+ grid->addMultiCellWidget(label2, 2, 2, 0, 1);
+ grid->addMultiCellWidget(m_smoothInput, 3, 3, 0, 1);
+ grid->setRowStretch(4, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "oilpaint Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ // this filter is relative slow, so we should use the try button instead right now
+
+ // connect(m_brushSizeInput, TQ_SIGNAL(valueChanged (int)),
+ // this, TQ_SLOT(slotTimer()));
+ //
+ // connect(m_smoothInput, TQ_SIGNAL(valueChanged (int)),
+ // this, TQ_SLOT(slotTimer()));
+}
+
+OilPaintTool::~OilPaintTool()
+{
+}
+
+void OilPaintTool::renderingFinished()
+{
+ m_brushSizeInput->setEnabled(true);
+ m_smoothInput->setEnabled(true);
+}
+
+void OilPaintTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("oilpaint Tool");
+ m_brushSizeInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+
+ m_brushSizeInput->setValue(config->readNumEntry("BrushSize", m_brushSizeInput->defaultValue()));
+ m_smoothInput->setValue(config->readNumEntry("SmoothAjustment", m_smoothInput->defaultValue()));
+
+ m_brushSizeInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void OilPaintTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("oilpaint Tool");
+ config->writeEntry("BrushSize", m_brushSizeInput->value());
+ config->writeEntry("SmoothAjustment", m_smoothInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void OilPaintTool::slotResetSettings()
+{
+ m_brushSizeInput->blockSignals(true);
+ m_smoothInput->blockSignals(true);
+
+ m_brushSizeInput->slotReset();
+ m_smoothInput->slotReset();
+
+ m_brushSizeInput->blockSignals(false);
+ m_smoothInput->blockSignals(false);
+}
+
+void OilPaintTool::prepareEffect()
+{
+ m_brushSizeInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+
+ int b = m_brushSizeInput->value();
+ int s = m_smoothInput->value();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new OilPaint(&image, this, b, s)));
+}
+
+void OilPaintTool::prepareFinal()
+{
+ m_brushSizeInput->setEnabled(false);
+ m_smoothInput->setEnabled(false);
+
+ int b = m_brushSizeInput->value();
+ int s = m_smoothInput->value();
+
+ ImageIface iface(0, 0);
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new OilPaint(iface.getOriginalImg(), this, b, s)));
+}
+
+void OilPaintTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void OilPaintTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Oil Paint"), filter()->getTargetImage().bits());
+}
+
+} // NameSpace DigikamOilPaintImagesPlugin
+
diff --git a/src/imageplugins/oilpaint/oilpainttool.h b/src/imageplugins/oilpaint/oilpainttool.h
new file mode 100644
index 00000000..9c488a57
--- /dev/null
+++ b/src/imageplugins/oilpaint/oilpainttool.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-25
+ * Description : a plugin to simulate Oil Painting
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef OILPAINTTOOL_H
+#define OILPAINTTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamOilPaintImagesPlugin
+{
+
+class OilPaintTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ OilPaintTool(TQObject* parent);
+ ~OilPaintTool();
+
+private slots:
+
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KDcrawIface::RIntNumInput *m_brushSizeInput;
+ KDcrawIface::RIntNumInput *m_smoothInput;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamOilPaintImagesPlugin
+
+#endif /* OILPAINTTOOL_H */
diff --git a/src/imageplugins/perspective/Makefile.am b/src/imageplugins/perspective/Makefile.am
new file mode 100644
index 00000000..028add37
--- /dev/null
+++ b/src/imageplugins/perspective/Makefile.am
@@ -0,0 +1,35 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_perspective_la_SOURCES = imageplugin_perspective.cpp \
+ perspectivetool.cpp \
+ perspectivewidget.cpp triangle.cpp matrix.cpp
+
+digikamimageplugin_perspective_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_perspective_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_perspective.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_perspective.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_perspective_ui.rc
+
diff --git a/src/imageplugins/perspective/digikamimageplugin_perspective.desktop b/src/imageplugins/perspective/digikamimageplugin_perspective.desktop
new file mode 100644
index 00000000..a6fcc4d3
--- /dev/null
+++ b/src/imageplugins/perspective/digikamimageplugin_perspective.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_Perspective
+Name[bg]=Приставка за снимки - Перспектива
+Name[da]=Billedplugin_Perspektivværktøj
+Name[el]=ΠρόσθετοΕικόνας_Προοπτική
+Name[fi]=Perspektiivimuunnos
+Name[hr]=Perspektiva
+Name[it]=PluginImmagini_Prospettiva
+Name[nl]=Afbeeldingsplugin_Perspectief
+Name[sr]=Перспектива
+Name[sr@Latn]=Perspektiva
+Name[sv]=Insticksprogram för perspektiv
+Name[tr]=ResimEklentisi_Perspektif
+Name[xx]=xxImagePlugin_Perspectivexx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Perspective tool plugin for digiKam
+Comment[bg]=Приставка на digiKam за промяна на перспективата
+Comment[ca]=Connector pel digiKam d'eina de perspectiva
+Comment[da]=Perspektivværktøjs-plugin for Digikam
+Comment[de]=digiKam-Modul zum Justieren der Perspektive eines Bildes
+Comment[el]=Πρόσθετο εργαλείο προοπτικής για το digiKam
+Comment[es]=Plugin para digiKam de herramientas de perspectiva
+Comment[et]=DigiKami perspektiiviplugin
+Comment[fa]=وصلۀ ابزار منظره برای digiKam
+Comment[fi]=Vääristää kuvan perspektiiviä
+Comment[fr]=Module externe pour modifier la perspective dans digiKam
+Comment[gl]=Un plugin de digiKam para a ferramenta de perspectiva
+Comment[hr]=digiKam dodatak za perspektivu
+Comment[it]=Plugin per lo strumento di prospettiva per digiKam
+Comment[ja]=digiKam 視点調整プラグイン
+Comment[ms]=Plugin alatan perspektif untuk digiKam
+Comment[nds]=digiKam-Moduul för den Kietwinkel
+Comment[nl]=Digikam-plugin voor perspectiefaanpassing
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਪਰੋਸਪੈਕਟਿਵ ਸੰਦ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam umożliwiająca korekcję perspektywy
+Comment[pt]=Um 'plugin' do digiKam para a ferramenta de perspectiva
+Comment[pt_BR]=Plugin de ferramenta de Perspectiva
+Comment[ru]=Модуль изменения перспективы для digiKam
+Comment[sk]=digiKam plugin pre nástroj s perspektívou
+Comment[sr]=digiKam-ов прикључак за перспективу
+Comment[sr@Latn]=digiKam-ov priključak za perspektivu
+Comment[sv]=Digikam insticksprogram med perspektivverktyg
+Comment[tr]=digiKam için perspektif aracı eklentisi
+Comment[uk]=Втулок засобу побудови перспективи для digiKam
+Comment[vi]=Phần bổ sung công cụ phối cảnh cho digiKam
+Comment[xx]=xxPerspective tool plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_perspective
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/perspective/digikamimageplugin_perspective_ui.rc b/src/imageplugins/perspective/digikamimageplugin_perspective_ui.rc
new file mode 100644
index 00000000..0f3bca57
--- /dev/null
+++ b/src/imageplugins/perspective/digikamimageplugin_perspective_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="4" name="digikamimageplugin_perspective" >
+
+ <MenuBar>
+
+ <Menu name="Transform" ><text>Tra&amp;nsform</text>
+ <Action name="imageplugin_perspective" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_perspective" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/perspective/imageeffect_perspective.cpp b/src/imageplugins/perspective/imageeffect_perspective.cpp
new file mode 100644
index 00000000..15bef877
--- /dev/null
+++ b/src/imageplugins/perspective/imageeffect_perspective.cpp
@@ -0,0 +1,252 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a plugin to change image perspective .
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqspinbox.h>
+#include <tqpushbutton.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <kcolorbutton.h>
+#include <kcursor.h>
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <kseparator.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "perspectivewidget.h"
+#include "imageeffect_perspective.h"
+#include "imageeffect_perspective.moc"
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+ImageEffect_Perspective::ImageEffect_Perspective(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Adjust Photograph Perspective"),
+ "perspective", false, false)
+{
+ TQString whatsThis;
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Perspective"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to process image perspective adjustment."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2006, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(plainPage());
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* l = new TQVBoxLayout(frame, 5, 0);
+ m_previewWidget = new PerspectiveWidget(525, 350, frame);
+ l->addWidget(m_previewWidget);
+ TQWhatsThis::add( m_previewWidget, i18n("<p>This is the perspective transformation operation preview. "
+ "You can use the mouse for dragging the corner to adjust the "
+ "perspective transformation area."));
+ setPreviewAreaWidget(frame);
+
+ // -------------------------------------------------------------
+
+ TQString temp;
+ Digikam::ImageIface iface(0, 0);
+
+ TQWidget *gbox2 = new TQWidget(plainPage());
+ TQGridLayout *gridLayout = new TQGridLayout( gbox2, 13, 2, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("New width:"), gbox2);
+ m_newWidthLabel = new TQLabel(temp.setNum( iface.originalWidth()) + i18n(" px"), gbox2);
+ m_newWidthLabel->setAlignment( AlignBottom | AlignRight );
+
+ TQLabel *label2 = new TQLabel(i18n("New height:"), gbox2);
+ m_newHeightLabel = new TQLabel(temp.setNum( iface.originalHeight()) + i18n(" px"), gbox2);
+ m_newHeightLabel->setAlignment( AlignBottom | AlignRight );
+
+ gridLayout->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridLayout->addMultiCellWidget(m_newWidthLabel, 0, 0, 1, 2);
+ gridLayout->addMultiCellWidget(label2, 1, 1, 0, 0);
+ gridLayout->addMultiCellWidget(m_newHeightLabel, 1, 1, 1, 2);
+
+ // -------------------------------------------------------------
+
+ KSeparator *line = new KSeparator(Horizontal, gbox2);
+
+ TQLabel *angleLabel = new TQLabel(i18n("Angles (in degrees):"), gbox2);
+ TQLabel *label3 = new TQLabel(i18n(" Top left:"), gbox2);
+ m_topLeftAngleLabel = new TQLabel(gbox2);
+ TQLabel *label4 = new TQLabel(i18n(" Top right:"), gbox2);
+ m_topRightAngleLabel = new TQLabel(gbox2);
+ TQLabel *label5 = new TQLabel(i18n(" Bottom left:"), gbox2);
+ m_bottomLeftAngleLabel = new TQLabel(gbox2);
+ TQLabel *label6 = new TQLabel(i18n(" Bottom right:"), gbox2);
+ m_bottomRightAngleLabel = new TQLabel(gbox2);
+
+ gridLayout->addMultiCellWidget(line, 2, 2, 0, 2);
+ gridLayout->addMultiCellWidget(angleLabel, 3, 3, 0, 2);
+ gridLayout->addMultiCellWidget(label3, 4, 4, 0, 0);
+ gridLayout->addMultiCellWidget(m_topLeftAngleLabel, 4, 4, 1, 2);
+ gridLayout->addMultiCellWidget(label4, 5, 5, 0, 0);
+ gridLayout->addMultiCellWidget(m_topRightAngleLabel, 5, 5, 1, 2);
+ gridLayout->addMultiCellWidget(label5, 6, 6, 0, 0);
+ gridLayout->addMultiCellWidget(m_bottomLeftAngleLabel, 6, 6, 1, 2);
+ gridLayout->addMultiCellWidget(label6, 7, 7, 0, 0);
+ gridLayout->addMultiCellWidget(m_bottomRightAngleLabel, 7, 7, 1, 2);
+
+ // -------------------------------------------------------------
+
+ KSeparator *line2 = new KSeparator(Horizontal, gbox2);
+
+ m_drawWhileMovingCheckBox = new TQCheckBox(i18n("Draw preview while moving"), gbox2);
+ gridLayout->addMultiCellWidget(line2, 8, 8, 0, 2);
+ gridLayout->addMultiCellWidget(m_drawWhileMovingCheckBox, 9, 9, 0, 2);
+
+ m_drawGridCheckBox = new TQCheckBox(i18n("Draw grid"), gbox2);
+ gridLayout->addMultiCellWidget(m_drawGridCheckBox, 10, 10, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label7 = new TQLabel(i18n("Guide color:"), gbox2);
+ m_guideColorBt = new KColorButton( TQColor( TQt::red ), gbox2 );
+ TQWhatsThis::add( m_guideColorBt, i18n("<p>Set here the color used to draw guides dashed-lines."));
+ gridLayout->addMultiCellWidget(label7, 11, 11, 0, 0);
+ gridLayout->addMultiCellWidget(m_guideColorBt, 11, 11, 2, 2);
+
+ TQLabel *label8 = new TQLabel(i18n("Guide width:"), gbox2);
+ m_guideSize = new TQSpinBox( 1, 5, 1, gbox2);
+ TQWhatsThis::add( m_guideSize, i18n("<p>Set here the width in pixels used to draw guides dashed-lines."));
+ gridLayout->addMultiCellWidget(label8, 12, 12, 0, 0);
+ gridLayout->addMultiCellWidget(m_guideSize, 12, 12, 2, 2);
+
+ gridLayout->setColStretch(1, 10);
+ gridLayout->setRowStretch(13, 10);
+
+ setUserAreaWidget(gbox2);
+
+ // -------------------------------------------------------------
+
+ connect(m_previewWidget, TQ_SIGNAL(signalPerspectiveChanged(TQRect, float, float, float, float)),
+ this, TQ_SLOT(slotUpdateInfo(TQRect, float, float, float, float)));
+
+ connect(m_drawWhileMovingCheckBox, TQ_SIGNAL(toggled(bool)),
+ m_previewWidget, TQ_SLOT(slotToggleDrawWhileMoving(bool)));
+
+ connect(m_drawGridCheckBox, TQ_SIGNAL(toggled(bool)),
+ m_previewWidget, TQ_SLOT(slotToggleDrawGrid(bool)));
+
+ connect(m_guideColorBt, TQ_SIGNAL(changed(const TQColor &)),
+ m_previewWidget, TQ_SLOT(slotChangeGuideColor(const TQColor &)));
+
+ connect(m_guideSize, TQ_SIGNAL(valueChanged(int)),
+ m_previewWidget, TQ_SLOT(slotChangeGuideSize(int)));
+}
+
+ImageEffect_Perspective::~ImageEffect_Perspective()
+{
+}
+
+void ImageEffect_Perspective::readUserSettings(void)
+{
+ TQColor defaultGuideColor(TQt::red);
+ TDEConfig *config = kapp->config();
+ config->setGroup("perspective Tool Dialog");
+ m_drawWhileMovingCheckBox->setChecked(config->readBoolEntry("Draw While Moving", true));
+ m_drawGridCheckBox->setChecked(config->readBoolEntry("Draw Grid", false));
+ m_guideColorBt->setColor(config->readColorEntry("Guide Color", &defaultGuideColor));
+ m_guideSize->setValue(config->readNumEntry("Guide Width", 1));
+ m_previewWidget->slotToggleDrawWhileMoving(m_drawWhileMovingCheckBox->isChecked());
+ m_previewWidget->slotToggleDrawGrid(m_drawGridCheckBox->isChecked());
+ m_previewWidget->slotChangeGuideColor(m_guideColorBt->color());
+ m_previewWidget->slotChangeGuideSize(m_guideSize->value());
+}
+
+void ImageEffect_Perspective::writeUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("perspective Tool Dialog");
+ config->writeEntry("Draw While Moving", m_drawWhileMovingCheckBox->isChecked());
+ config->writeEntry("Draw Grid", m_drawGridCheckBox->isChecked());
+ config->writeEntry("Guide Color", m_guideColorBt->color());
+ config->writeEntry("Guide Width", m_guideSize->value());
+ config->sync();
+}
+
+void ImageEffect_Perspective::resetValues()
+{
+ m_previewWidget->reset();
+}
+
+void ImageEffect_Perspective::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ m_previewWidget->applyPerspectiveAdjustment();
+ accept();
+ kapp->restoreOverrideCursor();
+}
+
+void ImageEffect_Perspective::slotUpdateInfo(TQRect newSize, float topLeftAngle, float topRightAngle,
+ float bottomLeftAngle, float bottomRightAngle)
+{
+ TQString temp;
+ m_newWidthLabel->setText(temp.setNum( newSize.width()) + i18n(" px") );
+ m_newHeightLabel->setText(temp.setNum( newSize.height()) + i18n(" px") );
+
+ m_topLeftAngleLabel->setText(temp.setNum( topLeftAngle, 'f', 1 ));
+ m_topRightAngleLabel->setText(temp.setNum( topRightAngle, 'f', 1 ));
+ m_bottomLeftAngleLabel->setText(temp.setNum( bottomLeftAngle, 'f', 1 ));
+ m_bottomRightAngleLabel->setText(temp.setNum( bottomRightAngle, 'f', 1 ));
+}
+
+} // NameSpace DigikamPerspectiveImagesPlugin
+
diff --git a/src/imageplugins/perspective/imageeffect_perspective.h b/src/imageplugins/perspective/imageeffect_perspective.h
new file mode 100644
index 00000000..41388aa7
--- /dev/null
+++ b/src/imageplugins/perspective/imageeffect_perspective.h
@@ -0,0 +1,89 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a plugin to change image perspective .
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_PERSPECTIVE_H
+#define IMAGEEFFECT_PERSPECTIVE_H
+
+// TQt includes.
+
+#include <tqrect.h>
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+
+class TQLabel;
+class TQCheckBox;
+class TQSpinBox;
+
+class KColorButton;
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+class PerspectiveWidget;
+
+class ImageEffect_Perspective : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Perspective(TQWidget* parent);
+ ~ImageEffect_Perspective();
+
+private slots:
+
+ void slotUpdateInfo(TQRect newSize, float topLeftAngle, float topRightAngle,
+ float bottomLeftAngle, float bottomRightAngle);
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void finalRendering();
+
+private:
+
+ TQLabel *m_newWidthLabel;
+ TQLabel *m_newHeightLabel;
+ TQLabel *m_topLeftAngleLabel;
+ TQLabel *m_topRightAngleLabel;
+ TQLabel *m_bottomLeftAngleLabel;
+ TQLabel *m_bottomRightAngleLabel;
+
+ TQCheckBox *m_drawWhileMovingCheckBox;
+ TQCheckBox *m_drawGridCheckBox;
+
+ TQSpinBox *m_guideSize;
+
+ KColorButton *m_guideColorBt;
+
+ PerspectiveWidget *m_previewWidget;
+};
+
+} // NameSpace DigikamPerspectiveImagesPlugin
+
+#endif /* IMAGEEFFECT_PERSPECTIVE_H */
diff --git a/src/imageplugins/perspective/imageplugin_perspective.cpp b/src/imageplugins/perspective/imageplugin_perspective.cpp
new file mode 100644
index 00000000..bb5d0444
--- /dev/null
+++ b/src/imageplugins/perspective/imageplugin_perspective.cpp
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a plugin to change image perspective .
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "perspectivetool.h"
+#include "imageplugin_perspective.h"
+#include "imageplugin_perspective.moc"
+
+using namespace DigikamPerspectiveImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_perspective,
+ KGenericFactory<ImagePlugin_Perspective>("digikamimageplugin_perspective"));
+
+ImagePlugin_Perspective::ImagePlugin_Perspective(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_Perspective")
+{
+ m_perspectiveAction = new TDEAction(i18n("Perspective Adjustment..."), "perspective", 0,
+ this, TQ_SLOT(slotPerspective()),
+ actionCollection(), "imageplugin_perspective");
+
+ setXMLFile("digikamimageplugin_perspective_ui.rc");
+
+ DDebug() << "ImagePlugin_Perspective plugin loaded" << endl;
+}
+
+ImagePlugin_Perspective::~ImagePlugin_Perspective()
+{
+}
+
+void ImagePlugin_Perspective::setEnabledActions(bool enable)
+{
+ m_perspectiveAction->setEnabled(enable);
+}
+
+void ImagePlugin_Perspective::slotPerspective()
+{
+ PerspectiveTool *tool = new PerspectiveTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/perspective/imageplugin_perspective.h b/src/imageplugins/perspective/imageplugin_perspective.h
new file mode 100644
index 00000000..5dee7b1b
--- /dev/null
+++ b/src/imageplugins/perspective/imageplugin_perspective.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a plugin to change image perspective .
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_PERSPECTIVE_H
+#define IMAGEPLUGIN_PERSPECTIVE_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Perspective : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Perspective(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_Perspective();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotPerspective();
+
+private:
+
+ TDEAction *m_perspectiveAction;
+};
+
+#endif /* IMAGEPLUGIN_PERSPECTIVE_H */
diff --git a/src/imageplugins/perspective/matrix.cpp b/src/imageplugins/perspective/matrix.cpp
new file mode 100644
index 00000000..65723c56
--- /dev/null
+++ b/src/imageplugins/perspective/matrix.cpp
@@ -0,0 +1,177 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a matrix implementation for image
+ * perspective adjustment.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Matrix3 implementation inspired from gimp 2.0
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstring>
+
+// Local includes.
+
+#include "matrix.h"
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+static double identityMatrix[3][3] = { { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 } };
+
+Matrix::Matrix()
+{
+ memcpy(coeff, identityMatrix, sizeof(coeff));
+}
+
+void Matrix::translate (double x, double y)
+{
+ double g, h, i;
+
+ g = coeff[2][0];
+ h = coeff[2][1];
+ i = coeff[2][2];
+
+ coeff[0][0] += x * g;
+ coeff[0][1] += x * h;
+ coeff[0][2] += x * i;
+ coeff[1][0] += y * g;
+ coeff[1][1] += y * h;
+ coeff[1][2] += y * i;
+}
+
+void Matrix::scale(double x, double y)
+{
+ coeff[0][0] *= x;
+ coeff[0][1] *= x;
+ coeff[0][2] *= x;
+
+ coeff[1][0] *= y;
+ coeff[1][1] *= y;
+ coeff[1][2] *= y;
+}
+
+void Matrix::multiply(const Matrix &matrix)
+{
+ int i, j;
+ Matrix tmp;
+ double t1, t2, t3;
+
+ for (i = 0; i < 3; i++)
+ {
+ t1 = matrix.coeff[i][0];
+ t2 = matrix.coeff[i][1];
+ t3 = matrix.coeff[i][2];
+
+ for (j = 0; j < 3; j++)
+ {
+ tmp.coeff[i][j] = t1 * coeff[0][j];
+ tmp.coeff[i][j] += t2 * coeff[1][j];
+ tmp.coeff[i][j] += t3 * coeff[2][j];
+ }
+ }
+
+ *this = tmp;
+}
+
+void Matrix::transformPoint(double x, double y, double *newx, double *newy) const
+{
+ double w;
+
+ w = coeff[2][0] * x + coeff[2][1] * y + coeff[2][2];
+
+ if (w == 0.0)
+ w = 1.0;
+ else
+ w = 1.0/w;
+
+ *newx = (coeff[0][0] * x +
+ coeff[0][1] * y +
+ coeff[0][2]) * w;
+ *newy = (coeff[1][0] * x +
+ coeff[1][1] * y +
+ coeff[1][2]) * w;
+}
+
+void Matrix::invert()
+{
+ Matrix inv;
+ double det;
+
+ det = determinant();
+
+ if (det == 0.0)
+ return;
+
+ det = 1.0 / det;
+
+ inv.coeff[0][0] = (coeff[1][1] * coeff[2][2] -
+ coeff[1][2] * coeff[2][1]) * det;
+
+ inv.coeff[1][0] = - (coeff[1][0] * coeff[2][2] -
+ coeff[1][2] * coeff[2][0]) * det;
+
+ inv.coeff[2][0] = (coeff[1][0] * coeff[2][1] -
+ coeff[1][1] * coeff[2][0]) * det;
+
+ inv.coeff[0][1] = - (coeff[0][1] * coeff[2][2] -
+ coeff[0][2] * coeff[2][1]) * det;
+
+ inv.coeff[1][1] = (coeff[0][0] * coeff[2][2] -
+ coeff[0][2] * coeff[2][0]) * det;
+
+ inv.coeff[2][1] = - (coeff[0][0] * coeff[2][1] -
+ coeff[0][1] * coeff[2][0]) * det;
+
+ inv.coeff[0][2] = (coeff[0][1] * coeff[1][2] -
+ coeff[0][2] * coeff[1][1]) * det;
+
+ inv.coeff[1][2] = - (coeff[0][0] * coeff[1][2] -
+ coeff[0][2] * coeff[1][0]) * det;
+
+ inv.coeff[2][2] = (coeff[0][0] * coeff[1][1] -
+ coeff[0][1] * coeff[1][0]) * det;
+
+ *this = inv;
+}
+
+double Matrix::determinant() const
+{
+ double determinant;
+
+ determinant = (coeff[0][0] *
+ (coeff[1][1] * coeff[2][2] -
+ coeff[1][2] * coeff[2][1]));
+ determinant -= (coeff[1][0] *
+ (coeff[0][1] * coeff[2][2] -
+ coeff[0][2] * coeff[2][1]));
+ determinant += (coeff[2][0] *
+ (coeff[0][1] * coeff[1][2] -
+ coeff[0][2] * coeff[1][1]));
+
+ return determinant;
+}
+
+} // namespace DigikamPerspectiveImagesPlugin
diff --git a/src/imageplugins/perspective/matrix.h b/src/imageplugins/perspective/matrix.h
new file mode 100644
index 00000000..fbc5a1aa
--- /dev/null
+++ b/src/imageplugins/perspective/matrix.h
@@ -0,0 +1,106 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a matrix implementation for image
+ * perspective adjustment.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_PERSPECTIVE_MATRIX_H
+#define IMAGEEFFECT_PERSPECTIVE_MATRIX_H
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+class Matrix
+{
+public:
+
+ /**
+ * Matrix:
+ *
+ * Initializes matrix to the identity matrix.
+ */
+ Matrix();
+
+ /**
+ * translate:
+ * @x: Translation in X direction.
+ * @y: Translation in Y direction.
+ *
+ * Translates the matrix by x and y.
+ */
+ void translate(double x, double y);
+
+ /**
+ * scale:
+ * @x: X scale factor.
+ * @y: Y scale factor.
+ *
+ * Scales the matrix by x and y
+ */
+ void scale(double x, double y);
+
+ /**
+ * invert:
+ *
+ * Inverts this matrix.
+ */
+ void invert();
+
+ /**
+ * multiply:
+ * @matrix: The other input matrix.
+ *
+ * Multiplies this matrix with another matrix
+ */
+ void multiply(const Matrix &matrix1);
+
+ /**
+ * transformPoint:
+ * @x: The source X coordinate.
+ * @y: The source Y coordinate.
+ * @newx: The transformed X coordinate.
+ * @newy: The transformed Y coordinate.
+ *
+ * Transforms a point in 2D as specified by the transformation matrix.
+ */
+ void transformPoint(double x, double y, double *newx, double *newy) const;
+
+ /**
+ * determinant:
+ *
+ * Calculates the determinant of this matrix.
+ *
+ * Returns: The determinant.
+ */
+ double determinant() const;
+
+ /**
+ * coeff:
+ *
+ * The 3x3 matrix data
+ */
+ double coeff[3][3];
+};
+
+} // namespace DigikamPerspectiveImagesPlugin
+
+#endif // IMAGEEFFECT_PERSPECTIVE_MATRIX_H
diff --git a/src/imageplugins/perspective/perspectivetool.cpp b/src/imageplugins/perspective/perspectivetool.cpp
new file mode 100644
index 00000000..30d0a2cf
--- /dev/null
+++ b/src/imageplugins/perspective/perspectivetool.cpp
@@ -0,0 +1,244 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a plugin to change image perspective.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqframe.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <kcolorbutton.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdepopupmenu.h>
+#include <kseparator.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "editortoolsettings.h"
+#include "perspectivewidget.h"
+#include "perspectivetool.h"
+#include "perspectivetool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+PerspectiveTool::PerspectiveTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("perspective");
+ setToolName(i18n("Perspective"));
+ setToolIcon(SmallIcon("perspective"));
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(0);
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* l = new TQVBoxLayout(frame, 5, 0);
+ m_previewWidget = new PerspectiveWidget(525, 350, frame);
+ l->addWidget(m_previewWidget);
+ TQWhatsThis::add(m_previewWidget, i18n("<p>This is the perspective transformation operation preview. "
+ "You can use the mouse for dragging the corner to adjust the "
+ "perspective transformation area."));
+ setToolView(frame);
+
+ // -------------------------------------------------------------
+
+ TQString temp;
+ Digikam::ImageIface iface(0, 0);
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout *gridLayout = new TQGridLayout( m_gboxSettings->plainPage(), 13, 2);
+
+ TQLabel *label1 = new TQLabel(i18n("New width:"), m_gboxSettings->plainPage());
+ m_newWidthLabel = new TQLabel(temp.setNum( iface.originalWidth()) + i18n(" px"), m_gboxSettings->plainPage());
+ m_newWidthLabel->setAlignment( AlignBottom | AlignRight );
+
+ TQLabel *label2 = new TQLabel(i18n("New height:"), m_gboxSettings->plainPage());
+ m_newHeightLabel = new TQLabel(temp.setNum( iface.originalHeight()) + i18n(" px"), m_gboxSettings->plainPage());
+ m_newHeightLabel->setAlignment( AlignBottom | AlignRight );
+
+ // -------------------------------------------------------------
+
+ KSeparator *line = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ TQLabel *angleLabel = new TQLabel(i18n("Angles (in degrees):"), m_gboxSettings->plainPage());
+ TQLabel *label3 = new TQLabel(i18n(" Top left:"), m_gboxSettings->plainPage());
+ m_topLeftAngleLabel = new TQLabel(m_gboxSettings->plainPage());
+ TQLabel *label4 = new TQLabel(i18n(" Top right:"), m_gboxSettings->plainPage());
+ m_topRightAngleLabel = new TQLabel(m_gboxSettings->plainPage());
+ TQLabel *label5 = new TQLabel(i18n(" Bottom left:"), m_gboxSettings->plainPage());
+ m_bottomLeftAngleLabel = new TQLabel(m_gboxSettings->plainPage());
+ TQLabel *label6 = new TQLabel(i18n(" Bottom right:"), m_gboxSettings->plainPage());
+ m_bottomRightAngleLabel = new TQLabel(m_gboxSettings->plainPage());
+
+ // -------------------------------------------------------------
+
+ KSeparator *line2 = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ m_drawWhileMovingCheckBox = new TQCheckBox(i18n("Draw preview while moving"), m_gboxSettings->plainPage());
+ gridLayout->addMultiCellWidget(line2, 8, 8, 0, 2);
+ gridLayout->addMultiCellWidget(m_drawWhileMovingCheckBox, 9, 9, 0, 2);
+
+ m_drawGridCheckBox = new TQCheckBox(i18n("Draw grid"), m_gboxSettings->plainPage());
+
+ // -------------------------------------------------------------
+
+ TQLabel *label7 = new TQLabel(i18n("Guide color:"), m_gboxSettings->plainPage());
+ m_guideColorBt = new KColorButton( TQColor( TQt::red ), m_gboxSettings->plainPage() );
+ TQWhatsThis::add( m_guideColorBt, i18n("<p>Set here the color used to draw guides dashed-lines."));
+ gridLayout->addMultiCellWidget(label7, 11, 11, 0, 0);
+ gridLayout->addMultiCellWidget(m_guideColorBt, 11, 11, 2, 2);
+
+ TQLabel *label8 = new TQLabel(i18n("Guide width:"), m_gboxSettings->plainPage());
+ m_guideSize = new RIntNumInput(m_gboxSettings->plainPage());
+ m_guideSize->input()->setRange(1, 5, 1, false);
+ m_guideSize->setDefaultValue(1);
+ TQWhatsThis::add( m_guideSize, i18n("<p>Set here the width in pixels used to draw guides dashed-lines."));
+
+ gridLayout->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridLayout->addMultiCellWidget(m_newWidthLabel, 0, 0, 1, 2);
+ gridLayout->addMultiCellWidget(label2, 1, 1, 0, 0);
+ gridLayout->addMultiCellWidget(m_newHeightLabel, 1, 1, 1, 2);
+ gridLayout->addMultiCellWidget(line, 2, 2, 0, 2);
+ gridLayout->addMultiCellWidget(angleLabel, 3, 3, 0, 2);
+ gridLayout->addMultiCellWidget(label3, 4, 4, 0, 0);
+ gridLayout->addMultiCellWidget(m_topLeftAngleLabel, 4, 4, 1, 2);
+ gridLayout->addMultiCellWidget(label4, 5, 5, 0, 0);
+ gridLayout->addMultiCellWidget(m_topRightAngleLabel, 5, 5, 1, 2);
+ gridLayout->addMultiCellWidget(label5, 6, 6, 0, 0);
+ gridLayout->addMultiCellWidget(m_bottomLeftAngleLabel, 6, 6, 1, 2);
+ gridLayout->addMultiCellWidget(label6, 7, 7, 0, 0);
+ gridLayout->addMultiCellWidget(m_bottomRightAngleLabel, 7, 7, 1, 2);
+ gridLayout->addMultiCellWidget(m_drawGridCheckBox, 10, 10, 0, 2);
+ gridLayout->addMultiCellWidget(label8, 12, 12, 0, 0);
+ gridLayout->addMultiCellWidget(m_guideSize, 12, 12, 2, 2);
+ gridLayout->setColStretch(1, 10);
+ gridLayout->setRowStretch(13, 10);
+ gridLayout->setMargin(m_gboxSettings->spacingHint());
+ gridLayout->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_previewWidget, TQ_SIGNAL(signalPerspectiveChanged(TQRect, float, float, float, float)),
+ this, TQ_SLOT(slotUpdateInfo(TQRect, float, float, float, float)));
+
+ connect(m_drawWhileMovingCheckBox, TQ_SIGNAL(toggled(bool)),
+ m_previewWidget, TQ_SLOT(slotToggleDrawWhileMoving(bool)));
+
+ connect(m_drawGridCheckBox, TQ_SIGNAL(toggled(bool)),
+ m_previewWidget, TQ_SLOT(slotToggleDrawGrid(bool)));
+
+ connect(m_guideColorBt, TQ_SIGNAL(changed(const TQColor&)),
+ m_previewWidget, TQ_SLOT(slotChangeGuideColor(const TQColor&)));
+
+ connect(m_guideSize, TQ_SIGNAL(valueChanged(int)),
+ m_previewWidget, TQ_SLOT(slotChangeGuideSize(int)));
+}
+
+PerspectiveTool::~PerspectiveTool()
+{
+}
+
+void PerspectiveTool::readSettings()
+{
+ TQColor defaultGuideColor(TQt::red);
+ TDEConfig *config = kapp->config();
+ config->setGroup("perspective Tool");
+ m_drawWhileMovingCheckBox->setChecked(config->readBoolEntry("Draw While Moving", true));
+ m_drawGridCheckBox->setChecked(config->readBoolEntry("Draw Grid", false));
+ m_guideColorBt->setColor(config->readColorEntry("Guide Color", &defaultGuideColor));
+ m_guideSize->setValue(config->readNumEntry("Guide Width", m_guideSize->defaultValue()));
+ m_previewWidget->slotToggleDrawWhileMoving(m_drawWhileMovingCheckBox->isChecked());
+ m_previewWidget->slotToggleDrawGrid(m_drawGridCheckBox->isChecked());
+ m_previewWidget->slotChangeGuideColor(m_guideColorBt->color());
+ m_previewWidget->slotChangeGuideSize(m_guideSize->value());
+}
+
+void PerspectiveTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("perspective Tool");
+ config->writeEntry("Draw While Moving", m_drawWhileMovingCheckBox->isChecked());
+ config->writeEntry("Draw Grid", m_drawGridCheckBox->isChecked());
+ config->writeEntry("Guide Color", m_guideColorBt->color());
+ config->writeEntry("Guide Width", m_guideSize->value());
+ config->sync();
+}
+
+void PerspectiveTool::slotResetSettings()
+{
+ m_previewWidget->reset();
+}
+
+void PerspectiveTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ m_previewWidget->applyPerspectiveAdjustment();
+ kapp->restoreOverrideCursor();
+}
+
+void PerspectiveTool::slotUpdateInfo(TQRect newSize, float topLeftAngle, float topRightAngle,
+ float bottomLeftAngle, float bottomRightAngle)
+{
+ TQString temp;
+ m_newWidthLabel->setText(temp.setNum( newSize.width()) + i18n(" px") );
+ m_newHeightLabel->setText(temp.setNum( newSize.height()) + i18n(" px") );
+
+ m_topLeftAngleLabel->setText(temp.setNum( topLeftAngle, 'f', 1 ));
+ m_topRightAngleLabel->setText(temp.setNum( topRightAngle, 'f', 1 ));
+ m_bottomLeftAngleLabel->setText(temp.setNum( bottomLeftAngle, 'f', 1 ));
+ m_bottomRightAngleLabel->setText(temp.setNum( bottomRightAngle, 'f', 1 ));
+}
+
+} // NameSpace DigikamPerspectiveImagesPlugin
diff --git a/src/imageplugins/perspective/perspectivetool.h b/src/imageplugins/perspective/perspectivetool.h
new file mode 100644
index 00000000..6186712f
--- /dev/null
+++ b/src/imageplugins/perspective/perspectivetool.h
@@ -0,0 +1,100 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-17
+ * Description : a plugin to change image perspective.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_PERSPECTIVE_H
+#define IMAGEEFFECT_PERSPECTIVE_H
+
+// TQt includes.
+
+#include <tqrect.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQLabel;
+class TQCheckBox;
+
+class KColorButton;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+}
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+class PerspectiveWidget;
+
+class PerspectiveTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ PerspectiveTool(TQObject* parent);
+ ~PerspectiveTool();
+
+private slots:
+
+ void slotResetSettings();
+ void slotUpdateInfo(TQRect newSize, float topLeftAngle, float topRightAngle,
+ float bottomLeftAngle, float bottomRightAngle);
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+
+private:
+
+ TQLabel *m_newWidthLabel;
+ TQLabel *m_newHeightLabel;
+ TQLabel *m_topLeftAngleLabel;
+ TQLabel *m_topRightAngleLabel;
+ TQLabel *m_bottomLeftAngleLabel;
+ TQLabel *m_bottomRightAngleLabel;
+
+ TQCheckBox *m_drawWhileMovingCheckBox;
+ TQCheckBox *m_drawGridCheckBox;
+
+ KDcrawIface::RIntNumInput *m_guideSize;
+
+ KColorButton *m_guideColorBt;
+
+ PerspectiveWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamPerspectiveImagesPlugin
+
+#endif /* IMAGEEFFECT_PERSPECTIVE_H */
diff --git a/src/imageplugins/perspective/perspectivewidget.cpp b/src/imageplugins/perspective/perspectivewidget.cpp
new file mode 100644
index 00000000..ae2c5c03
--- /dev/null
+++ b/src/imageplugins/perspective/perspectivewidget.cpp
@@ -0,0 +1,839 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-18
+ * Description : a widget class to edit perspective.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Matrix3 implementation inspired from gimp 2.0
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cmath>
+
+// TQt includes.
+
+#include <tqregion.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqbrush.h>
+#include <tqpixmap.h>
+#include <tqimage.h>
+#include <tqpointarray.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "triangle.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "dimgimagefilters.h"
+#include "perspectivewidget.h"
+#include "perspectivewidget.moc"
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+PerspectiveWidget::PerspectiveWidget(int w, int h, TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ setBackgroundMode(TQt::NoBackground);
+ setMinimumSize(w, h);
+ setMouseTracking(true);
+
+ m_drawGrid = false;
+ m_drawWhileMoving = true;
+ m_currentResizing = ResizingNone;
+ m_guideColor = TQt::red;
+ m_guideSize = 1;
+
+ m_iface = new Digikam::ImageIface(w, h);
+ uchar *data = m_iface->setPreviewImageSize(w, h);
+ m_w = m_iface->previewWidth();
+ m_h = m_iface->previewHeight();
+ m_origW = m_iface->originalWidth();
+ m_origH = m_iface->originalHeight();
+ m_previewImage = Digikam::DImg(m_w, m_h, m_iface->previewSixteenBit(), m_iface->previewHasAlpha(), data, false);
+
+ m_pixmap = new TQPixmap(w, h);
+
+ m_rect = TQRect(w/2-m_w/2, h/2-m_h/2, m_w, m_h);
+ m_grid = TQPointArray(60);
+
+ reset();
+}
+
+PerspectiveWidget::~PerspectiveWidget()
+{
+ delete m_iface;
+ delete m_pixmap;
+}
+
+Digikam::ImageIface* PerspectiveWidget::imageIface()
+{
+ return m_iface;
+}
+
+TQPoint PerspectiveWidget::getTopLeftCorner(void)
+{
+ return TQPoint( lroundf((float)(m_topLeftPoint.x()*m_origW) / (float)m_w),
+ lroundf((float)(m_topLeftPoint.y()*m_origH) / (float)m_h));
+}
+
+TQPoint PerspectiveWidget::getTopRightCorner(void)
+{
+ return TQPoint( lroundf((float)(m_topRightPoint.x()*m_origW) / (float)m_w),
+ lroundf((float)(m_topRightPoint.y()*m_origH) / (float)m_h));
+}
+
+TQPoint PerspectiveWidget::getBottomLeftCorner(void)
+{
+ return TQPoint( lroundf((float)(m_bottomLeftPoint.x()*m_origW) / (float)m_w),
+ lroundf((float)(m_bottomLeftPoint.y()*m_origH) / (float)m_h));
+}
+
+TQPoint PerspectiveWidget::getBottomRightCorner(void)
+{
+ return TQPoint( lroundf((float)(m_bottomRightPoint.x()*m_origW) / (float)m_w),
+ lroundf((float)(m_bottomRightPoint.y()*m_origH) / (float)m_h));
+}
+
+TQRect PerspectiveWidget::getTargetSize(void)
+{
+ TQPointArray perspectiveArea;
+
+ perspectiveArea.putPoints( 0, 4,
+ getTopLeftCorner().x(), getTopLeftCorner().y(),
+ getTopRightCorner().x(), getTopRightCorner().y(),
+ getBottomRightCorner().x(), getBottomRightCorner().y(),
+ getBottomLeftCorner().x(), getBottomLeftCorner().y() );
+
+ return perspectiveArea.boundingRect();
+}
+
+float PerspectiveWidget::getAngleTopLeft(void)
+{
+ Triangle topLeft(getTopLeftCorner(), getTopRightCorner(), getBottomLeftCorner());
+ return topLeft.angleBAC();
+}
+
+float PerspectiveWidget::getAngleTopRight(void)
+{
+ Triangle topLeft(getTopRightCorner(), getBottomRightCorner(), getTopLeftCorner());
+ return topLeft.angleBAC();
+}
+
+float PerspectiveWidget::getAngleBottomLeft(void)
+{
+ Triangle topLeft(getBottomLeftCorner(), getTopLeftCorner(), getBottomRightCorner());
+ return topLeft.angleBAC();
+}
+
+float PerspectiveWidget::getAngleBottomRight(void)
+{
+ Triangle topLeft(getBottomRightCorner(), getBottomLeftCorner(), getTopRightCorner());
+ return topLeft.angleBAC();
+}
+
+void PerspectiveWidget::reset(void)
+{
+ m_topLeftPoint.setX(0);
+ m_topLeftPoint.setY(0);
+
+ m_topRightPoint.setX(m_w-1);
+ m_topRightPoint.setY(0);
+
+ m_bottomLeftPoint.setX(0);
+ m_bottomLeftPoint.setY(m_h-1);
+
+ m_bottomRightPoint.setX(m_w-1);
+ m_bottomRightPoint.setY(m_h-1);
+
+ m_spot.setX(m_w / 2);
+ m_spot.setY(m_h / 2);
+
+ m_antiAlias = true;
+ updatePixmap();
+ repaint(false);
+}
+
+void PerspectiveWidget::applyPerspectiveAdjustment(void)
+{
+ Digikam::DImg *orgImage = m_iface->getOriginalImg();
+ Digikam::DImg destImage(orgImage->width(), orgImage->height(), orgImage->sixteenBit(), orgImage->hasAlpha());
+
+ Digikam::DColor background(0, 0, 0, orgImage->hasAlpha() ? 0 : 255, orgImage->sixteenBit());
+
+ // Perform perspective adjustment.
+
+ buildPerspective(TQPoint(0, 0), TQPoint(m_origW, m_origH),
+ getTopLeftCorner(), getTopRightCorner(),
+ getBottomLeftCorner(), getBottomRightCorner(),
+ orgImage, &destImage, background);
+
+ // Perform an auto-croping around the image.
+
+ Digikam::DImg targetImg = destImage.copy(getTargetSize());
+
+ // Update target image.
+ m_iface->putOriginalImage(i18n("Perspective Adjustment"),
+ targetImg.bits(), targetImg.width(), targetImg.height());
+}
+
+void PerspectiveWidget::slotToggleAntiAliasing(bool a)
+{
+ m_antiAlias = a;
+ updatePixmap();
+ repaint(false);
+}
+
+void PerspectiveWidget::slotToggleDrawWhileMoving(bool draw)
+{
+ m_drawWhileMoving = draw;
+}
+
+void PerspectiveWidget::slotToggleDrawGrid(bool grid)
+{
+ m_drawGrid = grid;
+ updatePixmap();
+ repaint(false);
+}
+
+void PerspectiveWidget::slotChangeGuideColor(const TQColor &color)
+{
+ m_guideColor = color;
+ updatePixmap();
+ repaint(false);
+}
+
+void PerspectiveWidget::slotChangeGuideSize(int size)
+{
+ m_guideSize = size;
+ updatePixmap();
+ repaint(false);
+}
+
+void PerspectiveWidget::updatePixmap(void)
+{
+ m_topLeftCorner.setRect(m_topLeftPoint.x() + m_rect.topLeft().x(),
+ m_topLeftPoint.y() + m_rect.topLeft().y(), 8, 8);
+ m_topRightCorner.setRect(m_topRightPoint.x() - 7 + m_rect.topLeft().x(),
+ m_topRightPoint.y() + m_rect.topLeft().y(), 8, 8);
+ m_bottomLeftCorner.setRect(m_bottomLeftPoint.x() + m_rect.topLeft().x(),
+ m_bottomLeftPoint.y() - 7 + m_rect.topLeft().y(), 8, 8);
+ m_bottomRightCorner.setRect(m_bottomRightPoint.x() - 7 + m_rect.topLeft().x(),
+ m_bottomRightPoint.y() - 7 + m_rect.topLeft().y(), 8, 8);
+
+ // Compute the grid array
+
+ int gXS = m_w / 15;
+ int gYS = m_h / 15;
+
+ for (int i = 0 ; i < 15 ; i++)
+ {
+ int j = i*4;
+
+ //Horizontal line.
+ m_grid.setPoint(j , 0, i*gYS);
+ m_grid.setPoint(j+1, m_w, i*gYS);
+
+ //Vertical line.
+ m_grid.setPoint(j+2, i*gXS, 0);
+ m_grid.setPoint(j+3, i*gXS, m_h);
+ }
+
+ // Draw background
+
+ m_pixmap->fill(colorGroup().background());
+
+ // if we are resizing with the mouse, compute and draw only if drawWhileMoving is set
+ if (m_currentResizing == ResizingNone || m_drawWhileMoving)
+ {
+ // Create preview image
+
+ Digikam::DImg destImage(m_previewImage.width(), m_previewImage.height(),
+ m_previewImage.sixteenBit(), m_previewImage.hasAlpha());
+
+ Digikam::DColor background(colorGroup().background());
+
+ m_transformedCenter = buildPerspective(TQPoint(0, 0), TQPoint(m_w, m_h),
+ m_topLeftPoint, m_topRightPoint,
+ m_bottomLeftPoint, m_bottomRightPoint,
+ &m_previewImage, &destImage, background);
+
+ m_iface->putPreviewImage(destImage.bits());
+
+ // Draw image
+
+ m_iface->paint(m_pixmap, m_rect.x(), m_rect.y(),
+ m_rect.width(), m_rect.height());
+ }
+ else
+ {
+ m_transformedCenter = buildPerspective(TQPoint(0, 0), TQPoint(m_w, m_h),
+ m_topLeftPoint, m_topRightPoint,
+ m_bottomLeftPoint, m_bottomRightPoint);
+ }
+
+ // Drawing selection borders.
+
+ TQPainter p(m_pixmap);
+ p.setPen(TQPen(TQColor(255, 64, 64), 1, TQt::SolidLine));
+ p.drawLine(m_topLeftPoint+m_rect.topLeft(), m_topRightPoint+m_rect.topLeft());
+ p.drawLine(m_topRightPoint+m_rect.topLeft(), m_bottomRightPoint+m_rect.topLeft());
+ p.drawLine(m_bottomRightPoint+m_rect.topLeft(), m_bottomLeftPoint+m_rect.topLeft());
+ p.drawLine(m_bottomLeftPoint+m_rect.topLeft(), m_topLeftPoint+m_rect.topLeft());
+
+ // Drawing selection corners.
+
+ TQBrush brush(TQColor(255, 64, 64));
+ p.fillRect(m_topLeftCorner, brush);
+ p.fillRect(m_topRightCorner, brush);
+ p.fillRect(m_bottomLeftCorner, brush);
+ p.fillRect(m_bottomRightCorner, brush);
+
+ // Drawing the grid.
+
+ if (m_drawGrid)
+ {
+ for (uint i = 0 ; i < m_grid.size() ; i += 4)
+ {
+ //Horizontal line.
+ p.drawLine(m_grid.point(i)+m_rect.topLeft(), m_grid.point(i+1)+m_rect.topLeft());
+
+ //Vertical line.
+ p.drawLine(m_grid.point(i+2)+m_rect.topLeft(), m_grid.point(i+3)+m_rect.topLeft());
+ }
+ }
+
+ // Drawing transformed center.
+
+ p.setPen(TQPen(TQColor(255, 64, 64), 3, TQt::SolidLine));
+ p.drawEllipse( m_transformedCenter.x()+m_rect.topLeft().x()-2,
+ m_transformedCenter.y()+m_rect.topLeft().y()-2, 4, 4 );
+
+ // Drawing vertical and horizontal guide lines.
+
+ int xspot = m_spot.x() + m_rect.x();
+ int yspot = m_spot.y() + m_rect.y();
+ p.setPen(TQPen(TQt::white, m_guideSize, TQt::SolidLine));
+ p.drawLine(xspot, m_rect.top(), xspot, m_rect.bottom());
+ p.drawLine(m_rect.left(), yspot, m_rect.right(), yspot);
+ p.setPen(TQPen(m_guideColor, m_guideSize, TQt::DotLine));
+ p.drawLine(xspot, m_rect.top(), xspot, m_rect.bottom());
+ p.drawLine(m_rect.left(), yspot, m_rect.right(), yspot);
+
+ p.end();
+
+ emit signalPerspectiveChanged(getTargetSize(), getAngleTopLeft(), getAngleTopRight(),
+ getAngleBottomLeft(), getAngleBottomRight());
+}
+
+TQPoint PerspectiveWidget::buildPerspective(TQPoint orignTopLeft, TQPoint orignBottomRight,
+ TQPoint transTopLeft, TQPoint transTopRight,
+ TQPoint transBottomLeft, TQPoint transBottomRight,
+ Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ Digikam::DColor background)
+{
+ Matrix matrix, transform;
+ double scalex;
+ double scaley;
+
+ double x1 = (double)orignTopLeft.x();
+ double y1 = (double)orignTopLeft.y();
+
+ double x2 = (double)orignBottomRight.x();
+ double y2 = (double)orignBottomRight.y();
+
+ double tx1 = (double)transTopLeft.x();
+ double ty1 = (double)transTopLeft.y();
+
+ double tx2 = (double)transTopRight.x();
+ double ty2 = (double)transTopRight.y();
+
+ double tx3 = (double)transBottomLeft.x();
+ double ty3 = (double)transBottomLeft.y();
+
+ double tx4 = (double)transBottomRight.x();
+ double ty4 = (double)transBottomRight.y();
+
+ scalex = scaley = 1.0;
+
+ if ((x2 - x1) > 0)
+ scalex = 1.0 / (double) (x2 - x1);
+
+ if ((y2 - y1) > 0)
+ scaley = 1.0 / (double) (y2 - y1);
+
+ // Determine the perspective transform that maps from
+ // the unit cube to the transformed coordinates
+
+ double dx1, dx2, dx3, dy1, dy2, dy3;
+
+ dx1 = tx2 - tx4;
+ dx2 = tx3 - tx4;
+ dx3 = tx1 - tx2 + tx4 - tx3;
+
+ dy1 = ty2 - ty4;
+ dy2 = ty3 - ty4;
+ dy3 = ty1 - ty2 + ty4 - ty3;
+
+ // Is the mapping affine?
+
+ if ((dx3 == 0.0) && (dy3 == 0.0))
+ {
+ matrix.coeff[0][0] = tx2 - tx1;
+ matrix.coeff[0][1] = tx4 - tx2;
+ matrix.coeff[0][2] = tx1;
+ matrix.coeff[1][0] = ty2 - ty1;
+ matrix.coeff[1][1] = ty4 - ty2;
+ matrix.coeff[1][2] = ty1;
+ matrix.coeff[2][0] = 0.0;
+ matrix.coeff[2][1] = 0.0;
+ }
+ else
+ {
+ double det1, det2;
+
+ det1 = dx3 * dy2 - dy3 * dx2;
+ det2 = dx1 * dy2 - dy1 * dx2;
+
+ if (det1 == 0.0 && det2 == 0.0)
+ matrix.coeff[2][0] = 1.0;
+ else
+ matrix.coeff[2][0] = det1 / det2;
+
+ det1 = dx1 * dy3 - dy1 * dx3;
+
+ if (det1 == 0.0 && det2 == 0.0)
+ matrix.coeff[2][1] = 1.0;
+ else
+ matrix.coeff[2][1] = det1 / det2;
+
+ matrix.coeff[0][0] = tx2 - tx1 + matrix.coeff[2][0] * tx2;
+ matrix.coeff[0][1] = tx3 - tx1 + matrix.coeff[2][1] * tx3;
+ matrix.coeff[0][2] = tx1;
+
+ matrix.coeff[1][0] = ty2 - ty1 + matrix.coeff[2][0] * ty2;
+ matrix.coeff[1][1] = ty3 - ty1 + matrix.coeff[2][1] * ty3;
+ matrix.coeff[1][2] = ty1;
+ }
+
+ matrix.coeff[2][2] = 1.0;
+
+ // transform is initialized to the identity matrix
+ transform.translate(-x1, -y1);
+ transform.scale (scalex, scaley);
+ transform.multiply (matrix);
+
+ // Compute perspective transformation to image if image data containers exist.
+ if (orgImage && destImage)
+ transformAffine(orgImage, destImage, transform, background);
+
+ // Calculate the grid array points.
+ double newX, newY;
+ for (uint i = 0 ; i < m_grid.size() ; i++)
+ {
+ transform.transformPoint(m_grid.point(i).x(), m_grid.point(i).y(), &newX, &newY);
+ m_grid.setPoint(i, lround(newX), lround(newY));
+ }
+
+ // Calculate and return new image center.
+ double newCenterX, newCenterY;
+ transform.transformPoint(x2/2.0, y2/2.0, &newCenterX, &newCenterY);
+
+ return TQPoint(lround(newCenterX), lround(newCenterY));
+}
+
+void PerspectiveWidget::transformAffine(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ const Matrix &matrix, Digikam::DColor background)
+{
+ Matrix m(matrix), inv(matrix);
+
+ int x1, y1, x2, y2; // target bounding box
+ int x, y; // target coordinates
+ int u1, v1, u2, v2; // source bounding box
+ double uinc, vinc, winc; // increments in source coordinates
+ // pr horizontal target coordinate
+
+ double u[5],v[5]; // source coordinates,
+ // 2
+ // / \ 0 is sample in the center of pixel
+ // 1 0 3 1..4 is offset 1 pixel in each
+ // \ / direction (in target space)
+ // 4
+
+ double tu[5],tv[5],tw[5]; // undivided source coordinates and divisor
+
+ uchar *data, *newData;
+ bool sixteenBit;
+ int coords;
+ int width, height;
+ int bytesDepth;
+ int offset;
+ uchar *dest, *d;
+ Digikam::DColor color;
+
+ bytesDepth = orgImage->bytesDepth();
+ data = orgImage->bits();
+ sixteenBit = orgImage->sixteenBit();
+ width = orgImage->width();
+ height = orgImage->height();
+ newData = destImage->bits();
+
+ if (sixteenBit)
+ background.convertToSixteenBit();
+
+ //destImage->fill(background);
+
+ Digikam::DImgImageFilters filters;
+
+ // Find the inverse of the transformation matrix
+ m.invert();
+
+ u1 = 0;
+ v1 = 0;
+ u2 = u1 + width;
+ v2 = v1 + height;
+
+ x1 = u1;
+ y1 = v1;
+ x2 = u2;
+ y2 = v2;
+
+ dest = new uchar[width * bytesDepth];
+
+ uinc = m.coeff[0][0];
+ vinc = m.coeff[1][0];
+ winc = m.coeff[2][0];
+
+ coords = 1;
+
+ // these loops could be rearranged, depending on which bit of code
+ // you'd most like to write more than once.
+
+ for (y = y1; y < y2; y++)
+ {
+ // set up inverse transform steps
+
+ tu[0] = uinc * (x1 + 0.5) + m.coeff[0][1] * (y + 0.5) + m.coeff[0][2] - 0.5;
+ tv[0] = vinc * (x1 + 0.5) + m.coeff[1][1] * (y + 0.5) + m.coeff[1][2] - 0.5;
+ tw[0] = winc * (x1 + 0.5) + m.coeff[2][1] * (y + 0.5) + m.coeff[2][2];
+
+ d = dest;
+
+ for (x = x1; x < x2; x++)
+ {
+ int i; // normalize homogeneous coords
+
+ for (i = 0; i < coords; i++)
+ {
+ if (tw[i] == 1.0)
+ {
+ u[i] = tu[i];
+ v[i] = tv[i];
+ }
+ else if (tw[i] != 0.0)
+ {
+ u[i] = tu[i] / tw[i];
+ v[i] = tv[i] / tw[i];
+ }
+ else
+ {
+ DDebug() << "homogeneous coordinate = 0...\n" << endl;
+ }
+ }
+
+ // Set the destination pixels
+
+ int iu = lround( u [0] );
+ int iv = lround( v [0] );
+
+ if (iu >= u1 && iu < u2 && iv >= v1 && iv < v2)
+ {
+ // u, v coordinates into source
+
+ int u = iu - u1;
+ int v = iv - v1;
+
+ //TODO: Check why antialiasing shows no effect
+ /*if (m_antiAlias)
+ {
+ if (sixteenBit)
+ {
+ unsigned short *d16 = (unsigned short *)d;
+ filters.pixelAntiAliasing16((unsigned short *)data,
+ width, height, u, v, d16+3, d16+2, d16+1, d16);
+ }
+ else
+ {
+ filters.pixelAntiAliasing(data, width, height, u, v,
+ d+3, d+2, d+1, d);
+ }
+ }
+ else
+ {*/
+ offset = (v * width * bytesDepth) + (u * bytesDepth);
+ color.setColor(data + offset, sixteenBit);
+ color.setPixel(d);
+ //}
+
+ d += bytesDepth;
+ }
+ else // not in source range
+ {
+ // set to background color
+
+ background.setPixel(d);
+ d += bytesDepth;
+ }
+
+ for (i = 0; i < coords; i++)
+ {
+ tu[i] += uinc;
+ tv[i] += vinc;
+ tw[i] += winc;
+ }
+ }
+
+ // set the pixel region row
+
+ offset = (y - y1) * width * bytesDepth;
+ memcpy(newData + offset, dest, width * bytesDepth);
+ }
+
+ delete [] dest;
+}
+
+void PerspectiveWidget::paintEvent( TQPaintEvent * )
+{
+ bitBlt(this, 0, 0, m_pixmap);
+}
+
+void PerspectiveWidget::resizeEvent(TQResizeEvent * e)
+{
+ int old_w = m_w;
+ int old_h = m_h;
+
+ delete m_pixmap;
+ int w = e->size().width();
+ int h = e->size().height();
+ uchar *data = m_iface->setPreviewImageSize(w, h);
+ m_w = m_iface->previewWidth();
+ m_h = m_iface->previewHeight();
+ m_previewImage = Digikam::DImg(m_w, m_h, m_iface->previewSixteenBit(), m_iface->previewHasAlpha(), data, false);
+
+ m_pixmap = new TQPixmap(w, h);
+ TQRect oldRect = m_rect;
+ m_rect = TQRect(w/2-m_w/2, h/2-m_h/2, m_w, m_h);
+
+ float xFactor = (float)m_rect.width()/(float)(oldRect.width());
+ float yFactor = (float)m_rect.height()/(float)(oldRect.height());
+
+ m_topLeftPoint = TQPoint(lroundf(m_topLeftPoint.x()*xFactor),
+ lroundf(m_topLeftPoint.y()*yFactor));
+ m_topRightPoint = TQPoint(lroundf(m_topRightPoint.x()*xFactor),
+ lroundf(m_topRightPoint.y()*yFactor));
+ m_bottomLeftPoint = TQPoint(lroundf(m_bottomLeftPoint.x()*xFactor),
+ lroundf(m_bottomLeftPoint.y()*yFactor));
+ m_bottomRightPoint = TQPoint(lroundf(m_bottomRightPoint.x()*xFactor),
+ lroundf(m_bottomRightPoint.y()*yFactor));
+ m_transformedCenter = TQPoint(lroundf(m_transformedCenter.x()*xFactor),
+ lroundf(m_transformedCenter.y()*yFactor));
+
+ m_spot.setX((int)((float)m_spot.x() * ( (float)m_w / (float)old_w)));
+ m_spot.setY((int)((float)m_spot.y() * ( (float)m_h / (float)old_h)));
+
+ updatePixmap();
+}
+
+void PerspectiveWidget::mousePressEvent ( TQMouseEvent * e )
+{
+ if ( e->button() == TQt::LeftButton &&
+ m_rect.contains( e->x(), e->y() ))
+ {
+ if ( m_topLeftCorner.contains( e->x(), e->y() ) )
+ m_currentResizing = ResizingTopLeft;
+ else if ( m_bottomRightCorner.contains( e->x(), e->y() ) )
+ m_currentResizing = ResizingBottomRight;
+ else if ( m_topRightCorner.contains( e->x(), e->y() ) )
+ m_currentResizing = ResizingTopRight;
+ else if ( m_bottomLeftCorner.contains( e->x(), e->y() ) )
+ m_currentResizing = ResizingBottomLeft;
+ else
+ {
+ m_spot.setX(e->x()-m_rect.x());
+ m_spot.setY(e->y()-m_rect.y());
+ }
+ }
+}
+
+void PerspectiveWidget::mouseReleaseEvent ( TQMouseEvent * e )
+{
+ if ( m_currentResizing != ResizingNone )
+ {
+ unsetCursor();
+ m_currentResizing = ResizingNone;
+
+ // in this case, the pixmap has not been drawn on mouse move
+ if (!m_drawWhileMoving)
+ {
+ updatePixmap();
+ repaint(false);
+ }
+ }
+ else
+ {
+ m_spot.setX(e->x()-m_rect.x());
+ m_spot.setY(e->y()-m_rect.y());
+ updatePixmap();
+ repaint(false);
+ }
+}
+
+void PerspectiveWidget::mouseMoveEvent ( TQMouseEvent * e )
+{
+ if ( e->state() == TQt::LeftButton )
+ {
+ if ( m_currentResizing != ResizingNone )
+ {
+ TQPointArray unsablePoints;
+ TQPoint pm(e->x(), e->y());
+
+ if (!m_rect.contains( pm ))
+ {
+ if (pm.x() > m_rect.right())
+ pm.setX(m_rect.right());
+ else if (pm.x() < m_rect.left())
+ pm.setX(m_rect.left());
+
+ if (pm.y() > m_rect.bottom())
+ pm.setY(m_rect.bottom());
+ else if (pm.y() < m_rect.top())
+ pm.setY(m_rect.top());
+ }
+
+ if ( m_currentResizing == ResizingTopLeft )
+ {
+ unsablePoints.putPoints(0, 7,
+ m_w-1, m_h-1,
+ 0, m_h-1,
+ 0, m_bottomLeftPoint.y()-10,
+ m_bottomLeftPoint.x(), m_bottomLeftPoint.y()-10,
+ m_topRightPoint.x()-10, m_topRightPoint.y(),
+ m_topRightPoint.x()-10, 0,
+ m_w-1, 0 );
+ TQRegion unsableArea(unsablePoints);
+
+ if ( unsableArea.contains(pm) ) return;
+
+ m_topLeftPoint = pm - m_rect.topLeft();
+ setCursor( KCursor::sizeFDiagCursor() );
+ }
+
+ else if ( m_currentResizing == ResizingTopRight )
+ {
+ unsablePoints.putPoints(0, 7,
+ 0, m_h-1,
+ 0, 0,
+ m_topLeftPoint.x()+10, 0,
+ m_topLeftPoint.x()+10, m_topLeftPoint.y(),
+ m_bottomRightPoint.x(), m_bottomRightPoint.y()-10,
+ m_w-1, m_bottomRightPoint.y()-10,
+ m_w-1, m_h-1);
+ TQRegion unsableArea(unsablePoints);
+
+ if ( unsableArea.contains(pm) ) return;
+
+ m_topRightPoint = pm - m_rect.topLeft();
+ setCursor( KCursor::sizeBDiagCursor() );
+ }
+
+ else if ( m_currentResizing == ResizingBottomLeft )
+ {
+ unsablePoints.putPoints(0, 7,
+ m_w-1, 0,
+ m_w-1, m_h-1,
+ m_bottomRightPoint.x()-10, m_h-1,
+ m_bottomRightPoint.x()-10, m_bottomRightPoint.y()+10,
+ m_topLeftPoint.x(), m_topLeftPoint.y()+10,
+ 0, m_topLeftPoint.y(),
+ 0, 0);
+ TQRegion unsableArea(unsablePoints);
+
+ if ( unsableArea.contains(pm) ) return;
+
+ m_bottomLeftPoint = pm - m_rect.topLeft();
+ setCursor( KCursor::sizeBDiagCursor() );
+ }
+
+ else if ( m_currentResizing == ResizingBottomRight )
+ {
+ unsablePoints.putPoints(0, 7,
+ 0, 0,
+ m_w-1, 0,
+ m_w-1, m_topRightPoint.y()+10,
+ m_topRightPoint.x(), m_topRightPoint.y()+10,
+ m_bottomLeftPoint.x()+10, m_bottomLeftPoint.y(),
+ m_bottomLeftPoint.x()+10, m_w-1,
+ 0, m_w-1);
+ TQRegion unsableArea(unsablePoints);
+
+ if ( unsableArea.contains(pm) ) return;
+
+ m_bottomRightPoint = pm - m_rect.topLeft();
+ setCursor( KCursor::sizeFDiagCursor() );
+ }
+
+ else
+ {
+ m_spot.setX(e->x()-m_rect.x());
+ m_spot.setY(e->y()-m_rect.y());
+ }
+
+ updatePixmap();
+ repaint(false);
+ }
+ }
+ else
+ {
+ if ( m_topLeftCorner.contains( e->x(), e->y() ) ||
+ m_bottomRightCorner.contains( e->x(), e->y() ) )
+ setCursor( KCursor::sizeFDiagCursor() );
+
+ else if ( m_topRightCorner.contains( e->x(), e->y() ) ||
+ m_bottomLeftCorner.contains( e->x(), e->y() ) )
+ setCursor( KCursor::sizeBDiagCursor() );
+ else
+ unsetCursor();
+ }
+}
+
+} // NameSpace DigikamPerspectiveImagesPlugin
+
diff --git a/src/imageplugins/perspective/perspectivewidget.h b/src/imageplugins/perspective/perspectivewidget.h
new file mode 100644
index 00000000..0047c3e8
--- /dev/null
+++ b/src/imageplugins/perspective/perspectivewidget.h
@@ -0,0 +1,172 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-18
+ * Description : a widget class to edit perspective.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef PERSPECTIVEWIDGET_H
+#define PERSPECTIVEWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqpoint.h>
+#include <tqpointarray.h>
+#include <tqcolor.h>
+#include <tqrect.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+
+// Local includes.
+
+#include "matrix.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+class ImageIface;
+}
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+class PerspectiveWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ PerspectiveWidget(int width, int height, TQWidget *parent=0);
+ ~PerspectiveWidget();
+
+ TQRect getTargetSize(void);
+ TQPoint getTopLeftCorner(void);
+ TQPoint getTopRightCorner(void);
+ TQPoint getBottomLeftCorner(void);
+ TQPoint getBottomRightCorner(void);
+ void reset(void);
+
+ float getAngleTopLeft(void);
+ float getAngleTopRight(void);
+ float getAngleBottomLeft(void);
+ float getAngleBottomRight(void);
+
+ void applyPerspectiveAdjustment(void);
+
+ Digikam::ImageIface* imageIface();
+
+public slots:
+
+ void slotToggleAntiAliasing(bool a);
+ void slotToggleDrawWhileMoving(bool draw);
+ void slotToggleDrawGrid(bool grid);
+
+ void slotChangeGuideColor(const TQColor &color);
+ void slotChangeGuideSize(int size);
+
+signals:
+
+ void signalPerspectiveChanged( TQRect newSize, float topLeftAngle, float topRightAngle,
+ float bottomLeftAngle, float bottomRightAngle );
+
+protected:
+
+ void paintEvent( TQPaintEvent *e );
+ void resizeEvent( TQResizeEvent * e );
+ void mousePressEvent ( TQMouseEvent * e );
+ void mouseReleaseEvent ( TQMouseEvent * e );
+ void mouseMoveEvent ( TQMouseEvent * e );
+
+private: // Widget methods.
+
+ void updatePixmap(void);
+
+ void transformAffine(Digikam::DImg *orgImage, Digikam::DImg *destImage,
+ const Matrix &matrix, Digikam::DColor background);
+
+ TQPoint buildPerspective(TQPoint orignTopLeft, TQPoint orignBottomRight,
+ TQPoint transTopLeft, TQPoint transTopRight,
+ TQPoint transBottomLeft, TQPoint transBottomRight,
+ Digikam::DImg *orgImage=0, Digikam::DImg *destImage=0,
+ Digikam::DColor background=Digikam::DColor());
+
+private:
+
+ enum ResizingMode
+ {
+ ResizingNone = 0,
+ ResizingTopLeft,
+ ResizingTopRight,
+ ResizingBottomLeft,
+ ResizingBottomRight
+ };
+
+ bool m_antiAlias;
+ bool m_drawWhileMoving;
+ bool m_drawGrid;
+
+ uint *m_data;
+ int m_w;
+ int m_h;
+ int m_origW;
+ int m_origH;
+
+ int m_currentResizing;
+
+ int m_guideSize;
+
+ TQRect m_rect;
+
+ // Tranformed center area for mouse position control.
+
+ TQPoint m_transformedCenter;
+
+ // Draggable local region selection corners.
+
+ TQRect m_topLeftCorner;
+ TQRect m_topRightCorner;
+ TQRect m_bottomLeftCorner;
+ TQRect m_bottomRightCorner;
+
+ TQPoint m_topLeftPoint;
+ TQPoint m_topRightPoint;
+ TQPoint m_bottomLeftPoint;
+ TQPoint m_bottomRightPoint;
+ TQPoint m_spot;
+
+ TQColor m_guideColor;
+
+ // 60 points will be stored to compute a grid of 15x15 lines.
+ TQPointArray m_grid;
+
+ TQPixmap *m_pixmap;
+
+ Digikam::ImageIface *m_iface;
+ Digikam::DImg m_previewImage;
+};
+
+} // NameSpace DigikamPerspectiveImagesPlugin
+
+#endif /* PERSPECTIVEWIDGET_H */
diff --git a/src/imageplugins/perspective/triangle.cpp b/src/imageplugins/perspective/triangle.cpp
new file mode 100644
index 00000000..84f80a07
--- /dev/null
+++ b/src/imageplugins/perspective/triangle.cpp
@@ -0,0 +1,65 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-18
+ * Description : triangle geometry calculation class.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cmath>
+
+// Local includes.
+
+#include "triangle.h"
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+Triangle::Triangle(TQPoint A, TQPoint B, TQPoint C)
+{
+ m_a = distanceP2P(B, C);
+ m_b = distanceP2P(A, C);
+ m_c = distanceP2P(A, B);
+}
+
+float Triangle::angleABC(void)
+{
+ return( 57.295779513082 * acos( (m_b*m_b - m_a*m_a - m_c*m_c ) / (-2*m_a*m_c ) ) );
+}
+
+float Triangle::angleACB(void)
+{
+ return( 57.295779513082 * acos( (m_c*m_c - m_a*m_a - m_b*m_b ) / (-2*m_a*m_b ) ) );
+}
+
+float Triangle::angleBAC(void)
+{
+ return( 57.295779513082 * acos( (m_a*m_a - m_b*m_b - m_c*m_c ) / (-2*m_b*m_c ) ) );
+}
+
+float Triangle::distanceP2P(const TQPoint& p1, const TQPoint& p2)
+{
+ return(sqrt( abs( p2.x()-p1.x() ) * abs( p2.x()-p1.x() ) +
+ abs( p2.y()-p1.y() ) * abs( p2.y()-p1.y() ) ));
+}
+
+} // NameSpace DigikamPerspectiveImagesPlugin
diff --git a/src/imageplugins/perspective/triangle.h b/src/imageplugins/perspective/triangle.h
new file mode 100644
index 00000000..27fcc087
--- /dev/null
+++ b/src/imageplugins/perspective/triangle.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-18
+ * Description : triangle geometry calculation class.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TRIANGLE_H
+#define TRIANGLE_H
+
+// TQt includes.
+
+#include <tqpoint.h>
+
+namespace DigikamPerspectiveImagesPlugin
+{
+
+class Triangle
+{
+
+public:
+
+ Triangle(TQPoint A, TQPoint B, TQPoint C);
+ ~Triangle(){};
+
+ float angleABC(void);
+ float angleACB(void);
+ float angleBAC(void);
+
+private:
+
+ float m_a;
+ float m_b;
+ float m_c;
+
+ float distanceP2P(const TQPoint& p1, const TQPoint& p2);
+};
+
+} // NameSpace DigikamPerspectiveImagesPlugin
+
+#endif /* TRIANGLE_H */
diff --git a/src/imageplugins/raindrop/Makefile.am b/src/imageplugins/raindrop/Makefile.am
new file mode 100644
index 00000000..29899232
--- /dev/null
+++ b/src/imageplugins/raindrop/Makefile.am
@@ -0,0 +1,34 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_raindrop_la_SOURCES = imageplugin_raindrop.cpp \
+ raindroptool.cpp raindrop.cpp
+
+digikamimageplugin_raindrop_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_raindrop_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_raindrop.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_raindrop.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_raindrop_ui.rc
+
diff --git a/src/imageplugins/raindrop/digikamimageplugin_raindrop.desktop b/src/imageplugins/raindrop/digikamimageplugin_raindrop.desktop
new file mode 100644
index 00000000..847411b7
--- /dev/null
+++ b/src/imageplugins/raindrop/digikamimageplugin_raindrop.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_RainDrop
+Name[bg]=Приставка за снимки - Дъждовни капки
+Name[da]=Billedplugin_Regndråber
+Name[el]=ΠροσθετοΕικόνας_Βροχή
+Name[fi]=Sadepisarat
+Name[hr]=Kišne kapi
+Name[it]=PluginImmagini_GocciaDiPioggia
+Name[nl]=Afbeeldingsplugin_Regendruppels
+Name[sr]=Кишне капи
+Name[sr@Latn]=Kišne kapi
+Name[sv]=Insticksprogram för regndroppar
+Name[tr]=ResimEklentisi_YağmurDamlaları
+Name[vi]=ImagePlugin_Perspective
+Name[xx]=xxImagePlugin_RainDropxx
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Rain dropping image effect plugin for digiKam
+Comment[bg]=Приставка на digiKam за добавяне на дъждовни капки
+Comment[ca]=Connector pel digiKam d'efecte d'imatge de gotes de pluja
+Comment[da]=Plugin til regndråbeeffekt på billeder i Digikam
+Comment[de]=digiKam-Modul zum Erzeugen eines Regentropfeneffektes
+Comment[el]=Πρόσθετο εφέ πτώσης βροχής για το digiKam
+Comment[es]=Plugin para digiKam de efectos de gotas de lluvia
+Comment[et]=DigiKami vihmapiiskade pildiefektiplugin
+Comment[fa]=وصلۀ جلوۀ تصویر بارش باران برای digiKam
+Comment[fi]=Lisää kuvan pintaan sadepisaroita
+Comment[gl]=Un plugin de digiKam para criar un efeito de pingas de chuvia
+Comment[hr]=digiKam dodatak za efekt padanja kiše
+Comment[is]=Íforrit fyrir digiKam sem setur inn regndropa!!!
+Comment[it]=Plugin per l'effetto a goccia di pioggia delle immagini per digiKam
+Comment[ja]=digiKam 雨滴効果プラグイン
+Comment[ms]=Plugin kesan imej titis hujan untuk digiKam
+Comment[nds]=digiKam-Moduul för Regendrüppeneffekten
+Comment[nl]=Digikam-plugin voor regendruppels
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਕਣੀ ਚਿੱਤਰ ਪਰਭਾਵ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam dodająca efekt kropel deszczu na obrazie
+Comment[pt]=Um 'plugin' do digiKam para criar um efeito de pingos de chuva
+Comment[pt_BR]=Um 'plugin' do digiKam para criar um efeito de pingos de chuva
+Comment[ru]=Модуль эффект "идущего дождя" для digiKam
+Comment[sk]=digiKam plugin pre efekt padajúcich kvapiek
+Comment[sr]=digiKam-ов прикључак за ефекат кишних капи у слици
+Comment[sr@Latn]=digiKam-ov priključak za efekat kišnih kapi u slici
+Comment[sv]=Digikam insticksprogram för regndroppsbildeffekt
+Comment[tr]=digiKam için yağmur damlaları eklentisi
+Comment[uk]=Втулок ефекту падання крапель для digiKam
+Comment[vi]=Phần bổ sung hiệu ứng ảnh giọt mưa cho digiKam
+Comment[xx]=xxRain dropping image effect plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_raindrop
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/raindrop/digikamimageplugin_raindrop_ui.rc b/src/imageplugins/raindrop/digikamimageplugin_raindrop_ui.rc
new file mode 100644
index 00000000..cf28a3f6
--- /dev/null
+++ b/src/imageplugins/raindrop/digikamimageplugin_raindrop_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_raindrop" >
+
+ <MenuBar>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ <Action name="imageplugin_raindrop" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_raindrop" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/raindrop/imageeffect_raindrop.cpp b/src/imageplugins/raindrop/imageeffect_raindrop.cpp
new file mode 100644
index 00000000..8a82829d
--- /dev/null
+++ b/src/imageplugins/raindrop/imageeffect_raindrop.cpp
@@ -0,0 +1,259 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-30
+ * Description : a plugin to add rain drop over an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "raindrop.h"
+#include "imageeffect_raindrop.h"
+#include "imageeffect_raindrop.moc"
+
+namespace DigikamRainDropImagesPlugin
+{
+
+ImageEffect_RainDrop::ImageEffect_RainDrop(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Add Raindrops to Photograph"),
+ "raindrops", false, true, false,
+ Digikam::ImageGuideWidget::HVGuideMode)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Raindrops"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to add raindrops to an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pieter Z. Voloshyn", I18N_NOOP("Raindrops algorithm"),
+ "pieter dot voloshyn at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ TQWhatsThis::add( m_imagePreviewWidget, i18n("<p>This is the preview of the Raindrop effect."
+ "<p>Note: if you have previously selected an area in the editor, "
+ "this will be unaffected by the filter. You can use this method to "
+ "disable the Raindrops effect on a human face, for example.") );
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 5, 2, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Drop size:"), gboxSettings);
+
+ m_dropInput = new KIntNumInput(gboxSettings);
+ m_dropInput->setRange(0, 200, 1, true);
+ m_dropInput->setValue(80);
+ TQWhatsThis::add( m_dropInput, i18n("<p>Set here the raindrops' size."));
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 2);
+ gridSettings->addMultiCellWidget(m_dropInput, 1, 1, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Number:"), gboxSettings);
+
+ m_amountInput = new KIntNumInput(gboxSettings);
+ m_amountInput->setRange(1, 500, 1, true);
+ m_amountInput->setValue(150);
+ TQWhatsThis::add( m_amountInput, i18n("<p>This value controls the maximum number of raindrops."));
+
+ gridSettings->addMultiCellWidget(label2, 2, 2, 0, 2);
+ gridSettings->addMultiCellWidget(m_amountInput, 3, 3, 0, 2);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Fish eyes:"), gboxSettings);
+
+ m_coeffInput = new KIntNumInput(gboxSettings);
+ m_coeffInput->setRange(1, 100, 1, true);
+ m_coeffInput->setValue(30);
+ TQWhatsThis::add( m_coeffInput, i18n("<p>This value is the fish-eye-effect optical "
+ "distortion coefficient."));
+
+ gridSettings->addMultiCellWidget(label3, 4, 4, 0, 2);
+ gridSettings->addMultiCellWidget(m_coeffInput, 5, 5, 0, 2);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_dropInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_amountInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_coeffInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_RainDrop::~ImageEffect_RainDrop()
+{
+}
+
+void ImageEffect_RainDrop::renderingFinished()
+{
+ m_dropInput->setEnabled(true);
+ m_amountInput->setEnabled(true);
+ m_coeffInput->setEnabled(true);
+}
+
+void ImageEffect_RainDrop::readUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("raindrops Tool Dialog");
+
+ m_dropInput->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_coeffInput->blockSignals(true);
+
+ m_dropInput->setValue(config->readNumEntry("DropAdjustment", 80));
+ m_amountInput->setValue(config->readNumEntry("AmountAdjustment", 150));
+ m_coeffInput->setValue(config->readNumEntry("CoeffAdjustment", 30));
+
+ m_dropInput->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_coeffInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void ImageEffect_RainDrop::writeUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("raindrops Tool Dialog");
+ config->writeEntry("DropAdjustment", m_dropInput->value());
+ config->writeEntry("AmountAdjustment", m_amountInput->value());
+ config->writeEntry("CoeffAdjustment", m_coeffInput->value());
+ config->sync();
+}
+
+void ImageEffect_RainDrop::resetValues()
+{
+ m_dropInput->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_coeffInput->blockSignals(true);
+
+ m_dropInput->setValue(80);
+ m_amountInput->setValue(150);
+ m_coeffInput->setValue(30);
+
+ m_dropInput->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_coeffInput->blockSignals(false);
+}
+
+void ImageEffect_RainDrop::prepareEffect()
+{
+ m_dropInput->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_coeffInput->setEnabled(false);
+
+ int d = m_dropInput->value();
+ int a = m_amountInput->value();
+ int c = m_coeffInput->value();
+
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+
+ // Selected data from the image
+ TQRect selection( iface->selectedXOrg(), iface->selectedYOrg(),
+ iface->selectedWidth(), iface->selectedHeight() );
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new RainDrop(iface->getOriginalImg(), this, d, a, c, &selection));
+}
+
+void ImageEffect_RainDrop::prepareFinal()
+{
+ m_dropInput->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_coeffInput->setEnabled(false);
+
+ int d = m_dropInput->value();
+ int a = m_amountInput->value();
+ int c = m_coeffInput->value();
+
+ Digikam::ImageIface iface(0, 0);
+
+ // Selected data from the image
+ TQRect selection( iface.selectedXOrg(), iface.selectedYOrg(),
+ iface.selectedWidth(), iface.selectedHeight() );
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new RainDrop(iface.getOriginalImg(), this, d, a, c, &selection));
+}
+
+void ImageEffect_RainDrop::putPreviewData(void)
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage()
+ .smoothScale(iface->previewWidth(), iface->previewHeight());
+ iface->putPreviewImage(imDest.bits());
+
+ m_imagePreviewWidget->updatePreview();
+}
+
+void ImageEffect_RainDrop::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("RainDrop"),
+ m_threadedFilter->getTargetImage().bits());
+}
+
+} // NameSpace DigikamRainDropImagesPlugin
+
diff --git a/src/imageplugins/raindrop/imageeffect_raindrop.h b/src/imageplugins/raindrop/imageeffect_raindrop.h
new file mode 100644
index 00000000..fe567ba3
--- /dev/null
+++ b/src/imageplugins/raindrop/imageeffect_raindrop.h
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-30
+ * Description : a plugin to add rain drop over an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_RAINDROP_H
+#define IMAGEEFFECT_RAINDROP_H
+
+// Digikam includes.
+
+#include "imageguidedlg.h"
+
+class KIntNumInput;
+
+namespace DigikamRainDropImagesPlugin
+{
+
+class ImageEffect_RainDrop : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_RainDrop(TQWidget *parent);
+ ~ImageEffect_RainDrop();
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KIntNumInput *m_dropInput;
+ KIntNumInput *m_amountInput;
+ KIntNumInput *m_coeffInput;
+};
+
+} // NameSpace DigikamRainDropImagesPlugin
+
+#endif /* IMAGEEFFECT_RAINDROP_H */
diff --git a/src/imageplugins/raindrop/imageplugin_raindrop.cpp b/src/imageplugins/raindrop/imageplugin_raindrop.cpp
new file mode 100644
index 00000000..20744961
--- /dev/null
+++ b/src/imageplugins/raindrop/imageplugin_raindrop.cpp
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-30
+ * Description : a plugin to add rain drop over an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "raindroptool.h"
+#include "imageplugin_raindrop.h"
+#include "imageplugin_raindrop.moc"
+
+using namespace DigikamRainDropImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_raindrop,
+ KGenericFactory<ImagePlugin_RainDrop>("digikamimageplugin_raindrop"));
+
+ImagePlugin_RainDrop::ImagePlugin_RainDrop(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_RainDrop")
+{
+ m_raindropAction = new TDEAction(i18n("Raindrops..."), "raindrop", 0,
+ this, TQ_SLOT(slotRainDrop()),
+ actionCollection(), "imageplugin_raindrop");
+
+ setXMLFile( "digikamimageplugin_raindrop_ui.rc" );
+
+ DDebug() << "ImagePlugin_RainDrop plugin loaded" << endl;
+}
+
+ImagePlugin_RainDrop::~ImagePlugin_RainDrop()
+{
+}
+
+void ImagePlugin_RainDrop::setEnabledActions(bool enable)
+{
+ m_raindropAction->setEnabled(enable);
+}
+
+void ImagePlugin_RainDrop::slotRainDrop()
+{
+ RainDropTool *raindrop = new RainDropTool(this);
+ loadTool(raindrop);
+}
diff --git a/src/imageplugins/raindrop/imageplugin_raindrop.h b/src/imageplugins/raindrop/imageplugin_raindrop.h
new file mode 100644
index 00000000..a85076f8
--- /dev/null
+++ b/src/imageplugins/raindrop/imageplugin_raindrop.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-30
+ * Description : a plugin to add rain drop over an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_RAINDROP_H
+#define IMAGEPLUGIN_RAINDROP_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_RainDrop : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_RainDrop(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_RainDrop();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotRainDrop();
+
+private:
+
+ TDEAction *m_raindropAction;
+};
+
+#endif /* IMAGEPLUGIN_RAINDROP_H */
diff --git a/src/imageplugins/raindrop/raindrop.cpp b/src/imageplugins/raindrop/raindrop.cpp
new file mode 100644
index 00000000..7be977fc
--- /dev/null
+++ b/src/imageplugins/raindrop/raindrop.cpp
@@ -0,0 +1,457 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Raindrop threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Original RainDrop algorithm copyrighted 2004-2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqdeepcopy.h>
+#include <tqdatetime.h>
+#include <tqrect.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "raindrop.h"
+
+namespace DigikamRainDropImagesPlugin
+{
+
+RainDrop::RainDrop(Digikam::DImg *orgImage, TQObject *parent, int drop,
+ int amount, int coeff, TQRect *selection)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "RainDrop")
+{
+ m_drop = drop;
+ m_amount = amount;
+ m_coeff = coeff;
+
+ m_selectedX = m_selectedY = m_selectedW = m_selectedH = 0;
+
+ if ( selection )
+ {
+ m_selectedX = selection->left();
+ m_selectedY = selection->top();
+ m_selectedW = selection->width();
+ m_selectedH = selection->height();
+ }
+
+ initFilter();
+}
+
+void RainDrop::filterImage(void)
+{
+ int w = m_orgImage.width();
+ int h = m_orgImage.height();
+
+ // If we have a region selection in image, use it to apply the filter modification around,
+ // else, applied the filter on the full image.
+
+ if (m_selectedW && m_selectedH)
+ {
+ Digikam::DImg zone1, zone2, zone3, zone4,
+ zone1Dest, zone2Dest, zone3Dest, zone4Dest,
+ selectedImg;
+ selectedImg = m_orgImage.copy(m_selectedX, m_selectedY, m_selectedW, m_selectedH);
+
+ // Cut the original image in 4 areas without clipping region.
+
+ zone1 = m_orgImage.copy(0, 0, m_selectedX, w);
+ zone2 = m_orgImage.copy(m_selectedX, 0, m_selectedX + m_selectedW, m_selectedY);
+ zone3 = m_orgImage.copy(m_selectedX, m_selectedY + m_selectedH, m_selectedX + m_selectedW, h);
+ zone4 = m_orgImage.copy(m_selectedX + m_selectedW, 0, w, h);
+
+ zone1Dest = Digikam::DImg(zone1.width(), zone1.height(), zone1.sixteenBit(), zone1.hasAlpha());
+ zone2Dest = Digikam::DImg(zone2.width(), zone2.height(), zone2.sixteenBit(), zone2.hasAlpha());
+ zone3Dest = Digikam::DImg(zone3.width(), zone3.height(), zone3.sixteenBit(), zone3.hasAlpha());
+ zone4Dest = Digikam::DImg(zone4.width(), zone4.height(), zone4.sixteenBit(), zone4.hasAlpha());
+
+ // Apply effect on each area.
+
+ rainDropsImage(&zone1, &zone1Dest, 0, m_drop, m_amount, m_coeff, true, 0, 25);
+ rainDropsImage(&zone2, &zone2Dest, 0, m_drop, m_amount, m_coeff, true, 25, 50);
+ rainDropsImage(&zone3, &zone3Dest, 0, m_drop, m_amount, m_coeff, true, 50, 75);
+ rainDropsImage(&zone4, &zone4Dest, 0, m_drop, m_amount, m_coeff, true, 75, 100);
+
+ // Build the target image.
+
+ m_destImage.bitBltImage(&zone1Dest, 0, 0);
+ m_destImage.bitBltImage(&zone2Dest, m_selectedX, 0);
+ m_destImage.bitBltImage(&zone3Dest, m_selectedX, m_selectedY + m_selectedH);
+ m_destImage.bitBltImage(&zone4Dest, m_selectedX + m_selectedW, 0);
+ m_destImage.bitBltImage(&selectedImg, m_selectedX, m_selectedY);
+ }
+ else
+ {
+ rainDropsImage(&m_orgImage, &m_destImage, 0, m_drop, m_amount, m_coeff, true, 0, 100);
+ }
+}
+
+/* Function to apply the RainDrops effect backported from ImageProcessing version 2
+ *
+ * orgImage => The image
+ * MinDropSize => It's the minimum random size for rain drop.
+ * MaxDropSize => It's the minimum random size for rain drop.
+ * Amount => It's the maximum number for rain drops inside the image.
+ * Coeff => It's the fisheye's coefficient.
+ * bLimitRange => If true, the drop will not be cut.
+ * progressMin => Min. value for progress bar (can be different if using clipping area).
+ * progressMax => Max. value for progress bar (can be different if using clipping area).
+ *
+ * Theory => This functions does several math's functions and the engine
+ * is simple to undestand, but a little hard to implement. A
+ * control will indicate if there is or not a raindrop in that
+ * area, if not, a fisheye effect with a random size (max=MaxDropSize)
+ * will be applied, after this, a shadow will be applied too.
+ * and after this, a blur function will finish the effect.
+ */
+void RainDrop::rainDropsImage(Digikam::DImg *orgImage, Digikam::DImg *destImage, int MinDropSize, int MaxDropSize,
+ int Amount, int Coeff, bool bLimitRange, int progressMin, int progressMax)
+{
+ bool bResp;
+ int nRandSize, i;
+ int nRandX, nRandY;
+ int nCounter = 0;
+ int nWidth = orgImage->width();
+ int nHeight = orgImage->height();
+ bool sixteenBit = orgImage->sixteenBit();
+ int bytesDepth = orgImage->bytesDepth();
+ uchar *data = orgImage->bits();
+ uchar *pResBits = destImage->bits();
+
+ if (Amount <= 0)
+ return;
+
+ if (MinDropSize >= MaxDropSize)
+ MaxDropSize = MinDropSize + 1;
+
+ if (MaxDropSize <= 0)
+ return;
+
+ uchar *pStatusBits = new uchar[nHeight * nWidth];
+ memset(pStatusBits, 0, sizeof(nHeight * nWidth));
+
+ // Initially, copy all pixels to destination
+
+ destImage->bitBltImage(orgImage, 0, 0);
+
+ // Randomize.
+
+ TQDateTime dt = TQDateTime::currentDateTime();
+ TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
+ uint seed = dt.secsTo(Y2000);
+
+ for (i = 0; !m_cancel && (i < Amount); i++)
+ {
+ nCounter = 0;
+
+ do
+ {
+ nRandX = (int)(rand_r(&seed) * ((double)( nWidth - 1) / RAND_MAX));
+ nRandY = (int)(rand_r(&seed) * ((double)(nHeight - 1) / RAND_MAX));
+
+ nRandSize = (rand() % (MaxDropSize - MinDropSize)) + MinDropSize;
+
+ bResp = CreateRainDrop (data, nWidth, nHeight, sixteenBit, bytesDepth,
+ pResBits, pStatusBits,
+ nRandX, nRandY, nRandSize, Coeff, bLimitRange);
+
+ nCounter++;
+ }
+ while ((bResp == false) && (nCounter < 10000) && !m_cancel);
+
+ // Update the progress bar in dialog.
+ if (nCounter >= 10000)
+ {
+ i = Amount;
+
+ postProgress(progressMax);
+ break;
+ }
+
+ postProgress( (int)(progressMin + ((double)(i) *
+ (double)(progressMax-progressMin)) / (double)Amount) );
+ }
+
+ delete [] pStatusBits;
+}
+
+bool RainDrop::CreateRainDrop(uchar *pBits, int Width, int Height, bool sixteenBit, int bytesDepth,
+ uchar *pResBits, uchar* pStatusBits,
+ int X, int Y, int DropSize, double Coeff, bool bLimitRange)
+{
+ int w, h, nw1, nh1, nw2, nh2;
+ int nHalfSize = DropSize / 2;
+ int nBright;
+ double lfRadius, lfOldRadius, lfAngle, lfDiv;
+
+ Digikam::DColor imageData;
+
+ uint nTotalR, nTotalG, nTotalB, offset;
+ int nBlurPixels, nBlurRadius;
+
+ if (CanBeDropped(Width, Height, pStatusBits, X, Y, DropSize, bLimitRange))
+ {
+ Coeff *= 0.01;
+ lfDiv = (double)nHalfSize / log (Coeff * (double)nHalfSize + 1.0);
+
+ for (h = -nHalfSize; !m_cancel && (h <= nHalfSize); h++)
+ {
+ for (w = -nHalfSize; !m_cancel && (w <= nHalfSize); w++)
+ {
+ lfRadius = sqrt (h * h + w * w);
+ lfAngle = atan2 ((double)h, (double)w);
+
+ if (lfRadius <= (double)nHalfSize)
+ {
+ lfOldRadius = lfRadius;
+ lfRadius = (exp (lfRadius / lfDiv) - 1.0) / Coeff;
+
+ nw1 = (int)((double)X + lfRadius * cos (lfAngle));
+ nh1 = (int)((double)Y + lfRadius * sin (lfAngle));
+
+ nw2 = X + w;
+ nh2 = Y + h;
+
+ if (IsInside(Width, Height, nw1, nh1))
+ {
+ if (IsInside(Width, Height, nw2, nh2))
+ {
+ nBright = 0;
+
+ if (lfOldRadius >= 0.9 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.0) && (lfAngle < 2.25))
+ nBright = -80;
+ else if ((lfAngle >= 2.25) && (lfAngle < 2.5))
+ nBright = -40;
+ else if ((lfAngle >= -0.25) && (lfAngle < 0.0))
+ nBright = -40;
+ }
+
+ else if (lfOldRadius >= 0.8 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.75) && (lfAngle < 1.50))
+ nBright = -40;
+ else if ((lfAngle >= -0.10) && (lfAngle < 0.75))
+ nBright = -30;
+ else if ((lfAngle >= 1.50) && (lfAngle < 2.35))
+ nBright = -30;
+ }
+
+ else if (lfOldRadius >= 0.7 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.10) && (lfAngle < 2.0))
+ nBright = -20;
+ else if ((lfAngle >= -2.50) && (lfAngle < -1.90))
+ nBright = 60;
+ }
+
+ else if (lfOldRadius >= 0.6 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.50) && (lfAngle < 1.75))
+ nBright = -20;
+ else if ((lfAngle >= 0.0) && (lfAngle < 0.25))
+ nBright = 20;
+ else if ((lfAngle >= 2.0) && (lfAngle < 2.25))
+ nBright = 20;
+ }
+
+ else if (lfOldRadius >= 0.5 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.25) && (lfAngle < 0.50))
+ nBright = 30;
+ else if ((lfAngle >= 1.75 ) && (lfAngle < 2.0))
+ nBright = 30;
+ }
+
+ else if (lfOldRadius >= 0.4 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.5) && (lfAngle < 1.75))
+ nBright = 40;
+ }
+
+ else if (lfOldRadius >= 0.3 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.0) && (lfAngle < 2.25))
+ nBright = 30;
+ }
+
+ else if (lfOldRadius >= 0.2 * (double)nHalfSize)
+ {
+ if ((lfAngle >= 0.5) && (lfAngle < 1.75))
+ nBright = 20;
+ }
+
+ imageData.setColor(pBits + Offset(Width, nw1, nh1, bytesDepth), sixteenBit);
+
+ if (sixteenBit)
+ {
+ // convert difference to 16-bit range
+ if (nBright > 0)
+ nBright = (nBright + 1) * 256 - 1;
+ else
+ nBright = (nBright - 1) * 256 + 1;
+
+ imageData.setRed (LimitValues16(imageData.red() + nBright));
+ imageData.setGreen(LimitValues16(imageData.green() + nBright));
+ imageData.setBlue (LimitValues16(imageData.blue() + nBright));
+ }
+ else
+ {
+ imageData.setRed (LimitValues8(imageData.red() + nBright));
+ imageData.setGreen(LimitValues8(imageData.green() + nBright));
+ imageData.setBlue (LimitValues8(imageData.blue() + nBright));
+ }
+
+ imageData.setPixel(pResBits + Offset(Width, nw2, nh2, bytesDepth));
+
+ }
+ }
+ }
+ }
+ }
+
+ nBlurRadius = DropSize / 25 + 1;
+
+ for (h = -nHalfSize - nBlurRadius; !m_cancel && (h <= nHalfSize + nBlurRadius); h++)
+ {
+ for (w = -nHalfSize - nBlurRadius; !m_cancel && (w <= nHalfSize + nBlurRadius); w++)
+ {
+ lfRadius = sqrt (h * h + w * w);
+
+ if (lfRadius <= (double)nHalfSize * 1.1)
+ {
+ nTotalR = nTotalG = nTotalB = 0;
+ nBlurPixels = 0;
+
+ for (nh1 = -nBlurRadius; !m_cancel && (nh1 <= nBlurRadius); nh1++)
+ {
+ for (nw1 = -nBlurRadius; !m_cancel && (nw1 <= nBlurRadius); nw1++)
+ {
+ nw2 = X + w + nw1;
+ nh2 = Y + h + nh1;
+
+ if (IsInside (Width, Height, nw2, nh2))
+ {
+ imageData.setColor(pResBits + Offset(Width, nw2, nh2, bytesDepth), sixteenBit);
+
+ nTotalR += imageData.red();
+ nTotalG += imageData.green();
+ nTotalB += imageData.blue();
+ nBlurPixels++;
+ }
+ }
+ }
+
+ nw1 = X + w;
+ nh1 = Y + h;
+
+ if (IsInside (Width, Height, nw1, nh1))
+ {
+ offset = Offset(Width, nw1, nh1, bytesDepth);
+
+ // to preserve alpha channel
+ imageData.setColor(pResBits + offset, sixteenBit);
+
+ imageData.setRed (nTotalR / nBlurPixels);
+ imageData.setGreen(nTotalG / nBlurPixels);
+ imageData.setBlue (nTotalB / nBlurPixels);
+
+ imageData.setPixel(pResBits + offset);
+ }
+ }
+ }
+ }
+
+ SetDropStatusBits (Width, Height, pStatusBits, X, Y, DropSize);
+ }
+ else
+ return (false);
+
+ return (true);
+}
+
+
+bool RainDrop::CanBeDropped(int Width, int Height, uchar *pStatusBits, int X, int Y,
+ int DropSize, bool bLimitRange)
+{
+ int w, h, i = 0;
+ int nHalfSize = DropSize / 2;
+
+ if (pStatusBits == NULL)
+ return (true);
+
+ for (h = Y - nHalfSize; h <= Y + nHalfSize; h++)
+ {
+ for (w = X - nHalfSize; w <= X + nHalfSize; w++)
+ {
+ if (IsInside (Width, Height, w, h))
+ {
+ i = h * Width + w;
+ if (pStatusBits[i])
+ return (false);
+ }
+ else
+ {
+ if (bLimitRange)
+ return (false);
+ }
+ }
+ }
+
+ return (true);
+}
+
+bool RainDrop::SetDropStatusBits (int Width, int Height, uchar *pStatusBits,
+ int X, int Y, int DropSize)
+{
+ int w, h, i = 0;
+ int nHalfSize = DropSize / 2;
+
+ if (pStatusBits == NULL)
+ return (false);
+
+ for (h = Y - nHalfSize; h <= Y + nHalfSize; h++)
+ {
+ for (w = X - nHalfSize; w <= X + nHalfSize; w++)
+ {
+ if (IsInside (Width, Height, w, h))
+ {
+ i = h * Width + w;
+ pStatusBits[i] = 255;
+ }
+ }
+ }
+
+ return (true);
+}
+
+} // NameSpace DigikamRainDropImagesPlugin
diff --git a/src/imageplugins/raindrop/raindrop.h b/src/imageplugins/raindrop/raindrop.h
new file mode 100644
index 00000000..9baca0d1
--- /dev/null
+++ b/src/imageplugins/raindrop/raindrop.h
@@ -0,0 +1,105 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Raindrop threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAINDROP_H
+#define RAINDROP_H
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+class TQRect;
+
+namespace DigikamRainDropImagesPlugin
+{
+
+class RainDrop : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ RainDrop(Digikam::DImg *orgImage, TQObject *parent=0, int drop=80,
+ int amount=150, int coeff=30, TQRect *selection=0L);
+
+ ~RainDrop(){};
+
+private:
+
+ virtual void filterImage(void);
+
+ void rainDropsImage(Digikam::DImg *orgImage, Digikam::DImg *destImage, int MinDropSize, int MaxDropSize,
+ int Amount, int Coeff, bool bLimitRange, int progressMin, int progressMax);
+
+ bool CreateRainDrop(uchar *pBits, int Width, int Height, bool sixteenBit, int bytesDepth,
+ uchar *pResBits, uchar* pStatusBits,
+ int X, int Y, int DropSize, double Coeff, bool bLimitRange);
+
+ bool CanBeDropped(int Width, int Height, uchar *pStatusBits, int X, int Y, int DropSize, bool bLimitRange);
+
+ bool SetDropStatusBits (int Width, int Height, uchar *pStatusBits, int X, int Y, int DropSize);
+
+ // A color is represented in RGB value (e.g. 0xFFFFFF is white color).
+ // But R, G and B values has 256 values to be used so, this function analize
+ // the value and limits to this range.
+ inline int LimitValues8(int ColorValue)
+ {
+ if (ColorValue > 255) ColorValue = 255;
+ if (ColorValue < 0) ColorValue = 0;
+ return ColorValue;
+ };
+
+ inline int LimitValues16(int ColorValue)
+ {
+ if (ColorValue > 65535) ColorValue = 65535;
+ if (ColorValue < 0) ColorValue = 0;
+ return ColorValue;
+ };
+
+ inline bool IsInside (int Width, int Height, int X, int Y)
+ {
+ bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true);
+ bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true);
+ return (bIsWOk && bIsHOk);
+ };
+
+ inline int Offset(int Width, int X, int Y, int bytesDepth)
+ {
+ return (Y * Width * bytesDepth + X * bytesDepth);
+ };
+
+private:
+
+ int m_drop;
+ int m_amount;
+ int m_coeff;
+
+ int m_selectedX;
+ int m_selectedY;
+ int m_selectedW;
+ int m_selectedH;
+};
+
+} // NameSpace DigikamRainDropImagesPlugin
+
+#endif /* RAINDROP_H */
diff --git a/src/imageplugins/raindrop/raindroptool.cpp b/src/imageplugins/raindrop/raindroptool.cpp
new file mode 100644
index 00000000..3d19238d
--- /dev/null
+++ b/src/imageplugins/raindrop/raindroptool.cpp
@@ -0,0 +1,254 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-30
+ * Description : a plugin to add rain drop over an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqframe.h>
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "editortoolsettings.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "raindrop.h"
+#include "raindroptool.h"
+#include "raindroptool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamRainDropImagesPlugin
+{
+
+RainDropTool::RainDropTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("raindrops");
+ setToolName(i18n("Raindrops"));
+ setToolIcon(SmallIcon("raindrop"));
+
+ m_previewWidget = new ImageWidget("raindrops Tool", 0,
+ i18n("<p>This is the preview of the Raindrop effect."
+ "<p>Note: if you have previously selected an area in the editor, "
+ "this will be unaffected by the filter. You can use this method to "
+ "disable the Raindrops effect on a human face, for example."),
+ false);
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQGridLayout* gridSettings = new TQGridLayout( m_gboxSettings->plainPage(), 7, 2);
+
+ TQLabel *label1 = new TQLabel(i18n("Drop size:"), m_gboxSettings->plainPage());
+
+ m_dropInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_dropInput->setRange(0, 200, 1);
+ m_dropInput->setDefaultValue(80);
+ TQWhatsThis::add( m_dropInput, i18n("<p>Set here the raindrops' size."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Number:"), m_gboxSettings->plainPage());
+
+ m_amountInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_amountInput->setRange(1, 500, 1);
+ m_amountInput->setDefaultValue(150);
+ TQWhatsThis::add( m_amountInput, i18n("<p>This value controls the maximum number of raindrops."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label3 = new TQLabel(i18n("Fish eyes:"), m_gboxSettings->plainPage());
+
+ m_coeffInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_coeffInput->setRange(1, 100, 1);
+ m_coeffInput->setDefaultValue(30);
+ TQWhatsThis::add( m_coeffInput, i18n("<p>This value is the fish-eye-effect optical "
+ "distortion coefficient."));
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 1);
+ gridSettings->addMultiCellWidget(m_dropInput, 1, 1, 0, 1);
+ gridSettings->addMultiCellWidget(label2, 2, 2, 0, 1);
+ gridSettings->addMultiCellWidget(m_amountInput, 3, 3, 0, 1);
+ gridSettings->addMultiCellWidget(label3, 4, 4, 0, 1);
+ gridSettings->addMultiCellWidget(m_coeffInput, 5, 5, 0, 1);
+ gridSettings->setRowStretch(6, 10);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_dropInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_amountInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_coeffInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+RainDropTool::~RainDropTool()
+{
+}
+
+void RainDropTool::renderingFinished()
+{
+ m_dropInput->setEnabled(true);
+ m_amountInput->setEnabled(true);
+ m_coeffInput->setEnabled(true);
+}
+
+void RainDropTool::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("raindrops Tool");
+
+ m_dropInput->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_coeffInput->blockSignals(true);
+
+ m_dropInput->setValue(config->readNumEntry("DropAdjustment", m_dropInput->defaultValue()));
+ m_amountInput->setValue(config->readNumEntry("AmountAdjustment", m_amountInput->defaultValue()));
+ m_coeffInput->setValue(config->readNumEntry("CoeffAdjustment", m_coeffInput->defaultValue()));
+
+ m_dropInput->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_coeffInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void RainDropTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("raindrops Tool");
+ config->writeEntry("DropAdjustment", m_dropInput->value());
+ config->writeEntry("AmountAdjustment", m_amountInput->value());
+ config->writeEntry("CoeffAdjustment", m_coeffInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void RainDropTool::slotResetSettings()
+{
+ m_dropInput->blockSignals(true);
+ m_amountInput->blockSignals(true);
+ m_coeffInput->blockSignals(true);
+
+ m_dropInput->slotReset();
+ m_amountInput->slotReset();
+ m_coeffInput->slotReset();
+
+ m_dropInput->blockSignals(false);
+ m_amountInput->blockSignals(false);
+ m_coeffInput->blockSignals(false);
+
+ slotEffect();
+}
+
+void RainDropTool::prepareEffect()
+{
+ m_dropInput->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_coeffInput->setEnabled(false);
+
+ int d = m_dropInput->value();
+ int a = m_amountInput->value();
+ int c = m_coeffInput->value();
+
+ ImageIface* iface = m_previewWidget->imageIface();
+
+ // Selected data from the image
+ TQRect selection(iface->selectedXOrg(), iface->selectedYOrg(),
+ iface->selectedWidth(), iface->selectedHeight());
+
+ setFilter(dynamic_cast<DImgThreadedFilter *>
+ (new RainDrop(iface->getOriginalImg(), this, d, a, c, &selection)));
+}
+
+void RainDropTool::prepareFinal()
+{
+ m_dropInput->setEnabled(false);
+ m_amountInput->setEnabled(false);
+ m_coeffInput->setEnabled(false);
+
+ int d = m_dropInput->value();
+ int a = m_amountInput->value();
+ int c = m_coeffInput->value();
+
+ ImageIface iface(0, 0);
+
+ // Selected data from the image
+ TQRect selection(iface.selectedXOrg(), iface.selectedYOrg(),
+ iface.selectedWidth(), iface.selectedHeight());
+
+ setFilter(dynamic_cast<DImgThreadedFilter *>
+ (new RainDrop(iface.getOriginalImg(), this, d, a, c, &selection)));
+}
+
+void RainDropTool::putPreviewData(void)
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+
+ DImg imDest = filter()->getTargetImage()
+ .smoothScale(iface->previewWidth(), iface->previewHeight());
+ iface->putPreviewImage(imDest.bits());
+
+ m_previewWidget->updatePreview();
+}
+
+void RainDropTool::putFinalData(void)
+{
+ ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("RainDrop"), filter()->getTargetImage().bits());
+}
+
+} // NameSpace DigikamRainDropImagesPlugin
+
diff --git a/src/imageplugins/raindrop/raindroptool.h b/src/imageplugins/raindrop/raindroptool.h
new file mode 100644
index 00000000..d083eb9c
--- /dev/null
+++ b/src/imageplugins/raindrop/raindroptool.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-30
+ * Description : a plugin to add rain drop over an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAINDROPTOOL_H
+#define RAINDROPTOOL_H
+
+// Digikam includes.
+
+#include "editortool.h"
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+}
+
+namespace Digikam
+{
+class ImageWidget;
+}
+
+namespace DigikamRainDropImagesPlugin
+{
+
+class RainDropTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ RainDropTool(TQObject *parent);
+ ~RainDropTool();
+
+private slots:
+
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ KDcrawIface::RIntNumInput *m_dropInput;
+ KDcrawIface::RIntNumInput *m_amountInput;
+ KDcrawIface::RIntNumInput *m_coeffInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamRainDropImagesPlugin
+
+#endif /* RAINDROPTOOL_H */
diff --git a/src/imageplugins/restoration/Makefile.am b/src/imageplugins/restoration/Makefile.am
new file mode 100644
index 00000000..695609be
--- /dev/null
+++ b/src/imageplugins/restoration/Makefile.am
@@ -0,0 +1,35 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/greycstoration \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_restoration_la_SOURCES = imageplugin_restoration.cpp \
+ restorationtool.cpp
+
+digikamimageplugin_restoration_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_restoration_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -no-undefined -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_restoration.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_restoration.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_restoration_ui.rc
+
diff --git a/src/imageplugins/restoration/digikamimageplugin_restoration.desktop b/src/imageplugins/restoration/digikamimageplugin_restoration.desktop
new file mode 100644
index 00000000..8d0e7bd8
--- /dev/null
+++ b/src/imageplugins/restoration/digikamimageplugin_restoration.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_Restoration
+Name[bg]=Приставка за снимки - Възстановяване
+Name[da]=Plugin for billedrestaurering
+Name[el]=ΠρόσθετοΕικόνας_Αποκατάσταση
+Name[fi]=Restaurointi
+Name[hr]=Obnavljanje
+Name[it]=PluginImmagini_Restauro
+Name[ms]=ImagePlugin_Pemulihan
+Name[nl]=Afbeeldingsplugin_Restauratie
+Name[sr]=Рестаурација
+Name[sr@Latn]=Restauracija
+Name[sv]=Insticksprogram för bildrestaurering
+Name[tr]=ResimEklentisi_Onarım
+Name[xx]=xxImagePlugin_Restorationxx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=digiKam plugin to restore a photograph
+Comment[bg]=Приставка на digiKam за възстановяване на снимки
+Comment[ca]=Connector pel digiKam per restaurar una fotografia
+Comment[da]=Digikam plugin til restaurering af et fotografi
+Comment[de]=digiKam-Modul zum Restaurieren eines Bildes
+Comment[el]=Πρόσθετο του digiKam για αποκατάσταση μιας φωτογραφίας
+Comment[es]=Plugin para digiKam para restaurar una fotografía
+Comment[et]=DigiKami foto restaureerimimise plugin
+Comment[fa]=وصلۀ digiKam برای ذخیرۀ یک عکس
+Comment[fi]=Korjaa kuvassa esiintyviä virheitä
+Comment[gl]=Un plugin de digiKam para restaurar unha fotografia
+Comment[hr]=digiKam dodatak za obnavljanje fotografije
+Comment[is]=Íforrit fyrir digiKam sem fjarlægir óhreinindi úr myndum
+Comment[it]=Plugin di digiKam per restaurare una fotografia
+Comment[ja]=digiKam 写真復元プラグイン
+Comment[nds]=digiKam-Moduul för't Wedderherstellen vun Fotos
+Comment[nl]=Digikam-plugin voor het herstellen van een foto
+Comment[pa]=ਇੱਕ ਫੋਟੋ ਮੁੜ-ਸਟੋਰ ਕਰਨ ਲਈ ਡਿਜ਼ੀਕੈਮ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam umożliwiająca odrestaurowanie zdjęcia
+Comment[pt]=Um 'plugin' do digiKam para restaurar uma fotografia
+Comment[pt_BR]=Plugin digiKam para restaurar una fotografia
+Comment[ru]=Модуль digiKam для восстановления фотографий
+Comment[sk]=digiKam plugin pre obnovenie fotografie
+Comment[sr]=digiKam-ов прикључак за поправку фотографија
+Comment[sr@Latn]=digiKam-ov priključak za popravku fotografija
+Comment[sv]=Digikam insticksprogram för restaurering av ett fotografi
+Comment[tr]=Fotoğraf onarmak için digiKam eklentisi
+Comment[uk]=Втулок відновлення фотографій для digiKam
+Comment[vi]=Phần bổ sung xây dựng lại ảnh chụp như cũ cho digiKam
+Comment[xx]=xxdigiKam plugin to restore a photographxx
+
+X-TDE-Library=digikamimageplugin_restoration
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/restoration/digikamimageplugin_restoration_ui.rc b/src/imageplugins/restoration/digikamimageplugin_restoration_ui.rc
new file mode 100644
index 00000000..27185294
--- /dev/null
+++ b/src/imageplugins/restoration/digikamimageplugin_restoration_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_restoration" >
+
+ <MenuBar>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ <Action name="imageplugin_restoration" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_restoration" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/restoration/imageeffect_restoration.cpp b/src/imageplugins/restoration/imageeffect_restoration.cpp
new file mode 100644
index 00000000..825ef23d
--- /dev/null
+++ b/src/imageplugins/restoration/imageeffect_restoration.cpp
@@ -0,0 +1,349 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-26
+ * Description : a digiKam image editor plugin to restore
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqcombobox.h>
+#include <tqtabwidget.h>
+#include <tqfile.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <kurllabel.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <kstandarddirs.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "greycstorationsettings.h"
+#include "greycstorationwidget.h"
+#include "greycstorationiface.h"
+#include "imageeffect_restoration.h"
+#include "imageeffect_restoration.moc"
+
+namespace DigikamRestorationImagesPlugin
+{
+
+ImageEffect_Restoration::ImageEffect_Restoration(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Photograph Restoration"),
+ "restoration", true, true, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Photograph Restoration"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to restore a photograph."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("David Tschumperle", I18N_NOOP("CImg library"), 0,
+ "http://cimg.sourceforge.net");
+
+ about->addAuthor("Gerhard Kulzer", I18N_NOOP("Feedback and plugin polishing"),
+ "gerhard at kulzer.net");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ m_mainTab = new TQTabWidget( m_imagePreviewWidget );
+
+ TQWidget* firstPage = new TQWidget( m_mainTab );
+ TQGridLayout* grid = new TQGridLayout( firstPage, 2, 2, spacingHint());
+ m_mainTab->addTab( firstPage, i18n("Preset") );
+
+ KURLLabel *cimgLogoLabel = new KURLLabel(firstPage);
+ cimgLogoLabel->setText(TQString());
+ cimgLogoLabel->setURL("http://cimg.sourceforge.net");
+ TDEGlobal::dirs()->addResourceType("logo-cimg", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-cimg", "logo-cimg.png");
+ cimgLogoLabel->setPixmap( TQPixmap( directory + "logo-cimg.png" ) );
+ TQToolTip::add(cimgLogoLabel, i18n("Visit CImg library website"));
+
+ TQLabel *typeLabel = new TQLabel(i18n("Filtering type:"), firstPage);
+ typeLabel->setAlignment ( TQt::AlignRight | TQt::AlignVCenter);
+ m_restorationTypeCB = new TQComboBox( false, firstPage );
+ m_restorationTypeCB->insertItem( i18n("None") );
+ m_restorationTypeCB->insertItem( i18n("Reduce Uniform Noise") );
+ m_restorationTypeCB->insertItem( i18n("Reduce JPEG Artefacts") );
+ m_restorationTypeCB->insertItem( i18n("Reduce Texturing") );
+ TQWhatsThis::add( m_restorationTypeCB, i18n("<p>Select the filter preset to use for photograph restoration:<p>"
+ "<b>None</b>: Most common values. Puts settings to default.<p>"
+ "<b>Reduce Uniform Noise</b>: reduce small image artifacts like sensor noise.<p>"
+ "<b>Reduce JPEG Artefacts</b>: reduce large image artifacts like JPEG compression mosaic.<p>"
+ "<b>Reduce Texturing</b>: reduce image artifacts like paper texture or Moire patterns "
+ "of a scanned image.<p>"));
+
+ grid->addMultiCellWidget(cimgLogoLabel, 0, 0, 1, 1);
+ grid->addMultiCellWidget(typeLabel, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_restorationTypeCB, 1, 1, 1, 1);
+ grid->setRowStretch(1, 10);
+
+ // -------------------------------------------------------------
+
+ m_settingsWidget = new Digikam::GreycstorationWidget( m_mainTab );
+ m_imagePreviewWidget->setUserAreaWidget(m_mainTab);
+
+ // -------------------------------------------------------------
+
+ connect(cimgLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processCImgURL(const TQString&)));
+
+ connect(m_restorationTypeCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotResetValues(int)));
+}
+
+ImageEffect_Restoration::~ImageEffect_Restoration()
+{
+}
+
+void ImageEffect_Restoration::renderingFinished()
+{
+ m_imagePreviewWidget->setEnable(true);
+ m_mainTab->setEnabled(true);
+}
+
+void ImageEffect_Restoration::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("restoration Tool Dialog");
+
+ Digikam::GreycstorationSettings settings;
+ settings.fastApprox = config->readBoolEntry("FastApprox", true);
+ settings.interp = config->readNumEntry("Interpolation",
+ Digikam::GreycstorationSettings::NearestNeighbor);
+ settings.amplitude = config->readDoubleNumEntry("Amplitude", 60.0);
+ settings.sharpness = config->readDoubleNumEntry("Sharpness", 0.7);
+ settings.anisotropy = config->readDoubleNumEntry("Anisotropy", 0.3);
+ settings.alpha = config->readDoubleNumEntry("Alpha", 0.6);
+ settings.sigma = config->readDoubleNumEntry("Sigma", 1.1);
+ settings.gaussPrec = config->readDoubleNumEntry("GaussPrec", 2.0);
+ settings.dl = config->readDoubleNumEntry("Dl", 0.8);
+ settings.da = config->readDoubleNumEntry("Da", 30.0);
+ settings.nbIter = config->readNumEntry("Iteration", 1);
+ settings.tile = config->readNumEntry("Tile", 512);
+ settings.btile = config->readNumEntry("BTile", 4);
+ m_settingsWidget->setSettings(settings);
+
+ int p = config->readNumEntry("Preset", NoPreset);
+ m_restorationTypeCB->setCurrentItem(p);
+ if (p == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+}
+
+void ImageEffect_Restoration::writeUserSettings()
+{
+ Digikam::GreycstorationSettings settings = m_settingsWidget->getSettings();
+ TDEConfig* config = kapp->config();
+ config->setGroup("restoration Tool Dialog");
+ config->writeEntry("Preset", m_restorationTypeCB->currentItem());
+ config->writeEntry("FastApprox", settings.fastApprox);
+ config->writeEntry("Interpolation", settings.interp);
+ config->writeEntry("Amplitude", settings.amplitude);
+ config->writeEntry("Sharpness", settings.sharpness);
+ config->writeEntry("Anisotropy", settings.anisotropy);
+ config->writeEntry("Alpha", settings.alpha);
+ config->writeEntry("Sigma", settings.sigma);
+ config->writeEntry("GaussPrec", settings.gaussPrec);
+ config->writeEntry("Dl", settings.dl);
+ config->writeEntry("Da", settings.da);
+ config->writeEntry("Iteration", settings.nbIter);
+ config->writeEntry("Tile", settings.tile);
+ config->writeEntry("BTile", settings.btile);
+ config->sync();
+}
+
+void ImageEffect_Restoration::slotResetValues(int i)
+{
+ if (i == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+
+ resetValues();
+}
+
+void ImageEffect_Restoration::resetValues()
+{
+ Digikam::GreycstorationSettings settings;
+ settings.setRestorationDefaultSettings();
+
+ switch(m_restorationTypeCB->currentItem())
+ {
+ case ReduceUniformNoise:
+ {
+ settings.amplitude = 40.0;
+ break;
+ }
+
+ case ReduceJPEGArtefacts:
+ {
+ settings.sharpness = 0.3;
+ settings.sigma = 1.0;
+ settings.amplitude = 100.0;
+ settings.nbIter = 2;
+ break;
+ }
+
+ case ReduceTexturing:
+ {
+ settings.sharpness = 0.5;
+ settings.sigma = 1.5;
+ settings.amplitude = 100.0;
+ settings.nbIter = 2;
+ break;
+ }
+ }
+
+ m_settingsWidget->setSettings(settings);
+}
+
+void ImageEffect_Restoration::processCImgURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void ImageEffect_Restoration::prepareEffect()
+{
+ m_mainTab->setEnabled(false);
+
+ Digikam::DImg previewImage = m_imagePreviewWidget->getOriginalRegionImage();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Digikam::GreycstorationIface(
+ &previewImage, m_settingsWidget->getSettings(),
+ Digikam::GreycstorationIface::Restore,
+ 0, 0, 0, this));
+}
+
+void ImageEffect_Restoration::prepareFinal()
+{
+ m_mainTab->setEnabled(false);
+
+ Digikam::ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ Digikam::DImg originalImage(iface.originalWidth(), iface.originalHeight(),
+ iface.originalSixteenBit(), iface.originalHasAlpha(), data);
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Digikam::GreycstorationIface(
+ &originalImage, m_settingsWidget->getSettings(),
+ Digikam::GreycstorationIface::Restore,
+ 0, 0, 0, this));
+
+ delete [] data;
+}
+
+void ImageEffect_Restoration::putPreviewData(void)
+{
+ Digikam::DImg imDest = m_threadedFilter->getTargetImage();
+ m_imagePreviewWidget->setPreviewImage(imDest);
+}
+
+void ImageEffect_Restoration::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+
+ iface.putOriginalImage(i18n("Restoration"),
+ m_threadedFilter->getTargetImage().bits());
+}
+
+void ImageEffect_Restoration::slotUser3()
+{
+ KURL loadRestorationFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Restoration Settings File to Load")) );
+ if( loadRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(loadRestorationFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ if (!m_settingsWidget->loadSettings(file, TQString("# Photograph Restoration Configuration File V2")))
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a Photograph Restoration settings text file.")
+ .arg(loadRestorationFile.fileName()));
+ file.close();
+ return;
+ }
+
+ slotEffect();
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the Photograph Restoration text file."));
+
+ file.close();
+ m_restorationTypeCB->blockSignals(true);
+ m_restorationTypeCB->setCurrentItem(NoPreset);
+ m_restorationTypeCB->blockSignals(false);
+ m_settingsWidget->setEnabled(true);
+}
+
+void ImageEffect_Restoration::slotUser2()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Restoration Settings File to Save")) );
+ if( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ m_settingsWidget->saveSettings(file, TQString("# Photograph Restoration Configuration File V2"));
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the Photograph Restoration text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamRestorationImagesPlugin
+
diff --git a/src/imageplugins/restoration/imageeffect_restoration.h b/src/imageplugins/restoration/imageeffect_restoration.h
new file mode 100644
index 00000000..7c28bd92
--- /dev/null
+++ b/src/imageplugins/restoration/imageeffect_restoration.h
@@ -0,0 +1,94 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-26
+ * Description : a digiKam image editor plugin to restore
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_RESTORATION_H
+#define IMAGEEFFECT_RESTORATION_H
+
+// TQt include.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+class TQComboBox;
+class TQTabWidget;
+
+namespace Digikam
+{
+class GreycstorationWidget;
+}
+
+namespace DigikamRestorationImagesPlugin
+{
+
+class ImageEffect_Restoration : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Restoration(TQWidget* parent);
+ ~ImageEffect_Restoration();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void processCImgURL(const TQString&);
+ void readUserSettings();
+ void slotResetValues(int);
+
+private:
+
+ void writeUserSettings();
+ void prepareEffect(void);
+ void prepareFinal(void);
+ void putPreviewData(void);
+ void putFinalData(void);
+ void resetValues(void);
+ void renderingFinished(void);
+
+private:
+
+ enum RestorationFilteringPreset
+ {
+ NoPreset=0,
+ ReduceUniformNoise,
+ ReduceJPEGArtefacts,
+ ReduceTexturing
+ };
+
+ TQTabWidget *m_mainTab;
+
+ TQComboBox *m_restorationTypeCB;
+
+ Digikam::GreycstorationWidget *m_settingsWidget;
+};
+
+} // NameSpace DigikamRestorationImagesPlugin
+
+#endif /* IMAGEEFFECT_RESTORATION_H */
diff --git a/src/imageplugins/restoration/imageplugin_restoration.cpp b/src/imageplugins/restoration/imageplugin_restoration.cpp
new file mode 100644
index 00000000..01cd52f7
--- /dev/null
+++ b/src/imageplugins/restoration/imageplugin_restoration.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-26
+ * Description : a digiKam image editor plugin to restore
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "restorationtool.h"
+#include "imageplugin_restoration.h"
+#include "imageplugin_restoration.moc"
+
+using namespace DigikamRestorationImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_restoration,
+ KGenericFactory<ImagePlugin_Restoration>("digikamimageplugin_restoration"));
+
+ImagePlugin_Restoration::ImagePlugin_Restoration(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_Restoration")
+{
+ m_restorationAction = new TDEAction(i18n("Restoration..."), "restoration", 0,
+ this, TQ_SLOT(slotRestoration()),
+ actionCollection(), "imageplugin_restoration");
+
+ setXMLFile( "digikamimageplugin_restoration_ui.rc" );
+
+ DDebug() << "ImagePlugin_Restoration plugin loaded" << endl;
+}
+
+ImagePlugin_Restoration::~ImagePlugin_Restoration()
+{
+}
+
+void ImagePlugin_Restoration::setEnabledActions(bool enable)
+{
+ m_restorationAction->setEnabled(enable);
+}
+
+void ImagePlugin_Restoration::slotRestoration()
+{
+ RestorationTool *tool = new RestorationTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/restoration/imageplugin_restoration.h b/src/imageplugins/restoration/imageplugin_restoration.h
new file mode 100644
index 00000000..50ac8326
--- /dev/null
+++ b/src/imageplugins/restoration/imageplugin_restoration.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-26
+ * Description : a digiKam image editor plugin to restore
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_RESTORATION_H
+#define IMAGEPLUGIN_RESTORATION_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Restoration : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Restoration(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_Restoration();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotRestoration();
+
+private:
+
+ TDEAction *m_restorationAction;
+};
+
+#endif /* IMAGEPLUGIN_RESTORATION_H */
diff --git a/src/imageplugins/restoration/restorationtool.cpp b/src/imageplugins/restoration/restorationtool.cpp
new file mode 100644
index 00000000..a6896135
--- /dev/null
+++ b/src/imageplugins/restoration/restorationtool.cpp
@@ -0,0 +1,356 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-26
+ * Description : a digiKam image editor plugin to restore
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqcombobox.h>
+#include <tqtabwidget.h>
+#include <tqfile.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <kurllabel.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <kstandarddirs.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "greycstorationsettings.h"
+#include "greycstorationwidget.h"
+#include "greycstorationiface.h"
+#include "restorationtool.h"
+#include "restorationtool.moc"
+
+using namespace Digikam;
+
+namespace DigikamRestorationImagesPlugin
+{
+
+RestorationTool::RestorationTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("restoration");
+ setToolName(i18n("Restoration"));
+ setToolIcon(SmallIcon("restoration"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Try,
+ EditorToolSettings::PanIcon);
+
+ TQGridLayout* gridSettings = new TQGridLayout(m_gboxSettings->plainPage(), 2, 1);
+ m_mainTab = new TQTabWidget( m_gboxSettings->plainPage() );
+
+ TQWidget* firstPage = new TQWidget( m_mainTab );
+ TQGridLayout* grid = new TQGridLayout(firstPage, 2, 2);
+ m_mainTab->addTab( firstPage, i18n("Preset") );
+
+ KURLLabel *cimgLogoLabel = new KURLLabel(firstPage);
+ cimgLogoLabel->setText(TQString());
+ cimgLogoLabel->setURL("http://cimg.sourceforge.net");
+ TDEGlobal::dirs()->addResourceType("logo-cimg", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-cimg", "logo-cimg.png");
+ cimgLogoLabel->setPixmap( TQPixmap( directory + "logo-cimg.png" ) );
+ TQToolTip::add(cimgLogoLabel, i18n("Visit CImg library website"));
+
+ TQLabel *typeLabel = new TQLabel(i18n("Filtering type:"), firstPage);
+ typeLabel->setAlignment ( TQt::AlignRight | TQt::AlignVCenter);
+ m_restorationTypeCB = new TQComboBox(false, firstPage);
+ m_restorationTypeCB->insertItem( i18n("None") );
+ m_restorationTypeCB->insertItem( i18n("Reduce Uniform Noise") );
+ m_restorationTypeCB->insertItem( i18n("Reduce JPEG Artefacts") );
+ m_restorationTypeCB->insertItem( i18n("Reduce Texturing") );
+ TQWhatsThis::add( m_restorationTypeCB, i18n("<p>Select the filter preset to use for photograph restoration:<p>"
+ "<b>None</b>: Most common values. Puts settings to default.<p>"
+ "<b>Reduce Uniform Noise</b>: reduce small image artifacts like sensor noise.<p>"
+ "<b>Reduce JPEG Artefacts</b>: reduce large image artifacts like JPEG compression mosaic.<p>"
+ "<b>Reduce Texturing</b>: reduce image artifacts like paper texture or Moire patterns "
+ "of a scanned image.<p>"));
+
+ grid->addMultiCellWidget(cimgLogoLabel, 0, 0, 1, 1);
+ grid->addMultiCellWidget(typeLabel, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_restorationTypeCB, 1, 1, 1, 1);
+ grid->setRowStretch(1, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ m_settingsWidget = new GreycstorationWidget( m_mainTab );
+ gridSettings->addMultiCellWidget(m_mainTab, 0, 0, 1, 1);
+ gridSettings->addMultiCellWidget(new TQLabel(m_gboxSettings->plainPage()), 1, 1, 1, 1);
+ gridSettings->setMargin(m_gboxSettings->spacingHint());
+ gridSettings->setSpacing(m_gboxSettings->spacingHint());
+ gridSettings->setRowStretch(2, 10);
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "restoration Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(cimgLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processCImgURL(const TQString&)));
+
+ connect(m_restorationTypeCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotResetValues(int)));
+
+ // -------------------------------------------------------------
+
+ GreycstorationSettings defaults;
+ defaults.setRestorationDefaultSettings();
+ m_settingsWidget->setDefaultSettings(defaults);
+}
+
+RestorationTool::~RestorationTool()
+{
+}
+
+void RestorationTool::renderingFinished()
+{
+ m_previewWidget->setEnable(true);
+ m_mainTab->setEnabled(true);
+}
+
+void RestorationTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("restoration Tool");
+
+ GreycstorationSettings settings;
+ GreycstorationSettings defaults;
+ defaults.setRestorationDefaultSettings();
+
+ settings.fastApprox = config->readBoolEntry("FastApprox", defaults.fastApprox);
+ settings.interp = config->readNumEntry("Interpolation", defaults.interp);
+ settings.amplitude = config->readDoubleNumEntry("Amplitude", defaults.amplitude);
+ settings.sharpness = config->readDoubleNumEntry("Sharpness", defaults.sharpness);
+ settings.anisotropy = config->readDoubleNumEntry("Anisotropy", defaults.anisotropy);
+ settings.alpha = config->readDoubleNumEntry("Alpha", defaults.alpha);
+ settings.sigma = config->readDoubleNumEntry("Sigma", defaults.sigma);
+ settings.gaussPrec = config->readDoubleNumEntry("GaussPrec", defaults.gaussPrec);
+ settings.dl = config->readDoubleNumEntry("Dl", defaults.dl);
+ settings.da = config->readDoubleNumEntry("Da", defaults.da);
+ settings.nbIter = config->readNumEntry("Iteration", defaults.nbIter);
+ settings.tile = config->readNumEntry("Tile", defaults.tile);
+ settings.btile = config->readNumEntry("BTile", defaults.btile);
+ m_settingsWidget->setSettings(settings);
+
+ int p = config->readNumEntry("Preset", NoPreset);
+ m_restorationTypeCB->setCurrentItem(p);
+ if (p == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+}
+
+void RestorationTool::writeSettings()
+{
+ GreycstorationSettings settings = m_settingsWidget->getSettings();
+ TDEConfig* config = kapp->config();
+ config->setGroup("restoration Tool");
+ config->writeEntry("Preset", m_restorationTypeCB->currentItem());
+ config->writeEntry("FastApprox", settings.fastApprox);
+ config->writeEntry("Interpolation", settings.interp);
+ config->writeEntry("Amplitude", settings.amplitude);
+ config->writeEntry("Sharpness", settings.sharpness);
+ config->writeEntry("Anisotropy", settings.anisotropy);
+ config->writeEntry("Alpha", settings.alpha);
+ config->writeEntry("Sigma", settings.sigma);
+ config->writeEntry("GaussPrec", settings.gaussPrec);
+ config->writeEntry("Dl", settings.dl);
+ config->writeEntry("Da", settings.da);
+ config->writeEntry("Iteration", settings.nbIter);
+ config->writeEntry("Tile", settings.tile);
+ config->writeEntry("BTile", settings.btile);
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void RestorationTool::slotResetValues(int i)
+{
+ if (i == NoPreset)
+ m_settingsWidget->setEnabled(true);
+ else
+ m_settingsWidget->setEnabled(false);
+
+ slotResetSettings();
+}
+
+void RestorationTool::slotResetSettings()
+{
+ GreycstorationSettings settings;
+ settings.setRestorationDefaultSettings();
+
+ switch(m_restorationTypeCB->currentItem())
+ {
+ case ReduceUniformNoise:
+ {
+ settings.amplitude = 40.0;
+ break;
+ }
+
+ case ReduceJPEGArtefacts:
+ {
+ settings.sharpness = 0.3;
+ settings.sigma = 1.0;
+ settings.amplitude = 100.0;
+ settings.nbIter = 2;
+ break;
+ }
+
+ case ReduceTexturing:
+ {
+ settings.sharpness = 0.5;
+ settings.sigma = 1.5;
+ settings.amplitude = 100.0;
+ settings.nbIter = 2;
+ break;
+ }
+ }
+
+ m_settingsWidget->setSettings(settings);
+}
+
+void RestorationTool::processCImgURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void RestorationTool::prepareEffect()
+{
+ m_mainTab->setEnabled(false);
+
+ DImg previewImage = m_previewWidget->getOriginalRegionImage();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new GreycstorationIface(&previewImage,
+ m_settingsWidget->getSettings(), GreycstorationIface::Restore,
+ 0, 0, 0, this)));
+}
+
+void RestorationTool::prepareFinal()
+{
+ m_mainTab->setEnabled(false);
+
+ ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ DImg originalImage(iface.originalWidth(), iface.originalHeight(),
+ iface.originalSixteenBit(), iface.originalHasAlpha(), data);
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new GreycstorationIface(&originalImage,
+ m_settingsWidget->getSettings(), GreycstorationIface::Restore,
+ 0, 0, 0, this)));
+
+ delete [] data;
+}
+
+void RestorationTool::putPreviewData()
+{
+ DImg imDest = filter()->getTargetImage();
+ m_previewWidget->setPreviewImage(imDest);
+}
+
+void RestorationTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Restoration"), filter()->getTargetImage().bits());
+}
+
+void RestorationTool::slotLoadSettings()
+{
+ KURL loadRestorationFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Restoration Settings File to Load")) );
+ if( loadRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(loadRestorationFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ if (!m_settingsWidget->loadSettings(file, TQString("# Photograph Restoration Configuration File V2")))
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("\"%1\" is not a Photograph Restoration settings text file.")
+ .arg(loadRestorationFile.fileName()));
+ file.close();
+ return;
+ }
+
+ slotEffect();
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load settings from the Photograph Restoration text file."));
+
+ file.close();
+ m_restorationTypeCB->blockSignals(true);
+ m_restorationTypeCB->setCurrentItem(NoPreset);
+ m_restorationTypeCB->blockSignals(false);
+ m_settingsWidget->setEnabled(true);
+}
+
+void RestorationTool::slotSaveAsSettings()
+{
+ KURL saveRestorationFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("Photograph Restoration Settings File to Save")) );
+ if( saveRestorationFile.isEmpty() )
+ return;
+
+ TQFile file(saveRestorationFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ m_settingsWidget->saveSettings(file, TQString("# Photograph Restoration Configuration File V2"));
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save settings to the Photograph Restoration text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamRestorationImagesPlugin
+
diff --git a/src/imageplugins/restoration/restorationtool.h b/src/imageplugins/restoration/restorationtool.h
new file mode 100644
index 00000000..6242a2b6
--- /dev/null
+++ b/src/imageplugins/restoration/restorationtool.h
@@ -0,0 +1,100 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-26
+ * Description : a digiKam image editor plugin to restore
+ * a photograph
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RESTORATIONTOOL_H
+#define RESTORATIONTOOL_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQComboBox;
+class TQTabWidget;
+
+namespace Digikam
+{
+class GreycstorationWidget;
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamRestorationImagesPlugin
+{
+
+class RestorationTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ RestorationTool(TQObject* parent);
+ ~RestorationTool();
+
+private slots:
+
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+ void slotResetSettings();
+ void processCImgURL(const TQString&);
+ void slotResetValues(int);
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ enum RestorationFilteringPreset
+ {
+ NoPreset=0,
+ ReduceUniformNoise,
+ ReduceJPEGArtefacts,
+ ReduceTexturing
+ };
+
+ TQTabWidget *m_mainTab;
+
+ TQComboBox *m_restorationTypeCB;
+
+ Digikam::GreycstorationWidget *m_settingsWidget;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamRestorationImagesPlugin
+
+#endif /* RESTORATIONTOOL_H */
diff --git a/src/imageplugins/sheartool/Makefile.am b/src/imageplugins/sheartool/Makefile.am
new file mode 100644
index 00000000..a601b8a5
--- /dev/null
+++ b/src/imageplugins/sheartool/Makefile.am
@@ -0,0 +1,33 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_sheartool_la_SOURCES = imageplugin_sheartool.cpp shear.cpp \
+ sheartool.cpp
+
+digikamimageplugin_sheartool_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_sheartool_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_sheartool.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_sheartool.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_sheartool_ui.rc
diff --git a/src/imageplugins/sheartool/digikamimageplugin_sheartool.desktop b/src/imageplugins/sheartool/digikamimageplugin_sheartool.desktop
new file mode 100644
index 00000000..8884ef27
--- /dev/null
+++ b/src/imageplugins/sheartool/digikamimageplugin_sheartool.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Name=ImagePlugin_ShearTool
+Name[bg]=Приставка за снимки - Инструмент за изрязване
+Name[da]=Billedplugin_Forskydningsværktøj
+Name[el]=ΠρόσθετοΕικόνας_ΕργαλείοΣτρέβλωσης
+Name[fi]=Väännin
+Name[hr]=Smicanje
+Name[it]=PluginImmagini_DistorsioneCurvilinea
+Name[ms]=ImagePlugin_AlatanPemotong
+Name[nl]=Afbeeldingsplugin_SchuinTrekken
+Name[sr]=Алат за искошавање
+Name[sr@Latn]=Alat za iskošavanje
+Name[sv]=Insticksprogram med skjuvningsverktyg
+Name[tr]=ResimEklentisi_EğmeAracı
+Name[xx]=xxImagePlugin_ShearToolxx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Shear tool plugin for digiKam
+Comment[bg]=Приставка на digiKam с инструмент за изрязване на снимки
+Comment[ca]=Connector pel digiKam d'eina per retallar
+Comment[da]=Forskydningsværktøjs-plugin for Digikam
+Comment[de]=digiKam-Modul zum Scheren eines Bildes
+Comment[el]=Πρόσθετο εργαλείο στρέβλωσης για το digiKam
+Comment[es]=Plugin para digiKam con herramientas para cizallar una imagen
+Comment[et]=DigiKami pildinihkeplugin
+Comment[fa]=وصلۀ ابزار چیدن برای digiKam
+Comment[fi]=Vääntää kuvaa vaaka- ja pystysuunnassa
+Comment[gl]=Un plugin de digiKam para inclinar unha imaxe
+Comment[hr]=digiKam dodatak za smicanje
+Comment[is]=Íforrit fyrir digiKam sem snýr upp á myndir
+Comment[it]=Plugin per lo strumento di distorsione curvilinea per digiKam
+Comment[ja]=digiKam 剪断変形プラグイン
+Comment[ms]=Templat plugin pemotong untuk digiKam
+Comment[nds]=digiKam-Warktüüchmoduul för't Scheren
+Comment[nl]=Digikam-plugin voor het schuintrekken van afbeeldingen
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਸ਼ੀਅਰ ਸੰਦ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam umożliwiająca pochylenie obrazu
+Comment[pt]=Um 'plugin' do digiKam para inclinar uma imagem
+Comment[pt_BR]=Um 'plugin' do digiKam para inclinar uma imagem
+Comment[ru]=Модуль сдвига фрагментов для digiKam
+Comment[sk]=digiKam plugin pre orezanie obrázku
+Comment[sr]=digiKam-ов прикључак за искошавање
+Comment[sr@Latn]=digiKam-ov priključak za iskošavanje
+Comment[sv]=Digikam insticksprogram med skjuvningsverktyg
+Comment[tr]=digiKam için eğme aracı eklentisi
+Comment[uk]=Втулок засобу перекошення для digiKam
+Comment[vi]=Phần bổ sung công cụ kéo cắt cho digiKam
+Comment[xx]=xxShear tool plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_sheartool
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/sheartool/digikamimageplugin_sheartool_ui.rc b/src/imageplugins/sheartool/digikamimageplugin_sheartool_ui.rc
new file mode 100644
index 00000000..0a2cf60a
--- /dev/null
+++ b/src/imageplugins/sheartool/digikamimageplugin_sheartool_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="4" name="digikamimageplugin_sheartool" >
+
+ <MenuBar>
+
+ <Menu name="Transform" ><text>Tra&amp;nsform</text>
+ <Action name="imageplugin_sheartool" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_sheartool" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/sheartool/imageeffect_sheartool.cpp b/src/imageplugins/sheartool/imageeffect_sheartool.cpp
new file mode 100644
index 00000000..e65bd91b
--- /dev/null
+++ b/src/imageplugins/sheartool/imageeffect_sheartool.cpp
@@ -0,0 +1,322 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-23
+ * Description : a plugin to shear an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqcheckbox.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+#include <kseparator.h>
+#include <kcursor.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "sheartool.h"
+#include "imageeffect_sheartool.h"
+#include "imageeffect_sheartool.moc"
+
+namespace DigikamShearToolImagesPlugin
+{
+
+ImageEffect_ShearTool::ImageEffect_ShearTool(TQWidget* parent)
+ : Digikam::ImageGuideDlg(parent, i18n("Shear Tool"), "sheartool",
+ false, true, true,
+ Digikam::ImageGuideWidget::HVGuideMode)
+{
+ // No need Abort button action.
+ showButton(User1, false);
+
+ TQString whatsThis;
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Shear Tool"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to shear an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2004-2008, Gilles Caulier",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pieter Z. Voloshyn", I18N_NOOP("Shear algorithm"),
+ "pieter dot voloshyn at gmail dot com");
+
+ setAboutData(about);
+
+ TQWhatsThis::add( m_imagePreviewWidget, i18n("<p>This is the shearing image operation preview. "
+ "If you move the mouse cursor on this preview, "
+ "a vertical and horizontal dashed line will be drawn "
+ "to guide you in adjusting the shearing correction. "
+ "Release the left mouse button to freeze the dashed "
+ "line's position."));
+
+ // -------------------------------------------------------------
+
+ TQString temp;
+ Digikam::ImageIface iface(0, 0);
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 11, 2, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("New width:"), gboxSettings);
+ m_newWidthLabel = new TQLabel(temp.setNum( iface.originalWidth()) + i18n(" px"), gboxSettings);
+ m_newWidthLabel->setAlignment( AlignBottom | AlignRight );
+
+ TQLabel *label2 = new TQLabel(i18n("New height:"), gboxSettings);
+ m_newHeightLabel = new TQLabel(temp.setNum( iface.originalHeight()) + i18n(" px"), gboxSettings);
+ m_newHeightLabel->setAlignment( AlignBottom | AlignRight );
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridSettings->addMultiCellWidget(m_newWidthLabel, 0, 0, 1, 2);
+ gridSettings->addMultiCellWidget(label2, 1, 1, 0, 0);
+ gridSettings->addMultiCellWidget(m_newHeightLabel, 1, 1, 1, 2);
+
+ KSeparator *line = new KSeparator(Horizontal, gboxSettings);
+ gridSettings->addMultiCellWidget(line, 2, 2, 0, 2);
+
+ TQLabel *label3 = new TQLabel(i18n("Main horizontal angle:"), gboxSettings);
+ m_mainHAngleInput = new KIntNumInput(gboxSettings);
+ m_mainHAngleInput->setRange(-45, 45, 1, true);
+ m_mainHAngleInput->setValue(0);
+ TQWhatsThis::add( m_mainHAngleInput, i18n("<p>The main horizontal shearing angle, in degrees."));
+ gridSettings->addMultiCellWidget(label3, 3, 3, 0, 2);
+ gridSettings->addMultiCellWidget(m_mainHAngleInput, 4, 4, 0, 2);
+
+ TQLabel *label4 = new TQLabel(i18n("Fine horizontal angle:"), gboxSettings);
+ m_fineHAngleInput = new KDoubleNumInput(gboxSettings);
+ m_fineHAngleInput->setRange(-5.0, 5.0, 0.01, true);
+ m_fineHAngleInput->setValue(0);
+ TQWhatsThis::add( m_fineHAngleInput, i18n("<p>This value in degrees will be added to main horizontal angle value "
+ "to set fine adjustments."));
+ gridSettings->addMultiCellWidget(label4, 5, 5, 0, 2);
+ gridSettings->addMultiCellWidget(m_fineHAngleInput, 6, 6, 0, 2);
+
+ TQLabel *label5 = new TQLabel(i18n("Main vertical angle:"), gboxSettings);
+ m_mainVAngleInput = new KIntNumInput(gboxSettings);
+ m_mainVAngleInput->setRange(-45, 45, 1, true);
+ m_mainVAngleInput->setValue(0);
+ TQWhatsThis::add( m_mainVAngleInput, i18n("<p>The main vertical shearing angle, in degrees."));
+ gridSettings->addMultiCellWidget(label5, 7, 7, 0, 0);
+ gridSettings->addMultiCellWidget(m_mainVAngleInput, 8, 8, 0, 2);
+
+ TQLabel *label6 = new TQLabel(i18n("Fine vertical angle:"), gboxSettings);
+ m_fineVAngleInput = new KDoubleNumInput(gboxSettings);
+ m_fineVAngleInput->setRange(-5.0, 5.0, 0.01, true);
+ m_fineVAngleInput->setValue(0);
+ TQWhatsThis::add( m_fineVAngleInput, i18n("<p>This value in degrees will be added to main vertical angle value "
+ "to set fine adjustments."));
+ gridSettings->addMultiCellWidget(label6, 9, 9, 0, 2);
+ gridSettings->addMultiCellWidget(m_fineVAngleInput, 10, 10, 0, 2);
+
+ m_antialiasInput = new TQCheckBox(i18n("Anti-Aliasing"), gboxSettings);
+ TQWhatsThis::add( m_antialiasInput, i18n("<p>Enable this option to apply the anti-aliasing filter "
+ "to the sheared image. "
+ "To smooth the target image, it will be blurred a little."));
+ gridSettings->addMultiCellWidget(m_antialiasInput, 11, 11, 0, 2);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_mainHAngleInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineHAngleInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_mainVAngleInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineVAngleInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_antialiasInput, TQ_SIGNAL(toggled (bool)),
+ this, TQ_SLOT(slotEffect()));
+}
+
+ImageEffect_ShearTool::~ImageEffect_ShearTool()
+{
+}
+
+void ImageEffect_ShearTool::readUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("sheartool Tool Dialog");
+ m_mainHAngleInput->setValue(config->readNumEntry("Main HAngle", 0));
+ m_mainVAngleInput->setValue(config->readNumEntry("Main VAngle", 0));
+ m_fineHAngleInput->setValue(config->readDoubleNumEntry("Fine HAngle", 0.0));
+ m_fineVAngleInput->setValue(config->readDoubleNumEntry("Fine VAngle", 0.0));
+ m_antialiasInput->setChecked(config->readBoolEntry("Anti Aliasing", true));
+ slotEffect();
+}
+
+void ImageEffect_ShearTool::writeUserSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("sheartool Tool Dialog");
+ config->writeEntry("Main HAngle", m_mainHAngleInput->value());
+ config->writeEntry("Main VAngle", m_mainVAngleInput->value());
+ config->writeEntry("Fine HAngle", m_fineHAngleInput->value());
+ config->writeEntry("Fine VAngle", m_fineVAngleInput->value());
+ config->writeEntry("Anti Aliasing", m_antialiasInput->isChecked());
+ config->sync();
+}
+
+void ImageEffect_ShearTool::resetValues()
+{
+ m_mainHAngleInput->blockSignals(true);
+ m_mainVAngleInput->blockSignals(true);
+ m_fineHAngleInput->blockSignals(true);
+ m_fineVAngleInput->blockSignals(true);
+ m_antialiasInput->blockSignals(true);
+ m_mainHAngleInput->setValue(0);
+ m_mainVAngleInput->setValue(0);
+ m_fineHAngleInput->setValue(0.0);
+ m_fineVAngleInput->setValue(0.0);
+ m_antialiasInput->setChecked(true);
+ m_mainHAngleInput->blockSignals(false);
+ m_mainVAngleInput->blockSignals(false);
+ m_fineHAngleInput->blockSignals(false);
+ m_fineVAngleInput->blockSignals(false);
+ m_antialiasInput->blockSignals(false);
+}
+
+void ImageEffect_ShearTool::prepareEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ m_mainHAngleInput->setEnabled(false);
+ m_mainVAngleInput->setEnabled(false);
+ m_fineHAngleInput->setEnabled(false);
+ m_fineVAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+
+ float hAngle = m_mainHAngleInput->value() + m_fineHAngleInput->value();
+ float vAngle = m_mainVAngleInput->value() + m_fineVAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ TQColor background = paletteBackgroundColor().rgb();
+
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ int orgW = iface->originalWidth();
+ int orgH = iface->originalHeight();
+
+ uchar *data = iface->getPreviewImage();
+ Digikam::DImg image(iface->previewWidth(), iface->previewHeight(), iface->previewSixteenBit(),
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new ShearTool(&image, this, hAngle, vAngle, antialiasing, background, orgW, orgH));
+}
+
+void ImageEffect_ShearTool::prepareFinal()
+{
+ m_mainHAngleInput->setEnabled(false);
+ m_mainVAngleInput->setEnabled(false);
+ m_fineHAngleInput->setEnabled(false);
+ m_fineVAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+
+ float hAngle = m_mainHAngleInput->value() + m_fineHAngleInput->value();
+ float vAngle = m_mainVAngleInput->value() + m_fineVAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ TQColor background = TQt::black;
+
+ Digikam::ImageIface iface(0, 0);
+ int orgW = iface.originalWidth();
+ int orgH = iface.originalHeight();
+
+ uchar *data = iface.getOriginalImage();
+ Digikam::DImg orgImage(orgW, orgH, iface.originalSixteenBit(),
+ iface.originalHasAlpha(), data);
+ delete [] data;
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new ShearTool(&orgImage, this, hAngle, vAngle, antialiasing, background, orgW, orgH));
+}
+
+void ImageEffect_ShearTool::putPreviewData(void)
+{
+ Digikam::ImageIface* iface = m_imagePreviewWidget->imageIface();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+
+ Digikam::DImg imTemp = m_threadedFilter->getTargetImage().smoothScale(w, h, TQSize::ScaleMin);
+ Digikam::DImg imDest( w, h, m_threadedFilter->getTargetImage().sixteenBit(),
+ m_threadedFilter->getTargetImage().hasAlpha() );
+
+ imDest.fill( Digikam::DColor(paletteBackgroundColor().rgb(),
+ m_threadedFilter->getTargetImage().sixteenBit()) );
+ imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+
+ m_imagePreviewWidget->updatePreview();
+ TQSize newSize = dynamic_cast<ShearTool *>(m_threadedFilter)->getNewSize();
+ TQString temp;
+ m_newWidthLabel->setText(temp.setNum( newSize.width()) + i18n(" px") );
+ m_newHeightLabel->setText(temp.setNum( newSize.height()) + i18n(" px") );
+}
+
+void ImageEffect_ShearTool::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ Digikam::DImg targetImage = m_threadedFilter->getTargetImage();
+ iface.putOriginalImage(i18n("Shear Tool"),
+ targetImage.bits(),
+ targetImage.width(), targetImage.height());
+}
+
+void ImageEffect_ShearTool::renderingFinished()
+{
+ m_mainHAngleInput->setEnabled(true);
+ m_mainVAngleInput->setEnabled(true);
+ m_fineHAngleInput->setEnabled(true);
+ m_fineVAngleInput->setEnabled(true);
+ m_antialiasInput->setEnabled(true);
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamShearToolImagesPlugin
+
diff --git a/src/imageplugins/sheartool/imageeffect_sheartool.h b/src/imageplugins/sheartool/imageeffect_sheartool.h
new file mode 100644
index 00000000..311b4e0d
--- /dev/null
+++ b/src/imageplugins/sheartool/imageeffect_sheartool.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-23
+ * Description : a plugin to shear an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_SHEARTOOL_H
+#define IMAGEEFFECT_SHEARTOOL_H
+
+// Local includes.
+
+#include "imageguidedlg.h"
+
+class TQFrame;
+class TQPushButton;
+class TQCheckBox;
+class TQLabel;
+
+class KIntNumInput;
+class KDoubleNumInput;
+
+namespace DigikamShearToolImagesPlugin
+{
+
+class ImageEffect_ShearTool : public Digikam::ImageGuideDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_ShearTool(TQWidget* parent);
+ ~ImageEffect_ShearTool();
+
+private slots:
+
+ void readUserSettings(void);
+
+protected:
+
+ void writeUserSettings(void);
+ void prepareEffect(void);
+ void prepareFinal(void);
+ void putPreviewData(void);
+ void putFinalData(void);
+ void resetValues(void);
+ void renderingFinished(void);
+
+private:
+
+ TQLabel *m_newWidthLabel;
+ TQLabel *m_newHeightLabel;
+
+ TQCheckBox *m_antialiasInput;
+
+ KIntNumInput *m_mainHAngleInput;
+ KIntNumInput *m_mainVAngleInput;
+
+ KDoubleNumInput *m_fineHAngleInput;
+ KDoubleNumInput *m_fineVAngleInput;
+};
+
+} // NameSpace DigikamShearToolImagesPlugin
+
+#endif /* IMAGEEFFECT_SHEARTOOL_H */
diff --git a/src/imageplugins/sheartool/imageplugin_sheartool.cpp b/src/imageplugins/sheartool/imageplugin_sheartool.cpp
new file mode 100644
index 00000000..34030899
--- /dev/null
+++ b/src/imageplugins/sheartool/imageplugin_sheartool.cpp
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-23
+ * Description : a plugin to shear an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "sheartool.h"
+#include "imageplugin_sheartool.h"
+#include "imageplugin_sheartool.moc"
+
+using namespace DigikamShearToolImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_sheartool,
+ KGenericFactory<ImagePlugin_ShearTool>("digikamimageplugin_sheartool"));
+
+ImagePlugin_ShearTool::ImagePlugin_ShearTool(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_ShearTool")
+{
+ m_sheartoolAction = new TDEAction(i18n("Shear..."), "shear", 0,
+ this, TQ_SLOT(slotShearTool()),
+ actionCollection(), "imageplugin_sheartool");
+
+ setXMLFile("digikamimageplugin_sheartool_ui.rc");
+
+ DDebug() << "ImagePlugin_ShearTool plugin loaded" << endl;
+}
+
+ImagePlugin_ShearTool::~ImagePlugin_ShearTool()
+{
+}
+
+void ImagePlugin_ShearTool::setEnabledActions(bool enable)
+{
+ m_sheartoolAction->setEnabled(enable);
+}
+
+void ImagePlugin_ShearTool::slotShearTool()
+{
+ ShearTool *tool = new ShearTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/sheartool/imageplugin_sheartool.h b/src/imageplugins/sheartool/imageplugin_sheartool.h
new file mode 100644
index 00000000..2423a1b1
--- /dev/null
+++ b/src/imageplugins/sheartool/imageplugin_sheartool.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-23
+ * Description : a plugin to shear an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_SHEARTOOL_H
+#define IMAGEPLUGIN_SHEARTOOL_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_ShearTool : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_ShearTool(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_ShearTool();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotShearTool();
+
+private:
+
+ TDEAction *m_sheartoolAction;
+};
+
+#endif /* IMAGEPLUGIN_SHEARTOOL_H */
diff --git a/src/imageplugins/sheartool/shear.cpp b/src/imageplugins/sheartool/shear.cpp
new file mode 100644
index 00000000..29af5390
--- /dev/null
+++ b/src/imageplugins/sheartool/shear.cpp
@@ -0,0 +1,185 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-18
+ * Description : Shear threaded image filter.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Shear algorithms copyrighted 2005 by
+ * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Degrees to radian convertion coeff (PI/180). To optimize computation.
+#define DEG2RAD 0.017453292519943
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "shear.h"
+
+namespace DigikamShearToolImagesPlugin
+{
+
+Shear::Shear(Digikam::DImg *orgImage, TQObject *parent, float hAngle, float vAngle,
+ bool antialiasing, TQColor backgroundColor, int orgW, int orgH)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "sheartool")
+{
+ m_hAngle = hAngle;
+ m_vAngle = vAngle;
+ m_orgW = orgW;
+ m_orgH = orgH;
+ m_antiAlias = antialiasing;
+ m_backgroundColor = backgroundColor;
+
+ initFilter();
+}
+
+void Shear::filterImage(void)
+{
+ int progress;
+ int x, y, p = 0, pt;
+ int new_width, new_height;
+ double nx, ny, dx, dy;
+ double horz_factor, vert_factor;
+ double horz_add, vert_add;
+ double horz_beta_angle, vert_beta_angle;
+
+ int nWidth = m_orgImage.width();
+ int nHeight = m_orgImage.height();
+
+ uchar *pBits = m_orgImage.bits();
+ unsigned short *pBits16 = (unsigned short*)m_orgImage.bits();
+
+ // get beta ( complementary ) angle for horizontal and vertical angles
+ horz_beta_angle = ( ( ( m_hAngle < 0.0 ) ? 180.0 : 90.0 ) - m_hAngle ) * DEG2RAD;
+ vert_beta_angle = ( ( ( m_vAngle < 0.0 ) ? 180.0 : 90.0 ) - m_vAngle ) * DEG2RAD;
+
+ // get new distance for width and height values
+ horz_add = nHeight * ( ( m_hAngle < 0.0 ) ? sin( horz_beta_angle ) : cos( horz_beta_angle ) );
+ vert_add = nWidth * ( ( m_vAngle < 0.0 ) ? sin( vert_beta_angle ) : cos( vert_beta_angle ) );
+
+ // get absolute values for the distances
+ horz_add = fabs( horz_add );
+ vert_add = fabs( vert_add );
+
+ // get new image size ( original size + distance )
+ new_width = (int)horz_add + nWidth;
+ new_height = (int)vert_add + nHeight;
+
+ // get scale factor for width and height
+ horz_factor = horz_add / new_height;
+ vert_factor = vert_add / new_width;
+
+ // if horizontal angle is greater than zero...
+ // else, initial distance is equal to maximum distance ( in negative form )
+ if( m_hAngle > 0.0 )
+ {
+ // initial distance is zero and scale is negative ( to decrease )
+ dx = 0;
+ horz_factor *= -1.0;
+ }
+ else
+ {
+ dx = -horz_add;
+ }
+
+ // if vertical angle is greater than zero...
+ // else, initial distance is equal to maximum distance ( in negative form )
+ if( m_vAngle > 0.0 )
+ {
+ // initial distance is zero and scale is negative ( to decrease )
+ dy = 0;
+ vert_factor *= -1.0;
+ }
+ else
+ {
+ dy = -vert_add;
+ }
+
+ // allocates a new image with the new size
+
+ bool sixteenBit = m_orgImage.sixteenBit();
+
+ m_destImage = Digikam::DImg(new_width, new_height, sixteenBit, m_orgImage.hasAlpha());
+ m_destImage.fill( Digikam::DColor(m_backgroundColor.rgb(), sixteenBit) );
+
+ uchar *pResBits = m_destImage.bits();
+ unsigned short *pResBits16 = (unsigned short *)m_destImage.bits();
+
+ Digikam::DImgImageFilters filters;
+
+ for( y = 0; y < new_height; y++)
+ {
+ for( x = 0; x < new_width; x++, p += 4 )
+ {
+ // get new positions
+ nx = x + dx + y * horz_factor;
+ ny = y + dy + x * vert_factor;
+
+ // if is inside the source image
+ if (isInside (nWidth, nHeight, ROUND( nx ), ROUND( ny )))
+ {
+ if( m_antiAlias )
+ {
+ if (!sixteenBit)
+ filters.pixelAntiAliasing(pBits, nWidth, nHeight, nx, ny,
+ &pResBits[p+3], &pResBits[p+2],
+ &pResBits[p+1], &pResBits[p]);
+ else
+ filters.pixelAntiAliasing16(pBits16, nWidth, nHeight, nx, ny,
+ &pResBits16[p+3], &pResBits16[p+2],
+ &pResBits16[p+1], &pResBits16[p]);
+ }
+ else
+ {
+ pt = setPosition (nWidth, ROUND( nx ), ROUND( ny ));
+
+ for (int z = 0 ; z < 4 ; z++)
+ {
+ if (!sixteenBit)
+ pResBits[p+z] = pBits[pt+z];
+ else
+ pResBits16[p+z] = pBits16[pt+z];
+ }
+ }
+ }
+ }
+
+ // Update the progress bar in dialog.
+ progress = (int)(((double)y * 100.0) / new_height);
+ if (progress%5 == 0)
+ postProgress( progress );
+ }
+
+ // To compute the rotated destination image size using original image dimensions.
+ int W = (int)(fabs(m_orgH * ( ( m_hAngle < 0.0 ) ? sin( horz_beta_angle ) : cos( horz_beta_angle ))))+
+ m_orgW;
+ int H = (int)(fabs(m_orgW * ( ( m_vAngle < 0.0 ) ? sin( vert_beta_angle ) : cos( vert_beta_angle ))))+
+ m_orgH;
+
+ m_newSize.setWidth(W);
+ m_newSize.setHeight(H);
+}
+
+} // NameSpace DigikamShearToolImagesPlugin
diff --git a/src/imageplugins/sheartool/shear.h b/src/imageplugins/sheartool/shear.h
new file mode 100644
index 00000000..480d1084
--- /dev/null
+++ b/src/imageplugins/sheartool/shear.h
@@ -0,0 +1,84 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-18
+ * Description : Shear threaded image filter.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SHEAR_H
+#define SHEAR_H
+
+// TQt includes.
+
+#include <tqsize.h>
+#include <tqcolor.h>
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamShearToolImagesPlugin
+{
+
+class Shear : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ Shear(Digikam::DImg *orgImage, TQObject *parent=0, float hAngle=0.0, float vAngle=0.0,
+ bool antialiasing=true, TQColor backgroundColor=TQt::black, int orgW=0, int orgH=0);
+
+ ~Shear(){};
+
+ TQSize getNewSize(void){ return m_newSize; };
+
+private:
+
+ virtual void filterImage(void);
+
+ inline int setPosition (int Width, int X, int Y)
+ {
+ return (Y *Width*4 + 4*X);
+ };
+
+ inline bool isInside (int Width, int Height, int X, int Y)
+ {
+ bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true);
+ bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true);
+ return (bIsWOk && bIsHOk);
+ };
+
+private:
+
+ bool m_antiAlias;
+
+ int m_orgW;
+ int m_orgH;
+
+ float m_hAngle;
+ float m_vAngle;
+
+ TQColor m_backgroundColor;
+
+ TQSize m_newSize;
+};
+
+} // NameSpace DigikamShearToolImagesPlugin
+
+#endif /* SHEAR_H */
diff --git a/src/imageplugins/sheartool/sheartool.cpp b/src/imageplugins/sheartool/sheartool.cpp
new file mode 100644
index 00000000..9f65b6d3
--- /dev/null
+++ b/src/imageplugins/sheartool/sheartool.cpp
@@ -0,0 +1,331 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-23
+ * Description : a plugin to shear an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kseparator.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "editortoolsettings.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "shear.h"
+#include "sheartool.h"
+#include "sheartool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamShearToolImagesPlugin
+{
+
+ShearTool::ShearTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("sheartool");
+ setToolName(i18n("Shear Tool"));
+ setToolIcon(SmallIcon("sheartool"));
+
+ m_previewWidget = new ImageWidget("sheartool Tool", 0,
+ i18n("<p>This is the shear operation preview. "
+ "If you move the mouse cursor on this preview, "
+ "a vertical and horizontal dashed line will be drawn "
+ "to guide you in adjusting the shear correction. "
+ "Release the left mouse button to freeze the dashed "
+ "line's position."),
+ false, ImageGuideWidget::HVGuideMode);
+
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQString temp;
+ Digikam::ImageIface iface(0, 0);
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel,
+ EditorToolSettings::ColorGuide);
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 12, 2);
+
+ TQLabel *label1 = new TQLabel(i18n("New width:"), m_gboxSettings->plainPage());
+ m_newWidthLabel = new TQLabel(temp.setNum( iface.originalWidth()) + i18n(" px"), m_gboxSettings->plainPage());
+ m_newWidthLabel->setAlignment( AlignBottom | AlignRight );
+
+ TQLabel *label2 = new TQLabel(i18n("New height:"), m_gboxSettings->plainPage());
+ m_newHeightLabel = new TQLabel(temp.setNum( iface.originalHeight()) + i18n(" px"), m_gboxSettings->plainPage());
+ m_newHeightLabel->setAlignment( AlignBottom | AlignRight );
+
+ KSeparator *line = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ TQLabel *label3 = new TQLabel(i18n("Main horizontal angle:"), m_gboxSettings->plainPage());
+ m_mainHAngleInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_mainHAngleInput->setRange(-45, 45, 1);
+ m_mainHAngleInput->setDefaultValue(0);
+ TQWhatsThis::add( m_mainHAngleInput, i18n("<p>The main horizontal shearing angle, in degrees."));
+
+ TQLabel *label4 = new TQLabel(i18n("Fine horizontal angle:"), m_gboxSettings->plainPage());
+ m_fineHAngleInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_fineHAngleInput->setRange(-5.0, 5.0, 0.01);
+ m_fineHAngleInput->setDefaultValue(0);
+ TQWhatsThis::add( m_fineHAngleInput, i18n("<p>This value in degrees will be added to main horizontal angle value "
+ "to set fine adjustments."));
+
+ TQLabel *label5 = new TQLabel(i18n("Main vertical angle:"), m_gboxSettings->plainPage());
+ m_mainVAngleInput = new RIntNumInput(m_gboxSettings->plainPage());
+ m_mainVAngleInput->setRange(-45, 45, 1);
+ m_mainVAngleInput->setDefaultValue(0);
+ TQWhatsThis::add( m_mainVAngleInput, i18n("<p>The main vertical shearing angle, in degrees."));
+
+ TQLabel *label6 = new TQLabel(i18n("Fine vertical angle:"), m_gboxSettings->plainPage());
+ m_fineVAngleInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_fineVAngleInput->setRange(-5.0, 5.0, 0.01);
+ m_fineVAngleInput->setDefaultValue(0);
+ TQWhatsThis::add( m_fineVAngleInput, i18n("<p>This value in degrees will be added to main vertical angle value "
+ "to set fine adjustments."));
+
+ m_antialiasInput = new TQCheckBox(i18n("Anti-Aliasing"), m_gboxSettings->plainPage());
+ TQWhatsThis::add( m_antialiasInput, i18n("<p>Enable this option to apply the anti-aliasing filter "
+ "to the sheared image. "
+ "To smooth the target image, it will be blurred a little."));
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 0);
+ grid->addMultiCellWidget(m_newWidthLabel, 0, 0, 1, 2);
+ grid->addMultiCellWidget(label2, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_newHeightLabel, 1, 1, 1, 2);
+ grid->addMultiCellWidget(line, 2, 2, 0, 2);
+ grid->addMultiCellWidget(label3, 3, 3, 0, 2);
+ grid->addMultiCellWidget(m_mainHAngleInput, 4, 4, 0, 2);
+ grid->addMultiCellWidget(label4, 5, 5, 0, 2);
+ grid->addMultiCellWidget(m_fineHAngleInput, 6, 6, 0, 2);
+ grid->addMultiCellWidget(label5, 7, 7, 0, 0);
+ grid->addMultiCellWidget(m_mainVAngleInput, 8, 8, 0, 2);
+ grid->addMultiCellWidget(label6, 9, 9, 0, 2);
+ grid->addMultiCellWidget(m_fineVAngleInput, 10, 10, 0, 2);
+ grid->addMultiCellWidget(m_antialiasInput, 11, 11, 0, 2);
+ grid->setRowStretch(12, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_mainHAngleInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineHAngleInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_mainVAngleInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineVAngleInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_antialiasInput, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_gboxSettings, TQ_SIGNAL(signalColorGuideChanged()),
+ this, TQ_SLOT(slotColorGuideChanged()));
+}
+
+ShearTool::~ShearTool()
+{
+}
+
+void ShearTool::slotColorGuideChanged()
+{
+ m_previewWidget->slotChangeGuideColor(m_gboxSettings->guideColor());
+ m_previewWidget->slotChangeGuideSize(m_gboxSettings->guideSize());
+}
+
+void ShearTool::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("sheartool Tool");
+ m_mainHAngleInput->setValue(config->readNumEntry("Main HAngle", m_mainHAngleInput->defaultValue()));
+ m_mainVAngleInput->setValue(config->readNumEntry("Main VAngle", m_mainVAngleInput->defaultValue()));
+ m_fineHAngleInput->setValue(config->readDoubleNumEntry("Fine HAngle", m_fineHAngleInput->defaultValue()));
+ m_fineVAngleInput->setValue(config->readDoubleNumEntry("Fine VAngle", m_fineVAngleInput->defaultValue()));
+ m_antialiasInput->setChecked(config->readBoolEntry("Anti Aliasing", true));
+ m_gboxSettings->setGuideColor(config->readColorEntry("Guide Color", &TQt::red));
+ m_gboxSettings->setGuideSize(config->readNumEntry("Guide Width", 1));
+
+ slotColorGuideChanged();
+ slotEffect();
+}
+
+void ShearTool::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup("sheartool Tool");
+ config->writeEntry("Main HAngle", m_mainHAngleInput->value());
+ config->writeEntry("Main VAngle", m_mainVAngleInput->value());
+ config->writeEntry("Fine HAngle", m_fineHAngleInput->value());
+ config->writeEntry("Fine VAngle", m_fineVAngleInput->value());
+ config->writeEntry("Anti Aliasing", m_antialiasInput->isChecked());
+ config->writeEntry("Guide Color", m_gboxSettings->guideColor());
+ config->writeEntry("Guide Width", m_gboxSettings->guideSize());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void ShearTool::slotResetSettings()
+{
+ m_mainHAngleInput->blockSignals(true);
+ m_mainVAngleInput->blockSignals(true);
+ m_fineHAngleInput->blockSignals(true);
+ m_fineVAngleInput->blockSignals(true);
+ m_antialiasInput->blockSignals(true);
+
+ m_mainHAngleInput->slotReset();
+ m_mainVAngleInput->slotReset();
+ m_fineHAngleInput->slotReset();
+ m_fineVAngleInput->slotReset();
+ m_antialiasInput->setChecked(true);
+
+ m_mainHAngleInput->blockSignals(false);
+ m_mainVAngleInput->blockSignals(false);
+ m_fineHAngleInput->blockSignals(false);
+ m_fineVAngleInput->blockSignals(false);
+ m_antialiasInput->blockSignals(false);
+}
+
+void ShearTool::prepareEffect()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ m_mainHAngleInput->setEnabled(false);
+ m_mainVAngleInput->setEnabled(false);
+ m_fineHAngleInput->setEnabled(false);
+ m_fineVAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+
+ float hAngle = m_mainHAngleInput->value() + m_fineHAngleInput->value();
+ float vAngle = m_mainVAngleInput->value() + m_fineVAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ TQColor background = m_previewWidget->paletteBackgroundColor().rgb();
+ ImageIface* iface = m_previewWidget->imageIface();
+ int orgW = iface->originalWidth();
+ int orgH = iface->originalHeight();
+ uchar *data = iface->getPreviewImage();
+ DImg image(iface->previewWidth(), iface->previewHeight(), iface->previewSixteenBit(),
+ iface->previewHasAlpha(), data);
+ delete [] data;
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Shear(&image, this, hAngle, vAngle, antialiasing,
+ background, orgW, orgH)));
+}
+
+void ShearTool::prepareFinal()
+{
+ m_mainHAngleInput->setEnabled(false);
+ m_mainVAngleInput->setEnabled(false);
+ m_fineHAngleInput->setEnabled(false);
+ m_fineVAngleInput->setEnabled(false);
+ m_antialiasInput->setEnabled(false);
+
+ float hAngle = m_mainHAngleInput->value() + m_fineHAngleInput->value();
+ float vAngle = m_mainVAngleInput->value() + m_fineVAngleInput->value();
+ bool antialiasing = m_antialiasInput->isChecked();
+ TQColor background = TQt::black;
+
+ ImageIface iface(0, 0);
+ int orgW = iface.originalWidth();
+ int orgH = iface.originalHeight();
+
+ uchar *data = iface.getOriginalImage();
+ DImg orgImage(orgW, orgH, iface.originalSixteenBit(), iface.originalHasAlpha(), data);
+ delete [] data;
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Shear(&orgImage, this, hAngle, vAngle, antialiasing,
+ background, orgW, orgH)));
+}
+
+void ShearTool::putPreviewData()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ DImg imTemp = filter()->getTargetImage().smoothScale(w, h, TQSize::ScaleMin);
+ DImg imDest( w, h, filter()->getTargetImage().sixteenBit(),
+ filter()->getTargetImage().hasAlpha() );
+
+ imDest.fill(DColor(m_previewWidget->paletteBackgroundColor().rgb(),
+ filter()->getTargetImage().sixteenBit()) );
+ imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2);
+
+ iface->putPreviewImage((imDest.smoothScale(iface->previewWidth(),
+ iface->previewHeight())).bits());
+
+ m_previewWidget->updatePreview();
+ TQSize newSize = dynamic_cast<Shear*>(filter())->getNewSize();
+ TQString temp;
+ m_newWidthLabel->setText(temp.setNum( newSize.width()) + i18n(" px") );
+ m_newHeightLabel->setText(temp.setNum( newSize.height()) + i18n(" px") );
+}
+
+void ShearTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ DImg targetImage = filter()->getTargetImage();
+ iface.putOriginalImage(i18n("Shear Tool"),
+ targetImage.bits(),
+ targetImage.width(), targetImage.height());
+}
+
+void ShearTool::renderingFinished()
+{
+ m_mainHAngleInput->setEnabled(true);
+ m_mainVAngleInput->setEnabled(true);
+ m_fineHAngleInput->setEnabled(true);
+ m_fineVAngleInput->setEnabled(true);
+ m_antialiasInput->setEnabled(true);
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamShearToolImagesPlugin
diff --git a/src/imageplugins/sheartool/sheartool.h b/src/imageplugins/sheartool/sheartool.h
new file mode 100644
index 00000000..1c8161b4
--- /dev/null
+++ b/src/imageplugins/sheartool/sheartool.h
@@ -0,0 +1,96 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-23
+ * Description : a plugin to shear an image
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SHEARTOOL_H
+#define SHEARTOOL_H
+
+// Local includes.
+
+#include "editortool.h"
+
+class TQFrame;
+class TQPushButton;
+class TQCheckBox;
+class TQLabel;
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RDoubleNumInput;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImageWidget;
+}
+
+namespace DigikamShearToolImagesPlugin
+{
+
+class ShearTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ ShearTool(TQObject* parent);
+ ~ShearTool();
+
+private slots:
+
+ void slotResetSettings();
+ void slotColorGuideChanged();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ TQLabel *m_newWidthLabel;
+ TQLabel *m_newHeightLabel;
+
+ TQCheckBox *m_antialiasInput;
+
+ KDcrawIface::RIntNumInput *m_mainHAngleInput;
+ KDcrawIface::RIntNumInput *m_mainVAngleInput;
+
+ KDcrawIface::RDoubleNumInput *m_fineHAngleInput;
+ KDcrawIface::RDoubleNumInput *m_fineVAngleInput;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamShearToolImagesPlugin
+
+#endif /* SHEARTOOL_H */
diff --git a/src/imageplugins/superimpose/Makefile.am b/src/imageplugins/superimpose/Makefile.am
new file mode 100644
index 00000000..686eff2a
--- /dev/null
+++ b/src/imageplugins/superimpose/Makefile.am
@@ -0,0 +1,35 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/thumbbar \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_superimpose_la_SOURCES = superimposewidget.cpp superimpose.cpp dirselectwidget.cpp \
+ imageplugin_superimpose.cpp superimposetool.cpp
+
+
+digikamimageplugin_superimpose_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_superimpose_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_superimpose.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_superimpose.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_superimpose_ui.rc
diff --git a/src/imageplugins/superimpose/digikamimageplugin_superimpose.desktop b/src/imageplugins/superimpose/digikamimageplugin_superimpose.desktop
new file mode 100644
index 00000000..ef395a4a
--- /dev/null
+++ b/src/imageplugins/superimpose/digikamimageplugin_superimpose.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_SuperImpose
+Name[bg]=Приставка за снимки - Налагане
+Name[da]=Billedplugin_Indkopiering
+Name[el]=ΠρόσθετοΕικόνας_Υπερέκθεση
+Name[fi]=SuperImpose
+Name[hr]=Presvlačenje
+Name[it]=PluginImmagini_Sovrapposizione
+Name[nl]=Afbeeldingsplugin_SjabloonAanbrengen
+Name[sr]=Надоградња
+Name[sr@Latn]=Nadogradnja
+Name[sv]=Insticksprogram för överlagring
+Name[tr]=ResimEklentisi_Büyüt
+Name[xx]=xxImagePlugin_SuperImposexx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=Template superimpose plugin for digiKam
+Comment[bg]=Приставка на digiKam за налагане на снимки една върху друга
+Comment[ca]=Connector pel digiKam per sobreimposar una plantilla
+Comment[da]=Plugin til skabelon-indkopiering i Digikam
+Comment[de]=digiKam-Modul zum Anwenden von Schablonen auf ein Bild
+Comment[el]=Πρόσθετο πρότυπης υπερέκθεσης για το digiKam
+Comment[es]=Plugin de digiKam para superponer una plantilla sobre la imagen
+Comment[et]=DigiKami pildile malli lisamise plugin
+Comment[fa]=وصلۀ افزودن قالب برای digiKam
+Comment[fi]=Liittää useampia kuvia päällekäin
+Comment[gl]=Un plugin de digiKam para sobrepor modelos
+Comment[hr]=digiKam dodatak za presvlačenje predloškom
+Comment[is]=Íforrit fyrir digiKam sem hleður forsniðinni mynd ofan á þá sem unnið er með
+Comment[it]=Plugin di sovrapposizione di modelli per digiKam
+Comment[ja]=digiKam テンプレート重ね合わせプラグイン
+Comment[ms]=Templat plugin superimpose untuk digiKam
+Comment[nds]=digiKam-Moduul för't Vörblennen vun Vörlagen
+Comment[nl]=Digikam-plugin voor het aanbrengen van een sjabloon op de afbeelding
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਨਮੂਨਾ ਸੁਪਰ-ਇਮਪੋਜ਼ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam umożliwiająca nałożenie szablonu na obraz
+Comment[pt]=Um 'plugin' do digiKam para sobrepor modelos
+Comment[pt_BR]=Um 'plugin' do digiKam para sobrepor modelos
+Comment[ru]=Модуль накладывания шаблона для digiKam
+Comment[sk]=digiKam plugin pre prekrytie fotografie šablónou
+Comment[sr]=digiKam-ов прикључак надоградњу шаблонима
+Comment[sr@Latn]=digiKam-ov priključak nadogradnju šablonima
+Comment[sv]=Digikam insticksprogram för överlagringsmall
+Comment[tr]=digiKam için şablon büyütme eklentisi
+Comment[uk]=Втулок накладання шаблону для digiKam
+Comment[vi]=Phần bổ sung đặt biểu mẫu trên cho digiKam
+Comment[xx]=xxTemplate superimpose plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_superimpose
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/superimpose/digikamimageplugin_superimpose_ui.rc b/src/imageplugins/superimpose/digikamimageplugin_superimpose_ui.rc
new file mode 100644
index 00000000..ced7edac
--- /dev/null
+++ b/src/imageplugins/superimpose/digikamimageplugin_superimpose_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="4" name="digikamimageplugin_superimpose" >
+
+ <MenuBar>
+
+ <Menu name="Decorate" ><text>&amp;Decorate</text>
+ <Action name="imageplugin_superimpose" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_superimpose" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/superimpose/dirselectwidget.cpp b/src/imageplugins/superimpose/dirselectwidget.cpp
new file mode 100644
index 00000000..4856d73c
--- /dev/null
+++ b/src/imageplugins/superimpose/dirselectwidget.cpp
@@ -0,0 +1,182 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqheader.h>
+#include <tqlistview.h>
+#include <tqdir.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dirselectwidget.h"
+#include "dirselectwidget.moc"
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+struct DirSelectWidget::Private
+{
+ KFileTreeBranch* m_item;
+ TQStringList m_pendingPath;
+ TQString m_handled;
+ KURL m_rootUrl;
+};
+
+DirSelectWidget::DirSelectWidget(TQWidget* parent, const char* name, TQString headerLabel)
+ : KFileTreeView( parent, name)
+{
+ d = new Private;
+
+ addColumn( headerLabel );
+
+ if ( headerLabel.isNull() )
+ header()->hide();
+
+ setAlternateBackground(TQColor());
+}
+
+DirSelectWidget::DirSelectWidget(KURL rootUrl, KURL currentUrl,
+ TQWidget* parent, const char* name, TQString headerLabel)
+ : KFileTreeView( parent, name)
+{
+ d = new Private;
+
+ addColumn( headerLabel );
+
+ if ( headerLabel.isNull() )
+ header()->hide();
+
+ setAlternateBackground(TQColor());
+ setRootPath(rootUrl, currentUrl);
+}
+
+DirSelectWidget::~DirSelectWidget()
+{
+ delete d;
+}
+
+KURL DirSelectWidget::path() const
+{
+ return currentURL();
+}
+
+void DirSelectWidget::load()
+{
+ if ( d->m_pendingPath.isEmpty() )
+ {
+ disconnect( d->m_item, TQ_SIGNAL( populateFinished(KFileTreeViewItem *) ),
+ this, TQ_SLOT( load() ) );
+
+ emit folderItemSelected(currentURL());
+ return;
+ }
+
+ TQString item = d->m_pendingPath.front();
+ d->m_pendingPath.pop_front();
+ d->m_handled += item;
+ KFileTreeViewItem* branch = findItem( d->m_item, d->m_handled );
+
+ if ( !branch )
+ {
+ DDebug() << "Unable to open " << d->m_handled << endl;
+ }
+ else
+ {
+ branch->setOpen( true );
+ setSelected( branch, true );
+ ensureItemVisible ( branch );
+ d->m_handled += '/';
+
+ if ( branch->alreadyListed() )
+ load();
+ }
+}
+
+void DirSelectWidget::setCurrentPath(KURL currentUrl)
+{
+ if ( !currentUrl.isValid() )
+ return;
+
+ TQString currentPath = TQDir::cleanDirPath(currentUrl.path());
+ currentPath = currentPath.mid( d->m_rootUrl.path().length() );
+ d->m_pendingPath.clear();
+ d->m_handled = TQString("");
+ d->m_pendingPath = TQStringList::split( "/", currentPath, true );
+
+ if ( !d->m_pendingPath[0].isEmpty() )
+ d->m_pendingPath.prepend( "" ); // ensure we open the root first.
+
+ connect( d->m_item, TQ_SIGNAL( populateFinished(KFileTreeViewItem *) ),
+ this, TQ_SLOT( load() ) );
+ load();
+}
+
+void DirSelectWidget::setRootPath(KURL rootUrl, KURL currentUrl)
+{
+ d->m_rootUrl = rootUrl;
+ clear();
+ TQString root = TQDir::cleanDirPath(rootUrl.path());
+
+ if ( !root.endsWith("/"))
+ root.append("/");
+
+ TQString currentPath = TQDir::cleanDirPath(currentUrl.isValid() ? currentUrl.path() : root);
+
+ d->m_item = addBranch( rootUrl, rootUrl.fileName() );
+ setDirOnlyMode( d->m_item, true );
+ currentPath = currentPath.mid( root.length() );
+ d->m_pendingPath = TQStringList::split( "/", currentPath, true );
+
+ if ( !d->m_pendingPath[0].isEmpty() )
+ d->m_pendingPath.prepend( "" ); // ensure we open the root first.
+
+ connect( d->m_item, TQ_SIGNAL( populateFinished(KFileTreeViewItem *) ),
+ this, TQ_SLOT( load() ) );
+
+ load();
+
+ connect( this, TQ_SIGNAL( executed(TQListViewItem *) ),
+ this, TQ_SLOT( slotFolderSelected(TQListViewItem *) ) );
+}
+
+KURL DirSelectWidget::rootPath(void)
+{
+ return d->m_rootUrl;
+}
+
+void DirSelectWidget::slotFolderSelected(TQListViewItem *)
+{
+ emit folderItemSelected(currentURL());
+}
+
+} // NameSpace DigikamSuperImposeImagesPlugin
+
diff --git a/src/imageplugins/superimpose/dirselectwidget.h b/src/imageplugins/superimpose/dirselectwidget.h
new file mode 100644
index 00000000..1ee4abc2
--- /dev/null
+++ b/src/imageplugins/superimpose/dirselectwidget.h
@@ -0,0 +1,78 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIRSELECTWIDGET_H
+#define DIRSELECTWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdefiletreeview.h>
+#include <kurl.h>
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+class DirSelectWidget : public KFileTreeView
+{
+TQ_OBJECT
+
+
+public:
+
+ DirSelectWidget(TQWidget* parent, const char* name=0, TQString headerLabel=TQString());
+
+ DirSelectWidget(KURL rootUrl=KURL("/"), KURL currentUrl=KURL(),
+ TQWidget* parent=0, const char* name=0, TQString headerLabel=TQString());
+
+ ~DirSelectWidget();
+
+ KURL path() const;
+ KURL rootPath(void);
+ void setRootPath(KURL rootUrl, KURL currentUrl=KURL(TQString()));
+ void setCurrentPath(KURL currentUrl);
+
+signals :
+
+ void folderItemSelected(const KURL &url);
+
+protected slots:
+
+ void load();
+ void slotFolderSelected(TQListViewItem *);
+
+private:
+
+ struct Private;
+ Private* d;
+};
+
+} // NameSpace DigikamSuperImposeImagesPlugin
+
+#endif /* DIRSELECTWIDGET_H */
diff --git a/src/imageplugins/superimpose/imageeffect_superimpose.cpp b/src/imageplugins/superimpose/imageeffect_superimpose.cpp
new file mode 100644
index 00000000..1cea3e08
--- /dev/null
+++ b/src/imageplugins/superimpose/imageeffect_superimpose.cpp
@@ -0,0 +1,280 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqpixmap.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqdir.h>
+#include <tqfile.h>
+#include <tqhbuttongroup.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <knuminput.h>
+#include <kiconloader.h>
+#include <tdefiledialog.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "thumbbar.h"
+#include "superimposewidget.h"
+#include "dirselectwidget.h"
+#include "imageeffect_superimpose.h"
+#include "imageeffect_superimpose.moc"
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+ImageEffect_SuperImpose::ImageEffect_SuperImpose(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("Template Superimpose to Photograph"),
+ "superimpose", false, false)
+{
+ TQString whatsThis;
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Template Superimpose"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to superimpose a template onto a photograph."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2006, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(plainPage());
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+
+ TQGridLayout* gridFrame = new TQGridLayout( frame, 1, 2, spacingHint());
+ m_previewWidget = new SuperImposeWidget(400, 300, frame);
+ gridFrame->addMultiCellWidget(m_previewWidget, 0, 0, 0, 2);
+ gridFrame->setRowStretch(0, 10);
+ TQWhatsThis::add( m_previewWidget, i18n("<p>This is the preview of the template "
+ "superimposed onto the image.") );
+
+ // -------------------------------------------------------------
+
+ TQHButtonGroup *bGroup = new TQHButtonGroup(frame);
+ TDEIconLoader icon;
+ bGroup->addSpace(0);
+ TQPushButton *zoomInButton = new TQPushButton( bGroup );
+ bGroup->insert(zoomInButton, ZOOMIN);
+ zoomInButton->setPixmap( icon.loadIcon( "zoom-in", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ zoomInButton->setToggleButton(true);
+ TQToolTip::add( zoomInButton, i18n( "Zoom in" ) );
+ bGroup->addSpace(20);
+ TQPushButton *zoomOutButton = new TQPushButton( bGroup );
+ bGroup->insert(zoomOutButton, ZOOMOUT);
+ zoomOutButton->setPixmap( icon.loadIcon( "zoom-out", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ zoomOutButton->setToggleButton(true);
+ TQToolTip::add( zoomOutButton, i18n( "Zoom out" ) );
+ bGroup->addSpace(20);
+ TQPushButton *moveButton = new TQPushButton( bGroup );
+ bGroup->insert(moveButton, MOVE);
+ moveButton->setPixmap( icon.loadIcon( "move", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ moveButton->setToggleButton(true);
+ moveButton->setOn(true);
+ TQToolTip::add( moveButton, i18n( "Move" ) );
+ bGroup->addSpace(20);
+ bGroup->setExclusive(true);
+ bGroup->setFrameShape(TQFrame::NoFrame);
+ gridFrame->addMultiCellWidget(bGroup, 1, 1, 1, 1);
+ gridFrame->setColStretch(0, 10);
+ gridFrame->setColStretch(2, 10);
+
+ setPreviewAreaWidget(frame);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gbox2 = new TQWidget(plainPage());
+ TQGridLayout* grid = new TQGridLayout( gbox2, 1, 1, marginHint(), spacingHint());
+
+ m_thumbnailsBar = new Digikam::ThumbBarView(gbox2);
+ m_dirSelect = new DirSelectWidget(gbox2);
+ TQPushButton *templateDirButton = new TQPushButton( i18n("Root Directory..."), gbox2 );
+ TQWhatsThis::add( templateDirButton, i18n("<p>Set here the current templates' root directory.") );
+
+ grid->addMultiCellWidget(m_thumbnailsBar, 0, 1, 0, 0);
+ grid->addMultiCellWidget(m_dirSelect, 0, 0, 1, 1);
+ grid->addMultiCellWidget(templateDirButton, 1, 1, 1, 1);
+ grid->setColStretch(1, 10);
+
+ setUserAreaWidget(gbox2);
+
+ // -------------------------------------------------------------
+
+ connect(bGroup, TQ_SIGNAL(released(int)),
+ m_previewWidget, TQ_SLOT(slotEditModeChanged(int)));
+
+ connect(m_thumbnailsBar, TQ_SIGNAL(signalURLSelected(const KURL&)),
+ m_previewWidget, TQ_SLOT(slotSetCurrentTemplate(const KURL&)));
+
+ connect(m_dirSelect, TQ_SIGNAL(folderItemSelected(const KURL &)),
+ this, TQ_SLOT(slotTemplateDirChanged(const KURL &)));
+
+ connect(templateDirButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRootTemplateDirChanged()));
+
+ // -------------------------------------------------------------
+
+ populateTemplates();
+}
+
+ImageEffect_SuperImpose::~ImageEffect_SuperImpose()
+{
+}
+
+void ImageEffect_SuperImpose::populateTemplates(void)
+{
+ m_thumbnailsBar->clear(true);
+
+ if (!m_templatesUrl.isValid() || !m_templatesUrl.isLocalFile())
+ return;
+
+ TQDir dir(m_templatesUrl.path(), "*.png *.PNG");
+
+ if (!dir.exists())
+ return;
+
+ dir.setFilter ( TQDir::Files | TQDir::NoSymLinks );
+
+ const TQFileInfoList* fileinfolist = dir.entryInfoList();
+ if (!fileinfolist)
+ return;
+
+ TQFileInfoListIterator it(*fileinfolist);
+ TQFileInfo* fi;
+
+ while( (fi = it.current() ) )
+ {
+ new Digikam::ThumbBarItem( m_thumbnailsBar, KURL(fi->filePath()) );
+ ++it;
+ }
+}
+
+void ImageEffect_SuperImpose::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Album Settings");
+ KURL albumDBUrl( config->readPathEntry("Album Path", TDEGlobalSettings::documentPath()) );
+ config->setGroup("superimpose Tool Dialog");
+ config->setGroup("Template Superimpose Tool Settings");
+ m_templatesRootUrl.setPath( config->readEntry("Templates Root URL", albumDBUrl.path()) );
+ m_templatesUrl.setPath( config->readEntry("Templates URL", albumDBUrl.path()) );
+ m_dirSelect->setRootPath(m_templatesRootUrl, m_templatesUrl);
+}
+
+void ImageEffect_SuperImpose::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("superimpose Tool Dialog");
+ config->writeEntry( "Templates Root URL", m_dirSelect->rootPath().path() );
+ config->writeEntry( "Templates URL", m_templatesUrl.path() );
+ config->sync();}
+
+void ImageEffect_SuperImpose::resetValues()
+{
+ m_previewWidget->resetEdit();
+}
+
+void ImageEffect_SuperImpose::slotRootTemplateDirChanged(void)
+{
+ KURL url = KFileDialog::getExistingDirectory(m_templatesRootUrl.path(), kapp->activeWindow(),
+ i18n("Select Template Root Directory to Use"));
+
+ if( url.isValid() )
+ {
+ m_dirSelect->setRootPath(url);
+ m_templatesRootUrl = url;
+ m_templatesUrl = url;
+ populateTemplates();
+ }
+}
+
+void ImageEffect_SuperImpose::slotTemplateDirChanged(const KURL& url)
+{
+ if( url.isValid() )
+ {
+ m_templatesUrl = url;
+ populateTemplates();
+ }
+}
+
+void ImageEffect_SuperImpose::finalRendering()
+{
+ setCursor(KCursor::waitCursor());
+ m_previewWidget->setEnabled(false);
+ m_dirSelect->setEnabled(false);
+ m_thumbnailsBar->setEnabled(false);
+
+ Digikam::ImageIface iface(0, 0);
+ Digikam::DImg img = m_previewWidget->makeSuperImpose();
+ iface.putOriginalImage(i18n("Super Impose"), img.bits(),
+ img.width(), img.height() );
+
+ m_previewWidget->setEnabled(true);
+ m_dirSelect->setEnabled(true);
+ m_thumbnailsBar->setEnabled(true);
+ unsetCursor();
+ accept();
+}
+
+} // NameSpace DigikamSuperImposeImagesPlugin
+
diff --git a/src/imageplugins/superimpose/imageeffect_superimpose.h b/src/imageplugins/superimpose/imageeffect_superimpose.h
new file mode 100644
index 00000000..8eb8c18f
--- /dev/null
+++ b/src/imageplugins/superimpose/imageeffect_superimpose.h
@@ -0,0 +1,87 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_SUPERIMPOSE_H
+#define IMAGEEFFECT_SUPERIMPOSE_H
+
+// KDE include.
+
+#include <kurl.h>
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+
+class TQPushButton;
+
+namespace Digikam
+{
+class ThumbBarView;
+}
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+class DirSelectWidget;
+class SuperImposeWidget;
+
+class ImageEffect_SuperImpose : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_SuperImpose(TQWidget* parent);
+ ~ImageEffect_SuperImpose();
+
+private slots:
+
+ void slotTemplateDirChanged(const KURL& url);
+ void slotRootTemplateDirChanged(void);
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+ void populateTemplates(void);
+ void finalRendering();
+
+private:
+
+ KURL m_templatesUrl;
+ KURL m_templatesRootUrl;
+
+ Digikam::ThumbBarView *m_thumbnailsBar;
+
+ SuperImposeWidget *m_previewWidget;
+
+ DirSelectWidget *m_dirSelect;
+};
+
+} // NameSpace DigikamSuperImposeImagesPlugin
+
+#endif /* IMAGEEFFECT_SUPERIMPOSE_H */
diff --git a/src/imageplugins/superimpose/imageplugin_superimpose.cpp b/src/imageplugins/superimpose/imageplugin_superimpose.cpp
new file mode 100644
index 00000000..ceba44ef
--- /dev/null
+++ b/src/imageplugins/superimpose/imageplugin_superimpose.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "superimposetool.h"
+#include "imageplugin_superimpose.h"
+#include "imageplugin_superimpose.moc"
+
+using namespace DigikamSuperImposeImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_superimpose,
+ KGenericFactory<ImagePlugin_SuperImpose>("digikamimageplugin_superimpose"));
+
+ImagePlugin_SuperImpose::ImagePlugin_SuperImpose(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_SuperImpose")
+{
+ m_superimposeAction = new TDEAction(i18n("Template Superimpose..."), "superimpose", 0,
+ this, TQ_SLOT(slotSuperImpose()),
+ actionCollection(), "imageplugin_superimpose");
+
+ setXMLFile("digikamimageplugin_superimpose_ui.rc");
+
+ DDebug() << "ImagePlugin_SuperImpose plugin loaded" << endl;
+}
+
+ImagePlugin_SuperImpose::~ImagePlugin_SuperImpose()
+{
+}
+
+void ImagePlugin_SuperImpose::setEnabledActions(bool enable)
+{
+ m_superimposeAction->setEnabled(enable);
+}
+
+void ImagePlugin_SuperImpose::slotSuperImpose()
+{
+ SuperImposeTool *tool = new SuperImposeTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/superimpose/imageplugin_superimpose.h b/src/imageplugins/superimpose/imageplugin_superimpose.h
new file mode 100644
index 00000000..6f3c3a69
--- /dev/null
+++ b/src/imageplugins/superimpose/imageplugin_superimpose.h
@@ -0,0 +1,58 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_SUPERIMPOSE_H
+#define IMAGEPLUGIN_SUPERIMPOSE_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_SuperImpose : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_SuperImpose(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_SuperImpose();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotSuperImpose();
+
+private:
+
+ TDEAction *m_superimposeAction;
+};
+
+#endif /* IMAGEPLUGIN_SUPERIMPOSE_H */
diff --git a/src/imageplugins/superimpose/superimpose.cpp b/src/imageplugins/superimpose/superimpose.cpp
new file mode 100644
index 00000000..7c0ac74b
--- /dev/null
+++ b/src/imageplugins/superimpose/superimpose.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-18-03
+ * Description : Superimpose filter.
+ *
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "superimpose.h"
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+SuperImpose::SuperImpose(Digikam::DImg *orgImage, Digikam::DImg *templ,
+ TQRect orgImageSelection,
+ Digikam::DColorComposer::CompositingOperation compositeRule)
+{
+ m_orgImage = *orgImage;
+ m_template = *templ;
+ m_selection = orgImageSelection;
+ m_compositeRule = compositeRule;
+
+ filterImage();
+}
+
+void SuperImpose::filterImage(void)
+{
+ if (m_template.isNull())
+ return;
+
+ int templateWidth = m_template.width();
+ int templateHeight = m_template.height();
+
+ // take selection of src image and scale it to size of template
+ m_destImage = m_orgImage.smoothScaleSection(m_selection.x(), m_selection.y(),
+ m_selection.width(), m_selection.height(), templateWidth, templateHeight);
+
+ // convert depth if necessary
+ m_template.convertToDepthOfImage(&m_destImage);
+
+ // get composer for compositing rule
+ Digikam::DColorComposer *composer = Digikam::DColorComposer::getComposer(m_compositeRule);
+ Digikam::DColorComposer::MultiplicationFlags flags = Digikam::DColorComposer::NoMultiplication;
+ if (m_compositeRule != Digikam::DColorComposer::PorterDuffNone)
+ flags = Digikam::DColorComposer::MultiplicationFlagsDImg;
+
+ // do alpha blending of template on dest image
+ m_destImage.bitBlendImage(composer, &m_template, 0, 0, templateWidth, templateHeight, 0, 0, flags);
+
+ delete composer;
+}
+
+} // namespace DigikamSuperImposeImagesPlugin
diff --git a/src/imageplugins/superimpose/superimpose.h b/src/imageplugins/superimpose/superimpose.h
new file mode 100644
index 00000000..a0b7b6da
--- /dev/null
+++ b/src/imageplugins/superimpose/superimpose.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-18-03
+ * Description : Superimpose filter.
+ *
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef SUPERIMPOSE_H
+#define SUPERIMPOSE_H
+
+// TQt includes.
+
+#include <tqrect.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "dcolor.h"
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+class SuperImpose
+{
+
+public:
+
+ SuperImpose(Digikam::DImg *orgImage, Digikam::DImg *templ,
+ TQRect orgImageSelection,
+ Digikam::DColorComposer::CompositingOperation
+ compositeRule = Digikam::DColorComposer::PorterDuffNone);
+
+ Digikam::DImg getTargetImage() { return m_destImage; }
+
+private:
+
+ void filterImage(void);
+
+private:
+
+ TQRect m_selection;
+
+ Digikam::DImg m_orgImage;
+ Digikam::DImg m_template;
+ Digikam::DImg m_destImage;
+ Digikam::DColorComposer::CompositingOperation m_compositeRule;
+};
+
+} // namespace DigikamSuperImposeImagesPlugin
+
+#endif /* SUPERIMPOSE_H */
diff --git a/src/imageplugins/superimpose/superimposetool.cpp b/src/imageplugins/superimpose/superimposetool.cpp
new file mode 100644
index 00000000..f035559c
--- /dev/null
+++ b/src/imageplugins/superimpose/superimposetool.cpp
@@ -0,0 +1,271 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqpixmap.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqdir.h>
+#include <tqfile.h>
+#include <tqhbuttongroup.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <knuminput.h>
+#include <kiconloader.h>
+#include <tdefiledialog.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "editortoolsettings.h"
+#include "thumbbar.h"
+#include "superimposewidget.h"
+#include "dirselectwidget.h"
+#include "superimposetool.h"
+#include "superimposetool.moc"
+
+using namespace Digikam;
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+SuperImposeTool::SuperImposeTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("superimpose");
+ setToolName(i18n("Template Superimpose"));
+ setToolIcon(SmallIcon("superimpose"));
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(0);
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+
+ TQGridLayout* gridFrame = new TQGridLayout(frame, 1, 2);
+ m_previewWidget = new SuperImposeWidget(400, 300, frame);
+ TQWhatsThis::add( m_previewWidget, i18n("<p>This is the preview of the template "
+ "superimposed onto the image.") );
+
+ // -------------------------------------------------------------
+
+ TQHButtonGroup *bGroup = new TQHButtonGroup(frame);
+ TDEIconLoader icon;
+ bGroup->addSpace(0);
+ TQPushButton *zoomInButton = new TQPushButton( bGroup );
+ bGroup->insert(zoomInButton, ZOOMIN);
+ zoomInButton->setPixmap( icon.loadIcon( "zoom-in", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ zoomInButton->setToggleButton(true);
+ TQToolTip::add( zoomInButton, i18n( "Zoom in" ) );
+ bGroup->addSpace(20);
+ TQPushButton *zoomOutButton = new TQPushButton( bGroup );
+ bGroup->insert(zoomOutButton, ZOOMOUT);
+ zoomOutButton->setPixmap( icon.loadIcon( "zoom-out", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ zoomOutButton->setToggleButton(true);
+ TQToolTip::add( zoomOutButton, i18n( "Zoom out" ) );
+ bGroup->addSpace(20);
+ TQPushButton *moveButton = new TQPushButton( bGroup );
+ bGroup->insert(moveButton, MOVE);
+ moveButton->setPixmap( icon.loadIcon( "move", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ moveButton->setToggleButton(true);
+ moveButton->setOn(true);
+ TQToolTip::add( moveButton, i18n( "Move" ) );
+ bGroup->addSpace(20);
+ bGroup->setExclusive(true);
+ bGroup->setFrameShape(TQFrame::NoFrame);
+
+ gridFrame->addMultiCellWidget(m_previewWidget, 0, 0, 0, 2);
+ gridFrame->addMultiCellWidget(bGroup, 1, 1, 1, 1);
+ gridFrame->setRowStretch(0, 10);
+ gridFrame->setColStretch(0, 10);
+ gridFrame->setColStretch(2, 10);
+ gridFrame->setMargin(0);
+ gridFrame->setSpacing(0);
+
+ setToolView(frame);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+ TQGridLayout* grid = new TQGridLayout(m_gboxSettings->plainPage(), 1, 1);
+
+ m_thumbnailsBar = new ThumbBarView(m_gboxSettings->plainPage());
+ m_dirSelect = new DirSelectWidget(m_gboxSettings->plainPage());
+ TQPushButton *templateDirButton = new TQPushButton( i18n("Root Directory..."), m_gboxSettings->plainPage() );
+ TQWhatsThis::add( templateDirButton, i18n("<p>Set here the current templates' root directory.") );
+
+ grid->addMultiCellWidget(m_thumbnailsBar, 0, 1, 0, 0);
+ grid->addMultiCellWidget(m_dirSelect, 0, 0, 1, 1);
+ grid->addMultiCellWidget(templateDirButton, 1, 1, 1, 1);
+ grid->setMargin(0);
+ grid->setSpacing(m_gboxSettings->spacingHint());
+ grid->setColStretch(1, 10);
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(bGroup, TQ_SIGNAL(released(int)),
+ m_previewWidget, TQ_SLOT(slotEditModeChanged(int)));
+
+ connect(m_thumbnailsBar, TQ_SIGNAL(signalURLSelected(const KURL&)),
+ m_previewWidget, TQ_SLOT(slotSetCurrentTemplate(const KURL&)));
+
+ connect(m_dirSelect, TQ_SIGNAL(folderItemSelected(const KURL &)),
+ this, TQ_SLOT(slotTemplateDirChanged(const KURL &)));
+
+ connect(templateDirButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRootTemplateDirChanged()));
+
+ // -------------------------------------------------------------
+
+ populateTemplates();
+}
+
+SuperImposeTool::~SuperImposeTool()
+{
+}
+
+void SuperImposeTool::populateTemplates()
+{
+ m_thumbnailsBar->clear(true);
+
+ if (!m_templatesUrl.isValid() || !m_templatesUrl.isLocalFile())
+ return;
+
+ TQDir dir(m_templatesUrl.path(), "*.png *.PNG");
+
+ if (!dir.exists())
+ return;
+
+ dir.setFilter ( TQDir::Files | TQDir::NoSymLinks );
+
+ const TQFileInfoList* fileinfolist = dir.entryInfoList();
+ if (!fileinfolist)
+ return;
+
+ TQFileInfoListIterator it(*fileinfolist);
+ TQFileInfo* fi;
+
+ while( (fi = it.current() ) )
+ {
+ new ThumbBarItem( m_thumbnailsBar, KURL(fi->filePath()) );
+ ++it;
+ }
+}
+
+void SuperImposeTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Album Settings");
+ KURL albumDBUrl( config->readPathEntry("Album Path", TDEGlobalSettings::documentPath()) );
+ config->setGroup("superimpose Tool");
+ config->setGroup("Template Superimpose Tool Settings");
+ m_templatesRootUrl.setPath( config->readEntry("Templates Root URL", albumDBUrl.path()) );
+ m_templatesUrl.setPath( config->readEntry("Templates URL", albumDBUrl.path()) );
+ m_dirSelect->setRootPath(m_templatesRootUrl, m_templatesUrl);
+}
+
+void SuperImposeTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("superimpose Tool");
+ config->writeEntry( "Templates Root URL", m_dirSelect->rootPath().path() );
+ config->writeEntry( "Templates URL", m_templatesUrl.path() );
+ config->sync();
+}
+
+void SuperImposeTool::slotResetSettings()
+{
+ m_previewWidget->resetEdit();
+}
+
+void SuperImposeTool::slotRootTemplateDirChanged()
+{
+ KURL url = KFileDialog::getExistingDirectory(m_templatesRootUrl.path(), kapp->activeWindow(),
+ i18n("Select Template Root Directory to Use"));
+
+ if( url.isValid() )
+ {
+ m_dirSelect->setRootPath(url);
+ m_templatesRootUrl = url;
+ m_templatesUrl = url;
+ populateTemplates();
+ }
+}
+
+void SuperImposeTool::slotTemplateDirChanged(const KURL& url)
+{
+ if( url.isValid() )
+ {
+ m_templatesUrl = url;
+ populateTemplates();
+ }
+}
+
+void SuperImposeTool::finalRendering()
+{
+ kapp->setOverrideCursor(KCursor::waitCursor());
+ m_previewWidget->setEnabled(false);
+ m_dirSelect->setEnabled(false);
+ m_thumbnailsBar->setEnabled(false);
+
+ ImageIface iface(0, 0);
+ DImg img = m_previewWidget->makeSuperImpose();
+ iface.putOriginalImage(i18n("Super Impose"), img.bits(),
+ img.width(), img.height() );
+
+ m_previewWidget->setEnabled(true);
+ m_dirSelect->setEnabled(true);
+ m_thumbnailsBar->setEnabled(true);
+ kapp->restoreOverrideCursor();
+}
+
+} // NameSpace DigikamSuperImposeImagesPlugin
diff --git a/src/imageplugins/superimpose/superimposetool.h b/src/imageplugins/superimpose/superimposetool.h
new file mode 100644
index 00000000..7501f23f
--- /dev/null
+++ b/src/imageplugins/superimpose/superimposetool.h
@@ -0,0 +1,90 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_SUPERIMPOSE_H
+#define IMAGEEFFECT_SUPERIMPOSE_H
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQPushButton;
+
+namespace Digikam
+{
+class ThumbBarView;
+class EditorToolSettings;
+}
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+class DirSelectWidget;
+class SuperImposeWidget;
+
+class SuperImposeTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ SuperImposeTool(TQObject* parent);
+ ~SuperImposeTool();
+
+private slots:
+
+ void slotTemplateDirChanged(const KURL& url);
+ void slotRootTemplateDirChanged();
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void populateTemplates();
+ void finalRendering();
+
+private:
+
+ KURL m_templatesUrl;
+ KURL m_templatesRootUrl;
+
+ Digikam::ThumbBarView *m_thumbnailsBar;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+
+ SuperImposeWidget *m_previewWidget;
+
+ DirSelectWidget *m_dirSelect;
+};
+
+} // NameSpace DigikamSuperImposeImagesPlugin
+
+#endif /* IMAGEEFFECT_SUPERIMPOSE_H */
diff --git a/src/imageplugins/superimpose/superimposewidget.cpp b/src/imageplugins/superimpose/superimposewidget.cpp
new file mode 100644
index 00000000..edae15aa
--- /dev/null
+++ b/src/imageplugins/superimpose/superimposewidget.cpp
@@ -0,0 +1,329 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqpainter.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+
+// Local includes.
+
+#include "superimpose.h"
+#include "superimposewidget.h"
+#include "superimposewidget.moc"
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+SuperImposeWidget::SuperImposeWidget(int w, int h, TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ m_pixmap = new TQPixmap(w, h);
+ m_editMode = MOVE;
+
+ Digikam::ImageIface iface(0, 0);
+ m_w = iface.originalWidth();
+ m_h = iface.originalHeight();
+
+ setBackgroundMode(TQt::NoBackground);
+ setMinimumSize(w, h);
+ setMouseTracking(true);
+
+ resetEdit();
+}
+
+SuperImposeWidget::~SuperImposeWidget()
+{
+ if(m_pixmap)
+ delete m_pixmap;
+}
+
+Digikam::DImg SuperImposeWidget::makeSuperImpose(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ SuperImpose superimpose(iface.getOriginalImg(), &m_template, m_currentSelection);
+ return superimpose.getTargetImage();
+}
+
+void SuperImposeWidget::resetEdit(void)
+{
+ m_zoomFactor = 1.0;
+ m_currentSelection = TQRect(m_w/2 - m_rect.width()/2, m_h/2 - m_rect.height()/2,
+ m_rect.width(), m_rect.height());
+ makePixmap();
+ repaint(false);
+}
+
+void SuperImposeWidget::makePixmap(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ SuperImpose superimpose(iface.getOriginalImg(), &m_templateScaled, m_currentSelection);
+ Digikam::DImg image = superimpose.getTargetImage();
+
+ m_pixmap->fill(colorGroup().background());
+ TQPainter p(m_pixmap);
+ TQPixmap imagePix = image.convertToPixmap();
+ p.drawPixmap(m_rect.x(), m_rect.y(), imagePix, 0, 0, m_rect.width(), m_rect.height());
+ p.end();
+}
+
+void SuperImposeWidget::resizeEvent(TQResizeEvent * e)
+{
+ blockSignals(true);
+ delete m_pixmap;
+ int w = e->size().width();
+ int h = e->size().height();
+ m_pixmap = new TQPixmap(w, h);
+
+ if (!m_template.isNull())
+ {
+ int templateWidth = m_template.width();
+ int templateHeight = m_template.height();
+
+ if (templateWidth < templateHeight)
+ {
+ int neww = (int) ((float)height() / (float)templateHeight * (float)templateWidth);
+ m_rect = TQRect(width()/2-neww/2, 0, neww, height());
+ }
+ else
+ {
+ int newh = (int) ((float)width() / (float)templateWidth * (float)templateHeight);
+ m_rect = TQRect(0, height()/2-newh/2, width(), newh);
+ }
+
+ m_templateScaled = m_template.smoothScale(m_rect.width(), m_rect.height());
+ makePixmap();
+ }
+ else
+ {
+ m_rect = TQRect();
+ m_pixmap->fill(colorGroup().background());
+ }
+
+ blockSignals(false);
+}
+
+void SuperImposeWidget::paintEvent( TQPaintEvent * )
+{
+ bitBlt(this, 0, 0, m_pixmap);
+}
+
+void SuperImposeWidget::slotEditModeChanged(int mode)
+{
+ m_editMode = mode;
+}
+
+void SuperImposeWidget::slotSetCurrentTemplate(const KURL& url)
+{
+ m_template.load(url.path());
+
+ if (m_template.isNull())
+ {
+ m_rect = TQRect();
+ return;
+ }
+
+ int templateWidth = m_template.width();
+ int templateHeight = m_template.height();
+
+ if (templateWidth < templateHeight)
+ {
+ int neww = (int) ((float)height() / (float)templateHeight * (float)templateWidth);
+ m_rect = TQRect(width()/2-neww/2, 0, neww, height());
+ }
+ else
+ {
+ int newh = (int) ((float)width() / (float)templateWidth * (float)templateHeight);
+ m_rect = TQRect(0, height()/2-newh/2, width(), newh);
+ }
+
+ m_templateScaled = m_template.smoothScale(m_rect.width(), m_rect.height());
+
+ m_currentSelection = TQRect(m_w/2 - m_rect.width()/2, m_h/2 - m_rect.height()/2, m_rect.width(), m_rect.height());
+ zoomSelection(0);
+}
+
+void SuperImposeWidget::moveSelection(int dx, int dy)
+{
+ TQRect selection = m_currentSelection;
+ float wf = (float)selection.width() / (float)m_rect.width();
+ float hf = (float)selection.height() / (float)m_rect.height();
+
+ selection.moveBy( -(int)(wf*(float)dx), -(int)(hf*(float)dy) );
+
+ if (selection.left() < 0)
+ selection.moveLeft(0);
+ if (selection.top() < 0)
+ selection.moveTop(0);
+ if (selection.bottom() > m_h)
+ selection.moveBottom(m_h);
+ if (selection.right() > m_w)
+ selection.moveRight(m_w);
+
+ m_currentSelection = selection;
+}
+
+bool SuperImposeWidget::zoomSelection(float deltaZoomFactor)
+{
+ float newZoom = m_zoomFactor + deltaZoomFactor;
+
+ if (newZoom < 0.0)
+ return false;
+
+ TQRect selection = m_currentSelection;
+ int wf = (int)((float)m_rect.width() / newZoom);
+ int hf = (int)((float)m_rect.height() / newZoom);
+ int deltaX = (m_currentSelection.width() - wf) / 2;
+ int deltaY = (m_currentSelection.height() - hf) / 2;
+
+ selection.setLeft(m_currentSelection.left() + deltaX);
+ selection.setTop(m_currentSelection.top() + deltaY);
+ selection.setWidth(wf);
+ selection.setHeight(hf);
+
+ // check that selection is still inside original image
+ TQRect orgImageRect(0, 0, m_w, m_h);
+ if (!orgImageRect.contains(selection))
+ {
+ // try to adjust
+ if (selection.left() < 0)
+ selection.moveLeft(0);
+ if (selection.top() < 0)
+ selection.moveTop(0);
+ if (selection.bottom() > m_h)
+ selection.moveBottom(m_h);
+ if (selection.right() > m_w)
+ selection.moveRight(m_w);
+
+ // was it successful?
+ if (selection.contains(orgImageRect))
+ return false;
+
+ }
+
+ m_zoomFactor = newZoom;
+ m_currentSelection = selection;
+
+ makePixmap();
+ repaint(false);
+
+ return true;
+}
+
+void SuperImposeWidget::mousePressEvent ( TQMouseEvent * e )
+{
+ if ( isEnabled() && e->button() == TQt::LeftButton &&
+ rect().contains( e->x(), e->y() ) )
+ {
+ switch (m_editMode)
+ {
+ case ZOOMIN:
+ if (zoomSelection(+0.05))
+ moveSelection(width()/2 - e->x(), height()/2 - e->y());
+ break;
+
+ case ZOOMOUT:
+ if (zoomSelection(-0.05))
+ moveSelection(width()/2 - e->x(), height()/2 - e->y());
+ break;
+
+ case MOVE:
+ m_xpos = e->x();
+ m_ypos = e->y();
+ }
+ }
+}
+
+void SuperImposeWidget::mouseReleaseEvent ( TQMouseEvent * )
+{
+ setEditModeCursor();
+}
+
+void SuperImposeWidget::mouseMoveEvent ( TQMouseEvent * e )
+{
+ if ( isEnabled() )
+ {
+ if ( e->state() == TQt::LeftButton )
+ {
+ switch (m_editMode)
+ {
+ case ZOOMIN:
+ case ZOOMOUT:
+ break;
+
+ case MOVE:
+ int newxpos = e->x();
+ int newypos = e->y();
+
+ if (newxpos < m_rect.left())
+ newxpos = m_rect.left();
+ if (newxpos > m_rect.right())
+ newxpos = m_rect.right();
+ if (newxpos < m_rect.top())
+ newxpos = m_rect.top();
+ if (newxpos > m_rect.bottom())
+ newxpos = m_rect.bottom();
+
+ moveSelection(newxpos - m_xpos, newypos - m_ypos);
+ makePixmap();
+ repaint(false);
+
+ m_xpos = newxpos;
+ m_ypos = newypos;
+ setCursor( KCursor::handCursor() );
+ break;
+ }
+ }
+ else if (rect().contains( e->x(), e->y() ))
+ {
+ setEditModeCursor();
+ }
+ }
+}
+
+void SuperImposeWidget::setEditModeCursor()
+{
+ switch (m_editMode)
+ {
+ case ZOOMIN:
+ case ZOOMOUT:
+ setCursor ( KCursor::crossCursor() );
+ break;
+
+ case MOVE:
+ setCursor ( KCursor::sizeAllCursor() );
+ }
+}
+
+} // NameSpace DigikamSuperImposeImagesPlugin
diff --git a/src/imageplugins/superimpose/superimposewidget.h b/src/imageplugins/superimpose/superimposewidget.h
new file mode 100644
index 00000000..8b3fb048
--- /dev/null
+++ b/src/imageplugins/superimpose/superimposewidget.h
@@ -0,0 +1,118 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-04
+ * Description : a Digikam image editor plugin for superimpose a
+ * template to an image.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef SUPERIMPOSEWIDGET_H
+#define SUPERIMPOSEWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqimage.h>
+#include <tqrect.h>
+#include <tqsize.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Digikam includes.
+
+#include "imageiface.h"
+#include "dimg.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+class ImageIface;
+}
+
+namespace DigikamSuperImposeImagesPlugin
+{
+
+enum Action
+{
+ ZOOMIN=0,
+ ZOOMOUT,
+ MOVE
+};
+
+class SuperImposeWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ SuperImposeWidget(int w, int h, TQWidget *parent=0);
+ ~SuperImposeWidget();
+
+ void setEditMode(int mode);
+ void resetEdit(void);
+ TQRect getCurrentSelection(void);
+ TQSize getTemplateSize(void);
+ Digikam::DImg makeSuperImpose(void);
+
+public slots:
+
+ void slotEditModeChanged(int mode);
+ void slotSetCurrentTemplate(const KURL& url);
+
+protected:
+
+ void paintEvent( TQPaintEvent *e );
+ void resizeEvent( TQResizeEvent * e );
+ void mousePressEvent ( TQMouseEvent * e );
+ void mouseReleaseEvent ( TQMouseEvent * e );
+ void mouseMoveEvent ( TQMouseEvent * e );
+
+ bool zoomSelection(float deltaZoomFactor);
+ void moveSelection(int x, int y);
+ void makePixmap(void);
+ void setEditModeCursor();
+
+private:
+
+ int m_w;
+ int m_h;
+
+ int m_xpos;
+ int m_ypos;
+ int m_editMode;
+ float m_zoomFactor;
+
+ TQPixmap *m_pixmap; // For image region selection manipulations.
+
+ TQRect m_rect; // For mouse drag position.
+ TQRect m_currentSelection; // Region selection in image displayed in the widget.
+
+ Digikam::DImg m_template; // Full template data.
+ Digikam::DImg m_templateScaled; // Template scaled to preview widget
+};
+
+} // NameSpace DigikamSuperImposeImagesPlugin
+
+#endif /* SUPERIMPOSEWIDGET_H */
diff --git a/src/imageplugins/texture/Makefile.am b/src/imageplugins/texture/Makefile.am
new file mode 100644
index 00000000..22697c65
--- /dev/null
+++ b/src/imageplugins/texture/Makefile.am
@@ -0,0 +1,35 @@
+METASOURCES = AUTO
+SUBDIRS = patterns
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_texture_la_SOURCES = imageplugin_texture.cpp \
+ texturetool.cpp texture.cpp
+
+digikamimageplugin_texture_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_texture_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_texture.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_texture.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_texture_ui.rc
+
diff --git a/src/imageplugins/texture/digikamimageplugin_texture.desktop b/src/imageplugins/texture/digikamimageplugin_texture.desktop
new file mode 100644
index 00000000..4caf7e41
--- /dev/null
+++ b/src/imageplugins/texture/digikamimageplugin_texture.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=ImagePlugin_Texture
+Name[bg]=Приставка за снимки - Текстури
+Name[el]=ΠρόσθετοΕικόνας_Υφή
+Name[fi]=Taustapinta
+Name[hr]=Tekstura
+Name[it]=PluginImmagini_Trama
+Name[ms]=ImagePlugin_Tekstur
+Name[nl]=Afbeeldingsplugin_Textuur
+Name[sr]=Текстуре
+Name[sr@Latn]=Teksture
+Name[sv]=Insticksprogram för struktur
+Name[tr]=ResimEklentisi_Doku
+Name[xx]=xxImagePlugin_Texturexx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=digiKam plugin to apply texture on image
+Comment[bg]=Приставка на digiKam за добавяне на текстури към снимки
+Comment[ca]=Connector pel digiKam per aplicar una textura a una imatge
+Comment[da]=digiKam-plugin til at anvende tekstur på billedet
+Comment[de]=digiKam-Modul für das Anwenden von Texturen auf ein Bild
+Comment[el]=Πρόσθετο εφαρμογής υφής σε εικόνα για το digiKam
+Comment[es]=Plugin para digiKam para aplicar una textura sobre una imagen
+Comment[et]=DigiKami pildile tekstuuri lisamise plugin
+Comment[fa]=وصلۀ digiKam جهت اعمال بافت در تصویر
+Comment[fi]=Lisää kuvaan taustapinnan
+Comment[fr]=Module externe pour appliquer une texture sur une image dans digiKam
+Comment[gl]=Un plugin de digiKam para aplicar unha textura nunha imaxe
+Comment[hr]=digiKam dodatak za primjenu teksture u slici
+Comment[is]=Íforrit fyrir digiKam sem setur áferð á mynd
+Comment[it]=Plugin di digiKam per applicare una trama su un'immagine
+Comment[ja]=digiKam テクスチャ適用プラグイン
+Comment[nds]=digiKam-Moduul för't Överdregen vun Texturen
+Comment[nl]=Digikam-plugin voor het aanbrengen van textuur op een afbeelding
+Comment[pa]=ਚਿੱਤਰ ਉੱਤੇ ਪਾਠ ਲਿਖਣ ਲਈ ਡਿਜ਼ੀਕੈਮ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam pozwalająca na nakładanie tekstury na obraz
+Comment[pt]=Um 'plugin' do digiKam para aplicar uma textura numa imagem
+Comment[pt_BR]=Um 'plugin' do digiKam para aplicar uma textura numa imagem
+Comment[ru]=Модуль digiKam для наложения текстур на изображение
+Comment[sk]=digiKam plugin pre aplikovanie textúry na obrázok
+Comment[sr]=digiKam-ов прикључак за додавање текстура у слику
+Comment[sr@Latn]=digiKam-ov priključak za dodavanje tekstura u sliku
+Comment[sv]=Digikam insticksprogram för att lägga till struktur på en bild
+Comment[tr]=Bir resme doku eklemek için digiKam eklentisi
+Comment[uk]=Втулок застосування текстури для digiKam
+Comment[vi]=Phần bổ sung áp dụng hoạ tiết trên ảnh cho digiKam
+Comment[xx]=xxdigiKam plugin to apply texture on imagexx
+
+X-TDE-Library=digikamimageplugin_texture
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/texture/digikamimageplugin_texture_ui.rc b/src/imageplugins/texture/digikamimageplugin_texture_ui.rc
new file mode 100644
index 00000000..122833cc
--- /dev/null
+++ b/src/imageplugins/texture/digikamimageplugin_texture_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="4" name="digikamimageplugin_texture" >
+
+ <MenuBar>
+
+ <Menu name="Decorate" ><text>&amp;Decorate</text>
+ <Action name="imageplugin_texture" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action shortcut="" name="imageplugin_texture" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/texture/imageeffect_texture.cpp b/src/imageplugins/texture/imageeffect_texture.cpp
new file mode 100644
index 00000000..5f5d7e9f
--- /dev/null
+++ b/src/imageplugins/texture/imageeffect_texture.cpp
@@ -0,0 +1,291 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-10
+ * Description : a plugin to apply texture over an image
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqcombobox.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "texture.h"
+#include "imageeffect_texture.h"
+#include "imageeffect_texture.moc"
+
+namespace DigikamTextureImagesPlugin
+{
+
+ImageEffect_Texture::ImageEffect_Texture(TQWidget* parent)
+ : Digikam::CtrlPanelDlg(parent, i18n("Apply Texture"),
+ "texture", false, false, true,
+ Digikam::ImagePannelWidget::SeparateViewAll)
+{
+ TQString whatsThis;
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("Apply Texture"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to apply a decorative "
+ "texture to an image."),
+ TDEAboutData::License_GPL,
+ "(c) 2005, Gilles Caulier\n"
+ "(c) 2006-2008, Gilles Caulier and Marcel Wiesweg",
+ 0,
+ "http://www.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Marcel Wiesweg", I18N_NOOP("Developer"),
+ "marcel dot wiesweg at gmx dot de");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(m_imagePreviewWidget);
+ TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 2, 1, 0, spacingHint());
+ TQLabel *label1 = new TQLabel(i18n("Type:"), gboxSettings);
+
+ m_textureType = new TQComboBox( false, gboxSettings );
+ m_textureType->insertItem( i18n("Paper") );
+ m_textureType->insertItem( i18n("Paper 2") );
+ m_textureType->insertItem( i18n("Fabric") );
+ m_textureType->insertItem( i18n("Burlap") );
+ m_textureType->insertItem( i18n("Bricks") );
+ m_textureType->insertItem( i18n("Bricks 2") );
+ m_textureType->insertItem( i18n("Canvas") );
+ m_textureType->insertItem( i18n("Marble") );
+ m_textureType->insertItem( i18n("Marble 2") );
+ m_textureType->insertItem( i18n("Blue Jean") );
+ m_textureType->insertItem( i18n("Cell Wood") );
+ m_textureType->insertItem( i18n("Metal Wire") );
+ m_textureType->insertItem( i18n("Modern") );
+ m_textureType->insertItem( i18n("Wall") );
+ m_textureType->insertItem( i18n("Moss") );
+ m_textureType->insertItem( i18n("Stone") );
+ TQWhatsThis::add( m_textureType, i18n("<p>Set here the texture type to apply to the image."));
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridSettings->addMultiCellWidget(m_textureType, 0, 0, 1, 1);
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Relief:"), gboxSettings);
+
+ m_blendGain = new KIntNumInput(gboxSettings);
+ m_blendGain->setRange(1, 255, 1, true);
+ m_blendGain->setValue(200);
+ TQWhatsThis::add( m_blendGain, i18n("<p>Set here the relief gain used to merge texture and image."));
+
+ gridSettings->addMultiCellWidget(label2, 1, 1, 0, 1);
+ gridSettings->addMultiCellWidget(m_blendGain, 2, 2, 0, 1);
+
+ m_imagePreviewWidget->setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_textureType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_blendGain, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+ImageEffect_Texture::~ImageEffect_Texture()
+{
+}
+
+void ImageEffect_Texture::renderingFinished()
+{
+ m_textureType->setEnabled(true);
+ m_blendGain->setEnabled(true);
+}
+
+void ImageEffect_Texture::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("texture Tool Dialog");
+ m_textureType->blockSignals(true);
+ m_blendGain->blockSignals(true);
+ m_textureType->setCurrentItem(config->readNumEntry("TextureType", PaperTexture));
+ m_blendGain->setValue(config->readNumEntry("BlendGain", 200));
+ m_textureType->blockSignals(false);
+ m_blendGain->blockSignals(false);
+}
+
+void ImageEffect_Texture::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("texture Tool Dialog");
+ config->writeEntry("TextureType", m_textureType->currentItem());
+ config->writeEntry("BlendGain", m_blendGain->value());
+ config->sync();
+}
+
+void ImageEffect_Texture::resetValues()
+{
+ m_textureType->blockSignals(true);
+ m_blendGain->blockSignals(true);
+ m_textureType->setCurrentItem(PaperTexture);
+ m_blendGain->setValue(200);
+ m_textureType->blockSignals(false);
+ m_blendGain->blockSignals(false);
+}
+
+void ImageEffect_Texture::prepareEffect()
+{
+ m_textureType->setEnabled(false);
+ m_blendGain->setEnabled(false);
+
+ Digikam::DImg image = m_imagePreviewWidget->getOriginalRegionImage();
+ TQString texture = getTexturePath( m_textureType->currentItem() );
+
+ int b = 255 - m_blendGain->value();
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Texture(&image, this, b, texture));
+}
+
+void ImageEffect_Texture::prepareFinal()
+{
+ m_textureType->setEnabled(false);
+ m_blendGain->setEnabled(false);
+
+ int b = 255 - m_blendGain->value();
+
+ Digikam::ImageIface iface(0, 0);
+ TQString texture = getTexturePath( m_textureType->currentItem() );
+
+ m_threadedFilter = dynamic_cast<Digikam::DImgThreadedFilter *>(
+ new Texture(iface.getOriginalImg(), this, b, texture));
+}
+
+void ImageEffect_Texture::putPreviewData(void)
+{
+ m_imagePreviewWidget->setPreviewImage(m_threadedFilter->getTargetImage());
+}
+
+void ImageEffect_Texture::putFinalData(void)
+{
+ Digikam::ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Texture"), m_threadedFilter->getTargetImage().bits());
+}
+
+TQString ImageEffect_Texture::getTexturePath(int texture)
+{
+ TQString pattern;
+
+ switch (texture)
+ {
+ case PaperTexture:
+ pattern = "paper-texture";
+ break;
+
+ case Paper2Texture:
+ pattern = "paper2-texture";
+ break;
+
+ case FabricTexture:
+ pattern = "fabric-texture";
+ break;
+
+ case BurlapTexture:
+ pattern = "burlap-texture";
+ break;
+
+ case BricksTexture:
+ pattern = "bricks-texture";
+ break;
+
+ case Bricks2Texture:
+ pattern = "bricks2-texture";
+ break;
+
+ case CanvasTexture:
+ pattern = "canvas-texture";
+ break;
+
+ case MarbleTexture:
+ pattern = "marble-texture";
+ break;
+
+ case Marble2Texture:
+ pattern = "marble2-texture";
+ break;
+
+ case BlueJeanTexture:
+ pattern = "bluejean-texture";
+ break;
+
+ case CellWoodTexture:
+ pattern = "cellwood-texture";
+ break;
+
+ case MetalWireTexture:
+ pattern = "metalwire-texture";
+ break;
+
+ case ModernTexture:
+ pattern = "modern-texture";
+ break;
+
+ case WallTexture:
+ pattern = "wall-texture";
+ break;
+
+ case MossTexture:
+ pattern = "moss-texture";
+ break;
+
+ case StoneTexture:
+ pattern = "stone-texture";
+ break;
+ }
+
+ TDEGlobal::dirs()->addResourceType(pattern.ascii(), TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ return (TDEGlobal::dirs()->findResourceDir(pattern.ascii(), pattern + ".png") + pattern + ".png" );
+}
+
+} // NameSpace DigikamTextureImagesPlugin
+
diff --git a/src/imageplugins/texture/imageeffect_texture.h b/src/imageplugins/texture/imageeffect_texture.h
new file mode 100644
index 00000000..d37068b6
--- /dev/null
+++ b/src/imageplugins/texture/imageeffect_texture.h
@@ -0,0 +1,100 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-10
+ * Description : a plugin to apply texture over an image
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_TEXTURE_H
+#define IMAGEEFFECT_TEXTURE_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "ctrlpaneldlg.h"
+
+class TQComboBox;
+
+class KIntNumInput;
+
+namespace DigikamTextureImagesPlugin
+{
+
+class ImageEffect_Texture : public Digikam::CtrlPanelDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_Texture(TQWidget* parent);
+ ~ImageEffect_Texture();
+
+private:
+
+ TQString getTexturePath(int texture);
+
+private slots:
+
+ void readUserSettings();
+
+private:
+
+ void writeUserSettings();
+ void resetValues();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ enum TextureTypes
+ {
+ PaperTexture=0,
+ Paper2Texture,
+ FabricTexture,
+ BurlapTexture,
+ BricksTexture,
+ Bricks2Texture,
+ CanvasTexture,
+ MarbleTexture,
+ Marble2Texture,
+ BlueJeanTexture,
+ CellWoodTexture,
+ MetalWireTexture,
+ ModernTexture,
+ WallTexture,
+ MossTexture,
+ StoneTexture
+ };
+
+ TQComboBox *m_textureType;
+
+ KIntNumInput *m_blendGain;
+};
+
+} // NameSpace DigikamTextureImagesPlugin
+
+#endif /* IMAGEEFFECT_TEXTURE_H */
diff --git a/src/imageplugins/texture/imageplugin_texture.cpp b/src/imageplugins/texture/imageplugin_texture.cpp
new file mode 100644
index 00000000..ec0e3ba8
--- /dev/null
+++ b/src/imageplugins/texture/imageplugin_texture.cpp
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-10
+ * Description : a plugin to apply texture over an image
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "texturetool.h"
+#include "imageplugin_texture.h"
+#include "imageplugin_texture.moc"
+
+using namespace DigikamTextureImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_texture,
+ KGenericFactory<ImagePlugin_Texture>("digikamimageplugin_texture"));
+
+ImagePlugin_Texture::ImagePlugin_Texture(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_Texture")
+{
+ m_textureAction = new TDEAction(i18n("Apply Texture..."), "texture", 0,
+ this, TQ_SLOT(slotTexture()),
+ actionCollection(), "imageplugin_texture");
+
+ setXMLFile( "digikamimageplugin_texture_ui.rc" );
+
+ DDebug() << "ImagePlugin_Texture plugin loaded" << endl;
+}
+
+ImagePlugin_Texture::~ImagePlugin_Texture()
+{
+}
+
+void ImagePlugin_Texture::setEnabledActions(bool enable)
+{
+ m_textureAction->setEnabled(enable);
+}
+
+void ImagePlugin_Texture::slotTexture()
+{
+ TextureTool *tool = new TextureTool(this);
+ loadTool(tool);
+}
diff --git a/src/imageplugins/texture/imageplugin_texture.h b/src/imageplugins/texture/imageplugin_texture.h
new file mode 100644
index 00000000..bae2ba5e
--- /dev/null
+++ b/src/imageplugins/texture/imageplugin_texture.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-10
+ * Description : a plugin to apply texture over an image
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_TEXTURE_H
+#define IMAGEPLUGIN_TEXTURE_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_Texture : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_Texture(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_Texture();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotTexture();
+
+private:
+
+ TDEAction *m_textureAction;
+};
+
+#endif /* IMAGEPLUGIN_TEXTURE_H */
diff --git a/src/imageplugins/texture/patterns/Makefile.am b/src/imageplugins/texture/patterns/Makefile.am
new file mode 100644
index 00000000..414c7f20
--- /dev/null
+++ b/src/imageplugins/texture/patterns/Makefile.am
@@ -0,0 +1,5 @@
+borderpicsdir = $(kde_datadir)/digikam/data
+borderpics_DATA = bricks2-texture.png bricks-texture.png burlap-texture.png canvas-texture.png \
+ marble2-texture.png marble-texture.png paper-texture.png stone-texture.png \
+ fabric-texture.png paper2-texture.png bluejean-texture.png cellwood-texture.png \
+ metalwire-texture.png modern-texture.png wall-texture.png moss-texture.png \ No newline at end of file
diff --git a/src/imageplugins/texture/patterns/bluejean-texture.png b/src/imageplugins/texture/patterns/bluejean-texture.png
new file mode 100644
index 00000000..3885d60b
--- /dev/null
+++ b/src/imageplugins/texture/patterns/bluejean-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/bricks-texture.png b/src/imageplugins/texture/patterns/bricks-texture.png
new file mode 100644
index 00000000..0ebfa881
--- /dev/null
+++ b/src/imageplugins/texture/patterns/bricks-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/bricks2-texture.png b/src/imageplugins/texture/patterns/bricks2-texture.png
new file mode 100644
index 00000000..985c5e84
--- /dev/null
+++ b/src/imageplugins/texture/patterns/bricks2-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/burlap-texture.png b/src/imageplugins/texture/patterns/burlap-texture.png
new file mode 100644
index 00000000..04494770
--- /dev/null
+++ b/src/imageplugins/texture/patterns/burlap-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/canvas-texture.png b/src/imageplugins/texture/patterns/canvas-texture.png
new file mode 100644
index 00000000..6aa3df8e
--- /dev/null
+++ b/src/imageplugins/texture/patterns/canvas-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/cellwood-texture.png b/src/imageplugins/texture/patterns/cellwood-texture.png
new file mode 100644
index 00000000..b94f618c
--- /dev/null
+++ b/src/imageplugins/texture/patterns/cellwood-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/fabric-texture.png b/src/imageplugins/texture/patterns/fabric-texture.png
new file mode 100644
index 00000000..ac88eb74
--- /dev/null
+++ b/src/imageplugins/texture/patterns/fabric-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/marble-texture.png b/src/imageplugins/texture/patterns/marble-texture.png
new file mode 100644
index 00000000..c22ab4ed
--- /dev/null
+++ b/src/imageplugins/texture/patterns/marble-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/marble2-texture.png b/src/imageplugins/texture/patterns/marble2-texture.png
new file mode 100644
index 00000000..45b77f05
--- /dev/null
+++ b/src/imageplugins/texture/patterns/marble2-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/metalwire-texture.png b/src/imageplugins/texture/patterns/metalwire-texture.png
new file mode 100644
index 00000000..cf0b402c
--- /dev/null
+++ b/src/imageplugins/texture/patterns/metalwire-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/modern-texture.png b/src/imageplugins/texture/patterns/modern-texture.png
new file mode 100644
index 00000000..45ed8b7a
--- /dev/null
+++ b/src/imageplugins/texture/patterns/modern-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/moss-texture.png b/src/imageplugins/texture/patterns/moss-texture.png
new file mode 100644
index 00000000..9403189f
--- /dev/null
+++ b/src/imageplugins/texture/patterns/moss-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/paper-texture.png b/src/imageplugins/texture/patterns/paper-texture.png
new file mode 100644
index 00000000..c9c90205
--- /dev/null
+++ b/src/imageplugins/texture/patterns/paper-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/paper2-texture.png b/src/imageplugins/texture/patterns/paper2-texture.png
new file mode 100644
index 00000000..36817ffd
--- /dev/null
+++ b/src/imageplugins/texture/patterns/paper2-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/stone-texture.png b/src/imageplugins/texture/patterns/stone-texture.png
new file mode 100644
index 00000000..6f26b474
--- /dev/null
+++ b/src/imageplugins/texture/patterns/stone-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/patterns/wall-texture.png b/src/imageplugins/texture/patterns/wall-texture.png
new file mode 100644
index 00000000..d7322500
--- /dev/null
+++ b/src/imageplugins/texture/patterns/wall-texture.png
Binary files differ
diff --git a/src/imageplugins/texture/texture.cpp b/src/imageplugins/texture/texture.cpp
new file mode 100644
index 00000000..42477d5d
--- /dev/null
+++ b/src/imageplugins/texture/texture.cpp
@@ -0,0 +1,181 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Texture threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "dimg.h"
+#include "ddebug.h"
+#include "texture.h"
+
+namespace DigikamTextureImagesPlugin
+{
+
+Texture::Texture(Digikam::DImg *orgImage, TQObject *parent, int blendGain, TQString texturePath)
+ : Digikam::DImgThreadedFilter(orgImage, parent, "Texture")
+{
+ m_blendGain = blendGain;
+ m_texturePath = texturePath;
+
+ initFilter();
+}
+
+// This method is based on the Simulate Texture Film tutorial from GimpGuru.org web site
+// available at this url : http://www.gimpguru.org/Tutorials/SimulatedTexture/
+
+//#define INT_MULT(a,b,t) ((t) = (a) * (b) + 0x80, ( ( (t >> 8) + t ) >> 8))
+
+inline static int intMult8(uint a, uint b)
+{
+ uint t = a * b + 0x80;
+ return ((t >> 8) + t) >> 8;
+}
+
+inline static int intMult16(uint a, uint b)
+{
+ uint t = a * b + 0x8000;
+ return ((t >> 16) + t) >> 16;
+}
+
+void Texture::filterImage(void)
+{
+ // Texture tile.
+
+ int w = m_orgImage.width();
+ int h = m_orgImage.height();
+ int bytesDepth = m_orgImage.bytesDepth();
+ bool sixteenBit = m_orgImage.sixteenBit();
+
+ DDebug() << "Texture File: " << m_texturePath << endl;
+ Digikam::DImg texture(m_texturePath);
+ if ( texture.isNull() ) return;
+
+ Digikam::DImg textureImg(w, h, m_orgImage.sixteenBit(), m_orgImage.hasAlpha());
+
+ texture.convertToDepthOfImage(&textureImg);
+
+ for (int x = 0 ; x < w ; x+=texture.width())
+ for (int y = 0 ; y < h ; y+=texture.height())
+ textureImg.bitBltImage(&texture, x, y);
+
+ // Apply texture.
+
+ uchar* data = m_orgImage.bits();
+ uchar* pTeData = textureImg.bits();
+ uchar* pOutBits = m_destImage.bits();
+ uint offset;
+
+ Digikam::DColor teData, transData, inData, outData;
+ uchar *ptr, *dptr, *tptr;
+ int progress;
+
+ int blendGain;
+ if (sixteenBit)
+ blendGain = (m_blendGain + 1) * 256 - 1;
+ else
+ blendGain = m_blendGain;
+
+ // Make textured transparent layout.
+
+ for (int x = 0; !m_cancel && x < w; x++)
+ {
+ for (int y = 0; !m_cancel && y < h; y++)
+ {
+ offset = x*bytesDepth + (y*w*bytesDepth);
+ ptr = data + offset;
+ tptr = pTeData + offset;
+
+ // Read color
+ teData.setColor(tptr, sixteenBit);
+
+ // in the old algorithm, this was
+ //teData.channel.red = (teData.channel.red * (255 - m_blendGain) +
+ // transData.channel.red * m_blendGain) >> 8;
+ // but transdata was uninitialized, its components were apparently 0,
+ // so I removed the part after the "+".
+
+ if (sixteenBit)
+ {
+ teData.blendInvAlpha16(blendGain);
+ }
+ else
+ {
+ teData.blendInvAlpha8(blendGain);
+ }
+
+ // Overwrite RGB.
+ teData.setPixel(tptr);
+ }
+
+ // Update progress bar in dialog.
+ progress = (int) (((double)x * 50.0) / w);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+
+ // Merge layout and image using overlay method.
+
+ for (int x = 0; !m_cancel && x < w; x++)
+ {
+ for (int y = 0; !m_cancel && y < h; y++)
+ {
+ offset = x*bytesDepth + (y*w*bytesDepth);
+ ptr = data + offset;
+ dptr = pOutBits + offset;
+ tptr = pTeData + offset;
+
+ inData.setColor (ptr, sixteenBit);
+ outData.setColor(dptr, sixteenBit);
+ teData.setColor (tptr, sixteenBit);
+
+ if (sixteenBit)
+ {
+ outData.setRed ( intMult16 (inData.red(), inData.red() + intMult16(2 * teData.red(), 65535 - inData.red()) ) );
+ outData.setGreen( intMult16 (inData.green(), inData.green() + intMult16(2 * teData.green(), 65535 - inData.green()) ) );
+ outData.setBlue ( intMult16 (inData.blue(), inData.blue() + intMult16(2 * teData.blue(), 65535 - inData.blue()) ) );
+ }
+ else
+ {
+ outData.setRed ( intMult8 (inData.red(), inData.red() + intMult8(2 * teData.red(), 255 - inData.red()) ) );
+ outData.setGreen( intMult8 (inData.green(), inData.green() + intMult8(2 * teData.green(), 255 - inData.green()) ) );
+ outData.setBlue ( intMult8 (inData.blue(), inData.blue() + intMult8(2 * teData.blue(), 255 - inData.blue()) ) );
+ }
+ outData.setAlpha( inData.alpha() );
+ outData.setPixel( dptr );
+ }
+
+ // Update progress bar in dialog.
+ progress = (int) (50.0 + ((double)x * 50.0) / w);
+
+ if (progress%5 == 0)
+ postProgress(progress);
+ }
+}
+
+} // NameSpace DigikamTextureImagesPlugin
diff --git a/src/imageplugins/texture/texture.h b/src/imageplugins/texture/texture.h
new file mode 100644
index 00000000..1bcda2ff
--- /dev/null
+++ b/src/imageplugins/texture/texture.h
@@ -0,0 +1,62 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : Texture threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef TEXTURE_H
+#define TEXTURE_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace DigikamTextureImagesPlugin
+{
+
+class Texture : public Digikam::DImgThreadedFilter
+{
+
+public:
+
+ Texture(Digikam::DImg *orgImage, TQObject *parent=0, int blendGain=200,
+ TQString texturePath=TQString());
+
+ ~Texture(){};
+
+private:
+
+ virtual void filterImage(void);
+
+private:
+
+ int m_blendGain;
+
+ TQString m_texturePath;
+};
+
+} // NameSpace DigikamTextureImagesPlugin
+
+#endif /* TEXTURE_H */
diff --git a/src/imageplugins/texture/texturetool.cpp b/src/imageplugins/texture/texturetool.cpp
new file mode 100644
index 00000000..71e29aea
--- /dev/null
+++ b/src/imageplugins/texture/texturetool.cpp
@@ -0,0 +1,294 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-10
+ * Description : a plugin to apply texture over an image
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imagepanelwidget.h"
+#include "editortoolsettings.h"
+#include "texture.h"
+#include "texturetool.h"
+#include "texturetool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamTextureImagesPlugin
+{
+
+TextureTool::TextureTool(TQObject* parent)
+ : EditorToolThreaded(parent)
+{
+ setName("texture");
+ setToolName(i18n("Texture"));
+ setToolIcon(SmallIcon("texture"));
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel|
+ EditorToolSettings::PanIcon);
+ TQGridLayout* grid = new TQGridLayout( m_gboxSettings->plainPage(), 3, 1);
+ TQLabel *label1 = new TQLabel(i18n("Type:"), m_gboxSettings->plainPage());
+
+ m_textureType = new RComboBox(m_gboxSettings->plainPage());
+ m_textureType->insertItem(i18n("Paper"));
+ m_textureType->insertItem(i18n("Paper 2"));
+ m_textureType->insertItem(i18n("Fabric"));
+ m_textureType->insertItem(i18n("Burlap"));
+ m_textureType->insertItem(i18n("Bricks"));
+ m_textureType->insertItem(i18n("Bricks 2"));
+ m_textureType->insertItem(i18n("Canvas"));
+ m_textureType->insertItem(i18n("Marble"));
+ m_textureType->insertItem(i18n("Marble 2"));
+ m_textureType->insertItem(i18n("Blue Jean"));
+ m_textureType->insertItem(i18n("Cell Wood"));
+ m_textureType->insertItem(i18n("Metal Wire"));
+ m_textureType->insertItem(i18n("Modern"));
+ m_textureType->insertItem(i18n("Wall"));
+ m_textureType->insertItem(i18n("Moss"));
+ m_textureType->insertItem(i18n("Stone"));
+ m_textureType->setDefaultItem(PaperTexture);
+ TQWhatsThis::add( m_textureType, i18n("<p>Set here the texture type to apply to the image."));
+
+ // -------------------------------------------------------------
+
+ TQLabel *label2 = new TQLabel(i18n("Relief:"), m_gboxSettings->plainPage());
+
+ m_blendGain = new RIntNumInput(m_gboxSettings->plainPage());
+ m_blendGain->setRange(1, 255, 1);
+ m_blendGain->setDefaultValue(200);
+ TQWhatsThis::add( m_blendGain, i18n("<p>Set here the relief gain used to merge texture and image."));
+
+ grid->addMultiCellWidget(label1, 0, 0, 0, 0);
+ grid->addMultiCellWidget(m_textureType, 0, 0, 1, 1);
+ grid->addMultiCellWidget(label2, 1, 1, 0, 1);
+ grid->addMultiCellWidget(m_blendGain, 2, 2, 0, 1);
+ grid->setRowStretch(3, 10);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImagePanelWidget(470, 350, "texture Tool", m_gboxSettings->panIconView());
+
+ setToolView(m_previewWidget);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_textureType, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotEffect()));
+
+ connect(m_blendGain, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotTimer()));
+}
+
+TextureTool::~TextureTool()
+{
+}
+
+void TextureTool::renderingFinished()
+{
+ m_textureType->setEnabled(true);
+ m_blendGain->setEnabled(true);
+}
+
+void TextureTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("texture Tool");
+
+ m_textureType->blockSignals(true);
+ m_blendGain->blockSignals(true);
+
+ m_textureType->setCurrentItem(config->readNumEntry("TextureType", m_textureType->defaultItem()));
+ m_blendGain->setValue(config->readNumEntry("BlendGain", m_blendGain->defaultValue()));
+
+ m_textureType->blockSignals(false);
+ m_blendGain->blockSignals(false);
+}
+
+void TextureTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("texture Tool");
+ config->writeEntry("TextureType", m_textureType->currentItem());
+ config->writeEntry("BlendGain", m_blendGain->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void TextureTool::slotResetSettings()
+{
+ m_textureType->blockSignals(true);
+ m_blendGain->blockSignals(true);
+
+ m_textureType->slotReset();
+ m_blendGain->slotReset();
+
+ m_textureType->blockSignals(false);
+ m_blendGain->blockSignals(false);
+}
+
+void TextureTool::prepareEffect()
+{
+ m_textureType->setEnabled(false);
+ m_blendGain->setEnabled(false);
+
+ DImg image = m_previewWidget->getOriginalRegionImage();
+ TQString texture = getTexturePath( m_textureType->currentItem() );
+
+ int b = 255 - m_blendGain->value();
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Texture(&image, this, b, texture)));
+}
+
+void TextureTool::prepareFinal()
+{
+ m_textureType->setEnabled(false);
+ m_blendGain->setEnabled(false);
+
+ int b = 255 - m_blendGain->value();
+
+ ImageIface iface(0, 0);
+ TQString texture = getTexturePath( m_textureType->currentItem() );
+
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new Texture(iface.getOriginalImg(), this, b, texture)));
+}
+
+void TextureTool::putPreviewData()
+{
+ m_previewWidget->setPreviewImage(filter()->getTargetImage());
+}
+
+void TextureTool::putFinalData()
+{
+ ImageIface iface(0, 0);
+ iface.putOriginalImage(i18n("Texture"), filter()->getTargetImage().bits());
+}
+
+TQString TextureTool::getTexturePath(int texture)
+{
+ TQString pattern;
+
+ switch (texture)
+ {
+ case PaperTexture:
+ pattern = "paper-texture";
+ break;
+
+ case Paper2Texture:
+ pattern = "paper2-texture";
+ break;
+
+ case FabricTexture:
+ pattern = "fabric-texture";
+ break;
+
+ case BurlapTexture:
+ pattern = "burlap-texture";
+ break;
+
+ case BricksTexture:
+ pattern = "bricks-texture";
+ break;
+
+ case Bricks2Texture:
+ pattern = "bricks2-texture";
+ break;
+
+ case CanvasTexture:
+ pattern = "canvas-texture";
+ break;
+
+ case MarbleTexture:
+ pattern = "marble-texture";
+ break;
+
+ case Marble2Texture:
+ pattern = "marble2-texture";
+ break;
+
+ case BlueJeanTexture:
+ pattern = "bluejean-texture";
+ break;
+
+ case CellWoodTexture:
+ pattern = "cellwood-texture";
+ break;
+
+ case MetalWireTexture:
+ pattern = "metalwire-texture";
+ break;
+
+ case ModernTexture:
+ pattern = "modern-texture";
+ break;
+
+ case WallTexture:
+ pattern = "wall-texture";
+ break;
+
+ case MossTexture:
+ pattern = "moss-texture";
+ break;
+
+ case StoneTexture:
+ pattern = "stone-texture";
+ break;
+ }
+
+ TDEGlobal::dirs()->addResourceType(pattern.ascii(), TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ return (TDEGlobal::dirs()->findResourceDir(pattern.ascii(), pattern + ".png") + pattern + ".png" );
+}
+
+} // NameSpace DigikamTextureImagesPlugin
diff --git a/src/imageplugins/texture/texturetool.h b/src/imageplugins/texture/texturetool.h
new file mode 100644
index 00000000..1287bb3e
--- /dev/null
+++ b/src/imageplugins/texture/texturetool.h
@@ -0,0 +1,112 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-10
+ * Description : a plugin to apply texture over an image
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef TEXTURETOOL_H
+#define TEXTURETOOL_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+namespace KDcrawIface
+{
+class RIntNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class EditorToolSettings;
+class ImagePanelWidget;
+}
+
+namespace DigikamTextureImagesPlugin
+{
+
+class TextureTool : public Digikam::EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ TextureTool(TQObject* parent);
+ ~TextureTool();
+
+private:
+
+ TQString getTexturePath(int texture);
+
+private slots:
+
+ void slotResetSettings();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void prepareEffect();
+ void prepareFinal();
+ void putPreviewData();
+ void putFinalData();
+ void renderingFinished();
+
+private:
+
+ enum TextureTypes
+ {
+ PaperTexture=0,
+ Paper2Texture,
+ FabricTexture,
+ BurlapTexture,
+ BricksTexture,
+ Bricks2Texture,
+ CanvasTexture,
+ MarbleTexture,
+ Marble2Texture,
+ BlueJeanTexture,
+ CellWoodTexture,
+ MetalWireTexture,
+ ModernTexture,
+ WallTexture,
+ MossTexture,
+ StoneTexture
+ };
+
+ KDcrawIface::RComboBox *m_textureType;
+
+ KDcrawIface::RIntNumInput *m_blendGain;
+
+ Digikam::ImagePanelWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamTextureImagesPlugin
+
+#endif /* TEXTURETOOL_H */
diff --git a/src/imageplugins/whitebalance/Makefile.am b/src/imageplugins/whitebalance/Makefile.am
new file mode 100644
index 00000000..b53cf65a
--- /dev/null
+++ b/src/imageplugins/whitebalance/Makefile.am
@@ -0,0 +1,33 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikamimageplugin_whitebalance_la_SOURCES = imageplugin_whitebalance.cpp \
+ whitebalancetool.cpp
+
+digikamimageplugin_whitebalance_la_LIBADD = $(LIB_TDEPARTS) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamimageplugin_whitebalance_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx -lkdcraw -ltdeio
+
+kde_services_DATA = digikamimageplugin_whitebalance.desktop
+
+kde_module_LTLIBRARIES = digikamimageplugin_whitebalance.la
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimageplugin_whitebalance_ui.rc
diff --git a/src/imageplugins/whitebalance/digikamimageplugin_whitebalance.desktop b/src/imageplugins/whitebalance/digikamimageplugin_whitebalance.desktop
new file mode 100644
index 00000000..df425c58
--- /dev/null
+++ b/src/imageplugins/whitebalance/digikamimageplugin_whitebalance.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Name=ImagePlugin_WhiteBalance
+Name[bg]=Приставка за снимки - Баланс на бялото
+Name[el]=ΠρόσθετοΕικόνας_ΙσορροπίαΛευκού
+Name[fi]=Valkotasapaino
+Name[hr]=Bijeli balans
+Name[it]=PluginImmagini_BilanciamentoDelBianco
+Name[ms]=ImagePlugin_ImbanganPutih
+Name[nl]=Afbeeldingsplugin_Witbalans
+Name[sr]=Баланс белог
+Name[sr@Latn]=Balans belog
+Name[sv]=Insticksprogram för vitbalans
+Name[tr]=ResimEklentisi_BeyazDengesi
+Name[xx]=xxImagePlugin_WhiteBalancexx
+
+Type=Service
+X-TDE-ServiceTypes=Digikam/ImagePlugin
+Encoding=UTF-8
+Comment=White balance correction plugin for digiKam
+Comment[bg]=Приставка на digiKam за промяна баланса на бялото
+Comment[ca]=Connector pel digiKam per corregir el balanç de blancs
+Comment[da]=Hvid balance rettelsesplugin for digiKam
+Comment[de]=digiKam-Modul zur Korrektur des Weißabgleiches
+Comment[el]=Πρόσθετο διόρθωσης ισορροπίας λευκού για το digiKam
+Comment[es]=Plugin de digiKam para la corrección del equilibrio de blancos
+Comment[et]=DigiKami valge tasakaalu korrigeerimise plugin
+Comment[fa]=وصلۀ اصلاح تعادل سفید برای digiKam
+Comment[fi]=Korjaa kuvan valkotasapainon
+Comment[fr]=Module externe pour corriger la balance des blancs dans digiKam
+Comment[gl]=Un plugin de digiKam para corrixir o balance do branco
+Comment[hr]=digiKam dodatak za ispravljanje bijelog balansa
+Comment[is]=Íforrit fyrir digiKam sem breytir hvítvægi mynda
+Comment[it]=Plugin di correzione del bilanciamento del bianco per digiKam
+Comment[ja]=digiKam ホワイトバランス補正プラグイン
+Comment[ms]=Plugin pembetulan imbangan putih untuk digiKam
+Comment[nds]=digiKam-Korrektuurmoduul för de Wittbalangs
+Comment[nl]=Digikam-plugin voor het corrigeren van de witbalans
+Comment[pa]=ਡਿਜ਼ੀਕੈਮ ਲਈ ਚਿੱਟਾ ਸਾਵਾਂ ਸੋਧ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka do programu digiKam korygująca balans bieli
+Comment[pt]=Um 'plugin' do digiKam para corrigir o balanceamento de branco
+Comment[pt_BR]=Plugin para Correção de iluminação
+Comment[ru]=Модуль коррекции баланса белого для digiKam
+Comment[sk]=digiKam plugin na korekciu vyváženia bielej
+Comment[sr]=digiKam-ов прикључак исправку баланса белог
+Comment[sr@Latn]=digiKam-ov priključak ispravku balansa belog
+Comment[sv]=Digikam insticksprogram för korrigering av vitbalans
+Comment[tr]=digiKam için beyaz dengesi düzeltme eklentisi
+Comment[uk]=Втулок виправлення балансу білого для digiKam
+Comment[vi]=Phần bổ sung sửa cân bằng trắng cho digiKam
+Comment[xx]=xxWhite balance correction plugin for digiKamxx
+
+X-TDE-Library=digikamimageplugin_whitebalance
+author=Gilles Caulier, caulier dot gilles at gmail dot com
diff --git a/src/imageplugins/whitebalance/digikamimageplugin_whitebalance_ui.rc b/src/imageplugins/whitebalance/digikamimageplugin_whitebalance_ui.rc
new file mode 100644
index 00000000..9bdf8247
--- /dev/null
+++ b/src/imageplugins/whitebalance/digikamimageplugin_whitebalance_ui.rc
@@ -0,0 +1,20 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="5" name="digikamimageplugin_whitebalance" >
+
+ <MenuBar>
+
+ <Menu name="Color"><text>&amp;Color</text>
+ <Action name="imageplugin_whitebalance" />
+ </Menu>
+
+ </MenuBar>
+
+ <ToolBar name="ToolBar" >
+ <text>Main Toolbar</text>
+ </ToolBar>
+
+ <ActionProperties>
+ <Action name="imageplugin_whitebalance" />
+ </ActionProperties>
+
+</kpartgui>
diff --git a/src/imageplugins/whitebalance/imageeffect_whitebalance.cpp b/src/imageplugins/whitebalance/imageeffect_whitebalance.cpp
new file mode 100644
index 00000000..f72609dd
--- /dev/null
+++ b/src/imageplugins/whitebalance/imageeffect_whitebalance.cpp
@@ -0,0 +1,842 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-11
+ * Description : a digiKam image editor plugin to correct
+ * image white balance
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Guillaume Castagnino <casta at xwing dot info>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqhgroupbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqhbuttongroup.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqcombobox.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqpixmap.h>
+#include <tqfile.h>
+#include <tqtextstream.h>
+#include <tqvbox.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <tdemessagebox.h>
+#include <knuminput.h>
+#include <tdeglobalsettings.h>
+#include <tdefiledialog.h>
+#include <kseparator.h>
+#include <tdeconfig.h>
+#include <kactivelabel.h>
+
+// Local includes.
+
+#include "version.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "dcolor.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "imagehistogram.h"
+#include "whitebalance.h"
+#include "colorgradientwidget.h"
+#include "histogramwidget.h"
+#include "dimgimagefilters.h"
+#include "imageeffect_whitebalance.h"
+#include "imageeffect_whitebalance.moc"
+
+namespace DigikamWhiteBalanceImagesPlugin
+{
+
+ImageEffect_WhiteBalance::ImageEffect_WhiteBalance(TQWidget* parent)
+ : Digikam::ImageDlgBase(parent, i18n("White Color Balance Correction"),
+ "whitebalance", true, false)
+{
+ TQString whatsThis;
+
+ Digikam::ImageIface iface(0, 0);
+
+ m_destinationPreviewData = 0L;
+
+ // About data and help button.
+
+ TDEAboutData* about = new TDEAboutData("digikam",
+ I18N_NOOP("White Color Balance Correction"),
+ digikam_version,
+ I18N_NOOP("A digiKam image plugin to correct white color balance."),
+ TDEAboutData::License_GPL,
+ "(c) 2005-2008, Gilles Caulier",
+ 0,
+ "http://wwww.digikam.org");
+
+ about->addAuthor("Gilles Caulier", I18N_NOOP("Author and maintainer"),
+ "caulier dot gilles at gmail dot com");
+
+ about->addAuthor("Pawel T. Jochym", I18N_NOOP("White color balance correction algorithm"),
+ "jochym at ifj edu pl");
+
+ setAboutData(about);
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new Digikam::ImageWidget("whitebalance Tool Dialog", plainPage(),
+ i18n("<p>You can see here the image's white-balance "
+ "adjustments preview. You can pick color on image to "
+ "see the color level corresponding on histogram."));
+ setPreviewAreaWidget(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxSettings = new TQWidget(plainPage());
+ TQVBoxLayout* layout2 = new TQVBoxLayout( gboxSettings, spacingHint() );
+ TQGridLayout *grid = new TQGridLayout( layout2, 2, 4, spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, gboxSettings );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(gboxSettings);
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the "
+ "graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ grid->addMultiCellLayout(l1, 0, 0, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(gboxSettings);
+ m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram "
+ "drawing of the selected image channel. This one is "
+ "re-computed at any filter settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ grid->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+
+ // -------------------------------------------------------------
+
+ TQGridLayout *grid2 = new TQGridLayout(layout2, 13, 5, spacingHint());
+
+ m_temperatureLabel = new KActiveLabel(i18n("<qt><a href='http://en.wikipedia.org/wiki/Color_temperature'>Color Temperature</a> "
+ " (K): </qt>"), gboxSettings);
+ m_adjTemperatureLabel = new TQLabel(i18n("Adjustment:"), gboxSettings);
+ m_temperatureInput = new KDoubleNumInput(gboxSettings);
+ m_temperatureInput->setPrecision(1);
+ m_temperatureInput->setRange(2000.0, 12000.0, 10.0, true);
+ TQWhatsThis::add( m_temperatureInput, i18n("<p>Set here the white balance color temperature in Kelvin."));
+
+ m_temperaturePresetLabel = new TQLabel(i18n("Preset:"), gboxSettings);
+ m_temperaturePresetCB = new TQComboBox( false, gboxSettings );
+ m_temperaturePresetCB->insertItem( i18n("Candle") );
+ m_temperaturePresetCB->insertItem( i18n("40W Lamp") );
+ m_temperaturePresetCB->insertItem( i18n("100W Lamp") );
+ m_temperaturePresetCB->insertItem( i18n("200W Lamp") );
+ m_temperaturePresetCB->insertItem( i18n("Sunrise") );
+ m_temperaturePresetCB->insertItem( i18n("Studio Lamp") );
+ m_temperaturePresetCB->insertItem( i18n("Moonlight") );
+ m_temperaturePresetCB->insertItem( i18n("Neutral") );
+ m_temperaturePresetCB->insertItem( i18n("Daylight D50") );
+ m_temperaturePresetCB->insertItem( i18n("Photo Flash") );
+ m_temperaturePresetCB->insertItem( i18n("Sun") );
+ m_temperaturePresetCB->insertItem( i18n("Xenon Lamp") );
+ m_temperaturePresetCB->insertItem( i18n("Daylight D65") );
+ m_temperaturePresetCB->insertItem( i18n("None") );
+ TQWhatsThis::add( m_temperaturePresetCB, i18n("<p>Select the white balance color temperature "
+ "preset to use here:<p>"
+ "<b>Candle</b>: candle light (1850K).<p>"
+ "<b>40W Lamp</b>: 40 Watt incandescent lamp (2680K).<p>"
+ "<b>100W Lamp</b>: 100 Watt incandescent lamp (2800K).<p>"
+ "<b>200W Lamp</b>: 200 Watt incandescent lamp (3000K).<p>"
+ "<b>Sunrise</b>: sunrise or sunset light (3200K).<p>"
+ "<b>Studio Lamp</b>: tungsten lamp used in photo studio "
+ "or light at 1 hour from dusk/dawn (3400K).<p>"
+ "<b>Moonlight</b>: moon light (4100K).<p>"
+ "<b>Neutral</b>: neutral color temperature (4750K).<p>"
+ "<b>Daylight D50</b>: sunny daylight around noon (5000K).<p>"
+ "<b>Photo Flash</b>: electronic photo flash (5500K).<p>"
+ "<b>Sun</b>: effective sun temperature (5770K).<p>"
+ "<b>Xenon Lamp</b>: xenon lamp or light arc (6420K).<p>"
+ "<b>Daylight D65</b>: overcast sky light (6500K).<p>"
+ "<b>None</b>: no preset value."));
+ m_pickTemperature = new TQPushButton(gboxSettings);
+ TDEGlobal::dirs()->addResourceType("color-picker-grey", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-grey", "color-picker-grey.png");
+ m_pickTemperature->setPixmap( TQPixmap( directory + "color-picker-grey.png" ) );
+ m_pickTemperature->setToggleButton(true);
+ TQToolTip::add( m_pickTemperature, i18n( "Temperature tone color picker." ) );
+ TQWhatsThis::add( m_pickTemperature, i18n("<p>With this button, you can pick the color from original "
+ "image used to set white color balance temperature and "
+ "green component."));
+
+ KSeparator *line = new KSeparator(Horizontal, gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_blackLabel = new TQLabel(i18n("Black point:"), gboxSettings);
+ m_blackInput = new KDoubleNumInput(gboxSettings);
+ m_blackInput->setPrecision(2);
+ m_blackInput->setRange(0.0, 0.05, 0.01, true);
+ TQWhatsThis::add( m_blackInput, i18n("<p>Set here the black level value."));
+
+ m_darkLabel = new TQLabel(i18n("Shadows:"), gboxSettings);
+ m_darkInput = new KDoubleNumInput(gboxSettings);
+ m_darkInput->setPrecision(2);
+ m_darkInput->setRange(0.0, 1.0, 0.01, true);
+ TQWhatsThis::add( m_darkInput, i18n("<p>Set here the shadows noise suppresion level."));
+
+ m_saturationLabel = new TQLabel(i18n("Saturation:"), gboxSettings);
+ m_saturationInput = new KDoubleNumInput(gboxSettings);
+ m_saturationInput->setPrecision(2);
+ m_saturationInput->setRange(0.0, 2.0, 0.01, true);
+ TQWhatsThis::add( m_saturationInput, i18n("<p>Set here the saturation value."));
+
+ m_gammaLabel = new TQLabel(i18n("Gamma:"), gboxSettings);
+ m_gammaInput = new KDoubleNumInput(gboxSettings);
+ m_gammaInput->setPrecision(2);
+ m_gammaInput->setRange(0.1, 3.0, 0.01, true);
+ TQWhatsThis::add( m_gammaInput, i18n("<p>Set here the gamma correction value."));
+
+ m_greenLabel = new TQLabel(i18n("Green:"), gboxSettings);
+ m_greenInput = new KDoubleNumInput(gboxSettings);
+ m_greenInput->setPrecision(2);
+ m_greenInput->setRange(0.2, 2.5, 0.01, true);
+ TQWhatsThis::add(m_greenInput, i18n("<p>Set here the green component to set magenta color "
+ "cast removal level."));
+
+ KSeparator *line2 = new KSeparator(Horizontal, gboxSettings);
+
+ // -------------------------------------------------------------
+
+ m_exposureLabel = new KActiveLabel(i18n("<qt><a href='http://en.wikipedia.org/wiki/Exposure_value'>Exposure Compensation</a> "
+ " (E.V): </qt>"), gboxSettings);
+ m_mainExposureLabel = new TQLabel(i18n("Main:"), gboxSettings);
+ m_autoAdjustExposure = new TQPushButton(gboxSettings);
+ m_autoAdjustExposure->setPixmap(kapp->iconLoader()->loadIcon("system-run", (TDEIcon::Group)TDEIcon::Toolbar));
+ TQToolTip::add( m_autoAdjustExposure, i18n( "Auto exposure adjustments" ) );
+ TQWhatsThis::add( m_autoAdjustExposure, i18n("<p>With this button, you can automatically adjust Exposure "
+ "and Black Point values."));
+ m_mainExposureInput = new KDoubleNumInput(gboxSettings);
+ m_mainExposureInput->setPrecision(2);
+ m_mainExposureInput->setRange(-6.0, 8.0, 0.1, true);
+ TQWhatsThis::add( m_mainExposureInput, i18n("<p>Set here the main exposure compensation value in E.V."));
+
+ m_fineExposureLabel = new TQLabel(i18n("Fine:"), gboxSettings);
+ m_fineExposureInput = new KDoubleNumInput(gboxSettings);
+ m_fineExposureInput->setPrecision(2);
+ m_fineExposureInput->setRange(-0.5, 0.5, 0.01, true);
+ TQWhatsThis::add( m_fineExposureInput, i18n("<p>This value in E.V will be added to main exposure "
+ "compensation value to set fine exposure adjustment."));
+
+ // -------------------------------------------------------------
+
+ grid2->addMultiCellWidget(m_temperatureLabel, 0, 0, 0, 5);
+ grid2->addMultiCellWidget(m_adjTemperatureLabel, 1, 1, 0, 0);
+ grid2->addMultiCellWidget(m_pickTemperature, 1, 1, 1, 1);
+ grid2->addMultiCellWidget(m_temperatureInput, 1, 1, 2, 5);
+ grid2->addMultiCellWidget(m_temperaturePresetLabel, 2, 2, 0, 0);
+ grid2->addMultiCellWidget(m_temperaturePresetCB, 2, 2, 2, 5);
+
+ grid2->addMultiCellWidget(line, 3, 3, 0, 5);
+
+ grid2->addMultiCellWidget(m_blackLabel, 4, 4, 0, 0);
+ grid2->addMultiCellWidget(m_blackInput, 4, 4, 1, 5);
+ grid2->addMultiCellWidget(m_darkLabel, 5, 5, 0, 0);
+ grid2->addMultiCellWidget(m_darkInput, 5, 5, 1, 5);
+ grid2->addMultiCellWidget(m_saturationLabel, 6, 6, 0, 0);
+ grid2->addMultiCellWidget(m_saturationInput, 6, 6, 1, 5);
+ grid2->addMultiCellWidget(m_gammaLabel, 7, 7, 0, 0);
+ grid2->addMultiCellWidget(m_gammaInput, 7, 7, 1, 5);
+ grid2->addMultiCellWidget(m_greenLabel, 8, 8, 0, 0);
+ grid2->addMultiCellWidget(m_greenInput, 8, 8, 1, 5);
+
+ grid2->addMultiCellWidget(line2, 9, 9, 0, 5);
+
+ grid2->addMultiCellWidget(m_exposureLabel, 10, 10, 0, 5);
+ grid2->addMultiCellWidget(m_mainExposureLabel, 11, 11, 0, 0);
+ grid2->addMultiCellWidget(m_autoAdjustExposure, 11, 11, 1, 1);
+ grid2->addMultiCellWidget(m_mainExposureInput, 11, 11, 2, 5);
+ grid2->addMultiCellWidget(m_fineExposureLabel, 12, 12, 0, 1);
+ grid2->addMultiCellWidget(m_fineExposureInput, 12, 12, 2, 5);
+ grid2->setRowStretch(13, 10);
+
+ setUserAreaWidget(gboxSettings);
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromOriginal( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // Correction Filter Slider controls.
+
+ connect(m_temperaturePresetCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotTemperaturePresetChanged(int)));
+
+ connect(m_temperatureInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTemperatureChanged(double)));
+
+ connect(m_darkInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_blackInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_mainExposureInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineExposureInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gammaInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_saturationInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_greenInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ // -------------------------------------------------------------
+ // Bouttons slots.
+
+ connect(m_autoAdjustExposure, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAutoAdjustExposure()));
+
+ connect(m_pickTemperature, TQ_SIGNAL(released()),
+ this, TQ_SLOT(slotPickerColorButtonActived()));
+}
+
+ImageEffect_WhiteBalance::~ImageEffect_WhiteBalance()
+{
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ delete m_histogramWidget;
+}
+
+void ImageEffect_WhiteBalance::slotTemperatureChanged(double temperature)
+{
+ switch((uint)temperature)
+ {
+ case 1850:
+ m_temperaturePresetCB->setCurrentItem(Candle);
+ break;
+
+ case 2680:
+ m_temperaturePresetCB->setCurrentItem(Lamp40W);
+ break;
+
+ case 2800:
+ m_temperaturePresetCB->setCurrentItem(Lamp100W);
+ break;
+
+ case 3000:
+ m_temperaturePresetCB->setCurrentItem(Lamp200W);
+ break;
+
+ case 3200:
+ m_temperaturePresetCB->setCurrentItem(Sunrise);
+ break;
+
+ case 3400:
+ m_temperaturePresetCB->setCurrentItem(StudioLamp);
+ break;
+
+ case 4100:
+ m_temperaturePresetCB->setCurrentItem(MoonLight);
+ break;
+
+ case 4750:
+ m_temperaturePresetCB->setCurrentItem(Neutral);
+ break;
+
+ case 5000:
+ m_temperaturePresetCB->setCurrentItem(DaylightD50);
+ break;
+
+ case 5500:
+ m_temperaturePresetCB->setCurrentItem(Flash);
+ break;
+
+ case 5770:
+ m_temperaturePresetCB->setCurrentItem(Sun);
+ break;
+
+ case 6420:
+ m_temperaturePresetCB->setCurrentItem(XeonLamp);
+ break;
+
+ case 6500:
+ m_temperaturePresetCB->setCurrentItem(DaylightD65);
+ break;
+
+ default:
+ m_temperaturePresetCB->setCurrentItem(None);
+ break;
+ }
+
+ slotTimer();
+}
+
+void ImageEffect_WhiteBalance::slotTemperaturePresetChanged(int tempPreset)
+{
+ switch(tempPreset)
+ {
+ case Candle:
+ m_temperatureInput->setValue(1850.0);
+ break;
+
+ case Lamp40W:
+ m_temperatureInput->setValue(2680.0);
+ break;
+
+ case Lamp100W:
+ m_temperatureInput->setValue(2800.0);
+ break;
+
+ case Lamp200W:
+ m_temperatureInput->setValue(3000.0);
+ break;
+
+ case Sunrise:
+ m_temperatureInput->setValue(3200.0);
+ break;
+
+ case StudioLamp:
+ m_temperatureInput->setValue(3400.0);
+ break;
+
+ case MoonLight:
+ m_temperatureInput->setValue(4100.0);
+ break;
+
+ case Neutral:
+ m_temperatureInput->setValue(4750.0);
+ break;
+
+ case DaylightD50:
+ m_temperatureInput->setValue(5000.0);
+ break;
+
+ case Flash:
+ m_temperatureInput->setValue(5500.0);
+ break;
+
+ case Sun:
+ m_temperatureInput->setValue(5770.0);
+ break;
+
+ case XeonLamp:
+ m_temperatureInput->setValue(6420.0);
+ break;
+
+ case DaylightD65:
+ m_temperatureInput->setValue(6500.0);
+ break;
+
+ default: // None.
+ break;
+ }
+
+ slotEffect();
+}
+
+void ImageEffect_WhiteBalance::slotPickerColorButtonActived()
+{
+ // Save previous rendering mode and toggle to original image.
+ m_currentPreviewMode = m_previewWidget->getRenderingPreviewMode();
+ m_previewWidget->setRenderingPreviewMode(Digikam::ImageGuideWidget::PreviewOriginalImage);
+}
+
+void ImageEffect_WhiteBalance::slotColorSelectedFromOriginal(const Digikam::DColor &color)
+{
+ if ( m_pickTemperature->isOn() )
+ {
+ Digikam::DColor dc = color;
+ TQColor tc = dc.getTQColor();
+ double temperatureLevel, greenLevel;
+
+ Digikam::WhiteBalance::autoWBAdjustementFromColor(tc, temperatureLevel, greenLevel);
+
+ m_temperatureInput->setValue(temperatureLevel);
+ m_greenInput->setValue(greenLevel);
+ m_pickTemperature->setOn(false);
+ }
+ else
+ return;
+
+ // restore previous rendering mode.
+ m_previewWidget->setRenderingPreviewMode(m_currentPreviewMode);
+
+ slotEffect();
+}
+
+void ImageEffect_WhiteBalance::slotColorSelectedFromTarget( const Digikam::DColor &color )
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void ImageEffect_WhiteBalance::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_WhiteBalance::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void ImageEffect_WhiteBalance::slotAutoAdjustExposure()
+{
+ parentWidget()->setCursor( KCursor::waitCursor() );
+
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int width = iface->originalWidth();
+ int height = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ double blackLevel;
+ double exposureLevel;
+
+ Digikam::WhiteBalance::autoExposureAdjustement(data, width, height, sb, blackLevel, exposureLevel);
+ delete [] data;
+
+ m_blackInput->setValue(blackLevel);
+ m_mainExposureInput->setValue(exposureLevel);
+ m_fineExposureInput->setValue(0.0);
+
+ parentWidget()->unsetCursor();
+ slotEffect();
+}
+
+void ImageEffect_WhiteBalance::slotEffect()
+{
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+
+ double temperature = m_temperatureInput->value();
+ double dark = m_darkInput->value();
+ double black = m_blackInput->value();
+ double mainExposure = m_mainExposureInput->value();
+ double fineExposure = m_fineExposureInput->value();
+ double gamma = m_gammaInput->value();
+ double saturation = m_saturationInput->value();
+ double green = m_greenInput->value();
+
+ Digikam::WhiteBalance wbFilter(sb);
+ wbFilter.whiteBalance(data, w, h, sb,
+ black, mainExposure + fineExposure,
+ temperature, green, dark,
+ gamma, saturation);
+
+ iface->putPreviewImage(data);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ memcpy (m_destinationPreviewData, data, w*h*(sb ? 8 : 4));
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] data;
+}
+
+void ImageEffect_WhiteBalance::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ Digikam::ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ double temperature = m_temperatureInput->value();
+ double dark = m_darkInput->value();
+ double black = m_blackInput->value();
+ double mainExposure = m_mainExposureInput->value();
+ double fineExposure = m_fineExposureInput->value();
+ double gamma = m_gammaInput->value();
+ double saturation = m_saturationInput->value();
+ double green = m_greenInput->value();
+
+ Digikam::WhiteBalance wbFilter(sb);
+ wbFilter.whiteBalance(data, w, h, sb,
+ black, mainExposure + fineExposure,
+ temperature, green, dark,
+ gamma, saturation);
+
+ iface->putOriginalImage(i18n("White Balance"), data);
+ delete [] data;
+ kapp->restoreOverrideCursor();
+ accept();
+}
+
+void ImageEffect_WhiteBalance::resetValues()
+{
+ m_darkInput->blockSignals(true);
+ m_blackInput->blockSignals(true);
+ m_mainExposureInput->blockSignals(true);
+ m_fineExposureInput->blockSignals(true);
+ m_gammaInput->blockSignals(true);
+ m_saturationInput->blockSignals(true);
+ m_greenInput->blockSignals(true);
+ m_temperaturePresetCB->blockSignals(true);
+
+ // Neutral color temperature settings is D65
+ m_darkInput->setValue(0.5);
+ m_blackInput->setValue(0.0);
+ m_mainExposureInput->setValue(0.0);
+ m_fineExposureInput->setValue(0.0);
+ m_gammaInput->setValue(1.0);
+ m_saturationInput->setValue(1.0);
+ m_greenInput->setValue(1.0);
+ m_temperaturePresetCB->setCurrentItem(DaylightD65);
+ slotTemperaturePresetChanged(DaylightD65);
+
+ m_previewWidget->resetSpotPosition();
+ m_channelCB->setCurrentItem(LuminosityChannel);
+ slotChannelChanged(LuminosityChannel);
+
+ m_histogramWidget->reset();
+
+ m_darkInput->blockSignals(false);
+ m_blackInput->blockSignals(false);
+ m_mainExposureInput->blockSignals(false);
+ m_fineExposureInput->blockSignals(false);
+ m_gammaInput->blockSignals(false);
+ m_saturationInput->blockSignals(false);
+ m_greenInput->blockSignals(false);
+ m_temperaturePresetCB->blockSignals(false);
+ slotEffect();
+}
+
+void ImageEffect_WhiteBalance::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("whitebalance Tool Dialog");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", Digikam::HistogramWidget::LogScaleHistogram));
+
+ m_darkInput->setValue(config->readDoubleNumEntry("Dark", 0.5));
+ m_blackInput->setValue(config->readDoubleNumEntry("Black", 0.0));
+ m_mainExposureInput->setValue(config->readDoubleNumEntry("MainExposure", 0.0));
+ m_fineExposureInput->setValue(config->readDoubleNumEntry("FineExposure", 0.0));
+ m_gammaInput->setValue(config->readDoubleNumEntry("Gamma", 1.0));
+ m_saturationInput->setValue(config->readDoubleNumEntry("Saturation", 1.0));
+ m_greenInput->setValue(config->readDoubleNumEntry("Green", 1.0));
+ m_temperatureInput->setValue(config->readDoubleNumEntry("Temperature", 6500.0));
+ slotTemperatureChanged(m_temperatureInput->value());
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void ImageEffect_WhiteBalance::writeUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("whitebalance Tool Dialog");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ config->writeEntry("Dark", m_darkInput->value());
+ config->writeEntry("Black", m_blackInput->value());
+ config->writeEntry("MainExposure", m_mainExposureInput->value());
+ config->writeEntry("FineExposure", m_fineExposureInput->value());
+ config->writeEntry("Gamma", m_gammaInput->value());
+ config->writeEntry("Saturation", m_saturationInput->value());
+ config->writeEntry("Green", m_greenInput->value());
+ config->writeEntry("Temperature", m_temperatureInput->value());
+ config->sync();
+}
+
+// Load all settings.
+void ImageEffect_WhiteBalance::slotUser3()
+{
+ KURL loadWhiteBalanceFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("White Color Balance Settings File to Load")) );
+ if( loadWhiteBalanceFile.isEmpty() )
+ return;
+
+ TQFile file(loadWhiteBalanceFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+
+ if ( stream.readLine() != "# White Color Balance Configuration File V2" )
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a White Color Balance settings text file.")
+ .arg(loadWhiteBalanceFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+ m_temperatureInput->setValue( stream.readLine().toDouble() );
+ m_darkInput->setValue( stream.readLine().toDouble() );
+ m_blackInput->setValue( stream.readLine().toDouble() );
+ m_mainExposureInput->setValue( stream.readLine().toDouble() );
+ m_fineExposureInput->setValue( stream.readLine().toDouble() );
+ m_gammaInput->setValue( stream.readLine().toDouble() );
+ m_saturationInput->setValue( stream.readLine().toDouble() );
+ m_greenInput->setValue( stream.readLine().toDouble() );
+ m_histogramWidget->reset();
+ blockSignals(false);
+ slotEffect();
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the White Color Balance text file."));
+
+ file.close();
+}
+
+// Save all settings.
+void ImageEffect_WhiteBalance::slotUser2()
+{
+ KURL saveWhiteBalanceFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("White Color Balance Settings File to Save")) );
+ if( saveWhiteBalanceFile.isEmpty() )
+ return;
+
+ TQFile file(saveWhiteBalanceFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# White Color Balance Configuration File V2\n";
+ stream << m_temperatureInput->value() << "\n";
+ stream << m_darkInput->value() << "\n";
+ stream << m_blackInput->value() << "\n";
+ stream << m_mainExposureInput->value() << "\n";
+ stream << m_fineExposureInput->value() << "\n";
+ stream << m_gammaInput->value() << "\n";
+ stream << m_saturationInput->value() << "\n";
+ stream << m_greenInput->value() << "\n";
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the White Color Balance text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamWhiteBalanceImagesPlugin
+
diff --git a/src/imageplugins/whitebalance/imageeffect_whitebalance.h b/src/imageplugins/whitebalance/imageeffect_whitebalance.h
new file mode 100644
index 00000000..6bef5607
--- /dev/null
+++ b/src/imageplugins/whitebalance/imageeffect_whitebalance.h
@@ -0,0 +1,168 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-11
+ * Description : a digiKam image editor plugin to correct
+ * image white balance
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Guillaume Castagnino <casta at xwing dot info>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEEFFECT_WHITEBALANCE_H
+#define IMAGEEFFECT_WHITEBALANCE_H
+
+// TQt include.
+
+#include <tqcolor.h>
+
+// Digikam includes.
+
+#include "imagedlgbase.h"
+
+class TQPushButton;
+class TQLabel;
+class TQComboBox;
+class TQPushButton;
+class TQHButtonGroup;
+
+class KDoubleNumInput;
+class KActiveLabel;
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+}
+
+namespace DigikamWhiteBalanceImagesPlugin
+{
+
+class ImageEffect_WhiteBalance : public Digikam::ImageDlgBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageEffect_WhiteBalance(TQWidget* parent);
+ ~ImageEffect_WhiteBalance();
+
+protected:
+
+ void finalRendering();
+
+private slots:
+
+ void slotUser2();
+ void slotUser3();
+ void slotEffect();
+ void slotColorSelectedFromOriginal(const Digikam::DColor &color);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+ void slotScaleChanged(int scale);
+ void slotChannelChanged(int channel);
+ void slotTemperatureChanged(double temperature);
+ void slotTemperaturePresetChanged(int tempPreset);
+ void slotAutoAdjustExposure(void);
+ void slotPickerColorButtonActived();
+
+private:
+
+ void readUserSettings();
+ void writeUserSettings();
+ void resetValues();
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum TemperaturePreset
+ {
+ Candle=0,
+ Lamp40W,
+ Lamp100W,
+ Lamp200W,
+ Sunrise,
+ StudioLamp,
+ MoonLight,
+ Neutral,
+ DaylightD50,
+ Flash,
+ Sun,
+ XeonLamp,
+ DaylightD65,
+ None
+ };
+
+ uchar *m_destinationPreviewData;
+
+ int m_currentPreviewMode;
+
+ TQPushButton *m_pickTemperature;
+ TQPushButton *m_autoAdjustExposure;
+
+ TQComboBox *m_temperaturePresetCB;
+ TQComboBox *m_channelCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQLabel *m_adjTemperatureLabel;
+ TQLabel *m_temperaturePresetLabel;
+ TQLabel *m_darkLabel;
+ TQLabel *m_blackLabel;
+ TQLabel *m_mainExposureLabel;
+ TQLabel *m_fineExposureLabel;
+ TQLabel *m_gammaLabel;
+ TQLabel *m_saturationLabel;
+ TQLabel *m_greenLabel;
+
+ KActiveLabel *m_exposureLabel;
+ KActiveLabel *m_temperatureLabel;
+
+ KDoubleNumInput *m_temperatureInput;
+ KDoubleNumInput *m_darkInput;
+ KDoubleNumInput *m_blackInput;
+ KDoubleNumInput *m_mainExposureInput;
+ KDoubleNumInput *m_fineExposureInput;
+ KDoubleNumInput *m_gammaInput;
+ KDoubleNumInput *m_saturationInput;
+ KDoubleNumInput *m_greenInput;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::ImageWidget *m_previewWidget;
+};
+
+} // NameSpace DigikamWhiteBalanceImagesPlugin
+
+#endif /* IMAGEEFFECT_WHITEBALANCE_H */
diff --git a/src/imageplugins/whitebalance/imageplugin_whitebalance.cpp b/src/imageplugins/whitebalance/imageplugin_whitebalance.cpp
new file mode 100644
index 00000000..071097fe
--- /dev/null
+++ b/src/imageplugins/whitebalance/imageplugin_whitebalance.cpp
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-11
+ * Description : a digiKam image editor plugin to correct
+ * image white balance
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kgenericfactory.h>
+#include <klibloader.h>
+#include <tdeaction.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "whitebalancetool.h"
+#include "imageplugin_whitebalance.h"
+#include "imageplugin_whitebalance.moc"
+
+using namespace DigikamWhiteBalanceImagesPlugin;
+
+K_EXPORT_COMPONENT_FACTORY(digikamimageplugin_whitebalance,
+ KGenericFactory<ImagePlugin_WhiteBalance>("digikamimageplugin_whitebalance"));
+
+ImagePlugin_WhiteBalance::ImagePlugin_WhiteBalance(TQObject *parent, const char*, const TQStringList &)
+ : Digikam::ImagePlugin(parent, "ImagePlugin_WhiteBalance")
+{
+ m_whitebalanceAction = new TDEAction(i18n("White Balance..."), "whitebalance",
+ CTRL+SHIFT+Key_W,
+ this, TQ_SLOT(slotWhiteBalance()),
+ actionCollection(), "imageplugin_whitebalance");
+
+ setXMLFile("digikamimageplugin_whitebalance_ui.rc");
+
+ DDebug() << "ImagePlugin_WhiteBalance plugin loaded" << endl;
+}
+
+ImagePlugin_WhiteBalance::~ImagePlugin_WhiteBalance()
+{
+}
+
+void ImagePlugin_WhiteBalance::setEnabledActions(bool enable)
+{
+ m_whitebalanceAction->setEnabled(enable);
+}
+
+void ImagePlugin_WhiteBalance::slotWhiteBalance()
+{
+ WhiteBalanceTool *wb = new WhiteBalanceTool(this);
+ loadTool(wb);
+}
diff --git a/src/imageplugins/whitebalance/imageplugin_whitebalance.h b/src/imageplugins/whitebalance/imageplugin_whitebalance.h
new file mode 100644
index 00000000..afc870c1
--- /dev/null
+++ b/src/imageplugins/whitebalance/imageplugin_whitebalance.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-11
+ * Description : a digiKam image editor plugin to correct
+ * image white balance
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_WHITEBALANCE_H
+#define IMAGEPLUGIN_WHITEBALANCE_H
+
+// Digikam includes.
+
+#include "imageplugin.h"
+#include "digikam_export.h"
+
+class TDEAction;
+
+class DIGIKAMIMAGEPLUGINS_EXPORT ImagePlugin_WhiteBalance : public Digikam::ImagePlugin
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin_WhiteBalance(TQObject *parent, const char* name,
+ const TQStringList &args);
+ ~ImagePlugin_WhiteBalance();
+
+ void setEnabledActions(bool enable);
+
+private slots:
+
+ void slotWhiteBalance();
+
+private:
+
+ TDEAction *m_whitebalanceAction;
+};
+
+#endif /* IMAGEPLUGIN_WHITEBALANCE_H */
diff --git a/src/imageplugins/whitebalance/whitebalancetool.cpp b/src/imageplugins/whitebalance/whitebalancetool.cpp
new file mode 100644
index 00000000..fdbe723a
--- /dev/null
+++ b/src/imageplugins/whitebalance/whitebalancetool.cpp
@@ -0,0 +1,850 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-11
+ * Description : a digiKam image editor plugin to correct
+ * image white balance
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Guillaume Castagnino <casta at xwing dot info>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcombobox.h>
+#include <tqfile.h>
+#include <tqframe.h>
+#include <tqhbuttongroup.h>
+#include <tqhgroupbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqpixmap.h>
+#include <tqpushbutton.h>
+#include <tqtextstream.h>
+#include <tqtimer.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqvgroupbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeaboutdata.h>
+#include <kactivelabel.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kcursor.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <tdepopupmenu.h>
+#include <kprogress.h>
+#include <kseparator.h>
+#include <kstandarddirs.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Digikam includes.
+
+#include "colorgradientwidget.h"
+#include "daboutdata.h"
+#include "dcolor.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgimagefilters.h"
+#include "editortoolsettings.h"
+#include "histogramwidget.h"
+#include "imagehistogram.h"
+#include "imageiface.h"
+#include "imagewidget.h"
+#include "whitebalance.h"
+
+// Local includes.
+
+#include "whitebalancetool.h"
+#include "whitebalancetool.moc"
+
+using namespace KDcrawIface;
+using namespace Digikam;
+
+namespace DigikamWhiteBalanceImagesPlugin
+{
+
+WhiteBalanceTool::WhiteBalanceTool(TQObject* parent)
+ : EditorTool(parent)
+{
+ setName("whitebalance");
+ setToolName(i18n("White Balance"));
+ setToolIcon(SmallIcon("whitebalance"));
+
+ m_destinationPreviewData = 0;
+
+ // -------------------------------------------------------------
+
+ m_previewWidget = new ImageWidget("whitebalance Tool", 0,
+ i18n("<p>You can see here the image's white-balance "
+ "adjustments preview. You can pick color on image to "
+ "see the color level corresponding on histogram."));
+ setToolView(m_previewWidget);
+
+ // -------------------------------------------------------------
+
+ m_gboxSettings = new EditorToolSettings(EditorToolSettings::Default|
+ EditorToolSettings::Load|
+ EditorToolSettings::SaveAs|
+ EditorToolSettings::Ok|
+ EditorToolSettings::Cancel);
+
+ TQVBoxLayout* layout2 = new TQVBoxLayout(m_gboxSettings->plainPage(), m_gboxSettings->spacingHint());
+ TQGridLayout *grid = new TQGridLayout(layout2, 2, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), m_gboxSettings->plainPage());
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ m_channelCB = new TQComboBox( false, m_gboxSettings->plainPage() );
+ m_channelCB->insertItem( i18n("Luminosity") );
+ m_channelCB->insertItem( i18n("Red") );
+ m_channelCB->insertItem( i18n("Green") );
+ m_channelCB->insertItem( i18n("Blue") );
+ TQWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"));
+
+ m_scaleBG = new TQHButtonGroup(m_gboxSettings->plainPage());
+ m_scaleBG->setExclusive(true);
+ m_scaleBG->setFrameShape(TQFrame::NoFrame);
+ m_scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the "
+ "graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ m_scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( m_scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ m_scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQHBoxLayout* l1 = new TQHBoxLayout();
+ l1->addWidget(label1);
+ l1->addWidget(m_channelCB);
+ l1->addStretch(10);
+ l1->addWidget(m_scaleBG);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(m_gboxSettings->plainPage());
+ m_histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram "
+ "drawing of the selected image channel. This one is "
+ "re-computed at any filter settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ m_hGradient = new ColorGradientWidget( ColorGradientWidget::Horizontal, 10, histoBox );
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ grid->addMultiCellLayout(l1, 0, 0, 0, 4);
+ grid->addMultiCellWidget(histoBox, 1, 2, 0, 4);
+ grid->setMargin(m_gboxSettings->spacingHint());
+ grid->setSpacing(m_gboxSettings->spacingHint());
+
+ // -------------------------------------------------------------
+
+ TQGridLayout *grid2 = new TQGridLayout(layout2, 13, 5);
+
+ m_temperatureLabel = new KActiveLabel(i18n("<qt><a href='http://en.wikipedia.org/wiki/Color_temperature'>Color Temperature</a> "
+ " (K): </qt>"), m_gboxSettings->plainPage());
+ m_adjTemperatureLabel = new TQLabel(i18n("Adjustment:"), m_gboxSettings->plainPage());
+ m_temperatureInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_temperatureInput->setPrecision(1);
+ m_temperatureInput->setRange(1750.0, 12000.0, 10.0);
+ m_temperatureInput->setDefaultValue(6500.0);
+ TQWhatsThis::add( m_temperatureInput, i18n("<p>Set here the white balance color temperature in Kelvin."));
+
+ m_temperaturePresetLabel = new TQLabel(i18n("Preset:"), m_gboxSettings->plainPage());
+ m_temperaturePresetCB = new RComboBox(m_gboxSettings->plainPage());
+ m_temperaturePresetCB->insertItem(i18n("Candle"));
+ m_temperaturePresetCB->insertItem(i18n("40W Lamp"));
+ m_temperaturePresetCB->insertItem(i18n("100W Lamp"));
+ m_temperaturePresetCB->insertItem(i18n("200W Lamp"));
+ m_temperaturePresetCB->insertItem(i18n("Sunrise"));
+ m_temperaturePresetCB->insertItem(i18n("Studio Lamp"));
+ m_temperaturePresetCB->insertItem(i18n("Moonlight"));
+ m_temperaturePresetCB->insertItem(i18n("Neutral"));
+ m_temperaturePresetCB->insertItem(i18n("Daylight D50"));
+ m_temperaturePresetCB->insertItem(i18n("Photo Flash"));
+ m_temperaturePresetCB->insertItem(i18n("Sun"));
+ m_temperaturePresetCB->insertItem(i18n("Xenon Lamp"));
+ m_temperaturePresetCB->insertItem(i18n("Daylight D65"));
+ m_temperaturePresetCB->insertItem(i18n("None"));
+ m_temperaturePresetCB->setDefaultItem(DaylightD65);
+ TQWhatsThis::add( m_temperaturePresetCB, i18n("<p>Select the white balance color temperature "
+ "preset to use here:<p>"
+ "<b>Candle</b>: candle light (1850K).<p>"
+ "<b>40W Lamp</b>: 40 Watt incandescent lamp (2680K).<p>"
+ "<b>100W Lamp</b>: 100 Watt incandescent lamp (2800K).<p>"
+ "<b>200W Lamp</b>: 200 Watt incandescent lamp (3000K).<p>"
+ "<b>Sunrise</b>: sunrise or sunset light (3200K).<p>"
+ "<b>Studio Lamp</b>: tungsten lamp used in photo studio "
+ "or light at 1 hour from dusk/dawn (3400K).<p>"
+ "<b>Moonlight</b>: moon light (4100K).<p>"
+ "<b>Neutral</b>: neutral color temperature (4750K).<p>"
+ "<b>Daylight D50</b>: sunny daylight around noon (5000K).<p>"
+ "<b>Photo Flash</b>: electronic photo flash (5500K).<p>"
+ "<b>Sun</b>: effective sun temperature (5770K).<p>"
+ "<b>Xenon Lamp</b>: xenon lamp or light arc (6420K).<p>"
+ "<b>Daylight D65</b>: overcast sky light (6500K).<p>"
+ "<b>None</b>: no preset value."));
+ m_pickTemperature = new TQPushButton(m_gboxSettings->plainPage());
+ TDEGlobal::dirs()->addResourceType("color-picker-grey", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("color-picker-grey", "color-picker-grey.png");
+ m_pickTemperature->setPixmap( TQPixmap( directory + "color-picker-grey.png" ) );
+ m_pickTemperature->setToggleButton(true);
+ TQToolTip::add( m_pickTemperature, i18n( "Temperature tone color picker." ) );
+ TQWhatsThis::add( m_pickTemperature, i18n("<p>With this button, you can pick the color from original "
+ "image used to set white color balance temperature and "
+ "green component."));
+
+ KSeparator *line = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ // -------------------------------------------------------------
+
+ m_blackLabel = new TQLabel(i18n("Black point:"), m_gboxSettings->plainPage());
+ m_blackInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_blackInput->setPrecision(2);
+ m_blackInput->setRange(0.0, 0.05, 0.01);
+ m_blackInput->setDefaultValue(0.0);
+ TQWhatsThis::add( m_blackInput, i18n("<p>Set here the black level value."));
+
+ m_darkLabel = new TQLabel(i18n("Shadows:"), m_gboxSettings->plainPage());
+ m_darkInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_darkInput->setPrecision(2);
+ m_darkInput->setRange(0.0, 1.0, 0.01);
+ m_darkInput->setDefaultValue(0.5);
+ TQWhatsThis::add( m_darkInput, i18n("<p>Set here the shadows noise suppresion level."));
+
+ m_saturationLabel = new TQLabel(i18n("Saturation:"), m_gboxSettings->plainPage());
+ m_saturationInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_saturationInput->setPrecision(2);
+ m_saturationInput->setRange(0.0, 2.0, 0.01);
+ m_saturationInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_saturationInput, i18n("<p>Set here the saturation value."));
+
+ m_gammaLabel = new TQLabel(i18n("Gamma:"), m_gboxSettings->plainPage());
+ m_gammaInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_gammaInput->setPrecision(2);
+ m_gammaInput->setRange(0.1, 3.0, 0.01);
+ m_gammaInput->setDefaultValue(1.0);
+ TQWhatsThis::add( m_gammaInput, i18n("<p>Set here the gamma correction value."));
+
+ m_greenLabel = new TQLabel(i18n("Green:"), m_gboxSettings->plainPage());
+ m_greenInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_greenInput->setPrecision(2);
+ m_greenInput->setRange(0.2, 2.5, 0.01);
+ m_greenInput->setDefaultValue(1.0);
+ TQWhatsThis::add(m_greenInput, i18n("<p>Set here the green component to set magenta color "
+ "cast removal level."));
+
+ KSeparator *line2 = new KSeparator(Horizontal, m_gboxSettings->plainPage());
+
+ // -------------------------------------------------------------
+
+ m_exposureLabel = new KActiveLabel(i18n("<qt><a href='http://en.wikipedia.org/wiki/Exposure_value'>Exposure Compensation</a> "
+ " (E.V): </qt>"), m_gboxSettings->plainPage());
+ m_mainExposureLabel = new TQLabel(i18n("Main:"), m_gboxSettings->plainPage());
+ m_autoAdjustExposure = new TQPushButton(m_gboxSettings->plainPage());
+ m_autoAdjustExposure->setPixmap(kapp->iconLoader()->loadIcon("system-run", (TDEIcon::Group)TDEIcon::Toolbar));
+ TQToolTip::add( m_autoAdjustExposure, i18n( "Auto exposure adjustments" ) );
+ TQWhatsThis::add( m_autoAdjustExposure, i18n("<p>With this button, you can automatically adjust Exposure "
+ "and Black Point values."));
+ m_mainExposureInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_mainExposureInput->setPrecision(2);
+ m_mainExposureInput->setRange(-6.0, 8.0, 0.1);
+ m_mainExposureInput->setDefaultValue(0.0);
+ TQWhatsThis::add( m_mainExposureInput, i18n("<p>Set here the main exposure compensation value in E.V."));
+
+ m_fineExposureLabel = new TQLabel(i18n("Fine:"), m_gboxSettings->plainPage());
+ m_fineExposureInput = new RDoubleNumInput(m_gboxSettings->plainPage());
+ m_fineExposureInput->setPrecision(2);
+ m_fineExposureInput->setRange(-0.5, 0.5, 0.01);
+ m_fineExposureInput->setDefaultValue(0.0);
+ TQWhatsThis::add( m_fineExposureInput, i18n("<p>This value in E.V will be added to main exposure "
+ "compensation value to set fine exposure adjustment."));
+
+ // -------------------------------------------------------------
+
+ grid2->addMultiCellWidget(m_temperatureLabel, 0, 0, 0, 5);
+ grid2->addMultiCellWidget(m_adjTemperatureLabel, 1, 1, 0, 0);
+ grid2->addMultiCellWidget(m_pickTemperature, 1, 1, 1, 1);
+ grid2->addMultiCellWidget(m_temperatureInput, 1, 1, 2, 5);
+ grid2->addMultiCellWidget(m_temperaturePresetLabel, 2, 2, 0, 0);
+ grid2->addMultiCellWidget(m_temperaturePresetCB, 2, 2, 2, 5);
+
+ grid2->addMultiCellWidget(line, 3, 3, 0, 5);
+
+ grid2->addMultiCellWidget(m_blackLabel, 4, 4, 0, 0);
+ grid2->addMultiCellWidget(m_blackInput, 4, 4, 1, 5);
+ grid2->addMultiCellWidget(m_darkLabel, 5, 5, 0, 0);
+ grid2->addMultiCellWidget(m_darkInput, 5, 5, 1, 5);
+ grid2->addMultiCellWidget(m_saturationLabel, 6, 6, 0, 0);
+ grid2->addMultiCellWidget(m_saturationInput, 6, 6, 1, 5);
+ grid2->addMultiCellWidget(m_gammaLabel, 7, 7, 0, 0);
+ grid2->addMultiCellWidget(m_gammaInput, 7, 7, 1, 5);
+ grid2->addMultiCellWidget(m_greenLabel, 8, 8, 0, 0);
+ grid2->addMultiCellWidget(m_greenInput, 8, 8, 1, 5);
+
+ grid2->addMultiCellWidget(line2, 9, 9, 0, 5);
+
+ grid2->addMultiCellWidget(m_exposureLabel, 10, 10, 0, 5);
+ grid2->addMultiCellWidget(m_mainExposureLabel, 11, 11, 0, 0);
+ grid2->addMultiCellWidget(m_autoAdjustExposure, 11, 11, 1, 1);
+ grid2->addMultiCellWidget(m_mainExposureInput, 11, 11, 2, 5);
+ grid2->addMultiCellWidget(m_fineExposureLabel, 12, 12, 0, 1);
+ grid2->addMultiCellWidget(m_fineExposureInput, 12, 12, 2, 5);
+ grid2->setRowStretch(13, 10);
+ grid2->setMargin(m_gboxSettings->spacingHint());
+ grid2->setSpacing(m_gboxSettings->spacingHint());
+
+ setToolSettings(m_gboxSettings);
+ init();
+
+ // -------------------------------------------------------------
+
+ connect(m_channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(m_scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromOriginal(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget(const Digikam::DColor&, const TQPoint&)),
+ this, TQ_SLOT(slotColorSelectedFromTarget(const Digikam::DColor&)));
+
+ connect(m_previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotEffect()));
+
+ // -------------------------------------------------------------
+ // Correction Filter Slider controls.
+
+ connect(m_temperaturePresetCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotTemperaturePresetChanged(int)));
+
+ connect(m_temperatureInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTemperatureChanged(double)));
+
+ connect(m_darkInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_blackInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_mainExposureInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_fineExposureInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_gammaInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_saturationInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(m_greenInput, TQ_SIGNAL(valueChanged (double)),
+ this, TQ_SLOT(slotTimer()));
+
+ // -------------------------------------------------------------
+ // Bouttons slots.
+
+ connect(m_autoAdjustExposure, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAutoAdjustExposure()));
+
+ connect(m_pickTemperature, TQ_SIGNAL(released()),
+ this, TQ_SLOT(slotPickerColorButtonActived()));
+}
+
+WhiteBalanceTool::~WhiteBalanceTool()
+{
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+}
+
+void WhiteBalanceTool::slotTemperatureChanged(double temperature)
+{
+ switch((uint)temperature)
+ {
+ case 1850:
+ m_temperaturePresetCB->setCurrentItem(Candle);
+ break;
+
+ case 2680:
+ m_temperaturePresetCB->setCurrentItem(Lamp40W);
+ break;
+
+ case 2800:
+ m_temperaturePresetCB->setCurrentItem(Lamp100W);
+ break;
+
+ case 3000:
+ m_temperaturePresetCB->setCurrentItem(Lamp200W);
+ break;
+
+ case 3200:
+ m_temperaturePresetCB->setCurrentItem(Sunrise);
+ break;
+
+ case 3400:
+ m_temperaturePresetCB->setCurrentItem(StudioLamp);
+ break;
+
+ case 4100:
+ m_temperaturePresetCB->setCurrentItem(MoonLight);
+ break;
+
+ case 4750:
+ m_temperaturePresetCB->setCurrentItem(Neutral);
+ break;
+
+ case 5000:
+ m_temperaturePresetCB->setCurrentItem(DaylightD50);
+ break;
+
+ case 5500:
+ m_temperaturePresetCB->setCurrentItem(Flash);
+ break;
+
+ case 5770:
+ m_temperaturePresetCB->setCurrentItem(Sun);
+ break;
+
+ case 6420:
+ m_temperaturePresetCB->setCurrentItem(XeonLamp);
+ break;
+
+ case 6500:
+ m_temperaturePresetCB->setCurrentItem(DaylightD65);
+ break;
+
+ default:
+ m_temperaturePresetCB->setCurrentItem(None);
+ break;
+ }
+
+ slotTimer();
+}
+
+void WhiteBalanceTool::slotTemperaturePresetChanged(int tempPreset)
+{
+ switch(tempPreset)
+ {
+ case Candle:
+ m_temperatureInput->setValue(1850.0);
+ break;
+
+ case Lamp40W:
+ m_temperatureInput->setValue(2680.0);
+ break;
+
+ case Lamp100W:
+ m_temperatureInput->setValue(2800.0);
+ break;
+
+ case Lamp200W:
+ m_temperatureInput->setValue(3000.0);
+ break;
+
+ case Sunrise:
+ m_temperatureInput->setValue(3200.0);
+ break;
+
+ case StudioLamp:
+ m_temperatureInput->setValue(3400.0);
+ break;
+
+ case MoonLight:
+ m_temperatureInput->setValue(4100.0);
+ break;
+
+ case Neutral:
+ m_temperatureInput->setValue(4750.0);
+ break;
+
+ case DaylightD50:
+ m_temperatureInput->setValue(5000.0);
+ break;
+
+ case Flash:
+ m_temperatureInput->setValue(5500.0);
+ break;
+
+ case Sun:
+ m_temperatureInput->setValue(5770.0);
+ break;
+
+ case XeonLamp:
+ m_temperatureInput->setValue(6420.0);
+ break;
+
+ case DaylightD65:
+ m_temperatureInput->setValue(6500.0);
+ break;
+
+ default: // None.
+ break;
+ }
+
+ slotEffect();
+}
+
+void WhiteBalanceTool::slotPickerColorButtonActived()
+{
+ // Save previous rendering mode and toggle to original image.
+ m_currentPreviewMode = m_previewWidget->getRenderingPreviewMode();
+ m_previewWidget->setRenderingPreviewMode(ImageGuideWidget::PreviewOriginalImage);
+}
+
+void WhiteBalanceTool::slotColorSelectedFromOriginal(const DColor &color)
+{
+ if ( m_pickTemperature->isOn() )
+ {
+ DColor dc = color;
+ TQColor tc = dc.getTQColor();
+ double temperatureLevel, greenLevel;
+
+ WhiteBalance::autoWBAdjustementFromColor(tc, temperatureLevel, greenLevel);
+
+ m_temperatureInput->setValue(temperatureLevel);
+ m_greenInput->setValue(greenLevel);
+ m_pickTemperature->setOn(false);
+ }
+ else
+ return;
+
+ // restore previous rendering mode.
+ m_previewWidget->setRenderingPreviewMode(m_currentPreviewMode);
+
+ slotEffect();
+}
+
+void WhiteBalanceTool::slotColorSelectedFromTarget(const DColor& color)
+{
+ m_histogramWidget->setHistogramGuideByColor(color);
+}
+
+void WhiteBalanceTool::slotScaleChanged(int scale)
+{
+ m_histogramWidget->m_scaleType = scale;
+ m_histogramWidget->repaint(false);
+}
+
+void WhiteBalanceTool::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case LuminosityChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ break;
+
+ case RedChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ break;
+
+ case GreenChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ break;
+
+ case BlueChannel:
+ m_histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ break;
+ }
+
+ m_histogramWidget->repaint(false);
+}
+
+void WhiteBalanceTool::slotAutoAdjustExposure()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int width = iface->originalWidth();
+ int height = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ double blackLevel;
+ double exposureLevel;
+
+ WhiteBalance::autoExposureAdjustement(data, width, height, sb, blackLevel, exposureLevel);
+ delete [] data;
+
+ m_blackInput->setValue(blackLevel);
+ m_mainExposureInput->setValue(exposureLevel);
+ m_fineExposureInput->setValue(0.0);
+
+ kapp->restoreOverrideCursor();
+ slotEffect();
+}
+
+void WhiteBalanceTool::slotEffect()
+{
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getPreviewImage();
+ int w = iface->previewWidth();
+ int h = iface->previewHeight();
+ bool sb = iface->previewSixteenBit();
+
+ // Create the new empty destination image data space.
+ m_histogramWidget->stopHistogramComputation();
+
+ if (m_destinationPreviewData)
+ delete [] m_destinationPreviewData;
+
+ m_destinationPreviewData = new uchar[w*h*(sb ? 8 : 4)];
+
+ double temperature = m_temperatureInput->value();
+ double dark = m_darkInput->value();
+ double black = m_blackInput->value();
+ double mainExposure = m_mainExposureInput->value();
+ double fineExposure = m_fineExposureInput->value();
+ double gamma = m_gammaInput->value();
+ double saturation = m_saturationInput->value();
+ double green = m_greenInput->value();
+
+ WhiteBalance wbFilter(sb);
+ wbFilter.whiteBalance(data, w, h, sb,
+ black, mainExposure + fineExposure,
+ temperature, green, dark,
+ gamma, saturation);
+
+ iface->putPreviewImage(data);
+ m_previewWidget->updatePreview();
+
+ // Update histogram.
+ memcpy (m_destinationPreviewData, data, w*h*(sb ? 8 : 4));
+ m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
+ delete [] data;
+}
+
+void WhiteBalanceTool::finalRendering()
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ ImageIface* iface = m_previewWidget->imageIface();
+ uchar *data = iface->getOriginalImage();
+ int w = iface->originalWidth();
+ int h = iface->originalHeight();
+ bool sb = iface->originalSixteenBit();
+
+ double temperature = m_temperatureInput->value();
+ double dark = m_darkInput->value();
+ double black = m_blackInput->value();
+ double mainExposure = m_mainExposureInput->value();
+ double fineExposure = m_fineExposureInput->value();
+ double gamma = m_gammaInput->value();
+ double saturation = m_saturationInput->value();
+ double green = m_greenInput->value();
+
+ WhiteBalance wbFilter(sb);
+ wbFilter.whiteBalance(data, w, h, sb,
+ black, mainExposure + fineExposure,
+ temperature, green, dark,
+ gamma, saturation);
+
+ iface->putOriginalImage(i18n("White Balance"), data);
+ delete [] data;
+ kapp->restoreOverrideCursor();
+}
+
+void WhiteBalanceTool::slotResetSettings()
+{
+ m_blackInput->blockSignals(true);
+ m_darkInput->blockSignals(true);
+ m_fineExposureInput->blockSignals(true);
+ m_gammaInput->blockSignals(true);
+ m_greenInput->blockSignals(true);
+ m_mainExposureInput->blockSignals(true);
+ m_saturationInput->blockSignals(true);
+ m_temperatureInput->blockSignals(true);
+ m_temperaturePresetCB->blockSignals(true);
+
+ // Neutral color temperature settings is D65
+ m_blackInput->slotReset();
+ m_darkInput->slotReset();
+ m_fineExposureInput->slotReset();
+ m_gammaInput->slotReset();
+ m_greenInput->slotReset();
+ m_mainExposureInput->slotReset();
+ m_saturationInput->slotReset();
+ m_temperaturePresetCB->slotReset();
+ slotTemperaturePresetChanged(m_temperaturePresetCB->defaultItem());
+ m_temperatureInput->slotReset();
+
+ m_previewWidget->resetSpotPosition();
+ m_channelCB->setCurrentItem(LuminosityChannel);
+ slotChannelChanged(LuminosityChannel);
+
+ m_histogramWidget->reset();
+
+ m_blackInput->blockSignals(false);
+ m_darkInput->blockSignals(false);
+ m_fineExposureInput->blockSignals(false);
+ m_gammaInput->blockSignals(false);
+ m_greenInput->blockSignals(false);
+ m_mainExposureInput->blockSignals(false);
+ m_saturationInput->blockSignals(false);
+ m_temperatureInput->blockSignals(false);
+ m_temperaturePresetCB->blockSignals(false);
+
+ slotEffect();
+}
+
+void WhiteBalanceTool::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("whitebalance Tool");
+ m_channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ m_scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+
+ m_darkInput->setValue(config->readDoubleNumEntry("Dark",m_darkInput->defaultValue()));
+ m_blackInput->setValue(config->readDoubleNumEntry("Black", m_blackInput->defaultValue()));
+ m_mainExposureInput->setValue(config->readDoubleNumEntry("MainExposure", m_mainExposureInput->defaultValue()));
+ m_fineExposureInput->setValue(config->readDoubleNumEntry("FineExposure", m_fineExposureInput->defaultValue()));
+ m_gammaInput->setValue(config->readDoubleNumEntry("Gamma", m_gammaInput->defaultValue()));
+ m_saturationInput->setValue(config->readDoubleNumEntry("Saturation", m_saturationInput->defaultValue()));
+ m_greenInput->setValue(config->readDoubleNumEntry("Green", m_greenInput->defaultValue()));
+ m_temperatureInput->setValue(config->readDoubleNumEntry("Temperature", m_temperatureInput->defaultValue()));
+
+ slotTemperatureChanged(m_temperatureInput->value());
+ m_histogramWidget->reset();
+ slotChannelChanged(m_channelCB->currentItem());
+ slotScaleChanged(m_scaleBG->selectedId());
+}
+
+void WhiteBalanceTool::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("whitebalance Tool");
+ config->writeEntry("Histogram Channel", m_channelCB->currentItem());
+ config->writeEntry("Histogram Scale", m_scaleBG->selectedId());
+
+ config->writeEntry("Dark", m_darkInput->value());
+ config->writeEntry("Black", m_blackInput->value());
+ config->writeEntry("MainExposure", m_mainExposureInput->value());
+ config->writeEntry("FineExposure", m_fineExposureInput->value());
+ config->writeEntry("Gamma", m_gammaInput->value());
+ config->writeEntry("Saturation", m_saturationInput->value());
+ config->writeEntry("Green", m_greenInput->value());
+ config->writeEntry("Temperature", m_temperatureInput->value());
+ m_previewWidget->writeSettings();
+ config->sync();
+}
+
+void WhiteBalanceTool::slotLoadSettings()
+{
+ KURL loadWhiteBalanceFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("White Color Balance Settings File to Load")) );
+ if( loadWhiteBalanceFile.isEmpty() )
+ return;
+
+ TQFile file(loadWhiteBalanceFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ TQTextStream stream( &file );
+
+ if ( stream.readLine() != "# White Color Balance Configuration File V2" )
+ {
+ KMessageBox::error(kapp->activeWindow(),
+ i18n("\"%1\" is not a White Color Balance settings text file.")
+ .arg(loadWhiteBalanceFile.fileName()));
+ file.close();
+ return;
+ }
+
+ blockSignals(true);
+ m_temperatureInput->setValue( stream.readLine().toDouble() );
+ m_darkInput->setValue( stream.readLine().toDouble() );
+ m_blackInput->setValue( stream.readLine().toDouble() );
+ m_mainExposureInput->setValue( stream.readLine().toDouble() );
+ m_fineExposureInput->setValue( stream.readLine().toDouble() );
+ m_gammaInput->setValue( stream.readLine().toDouble() );
+ m_saturationInput->setValue( stream.readLine().toDouble() );
+ m_greenInput->setValue( stream.readLine().toDouble() );
+ m_histogramWidget->reset();
+ blockSignals(false);
+ slotEffect();
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot load settings from the White Color Balance text file."));
+
+ file.close();
+}
+
+void WhiteBalanceTool::slotSaveAsSettings()
+{
+ KURL saveWhiteBalanceFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), kapp->activeWindow(),
+ TQString( i18n("White Color Balance Settings File to Save")) );
+ if( saveWhiteBalanceFile.isEmpty() )
+ return;
+
+ TQFile file(saveWhiteBalanceFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ {
+ TQTextStream stream( &file );
+ stream << "# White Color Balance Configuration File V2\n";
+ stream << m_temperatureInput->value() << "\n";
+ stream << m_darkInput->value() << "\n";
+ stream << m_blackInput->value() << "\n";
+ stream << m_mainExposureInput->value() << "\n";
+ stream << m_fineExposureInput->value() << "\n";
+ stream << m_gammaInput->value() << "\n";
+ stream << m_saturationInput->value() << "\n";
+ stream << m_greenInput->value() << "\n";
+ }
+ else
+ KMessageBox::error(kapp->activeWindow(), i18n("Cannot save settings to the White Color Balance text file."));
+
+ file.close();
+}
+
+} // NameSpace DigikamWhiteBalanceImagesPlugin
diff --git a/src/imageplugins/whitebalance/whitebalancetool.h b/src/imageplugins/whitebalance/whitebalancetool.h
new file mode 100644
index 00000000..2df2877e
--- /dev/null
+++ b/src/imageplugins/whitebalance/whitebalancetool.h
@@ -0,0 +1,174 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-11
+ * Description : a digiKam image editor plugin to correct
+ * image white balance
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Guillaume Castagnino <casta at xwing dot info>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef WHITEBALANCETOOL_H
+#define WHITEBALANCETOOL_H
+
+// TQt includes.
+
+#include <tqcolor.h>
+
+// Digikam includes.
+
+#include "editortool.h"
+
+class TQComboBox;
+class TQPushButton;
+class TQLabel;
+class TQPushButton;
+class TQHButtonGroup;
+
+class KActiveLabel;
+
+namespace KDcrawIface
+{
+class RDoubleNumInput;
+class RComboBox;
+}
+
+namespace Digikam
+{
+class HistogramWidget;
+class ColorGradientWidget;
+class ImageWidget;
+class DColor;
+class EditorToolSettings;
+}
+
+namespace DigikamWhiteBalanceImagesPlugin
+{
+
+class WhiteBalanceTool : public Digikam::EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ WhiteBalanceTool(TQObject* parent);
+ ~WhiteBalanceTool();
+
+private:
+
+ void readSettings();
+ void writeSettings();
+ void finalRendering();
+
+private slots:
+
+ void slotSaveAsSettings();
+ void slotLoadSettings();
+ void slotEffect();
+ void slotResetSettings();
+ void slotColorSelectedFromOriginal(const Digikam::DColor &color);
+ void slotColorSelectedFromTarget(const Digikam::DColor &color);
+ void slotScaleChanged(int scale);
+ void slotChannelChanged(int channel);
+ void slotTemperatureChanged(double temperature);
+ void slotTemperaturePresetChanged(int tempPreset);
+ void slotAutoAdjustExposure(void);
+ void slotPickerColorButtonActived();
+
+private:
+
+ enum HistogramScale
+ {
+ Linear=0,
+ Logarithmic
+ };
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel
+ };
+
+ enum TemperaturePreset
+ {
+ Candle=0,
+ Lamp40W,
+ Lamp100W,
+ Lamp200W,
+ Sunrise,
+ StudioLamp,
+ MoonLight,
+ Neutral,
+ DaylightD50,
+ Flash,
+ Sun,
+ XeonLamp,
+ DaylightD65,
+ None
+ };
+
+ uchar *m_destinationPreviewData;
+
+ int m_currentPreviewMode;
+
+ TQComboBox *m_channelCB;
+
+ TQPushButton *m_pickTemperature;
+ TQPushButton *m_autoAdjustExposure;
+
+ KDcrawIface::RComboBox *m_temperaturePresetCB;
+
+ TQHButtonGroup *m_scaleBG;
+
+ TQLabel *m_adjTemperatureLabel;
+ TQLabel *m_temperaturePresetLabel;
+ TQLabel *m_darkLabel;
+ TQLabel *m_blackLabel;
+ TQLabel *m_mainExposureLabel;
+ TQLabel *m_fineExposureLabel;
+ TQLabel *m_gammaLabel;
+ TQLabel *m_saturationLabel;
+ TQLabel *m_greenLabel;
+
+ KActiveLabel *m_exposureLabel;
+ KActiveLabel *m_temperatureLabel;
+
+ KDcrawIface::RDoubleNumInput *m_temperatureInput;
+ KDcrawIface::RDoubleNumInput *m_darkInput;
+ KDcrawIface::RDoubleNumInput *m_blackInput;
+ KDcrawIface::RDoubleNumInput *m_mainExposureInput;
+ KDcrawIface::RDoubleNumInput *m_fineExposureInput;
+ KDcrawIface::RDoubleNumInput *m_gammaInput;
+ KDcrawIface::RDoubleNumInput *m_saturationInput;
+ KDcrawIface::RDoubleNumInput *m_greenInput;
+
+ Digikam::HistogramWidget *m_histogramWidget;
+
+ Digikam::ColorGradientWidget *m_hGradient;
+
+ Digikam::ImageWidget *m_previewWidget;
+
+ Digikam::EditorToolSettings *m_gboxSettings;
+};
+
+} // NameSpace DigikamWhiteBalanceImagesPlugin
+
+#endif /* WHITEBALANCETOOL_H */
diff --git a/src/libs/Makefile.am b/src/libs/Makefile.am
new file mode 100644
index 00000000..a9af1e06
--- /dev/null
+++ b/src/libs/Makefile.am
@@ -0,0 +1,9 @@
+if with_included_sqlite3
+ SQLITE3_SUBDIR = sqlite3
+endif
+
+COMPILE_FIRST = sqlite2 $(SQLITE3_SUBDIR)
+
+SUBDIRS = sqlite2 $(SQLITE3_SUBDIR) lprof histogram levels curves whitebalance dmetadata \
+ dimg threadimageio themeengine widgets greycstoration \
+ thumbbar jpegutils imageproperties dialogs
diff --git a/src/libs/curves/Makefile.am b/src/libs/curves/Makefile.am
new file mode 100644
index 00000000..f183fcc7
--- /dev/null
+++ b/src/libs/curves/Makefile.am
@@ -0,0 +1,16 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libcurves.la
+
+libcurves_la_SOURCES = imagecurves.cpp
+
+libcurves_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/digikam \
+ $(all_includes)
+
+digikaminclude_HEADERS = imagecurves.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/curves/imagecurves.cpp b/src/libs/curves/imagecurves.cpp
new file mode 100644
index 00000000..c5e067eb
--- /dev/null
+++ b/src/libs/curves/imagecurves.cpp
@@ -0,0 +1,768 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image curves manipulation methods.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Some code parts are inspired from gimp 2.0
+ * app/base/curves.c, gimplut.c, and app/base/gimpcurvetool.c
+ * source files.
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+#include <cstring>
+#include <cstdlib>
+#include <cerrno>
+
+// TQt includes.
+
+#include <tqfile.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagecurves.h"
+
+namespace Digikam
+{
+
+class ImageCurvesPriv
+{
+
+public:
+
+ struct _Curves
+ {
+ ImageCurves::CurveType curve_type[5]; // Curve types by channels (Smooth or Free).
+ int points[5][17][2]; // Curve main points in Smooth mode ([channel][point id][x,y]).
+ unsigned short curve[5][65536]; // Curve values by channels.
+ };
+
+ struct _Lut
+ {
+ unsigned short **luts;
+ int nchannels;
+ };
+
+public:
+
+ ImageCurvesPriv()
+ {
+ curves = 0;
+ lut = 0;
+ dirty = false;
+ }
+
+ // Curves data.
+ struct _Curves *curves;
+
+ // Lut data.
+ struct _Lut *lut;
+
+ int segmentMax;
+
+ bool dirty;
+};
+
+ImageCurves::CRMatrix CR_basis =
+{
+ { -0.5, 1.5, -1.5, 0.5 },
+ { 1.0, -2.5, 2.0, -0.5 },
+ { -0.5, 0.0, 0.5, 0.0 },
+ { 0.0, 1.0, 0.0, 0.0 },
+};
+
+ImageCurves::ImageCurves(bool sixteenBit)
+{
+ d = new ImageCurvesPriv;
+ d->lut = new ImageCurvesPriv::_Lut;
+ d->curves = new ImageCurvesPriv::_Curves;
+ d->segmentMax = sixteenBit ? 65535 : 255;
+
+ curvesReset();
+}
+
+ImageCurves::~ImageCurves()
+{
+ if (d->lut)
+ {
+ if (d->lut->luts)
+ {
+ for (int i = 0 ; i < d->lut->nchannels ; i++)
+ delete [] d->lut->luts[i];
+
+ delete [] d->lut->luts;
+ }
+
+ delete d->lut;
+ }
+
+ if (d->curves)
+ delete d->curves;
+
+ delete d;
+}
+
+bool ImageCurves::isDirty()
+{
+ return d->dirty;
+}
+
+bool ImageCurves::isSixteenBits()
+{
+ return (d->segmentMax == 65535);
+}
+
+void ImageCurves::curvesReset()
+{
+ memset(d->curves, 0, sizeof(struct ImageCurvesPriv::_Curves));
+ d->lut->luts = NULL;
+ d->lut->nchannels = 0;
+ d->dirty = false;
+
+ for (int channel = 0 ; channel < 5 ; channel++)
+ {
+ setCurveType(channel, CURVE_SMOOTH);
+ curvesChannelReset(channel);
+ }
+}
+
+void ImageCurves::curvesChannelReset(int channel)
+{
+ int j;
+
+ if (!d->curves) return;
+
+ // Contruct a linear curve.
+
+ for (j = 0 ; j <= d->segmentMax ; j++)
+ d->curves->curve[channel][j] = j;
+
+ // Init coordinates points to null.
+
+ for (j = 0 ; j < 17 ; j++)
+ {
+ d->curves->points[channel][j][0] = -1;
+ d->curves->points[channel][j][1] = -1;
+ }
+
+ // First and last points init.
+
+ d->curves->points[channel][0][0] = 0;
+ d->curves->points[channel][0][1] = 0;
+ d->curves->points[channel][16][0] = d->segmentMax;
+ d->curves->points[channel][16][1] = d->segmentMax;
+}
+
+void ImageCurves::curvesCalculateCurve(int channel)
+{
+ int i;
+ int points[17];
+ int num_pts;
+ int p1, p2, p3, p4;
+
+ if (!d->curves) return;
+
+ switch (d->curves->curve_type[channel])
+ {
+ case CURVE_FREE:
+ break;
+
+ case CURVE_SMOOTH:
+ {
+ // Cycle through the curves
+
+ num_pts = 0;
+
+ for (i = 0 ; i < 17 ; i++)
+ if (d->curves->points[channel][i][0] != -1)
+ points[num_pts++] = i;
+
+ // Initialize boundary curve points
+
+ if (num_pts != 0)
+ {
+ for (i = 0 ; i < d->curves->points[channel][points[0]][0] ; i++)
+ {
+ d->curves->curve[channel][i] = d->curves->points[channel][points[0]][1];
+ }
+
+ for (i = d->curves->points[channel][points[num_pts - 1]][0] ; i <= d->segmentMax ; i++)
+ {
+ d->curves->curve[channel][i] = d->curves->points[channel][points[num_pts - 1]][1];
+ }
+ }
+
+ for (i = 0 ; i < num_pts - 1 ; i++)
+ {
+ p1 = (i == 0) ? points[i] : points[(i - 1)];
+ p2 = points[i];
+ p3 = points[(i + 1)];
+ p4 = (i == (num_pts - 2)) ? points[(num_pts - 1)] : points[(i + 2)];
+
+ curvesPlotCurve(channel, p1, p2, p3, p4);
+ }
+
+ // Ensure that the control points are used exactly
+
+ for (i = 0 ; i < num_pts ; i++)
+ {
+ int x, y;
+
+ x = d->curves->points[channel][points[i]][0];
+ y = d->curves->points[channel][points[i]][1];
+ d->curves->curve[channel][x] = y;
+ }
+
+ break;
+ }
+ }
+}
+
+float ImageCurves::curvesLutFunc(int n_channels, int channel, float value)
+{
+ float f;
+ int index;
+ double inten;
+ int j;
+
+ if (!d->curves) return 0.0;
+
+ if (n_channels == 1)
+ j = 0;
+ else
+ j = channel + 1;
+
+ inten = value;
+
+ // For color images this runs through the loop with j = channel +1
+ // the first time and j = 0 the second time.
+
+ // For bw images this runs through the loop with j = 0 the first and
+ // only time.
+
+ for ( ; j >= 0 ; j -= (channel + 1))
+ {
+ // Don't apply the overall curve to the alpha channel.
+
+ if (j == 0 && (n_channels == 2 || n_channels == 4) && channel == n_channels -1)
+ return inten;
+
+ if (inten < 0.0)
+ inten = d->curves->curve[j][0]/(float)d->segmentMax;
+ else if (inten >= 1.0)
+ inten = d->curves->curve[j][d->segmentMax]/(float)(d->segmentMax);
+ else // interpolate the curve.
+ {
+ index = (int)floor(inten * (float)(d->segmentMax));
+ f = inten * (float)(d->segmentMax) - index;
+ inten = ((1.0 - f) * d->curves->curve[j][index ] +
+ ( f) * d->curves->curve[j][index + 1] ) / (float)(d->segmentMax);
+ }
+ }
+
+ return inten;
+}
+
+void ImageCurves::curvesPlotCurve(int channel, int p1, int p2, int p3, int p4)
+{
+ CRMatrix geometry;
+ CRMatrix tmp1, tmp2;
+ CRMatrix deltas;
+ double x, dx, dx2, dx3;
+ double y, dy, dy2, dy3;
+ double d1, d2, d3;
+ int lastx, lasty;
+ int newx, newy;
+ int i;
+ int loopdiv = d->segmentMax * 3;
+
+ if (!d->curves) return;
+
+ // Construct the geometry matrix from the segment.
+
+ for (i = 0 ; i < 4 ; i++)
+ {
+ geometry[i][2] = 0;
+ geometry[i][3] = 0;
+ }
+
+ for (i = 0 ; i < 2 ; i++)
+ {
+ geometry[0][i] = d->curves->points[channel][p1][i];
+ geometry[1][i] = d->curves->points[channel][p2][i];
+ geometry[2][i] = d->curves->points[channel][p3][i];
+ geometry[3][i] = d->curves->points[channel][p4][i];
+ }
+
+ // Subdivide the curve 1000 times.
+ // n can be adjusted to give a finer or coarser curve.
+
+ d1 = 1.0 / loopdiv;
+ d2 = d1 * d1;
+ d3 = d1 * d1 * d1;
+
+ // Construct a temporary matrix for determining the forward differencing deltas.
+
+ tmp2[0][0] = 0; tmp2[0][1] = 0; tmp2[0][2] = 0; tmp2[0][3] = 1;
+ tmp2[1][0] = d3; tmp2[1][1] = d2; tmp2[1][2] = d1; tmp2[1][3] = 0;
+ tmp2[2][0] = 6*d3; tmp2[2][1] = 2*d2; tmp2[2][2] = 0; tmp2[2][3] = 0;
+ tmp2[3][0] = 6*d3; tmp2[3][1] = 0; tmp2[3][2] = 0; tmp2[3][3] = 0;
+
+ // Compose the basis and geometry matrices.
+
+ curvesCRCompose(CR_basis, geometry, tmp1);
+
+ // Compose the above results to get the deltas matrix.
+
+ curvesCRCompose(tmp2, tmp1, deltas);
+
+ // Extract the x deltas.
+
+ x = deltas[0][0];
+ dx = deltas[1][0];
+ dx2 = deltas[2][0];
+ dx3 = deltas[3][0];
+
+ // Extract the y deltas.
+
+ y = deltas[0][1];
+ dy = deltas[1][1];
+ dy2 = deltas[2][1];
+ dy3 = deltas[3][1];
+
+ lastx = (int)CLAMP (x, 0, d->segmentMax);
+ lasty = (int)CLAMP (y, 0, d->segmentMax);
+
+ d->curves->curve[channel][lastx] = lasty;
+
+ // Loop over the curve.
+
+ for (i = 0 ; i < loopdiv ; i++)
+ {
+ // Increment the x values.
+
+ x += dx;
+ dx += dx2;
+ dx2 += dx3;
+
+ // Increment the y values.
+
+ y += dy;
+ dy += dy2;
+ dy2 += dy3;
+
+ newx = CLAMP(ROUND (x), 0, d->segmentMax);
+ newy = CLAMP(ROUND (y), 0, d->segmentMax);
+
+ // If this point is different than the last one...then draw it.
+
+ if ((lastx != newx) || (lasty != newy))
+ d->curves->curve[channel][newx] = newy;
+
+ lastx = newx;
+ lasty = newy;
+ }
+}
+
+void ImageCurves::curvesCRCompose(CRMatrix a, CRMatrix b, CRMatrix ab)
+{
+ int i, j;
+
+ for (i = 0 ; i < 4 ; i++)
+ {
+ for (j = 0 ; j < 4 ; j++)
+ {
+ ab[i][j] = (a[i][0] * b[0][j] +
+ a[i][1] * b[1][j] +
+ a[i][2] * b[2][j] +
+ a[i][3] * b[3][j]);
+ }
+ }
+}
+
+void ImageCurves::curvesLutSetup(int nchannels)
+{
+ int i;
+ uint v;
+ double val;
+
+ if (d->lut->luts)
+ {
+ for (i = 0 ; i < d->lut->nchannels ; i++)
+ delete [] d->lut->luts[i];
+
+ delete [] d->lut->luts;
+ }
+
+ d->lut->nchannels = nchannels;
+ d->lut->luts = new unsigned short*[d->lut->nchannels];
+
+ for (i = 0 ; i < d->lut->nchannels ; i++)
+ {
+ d->lut->luts[i] = new unsigned short[d->segmentMax+1];
+
+ for (v = 0 ; v <= (uint)d->segmentMax ; v++)
+ {
+ // To add gamma correction use func(v ^ g) ^ 1/g instead.
+
+ val = (float)(d->segmentMax) * curvesLutFunc( d->lut->nchannels, i, v / (float)(d->segmentMax)) + 0.5;
+
+ d->lut->luts[i][v] = (unsigned short)CLAMP (val, 0, d->segmentMax);
+ }
+ }
+}
+
+void ImageCurves::curvesLutProcess(uchar *srcPR, uchar *destPR, int w, int h)
+{
+ unsigned short *lut0 = NULL, *lut1 = NULL, *lut2 = NULL, *lut3 = NULL;
+
+ int i;
+
+ if (d->lut->nchannels > 0)
+ lut0 = d->lut->luts[0];
+ if (d->lut->nchannels > 1)
+ lut1 = d->lut->luts[1];
+ if (d->lut->nchannels > 2)
+ lut2 = d->lut->luts[2];
+ if (d->lut->nchannels > 3)
+ lut3 = d->lut->luts[3];
+
+ if (d->segmentMax == 255) // 8 bits image.
+ {
+ uchar red, green, blue, alpha;
+ uchar *ptr = srcPR;
+ uchar *dst = destPR;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if ( d->lut->nchannels > 0 )
+ red = lut0[red];
+
+ if ( d->lut->nchannels > 1 )
+ green = lut1[green];
+
+ if ( d->lut->nchannels > 2 )
+ blue = lut2[blue];
+
+ if ( d->lut->nchannels > 3 )
+ alpha = lut3[alpha];
+
+ dst[0] = blue;
+ dst[1] = green;
+ dst[2] = red;
+ dst[3] = alpha;
+
+ ptr += 4;
+ dst += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue, alpha;
+ unsigned short *ptr = (unsigned short *)srcPR;
+ unsigned short *dst = (unsigned short *)destPR;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if ( d->lut->nchannels > 0 )
+ red = lut0[red];
+
+ if ( d->lut->nchannels > 1 )
+ green = lut1[green];
+
+ if ( d->lut->nchannels > 2 )
+ blue = lut2[blue];
+
+ if ( d->lut->nchannels > 3 )
+ alpha = lut3[alpha];
+
+ dst[0] = blue;
+ dst[1] = green;
+ dst[2] = red;
+ dst[3] = alpha;
+
+ ptr += 4;
+ dst += 4;
+ }
+ }
+}
+
+int ImageCurves::getCurveValue(int channel, int bin)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ bin>=0 && bin<=d->segmentMax )
+ return(d->curves->curve[channel][bin]);
+
+ return 0;
+}
+
+TQPoint ImageCurves::getCurvePoint(int channel, int point)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ point>=0 && point<=17 )
+ return(TQPoint(d->curves->points[channel][point][0],
+ d->curves->points[channel][point][1]) );
+
+ return TQPoint(-1, -1);
+}
+
+TQPointArray ImageCurves::getCurvePoints(int channel)
+{
+ TQPointArray array(18);
+
+ if ( d->curves &&
+ channel>=0 && channel<5)
+ {
+ for (int j = 0 ; j <= 17 ; j++)
+ array.setPoint(j, getCurvePoint(channel, j));
+ }
+
+ return array;
+}
+
+int ImageCurves::getCurvePointX(int channel, int point)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ point>=0 && point<=17 )
+ return(d->curves->points[channel][point][0]);
+
+ return(-1);
+}
+
+int ImageCurves::getCurvePointY(int channel, int point)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ point>=0 && point<=17 )
+ return(d->curves->points[channel][point][1]);
+
+ return (-1);
+}
+
+int ImageCurves::getCurveType(int channel)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 )
+ return ( d->curves->curve_type[channel] );
+
+ return (-1);
+}
+
+void ImageCurves::setCurveValue(int channel, int bin, int val)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ bin>=0 && bin<=d->segmentMax )
+ {
+ d->dirty = true;
+ d->curves->curve[channel][bin] = val;
+ }
+}
+
+void ImageCurves::setCurvePoint(int channel, int point, const TQPoint& val)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ point>=0 && point<=17 &&
+ val.x()>=-1 && val.x()<=d->segmentMax && // x can be egal to -1
+ val.y()>=0 && val.y()<=d->segmentMax) // if the current point is disable !!!
+ {
+ d->dirty = true;
+ d->curves->points[channel][point][0] = val.x();
+ d->curves->points[channel][point][1] = val.y();
+ }
+}
+
+void ImageCurves::setCurvePoints(int channel, const TQPointArray& vals)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ vals.size() == 18 )
+ {
+ d->dirty = true;
+ for (int j = 0 ; j <= 17 ; j++)
+ {
+ setCurvePoint(channel, j, vals.point(j));
+ }
+ }
+}
+
+void ImageCurves::setCurvePointX(int channel, int point, int x)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ point>=0 && point<=17 &&
+ x>=-1 && x<=d->segmentMax) // x can be egal to -1 if the current point is disable !!!
+ {
+ d->dirty = true;
+ d->curves->points[channel][point][0] = x;
+ }
+}
+
+void ImageCurves::setCurvePointY(int channel, int point, int y)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ point>=0 && point<=17 &&
+ y>=0 && y<=d->segmentMax)
+ {
+ d->dirty = true;
+ d->curves->points[channel][point][1] = y;
+ }
+}
+
+void ImageCurves::setCurveType(int channel, CurveType type)
+{
+ if ( d->curves &&
+ channel>=0 && channel<5 &&
+ type>=CURVE_SMOOTH && type<=CURVE_FREE )
+ d->curves->curve_type[channel] = type;
+}
+
+bool ImageCurves::loadCurvesFromGimpCurvesFile(const KURL& fileUrl)
+{
+ // TODO : support KURL !
+
+ FILE *file;
+ int i, j;
+ int fields;
+ char buf[50];
+ int index[5][17];
+ int value[5][17];
+
+ file = fopen(TQFile::encodeName(fileUrl.path()), "r");
+ if (!file)
+ return false;
+
+ if (! fgets (buf, sizeof (buf), file))
+ {
+ fclose(file);
+ return false;
+ }
+
+ if (strcmp (buf, "# GIMP Curves File\n") != 0)
+ return false;
+
+ for (i = 0 ; i < 5 ; i++)
+ {
+ for (j = 0 ; j < 17 ; j++)
+ {
+ fields = fscanf (file, "%d %d ", &index[i][j], &value[i][j]);
+ if (fields != 2)
+ {
+ DWarning() << "Invalid Gimp curves file!" << endl;
+ fclose(file);
+ return false;
+ }
+ }
+ }
+
+ curvesReset();
+
+ for (i = 0 ; i < 5 ; i++)
+ {
+ d->curves->curve_type[i] = CURVE_SMOOTH;
+
+ for (j = 0 ; j < 17 ; j++)
+ {
+ d->curves->points[i][j][0] = ((d->segmentMax == 65535) && (index[i][j] !=-1) ?
+ index[i][j]*255 : index[i][j]);
+ d->curves->points[i][j][1] = ((d->segmentMax == 65535) && (value[i][j] !=-1) ?
+ value[i][j]*255 : value[i][j]);
+ }
+ }
+
+ for (i = 0 ; i < 5 ; i++)
+ curvesCalculateCurve(i);
+
+ fclose(file);
+ return true;
+}
+
+bool ImageCurves::saveCurvesToGimpCurvesFile(const KURL& fileUrl)
+{
+ // TODO : support KURL !
+
+ FILE *file;
+ int i, j;
+ int index;
+
+ file = fopen(TQFile::encodeName(fileUrl.path()), "w");
+
+ if (!file)
+ return false;
+
+ for (i = 0 ; i < 5 ; i++)
+ {
+ if (d->curves->curve_type[i] == CURVE_FREE)
+ {
+ // Pick representative points from the curve and make them control points.
+
+ for (j = 0 ; j <= 8 ; j++)
+ {
+ index = CLAMP(j * 32, 0, d->segmentMax);
+ d->curves->points[i][j * 2][0] = index;
+ d->curves->points[i][j * 2][1] = d->curves->curve[i][index];
+ }
+ }
+ }
+
+ fprintf (file, "# GIMP Curves File\n");
+
+ for (i = 0 ; i < 5 ; i++)
+ {
+ for (j = 0 ; j < 17 ; j++)
+ {
+ fprintf (file, "%d %d ",
+ ((d->segmentMax == 65535) && (d->curves->points[i][j][0]!=-1) ?
+ d->curves->points[i][j][0]/255 : d->curves->points[i][j][0]),
+ ((d->segmentMax == 65535) && (d->curves->points[i][j][1]!=-1) ?
+ d->curves->points[i][j][1]/255 : d->curves->points[i][j][1]));
+
+ fprintf (file, "\n");
+ }
+ }
+
+ fflush(file);
+ fclose(file);
+
+ return true;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/curves/imagecurves.h b/src/libs/curves/imagecurves.h
new file mode 100644
index 00000000..69d33c08
--- /dev/null
+++ b/src/libs/curves/imagecurves.h
@@ -0,0 +1,111 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : image curves manipulation methods.
+ *
+ * Copyright (c) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGECURVES_H
+#define IMAGECURVES_H
+
+#define ROUND(x) ((int) ((x) + 0.5))
+
+// TQt includes.
+
+#include <tqpoint.h>
+#include <tqpointarray.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImageCurvesPriv;
+
+class DIGIKAM_EXPORT ImageCurves
+{
+
+public:
+
+ enum CurveType
+ {
+ CURVE_SMOOTH = 0, // Smooth curve type
+ CURVE_FREE // Freehand curve type.
+ };
+
+ typedef double CRMatrix[4][4];
+
+public:
+
+ ImageCurves(bool sixteenBit);
+ ~ImageCurves();
+
+ // Methods for to manipulate the curves data.
+
+ bool isDirty();
+ bool isSixteenBits();
+ void curvesReset();
+ void curvesChannelReset(int channel);
+ void curvesCalculateCurve(int channel);
+ float curvesLutFunc(int n_channels, int channel, float value);
+ void curvesLutSetup(int nchannels);
+ void curvesLutProcess(uchar *srcPR, uchar *destPR, int w, int h);
+
+ // Methods for to set manually the curves values.
+
+ void setCurveValue(int channel, int bin, int val);
+ void setCurvePointX(int channel, int point, int x);
+ void setCurvePointY(int channel, int point, int y);
+ void setCurveType(int channel, CurveType type);
+
+ void setCurvePoint(int channel, int point, const TQPoint& val);
+ void setCurvePoints(int channel, const TQPointArray& vals);
+
+ int getCurveValue(int channel, int bin);
+ int getCurvePointX(int channel, int point);
+ int getCurvePointY(int channel, int point);
+ int getCurveType(int channel);
+
+ TQPoint getCurvePoint(int channel, int point);
+ TQPointArray getCurvePoints(int channel);
+
+ // Methods for to save/load the curves values to/from a Gimp curves text file.
+
+ bool saveCurvesToGimpCurvesFile(const KURL& fileUrl);
+ bool loadCurvesFromGimpCurvesFile(const KURL& fileUrl);
+
+private:
+
+ void curvesPlotCurve(int channel, int p1, int p2, int p3, int p4);
+ void curvesCRCompose(CRMatrix a, CRMatrix b, CRMatrix ab);
+
+private:
+
+ ImageCurvesPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGECURVES_H */
diff --git a/src/libs/dialogs/Makefile.am b/src/libs/dialogs/Makefile.am
new file mode 100644
index 00000000..19d50423
--- /dev/null
+++ b/src/libs/dialogs/Makefile.am
@@ -0,0 +1,33 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdialog.la libdialogshowfoto.la
+
+# Dialogs collection used by Showfoto.
+
+libdialogshowfoto_la_SOURCES = iccprofileinfodlg.cpp imagedialog.cpp rawcameradlg.cpp
+
+libdialogshowfoto_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+# Dialogs collection used by digiKam.
+
+libdialog_la_SOURCES = deletedialogbase.ui imagedialog.cpp rawcameradlg.cpp \
+ iccprofileinfodlg.cpp deletedialog.cpp dprogressdlg.cpp
+
+libdialog_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/thumbbar \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/metadata \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ $(LIBKDCRAW_CFLAGS) \
+ $(LIBKEXIV2_CFLAGS) \
+ $(all_includes)
+
+digikaminclude_HEADERS = iccprofileinfodlg.h dprogressdlg.h imagedialog.h rawcameradlg.h
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/dialogs/ctrlpaneldlg.cpp b/src/libs/dialogs/ctrlpaneldlg.cpp
new file mode 100644
index 00000000..450d380f
--- /dev/null
+++ b/src/libs/dialogs/ctrlpaneldlg.cpp
@@ -0,0 +1,445 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-07
+ * Description : A threaded filter control panel dialog for
+ * image editor plugins using DImg
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimgthreadedfilter.h"
+#include "dimginterface.h"
+#include "ctrlpaneldlg.h"
+#include "ctrlpaneldlg.moc"
+
+namespace Digikam
+{
+
+class CtrlPanelDlgPriv
+{
+public:
+
+ enum RunningMode
+ {
+ NoneRendering=0,
+ PreviewRendering,
+ FinalRendering
+ };
+
+ CtrlPanelDlgPriv()
+ {
+ parent = 0;
+ timer = 0;
+ aboutData = 0;
+ progressBar = true;
+ tryAction = false;
+ currentRenderingMode = NoneRendering;
+ }
+
+ bool tryAction;
+ bool progressBar;
+
+ int currentRenderingMode;
+
+ TQWidget *parent;
+
+ TQTimer *timer;
+
+ TQString name;
+
+ TDEAboutData *aboutData;
+};
+
+CtrlPanelDlg::CtrlPanelDlg(TQWidget* parent, TQString title, TQString name,
+ bool loadFileSettings, bool tryAction, bool progressBar,
+ int separateViewMode, TQFrame* bannerFrame)
+ : KDialogBase(Plain, 0,
+ Help|Default|User1|User2|User3|Try|Ok|Cancel, Ok,
+ parent, 0, true, true,
+ i18n("&Abort"),
+ i18n("&Save As..."),
+ i18n("&Load..."))
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ setCaption(DImgInterface::defaultInterface()->getImageFileName() + TQString(" - ") + title);
+
+ d = new CtrlPanelDlgPriv;
+ d->parent = parent;
+ d->name = name;
+ d->tryAction = tryAction;
+ d->progressBar = progressBar;
+ m_threadedFilter = 0;
+ TQString whatsThis;
+
+ setButtonWhatsThis ( Default, i18n("<p>Reset all filter parameters to their default values.") );
+ setButtonWhatsThis ( User1, i18n("<p>Abort the current image rendering.") );
+ setButtonWhatsThis ( User3, i18n("<p>Load all filter parameters from settings text file.") );
+ setButtonWhatsThis ( User2, i18n("<p>Save all filter parameters to settings text file.") );
+ showButton(User2, loadFileSettings);
+ showButton(User3, loadFileSettings);
+ showButton(Try, tryAction);
+
+ // disable Abort button on startup
+ enableButton(User1, false);
+
+ resize(configDialogSize(name + TQString(" Tool Dialog")));
+ TQVBoxLayout *topLayout = new TQVBoxLayout( plainPage(), 0, spacingHint());
+
+ // -------------------------------------------------------------
+
+ if (bannerFrame)
+ {
+ bannerFrame->reparent( plainPage(), TQPoint(0, 0) );
+ topLayout->addWidget(bannerFrame);
+ }
+
+ // -------------------------------------------------------------
+
+ m_imagePreviewWidget = new ImagePannelWidget(470, 350, name + TQString(" Tool Dialog"),
+ plainPage(), separateViewMode);
+ topLayout->addWidget(m_imagePreviewWidget);
+
+ // -------------------------------------------------------------
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotInit()));
+ kapp->restoreOverrideCursor();
+}
+
+CtrlPanelDlg::~CtrlPanelDlg()
+{
+ if (d->aboutData)
+ delete d->aboutData;
+
+ if (d->timer)
+ delete d->timer;
+
+ if (m_threadedFilter)
+ delete m_threadedFilter;
+
+ delete d;
+}
+
+void CtrlPanelDlg::slotInit()
+{
+ // Reset values to defaults.
+ TQTimer::singleShot(0, this, TQ_SLOT(readUserSettings()));
+
+ if (!d->tryAction)
+ {
+ connect(m_imagePreviewWidget, TQ_SIGNAL(signalOriginalClipFocusChanged()),
+ this, TQ_SLOT(slotFocusChanged()));
+ }
+ else
+ {
+ connect(m_imagePreviewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotFocusChanged()));
+ }
+}
+
+void CtrlPanelDlg::setAboutData(TDEAboutData *about)
+{
+ d->aboutData = about;
+ TQPushButton *helpButton = actionButton( Help );
+ KHelpMenu* helpMenu = new KHelpMenu(this, d->aboutData, false);
+ helpMenu->menu()->removeItemAt(0);
+ helpMenu->menu()->insertItem(i18n("digiKam Handbook"), this, TQ_SLOT(slotHelp()), 0, -1, 0);
+ helpButton->setPopup( helpMenu->menu() );
+}
+
+void CtrlPanelDlg::abortPreview()
+{
+ d->currentRenderingMode = CtrlPanelDlgPriv::NoneRendering;
+ m_imagePreviewWidget->setProgress(0);
+ m_imagePreviewWidget->setPreviewImageWaitCursor(false);
+ m_imagePreviewWidget->setEnable(true);
+ m_imagePreviewWidget->setProgressVisible(false);
+ enableButton(Ok, true);
+ enableButton(User1, false);
+ enableButton(User2, true);
+ enableButton(User3, true);
+ enableButton(Try, true);
+ enableButton(Default, true);
+ renderingFinished();
+}
+
+void CtrlPanelDlg::slotTry()
+{
+ slotEffect();
+}
+
+void CtrlPanelDlg::slotUser1()
+{
+ if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering)
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+}
+
+void CtrlPanelDlg::slotDefault()
+{
+ resetValues();
+ slotEffect();
+}
+
+void CtrlPanelDlg::slotCancel()
+{
+ if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering)
+ {
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+
+ kapp->restoreOverrideCursor();
+ }
+
+ saveDialogSize(d->name + TQString(" Tool Dialog"));
+ done(Cancel);
+}
+
+void CtrlPanelDlg::closeEvent(TQCloseEvent *e)
+{
+ if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering)
+ {
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+
+ kapp->restoreOverrideCursor();
+ }
+
+ saveDialogSize(d->name + TQString(" Tool Dialog"));
+ e->accept();
+}
+
+void CtrlPanelDlg::slotFocusChanged(void)
+{
+ if (d->currentRenderingMode == CtrlPanelDlgPriv::FinalRendering)
+ {
+ m_imagePreviewWidget->update();
+ return;
+ }
+ else if (d->currentRenderingMode == CtrlPanelDlgPriv::PreviewRendering)
+ {
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+ }
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotEffect()));
+}
+
+void CtrlPanelDlg::slotHelp()
+{
+ // If setAboutData() is called by plugin, well DigikamImagePlugins help is lauched,
+ // else digiKam help. In this case, setHelp() method must be used to set anchor and handbook name.
+
+ if (d->aboutData)
+ TDEApplication::kApplication()->invokeHelp(d->name, "digikam");
+ else
+ KDialogBase::slotHelp();
+}
+
+void CtrlPanelDlg::slotTimer()
+{
+ if (d->timer)
+ {
+ d->timer->stop();
+ delete d->timer;
+ }
+
+ d->timer = new TQTimer( this );
+ connect( d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotEffect()) );
+ d->timer->start(500, true);
+}
+
+void CtrlPanelDlg::slotEffect()
+{
+ // Computation already in process.
+ if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering)
+ return;
+
+ d->currentRenderingMode = CtrlPanelDlgPriv::PreviewRendering;
+ DDebug() << "Preview " << d->name << " started..." << endl;
+
+ m_imagePreviewWidget->setEnable(false);
+ m_imagePreviewWidget->setProgressVisible(true);
+ enableButton(Ok, false);
+ enableButton(User1, true);
+ enableButton(User2, false);
+ enableButton(User3, false);
+ enableButton(Try, false);
+ enableButton(Default, false);
+ m_imagePreviewWidget->setPreviewImageWaitCursor(true);
+ m_imagePreviewWidget->setProgress(0);
+
+ if (m_threadedFilter)
+ {
+ delete m_threadedFilter;
+ m_threadedFilter = 0;
+ }
+
+ prepareEffect();
+}
+
+void CtrlPanelDlg::slotOk()
+{
+ d->currentRenderingMode = CtrlPanelDlgPriv::FinalRendering;
+ DDebug() << "Final " << d->name << " started..." << endl;
+ saveDialogSize(d->name + TQString(" Tool Dialog"));
+ writeUserSettings();
+
+ m_imagePreviewWidget->setEnable(false);
+ m_imagePreviewWidget->setProgressVisible(true);
+ enableButton(Ok, false);
+ enableButton(User1, false);
+ enableButton(User2, false);
+ enableButton(User3, false);
+ enableButton(Try, false);
+ enableButton(Default, false);
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ m_imagePreviewWidget->setProgress(0);
+
+ if (m_threadedFilter)
+ {
+ delete m_threadedFilter;
+ m_threadedFilter = 0;
+ }
+
+ prepareFinal();
+}
+
+void CtrlPanelDlg::customEvent(TQCustomEvent *event)
+{
+ if (!event) return;
+
+ DImgThreadedFilter::EventData *ed = (DImgThreadedFilter::EventData*) event->data();
+
+ if (!ed) return;
+
+ if (ed->starting) // Computation in progress !
+ {
+ m_imagePreviewWidget->setProgress(ed->progress);
+ }
+ else
+ {
+ if (ed->success) // Computation Completed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case CtrlPanelDlgPriv::PreviewRendering:
+ {
+ DDebug() << "Preview " << d->name << " completed..." << endl;
+ putPreviewData();
+ abortPreview();
+ break;
+ }
+
+ case CtrlPanelDlgPriv::FinalRendering:
+ {
+ DDebug() << "Final" << d->name << " completed..." << endl;
+ putFinalData();
+ kapp->restoreOverrideCursor();
+ accept();
+ break;
+ }
+ }
+ }
+ else // Computation Failed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case CtrlPanelDlgPriv::PreviewRendering:
+ {
+ DDebug() << "Preview " << d->name << " failed..." << endl;
+ // abortPreview() must be call here for set progress bar to 0 properly.
+ abortPreview();
+ break;
+ }
+
+ case CtrlPanelDlgPriv::FinalRendering:
+ break;
+ }
+ }
+ }
+
+ delete ed;
+}
+
+// Backport KDialog::keyPressEvent() implementation from KDELibs to ignore Enter/Return Key events
+// to prevent any conflicts between dialog keys events and SpinBox keys events.
+
+void CtrlPanelDlg::keyPressEvent(TQKeyEvent *e)
+{
+ if ( e->state() == 0 )
+ {
+ switch ( e->key() )
+ {
+ case Key_Escape:
+ e->accept();
+ reject();
+ break;
+ case Key_Enter:
+ case Key_Return:
+ e->ignore();
+ break;
+ default:
+ e->ignore();
+ return;
+ }
+ }
+ else
+ {
+ // accept the dialog when Ctrl-Return is pressed
+ if ( e->state() == ControlButton &&
+ (e->key() == Key_Return || e->key() == Key_Enter) )
+ {
+ e->accept();
+ accept();
+ }
+ else
+ {
+ e->ignore();
+ }
+ }
+}
+
+} // NameSpace Digikam
+
diff --git a/src/libs/dialogs/ctrlpaneldlg.h b/src/libs/dialogs/ctrlpaneldlg.h
new file mode 100644
index 00000000..eb60877e
--- /dev/null
+++ b/src/libs/dialogs/ctrlpaneldlg.h
@@ -0,0 +1,110 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-07
+ * Description : A threaded filter control panel dialog for
+ * image editor plugins using DImg
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CTRLPANELDLG_H
+#define CTRLPANELDLG_H
+
+// TQt includes
+
+#include <tqstring.h>
+
+// KDE include.
+
+#include <kdialogbase.h>
+
+// Local includes
+
+#include "imagepannelwidget.h"
+#include "digikam_export.h"
+
+class TQFrame;
+
+namespace Digikam
+{
+
+class CtrlPanelDlgPriv;
+class DImgThreadedFilter;
+
+class DIGIKAM_EXPORT CtrlPanelDlg : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ CtrlPanelDlg(TQWidget* parent, TQString title, TQString name,
+ bool loadFileSettings=false, bool tryAction=false, bool progressBar=true,
+ int separateViewMode=ImagePannelWidget::SeparateViewAll,
+ TQFrame* bannerFrame=0);
+ ~CtrlPanelDlg();
+
+ void setAboutData(TDEAboutData *about);
+
+public:
+
+ ImagePannelWidget *m_imagePreviewWidget;
+
+ DImgThreadedFilter *m_threadedFilter;
+
+public slots:
+
+ void slotTimer();
+ void slotEffect();
+ void slotOk();
+ void slotTry();
+
+private slots:
+
+ virtual void slotDefault();
+ virtual void slotCancel();
+ virtual void slotUser1();
+ virtual void slotInit();
+ virtual void readUserSettings(void){ slotDefault(); };
+
+ void slotHelp();
+ void slotFocusChanged(void);
+
+protected:
+
+ void closeEvent(TQCloseEvent *e);
+ void customEvent(TQCustomEvent *event);
+ void abortPreview(void);
+ void keyPressEvent(TQKeyEvent *e);
+
+ virtual void writeUserSettings(void){};
+ virtual void resetValues(void){};
+ virtual void prepareEffect(void){};
+ virtual void prepareFinal(void){};
+ virtual void putPreviewData(void){};
+ virtual void putFinalData(void){};
+ virtual void renderingFinished(void){};
+
+private:
+
+ CtrlPanelDlgPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* CTRLPANELDLG_H */
diff --git a/src/libs/dialogs/deletedialog.cpp b/src/libs/dialogs/deletedialog.cpp
new file mode 100644
index 00000000..1db9b9c4
--- /dev/null
+++ b/src/libs/dialogs/deletedialog.cpp
@@ -0,0 +1,309 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-07
+ * Description : a dialog to delete item.
+ *
+ * Copyright (C) 2004 by Michael Pyne <michael.pyne@kdemail.net>
+ * Copyright (C) 2006 by Ian Monroe <ian@monroe.nu>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstringlist.h>
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqvbox.h>
+#include <tqhbox.h>
+#include <tqwidgetstack.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdeversion.h>
+#include <kdialogbase.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+#include <tdeio/job.h>
+#include <tdelocale.h>
+#include <kstdguiitem.h>
+
+// Local includes.
+
+#include "deletedialog.h"
+#include "albumsettings.h"
+#include "deletedialog.moc"
+
+namespace Digikam
+{
+
+//////////////////////////////////////////////////////////////////////////////
+// DeleteWidget implementation
+//////////////////////////////////////////////////////////////////////////////
+
+DeleteWidget::DeleteWidget(TQWidget *parent, const char *name)
+ : DeleteDialogBase(parent, name),
+ m_listMode(DeleteDialogMode::Files),
+ m_deleteMode(DeleteDialogMode::UseTrash)
+{
+ ddCheckBoxStack->raiseWidget(ddShouldDeletePage);
+
+ bool deleteInstead = !AlbumSettings::instance()->getUseTrash();
+ slotShouldDelete(deleteInstead);
+ ddShouldDelete->setChecked(deleteInstead);
+}
+
+void DeleteWidget::setFiles(const KURL::List &files)
+{
+ ddFileList->clear();
+ for( KURL::List::ConstIterator it = files.begin(); it != files.end(); it++)
+ {
+ if( (*it).isLocalFile() ) //path is nil for non-local
+ ddFileList->insertItem( (*it).path() );
+ else if ( (*it).protocol() == "digikamalbums")
+ ddFileList->insertItem( (*it).path() );
+ else
+ ddFileList->insertItem( (*it).prettyURL() );
+ }
+ updateText();
+}
+
+void DeleteWidget::slotShouldDelete(bool shouldDelete)
+{
+ setDeleteMode(shouldDelete ? DeleteDialogMode::DeletePermanently : DeleteDialogMode::UseTrash);
+}
+
+void DeleteWidget::setDeleteMode(DeleteDialogMode::DeleteMode deleteMode)
+{
+ m_deleteMode = deleteMode;
+ updateText();
+}
+
+void DeleteWidget::setListMode(DeleteDialogMode::ListMode listMode)
+{
+ m_listMode = listMode;
+ updateText();
+}
+
+void DeleteWidget::updateText()
+{
+ switch (m_listMode)
+ {
+ case DeleteDialogMode::Files:
+
+ // Delete files
+
+ if (m_deleteMode == DeleteDialogMode::DeletePermanently)
+ {
+ ddDeleteText->setText(i18n("<qt>These items will be <b>permanently "
+ "deleted</b> from your hard disk.</qt>"));
+ ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_warning",
+ TDEIcon::Desktop, TDEIcon::SizeLarge));
+ }
+ else
+ {
+ ddDeleteText->setText(i18n("<qt>These items will be moved to Trash.</qt>"));
+ ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("trashcan_full",
+ TDEIcon::Desktop, TDEIcon::SizeLarge));
+ }
+ ddNumFiles->setText(i18n("<b>1</b> file selected.", "<b>%n</b> files selected.", ddFileList->count()));
+ break;
+
+ case DeleteDialogMode::Albums:
+
+ // Delete albums = folders
+
+ if (m_deleteMode == DeleteDialogMode::DeletePermanently)
+ {
+ ddDeleteText->setText(i18n("<qt>These albums will be <b>permanently "
+ "deleted</b> from your hard disk.</qt>"));
+ ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_warning",
+ TDEIcon::Desktop, TDEIcon::SizeLarge));
+ }
+ else
+ {
+ ddDeleteText->setText(i18n("<qt>These albums will be moved to Trash.</qt>"));
+ ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("trashcan_full",
+ TDEIcon::Desktop, TDEIcon::SizeLarge));
+ }
+ ddNumFiles->setText(i18n("<b>1</b> album selected.", "<b>%n</b> albums selected.", ddFileList->count()));
+ break;
+
+ case DeleteDialogMode::Subalbums:
+
+ // As above, but display additional warning
+
+ if (m_deleteMode == DeleteDialogMode::DeletePermanently)
+ {
+ ddDeleteText->setText(i18n("<qt>These albums will be <b>permanently "
+ "deleted</b> from your hard disk.<br>"
+ "Note that <b>all subalbums</b> "
+ "are included in this list and will "
+ "be deleted permanently as well.</qt>"));
+ ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_warning",
+ TDEIcon::Desktop, TDEIcon::SizeLarge));
+ }
+ else
+ {
+ ddDeleteText->setText(i18n("<qt>These albums will be moved to Trash.<br>"
+ "Note that <b>all subalbums</b> "
+ "are included in this list and will "
+ "be moved to Trash as well.</qt>"));
+ ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("trashcan_full",
+ TDEIcon::Desktop, TDEIcon::SizeLarge));
+ }
+ ddNumFiles->setText(i18n("<b>1</b> album selected.", "<b>%n</b> albums selected.", ddFileList->count()));
+ break;
+
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// DeleteDialog implementation
+//////////////////////////////////////////////////////////////////////////////
+
+DeleteDialog::DeleteDialog(TQWidget *parent, const char *name)
+ : KDialogBase(Swallow, WStyle_DialogBorder, parent, name,
+ true, // modal
+ i18n("About to delete selected files"), // caption
+ Ok | Cancel, // available buttons
+ Ok, // default button
+ true // use separator between buttons and the main widget
+ ),
+ m_saveShouldDeleteUserPreference(true),
+ m_saveDoNotShowAgain(false),
+ m_trashGuiItem(i18n("&Move to Trash"), "trashcan_full")
+{
+ m_widget = new DeleteWidget(this, "delete_dialog_widget");
+ setMainWidget(m_widget);
+
+ m_widget->setMinimumSize(400, 300);
+ setMinimumSize(410, 326);
+ adjustSize();
+
+ slotShouldDelete(shouldDelete());
+ connect(m_widget->ddShouldDelete, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotShouldDelete(bool)));
+
+ actionButton(Ok)->setFocus();
+}
+
+bool DeleteDialog::confirmDeleteList(const KURL::List& condemnedFiles,
+ DeleteDialogMode::ListMode listMode,
+ DeleteDialogMode::DeleteMode deleteMode)
+{
+ setURLs(condemnedFiles);
+ presetDeleteMode(deleteMode);
+ setListMode(listMode);
+
+ if (deleteMode == DeleteDialogMode::NoChoiceTrash)
+ {
+ if (!AlbumSettings::instance()->getShowTrashDeleteDialog())
+ return true;
+ }
+ return exec() == TQDialog::Accepted;
+}
+
+void DeleteDialog::setURLs(const KURL::List &files)
+{
+ m_widget->setFiles(files);
+}
+
+void DeleteDialog::accept()
+{
+ // Save user's preference
+ AlbumSettings *settings = AlbumSettings::instance();
+
+ if (m_saveShouldDeleteUserPreference)
+ {
+ settings->setUseTrash(!shouldDelete());
+ }
+ if (m_saveDoNotShowAgain)
+ {
+ settings->setShowTrashDeleteDialog(!m_widget->ddDoNotShowAgain->isChecked());
+ }
+
+ settings->saveSettings();
+
+ KDialogBase::accept();
+}
+
+void DeleteDialog::slotShouldDelete(bool shouldDelete)
+{
+ // This is called once from constructor, and then when the user changed the checkbox state.
+ // In that case, save the user's preference.
+ m_saveShouldDeleteUserPreference = true;
+ setButtonGuiItem(Ok, shouldDelete ? KStdGuiItem::del() : m_trashGuiItem);
+}
+
+void DeleteDialog::presetDeleteMode(DeleteDialogMode::DeleteMode mode)
+{
+ switch (mode)
+ {
+ case DeleteDialogMode::NoChoiceTrash:
+ {
+ // access the widget directly, signals will be fired to DeleteDialog and DeleteWidget
+ m_widget->ddShouldDelete->setChecked(false);
+ m_widget->ddCheckBoxStack->raiseWidget(m_widget->ddDoNotShowAgainPage);
+ m_saveDoNotShowAgain = true;
+ break;
+ }
+ case DeleteDialogMode::NoChoiceDeletePermanently:
+ {
+ m_widget->ddShouldDelete->setChecked(true);
+ m_widget->ddCheckBoxStack->hide();
+ break;
+ }
+ case DeleteDialogMode::UserPreference:
+ {
+ break;
+ }
+ case DeleteDialogMode::UseTrash:
+ case DeleteDialogMode::DeletePermanently:
+ {
+ // toggles signals which do the rest
+ m_widget->ddShouldDelete->setChecked(mode == DeleteDialogMode::DeletePermanently);
+
+ // the preference set by this preset method will be ignored
+ // for the next DeleteDialog instance and not stored as user preference.
+ // Only if the user once changes this value, it will be taken as user preference.
+ m_saveShouldDeleteUserPreference = false;
+ break;
+ }
+ }
+}
+
+void DeleteDialog::setListMode(DeleteDialogMode::ListMode mode)
+{
+ m_widget->setListMode(mode);
+ switch (mode)
+ {
+ case DeleteDialogMode::Files:
+ setCaption(i18n("About to delete selected files"));
+ break;
+
+ case DeleteDialogMode::Albums:
+ case DeleteDialogMode::Subalbums:
+ setCaption(i18n("About to delete selected albums"));
+ break;
+ }
+}
+
+} // namespace Digikam
diff --git a/src/libs/dialogs/deletedialog.h b/src/libs/dialogs/deletedialog.h
new file mode 100644
index 00000000..16ab9f89
--- /dev/null
+++ b/src/libs/dialogs/deletedialog.h
@@ -0,0 +1,140 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-07
+ * Description : a dialog to delete item.
+ *
+ * Copyright (C) 2004 by Michael Pyne <michael.pyne@kdemail.net>
+ * Copyright (C) 2006 by Ian Monroe <ian@monroe.nu>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef _DELETEDIALOG_H
+#define _DELETEDIALOG_H
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "deletedialogbase.h"
+
+class TQStringList;
+class TDEListBox;
+class KGuiItem;
+class TQLabel;
+class TQWidgetStack;
+
+namespace Digikam
+{
+
+namespace DeleteDialogMode
+{
+ enum ListMode
+ {
+ Files,
+ Albums,
+ Subalbums
+ };
+
+ enum DeleteMode
+ {
+ NoChoiceTrash, // "Do not show again" checkbox, does not show if config entry is set
+ NoChoiceDeletePermanently, // No checkbox
+ UserPreference, // Checkbox to toggle trash/permanent, preset to user's last preference
+ UseTrash, // same beckbox as above, preset to trash
+ DeletePermanently // same checkbox as above, preset to permanent
+ };
+}
+
+class DeleteWidget : public DeleteDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ DeleteWidget(TQWidget *parent = 0, const char *name = 0);
+
+ void setFiles(const KURL::List &files);
+ void setListMode(DeleteDialogMode::ListMode mode);
+ void setDeleteMode(DeleteDialogMode::DeleteMode deleteMode);
+
+protected slots:
+
+ void slotShouldDelete(bool shouldDelete);
+
+protected:
+
+ void updateText();
+ DeleteDialogMode::ListMode m_listMode;
+ DeleteDialogMode::DeleteMode m_deleteMode;
+};
+
+class DIGIKAM_EXPORT DeleteDialog : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum Mode
+ {
+ ModeFiles,
+ ModeAlbums,
+ ModeSubalbums
+ };
+
+public:
+
+ DeleteDialog(TQWidget *parent, const char *name = "delete_dialog");
+
+ bool confirmDeleteList(const KURL::List &condemnedURLs,
+ DeleteDialogMode::ListMode listMode,
+ DeleteDialogMode::DeleteMode deleteMode);
+ bool shouldDelete() const { return m_widget->ddShouldDelete->isChecked(); }
+
+ void setURLs(const KURL::List &files);
+ void presetDeleteMode(DeleteDialogMode::DeleteMode mode);
+ void setListMode(DeleteDialogMode::ListMode mode);
+
+protected slots:
+
+ virtual void accept();
+ void slotShouldDelete(bool shouldDelete);
+
+private:
+
+ bool m_saveShouldDeleteUserPreference;
+ bool m_saveDoNotShowAgain;
+
+ KGuiItem m_trashGuiItem;
+
+ DeleteWidget *m_widget;
+};
+
+} // namespace Digikam
+
+#endif // _DELETEDIALOG_H
+
diff --git a/src/libs/dialogs/deletedialogbase.ui b/src/libs/dialogs/deletedialogbase.ui
new file mode 100644
index 00000000..8527a903
--- /dev/null
+++ b/src/libs/dialogs/deletedialogbase.ui
@@ -0,0 +1,188 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+ <class>DeleteDialogBase</class>
+ <widget class="TQWidget">
+ <property name="name">
+ <cstring>DeleteDialogBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>517</width>
+ <height>162</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>DeleteDialogBase</string>
+ </property>
+ <property name="geometry" stdset="0">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>542</width>
+ <height>374</height>
+ </rect>
+ </property>
+ <property name="minimumSize" stdset="0">
+ <size>
+ <width>420</width>
+ <height>320</height>
+ </size>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="TQLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="TQLabel">
+ <property name="name">
+ <cstring>ddWarningIcon</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>4</hsizetype>
+ <vsizetype>4</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Icon Placeholder, not in GUI</string>
+ </property>
+ </widget>
+ <widget class="TQLayoutWidget">
+ <property name="name">
+ <cstring>layout3</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="TQLabel">
+ <property name="name">
+ <cstring>ddDeleteText</cstring>
+ </property>
+ <property name="text">
+ <string>Deletion method placeholder, never shown to user.</string>
+ </property>
+ <property name="alignment" stdset="0">
+ <string>WordBreak|AlignCenter</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="TDEListBox">
+ <property name="name">
+ <cstring>ddFileList</cstring>
+ </property>
+ <property name="selectionMode">
+ <enum>NoSelection</enum>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>List of files that are about to be deleted.</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>This is the list of items that are about to be deleted.</string>
+ </property>
+ </widget>
+ <widget class="TQLabel">
+ <property name="name">
+ <cstring>ddNumFiles</cstring>
+ </property>
+ <property name="text">
+ <string>Placeholder for number of files, not in GUI</string>
+ </property>
+ <property name="alignment" stdset="0">
+ <string>AlignVCenter|AlignRight</string>
+ </property>
+ </widget>
+ <widget class="TQWidgetStack">
+ <property name="name">
+ <cstring>ddCheckBoxStack</cstring>
+ </property>
+ <widget class="TQWidget">
+ <property name="name">
+ <cstring>ddShouldDeletePage</cstring>
+ </property>
+ <attribute name="id">
+ <number>0</number>
+ </attribute>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="TQCheckBox">
+ <property name="name">
+ <cstring>ddShouldDelete</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Delete files instead of moving them to the trash</string>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>If checked, files will be permanently removed instead of being placed in the Trash Bin</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;&lt;p&gt;If this box is checked, files will be &lt;b&gt;permanently removed&lt;/b&gt; instead of being placed in the Trash Bin.&lt;/p&gt;
+
+ &lt;p&gt;&lt;em&gt;Use this option with caution&lt;/em&gt;: most filesystems are unable to undelete deleted files reliably.&lt;/p&gt;&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="TQWidget">
+ <property name="name">
+ <cstring>ddDoNotShowAgainPage</cstring>
+ </property>
+ <attribute name="id">
+ <number>1</number>
+ </attribute>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="TQCheckBox">
+ <property name="name">
+ <cstring>ddDoNotShowAgain</cstring>
+ </property>
+ <property name="text">
+ <string>Do not &amp;ask again</string>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>If checked, this dialog will no longer be shown, and files will be directly moved to the Trash Bin</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;&lt;p&gt;If this box is checked, this dialog will no longer be shown, and files will be directly moved to the Trash Bin&lt;/p&gt;</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ </widget>
+ </vbox>
+ </widget>
+ <customwidgets>
+ </customwidgets>
+ <connections>
+ </connections>
+ <layoutdefaults spacing="6" margin="11"/>
+ <includes>
+ <include location="global" impldecl="in implementation">tdelistbox.h</include>
+ </includes>
+</UI>
diff --git a/src/libs/dialogs/dprogressdlg.cpp b/src/libs/dialogs/dprogressdlg.cpp
new file mode 100644
index 00000000..d4b0dc29
--- /dev/null
+++ b/src/libs/dialogs/dprogressdlg.cpp
@@ -0,0 +1,224 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-30-08
+ * Description : a progress dialog for digiKam
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+#include <tqheader.h>
+#include <tqlabel.h>
+#include <tqimage.h>
+#include <tqpushbutton.h>
+#include <tqlistview.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kprogress.h>
+#include <tdeapplication.h>
+#include <kdialogbase.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dprogressdlg.h"
+#include "dprogressdlg.moc"
+
+namespace Digikam
+{
+
+class DProgressDlgPriv
+{
+public:
+
+ DProgressDlgPriv()
+ {
+ progress = 0;
+ actionsList = 0;
+ logo = 0;
+ title = 0;
+ label = 0;
+ allowCancel = true;
+ cancelled = false;
+ }
+
+ bool allowCancel;
+ bool cancelled;
+
+ TQLabel *logo;
+ TQLabel *title;
+ TQLabel *label;
+
+ TQListView *actionsList;
+
+ KProgress *progress;
+};
+
+DProgressDlg::DProgressDlg(TQWidget *parent, const TQString &caption)
+ : KDialogBase(parent, 0, true, caption, Cancel)
+{
+ d = new DProgressDlgPriv;
+
+ TQFrame *page = makeMainWidget();
+ TQGridLayout* grid = new TQGridLayout(page, 1, 1, 0, spacingHint());
+ TQVBoxLayout *vlay = new TQVBoxLayout();
+ d->actionsList = new TQListView(page);
+ d->label = new TQLabel(page);
+ d->title = new TQLabel(page);
+ d->logo = new TQLabel(page);
+ d->progress = new KProgress(page);
+ vlay->addWidget(d->logo);
+ vlay->addWidget(d->progress);
+ vlay->addWidget(d->title);
+ vlay->addStretch();
+
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ d->logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 128, TDEIcon::DefaultState, 0, true));
+
+ d->actionsList->addColumn("Thumb"); // no i18n here: hiden column
+ d->actionsList->addColumn("Status"); // no i18n here: hiden column
+ d->actionsList->setSorting(-1);
+ d->actionsList->setItemMargin(1);
+ d->actionsList->setSelectionMode(TQListView::NoSelection);
+ d->actionsList->header()->hide();
+ d->actionsList->setResizeMode(TQListView::LastColumn);
+
+ grid->addMultiCellLayout(vlay, 0, 1, 0, 0);
+ grid->addMultiCellWidget(d->label, 0, 0, 1, 1);
+ grid->addMultiCellWidget(d->actionsList, 1, 1, 1, 1);
+ grid->setRowStretch(1, 10);
+ grid->setColStretch(1, 10);
+}
+
+DProgressDlg::~DProgressDlg()
+{
+ delete d;
+}
+
+void DProgressDlg::slotCancel()
+{
+ d->cancelled = true;
+
+ if (d->allowCancel)
+ {
+ KDialogBase::slotCancel();
+ }
+}
+
+void DProgressDlg::setButtonText(const TQString &text)
+{
+ KDialogBase::setButtonText(Cancel, text);
+}
+
+void DProgressDlg::addedAction(const TQPixmap& pix, const TQString &text)
+{
+ TQImage img;
+ TQListViewItem *item = new TQListViewItem(d->actionsList,
+ d->actionsList->lastItem(), TQString(), text);
+
+ if (pix.isNull())
+ {
+ TQString dir = TDEGlobal::dirs()->findResourceDir("digikam_imagebroken",
+ "image-broken.png");
+ dir = dir + "/image-broken.png";
+ TQPixmap pixbi(dir);
+ img = pixbi.convertToImage().scale(32, 32, TQImage::ScaleMin);
+ }
+ else
+ {
+ img = pix.convertToImage().scale(32, 32, TQImage::ScaleMin);
+ }
+
+ TQPixmap pixmap(img);
+ item->setPixmap(0, pixmap);
+ d->actionsList->ensureItemVisible(item);
+}
+
+void DProgressDlg::reset()
+{
+ d->actionsList->clear();
+ d->progress->setValue(0);
+}
+
+void DProgressDlg::setTotalSteps(int total)
+{
+ d->progress->setTotalSteps(total);
+}
+
+void DProgressDlg::setValue(int value)
+{
+ d->progress->setValue(value);
+}
+
+void DProgressDlg::advance(int value)
+{
+ d->progress->advance(value);
+}
+
+void DProgressDlg::setLabel(const TQString &text)
+{
+ d->label->setText(text);
+}
+
+void DProgressDlg::setTitle(const TQString &text)
+{
+ d->title->setText(text);
+}
+
+void DProgressDlg::showCancelButton(bool show)
+{
+ showButtonCancel(show);
+}
+
+void DProgressDlg::setAllowCancel(bool allowCancel)
+{
+ d->allowCancel = allowCancel;
+ showCancelButton(allowCancel);
+}
+
+bool DProgressDlg::allowCancel() const
+{
+ return d->allowCancel;
+}
+
+bool DProgressDlg::wasCancelled() const
+{
+ return d->cancelled;
+}
+
+KProgress *DProgressDlg::progressBar() const
+{
+ return d->progress;
+}
+
+void DProgressDlg::setActionListVSBarVisible(bool visible)
+{
+ if (!visible)
+ d->actionsList->setVScrollBarMode(TQScrollView::AlwaysOff);
+ else
+ d->actionsList->setVScrollBarMode(TQScrollView::Auto);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dialogs/dprogressdlg.h b/src/libs/dialogs/dprogressdlg.h
new file mode 100644
index 00000000..d75f509d
--- /dev/null
+++ b/src/libs/dialogs/dprogressdlg.h
@@ -0,0 +1,79 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-30-08
+ * Description : a progress dialog for digiKam
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DPROGRESSDLG_H
+#define DPROGRESSDLG_H
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class KProgress;
+
+namespace Digikam
+{
+
+class DProgressDlgPriv;
+
+class DIGIKAM_EXPORT DProgressDlg : public KDialogBase
+{
+TQ_OBJECT
+
+
+ public:
+
+ DProgressDlg(TQWidget *parent=0, const TQString &caption=TQString());
+ ~DProgressDlg();
+
+ void setButtonText(const TQString &text);
+ void addedAction(const TQPixmap& pix, const TQString &text);
+ void reset();
+ void setTotalSteps(int total);
+ void setValue(int value);
+ void advance(int value);
+ void setLabel(const TQString &text);
+ void setTitle(const TQString &text);
+ void setActionListVSBarVisible(bool visible);
+ void showCancelButton(bool show);
+ void setAllowCancel(bool allowCancel);
+ bool wasCancelled() const;
+ bool allowCancel() const;
+
+ KProgress *progressBar() const;
+
+ protected slots:
+
+ void slotCancel();
+
+ private:
+
+ DProgressDlgPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif // DPROGRESSDLG_H
diff --git a/src/libs/dialogs/iccprofileinfodlg.cpp b/src/libs/dialogs/iccprofileinfodlg.cpp
new file mode 100644
index 00000000..04f30c0c
--- /dev/null
+++ b/src/libs/dialogs/iccprofileinfodlg.cpp
@@ -0,0 +1,60 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-16
+ * Description : a dialog to display icc profile information.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "iccprofilewidget.h"
+#include "iccprofileinfodlg.h"
+
+namespace Digikam
+{
+
+ICCProfileInfoDlg::ICCProfileInfoDlg(TQWidget* parent, const TQString& profilePath,
+ const TQByteArray& profileData)
+ : KDialogBase(parent, 0, true, i18n("Color Profile Info"),
+ Help|Ok, Ok, true)
+{
+ setHelp("iccprofile.anchor", "digikam");
+ setCaption(profilePath);
+
+ ICCProfileWidget *profileWidget = new ICCProfileWidget(this, 0, 340, 256);
+
+ if (profileData.isEmpty())
+ profileWidget->loadFromURL(KURL(profilePath));
+ else
+ profileWidget->loadFromData(profilePath, profileData);
+
+ setMainWidget(profileWidget);
+}
+
+ICCProfileInfoDlg::~ICCProfileInfoDlg()
+{
+}
+
+} // NameSpace Digikam
+
diff --git a/src/libs/dialogs/iccprofileinfodlg.h b/src/libs/dialogs/iccprofileinfodlg.h
new file mode 100644
index 00000000..abcc1ed8
--- /dev/null
+++ b/src/libs/dialogs/iccprofileinfodlg.h
@@ -0,0 +1,58 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-16
+ * Description : a dialog to display ICC profile information.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICCPROFILEINFODLG_H
+#define ICCPROFILEINFODLG_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQWidget;
+
+namespace Digikam
+{
+
+class ICCProfileInfoDlgPriv;
+
+class DIGIKAM_EXPORT ICCProfileInfoDlg : public KDialogBase
+{
+
+public:
+
+ ICCProfileInfoDlg(TQWidget *parent, const TQString& profilePath, const TQByteArray& profileData=TQByteArray());
+ ~ICCProfileInfoDlg();
+
+};
+
+} // Namespace Digikam
+
+#endif /* ICCPROFILEINFODLG_H */
diff --git a/src/libs/dialogs/imagedialog.cpp b/src/libs/dialogs/imagedialog.cpp
new file mode 100644
index 00000000..d052a2af
--- /dev/null
+++ b/src/libs/dialogs/imagedialog.cpp
@@ -0,0 +1,366 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-03-13
+ * Description : image files selector dialog.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqguardedptr.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+#include <tdefiledialog.h>
+#include <kimageio.h>
+#include <kiconloader.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "thumbnailsize.h"
+#include "thumbnailjob.h"
+#include "imagedialog.h"
+#include "imagedialog.moc"
+
+namespace Digikam
+{
+
+class ImageDialogPreviewPrivate
+{
+
+public:
+
+ ImageDialogPreviewPrivate()
+ {
+ imageLabel = 0;
+ infoLabel = 0;
+ thumbJob = 0;
+ timer = 0;
+ }
+
+ TQTimer *timer;
+
+ TQLabel *imageLabel;
+ TQLabel *infoLabel;
+
+ KURL currentURL;
+
+ DMetadata metaIface;
+
+ TQGuardedPtr<ThumbnailJob> thumbJob;
+};
+
+ImageDialogPreview::ImageDialogPreview(TQWidget *parent)
+ : KPreviewWidgetBase(parent)
+{
+ d = new ImageDialogPreviewPrivate;
+
+ TQVBoxLayout *vlay = new TQVBoxLayout(this);
+ d->imageLabel = new TQLabel(this);
+ d->imageLabel->setAlignment(TQt::AlignHCenter | TQt::AlignVCenter);
+ d->imageLabel->setSizePolicy(TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding));
+
+ d->infoLabel = new TQLabel(this);
+
+ vlay->setMargin(0);
+ vlay->setSpacing(KDialog::spacingHint());
+ vlay->addWidget(d->imageLabel);
+ vlay->addWidget(d->infoLabel);
+
+ setSupportedMimeTypes(KImageIO::mimeTypes());
+
+ d->timer = new TQTimer(this);
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(showPreview()) );
+}
+
+ImageDialogPreview::~ImageDialogPreview()
+{
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+ delete d;
+}
+
+TQSize ImageDialogPreview::sizeHint() const
+{
+ return TQSize(256, 256);
+}
+
+void ImageDialogPreview::resizeEvent(TQResizeEvent *)
+{
+ d->timer->start(100, true);
+}
+
+void ImageDialogPreview::showPreview()
+{
+ KURL url(d->currentURL);
+ clearPreview();
+ showPreview(url);
+}
+
+void ImageDialogPreview::showPreview(const KURL& url)
+{
+ if (!url.isValid())
+ {
+ clearPreview();
+ return;
+ }
+
+ if (url != d->currentURL)
+ {
+ clearPreview();
+ d->currentURL = url;
+
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ d->thumbJob = new ThumbnailJob(url, ThumbnailSize::Huge, true, true);
+
+ connect(d->thumbJob, TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnail(const KURL&, const TQPixmap&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalFailed(const KURL&)),
+ this, TQ_SLOT(slotFailedThumbnail(const KURL&)));
+
+ d->metaIface.load(d->currentURL.path());
+ PhotoInfoContainer info = d->metaIface.getPhotographInformations();
+ if (!info.isEmpty())
+ {
+ TQString identify;
+ TQString make, model, dateTime, aperture, focalLength, exposureTime, sensitivity;
+ TQString unavailable(i18n("<i>unavailable</i>"));
+ TQString cellBeg("<tr><td><nobr><font size=-1>");
+ TQString cellMid("</font></nobr></td><td><nobr><font size=-1>");
+ TQString cellEnd("</font></nobr></td></tr>");
+
+ if (info.make.isEmpty()) make = unavailable;
+ else make = info.make;
+
+ if (info.model.isEmpty()) model = unavailable;
+ else model = info.model;
+
+ if (!info.dateTime.isValid()) dateTime = unavailable;
+ else dateTime = TDEGlobal::locale()->formatDateTime(info.dateTime, true, true);
+
+ if (info.aperture.isEmpty()) aperture = unavailable;
+ else aperture = info.aperture;
+
+ if (info.focalLength.isEmpty()) focalLength = unavailable;
+ else focalLength = info.focalLength;
+
+ if (info.exposureTime.isEmpty()) exposureTime = unavailable;
+ else exposureTime = info.exposureTime;
+
+ if (info.sensitivity.isEmpty()) sensitivity = unavailable;
+ else sensitivity = i18n("%1 ISO").arg(info.sensitivity);
+
+ identify = "<table cellspacing=0 cellpadding=0>";
+ identify += cellBeg + i18n("Make:") + cellMid + make + cellEnd;
+ identify += cellBeg + i18n("Model:") + cellMid + model + cellEnd;
+ identify += cellBeg + i18n("Created:") + cellMid + dateTime + cellEnd;
+ identify += cellBeg + i18n("Aperture:") + cellMid + aperture + cellEnd;
+ identify += cellBeg + i18n("Focal:") + cellMid + focalLength + cellEnd;
+ identify += cellBeg + i18n("Exposure:") + cellMid + exposureTime + cellEnd;
+ identify += cellBeg + i18n("Sensitivity:") + cellMid + sensitivity + cellEnd;
+ identify += "</table>";
+
+ d->infoLabel->setText(identify);
+ }
+ else
+ d->infoLabel->clear();
+ }
+}
+
+void ImageDialogPreview::slotGotThumbnail(const KURL& url, const TQPixmap& pix)
+{
+ if (url == d->currentURL)
+ {
+ TQPixmap pixmap;
+ TQSize s = d->imageLabel->contentsRect().size();
+
+ if (s.width() < pix.width() || s.height() < pix.height())
+ pixmap = pix.convertToImage().smoothScale(s, TQImage::ScaleMin);
+ else
+ pixmap = pix;
+
+ d->imageLabel->setPixmap(pixmap);
+ }
+}
+
+void ImageDialogPreview::slotFailedThumbnail(const KURL& /*url*/)
+{
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ d->imageLabel->setPixmap(iconLoader->loadIcon("image-x-generic", TDEIcon::NoGroup, 128,
+ TDEIcon::DefaultState, 0, true));
+}
+
+void ImageDialogPreview::clearPreview()
+{
+ d->imageLabel->clear();
+ d->infoLabel->clear();
+ d->currentURL = KURL();
+}
+
+// ------------------------------------------------------------------------
+
+class ImageDialogPrivate
+{
+
+public:
+
+ ImageDialogPrivate()
+ {
+ singleSelect = false;
+ }
+
+ bool singleSelect;
+
+ TQString fileformats;
+
+ KURL url;
+ KURL::List urls;
+};
+
+ImageDialog::ImageDialog(TQWidget* parent, const KURL &url, bool singleSelect, const TQString& caption)
+{
+ d = new ImageDialogPrivate;
+ d->singleSelect = singleSelect;
+
+ TQStringList patternList = TQStringList::split('\n', KImageIO::pattern(KImageIO::Reading));
+
+ // All Images from list must been always the first entry given by KDE API
+ TQString allPictures = patternList[0];
+
+#if KDCRAW_VERSION < 0x000106
+ // Add other files format witch are missing to All Images" type mime provided by KDE and remplace current.
+ if (KDcrawIface::DcrawBinary::instance()->versionIsRight())
+ {
+ allPictures.insert(allPictures.find("|"), TQString(KDcrawIface::DcrawBinary::instance()->rawFiles()) + TQString(" *.JPE *.TIF"));
+ patternList.remove(patternList[0]);
+ patternList.prepend(allPictures);
+ // Added RAW file formats supported by dcraw program like a type mime.
+ // Nota: we cannot use here "image/x-raw" type mime from KDE because it uncomplete
+ // or unavailable (see file #121242 in B.K.O).
+ patternList.append(i18n("\n%1|Camera RAW files").arg(TQString(KDcrawIface::DcrawBinary::instance()->rawFiles())));
+ }
+#else
+ allPictures.insert(allPictures.find("|"), TQString(KDcrawIface::KDcraw::rawFiles()) + TQString(" *.JPE *.TIF"));
+ patternList.remove(patternList[0]);
+ patternList.prepend(allPictures);
+ // Added RAW file formats supported by dcraw program like a type mime.
+ // Nota: we cannot use here "image/x-raw" type mime from KDE because it uncomplete
+ // or unavailable (see file #121242 in B.K.O).
+ patternList.append(i18n("\n%1|Camera RAW files").arg(TQString(KDcrawIface::KDcraw::rawFiles())));
+#endif
+
+ d->fileformats = patternList.join("\n");
+
+ DDebug() << "fileformats=" << d->fileformats << endl;
+
+ KFileDialog dlg(url.path(), d->fileformats, parent, "imageFileOpenDialog", false);
+ ImageDialogPreview *preview = new ImageDialogPreview(&dlg);
+ dlg.setPreviewWidget(preview);
+ dlg.setOperationMode(KFileDialog::Opening);
+
+ if (d->singleSelect)
+ {
+ dlg.setMode(KFile::File);
+ if (caption.isEmpty()) dlg.setCaption(i18n("Select an Image"));
+ else dlg.setCaption(caption);
+ dlg.exec();
+ d->url = dlg.selectedURL();
+ }
+ else
+ {
+ dlg.setMode(KFile::Files);
+ if (caption.isEmpty()) dlg.setCaption(i18n("Select Images"));
+ else dlg.setCaption(caption);
+ dlg.exec();
+ d->urls = dlg.selectedURLs();
+ }
+}
+
+ImageDialog::~ImageDialog()
+{
+ delete d;
+}
+
+bool ImageDialog::singleSelect() const
+{
+ return d->singleSelect;
+}
+
+TQString ImageDialog::fileformats() const
+{
+ return d->fileformats;
+}
+
+KURL ImageDialog::url() const
+{
+ return d->url;
+}
+
+KURL::List ImageDialog::urls() const
+{
+ return d->urls;
+}
+
+KURL::List ImageDialog::getImageURLs(TQWidget* parent, const KURL& url, const TQString& caption)
+{
+ ImageDialog dlg(parent, url, false, caption);
+ if (!dlg.urls().isEmpty())
+ return dlg.urls();
+ else
+ return KURL::List();
+}
+
+KURL ImageDialog::getImageURL(TQWidget* parent, const KURL& url, const TQString& caption)
+{
+ ImageDialog dlg(parent, url, true, caption);
+ if (dlg.url() != KURL())
+ return dlg.url();
+ else
+ return KURL();
+}
+
+} // namespace Digikam
diff --git a/src/libs/dialogs/imagedialog.h b/src/libs/dialogs/imagedialog.h
new file mode 100644
index 00000000..275765cd
--- /dev/null
+++ b/src/libs/dialogs/imagedialog.h
@@ -0,0 +1,100 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-03-13
+ * Description : image files selector dialog.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEDIALOG_H
+#define IMAGEDIALOG_H
+
+// KDE includes.
+
+#include <kurl.h>
+#include <kpreviewwidgetbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImageDialogPrivate;
+class ImageDialogPreviewPrivate;
+
+class DIGIKAM_EXPORT ImageDialogPreview : public KPreviewWidgetBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageDialogPreview(TQWidget *parent=0);
+ ~ImageDialogPreview();
+
+ TQSize sizeHint() const;
+
+public slots:
+
+ void showPreview(const KURL &url);
+
+private slots:
+
+ void showPreview();
+ void slotGotThumbnail(const KURL& url, const TQPixmap& pix);
+ void slotFailedThumbnail(const KURL& url);
+ void clearPreview();
+
+private:
+
+ void resizeEvent(TQResizeEvent *e);
+
+private:
+
+ class ImageDialogPreviewPrivate *d;
+};
+
+// ------------------------------------------------------------------------
+
+class DIGIKAM_EXPORT ImageDialog
+{
+
+public:
+
+ ImageDialog(TQWidget* parent, const KURL &url, bool singleSelect=false, const TQString& caption=TQString());
+ ~ImageDialog();
+
+ KURL url() const;
+ KURL::List urls() const;
+
+ bool singleSelect() const;
+ TQString fileformats() const;
+
+ static KURL::List getImageURLs(TQWidget* parent, const KURL& url, const TQString& caption=TQString());
+ static KURL getImageURL(TQWidget* parent, const KURL& url, const TQString& caption=TQString());
+
+private:
+
+ ImageDialogPrivate* d;
+};
+
+} // namespace Digikam
+
+#endif /* IMAGEDIALOG_H */
diff --git a/src/libs/dialogs/imagedlgbase.cpp b/src/libs/dialogs/imagedlgbase.cpp
new file mode 100644
index 00000000..61666406
--- /dev/null
+++ b/src/libs/dialogs/imagedlgbase.cpp
@@ -0,0 +1,261 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-23
+ * Description : simple plugins dialog without threadable
+ * filter interface. The dialog layout is
+ * designed to accept custom widgets in
+ * preview and settings area.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+#include <tqsplitter.h>
+#include <tqhbox.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeglobalsettings.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "sidebar.h"
+#include "dimginterface.h"
+#include "imagedlgbase.h"
+#include "imagedlgbase.moc"
+
+namespace Digikam
+{
+
+class ImageDlgBasePriv
+{
+public:
+
+ ImageDlgBasePriv()
+ {
+ aboutData = 0;
+ timer = 0;
+ parent = 0;
+ mainLayout = 0;
+ hbox = 0;
+ settingsSideBar = 0;
+ splitter = 0;
+ }
+
+ bool tryAction;
+
+ TQGridLayout *mainLayout;
+
+ TQWidget *parent;
+
+ TQString name;
+
+ TQTimer *timer;
+
+ TQHBox *hbox;
+
+ TQSplitter *splitter;
+
+ TDEAboutData *aboutData;
+
+ Sidebar *settingsSideBar;
+};
+
+ImageDlgBase::ImageDlgBase(TQWidget* parent, TQString title, TQString name,
+ bool loadFileSettings, bool tryAction, TQFrame* bannerFrame)
+ : KDialogBase(Plain, 0, Help|Default|User1|User2|User3|Try|Ok|Cancel, Ok,
+ parent, 0, true, true,
+ TQString(),
+ i18n("&Save As..."),
+ i18n("&Load..."))
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ setCaption(DImgInterface::defaultInterface()->getImageFileName() + TQString(" - ") + title);
+ showButton(User1, false);
+
+ d = new ImageDlgBasePriv;
+ d->parent = parent;
+ d->name = name;
+ d->tryAction = tryAction;
+
+ setButtonWhatsThis ( Default, i18n("<p>Reset all filter parameters to their default values.") );
+ setButtonWhatsThis ( User3, i18n("<p>Load all filter parameters from settings text file.") );
+ setButtonWhatsThis ( User2, i18n("<p>Save all filter parameters to settings text file.") );
+ showButton(User2, loadFileSettings);
+ showButton(User3, loadFileSettings);
+ showButton(Try, tryAction);
+
+ resize(configDialogSize(name + TQString(" Tool Dialog")));
+
+ // -------------------------------------------------------------
+
+ d->mainLayout = new TQGridLayout( plainPage(), 2, 1);
+ if (bannerFrame)
+ {
+ bannerFrame->reparent( plainPage(), TQPoint(0, 0) );
+ d->mainLayout->addMultiCellWidget(bannerFrame, 0, 0, 0, 1);
+ }
+
+ // -------------------------------------------------------------
+
+ d->hbox = new TQHBox(plainPage());
+ d->splitter = new TQSplitter(d->hbox);
+ d->splitter->setFrameStyle( TQFrame::NoFrame );
+ d->splitter->setFrameShadow( TQFrame::Plain );
+ d->splitter->setFrameShape( TQFrame::NoFrame );
+ d->splitter->setOpaqueResize(false);
+
+ d->mainLayout->addMultiCellWidget(d->hbox, 1, 2, 0, 1);
+ d->mainLayout->setColStretch(0, 10);
+ d->mainLayout->setRowStretch(2, 10);
+
+ kapp->restoreOverrideCursor();
+}
+
+ImageDlgBase::~ImageDlgBase()
+{
+ if (d->timer)
+ delete d->timer;
+
+ if (d->aboutData)
+ delete d->aboutData;
+
+ delete d->settingsSideBar;
+ delete d;
+}
+
+void ImageDlgBase::readSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->name + TQString(" Tool Dialog"));
+ if(config->hasKey("SplitterSizes"))
+ d->splitter->setSizes(config->readIntListEntry("SplitterSizes"));
+
+ readUserSettings();
+}
+
+void ImageDlgBase::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->name + TQString(" Tool Dialog"));
+ config->writeEntry("SplitterSizes", d->splitter->sizes());
+ config->sync();
+ saveDialogSize(d->name + TQString(" Tool Dialog"));
+}
+
+void ImageDlgBase::closeEvent(TQCloseEvent *e)
+{
+ writeSettings();
+ e->accept();
+}
+
+void ImageDlgBase::slotCancel()
+{
+ writeSettings();
+ done(Cancel);
+}
+
+void ImageDlgBase::slotOk()
+{
+ writeSettings();
+ writeUserSettings();
+ finalRendering();
+}
+
+void ImageDlgBase::slotDefault()
+{
+ resetValues();
+ slotEffect();
+}
+
+void ImageDlgBase::slotHelp()
+{
+ // If setAboutData() is called by plugin, well DigikamImagePlugins help is launched,
+ // else digiKam help. In this case, setHelp() method must be used to set anchor and handbook name.
+
+ if (d->aboutData)
+ TDEApplication::kApplication()->invokeHelp(d->name, "digikam");
+ else
+ KDialogBase::slotHelp();
+}
+
+void ImageDlgBase::setAboutData(TDEAboutData *about)
+{
+ d->aboutData = about;
+ TQPushButton *helpButton = actionButton( Help );
+ KHelpMenu* helpMenu = new KHelpMenu(this, d->aboutData, false);
+ helpMenu->menu()->removeItemAt(0);
+ helpMenu->menu()->insertItem(i18n("digiKam Handbook"), this, TQ_SLOT(slotHelp()), 0, -1, 0);
+ helpButton->setPopup( helpMenu->menu() );
+}
+
+void ImageDlgBase::setPreviewAreaWidget(TQWidget *w)
+{
+ w->reparent( d->splitter, TQPoint(0, 0) );
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred,
+ TQSizePolicy::Expanding,
+ 2, 1);
+ w->setSizePolicy(rightSzPolicy);
+}
+
+void ImageDlgBase::setUserAreaWidget(TQWidget *w)
+{
+ TQString sbName(d->name + TQString(" Image Plugin Sidebar"));
+ d->settingsSideBar = new Sidebar(d->hbox, sbName.ascii(), Sidebar::Right);
+ d->settingsSideBar->setSplitter(d->splitter);
+ d->settingsSideBar->appendTab(w, SmallIcon("configure"), i18n("Settings"));
+ d->settingsSideBar->loadViewState();
+
+ readSettings();
+}
+
+void ImageDlgBase::slotTimer()
+{
+ if (d->timer)
+ {
+ d->timer->stop();
+ delete d->timer;
+ }
+
+ d->timer = new TQTimer( this );
+ connect( d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotEffect()) );
+ d->timer->start(500, true);
+}
+
+} // NameSpace Digikam
+
diff --git a/src/libs/dialogs/imagedlgbase.h b/src/libs/dialogs/imagedlgbase.h
new file mode 100644
index 00000000..97dc6a35
--- /dev/null
+++ b/src/libs/dialogs/imagedlgbase.h
@@ -0,0 +1,98 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-23
+ * Description : simple plugins dialog without threadable
+ * filter interface. The dialog layout is
+ * designed to accept custom widgets in
+ * preview and settings area.
+ *
+ * Copyright 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEDLGBASE_H
+#define IMAGEDLGBASE_H
+
+// TQt includes
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQWidget;
+
+class TDEAboutData;
+
+namespace Digikam
+{
+
+class ImageDlgBasePriv;
+
+class DIGIKAM_EXPORT ImageDlgBase : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageDlgBase(TQWidget *parent, TQString title, TQString name,
+ bool loadFileSettings=true, bool tryAction=false, TQFrame* bannerFrame=0);
+ ~ImageDlgBase();
+
+ void setAboutData(TDEAboutData *about);
+ void setPreviewAreaWidget(TQWidget *w);
+ void setUserAreaWidget(TQWidget *w);
+
+protected slots:
+
+ virtual void slotDefault();
+ virtual void slotTimer();
+
+protected:
+
+ void closeEvent(TQCloseEvent *e);
+ virtual void finalRendering(){};
+ virtual void writeUserSettings(void){};
+ virtual void readUserSettings(void){ slotDefault(); };
+ virtual void resetValues(void){};
+
+private slots:
+
+ void slotHelp();
+ void slotCancel();
+ void slotOk();
+ virtual void slotEffect(){};
+
+private:
+
+ void readSettings(void);
+ void writeSettings(void);
+
+private:
+
+ ImageDlgBasePriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEDLGBASE */
diff --git a/src/libs/dialogs/imageguidedlg.cpp b/src/libs/dialogs/imageguidedlg.cpp
new file mode 100644
index 00000000..9713a872
--- /dev/null
+++ b/src/libs/dialogs/imageguidedlg.cpp
@@ -0,0 +1,597 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-07
+ * Description : A threaded filter plugin dialog with a preview
+ * image guide widget and a settings user area
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtimer.h>
+#include <tqspinbox.h>
+#include <tqsplitter.h>
+#include <tqhbox.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <tdeglobalsettings.h>
+#include <kprogress.h>
+#include <kcolorbutton.h>
+#include <tdeconfig.h>
+#include <kseparator.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "sidebar.h"
+#include "dimgthreadedfilter.h"
+#include "dimginterface.h"
+#include "imageguidedlg.h"
+#include "imageguidedlg.moc"
+
+namespace Digikam
+{
+
+class ImageGuideDlgPriv
+{
+public:
+
+ enum RunningMode
+ {
+ NoneRendering=0,
+ PreviewRendering,
+ FinalRendering
+ };
+
+ ImageGuideDlgPriv()
+ {
+ tryAction = false;
+ progress = true;
+ currentRenderingMode = NoneRendering;
+ parent = 0;
+ settings = 0;
+ timer = 0;
+ aboutData = 0;
+ guideColorBt = 0;
+ progressBar = 0;
+ guideSize = 0;
+ mainLayout = 0;
+ settingsLayout = 0;
+ hbox = 0;
+ settingsSideBar = 0;
+ splitter = 0;
+ }
+
+ bool tryAction;
+ bool progress;
+
+ int currentRenderingMode;
+
+ TQWidget *parent;
+ TQWidget *settings;
+
+ TQTimer *timer;
+
+ TQString name;
+
+ TQGridLayout *mainLayout;
+ TQGridLayout *settingsLayout;
+
+ TQSpinBox *guideSize;
+
+ TQHBox *hbox;
+
+ TQSplitter *splitter;
+
+ KProgress *progressBar;
+
+ KColorButton *guideColorBt;
+
+ TDEAboutData *aboutData;
+
+ Sidebar *settingsSideBar;
+};
+
+ImageGuideDlg::ImageGuideDlg(TQWidget* parent, TQString title, TQString name,
+ bool loadFileSettings, bool progress,
+ bool guideVisible, int guideMode, TQFrame* bannerFrame,
+ bool prevModeOptions, bool useImageSelection,
+ bool tryAction)
+ : KDialogBase(Plain, 0,
+ Help|Default|User1|User2|User3|Try|Ok|Cancel, Ok,
+ parent, 0, true, true,
+ i18n("&Abort"),
+ i18n("&Save As..."),
+ i18n("&Load..."))
+{
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ setCaption(DImgInterface::defaultInterface()->getImageFileName() + TQString(" - ") + title);
+
+ d = new ImageGuideDlgPriv;
+ d->parent = parent;
+ d->name = name;
+ d->progress = progress;
+ d->tryAction = tryAction;
+ m_threadedFilter = 0;
+ TQString whatsThis;
+
+ setButtonWhatsThis ( Default, i18n("<p>Reset all filter parameters to their default values.") );
+ setButtonWhatsThis ( User1, i18n("<p>Abort the current image rendering.") );
+ setButtonWhatsThis ( User3, i18n("<p>Load all filter parameters from settings text file.") );
+ setButtonWhatsThis ( User2, i18n("<p>Save all filter parameters to settings text file.") );
+ showButton(User2, loadFileSettings);
+ showButton(User3, loadFileSettings);
+ showButton(Try, tryAction);
+
+ resize(configDialogSize(name + TQString(" Tool Dialog")));
+
+ // -------------------------------------------------------------
+
+ d->mainLayout = new TQGridLayout( plainPage(), 2, 1);
+
+ if (bannerFrame)
+ {
+ bannerFrame->reparent( plainPage(), TQPoint(0, 0) );
+ d->mainLayout->addMultiCellWidget(bannerFrame, 0, 0, 0, 1);
+ }
+
+ // -------------------------------------------------------------
+
+ TQString desc;
+
+ if (guideVisible)
+ desc = i18n("<p>This is the the image filter effect preview. "
+ "If you move the mouse cursor on this area, "
+ "a vertical and horizontal dashed line will be draw "
+ "to guide you in adjusting the filter settings. "
+ "Press the left mouse button to freeze the dashed "
+ "line's position.");
+ else
+ desc = i18n("<p>This is the image filter effect preview.");
+
+ d->hbox = new TQHBox(plainPage());
+ d->splitter = new TQSplitter(d->hbox);
+ m_imagePreviewWidget = new ImageWidget(d->name, d->splitter, desc, prevModeOptions,
+ guideMode, guideVisible, useImageSelection);
+
+ d->splitter->setFrameStyle( TQFrame::NoFrame );
+ d->splitter->setFrameShadow( TQFrame::Plain );
+ d->splitter->setFrameShape( TQFrame::NoFrame );
+ d->splitter->setOpaqueResize(false);
+
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+ m_imagePreviewWidget->setSizePolicy(rightSzPolicy);
+
+ TQString sbName(d->name + TQString(" Image Plugin Sidebar"));
+ d->settingsSideBar = new Sidebar(d->hbox, sbName.ascii(), Sidebar::Right);
+ d->settingsSideBar->setSplitter(d->splitter);
+
+ d->mainLayout->addMultiCellWidget(d->hbox, 1, 2, 0, 1);
+ d->mainLayout->setColStretch(0, 10);
+ d->mainLayout->setRowStretch(2, 10);
+
+ // -------------------------------------------------------------
+
+ d->settings = new TQWidget(plainPage());
+ d->settingsLayout = new TQGridLayout( d->settings, 1, 0);
+ TQVBoxLayout *vLayout = new TQVBoxLayout( spacingHint() );
+
+ // -------------------------------------------------------------
+
+ TQWidget *gboxGuideSettings = new TQWidget(d->settings);
+ TQGridLayout* grid = new TQGridLayout( gboxGuideSettings, 2, 2, marginHint(), spacingHint());
+ KSeparator *line = new KSeparator(TQt::Horizontal, gboxGuideSettings);
+ grid->addMultiCellWidget(line, 0, 0, 0, 2);
+
+ TQLabel *label5 = new TQLabel(i18n("Guide color:"), gboxGuideSettings);
+ d->guideColorBt = new KColorButton( TQColor( TQt::red ), gboxGuideSettings );
+ TQWhatsThis::add( d->guideColorBt, i18n("<p>Set here the color used to draw guides dashed-lines."));
+ grid->addMultiCellWidget(label5, 1, 1, 0, 0);
+ grid->addMultiCellWidget(d->guideColorBt, 1, 1, 2, 2);
+
+ TQLabel *label6 = new TQLabel(i18n("Guide width:"), gboxGuideSettings);
+ d->guideSize = new TQSpinBox( 1, 5, 1, gboxGuideSettings);
+ TQWhatsThis::add( d->guideSize, i18n("<p>Set here the width in pixels used to draw guides dashed-lines."));
+ grid->addMultiCellWidget(label6, 2, 2, 0, 0);
+ grid->addMultiCellWidget(d->guideSize, 2, 2, 2, 2);
+ grid->setColStretch(1, 10);
+
+ if (guideVisible) gboxGuideSettings->show();
+ else gboxGuideSettings->hide();
+
+ vLayout->addWidget(gboxGuideSettings);
+
+ TQHBox *hbox = new TQHBox(d->settings);
+ TQLabel *space1 = new TQLabel(hbox);
+ space1->setFixedWidth(spacingHint());
+ d->progressBar = new KProgress(100, hbox);
+ d->progressBar->setMaximumHeight( fontMetrics().height() );
+ TQWhatsThis::add(d->progressBar ,i18n("<p>This is the percentage of the task which has been completed up to this point."));
+ d->progressBar->setValue(0);
+ setProgressVisible(false);
+ TQLabel *space2 = new TQLabel(hbox);
+ space2->setFixedWidth(spacingHint());
+
+ vLayout->addWidget(hbox);
+ vLayout->addStretch(10);
+
+ d->settingsLayout->addMultiCellLayout(vLayout, 1, 1, 0, 0);
+
+ d->settingsSideBar->appendTab(d->settings, SmallIcon("configure"), i18n("Settings"));
+ d->settingsSideBar->loadViewState();
+
+ // Reading splitter sizes here prevent flicker effect in dialog.
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->name + TQString(" Tool Dialog"));
+ if(config->hasKey("SplitterSizes"))
+ d->splitter->setSizes(config->readIntListEntry("SplitterSizes"));
+
+ // -------------------------------------------------------------
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotInit()));
+ kapp->restoreOverrideCursor();
+}
+
+ImageGuideDlg::~ImageGuideDlg()
+{
+ if (d->timer)
+ delete d->timer;
+
+ if (m_threadedFilter)
+ delete m_threadedFilter;
+
+ if (d->aboutData)
+ delete d->aboutData;
+
+ delete d->settingsSideBar;
+ delete d;
+}
+
+void ImageGuideDlg::readSettings(void)
+{
+ TQColor defaultGuideColor(TQt::red);
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->name + TQString(" Tool Dialog"));
+ d->guideColorBt->setColor(config->readColorEntry("Guide Color", &defaultGuideColor));
+ d->guideSize->setValue(config->readNumEntry("Guide Width", 1));
+ m_imagePreviewWidget->slotChangeGuideSize(d->guideSize->value());
+ m_imagePreviewWidget->slotChangeGuideColor(d->guideColorBt->color());
+}
+
+void ImageGuideDlg::writeSettings(void)
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->name + TQString(" Tool Dialog"));
+ config->writeEntry( "Guide Color", d->guideColorBt->color() );
+ config->writeEntry( "Guide Width", d->guideSize->value() );
+ config->writeEntry( "SplitterSizes", d->splitter->sizes() );
+ config->sync();
+ saveDialogSize(d->name + TQString(" Tool Dialog"));
+}
+
+void ImageGuideDlg::slotInit()
+{
+ readSettings();
+ // Reset values to defaults.
+ TQTimer::singleShot(0, this, TQ_SLOT(readUserSettings()));
+
+ if (!d->tryAction)
+ {
+ connect(m_imagePreviewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotResized()));
+ }
+
+ connect(d->guideColorBt, TQ_SIGNAL(changed(const TQColor &)),
+ m_imagePreviewWidget, TQ_SLOT(slotChangeGuideColor(const TQColor &)));
+
+ connect(d->guideSize, TQ_SIGNAL(valueChanged(int)),
+ m_imagePreviewWidget, TQ_SLOT(slotChangeGuideSize(int)));
+}
+
+void ImageGuideDlg::setUserAreaWidget(TQWidget *w)
+{
+ w->reparent( d->settings, TQPoint(0, 0) );
+ TQVBoxLayout *vLayout = new TQVBoxLayout( spacingHint() );
+ vLayout->addWidget(w);
+ d->settingsLayout->addMultiCellLayout(vLayout, 0, 0, 0, 0);
+}
+
+void ImageGuideDlg::setAboutData(TDEAboutData *about)
+{
+ d->aboutData = about;
+ TQPushButton *helpButton = actionButton( Help );
+ KHelpMenu* helpMenu = new KHelpMenu(this, d->aboutData, false);
+ helpMenu->menu()->removeItemAt(0);
+ helpMenu->menu()->insertItem(i18n("digiKam Handbook"), this, TQ_SLOT(slotHelp()), 0, -1, 0);
+ helpButton->setPopup( helpMenu->menu() );
+}
+
+void ImageGuideDlg::setProgressVisible(bool v)
+{
+ if (v)
+ d->progressBar->show();
+ else
+ d->progressBar->hide();
+}
+
+void ImageGuideDlg::abortPreview()
+{
+ d->currentRenderingMode = ImageGuideDlgPriv::NoneRendering;
+ d->progressBar->setValue(0);
+ setProgressVisible(false);
+ enableButton(Ok, true);
+ enableButton(User1, false);
+ enableButton(User2, true);
+ enableButton(User3, true);
+ enableButton(Try, true);
+ enableButton(Default, true);
+ renderingFinished();
+}
+
+void ImageGuideDlg::slotTry()
+{
+ slotEffect();
+}
+
+void ImageGuideDlg::slotResized(void)
+{
+ if (d->currentRenderingMode == ImageGuideDlgPriv::FinalRendering)
+ {
+ m_imagePreviewWidget->update();
+ return;
+ }
+ else if (d->currentRenderingMode == ImageGuideDlgPriv::PreviewRendering)
+ {
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+ }
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotEffect()));
+}
+
+void ImageGuideDlg::slotUser1()
+{
+ if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering)
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+}
+
+void ImageGuideDlg::slotDefault()
+{
+ resetValues();
+ slotEffect();
+}
+
+void ImageGuideDlg::slotCancel()
+{
+ if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering)
+ {
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+
+ kapp->restoreOverrideCursor();
+ }
+
+ writeSettings();
+ done(Cancel);
+}
+
+void ImageGuideDlg::closeEvent(TQCloseEvent *e)
+{
+ if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering)
+ {
+ if (m_threadedFilter)
+ m_threadedFilter->stopComputation();
+
+ kapp->restoreOverrideCursor();
+ }
+
+ writeSettings();
+ e->accept();
+}
+
+void ImageGuideDlg::slotHelp()
+{
+ // If setAboutData() is called by plugin, well DigikamImagePlugins help is lauched,
+ // else digiKam help. In this case, setHelp() method must be used to set anchor and handbook name.
+
+ if (d->aboutData)
+ TDEApplication::kApplication()->invokeHelp(d->name, "digikam");
+ else
+ KDialogBase::slotHelp();
+}
+
+void ImageGuideDlg::slotTimer()
+{
+ if (d->timer)
+ {
+ d->timer->stop();
+ delete d->timer;
+ }
+
+ d->timer = new TQTimer( this );
+ connect( d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotEffect()) );
+ d->timer->start(500, true);
+}
+
+void ImageGuideDlg::slotEffect()
+{
+ // Computation already in process.
+ if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering)
+ return;
+
+ d->currentRenderingMode = ImageGuideDlgPriv::PreviewRendering;
+ DDebug() << "Preview " << d->name << " started..." << endl;
+
+ enableButton(Ok, false);
+ enableButton(User1, true);
+ enableButton(User2, false);
+ enableButton(User3, false);
+ enableButton(Default, false);
+ enableButton(Try, false);
+ d->progressBar->setValue(0);
+ if (d->progress) setProgressVisible(true);
+
+ if (m_threadedFilter)
+ {
+ delete m_threadedFilter;
+ m_threadedFilter = 0;
+ }
+
+ prepareEffect();
+}
+
+void ImageGuideDlg::slotOk()
+{
+ d->currentRenderingMode = ImageGuideDlgPriv::FinalRendering;
+ DDebug() << "Final " << d->name << " started..." << endl;
+ writeSettings();
+ writeUserSettings();
+
+ enableButton(Ok, false);
+ enableButton(User1, false);
+ enableButton(User2, false);
+ enableButton(User3, false);
+ enableButton(Default, false);
+ enableButton(Try, false);
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ d->progressBar->setValue(0);
+
+ if (m_threadedFilter)
+ {
+ delete m_threadedFilter;
+ m_threadedFilter = 0;
+ }
+
+ prepareFinal();
+}
+
+void ImageGuideDlg::customEvent(TQCustomEvent *event)
+{
+ if (!event) return;
+
+ DImgThreadedFilter::EventData *ed = (DImgThreadedFilter::EventData*) event->data();
+
+ if (!ed) return;
+
+ if (ed->starting) // Computation in progress !
+ {
+ d->progressBar->setValue(ed->progress);
+ }
+ else
+ {
+ if (ed->success) // Computation Completed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case ImageGuideDlgPriv::PreviewRendering:
+ {
+ DDebug() << "Preview " << d->name << " completed..." << endl;
+ putPreviewData();
+ abortPreview();
+ break;
+ }
+
+ case ImageGuideDlgPriv::FinalRendering:
+ {
+ DDebug() << "Final" << d->name << " completed..." << endl;
+ putFinalData();
+ kapp->restoreOverrideCursor();
+ accept();
+ break;
+ }
+ }
+ }
+ else // Computation Failed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case ImageGuideDlgPriv::PreviewRendering:
+ {
+ DDebug() << "Preview " << d->name << " failed..." << endl;
+ // abortPreview() must be call here for set progress bar to 0 properly.
+ abortPreview();
+ break;
+ }
+
+ case ImageGuideDlgPriv::FinalRendering:
+ break;
+ }
+ }
+ }
+
+ delete ed;
+}
+
+// Backport KDialog::keyPressEvent() implementation from KDELibs to ignore Enter/Return Key events
+// to prevent any conflicts between dialog keys events and SpinBox keys events.
+
+void ImageGuideDlg::keyPressEvent(TQKeyEvent *e)
+{
+ if ( e->state() == 0 )
+ {
+ switch ( e->key() )
+ {
+ case Key_Escape:
+ e->accept();
+ reject();
+ break;
+ case Key_Enter:
+ case Key_Return:
+ e->ignore();
+ break;
+ default:
+ e->ignore();
+ return;
+ }
+ }
+ else
+ {
+ // accept the dialog when Ctrl-Return is pressed
+ if ( e->state() == ControlButton &&
+ (e->key() == Key_Return || e->key() == Key_Enter) )
+ {
+ e->accept();
+ accept();
+ }
+ else
+ {
+ e->ignore();
+ }
+ }
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dialogs/imageguidedlg.h b/src/libs/dialogs/imageguidedlg.h
new file mode 100644
index 00000000..e6841c43
--- /dev/null
+++ b/src/libs/dialogs/imageguidedlg.h
@@ -0,0 +1,123 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-07
+ * Description : A threaded filter plugin dialog with a preview
+ * image guide widget and a settings user area
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEGUIDEDLG_H
+#define IMAGEGUIDEDLG_H
+
+// TQt includes
+
+#include <tqstring.h>
+
+// KDE include.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "imagewidget.h"
+#include "imageguidewidget.h"
+#include "digikam_export.h"
+
+class TQFrame;
+
+class TDEAboutData;
+
+namespace Digikam
+{
+
+class ImageGuideDlgPriv;
+class DImgThreadedFilter;
+
+class DIGIKAM_EXPORT ImageGuideDlg : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageGuideDlg(TQWidget* parent, TQString title, TQString name,
+ bool loadFileSettings=false, bool progress=true,
+ bool guideVisible=true,
+ int guideMode=ImageGuideWidget::HVGuideMode,
+ TQFrame* bannerFrame=0,
+ bool prevModeOptions=false,
+ bool useImageSelection=false,
+ bool tryAction=false);
+ ~ImageGuideDlg();
+
+ void setAboutData(TDEAboutData *about);
+ void setUserAreaWidget(TQWidget *w);
+ void setProgressVisible(bool v);
+
+public:
+
+ DImgThreadedFilter *m_threadedFilter;
+
+ ImageWidget *m_imagePreviewWidget;
+
+public slots:
+
+ void slotTimer();
+ void slotEffect();
+ void slotOk();
+ void slotTry();
+
+protected slots:
+
+ virtual void slotCancel();
+ virtual void slotUser1();
+ virtual void slotDefault();
+ virtual void slotInit();
+ virtual void readUserSettings(void){ slotDefault(); };
+
+private slots:
+
+ void slotResized();
+ void slotHelp();
+
+protected:
+
+ void closeEvent(TQCloseEvent *e);
+ void customEvent(TQCustomEvent *event);
+ void abortPreview(void);
+ void readSettings(void);
+ void writeSettings(void);
+ void keyPressEvent(TQKeyEvent *e);
+
+ virtual void writeUserSettings(void){};
+ virtual void resetValues(void){};
+ virtual void prepareEffect(void){};
+ virtual void prepareFinal(void){};
+ virtual void putPreviewData(void){};
+ virtual void putFinalData(void){};
+ virtual void renderingFinished(void){};
+
+private:
+
+ ImageGuideDlgPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEGUIDEDLG_H */
diff --git a/src/libs/dialogs/rawcameradlg.cpp b/src/libs/dialogs/rawcameradlg.cpp
new file mode 100644
index 00000000..298b47d4
--- /dev/null
+++ b/src/libs/dialogs/rawcameradlg.cpp
@@ -0,0 +1,178 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-04-07
+ * Description : Raw camera list dialog
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqstringlist.h>
+#include <tqstring.h>
+#include <tqlabel.h>
+#include <tqlistview.h>
+#include <tqheader.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdeaboutdata.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "searchtextbar.h"
+#include "rawcameradlg.h"
+#include "rawcameradlg.moc"
+
+namespace Digikam
+{
+
+class RawCameraDlgPriv
+{
+public:
+
+ RawCameraDlgPriv()
+ {
+ listView = 0;
+ searchBar = 0;
+ }
+
+ TQListView *listView;
+
+ SearchTextBar *searchBar;
+};
+
+RawCameraDlg::RawCameraDlg(TQWidget *parent)
+ : KDialogBase(parent, 0, true, TQString(), Help|Ok, Ok, true)
+{
+ setHelp("digitalstillcamera.anchor", "digikam");
+ setCaption(i18n("List of supported RAW cameras"));
+
+ d = new RawCameraDlgPriv;
+
+ TQWidget *page = makeMainWidget();
+ TQGridLayout* grid = new TQGridLayout(page, 2, 2, 0, spacingHint());
+
+#if KDCRAW_VERSION < 0x000106
+ TQStringList list = KDcrawIface::DcrawBinary::instance()->supportedCamera();
+ TQString dcrawVer = KDcrawIface::DcrawBinary::instance()->internalVersion();
+#else
+ TQStringList list = KDcrawIface::KDcraw::supportedCamera();
+ TQString librawVer = KDcrawIface::KDcraw::librawVersion();
+#endif
+ TQString KDcrawVer = KDcrawIface::KDcraw::version();
+
+ // --------------------------------------------------------
+
+ TQLabel *logo = new TQLabel(page);
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+
+ if (TDEApplication::kApplication()->aboutData()->appName() == TQString("digikam"))
+ logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 96, TDEIcon::DefaultState, 0, true));
+ else
+ logo->setPixmap(iconLoader->loadIcon("showfoto", TDEIcon::NoGroup, 96, TDEIcon::DefaultState, 0, true));
+
+ // --------------------------------------------------------
+
+ TQLabel *header = new TQLabel(page);
+#if KDCRAW_VERSION < 0x000106
+ header->setText(i18n("<p>Using KDcraw library version %1"
+ "<p>Using Dcraw program version %2"
+ "<p>%3 models in the list")
+ .arg(KDcrawVer).arg(dcrawVer).arg(list.count()));
+#else
+ header->setText(i18n("<p>Using KDcraw library version %1"
+ "<p>Using LibRaw version %2"
+ "<p>%3 models in the list")
+ .arg(KDcrawVer).arg(librawVer).arg(list.count()));
+#endif
+
+ // --------------------------------------------------------
+
+ d->searchBar = new SearchTextBar(page, "RawCameraDlgSearchBar");
+ d->listView = new TQListView(page);
+ d->listView->addColumn("Camera Model"); // Header is hiden. No i18n here.
+ d->listView->setSorting(1);
+ d->listView->header()->hide();
+ d->listView->setResizeMode(TQListView::LastColumn);
+
+ for (TQStringList::Iterator it = list.begin() ; it != list.end() ; ++it)
+ new TQListViewItem(d->listView, *it);
+
+ // --------------------------------------------------------
+
+
+ grid->addMultiCellWidget(logo, 0, 0, 0, 0);
+ grid->addMultiCellWidget(header, 0, 0, 1, 2);
+ grid->addMultiCellWidget(d->listView, 1, 1, 0, 2);
+ grid->addMultiCellWidget(d->searchBar, 2, 2, 0, 2);
+ grid->setColStretch(1, 10);
+ grid->setRowStretch(1, 10);
+
+ // --------------------------------------------------------
+
+ connect(d->searchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ this, TQ_SLOT(slotSearchTextChanged(const TQString&)));
+
+ resize(500, 500);
+}
+
+RawCameraDlg::~RawCameraDlg()
+{
+ delete d;
+}
+
+void RawCameraDlg::slotSearchTextChanged(const TQString& filter)
+{
+ bool query = false;
+ TQString search = filter.lower();
+
+ TQListViewItemIterator it(d->listView);
+
+ for ( ; it.current(); ++it )
+ {
+ TQListViewItem *item = it.current();
+
+ if (item->text(0).lower().contains(search))
+ {
+ query = true;
+ item->setVisible(true);
+ }
+ else
+ {
+ item->setVisible(false);
+ }
+ }
+
+ d->searchBar->slotSearchResult(query);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dialogs/rawcameradlg.h b/src/libs/dialogs/rawcameradlg.h
new file mode 100644
index 00000000..41d4a7de
--- /dev/null
+++ b/src/libs/dialogs/rawcameradlg.h
@@ -0,0 +1,61 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-04-07
+ * Description : Raw camera list dialog
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAWCAMERADLG_H
+#define RAWCAMERADLG_H
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class RawCameraDlgPriv;
+
+class DIGIKAM_EXPORT RawCameraDlg : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ RawCameraDlg(TQWidget* parent);
+ ~RawCameraDlg();
+
+private slots:
+
+ void slotSearchTextChanged(const TQString&);
+
+private:
+
+ RawCameraDlgPriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif // RAWCAMERADLG_H
diff --git a/src/libs/dimg/Makefile.am b/src/libs/dimg/Makefile.am
new file mode 100644
index 00000000..23e746a2
--- /dev/null
+++ b/src/libs/dimg/Makefile.am
@@ -0,0 +1,27 @@
+SUBDIRS = loaders filters
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdimg.la
+
+libdimg_la_SOURCES = dimg.cpp dimgscale.cpp dcolor.cpp dcolorcomposer.cpp ddebug.cpp
+
+libdimg_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+libdimg_la_LIBADD = $(top_builddir)/src/libs/histogram/libhistogram.la \
+ $(top_builddir)/src/libs/levels/liblevels.la \
+ $(top_builddir)/src/libs/curves/libcurves.la \
+ $(top_builddir)/src/libs/whitebalance/libwhitebalance.la \
+ $(top_builddir)/src/libs/dimg/loaders/libdimgloaders.la \
+ $(top_builddir)/src/libs/dimg/filters/libdimgfilters.la \
+ $(top_builddir)/src/libs/dmetadata/libdmetadata.la \
+ $(LIBKDCRAW_LIBS) $(LCMS_LIBS)
+
+INCLUDES = $(all_includes) $(LIBKDCRAW_CFLAGS) \
+ -I$(top_srcdir)/src/libs/dimg/loaders \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/digikam
+
+digikaminclude_HEADERS = dimg.h dcolor.h dcolorpixelaccess.h dcolorcomposer.h \
+ dcolorblend.h ddebug.h
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/dimg/README b/src/libs/dimg/README
new file mode 100644
index 00000000..4b13f3fe
--- /dev/null
+++ b/src/libs/dimg/README
@@ -0,0 +1,95 @@
+
+---------------------------------------------------------------------------
+WHAT'S DIMG FRAMEWORK ?
+
+
+Original post from Renchi Raju on digiKam mailing list :
+
+[Digikam-devel] Imaging Library
+Renchi Raju renchi at pooh.tam.uiuc.edu
+Sun Jun 19 23:15:20 CEST 2005
+
+as you all know, we have been using imlib2 library for the 0.7 series. we
+have had a fairly large number of bugreports because of imlib2 (not
+imlib2's fault, but because of tdelibs's handling of ltdl). In addition,
+some imlib2 bugreports I reported to upstream have gone unfixed for long
+time now.
+
+Also, we need to think about 16bit imaging support. this won't come from
+imlib2 and neither from tqt. with qt4 there was hope of 16bit image
+support, but trolltech has made it clear that imaging apps forms only 0.1%
+of their customer base and they are not interested in providing custom
+support for them. so the only solution I see (without depending on
+imagemagick) is to roll our own library.
+
+i have been working on a imaging library for digikam, its called DImg.
+it doesn't aim to be a complete imaging library; it uses TQImage for
+rendering and for loading files which are not supported natively by it.
+some of the working/planned features:
+
+* Native Image Loaders, for some imageformats which are of interest to
+us: JPEG (complete), TIFF (a rudimentary one currently), PNG (planned), PPM
+(planned). for the rest tqt's qimage is used.
+
+* Metadata preservation: when a file is loaded, its metadata like XMP,
+IPTC, EXIF, JFIF are read and held in memory. now when you save back the
+file to the original file or to a different file, the metadata is
+automatically written (How, we have been handling it currently with
+imlib2 is on saving a file: we save the file to a temporary file, reread
+the exif info from the original file and then write to a second temporary
+file.)
+
+* Explicitly Shared Container format (see tqt docs): this is necessary for
+performance reasons.
+
+* 8bit and 16bit support: if the file format is 16 bit, it will load up
+the image in 16bit format (currently only 16bit tiff support) and all
+operations are done in 16 bit format, except when the rendering to screen
+is done, when its converted on the fly to a temporary 8bit image and then
+rendered.
+
+* Basic image manipulation: rotate, flip, color modifications, crop,
+scale (this has been ported from Imlib2 - originally ported by Mosfet, I
+added 16 bit scaling support and support for scaling of only a section of
+the image)
+
+* Rendering to Pixmap: using TQImage/QPixmap. (see above for rendering of
+16bit images).
+
+* Pixel format: the pixel format is different from TQImage/Imlib2 pixel
+format. In TQImage/Imlib2 the pixel data is stored as unsigned ints and to
+access the individual colors you need to use bit-shifting to ensure
+endian correctness. in DImg, the pixel data is stored as unsigned char.
+the color layout is B,G,R,A (blue, green, red, alpha)
+
+for 8bit images: you can access individual color components like this:
+
+uchar* pixels = image.bits();
+for (int i=0; i<image.width()*image.height(); i++)
+{
+ pixel[0] // blue
+ pixel[1] // green
+ pixel[2] // red
+ pixel[3] // alpha
+
+ pixel += 4; // go to next pixel
+}
+
+and for 16bit images:
+
+ushort* pixels = (ushort*)image.bits();
+for (int i=0; i<image.width()*image.height(); i++)
+{
+ pixel[0] // blue
+ pixel[1] // green
+ pixel[2] // red
+ pixel[3] // alpha
+
+ pixel += 4; // go to next pixel
+}
+
+the above is true for both big and little endian platforms. What this also
+means is that the pixel format is different from that of TQImage for big
+endian machines. Functions are provided if you want to get a copy of the
+DImg as a TQImage.
+
diff --git a/src/libs/dimg/dcolor.cpp b/src/libs/dimg/dcolor.cpp
new file mode 100644
index 00000000..5a363c78
--- /dev/null
+++ b/src/libs/dimg/dcolor.cpp
@@ -0,0 +1,281 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-02
+ * Description : 8-16 bits color container.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * RGB<->HLS transformation algorithms are inspired from methods
+ * describe at this url :
+ * http://www.paris-pc-gis.com/MI_Enviro/Colors/color_models.htm
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dcolor.h"
+
+namespace Digikam
+{
+
+/*
+DColor::DColor(const DColor& color)
+{
+ m_red = color.m_red;
+ m_green = color.m_green;
+ m_blue = color.m_blue;
+ m_alpha = color.m_alpha;
+ m_sixteenBit = color.m_sixteenBit;
+}
+*/
+
+DColor::DColor(const TQColor& color, bool sixteenBit)
+{
+ // initialize as eight bit
+ m_red = color.red();
+ m_green = color.green();
+ m_blue = color.blue();
+ m_alpha = 255;
+ m_sixteenBit = false;
+
+ // convert to sixteen bit if requested
+ if (sixteenBit)
+ convertToSixteenBit();
+}
+
+/*
+DColor& DColor::operator=(const DColor& color)
+{
+ m_red = color.m_red;
+ m_green = color.m_green;
+ m_blue = color.m_blue;
+ m_alpha = color.m_alpha;
+ m_sixteenBit = color.m_sixteenBit;
+ return *this;
+}
+*/
+
+TQColor DColor::getTQColor() const
+{
+ if (m_sixteenBit)
+ {
+ DColor eightBit(*this);
+ eightBit.convertToEightBit();
+ return eightBit.getTQColor();
+ }
+
+ return (TQColor(m_red, m_green, m_blue));
+}
+
+void DColor::convertToSixteenBit()
+{
+ if (m_sixteenBit)
+ return;
+
+ m_red = (m_red + 1) * 256 - 1;
+ m_green = (m_green + 1) * 256 - 1;
+ m_blue = (m_blue + 1) * 256 - 1;
+ m_alpha = (m_alpha + 1) * 256 - 1;
+ m_sixteenBit = true;
+}
+
+void DColor::convertToEightBit()
+{
+ if (!m_sixteenBit)
+ return;
+
+ m_red = (m_red + 1) / 256 - 1;
+ m_green = (m_green + 1) / 256 - 1;
+ m_blue = (m_blue + 1) / 256 - 1;
+ m_alpha = (m_alpha + 1) / 256 - 1;
+ m_sixteenBit = false;
+}
+
+
+void DColor::getHSL(int* h, int* s, int* l) const
+{
+ double min;
+ double max;
+ double red;
+ double green;
+ double blue;
+ double delta;
+ double sum;
+ double hue, sat, lig;
+
+ double range = m_sixteenBit ? 65535.0 : 255.0;
+
+ red = m_red / range;
+ green = m_green / range;
+ blue = m_blue / range;
+
+ if (red > green)
+ {
+ if (red > blue)
+ max = red;
+ else
+ max = blue;
+
+ if (green < blue)
+ min = green;
+ else
+ min = blue;
+ }
+ else
+ {
+ if (green > blue)
+ max = green;
+ else
+ max = blue;
+
+ if (red < blue)
+ min = red;
+ else
+ min = blue;
+ }
+
+ sum = max + min;
+
+ lig = sum / 2;
+ sat = 0;
+ hue = 0;
+
+ if (max != min)
+ {
+ delta = max - min;
+
+ if (lig <= 0.5)
+ sat = delta / sum;
+ else
+ sat = delta / (2 - sum);
+
+ if (red == max)
+ hue = (green - blue) / delta;
+ else if (green == max)
+ hue = 2 + (blue - red) / delta;
+ else if (blue == max)
+ hue = 4 + (red - green) / delta;
+
+ if (hue < 0)
+ hue += 6;
+ if (hue > 6)
+ hue -= 6;
+
+ hue *= 60;
+ }
+
+ *h = lround(hue * range / 360.0);
+ *s = lround(sat * range);
+ *l = lround(lig * range);
+}
+
+void DColor::setRGB(int h, int s, int l, bool sixteenBit)
+{
+ double hue;
+ double lightness;
+ double saturation;
+ double m1, m2;
+ double r, g, b;
+
+ double range = m_sixteenBit ? 65535.0 : 255.0;
+
+ if (s == 0)
+ {
+ m_red = l;
+ m_green = l;
+ m_blue = l;
+ }
+ else
+ {
+ hue = (double)(h * 360.0 / range);
+ lightness = (double)(l / range);
+ saturation = (double)(s / range);
+
+ if (lightness <= 0.5)
+ m2 = lightness * (1 + saturation);
+ else
+ m2 = lightness + saturation - lightness * saturation;
+
+ m1 = 2 * lightness - m2;
+
+ double mh;
+
+ mh = hue + 120;
+ while (mh > 360)
+ mh -= 360;
+ while (mh < 0)
+ mh += 360;
+
+ if (mh < 60)
+ r = m1 + (m2 - m1) * mh / 60;
+ else if (mh < 180)
+ r = m2;
+ else if (mh < 240)
+ r = m1 + (m2 - m1) * (240 - mh) / 60;
+ else
+ r = m1;
+
+ mh = hue;
+ while (mh > 360)
+ mh -= 360;
+ while (mh < 0)
+ mh += 360;
+
+ if (mh < 60)
+ g = m1 + (m2 - m1) * mh / 60;
+ else if (mh < 180)
+ g = m2;
+ else if (mh < 240)
+ g = m1 + (m2 - m1) * (240 - mh) / 60;
+ else
+ g = m1;
+
+ mh = hue - 120;
+ while (mh > 360)
+ mh -= 360;
+ while (mh < 0)
+ mh += 360;
+
+ if (mh < 60)
+ b = m1 + (m2 - m1) * mh / 60;
+ else if (mh < 180)
+ b = m2;
+ else if (mh < 240)
+ b = m1 + (m2 - m1) * (240 - mh) / 60;
+ else
+ b = m1;
+
+ m_red = lround(r * range);
+ m_green = lround(g * range);
+ m_blue = lround(b * range);
+ }
+
+ m_sixteenBit = sixteenBit;
+
+ // Fully opaque color.
+ if (m_sixteenBit)
+ m_alpha = 65535;
+ else
+ m_alpha = 255;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/dcolor.h b/src/libs/dimg/dcolor.h
new file mode 100644
index 00000000..16a34f55
--- /dev/null
+++ b/src/libs/dimg/dcolor.h
@@ -0,0 +1,152 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-02
+ * Description : 8-16 bits color container.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DCOLOR_H
+#define DCOLOR_H
+
+// TQt includes.
+
+#include <tqcolor.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DColor
+{
+public:
+
+ /** Initialize with default value, fully transparent eight bit black */
+ DColor()
+ : m_red(0), m_green(0), m_blue(0), m_alpha(0), m_sixteenBit(false)
+ {};
+
+ /** Read value from data. Equivalent to setColor() */
+ DColor(const uchar *data, bool sixteenBit = false)
+ { setColor(data, sixteenBit); }
+
+ /** Initialize with given RGBA values */
+ DColor(int red, int green, int blue, int alpha, bool sixteenBit)
+ : m_red(red), m_green(green), m_blue(blue), m_alpha(alpha), m_sixteenBit(sixteenBit)
+ {};
+
+ /** Read values from TQColor, convert to sixteenBit of sixteenBit is true */
+ DColor(const TQColor& color, bool sixteenBit=false);
+
+ // Use default copy constructor, assignment operator and destructor
+
+ /** Read color values as RGBA from the given memory location.
+ If sixteenBit is false, 4 bytes are read.
+ If sixteenBit is true, 8 bytes are read.
+ Inline method.
+ */
+ void setColor(const uchar *data, bool sixteenBit = false);
+
+ /** Write the values of this color to the given memory location.
+ If sixteenBit is false, 4 bytes are written.
+ If sixteenBit is true, 8 bytes are written.
+ Inline method.
+ */
+ void setPixel(uchar *data) const;
+
+ int red () const { return m_red; }
+ int green() const { return m_green; }
+ int blue () const { return m_blue; }
+ int alpha() const { return m_alpha; }
+ bool sixteenBit() const { return m_sixteenBit; }
+
+ void setRed (int red) { m_red = red; }
+ void setGreen(int green) { m_green = green; }
+ void setBlue (int blue) { m_blue = blue; }
+ void setAlpha(int alpha) { m_alpha = alpha; }
+ void setSixteenBit(bool sixteenBit) { m_sixteenBit = sixteenBit; }
+
+ TQColor getTQColor() const;
+
+ /** Convert the color values of this color to and from sixteen bit
+ and set the sixteenBit value accordingly
+ */
+ void convertToSixteenBit();
+ void convertToEightBit();
+
+ /** Premultiply and demultiply this color.
+ DImg stores the color non-premultiplied.
+ Inline methods.
+ */
+ void premultiply();
+ void demultiply();
+
+ /** Return the current RGB color values of this color
+ in the HSL color space.
+ Alpha is ignored for the conversion.
+ */
+ void getHSL(int* h, int* s, int* l) const;
+
+ /** Set the RGB color values of this color
+ to the given HSL values converted to RGB.
+ Alpha is set to be fully opaque.
+ sixteenBit determines both how the HSL values are interpreted
+ and the sixteenBit value of this color after this operation.
+ */
+ void setRGB(int h, int s, int l, bool sixteenBit);
+
+private:
+
+ int m_red;
+ int m_green;
+ int m_blue;
+ int m_alpha;
+
+ bool m_sixteenBit;
+
+public:
+
+ // Inline alpha blending helper functions.
+ // These functions are used by DColorComposer.
+ // Look at that code to learn how to use them for
+ // composition if you want to use them in optimized code.
+ void blendZero();
+ void blendAlpha8(int alpha);
+ void blendInvAlpha8(int alpha);
+ void blendAlpha16(int alpha);
+ void blendInvAlpha16(int alpha);
+ void premultiply16(int alpha);
+ void premultiply8(int alpha);
+ void demultiply16(int alpha);
+ void demultiply8(int alpha);
+ void blendAdd(const DColor &src);
+ void blendClamp8();
+ void blendClamp16();
+};
+
+} // NameSpace Digikam
+
+
+// Inline methods
+#include "dcolorpixelaccess.h"
+#include "dcolorblend.h"
+
+#endif /* DCOLOR_H */
diff --git a/src/libs/dimg/dcolorblend.h b/src/libs/dimg/dcolorblend.h
new file mode 100644
index 00000000..b68322ba
--- /dev/null
+++ b/src/libs/dimg/dcolorblend.h
@@ -0,0 +1,179 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-03-01
+ * Description : DColor methods for blending
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Inspired by DirectFB, src/gfx/generic/generic.c:
+
+/*
+ (c) Copyright 2000-2002 convergence integrated media GmbH <curanz@convergence.de>
+ (c) Copyright 2002-2005 convergence GmbH.
+
+ All rights reserved.
+
+ Written by Denis Oliver Kropp <dok@directfb.org>,
+ Andreas Hundt <andi@fischlustig.de>,
+ Sven Neumann <neo@directfb.org>,
+ Ville Syrjl <syrjala@sci.fi> and
+ Claudio Ciccani <klan@users.sf.net>.
+
+*/
+
+#ifndef DCOLORBLEND_H
+#define DCOLORBLEND_H
+
+namespace Digikam
+{
+
+inline void DColor::premultiply()
+{
+ if (sixteenBit())
+ premultiply16(alpha());
+ else
+ premultiply8(alpha());
+}
+
+inline void DColor::demultiply()
+{
+ if (sixteenBit())
+ {
+ demultiply16(alpha());
+ blendClamp16();
+ }
+ else
+ {
+ demultiply8(alpha());
+ blendClamp8();
+ }
+}
+
+inline void DColor::blendZero()
+{
+ setAlpha(0);
+ setRed(0);
+ setGreen(0);
+ setBlue(0);
+}
+
+inline void DColor::blendAlpha16(int alphaValue)
+{
+ uint Sa = alphaValue + 1;
+
+ setRed ((Sa * (uint)red()) >> 16);
+ setGreen((Sa * (uint)green()) >> 16);
+ setBlue ((Sa * (uint)blue()) >> 16);
+ setAlpha((Sa * (uint)alpha()) >> 16);
+}
+
+inline void DColor::blendAlpha8(int alphaValue)
+{
+ uint Sa = alphaValue + 1;
+
+ setRed ((Sa * red()) >> 8);
+ setGreen((Sa * green()) >> 8);
+ setBlue ((Sa * blue()) >> 8);
+ setAlpha((Sa * alpha()) >> 8);
+}
+
+inline void DColor::blendInvAlpha16(int alphaValue)
+{
+ uint Sa = 65536 - alphaValue;
+
+ setRed ((Sa * (uint)red()) >> 16);
+ setGreen((Sa * (uint)green()) >> 16);
+ setBlue ((Sa * (uint)blue()) >> 16);
+ setAlpha((Sa * (uint)alpha()) >> 16);
+}
+
+inline void DColor::blendInvAlpha8(int alphaValue)
+{
+ uint Sa = 256 - alphaValue;
+
+ setRed ((Sa * red()) >> 8);
+ setGreen((Sa * green()) >> 8);
+ setBlue ((Sa * blue()) >> 8);
+ setAlpha((Sa * alpha()) >> 8);
+}
+
+inline void DColor::premultiply16(int alphaValue)
+{
+ uint Da = alphaValue + 1;
+
+ setRed ((Da * (uint)red()) >> 16);
+ setGreen((Da * (uint)green()) >> 16);
+ setBlue ((Da * (uint)blue()) >> 16);
+}
+
+inline void DColor::premultiply8(int alphaValue)
+{
+ uint Da = alphaValue + 1;
+
+ setRed ((Da * red()) >> 8);
+ setGreen((Da * green()) >> 8);
+ setBlue ((Da * blue()) >> 8);
+}
+
+inline void DColor::demultiply16(int alphaValue)
+{
+ uint Da = alphaValue + 1;
+
+ setRed (((uint)red() << 16) / Da);
+ setGreen(((uint)green() << 16) / Da);
+ setBlue (((uint)blue() << 16) / Da);
+}
+
+inline void DColor::demultiply8(int alphaValue)
+{
+ uint Da = alphaValue + 1;
+
+ setRed ((red() << 8) / Da);
+ setGreen((green() << 8) / Da);
+ setBlue ((blue() << 8) / Da);
+}
+
+inline void DColor::blendAdd(const DColor &src)
+{
+ setRed (red() + src.red());
+ setGreen(green() + src.green());
+ setBlue (blue() + src.blue());
+ setAlpha(alpha() + src.alpha());
+}
+
+inline void DColor::blendClamp16()
+{
+ if (0xFFFF0000 & red()) setRed(65535);
+ if (0xFFFF0000 & green()) setGreen(65535);
+ if (0xFFFF0000 & blue()) setBlue(65535);
+ if (0xFFFF0000 & alpha()) setAlpha(65535);
+}
+
+inline void DColor::blendClamp8()
+{
+ if (0xFF00 & red()) setRed(255);
+ if (0xFF00 & green()) setGreen(255);
+ if (0xFF00 & blue()) setBlue(255);
+ if (0xFF00 & alpha()) setAlpha(255);
+}
+
+} // namespace Digikam
+
+#endif // DCOLORBLEND_H
+
diff --git a/src/libs/dimg/dcolorcomposer.cpp b/src/libs/dimg/dcolorcomposer.cpp
new file mode 100644
index 00000000..653dbab3
--- /dev/null
+++ b/src/libs/dimg/dcolorcomposer.cpp
@@ -0,0 +1,436 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-03-02
+ * Description : DColor methods for composing
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Integer arithmetic inspired by DirectFB,
+// src/gfx/generic/generic.c and src/display/idirectfbsurface.c:
+
+/*
+ (c) Copyright 2000-2002 convergence integrated media GmbH <curanz@convergence.de>
+ (c) Copyright 2002-2005 convergence GmbH.
+
+ All rights reserved.
+
+ Written by Denis Oliver Kropp <dok@directfb.org>,
+ Andreas Hundt <andi@fischlustig.de>,
+ Sven Neumann <neo@directfb.org>,
+ Ville Syrj�l� <syrjala@sci.fi> and
+ Claudio Ciccani <klan@users.sf.net>.
+
+*/
+
+// C++ includes.
+
+#include <cmath>
+
+// Local includes.
+
+#include "dcolorcomposer.h"
+
+namespace Digikam
+{
+
+class DColorComposerPorterDuffNone : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffClear : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+ virtual void compose(DColor &dest, DColor src, MultiplicationFlags multiplicationFlags);
+};
+
+class DColorComposerPorterDuffSrc : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+ virtual void compose(DColor &dest, DColor src, MultiplicationFlags multiplicationFlags);
+};
+
+class DColorComposerPorterDuffSrcOver : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffDstOver : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffSrcIn : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffDstIn : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffSrcOut : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffDstOut : public DColorComposer
+{
+public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffSrcAtop : public DColorComposer
+{
+ public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffDstAtop : public DColorComposer
+{
+ public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+class DColorComposerPorterDuffXor : public DColorComposer
+{
+ public:
+ virtual void compose(DColor &dest, DColor src);
+};
+
+// Porter-Duff None
+// component = (source * sa + destination * (1-sa))
+// Src blending function Src Alpha
+// Dst blending function Inv Src Alpha
+void DColorComposerPorterDuffNone::compose(DColor &dest, DColor src)
+{
+ // preserve src alpha value for dest blending,
+ // src.alpha() will be changed after blending src
+ int sa = src.alpha();
+ if (dest.sixteenBit())
+ {
+ src.blendAlpha16(sa);
+ dest.blendInvAlpha16(sa);
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendAlpha8(sa);
+ dest.blendInvAlpha8(sa);
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Clear
+// component = (source * 0 + destination * 0)
+// Src blending function Zero
+// Dst blending function Zero
+void DColorComposerPorterDuffClear::compose(DColor &dest, DColor src)
+{
+ src.blendZero();
+ dest.blendZero();
+ dest.blendAdd(src);
+}
+
+void DColorComposerPorterDuffClear::compose(DColor &dest, DColor src, MultiplicationFlags)
+{
+ // skip pre- and demultiplication
+ compose(dest, src);
+}
+
+// Porter-Duff Src
+// Normal Painter's algorithm
+// component = (source * 1 + destination * 0)
+// Src blending function One
+// Dst blending function Zero
+void DColorComposerPorterDuffSrc::compose(DColor &dest, DColor src)
+{
+ // src: no-op
+ dest.blendZero();
+ dest.blendAdd(src);
+}
+
+void DColorComposerPorterDuffSrc::compose(DColor &dest, DColor src, MultiplicationFlags)
+{
+ // skip pre- and demultiplication
+ compose(dest, src);
+}
+
+// Porter-Duff Src Over
+// component = (source * 1 + destination * (1-sa))
+// Src blending function One
+// Dst blending function Inv Src Alpha
+void DColorComposerPorterDuffSrcOver::compose(DColor &dest, DColor src)
+{
+ if (dest.sixteenBit())
+ {
+ // src: no-op
+ dest.blendInvAlpha16(src.alpha());
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ // src: no-op
+ dest.blendInvAlpha8(src.alpha());
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Dst over
+// component = (source * (1.0-da) + destination * 1)
+// Src blending function Inv Dst Alpha
+// Dst blending function One
+void DColorComposerPorterDuffDstOver::compose(DColor &dest, DColor src)
+{
+ if (dest.sixteenBit())
+ {
+ src.blendInvAlpha16(dest.alpha());
+ // dest: no-op
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendInvAlpha8(dest.alpha());
+ // dest: no-op
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Src In
+// component = (source * da + destination * 0)
+// Src blending function Dst Alpha
+// Dst blending function Zero
+void DColorComposerPorterDuffSrcIn::compose(DColor &dest, DColor src)
+{
+ if (dest.sixteenBit())
+ {
+ src.blendAlpha16(dest.alpha());
+ dest.blendZero();
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendAlpha8(dest.alpha());
+ dest.blendZero();
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Dst In
+// component = (source * 0 + destination * sa)
+// Src blending function Zero
+// Dst blending function Src Alpha
+void DColorComposerPorterDuffDstIn::compose(DColor &dest, DColor src)
+{
+ int sa = src.alpha();
+ if (dest.sixteenBit())
+ {
+ src.blendZero();
+ dest.blendAlpha16(sa);
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendZero();
+ dest.blendAlpha8(sa);
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Src Out
+// component = (source * (1-da) + destination * 0)
+// Src blending function Inv Dst Alpha
+// Dst blending function Zero
+void DColorComposerPorterDuffSrcOut::compose(DColor &dest, DColor src)
+{
+ if (dest.sixteenBit())
+ {
+ src.blendInvAlpha16(dest.alpha());
+ dest.blendZero();
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendInvAlpha8(dest.alpha());
+ dest.blendZero();
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Dst Out
+// component = (source * 0 + destination * (1-sa))
+// Src blending function Zero
+// Dst blending function Inv Src Alpha
+void DColorComposerPorterDuffDstOut::compose(DColor &dest, DColor src)
+{
+ int sa = src.alpha();
+ if (dest.sixteenBit())
+ {
+ src.blendZero();
+ dest.blendInvAlpha16(sa);
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendZero();
+ dest.blendInvAlpha8(sa);
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Src Atop
+// component = (source * da + destination * (1-sa))
+// Src blending function Dst Alpha
+// Dst blending function Inv Src Alpha
+void DColorComposerPorterDuffSrcAtop::compose(DColor &dest, DColor src)
+{
+ int sa = src.alpha();
+ if (dest.sixteenBit())
+ {
+ src.blendAlpha16(dest.alpha());
+ dest.blendInvAlpha16(sa);
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendAlpha8(dest.alpha());
+ dest.blendInvAlpha8(sa);
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Dst Atop
+// component = (source * (1-da) + destination * sa)
+// Src blending function Inv Dest Alpha
+// Dst blending function Src Alpha
+void DColorComposerPorterDuffDstAtop::compose(DColor &dest, DColor src)
+{
+ int sa = src.alpha();
+ if (dest.sixteenBit())
+ {
+ src.blendInvAlpha16(dest.alpha());
+ dest.blendAlpha16(sa);
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendInvAlpha8(dest.alpha());
+ dest.blendInvAlpha8(sa);
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// Porter-Duff Xor
+// component = (source * (1-da) + destination * (1-sa))
+// Src blending function Inv Dst Alpha
+// Dst blending function Inv Src Alpha
+void DColorComposerPorterDuffXor::compose(DColor &dest, DColor src)
+{
+ int sa = src.alpha();
+ if (dest.sixteenBit())
+ {
+ src.blendInvAlpha16(dest.alpha());
+ dest.blendInvAlpha16(sa);
+ dest.blendAdd(src);
+ dest.blendClamp16();
+ }
+ else
+ {
+ src.blendInvAlpha8(dest.alpha());
+ dest.blendInvAlpha8(sa);
+ dest.blendAdd(src);
+ dest.blendClamp8();
+ }
+}
+
+// -----------------------------------------------------------------------
+
+void DColorComposer::compose(DColor &dest, DColor src, DColorComposer::MultiplicationFlags multiplicationFlags)
+{
+ if (multiplicationFlags & PremultiplySrc)
+ src.premultiply();
+ if (multiplicationFlags & PremultiplyDst)
+ dest.premultiply();
+
+ compose(dest, src);
+
+ if (multiplicationFlags & DemultiplyDst)
+ dest.demultiply();
+}
+
+DColorComposer *DColorComposer::getComposer(DColorComposer::CompositingOperation rule)
+{
+ switch(rule)
+ {
+ case PorterDuffNone:
+ return new DColorComposerPorterDuffNone;
+ case PorterDuffClear:
+ return new DColorComposerPorterDuffClear;
+ case PorterDuffSrc:
+ return new DColorComposerPorterDuffSrc;
+ case PorterDuffSrcOver:
+ return new DColorComposerPorterDuffSrcOver;
+ case PorterDuffDstOver:
+ return new DColorComposerPorterDuffDstOver;
+ case PorterDuffSrcIn:
+ return new DColorComposerPorterDuffSrcIn;
+ case PorterDuffDstIn:
+ return new DColorComposerPorterDuffDstIn;
+ case PorterDuffSrcOut:
+ return new DColorComposerPorterDuffSrcOut;
+ case PorterDuffDstOut:
+ return new DColorComposerPorterDuffDstOut;
+ case PorterDuffSrcAtop:
+ return new DColorComposerPorterDuffDstOut;
+ case PorterDuffDstAtop:
+ return new DColorComposerPorterDuffDstOut;
+ case PorterDuffXor:
+ return new DColorComposerPorterDuffDstOut;
+ }
+ return 0;
+}
+
+} // namespace DigiKam
diff --git a/src/libs/dimg/dcolorcomposer.h b/src/libs/dimg/dcolorcomposer.h
new file mode 100644
index 00000000..3e885ca8
--- /dev/null
+++ b/src/libs/dimg/dcolorcomposer.h
@@ -0,0 +1,133 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-03-02
+ * Description : DColor methods for composing
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DCOLORCOMPOSER_H
+#define DCOLORCOMPOSER_H
+
+// Local includes.
+
+#include "dcolor.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DColorComposer
+{
+public:
+ /** The available rules to combine src and destination color.
+
+ For the Porter-Duff rules, the formula is
+ component = (source * fs + destination * fd)
+ where
+ fs, fd according to the following table with
+ sa = source alpha,
+ da = destination alpha:
+
+ None fs: sa fd: 1.0-sa
+ Clear fs: 0.0 fd: 0.0
+ Src fs: 1.0 fd: 0.0
+ Src Over fs: 1.0 fd: 1.0-sa
+ Dst Over fs: 1.0-da fd: 1.0
+ Src In fs: da fd: 0.0
+ Dst In fs: 0.0 fd: sa
+ Src Out fs: 1.0-da fd: 0.0
+ Dst Out fs: 0.0 fd: 1.0-sa
+
+ Src Atop fs: da fd: 1.0-sa
+ Dst Atop fs: 1.0-da fd: sa
+ Xor fs: 1.0-da fd: 1.0-sa
+
+ None is the default, classical blending mode, a "Src over" simplification:
+ Blend non-premultiplied RGBA data "src over" a fully opaque background.
+ Src is the painter's algorithm.
+ All other operations require premultiplied colors.
+ The documentation of java.awt.AlphaComposite (Java 1.5)
+ provides a good introduction and documentation on Porter Duff.
+ */
+
+ enum CompositingOperation
+ {
+ PorterDuffNone,
+ PorterDuffClear,
+ PorterDuffSrc,
+ PorterDuffSrcOver,
+ PorterDuffDstOver,
+ PorterDuffSrcIn,
+ PorterDuffDstIn,
+ PorterDuffSrcOut,
+ PorterDuffDstOut,
+ PorterDuffSrcAtop,
+ PorterDuffDstAtop,
+ PorterDuffXor
+ };
+
+ enum MultiplicationFlags
+ {
+ NoMultiplication = 0x00,
+ PremultiplySrc = 0x01,
+ PremultiplyDst = 0x02,
+ DemultiplyDst = 0x04,
+
+ MultiplicationFlagsDImg = PremultiplySrc | PremultiplyDst | DemultiplyDst,
+ MultiplicationFlagsPremultipliedColorOnDImg = PremultiplyDst | DemultiplyDst
+ };
+
+ /**
+ Retrieve a DColorComposer object for one of the predefined rules.
+ The object needs to be deleted by the caller.
+ */
+ static DColorComposer *getComposer(CompositingOperation rule);
+
+ /**
+ Carry out the actual composition process.
+ Src and Dest are composed and the result is written to dest.
+ No pre-/demultiplication is done by this method, use the other overloaded
+ methods, which call this method, if you need pre- or demultiplication
+ (you need it if any of the colors are read from or written to a DImg).
+
+ If you just pass the object to a DImg method, you do not need to call this.
+ Call this function if you want to compose two colors.
+ Implement this function if you create a custom DColorComposer.
+
+ The bit depth of source and destination color must be identical.
+ */
+ virtual void compose(DColor &dest, DColor src) = 0;
+
+ /**
+ Compose the two colors by calling compose(dest, src).
+ Pre- and demultiplication operations are done as specified.
+ For PorterDuff operations except PorterDuffNone, you need
+
+ - PremultiplySrc if src is not premultiplied (read from a DImg)
+ - PremultiplyDst if dst is not premultiplied (read from a DImg)
+ - DemultiplyDst if dst will be written to non-premultiplied data (a DImg)
+ */
+ virtual void compose(DColor &dest, DColor src, MultiplicationFlags multiplicationFlags);
+
+ virtual ~DColorComposer(){};
+};
+
+} // namespace Digikam
+
+#endif // DCOLORCOMPOSER_H
diff --git a/src/libs/dimg/dcolorpixelaccess.h b/src/libs/dimg/dcolorpixelaccess.h
new file mode 100644
index 00000000..30695c3e
--- /dev/null
+++ b/src/libs/dimg/dcolorpixelaccess.h
@@ -0,0 +1,78 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-02
+ * Description : methods to access on pixels color
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DCOLORPIXELACCESS_H
+#define DCOLORPIXELACCESS_H
+
+namespace Digikam
+{
+
+// These methods are used in quite a few image effects,
+// typically in loops iterating the data.
+// Providing them as inline methods allows the compiler to optimize better.
+
+inline void DColor::setColor(const uchar *data, bool sixteenBit)
+{
+ m_sixteenBit = sixteenBit;
+
+ if (!sixteenBit) // 8 bits image
+ {
+ setBlue (data[0]);
+ setGreen(data[1]);
+ setRed (data[2]);
+ setAlpha(data[3]);
+ }
+ else // 16 bits image
+ {
+ unsigned short* data16 = (unsigned short*)data;
+ setBlue (data16[0]);
+ setGreen(data16[1]);
+ setRed (data16[2]);
+ setAlpha(data16[3]);
+ }
+}
+
+inline void DColor::setPixel(uchar *data) const
+{
+ if (sixteenBit()) // 16 bits image.
+ {
+ unsigned short *data16 = (unsigned short *)data;
+ data16[0] = (unsigned short)blue();
+ data16[1] = (unsigned short)green();
+ data16[2] = (unsigned short)red();
+ data16[3] = (unsigned short)alpha();
+ }
+ else // 8 bits image.
+ {
+ data[0] = (uchar)blue();
+ data[1] = (uchar)green();
+ data[2] = (uchar)red();
+ data[3] = (uchar)alpha();
+ }
+}
+
+
+} // namespace Digikam
+
+#endif // DCOLORPIXELACCESS_H
diff --git a/src/libs/dimg/ddebug.cpp b/src/libs/dimg/ddebug.cpp
new file mode 100644
index 00000000..3f8f07c4
--- /dev/null
+++ b/src/libs/dimg/ddebug.cpp
@@ -0,0 +1,92 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-11
+ * Description : thread safe debugging.
+ *
+ * See B.K.O #133026: because kdDebug() is not thread-safe
+ * we need to use a dedicaced debug statements in threaded
+ * implementation to prevent crash.
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqmutex.h>
+
+// Local includes.
+
+#include "ddebug.h"
+
+#undef DDebug
+#undef kdDebug
+
+namespace Digikam
+{
+
+//static KStaticDeleter<TQMutex> deleter;
+static TQMutex *_ddebug_mutex_ = 0;
+
+Ddbgstream::Ddbgstream(kdbgstream stream)
+ : kdbgstream(stream)
+{
+ // using a static variable here - we can safely assume that kdDebug
+ // is called at least once from the main thread before threads start.
+ if (!_ddebug_mutex_)
+ {
+ // leak the mutex object for simplicity
+ _ddebug_mutex_ = new TQMutex;
+ //deleter.setObject(mutex, new TQMutex);
+ //TDEGlobal::unregisterStaticDeleter(&deleter);
+ }
+ _ddebug_mutex_->lock();
+}
+
+Ddbgstream::~Ddbgstream()
+{
+ _ddebug_mutex_->unlock();
+}
+
+Dndbgstream::Dndbgstream(kndbgstream stream)
+ : kndbgstream(stream)
+{
+ // using a static variable here - we can safely assume that kdDebug
+ // is called at least once from the main thread before threads start.
+ if (!_ddebug_mutex_)
+ {
+ // leak the mutex object for simplicity
+ _ddebug_mutex_ = new TQMutex;
+ //deleter.setObject(mutex, new TQMutex);
+ //TDEGlobal::unregisterStaticDeleter(&deleter);
+ }
+ _ddebug_mutex_->lock();
+}
+
+Dndbgstream::~Dndbgstream()
+{
+ _ddebug_mutex_->unlock();
+}
+
+} // namespace Digikam
+
+Digikam::Ddbgstream DDebug(int area) { return Digikam::Ddbgstream(kdDebug(area)); }
+Digikam::Ddbgstream DError(int area) { return Digikam::Ddbgstream(kdError(area)); }
+Digikam::Ddbgstream DWarning(int area) { return Digikam::Ddbgstream(kdWarning(area)); }
+
+Digikam::Dndbgstream DnDebug(int area) { return Digikam::Dndbgstream(kndDebug(area)); }
+
diff --git a/src/libs/dimg/ddebug.h b/src/libs/dimg/ddebug.h
new file mode 100644
index 00000000..1ea337eb
--- /dev/null
+++ b/src/libs/dimg/ddebug.h
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-11
+ * Description : thread safe debugging.
+ *
+ * See B.K.O #133026: because kdDebug() is not thread-safe
+ * we need to use a dedicaced debug statements in threaded
+ * implementation to prevent crash.
+ *
+ * Copyright (C) 2006 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef _DDEBUG_H_
+#define _DDEBUG_H_
+
+// KDE includes.
+
+#include <kdebug.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT Ddbgstream : public kdbgstream
+{
+
+public:
+
+ Ddbgstream(kdbgstream stream);
+ ~Ddbgstream();
+};
+
+class DIGIKAM_EXPORT Dndbgstream : public kndbgstream
+{
+
+public:
+
+ Dndbgstream(kndbgstream stream);
+ ~Dndbgstream();
+};
+
+} // namespace Digikam
+
+DIGIKAM_EXPORT Digikam::Ddbgstream DDebug(int area = 0);
+DIGIKAM_EXPORT Digikam::Ddbgstream DWarning(int area = 0);
+DIGIKAM_EXPORT Digikam::Ddbgstream DError(int area = 0);
+
+DIGIKAM_EXPORT Digikam::Dndbgstream DnDebug(int area = 0);
+
+#ifdef NDEBUG
+#define DDebug DnDebug
+#endif
+
+#endif // _DDEBUG_H_
+
diff --git a/src/libs/dimg/dimg.cpp b/src/libs/dimg/dimg.cpp
new file mode 100644
index 00000000..d61f2dd0
--- /dev/null
+++ b/src/libs/dimg/dimg.cpp
@@ -0,0 +1,1700 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : digiKam 8/16 bits image management API
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C ANSI includes.
+
+extern "C"
+{
+#if !defined(__STDC_LIMIT_MACROS)
+#define __STDC_LIMIT_MACROS
+#endif
+#include <stdint.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+#include <tqmap.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "pngloader.h"
+#include "jpegloader.h"
+#include "tiffloader.h"
+#include "ppmloader.h"
+#include "rawloader.h"
+#include "jp2kloader.h"
+#include "qimageloader.h"
+#include "icctransform.h"
+#include "exposurecontainer.h"
+#include "ddebug.h"
+#include "dimgprivate.h"
+#include "dimgloaderobserver.h"
+#include "dimg.h"
+
+typedef uint64_t ullong;
+typedef int64_t llong;
+
+namespace Digikam
+{
+
+DImg::DImg()
+ : m_priv(new DImgPrivate)
+{
+}
+
+DImg::DImg(const TQCString& filePath, DImgLoaderObserver *observer,
+ DRawDecoding rawDecodingSettings)
+ : m_priv(new DImgPrivate)
+{
+ load(filePath, observer, rawDecodingSettings);
+}
+
+DImg::DImg(const TQString& filePath, DImgLoaderObserver *observer,
+ DRawDecoding rawDecodingSettings)
+ : m_priv(new DImgPrivate)
+{
+ load(filePath, observer, rawDecodingSettings);
+}
+
+DImg::DImg(const DImg& image)
+{
+ m_priv = image.m_priv;
+ m_priv->ref();
+}
+
+DImg::DImg(uint width, uint height, bool sixteenBit, bool alpha, uchar* data, bool copyData)
+ : m_priv(new DImgPrivate)
+{
+ putImageData(width, height, sixteenBit, alpha, data, copyData);
+}
+
+DImg::DImg(const DImg &image, int w, int h)
+ : m_priv(new DImgPrivate)
+{
+ // This private constructor creates a copy of everything except the data.
+ // The image size is set to the given values and a buffer corresponding to these values is allocated.
+ // This is used by copy and scale.
+ copyImageData(image.m_priv);
+ copyMetaData(image.m_priv);
+ setImageDimension(w, h);
+ allocateData();
+}
+
+DImg::DImg(const TQImage& image)
+ : m_priv(new DImgPrivate)
+{
+ if (!image.isNull())
+ {
+ TQImage target = image.convertDepth(32);
+
+ uint w = target.width();
+ uint h = target.height();
+ uchar* data = new uchar[w*h*4];
+ uint* sptr = (uint*)target.bits();
+ uchar* dptr = data;
+
+ for (uint i = 0 ; i < w*h ; i++)
+ {
+ dptr[0] = tqBlue(*sptr);
+ dptr[1] = tqGreen(*sptr);
+ dptr[2] = tqRed(*sptr);
+ dptr[3] = tqAlpha(*sptr);
+
+ dptr += 4;
+ sptr++;
+ }
+
+ putImageData(w, h, false, image.hasAlphaBuffer(), data, false);
+ }
+}
+
+DImg::~DImg()
+{
+ if (m_priv->deref())
+ delete m_priv;
+}
+
+
+//---------------------------------------------------------------------------------------------------
+// data management
+
+
+DImg& DImg::operator=(const DImg& image)
+{
+ if (m_priv == image.m_priv)
+ return *this;
+
+ if (m_priv->deref())
+ {
+ delete m_priv;
+ m_priv = 0;
+ }
+
+ m_priv = image.m_priv;
+ m_priv->ref();
+
+ return *this;
+}
+
+bool DImg::operator==(const DImg& image) const
+{
+ return m_priv == image.m_priv;
+}
+
+void DImg::reset(void)
+{
+ if (m_priv->deref())
+ delete m_priv;
+
+ m_priv = new DImgPrivate;
+}
+
+void DImg::detach()
+{
+ // are we being shared?
+ if (m_priv->count <= 1)
+ {
+ return;
+ }
+
+ DImgPrivate* old = m_priv;
+
+ m_priv = new DImgPrivate;
+ copyImageData(old);
+ copyMetaData(old);
+
+ if (old->data)
+ {
+ int size = allocateData();
+ memcpy(m_priv->data, old->data, size);
+ }
+
+ old->deref();
+}
+
+void DImg::putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar *data, bool copyData)
+{
+ // set image data, metadata is untouched
+
+ bool null = (width == 0) || (height == 0);
+ // allocateData, or code below will set null to false
+ setImageData(true, width, height, sixteenBit, alpha);
+
+ // replace data
+ delete [] m_priv->data;
+ if (null)
+ {
+ // image is null - no data
+ m_priv->data = 0;
+ }
+ else if (copyData)
+ {
+ int size = allocateData();
+ if (data)
+ memcpy(m_priv->data, data, size);
+ }
+ else
+ {
+ if (data)
+ {
+ m_priv->data = data;
+ m_priv->null = false;
+ }
+ else
+ allocateData();
+ }
+}
+
+void DImg::putImageData(uchar *data, bool copyData)
+{
+ if (!data)
+ {
+ delete [] m_priv->data;
+ m_priv->data = 0;
+ m_priv->null = true;
+ }
+ else if (copyData)
+ {
+ memcpy(m_priv->data, data, numBytes());
+ }
+ else
+ {
+ m_priv->data = data;
+ }
+}
+
+void DImg::resetMetaData()
+{
+ m_priv->attributes.clear();
+ m_priv->embeddedText.clear();
+ m_priv->metaData.clear();
+}
+
+uchar *DImg::stripImageData()
+{
+ uchar *data = m_priv->data;
+ m_priv->data = 0;
+ m_priv->null = true;
+ return data;
+}
+
+void DImg::copyMetaData(const DImgPrivate *src)
+{
+ m_priv->isReadOnly = src->isReadOnly;
+ m_priv->attributes = src->attributes;
+ m_priv->embeddedText = src->embeddedText;
+
+ // since qbytearrays are explicitly shared, we need to make sure that they are
+ // detached from any shared references
+
+ for (TQMap<int, TQByteArray>::const_iterator it = src->metaData.begin();
+ it != src->metaData.end(); ++it)
+ {
+ m_priv->metaData.insert(it.key(), it.data().copy());
+ }
+}
+
+void DImg::copyImageData(const DImgPrivate *src)
+{
+ setImageData(src->null, src->width, src->height, src->sixteenBit, src->alpha);
+}
+
+int DImg::allocateData()
+{
+ int size = m_priv->width * m_priv->height * (m_priv->sixteenBit ? 8 : 4);
+ m_priv->data = new uchar[size];
+ m_priv->null = false;
+ return size;
+}
+
+void DImg::setImageDimension(uint width, uint height)
+{
+ m_priv->width = width;
+ m_priv->height = height;
+}
+
+void DImg::setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha)
+{
+ m_priv->null = null;
+ m_priv->width = width;
+ m_priv->height = height;
+ m_priv->alpha = alpha;
+ m_priv->sixteenBit = sixteenBit;
+}
+
+
+//---------------------------------------------------------------------------------------------------
+// load and save
+
+
+bool DImg::load(const TQString& filePath, DImgLoaderObserver *observer,
+ DRawDecoding rawDecodingSettings)
+{
+ FORMAT format = fileFormat(filePath);
+
+ switch (format)
+ {
+ case(NONE):
+ {
+ DDebug() << filePath << " : Unknown image format !!!" << endl;
+ return false;
+ break;
+ }
+ case(JPEG):
+ {
+ DDebug() << filePath << " : JPEG file identified" << endl;
+ JPEGLoader loader(this);
+ if (loader.load(filePath, observer))
+ {
+ m_priv->null = false;
+ m_priv->alpha = loader.hasAlpha();
+ m_priv->sixteenBit = loader.sixteenBit();
+ m_priv->isReadOnly = loader.isReadOnly();
+ return true;
+ }
+ break;
+ }
+ case(TIFF):
+ {
+ DDebug() << filePath << " : TIFF file identified" << endl;
+ TIFFLoader loader(this);
+ if (loader.load(filePath, observer))
+ {
+ m_priv->null = false;
+ m_priv->alpha = loader.hasAlpha();
+ m_priv->sixteenBit = loader.sixteenBit();
+ m_priv->isReadOnly = loader.isReadOnly();
+ return true;
+ }
+ break;
+ }
+ case(PNG):
+ {
+ DDebug() << filePath << " : PNG file identified" << endl;
+ PNGLoader loader(this);
+ if (loader.load(filePath, observer))
+ {
+ m_priv->null = false;
+ m_priv->alpha = loader.hasAlpha();
+ m_priv->sixteenBit = loader.sixteenBit();
+ m_priv->isReadOnly = loader.isReadOnly();
+ return true;
+ }
+ break;
+ }
+ case(PPM):
+ {
+ DDebug() << filePath << " : PPM file identified" << endl;
+ PPMLoader loader(this);
+ if (loader.load(filePath, observer))
+ {
+ m_priv->null = false;
+ m_priv->alpha = loader.hasAlpha();
+ m_priv->sixteenBit = loader.sixteenBit();
+ m_priv->isReadOnly = loader.isReadOnly();
+ return true;
+ }
+ break;
+ }
+ case(RAW):
+ {
+ DDebug() << filePath << " : RAW file identified" << endl;
+ RAWLoader loader(this, rawDecodingSettings);
+ if (loader.load(filePath, observer))
+ {
+ m_priv->null = false;
+ m_priv->alpha = loader.hasAlpha();
+ m_priv->sixteenBit = loader.sixteenBit();
+ m_priv->isReadOnly = loader.isReadOnly();
+ return true;
+ }
+ break;
+ }
+ case(JP2K):
+ {
+ DDebug() << filePath << " : JPEG2000 file identified" << endl;
+ JP2KLoader loader(this);
+ if (loader.load(filePath, observer))
+ {
+ m_priv->null = false;
+ m_priv->alpha = loader.hasAlpha();
+ m_priv->sixteenBit = loader.sixteenBit();
+ m_priv->isReadOnly = loader.isReadOnly();
+ return true;
+ }
+ break;
+ }
+ default:
+ {
+ DDebug() << filePath << " : TQIMAGE file identified" << endl;
+ TQImageLoader loader(this);
+ if (loader.load(filePath, observer))
+ {
+ m_priv->null = false;
+ m_priv->alpha = loader.hasAlpha();
+ m_priv->sixteenBit = loader.sixteenBit();
+ m_priv->isReadOnly = loader.isReadOnly();
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool DImg::save(const TQString& filePath, const TQString& format, DImgLoaderObserver *observer)
+{
+ if (isNull())
+ return false;
+
+ if (format.isEmpty())
+ return false;
+
+ TQString frm = format.upper();
+
+ if (frm == "JPEG" || frm == "JPG" || frm == "JPE")
+ {
+ JPEGLoader loader(this);
+ return loader.save(filePath, observer);
+ }
+ else if (frm == "PNG")
+ {
+ PNGLoader loader(this);
+ return loader.save(filePath, observer);
+ }
+ else if (frm == "TIFF" || frm == "TIF")
+ {
+ TIFFLoader loader(this);
+ return loader.save(filePath, observer);
+ }
+ else if (frm == "PPM")
+ {
+ PPMLoader loader(this);
+ return loader.save(filePath, observer);
+ }
+ if (frm == "JP2" || frm == "JPX" || frm == "JPC" || frm == "PGX")
+ {
+ JP2KLoader loader(this);
+ return loader.save(filePath, observer);
+ }
+ else
+ {
+ setAttribute("format", format);
+ TQImageLoader loader(this);
+ return loader.save(filePath, observer);
+ }
+
+ return false;
+}
+
+DImg::FORMAT DImg::fileFormat(const TQString& filePath)
+{
+ if ( filePath.isNull() )
+ return NONE;
+
+ // In first we trying to check the file extension. This is mandatory because
+ // some tiff files are detected like RAW files by dcraw::identify method.
+
+ TQFileInfo fileInfo(filePath);
+ if (!fileInfo.exists())
+ {
+ DDebug() << k_funcinfo << "File \"" << filePath << "\" does not exist" << endl;
+ return NONE;
+ }
+
+#if KDCRAW_VERSION < 0x000106
+ TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
+#endif
+ TQString ext = fileInfo.extension(false).upper();
+
+ if (!ext.isEmpty())
+ {
+ if (ext == TQString("JPEG") || ext == TQString("JPG") || ext == TQString("JPE"))
+ return JPEG;
+ else if (ext == TQString("PNG"))
+ return PNG;
+ else if (ext == TQString("TIFF") || ext == TQString("TIF"))
+ return TIFF;
+ else if (rawFilesExt.upper().contains(ext))
+ return RAW;
+ if (ext == TQString("JP2") || ext == TQString("JPX") || // JPEG2000 file format
+ ext == TQString("JPC") || // JPEG2000 code stream
+ ext == TQString("PGX")) // JPEG2000 WM format
+ return JP2K;
+ }
+
+ // In second, we trying to parse file header.
+
+ FILE* f = fopen(TQFile::encodeName(filePath), "rb");
+
+ if (!f)
+ {
+ DDebug() << k_funcinfo << "Failed to open file \"" << filePath << "\"" << endl;
+ return NONE;
+ }
+
+ const int headerLen = 9;
+ unsigned char header[headerLen];
+
+ if (fread(&header, headerLen, 1, f) != 1)
+ {
+ DDebug() << k_funcinfo << "Failed to read header of file \"" << filePath << "\"" << endl;
+ fclose(f);
+ return NONE;
+ }
+
+ fclose(f);
+
+ KDcrawIface::DcrawInfoContainer dcrawIdentify;
+ KDcrawIface::KDcraw::rawFileIdentify(dcrawIdentify, filePath);
+ uchar jpegID[2] = { 0xFF, 0xD8 };
+ uchar tiffBigID[2] = { 0x4D, 0x4D };
+ uchar tiffLilID[2] = { 0x49, 0x49 };
+ uchar pngID[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
+ uchar jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, };
+ uchar jpcID[2] = { 0xFF, 0x4F };
+
+ if (memcmp(&header, &jpegID, 2) == 0) // JPEG file ?
+ {
+ return JPEG;
+ }
+ else if (memcmp(&header, &pngID, 8) == 0) // PNG file ?
+ {
+ return PNG;
+ }
+ else if (memcmp(&header[0], "P", 1) == 0 &&
+ memcmp(&header[2], "\n", 1) == 0) // PPM 16 bits file ?
+ {
+ int width, height, rgbmax;
+ char nl;
+ FILE *file = fopen(TQFile::encodeName(filePath), "rb");
+
+ if (fscanf (file, "P6 %d %d %d%c", &width, &height, &rgbmax, &nl) == 4)
+ {
+ if (rgbmax > 255)
+ {
+ pclose (file);
+ return PPM;
+ }
+ }
+
+ pclose (file);
+ }
+ else if (dcrawIdentify.isDecodable)
+ {
+ // RAW File test using dcraw::identify method.
+ // Need to test it before TIFF because any RAW file
+ // formats using TIFF header.
+ return RAW;
+ }
+ else if (memcmp(&header, &tiffBigID, 2) == 0 || // TIFF file ?
+ memcmp(&header, &tiffLilID, 2) == 0)
+ {
+ return TIFF;
+ }
+ else if (memcmp(&header[4], &jp2ID, 5) == 0 || // JPEG2000 file ?
+ memcmp(&header, &jpcID, 2) == 0)
+ {
+ return JP2K;
+ }
+
+ // In others cases, TQImage will be used to try to open file.
+ return TQIMAGE;
+}
+
+
+//---------------------------------------------------------------------------------------------------
+// accessing properties
+
+
+bool DImg::isNull() const
+{
+ return m_priv->null;
+}
+
+uint DImg::width() const
+{
+ return m_priv->width;
+}
+
+uint DImg::height() const
+{
+ return m_priv->height;
+}
+
+TQSize DImg::size() const
+{
+ return TQSize(m_priv->width, m_priv->height);
+}
+
+uchar* DImg::bits() const
+{
+ return m_priv->data;
+}
+
+uchar* DImg::scanLine(uint i) const
+{
+ if ( i >= height() )
+ return 0;
+
+ uchar *data = bits() + (width() * bytesDepth() * i);
+ return data;
+}
+
+bool DImg::hasAlpha() const
+{
+ return m_priv->alpha;
+}
+
+bool DImg::sixteenBit() const
+{
+ return m_priv->sixteenBit;
+}
+
+bool DImg::isReadOnly() const
+{
+ return m_priv->isReadOnly;
+}
+
+bool DImg::getICCProfilFromFile(const TQString& filePath)
+{
+ TQFile file(filePath);
+ if ( !file.open(IO_ReadOnly) )
+ return false;
+
+ TQByteArray data(file.size());
+ TQDataStream stream( &file );
+ stream.readRawBytes(data.data(), data.size());
+ setICCProfil(data);
+ file.close();
+ return true;
+}
+
+bool DImg::setICCProfilToFile(const TQString& filePath)
+{
+ TQFile file(filePath);
+ if ( !file.open(IO_WriteOnly) )
+ return false;
+
+ TQByteArray data(getICCProfil());
+ TQDataStream stream( &file );
+ stream.writeRawBytes(data.data(), data.size());
+ file.close();
+ return true;
+}
+
+TQByteArray DImg::getComments() const
+{
+ return metadata(COM);
+}
+
+TQByteArray DImg::getExif() const
+{
+ return metadata(EXIF);
+}
+
+TQByteArray DImg::getIptc() const
+{
+ return metadata(IPTC);
+}
+
+TQByteArray DImg::getICCProfil() const
+{
+ return metadata(ICC);
+}
+
+void DImg::setComments(const TQByteArray& commentsData)
+{
+ m_priv->metaData.replace(COM, commentsData);
+}
+
+void DImg::setExif(const TQByteArray& exifData)
+{
+ m_priv->metaData.replace(EXIF, exifData);
+}
+
+void DImg::setIptc(const TQByteArray& iptcData)
+{
+ m_priv->metaData.replace(IPTC, iptcData);
+}
+
+void DImg::setICCProfil(const TQByteArray& profile)
+{
+ m_priv->metaData.replace(ICC, profile);
+}
+
+TQByteArray DImg::metadata(DImg::METADATA key) const
+{
+ typedef TQMap<int, TQByteArray> MetaDataMap;
+
+ for (MetaDataMap::iterator it = m_priv->metaData.begin(); it != m_priv->metaData.end(); ++it)
+ {
+ if (it.key() == key)
+ return it.data();
+ }
+
+ return TQByteArray();
+}
+
+uint DImg::numBytes() const
+{
+ return (width() * height() * bytesDepth());
+}
+
+uint DImg::numPixels() const
+{
+ return (width() * height());
+}
+
+int DImg::bytesDepth() const
+{
+ if (sixteenBit())
+ return 8;
+
+ return 4;
+}
+
+int DImg::bitsDepth() const
+{
+ if (sixteenBit())
+ return 16;
+
+ return 8;
+}
+
+void DImg::setAttribute(const TQString& key, const TQVariant& value)
+{
+ m_priv->attributes.insert(key, value);
+}
+
+TQVariant DImg::attribute(const TQString& key) const
+{
+ if (m_priv->attributes.contains(key))
+ return m_priv->attributes[key];
+
+ return TQVariant();
+}
+
+void DImg::setEmbeddedText(const TQString& key, const TQString& text)
+{
+ m_priv->embeddedText.insert(key, text);
+}
+
+TQString DImg::embeddedText(const TQString& key) const
+{
+ if (m_priv->embeddedText.contains(key))
+ return m_priv->embeddedText[key];
+
+ return TQString();
+}
+
+DColor DImg::getPixelColor(uint x, uint y) const
+{
+ if (isNull() || x > width() || y > height())
+ {
+ DDebug() << k_funcinfo << " : wrong pixel position!" << endl;
+ return DColor();
+ }
+
+ uchar *data = bits() + x*bytesDepth() + (width()*y*bytesDepth());
+
+ return( DColor(data, sixteenBit()) );
+}
+
+void DImg::setPixelColor(uint x, uint y, DColor color)
+{
+ if (isNull() || x > width() || y > height())
+ {
+ DDebug() << k_funcinfo << " : wrong pixel position!" << endl;
+ return;
+ }
+
+ if (color.sixteenBit() != sixteenBit())
+ {
+ DDebug() << k_funcinfo << " : wrong color depth!" << endl;
+ return;
+ }
+
+ uchar *data = bits() + x*bytesDepth() + (width()*y*bytesDepth());
+ color.setPixel(data);
+}
+
+
+//---------------------------------------------------------------------------------------------------
+// copying operations
+
+
+DImg DImg::copy()
+{
+ DImg img(*this);
+ img.detach();
+ return img;
+}
+
+DImg DImg::copyImageData()
+{
+ DImg img(width(), height(), sixteenBit(), hasAlpha(), bits(), true);
+ return img;
+}
+
+DImg DImg::copyMetaData()
+{
+ DImg img;
+ // copy width, height, alpha, sixteenBit, null
+ img.copyImageData(m_priv);
+ // deeply copy metadata
+ img.copyMetaData(m_priv);
+ // set image to null
+ img.m_priv->null = true;
+ return img;
+}
+
+DImg DImg::copy(TQRect rect)
+{
+ return copy(rect.x(), rect.y(), rect.width(), rect.height());
+}
+
+DImg DImg::copy(int x, int y, int w, int h)
+{
+ if ( isNull() || w <= 0 || h <= 0)
+ {
+ DDebug() << k_funcinfo << " : return null image!" << endl;
+ return DImg();
+ }
+
+ DImg image(*this, w, h);
+ image.bitBltImage(this, x, y, w, h, 0, 0);
+
+ return image;
+}
+
+
+//---------------------------------------------------------------------------------------------------
+// bitwise operations
+
+
+void DImg::bitBltImage(const DImg* src, int dx, int dy)
+{
+ bitBltImage(src, 0, 0, src->width(), src->height(), dx, dy);
+}
+
+void DImg::bitBltImage(const DImg* src, int sx, int sy, int dx, int dy)
+{
+ bitBltImage(src, sx, sy, src->width() - sx, src->height() - sy, dx, dy);
+}
+
+void DImg::bitBltImage(const DImg* src, int sx, int sy, int w, int h, int dx, int dy)
+{
+ if (isNull())
+ return;
+
+ if (src->sixteenBit() != sixteenBit())
+ {
+ DWarning() << "Blitting from 8-bit to 16-bit or vice versa is not supported" << endl;
+ return;
+ }
+
+ if (w == -1 && h == -1)
+ {
+ w = src->width();
+ h = src->height();
+ }
+
+ bitBlt(src->bits(), bits(), sx, sy, w, h, dx, dy,
+ src->width(), src->height(), width(), height(), sixteenBit(), src->bytesDepth(), bytesDepth());
+}
+
+void DImg::bitBltImage(const uchar* src, int sx, int sy, int w, int h, int dx, int dy,
+ uint swidth, uint sheight, int sdepth)
+{
+ if (isNull())
+ return;
+
+ if (bytesDepth() != sdepth)
+ {
+ DWarning() << "Blitting from 8-bit to 16-bit or vice versa is not supported" << endl;
+ return;
+ }
+
+ if (w == -1 && h == -1)
+ {
+ w = swidth;
+ h = sheight;
+ }
+
+ bitBlt(src, bits(), sx, sy, w, h, dx, dy, swidth, sheight, width(), height(), sixteenBit(), sdepth, bytesDepth());
+}
+
+bool DImg::normalizeRegionArguments(int &sx, int &sy, int &w, int &h, int &dx, int &dy,
+ uint swidth, uint sheight, uint dwidth, uint dheight)
+{
+ if (sx < 0)
+ {
+ // sx is negative, so + is - and - is +
+ dx -= sx;
+ w += sx;
+ sx = 0;
+ }
+
+ if (sy < 0)
+ {
+ dy -= sy;
+ h += sy;
+ sy = 0;
+ }
+
+ if (dx < 0)
+ {
+ sx -= dx;
+ w += dx;
+ dx = 0;
+ }
+
+ if (dy < 0)
+ {
+ sy -= dy;
+ h += dy;
+ dy = 0;
+ }
+
+ if (sx + w > (int)swidth)
+ {
+ w = swidth - sx;
+ }
+
+ if (sy + h > (int)sheight)
+ {
+ h = sheight - sy;
+ }
+
+ if (dx + w > (int)dwidth)
+ {
+ w = dwidth - dx;
+ }
+
+ if (dy + h > (int)dheight)
+ {
+ h = dheight - dy;
+ }
+
+ // Nothing left to copy
+ if (w <= 0 || h <= 0)
+ return false;
+
+ return true;
+}
+
+void DImg::bitBlt (const uchar *src, uchar *dest,
+ int sx, int sy, int w, int h, int dx, int dy,
+ uint swidth, uint sheight, uint dwidth, uint dheight,
+ bool /*sixteenBit*/, int sdepth, int ddepth)
+{
+ // Normalize
+ if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight))
+ return;
+
+ // Same pixels
+ if (src == dest && dx==sx && dy==sy)
+ return;
+
+ const uchar *sptr;
+ uchar *dptr;
+ uint slinelength = swidth * sdepth;
+ uint dlinelength = dwidth * ddepth;
+
+ int scurY = sy;
+ int dcurY = dy;
+ for (int j = 0 ; j < h ; j++, scurY++, dcurY++)
+ {
+ sptr = &src [ scurY * slinelength ] + sx * sdepth;
+ dptr = &dest[ dcurY * dlinelength ] + dx * ddepth;
+
+ // plain and simple bitBlt
+ for (int i = 0; i < w * sdepth ; i++, sptr++, dptr++)
+ {
+ *dptr = *sptr;
+ }
+ }
+}
+
+void DImg::bitBlendImage(DColorComposer *composer, const DImg* src,
+ int sx, int sy, int w, int h, int dx, int dy,
+ DColorComposer::MultiplicationFlags multiplicationFlags)
+{
+ if (isNull())
+ return;
+
+ if (src->sixteenBit() != sixteenBit())
+ {
+ DWarning() << "Blending from 8-bit to 16-bit or vice versa is not supported" << endl;
+ return;
+ }
+
+ bitBlend(composer, src->bits(), bits(), sx, sy, w, h, dx, dy,
+ src->width(), src->height(), width(), height(), sixteenBit(),
+ src->bytesDepth(), bytesDepth(), multiplicationFlags);
+}
+
+void DImg::bitBlend (DColorComposer *composer, const uchar *src, uchar *dest,
+ int sx, int sy, int w, int h, int dx, int dy,
+ uint swidth, uint sheight, uint dwidth, uint dheight,
+ bool sixteenBit, int sdepth, int ddepth,
+ DColorComposer::MultiplicationFlags multiplicationFlags)
+{
+ // Normalize
+ if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight))
+ return;
+
+ const uchar *sptr;
+ uchar *dptr;
+ uint slinelength = swidth * sdepth;
+ uint dlinelength = dwidth * ddepth;
+
+ int scurY = sy;
+ int dcurY = dy;
+ for (int j = 0 ; j < h ; j++, scurY++, dcurY++)
+ {
+ sptr = &src [ scurY * slinelength ] + sx * sdepth;
+ dptr = &dest[ dcurY * dlinelength ] + dx * ddepth;
+
+ // blend src and destination
+ for (int i = 0 ; i < w ; i++, sptr+=sdepth, dptr+=ddepth)
+ {
+ DColor src(sptr, sixteenBit);
+ DColor dst(dptr, sixteenBit);
+
+ // blend colors
+ composer->compose(dst, src, multiplicationFlags);
+
+ dst.setPixel(dptr);
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------------
+// TQImage / TQPixmap access
+
+
+TQImage DImg::copyTQImage()
+{
+ if (isNull())
+ return TQImage();
+
+ if (sixteenBit())
+ {
+ DImg img(*this);
+ img.detach();
+ img.convertDepth(32);
+ return img.copyTQImage();
+ }
+
+ TQImage img(width(), height(), 32);
+
+ uchar* sptr = bits();
+ uint* dptr = (uint*)img.bits();
+
+ for (uint i=0; i < width()*height(); i++)
+ {
+ *dptr++ = tqRgba(sptr[2], sptr[1], sptr[0], sptr[3]);
+ sptr += 4;
+ }
+
+ if (hasAlpha())
+ {
+ img.setAlphaBuffer(true);
+ }
+
+ return img;
+}
+
+TQImage DImg::copyTQImage(TQRect rect)
+{
+ return (copyTQImage(rect.x(), rect.y(), rect.width(), rect.height()));
+}
+
+TQImage DImg::copyTQImage(int x, int y, int w, int h)
+{
+ if (isNull())
+ return TQImage();
+
+ DImg img = copy(x, y, w, h);
+
+ if (img.sixteenBit())
+ img.convertDepth(32);
+
+ return img.copyTQImage();
+}
+
+TQPixmap DImg::convertToPixmap()
+{
+ if (isNull())
+ return TQPixmap();
+
+ if (sixteenBit())
+ {
+ // make fastaaaa..
+ return TQPixmap(copyTQImage(0, 0, width(), height()));
+ }
+
+ if (TQImage::systemByteOrder() == TQImage::BigEndian)
+ {
+ TQImage img(width(), height(), 32);
+
+ uchar* sptr = bits();
+ uint* dptr = (uint*)img.bits();
+
+ for (uint i=0; i<width()*height(); i++)
+ {
+ *dptr++ = tqRgba(sptr[2], sptr[1], sptr[0], sptr[3]);
+ sptr += 4;
+ }
+
+ if (hasAlpha())
+ {
+ img.setAlphaBuffer(true);
+ }
+
+ return TQPixmap(img);
+ }
+ else
+ {
+ TQImage img(bits(), width(), height(), 32, 0, 0, TQImage::IgnoreEndian);
+
+ if (hasAlpha())
+ {
+ img.setAlphaBuffer(true);
+ }
+
+ return TQPixmap(img);
+ }
+}
+
+TQPixmap DImg::convertToPixmap(IccTransform *monitorICCtrans)
+{
+ if (isNull())
+ return TQPixmap();
+
+ if (!monitorICCtrans->hasOutputProfile())
+ {
+ DDebug() << k_funcinfo << " : no monitor ICC profile available!" << endl;
+ return convertToPixmap();
+ }
+
+ DImg img = copy();
+
+ // Without embedded profile
+ if (img.getICCProfil().isNull())
+ {
+ TQByteArray fakeProfile;
+ monitorICCtrans->apply(img, fakeProfile, monitorICCtrans->getRenderingIntent(),
+ monitorICCtrans->getUseBPC(), false,
+ monitorICCtrans->inputProfile().isNull());
+ }
+ // With embedded profile.
+ else
+ {
+ monitorICCtrans->getEmbeddedProfile( img );
+ monitorICCtrans->apply( img );
+ }
+
+ return (img.convertToPixmap());
+}
+
+TQImage DImg::pureColorMask(ExposureSettingsContainer *expoSettings)
+{
+ if (isNull() || (!expoSettings->underExposureIndicator && !expoSettings->overExposureIndicator))
+ return TQImage();
+
+ TQImage img(size(), 32);
+ img.fill(0x00000000); // Full transparent.
+ img.setAlphaBuffer(true);
+
+ uchar *bits = img.bits();
+ int max = sixteenBit() ? 65535 : 255;
+ int index;
+ DColor pix;
+
+ for (uint x=0 ; x < width() ; x++)
+ {
+ for (uint y=0 ; y<height() ; y++)
+ {
+ pix = getPixelColor(x, y);
+ index = y*img.bytesPerLine() + x*4;
+
+ if (expoSettings->underExposureIndicator &&
+ pix.red() == 0 && pix.green() == 0 && pix.blue() == 0)
+ {
+ bits[index ] = expoSettings->underExposureColor.blue();
+ bits[index + 1] = expoSettings->underExposureColor.green();
+ bits[index + 2] = expoSettings->underExposureColor.red();
+ bits[index + 3] = 0xFF;
+ }
+
+ if (expoSettings->overExposureIndicator &&
+ pix.red() == max && pix.green() == max && pix.blue() == max)
+ {
+ bits[index ] = expoSettings->overExposureColor.blue();
+ bits[index + 1] = expoSettings->overExposureColor.green();
+ bits[index + 2] = expoSettings->overExposureColor.red();
+ bits[index + 3] = 0xFF;
+ }
+ }
+ }
+
+ return img;
+}
+
+
+//---------------------------------------------------------------------------------------------------
+// basic imaging operations
+
+
+void DImg::crop(TQRect rect)
+{
+ crop(rect.x(), rect.y(), rect.width(), rect.height());
+}
+
+void DImg::crop(int x, int y, int w, int h)
+{
+ if ( isNull() || w <= 0 || h <= 0)
+ return;
+
+ uint oldw = width();
+ uint oldh = height();
+ uchar *old = stripImageData();
+
+ // set new image data, bits(), width(), height() change
+ setImageDimension(w, h);
+ allocateData();
+
+ // copy image region (x|y), wxh, from old data to point (0|0) of new data
+ bitBlt(old, bits(), x, y, w, h, 0, 0, oldw, oldh, width(), height(), sixteenBit(), bytesDepth(), bytesDepth());
+ delete [] old;
+}
+
+void DImg::resize(int w, int h)
+{
+ if ( w <= 0 || h <= 0)
+ return;
+
+ DImg image = smoothScale(w, h);
+
+ delete [] m_priv->data;
+ m_priv->data = image.stripImageData();
+ setImageDimension(w, h);
+}
+
+void DImg::rotate(ANGLE angle)
+{
+ if (isNull())
+ return;
+
+ switch (angle)
+ {
+ case(ROT90):
+ {
+ uint w = height();
+ uint h = width();
+
+ if (sixteenBit())
+ {
+ ullong* newData = new ullong[w*h];
+
+ ullong *from = (ullong*) m_priv->data;
+ ullong *to;
+
+ for (int y = w-1; y >=0; y--)
+ {
+ to = newData + y;
+
+ for (uint x=0; x < h; x++)
+ {
+ *to = *from++;
+ to += w;
+ }
+ }
+
+ setImageDimension(w, h);
+
+ delete [] m_priv->data;
+ m_priv->data = (uchar*)newData;
+ }
+ else
+ {
+ uint* newData = new uint[w*h];
+
+ uint *from = (uint*) m_priv->data;
+ uint *to;
+
+ for (int y = w-1; y >=0; y--)
+ {
+ to = newData + y;
+
+ for (uint x=0; x < h; x++)
+ {
+ *to = *from++;
+ to += w;
+ }
+ }
+
+ setImageDimension(w, h);
+
+ delete [] m_priv->data;
+ m_priv->data = (uchar*)newData;
+ }
+
+ break;
+ }
+ case(ROT180):
+ {
+ uint w = width();
+ uint h = height();
+
+ int middle_line = -1;
+ if (h % 2)
+ middle_line = h / 2;
+
+ if (sixteenBit())
+ {
+ ullong *line1;
+ ullong *line2;
+
+ ullong* data = (ullong*) bits();
+ ullong tmp;
+
+ // can be done inplace
+ for (uint y = 0; y < (h+1)/2; y++)
+ {
+ line1 = data + y * w;
+ line2 = data + (h-y) * w;
+ for (uint x=0; x < w; x++)
+ {
+ tmp = *line1;
+ *line1 = *line2;
+ *line2 = tmp;
+
+ line1++;
+ line2--;
+ if ((int)y == middle_line && x * 2 >= w)
+ break;
+ }
+ }
+ }
+ else
+ {
+ uint *line1;
+ uint *line2;
+
+ uint* data = (uint*) bits();
+ uint tmp;
+
+ // can be done inplace
+ for (uint y = 0; y < (h+1)/2; y++)
+ {
+ line1 = data + y * w;
+ line2 = data + (h-y) * w;
+
+ for (uint x=0; x < w; x++)
+ {
+ tmp = *line1;
+ *line1 = *line2;
+ *line2 = tmp;
+
+ line1++;
+ line2--;
+ if ((int)y == middle_line && x * 2 >= w)
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case(ROT270):
+ {
+ uint w = height();
+ uint h = width();
+
+ if (sixteenBit())
+ {
+ ullong* newData = new ullong[w*h];
+
+ ullong *from = (ullong*) m_priv->data;
+ ullong *to;
+
+ for (uint y = 0; y < w; y++)
+ {
+ to = newData + y + w*(h-1);
+
+ for (uint x=0; x < h; x++)
+ {
+ *to = *from++;
+ to -= w;
+ }
+ }
+
+ setImageDimension(w, h);
+
+ delete [] m_priv->data;
+ m_priv->data = (uchar*)newData;
+ }
+ else
+ {
+ uint* newData = new uint[w*h];
+
+ uint *from = (uint*) m_priv->data;
+ uint *to;
+
+ for (uint y = 0; y < w; y++)
+ {
+ to = newData + y + w*(h-1);
+
+ for (uint x=0; x < h; x++)
+ {
+ *to = *from++;
+ to -= w;
+ }
+ }
+
+ setImageDimension(w, h);
+
+ delete [] m_priv->data;
+ m_priv->data = (uchar*)newData;
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+// 15-11-2005: This method have been tested indeep with valgrind by Gilles.
+
+void DImg::flip(FLIP direction)
+{
+ if (isNull())
+ return;
+
+ switch (direction)
+ {
+ case(HORIZONTAL):
+ {
+ uint w = width();
+ uint h = height();
+
+ if (sixteenBit())
+ {
+ unsigned short tmp[4];
+ unsigned short *beg;
+ unsigned short *end;
+
+ unsigned short * data = (unsigned short *)bits();
+
+ // can be done inplace
+ for (uint y = 0 ; y < h ; y++)
+ {
+ beg = data + y * w * 4;
+ end = beg + (w-1) * 4;
+
+ for (uint x=0 ; x < (w/2) ; x++)
+ {
+ memcpy(&tmp, beg, 8);
+ memcpy(beg, end, 8);
+ memcpy(end, &tmp, 8);
+
+ beg+=4;
+ end-=4;
+ }
+ }
+ }
+ else
+ {
+ uchar tmp[4];
+ uchar *beg;
+ uchar *end;
+
+ uchar* data = bits();
+
+ // can be done inplace
+ for (uint y = 0 ; y < h ; y++)
+ {
+ beg = data + y * w * 4;
+ end = beg + (w-1) * 4;
+
+ for (uint x=0 ; x < (w/2) ; x++)
+ {
+ memcpy(&tmp, beg, 4);
+ memcpy(beg, end, 4);
+ memcpy(end, &tmp, 4);
+
+ beg+=4;
+ end-=4;
+ }
+ }
+ }
+
+ break;
+ }
+ case(VERTICAL):
+ {
+ uint w = width();
+ uint h = height();
+
+ if (sixteenBit())
+ {
+ unsigned short tmp[4];
+ unsigned short *line1;
+ unsigned short *line2;
+
+ unsigned short* data = (unsigned short*) bits();
+
+ // can be done inplace
+ for (uint y = 0 ; y < (h/2) ; y++)
+ {
+ line1 = data + y * w * 4;
+ line2 = data + (h-y-1) * w * 4;
+
+ for (uint x=0 ; x < w ; x++)
+ {
+ memcpy(&tmp, line1, 8);
+ memcpy(line1, line2, 8);
+ memcpy(line2, &tmp, 8);
+
+ line1+=4;
+ line2+=4;
+ }
+ }
+ }
+ else
+ {
+ uchar tmp[4];
+ uchar *line1;
+ uchar *line2;
+
+ uchar* data = bits();
+
+ // can be done inplace
+ for (uint y = 0 ; y < (h/2) ; y++)
+ {
+ line1 = data + y * w * 4;
+ line2 = data + (h-y-1) * w * 4;
+
+ for (uint x=0 ; x < w ; x++)
+ {
+ memcpy(&tmp, line1, 4);
+ memcpy(line1, line2, 4);
+ memcpy(line2, &tmp, 4);
+
+ line1+=4;
+ line2+=4;
+ }
+ }
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void DImg::convertToSixteenBit()
+{
+ convertDepth(64);
+}
+
+void DImg::convertToEightBit()
+{
+ convertDepth(32);
+}
+
+void DImg::convertToDepthOfImage(const DImg *otherImage)
+{
+ if (otherImage->sixteenBit())
+ convertToSixteenBit();
+ else
+ convertToEightBit();
+}
+
+void DImg::convertDepth(int depth)
+{
+ if (isNull())
+ return;
+
+ if (depth != 32 && depth != 64)
+ {
+ DDebug() << k_funcinfo << " : wrong color depth!" << endl;
+ return;
+ }
+
+ if (((depth == 32) && !sixteenBit()) ||
+ ((depth == 64) && sixteenBit()))
+ return;
+
+ if (depth == 32)
+ {
+ // downgrading from 16 bit to 8 bit
+
+ uchar* data = new uchar[width()*height()*4];
+ uchar* dptr = data;
+ ushort* sptr = (ushort*)bits();
+
+ for (uint i=0; i<width()*height()*4; i++)
+ {
+ *dptr++ = (*sptr++ * 255UL)/65535UL;
+ }
+
+ delete [] m_priv->data;
+ m_priv->data = data;
+ m_priv->sixteenBit = false;
+ }
+ else if (depth == 64)
+ {
+ // upgrading from 8 bit to 16 bit
+
+ uchar* data = new uchar[width()*height()*8];
+ ushort* dptr = (ushort*)data;
+ uchar* sptr = bits();
+
+ for (uint i=0; i<width()*height()*4; i++)
+ {
+ *dptr++ = (*sptr++ * 65535ULL)/255ULL;
+ }
+
+ delete [] m_priv->data;
+ m_priv->data = data;
+ m_priv->sixteenBit = true;
+ }
+}
+
+void DImg::fill(DColor color)
+{
+ if (sixteenBit())
+ {
+ unsigned short *imgData16 = (unsigned short *)m_priv->data;
+
+ for (uint i = 0 ; i < width()*height()*4 ; i+=4)
+ {
+ imgData16[ i ] = (unsigned short)color.blue();
+ imgData16[i+1] = (unsigned short)color.green();
+ imgData16[i+2] = (unsigned short)color.red();
+ imgData16[i+3] = (unsigned short)color.alpha();
+ }
+ }
+ else
+ {
+ uchar *imgData = m_priv->data;
+
+ for (uint i = 0 ; i < width()*height()*4 ; i+=4)
+ {
+ imgData[ i ] = (uchar)color.blue();
+ imgData[i+1] = (uchar)color.green();
+ imgData[i+2] = (uchar)color.red();
+ imgData[i+3] = (uchar)color.alpha();
+ }
+ }
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/dimg.h b/src/libs/dimg/dimg.h
new file mode 100644
index 00000000..48973e47
--- /dev/null
+++ b/src/libs/dimg/dimg.h
@@ -0,0 +1,353 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : digiKam 8/16 bits image management API
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMG_H
+#define DIMG_H
+
+// TQt includes.
+
+#include <tqcstring.h>
+#include <tqsize.h>
+#include <tqrect.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqvariant.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "drawdecoding.h"
+#include "dcolor.h"
+#include "dcolorcomposer.h"
+
+class TQString;
+
+namespace Digikam
+{
+
+class ExposureSettingsContainer;
+class DImgPrivate;
+class IccTransform;
+class DImgLoaderObserver;
+
+class DIGIKAM_EXPORT DImg
+{
+public:
+
+ enum FORMAT
+ {
+ NONE = 0,
+ JPEG,
+ PNG,
+ TIFF,
+ RAW,
+ PPM,
+ JP2K,
+ TQIMAGE
+ };
+
+ enum METADATA
+ {
+ COM, // JFIF comments section data.
+ EXIF, // EXIF meta-data.
+ IPTC, // IPTC meta-data.
+ ICC // ICC color profile.
+ };
+
+ enum ANGLE
+ {
+ ROT90,
+ ROT180,
+ ROT270
+ };
+
+ enum FLIP
+ {
+ HORIZONTAL,
+ VERTICAL
+ };
+
+ /** Identify file format */
+ static FORMAT fileFormat(const TQString& filePath);
+
+ /** Create null image */
+ DImg();
+
+ /** Load image using TQCString as file path */
+ DImg(const TQCString& filePath, DImgLoaderObserver *observer = 0,
+ DRawDecoding rawDecodingSettings=DRawDecoding());
+
+ /** Load image using TQString as file path */
+ DImg(const TQString& filePath, DImgLoaderObserver *observer = 0,
+ DRawDecoding rawDecodingSettings=DRawDecoding());
+
+ /** Copy image: Creates a shallow copy that refers to the same shared data.
+ The two images will be equal. Call detach() or copy() to create deep copies.
+ */
+ DImg(const DImg& image);
+
+ /** Copy image: Creates a copy of a TQImage object. If the TQImage is null, a
+ null DImg will be created.
+ */
+ DImg(const TQImage& image);
+
+ /** Create image from data.
+ If data is 0, a new buffer will be allocated, otherwise the given data will be used:
+ If copydata is true, the data will be copied to a newly allocated buffer.
+ If copyData is false, this DImg object will take ownership of the data pointer.
+
+ If there is an alpha channel, the data shall be in non-premultiplied form (unassociated alpha).
+ */
+ DImg(uint width, uint height, bool sixteenBit, bool alpha=false, uchar* data = 0, bool copyData = true);
+
+ ~DImg();
+
+ /** Equivalent to the copy constructor */
+ DImg& operator=(const DImg& image);
+
+ /** Detaches from shared data and makes sure that this image
+ is the only one referring to the data.
+ If multiple images share common data, this image makes a copy
+ of the data and detaches itself from the sharing mechanism.
+ Nothing is done if there is just a single reference.
+ */
+ void detach();
+
+ /** Returns whether two images are equal.
+ Two images are equal if and only if they refer to the same shared data.
+ (Thus, DImg() == DImg() is not true, both instances refer two their
+ own shared data. image == DImg(image) is true.)
+ If two or more images refer to the same data, they have the same
+ image data, bits() returns the same data, they have the same metadata,
+ and a change to one image also affects the others.
+ Call detach() to split one image from the group of equal images.
+ */
+ bool operator==(const DImg& image) const;
+
+
+ /** Replaces image data of this object. Metadata is unchanged. Parameters like constructor above. */
+ void putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar *data, bool copyData = true);
+
+ /** Overloaded function, provided for convenience, behaves essentially
+ like the function above if data is not 0.
+ Uses current width, height, sixteenBit, and alpha values.
+ If data is 0, the current data is deleted and the image is set to null
+ (But metadata unchanged).
+ */
+ void putImageData(uchar *data, bool copyData = true);
+
+ /** Reset metadata and image data to null image */
+ void reset(void);
+
+ /** Reset metadata, but do not change image data */
+ void resetMetaData(void);
+
+ /** Returns the data of this image.
+ Ownership of the buffer is passed to the caller, this image will be null afterwards.
+ */
+ uchar* stripImageData();
+
+
+
+ bool load(const TQString& filePath, DImgLoaderObserver *observer = 0,
+ DRawDecoding rawDecodingSettings=DRawDecoding());
+
+ bool save(const TQString& filePath, const TQString& format, DImgLoaderObserver *observer = 0);
+
+ bool isNull() const;
+ uint width() const;
+ uint height() const;
+ TQSize size() const;
+ uchar* bits() const;
+ uchar* scanLine(uint i) const;
+ bool hasAlpha() const;
+ bool sixteenBit() const;
+ uint numBytes() const;
+ uint numPixels() const;
+
+ /** Return the number of bytes depth of one pixel : 4 (non sixteenBit) or 8 (sixteen) */
+ int bytesDepth() const;
+
+ /** Return the number of bits depth of one color component for one pixel : 8 (non sixteenBit) or 16 (sixteen) */
+ int bitsDepth() const;
+
+ /** Access a single pixel of the image.
+ These functions add some safety checks and then use the methods from DColor.
+ In optimized code working directly on the data,
+ better use the inline methods from DColor.
+ */
+ DColor getPixelColor(uint x, uint y) const;
+ void setPixelColor(uint x, uint y, DColor color);
+
+ /**
+ Return true if the original image file format cannot be saved.
+ This is depending of DImgLoader::save() implementation. For example
+ RAW file formats are supported by DImg uing dcraw than cannot support
+ writing operations.
+ */
+ bool isReadOnly() const;
+
+ /** Metadata manipulation methods */
+ TQByteArray getComments() const;
+ TQByteArray getExif() const;
+ TQByteArray getIptc() const;
+ TQByteArray getICCProfil() const;
+ void setComments(const TQByteArray& commentsData);
+ void setExif(const TQByteArray& exifData);
+ void setIptc(const TQByteArray& iptcData);
+ void setICCProfil(const TQByteArray& profile);
+
+ TQByteArray metadata(METADATA key) const;
+
+ bool getICCProfilFromFile(const TQString& filePath);
+ bool setICCProfilToFile(const TQString& filePath);
+
+ void setAttribute(const TQString& key, const TQVariant& value);
+ TQVariant attribute(const TQString& key) const;
+
+ void setEmbeddedText(const TQString& key, const TQString& text);
+ TQString embeddedText(const TQString& key) const;
+
+
+ /** Return a deep copy of full image */
+ DImg copy();
+
+ /** Return a deep copy of the image, but do not include metadata. */
+ DImg copyImageData();
+
+ /** Return an image that containes a deep copy of
+ this image's metadata and the information associated
+ with the image data (width, height, hasAlpha, sixteenBit),
+ but no image data, i.e. isNull() is true.
+ */
+ DImg copyMetaData();
+
+ /** Return a region of image */
+ DImg copy(TQRect rect);
+ DImg copy(int x, int y, int w, int h);
+
+ /** Copy a region of pixels from a source image to this image.
+ Parameters:
+ sx|sy Coordinates in the source image of the rectangle to be copied
+ w h Width and height of the rectangle (Default, or when both are -1: whole source image)
+ dx|dy Coordinates in this image of the rectangle in which the region will be copied
+ (Default: 0|0)
+ The bit depth of source and destination must be identical.
+ */
+ void bitBltImage(const DImg* src, int dx, int dy);
+ void bitBltImage(const DImg* src, int sx, int sy, int dx, int dy);
+ void bitBltImage(const DImg* src, int sx, int sy, int w, int h, int dx, int dy);
+ void bitBltImage(const uchar* src, int sx, int sy, int w, int h, int dx, int dy,
+ uint swidth, uint sheight, int sdepth);
+
+ /** Blend src image on this image (this is dest) with the specified composer
+ and multiplication flags. See documentation of DColorComposer for more info.
+ For the other arguments, see documentation of bitBltImage above.
+ */
+ void bitBlendImage(DColorComposer *composer, const DImg* src,
+ int sx, int sy, int w, int h, int dx, int dy,
+ DColorComposer::MultiplicationFlags multiplicationFlags =
+ DColorComposer::NoMultiplication);
+
+ /** TQImage wrapper methods */
+ TQImage copyTQImage();
+ TQImage copyTQImage(TQRect rect);
+ TQImage copyTQImage(int x, int y, int w, int h);
+
+ /** Crop image to the specified region */
+ void crop(TQRect rect);
+ void crop(int x, int y, int w, int h);
+
+ /** Set width and height of this image, smoothScale it to the given size */
+ void resize(int w, int h);
+
+ /** Return a version of this image scaled to the specified size with the specified mode.
+ See TQSize documentation for information on available modes
+ */
+ DImg smoothScale(int width, int height, TQSize::ScaleMode scaleMode=TQSize::ScaleFree);
+
+ /** Take the region specified by the rectangle sx|sy, width and height sw * sh,
+ and scale it to an image with size dw * dh
+ */
+ DImg smoothScaleSection(int sx, int sy, int sw, int sh,
+ int dw, int dh);
+
+ void rotate(ANGLE angle);
+ void flip(FLIP direction);
+
+ TQPixmap convertToPixmap();
+ TQPixmap convertToPixmap(IccTransform* monitorICCtrans);
+
+ /** Return a mask image where pure white and pure black pixels are over-colored.
+ This way is used to identify over and under exposed pixels.
+ */
+ TQImage pureColorMask(ExposureSettingsContainer *expoSettings);
+
+ /** Convert depth of image. Depth is bytesDepth * bitsDepth.
+ If depth is 32, converts to 8 bits,
+ if depth is 64, converts to 16 bits.
+ */
+ void convertDepth(int depth);
+
+ /** Wrapper methods for convertDepth */
+ void convertToSixteenBit();
+ void convertToEightBit();
+ void convertToDepthOfImage(const DImg *otherImage);
+
+ /** Fill whole image with specified color.
+ The bit depth of the color must be identical to the depth of this image.
+ */
+ void fill(DColor color);
+
+private:
+
+ DImgPrivate *m_priv;
+
+private:
+
+ void copyMetaData(const DImgPrivate *src);
+ void copyImageData(const DImgPrivate *src);
+ void setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha);
+ void setImageDimension(uint width, uint height);
+ int allocateData();
+ DImg(const DImg &image, int w, int h);
+ static void bitBlt(const uchar *src, uchar *dest,
+ int sx, int sy, int w, int h, int dx, int dy,
+ uint swidth, uint sheight, uint dwidth, uint dheight,
+ bool sixteenBit, int sdepth, int ddepth);
+ static void bitBlend(DColorComposer *composer, const uchar *src, uchar *dest,
+ int sx, int sy, int w, int h, int dx, int dy,
+ uint swidth, uint sheight, uint dwidth, uint dheight,
+ bool sixteenBit, int sdepth, int ddepth,
+ DColorComposer::MultiplicationFlags multiplicationFlags);
+ static bool normalizeRegionArguments(int &sx, int &sy, int &w, int &h, int &dx, int &dy,
+ uint swidth, uint sheight, uint dwidth, uint dheight);
+
+ friend class DImgLoader;
+};
+
+} // NameSpace Digikam
+
+#endif /* DIMG_H */
diff --git a/src/libs/dimg/dimgprivate.h b/src/libs/dimg/dimgprivate.h
new file mode 100644
index 00000000..021208d3
--- /dev/null
+++ b/src/libs/dimg/dimgprivate.h
@@ -0,0 +1,81 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-15
+ * Description : DImg private data members
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGPRIVATE_H
+#define DIMGPRIVATE_H
+
+// TQt includes.
+
+#include <tqshared.h>
+#include <tqstring.h>
+#include <tqcstring.h>
+#include <tqvariant.h>
+#include <tqmap.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DImgPrivate : public TQShared
+{
+public:
+
+ DImgPrivate()
+ {
+ null = true;
+ width = 0;
+ height = 0;
+ data = 0;
+ alpha = false;
+ sixteenBit = false;
+ isReadOnly = false;
+ }
+
+ ~DImgPrivate()
+ {
+ delete [] data;
+ }
+
+ bool null;
+ bool alpha;
+ bool sixteenBit;
+ bool isReadOnly;
+
+ unsigned int width;
+ unsigned int height;
+
+ unsigned char *data;
+
+ TQMap<int, TQByteArray> metaData;
+ TQStringVariantMap attributes;
+ TQMap<TQString, TQString> embeddedText;
+
+};
+
+} // NameSpace Digikam
+
+#endif /* DIMGPRIVATE_H */
diff --git a/src/libs/dimg/dimgscale.cpp b/src/libs/dimg/dimgscale.cpp
new file mode 100644
index 00000000..850e36de
--- /dev/null
+++ b/src/libs/dimg/dimgscale.cpp
@@ -0,0 +1,2127 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : This is the normal smoothscale method,
+ * based on Imlib2's smoothscale. Added
+ * smoothScaleSection - Scaling only of a
+ * section of a image. Added 16bit image support
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * Ported to C++/TQImage by Daniel M. Duley
+ * Following modification are (C) Daniel M. Duley
+ * Changes include formatting, namespaces and other C++'ings, removal of old
+ * #ifdef'ed code, and removal of unneeded border calculation code.
+ *
+ * Imlib2 is (C) Carsten Haitzler and various contributors. The MMX code
+ * is by Willem Monsuwe <willem@stack.nl>.
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C ansi includes.
+
+extern "C"
+{
+#include <stdint.h>
+}
+
+// C++ includes.
+
+#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+
+// Local includes.
+
+#include "dimgprivate.h"
+#include "dimg.h"
+
+typedef uint64_t ullong;
+typedef int64_t llong;
+
+namespace Digikam
+{
+
+namespace DImgScale
+{
+ typedef struct __dimg_scale_info
+ {
+ int *xpoints;
+ uint **ypoints;
+ ullong **ypoints16;
+ int *xapoints;
+ int *yapoints;
+ int xup_yup;
+ } DImgScaleInfo;
+
+ uint** dimgCalcYPoints(uint *src, int sw, int sh, int dh);
+ ullong** dimgCalcYPoints16(ullong *src, int sw, int sh, int dh);
+ int* dimgCalcXPoints(int sw, int dw);
+ int* dimgCalcApoints(int s, int d, int up);
+
+ DImgScaleInfo* dimgFreeScaleInfo(DImgScaleInfo *isi);
+ DImgScaleInfo *dimgCalcScaleInfo(const DImg &img,
+ int sw, int sh,
+ int dw, int dh,
+ bool sixteenBit,
+ bool aa);
+
+ void dimgSampleRGBA(DImgScaleInfo *isi, unsigned int *dest, int dxx,
+ int dyy, int dx, int dy, int dw, int dh, int dow);
+ void dimgScaleAARGBA(DImgScaleInfo *isi, unsigned int *dest, int dxx,
+ int dyy, int dx, int dy, int dw, int dh, int dow,
+ int sow);
+ void dimgScaleAARGB(DImgScaleInfo *isi, unsigned int *dest, int dxx,
+ int dyy, int dx, int dy, int dw, int dh, int dow, int
+ sow);
+
+ void dimgScaleAARGBA16(DImgScaleInfo *isi, ullong *dest,
+ int dxx, int dyy, int dw, int dh,
+ int dow, int sow);
+ void dimgScaleAARGB16(DImgScaleInfo *isi, ullong *dest,
+ int dxx, int dyy, int dw, int dh,
+ int dow, int sow);
+};
+
+using namespace DImgScale;
+
+DImg DImg::smoothScale(int dw, int dh, TQSize::ScaleMode scaleMode)
+{
+ if (dw < 0 || dh < 0 || isNull())
+ return DImg();
+
+ uint w = width();
+ uint h = height();
+
+ if (w <= 0 || h <= 0)
+ return DImg();
+
+ TQSize newSize(w, h);
+ newSize.scale( TQSize(dw, dh), scaleMode );
+ if (!newSize.isValid())
+ return DImg();
+
+ dw = newSize.width();
+ dh = newSize.height();
+
+ // do we actually need to scale?
+ if ((w == (uint)dw) && (h == (uint)dh))
+ {
+ return copy();
+ }
+
+ DImgScale::DImgScaleInfo *scaleinfo = dimgCalcScaleInfo(*this, w, h, dw, dh, sixteenBit(), true);
+ if (!scaleinfo)
+ return *this;
+
+ DImg buffer(*this, dw, dh);
+
+ if (sixteenBit())
+ {
+ if (hasAlpha())
+ {
+ dimgScaleAARGBA16(scaleinfo, (ullong*) buffer.bits(),
+ 0, 0, dw, dh, dw, w);
+ }
+ else
+ {
+ dimgScaleAARGB16(scaleinfo, (ullong*) buffer.bits(),
+ 0, 0, dw, dh, dw, w);
+ }
+ }
+ else
+ {
+ if (hasAlpha())
+ {
+ dimgScaleAARGBA(scaleinfo, (unsigned int *)buffer.bits(),
+ 0, 0, 0, 0, dw, dh, dw, w);
+ }
+ else
+ {
+ dimgScaleAARGB(scaleinfo, (unsigned int *)buffer.bits(),
+ 0, 0, 0, 0, dw, dh, dw, w);
+ }
+ }
+
+ dimgFreeScaleInfo(scaleinfo);
+
+ return buffer;
+}
+
+#define CLIP(x, y, w, h, xx, yy, ww, hh) \
+if (x < (xx)) {w += (x - (xx)); x = (xx);} \
+if (y < (yy)) {h += (y - (yy)); y = (yy);} \
+if ((x + w) > ((xx) + (ww))) {w = (ww) - (x - xx);} \
+if ((y + h) > ((yy) + (hh))) {h = (hh) - (y - yy);}
+
+DImg DImg::smoothScaleSection(int sx, int sy,
+ int sw, int sh,
+ int dw, int dh)
+{
+ uint w = width();
+ uint h = height();
+
+ // sanity checks
+ if ((dw <= 0) || (dh <= 0))
+ return DImg();
+
+ if ((sw <= 0) || (sh <= 0))
+ return DImg();
+
+ // clip the source rect to be within the actual image
+ int psx, psy, psw, psh;
+ psx = sx;
+ psy = sy;
+ psw = sw;
+ psh = sh;
+ CLIP(sx, sy, sw, sh, 0, 0, (int)w, (int)h);
+
+ // clip output coords to clipped input coords
+ if (psw != sw)
+ dw = (dw * sw) / psw;
+ if (psh != sh)
+ dh = (dh * sh) / psh;
+
+ // do a second check to see if we now have invalid coords
+ // do not do anything if we have a 0 widht or height image to render
+ if ((dw <= 0) || (dh <= 0))
+ return DImg();
+
+ // if the input rect size < 0 do not render either
+ if ((sw <= 0) || (sh <= 0))
+ return DImg();
+
+ // do we actually need to scale?
+ if ((sw == dw) && (sh == dh))
+ {
+ return copy(sx, sy, sw, sh);
+ }
+
+ // calculate scaleinfo
+ DImgScaleInfo *scaleinfo = dimgCalcScaleInfo(*this, sw, sh, dw, dh, sixteenBit(), true);
+ if (!scaleinfo)
+ return DImg();
+
+ DImg buffer(*this, dw, dh);
+
+ if (sixteenBit())
+ {
+ if (hasAlpha())
+ {
+ dimgScaleAARGBA16(scaleinfo, (ullong*) buffer.bits(),
+ ((sx * dw) / sw),
+ ((sy * dh) / sh),
+ dw, dh,
+ dw, w);
+ }
+ else
+ {
+ dimgScaleAARGB16(scaleinfo, (ullong*) buffer.bits(),
+ ((sx * dw) / sw),
+ ((sy * dh) / sh),
+ dw, dh,
+ dw, w);
+ }
+ }
+ else
+ {
+ if (hasAlpha())
+ {
+ dimgScaleAARGBA(scaleinfo,
+ (uint *)buffer.bits(),
+ ((sx * dw) / sw),
+ ((sy * dh) / sh),
+ 0, 0,
+ dw, dh,
+ dw, w);
+ }
+ else
+ {
+ dimgScaleAARGB(scaleinfo,
+ (uint *)buffer.bits(),
+ ((sx * dw) / sw),
+ ((sy * dh) / sh),
+ 0, 0,
+ dw, dh,
+ dw, w);
+ }
+ }
+
+ dimgFreeScaleInfo(scaleinfo);
+
+ return buffer;
+}
+
+
+//
+// Code ported from Imlib2...
+//
+
+// FIXME: replace with mRed, etc... These work on pointers to pixels, not
+// pixel values
+#define A_VAL(p) ((unsigned char *)(p))[3]
+#define R_VAL(p) ((unsigned char *)(p))[2]
+#define G_VAL(p) ((unsigned char *)(p))[1]
+#define B_VAL(p) ((unsigned char *)(p))[0]
+
+#define INV_XAP (256 - xapoints[x])
+#define XAP (xapoints[x])
+#define INV_YAP (256 - yapoints[dyy + y])
+#define YAP (yapoints[dyy + y])
+
+unsigned int** DImgScale::dimgCalcYPoints(unsigned int *src, int sw, int sh, int dh)
+{
+ unsigned int **p;
+ int i, j = 0;
+ int val, inc;
+
+ p = new unsigned int* [dh+1];
+
+ val = 0;
+ inc = (sh << 16) / dh;
+ for(i = 0; i < dh; i++)
+ {
+ p[j++] = src + ((val >> 16) * sw);
+ val += inc;
+ }
+
+ return(p);
+}
+
+ullong** DImgScale::dimgCalcYPoints16(ullong* src, int sw, int sh, int dh)
+{
+ ullong** p;
+ int i, j = 0;
+ int val, inc;
+
+ p = new ullong*[(dh+1)];
+
+ val = 0;
+ inc = (sh << 16) / dh;
+ for(i = 0; i < dh; i++)
+ {
+ p[j++] = src + ((val >> 16) * sw);
+ val += inc;
+ }
+
+ return p;
+}
+
+int* DImgScale::dimgCalcXPoints(int sw, int dw)
+{
+ int *p, i, j = 0;
+ int val, inc;
+
+ p = new int[dw+1];
+
+ val = 0;
+ inc = (sw << 16) / dw;
+ for(i = 0; i < dw; i++)
+ {
+ p[j++] = (val >> 16);
+ val += inc;
+ }
+
+ return(p);
+}
+
+int* DImgScale::dimgCalcApoints(int s, int d, int up)
+{
+ int *p, i, j = 0;
+
+ p = new int[d];
+
+ /* scaling up */
+ if(up)
+ {
+ int val, inc;
+
+ val = 0;
+ inc = (s << 16) / d;
+ for(i = 0; i < d; i++)
+ {
+ p[j++] = (val >> 8) - ((val >> 8) & 0xffffff00);
+ if((val >> 16) >= (s - 1))
+ p[j - 1] = 0;
+ val += inc;
+ }
+ }
+ /* scaling down */
+ else
+ {
+ int val, inc, ap, Cp;
+ val = 0;
+ inc = (s << 16) / d;
+ Cp = ((d << 14) / s) + 1;
+
+ for(i = 0; i < d; i++)
+ {
+ ap = ((0x100 - ((val >> 8) & 0xff)) * Cp) >> 8;
+ p[j] = ap | (Cp << 16);
+ j++;
+ val += inc;
+ }
+ }
+
+ return(p);
+}
+
+DImgScaleInfo* DImgScale::dimgCalcScaleInfo(const DImg &img,
+ int sw, int sh,
+ int dw, int dh,
+ bool /*sixteenBit*/,
+ bool aa)
+{
+ DImgScaleInfo *isi;
+ int scw, sch;
+
+ scw = dw * img.width() / sw;
+ sch = dh * img.height() / sh;
+
+ isi = new DImgScaleInfo;
+ if(!isi)
+ return(NULL);
+
+ memset(isi, 0, sizeof(DImgScaleInfo));
+
+ isi->xup_yup = (abs(dw) >= sw) + ((abs(dh) >= sh) << 1);
+
+ isi->xpoints = dimgCalcXPoints(img.width(), scw);
+ if(!isi->xpoints)
+ return(dimgFreeScaleInfo(isi));
+
+ if (img.sixteenBit())
+ {
+ isi->ypoints = 0;
+ isi->ypoints16 = dimgCalcYPoints16((ullong*)img.bits(), img.width(), img.height(), sch);
+ if (!isi->ypoints16) return(dimgFreeScaleInfo(isi));
+ }
+ else
+ {
+ isi->ypoints16 = 0;
+ isi->ypoints = dimgCalcYPoints((uint*)img.bits(), img.width(), img.height(), sch);
+ if (!isi->ypoints) return(dimgFreeScaleInfo(isi));
+ }
+
+ if (aa)
+ {
+ isi->xapoints = dimgCalcApoints(img.width(), scw, isi->xup_yup & 1);
+ if(!isi->xapoints) return(dimgFreeScaleInfo(isi));
+
+ isi->yapoints = dimgCalcApoints(img.height(), sch, isi->xup_yup & 2);
+ if(!isi->yapoints) return(dimgFreeScaleInfo(isi));
+ }
+/* It doesn't work...
+ else
+ {
+ isi->xapoints = new int[scw];
+ if(!isi->xapoints) return(dimgFreeScaleInfo(isi));
+ for(int i = 0; i < scw; i++) isi->xapoints[i] = 0;
+
+ isi->yapoints = new int[sch];
+ if(!isi->yapoints) return(dimgFreeScaleInfo(isi));
+ for(int i = 0; i < sch; i++) isi->yapoints[i] = 0;
+ }*/
+
+ return(isi);
+}
+
+DImgScaleInfo* DImgScale::dimgFreeScaleInfo(DImgScaleInfo *isi)
+{
+ if(isi)
+ {
+ delete [] isi->xpoints;
+ delete [] isi->ypoints;
+ delete [] isi->ypoints16;
+ delete [] isi->xapoints;
+ delete [] isi->yapoints;
+ delete isi;
+ }
+
+ return 0;
+}
+
+/** scale by pixel sampling only */
+void DImgScale::dimgSampleRGBA(DImgScaleInfo *isi, unsigned int *dest,
+ int dxx, int dyy, int dx, int dy, int dw,
+ int dh, int dow)
+{
+ unsigned int *sptr, *dptr;
+ int x, y, end;
+ unsigned int **ypoints = isi->ypoints;
+ int *xpoints = isi->xpoints;
+
+ /* whats the last pixel ont he line so we stop there */
+ end = dxx + dw;
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ /* get the pointer to the start of the destination scanline */
+ dptr = dest + dx + ((y + dy) * dow);
+ /* calculate the source line we'll scan from */
+ sptr = ypoints[dyy + y];
+ /* go thru the scanline and copy across */
+ for(x = dxx; x < end; x++)
+ *dptr++ = sptr[xpoints[x]];
+ }
+}
+
+/* FIXME: NEED to optimise ScaleAARGBA - currently its "ok" but needs work*/
+
+/** scale by area sampling */
+void DImgScale::dimgScaleAARGBA(DImgScaleInfo *isi, unsigned int *dest,
+ int dxx, int dyy, int dx, int dy, int dw,
+ int dh, int dow, int sow)
+{
+ unsigned int *sptr, *dptr;
+ int x, y, end;
+ unsigned int **ypoints = isi->ypoints;
+ int *xpoints = isi->xpoints;
+ int *xapoints = isi->xapoints;
+ int *yapoints = isi->yapoints;
+
+ end = dxx + dw;
+ /* scaling up both ways */
+ if(isi->xup_yup == 3)
+ {
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ /* calculate the source line we'll scan from */
+ dptr = dest + dx + ((y + dy) * dow);
+ sptr = ypoints[dyy + y];
+ if(YAP > 0)
+ {
+ for(x = dxx; x < end; x++)
+ {
+ int r, g, b, a;
+ int rr, gg, bb, aa;
+ unsigned int *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL(pix) * INV_XAP;
+ g = G_VAL(pix) * INV_XAP;
+ b = B_VAL(pix) * INV_XAP;
+ a = A_VAL(pix) * INV_XAP;
+ pix++;
+ r += R_VAL(pix) * XAP;
+ g += G_VAL(pix) * XAP;
+ b += B_VAL(pix) * XAP;
+ a += A_VAL(pix) * XAP;
+ pix += sow;
+ rr = R_VAL(pix) * XAP;
+ gg = G_VAL(pix) * XAP;
+ bb = B_VAL(pix) * XAP;
+ aa = A_VAL(pix) * XAP;
+ pix--;
+ rr += R_VAL(pix) * INV_XAP;
+ gg += G_VAL(pix) * INV_XAP;
+ bb += B_VAL(pix) * INV_XAP;
+ aa += A_VAL(pix) * INV_XAP;
+ r = ((rr * YAP) + (r * INV_YAP)) >> 16;
+ g = ((gg * YAP) + (g * INV_YAP)) >> 16;
+ b = ((bb * YAP) + (b * INV_YAP)) >> 16;
+ a = ((aa * YAP) + (a * INV_YAP)) >> 16;
+
+ A_VAL(dptr) = a;
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+
+ dptr++;
+ }
+ else
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL(pix) * INV_YAP;
+ g = G_VAL(pix) * INV_YAP;
+ b = B_VAL(pix) * INV_YAP;
+ a = A_VAL(pix) * INV_YAP;
+ pix += sow;
+ r += R_VAL(pix) * YAP;
+ g += G_VAL(pix) * YAP;
+ b += B_VAL(pix) * YAP;
+ a += A_VAL(pix) * YAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+ a >>= 8;
+
+ A_VAL(dptr) = a;
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+
+ dptr++;
+ }
+ }
+ }
+ else
+ {
+ for(x = dxx; x < end; x++)
+ {
+ int r, g, b, a;
+ unsigned int *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL(pix) * INV_XAP;
+ g = G_VAL(pix) * INV_XAP;
+ b = B_VAL(pix) * INV_XAP;
+ a = A_VAL(pix) * INV_XAP;
+ pix++;
+ r += R_VAL(pix) * XAP;
+ g += G_VAL(pix) * XAP;
+ b += B_VAL(pix) * XAP;
+ a += A_VAL(pix) * XAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+ a >>= 8;
+
+ A_VAL(dptr) = a;
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+
+ dptr++;
+ }
+ else
+ *dptr++ = sptr[xpoints[x] ];
+ }
+ }
+ }
+ }
+ /* if we're scaling down vertically */
+ else if(isi->xup_yup == 1)
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification \*/
+ int Cy, j;
+ unsigned int *pix;
+ int r, g, b, a, rr, gg, bb, aa;
+ int yap;
+
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+
+ dptr = dest + dx + ((y + dy) * dow);
+ for(x = dxx; x < end; x++)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL(pix) * yap) >> 10;
+ g = (G_VAL(pix) * yap) >> 10;
+ b = (B_VAL(pix) * yap) >> 10;
+ a = (A_VAL(pix) * yap) >> 10;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix += sow;
+ r += (R_VAL(pix) * Cy) >> 10;
+ g += (G_VAL(pix) * Cy) >> 10;
+ b += (B_VAL(pix) * Cy) >> 10;
+ a += (A_VAL(pix) * Cy) >> 10;
+ }
+ if(j > 0)
+ {
+ pix += sow;
+ r += (R_VAL(pix) * j) >> 10;
+ g += (G_VAL(pix) * j) >> 10;
+ b += (B_VAL(pix) * j) >> 10;
+ a += (A_VAL(pix) * j) >> 10;
+ }
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + 1;
+ rr = (R_VAL(pix) * yap) >> 10;
+ gg = (G_VAL(pix) * yap) >> 10;
+ bb = (B_VAL(pix) * yap) >> 10;
+ aa = (A_VAL(pix) * yap) >> 10;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix += sow;
+ rr += (R_VAL(pix) * Cy) >> 10;
+ gg += (G_VAL(pix) * Cy) >> 10;
+ bb += (B_VAL(pix) * Cy) >> 10;
+ aa += (A_VAL(pix) * Cy) >> 10;
+ }
+ if(j > 0)
+ {
+ pix += sow;
+ rr += (R_VAL(pix) * j) >> 10;
+ gg += (G_VAL(pix) * j) >> 10;
+ bb += (B_VAL(pix) * j) >> 10;
+ aa += (A_VAL(pix) * j) >> 10;
+ }
+ r = r * INV_XAP;
+ g = g * INV_XAP;
+ b = b * INV_XAP;
+ a = a * INV_XAP;
+ r = (r + ((rr * XAP))) >> 12;
+ g = (g + ((gg * XAP))) >> 12;
+ b = (b + ((bb * XAP))) >> 12;
+ a = (a + ((aa * XAP))) >> 12;
+ }
+ else
+ {
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ a >>= 4;
+ }
+
+ A_VAL(dptr) = a;
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+
+ dptr++;
+ }
+ }
+ }
+ /* if we're scaling down horizontally */
+ else if(isi->xup_yup == 2)
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification \*/
+ int Cx, j;
+ unsigned int *pix;
+ int r, g, b, a, rr, gg, bb, aa;
+ int xap;
+
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ dptr = dest + dx + ((y + dy) * dow);
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL(pix) * xap) >> 10;
+ g = (G_VAL(pix) * xap) >> 10;
+ b = (B_VAL(pix) * xap) >> 10;
+ a = (A_VAL(pix) * xap) >> 10;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ pix++;
+ r += (R_VAL(pix) * Cx) >> 10;
+ g += (G_VAL(pix) * Cx) >> 10;
+ b += (B_VAL(pix) * Cx) >> 10;
+ a += (A_VAL(pix) * Cx) >> 10;
+ }
+ if(j > 0)
+ {
+ pix++;
+ r += (R_VAL(pix) * j) >> 10;
+ g += (G_VAL(pix) * j) >> 10;
+ b += (B_VAL(pix) * j) >> 10;
+ a += (A_VAL(pix) * j) >> 10;
+ }
+ if(YAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + sow;
+ rr = (R_VAL(pix) * xap) >> 10;
+ gg = (G_VAL(pix) * xap) >> 10;
+ bb = (B_VAL(pix) * xap) >> 10;
+ aa = (A_VAL(pix) * xap) >> 10;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ pix++;
+ rr += (R_VAL(pix) * Cx) >> 10;
+ gg += (G_VAL(pix) * Cx) >> 10;
+ bb += (B_VAL(pix) * Cx) >> 10;
+ aa += (A_VAL(pix) * Cx) >> 10;
+ }
+ if(j > 0)
+ {
+ pix++;
+ rr += (R_VAL(pix) * j) >> 10;
+ gg += (G_VAL(pix) * j) >> 10;
+ bb += (B_VAL(pix) * j) >> 10;
+ aa += (A_VAL(pix) * j) >> 10;
+ }
+ r = r * INV_YAP;
+ g = g * INV_YAP;
+ b = b * INV_YAP;
+ a = a * INV_YAP;
+ r = (r + ((rr * YAP))) >> 12;
+ g = (g + ((gg * YAP))) >> 12;
+ b = (b + ((bb * YAP))) >> 12;
+ a = (a + ((aa * YAP))) >> 12;
+ }
+ else
+ {
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ a >>= 4;
+ }
+
+ A_VAL(dptr) = a;
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+
+ dptr++;
+ }
+ }
+ }
+ /* if we're scaling down horizontally & vertically */
+ else
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification:
+ |*| The operation 'b = (b * c) >> 16' translates to pmulhw,
+ |*| so the operation 'b = (b * c) >> d' would translate to
+ |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb
+ \*/
+ int Cx, Cy, i, j;
+ unsigned int *pix;
+ int a, r, g, b, ax, rx, gx, bx;
+ int xap, yap;
+
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+
+ dptr = dest + dx + ((y + dy) * dow);
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ sptr = ypoints[dyy + y] + xpoints[x];
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL(pix) * xap) >> 9;
+ gx = (G_VAL(pix) * xap) >> 9;
+ bx = (B_VAL(pix) * xap) >> 9;
+ ax = (A_VAL(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL(pix) * Cx) >> 9;
+ gx += (G_VAL(pix) * Cx) >> 9;
+ bx += (B_VAL(pix) * Cx) >> 9;
+ ax += (A_VAL(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL(pix) * i) >> 9;
+ gx += (G_VAL(pix) * i) >> 9;
+ bx += (B_VAL(pix) * i) >> 9;
+ ax += (A_VAL(pix) * i) >> 9;
+ }
+
+ r = (rx * yap) >> 14;
+ g = (gx * yap) >> 14;
+ b = (bx * yap) >> 14;
+ a = (ax * yap) >> 14;
+
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL(pix) * xap) >> 9;
+ gx = (G_VAL(pix) * xap) >> 9;
+ bx = (B_VAL(pix) * xap) >> 9;
+ ax = (A_VAL(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL(pix) * Cx) >> 9;
+ gx += (G_VAL(pix) * Cx) >> 9;
+ bx += (B_VAL(pix) * Cx) >> 9;
+ ax += (A_VAL(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL(pix) * i) >> 9;
+ gx += (G_VAL(pix) * i) >> 9;
+ bx += (B_VAL(pix) * i) >> 9;
+ ax += (A_VAL(pix) * i) >> 9;
+ }
+
+ r += (rx * Cy) >> 14;
+ g += (gx * Cy) >> 14;
+ b += (bx * Cy) >> 14;
+ a += (ax * Cy) >> 14;
+ }
+ if(j > 0)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL(pix) * xap) >> 9;
+ gx = (G_VAL(pix) * xap) >> 9;
+ bx = (B_VAL(pix) * xap) >> 9;
+ ax = (A_VAL(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL(pix) * Cx) >> 9;
+ gx += (G_VAL(pix) * Cx) >> 9;
+ bx += (B_VAL(pix) * Cx) >> 9;
+ ax += (A_VAL(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL(pix) * i) >> 9;
+ gx += (G_VAL(pix) * i) >> 9;
+ bx += (B_VAL(pix) * i) >> 9;
+ ax += (A_VAL(pix) * i) >> 9;
+ }
+
+ r += (rx * j) >> 14;
+ g += (gx * j) >> 14;
+ b += (bx * j) >> 14;
+ a += (ax * j) >> 14;
+ }
+
+ R_VAL(dptr) = r >> 5;
+ G_VAL(dptr) = g >> 5;
+ B_VAL(dptr) = b >> 5;
+ A_VAL(dptr) = a >> 5;
+ dptr++;
+ }
+ }
+ }
+}
+
+/** scale by area sampling - IGNORE the ALPHA byte */
+void DImgScale::dimgScaleAARGB(DImgScaleInfo *isi, unsigned int *dest,
+ int dxx, int dyy, int dx, int dy, int dw,
+ int dh, int dow, int sow)
+{
+ unsigned int *sptr, *dptr;
+ int x, y, end;
+ unsigned int **ypoints = isi->ypoints;
+ int *xpoints = isi->xpoints;
+ int *xapoints = isi->xapoints;
+ int *yapoints = isi->yapoints;
+
+ end = dxx + dw;
+ /* scaling up both ways */
+ if(isi->xup_yup == 3)
+ {
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ /* calculate the source line we'll scan from */
+ dptr = dest + dx + ((y + dy) * dow);
+ sptr = ypoints[dyy + y];
+ if(YAP > 0)
+ {
+ for(x = dxx; x < end; x++)
+ {
+ int r = 0, g = 0, b = 0;
+ int rr = 0, gg = 0, bb = 0;
+ unsigned int *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL(pix) * INV_XAP;
+ g = G_VAL(pix) * INV_XAP;
+ b = B_VAL(pix) * INV_XAP;
+ pix++;
+ r += R_VAL(pix) * XAP;
+ g += G_VAL(pix) * XAP;
+ b += B_VAL(pix) * XAP;
+ pix += sow;
+ rr = R_VAL(pix) * XAP;
+ gg = G_VAL(pix) * XAP;
+ bb = B_VAL(pix) * XAP;
+ pix --;
+ rr += R_VAL(pix) * INV_XAP;
+ gg += G_VAL(pix) * INV_XAP;
+ bb += B_VAL(pix) * INV_XAP;
+ r = ((rr * YAP) + (r * INV_YAP)) >> 16;
+ g = ((gg * YAP) + (g * INV_YAP)) >> 16;
+ b = ((bb * YAP) + (b * INV_YAP)) >> 16;
+
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+ A_VAL(dptr) = 0xFF;
+
+ dptr++;
+ }
+ else
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL(pix) * INV_YAP;
+ g = G_VAL(pix) * INV_YAP;
+ b = B_VAL(pix) * INV_YAP;
+ pix += sow;
+ r += R_VAL(pix) * YAP;
+ g += G_VAL(pix) * YAP;
+ b += B_VAL(pix) * YAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+ A_VAL(dptr) = 0xFF;
+
+ dptr++;
+ }
+ }
+ }
+ else
+ {
+ for(x = dxx; x < end; x++)
+ {
+ int r = 0, g = 0, b = 0;
+ unsigned int *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL(pix) * INV_XAP;
+ g = G_VAL(pix) * INV_XAP;
+ b = B_VAL(pix) * INV_XAP;
+ pix++;
+ r += R_VAL(pix) * XAP;
+ g += G_VAL(pix) * XAP;
+ b += B_VAL(pix) * XAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+ A_VAL(dptr) = 0xFF;
+
+ dptr++;
+ }
+ else
+ *dptr++ = sptr[xpoints[x] ];
+ }
+ }
+ }
+ }
+ /* if we're scaling down vertically */
+ else if(isi->xup_yup == 1)
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification \*/
+ int Cy, j;
+ unsigned int *pix;
+ int r, g, b, rr, gg, bb;
+ int yap;
+
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+
+ dptr = dest + dx + ((y + dy) * dow);
+ for(x = dxx; x < end; x++)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL(pix) * yap) >> 10;
+ g = (G_VAL(pix) * yap) >> 10;
+ b = (B_VAL(pix) * yap) >> 10;
+ pix += sow;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ r += (R_VAL(pix) * Cy) >> 10;
+ g += (G_VAL(pix) * Cy) >> 10;
+ b += (B_VAL(pix) * Cy) >> 10;
+ pix += sow;
+ }
+ if(j > 0)
+ {
+ r += (R_VAL(pix) * j) >> 10;
+ g += (G_VAL(pix) * j) >> 10;
+ b += (B_VAL(pix) * j) >> 10;
+ }
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + 1;
+ rr = (R_VAL(pix) * yap) >> 10;
+ gg = (G_VAL(pix) * yap) >> 10;
+ bb = (B_VAL(pix) * yap) >> 10;
+ pix += sow;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ rr += (R_VAL(pix) * Cy) >> 10;
+ gg += (G_VAL(pix) * Cy) >> 10;
+ bb += (B_VAL(pix) * Cy) >> 10;
+ pix += sow;
+ }
+ if(j > 0)
+ {
+ rr += (R_VAL(pix) * j) >> 10;
+ gg += (G_VAL(pix) * j) >> 10;
+ bb += (B_VAL(pix) * j) >> 10;
+ }
+ r = r * INV_XAP;
+ g = g * INV_XAP;
+ b = b * INV_XAP;
+ r = (r + ((rr * XAP))) >> 12;
+ g = (g + ((gg * XAP))) >> 12;
+ b = (b + ((bb * XAP))) >> 12;
+ }
+ else
+ {
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ }
+
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+ A_VAL(dptr) = 0xFF;
+
+ dptr++;
+ }
+ }
+ }
+ /* if we're scaling down horizontally */
+ else if(isi->xup_yup == 2)
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification \*/
+ int Cx, j;
+ unsigned int *pix;
+ int r, g, b, rr, gg, bb;
+ int xap;
+
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ dptr = dest + dx + ((y + dy) * dow);
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL(pix) * xap) >> 10;
+ g = (G_VAL(pix) * xap) >> 10;
+ b = (B_VAL(pix) * xap) >> 10;
+ pix++;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ r += (R_VAL(pix) * Cx) >> 10;
+ g += (G_VAL(pix) * Cx) >> 10;
+ b += (B_VAL(pix) * Cx) >> 10;
+ pix++;
+ }
+ if(j > 0)
+ {
+ r += (R_VAL(pix) * j) >> 10;
+ g += (G_VAL(pix) * j) >> 10;
+ b += (B_VAL(pix) * j) >> 10;
+ }
+ if(YAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + sow;
+ rr = (R_VAL(pix) * xap) >> 10;
+ gg = (G_VAL(pix) * xap) >> 10;
+ bb = (B_VAL(pix) * xap) >> 10;
+ pix++;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ rr += (R_VAL(pix) * Cx) >> 10;
+ gg += (G_VAL(pix) * Cx) >> 10;
+ bb += (B_VAL(pix) * Cx) >> 10;
+ pix++;
+ }
+ if(j > 0)
+ {
+ rr += (R_VAL(pix) * j) >> 10;
+ gg += (G_VAL(pix) * j) >> 10;
+ bb += (B_VAL(pix) * j) >> 10;
+ }
+ r = r * INV_YAP;
+ g = g * INV_YAP;
+ b = b * INV_YAP;
+ r = (r + ((rr * YAP))) >> 12;
+ g = (g + ((gg * YAP))) >> 12;
+ b = (b + ((bb * YAP))) >> 12;
+ }
+ else
+ {
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ }
+
+ R_VAL(dptr) = r;
+ G_VAL(dptr) = g;
+ B_VAL(dptr) = b;
+ A_VAL(dptr) = 0xFF;
+
+ dptr++;
+ }
+ }
+ }
+ /* fully optimized (i think) - onyl change of algorithm can help */
+ /* if we're scaling down horizontally & vertically */
+ else
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification \*/
+ int Cx, Cy, i, j;
+ unsigned int *pix;
+ int r, g, b, rx, gx, bx;
+ int xap, yap;
+
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+
+ dptr = dest + dx + ((y + dy) * dow);
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ sptr = ypoints[dyy + y] + xpoints[x];
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL(pix) * xap) >> 9;
+ gx = (G_VAL(pix) * xap) >> 9;
+ bx = (B_VAL(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL(pix) * Cx) >> 9;
+ gx += (G_VAL(pix) * Cx) >> 9;
+ bx += (B_VAL(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL(pix) * i) >> 9;
+ gx += (G_VAL(pix) * i) >> 9;
+ bx += (B_VAL(pix) * i) >> 9;
+ }
+
+ r = (rx * yap) >> 14;
+ g = (gx * yap) >> 14;
+ b = (bx * yap) >> 14;
+
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL(pix) * xap) >> 9;
+ gx = (G_VAL(pix) * xap) >> 9;
+ bx = (B_VAL(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL(pix) * Cx) >> 9;
+ gx += (G_VAL(pix) * Cx) >> 9;
+ bx += (B_VAL(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL(pix) * i) >> 9;
+ gx += (G_VAL(pix) * i) >> 9;
+ bx += (B_VAL(pix) * i) >> 9;
+ }
+
+ r += (rx * Cy) >> 14;
+ g += (gx * Cy) >> 14;
+ b += (bx * Cy) >> 14;
+ }
+ if(j > 0)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL(pix) * xap) >> 9;
+ gx = (G_VAL(pix) * xap) >> 9;
+ bx = (B_VAL(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL(pix) * Cx) >> 9;
+ gx += (G_VAL(pix) * Cx) >> 9;
+ bx += (B_VAL(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL(pix) * i) >> 9;
+ gx += (G_VAL(pix) * i) >> 9;
+ bx += (B_VAL(pix) * i) >> 9;
+ }
+
+ r += (rx * j) >> 14;
+ g += (gx * j) >> 14;
+ b += (bx * j) >> 14;
+ }
+
+ R_VAL(dptr) = r >> 5;
+ G_VAL(dptr) = g >> 5;
+ B_VAL(dptr) = b >> 5;
+ A_VAL(dptr) = 0xFF;
+ dptr++;
+ }
+ }
+ }
+}
+
+#define A_VAL16(p) ((ushort *)(p))[3]
+#define R_VAL16(p) ((ushort *)(p))[2]
+#define G_VAL16(p) ((ushort *)(p))[1]
+#define B_VAL16(p) ((ushort *)(p))[0]
+
+/** scale by area sampling - IGNORE the ALPHA byte*/
+void DImgScale::dimgScaleAARGB16(DImgScaleInfo *isi, ullong *dest,
+ int dxx, int dyy, int dw, int dh,
+ int dow, int sow)
+{
+ ullong *sptr, *dptr;
+ int x, y, end;
+ ullong **ypoints = isi->ypoints16;
+ int *xpoints = isi->xpoints;
+ int *xapoints = isi->xapoints;
+ int *yapoints = isi->yapoints;
+
+ end = dxx + dw;
+
+ // scaling up both ways
+ if(isi->xup_yup == 3)
+ {
+ // go through every scanline in the output buffer
+ for(y = 0; y < dh; y++)
+ {
+ // calculate the source line we'll scan from
+ dptr = dest + (y * dow);
+ sptr = ypoints[dyy + y];
+ if(YAP > 0)
+ {
+ for(x = dxx; x < end; x++)
+ {
+ llong r = 0, g = 0, b = 0;
+ llong rr = 0, gg = 0, bb = 0;
+ ullong *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL16(pix) * INV_XAP;
+ g = G_VAL16(pix) * INV_XAP;
+ b = B_VAL16(pix) * INV_XAP;
+ pix++;
+ r += R_VAL16(pix) * XAP;
+ g += G_VAL16(pix) * XAP;
+ b += B_VAL16(pix) * XAP;
+ pix += sow;
+ rr = R_VAL16(pix) * XAP;
+ gg = G_VAL16(pix) * XAP;
+ bb = B_VAL16(pix) * XAP;
+ pix --;
+ rr += R_VAL16(pix) * INV_XAP;
+ gg += G_VAL16(pix) * INV_XAP;
+ bb += B_VAL16(pix) * INV_XAP;
+ r = ((rr * YAP) + (r * INV_YAP)) >> 16;
+ g = ((gg * YAP) + (g * INV_YAP)) >> 16;
+ b = ((bb * YAP) + (b * INV_YAP)) >> 16;
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = 0xFFFF;
+
+ dptr++;
+ }
+ else
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL16(pix) * INV_YAP;
+ g = G_VAL16(pix) * INV_YAP;
+ b = B_VAL16(pix) * INV_YAP;
+ pix += sow;
+ r += R_VAL16(pix) * YAP;
+ g += G_VAL16(pix) * YAP;
+ b += B_VAL16(pix) * YAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = 0xFFFF;
+
+ dptr++;
+ }
+ }
+ }
+ else
+ {
+ for(x = dxx; x < end; x++)
+ {
+ llong r = 0, g = 0, b = 0;
+ ullong *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL16(pix) * INV_XAP;
+ g = G_VAL16(pix) * INV_XAP;
+ b = B_VAL16(pix) * INV_XAP;
+ pix++;
+ r += R_VAL16(pix) * XAP;
+ g += G_VAL16(pix) * XAP;
+ b += B_VAL16(pix) * XAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = 0xFFFF;
+
+ dptr++;
+ }
+ else
+ *dptr++ = sptr[xpoints[x] ];
+ }
+ }
+ }
+ }
+ // if we're scaling down vertically
+ else if(isi->xup_yup == 1)
+ {
+ // 'Correct' version, with math units prepared for MMXification
+ int Cy, j;
+ ullong *pix;
+ llong r, g, b, rr, gg, bb;
+ int yap;
+
+ // go through every scanline in the output buffer
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+
+ dptr = dest + y * dow;
+ for(x = dxx; x < end; x++)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL16(pix) * yap) >> 10;
+ g = (G_VAL16(pix) * yap) >> 10;
+ b = (B_VAL16(pix) * yap) >> 10;
+ pix += sow;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ r += (R_VAL16(pix) * Cy) >> 10;
+ g += (G_VAL16(pix) * Cy) >> 10;
+ b += (B_VAL16(pix) * Cy) >> 10;
+ pix += sow;
+ }
+ if(j > 0)
+ {
+ r += (R_VAL16(pix) * j) >> 10;
+ g += (G_VAL16(pix) * j) >> 10;
+ b += (B_VAL16(pix) * j) >> 10;
+ }
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + 1;
+ rr = (R_VAL16(pix) * yap) >> 10;
+ gg = (G_VAL16(pix) * yap) >> 10;
+ bb = (B_VAL16(pix) * yap) >> 10;
+ pix += sow;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ rr += (R_VAL16(pix) * Cy) >> 10;
+ gg += (G_VAL16(pix) * Cy) >> 10;
+ bb += (B_VAL16(pix) * Cy) >> 10;
+ pix += sow;
+ }
+ if(j > 0)
+ {
+ rr += (R_VAL16(pix) * j) >> 10;
+ gg += (G_VAL16(pix) * j) >> 10;
+ bb += (B_VAL16(pix) * j) >> 10;
+ }
+ r = r * INV_XAP;
+ g = g * INV_XAP;
+ b = b * INV_XAP;
+ r = (r + ((rr * XAP))) >> 12;
+ g = (g + ((gg * XAP))) >> 12;
+ b = (b + ((bb * XAP))) >> 12;
+ }
+ else
+ {
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ }
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = 0xFFFF;
+ dptr++;
+ }
+ }
+ }
+ // if we're scaling down horizontally
+ else if(isi->xup_yup == 2)
+ {
+ // 'Correct' version, with math units prepared for MMXification
+ int Cx, j;
+ ullong *pix;
+ llong r, g, b, rr, gg, bb;
+ int xap;
+
+ // go through every scanline in the output buffer
+ for(y = 0; y < dh; y++)
+ {
+ dptr = dest + y * dow;
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL16(pix) * xap) >> 10;
+ g = (G_VAL16(pix) * xap) >> 10;
+ b = (B_VAL16(pix) * xap) >> 10;
+ pix++;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ r += (R_VAL16(pix) * Cx) >> 10;
+ g += (G_VAL16(pix) * Cx) >> 10;
+ b += (B_VAL16(pix) * Cx) >> 10;
+ pix++;
+ }
+ if(j > 0)
+ {
+ r += (R_VAL16(pix) * j) >> 10;
+ g += (G_VAL16(pix) * j) >> 10;
+ b += (B_VAL16(pix) * j) >> 10;
+ }
+ if(YAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + sow;
+ rr = (R_VAL16(pix) * xap) >> 10;
+ gg = (G_VAL16(pix) * xap) >> 10;
+ bb = (B_VAL16(pix) * xap) >> 10;
+ pix++;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ rr += (R_VAL16(pix) * Cx) >> 10;
+ gg += (G_VAL16(pix) * Cx) >> 10;
+ bb += (B_VAL16(pix) * Cx) >> 10;
+ pix++;
+ }
+ if(j > 0)
+ {
+ rr += (R_VAL16(pix) * j) >> 10;
+ gg += (G_VAL16(pix) * j) >> 10;
+ bb += (B_VAL16(pix) * j) >> 10;
+ }
+ r = r * INV_YAP;
+ g = g * INV_YAP;
+ b = b * INV_YAP;
+ r = (r + ((rr * YAP))) >> 12;
+ g = (g + ((gg * YAP))) >> 12;
+ b = (b + ((bb * YAP))) >> 12;
+ }
+ else{
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ }
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = 0xFFFF;
+ dptr++;
+ }
+ }
+ }
+ // fully optimized (i think) - onyl change of algorithm can help
+ // if we're scaling down horizontally & vertically
+ else
+ {
+ // 'Correct' version, with math units prepared for MMXification
+ int Cx, Cy, i, j;
+ ullong *pix;
+ llong r, g, b, rx, gx, bx;
+ int xap, yap;
+
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+ dptr = dest + y * dow;
+
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ sptr = ypoints[dyy + y] + xpoints[x];
+ pix = sptr;
+ sptr += sow;
+
+ rx = (R_VAL16(pix) * xap) >> 9;
+ gx = (G_VAL16(pix) * xap) >> 9;
+ bx = (B_VAL16(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL16(pix) * Cx) >> 9;
+ gx += (G_VAL16(pix) * Cx) >> 9;
+ bx += (B_VAL16(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL16(pix) * i) >> 9;
+ gx += (G_VAL16(pix) * i) >> 9;
+ bx += (B_VAL16(pix) * i) >> 9;
+ }
+
+ r = (rx * yap) >> 14;
+ g = (gx * yap) >> 14;
+ b = (bx * yap) >> 14;
+
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL16(pix) * xap) >> 9;
+ gx = (G_VAL16(pix) * xap) >> 9;
+ bx = (B_VAL16(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL16(pix) * Cx) >> 9;
+ gx += (G_VAL16(pix) * Cx) >> 9;
+ bx += (B_VAL16(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL16(pix) * i) >> 9;
+ gx += (G_VAL16(pix) * i) >> 9;
+ bx += (B_VAL16(pix) * i) >> 9;
+ }
+
+ r += (rx * Cy) >> 14;
+ g += (gx * Cy) >> 14;
+ b += (bx * Cy) >> 14;
+ }
+ if(j > 0)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL16(pix) * xap) >> 9;
+ gx = (G_VAL16(pix) * xap) >> 9;
+ bx = (B_VAL16(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL16(pix) * Cx) >> 9;
+ gx += (G_VAL16(pix) * Cx) >> 9;
+ bx += (B_VAL16(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL16(pix) * i) >> 9;
+ gx += (G_VAL16(pix) * i) >> 9;
+ bx += (B_VAL16(pix) * i) >> 9;
+ }
+
+ r += (rx * j) >> 14;
+ g += (gx * j) >> 14;
+ b += (bx * j) >> 14;
+ }
+
+ R_VAL16(dptr) = r >> 5;
+ G_VAL16(dptr) = g >> 5;
+ B_VAL16(dptr) = b >> 5;
+ A_VAL16(dptr) = 0xFFFF;
+ dptr++;
+ }
+ }
+ }
+}
+
+/* scale by area sampling */
+void DImgScale::dimgScaleAARGBA16(DImgScaleInfo *isi, ullong *dest,
+ int dxx, int dyy,
+ int dw, int dh,
+ int dow, int sow)
+{
+ ullong *sptr, *dptr;
+ int x, y, end;
+ ullong **ypoints = isi->ypoints16;
+ int *xpoints = isi->xpoints;
+ int *xapoints = isi->xapoints;
+ int *yapoints = isi->yapoints;
+
+ end = dxx + dw;
+ /* scaling up both ways */
+ if(isi->xup_yup == 3)
+ {
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ /* calculate the source line we'll scan from */
+ dptr = dest + (y * dow);
+ sptr = ypoints[dyy + y];
+ if(YAP > 0)
+ {
+ for(x = dxx; x < end; x++)
+ {
+ llong r, g, b, a;
+ llong rr, gg, bb, aa;
+ ullong *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL16(pix) * INV_XAP;
+ g = G_VAL16(pix) * INV_XAP;
+ b = B_VAL16(pix) * INV_XAP;
+ a = A_VAL16(pix) * INV_XAP;
+ pix++;
+ r += R_VAL16(pix) * XAP;
+ g += G_VAL16(pix) * XAP;
+ b += B_VAL16(pix) * XAP;
+ a += A_VAL16(pix) * XAP;
+ pix += sow;
+ rr = R_VAL16(pix) * XAP;
+ gg = G_VAL16(pix) * XAP;
+ bb = B_VAL16(pix) * XAP;
+ aa = A_VAL16(pix) * XAP;
+ pix--;
+ rr += R_VAL16(pix) * INV_XAP;
+ gg += G_VAL16(pix) * INV_XAP;
+ bb += B_VAL16(pix) * INV_XAP;
+ aa += A_VAL16(pix) * INV_XAP;
+ r = ((rr * YAP) + (r * INV_YAP)) >> 16;
+ g = ((gg * YAP) + (g * INV_YAP)) >> 16;
+ b = ((bb * YAP) + (b * INV_YAP)) >> 16;
+ a = ((aa * YAP) + (a * INV_YAP)) >> 16;
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = a;
+
+ dptr++;
+ }
+ else
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL16(pix) * INV_YAP;
+ g = G_VAL16(pix) * INV_YAP;
+ b = B_VAL16(pix) * INV_YAP;
+ a = A_VAL16(pix) * INV_YAP;
+ pix += sow;
+ r += R_VAL16(pix) * YAP;
+ g += G_VAL16(pix) * YAP;
+ b += B_VAL16(pix) * YAP;
+ a += A_VAL16(pix) * YAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+ a >>= 8;
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = a;
+
+ dptr++;
+ }
+ }
+ }
+ else
+ {
+ for(x = dxx; x < end; x++)
+ {
+ llong r, g, b, a;
+ ullong *pix;
+
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = R_VAL16(pix) * INV_XAP;
+ g = G_VAL16(pix) * INV_XAP;
+ b = B_VAL16(pix) * INV_XAP;
+ a = A_VAL16(pix) * INV_XAP;
+ pix++;
+ r += R_VAL16(pix) * XAP;
+ g += G_VAL16(pix) * XAP;
+ b += B_VAL16(pix) * XAP;
+ a += A_VAL16(pix) * XAP;
+ r >>= 8;
+ g >>= 8;
+ b >>= 8;
+ a >>= 8;
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = a;
+
+ dptr++;
+ }
+ else
+ *dptr++ = sptr[xpoints[x] ];
+ }
+ }
+ }
+ }
+ /* if we're scaling down vertically */
+ else if(isi->xup_yup == 1)
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification \*/
+ int Cy, j;
+ ullong *pix;
+ llong r, g, b, a, rr, gg, bb, aa;
+ int yap;
+
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+
+ dptr = dest + (y * dow);
+ for(x = dxx; x < end; x++)
+ {
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL16(pix) * yap) >> 10;
+ g = (G_VAL16(pix) * yap) >> 10;
+ b = (B_VAL16(pix) * yap) >> 10;
+ a = (A_VAL16(pix) * yap) >> 10;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix += sow;
+ r += (R_VAL16(pix) * Cy) >> 10;
+ g += (G_VAL16(pix) * Cy) >> 10;
+ b += (B_VAL16(pix) * Cy) >> 10;
+ a += (A_VAL16(pix) * Cy) >> 10;
+ }
+ if(j > 0)
+ {
+ pix += sow;
+ r += (R_VAL16(pix) * j) >> 10;
+ g += (G_VAL16(pix) * j) >> 10;
+ b += (B_VAL16(pix) * j) >> 10;
+ a += (A_VAL16(pix) * j) >> 10;
+ }
+ if(XAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + 1;
+ rr = (R_VAL16(pix) * yap) >> 10;
+ gg = (G_VAL16(pix) * yap) >> 10;
+ bb = (B_VAL16(pix) * yap) >> 10;
+ aa = (A_VAL16(pix) * yap) >> 10;
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix += sow;
+ rr += (R_VAL16(pix) * Cy) >> 10;
+ gg += (G_VAL16(pix) * Cy) >> 10;
+ bb += (B_VAL16(pix) * Cy) >> 10;
+ aa += (A_VAL16(pix) * Cy) >> 10;
+ }
+ if(j > 0)
+ {
+ pix += sow;
+ rr += (R_VAL16(pix) * j) >> 10;
+ gg += (G_VAL16(pix) * j) >> 10;
+ bb += (B_VAL16(pix) * j) >> 10;
+ aa += (A_VAL16(pix) * j) >> 10;
+ }
+ r = r * INV_XAP;
+ g = g * INV_XAP;
+ b = b * INV_XAP;
+ a = a * INV_XAP;
+ r = (r + ((rr * XAP))) >> 12;
+ g = (g + ((gg * XAP))) >> 12;
+ b = (b + ((bb * XAP))) >> 12;
+ a = (a + ((aa * XAP))) >> 12;
+ }
+ else
+ {
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ a >>= 4;
+ }
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = a;
+
+ dptr++;
+ }
+ }
+ }
+ /* if we're scaling down horizontally */
+ else if(isi->xup_yup == 2)
+ {
+ /*\ 'Correct' version, with math units prepared for MMXification \*/
+ int Cx, j;
+ ullong *pix;
+ llong r, g, b, a, rr, gg, bb, aa;
+ int xap;
+
+ /* go through every scanline in the output buffer */
+ for(y = 0; y < dh; y++)
+ {
+ dptr = dest + y * dow;
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ pix = ypoints[dyy + y] + xpoints[x];
+ r = (R_VAL16(pix) * xap) >> 10;
+ g = (G_VAL16(pix) * xap) >> 10;
+ b = (B_VAL16(pix) * xap) >> 10;
+ a = (A_VAL16(pix) * xap) >> 10;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ pix++;
+ r += (R_VAL16(pix) * Cx) >> 10;
+ g += (G_VAL16(pix) * Cx) >> 10;
+ b += (B_VAL16(pix) * Cx) >> 10;
+ a += (A_VAL16(pix) * Cx) >> 10;
+ }
+ if(j > 0)
+ {
+ pix++;
+ r += (R_VAL16(pix) * j) >> 10;
+ g += (G_VAL16(pix) * j) >> 10;
+ b += (B_VAL16(pix) * j) >> 10;
+ a += (A_VAL16(pix) * j) >> 10;
+ }
+ if(YAP > 0)
+ {
+ pix = ypoints[dyy + y] + xpoints[x] + sow;
+ rr = (R_VAL16(pix) * xap) >> 10;
+ gg = (G_VAL16(pix) * xap) >> 10;
+ bb = (B_VAL16(pix) * xap) >> 10;
+ aa = (A_VAL16(pix) * xap) >> 10;
+ for(j = (1 << 14) - xap; j > Cx; j -= Cx)
+ {
+ pix++;
+ rr += (R_VAL16(pix) * Cx) >> 10;
+ gg += (G_VAL16(pix) * Cx) >> 10;
+ bb += (B_VAL16(pix) * Cx) >> 10;
+ aa += (A_VAL16(pix) * Cx) >> 10;
+ }
+ if(j > 0)
+ {
+ pix++;
+ rr += (R_VAL16(pix) * j) >> 10;
+ gg += (G_VAL16(pix) * j) >> 10;
+ bb += (B_VAL16(pix) * j) >> 10;
+ aa += (A_VAL16(pix) * j) >> 10;
+ }
+ r = r * INV_YAP;
+ g = g * INV_YAP;
+ b = b * INV_YAP;
+ a = a * INV_YAP;
+ r = (r + ((rr * YAP))) >> 12;
+ g = (g + ((gg * YAP))) >> 12;
+ b = (b + ((bb * YAP))) >> 12;
+ a = (a + ((aa * YAP))) >> 12;
+ }
+ else
+ {
+ r >>= 4;
+ g >>= 4;
+ b >>= 4;
+ a >>= 4;
+ }
+
+ R_VAL16(dptr) = r;
+ G_VAL16(dptr) = g;
+ B_VAL16(dptr) = b;
+ A_VAL16(dptr) = a;
+
+ dptr++;
+ }
+ }
+ }
+ /* if we're scaling down horizontally & vertically */
+ else{
+ /*\ 'Correct' version, with math units prepared for MMXification:
+ |*| The operation 'b = (b * c) >> 16' translates to pmulhw,
+ |*| so the operation 'b = (b * c) >> d' would translate to
+ |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb
+ \*/
+ int Cx, Cy, i, j;
+ ullong *pix;
+ llong a, r, g, b, ax, rx, gx, bx;
+ int xap, yap;
+
+ for(y = 0; y < dh; y++)
+ {
+ Cy = YAP >> 16;
+ yap = YAP & 0xffff;
+
+ dptr = dest + y * dow;
+ for(x = dxx; x < end; x++)
+ {
+ Cx = XAP >> 16;
+ xap = XAP & 0xffff;
+
+ sptr = ypoints[dyy + y] + xpoints[x];
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL16(pix) * xap) >> 9;
+ gx = (G_VAL16(pix) * xap) >> 9;
+ bx = (B_VAL16(pix) * xap) >> 9;
+ ax = (A_VAL16(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL16(pix) * Cx) >> 9;
+ gx += (G_VAL16(pix) * Cx) >> 9;
+ bx += (B_VAL16(pix) * Cx) >> 9;
+ ax += (A_VAL16(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL16(pix) * i) >> 9;
+ gx += (G_VAL16(pix) * i) >> 9;
+ bx += (B_VAL16(pix) * i) >> 9;
+ ax += (A_VAL16(pix) * i) >> 9;
+ }
+
+ r = (rx * yap) >> 14;
+ g = (gx * yap) >> 14;
+ b = (bx * yap) >> 14;
+ a = (ax * yap) >> 14;
+
+
+ for(j = (1 << 14) - yap; j > Cy; j -= Cy)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL16(pix) * xap) >> 9;
+ gx = (G_VAL16(pix) * xap) >> 9;
+ bx = (B_VAL16(pix) * xap) >> 9;
+ ax = (A_VAL16(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL16(pix) * Cx) >> 9;
+ gx += (G_VAL16(pix) * Cx) >> 9;
+ bx += (B_VAL16(pix) * Cx) >> 9;
+ ax += (A_VAL16(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL16(pix) * i) >> 9;
+ gx += (G_VAL16(pix) * i) >> 9;
+ bx += (B_VAL16(pix) * i) >> 9;
+ ax += (A_VAL16(pix) * i) >> 9;
+ }
+
+ r += (rx * Cy) >> 14;
+ g += (gx * Cy) >> 14;
+ b += (bx * Cy) >> 14;
+ a += (ax * Cy) >> 14;
+ }
+ if(j > 0)
+ {
+ pix = sptr;
+ sptr += sow;
+ rx = (R_VAL16(pix) * xap) >> 9;
+ gx = (G_VAL16(pix) * xap) >> 9;
+ bx = (B_VAL16(pix) * xap) >> 9;
+ ax = (A_VAL16(pix) * xap) >> 9;
+ pix++;
+ for(i = (1 << 14) - xap; i > Cx; i -= Cx)
+ {
+ rx += (R_VAL16(pix) * Cx) >> 9;
+ gx += (G_VAL16(pix) * Cx) >> 9;
+ bx += (B_VAL16(pix) * Cx) >> 9;
+ ax += (A_VAL16(pix) * Cx) >> 9;
+ pix++;
+ }
+ if(i > 0)
+ {
+ rx += (R_VAL16(pix) * i) >> 9;
+ gx += (G_VAL16(pix) * i) >> 9;
+ bx += (B_VAL16(pix) * i) >> 9;
+ ax += (A_VAL16(pix) * i) >> 9;
+ }
+
+ r += (rx * j) >> 14;
+ g += (gx * j) >> 14;
+ b += (bx * j) >> 14;
+ a += (ax * j) >> 14;
+ }
+
+ R_VAL16(dptr) = r >> 5;
+ G_VAL16(dptr) = g >> 5;
+ B_VAL16(dptr) = b >> 5;
+ A_VAL16(dptr) = a >> 5;
+ dptr++;
+ }
+ }
+ }
+}
+
+/**
+//Documentation of the cryptic dimgScaleAARGBA
+dimgScaleAARGBA(
+DImgScaleInfo *isi, // scaleinfo
+unsigned int *dest, // destination img data
+int dxx, // destination x location corresponding to start x of src section
+int dyy, // destination y location corresponding to start y of src section
+int dx, // destination x start location
+int dy, // destination y start location
+int dw, // destination width
+int dh, // destination height
+int dow, // destination scanline width
+int sow); // src scanline width
+*/
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/drawdecoding.h b/src/libs/dimg/drawdecoding.h
new file mode 100644
index 00000000..e2d82fdc
--- /dev/null
+++ b/src/libs/dimg/drawdecoding.h
@@ -0,0 +1,128 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-06
+ * Description : Raw decoding settings for digiKam:
+ * standard libkdcraw parameters plus
+ * few customized for post processing.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DRAW_DECODING_H
+#define DRAW_DECODING_H
+
+// TQt includes.
+
+#include <tqvaluelist.h>
+#include <tqpointarray.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rawdecodingsettings.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DRawDecoding : public KDcrawIface::RawDecodingSettings
+{
+
+public:
+
+ /** Standard constructor with default settings
+ */
+ DRawDecoding()
+ {
+ resetPostProcessingSettings();
+ };
+
+ /** Standard destructor
+ */
+ virtual ~DRawDecoding(){};
+
+ /** Method to use a settings to optimize time loading, for exemple to compute image histogram
+ */
+ void optimizeTimeLoading()
+ {
+ KDcrawIface::RawDecodingSettings::optimizeTimeLoading();
+ resetPostProcessingSettings();
+ };
+
+ /** Method to reset to default values all Raw processing settings.
+ */
+ void resetPostProcessingSettings()
+ {
+ lightness = 0.0;
+ contrast = 1.0;
+ gamma = 1.0;
+ saturation = 1.0;
+ exposureComp = 0.0;
+ curveAdjust = TQPointArray();
+ levelsAdjust = TQValueList<int>();
+ };
+
+ /** Method to check is a post-processing setting have been changed
+ */
+ bool postProcessingSettingsIsDirty() const
+ {
+ return (lightness != 0.0 ||
+ contrast != 1.0 ||
+ gamma != 1.0 ||
+ saturation != 1.0 ||
+ exposureComp != 0.0 ||
+ !curveAdjust.isEmpty() ||
+ !levelsAdjust.isEmpty());
+ }
+
+public:
+
+ /** Lightness correction value.
+ */
+ double lightness;
+
+ /** Contrast correction value.
+ */
+ double contrast;
+
+ /** Gamma correction value.
+ */
+ double gamma;
+
+ /** Color saturation correction value.
+ */
+ double saturation;
+
+ /** Exposure compensation value.
+ */
+ double exposureComp;
+
+ /** Luminosity curve adjustements.
+ */
+ TQPointArray curveAdjust;
+
+ /** Levels adjustements: 4 channels (L, R, G, B * 2 values).
+ */
+ TQValueList<int> levelsAdjust;
+};
+
+} // namespace Digikam
+
+#endif /* DRAW_DECODING_H */
diff --git a/src/libs/dimg/exposurecontainer.h b/src/libs/dimg/exposurecontainer.h
new file mode 100644
index 00000000..62469258
--- /dev/null
+++ b/src/libs/dimg/exposurecontainer.h
@@ -0,0 +1,65 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-12
+ * Description : exposure indicator settings container.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EXPOSURESETTINGSCONTAINER_H
+#define EXPOSURESETTINGSCONTAINER_H
+
+// TQt includes.
+
+#include <tqcolor.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT ExposureSettingsContainer
+{
+
+public:
+
+ ExposureSettingsContainer()
+ {
+ underExposureIndicator = false;
+ overExposureIndicator = false;
+
+ underExposureColor = TQt::white;
+ overExposureColor = TQt::black;
+ };
+
+ ~ExposureSettingsContainer(){};
+
+public:
+
+ bool underExposureIndicator;
+ bool overExposureIndicator;
+
+ TQColor underExposureColor;
+ TQColor overExposureColor;
+};
+
+} // namespace Digikam
+
+#endif // EXPOSURESETTINGSCONTAINER_H
diff --git a/src/libs/dimg/filters/Makefile.am b/src/libs/dimg/filters/Makefile.am
new file mode 100644
index 00000000..25c4ee54
--- /dev/null
+++ b/src/libs/dimg/filters/Makefile.am
@@ -0,0 +1,21 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdimgfilters.la
+
+libdimgfilters_la_SOURCES = bcgmodifier.cpp hslmodifier.cpp icctransform.cpp \
+ dimgimagefilters.cpp dimgthreadedfilter.cpp \
+ dimggaussianblur.cpp dimgsharpen.cpp colormodifier.cpp
+
+libdimgfilters_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+
+digikaminclude_HEADERS = bcgmodifier.h hslmodifier.h dimgthreadedfilter.h dimgimagefilters.h \
+ icctransform.h colormodifier.h dimgsharpen.h dimggaussianblur.h
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/dimg/filters/bcgmodifier.cpp b/src/libs/dimg/filters/bcgmodifier.cpp
new file mode 100644
index 00000000..b3899c20
--- /dev/null
+++ b/src/libs/dimg/filters/bcgmodifier.cpp
@@ -0,0 +1,208 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-06
+ * Description : a Brightness/Contrast/Gamma image filter.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define CLAMP_0_255(x) TQMAX(TQMIN(x, 255), 0)
+#define CLAMP_0_65535(x) TQMAX(TQMIN(x, 65535), 0)
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+
+// Local includes.
+
+#include "dimg.h"
+#include "bcgmodifier.h"
+
+namespace Digikam
+{
+
+class BCGModifierPriv
+{
+public:
+
+ BCGModifierPriv()
+ {
+ channel = BCGModifier::CHANNEL_ALL;
+ modified = false;
+ }
+
+ bool modified;
+
+ int channel;
+ int map16[65536];
+ int map[256];
+};
+
+BCGModifier::BCGModifier()
+{
+ d = new BCGModifierPriv;
+ reset();
+}
+
+BCGModifier::~BCGModifier()
+{
+ delete d;
+}
+
+bool BCGModifier::modified() const
+{
+ return d->modified;
+}
+
+void BCGModifier::reset()
+{
+ // initialize to linear mapping
+
+ for (int i=0; i<65536; i++)
+ d->map16[i] = i;
+
+ for (int i=0; i<256; i++)
+ d->map[i] = i;
+
+ d->modified = false;
+}
+
+void BCGModifier::applyBCG(DImg& image)
+{
+ if (!d->modified || image.isNull())
+ return;
+
+ applyBCG(image.bits(), image.width(), image.height(), image.sixteenBit());
+}
+
+void BCGModifier::applyBCG(uchar *bits, uint width, uint height, bool sixteenBits)
+{
+ if (!d->modified || !bits)
+ return;
+
+ uint size = width*height;
+
+ if (!sixteenBits) // 8 bits image.
+ {
+ uchar* data = bits;
+
+ for (uint i=0; i<size; i++)
+ {
+ switch (d->channel)
+ {
+ case CHANNEL_BLUE:
+ data[0] = CLAMP_0_255(d->map[data[0]]);
+ break;
+
+ case CHANNEL_GREEN:
+ data[1] = CLAMP_0_255(d->map[data[1]]);
+ break;
+
+ case CHANNEL_RED:
+ data[2] = CLAMP_0_255(d->map[data[2]]);
+ break;
+
+ default: // CHANNEL_ALL
+ data[0] = CLAMP_0_255(d->map[data[0]]);
+ data[1] = CLAMP_0_255(d->map[data[1]]);
+ data[2] = CLAMP_0_255(d->map[data[2]]);
+ break;
+ }
+
+ data += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ ushort* data = (ushort*)bits;
+
+ for (uint i=0; i<size; i++)
+ {
+ switch (d->channel)
+ {
+ case CHANNEL_BLUE:
+ data[0] = CLAMP_0_65535(d->map16[data[0]]);
+ break;
+
+ case CHANNEL_GREEN:
+ data[1] = CLAMP_0_65535(d->map16[data[1]]);
+ break;
+
+ case CHANNEL_RED:
+ data[2] = CLAMP_0_65535(d->map16[data[2]]);
+ break;
+
+ default: // CHANNEL_ALL
+ data[0] = CLAMP_0_65535(d->map16[data[0]]);
+ data[1] = CLAMP_0_65535(d->map16[data[1]]);
+ data[2] = CLAMP_0_65535(d->map16[data[2]]);
+ break;
+ }
+
+ data += 4;
+ }
+ }
+}
+
+void BCGModifier::setChannel(int channel)
+{
+ d->channel = channel;
+}
+
+void BCGModifier::setGamma(double val)
+{
+ val = (val < 0.01) ? 0.01 : val;
+
+ for (int i=0; i<65536; i++)
+ d->map16[i] = lround(pow(((double)d->map16[i] / 65535.0), (1.0 / val)) * 65535.0);
+
+ for (int i=0; i<256; i++)
+ d->map[i] = lround(pow(((double)d->map[i] / 255.0), (1.0 / val)) * 255.0);
+
+ d->modified = true;
+}
+
+void BCGModifier::setBrightness(double val)
+{
+ int val1 = lround(val * 65535);
+
+ for (int i = 0; i < 65536; i++)
+ d->map16[i] = d->map16[i] + val1;
+
+ val1 = lround(val * 255);
+
+ for (int i = 0; i < 256; i++)
+ d->map[i] = d->map[i] + val1;
+
+ d->modified = true;
+}
+
+void BCGModifier::setContrast(double val)
+{
+ for (int i = 0; i < 65536; i++)
+ d->map16[i] = lround((d->map16[i] - 32767) * val) + 32767;
+
+ for (int i = 0; i < 256; i++)
+ d->map[i] = lround((d->map[i] - 127) * val) + 127;
+
+ d->modified = true;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/bcgmodifier.h b/src/libs/dimg/filters/bcgmodifier.h
new file mode 100644
index 00000000..b0c915d6
--- /dev/null
+++ b/src/libs/dimg/filters/bcgmodifier.h
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-06
+ * Description : a Brightness/Contrast/Gamma image filter.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BCGMODIFIER_H
+#define BCGMODIFIER_H
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImg;
+class BCGModifierPriv;
+
+class DIGIKAM_EXPORT BCGModifier
+{
+
+public:
+
+ enum CHANNEL
+ {
+ CHANNEL_ALL=0,
+ CHANNEL_RED,
+ CHANNEL_GREEN,
+ CHANNEL_BLUE
+ };
+
+public:
+
+ BCGModifier();
+ ~BCGModifier();
+
+ void reset();
+ bool modified() const;
+
+ void setChannel(int channel);
+ void setGamma(double val);
+ void setBrightness(double val);
+ void setContrast(double val);
+ void applyBCG(DImg& image);
+ void applyBCG(uchar *bits, uint width, uint height, bool sixteenBits);
+
+private:
+
+ BCGModifierPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* BCGMODIFIER_H */
diff --git a/src/libs/dimg/filters/colormodifier.cpp b/src/libs/dimg/filters/colormodifier.cpp
new file mode 100644
index 00000000..74ddf241
--- /dev/null
+++ b/src/libs/dimg/filters/colormodifier.cpp
@@ -0,0 +1,287 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-18
+ * Description : color modifier methods for DImg framework
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define CLAMP_0_255(x) TQMAX(TQMIN(x, 255), 0)
+#define CLAMP_0_65535(x) TQMAX(TQMIN(x, 65535), 0)
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+
+// Local includes.
+
+#include "dimg.h"
+#include "colormodifier.h"
+
+namespace Digikam
+{
+
+class ColorModifierPriv
+{
+public:
+
+ ColorModifierPriv()
+ {
+ modified = false;
+ }
+
+ bool modified;
+
+ int redMap[256];
+ int greenMap[256];
+ int blueMap[256];
+ int alphaMap[256];
+
+ int redMap16[65536];
+ int greenMap16[65536];
+ int blueMap16[65536];
+ int alphaMap16[65536];
+};
+
+ColorModifier::ColorModifier()
+{
+ d = new ColorModifierPriv;
+ reset();
+}
+
+ColorModifier::~ColorModifier()
+{
+ delete d;
+}
+
+bool ColorModifier::modified() const
+{
+ return d->modified;
+}
+
+void ColorModifier::reset()
+{
+ // initialize to linear mapping
+
+ for (int i=0; i<65536; i++)
+ {
+ d->redMap16[i] = i;
+ d->greenMap16[i] = i;
+ d->blueMap16[i] = i;
+ d->alphaMap16[i] = i;
+ }
+
+ for (int i=0; i<256; i++)
+ {
+ d->redMap[i] = i;
+ d->greenMap[i] = i;
+ d->blueMap[i] = i;
+ d->alphaMap[i] = i;
+ }
+
+ d->modified = false;
+}
+
+void ColorModifier::setTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit)
+{
+ if (!sixteenBit)
+ {
+ for (int i = 0; i < 256; i++)
+ {
+ if (redMap)
+ d->redMap[i] = redMap[i];
+ if (greenMap)
+ d->greenMap[i] = greenMap[i];
+ if (blueMap)
+ d->blueMap[i] = blueMap[i];
+ if (alphaMap)
+ d->alphaMap[i] = alphaMap[i];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < 65536; i++)
+ {
+ if (redMap)
+ d->redMap16[i] = redMap[i];
+ if (greenMap)
+ d->greenMap16[i] = greenMap[i];
+ if (blueMap)
+ d->blueMap16[i] = blueMap[i];
+ if (alphaMap)
+ d->alphaMap16[i] = alphaMap[i];
+ }
+ }
+
+ d->modified = true;
+}
+
+void ColorModifier::getTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit)
+{
+ if (!sixteenBit)
+ {
+ if (redMap)
+ memcpy(redMap, d->redMap, (256 * sizeof(int)));
+ if (greenMap)
+ memcpy(greenMap, d->greenMap, (256 * sizeof(int)));
+ if (blueMap)
+ memcpy(blueMap, d->blueMap, (256 * sizeof(int)));
+ if (alphaMap)
+ memcpy(alphaMap, d->alphaMap, (256 * sizeof(int)));
+ }
+ else
+ {
+ if (redMap)
+ memcpy(redMap, d->redMap16, (65536 * sizeof(int)));
+ if (greenMap)
+ memcpy(greenMap, d->greenMap16, (65536 * sizeof(int)));
+ if (blueMap)
+ memcpy(blueMap, d->blueMap16, (65536 * sizeof(int)));
+ if (alphaMap)
+ memcpy(alphaMap, d->alphaMap16, (65536 * sizeof(int)));
+ }
+}
+
+void ColorModifier::applyColorModifier(DImg& image, double r, double g, double b, double a)
+{
+ if (image.isNull())
+ return;
+
+ adjustRGB(r, g, b, a, image.sixteenBit());
+
+ if (!image.sixteenBit()) // 8 bits image.
+ {
+ uchar* data = (uchar*) image.bits();
+
+ for (uint i=0; i<image.width()*image.height(); i++)
+ {
+ data[0] = d->blueMap[data[0]];
+ data[1] = d->greenMap[data[1]];
+ data[2] = d->redMap[data[2]];
+ data[3] = d->alphaMap[data[3]];
+
+ data += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ ushort* data = (ushort*) image.bits();
+
+ for (uint i=0; i<image.width()*image.height(); i++)
+ {
+ data[0] = d->blueMap16[data[0]];
+ data[1] = d->greenMap16[data[1]];
+ data[2] = d->redMap16[data[2]];
+ data[3] = d->alphaMap16[data[3]];
+
+ data += 4;
+ }
+ }
+}
+
+void ColorModifier::setGamma(double val)
+{
+ val = (val < 0.01) ? 0.01 : val;
+ int val2;
+
+ for (int i=0; i<65536; i++)
+ {
+ val2 = (int)(pow(((double)d->redMap16[i] / 65535), (1 / val)) * 65535);
+ d->redMap16[i] = CLAMP_0_65535(val2);
+
+ val2 = (int)(pow(((double)d->greenMap16[i] / 65535), (1 / val)) * 65535);
+ d->greenMap16[i] = CLAMP_0_65535(val2);
+
+ val2 = (int)(pow(((double)d->blueMap16[i] / 65535), (1 / val)) * 65535);
+ d->blueMap16[i] = CLAMP_0_65535(val2);
+
+ val2 = (int)(pow(((double)d->alphaMap16[i] / 65535), (1 / val)) * 65535);
+ d->alphaMap16[i] = CLAMP_0_65535(val2);
+ }
+
+ for (int i=0; i<256; i++)
+ {
+ val2 = (int)(pow(((double)d->redMap[i] / 255), (1 / val)) * 255);
+ d->redMap[i] = CLAMP_0_255(val2);
+
+ val2 = (int)(pow(((double)d->greenMap[i] / 255), (1 / val)) * 255);
+ d->greenMap[i] = CLAMP_0_255(val2);
+
+ val2 = (int)(pow(((double)d->blueMap[i] / 255), (1 / val)) * 255);
+ d->blueMap[i] = CLAMP_0_255(val2);
+
+ val2 = (int)(pow(((double)d->alphaMap[i] / 255), (1 / val)) * 255);
+ d->alphaMap[i] = CLAMP_0_255(val2);
+ }
+
+ d->modified = true;
+}
+
+void ColorModifier::adjustRGB(double r, double g, double b, double a, bool sixteenBit)
+{
+ int r_table[65536];
+ int g_table[65536];
+ int b_table[65536];
+ int a_table[65536];
+ int dummy_table[65536];
+
+ if (r == 1.0 && g == 1.0 && b == 1.0 && a == 1.0)
+ return ;
+
+ if (r == g && r == b && r == a)
+ {
+ setGamma(r);
+ }
+ else
+ {
+ getTables(r_table, g_table, b_table, a_table, sixteenBit);
+
+ if(r != 1.0)
+ {
+ setGamma(r);
+ getTables(r_table, dummy_table, dummy_table, dummy_table, sixteenBit);
+ reset();
+ }
+
+ if(g != 1.0)
+ {
+ setGamma(g);
+ getTables(dummy_table, g_table, dummy_table, dummy_table, sixteenBit);
+ reset();
+ }
+
+ if(b != 1.0)
+ {
+ setGamma(b);
+ getTables(dummy_table, dummy_table, b_table, dummy_table, sixteenBit);
+ reset();
+ }
+
+ if(a != 1.0)
+ {
+ setGamma(a);
+ getTables(dummy_table, dummy_table, dummy_table, a_table, sixteenBit);
+ reset();
+ }
+
+ setTables(r_table, g_table, b_table, a_table, sixteenBit);
+ }
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/colormodifier.h b/src/libs/dimg/filters/colormodifier.h
new file mode 100644
index 00000000..9473b273
--- /dev/null
+++ b/src/libs/dimg/filters/colormodifier.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-18
+ * Description : color modifier methods for DImg framework
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef COLORMODIFIER_H
+#define COLORMODIFIER_H
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImg;
+class ColorModifierPriv;
+
+class DIGIKAM_EXPORT ColorModifier
+{
+public:
+
+ ColorModifier();
+ ~ColorModifier();
+
+ void reset();
+ bool modified() const;
+ void applyColorModifier(DImg& image, double r, double g, double b, double a);
+
+private:
+
+ void setTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit);
+ void getTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit);
+ void setGamma(double val);
+ void adjustRGB(double r, double g, double b, double a, bool sixteenBit);
+
+private:
+
+ ColorModifierPriv* d;
+
+};
+
+} // NameSpace Digikam
+
+#endif /* COLORMODIFIER_H */
diff --git a/src/libs/dimg/filters/dimggaussianblur.cpp b/src/libs/dimg/filters/dimggaussianblur.cpp
new file mode 100644
index 00000000..63e19909
--- /dev/null
+++ b/src/libs/dimg/filters/dimggaussianblur.cpp
@@ -0,0 +1,327 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-17-07
+ * Description : A Gaussian Blur threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Gaussian Blur algorithm copyrighted 2004 by
+ * Pieter Z. Voloshyn <pieter_voloshyn at ame dot com dot br>.
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimgimagefilters.h"
+#include "dimggaussianblur.h"
+
+namespace Digikam
+{
+
+DImgGaussianBlur::DImgGaussianBlur(DImg *orgImage, TQObject *parent, int radius)
+ : DImgThreadedFilter(orgImage, parent, "GaussianBlur")
+{
+ m_radius = radius;
+ initFilter();
+}
+
+DImgGaussianBlur::DImgGaussianBlur(DImgThreadedFilter *parentFilter,
+ const DImg &orgImage, const DImg &destImage,
+ int progressBegin, int progressEnd, int radius)
+ : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd,
+ parentFilter->filterName() + ": GaussianBlur")
+{
+ m_radius = radius;
+ filterImage();
+}
+
+
+void DImgGaussianBlur::filterImage(void)
+{
+ gaussianBlurImage(m_orgImage.bits(), m_orgImage.width(), m_orgImage.height(),
+ m_orgImage.sixteenBit(), m_radius);
+}
+
+/** Function to apply the Gaussian Blur on an image*/
+
+void DImgGaussianBlur::gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius)
+{
+ if (!data || !width || !height)
+ {
+ DWarning() << ("DImgGaussianBlur::gaussianBlurImage: no image data available!")
+ << endl;
+ return;
+ }
+
+ if (radius > 100) radius = 100;
+ if (radius <= 0)
+ {
+ m_destImage = m_orgImage;
+ return;
+ }
+
+ // Gaussian kernel computation using the Radius parameter.
+
+ int nKSize, nCenter;
+ double x, sd, factor, lnsd, lnfactor;
+ int i, j, n, h, w;
+
+ nKSize = 2 * radius + 1;
+ nCenter = nKSize / 2;
+ int *Kernel = new int[nKSize];
+
+ lnfactor = (4.2485 - 2.7081) / 10 * nKSize + 2.7081;
+ lnsd = (0.5878 + 0.5447) / 10 * nKSize - 0.5447;
+ factor = exp (lnfactor);
+ sd = exp (lnsd);
+
+ for (i = 0; !m_cancel && (i < nKSize); i++)
+ {
+ x = sqrt ((i - nCenter) * (i - nCenter));
+ Kernel[i] = (int)(factor * exp (-0.5 * pow ((x / sd), 2)) / (sd * sqrt (2.0 * M_PI)));
+ }
+
+ // Now, we need to convolve the image descriptor.
+ // I've worked hard here, but I think this is a very smart
+ // way to convolve an array, its very hard to explain how I reach
+ // this, but the trick here its to store the sum used by the
+ // previous pixel, so we sum with the other pixels that wasn't get.
+
+ int nSumA, nSumR, nSumG, nSumB, nCount, progress;
+ int nKernelWidth = radius * 2 + 1;
+
+ // We need to alloc a 2d array to help us to store the values
+
+ int** arrMult = Alloc2DArray (nKernelWidth, sixteenBit ? 65536 : 256);
+
+ for (i = 0; !m_cancel && (i < nKernelWidth); i++)
+ for (j = 0; !m_cancel && (j < (sixteenBit ? 65536 : 256)); j++)
+ arrMult[i][j] = j * Kernel[i];
+
+ // We need to copy our bits to blur bits
+
+ uchar* pOutBits = m_destImage.bits();
+ uchar* pBlur = new uchar[m_destImage.numBytes()];
+
+ memcpy (pBlur, data, m_destImage.numBytes());
+
+ // We need to initialize all the loop and iterator variables
+
+ nSumA = nSumR = nSumG = nSumB = nCount = i = j = 0;
+ unsigned short* data16 = (unsigned short*)data;
+ unsigned short* pBlur16 = (unsigned short*)pBlur;
+ unsigned short* pOutBits16 = (unsigned short*)pOutBits;
+
+ // Now, we enter in the main loop
+
+ for (h = 0; !m_cancel && (h < height); h++)
+ {
+ for (w = 0; !m_cancel && (w < width); w++, i+=4)
+ {
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar *org, *dst;
+
+ // first of all, we need to blur the horizontal lines
+
+ for (n = -radius; !m_cancel && (n <= radius); n++)
+ {
+ // if is inside...
+ if (IsInside (width, height, w + n, h))
+ {
+ // we points to the pixel
+ j = i + 4*n;
+
+ // finally, we sum the pixels using a method similar to assigntables
+
+ org = &data[j];
+ nSumA += arrMult[n + radius][org[3]];
+ nSumR += arrMult[n + radius][org[2]];
+ nSumG += arrMult[n + radius][org[1]];
+ nSumB += arrMult[n + radius][org[0]];
+
+ // we need to add to the counter, the kernel value
+ nCount += Kernel[n + radius];
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // now, we return to blur bits the horizontal blur values
+ dst = &pBlur[i];
+ dst[3] = (uchar)CLAMP (nSumA / nCount, 0, 255);
+ dst[2] = (uchar)CLAMP (nSumR / nCount, 0, 255);
+ dst[1] = (uchar)CLAMP (nSumG / nCount, 0, 255);
+ dst[0] = (uchar)CLAMP (nSumB / nCount, 0, 255);
+
+ // ok, now we reinitialize the variables
+ nSumA = nSumR = nSumG = nSumB = nCount = 0;
+ }
+ else // 16 bits image.
+ {
+ unsigned short *org, *dst;
+
+ // first of all, we need to blur the horizontal lines
+
+ for (n = -radius; !m_cancel && (n <= radius); n++)
+ {
+ // if is inside...
+ if (IsInside (width, height, w + n, h))
+ {
+ // we points to the pixel
+ j = i + 4*n;
+
+ // finally, we sum the pixels using a method similar to assigntables
+
+ org = &data16[j];
+ nSumA += arrMult[n + radius][org[3]];
+ nSumR += arrMult[n + radius][org[2]];
+ nSumG += arrMult[n + radius][org[1]];
+ nSumB += arrMult[n + radius][org[0]];
+
+ // we need to add to the counter, the kernel value
+ nCount += Kernel[n + radius];
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // now, we return to blur bits the horizontal blur values
+ dst = &pBlur16[i];
+ dst[3] = (unsigned short)CLAMP (nSumA / nCount, 0, 65535);
+ dst[2] = (unsigned short)CLAMP (nSumR / nCount, 0, 65535);
+ dst[1] = (unsigned short)CLAMP (nSumG / nCount, 0, 65535);
+ dst[0] = (unsigned short)CLAMP (nSumB / nCount, 0, 65535);
+
+ // ok, now we reinitialize the variables
+ nSumA = nSumR = nSumG = nSumB = nCount = 0;
+ }
+ }
+
+ progress = (int) (((double)h * 50.0) / height);
+ if ( progress%5 == 0 )
+ postProgress( progress );
+ }
+
+ // getting the blur bits, we initialize position variables
+ i = j = 0;
+
+ // We enter in the second main loop
+ for (w = 0; !m_cancel && (w < width); w++, i = w*4)
+ {
+ for (h = 0; !m_cancel && (h < height); h++, i += width*4)
+ {
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar *org, *dst;
+
+ // first of all, we need to blur the vertical lines
+ for (n = -radius; !m_cancel && (n <= radius); n++)
+ {
+ // if is inside...
+ if (IsInside(width, height, w, h + n))
+ {
+ // we points to the pixel
+ j = i + n * 4 * width;
+
+ // finally, we sum the pixels using a method similar to assigntables
+ org = &pBlur[j];
+ nSumA += arrMult[n + radius][org[3]];
+ nSumR += arrMult[n + radius][org[2]];
+ nSumG += arrMult[n + radius][org[1]];
+ nSumB += arrMult[n + radius][org[0]];
+
+ // we need to add to the counter, the kernel value
+ nCount += Kernel[n + radius];
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // To preserve Alpha channel.
+ memcpy (&pOutBits[i], &data[i], 4);
+
+ // now, we return to bits the vertical blur values
+ dst = &pOutBits[i];
+ dst[3] = (uchar)CLAMP (nSumA / nCount, 0, 255);
+ dst[2] = (uchar)CLAMP (nSumR / nCount, 0, 255);
+ dst[1] = (uchar)CLAMP (nSumG / nCount, 0, 255);
+ dst[0] = (uchar)CLAMP (nSumB / nCount, 0, 255);
+
+ // ok, now we reinitialize the variables
+ nSumA = nSumR = nSumG = nSumB = nCount = 0;
+ }
+ else // 16 bits image.
+ {
+ unsigned short *org, *dst;
+
+ // first of all, we need to blur the vertical lines
+ for (n = -radius; !m_cancel && (n <= radius); n++)
+ {
+ // if is inside...
+ if (IsInside(width, height, w, h + n))
+ {
+ // we points to the pixel
+ j = i + n * 4 * width;
+
+ // finally, we sum the pixels using a method similar to assigntables
+ org = &pBlur16[j];
+ nSumA += arrMult[n + radius][org[3]];
+ nSumR += arrMult[n + radius][org[2]];
+ nSumG += arrMult[n + radius][org[1]];
+ nSumB += arrMult[n + radius][org[0]];
+
+ // we need to add to the counter, the kernel value
+ nCount += Kernel[n + radius];
+ }
+ }
+
+ if (nCount == 0) nCount = 1;
+
+ // To preserve Alpha channel.
+ memcpy (&pOutBits16[i], &data16[i], 8);
+
+ // now, we return to bits the vertical blur values
+ dst = &pOutBits16[i];
+ dst[3] = (unsigned short)CLAMP (nSumA / nCount, 0, 65535);
+ dst[2] = (unsigned short)CLAMP (nSumR / nCount, 0, 65535);
+ dst[1] = (unsigned short)CLAMP (nSumG / nCount, 0, 65535);
+ dst[0] = (unsigned short)CLAMP (nSumB / nCount, 0, 65535);
+
+ // ok, now we reinitialize the variables
+ nSumA = nSumR = nSumG = nSumB = nCount = 0;
+ }
+ }
+
+ progress = (int) (50.0 + ((double)w * 50.0) / width);
+ if ( progress%5 == 0 )
+ postProgress( progress );
+ }
+
+ // now, we must free memory
+ Free2DArray (arrMult, nKernelWidth);
+ delete [] pBlur;
+ delete [] Kernel;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/dimggaussianblur.h b/src/libs/dimg/filters/dimggaussianblur.h
new file mode 100644
index 00000000..e88944bc
--- /dev/null
+++ b/src/libs/dimg/filters/dimggaussianblur.h
@@ -0,0 +1,97 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-17-07
+ * Description : A Gaussian Blur threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGGAUSSIAN_BLUR_H
+#define DIMGGAUSSIAN_BLUR_H
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+// Local includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DImgGaussianBlur : public DImgThreadedFilter
+{
+
+public:
+
+ DImgGaussianBlur(DImg *orgImage, TQObject *parent=0, int radius=3);
+
+ // Constructor for slave mode: execute immediately in current thread with specified master filter
+ DImgGaussianBlur(DImgThreadedFilter *parentFilter, const DImg &orgImage, const DImg &destImage,
+ int progressBegin=0, int progressEnd=100, int radius=3);
+
+ ~DImgGaussianBlur(){};
+
+private: // Gaussian blur filter data.
+
+ int m_radius;
+
+private: // Gaussian blur filter methods.
+
+ virtual void filterImage(void);
+
+ void gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius);
+
+ // function to allocate a 2d array
+ int** Alloc2DArray (int Columns, int Rows)
+ {
+ // First, we declare our future 2d array to be returned
+ int** lpcArray = 0L;
+
+ // Now, we alloc the main pointer with Columns
+ lpcArray = new int*[Columns];
+
+ for (int i = 0; i < Columns; i++)
+ lpcArray[i] = new int[Rows];
+
+ return (lpcArray);
+ };
+
+ // Function to deallocates the 2d array previously created
+ void Free2DArray (int** lpcArray, int Columns)
+ {
+ // loop to dealocate the columns
+ for (int i = 0; i < Columns; i++)
+ delete [] lpcArray[i];
+
+ // now, we delete the main pointer
+ delete [] lpcArray;
+ };
+
+ inline bool IsInside (int Width, int Height, int X, int Y)
+ {
+ bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true);
+ bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true);
+ return (bIsWOk && bIsHOk);
+ };
+};
+
+} // NameSpace Digikam
+
+#endif /* DIMGGAUSSIAN_BLUR_H */
diff --git a/src/libs/dimg/filters/dimgimagefilters.cpp b/src/libs/dimg/filters/dimgimagefilters.cpp
new file mode 100644
index 00000000..b964ed4d
--- /dev/null
+++ b/src/libs/dimg/filters/dimgimagefilters.cpp
@@ -0,0 +1,977 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-24-01
+ * Description : misc image filters
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Equalise and StretchContrast Algorithms copyright 2002
+ * by Daniel M. Duley <mosfet@kde.org> from KImageEffect API.
+ *
+ * Original Normalize Image algorithm copyrighted 1997 by
+ * Adam D. Moss <adam@foxbox.org> from Gimp 2.0 implementation.
+ *
+ * Original channel mixer algorithm copyrighted 2002 by
+ * Martin Guldahl <mguldahl at xmission dot com> from Gimp 2.2
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstring>
+#include <cstdlib>
+
+// Local includes.
+
+#include "imagehistogram.h"
+#include "imagelevels.h"
+#include "dcolor.h"
+#include "ddebug.h"
+#include "dimggaussianblur.h"
+#include "dimgsharpen.h"
+#include "dimgimagefilters.h"
+
+namespace Digikam
+{
+
+/** Performs an histogram equalisation of the image.
+ this method adjusts the brightness of colors across the
+ active image so that the histogram for the value channel
+ is as nearly as possible flat, that is, so that each possible
+ brightness value appears at about the same number of pixels
+ as each other value. Sometimes Equalize works wonderfully at
+ enhancing the contrasts in an image. Other times it gives
+ garbage. It is a very powerful operation, which can either work
+ miracles on an image or destroy it.*/
+void DImgImageFilters::equalizeImage(uchar *data, int w, int h, bool sixteenBit)
+{
+ if (!data || !w || !h)
+ {
+ DWarning() << ("DImgImageFilters::equalizeImage: no image data available!") << endl;
+ return;
+ }
+
+ struct double_packet high, low, intensity;
+ struct double_packet *map;
+ struct int_packet *equalize_map;
+ long i;
+
+ // Create an histogram of the current image.
+ ImageHistogram *histogram = new ImageHistogram(data, w, h, sixteenBit);
+
+ // Memory allocation.
+ map = new double_packet[histogram->getHistogramSegment()];
+ equalize_map = new int_packet[histogram->getHistogramSegment()];
+
+ if( !histogram || !map || !equalize_map )
+ {
+ if(histogram)
+ delete histogram;
+
+ if(map)
+ delete [] map;
+
+ if(equalize_map)
+ delete [] equalize_map;
+
+ DWarning() << ("DImgImageFilters::equalizeImage: Unable to allocate memory!") << endl;
+ return;
+ }
+
+ // Integrate the histogram to get the equalization map.
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+ memset(&high, 0, sizeof(struct double_packet));
+ memset(&low, 0, sizeof(struct double_packet));
+
+ for(i = 0 ; i < histogram->getHistogramSegment() ; i++)
+ {
+ intensity.red += histogram->getValue(ImageHistogram::RedChannel, i);
+ intensity.green += histogram->getValue(ImageHistogram::GreenChannel, i);
+ intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, i);
+ intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, i);
+ map[i] = intensity;
+ }
+
+ // Stretch the histogram.
+
+ low = map[0];
+ high = map[histogram->getHistogramSegment()-1];
+ memset(equalize_map, 0, histogram->getHistogramSegment()*sizeof(int_packet));
+
+ for(i = 0 ; i < histogram->getHistogramSegment() ; i++)
+ {
+ if(high.red != low.red)
+ equalize_map[i].red = (uint)(((256*histogram->getHistogramSegment() -1) *
+ (map[i].red-low.red))/(high.red-low.red));
+
+ if(high.green != low.green)
+ equalize_map[i].green = (uint)(((256*histogram->getHistogramSegment() -1) *
+ (map[i].green-low.green))/(high.green-low.green));
+
+ if(high.blue != low.blue)
+ equalize_map[i].blue = (uint)(((256*histogram->getHistogramSegment() -1) *
+ (map[i].blue-low.blue))/(high.blue-low.blue));
+
+ if(high.alpha != low.alpha)
+ equalize_map[i].alpha = (uint)(((256*histogram->getHistogramSegment() -1) *
+ (map[i].alpha-low.alpha))/(high.alpha-low.alpha));
+ }
+
+ delete histogram;
+ delete [] map;
+
+ // Apply results to image.
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue, alpha;
+ uchar *ptr = data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if(low.red != high.red)
+ red = (equalize_map[red].red)/257;
+
+ if(low.green != high.green)
+ green = (equalize_map[green].green)/257;
+
+ if(low.blue != high.blue)
+ blue = (equalize_map[blue].blue)/257;
+
+ if(low.alpha != high.alpha)
+ alpha = (equalize_map[alpha].alpha)/257;
+
+ ptr[0] = blue;
+ ptr[1] = green;
+ ptr[2] = red;
+ ptr[3] = alpha;
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue, alpha;
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if(low.red != high.red)
+ red = (equalize_map[red].red)/257;
+
+ if(low.green != high.green)
+ green = (equalize_map[green].green)/257;
+
+ if(low.blue != high.blue)
+ blue = (equalize_map[blue].blue)/257;
+
+ if(low.alpha != high.alpha)
+ alpha = (equalize_map[alpha].alpha)/257;
+
+ ptr[0] = blue;
+ ptr[1] = green;
+ ptr[2] = red;
+ ptr[3] = alpha;
+ ptr += 4;
+ }
+ }
+
+ delete [] equalize_map;
+}
+
+/** Performs histogram normalization of the image. The algorithm normalizes
+ the pixel values from an image for to span the full range
+ of color values. This is a contrast enhancement technique.*/
+void DImgImageFilters::stretchContrastImage(uchar *data, int w, int h, bool sixteenBit)
+{
+ if (!data || !w || !h)
+ {
+ DWarning() << ("DImgImageFilters::stretchContrastImage: no image data available!") << endl;
+ return;
+ }
+
+ struct double_packet high, low, intensity;
+ struct int_packet *normalize_map;
+ long long number_pixels;
+ long i;
+ unsigned long threshold_intensity;
+
+ // Create an histogram of the current image.
+ ImageHistogram *histogram = new ImageHistogram(data, w, h, sixteenBit);
+
+ // Memory allocation.
+ normalize_map = new int_packet[histogram->getHistogramSegment()];
+
+ if( !histogram || !normalize_map )
+ {
+ if(histogram)
+ delete histogram;
+
+ if(normalize_map)
+ delete [] normalize_map;
+
+ DWarning() << ("DImgImageFilters::stretchContrastImage: Unable to allocate memory!") << endl;
+ return;
+ }
+
+ // Find the histogram boundaries by locating the 0.1 percent levels.
+
+ number_pixels = (long long)(w*h);
+ threshold_intensity = number_pixels / 1000;
+
+ memset(&high, 0, sizeof(struct double_packet));
+ memset(&low, 0, sizeof(struct double_packet));
+
+ // Red.
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.red = histogram->getHistogramSegment()-1 ; high.red != 0 ; high.red--)
+ {
+ intensity.red += histogram->getValue(ImageHistogram::RedChannel, (int)high.red);
+
+ if( intensity.red > threshold_intensity )
+ break;
+ }
+
+ if( low.red == high.red )
+ {
+ threshold_intensity = 0;
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(low.red = 0 ; low.red < histogram->getHistogramSegment()-1 ; low.red++)
+ {
+ intensity.red += histogram->getValue(ImageHistogram::RedChannel, (int)low.red);
+
+ if( intensity.red > threshold_intensity )
+ break;
+ }
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.red = histogram->getHistogramSegment()-1 ; high.red != 0 ; high.red--)
+ {
+ intensity.red += histogram->getValue(ImageHistogram::RedChannel, (int)high.red);
+
+ if( intensity.red > threshold_intensity )
+ break;
+ }
+ }
+
+ // Green.
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.green = histogram->getHistogramSegment()-1 ; high.green != 0 ; high.green--)
+ {
+ intensity.green += histogram->getValue(ImageHistogram::GreenChannel, (int)high.green);
+
+ if( intensity.green > threshold_intensity )
+ break;
+ }
+
+ if( low.green == high.green )
+ {
+ threshold_intensity = 0;
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(low.green = 0 ; low.green < histogram->getHistogramSegment()-1 ; low.green++)
+ {
+ intensity.green += histogram->getValue(ImageHistogram::GreenChannel, (int)low.green);
+
+ if( intensity.green > threshold_intensity )
+ break;
+ }
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.green = histogram->getHistogramSegment()-1 ; high.green != 0 ; high.green--)
+ {
+ intensity.green += histogram->getValue(ImageHistogram::GreenChannel, (int)high.green);
+
+ if( intensity.green > threshold_intensity )
+ break;
+ }
+ }
+
+ // Blue.
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.blue = histogram->getHistogramSegment()-1 ; high.blue != 0 ; high.blue--)
+ {
+ intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, (int)high.blue);
+
+ if( intensity.blue > threshold_intensity )
+ break;
+ }
+
+ if( low.blue == high.blue )
+ {
+ threshold_intensity = 0;
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(low.blue = 0 ; low.blue < histogram->getHistogramSegment()-1 ; low.blue++)
+ {
+ intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, (int)low.blue);
+
+ if( intensity.blue > threshold_intensity )
+ break;
+ }
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.blue = histogram->getHistogramSegment()-1 ; high.blue != 0 ; high.blue--)
+ {
+ intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, (int)high.blue);
+
+ if( intensity.blue > threshold_intensity )
+ break;
+ }
+ }
+
+ // Alpha.
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.alpha = histogram->getHistogramSegment()-1 ; high.alpha != 0 ; high.alpha--)
+ {
+ intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, (int)high.alpha);
+
+ if( intensity.alpha > threshold_intensity )
+ break;
+ }
+
+ if( low.alpha == high.alpha )
+ {
+ threshold_intensity = 0;
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(low.alpha = 0 ; low.alpha < histogram->getHistogramSegment()-1 ; low.alpha++)
+ {
+ intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, (int)low.alpha);
+
+ if( intensity.alpha > threshold_intensity )
+ break;
+ }
+
+ memset(&intensity, 0, sizeof(struct double_packet));
+
+ for(high.alpha = histogram->getHistogramSegment()-1 ; high.alpha != 0 ; high.alpha--)
+ {
+ intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, (int)high.alpha);
+
+ if( intensity.alpha > threshold_intensity )
+ break;
+ }
+ }
+
+ delete histogram;
+
+ // Stretch the histogram to create the normalized image mapping.
+
+ memset(normalize_map, 0, histogram->getHistogramSegment()*sizeof(struct int_packet));
+
+ for(i = 0 ; i <= (long)histogram->getHistogramSegment()-1 ; i++)
+ {
+ if(i < (long) low.red)
+ normalize_map[i].red = 0;
+ else if (i > (long) high.red)
+ normalize_map[i].red = (256*histogram->getHistogramSegment() -1);
+ else if (low.red != high.red)
+ normalize_map[i].red = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.red))/(high.red-low.red));
+
+ if(i < (long) low.green)
+ normalize_map[i].green = 0;
+ else if (i > (long) high.green)
+ normalize_map[i].green = (256*histogram->getHistogramSegment() -1);
+ else if (low.green != high.green)
+ normalize_map[i].green = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.green))/(high.green-low.green));
+
+ if(i < (long) low.blue)
+ normalize_map[i].blue = 0;
+ else if (i > (long) high.blue)
+ normalize_map[i].blue = (256*histogram->getHistogramSegment() -1);
+ else if (low.blue != high.blue)
+ normalize_map[i].blue = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.blue))/(high.blue-low.blue));
+
+ if(i < (long) low.alpha)
+ normalize_map[i].alpha = 0;
+ else if (i > (long) high.alpha)
+ normalize_map[i].alpha = (256*histogram->getHistogramSegment() -1);
+ else if (low.alpha != high.alpha)
+ normalize_map[i].alpha = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.alpha))/(high.alpha-low.alpha));
+ }
+
+ // Apply result to image.
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue, alpha;
+ uchar *ptr = data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if(low.red != high.red)
+ red = (normalize_map[red].red)/257;
+
+ if(low.green != high.green)
+ green = (normalize_map[green].green)/257;
+
+ if(low.blue != high.blue)
+ blue = (normalize_map[blue].blue)/257;
+
+ if(low.alpha != high.alpha)
+ alpha = (normalize_map[alpha].alpha)/257;
+
+ ptr[0] = blue;
+ ptr[1] = green;
+ ptr[2] = red;
+ ptr[3] = alpha;
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue, alpha;
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if(low.red != high.red)
+ red = (normalize_map[red].red)/257;
+
+ if(low.green != high.green)
+ green = (normalize_map[green].green)/257;
+
+ if(low.blue != high.blue)
+ blue = (normalize_map[blue].blue)/257;
+
+ if(low.alpha != high.alpha)
+ alpha = (normalize_map[alpha].alpha)/257;
+
+ ptr[0] = blue;
+ ptr[1] = green;
+ ptr[2] = red;
+ ptr[3] = alpha;
+ ptr += 4;
+ }
+ }
+
+ delete [] normalize_map;
+}
+
+/** This method scales brightness values across the active
+ image so that the darkest point becomes black, and the
+ brightest point becomes as bright as possible without
+ altering its hue. This is often a magic fix for
+ images that are dim or washed out.*/
+void DImgImageFilters::normalizeImage(uchar *data, int w, int h, bool sixteenBit)
+{
+ NormalizeParam param;
+ int x, i;
+ unsigned short range;
+
+ int segments = sixteenBit ? 65536 : 256;
+
+ // Memory allocation.
+
+ param.lut = new unsigned short[segments];
+
+ // Find min. and max. values.
+
+ param.min = segments-1;
+ param.max = 0;
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue;
+ uchar *ptr = data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ if (red < param.min) param.min = red;
+ if (red > param.max) param.max = red;
+
+ if (green < param.min) param.min = green;
+ if (green > param.max) param.max = green;
+
+ if (blue < param.min) param.min = blue;
+ if (blue > param.max) param.max = blue;
+
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue;
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ if (red < param.min) param.min = red;
+ if (red > param.max) param.max = red;
+
+ if (green < param.min) param.min = green;
+ if (green > param.max) param.max = green;
+
+ if (blue < param.min) param.min = blue;
+ if (blue > param.max) param.max = blue;
+
+ ptr += 4;
+ }
+ }
+
+ // Calculate LUT.
+
+ range = (unsigned short)(param.max - param.min);
+
+ if (range != 0)
+ {
+ for (x = (int)param.min ; x <= (int)param.max ; x++)
+ param.lut[x] = (unsigned short)((segments-1) * (x - param.min) / range);
+ }
+ else
+ param.lut[(int)param.min] = (unsigned short)param.min;
+
+ // Apply LUT to image.
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue;
+ uchar *ptr = data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ ptr[0] = param.lut[blue];
+ ptr[1] = param.lut[green];
+ ptr[2] = param.lut[red];
+
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue;
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ ptr[0] = param.lut[blue];
+ ptr[1] = param.lut[green];
+ ptr[2] = param.lut[red];
+
+ ptr += 4;
+ }
+ }
+
+ delete [] param.lut;
+}
+
+/** Performs histogram auto correction of levels.
+ This method maximizes the tonal range in the Red,
+ Green, and Blue channels. It search the image shadow and highlight
+ limit values and adjust the Red, Green, and Blue channels
+ to a full histogram range.*/
+void DImgImageFilters::autoLevelsCorrectionImage(uchar *data, int w, int h, bool sixteenBit)
+{
+ if (!data || !w || !h)
+ {
+ DWarning() << ("DImgImageFilters::autoLevelsCorrectionImage: no image data available!")
+ << endl;
+ return;
+ }
+ uchar* desData;
+
+ // Create the new empty destination image data space.
+ if (sixteenBit)
+ desData = new uchar[w*h*8];
+ else
+ desData = new uchar[w*h*4];
+
+ // Create an histogram of the current image.
+ ImageHistogram *histogram = new ImageHistogram(data, w, h, sixteenBit);
+
+ // Create an empty instance of levels to use.
+ ImageLevels *levels = new ImageLevels(sixteenBit);
+
+ // Initialize an auto levels correction of the histogram.
+ levels->levelsAuto(histogram);
+
+ // Calculate the LUT to apply on the image.
+ levels->levelsLutSetup(ImageHistogram::AlphaChannel);
+
+ // Apply the lut to the image.
+ levels->levelsLutProcess(data, desData, w, h);
+
+ if (sixteenBit)
+ memcpy (data, desData, w*h*8);
+ else
+ memcpy (data, desData, w*h*4);
+
+ delete [] desData;
+ delete histogram;
+ delete levels;
+}
+
+/** Performs image colors inversion. This tool is used for negate image
+ resulting of a positive film scanned.*/
+void DImgImageFilters::invertImage(uchar *data, int w, int h, bool sixteenBit)
+{
+ if (!data || !w || !h)
+ {
+ DWarning() << ("DImgImageFilters::invertImage: no image data available!")
+ << endl;
+ return;
+ }
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar *ptr = data;
+
+ for (int i = 0 ; i < w*h ; i++)
+ {
+ ptr[0] = 255 - ptr[0];
+ ptr[1] = 255 - ptr[1];
+ ptr[2] = 255 - ptr[2];
+ ptr[3] = 255 - ptr[3];
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (int i = 0 ; i < w*h ; i++)
+ {
+ ptr[0] = 65535 - ptr[0];
+ ptr[1] = 65535 - ptr[1];
+ ptr[2] = 65535 - ptr[2];
+ ptr[3] = 65535 - ptr[3];
+ ptr += 4;
+ }
+ }
+}
+
+/** Mix RGB channel color from image*/
+void DImgImageFilters::channelMixerImage(uchar *data, int Width, int Height, bool sixteenBit,
+ bool bPreserveLum, bool bMonochrome,
+ float rrGain, float rgGain, float rbGain,
+ float grGain, float ggGain, float gbGain,
+ float brGain, float bgGain, float bbGain)
+{
+ if (!data || !Width || !Height)
+ {
+ DWarning() << ("DImgImageFilters::channelMixerImage: no image data available!")
+ << endl;
+ return;
+ }
+
+ int i;
+
+ double rnorm = CalculateNorm (rrGain, rgGain, rbGain, bPreserveLum);
+ double gnorm = CalculateNorm (grGain, ggGain, gbGain, bPreserveLum);
+ double bnorm = CalculateNorm (brGain, bgGain, bbGain, bPreserveLum);
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar nGray, red, green, blue;
+ uchar *ptr = data;
+
+ for (i = 0 ; i < Width*Height ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ if (bMonochrome)
+ {
+ nGray = MixPixel (rrGain, rgGain, rbGain,
+ (unsigned short)red, (unsigned short)green, (unsigned short)blue,
+ sixteenBit, rnorm);
+ ptr[0] = ptr[1] = ptr[2] = nGray;
+ }
+ else
+ {
+ ptr[0] = (uchar)MixPixel (brGain, bgGain, bbGain,
+ (unsigned short)red, (unsigned short)green, (unsigned short)blue,
+ sixteenBit, bnorm);
+ ptr[1] = (uchar)MixPixel (grGain, ggGain, gbGain,
+ (unsigned short)red, (unsigned short)green, (unsigned short)blue,
+ sixteenBit, gnorm);
+ ptr[2] = (uchar)MixPixel (rrGain, rgGain, rbGain,
+ (unsigned short)red, (unsigned short)green, (unsigned short)blue,
+ sixteenBit, rnorm);
+ }
+
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short nGray, red, green, blue;
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (i = 0 ; i < Width*Height ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ if (bMonochrome)
+ {
+ nGray = MixPixel (rrGain, rgGain, rbGain, red, green, blue, sixteenBit, rnorm);
+ ptr[0] = ptr[1] = ptr[2] = nGray;
+ }
+ else
+ {
+ ptr[0] = MixPixel (brGain, bgGain, bbGain, red, green, blue, sixteenBit, bnorm);
+ ptr[1] = MixPixel (grGain, ggGain, gbGain, red, green, blue, sixteenBit, gnorm);
+ ptr[2] = MixPixel (rrGain, rgGain, rbGain, red, green, blue, sixteenBit, rnorm);
+ }
+
+ ptr += 4;
+ }
+ }
+}
+
+/** Change color tonality of an image to appling a RGB color mask.*/
+void DImgImageFilters::changeTonality(uchar *data, int width, int height, bool sixteenBit,
+ int redMask, int greenMask, int blueMask)
+{
+ if (!data || !width || !height)
+ {
+ DWarning() << ("DImgImageFilters::changeTonality: no image data available!")
+ << endl;
+ return;
+ }
+
+ int hue, sat, lig;
+
+ DColor mask(redMask, greenMask, blueMask, 0, sixteenBit);
+ mask.getHSL(&hue, &sat, &lig);
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar *ptr = data;
+
+ for (int i = 0 ; i < width*height ; i++)
+ {
+ // Convert to grayscale using tonal mask
+
+ lig = ROUND (0.3 * ptr[2] + 0.59 * ptr[1] + 0.11 * ptr[0]);
+
+ mask.setRGB(hue, sat, lig, sixteenBit);
+
+ ptr[0] = (uchar)mask.blue();
+ ptr[1] = (uchar)mask.green();
+ ptr[2] = (uchar)mask.red();
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (int i = 0 ; i < width*height ; i++)
+ {
+ // Convert to grayscale using tonal mask
+
+ lig = ROUND (0.3 * ptr[2] + 0.59 * ptr[1] + 0.11 * ptr[0]);
+
+ mask.setRGB(hue, sat, lig, sixteenBit);
+
+ ptr[0] = (unsigned short)mask.blue();
+ ptr[1] = (unsigned short)mask.green();
+ ptr[2] = (unsigned short)mask.red();
+ ptr += 4;
+ }
+ }
+}
+
+/** Function to apply the GaussianBlur on an image. This method do not use a
+ dedicaced thread.*/
+void DImgImageFilters::gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius)
+{
+ if (!data || !width || !height)
+ {
+ DWarning() << ("DImgImageFilters::gaussianBlurImage: no image data available!")
+ << endl;
+ return;
+ }
+
+ if (radius > 100) radius = 100;
+ if (radius <= 0) return;
+
+ DImg orgImage(width, height, sixteenBit, true, data);
+ DImgGaussianBlur *filter = new DImgGaussianBlur(&orgImage, 0L, radius);
+ DImg imDest = filter->getTargetImage();
+ memcpy( data, imDest.bits(), imDest.numBytes() );
+ delete filter;
+}
+
+/** Function to apply the sharpen filter on an image. This method do not use a
+ dedicaced thread.*/
+void DImgImageFilters::sharpenImage(uchar *data, int width, int height, bool sixteenBit, int radius)
+{
+ if (!data || !width || !height)
+ {
+ DWarning() << ("DImgImageFilters::sharpenImage: no image data available!")
+ << endl;
+ return;
+ }
+
+ if (radius > 100) radius = 100;
+ if (radius <= 0) return;
+
+ DImg orgImage(width, height, sixteenBit, true, data);
+ DImgSharpen *filter = new DImgSharpen(&orgImage, 0L, radius);
+ DImg imDest = filter->getTargetImage();
+ memcpy( data, imDest.bits(), imDest.numBytes() );
+ delete filter;
+}
+
+/** Function to perform pixel antialiasing with 8 bits/color/pixel images. This method is used to smooth target
+ image in transformation method like free rotation or shear tool. */
+void DImgImageFilters::pixelAntiAliasing(uchar *data, int Width, int Height, double X, double Y,
+ uchar *A, uchar *R, uchar *G, uchar *B)
+{
+ int nX, nY, j;
+ double lfWeightX[2], lfWeightY[2], lfWeight;
+ double lfTotalR = 0.0, lfTotalG = 0.0, lfTotalB = 0.0, lfTotalA = 0.0;
+
+ nX = (int)X;
+ nY = (int)Y;
+
+ if (Y >= 0.0)
+ lfWeightY[0] = 1.0 - (lfWeightY[1] = Y - (double)nY);
+ else
+ lfWeightY[1] = 1.0 - (lfWeightY[0] = -(Y - (double)nY));
+
+ if (X >= 0.0)
+ lfWeightX[0] = 1.0 - (lfWeightX[1] = X - (double)nX);
+ else
+ lfWeightX[1] = 1.0 - (lfWeightX[0] = -(X - (double)nX));
+
+ for (int loopx = 0; loopx <= 1; loopx++)
+ {
+ for (int loopy = 0; loopy <= 1; loopy++)
+ {
+ lfWeight = lfWeightX[loopx] * lfWeightY[loopy];
+ j = setPositionAdjusted (Width, Height, nX + loopx, nY + loopy);
+
+ lfTotalB += ((double)data[j] * lfWeight);
+ j++;
+ lfTotalG += ((double)data[j] * lfWeight);
+ j++;
+ lfTotalR += ((double)data[j] * lfWeight);
+ j++;
+ lfTotalA += ((double)data[j] * lfWeight);
+ j++;
+ }
+ }
+
+ *B = CLAMP0255((int)lfTotalB);
+ *G = CLAMP0255((int)lfTotalG);
+ *R = CLAMP0255((int)lfTotalR);
+ *A = CLAMP0255((int)lfTotalA);
+}
+
+/** Function to perform pixel antialiasing with 16 bits/color/pixel images. This method is used to smooth target
+ image in transformation method like free rotation or shear tool. */
+void DImgImageFilters::pixelAntiAliasing16(unsigned short *data, int Width, int Height, double X, double Y,
+ unsigned short *A, unsigned short *R, unsigned short *G,
+ unsigned short *B)
+{
+ int nX, nY, j;
+ double lfWeightX[2], lfWeightY[2], lfWeight;
+ double lfTotalR = 0.0, lfTotalG = 0.0, lfTotalB = 0.0, lfTotalA = 0.0;
+
+ nX = (int)X;
+ nY = (int)Y;
+
+ if (Y >= 0.0)
+ lfWeightY[0] = 1.0 - (lfWeightY[1] = Y - (double)nY);
+ else
+ lfWeightY[1] = 1.0 - (lfWeightY[0] = -(Y - (double)nY));
+
+ if (X >= 0.0)
+ lfWeightX[0] = 1.0 - (lfWeightX[1] = X - (double)nX);
+ else
+ lfWeightX[1] = 1.0 - (lfWeightX[0] = -(X - (double)nX));
+
+ for (int loopx = 0; loopx <= 1; loopx++)
+ {
+ for (int loopy = 0; loopy <= 1; loopy++)
+ {
+ lfWeight = lfWeightX[loopx] * lfWeightY[loopy];
+ j = setPositionAdjusted (Width, Height, nX + loopx, nY + loopy);
+
+ lfTotalB += ((double)data[j] * lfWeight);
+ j++;
+ lfTotalG += ((double)data[j] * lfWeight);
+ j++;
+ lfTotalR += ((double)data[j] * lfWeight);
+ j++;
+ lfTotalA += ((double)data[j] * lfWeight);
+ j++;
+ }
+ }
+
+ *B = CLAMP065535((int)lfTotalB);
+ *G = CLAMP065535((int)lfTotalG);
+ *R = CLAMP065535((int)lfTotalR);
+ *A = CLAMP065535((int)lfTotalA);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/dimgimagefilters.h b/src/libs/dimg/filters/dimgimagefilters.h
new file mode 100644
index 00000000..009c3efb
--- /dev/null
+++ b/src/libs/dimg/filters/dimgimagefilters.h
@@ -0,0 +1,133 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-24-01
+ * Description : misc image filters
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGIMAGE_FILTERS_H
+#define DIMGIMAGE_FILTERS_H
+
+#define CLAMP0255(a) TQMIN(TQMAX(a,0), 255)
+#define CLAMP065535(a) TQMIN(TQMAX(a,0), 65535)
+#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
+#define ROUND(x) ((int) ((x) + 0.5))
+
+// C++ includes.
+
+#include <cmath>
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DImgImageFilters
+{
+public:
+
+ DImgImageFilters(){};
+ ~DImgImageFilters(){};
+
+private: // Private structures used internally.
+
+ struct double_packet
+ {
+ double red;
+ double green;
+ double blue;
+ double alpha;
+ };
+
+ struct int_packet
+ {
+ unsigned int red;
+ unsigned int green;
+ unsigned int blue;
+ unsigned int alpha;
+ };
+
+ struct NormalizeParam
+ {
+ unsigned short *lut;
+ double min;
+ double max;
+ };
+
+private: // Private methods used internally.
+
+ // Methods for Channel Mixer.
+
+ inline double CalculateNorm(float RedGain, float GreenGain, float BlueGain, bool bPreserveLum)
+ {
+ double lfSum = RedGain + GreenGain + BlueGain;
+
+ if ((lfSum == 0.0) || (bPreserveLum == false))
+ return (1.0);
+
+ return( fabs (1.0 / lfSum) );
+ };
+
+ inline unsigned short MixPixel(float RedGain, float GreenGain, float BlueGain,
+ unsigned short R, unsigned short G, unsigned short B, bool sixteenBit,
+ double Norm)
+ {
+ double lfMix = RedGain * (double)R + GreenGain * (double)G + BlueGain * (double)B;
+ lfMix *= Norm;
+ int segment = sixteenBit ? 65535 : 255;
+
+ return( (unsigned short)CLAMP (lfMix, 0, segment) );
+ };
+
+ inline int setPositionAdjusted (int Width, int Height, int X, int Y)
+ {
+ X = (X < 0) ? 0 : (X >= Width ) ? Width - 1 : X;
+ Y = (Y < 0) ? 0 : (Y >= Height) ? Height - 1 : Y;
+ return (Y*Width*4 + 4*X);
+ };
+
+public: // Public methods.
+
+ void equalizeImage(uchar *data, int w, int h, bool sixteenBit);
+ void stretchContrastImage(uchar *data, int w, int h, bool sixteenBit);
+ void normalizeImage(uchar *data, int w, int h, bool sixteenBit);
+ void autoLevelsCorrectionImage(uchar *data, int w, int h, bool sixteenBit);
+ void invertImage(uchar *data, int w, int h, bool sixteenBit);
+ void channelMixerImage(uchar *data, int Width, int Height, bool sixteenBit,
+ bool bPreserveLum, bool bMonochrome,
+ float rrGain, float rgGain, float rbGain,
+ float grGain, float ggGain, float gbGain,
+ float brGain, float bgGain, float bbGain);
+ void changeTonality(uchar *data, int width, int height, bool sixteenBit,
+ int redMask, int greenMask, int blueMask);
+ void gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius);
+ void sharpenImage(uchar *data, int width, int height, bool sixteenBit, int radius);
+
+ void pixelAntiAliasing(uchar *data, int Width, int Height, double X, double Y,
+ uchar *A, uchar *R, uchar *G, uchar *B);
+
+ void pixelAntiAliasing16(unsigned short *data, int Width, int Height, double X, double Y,
+ unsigned short *A, unsigned short *R, unsigned short *G, unsigned short *B);
+};
+
+} // NameSpace Digikam
+
+#endif /* DIMGIMAGE_FILTERS_H */
diff --git a/src/libs/dimg/filters/dimgsharpen.cpp b/src/libs/dimg/filters/dimgsharpen.cpp
new file mode 100644
index 00000000..a27d5b10
--- /dev/null
+++ b/src/libs/dimg/filters/dimgsharpen.cpp
@@ -0,0 +1,243 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-17-07
+ * Description : A Sharpen threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Original Sharpen algorithm copyright 2002
+ * by Daniel M. Duley <mosfet@kde.org> from KImageEffect API.
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#define SQ2PI 2.50662827463100024161235523934010416269302368164062
+#define Epsilon 1.0e-12
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimgimagefilters.h"
+#include "dimgsharpen.h"
+
+namespace Digikam
+{
+
+DImgSharpen::DImgSharpen(DImg *orgImage, TQObject *parent, double radius, double sigma)
+ : DImgThreadedFilter(orgImage, parent, "Sharpen")
+{
+ m_radius = radius;
+ m_sigma = sigma;
+ initFilter();
+}
+
+DImgSharpen::DImgSharpen(DImgThreadedFilter *parentFilter,
+ const DImg &orgImage, const DImg &destImage,
+ int progressBegin, int progressEnd, double radius, double sigma)
+ : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd,
+ parentFilter->filterName() + ": Sharpen")
+{
+ m_radius = radius;
+ m_sigma = sigma;
+ // We need to provide support for orgImage == destImage.
+ // The algorithm does not support this out of the box, so use a temporary.
+ if (orgImage.bits() == destImage.bits())
+ m_destImage = DImg(destImage.width(), destImage.height(), destImage.sixteenBit());
+ filterImage();
+ if (orgImage.bits() == destImage.bits())
+ memcpy(destImage.bits(), m_destImage.bits(), m_destImage.numBytes());
+}
+
+void DImgSharpen::filterImage(void)
+{
+ sharpenImage(m_radius, m_sigma);
+}
+
+/** Function to apply the sharpen filter on an image*/
+
+void DImgSharpen::sharpenImage(double radius, double sigma)
+{
+ if (m_orgImage.isNull())
+ {
+ DWarning() << k_funcinfo << "No image data available!"
+ << endl;
+ return;
+ }
+
+ if (radius <= 0.0)
+ {
+ m_destImage = m_orgImage;
+ return;
+ }
+
+ double alpha, normalize=0.0;
+ long i=0, u, v;
+
+ int kernelWidth = getOptimalKernelWidth(radius, sigma);
+
+ if((int)m_orgImage.width() < kernelWidth)
+ {
+ DWarning() << k_funcinfo << "Image is smaller than radius!"
+ << endl;
+ return;
+ }
+
+ double *kernel = new double[kernelWidth*kernelWidth];
+
+ if(!kernel)
+ {
+ DWarning() << k_funcinfo << "Unable to allocate memory!"
+ << endl;
+ return;
+ }
+
+ for(v=(-kernelWidth/2) ; v <= (kernelWidth/2) ; v++)
+ {
+ for(u=(-kernelWidth/2) ; u <= (kernelWidth/2) ; u++)
+ {
+ alpha = exp(-((double) u*u+v*v)/(2.0*sigma*sigma));
+ kernel[i] = alpha/(2.0*M_PI*sigma*sigma);
+ normalize += kernel[i];
+ i++;
+ }
+ }
+
+ kernel[i/2] = (-2.0)*normalize;
+ convolveImage(kernelWidth, kernel);
+
+ delete [] kernel;
+}
+
+bool DImgSharpen::convolveImage(const unsigned int order, const double *kernel)
+{
+ uint x, y;
+ int mx, my, sx, sy, mcx, mcy, progress;
+ long kernelWidth, i;
+ double red, green, blue, alpha, normalize=0.0;
+ double *k=0;
+ DColor color;
+
+ kernelWidth = order;
+
+ if((kernelWidth % 2) == 0)
+ {
+ DWarning() << k_funcinfo << "Kernel width must be an odd number!"
+ << endl;
+ return(false);
+ }
+
+ double *normal_kernel = new double[kernelWidth*kernelWidth];
+
+ if(!normal_kernel)
+ {
+ DWarning() << k_funcinfo << "Unable to allocate memory!"
+ << endl;
+ return(false);
+ }
+
+ for(i=0 ; i < (kernelWidth*kernelWidth) ; i++)
+ normalize += kernel[i];
+
+ if(fabs(normalize) <= Epsilon)
+ normalize=1.0;
+
+ normalize = 1.0/normalize;
+
+ for(i=0 ; i < (kernelWidth*kernelWidth) ; i++)
+ normal_kernel[i] = normalize*kernel[i];
+
+ double maxClamp = m_destImage.sixteenBit() ? 16777215.0 : 65535.0;
+
+ for(y=0 ; !m_cancel && (y < m_destImage.height()) ; y++)
+ {
+ sy = y-(kernelWidth/2);
+
+ for(x=0 ; !m_cancel && (x < m_destImage.width()) ; x++)
+ {
+ k = normal_kernel;
+ red = green = blue = alpha = 0;
+ sy = y-(kernelWidth/2);
+
+ for(mcy=0 ; !m_cancel && (mcy < kernelWidth) ; mcy++, sy++)
+ {
+ my = sy < 0 ? 0 : sy > (int)m_destImage.height()-1 ? m_destImage.height()-1 : sy;
+ sx = x+(-kernelWidth/2);
+
+ for(mcx=0 ; !m_cancel && (mcx < kernelWidth) ; mcx++, sx++)
+ {
+ mx = sx < 0 ? 0 : sx > (int)m_destImage.width()-1 ? m_destImage.width()-1 : sx;
+ color = m_orgImage.getPixelColor(mx, my);
+ red += (*k)*(color.red() * 257.0);
+ green += (*k)*(color.green() * 257.0);
+ blue += (*k)*(color.blue() * 257.0);
+ alpha += (*k)*(color.alpha() * 257.0);
+ k++;
+ }
+ }
+
+ red = red < 0.0 ? 0.0 : red > maxClamp ? maxClamp : red+0.5;
+ green = green < 0.0 ? 0.0 : green > maxClamp ? maxClamp : green+0.5;
+ blue = blue < 0.0 ? 0.0 : blue > maxClamp ? maxClamp : blue+0.5;
+ alpha = alpha < 0.0 ? 0.0 : alpha > maxClamp ? maxClamp : alpha+0.5;
+
+ m_destImage.setPixelColor(x, y, DColor((int)(red / 257UL), (int)(green / 257UL),
+ (int)(blue / 257UL), (int)(alpha / 257UL),
+ m_destImage.sixteenBit()));
+ }
+
+ progress = (int)(((double)y * 100.0) / m_destImage.height());
+ if ( progress%5 == 0 )
+ postProgress( progress );
+ }
+
+ delete [] normal_kernel;
+ return(true);
+}
+
+int DImgSharpen::getOptimalKernelWidth(double radius, double sigma)
+{
+ double normalize, value;
+ long kernelWidth;
+ long u;
+
+ if(radius > 0.0)
+ return((int)(2.0*ceil(radius)+1.0));
+
+ for(kernelWidth=5; ;)
+ {
+ normalize=0.0;
+
+ for(u=(-kernelWidth/2) ; u <= (kernelWidth/2) ; u++)
+ normalize += exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma);
+
+ u = kernelWidth/2;
+ value = exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma)/normalize;
+
+ if((long)(65535*value) <= 0)
+ break;
+
+ kernelWidth+=2;
+ }
+
+ return((int)kernelWidth-2);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/dimgsharpen.h b/src/libs/dimg/filters/dimgsharpen.h
new file mode 100644
index 00000000..5802769a
--- /dev/null
+++ b/src/libs/dimg/filters/dimgsharpen.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-17-07
+ * Description : A Sharpen threaded image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGSHARPEN_H
+#define DIMGSHARPEN_H
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+// Local includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DImgSharpen : public DImgThreadedFilter
+{
+
+public:
+
+ DImgSharpen(DImg *orgImage, TQObject *parent=0, double radius=0.0, double sigma=1.0);
+
+ // Constructor for slave mode: execute immediately in current thread with specified master filter
+ DImgSharpen(DImgThreadedFilter *parentFilter, const DImg &orgImage, const DImg &destImage,
+ int progressBegin=0, int progressEnd=100, double radius=0.0, double sigma=1.0);
+
+ ~DImgSharpen(){};
+
+private: // DImgSharpen filter data.
+
+ double m_radius;
+ double m_sigma;
+
+private: // DImgSharpen filter methods.
+
+ virtual void filterImage(void);
+
+ void sharpenImage(double radius, double sigma);
+
+ bool convolveImage(const unsigned int order, const double *kernel);
+
+ int getOptimalKernelWidth(double radius, double sigma);
+};
+
+} // NameSpace Digikam
+
+#endif /* DIMGSHARPEN_H */
diff --git a/src/libs/dimg/filters/dimgthreadedfilter.cpp b/src/libs/dimg/filters/dimgthreadedfilter.cpp
new file mode 100644
index 00000000..205405e8
--- /dev/null
+++ b/src/libs/dimg/filters/dimgthreadedfilter.cpp
@@ -0,0 +1,170 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : threaded image filter class.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqevent.h>
+#include <tqdeepcopy.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimgthreadedfilter.h"
+
+namespace Digikam
+{
+
+DImgThreadedFilter::DImgThreadedFilter(DImg *orgImage, TQObject *parent,
+ const TQString& name)
+ : TQThread()
+{
+ // remove meta data
+ m_orgImage = orgImage->copyImageData();
+ m_parent = parent;
+ m_cancel = false;
+
+ // See B.K.O #133026: make a deep copy of Qstring to prevent crash
+ // on Hyperthreading computer.
+ m_name = TQDeepCopy<TQString>(name);
+
+ m_master = 0;
+ m_slave = 0;
+ m_progressBegin = 0;
+ m_progressSpan = 100;
+}
+
+DImgThreadedFilter::DImgThreadedFilter(DImgThreadedFilter *master, const DImg &orgImage,
+ const DImg &destImage, int progressBegin, int progressEnd,
+ const TQString& name)
+{
+ m_orgImage = orgImage;
+ m_destImage = destImage;
+ m_parent = 0;
+ m_cancel = false;
+
+ // See B.K.O #133026: make a deep copy of Qstring to prevent crash
+ // on Hyperthreading computer.
+ m_name = TQDeepCopy<TQString>(name);
+
+ m_master = master;
+ m_slave = 0;
+ m_progressBegin = progressBegin;
+ m_progressSpan = progressEnd - progressBegin;
+
+ m_master->setSlave(this);
+}
+
+DImgThreadedFilter::~DImgThreadedFilter()
+{
+ stopComputation();
+ if (m_master)
+ m_master->setSlave(0);
+}
+
+void DImgThreadedFilter::initFilter(void)
+{
+ m_destImage.reset();
+ m_destImage = DImg(m_orgImage.width(), m_orgImage.height(),
+ m_orgImage.sixteenBit(), m_orgImage.hasAlpha());
+
+ if (m_orgImage.width() && m_orgImage.height())
+ {
+ if (m_parent)
+ start(); // m_parent is valide, start thread ==> run()
+ else
+ startComputation(); // no parent : no using thread.
+ }
+ else // No image data
+ {
+ if (m_parent) // If parent then send event about a problem.
+ {
+ postProgress(0, false, false);
+ DDebug() << m_name << "::No valid image data !!! ..." << endl;
+ }
+ }
+}
+
+void DImgThreadedFilter::stopComputation(void)
+{
+ m_cancel = true;
+ if (m_slave)
+ {
+ m_slave->m_cancel = true;
+ // do not wait on slave, it is not running in its own separate thread!
+ //m_slave->cleanupFilter();
+ }
+ wait();
+ cleanupFilter();
+}
+
+void DImgThreadedFilter::postProgress(int progress, bool starting, bool success)
+{
+ if (m_master)
+ {
+ progress = modulateProgress(progress);
+ m_master->postProgress(progress, starting, success);
+ }
+ else if (m_parent)
+ {
+ EventData *eventData = new EventData();
+ eventData->progress = progress;
+ eventData->starting = starting;
+ eventData->success = success;
+ TQApplication::postEvent(m_parent, new TQCustomEvent(TQEvent::User, eventData));
+ }
+}
+
+void DImgThreadedFilter::startComputation()
+{
+ // See B.K.O #133026: do not use kdDebug() statements in threaded implementation
+ // to prevent crash under Hyperthreaded CPU.
+
+ if (m_parent)
+ postProgress(0, true, false);
+
+ filterImage();
+
+ if (!m_cancel)
+ {
+ if (m_parent)
+ postProgress(0, false, true);
+ }
+ else
+ {
+ if (m_parent)
+ postProgress(0, false, false);
+ }
+}
+
+void DImgThreadedFilter::setSlave(DImgThreadedFilter *slave)
+{
+ m_slave = slave;
+}
+
+int DImgThreadedFilter::modulateProgress(int progress)
+{
+ return m_progressBegin + (int)((double)progress * (double)m_progressSpan / 100.0);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/dimgthreadedfilter.h b/src/libs/dimg/filters/dimgthreadedfilter.h
new file mode 100644
index 00000000..1d4a97b8
--- /dev/null
+++ b/src/libs/dimg/filters/dimgthreadedfilter.h
@@ -0,0 +1,153 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-25
+ * Description : threaded image filter class.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGTHREADEDFILTER_H
+#define DIMGTHREADEDFILTER_H
+
+// TQt includes.
+
+#include <tqthread.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+
+class TQObject;
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DImgThreadedFilter : public TQThread
+{
+
+public:
+
+/** Class used to post status of computation to parent. */
+class EventData
+{
+ public:
+
+ EventData()
+ {
+ starting = false;
+ success = false;
+ }
+
+ bool starting;
+ bool success;
+ int progress;
+};
+
+public:
+
+ DImgThreadedFilter(DImg *orgImage, TQObject *parent=0,
+ const TQString& name=TQString());
+
+ ~DImgThreadedFilter();
+
+ DImg getTargetImage(void) { return m_destImage; };
+
+ virtual void startComputation(void);
+ virtual void stopComputation(void);
+
+ const TQString &filterName() { return m_name; };
+
+protected:
+
+ /** Start filter operation before threaded method. Must be calls by your constructor. */
+ virtual void initFilter(void);
+
+ /** List of threaded operations by filter. */
+ virtual void run(){ startComputation(); };
+
+ /** Main image filter method. */
+ virtual void filterImage(void){};
+
+ /** Clean up filter data if necessary. Call by stopComputation() method. */
+ virtual void cleanupFilter(void){};
+
+ /** Post Event to parent about progress. Warning: you need to delete
+ 'EventData' instance to 'customEvent' parent implementation. */
+ void postProgress(int progress=0, bool starting=true, bool success=false);
+
+protected:
+
+ /**
+ Support for chaining two filters as master and thread.
+
+ Constructor for slave mode:
+ Constructs a new slave filter with the specified master.
+ The filter will be executed in the current thread.
+ orgImage and destImage will not be copied.
+ progressBegin and progressEnd can indicate the progress span
+ that the slave filter uses in the parent filter's progress.
+ Any derived filter class that is publicly available to other filters
+ should implement an additional constructor using this constructor.
+ */
+ DImgThreadedFilter(DImgThreadedFilter *master, const DImg &orgImage, const DImg &destImage,
+ int progressBegin=0, int progressEnd=100, const TQString& name=TQString());
+
+ /** Inform the master that there is currently a slave. At destruction of the slave, call with slave=0. */
+ void setSlave(DImgThreadedFilter *slave);
+
+ /** This method modulates the progress value from the 0..100 span to the span of this slave.
+ Called by postProgress if master is not null. */
+ virtual int modulateProgress(int progress);
+
+protected:
+
+ /** Used to stop compution loop. */
+ bool m_cancel;
+
+ /** The progress span that a slave filter uses in the parent filter's progress. */
+ int m_progressBegin;
+ int m_progressSpan;
+
+ /** To post event from thread to parent. */
+ TQObject *m_parent;
+
+ /** Filter name.*/
+ TQString m_name;
+
+ /** Copy of original Image data. */
+ DImg m_orgImage;
+
+ /** Output image data. */
+ DImg m_destImage;
+
+ /** The current slave. Any filter might want to use another filter while processing. */
+ DImgThreadedFilter *m_slave;
+
+ /** The master of this slave filter. Progress info will be routed to this one. */
+ DImgThreadedFilter *m_master;
+};
+
+} // NameSpace Digikam
+
+#endif /* DIMGTHREADEDFILTER_H */
diff --git a/src/libs/dimg/filters/hslmodifier.cpp b/src/libs/dimg/filters/hslmodifier.cpp
new file mode 100644
index 00000000..4f924d76
--- /dev/null
+++ b/src/libs/dimg/filters/hslmodifier.cpp
@@ -0,0 +1,240 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-06
+ * Description : Hue/Saturation/Lightness image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
+#define CLAMP_0_255(x) TQMAX(TQMIN(x, 255), 0)
+#define CLAMP_0_65535(x) TQMAX(TQMIN(x, 65535), 0)
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dcolor.h"
+#include "dimg.h"
+#include "hslmodifier.h"
+
+namespace Digikam
+{
+
+class HSLModifierPriv
+{
+public:
+
+ HSLModifierPriv()
+ {
+ modified = false;
+ }
+
+ bool modified;
+
+ int htransfer[256];
+ int ltransfer[256];
+ int stransfer[256];
+
+ int htransfer16[65536];
+ int ltransfer16[65536];
+ int stransfer16[65536];
+};
+
+HSLModifier::HSLModifier()
+{
+ d = new HSLModifierPriv;
+ reset();
+}
+
+HSLModifier::~HSLModifier()
+{
+ delete d;
+}
+
+bool HSLModifier::modified() const
+{
+ return d->modified;
+}
+
+void HSLModifier::reset()
+{
+ // initialize to linear mapping
+
+ for (int i=0; i<65536; i++)
+ {
+ d->htransfer16[i] = i;
+ d->ltransfer16[i] = i;
+ d->stransfer16[i] = i;
+ }
+
+ for (int i=0; i<256; i++)
+ {
+ d->htransfer[i] = i;
+ d->ltransfer[i] = i;
+ d->stransfer[i] = i;
+ }
+
+ d->modified = false;
+}
+
+void HSLModifier::applyHSL(DImg& image)
+{
+ if (!d->modified || image.isNull())
+ return;
+
+ bool sixteenBit = image.sixteenBit();
+ uint numberOfPixels = image.numPixels();
+
+ if (sixteenBit) // 16 bits image.
+ {
+ unsigned short* data = (unsigned short*) image.bits();
+
+ for (uint i=0; i<numberOfPixels; i++)
+ {
+ int hue, sat, lig;
+
+ DColor color(data[2], data[1], data[0], 0, sixteenBit);
+
+ // convert RGB to HSL
+ color.getHSL(&hue, &sat, &lig);
+
+ // convert HSL to RGB
+ color.setRGB(d->htransfer16[hue], d->stransfer16[sat], d->ltransfer16[lig], sixteenBit);
+
+ data[2] = color.red();
+ data[1] = color.green();
+ data[0] = color.blue();
+
+ data += 4;
+ }
+ }
+ else // 8 bits image.
+ {
+ uchar* data = image.bits();
+
+ for (uint i=0; i<numberOfPixels; i++)
+ {
+ int hue, sat, lig;
+
+ DColor color(data[2], data[1], data[0], 0, sixteenBit);
+
+ // convert RGB to HSL
+ color.getHSL(&hue, &sat, &lig);
+
+ // convert HSL to RGB
+ color.setRGB(d->htransfer[hue], d->stransfer[sat], d->ltransfer[lig], sixteenBit);
+
+ data[2] = color.red();
+ data[1] = color.green();
+ data[0] = color.blue();
+
+ data += 4;
+ }
+ }
+}
+
+void HSLModifier::setHue(double val)
+{
+ int value;
+
+ for (int i = 0; i < 65536; i++)
+ {
+ value = lround(val * 65535.0 / 360.0);
+
+ if ((i + value) < 0)
+ d->htransfer16[i] = 65535 + (i + value);
+ else if ((i + value) > 65535)
+ d->htransfer16[i] = i + value - 65535;
+ else
+ d->htransfer16[i] = i + value;
+ }
+
+ for (int i = 0; i < 256; i++)
+ {
+ value = lround(val * 255.0 / 360.0);
+
+ if ((i + value) < 0)
+ d->htransfer[i] = 255 + (i + value);
+ else if ((i + value) > 255)
+ d->htransfer[i] = i + value - 255;
+ else
+ d->htransfer[i] = i + value;
+ }
+
+ d->modified = true;
+}
+
+void HSLModifier::setSaturation(double val)
+{
+ val = CLAMP(val, -100.0, 100.0);
+ int value;
+
+ for (int i = 0; i < 65536; i++)
+ {
+ value = lround( (i * (100.0 + val)) / 100.0 );
+ d->stransfer16[i] = CLAMP_0_65535(value);
+ }
+
+ for (int i = 0; i < 256; i++)
+ {
+ value = lround( (i * (100.0 + val)) / 100.0 );
+ d->stransfer[i] = CLAMP_0_255(value);
+ }
+
+ d->modified = true;
+}
+
+void HSLModifier::setLightness(double val)
+{
+ // val needs to be in that range so that the result is in the range 0..65535
+ val = CLAMP(val, -100.0, 100.0);
+
+ if (val < 0)
+ {
+ for (int i = 0; i < 65536; i++)
+ {
+ d->ltransfer16[i] = lround( (i * ( val + 100.0 )) / 100.0);
+ }
+
+ for (int i = 0; i < 256; i++)
+ {
+ d->ltransfer[i] = lround( (i * ( val + 100.0 )) / 100.0);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < 65536; i++)
+ {
+ d->ltransfer16[i] = lround( i * ( 1.0 - val / 100.0 ) + 65535.0 / 100.0 * val );
+ }
+
+ for (int i = 0; i < 256; i++)
+ {
+ d->ltransfer[i] = lround( i * ( 1.0 - val / 100.0 ) + 255.0 / 100.0 * val );
+ }
+ }
+
+ d->modified = true;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/hslmodifier.h b/src/libs/dimg/filters/hslmodifier.h
new file mode 100644
index 00000000..02a1131d
--- /dev/null
+++ b/src/libs/dimg/filters/hslmodifier.h
@@ -0,0 +1,58 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-06
+ * Description : Hue/Saturation/Lightness image filter.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ * ============================================================ */
+
+#ifndef HSLMODIFIER_H
+#define HSLMODIFIER_H
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImg;
+class HSLModifierPriv;
+
+class DIGIKAM_EXPORT HSLModifier
+{
+public:
+
+ HSLModifier();
+ ~HSLModifier();
+
+ void reset();
+ bool modified() const;
+
+ void setHue(double val);
+ void setSaturation(double val);
+ void setLightness(double val);
+ void applyHSL(DImg& image);
+
+private:
+
+ HSLModifierPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* HSLMODIFIER_H */
diff --git a/src/libs/dimg/filters/icctransform.cpp b/src/libs/dimg/filters/icctransform.cpp
new file mode 100644
index 00000000..0a1324db
--- /dev/null
+++ b/src/libs/dimg/filters/icctransform.cpp
@@ -0,0 +1,831 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-18
+ * Description : a class to apply ICC color correction to image.
+ *
+ * Copyright (C) 2005-2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#include <config.h>
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqcstring.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// Lcms includes.
+
+#include LCMS_HEADER
+#if LCMS_VERSION < 114
+#define cmsTakeCopyright(profile) "Unknown"
+#endif // LCMS_VERSION < 114
+
+// Local includes.
+
+#include "ddebug.h"
+#include "icctransform.h"
+
+namespace Digikam
+{
+
+class IccTransformPriv
+{
+public:
+
+ IccTransformPriv()
+ {
+ has_embedded_profile = false;
+ do_proof_profile = false;
+ }
+
+ bool do_proof_profile;
+ bool has_embedded_profile;
+
+ TQByteArray embedded_profile;
+ TQByteArray input_profile;
+ TQByteArray output_profile;
+ TQByteArray proof_profile;
+};
+
+IccTransform::IccTransform()
+{
+ d = new IccTransformPriv;
+ cmsErrorAction(LCMS_ERROR_SHOW);
+}
+
+IccTransform::~IccTransform()
+{
+ delete d;
+}
+
+bool IccTransform::hasInputProfile()
+{
+ return !(d->input_profile.isEmpty());
+}
+
+bool IccTransform::hasOutputProfile()
+{
+ return !(d->output_profile.isEmpty());
+}
+
+TQByteArray IccTransform::embeddedProfile() const
+{
+ return d->embedded_profile;
+}
+
+TQByteArray IccTransform::inputProfile() const
+{
+ return d->input_profile;
+}
+
+TQByteArray IccTransform::outputProfile() const
+{
+ return d->output_profile;
+}
+
+TQByteArray IccTransform::proofProfile() const
+{
+ return d->proof_profile;
+}
+
+void IccTransform::getTransformType(bool do_proof_profile)
+{
+ if (do_proof_profile)
+ {
+ d->do_proof_profile = true;
+ }
+ else
+ {
+ d->do_proof_profile = false;
+ }
+}
+
+void IccTransform::getEmbeddedProfile(const DImg& image)
+{
+ if (!image.getICCProfil().isNull())
+ {
+ d->embedded_profile = image.getICCProfil();
+ d->has_embedded_profile = true;
+ }
+}
+
+TQString IccTransform::getProfileDescription(const TQString& profile)
+{
+ cmsHPROFILE _profile = cmsOpenProfileFromFile(TQFile::encodeName(profile), "r");
+ TQString _description = cmsTakeProductDesc(_profile);
+ cmsCloseProfile(_profile);
+ return _description;
+}
+
+int IccTransform::getRenderingIntent()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Color Management");
+ return config->readNumEntry("RenderingIntent", 0);
+}
+
+bool IccTransform::getUseBPC()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Color Management");
+ return config->readBoolEntry("BPCAlgorithm", false);
+}
+
+TQByteArray IccTransform::loadICCProfilFile(const TQString& filePath)
+{
+ TQFile file(filePath);
+ if ( !file.open(IO_ReadOnly) )
+ return TQByteArray();
+
+ TQByteArray data(file.size());
+ TQDataStream stream( &file );
+ stream.readRawBytes(data.data(), data.size());
+ file.close();
+ return data;
+}
+
+void IccTransform::setProfiles(const TQString& input_profile, const TQString& output_profile)
+{
+ d->input_profile = loadICCProfilFile(input_profile);
+ d->output_profile = loadICCProfilFile(output_profile);
+}
+
+void IccTransform::setProfiles(const TQString& input_profile, const TQString& output_profile,
+ const TQString& proof_profile)
+{
+ d->input_profile = loadICCProfilFile(input_profile);
+ d->output_profile = loadICCProfilFile(output_profile);
+ d->proof_profile = loadICCProfilFile(proof_profile);
+}
+
+void IccTransform::setProfiles(const TQString& output_profile)
+{
+ d->output_profile = loadICCProfilFile(output_profile);
+}
+
+void IccTransform::setProfiles(const TQString& output_profile, const TQString& proof_profile, bool forProof)
+{
+ if (forProof)
+ {
+ d->output_profile = loadICCProfilFile(output_profile);
+ d->proof_profile = loadICCProfilFile(proof_profile);
+ }
+}
+
+TQString IccTransform::getEmbeddedProfileDescriptor()
+{
+ if (d->embedded_profile.isEmpty())
+ return TQString();
+
+ cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->embedded_profile.data(),
+ (DWORD)d->embedded_profile.size());
+ TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile));
+ cmsCloseProfile(tmpProfile);
+ return embeddedProfileDescriptor;
+}
+
+TQString IccTransform::getInputProfileDescriptor()
+{
+ if (d->input_profile.isEmpty()) return TQString();
+ cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->input_profile.data(), (DWORD)d->input_profile.size());
+ TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile));
+ cmsCloseProfile(tmpProfile);
+ return embeddedProfileDescriptor;
+}
+
+TQString IccTransform::getOutpoutProfileDescriptor()
+{
+ if (d->output_profile.isEmpty()) return TQString();
+ cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->output_profile.data(), (DWORD)d->output_profile.size());
+ TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile));
+ cmsCloseProfile(tmpProfile);
+ return embeddedProfileDescriptor;
+}
+
+TQString IccTransform::getProofProfileDescriptor()
+{
+ if (d->proof_profile.isEmpty()) return TQString();
+ cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->proof_profile.data(), (DWORD)d->proof_profile.size());
+ TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile));
+ cmsCloseProfile(tmpProfile);
+ return embeddedProfileDescriptor;
+}
+
+bool IccTransform::apply(DImg& image)
+{
+ cmsHPROFILE inprofile=0, outprofile=0, proofprofile=0;
+ cmsHTRANSFORM transform;
+ int inputFormat = 0;
+ int intent = INTENT_PERCEPTUAL;
+
+ switch (getRenderingIntent())
+ {
+ case 0:
+ intent = INTENT_PERCEPTUAL;
+ break;
+ case 1:
+ intent = INTENT_RELATIVE_COLORIMETRIC;
+ break;
+ case 2:
+ intent = INTENT_SATURATION;
+ break;
+ case 3:
+ intent = INTENT_ABSOLUTE_COLORIMETRIC;
+ break;
+ }
+
+ //DDebug() << "Intent is: " << intent << endl;
+
+ if (d->has_embedded_profile)
+ {
+ inprofile = cmsOpenProfileFromMem(d->embedded_profile.data(),
+ (DWORD)d->embedded_profile.size());
+ }
+ else
+ {
+ inprofile = cmsOpenProfileFromMem(d->input_profile.data(),
+ (DWORD)d->input_profile.size());
+ }
+ if (inprofile == NULL)
+ {
+ DDebug() << "Error: Input profile is NULL" << endl;
+ cmsCloseProfile(inprofile);
+ return false;
+ }
+
+// if (d->has_embedded_profile)
+// {
+// outprofile = cmsOpenProfileFromMem(d->embedded_profile.data(),
+// (DWORD)d->embedded_profile.size());
+// }
+// else
+// {
+ outprofile = cmsOpenProfileFromMem(d->output_profile.data(),
+ (DWORD)d->output_profile.size());
+// }
+
+ if (outprofile == NULL)
+ {
+ DDebug() << "Error: Output profile is NULL" << endl;
+ cmsCloseProfile(outprofile);
+ return false;
+ }
+
+ if (!d->do_proof_profile)
+ {
+ if (image.sixteenBit())
+ {
+ if (image.hasAlpha())
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAYA_16;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_16;
+ break;
+ default:
+ inputFormat = TYPE_BGRA_16;
+ }
+
+ transform = cmsCreateTransform( inprofile,
+ inputFormat,
+ outprofile,
+ TYPE_BGRA_16,
+ intent,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAY_16;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_16;
+ break;
+ default:
+ inputFormat = TYPE_BGR_16;
+ }
+
+ transform = cmsCreateTransform( inprofile,
+ inputFormat,
+ outprofile,
+ TYPE_BGR_16,
+ intent,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (image.hasAlpha())
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAYA_8;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_8;
+ break;
+ default:
+ inputFormat = TYPE_BGRA_8;
+ }
+
+ transform = cmsCreateTransform( inprofile,
+ inputFormat,
+ outprofile,
+ TYPE_BGRA_8,
+ intent,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAYA_8;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_8;
+ //DDebug() << "input profile: cmyk no alpha" << endl;
+ break;
+ default:
+ inputFormat = TYPE_BGR_8;
+ //DDebug() << "input profile: default no alpha" << endl;
+ }
+
+ transform = cmsCreateTransform(inprofile, inputFormat, outprofile,
+ TYPE_BGR_8, intent,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ }
+ else
+ {
+ proofprofile = cmsOpenProfileFromMem(d->proof_profile.data(),
+ (DWORD)d->proof_profile.size());
+
+ if (proofprofile == NULL)
+ {
+ DDebug() << "Error: Input profile is NULL" << endl;
+ cmsCloseProfile(inprofile);
+ cmsCloseProfile(outprofile);
+ return false;
+ }
+
+ if (image.sixteenBit())
+ {
+ if (image.hasAlpha())
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGRA_16,
+ outprofile,
+ TYPE_BGRA_16,
+ proofprofile,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGR_16,
+ outprofile,
+ TYPE_BGR_16,
+ proofprofile,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (image.hasAlpha())
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGR_8,
+ outprofile,
+ TYPE_BGR_8,
+ proofprofile,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGR_8,
+ outprofile,
+ TYPE_BGR_8,
+ proofprofile,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ INTENT_ABSOLUTE_COLORIMETRIC,
+ cmsFLAGS_WHITEBLACKCOMPENSATION);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ }
+
+ // We need to work using temp pixel buffer to apply ICC transformations.
+ uchar transdata[image.bytesDepth()];
+
+ // Always working with uchar* prevent endianess problem.
+ uchar *data = image.bits();
+
+ // We scan all image pixels one by one.
+ for (uint i=0; i < image.width()*image.height()*image.bytesDepth(); i+=image.bytesDepth())
+ {
+ // Apply ICC transformations.
+ cmsDoTransform( transform, &data[i], &transdata[0], 1);
+
+ // Copy buffer to source to update original image with ICC corrections.
+ // Alpha channel is restored in all cases.
+ memcpy (&data[i], &transdata[0], (image.bytesDepth() == 8) ? 6 : 3);
+ }
+
+ cmsDeleteTransform(transform);
+ cmsCloseProfile(inprofile);
+ cmsCloseProfile(outprofile);
+
+ if (d->do_proof_profile)
+ cmsCloseProfile(proofprofile);
+
+ return true;
+}
+
+bool IccTransform::apply( DImg& image, TQByteArray& profile, int intent, bool useBPC,
+ bool checkGamut, bool useBuiltin )
+{
+ cmsHPROFILE inprofile=0, outprofile=0, proofprofile=0;
+ cmsHTRANSFORM transform;
+ int transformFlags = 0, inputFormat = 0;
+
+ switch (intent)
+ {
+ case 0:
+ intent = INTENT_PERCEPTUAL;
+ break;
+ case 1:
+ intent = INTENT_RELATIVE_COLORIMETRIC;
+ break;
+ case 2:
+ intent = INTENT_SATURATION;
+ break;
+ case 3:
+ intent = INTENT_ABSOLUTE_COLORIMETRIC;
+ break;
+ }
+
+ //DDebug() << "Intent is: " << intent << endl;
+
+ if (!profile.isNull())
+ {
+ inprofile = cmsOpenProfileFromMem(profile.data(),
+ (DWORD)profile.size());
+ }
+ else if (useBuiltin)
+ {
+ inprofile = cmsCreate_sRGBProfile();
+ }
+ else
+ {
+ inprofile = cmsOpenProfileFromMem(d->input_profile.data(),
+ (DWORD)d->input_profile.size());
+ }
+
+ if (inprofile == NULL)
+ {
+ DDebug() << "Error: Input profile is NULL" << endl;
+ return false;
+ }
+
+ outprofile = cmsOpenProfileFromMem(d->output_profile.data(),
+ (DWORD)d->output_profile.size());
+
+ if (outprofile == NULL)
+ {
+ DDebug() << "Error: Output profile is NULL" << endl;
+ cmsCloseProfile(inprofile);
+ return false;
+ }
+
+ if (useBPC)
+ {
+ transformFlags |= cmsFLAGS_WHITEBLACKCOMPENSATION;
+ }
+
+ if (!d->do_proof_profile)
+ {
+ if (image.sixteenBit())
+ {
+ if (image.hasAlpha())
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAYA_16;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_16;
+ break;
+ default:
+ inputFormat = TYPE_BGRA_16;
+ }
+
+ transform = cmsCreateTransform( inprofile,
+ inputFormat,
+ outprofile,
+ TYPE_BGRA_16,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAY_16;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_16;
+ break;
+ default:
+ inputFormat = TYPE_BGR_16;
+ }
+
+ transform = cmsCreateTransform( inprofile,
+ inputFormat,
+ outprofile,
+ TYPE_BGR_16,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (image.hasAlpha())
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAYA_8;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_8;
+ break;
+ default:
+ inputFormat = TYPE_BGRA_8;
+ }
+
+ transform = cmsCreateTransform( inprofile,
+ inputFormat,
+ outprofile,
+ TYPE_BGRA_8,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ switch (cmsGetColorSpace(inprofile))
+ {
+ case icSigGrayData:
+ inputFormat = TYPE_GRAY_8;
+ break;
+ case icSigCmykData:
+ inputFormat = TYPE_CMYK_8;
+ break;
+ default:
+ inputFormat = TYPE_BGR_8;
+ }
+
+ transform = cmsCreateTransform( inprofile,
+ inputFormat,
+ outprofile,
+ TYPE_BGR_8,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ }
+ else
+ {
+ proofprofile = cmsOpenProfileFromMem(d->proof_profile.data(),
+ (DWORD)d->proof_profile.size());
+
+ if (proofprofile == NULL)
+ {
+ DDebug() << "Error: Input profile is NULL" << endl;
+ cmsCloseProfile(inprofile);
+ cmsCloseProfile(outprofile);
+ return false;
+ }
+
+ transformFlags |= cmsFLAGS_SOFTPROOFING;
+ if (checkGamut)
+ {
+ cmsSetAlarmCodes(126, 255, 255);
+ transformFlags |= cmsFLAGS_GAMUTCHECK;
+ }
+
+ if (image.sixteenBit())
+ {
+ if (image.hasAlpha())
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGRA_16,
+ outprofile,
+ TYPE_BGRA_16,
+ proofprofile,
+ intent,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGR_16,
+ outprofile,
+ TYPE_BGR_16,
+ proofprofile,
+ intent,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (image.hasAlpha())
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGR_8,
+ outprofile,
+ TYPE_BGR_8,
+ proofprofile,
+ intent,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ transform = cmsCreateProofingTransform( inprofile,
+ TYPE_BGR_8,
+ outprofile,
+ TYPE_BGR_8,
+ proofprofile,
+ intent,
+ intent,
+ transformFlags);
+
+ if (!transform)
+ {
+ DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl;
+ return false;
+ }
+ }
+ }
+ }
+
+ //DDebug() << "Transform flags are: " << transformFlags << endl;
+
+ // We need to work using temp pixel buffer to apply ICC transformations.
+ uchar transdata[image.bytesDepth()];
+
+ // Always working with uchar* prevent endianess problem.
+ uchar *data = image.bits();
+
+ // We scan all image pixels one by one.
+ for (uint i=0; i < image.width()*image.height()*image.bytesDepth(); i+=image.bytesDepth())
+ {
+ // Apply ICC transformations.
+ cmsDoTransform( transform, &data[i], &transdata[0], 1);
+
+ // Copy buffer to source to update original image with ICC corrections.
+ // Alpha channel is restored in all cases.
+ memcpy (&data[i], &transdata[0], (image.bytesDepth() == 8) ? 6 : 3);
+ }
+
+ cmsDeleteTransform(transform);
+ cmsCloseProfile(inprofile);
+ cmsCloseProfile(outprofile);
+
+ if (d->do_proof_profile)
+ cmsCloseProfile(proofprofile);
+
+ return true;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/filters/icctransform.h b/src/libs/dimg/filters/icctransform.h
new file mode 100644
index 00000000..0590c44f
--- /dev/null
+++ b/src/libs/dimg/filters/icctransform.h
@@ -0,0 +1,94 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-18
+ * Description : a class to apply ICC color correction to image.
+ *
+ * Copyright (C) 2005-2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICCTRANSFORM_H
+#define ICCTRANSFORM_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class IccTransformPriv;
+
+class DIGIKAM_EXPORT IccTransform
+{
+public:
+
+ IccTransform();
+ ~IccTransform();
+
+ bool apply(DImg& image);
+ bool apply(DImg& image, TQByteArray& profile, int intent,
+ bool useBPC=false, bool checkGamut=false, bool useBuiltin=false);
+
+ void getTransformType(bool do_proof_profile);
+ void getEmbeddedProfile(const DImg& image);
+ int getRenderingIntent();
+ bool getUseBPC();
+
+ bool hasInputProfile();
+ bool hasOutputProfile();
+
+ TQByteArray embeddedProfile() const;
+ TQByteArray inputProfile() const;
+ TQByteArray outputProfile() const;
+ TQByteArray proofProfile() const;
+
+ /** Input profile from file methods */
+ void setProfiles(const TQString& input_profile, const TQString& output_profile);
+ void setProfiles(const TQString& input_profile, const TQString& output_profile, const TQString& proof_profile);
+
+ /** Embedded input profile methods */
+ void setProfiles(const TQString& output_profile);
+ void setProfiles(const TQString& output_profile, const TQString& proof_profile, bool forProof);
+
+ /** Profile info methods */
+ TQString getProfileDescription(const TQString& profile);
+
+ TQString getEmbeddedProfileDescriptor();
+ TQString getInputProfileDescriptor();
+ TQString getOutpoutProfileDescriptor();
+ TQString getProofProfileDescriptor();
+
+private:
+
+ TQByteArray loadICCProfilFile(const TQString& filePath);
+
+private:
+
+ IccTransformPriv* d;
+
+};
+
+} // NameSpace Digikam
+
+#endif // ICCTRANSFORM_H
diff --git a/src/libs/dimg/loaders/Makefile.am b/src/libs/dimg/loaders/Makefile.am
new file mode 100644
index 00000000..62e96fe8
--- /dev/null
+++ b/src/libs/dimg/loaders/Makefile.am
@@ -0,0 +1,21 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdimgloaders.la
+
+libdimgloaders_la_SOURCES = dimgloader.cpp pngloader.cpp jpegloader.cpp tiffloader.cpp \
+ rawloader.cpp ppmloader.cpp qimageloader.cpp iccjpeg.c \
+ jp2kloader.cpp jpegsettings.cpp pngsettings.cpp \
+ tiffsettings.cpp jp2ksettings.cpp
+
+libdimgloaders_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) \
+ $(LIBJPEG) $(LIB_TIFF) $(LIB_PNG) $(LIB_JASPER)
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS)
diff --git a/src/libs/dimg/loaders/README b/src/libs/dimg/loaders/README
new file mode 100644
index 00000000..b77e9c4c
--- /dev/null
+++ b/src/libs/dimg/loaders/README
@@ -0,0 +1,42 @@
+---------------------------------------------------------------------------
+
+Native DIMG Loaders status (Gilles Caulier - 2006-11-29)
+
+Format Read Write ICC MetaData Thumb 8bits 16bits depency Remarks
+
+JPG Done Done Done Done TODO yes N.A libjpeg Metadata are EXIF/IPTC/XMP/ICC profil
+PNG Done Done Done Done N.A yes yes libpng Metadata are EXIF/IPTC/XMP/ICC profil
+TIF/EP Done Done Done Done TODO yes yes libtiff Metadata are EXIF/IPTC/XMP/ICC profil
+RAW Done N.A N.A Done Done yes yes dcraw Metadata are EXIF
+PPM Done TODO N.A N.A N.A yes yes none
+JPEG2K Done Done Done TODO N.A yes yes libjasper Metadata are EXIF/XMP/ICC profil
+
+Others file formats are supported only in 8 bits/color/pixel using TQImage/kimgio.
+QT3.x + KDE 3.4.x support these formats :
+
+Format Read Write Remarks
+
+PSD yes no Photoshop file format
+EXR yes no OpenEXR (libopenexr)
+XCF yes no Gimp file format
+PBM yes yes
+PGM yes yes
+PPM no yes
+TGA yes yes
+PCX yes yes
+BMP yes yes Win32 bitmap format
+RGB yes yes
+XBM yes yes
+XPM yes yes
+EPS yes yes
+DDS yes no
+ICO yes no Win32 icon format
+MNG yes no
+GIF yes no
+
+---------------------------------------------------------------------------
+
+TODO :
+
+Add PCD support using http://linux.bytesex.org/fbida/libpcd.html
+
diff --git a/src/libs/dimg/loaders/dimgloader.cpp b/src/libs/dimg/loaders/dimgloader.cpp
new file mode 100644
index 00000000..615b11e1
--- /dev/null
+++ b/src/libs/dimg/loaders/dimgloader.cpp
@@ -0,0 +1,200 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : DImg image loader interface
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimgprivate.h"
+#include "dmetadata.h"
+#include "dimgloaderobserver.h"
+#include "dimgloader.h"
+
+namespace Digikam
+{
+
+DImgLoader::DImgLoader(DImg* image)
+ : m_image(image)
+{
+}
+
+int DImgLoader::granularity(DImgLoaderObserver *observer, int total, float progressSlice)
+{
+ // Splits expect total value into the chunks where checks shall occur
+ // and combines this with a possible correction factor from observer.
+ // Progress slice is the part of 100% concerned with the current granularity
+ // (E.g. in a loop only the values from 10% to 90% are used, then progressSlice is 0.8)
+ // Current default is 1/20, that is progress info every 5%
+ int granularity=0;
+
+ if (observer)
+ granularity = (int)(( total / (20 * progressSlice)) / observer->granularity());
+
+ return granularity ? granularity : 1;
+}
+
+unsigned char*& DImgLoader::imageData()
+{
+ return m_image->m_priv->data;
+}
+
+unsigned int& DImgLoader::imageWidth()
+{
+ return m_image->m_priv->width;
+}
+
+unsigned int& DImgLoader::imageHeight()
+{
+ return m_image->m_priv->height;
+}
+
+bool DImgLoader::imageHasAlpha()
+{
+ return m_image->hasAlpha();
+}
+
+bool DImgLoader::imageSixteenBit()
+{
+ return m_image->sixteenBit();
+}
+
+int DImgLoader::imageBitsDepth()
+{
+ return m_image->bitsDepth();
+}
+
+int DImgLoader::imageBytesDepth()
+{
+ return m_image->bytesDepth();
+}
+
+TQMap<int, TQByteArray>& DImgLoader::imageMetaData()
+{
+ return m_image->m_priv->metaData;
+}
+
+TQVariant DImgLoader::imageGetAttribute(const TQString& key)
+{
+ return m_image->attribute(key);
+}
+
+TQString DImgLoader::imageGetEmbbededText(const TQString& key)
+{
+ return m_image->embeddedText(key);
+}
+
+void DImgLoader::imageSetAttribute(const TQString& key, const TQVariant& value)
+{
+ m_image->setAttribute(key, value);
+}
+
+TQMap<TQString, TQString>& DImgLoader::imageEmbeddedText()
+{
+ return m_image->m_priv->embeddedText;
+}
+
+void DImgLoader::imageSetEmbbededText(const TQString& key, const TQString& text)
+{
+ m_image->setEmbeddedText(key, text);
+}
+
+bool DImgLoader::readMetadata(const TQString& filePath, DImg::FORMAT /*ff*/)
+{
+ TQMap<int, TQByteArray>& imageMetadata = imageMetaData();
+ imageMetadata.clear();
+
+ DMetadata metaDataFromFile(filePath);
+ if (!metaDataFromFile.load(filePath))
+ return false;
+
+ // Do not insert null data into metaData map:
+ // Even if byte array is null, if there is a key in the map, it will
+ // be interpreted as "There was data, so write it again to the file".
+ if (!metaDataFromFile.getComments().isNull())
+ imageMetadata.insert(DImg::COM, metaDataFromFile.getComments());
+ if (!metaDataFromFile.getExif().isNull())
+ imageMetadata.insert(DImg::EXIF, metaDataFromFile.getExif());
+ if (!metaDataFromFile.getIptc().isNull())
+ imageMetadata.insert(DImg::IPTC, metaDataFromFile.getIptc());
+
+ return true;
+}
+
+bool DImgLoader::saveMetadata(const TQString& filePath)
+{
+ DMetadata metaDataToFile(filePath);
+ metaDataToFile.setComments(m_image->getComments());
+ metaDataToFile.setExif(m_image->getExif());
+ metaDataToFile.setIptc(m_image->getIptc());
+ return metaDataToFile.applyChanges();
+}
+
+bool DImgLoader::checkExifWorkingColorSpace()
+{
+ DMetadata metaData;
+ metaData.setExif(m_image->getExif());
+
+ // Check if Exif data contains an ICC color profile.
+ TQByteArray profile = metaData.getExifTagData("Exif.Image.InterColorProfile");
+ if (!profile.isNull())
+ {
+ DDebug() << "Found an ICC profile in Exif metadata" << endl;
+ m_image->setICCProfil(profile);
+ return true;
+ }
+
+ // Else check the Exif color-space tag and use a default profiles available in digiKam.
+ TDEGlobal::dirs()->addResourceType("profiles", TDEGlobal::dirs()->kde_default("data") + "digikam/profiles");
+
+ switch(metaData.getImageColorWorkSpace())
+ {
+ case DMetadata::WORKSPACE_SRGB:
+ {
+ TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "srgb-d65.icm");
+ m_image->getICCProfilFromFile(directory + "srgb-d65.icm");
+ DDebug() << "Exif color-space tag is sRGB. Using default sRGB ICC profile." << endl;
+ return true;
+ break;
+ }
+
+ case DMetadata::WORKSPACE_ADOBERGB:
+ {
+ TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "adobergb.icm");
+ m_image->getICCProfilFromFile(directory + "adobergb.icm");
+ DDebug() << "Exif color-space tag is AdobeRGB. Using default AdobeRGB ICC profile." << endl;
+ return true;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/dimgloader.h b/src/libs/dimg/loaders/dimgloader.h
new file mode 100644
index 00000000..39025888
--- /dev/null
+++ b/src/libs/dimg/loaders/dimgloader.h
@@ -0,0 +1,97 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : DImg image loader interface
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGLOADER_H
+#define DIMGLOADER_H
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqstring.h>
+#include <tqcstring.h>
+#include <tqvariant.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImgLoaderObserver;
+
+class DIGIKAM_EXPORT DImgLoader
+{
+public:
+
+ virtual ~DImgLoader() {};
+
+ virtual bool load(const TQString& filePath, DImgLoaderObserver *observer) = 0;
+ virtual bool save(const TQString& filePath, DImgLoaderObserver *observer) = 0;
+
+ virtual bool hasAlpha() const = 0;
+ virtual bool sixteenBit() const = 0;
+ virtual bool isReadOnly() const = 0;
+
+protected:
+
+ DImgLoader(DImg* image);
+
+ unsigned char*& imageData();
+ unsigned int& imageWidth();
+ unsigned int& imageHeight();
+
+ bool imageHasAlpha();
+ bool imageSixteenBit();
+
+ int imageBitsDepth();
+ int imageBytesDepth();
+
+ TQMap<int, TQByteArray>& imageMetaData();
+ TQVariant imageGetAttribute(const TQString& key);
+ void imageSetAttribute(const TQString& key, const TQVariant& value);
+
+ TQMap<TQString, TQString>& imageEmbeddedText();
+ TQString imageGetEmbbededText(const TQString& key);
+ void imageSetEmbbededText(const TQString& key, const TQString& text);
+
+ virtual bool readMetadata(const TQString& filePath, DImg::FORMAT ff);
+ virtual bool saveMetadata(const TQString& filePath);
+ virtual int granularity(DImgLoaderObserver *observer, int total, float progressSlice = 1.0);
+
+ bool checkExifWorkingColorSpace();
+
+protected:
+
+ DImg *m_image;
+
+private:
+
+ DImgLoader();
+};
+
+} // NameSpace Digikam
+
+#endif /* DIMGLOADER_H */
diff --git a/src/libs/dimg/loaders/dimgloaderobserver.h b/src/libs/dimg/loaders/dimgloaderobserver.h
new file mode 100644
index 00000000..ea83bede
--- /dev/null
+++ b/src/libs/dimg/loaders/dimgloaderobserver.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-03
+ * Description : DImgLoader observer interface
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGLOADEROBSERVER_H
+#define DIMGLOADEROBSERVER_H
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImg;
+
+class DIGIKAM_EXPORT DImgLoaderObserver
+{
+
+public:
+ // posts progress information about image IO
+ virtual void progressInfo(const DImg *, float /*progress*/)
+ {};
+
+ // queries whether the image IO operation shall be continued
+ virtual bool continueQuery(const DImg *)
+ { return true; };
+
+ // Return a relative value which determines the granularity, the frequency
+ // with which the DImgLoaderObserver is checked and progress is posted.
+ // Standard is 1.0. Values < 1 mean less granularity (fewer checks),
+ // values > 1 mean higher granularity (more checks).
+ virtual float granularity()
+ { return 1.0; };
+
+ // This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader)
+ // is waiting for the process to finish, but the main thread is waiting
+ // for the thread to finish and no TDEProcess events are delivered.
+ // Remove when porting to TQt4.
+ virtual bool isShuttingDown()
+ { return false; }
+
+ virtual ~DImgLoaderObserver(){};
+};
+
+} // namespace Digikam
+
+#endif // DIMGLOADEROBSERVER_H
diff --git a/src/libs/dimg/loaders/iccjpeg.c b/src/libs/dimg/loaders/iccjpeg.c
new file mode 100644
index 00000000..fefa9509
--- /dev/null
+++ b/src/libs/dimg/loaders/iccjpeg.c
@@ -0,0 +1,270 @@
+/*
+ * Little cms
+ * Copyright (C) 1998-2004 Marti Maria
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * iccprofile.c
+ *
+ * This file provides code to read and write International Color Consortium
+ * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
+ * defined a standard format for including such data in JPEG "APP2" markers.
+ * The code given here does not know anything about the internal structure
+ * of the ICC profile data; it just knows how to put the profile data into
+ * a JPEG file being written, or get it back out when reading.
+ *
+ * This code depends on new features added to the IJG JPEG library as of
+ * IJG release 6b; it will not compile or work with older IJG versions.
+ *
+ * NOTE: this code would need surgery to work on 16-bit-int machines
+ * with ICC profiles exceeding 64K bytes in size. If you need to do that,
+ * change all the "unsigned int" variables to "INT32". You'll also need
+ * to find a malloc() replacement that can allocate more than 64K.
+ */
+
+#include "iccjpeg.h"
+#include <stdlib.h> /* define malloc() */
+
+
+/*
+ * Since an ICC profile can be larger than the maximum size of a JPEG marker
+ * (64K), we need provisions to split it into multiple markers. The format
+ * defined by the ICC specifies one or more APP2 markers containing the
+ * following data:
+ * Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
+ * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
+ * Number of markers Total number of APP2's used (1 byte)
+ * Profile data (remainder of APP2 data)
+ * Decoders should use the marker sequence numbers to reassemble the profile,
+ * rather than assuming that the APP2 markers appear in the correct sequence.
+ */
+
+#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
+#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
+#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
+#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
+
+
+/*
+ * This routine writes the given ICC profile data into a JPEG file.
+ * It *must* be called AFTER calling jpeg_start_compress() and BEFORE
+ * the first call to jpeg_write_scanlines().
+ * (This ordering ensures that the APP2 marker(s) will appear after the
+ * SOI and JFIF or Adobe markers, but before all else.)
+ */
+
+void
+write_icc_profile (j_compress_ptr cinfo,
+ const JOCTET *icc_data_ptr,
+ unsigned int icc_data_len)
+{
+ unsigned int num_markers; /* total number of markers we'll write */
+ int cur_marker = 1; /* per spec, counting starts at 1 */
+ unsigned int length; /* number of bytes to write in this marker */
+
+ /* Calculate the number of markers we'll need, rounding up of course */
+ num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER;
+ if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len)
+ num_markers++;
+
+ while (icc_data_len > 0) {
+ /* length of profile to put in this marker */
+ length = icc_data_len;
+ if (length > MAX_DATA_BYTES_IN_MARKER)
+ length = MAX_DATA_BYTES_IN_MARKER;
+ icc_data_len -= length;
+
+ /* Write the JPEG marker header (APP2 code and marker length) */
+ jpeg_write_m_header(cinfo, ICC_MARKER,
+ (unsigned int) (length + ICC_OVERHEAD_LEN));
+
+ /* Write the marker identifying string "ICC_PROFILE" (null-terminated).
+ * We code it in this less-than-transparent way so that the code works
+ * even if the local character set is not ASCII.
+ */
+ jpeg_write_m_byte(cinfo, 0x49);
+ jpeg_write_m_byte(cinfo, 0x43);
+ jpeg_write_m_byte(cinfo, 0x43);
+ jpeg_write_m_byte(cinfo, 0x5F);
+ jpeg_write_m_byte(cinfo, 0x50);
+ jpeg_write_m_byte(cinfo, 0x52);
+ jpeg_write_m_byte(cinfo, 0x4F);
+ jpeg_write_m_byte(cinfo, 0x46);
+ jpeg_write_m_byte(cinfo, 0x49);
+ jpeg_write_m_byte(cinfo, 0x4C);
+ jpeg_write_m_byte(cinfo, 0x45);
+ jpeg_write_m_byte(cinfo, 0x0);
+
+ /* Add the sequencing info */
+ jpeg_write_m_byte(cinfo, cur_marker);
+ jpeg_write_m_byte(cinfo, (int) num_markers);
+
+ /* Add the profile data */
+ while (length--) {
+ jpeg_write_m_byte(cinfo, *icc_data_ptr);
+ icc_data_ptr++;
+ }
+ cur_marker++;
+ }
+}
+
+
+/*
+ * Prepare for reading an ICC profile
+ */
+
+void
+setup_read_icc_profile (j_decompress_ptr cinfo)
+{
+ /* Tell the library to keep any APP2 data it may find */
+ jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF);
+}
+
+
+/*
+ * Handy subroutine to test whether a saved marker is an ICC profile marker.
+ */
+
+static boolean
+marker_is_icc (jpeg_saved_marker_ptr marker)
+{
+ return
+ marker->marker == ICC_MARKER &&
+ marker->data_length >= ICC_OVERHEAD_LEN &&
+ /* verify the identifying string */
+ GETJOCTET(marker->data[0]) == 0x49 &&
+ GETJOCTET(marker->data[1]) == 0x43 &&
+ GETJOCTET(marker->data[2]) == 0x43 &&
+ GETJOCTET(marker->data[3]) == 0x5F &&
+ GETJOCTET(marker->data[4]) == 0x50 &&
+ GETJOCTET(marker->data[5]) == 0x52 &&
+ GETJOCTET(marker->data[6]) == 0x4F &&
+ GETJOCTET(marker->data[7]) == 0x46 &&
+ GETJOCTET(marker->data[8]) == 0x49 &&
+ GETJOCTET(marker->data[9]) == 0x4C &&
+ GETJOCTET(marker->data[10]) == 0x45 &&
+ GETJOCTET(marker->data[11]) == 0x0;
+}
+
+
+/*
+ * See if there was an ICC profile in the JPEG file being read;
+ * if so, reassemble and return the profile data.
+ *
+ * TRUE is returned if an ICC profile was found, FALSE if not.
+ * If TRUE is returned, *icc_data_ptr is set to point to the
+ * returned data, and *icc_data_len is set to its length.
+ *
+ * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
+ * and must be freed by the caller with free() when the caller no longer
+ * needs it. (Alternatively, we could write this routine to use the
+ * IJG library's memory allocator, so that the data would be freed implicitly
+ * at jpeg_finish_decompress() time. But it seems likely that many apps
+ * will prefer to have the data stick around after decompression finishes.)
+ *
+ * NOTE: if the file contains invalid ICC APP2 markers, we just silently
+ * return FALSE. You might want to issue an error message instead.
+ */
+
+boolean
+read_icc_profile (j_decompress_ptr cinfo,
+ JOCTET **icc_data_ptr,
+ unsigned int *icc_data_len)
+{
+ jpeg_saved_marker_ptr marker;
+ int num_markers = 0;
+ int seq_no;
+ JOCTET *icc_data;
+ unsigned int total_length;
+#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */
+ char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */
+ unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */
+ unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */
+
+ *icc_data_ptr = NULL; /* avoid confusion if FALSE return */
+ *icc_data_len = 0;
+
+ /* This first pass over the saved markers discovers whether there are
+ * any ICC markers and verifies the consistency of the marker numbering.
+ */
+
+ for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++)
+ marker_present[seq_no] = 0;
+
+ for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (marker_is_icc(marker)) {
+ if (num_markers == 0)
+ num_markers = GETJOCTET(marker->data[13]);
+ else if (num_markers != GETJOCTET(marker->data[13]))
+ return FALSE; /* inconsistent num_markers fields */
+ seq_no = GETJOCTET(marker->data[12]);
+ if (seq_no <= 0 || seq_no > num_markers)
+ return FALSE; /* bogus sequence number */
+ if (marker_present[seq_no])
+ return FALSE; /* duplicate sequence numbers */
+ marker_present[seq_no] = 1;
+ data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
+ }
+ }
+
+ if (num_markers == 0)
+ return FALSE;
+
+ /* Check for missing markers, count total space needed,
+ * compute offset of each marker's part of the data.
+ */
+
+ total_length = 0;
+ for (seq_no = 1; seq_no <= num_markers; seq_no++) {
+ if (marker_present[seq_no] == 0)
+ return FALSE; /* missing sequence number */
+ data_offset[seq_no] = total_length;
+ total_length += data_length[seq_no];
+ }
+
+ if (total_length <= 0)
+ return FALSE; /* found only empty markers? */
+
+ /* Allocate space for assembled data */
+ icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET));
+ if (icc_data == NULL)
+ return FALSE; /* oops, out of memory */
+
+ /* and fill it in */
+ for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (marker_is_icc(marker)) {
+ JOCTET FAR *src_ptr;
+ JOCTET *dst_ptr;
+ unsigned int length;
+ seq_no = GETJOCTET(marker->data[12]);
+ dst_ptr = icc_data + data_offset[seq_no];
+ src_ptr = marker->data + ICC_OVERHEAD_LEN;
+ length = data_length[seq_no];
+ while (length--) {
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ }
+
+ *icc_data_ptr = icc_data;
+ *icc_data_len = total_length;
+
+ return TRUE;
+}
diff --git a/src/libs/dimg/loaders/iccjpeg.h b/src/libs/dimg/loaders/iccjpeg.h
new file mode 100644
index 00000000..2aab4196
--- /dev/null
+++ b/src/libs/dimg/loaders/iccjpeg.h
@@ -0,0 +1,101 @@
+/*
+ * Little cms
+ * Copyright (C) 1998-2004 Marti Maria
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * iccprofile.h
+ *
+ * This file provides code to read and write International Color Consortium
+ * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
+ * defined a standard format for including such data in JPEG "APP2" markers.
+ * The code given here does not know anything about the internal structure
+ * of the ICC profile data; it just knows how to put the profile data into
+ * a JPEG file being written, or get it back out when reading.
+ *
+ * This code depends on new features added to the IJG JPEG library as of
+ * IJG release 6b; it will not compile or work with older IJG versions.
+ *
+ * NOTE: this code would need surgery to work on 16-bit-int machines
+ * with ICC profiles exceeding 64K bytes in size. See iccprofile.c
+ * for details.
+ */
+
+#ifndef ICCJPEG_H
+#define ICCJPEG_H
+
+#include <stdio.h> /* needed to define "FILE", "NULL" */
+#include <jpeglib.h>
+
+
+/**
+ * This routine writes the given ICC profile data into a JPEG file.
+ * It *must* be called AFTER calling jpeg_start_compress() and BEFORE
+ * the first call to jpeg_write_scanlines().
+ * (This ordering ensures that the APP2 marker(s) will appear after the
+ * SOI and JFIF or Adobe markers, but before all else.)
+ */
+
+extern void write_icc_profile JPP((j_compress_ptr cinfo,
+ const JOCTET *icc_data_ptr,
+ unsigned int icc_data_len));
+
+
+/**
+ * Reading a JPEG file that may contain an ICC profile requires two steps:
+ *
+ * 1. After jpeg_create_decompress() but before jpeg_read_header(),
+ * call setup_read_icc_profile(). This routine tells the IJG library
+ * to save in memory any APP2 markers it may find in the file.
+ *
+ * 2. After jpeg_read_header(), call read_icc_profile() to find out
+ * whether there was a profile and obtain it if so.
+ */
+
+
+/**
+ * Prepare for reading an ICC profile
+ */
+
+extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo));
+
+
+/**
+ * See if there was an ICC profile in the JPEG file being read;
+ * if so, reassemble and return the profile data.
+ *
+ * TRUE is returned if an ICC profile was found, FALSE if not.
+ * If TRUE is returned, *icc_data_ptr is set to point to the
+ * returned data, and *icc_data_len is set to its length.
+ *
+ * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
+ * and must be freed by the caller with free() when the caller no longer
+ * needs it. (Alternatively, we could write this routine to use the
+ * IJG library's memory allocator, so that the data would be freed implicitly
+ * at jpeg_finish_decompress() time. But it seems likely that many apps
+ * will prefer to have the data stick around after decompression finishes.)
+ */
+
+extern boolean read_icc_profile JPP((j_decompress_ptr cinfo,
+ JOCTET **icc_data_ptr,
+ unsigned int *icc_data_len));
+
+#endif /* ICCJPEG_H */
diff --git a/src/libs/dimg/loaders/jp2kloader.cpp b/src/libs/dimg/loaders/jp2kloader.cpp
new file mode 100644
index 00000000..66351c25
--- /dev/null
+++ b/src/libs/dimg/loaders/jp2kloader.cpp
@@ -0,0 +1,715 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-14
+ * Description : A JPEG2000 IO file for DImg framework
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * This implementation use Jasper API
+ * library : http://www.ece.uvic.ca/~mdadams/jasper
+ * Other JPEG2000 encoder-decoder : http://www.openjpeg.org
+ *
+ * Others Linux JPEG2000 Loader implementation using Jasper:
+ * http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/jp2.c
+ * https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/jp2.c
+ * http://svn.ghostscript.com:8080/jasper/trunk/src/appl/jasper.c
+ * http://websvn.kde.org/trunk/KDE/tdelibs/kimgio/jp2.cpp
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// This line must be commented to prevent any latency time
+// when we use threaded image loader interface for each image
+// files io. Uncomment this line only for debugging.
+//#define ENABLE_DEBUG_MESSAGES
+
+// C ANSI includes.
+
+extern "C"
+{
+#if !defined(__STDC_LIMIT_MACROS)
+#define __STDC_LIMIT_MACROS
+#endif
+#include <stdint.h>
+}
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqcstring.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "jp2kloader.h"
+
+namespace Digikam
+{
+
+JP2KLoader::JP2KLoader(DImg* image)
+ : DImgLoader(image)
+{
+ m_hasAlpha = false;
+ m_sixteenBit = false;
+}
+
+bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ readMetadata(filePath, DImg::JPEG);
+
+ FILE *file = fopen(TQFile::encodeName(filePath), "rb");
+ if (!file)
+ return false;
+
+ unsigned char header[9];
+
+ if (fread(&header, 9, 1, file) != 1)
+ {
+ fclose(file);
+ return false;
+ }
+
+ unsigned char jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, };
+ unsigned char jpcID[2] = { 0xFF, 0x4F };
+
+ if (memcmp(&header[4], &jp2ID, 5) != 0 &&
+ memcmp(&header, &jpcID, 2) != 0)
+ {
+ // not a jpeg2000 file
+ fclose(file);
+ return false;
+ }
+
+ fclose(file);
+
+ // -------------------------------------------------------------------
+ // Initialize JPEG 2000 API.
+
+ long i, x, y;
+ int components[4];
+ unsigned int maximum_component_depth, scale[4], x_step[4], y_step[4];
+ unsigned long number_components;
+
+ jas_image_t *jp2_image = 0;
+ jas_stream_t *jp2_stream = 0;
+ jas_matrix_t *pixels[4];
+
+ int init = jas_init();
+ if (init != 0)
+ {
+ DDebug() << "Unable to init JPEG2000 decoder" << endl;
+ return false;
+ }
+
+ jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "rb");
+ if (jp2_stream == 0)
+ {
+ DDebug() << "Unable to open JPEG2000 stream" << endl;
+ return false;
+ }
+
+ jp2_image = jas_image_decode(jp2_stream, -1, 0);
+ if (jp2_image == 0)
+ {
+ jas_stream_close(jp2_stream);
+ DDebug() << "Unable to decode JPEG2000 image" << endl;
+ return false;
+ }
+
+ jas_stream_close(jp2_stream);
+
+ // some pseudo-progress
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ // -------------------------------------------------------------------
+ // Check color space.
+
+ switch (jas_clrspc_fam(jas_image_clrspc(jp2_image)))
+ {
+ case JAS_CLRSPC_FAM_RGB:
+ {
+ components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R);
+ components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G);
+ components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B);
+ if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0))
+ {
+ jas_image_destroy(jp2_image);
+ DDebug() << "Error parsing JPEG2000 image : Missing Image Channel" << endl;
+ return false;
+ }
+
+ number_components = 3;
+ components[3] = jas_image_getcmptbytype(jp2_image, 3);
+ if (components[3] > 0)
+ {
+ m_hasAlpha = true;
+ number_components++;
+ }
+ break;
+ }
+ case JAS_CLRSPC_FAM_GRAY:
+ {
+ components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y);
+ if (components[0] < 0)
+ {
+ jas_image_destroy(jp2_image);
+ DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl;
+ return false;
+ }
+ number_components=1;
+ break;
+ }
+ case JAS_CLRSPC_FAM_YCBCR:
+ {
+ components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y);
+ components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB);
+ components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR);
+ if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0))
+ {
+ jas_image_destroy(jp2_image);
+ DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl;
+ return false;
+ }
+ number_components = 3;
+ components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN);
+ if (components[3] > 0)
+ {
+ m_hasAlpha = true;
+ number_components++;
+ }
+ // FIXME : image->colorspace=YCbCrColorspace;
+ break;
+ }
+ default:
+ {
+ jas_image_destroy(jp2_image);
+ DDebug() << "Error parsing JP2000 image : Colorspace Model Is Not Supported" << endl;
+ return false;
+ }
+ }
+
+ // -------------------------------------------------------------------
+ // Check image geometry.
+
+ imageWidth() = jas_image_width(jp2_image);
+ imageHeight() = jas_image_height(jp2_image);
+
+ for (i = 0; i < (long)number_components; i++)
+ {
+ if ((((jas_image_cmptwidth(jp2_image, components[i])*
+ jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) ||
+ (((jas_image_cmptheight(jp2_image, components[i])*
+ jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) ||
+ (jas_image_cmpttlx(jp2_image, components[i]) != 0) ||
+ (jas_image_cmpttly(jp2_image, components[i]) != 0) ||
+ (jas_image_cmptsgnd(jp2_image, components[i]) != false))
+ {
+ jas_image_destroy(jp2_image);
+ DDebug() << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported" << endl;
+ return false;
+ }
+ x_step[i] = jas_image_cmpthstep(jp2_image, components[i]);
+ y_step[i] = jas_image_cmptvstep(jp2_image, components[i]);
+ }
+
+ // -------------------------------------------------------------------
+ // Convert image data.
+
+ m_hasAlpha = number_components > 3;
+ maximum_component_depth = 0;
+
+ for (i = 0; i < (long)number_components; i++)
+ {
+ maximum_component_depth = TQMAX(jas_image_cmptprec(jp2_image,components[i]),
+ (long)maximum_component_depth);
+ pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth())/x_step[i]);
+ if (!pixels[i])
+ {
+ jas_image_destroy(jp2_image);
+ DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl;
+ return false;
+ }
+ }
+
+ if (maximum_component_depth > 8)
+ m_sixteenBit = true;
+
+ for (i = 0 ; i < (long)number_components ; i++)
+ {
+ scale[i] = 1;
+ int prec = jas_image_cmptprec(jp2_image, components[i]);
+ if (m_sixteenBit && prec < 16)
+ scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i])));
+ }
+
+ uchar* data = 0;
+ if (m_sixteenBit) // 16 bits image.
+ data = new uchar[imageWidth()*imageHeight()*8];
+ else
+ data = new uchar[imageWidth()*imageHeight()*4];
+
+ if (!data)
+ {
+ DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl;
+ jas_image_destroy(jp2_image);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+ return false;
+ }
+
+ uint checkPoint = 0;
+ uchar *dst = data;
+ unsigned short *dst16 = (unsigned short *)data;
+
+ for (y = 0 ; y < (long)imageHeight() ; y++)
+ {
+ for (i = 0 ; i < (long)number_components; i++)
+ {
+ int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0,
+ ((unsigned int) y) / y_step[i],
+ ((unsigned int) imageWidth()) / x_step[i],
+ 1, pixels[i]);
+ if (ret != 0)
+ {
+ DDebug() << "Error decoding JPEG2000 image data" << endl;
+ delete [] data;
+ jas_image_destroy(jp2_image);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+ return false;
+ }
+ }
+
+ switch (number_components)
+ {
+ case 1: // Grayscale.
+ {
+ for (x = 0 ; x < (long)imageWidth() ; x++)
+ {
+ dst[0] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
+ dst[1] = dst[0];
+ dst[2] = dst[0];
+ dst[3] = 0xFF;
+
+ dst += 4;
+ }
+ break;
+ }
+ case 3: // RGB.
+ {
+ if (!m_sixteenBit) // 8 bits image.
+ {
+ for (x = 0 ; x < (long)imageWidth() ; x++)
+ {
+ // Blue
+ dst[0] = (uchar)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2]));
+ // Green
+ dst[1] = (uchar)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1]));
+ // Red
+ dst[2] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
+ // Alpha
+ dst[3] = 0xFF;
+
+ dst += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ for (x = 0 ; x < (long)imageWidth() ; x++)
+ {
+ // Blue
+ dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2]));
+ // Green
+ dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1]));
+ // Red
+ dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
+ // Alpha
+ dst16[3] = 0xFFFF;
+
+ dst16 += 4;
+ }
+ }
+
+ break;
+ }
+ case 4: // RGBA.
+ {
+ if (!m_sixteenBit) // 8 bits image.
+ {
+ for (x = 0 ; x < (long)imageWidth() ; x++)
+ {
+ // Blue
+ dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x/x_step[2]));
+ // Green
+ dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x/x_step[1]));
+ // Red
+ dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x/x_step[0]));
+ // Alpha
+ dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x/x_step[3]));
+
+ dst += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ for (x = 0 ; x < (long)imageWidth() ; x++)
+ {
+ // Blue
+ dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2]));
+ // Green
+ dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1]));
+ // Red
+ dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0]));
+ // Alpha
+ dst16[3] = (unsigned short)(scale[3]*jas_matrix_getv(pixels[3], x/x_step[3]));
+
+ dst16 += 4;
+ }
+ }
+
+ break;
+ }
+ }
+
+ // use 0-10% and 90-100% for pseudo-progress
+ if (observer && y >= (long)checkPoint)
+ {
+ checkPoint += granularity(observer, y, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ jas_image_destroy(jp2_image);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) )));
+ }
+ }
+
+ // -------------------------------------------------------------------
+ // Get ICC color profile.
+
+ jas_iccprof_t *icc_profile = 0;
+ jas_stream_t *icc_stream = 0;
+ jas_cmprof_t *cm_profile = 0;
+
+ cm_profile = jas_image_cmprof(jp2_image);
+ if (cm_profile != 0)
+ icc_profile = jas_iccprof_createfromcmprof(cm_profile);
+
+ if (icc_profile != 0)
+ {
+ icc_stream = jas_stream_memopen(NULL, 0);
+
+ if (icc_stream != 0)
+ {
+ if (jas_iccprof_save(icc_profile, icc_stream) == 0)
+ {
+ if (jas_stream_flush(icc_stream) == 0)
+ {
+ TQMap<int, TQByteArray>& metaData = imageMetaData();
+ jas_stream_memobj_t *blob = (jas_stream_memobj_t *) icc_stream->obj_;
+ TQByteArray profile_rawdata(blob->len_);
+ memcpy(profile_rawdata.data(), blob->buf_, blob->len_);
+ metaData.insert(DImg::ICC, profile_rawdata);
+ jas_stream_close(icc_stream);
+ }
+ }
+ }
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 1.0);
+
+ imageSetAttribute("format", "JP2K");
+ imageData() = data;
+
+ jas_image_destroy(jp2_image);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+
+ return true;
+}
+
+bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ FILE *file = fopen(TQFile::encodeName(filePath), "wb");
+ if (!file)
+ return false;
+
+ fclose(file);
+
+ // -------------------------------------------------------------------
+ // Initialize JPEG 2000 API.
+
+ long i, x, y;
+ unsigned long number_components;
+
+ jas_image_t *jp2_image = 0;
+ jas_stream_t *jp2_stream = 0;
+ jas_matrix_t *pixels[4];
+ jas_image_cmptparm_t component_info[4];
+
+ int init = jas_init();
+ if (init != 0)
+ {
+ DDebug() << "Unable to init JPEG2000 decoder" << endl;
+ return false;
+ }
+
+ jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "wb");
+ if (jp2_stream == 0)
+ {
+ DDebug() << "Unable to open JPEG2000 stream" << endl;
+ return false;
+ }
+
+ number_components = imageHasAlpha() ? 4 : 3;
+
+ for (i = 0 ; i < (long)number_components ; i++)
+ {
+ component_info[i].tlx = 0;
+ component_info[i].tly = 0;
+ component_info[i].hstep = 1;
+ component_info[i].vstep = 1;
+ component_info[i].width = imageWidth();
+ component_info[i].height = imageHeight();
+ component_info[i].prec = imageBitsDepth();
+ component_info[i].sgnd = false;
+ }
+
+ jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN);
+ if (jp2_image == 0)
+ {
+ jas_stream_close(jp2_stream);
+ DDebug() << "Unable to create JPEG2000 image" << endl;
+ return false;
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ // -------------------------------------------------------------------
+ // Check color space.
+
+ if (number_components >= 3 ) // RGB & RGBA
+ {
+ // Alpha Channel
+ if (number_components == 4 )
+ jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY);
+
+ jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB);
+ jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R));
+ jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G));
+ jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B));
+ }
+
+ // -------------------------------------------------------------------
+ // Set ICC color profile.
+
+ // FIXME : doesn't work yet!
+
+ jas_cmprof_t *cm_profile = 0;
+ jas_iccprof_t *icc_profile = 0;
+
+ TQByteArray profile_rawdata = m_image->getICCProfil();
+
+ icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size());
+ if (icc_profile != 0)
+ {
+ cm_profile = jas_cmprof_createfromiccprof(icc_profile);
+ if (cm_profile != 0)
+ {
+ jas_image_setcmprof(jp2_image, cm_profile);
+ }
+ }
+
+ // -------------------------------------------------------------------
+ // Convert to JPEG 2000 pixels.
+
+ for (i = 0 ; i < (long)number_components ; i++)
+ {
+ pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth());
+ if (pixels[i] == 0)
+ {
+ for (x = 0 ; x < i ; x++)
+ jas_matrix_destroy(pixels[x]);
+
+ jas_image_destroy(jp2_image);
+ DDebug() << "Error encoding JPEG2000 image data : Memory Allocation Failed" << endl;
+ return false;
+ }
+ }
+
+ unsigned char* data = imageData();
+ unsigned char* pixel;
+ unsigned short r, g, b, a=0;
+ uint checkpoint = 0;
+
+ for (y = 0 ; y < (long)imageHeight() ; y++)
+ {
+ if (observer && y == (long)checkpoint)
+ {
+ checkpoint += granularity(observer, imageHeight(), 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ jas_image_destroy(jp2_image);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) )));
+ }
+
+ for (x = 0 ; x < (long)imageWidth() ; x++)
+ {
+ pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()];
+
+ if ( imageSixteenBit() ) // 16 bits image.
+ {
+ b = (unsigned short)(pixel[0]+256*pixel[1]);
+ g = (unsigned short)(pixel[2]+256*pixel[3]);
+ r = (unsigned short)(pixel[4]+256*pixel[5]);
+
+ if (imageHasAlpha())
+ a = (unsigned short)(pixel[6]+256*pixel[7]);
+ }
+ else // 8 bits image.
+ {
+ b = (unsigned short)pixel[0];
+ g = (unsigned short)pixel[1];
+ r = (unsigned short)pixel[2];
+
+ if (imageHasAlpha())
+ a = (unsigned short)(pixel[3]);
+ }
+
+ jas_matrix_setv(pixels[0], x, r);
+ jas_matrix_setv(pixels[1], x, g);
+ jas_matrix_setv(pixels[2], x, b);
+
+ if (number_components > 3)
+ jas_matrix_setv(pixels[3], x, a);
+ }
+
+ for (i = 0 ; i < (long)number_components ; i++)
+ {
+ int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y,
+ (unsigned int)imageWidth(), 1, pixels[i]);
+ if (ret != 0)
+ {
+ DDebug() << "Error encoding JPEG2000 image data" << endl;
+
+ jas_image_destroy(jp2_image);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+ return false;
+ }
+ }
+ }
+
+ TQVariant qualityAttr = imageGetAttribute("quality");
+ int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
+
+ if (quality < 0)
+ quality = 90;
+ if (quality > 100)
+ quality = 100;
+
+ TQString rate;
+ TQTextStream ts( &rate, IO_WriteOnly );
+
+ // NOTE: to have a lossless compression use quality=100.
+ // jp2_encode()::optstr:
+ // - rate=#B => the resulting file size is about # bytes
+ // - rate=0.0 .. 1.0 => the resulting file size is about the factor times
+ // the uncompressed size
+ ts << "rate=" << ( quality / 100.0F );
+
+ DDebug() << "JPEG2000 quality: " << quality << endl;
+ DDebug() << "JPEG2000 " << rate << endl;
+
+# if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3)
+ const jas_image_fmtinfo_t *jp2_fmtinfo = jas_image_lookupfmtbyname("jp2");
+ int ret = -1;
+ if (jp2_fmtinfo)
+ {
+ ret = jas_image_encode(jp2_image, jp2_stream, jp2_fmtinfo->id, rate.utf8().data());
+ }
+# else
+ int ret = jp2_encode(jp2_image, jp2_stream, rate.utf8().data());
+# endif
+
+ if (ret != 0)
+ {
+ DDebug() << "Unable to encode JPEG2000 image" << endl;
+
+ jas_image_destroy(jp2_image);
+ jas_stream_close(jp2_stream);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+
+ return false;
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 1.0);
+
+ imageSetAttribute("savedformat", "JP2K");
+
+ saveMetadata(filePath);
+
+ jas_image_destroy(jp2_image);
+ jas_stream_close(jp2_stream);
+ for (i = 0 ; i < (long)number_components ; i++)
+ jas_matrix_destroy(pixels[i]);
+
+ jas_cleanup();
+
+ return true;
+}
+
+bool JP2KLoader::hasAlpha() const
+{
+ return m_hasAlpha;
+}
+
+bool JP2KLoader::sixteenBit() const
+{
+ return m_sixteenBit;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/jp2kloader.h b/src/libs/dimg/loaders/jp2kloader.h
new file mode 100644
index 00000000..04ec214e
--- /dev/null
+++ b/src/libs/dimg/loaders/jp2kloader.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-14
+ * Description : A JPEG2000 IO file for DImg framework
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef JP2KLOADER_H
+#define JP2KLOADER_H
+
+// C ansi includes.
+
+extern "C"
+{
+#include <jasper/jasper.h>
+}
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Local includes.
+
+#include "dimgloader.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+class DImg;
+
+class DIGIKAM_EXPORT JP2KLoader : public DImgLoader
+{
+
+public:
+
+ JP2KLoader(DImg* image);
+
+ bool load(const TQString& filePath, DImgLoaderObserver *observer);
+ bool save(const TQString& filePath, DImgLoaderObserver *observer);
+
+ virtual bool hasAlpha() const;
+ virtual bool sixteenBit() const;
+ virtual bool isReadOnly() const { return false; };
+
+private:
+
+ bool m_sixteenBit;
+ bool m_hasAlpha;
+};
+
+} // NameSpace Digikam
+
+#endif /* JP2KLOADER_H */
diff --git a/src/libs/dimg/loaders/jp2ksettings.cpp b/src/libs/dimg/loaders/jp2ksettings.cpp
new file mode 100644
index 00000000..af0737c1
--- /dev/null
+++ b/src/libs/dimg/loaders/jp2ksettings.cpp
@@ -0,0 +1,139 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save JPEG 2000 image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlabel.h>
+#include <tqcheckbox.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <knuminput.h>
+
+// Local includes.
+
+#include "jp2ksettings.h"
+#include "jp2ksettings.moc"
+
+namespace Digikam
+{
+
+class JP2KSettingsPriv
+{
+
+public:
+
+ JP2KSettingsPriv()
+ {
+ JPEG2000Grid = 0;
+ labelJPEG2000compression = 0;
+ JPEG2000compression = 0;
+ JPEG2000LossLess = 0;
+ }
+
+ TQGridLayout *JPEG2000Grid;
+
+ TQLabel *labelJPEG2000compression;
+
+ TQCheckBox *JPEG2000LossLess;
+
+ KIntNumInput *JPEG2000compression;
+};
+
+JP2KSettings::JP2KSettings(TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new JP2KSettingsPriv;
+
+ d->JPEG2000Grid = new TQGridLayout(this, 1, 1, KDialog::spacingHint());
+ d->JPEG2000LossLess = new TQCheckBox(i18n("Lossless JPEG 2000 files"), this);
+
+ TQWhatsThis::add(d->JPEG2000LossLess, i18n("<p>Toggle lossless compression for JPEG 2000 images.<p>"
+ "If you enable this option, you will use a lossless method "
+ "to compress JPEG 2000 pictures.<p>"));
+
+ d->JPEG2000compression = new KIntNumInput(75, this);
+ d->JPEG2000compression->setRange(1, 100, 1, true );
+ d->labelJPEG2000compression = new TQLabel(i18n("JPEG 2000 quality:"), this);
+
+ TQWhatsThis::add( d->JPEG2000compression, i18n("<p>The quality value for JPEG 2000 images:<p>"
+ "<b>1</b>: low quality (high compression and small "
+ "file size)<p>"
+ "<b>50</b>: medium quality<p>"
+ "<b>75</b>: good quality (default)<p>"
+ "<b>100</b>: high quality (no compression and "
+ "large file size)<p>"
+ "<b>Note: JPEG 2000 is not a lossless image "
+ "compression format when you use this setting.</b>"));
+
+ d->JPEG2000Grid->addMultiCellWidget(d->JPEG2000LossLess, 0, 0, 0, 1);
+ d->JPEG2000Grid->addMultiCellWidget(d->labelJPEG2000compression, 1, 1, 0, 0);
+ d->JPEG2000Grid->addMultiCellWidget(d->JPEG2000compression, 1, 1, 1, 1);
+ d->JPEG2000Grid->setColStretch(1, 10);
+
+ connect(d->JPEG2000LossLess, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotToggleJPEG2000LossLess(bool)));
+
+ connect(d->JPEG2000LossLess, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotToggleJPEG2000LossLess(bool)));
+}
+
+JP2KSettings::~JP2KSettings()
+{
+ delete d;
+}
+
+void JP2KSettings::setCompressionValue(int val)
+{
+ d->JPEG2000compression->setValue(val);
+}
+
+int JP2KSettings::getCompressionValue()
+{
+ return d->JPEG2000compression->value();
+}
+
+void JP2KSettings::setLossLessCompression(bool b)
+{
+ d->JPEG2000LossLess->setChecked(b);
+ slotToggleJPEG2000LossLess(d->JPEG2000LossLess->isChecked());
+}
+
+bool JP2KSettings::getLossLessCompression()
+{
+ return d->JPEG2000LossLess->isChecked();
+}
+
+void JP2KSettings::slotToggleJPEG2000LossLess(bool b)
+{
+ d->JPEG2000compression->setEnabled(!b);
+ d->labelJPEG2000compression->setEnabled(!b);
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/dimg/loaders/jp2ksettings.h b/src/libs/dimg/loaders/jp2ksettings.h
new file mode 100644
index 00000000..951cb2fc
--- /dev/null
+++ b/src/libs/dimg/loaders/jp2ksettings.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save JPEG 2000 image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef JP2KSETTINGS_H
+#define JP2KSETTINGS_H
+
+// KDE includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class JP2KSettingsPriv;
+
+class DIGIKAM_EXPORT JP2KSettings : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ JP2KSettings(TQWidget *parent=0);
+ ~JP2KSettings();
+
+ void setCompressionValue(int val);
+ int getCompressionValue();
+
+ void setLossLessCompression(bool b);
+ bool getLossLessCompression();
+
+private slots:
+
+ void slotToggleJPEG2000LossLess(bool);
+
+private:
+
+ JP2KSettingsPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* JP2KSETTINGS_H */
diff --git a/src/libs/dimg/loaders/jpegloader.cpp b/src/libs/dimg/loaders/jpegloader.cpp
new file mode 100644
index 00000000..58f6e20a
--- /dev/null
+++ b/src/libs/dimg/loaders/jpegloader.cpp
@@ -0,0 +1,676 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : A JPEG IO file for DImg framework
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define XMD_H
+
+// This line must be commented to prevent any latency time
+// when we use threaded image loader interface for each image
+// files io. Uncomment this line only for debugging.
+//#define ENABLE_DEBUG_MESSAGES
+
+// C ansi includes.
+
+extern "C"
+{
+#include "iccjpeg.h"
+}
+
+// C+ includes.
+
+#include <cstdio>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqcstring.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "jpegloader.h"
+
+namespace Digikam
+{
+
+// To manage Errors/Warnings handling provide by libjpeg
+
+void JPEGLoader::dimg_jpeg_error_exit(j_common_ptr cinfo)
+{
+ dimg_jpeg_error_mgr* myerr = (dimg_jpeg_error_mgr*) cinfo->err;
+
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << buffer << endl;
+#endif
+
+ longjmp(myerr->setjmp_buffer, 1);
+}
+
+void JPEGLoader::dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << buffer << " (" << msg_level << ")" << endl;
+#else
+ Q_UNUSED(msg_level);
+#endif
+}
+
+void JPEGLoader::dimg_jpeg_output_message(j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << buffer << endl;
+#endif
+}
+
+JPEGLoader::JPEGLoader(DImg* image)
+ : DImgLoader(image)
+{
+}
+
+bool JPEGLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ readMetadata(filePath, DImg::JPEG);
+
+ FILE *file = fopen(TQFile::encodeName(filePath), "rb");
+ if (!file)
+ return false;
+
+ unsigned char header[2];
+
+ if (fread(&header, 2, 1, file) != 1)
+ {
+ fclose(file);
+ return false;
+ }
+
+ unsigned char jpegID[] = { 0xFF, 0xD8 };
+
+ if (memcmp(header, jpegID, 2) != 0)
+ {
+ // not a jpeg file
+ fclose(file);
+ return false;
+ }
+
+ fseek(file, 0L, SEEK_SET);
+
+ struct jpeg_decompress_struct cinfo;
+ struct dimg_jpeg_error_mgr jerr;
+
+ // -------------------------------------------------------------------
+ // JPEG error handling.
+
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.err->error_exit = dimg_jpeg_error_exit;
+ cinfo.err->emit_message = dimg_jpeg_emit_message;
+ cinfo.err->output_message = dimg_jpeg_output_message;
+
+ // If an error occurs during reading, libjpeg will jump here
+
+ if (setjmp(jerr.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ return false;
+ }
+
+ // -------------------------------------------------------------------
+ // Find out if we do the fast-track loading with reduced size. Jpeg specific.
+ int scaledLoadingSize = 0;
+ TQVariant attribute = imageGetAttribute("jpegScaledLoadingSize");
+ if (attribute.isValid())
+ scaledLoadingSize = attribute.toInt();
+
+ // -------------------------------------------------------------------
+ // Set JPEG decompressor instance
+
+ jpeg_create_decompress(&cinfo);
+ jpeg_stdio_src(&cinfo, file);
+
+ // Recording ICC profile marker (from iccjpeg.c)
+ setup_read_icc_profile(&cinfo);
+
+ // read image information
+ jpeg_read_header(&cinfo, true);
+
+ // set decompression parameters
+ cinfo.do_fancy_upsampling = false;
+ cinfo.do_block_smoothing = false;
+
+ if (scaledLoadingSize)
+ {
+ int imgSize = TQMAX(cinfo.image_width, cinfo.image_height);
+
+ // libjpeg supports 1/1, 1/2, 1/4, 1/8
+ int scale=1;
+ while(scaledLoadingSize*scale*2<=imgSize)
+ {
+ scale*=2;
+ }
+ if(scale>8) scale=8;
+
+ cinfo.scale_num=1;
+ cinfo.scale_denom=scale;
+ }
+
+ // Libjpeg handles the following conversions:
+ // YCbCr => GRAYSCALE, YCbCr => RGB, GRAYSCALE => RGB, YCCK => CMYK
+ // So we cannot get RGB from CMYK or YCCK, CMYK conversion is handled below
+ switch (cinfo.jpeg_color_space)
+ {
+ case JCS_UNKNOWN:
+ // perhaps jpeg_read_header did some guessing, leave value unchanged
+ break;
+ case JCS_GRAYSCALE:
+ case JCS_RGB:
+ case JCS_YCbCr:
+ cinfo.out_color_space = JCS_RGB;
+ break;
+ case JCS_CMYK:
+ case JCS_YCCK:
+ cinfo.out_color_space = JCS_CMYK;
+ break;
+ }
+
+ // initialize decompression
+ jpeg_start_decompress(&cinfo);
+
+ // some pseudo-progress
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ // -------------------------------------------------------------------
+ // Get image data.
+
+ int w = cinfo.output_width;
+ int h = cinfo.output_height;
+ uchar *dest = 0;
+
+ uchar *ptr, *line[16], *data=0;
+ uchar *ptr2=0;
+ int x, y, l, i, scans, count, prevy;
+
+ if (cinfo.rec_outbuf_height > 16)
+ {
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ DDebug() << k_funcinfo << "Height of JPEG scanline buffer out of range!" << endl;
+ return false;
+ }
+
+ // We only take RGB with 1 or 3 components, or CMYK with 4 components
+ if (!(
+ (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1))
+ || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4)
+ ))
+ {
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ DDebug() << k_funcinfo
+ << "JPEG colorspace ("
+ << cinfo.out_color_space
+ << ") or Number of JPEG color components ("
+ << cinfo.output_components
+ << ") unsupported!" << endl;
+ return false;
+ }
+
+ data = new uchar[w * 16 * cinfo.output_components];
+
+ if (!data)
+ {
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ DDebug() << k_funcinfo << "Cannot allocate memory!" << endl;
+ return false;
+ }
+
+ dest = new uchar[w * h * 4];
+
+ if (!dest)
+ {
+ delete [] data;
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ DDebug() << k_funcinfo << "Cannot allocate memory!" << endl;
+ return false;
+ }
+
+ ptr2 = dest;
+ count = 0;
+ prevy = 0;
+
+ if (cinfo.output_components == 3)
+ {
+ for (i = 0; i < cinfo.rec_outbuf_height; i++)
+ line[i] = data + (i * w * 3);
+
+ int checkPoint = 0;
+ for (l = 0; l < h; l += cinfo.rec_outbuf_height)
+ {
+ // use 0-10% and 90-100% for pseudo-progress
+ if (observer && l >= checkPoint)
+ {
+ checkPoint += granularity(observer, h, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ delete [] dest;
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) )));
+ }
+
+ jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height);
+ scans = cinfo.rec_outbuf_height;
+
+ if ((h - l) < scans)
+ scans = h - l;
+
+ ptr = data;
+
+ for (y = 0; y < scans; y++)
+ {
+ for (x = 0; x < w; x++)
+ {
+ ptr2[3] = 0xFF;
+ ptr2[2] = ptr[0];
+ ptr2[1] = ptr[1];
+ ptr2[0] = ptr[2];
+
+ ptr += 3;
+ ptr2 += 4;
+ }
+ }
+ }
+ }
+ else if (cinfo.output_components == 1)
+ {
+ for (i = 0; i < cinfo.rec_outbuf_height; i++)
+ line[i] = data + (i * w);
+
+ int checkPoint = 0;
+ for (l = 0; l < h; l += cinfo.rec_outbuf_height)
+ {
+ if (observer && l >= checkPoint)
+ {
+ checkPoint += granularity(observer, h, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ delete [] dest;
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) )));
+ }
+
+ jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height);
+ scans = cinfo.rec_outbuf_height;
+
+ if ((h - l) < scans)
+ scans = h - l;
+
+ ptr = data;
+
+ for (y = 0; y < scans; y++)
+ {
+ for (x = 0; x < w; x++)
+ {
+ ptr2[3] = 0xFF;
+ ptr2[2] = ptr[0];
+ ptr2[1] = ptr[0];
+ ptr2[0] = ptr[0];
+
+ ptr ++;
+ ptr2 += 4;
+ }
+ }
+ }
+ }
+ else // CMYK
+ {
+ for (i = 0; i < cinfo.rec_outbuf_height; i++)
+ line[i] = data + (i * w * 4);
+
+ int checkPoint = 0;
+ for (l = 0; l < h; l += cinfo.rec_outbuf_height)
+ {
+ // use 0-10% and 90-100% for pseudo-progress
+ if (observer && l >= checkPoint)
+ {
+ checkPoint += granularity(observer, h, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ delete [] dest;
+ jpeg_destroy_decompress(&cinfo);
+ fclose(file);
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) )));
+ }
+
+ jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height);
+ scans = cinfo.rec_outbuf_height;
+
+ if ((h - l) < scans)
+ scans = h - l;
+
+ ptr = data;
+
+ for (y = 0; y < scans; y++)
+ {
+ for (x = 0; x < w; x++)
+ {
+ // Inspired by TQt's JPEG loader
+
+ int k = ptr[3];
+
+ ptr2[3] = 0xFF;
+ ptr2[2] = k * ptr[0] / 255;
+ ptr2[1] = k * ptr[1] / 255;
+ ptr2[0] = k * ptr[2] / 255;
+
+ ptr += 4;
+ ptr2 += 4;
+ }
+ }
+ }
+ }
+
+ delete [] data;
+
+ // -------------------------------------------------------------------
+ // Read image ICC profile
+
+ TQMap<int, TQByteArray>& metaData = imageMetaData();
+
+ JOCTET *profile_data=NULL;
+ uint profile_size;
+
+ read_icc_profile (&cinfo, &profile_data, &profile_size);
+
+ if (profile_data != NULL)
+ {
+ TQByteArray profile_rawdata(profile_size);
+ memcpy(profile_rawdata.data(), profile_data, profile_size);
+ metaData.insert(DImg::ICC, profile_rawdata);
+ free (profile_data);
+ }
+ else
+ {
+ // If ICC profile is null, check Exif metadata.
+ checkExifWorkingColorSpace();
+ }
+
+ // -------------------------------------------------------------------
+
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+
+ // -------------------------------------------------------------------
+
+ fclose(file);
+
+ if (observer)
+ observer->progressInfo(m_image, 1.0);
+
+ imageWidth() = w;
+ imageHeight() = h;
+ imageData() = dest;
+ imageSetAttribute("format", "JPEG");
+
+ return true;
+}
+
+bool JPEGLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ FILE *file = fopen(TQFile::encodeName(filePath), "wb");
+ if (!file)
+ return false;
+
+ struct jpeg_compress_struct cinfo;
+ struct dimg_jpeg_error_mgr jerr;
+
+ // -------------------------------------------------------------------
+ // JPEG error handling.
+
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.err->error_exit = dimg_jpeg_error_exit;
+ cinfo.err->emit_message = dimg_jpeg_emit_message;
+ cinfo.err->output_message = dimg_jpeg_output_message;
+
+ // If an error occurs during writing, libjpeg will jump here
+
+ if (setjmp(jerr.setjmp_buffer))
+ {
+ jpeg_destroy_compress(&cinfo);
+ fclose(file);
+ return false;
+ }
+
+ // -------------------------------------------------------------------
+ // Set JPEG compressor instance
+
+ jpeg_create_compress(&cinfo);
+ jpeg_stdio_dest(&cinfo, file);
+
+ uint& w = imageWidth();
+ uint& h = imageHeight();
+ unsigned char*& data = imageData();
+
+ // Size of image.
+ cinfo.image_width = w;
+ cinfo.image_height = h;
+
+ // Color components of image in RGB.
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+
+ TQVariant qualityAttr = imageGetAttribute("quality");
+ int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
+
+ if (quality < 0)
+ quality = 90;
+ if (quality > 100)
+ quality = 100;
+
+ TQVariant subSamplingAttr = imageGetAttribute("subsampling");
+ int subsampling = subSamplingAttr.isValid() ? subSamplingAttr.toInt() : 1; // Medium
+
+ jpeg_set_defaults(&cinfo);
+
+ // B.K.O #149578: set horizontal and vertical chroma subsampling factor to encoder.
+ // See this page for details: http://en.wikipedia.org/wiki/Chroma_subsampling
+
+ switch (subsampling)
+ {
+ case 1: // 2x1, 1x1, 1x1 (4:2:2) : Medium
+ {
+ DDebug() << "Using LibJPEG medium chroma-subsampling (4:2:2)" << endl;
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].h_samp_factor = 1;
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1;
+ cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+ }
+ case 2: // 2x2, 1x1, 1x1 (4:1:1) : High
+ {
+ DDebug() << "Using LibJPEG high chroma-subsampling (4:1:1)" << endl;
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ cinfo.comp_info[1].h_samp_factor = 1;
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1;
+ cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+ }
+ default: // 1x1 1x1 1x1 (4:4:4) : None
+ {
+ DDebug() << "Using LibJPEG none chroma-subsampling (4:4:4)" << endl;
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].h_samp_factor = 1;
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1;
+ cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+ }
+ }
+
+ jpeg_set_quality(&cinfo, quality, true);
+ jpeg_start_compress(&cinfo, true);
+
+ DDebug() << "Using LibJPEG quality compression value: " << quality << endl;
+
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ // -------------------------------------------------------------------
+ // Write ICC profil.
+
+ TQByteArray profile_rawdata = m_image->getICCProfil();
+
+ if (!profile_rawdata.isEmpty())
+ {
+ write_icc_profile (&cinfo, (JOCTET *)profile_rawdata.data(), profile_rawdata.size());
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 0.2);
+
+ // -------------------------------------------------------------------
+ // Write Image data.
+
+ uchar* line = new uchar[w*3];
+ uchar* dstPtr = 0;
+ uint checkPoint = 0;
+
+ if (!imageSixteenBit()) // 8 bits image.
+ {
+
+ uchar* srcPtr = data;
+
+ for (uint j=0; j<h; j++)
+ {
+
+ if (observer && j == checkPoint)
+ {
+ checkPoint += granularity(observer, h, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] line;
+ jpeg_destroy_compress(&cinfo);
+ fclose(file);
+ return false;
+ }
+ // use 0-20% for pseudo-progress, now fill 20-100%
+ observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)j)/((float)h) )));
+ }
+
+ dstPtr = line;
+
+ for (uint i = 0; i < w; i++)
+ {
+ dstPtr[2] = srcPtr[0];
+ dstPtr[1] = srcPtr[1];
+ dstPtr[0] = srcPtr[2];
+
+ srcPtr += 4;
+ dstPtr += 3;
+ }
+
+ jpeg_write_scanlines(&cinfo, &line, 1);
+ }
+ }
+ else
+ {
+ unsigned short* srcPtr = (unsigned short*)data;
+
+ for (uint j=0; j<h; j++)
+ {
+
+ if (observer && j == checkPoint)
+ {
+ checkPoint += granularity(observer, h, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] line;
+ jpeg_destroy_compress(&cinfo);
+ fclose(file);
+ return false;
+ }
+ // use 0-20% for pseudo-progress, now fill 20-100%
+ observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)j)/((float)h) )));
+ }
+
+ dstPtr = line;
+
+ for (uint i = 0; i < w; i++)
+ {
+ dstPtr[2] = (srcPtr[0] * 255UL)/65535UL;
+ dstPtr[1] = (srcPtr[1] * 255UL)/65535UL;
+ dstPtr[0] = (srcPtr[2] * 255UL)/65535UL;
+
+ srcPtr += 4;
+ dstPtr += 3;
+ }
+
+ jpeg_write_scanlines(&cinfo, &line, 1);
+ }
+ }
+
+ delete [] line;
+
+ // -------------------------------------------------------------------
+
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+ fclose(file);
+
+ imageSetAttribute("savedformat", "JPEG");
+
+ saveMetadata(filePath);
+
+ return true;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/jpegloader.h b/src/libs/dimg/loaders/jpegloader.h
new file mode 100644
index 00000000..b5d64f18
--- /dev/null
+++ b/src/libs/dimg/loaders/jpegloader.h
@@ -0,0 +1,80 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : A JPEG IO file for DImg framework
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>, Gilles Caulier
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef JPEGLOADER_H
+#define JPEGLOADER_H
+
+// C ansi includes.
+
+extern "C"
+{
+#include <setjmp.h>
+#include <jpeglib.h>
+}
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Local includes.
+
+#include "dimgloader.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+class DImg;
+
+class DIGIKAM_EXPORT JPEGLoader : public DImgLoader
+{
+
+public:
+
+ JPEGLoader(DImg* image);
+
+ bool load(const TQString& filePath, DImgLoaderObserver *observer);
+ bool save(const TQString& filePath, DImgLoaderObserver *observer);
+
+ virtual bool hasAlpha() const { return false; }
+ virtual bool sixteenBit() const { return false; }
+ virtual bool isReadOnly() const { return false; };
+
+private:
+
+ // To manage Errors/Warnings handling provide by libjpeg
+
+ struct dimg_jpeg_error_mgr : public jpeg_error_mgr
+ {
+ jmp_buf setjmp_buffer;
+ };
+
+ static void dimg_jpeg_error_exit(j_common_ptr cinfo);
+ static void dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level);
+ static void dimg_jpeg_output_message(j_common_ptr cinfo);
+
+};
+
+} // NameSpace Digikam
+
+#endif /* JPEGLOADER_H */
diff --git a/src/libs/dimg/loaders/jpegsettings.cpp b/src/libs/dimg/loaders/jpegsettings.cpp
new file mode 100644
index 00000000..62bd6365
--- /dev/null
+++ b/src/libs/dimg/loaders/jpegsettings.cpp
@@ -0,0 +1,154 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save JPEG image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+#include <tqcombobox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <knuminput.h>
+#include <kactivelabel.h>
+
+// Local includes.
+
+#include "jpegsettings.h"
+#include "jpegsettings.moc"
+
+namespace Digikam
+{
+
+class JPEGSettingsPriv
+{
+
+public:
+
+ JPEGSettingsPriv()
+ {
+ JPEGGrid = 0;
+ labelJPEGcompression = 0;
+ JPEGcompression = 0;
+ labelWarning = 0;
+ labelSubSampling = 0;
+ subSamplingCB = 0;
+ }
+
+ TQGridLayout *JPEGGrid;
+
+ TQLabel *labelJPEGcompression;
+ TQLabel *labelSubSampling;
+
+ TQComboBox *subSamplingCB;
+
+ KActiveLabel *labelWarning;
+
+ KIntNumInput *JPEGcompression;
+};
+
+JPEGSettings::JPEGSettings(TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new JPEGSettingsPriv;
+
+ d->JPEGGrid = new TQGridLayout(this, 1, 2, KDialog::spacingHint());
+ d->JPEGcompression = new KIntNumInput(75, this);
+ d->JPEGcompression->setRange(1, 100, 1, true );
+ d->labelJPEGcompression = new TQLabel(i18n("JPEG quality:"), this);
+
+ TQWhatsThis::add(d->JPEGcompression, i18n("<p>The JPEG image quality:<p>"
+ "<b>1</b>: low quality (high compression and small "
+ "file size)<p>"
+ "<b>50</b>: medium quality<p>"
+ "<b>75</b>: good quality (default)<p>"
+ "<b>100</b>: high quality (no compression and "
+ "large file size)<p>"
+ "<b>Note: JPEG always uses lossy compression.</b>"));
+
+ d->labelWarning = new KActiveLabel(i18n("<qt><font size=-1 color=\"red\"><i>"
+ "Warning: <a href='http://en.wikipedia.org/wiki/JPEG'>JPEG</a> is a<br>"
+ "lossy compression<br>"
+ "image format!</p>"
+ "</i></qt>"), this);
+
+ d->labelWarning->setFrameStyle(TQFrame::Box | TQFrame::Plain);
+ d->labelWarning->setLineWidth(1);
+ d->labelWarning->setFrameShape(TQFrame::Box);
+
+ d->labelSubSampling = new TQLabel(i18n("Chroma subsampling:"), this);
+
+ d->subSamplingCB = new TQComboBox(false, this);
+ d->subSamplingCB->insertItem(i18n("None")); // 1x1, 1x1, 1x1 (4:4:4)
+ d->subSamplingCB->insertItem(i18n("Medium")); // 2x1, 1x1, 1x1 (4:2:2)
+ d->subSamplingCB->insertItem(i18n("High")); // 2x2, 1x1, 1x1 (4:1:1)
+ TQWhatsThis::add(d->subSamplingCB, i18n("<p>JPEG Chroma subsampling level \n(color is saved with less resolution " "than luminance):<p>"
+ "<b>None</b>=best: uses 4:4:4 ratio. Does not employ chroma "
+ "subsampling at all. This preserves edges and contrasting "
+ "colors, whilst adding no additional compression<p>"
+ "<b>Medium</b>: uses 4:2:2 ratio. Medium compression: reduces "
+ "the color resolution by one-third with little to "
+ "no visual difference<p>"
+ "<b>High</b>: use 4:1:1 ratio. High compression: suits "
+ "images with soft edges but tends to alter colors<p>"
+ "<b>Note: JPEG always uses lossy compression.</b>"));
+
+ d->JPEGGrid->addMultiCellWidget(d->labelJPEGcompression, 0, 0, 0, 0);
+ d->JPEGGrid->addMultiCellWidget(d->JPEGcompression, 0, 0, 1, 1);
+ d->JPEGGrid->addMultiCellWidget(d->labelSubSampling, 1, 1, 0, 0);
+ d->JPEGGrid->addMultiCellWidget(d->subSamplingCB, 1, 1, 1, 1);
+ d->JPEGGrid->addMultiCellWidget(d->labelWarning, 0, 1, 2, 2);
+ d->JPEGGrid->setColStretch(1, 10);
+ d->JPEGGrid->setRowStretch(2, 10);
+}
+
+JPEGSettings::~JPEGSettings()
+{
+ delete d;
+}
+
+void JPEGSettings::setCompressionValue(int val)
+{
+ d->JPEGcompression->setValue(val);
+}
+
+int JPEGSettings::getCompressionValue()
+{
+ return d->JPEGcompression->value();
+}
+
+void JPEGSettings::setSubSamplingValue(int val)
+{
+ d->subSamplingCB->setCurrentItem(val);
+}
+
+int JPEGSettings::getSubSamplingValue()
+{
+ return d->subSamplingCB->currentItem();
+}
+
+} // namespace Digikam
diff --git a/src/libs/dimg/loaders/jpegsettings.h b/src/libs/dimg/loaders/jpegsettings.h
new file mode 100644
index 00000000..70e20d42
--- /dev/null
+++ b/src/libs/dimg/loaders/jpegsettings.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save JPEG image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef JPEGSETTINGS_H
+#define JPEGSETTINGS_H
+
+// KDE includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class JPEGSettingsPriv;
+
+class DIGIKAM_EXPORT JPEGSettings : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ JPEGSettings(TQWidget *parent=0);
+ ~JPEGSettings();
+
+ void setCompressionValue(int val);
+ int getCompressionValue();
+
+ void setSubSamplingValue(int val);
+ int getSubSamplingValue();
+
+private:
+
+ JPEGSettingsPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* JPEGSETTINGS_H */
diff --git a/src/libs/dimg/loaders/pngloader.cpp b/src/libs/dimg/loaders/pngloader.cpp
new file mode 100644
index 00000000..402cf944
--- /dev/null
+++ b/src/libs/dimg/loaders/pngloader.cpp
@@ -0,0 +1,993 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-01
+ * Description : a PNG image loader for DImg framework.
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// This line must be commented to prevent any latency time
+// when we use threaded image loader interface for each image
+// files io. Uncomment this line only for debugging.
+//#define ENABLE_DEBUG_MESSAGES
+
+#define PNG_BYTES_TO_CHECK 4
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+#include <stdarg.h>
+}
+
+// C++ includes.
+
+#include <cstdlib>
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqcstring.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "pngloader.h"
+
+namespace Digikam
+{
+
+#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 )
+ typedef png_bytep iCCP_data;
+#else
+ typedef png_charp iCCP_data;
+#endif
+
+PNGLoader::PNGLoader(DImg* image)
+ : DImgLoader(image)
+{
+ m_hasAlpha = false;
+ m_sixteenBit = false;
+}
+
+bool PNGLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ png_uint_32 w32, h32;
+ int width, height;
+ FILE *f;
+ int bit_depth, color_type, interlace_type;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+
+ readMetadata(filePath, DImg::PNG);
+
+ // -------------------------------------------------------------------
+ // Open the file
+
+ f = fopen(TQFile::encodeName(filePath), "rb");
+ if ( !f )
+ {
+ DDebug() << k_funcinfo << "Cannot open image file." << endl;
+ return false;
+ }
+
+ unsigned char buf[PNG_BYTES_TO_CHECK];
+
+ fread(buf, 1, PNG_BYTES_TO_CHECK, f);
+ if (png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK))
+ {
+ DDebug() << k_funcinfo << "Not a PNG image file." << endl;
+ fclose(f);
+ return false;
+ }
+ rewind(f);
+
+ // -------------------------------------------------------------------
+ // Initialize the internal structures
+
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr)
+ {
+ DDebug() << k_funcinfo << "Invalid PNG image file structure." << endl;
+ fclose(f);
+ return false;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ {
+ DDebug() << k_funcinfo << "Cannot reading PNG image file structure." << endl;
+ png_destroy_read_struct(&png_ptr, NULL, NULL);
+ fclose(f);
+ return false;
+ }
+
+ // -------------------------------------------------------------------
+ // PNG error handling. If an error occurs during reading, libpng
+ // will jump here
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ {
+ DDebug() << k_funcinfo << "Internal libPNG error during reading file. Process aborted!" << endl;
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ fclose(f);
+ return false;
+ }
+
+ png_init_io(png_ptr, f);
+
+ // -------------------------------------------------------------------
+ // Read all PNG info up to image data
+
+ png_read_info(png_ptr, info_ptr);
+
+ png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) (&w32),
+ (png_uint_32 *) (&h32), &bit_depth, &color_type,
+ &interlace_type, NULL, NULL);
+
+ width = (int)w32;
+ height = (int)h32;
+
+ // TODO: Endianness:
+ // You may notice that the code for little and big endian
+ // below is now identical. This was found to work by PPC users.
+ // If this proves right, all the conditional clauses can be removed.
+
+ if (bit_depth == 16)
+ {
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in 16 bits/color/pixel." << endl;
+#endif
+ m_sixteenBit = true;
+
+ switch (color_type)
+ {
+ case PNG_COLOR_TYPE_RGB : // RGB
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_RGB" << endl;
+#endif
+ m_hasAlpha = false;
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER);
+ else // PPC
+ png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER);
+
+ break;
+
+ case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_RGB_ALPHA" << endl;
+#endif
+ m_hasAlpha = true;
+ break;
+
+ case PNG_COLOR_TYPE_GRAY : // Grayscale
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_GRAY" << endl;
+#endif
+ png_set_gray_to_rgb(png_ptr);
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER);
+ else // PPC
+ png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER);
+
+ m_hasAlpha = false;
+ break;
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + Alpha
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA" << endl;
+#endif
+ png_set_gray_to_rgb(png_ptr);
+ m_hasAlpha = true;
+ break;
+
+ case PNG_COLOR_TYPE_PALETTE : // Indexed
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_PALETTE" << endl;
+#endif
+ png_set_palette_to_rgb(png_ptr);
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER);
+ else // PPC
+ png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER);
+
+ m_hasAlpha = false;
+ break;
+
+ default:
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << "PNG color type unknown." << endl;
+#endif
+ return false;
+ }
+ }
+ else
+ {
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << "PNG in >=8 bits/color/pixel." << endl;
+#endif
+ m_sixteenBit = false;
+ png_set_packing(png_ptr);
+
+ switch (color_type)
+ {
+ case PNG_COLOR_TYPE_RGB : // RGB
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_RGB" << endl;
+#endif
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+ else // PPC
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+
+ m_hasAlpha = false;
+ break;
+
+ case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_RGB_ALPHA" << endl;
+#endif
+ m_hasAlpha = true;
+ break;
+
+ case PNG_COLOR_TYPE_GRAY : // Grayscale
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_GRAY" << endl;
+#endif
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+ png_set_gray_to_rgb(png_ptr);
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+ else // PPC
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+
+ m_hasAlpha = false;
+ break;
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + alpha
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA" << endl;
+#endif
+ png_set_gray_to_rgb(png_ptr);
+ m_hasAlpha = true;
+ break;
+
+ case PNG_COLOR_TYPE_PALETTE : // Indexed
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "PNG in PNG_COLOR_TYPE_PALETTE" << endl;
+#endif
+ png_set_packing(png_ptr);
+ png_set_palette_to_rgb(png_ptr);
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+ else // PPC
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+
+ m_hasAlpha = true;
+ break;
+
+ default:
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << "PNG color type unknown." << endl;
+#endif
+ return false;
+ }
+ }
+
+ if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
+ png_set_tRNS_to_alpha(png_ptr);
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_bgr(png_ptr);
+ else // PPC
+ png_set_bgr(png_ptr);
+ //png_set_swap_alpha(png_ptr);
+
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ // -------------------------------------------------------------------
+ // Get image data.
+
+ // Call before png_read_update_info and png_start_read_image()
+ // For non-interlaced images number_passes will be 1
+ int number_passes = png_set_interlace_handling(png_ptr);
+
+ png_read_update_info(png_ptr, info_ptr);
+
+ uchar *data = 0;
+
+ if (m_sixteenBit)
+ data = new uchar[width*height*8]; // 16 bits/color/pixel
+ else
+ data = new uchar[width*height*4]; // 8 bits/color/pixel
+
+ uchar **lines = 0;
+ lines = (uchar **)malloc(height * sizeof(uchar *));
+ if (!lines)
+ {
+ DDebug() << k_funcinfo << "Cannot allocate memory to load PNG image data." << endl;
+ png_read_end(png_ptr, info_ptr);
+ png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
+ fclose(f);
+ delete [] data;
+ return false;
+ }
+
+ for (int i = 0; i < height; i++)
+ {
+ if (m_sixteenBit)
+ lines[i] = data + (i * width * 8);
+ else
+ lines[i] = data + (i * width * 4);
+ }
+
+ // The easy way to read the whole image
+ // png_read_image(png_ptr, lines);
+ // The other way to read images is row by row. Necessary for observer.
+ // Now we need to deal with interlacing.
+
+ for (int pass = 0; pass < number_passes; pass++)
+ {
+ int y;
+ int checkPoint = 0;
+ for (y = 0; y < height; y++)
+ {
+ if (observer && y == checkPoint)
+ {
+ checkPoint += granularity(observer, height, 0.7);
+ if (!observer->continueQuery(m_image))
+ {
+ png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
+ fclose(f);
+ delete [] data;
+ free(lines);
+ return false;
+ }
+ // use 10% - 80% for progress while reading rows
+ observer->progressInfo(m_image, 0.1 + (0.7 * ( ((float)y)/((float)height) )) );
+ }
+
+ png_read_rows(png_ptr, lines+y, NULL, 1);
+ }
+ }
+
+ free(lines);
+
+ // Swap bytes in 16 bits/color/pixel for DImg
+
+ if (m_sixteenBit)
+ {
+ uchar ptr[8]; // One pixel to swap
+
+ for (int p = 0; p < width*height*8; p+=8)
+ {
+ memcpy (&ptr[0], &data[p], 8); // Current pixel
+
+ data[ p ] = ptr[1]; // Blue
+ data[p+1] = ptr[0];
+ data[p+2] = ptr[3]; // Green
+ data[p+3] = ptr[2];
+ data[p+4] = ptr[5]; // Red
+ data[p+5] = ptr[4];
+ data[p+6] = ptr[7]; // Alpha
+ data[p+7] = ptr[6];
+ }
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 0.9);
+
+ // -------------------------------------------------------------------
+ // Read image ICC profile
+
+ TQMap<int, TQByteArray>& metaData = imageMetaData();
+
+#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 )
+ png_charp profile_name;
+ iCCP_data profile_data=NULL;
+#else
+ png_charp profile_name, profile_data=NULL;
+#endif
+ png_uint_32 profile_size;
+ int compression_type;
+
+ png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_size);
+
+ if (profile_data != NULL)
+ {
+ TQByteArray profile_rawdata(profile_size);
+ memcpy(profile_rawdata.data(), profile_data, profile_size);
+ metaData.insert(DImg::ICC, profile_rawdata);
+ }
+ else
+ {
+ // If ICC profile is null, check Exif metadata.
+ checkExifWorkingColorSpace();
+ }
+
+ // -------------------------------------------------------------------
+ // Get embbeded text data.
+
+ png_text* text_ptr;
+ int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, NULL);
+
+ /*
+ Standard Embedded text includes in PNG :
+
+ Title Short (one line) title or caption for image
+ Author Name of image's creator
+ Description Description of image (possibly long)
+ Copyright Copyright notice
+ Creation Time Time of original image creation
+ Software Software used to create the image
+ Disclaimer Legal disclaimer
+ Warning Warning of nature of content
+ Source Device used to create the image
+ Comment Miscellaneous comment; conversion from GIF comment
+
+ Extra Raw profiles tag are used by ImageMAgick and defines at this URL :
+ http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-5.87/html/TagNames/PNG.html#TextualData
+ */
+
+ for (int i = 0; i < num_comments; i++)
+ {
+ // Check if we have a Raw profile embedded using ImageMagick technic.
+
+ if (memcmp(text_ptr[i].key, "Raw profile type exif", 21) != 0 ||
+ memcmp(text_ptr[i].key, "Raw profile type APP1", 21) != 0 ||
+ memcmp(text_ptr[i].key, "Raw profile type iptc", 21) != 0)
+ {
+ imageSetEmbbededText(text_ptr[i].key, text_ptr[i].text);
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "Reading PNG Embedded text: key=" << text_ptr[i].key
+ << " text=" << text_ptr[i].text << endl;
+#endif
+ }
+ }
+
+ // -------------------------------------------------------------------
+
+ png_read_end(png_ptr, info_ptr);
+ png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
+ fclose(f);
+
+ if (observer)
+ observer->progressInfo(m_image, 1.0);
+
+ imageWidth() = width;
+ imageHeight() = height;
+ imageData() = data;
+ imageSetAttribute("format", "PNG");
+
+ return true;
+}
+
+bool PNGLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ FILE *f;
+ png_structp png_ptr;
+ png_infop info_ptr;
+ uchar *ptr, *data = 0;
+ uint x, y, j;
+ png_bytep row_ptr;
+ png_color_8 sig_bit;
+ int quality = 75;
+ int compression = 3;
+
+ // -------------------------------------------------------------------
+ // Open the file
+
+ f = fopen(TQFile::encodeName(filePath), "wb");
+ if ( !f )
+ {
+ DDebug() << k_funcinfo << "Cannot open target image file." << endl;
+ return false;
+ }
+
+
+ // -------------------------------------------------------------------
+ // Initialize the internal structures
+
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr)
+ {
+ DDebug() << k_funcinfo << "Invalid target PNG image file structure." << endl;
+ fclose(f);
+ return false;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL)
+ {
+ DDebug() << k_funcinfo << "Cannot create PNG image file structure." << endl;
+ png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
+ fclose(f);
+ return false;
+ }
+
+ // -------------------------------------------------------------------
+ // PNG error handling. If an error occurs during writing, libpng
+ // will jump here
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ {
+ DDebug() << k_funcinfo << "Internal libPNG error during writing file. Process aborted!" << endl;
+ fclose(f);
+ png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
+ png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
+ return false;
+ }
+
+ png_init_io(png_ptr, f);
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel
+ png_set_bgr(png_ptr);
+ else // PPC
+ png_set_swap_alpha(png_ptr);
+
+ if (imageHasAlpha())
+ {
+ png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(),
+ PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ if (imageSixteenBit())
+ data = new uchar[imageWidth() * 8 * sizeof(uchar)];
+ else
+ data = new uchar[imageWidth() * 4 * sizeof(uchar)];
+ }
+ else
+ {
+ png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(),
+ PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ if (imageSixteenBit())
+ data = new uchar[imageWidth() * 6 * sizeof(uchar)];
+ else
+ data = new uchar[imageWidth() * 3 * sizeof(uchar)];
+ }
+
+ sig_bit.red = imageBitsDepth();
+ sig_bit.green = imageBitsDepth();
+ sig_bit.blue = imageBitsDepth();
+ sig_bit.alpha = imageBitsDepth();
+ png_set_sBIT(png_ptr, info_ptr, &sig_bit);
+
+ // -------------------------------------------------------------------
+ // Quality to convert to compression
+
+ TQVariant qualityAttr = imageGetAttribute("quality");
+ quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
+
+ if (quality < 1)
+ quality = 1;
+ if (quality > 99)
+ quality = 99;
+
+ quality = quality / 10;
+ compression = 9 - quality;
+
+ if (compression < 0)
+ compression = 0;
+ if (compression > 9)
+ compression = 9;
+
+ png_set_compression_level(png_ptr, compression);
+
+ // -------------------------------------------------------------------
+ // Write ICC profil.
+
+ TQByteArray profile_rawdata = m_image->getICCProfil();
+
+ if (!profile_rawdata.isEmpty())
+ {
+#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 )
+ png_set_iCCP(png_ptr, info_ptr, (png_charp)("icc"), PNG_COMPRESSION_TYPE_BASE, reinterpret_cast<iCCP_data>(profile_rawdata.data()), profile_rawdata.size());
+#else
+ png_set_iCCP(png_ptr, info_ptr, (png_charp)"icc", PNG_COMPRESSION_TYPE_BASE, profile_rawdata.data(), profile_rawdata.size());
+#endif
+ }
+
+ // -------------------------------------------------------------------
+ // Write embbeded Text
+
+ typedef TQMap<TQString, TQString> EmbeddedTextMap;
+ EmbeddedTextMap map = imageEmbeddedText();
+
+ for (EmbeddedTextMap::iterator it = map.begin(); it != map.end(); ++it)
+ {
+ if (it.key() != TQString("Software") && it.key() != TQString("Comment"))
+ {
+ png_text text;
+ text.key = (char*)it.key().ascii();
+ text.text = (char*)it.data().ascii();
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text << endl;
+#endif
+ text.compression = PNG_TEXT_COMPRESSION_zTXt;
+ png_set_text(png_ptr, info_ptr, &(text), 1);
+ }
+ }
+
+ // Update 'Software' text tag.
+ TQString software("digiKam ");
+ software.append(digikam_version);
+ TQString libpngver(PNG_HEADER_VERSION_STRING);
+ libpngver.replace('\n', ' ');
+ software.append(TQString(" (%1)").arg(libpngver));
+ png_text text;
+ text.key = (png_charp)("Software");
+ text.text = (char *)software.ascii();
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text << endl;
+#endif
+ text.compression = PNG_TEXT_COMPRESSION_zTXt;
+ png_set_text(png_ptr, info_ptr, &(text), 1);
+
+ // Write embedded Raw profiles metadata (Exif/Iptc) in text tag using ImageMagick technic.
+ // Write digiKam comment like an iTXt chunk using UTF8 encoding.
+ // NOTE: iTXt will be enable by default with libpng >= 1.3.0.
+
+ typedef TQMap<int, TQByteArray> MetaDataMap;
+ MetaDataMap metaDataMap = imageMetaData();
+
+ for (MetaDataMap::iterator it = metaDataMap.begin(); it != metaDataMap.end(); ++it)
+ {
+ TQByteArray ba = it.data();
+
+ switch (it.key())
+ {
+
+#ifdef PNG_iTXt_SUPPORTED
+
+ // TODO : this code is not yet tested. It require libpng 1.3.0.
+
+ case(DImg::COM):
+ {
+ png_text comment;
+ comment.key = "Comment";
+ comment.text = ba.data();
+ comment.itxt_length = ba.size();
+ comment.compression = PNG_ITXT_COMPRESSION_zTXt;
+ png_set_text(png_ptr, info_ptr, &(comment), 1);
+
+ DDebug() << "Writing digiKam comment into iTXt PNG chunk : " << ba << endl;
+ break;
+ }
+#endif
+
+ case(DImg::EXIF):
+ {
+ const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
+ TQByteArray profile;
+
+ // If bytes array do not start with ImageMagick header, Exif metadata have been created from
+ // scratch using Exiv2. In this case, we need to add Exif header from start.
+ if (memcmp(ba.data(), "exif", 4) != 0 &&
+ memcmp(ba.data(), "iptc", 4) != 0 &&
+ memcmp(ba.data(), "profile", 7) != 0)
+ {
+ profile = TQByteArray(ba.size() + sizeof(ExifHeader));
+ memcpy(profile.data(), ExifHeader, sizeof(ExifHeader));
+ memcpy(profile.data()+sizeof(ExifHeader), ba.data(), ba.size());
+ }
+ else
+ {
+ profile = ba;
+ }
+
+ writeRawProfile(png_ptr, info_ptr, (png_charp)("exif"), profile.data(), (png_uint_32) profile.size());
+ break;
+ }
+ case(DImg::IPTC):
+ {
+ writeRawProfile(png_ptr, info_ptr, (png_charp)("iptc"), ba.data(), (png_uint_32) ba.size());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 0.2);
+
+ // -------------------------------------------------------------------
+ // Write image data
+
+ png_write_info(png_ptr, info_ptr);
+ png_set_shift(png_ptr, &sig_bit);
+ png_set_packing(png_ptr);
+ ptr = imageData();
+
+ uint checkPoint = 0;
+ for (y = 0; y < imageHeight(); y++)
+ {
+
+ if (observer && y == checkPoint)
+ {
+ checkPoint += granularity(observer, imageHeight(), 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ fclose(f);
+ png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
+ png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
+ return false;
+ }
+ observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)y)/((float)imageHeight()) )));
+ }
+
+ j = 0;
+
+ for (x = 0; x < imageWidth()*imageBytesDepth(); x+=imageBytesDepth())
+ {
+ if (imageSixteenBit())
+ {
+ if (imageHasAlpha())
+ {
+ data[j++] = ptr[x+1]; // Blue
+ data[j++] = ptr[ x ];
+ data[j++] = ptr[x+3]; // Green
+ data[j++] = ptr[x+2];
+ data[j++] = ptr[x+5]; // Red
+ data[j++] = ptr[x+4];
+ data[j++] = ptr[x+7]; // Alpha
+ data[j++] = ptr[x+6];
+ }
+ else
+ {
+ data[j++] = ptr[x+1]; // Blue
+ data[j++] = ptr[ x ];
+ data[j++] = ptr[x+3]; // Green
+ data[j++] = ptr[x+2];
+ data[j++] = ptr[x+5]; // Red
+ data[j++] = ptr[x+4];
+ }
+ }
+ else
+ {
+ if (imageHasAlpha())
+ {
+ data[j++] = ptr[ x ]; // Blue
+ data[j++] = ptr[x+1]; // Green
+ data[j++] = ptr[x+2]; // Red
+ data[j++] = ptr[x+3]; // Alpha
+ }
+ else
+ {
+ data[j++] = ptr[ x ]; // Blue
+ data[j++] = ptr[x+1]; // Green
+ data[j++] = ptr[x+2]; // Red
+ }
+ }
+ }
+
+ row_ptr = (png_bytep) data;
+
+ png_write_rows(png_ptr, &row_ptr, 1);
+ ptr += (imageWidth() * imageBytesDepth());
+ }
+
+ delete [] data;
+
+ // -------------------------------------------------------------------
+
+ png_write_end(png_ptr, info_ptr);
+ png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
+ png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
+
+ fclose(f);
+
+ imageSetAttribute("savedformat", "PNG");
+
+ saveMetadata(filePath);
+
+ return true;
+}
+
+bool PNGLoader::hasAlpha() const
+{
+ return m_hasAlpha;
+}
+
+bool PNGLoader::sixteenBit() const
+{
+ return m_sixteenBit;
+}
+
+void PNGLoader::writeRawProfile(png_struct *ping, png_info *ping_info, char *profile_type,
+ char *profile_data, png_uint_32 length)
+{
+ png_textp text;
+
+ long i;
+
+ uchar *sp;
+
+ png_charp dp;
+
+ png_uint_32 allocated_length, description_length;
+
+ const uchar hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
+
+ DDebug() << "Writing Raw profile: type=" << profile_type << ", length=" << length << endl;
+
+ text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text));
+ description_length = strlen((const char *) profile_type);
+ allocated_length = (png_uint_32) (length*2 + (length >> 5) + 20 + description_length);
+
+ text[0].text = (png_charp) png_malloc(ping, allocated_length);
+ text[0].key = (png_charp) png_malloc(ping, (png_uint_32) 80);
+ text[0].key[0] = '\0';
+
+ concatenateString(text[0].key, "Raw profile type ", 4096);
+ concatenateString(text[0].key, (const char *) profile_type, 62);
+
+ sp = (uchar*)profile_data;
+ dp = text[0].text;
+ *dp++='\n';
+
+ copyString(dp, (const char *) profile_type, allocated_length);
+
+ dp += description_length;
+ *dp++='\n';
+
+ formatString(dp, allocated_length-strlen(text[0].text), "%8lu ", length);
+
+ dp += 8;
+
+ for (i=0; i < (long) length; i++)
+ {
+ if (i%36 == 0)
+ *dp++='\n';
+
+ *(dp++)=(char) hex[((*sp >> 4) & 0x0f)];
+ *(dp++)=(char) hex[((*sp++ ) & 0x0f)];
+ }
+
+ *dp++='\n';
+ *dp='\0';
+ text[0].text_length = (png_size_t) (dp-text[0].text);
+ text[0].compression = -1;
+
+ if (text[0].text_length <= allocated_length)
+ png_set_text(ping, ping_info,text, 1);
+
+ png_free(ping, text[0].text);
+ png_free(ping, text[0].key);
+ png_free(ping, text);
+}
+
+size_t PNGLoader::concatenateString(char *destination, const char *source, const size_t length)
+{
+ char *q;
+
+ const char *p;
+
+ size_t i;
+
+ size_t count;
+
+ if ( !destination || !source || length == 0 )
+ return 0;
+
+ p = source;
+ q = destination;
+ i = length;
+
+ while ((i-- != 0) && (*q != '\0'))
+ q++;
+
+ count = (size_t) (q-destination);
+ i = length-count;
+
+ if (i == 0)
+ return(count+strlen(p));
+
+ while (*p != '\0')
+ {
+ if (i != 1)
+ {
+ *q++=(*p);
+ i--;
+ }
+ p++;
+ }
+
+ *q='\0';
+
+ return(count+(p-source));
+}
+
+size_t PNGLoader::copyString(char *destination, const char *source, const size_t length)
+{
+ char *q;
+
+ const char *p;
+
+ size_t i;
+
+ if ( !destination || !source || length == 0 )
+ return 0;
+
+ p = source;
+ q = destination;
+ i = length;
+
+ if ((i != 0) && (--i != 0))
+ {
+ do
+ {
+ if ((*q++=(*p++)) == '\0')
+ break;
+ }
+ while (--i != 0);
+ }
+
+ if (i == 0)
+ {
+ if (length != 0)
+ *q='\0';
+
+ do
+ {
+ }
+ while (*p++ != '\0');
+ }
+
+ return((size_t) (p-source-1));
+}
+
+long PNGLoader::formatString(char *string, const size_t length, const char *format,...)
+{
+ long n;
+
+ va_list operands;
+
+ va_start(operands,format);
+ n = (long) formatStringList(string, length, format, operands);
+ va_end(operands);
+ return(n);
+}
+
+long PNGLoader::formatStringList(char *string, const size_t length, const char *format, va_list operands)
+{
+ int n = vsnprintf(string, length, format, operands);
+
+ if (n < 0)
+ string[length-1] = '\0';
+
+ return((long) n);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/pngloader.h b/src/libs/dimg/loaders/pngloader.h
new file mode 100644
index 00000000..202a278c
--- /dev/null
+++ b/src/libs/dimg/loaders/pngloader.h
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-01
+ * Description : a PNG image loader for DImg framework.
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PNGLOADER_H
+#define PNGLOADER_H
+
+extern "C"
+{
+#include <stdarg.h>
+#include <png.h>
+}
+
+// Local includes.
+
+#include "dimgloader.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+class DImg;
+
+class DIGIKAM_EXPORT PNGLoader : public DImgLoader
+{
+public:
+
+ PNGLoader(DImg* image);
+
+ bool load(const TQString& filePath, DImgLoaderObserver *observer);
+ bool save(const TQString& filePath, DImgLoaderObserver *observer);
+
+ virtual bool hasAlpha() const;
+ virtual bool sixteenBit() const;
+ virtual bool isReadOnly() const { return false; };
+
+private:
+
+ void writeRawProfile(png_struct *ping, png_info *ping_info, char *profile_type,
+ char *profile_data, png_uint_32 length);
+
+ size_t concatenateString(char *destination, const char *source, const size_t length);
+ size_t copyString(char *destination, const char *source, const size_t length);
+ long formatString(char *string, const size_t length, const char *format,...);
+ long formatStringList(char *string, const size_t length, const char *format, va_list operands);
+
+private:
+
+ bool m_sixteenBit;
+ bool m_hasAlpha;
+};
+
+} // NameSpace Digikam
+
+#endif /* PNGLOADER_H */
diff --git a/src/libs/dimg/loaders/pngsettings.cpp b/src/libs/dimg/loaders/pngsettings.cpp
new file mode 100644
index 00000000..ce39219b
--- /dev/null
+++ b/src/libs/dimg/loaders/pngsettings.cpp
@@ -0,0 +1,102 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save PNG image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <knuminput.h>
+
+// Local includes.
+
+#include "pngsettings.h"
+#include "pngsettings.moc"
+
+namespace Digikam
+{
+
+class PNGSettingsPriv
+{
+
+public:
+
+ PNGSettingsPriv()
+ {
+ PNGGrid = 0;
+ labelPNGcompression = 0;
+ PNGcompression = 0;
+ }
+
+ TQGridLayout *PNGGrid;
+
+ TQLabel *labelPNGcompression;
+
+ KIntNumInput *PNGcompression;
+};
+
+PNGSettings::PNGSettings(TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new PNGSettingsPriv;
+
+ d->PNGGrid = new TQGridLayout(this, 1, 1, KDialog::spacingHint());
+ d->PNGcompression = new KIntNumInput(9, this);
+ d->PNGcompression->setRange(1, 9, 1, true );
+ d->labelPNGcompression = new TQLabel(i18n("PNG compression:"), this);
+
+ TQWhatsThis::add(d->PNGcompression, i18n("<p>The compression value for PNG images:<p>"
+ "<b>1</b>: low compression (large file size but "
+ "short compression duration - default)<p>"
+ "<b>5</b>: medium compression<p>"
+ "<b>9</b>: high compression (small file size but "
+ "long compression duration)<p>"
+ "<b>Note: PNG is always a lossless image "
+ "compression format.</b>"));
+ d->PNGGrid->addMultiCellWidget(d->labelPNGcompression, 0, 0, 0, 0);
+ d->PNGGrid->addMultiCellWidget(d->PNGcompression, 0, 0, 1, 1);
+ d->PNGGrid->setColStretch(1, 10);
+}
+
+PNGSettings::~PNGSettings()
+{
+ delete d;
+}
+
+void PNGSettings::setCompressionValue(int val)
+{
+ d->PNGcompression->setValue(val);
+}
+
+int PNGSettings::getCompressionValue()
+{
+ return d->PNGcompression->value();
+}
+
+} // namespace Digikam
diff --git a/src/libs/dimg/loaders/pngsettings.h b/src/libs/dimg/loaders/pngsettings.h
new file mode 100644
index 00000000..aecca935
--- /dev/null
+++ b/src/libs/dimg/loaders/pngsettings.h
@@ -0,0 +1,60 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save PNG image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PNGSETTINGS_H
+#define PNGSETTINGS_H
+
+// KDE includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class PNGSettingsPriv;
+
+class DIGIKAM_EXPORT PNGSettings : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ PNGSettings(TQWidget *parent=0);
+ ~PNGSettings();
+
+ void setCompressionValue(int val);
+ int getCompressionValue();
+
+private:
+
+ PNGSettingsPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* PNGSETTINGS_H */
diff --git a/src/libs/dimg/loaders/ppmloader.cpp b/src/libs/dimg/loaders/ppmloader.cpp
new file mode 100644
index 00000000..15c19423
--- /dev/null
+++ b/src/libs/dimg/loaders/ppmloader.cpp
@@ -0,0 +1,178 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-21-11
+ * Description : A 16 bits/color/pixel PPM IO file for
+ * DImg framework
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// This line must be commented to prevent any latency time
+// when we use threaded image loader interface for each image
+// files io. Uncomment this line only for debugging.
+//#define ENABLE_DEBUG_MESSAGES
+
+// C ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqimage.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "ppmloader.h"
+
+namespace Digikam
+{
+
+PPMLoader::PPMLoader(DImg* image)
+ : DImgLoader(image)
+{
+}
+
+bool PPMLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ //TODO: progress information
+ int width, height, rgbmax;
+ char nl;
+
+ FILE *file = fopen(TQFile::encodeName(filePath), "rb");
+ if (!file)
+ {
+ DDebug() << k_funcinfo << "Cannot open image file." << endl;
+ return false;
+ }
+
+ ushort header;
+
+ if (fread(&header, 2, 1, file) != 1)
+ {
+ DDebug() << k_funcinfo << "Cannot read header of file." << endl;
+ fclose(file);
+ return false;
+ }
+
+ uchar* c = (uchar*) &header;
+ if (*c != 'P')
+ {
+ DDebug() << k_funcinfo << "Not a PPM file." << endl;
+ fclose(file);
+ return false;
+ }
+
+ c++;
+ if (*c != '6')
+ {
+ DDebug() << k_funcinfo << "Not a PPM file." << endl;
+ fclose(file);
+ return false;
+ }
+
+ rewind(file);
+
+ if (fscanf (file, "P6 %d %d %d%c", &width, &height, &rgbmax, &nl) != 4)
+ {
+ DDebug() << "Corrupted PPM file." << endl;
+ pclose (file);
+ return false;
+ }
+
+ if (rgbmax <= 255)
+ {
+ DDebug() << k_funcinfo << "Not a 16 bits per color per pixel PPM file." << endl;
+ pclose (file);
+ return false;
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ unsigned short *data;
+
+ data = new unsigned short[width*height*4];
+ unsigned short *dst = data;
+ uchar src[6];
+ float fac = 65535.0 / rgbmax;
+ int checkpoint = 0;
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << "rgbmax=" << rgbmax << " fac=" << fac << endl;
+#endif
+
+ for (int h = 0; h < height; h++)
+ {
+
+ if (observer && h == checkpoint)
+ {
+ checkpoint += granularity(observer, height, 0.9);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ pclose( file );
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.9 * ( ((float)h)/((float)height) )));
+ }
+
+ for (int w = 0; w < width; w++)
+ {
+
+ fread (src, 6 *sizeof(unsigned char), 1, file);
+
+ dst[0] = (unsigned short)((src[4]*256 + src[5]) * fac); // Blue
+ dst[1] = (unsigned short)((src[2]*256 + src[3]) * fac); // Green
+ dst[2] = (unsigned short)((src[0]*256 + src[1]) * fac); // Red
+ dst[3] = 0xFFFF;
+
+ dst += 4;
+ }
+ }
+
+ fclose( file );
+
+ //----------------------------------------------------------
+
+ imageWidth() = width;
+ imageHeight() = height;
+ imageData() = (uchar*)data;
+ imageSetAttribute("format", "PPM");
+
+ return true;
+}
+
+bool PPMLoader::save(const TQString& /*filePath*/, DImgLoaderObserver */*observer*/)
+{
+ return false;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/ppmloader.h b/src/libs/dimg/loaders/ppmloader.h
new file mode 100644
index 00000000..283fdd26
--- /dev/null
+++ b/src/libs/dimg/loaders/ppmloader.h
@@ -0,0 +1,59 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-21-11
+ * Description : A 16 bits/color/pixel PPM IO file for
+ * DImg framework
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2006 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PPMLOADER_H
+#define PPMLOADER_H
+
+// Local includes.
+
+#include "dimgloader.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+class DImg;
+
+class DIGIKAM_EXPORT PPMLoader : public DImgLoader
+{
+public:
+
+ PPMLoader(DImg* image);
+
+ bool load(const TQString& filePath, DImgLoaderObserver *observer);
+ bool save(const TQString& filePath, DImgLoaderObserver *observer);
+
+ virtual bool hasAlpha() const { return false; };
+ virtual bool sixteenBit() const { return true; };
+ virtual bool isReadOnly() const { return true; };
+
+private:
+
+ bool m_alpha;
+ bool m_sixteenBit;
+};
+
+} // NameSpace Digikam
+
+#endif /* PPMLOADER_H */
diff --git a/src/libs/dimg/loaders/qimageloader.cpp b/src/libs/dimg/loaders/qimageloader.cpp
new file mode 100644
index 00000000..f35335cf
--- /dev/null
+++ b/src/libs/dimg/loaders/qimageloader.cpp
@@ -0,0 +1,126 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : A TQImage loader for DImg framework.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Caulier Gilles <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqimage.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "qimageloader.h"
+
+namespace Digikam
+{
+
+TQImageLoader::TQImageLoader(DImg* image)
+ : DImgLoader(image)
+{
+}
+
+bool TQImageLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ // Loading is opaque to us. No support for stopping from observer,
+ // progress info are only pseudo values
+ TQImage image(filePath);
+
+ if (observer)
+ observer->progressInfo(m_image, 0.9);
+
+ if (image.isNull())
+ {
+ DDebug() << "Cannot loading \"" << filePath << "\" using DImg::TQImageLoader!" << endl;
+ return false;
+ }
+
+ m_hasAlpha = image.hasAlphaBuffer();
+ TQImage target = image.convertDepth(32);
+
+ uint w = target.width();
+ uint h = target.height();
+ uchar* data = new uchar[w*h*4];
+ uint* sptr = (uint*)target.bits();
+ uchar* dptr = data;
+
+ for (uint i = 0 ; i < w*h ; i++)
+ {
+ dptr[0] = tqBlue(*sptr);
+ dptr[1] = tqGreen(*sptr);
+ dptr[2] = tqRed(*sptr);
+ dptr[3] = tqAlpha(*sptr);
+
+ dptr += 4;
+ sptr++;
+ }
+
+ if (observer)
+ observer->progressInfo(m_image, 1.0);
+
+ imageWidth() = w;
+ imageHeight() = h;
+ imageData() = data;
+
+ // We considering that PNG is the most representative format of an image loaded by TQt
+ imageSetAttribute("format", "PNG");
+
+ return true;
+}
+
+bool TQImageLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ TQVariant qualityAttr = imageGetAttribute("quality");
+ int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
+
+ if (quality < 0)
+ quality = 90;
+ if (quality > 100)
+ quality = 100;
+
+ TQVariant formatAttr = imageGetAttribute("format");
+ TQCString format = formatAttr.toCString();
+
+ TQImage image = m_image->copyTQImage();
+
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ // Saving is opaque to us. No support for stopping from observer,
+ // progress info are only pseudo values
+ bool success = image.save(filePath, format.upper(), quality);
+ if (observer && success)
+ observer->progressInfo(m_image, 1.0);
+
+ imageSetAttribute("format", format.upper());
+
+ return success;
+}
+
+bool TQImageLoader::hasAlpha() const
+{
+ return m_hasAlpha;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/qimageloader.h b/src/libs/dimg/loaders/qimageloader.h
new file mode 100644
index 00000000..f81cf7ef
--- /dev/null
+++ b/src/libs/dimg/loaders/qimageloader.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-14
+ * Description : A TQImage loader for DImg framework.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Caulier Gilles <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TQIMAGELOADER_H
+#define TQIMAGELOADER_H
+
+// Local includes.
+
+#include "dimgloader.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+class DImg;
+
+class DIGIKAM_EXPORT TQImageLoader : public DImgLoader
+{
+public:
+
+ TQImageLoader(DImg* image);
+
+ virtual bool load(const TQString& filePath, DImgLoaderObserver *observer);
+ virtual bool save(const TQString& filePath, DImgLoaderObserver *observer);
+
+ virtual bool hasAlpha() const;
+ virtual bool sixteenBit() const { return false; };
+ virtual bool isReadOnly() const { return false; };
+
+private:
+
+ bool m_hasAlpha;
+};
+
+} // NameSpace Digikam
+
+#endif /* TQIMAGELOADER_H */
diff --git a/src/libs/dimg/loaders/rawloader.cpp b/src/libs/dimg/loaders/rawloader.cpp
new file mode 100644
index 00000000..8ecaa1f3
--- /dev/null
+++ b/src/libs/dimg/loaders/rawloader.cpp
@@ -0,0 +1,371 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-01
+ * Description : A digital camera RAW files loader for DImg
+ * framework using an external dcraw instance.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2008 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqcstring.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "imagecurves.h"
+#include "imagelevels.h"
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "bcgmodifier.h"
+#include "whitebalance.h"
+#include "rawloader.h"
+#include "rawloader.moc"
+
+namespace Digikam
+{
+
+RAWLoader::RAWLoader(DImg* image, DRawDecoding rawDecodingSettings)
+ : DImgLoader(image)
+{
+ m_rawDecodingSettings = rawDecodingSettings;
+ m_customRawSettings = rawDecodingSettings;
+ m_observer = 0;
+}
+
+bool RAWLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ m_observer = observer;
+
+ // We are using TDEProcess here, and make two assumptions:
+ // - there is an event loop (not for ioslaves)
+ // - we are not called from the event loop thread
+ // These assumptions are currently true for all use cases in digikam,
+ // except the thumbnails iosalve, which will set this attribute.
+ // I hope when porting to TQt4, all the event loop stuff (and this problem) can be removed.
+ if (imageGetAttribute("noeventloop").isValid())
+ return false;
+
+ readMetadata(filePath, DImg::RAW);
+
+ // NOTE: Here, we don't check a possible embedded work-space color profile using
+ // the method checkExifWorkingColorSpace() like with JPEG, PNG, and TIFF loaders,
+ // because RAW file are always in linear mode.
+
+ int width, height, rgbmax;
+ TQByteArray data;
+ if (!KDcrawIface::KDcraw::decodeRAWImage(filePath, m_rawDecodingSettings,
+ data, width, height, rgbmax))
+ return false;
+
+ return (loadedFromDcraw(data, width, height, rgbmax, observer));
+}
+
+bool RAWLoader::checkToCancelWaitingData()
+{
+ return (m_observer ? !m_observer->continueQuery(m_image) : false);
+}
+
+void RAWLoader::setWaitingDataProgress(double value)
+{
+ if (m_observer)
+ m_observer->progressInfo(m_image, value);
+}
+
+#if KDCRAW_VERSION < 0x000106
+bool RAWLoader::checkToCancelRecievingData()
+{
+ return (m_observer ? m_observer->isShuttingDown() : false);
+}
+
+void RAWLoader::setRecievingDataProgress(double value)
+{
+ if (m_observer)
+ m_observer->progressInfo(m_image, value);
+}
+#endif
+
+bool RAWLoader::loadedFromDcraw(TQByteArray data, int width, int height, int rgbmax,
+ DImgLoaderObserver *observer)
+{
+ int checkpoint = 0;
+
+ if (m_rawDecodingSettings.sixteenBitsImage) // 16 bits image
+ {
+ uchar *image = new uchar[width*height*8];
+
+ unsigned short *dst = (unsigned short *)image;
+ uchar *src = (uchar*)data.data();
+ float fac = 65535.0 / rgbmax;
+ checkpoint = 0;
+
+ for (int h = 0; h < height; h++)
+ {
+ if (observer && h == checkpoint)
+ {
+ checkpoint += granularity(observer, height, 1.0);
+ if (!observer->continueQuery(m_image))
+ {
+ return false;
+ }
+ observer->progressInfo(m_image, 0.7 + 0.2*(((float)h)/((float)height)) );
+ }
+
+ for (int w = 0; w < width; w++)
+ {
+#if KDCRAW_VERSION < 0x000106
+ dst[0] = (unsigned short)((src[4]*256 + src[5]) * fac); // Blue
+ dst[1] = (unsigned short)((src[2]*256 + src[3]) * fac); // Green
+ dst[2] = (unsigned short)((src[0]*256 + src[1]) * fac); // Red
+#else
+ dst[0] = (unsigned short)((src[5]*256 + src[4]) * fac); // Blue
+ dst[1] = (unsigned short)((src[3]*256 + src[2]) * fac); // Green
+ dst[2] = (unsigned short)((src[1]*256 + src[0]) * fac); // Red
+#endif
+ dst[3] = 0xFFFF;
+
+ dst += 4;
+ src += 6;
+ }
+ }
+
+
+#if KDCRAW_VERSION < 0x000106
+ // ----------------------------------------------------------
+
+ // Special case : if Color Management is not used here, output color space is in sRGB* color space
+ // RAW decoded image is a linear-histogram image with 16 bits color depth.
+ // No auto white balance and no gamma adjustemnts are performed. Image is a black hole.
+ // We need to reproduce all dcraw 8 bits color depth adjustements here.
+
+ if (m_rawDecodingSettings.outputColorSpace != DRawDecoding::RAWCOLOR)
+ {
+ ImageHistogram histogram(image, width, height, true);
+
+ int perc, val, total;
+ float white=0.0, r, gamma=2.222222;
+ unsigned short lut[65536];
+
+ // Search 99th percentile white level.
+
+ perc = (int)(width * height * 0.01);
+ DDebug() << "White Level: " << perc << endl;
+ for (int c = 1 ; c < 4 ; c++)
+ {
+ total = 0;
+ for (val = 65535 ; val > 256 ; --val)
+ if ((total += (int)histogram.getValue(c, val)) > perc)
+ break;
+
+ if (white < val) white = (float)val;
+ }
+
+ white *= 1.0 / m_rawDecodingSettings.brightness;
+
+ DDebug() << "White Point: " << white << endl;
+
+ // Compute the Gamma lut accordingly.
+
+ for (int i=0; i < 65536; i++)
+ {
+ r = i / white;
+ val = (int)(65536.0 * (r <= 0.018 ? r*4.5 : pow(r, 1.0/gamma) * 1.099-0.099));
+ if (val > 65535) val = 65535;
+ lut[i] = val;
+ }
+
+ // Apply Gamma lut to the whole image.
+
+ unsigned short *im = (unsigned short *)image;
+ for (int i = 0; i < width*height; i++)
+ {
+ im[0] = lut[im[0]]; // Blue
+ im[1] = lut[im[1]]; // Green
+ im[2] = lut[im[2]]; // Red
+ im += 4;
+ }
+ }
+#endif
+
+ // ----------------------------------------------------------
+
+ imageData() = (uchar *)image;
+ }
+ else // 8 bits image
+ {
+ uchar *image = new uchar[width*height*4];
+ uchar *dst = image;
+ uchar *src = (uchar*)data.data();
+ checkpoint = 0;
+
+ for (int h = 0; h < height; h++)
+ {
+
+ if (observer && h == checkpoint)
+ {
+ checkpoint += granularity(observer, height, 1.0);
+ if (!observer->continueQuery(m_image))
+ {
+ return false;
+ }
+ observer->progressInfo(m_image, 0.7 + 0.2*(((float)h)/((float)height)) );
+ }
+
+ for (int w = 0; w < width; w++)
+ {
+ // No need to adapt RGB components accordinly with rgbmax value because dcraw
+ // always return rgbmax to 255 in 8 bits/color/pixels.
+
+ dst[0] = src[2]; // Blue
+ dst[1] = src[1]; // Green
+ dst[2] = src[0]; // Red
+ dst[3] = 0xFF; // Alpha
+
+ dst += 4;
+ src += 3;
+ }
+ }
+
+ // NOTE: if Color Management is not used here, output color space is in sRGB* color space.
+ // Gamma and White balance are previously adjusted by dcraw in 8 bits color depth.
+
+ imageData() = image;
+ }
+
+ //----------------------------------------------------------
+ // Assign the right color-space profile.
+
+ TDEGlobal::dirs()->addResourceType("profiles", TDEGlobal::dirs()->kde_default("data") + "digikam/profiles");
+ switch(m_rawDecodingSettings.outputColorSpace)
+ {
+ case DRawDecoding::SRGB:
+ {
+ TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "srgb.icm");
+ m_image->getICCProfilFromFile(directory + "srgb.icm");
+ break;
+ }
+ case DRawDecoding::ADOBERGB:
+ {
+ TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "adobergb.icm");
+ m_image->getICCProfilFromFile(directory + "adobergb.icm");
+ break;
+ }
+ case DRawDecoding::WIDEGAMMUT:
+ {
+ TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "widegamut.icm");
+ m_image->getICCProfilFromFile(directory + "widegamut.icm");
+ break;
+ }
+ case DRawDecoding::PROPHOTO:
+ {
+ TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "prophoto.icm");
+ m_image->getICCProfilFromFile(directory + "prophoto.icm");
+ break;
+ }
+ default:
+ // No icc color-space profile to assign in RAW color mode.
+ break;
+ }
+
+ //----------------------------------------------------------
+
+
+ imageWidth() = width;
+ imageHeight() = height;
+ imageSetAttribute("format", "RAW");
+
+ postProcessing(observer);
+
+ return true;
+}
+
+void RAWLoader::postProcessing(DImgLoaderObserver *observer)
+{
+ if (!m_customRawSettings.postProcessingSettingsIsDirty())
+ return;
+
+ if (m_customRawSettings.exposureComp != 0.0 || m_customRawSettings.saturation != 1.0)
+ {
+ WhiteBalance wb(m_rawDecodingSettings.sixteenBitsImage);
+ wb.whiteBalance(imageData(), imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage,
+ 0.0, // black
+ m_customRawSettings.exposureComp, // exposure
+ 6500.0, // temperature (neutral)
+ 1.0, // green
+ 0.5, // dark
+ 1.0, // gamma
+ m_customRawSettings.saturation); // saturation
+ }
+ if (observer) observer->progressInfo(m_image, 0.92);
+
+ if (m_customRawSettings.lightness != 0.0 ||
+ m_customRawSettings.contrast != 1.0 ||
+ m_customRawSettings.gamma != 1.0)
+ {
+ BCGModifier bcg;
+ bcg.setBrightness(m_customRawSettings.lightness);
+ bcg.setContrast(m_customRawSettings.contrast);
+ bcg.setGamma(m_customRawSettings.gamma);
+ bcg.applyBCG(imageData(), imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage);
+ }
+ if (observer) observer->progressInfo(m_image, 0.94);
+
+ if (!m_customRawSettings.curveAdjust.isEmpty())
+ {
+ DImg tmp(imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage);
+ ImageCurves curves(m_rawDecodingSettings.sixteenBitsImage);
+ curves.setCurvePoints(ImageHistogram::ValueChannel, m_customRawSettings.curveAdjust);
+ curves.curvesCalculateCurve(ImageHistogram::ValueChannel);
+ curves.curvesLutSetup(ImageHistogram::AlphaChannel);
+ curves.curvesLutProcess(imageData(), tmp.bits(), imageWidth(), imageHeight());
+ memcpy(imageData(), tmp.bits(), tmp.numBytes());
+ }
+ if (observer) observer->progressInfo(m_image, 0.96);
+
+ if (!m_customRawSettings.levelsAdjust.isEmpty())
+ {
+ DImg tmp(imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage);
+ ImageLevels levels(m_rawDecodingSettings.sixteenBitsImage);
+ int j=0;
+ for (int i = 0 ; i < 4; i++)
+ {
+ levels.setLevelLowInputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ levels.setLevelHighInputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ levels.setLevelLowOutputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ levels.setLevelHighOutputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ }
+
+ levels.levelsLutSetup(ImageHistogram::AlphaChannel);
+ levels.levelsLutProcess(imageData(), tmp.bits(), imageWidth(), imageHeight());
+ memcpy(imageData(), tmp.bits(), tmp.numBytes());
+ }
+ if (observer) observer->progressInfo(m_image, 0.98);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/rawloader.h b/src/libs/dimg/loaders/rawloader.h
new file mode 100644
index 00000000..22171a20
--- /dev/null
+++ b/src/libs/dimg/loaders/rawloader.h
@@ -0,0 +1,86 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-01
+ * Description : A digital camera RAW files loader for DImg
+ * framework using an external dcraw instance.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2005-2008 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAWLOADER_H
+#define RAWLOADER_H
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+// Local includes.
+
+#include "drawdecoding.h"
+#include "dimgloader.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+class DImg;
+
+class DIGIKAM_EXPORT RAWLoader : public KDcrawIface::KDcraw, public DImgLoader
+{
+ TQ_OBJECT
+
+
+public:
+
+ RAWLoader(DImg* image, DRawDecoding rawDecodingSettings=DRawDecoding());
+
+ bool load(const TQString& filePath, DImgLoaderObserver *observer=0);
+
+ // NOTE: RAW files are always Read only.
+ bool save(const TQString& /*filePath*/, DImgLoaderObserver */*observer=0*/) { return false; };
+
+ bool hasAlpha() const { return false; };
+ bool isReadOnly() const { return true; };
+ bool sixteenBit() const { return m_rawDecodingSettings.sixteenBitsImage; };
+
+private:
+
+ // Methods to load RAW image using external dcraw instance.
+
+ bool loadedFromDcraw(TQByteArray data, int width, int height, int rgbmax,
+ DImgLoaderObserver *observer);
+
+ bool checkToCancelWaitingData();
+ void setWaitingDataProgress(double value);
+ void postProcessing(DImgLoaderObserver *observer);
+
+#if KDCRAW_VERSION < 0x000106
+ bool checkToCancelRecievingData();
+ void setRecievingDataProgress(double value);
+#endif
+
+private:
+
+ DImgLoaderObserver *m_observer;
+ DRawDecoding m_customRawSettings;
+};
+
+} // NameSpace Digikam
+
+#endif /* RAWLOADER_H */
diff --git a/src/libs/dimg/loaders/tiffloader.cpp b/src/libs/dimg/loaders/tiffloader.cpp
new file mode 100644
index 00000000..2e554143
--- /dev/null
+++ b/src/libs/dimg/loaders/tiffloader.cpp
@@ -0,0 +1,806 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-17
+ * Description : A TIFF IO file for DImg framework
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Specifications & references:
+ * - TIFF 6.0 : http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
+ * - TIFF/EP : http://www.map.tu.chiba-u.ac.jp/IEC/100/TA2/recdoc/N4378.pdf
+ * - TIFF/Tags : http://www.awaresystems.be/imaging/tiff/tifftags.html
+ * - DNG : http://www.adobe.com/products/dng/pdfs/dng_spec.pdf
+ *
+ * Others Linux Tiff Loader implementation using libtiff:
+ * - http://websvn.kde.org/trunk/koffice/filters/krita/tiff/kis_tiff_converter.cc
+ * - http://artis.inrialpes.fr/Software/TiffIO/
+ * - http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/tiff.c
+ * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/FreeImage/PluginTIFF.cpp
+ * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/Metadata/XTIFF.cpp
+ * - https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/tiff.c
+ *
+ * Test images repository:
+ * - http://www.remotesensing.org/libtiff/images.html
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// This line must be commented to prevent any latency time
+// when we use threaded image loader interface for each image
+// files io. Uncomment this line only for debugging.
+//#define ENABLE_DEBUG_MESSAGES
+
+// C ANSI includes.
+
+extern "C"
+{
+#include <tiffvers.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqfile.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "dmetadata.h"
+#include "tiffloader.h"
+
+namespace Digikam
+{
+
+// To manage Errors/Warnings handling provide by libtiff
+
+void TIFFLoader::dimg_tiff_warning(const char* module, const char* format, va_list warnings)
+{
+#ifdef ENABLE_DEBUG_MESSAGES
+ char message[4096];
+ vsnprintf(message, 4096, format, warnings);
+ DDebug() << module << "::" << message << endl;
+#else
+ Q_UNUSED(module);
+ Q_UNUSED(format);
+ Q_UNUSED(warnings);
+#endif
+}
+
+void TIFFLoader::dimg_tiff_error(const char* module, const char* format, va_list errors)
+{
+#ifdef ENABLE_DEBUG_MESSAGES
+ char message[4096];
+ vsnprintf(message, 4096, format, errors);
+ DDebug() << module << "::" << message << endl;
+#else
+ Q_UNUSED(module);
+ Q_UNUSED(format);
+ Q_UNUSED(errors);
+#endif
+}
+
+TIFFLoader::TIFFLoader(DImg* image)
+ : DImgLoader(image)
+{
+ m_hasAlpha = false;
+ m_sixteenBit = false;
+}
+
+bool TIFFLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ readMetadata(filePath, DImg::TIFF);
+
+ // -------------------------------------------------------------------
+ // TIFF error handling. If an errors/warnings occurs during reading,
+ // libtiff will call these methods
+
+ TIFFSetWarningHandler(dimg_tiff_warning);
+ TIFFSetErrorHandler(dimg_tiff_error);
+
+ // -------------------------------------------------------------------
+ // Open the file
+
+ TIFF* tif = TIFFOpen(TQFile::encodeName(filePath), "r");
+ if (!tif)
+ {
+ DDebug() << k_funcinfo << "Cannot open image file." << endl;
+ return false;
+ }
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ TIFFPrintDirectory(tif, stdout, 0);
+#endif
+
+ // -------------------------------------------------------------------
+ // Get image information.
+
+ uint32 w, h;
+ uint16 bits_per_sample;
+ uint16 samples_per_pixel;
+ uint16 photometric;
+ uint32 rows_per_strip;
+ tsize_t strip_size;
+ tstrip_t num_of_strips;
+
+ TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &w);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &h);
+
+ TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
+
+ if (TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip) == 0 ||
+ rows_per_strip == 0 || rows_per_strip == (unsigned int)-1)
+ {
+ DWarning() << "TIFF loader: Cannot handle non-stripped images. Loading file " << filePath << endl;
+ TIFFClose(tif);
+ return false;
+ }
+
+ if (bits_per_sample == 0 || samples_per_pixel == 0 ||
+ rows_per_strip == 0 || rows_per_strip > h)
+ {
+ DWarning() << "TIFF loader: Encountered invalid value 0 in image."
+ << " bits_per_sample " << bits_per_sample
+ << " samples_per_pixel " << samples_per_pixel
+ << " rows_per_strip " << rows_per_strip
+ << " Loading file " << filePath << endl;
+ TIFFClose(tif);
+ return false;
+ }
+
+ // TODO: check others TIFF color-spaces here. Actually, only RGB and MINISBLACK
+ // have been tested.
+ // Complete description of TIFFTAG_PHOTOMETRIC tag can be found at this url:
+ // http://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html
+
+ TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
+ if (photometric != PHOTOMETRIC_RGB &&
+ photometric != PHOTOMETRIC_MINISBLACK)
+ {
+ DWarning() << "Can't handle image without RGB color-space: "
+ << photometric << endl;
+ TIFFClose(tif);
+ return false;
+ }
+
+ if (samples_per_pixel == 4)
+ m_hasAlpha = true;
+ else
+ m_hasAlpha = false;
+
+ if (bits_per_sample == 16)
+ m_sixteenBit = true;
+ else
+ m_sixteenBit = false;
+
+ // -------------------------------------------------------------------
+ // Read image ICC profile
+
+ TQMap<int, TQByteArray>& metaData = imageMetaData();
+
+ uchar *profile_data=0;
+ uint32 profile_size;
+
+ if (TIFFGetField (tif, TIFFTAG_ICCPROFILE, &profile_size, &profile_data))
+ {
+ TQByteArray profile_rawdata(profile_size);
+ memcpy(profile_rawdata.data(), profile_data, profile_size);
+ metaData.insert(DImg::ICC, profile_rawdata);
+ }
+ else
+ {
+ // If ICC profile is null, check Exif metadata.
+ checkExifWorkingColorSpace();
+ }
+
+ // -------------------------------------------------------------------
+ // Get image data.
+
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ uchar* data = 0;
+
+ strip_size = TIFFStripSize(tif);
+ num_of_strips = TIFFNumberOfStrips(tif);
+
+ if (bits_per_sample == 16) // 16 bits image.
+ {
+ data = new uchar[w*h*8];
+ uchar* strip = new uchar[strip_size];
+ long offset = 0;
+ long bytesRead = 0;
+
+ uint checkpoint = 0;
+
+ for (tstrip_t st=0; st < num_of_strips; st++)
+ {
+ if (observer && st == checkpoint)
+ {
+ checkpoint += granularity(observer, num_of_strips, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ delete [] strip;
+ TIFFClose(tif);
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)st)/((float)num_of_strips) )));
+ }
+
+ bytesRead = TIFFReadEncodedStrip(tif, st, strip, strip_size);
+
+ if (bytesRead == -1)
+ {
+ DDebug() << k_funcinfo << "Failed to read strip" << endl;
+ delete [] data;
+ TIFFClose(tif);
+ return false;
+ }
+
+ ushort *stripPtr = (ushort*)(strip);
+ ushort *dataPtr = (ushort*)(data + offset);
+ ushort *p;
+
+ // tiff data is read as BGR or ABGR or Greyscale
+
+ if (samples_per_pixel == 3)
+ {
+ for (int i=0; i < bytesRead/6; i++)
+ {
+ p = dataPtr;
+
+ // See B.K.O #148037 : take a care about byte order with Motorola computers.
+ if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC
+ {
+ p[3] = *stripPtr++;
+ p[0] = *stripPtr++;
+ p[1] = *stripPtr++;
+ p[2] = 0xFFFF;
+ }
+ else
+ {
+ p[2] = *stripPtr++;
+ p[1] = *stripPtr++;
+ p[0] = *stripPtr++;
+ p[3] = 0xFFFF;
+ }
+
+ dataPtr += 4;
+ }
+
+ offset += bytesRead/6 * 8;
+ }
+ else if (samples_per_pixel == 1) // See B.K.O #148400: Greyscale pictures only have _one_ sample per pixel
+ {
+ for (int i=0; i < bytesRead/2; i++)
+ {
+ // We have to read two bytes for one pixel
+ p = dataPtr;
+
+ // See B.K.O #148037 : take a care about byte order with Motorola computers.
+ if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC
+ {
+ p[3] = 0xFFFF;
+ p[0] = *stripPtr;
+ p[1] = *stripPtr;
+ p[2] = *stripPtr++;
+ }
+ else
+ {
+ p[0] = *stripPtr; // RGB have to be set to the _same_ value
+ p[1] = *stripPtr;
+ p[2] = *stripPtr++;
+ p[3] = 0xFFFF; // set alpha to 100%
+ }
+ dataPtr += 4;
+ }
+
+ offset += bytesRead*4; // The _byte_offset in the data array is, of course, four times bytesRead
+ }
+ else // ABGR
+ {
+ for (int i=0; i < bytesRead/8; i++)
+ {
+ p = dataPtr;
+
+ // See B.K.O #148037 : take a care about byte order with Motorola computers.
+ if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC
+ {
+ p[3] = *stripPtr++;
+ p[0] = *stripPtr++;
+ p[1] = *stripPtr++;
+ p[2] = *stripPtr++;
+ }
+ else
+ {
+ p[2] = *stripPtr++;
+ p[1] = *stripPtr++;
+ p[0] = *stripPtr++;
+ p[3] = *stripPtr++;
+ }
+
+ dataPtr += 4;
+ }
+
+ offset += bytesRead;
+ }
+ }
+
+ delete [] strip;
+ }
+ else // Non 16 bits images ==> get it on BGRA 8 bits.
+ {
+ data = new uchar[w*h*4];
+ uchar* strip = new uchar[w*rows_per_strip*4];
+ long offset = 0;
+ long pixelsRead = 0;
+
+ // this is inspired by TIFFReadRGBAStrip, tif_getimage.c
+ char emsg[1024] = "";
+ TIFFRGBAImage img;
+ uint32 rows_to_read;
+
+ uint checkpoint = 0;
+
+ // test whether libtiff can read format and initiate reading
+
+ if (!TIFFRGBAImageOK(tif, emsg) || !TIFFRGBAImageBegin(&img, tif, 0, emsg))
+ {
+ DDebug() << k_funcinfo << "Failed to set up RGBA reading of image, filename "
+ << TIFFFileName(tif) << " error message from Libtiff: " << emsg << endl;
+ delete [] data;
+ delete [] strip;
+ TIFFClose(tif);
+ return false;
+ }
+
+ img.req_orientation = ORIENTATION_TOPLEFT;
+
+ // read strips from image: read rows_per_strip, so always start at beginning of a strip
+ for (uint row = 0; row < h; row += rows_per_strip)
+ {
+ if (observer && row >= checkpoint)
+ {
+ checkpoint += granularity(observer, h, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ delete [] data;
+ delete [] strip;
+ TIFFClose(tif);
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)row)/((float)h) )));
+ }
+
+ img.row_offset = row;
+ img.col_offset = 0;
+
+ if( row + rows_per_strip > img.height )
+ rows_to_read = img.height - row;
+ else
+ rows_to_read = rows_per_strip;
+
+ // Read data
+
+ if (TIFFRGBAImageGet(&img, (uint32*)strip, img.width, rows_to_read ) == -1)
+ {
+ DDebug() << k_funcinfo << "Failed to read image data" << endl;
+ delete [] data;
+ delete [] strip;
+ TIFFClose(tif);
+ return false;
+ }
+
+ pixelsRead = rows_to_read * img.width;
+
+ uchar *stripPtr = (uchar*)(strip);
+ uchar *dataPtr = (uchar*)(data + offset);
+ uchar *p;
+
+ // Reverse red and blue
+
+ for (int i=0; i < pixelsRead; i++)
+ {
+ p = dataPtr;
+
+ // See B.K.O #148037 : take a care about byte order with Motorola computers.
+ if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC
+ {
+ p[3] = *stripPtr++;
+ p[0] = *stripPtr++;
+ p[1] = *stripPtr++;
+ p[2] = *stripPtr++;
+ }
+ else
+ {
+ p[2] = *stripPtr++;
+ p[1] = *stripPtr++;
+ p[0] = *stripPtr++;
+ p[3] = *stripPtr++;
+ }
+
+ dataPtr += 4;
+ }
+
+ offset += pixelsRead * 4;
+ }
+
+ TIFFRGBAImageEnd(&img);
+ delete [] strip;
+ }
+
+ // -------------------------------------------------------------------
+
+ TIFFClose(tif);
+
+ if (observer)
+ observer->progressInfo(m_image, 1.0);
+
+ imageWidth() = w;
+ imageHeight() = h;
+ imageData() = data;
+ imageSetAttribute("format", "TIFF");
+
+ return true;
+}
+
+bool TIFFLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
+{
+ TIFF *tif;
+ uchar *data;
+ uint32 w, h;
+
+ w = imageWidth();
+ h = imageHeight();
+ data = imageData();
+
+ // -------------------------------------------------------------------
+ // TIFF error handling. If an errors/warnings occurs during reading,
+ // libtiff will call these methods
+
+ TIFFSetWarningHandler(dimg_tiff_warning);
+ TIFFSetErrorHandler(dimg_tiff_error);
+
+ // -------------------------------------------------------------------
+ // Open the file
+
+ tif = TIFFOpen(TQFile::encodeName(filePath), "w");
+
+ if (!tif)
+ {
+ DDebug() << k_funcinfo << "Cannot open target image file." << endl;
+ return false;
+ }
+
+ // -------------------------------------------------------------------
+ // Set image properties
+
+ TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, w);
+ TIFFSetField(tif, TIFFTAG_IMAGELENGTH, h);
+ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+ TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+ TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
+ TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE);
+
+ // Image must be compressed using deflate algorithm ?
+ TQVariant compressAttr = imageGetAttribute("compress");
+ bool compress = compressAttr.isValid() ? compressAttr.toBool() : false;
+
+ if (compress)
+ {
+ TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE);
+ TIFFSetField(tif, TIFFTAG_ZIPQUALITY, 9);
+ // NOTE : this tag values aren't defined in libtiff 3.6.1. '2' is PREDICTOR_HORIZONTAL.
+ // Use horizontal differencing for images which are
+ // likely to be continuous tone. The TIFF spec says that this
+ // usually leads to better compression.
+ // See this url for more details:
+ // http://www.awaresystems.be/imaging/tiff/tifftags/predictor.html
+ TIFFSetField(tif, TIFFTAG_PREDICTOR, 2);
+ }
+ else
+ TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+
+ // Image has an alpha channel ?
+ if (imageHasAlpha())
+ {
+ uint16 sampleinfo[1] = { EXTRASAMPLE_UNASSALPHA };
+ TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4);
+ TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, EXTRASAMPLE_ASSOCALPHA, sampleinfo);
+ }
+ else
+ {
+ TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
+ }
+
+ TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (uint16)imageBitsDepth());
+ TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0));
+
+ // -------------------------------------------------------------------
+ // Write meta-data Tags contents.
+
+ DMetadata metaData;
+ metaData.setExif(m_image->getExif());
+ metaData.setIptc(m_image->getIptc());
+
+ // Standard IPTC tag (available with libtiff 3.6.1)
+
+ TQByteArray ba = metaData.getIptc(true);
+ if (!ba.isEmpty())
+ {
+#if defined(TIFFTAG_PHOTOSHOP)
+ TIFFSetField (tif, TIFFTAG_PHOTOSHOP, (uint32)ba.size(), (uchar *)ba.data());
+#endif
+ }
+
+ // Standard XMP tag (available with libtiff 3.6.1)
+
+#if defined(TIFFTAG_XMLPACKET)
+ tiffSetExifDataTag(tif, TIFFTAG_XMLPACKET, &metaData, "Exif.Image.XMLPacket");
+#endif
+
+ // Standard Exif Ascii tags (available with libtiff 3.6.1)
+
+ tiffSetExifAsciiTag(tif, TIFFTAG_DOCUMENTNAME, &metaData, "Exif.Image.DocumentName");
+ tiffSetExifAsciiTag(tif, TIFFTAG_IMAGEDESCRIPTION, &metaData, "Exif.Image.ImageDescription");
+ tiffSetExifAsciiTag(tif, TIFFTAG_MAKE, &metaData, "Exif.Image.Make");
+ tiffSetExifAsciiTag(tif, TIFFTAG_MODEL, &metaData, "Exif.Image.Model");
+ tiffSetExifAsciiTag(tif, TIFFTAG_DATETIME, &metaData, "Exif.Image.DateTime");
+ tiffSetExifAsciiTag(tif, TIFFTAG_ARTIST, &metaData, "Exif.Image.Artist");
+ tiffSetExifAsciiTag(tif, TIFFTAG_COPYRIGHT, &metaData, "Exif.Image.Copyright");
+
+ TQString soft = metaData.getExifTagString("Exif.Image.Software");
+ TQString libtiffver(TIFFLIB_VERSION_STR);
+ libtiffver.replace('\n', ' ');
+ soft.append(TQString(" ( %1 )").arg(libtiffver));
+ TIFFSetField(tif, TIFFTAG_SOFTWARE, (const char*)soft.ascii());
+
+ // NOTE: All others Exif tags will be written by Exiv2 (<= 0.18)
+
+ // -------------------------------------------------------------------
+ // Write ICC profil.
+
+ TQByteArray profile_rawdata = m_image->getICCProfil();
+
+ if (!profile_rawdata.isEmpty())
+ {
+#if defined(TIFFTAG_ICCPROFILE)
+ TIFFSetField(tif, TIFFTAG_ICCPROFILE, (uint32)profile_rawdata.size(), (uchar *)profile_rawdata.data());
+#endif
+ }
+
+ // -------------------------------------------------------------------
+ // Write full image data in tiff directory IFD0
+
+ if (observer)
+ observer->progressInfo(m_image, 0.1);
+
+ uint8 *buf=0;
+ uchar *pixel;
+ double alpha_factor;
+ uint32 x, y;
+ uint8 r8, g8, b8, a8=0;
+ uint16 r16, g16, b16, a16=0;
+ int i=0;
+
+ buf = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif));
+
+ if (!buf)
+ {
+ DDebug() << k_funcinfo << "Cannot allocate memory buffer for main image." << endl;
+ TIFFClose(tif);
+ return false;
+ }
+
+ uint checkpoint = 0;
+
+ for (y = 0; y < h; y++)
+ {
+
+ if (observer && y == checkpoint)
+ {
+ checkpoint += granularity(observer, h, 0.8);
+ if (!observer->continueQuery(m_image))
+ {
+ _TIFFfree(buf);
+ TIFFClose(tif);
+ return false;
+ }
+ observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)h) )));
+ }
+
+ i = 0;
+
+ for (x = 0; x < w; x++)
+ {
+ pixel = &data[((y * w) + x) * imageBytesDepth()];
+
+ if ( imageSixteenBit() ) // 16 bits image.
+ {
+ b16 = (uint16)(pixel[0]+256*pixel[1]);
+ g16 = (uint16)(pixel[2]+256*pixel[3]);
+ r16 = (uint16)(pixel[4]+256*pixel[5]);
+
+ if (imageHasAlpha())
+ {
+ // TIFF makes you pre-mutiply the rgb components by alpha
+
+ a16 = (uint16)(pixel[6]+256*pixel[7]);
+ alpha_factor = ((double)a16 / 65535.0);
+ r16 = (uint16)(r16*alpha_factor);
+ g16 = (uint16)(g16*alpha_factor);
+ b16 = (uint16)(b16*alpha_factor);
+ }
+
+ // This might be endian dependent
+
+ buf[i++] = (uint8)(r16);
+ buf[i++] = (uint8)(r16 >> 8);
+ buf[i++] = (uint8)(g16);
+ buf[i++] = (uint8)(g16 >> 8);
+ buf[i++] = (uint8)(b16);
+ buf[i++] = (uint8)(b16 >> 8);
+
+ if (imageHasAlpha())
+ {
+ buf[i++] = (uint8)(a16) ;
+ buf[i++] = (uint8)(a16 >> 8) ;
+ }
+ }
+ else // 8 bits image.
+ {
+ b8 = (uint8)pixel[0];
+ g8 = (uint8)pixel[1];
+ r8 = (uint8)pixel[2];
+
+ if (imageHasAlpha())
+ {
+ // TIFF makes you pre-mutiply the rgb components by alpha
+
+ a8 = (uint8)(pixel[3]);
+ alpha_factor = ((double)a8 / 255.0);
+ r8 = (uint8)(r8*alpha_factor);
+ g8 = (uint8)(g8*alpha_factor);
+ b8 = (uint8)(b8*alpha_factor);
+ }
+
+ // This might be endian dependent
+
+ buf[i++] = r8;
+ buf[i++] = g8;
+ buf[i++] = b8;
+
+ if (imageHasAlpha())
+ buf[i++] = a8;
+ }
+ }
+
+ if (!TIFFWriteScanline(tif, buf, y, 0))
+ {
+ DDebug() << k_funcinfo << "Cannot write main image to target file." << endl;
+ _TIFFfree(buf);
+ TIFFClose(tif);
+ return false;
+ }
+ }
+
+ _TIFFfree(buf);
+ TIFFWriteDirectory(tif);
+
+ // -------------------------------------------------------------------
+ // Write thumbnail in tiff directory IFD1
+
+ TQImage thumb = m_image->smoothScale(160, 120, TQSize::ScaleMin).copyTQImage();
+
+ TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint32)thumb.width());
+ TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (uint32)thumb.height());
+ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+ TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+ TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
+ TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE);
+ TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+ TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
+ TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
+ TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0));
+
+ uchar *pixelThumb;
+ uchar *dataThumb = thumb.bits();
+ uint8 *bufThumb = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif));
+
+ if (!bufThumb)
+ {
+ DDebug() << k_funcinfo << "Cannot allocate memory buffer for thumbnail." << endl;
+ TIFFClose(tif);
+ return false;
+ }
+
+ for (y = 0 ; y < uint32(thumb.height()) ; y++)
+ {
+ i = 0;
+
+ for (x = 0 ; x < uint32(thumb.width()) ; x++)
+ {
+ pixelThumb = &dataThumb[((y * thumb.width()) + x) * 4];
+
+ // This might be endian dependent
+ bufThumb[i++] = (uint8)pixelThumb[2];
+ bufThumb[i++] = (uint8)pixelThumb[1];
+ bufThumb[i++] = (uint8)pixelThumb[0];
+ }
+
+ if (!TIFFWriteScanline(tif, bufThumb, y, 0))
+ {
+ DDebug() << k_funcinfo << "Cannot write thumbnail to target file." << endl;
+ _TIFFfree(bufThumb);
+ TIFFClose(tif);
+ return false;
+ }
+ }
+
+ _TIFFfree(bufThumb);
+ TIFFClose(tif);
+
+ // -------------------------------------------------------------------
+
+ if (observer)
+ observer->progressInfo(m_image, 1.0);
+
+ imageSetAttribute("savedformat", "TIFF");
+
+ saveMetadata(filePath);
+
+ return true;
+}
+
+bool TIFFLoader::hasAlpha() const
+{
+ return m_hasAlpha;
+}
+
+bool TIFFLoader::sixteenBit() const
+{
+ return m_sixteenBit;
+}
+
+void TIFFLoader::tiffSetExifAsciiTag(TIFF* tif, ttag_t tiffTag,
+ const DMetadata *metaData, const char* exifTagName)
+{
+ TQByteArray tag = metaData->getExifTagData(exifTagName);
+ if (!tag.isEmpty())
+ {
+ TQCString str(tag.data(), tag.size());
+ TIFFSetField(tif, tiffTag, (const char*)str);
+ }
+}
+
+void TIFFLoader::tiffSetExifDataTag(TIFF* tif, ttag_t tiffTag,
+ const DMetadata *metaData, const char* exifTagName)
+{
+ TQByteArray tag = metaData->getExifTagData(exifTagName);
+ if (!tag.isEmpty())
+ {
+ TIFFSetField (tif, tiffTag, (uint32)tag.size(), (char *)tag.data());
+ }
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dimg/loaders/tiffloader.h b/src/libs/dimg/loaders/tiffloader.h
new file mode 100644
index 00000000..484afd06
--- /dev/null
+++ b/src/libs/dimg/loaders/tiffloader.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-17
+ * Description : A TIFF IO file for DImg framework
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TIFFLOADER_H
+#define TIFFLOADER_H
+
+// C ansi includes.
+
+extern "C"
+{
+#include <tiffio.h>
+#include <tiff.h>
+}
+
+// Local includes.
+
+#include "dimgloader.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImg;
+class DMetadata;
+
+class DIGIKAM_EXPORT TIFFLoader : public DImgLoader
+{
+public:
+
+ TIFFLoader(DImg* image);
+
+ bool load(const TQString& filePath, DImgLoaderObserver *observer);
+ bool save(const TQString& filePath, DImgLoaderObserver *observer);
+
+ virtual bool hasAlpha() const;
+ virtual bool sixteenBit() const;
+ virtual bool isReadOnly() const { return false; };
+
+private:
+
+ void tiffSetExifAsciiTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName);
+ void tiffSetExifDataTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName);
+
+ static void dimg_tiff_warning(const char* module, const char* format, va_list warnings);
+ static void dimg_tiff_error(const char* module, const char* format, va_list errors);
+
+private:
+
+ bool m_sixteenBit;
+ bool m_hasAlpha;
+};
+
+} // NameSpace Digikam
+
+#endif /* TIFFLOADER_H */
diff --git a/src/libs/dimg/loaders/tiffsettings.cpp b/src/libs/dimg/loaders/tiffsettings.cpp
new file mode 100644
index 00000000..3ea7e20c
--- /dev/null
+++ b/src/libs/dimg/loaders/tiffsettings.cpp
@@ -0,0 +1,94 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save TIFF image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlabel.h>
+#include <tqcheckbox.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+
+// Local includes.
+
+#include "tiffsettings.h"
+#include "tiffsettings.moc"
+
+namespace Digikam
+{
+
+class TIFFSettingsPriv
+{
+
+public:
+
+ TIFFSettingsPriv()
+ {
+ TIFFGrid = 0;
+ TIFFcompression = 0;
+ }
+
+ TQGridLayout *TIFFGrid;
+
+ TQCheckBox *TIFFcompression;
+};
+
+TIFFSettings::TIFFSettings(TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new TIFFSettingsPriv;
+
+ d->TIFFGrid = new TQGridLayout(this, 1, 1, KDialog::spacingHint());
+ d->TIFFcompression = new TQCheckBox(i18n("Compress TIFF files"), this);
+
+ TQWhatsThis::add( d->TIFFcompression, i18n("<p>Toggle compression for TIFF images.<p>"
+ "If you enable this option, you can reduce "
+ "the final file size of the TIFF image.</p>"
+ "<p>A lossless compression format (Deflate) "
+ "is used to save the file.<p>"));
+ d->TIFFGrid->addMultiCellWidget(d->TIFFcompression, 0, 0, 0, 1);
+ d->TIFFGrid->setColStretch(1, 10);
+}
+
+TIFFSettings::~TIFFSettings()
+{
+ delete d;
+}
+
+void TIFFSettings::setCompression(bool b)
+{
+ d->TIFFcompression->setChecked(b);
+}
+
+bool TIFFSettings::getCompression()
+{
+ return d->TIFFcompression->isChecked();
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/dimg/loaders/tiffsettings.h b/src/libs/dimg/loaders/tiffsettings.h
new file mode 100644
index 00000000..e546ce0f
--- /dev/null
+++ b/src/libs/dimg/loaders/tiffsettings.h
@@ -0,0 +1,60 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : save TIFF image options.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TIFFSETTINGS_H
+#define TIFFSETTINGS_H
+
+// KDE includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class TIFFSettingsPriv;
+
+class DIGIKAM_EXPORT TIFFSettings : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ TIFFSettings(TQWidget *parent=0);
+ ~TIFFSettings();
+
+ void setCompression(bool b);
+ bool getCompression();
+
+private:
+
+ TIFFSettingsPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* TIFFSETTINGS_H */
diff --git a/src/libs/dmetadata/Makefile.am b/src/libs/dmetadata/Makefile.am
new file mode 100644
index 00000000..73ff6010
--- /dev/null
+++ b/src/libs/dmetadata/Makefile.am
@@ -0,0 +1,18 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdmetadata.la
+
+libdmetadata_la_SOURCES = dmetadata.cpp
+
+libdmetadata_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+libdmetadata_la_LIBADD = $(LIBKEXIV2_LIBS) $(LIBKDCRAW_LIBS)
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKEXIV2_CFLAGS) \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikaminclude_HEADERS = dmetadata.h photoinfocontainer.h
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/dmetadata/dmetadata.cpp b/src/libs/dmetadata/dmetadata.cpp
new file mode 100644
index 00000000..bb37506c
--- /dev/null
+++ b/src/libs/dmetadata/dmetadata.cpp
@@ -0,0 +1,544 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-23
+ * Description : image metadata interface
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdom.h>
+#include <tqfile.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/dcrawinfocontainer.h>
+#include <libkdcraw/kdcraw.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "constants.h"
+#include "ddebug.h"
+#include "dmetadata.h"
+
+namespace Digikam
+{
+
+DMetadata::DMetadata()
+ : KExiv2Iface::KExiv2()
+{
+}
+
+DMetadata::DMetadata(const TQString& filePath)
+ : KExiv2Iface::KExiv2()
+{
+ load(filePath);
+}
+
+DMetadata::~DMetadata()
+{
+}
+
+bool DMetadata::load(const TQString& filePath)
+{
+ // In first, we trying to get metadata using Exiv2,
+ // else we will use dcraw to extract minimal information.
+
+ if (!KExiv2::load(filePath))
+ {
+ if (!loadUsingDcraw(filePath))
+ return false;
+ }
+
+ return true;
+}
+
+bool DMetadata::loadUsingDcraw(const TQString& filePath)
+{
+ KDcrawIface::DcrawInfoContainer identify;
+ if (KDcrawIface::KDcraw::rawFileIdentify(identify, filePath))
+ {
+ long int num=1, den=1;
+
+ if (!identify.model.isNull())
+ setExifTagString("Exif.Image.Model", identify.model.latin1(), false);
+
+ if (!identify.make.isNull())
+ setExifTagString("Exif.Image.Make", identify.make.latin1(), false);
+
+ if (!identify.owner.isNull())
+ setExifTagString("Exif.Image.Artist", identify.owner.latin1(), false);
+
+ if (identify.sensitivity != -1)
+ setExifTagLong("Exif.Photo.ISOSpeedRatings", identify.sensitivity, false);
+
+ if (identify.dateTime.isValid())
+ setImageDateTime(identify.dateTime, false, false);
+
+ if (identify.exposureTime != -1.0)
+ {
+ convertToRational(1/identify.exposureTime, &num, &den, 8);
+ setExifTagRational("Exif.Photo.ExposureTime", num, den, false);
+ }
+
+ if (identify.aperture != -1.0)
+ {
+ convertToRational(identify.aperture, &num, &den, 8);
+ setExifTagRational("Exif.Photo.ApertureValue", num, den, false);
+ }
+
+ if (identify.focalLength != -1.0)
+ {
+ convertToRational(identify.focalLength, &num, &den, 8);
+ setExifTagRational("Exif.Photo.FocalLength", num, den, false);
+ }
+
+ if (identify.imageSize.isValid())
+ setImageDimensions(identify.imageSize, false);
+
+ // A RAW image is always uncalibrated. */
+ setImageColorWorkSpace(WORKSPACE_UNCALIBRATED, false);
+
+ return true;
+ }
+
+ return false;
+}
+
+TQString DMetadata::getImageComment() const
+{
+ if (getFilePath().isEmpty())
+ return TQString();
+
+ // In first we trying to get image comments, outside of Exif and IPTC.
+
+ TQString comment = getCommentsDecoded();
+ if (!comment.isEmpty())
+ return comment;
+
+ // In second, we trying to get Exif comments
+
+ if (!getExif().isEmpty())
+ {
+ TQString exifComment = getExifComment();
+ if (!exifComment.isEmpty())
+ return exifComment;
+ }
+
+ // In third, we trying to get IPTC comments
+
+ if (!getIptc().isEmpty())
+ {
+ TQString iptcComment = getIptcTagString("Iptc.Application2.Caption", false);
+ if (!iptcComment.isEmpty() && !iptcComment.stripWhiteSpace().isEmpty())
+ return iptcComment;
+ }
+
+ return TQString();
+}
+
+bool DMetadata::setImageComment(const TQString& comment)
+{
+ //See bug #139313: An empty string is also a valid value
+ //if (comment.isEmpty())
+ // return false;
+
+ DDebug() << getFilePath() << " ==> Comment: " << comment << endl;
+
+ if (!setProgramId())
+ return false;
+
+ // In first we trying to set image comments, outside of Exif and IPTC.
+
+ if (!setComments(comment.utf8()))
+ return false;
+
+ // In Second we write comments into Exif.
+
+ if (!setExifComment(comment))
+ return false;
+
+ // In Third we write comments into Iptc.
+ // Note that Caption IPTC tag is limited to 2000 char and ASCII charset.
+
+ TQString commentIptc = comment;
+ commentIptc.truncate(2000);
+
+ if (!setIptcTagString("Iptc.Application2.Caption", commentIptc))
+ return false;
+
+ return true;
+}
+
+/*
+Iptc.Application2.Urgency <==> digiKam Rating links:
+
+digiKam IPTC
+Rating Urgency
+
+0 star <=> 8 // Least important
+1 star <=> 7
+1 star <== 6
+2 star <=> 5
+3 star <=> 4
+4 star <== 3
+4 star <=> 2
+5 star <=> 1 // Most important
+*/
+
+int DMetadata::getImageRating() const
+{
+ if (getFilePath().isEmpty())
+ return -1;
+
+ // Check Exif rating tag set by Windows Vista
+ // Note : no need to check rating in percent tags (Exif.image.0x4747) here because
+ // its appear always with rating tag value (Exif.image.0x4749).
+
+ if (!getExif().isEmpty())
+ {
+ long rating = -1;
+ if (getExifTagLong("Exif.Image.0x4746", rating))
+ {
+ if (rating >= RatingMin && rating <= RatingMax)
+ return rating;
+ }
+ }
+
+ // Check Iptc Urgency tag content
+
+ if (!getIptc().isEmpty())
+ {
+ TQString IptcUrgency(getIptcTagData("Iptc.Application2.Urgency"));
+
+ if (!IptcUrgency.isEmpty())
+ {
+ if (IptcUrgency == TQString("1"))
+ return 5;
+ else if (IptcUrgency == TQString("2"))
+ return 4;
+ else if (IptcUrgency == TQString("3"))
+ return 4;
+ else if (IptcUrgency == TQString("4"))
+ return 3;
+ else if (IptcUrgency == TQString("5"))
+ return 2;
+ else if (IptcUrgency == TQString("6"))
+ return 1;
+ else if (IptcUrgency == TQString("7"))
+ return 1;
+ else if (IptcUrgency == TQString("8"))
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+bool DMetadata::setImageRating(int rating)
+{
+ if (rating < RatingMin || rating > RatingMax)
+ {
+ DDebug() << k_funcinfo << "Rating value to write is out of range!" << endl;
+ return false;
+ }
+
+ DDebug() << getFilePath() << " ==> Rating: " << rating << endl;
+
+ if (!setProgramId())
+ return false;
+
+ // Set Exif rating tag used by Windows Vista.
+
+ if (!setExifTagLong("Exif.Image.0x4746", rating))
+ return false;
+
+ // Wrapper around rating percents managed by Windows Vista.
+ int ratePercents = 0;
+ switch(rating)
+ {
+ case 0:
+ ratePercents = 0;
+ break;
+ case 1:
+ ratePercents = 1;
+ break;
+ case 2:
+ ratePercents = 25;
+ break;
+ case 3:
+ ratePercents = 50;
+ break;
+ case 4:
+ ratePercents = 75;
+ break;
+ case 5:
+ ratePercents = 99;
+ break;
+ }
+
+ if (!setExifTagLong("Exif.Image.0x4749", ratePercents))
+ return false;
+
+ // Set Iptc Urgency tag value.
+
+ TQString urgencyTag;
+
+ switch(rating)
+ {
+ case 0:
+ urgencyTag = TQString("8");
+ break;
+ case 1:
+ urgencyTag = TQString("7");
+ break;
+ case 2:
+ urgencyTag = TQString("5");
+ break;
+ case 3:
+ urgencyTag = TQString("4");
+ break;
+ case 4:
+ urgencyTag = TQString("3");
+ break;
+ case 5:
+ urgencyTag = TQString("1");
+ break;
+ }
+
+ if (!setIptcTagString("Iptc.Application2.Urgency", urgencyTag))
+ return false;
+
+ return true;
+}
+
+bool DMetadata::setIptcTag(const TQString& text, int maxLength, const char* debugLabel, const char* tagKey)
+{
+ TQString truncatedText = text;
+ truncatedText.truncate(maxLength);
+ DDebug() << getFilePath() << " ==> " << debugLabel << ": " << truncatedText << endl;
+ return setIptcTagString(tagKey, truncatedText); // returns false if failed
+}
+
+bool DMetadata::setImagePhotographerId(const TQString& author, const TQString& authorTitle)
+{
+ if (!setProgramId())
+ return false;
+
+ //TODO Exernalize the hard-coded values
+ if (!setIptcTag(author, 32, "Author", "Iptc.Application2.Byline")) return false;
+ if (!setIptcTag(authorTitle, 32, "Author Title", "Iptc.Application2.BylineTitle")) return false;
+
+ return true;
+}
+
+bool DMetadata::setImageCredits(const TQString& credit, const TQString& source, const TQString& copyright)
+{
+ if (!setProgramId())
+ return false;
+
+ //TODO Exernalize the hard-coded values
+ if (!setIptcTag(credit, 32, "Credit", "Iptc.Application2.Credit")) return false;
+ if (!setIptcTag(source, 32, "Source", "Iptc.Application2.Source")) return false;
+ if (!setIptcTag(copyright, 128, "Copyright", "Iptc.Application2.Copyright")) return false;
+
+ return true;
+}
+
+bool DMetadata::setProgramId(bool on)
+{
+ if (on)
+ {
+ TQString version(digikam_version);
+ TQString software("digiKam");
+ return setImageProgramId(software, version);
+ }
+
+ return true;
+}
+
+PhotoInfoContainer DMetadata::getPhotographInformations() const
+{
+ PhotoInfoContainer photoInfo;
+
+ if (!getExif().isEmpty())
+ {
+ photoInfo.dateTime = getImageDateTime();
+ photoInfo.make = getExifTagString("Exif.Image.Make");
+ photoInfo.model = getExifTagString("Exif.Image.Model");
+
+ photoInfo.aperture = getExifTagString("Exif.Photo.FNumber");
+ if (photoInfo.aperture.isEmpty())
+ photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue");
+
+ photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime");
+ if (photoInfo.exposureTime.isEmpty())
+ photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue");
+
+ photoInfo.exposureMode = getExifTagString("Exif.Photo.ExposureMode");
+ photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram");
+
+ photoInfo.focalLength = getExifTagString("Exif.Photo.FocalLength");
+ photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm");
+
+ photoInfo.sensitivity = getExifTagString("Exif.Photo.ISOSpeedRatings");
+ if (photoInfo.sensitivity.isEmpty())
+ photoInfo.sensitivity = getExifTagString("Exif.Photo.ExposureIndex");
+
+ photoInfo.flash = getExifTagString("Exif.Photo.Flash");
+ photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance");
+ }
+
+ return photoInfo;
+}
+
+/**
+The following methods set and get an XML dataset into a private IPTC.Application2 tags
+to backup digiKam image properties. The XML text data are compressed using zlib and stored
+like a byte array. The XML text data format are like below:
+
+<?xml version="1.0" encoding="UTF-8"?>
+<digikamproperties>
+ <comments value="A cool photo from Adrien..." />
+ <date value="2006-11-23T13:36:26" />
+ <rating value="4" />
+ <tagslist>
+ <tag path="Gilles/Adrien/testphoto" />
+ <tag path="monuments/Trocadero/Tour Eiffel" />
+ <tag path="City/Paris" />
+ </tagslist>
+</digikamproperties>
+
+*/
+
+bool DMetadata::getXMLImageProperties(TQString& comments, TQDateTime& date,
+ int& rating, TQStringList& tagsPath)
+{
+ rating = 0;
+
+ TQByteArray data = getIptcTagData("Iptc.Application2.0x00ff");
+ if (data.isEmpty())
+ return false;
+ TQByteArray decompressedData = tqUncompress(data);
+ TQString doc;
+ TQDataStream ds(decompressedData, IO_ReadOnly);
+ ds >> doc;
+
+ TQDomDocument xmlDoc;
+ TQString error;
+ int row, col;
+ if (!xmlDoc.setContent(doc, true, &error, &row, &col))
+ {
+ DDebug() << doc << endl;
+ DDebug() << error << " :: row=" << row << " , col=" << col << endl;
+ return false;
+ }
+
+ TQDomElement rootElem = xmlDoc.documentElement();
+ if (rootElem.tagName() != TQString::fromLatin1("digikamproperties"))
+ return false;
+
+ for (TQDomNode node = rootElem.firstChild();
+ !node.isNull(); node = node.nextSibling())
+ {
+ TQDomElement e = node.toElement();
+ TQString name = e.tagName();
+ TQString val = e.attribute(TQString::fromLatin1("value"));
+
+ if (name == TQString::fromLatin1("comments"))
+ {
+ comments = val;
+ }
+ else if (name == TQString::fromLatin1("date"))
+ {
+ if (val.isEmpty()) continue;
+ date = TQDateTime::fromString(val, TQt::ISODate);
+ }
+ else if (name == TQString::fromLatin1("rating"))
+ {
+ if (val.isEmpty()) continue;
+ bool ok=false;
+ rating = val.toInt(&ok);
+ if (!ok) rating = 0;
+ }
+ else if (name == TQString::fromLatin1("tagslist"))
+ {
+ for (TQDomNode node2 = e.firstChild();
+ !node2.isNull(); node2 = node2.nextSibling())
+ {
+ TQDomElement e2 = node2.toElement();
+ TQString name2 = e2.tagName();
+ TQString val2 = e2.attribute(TQString::fromLatin1("path"));
+
+ if (name2 == TQString::fromLatin1("tag"))
+ {
+ if (val2.isEmpty()) continue;
+ tagsPath.append(val2);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool DMetadata::setXMLImageProperties(const TQString& comments, const TQDateTime& date,
+ int rating, const TQStringList& tagsPath)
+{
+ TQDomDocument xmlDoc;
+
+ xmlDoc.appendChild(xmlDoc.createProcessingInstruction( TQString::fromLatin1("xml"),
+ TQString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"") ) );
+
+ TQDomElement propertiesElem = xmlDoc.createElement(TQString::fromLatin1("digikamproperties"));
+ xmlDoc.appendChild( propertiesElem );
+
+ TQDomElement c = xmlDoc.createElement(TQString::fromLatin1("comments"));
+ c.setAttribute(TQString::fromLatin1("value"), comments);
+ propertiesElem.appendChild(c);
+
+ TQDomElement d = xmlDoc.createElement(TQString::fromLatin1("date"));
+ d.setAttribute(TQString::fromLatin1("value"), date.toString(TQt::ISODate));
+ propertiesElem.appendChild(d);
+
+ TQDomElement r = xmlDoc.createElement(TQString::fromLatin1("rating"));
+ r.setAttribute(TQString::fromLatin1("value"), rating);
+ propertiesElem.appendChild(r);
+
+ TQDomElement tagsElem = xmlDoc.createElement(TQString::fromLatin1("tagslist"));
+ propertiesElem.appendChild(tagsElem);
+
+ TQStringList path = tagsPath;
+ for ( TQStringList::Iterator it = path.begin(); it != path.end(); ++it )
+ {
+ TQDomElement e = xmlDoc.createElement(TQString::fromLatin1("tag"));
+ e.setAttribute(TQString::fromLatin1("path"), *it);
+ tagsElem.appendChild(e);
+ }
+
+ TQByteArray data, compressedData;
+ TQDataStream ds(data, IO_WriteOnly);
+ ds << xmlDoc.toString();
+ compressedData = tqCompress(data);
+ return (setIptcTagData("Iptc.Application2.0x00ff", compressedData));
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/dmetadata/dmetadata.h b/src/libs/dmetadata/dmetadata.h
new file mode 100644
index 00000000..a33bc61c
--- /dev/null
+++ b/src/libs/dmetadata/dmetadata.h
@@ -0,0 +1,86 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-23
+ * Description : image metadata interface
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DMETADATA_H
+#define DMETADATA_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// LibKExiv2 includes.
+
+#include <libkexiv2/kexiv2.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "photoinfocontainer.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DMetadata : public KExiv2Iface::KExiv2
+{
+
+public:
+
+ DMetadata();
+ DMetadata(const TQString& filePath);
+ ~DMetadata();
+
+ /** Re-implemented from libKexiv2 to use dcraw identify method if Exiv2 failed. */
+ bool load(const TQString& filePath);
+
+ /** Try to extract metadata using dcraw identify method */
+ bool loadUsingDcraw(const TQString& filePath);
+
+ /** Metadata manipulation methods */
+
+ TQString getImageComment() const;
+ bool setImageComment(const TQString& comment);
+
+ int getImageRating() const;
+ bool setImageRating(int rating);
+
+ bool setImagePhotographerId(const TQString& author, const TQString& authorTitle);
+ bool setImageCredits(const TQString& credit, const TQString& source, const TQString& copyright);
+
+ PhotoInfoContainer getPhotographInformations() const;
+
+ bool getXMLImageProperties(TQString& comments, TQDateTime& date,
+ int& rating, TQStringList& tagsPath);
+ bool setXMLImageProperties(const TQString& comments, const TQDateTime& date,
+ int rating, const TQStringList& tagsPath);
+
+private:
+
+ bool setProgramId(bool on=true);
+ bool setIptcTag(const TQString& text, int maxLength, const char* debugLabel, const char* tagKey);
+};
+
+} // NameSpace Digikam
+
+#endif /* DMETADATA_H */
diff --git a/src/libs/dmetadata/photoinfocontainer.h b/src/libs/dmetadata/photoinfocontainer.h
new file mode 100644
index 00000000..b008b6b4
--- /dev/null
+++ b/src/libs/dmetadata/photoinfocontainer.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-04-21
+ * Description : main photograph information container
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PHOTOINFOCONTAINER_H
+#define PHOTOINFOCONTAINER_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqdatetime.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT PhotoInfoContainer
+{
+
+public:
+
+ PhotoInfoContainer(){};
+
+ bool isEmpty()
+ {
+ if ( make.isEmpty() &&
+ model.isEmpty() &&
+ exposureTime.isEmpty() &&
+ exposureMode.isEmpty() &&
+ exposureProgram.isEmpty() &&
+ aperture.isEmpty() &&
+ focalLength.isEmpty() &&
+ focalLength35mm.isEmpty() &&
+ sensitivity.isEmpty() &&
+ flash.isEmpty() &&
+ whiteBalance.isEmpty() &&
+ !dateTime.isValid() )
+ return true;
+ else
+ return false;
+ };
+
+ TQString make;
+ TQString model;
+ TQString exposureTime;
+ TQString exposureMode;
+ TQString exposureProgram;
+ TQString aperture;
+ TQString focalLength;
+ TQString focalLength35mm;
+ TQString sensitivity;
+ TQString flash;
+ TQString whiteBalance;
+
+ TQDateTime dateTime;
+};
+
+} // namespace Digikam
+
+#endif /* PHOTOINFOCONTAINER_H */
diff --git a/src/libs/greycstoration/CImg.h b/src/libs/greycstoration/CImg.h
new file mode 100644
index 00000000..9f6662e1
--- /dev/null
+++ b/src/libs/greycstoration/CImg.h
@@ -0,0 +1,36837 @@
+/*
+ #
+ # File : CImg.h
+ # ( C++ header file )
+ #
+ # Description : The C++ Template Image Processing Library.
+ # This file is the main part of the CImg Library project.
+ # ( http://cimg.sourceforge.net )
+ #
+ # Project manager : David Tschumperle.
+ # ( http://www.greyc.ensicaen.fr/~dtschump/ )
+ #
+ # The complete contributor list can be seen in the 'README.txt' file.
+ #
+ # Licenses : This file is "dual-licensed", you have to choose one
+ # of the two licenses below to apply on this file.
+ #
+ # CeCILL-C
+ # The CeCILL-C license is close to the GNU LGPL.
+ # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html )
+ #
+ # or CeCILL v2.0
+ # The CeCILL license is compatible with the GNU GPL.
+ # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
+ #
+ # This software is governed either by the CeCILL or the CeCILL-C license
+ # under French law and abiding by the rules of distribution of free software.
+ # You can use, modify and or redistribute the software under the terms of
+ # the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA
+ # at the following URL : "http://www.cecill.info".
+ #
+ # As a counterpart to the access to the source code and rights to copy,
+ # modify and redistribute granted by the license, users are provided only
+ # with a limited warranty and the software's author, the holder of the
+ # economic rights, and the successive licensors have only limited
+ # liability.
+ #
+ # In this respect, the user's attention is drawn to the risks associated
+ # with loading, using, modifying and/or developing or reproducing the
+ # software by the user in light of its specific status of free software,
+ # that may mean that it is complicated to manipulate, and that also
+ # therefore means that it is reserved for developers and experienced
+ # professionals having in-depth computer knowledge. Users are therefore
+ # encouraged to load and test the software's suitability as regards their
+ # requirements in conditions enabling the security of their systems and/or
+ # data to be ensured and, more generally, to use and operate it in the
+ # same conditions as regards security.
+ #
+ # The fact that you are presently reading this means that you have had
+ # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms.
+ #
+*/
+
+// Define version number of the current file.
+//
+#ifndef cimg_version
+#define cimg_version 130
+
+/*-----------------------------------------------------------
+ #
+ # Test/auto-set CImg configuration variables
+ # and include required headers.
+ #
+ # If you find that default configuration variables are
+ # not adapted, you can override their values before including
+ # the header file "CImg.h" (using the #define directive).
+ #
+ ------------------------------------------------------------*/
+
+// Include required standard C++ headers.
+//
+#include <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+#include <cstring>
+#include <cmath>
+#include <ctime>
+
+// Operating system configuration.
+//
+// Define 'cimg_OS' to : 0 for an unknown OS (will try to minize library dependancies).
+// 1 for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...).
+// 2 for Microsoft Windows.
+//
+#ifndef cimg_OS
+#if defined(unix) || defined(__unix) || defined(__unix__) \
+ || defined(linux) || defined(__linux) || defined(__linux__) \
+ || defined(sun) || defined(__sun) \
+ || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \
+ || defined(__FreeBSD__) || defined __DragonFly__ \
+ || defined(sgi) || defined(__sgi) \
+ || defined(__MACOSX__) || defined(__APPLE__) \
+ || defined(__CYGWIN__)
+#define cimg_OS 1
+#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \
+ || defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
+#define cimg_OS 2
+#else
+#define cimg_OS 0
+#endif
+#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2)
+#error CImg Library : Configuration variable 'cimg_OS' is badly defined.
+#error (valid values are '0=unknown OS', '1=Unix-like OS', '2=Microsoft Windows').
+#endif
+
+// Compiler configuration.
+//
+// Try to detect Microsoft VC++ compilers.
+// (lot of workarounds are needed afterwards to
+// make CImg working, particularly with VC++ 6.0).
+//
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4311)
+#pragma warning(disable:4312)
+#pragma warning(disable:4800)
+#pragma warning(disable:4804)
+#pragma warning(disable:4996)
+#define _CRT_SECURE_NO_DEPRECATE 1
+#define _CRT_NONSTDC_NO_DEPRECATE 1
+#if _MSC_VER<1300
+#define cimg_use_visualcpp6
+#define cimg_std
+#define _WIN32_WINNT 0x0500
+#endif
+#endif
+
+// Include OS-specific headers.
+//
+#if cimg_OS==1
+#include <sys/time.h>
+#include <unistd.h>
+#elif cimg_OS==2
+#include <windows.h>
+#ifndef _WIN32_IE
+#define _WIN32_IE 0x0400
+#endif
+#include <shlobj.h>
+#endif
+
+// Define defaut pipe for output messages
+//
+// Define 'cimg_stdout' to : stdout to print CImg messages on the standard output.
+// stderr to print CImg messages on the standart error output (default behavior).
+//
+#ifndef cimg_std
+#define cimg_std std
+#endif
+#ifndef cimg_stdout
+#define cimg_stdout stderr
+#endif
+
+// Output messages configuration.
+//
+// Define 'cimg_debug' to : 0 to hide debug messages (quiet mode, but exceptions are still thrown).
+// 1 to display debug messages on the console.
+// 2 to display debug messages with dialog windows (default behavior).
+// 3 to do as 1 + add extra warnings (may slow down the code !).
+// 4 to do as 2 + add extra warnings (may slow down the code !).
+//
+// Define 'cimg_strict_warnings' to replace warning messages by exception throwns.
+//
+// Define 'cimg_use_vt100' to allow output of color messages (require VT100-compatible terminal).
+//
+#ifndef cimg_debug
+#define cimg_debug 2
+#elif !(cimg_debug==0 || cimg_debug==1 || cimg_debug==2 || cimg_debug==3 || cimg_debug==4)
+#error CImg Library : Configuration variable 'cimg_debug' is badly defined.
+#error (valid values are '0=quiet', '1=console', '2=dialog', '3=console+warnings', '4=dialog+warnings').
+#endif
+
+// Display framework configuration.
+//
+// Define 'cimg_display' to : 0 to disable display capabilities.
+// 1 to use X-Window framework (X11).
+// 2 to use Microsoft GDI32 framework.
+// 3 to use Apple Carbon framework.
+//
+#ifndef cimg_display
+#if cimg_OS==0
+#define cimg_display 0
+#elif cimg_OS==1
+#if defined(__MACOSX__) || defined(__APPLE__)
+#define cimg_display 1
+#else
+#define cimg_display 1
+#endif
+#elif cimg_OS==2
+#define cimg_display 2
+#endif
+#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2 || cimg_display==3)
+#error CImg Library : Configuration variable 'cimg_display' is badly defined.
+#error (valid values are '0=disable', '1=X-Window (X11)', '2=Microsoft GDI32', '3=Apple Carbon').
+#endif
+
+// Include display-specific headers.
+//
+#if cimg_display==1
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <pthread.h>
+#ifdef cimg_use_xshm
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+#ifdef cimg_use_xrandr
+#include <X11/extensions/Xrandr.h>
+#endif
+#elif cimg_display==3
+#include <Carbon/Carbon.h>
+#include <pthread.h>
+#endif
+
+// OpenMP configuration.
+// (http://www.openmp.org)
+//
+// Define 'cimg_use_openmp' to enable OpenMP support.
+//
+// OpenMP directives can be used in few CImg functions to get
+// advantages of multi-core CPUs. Using OpenMP is not mandatory.
+//
+#ifdef cimg_use_openmp
+#include "omp.h"
+#endif
+
+// LibPNG configuration.
+// (http://www.libpng.org)
+//
+// Define 'cimg_use_png' to enable LibPNG support.
+//
+// LibPNG can be used in functions 'CImg<T>::{load,save}_png()'
+// to get a builtin support of PNG files. Using LibPNG is not mandatory.
+//
+#ifdef cimg_use_png
+extern "C" {
+#include "png.h"
+}
+#endif
+
+// LibJPEG configuration.
+// (http://en.wikipedia.org/wiki/Libjpeg)
+//
+// Define 'cimg_use_jpeg' to enable LibJPEG support.
+//
+// LibJPEG can be used in functions 'CImg<T>::{load,save}_jpeg()'
+// to get a builtin support of JPEG files. Using LibJPEG is not mandatory.
+//
+#ifdef cimg_use_jpeg
+extern "C" {
+#include "jpeglib.h"
+}
+#endif
+
+// LibTIFF configuration.
+// (http://www.libtiff.org)
+//
+// Define 'cimg_use_tiff' to enable LibTIFF support.
+//
+// LibTIFF can be used in functions 'CImg[List]<T>::{load,save}_tiff()'
+// to get a builtin support of TIFF files. Using LibTIFF is not mandatory.
+//
+#ifdef cimg_use_tiff
+extern "C" {
+#include "tiffio.h"
+}
+#endif
+
+// FFMPEG Avcodec and Avformat libraries configuration.
+// (http://www.ffmpeg.org)
+//
+// Define 'cimg_use_ffmpeg' to enable FFMPEG lib support.
+//
+// Avcodec and Avformat libraries can be used in functions
+// 'CImg[List]<T>::load_ffmpeg()' to get a builtin
+// support of various image sequences files.
+// Using FFMPEG libraries is not mandatory.
+//
+#ifdef cimg_use_ffmpeg
+extern "C" {
+#include "avformat.h"
+#include "avcodec.h"
+#include "swscale.h"
+}
+#endif
+
+// Zlib configuration
+// (http://www.zlib.net)
+//
+// Define 'cimg_use_zlib' to enable Zlib support.
+//
+// Zlib can be used in functions 'CImg[List]<T>::{load,save}_cimg()'
+// to allow compressed data in '.cimg' files. Using Zlib is not mandatory.
+//
+#ifdef cimg_use_zlib
+extern "C" {
+#include "zlib.h"
+}
+#endif
+
+// Magick++ configuration.
+// (http://www.imagemagick.org/Magick++)
+//
+// Define 'cimg_use_magick' to enable Magick++ support.
+//
+// Magick++ library can be used in functions 'CImg<T>::{load,save}()'
+// to get a builtin support of various image formats (PNG,JPEG,TIFF,...).
+// Using Magick++ is not mandatory.
+//
+#ifdef cimg_use_magick
+#include "Magick++.h"
+#endif
+
+// FFTW3 configuration.
+// (http://www.fftw.org)
+//
+// Define 'cimg_use_fftw3' to enable libFFTW3 support.
+//
+// FFTW3 library can be used in functions 'CImg[List]<T>::FFT()' to
+// efficiently compile the Fast Fourier Transform of image data.
+//
+#ifdef cimg_use_fftw3
+extern "C" {
+#include "fftw3.h"
+}
+#endif
+
+// Board configuration.
+// (http://libboard.sourceforge.net/)
+//
+// Define 'cimg_use_board' to enable Board support.
+//
+// Board library can be used in functions 'CImg<T>::draw_object3d()'
+// to draw objects 3D in vector-graphics canvas that can be saved
+// as .PS or .SVG files afterwards.
+//
+#ifdef cimg_use_board
+#include "Board.h"
+#endif
+
+// Lapack configuration.
+// (http://www.netlib.org/lapack)
+//
+// Define 'cimg_use_lapack' to enable LAPACK support.
+//
+// Lapack can be used in various CImg functions dealing with
+// matrix computation and algorithms (eigenvalues, inverse, ...).
+// Using Lapack is not mandatory.
+//
+#ifdef cimg_use_lapack
+extern "C" {
+ extern void sgetrf_(int*, int*, float*, int*, int*, int*);
+ extern void sgetri_(int*, float*, int*, int*, float*, int*, int*);
+ extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*);
+ extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*);
+ extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*);
+ extern void dgetrf_(int*, int*, double*, int*, int*, int*);
+ extern void dgetri_(int*, double*, int*, int*, double*, int*, int*);
+ extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*);
+ extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, int*, double*, int*, double*, int*, int*);
+ extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*);
+}
+#endif
+
+// Check if min/max macros are defined.
+//
+// CImg does not compile if macros 'min' or 'max' are defined,
+// because min() and max() functions are also defined in the cimg:: namespace.
+// so it '#undef' these macros if necessary, and restore them to reasonable
+// values at the end of the file.
+//
+#ifdef min
+#undef min
+#define _cimg_redefine_min
+#endif
+#ifdef max
+#undef max
+#define _cimg_redefine_max
+#endif
+
+// Set the current working directory for native MacOSX bundled applications.
+//
+// By default, MacOS bundled applications set the cwd at the root directory '/',
+// the code below allows to set it to the current exec directory instead when
+// a CImg-based program is executed.
+//
+#if cimg_OS==1 && cimg_display==3
+static struct _cimg_macosx_setcwd {
+ _cimg_macosx_setcwd() {
+ FSRef location;
+ ProcessSerialNumber psn;
+ char filePath[512];
+ if (GetCurrentProcess(&psn)!=noErr) return;
+ if (GetProcessBundleLocation(&psn,&location)!=noErr) return;
+ FSRefMakePath(&location,(UInt8*)filePath,sizeof(filePath)-1);
+ int p = cimg_std::strlen(filePath);
+ while (filePath[p] != '/') --p;
+ filePath[p] = 0;
+ chdir(filePath);
+ }
+} cimg_macosx_setcwd;
+#endif
+
+/*------------------------------------------------------------------------------
+ #
+ # Define user-friendly macros.
+ #
+ # User macros are prefixed by 'cimg_' and can be used in your own code.
+ # They are particularly useful for option parsing, and image loops creation.
+ #
+ ------------------------------------------------------------------------------*/
+
+// Define the program usage, and retrieve command line arguments.
+//
+#define cimg_usage(usage) cimg_library::cimg::option((char*)0,argc,argv,(char*)0,usage)
+#define cimg_help(str) cimg_library::cimg::option((char*)0,argc,argv,str,(char*)0)
+#define cimg_option(name,defaut,usage) cimg_library::cimg::option(name,argc,argv,defaut,usage)
+#define cimg_argument(pos) cimg_library::cimg::argument(pos,argc,argv)
+#define cimg_argument1(pos,s0) cimg_library::cimg::argument(pos,argc,argv,1,s0)
+#define cimg_argument2(pos,s0,s1) cimg_library::cimg::argument(pos,argc,argv,2,s0,s1)
+#define cimg_argument3(pos,s0,s1,s2) cimg_library::cimg::argument(pos,argc,argv,3,s0,s1,s2)
+#define cimg_argument4(pos,s0,s1,s2,s3) cimg_library::cimg::argument(pos,argc,argv,4,s0,s1,s2,s3)
+#define cimg_argument5(pos,s0,s1,s2,s3,s4) cimg_library::cimg::argument(pos,argc,argv,5,s0,s1,s2,s3,s4)
+#define cimg_argument6(pos,s0,s1,s2,s3,s4,s5) cimg_library::cimg::argument(pos,argc,argv,6,s0,s1,s2,s3,s4,s5)
+#define cimg_argument7(pos,s0,s1,s2,s3,s4,s5,s6) cimg_library::cimg::argument(pos,argc,argv,7,s0,s1,s2,s3,s4,s5,s6)
+#define cimg_argument8(pos,s0,s1,s2,s3,s4,s5,s6,s7) cimg_library::cimg::argument(pos,argc,argv,8,s0,s1,s2,s3,s4,s5,s6,s7)
+#define cimg_argument9(pos,s0,s1,s2,s3,s4,s5,s6,s7,s8) cimg_library::cimg::argument(pos,argc,argv,9,s0,s1,s2,s3,s4,s5,s6,s7,s8)
+
+// Define and manipulate local neighborhoods.
+//
+#define CImg_2x2(I,T) T I[4]; \
+ T& I##cc = I[0]; T& I##nc = I[1]; \
+ T& I##cn = I[2]; T& I##nn = I[3]; \
+ I##cc = I##nc = \
+ I##cn = I##nn = 0
+
+#define CImg_3x3(I,T) T I[9]; \
+ T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \
+ T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \
+ T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \
+ I##pp = I##cp = I##np = \
+ I##pc = I##cc = I##nc = \
+ I##pn = I##cn = I##nn = 0
+
+#define CImg_4x4(I,T) T I[16]; \
+ T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \
+ T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \
+ T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \
+ T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \
+ I##pp = I##cp = I##np = I##ap = \
+ I##pc = I##cc = I##nc = I##ac = \
+ I##pn = I##cn = I##nn = I##an = \
+ I##pa = I##ca = I##na = I##aa = 0
+
+#define CImg_5x5(I,T) T I[25]; \
+ T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \
+ T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \
+ T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \
+ T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \
+ T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \
+ I##bb = I##pb = I##cb = I##nb = I##ab = \
+ I##bp = I##pp = I##cp = I##np = I##ap = \
+ I##bc = I##pc = I##cc = I##nc = I##ac = \
+ I##bn = I##pn = I##cn = I##nn = I##an = \
+ I##ba = I##pa = I##ca = I##na = I##aa = 0
+
+#define CImg_2x2x2(I,T) T I[8]; \
+ T& I##ccc = I[0]; T& I##ncc = I[1]; \
+ T& I##cnc = I[2]; T& I##nnc = I[3]; \
+ T& I##ccn = I[4]; T& I##ncn = I[5]; \
+ T& I##cnn = I[6]; T& I##nnn = I[7]; \
+ I##ccc = I##ncc = \
+ I##cnc = I##nnc = \
+ I##ccn = I##ncn = \
+ I##cnn = I##nnn = 0
+
+#define CImg_3x3x3(I,T) T I[27]; \
+ T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \
+ T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \
+ T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \
+ T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \
+ T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \
+ T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \
+ T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \
+ T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \
+ T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \
+ I##ppp = I##cpp = I##npp = \
+ I##pcp = I##ccp = I##ncp = \
+ I##pnp = I##cnp = I##nnp = \
+ I##ppc = I##cpc = I##npc = \
+ I##pcc = I##ccc = I##ncc = \
+ I##pnc = I##cnc = I##nnc = \
+ I##ppn = I##cpn = I##npn = \
+ I##pcn = I##ccn = I##ncn = \
+ I##pnn = I##cnn = I##nnn = 0
+
+#define cimg_get2x2(img,x,y,z,v,I) \
+ I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v)
+
+#define cimg_get3x3(img,x,y,z,v,I) \
+ I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_p1##x,y,z,v), \
+ I[4] = (img)(x,y,z,v), I[5] = (img)(_n1##x,y,z,v), I[6] = (img)(_p1##x,_n1##y,z,v), I[7] = (img)(x,_n1##y,z,v), \
+ I[8] = (img)(_n1##x,_n1##y,z,v)
+
+#define cimg_get4x4(img,x,y,z,v,I) \
+ I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_n2##x,_p1##y,z,v), \
+ I[4] = (img)(_p1##x,y,z,v), I[5] = (img)(x,y,z,v), I[6] = (img)(_n1##x,y,z,v), I[7] = (img)(_n2##x,y,z,v), \
+ I[8] = (img)(_p1##x,_n1##y,z,v), I[9] = (img)(x,_n1##y,z,v), I[10] = (img)(_n1##x,_n1##y,z,v), I[11] = (img)(_n2##x,_n1##y,z,v), \
+ I[12] = (img)(_p1##x,_n2##y,z,v), I[13] = (img)(x,_n2##y,z,v), I[14] = (img)(_n1##x,_n2##y,z,v), I[15] = (img)(_n2##x,_n2##y,z,v)
+
+#define cimg_get5x5(img,x,y,z,v,I) \
+ I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \
+ I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_p2##x,_p1##y,z,v), I[6] = (img)(_p1##x,_p1##y,z,v), I[7] = (img)(x,_p1##y,z,v), \
+ I[8] = (img)(_n1##x,_p1##y,z,v), I[9] = (img)(_n2##x,_p1##y,z,v), I[10] = (img)(_p2##x,y,z,v), I[11] = (img)(_p1##x,y,z,v), \
+ I[12] = (img)(x,y,z,v), I[13] = (img)(_n1##x,y,z,v), I[14] = (img)(_n2##x,y,z,v), I[15] = (img)(_p2##x,_n1##y,z,v), \
+ I[16] = (img)(_p1##x,_n1##y,z,v), I[17] = (img)(x,_n1##y,z,v), I[18] = (img)(_n1##x,_n1##y,z,v), I[19] = (img)(_n2##x,_n1##y,z,v), \
+ I[20] = (img)(_p2##x,_n2##y,z,v), I[21] = (img)(_p1##x,_n2##y,z,v), I[22] = (img)(x,_n2##y,z,v), I[23] = (img)(_n1##x,_n2##y,z,v), \
+ I[24] = (img)(_n2##x,_n2##y,z,v)
+
+#define cimg_get6x6(img,x,y,z,v,I) \
+ I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \
+ I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_n3##x,_p2##y,z,v), I[6] = (img)(_p2##x,_p1##y,z,v), I[7] = (img)(_p1##x,_p1##y,z,v), \
+ I[8] = (img)(x,_p1##y,z,v), I[9] = (img)(_n1##x,_p1##y,z,v), I[10] = (img)(_n2##x,_p1##y,z,v), I[11] = (img)(_n3##x,_p1##y,z,v), \
+ I[12] = (img)(_p2##x,y,z,v), I[13] = (img)(_p1##x,y,z,v), I[14] = (img)(x,y,z,v), I[15] = (img)(_n1##x,y,z,v), \
+ I[16] = (img)(_n2##x,y,z,v), I[17] = (img)(_n3##x,y,z,v), I[18] = (img)(_p2##x,_n1##y,z,v), I[19] = (img)(_p1##x,_n1##y,z,v), \
+ I[20] = (img)(x,_n1##y,z,v), I[21] = (img)(_n1##x,_n1##y,z,v), I[22] = (img)(_n2##x,_n1##y,z,v), I[23] = (img)(_n3##x,_n1##y,z,v), \
+ I[24] = (img)(_p2##x,_n2##y,z,v), I[25] = (img)(_p1##x,_n2##y,z,v), I[26] = (img)(x,_n2##y,z,v), I[27] = (img)(_n1##x,_n2##y,z,v), \
+ I[28] = (img)(_n2##x,_n2##y,z,v), I[29] = (img)(_n3##x,_n2##y,z,v), I[30] = (img)(_p2##x,_n3##y,z,v), I[31] = (img)(_p1##x,_n3##y,z,v), \
+ I[32] = (img)(x,_n3##y,z,v), I[33] = (img)(_n1##x,_n3##y,z,v), I[34] = (img)(_n2##x,_n3##y,z,v), I[35] = (img)(_n3##x,_n3##y,z,v)
+
+#define cimg_get7x7(img,x,y,z,v,I) \
+ I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \
+ I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_p3##x,_p2##y,z,v), \
+ I[8] = (img)(_p2##x,_p2##y,z,v), I[9] = (img)(_p1##x,_p2##y,z,v), I[10] = (img)(x,_p2##y,z,v), I[11] = (img)(_n1##x,_p2##y,z,v), \
+ I[12] = (img)(_n2##x,_p2##y,z,v), I[13] = (img)(_n3##x,_p2##y,z,v), I[14] = (img)(_p3##x,_p1##y,z,v), I[15] = (img)(_p2##x,_p1##y,z,v), \
+ I[16] = (img)(_p1##x,_p1##y,z,v), I[17] = (img)(x,_p1##y,z,v), I[18] = (img)(_n1##x,_p1##y,z,v), I[19] = (img)(_n2##x,_p1##y,z,v), \
+ I[20] = (img)(_n3##x,_p1##y,z,v), I[21] = (img)(_p3##x,y,z,v), I[22] = (img)(_p2##x,y,z,v), I[23] = (img)(_p1##x,y,z,v), \
+ I[24] = (img)(x,y,z,v), I[25] = (img)(_n1##x,y,z,v), I[26] = (img)(_n2##x,y,z,v), I[27] = (img)(_n3##x,y,z,v), \
+ I[28] = (img)(_p3##x,_n1##y,z,v), I[29] = (img)(_p2##x,_n1##y,z,v), I[30] = (img)(_p1##x,_n1##y,z,v), I[31] = (img)(x,_n1##y,z,v), \
+ I[32] = (img)(_n1##x,_n1##y,z,v), I[33] = (img)(_n2##x,_n1##y,z,v), I[34] = (img)(_n3##x,_n1##y,z,v), I[35] = (img)(_p3##x,_n2##y,z,v), \
+ I[36] = (img)(_p2##x,_n2##y,z,v), I[37] = (img)(_p1##x,_n2##y,z,v), I[38] = (img)(x,_n2##y,z,v), I[39] = (img)(_n1##x,_n2##y,z,v), \
+ I[40] = (img)(_n2##x,_n2##y,z,v), I[41] = (img)(_n3##x,_n2##y,z,v), I[42] = (img)(_p3##x,_n3##y,z,v), I[43] = (img)(_p2##x,_n3##y,z,v), \
+ I[44] = (img)(_p1##x,_n3##y,z,v), I[45] = (img)(x,_n3##y,z,v), I[46] = (img)(_n1##x,_n3##y,z,v), I[47] = (img)(_n2##x,_n3##y,z,v), \
+ I[48] = (img)(_n3##x,_n3##y,z,v)
+
+#define cimg_get8x8(img,x,y,z,v,I) \
+ I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \
+ I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_n4##x,_p3##y,z,v), \
+ I[8] = (img)(_p3##x,_p2##y,z,v), I[9] = (img)(_p2##x,_p2##y,z,v), I[10] = (img)(_p1##x,_p2##y,z,v), I[11] = (img)(x,_p2##y,z,v), \
+ I[12] = (img)(_n1##x,_p2##y,z,v), I[13] = (img)(_n2##x,_p2##y,z,v), I[14] = (img)(_n3##x,_p2##y,z,v), I[15] = (img)(_n4##x,_p2##y,z,v), \
+ I[16] = (img)(_p3##x,_p1##y,z,v), I[17] = (img)(_p2##x,_p1##y,z,v), I[18] = (img)(_p1##x,_p1##y,z,v), I[19] = (img)(x,_p1##y,z,v), \
+ I[20] = (img)(_n1##x,_p1##y,z,v), I[21] = (img)(_n2##x,_p1##y,z,v), I[22] = (img)(_n3##x,_p1##y,z,v), I[23] = (img)(_n4##x,_p1##y,z,v), \
+ I[24] = (img)(_p3##x,y,z,v), I[25] = (img)(_p2##x,y,z,v), I[26] = (img)(_p1##x,y,z,v), I[27] = (img)(x,y,z,v), \
+ I[28] = (img)(_n1##x,y,z,v), I[29] = (img)(_n2##x,y,z,v), I[30] = (img)(_n3##x,y,z,v), I[31] = (img)(_n4##x,y,z,v), \
+ I[32] = (img)(_p3##x,_n1##y,z,v), I[33] = (img)(_p2##x,_n1##y,z,v), I[34] = (img)(_p1##x,_n1##y,z,v), I[35] = (img)(x,_n1##y,z,v), \
+ I[36] = (img)(_n1##x,_n1##y,z,v), I[37] = (img)(_n2##x,_n1##y,z,v), I[38] = (img)(_n3##x,_n1##y,z,v), I[39] = (img)(_n4##x,_n1##y,z,v), \
+ I[40] = (img)(_p3##x,_n2##y,z,v), I[41] = (img)(_p2##x,_n2##y,z,v), I[42] = (img)(_p1##x,_n2##y,z,v), I[43] = (img)(x,_n2##y,z,v), \
+ I[44] = (img)(_n1##x,_n2##y,z,v), I[45] = (img)(_n2##x,_n2##y,z,v), I[46] = (img)(_n3##x,_n2##y,z,v), I[47] = (img)(_n4##x,_n2##y,z,v), \
+ I[48] = (img)(_p3##x,_n3##y,z,v), I[49] = (img)(_p2##x,_n3##y,z,v), I[50] = (img)(_p1##x,_n3##y,z,v), I[51] = (img)(x,_n3##y,z,v), \
+ I[52] = (img)(_n1##x,_n3##y,z,v), I[53] = (img)(_n2##x,_n3##y,z,v), I[54] = (img)(_n3##x,_n3##y,z,v), I[55] = (img)(_n4##x,_n3##y,z,v), \
+ I[56] = (img)(_p3##x,_n4##y,z,v), I[57] = (img)(_p2##x,_n4##y,z,v), I[58] = (img)(_p1##x,_n4##y,z,v), I[59] = (img)(x,_n4##y,z,v), \
+ I[60] = (img)(_n1##x,_n4##y,z,v), I[61] = (img)(_n2##x,_n4##y,z,v), I[62] = (img)(_n3##x,_n4##y,z,v), I[63] = (img)(_n4##x,_n4##y,z,v);
+
+#define cimg_get9x9(img,x,y,z,v,I) \
+ I[0] = (img)(_p4##x,_p4##y,z,v), I[1] = (img)(_p3##x,_p4##y,z,v), I[2] = (img)(_p2##x,_p4##y,z,v), I[3] = (img)(_p1##x,_p4##y,z,v), \
+ I[4] = (img)(x,_p4##y,z,v), I[5] = (img)(_n1##x,_p4##y,z,v), I[6] = (img)(_n2##x,_p4##y,z,v), I[7] = (img)(_n3##x,_p4##y,z,v), \
+ I[8] = (img)(_n4##x,_p4##y,z,v), I[9] = (img)(_p4##x,_p3##y,z,v), I[10] = (img)(_p3##x,_p3##y,z,v), I[11] = (img)(_p2##x,_p3##y,z,v), \
+ I[12] = (img)(_p1##x,_p3##y,z,v), I[13] = (img)(x,_p3##y,z,v), I[14] = (img)(_n1##x,_p3##y,z,v), I[15] = (img)(_n2##x,_p3##y,z,v), \
+ I[16] = (img)(_n3##x,_p3##y,z,v), I[17] = (img)(_n4##x,_p3##y,z,v), I[18] = (img)(_p4##x,_p2##y,z,v), I[19] = (img)(_p3##x,_p2##y,z,v), \
+ I[20] = (img)(_p2##x,_p2##y,z,v), I[21] = (img)(_p1##x,_p2##y,z,v), I[22] = (img)(x,_p2##y,z,v), I[23] = (img)(_n1##x,_p2##y,z,v), \
+ I[24] = (img)(_n2##x,_p2##y,z,v), I[25] = (img)(_n3##x,_p2##y,z,v), I[26] = (img)(_n4##x,_p2##y,z,v), I[27] = (img)(_p4##x,_p1##y,z,v), \
+ I[28] = (img)(_p3##x,_p1##y,z,v), I[29] = (img)(_p2##x,_p1##y,z,v), I[30] = (img)(_p1##x,_p1##y,z,v), I[31] = (img)(x,_p1##y,z,v), \
+ I[32] = (img)(_n1##x,_p1##y,z,v), I[33] = (img)(_n2##x,_p1##y,z,v), I[34] = (img)(_n3##x,_p1##y,z,v), I[35] = (img)(_n4##x,_p1##y,z,v), \
+ I[36] = (img)(_p4##x,y,z,v), I[37] = (img)(_p3##x,y,z,v), I[38] = (img)(_p2##x,y,z,v), I[39] = (img)(_p1##x,y,z,v), \
+ I[40] = (img)(x,y,z,v), I[41] = (img)(_n1##x,y,z,v), I[42] = (img)(_n2##x,y,z,v), I[43] = (img)(_n3##x,y,z,v), \
+ I[44] = (img)(_n4##x,y,z,v), I[45] = (img)(_p4##x,_n1##y,z,v), I[46] = (img)(_p3##x,_n1##y,z,v), I[47] = (img)(_p2##x,_n1##y,z,v), \
+ I[48] = (img)(_p1##x,_n1##y,z,v), I[49] = (img)(x,_n1##y,z,v), I[50] = (img)(_n1##x,_n1##y,z,v), I[51] = (img)(_n2##x,_n1##y,z,v), \
+ I[52] = (img)(_n3##x,_n1##y,z,v), I[53] = (img)(_n4##x,_n1##y,z,v), I[54] = (img)(_p4##x,_n2##y,z,v), I[55] = (img)(_p3##x,_n2##y,z,v), \
+ I[56] = (img)(_p2##x,_n2##y,z,v), I[57] = (img)(_p1##x,_n2##y,z,v), I[58] = (img)(x,_n2##y,z,v), I[59] = (img)(_n1##x,_n2##y,z,v), \
+ I[60] = (img)(_n2##x,_n2##y,z,v), I[61] = (img)(_n3##x,_n2##y,z,v), I[62] = (img)(_n4##x,_n2##y,z,v), I[63] = (img)(_p4##x,_n3##y,z,v), \
+ I[64] = (img)(_p3##x,_n3##y,z,v), I[65] = (img)(_p2##x,_n3##y,z,v), I[66] = (img)(_p1##x,_n3##y,z,v), I[67] = (img)(x,_n3##y,z,v), \
+ I[68] = (img)(_n1##x,_n3##y,z,v), I[69] = (img)(_n2##x,_n3##y,z,v), I[70] = (img)(_n3##x,_n3##y,z,v), I[71] = (img)(_n4##x,_n3##y,z,v), \
+ I[72] = (img)(_p4##x,_n4##y,z,v), I[73] = (img)(_p3##x,_n4##y,z,v), I[74] = (img)(_p2##x,_n4##y,z,v), I[75] = (img)(_p1##x,_n4##y,z,v), \
+ I[76] = (img)(x,_n4##y,z,v), I[77] = (img)(_n1##x,_n4##y,z,v), I[78] = (img)(_n2##x,_n4##y,z,v), I[79] = (img)(_n3##x,_n4##y,z,v), \
+ I[80] = (img)(_n4##x,_n4##y,z,v)
+
+#define cimg_get2x2x2(img,x,y,z,v,I) \
+ I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v), \
+ I[4] = (img)(x,y,_n1##z,v), I[5] = (img)(_n1##x,y,_n1##z,v), I[6] = (img)(x,_n1##y,_n1##z,v), I[7] = (img)(_n1##x,_n1##y,_n1##z,v)
+
+#define cimg_get3x3x3(img,x,y,z,v,I) \
+ I[0] = (img)(_p1##x,_p1##y,_p1##z,v), I[1] = (img)(x,_p1##y,_p1##z,v), I[2] = (img)(_n1##x,_p1##y,_p1##z,v), \
+ I[3] = (img)(_p1##x,y,_p1##z,v), I[4] = (img)(x,y,_p1##z,v), I[5] = (img)(_n1##x,y,_p1##z,v), \
+ I[6] = (img)(_p1##x,_n1##y,_p1##z,v), I[7] = (img)(x,_n1##y,_p1##z,v), I[8] = (img)(_n1##x,_n1##y,_p1##z,v), \
+ I[9] = (img)(_p1##x,_p1##y,z,v), I[10] = (img)(x,_p1##y,z,v), I[11] = (img)(_n1##x,_p1##y,z,v), \
+ I[12] = (img)(_p1##x,y,z,v), I[13] = (img)(x,y,z,v), I[14] = (img)(_n1##x,y,z,v), \
+ I[15] = (img)(_p1##x,_n1##y,z,v), I[16] = (img)(x,_n1##y,z,v), I[17] = (img)(_n1##x,_n1##y,z,v), \
+ I[18] = (img)(_p1##x,_p1##y,_n1##z,v), I[19] = (img)(x,_p1##y,_n1##z,v), I[20] = (img)(_n1##x,_p1##y,_n1##z,v), \
+ I[21] = (img)(_p1##x,y,_n1##z,v), I[22] = (img)(x,y,_n1##z,v), I[23] = (img)(_n1##x,y,_n1##z,v), \
+ I[24] = (img)(_p1##x,_n1##y,_n1##z,v), I[25] = (img)(x,_n1##y,_n1##z,v), I[26] = (img)(_n1##x,_n1##y,_n1##z,v)
+
+// Define various image loops.
+//
+// These macros generally avoid the use of iterators, but you are not forced to used them !
+//
+#define cimg_for(img,ptr,T_ptr) for (T_ptr *ptr = (img).data + (img).size(); (ptr--)>(img).data; )
+#define cimg_foroff(img,off) for (unsigned int off = 0, _max##off = (unsigned int)(img).size(); off<_max##off; ++off)
+#define cimglist_for(list,l) for (unsigned int l=0; l<(list).size; ++l)
+#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn
+
+#define cimg_for1(bound,i) for (int i = 0; i<(int)(bound); ++i)
+#define cimg_forX(img,x) cimg_for1((img).width,x)
+#define cimg_forY(img,y) cimg_for1((img).height,y)
+#define cimg_forZ(img,z) cimg_for1((img).depth,z)
+#define cimg_forV(img,v) cimg_for1((img).dim,v)
+#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x)
+#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x)
+#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y)
+#define cimg_forXV(img,x,v) cimg_forV(img,v) cimg_forX(img,x)
+#define cimg_forYV(img,y,v) cimg_forV(img,v) cimg_forY(img,y)
+#define cimg_forZV(img,z,v) cimg_forV(img,v) cimg_forZ(img,z)
+#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y)
+#define cimg_forXYV(img,x,y,v) cimg_forV(img,v) cimg_forXY(img,x,y)
+#define cimg_forXZV(img,x,z,v) cimg_forV(img,v) cimg_forXZ(img,x,z)
+#define cimg_forYZV(img,y,z,v) cimg_forV(img,v) cimg_forYZ(img,y,z)
+#define cimg_forXYZV(img,x,y,z,v) cimg_forV(img,v) cimg_forXYZ(img,x,y,z)
+
+#define cimg_for_in1(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound)-1; i<=_max##i; ++i)
+#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img).width,x0,x1,x)
+#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img).height,y0,y1,y)
+#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img).depth,z0,z1,z)
+#define cimg_for_inV(img,v0,v1,v) cimg_for_in1((img).dim,v0,v1,v)
+#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inXV(img,x0,v0,x1,v1,x,v) cimg_for_inV(img,v0,v1,v) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y)
+#define cimg_for_inYV(img,y0,v0,y1,v1,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inY(img,y0,y1,y)
+#define cimg_for_inZV(img,z0,v0,z1,v1,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inZ(img,z0,z1,z)
+#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_inXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_inXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXZ(img,x0,z0,x1,z1,x,z)
+#define cimg_for_inYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inYZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_inXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img).width-1-(n),x)
+#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img).height-1-(n),y)
+#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img).depth-1-(n),z)
+#define cimg_for_insideV(img,v,n) cimg_for_inV(img,n,(img).dim-1-(n),v)
+#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y)
+#define cimg_for_insideXYZ(img,x,y,z,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
+#define cimg_for_insideXYZV(img,x,y,z,v,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
+
+#define cimg_for_out1(boundi,i0,i1,i) \
+ for (int i = (int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1)+1:i)
+#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \
+ for (int j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
+ ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1)+1:i))
+#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \
+ for (int k = 0; k<(int)(boundk); ++k) \
+ for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
+ ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1)+1:i))
+#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \
+ for (int l = 0; l<(int)(boundl); ++l) \
+ for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \
+ for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
+ ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1)+1:i))
+#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img).width,x0,x1,x)
+#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img).height,y0,y1,y)
+#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img).depth,z0,z1,z)
+#define cimg_for_outV(img,v0,v1,v) cimg_for_out1((img).dim,v0,v1,v)
+#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img).width,(img).height,x0,y0,x1,y1,x,y)
+#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img).width,(img).depth,x0,z0,x1,z1,x,z)
+#define cimg_for_outXV(img,x0,v0,x1,v1,x,v) cimg_for_out2((img).width,(img).dim,x0,v0,x1,v1,x,v)
+#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img).height,(img).depth,y0,z0,y1,z1,y,z)
+#define cimg_for_outYV(img,y0,v0,y1,v1,y,v) cimg_for_out2((img).height,(img).dim,y0,v0,y1,v1,y,v)
+#define cimg_for_outZV(img,z0,v0,z1,v1,z,v) cimg_for_out2((img).depth,(img).dim,z0,v0,z1,v1,z,v)
+#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_out3((img).width,(img).height,(img).depth,x0,y0,z0,x1,y1,z1,x,y,z)
+#define cimg_for_outXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_out3((img).width,(img).height,(img).dim,x0,y0,v0,x1,y1,v1,x,y,v)
+#define cimg_for_outXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_out3((img).width,(img).depth,(img).dim,x0,z0,v0,x1,z1,v1,x,z,v)
+#define cimg_for_outYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_out3((img).height,(img).depth,(img).dim,y0,z0,v0,y1,z1,v1,y,z,v)
+#define cimg_for_outXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) \
+ cimg_for_out4((img).width,(img).height,(img).depth,(img).dim,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v)
+#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img).width-1-(n),x)
+#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img).height-1-(n),y)
+#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img).depth-1-(n),z)
+#define cimg_for_borderV(img,v,n) cimg_for_outV(img,n,(img).dim-1-(n),v)
+#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y)
+#define cimg_for_borderXYZ(img,x,y,z,n) cimg_for_outXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
+#define cimg_for_borderXYZV(img,x,y,z,v,n) \
+ cimg_for_outXYZV(img,n,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),(img).dim-1-(n),x,y,z,v)
+
+#define cimg_for_spiralXY(img,x,y) \
+ for (int x = 0, y = 0, _n1##x = 1, _n1##y = (int)((img).width*(img).height); _n1##y; \
+ --_n1##y, _n1##x += (_n1##x>>2)-((!(_n1##x&3)?--y:((_n1##x&3)==1?(img).width-1-++x:((_n1##x&3)==2?(img).height-1-++y:--x))))?0:1)
+
+#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \
+ for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \
+ _dx=(x1)>(x0)?(int)(x1)-(int)(x0):(_sx=-1,(int)(x0)-(int)(x1)), \
+ _dy=(y1)>(y0)?(int)(y1)-(int)(y0):(_sy=-1,(int)(y0)-(int)(y1)), \
+ _counter = _dx, \
+ _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \
+ _counter>=0; \
+ --_counter, x+=_steep? \
+ (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \
+ (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx))
+
+#define cimg_for2(bound,i) \
+ for (int i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ ++i, ++_n1##i)
+#define cimg_for2X(img,x) cimg_for2((img).width,x)
+#define cimg_for2Y(img,y) cimg_for2((img).height,y)
+#define cimg_for2Z(img,z) cimg_for2((img).depth,z)
+#define cimg_for2V(img,v) cimg_for2((img).dim,v)
+#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x)
+#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x)
+#define cimg_for2XV(img,x,v) cimg_for2V(img,v) cimg_for2X(img,x)
+#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y)
+#define cimg_for2YV(img,y,v) cimg_for2V(img,v) cimg_for2Y(img,y)
+#define cimg_for2ZV(img,z,v) cimg_for2V(img,v) cimg_for2Z(img,z)
+#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y)
+#define cimg_for2XZV(img,x,z,v) cimg_for2V(img,v) cimg_for2XZ(img,x,z)
+#define cimg_for2YZV(img,y,z,v) cimg_for2V(img,v) cimg_for2YZ(img,y,z)
+#define cimg_for2XYZV(img,x,y,z,v) cimg_for2V(img,v) cimg_for2XYZ(img,x,y,z)
+
+#define cimg_for_in2(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
+ i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
+ ++i, ++_n1##i)
+#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img).width,x0,x1,x)
+#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img).height,y0,y1,y)
+#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img).depth,z0,z1,z)
+#define cimg_for_in2V(img,v0,v1,v) cimg_for_in2((img).dim,v0,v1,v)
+#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2XV(img,x0,v0,x1,v1,x,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y)
+#define cimg_for_in2YV(img,y0,v0,y1,v1,y,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Y(img,y0,y1,y)
+#define cimg_for_in2ZV(img,z0,v0,z1,v1,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Z(img,z0,z1,z)
+#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in2XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in2YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in2XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ _p1##i = i++, ++_n1##i)
+#define cimg_for3X(img,x) cimg_for3((img).width,x)
+#define cimg_for3Y(img,y) cimg_for3((img).height,y)
+#define cimg_for3Z(img,z) cimg_for3((img).depth,z)
+#define cimg_for3V(img,v) cimg_for3((img).dim,v)
+#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x)
+#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x)
+#define cimg_for3XV(img,x,v) cimg_for3V(img,v) cimg_for3X(img,x)
+#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y)
+#define cimg_for3YV(img,y,v) cimg_for3V(img,v) cimg_for3Y(img,y)
+#define cimg_for3ZV(img,z,v) cimg_for3V(img,v) cimg_for3Z(img,z)
+#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y)
+#define cimg_for3XZV(img,x,z,v) cimg_for3V(img,v) cimg_for3XZ(img,x,z)
+#define cimg_for3YZV(img,y,z,v) cimg_for3V(img,v) cimg_for3YZ(img,y,z)
+#define cimg_for3XYZV(img,x,y,z,v) cimg_for3V(img,v) cimg_for3XYZ(img,x,y,z)
+
+#define cimg_for_in3(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p1##i = i-1<0?0:i-1, \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
+ i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
+ _p1##i = i++, ++_n1##i)
+#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img).width,x0,x1,x)
+#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img).height,y0,y1,y)
+#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img).depth,z0,z1,z)
+#define cimg_for_in3V(img,v0,v1,v) cimg_for_in3((img).dim,v0,v1,v)
+#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3XV(img,x0,v0,x1,v1,x,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y)
+#define cimg_for_in3YV(img,y0,v0,y1,v1,y,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Y(img,y0,y1,y)
+#define cimg_for_in3ZV(img,z0,v0,z1,v1,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Z(img,z0,z1,z)
+#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in3XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in3YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in3XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for4(bound,i) \
+ for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+ _n2##i = 2>=(bound)?(int)(bound)-1:2; \
+ _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
+ _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for4X(img,x) cimg_for4((img).width,x)
+#define cimg_for4Y(img,y) cimg_for4((img).height,y)
+#define cimg_for4Z(img,z) cimg_for4((img).depth,z)
+#define cimg_for4V(img,v) cimg_for4((img).dim,v)
+#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x)
+#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x)
+#define cimg_for4XV(img,x,v) cimg_for4V(img,v) cimg_for4X(img,x)
+#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y)
+#define cimg_for4YV(img,y,v) cimg_for4V(img,v) cimg_for4Y(img,y)
+#define cimg_for4ZV(img,z,v) cimg_for4V(img,v) cimg_for4Z(img,z)
+#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y)
+#define cimg_for4XZV(img,x,z,v) cimg_for4V(img,v) cimg_for4XZ(img,x,z)
+#define cimg_for4YZV(img,y,z,v) cimg_for4V(img,v) cimg_for4YZ(img,y,z)
+#define cimg_for4XYZV(img,x,y,z,v) cimg_for4V(img,v) cimg_for4XYZ(img,x,y,z)
+
+#define cimg_for_in4(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p1##i = i-1<0?0:i-1, \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+ _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
+ i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
+ _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img).width,x0,x1,x)
+#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img).height,y0,y1,y)
+#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img).depth,z0,z1,z)
+#define cimg_for_in4V(img,v0,v1,v) cimg_for_in4((img).dim,v0,v1,v)
+#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4XV(img,x0,v0,x1,v1,x,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y)
+#define cimg_for_in4YV(img,y0,v0,y1,v1,y,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Y(img,y0,y1,y)
+#define cimg_for_in4ZV(img,z0,v0,z1,v1,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Z(img,z0,z1,z)
+#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in4XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in4YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in4XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for5(bound,i) \
+ for (int i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+ _n2##i = 2>=(bound)?(int)(bound)-1:2; \
+ _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for5X(img,x) cimg_for5((img).width,x)
+#define cimg_for5Y(img,y) cimg_for5((img).height,y)
+#define cimg_for5Z(img,z) cimg_for5((img).depth,z)
+#define cimg_for5V(img,v) cimg_for5((img).dim,v)
+#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x)
+#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x)
+#define cimg_for5XV(img,x,v) cimg_for5V(img,v) cimg_for5X(img,x)
+#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y)
+#define cimg_for5YV(img,y,v) cimg_for5V(img,v) cimg_for5Y(img,y)
+#define cimg_for5ZV(img,z,v) cimg_for5V(img,v) cimg_for5Z(img,z)
+#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y)
+#define cimg_for5XZV(img,x,z,v) cimg_for5V(img,v) cimg_for5XZ(img,x,z)
+#define cimg_for5YZV(img,y,z,v) cimg_for5V(img,v) cimg_for5YZ(img,y,z)
+#define cimg_for5XYZV(img,x,y,z,v) cimg_for5V(img,v) cimg_for5XYZ(img,x,y,z)
+
+#define cimg_for_in5(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p2##i = i-2<0?0:i-2, \
+ _p1##i = i-1<0?0:i-1, \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+ _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
+ i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img).width,x0,x1,x)
+#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img).height,y0,y1,y)
+#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img).depth,z0,z1,z)
+#define cimg_for_in5V(img,v0,v1,v) cimg_for_in5((img).dim,v0,v1,v)
+#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5XV(img,x0,v0,x1,v1,x,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y)
+#define cimg_for_in5YV(img,y0,v0,y1,v1,y,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Y(img,y0,y1,y)
+#define cimg_for_in5ZV(img,z0,v0,z1,v1,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Z(img,z0,z1,z)
+#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in5XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in5YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in5XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for6(bound,i) \
+ for (int i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+ _n2##i = 2>=(bound)?(int)(bound)-1:2, \
+ _n3##i = 3>=(bound)?(int)(bound)-1:3; \
+ _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for6X(img,x) cimg_for6((img).width,x)
+#define cimg_for6Y(img,y) cimg_for6((img).height,y)
+#define cimg_for6Z(img,z) cimg_for6((img).depth,z)
+#define cimg_for6V(img,v) cimg_for6((img).dim,v)
+#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x)
+#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x)
+#define cimg_for6XV(img,x,v) cimg_for6V(img,v) cimg_for6X(img,x)
+#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y)
+#define cimg_for6YV(img,y,v) cimg_for6V(img,v) cimg_for6Y(img,y)
+#define cimg_for6ZV(img,z,v) cimg_for6V(img,v) cimg_for6Z(img,z)
+#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y)
+#define cimg_for6XZV(img,x,z,v) cimg_for6V(img,v) cimg_for6XZ(img,x,z)
+#define cimg_for6YZV(img,y,z,v) cimg_for6V(img,v) cimg_for6YZ(img,y,z)
+#define cimg_for6XYZV(img,x,y,z,v) cimg_for6V(img,v) cimg_for6XYZ(img,x,y,z)
+
+#define cimg_for_in6(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p2##i = i-2<0?0:i-2, \
+ _p1##i = i-1<0?0:i-1, \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+ _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+ _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
+ i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img).width,x0,x1,x)
+#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img).height,y0,y1,y)
+#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img).depth,z0,z1,z)
+#define cimg_for_in6V(img,v0,v1,v) cimg_for_in6((img).dim,v0,v1,v)
+#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6XV(img,x0,v0,x1,v1,x,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y)
+#define cimg_for_in6YV(img,y0,v0,y1,v1,y,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Y(img,y0,y1,y)
+#define cimg_for_in6ZV(img,z0,v0,z1,v1,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Z(img,z0,z1,z)
+#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in6XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in6YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in6XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for7(bound,i) \
+ for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+ _n2##i = 2>=(bound)?(int)(bound)-1:2, \
+ _n3##i = 3>=(bound)?(int)(bound)-1:3; \
+ _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for7X(img,x) cimg_for7((img).width,x)
+#define cimg_for7Y(img,y) cimg_for7((img).height,y)
+#define cimg_for7Z(img,z) cimg_for7((img).depth,z)
+#define cimg_for7V(img,v) cimg_for7((img).dim,v)
+#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x)
+#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x)
+#define cimg_for7XV(img,x,v) cimg_for7V(img,v) cimg_for7X(img,x)
+#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y)
+#define cimg_for7YV(img,y,v) cimg_for7V(img,v) cimg_for7Y(img,y)
+#define cimg_for7ZV(img,z,v) cimg_for7V(img,v) cimg_for7Z(img,z)
+#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y)
+#define cimg_for7XZV(img,x,z,v) cimg_for7V(img,v) cimg_for7XZ(img,x,z)
+#define cimg_for7YZV(img,y,z,v) cimg_for7V(img,v) cimg_for7YZ(img,y,z)
+#define cimg_for7XYZV(img,x,y,z,v) cimg_for7V(img,v) cimg_for7XYZ(img,x,y,z)
+
+#define cimg_for_in7(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p3##i = i-3<0?0:i-3, \
+ _p2##i = i-2<0?0:i-2, \
+ _p1##i = i-1<0?0:i-1, \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+ _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+ _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
+ i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img).width,x0,x1,x)
+#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img).height,y0,y1,y)
+#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img).depth,z0,z1,z)
+#define cimg_for_in7V(img,v0,v1,v) cimg_for_in7((img).dim,v0,v1,v)
+#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7XV(img,x0,v0,x1,v1,x,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y)
+#define cimg_for_in7YV(img,y0,v0,y1,v1,y,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Y(img,y0,y1,y)
+#define cimg_for_in7ZV(img,z0,v0,z1,v1,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Z(img,z0,z1,z)
+#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in7XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in7YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in7XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for8(bound,i) \
+ for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+ _n2##i = 2>=(bound)?(int)(bound)-1:2, \
+ _n3##i = 3>=(bound)?(int)(bound)-1:3, \
+ _n4##i = 4>=(bound)?(int)(bound)-1:4; \
+ _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for8X(img,x) cimg_for8((img).width,x)
+#define cimg_for8Y(img,y) cimg_for8((img).height,y)
+#define cimg_for8Z(img,z) cimg_for8((img).depth,z)
+#define cimg_for8V(img,v) cimg_for8((img).dim,v)
+#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x)
+#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x)
+#define cimg_for8XV(img,x,v) cimg_for8V(img,v) cimg_for8X(img,x)
+#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y)
+#define cimg_for8YV(img,y,v) cimg_for8V(img,v) cimg_for8Y(img,y)
+#define cimg_for8ZV(img,z,v) cimg_for8V(img,v) cimg_for8Z(img,z)
+#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y)
+#define cimg_for8XZV(img,x,z,v) cimg_for8V(img,v) cimg_for8XZ(img,x,z)
+#define cimg_for8YZV(img,y,z,v) cimg_for8V(img,v) cimg_for8YZ(img,y,z)
+#define cimg_for8XYZV(img,x,y,z,v) cimg_for8V(img,v) cimg_for8XYZ(img,x,y,z)
+
+#define cimg_for_in8(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p3##i = i-3<0?0:i-3, \
+ _p2##i = i-2<0?0:i-2, \
+ _p1##i = i-1<0?0:i-1, \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+ _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+ _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
+ _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
+ i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img).width,x0,x1,x)
+#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img).height,y0,y1,y)
+#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img).depth,z0,z1,z)
+#define cimg_for_in8V(img,v0,v1,v) cimg_for_in8((img).dim,v0,v1,v)
+#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8XV(img,x0,v0,x1,v1,x,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y)
+#define cimg_for_in8YV(img,y0,v0,y1,v1,y,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Y(img,y0,y1,y)
+#define cimg_for_in8ZV(img,z0,v0,z1,v1,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Z(img,z0,z1,z)
+#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in8XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in8YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in8XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for9(bound,i) \
+ for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(int)(bound)?(int)(bound)-1:1, \
+ _n2##i = 2>=(int)(bound)?(int)(bound)-1:2, \
+ _n3##i = 3>=(int)(bound)?(int)(bound)-1:3, \
+ _n4##i = 4>=(int)(bound)?(int)(bound)-1:4; \
+ _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
+ _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for9X(img,x) cimg_for9((img).width,x)
+#define cimg_for9Y(img,y) cimg_for9((img).height,y)
+#define cimg_for9Z(img,z) cimg_for9((img).depth,z)
+#define cimg_for9V(img,v) cimg_for9((img).dim,v)
+#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x)
+#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x)
+#define cimg_for9XV(img,x,v) cimg_for9V(img,v) cimg_for9X(img,x)
+#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y)
+#define cimg_for9YV(img,y,v) cimg_for9V(img,v) cimg_for9Y(img,y)
+#define cimg_for9ZV(img,z,v) cimg_for9V(img,v) cimg_for9Z(img,z)
+#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y)
+#define cimg_for9XZV(img,x,z,v) cimg_for9V(img,v) cimg_for9XZ(img,x,z)
+#define cimg_for9YZV(img,y,z,v) cimg_for9V(img,v) cimg_for9YZ(img,y,z)
+#define cimg_for9XYZV(img,x,y,z,v) cimg_for9V(img,v) cimg_for9XYZ(img,x,y,z)
+
+#define cimg_for_in9(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p4##i = i-4<0?0:i-4, \
+ _p3##i = i-3<0?0:i-3, \
+ _p2##i = i-2<0?0:i-2, \
+ _p1##i = i-1<0?0:i-1, \
+ _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+ _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+ _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
+ _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
+ i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
+ _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img).width,x0,x1,x)
+#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img).height,y0,y1,y)
+#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img).depth,z0,z1,z)
+#define cimg_for_in9V(img,v0,v1,v) cimg_for_in9((img).dim,v0,v1,v)
+#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9XV(img,x0,v0,x1,v1,x,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y)
+#define cimg_for_in9YV(img,y0,v0,y1,v1,y,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Y(img,y0,y1,y)
+#define cimg_for_in9ZV(img,z0,v0,z1,v1,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Z(img,z0,z1,z)
+#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in9XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in9YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in9XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for2x2(img,x,y,z,v,I) \
+ cimg_for2((img).height,y) for (int x = 0, \
+ _n1##x = (int)( \
+ (I[0] = (img)(0,y,z,v)), \
+ (I[2] = (img)(0,_n1##y,z,v)), \
+ 1>=(img).width?(int)((img).width)-1:1); \
+ (_n1##x<(int)((img).width) && ( \
+ (I[1] = (img)(_n1##x,y,z,v)), \
+ (I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], \
+ I[2] = I[3], \
+ ++x, ++_n1##x)
+
+#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _n1##x = (int)( \
+ (I[0] = (img)(x,y,z,v)), \
+ (I[2] = (img)(x,_n1##y,z,v)), \
+ x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
+ x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
+ (I[1] = (img)(_n1##x,y,z,v)), \
+ (I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], \
+ I[2] = I[3], \
+ ++x, ++_n1##x)
+
+#define cimg_for3x3(img,x,y,z,v,I) \
+ cimg_for3((img).height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[0] = I[1] = (img)(0,_p1##y,z,v)), \
+ (I[3] = I[4] = (img)(0,y,z,v)), \
+ (I[6] = I[7] = (img)(0,_n1##y,z,v)), \
+ 1>=(img).width?(int)((img).width)-1:1); \
+ (_n1##x<(int)((img).width) && ( \
+ (I[2] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[5] = (img)(_n1##x,y,z,v)), \
+ (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = (int)( \
+ (I[0] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[3] = (img)(_p1##x,y,z,v)), \
+ (I[6] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[1] = (img)(x,_p1##y,z,v)), \
+ (I[4] = (img)(x,y,z,v)), \
+ (I[7] = (img)(x,_n1##y,z,v)), \
+ x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
+ x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
+ (I[2] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[5] = (img)(_n1##x,y,z,v)), \
+ (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+#define cimg_for4x4(img,x,y,z,v,I) \
+ cimg_for4((img).height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
+ _n2##x = (int)( \
+ (I[0] = I[1] = (img)(0,_p1##y,z,v)), \
+ (I[4] = I[5] = (img)(0,y,z,v)), \
+ (I[8] = I[9] = (img)(0,_n1##y,z,v)), \
+ (I[12] = I[13] = (img)(0,_n2##y,z,v)), \
+ (I[2] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[6] = (img)(_n1##x,y,z,v)), \
+ (I[10] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[14] = (img)(_n1##x,_n2##y,z,v)), \
+ 2>=(img).width?(int)((img).width)-1:2); \
+ (_n2##x<(int)((img).width) && ( \
+ (I[3] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[7] = (img)(_n2##x,y,z,v)), \
+ (I[11] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], \
+ I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in4((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
+ _n2##x = (int)( \
+ (I[0] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[4] = (img)(_p1##x,y,z,v)), \
+ (I[8] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[12] = (img)(_p1##x,_n2##y,z,v)), \
+ (I[1] = (img)(x,_p1##y,z,v)), \
+ (I[5] = (img)(x,y,z,v)), \
+ (I[9] = (img)(x,_n1##y,z,v)), \
+ (I[13] = (img)(x,_n2##y,z,v)), \
+ (I[2] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[6] = (img)(_n1##x,y,z,v)), \
+ (I[10] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[14] = (img)(_n1##x,_n2##y,z,v)), \
+ x+2>=(int)(img).width?(int)((img).width)-1:x+2); \
+ x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \
+ (I[3] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[7] = (img)(_n2##x,y,z,v)), \
+ (I[11] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], \
+ I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for5x5(img,x,y,z,v,I) \
+ cimg_for5((img).height,y) for (int x = 0, \
+ _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
+ _n2##x = (int)( \
+ (I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \
+ (I[5] = I[6] = I[7] = (img)(0,_p1##y,z,v)), \
+ (I[10] = I[11] = I[12] = (img)(0,y,z,v)), \
+ (I[15] = I[16] = I[17] = (img)(0,_n1##y,z,v)), \
+ (I[20] = I[21] = I[22] = (img)(0,_n2##y,z,v)), \
+ (I[3] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[8] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[13] = (img)(_n1##x,y,z,v)), \
+ (I[18] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[23] = (img)(_n1##x,_n2##y,z,v)), \
+ 2>=(img).width?(int)((img).width)-1:2); \
+ (_n2##x<(int)((img).width) && ( \
+ (I[4] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[9] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[14] = (img)(_n2##x,y,z,v)), \
+ (I[19] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
+ I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
+ I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
+ I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
+ I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in5((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p2##x = x-2<0?0:x-2, \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
+ _n2##x = (int)( \
+ (I[0] = (img)(_p2##x,_p2##y,z,v)), \
+ (I[5] = (img)(_p2##x,_p1##y,z,v)), \
+ (I[10] = (img)(_p2##x,y,z,v)), \
+ (I[15] = (img)(_p2##x,_n1##y,z,v)), \
+ (I[20] = (img)(_p2##x,_n2##y,z,v)), \
+ (I[1] = (img)(_p1##x,_p2##y,z,v)), \
+ (I[6] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[11] = (img)(_p1##x,y,z,v)), \
+ (I[16] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[21] = (img)(_p1##x,_n2##y,z,v)), \
+ (I[2] = (img)(x,_p2##y,z,v)), \
+ (I[7] = (img)(x,_p1##y,z,v)), \
+ (I[12] = (img)(x,y,z,v)), \
+ (I[17] = (img)(x,_n1##y,z,v)), \
+ (I[22] = (img)(x,_n2##y,z,v)), \
+ (I[3] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[8] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[13] = (img)(_n1##x,y,z,v)), \
+ (I[18] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[23] = (img)(_n1##x,_n2##y,z,v)), \
+ x+2>=(int)(img).width?(int)((img).width)-1:x+2); \
+ x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \
+ (I[4] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[9] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[14] = (img)(_n2##x,y,z,v)), \
+ (I[19] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
+ I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
+ I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
+ I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
+ I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for6x6(img,x,y,z,v,I) \
+ cimg_for6((img).height,y) for (int x = 0, \
+ _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
+ _n2##x = 2>=(img).width?(int)((img).width)-1:2, \
+ _n3##x = (int)( \
+ (I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \
+ (I[6] = I[7] = I[8] = (img)(0,_p1##y,z,v)), \
+ (I[12] = I[13] = I[14] = (img)(0,y,z,v)), \
+ (I[18] = I[19] = I[20] = (img)(0,_n1##y,z,v)), \
+ (I[24] = I[25] = I[26] = (img)(0,_n2##y,z,v)), \
+ (I[30] = I[31] = I[32] = (img)(0,_n3##y,z,v)), \
+ (I[3] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[9] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[15] = (img)(_n1##x,y,z,v)), \
+ (I[21] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[27] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[33] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[4] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[10] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[16] = (img)(_n2##x,y,z,v)), \
+ (I[22] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[28] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[34] = (img)(_n2##x,_n3##y,z,v)), \
+ 3>=(img).width?(int)((img).width)-1:3); \
+ (_n3##x<(int)((img).width) && ( \
+ (I[5] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[11] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[17] = (img)(_n3##x,y,z,v)), \
+ (I[23] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[29] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
+ I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in6((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \
+ _p2##x = x-2<0?0:x-2, \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
+ _n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \
+ _n3##x = (int)( \
+ (I[0] = (img)(_p2##x,_p2##y,z,v)), \
+ (I[6] = (img)(_p2##x,_p1##y,z,v)), \
+ (I[12] = (img)(_p2##x,y,z,v)), \
+ (I[18] = (img)(_p2##x,_n1##y,z,v)), \
+ (I[24] = (img)(_p2##x,_n2##y,z,v)), \
+ (I[30] = (img)(_p2##x,_n3##y,z,v)), \
+ (I[1] = (img)(_p1##x,_p2##y,z,v)), \
+ (I[7] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[13] = (img)(_p1##x,y,z,v)), \
+ (I[19] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[25] = (img)(_p1##x,_n2##y,z,v)), \
+ (I[31] = (img)(_p1##x,_n3##y,z,v)), \
+ (I[2] = (img)(x,_p2##y,z,v)), \
+ (I[8] = (img)(x,_p1##y,z,v)), \
+ (I[14] = (img)(x,y,z,v)), \
+ (I[20] = (img)(x,_n1##y,z,v)), \
+ (I[26] = (img)(x,_n2##y,z,v)), \
+ (I[32] = (img)(x,_n3##y,z,v)), \
+ (I[3] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[9] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[15] = (img)(_n1##x,y,z,v)), \
+ (I[21] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[27] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[33] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[4] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[10] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[16] = (img)(_n2##x,y,z,v)), \
+ (I[22] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[28] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[34] = (img)(_n2##x,_n3##y,z,v)), \
+ x+3>=(int)(img).width?(int)((img).width)-1:x+3); \
+ x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \
+ (I[5] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[11] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[17] = (img)(_n3##x,y,z,v)), \
+ (I[23] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[29] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
+ I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for7x7(img,x,y,z,v,I) \
+ cimg_for7((img).height,y) for (int x = 0, \
+ _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
+ _n2##x = 2>=(img).width?(int)((img).width)-1:2, \
+ _n3##x = (int)( \
+ (I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \
+ (I[7] = I[8] = I[9] = I[10] = (img)(0,_p2##y,z,v)), \
+ (I[14] = I[15] = I[16] = I[17] = (img)(0,_p1##y,z,v)), \
+ (I[21] = I[22] = I[23] = I[24] = (img)(0,y,z,v)), \
+ (I[28] = I[29] = I[30] = I[31] = (img)(0,_n1##y,z,v)), \
+ (I[35] = I[36] = I[37] = I[38] = (img)(0,_n2##y,z,v)), \
+ (I[42] = I[43] = I[44] = I[45] = (img)(0,_n3##y,z,v)), \
+ (I[4] = (img)(_n1##x,_p3##y,z,v)), \
+ (I[11] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[18] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[25] = (img)(_n1##x,y,z,v)), \
+ (I[32] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[39] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[46] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[5] = (img)(_n2##x,_p3##y,z,v)), \
+ (I[12] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[19] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[26] = (img)(_n2##x,y,z,v)), \
+ (I[33] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[40] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[47] = (img)(_n2##x,_n3##y,z,v)), \
+ 3>=(img).width?(int)((img).width)-1:3); \
+ (_n3##x<(int)((img).width) && ( \
+ (I[6] = (img)(_n3##x,_p3##y,z,v)), \
+ (I[13] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[20] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[27] = (img)(_n3##x,y,z,v)), \
+ (I[34] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[41] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
+ I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
+ I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
+ I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
+ I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
+ I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
+ I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in7((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p3##x = x-3<0?0:x-3, \
+ _p2##x = x-2<0?0:x-2, \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
+ _n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \
+ _n3##x = (int)( \
+ (I[0] = (img)(_p3##x,_p3##y,z,v)), \
+ (I[7] = (img)(_p3##x,_p2##y,z,v)), \
+ (I[14] = (img)(_p3##x,_p1##y,z,v)), \
+ (I[21] = (img)(_p3##x,y,z,v)), \
+ (I[28] = (img)(_p3##x,_n1##y,z,v)), \
+ (I[35] = (img)(_p3##x,_n2##y,z,v)), \
+ (I[42] = (img)(_p3##x,_n3##y,z,v)), \
+ (I[1] = (img)(_p2##x,_p3##y,z,v)), \
+ (I[8] = (img)(_p2##x,_p2##y,z,v)), \
+ (I[15] = (img)(_p2##x,_p1##y,z,v)), \
+ (I[22] = (img)(_p2##x,y,z,v)), \
+ (I[29] = (img)(_p2##x,_n1##y,z,v)), \
+ (I[36] = (img)(_p2##x,_n2##y,z,v)), \
+ (I[43] = (img)(_p2##x,_n3##y,z,v)), \
+ (I[2] = (img)(_p1##x,_p3##y,z,v)), \
+ (I[9] = (img)(_p1##x,_p2##y,z,v)), \
+ (I[16] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[23] = (img)(_p1##x,y,z,v)), \
+ (I[30] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[37] = (img)(_p1##x,_n2##y,z,v)), \
+ (I[44] = (img)(_p1##x,_n3##y,z,v)), \
+ (I[3] = (img)(x,_p3##y,z,v)), \
+ (I[10] = (img)(x,_p2##y,z,v)), \
+ (I[17] = (img)(x,_p1##y,z,v)), \
+ (I[24] = (img)(x,y,z,v)), \
+ (I[31] = (img)(x,_n1##y,z,v)), \
+ (I[38] = (img)(x,_n2##y,z,v)), \
+ (I[45] = (img)(x,_n3##y,z,v)), \
+ (I[4] = (img)(_n1##x,_p3##y,z,v)), \
+ (I[11] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[18] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[25] = (img)(_n1##x,y,z,v)), \
+ (I[32] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[39] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[46] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[5] = (img)(_n2##x,_p3##y,z,v)), \
+ (I[12] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[19] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[26] = (img)(_n2##x,y,z,v)), \
+ (I[33] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[40] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[47] = (img)(_n2##x,_n3##y,z,v)), \
+ x+3>=(int)(img).width?(int)((img).width)-1:x+3); \
+ x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \
+ (I[6] = (img)(_n3##x,_p3##y,z,v)), \
+ (I[13] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[20] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[27] = (img)(_n3##x,y,z,v)), \
+ (I[34] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[41] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
+ I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
+ I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
+ I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
+ I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
+ I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
+ I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for8x8(img,x,y,z,v,I) \
+ cimg_for8((img).height,y) for (int x = 0, \
+ _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=((img).width)?(int)((img).width)-1:1, \
+ _n2##x = 2>=((img).width)?(int)((img).width)-1:2, \
+ _n3##x = 3>=((img).width)?(int)((img).width)-1:3, \
+ _n4##x = (int)( \
+ (I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \
+ (I[8] = I[9] = I[10] = I[11] = (img)(0,_p2##y,z,v)), \
+ (I[16] = I[17] = I[18] = I[19] = (img)(0,_p1##y,z,v)), \
+ (I[24] = I[25] = I[26] = I[27] = (img)(0,y,z,v)), \
+ (I[32] = I[33] = I[34] = I[35] = (img)(0,_n1##y,z,v)), \
+ (I[40] = I[41] = I[42] = I[43] = (img)(0,_n2##y,z,v)), \
+ (I[48] = I[49] = I[50] = I[51] = (img)(0,_n3##y,z,v)), \
+ (I[56] = I[57] = I[58] = I[59] = (img)(0,_n4##y,z,v)), \
+ (I[4] = (img)(_n1##x,_p3##y,z,v)), \
+ (I[12] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[20] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[28] = (img)(_n1##x,y,z,v)), \
+ (I[36] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[44] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[52] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[60] = (img)(_n1##x,_n4##y,z,v)), \
+ (I[5] = (img)(_n2##x,_p3##y,z,v)), \
+ (I[13] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[21] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[29] = (img)(_n2##x,y,z,v)), \
+ (I[37] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[45] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[53] = (img)(_n2##x,_n3##y,z,v)), \
+ (I[61] = (img)(_n2##x,_n4##y,z,v)), \
+ (I[6] = (img)(_n3##x,_p3##y,z,v)), \
+ (I[14] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[22] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[30] = (img)(_n3##x,y,z,v)), \
+ (I[38] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[46] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[54] = (img)(_n3##x,_n3##y,z,v)), \
+ (I[62] = (img)(_n3##x,_n4##y,z,v)), \
+ 4>=((img).width)?(int)((img).width)-1:4); \
+ (_n4##x<(int)((img).width) && ( \
+ (I[7] = (img)(_n4##x,_p3##y,z,v)), \
+ (I[15] = (img)(_n4##x,_p2##y,z,v)), \
+ (I[23] = (img)(_n4##x,_p1##y,z,v)), \
+ (I[31] = (img)(_n4##x,y,z,v)), \
+ (I[39] = (img)(_n4##x,_n1##y,z,v)), \
+ (I[47] = (img)(_n4##x,_n2##y,z,v)), \
+ (I[55] = (img)(_n4##x,_n3##y,z,v)), \
+ (I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
+ I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
+ I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
+ I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
+ I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in8((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p3##x = x-3<0?0:x-3, \
+ _p2##x = x-2<0?0:x-2, \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \
+ _n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \
+ _n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \
+ _n4##x = (int)( \
+ (I[0] = (img)(_p3##x,_p3##y,z,v)), \
+ (I[8] = (img)(_p3##x,_p2##y,z,v)), \
+ (I[16] = (img)(_p3##x,_p1##y,z,v)), \
+ (I[24] = (img)(_p3##x,y,z,v)), \
+ (I[32] = (img)(_p3##x,_n1##y,z,v)), \
+ (I[40] = (img)(_p3##x,_n2##y,z,v)), \
+ (I[48] = (img)(_p3##x,_n3##y,z,v)), \
+ (I[56] = (img)(_p3##x,_n4##y,z,v)), \
+ (I[1] = (img)(_p2##x,_p3##y,z,v)), \
+ (I[9] = (img)(_p2##x,_p2##y,z,v)), \
+ (I[17] = (img)(_p2##x,_p1##y,z,v)), \
+ (I[25] = (img)(_p2##x,y,z,v)), \
+ (I[33] = (img)(_p2##x,_n1##y,z,v)), \
+ (I[41] = (img)(_p2##x,_n2##y,z,v)), \
+ (I[49] = (img)(_p2##x,_n3##y,z,v)), \
+ (I[57] = (img)(_p2##x,_n4##y,z,v)), \
+ (I[2] = (img)(_p1##x,_p3##y,z,v)), \
+ (I[10] = (img)(_p1##x,_p2##y,z,v)), \
+ (I[18] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[26] = (img)(_p1##x,y,z,v)), \
+ (I[34] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[42] = (img)(_p1##x,_n2##y,z,v)), \
+ (I[50] = (img)(_p1##x,_n3##y,z,v)), \
+ (I[58] = (img)(_p1##x,_n4##y,z,v)), \
+ (I[3] = (img)(x,_p3##y,z,v)), \
+ (I[11] = (img)(x,_p2##y,z,v)), \
+ (I[19] = (img)(x,_p1##y,z,v)), \
+ (I[27] = (img)(x,y,z,v)), \
+ (I[35] = (img)(x,_n1##y,z,v)), \
+ (I[43] = (img)(x,_n2##y,z,v)), \
+ (I[51] = (img)(x,_n3##y,z,v)), \
+ (I[59] = (img)(x,_n4##y,z,v)), \
+ (I[4] = (img)(_n1##x,_p3##y,z,v)), \
+ (I[12] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[20] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[28] = (img)(_n1##x,y,z,v)), \
+ (I[36] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[44] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[52] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[60] = (img)(_n1##x,_n4##y,z,v)), \
+ (I[5] = (img)(_n2##x,_p3##y,z,v)), \
+ (I[13] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[21] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[29] = (img)(_n2##x,y,z,v)), \
+ (I[37] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[45] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[53] = (img)(_n2##x,_n3##y,z,v)), \
+ (I[61] = (img)(_n2##x,_n4##y,z,v)), \
+ (I[6] = (img)(_n3##x,_p3##y,z,v)), \
+ (I[14] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[22] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[30] = (img)(_n3##x,y,z,v)), \
+ (I[38] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[46] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[54] = (img)(_n3##x,_n3##y,z,v)), \
+ (I[62] = (img)(_n3##x,_n4##y,z,v)), \
+ x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \
+ x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \
+ (I[7] = (img)(_n4##x,_p3##y,z,v)), \
+ (I[15] = (img)(_n4##x,_p2##y,z,v)), \
+ (I[23] = (img)(_n4##x,_p1##y,z,v)), \
+ (I[31] = (img)(_n4##x,y,z,v)), \
+ (I[39] = (img)(_n4##x,_n1##y,z,v)), \
+ (I[47] = (img)(_n4##x,_n2##y,z,v)), \
+ (I[55] = (img)(_n4##x,_n3##y,z,v)), \
+ (I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
+ I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
+ I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
+ I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
+ I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for9x9(img,x,y,z,v,I) \
+ cimg_for9((img).height,y) for (int x = 0, \
+ _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=((img).width)?(int)((img).width)-1:1, \
+ _n2##x = 2>=((img).width)?(int)((img).width)-1:2, \
+ _n3##x = 3>=((img).width)?(int)((img).width)-1:3, \
+ _n4##x = (int)( \
+ (I[0] = I[1] = I[2] = I[3] = I[4] = (img)(0,_p4##y,z,v)), \
+ (I[9] = I[10] = I[11] = I[12] = I[13] = (img)(0,_p3##y,z,v)), \
+ (I[18] = I[19] = I[20] = I[21] = I[22] = (img)(0,_p2##y,z,v)), \
+ (I[27] = I[28] = I[29] = I[30] = I[31] = (img)(0,_p1##y,z,v)), \
+ (I[36] = I[37] = I[38] = I[39] = I[40] = (img)(0,y,z,v)), \
+ (I[45] = I[46] = I[47] = I[48] = I[49] = (img)(0,_n1##y,z,v)), \
+ (I[54] = I[55] = I[56] = I[57] = I[58] = (img)(0,_n2##y,z,v)), \
+ (I[63] = I[64] = I[65] = I[66] = I[67] = (img)(0,_n3##y,z,v)), \
+ (I[72] = I[73] = I[74] = I[75] = I[76] = (img)(0,_n4##y,z,v)), \
+ (I[5] = (img)(_n1##x,_p4##y,z,v)), \
+ (I[14] = (img)(_n1##x,_p3##y,z,v)), \
+ (I[23] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[32] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[41] = (img)(_n1##x,y,z,v)), \
+ (I[50] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[59] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[68] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[77] = (img)(_n1##x,_n4##y,z,v)), \
+ (I[6] = (img)(_n2##x,_p4##y,z,v)), \
+ (I[15] = (img)(_n2##x,_p3##y,z,v)), \
+ (I[24] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[33] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[42] = (img)(_n2##x,y,z,v)), \
+ (I[51] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[60] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[69] = (img)(_n2##x,_n3##y,z,v)), \
+ (I[78] = (img)(_n2##x,_n4##y,z,v)), \
+ (I[7] = (img)(_n3##x,_p4##y,z,v)), \
+ (I[16] = (img)(_n3##x,_p3##y,z,v)), \
+ (I[25] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[34] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[43] = (img)(_n3##x,y,z,v)), \
+ (I[52] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[61] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[70] = (img)(_n3##x,_n3##y,z,v)), \
+ (I[79] = (img)(_n3##x,_n4##y,z,v)), \
+ 4>=((img).width)?(int)((img).width)-1:4); \
+ (_n4##x<(int)((img).width) && ( \
+ (I[8] = (img)(_n4##x,_p4##y,z,v)), \
+ (I[17] = (img)(_n4##x,_p3##y,z,v)), \
+ (I[26] = (img)(_n4##x,_p2##y,z,v)), \
+ (I[35] = (img)(_n4##x,_p1##y,z,v)), \
+ (I[44] = (img)(_n4##x,y,z,v)), \
+ (I[53] = (img)(_n4##x,_n1##y,z,v)), \
+ (I[62] = (img)(_n4##x,_n2##y,z,v)), \
+ (I[71] = (img)(_n4##x,_n3##y,z,v)), \
+ (I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
+ I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+ I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
+ I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
+ I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
+ I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
+ I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
+ _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,v,I) \
+ cimg_for_in9((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p4##x = x-4<0?0:x-4, \
+ _p3##x = x-3<0?0:x-3, \
+ _p2##x = x-2<0?0:x-2, \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \
+ _n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \
+ _n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \
+ _n4##x = (int)( \
+ (I[0] = (img)(_p4##x,_p4##y,z,v)), \
+ (I[9] = (img)(_p4##x,_p3##y,z,v)), \
+ (I[18] = (img)(_p4##x,_p2##y,z,v)), \
+ (I[27] = (img)(_p4##x,_p1##y,z,v)), \
+ (I[36] = (img)(_p4##x,y,z,v)), \
+ (I[45] = (img)(_p4##x,_n1##y,z,v)), \
+ (I[54] = (img)(_p4##x,_n2##y,z,v)), \
+ (I[63] = (img)(_p4##x,_n3##y,z,v)), \
+ (I[72] = (img)(_p4##x,_n4##y,z,v)), \
+ (I[1] = (img)(_p3##x,_p4##y,z,v)), \
+ (I[10] = (img)(_p3##x,_p3##y,z,v)), \
+ (I[19] = (img)(_p3##x,_p2##y,z,v)), \
+ (I[28] = (img)(_p3##x,_p1##y,z,v)), \
+ (I[37] = (img)(_p3##x,y,z,v)), \
+ (I[46] = (img)(_p3##x,_n1##y,z,v)), \
+ (I[55] = (img)(_p3##x,_n2##y,z,v)), \
+ (I[64] = (img)(_p3##x,_n3##y,z,v)), \
+ (I[73] = (img)(_p3##x,_n4##y,z,v)), \
+ (I[2] = (img)(_p2##x,_p4##y,z,v)), \
+ (I[11] = (img)(_p2##x,_p3##y,z,v)), \
+ (I[20] = (img)(_p2##x,_p2##y,z,v)), \
+ (I[29] = (img)(_p2##x,_p1##y,z,v)), \
+ (I[38] = (img)(_p2##x,y,z,v)), \
+ (I[47] = (img)(_p2##x,_n1##y,z,v)), \
+ (I[56] = (img)(_p2##x,_n2##y,z,v)), \
+ (I[65] = (img)(_p2##x,_n3##y,z,v)), \
+ (I[74] = (img)(_p2##x,_n4##y,z,v)), \
+ (I[3] = (img)(_p1##x,_p4##y,z,v)), \
+ (I[12] = (img)(_p1##x,_p3##y,z,v)), \
+ (I[21] = (img)(_p1##x,_p2##y,z,v)), \
+ (I[30] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[39] = (img)(_p1##x,y,z,v)), \
+ (I[48] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[57] = (img)(_p1##x,_n2##y,z,v)), \
+ (I[66] = (img)(_p1##x,_n3##y,z,v)), \
+ (I[75] = (img)(_p1##x,_n4##y,z,v)), \
+ (I[4] = (img)(x,_p4##y,z,v)), \
+ (I[13] = (img)(x,_p3##y,z,v)), \
+ (I[22] = (img)(x,_p2##y,z,v)), \
+ (I[31] = (img)(x,_p1##y,z,v)), \
+ (I[40] = (img)(x,y,z,v)), \
+ (I[49] = (img)(x,_n1##y,z,v)), \
+ (I[58] = (img)(x,_n2##y,z,v)), \
+ (I[67] = (img)(x,_n3##y,z,v)), \
+ (I[76] = (img)(x,_n4##y,z,v)), \
+ (I[5] = (img)(_n1##x,_p4##y,z,v)), \
+ (I[14] = (img)(_n1##x,_p3##y,z,v)), \
+ (I[23] = (img)(_n1##x,_p2##y,z,v)), \
+ (I[32] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[41] = (img)(_n1##x,y,z,v)), \
+ (I[50] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[59] = (img)(_n1##x,_n2##y,z,v)), \
+ (I[68] = (img)(_n1##x,_n3##y,z,v)), \
+ (I[77] = (img)(_n1##x,_n4##y,z,v)), \
+ (I[6] = (img)(_n2##x,_p4##y,z,v)), \
+ (I[15] = (img)(_n2##x,_p3##y,z,v)), \
+ (I[24] = (img)(_n2##x,_p2##y,z,v)), \
+ (I[33] = (img)(_n2##x,_p1##y,z,v)), \
+ (I[42] = (img)(_n2##x,y,z,v)), \
+ (I[51] = (img)(_n2##x,_n1##y,z,v)), \
+ (I[60] = (img)(_n2##x,_n2##y,z,v)), \
+ (I[69] = (img)(_n2##x,_n3##y,z,v)), \
+ (I[78] = (img)(_n2##x,_n4##y,z,v)), \
+ (I[7] = (img)(_n3##x,_p4##y,z,v)), \
+ (I[16] = (img)(_n3##x,_p3##y,z,v)), \
+ (I[25] = (img)(_n3##x,_p2##y,z,v)), \
+ (I[34] = (img)(_n3##x,_p1##y,z,v)), \
+ (I[43] = (img)(_n3##x,y,z,v)), \
+ (I[52] = (img)(_n3##x,_n1##y,z,v)), \
+ (I[61] = (img)(_n3##x,_n2##y,z,v)), \
+ (I[70] = (img)(_n3##x,_n3##y,z,v)), \
+ (I[79] = (img)(_n3##x,_n4##y,z,v)), \
+ x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \
+ x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \
+ (I[8] = (img)(_n4##x,_p4##y,z,v)), \
+ (I[17] = (img)(_n4##x,_p3##y,z,v)), \
+ (I[26] = (img)(_n4##x,_p2##y,z,v)), \
+ (I[35] = (img)(_n4##x,_p1##y,z,v)), \
+ (I[44] = (img)(_n4##x,y,z,v)), \
+ (I[53] = (img)(_n4##x,_n1##y,z,v)), \
+ (I[62] = (img)(_n4##x,_n2##y,z,v)), \
+ (I[71] = (img)(_n4##x,_n3##y,z,v)), \
+ (I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
+ I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+ I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
+ I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
+ I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
+ I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
+ I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
+ _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for2x2x2(img,x,y,z,v,I) \
+ cimg_for2((img).depth,z) cimg_for2((img).height,y) for (int x = 0, \
+ _n1##x = (int)( \
+ (I[0] = (img)(0,y,z,v)), \
+ (I[2] = (img)(0,_n1##y,z,v)), \
+ (I[4] = (img)(0,y,_n1##z,v)), \
+ (I[6] = (img)(0,_n1##y,_n1##z,v)), \
+ 1>=(img).width?(int)((img).width)-1:1); \
+ (_n1##x<(int)((img).width) && ( \
+ (I[1] = (img)(_n1##x,y,z,v)), \
+ (I[3] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[5] = (img)(_n1##x,y,_n1##z,v)), \
+ (I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
+ ++x, ++_n1##x)
+
+#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \
+ cimg_for_in2((img).depth,z0,z1,z) cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _n1##x = (int)( \
+ (I[0] = (img)(x,y,z,v)), \
+ (I[2] = (img)(x,_n1##y,z,v)), \
+ (I[4] = (img)(x,y,_n1##z,v)), \
+ (I[6] = (img)(x,_n1##y,_n1##z,v)), \
+ x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
+ x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
+ (I[1] = (img)(_n1##x,y,z,v)), \
+ (I[3] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[5] = (img)(_n1##x,y,_n1##z,v)), \
+ (I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
+ ++x, ++_n1##x)
+
+#define cimg_for3x3x3(img,x,y,z,v,I) \
+ cimg_for3((img).depth,z) cimg_for3((img).height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[0] = I[1] = (img)(0,_p1##y,_p1##z,v)), \
+ (I[3] = I[4] = (img)(0,y,_p1##z,v)), \
+ (I[6] = I[7] = (img)(0,_n1##y,_p1##z,v)), \
+ (I[9] = I[10] = (img)(0,_p1##y,z,v)), \
+ (I[12] = I[13] = (img)(0,y,z,v)), \
+ (I[15] = I[16] = (img)(0,_n1##y,z,v)), \
+ (I[18] = I[19] = (img)(0,_p1##y,_n1##z,v)), \
+ (I[21] = I[22] = (img)(0,y,_n1##z,v)), \
+ (I[24] = I[25] = (img)(0,_n1##y,_n1##z,v)), \
+ 1>=(img).width?(int)((img).width)-1:1); \
+ (_n1##x<(int)((img).width) && ( \
+ (I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \
+ (I[5] = (img)(_n1##x,y,_p1##z,v)), \
+ (I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \
+ (I[11] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[14] = (img)(_n1##x,y,z,v)), \
+ (I[17] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \
+ (I[23] = (img)(_n1##x,y,_n1##z,v)), \
+ (I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
+ _p1##x = x++, ++_n1##x)
+
+#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \
+ cimg_for_in3((img).depth,z0,z1,z) cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p1##x = x-1<0?0:x-1, \
+ _n1##x = (int)( \
+ (I[0] = (img)(_p1##x,_p1##y,_p1##z,v)), \
+ (I[3] = (img)(_p1##x,y,_p1##z,v)), \
+ (I[6] = (img)(_p1##x,_n1##y,_p1##z,v)), \
+ (I[9] = (img)(_p1##x,_p1##y,z,v)), \
+ (I[12] = (img)(_p1##x,y,z,v)), \
+ (I[15] = (img)(_p1##x,_n1##y,z,v)), \
+ (I[18] = (img)(_p1##x,_p1##y,_n1##z,v)), \
+ (I[21] = (img)(_p1##x,y,_n1##z,v)), \
+ (I[24] = (img)(_p1##x,_n1##y,_n1##z,v)), \
+ (I[1] = (img)(x,_p1##y,_p1##z,v)), \
+ (I[4] = (img)(x,y,_p1##z,v)), \
+ (I[7] = (img)(x,_n1##y,_p1##z,v)), \
+ (I[10] = (img)(x,_p1##y,z,v)), \
+ (I[13] = (img)(x,y,z,v)), \
+ (I[16] = (img)(x,_n1##y,z,v)), \
+ (I[19] = (img)(x,_p1##y,_n1##z,v)), \
+ (I[22] = (img)(x,y,_n1##z,v)), \
+ (I[25] = (img)(x,_n1##y,_n1##z,v)), \
+ x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
+ x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
+ (I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \
+ (I[5] = (img)(_n1##x,y,_p1##z,v)), \
+ (I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \
+ (I[11] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[14] = (img)(_n1##x,y,z,v)), \
+ (I[17] = (img)(_n1##x,_n1##y,z,v)), \
+ (I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \
+ (I[23] = (img)(_n1##x,y,_n1##z,v)), \
+ (I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
+ _p1##x = x++, ++_n1##x)
+
+/*------------------------------------------------
+ #
+ #
+ # Definition of the cimg_library:: namespace
+ #
+ #
+ -------------------------------------------------*/
+//! This namespace encompasses all classes and functions of the %CImg library.
+/**
+ This namespace is defined to avoid functions and class names collisions
+ that could happen with the include of other C++ header files.
+ Anyway, it should not happen often and you should reasonnably start most of your
+ %CImg-based programs with
+ \code
+ #include "CImg.h"
+ using namespace cimg_library;
+ \endcode
+ to simplify the declaration of %CImg Library variables afterwards.
+**/
+namespace cimg_library {
+
+ // Declare the only four classes of the CImg Library.
+ //
+ template<typename T=float> struct CImg;
+ template<typename T=float> struct CImgList;
+ struct CImgDisplay;
+ struct CImgException;
+
+ // (Pre)declare the cimg namespace.
+ // This is not the complete namespace declaration. It only contains some
+ // necessary stuffs to ensure a correct declaration order of classes and functions
+ // defined afterwards.
+ //
+ namespace cimg {
+
+#ifdef cimg_use_vt100
+ const char t_normal[] = { 0x1b,'[','0',';','0',';','0','m','\0' };
+ const char t_red[] = { 0x1b,'[','4',';','3','1',';','5','9','m','\0' };
+ const char t_bold[] = { 0x1b,'[','1','m','\0' };
+ const char t_purple[] = { 0x1b,'[','0',';','3','5',';','5','9','m','\0' };
+ const char t_green[] = { 0x1b,'[','0',';','3','2',';','5','9','m','\0' };
+#else
+ const char t_normal[] = { '\0' };
+ const char *const t_red = cimg::t_normal, *const t_bold = cimg::t_normal,
+ *const t_purple = cimg::t_normal, *const t_green = cimg::t_normal;
+#endif
+
+ inline void info();
+
+ //! Get/set the current CImg exception mode.
+ /**
+ The way error messages are handled by CImg can be changed dynamically, using this function.
+ Possible values are :
+ - 0 to hide debug messages (quiet mode, but exceptions are still thrown).
+ - 1 to display debug messages on standard error (console).
+ - 2 to display debug messages in modal windows (default behavior).
+ - 3 to do as 1 + add extra warnings (may slow down the code !).
+ - 4 to do as 2 + add extra warnings (may slow down the code !).
+ **/
+ inline unsigned int& exception_mode() { static unsigned int mode = cimg_debug; return mode; }
+
+ inline int dialog(const char *title, const char *msg, const char *button1_txt="OK",
+ const char *button2_txt=0, const char *button3_txt=0,
+ const char *button4_txt=0, const char *button5_txt=0,
+ const char *button6_txt=0, const bool centering=false);
+ }
+
+ /*----------------------------------------------
+ #
+ # Definition of the CImgException structures
+ #
+ ----------------------------------------------*/
+ //! Instances of this class are thrown when errors occur during a %CImg library function call.
+ /**
+ \section ex1 Overview
+
+ CImgException is the base class of %CImg exceptions.
+ Exceptions are thrown by the %CImg Library when an error occured in a %CImg library function call.
+ CImgException is seldom thrown itself. Children classes that specify the kind of error encountered
+ are generally used instead. These sub-classes are :
+
+ - \b CImgInstanceException : Thrown when the instance associated to the called %CImg function is not
+ correctly defined. Generally, this exception is thrown when one tries to process \a empty images. The example
+ below will throw a \a CImgInstanceException.
+ \code
+ CImg<float> img; // Construct an empty image.
+ img.blur(10); // Try to blur the image.
+ \endcode
+
+ - \b CImgArgumentException : Thrown when one of the arguments given to the called %CImg function is not correct.
+ Generally, this exception is thrown when arguments passed to the function are outside an admissible range of values.
+ The example below will throw a \a CImgArgumentException.
+ \code
+ CImg<float> img(100,100,1,3); // Define a 100x100 color image with float pixels.
+ img = 0; // Try to fill pixels from the 0 pointer (invalid argument to operator=() ).
+ \endcode
+
+ - \b CImgIOException : Thrown when an error occured when trying to load or save image files.
+ The example below will throw a \a CImgIOException.
+ \code
+ CImg<float> img("file_doesnt_exist.jpg"); // Try to load a file that doesn't exist.
+ \endcode
+
+ - \b CImgDisplayException : Thrown when an error occured when trying to display an image in a window.
+ This exception is thrown when image display request cannot be satisfied.
+
+ The parent class CImgException may be thrown itself when errors that cannot be classified in one of
+ the above type occur. It is recommended not to throw CImgExceptions yourself, since there are normally
+ reserved to %CImg Library functions.
+ \b CImgInstanceException, \b CImgArgumentException, \b CImgIOException and \b CImgDisplayException are simple
+ subclasses of CImgException and are thus not detailled more in this reference documentation.
+
+ \section ex2 Exception handling
+
+ When an error occurs, the %CImg Library first displays the error in a modal window.
+ Then, it throws an instance of the corresponding exception class, generally leading the program to stop
+ (this is the default behavior).
+ You can bypass this default behavior by handling the exceptions yourself,
+ using a code block <tt>try { ... } catch() { ... }</tt>.
+ In this case, you can avoid the apparition of the modal window, by
+ defining the environment variable <tt>cimg_debug</tt> to 0 before including the %CImg header file.
+ The example below shows how to cleanly handle %CImg Library exceptions :
+ \code
+ #define cimg_debug 0 // Disable modal window in CImg exceptions.
+ #define "CImg.h"
+ int main() {
+ try {
+ ...; // Here, do what you want.
+ }
+ catch (CImgInstanceException &e) {
+ std::fprintf(stderr,"CImg Library Error : %s",e.message); // Display your own error message
+ ... // Do what you want now.
+ }
+ }
+ \endcode
+ **/
+ struct CImgException {
+#define _cimg_exception_err(etype,disp_flag) \
+ cimg_std::va_list ap; va_start(ap,format); cimg_std::vsprintf(message,format,ap); va_end(ap); \
+ switch (cimg::exception_mode()) { \
+ case 0 : break; \
+ case 2 : case 4 : try { cimg::dialog(etype,message,"Abort"); } catch (CImgException&) { \
+ cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \
+ } break; \
+ default : cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \
+ } \
+ if (cimg::exception_mode()>=3) cimg_library::cimg::info();
+
+ char message[1024]; //!< Message associated with the error that thrown the exception.
+ CImgException() { message[0]='\0'; }
+ CImgException(const char *format, ...) { _cimg_exception_err("CImgException",true); }
+ };
+
+ // The \ref CImgInstanceException class is used to throw an exception related
+ // to a non suitable instance encountered in a library function call.
+ struct CImgInstanceException: public CImgException {
+ CImgInstanceException(const char *format, ...) { _cimg_exception_err("CImgInstanceException",true); }
+ };
+
+ // The \ref CImgArgumentException class is used to throw an exception related
+ // to invalid arguments encountered in a library function call.
+ struct CImgArgumentException: public CImgException {
+ CImgArgumentException(const char *format, ...) { _cimg_exception_err("CImgArgumentException",true); }
+ };
+
+ // The \ref CImgIOException class is used to throw an exception related
+ // to Input/Output file problems encountered in a library function call.
+ struct CImgIOException: public CImgException {
+ CImgIOException(const char *format, ...) { _cimg_exception_err("CImgIOException",true); }
+ };
+
+ // The CImgDisplayException class is used to throw an exception related to display problems
+ // encountered in a library function call.
+ struct CImgDisplayException: public CImgException {
+ CImgDisplayException(const char *format, ...) { _cimg_exception_err("CImgDisplayException",false); }
+ };
+
+ // The CImgWarningException class is used to throw an exception for warnings
+ // encountered in a library function call.
+ struct CImgWarningException: public CImgException {
+ CImgWarningException(const char *format, ...) { _cimg_exception_err("CImgWarningException",false); }
+ };
+
+ /*-------------------------------------
+ #
+ # Definition of the namespace 'cimg'
+ #
+ --------------------------------------*/
+ //! Namespace that encompasses \a low-level functions and variables of the %CImg Library.
+ /**
+ Most of the functions and variables within this namespace are used by the library for low-level processing.
+ Nevertheless, documented variables and functions of this namespace may be used safely in your own source code.
+
+ \warning Never write <tt>using namespace cimg_library::cimg;</tt> in your source code, since a lot of functions of the
+ <tt>cimg::</tt> namespace have prototypes similar to standard C functions that could defined in the global namespace <tt>::</tt>.
+ **/
+ namespace cimg {
+
+ // Define the traits that will be used to determine the best data type to work with.
+ //
+ template<typename T> struct type {
+ static const char* string() {
+ static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24",
+ "unknown32", "unknown40", "unknown48", "unknown56",
+ "unknown64", "unknown72", "unknown80", "unknown88",
+ "unknown96", "unknown104", "unknown112", "unknown120",
+ "unknown128" };
+ return s[(sizeof(T)<17)?sizeof(T):0];
+ }
+ static bool is_float() { return false; }
+ static T min() { return (T)-1>0?(T)0:(T)-1<<(8*sizeof(T)-1); }
+ static T max() { return (T)-1>0?(T)-1:~((T)-1<<(8*sizeof(T)-1)); }
+ static const char* format() { return "%s"; }
+ static const char* format(const T val) { static const char *s = "unknown"; return s; }
+ };
+
+ template<> struct type<bool> {
+ static const char* string() { static const char *const s = "bool"; return s; }
+ static bool is_float() { return false; }
+ static bool min() { return false; }
+ static bool max() { return true; }
+ static const char* format() { return "%s"; }
+ static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; }
+ };
+
+ template<> struct type<unsigned char> {
+ static const char* string() { static const char *const s = "unsigned char"; return s; }
+ static bool is_float() { return false; }
+ static unsigned char min() { return 0; }
+ static unsigned char max() { return (unsigned char)~0U; }
+ static const char* format() { return "%u"; }
+ static unsigned int format(const unsigned char val) { return (unsigned int)val; }
+ };
+
+ template<> struct type<char> {
+ static const char* string() { static const char *const s = "char"; return s; }
+ static bool is_float() { return false; }
+ static char min() { return (char)(-1L<<(8*sizeof(char)-1)); }
+ static char max() { return ~((char)(-1L<<(8*sizeof(char)-1))); }
+ static const char* format() { return "%d"; }
+ static int format(const char val) { return (int)val; }
+ };
+
+ template<> struct type<signed char> {
+ static const char* string() { static const char *const s = "signed char"; return s; }
+ static bool is_float() { return false; }
+ static signed char min() { return (signed char)(-1L<<(8*sizeof(signed char)-1)); }
+ static signed char max() { return ~((signed char)(-1L<<(8*sizeof(signed char)-1))); }
+ static const char* format() { return "%d"; }
+ static unsigned int format(const signed char val) { return (int)val; }
+ };
+
+ template<> struct type<unsigned short> {
+ static const char* string() { static const char *const s = "unsigned short"; return s; }
+ static bool is_float() { return false; }
+ static unsigned short min() { return 0; }
+ static unsigned short max() { return (unsigned short)~0U; }
+ static const char* format() { return "%u"; }
+ static unsigned int format(const unsigned short val) { return (unsigned int)val; }
+ };
+
+ template<> struct type<short> {
+ static const char* string() { static const char *const s = "short"; return s; }
+ static bool is_float() { return false; }
+ static short min() { return (short)(-1L<<(8*sizeof(short)-1)); }
+ static short max() { return ~((short)(-1L<<(8*sizeof(short)-1))); }
+ static const char* format() { return "%d"; }
+ static int format(const short val) { return (int)val; }
+ };
+
+ template<> struct type<unsigned int> {
+ static const char* string() { static const char *const s = "unsigned int"; return s; }
+ static bool is_float() { return false; }
+ static unsigned int min() { return 0; }
+ static unsigned int max() { return (unsigned int)~0U; }
+ static const char* format() { return "%u"; }
+ static unsigned int format(const unsigned int val) { return val; }
+ };
+
+ template<> struct type<int> {
+ static const char* string() { static const char *const s = "int"; return s; }
+ static bool is_float() { return false; }
+ static int min() { return (int)(-1L<<(8*sizeof(int)-1)); }
+ static int max() { return ~((int)(-1L<<(8*sizeof(int)-1))); }
+ static const char* format() { return "%d"; }
+ static int format(const int val) { return val; }
+ };
+
+ template<> struct type<unsigned long> {
+ static const char* string() { static const char *const s = "unsigned long"; return s; }
+ static bool is_float() { return false; }
+ static unsigned long min() { return 0; }
+ static unsigned long max() { return (unsigned long)~0UL; }
+ static const char* format() { return "%lu"; }
+ static unsigned long format(const unsigned long val) { return val; }
+ };
+
+ template<> struct type<long> {
+ static const char* string() { static const char *const s = "long"; return s; }
+ static bool is_float() { return false; }
+ static long min() { return (long)(-1L<<(8*sizeof(long)-1)); }
+ static long max() { return ~((long)(-1L<<(8*sizeof(long)-1))); }
+ static const char* format() { return "%ld"; }
+ static long format(const long val) { return val; }
+ };
+
+ template<> struct type<float> {
+ static const char* string() { static const char *const s = "float"; return s; }
+ static bool is_float() { return true; }
+ static float min() { return -3.4E38f; }
+ static float max() { return 3.4E38f; }
+ static const char* format() { return "%g"; }
+ static double format(const float val) { return (double)val; }
+ };
+
+ template<> struct type<double> {
+ static const char* string() { static const char *const s = "double"; return s; }
+ static bool is_float() { return true; }
+ static double min() { return -1.7E308; }
+ static double max() { return 1.7E308; }
+ static const char* format() { return "%g"; }
+ static double format(const double val) { return val; }
+ };
+
+ template<typename T, typename t> struct superset { typedef T type; };
+ template<> struct superset<bool,unsigned char> { typedef unsigned char type; };
+ template<> struct superset<bool,char> { typedef char type; };
+ template<> struct superset<bool,signed char> { typedef signed char type; };
+ template<> struct superset<bool,unsigned short> { typedef unsigned short type; };
+ template<> struct superset<bool,short> { typedef short type; };
+ template<> struct superset<bool,unsigned int> { typedef unsigned int type; };
+ template<> struct superset<bool,int> { typedef int type; };
+ template<> struct superset<bool,unsigned long> { typedef unsigned long type; };
+ template<> struct superset<bool,long> { typedef long type; };
+ template<> struct superset<bool,float> { typedef float type; };
+ template<> struct superset<bool,double> { typedef double type; };
+ template<> struct superset<unsigned char,char> { typedef short type; };
+ template<> struct superset<unsigned char,signed char> { typedef short type; };
+ template<> struct superset<unsigned char,unsigned short> { typedef unsigned short type; };
+ template<> struct superset<unsigned char,short> { typedef short type; };
+ template<> struct superset<unsigned char,unsigned int> { typedef unsigned int type; };
+ template<> struct superset<unsigned char,int> { typedef int type; };
+ template<> struct superset<unsigned char,unsigned long> { typedef unsigned long type; };
+ template<> struct superset<unsigned char,long> { typedef long type; };
+ template<> struct superset<unsigned char,float> { typedef float type; };
+ template<> struct superset<unsigned char,double> { typedef double type; };
+ template<> struct superset<signed char,unsigned char> { typedef short type; };
+ template<> struct superset<signed char,char> { typedef short type; };
+ template<> struct superset<signed char,unsigned short> { typedef int type; };
+ template<> struct superset<signed char,short> { typedef short type; };
+ template<> struct superset<signed char,unsigned int> { typedef long type; };
+ template<> struct superset<signed char,int> { typedef int type; };
+ template<> struct superset<signed char,unsigned long> { typedef long type; };
+ template<> struct superset<signed char,long> { typedef long type; };
+ template<> struct superset<signed char,float> { typedef float type; };
+ template<> struct superset<signed char,double> { typedef double type; };
+ template<> struct superset<char,unsigned char> { typedef short type; };
+ template<> struct superset<char,signed char> { typedef short type; };
+ template<> struct superset<char,unsigned short> { typedef int type; };
+ template<> struct superset<char,short> { typedef short type; };
+ template<> struct superset<char,unsigned int> { typedef long type; };
+ template<> struct superset<char,int> { typedef int type; };
+ template<> struct superset<char,unsigned long> { typedef long type; };
+ template<> struct superset<char,long> { typedef long type; };
+ template<> struct superset<char,float> { typedef float type; };
+ template<> struct superset<char,double> { typedef double type; };
+ template<> struct superset<unsigned short,char> { typedef int type; };
+ template<> struct superset<unsigned short,signed char> { typedef int type; };
+ template<> struct superset<unsigned short,short> { typedef int type; };
+ template<> struct superset<unsigned short,unsigned int> { typedef unsigned int type; };
+ template<> struct superset<unsigned short,int> { typedef int type; };
+ template<> struct superset<unsigned short,unsigned long> { typedef unsigned long type; };
+ template<> struct superset<unsigned short,long> { typedef long type; };
+ template<> struct superset<unsigned short,float> { typedef float type; };
+ template<> struct superset<unsigned short,double> { typedef double type; };
+ template<> struct superset<short,unsigned short> { typedef int type; };
+ template<> struct superset<short,unsigned int> { typedef long type; };
+ template<> struct superset<short,int> { typedef int type; };
+ template<> struct superset<short,unsigned long> { typedef long type; };
+ template<> struct superset<short,long> { typedef long type; };
+ template<> struct superset<short,float> { typedef float type; };
+ template<> struct superset<short,double> { typedef double type; };
+ template<> struct superset<unsigned int,char> { typedef long type; };
+ template<> struct superset<unsigned int,signed char> { typedef long type; };
+ template<> struct superset<unsigned int,short> { typedef long type; };
+ template<> struct superset<unsigned int,int> { typedef long type; };
+ template<> struct superset<unsigned int,unsigned long> { typedef unsigned long type; };
+ template<> struct superset<unsigned int,long> { typedef long type; };
+ template<> struct superset<unsigned int,float> { typedef float type; };
+ template<> struct superset<unsigned int,double> { typedef double type; };
+ template<> struct superset<int,unsigned int> { typedef long type; };
+ template<> struct superset<int,unsigned long> { typedef long type; };
+ template<> struct superset<int,long> { typedef long type; };
+ template<> struct superset<int,float> { typedef float type; };
+ template<> struct superset<int,double> { typedef double type; };
+ template<> struct superset<unsigned long,char> { typedef long type; };
+ template<> struct superset<unsigned long,signed char> { typedef long type; };
+ template<> struct superset<unsigned long,short> { typedef long type; };
+ template<> struct superset<unsigned long,int> { typedef long type; };
+ template<> struct superset<unsigned long,long> { typedef long type; };
+ template<> struct superset<unsigned long,float> { typedef float type; };
+ template<> struct superset<unsigned long,double> { typedef double type; };
+ template<> struct superset<long,float> { typedef float type; };
+ template<> struct superset<long,double> { typedef double type; };
+ template<> struct superset<float,double> { typedef double type; };
+
+ template<typename t1, typename t2, typename t3> struct superset2 {
+ typedef typename superset<t1, typename superset<t2,t3>::type>::type type;
+ };
+
+ template<typename t1, typename t2, typename t3, typename t4> struct superset3 {
+ typedef typename superset<t1, typename superset2<t2,t3,t4>::type>::type type;
+ };
+
+ template<typename t1, typename t2> struct last { typedef t2 type; };
+
+#define _cimg_Tuchar typename cimg::superset<T,unsigned char>::type
+#define _cimg_Tint typename cimg::superset<T,int>::type
+#define _cimg_Tfloat typename cimg::superset<T,float>::type
+#define _cimg_Tdouble typename cimg::superset<T,double>::type
+#define _cimg_Tt typename cimg::superset<T,t>::type
+
+ // Define internal library variables.
+ //
+#if cimg_display==1
+ struct X11info {
+ volatile unsigned int nb_wins;
+ pthread_t* event_thread;
+ CImgDisplay* wins[1024];
+ Display* display;
+ unsigned int nb_bits;
+ GC* gc;
+ bool blue_first;
+ bool byte_order;
+ bool shm_enabled;
+#ifdef cimg_use_xrandr
+ XRRScreenSize *resolutions;
+ Rotation curr_rotation;
+ unsigned int curr_resolution;
+ unsigned int nb_resolutions;
+#endif
+ X11info():nb_wins(0),event_thread(0),display(0),
+ nb_bits(0),gc(0),blue_first(false),byte_order(false),shm_enabled(false) {
+#ifdef cimg_use_xrandr
+ resolutions = 0;
+ curr_rotation = 0;
+ curr_resolution = nb_resolutions = 0;
+#endif
+ }
+ };
+#if defined(cimg_module)
+ X11info& X11attr();
+#elif defined(cimg_main)
+ X11info& X11attr() { static X11info val; return val; }
+#else
+ inline X11info& X11attr() { static X11info val; return val; }
+#endif
+
+#elif cimg_display==2
+ struct Win32info {
+ HANDLE wait_event;
+ Win32info() { wait_event = CreateEvent(0,FALSE,FALSE,0); }
+ };
+#if defined(cimg_module)
+ Win32info& Win32attr();
+#elif defined(cimg_main)
+ Win32info& Win32attr() { static Win32info val; return val; }
+#else
+ inline Win32info& Win32attr() { static Win32info val; return val; }
+#endif
+
+#elif cimg_display==3
+ struct CarbonInfo {
+ MPCriticalRegionID windowListCR; // Protects access to the list of windows
+ int windowCount; // Count of displays used on the screen
+ pthread_t event_thread; // The background event thread
+ MPSemaphoreID sync_event; // Event used to perform tasks synchronizations
+ MPSemaphoreID wait_event; // Event used to notify that new events occured on the display
+ MPQueueID com_queue; // The message queue
+ CarbonInfo(): windowCount(0),event_thread(0),sync_event(0),com_queue(0) {
+ if (MPCreateCriticalRegion(&windowListCR) != noErr) // Create the critical region
+ throw CImgDisplayException("MPCreateCriticalRegion failed.");
+ if (MPCreateSemaphore(1, 0, &sync_event) != noErr) // Create the inter-thread sync object
+ throw CImgDisplayException("MPCreateSemaphore failed.");
+ if (MPCreateSemaphore(1, 0, &wait_event) != noErr) // Create the event sync object
+ throw CImgDisplayException("MPCreateSemaphore failed.");
+ if (MPCreateQueue(&com_queue) != noErr) // Create the shared queue
+ throw CImgDisplayException("MPCreateQueue failed.");
+ }
+ ~CarbonInfo() {
+ if (event_thread != 0) { // Terminates the resident thread, if needed
+ pthread_cancel(event_thread);
+ pthread_join(event_thread, NULL);
+ event_thread = 0;
+ }
+ if (MPDeleteCriticalRegion(windowListCR) != noErr) // Delete the critical region
+ throw CImgDisplayException("MPDeleteCriticalRegion failed.");
+ if (MPDeleteSemaphore(wait_event) != noErr) // Delete the event sync event
+ throw CImgDisplayException("MPDeleteEvent failed.");
+ if (MPDeleteSemaphore(sync_event) != noErr) // Delete the inter-thread sync event
+ throw CImgDisplayException("MPDeleteEvent failed.");
+ if (MPDeleteQueue(com_queue) != noErr) // Delete the shared queue
+ throw CImgDisplayException("MPDeleteQueue failed.");
+ }
+ };
+#if defined(cimg_module)
+ CarbonInfo& CarbonAttr();
+#elif defined(cimg_main)
+ CarbonInfo CarbonAttr() { static CarbonInfo val; return val; }
+#else
+ inline CarbonInfo& CarbonAttr() { static CarbonInfo val; return val; }
+#endif
+#endif
+
+#if cimg_display==1
+ // Keycodes for X11-based graphical systems.
+ //
+ const unsigned int keyESC = XK_Escape;
+ const unsigned int keyF1 = XK_F1;
+ const unsigned int keyF2 = XK_F2;
+ const unsigned int keyF3 = XK_F3;
+ const unsigned int keyF4 = XK_F4;
+ const unsigned int keyF5 = XK_F5;
+ const unsigned int keyF6 = XK_F6;
+ const unsigned int keyF7 = XK_F7;
+ const unsigned int keyF8 = XK_F8;
+ const unsigned int keyF9 = XK_F9;
+ const unsigned int keyF10 = XK_F10;
+ const unsigned int keyF11 = XK_F11;
+ const unsigned int keyF12 = XK_F12;
+ const unsigned int keyPAUSE = XK_Pause;
+ const unsigned int key1 = XK_1;
+ const unsigned int key2 = XK_2;
+ const unsigned int key3 = XK_3;
+ const unsigned int key4 = XK_4;
+ const unsigned int key5 = XK_5;
+ const unsigned int key6 = XK_6;
+ const unsigned int key7 = XK_7;
+ const unsigned int key8 = XK_8;
+ const unsigned int key9 = XK_9;
+ const unsigned int key0 = XK_0;
+ const unsigned int keyBACKSPACE = XK_BackSpace;
+ const unsigned int keyINSERT = XK_Insert;
+ const unsigned int keyHOME = XK_Home;
+ const unsigned int keyPAGEUP = XK_Page_Up;
+ const unsigned int keyTAB = XK_Tab;
+ const unsigned int keyQ = XK_q;
+ const unsigned int keyW = XK_w;
+ const unsigned int keyE = XK_e;
+ const unsigned int keyR = XK_r;
+ const unsigned int keyT = XK_t;
+ const unsigned int keyY = XK_y;
+ const unsigned int keyU = XK_u;
+ const unsigned int keyI = XK_i;
+ const unsigned int keyO = XK_o;
+ const unsigned int keyP = XK_p;
+ const unsigned int keyDELETE = XK_Delete;
+ const unsigned int keyEND = XK_End;
+ const unsigned int keyPAGEDOWN = XK_Page_Down;
+ const unsigned int keyCAPSLOCK = XK_Caps_Lock;
+ const unsigned int keyA = XK_a;
+ const unsigned int keyS = XK_s;
+ const unsigned int keyD = XK_d;
+ const unsigned int keyF = XK_f;
+ const unsigned int keyG = XK_g;
+ const unsigned int keyH = XK_h;
+ const unsigned int keyJ = XK_j;
+ const unsigned int keyK = XK_k;
+ const unsigned int keyL = XK_l;
+ const unsigned int keyENTER = XK_Return;
+ const unsigned int keySHIFTLEFT = XK_Shift_L;
+ const unsigned int keyZ = XK_z;
+ const unsigned int keyX = XK_x;
+ const unsigned int keyC = XK_c;
+ const unsigned int keyV = XK_v;
+ const unsigned int keyB = XK_b;
+ const unsigned int keyN = XK_n;
+ const unsigned int keyM = XK_m;
+ const unsigned int keySHIFTRIGHT = XK_Shift_R;
+ const unsigned int keyARROWUP = XK_Up;
+ const unsigned int keyCTRLLEFT = XK_Control_L;
+ const unsigned int keyAPPLEFT = XK_Super_L;
+ const unsigned int keyALT = XK_Alt_L;
+ const unsigned int keySPACE = XK_space;
+ const unsigned int keyALTGR = XK_Alt_R;
+ const unsigned int keyAPPRIGHT = XK_Super_R;
+ const unsigned int keyMENU = XK_Menu;
+ const unsigned int keyCTRLRIGHT = XK_Control_R;
+ const unsigned int keyARROWLEFT = XK_Left;
+ const unsigned int keyARROWDOWN = XK_Down;
+ const unsigned int keyARROWRIGHT = XK_Right;
+ const unsigned int keyPAD0 = XK_KP_0;
+ const unsigned int keyPAD1 = XK_KP_1;
+ const unsigned int keyPAD2 = XK_KP_2;
+ const unsigned int keyPAD3 = XK_KP_3;
+ const unsigned int keyPAD4 = XK_KP_4;
+ const unsigned int keyPAD5 = XK_KP_5;
+ const unsigned int keyPAD6 = XK_KP_6;
+ const unsigned int keyPAD7 = XK_KP_7;
+ const unsigned int keyPAD8 = XK_KP_8;
+ const unsigned int keyPAD9 = XK_KP_9;
+ const unsigned int keyPADADD = XK_KP_Add;
+ const unsigned int keyPADSUB = XK_KP_Subtract;
+ const unsigned int keyPADMUL = XK_KP_Multiply;
+ const unsigned int keyPADDIV = XK_KP_Divide;
+
+#elif cimg_display==2
+ // Keycodes for Windows.
+ //
+ const unsigned int keyESC = VK_ESCAPE;
+ const unsigned int keyF1 = VK_F1;
+ const unsigned int keyF2 = VK_F2;
+ const unsigned int keyF3 = VK_F3;
+ const unsigned int keyF4 = VK_F4;
+ const unsigned int keyF5 = VK_F5;
+ const unsigned int keyF6 = VK_F6;
+ const unsigned int keyF7 = VK_F7;
+ const unsigned int keyF8 = VK_F8;
+ const unsigned int keyF9 = VK_F9;
+ const unsigned int keyF10 = VK_F10;
+ const unsigned int keyF11 = VK_F11;
+ const unsigned int keyF12 = VK_F12;
+ const unsigned int keyPAUSE = VK_PAUSE;
+ const unsigned int key1 = '1';
+ const unsigned int key2 = '2';
+ const unsigned int key3 = '3';
+ const unsigned int key4 = '4';
+ const unsigned int key5 = '5';
+ const unsigned int key6 = '6';
+ const unsigned int key7 = '7';
+ const unsigned int key8 = '8';
+ const unsigned int key9 = '9';
+ const unsigned int key0 = '0';
+ const unsigned int keyBACKSPACE = VK_BACK;
+ const unsigned int keyINSERT = VK_INSERT;
+ const unsigned int keyHOME = VK_HOME;
+ const unsigned int keyPAGEUP = VK_PRIOR;
+ const unsigned int keyTAB = VK_TAB;
+ const unsigned int keyQ = 'Q';
+ const unsigned int keyW = 'W';
+ const unsigned int keyE = 'E';
+ const unsigned int keyR = 'R';
+ const unsigned int keyT = 'T';
+ const unsigned int keyY = 'Y';
+ const unsigned int keyU = 'U';
+ const unsigned int keyI = 'I';
+ const unsigned int keyO = 'O';
+ const unsigned int keyP = 'P';
+ const unsigned int keyDELETE = VK_DELETE;
+ const unsigned int keyEND = VK_END;
+ const unsigned int keyPAGEDOWN = VK_NEXT;
+ const unsigned int keyCAPSLOCK = VK_CAPITAL;
+ const unsigned int keyA = 'A';
+ const unsigned int keyS = 'S';
+ const unsigned int keyD = 'D';
+ const unsigned int keyF = 'F';
+ const unsigned int keyG = 'G';
+ const unsigned int keyH = 'H';
+ const unsigned int keyJ = 'J';
+ const unsigned int keyK = 'K';
+ const unsigned int keyL = 'L';
+ const unsigned int keyENTER = VK_RETURN;
+ const unsigned int keySHIFTLEFT = VK_SHIFT;
+ const unsigned int keyZ = 'Z';
+ const unsigned int keyX = 'X';
+ const unsigned int keyC = 'C';
+ const unsigned int keyV = 'V';
+ const unsigned int keyB = 'B';
+ const unsigned int keyN = 'N';
+ const unsigned int keyM = 'M';
+ const unsigned int keySHIFTRIGHT = VK_SHIFT;
+ const unsigned int keyARROWUP = VK_UP;
+ const unsigned int keyCTRLLEFT = VK_CONTROL;
+ const unsigned int keyAPPLEFT = VK_LWIN;
+ const unsigned int keyALT = VK_LMENU;
+ const unsigned int keySPACE = VK_SPACE;
+ const unsigned int keyALTGR = VK_CONTROL;
+ const unsigned int keyAPPRIGHT = VK_RWIN;
+ const unsigned int keyMENU = VK_APPS;
+ const unsigned int keyCTRLRIGHT = VK_CONTROL;
+ const unsigned int keyARROWLEFT = VK_LEFT;
+ const unsigned int keyARROWDOWN = VK_DOWN;
+ const unsigned int keyARROWRIGHT = VK_RIGHT;
+ const unsigned int keyPAD0 = 0x60;
+ const unsigned int keyPAD1 = 0x61;
+ const unsigned int keyPAD2 = 0x62;
+ const unsigned int keyPAD3 = 0x63;
+ const unsigned int keyPAD4 = 0x64;
+ const unsigned int keyPAD5 = 0x65;
+ const unsigned int keyPAD6 = 0x66;
+ const unsigned int keyPAD7 = 0x67;
+ const unsigned int keyPAD8 = 0x68;
+ const unsigned int keyPAD9 = 0x69;
+ const unsigned int keyPADADD = VK_ADD;
+ const unsigned int keyPADSUB = VK_SUBTRACT;
+ const unsigned int keyPADMUL = VK_MULTIPLY;
+ const unsigned int keyPADDIV = VK_DIVIDE;
+
+#elif cimg_display==3
+ // Keycodes for MacOSX, when using the Carbon framework.
+ //
+ const unsigned int keyESC = kEscapeCharCode;
+ const unsigned int keyF1 = 2U;
+ const unsigned int keyF2 = 3U;
+ const unsigned int keyF3 = 4U;
+ const unsigned int keyF4 = 5U;
+ const unsigned int keyF5 = 6U;
+ const unsigned int keyF6 = 7U;
+ const unsigned int keyF7 = 8U;
+ const unsigned int keyF8 = 9U;
+ const unsigned int keyF9 = 10U;
+ const unsigned int keyF10 = 11U;
+ const unsigned int keyF11 = 12U;
+ const unsigned int keyF12 = 13U;
+ const unsigned int keyPAUSE = 14U;
+ const unsigned int key1 = '1';
+ const unsigned int key2 = '2';
+ const unsigned int key3 = '3';
+ const unsigned int key4 = '4';
+ const unsigned int key5 = '5';
+ const unsigned int key6 = '6';
+ const unsigned int key7 = '7';
+ const unsigned int key8 = '8';
+ const unsigned int key9 = '9';
+ const unsigned int key0 = '0';
+ const unsigned int keyBACKSPACE = kBackspaceCharCode;
+ const unsigned int keyINSERT = 26U;
+ const unsigned int keyHOME = kHomeCharCode;
+ const unsigned int keyPAGEUP = kPageUpCharCode;
+ const unsigned int keyTAB = kTabCharCode;
+ const unsigned int keyQ = 'q';
+ const unsigned int keyW = 'w';
+ const unsigned int keyE = 'e';
+ const unsigned int keyR = 'r';
+ const unsigned int keyT = 't';
+ const unsigned int keyY = 'y';
+ const unsigned int keyU = 'u';
+ const unsigned int keyI = 'i';
+ const unsigned int keyO = 'o';
+ const unsigned int keyP = 'p';
+ const unsigned int keyDELETE = kDeleteCharCode;
+ const unsigned int keyEND = kEndCharCode;
+ const unsigned int keyPAGEDOWN = kPageDownCharCode;
+ const unsigned int keyCAPSLOCK = 43U;
+ const unsigned int keyA = 'a';
+ const unsigned int keyS = 's';
+ const unsigned int keyD = 'd';
+ const unsigned int keyF = 'f';
+ const unsigned int keyG = 'g';
+ const unsigned int keyH = 'h';
+ const unsigned int keyJ = 'j';
+ const unsigned int keyK = 'k';
+ const unsigned int keyL = 'l';
+ const unsigned int keyENTER = kEnterCharCode;
+ const unsigned int keySHIFTLEFT = 54U; //Macintosh modifier key, emulated
+ const unsigned int keyZ = 'z';
+ const unsigned int keyX = 'x';
+ const unsigned int keyC = 'c';
+ const unsigned int keyV = 'v';
+ const unsigned int keyB = 'b';
+ const unsigned int keyN = 'n';
+ const unsigned int keyM = 'm';
+ const unsigned int keySHIFTRIGHT = 62U; //Macintosh modifier key, emulated
+ const unsigned int keyARROWUP = kUpArrowCharCode;
+ const unsigned int keyCTRLLEFT = 64U; //Macintosh modifier key, emulated
+ const unsigned int keyAPPLEFT = 65U; //Macintosh modifier key, emulated
+ const unsigned int keyALT = 66U;
+ const unsigned int keySPACE = kSpaceCharCode;
+ const unsigned int keyALTGR = 67U; //Macintosh modifier key, emulated
+ const unsigned int keyAPPRIGHT = 68U; //Aliased on keyAPPLEFT
+ const unsigned int keyMENU = 69U;
+ const unsigned int keyCTRLRIGHT = 70U; //Macintosh modifier key, emulated
+ const unsigned int keyARROWLEFT = kLeftArrowCharCode;
+ const unsigned int keyARROWDOWN = kDownArrowCharCode;
+ const unsigned int keyARROWRIGHT = kRightArrowCharCode;
+ const unsigned int keyPAD0 = 74U;
+ const unsigned int keyPAD1 = 75U;
+ const unsigned int keyPAD2 = 76U;
+ const unsigned int keyPAD3 = 77U;
+ const unsigned int keyPAD4 = 78U;
+ const unsigned int keyPAD5 = 79U;
+ const unsigned int keyPAD6 = 80U;
+ const unsigned int keyPAD7 = 81U;
+ const unsigned int keyPAD8 = 82U;
+ const unsigned int keyPAD9 = 83U;
+ const unsigned int keyPADADD = 84U;
+ const unsigned int keyPADSUB = 85U;
+ const unsigned int keyPADMUL = 86U;
+ const unsigned int keyPADDIV = 87U;
+
+#else
+ // Define unknow keycodes when no display are available.
+ // (should rarely be used then !).
+ //
+ const unsigned int keyESC = 1U;
+ const unsigned int keyF1 = 2U;
+ const unsigned int keyF2 = 3U;
+ const unsigned int keyF3 = 4U;
+ const unsigned int keyF4 = 5U;
+ const unsigned int keyF5 = 6U;
+ const unsigned int keyF6 = 7U;
+ const unsigned int keyF7 = 8U;
+ const unsigned int keyF8 = 9U;
+ const unsigned int keyF9 = 10U;
+ const unsigned int keyF10 = 11U;
+ const unsigned int keyF11 = 12U;
+ const unsigned int keyF12 = 13U;
+ const unsigned int keyPAUSE = 14U;
+ const unsigned int key1 = 15U;
+ const unsigned int key2 = 16U;
+ const unsigned int key3 = 17U;
+ const unsigned int key4 = 18U;
+ const unsigned int key5 = 19U;
+ const unsigned int key6 = 20U;
+ const unsigned int key7 = 21U;
+ const unsigned int key8 = 22U;
+ const unsigned int key9 = 23U;
+ const unsigned int key0 = 24U;
+ const unsigned int keyBACKSPACE = 25U;
+ const unsigned int keyINSERT = 26U;
+ const unsigned int keyHOME = 27U;
+ const unsigned int keyPAGEUP = 28U;
+ const unsigned int keyTAB = 29U;
+ const unsigned int keyQ = 30U;
+ const unsigned int keyW = 31U;
+ const unsigned int keyE = 32U;
+ const unsigned int keyR = 33U;
+ const unsigned int keyT = 34U;
+ const unsigned int keyY = 35U;
+ const unsigned int keyU = 36U;
+ const unsigned int keyI = 37U;
+ const unsigned int keyO = 38U;
+ const unsigned int keyP = 39U;
+ const unsigned int keyDELETE = 40U;
+ const unsigned int keyEND = 41U;
+ const unsigned int keyPAGEDOWN = 42U;
+ const unsigned int keyCAPSLOCK = 43U;
+ const unsigned int keyA = 44U;
+ const unsigned int keyS = 45U;
+ const unsigned int keyD = 46U;
+ const unsigned int keyF = 47U;
+ const unsigned int keyG = 48U;
+ const unsigned int keyH = 49U;
+ const unsigned int keyJ = 50U;
+ const unsigned int keyK = 51U;
+ const unsigned int keyL = 52U;
+ const unsigned int keyENTER = 53U;
+ const unsigned int keySHIFTLEFT = 54U;
+ const unsigned int keyZ = 55U;
+ const unsigned int keyX = 56U;
+ const unsigned int keyC = 57U;
+ const unsigned int keyV = 58U;
+ const unsigned int keyB = 59U;
+ const unsigned int keyN = 60U;
+ const unsigned int keyM = 61U;
+ const unsigned int keySHIFTRIGHT = 62U;
+ const unsigned int keyARROWUP = 63U;
+ const unsigned int keyCTRLLEFT = 64U;
+ const unsigned int keyAPPLEFT = 65U;
+ const unsigned int keyALT = 66U;
+ const unsigned int keySPACE = 67U;
+ const unsigned int keyALTGR = 68U;
+ const unsigned int keyAPPRIGHT = 69U;
+ const unsigned int keyMENU = 70U;
+ const unsigned int keyCTRLRIGHT = 71U;
+ const unsigned int keyARROWLEFT = 72U;
+ const unsigned int keyARROWDOWN = 73U;
+ const unsigned int keyARROWRIGHT = 74U;
+ const unsigned int keyPAD0 = 75U;
+ const unsigned int keyPAD1 = 76U;
+ const unsigned int keyPAD2 = 77U;
+ const unsigned int keyPAD3 = 78U;
+ const unsigned int keyPAD4 = 79U;
+ const unsigned int keyPAD5 = 80U;
+ const unsigned int keyPAD6 = 81U;
+ const unsigned int keyPAD7 = 82U;
+ const unsigned int keyPAD8 = 83U;
+ const unsigned int keyPAD9 = 84U;
+ const unsigned int keyPADADD = 85U;
+ const unsigned int keyPADSUB = 86U;
+ const unsigned int keyPADMUL = 87U;
+ const unsigned int keyPADDIV = 88U;
+#endif
+
+ const double valuePI = 3.14159265358979323846; //!< Definition of the mathematical constant PI
+
+ // Definition of a 7x11 font, used to return a default font for drawing text.
+ const unsigned int font7x11[7*11*256/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x90,0x0,0x7f0000,0x40000,0x0,0x0,0x4010c0a4,0x82000040,0x11848402,0x18480050,0x80430292,0x8023,0x9008000,
+ 0x40218140,0x4000040,0x21800402,0x18000051,0x1060500,0x8083,0x10000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x24002,0x4031,0x80000000,0x10000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c0400,0x40020000,0x80070080,0x40440e00,0x0,0x0,0x1,0x88180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x200000,0x0,0x0,0x80000,0x0,0x0,0x20212140,0x5000020,0x22400204,0x240000a0,0x40848500,0x4044,0x80010038,0x20424285,0xa000020,
+ 0x42428204,0x2428e0a0,0x82090a14,0x4104,0x85022014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10240a7,0x88484040,0x40800000,0x270c3,0x87811e0e,
+ 0x7c70e000,0x78,0x3c23c1ef,0x1f3e1e89,0xf1c44819,0xa23cf0f3,0xc3cff120,0xc18307f4,0x4040400,0x20000,0x80080080,0x40200,0x0,
+ 0x40000,0x2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8188,0x50603800,0xf3c00000,0x1c004003,0xc700003e,0x18180,0xc993880,0x10204081,
+ 0x2071ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x7d1224,0x48906048,0x0,0x4000000,0x0,0x9000,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x10240aa,0x14944080,0x23610000,0x68940,0x40831010,0x8891306,0x802044,0x44522208,0x90202088,0x40448819,0xb242890a,0x24011111,
+ 0x49448814,0x4040a00,0xe2c3c7,0x8e3f3cb9,0xc1c44216,0xee38b0f2,0xe78f9120,0xc18507e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x101c207,0x88a04001,0x9c00000,0x2200a041,0x8200113a,0x8240,0x50a3110,0x2850a142,0x850c2081,0x2040204,0x8104592,0x142850a1,
+ 0x42cd1224,0x4888bc48,0x70e1c387,0xe3b3c70,0xe1c38e1c,0x38707171,0xc3870e1c,0x10791224,0x48906c41,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x10003ee,0x15140080,0x21810000,0x48840,0x40851020,0x8911306,0x31fd804,0x9c522408,0x90204088,0x4045081a,0xba42890a,0x24011111,
+ 0x49285024,0x2041b00,0x132408,0x910844c8,0x4044821b,0x7244c913,0x24041111,0x49488822,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x28204,0x85006001,0x6a414000,0x3a004043,0xc700113a,0x8245,0x50a3a00,0x2850a142,0x850c4081,0x2040204,0x81045d2,0x142850a1,
+ 0x24951224,0x48852250,0x8102040,0x81054089,0x12244204,0x8108992,0x24489122,0x991224,0x4888b222,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x1000143,0xa988080,0x2147c01f,0x88840,0x83091c2c,0x1070f000,0xc000608,0xa48bc408,0x9e3c46f8,0x40460816,0xaa42f10b,0xc3811111,
+ 0x35102044,0x1041100,0xf22408,0x9f084488,0x40470212,0x62448912,0x6041111,0x55308846,0x8061c80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x1028704,0x8f805801,0x4be28fdf,0x220001f0,0x111a,0x60000182,0x82c5c710,0x44891224,0x489640f1,0xe3c78204,0x810e552,0x142850a1,
+ 0x18a51224,0x48822250,0x78f1e3c7,0x8f1f40f9,0xf3e7c204,0x8108912,0x24489122,0x7ea91224,0x4888a222,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x10007e2,0x85648080,0x20010000,0x88841,0x8f8232,0x20881000,0xc1fc610,0xbefa2408,0x90204288,0x40450816,0xa642810a,0x4041110a,
+ 0x36282084,0x1042080,0x1122408,0x90084488,0x40450212,0x62448912,0x184110a,0x55305082,0x8042700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x1028207,0x82004801,0x68050040,0x1c000040,0x110a,0x60000001,0x45484d10,0x7cf9f3e7,0xcf944081,0x2040204,0x8104532,0x142850a1,
+ 0x18a51224,0x48822248,0x89122448,0x91244081,0x2040204,0x8108912,0x24489122,0xc91224,0x48852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x282,
+ 0x89630080,0x20010c00,0x30108842,0x810222,0x20882306,0x3001800,0x408a2208,0x90202288,0x40448814,0xa642810a,0x2041110a,0x26442104,
+ 0x840000,0x1122408,0x90084488,0x40448212,0x62448912,0x84130a,0x36485102,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x101c208,0x4f802801,
+ 0x8028040,0x40,0x130a,0x2,0x85e897a0,0x44891224,0x489c2081,0x2040204,0x8104532,0x142850a1,0x24cd1224,0x48823c44,0x89122448,
+ 0x91244081,0x2040204,0x8108912,0x24489122,0xc93264,0xc9852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100028f,0x109f0080,0x20010c00,
+ 0x303071f3,0xc7011c1c,0x4071c306,0x802010,0x3907c1ef,0x1f201e89,0xf3844f90,0xa23c80f2,0x17810e04,0x228223f4,0x840000,0xfbc3c7,
+ 0x8f083c88,0x40444212,0x6238f0f2,0x7039d04,0x228423e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1008780,0x2201800,0xf0014000,0x1f0,
+ 0x1d0a,0x5,0x851e140,0x83060c18,0x30671ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x42f8e1c3,0x8702205c,0x7cf9f3e7,0xcf9b3c78,0xf1e3c204,
+ 0x8107111,0xc3870e1c,0x10f1d3a7,0x4e823c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x40,0x40000400,0x200000,0x0,0x2,0x0,0x0,0x0,0x0,0x18,
+ 0x0,0x4,0x44007f,0x0,0x400,0x400000,0x8010,0x0,0x6002,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000000,0x200800,0x0,0x0,0x100a,
+ 0x400000,0x44,0x0,0x400,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x62018,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x31,0x80000800,
+ 0x400000,0x0,0x4,0x0,0x0,0x0,0x0,0xc,0x0,0x7,0x3c0000,0x0,0x3800,0x3800000,0x8010,0x0,0x1c001,0x881c0000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x207000,0x0,0x0,0x100a,0xc00000,0x3c,0x0,0xc00,0x0,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x0,0x1c2070
+ };
+
+ // Definition of a 10x13 font (used in dialog boxes).
+ const unsigned int font10x13[256*10*13/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80100c0,
+ 0x68000300,0x801,0xc00010,0x100c000,0x68100,0x100c0680,0x2,0x403000,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x0,0x0,0x0,0x0,0x0,0x4020120,
+ 0x58120480,0x402,0x1205008,0x2012050,0x58080,0x20120581,0x40000001,0x804812,0x2000000,0x0,0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x140,0x80000,0x200402,0x800000,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x7010,0x7000000,0x8000200,0x20000,0xc0002000,0x8008,0x0,0x0,0x0,0x0,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x80000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x80100c0,0x68000480,0x1001,
+ 0xc00010,0x1018000,0x68100,0x100c0680,0x4,0x403000,0x1020000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,0x28081883,0x200801,
+ 0x2a00000,0x10,0x1c0201c0,0x70040f80,0xc0f81c07,0x0,0x70,0x3e0303c0,0x3c3c0f83,0xe03c2107,0xe08810,0x18c31070,0x3c0703c0,
+ 0x783e0842,0x22222208,0x83e04010,0x1008000,0x4000200,0x20001,0x2002,0x408008,0x0,0x0,0x100000,0x0,0x1008,0x2000000,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20080,0x38000880,0x8078140f,0x81c00000,0x3e000,0xc020180,0x60080001,0xe0000002,0xc00042,0x108e2010,
+ 0xc0300c0,0x300c0303,0xf83c3e0f,0x83e0f81c,0x701c070,0x3c0c41c0,0x701c0701,0xc0001d08,0x42108421,0x8820088,0x4020120,0x58140480,
+ 0x802,0x1205008,0x3014050,0xc058080,0x20120581,0x40000002,0x804814,0x2020050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,
+ 0x281e2484,0x80200801,0x1c02000,0x10,0x22060220,0x880c0801,0x82208,0x80000001,0x20008,0x41030220,0x40220802,0x402102,0x209010,
+ 0x18c31088,0x22088220,0x80080842,0x22222208,0x80204010,0x1014000,0x200,0x20001,0x2000,0x8008,0x0,0x0,0x100000,0x0,0x1008,
+ 0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x40000500,0x80800010,0x40200000,0x41000,0x12020040,0x10000003,0xa0000006,
+ 0x12000c4,0x31014000,0xc0300c0,0x300c0302,0x80402008,0x2008008,0x2008020,0x220c4220,0x88220882,0x20002208,0x42108421,0x8820088,
+ 0x0,0x300,0x0,0x0,0x0,0x14000000,0x0,0x200200,0x0,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xfc282504,0x80001000,
+ 0x82a02000,0x20,0x22020020,0x8140802,0x102208,0x80801006,0x18008,0x9c848220,0x80210802,0x802102,0x20a010,0x15429104,0x22104220,
+ 0x80080842,0x22221405,0x404008,0x1022000,0x703c0,0x381e0701,0xc0783c02,0xc09008,0x1d83c070,0x3c078140,0x381c0882,0x21242208,
+ 0x81e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,0x40220500,0x80800027,0x20e02800,0x9c800,0x12020040,
+ 0x20000883,0xa0200002,0x120a044,0x11064010,0x12048120,0x48120484,0x80802008,0x2008008,0x2008020,0x210a4411,0x4411044,0x10884508,
+ 0x42108421,0x503c0b0,0x1c0701c0,0x701c0707,0x70381c07,0x1c07008,0x2008020,0x20f01c0,0x701c0701,0xc0201c08,0x82208822,0x883c088,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x50281903,0x20001000,0x80802000,0x20,0x22020040,0x30240f03,0xc0101c08,0x80801018,
+ 0x1fc06010,0xa48483c0,0x80210f03,0xe0803f02,0x20c010,0x15429104,0x22104220,0x70080841,0x41540805,0x804008,0x1041000,0x8220,
+ 0x40220881,0x882202,0x40a008,0x12422088,0x22088180,0x40100882,0x21241408,0x80201008,0x2031000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x20280,0x401c0200,0x700028,0x21205000,0x92800,0xc1fc080,0x10000883,0xa0200002,0x1205049,0x12c19010,0x12048120,0x48120484,
+ 0xf0803c0f,0x3c0f008,0x2008020,0x790a4411,0x4411044,0x10504908,0x42108421,0x5022088,0x2008020,0x8020080,0x88402208,0x82208808,
+ 0x2008020,0x1e088220,0x88220882,0x20002608,0x82208822,0x8822088,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x501c0264,
+ 0xa0001000,0x8001fc00,0x7000020,0x22020080,0x83e0082,0x20202207,0x80000020,0x1020,0xa4848220,0x80210802,0x9c2102,0x20c010,
+ 0x12425104,0x3c1043c0,0x8080841,0x41540802,0x804008,0x1000000,0x78220,0x40220f81,0x882202,0x40c008,0x12422088,0x22088100,
+ 0x60100881,0x41540805,0x406008,0x1849000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0xf0140200,0x880028,0x20e0a03f,0x709c800,
+ 0x201c0,0x60000881,0xa0000007,0xc0284b,0x122eb020,0x12048120,0x48120487,0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,
+ 0x10204908,0x42108421,0x2022088,0x1e0781e0,0x781e0787,0xf8403e0f,0x83e0f808,0x2008020,0x22088220,0x88220882,0x21fc2a08,0x82208822,
+ 0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xf80a0294,0x40001000,0x80002000,0x20,0x22020100,0x8040082,0x20202200,
+ 0x80000018,0x1fc06020,0xa48fc220,0x80210802,0x842102,0x20a010,0x12425104,0x20104240,0x8080841,0x41541402,0x1004008,0x1000000,
+ 0x88220,0x40220801,0x882202,0x40a008,0x12422088,0x22088100,0x18100881,0x41540805,0x801008,0x2046000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x20280,0x401c0f80,0x80880028,0x20005001,0x94800,0x20000,0x880,0xa0000000,0x5015,0x4215040,0x3f0fc3f0,0xfc3f0fc8,
+ 0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,0x10505108,0x42108421,0x203c088,0x22088220,0x88220888,0x80402008,0x2008008,
+ 0x2008020,0x22088220,0x88220882,0x20002a08,0x82208822,0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa00a0494,0x60001000,
+ 0x80002004,0x8020,0x22020200,0x88040882,0x20402201,0x801006,0x18000,0x9f084220,0x40220802,0x442102,0x209010,0x10423088,0x20088220,
+ 0x8080840,0x80882202,0x2004008,0x1000000,0x88220,0x40220881,0x882202,0x409008,0x12422088,0x22088100,0x8100880,0x80881402,
+ 0x1001008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0x40220200,0x80700027,0x20002801,0x92800,0x1fc000,0x980,
+ 0xa0000000,0xa017,0x84417840,0x21084210,0x84210848,0x80402008,0x2008008,0x2008020,0x2208c220,0x88220882,0x20882208,0x42108421,
+ 0x2020088,0x22088220,0x88220888,0xc8402208,0x82208808,0x2008020,0x22088220,0x88220882,0x20203208,0x82208822,0x2022020,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xa03c0463,0x90000801,0x2004,0x8040,0x1c0703e0,0x70040701,0xc0401c06,0x801001,0x20020,
+ 0x400843c0,0x3c3c0f82,0x3c2107,0x1c0881e,0x10423070,0x20070210,0xf0080780,0x80882202,0x3e04004,0x1000000,0x783c0,0x381e0701,
+ 0x782202,0x408808,0x12422070,0x3c078100,0x700c0780,0x80882202,0x1e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,
+ 0xf8000200,0x80080010,0x40000001,0x41000,0x0,0xe80,0xa0000000,0x21,0x8e21038,0x21084210,0x84210848,0xf83c3e0f,0x83e0f81c,
+ 0x701c070,0x3c08c1c0,0x701c0701,0xc0005c07,0x81e0781e,0x20200b0,0x1e0781e0,0x781e0787,0x30381c07,0x1c07008,0x2008020,0x1c0881c0,
+ 0x701c0701,0xc0201c07,0x81e0781e,0x203c020,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x801,0x4,0x40,0x0,0x0,0x0,0x1000,
+ 0x0,0x3c000000,0x0,0x0,0x0,0x0,0x10000,0x0,0x0,0x4004,0x1000000,0x0,0x0,0x80000,0x400000,0x0,0x20008000,0x0,0x4,0x1008,0x2000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x8008000f,0x80000000,0x3e000,0x0,0x800,0xa0000400,0x0,0x0,0x0,0x0,0x80000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100000,0x0,0x0,0x0,0x0,0x2000,0x0,0x4020040,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,
+ 0x402,0x8,0x40,0x0,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x7004,0x70000fc,0x0,0x0,0x700000,0x800000,0x0,0x20008000,
+ 0x0,0x4,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x80f00000,0x0,0x0,0x0,0x800,0xa0001800,0x0,0x0,0x0,0x0,
+ 0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x4020040
+ };
+
+ // Definition of a 8x17 font.
+ const unsigned int font8x17[8*17*256/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x2400,0x2400,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20081834,0x1c0000,0x20081800,0x20081800,0x342008,
+ 0x18340000,0x200818,0x80000,0x0,0x180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4200000,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x380000,0x4000,0x2000c00,0x40100840,0x70000000,0x0,0x0,0x1c,0x10700000,0x7,0x0,
+ 0x1800,0x1800,0x0,0x0,0x0,0x14,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1010242c,0x14140000,0x10102414,0x10102414,0x2c1010,0x242c1400,
+ 0x101024,0x14100038,0x0,0x240000,0x0,0x0,0x30000000,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x12,0x0,0x8100000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x80000,0x10004000,0x2001000,0x40000040,0x10000000,0x0,0x0,0x10,0x10100000,0x4,
+ 0x0,0x18000000,0x0,0x0,0x0,0x34002400,0x2400,0x0,0x0,0x0,0x3c,0x0,0x8000000,0x0,0x60607800,0x0,0x140000,0x0,0x0,0x0,0x0,0x0,
+ 0x44,0x10081834,0x240000,0x10081800,0x10081800,0x1c341008,0x18340000,0x100818,0x84000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102812,
+ 0x8601c10,0x8100800,0x2,0x1c383e3e,0x67e1e7f,0x3e3c0000,0x38,0x1e087e1e,0x7c7f7f1e,0x417c1c42,0x4063611c,0x7e1c7e3e,0xfe414181,
+ 0x63827f10,0x40081000,0x8004000,0x2001000,0x40000040,0x10000000,0x0,0x10000000,0x10,0x10100000,0x3c000008,0x0,0x24003e00,
+ 0x3f007f00,0x0,0x0,0x2ce91800,0x1882,0x10101c,0xc2103c,0x143c3c00,0x3c00,0x18003c3c,0x10001f00,0x181c00,0x20200810,0x8080808,
+ 0x8083e1e,0x7f7f7f7f,0x7c7c7c7c,0x7c611c1c,0x1c1c1c00,0x1e414141,0x41824044,0x810242c,0x14180000,0x8102414,0x8102414,0x382c0810,
+ 0x242c1400,0x81024,0x14104014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102816,0x3e902010,0x10084910,0x4,0x22084343,0xa402102,0x41620000,
+ 0x44,0x33144121,0x42404021,0x41100444,0x40636122,0x43224361,0x10416381,0x22440310,0x20082800,0x4000,0x2001000,0x40000040,
+ 0x10000000,0x0,0x10000000,0x10,0x10100000,0x24000008,0x0,0x606100,0x68000300,0x8106c,0x34000000,0x4f0000,0x44,0x101020,0x441040,
+ 0x420200,0x4200,0x24000404,0x7d00,0x82200,0x20203010,0x14141414,0x14082821,0x40404040,0x10101010,0x42612222,0x22222200,0x23414141,
+ 0x41447e48,0x0,0x0,0x0,0x0,0x4000000,0x18,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10287f,0x49902010,0x10083e10,0x4,0x41080101,
+ 0x1a404002,0x41411818,0x1004004,0x21144140,0x41404040,0x41100448,0x40555141,0x41414140,0x10412281,0x14280610,0x20084400,0x1c7c1c,
+ 0x3e3c7c3a,0x5c703844,0x107f5c3c,0x7c3e3c3c,0x7e424281,0x66427e10,0x10100000,0x40100008,0x1010,0xa04000,0x48100610,0x100c3024,
+ 0x24000000,0x4f3c00,0x2c107e28,0x3820,0x42281060,0x9d1e12,0xbd00,0x24100818,0x427d00,0x82248,0x20200800,0x14141414,0x14142840,
+ 0x40404040,0x10101010,0x41514141,0x41414142,0x43414141,0x41284350,0x1c1c1c1c,0x1c1c6c1c,0x3c3c3c3c,0x70707070,0x3c5c3c3c,
+ 0x3c3c3c18,0x3e424242,0x42427c42,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102824,0x48623010,0x10081c10,0x8,0x41080103,0x127c5e04,
+ 0x41411818,0xe7f3808,0x4f144140,0x41404040,0x41100450,0x40555141,0x41414160,0x1041225a,0x1c280410,0x1008c600,0x226622,0x66661066,
+ 0x62100848,0x10496266,0x66663242,0x10426681,0x24220260,0x100c0000,0xf8280008,0x1010,0x606000,0x48280428,0x28042014,0x48000000,
+ 0x494200,0x52280228,0x105420,0x3cee1058,0xa12236,0xa500,0x18101004,0x427d00,0x8226c,0x76767e10,0x14141414,0x14142840,0x40404040,
+ 0x10101010,0x41514141,0x41414124,0x45414141,0x41284150,0x22222222,0x22221222,0x66666666,0x10101010,0x66626666,0x66666600,
+ 0x66424242,0x42226622,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100024,0x381c4900,0x10086bfe,0x8,0x4908021c,0x22036304,0x3e630000,
+ 0x70000710,0x51227e40,0x417f7f43,0x7f100470,0x40554941,0x43417e3e,0x1041225a,0x8100810,0x10080000,0x24240,0x42421042,0x42100850,
+ 0x10494242,0x42422040,0x1042245a,0x18240410,0x10103900,0x407c003e,0x1818,0x1c3e10,0x4f7c087c,0x7c002010,0x48000000,0x4008,
+ 0x527c0410,0x105078,0x2410104c,0xa13e6c,0x7f00b900,0xfe3c3c,0x421d18,0x1c1c36,0x38383810,0x22222222,0x22144e40,0x7f7f7f7f,
+ 0x10101010,0xf1494141,0x41414118,0x49414141,0x4110435c,0x2020202,0x2021240,0x42424242,0x10101010,0x42424242,0x424242ff,0x4e424242,
+ 0x42244224,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000fe,0xe664d00,0x10080810,0x380010,0x41080c03,0x42014108,0x633d0000,0x70000710,
+ 0x51224140,0x41404041,0x41100448,0x40494541,0x7e414203,0x1041145a,0x14101010,0x10080000,0x3e4240,0x427e1042,0x42100870,0x10494242,
+ 0x4242203c,0x1042245a,0x18241810,0x10104600,0xf8f60008,0x1010,0x600320,0x48f610f6,0xf6000000,0x187eff,0x3c04,0x5ef61810,0x105020,
+ 0x24fe0064,0x9d006c,0x138ad00,0x100000,0x420518,0x36,0xc0c0c020,0x22222222,0x22224840,0x40404040,0x10101010,0x41454141,0x41414118,
+ 0x51414141,0x41107e46,0x3e3e3e3e,0x3e3e7e40,0x7e7e7e7e,0x10101010,0x42424242,0x42424200,0x5a424242,0x42244224,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x28,0x9094500,0x10080010,0x10,0x41081801,0x7f014118,0x41010000,0xe7f3800,0x513e4140,0x41404041,0x41100444,
+ 0x40414541,0x40414101,0x10411466,0x36103010,0x8080000,0x424240,0x42401042,0x42100848,0x10494242,0x42422002,0x10423c5a,0x18142010,
+ 0x10100000,0x407c0010,0x1010,0x260140,0x487c307c,0x7c000000,0x180000,0x202,0x507c2010,0x105020,0x3c10003c,0x423e36,0x1004200,
+ 0x100000,0x420500,0x3e6c,0x41e0440,0x3e3e3e3e,0x3e3e7840,0x40404040,0x10101010,0x41454141,0x41414124,0x61414141,0x41104042,
+ 0x42424242,0x42425040,0x40404040,0x10101010,0x42424242,0x42424218,0x72424242,0x42144214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048,
+ 0x49096200,0x8100010,0x18001810,0x22082043,0x2432310,0x61421818,0x1004010,0x4f634121,0x42404021,0x41104444,0x40414322,0x40234143,
+ 0x10411466,0x22106010,0x8080000,0x466622,0x66621066,0x42100844,0x10494266,0x66662042,0x10461824,0x24184010,0x10100000,0x24381010,
+ 0x34001018,0xda4320,0x68386038,0x38000000,0x0,0x4204,0x50384010,0x105420,0x4210100c,0x3c0012,0x3c00,0x0,0x460500,0x48,0xc020c44,
+ 0x63636363,0x63228821,0x40404040,0x10101010,0x42432222,0x22222242,0x62414141,0x41104042,0x46464646,0x46465022,0x62626262,
+ 0x10101010,0x66426666,0x66666618,0x66464646,0x46186618,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048,0x3e063d00,0x8100000,0x18001820,
+ 0x1c3e7f3e,0x23c1e20,0x3e3c1818,0x10,0x20417e1e,0x7c7f401e,0x417c3842,0x7f41431c,0x401e40be,0x103e0866,0x41107f10,0x4080000,
+ 0x3a5c1c,0x3a3c103a,0x427c0842,0xe49423c,0x7c3e203c,0xe3a1824,0x66087e10,0x10100000,0x3c103010,0x245a1010,0x5a3e10,0x3f107f10,
+ 0x10000000,0x0,0x3c08,0x2e107e10,0x1038fc,0x101004,0x0,0x0,0xfe0000,0x7f0500,0x0,0x14041438,0x41414141,0x41418e1e,0x7f7f7f7f,
+ 0x7c7c7c7c,0x7c431c1c,0x1c1c1c00,0xbc3e3e3e,0x3e10405c,0x3a3a3a3a,0x3a3a6e1c,0x3c3c3c3c,0x7c7c7c7c,0x3c423c3c,0x3c3c3c00,
+ 0x7c3a3a3a,0x3a087c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x4200000,0x10000020,0x0,0x0,0x10,0x0,0x30000000,0x0,
+ 0x0,0x0,0x60000,0x0,0x1c,0x4380000,0x0,0x2,0x800,0x0,0x40020000,0x0,0x8000c,0x10600000,0x2010,0x48000000,0x240000,0x0,0x0,
+ 0x0,0x0,0x0,0x1000,0x1078,0x0,0x0,0x0,0x400500,0x0,0x1e081e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x84008,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x0,0x20000040,0x0,0x0,0x20,0x0,0x1e000000,0x0,0x0,0x0,0x20000,0x0,
+ 0x0,0x2000000,0x0,0x26,0x800,0x0,0x40020000,0x0,0x100000,0x10000000,0x2030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x1000,0x0,
+ 0x0,0x0,0x400000,0x8000000,0x41e0400,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x104010,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x1c,0x7000,0x0,0x40020000,0x0,0x300000,
+ 0x0,0xe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x0,0x0,0x0,0x400000,0x38000000,0x0,0x0,0x1c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x1c,0x0,0x0,0x0,0x0,0x0,0x304030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+ // Definition of a 10x19 font.
+ const unsigned int font10x19[10*19*256/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3600000,0x36000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x180181c0,0xe81b0300,0x1801,0x81c06c18,0x181c06c,0xe8180,0x181c0e81,0xb0000006,0x60701b,0x1800000,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x1c000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0xc030360,0xb81b0480,0xc03,0x3606c0c,0x303606c,0xb80c0,0x30360b81,0xb0000003,0xc0d81b,0x3000000,0x0,
+ 0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x0,0x2200000,
+ 0x22000,0x0,0x0,0x0,0x0,0x0,0x0,0x30000,0x0,0xe0,0x38078000,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000c080,0x480,0x3000,
+ 0xc0800030,0xc08000,0x300,0xc080000,0xc,0x302000,0xc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x41c01,0xe020060c,
+ 0x800000,0x4,0x1e0703e0,0xf8060fc1,0xe1fe1e07,0x80000000,0x78,0x307e0,0x3c7c1fe7,0xf83c408f,0x80f10440,0x18660878,0x7e0787e0,
+ 0x78ff9024,0xa0140a0,0x27f83840,0x700e000,0x18000400,0x8000,0x70004002,0x410078,0x0,0x0,0x0,0x0,0x1808,0xc000000,0xf000000,
+ 0xe000000,0x1400,0x1e0001f,0x8007f800,0x0,0x0,0x3a3b,0x61400000,0x14202,0x20000,0x38002020,0x3c1b00,0x3e00000,0xf8,0x1c0001c0,
+ 0x78060001,0xf800000e,0x1e00020,0x8004020,0xc0300c0,0x300c0301,0xf83c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1821e0,0x781e0781,0xe0001f10,
+ 0x24090240,0xa02400f8,0x18018140,0xe81b0480,0x1801,0x81406c18,0x181406c,0x190e8180,0x18140e81,0xb0000006,0x60501b,0x184006c,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x26042202,0x200c06,0x800000,0x8,0x210d0611,0x40e0803,0x10026188,0x40000000,
+ 0x8c,0xf030418,0xc6431004,0xc64082,0x110840,0x18660884,0x41084410,0x8c081024,0xa012110,0x40082020,0x101b000,0xc000400,0x8000,
+ 0x80004002,0x410008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x18800000,0x10000000,0x2200,0x2300024,0x800,0x0,0x0,0x2e13,0x60800000,
+ 0x8104,0x20040,0x64001040,0x80401b07,0x80100000,0x1e000,0x22000020,0x40c0003,0xc8000002,0x3300020,0x8004020,0xc0300c0,0x300c0301,
+ 0x40c64010,0x4010008,0x2008020,0x43182210,0x84210842,0x10002190,0x24090240,0x9044018c,0xc030220,0xb81b0300,0xc03,0x2206c0c,
+ 0x302206c,0x1e0b80c0,0x30220b81,0xb0000003,0xc0881b,0x304006c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x241f2202,
+ 0x200802,0x4900000,0x8,0x21010408,0x20a0802,0x44090,0x20000000,0x4,0x11878408,0x80411004,0x804082,0x111040,0x1ce50986,0x40986409,
+ 0x81022,0x12012108,0x80102020,0x1031800,0x400,0x8000,0x80004000,0x10008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x10000000,
+ 0x10000000,0x18,0x4000044,0x1000,0x30180,0xd81b0000,0x13,0xe0000000,0x88,0x40,0x400018c0,0x80400018,0x61f00000,0x61800,0x22020020,
+ 0x4000007,0xc8000002,0x2100020,0x8038000,0x1e0781e0,0x781e0301,0x40804010,0x4010008,0x2008020,0x41142619,0x86619866,0x18002190,
+ 0x24090240,0x8887e104,0x0,0x0,0x0,0x0,0x0,0x2000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x2434a202,
+ 0x200802,0x3e00000,0x10,0x40810008,0x21a0804,0x44090,0x20000000,0x80040004,0x20848409,0x409004,0x1004082,0x112040,0x14a50902,
+ 0x40902409,0x81022,0x11321208,0x80202010,0x1060c00,0x7c5e0,0x781e8783,0xf07a5f0e,0x1c10808,0xfc5f078,0x5e07a170,0x7c7e1024,
+ 0xa016190,0x27f82008,0x2000000,0x20000000,0x10000000,0x80200024,0x4000044,0x2000,0x18180,0xc8320000,0x12,0xa1f00037,0x7f888,
+ 0x1e0,0x40410880,0x80600017,0xa2100000,0x5e800,0x22020040,0x38001027,0xc8000002,0x2100020,0x8004020,0x12048120,0x48120482,
+ 0x41004010,0x4010008,0x2008020,0x40942409,0x2409024,0x9044390,0x24090240,0x88841918,0x1f07c1f0,0x7c1f07c3,0x70781e07,0x81e07838,
+ 0xe0380e0,0x1f17c1e0,0x781e0781,0xe0001f90,0x24090240,0x9025e102,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xff241c41,
+ 0x1001,0x1c02000,0x10,0x40810008,0x6120f85,0xe0086190,0x20c03007,0x8007800c,0x27848419,0x409004,0x1004082,0x114040,0x14a48902,
+ 0x40902409,0x81022,0x11321205,0x602010,0x1000000,0x86610,0x84218840,0x80866182,0x411008,0x9261884,0x61086189,0x82101022,0x12012108,
+ 0x40082008,0x2000000,0x20030000,0x20000000,0x80200024,0x4000044,0x3006030,0xc018100,0x4c260000,0x12,0x26080048,0x83000850,
+ 0x20250,0x403e0500,0x8078002c,0x12302200,0x92400,0x1c0200c0,0x4001027,0xc8000002,0x3308820,0x8004020,0x12048120,0x48120482,
+ 0x41004010,0x4010008,0x2008020,0x40922409,0x2409024,0x8884690,0x24090240,0x85040920,0x21886218,0x86218860,0x88842108,0x42108408,
+ 0x2008020,0x21186210,0x84210842,0x10302190,0x24090240,0x88461084,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x4c240182,
+ 0x80001001,0x6b02000,0x20,0x4c810010,0x78220846,0x10081e10,0x20c0301c,0x1fe0e018,0x4d8487e1,0x409fe7,0xf9007f82,0x11a040,
+ 0x13248902,0x41102418,0xe0081022,0x11320c05,0x402008,0x1000000,0x2409,0x409020,0x81024082,0x412008,0x9240902,0x40902101,0x101022,
+ 0x11321208,0x40102008,0x2000000,0x7e0c8000,0xfc000003,0xf0fc0018,0x43802047,0x8c8040c8,0x32008300,0x44240000,0x0,0x4000048,
+ 0x8c801050,0x20440,0x40221dc0,0x808c0028,0x11d0667f,0x8009c400,0x1fc180,0x4001023,0xc8300002,0x1e0ccfb,0x3ec7b020,0x12048120,
+ 0x48120482,0x79007f9f,0xe7f9fe08,0x2008020,0xf0922409,0x2409024,0x8504490,0x24090240,0x85040920,0x802008,0x2008020,0x89004090,
+ 0x24090208,0x2008020,0x40902409,0x2409024,0x8304390,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,
+ 0x481c0606,0xc8001001,0x802000,0x20,0x4c810020,0x4220024,0x8102108,0x60000070,0x3820,0x48884419,0x409004,0x10e4082,0x112040,
+ 0x13244902,0x7e1027e0,0x3c081021,0x21320c02,0x802008,0x1000000,0x7e409,0x409020,0x81024082,0x414008,0x9240902,0x40902101,
+ 0x80101022,0x11320c08,0x40202008,0x2038800,0x200bc000,0x20000000,0x80200003,0x80f04044,0xbc080bc,0x2f000200,0x0,0x0,0x6001048,
+ 0x8bc02020,0x20441,0xf8220200,0x80820028,0x1000cc00,0x80094400,0x201e0,0x78001021,0xc830000f,0x8000663c,0xf03c0c0,0x21084210,
+ 0x84210846,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8204890,0x24090240,0x82040930,0x1f87e1f8,0x7e1f87e0,0x89004090,
+ 0x24090208,0x2008020,0x40902409,0x2409024,0x8004690,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,
+ 0x480719c4,0x48001001,0x81fc00,0x7800020,0x40810040,0x2420024,0x8104087,0xa0000070,0x3820,0x48884409,0x409004,0x1024082,0x111040,
+ 0x13244902,0x40102410,0x2081021,0x214a1202,0x1802008,0x1000000,0x182409,0x409fe0,0x81024082,0x41a008,0x9240902,0x40902100,
+ 0xf8101021,0x214a0c04,0x80c0c008,0x1847000,0x7c1ee000,0x20000000,0x8020000c,0x8c044,0x1ee181ee,0x7b800000,0x707,0xf3ff0000,
+ 0x3e0084f,0x9ee0c020,0x20440,0x40221fc0,0xc2002c,0x13f11000,0x87892400,0x20000,0x1020,0x48000000,0x3f011c6,0x31cc6180,0x21084210,
+ 0x84210844,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8505090,0x24090240,0x8204191c,0x60982609,0x82609823,0xf9007f9f,
+ 0xe7f9fe08,0x2008020,0x40902409,0x2409024,0x9fe4c90,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xfe048224,
+ 0x28001001,0x2000,0x40,0x40810080,0x27f8024,0x8104080,0x2000001c,0x1fe0e020,0x488fc409,0x409004,0x1024082,0x110840,0x10242902,
+ 0x40102408,0x2081021,0x214a1202,0x1002004,0x1000000,0x102409,0x409000,0x81024082,0x411008,0x9240902,0x40902100,0x6101021,
+ 0x214a0c04,0x81002008,0x2000000,0x201dc000,0x20000000,0x80200000,0x98044,0x1dc101dc,0x77000000,0x700,0x0,0x180448,0x1dc10020,
+ 0x20440,0x403e0200,0x620017,0xa000cc00,0x80052800,0x20000,0x1020,0x48000000,0x6606,0x206100,0x3f0fc3f0,0xfc3f0fc7,0xc1004010,
+ 0x4010008,0x2008020,0x4090a409,0x2409024,0x8886090,0x24090240,0x8207e106,0x40902409,0x2409024,0x81004010,0x4010008,0x2008020,
+ 0x40902409,0x2409024,0x8005890,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x98048224,0x30001001,0x2000,
+ 0x40,0x21010100,0x2020024,0x8204080,0x40000007,0x80078000,0x48884408,0x80411004,0x824082,0x110840,0x10242986,0x40086409,0x2081021,
+ 0xe14a2102,0x2002004,0x1000000,0x106409,0x409000,0x81024082,0x410808,0x9240902,0x40902100,0x2101021,0x214a1202,0x82002008,
+ 0x2000000,0x300f8000,0x20000000,0x80fc001d,0xe4088044,0xf8200f8,0x3e000000,0x300,0x0,0x80c48,0xf820020,0x20640,0x40410200,
+ 0x803c0018,0x60006600,0x61800,0x0,0x1020,0x48000000,0xcc0a,0x20a100,0x21084210,0x84210844,0x40804010,0x4010008,0x2008020,
+ 0x4110a619,0x86619866,0x19046110,0x24090240,0x82040102,0x41906419,0x6419064,0x81004010,0x4010008,0x2008020,0x40902409,0x2409024,
+ 0x8307090,0x24090240,0x82840828,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x90248222,0x30000802,0x200c,0xc080,0x21010301,
+ 0x4021042,0x10202108,0xc0c03000,0x80040020,0x4d902418,0xc6431004,0xc24082,0x6210440,0x10241884,0x40084409,0x86080840,0xc0842102,
+ 0x4002002,0x1000000,0x18e610,0x84218820,0x80864082,0x410408,0x9240884,0x61086101,0x6101860,0xc0842103,0x4002008,0x2000000,
+ 0x10850180,0x20330000,0x80200013,0x26184024,0x5040050,0x14000000,0x0,0x0,0x4180848,0x85040020,0x20350,0x40000200,0x800c0007,
+ 0x80002200,0x1e000,0x0,0x1860,0x48000000,0x880a,0x40a188,0x40902409,0x2409028,0x40c64010,0x4010008,0x2008020,0x43106210,0x84210842,
+ 0x10006108,0x42108421,0x2040102,0x6398e639,0x8e6398e4,0x88842088,0x22088208,0x2008020,0x21102210,0x84210842,0x10306118,0x66198661,
+ 0x83061030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0x901f01c1,0xe8000802,0xc,0xc080,0x1e07c7f8,0xf8020f81,0xe0401e07,
+ 0x80c03000,0x20,0x279027e0,0x3c7c1fe4,0x3c408f,0x83c1027f,0x90241878,0x4007c404,0xf8080780,0xc0844082,0x7f82002,0x1000000,
+ 0xfa5e0,0x781e87c0,0x807a409f,0xc0410207,0x9240878,0x5e07a100,0xf80e0fa0,0xc0846183,0x7f82008,0x2000000,0xf020100,0x40321360,
+ 0x80200014,0xa3e0201f,0x8207f820,0x8000000,0x0,0x0,0x3e01037,0x207f820,0x201e1,0xfc000200,0x80040000,0x0,0x0,0x1fc000,0x17b0,
+ 0x48000000,0x12,0xc120f0,0x40902409,0x2409028,0x783c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1061e0,0x781e0781,0xe000be07,0x81e0781e,
+ 0x204017c,0x3e8fa3e8,0xfa3e8fa3,0x70781f07,0xc1f07c7f,0x1fc7f1fc,0x1e1021e0,0x781e0781,0xe0007e0f,0xa3e8fa3e,0x8305e030,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0xc06,0xc,0x100,0x0,0x0,0x0,0x3000,0x0,0x20000000,0x0,0x0,0x0,0x0,0xc000,
+ 0x0,0x0,0x2001,0x1000000,0x0,0x0,0x20000,0x400000,0x0,0x40002000,0x0,0x1,0x2008,0x2000000,0x100,0x40240000,0x80200008,0x40000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80040000,0x0,0x0,0x0,0x1000,0x48000000,0x1f,0x181f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1040010,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x60c,0x18,0x0,
+ 0x0,0x0,0x0,0x6000,0x0,0x10000000,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x3800,0x7000000,0x0,0x0,0x840000,0x400000,0x0,0x40002000,
+ 0x0,0x2,0x2008,0x2000000,0x200,0x40440000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80780000,0x0,0x0,0x0,0x1000,0x48000400,
+ 0x2,0x1e02000,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x2040020,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x4000,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x780000,0x3800000,0x0,0x40002000,0x0,0xe,0x1808,0xc000000,0x3,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,
+ 0x0,0x0,0x0,0x1000,0x1c00,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0xe0400e0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+ // Definition of a 12x24 font.
+ const unsigned int font12x24[12*24*256/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x19,0x80000000,0x198000,0x0,0x0,0x0,0x0,
+ 0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc001806,0xc81980,0x60000000,0xc001806,0x1980c00,0x18060198,0xc80c,
+ 0x180600,0xc8198000,0xc001,0x80601980,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x600300f,0x1301980,0x90000000,0x600300f,0x1980600,0x300f0198,0x13006,0x300f01,0x30198000,0x6003,
+ 0xf01980,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x0,0x60000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7007,0x3c0000,0x3006019,
+ 0x80000000,0x90000000,0x3006019,0x80000300,0x60198000,0x3,0x601980,0x0,0x3006,0x1980000,0x60000000,0x0,0x0,0xe0000000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000000,
+ 0x0,0x0,0x0,0x0,0x0,0xc800019,0x80000000,0x198000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x1001,0x420000,0x0,0x0,0x90000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000c06,0xc80001,0x10000000,0x18000c06,0x1800,0xc060000,0xc818,0xc0600,0xc8000000,
+ 0x18000,0xc0600000,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80660207,0x800f8060,0x300c004,0x0,0x6,
+ 0xe00703f,0x3f00383,0xf80f07fc,0x1f01f000,0x0,0xf8,0x607f,0x7c7e07,0xfe7fe0f8,0x6063fc1f,0x86066007,0xe7060f0,0x7f80f07f,
+ 0x81f8fff6,0x6606c03,0x70ee077f,0xe0786000,0xf0070000,0xc000060,0xc0,0x3e000,0x60006003,0x600fc00,0x0,0x0,0x0,0x0,0x0,0x3c0603,
+ 0xc0000000,0x7800000,0xf0000,0x0,0xf00001f,0x80001fe0,0x7fe000,0x0,0x0,0x0,0x168fe609,0x0,0x90e07,0x6000,0x3c000e,0x70000f8,
+ 0x1980001f,0x0,0x1f8,0xf00000f,0xf00180,0xfe000,0xe00e,0x1001,0x20060,0x6006006,0x600600,0x600fe07c,0x7fe7fe7f,0xe7fe3fc3,
+ 0xfc3fc3fc,0x7e07060f,0xf00f00,0xf00f0000,0xf360660,0x6606606e,0x76001e0,0xc00180f,0x1681981,0x10000000,0xc00180f,0x1980c00,
+ 0x180f0198,0x3801680c,0x180f01,0x68198000,0xc001,0x80f01980,0x18600198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,
+ 0x8044020c,0xc01f8060,0x2004004,0x0,0xc,0x3f81f07f,0x87f80383,0xf81f87fc,0x3f83f800,0x0,0x1fc,0x780607f,0x81fe7f87,0xfe7fe1fc,
+ 0x6063fc1f,0x860c6007,0xe7061f8,0x7fc1f87f,0xc3fcfff6,0x6606c03,0x30c6067f,0xe0783000,0xf00d8000,0x6000060,0xc0,0x7e000,0x60006003,
+ 0x600fc00,0x0,0x0,0xc00,0x0,0x0,0x7c0603,0xe0000000,0xfc00000,0x1f0000,0x0,0x900003f,0xc0003fe0,0x7fe000,0x0,0x0,0x0,0x1302660f,
+ 0x0,0xf0606,0x6004,0x7e0006,0x60601f8,0x19800001,0x80000000,0x1f8,0x19800010,0x81080300,0x3f2000,0x2011,0x1001,0x1c0060,0x6006006,
+ 0x600600,0x601fe1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f87061f,0x81f81f81,0xf81f8000,0x3fa60660,0x66066066,0x66003f0,0x6003009,
+ 0x1301981,0x10000000,0x6003009,0x1980600,0x30090198,0x1f013006,0x300901,0x30198000,0x6003,0x901980,0x30600198,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc0f8c,0xc0180060,0x6006044,0x40000000,0xc,0x3181b041,0xc41c0783,0x388018,
+ 0x71c71800,0x0,0x106,0x18c0f061,0xc38261c6,0x600384,0x60606001,0x86186007,0xe78630c,0x60e30c60,0xe7040606,0x630cc03,0x39c30c00,
+ 0xc0603000,0x3018c000,0x3000060,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,0x60000000,0x18400000,0x180000,
+ 0x0,0x19800070,0x40003600,0xc000,0x0,0x0,0x0,0x25a06,0x0,0x6030c,0x4,0xe20007,0xe060180,0xf000,0x80000000,0xf0000,0x10800000,
+ 0x80080600,0x7f2000,0x2020,0x80001001,0x20000,0xf00f00f,0xf00f00,0x601b0382,0x60060060,0x6000600,0x60060060,0x61c78630,0xc30c30c3,
+ 0xc30c000,0x30e60660,0x66066063,0xc600738,0x3006019,0x80000000,0xe0000000,0x3006019,0x80000300,0x60198000,0x3e000003,0x601980,
+ 0x0,0x3006,0x1980000,0x60600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc1fcc,0xc0180060,0x6006035,0x80000000,
+ 0x18,0x71c03000,0xc00c0583,0x300018,0x60c60c00,0x0,0x6,0x3060f060,0xc30060c6,0x600300,0x60606001,0x86306007,0x9e78670e,0x60670e60,
+ 0x66000606,0x630c606,0x19830c01,0xc0601800,0x30306000,0x60,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,
+ 0x60000000,0x18000000,0x300000,0x0,0x78060,0x6600,0x1c000,0x300c,0x39819c0,0x0,0x25a00,0x0,0x30c,0x4,0xc00003,0xc060180,0x30c1f,
+ 0x80000000,0x30c000,0x10800001,0x80700000,0x7f2000,0x2020,0x80001001,0x20060,0xf00f00f,0xf00f00,0xf01b0300,0x60060060,0x6000600,
+ 0x60060060,0x60c78670,0xe70e70e7,0xe70e000,0x70c60660,0x66066063,0xc7f8618,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,
+ 0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x87ff3a4c,0xc0180060,0x400600e,0x600000,0x18,0x60c03000,
+ 0xc00c0d83,0x700018,0x60c60c00,0x20,0x400006,0x3060f060,0xc6006066,0x600600,0x60606001,0x86606006,0x966c6606,0x60660660,0x66000606,
+ 0x630c666,0xf019801,0x80601800,0x30603000,0x1f06f,0xf01ec0,0xf03fe1ec,0x6703e01f,0x61c0c06,0xdc6701f0,0x6f01ec0c,0xe1f87fc6,
+ 0xc60cc03,0x71c60c7f,0xc0600600,0x60000000,0x30000000,0x300000,0x40040,0x88060,0x6600,0x18000,0x300c,0x1981980,0x0,0x2421f,
+ 0x80003ce0,0x7fc198,0x601f,0xc02021,0x980600c0,0x40230,0x80000000,0x402000,0x19806003,0x80006,0xc7f2000,0x2020,0x80001001,
+ 0x420060,0xf00f00f,0xf00f00,0xf01b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x6606208,0x60e60660,0x66066061,
+ 0x987fc670,0x1f01f01f,0x1f01f01,0xf039c0f0,0xf00f00f,0xf03e03,0xe03e03e0,0x1f06701f,0x1f01f01,0xf01f0060,0x1e660c60,0xc60c60c6,
+ 0xc6f060c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x7ff3207,0x8c0c0000,0xc00300e,0x600000,0x30,0x60c03000,
+ 0xc01c0983,0xf0600030,0x31860c06,0x6001e0,0x78000e,0x23e1f861,0xc6006066,0x600600,0x60606001,0x86c06006,0x966c6606,0x60660660,
+ 0xe7000606,0x630c666,0xf01f803,0x600c00,0x30000000,0x3f87f,0x83f83fc3,0xf83fe3fc,0x7f83e01f,0x6380c07,0xfe7f83f8,0x7f83fc0d,
+ 0xf3fc7fc6,0xc71cc03,0x3183187f,0xc0600600,0x60000000,0xff806000,0x300000,0x40040,0x88070,0x6600,0x60030060,0x6001818,0x1883180,
+ 0x0,0x2423f,0xc0007ff0,0x607fc1f8,0x603f,0x80c01fc1,0xf80601e0,0x5f220,0x80420000,0x5f2000,0xf006006,0x80006,0xc7f2000,0x2020,
+ 0x82107c07,0xc03c0060,0x1f81f81f,0x81f81f80,0xf03b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x660671c,0x61660660,
+ 0x66066061,0xf860e6c0,0x3f83f83f,0x83f83f83,0xf87fe3f8,0x3f83f83f,0x83f83e03,0xe03e03e0,0x3f87f83f,0x83f83f83,0xf83f8060,
+ 0x3fc60c60,0xc60c60c3,0x187f8318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x883200,0x300c0000,0xc003035,0x80600000,
+ 0x30,0x66c03001,0xc0f81983,0xf86f0030,0x1f071c06,0x600787,0xfe1e001c,0x6261987f,0x86006067,0xfe7fc600,0x7fe06001,0x87c06006,
+ 0xf6646606,0x60e6067f,0xc3e00606,0x61986f6,0x600f007,0x600c00,0x30000000,0x21c71,0x830831c3,0x1c06031c,0x71c06003,0x6700c06,
+ 0x6671c318,0x71831c0f,0x16040c06,0xc318606,0x1b031803,0x80600600,0x60000000,0x30009000,0x300000,0x40040,0x7003e,0x67e0,0x90070090,
+ 0x9001818,0x8c3100,0x0,0x60,0x4000e730,0x900380f0,0x6034,0x80c018c7,0xfe060338,0xb0121,0x80c60000,0x909000,0x6008,0x1080006,
+ 0xc3f2000,0x2011,0x3180060,0x60060e0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0x60664660,0x66066066,
+ 0x66063b8,0x62660660,0x66066060,0xf06066c0,0x21c21c21,0xc21c21c2,0x1c466308,0x31c31c31,0xc31c0600,0x60060060,0x31871c31,0x83183183,
+ 0x18318000,0x71860c60,0xc60c60c3,0x18718318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1981a00,0xe03e0000,0xc003044,
+ 0x40600000,0x60,0x66c03001,0x80f03182,0x1c7f8030,0x3f83fc06,0x601e07,0xfe078038,0x6661987f,0x86006067,0xfe7fc61e,0x7fe06001,
+ 0x87e06006,0x66666606,0x7fc6067f,0x81f80606,0x61986f6,0x6006006,0x600600,0x30000000,0xc60,0xc60060c6,0xc06060c,0x60c06003,
+ 0x6e00c06,0x6660c60c,0x60c60c0e,0x6000c06,0xc318666,0x1f031803,0x600600,0x603c2000,0x30016800,0x1fe0000,0x1f81f8,0x1c1f,0x804067e1,
+ 0x68060168,0x16800810,0xc42300,0x0,0x60,0x20c331,0x68030060,0x6064,0x3fc1040,0xf006031c,0xa011e,0x818c7fe0,0x909000,0x7fe1f,
+ 0x80f00006,0xc0f2060,0xf80e,0x18c0780,0x780781c0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0xfc666660,
+ 0x66066066,0x66061f0,0x66660660,0x66066060,0x606066e0,0xc00c00,0xc00c00c0,0xc066600,0x60c60c60,0xc60c0600,0x60060060,0x60c60c60,
+ 0xc60c60c6,0xc60c000,0x61c60c60,0xc60c60c3,0x1860c318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1980f81,0x80373000,
+ 0xc003004,0x7fe0001,0xf0000060,0x60c03003,0x183180,0xc71c060,0x3181ec00,0x7000,0xe070,0x66619860,0xc6006066,0x60061e,0x60606001,
+ 0x87606006,0x66626606,0x7f860661,0xc01c0606,0x6198696,0xf00600e,0x600600,0x30000000,0x1fc60,0xc60060c7,0xfc06060c,0x60c06003,
+ 0x7c00c06,0x6660c60c,0x60c60c0c,0x7f00c06,0xc3b8666,0xe01b007,0x3c00600,0x3c7fe000,0xff03ec00,0x1fe0000,0x40040,0xe001,0xc0806603,
+ 0xec0e03ec,0x3ec00010,0x0,0x60000000,0x7f,0x10c3f3,0xec070060,0x6064,0x3fc1040,0x6000030c,0xa0100,0x3187fe1,0xf09f1000,0x7fe00,
+ 0x6,0xc012060,0x0,0xc63c03,0xc03c0380,0x19819819,0x81981981,0x98330600,0x60060060,0x6000600,0x60060060,0xfc662660,0x66066066,
+ 0x66060e0,0x6c660660,0x66066060,0x6060e630,0x1fc1fc1f,0xc1fc1fc1,0xfc3fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
+ 0xc60c7fe,0x62c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe02c6,0x3c633000,0xc003004,
+ 0x7fe0001,0xf00000c0,0x60c03006,0xc6180,0xc60c060,0x60c00c00,0x7000,0xe060,0x66639c60,0x66006066,0x600606,0x60606001,0x86306006,
+ 0x66636606,0x60060660,0xc0060606,0x61f8696,0xf00600c,0x600300,0x30000000,0x3fc60,0xc60060c7,0xfc06060c,0x60c06003,0x7c00c06,
+ 0x6660c60c,0x60c60c0c,0x1f80c06,0xc1b0666,0xe01b00e,0x3c00600,0x3c43c000,0x3007de00,0x600000,0x40040,0x30000,0x61006607,0xde0c07de,
+ 0x7de00000,0x0,0xf07fefff,0x1f,0x8008c3f7,0xde0e0060,0x6064,0xc01047,0xfe00018c,0xb013f,0x86300061,0xf0911000,0x6000,0x6,
+ 0xc012060,0x3f,0x8063c0cc,0x3cc0c700,0x39c39c39,0xc39c39c1,0x98630600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,
+ 0x66061f0,0x78660660,0x66066060,0x607fc618,0x3fc3fc3f,0xc3fc3fc3,0xfc7fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
+ 0xc60c7fe,0x64c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe0260,0x6661b000,0xc003000,
+ 0x600000,0xc0,0x60c0300c,0xc7fe0,0xc60c060,0x60c01c00,0x1e07,0xfe078060,0x6663fc60,0x66006066,0x600606,0x60606001,0x86386006,
+ 0x6636606,0x60060660,0xe0060606,0x60f039c,0x1b806018,0x600300,0x30000000,0x70c60,0xc60060c6,0x6060c,0x60c06003,0x7600c06,
+ 0x6660c60c,0x60c60c0c,0x1c0c06,0xc1b03fc,0xe01f01c,0xe00600,0x70000000,0x3007fc00,0x600000,0x40040,0x0,0x62006607,0xfc1807fc,
+ 0x7fc00000,0x0,0xf0000000,0x1,0xc004c307,0xfc1c0060,0x6064,0xc018c0,0x600000d8,0x5f200,0x3180060,0x50a000,0x6000,0x6,0xc012000,
+ 0x0,0xc601c0,0x4201c600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,0x66063b8,
+ 0x70660660,0x66066060,0x607f860c,0x70c70c70,0xc70c70c7,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,
+ 0x68c60c60,0xc60c60c1,0xf060c1f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3300260,0x6661e000,0xc003000,0x600000,
+ 0x180,0x71c03018,0xc7fe0,0xc60c0c0,0x60c01800,0x787,0xfe1e0060,0x6663fc60,0x630060c6,0x600306,0x60606001,0x86186006,0x661e70e,
+ 0x60070c60,0x60060606,0x60f039c,0x19806038,0x600180,0x30000000,0x60c60,0xc60060c6,0x6060c,0x60c06003,0x6700c06,0x6660c60c,
+ 0x60c60c0c,0xc0c06,0xc1b039c,0x1f00e018,0x600600,0x60000000,0x1803f800,0x600000,0x40040,0x39e00,0x63006603,0xf83803f8,0x3f800000,
+ 0x0,0x60000000,0x0,0xc00cc303,0xf8180060,0x6064,0xc01fc0,0x60060070,0x40200,0x18c0060,0x402000,0x6000,0x6,0xc012000,0x0,0x18c0140,
+ 0x2014600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0300,0x60060060,0x6000600,0x60060060,0x60c61e70,0xe70e70e7,0xe70e71c,0x60e60660,0x66066060,
+ 0x6060060c,0x60c60c60,0xc60c60c6,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,0x70c60c60,0xc60c60c0,
+ 0xe060c0e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x33022e0,0x6670c000,0xc003000,0x600600,0x60180,0x31803030,
+ 0x41c0184,0x1831c0c0,0x71c23806,0x6001e0,0x780000,0x62630c60,0xe38261c6,0x600386,0x60606043,0x860c6006,0x661e30c,0x60030c60,
+ 0x740e0607,0xe0f039c,0x31c06030,0x600180,0x30000000,0x61c71,0x830831c3,0x406031c,0x60c06003,0x6300c06,0x6660c318,0x71831c0c,
+ 0x41c0c07,0x1c0e039c,0x1b00e030,0x600600,0x60000000,0x1c41b00e,0x601cc0,0x401f8,0x45240,0xe1803601,0xb03001b0,0x1b000000,
+ 0x0,0x0,0x41,0xc008e711,0xb0300060,0x6034,0x80c02020,0x60060030,0x30c00,0xc60000,0x30c000,0x0,0x7,0x1c012000,0x0,0x3180240,
+ 0x6024608,0x30c30c30,0xc30c30c3,0xc630382,0x60060060,0x6000600,0x60060060,0x61c61e30,0xc30c30c3,0xc30c208,0x70c70e70,0xe70e70e0,
+ 0x6060068c,0x61c61c61,0xc61c61c6,0x1cc62308,0x30430430,0x43040600,0x60060060,0x31860c31,0x83183183,0x18318060,0x31c71c71,
+ 0xc71c71c0,0xe07180e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2203fc0,0x663f6000,0x6006000,0x600600,0x60300,
+ 0x3f81fe7f,0xc7f80187,0xf83f80c0,0x3f83f006,0x600020,0x400060,0x33e6067f,0xc1fe7f87,0xfe6001fe,0x6063fc7f,0x60e7fe6,0x660e3f8,
+ 0x6001f860,0x37fc0603,0xfc06030c,0x30c0607f,0xe06000c0,0x30000000,0x7fc7f,0x83f83fc3,0xfc0603fc,0x60c7fe03,0x61807c6,0x6660c3f8,
+ 0x7f83fc0c,0x7f80fc3,0xfc0e039c,0x3180607f,0xc0600600,0x60000000,0xfc0e00c,0x601986,0x66040040,0x4527f,0xc0803fe0,0xe07fe0e0,
+ 0xe000000,0x0,0x0,0x7f,0x80107ff0,0xe07fc060,0x603f,0x83fe0000,0x60060018,0xf000,0x420000,0xf0000,0x7fe00,0x7,0xfe012000,
+ 0x0,0x2100640,0xc0643f8,0x60660660,0x66066067,0xec3e1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f860e3f,0x83f83f83,0xf83f8000,
+ 0x5fc3fc3f,0xc3fc3fc0,0x606006fc,0x7fc7fc7f,0xc7fc7fc7,0xfcffe3f8,0x3fc3fc3f,0xc3fc7fe7,0xfe7fe7fe,0x3f860c3f,0x83f83f83,
+ 0xf83f8060,0x7f83fc3f,0xc3fc3fc0,0x607f8060,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2201f80,0x3c1e7000,0x6006000,
+ 0x600,0x60300,0xe01fe7f,0xc3f00183,0xe01f0180,0x1f01e006,0x600000,0x60,0x3006067f,0x807c7e07,0xfe6000f8,0x6063fc3e,0x6067fe6,
+ 0x660e0f0,0x6000f060,0x3bf80601,0xf806030c,0x60e0607f,0xe06000c0,0x30000000,0x1ec6f,0xf01ec0,0xf80601ec,0x60c7fe03,0x61c03c6,
+ 0x6660c1f0,0x6f01ec0c,0x3f007c1,0xcc0e030c,0x71c0c07f,0xc0600600,0x60000000,0x7804018,0xe01186,0x66040040,0x39e3f,0x80401fe0,
+ 0x407fe040,0x4000000,0x0,0x0,0x3f,0x203ce0,0x407fc060,0x601f,0x3fe0000,0x60060018,0x0,0x0,0x0,0x7fe00,0x6,0xe6012000,0x0,
+ 0x7e0,0x1807e1f0,0x60660660,0x66066066,0x6c3e07c,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7e060e0f,0xf00f00,0xf00f0000,0x8f01f81f,
+ 0x81f81f80,0x60600670,0x1ec1ec1e,0xc1ec1ec1,0xec79c0f0,0xf80f80f,0x80f87fe7,0xfe7fe7fe,0x1f060c1f,0x1f01f01,0xf01f0000,0x4f01cc1c,
+ 0xc1cc1cc0,0xc06f00c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x6006000,0x600,0x600,0x0,0x0,0x0,0x0,
+ 0x600000,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x600060,0x30000000,0x0,0x0,0xc,0x3,0x0,0x0,0x60000c00,0x0,
+ 0x0,0xc000,0x600600,0x60000000,0x18,0xc03100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f8,0x0,0x0,0x0,0x0,0x6,
+ 0x12000,0x2000000,0x40,0x20004000,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0xc06000c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x2004000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,
+ 0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0xc00,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x21c,0x3,0x0,0x0,0x60000c00,0x0,0x0,0xc000,
+ 0x7c0603,0xe0000000,0x10,0xc02300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f0,0x0,0x0,0x0,0x0,0x6,0x12000,0x1000000,
+ 0x40,0x7e004000,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc06000c0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x300c000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,0x0,0x7800000,0x0,
+ 0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x3f8,0x3e,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x3c0603,0xc0000000,
+ 0x10,0xfc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x60000,0x0,0x0,0x0,0x0,0x6,0x0,0x1000000,0x0,0x0,0x0,0x0,
+ 0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,
+ 0x0,0x1f0,0x3c,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x600,0x0,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x6,0x0,0xe000000,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+ // Definition of a 16x32 font.
+ const unsigned int font16x32[16*32*256/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70000e0,0x3c00730,0xe7001c0,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0x730,0x70000e0,0x3c00730,
+ 0xe700000,0x700,0xe003c0,0xe7000e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x18001c0,0x6600ff0,0xe7003e0,0x0,0x18001c0,0x6600e70,0x18001c0,0x6600e70,0xff0,0x18001c0,0x6600ff0,0xe700000,0x180,
+ 0x1c00660,0xe7001c0,0x0,0x0,0x0,0x380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,
+ 0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00380,
+ 0xc300ce0,0xe700630,0x0,0x1c00380,0xc300e70,0x1c00380,0xc300e70,0xce0,0x1c00380,0xc300ce0,0xe700000,0x1c0,0x3800c30,0xe700380,
+ 0x0,0x0,0x0,0x7c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x700000,0x0,0x0,0x0,0x7c007c00,0x3e000000,
+ 0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000070,0x1800000,0xc60,0x0,0xe000070,0x1800000,0xe000070,
+ 0x1800000,0x0,0xe000070,0x1800000,0x0,0xe00,0x700180,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x800000,0x0,0x600600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x3f0,0xfc0,0x0,0x7000000,0x38000000,0x1c0000,0xfc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,
+ 0x1801f00,0x0,0x0,0x1c,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7300000,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0xe700000,
+ 0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0x0,0xc000c00,0x43800000,0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0xf80,0x70000e0,0x3c00730,0xe700c60,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0xe000730,0x70000e0,0x3c00730,0xe700000,0x700,
+ 0xe003c0,0xe7000e0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300000,0x803c00,0x7c00180,
+ 0xc00300,0x1000000,0x0,0x1c,0x3c007c0,0xfc007e0,0xe01ff8,0x3f03ffc,0x7e007c0,0x0,0x0,0x7c0,0x1c0,0x7f8003f0,0x7f007ff8,0x7ff803f0,
+ 0x70381ffc,0xff0700e,0x7000783c,0x783807c0,0x7fc007c0,0x7fc00fc0,0x7fff7038,0x700ee007,0x780f780f,0x7ffc03f0,0x70000fc0,0x3c00000,
+ 0x3000000,0x38000000,0x1c0000,0x1fc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x1801f80,0x0,0x1f80000,
+ 0x7e,0x0,0x0,0x2400000,0xfc00000,0x7ff0000,0x7ffc0000,0x0,0x0,0x0,0x0,0xf30fb0c,0x2400000,0x0,0x240780f,0x1c0,0xfc,0x780f,
+ 0x18003f0,0xe700000,0x7c00000,0x0,0xff0,0x3c00000,0x78007c0,0xc00000,0xff80000,0xf80,0x7c00000,0xc000c00,0x18001c0,0x1c001c0,
+ 0x1c001c0,0x1c003e0,0x7fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007838,0x7c007c0,0x7c007c0,0x7c00000,0x7c67038,
+ 0x70387038,0x7038780f,0x70001fe0,0x30000c0,0x2400f30,0xe700c60,0x0,0x30000c0,0x2400e70,0x30000c0,0x2400e70,0xf700f30,0x30000c0,
+ 0x2400f30,0xe700000,0x300,0xc00240,0xe7000c0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,
+ 0x630018c,0x807e00,0xfe00180,0xc00300,0x1000000,0x0,0x38,0xff01fc0,0x3ff01ff0,0x1e01ff8,0x7f83ffc,0x1ff80ff0,0x0,0x0,0xff0,
+ 0x1f003e0,0x7fe00ff8,0x7fc07ff8,0x7ff80ff8,0x70381ffc,0xff0701c,0x7000783c,0x78381ff0,0x7fe01ff0,0x7fe01ff0,0x7fff7038,0x781ee007,
+ 0x3c1e380e,0x7ffc0380,0x380001c0,0x3c00000,0x1800000,0x38000000,0x1c0000,0x3c00000,0x380001c0,0xe01c00,0x3800000,0x0,0x0,
+ 0x0,0x7000000,0x0,0x0,0x1e0,0x18003c0,0x0,0x3fc0000,0x70,0x0,0x0,0x6600000,0x1ff00000,0x1fff0000,0x7ffc0000,0x0,0x0,0x0,0x0,
+ 0xcf0239c,0x3c00000,0x0,0x3c0380e,0x1c0,0x2001fe,0x380e,0x18007f8,0xe700000,0x8600000,0x0,0xff0,0x7e00000,0x8c00870,0x1800000,
+ 0x1ff80000,0x180,0xc600000,0xc000c00,0x38001c0,0x3e003e0,0x3e003e0,0x3e001c0,0x7fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,
+ 0x7fc07838,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x1fec7038,0x70387038,0x7038380e,0x70003ce0,0x1800180,0x6600cf0,0xe7007c0,0x0,
+ 0x1800180,0x6600e70,0x1800180,0x6600e70,0x7c00cf0,0x1800180,0x6600cf0,0xe700000,0x180,0x1800660,0xe700180,0x38000e70,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630030c,0x3f0e700,0x1e200180,0x1800180,0x21100000,0x0,
+ 0x38,0x1e7819c0,0x38781038,0x1e01c00,0xf080038,0x1c381c38,0x0,0x0,0x1878,0x7fc03e0,0x70e01e18,0x70e07000,0x70001e18,0x703801c0,
+ 0x707038,0x70007c7c,0x7c381c70,0x70701c70,0x70703830,0x1c07038,0x381ce007,0x1c1c3c1e,0x3c0380,0x380001c0,0x7e00000,0xc00000,
+ 0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,0x70c0000,0xe0,
+ 0x0,0x0,0xc300000,0x38300000,0x3c700000,0x3c0000,0x0,0x0,0x0,0x0,0xce022f4,0x1800000,0x0,0x1803c1e,0x1c0,0x2003c2,0x3c1e,
+ 0x1800e08,0x7e0,0x300000,0x0,0x7e00000,0xe700000,0x600030,0x3000000,0x3f980000,0x180,0x18200000,0xc000c00,0x1e0001c0,0x3e003e0,
+ 0x3e003e0,0x3e003e0,0xfe01e18,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70e07c38,0x1c701c70,0x1c701c70,0x1c700000,0x3c787038,
+ 0x70387038,0x70383c1e,0x70003870,0xc00300,0xc300ce0,0x380,0x0,0xc00300,0xc300000,0xc00300,0xc300000,0xfc00ce0,0xc00300,0xc300ce0,
+ 0x0,0xc0,0x3000c30,0x300,0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630031c,0xff8c300,
+ 0x1c000180,0x1800180,0x39380000,0x0,0x70,0x1c3801c0,0x203c001c,0x3e01c00,0x1c000038,0x381c3838,0x0,0x0,0x1038,0xe0e03e0,0x70703c08,
+ 0x70707000,0x70003808,0x703801c0,0x707070,0x70007c7c,0x7c383838,0x70383838,0x70387010,0x1c07038,0x381c700e,0x1e3c1c1c,0x780380,
+ 0x1c0001c0,0xe700000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,
+ 0x0,0xe000000,0xe0,0x0,0x1000100,0x3800,0x70100000,0x38700000,0x780000,0x1c0,0x7801ce0,0xe380000,0x0,0x2264,0x0,0x0,0x1c1c,
+ 0x0,0x200780,0x1c1c,0x1800c00,0x1818,0x7f00000,0x0,0x18180000,0xc300000,0x600070,0x0,0x7f980000,0x180,0x18300000,0xc000c00,
+ 0x3000000,0x3e003e0,0x3e003e0,0x3e003e0,0xee03c08,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,
+ 0x38380000,0x38387038,0x70387038,0x70381c1c,0x7fc03870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xbc00000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0xe88c300,0x1c000180,0x38001c0,
+ 0xfe00180,0x0,0x70,0x1c3801c0,0x1c001c,0x6e01c00,0x1c000078,0x381c3818,0x0,0x40000,0x40000038,0x1c0607e0,0x70703800,0x70707000,
+ 0x70003800,0x703801c0,0x7070e0,0x70007c7c,0x7c383838,0x70383838,0x70387000,0x1c07038,0x381c700e,0xf780e38,0x700380,0x1c0001c0,
+ 0x1c380000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,
+ 0xe000000,0xe0,0x0,0x1000100,0x4400,0x70000000,0x38700000,0x700000,0xe0,0x7001c70,0xe380000,0x0,0x2264,0x0,0x0,0xe38,0x0,
+ 0x200700,0xe38,0x1800c00,0x300c,0xc300000,0x0,0x300c0000,0xc300180,0x6003c0,0x0,0x7f980000,0x180,0x18300000,0xc000c00,0x1800000,
+ 0x7e007e0,0x7e007e0,0x7e003e0,0xee03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,0x38380000,
+ 0x38387038,0x70387038,0x70380e38,0x7ff039f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x40000,0x0,0x0,0x38000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0x1c80e700,0x1c000180,0x38001c0,0x3800180,
+ 0x0,0xe0,0x381c01c0,0x1c001c,0x6e01c00,0x38000070,0x381c381c,0x0,0x3c0000,0x78000078,0x38030770,0x70707800,0x70387000,0x70007000,
+ 0x703801c0,0x7071c0,0x7000745c,0x7638701c,0x7038701c,0x70387000,0x1c07038,0x1c38718e,0x7700f78,0xf00380,0xe0001c0,0x381c0000,
+ 0x7e0,0x39e003e0,0x79c03f0,0x3ffc079c,0x39e01fc0,0xfe01c1e,0x3807778,0x39e007e0,0x39e0079c,0x73c07e0,0x7ff83838,0x701ce007,
+ 0x783c701c,0x1ffc01c0,0x18001c0,0x0,0x1c000100,0xe0,0x0,0x1000100,0x4200,0x70000000,0x70700100,0xf00100,0x10000e0,0x7000c70,
+ 0xc700000,0x0,0x2204,0x7e00000,0x1e380100,0x1ffc0f78,0x0,0xf80700,0xf78,0x1800e00,0x63e6,0x18300000,0x0,0x6fe60000,0xe700180,
+ 0xc00060,0x3838,0x7f980000,0x180,0x18300000,0xc000c00,0x18001c0,0x7700770,0x7700770,0x77007f0,0xee07800,0x70007000,0x70007000,
+ 0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1008,0x707c7038,0x70387038,0x70380f78,0x707039c0,0x7e007e0,0x7e007e0,
+ 0x7e007e0,0x1f3c03e0,0x3f003f0,0x3f003f0,0x1fc01fc0,0x1fc01fc0,0x7f039e0,0x7e007e0,0x7e007e0,0x7e00380,0x7ce3838,0x38383838,
+ 0x3838701c,0x39e0701c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6307fff,0x1c807e0c,0xe000180,
+ 0x30000c0,0x3800180,0x0,0xe0,0x381c01c0,0x1c001c,0xce01fe0,0x38000070,0x381c381c,0x3800380,0xfc0000,0x7e0000f0,0x30030770,
+ 0x70707000,0x70387000,0x70007000,0x703801c0,0x707380,0x700076dc,0x7638701c,0x7038701c,0x70387800,0x1c07038,0x1c3873ce,0x7f00770,
+ 0xe00380,0xe0001c0,0x700e0000,0x1ff8,0x3ff00ff0,0xffc0ff8,0x3ffc0ffc,0x3bf01fc0,0xfe01c3c,0x3807f78,0x3bf00ff0,0x3ff00ffc,
+ 0x77e0ff0,0x7ff83838,0x3838e007,0x3c783838,0x1ffc01c0,0x18001c0,0x0,0x7ff00380,0x1e0,0x0,0x1000100,0x4200,0x78000000,0x70700380,
+ 0xe00380,0x3800060,0xe000e30,0x1c600000,0x0,0x2204,0xff00000,0x7f7c0380,0x1ffc0770,0x1c0,0x3fc0700,0x18040770,0x1800780,0x4e12,
+ 0x18300104,0x0,0x4c320000,0x7e00180,0x1c00030,0x3838,0x7f980000,0x180,0x18302080,0xc000c00,0x18001c0,0x7700770,0x7700770,
+ 0x7700770,0x1ee07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c381c,0x705c7038,0x70387038,
+ 0x70380770,0x70383b80,0x1ff81ff8,0x1ff81ff8,0x1ff81ff8,0x3fbe0ff0,0xff80ff8,0xff80ff8,0x1fc01fc0,0x1fc01fc0,0xff83bf0,0xff00ff0,
+ 0xff00ff0,0xff00380,0xffc3838,0x38383838,0x38383838,0x3ff03838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x1c0,0x7fff,0x1c803c38,0xf000000,0x70000e0,0xfe00180,0x0,0x1c0,0x381c01c0,0x3c0078,0xce01ff0,0x39e000f0,0x1c38381c,0x3800380,
+ 0x3e07ffc,0xf8001f0,0x307b0770,0x70e07000,0x70387000,0x70007000,0x703801c0,0x707700,0x700076dc,0x7638701c,0x7038701c,0x70387e00,
+ 0x1c07038,0x1c3873ce,0x3e007f0,0x1e00380,0x70001c0,0x0,0x1038,0x3c381e18,0x1c7c1e3c,0x3801e3c,0x3c7801c0,0xe01c78,0x380739c,
+ 0x3c781c38,0x3c381c3c,0x7c21e10,0x7003838,0x3838700e,0x1ef03838,0x3c01c0,0x18001c0,0x0,0x7fe007c0,0x1c0,0x0,0x1000100,0x6400,
+ 0x7e000000,0x707007c0,0x1e007c0,0x7c00070,0xe000638,0x18600000,0x0,0x0,0x1e100000,0x73ce07c0,0x3c07f0,0x1c0,0x7240700,0x1ddc3ffe,
+ 0x1800de0,0x8c01,0x1870030c,0x0,0x8c310000,0x3c00180,0x3800030,0x3838,0x7f980000,0x180,0x183030c0,0xc000c00,0x430001c0,0x7700770,
+ 0x7700770,0x7700770,0x1ce07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1c38,0x70dc7038,
+ 0x70387038,0x703807f0,0x70383b80,0x10381038,0x10381038,0x10381038,0x21e71e18,0x1e3c1e3c,0x1e3c1e3c,0x1c001c0,0x1c001c0,0x1e383c78,
+ 0x1c381c38,0x1c381c38,0x1c380380,0x1c383838,0x38383838,0x38383838,0x3c383838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0x1e8000e0,0x1f000000,0x70000e0,0x39380180,0x0,0x1c0,0x3b9c01c0,0x3c07f0,0x18e01078,0x3bf800e0,
+ 0x7e0383c,0x3800380,0x1f807ffc,0x3f001c0,0x61ff0e38,0x7fc07000,0x70387ff0,0x7ff07000,0x7ff801c0,0x707f00,0x7000729c,0x7338701c,
+ 0x7070701c,0x70703fc0,0x1c07038,0x1e7873ce,0x1c003e0,0x3c00380,0x70001c0,0x0,0x1c,0x3c381c00,0x1c3c1c1c,0x3801c3c,0x383801c0,
+ 0xe01cf0,0x380739c,0x38381c38,0x3c381c3c,0x7801c00,0x7003838,0x3838700e,0xfe03c78,0x7801c0,0x18001c0,0x0,0x1c000c20,0xff8,
+ 0x0,0x1ff01ff0,0x3818,0x3fc00100,0x707e0c20,0x3c00c20,0xc200030,0xc000618,0x18c00000,0x0,0x0,0x1c000080,0xe1ce0c20,0x7803e0,
+ 0x1c0,0xe200700,0xff83ffe,0x1801878,0x9801,0x1cf0071c,0x7ffc0000,0x8c310000,0x7ffe,0x7000030,0x3838,0x3f980380,0x180,0xc6038e0,
+ 0x7f9c7f9c,0x3e1c01c0,0xe380e38,0xe380e38,0xe380f78,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,0x1c001c0,0xfe387338,0x701c701c,
+ 0x701c701c,0x701c0e70,0x719c7038,0x70387038,0x703803e0,0x70383b80,0x1c001c,0x1c001c,0x1c001c,0xe71c00,0x1c1c1c1c,0x1c1c1c1c,
+ 0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380000,0x3c383838,0x38383838,0x38383c78,0x3c383c78,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0xf800380,0x3f830000,0x70000e0,0x31080180,0x0,0x380,0x3b9c01c0,
+ 0x7807e0,0x38e00038,0x3c3800e0,0xff01c3c,0x3800380,0x7c000000,0x7c03c0,0x61870e38,0x7fc07000,0x70387ff0,0x7ff070fc,0x7ff801c0,
+ 0x707f80,0x7000739c,0x7338701c,0x7ff0701c,0x7fe00ff0,0x1c07038,0xe7073ce,0x1c003e0,0x3800380,0x38001c0,0x0,0x1c,0x381c3800,
+ 0x381c380e,0x380381c,0x383801c0,0xe01de0,0x380739c,0x3838381c,0x381c381c,0x7001e00,0x7003838,0x1c70718e,0x7e01c70,0xf00380,
+ 0x18001e0,0x1e000000,0x1c001bb0,0xff8,0x0,0x1000100,0xe0,0xff00300,0x707e1bb0,0x3801bb0,0x1bb00010,0x8000308,0x30c00000,0x0,
+ 0x0,0x1e0000c0,0xe1ce1bb0,0xf003e0,0x1c0,0x1c203ff8,0x63003e0,0x180181c,0x9801,0xfb00e38,0x7ffc0000,0x8fc10000,0x7ffe,0xe000860,
+ 0x3838,0x1f980380,0x180,0x7c01c70,0x1f001f0,0x1f003c0,0xe380e38,0xe380e38,0xe380e38,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,
+ 0x1c001c0,0xfe387338,0x701c701c,0x701c701c,0x701c07e0,0x731c7038,0x70387038,0x703803e0,0x70383980,0x1c001c,0x1c001c,0x1c001c,
+ 0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x387c3838,0x38383838,0x38381c70,
+ 0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc30,0x7f00e00,0x33c30000,0x70000e0,0x1007ffe,
+ 0x0,0x380,0x3b9c01c0,0xf00078,0x30e0001c,0x3c1c01c0,0x1c381fdc,0x0,0x70000000,0x1c0380,0x63030e38,0x70707000,0x70387000,0x700070fc,
+ 0x703801c0,0x707b80,0x7000739c,0x7338701c,0x7fc0701c,0x7fc001f0,0x1c07038,0xe703e5c,0x3e001c0,0x7800380,0x38001c0,0x0,0x7fc,
+ 0x381c3800,0x381c380e,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7001fc0,0x7003838,0x1c70718e,0x7c01c70,
+ 0xe01f00,0x180007c,0x7f8c0000,0x7fc03fb8,0x1c0,0x0,0x1000100,0x700,0x1f00600,0x70703fb8,0x7803fb8,0x3fb80000,0x8000000,0x180,
+ 0x0,0x0,0x1fc00060,0xe1ce3fb8,0xe001c0,0x1c0,0x1c203ff8,0xc1801c0,0x180c,0x9801,0x1c70,0xc0000,0x8cc10000,0x180,0xfe007c0,
+ 0x3838,0x7980380,0xff0,0xe38,0x3e003e00,0x3e000380,0xe380e38,0xe380e38,0xe380e38,0x38e07000,0x70007000,0x70007000,0x1c001c0,
+ 0x1c001c0,0x70387338,0x701c701c,0x701c701c,0x701c03c0,0x731c7038,0x70387038,0x703801c0,0x703838e0,0x7fc07fc,0x7fc07fc,0x7fc07fc,
+ 0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x38dc3838,0x38383838,0x38381c70,
+ 0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc60,0xf83878,0x71e30000,0x70000e0,0x1007ffe,
+ 0x7f0,0x380,0x381c01c0,0x1e0003c,0x60e0001c,0x381c01c0,0x381c079c,0x0,0x7c000000,0x7c0380,0x63031c1c,0x70307000,0x70387000,
+ 0x7000701c,0x703801c0,0x7071c0,0x7000739c,0x71b8701c,0x7000701c,0x71e00078,0x1c07038,0xe703e7c,0x7e001c0,0xf000380,0x38001c0,
+ 0x0,0x1ffc,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fc0,0x380739c,0x3838381c,0x381c381c,0x7000ff0,0x7003838,0x1ef03bdc,
+ 0x3800ee0,0x1e01f00,0x180007c,0x61fc0000,0x7fc07f3c,0x1c0,0x0,0x1000100,0x1800,0x780c00,0x70707f3c,0xf007f3c,0x7f3c0000,0x0,
+ 0x3c0,0x3ffcffff,0x0,0xff00030,0xe1fe7f3c,0x1e001c0,0x1c0,0x1c200700,0xc183ffe,0xe0c,0x9801,0x1ff038e0,0xc07f0,0x8c610000,
+ 0x180,0x0,0x3838,0x1980380,0x0,0x1ff0071c,0xe000e000,0xe0000f80,0x1c1c1c1c,0x1c1c1c1c,0x1c1c1e38,0x38e07000,0x70007000,0x70007000,
+ 0x1c001c0,0x1c001c0,0x703871b8,0x701c701c,0x701c701c,0x701c03c0,0x761c7038,0x70387038,0x703801c0,0x70703870,0x1ffc1ffc,0x1ffc1ffc,
+ 0x1ffc1ffc,0xfff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x389c3838,0x38383838,
+ 0x38380ee0,0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xfffc,0xbc60fc,0x70e30000,0x70000e0,
+ 0x180,0x7f0,0x700,0x381c01c0,0x3e0001c,0x7ffc001c,0x381c03c0,0x381c001c,0x0,0x1f807ffc,0x3f00380,0x63031ffc,0x70387000,0x70387000,
+ 0x7000701c,0x703801c0,0x7071e0,0x7000701c,0x71b8701c,0x7000701c,0x70f00038,0x1c07038,0x7e03e7c,0x77001c0,0xe000380,0x1c001c0,
+ 0x0,0x3c1c,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x70003f8,0x7003838,0xee03bdc,
+ 0x3c00ee0,0x3c00380,0x18000e0,0xf00000,0x1c007e7c,0x3c0,0x0,0x1000100,0x0,0x381800,0x70707e7c,0xe007e7c,0x7e7c0000,0x0,0x7c0,
+ 0x0,0x0,0x3f80018,0xe1fe7e7c,0x3c001c0,0x1c0,0x1c200700,0xc183ffe,0xf0c,0x8c01,0x38e0,0xc07f0,0x8c710000,0x180,0x0,0x3838,
+ 0x1980000,0x0,0x71c,0x7000f0,0x700f00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x3fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
+ 0x703871b8,0x701c701c,0x701c701c,0x701c07e0,0x7c1c7038,0x70387038,0x703801c0,0x7ff03838,0x3c1c3c1c,0x3c1c3c1c,0x3c1c3c1c,
+ 0x3fff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x391c3838,0x38383838,0x38380ee0,
+ 0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffc,0x9c01ce,0x70f60000,0x70000e0,0x180,
+ 0x0,0x700,0x381c01c0,0x780001c,0x7ffc001c,0x381c0380,0x381c003c,0x0,0x3e07ffc,0xf800380,0x63031ffc,0x70387000,0x70387000,
+ 0x7000701c,0x703801c0,0x7070f0,0x7000701c,0x71b8701c,0x7000701c,0x70700038,0x1c07038,0x7e03e7c,0xf7801c0,0x1e000380,0x1c001c0,
+ 0x0,0x381c,0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7000078,0x7003838,0xee03a5c,
+ 0x7c00fe0,0x78001c0,0x18001c0,0x0,0x1c003ef8,0x380,0x0,0x1000100,0x810,0x383000,0x70703ef8,0x1e003ef8,0x3ef80000,0x0,0x7c0,
+ 0x0,0x0,0x78000c,0xe1c03ef8,0x78001c0,0x1c0,0x1c200700,0x63001c0,0x18003f8,0x4e12,0x1c70,0xc0000,0x4c320000,0x180,0x0,0x3838,
+ 0x1980000,0x0,0xe38,0x700118,0x701e00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x7fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
+ 0x703871b8,0x701c701c,0x701c701c,0x701c0e70,0x7c1c7038,0x70387038,0x703801c0,0x7fc0381c,0x381c381c,0x381c381c,0x381c381c,
+ 0x78e03800,0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x3b1c3838,0x38383838,0x38380fe0,
+ 0x381c0fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1860,0x9c0186,0x707e0000,0x30000c0,0x180,
+ 0x0,0xe00,0x183801c0,0xf00001c,0xe0001c,0x181c0380,0x381c0038,0x0,0xfc0000,0x7e000000,0x61873c1e,0x70383800,0x70707000,0x7000381c,
+ 0x703801c0,0x707070,0x7000701c,0x70f83838,0x70003838,0x70780038,0x1c07038,0x7e03c3c,0xe3801c0,0x1c000380,0xe001c0,0x0,0x381c,
+ 0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01ef0,0x380739c,0x3838381c,0x381c381c,0x7000038,0x7003838,0xfe03e7c,0xfe007c0,
+ 0x70001c0,0x18001c0,0x0,0xe001ff0,0x380,0x0,0x1000100,0x162c,0x381800,0x30701ff0,0x1c001ff0,0x1ff00000,0x0,0x3c0,0x0,0x0,
+ 0x380018,0xe1c01ff0,0x70001c0,0x1c0,0x1c200700,0xff801c0,0x18000f0,0x63e6,0xe38,0x0,0x6c3e0000,0x0,0x0,0x3838,0x1980000,0x0,
+ 0x1c70,0xf0000c,0xf01c00,0x3c1e3c1e,0x3c1e3c1e,0x3c1e3c1c,0x70e03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x707070f8,
+ 0x38383838,0x38383838,0x38381c38,0x38387038,0x70387038,0x703801c0,0x7000381c,0x381c381c,0x381c381c,0x381c381c,0x70e03800,
+ 0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0380,0x3e1c3838,0x38383838,0x383807c0,0x381c07c0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18c0,0x9c0186,0x783c0000,0x38001c0,0x180,0x3800000,
+ 0x3800e00,0x1c3801c0,0x1e00003c,0xe00038,0x1c1c0780,0x381c0038,0x3800380,0x3c0000,0x78000000,0x61ff380e,0x70383808,0x70707000,
+ 0x7000381c,0x703801c0,0x40707078,0x7000701c,0x70f83838,0x70003838,0x70384038,0x1c07038,0x7e03c3c,0x1e3c01c0,0x3c000380,0xe001c0,
+ 0x0,0x383c,0x3c381c00,0x1c3c1c00,0x3801c3c,0x383801c0,0xe01c78,0x380739c,0x38381c38,0x3c381c3c,0x7000038,0x7003878,0x7c01e78,
+ 0x1ef007c0,0xf0001c0,0x18001c0,0x0,0xe000ee0,0x7800380,0xe380000,0x1001ff0,0x2242,0x40380c00,0x38700ee0,0x3c000ee0,0xee00000,
+ 0x0,0x0,0x0,0x0,0x380030,0xe1c00ee0,0xf0001c0,0x1c0,0xe200700,0xdd801c0,0x1800038,0x300c,0x71c,0x0,0x300c0000,0x0,0x0,0x3838,
+ 0x1980000,0x0,0x38e0,0xb0000c,0xb01c08,0x380e380e,0x380e380e,0x380e380e,0x70e03808,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
+ 0x707070f8,0x38383838,0x38383838,0x3838381c,0x38387038,0x70387038,0x703801c0,0x7000381c,0x383c383c,0x383c383c,0x383c383c,
+ 0x70e01c00,0x1c001c00,0x1c001c00,0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383878,0x38783878,0x387807c0,
+ 0x3c3807c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x18c0,0x10b801ce,0x3c3e0000,0x38001c0,0x180,
+ 0x3800000,0x3801c00,0x1e7801c0,0x3c002078,0xe02078,0x1c380700,0x1c3810f0,0x3800380,0x40000,0x40000380,0x307b380e,0x70701e18,
+ 0x70e07000,0x70001c1c,0x703801c0,0x60e0703c,0x7000701c,0x70f83c78,0x70003c70,0x703c70f0,0x1c03870,0x3c01c3c,0x3c1c01c0,0x78000380,
+ 0x7001c0,0x0,0x3c7c,0x3c381e18,0x1c7c1e0c,0x3801c3c,0x383801c0,0xe01c38,0x3c0739c,0x38381c38,0x3c381c3c,0x7001078,0x7803c78,
+ 0x7c01c38,0x1c780380,0x1e0001c0,0x18001c0,0x0,0x70c06c0,0x7000380,0xe300000,0x1000100,0x2142,0x70f00600,0x3c7006c0,0x780006c0,
+ 0x6c00000,0x0,0x0,0x0,0x0,0x10780060,0x73e206c0,0x1e0001c0,0x1c0,0x7240700,0x180c01c0,0x1800018,0x1818,0x30c,0x0,0x18180000,
+ 0x0,0x0,0x3c78,0x1980000,0x0,0x30c0,0x130000c,0x1301c18,0x380e380e,0x380e380e,0x380e380e,0x70e01e18,0x70007000,0x70007000,
+ 0x1c001c0,0x1c001c0,0x70e070f8,0x3c783c78,0x3c783c78,0x3c781008,0x7c783870,0x38703870,0x387001c0,0x70003a3c,0x3c7c3c7c,0x3c7c3c7c,
+ 0x3c7c3c7c,0x79f11e18,0x1e0c1e0c,0x1e0c1e0c,0x1c001c0,0x1c001c0,0x1c783838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383c78,0x3c783c78,
+ 0x3c780380,0x3c380380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x38c0,0x1ff800fc,0x1fee0000,
+ 0x1800180,0x180,0x3800000,0x3801c00,0xff01ffc,0x3ffc3ff0,0xe03ff0,0xff00700,0x1ff81fe0,0x3800380,0x0,0x380,0x3000780f,0x7ff00ff8,
+ 0x7fc07ff8,0x70000ffc,0x70381ffc,0x7fe0701c,0x7ff8701c,0x70781ff0,0x70001ff0,0x701c7ff0,0x1c01fe0,0x3c01c38,0x380e01c0,0x7ffc0380,
+ 0x7001c0,0x0,0x1fdc,0x3ff00ff0,0xffc0ffc,0x3800fdc,0x38383ffe,0xe01c3c,0x1fc739c,0x38380ff0,0x3ff00ffc,0x7001ff0,0x3f81fb8,
+ 0x7c01c38,0x3c3c0380,0x1ffc01c0,0x18001c0,0x0,0x3fc0380,0x7000380,0xc70718c,0x1000100,0x2244,0x7ff00200,0x1fff0380,0x7ffc0380,
+ 0x3800000,0x0,0x0,0x0,0x0,0x1ff000c0,0x7f7e0380,0x1ffc01c0,0x1c0,0x3fc3ffe,0x1c0,0x1800018,0x7e0,0x104,0x0,0x7e00000,0x7ffe,
+ 0x0,0x3fde,0x1980000,0x0,0x2080,0x3300018,0x3300ff0,0x780f780f,0x780f780f,0x780f780e,0xf0fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,
+ 0x1ffc1ffc,0x7fc07078,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x7ff01fe0,0x1fe01fe0,0x1fe001c0,0x70003bf8,0x1fdc1fdc,0x1fdc1fdc,
+ 0x1fdc1fdc,0x3fbf0ff0,0xffc0ffc,0xffc0ffc,0x3ffe3ffe,0x3ffe3ffe,0xff03838,0xff00ff0,0xff00ff0,0xff00000,0x3ff01fb8,0x1fb81fb8,
+ 0x1fb80380,0x3ff00380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x31c0,0x7e00078,0x7cf0000,0x1800180,
+ 0x0,0x3800000,0x3803800,0x3c01ffc,0x3ffc0fe0,0xe01fc0,0x3e00e00,0x7e00f80,0x3800380,0x0,0x380,0x18007007,0x7fc003f0,0x7f007ff8,
+ 0x700003f0,0x70381ffc,0x3f80701e,0x7ff8701c,0x707807c0,0x700007c0,0x701e1fc0,0x1c00fc0,0x3c01818,0x780f01c0,0x7ffc0380,0x3801c0,
+ 0x0,0xf9c,0x39e003e0,0x79c03f0,0x380079c,0x38383ffe,0xe01c1e,0x7c739c,0x383807e0,0x39e0079c,0x7000fc0,0x1f80f38,0x3801c38,
+ 0x781e0380,0x1ffc01c0,0x18001c0,0x0,0x1f80100,0xe000700,0x1c60718c,0x1000100,0x1e3c,0x1fc00100,0x7ff0100,0x7ffc0100,0x1000000,
+ 0x0,0x0,0x0,0x0,0xfc00080,0x3e3c0100,0x1ffc01c0,0x1c0,0xf83ffe,0x1c0,0x1800838,0x0,0x0,0x0,0x0,0x7ffe,0x0,0x3b9e,0x1980000,
+ 0x0,0x0,0x2300038,0x23003e0,0x70077007,0x70077007,0x70077007,0xe0fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007078,
+ 0x7c007c0,0x7c007c0,0x7c00000,0xc7c00fc0,0xfc00fc0,0xfc001c0,0x700039f0,0xf9c0f9c,0xf9c0f9c,0xf9c0f9c,0x1f1e03e0,0x3f003f0,
+ 0x3f003f0,0x3ffe3ffe,0x3ffe3ffe,0x7e03838,0x7e007e0,0x7e007e0,0x7e00000,0x63e00f38,0xf380f38,0xf380380,0x39e00380,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x3000000,0x3800,0x0,0x0,0x0,0x0,
+ 0x0,0x300,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0,0x0,0x0,0x0,0x0,0x380,0x3801c0,0x0,0x0,0x0,0x0,0x1c,0x0,0xe00000,
+ 0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1c0,0x18001c0,0x0,0x0,0xe000700,0x18600000,0x1000100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800ff0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0x1800000,0x0,0x6300070,0x6300000,0x0,
+ 0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000000,
+ 0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x7000000,
+ 0x7000,0x0,0x0,0x0,0x0,0x0,0x700,0x0,0x0,0xf040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x3f0,0x1c0fc0,0x0,0x0,
+ 0x0,0x0,0x1c,0x0,0xe00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1e0,0x18003c0,0x0,0x0,0xc000700,0x18c00000,0x1000000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x18007e0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
+ 0x0,0x7f800e0,0x7f80000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,
+ 0x0,0x600600,0x0,0x6000000,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,
+ 0x3f0,0xfc0,0x0,0x0,0x0,0x0,0x838,0x0,0x1e00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0xf00,0xfc,0x1801f80,0x0,0x0,0x8008e00,0x30c00000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
+ 0x0,0x3001c0,0x300000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0xf00,0x38000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0xff0,0x0,0x1fc00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3e00,0x7c,0x1801f00,0x0,0x0,0x800fe00,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7c00000,0x0,0x3001fc,0x300000,
+ 0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x3e00,0x38003e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x7e0,0x0,0x1f000000,
+ 0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3c00,0x0,0x1800000,0x0,0x0,0x7800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00,0x38003c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+ // Definition of a 19x38 font.
+ const unsigned int font19x38[19*38*256/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c380000,0x0,0x1c380,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800007,0x3c003,0x86000000,
+ 0x1e00000,0x3,0x80000700,0x3c00000,0x380000,0x70003c00,0x0,0xe1800e,0x1c00,0xf000e18,0x0,0x0,0x700000e0,0x780000,0x7000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe700000,0x0,0xe700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0000e,0x7e003,0xe60071c0,0x7f80000,0x1,0xc0000e00,0x7e0038e,0x1c0000,
+ 0xe0007e00,0x38e00000,0xf98007,0x3800,0x1f800f98,0x1c70000,0x0,0x380001c0,0xfc0071,0xc000e000,0x0,0x0,0x0,0x0,0x3e00000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x7e00000,0x0,0x7e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0001c,0xe7006,0x7c0071c0,0xe180000,0x0,0xe0001c00,0xe70038e,0xe0001,0xc000e700,0x38e00000,
+ 0x19f0003,0x80007000,0x39c019f0,0x1c70000,0x0,0x1c000380,0x1ce0071,0xc001c000,0x0,0x0,0x0,0x0,0x7f00000,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,
+ 0x0,0x3c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x700038,0x1c3806,0x3c0071c0,0xc0c0000,0x0,0x70003800,0x1c38038e,0x70003,0x8001c380,0x38e00000,0x18f0001,0xc000e000,
+ 0x70e018f0,0x1c70000,0x0,0xe000700,0x3870071,0xc0038000,0x0,0x0,0x0,0x0,0xe380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60000000,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c38,0x0,0x1,0xc3800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000003,0x80018000,0x0,0xc180000,
+ 0xe,0x380,0x1800000,0xe00000,0x38001800,0x0,0x38,0xe00,0x6000000,0x0,0x1,0xc0000070,0x300000,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78c00,0xc30,
+ 0x0,0x0,0xc3000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800000,0x0,0x0,0x0,0xe0,0x1c000f,0xc0000000,0x0,0x0,
+ 0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000007,0x3c003,0xc6000000,0xc180000,0x7,0x700,
+ 0x3c00000,0x700000,0x70003c00,0x0,0xf1801c,0x1c00,0xf000f18,0x0,0x0,0xe00000e0,0x780000,0x7000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x3800000,0x700000,0x38,
+ 0x7,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf800e,0x3e0000,0x0,0x0,0x0,0x1e00000,0x0,0x1,
+ 0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7cc00,0x660,0x0,0x0,0x66000000,0x0,0x0,0x0,0x0,0x7,0x1c000000,0x0,0x0,0x0,0x3fe00000,
+ 0x0,0x0,0x7000000,0x0,0x0,0x0,0x3e0,0x7c001f,0xe0000000,0x0,0x0,0x0,0xe1c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x1f80,0x380000e,0x7e007,0xe60071c0,0xc180000,0x3,0x80000e00,0x7e0038e,0x380000,0xe0007e00,0x38e00f00,0x1f9800e,
+ 0x3800,0x1f801f98,0x1c70000,0x0,0x700001c0,0xfc0071,0xc000e007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61c00600,0x1e00007e,0x70000,0x18003000,0x1800000,0x0,0x0,0x1c01f0,0x7e003f,0xc003f800,
+ 0x1e03ffc,0x7f01ff,0xfc03f000,0x7e000000,0x0,0x0,0xfc0,0x1e,0x7fe000,0x7e03fe00,0x3fff07ff,0xe007e038,0x383ffe0,0xff81c01,
+ 0xe1c000f8,0xf8f00e0,0xfc01ffc,0x3f00ff,0xc000fe07,0xfffc7007,0x1c007700,0x73c01ef,0x78ffff,0xfe0380,0xfe000,0x38000000,0x1800000,
+ 0x700000,0x38,0x1f,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0xfc0000,
+ 0x0,0x7f00000,0x0,0x1,0x98000000,0x7f00000,0x3ffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0xcf81f,0xee3807e0,0x0,0x0,0x7e03c01e,0x1c,
+ 0x0,0x1f800000,0xf0078038,0xfc007,0x1c000000,0xfe00000,0x0,0x0,0x3fe000f0,0xf,0xc001f800,0x6000000,0xffc000,0x0,0x1c0007e0,
+ 0x360,0x6c0010,0x70000700,0xf0001e,0x3c000,0x78000f00,0x7f800ff,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0,
+ 0x7807007,0xe000fc00,0x1f8003f0,0x7e0000,0x1f867,0x70e00e,0x1c01c380,0x38f00787,0x3fe0,0x180000c,0x66006,0x7c0071c0,0xe380000,
+ 0x1,0x80000c00,0x660038e,0x180000,0xc0006600,0x38e0078e,0x19f0006,0x3000,0x198019f0,0x1c70000,0x0,0x30000180,0xcc0071,0xc000c007,
+ 0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61800600,0x7f8001ff,0x70000,
+ 0x38003800,0x1800000,0x0,0x0,0x3807fc,0x1fe00ff,0xf00ffe00,0x3e03ffc,0xff81ff,0xfc07fc01,0xff800000,0x0,0x0,0x3fe0,0xfe001e,
+ 0x7ff801,0xff83ff80,0x3fff07ff,0xe01ff838,0x383ffe0,0xff81c03,0xc1c000f8,0xf8f80e0,0x3ff01fff,0xffc0ff,0xf003ff87,0xfffc7007,
+ 0x1e00f700,0x71c03c7,0x70ffff,0xfe01c0,0xfe000,0x7c000000,0xc00000,0x700000,0x38,0x3f,0xe000001c,0x1c00,0x1c00700,0x7fc0000,
+ 0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0x3fe0000,0x0,0xff00000,0x0,0x3,0xc000000,0x1ffc0000,0xfffe00,
+ 0xffff0,0x0,0x0,0x0,0x0,0x0,0xc781f,0xee3803c0,0x0,0x0,0x3c01c01c,0x1c,0xc000,0x7fc00000,0x70070038,0x3fe007,0x1c000000,0x1ff80000,
+ 0x0,0x0,0x3fe003fc,0x1f,0xe003fc00,0xc000000,0x3ffc000,0x0,0x7c000ff0,0x60,0xc0000,0x30000700,0xf0001e,0x3c000,0x78000f00,
+ 0x3f000ff,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x7c0701f,0xf803ff00,0x7fe00ffc,0x1ff8000,0x7fe67,
+ 0x70e00e,0x1c01c380,0x38700707,0x7ff0,0xc00018,0xc3006,0x3c0071c0,0x7f00000,0x0,0xc0001800,0xc30038e,0xc0001,0x8000c300,0x38e003fc,
+ 0x18f0003,0x6000,0x30c018f0,0x1c70000,0x0,0x18000300,0x1860071,0xc0018007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe1801fc0,0x618001ff,0x70000,0x30001800,0x21840000,0x0,0x0,0x380ffe,0x1fe00ff,
+ 0xfc0fff00,0x3e03ffc,0x1ff81ff,0xfc0ffe03,0xffc00000,0x0,0x0,0x7ff0,0x3ff803f,0x7ffc03,0xffc3ffc0,0x3fff07ff,0xe03ffc38,0x383ffe0,
+ 0xff81c07,0x81c000f8,0xf8f80e0,0x7ff81fff,0x81ffe0ff,0xf80fff87,0xfffc7007,0xe00e700,0x70e0387,0x80f0ffff,0xe001c0,0xe000,
+ 0xfe000000,0xe00000,0x700000,0x38,0x3c,0x1c,0x1c00,0x1c00700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x78000e,0x3c000,
+ 0x0,0x7ff0000,0x0,0xf100000,0x0,0x7,0xe000000,0x7ffc0000,0x1fffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0x3,0xf780180,0x0,0x0,0x1801e03c,
+ 0x1c,0xc000,0xffc00000,0x780f0038,0x786000,0x7f00,0x18380000,0x0,0xfe00,0x30c,0x10,0x70020e00,0x1c000000,0x7f8c000,0x0,0x6c001c38,
+ 0x60,0xc0000,0x70000700,0x1f8003f,0x7e000,0xfc001f80,0x3f000ff,0xf03ffc1f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc,
+ 0x7c0703f,0xfc07ff80,0xfff01ffe,0x3ffc000,0xffec7,0x70e00e,0x1c01c380,0x38780f07,0xf070,0xe00038,0x1c3800,0x0,0x3e00000,0x0,
+ 0xe0003800,0x1c380000,0xe0003,0x8001c380,0x3e0,0x3,0x8000e000,0x70e00000,0x0,0x0,0x1c000700,0x3870000,0x38007,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe3807ff0,0xc0c003c1,0x70000,0x70001c00,
+ 0x718e0000,0x0,0x0,0x700f1e,0x1ce00c0,0x3c0c0f80,0x7e03800,0x3e08000,0x381e0f03,0xc1e00000,0x0,0x0,0x7078,0x783c03f,0x701e07,
+ 0xc1c383e0,0x38000700,0x7c1c38,0x3801c00,0x381c0f,0x1c000fc,0x1f8f80e0,0x78781c07,0x81e1e0e0,0x780f0180,0xe007007,0xe00e380,
+ 0xe0f0783,0x80e0000e,0xe000e0,0xe001,0xef000000,0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,
+ 0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xf830000,0x0,0x1e000000,0x0,0x0,0x10000,0x780c0000,0x3e38000,0xe0,0x0,0x0,0x0,0x0,0x0,0x3,
+ 0xd580000,0x0,0x0,0xe038,0x1c,0xc000,0xf0400000,0x380e0038,0x702000,0x1ffc0,0xc0000,0x0,0x3ff80,0x606,0x0,0x30000600,0x0,
+ 0x7f8c000,0x0,0xc001818,0x60,0xc0003,0xe0000700,0x1f8003f,0x7e000,0xfc001f80,0x73801ee,0x7c1c1c,0x38000,0x70000e00,0xe0001,
+ 0xc0003800,0x700383e,0x7c0703c,0x3c078780,0xf0f01e1e,0x3c3c000,0xf0f87,0x70e00e,0x1c01c380,0x38380e07,0xe038,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0xff0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xc380fff0,0xc0c00380,0x70000,0x70001c00,0x3dbc0070,0x0,0x0,0x701e0f,0xe0000,0x1e000380,
+ 0x6e03800,0x7800000,0x781c0707,0x80e00000,0x0,0x0,0x4038,0xe00c03f,0x700e07,0x4380f0,0x38000700,0x700438,0x3801c00,0x381c0e,
+ 0x1c000ec,0x1b8fc0e0,0xf03c1c03,0xc3c0f0e0,0x3c1e0000,0xe007007,0xe00e380,0xe070703,0xc1e0001e,0xe000e0,0xe001,0xc7000000,
+ 0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xe010000,0x0,
+ 0x1c000000,0x10,0x20000,0x6c000,0xf0000000,0x3838000,0x1e0,0x0,0xf000f,0xf1e00,0x78f00000,0x0,0x3,0xdd80000,0x0,0x0,0xf078,
+ 0x0,0xc001,0xe0000000,0x1c1c0038,0x700000,0x3c1e0,0xc0000,0x0,0x783c0,0x606,0x0,0x30000e00,0x0,0xff8c000,0x0,0xc00300c,0x60,
+ 0xc0003,0xe0000000,0x1f8003f,0x7e000,0xfc001f80,0x73801ce,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x7e07078,
+ 0x1e0f03c1,0xe0783c0f,0x781e000,0x1c0787,0x70e00e,0x1c01c380,0x383c1e07,0xff00e038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x878,
+ 0x0,0x0,0x0,0x7,0x80000080,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,
+ 0x1c7000,0xc301e630,0xc0c00380,0x70000,0xe0000e00,0xff00070,0x0,0x0,0xe01c07,0xe0000,0xe000380,0xce03800,0x7000000,0x701c0707,
+ 0x600000,0x0,0x4000010,0x38,0x1c00e07f,0x80700e0e,0x38070,0x38000700,0xe00038,0x3801c00,0x381c1c,0x1c000ec,0x1b8ec0e0,0xe01c1c01,
+ 0xc38070e0,0x1c1c0000,0xe007007,0x701c380,0xe078e01,0xc1c0003c,0xe00070,0xe003,0x83800000,0x7f,0x71f000,0x3e003e38,0x3f007ff,
+ 0xe01f1c1c,0x7801fc00,0x3fc00701,0xe01c0077,0x8f071e00,0xf801c7c,0x7c700e,0x3e01fc03,0xfff8380e,0xe007700,0x73c0787,0x387ffc,
+ 0x70000e,0x1c000,0x0,0xe000000,0x0,0x1c000000,0x10,0x20000,0xc2000,0xe0000000,0x3838000,0x3c0,0x0,0xf000f,0x78e00,0x70e00000,
+ 0x0,0x3,0xc980fe0,0x1f0,0xf8000007,0xffc07070,0x0,0x3f801,0xc0000000,0x1e3c0038,0x700000,0x70070,0x7fc0000,0x0,0xe00e0,0x606,
+ 0x1c0000,0x70007c00,0x380e,0xff8c000,0x0,0xc00300c,0x60,0xc0000,0x70000000,0x3fc007f,0x800ff001,0xfe003fc0,0x73801ce,0xe0001c,
+ 0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807,0x7607070,0xe0e01c1,0xc0383807,0x700e000,0x1c0387,0x70e00e,0x1c01c380,0x381c1c07,
+ 0xffc0e0f8,0x3f8007f,0xfe001,0xfc003f80,0x7f007e3,0xe003e001,0xf8003f00,0x7e000fc,0xfe001f,0xc003f800,0x7f00003c,0x38f0007,
+ 0xc000f800,0x1f0003e0,0x7c0007,0x8003f0c3,0x80e0701c,0xe0381c0,0x70700387,0x1f01c00e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0xc0c00380,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03c07,
+ 0x800e0000,0xe000380,0x1ce03800,0x7000000,0x701c0707,0x7003c0,0x780000,0x3c00001e,0x38,0x18006073,0x80700e0e,0x38070,0x38000700,
+ 0xe00038,0x3801c00,0x381c38,0x1c000ee,0x3b8ee0e1,0xe01e1c01,0xc78078e0,0x1c1c0000,0xe007007,0x701c387,0xe03de00,0xe3800038,
+ 0xe00070,0xe007,0x1c00000,0x1ff,0xc077f801,0xff807fb8,0xff807ff,0xe03fdc1d,0xfc01fc00,0x3fc00703,0xc01c007f,0xdf877f00,0x3fe01dfe,
+ 0xff700e,0xff07ff03,0xfff8380e,0x700f700,0x71e0f03,0x80707ffc,0x70000e,0x1c000,0x0,0x1c000008,0x0,0x1c000000,0x10,0x20000,
+ 0x82000,0xe0000000,0x7038000,0x80000380,0x2000040,0x7000e,0x38700,0xf1e00000,0x0,0x3,0xc183ff8,0x3fd,0xfc008007,0xffc038e0,
+ 0x0,0xffc01,0xc0008008,0xe380038,0x380000,0xe3e38,0x1ffc0040,0x80000000,0x1cfc70,0x606,0x1c0000,0xe0007c00,0x380e,0xff8c000,
+ 0x0,0xc00300c,0x8100060,0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0x73801ce,0xe0001c,0x38000,0x70000e00,0xe0001,
+ 0xc0003800,0x7003807,0x77070f0,0xf1e01e3,0xc03c7807,0x8f00f080,0x83c0787,0x70e00e,0x1c01c380,0x380e3807,0xffe0e1c0,0xffe01ff,
+ 0xc03ff807,0xff00ffe0,0x1ffc0ff7,0xf01ff807,0xfc00ff80,0x1ff003fe,0xfe001f,0xc003f800,0x7f0003fc,0x3bf801f,0xf003fe00,0x7fc00ff8,
+ 0x1ff0007,0x8007fd83,0x80e0701c,0xe0381c0,0x70380707,0x7f80e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0x618081c0,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03803,0x800e0000,0xe000380,0x18e03800,
+ 0xf000000,0xf01c0707,0x7003c0,0x780000,0xfc00001f,0x80000078,0x301e6073,0x80700e1c,0x38038,0x38000700,0x1c00038,0x3801c00,
+ 0x381c70,0x1c000e6,0x338ee0e1,0xc00e1c01,0xc70038e0,0x1c1c0000,0xe007007,0x701c387,0xe01dc00,0xf7800078,0xe00070,0xe00e,0xe00000,
+ 0x3ff,0xe07ffc03,0xffc0fff8,0x1ffc07ff,0xe07ffc1d,0xfe01fc00,0x3fc00707,0x801c007f,0xdf877f80,0x7ff01fff,0x1fff00e,0xff07ff03,
+ 0xfff8380e,0x700e380,0xe0e0e03,0x80707ffc,0x70000e,0x1c000,0x0,0x7ffc001c,0x0,0x1c000000,0x10,0x20000,0x82000,0xe0000000,
+ 0x7038001,0xc0000780,0x70000e0,0x3800e,0x38700,0xe1c00000,0x0,0x3,0xc183ff8,0x7ff,0xfc01c007,0xffc03de0,0x0,0x1ffc01,0xc001c01c,
+ 0xf780038,0x3c0000,0xcff18,0x380c00c1,0x80000000,0x18fe30,0x30c,0x1c0001,0xc0000e00,0x380e,0xff8c000,0x0,0xc00300c,0xc180060,
+ 0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x877070e0,
+ 0x71c00e3,0x801c7003,0x8e0071c0,0x1c380fc7,0x70e00e,0x1c01c380,0x380f7807,0x1e0e380,0x1fff03ff,0xe07ffc0f,0xff81fff0,0x3ffe0fff,
+ 0xf03ffc0f,0xfe01ffc0,0x3ff807ff,0xfe001f,0xc003f800,0x7f0007fe,0x3bfc03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x800fff83,0x80e0701c,
+ 0xe0381c0,0x70380707,0xffc0e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f,
+ 0xfff1c600,0x7f8381e0,0x70000,0xc0000600,0xff00070,0x0,0x0,0x1c03803,0x800e0000,0xe000f00,0x38e03fe0,0xe000000,0xe00e0e07,
+ 0x7003c0,0x780007,0xf0ffff87,0xf00000f0,0x307fe0f3,0xc0703c1c,0x38038,0x38000700,0x1c00038,0x3801c00,0x381ce0,0x1c000e6,0x338e70e1,
+ 0xc00e1c01,0xc70038e0,0x3c1e0000,0xe007007,0x783c38f,0x8e01fc00,0x770000f0,0xe00038,0xe01c,0x700000,0x381,0xe07c1e07,0xc0c1e0f8,
+ 0x3c1e0038,0xf07c1f,0xe001c00,0x1c0070f,0x1c0079,0xf3c7c380,0xf0781f07,0x83c1f00f,0xc10f0300,0x1c00380e,0x700e380,0xe0f1e03,
+ 0xc0f00078,0x70000e,0x1c000,0x0,0xfff8003e,0x0,0x3c000000,0x10,0x20000,0xc6000,0xf0000000,0x7038003,0xe0000f00,0xf8001f0,
+ 0x3801c,0x18300,0xe1800000,0x0,0x3,0xc187818,0x70f,0x9e03e000,0x7801dc0,0x1c,0x3cc401,0xc000efb8,0x7f7f0038,0x3f0000,0x1ce11c,
+ 0x300c01c3,0x80000000,0x38c638,0x3fc,0x1c0003,0x80000600,0x380e,0xff8c000,0x0,0xc00300c,0xe1c0060,0xc0010,0x70000700,0x79e00f3,
+ 0xc01e7803,0xcf0079e0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003,
+ 0x8e0070e0,0x38381dc7,0x70e00e,0x1c01c380,0x38077007,0xf0e700,0x1c0f0381,0xe0703c0e,0x781c0f0,0x381e083e,0x787c0c1e,0xf03c1e0,
+ 0x783c0f07,0x800e0001,0xc0003800,0x7000fff,0x3e1c078,0x3c0f0781,0xe0f03c1e,0x783c000,0x1e0f03,0x80e0701c,0xe0381c0,0x70380f07,
+ 0xc1e0e03c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1,0x8701c600,0x1e0f01e0,0x1,
+ 0xc0000700,0x3dbc0070,0x0,0x0,0x1c03803,0x800e0000,0x1e01fe00,0x70e03ff8,0xe3e0001,0xe007fc07,0x80f003c0,0x78001f,0xc0ffff81,
+ 0xfc0001e0,0x30e1e0e1,0xc07ff81c,0x38038,0x3ffe07ff,0xc1c0003f,0xff801c00,0x381de0,0x1c000e7,0x738e70e1,0xc00e1c03,0xc70038e0,
+ 0x780f8000,0xe007007,0x383838d,0x8e00f800,0x7f0000e0,0xe00038,0xe000,0x0,0x200,0xf0780e07,0x8041c078,0x380e0038,0xe03c1e,
+ 0xf001c00,0x1c0071e,0x1c0070,0xe1c783c0,0xe0381e03,0x8380f00f,0xe0000,0x1c00380e,0x381c380,0xe07bc01,0xc0e00078,0x70000e,
+ 0x1c000,0x0,0x1c000061,0x0,0x38000000,0x10,0x20000,0x7c000,0x7c000000,0x703fc06,0x10000e00,0x18400308,0x1801c,0x1c381,0xc3800000,
+ 0x0,0x0,0x7000,0xe0f,0xe061000,0x7801fc0,0x1c,0x38c001,0xc0007ff0,0x7fff0038,0x77c000,0x19c00c,0x301c0387,0x0,0x30c618,0xf0,
+ 0x1c0007,0x600,0x380e,0x7f8c007,0x80000000,0xc001818,0x70e03fc,0x387f871f,0xe0e00700,0x70e00e1,0xc01c3803,0x870070e0,0xe1c038f,
+ 0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003,0x8e007070,0x703839c7,0x70e00e,
+ 0x1c01c380,0x3807f007,0x70e700,0x10078200,0xf0401e08,0x3c10078,0x200f001c,0x3878041c,0x70380e0,0x701c0e03,0x800e0001,0xc0003800,
+ 0x7001e0f,0x3c1e070,0x1c0e0381,0xc070380e,0x701c000,0x1c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80e07038,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600e600,0x7803f0,0x1,0xc0000700,0x718e0070,0x0,0x0,0x38038c3,
+ 0x800e0000,0x3c01f800,0x60e03ffc,0xeff8001,0xc001f003,0xc1f003c0,0x7800fe,0xffff80,0x3f8003c0,0x60c0e0e1,0xc07fe01c,0x38038,
+ 0x3ffe07ff,0xc1c07e3f,0xff801c00,0x381fe0,0x1c000e3,0x638e30e1,0xc00e1c07,0x870038ff,0xf00ff800,0xe007007,0x38381cd,0x9c007000,
+ 0x3e0001e0,0xe0001c,0xe000,0x0,0x0,0x70780f0f,0x3c078,0x70070038,0x1e03c1c,0x7001c00,0x1c0073c,0x1c0070,0xe1c701c1,0xe03c1e03,
+ 0xc780f00f,0xe0000,0x1c00380e,0x381c387,0xe03f801,0xc0e000f0,0x70000e,0x1c007,0xe0100000,0x1c0000cd,0x80000003,0xffc00000,
+ 0x3ff,0x807ff000,0xe0,0x7fc00060,0x703fc0c,0xd8001e00,0x3360066c,0x1c018,0xc181,0x83000000,0x0,0x0,0x7000,0x300e07,0xe0cd800,
+ 0xf000f80,0x1c,0x78c00f,0xff0038e0,0x3e00038,0xe1e000,0x19800c,0x383c070e,0x7fffc00,0x30fc18,0x0,0xffff80e,0x20e00,0x380e,
+ 0x7f8c007,0x80000000,0xc001c38,0x38703ff,0xf87fff0f,0xcfe00f00,0x70e00e1,0xc01c3803,0x870070e0,0x1e1e078f,0xe1c0001f,0xff03ffe0,
+ 0x7ffc0fff,0x800e0001,0xc0003800,0x700ff83,0x871870e0,0x71c00e3,0x801c7003,0x8e007038,0xe03871c7,0x70e00e,0x1c01c380,0x3803e007,
+ 0x70e700,0x38000,0x70000e00,0x1c00038,0x7001c,0x38f00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7001c07,0x380e0f0,0x1e1e03c3,
+ 0xc078780f,0xf01e000,0x3c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80f07038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600ff00,0x1e00778,0x38000001,0xc0000700,0x21843fff,0xe0000000,0x0,0x38039e3,0x800e0000,
+ 0x7c01fe00,0xe0e0203e,0xeffc001,0xc00ffe03,0xff700000,0x7f0,0x0,0x7f00380,0x618060e1,0xc07ffc1c,0x38038,0x3ffe07ff,0xc1c07e3f,
+ 0xff801c00,0x381ff0,0x1c000e3,0x638e38e1,0xc00e1fff,0x870038ff,0xc003fe00,0xe007007,0x38381cd,0x9c00f800,0x3e0003c0,0xe0001c,
+ 0xe000,0x0,0x0,0x7070070e,0x38038,0x70070038,0x1c01c1c,0x7001c00,0x1c00778,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0xfc000,
+ 0x1c00380e,0x381c3c7,0x1e01f001,0xe1e001e0,0xf0000e,0x1e01f,0xf8300000,0x1c00019c,0xc0000003,0xffc00000,0x10,0x20000,0x700,
+ 0x1ff000c0,0x703fc19,0xcc003c00,0x67300ce6,0xc038,0xc181,0x83000000,0x0,0x0,0x7e00,0x180e07,0xe19cc00,0x1e000f80,0x1c,0x70c00f,
+ 0xff007070,0x3e00038,0xe0f000,0x19800c,0x1fec0e1c,0x7fffc00,0x30f818,0x0,0xffff81f,0xf003fc00,0x380e,0x3f8c007,0x80000000,
+ 0x7f800ff0,0x1c3803f,0xe007fc00,0xff800e00,0x70e00e1,0xc01c3803,0x870070e0,0x1c0e070f,0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001,
+ 0xc0003800,0x700ff83,0x871c70e0,0x71c00e3,0x801c7003,0x8e00701d,0xc038e1c7,0x70e00e,0x1c01c380,0x3803e007,0x70e3c0,0x38000,
+ 0x70000e00,0x1c00038,0x7001c,0x38e00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7003c07,0x8380e0e0,0xe1c01c3,0x80387007,
+ 0xe00e1ff,0xfe381b83,0x80e0701c,0xe0381c0,0x701e1e07,0x707878,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x1c,0x3,0xe007fe0,0x7800e3c,0x38000001,0xc0000700,0x1803fff,0xe0000000,0x0,0x70039c3,0x800e0000,0xf8000f80,
+ 0xc0e0000e,0xf83c003,0xc01e0f01,0xff700000,0x7c0,0x0,0x1f00780,0x618061c0,0xe0701e1c,0x38038,0x38000700,0x1c07e38,0x3801c00,
+ 0x381e78,0x1c000e3,0xe38e18e1,0xc00e1fff,0x70038ff,0xe0007f80,0xe007007,0x1c701dd,0x9c00f800,0x1c000780,0xe0000e,0xe000,0x0,
+ 0x7f,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x7fc00,0x1c00380e,
+ 0x1c381c7,0x1c01f000,0xe1c001c0,0xfe0000e,0xfe1f,0xfff00000,0x7ff003fc,0xe0000003,0xffc00000,0x10,0x20000,0x3800,0x3fc0180,
+ 0x703803f,0xce007800,0xff381fe7,0x30,0x0,0xc0,0x0,0x0,0x3fe0,0xc0e07,0xfe3fce00,0x1c000700,0x1c,0x70c00f,0xff006030,0x1c00000,
+ 0xe07800,0x19800c,0xfcc1c38,0x7fffc00,0x30d818,0x0,0xffff81f,0xf001f800,0x380e,0xf8c007,0x80000000,0x7f8007e0,0xe1c3fe,0x7fc00f,
+ 0xf8001e00,0xe0701c0,0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700ff83,0x870c70e0,
+ 0x71c00e3,0x801c7003,0x8e00700f,0x8038c1c7,0x70e00e,0x1c01c380,0x3801c007,0xf0e3e0,0x3ff807f,0xf00ffe01,0xffc03ff8,0x7ff03ff,
+ 0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe383383,0x80e0701c,
+ 0xe0381c0,0x700e1c07,0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0xc000ff0,
+ 0x3c1e1c1c,0x38000001,0xc0000700,0x1803fff,0xe0000007,0xf8000000,0x7003803,0x800e0001,0xf0000381,0xc0e00007,0xf01e003,0x801c0700,
+ 0x7c700000,0x7c0,0x0,0x1f00700,0x618061c0,0xe0700e1c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381e38,0x1c000e1,0xc38e1ce1,
+ 0xc00e1ffc,0x70038e0,0xf0000780,0xe007007,0x1c701dd,0xdc01fc00,0x1c000780,0xe0000e,0xe000,0x0,0x1ff,0xf070070e,0x38038,0x7fff0038,
+ 0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3ff00,0x1c00380e,0x1c381cd,0x9c00e000,0xe1c003c0,
+ 0xf80000e,0x3e18,0x3ff00000,0xffe007fd,0xf0000000,0x38000000,0x10,0x20000,0x1c000,0x3c0300,0x703807f,0xdf007801,0xff7c3fef,
+ 0x80000000,0x0,0x3e0,0x7ffe7ff,0xff000000,0x1ff8,0x60e07,0xfe7fdf00,0x3c000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0xf03800,
+ 0x19800c,0x1c38,0x1c07,0xf830cc18,0x0,0x1c0000,0x0,0x380e,0x18c007,0x80000000,0x0,0xe1cfe0,0x1fc003f,0x80003c00,0xe0701c0,
+ 0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003,
+ 0x8e007007,0x3981c7,0x70e00e,0x1c01c380,0x3801c007,0x1e0e0f8,0xfff81ff,0xf03ffe07,0xffc0fff8,0x1fff07ff,0xf8e0003f,0xff87fff0,
+ 0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe386383,0x80e0701c,0xe0381c0,0x700e1c07,
+ 0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x7f,0xffc00678,0x707f9c1e,0x38000001,
+ 0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0003,0xe00001c3,0x80e00007,0xe00e007,0x80380380,0x700000,0x7f0,0x0,0x7f00700,
+ 0x618061ff,0xe070071c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381c3c,0x1c000e1,0xc38e1ce1,0xc00e1c00,0x70038e0,0x700003c0,
+ 0xe007007,0x1c701d8,0xdc03dc00,0x1c000f00,0xe00007,0xe000,0x0,0x3ff,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007fc,
+ 0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3f00,0x1c00380e,0x1c381cd,0x9c01f000,0x73800780,0xfe0000e,0xfe10,0x7c00000,0x1c000ffb,
+ 0xf8000000,0x38000000,0x10,0x20000,0x20000,0x1e0700,0x70380ff,0xbf80f003,0xfefe7fdf,0xc0000000,0x0,0x3f0,0x7ffe7ff,0xff000000,
+ 0x1f8,0x30e07,0xfeffbf80,0x78000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0x783800,0x1ce11c,0xe1c,0x1c07,0xf838ce38,0x0,0x1c0000,
+ 0x0,0x380e,0x18c000,0x0,0x0,0x1c38c00,0x1800030,0x7800,0xfff01ff,0xe03ffc07,0xff80fff0,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00,
+ 0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003,0x8e00700f,0x803b81c7,0x70e00e,0x1c01c380,0x3801c007,0xffe0e03c,
+ 0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0fff,0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,
+ 0x80387007,0xe00e000,0x38c383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0063c,0x40619c0f,0x30000001,0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0007,0xc00001c3,
+ 0xfffc0007,0xe00e007,0x380380,0xf00000,0xfe,0xffff80,0x3f800700,0x618063ff,0xf070071c,0x38038,0x38000700,0x1c00e38,0x3801c00,
+ 0x381c1e,0x1c000e0,0x38e0ee1,0xc00e1c00,0x70038e0,0x380001c0,0xe007007,0x1ef01d8,0xdc038e00,0x1c001e00,0xe00007,0xe000,0x0,
+ 0x7c0,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0079e,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x780,0x1c00380e,
+ 0xe701cd,0x9c01f000,0x73800f00,0xe0000e,0xe000,0x0,0x1c0007f7,0xf0000000,0x70000000,0x10,0x20000,0x0,0xe0e00,0x703807f,0x7f01e001,
+ 0xfdfc3fbf,0x80000000,0x0,0x7f0,0x0,0x0,0x3c,0x18e07,0x7f7f00,0xf0000700,0x1c,0x70c001,0xc0007070,0x1c00000,0x3e7000,0xcff18,
+ 0x3ffc070e,0x1c07,0xf818c630,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x3870000,0xe000fc00,0x380f000,0x1fff83ff,0xf07ffe0f,
+ 0xffc1fff8,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870770e0,0x71c00e3,0x801c7003,0x8e00701d,
+ 0xc03f01c7,0x70e00e,0x1c01c380,0x3801c007,0xffc0e01c,0x3e0387c0,0x70f80e1f,0x1c3e038,0x7c071e1c,0xe00038,0x70000,0xe0001c00,
+ 0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x398383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0061c,0xc0dc07,0xf0000001,0xc0000700,
+ 0x70,0x0,0x0,0x1c003c07,0x800e000f,0x1c3,0xfffc0007,0xe00e007,0x380380,0xe00000,0x1f,0xc0ffff81,0xfc000700,0x618063ff,0xf070070e,
+ 0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0e,0x1c000e0,0x38e0ee1,0xe01e1c00,0x78078e0,0x380001c0,0xe007007,0xee01f8,0xfc078f00,
+ 0x1c001c00,0xe00003,0x8000e000,0x0,0x700,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0070e,0x1c0070,0xe1c701c1,
+ 0xc01c1c01,0xc700700e,0x380,0x1c00380e,0xe700ed,0xb803f800,0x77800f00,0x70000e,0x1c000,0x0,0xe0003f7,0xe0000000,0x70000000,
+ 0x10,0x20000,0x1c0e0,0xe1c00,0x703803f,0x7e01c000,0xfdf81fbf,0x0,0x0,0x3f0,0x0,0x0,0x1c,0x1ce07,0x3f7e00,0xf0000700,0x1c,
+ 0x70c001,0xc00038e0,0x1c00038,0xf7000,0xe3e38,0x3ffc0387,0x1c00,0x1cc770,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x70e0001,
+ 0xe001fe00,0x780e000,0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0ffe,0xe0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807,
+ 0x70770f0,0xf1e01e3,0xc03c7807,0x8f00f038,0xe03e03c7,0x70e00e,0x1c01c380,0x3801c007,0xff00e00e,0x38038700,0x70e00e1c,0x1c38038,
+ 0x70071c1c,0xe00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x3b0383,0x80e0701c,
+ 0xe0381c0,0x70077807,0x701de0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x1c00061c,
+ 0xc0de03,0xe0000001,0xc0000700,0x70,0x0,0x0,0x1c001c07,0xe001e,0x1c3,0xfffc0007,0x600e00e,0x380380,0xe00000,0x7,0xf0ffff87,
+ 0xf0000000,0x60c0e380,0x7070070e,0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0f,0x1c000e0,0x38e06e0,0xe01c1c00,0x38070e0,
+ 0x1c0001c0,0xe007007,0xee00f8,0xf80f0700,0x1c003c00,0xe00003,0x8000e000,0x0,0x700,0x70780f0f,0x3c078,0x70000038,0x1e03c1c,
+ 0x7001c00,0x1c0070f,0x1c0070,0xe1c701c1,0xe03c1e03,0xc780f00e,0x380,0x1c00380e,0xe700f8,0xf807bc00,0x3f001e00,0x70000e,0x1c000,
+ 0x0,0xe0001ff,0xc0000000,0x70000000,0x10,0x20000,0x33110,0xe0e00,0x383801f,0xfc03c000,0x7ff00ffe,0x0,0x0,0x3e0,0x0,0x0,0x1c,
+ 0x38e07,0x1ffc01,0xe0000700,0x1c,0x78c001,0xc0007ff0,0x1c00038,0x7c000,0x70070,0x1c3,0x80001c00,0xe00e0,0x0,0x1c0000,0x0,
+ 0x380e,0x18c000,0x0,0x0,0xe1c0001,0xe0010700,0x780e000,0x1c038380,0x70700e0e,0x1c1c038,0x78070e0e,0xe0001c,0x38000,0x70000e00,
+ 0xe0001,0xc0003800,0x7003807,0x7037070,0xe0e01c1,0xc0383807,0x700e070,0x701c0387,0x70e00e,0x1c01c380,0x3801c007,0xe00e,0x38038700,
+ 0x70e00e1c,0x1c38038,0x70071c1c,0xf00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003c07,0x8380e0f0,0x1e1e03c3,0xc078780f,
+ 0xf01e007,0x803e0783,0x80e0701c,0xe0381c0,0x7003f007,0x80f00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x6,0x1800061c,0xc0de01,0xc0000000,0xc0000e00,0x70,0xf0000,0x3c00,0x38001c0f,0xe003c,0x3c0,0xe0000e,0x701e00e,
+ 0x3c0780,0x1e003c0,0x780000,0xfc00001f,0x80000000,0x60e1e780,0x78700f07,0x4380f0,0x38000700,0xf00e38,0x3801c00,0xc0781c07,
+ 0x81c000e0,0x38e07e0,0xe03c1c00,0x380f0e0,0x1e0003c0,0xe00780f,0xee00f0,0x780e0780,0x1c007800,0xe00001,0xc000e000,0x0,0x700,
+ 0xf0780e07,0x8041c078,0x38020038,0xe03c1c,0x7001c00,0x1c00707,0x801c0070,0xe1c701c0,0xe0381e03,0x8380f00e,0x80380,0x1c003c1e,
+ 0x7e00f8,0xf80f1e00,0x3f003c00,0x70000e,0x1c000,0x0,0xf0100f7,0x80078000,0x700078f0,0x10,0x7ff000,0x61208,0x1e0700,0x383800f,
+ 0x78078000,0x3de007bc,0x0,0x0,0x0,0x0,0x0,0x401c,0x70e0f,0xf7803,0xc0000700,0x1c,0x38c001,0xc000efb8,0x1c00038,0x1e000,0x3c1e0,
+ 0xc1,0x80000000,0x783c0,0x0,0x0,0x0,0x3c1e,0x18c000,0x0,0x0,0xc180003,0x60000300,0xd80e010,0x3c03c780,0x78f00f1e,0x1e3c03c,
+ 0x70039c0e,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x703f070,0x1e0e03c1,0xc078380f,0x701e0e0,0x381c0787,
+ 0x80f0f01e,0x1e03c3c0,0x7801c007,0xe00e,0x38078700,0xf0e01e1c,0x3c38078,0x700f1c1c,0x78041c,0x1038020,0x70040e00,0x800e0001,
+ 0xc0003800,0x7001c07,0x380e070,0x1c0e0381,0xc070380e,0x701c007,0x801e0703,0xc1e0783c,0xf0781e0,0xf003f007,0x80e00fc0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xe,0x1801867c,0xc0cf83,0xe0000000,0xe0000e00,
+ 0x70,0xf0000,0x3c00,0x38000f1e,0xe0070,0x180780,0xe0603e,0x783c01e,0x1e0f01,0x7c003c0,0x780000,0x3c00001e,0x700,0x307fe700,
+ 0x38701e07,0xc1c383e0,0x38000700,0x7c1e38,0x3801c00,0xe0f01c03,0x81c000e0,0x38e03e0,0x78781c00,0x1e1e0e0,0xe180780,0xe003c1e,
+ 0x7c00f0,0x781e03c0,0x1c007000,0xe00001,0xc000e000,0x0,0x783,0xf07c1e07,0xc0c1e0f8,0x3e0e0038,0xf07c1c,0x7001c00,0x1c00703,
+ 0xc01e0070,0xe1c701c0,0xf0781f07,0x83c1f00e,0xe0f80,0x1e003c3e,0x7e00f8,0xf80e0e00,0x3f003800,0x70000e,0x1c000,0x0,0x7830077,
+ 0xf0000,0x700078f0,0x10,0x20000,0x41208,0xc03c0380,0x3c38007,0x70070000,0x1dc003b8,0x0,0x0,0x0,0x0,0x0,0x707c,0x6070f,0x86077003,
+ 0x80000700,0x1c,0x3ec401,0xc001c01c,0x1c00038,0xf000,0x1ffc0,0x40,0x80000000,0x3ff80,0x0,0x0,0x0,0x3e3e,0x18c000,0x0,0x0,
+ 0x8100006,0x60000300,0x1980f070,0x3801c700,0x38e0071c,0xe3801c,0x70039c0e,0x7c1c1c,0x38000,0x70000e00,0xe0001,0xc0003800,
+ 0x700383e,0x701f03c,0x3c078780,0xf0f01e1e,0x3c3c1c0,0x1c3f0f03,0xc1e0783c,0xf0781e0,0xf001c007,0xe81e,0x3c1f8783,0xf0f07e1e,
+ 0xfc3c1f8,0x783f1e3e,0x187c0c1f,0x703e0e0,0x7c1c0f83,0x800e0001,0xc0003800,0x7001e0f,0x380e078,0x3c0f0781,0xe0f03c1e,0x783c007,
+ 0x801e0f03,0xc3e0787c,0xf0f81e1,0xf003f007,0xc1e00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x1c,0xe,0x3801fff8,0x6187ff,0xe0000000,0xe0000e00,0x70,0xf0000,0x3c00,0x38000ffe,0x1fff0ff,0xfe1fff80,0xe07ffc,0x3ffc01c,
+ 0x1fff01,0xff8003c0,0x780000,0x4000010,0x700,0x301e6700,0x387ffe03,0xffc3ffc0,0x3fff0700,0x3ffe38,0x383ffe0,0xfff01c03,0xc1fff8e0,
+ 0x38e03e0,0x7ff81c00,0x1ffe0e0,0xf1fff80,0xe003ffe,0x7c00f0,0x781c01c0,0x1c00ffff,0xe00001,0xc000e000,0x0,0x3ff,0x707ffc03,
+ 0xffc0fff8,0x1ffe0038,0x7ffc1c,0x707fff0,0x1c00701,0xc00ff070,0xe1c701c0,0x7ff01fff,0x1fff00e,0xfff00,0xff81fee,0x7e00f0,
+ 0x781e0f00,0x1e007ffc,0x70000e,0x1c000,0x0,0x3ff003e,0xf0000,0xe00070e0,0x60830010,0x20000,0x41208,0xfffc01c0,0x1fffe03,0xe00ffff0,
+ 0xf8001f0,0x0,0x0,0x0,0x0,0x0,0x7ff8,0xc07fd,0xfe03e007,0xffc00700,0x1c,0x1ffc1f,0xffc08008,0x1c00038,0x7000,0x7f00,0x0,0x0,
+ 0xfe00,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0x6,0x60000700,0x19807ff0,0x3801c700,0x38e0071c,0xe3801c,0x70039c0f,0xf03ffc1f,
+ 0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc,0x701f03f,0xfc07ff80,0xfff01ffe,0x3ffc080,0x83fff03,0xffe07ffc,0xfff81ff,
+ 0xf001c007,0xeffc,0x1ffb83ff,0x707fee0f,0xfdc1ffb8,0x3ff70ff7,0xf83ffc0f,0xff01ffe0,0x3ffc07ff,0x83fff87f,0xff0fffe1,0xfffc0ffe,
+ 0x380e03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x803ffe01,0xfee03fdc,0x7fb80ff,0x7001e007,0xffc00780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x3801fff0,0x7f83fe,0x70000000,0xe0000e00,0x0,0xf0000,0x3c00,0x700007fc,
+ 0x1fff0ff,0xfe1ffe00,0xe07ff8,0x1ff801c,0xffe01,0xff0003c0,0x780000,0x0,0x700,0x38000f00,0x3c7ffc01,0xff83ff80,0x3fff0700,
+ 0x1ffc38,0x383ffe0,0x7fe01c01,0xe1fff8e0,0x38e03e0,0x3ff01c00,0xffc0e0,0x71fff00,0xe001ffc,0x7c00f0,0x783c01e0,0x1c00ffff,
+ 0xe00000,0xe000e000,0x0,0x1ff,0x7077f801,0xff807fb8,0xffc0038,0x3fdc1c,0x707fff0,0x1c00701,0xe007f070,0xe1c701c0,0x3fe01dfe,
+ 0xff700e,0x7fe00,0xff80fee,0x3c0070,0x703c0780,0x1e007ffc,0x70000e,0x1c000,0x0,0x1fe001c,0xe0000,0xe000e1c0,0x71c78010,0x20000,
+ 0x21318,0xfff800c0,0xfffe01,0xc00ffff0,0x70000e0,0x0,0x0,0x0,0x0,0x0,0x3ff0,0x1803fd,0xfe01c007,0xffc00700,0x1c,0xffc1f,0xffc00000,
+ 0x1c00038,0x7000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0xc,0x60000e00,0x31803fe0,0x7801ef00,0x3de007bc,
+ 0xf7801e,0xf003fc0f,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x701f01f,0xf803ff00,0x7fe00ffc,0x1ff8000,
+ 0x67fe01,0xffc03ff8,0x7ff00ff,0xe001c007,0xeff8,0xffb81ff,0x703fee07,0xfdc0ffb8,0x1ff70ff7,0xf81ff807,0xfe00ffc0,0x1ff803ff,
+ 0x3fff87f,0xff0fffe1,0xfffc07fc,0x380e01f,0xf003fe00,0x7fc00ff8,0x1ff0000,0x37fc00,0xfee01fdc,0x3fb807f,0x7001e007,0x7f800780,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x30007fc0,0x1e00f8,0x78000000,0x70001c00,
+ 0x0,0xe0000,0x3c00,0x700001f0,0x1fff0ff,0xfe07f800,0xe01fe0,0x7e0038,0x3f800,0xfc0003c0,0x700000,0x0,0x700,0x18000e00,0x1c7ff000,
+ 0x7e03fe00,0x3fff0700,0x7f038,0x383ffe0,0x1f801c00,0xf1fff8e0,0x38e01e0,0xfc01c00,0x3f80e0,0x787fc00,0xe0007f0,0x7c00f0,0x387800f0,
+ 0x1c00ffff,0xe00000,0xe000e000,0x0,0xfc,0x7071f000,0x3f003e38,0x3f00038,0x1f1c1c,0x707fff0,0x1c00700,0xf003f070,0xe1c701c0,
+ 0x1f801c7c,0x7c700e,0x1f800,0x3f8078e,0x3c0070,0x707803c0,0x1c007ffc,0x70000e,0x1c000,0x0,0x7c0008,0x1e0000,0xe000e1c0,0x71c30010,
+ 0x20000,0x1e1f0,0x3fe00020,0x3ffe00,0x800ffff0,0x2000040,0x0,0x0,0x0,0x0,0x0,0xfc0,0x3001f0,0x78008007,0xffc00700,0x1c,0x3f81f,
+ 0xffc00000,0x1c00038,0x407000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x39c7,0x18c000,0x0,0x0,0x18,0x60001c00,0x61801f80,0x7000ee00,
+ 0x1dc003b8,0x77000e,0xe001f80f,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0,0x700f007,0xe000fc00,0x1f8003f0,
+ 0x7e0000,0xe1f800,0x7f000fe0,0x1fc003f,0x8001c007,0xe7f0,0x7e380fc,0x701f8e03,0xf1c07e38,0xfc703c1,0xe003f001,0xf8003f00,
+ 0x7e000fc,0x3fff87f,0xff0fffe1,0xfffc03f8,0x380e00f,0xc001f800,0x3f0007e0,0xfc0000,0x61f800,0x78e00f1c,0x1e3803c,0x7001c007,
+ 0x1f000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x70001c00,0x0,
+ 0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,
+ 0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000,
+ 0x70000e,0x1c000,0x0,0x0,0x1c0000,0xe000c180,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
+ 0x0,0x38,0x70e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x2000,0x0,0x1f,0xf8003800,0x7fe00000,0x0,0x0,0x0,0x0,0x4000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000,
+ 0x0,0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x30001800,
+ 0x0,0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e000,
+ 0x0,0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000,
+ 0x70000e,0x1c000,0x0,0x0,0x1c0001,0xe001c380,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
+ 0x0,0x38,0x7fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x3000,0x0,0x1f,0xf8007000,0x7fe00000,0x0,0x0,0x0,0x0,0x6000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x38003800,
+ 0x0,0x380000,0x1,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x3c18000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf000,
+ 0x0,0x0,0x0,0x0,0x0,0xfe0000,0x380fe000,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x38000000,
+ 0x78000e,0x3c000,0x0,0x0,0x180001,0xc0018300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,
+ 0x38,0x1f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x1800,0x0,0x0,0x6000e000,0x1800000,0x0,0x0,0x0,0x0,0x3000,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x38007,0xe00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x18003000,
+ 0x0,0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,
+ 0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x0,0x0,0x0,0x0,0x607800,0x0,0x3c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x78000000,
+ 0x3f800e,0x3f8000,0x0,0x0,0x300043,0xc0018200,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
+ 0x0,0x38,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x11800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x23000,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x23000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78007,
+ 0x1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000,
+ 0xfe000,0x0,0x0,0x0,0x0,0x0,0x7ff000,0x0,0x7f800000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf8000000,0x3f800e,0x3f8000,0x0,
+ 0x0,0x10007f,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x38,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x3800,0x0,0x1f800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f8007,0xfe00,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x7fe000,0x0,
+ 0x7f000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf0000000,0xf800e,0x3e0000,0x0,0x0,0x7f,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1f000,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x3f0007,0xfc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x1fc000,0x0,0x7e000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xc0000000,0xe,0x0,
+ 0x0,0x0,0x3e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0007,0xf000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+ // Definition of a 29x57 font.
+ const unsigned int font29x57[29*57*256/32] = {
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x781e00,0x0,0x0,0x7,0x81e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0000,0xf8000,0x7e00000,0x0,0x7,
+ 0xc0000000,0x0,0x7c00,0xf80,0x7e000,0x0,0x7c00000,0xf80000,0x7e000000,0x0,0x0,0x1f00,0x3e0,0x1f800,0x0,0x0,0x0,0x3,0xe0000000,
+ 0x7c00003f,0x0,0xf8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x3c3c00,0x0,0x0,0x3,0xc3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0000,
+ 0x1f0000,0x7e00000,0xf838001f,0xf80001f,0xf0000000,0x0,0x3e00,0x1f00,0x7e000,0x3e1f000,0x3e00000,0x1f00000,0x7e00003e,0x1f000000,
+ 0x3e0,0xe0000f80,0x7c0,0x1f800,0x3e0e00,0x7c3e000,0x0,0x1,0xf0000000,0xf800003f,0x1f0f,0x800001f0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e7800,0x0,0x0,
+ 0x1,0xe7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x1e0000,0xff00001,0xfe38001f,0xf80003f,
+ 0xf8000000,0x0,0x1e00,0x1e00,0xff000,0x3e1f000,0x1e00000,0x1e00000,0xff00003e,0x1f000000,0x7f8,0xe0000780,0x780,0x3fc00,0x7f8e00,
+ 0x7c3e000,0x0,0x0,0xf0000000,0xf000007f,0x80001f0f,0x800001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xef000,0x0,0x0,0x0,0xef000000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000,0x3c0000,0x1e780003,0xfff8001f,0xf80003c,0x78000000,0x0,0xf00,0x3c00,0x1e7800,
+ 0x3e1f000,0xf00000,0x3c00001,0xe780003e,0x1f000000,0xfff,0xe00003c0,0xf00,0x79e00,0xfffe00,0x7c3e000,0x0,0x0,0x78000001,0xe00000f3,
+ 0xc0001f0f,0x800003c0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x78000,0x780000,0x3c3c0003,0x8ff0001f,0xf800078,0x3c000000,0x0,0x780,0x7800,0x3c3c00,0x3e1f000,0x780000,0x7800003,0xc3c0003e,
+ 0x1f000000,0xe3f,0xc00001e0,0x1e00,0xf0f00,0xe3fc00,0x7c3e000,0x0,0x0,0x3c000003,0xc00001e1,0xe0001f0f,0x80000780,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x1f,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc00,0x7e000,0xfe000,0x0,0x3c000,0xf00000,0x781e0003,
+ 0x83e0001f,0xf800070,0x1c000000,0x0,0x3c0,0xf000,0x781e00,0x3e1f000,0x3c0000,0xf000007,0x81e0003e,0x1f000000,0xe0f,0x800000f0,
+ 0x3c00,0x1e0780,0xe0f800,0x7c3e000,0x0,0x0,0x1e000007,0x800003c0,0xf0001f0f,0x80000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf8000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ff800,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x78,0xf000000,0x0,0x0,0x780f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ffc00,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x3e000,0x3e00000,0x0,0x78,0x3c000000,0x0,0x1f000,0x3e0,
+ 0x3e000,0x0,0x1f000000,0x3e0000,0x3e000000,0x0,0x0,0x7c00,0xf8,0xf800,0x0,0x0,0x0,0xf,0x80000000,0x1f00001f,0x0,0x3e,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x781c0000,0x38,0xe000000,0x0,0x0,0x380e0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x39c00,0x1ce000,0x303e00,
+ 0x0,0x0,0x0,0x0,0x0,0x78,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0xf80000,0x7c000,0x3e00000,0xf0380000,0x70,0x1c000000,0x0,0xf800,0x7c0,0x3e000,0x0,0xf800000,0x7c0000,0x3e000000,
+ 0x0,0x3c0,0xe0003e00,0x1f0,0xf800,0x3c0e00,0x0,0x0,0x7,0xc0000000,0x3e00001f,0x0,0x7c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0xff,0x0,
+ 0xf8,0xf8000,0x1c000,0x0,0x0,0x0,0x0,0x1f,0xc0000000,0x1ff8,0xff00,0x0,0x0,0x3fe000,0x0,0x1fc00001,0xfe000000,0x0,0x0,0x0,
+ 0x0,0x7f800,0x0,0x0,0x0,0xff00000,0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf8000000,0xfe,0x0,0x7f80,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x780000,0x1,0xe0000000,0x0,0x780000,0x3,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x3fc00,0x0,0x0,0x1fc000,0x0,0x0,0x0,0x1fc0,
+ 0x0,0xff000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe1c0000,0x1c,0x1c000000,0x0,0x0,0x1c1c0,0x0,0x0,0x0,0x0,0x1fe0000,
+ 0x0,0x0,0x1ff,0x1f0f8,0x0,0xff000,0x0,0x0,0x0,0x3f,0xff00000f,0x80000000,0xfe0,0x3f80,0xf00,0x0,0x0,0x0,0x1,0xf8000003,0xe0000000,
+ 0x1c00,0xe000,0xe00,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,
+ 0x7f0000,0x0,0x1fc07000,0x0,0x0,0x0,0x0,0x0,0x3f800,0x780000,0x78000,0x7f00001,0xfc38001f,0xf800070,0x1c000000,0x0,0x7800,
+ 0x780,0x7f000,0x3e1f000,0x7800000,0x780000,0x7f00003e,0x1f0003f0,0x7f0,0xe0001e00,0x1e0,0x1fc00,0x7f0e00,0x7c3e000,0x0,0x3,
+ 0xc0000000,0x3c00003f,0x80001f0f,0x80000078,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x1e078000,0x30000000,0x3ff,0xc00001e0,0xf0,
+ 0x78000,0x1c000,0x0,0x0,0x0,0x0,0x1e0007f,0xf000007e,0x1ffff,0x7ffe0,0x1f80,0x3ffff80,0xfff803,0xfffff800,0xfff80007,0xff800000,
+ 0x0,0x0,0x0,0x0,0x1ffe00,0x0,0xfe0003,0xfff80000,0x3ffe01ff,0xe00003ff,0xffe01fff,0xff0003ff,0xe01e0007,0x803ffff0,0xfff80,
+ 0x3c000fc0,0x7800001f,0x8003f07e,0x1e000f,0xfe0007ff,0xf00003ff,0x8007ffe0,0x1fff8,0x7fffffe,0xf0003c1,0xe000079e,0xf1f,0x1f3e0,
+ 0x1f01ff,0xfff8003f,0xf003c000,0x7fe0,0x3f00,0x0,0x3c0000,0x1,0xe0000000,0x0,0x780000,0xf,0xfe000000,0x78000,0x3c00,0xf000,
+ 0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xfc0000f0,0x3fe00,0x0,0x0,0xfff00,0x0,0x0,0x3fe000,
+ 0x0,0x0,0x0,0x1dc0,0x0,0x3fff00,0x0,0x3ffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff1c07ff,0x3c0f001e,0x3c000000,
+ 0x0,0x0,0x1e3c0,0xf80007c,0x0,0x780000,0x0,0xfff8000,0x3e00,0x1f00000,0x7ff,0xc001f0f8,0x0,0x3ffc00,0x0,0x0,0x0,0x3f,0xff00003f,
+ 0xe0000000,0x3ff8,0xffe0,0x1e00,0x0,0xfffc00,0x0,0x7,0xf800000f,0xf8000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,
+ 0x3f800001,0xfc00003f,0xf80000ff,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,
+ 0xfc00,0x3c001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x0,0x7ff8f0f0,0x3c0780,0x1e03c00,0xf01e000,0x783e0001,0xf01e0000,0xffe00,
+ 0x3c0000,0xf0000,0x7700001,0xfe38001f,0xf800070,0x1c000000,0x0,0x3c00,0xf00,0x77000,0x3e1f000,0x3c00000,0xf00000,0x7700003e,
+ 0x1f0000f8,0xc0007f8,0xe0000f00,0x3c0,0x1dc00,0x7f8e00,0x7c3e000,0x0,0x1,0xe0000000,0x7800003b,0x80001f0f,0x800000f0,0x1e0000,
+ 0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x780000,0x3c1e0000,0x1e070000,0x300001f0,0x7ff,0xc00001e0,0x1e0,0x7c000,0x1c000,0x0,0x0,0x0,0x0,0x3c000ff,0xf80007fe,
+ 0x3ffff,0x801ffff8,0x1f80,0x3ffff80,0x3fff803,0xfffff801,0xfffc000f,0xffc00000,0x0,0x0,0x0,0x0,0x7fff80,0x0,0xfe0003,0xffff0000,
+ 0xffff01ff,0xfc0003ff,0xffe01fff,0xff000fff,0xf01e0007,0x803ffff0,0xfff80,0x3c001f80,0x7800001f,0xc007f07e,0x1e001f,0xff0007ff,
+ 0xfc0007ff,0xc007fffc,0x3fffc,0x7fffffe,0xf0003c1,0xf0000f9e,0xf0f,0x8003e1e0,0x1e01ff,0xfff8003f,0xf001e000,0x7fe0,0x3f00,
+ 0x0,0x1e0000,0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x1fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x3de0,0x0,0x7fff80,0x0,0xfffff80,
+ 0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe7bc07ff,0x3e1f000f,0x78000000,0x0,0x0,0xf780,0x7800078,0x0,0x780000,0x180000,
+ 0x1fff8000,0x1e00,0x1e0003c,0xfff,0xc001f0f8,0x0,0x7ffe00,0x0,0x0,0x0,0x3f,0xff00007f,0xf0000000,0x3ffc,0xfff0,0x3c00,0x0,
+ 0x7fffc00,0x0,0x7,0xf800003f,0xfe000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xe00001ff,
+ 0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000fc00,0x3c003ffe,0x1fff0,
+ 0xfff80,0x7ffc00,0x3ffe000,0x0,0xfffce0f0,0x3c0780,0x1e03c00,0xf01e000,0x781e0001,0xe01e0000,0x3fff00,0x1e0000,0x1e0000,0xf780003,
+ 0xcf78001f,0xf800078,0x3c000000,0x0,0x1e00,0x1e00,0xf7800,0x3e1f000,0x1e00000,0x1e00000,0xf780003e,0x1f0000fc,0x7c000f3d,
+ 0xe0000780,0x780,0x3de00,0xf3de00,0x7c3e000,0x0,0x0,0xf0000000,0xf000007b,0xc0001f0f,0x800001e0,0x1e0000,0x3e1f00,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
+ 0x3c1e0000,0x1e0f0000,0x300007fc,0xfff,0xc00001e0,0x1e0,0x3c000,0x1c000,0x0,0x0,0x0,0x0,0x3c001ff,0xfc001ffe,0x3ffff,0xc01ffffc,
+ 0x3f80,0x3ffff80,0x7fff803,0xfffff803,0xfffe001f,0xffe00000,0x0,0x0,0x0,0x0,0xffff80,0x7f800,0xfe0003,0xffff8001,0xffff01ff,
+ 0xff0003ff,0xffe01fff,0xff001fff,0xf01e0007,0x803ffff0,0xfff80,0x3c003f00,0x7800001f,0xc007f07f,0x1e003f,0xff8007ff,0xff000fff,
+ 0xe007ffff,0x7fffc,0x7fffffe,0xf0003c0,0xf0000f1e,0xf07,0x8003c1f0,0x3e01ff,0xfff8003f,0xf001e000,0x7fe0,0x7f80,0x0,0xe0000,
+ 0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,
+ 0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x3fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x78f0,0x0,0xffff80,0x0,0x3fffff80,0x1f,
+ 0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc7f80070,0x3e1f0007,0x70000000,0x0,0x0,0x7700,0x7c000f8,0x0,0x780000,0x180000,
+ 0x3fff8000,0x1f00,0x3e0003c,0x1f03,0xc001f0f8,0x0,0x703f00,0x0,0x0,0x0,0x3f,0xff0000f0,0xf8000000,0x303e,0xc0f8,0x7800,0x0,
+ 0xffffc00,0x0,0x7,0x3800003e,0x3e000000,0x1c00,0xe000,0x3c00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00000f,0xe00001ff,
+ 0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000fe00,0x3c007fff,0x3fff8,
+ 0x1fffc0,0xfffe00,0x7fff000,0x1,0xffffc0f0,0x3c0780,0x1e03c00,0xf01e000,0x781f0003,0xe01e0000,0x3fff80,0xe0000,0x3c0000,0x1e3c0003,
+ 0x8ff0001f,0xf80003c,0x78000000,0x0,0xe00,0x3c00,0x1e3c00,0x3e1f000,0xe00000,0x3c00001,0xe3c0003e,0x1f00007f,0xf8000e3f,0xc0000380,
+ 0xf00,0x78f00,0xe3fc00,0x7c3e000,0x0,0x0,0x70000001,0xe00000f1,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0000,
+ 0x30000ffe,0xf80,0xc00001e0,0x3c0,0x1e000,0x101c040,0x0,0x0,0x0,0x0,0x78003f0,0x7e001ffe,0x3f807,0xe01f00fe,0x3f80,0x3ffff80,
+ 0x7e01803,0xfffff007,0xe03f003f,0x3f00000,0x0,0x0,0x0,0x0,0xfc0fc0,0x3ffe00,0xfe0003,0xffffc003,0xf81f01ff,0xff8003ff,0xffe01fff,
+ 0xff003f01,0xf01e0007,0x803ffff0,0xfff80,0x3c007e00,0x7800001f,0xc007f07f,0x1e007e,0xfc007ff,0xff801f83,0xf007ffff,0x800fc07c,
+ 0x7fffffe,0xf0003c0,0xf0000f0f,0x1e07,0xc007c0f8,0x7c01ff,0xfff8003c,0xf000,0x1e0,0xffc0,0x0,0xf0000,0x1,0xe0000000,0x0,0x780000,
+ 0x3e,0x0,0x78000,0x3c00,0xf000,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0x800000f0,0x1f80,
+ 0x0,0x0,0x7e0780,0x0,0x0,0x1f82000,0x0,0x0,0x0,0x7070,0x0,0x1f80f80,0x0,0x7fffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x1,0xc3f80070,0x3f3f0007,0xf0000000,0x0,0x0,0x7f00,0x3e001f0,0x0,0x780000,0x180000,0x7f018000,0xf80,0x7c0003c,0x3e00,
+ 0x4001f0f8,0xfe00,0x400f00,0x0,0x0,0x0,0x7f000000,0xe0,0x38000000,0x1e,0x38,0x7800,0x0,0x1ffe1c00,0x0,0x0,0x38000078,0xf000000,
+ 0x1c00,0xe000,0x7f800,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xf00001ff,0xffc03f81,0xf007ffff,0xc03ffffe,
+ 0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf800fe00,0x3c00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,
+ 0x3,0xf07fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x780f8007,0xc01e0000,0x7e0fc0,0xf0000,0x3c0000,0x1c1c0003,0x87f0001f,0xf80003f,
+ 0xf8000000,0x0,0xf00,0x3c00,0x1c1c00,0x3e1f000,0xf00000,0x3c00001,0xc1c0003e,0x1f00003f,0xc0000e1f,0xc00003c0,0xf00,0x70700,
+ 0xe1fc00,0x7c3e000,0x0,0x0,0x78000001,0xe00000e0,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0001,0xff801e0f,
+ 0x1f00,0x1e0,0x3c0,0x1e000,0x3c1c1e0,0x0,0x0,0x0,0x0,0x78007c0,0x1f001f9e,0x3c001,0xf010003e,0x7780,0x3c00000,0xf800000,0xf007,
+ 0xc01f007c,0x1f80000,0x0,0x0,0x0,0x0,0xe003e0,0x7fff00,0x1ef0003,0xc007e007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x301e0007,
+ 0x80007800,0x780,0x3c00fc00,0x7800001f,0xe00ff07f,0x1e00f8,0x3e00780,0x1fc03e00,0xf807801f,0xc01f001c,0xf000,0xf0003c0,0xf0000f0f,
+ 0x1e03,0xc00f8078,0x780000,0xf0003c,0xf000,0x1e0,0x1f3e0,0x0,0x78000,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,
+ 0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0xf0,0xf80,0x0,0x0,0xf80180,0x0,0x0,0x1e00000,
+ 0x0,0x0,0x0,0xe038,0x0,0x3e00380,0x0,0xfe0f0000,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc0f00070,0x3b370003,0xe0000000,
+ 0x0,0x0,0x3e00,0x1e001e0,0x0,0x780000,0x180000,0x7c000000,0x780,0x780003c,0x3c00,0x0,0x7ffc0,0x780,0x0,0x0,0x3,0xffe00000,
+ 0x1c0,0x3c000000,0xe,0x38,0xf000,0x0,0x3ffe1c00,0x0,0x0,0x38000078,0xf000000,0x1c00,0xe000,0x7f000,0xf000,0x3de000,0x1ef0000,
+ 0xf780000,0x7bc00003,0xde00001e,0xf00003e7,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+ 0xe0001e03,0xfc00fe00,0x3c01f007,0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x7,0xc01f80f0,0x3c0780,0x1e03c00,0xf01e000,0x78078007,
+ 0x801e0000,0x7803c0,0x78000,0x780000,0x380e0003,0x81e00000,0x1f,0xf0000000,0x0,0x780,0x7800,0x380e00,0x0,0x780000,0x7800003,
+ 0x80e00000,0x1ff,0x80000e07,0x800001e0,0x1e00,0xe0380,0xe07800,0x0,0x0,0x0,0x3c000003,0xc00001c0,0x70000000,0x780,0x1e0000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x780000,0x3c1e0000,0x3c0e0007,0xfff01c07,0x1e00,0x1e0,0x780,0xf000,0x3e1c3e0,0x0,0x0,0x0,0x0,0xf0007c0,0x1f00181e,0x20000,
+ 0xf000001f,0xf780,0x3c00000,0x1f000000,0x1f00f,0x800f8078,0xf80000,0x0,0x0,0x0,0x0,0x8003e0,0x1fc0f80,0x1ef0003,0xc001e007,
+ 0x800101e0,0x7e003c0,0x1e00,0x7800,0x101e0007,0x80007800,0x780,0x3c00f800,0x7800001e,0xe00ef07f,0x801e00f0,0x1e00780,0x7c03c00,
+ 0x78078007,0xc01e0004,0xf000,0xf0003c0,0x78001e0f,0x1e03,0xe00f807c,0xf80000,0x1f0003c,0x7800,0x1e0,0x3e1f0,0x0,0x3c000,0x1,
+ 0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,
+ 0x1e,0xf0,0x780,0x0,0x0,0x1f00080,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x1e03c,0x0,0x3c00080,0x0,0xf80f0000,0x0,0x1f0000,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x3bf70003,0xe0000000,0x0,0x0,0x3e00,0x1f003e0,0x0,0x780000,0x180000,0x78000000,0x7c0,0xf80003c,
+ 0x3c00,0x0,0x1f01f0,0x780,0x0,0x0,0xf,0x80f80000,0x1c0,0x1c000000,0xe,0x38,0x1e000,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,0x7800000,
+ 0x1c00,0xe000,0x7fc00,0xf000,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x80007800,0x10078000,0x3c0000,
+ 0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00ff00,0x3c01e003,0xc00f001e,0x7800f0,0x3c00780,0x1e003c00,
+ 0x7,0x800f00f0,0x3c0780,0x1e03c00,0xf01e000,0x7807c00f,0x801e0000,0xf803c0,0x3c000,0xf00000,0x780f0000,0x0,0x7,0xc0000000,
+ 0x0,0x3c0,0xf000,0x780f00,0x0,0x3c0000,0xf000007,0x80f00000,0x7ff,0xc0000000,0xf0,0x3c00,0x1e03c0,0x0,0x0,0x0,0x0,0x1e000007,
+ 0x800003c0,0x78000000,0xf00,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c1e001f,0xfff03803,0x80001e00,0x1e0,0x780,0xf000,0xf9cf80,
+ 0x0,0x0,0x0,0x0,0xf000780,0xf00001e,0x0,0xf800000f,0xe780,0x3c00000,0x1e000000,0x1e00f,0x78078,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,
+ 0x3f003c0,0x1ef0003,0xc000f00f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,0x3c01f000,0x7800001e,0xe00ef07f,
+ 0x801e01f0,0x1e00780,0x3c07c00,0x78078003,0xc03e0000,0xf000,0xf0003c0,0x78001e0f,0x1e01,0xf01f003c,0xf00000,0x3e0003c,0x7800,
+ 0x1e0,0x7c0f8,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,
+ 0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x8,0x40,0x0,0x7e0000,0x7c00000,0x1,0xf00f0000,
+ 0x0,0x3e0000,0x0,0x3f,0xfc0,0xfc3f0,0xfc3f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,0xf003c0,0x0,0x0,0x180000,0xf8000000,
+ 0x3c0,0xf00003c,0x3c00,0x0,0x3c0078,0x7ff80,0x0,0x0,0x1e,0x3c0000,0x1c0,0x1c000000,0xe,0xf0,0x0,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,
+ 0x7800000,0x1c00,0xe000,0x3c00,0x0,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x8000f800,0x78000,0x3c0000,
+ 0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00ff00,0x3c03e003,0xc01f001e,0xf800f0,0x7c00780,0x3e003c00,
+ 0xf,0x800f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803c00f,0x1fffc0,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x307,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x781e003f,0xfff03803,
+ 0x80001e00,0x1e0,0xf80,0xf000,0x3dde00,0x0,0x0,0x0,0x0,0xf000f00,0x780001e,0x0,0x7800000f,0x1e780,0x3c00000,0x3e000000,0x3e00f,
+ 0x780f0,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,0x7c001e0,0x3ef8003,0xc000f00f,0x1e0,0xf003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,
+ 0x3c03e000,0x7800001e,0xf01ef07b,0xc01e01e0,0xf00780,0x3e07800,0x3c078003,0xe03c0000,0xf000,0xf0003c0,0x78001e0f,0x1e00,0xf01e003e,
+ 0x1f00000,0x3c0003c,0x7800,0x1e0,0x78078,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,
+ 0xe70000,0x7800000,0x1,0xe00f0000,0x0,0x3c0000,0x0,0x3f,0xfc0,0xfc1f0,0x1f83f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,
+ 0xf807c0,0x0,0x0,0x180000,0xf0000000,0x3e0,0x1f00003c,0x3e00,0x0,0x70001c,0x3fff80,0x0,0x0,0x38,0xe0000,0x1c0,0x1c000078,
+ 0x1c,0x1fe0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x7df000,0x3ef8000,0x1f7c0000,0xfbe00007,
+ 0xdf00003c,0x780003c7,0x8000f000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f780,
+ 0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0xf80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803e01f,0x1ffff8,0xf001e0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x0,0x0,0x1e0000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x780000,0x3c1e0000,0x781e003e,0x30703803,0x80001e00,0x1e0,0xf00,0x7800,0xff800,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,
+ 0x0,0x7800000f,0x3c780,0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x2000000,0x800000,0x1e0,0x78000e0,0x3c78003,
+ 0xc000f01e,0x1e0,0xf803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x701cf07b,0xc01e01e0,0xf00780,0x1e07800,
+ 0x3c078001,0xe03c0000,0xf000,0xf0003c0,0x7c003e0f,0x1e00,0xf83e001e,0x1e00000,0x7c0003c,0x3c00,0x1e0,0xf807c,0x0,0x0,0x1fe0001,
+ 0xe1fc0000,0x7f00003,0xf8780007,0xf000003c,0x7f0,0x783f0,0x0,0x0,0x7800000,0x1e00000,0x3e0f8000,0xfc00007,0xf8000007,0xf00001fc,
+ 0xf,0xc0003fc0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,0x1818000,
+ 0x7800000,0x1,0xe00f0000,0x0,0x7c0000,0x0,0x1f,0x80001f80,0x7c1f8,0x1f83e0,0x0,0x0,0x0,0x70,0x38c70007,0xf8000000,0x7f03,
+ 0xf0000000,0x0,0x780780,0x0,0x0,0xfe0000,0xf0000000,0x1e0,0x1e00003c,0x3f00,0x0,0xe07f0e,0x7fff80,0x0,0x0,0x70,0x70000,0x1c0,
+ 0x1c000078,0x3c,0x1fc0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x78f000,0x3c78000,0x1e3c0000,
+ 0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
+ 0xf80f780,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0x1f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801e01e,0x1ffffc,
+ 0xf007e0,0x3fc000,0x1fe0000,0xff00000,0x7f800003,0xfc00001f,0xe0000fc0,0xfc00007f,0xfe0,0x7f00,0x3f800,0x1fc000,0x0,0x0,0x0,
+ 0x1,0xf000001f,0x80000ff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x1f80000,0x1fc1e000,0x0,0x0,0x0,0x0,0x1e1fc0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,
+ 0x781c007c,0x30003803,0x80001f00,0x1e0,0xf00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,0x0,0x7800000f,0x3c780,
+ 0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x1e000000,0xf00000,0x3e0,0xf0000e0,0x3c78003,0xc000f01e,0x1e0,0x7803c0,
+ 0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c0f8000,0x7800001e,0x701cf079,0xe01e01e0,0xf00780,0x1e07800,0x3c078001,0xe03c0000,
+ 0xf000,0xf0003c0,0x3c003c0f,0x3e00,0x787c001f,0x3e00000,0xf80003c,0x3c00,0x1e0,0x1f003e,0x0,0x0,0x1fffc001,0xe7ff0000,0x3ffe000f,
+ 0xfe78003f,0xfc001fff,0xfe001ffc,0xf0078ffc,0x1ffc00,0x7ff000,0x7800f80,0x1e0000f,0x7f1fc01e,0x3ff0001f,0xfe00079f,0xfc0007ff,
+ 0x3c003c7f,0xf001fff8,0x1fffff0,0x3c003c0,0xf0000f1e,0xf1f,0x7c1f0,0x1f00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3c00000,0x100000,
+ 0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7800000,0x1,0xe00f0000,0x1000000,0xf80000,0x40000002,0xf,0x80001f00,0x7e0f8,0x1f07c0,
+ 0x0,0x0,0x0,0x70,0x38c7003f,0xff000000,0xff8f,0xf8000100,0xffffe,0x7c0f80,0x0,0x0,0x3ffc000,0xf0000020,0x1001f0,0x3c00003c,
+ 0x1f80,0x0,0x1c3ffc7,0x7c0780,0x0,0x0,0xe3,0xff038000,0xe0,0x38000078,0x78,0x1ff0,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,
+ 0x7800000,0x1c00,0xe000,0xe00,0xf000,0x78f000,0x3c78000,0x1e3c0000,0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,
+ 0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,
+ 0x4000200f,0x3f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801f03e,0x1ffffe,0xf01fe0,0x3fff800,0x1fffc000,0xfffe0007,0xfff0003f,
+ 0xff8001ff,0xfc003ff3,0xfe0003ff,0xe0007ff8,0x3ffc0,0x1ffe00,0xfff000,0x3ff80001,0xffc0000f,0xfe00007f,0xf000003f,0xf8003c7f,
+ 0xe0003ffc,0x1ffe0,0xfff00,0x7ff800,0x3ffc000,0x1f80000,0xfff1c03c,0x3c01e0,0x1e00f00,0xf007800,0x781f0001,0xf01e7ff0,0x7c0007c,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
+ 0x3c1e003f,0xfffff078,0x30003803,0x80000f00,0x1e0,0x1f00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x3c000f00,0x780001e,0x0,0x7800000f,
+ 0x78780,0x3c00000,0x3c000000,0x7c00f,0x780f0,0x3c0007,0xe000003f,0x0,0xfe000000,0xfe0000,0x3c0,0x1f000070,0x7c7c003,0xc000f01e,
+ 0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c1f0000,0x7800001e,0x783cf079,0xe01e03c0,0xf00780,0x1e0f000,0x3c078001,
+ 0xe03c0000,0xf000,0xf0003c0,0x3c003c07,0x81f03c00,0x7c7c000f,0x87c00000,0xf00003c,0x1e00,0x1e0,0x3e001f,0x0,0x0,0x3fffe001,
+ 0xefff8000,0x7fff001f,0xff78007f,0xfe001fff,0xfe003ffe,0xf0079ffe,0x1ffc00,0x7ff000,0x7801f00,0x1e0000f,0xffbfe01e,0x7ff8003f,
+ 0xff0007bf,0xfe000fff,0xbc003cff,0xf803fffc,0x1fffff0,0x3c003c0,0x78001e1e,0xf0f,0x800f80f0,0x1e00ff,0xffe0001e,0xf0,0x780,
+ 0x0,0x0,0x3c00000,0x380000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1008000,0x7800000,0x3,0xe00f0000,0x3800000,0xf00000,0xe0000007,
+ 0xf,0x80001f00,0x3e0f8,0x1e07c0,0x0,0x0,0x0,0x70,0x3807007f,0xff800000,0x1ffdf,0xfc000380,0xffffe,0x3e1f00,0x0,0x0,0xfffe000,
+ 0xf0000030,0x3800f8,0x7c00003c,0xfc0,0x0,0x18780c3,0xf00780,0x80100,0x0,0xc3,0xffc18000,0xf0,0x78000078,0xf0,0xf0,0x0,0x3c003c0,
+ 0xfffe1c00,0x0,0x0,0x380000f0,0x7800801,0x1c00,0xe000,0x1e00,0xf000,0xf8f800,0x7c7c000,0x3e3e0001,0xf1f0000f,0x8f80007c,0x7c000787,
+ 0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078001,0xe03c000f,
+ 0x1e00078,0xf0003c0,0x78001e00,0xe000701f,0x3fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x7800f87c,0x1e007f,0xf07e00,0x7fffc00,0x3fffe001,
+ 0xffff000f,0xfff8007f,0xffc003ff,0xfe007ff7,0xff0007ff,0xf000fffc,0x7ffe0,0x3fff00,0x1fff800,0x3ff80001,0xffc0000f,0xfe00007f,
+ 0xf00000ff,0xf8003cff,0xf0007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x1f80001,0xfffb803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,
+ 0xe01efff8,0x3c00078,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e003f,0xfffff078,0x30001c07,0xf80,0x1e0,0x1e00,0x3c00,0xff800,0x1e0000,0x0,0x0,0x0,0x3c001e00,
+ 0x3c0001e,0x0,0x7800001e,0x70780,0x3c00000,0x78000000,0x78007,0x800f00f0,0x3e0007,0xe000003f,0x3,0xfe000000,0xff8000,0x7c0,
+ 0x1e000070,0x783c003,0xc001f01e,0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c3e0000,0x7800001e,0x3838f079,
+ 0xe01e03c0,0x780780,0x1e0f000,0x1e078001,0xe03c0000,0xf000,0xf0003c0,0x3c007c07,0x81f03c00,0x3ef80007,0x87800000,0x1f00003c,
+ 0x1e00,0x1e0,0x7c000f,0x80000000,0x0,0x3ffff001,0xffffc000,0xffff003f,0xff7800ff,0xff001fff,0xfe007ffe,0xf007bffe,0x1ffc00,
+ 0x7ff000,0x7803e00,0x1e0000f,0xffffe01e,0xfff8007f,0xff8007ff,0xff001fff,0xbc003dff,0xf807fffc,0x1fffff0,0x3c003c0,0x78001e0f,
+ 0x1e07,0xc01f00f0,0x1e00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7c00000,0x7c0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1018000,0x7800000,
+ 0x3,0xc00f0000,0x7c00000,0x1f00001,0xf000000f,0x80000007,0xc0003e00,0x1e07c,0x3e0780,0x0,0x0,0x0,0x70,0x380700ff,0xff800000,
+ 0x3ffff,0xfe0007c0,0xffffe,0x1e1e00,0x0,0x780000,0x1fffe000,0xf0000078,0x7c0078,0x7800003c,0xff0,0x0,0x38e0003,0x80f00780,
+ 0x180300,0x0,0x1c3,0x81e1c000,0x7f,0xf0000078,0x1e0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800c01,0x80001c00,
+ 0xe000,0x603e00,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x7800078,0x3c000f87,0x8001e000,0x78000,0x3c0000,0x1e00000,
+ 0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f01,0xf000f81e,
+ 0x7bc0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007878,0x1e001f,0xf0f800,0x7fffe00,0x3ffff001,0xffff800f,0xfffc007f,0xffe003ff,
+ 0xff007fff,0xff800fff,0xf001fffe,0xffff0,0x7fff80,0x3fffc00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00001ff,0xfc003dff,0xf000ffff,
+ 0x7fff8,0x3fffc0,0x1fffe00,0xffff000,0x1f80003,0xffff803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,0xe01ffffc,0x3c00078,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
+ 0x3c1e003f,0xfffff078,0x30001e0f,0x300780,0x1e0,0x1e00,0x3c00,0x3dde00,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf800003e,
+ 0xf0780,0x3dfc000,0x783f8000,0xf8007,0xc01f00f0,0x3e0007,0xe000003f,0x1f,0xfc000000,0x7ff000,0xf80,0x3e007c70,0x783c003,0xc001e03c,
+ 0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,0x80007800,0x780,0x3c7c0000,0x7800001e,0x3878f078,0xf01e03c0,0x780780,0x1e0f000,0x1e078001,
+ 0xe03e0000,0xf000,0xf0003c0,0x1e007807,0x83f03c00,0x3ef00007,0xcf800000,0x3e00003c,0xf00,0x1e0,0xf80007,0xc0000000,0x0,0x3e01f801,
+ 0xfe07e001,0xf80f007e,0x7f801f8,0x1f801fff,0xfe00fc0f,0xf007f83f,0x1ffc00,0x7ff000,0x7807c00,0x1e0000f,0x87e1e01f,0xe0fc00fc,
+ 0xfc007f8,0x1f803f03,0xfc003df0,0x3807e03c,0x1fffff0,0x3c003c0,0x78003e0f,0x1e03,0xe03e00f8,0x3e00ff,0xffe0001e,0xf0,0x780,
+ 0x0,0x0,0x7800000,0xfe0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7c00000,0x3,0xc00f0000,0xfe00000,0x3e00003,0xf800001f,
+ 0xc0000007,0xc0003e00,0x1e03c,0x3c0f80,0x0,0x0,0x0,0x70,0x380700fc,0x7800000,0x7c1fe,0x3e000fe0,0xffffe,0x1f3e00,0x0,0x780000,
+ 0x3f98e000,0xf000003c,0xfcf8007c,0xf800003c,0x3ffc,0x0,0x31c0001,0x80f00f80,0x380700,0x0,0x183,0x80e0c000,0x3f,0xe0000078,
+ 0x3c0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x38000078,0xf000e01,0xc003ffe0,0x1fff00,0x7ffc00,0xf000,0xf07800,0x783c000,0x3c1e0001,
+ 0xe0f0000f,0x7800078,0x3c000f07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
+ 0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf801f01e,0xf3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007cf8,
+ 0x1e000f,0x80f0f000,0x7c03f00,0x3e01f801,0xf00fc00f,0x807e007c,0x3f003e0,0x1f80707f,0x8f801f80,0xf003f03f,0x1f81f8,0xfc0fc0,
+ 0x7e07e00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00003ff,0xfc003fc1,0xf801f81f,0x800fc0fc,0x7e07e0,0x3f03f00,0x1f81f800,0x1f80007,
+ 0xe07f003c,0x3c01e0,0x1e00f00,0xf007800,0x780f8003,0xe01fe07e,0x3e000f8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3f,0xfffff078,0x30000ffe,0x1f007c0,0x0,0x1e00,
+ 0x3c00,0xf9cf80,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf00000fc,0x1e0780,0x3fff800,0x78ffe000,0xf0003,0xe03e00f0,
+ 0x3e0007,0xe000003f,0x7f,0xe01fffff,0xf00ffc00,0x1f80,0x3c01ff70,0x783c003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,
+ 0x80007800,0x780,0x3cfc0000,0x7800001e,0x3c78f078,0xf01e03c0,0x780780,0x3e0f000,0x1e078003,0xc01f0000,0xf000,0xf0003c0,0x1e007807,
+ 0x83f83c00,0x1ff00003,0xcf000000,0x3e00003c,0xf00,0x1e0,0x0,0x0,0x0,0x20007801,0xfc03e003,0xe003007c,0x3f803e0,0x7c0003c,
+ 0xf807,0xf007e00f,0x3c00,0xf000,0x780f800,0x1e0000f,0x87e1f01f,0x803c00f8,0x7c007f0,0xf803e01,0xfc003f80,0x80f8004,0x3c000,
+ 0x3c003c0,0x3c003c0f,0x1e03,0xe03e0078,0x3c0000,0x7c0001e,0xf0,0x780,0x0,0x0,0x3ffff800,0x1ff0000,0x0,0x7800000,0x0,0x18,
+ 0xc0,0x0,0x1818000,0x3e00000,0x3,0xc00f0000,0x1ff00000,0x3e00007,0xfc00003f,0xe0000003,0xc0003c00,0xf03c,0x3c0f00,0x0,0x0,
+ 0x0,0x70,0x380701f0,0x800000,0x780fc,0x1e001ff0,0x7c,0xf3c00,0x0,0x780000,0x7e182000,0xf000001f,0xfff00ffc,0xffc0003c,0x3cfe,
+ 0x0,0x31c0001,0x80f01f80,0x780f00,0x0,0x183,0x80e0c000,0xf,0x80000078,0x780,0x38,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x38000078,
+ 0xf000f01,0xe003ffe0,0x1fff00,0x7ff800,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x78000f8,0x3e000f07,0x8003c000,0x78000,
+ 0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
+ 0x78000f00,0x7c03e01e,0x1e3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78003cf0,0x1e0007,0x80f1e000,0x4000f00,0x20007801,0x3c008,
+ 0x1e0040,0xf00200,0x780403f,0x7803e00,0x3007c00f,0x803e007c,0x1f003e0,0xf801f00,0x780000,0x3c00000,0x1e000000,0xf00007f0,
+ 0x3e003f00,0x7801f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e003c,0x3c01e0,0x1e00f00,0xf007800,0x78078003,
+ 0xc01fc03e,0x1e000f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xf078007c,0x300007fc,0x7e00fe0,0x0,0x1e00,0x3c00,0x3e1c3e0,0x1e0000,0x0,0x0,0x0,0xf0001e00,
+ 0x3c0001e,0x1,0xf000fff8,0x1e0780,0x3fffe00,0x79fff000,0x1f0001,0xfffc00f0,0x7e0007,0xe000003f,0x3ff,0x801fffff,0xf003ff80,
+ 0x3f00,0x3c03fff0,0xf01e003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3df80000,0x7800001e,
+ 0x1c70f078,0x781e03c0,0x780780,0x3c0f000,0x1e078007,0xc01f8000,0xf000,0xf0003c0,0x1e007807,0x83f83c00,0xfe00003,0xff000000,
+ 0x7c00003c,0x780,0x1e0,0x0,0x0,0x0,0x7c01,0xf801f007,0xc00100f8,0x1f803c0,0x3c0003c,0x1f003,0xf007c00f,0x80003c00,0xf000,
+ 0x783f000,0x1e0000f,0x3c0f01f,0x3e01f0,0x3e007e0,0x7c07c00,0xfc003f00,0xf0000,0x3c000,0x3c003c0,0x3c003c0f,0x1e01,0xf07c007c,
+ 0x7c0000,0xfc0001e,0xf0,0x780,0x0,0x0,0x3ffff000,0x3838000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0xff0000,0x3f00000,0x3,0xc00fff00,
+ 0x38380000,0x7c0000e,0xe000070,0x70000001,0xe0003c00,0xf01e,0x780e00,0x0,0x0,0x0,0x0,0x1e0,0x0,0x780f8,0xf003838,0xfc,0xffc00,
+ 0x0,0x780000,0x7c180000,0xf000000f,0xffe00fff,0xffc0003c,0x783f,0x80000000,0x6380000,0xc0f83f80,0xf81f00,0x0,0x303,0x80e06000,
+ 0x0,0x78,0xf00,0x78,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x3800003c,0x3e000f81,0xf003ffe0,0x1fff00,0x1fc000,0xf000,0x1e03c00,
+ 0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e000f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,
+ 0x3c000001,0xe0001e00,0x3c0f0f0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3e07c01e,0x1e3c0f0,0x3c0780,0x1e03c00,
+ 0xf01e000,0x78003ff0,0x1e0007,0x80f1e000,0xf80,0x7c00,0x3e000,0x1f0000,0xf80000,0x7c0001e,0x3c07c00,0x10078007,0x803c003c,
+ 0x1e001e0,0xf000f00,0x780000,0x3c00000,0x1e000000,0xf00007c0,0x1e003e00,0x7c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,
+ 0xf,0x801f003c,0x3c01e0,0x1e00f00,0xf007800,0x7807c007,0xc01f801f,0x1f001f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xe078003c,0x300001f0,0x3f801ff0,0x0,
+ 0x3c00,0x1e00,0x3c1c1e0,0x1e0000,0x0,0x0,0x0,0xf0001e0f,0x3c0001e,0x3,0xe000fff0,0x3c0780,0x3ffff00,0x7bfff800,0x1e0000,0x7ff00078,
+ 0x7e0007,0xe000003f,0x1ffc,0x1fffff,0xf0007ff0,0x7e00,0x3c07c3f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,
+ 0x1fffff,0x80007800,0x780,0x3ffc0000,0x7800001e,0x1ef0f078,0x781e03c0,0x780780,0x7c0f000,0x1e07801f,0x800ff000,0xf000,0xf0003c0,
+ 0xf00f807,0x83b83c00,0xfc00001,0xfe000000,0xf800003c,0x780,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0xc00000f0,0xf80780,0x3c0003c,
+ 0x1e001,0xf007c007,0x80003c00,0xf000,0x787e000,0x1e0000f,0x3c0f01f,0x1e01e0,0x1e007c0,0x3c07800,0x7c003f00,0xf0000,0x3c000,
+ 0x3c003c0,0x3e007c07,0x80003c00,0xf8f8003c,0x780000,0xf80001e,0xf0,0x780,0x0,0x0,0x7ffff000,0x601c000,0x3,0xffff0000,0x0,
+ 0xfff,0xf8007fff,0xc0000000,0x7e003c,0x1fe0000,0xc0003,0xc00fff00,0x601c0000,0xf800018,0x70000c0,0x38000001,0xe0007800,0x701e,
+ 0x701e00,0x0,0x0,0x0,0x0,0x1e0,0x6,0x700f8,0xf00601c,0xf8,0x7f800,0x0,0x780000,0xf8180000,0xf000000f,0x87c00fff,0xffc0003c,
+ 0xf01f,0xc0000000,0x6380000,0xc07ff780,0x1f03e03,0xfffffe00,0x303,0x81c06000,0x0,0x1ffff,0xfe001e00,0x180f8,0x0,0x3c003c0,
+ 0x3ffe1c00,0x3f00000,0x0,0x3800003f,0xfe0007c0,0xf8000000,0x18000000,0xc0000006,0x1f000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,
+ 0x3c000f0,0x1e001f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f0f0,
+ 0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f0f801e,0x3c3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,
+ 0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07c00,0xf0007,0x8078003c,0x3c001e0,0x1e000f00,0x780000,0x3c00000,
+ 0x1e000000,0xf0000f80,0x1f003e00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0xf,0x3f003c,0x3c01e0,0x1e00f00,0xf007800,
+ 0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe078003f,0xb0000000,0xfc003cf0,0x0,0x3c00,0x1e00,0x101c040,0x1e0000,0x0,0x0,0x1,
+ 0xe0001e1f,0x83c0001e,0x7,0xe000fff0,0x3c0780,0x3c03f80,0x7fc0fc00,0x1e0000,0xfff80078,0xfe0007,0xe000003f,0x7fe0,0x1fffff,
+ 0xf0000ffc,0xfc00,0x780f81f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3ffc0000,
+ 0x7800001e,0x1ef0f078,0x3c1e03c0,0x780780,0x1fc0f000,0x1e07ffff,0x7ff00,0xf000,0xf0003c0,0xf00f007,0xc3b87c00,0x7c00001,0xfe000000,
+ 0xf800003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0x800000f0,0xf80780,0x1e0003c,0x1e001,0xf0078007,0x80003c00,0xf000,0x78fc000,
+ 0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,0x3c07800,0x7c003e00,0xf0000,0x3c000,0x3c003c0,0x1e007807,0x80003c00,0x7df0003c,0x780000,
+ 0x1f00001e,0xf0,0x780,0x0,0x0,0x7800000,0xe7ce000,0x3,0xffff0000,0x0,0xfff,0xf8007fff,0xc0000000,0x1f0,0xffe000,0x1c0003,
+ 0xc00fff00,0xe7ce0000,0xf800039,0xf38001cf,0x9c000000,0xe0007800,0x780e,0x701c00,0x0,0x0,0x0,0x0,0x1e0,0x7,0xf0078,0xf00e7ce,
+ 0x1f0,0x7f800,0x0,0x780000,0xf0180000,0xf000000e,0x1c0001f,0xe000003c,0xf007,0xe0000000,0x6380000,0xc03fe780,0x3e07c03,0xfffffe00,
+ 0x303,0xffc06000,0x0,0x1ffff,0xfe003ffe,0x1fff0,0x0,0x3c003c0,0x1ffe1c00,0x3f00000,0x7,0xffc0001f,0xfc0003e0,0x7c000001,0xfc00000f,
+ 0xe000007f,0x1e000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,
+ 0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,
+ 0x783c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07800,
+ 0xf0003,0xc078001e,0x3c000f0,0x1e000780,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,
+ 0x7800780,0x3c003c00,0xf,0x7f003c,0x3c01e0,0x1e00f00,0xf007800,0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe070001f,0xf8000007,
+ 0xf0007cf8,0x7800000,0x3c00,0x1e00,0x1c000,0x1e0000,0x0,0x0,0x1,0xe0001e1f,0x83c0001e,0xf,0xc000fff8,0x780780,0x2000f80,0x7f803e00,
+ 0x3e0003,0xfffe007c,0x1fe0000,0x0,0x3ff00,0x0,0x1ff,0x8001f000,0x780f00f0,0x1f00f003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,
+ 0xfe03c00f,0xf81fffff,0x80007800,0x780,0x3ffe0000,0x7800001e,0xee0f078,0x3c1e03c0,0x7807ff,0xff80f000,0x1e07fffe,0x3ffe0,
+ 0xf000,0xf0003c0,0xf00f003,0xc7bc7800,0xfc00000,0xfc000001,0xf000003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xe000f80f,0x800001e0,
+ 0xf80f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x79f8000,0x1e0000f,0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003e00,
+ 0xf0000,0x3c000,0x3c003c0,0x1e007807,0x81e03c00,0x7df0003e,0xf80000,0x3e00003e,0xf0,0x7c0,0xfc000,0x80000000,0x7800000,0x1e7cf000,
+ 0x3,0xffff0000,0x0,0x18,0xc0,0x0,0xf80,0x7ffc00,0x380003,0xc00fff01,0xe7cf0000,0x1f000079,0xf3c003cf,0x9e000000,0xe0007000,
+ 0x380e,0xe01c00,0x0,0x0,0x0,0x0,0x1e0,0x3,0x800f0078,0xf01e7cf,0x3e0,0x3f000,0x0,0x780000,0xf018001f,0xfff8001e,0x1e0000f,
+ 0xc000003c,0xf003,0xe0000000,0x6380000,0xc00fc780,0x7c0f803,0xfffffe00,0x303,0xfe006000,0x0,0x1ffff,0xfe003ffe,0x1ffe0,0x0,
+ 0x3c003c0,0xffe1c00,0x3f00000,0x7,0xffc00007,0xf00001f0,0x3e00001f,0xfc0000ff,0xe00007ff,0x3e000,0x3e01e00,0x1f00f000,0xf8078007,
+ 0xc03c003e,0x1e001e0,0xf001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,
+ 0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000fc0,
+ 0x1e0007,0x80f1f000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c0f800,0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,
+ 0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1e,0xf7803c,0x3c01e0,0x1e00f00,
+ 0xf007800,0x7803e00f,0x801e000f,0x80f803e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe0f0000f,0xff00001f,0x8000f87c,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,
+ 0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x1f,0x800000fe,0xf00780,0x7c0,0x7f001e00,0x3c0007,0xe03f003f,0x3fe0000,0x0,0x3fc00,0x0,
+ 0x7f,0x8001e000,0x781f00f0,0x1e00f003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3f9f0000,0x7800001e,
+ 0xfe0f078,0x3c1e03c0,0x7807ff,0xff00f000,0x1e07fff8,0xfff8,0xf000,0xf0003c0,0xf81f003,0xc7bc7800,0xfe00000,0x78000003,0xe000003c,
+ 0x1e0,0x1e0,0x0,0x0,0x0,0x1fffc01,0xe000780f,0x1e0,0x780f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7bf0000,0x1e0000f,
+ 0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xf8000,0x3c000,0x3c003c0,0x1f00f807,0x81f03c00,0x3fe0001e,0xf00000,0x7c00007c,
+ 0xf0,0x3e0,0x3ff801,0x80000000,0x7800000,0x3cfcf800,0x3,0xffff0000,0x0,0x18,0xc0,0x0,0x7c00,0x1fff00,0x700003,0xc00f0003,
+ 0xcfcf8000,0x3e0000f3,0xf3e0079f,0x9f000000,0xf000,0x1000,0x0,0x0,0x0,0x0,0x0,0x1f0,0x1,0xc00f0078,0xf03cfcf,0x800007c0,0x1e000,
+ 0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x8000003c,0xf001,0xf0000000,0x6380000,0xc0000000,0xf81f003,0xfffffe00,0x303,
+ 0x87006000,0x0,0x1ffff,0xfe003ffe,0x7f00,0x0,0x3c003c0,0x3fe1c00,0x3f00000,0x7,0xffc00000,0xf8,0x1f0001ff,0xf0000fff,0x80007ffc,
+ 0xfc000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf001e07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,
+ 0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3fc001e,0x1e03c0f0,0x3c0780,
+ 0x1e03c00,0xf01e000,0x78000780,0x1e0007,0x80f0fc00,0x3fff80,0x1fffc00,0xfffe000,0x7fff0003,0xfff8001f,0xffc0001e,0x3c0f000,
+ 0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,0x3c00000,0x1e000000,0xf0001e00,0xf803c00,0x3c078001,0xe03c000f,0x1e00078,
+ 0xf0003c0,0x78001e07,0xfffffe1e,0x1e7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801e00f,0x1e0007,0x807803c0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00007,
+ 0xffc0007e,0xf03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x3f,0x3e,0xf00780,0x3c0,0x7e001e00,
+ 0x7c000f,0x800f001f,0xffde0000,0x0,0x3e000,0x0,0xf,0x8003e000,0x781e0070,0x1e00f003,0xc001f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,
+ 0xf81e0007,0x80007800,0x780,0x3f1f0000,0x7800001e,0x7c0f078,0x1e1e03c0,0x7807ff,0xfc00f000,0x1e07fffe,0xffc,0xf000,0xf0003c0,
+ 0x781e003,0xc71c7800,0x1ff00000,0x78000003,0xe000003c,0x1e0,0x1e0,0x0,0x0,0x0,0xffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,
+ 0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7f000,0x3c000,
+ 0x3c003c0,0xf00f007,0xc1f07c00,0x1fc0001f,0x1f00000,0xfc000ff8,0xf0,0x1ff,0xfffe07,0x80000000,0x7800000,0x7ffcfc00,0x0,0xf000000,
+ 0x0,0x18,0xc0,0x0,0x3e000,0x1ff80,0xe00003,0xc00f0007,0xffcfc000,0x3e0001ff,0xf3f00fff,0x9f800000,0x6000,0x0,0x0,0x7c000,
+ 0x0,0x0,0x0,0xfe,0x0,0xe00f007f,0xff07ffcf,0xc0000fc0,0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x80000000,0xf800,
+ 0xf0000000,0x6380000,0xc0000000,0x1f03c000,0x1e00,0x303,0x83806000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xfe1c00,0x3f00000,0x0,
+ 0x0,0x3c,0xf801fff,0xfff8,0x7ffc0,0x1f8000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf003c07,0x8003c000,0x78000,
+ 0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
+ 0x78000f00,0x1f8001e,0x1e03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e000f,0x80f0ff00,0x1ffff80,0xffffc00,0x7fffe003,
+ 0xffff001f,0xfff800ff,0xffc007ff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,
+ 0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x3c7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801f01f,
+ 0x1e0007,0x807c07c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00000,0xfff003f0,0x1f00f03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x7ff80000,0x3,
+ 0xc0001e0f,0x3c0001e,0x7e,0x1f,0x1e00780,0x3e0,0x7e000f00,0x78000f,0x7800f,0xff9e0000,0x0,0x3fc00,0x0,0x7f,0x8003c000,0x781e0070,
+ 0x3e00f803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3e0f8000,0x7800001e,0x7c0f078,0x1e1e03c0,
+ 0x7807ff,0xf000f000,0x1e07807f,0xfe,0xf000,0xf0003c0,0x781e003,0xc71c7800,0x3ef00000,0x78000007,0xc000003c,0x1e0,0x1e0,0x0,
+ 0x0,0x0,0x1ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,
+ 0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7ff80,0x3c000,0x3c003c0,0xf00f003,0xc1f07800,0x1fc0000f,0x1e00000,0xf8000ff0,0xf0,
+ 0xff,0xffffff,0x80000000,0x3fffc000,0xfff9fe00,0x0,0xf000000,0x0,0x18,0xc0,0x0,0x1f0000,0x1fc0,0x1c00003,0xc00f000f,0xff9fe000,
+ 0x7c0003ff,0xe7f81fff,0x3fc00000,0x0,0x0,0x0,0xfe000,0x1ffffc0f,0xfffffc00,0x0,0xff,0xf0000000,0x700f007f,0xff0fff9f,0xe0000f80,
+ 0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00fff,0xffc00000,0xf800,0xf0000000,0x6380000,0xc0ffff80,0x3e078000,0x1e00,0x7ff80303,
+ 0x83c06000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,0x0,0x7f,0xff00001e,0x7c1fff0,0xfff80,0x7ffc00,0x3f0000,0x7c01f00,
+ 0x3e00f801,0xf007c00f,0x803e007c,0x1f003e0,0xf803c07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+ 0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f8001e,0x3c03c0f0,0x3c0780,0x1e03c00,0xf01e000,
+ 0x78000780,0x1e001f,0xf07f80,0x3ffff80,0x1ffffc00,0xffffe007,0xffff003f,0xfff801ff,0xffc03fff,0xffc0f000,0x1fffff,0xc0fffffe,
+ 0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,
+ 0xfffffe1e,0x787803c,0x3c01e0,0x1e00f00,0xf007800,0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x3ff80fc0,0x7fc1e01f,
+ 0x7800000,0x3c00,0x1e00,0x0,0x7fffff80,0x0,0x7ff80000,0x7,0x80001e00,0x3c0001e,0xfc,0xf,0x1e00780,0x1e0,0x7c000f00,0x78000f,
+ 0x78007,0xff1e0000,0x0,0x3ff00,0x0,0x1ff,0x8003c000,0x781e0070,0x3c007803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,
+ 0x80007800,0x780,0x3c07c000,0x7800001e,0x7c0f078,0xf1e03c0,0x780780,0xf000,0x1e07801f,0x3e,0xf000,0xf0003c0,0x781e003,0xcf1c7800,
+ 0x3cf80000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,0x0,0x0,0x3ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,
+ 0x80003c00,0xf000,0x7ff8000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3fff0,0x3c000,0x3c003c0,0xf81f003,
+ 0xc3b87800,0xf80000f,0x1e00001,0xf0000ff0,0xf0,0xff,0xf03fff,0x80000000,0x3fff8001,0xfff1ff00,0x0,0xf000000,0x0,0x18,0xc0,
+ 0x0,0x380000,0x7c0,0x3c00003,0xc00f001f,0xff1ff000,0xf80007ff,0xc7fc3ffe,0x3fe00000,0x0,0x0,0x0,0x1ff000,0x7ffffe1f,0xffffff00,
+ 0x0,0x7f,0xfe000000,0x780f007f,0xff1fff1f,0xf0001f00,0x1e000,0x0,0x780001,0xe0180000,0xf000001c,0xe00fff,0xffc00000,0x7c00,
+ 0xf0000000,0x31c0001,0x80ffff80,0x3e078000,0x1e00,0x7ff80183,0x81c0c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,
+ 0x0,0x7f,0xff00001e,0x7c7ff03,0xc03ff8fe,0x1ffc0f0,0x7e0000,0x7800f00,0x3c007801,0xe003c00f,0x1e0078,0xf003c0,0x7803c07,0x8003c000,
+ 0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,
+ 0xf0001e0,0x78000f00,0x3fc001e,0x7803c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e007f,0xf03fe0,0x7ffff80,0x3ffffc01,
+ 0xffffe00f,0xffff007f,0xfff803ff,0xffc07fff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,
+ 0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x707803c,0x3c01e0,0x1e00f00,0xf007800,
+ 0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x30f81f00,0xffe1e00f,0x87800000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,
+ 0x7,0x80001e00,0x3c0001e,0x1f8,0x7,0x83c00780,0x1e0,0x7c000f00,0xf8001e,0x3c001,0xfc1e0000,0x0,0x7fe0,0x0,0xffc,0x3c000,0x781e0070,
+ 0x3ffff803,0xc000783c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x380f078,0xf1e03c0,
+ 0x780780,0xf000,0x1e07800f,0x8000001e,0xf000,0xf0003c0,0x3c3c003,0xcf1e7800,0x7c780000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,
+ 0x0,0x0,0x7f003c01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7f7c000,0x1e0000f,0x3c0f01e,
+ 0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfff8,0x3c000,0x3c003c0,0x781e003,0xc3b87800,0x1fc00007,0x83e00003,0xe0000ff8,0xf0,
+ 0x1ff,0xc007fe,0x0,0x7fff8001,0xffe3ff00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x3c0,0x7800003,0xc00f001f,0xfe3ff000,0xf80007ff,
+ 0x8ffc3ffc,0x7fe00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x1f,0xff000000,0x3c0f007f,0xff1ffe3f,0xf0003e00,0x1e000,0x0,0x780001,
+ 0xe0180000,0xf000001e,0x1e00fff,0xffc00000,0x3f00,0xf0000000,0x31c0001,0x80ffff80,0x1f03c000,0x1e00,0x7ff80183,0x81c0c000,
+ 0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x7f,0xff00003c,0xf87f007,0xc03f83ff,0x81fc01f0,0x7c0000,0x7ffff00,0x3ffff801,
+ 0xffffc00f,0xfffe007f,0xfff003ff,0xff807fff,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+ 0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf003c0f0,0x3c0780,0x1e03c00,0xf01e000,
+ 0x78000780,0x1ffffe,0xf00ff0,0xfe00780,0x7f003c03,0xf801e01f,0xc00f00fe,0x7807f0,0x3c0ffff,0xffc0f000,0x1fffff,0xc0fffffe,
+ 0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
+ 0x1e,0xf07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783e,0x1e0007,0x801e0f80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x307c0801,0xe1f1e00f,0x87000000,
+ 0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,0xf,0x1e00,0x3c0001e,0x3f0,0x7,0x83fffffc,0x1e0,0x7c000f00,0xf0001e,0x3c000,0x3e0000,
+ 0x0,0x1ffc,0x1fffff,0xf0007ff0,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x3c000,0x781e0007,0x80007800,
+ 0x780,0x3c03e000,0x7800001e,0xf078,0x79e03c0,0x780780,0xf000,0x1e078007,0x8000000f,0xf000,0xf0003c0,0x3c3c001,0xee0ef000,
+ 0xf87c0000,0x7800001f,0x3c,0x78,0x1e0,0x0,0x0,0x0,0x7c003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
+ 0xf000,0x7e3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x1ffc,0x3c000,0x3c003c0,0x781e003,0xe3b8f800,
+ 0x1fc00007,0x83c00007,0xc00000fc,0xf0,0x3e0,0x8001f8,0x0,0x7800000,0xffc7fe00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,
+ 0xf000003,0xc00f000f,0xfc7fe001,0xf00003ff,0x1ff81ff8,0xffc00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x3,0xff800000,0x1e0f0078,
+ 0xffc7f,0xe0007c00,0x1e000,0x0,0x780001,0xe0180000,0xf000000e,0x1c00007,0x80000000,0x1f81,0xe0000000,0x38e0003,0x80000000,
+ 0xf81f000,0x1e00,0x7ff801c3,0x80e1c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf8,0x1f070007,0xc03803ff,0xc1c001f0,
+ 0xf80000,0xfffff00,0x7ffff803,0xffffc01f,0xfffe00ff,0xfff007ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,
+ 0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f00f,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,0xf003c0f0,
+ 0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1ffffc,0xf003f8,0xf800780,0x7c003c03,0xe001e01f,0xf00f8,0x7807c0,0x3c0fc1e,0xf000,
+ 0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,
+ 0xf0003c0,0x78001e00,0x1e,0x1e07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783c,0x1e0007,0x801e0f00,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xffff8000,0x303c0001,
+ 0xc071e007,0xcf000000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0xf,0xf00,0x780001e,0x7e0,0x7,0x83fffffc,0x1e0,0x7c000f00,0x1f0001e,
+ 0x3c000,0x3c0000,0x0,0x3ff,0x801fffff,0xf003ff80,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,
+ 0x80007800,0x780,0x3c01f000,0x7800001e,0xf078,0x79e03c0,0xf00780,0xf000,0x3e078007,0xc000000f,0xf000,0xf0003c0,0x3c3c001,
+ 0xee0ef000,0xf03e0000,0x7800003e,0x3c,0x78,0x1e0,0x0,0x0,0x0,0xf8003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,
+ 0x80003c00,0xf000,0x7c3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfc,0x3c000,0x3c003c0,0x3c3e001,0xe7b8f000,
+ 0x3fe00007,0xc7c0000f,0xc000003e,0xf0,0x7c0,0x0,0x0,0x7c00000,0x7fcffc00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,0x1e000003,
+ 0xc00f0007,0xfcffc003,0xe00001ff,0x3ff00ff9,0xff800000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x1f800000,0xf0f0078,0x7fcff,
+ 0xc000fc00,0x1e000,0x0,0x780001,0xe0180000,0xf000000f,0x87c00007,0x80000000,0xfe3,0xe0000000,0x18780c3,0x0,0x7c0f800,0x1e00,
+ 0xc3,0x80e18000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x1f0,0x3e00000f,0xc0000303,0xe00003f0,0xf00000,0xfffff80,
+ 0x7ffffc03,0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,
+ 0x3c000001,0xe0001e00,0x780f00f,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1f0f801f,0xe00780f0,0x3c0780,0x1e03c00,
+ 0xf01e000,0x78000780,0x1ffff8,0xf000f8,0x1f000780,0xf8003c07,0xc001e03e,0xf01f0,0x780f80,0x3c1f01e,0xf000,0x1e0000,0xf00000,
+ 0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
+ 0x1e,0x3c07803c,0x3c01e0,0x1e00f00,0xf007800,0x78007c7c,0x1e0007,0x801f1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c00000,0x303c0003,0x8039e003,0xef000000,
+ 0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0xfc0,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,
+ 0x0,0x7f,0xe01fffff,0xf00ffc00,0x3c000,0x781f00f0,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,0x80007800,
+ 0x780,0x3c01f000,0x7800001e,0xf078,0x7de01e0,0xf00780,0x7800,0x3c078003,0xc000000f,0xf000,0xf0003c0,0x3e7c001,0xee0ef001,
+ 0xf01e0000,0x7800003e,0x3c,0x3c,0x1e0,0x0,0x0,0x0,0xf0003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
+ 0xf000,0x781f000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0x7df00003,
+ 0xc780000f,0x8000003e,0xf0,0x780,0x0,0x0,0x3c00000,0x3fcff800,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x1f00fc,0x1e0,0x1e000001,
+ 0xe00f0003,0xfcff8003,0xe00000ff,0x3fe007f9,0xff000000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x7c00000,0xf0f0078,0x3fcff,0x8000f800,
+ 0x1e000,0x0,0x780001,0xe0180000,0xf000001f,0xffe00007,0x8000003c,0x7ff,0xc0000000,0x1c3ffc7,0x0,0x3e07c00,0x1e00,0xe3,0x80738000,
+ 0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x3e0,0x7c00001d,0xc0000001,0xe0000770,0x1f00000,0xfffff80,0x7ffffc03,
+ 0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+ 0xe0001e00,0x780f00f,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0x3e07c01f,0xc00780f0,0x3c0780,0x1e03c00,0xf01e000,
+ 0x78000780,0x1fffc0,0xf0007c,0x1e000780,0xf0003c07,0x8001e03c,0xf01e0,0x780f00,0x3c1e01e,0xf000,0x1e0000,0xf00000,0x7800000,
+ 0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1e,0x7807803c,
+ 0x3c01e0,0x1e00f00,0xf007800,0x78003c78,0x1e0007,0x800f1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x83c00000,0x303c0003,0x8039e001,0xee000000,0x1e00,0x3c00,
+ 0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0x1f80,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,0x0,0x1f,0xfc1fffff,
+ 0xf07ff000,0x0,0x780f00f0,0x78003c03,0xc000781e,0x1e0,0xf803c0,0x1e00,0x1e000,0x781e0007,0x80007800,0x780,0x3c00f800,0x7800001e,
+ 0xf078,0x3de01e0,0xf00780,0x7800,0x3c078003,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfe0ff003,0xe01f0000,0x7800007c,0x3c,0x3c,
+ 0x1e0,0x0,0x0,0x0,0xf0007c01,0xe000f80f,0x800001e0,0xf80f00,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x780f800,0x1e0000f,
+ 0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003c00,0x1e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0xf8f80003,0xe780001f,0x1e,
+ 0xf0,0x780,0x0,0x0,0x3c00000,0x1ffff000,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x3bc1de,0x1e0,0xf000001,0xe00f0001,0xffff0007,0xc000007f,
+ 0xffc003ff,0xfe000000,0x0,0x0,0x0,0xfe000,0x0,0x0,0x0,0x0,0x3c00000,0x1e0f0078,0x1ffff,0x1f000,0x1e000,0x0,0x780000,0xf0180000,
+ 0xf000001f,0xfff00007,0x8000003c,0x1ff,0x80000000,0xe0ff0e,0x0,0x1f03e00,0x1e00,0x70,0x70000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,
+ 0xe1c00,0x0,0x0,0x0,0x7c0,0xf8000019,0xc0000000,0xe0000670,0x1e00000,0xf000780,0x78003c03,0xc001e01e,0xf00f0,0x780780,0x3c0f807,
+ 0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf80f007,0xbc03c001,0xe01e000f,
+ 0xf00078,0x78003c0,0x3c001e00,0x7c03e00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,
+ 0xf0007c07,0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0xf800,0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,
+ 0xf0001e00,0x7803c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1f8001f,0xf00f803c,0x3c01e0,0x1e00f00,0xf007800,
+ 0x78003e78,0x1e000f,0x800f9e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x3c00000,0x303c0003,0x8039f001,0xfe000000,0x1e00,0x3c00,0x0,0x1e0000,0x0,0x0,0x3c,0xf00,
+ 0x780001e,0x3f00,0x7,0x80000780,0x3e0,0x3e000f00,0x3c0001e,0x3c000,0x7c0000,0x0,0x3,0xfe000000,0xff8000,0x0,0x3c0f81f0,0xf0001e03,
+ 0xc000780f,0x1e0,0xf003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x780,0x3c007c00,0x7800001e,0xf078,0x3de01e0,0xf00780,0x7800,
+ 0x3c078001,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfc07f003,0xe00f0000,0x78000078,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
+ 0xf000f007,0x800000f0,0xf80780,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
+ 0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78001,0xe71df000,0xf8f80001,0xef80003e,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,
+ 0xfffe000,0x0,0x3e000000,0x0,0x18,0x7fff,0xc0000000,0x60c306,0x1e0,0x7800001,0xe00f0000,0xfffe0007,0x8000003f,0xff8001ff,
+ 0xfc000000,0x0,0x0,0x0,0x7c000,0x0,0x0,0x0,0x0,0x3c00000,0x3c0f0078,0xfffe,0x3e000,0x1e000,0x0,0x780000,0xf0180000,0xf000003c,
+ 0xfcf80007,0x8000003c,0x7f,0x0,0x70001c,0x0,0xf81f00,0x0,0x38,0xe0000,0x0,0x0,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf81,
+ 0xf0000039,0xc0000000,0xe0000e70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,0x8000f000,0x78000,
+ 0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f007,0xbc03c001,0xe01e000f,0xf00078,0x78003c0,
+ 0x3c001e00,0xf801f00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,0x8003e03c,
+ 0x1f01e0,0xf80f00,0x7c1e01e,0x7800,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,
+ 0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xe00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef8,0x1f000f,
+ 0x7be00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0xf,0x3c00000,0x307c0003,0x8038f000,0xfc000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e00003c,0x780,0xf00001e,
+ 0x7e00,0xf,0x80000780,0x3c0,0x3e001e00,0x3c0001f,0x7c000,0x780007,0xe000003f,0x0,0xfe000000,0xfe0000,0x0,0x3c07c3f0,0xf0001e03,
+ 0xc000f80f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x4000f80,0x3c003c00,0x7800001e,0xf078,0x1fe01f0,0x1f00780,
+ 0x7c00,0x7c078001,0xf000001f,0xf000,0xf0003c0,0x1e78001,0xfc07f007,0xc00f8000,0x780000f8,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
+ 0xf000f007,0xc00000f0,0xf80780,0x3c,0x1f003,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
+ 0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78000,0xfe0fe001,0xf07c0001,0xef00007c,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,
+ 0x7cfc000,0xfc00000,0x3c00000f,0xc3f00000,0x18,0x7fff,0xc0000000,0x406303,0x3e0,0x3c00001,0xf00f0000,0x7cfc000f,0x8000001f,
+ 0x3f0000f9,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x780700f8,0x7cfc,0x7c000,0x1e000,0x0,0x780000,0xf8180000,
+ 0xf0000070,0x3c0007,0x8000003c,0x3f,0x80000000,0x3c0078,0x0,0x780f00,0x0,0x1e,0x3c0000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,0xe1c00,
+ 0x0,0x0,0x0,0xf01,0xe0000071,0xc0000000,0xe0001c70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
+ 0x8000f800,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00f003,0xfc03e003,0xe01f001f,
+ 0xf800f8,0x7c007c0,0x3e003e01,0xf000f80f,0xf00f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,
+ 0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0x7c00,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,
+ 0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xc00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef0,
+ 0x1f000f,0x7bc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x780000,0xf,0x3800040,0x30780003,0x8038f800,0x78000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
+ 0x780,0x1f00001e,0xfc00,0x20001f,0x780,0x80007c0,0x1f001e00,0x7c0000f,0x78000,0xf80007,0xe000003f,0x0,0x1e000000,0xf00000,
+ 0x3c000,0x3c03fff0,0xf0001e03,0xc001f007,0x800101e0,0x7e003c0,0x1e00,0x7800,0x781e0007,0x80007800,0x6000f00,0x3c003e00,0x7800001e,
+ 0xf078,0x1fe00f0,0x1e00780,0x3c00,0x78078000,0xf020001e,0xf000,0x7800780,0xff0001,0xfc07f00f,0x8007c000,0x780001f0,0x3c,0xf,
+ 0x1e0,0x0,0x0,0x0,0xf800fc01,0xf801f007,0xc00100f8,0x1f807c0,0x40003c,0xf807,0xf0078007,0x80003c00,0xf000,0x7803e00,0x1f0000f,
+ 0x3c0f01e,0x1e01f0,0x3e007e0,0x7c07c00,0xfc003c00,0x1e,0x3e000,0x3e007c0,0x1ff8000,0xfe0fe003,0xe03e0001,0xff0000fc,0x1e,
+ 0xf0,0x780,0x0,0x0,0x1f00080,0x3cf8000,0xfc00000,0x3c00001f,0x83f00000,0x18,0xc0,0x0,0xc06203,0x40003c0,0x1c00000,0xf80f0000,
+ 0x3cf8001f,0xf,0x3e000079,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x700780fc,0x3cf8,0xfc000,0x1e000,0x0,0x780000,
+ 0x7c180000,0xf0000020,0x100007,0x8000003c,0xf,0x80000000,0x1f01f0,0x0,0x380700,0x0,0xf,0x80f80000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,
+ 0xe1c00,0x0,0x0,0x0,0xe01,0xc0000071,0xc0000001,0xc0001c70,0x1e00040,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
+ 0x80007800,0x10078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00f003,0xfc01e003,0xc00f001e,
+ 0x7800f0,0x3c00780,0x1e003c00,0xe000700f,0x800f0078,0x7803c0,0x3c01e00,0x1e00f000,0xf0000780,0x1e0000,0xf0003c,0x1f001f80,
+ 0xf800fc07,0xc007e03e,0x3f01f0,0x1f80f80,0xfc1e01f,0x7c00,0x100f8000,0x807c0004,0x3e00020,0x1f000100,0x780000,0x3c00000,0x1e000000,
+ 0xf0000f80,0x1f003c00,0x3c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,0x1f8000f,0x801f003e,0x7c01f0,0x3e00f80,0x1f007c00,
+ 0xf8001ff0,0x1f801f,0x7fc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0xf,0x7800078,0x31f80001,0xc070fc00,0xfc000000,0x1e00,0x7c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
+ 0x7c0,0x1f00001e,0x1f000,0x38003f,0x780,0xe000f80,0x1f803e00,0x780000f,0x800f8000,0x1f00007,0xe000003f,0x0,0x2000000,0x800000,
+ 0x3c000,0x3e01ff71,0xf0001f03,0xc007f007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x781e0007,0x80007800,0x7801f00,0x3c001f00,0x7800001e,
+ 0xf078,0xfe00f8,0x3e00780,0x3e00,0xf8078000,0xf838003e,0xf000,0x7c00f80,0xff0000,0xfc07e00f,0x8003c000,0x780001e0,0x3c,0xf,
+ 0x1e0,0x0,0x0,0x0,0xf801fc01,0xfc03e003,0xe003007c,0x3f803e0,0x1c0003c,0xfc0f,0xf0078007,0x80003c00,0xf000,0x7801f00,0xf8000f,
+ 0x3c0f01e,0x1e00f8,0x7c007f0,0xf803e01,0xfc003c00,0x8003e,0x1f000,0x1e00fc0,0xff0000,0xfe0fe007,0xc01f0000,0xfe0000f8,0x1e,
+ 0xf0,0x780,0x0,0x0,0xf80180,0x1cf0000,0x1f800000,0x3c00001f,0x83e00000,0x18,0xc0,0x0,0xc06203,0x70007c0,0xe00000,0x7e0f0000,
+ 0x1cf0001e,0x7,0x3c000039,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100,0x7c00000,0xe00780fc,0x2001cf0,0xf8000,0x1e000,0x0,
+ 0x780000,0x7e182000,0xf0000000,0x7,0x8000003c,0x7,0xc0000000,0x7ffc0,0x0,0x180300,0x0,0x3,0xffe00000,0x0,0x0,0x0,0x0,0x0,
+ 0x3f00fc0,0xe1c00,0x0,0x0,0x0,0xc01,0x800000e1,0xc0000003,0xc0003870,0x1f001c0,0x3e0003e1,0xf0001f0f,0x8000f87c,0x7c3e0,0x3e1f00,
+ 0x1f1e007,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e03,0xfc00f001,0xfc01f007,
+ 0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x4000201f,0xc01f007c,0xf803e0,0x7c01f00,0x3e00f801,0xf0000780,0x1e0000,0xf0007c,
+ 0x1f003f80,0xf801fc07,0xc00fe03e,0x7f01f0,0x3f80f80,0x1fc1f03f,0x803e00,0x3007c003,0x803e001c,0x1f000e0,0xf800700,0x780000,
+ 0x3c00000,0x1e000000,0xf00007c0,0x3e003c00,0x3c01f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e001e,0xfc00f0,
+ 0x7e00780,0x3f003c01,0xf8000fe0,0x1fc03e,0x3f800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,0xfff00001,0xe0f07f03,0xfe000000,0xf00,0x7800,0x0,
+ 0x1e0000,0xfc0000,0x0,0x7e0000f0,0x3f0,0x7e000fff,0xfc03ffff,0xf83f00fe,0x780,0xfc03f80,0xfc0fc00,0xf800007,0xe03f0018,0x7e00007,
+ 0xe000003f,0x0,0x0,0x0,0x3c000,0x1e007c71,0xe0000f03,0xffffe003,0xf01f01ff,0xff8003ff,0xffe01e00,0x3f01,0xf81e0007,0x803ffff0,
+ 0x7e03f00,0x3c000f00,0x7ffffe1e,0xf078,0xfe007e,0xfc00780,0x1f83,0xf0078000,0x783f00fe,0xf000,0x3f03f00,0xff0000,0xfc07e01f,
+ 0x3e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7e07fc01,0xfe07e001,0xf80f007e,0x7f801f8,0xfc0003c,0x7ffe,0xf0078007,
+ 0x807ffffe,0xf000,0x7801f00,0xfff00f,0x3c0f01e,0x1e00fc,0xfc007f8,0x1f803f03,0xfc003c00,0xf80fc,0x1fff0,0x1f83fc0,0xff0000,
+ 0xfc07e007,0xc01f0000,0xfe0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfe0780,0xfe0000,0x1f000000,0x3c00001f,0x7c00e03,0x81c00018,
+ 0xc0,0x0,0x406203,0x7e01fc0,0x700000,0x7fffff80,0xfe0003f,0xffffc003,0xf800001f,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f0,
+ 0x1f800001,0xc007c1fe,0x6000fe0,0x1ffffe,0x1e000,0x0,0x780000,0x3f98e03f,0xffff8000,0x7,0x8000003c,0x7,0xc0000000,0xfe00,
+ 0x0,0x80100,0x0,0x0,0x7f000000,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3f83fe8,0xe1c00,0x0,0x0,0x0,0x801,0xc1,0xc0000007,0x80003070,
+ 0xfc0fc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc03f01,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,
+ 0xffff001f,0xfff800ff,0xffc01fff,0xf800f001,0xfc00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,0x1f,0xf07e003f,0x3f001f8,
+ 0x1f800fc0,0xfc007e07,0xe0000780,0x1e0000,0xf301f8,0xfc0ff80,0x7e07fc03,0xf03fe01f,0x81ff00fc,0xff807e0,0x7fc0f87f,0x81801f80,
+ 0xf003f01f,0x801f80fc,0xfc07e0,0x7e03f00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff807e0,0x7e003c00,0x3c01f81f,0x800fc0fc,0x7e07e0,
+ 0x3f03f00,0x1f81f800,0x1f8000f,0xe07e001f,0x83fc00fc,0x1fe007e0,0xff003f07,0xf8000fe0,0x1fe07e,0x3f800,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,
+ 0xffe00000,0xffe03fff,0xdf000000,0xf00,0x7800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0x1ff,0xfc000fff,0xfc03ffff,0xf83ffffc,0x780,
+ 0xfffff00,0x7fff800,0xf000007,0xffff001f,0xffe00007,0xe000003f,0x0,0x0,0x0,0x3c000,0x1e000001,0xe0000f03,0xffffc001,0xffff01ff,
+ 0xff0003ff,0xffe01e00,0x1fff,0xf81e0007,0x803ffff0,0x7fffe00,0x3c000f80,0x7ffffe1e,0xf078,0xfe003f,0xff800780,0xfff,0xf0078000,
+ 0x7c3ffffc,0xf000,0x3ffff00,0xff0000,0xf803e01e,0x1e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7fffbc01,0xffffc000,
+ 0xffff003f,0xfff800ff,0xffc0003c,0x3ffe,0xf0078007,0x807ffffe,0xf000,0x7800f80,0x7ff00f,0x3c0f01e,0x1e007f,0xff8007ff,0xff001fff,
+ 0xbc003c00,0xffffc,0x1fff0,0x1fffbc0,0xff0000,0x7c07c00f,0x800f8000,0x7e0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7fff80,0x7c0000,
+ 0x1f000000,0x3c00001e,0x7c00f07,0xc1e00018,0xc0,0x0,0x60e303,0x7ffff80,0x380000,0x3fffff80,0x7c0003f,0xffffc001,0xf000000f,
+ 0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff800003,0x8003ffff,0xfe0007c0,0x1ffffe,0x1e000,0x0,0x780000,0x1fffe03f,0xffff8000,
+ 0x7,0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3fffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x1c1,
+ 0xc000000f,0x7070,0x7fffc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,
+ 0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000f001,0xfc007fff,0x3fff8,0x1fffc0,0xfffe00,0x7fff000,0x3b,0xfffc003f,
+ 0xfff001ff,0xff800fff,0xfc007fff,0xe0000780,0x1e0000,0xf3fff8,0xffff780,0x7fffbc03,0xfffde01f,0xffef00ff,0xff7807ff,0xfbc0ffff,
+ 0xff800fff,0xf001ffff,0x800ffffc,0x7fffe0,0x3ffff00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff803ff,0xfc003c00,0x3c00ffff,0x7fff8,
+ 0x3fffc0,0x1fffe00,0xffff000,0x1f,0xfffc001f,0xffbc00ff,0xfde007ff,0xef003fff,0x780007e0,0x1ffffc,0x1f800,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x700003f,
+ 0xffc00000,0x7fc01fff,0x9f800000,0xf80,0xf800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0xff,0xf8000fff,0xfc03ffff,0xf83ffff8,0x780,
+ 0xffffe00,0x7fff000,0xf000003,0xfffe001f,0xffc00007,0xe000003f,0x0,0x0,0x0,0x3c000,0xf000003,0xe0000f83,0xffff0000,0xffff01ff,
+ 0xfc0003ff,0xffe01e00,0xfff,0xf01e0007,0x803ffff0,0x7fffc00,0x3c0007c0,0x7ffffe1e,0xf078,0x7e003f,0xff000780,0x7ff,0xe0078000,
+ 0x3c3ffff8,0xf000,0x1fffe00,0x7e0000,0xf803e03e,0x1f000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x3fff3c01,0xefff8000,
+ 0x7ffe001f,0xff78007f,0xff80003c,0x1ffc,0xf0078007,0x807ffffe,0xf000,0x78007c0,0x3ff00f,0x3c0f01e,0x1e003f,0xff0007bf,0xfe000fff,
+ 0xbc003c00,0xffff8,0xfff0,0xfff3c0,0x7e0000,0x7c07c01f,0x7c000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3fff80,0x380000,
+ 0x3e000000,0x7c00003e,0x7801f07,0xc1e00018,0xc0,0x0,0x39c1ce,0x7ffff00,0x1c0000,0xfffff80,0x380003f,0xffffc000,0xe0000007,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff000007,0x1ffcf,0xfe000380,0x1ffffe,0x1e000,0x0,0x780000,0xfffe03f,0xffff8000,0x7,
+ 0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x381,
+ 0xc000001e,0xe070,0x7fff80,0x7c0001f3,0xe0000f9f,0x7cf8,0x3e7c0,0x1f3e00,0xfbe007,0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,
+ 0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000f000,0xfc007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x79,0xfff8001f,
+ 0xffe000ff,0xff0007ff,0xf8003fff,0xc0000780,0x1e0000,0xf3fff0,0x7ffe780,0x3fff3c01,0xfff9e00f,0xffcf007f,0xfe7803ff,0xf3c07ff3,
+ 0xff8007ff,0xe000ffff,0x7fff8,0x3fffc0,0x1fffe00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff801ff,0xf8003c00,0x3c007ffe,0x3fff0,
+ 0x1fff80,0xfffc00,0x7ffe000,0x1d,0xfff8000f,0xff3c007f,0xf9e003ff,0xcf001ffe,0x780007c0,0x1efff8,0x1f000,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0xf000003,
+ 0xfe000000,0x1f000fff,0xfc00000,0x780,0xf000,0x0,0x0,0xf80000,0x0,0x7e0001e0,0x7f,0xf0000fff,0xfc03ffff,0xf81ffff0,0x780,
+ 0x7fff800,0x1ffe000,0x1f000000,0xfff8001f,0xff000007,0xe000003e,0x0,0x0,0x0,0x3c000,0xf800003,0xc0000783,0xfff80000,0x3ffe01ff,
+ 0xe00003ff,0xffe01e00,0x7ff,0xc01e0007,0x803ffff0,0x3fff800,0x3c0003c0,0x7ffffe1e,0xf078,0x7e000f,0xfe000780,0x3ff,0xc0078000,
+ 0x3e1fffe0,0xf000,0x7ff800,0x7e0000,0xf803e07c,0xf800,0x780003ff,0xfffc003c,0x3,0xc00001e0,0x0,0x0,0x0,0xffe3c01,0xe7ff0000,
+ 0x3ffc000f,0xfe78003f,0xfe00003c,0x7f0,0xf0078007,0x807ffffe,0xf000,0x78003e0,0xff00f,0x3c0f01e,0x1e001f,0xfe00079f,0xfc0007ff,
+ 0x3c003c00,0x7ffe0,0x1ff0,0x7fe3c0,0x7e0000,0x7c07c03e,0x3e000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfff00,0x100000,
+ 0x3e000000,0x7800003c,0xf800f07,0xc1e00018,0xc0,0x0,0x1f80fc,0x3fffc00,0xc0000,0x3ffff80,0x100003f,0xffffc000,0x40000002,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xfc000006,0xff87,0xfc000100,0x1ffffe,0x1e000,0x0,0x780000,0x3ffc03f,0xffff8000,0x7,
+ 0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dff9f8,0xe1c00,0x0,0x0,0x0,0x0,0x3ff,
+ 0xf800003c,0xfffe,0x1ffe00,0x780000f3,0xc000079e,0x3cf0,0x1e780,0xf3c00,0x7bc007,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,
+ 0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,0xf000,0xfc001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x70,0xfff00007,
+ 0xff80003f,0xfc0001ff,0xe0000fff,0x780,0x1e0000,0xf3ffe0,0x1ffc780,0xffe3c00,0x7ff1e003,0xff8f001f,0xfc7800ff,0xe3c03fe1,
+ 0xff0003ff,0xc0007ffc,0x3ffe0,0x1fff00,0xfff800,0xfffffc07,0xffffe03f,0xffff01ff,0xfff800ff,0xf0003c00,0x3c003ffc,0x1ffe0,
+ 0xfff00,0x7ff800,0x3ffc000,0x38,0xfff00007,0xfe3c003f,0xf1e001ff,0x8f000ffc,0x780007c0,0x1e7ff0,0x1f000,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
+ 0x1fc,0x0,0x780,0xf000,0x0,0x0,0x1f80000,0x0,0x1e0,0x1f,0xc0000000,0x0,0x1ff80,0x0,0xffc000,0x7f8000,0x0,0x3fe00007,0xfc000000,
+ 0x7e,0x0,0x0,0x0,0x0,0x7c00000,0x0,0x0,0xff00000,0x0,0x0,0xfe,0x0,0x0,0x3fc000,0x0,0x0,0x0,0x3,0xf8000000,0xff,0xc0000000,
+ 0x1ff00,0x0,0x1fe000,0x0,0x0,0x0,0x0,0x3c,0x3,0xc00001e0,0x0,0x0,0x0,0x3f80000,0x1fc0000,0x7f00003,0xf8000007,0xf0000000,
+ 0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x7,0xf8000787,0xf00001fc,0x3c000000,0x7f80,0x0,0x1f8000,0x0,0x0,0x0,0x7c000000,0x1e,
+ 0xf0,0x780,0x0,0x0,0x3fc00,0x0,0x3c000000,0x7800003c,0xf000601,0xc00018,0xc0,0x0,0x0,0x3fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xf0000000,0x7e03,0xf0000000,0x0,0x0,0x0,0x0,0xfe0000,0x0,0x0,0x3c,0x2007,0x80000000,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c7e0f0,0xe1c00,0x0,0x3800000,0x0,0x0,0x3ff,0xf8000078,0xfffe,0x7f800,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,0x7f0000,0x70,0x3fc00001,0xfe00000f,0xf000007f,
+ 0x800003fc,0x0,0x0,0xff00,0x7f0000,0x3f80000,0x1fc00000,0xfe000007,0xf000003f,0x80001f80,0xfc00007f,0xfe0,0x7f00,0x3f800,
+ 0x1fc000,0x0,0x0,0x0,0x3f,0xc0000000,0xff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x78,0x3fc00001,0xf800000f,0xc000007e,0x3f0,0x7c0,
+ 0x1e1fc0,0x1f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xe0000000,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,0x1e,0xf0,0x780,0x0,0x0,0x0,0x0,0x3c000000,0x78000078,0xf000000,0x18,0xc0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3c0f,0x80000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0x1800000,0x0,0x0,0x3ff,0xf80000f0,0xfffe,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x780,0x1e0000,0x1e000,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
+ 0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x1f80000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf8000000,
+ 0x1f,0xf0,0xf80,0x0,0x0,0x0,0x0,0x78000000,0xf8000078,0x1e000000,0x8,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3fff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x3c00000,0xe1c00,0x0,0x1c00000,0x0,0x0,0x1,0xc00001e0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x1e0000,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x3c000,0x0,0x0,0x1f00000,
+ 0x0,0x780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0xfe0100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0xf0007fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,
+ 0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x1f,0x800000f0,0x1f80,0x0,0x0,0x0,0x0,
+ 0x78000000,0xf0000070,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3ffe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,
+ 0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0xf00,0x1e0000,0x3c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x7c000,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x7fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4003,0xe0000000,0x0,0x1f000,0x0,0x0,
+ 0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x1,0xf0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0x70000001,0xf00000e0,
+ 0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,
+ 0x0,0x0,0x3c,0xff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,0x0,0x0,0x1,0xc00003ff,
+ 0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00,0x1e0000,
+ 0x7c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0xf0,0x78000,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf8,0x0,
+ 0x0,0x0,0x0,0x1fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,
+ 0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780f,0xc0000000,0x0,0x3e000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
+ 0x0,0x0,0x0,0x0,0x3,0xe0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0xf0000103,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x21e00000,0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e00,0x1e0000,0xf8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,
+ 0xf8,0xf8000,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x1fe00,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x7fff,0xc0000000,0x0,0x3ffe000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0xe0000000,0x7,0xfc0000f0,
+ 0x3fe00,0x0,0x0,0x0,0x0,0x600001ff,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,
+ 0x3fe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x7fe00,0x1e0000,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff,0x80000000,0x0,0x3ffc000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
+ 0x0,0x0,0x0,0x0,0x7f,0xc0000000,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x0,0x0,0x1ff,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3fc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fc00,0x1e0000,0x1ff0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffe,0x0,0x0,0x3ff8000,0x0,0x0,0x0,
+ 0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0x80000000,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x80000000,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f800,0x1e0000,0x1fe0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8,0x0,0x0,0x3fe0000,
+ 0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7e,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x1e0000,0x1f80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+ // Definition of a 40x38 'danger' color logo.
+ const unsigned char logo40x38[4576] = {
+ 177,200,200,200,3,123,123,0,36,200,200,200,1,123,123,0,2,255,255,0,1,189,189,189,1,0,0,0,34,200,200,200,
+ 1,123,123,0,4,255,255,0,1,189,189,189,1,0,0,0,1,123,123,123,32,200,200,200,1,123,123,0,5,255,255,0,1,0,0,
+ 0,2,123,123,123,30,200,200,200,1,123,123,0,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,29,200,200,200,
+ 1,123,123,0,7,255,255,0,1,0,0,0,2,123,123,123,28,200,200,200,1,123,123,0,8,255,255,0,1,189,189,189,1,0,0,0,
+ 2,123,123,123,27,200,200,200,1,123,123,0,9,255,255,0,1,0,0,0,2,123,123,123,26,200,200,200,1,123,123,0,10,255,
+ 255,0,1,189,189,189,1,0,0,0,2,123,123,123,25,200,200,200,1,123,123,0,3,255,255,0,1,189,189,189,3,0,0,0,1,189,
+ 189,189,3,255,255,0,1,0,0,0,2,123,123,123,24,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,3,255,255,0,1,189,
+ 189,189,1,0,0,0,2,123,123,123,23,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,4,255,255,0,1,0,0,0,2,123,123,123,
+ 22,200,200,200,1,123,123,0,5,255,255,0,5,0,0,0,4,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,21,200,200,200,
+ 1,123,123,0,5,255,255,0,5,0,0,0,5,255,255,0,1,0,0,0,2,123,123,123,20,200,200,200,1,123,123,0,6,255,255,0,5,0,0,
+ 0,5,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,19,200,200,200,1,123,123,0,6,255,255,0,1,123,123,0,3,0,0,0,1,
+ 123,123,0,6,255,255,0,1,0,0,0,2,123,123,123,18,200,200,200,1,123,123,0,7,255,255,0,1,189,189,189,3,0,0,0,1,189,
+ 189,189,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,17,200,200,200,1,123,123,0,8,255,255,0,3,0,0,0,8,255,255,
+ 0,1,0,0,0,2,123,123,123,16,200,200,200,1,123,123,0,9,255,255,0,1,123,123,0,1,0,0,0,1,123,123,0,8,255,255,0,1,189,
+ 189,189,1,0,0,0,2,123,123,123,15,200,200,200,1,123,123,0,9,255,255,0,1,189,189,189,1,0,0,0,1,189,189,189,9,255,255,
+ 0,1,0,0,0,2,123,123,123,14,200,200,200,1,123,123,0,11,255,255,0,1,0,0,0,10,255,255,0,1,189,189,189,1,0,0,0,2,123,
+ 123,123,13,200,200,200,1,123,123,0,23,255,255,0,1,0,0,0,2,123,123,123,12,200,200,200,1,123,123,0,11,255,255,0,1,189,
+ 189,189,2,0,0,0,1,189,189,189,9,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,11,200,200,200,1,123,123,0,11,255,255,
+ 0,4,0,0,0,10,255,255,0,1,0,0,0,2,123,123,123,10,200,200,200,1,123,123,0,12,255,255,0,4,0,0,0,10,255,255,0,1,189,189,
+ 189,1,0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,12,255,255,0,1,189,189,189,2,0,0,0,1,189,189,189,11,255,255,0,1,
+ 0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,27,255,255,0,1,0,0,0,3,123,123,123,8,200,200,200,1,123,123,0,26,255,
+ 255,0,1,189,189,189,1,0,0,0,3,123,123,123,9,200,200,200,1,123,123,0,24,255,255,0,1,189,189,189,1,0,0,0,4,123,123,
+ 123,10,200,200,200,1,123,123,0,24,0,0,0,5,123,123,123,12,200,200,200,27,123,123,123,14,200,200,200,25,123,123,123,86,
+ 200,200,200,91,49,124,118,124,71,32,124,95,49,56,114,52,82,121,0};
+
+ //! Display a warning message.
+ /**
+ \param format is a C-string describing the format of the message, as in <tt>std::printf()</tt>.
+ **/
+ inline void warn(const char *format, ...) {
+ if (cimg::exception_mode()>=1) {
+ char message[8192];
+ cimg_std::va_list ap;
+ va_start(ap,format);
+ cimg_std::vsprintf(message,format,ap);
+ va_end(ap);
+#ifdef cimg_strict_warnings
+ throw CImgWarningException(message);
+#else
+ cimg_std::fprintf(cimg_stdout,"\n%s# CImg Warning%s :\n%s\n",cimg::t_red,cimg::t_normal,message);
+#endif
+ }
+ }
+
+ // Execute an external system command.
+ /**
+ \note This function is similar to <tt>std::system()</tt>
+ and is here because using the <tt>std::</tt> version on
+ Windows may open undesired consoles.
+ **/
+ inline int system(const char *const command, const char *const module_name=0) {
+#if cimg_OS==2
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si;
+ cimg_std::memset(&pi,0,sizeof(PROCESS_INFORMATION));
+ cimg_std::memset(&si,0,sizeof(STARTUPINFO));
+ GetStartupInfo(&si);
+ si.cb = sizeof(si);
+ si.wShowWindow = SW_HIDE;
+ si.dwFlags |= SW_HIDE;
+ const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi);
+ if (res) {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+ return 0;
+ } else
+#endif
+ return cimg_std::system(command);
+ return module_name?0:1;
+ }
+
+ //! Return a reference to a temporary variable of type T.
+ template<typename T>
+ inline T& temporary(const T&) {
+ static T temp;
+ return temp;
+ }
+
+ //! Exchange values of variables \p a and \p b.
+ template<typename T>
+ inline void swap(T& a, T& b) { T t = a; a = b; b = t; }
+
+ //! Exchange values of variables (\p a1,\p a2) and (\p b1,\p b2).
+ template<typename T1, typename T2>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) {
+ cimg::swap(a1,b1); cimg::swap(a2,b2);
+ }
+
+ //! Exchange values of variables (\p a1,\p a2,\p a3) and (\p b1,\p b2,\p b3).
+ template<typename T1, typename T2, typename T3>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) {
+ cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3);
+ }
+
+ //! Exchange values of variables (\p a1,\p a2,...,\p a4) and (\p b1,\p b2,...,\p b4).
+ template<typename T1, typename T2, typename T3, typename T4>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) {
+ cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4);
+ }
+
+ //! Exchange values of variables (\p a1,\p a2,...,\p a5) and (\p b1,\p b2,...,\p b5).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5);
+ }
+
+ //! Exchange values of variables (\p a1,\p a2,...,\p a6) and (\p b1,\p b2,...,\p b6).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6);
+ }
+
+ //! Exchange values of variables (\p a1,\p a2,...,\p a7) and (\p b1,\p b2,...,\p b7).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
+ T7& a7, T7& b7) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7);
+ }
+
+ //! Exchange values of variables (\p a1,\p a2,...,\p a8) and (\p b1,\p b2,...,\p b8).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
+ T7& a7, T7& b7, T8& a8, T8& b8) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8);
+ }
+
+ //! Return the current endianness of the CPU.
+ /**
+ \return \c false for "Little Endian", \c true for "Big Endian".
+ **/
+ inline bool endianness() {
+ const int x = 1;
+ return ((unsigned char*)&x)[0]?false:true;
+ }
+
+ //! Invert endianness of a memory buffer.
+ template<typename T>
+ inline void invert_endianness(T* const buffer, const unsigned int size) {
+ if (size) switch (sizeof(T)) {
+ case 1 : break;
+ case 2 : { for (unsigned short *ptr = (unsigned short*)buffer+size; ptr>(unsigned short*)buffer; ) {
+ const unsigned short val = *(--ptr);
+ *ptr = (unsigned short)((val>>8)|((val<<8)));
+ }} break;
+ case 4 : { for (unsigned int *ptr = (unsigned int*)buffer+size; ptr>(unsigned int*)buffer; ) {
+ const unsigned int val = *(--ptr);
+ *ptr = (val>>24)|((val>>8)&0xff00)|((val<<8)&0xff0000)|(val<<24);
+ }} break;
+ default : { for (T* ptr = buffer+size; ptr>buffer; ) {
+ unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T);
+ for (int i=0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe));
+ }}
+ }
+ }
+
+ //! Invert endianness of a single variable.
+ template<typename T>
+ inline T& invert_endianness(T& a) {
+ invert_endianness(&a,1);
+ return a;
+ }
+
+ //! Get the value of a system timer with a millisecond precision.
+ inline unsigned long time() {
+#if cimg_OS==1
+ struct timeval st_time;
+ gettimeofday(&st_time,0);
+ return (unsigned long)(st_time.tv_usec/1000 + st_time.tv_sec*1000);
+#elif cimg_OS==2
+ static SYSTEMTIME st_time;
+ GetSystemTime(&st_time);
+ return (unsigned long)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour)));
+#else
+ return 0;
+#endif
+ }
+
+ //! Sleep for a certain numbers of milliseconds.
+ /**
+ This function frees the CPU ressources during the sleeping time.
+ It may be used to temporize your program properly, without wasting CPU time.
+ **/
+ inline void sleep(const unsigned int milliseconds) {
+#if cimg_OS==1
+ struct timespec tv;
+ tv.tv_sec = milliseconds/1000;
+ tv.tv_nsec = (milliseconds%1000)*1000000;
+ nanosleep(&tv,0);
+#elif cimg_OS==2
+ Sleep(milliseconds);
+#endif
+ }
+
+ inline unsigned int _sleep(const unsigned int milliseconds, unsigned long& timer) {
+ if (!timer) timer = cimg::time();
+ const unsigned long current_time = cimg::time();
+ if (current_time>=timer+milliseconds) { timer = current_time; return 0; }
+ const unsigned long time_diff = timer + milliseconds - current_time;
+ timer = current_time + time_diff;
+ cimg::sleep(time_diff);
+ return (unsigned int)time_diff;
+ }
+
+ //! Wait for a certain number of milliseconds since the last call.
+ /**
+ This function is equivalent to sleep() but the waiting time is computed with regard to the last call
+ of wait(). It may be used to temporize your program properly.
+ **/
+ inline unsigned int wait(const unsigned int milliseconds) {
+ static unsigned long timer = 0;
+ if (!timer) timer = cimg::time();
+ return _sleep(milliseconds,timer);
+ }
+
+ // Use a specific srand initialization to avoid multi-threads to have to the
+ // same series of random numbers (executed only once for a single program).
+ inline void srand() {
+ static bool first_time = true;
+ if (first_time) {
+ cimg_std::srand(cimg::time());
+ unsigned char *const rand_ptr = new unsigned char[1+cimg_std::rand()%2048];
+ cimg_std::srand((unsigned int)cimg_std::rand() + *(unsigned int*)(void*)rand_ptr);
+ delete[] rand_ptr;
+ first_time = false;
+ }
+ }
+
+ //! Return a left bitwise-rotated number.
+ template<typename T>
+ inline const T rol(const T a, const unsigned int n=1) {
+ return n?(T)((a<<n)|(a>>((sizeof(T)<<3)-n))):a;
+ }
+
+ //! Return a right bitwise-rotated number.
+ template<typename T>
+ inline const T ror(const T a, const unsigned int n=1) {
+ return n?(T)((a>>n)|(a<<((sizeof(T)<<3)-n))):a;
+ }
+
+ //! Return the absolute value of a number.
+ /**
+ \note This function is different from <tt>std::abs()</tt> or <tt>std::fabs()</tt>
+ because it is able to consider a variable of any type, without cast needed.
+ **/
+ template<typename T>
+ inline T abs(const T a) {
+ return a>=0?a:-a;
+ }
+ inline bool abs(const bool a) {
+ return a;
+ }
+ inline unsigned char abs(const unsigned char a) {
+ return a;
+ }
+ inline unsigned short abs(const unsigned short a) {
+ return a;
+ }
+ inline unsigned int abs(const unsigned int a) {
+ return a;
+ }
+ inline unsigned long abs(const unsigned long a) {
+ return a;
+ }
+ inline double abs(const double a) {
+ return cimg_std::fabs(a);
+ }
+ inline float abs(const float a) {
+ return (float)cimg_std::fabs((double)a);
+ }
+ inline int abs(const int a) {
+ return cimg_std::abs(a);
+ }
+
+ //! Return the square of a number.
+ template<typename T>
+ inline T sqr(const T val) {
+ return val*val;
+ }
+
+ //! Return 1 + log_10(x).
+ inline int xln(const int x) {
+ return x>0?(int)(1+cimg_std::log10((double)x)):1;
+ }
+
+ //! Return the minimum value between two numbers.
+ template<typename t1, typename t2>
+ inline typename cimg::superset<t1,t2>::type min(const t1& a, const t2& b) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return (t1t2)(a<=b?a:b);
+ }
+
+ //! Return the minimum value between three numbers.
+ template<typename t1, typename t2, typename t3>
+ inline typename cimg::superset2<t1,t2,t3>::type min(const t1& a, const t2& b, const t3& c) {
+ typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
+ return (t1t2t3)cimg::min(cimg::min(a,b),c);
+ }
+
+ //! Return the minimum value between four numbers.
+ template<typename t1, typename t2, typename t3, typename t4>
+ inline typename cimg::superset3<t1,t2,t3,t4>::type min(const t1& a, const t2& b, const t3& c, const t4& d) {
+ typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
+ return (t1t2t3t4)cimg::min(cimg::min(a,b,c),d);
+ }
+
+ //! Return the maximum value between two numbers.
+ template<typename t1, typename t2>
+ inline typename cimg::superset<t1,t2>::type max(const t1& a, const t2& b) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return (t1t2)(a>=b?a:b);
+ }
+
+ //! Return the maximum value between three numbers.
+ template<typename t1, typename t2, typename t3>
+ inline typename cimg::superset2<t1,t2,t3>::type max(const t1& a, const t2& b, const t3& c) {
+ typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
+ return (t1t2t3)cimg::max(cimg::max(a,b),c);
+ }
+
+ //! Return the maximum value between four numbers.
+ template<typename t1, typename t2, typename t3, typename t4>
+ inline typename cimg::superset3<t1,t2,t3,t4>::type max(const t1& a, const t2& b, const t3& c, const t4& d) {
+ typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
+ return (t1t2t3t4)cimg::max(cimg::max(a,b,c),d);
+ }
+
+ //! Return the sign of a number.
+ template<typename T>
+ inline T sign(const T x) {
+ return (x<0)?(T)(-1):(x==0?(T)0:(T)1);
+ }
+
+ //! Return the nearest power of 2 higher than a given number.
+ template<typename T>
+ inline unsigned long nearest_pow2(const T x) {
+ unsigned long i = 1;
+ while (x>i) i<<=1;
+ return i;
+ }
+
+ //! Return the modulo of a number.
+ /**
+ \note This modulo function accepts negative and floating-points modulo numbers, as well as
+ variable of any type.
+ **/
+ template<typename T>
+ inline T mod(const T& x, const T& m) {
+ const double dx = (double)x, dm = (double)m;
+ if (x<0) { return (T)(dm+dx+dm*cimg_std::floor(-dx/dm)); }
+ return (T)(dx-dm*cimg_std::floor(dx/dm));
+ }
+ inline int mod(const bool x, const bool m) {
+ return m?(x?1:0):0;
+ }
+ inline int mod(const char x, const char m) {
+ return x>=0?x%m:(x%m?m+x%m:0);
+ }
+ inline int mod(const short x, const short m) {
+ return x>=0?x%m:(x%m?m+x%m:0);
+ }
+ inline int mod(const int x, const int m) {
+ return x>=0?x%m:(x%m?m+x%m:0);
+ }
+ inline int mod(const long x, const long m) {
+ return x>=0?x%m:(x%m?m+x%m:0);
+ }
+ inline int mod(const unsigned char x, const unsigned char m) {
+ return x%m;
+ }
+ inline int mod(const unsigned short x, const unsigned short m) {
+ return x%m;
+ }
+ inline int mod(const unsigned int x, const unsigned int m) {
+ return x%m;
+ }
+ inline int mod(const unsigned long x, const unsigned long m) {
+ return x%m;
+ }
+
+ //! Return the minmod of two numbers.
+ /**
+ <i>minmod(\p a,\p b)</i> is defined to be :
+ - <i>minmod(\p a,\p b) = min(\p a,\p b)</i>, if \p a and \p b have the same sign.
+ - <i>minmod(\p a,\p b) = 0</i>, if \p a and \p b have different signs.
+ **/
+ template<typename T>
+ inline T minmod(const T a, const T b) {
+ return a*b<=0?0:(a>0?(a<b?a:b):(a<b?b:a));
+ }
+
+ //! Return a random variable between [0,1] with respect to an uniform distribution.
+ inline double rand() {
+ static bool first_time = true;
+ if (first_time) { cimg::srand(); first_time = false; }
+ return (double)cimg_std::rand()/RAND_MAX;
+ }
+
+ //! Return a random variable between [-1,1] with respect to an uniform distribution.
+ inline double crand() {
+ return 1-2*cimg::rand();
+ }
+
+ //! Return a random variable following a gaussian distribution and a standard deviation of 1.
+ inline double grand() {
+ double x1, w;
+ do {
+ const double x2 = 2*cimg::rand() - 1.0;
+ x1 = 2*cimg::rand()-1.0;
+ w = x1*x1 + x2*x2;
+ } while (w<=0 || w>=1.0);
+ return x1*cimg_std::sqrt((-2*cimg_std::log(w))/w);
+ }
+
+ //! Return a random variable following a Poisson distribution of parameter z.
+ inline unsigned int prand(const double z) {
+ if (z<=1.0e-10) return 0;
+ if (z>100.0) return (unsigned int)((std::sqrt(z) * cimg::grand()) + z);
+ unsigned int k = 0;
+ const double y = std::exp(-z);
+ for (double s = 1.0; s>=y; ++k) s*=cimg::rand();
+ return k-1;
+ }
+
+ //! Return a rounded number.
+ /**
+ \param x is the number to be rounded.
+ \param y is the rounding precision.
+ \param rounding_type defines the type of rounding (0=nearest, -1=backward, 1=forward).
+ **/
+ inline double round(const double x, const double y, const int rounding_type=0) {
+ if (y<=0) return x;
+ const double delta = cimg::mod(x,y);
+ if (delta==0.0) return x;
+ const double
+ backward = x - delta,
+ forward = backward + y;
+ return rounding_type<0?backward:(rounding_type>0?forward:(2*delta<y?backward:forward));
+ }
+
+ inline double _pythagore(double a, double b) {
+ const double absa = cimg::abs(a), absb = cimg::abs(b);
+ if (absa>absb) { const double tmp = absb/absa; return absa*cimg_std::sqrt(1.0+tmp*tmp); }
+ else { const double tmp = absa/absb; return (absb==0?0:absb*cimg_std::sqrt(1.0+tmp*tmp)); }
+ }
+
+ //! Remove the 'case' of an ASCII character.
+ inline char uncase(const char x) {
+ return (char)((x<'A'||x>'Z')?x:x-'A'+'a');
+ }
+
+ //! Remove the 'case' of a C string.
+ /**
+ Acts in-place.
+ **/
+ inline void uncase(char *const string) {
+ if (string) for (char *ptr = string; *ptr; ++ptr) *ptr = uncase(*ptr);
+ }
+
+ //! Read a float number from a C-string.
+ /**
+ \note This function is quite similar to <tt>std::atof()</tt>,
+ but that it allows the retrieval of fractions as in "1/2".
+ **/
+ inline float atof(const char *const str) {
+ float x = 0,y = 1;
+ if (!str) return 0; else { cimg_std::sscanf(str,"%g/%g",&x,&y); return x/y; }
+ }
+
+ //! Compute the length of a C-string.
+ /**
+ \note This function is similar to <tt>std::strlen()</tt>
+ and is here because some old compilers do not
+ define the <tt>std::</tt> version.
+ **/
+ inline int strlen(const char *const s) {
+ if (!s) return -1;
+ int k = 0;
+ for (const char *ns = s; *ns; ++ns) ++k;
+ return k;
+ }
+
+ //! Compare the first \p n characters of two C-strings.
+ /**
+ \note This function is similar to <tt>std::strncmp()</tt>
+ and is here because some old compilers do not
+ define the <tt>std::</tt> version.
+ **/
+ inline int strncmp(const char *const s1, const char *const s2, const int l) {
+ if (!s1) return s2?-1:0;
+ const char *ns1 = s1, *ns2 = s2;
+ int k, diff = 0; for (k = 0; k<l && !(diff = *ns1-*ns2); ++k) { ++ns1; ++ns2; }
+ return k!=l?diff:0;
+ }
+
+ //! Compare the first \p n characters of two C-strings, ignoring the case.
+ /**
+ \note This function is similar to <tt>std::strncasecmp()</tt>
+ and is here because some old compilers do not
+ define the <tt>std::</tt> version.
+ **/
+ inline int strncasecmp(const char *const s1, const char *const s2, const int l) {
+ if (!s1) return s2?-1:0;
+ const char *ns1 = s1, *ns2 = s2;
+ int k, diff = 0; for (k = 0; k<l && !(diff = uncase(*ns1)-uncase(*ns2)); ++k) { ++ns1; ++ns2; }
+ return k!=l?diff:0;
+ }
+
+ //! Compare two C-strings.
+ /**
+ \note This function is similar to <tt>std::strcmp()</tt>
+ and is here because some old compilers do not
+ define the <tt>std::</tt> version.
+ **/
+ inline int strcmp(const char *const s1, const char *const s2) {
+ const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
+ return cimg::strncmp(s1,s2,1+(l1<l2?l1:l2));
+ }
+
+ //! Compare two C-strings, ignoring the case.
+ /**
+ \note This function is similar to <tt>std::strcasecmp()</tt>
+ and is here because some old compilers do not
+ define the <tt>std::</tt> version.
+ **/
+ inline int strcasecmp(const char *const s1, const char *const s2) {
+ const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
+ return cimg::strncasecmp(s1,s2,1+(l1<l2?l1:l2));
+ }
+
+ //! Find a character in a C-string.
+ inline int strfind(const char *const s, const char c) {
+ if (!s) return -1;
+ int l; for (l = cimg::strlen(s); l>=0 && s[l]!=c; --l) {}
+ return l;
+ }
+
+ //! Remove useless delimiters on the borders of a C-string
+ inline bool strpare(char *const s, const char delimiter=' ', const bool symmetric=false) {
+ if (!s) return false;
+ const int l = cimg::strlen(s);
+ int p, q;
+ if (symmetric) for (p = 0, q = l-1; p<q && s[p]==delimiter && s[q]==delimiter; ++p) --q;
+ else {
+ for (p = 0; p<l && s[p]==delimiter; ) ++p;
+ for (q = l-1; q>p && s[q]==delimiter; ) --q;
+ }
+ const int n = q - p + 1;
+ if (n!=l) { cimg_std::memmove(s,s+p,n); s[n] = '\0'; return true; }
+ return false;
+ }
+
+ //! Remove useless spaces and symmetric delimiters ', " and ` from a C-string.
+ inline void strclean(char *const s) {
+ if (!s) return;
+ strpare(s,' ',false);
+ for (bool need_iter = true; need_iter; ) {
+ need_iter = false;
+ need_iter |= strpare(s,'\'',true);
+ need_iter |= strpare(s,'\"',true);
+ need_iter |= strpare(s,'`',true);
+ }
+ }
+
+ //! Replace explicit escape sequences '\x' in C-strings (where x in [ntvbrfa?'"0]).
+ inline void strescape(char *const s) {
+#define cimg_strescape(ci,co) case ci: *nd = co; break;
+ char *ns, *nd;
+ for (ns = nd = s; *ns; ++ns, ++nd)
+ if (*ns=='\\') switch (*(++ns)) {
+ cimg_strescape('n','\n');
+ cimg_strescape('t','\t');
+ cimg_strescape('v','\v');
+ cimg_strescape('b','\b');
+ cimg_strescape('r','\r');
+ cimg_strescape('f','\f');
+ cimg_strescape('a','\a');
+ cimg_strescape('\\','\\');
+ cimg_strescape('\?','\?');
+ cimg_strescape('\'','\'');
+ cimg_strescape('\"','\"');
+ cimg_strescape('\0','\0');
+ }
+ else *nd = *ns;
+ *nd = 0;
+ }
+
+ //! Compute the basename of a filename.
+ inline const char* basename(const char *const s) {
+ return (cimg_OS!=2)?(s?s+1+cimg::strfind(s,'/'):0):(s?s+1+cimg::strfind(s,'\\'):0);
+ }
+
+ // Generate a random filename.
+ inline const char* filenamerand() {
+ static char id[9] = { 0,0,0,0,0,0,0,0,0 };
+ cimg::srand();
+ for (unsigned int k=0; k<8; ++k) {
+ const int v = (int)cimg_std::rand()%3;
+ id[k] = (char)(v==0?('0'+(cimg_std::rand()%10)):(v==1?('a'+(cimg_std::rand()%26)):('A'+(cimg_std::rand()%26))));
+ }
+ return id;
+ }
+
+ // Convert filename into a Windows-style filename.
+ inline void winformat_string(char *const s) {
+ if (s && s[0]) {
+#if cimg_OS==2
+ char *const ns = new char[MAX_PATH];
+ if (GetShortPathNameA(s,ns,MAX_PATH)) cimg_std::strcpy(s,ns);
+#endif
+ }
+ }
+
+ //! Return or set path to store temporary files.
+ inline const char* temporary_path(const char *const user_path=0, const bool reinit_path=false) {
+#define _cimg_test_temporary_path(p) \
+ if (!path_found) { \
+ cimg_std::sprintf(st_path,"%s",p); \
+ cimg_std::sprintf(tmp,"%s%s%s",st_path,cimg_OS==2?"\\":"/",filetmp); \
+ if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; } \
+ }
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ char tmp[1024], filetmp[512];
+ cimg_std::FILE *file = 0;
+ cimg_std::sprintf(filetmp,"%s.tmp",cimg::filenamerand());
+ char *tmpPath = getenv("TMP");
+ if (!tmpPath) { tmpPath = getenv("TEMP"); winformat_string(tmpPath); }
+ if (tmpPath) _cimg_test_temporary_path(tmpPath);
+#if cimg_OS==2
+ _cimg_test_temporary_path("C:\\WINNT\\Temp");
+ _cimg_test_temporary_path("C:\\WINDOWS\\Temp");
+ _cimg_test_temporary_path("C:\\Temp");
+ _cimg_test_temporary_path("C:");
+ _cimg_test_temporary_path("D:\\WINNT\\Temp");
+ _cimg_test_temporary_path("D:\\WINDOWS\\Temp");
+ _cimg_test_temporary_path("D:\\Temp");
+ _cimg_test_temporary_path("D:");
+#else
+ _cimg_test_temporary_path("/tmp");
+ _cimg_test_temporary_path("/var/tmp");
+#endif
+ if (!path_found) {
+ st_path[0]='\0';
+ cimg_std::strcpy(tmp,filetmp);
+ if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; }
+ }
+ if (!path_found)
+ throw CImgIOException("cimg::temporary_path() : Unable to find a temporary path accessible for writing\n"
+ "you have to set the macro 'cimg_temporary_path' to a valid path where you have writing access :\n"
+ "#define cimg_temporary_path \"path\" (before including 'CImg.h')");
+ }
+ return st_path;
+ }
+
+ // Return or set path to the "Program files/" directory (windows only).
+#if cimg_OS==2
+ inline const char* programfiles_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[MAX_PATH];
+ cimg_std::memset(st_path,0,MAX_PATH);
+ // Note : in the following line, 0x26 = CSIDL_PROGRAM_FILES (not defined on every compiler).
+#if !defined(__INTEL_COMPILER)
+ if (!SHGetSpecialFolderPathA(0,st_path,0x0026,false)) {
+ const char *pfPath = getenv("PROGRAMFILES");
+ if (pfPath) cimg_std::strncpy(st_path,pfPath,MAX_PATH-1);
+ else cimg_std::strcpy(st_path,"C:\\PROGRA~1");
+ }
+#else
+ cimg_std::strcpy(st_path,"C:\\PROGRA~1");
+#endif
+ }
+ return st_path;
+ }
+#endif
+
+ //! Return or set path to the ImageMagick's \c convert tool.
+ inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ cimg_std::FILE *file = 0;
+#if cimg_OS==2
+ const char *pf_path = programfiles_path();
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\convert.exe");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ if (!path_found) cimg_std::strcpy(st_path,"convert.exe");
+#else
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"./convert");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"convert");
+#endif
+ winformat_string(st_path);
+ }
+ return st_path;
+ }
+
+ //! Return path of the GraphicsMagick's \c gm tool.
+ inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ cimg_std::FILE *file = 0;
+#if cimg_OS==2
+ const char* pf_path = programfiles_path();
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\gm.exe");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=10 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=9; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ { for (int k=32; k>=0 && !path_found; --k) {
+ cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }}
+ if (!path_found) cimg_std::strcpy(st_path,"gm.exe");
+#else
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"./gm");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"gm");
+#endif
+ winformat_string(st_path);
+ }
+ return st_path;
+ }
+
+ //! Return or set path of the \c XMedcon tool.
+ inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ cimg_std::FILE *file = 0;
+#if cimg_OS==2
+ const char* pf_path = programfiles_path();
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\medcon.bat");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\medcon.exe");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.bat",pf_path);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.exe",pf_path);
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"medcon.bat");
+#else
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"./medcon");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"medcon");
+#endif
+ winformat_string(st_path);
+ }
+ return st_path;
+ }
+
+ //! Return or set path to the 'ffmpeg' command.
+ inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ cimg_std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\ffmpeg.exe");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"ffmpeg.exe");
+#else
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"./ffmpeg");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"ffmpeg");
+#endif
+ winformat_string(st_path);
+ }
+ return st_path;
+ }
+
+ //! Return or set path to the 'gzip' command.
+ inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ cimg_std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\gzip.exe");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"gzip.exe");
+#else
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"./gzip");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"gzip");
+#endif
+ winformat_string(st_path);
+ }
+ return st_path;
+ }
+
+ //! Return or set path to the 'gunzip' command.
+ inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ cimg_std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\gunzip.exe");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"gunzip.exe");
+#else
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"./gunzip");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"gunzip");
+#endif
+ winformat_string(st_path);
+ }
+ return st_path;
+ }
+
+ //! Return or set path to the 'dcraw' command.
+ inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false) {
+ static char *st_path = 0;
+ if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+ if (user_path) {
+ if (!st_path) st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ cimg_std::strncpy(st_path,user_path,1023);
+ } else if (!st_path) {
+ st_path = new char[1024];
+ cimg_std::memset(st_path,0,1024);
+ bool path_found = false;
+ cimg_std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ cimg_std::sprintf(st_path,".\\dcraw.exe");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"dcraw.exe");
+#else
+ if (!path_found) {
+ cimg_std::sprintf(st_path,"./dcraw");
+ if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
+ }
+ if (!path_found) cimg_std::strcpy(st_path,"dcraw");
+#endif
+ winformat_string(st_path);
+ }
+ return st_path;
+ }
+
+ //! Split a filename into two strings 'body' and 'extension'.
+ inline const char *split_filename(const char *const filename, char *const body=0) {
+ if (!filename) { if (body) body[0]='\0'; return 0; }
+ int l = cimg::strfind(filename,'.');
+ if (l>=0) { if (body) { cimg_std::strncpy(body,filename,l); body[l]='\0'; }}
+ else { if (body) cimg_std::strcpy(body,filename); l = (int)cimg::strlen(filename)-1; }
+ return filename+l+1;
+ }
+
+ //! Create a numbered version of a filename.
+ inline char* number_filename(const char *const filename, const int number, const unsigned int n, char *const string) {
+ if (!filename) { if (string) string[0]='\0'; return 0; }
+ char format[1024],body[1024];
+ const char *ext = cimg::split_filename(filename,body);
+ if (n>0) cimg_std::sprintf(format,"%s_%%.%ud.%s",body,n,ext);
+ else cimg_std::sprintf(format,"%s_%%d.%s",body,ext);
+ cimg_std::sprintf(string,format,number);
+ return string;
+ }
+
+ //! Open a file, and check for possible errors.
+ inline cimg_std::FILE *fopen(const char *const path, const char *const mode) {
+ if(!path || !mode)
+ throw CImgArgumentException("cimg::fopen() : File '%s', cannot open with mode '%s'.",
+ path?path:"(null)",mode?mode:"(null)");
+ if (path[0]=='-') return (mode[0]=='r')?stdin:stdout;
+ cimg_std::FILE *dest = cimg_std::fopen(path,mode);
+ if (!dest)
+ throw CImgIOException("cimg::fopen() : File '%s', cannot open file %s",
+ path,mode[0]=='r'?"for reading.":(mode[0]=='w'?"for writing.":"."),path);
+ return dest;
+ }
+
+ //! Close a file, and check for possible errors.
+ inline int fclose(cimg_std::FILE *file) {
+ if (!file) warn("cimg::fclose() : Can't close (null) file");
+ if (!file || file==stdin || file==stdout) return 0;
+ const int errn = cimg_std::fclose(file);
+ if (errn!=0) warn("cimg::fclose() : Error %d during file closing",errn);
+ return errn;
+ }
+
+ //! Try to guess the image format of a filename, using its magick numbers.
+ inline const char *file_type(cimg_std::FILE *const file, const char *const filename) {
+ static const char
+ *const _pnm = "pnm",
+ *const _bmp = "bmp",
+ *const _gif = "gif",
+ *const _jpeg = "jpeg",
+ *const _off = "off",
+ *const _pan = "pan",
+ *const _png = "png",
+ *const _tiff = "tiff";
+ if (!filename && !file) throw CImgArgumentException("cimg::file_type() : Cannot load (null) filename.");
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ const char *ftype = 0, *head;
+ char header[2048], item[1024];
+ const unsigned char *const uheader = (unsigned char*)header;
+ int err;
+ const unsigned int siz = (unsigned int)cimg_std::fread(header,2048,1,nfile); // Read first 2048 bytes.
+ if (!file) cimg::fclose(nfile);
+ if (!ftype) { // Check for BMP format.
+ if (header[0]=='B' && header[1]=='M') ftype = _bmp;
+ }
+ if (!ftype) { // Check for GIF format.
+ if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' &&
+ (header[4]=='7' || header[4]=='9')) ftype = _gif;
+ }
+ if (!ftype) { // Check for JPEG format.
+ if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) ftype = _jpeg;
+ }
+ if (!ftype) { // Check for OFF format.
+ if (header[0]=='O' && header[1]=='F' && header[2]=='F' && header[3]=='\n') ftype = _off;
+ }
+ if (!ftype) { // Check for PAN format.
+ if (header[0]=='P' && header[1]=='A' && header[2]=='N' && header[3]=='D' && header[4]=='O' &&
+ header[5]=='R' && header[6]=='E') ftype = _pan;
+ }
+ if (!ftype) { // Check for PNG format.
+ if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 &&
+ uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) ftype = _png;
+ }
+ if (!ftype) { // Check for PNM format.
+ head = header;
+ while (head<header+siz && (err=cimg_std::sscanf(head,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err))
+ head+=1+(err?cimg::strlen(item):0);
+ if (cimg_std::sscanf(item," P%d",&err)==1) ftype = _pnm;
+ }
+ if (!ftype) { // Check for TIFF format.
+ if ((uheader[0]==0x49 && uheader[1]==0x49) || (uheader[0]==0x4D && uheader[1]==0x4D)) ftype = _tiff;
+ }
+ return ftype;
+ }
+
+ //! Read file data, and check for possible errors.
+ template<typename T>
+ inline int fread(T *const ptr, const unsigned int nmemb, cimg_std::FILE *stream) {
+ if (!ptr || nmemb<=0 || !stream)
+ throw CImgArgumentException("cimg::fread() : Can't read %u x %u bytes of file pointer '%p' in buffer '%p'",
+ nmemb,sizeof(T),stream,ptr);
+ const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+ unsigned int toread = nmemb, alread = 0, ltoread = 0, lalread = 0;
+ do {
+ ltoread = (toread*sizeof(T))<wlimitT?toread:wlimit;
+ lalread = (unsigned int)cimg_std::fread((void*)(ptr+alread),sizeof(T),ltoread,stream);
+ alread+=lalread;
+ toread-=lalread;
+ } while (ltoread==lalread && toread>0);
+ if (toread>0) warn("cimg::fread() : File reading problems, only %u/%u elements read",alread,nmemb);
+ return alread;
+ }
+
+ //! Write data to a file, and check for possible errors.
+ template<typename T>
+ inline int fwrite(const T *ptr, const unsigned int nmemb, cimg_std::FILE *stream) {
+ if (!ptr || !stream)
+ throw CImgArgumentException("cimg::fwrite() : Can't write %u x %u bytes of file pointer '%p' from buffer '%p'",
+ nmemb,sizeof(T),stream,ptr);
+ if (nmemb<=0) return 0;
+ const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+ unsigned int towrite = nmemb, alwrite = 0, ltowrite = 0, lalwrite = 0;
+ do {
+ ltowrite = (towrite*sizeof(T))<wlimitT?towrite:wlimit;
+ lalwrite = (unsigned int)cimg_std::fwrite((void*)(ptr+alwrite),sizeof(T),ltowrite,stream);
+ alwrite+=lalwrite;
+ towrite-=lalwrite;
+ } while (ltowrite==lalwrite && towrite>0);
+ if (towrite>0) warn("cimg::fwrite() : File writing problems, only %u/%u elements written",alwrite,nmemb);
+ return alwrite;
+ }
+
+ inline const char* option(const char *const name, const int argc, const char *const *const argv,
+ const char *defaut, const char *const usage=0) {
+ static bool first = true, visu = false;
+ const char *res = 0;
+ if (first) {
+ first=false;
+ visu = (cimg::option("-h",argc,argv,(char*)0)!=0);
+ visu |= (cimg::option("-help",argc,argv,(char*)0)!=0);
+ visu |= (cimg::option("--help",argc,argv,(char*)0)!=0);
+ }
+ if (!name && visu) {
+ if (usage) {
+ cimg_std::fprintf(cimg_stdout,"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal);
+ cimg_std::fprintf(cimg_stdout," : %s",usage);
+ cimg_std::fprintf(cimg_stdout," (%s, %s)\n\n",__DATE__,__TIME__);
+ }
+ if (defaut) cimg_std::fprintf(cimg_stdout,"%s\n",defaut);
+ }
+ if (name) {
+ if (argc>0) {
+ int k = 0;
+ while (k<argc && cimg::strcmp(argv[k],name)) ++k;
+ res = (k++==argc?defaut:(k==argc?argv[--k]:argv[k]));
+ } else res = defaut;
+ if (visu && usage) cimg_std::fprintf(cimg_stdout," %s%-16s%s %-24s %s%s%s\n",
+ cimg::t_bold,name,cimg::t_normal,res?res:"0",cimg::t_green,usage,cimg::t_normal);
+ }
+ return res;
+ }
+
+ inline bool option(const char *const name, const int argc, const char *const *const argv,
+ const bool defaut, const char *const usage=0) {
+ const char *s = cimg::option(name,argc,argv,(char*)0);
+ const bool res = s?(cimg::strcasecmp(s,"false") && cimg::strcasecmp(s,"off") && cimg::strcasecmp(s,"0")):defaut;
+ cimg::option(name,0,0,res?"true":"false",usage);
+ return res;
+ }
+
+ inline int option(const char *const name, const int argc, const char *const *const argv,
+ const int defaut, const char *const usage=0) {
+ const char *s = cimg::option(name,argc,argv,(char*)0);
+ const int res = s?cimg_std::atoi(s):defaut;
+ char tmp[256];
+ cimg_std::sprintf(tmp,"%d",res);
+ cimg::option(name,0,0,tmp,usage);
+ return res;
+ }
+
+ inline char option(const char *const name, const int argc, const char *const *const argv,
+ const char defaut, const char *const usage=0) {
+ const char *s = cimg::option(name,argc,argv,(char*)0);
+ const char res = s?s[0]:defaut;
+ char tmp[8];
+ tmp[0] = res; tmp[1] ='\0';
+ cimg::option(name,0,0,tmp,usage);
+ return res;
+ }
+
+ inline float option(const char *const name, const int argc, const char *const *const argv,
+ const float defaut, const char *const usage=0) {
+ const char *s = cimg::option(name,argc,argv,(char*)0);
+ const float res = s?cimg::atof(s):defaut;
+ char tmp[256];
+ cimg_std::sprintf(tmp,"%g",res);
+ cimg::option(name,0,0,tmp,usage);
+ return res;
+ }
+
+ inline double option(const char *const name, const int argc, const char *const *const argv,
+ const double defaut, const char *const usage=0) {
+ const char *s = cimg::option(name,argc,argv,(char*)0);
+ const double res = s?cimg::atof(s):defaut;
+ char tmp[256];
+ cimg_std::sprintf(tmp,"%g",res);
+ cimg::option(name,0,0,tmp,usage);
+ return res;
+ }
+
+ inline const char* argument(const unsigned int nb, const int argc, const char *const *const argv, const unsigned int nb_singles=0, ...) {
+ for (int k = 1, pos = 0; k<argc;) {
+ const char *const item = argv[k];
+ bool option = (*item=='-'), single_option = false;
+ if (option) {
+ va_list ap;
+ va_start(ap,nb_singles);
+ for (unsigned int i=0; i<nb_singles; ++i) if (!cimg::strcasecmp(item,va_arg(ap,char*))) { single_option = true; break; }
+ va_end(ap);
+ }
+ if (option) { ++k; if (!single_option) ++k; }
+ else { if (pos++==(int)nb) return item; else ++k; }
+ }
+ return 0;
+ }
+
+ //! Print informations about %CImg environement variables.
+ /**
+ Printing is done on the standard error output.
+ **/
+ inline void info() {
+ char tmp[1024] = { 0 };
+ cimg_std::fprintf(cimg_stdout,"\n %sCImg Library %u.%u.%u%s, compiled %s ( %s ) with the following flags :\n\n",
+ cimg::t_red,cimg_version/100,(cimg_version/10)%10,cimg_version%10,
+ cimg::t_normal,__DATE__,__TIME__);
+
+ cimg_std::fprintf(cimg_stdout," > Operating System : %s%-13s%s %s('cimg_OS'=%d)%s\n",
+ cimg::t_bold,
+ cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"),
+ cimg::t_normal,cimg::t_green,
+ cimg_OS,
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > CPU endianness : %s%s Endian%s\n",
+ cimg::t_bold,
+ cimg::endianness()?"Big":"Little",
+ cimg::t_normal);
+
+#ifdef cimg_use_visualcpp6
+ cimg_std::fprintf(cimg_stdout," > Using Visual C++ 6.0 : %s%-13s%s %s('cimg_use_visualcpp6' defined)%s\n",
+ cimg::t_bold,"Yes",cimg::t_normal,cimg::t_green,cimg::t_normal);
+#endif
+
+ cimg_std::fprintf(cimg_stdout," > Debug messages : %s%-13s%s %s('cimg_debug'=%d)%s\n",
+ cimg::t_bold,
+ cimg_debug==0?"Quiet":(cimg_debug==1?"Console":(cimg_debug==2?"Dialog":(cimg_debug==3?"Console+Warnings":"Dialog+Warnings"))),
+ cimg::t_normal,cimg::t_green,
+ cimg_debug,
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Stricts warnings : %s%-13s%s %s('cimg_strict_warnings' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_strict_warnings
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Using VT100 messages : %s%-13s%s %s('cimg_use_vt100' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_vt100
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Display type : %s%-13s%s %s('cimg_display'=%d)%s\n",
+ cimg::t_bold,
+ cimg_display==0?"No display":
+ (cimg_display==1?"X11":
+ (cimg_display==2?"Windows GDI":
+ (cimg_display==3?"Carbon":"Unknow"))),
+ cimg::t_normal,cimg::t_green,
+ cimg_display,
+ cimg::t_normal);
+
+#if cimg_display==1
+ cimg_std::fprintf(cimg_stdout," > Using XShm for X11 : %s%-13s%s %s('cimg_use_xshm' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_xshm
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Using XRand for X11 : %s%-13s%s %s('cimg_use_xrandr' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_xrandr
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+#endif
+ cimg_std::fprintf(cimg_stdout," > Using OpenMP : %s%-13s%s %s('cimg_use_openmp' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_openmp
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+ cimg_std::fprintf(cimg_stdout," > Using PNG library : %s%-13s%s %s('cimg_use_png' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_png
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+ cimg_std::fprintf(cimg_stdout," > Using JPEG library : %s%-13s%s %s('cimg_use_jpeg' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_jpeg
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Using TIFF library : %s%-13s%s %s('cimg_use_tiff' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_tiff
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Using Magick++ library : %s%-13s%s %s('cimg_use_magick' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_magick
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Using FFTW3 library : %s%-13s%s %s('cimg_use_fftw3' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_fftw3
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout," > Using LAPACK library : %s%-13s%s %s('cimg_use_lapack' %s)%s\n",
+ cimg::t_bold,
+#ifdef cimg_use_lapack
+ "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+ "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+ cimg::t_normal);
+
+ cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::imagemagick_path());
+ cimg_std::fprintf(cimg_stdout," > Path of ImageMagick : %s%-13s%s\n",
+ cimg::t_bold,
+ tmp,
+ cimg::t_normal);
+
+ cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::graphicsmagick_path());
+ cimg_std::fprintf(cimg_stdout," > Path of GraphicsMagick : %s%-13s%s\n",
+ cimg::t_bold,
+ tmp,
+ cimg::t_normal);
+
+ cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::medcon_path());
+ cimg_std::fprintf(cimg_stdout," > Path of 'medcon' : %s%-13s%s\n",
+ cimg::t_bold,
+ tmp,
+ cimg::t_normal);
+
+ cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::temporary_path());
+ cimg_std::fprintf(cimg_stdout," > Temporary path : %s%-13s%s\n",
+ cimg::t_bold,
+ tmp,
+ cimg::t_normal);
+
+ cimg_std::fprintf(cimg_stdout,"\n");
+ }
+
+ // Declare LAPACK function signatures if necessary.
+ //
+#ifdef cimg_use_lapack
+ template<typename T>
+ inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) {
+ dgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
+ }
+
+ inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) {
+ sgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
+ }
+
+ template<typename T>
+ inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) {
+ dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
+ }
+
+ inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) {
+ sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
+ }
+
+ template<typename T>
+ inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN,
+ T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) {
+ dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
+ }
+
+ inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN,
+ float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) {
+ sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
+ }
+
+ template<typename T>
+ inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) {
+ int one = 1;
+ dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
+ }
+
+ inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) {
+ int one = 1;
+ sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
+ }
+
+ template<typename T>
+ inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) {
+ dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
+ }
+
+ inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) {
+ ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
+ }
+#endif
+
+ // End of the 'cimg' namespace
+ }
+
+ /*------------------------------------------------
+ #
+ #
+ # Definition of mathematical operators and
+ # external functions.
+ #
+ #
+ -------------------------------------------------*/
+ //
+ // These functions are extern to any classes and can be used for a "functional-style" programming,
+ // such as writting :
+ // cos(img);
+ // instead of img.get_cos();
+ //
+ // Note that only the arithmetic operators and functions are implemented here.
+ //
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator+(const CImg<t>& img, const t val) {
+ return CImg<t>(img,false)+=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img,false)+=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator+(const t val, const CImg<t>& img) {
+ return img + val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator+(const t1 val, const CImg<t2>& img) {
+ return img + val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<t> operator+(const CImgList<t>& list, const t val) {
+ return CImgList<t>(list)+=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list)+=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<t> operator+(const t val, const CImgList<t>& list) {
+ return list + val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const t1 val, const CImgList<t2>& list) {
+ return list + val;
+ }
+#endif
+
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img1, const CImg<t2>& img2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img1,false)+=img2;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img, const CImgList<t2>& list) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list)+=img;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list, const CImg<t2>& img) {
+ return img + list;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list1, const CImgList<t2>& list2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list1)+=list2;
+ }
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator-(const CImg<t>& img, const t val) {
+ return CImg<t>(img,false)-=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img,false)-=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator-(const t val, const CImg<t>& img) {
+ return CImg<t>(img.width,img.height,img.depth,img.dim,val)-=img;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator-(const t1 val, const CImg<t2>& img) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img.width,img.height,img.depth,img.dim,(t1t2)val)-=img;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<t> operator-(const CImgList<t>& list, const t val) {
+ return CImgList<t>(list)-=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list)-=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<double> operator-(const t val, const CImgList<t>& list) {
+ CImgList<t> res(list.size);
+ cimglist_for(res,l) res[l] = val - list[l];
+ return res;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const t1 val, const CImgList<t2>& list) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ CImgList<t1t2> res(list.size);
+ cimglist_for(res,l) res[l] = val - list[l];
+ return res;
+ }
+#endif
+
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img1, const CImg<t2>& img2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img1,false)-=img2;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img, const CImgList<t2>& list) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ CImgList<t1t2> res(list.size);
+ cimglist_for(res,l) res[l] = img - list[l];
+ return res;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list, const CImg<t2>& img) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list)-=img;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list1, const CImgList<t2>& list2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list1)-=list2;
+ }
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator*(const CImg<t>& img, const double val) {
+ return CImg<t>(img,false)*=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img,false)*=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator*(const double val, const CImg<t>& img) {
+ return img*val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator*(const t1 val, const CImg<t2>& img) {
+ return img*val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<t> operator*(const CImgList<t>& list, const double val) {
+ return CImgList<t>(list)*=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list)*=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<t> operator*(const double val, const CImgList<t>& list) {
+ return list*val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const t1 val, const CImgList<t2>& list) {
+ return list*val;
+ }
+#endif
+
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img1, const CImg<t2>& img2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ if (img1.width!=img2.height)
+ throw CImgArgumentException("operator*() : can't multiply a matrix (%ux%u) by a matrix (%ux%u)",
+ img1.width,img1.height,img2.width,img2.height);
+ CImg<t1t2> res(img2.width,img1.height);
+ t1t2 val;
+#ifdef cimg_use_openmp
+#pragma omp parallel for if (img1.size()>=1000 && img2.size()>=1000) private(val)
+#endif
+ cimg_forXY(res,i,j) { val = 0; cimg_forX(img1,k) val+=img1(k,j)*img2(i,k); res(i,j) = val; }
+ return res;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img, const CImgList<t2>& list) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ CImgList<t1t2> res(list.size);
+ cimglist_for(res,l) res[l] = img*list[l];
+ return res;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list, const CImg<t2>& img) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ CImgList<t1t2> res(list.size);
+ cimglist_for(res,l) res[l] = list[l]*img;
+ return res;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list1, const CImgList<t2>& list2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ CImgList<t1t2> res(cimg::min(list1.size,list2.size));
+ cimglist_for(res,l) res[l] = list1[l]*list2[l];
+ return res;
+ }
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator/(const CImg<t>& img, const double val) {
+ return CImg<t>(img,false)/=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img,false)/=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImg<t> operator/(const double val, CImg<t>& img) {
+ return val*img.get_invert();
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator/(const t1 val, CImg<t2>& img) {
+ return val*img.get_invert();
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<t> operator/(const CImgList<t>& list, const double val) {
+ return CImgList<t>(list)/=val;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list, const t2 val) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list)/=val;
+ }
+#endif
+
+#ifdef cimg_use_visualcpp6
+ template<typename t>
+ inline CImgList<t> operator/(const double val, const CImgList<t>& list) {
+ CImgList<t> res(list.size);
+ cimglist_for(res,l) res[l] = val/list[l];
+ return res;
+ }
+#else
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const t1 val, const CImgList<t2>& list) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ CImgList<t1t2> res(list.size);
+ cimglist_for(res,l) res[l] = val/list[l];
+ return res;
+ }
+#endif
+
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img1, const CImg<t2>& img2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImg<t1t2>(img1,false)*=img2.get_invert();
+ }
+
+ template<typename t1, typename t2>
+ inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img, const CImgList<t2>& list) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ CImgList<t1t2> res(list.size);
+ cimglist_for(res,l) res[l] = img/list[l];
+ return res;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list, const CImg<t2>& img) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list)/=img;
+ }
+
+ template<typename t1, typename t2>
+ inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list1, const CImgList<t2>& list2) {
+ typedef typename cimg::superset<t1,t2>::type t1t2;
+ return CImgList<t1t2>(list1)/=list2;
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sqr(const CImg<T>& instance) {
+ return instance.get_sqr();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sqrt(const CImg<T>& instance) {
+ return instance.get_sqrt();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> exp(const CImg<T>& instance) {
+ return instance.get_exp();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> log(const CImg<T>& instance) {
+ return instance.get_log();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> log10(const CImg<T>& instance) {
+ return instance.get_log10();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> abs(const CImg<T>& instance) {
+ return instance.get_abs();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> cos(const CImg<T>& instance) {
+ return instance.get_cos();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sin(const CImg<T>& instance) {
+ return instance.get_sin();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> tan(const CImg<T>& instance) {
+ return instance.get_tan();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> acos(const CImg<T>& instance) {
+ return instance.get_acos();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> asin(const CImg<T>& instance) {
+ return instance.get_asin();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> atan(const CImg<T>& instance) {
+ return instance.get_atan();
+ }
+
+ template<typename T>
+ inline CImg<T> transpose(const CImg<T>& instance) {
+ return instance.get_transpose();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> invert(const CImg<T>& instance) {
+ return instance.get_invert();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> pseudoinvert(const CImg<T>& instance) {
+ return instance.get_pseudoinvert();
+ }
+
+ /*-------------------------------------------
+ #
+ #
+ #
+ # Definition of the CImgDisplay structure
+ #
+ #
+ #
+ --------------------------------------------*/
+
+ //! This class represents a window which can display \ref CImg images and handles mouse and keyboard events.
+ /**
+ Creating a \c CImgDisplay instance opens a window that can be used to display a \c CImg<T> image
+ of a \c CImgList<T> image list inside. When a display is created, associated window events
+ (such as mouse motion, keyboard and window size changes) are handled and can be easily
+ detected by testing specific \c CImgDisplay data fields.
+ See \ref cimg_displays for a complete tutorial on using the \c CImgDisplay class.
+ **/
+
+ struct CImgDisplay {
+
+ //! Width of the display
+ unsigned int width;
+
+ //! Height of the display
+ unsigned int height;
+
+ //! Normalization type used for the display
+ unsigned int normalization;
+
+ //! Display title
+ char* title;
+
+ //! X-pos of the display on the screen
+ volatile int window_x;
+
+ //! Y-pos of the display on the screen
+ volatile int window_y;
+
+ //! Width of the underlying window
+ volatile unsigned int window_width;
+
+ //! Height of the underlying window
+ volatile unsigned int window_height;
+
+ //! X-coordinate of the mouse pointer on the display
+ volatile int mouse_x;
+
+ //! Y-coordinate of the mouse pointer on the display
+ volatile int mouse_y;
+
+ //! Button state of the mouse
+ volatile unsigned int buttons[512];
+ volatile unsigned int& button;
+
+ //! Wheel state of the mouse
+ volatile int wheel;
+
+ //! Key value if pressed
+ volatile unsigned int& key;
+ volatile unsigned int keys[512];
+
+ //! Key value if released
+ volatile unsigned int& released_key;
+ volatile unsigned int released_keys[512];
+
+ //! Closed state of the window
+ volatile bool is_closed;
+
+ //! Resized state of the window
+ volatile bool is_resized;
+
+ //! Moved state of the window
+ volatile bool is_moved;
+
+ //! Event state of the window
+ volatile bool is_event;
+
+ //! Current state of the corresponding key (exists for all referenced keys).
+ volatile bool is_keyESC;
+ volatile bool is_keyF1;
+ volatile bool is_keyF2;
+ volatile bool is_keyF3;
+ volatile bool is_keyF4;
+ volatile bool is_keyF5;
+ volatile bool is_keyF6;
+ volatile bool is_keyF7;
+ volatile bool is_keyF8;
+ volatile bool is_keyF9;
+ volatile bool is_keyF10;
+ volatile bool is_keyF11;
+ volatile bool is_keyF12;
+ volatile bool is_keyPAUSE;
+ volatile bool is_key1;
+ volatile bool is_key2;
+ volatile bool is_key3;
+ volatile bool is_key4;
+ volatile bool is_key5;
+ volatile bool is_key6;
+ volatile bool is_key7;
+ volatile bool is_key8;
+ volatile bool is_key9;
+ volatile bool is_key0;
+ volatile bool is_keyBACKSPACE;
+ volatile bool is_keyINSERT;
+ volatile bool is_keyHOME;
+ volatile bool is_keyPAGEUP;
+ volatile bool is_keyTAB;
+ volatile bool is_keyQ;
+ volatile bool is_keyW;
+ volatile bool is_keyE;
+ volatile bool is_keyR;
+ volatile bool is_keyT;
+ volatile bool is_keyY;
+ volatile bool is_keyU;
+ volatile bool is_keyI;
+ volatile bool is_keyO;
+ volatile bool is_keyP;
+ volatile bool is_keyDELETE;
+ volatile bool is_keyEND;
+ volatile bool is_keyPAGEDOWN;
+ volatile bool is_keyCAPSLOCK;
+ volatile bool is_keyA;
+ volatile bool is_keyS;
+ volatile bool is_keyD;
+ volatile bool is_keyF;
+ volatile bool is_keyG;
+ volatile bool is_keyH;
+ volatile bool is_keyJ;
+ volatile bool is_keyK;
+ volatile bool is_keyL;
+ volatile bool is_keyENTER;
+ volatile bool is_keySHIFTLEFT;
+ volatile bool is_keyZ;
+ volatile bool is_keyX;
+ volatile bool is_keyC;
+ volatile bool is_keyV;
+ volatile bool is_keyB;
+ volatile bool is_keyN;
+ volatile bool is_keyM;
+ volatile bool is_keySHIFTRIGHT;
+ volatile bool is_keyARROWUP;
+ volatile bool is_keyCTRLLEFT;
+ volatile bool is_keyAPPLEFT;
+ volatile bool is_keyALT;
+ volatile bool is_keySPACE;
+ volatile bool is_keyALTGR;
+ volatile bool is_keyAPPRIGHT;
+ volatile bool is_keyMENU;
+ volatile bool is_keyCTRLRIGHT;
+ volatile bool is_keyARROWLEFT;
+ volatile bool is_keyARROWDOWN;
+ volatile bool is_keyARROWRIGHT;
+ volatile bool is_keyPAD0;
+ volatile bool is_keyPAD1;
+ volatile bool is_keyPAD2;
+ volatile bool is_keyPAD3;
+ volatile bool is_keyPAD4;
+ volatile bool is_keyPAD5;
+ volatile bool is_keyPAD6;
+ volatile bool is_keyPAD7;
+ volatile bool is_keyPAD8;
+ volatile bool is_keyPAD9;
+ volatile bool is_keyPADADD;
+ volatile bool is_keyPADSUB;
+ volatile bool is_keyPADMUL;
+ volatile bool is_keyPADDIV;
+
+ //! Fullscreen state of the display
+ bool is_fullscreen;
+
+ float fps_fps, min, max;
+ unsigned long timer, fps_frames, fps_timer;
+
+#ifdef cimgdisplay_plugin
+#include cimgdisplay_plugin
+#endif
+#ifdef cimgdisplay_plugin1
+#include cimgdisplay_plugin1
+#endif
+#ifdef cimgdisplay_plugin2
+#include cimgdisplay_plugin2
+#endif
+#ifdef cimgdisplay_plugin3
+#include cimgdisplay_plugin3
+#endif
+#ifdef cimgdisplay_plugin4
+#include cimgdisplay_plugin4
+#endif
+#ifdef cimgdisplay_plugin5
+#include cimgdisplay_plugin5
+#endif
+#ifdef cimgdisplay_plugin6
+#include cimgdisplay_plugin6
+#endif
+#ifdef cimgdisplay_plugin7
+#include cimgdisplay_plugin7
+#endif
+#ifdef cimgdisplay_plugin8
+#include cimgdisplay_plugin8
+#endif
+
+ //! Create an empty display window.
+ CImgDisplay():
+ width(0),height(0),normalization(0),title(0),
+ window_x(0),window_y(0),window_width(0),window_height(0),
+ mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
+ is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),
+ min(0),max(0) {}
+
+ //! Create a display window with a specified size \p pwidth x \p height.
+ /** \param dimw Width of the display window.
+ \param dimh Height of the display window.
+ \param title Title of the display window.
+ \param normalization_type Normalization type of the display window (0=none, 1=always, 2=once).
+ \param fullscreen_flag : Fullscreen mode.
+ \param closed_flag : Initially visible mode.
+ A black image will be initially displayed in the display window.
+ **/
+ CImgDisplay(const unsigned int dimw, const unsigned int dimh, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false):
+ width(0),height(0),normalization(0),title(0),
+ window_x(0),window_y(0),window_width(0),window_height(0),
+ mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
+ is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),
+ min(0),max(0) {
+ assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
+ }
+
+ //! Create a display window from an image.
+ /** \param img : Image that will be used to create the display window.
+ \param title : Title of the display window
+ \param normalization_type : Normalization type of the display window.
+ \param fullscreen_flag : Fullscreen mode.
+ \param closed_flag : Initially visible mode.
+ **/
+ template<typename T>
+ CImgDisplay(const CImg<T>& img, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false):
+ width(0),height(0),normalization(0),title(0),
+ window_x(0),window_y(0),window_width(0),window_height(0),
+ mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
+ is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
+ assign(img,title,normalization_type,fullscreen_flag,closed_flag);
+ }
+
+ //! Create a display window from an image list.
+ /** \param list : The list of images to display.
+ \param title : Title of the display window
+ \param normalization_type : Normalization type of the display window.
+ \param fullscreen_flag : Fullscreen mode.
+ \param closed_flag : Initially visible mode.
+ **/
+ template<typename T>
+ CImgDisplay(const CImgList<T>& list, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false):
+ width(0),height(0),normalization(0),title(0),
+ window_x(0),window_y(0),window_width(0),window_height(0),
+ mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
+ is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
+ assign(list,title,normalization_type,fullscreen_flag,closed_flag);
+ }
+
+ //! Create a display window by copying another one.
+ /**
+ \param disp : Display window to copy.
+ **/
+ CImgDisplay(const CImgDisplay& disp):
+ width(0),height(0),normalization(0),title(0),
+ window_x(0),window_y(0),window_width(0),window_height(0),
+ mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
+ is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
+ assign(disp);
+ }
+
+ //! Destructor.
+ ~CImgDisplay() {
+ assign();
+ }
+
+ //! Assignment operator.
+ CImgDisplay& operator=(const CImgDisplay& disp) {
+ return assign(disp);
+ }
+
+ //! Return true is display is empty.
+ bool is_empty() const {
+ return (!width || !height);
+ }
+
+ //! Return true if display is not empty.
+ operator bool() const {
+ return !is_empty();
+ }
+
+ //! Return display width.
+ int dimx() const {
+ return (int)width;
+ }
+
+ //! Return display height.
+ int dimy() const {
+ return (int)height;
+ }
+
+ //! Return display window width.
+ int window_dimx() const {
+ return (int)window_width;
+ }
+
+ //! Return display window height.
+ int window_dimy() const {
+ return (int)window_height;
+ }
+
+ //! Return X-coordinate of the window.
+ int window_posx() const {
+ return window_x;
+ }
+
+ //! Return Y-coordinate of the window.
+ int window_posy() const {
+ return window_y;
+ }
+
+ //! Synchronized waiting function. Same as cimg::wait().
+ CImgDisplay& wait(const unsigned int milliseconds) {
+ cimg::_sleep(milliseconds,timer);
+ return *this;
+ }
+
+ //! Wait for an event occuring on the current display.
+ CImgDisplay& wait() {
+ if (!is_empty()) wait(*this);
+ return *this;
+ }
+
+ //! Wait for any event occuring on the display \c disp1.
+ static void wait(CImgDisplay& disp1) {
+ disp1.is_event = 0;
+ while (!disp1.is_event) wait_all();
+ }
+
+ //! Wait for any event occuring either on the display \c disp1 or \c disp2.
+ static void wait(CImgDisplay& disp1, CImgDisplay& disp2) {
+ disp1.is_event = disp2.is_event = 0;
+ while (!disp1.is_event && !disp2.is_event) wait_all();
+ }
+
+ //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3.
+ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) {
+ disp1.is_event = disp2.is_event = disp3.is_event = 0;
+ while (!disp1.is_event && !disp2.is_event && !disp3.is_event) wait_all();
+ }
+
+ //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4.
+ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) {
+ disp1.is_event = disp2.is_event = disp3.is_event = disp4.is_event = 0;
+ while (!disp1.is_event && !disp2.is_event && !disp3.is_event && !disp4.is_event) wait_all();
+ }
+
+ //! Return the frame per second rate.
+ float frames_per_second() {
+ if (!fps_timer) fps_timer = cimg::time();
+ const float delta = (cimg::time()-fps_timer)/1000.0f;
+ ++fps_frames;
+ if (delta>=1) {
+ fps_fps = fps_frames/delta;
+ fps_frames = 0;
+ fps_timer = cimg::time();
+ }
+ return fps_fps;
+ }
+
+ //! Display an image list CImgList<T> into a display window.
+ /** First, all images of the list are appended into a single image used for visualization,
+ then this image is displayed in the current display window.
+ \param list : The list of images to display.
+ \param axis : The axis used to append the image for visualization. Can be 'x' (default),'y','z' or 'v'.
+ \param align : Defines the relative alignment of images when displaying images of different sizes.
+ Can be '\p c' (centered, which is the default), '\p p' (top alignment) and '\p n' (bottom aligment).
+ **/
+ template<typename T>
+ CImgDisplay& display(const CImgList<T>& list, const char axis='x', const char align='p') {
+ return display(list.get_append(axis,align));
+ }
+
+ //! Display an image CImg<T> into a display window.
+ template<typename T>
+ CImgDisplay& operator<<(const CImg<T>& img) {
+ return display(img);
+ }
+
+ //! Display an image CImg<T> into a display window.
+ template<typename T>
+ CImgDisplay& operator<<(const CImgList<T>& list) {
+ return display(list);
+ }
+
+ //! Resize a display window with the size of an image.
+ /** \param img : Input image. \p image.width and \p image.height give the new dimensions of the display window.
+ \param redraw : If \p true (default), the current displayed image in the display window will
+ be bloc-interpolated to fit the new dimensions. If \p false, a black image will be drawn in the resized window.
+ **/
+ template<typename T>
+ CImgDisplay& resize(const CImg<T>& img, const bool redraw=true) {
+ return resize(img.width,img.height,redraw);
+ }
+
+ //! Resize a display window using the size of the given display \p disp.
+ CImgDisplay& resize(const CImgDisplay& disp, const bool redraw=true) {
+ return resize(disp.width,disp.height,redraw);
+ }
+
+ //! Resize a display window in its current size.
+ CImgDisplay& resize(const bool redraw=true) {
+ resize(window_width,window_height,redraw);
+ return *this;
+ }
+
+ //! Set fullscreen mode.
+ CImgDisplay& fullscreen(const bool redraw=true) {
+ if (is_empty() || is_fullscreen) return *this;
+ return toggle_fullscreen(redraw);
+ }
+
+ //! Set normal screen mode.
+ CImgDisplay& normalscreen(const bool redraw=true) {
+ if (is_empty() || !is_fullscreen) return *this;
+ return toggle_fullscreen(redraw);
+ }
+
+ // Inner routine used for fast resizing of buffer to display size.
+ template<typename t, typename T>
+ static void _render_resize(const T *ptrs, const unsigned int ws, const unsigned int hs,
+ t *ptrd, const unsigned int wd, const unsigned int hd) {
+ unsigned int *const offx = new unsigned int[wd], *const offy = new unsigned int[hd+1], *poffx, *poffy;
+ float s, curr, old;
+ s = (float)ws/wd;
+ poffx = offx; curr = 0; for (unsigned int x=0; x<wd; ++x) { old=curr; curr+=s; *(poffx++) = (unsigned int)curr-(unsigned int)old; }
+ s = (float)hs/hd;
+ poffy = offy; curr = 0; for (unsigned int y=0; y<hd; ++y) { old=curr; curr+=s; *(poffy++) = ws*((unsigned int)curr-(unsigned int)old); }
+ *poffy = 0;
+ poffy = offy;
+ {for (unsigned int y=0; y<hd; ) {
+ const T *ptr = ptrs;
+ poffx = offx;
+ for (unsigned int x=0; x<wd; ++x) { *(ptrd++) = *ptr; ptr+=*(poffx++); }
+ ++y;
+ unsigned int dy=*(poffy++);
+ for (;!dy && y<hd; cimg_std::memcpy(ptrd, ptrd-wd, sizeof(t)*wd), ++y, ptrd+=wd, dy=*(poffy++)) {}
+ ptrs+=dy;
+ }}
+ delete[] offx; delete[] offy;
+ }
+
+ //! Clear all events of the current display.
+ CImgDisplay& flush() {
+ cimg_std::memset((void*)buttons,0,512*sizeof(unsigned int));
+ cimg_std::memset((void*)keys,0,512*sizeof(unsigned int));
+ cimg_std::memset((void*)released_keys,0,512*sizeof(unsigned int));
+ is_keyESC = is_keyF1 = is_keyF2 = is_keyF3 = is_keyF4 = is_keyF5 = is_keyF6 = is_keyF7 = is_keyF8 = is_keyF9 =
+ is_keyF10 = is_keyF11 = is_keyF12 = is_keyPAUSE = is_key1 = is_key2 = is_key3 = is_key4 = is_key5 = is_key6 =
+ is_key7 = is_key8 = is_key9 = is_key0 = is_keyBACKSPACE = is_keyINSERT = is_keyHOME = is_keyPAGEUP = is_keyTAB =
+ is_keyQ = is_keyW = is_keyE = is_keyR = is_keyT = is_keyY = is_keyU = is_keyI = is_keyO = is_keyP = is_keyDELETE =
+ is_keyEND = is_keyPAGEDOWN = is_keyCAPSLOCK = is_keyA = is_keyS = is_keyD = is_keyF = is_keyG = is_keyH = is_keyJ =
+ is_keyK = is_keyL = is_keyENTER = is_keySHIFTLEFT = is_keyZ = is_keyX = is_keyC = is_keyV = is_keyB = is_keyN =
+ is_keyM = is_keySHIFTRIGHT = is_keyARROWUP = is_keyCTRLLEFT = is_keyAPPLEFT = is_keyALT = is_keySPACE = is_keyALTGR = is_keyAPPRIGHT =
+ is_keyMENU = is_keyCTRLRIGHT = is_keyARROWLEFT = is_keyARROWDOWN = is_keyARROWRIGHT = is_keyPAD0 = is_keyPAD1 = is_keyPAD2 =
+ is_keyPAD3 = is_keyPAD4 = is_keyPAD5 = is_keyPAD6 = is_keyPAD7 = is_keyPAD8 = is_keyPAD9 = is_keyPADADD = is_keyPADSUB =
+ is_keyPADMUL = is_keyPADDIV = false;
+ is_resized = is_moved = is_event = false;
+ fps_timer = fps_frames = timer = wheel = 0;
+ mouse_x = mouse_y = -1;
+ fps_fps = 0;
+ return *this;
+ }
+
+ // Update 'is_key' fields.
+ void update_iskey(const unsigned int key, const bool pressed=true) {
+#define _cimg_iskey_case(k) if (key==cimg::key##k) is_key##k = pressed;
+ _cimg_iskey_case(ESC); _cimg_iskey_case(F1); _cimg_iskey_case(F2); _cimg_iskey_case(F3);
+ _cimg_iskey_case(F4); _cimg_iskey_case(F5); _cimg_iskey_case(F6); _cimg_iskey_case(F7);
+ _cimg_iskey_case(F8); _cimg_iskey_case(F9); _cimg_iskey_case(F10); _cimg_iskey_case(F11);
+ _cimg_iskey_case(F12); _cimg_iskey_case(PAUSE); _cimg_iskey_case(1); _cimg_iskey_case(2);
+ _cimg_iskey_case(3); _cimg_iskey_case(4); _cimg_iskey_case(5); _cimg_iskey_case(6);
+ _cimg_iskey_case(7); _cimg_iskey_case(8); _cimg_iskey_case(9); _cimg_iskey_case(0);
+ _cimg_iskey_case(BACKSPACE); _cimg_iskey_case(INSERT); _cimg_iskey_case(HOME);
+ _cimg_iskey_case(PAGEUP); _cimg_iskey_case(TAB); _cimg_iskey_case(Q); _cimg_iskey_case(W);
+ _cimg_iskey_case(E); _cimg_iskey_case(R); _cimg_iskey_case(T); _cimg_iskey_case(Y);
+ _cimg_iskey_case(U); _cimg_iskey_case(I); _cimg_iskey_case(O); _cimg_iskey_case(P);
+ _cimg_iskey_case(DELETE); _cimg_iskey_case(END); _cimg_iskey_case(PAGEDOWN);
+ _cimg_iskey_case(CAPSLOCK); _cimg_iskey_case(A); _cimg_iskey_case(S); _cimg_iskey_case(D);
+ _cimg_iskey_case(F); _cimg_iskey_case(G); _cimg_iskey_case(H); _cimg_iskey_case(J);
+ _cimg_iskey_case(K); _cimg_iskey_case(L); _cimg_iskey_case(ENTER);
+ _cimg_iskey_case(SHIFTLEFT); _cimg_iskey_case(Z); _cimg_iskey_case(X); _cimg_iskey_case(C);
+ _cimg_iskey_case(V); _cimg_iskey_case(B); _cimg_iskey_case(N); _cimg_iskey_case(M);
+ _cimg_iskey_case(SHIFTRIGHT); _cimg_iskey_case(ARROWUP); _cimg_iskey_case(CTRLLEFT);
+ _cimg_iskey_case(APPLEFT); _cimg_iskey_case(ALT); _cimg_iskey_case(SPACE); _cimg_iskey_case(ALTGR);
+ _cimg_iskey_case(APPRIGHT); _cimg_iskey_case(MENU); _cimg_iskey_case(CTRLRIGHT);
+ _cimg_iskey_case(ARROWLEFT); _cimg_iskey_case(ARROWDOWN); _cimg_iskey_case(ARROWRIGHT);
+ _cimg_iskey_case(PAD0); _cimg_iskey_case(PAD1); _cimg_iskey_case(PAD2);
+ _cimg_iskey_case(PAD3); _cimg_iskey_case(PAD4); _cimg_iskey_case(PAD5);
+ _cimg_iskey_case(PAD6); _cimg_iskey_case(PAD7); _cimg_iskey_case(PAD8);
+ _cimg_iskey_case(PAD9); _cimg_iskey_case(PADADD); _cimg_iskey_case(PADSUB);
+ _cimg_iskey_case(PADMUL); _cimg_iskey_case(PADDIV);
+ }
+
+ //! Test if any key has been pressed.
+ bool is_key(const bool remove=false) {
+ for (unsigned int *ptrs=(unsigned int*)keys+512-1; ptrs>=keys; --ptrs) if (*ptrs) { if (remove) *ptrs = 0; return true; }
+ return false;
+ }
+
+ //! Test if a key has been pressed.
+ bool is_key(const unsigned int key1, const bool remove) {
+ for (unsigned int *ptrs=(unsigned int*)keys+512-1; ptrs>=keys; --ptrs) if (*ptrs==key1) { if (remove) *ptrs = 0; return true; }
+ return false;
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const bool remove) {
+ const unsigned int seq[] = { key1, key2 };
+ return is_key(seq,2,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, const bool remove) {
+ const unsigned int seq[] = { key1, key2, key3 };
+ return is_key(seq,3,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
+ const unsigned int key4, const bool remove) {
+ const unsigned int seq[] = { key1, key2, key3, key4 };
+ return is_key(seq,4,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
+ const unsigned int key4, const unsigned int key5, const bool remove) {
+ const unsigned int seq[] = { key1, key2, key3, key4, key5 };
+ return is_key(seq,5,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
+ const unsigned int key4, const unsigned int key5, const unsigned int key6, const bool remove) {
+ const unsigned int seq[] = { key1, key2, key3, key4, key5, key6 };
+ return is_key(seq,6,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
+ const unsigned int key4, const unsigned int key5, const unsigned int key6,
+ const unsigned int key7, const bool remove) {
+ const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7 };
+ return is_key(seq,7,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
+ const unsigned int key4, const unsigned int key5, const unsigned int key6,
+ const unsigned int key7, const unsigned int key8, const bool remove) {
+ const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8 };
+ return is_key(seq,8,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
+ const unsigned int key4, const unsigned int key5, const unsigned int key6,
+ const unsigned int key7, const unsigned int key8, const unsigned int key9, const bool remove) {
+ const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8, key9 };
+ return is_key(seq,9,remove);
+ }
+
+ //! Test if a key sequence has been typed.
+ bool is_key(const unsigned int *const keyseq, const unsigned int N, const bool remove=true) {
+ if (keyseq && N) {
+ const unsigned int *const ps_end = keyseq+N-1, k = *ps_end, *const pk_end = (unsigned int*)keys+1+512-N;
+ for (unsigned int *pk = (unsigned int*)keys; pk<pk_end; ) {
+ if (*(pk++)==k) {
+ bool res = true;
+ const unsigned int *ps = ps_end, *pk2 = pk;
+ for (unsigned int i=1; i<N; ++i) res = (*(--ps)==*(pk2++));
+ if (res) {
+ if (remove) cimg_std::memset((void*)(pk-1),0,sizeof(unsigned int)*N);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // Find the good width and height of a window to display an image (internal routine).
+#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,128,-85,false),CImgDisplay::_fitscreen(dx,dy,dz,128,-85,true)
+ static unsigned int _fitscreen(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1,
+ const int dmin=128, const int dmax=-85,const bool return_last=false) {
+ unsigned int nw = dx + (dz>1?dz:0), nh = dy + (dz>1?dz:0);
+ const unsigned int
+ sw = CImgDisplay::screen_dimx(), sh = CImgDisplay::screen_dimy(),
+ mw = dmin<0?(unsigned int)(sw*-dmin/100):(unsigned int)dmin,
+ mh = dmin<0?(unsigned int)(sh*-dmin/100):(unsigned int)dmin,
+ Mw = dmax<0?(unsigned int)(sw*-dmax/100):(unsigned int)dmax,
+ Mh = dmax<0?(unsigned int)(sh*-dmax/100):(unsigned int)dmax;
+ if (nw<mw) { nh = nh*mw/nw; nh+=(nh==0); nw = mw; }
+ if (nh<mh) { nw = nw*mh/nh; nw+=(nw==0); nh = mh; }
+ if (nw>Mw) { nh = nh*Mw/nw; nh+=(nh==0); nw = Mw; }
+ if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0); nh = Mh; }
+ if (nw<mw) nw = mw;
+ if (nh<mh) nh = mh;
+ if (return_last) return nh;
+ return nw;
+ }
+
+ // When no display available
+ //---------------------------
+#if cimg_display==0
+
+ //! Return the width of the screen resolution.
+ static int screen_dimx() {
+ return 0;
+ }
+
+ //! Return the height of the screen resolution.
+ static int screen_dimy() {
+ return 0;
+ }
+
+ //! Wait for a window event in any CImg window.
+ static void wait_all() {}
+
+ //! In-place version of the destructor.
+ CImgDisplay& assign() {
+ return *this;
+ }
+
+ //! In-place version of the previous constructor.
+ CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ throw CImgDisplayException("CImgDisplay() : Display has been required but is not available (cimg_display=0)");
+ const char* avoid_warning = title + dimw + dimh + normalization_type + (int)fullscreen_flag + (int)closed_flag;
+ avoid_warning = 0;
+ return *this;
+ }
+
+ //! In-place version of the previous constructor.
+ template<typename T>
+ CImgDisplay& assign(const CImg<T>& img, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)");
+ const char* avoid_warning = title + img.width + normalization_type + (int)fullscreen_flag + (int)closed_flag;
+ avoid_warning = 0;
+ return assign(0,0);
+ }
+
+ //! In-place version of the previous constructor.
+ template<typename T>
+ CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)");
+ const char* avoid_warning = title + list.size + normalization_type + (int)fullscreen_flag + (int)closed_flag;
+ avoid_warning = 0;
+ return assign(0,0);
+ }
+
+ //! In-place version of the previous constructor.
+ CImgDisplay& assign(const CImgDisplay &disp) {
+ return assign(disp.width,disp.height);
+ }
+
+ //! Resize window.
+ CImgDisplay& resize(const int width, const int height, const bool redraw=true) {
+ int avoid_warning = width | height | (int)redraw;
+ avoid_warning = 0;
+ return *this;
+ }
+
+ //! Toggle fullscreen mode.
+ CImgDisplay& toggle_fullscreen(const bool redraw=true) {
+ bool avoid_warning = redraw;
+ avoid_warning = false;
+ return *this;
+ }
+
+ //! Show a closed display.
+ CImgDisplay& show() {
+ return *this;
+ }
+
+ //! Close a visible display.
+ CImgDisplay& close() {
+ return *this;
+ }
+
+ //! Move window.
+ CImgDisplay& move(const int posx, const int posy) {
+ int avoid_warning = posx | posy;
+ avoid_warning = 0;
+ return *this;
+ }
+
+ //! Show mouse pointer.
+ CImgDisplay& show_mouse() {
+ return *this;
+ }
+
+ //! Hide mouse pointer.
+ CImgDisplay& hide_mouse() {
+ return *this;
+ }
+
+ //! Move mouse pointer to a specific location.
+ CImgDisplay& set_mouse(const int posx, const int posy) {
+ int avoid_warning = posx | posy;
+ avoid_warning = 0;
+ return *this;
+ }
+
+ //! Set the window title.
+ CImgDisplay& set_title(const char *format, ...) {
+ const char *avoid_warning = format;
+ avoid_warning = 0;
+ return *this;
+ }
+
+ //! Display an image in a window.
+ template<typename T>
+ CImgDisplay& display(const CImg<T>& img) {
+ unsigned int avoid_warning = img.width;
+ avoid_warning = 0;
+ return *this;
+ }
+
+ //! Re-paint image content in window.
+ CImgDisplay& paint() {
+ return *this;
+ }
+
+ //! Render image buffer into GDI native image format.
+ template<typename T>
+ CImgDisplay& render(const CImg<T>& img) {
+ unsigned int avoid_warning = img.width;
+ avoid_warning = 0;
+ return *this;
+ }
+
+ //! Take a snapshot of the display in the specified image.
+ template<typename T>
+ const CImgDisplay& snapshot(CImg<T>& img) const {
+ img.assign(width,height,1,3,0);
+ return *this;
+ }
+
+ // X11-based display
+ //-------------------
+#elif cimg_display==1
+ Atom wm_delete_window, wm_delete_protocol;
+ Window window, background_window;
+ Colormap colormap;
+ XImage *image;
+ void *data;
+#ifdef cimg_use_xshm
+ XShmSegmentInfo *shminfo;
+#endif
+
+ static int screen_dimx() {
+ int res = 0;
+ if (!cimg::X11attr().display) {
+ Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0"));
+ if (!disp)
+ throw CImgDisplayException("CImgDisplay::screen_dimx() : Can't open X11 display.");
+ res = DisplayWidth(disp,DefaultScreen(disp));
+ XCloseDisplay(disp);
+ } else {
+#ifdef cimg_use_xrandr
+ if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution)
+ res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width;
+ else
+#endif
+ res = DisplayWidth(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
+ }
+ return res;
+ }
+
+ static int screen_dimy() {
+ int res = 0;
+ if (!cimg::X11attr().display) {
+ Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY") ? cimg_std::getenv("DISPLAY") : ":0.0"));
+ if (!disp)
+ throw CImgDisplayException("CImgDisplay::screen_dimy() : Can't open X11 display.");
+ res = DisplayHeight(disp,DefaultScreen(disp));
+ XCloseDisplay(disp);
+ } else {
+#ifdef cimg_use_xrandr
+ if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution)
+ res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height;
+ else
+#endif
+ res = DisplayHeight(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
+ }
+ return res;
+ }
+
+ static void wait_all() {
+ if (cimg::X11attr().display) {
+ XLockDisplay(cimg::X11attr().display);
+ bool flag = true;
+ XEvent event;
+ while (flag) {
+ XNextEvent(cimg::X11attr().display, &event);
+ for (unsigned int i = 0; i<cimg::X11attr().nb_wins; ++i)
+ if (!cimg::X11attr().wins[i]->is_closed && event.xany.window==cimg::X11attr().wins[i]->window) {
+ cimg::X11attr().wins[i]->_handle_events(&event);
+ if (cimg::X11attr().wins[i]->is_event) flag = false;
+ }
+ }
+ XUnlockDisplay(cimg::X11attr().display);
+ }
+ }
+
+ void _handle_events(const XEvent *const pevent) {
+ XEvent event = *pevent;
+ switch (event.type) {
+ case ClientMessage : {
+ if ((int)event.xclient.message_type==(int)wm_delete_protocol &&
+ (int)event.xclient.data.l[0]==(int)wm_delete_window) {
+ XUnmapWindow(cimg::X11attr().display,window);
+ mouse_x = mouse_y = -1;
+ if (button) { cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button = 0; }
+ if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; }
+ if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; }
+ is_closed = is_event = true;
+ }
+ } break;
+ case ConfigureNotify : {
+ while (XCheckWindowEvent(cimg::X11attr().display,window,StructureNotifyMask,&event)) {}
+ const unsigned int
+ nw = event.xconfigure.width,
+ nh = event.xconfigure.height;
+ const int
+ nx = event.xconfigure.x,
+ ny = event.xconfigure.y;
+ if (nw && nh && (nw!=window_width || nh!=window_height)) {
+ window_width = nw;
+ window_height = nh;
+ mouse_x = mouse_y = -1;
+ XResizeWindow(cimg::X11attr().display,window,window_width,window_height);
+ is_resized = is_event = true;
+ }
+ if (nx!=window_x || ny!=window_y) {
+ window_x = nx;
+ window_y = ny;
+ is_moved = is_event = true;
+ }
+ } break;
+ case Expose : {
+ while (XCheckWindowEvent(cimg::X11attr().display,window,ExposureMask,&event)) {}
+ _paint(false);
+ if (is_fullscreen) {
+ XWindowAttributes attr;
+ XGetWindowAttributes(cimg::X11attr().display, window, &attr);
+ while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False);
+ XSetInputFocus(cimg::X11attr().display, window, RevertToParent, CurrentTime);
+ }
+ } break;
+ case ButtonPress : {
+ do {
+ mouse_x = event.xmotion.x;
+ mouse_y = event.xmotion.y;
+ if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
+ switch (event.xbutton.button) {
+ case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=1; is_event = true; break;
+ case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=4; is_event = true; break;
+ case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=2; is_event = true; break;
+ }
+ } while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonPressMask,&event));
+ } break;
+ case ButtonRelease : {
+ do {
+ mouse_x = event.xmotion.x;
+ mouse_y = event.xmotion.y;
+ if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
+ switch (event.xbutton.button) {
+ case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~1U; is_event = true; break;
+ case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~4U; is_event = true; break;
+ case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~2U; is_event = true; break;
+ case 4 : ++wheel; is_event = true; break;
+ case 5 : --wheel; is_event = true; break;
+ }
+ } while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonReleaseMask,&event));
+ } break;
+ case KeyPress : {
+ char tmp;
+ KeySym ksym;
+ XLookupString(&event.xkey,&tmp,1,&ksym,0);
+ update_iskey((unsigned int)ksym,true);
+ if (key) cimg_std::memmove((void*)(keys+1),(void*)keys,512-1);
+ key = (unsigned int)ksym;
+ if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; }
+ is_event = true;
+ } break;
+ case KeyRelease : {
+ char tmp;
+ KeySym ksym;
+ XLookupString(&event.xkey,&tmp,1,&ksym,0);
+ update_iskey((unsigned int)ksym,false);
+ if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; }
+ if (released_key) cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1);
+ released_key = (unsigned int)ksym;
+ is_event = true;
+ } break;
+ case EnterNotify: {
+ while (XCheckWindowEvent(cimg::X11attr().display,window,EnterWindowMask,&event)) {}
+ mouse_x = event.xmotion.x;
+ mouse_y = event.xmotion.y;
+ if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
+ } break;
+ case LeaveNotify : {
+ while (XCheckWindowEvent(cimg::X11attr().display,window,LeaveWindowMask,&event)) {}
+ mouse_x = mouse_y =-1;
+ is_event = true;
+ } break;
+ case MotionNotify : {
+ while (XCheckWindowEvent(cimg::X11attr().display,window,PointerMotionMask,&event)) {}
+ mouse_x = event.xmotion.x;
+ mouse_y = event.xmotion.y;
+ if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
+ is_event = true;
+ } break;
+ }
+ }
+
+ static void* _events_thread(void *arg) {
+ arg = 0;
+ XEvent event;
+ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
+ for (;;) {
+ XLockDisplay(cimg::X11attr().display);
+ bool event_flag = XCheckTypedEvent(cimg::X11attr().display, ClientMessage, &event);
+ if (!event_flag) event_flag = XCheckMaskEvent(cimg::X11attr().display,
+ ExposureMask|StructureNotifyMask|ButtonPressMask|
+ KeyPressMask|PointerMotionMask|EnterWindowMask|LeaveWindowMask|
+ ButtonReleaseMask|KeyReleaseMask,&event);
+ if (event_flag) {
+ for (unsigned int i=0; i<cimg::X11attr().nb_wins; ++i)
+ if (!cimg::X11attr().wins[i]->is_closed && event.xany.window==cimg::X11attr().wins[i]->window)
+ cimg::X11attr().wins[i]->_handle_events(&event);
+ }
+ XUnlockDisplay(cimg::X11attr().display);
+ pthread_testcancel();
+ cimg::sleep(7);
+ }
+ return 0;
+ }
+
+ void _set_colormap(Colormap& colormap, const unsigned int dim) {
+ XColor palette[256];
+ switch (dim) {
+ case 1 : { // palette for greyscale images
+ for (unsigned int index=0; index<256; ++index) {
+ palette[index].pixel = index;
+ palette[index].red = palette[index].green = palette[index].blue = (unsigned short)(index<<8);
+ palette[index].flags = DoRed | DoGreen | DoBlue;
+ }
+ } break;
+ case 2 : { // palette for RG images
+ for (unsigned int index=0, r=8; r<256; r+=16)
+ for (unsigned int g=8; g<256; g+=16) {
+ palette[index].pixel = index;
+ palette[index].red = palette[index].blue = (unsigned short)(r<<8);
+ palette[index].green = (unsigned short)(g<<8);
+ palette[index++].flags = DoRed | DoGreen | DoBlue;
+ }
+ } break;
+ default : { // palette for RGB images
+ for (unsigned int index=0, r=16; r<256; r+=32)
+ for (unsigned int g=16; g<256; g+=32)
+ for (unsigned int b=32; b<256; b+=64) {
+ palette[index].pixel = index;
+ palette[index].red = (unsigned short)(r<<8);
+ palette[index].green = (unsigned short)(g<<8);
+ palette[index].blue = (unsigned short)(b<<8);
+ palette[index++].flags = DoRed | DoGreen | DoBlue;
+ }
+ }
+ }
+ XStoreColors(cimg::X11attr().display,colormap,palette,256);
+ }
+
+ void _map_window() {
+ XWindowAttributes attr;
+ XEvent event;
+ bool exposed = false, mapped = false;
+ XMapRaised(cimg::X11attr().display,window);
+ XSync(cimg::X11attr().display,False);
+ do {
+ XWindowEvent(cimg::X11attr().display,window,StructureNotifyMask | ExposureMask,&event);
+ switch (event.type) {
+ case MapNotify : mapped = true; break;
+ case Expose : exposed = true; break;
+ default : XSync(cimg::X11attr().display, False); cimg::sleep(10);
+ }
+ } while (!(exposed && mapped));
+ do {
+ XGetWindowAttributes(cimg::X11attr().display, window, &attr);
+ if (attr.map_state!=IsViewable) { XSync(cimg::X11attr().display,False); cimg::sleep(10); }
+ } while (attr.map_state != IsViewable);
+ window_x = attr.x;
+ window_y = attr.y;
+ }
+
+ void _paint(const bool wait_expose=true) {
+ if (!is_closed) {
+ if (wait_expose) {
+ static XEvent event;
+ event.xexpose.type = Expose;
+ event.xexpose.serial = 0;
+ event.xexpose.send_event = True;
+ event.xexpose.display = cimg::X11attr().display;
+ event.xexpose.window = window;
+ event.xexpose.x = 0;
+ event.xexpose.y = 0;
+ event.xexpose.width = dimx();
+ event.xexpose.height = dimy();
+ event.xexpose.count = 0;
+ XSendEvent(cimg::X11attr().display, window, False, 0, &event);
+ } else {
+#ifdef cimg_use_xshm
+ if (shminfo) XShmPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height,False);
+ else
+#endif
+ XPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height);
+ XSync(cimg::X11attr().display, False);
+ }
+ }
+ }
+
+ template<typename T>
+ void _resize(T foo, const unsigned int ndimx, const unsigned int ndimy, const bool redraw) {
+ foo = 0;
+#ifdef cimg_use_xshm
+ if (shminfo) {
+ XShmSegmentInfo *nshminfo = new XShmSegmentInfo;
+ XImage *nimage = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
+ cimg::X11attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy);
+ if (!nimage) {
+ delete nshminfo;
+ return;
+ } else {
+ nshminfo->shmid = shmget(IPC_PRIVATE, ndimx*ndimy*sizeof(T), IPC_CREAT | 0777);
+ if (nshminfo->shmid==-1) {
+ XDestroyImage(nimage);
+ delete nshminfo;
+ return;
+ } else {
+ nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0);
+ if (nshminfo->shmaddr==(char*)-1) {
+ shmctl(nshminfo->shmid,IPC_RMID,0);
+ XDestroyImage(nimage);
+ delete nshminfo;
+ return;
+ } else {
+ nshminfo->readOnly = False;
+ cimg::X11attr().shm_enabled = true;
+ XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+ XShmAttach(cimg::X11attr().display, nshminfo);
+ XSync(cimg::X11attr().display, False);
+ XSetErrorHandler(oldXErrorHandler);
+ if (!cimg::X11attr().shm_enabled) {
+ shmdt(nshminfo->shmaddr);
+ shmctl(nshminfo->shmid,IPC_RMID,0);
+ XDestroyImage(nimage);
+ delete nshminfo;
+ return;
+ } else {
+ T *const ndata = (T*)nimage->data;
+ if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy);
+ else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
+ XShmDetach(cimg::X11attr().display, shminfo);
+ XDestroyImage(image);
+ shmdt(shminfo->shmaddr);
+ shmctl(shminfo->shmid,IPC_RMID,0);
+ delete shminfo;
+ shminfo = nshminfo;
+ image = nimage;
+ data = (void*)ndata;
+ }
+ }
+ }
+ }
+ } else
+#endif
+ {
+ T *ndata = (T*)cimg_std::malloc(ndimx*ndimy*sizeof(T));
+ if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy);
+ else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
+ data = (void*)ndata;
+ XDestroyImage(image);
+ image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
+ cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,ndimx,ndimy,8,0);
+ }
+ }
+
+ void _init_fullscreen() {
+ background_window = 0;
+ if (is_fullscreen && !is_closed) {
+#ifdef cimg_use_xrandr
+ int foo;
+ if (XRRQueryExtension(cimg::X11attr().display,&foo,&foo)) {
+ XRRRotations(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display), &cimg::X11attr().curr_rotation);
+ if (!cimg::X11attr().resolutions) {
+ cimg::X11attr().resolutions = XRRSizes(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display),&foo);
+ cimg::X11attr().nb_resolutions = (unsigned int)foo;
+ }
+ if (cimg::X11attr().resolutions) {
+ cimg::X11attr().curr_resolution = 0;
+ for (unsigned int i=0; i<cimg::X11attr().nb_resolutions; ++i) {
+ const unsigned int
+ nw = (unsigned int)(cimg::X11attr().resolutions[i].width),
+ nh = (unsigned int)(cimg::X11attr().resolutions[i].height);
+ if (nw>=width && nh>=height &&
+ nw<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width) &&
+ nh<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height))
+ cimg::X11attr().curr_resolution = i;
+ }
+ if (cimg::X11attr().curr_resolution>0) {
+ XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display));
+ XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display),
+ cimg::X11attr().curr_resolution, cimg::X11attr().curr_rotation, CurrentTime);
+ XRRFreeScreenConfigInfo(config);
+ XSync(cimg::X11attr().display, False);
+ }
+ }
+ }
+ if (!cimg::X11attr().resolutions)
+ cimg::warn("CImgDisplay::_create_window() : Xrandr extension is not supported by the X server.");
+#endif
+ const unsigned int sx = screen_dimx(), sy = screen_dimy();
+ XSetWindowAttributes winattr;
+ winattr.override_redirect = True;
+ if (sx!=width || sy!=height) {
+ background_window = XCreateWindow(cimg::X11attr().display,
+ RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),0,0,
+ sx,sy,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+ const unsigned int bufsize = sx*sy*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
+ void *background_data = cimg_std::malloc(bufsize);
+ cimg_std::memset(background_data,0,bufsize);
+ XImage *background_image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
+ cimg::X11attr().nb_bits,ZPixmap,0,(char*)background_data,sx,sy,8,0);
+ XEvent event;
+ XSelectInput(cimg::X11attr().display,background_window,StructureNotifyMask);
+ XMapRaised(cimg::X11attr().display,background_window);
+ do XWindowEvent(cimg::X11attr().display,background_window,StructureNotifyMask,&event);
+ while (event.type!=MapNotify);
+#ifdef cimg_use_xshm
+ if (shminfo) XShmPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy,False);
+ else
+#endif
+ XPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy);
+ XWindowAttributes attr;
+ XGetWindowAttributes(cimg::X11attr().display, background_window, &attr);
+ while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False);
+ XDestroyImage(background_image);
+ }
+ }
+ }
+
+ void _desinit_fullscreen() {
+ if (is_fullscreen) {
+ XUngrabKeyboard(cimg::X11attr().display,CurrentTime);
+#ifdef cimg_use_xrandr
+ if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution) {
+ XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display));
+ XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display),
+ 0, cimg::X11attr().curr_rotation, CurrentTime);
+ XRRFreeScreenConfigInfo(config);
+ XSync(cimg::X11attr().display, False);
+ cimg::X11attr().curr_resolution = 0;
+ }
+#endif
+ if (background_window) XDestroyWindow(cimg::X11attr().display,background_window);
+ background_window = 0;
+ is_fullscreen = false;
+ }
+ }
+
+ static int _assign_xshm(Display *dpy, XErrorEvent *error) {
+ dpy = 0; error = 0;
+ cimg::X11attr().shm_enabled = false;
+ return 0;
+ }
+
+ void _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+
+ // Allocate space for window title
+ const int s = cimg::strlen(ptitle)+1;
+ char *tmp_title = s?new char[s]:0;
+ if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));
+
+ // Destroy previous display window if existing
+ if (!is_empty()) assign();
+
+ // Open X11 display if necessary.
+ if (!cimg::X11attr().display) {
+ static bool xinit_threads = false;
+ if (!xinit_threads) { XInitThreads(); xinit_threads = true; }
+ cimg::X11attr().nb_wins = 0;
+ cimg::X11attr().display = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0"));
+ if (!cimg::X11attr().display)
+ throw CImgDisplayException("CImgDisplay::_create_window() : Can't open X11 display");
+ cimg::X11attr().nb_bits = DefaultDepth(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display));
+ if (cimg::X11attr().nb_bits!=8 && cimg::X11attr().nb_bits!=16 && cimg::X11attr().nb_bits!=24 && cimg::X11attr().nb_bits!=32)
+ throw CImgDisplayException("CImgDisplay::_create_window() : %u bits mode is not supported "
+ "(only 8, 16, 24 and 32 bits modes are supported)",cimg::X11attr().nb_bits);
+ cimg::X11attr().gc = new GC;
+ *cimg::X11attr().gc = DefaultGC(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
+ Visual *visual = DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
+ XVisualInfo vtemplate;
+ vtemplate.visualid = XVisualIDFromVisual(visual);
+ int nb_visuals;
+ XVisualInfo *vinfo = XGetVisualInfo(cimg::X11attr().display,VisualIDMask,&vtemplate,&nb_visuals);
+ if (vinfo && vinfo->red_mask<vinfo->blue_mask) cimg::X11attr().blue_first = true;
+ cimg::X11attr().byte_order = ImageByteOrder(cimg::X11attr().display);
+ XFree(vinfo);
+ XLockDisplay(cimg::X11attr().display);
+ cimg::X11attr().event_thread = new pthread_t;
+ pthread_create(cimg::X11attr().event_thread,0,_events_thread,0);
+ } else XLockDisplay(cimg::X11attr().display);
+
+ // Set display variables
+ width = cimg::min(dimw,(unsigned int)screen_dimx());
+ height = cimg::min(dimh,(unsigned int)screen_dimy());
+ normalization = normalization_type<4?normalization_type:3;
+ is_fullscreen = fullscreen_flag;
+ window_x = window_y = 0;
+ is_closed = closed_flag;
+ title = tmp_title;
+ flush();
+
+ // Create X11 window and palette (if 8bits display)
+ if (is_fullscreen) {
+ if (!is_closed) _init_fullscreen();
+ const unsigned int sx = screen_dimx(), sy = screen_dimy();
+ XSetWindowAttributes winattr;
+ winattr.override_redirect = True;
+ window = XCreateWindow(cimg::X11attr().display,
+ RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
+ (sx-width)/2,(sy-height)/2,
+ width,height,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+ } else
+ window = XCreateSimpleWindow(cimg::X11attr().display,
+ RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
+ 0,0,width,height,2,0,0x0L);
+ XStoreName(cimg::X11attr().display,window,title?title:" ");
+ if (cimg::X11attr().nb_bits==8) {
+ colormap = XCreateColormap(cimg::X11attr().display,window,DefaultVisual(cimg::X11attr().display,
+ DefaultScreen(cimg::X11attr().display)),AllocAll);
+ _set_colormap(colormap,3);
+ XSetWindowColormap(cimg::X11attr().display,window,colormap);
+ }
+ window_width = width;
+ window_height = height;
+
+ // Create XImage
+ const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
+#ifdef cimg_use_xshm
+ shminfo = 0;
+ if (XShmQueryExtension(cimg::X11attr().display)) {
+ shminfo = new XShmSegmentInfo;
+ image = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
+ cimg::X11attr().nb_bits,ZPixmap,0,shminfo,width,height);
+ if (!image) {
+ delete shminfo;
+ shminfo = 0;
+ } else {
+ shminfo->shmid = shmget(IPC_PRIVATE, bufsize, IPC_CREAT | 0777);
+ if (shminfo->shmid==-1) {
+ XDestroyImage(image);
+ delete shminfo;
+ shminfo = 0;
+ } else {
+ shminfo->shmaddr = image->data = (char*)(data = shmat(shminfo->shmid,0,0));
+ if (shminfo->shmaddr==(char*)-1) {
+ shmctl(shminfo->shmid,IPC_RMID,0);
+ XDestroyImage(image);
+ delete shminfo;
+ shminfo = 0;
+ } else {
+ shminfo->readOnly = False;
+ cimg::X11attr().shm_enabled = true;
+ XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+ XShmAttach(cimg::X11attr().display, shminfo);
+ XSync(cimg::X11attr().display, False);
+ XSetErrorHandler(oldXErrorHandler);
+ if (!cimg::X11attr().shm_enabled) {
+ shmdt(shminfo->shmaddr);
+ shmctl(shminfo->shmid,IPC_RMID,0);
+ XDestroyImage(image);
+ delete shminfo;
+ shminfo = 0;
+ }
+ }
+ }
+ }
+ }
+ if (!shminfo)
+#endif
+ {
+ data = cimg_std::malloc(bufsize);
+ image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
+ cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,width,height,8,0);
+ }
+
+ wm_delete_window = XInternAtom(cimg::X11attr().display, "WM_DELETE_WINDOW", False);
+ wm_delete_protocol = XInternAtom(cimg::X11attr().display, "WM_PROTOCOLS", False);
+ XSetWMProtocols(cimg::X11attr().display, window, &wm_delete_window, 1);
+ XSelectInput(cimg::X11attr().display,window,
+ ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask |
+ EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask);
+ if (is_fullscreen) XGrabKeyboard(cimg::X11attr().display, window, True, GrabModeAsync, GrabModeAsync, CurrentTime);
+ cimg::X11attr().wins[cimg::X11attr().nb_wins++]=this;
+ if (!is_closed) _map_window(); else { window_x = window_y = cimg::type<int>::min(); }
+ XUnlockDisplay(cimg::X11attr().display);
+ }
+
+ CImgDisplay& assign() {
+ if (is_empty()) return *this;
+ XLockDisplay(cimg::X11attr().display);
+
+ // Remove display window from event thread list.
+ unsigned int i;
+ for (i = 0; i<cimg::X11attr().nb_wins && cimg::X11attr().wins[i]!=this; ++i) {}
+ for (; i<cimg::X11attr().nb_wins-1; ++i) cimg::X11attr().wins[i] = cimg::X11attr().wins[i+1];
+ --cimg::X11attr().nb_wins;
+
+ // Destroy window, image, colormap and title.
+ if (is_fullscreen && !is_closed) _desinit_fullscreen();
+ XDestroyWindow(cimg::X11attr().display,window);
+ window = 0;
+#ifdef cimg_use_xshm
+ if (shminfo) {
+ XShmDetach(cimg::X11attr().display, shminfo);
+ XDestroyImage(image);
+ shmdt(shminfo->shmaddr);
+ shmctl(shminfo->shmid,IPC_RMID,0);
+ delete shminfo;
+ shminfo = 0;
+ } else
+#endif
+ XDestroyImage(image);
+ data = 0; image = 0;
+ if (cimg::X11attr().nb_bits==8) XFreeColormap(cimg::X11attr().display,colormap);
+ colormap = 0;
+ XSync(cimg::X11attr().display, False);
+
+ // Reset display variables
+ if (title) delete[] title;
+ width = height = normalization = window_width = window_height = 0;
+ window_x = window_y = 0;
+ is_fullscreen = false;
+ is_closed = true;
+ min = max = 0;
+ title = 0;
+ flush();
+
+ // End event thread and close display if necessary
+ XUnlockDisplay(cimg::X11attr().display);
+
+ /* The code below was used to close the X11 display when not used anymore,
+ unfortunately, since the latest Xorg versions, it randomely hangs, so
+ I prefer to remove it. A fix would be needed anyway.
+
+ if (!cimg::X11attr().nb_wins) {
+ // Kill event thread
+ pthread_cancel(*cimg::X11attr().event_thread);
+ XUnlockDisplay(cimg::X11attr().display);
+ pthread_join(*cimg::X11attr().event_thread,0);
+ delete cimg::X11attr().event_thread;
+ cimg::X11attr().event_thread = 0;
+ XCloseDisplay(cimg::X11attr().display);
+ cimg::X11attr().display = 0;
+ delete cimg::X11attr().gc;
+ cimg::X11attr().gc = 0;
+ } else XUnlockDisplay(cimg::X11attr().display);
+ */
+ return *this;
+ }
+
+ CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!dimw || !dimh) return assign();
+ _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
+ min = max = 0;
+ cimg_std::memset(data,0,(cimg::X11attr().nb_bits==8?sizeof(unsigned char):
+ (cimg::X11attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))*width*height);
+ return paint();
+ }
+
+ template<typename T>
+ CImgDisplay& assign(const CImg<T>& img, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!img) return assign();
+ CImg<T> tmp;
+ const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
+ if (normalization==2) min = (float)nimg.minmax(max);
+ return render(nimg).paint();
+ }
+
+ template<typename T>
+ CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!list) return assign();
+ CImg<T> tmp;
+ const CImg<T> img = list.get_append('x','p'),
+ &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
+ if (normalization==2) min = (float)nimg.minmax(max);
+ return render(nimg).paint();
+ }
+
+ CImgDisplay& assign(const CImgDisplay& win) {
+ if (!win) return assign();
+ _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
+ cimg_std::memcpy(data,win.data,(cimg::X11attr().nb_bits==8?sizeof(unsigned char):
+ cimg::X11attr().nb_bits==16?sizeof(unsigned short):
+ sizeof(unsigned int))*width*height);
+ return paint();
+ }
+
+ CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
+ if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+ if (is_empty()) return assign(nwidth,nheight);
+ const unsigned int
+ tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100),
+ tmpdimy = (nheight>0)?nheight:(-nheight*height/100),
+ dimx = tmpdimx?tmpdimx:1,
+ dimy = tmpdimy?tmpdimy:1;
+ XLockDisplay(cimg::X11attr().display);
+ if (window_width!=dimx || window_height!=dimy) XResizeWindow(cimg::X11attr().display,window,dimx,dimy);
+ if (width!=dimx || height!=dimy) switch (cimg::X11attr().nb_bits) {
+ case 8 : { unsigned char foo = 0; _resize(foo,dimx,dimy,redraw); } break;
+ case 16 : { unsigned short foo = 0; _resize(foo,dimx,dimy,redraw); } break;
+ default : { unsigned int foo = 0; _resize(foo,dimx,dimy,redraw); }
+ }
+ window_width = width = dimx; window_height = height = dimy;
+ is_resized = false;
+ XUnlockDisplay(cimg::X11attr().display);
+ if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
+ if (redraw) return paint();
+ return *this;
+ }
+
+ CImgDisplay& toggle_fullscreen(const bool redraw=true) {
+ if (is_empty()) return *this;
+ if (redraw) {
+ const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
+ void *odata = cimg_std::malloc(bufsize);
+ cimg_std::memcpy(odata,data,bufsize);
+ assign(width,height,title,normalization,!is_fullscreen,false);
+ cimg_std::memcpy(data,odata,bufsize);
+ cimg_std::free(odata);
+ return paint(false);
+ }
+ return assign(width,height,title,normalization,!is_fullscreen,false);
+ }
+
+ CImgDisplay& show() {
+ if (!is_empty() && is_closed) {
+ XLockDisplay(cimg::X11attr().display);
+ if (is_fullscreen) _init_fullscreen();
+ _map_window();
+ is_closed = false;
+ XUnlockDisplay(cimg::X11attr().display);
+ return paint();
+ }
+ return *this;
+ }
+
+ CImgDisplay& close() {
+ if (!is_empty() && !is_closed) {
+ XLockDisplay(cimg::X11attr().display);
+ if (is_fullscreen) _desinit_fullscreen();
+ XUnmapWindow(cimg::X11attr().display,window);
+ window_x = window_y = -1;
+ is_closed = true;
+ XUnlockDisplay(cimg::X11attr().display);
+ }
+ return *this;
+ }
+
+ CImgDisplay& move(const int posx, const int posy) {
+ if (is_empty()) return *this;
+ show();
+ XLockDisplay(cimg::X11attr().display);
+ XMoveWindow(cimg::X11attr().display,window,posx,posy);
+ window_x = posx; window_y = posy;
+ is_moved = false;
+ XUnlockDisplay(cimg::X11attr().display);
+ return paint();
+ }
+
+ CImgDisplay& show_mouse() {
+ if (is_empty()) return *this;
+ XLockDisplay(cimg::X11attr().display);
+ XDefineCursor(cimg::X11attr().display,window,None);
+ XUnlockDisplay(cimg::X11attr().display);
+ return *this;
+ }
+
+ CImgDisplay& hide_mouse() {
+ if (is_empty()) return *this;
+ XLockDisplay(cimg::X11attr().display);
+ const char pix_data[8] = { 0 };
+ XColor col;
+ col.red = col.green = col.blue = 0;
+ Pixmap pix = XCreateBitmapFromData(cimg::X11attr().display,window,pix_data,8,8);
+ Cursor cur = XCreatePixmapCursor(cimg::X11attr().display,pix,pix,&col,&col,0,0);
+ XFreePixmap(cimg::X11attr().display,pix);
+ XDefineCursor(cimg::X11attr().display,window,cur);
+ XUnlockDisplay(cimg::X11attr().display);
+ return *this;
+ }
+
+ CImgDisplay& set_mouse(const int posx, const int posy) {
+ if (is_empty() || is_closed) return *this;
+ XLockDisplay(cimg::X11attr().display);
+ XWarpPointer(cimg::X11attr().display,None,window,0,0,0,0,posx,posy);
+ mouse_x = posx; mouse_y = posy;
+ is_moved = false;
+ XSync(cimg::X11attr().display, False);
+ XUnlockDisplay(cimg::X11attr().display);
+ return *this;
+ }
+
+ CImgDisplay& set_title(const char *format, ...) {
+ if (is_empty()) return *this;
+ char tmp[1024] = {0};
+ va_list ap;
+ va_start(ap, format);
+ cimg_std::vsprintf(tmp,format,ap);
+ va_end(ap);
+ if (title) delete[] title;
+ const int s = cimg::strlen(tmp)+1;
+ title = new char[s];
+ cimg_std::memcpy(title,tmp,s*sizeof(char));
+ XLockDisplay(cimg::X11attr().display);
+ XStoreName(cimg::X11attr().display,window,tmp);
+ XUnlockDisplay(cimg::X11attr().display);
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& display(const CImg<T>& img) {
+ if (img.is_empty())
+ throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image.");
+ if (is_empty()) assign(img.width,img.height);
+ return render(img).paint(false);
+ }
+
+ CImgDisplay& paint(const bool wait_expose=true) {
+ if (is_empty()) return *this;
+ XLockDisplay(cimg::X11attr().display);
+ _paint(wait_expose);
+ XUnlockDisplay(cimg::X11attr().display);
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& render(const CImg<T>& img, const bool flag8=false) {
+ if (is_empty()) return *this;
+ if (!img)
+ throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
+ img.width,img.height,img.depth,img.dim,img.data);
+ if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ if (cimg::X11attr().nb_bits==8 && (img.width!=width || img.height!=height)) return render(img.get_resize(width,height,1,-100,1));
+ if (cimg::X11attr().nb_bits==8 && !flag8 && img.dim==3) return render(img.get_RGBtoLUT(true),true);
+
+ const T
+ *data1 = img.data,
+ *data2 = (img.dim>1)?img.ptr(0,0,0,1):data1,
+ *data3 = (img.dim>2)?img.ptr(0,0,0,2):data1;
+
+ if (cimg::X11attr().blue_first) cimg::swap(data1,data3);
+ XLockDisplay(cimg::X11attr().display);
+
+ if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
+ min = max = 0;
+ switch (cimg::X11attr().nb_bits) {
+ case 8 : { // 256 color palette, no normalization
+ _set_colormap(colormap,img.dim);
+ unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height];
+ unsigned char *ptrd = (unsigned char*)ndata;
+ switch (img.dim) {
+ case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) (*ptrd++) = (unsigned char)*(data1++);
+ break;
+ case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++);
+ (*ptrd++) = (R&0xf0) | (G>>4);
+ } break;
+ default : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++), B = (unsigned char)*(data3++);
+ (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
+ }
+ }
+ if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; }
+ } break;
+ case 16 : { // 16 bits colors, no normalization
+ unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height];
+ unsigned char *ptrd = (unsigned char*)ndata;
+ const unsigned int M = 248;
+ switch (img.dim) {
+ case 1 :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++), G = val>>2;
+ *(ptrd++) = (val&M) | (G>>3);
+ *(ptrd++) = (G<<5) | (G>>1);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++), G = val>>2;
+ *(ptrd++) = (G<<5) | (G>>1);
+ *(ptrd++) = (val&M) | (G>>3);
+ }
+ break;
+ case 2 :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+ *(ptrd++) = (G<<5);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ *(ptrd++) = (G<<5);
+ *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+ }
+ break;
+ default :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+ *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
+ *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+ }
+ }
+ if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; }
+ } break;
+ default : { // 24 bits colors, no normalization
+ unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height];
+ if (sizeof(int)==4) { // 32 bits int uses optimized version
+ unsigned int *ptrd = ndata;
+ switch (img.dim) {
+ case 1 :
+ if (cimg::X11attr().byte_order==cimg::endianness())
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++);
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }
+ else
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++)<<8;
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }
+ break;
+ case 2 :
+ if (cimg::X11attr().byte_order==cimg::endianness())
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
+ else
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
+ break;
+ default :
+ if (cimg::X11attr().byte_order==cimg::endianness())
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
+ else
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
+ }
+ } else {
+ unsigned char *ptrd = (unsigned char*)ndata;
+ switch (img.dim) {
+ case 1 :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ *(ptrd++) = 0;
+ *(ptrd++) = (unsigned char)*(data1++);
+ *(ptrd++) = 0;
+ *(ptrd++) = 0;
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ *(ptrd++) = 0;
+ *(ptrd++) = 0;
+ *(ptrd++) = (unsigned char)*(data1++);
+ *(ptrd++) = 0;
+ }
+ break;
+ case 2 :
+ if (cimg::X11attr().byte_order) cimg::swap(data1,data2);
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ *(ptrd++) = 0;
+ *(ptrd++) = (unsigned char)*(data2++);
+ *(ptrd++) = (unsigned char)*(data1++);
+ *(ptrd++) = 0;
+ }
+ break;
+ default :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ *(ptrd++) = 0;
+ *(ptrd++) = (unsigned char)*(data1++);
+ *(ptrd++) = (unsigned char)*(data2++);
+ *(ptrd++) = (unsigned char)*(data3++);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ *(ptrd++) = (unsigned char)*(data3++);
+ *(ptrd++) = (unsigned char)*(data2++);
+ *(ptrd++) = (unsigned char)*(data1++);
+ *(ptrd++) = 0;
+ }
+ }
+ }
+ if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; }
+ }
+ };
+ } else {
+ if (normalization==3) {
+ if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
+ else { min = (float)cimg::type<T>::min(); max = (float)cimg::type<T>::max(); }
+ } else if ((min>max) || normalization==1) min = (float)img.minmax(max);
+ const float delta = max-min, mm = delta?delta:1.0f;
+ switch (cimg::X11attr().nb_bits) {
+ case 8 : { // 256 color palette, with normalization
+ _set_colormap(colormap,img.dim);
+ unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height];
+ unsigned char *ptrd = (unsigned char*)ndata;
+ switch (img.dim) {
+ case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char R = (unsigned char)(255*(*(data1++)-min)/mm);
+ *(ptrd++) = R;
+ } break;
+ case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)(255*(*(data1++)-min)/mm),
+ G = (unsigned char)(255*(*(data2++)-min)/mm);
+ (*ptrd++) = (R&0xf0) | (G>>4);
+ } break;
+ default :
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)(255*(*(data1++)-min)/mm),
+ G = (unsigned char)(255*(*(data2++)-min)/mm),
+ B = (unsigned char)(255*(*(data3++)-min)/mm);
+ *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
+ }
+ }
+ if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; }
+ } break;
+ case 16 : { // 16 bits colors, with normalization
+ unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height];
+ unsigned char *ptrd = (unsigned char*)ndata;
+ const unsigned int M = 248;
+ switch (img.dim) {
+ case 1 :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2;
+ *(ptrd++) = (val&M) | (G>>3);
+ *(ptrd++) = (G<<5) | (val>>3);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2;
+ *(ptrd++) = (G<<5) | (val>>3);
+ *(ptrd++) = (val&M) | (G>>3);
+ }
+ break;
+ case 2 :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
+ *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
+ *(ptrd++) = (G<<5);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
+ *(ptrd++) = (G<<5);
+ *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
+ }
+ break;
+ default :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
+ *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
+ *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
+ *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3);
+ *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
+ }
+ }
+ if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; }
+ } break;
+ default : { // 24 bits colors, with normalization
+ unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height];
+ if (sizeof(int)==4) { // 32 bits int uses optimized version
+ unsigned int *ptrd = ndata;
+ switch (img.dim) {
+ case 1 :
+ if (cimg::X11attr().byte_order==cimg::endianness())
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }
+ else
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
+ *(ptrd++) = (val<<24) | (val<<16) | (val<<8);
+ }
+ break;
+ case 2 :
+ if (cimg::X11attr().byte_order==cimg::endianness())
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)(255*(*(data1++)-min)/mm)<<16) |
+ ((unsigned char)(255*(*(data2++)-min)/mm)<<8);
+ else
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)(255*(*(data2++)-min)/mm)<<16) |
+ ((unsigned char)(255*(*(data1++)-min)/mm)<<8);
+ break;
+ default :
+ if (cimg::X11attr().byte_order==cimg::endianness())
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)(255*(*(data1++)-min)/mm)<<16) |
+ ((unsigned char)(255*(*(data2++)-min)/mm)<<8) |
+ (unsigned char)(255*(*(data3++)-min)/mm);
+ else
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)(255*(*(data3++)-min)/mm)<<24) |
+ ((unsigned char)(255*(*(data2++)-min)/mm)<<16) |
+ ((unsigned char)(255*(*(data1++)-min)/mm)<<8);
+ }
+ } else {
+ unsigned char *ptrd = (unsigned char*)ndata;
+ switch (img.dim) {
+ case 1 :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
+ (*ptrd++) = 0;
+ (*ptrd++) = val;
+ (*ptrd++) = val;
+ (*ptrd++) = val;
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
+ (*ptrd++) = val;
+ (*ptrd++) = val;
+ (*ptrd++) = val;
+ (*ptrd++) = 0;
+ }
+ break;
+ case 2 :
+ if (cimg::X11attr().byte_order) cimg::swap(data1,data2);
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ (*ptrd++) = 0;
+ (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
+ (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
+ (*ptrd++) = 0;
+ }
+ break;
+ default :
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ (*ptrd++) = 0;
+ (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
+ (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
+ (*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ (*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm);
+ (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
+ (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
+ (*ptrd++) = 0;
+ }
+ }
+ }
+ if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; }
+ }
+ }
+ }
+ XUnlockDisplay(cimg::X11attr().display);
+ return *this;
+ }
+
+ template<typename T>
+ const CImgDisplay& snapshot(CImg<T>& img) const {
+ if (is_empty()) img.assign();
+ else {
+ img.assign(width,height,1,3);
+ T
+ *data1 = img.ptr(0,0,0,0),
+ *data2 = img.ptr(0,0,0,1),
+ *data3 = img.ptr(0,0,0,2);
+ if (cimg::X11attr().blue_first) cimg::swap(data1,data3);
+ switch (cimg::X11attr().nb_bits) {
+ case 8 : {
+ unsigned char *ptrs = (unsigned char*)data;
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = *(ptrs++);
+ *(data1++) = val&0xe0;
+ *(data2++) = (val&0x1c)<<3;
+ *(data3++) = val<<6;
+ }
+ } break;
+ case 16 : {
+ unsigned char *ptrs = (unsigned char*)data;
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val0 = *(ptrs++), val1 = *(ptrs++);
+ *(data1++) = val0&0xf8;
+ *(data2++) = (val0<<5) | ((val1&0xe0)>>5);
+ *(data3++) = val1<<3;
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned short val0 = *(ptrs++), val1 = *(ptrs++);
+ *(data1++) = val1&0xf8;
+ *(data2++) = (val1<<5) | ((val0&0xe0)>>5);
+ *(data3++) = val0<<3;
+ }
+ } break;
+ default : {
+ unsigned char *ptrs = (unsigned char*)data;
+ if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ ++ptrs;
+ *(data1++) = *(ptrs++);
+ *(data2++) = *(ptrs++);
+ *(data3++) = *(ptrs++);
+ } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ *(data3++) = *(ptrs++);
+ *(data2++) = *(ptrs++);
+ *(data1++) = *(ptrs++);
+ ++ptrs;
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ // Windows-based display
+ //-----------------------
+#elif cimg_display==2
+ CLIENTCREATESTRUCT ccs;
+ BITMAPINFO bmi;
+ unsigned int *data;
+ DEVMODE curr_mode;
+ HWND window;
+ HWND background_window;
+ HDC hdc;
+ HANDLE thread;
+ HANDLE created;
+ HANDLE mutex;
+ bool mouse_tracking;
+ bool visible_cursor;
+
+ static int screen_dimx() {
+ DEVMODE mode;
+ mode.dmSize = sizeof(DEVMODE);
+ mode.dmDriverExtra = 0;
+ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+ return mode.dmPelsWidth;
+ }
+
+ static int screen_dimy() {
+ DEVMODE mode;
+ mode.dmSize = sizeof(DEVMODE);
+ mode.dmDriverExtra = 0;
+ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+ return mode.dmPelsHeight;
+ }
+
+ static void wait_all() {
+ WaitForSingleObject(cimg::Win32attr().wait_event,INFINITE);
+ }
+
+ static LRESULT APIENTRY _handle_events(HWND window,UINT msg,WPARAM wParam,LPARAM lParam) {
+#ifdef _WIN64
+ CImgDisplay* disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA);
+#else
+ CImgDisplay* disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA);
+#endif
+ MSG st_msg;
+
+ switch (msg) {
+ case WM_CLOSE :
+ disp->mouse_x = disp->mouse_y = -1;
+ disp->window_x = disp->window_y = 0;
+ if (disp->button) {
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button = 0;
+ }
+ if (disp->key) {
+ cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
+ disp->key = 0;
+ }
+ if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
+ disp->is_closed = true;
+ ReleaseMutex(disp->mutex);
+ ShowWindow(disp->window,SW_HIDE);
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ return 0;
+ case WM_SIZE : {
+ while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
+ WaitForSingleObject(disp->mutex,INFINITE);
+ const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam);
+ if (nw && nh && (nw!=disp->width || nh!=disp->height)) {
+ disp->window_width = nw;
+ disp->window_height = nh;
+ disp->mouse_x = disp->mouse_y = -1;
+ disp->is_resized = disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ }
+ ReleaseMutex(disp->mutex);
+ } break;
+ case WM_MOVE : {
+ while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
+ WaitForSingleObject(disp->mutex,INFINITE);
+ const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam));
+ if (nx!=disp->window_x || ny!=disp->window_y) {
+ disp->window_x = nx;
+ disp->window_y = ny;
+ disp->is_moved = disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ }
+ ReleaseMutex(disp->mutex);
+ } break;
+ case WM_PAINT :
+ disp->paint();
+ break;
+ case WM_KEYDOWN :
+ disp->update_iskey((unsigned int)wParam,true);
+ if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
+ disp->key = (unsigned int)wParam;
+ if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case WM_MOUSEMOVE : {
+ while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {}
+ disp->mouse_x = LOWORD(lParam);
+ disp->mouse_y = HIWORD(lParam);
+#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT)
+ if (!disp->mouse_tracking) {
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(TRACKMOUSEEVENT);
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = disp->window;
+ if (TrackMouseEvent(&tme)) disp->mouse_tracking = true;
+ }
+#endif
+ if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy())
+ disp->mouse_x = disp->mouse_y = -1;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ } break;
+ case WM_MOUSELEAVE : {
+ disp->mouse_x = disp->mouse_y = -1;
+ disp->mouse_tracking = false;
+ } break;
+ case WM_LBUTTONDOWN :
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button|=1U;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case WM_RBUTTONDOWN :
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button|=2U;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case WM_MBUTTONDOWN :
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button|=4U;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case 0x020A : // WM_MOUSEWHEEL:
+ disp->wheel+=(int)((short)HIWORD(wParam))/120;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ case WM_KEYUP :
+ disp->update_iskey((unsigned int)wParam,false);
+ if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; }
+ if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1);
+ disp->released_key = (unsigned int)wParam;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case WM_LBUTTONUP :
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button&=~1U;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case WM_RBUTTONUP :
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button&=~2U;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case WM_MBUTTONUP :
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button&=~4U;
+ disp->is_event = true;
+ SetEvent(cimg::Win32attr().wait_event);
+ break;
+ case WM_SETCURSOR :
+ if (disp->visible_cursor) ShowCursor(TRUE);
+ else ShowCursor(FALSE);
+ break;
+ }
+ return DefWindowProc(window,msg,wParam,lParam);
+ }
+
+ static DWORD WINAPI _events_thread(void* arg) {
+ CImgDisplay *disp = (CImgDisplay*)(((void**)arg)[0]);
+ const char *title = (const char*)(((void**)arg)[1]);
+ MSG msg;
+ delete[] (void**)arg;
+ disp->bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ disp->bmi.bmiHeader.biWidth = disp->width;
+ disp->bmi.bmiHeader.biHeight = -(int)disp->height;
+ disp->bmi.bmiHeader.biPlanes = 1;
+ disp->bmi.bmiHeader.biBitCount = 32;
+ disp->bmi.bmiHeader.biCompression = BI_RGB;
+ disp->bmi.bmiHeader.biSizeImage = 0;
+ disp->bmi.bmiHeader.biXPelsPerMeter = 1;
+ disp->bmi.bmiHeader.biYPelsPerMeter = 1;
+ disp->bmi.bmiHeader.biClrUsed = 0;
+ disp->bmi.bmiHeader.biClrImportant = 0;
+ disp->data = new unsigned int[disp->width*disp->height];
+ if (!disp->is_fullscreen) { // Normal window
+ RECT rect;
+ rect.left = rect.top = 0; rect.right = disp->width-1; rect.bottom = disp->height-1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int border1 = (rect.right-rect.left+1-disp->width)/2, border2 = rect.bottom-rect.top+1-disp->height-border1;
+ disp->window = CreateWindowA("MDICLIENT",title?title:" ",
+ WS_OVERLAPPEDWINDOW | (disp->is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT,
+ disp->width + 2*border1, disp->height + border1 + border2,
+ 0,0,0,&(disp->ccs));
+ if (!disp->is_closed) {
+ GetWindowRect(disp->window,&rect);
+ disp->window_x = rect.left + border1;
+ disp->window_y = rect.top + border2;
+ } else disp->window_x = disp->window_y = 0;
+ } else { // Fullscreen window
+ const unsigned int sx = screen_dimx(), sy = screen_dimy();
+ disp->window = CreateWindowA("MDICLIENT",title?title:" ",
+ WS_POPUP | (disp->is_closed?0:WS_VISIBLE), (sx-disp->width)/2, (sy-disp->height)/2,
+ disp->width,disp->height,0,0,0,&(disp->ccs));
+ disp->window_x = disp->window_y = 0;
+ }
+ SetForegroundWindow(disp->window);
+ disp->hdc = GetDC(disp->window);
+ disp->window_width = disp->width;
+ disp->window_height = disp->height;
+ disp->flush();
+#ifdef _WIN64
+ SetWindowLongPtr(disp->window,GWLP_USERDATA,(LONG_PTR)disp);
+ SetWindowLongPtr(disp->window,GWLP_WNDPROC,(LONG_PTR)_handle_events);
+#else
+ SetWindowLong(disp->window,GWL_USERDATA,(LONG)disp);
+ SetWindowLong(disp->window,GWL_WNDPROC,(LONG)_handle_events);
+#endif
+ SetEvent(disp->created);
+ while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg);
+ return 0;
+ }
+
+ CImgDisplay& _update_window_pos() {
+ if (!is_closed) {
+ RECT rect;
+ rect.left = rect.top = 0; rect.right = width-1; rect.bottom = height-1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1;
+ GetWindowRect(window,&rect);
+ window_x = rect.left + border1;
+ window_y = rect.top + border2;
+ } else window_x = window_y = -1;
+ return *this;
+ }
+
+ void _init_fullscreen() {
+ background_window = 0;
+ if (is_fullscreen && !is_closed) {
+ DEVMODE mode;
+ unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U;
+ for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) {
+ const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight;
+ if (nw>=width && nh>=height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) {
+ bestbpp = mode.dmBitsPerPel;
+ ibest = imode;
+ bw = nw; bh = nh;
+ }
+ }
+ if (bestbpp) {
+ curr_mode.dmSize = sizeof(DEVMODE); curr_mode.dmDriverExtra = 0;
+ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&curr_mode);
+ EnumDisplaySettings(0,ibest,&mode);
+ ChangeDisplaySettings(&mode,0);
+ } else curr_mode.dmSize = 0;
+
+ const unsigned int sx = screen_dimx(), sy = screen_dimy();
+ if (sx!=width || sy!=height) {
+ CLIENTCREATESTRUCT background_ccs;
+ background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs);
+ SetForegroundWindow(background_window);
+ }
+ } else curr_mode.dmSize = 0;
+ }
+
+ void _desinit_fullscreen() {
+ if (is_fullscreen) {
+ if (background_window) DestroyWindow(background_window);
+ background_window = 0;
+ if (curr_mode.dmSize) ChangeDisplaySettings(&curr_mode,0);
+ is_fullscreen = false;
+ }
+ }
+
+ CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+
+ // Allocate space for window title
+ const int s = cimg::strlen(ptitle)+1;
+ char *tmp_title = s?new char[s]:0;
+ if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));
+
+ // Destroy previous window if existing
+ if (!is_empty()) assign();
+
+ // Set display variables
+ width = cimg::min(dimw,(unsigned int)screen_dimx());
+ height = cimg::min(dimh,(unsigned int)screen_dimy());
+ normalization = normalization_type<4?normalization_type:3;
+ is_fullscreen = fullscreen_flag;
+ window_x = window_y = 0;
+ is_closed = closed_flag;
+ visible_cursor = true;
+ mouse_tracking = false;
+ title = tmp_title;
+ flush();
+ if (is_fullscreen) _init_fullscreen();
+
+ // Create event thread
+ void *arg = (void*)(new void*[2]);
+ ((void**)arg)[0]=(void*)this;
+ ((void**)arg)[1]=(void*)title;
+ unsigned long ThreadID = 0;
+ mutex = CreateMutex(0,FALSE,0);
+ created = CreateEvent(0,FALSE,FALSE,0);
+ thread = CreateThread(0,0,_events_thread,arg,0,&ThreadID);
+ WaitForSingleObject(created,INFINITE);
+ return *this;
+ }
+
+ CImgDisplay& assign() {
+ if (is_empty()) return *this;
+ DestroyWindow(window);
+ TerminateThread(thread,0);
+ if (data) delete[] data;
+ if (title) delete[] title;
+ if (is_fullscreen) _desinit_fullscreen();
+ width = height = normalization = window_width = window_height = 0;
+ window_x = window_y = 0;
+ is_fullscreen = false;
+ is_closed = true;
+ min = max = 0;
+ title = 0;
+ flush();
+ return *this;
+ }
+
+ CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!dimw || !dimh) return assign();
+ _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
+ min = max = 0;
+ cimg_std::memset(data,0,sizeof(unsigned int)*width*height);
+ return paint();
+ }
+
+ template<typename T>
+ CImgDisplay& assign(const CImg<T>& img, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!img) return assign();
+ CImg<T> tmp;
+ const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
+ if (normalization==2) min = (float)nimg.minmax(max);
+ return display(nimg);
+ }
+
+ template<typename T>
+ CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!list) return assign();
+ CImg<T> tmp;
+ const CImg<T> img = list.get_append('x','p'),
+ &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
+ if (normalization==2) min = (float)nimg.minmax(max);
+ return display(nimg);
+ }
+
+ CImgDisplay& assign(const CImgDisplay& win) {
+ if (!win) return assign();
+ _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
+ cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height);
+ return paint();
+ }
+
+ CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
+ if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+ if (is_empty()) return assign(nwidth,nheight);
+ const unsigned int
+ tmpdimx=(nwidth>0)?nwidth:(-nwidth*width/100),
+ tmpdimy=(nheight>0)?nheight:(-nheight*height/100),
+ dimx = tmpdimx?tmpdimx:1,
+ dimy = tmpdimy?tmpdimy:1;
+ if (window_width!=dimx || window_height!=dimy) {
+ RECT rect; rect.left = rect.top = 0; rect.right = dimx-1; rect.bottom = dimy-1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int cwidth = rect.right-rect.left+1, cheight = rect.bottom-rect.top+1;
+ SetWindowPos(window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS);
+ }
+ if (width!=dimx || height!=dimy) {
+ unsigned int *ndata = new unsigned int[dimx*dimy];
+ if (redraw) _render_resize(data,width,height,ndata,dimx,dimy);
+ else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
+ delete[] data;
+ data = ndata;
+ bmi.bmiHeader.biWidth = dimx;
+ bmi.bmiHeader.biHeight = -(int)dimy;
+ width = dimx;
+ height = dimy;
+ }
+ window_width = dimx; window_height = dimy;
+ is_resized = false;
+ if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
+ if (redraw) return paint();
+ return *this;
+ }
+
+ CImgDisplay& toggle_fullscreen(const bool redraw=true) {
+ if (is_empty()) return *this;
+ if (redraw) {
+ const unsigned int bufsize = width*height*4;
+ void *odata = cimg_std::malloc(bufsize);
+ cimg_std::memcpy(odata,data,bufsize);
+ assign(width,height,title,normalization,!is_fullscreen,false);
+ cimg_std::memcpy(data,odata,bufsize);
+ cimg_std::free(odata);
+ return paint();
+ }
+ return assign(width,height,title,normalization,!is_fullscreen,false);
+ }
+
+ CImgDisplay& show() {
+ if (is_empty()) return *this;
+ if (is_closed) {
+ is_closed = false;
+ if (is_fullscreen) _init_fullscreen();
+ ShowWindow(window,SW_SHOW);
+ _update_window_pos();
+ }
+ return paint();
+ }
+
+ CImgDisplay& close() {
+ if (is_empty()) return *this;
+ if (!is_closed && !is_fullscreen) {
+ if (is_fullscreen) _desinit_fullscreen();
+ ShowWindow(window,SW_HIDE);
+ is_closed = true;
+ window_x = window_y = 0;
+ }
+ return *this;
+ }
+
+ CImgDisplay& move(const int posx, const int posy) {
+ if (is_empty()) return *this;
+ if (!is_fullscreen) {
+ RECT rect; rect.left = rect.top = 0; rect.right=window_width-1; rect.bottom=window_height-1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1;
+ SetWindowPos(window,0,posx-border1,posy-border2,0,0,SWP_NOSIZE | SWP_NOZORDER);
+ } else SetWindowPos(window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER);
+ window_x = posx;
+ window_y = posy;
+ is_moved = false;
+ return show();
+ }
+
+ CImgDisplay& show_mouse() {
+ if (is_empty()) return *this;
+ visible_cursor = true;
+ ShowCursor(TRUE);
+ SendMessage(window,WM_SETCURSOR,0,0);
+ return *this;
+ }
+
+ CImgDisplay& hide_mouse() {
+ if (is_empty()) return *this;
+ visible_cursor = false;
+ ShowCursor(FALSE);
+ SendMessage(window,WM_SETCURSOR,0,0);
+ return *this;
+ }
+
+ CImgDisplay& set_mouse(const int posx, const int posy) {
+ if (!is_closed && posx>=0 && posy>=0) {
+ _update_window_pos();
+ const int res = (int)SetCursorPos(window_x+posx,window_y+posy);
+ if (res) { mouse_x = posx; mouse_y = posy; }
+ }
+ return *this;
+ }
+
+ CImgDisplay& set_title(const char *format, ...) {
+ if (is_empty()) return *this;
+ char tmp[1024] = {0};
+ va_list ap;
+ va_start(ap, format);
+ cimg_std::vsprintf(tmp,format,ap);
+ va_end(ap);
+ if (title) delete[] title;
+ const int s = cimg::strlen(tmp)+1;
+ title = new char[s];
+ cimg_std::memcpy(title,tmp,s*sizeof(char));
+ SetWindowTextA(window, tmp);
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& display(const CImg<T>& img) {
+ if (img.is_empty())
+ throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image.");
+ if (is_empty()) assign(img.width,img.height);
+ return render(img).paint();
+ }
+
+ CImgDisplay& paint() {
+ if (!is_closed) {
+ WaitForSingleObject(mutex,INFINITE);
+ SetDIBitsToDevice(hdc,0,0,width,height,0,0,0,height,data,&bmi,DIB_RGB_COLORS);
+ ReleaseMutex(mutex);
+ }
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& render(const CImg<T>& img) {
+ if (is_empty()) return *this;
+ if (!img)
+ throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
+ img.width,img.height,img.depth,img.dim,img.data);
+ if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+
+ const T
+ *data1 = img.data,
+ *data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1,
+ *data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1;
+
+ WaitForSingleObject(mutex,INFINITE);
+ unsigned int
+ *const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height],
+ *ptrd = ndata;
+
+ if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
+ min = max = 0;
+ switch (img.dim) {
+ case 1 : {
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++);
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }} break;
+ case 2 : {
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
+ } break;
+ default : {
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
+ }
+ }
+ } else {
+ if (normalization==3) {
+ if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
+ else { min = (float)cimg::type<T>::min(); max = (float)cimg::type<T>::max(); }
+ } else if ((min>max) || normalization==1) min = (float)img.minmax(max);
+ const float delta = max-min, mm = delta?delta:1.0f;
+ switch (img.dim) {
+ case 1 : {
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }} break;
+ case 2 : {
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)(255*(*(data1++)-min)/mm),
+ G = (unsigned char)(255*(*(data2++)-min)/mm);
+ *(ptrd++) = (R<<16) | (G<<8);
+ }} break;
+ default : {
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)(255*(*(data1++)-min)/mm),
+ G = (unsigned char)(255*(*(data2++)-min)/mm),
+ B = (unsigned char)(255*(*(data3++)-min)/mm);
+ *(ptrd++) = (R<<16) | (G<<8) | B;
+ }}
+ }
+ }
+ if (ndata!=data) { _render_resize(ndata,img.width,img.height,data,width,height); delete[] ndata; }
+ ReleaseMutex(mutex);
+ return *this;
+ }
+
+ template<typename T>
+ const CImgDisplay& snapshot(CImg<T>& img) const {
+ if (is_empty()) img.assign();
+ else {
+ img.assign(width,height,1,3);
+ T
+ *data1 = img.ptr(0,0,0,0),
+ *data2 = img.ptr(0,0,0,1),
+ *data3 = img.ptr(0,0,0,2);
+ unsigned int *ptrs = data;
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned int val = *(ptrs++);
+ *(data1++) = (unsigned char)(val>>16);
+ *(data2++) = (unsigned char)((val>>8)&0xFF);
+ *(data3++) = (unsigned char)(val&0xFF);
+ }
+ }
+ return *this;
+ }
+
+ // MacOSX - Carbon-based display
+ //-------------------------------
+ // (Code by Adrien Reboisson && Romain Blei, supervised by Jean-Marie Favreau)
+ //
+#elif cimg_display==3
+ unsigned int *data; // The bits of the picture
+ WindowRef carbonWindow; // The opaque carbon window struct associated with the display
+ MPCriticalRegionID paintCriticalRegion; // Critical section used when drawing
+ CGColorSpaceRef csr; // Needed for painting
+ CGDataProviderRef dataProvider; // Needed for painting
+ CGImageRef imageRef; // The image
+ UInt32 lastKeyModifiers; // Buffer storing modifiers state
+
+ // Define the kind of the queries which can be serialized using the event thread.
+ typedef enum {
+ COM_CREATEWINDOW = 0, // Create window query
+ COM_RELEASEWINDOW, // Release window query
+ COM_SHOWWINDOW, // Show window query
+ COM_HIDEWINDOW, // Hide window query
+ COM_SHOWMOUSE, // Show mouse query
+ COM_HIDEMOUSE, // Hide mouse query
+ COM_RESIZEWINDOW, // Resize window query
+ COM_MOVEWINDOW, // Move window query
+ COM_SETTITLE, // Set window title query
+ COM_SETMOUSEPOS // Set cursor position query
+ } CImgCarbonQueryKind;
+
+ // The query destructor send to the event thread.
+ struct CbSerializedQuery {
+ CImgDisplay* sender; // Query's sender
+ CImgCarbonQueryKind kind; // The kind of the query sent to the background thread
+ short x, y; // X:Y values for move/resize operations
+ char *c; // Char values for window title
+ bool createFullScreenWindow; // Boolean value used for full-screen window creation
+ bool createClosedWindow; // Boolean value used for closed-window creation
+ bool update; // Boolean value used for resize
+ bool success; // Succes or failure of the message, used as return value
+ CbSerializedQuery(CImgDisplay *s, CImgCarbonQueryKind k):sender(s),kind(k),success(false) {};
+
+ inline static CbSerializedQuery BuildReleaseWindowQuery(CImgDisplay* sender) {
+ return CbSerializedQuery(sender, COM_RELEASEWINDOW);
+ }
+ inline static CbSerializedQuery BuildCreateWindowQuery(CImgDisplay* sender, const bool fullscreen, const bool closed) {
+ CbSerializedQuery q(sender, COM_CREATEWINDOW);
+ q.createFullScreenWindow = fullscreen;
+ q.createClosedWindow = closed;
+ return q;
+ }
+ inline static CbSerializedQuery BuildShowWindowQuery(CImgDisplay* sender) {
+ return CbSerializedQuery(sender, COM_SHOWWINDOW);
+ }
+ inline static CbSerializedQuery BuildHideWindowQuery(CImgDisplay* sender) {
+ return CbSerializedQuery(sender, COM_HIDEWINDOW);
+ }
+ inline static CbSerializedQuery BuildShowMouseQuery(CImgDisplay* sender) {
+ return CbSerializedQuery(sender, COM_SHOWMOUSE);
+ }
+ inline static CbSerializedQuery BuildHideMouseQuery(CImgDisplay* sender) {
+ return CbSerializedQuery(sender, COM_HIDEMOUSE);
+ }
+ inline static CbSerializedQuery BuildResizeWindowQuery(CImgDisplay* sender, const int x, const int y, bool update) {
+ CbSerializedQuery q(sender, COM_RESIZEWINDOW);
+ q.x = x, q.y = y;
+ q.update = update;
+ return q;
+ }
+ inline static CbSerializedQuery BuildMoveWindowQuery(CImgDisplay* sender, const int x, const int y) {
+ CbSerializedQuery q(sender, COM_MOVEWINDOW);
+ q.x = x, q.y = y;
+ return q;
+ }
+ inline static CbSerializedQuery BuildSetWindowTitleQuery(CImgDisplay* sender, char* c) {
+ CbSerializedQuery q(sender, COM_SETTITLE);
+ q.c = c;
+ return q;
+ }
+ inline static CbSerializedQuery BuildSetWindowPosQuery(CImgDisplay* sender, const int x, const int y) {
+ CbSerializedQuery q(sender, COM_SETMOUSEPOS);
+ q.x = x, q.y = y;
+ return q;
+ }
+ };
+
+ // Send a serialized query in a synchroneous way.
+ // @param c Application Carbon global settings.
+ // @param m The query to send.
+ // @result Success/failure of the operation returned by the event thread.
+ bool _CbSendMsg(cimg::CarbonInfo& c, CbSerializedQuery m) {
+ MPNotifyQueue(c.com_queue,&m,0,0); // Send the given message
+ MPWaitOnSemaphore(c.sync_event,kDurationForever); // Wait end of processing notification
+ return m.success;
+ }
+
+ // Free the window attached to the current display.
+ // @param c Application Carbon global settings.
+ // @result Success/failure of the operation.
+ bool _CbFreeAttachedWindow(cimg::CarbonInfo& c) {
+ if (!_CbSendMsg(c, CbSerializedQuery::BuildReleaseWindowQuery(this))) // Ask the main thread to free the given window
+ throw CImgDisplayException("Cannot release window associated with the current display.");
+ // If a window existed, ask to release it
+ MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows
+ --c.windowCount; //Decrement the window count
+ MPExitCriticalRegion(c.windowListCR); // Unlock the list
+ return c.windowCount == 0;
+ }
+
+ // Create the window attached to the current display.
+ // @param c Application Carbon global settings.
+ // @param title The window title, if any.
+ // @param fullscreen Shoud we start in fullscreen mode ?
+ // @param create_closed If true, the window is created but not displayed.
+ // @result Success/failure of the operation.
+ void _CbCreateAttachedWindow(cimg::CarbonInfo& c, const char* title, const bool fullscreen, const bool create_closed) {
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildCreateWindowQuery(this,fullscreen,create_closed))) // Ask the main thread to create the window
+ throw CImgDisplayException("Cannot create the window associated with the current display.");
+ if (title) set_title(title); // Set the title, if any
+ // Now we can register the window
+ MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows
+ ++c.windowCount; //Increment the window count
+ MPExitCriticalRegion(c.windowListCR); // Unlock the list
+ }
+
+ // Destroy graphic objects previously allocated. We free the image, the data provider, then the colorspace.
+ void _CbFinalizeGraphics() {
+ CGImageRelease (imageRef); // Release the picture
+ CGDataProviderRelease(dataProvider); // Release the DP
+ CGColorSpaceRelease(csr); // Free the cs
+ }
+
+ // Create graphic objects associated to a display. We have to create a colormap, a data provider, and the image.
+ void _CbInitializeGraphics() {
+ csr = CGColorSpaceCreateDeviceRGB(); // Create the color space first
+ if (!csr)
+ throw CImgDisplayException("CGColorSpaceCreateDeviceRGB() failed.");
+ // Create the DP
+ dataProvider = CGDataProviderCreateWithData(0,data,height*width*sizeof(unsigned int),0);
+ if (!dataProvider)
+ throw CImgDisplayException("CGDataProviderCreateWithData() failed.");
+ // ... and finally the image.
+ if (cimg::endianness())
+ imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr,
+ kCGImageAlphaNoneSkipFirst,dataProvider,0,false,kCGRenderingIntentDefault);
+ else
+ imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr,
+ kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host,dataProvider,0,false,kCGRenderingIntentDefault);
+ if (!imageRef)
+ throw CImgDisplayException("CGImageCreate() failed.");
+ }
+
+ // Reinit graphic objects. Free them, then reallocate all.
+ // This is used when image bounds are changed or when data source get invalid.
+ void _CbReinitGraphics() {
+ MPEnterCriticalRegion(paintCriticalRegion, kDurationForever);
+ _CbFinalizeGraphics();
+ _CbInitializeGraphics();
+ MPExitCriticalRegion(paintCriticalRegion);
+ }
+
+ // Convert a point having global coordonates into the window coordonates.
+ // We use this function to replace the deprecated GlobalToLocal QuickDraw API.
+ // @param mouseEvent The mouse event which triggered the event handler.
+ // @param window The window where the event occured.
+ // @param point The modified point struct.
+ // @result True if the point struct has been converted successfully.
+ static bool _CbToLocalPointFromMouseEvent(EventRef mouseEvent, WindowRef window, HIPoint* point) {
+ Rect bounds;
+ if (GetWindowBounds(window,kWindowStructureRgn,&bounds)==noErr) {
+ point->x -= bounds.left;
+ point->y -= bounds.top;
+ HIViewRef view = NULL;
+ if (HIViewGetViewForMouseEvent(HIViewGetRoot(window),mouseEvent,&view)==noErr)
+ return HIViewConvertPoint(point, NULL, view) == noErr;
+ }
+ return false;
+ }
+
+ static int screen_dimx() {
+ return CGDisplayPixelsWide(kCGDirectMainDisplay);
+ }
+
+ static int screen_dimy() {
+ return CGDisplayPixelsHigh(kCGDirectMainDisplay);
+ }
+
+ CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!dimw || !dimh) return assign();
+ _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
+ min = max = 0;
+ cimg_std::memset(data,0,sizeof(unsigned int)*width*height);
+ return paint();
+ }
+
+ template<typename T>
+ CImgDisplay& assign(const CImg<T>& img, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!img) return assign();
+ CImg<T> tmp;
+ const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
+ if (normalization==2) min = (float)nimg.minmax(max);
+ return display(nimg);
+ }
+
+ template<typename T>
+ CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!list) return assign();
+ CImg<T> tmp;
+ const CImg<T> img = list.get_append('x','p'),
+ &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
+ if (normalization==2) min = (float)nimg.minmax(max);
+ return display(nimg);
+ }
+
+ CImgDisplay& assign(const CImgDisplay &win) {
+ if (!win) return assign();
+ _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
+ cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height);
+ return paint();
+ }
+
+ template<typename T>
+ CImgDisplay& display(const CImg<T>& img) {
+ if (is_empty()) assign(img.width,img.height);
+ return render(img).paint();
+ }
+
+ CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
+ if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+ if (is_empty()) return assign(nwidth,nheight);
+ const unsigned int
+ tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100),
+ tmpdimy = (nheight>0)?nheight:(-nheight*height/100),
+ dimx = tmpdimx?tmpdimx:1,
+ dimy = tmpdimy?tmpdimy:1;
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+
+ if ((window_width!=dimx || window_height!=dimy) &&
+ !_CbSendMsg(c,CbSerializedQuery::BuildResizeWindowQuery(this,dimx,dimy,redraw)))
+ throw CImgDisplayException("CImgDisplay::resize() : Cannot resize the window associated to the current display.");
+
+ if (width!=dimx || height!=dimy) {
+ unsigned int *ndata = new unsigned int[dimx*dimy];
+ if (redraw) _render_resize(data,width,height,ndata,dimx,dimy);
+ else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
+ unsigned int const* old_data = data;
+ data = ndata;
+ delete[] old_data;
+ _CbReinitGraphics();
+ }
+ window_width = width = dimx; window_height = height = dimy;
+ is_resized = false;
+ if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
+ if (redraw) return paint();
+ return *this;
+ }
+
+ CImgDisplay& move(const int posx, const int posy) {
+ if (is_empty()) return *this;
+ if (!is_fullscreen) {
+ // If the operation succeeds, window_x and window_y are updated by the event thread
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ // Send the query
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildMoveWindowQuery(this,posx,posy)))
+ throw CImgDisplayException("CImgDisplay::move() : Cannot move the window associated to the current display.");
+ }
+ return show();
+ }
+
+ CImgDisplay& set_mouse(const int posx, const int posy) {
+ if (!is_closed && posx>=0 && posy>=0) {
+ // If the operation succeeds, mouse_x and mouse_y are updated by the event thread
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ // Send the query
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowPosQuery(this,posx,posy)))
+ throw CImgDisplayException("CImgDisplay::set_mouse() : Cannot set the mouse position to the current display.");
+ }
+ return *this;
+ }
+
+ CImgDisplay& hide_mouse() {
+ if (is_empty()) return *this;
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ // Send the query
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildHideMouseQuery(this)))
+ throw CImgDisplayException("CImgDisplay::hide_mouse() : Cannot hide the mouse associated to the current display.");
+ return *this;
+ }
+
+ CImgDisplay& show_mouse() {
+ if (is_empty()) return *this;
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ // Send the query
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildShowMouseQuery(this)))
+ throw CImgDisplayException("CImgDisplay::show_mouse() : Cannot show the mouse associated to the current display.");
+ return *this;
+ }
+
+ static void wait_all() {
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ MPWaitOnSemaphore(c.wait_event,kDurationForever);
+ }
+
+ CImgDisplay& show() {
+ if (is_empty()) return *this;
+ if (is_closed) {
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildShowWindowQuery(this)))
+ throw CImgDisplayException("CImgDisplay::show() : Cannot show the window associated to the current display.");
+ }
+ return paint();
+ }
+
+ CImgDisplay& close() {
+ if (is_empty()) return *this;
+ if (!is_closed && !is_fullscreen) {
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ // If the operation succeeds, window_x and window_y are updated on the event thread
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildHideWindowQuery(this)))
+ throw CImgDisplayException("CImgDisplay::close() : Cannot hide the window associated to the current display.");
+ }
+ return *this;
+ }
+
+ CImgDisplay& set_title(const char *format, ...) {
+ if (is_empty()) return *this;
+ char tmp[1024] = {0};
+ va_list ap;
+ va_start(ap, format);
+ cimg_std::vsprintf(tmp,format,ap);
+ va_end(ap);
+ if (title) delete[] title;
+ const int s = cimg::strlen(tmp)+1;
+ title = new char[s];
+ cimg_std::memcpy(title,tmp,s*sizeof(char));
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowTitleQuery(this,tmp)))
+ throw CImgDisplayException("CImgDisplay::set_title() : Cannot set the window title associated to the current display.");
+ return *this;
+ }
+
+ CImgDisplay& paint() {
+ if (!is_closed) {
+ MPEnterCriticalRegion(paintCriticalRegion,kDurationForever);
+ CGrafPtr portPtr = GetWindowPort(carbonWindow);
+ CGContextRef currentContext = 0;
+ TQDBeginCGContext(portPtr,&currentContext);
+ CGContextSetRGBFillColor(currentContext,255,255,255,255);
+ CGContextFillRect(currentContext,CGRectMake(0,0,window_width,window_height));
+ CGContextDrawImage(currentContext,CGRectMake(0,int(window_height-height)<0?0:window_height-height,width,height),imageRef);
+ CGContextFlush(currentContext);
+ TQDEndCGContext(portPtr, &currentContext);
+ MPExitCriticalRegion(paintCriticalRegion);
+ }
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& render(const CImg<T>& img) {
+ if (is_empty()) return *this;
+ if (!img)
+ throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
+ img.width,img.height,img.depth,img.dim,img.data);
+ if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
+ const T
+ *data1 = img.data,
+ *data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1,
+ *data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1;
+ MPEnterCriticalRegion(paintCriticalRegion, kDurationForever);
+ unsigned int
+ *const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height],
+ *ptrd = ndata;
+ if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
+ min = max = 0;
+ for (unsigned int xy = img.width*img.height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
+ } else {
+ if (normalization==3) {
+ if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
+ else {
+ min = (float)cimg::type<T>::min();
+ max = (float)cimg::type<T>::max();
+ }
+ } else if ((min>max) || normalization==1) min = (float)img.minmax(max);
+ const float delta = max-min, mm = delta?delta:1.0f;
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)(255*(*(data1++)-min)/mm),
+ G = (unsigned char)(255*(*(data2++)-min)/mm),
+ B = (unsigned char)(255*(*(data3++)-min)/mm);
+ *(ptrd++) = (R<<16) | (G<<8) | (B);
+ }
+ }
+ if (ndata!=data) {
+ _render_resize(ndata,img.width,img.height,data,width,height);
+ delete[] ndata;
+ }
+ MPExitCriticalRegion(paintCriticalRegion);
+ return *this;
+ }
+
+ template<typename T>
+ const CImgDisplay& snapshot(CImg<T>& img) const {
+ if (is_empty()) img.assign();
+ else {
+ img.assign(width,height,1,3);
+ T
+ *data1 = img.ptr(0,0,0,0),
+ *data2 = img.ptr(0,0,0,1),
+ *data3 = img.ptr(0,0,0,2);
+ unsigned int *ptrs = data;
+ for (unsigned int xy = img.width*img.height; xy>0; --xy) {
+ const unsigned int val = *(ptrs++);
+ *(data1++) = (unsigned char)(val>>16);
+ *(data2++) = (unsigned char)((val>>8)&0xFF);
+ *(data3++) = (unsigned char)(val&0xFF);
+ }
+ }
+ return *this;
+ }
+
+ CImgDisplay& toggle_fullscreen(const bool redraw=true) {
+ if (is_empty()) return *this;
+ if (redraw) {
+ const unsigned int bufsize = width*height*4;
+ void *odata = cimg_std::malloc(bufsize);
+ cimg_std::memcpy(odata,data,bufsize);
+ assign(width,height,title,normalization,!is_fullscreen,false);
+ cimg_std::memcpy(data,odata,bufsize);
+ cimg_std::free(odata);
+ return paint();
+ }
+ return assign(width,height,title,normalization,!is_fullscreen,false);
+ }
+
+ static OSStatus CarbonEventHandler(EventHandlerCallRef myHandler, EventRef theEvent, void* userData) {
+ OSStatus result = eventNotHandledErr;
+ CImgDisplay* disp = (CImgDisplay*) userData;
+ (void)myHandler; // Avoid "unused parameter"
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ // Gets the associated display
+ if (disp) {
+ // Window events are always handled
+ if (GetEventClass(theEvent)==kEventClassWindow) switch (GetEventKind (theEvent)) {
+ case kEventWindowClose :
+ disp->mouse_x = disp->mouse_y = -1;
+ disp->window_x = disp->window_y = 0;
+ if (disp->button) {
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ disp->button = 0;
+ }
+ if (disp->key) {
+ cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
+ disp->key = 0;
+ }
+ if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
+ disp->is_closed = true;
+ HideWindow(disp->carbonWindow);
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ result = noErr;
+ break;
+ // There is a lot of case where we have to redraw our window
+ case kEventWindowBoundsChanging :
+ case kEventWindowResizeStarted :
+ case kEventWindowCollapsed : //Not sure it's really needed :-)
+ break;
+ case kEventWindowZoomed :
+ case kEventWindowExpanded :
+ case kEventWindowResizeCompleted : {
+ MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever);
+ // Now we retrieve the new size of the window
+ Rect newContentRect;
+ GetWindowBounds(disp->carbonWindow,kWindowContentRgn,&newContentRect);
+ const unsigned int
+ nw = (unsigned int)(newContentRect.right - newContentRect.left),
+ nh = (unsigned int)(newContentRect.bottom - newContentRect.top);
+
+ // Then we update CImg internal settings
+ if (nw && nh && (nw!=disp->width || nh!=disp->height)) {
+ disp->window_width = nw;
+ disp->window_height = nh;
+ disp->mouse_x = disp->mouse_y = -1;
+ disp->is_resized = true;
+ }
+ disp->is_event = true;
+ MPExitCriticalRegion(disp->paintCriticalRegion);
+ disp->paint(); // Coords changed, must update the screen
+ MPSignalSemaphore(c.wait_event);
+ result = noErr;
+ } break;
+ case kEventWindowDragStarted :
+ case kEventWindowDragCompleted : {
+ MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever);
+ // Now we retrieve the new size of the window
+ Rect newContentRect ;
+ GetWindowBounds(disp->carbonWindow,kWindowStructureRgn,&newContentRect);
+ const int nx = (int)(newContentRect.left), ny = (int)(newContentRect.top);
+ // Then we update CImg internal settings
+ if (nx!=disp->window_x || ny!=disp->window_y) {
+ disp->window_x = nx;
+ disp->window_y = ny;
+ disp->is_moved = true;
+ }
+ disp->is_event = true;
+ MPExitCriticalRegion(disp->paintCriticalRegion);
+ disp->paint(); // Coords changed, must update the screen
+ MPSignalSemaphore(c.wait_event);
+ result = noErr;
+ } break;
+ case kEventWindowPaint :
+ disp->paint();
+ break;
+ }
+
+ switch (GetEventClass(theEvent)) {
+ case kEventClassKeyboard : {
+ if (GetEventKind(theEvent)==kEventRawKeyModifiersChanged) {
+ // Apple has special keys named "notifiers", we have to convert this (exotic ?) key handling into the regular CImg processing.
+ UInt32 newModifiers;
+ if (GetEventParameter(theEvent,kEventParamKeyModifiers,typeUInt32,0,sizeof(UInt32),0,&newModifiers)==noErr) {
+ int newKeyCode = -1;
+ UInt32 changed = disp->lastKeyModifiers^newModifiers;
+ // Find what changed here
+ if ((changed & rightShiftKey)!=0) newKeyCode = cimg::keySHIFTRIGHT;
+ if ((changed & shiftKey)!=0) newKeyCode = cimg::keySHIFTLEFT;
+
+ // On the Mac, the "option" key = the ALT key
+ if ((changed & (optionKey | rightOptionKey))!=0) newKeyCode = cimg::keyALTGR;
+ if ((changed & controlKey)!=0) newKeyCode = cimg::keyCTRLLEFT;
+ if ((changed & rightControlKey)!=0) newKeyCode = cimg::keyCTRLRIGHT;
+ if ((changed & cmdKey)!=0) newKeyCode = cimg::keyAPPLEFT;
+ if ((changed & alphaLock)!=0) newKeyCode = cimg::keyCAPSLOCK;
+ if (newKeyCode != -1) { // Simulate keystroke
+ if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
+ disp->key = (int)newKeyCode;
+ }
+ disp->lastKeyModifiers = newModifiers; // Save current state
+ }
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ }
+ if (GetEventKind(theEvent)==kEventRawKeyDown || GetEventKind(theEvent)==kEventRawKeyRepeat) {
+ char keyCode;
+ if (GetEventParameter(theEvent,kEventParamKeyMacCharCodes,typeChar,0,sizeof(keyCode),0,&keyCode)==noErr) {
+ disp->update_iskey((unsigned int)keyCode,true);
+ if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
+ disp->key = (unsigned int)keyCode;
+ if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
+ }
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ }
+ } break;
+
+ case kEventClassMouse :
+ switch (GetEventKind(theEvent)) {
+ case kEventMouseDragged :
+ // When you push the main button on the Apple mouse while moving it, you got NO kEventMouseMoved msg,
+ // but a kEventMouseDragged one. So we merge them here.
+ case kEventMouseMoved :
+ HIPoint point;
+ if (GetEventParameter(theEvent,kEventParamMouseLocation,typeHIPoint,0,sizeof(point),0,&point)==noErr) {
+ if (_CbToLocalPointFromMouseEvent(theEvent,disp->carbonWindow,&point)) {
+ disp->mouse_x = (int)point.x;
+ disp->mouse_y = (int)point.y;
+ if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy())
+ disp->mouse_x = disp->mouse_y = -1;
+ } else disp->mouse_x = disp->mouse_y = -1;
+ }
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ break;
+ case kEventMouseDown :
+ UInt16 btn;
+ if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) {
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ if (btn==kEventMouseButtonPrimary) disp->button|=1U;
+ // For those who don't have a multi-mouse button (as me), I think it's better to allow the user
+ // to emulate a right click by using the Control key
+ if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)
+ cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Down]");
+ if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button|=2U;
+ if (btn==kEventMouseButtonTertiary) disp->button|=4U;
+ }
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ break;
+ case kEventMouseWheelMoved :
+ EventMouseWheelAxis wheelax;
+ SInt32 delta;
+ if (GetEventParameter(theEvent,kEventParamMouseWheelAxis,typeMouseWheelAxis,0,sizeof(wheelax),0,&wheelax)==noErr)
+ if (wheelax==kEventMouseWheelAxisY) {
+ if (GetEventParameter(theEvent,kEventParamMouseWheelDelta,typeLongInteger,0,sizeof(delta),0,&delta)==noErr)
+ if (delta>0) disp->wheel+=delta/120; //FIXME: why 120 ?
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ }
+ break;
+ }
+ }
+
+ switch (GetEventClass(theEvent)) {
+ case kEventClassKeyboard :
+ if (GetEventKind(theEvent)==kEventRawKeyUp) {
+ UInt32 keyCode;
+ if (GetEventParameter(theEvent,kEventParamKeyCode,typeUInt32,0,sizeof(keyCode),0,&keyCode)==noErr) {
+ disp->update_iskey((unsigned int)keyCode,false);
+ if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; }
+ if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1);
+ disp->released_key = (int)keyCode;
+ }
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ }
+ break;
+
+ case kEventClassMouse :
+ switch (GetEventKind(theEvent)) {
+ case kEventMouseUp :
+ UInt16 btn;
+ if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) {
+ cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
+ if (btn==kEventMouseButtonPrimary) disp->button&=~1U;
+ // See note in kEventMouseDown handler.
+ if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)
+ cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Up]");
+ if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button&=~2U;
+ if (btn==kEventMouseButtonTertiary) disp->button&=~2U;
+ }
+ disp->is_event = true;
+ MPSignalSemaphore(c.wait_event);
+ break;
+ }
+ }
+ }
+ return (result);
+ }
+
+ static void* _events_thread(void* args) {
+ (void)args; // Make the compiler happy
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
+ MPSignalSemaphore(c.sync_event); // Notify the caller that all goes fine
+ EventRef theEvent;
+ EventTargetRef theTarget;
+ OSStatus err;
+ CbSerializedQuery* query;
+ theTarget = GetEventDispatcherTarget();
+
+ // Enter in the main loop
+ while (true) {
+ pthread_testcancel(); /* Check if cancelation happens */
+ err = ReceiveNextEvent(0,0,kDurationImmediate,true,&theEvent); // Fetch new events
+ if (err==noErr) { // Received a carbon event, so process it !
+ SendEventToEventTarget (theEvent, theTarget);
+ ReleaseEvent(theEvent);
+ } else if (err == eventLoopTimedOutErr) { // There is no event to process, so check if there is new messages to process
+ OSStatus r =MPWaitOnQueue(c.com_queue,(void**)&query,0,0,10*kDurationMillisecond);
+ if (r!=noErr) continue; //nothing in the queue or an error.., bye
+ // If we're here, we've something to do now.
+ if (query) {
+ switch (query->kind) {
+ case COM_SETMOUSEPOS : { // change the cursor position
+ query->success = CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,CGPointMake(query->sender->window_x+query->x,query->sender->window_y+query->y))
+ == kCGErrorSuccess;
+ if (query->success) {
+ query->sender->mouse_x = query->x;
+ query->sender->mouse_y = query->y;
+ } else cimg::warn("CImgDisplay::_events_thread() : CGDisplayMoveCursorToPoint failed.");
+ } break;
+ case COM_SETTITLE : { // change the title bar caption
+ CFStringRef windowTitle = CFStringCreateWithCString(0,query->c,kCFStringEncodingMacRoman);
+ query->success = SetWindowTitleWithCFString(query->sender->carbonWindow,windowTitle)==noErr;
+ if (!query->success)
+ cimg::warn("CImgDisplay::_events_thread() : SetWindowTitleWithCFString failed.");
+ CFRelease(windowTitle);
+ } break;
+ case COM_RESIZEWINDOW : { // Resize a window
+ SizeWindow(query->sender->carbonWindow,query->x,query->y,query->update);
+ // If the window has been resized successfully, update display informations
+ query->sender->window_width = query->x;
+ query->sender->window_height = query->y;
+ query->success = true;
+ } break;
+ case COM_MOVEWINDOW : { // Move a window
+ MoveWindow(query->sender->carbonWindow,query->x,query->y,false);
+ query->sender->window_x = query->x;
+ query->sender->window_y = query->y;
+ query->sender->is_moved = false;
+ query->success = true;
+ } break;
+ case COM_SHOWMOUSE : { // Show the mouse
+ query->success = CGDisplayShowCursor(kCGDirectMainDisplay)==noErr;
+ if (!query->success)
+ cimg::warn("CImgDisplay::_events_thread() : CGDisplayShowCursor failed.");
+ } break;
+ case COM_HIDEMOUSE : { // Hide the mouse
+ query->success = CGDisplayHideCursor(kCGDirectMainDisplay)==noErr;
+ if (!query->success)
+ cimg::warn("CImgDisplay::_events_thread() : CGDisplayHideCursor failed.");
+ } break;
+ case COM_SHOWWINDOW : { // We've to show a window
+ ShowWindow(query->sender->carbonWindow);
+ query->success = true;
+ query->sender->is_closed = false;
+ } break;
+ case COM_HIDEWINDOW : { // We've to show a window
+ HideWindow(query->sender->carbonWindow);
+ query->sender->is_closed = true;
+ query->sender->window_x = query->sender->window_y = 0;
+ query->success = true;
+ } break;
+ case COM_RELEASEWINDOW : { // We have to release a given window handle
+ query->success = true;
+ CFRelease(query->sender->carbonWindow);
+ } break;
+ case COM_CREATEWINDOW : { // We have to create a window
+ query->success = true;
+ WindowAttributes windowAttrs;
+ Rect contentRect;
+ if (query->createFullScreenWindow) {
+ // To simulate a "true" full screen, we remove menus and close boxes
+ windowAttrs = (1L << 9); //Why ? kWindowNoTitleBarAttribute seems to be not defined on 10.3
+ // Define a full screen bound rect
+ SetRect(&contentRect,0,0,CGDisplayPixelsWide(kCGDirectMainDisplay),CGDisplayPixelsHigh(kCGDirectMainDisplay));
+ } else { // Set the window size
+ SetRect(&contentRect,0,0,query->sender->width,query->sender->height); // Window will be centered with RepositionWindow.
+ // Use default attributes
+ windowAttrs = kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute | kWindowLiveResizeAttribute;
+ }
+ // Update window position
+ if (query->createClosedWindow) query->sender->window_x = query->sender->window_y = 0;
+ else {
+ query->sender->window_x = contentRect.left;
+ query->sender->window_y = contentRect.top;
+ }
+ // Update window flags
+ query->sender->window_width = query->sender->width;
+ query->sender->window_height = query->sender->height;
+ query->sender->flush();
+ // Create the window
+ if (CreateNewWindow(kDocumentWindowClass,windowAttrs,&contentRect,&query->sender->carbonWindow)!=noErr) {
+ query->success = false;
+ cimg::warn("CImgDisplay::_events_thread() : CreateNewWindow() failed.");
+ }
+ // Send it to the foreground
+ if (RepositionWindow(query->sender->carbonWindow,0,kWindowCenterOnMainScreen)!=noErr) query->success = false;
+ // Show it, if needed
+ if (!query->createClosedWindow) ShowWindow(query->sender->carbonWindow);
+
+ // Associate a valid event handler
+ EventTypeSpec eventList[] = {
+ { kEventClassWindow, kEventWindowClose },
+ { kEventClassWindow, kEventWindowResizeStarted },
+ { kEventClassWindow, kEventWindowResizeCompleted },
+ { kEventClassWindow, kEventWindowDragStarted},
+ { kEventClassWindow, kEventWindowDragCompleted },
+ { kEventClassWindow, kEventWindowPaint },
+ { kEventClassWindow, kEventWindowBoundsChanging },
+ { kEventClassWindow, kEventWindowCollapsed },
+ { kEventClassWindow, kEventWindowExpanded },
+ { kEventClassWindow, kEventWindowZoomed },
+ { kEventClassKeyboard, kEventRawKeyDown },
+ { kEventClassKeyboard, kEventRawKeyUp },
+ { kEventClassKeyboard, kEventRawKeyRepeat },
+ { kEventClassKeyboard, kEventRawKeyModifiersChanged },
+ { kEventClassMouse, kEventMouseMoved },
+ { kEventClassMouse, kEventMouseDown },
+ { kEventClassMouse, kEventMouseUp },
+ { kEventClassMouse, kEventMouseDragged }
+ };
+
+ // Set up the handler
+ if (InstallWindowEventHandler(query->sender->carbonWindow,NewEventHandlerUPP(CarbonEventHandler),GetEventTypeCount(eventList),
+ eventList,(void*)query->sender,0)!=noErr) {
+ query->success = false;
+ cimg::warn("CImgDisplay::_events_thread() : InstallWindowEventHandler failed.");
+ }
+
+ // Paint
+ query->sender->paint();
+ } break;
+ default :
+ cimg::warn("CImgDisplay::_events_thread() : Received unknow code %d.",query->kind);
+ }
+ // Signal that the message has been processed
+ MPSignalSemaphore(c.sync_event);
+ }
+ }
+ }
+ // If we are here, the application is now finished
+ pthread_exit(0);
+ }
+
+ CImgDisplay& assign() {
+ if (is_empty()) return *this;
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+ // Destroy the window associated to the display
+ _CbFreeAttachedWindow(c);
+ // Don't destroy the background thread here.
+ // If you check whether _CbFreeAttachedWindow() returned true,
+ // - saying that there were no window left on screen - and
+ // you destroy the background thread here, ReceiveNextEvent won't
+ // work anymore if you create a new window after. So the
+ // background thread must be killed (pthread_cancel() + pthread_join())
+ // only on the application shutdown.
+
+ // Finalize graphics
+ _CbFinalizeGraphics();
+
+ // Do some cleanup
+ if (data) delete[] data;
+ if (title) delete[] title;
+ width = height = normalization = window_width = window_height = 0;
+ window_x = window_y = 0;
+ is_fullscreen = false;
+ is_closed = true;
+ min = max = 0;
+ title = 0;
+ flush();
+ if (MPDeleteCriticalRegion(paintCriticalRegion)!=noErr)
+ throw CImgDisplayException("CImgDisplay()::assign() : MPDeleteCriticalRegion failed.");
+ return *this;
+ }
+
+ CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ cimg::CarbonInfo& c = cimg::CarbonAttr();
+
+ // Allocate space for window title
+ const int s = cimg::strlen(ptitle)+1;
+ char *tmp_title = s?new char[s]:0;
+ if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));
+
+ // Destroy previous window if existing
+ if (!is_empty()) assign();
+
+ // Set display variables
+ width = cimg::min(dimw,(unsigned int)screen_dimx());
+ height = cimg::min(dimh,(unsigned int)screen_dimy());
+ normalization = normalization_type<4?normalization_type:3;
+ is_fullscreen = fullscreen_flag;
+ is_closed = closed_flag;
+ lastKeyModifiers = 0;
+ title = tmp_title;
+ flush();
+
+ // Create the paint CR
+ if (MPCreateCriticalRegion(&paintCriticalRegion) != noErr)
+ throw CImgDisplayException("CImgDisplay::_assign() : MPCreateCriticalRegion() failed.");
+
+ // Create the thread if it's not already created
+ if (c.event_thread==0) {
+ // Background thread does not exists, so create it !
+ if (pthread_create(&c.event_thread,0,_events_thread,0)!=0)
+ throw CImgDisplayException("CImgDisplay::_assign() : pthread_create() failed.");
+ // Wait for thread initialization
+ MPWaitOnSemaphore(c.sync_event, kDurationForever);
+ }
+
+ // Init disp. graphics
+ data = new unsigned int[width*height];
+ _CbInitializeGraphics();
+
+ // Now ask the thread to create the window
+ _CbCreateAttachedWindow(c,ptitle,fullscreen_flag,closed_flag);
+ return *this;
+ }
+
+#endif
+
+ };
+
+ /*
+ #--------------------------------------
+ #
+ #
+ #
+ # Definition of the CImg<T> structure
+ #
+ #
+ #
+ #--------------------------------------
+ */
+
+ //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T.
+ /**
+ This is the main class of the %CImg Library. It declares and constructs
+ an image, allows access to its pixel values, and is able to perform various image operations.
+
+ \par Image representation
+
+ A %CImg image is defined as an instance of the container \ref CImg<\c T>, which contains a regular grid of pixels,
+ each pixel value being of type \c T. The image grid can have up to 4 dimensions : width, height, depth
+ and number of channels.
+ Usually, the three first dimensions are used to describe spatial coordinates <tt>(x,y,z)</tt>, while the number of channels
+ is rather used as a vector-valued dimension (it may describe the R,G,B color channels for instance).
+ If you need a fifth dimension, you can use image lists \ref CImgList<\c T> rather than simple images \ref CImg<\c T>.
+
+ Thus, the \ref CImg<\c T> class is able to represent volumetric images of vector-valued pixels,
+ as well as images with less dimensions (1D scalar signal, 2D color images, ...).
+ Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions.
+
+ Concerning the pixel value type \c T :
+ fully supported template types are the basic C++ types : <tt>unsigned char, char, short, unsigned int, int,
+ unsigned long, long, float, double, ... </tt>.
+ Typically, fast image display can be done using <tt>CImg<unsigned char></tt> images,
+ while complex image processing algorithms may be rather coded using <tt>CImg<float></tt> or <tt>CImg<double></tt>
+ images that have floating-point pixel values. The default value for the template T is \c float.
+ Using your own template types may be possible. However, you will certainly have to define the complete set
+ of arithmetic and logical operators for your class.
+
+ \par Image structure
+
+ The \ref CImg<\c T> structure contains \a six fields :
+ - \ref width defines the number of \a columns of the image (size along the X-axis).
+ - \ref height defines the number of \a rows of the image (size along the Y-axis).
+ - \ref depth defines the number of \a slices of the image (size along the Z-axis).
+ - \ref dim defines the number of \a channels of the image (size along the V-axis).
+ - \ref data defines a \a pointer to the \a pixel \a data (of type \c T).
+ - \ref is_shared is a boolean that tells if the memory buffer \ref data is shared with
+ another image.
+
+ You can access these fields publicly although it is recommended to use the dedicated functions
+ dimx(), dimy(), dimz(), dimv() and ptr() to do so.
+ Image dimensions are not limited to a specific range (as long as you got enough available memory).
+ A value of \e 1 usually means that the corresponding dimension is \a flat.
+ If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty.
+ Empty images should not contain any pixel data and thus, will not be processed by CImg member functions
+ (a CImgInstanceException will be thrown instead).
+ Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage).
+
+ \par Image declaration and construction
+
+ Declaring an image can be done by using one of the several available constructors.
+ Here is a list of the most used :
+
+ - Construct images from arbitrary dimensions :
+ - <tt>CImg<char> img;</tt> declares an empty image.
+ - <tt>CImg<unsigned char> img(128,128);</tt> declares a 128x128 greyscale image with
+ \c unsigned \c char pixel values.
+ - <tt>CImg<double> img(3,3);</tt> declares a 3x3 matrix with \c double coefficients.
+ - <tt>CImg<unsigned char> img(256,256,1,3);</tt> declares a 256x256x1x3 (color) image
+ (colors are stored as an image with three channels).
+ - <tt>CImg<double> img(128,128,128);</tt> declares a 128x128x128 volumetric and greyscale image
+ (with \c double pixel values).
+ - <tt>CImg<> img(128,128,128,3);</tt> declares a 128x128x128 volumetric color image
+ (with \c float pixels, which is the default value of the template parameter \c T).
+ - \b Note : images pixels are <b>not automatically initialized to 0</b>. You may use the function \ref fill() to
+ do it, or use the specific constructor taking 5 parameters like this :
+ <tt>CImg<> img(128,128,128,3,0);</tt> declares a 128x128x128 volumetric color image with all pixel values to 0.
+
+ - Construct images from filenames :
+ - <tt>CImg<unsigned char> img("image.jpg");</tt> reads a JPEG color image from the file "image.jpg".
+ - <tt>CImg<float> img("analyze.hdr");</tt> reads a volumetric image (ANALYZE7.5 format) from the file "analyze.hdr".
+ - \b Note : You need to install <a href="http://www.imagemagick.org">ImageMagick</a>
+ to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io).
+
+ - Construct images from C-style arrays :
+ - <tt>CImg<int> img(data_buffer,256,256);</tt> constructs a 256x256 greyscale image from a \c int* buffer
+ \c data_buffer (of size 256x256=65536).
+ - <tt>CImg<unsigned char> img(data_buffer,256,256,1,3,false);</tt> constructs a 256x256 color image
+ from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others).
+ - <tt>CImg<unsigned char> img(data_buffer,256,256,1,3,true);</tt> constructs a 256x256 color image
+ from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels are multiplexed).
+
+ The complete list of constructors can be found <a href="#constructors">here</a>.
+
+ \par Most useful functions
+
+ The \ref CImg<\c T> class contains a lot of functions that operates on images.
+ Some of the most useful are :
+
+ - operator()(), operator[]() : allows to access or write pixel values.
+ - display() : displays the image in a new window.
+ **/
+ template<typename T>
+ struct CImg {
+
+ //! Variable representing the width of the instance image (i.e. dimensions along the X-axis).
+ /**
+ \remark
+ - Prefer using the function CImg<T>::dimx() to get information about the width of an image.
+ - Use function CImg<T>::resize() to set a new width for an image. Setting directly the variable \c width would probably
+ result in a library crash.
+ - Empty images have \c width defined to \c 0.
+ **/
+ unsigned int width;
+
+ //! Variable representing the height of the instance image (i.e. dimensions along the Y-axis).
+ /**
+ \remark
+ - Prefer using the function CImg<T>::dimy() to get information about the height of an image.
+ - Use function CImg<T>::resize() to set a new height for an image. Setting directly the variable \c height would probably
+ result in a library crash.
+ - 1D signals have \c height defined to \c 1.
+ - Empty images have \c height defined to \c 0.
+ **/
+ unsigned int height;
+
+ //! Variable representing the depth of the instance image (i.e. dimensions along the Z-axis).
+ /**
+ \remark
+ - Prefer using the function CImg<T>::dimz() to get information about the depth of an image.
+ - Use function CImg<T>::resize() to set a new depth for an image. Setting directly the variable \c depth would probably
+ result in a library crash.
+ - Classical 2D images have \c depth defined to \c 1.
+ - Empty images have \c depth defined to \c 0.
+ **/
+ unsigned int depth;
+
+ //! Variable representing the number of channels of the instance image (i.e. dimensions along the V-axis).
+ /**
+ \remark
+ - Prefer using the function CImg<T>::dimv() to get information about the depth of an image.
+ - Use function CImg<T>::resize() to set a new vector dimension for an image. Setting directly the variable \c dim would probably
+ result in a library crash.
+ - Scalar-valued images (one value per pixel) have \c dim defined to \c 1.
+ - Empty images have \c depth defined to \c 0.
+ **/
+ unsigned int dim;
+
+ //! Variable telling if pixel buffer of the instance image is shared with another one.
+ bool is_shared;
+
+ //! Pointer to the first pixel of the pixel buffer.
+ T *data;
+
+ //! Iterator type for CImg<T>.
+ /**
+ \remark
+ - An \p iterator is a <tt>T*</tt> pointer (address of a pixel value in the pixel buffer).
+ - Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL.
+ **/
+ typedef T* iterator;
+
+ //! Const iterator type for CImg<T>.
+ /**
+ \remark
+ - A \p const_iterator is a <tt>const T*</tt> pointer (address of a pixel value in the pixel buffer).
+ - Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL.
+ **/
+ typedef const T* const_iterator;
+
+ //! Get value type
+ typedef T value_type;
+
+ // Define common T-dependant types.
+ typedef typename cimg::superset<T,bool>::type Tbool;
+ typedef typename cimg::superset<T,unsigned char>::type Tuchar;
+ typedef typename cimg::superset<T,char>::type Tchar;
+ typedef typename cimg::superset<T,unsigned short>::type Tushort;
+ typedef typename cimg::superset<T,short>::type Tshort;
+ typedef typename cimg::superset<T,unsigned int>::type Tuint;
+ typedef typename cimg::superset<T,int>::type Tint;
+ typedef typename cimg::superset<T,unsigned long>::type Tulong;
+ typedef typename cimg::superset<T,long>::type Tlong;
+ typedef typename cimg::superset<T,float>::type Tfloat;
+ typedef typename cimg::superset<T,double>::type Tdouble;
+ typedef typename cimg::last<T,bool>::type boolT;
+ typedef typename cimg::last<T,unsigned char>::type ucharT;
+ typedef typename cimg::last<T,char>::type charT;
+ typedef typename cimg::last<T,unsigned short>::type ushortT;
+ typedef typename cimg::last<T,short>::type shortT;
+ typedef typename cimg::last<T,unsigned int>::type uintT;
+ typedef typename cimg::last<T,int>::type intT;
+ typedef typename cimg::last<T,unsigned long>::type ulongT;
+ typedef typename cimg::last<T,long>::type longT;
+ typedef typename cimg::last<T,float>::type floatT;
+ typedef typename cimg::last<T,double>::type doubleT;
+
+ //@}
+ //---------------------------
+ //
+ //! \name Plugins
+ //@{
+ //---------------------------
+#ifdef cimg_plugin
+#include cimg_plugin
+#endif
+#ifdef cimg_plugin1
+#include cimg_plugin1
+#endif
+#ifdef cimg_plugin2
+#include cimg_plugin2
+#endif
+#ifdef cimg_plugin3
+#include cimg_plugin3
+#endif
+#ifdef cimg_plugin4
+#include cimg_plugin4
+#endif
+#ifdef cimg_plugin5
+#include cimg_plugin5
+#endif
+#ifdef cimg_plugin6
+#include cimg_plugin6
+#endif
+#ifdef cimg_plugin7
+#include cimg_plugin7
+#endif
+#ifdef cimg_plugin8
+#include cimg_plugin8
+#endif
+#ifndef cimg_plugin_greycstoration
+#define cimg_plugin_greycstoration_count
+#endif
+#ifndef cimg_plugin_greycstoration_lock
+#define cimg_plugin_greycstoration_lock
+#endif
+#ifndef cimg_plugin_greycstoration_unlock
+#define cimg_plugin_greycstoration_unlock
+#endif
+
+ //@}
+
+ //--------------------------------------
+ //
+ //! \name Constructors-Destructor-Copy
+ //@{
+ //--------------------------------------
+
+ //! Destructor.
+ /**
+ The destructor destroys the instance image.
+ \remark
+ - Destructing an empty or shared image does nothing.
+ - Otherwise, all memory used to store the pixel data of the instance image is freed.
+ - When destroying a non-shared image, be sure that every shared instances of the same image are
+ also destroyed to avoid further access to desallocated memory buffers.
+ **/
+ ~CImg() {
+ if (data && !is_shared) delete[] data;
+ }
+
+ //! Default constructor.
+ /**
+ The default constructor creates an empty instance image.
+ \remark
+ - An empty image does not contain any data and has all of its dimensions \ref width, \ref height, \ref depth, \ref dim
+ set to 0 as well as its pointer to the pixel buffer \ref data.
+ - An empty image is non-shared.
+ **/
+ CImg():
+ width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {}
+
+ //! Constructs a new image with given size (\p dx,\p dy,\p dz,\p dv).
+ /**
+ This constructors create an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T.
+ \param dx Desired size along the X-axis, i.e. the \ref width of the image.
+ \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
+ \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
+ \param dv Desired size along the V-axis, i.e. the number of image channels \ref dim.
+ \remark
+ - If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the created image is empty
+ and all has its dimensions set to 0. No memory for pixel data is then allocated.
+ - This constructor creates only non-shared images.
+ - Image pixels allocated by this constructor are \b not \b initialized.
+ Use the constructor CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
+ to get an image of desired size with pixels set to a particular value.
+ **/
+ explicit CImg(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1):
+ is_shared(false) {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; }
+ else { width = height = depth = dim = 0; data = 0; }
+ }
+
+ //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with pixel having a default value \p val.
+ /**
+ This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T and sets all pixel
+ values of the created instance image to \p val.
+ \param dx Desired size along the X-axis, i.e. the \ref width of the image.
+ \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
+ \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
+ \param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
+ \param val Default value for image pixels.
+ \remark
+ - This constructor has the same properties as CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
+ **/
+ CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val):
+ is_shared(false) {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(val); }
+ else { width = height = depth = dim = 0; data = 0; }
+ }
+
+ //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (int version).
+ CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
+ const int val0, const int val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
+#define _CImg_stdarg(img,a0,a1,N,t) { \
+ unsigned int _siz = (unsigned int)N; \
+ if (_siz--) { \
+ va_list ap; \
+ va_start(ap,a1); \
+ T *ptrd = (img).data; \
+ *(ptrd++) = (T)a0; \
+ if (_siz--) { \
+ *(ptrd++) = (T)a1; \
+ for (; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \
+ } \
+ va_end(ap); \
+ }}
+ assign(dx,dy,dz,dv);
+ _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int);
+ }
+
+ //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (double version).
+ CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
+ const double val0, const double val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
+ assign(dx,dy,dz,dv);
+ _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double);
+ }
+
+ //! Construct an image with given size and with specified values given in a string.
+ CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
+ const char *const values, const bool repeat_pattern):is_shared(false) {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(values,repeat_pattern); }
+ else { width = height = depth = dim = 0; data = 0; }
+ }
+
+ //! Construct an image from a raw memory buffer.
+ /**
+ This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) and fill its pixel buffer by
+ copying data values from the input raw pixel buffer \p data_buffer.
+ **/
+ template<typename t>
+ CImg(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1, const bool shared=false):is_shared(false) {
+ if (shared)
+ throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a (%s*) buffer "
+ "(different pixel types).",
+ pixel_type(),CImg<t>::pixel_type());
+ const unsigned long siz = dx*dy*dz*dv;
+ if (data_buffer && siz) {
+ width = dx; height = dy; depth = dz; dim = dv; data = new T[siz];
+ const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+ } else { width = height = depth = dim = 0; data = 0; }
+ }
+
+#ifndef cimg_use_visualcpp6
+ CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1, const bool shared=false)
+#else
+ CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
+ const unsigned int dz, const unsigned int dv, const bool shared)
+#endif
+ {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (data_buffer && siz) {
+ width = dx; height = dy; depth = dz; dim = dv; is_shared = shared;
+ if (is_shared) data = const_cast<T*>(data_buffer);
+ else { data = new T[siz]; cimg_std::memcpy(data,data_buffer,siz*sizeof(T)); }
+ } else { width = height = depth = dim = 0; is_shared = false; data = 0; }
+ }
+
+ //! Default copy constructor.
+ /**
+ The default copy constructor creates a new instance image having same dimensions
+ (\ref width, \ref height, \ref depth, \ref dim) and same pixel values as the input image \p img.
+ \param img The input image to copy.
+ \remark
+ - If the input image \p img is non-shared or have a different template type \p t != \p T,
+ the default copy constructor allocates a new pixel buffer and copy the pixel data
+ of \p img into it. In this case, the pointers \ref data to the pixel buffers of the two images are different
+ and the resulting instance image is non-shared.
+ - If the input image \p img is shared and has the same template type \p t == \p T,
+ the default copy constructor does not allocate a new pixel buffer and the resulting instance image
+ shares its pixel buffer with the input image \p img, which means that modifying pixels of \p img also modifies
+ the created instance image.
+ - Copying an image having a different template type \p t != \p T performs a crude static cast conversion of each pixel value from
+ type \p t to type \p T.
+ - Copying an image having the same template type \p t == \p T is significantly faster.
+ **/
+ template<typename t>
+ CImg(const CImg<t>& img):is_shared(false) {
+ const unsigned int siz = img.size();
+ if (img.data && siz) {
+ width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz];
+ const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+ } else { width = height = depth = dim = 0; data = 0; }
+ }
+
+ CImg(const CImg<T>& img) {
+ const unsigned int siz = img.size();
+ if (img.data && siz) {
+ width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = img.is_shared;
+ if (is_shared) data = const_cast<T*>(img.data);
+ else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); }
+ } else { width = height = depth = dim = 0; is_shared = false; data = 0; }
+ }
+
+ //! Advanced copy constructor.
+ /**
+ The advanced copy constructor - as the default constructor CImg(const CImg< t >&) - creates a new instance image having same dimensions
+ \ref width, \ref height, \ref depth, \ref dim and same pixel values as the input image \p img.
+ But it also decides if the created instance image shares its memory with the input image \p img (if the input parameter
+ \p shared is set to \p true) or not (if the input parameter \p shared is set to \p false).
+ \param img The input image to copy.
+ \param shared Boolean flag that decides if the copy is shared on non-shared.
+ \remark
+ - It is not possible to create a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
+ - If a non-shared copy of the input image \p img is created, a new memory buffer is allocated for pixel data.
+ - If a shared copy of the input image \p img is created, no extra memory is allocated and the pixel buffer of the instance
+ image is the same as the one used by the input image \p img.
+ **/
+ template<typename t>
+ CImg(const CImg<t>& img, const bool shared):is_shared(false) {
+ if (shared)
+ throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a CImg<%s> instance "
+ "(different pixel types).",
+ pixel_type(),CImg<t>::pixel_type());
+ const unsigned int siz = img.size();
+ if (img.data && siz) {
+ width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz];
+ const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+ } else { width = height = depth = dim = 0; data = 0; }
+ }
+
+ CImg(const CImg<T>& img, const bool shared) {
+ const unsigned int siz = img.size();
+ if (img.data && siz) {
+ width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = shared;
+ if (is_shared) data = const_cast<T*>(img.data);
+ else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); }
+ } else { width = height = depth = dim = 0; is_shared = false; data = 0; }
+ }
+
+ //! Construct an image using dimensions of another image
+ template<typename t>
+ CImg(const CImg<t>& img, const char *const dimensions):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
+ assign(img,dimensions);
+ }
+
+ //! Construct an image using dimensions of another image, and fill it with a default value
+ template<typename t>
+ CImg(const CImg<t>& img, const char *const dimensions, const T val):
+ width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
+ assign(img,dimensions).fill(val);
+ }
+
+ //! Construct an image from an image file.
+ /**
+ This constructor creates an instance image by reading it from a file.
+ \param filename Filename of the image file.
+ \remark
+ - The image format is deduced from the filename only by looking for the filename extension i.e. without
+ analyzing the file itself.
+ - Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with.
+ More informations on this topic can be found in cimg_files_io.
+ - If the filename is not found, a CImgIOException is thrown by this constructor.
+ **/
+ CImg(const char *const filename):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
+ assign(filename);
+ }
+
+ //! Construct an image from the content of a CImgDisplay instance.
+ CImg(const CImgDisplay &disp):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
+ disp.snapshot(*this);
+ }
+
+ //! In-place version of the default constructor/destructor.
+ /**
+ This function replaces the instance image by an empty image.
+ \remark
+ - Memory used by the previous content of the instance image is freed if necessary.
+ - If the instance image was initially shared, it is replaced by a (non-shared) empty image.
+ - This function is useful to free memory used by an image that is not of use, but which
+ has been created in the current code scope (i.e. not destroyed yet).
+ **/
+ CImg<T>& assign() {
+ if (data && !is_shared) delete[] data;
+ width = height = depth = dim = 0; is_shared = false; data = 0;
+ return *this;
+ }
+
+ //! In-place version of the default constructor.
+ /**
+ This function is strictly equivalent to \ref assign() and has been
+ introduced for having a STL-compliant function name.
+ **/
+ CImg<T>& clear() {
+ return assign();
+ }
+
+ //! In-place version of the previous constructor.
+ /**
+ This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T.
+ \param dx Desired size along the X-axis, i.e. the \ref width of the image.
+ \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
+ \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
+ \param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
+ - If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the instance image becomes empty
+ and all has its dimensions set to 0. No memory for pixel data is then allocated.
+ - Memory buffer used to store previous pixel values is freed if necessary.
+ - If the instance image is shared, this constructor actually does nothing more than verifying
+ that new and old image dimensions fit.
+ - Image pixels allocated by this function are \b not \b initialized.
+ Use the function assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
+ to assign an image of desired size with pixels set to a particular value.
+ **/
+ CImg<T>& assign(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1) {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (!siz) return assign();
+ const unsigned long curr_siz = size();
+ if (siz!=curr_siz) {
+ if (is_shared)
+ throw CImgArgumentException("CImg<%s>::assign() : Cannot assign image (%u,%u,%u,%u) to shared instance image (%u,%u,%u,%u,%p).",
+ pixel_type(),dx,dy,dz,dv,width,height,depth,dim,data);
+ else { if (data) delete[] data; data = new T[siz]; }
+ }
+ width = dx; height = dy; depth = dz; dim = dv;
+ return *this;
+ }
+
+ //! In-place version of the previous constructor.
+ /**
+ This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T
+ and sets all pixel values of the instance image to \p val.
+ \param dx Desired size along the X-axis, i.e. the \ref width of the image.
+ \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
+ \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
+ \param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
+ \param val Default value for image pixels.
+ \remark
+ - This function has the same properties as assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
+ **/
+ CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val) {
+ return assign(dx,dy,dz,dv).fill(val);
+ }
+
+ //! In-place version of the previous constructor.
+ CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
+ const int val0, const int val1, ...) {
+ assign(dx,dy,dz,dv);
+ _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int);
+ return *this;
+ }
+
+ //! In-place version of the previous constructor.
+ CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
+ const double val0, const double val1, ...) {
+ assign(dx,dy,dz,dv);
+ _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double);
+ return *this;
+ }
+
+ //! In-place version of the previous constructor.
+ template<typename t>
+ CImg<T>& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1) {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (!data_buffer || !siz) return assign();
+ assign(dx,dy,dz,dv);
+ const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+ return *this;
+ }
+
+#ifndef cimg_use_visualcpp6
+ CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1)
+#else
+ CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
+ const unsigned int dz, const unsigned int dv)
+#endif
+ {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (!data_buffer || !siz) return assign();
+ const unsigned long curr_siz = size();
+ if (data_buffer==data && siz==curr_siz) return assign(dx,dy,dz,dv);
+ if (is_shared || data_buffer+siz<data || data_buffer>=data+size()) {
+ assign(dx,dy,dz,dv);
+ if (is_shared) cimg_std::memmove(data,data_buffer,siz*sizeof(T));
+ else cimg_std::memcpy(data,data_buffer,siz*sizeof(T));
+ } else {
+ T *new_data = new T[siz];
+ cimg_std::memcpy(new_data,data_buffer,siz*sizeof(T));
+ delete[] data; data = new_data; width = dx; height = dy; depth = dz; dim = dv;
+ }
+ return *this;
+ }
+
+ //! In-place version of the previous constructor, allowing to force the shared state of the instance image.
+ template<typename t>
+ CImg<T>& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy,
+ const unsigned int dz, const unsigned int dv, const bool shared) {
+ if (shared)
+ throw CImgArgumentException("CImg<%s>::assign() : Cannot assign buffer (%s*) to shared instance image (%u,%u,%u,%u,%p)"
+ "(different pixel types).",
+ pixel_type(),CImg<t>::pixel_type(),width,height,depth,dim,data);
+ return assign(data_buffer,dx,dy,dz,dv);
+ }
+
+ CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
+ const unsigned int dz, const unsigned int dv, const bool shared) {
+ const unsigned long siz = dx*dy*dz*dv;
+ if (!data_buffer || !siz) return assign();
+ if (!shared) { if (is_shared) assign(); assign(data_buffer,dx,dy,dz,dv); }
+ else {
+ if (!is_shared) {
+ if (data_buffer+siz<data || data_buffer>=data+size()) assign();
+ else cimg::warn("CImg<%s>::assign() : Shared instance image has overlapping memory !",
+ pixel_type());
+ }
+ width = dx; height = dy; depth = dz; dim = dv; is_shared = true;
+ data = const_cast<T*>(data_buffer);
+ }
+ return *this;
+ }
+
+ //! In-place version of the default copy constructor.
+ /**
+ This function assigns a copy of the input image \p img to the current instance image.
+ \param img The input image to copy.
+ \remark
+ - If the instance image is not shared, the content of the input image \p img is copied into a new buffer
+ becoming the new pixel buffer of the instance image, while the old pixel buffer is freed if necessary.
+ - If the instance image is shared, the content of the input image \p img is copied into the current (shared) pixel buffer
+ of the instance image, modifying then the image referenced by the shared instance image. The instance image still remains shared.
+ **/
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img) {
+ return assign(img.data,img.width,img.height,img.depth,img.dim);
+ }
+
+ //! In-place version of the advanced constructor.
+ /**
+ This function - as the simpler function assign(const CImg< t >&) - assigns a copy of the input image \p img to the
+ current instance image. But it also decides if the copy is shared (if the input parameter \p shared is set to \c true)
+ or non-shared (if the input parameter \p shared is set to \c false).
+ \param img The input image to copy.
+ \param shared Boolean flag that decides if the copy is shared or non-shared.
+ \remark
+ - It is not possible to assign a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
+ - If a non-shared copy of the input image \p img is assigned, a new memory buffer is allocated for pixel data.
+ - If a shared copy of the input image \p img is assigned, no extra memory is allocated and the pixel buffer of the instance
+ image is the same as the one used by the input image \p img.
+ **/
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img, const bool shared) {
+ return assign(img.data,img.width,img.height,img.depth,img.dim,shared);
+ }
+
+ //! In-place version of the previous constructor.
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img, const char *const dimensions) {
+ if (dimensions) {
+ unsigned int siz[4] = { 0,1,1,1 };
+ const char *s = dimensions;
+ char tmp[256] = { 0 }, c = 0;
+ int val = 0;
+ for (unsigned int k=0; k<4; ++k) {
+ const int err = cimg_std::sscanf(s,"%[-0-9]%c",tmp,&c);
+ if (err>=1) {
+ const int err = cimg_std::sscanf(s,"%d",&val);
+ if (err==1) {
+ int val2 = val<0?-val:(c=='%'?val:-1);
+ if (val2>=0) {
+ val = (int)((k==0?img.width:(k==1?img.height:(k==2?img.depth:img.dim)))*val2/100);
+ if (c!='%' && !val) val = 1;
+ }
+ siz[k] = val;
+ }
+ s+=cimg::strlen(tmp);
+ if (c=='%') ++s;
+ }
+ if (!err) {
+ if (!cimg::strncasecmp(s,"x",1)) { ++s; siz[k] = img.width; }
+ else if (!cimg::strncasecmp(s,"y",1)) { ++s; siz[k] = img.height; }
+ else if (!cimg::strncasecmp(s,"z",1)) { ++s; siz[k] = img.depth; }
+ else if (!cimg::strncasecmp(s,"v",1)) { ++s; siz[k] = img.dim; }
+ else if (!cimg::strncasecmp(s,"dx",2)) { s+=2; siz[k] = img.width; }
+ else if (!cimg::strncasecmp(s,"dy",2)) { s+=2; siz[k] = img.height; }
+ else if (!cimg::strncasecmp(s,"dz",2)) { s+=2; siz[k] = img.depth; }
+ else if (!cimg::strncasecmp(s,"dv",2)) { s+=2; siz[k] = img.dim; }
+ else if (!cimg::strncasecmp(s,"dimx",4)) { s+=4; siz[k] = img.width; }
+ else if (!cimg::strncasecmp(s,"dimy",4)) { s+=4; siz[k] = img.height; }
+ else if (!cimg::strncasecmp(s,"dimz",4)) { s+=4; siz[k] = img.depth; }
+ else if (!cimg::strncasecmp(s,"dimv",4)) { s+=4; siz[k] = img.dim; }
+ else if (!cimg::strncasecmp(s,"width",5)) { s+=5; siz[k] = img.width; }
+ else if (!cimg::strncasecmp(s,"height",6)) { s+=6; siz[k] = img.height; }
+ else if (!cimg::strncasecmp(s,"depth",5)) { s+=5; siz[k] = img.depth; }
+ else if (!cimg::strncasecmp(s,"dim",3)) { s+=3; siz[k] = img.dim; }
+ else { ++s; --k; }
+ }
+ }
+ return assign(siz[0],siz[1],siz[2],siz[3]);
+ }
+ return assign();
+ }
+
+ //! In-place version of the previous constructor.
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img, const char *const dimensions, const T val) {
+ return assign(img,dimensions).fill(val);
+ }
+
+ //! In-place version of the previous constructor.
+ /**
+ This function replaces the instance image by the one that have been read from the given file.
+ \param filename Filename of the image file.
+ - The image format is deduced from the filename only by looking for the filename extension i.e. without
+ analyzing the file itself.
+ - Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with.
+ More informations on this topic can be found in cimg_files_io.
+ - If the filename is not found, a CImgIOException is thrown by this constructor.
+ **/
+ CImg<T>& assign(const char *const filename) {
+ return load(filename);
+ }
+
+ //! In-place version of the previous constructor.
+ CImg<T>& assign(const CImgDisplay &disp) {
+ disp.snapshot(*this);
+ return *this;
+ }
+
+ //! Transfer the content of the instance image into another one in a way that memory copies are avoided if possible.
+ /**
+ The instance image is always empty after a call to this function.
+ **/
+ template<typename t>
+ CImg<t>& transfer_to(CImg<t>& img) {
+ img.assign(*this);
+ assign();
+ return img;
+ }
+
+ CImg<T>& transfer_to(CImg<T>& img) {
+ if (is_shared || img.is_shared) { img.assign(*this); assign(); } else { img.assign(); swap(img); }
+ return img;
+ }
+
+ //! Swap all fields of two images. Use with care !
+ CImg<T>& swap(CImg<T>& img) {
+ cimg::swap(width,img.width);
+ cimg::swap(height,img.height);
+ cimg::swap(depth,img.depth);
+ cimg::swap(dim,img.dim);
+ cimg::swap(data,img.data);
+ cimg::swap(is_shared,img.is_shared);
+ return img;
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Image Informations
+ //@{
+ //-------------------------------------
+
+ //! Return the type of the pixel values.
+ /**
+ \return a string describing the type of the image pixels (template parameter \p T).
+ - The string returned may contains spaces (<tt>"unsigned char"</tt>).
+ - If the template parameter T does not correspond to a registered type, the string <tt>"unknown"</tt> is returned.
+ **/
+ static const char* pixel_type() {
+ return cimg::type<T>::string();
+ }
+
+ //! Return the total number of pixel values in an image.
+ /**
+ - Equivalent to : dimx() * dimy() * dimz() * dimv().
+
+ \par example:
+ \code
+ CImg<> img(100,100,1,3);
+ if (img.size()==100*100*3) std::fprintf(stderr,"This statement is true");
+ \endcode
+ **/
+ unsigned long size() const {
+ return width*height*depth*dim;
+ }
+
+ //! Return the number of columns of the instance image (size along the X-axis, i.e image width).
+ int dimx() const {
+ return (int)width;
+ }
+
+ //! Return the number of rows of the instance image (size along the Y-axis, i.e image height).
+ int dimy() const {
+ return (int)height;
+ }
+
+ //! Return the number of slices of the instance image (size along the Z-axis).
+ int dimz() const {
+ return (int)depth;
+ }
+
+ //! Return the number of vector channels of the instance image (size along the V-axis).
+ int dimv() const {
+ return (int)dim;
+ }
+
+ //! Return \c true if image (*this) has the specified width.
+ bool is_sameX(const unsigned int dx) const {
+ return (width==dx);
+ }
+
+ //! Return \c true if images \c (*this) and \c img have same width.
+ template<typename t>
+ bool is_sameX(const CImg<t>& img) const {
+ return is_sameX(img.width);
+ }
+
+ //! Return \c true if images \c (*this) and the display \c disp have same width.
+ bool is_sameX(const CImgDisplay& disp) const {
+ return is_sameX(disp.width);
+ }
+
+ //! Return \c true if image (*this) has the specified height.
+ bool is_sameY(const unsigned int dy) const {
+ return (height==dy);
+ }
+
+ //! Return \c true if images \c (*this) and \c img have same height.
+ template<typename t>
+ bool is_sameY(const CImg<t>& img) const {
+ return is_sameY(img.height);
+ }
+
+ //! Return \c true if images \c (*this) and the display \c disp have same height.
+ bool is_sameY(const CImgDisplay& disp) const {
+ return is_sameY(disp.height);
+ }
+
+ //! Return \c true if image (*this) has the specified depth.
+ bool is_sameZ(const unsigned int dz) const {
+ return (depth==dz);
+ }
+
+ //! Return \c true if images \c (*this) and \c img have same depth.
+ template<typename t>
+ bool is_sameZ(const CImg<t>& img) const {
+ return is_sameZ(img.depth);
+ }
+
+ //! Return \c true if image (*this) has the specified number of channels.
+ bool is_sameV(const unsigned int dv) const {
+ return (dim==dv);
+ }
+
+ //! Return \c true if images \c (*this) and \c img have same dim.
+ template<typename t>
+ bool is_sameV(const CImg<t>& img) const {
+ return is_sameV(img.dim);
+ }
+
+ //! Return \c true if image (*this) has the specified width and height.
+ bool is_sameXY(const unsigned int dx, const unsigned int dy) const {
+ return (is_sameX(dx) && is_sameY(dy));
+ }
+
+ //! Return \c true if images have same width and same height.
+ template<typename t>
+ bool is_sameXY(const CImg<t>& img) const {
+ return (is_sameX(img) && is_sameY(img));
+ }
+
+ //! Return \c true if image \c (*this) and the display \c disp have same width and same height.
+ bool is_sameXY(const CImgDisplay& disp) const {
+ return (is_sameX(disp) && is_sameY(disp));
+ }
+
+ //! Return \c true if image (*this) has the specified width and depth.
+ bool is_sameXZ(const unsigned int dx, const unsigned int dz) const {
+ return (is_sameX(dx) && is_sameZ(dz));
+ }
+
+ //! Return \c true if images have same width and same depth.
+ template<typename t>
+ bool is_sameXZ(const CImg<t>& img) const {
+ return (is_sameX(img) && is_sameZ(img));
+ }
+
+ //! Return \c true if image (*this) has the specified width and number of channels.
+ bool is_sameXV(const unsigned int dx, const unsigned int dv) const {
+ return (is_sameX(dx) && is_sameV(dv));
+ }
+
+ //! Return \c true if images have same width and same number of channels.
+ template<typename t>
+ bool is_sameXV(const CImg<t>& img) const {
+ return (is_sameX(img) && is_sameV(img));
+ }
+
+ //! Return \c true if image (*this) has the specified height and depth.
+ bool is_sameYZ(const unsigned int dy, const unsigned int dz) const {
+ return (is_sameY(dy) && is_sameZ(dz));
+ }
+
+ //! Return \c true if images have same height and same depth.
+ template<typename t>
+ bool is_sameYZ(const CImg<t>& img) const {
+ return (is_sameY(img) && is_sameZ(img));
+ }
+
+ //! Return \c true if image (*this) has the specified height and number of channels.
+ bool is_sameYV(const unsigned int dy, const unsigned int dv) const {
+ return (is_sameY(dy) && is_sameV(dv));
+ }
+
+ //! Return \c true if images have same height and same number of channels.
+ template<typename t>
+ bool is_sameYV(const CImg<t>& img) const {
+ return (is_sameY(img) && is_sameV(img));
+ }
+
+ //! Return \c true if image (*this) has the specified depth and number of channels.
+ bool is_sameZV(const unsigned int dz, const unsigned int dv) const {
+ return (is_sameZ(dz) && is_sameV(dv));
+ }
+
+ //! Return \c true if images have same depth and same number of channels.
+ template<typename t>
+ bool is_sameZV(const CImg<t>& img) const {
+ return (is_sameZ(img) && is_sameV(img));
+ }
+
+ //! Return \c true if image (*this) has the specified width, height and depth.
+ bool is_sameXYZ(const unsigned int dx, const unsigned int dy, const unsigned int dz) const {
+ return (is_sameXY(dx,dy) && is_sameZ(dz));
+ }
+
+ //! Return \c true if images have same width, same height and same depth.
+ template<typename t>
+ bool is_sameXYZ(const CImg<t>& img) const {
+ return (is_sameXY(img) && is_sameZ(img));
+ }
+
+ //! Return \c true if image (*this) has the specified width, height and depth.
+ bool is_sameXYV(const unsigned int dx, const unsigned int dy, const unsigned int dv) const {
+ return (is_sameXY(dx,dy) && is_sameV(dv));
+ }
+
+ //! Return \c true if images have same width, same height and same number of channels.
+ template<typename t>
+ bool is_sameXYV(const CImg<t>& img) const {
+ return (is_sameXY(img) && is_sameV(img));
+ }
+
+ //! Return \c true if image (*this) has the specified width, height and number of channels.
+ bool is_sameXZV(const unsigned int dx, const unsigned int dz, const unsigned int dv) const {
+ return (is_sameXZ(dx,dz) && is_sameV(dv));
+ }
+
+ //! Return \c true if images have same width, same depth and same number of channels.
+ template<typename t>
+ bool is_sameXZV(const CImg<t>& img) const {
+ return (is_sameXZ(img) && is_sameV(img));
+ }
+
+ //! Return \c true if image (*this) has the specified height, depth and number of channels.
+ bool is_sameYZV(const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
+ return (is_sameYZ(dy,dz) && is_sameV(dv));
+ }
+
+ //! Return \c true if images have same height, same depth and same number of channels.
+ template<typename t>
+ bool is_sameYZV(const CImg<t>& img) const {
+ return (is_sameYZ(img) && is_sameV(img));
+ }
+
+ //! Return \c true if image (*this) has the specified width, height, depth and number of channels.
+ bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
+ return (is_sameXYZ(dx,dy,dz) && is_sameV(dv));
+ }
+
+ //! Return \c true if images \c (*this) and \c img have same width, same height, same depth and same number of channels.
+ template<typename t>
+ bool is_sameXYZV(const CImg<t>& img) const {
+ return (is_sameXYZ(img) && is_sameV(img));
+ }
+
+ //! Return \c true if current image is empty.
+ bool is_empty() const {
+ return !(data && width && height && depth && dim);
+ }
+
+ //! Return \p true if image is not empty.
+ operator bool() const {
+ return !is_empty();
+ }
+
+ //! Return an iterator to the first image pixel
+ iterator begin() {
+ return data;
+ }
+
+ const_iterator begin() const {
+ return data;
+ }
+
+ //! Return reference to the first image pixel
+ const T& first() const {
+ return *data;
+ }
+
+ T& first() {
+ return *data;
+ }
+
+ //! Return an iterator pointing after the last image pixel
+ iterator end() {
+ return data + size();
+ }
+
+ const_iterator end() const {
+ return data + size();
+ }
+
+ //! Return a reference to the last image pixel
+ const T& last() const {
+ return data[size() - 1];
+ }
+
+ T& last() {
+ return data[size() - 1];
+ }
+
+ //! Return a pointer to the pixel buffer.
+ T* ptr() {
+ return data;
+ }
+
+ const T* ptr() const {
+ return data;
+ }
+
+ //! Return a pointer to the pixel value located at (\p x,\p y,\p z,\p v).
+ /**
+ \param x X-coordinate of the pixel.
+ \param y Y-coordinate of the pixel.
+ \param z Z-coordinate of the pixel.
+ \param v V-coordinate of the pixel.
+
+ - When called without parameters, ptr() returns a pointer to the begining of the pixel buffer.
+ - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear if
+ given coordinates are outside the image range (but function performances decrease).
+
+ \par example:
+ \code
+ CImg<float> img(100,100,1,1,0); // Define a 100x100 greyscale image with float-valued pixels.
+ float *ptr = ptr(10,10); // Get a pointer to the pixel located at (10,10).
+ float val = *ptr; // Get the pixel value.
+ \endcode
+ **/
+#if cimg_debug>=3
+ T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
+ const long off = offset(x,y,z,v);
+ if (off<0 || off>=(long)size()) {
+ cimg::warn("CImg<%s>::ptr() : Asked for a pointer at coordinates (%u,%u,%u,%u) (offset=%ld), "
+ "outside image range (%u,%u,%u,%u) (size=%lu)",
+ pixel_type(),x,y,z,v,off,width,height,depth,dim,size());
+ return data;
+ }
+ return data + off;
+ }
+
+ const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
+ return const_cast<CImg<T>*>(this)->ptr(x,y,z,v);
+ }
+#else
+ T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
+ return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
+ }
+
+ const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
+ return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
+ }
+#endif
+
+ //! Return \c true if the memory buffers of the two images overlaps.
+ /**
+ May happen when using shared images.
+ **/
+ template<typename t>
+ bool is_overlapped(const CImg<t>& img) const {
+ const unsigned long csiz = size(), isiz = img.size();
+ return !((void*)(data+csiz)<=(void*)img.data || (void*)data>=(void*)(img.data+isiz));
+ }
+
+ //! Return the offset of the pixel coordinates (\p x,\p y,\p z,\p v) with respect to the data pointer \c data.
+ /**
+ \param x X-coordinate of the pixel.
+ \param y Y-coordinate of the pixel.
+ \param z Z-coordinate of the pixel.
+ \param v V-coordinate of the pixel.
+
+ - No checking is done on the validity of the given coordinates.
+
+ \par Example:
+ \code
+ CImg<float> img(100,100,1,3,0); // Define a 100x100 color image with float-valued black pixels.
+ long off = img.offset(10,10,0,2); // Get the offset of the blue value of the pixel located at (10,10).
+ float val = img[off]; // Get the blue value of the pixel.
+ \endcode
+ **/
+ long offset(const int x, const int y=0, const int z=0, const int v=0) const {
+ return (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
+ }
+
+ //! Fast access to pixel value for reading or writing.
+ /**
+ \param x X-coordinate of the pixel.
+ \param y Y-coordinate of the pixel.
+ \param z Z-coordinate of the pixel.
+ \param v V-coordinate of the pixel.
+
+ - If one image dimension is equal to 1, it can be omitted in the coordinate list (see example below).
+ - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear
+ (but function performances decrease).
+
+ \par example:
+ \code
+ CImg<float> img(100,100,1,3,0); // Define a 100x100 color image with float-valued black pixels.
+ const float valR = img(10,10,0,0); // Read the red component at coordinates (10,10).
+ const float valG = img(10,10,0,1); // Read the green component at coordinates (10,10)
+ const float valB = img(10,10,2); // Read the blue component at coordinates (10,10) (Z-coordinate omitted here).
+ const float avg = (valR + valG + valB)/3; // Compute average pixel value.
+ img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the pixel (10,10) by the average grey value.
+ \endcode
+ **/
+#if cimg_debug>=3
+ T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
+ const long off = offset(x,y,z,v);
+ if (!data || off>=(long)size()) {
+ cimg::warn("CImg<%s>::operator() : Pixel access requested at (%u,%u,%u,%u) (offset=%ld) "
+ "outside the image range (%u,%u,%u,%u) (size=%lu)",
+ pixel_type(),x,y,z,v,off,width,height,depth,dim,size());
+ return *data;
+ }
+ else return data[off];
+ }
+
+ const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
+ return const_cast<CImg<T>*>(this)->operator()(x,y,z,v);
+ }
+#else
+ T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
+ return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth];
+ }
+
+ const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
+ return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth];
+ }
+#endif
+
+ //! Fast access to pixel value for reading or writing, using an offset to the image pixel.
+ /**
+ \param off Offset of the pixel according to the begining of the pixel buffer, given by ptr().
+
+ - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear
+ (but function performances decrease).
+ - As pixel values are aligned in memory, this operator can sometime useful to access values easier than
+ with operator()() (see example below).
+
+ \par example:
+ \code
+ CImg<float> vec(1,10); // Define a vector of float values (10 lines, 1 row).
+ const float val1 = vec(0,4); // Get the fifth element using operator()().
+ const float val2 = vec[4]; // Get the fifth element using operator[]. Here, val2==val1.
+ \endcode
+ **/
+#if cimg_debug>=3
+ T& operator[](const unsigned long off) {
+ if (!data || off>=size()) {
+ cimg::warn("CImg<%s>::operator[] : Pixel access requested at offset=%lu "
+ "outside the image range (%u,%u,%u,%u) (size=%lu)",
+ pixel_type(),off,width,height,depth,dim,size());
+ return *data;
+ }
+ else return data[off];
+ }
+
+ const T& operator[](const unsigned long off) const {
+ return const_cast<CImg<T>*>(this)->operator[](off);
+ }
+#else
+ T& operator[](const unsigned long off) {
+ return data[off];
+ }
+
+ const T& operator[](const unsigned long off) const {
+ return data[off];
+ }
+#endif
+
+ //! Return a reference to the last image value
+ T& back() {
+ return operator()(size()-1);
+ }
+
+ const T& back() const {
+ return operator()(size()-1);
+ }
+
+ //! Return a reference to the first image value
+ T& front() {
+ return *data;
+ }
+
+ const T& front() const {
+ return *data;
+ }
+
+ //! Return \c true if pixel (x,y,z,v) is inside image boundaries.
+ bool containsXYZV(const int x, const int y=0, const int z=0, const int v=0) const {
+ return !is_empty() && x>=0 && x<dimx() && y>=0 && y<dimy() && z>=0 && z<dimz() && v>=0 && v<dimv();
+ }
+
+ //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z,v).
+ template<typename t>
+ bool contains(const T& pixel, t& x, t& y, t& z, t& v) const {
+ const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim;
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
+ unsigned long off = (unsigned long)(ppixel - data);
+ const unsigned long nv = off/whz;
+ off%=whz;
+ const unsigned long nz = off/wh;
+ off%=wh;
+ const unsigned long ny = off/width, nx = off%width;
+ x = (t)nx; y = (t)ny; z = (t)nz; v = (t)nv;
+ return true;
+ }
+
+ //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z).
+ template<typename t>
+ bool contains(const T& pixel, t& x, t& y, t& z) const {
+ const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim;
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
+ unsigned long off = ((unsigned long)(ppixel - data))%whz;
+ const unsigned long nz = off/wh;
+ off%=wh;
+ const unsigned long ny = off/width, nx = off%width;
+ x = (t)nx; y = (t)ny; z = (t)nz;
+ return true;
+ }
+
+ //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y).
+ template<typename t>
+ bool contains(const T& pixel, t& x, t& y) const {
+ const unsigned long wh = width*height, siz = wh*depth*dim;
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
+ unsigned long off = ((unsigned long)(ppixel - data))%wh;
+ const unsigned long ny = off/width, nx = off%width;
+ x = (t)nx; y = (t)ny;
+ return true;
+ }
+
+ //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x).
+ template<typename t>
+ bool contains(const T& pixel, t& x) const {
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<data || ppixel>=data+size()) return false;
+ x = (t)(((unsigned long)(ppixel - data))%width);
+ return true;
+ }
+
+ //! Return \c true if specified referenced value is inside the image boundaries.
+ bool contains(const T& pixel) const {
+ const T *const ppixel = &pixel;
+ return !is_empty() && ppixel>=data && ppixel<data+size();
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions.
+ T& at(const int off, const T out_val) {
+ return (off<0 || off>=(int)size())?(cimg::temporary(out_val)=out_val):(*this)[off];
+ }
+
+ T at(const int off, const T out_val) const {
+ return (off<0 || off>=(int)size())?out_val:(*this)[off];
+ }
+
+ //! Read a pixel value with Neumann boundary conditions.
+ T& at(const int off) {
+ if (!size())
+ throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.",
+ pixel_type());
+ return _at(off);
+ }
+
+ T at(const int off) const {
+ if (!size())
+ throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.",
+ pixel_type());
+ return _at(off);
+ }
+
+ T& _at(const int off) {
+ const unsigned int siz = (unsigned int)size();
+ return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
+ }
+
+ T _at(const int off) const {
+ const unsigned int siz = (unsigned int)size();
+ return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions.
+ T& atXYZV(const int x, const int y, const int z, const int v, const T out_val) {
+ return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())?
+ (cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
+ }
+
+ T atXYZV(const int x, const int y, const int z, const int v, const T out_val) const {
+ return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())?out_val:(*this)(x,y,z,v);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions.
+ T& atXYZV(const int x, const int y, const int z, const int v) {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.",
+ pixel_type());
+ return _atXYZV(x,y,z,v);
+ }
+
+ T atXYZV(const int x, const int y, const int z, const int v) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.",
+ pixel_type());
+ return _atXYZV(x,y,z,v);
+ }
+
+ T& _atXYZV(const int x, const int y, const int z, const int v) {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),
+ z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v));
+ }
+
+ T _atXYZV(const int x, const int y, const int z, const int v) const {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),
+ z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v));
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c x,\c y,\c z).
+ T& atXYZ(const int x, const int y, const int z, const int v, const T out_val) {
+ return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())?
+ (cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
+ }
+
+ T atXYZ(const int x, const int y, const int z, const int v, const T out_val) const {
+ return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())?out_val:(*this)(x,y,z,v);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c x,\c y,\c z).
+ T& atXYZ(const int x, const int y, const int z, const int v=0) {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.",
+ pixel_type());
+ return _atXYZ(x,y,z,v);
+ }
+
+ T atXYZ(const int x, const int y, const int z, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.",
+ pixel_type());
+ return _atXYZ(x,y,z,v);
+ }
+
+ T& _atXYZ(const int x, const int y, const int z, const int v=0) {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y),
+ z<0?0:(z>=dimz()?dimz()-1:z),v);
+ }
+
+ T _atXYZ(const int x, const int y, const int z, const int v=0) const {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y),
+ z<0?0:(z>=dimz()?dimz()-1:z),v);
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c x,\c y).
+ T& atXY(const int x, const int y, const int z, const int v, const T out_val) {
+ return (x<0 || y<0 || x>=dimx() || y>=dimy())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
+ }
+
+ T atXY(const int x, const int y, const int z, const int v, const T out_val) const {
+ return (x<0 || y<0 || x>=dimx() || y>=dimy())?out_val:(*this)(x,y,z,v);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c x,\c y).
+ T& atXY(const int x, const int y, const int z=0, const int v=0) {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.",
+ pixel_type());
+ return _atXY(x,y,z,v);
+ }
+
+ T atXY(const int x, const int y, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.",
+ pixel_type());
+ return _atXY(x,y,z,v);
+ }
+
+ T& _atXY(const int x, const int y, const int z=0, const int v=0) {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v);
+ }
+
+ T _atXY(const int x, const int y, const int z=0, const int v=0) const {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v);
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c x).
+ T& atX(const int x, const int y, const int z, const int v, const T out_val) {
+ return (x<0 || x>=dimx())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
+ }
+
+ T atX(const int x, const int y, const int z, const int v, const T out_val) const {
+ return (x<0 || x>=dimx())?out_val:(*this)(x,y,z,v);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c x).
+ T& atX(const int x, const int y=0, const int z=0, const int v=0) {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.",
+ pixel_type());
+ return _atX(x,y,z,v);
+ }
+
+ T atX(const int x, const int y=0, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.",
+ pixel_type());
+ return _atX(x,y,z,v);
+ }
+
+ T& _atX(const int x, const int y=0, const int z=0, const int v=0) {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v);
+ }
+
+ T _atX(const int x, const int y=0, const int z=0, const int v=0) const {
+ return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v);
+ }
+
+ //! Read a pixel value using linear interpolation and Dirichlet boundary conditions.
+ Tfloat linear_atXYZV(const float fx, const float fy, const float fz, const float fv, const T out_val) const {
+ const int
+ x = (int)fx-(fx>=0?0:1), nx = x+1,
+ y = (int)fy-(fy>=0?0:1), ny = y+1,
+ z = (int)fz-(fz>=0?0:1), nz = z+1,
+ v = (int)fv-(fv>=0?0:1), nv = v+1;
+ const float
+ dx = fx-x,
+ dy = fy-y,
+ dz = fz-z,
+ dv = fv-v;
+ const Tfloat
+ Icccc = (Tfloat)atXYZV(x,y,z,v,out_val), Inccc = (Tfloat)atXYZV(nx,y,z,v,out_val),
+ Icncc = (Tfloat)atXYZV(x,ny,z,v,out_val), Inncc = (Tfloat)atXYZV(nx,ny,z,v,out_val),
+ Iccnc = (Tfloat)atXYZV(x,y,nz,v,out_val), Incnc = (Tfloat)atXYZV(nx,y,nz,v,out_val),
+ Icnnc = (Tfloat)atXYZV(x,ny,nz,v,out_val), Innnc = (Tfloat)atXYZV(nx,ny,nz,v,out_val),
+ Icccn = (Tfloat)atXYZV(x,y,z,nv,out_val), Inccn = (Tfloat)atXYZV(nx,y,z,nv,out_val),
+ Icncn = (Tfloat)atXYZV(x,ny,z,nv,out_val), Inncn = (Tfloat)atXYZV(nx,ny,z,nv,out_val),
+ Iccnn = (Tfloat)atXYZV(x,y,nz,nv,out_val), Incnn = (Tfloat)atXYZV(nx,y,nz,nv,out_val),
+ Icnnn = (Tfloat)atXYZV(x,ny,nz,nv,out_val), Innnn = (Tfloat)atXYZV(nx,ny,nz,nv,out_val);
+ return Icccc +
+ dx*(Inccc-Icccc +
+ dy*(Icccc+Inncc-Icncc-Inccc +
+ dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
+ dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
+ dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
+ dz*(Icccc+Incnc-Iccnc-Inccc +
+ dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
+ dv*(Icccc+Inccn-Inccc-Icccn)) +
+ dy*(Icncc-Icccc +
+ dz*(Icccc+Icnnc-Iccnc-Icncc +
+ dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
+ dv*(Icccc+Icncn-Icncc-Icccn)) +
+ dz*(Iccnc-Icccc +
+ dv*(Icccc+Iccnn-Iccnc-Icccn)) +
+ dv*(Icccn-Icccc);
+ }
+
+ //! Read a pixel value using linear interpolation and Neumann boundary conditions.
+ Tfloat linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::linear_atXYZV() : Instance image is empty.",
+ pixel_type());
+ return _linear_atXYZV(fx,fy,fz,fv);
+ }
+
+ Tfloat _linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const {
+ const float
+ nfx = fx<0?0:(fx>width-1?width-1:fx),
+ nfy = fy<0?0:(fy>height-1?height-1:fy),
+ nfz = fz<0?0:(fz>depth-1?depth-1:fz),
+ nfv = fv<0?0:(fv>dim-1?dim-1:fv);
+ const unsigned int
+ x = (unsigned int)nfx,
+ y = (unsigned int)nfy,
+ z = (unsigned int)nfz,
+ v = (unsigned int)nfv;
+ const float
+ dx = nfx-x,
+ dy = nfy-y,
+ dz = nfz-z,
+ dv = nfv-v;
+ const unsigned int
+ nx = dx>0?x+1:x,
+ ny = dy>0?y+1:y,
+ nz = dz>0?z+1:z,
+ nv = dv>0?v+1:v;
+ const Tfloat
+ Icccc = (Tfloat)(*this)(x,y,z,v), Inccc = (Tfloat)(*this)(nx,y,z,v),
+ Icncc = (Tfloat)(*this)(x,ny,z,v), Inncc = (Tfloat)(*this)(nx,ny,z,v),
+ Iccnc = (Tfloat)(*this)(x,y,nz,v), Incnc = (Tfloat)(*this)(nx,y,nz,v),
+ Icnnc = (Tfloat)(*this)(x,ny,nz,v), Innnc = (Tfloat)(*this)(nx,ny,nz,v),
+ Icccn = (Tfloat)(*this)(x,y,z,nv), Inccn = (Tfloat)(*this)(nx,y,z,nv),
+ Icncn = (Tfloat)(*this)(x,ny,z,nv), Inncn = (Tfloat)(*this)(nx,ny,z,nv),
+ Iccnn = (Tfloat)(*this)(x,y,nz,nv), Incnn = (Tfloat)(*this)(nx,y,nz,nv),
+ Icnnn = (Tfloat)(*this)(x,ny,nz,nv), Innnn = (Tfloat)(*this)(nx,ny,nz,nv);
+ return Icccc +
+ dx*(Inccc-Icccc +
+ dy*(Icccc+Inncc-Icncc-Inccc +
+ dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
+ dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
+ dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
+ dz*(Icccc+Incnc-Iccnc-Inccc +
+ dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
+ dv*(Icccc+Inccn-Inccc-Icccn)) +
+ dy*(Icncc-Icccc +
+ dz*(Icccc+Icnnc-Iccnc-Icncc +
+ dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
+ dv*(Icccc+Icncn-Icncc-Icccn)) +
+ dz*(Iccnc-Icccc +
+ dv*(Icccc+Iccnn-Iccnc-Icccn)) +
+ dv*(Icccn-Icccc);
+ }
+
+ //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first three coordinates).
+ Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int v, const T out_val) const {
+ const int
+ x = (int)fx-(fx>=0?0:1), nx = x+1,
+ y = (int)fy-(fy>=0?0:1), ny = y+1,
+ z = (int)fz-(fz>=0?0:1), nz = z+1;
+ const float
+ dx = fx-x,
+ dy = fy-y,
+ dz = fz-z;
+ const Tfloat
+ Iccc = (Tfloat)atXYZ(x,y,z,v,out_val), Incc = (Tfloat)atXYZ(nx,y,z,v,out_val),
+ Icnc = (Tfloat)atXYZ(x,ny,z,v,out_val), Innc = (Tfloat)atXYZ(nx,ny,z,v,out_val),
+ Iccn = (Tfloat)atXYZ(x,y,nz,v,out_val), Incn = (Tfloat)atXYZ(nx,y,nz,v,out_val),
+ Icnn = (Tfloat)atXYZ(x,ny,nz,v,out_val), Innn = (Tfloat)atXYZ(nx,ny,nz,v,out_val);
+ return Iccc +
+ dx*(Incc-Iccc +
+ dy*(Iccc+Innc-Icnc-Incc +
+ dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
+ dz*(Iccc+Incn-Iccn-Incc)) +
+ dy*(Icnc-Iccc +
+ dz*(Iccc+Icnn-Iccn-Icnc)) +
+ dz*(Iccn-Iccc);
+ }
+
+ //! Read a pixel value using linear interpolation and Neumann boundary conditions (first three coordinates).
+ Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::linear_atXYZ() : Instance image is empty.",
+ pixel_type());
+ return _linear_atXYZ(fx,fy,fz,v);
+ }
+
+ Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const {
+ const float
+ nfx = fx<0?0:(fx>width-1?width-1:fx),
+ nfy = fy<0?0:(fy>height-1?height-1:fy),
+ nfz = fz<0?0:(fz>depth-1?depth-1:fz);
+ const unsigned int
+ x = (unsigned int)nfx,
+ y = (unsigned int)nfy,
+ z = (unsigned int)nfz;
+ const float
+ dx = nfx-x,
+ dy = nfy-y,
+ dz = nfz-z;
+ const unsigned int
+ nx = dx>0?x+1:x,
+ ny = dy>0?y+1:y,
+ nz = dz>0?z+1:z;
+ const Tfloat
+ Iccc = (Tfloat)(*this)(x,y,z,v), Incc = (Tfloat)(*this)(nx,y,z,v),
+ Icnc = (Tfloat)(*this)(x,ny,z,v), Innc = (Tfloat)(*this)(nx,ny,z,v),
+ Iccn = (Tfloat)(*this)(x,y,nz,v), Incn = (Tfloat)(*this)(nx,y,nz,v),
+ Icnn = (Tfloat)(*this)(x,ny,nz,v), Innn = (Tfloat)(*this)(nx,ny,nz,v);
+ return Iccc +
+ dx*(Incc-Iccc +
+ dy*(Iccc+Innc-Icnc-Incc +
+ dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
+ dz*(Iccc+Incn-Iccn-Incc)) +
+ dy*(Icnc-Iccc +
+ dz*(Iccc+Icnn-Iccn-Icnc)) +
+ dz*(Iccn-Iccc);
+ }
+
+ //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first two coordinates).
+ Tfloat linear_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const {
+ const int
+ x = (int)fx-(fx>=0?0:1), nx = x+1,
+ y = (int)fy-(fy>=0?0:1), ny = y+1;
+ const float
+ dx = fx-x,
+ dy = fy-y;
+ const Tfloat
+ Icc = (Tfloat)atXY(x,y,z,v,out_val), Inc = (Tfloat)atXY(nx,y,z,v,out_val),
+ Icn = (Tfloat)atXY(x,ny,z,v,out_val), Inn = (Tfloat)atXY(nx,ny,z,v,out_val);
+ return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
+ }
+
+ //! Read a pixel value using linear interpolation and Neumann boundary conditions (first two coordinates).
+ Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::linear_atXY() : Instance image is empty.",
+ pixel_type());
+ return _linear_atXY(fx,fy,z,v);
+ }
+
+ Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
+ const float
+ nfx = fx<0?0:(fx>width-1?width-1:fx),
+ nfy = fy<0?0:(fy>height-1?height-1:fy);
+ const unsigned int
+ x = (unsigned int)nfx,
+ y = (unsigned int)nfy;
+ const float
+ dx = nfx-x,
+ dy = nfy-y;
+ const unsigned int
+ nx = dx>0?x+1:x,
+ ny = dy>0?y+1:y;
+ const Tfloat
+ Icc = (Tfloat)(*this)(x,y,z,v), Inc = (Tfloat)(*this)(nx,y,z,v),
+ Icn = (Tfloat)(*this)(x,ny,z,v), Inn = (Tfloat)(*this)(nx,ny,z,v);
+ return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
+ }
+
+ //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first coordinate).
+ Tfloat linear_atX(const float fx, const int y, const int z, const int v, const T out_val) const {
+ const int
+ x = (int)fx-(fx>=0?0:1), nx = x+1;
+ const float
+ dx = fx-x;
+ const Tfloat
+ Ic = (Tfloat)atX(x,y,z,v,out_val), In = (Tfloat)atXY(nx,y,z,v,out_val);
+ return Ic + dx*(In-Ic);
+ }
+
+ //! Read a pixel value using linear interpolation and Neumann boundary conditions (first coordinate).
+ Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::linear_atX() : Instance image is empty.",
+ pixel_type());
+ return _linear_atX(fx,y,z,v);
+ }
+
+ Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
+ const float
+ nfx = fx<0?0:(fx>width-1?width-1:fx);
+ const unsigned int
+ x = (unsigned int)nfx;
+ const float
+ dx = nfx-x;
+ const unsigned int
+ nx = dx>0?x+1:x;
+ const Tfloat
+ Ic = (Tfloat)(*this)(x,y,z,v), In = (Tfloat)(*this)(nx,y,z,v);
+ return Ic + dx*(In-Ic);
+ }
+
+ //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions.
+ Tfloat cubic_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const {
+ const int
+ x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2,
+ y = (int)fy-(fy>=0?0:1), py = y-1, ny = y+1, ay = y+2;
+ const float
+ dx = fx-x, dx2 = dx*dx, dx3 = dx2*dx,
+ dy = fy-y;
+ const Tfloat
+ Ipp = (Tfloat)atXY(px,py,z,v,out_val), Icp = (Tfloat)atXY(x,py,z,v,out_val),
+ Inp = (Tfloat)atXY(nx,py,z,v,out_val), Iap = (Tfloat)atXY(ax,py,z,v,out_val),
+ Ipc = (Tfloat)atXY(px,y,z,v,out_val), Icc = (Tfloat)atXY(x,y,z,v,out_val),
+ Inc = (Tfloat)atXY(nx,y,z,v,out_val), Iac = (Tfloat)atXY(ax,y,z,v,out_val),
+ Ipn = (Tfloat)atXY(px,ny,z,v,out_val), Icn = (Tfloat)atXY(x,ny,z,v,out_val),
+ Inn = (Tfloat)atXY(nx,ny,z,v,out_val), Ian = (Tfloat)atXY(ax,ny,z,v,out_val),
+ Ipa = (Tfloat)atXY(px,ay,z,v,out_val), Ica = (Tfloat)atXY(x,ay,z,v,out_val),
+ Ina = (Tfloat)atXY(nx,ay,z,v,out_val), Iaa = (Tfloat)atXY(ax,ay,z,v,out_val),
+ valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)),
+ valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)),
+ u0p = Icp - Ipp,
+ u1p = Iap - Inp,
+ ap = 2*(Icp-Inp) + u0p + u1p,
+ bp = 3*(Inp-Icp) - 2*u0p - u1p,
+ u0c = Icc - Ipc,
+ u1c = Iac - Inc,
+ ac = 2*(Icc-Inc) + u0c + u1c,
+ bc = 3*(Inc-Icc) - 2*u0c - u1c,
+ u0n = Icn - Ipn,
+ u1n = Ian - Inn,
+ an = 2*(Icn-Inn) + u0n + u1n,
+ bn = 3*(Inn-Icn) - 2*u0n - u1n,
+ u0a = Ica - Ipa,
+ u1a = Iaa - Ina,
+ aa = 2*(Ica-Ina) + u0a + u1a,
+ ba = 3*(Ina-Ica) - 2*u0a - u1a,
+ valp = ap*dx3 + bp*dx2 + u0p*dx + Icp,
+ valc = ac*dx3 + bc*dx2 + u0c*dx + Icc,
+ valn = an*dx3 + bn*dx2 + u0n*dx + Icn,
+ vala = aa*dx3 + ba*dx2 + u0a*dx + Ica,
+ u0 = valc - valp,
+ u1 = vala - valn,
+ a = 2*(valc-valn) + u0 + u1,
+ b = 3*(valn-valc) - 2*u0 - u1,
+ val = a*dy*dy*dy + b*dy*dy + u0*dy + valc;
+ return val<valm?valm:(val>valM?valM:val);
+ }
+
+ //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+ Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::cubic_atXY() : Instance image is empty.",
+ pixel_type());
+ return _cubic_atXY(fx,fy,z,v);
+ }
+
+ Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
+ const float
+ nfx = fx<0?0:(fx>width-1?width-1:fx),
+ nfy = fy<0?0:(fy>height-1?height-1:fy);
+ const int
+ x = (int)nfx,
+ y = (int)nfy;
+ const float
+ dx = nfx-x, dx2 = dx*dx, dx3 = dx2*dx,
+ dy = nfy-y;
+ const int
+ px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2,
+ py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=dimy()?dimy()-1:y+2;
+ const Tfloat
+ Ipp = (Tfloat)(*this)(px,py,z,v), Icp = (Tfloat)(*this)(x,py,z,v),
+ Inp = (Tfloat)(*this)(nx,py,z,v), Iap = (Tfloat)(*this)(ax,py,z,v),
+ Ipc = (Tfloat)(*this)(px,y,z,v), Icc = (Tfloat)(*this)(x,y,z,v),
+ Inc = (Tfloat)(*this)(nx,y,z,v), Iac = (Tfloat)(*this)(ax,y,z,v),
+ Ipn = (Tfloat)(*this)(px,ny,z,v), Icn = (Tfloat)(*this)(x,ny,z,v),
+ Inn = (Tfloat)(*this)(nx,ny,z,v), Ian = (Tfloat)(*this)(ax,ny,z,v),
+ Ipa = (Tfloat)(*this)(px,ay,z,v), Ica = (Tfloat)(*this)(x,ay,z,v),
+ Ina = (Tfloat)(*this)(nx,ay,z,v), Iaa = (Tfloat)(*this)(ax,ay,z,v),
+ valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)),
+ valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)),
+ u0p = Icp - Ipp,
+ u1p = Iap - Inp,
+ ap = 2*(Icp-Inp) + u0p + u1p,
+ bp = 3*(Inp-Icp) - 2*u0p - u1p,
+ u0c = Icc - Ipc,
+ u1c = Iac - Inc,
+ ac = 2*(Icc-Inc) + u0c + u1c,
+ bc = 3*(Inc-Icc) - 2*u0c - u1c,
+ u0n = Icn - Ipn,
+ u1n = Ian - Inn,
+ an = 2*(Icn-Inn) + u0n + u1n,
+ bn = 3*(Inn-Icn) - 2*u0n - u1n,
+ u0a = Ica - Ipa,
+ u1a = Iaa - Ina,
+ aa = 2*(Ica-Ina) + u0a + u1a,
+ ba = 3*(Ina-Ica) - 2*u0a - u1a,
+ valp = ap*dx3 + bp*dx2 + u0p*dx + Icp,
+ valc = ac*dx3 + bc*dx2 + u0c*dx + Icc,
+ valn = an*dx3 + bn*dx2 + u0n*dx + Icn,
+ vala = aa*dx3 + ba*dx2 + u0a*dx + Ica,
+ u0 = valc - valp,
+ u1 = vala - valn,
+ a = 2*(valc-valn) + u0 + u1,
+ b = 3*(valn-valc) - 2*u0 - u1,
+ val = a*dy*dy*dy + b*dy*dy + u0*dy + valc;
+ return val<valm?valm:(val>valM?valM:val);
+ }
+
+ //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions (first coordinates).
+ Tfloat cubic_atX(const float fx, const int y, const int z, const int v, const T out_val) const {
+ const int
+ x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2;
+ const float
+ dx = fx-x;
+ const Tfloat
+ Ip = (Tfloat)atX(px,y,z,v,out_val), Ic = (Tfloat)atX(x,y,z,v,out_val),
+ In = (Tfloat)atX(nx,y,z,v,out_val), Ia = (Tfloat)atX(ax,y,z,v,out_val),
+ valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia),
+ u0 = Ic - Ip,
+ u1 = Ia - In,
+ a = 2*(Ic-In) + u0 + u1,
+ b = 3*(In-Ic) - 2*u0 - u1,
+ val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic;
+ return val<valm?valm:(val>valM?valM:val);
+ }
+
+ //! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates).
+ Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::cubic_atX() : Instance image is empty.",
+ pixel_type());
+ return _cubic_atX(fx,y,z,v);
+ }
+
+ Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
+ const float
+ nfx = fx<0?0:(fx>width-1?width-1:fx);
+ const int
+ x = (int)nfx;
+ const float
+ dx = nfx-x;
+ const int
+ px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2;
+ const Tfloat
+ Ip = (Tfloat)(*this)(px,y,z,v), Ic = (Tfloat)(*this)(x,y,z,v),
+ In = (Tfloat)(*this)(nx,y,z,v), Ia = (Tfloat)(*this)(ax,y,z,v),
+ valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia),
+ u0 = Ic - Ip,
+ u1 = Ia - In,
+ a = 2*(Ic-In) + u0 + u1,
+ b = 3*(In-Ic) - 2*u0 - u1,
+ val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic;
+ return val<valm?valm:(val>valM?valM:val);
+ }
+
+ //! Set a pixel value, with 3D float coordinates, using linear interpolation.
+ CImg& set_linear_atXYZ(const T& val, const float fx, const float fy=0, const float fz=0, const int v=0,
+ const bool add=false) {
+ const int
+ x = (int)fx-(fx>=0?0:1), nx = x+1,
+ y = (int)fy-(fy>=0?0:1), ny = y+1,
+ z = (int)fz-(fz>=0?0:1), nz = z+1;
+ const float
+ dx = fx-x,
+ dy = fy-y,
+ dz = fz-z;
+ if (v>=0 && v<dimv()) {
+ if (z>=0 && z<dimz()) {
+ if (y>=0 && y<dimy()) {
+ if (x>=0 && x<dimx()) {
+ const float w1 = (1-dx)*(1-dy)*(1-dz), w2 = add?1:(1-w1);
+ (*this)(x,y,z,v) = (T)(w1*val + w2*(*this)(x,y,z,v));
+ }
+ if (nx>=0 && nx<dimx()) {
+ const float w1 = dx*(1-dy)*(1-dz), w2 = add?1:(1-w1);
+ (*this)(nx,y,z,v) = (T)(w1*val + w2*(*this)(nx,y,z,v));
+ }
+ }
+ if (ny>=0 && ny<dimy()) {
+ if (x>=0 && x<dimx()) {
+ const float w1 = (1-dx)*dy*(1-dz), w2 = add?1:(1-w1);
+ (*this)(x,ny,z,v) = (T)(w1*val + w2*(*this)(x,ny,z,v));
+ }
+ if (nx>=0 && nx<dimx()) {
+ const float w1 = dx*dy*(1-dz), w2 = add?1:(1-w1);
+ (*this)(nx,ny,z,v) = (T)(w1*val + w2*(*this)(nx,ny,z,v));
+ }
+ }
+ }
+ if (nz>=0 && nz<dimz()) {
+ if (y>=0 && y<dimy()) {
+ if (x>=0 && x<dimx()) {
+ const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
+ (*this)(x,y,nz,v) = (T)(w1*val + w2*(*this)(x,y,nz,v));
+ }
+ if (nx>=0 && nx<dimx()) {
+ const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
+ (*this)(nx,y,nz,v) = (T)(w1*val + w2*(*this)(nx,y,nz,v));
+ }
+ }
+ if (ny>=0 && ny<dimy()) {
+ if (x>=0 && x<dimx()) {
+ const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
+ (*this)(x,ny,nz,v) = (T)(w1*val + w2*(*this)(x,ny,nz,v));
+ }
+ if (nx>=0 && nx<dimx()) {
+ const float w1 = dx*dy, w2 = add?1:(1-w1);
+ (*this)(nx,ny,nz,v) = (T)(w1*val + w2*(*this)(nx,ny,nz,v));
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Set a pixel value, with 2D float coordinates, using linear interpolation.
+ CImg& set_linear_atXY(const T& val, const float fx, const float fy=0, const int z=0, const int v=0,
+ const bool add=false) {
+ const int
+ x = (int)fx-(fx>=0?0:1), nx = x+1,
+ y = (int)fy-(fy>=0?0:1), ny = y+1;
+ const float
+ dx = fx-x,
+ dy = fy-y;
+ if (z>=0 && z<dimz() && v>=0 && v<dimv()) {
+ if (y>=0 && y<dimy()) {
+ if (x>=0 && x<dimx()) {
+ const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
+ (*this)(x,y,z,v) = (T)(w1*val + w2*(*this)(x,y,z,v));
+ }
+ if (nx>=0 && nx<dimx()) {
+ const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
+ (*this)(nx,y,z,v) = (T)(w1*val + w2*(*this)(nx,y,z,v));
+ }
+ }
+ if (ny>=0 && ny<dimy()) {
+ if (x>=0 && x<dimx()) {
+ const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
+ (*this)(x,ny,z,v) = (T)(w1*val + w2*(*this)(x,ny,z,v));
+ }
+ if (nx>=0 && nx<dimx()) {
+ const float w1 = dx*dy, w2 = add?1:(1-w1);
+ (*this)(nx,ny,z,v) = (T)(w1*val + w2*(*this)(nx,ny,z,v));
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance image
+ const T& min() const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::min() : Instance image is empty.",
+ pixel_type());
+ const T *ptrmin = data;
+ T min_value = *ptrmin;
+ cimg_for(*this,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
+ return *ptrmin;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance image
+ T& min() {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::min() : Instance image is empty.",
+ pixel_type());
+ T *ptrmin = data;
+ T min_value = *ptrmin;
+ cimg_for(*this,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
+ return *ptrmin;
+ }
+
+ //! Return a reference to the maximum pixel value of the instance image
+ const T& max() const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::max() : Instance image is empty.",
+ pixel_type());
+ const T *ptrmax = data;
+ T max_value = *ptrmax;
+ cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
+ return *ptrmax;
+ }
+
+ //! Return a reference to the maximum pixel value of the instance image
+ T& max() {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::max() : Instance image is empty.",
+ pixel_type());
+ T *ptrmax = data;
+ T max_value = *ptrmax;
+ cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
+ return *ptrmax;
+ }
+
+ //! Return a reference to the minimum pixel value and return also the maximum pixel value.
+ template<typename t>
+ const T& minmax(t& max_val) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.",
+ pixel_type());
+ const T *ptrmin = data;
+ T min_value = *ptrmin, max_value = min_value;
+ cimg_for(*this,ptr,T) {
+ const T val = *ptr;
+ if (val<min_value) { min_value = val; ptrmin = ptr; }
+ if (val>max_value) max_value = val;
+ }
+ max_val = (t)max_value;
+ return *ptrmin;
+ }
+
+ //! Return a reference to the minimum pixel value and return also the maximum pixel value.
+ template<typename t>
+ T& minmax(t& max_val) {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.",
+ pixel_type());
+ T *ptrmin = data;
+ T min_value = *ptrmin, max_value = min_value;
+ cimg_for(*this,ptr,T) {
+ const T val = *ptr;
+ if (val<min_value) { min_value = val; ptrmin = ptr; }
+ if (val>max_value) max_value = val;
+ }
+ max_val = (t)max_value;
+ return *ptrmin;
+ }
+
+ //! Return a reference to the maximum pixel value and return also the minimum pixel value.
+ template<typename t>
+ const T& maxmin(t& min_val) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.",
+ pixel_type());
+ const T *ptrmax = data;
+ T max_value = *ptrmax, min_value = max_value;
+ cimg_for(*this,ptr,T) {
+ const T val = *ptr;
+ if (val>max_value) { max_value = val; ptrmax = ptr; }
+ if (val<min_value) min_value = val;
+ }
+ min_val = (t)min_value;
+ return *ptrmax;
+ }
+
+ //! Return a reference to the maximum pixel value and return also the minimum pixel value.
+ template<typename t>
+ T& maxmin(t& min_val) {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.",
+ pixel_type());
+ T *ptrmax = data;
+ T max_value = *ptrmax, min_value = max_value;
+ cimg_for(*this,ptr,T) {
+ const T val = *ptr;
+ if (val>max_value) { max_value = val; ptrmax = ptr; }
+ if (val<min_value) min_value = val;
+ }
+ min_val = (t)min_value;
+ return *ptrmax;
+ }
+
+ //! Return the sum of all the pixel values in an image.
+ Tfloat sum() const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::sum() : Instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ Tfloat res = 0;
+ cimg_for(*this,ptr,T) res+=*ptr;
+ return res;
+ }
+
+ //! Return the mean pixel value of the instance image.
+ Tfloat mean() const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::mean() : Instance image is empty.",
+ pixel_type());
+ Tfloat val = 0;
+ cimg_for(*this,ptr,T) val+=*ptr;
+ return val/size();
+ }
+
+ //! Return the variance of the image.
+ /**
+ @param variance_method Determines how to calculate the variance
+ <table border="0">
+ <tr><td>0</td>
+ <td>Second moment:
+ @f$ v = 1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2
+ = 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right) @f$
+ with @f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$</td></tr>
+ <tr><td>1</td>
+ <td>Best unbiased estimator: @f$ v = \frac{1}{N-1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 @f$</td></tr>
+ <tr><td>2</td>
+ <td>Least median of squares</td></tr>
+ <tr><td>3</td>
+ <td>Least trimmed of squares</td></tr>
+ </table>
+ */
+ Tfloat variance(const unsigned int variance_method=1) const {
+ Tfloat foo;
+ return variancemean(variance_method,foo);
+ }
+
+ //! Return the variance and the mean of the image.
+ template<typename t>
+ Tfloat variancemean(const unsigned int variance_method, t& mean) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::variance() : Instance image is empty.",
+ pixel_type());
+ Tfloat variance = 0, average = 0;
+ const unsigned int siz = size();
+ switch (variance_method) {
+ case 3 : { // Least trimmed of Squares
+ CImg<Tfloat> buf(*this);
+ const unsigned int siz2 = siz>>1;
+ { cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; (*ptrs)*=val; average+=val; }}
+ buf.sort();
+ Tfloat a = 0;
+ const Tfloat *ptrs = buf.ptr();
+ for (unsigned int j = 0; j<siz2; ++j) a+=*(ptrs++);
+ const Tfloat sig = (Tfloat)(2.6477*cimg_std::sqrt(a/siz2));
+ variance = sig*sig;
+ } break;
+ case 2 : { // Least Median of Squares (MAD)
+ CImg<Tfloat> buf(*this);
+ buf.sort();
+ const unsigned int siz2 = siz>>1;
+ const Tfloat med_i = buf[siz2];
+ cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; *ptrs = cimg::abs(val - med_i); average+=val; }
+ buf.sort();
+ const Tfloat sig = (Tfloat)(1.4828*buf[siz2]);
+ variance = sig*sig;
+ } break;
+ case 1 : { // Least mean square (robust definition)
+ Tfloat S = 0, S2 = 0;
+ cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val; S2+=val*val; }
+ variance = siz>1?(S2 - S*S/siz)/(siz - 1):0;
+ average = S;
+ } break;
+ case 0 :{ // Least mean square (standard definition)
+ Tfloat S = 0, S2 = 0;
+ cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val; S2+=val*val; }
+ variance = (S2 - S*S/siz)/siz;
+ average = S;
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::variancemean() : Incorrect parameter 'variance_method = %d' (correct values are 0,1,2 or 3).",
+ pixel_type(),variance_method);
+ }
+ mean = (t)(average/siz);
+ return variance>0?variance:0;
+ }
+
+ //! Return the kth smallest element of the image.
+ // (Adapted from the numerical recipies for CImg)
+ T kth_smallest(const unsigned int k) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::kth_smallest() : Instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ CImg<T> arr(*this);
+ unsigned long l = 0, ir = size()-1;
+ for (;;) {
+ if (ir<=l+1) {
+ if (ir==l+1 && arr[ir]<arr[l]) cimg::swap(arr[l],arr[ir]);
+ return arr[k];
+ } else {
+ const unsigned long mid = (l+ir)>>1;
+ cimg::swap(arr[mid],arr[l+1]);
+ if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]);
+ if (arr[l+1]>arr[ir]) cimg::swap(arr[l+1],arr[ir]);
+ if (arr[l]>arr[l+1]) cimg::swap(arr[l],arr[l+1]);
+ unsigned long i = l+1, j = ir;
+ const T pivot = arr[l+1];
+ for (;;) {
+ do ++i; while (arr[i]<pivot);
+ do --j; while (arr[j]>pivot);
+ if (j<i) break;
+ cimg::swap(arr[i],arr[j]);
+ }
+ arr[l+1] = arr[j];
+ arr[j] = pivot;
+ if (j>=k) ir=j-1;
+ if (j<=k) l=i;
+ }
+ }
+ return 0;
+ }
+
+ //! Compute a statistics vector (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax).
+ CImg<T>& stats(const unsigned int variance_method=1) {
+ return get_stats(variance_method).transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_stats(const unsigned int variance_method=1) const {
+ if (is_empty()) return CImg<Tfloat>();
+ const unsigned long siz = size();
+ const T *const odata = data;
+ const T *pm = odata, *pM = odata;
+ Tfloat S = 0, S2 = 0;
+ T m = *pm, M = m;
+ cimg_for(*this,ptr,T) {
+ const T val = *ptr;
+ const Tfloat fval = (Tfloat)val;
+ if (val<m) { m = val; pm = ptr; }
+ if (val>M) { M = val; pM = ptr; }
+ S+=fval;
+ S2+=fval*fval;
+ }
+ const Tfloat
+ mean_value = S/siz,
+ _variance_value = variance_method==0?(S2 - S*S/siz)/siz:
+ (variance_method==1?(siz>1?(S2 - S*S/siz)/(siz - 1):0):
+ variance(variance_method)),
+ variance_value = _variance_value>0?_variance_value:0;
+ int
+ xm = 0, ym = 0, zm = 0, vm = 0,
+ xM = 0, yM = 0, zM = 0, vM = 0;
+ contains(*pm,xm,ym,zm,vm);
+ contains(*pM,xM,yM,zM,vM);
+ return CImg<Tfloat>(1,12).fill((Tfloat)m,(Tfloat)M,mean_value,variance_value,
+ (Tfloat)xm,(Tfloat)ym,(Tfloat)zm,(Tfloat)vm,
+ (Tfloat)xM,(Tfloat)yM,(Tfloat)zM,(Tfloat)vM);
+ }
+
+ //! Return the median value of the image.
+ T median() const {
+ const unsigned int s = size();
+ const T res = kth_smallest(s>>1);
+ return (s%2)?res:((res+kth_smallest((s>>1)-1))/2);
+ }
+
+ //! Compute the MSE (Mean-Squared Error) between two images.
+ template<typename t>
+ Tfloat MSE(const CImg<t>& img) const {
+ if (img.size()!=size())
+ throw CImgArgumentException("CImg<%s>::MSE() : Instance image (%u,%u,%u,%u) and given image (%u,%u,%u,%u) have different dimensions.",
+ pixel_type(),width,height,depth,dim,img.width,img.height,img.depth,img.dim);
+
+ Tfloat vMSE = 0;
+ const t* ptr2 = img.end();
+ cimg_for(*this,ptr1,T) {
+ const Tfloat diff = (Tfloat)*ptr1 - (Tfloat)*(--ptr2);
+ vMSE += diff*diff;
+ }
+ vMSE/=img.size();
+ return vMSE;
+ }
+
+ //! Compute the PSNR between two images.
+ template<typename t>
+ Tfloat PSNR(const CImg<t>& img, const Tfloat valmax=(Tfloat)255) const {
+ const Tfloat vMSE = (Tfloat)cimg_std::sqrt(MSE(img));
+ return (vMSE!=0)?(Tfloat)(20*cimg_std::log10(valmax/vMSE)):(Tfloat)(cimg::type<Tfloat>::max());
+ }
+
+ //! Return the trace of the image, viewed as a matrix.
+ Tfloat trace() const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::trace() : Instance matrix (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ Tfloat res = 0;
+ cimg_forX(*this,k) res+=(*this)(k,k);
+ return res;
+ }
+
+ //! Return the dot product of the current vector/matrix with the vector/matrix \p img.
+ template<typename t>
+ Tfloat dot(const CImg<t>& img) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::dot() : Instance object (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ if (!img)
+ throw CImgArgumentException("CImg<%s>::trace() : Specified argument (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),img.width,img.height,img.depth,img.dim,img.data);
+ const unsigned long nb = cimg::min(size(),img.size());
+ Tfloat res = 0;
+ for (unsigned long off = 0; off<nb; ++off) res+=(Tfloat)data[off]*(Tfloat)img[off];
+ return res;
+ }
+
+ //! Return the determinant of the image, viewed as a matrix.
+ Tfloat det() const {
+ if (is_empty() || width!=height || depth!=1 || dim!=1)
+ throw CImgInstanceException("CImg<%s>::det() : Instance matrix (%u,%u,%u,%u,%p) is not square or is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ switch (width) {
+ case 1 : return (Tfloat)((*this)(0,0));
+ case 2 : return (Tfloat)((*this)(0,0))*(Tfloat)((*this)(1,1)) - (Tfloat)((*this)(0,1))*(Tfloat)((*this)(1,0));
+ case 3 : {
+ const Tfloat
+ a = (Tfloat)data[0], d = (Tfloat)data[1], g = (Tfloat)data[2],
+ b = (Tfloat)data[3], e = (Tfloat)data[4], h = (Tfloat)data[5],
+ c = (Tfloat)data[6], f = (Tfloat)data[7], i = (Tfloat)data[8];
+ return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e;
+ }
+ default : {
+ CImg<Tfloat> lu(*this);
+ CImg<uintT> indx;
+ bool d;
+ lu._LU(indx,d);
+ Tfloat res = d?(Tfloat)1:(Tfloat)-1;
+ cimg_forX(lu,i) res*=lu(i,i);
+ return res;
+ }
+ }
+ return 0;
+ }
+
+ //! Return the norm of the current vector/matrix. \p ntype = norm type (0=L2, 1=L1, -1=Linf).
+ Tfloat norm(const int norm_type=2) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::norm() : Instance object (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ Tfloat res = 0;
+ switch (norm_type) {
+ case -1 : {
+ cimg_foroff(*this,off) {
+ const Tfloat tmp = cimg::abs((Tfloat)data[off]);
+ if (tmp>res) res = tmp;
+ }
+ return res;
+ } break;
+ case 1 : {
+ cimg_foroff(*this,off) res+=cimg::abs((Tfloat)data[off]);
+ return res;
+ } break;
+ case 2 : return (Tfloat)cimg_std::sqrt(dot(*this)); break;
+ default :
+ throw CImgArgumentException("CImg<%s>::norm() : Incorrect parameter 'norm_type=%d' (correct values are -1,1 or 2).",
+ pixel_type(),norm_type);
+ }
+ return 0;
+ }
+
+ //! Return a C-string containing the values of the instance image.
+ CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
+ if (is_empty()) return CImg<charT>(1,1,1,1,0);
+ const unsigned int siz = (unsigned int)size();
+ CImgList<charT> items;
+ char item[256] = { 0 };
+ const T *ptrs = ptr();
+ for (unsigned int off = 0; off<siz-1; ++off) {
+ cimg_std::sprintf(item,cimg::type<T>::format(),cimg::type<T>::format(*(ptrs++)));
+ const int l = cimg::strlen(item);
+ items.insert(CImg<charT>(item,l+1));
+ items[items.size-1](l) = separator;
+ }
+ cimg_std::sprintf(item,cimg::type<T>::format(),cimg::type<T>::format(*ptrs));
+ items.insert(CImg<charT>(item,cimg::strlen(item)+1));
+ CImg<ucharT> res = items.get_append('x');
+ if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
+ return res;
+ }
+
+ //! Display informations about the image on the standard error output.
+ /**
+ \param title Name for the considered image (optional).
+ \param display_stats Compute and display image statistics (optional).
+ **/
+ const CImg<T>& print(const char *title=0, const bool display_stats=true) const {
+ int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0;
+ static CImg<doubleT> st;
+ if (!is_empty() && display_stats) {
+ st = get_stats();
+ xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7];
+ xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11];
+ }
+ const unsigned long siz = size(), msiz = siz*sizeof(T), siz1 = siz-1;
+ const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2), width1 = width-1;
+ char ntitle[64] = { 0 };
+ if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
+ cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = (%u,%u,%u,%u) [%lu %s], data = (%s*)%p (%s) = [ ",
+ title?title:ntitle,(void*)this,width,height,depth,dim,
+ mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
+ mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
+ pixel_type(),(void*)data,is_shared?"shared":"not shared");
+ if (!is_empty()) cimg_foroff(*this,off) {
+ cimg_std::fprintf(cimg_stdout,cimg::type<T>::format(),cimg::type<T>::format(data[off]));
+ if (off!=siz1) cimg_std::fprintf(cimg_stdout,"%s",off%width==width1?" ; ":" ");
+ if (off==7 && siz>16) { off = siz1-8; if (off!=7) cimg_std::fprintf(cimg_stdout,"... "); }
+ }
+ if (!is_empty() && display_stats)
+ cimg_std::fprintf(cimg_stdout," ], min = %g, max = %g, mean = %g, std = %g, coords(min) = (%u,%u,%u,%u), coords(max) = (%u,%u,%u,%u).\n",
+ st[0],st[1],st[2],cimg_std::sqrt(st[3]),xm,ym,zm,vm,xM,yM,zM,vM);
+ else cimg_std::fprintf(cimg_stdout,"%s].\n",is_empty()?"":" ");
+ return *this;
+ }
+
+ //@}
+ //------------------------------------------
+ //
+ //! \name Arithmetic and Boolean Operators
+ //@{
+ //------------------------------------------
+
+ //! Assignment operator.
+ /**
+ This operator assigns a copy of the input image \p img to the current instance image.
+ \param img The input image to copy.
+ \remark
+ - This operator is strictly equivalent to the function assign(const CImg< t >&) and has exactly the same properties.
+ **/
+ template<typename t>
+ CImg<T>& operator=(const CImg<t>& img) {
+ return assign(img);
+ }
+
+ CImg<T>& operator=(const CImg<T>& img) {
+ return assign(img);
+ }
+
+ //! Assign values of a C-array to the instance image.
+ /**
+ \param buf Pointer to a C-style array having a size of (at least) <tt>this->size()</tt>.
+
+ - Replace pixel values by the content of the array \c buf.
+ - Warning : the value types in the array and in the image must be the same.
+
+ \par example:
+ \code
+ float tab[4*4] = { 1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16 }; // Define a 4x4 matrix in C-style.
+ CImg<float> matrice(4,4); // Define a 4x4 greyscale image.
+ matrice = tab; // Fill the image by the values in tab.
+ \endcode
+ **/
+ CImg<T>& operator=(const T *buf) {
+ return assign(buf,width,height,depth,dim);
+ }
+
+ //! Assign a value to each image pixel of the instance image.
+ CImg<T>& operator=(const T val) {
+ return fill(val);
+ }
+
+ //! Operator+
+ /**
+ \remark
+ - This operator can be used to get a non-shared copy of an image.
+ **/
+ CImg<T> operator+() const {
+ return CImg<T>(*this,false);
+ }
+
+ //! Operator+=;
+#ifdef cimg_use_visualcpp6
+ CImg<T>& operator+=(const T val)
+#else
+ template<typename t>
+ CImg<T>& operator+=(const t val)
+#endif
+ {
+ cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)+val);
+ return *this;
+ }
+
+ //! Operator+=
+ template<typename t>
+ CImg<T>& operator+=(const CImg<t>& img) {
+ if (is_overlapped(img)) return *this+=+img;
+ const unsigned int smin = cimg::min(size(),img.size());
+ t *ptrs = img.data + smin;
+ for (T *ptrd = data + smin; ptrd>data; --ptrd, (*ptrd)=(T)((*ptrd)+(*(--ptrs)))) {}
+ return *this;
+ }
+
+ //! Operator++ (prefix)
+ CImg<T>& operator++() {
+ cimg_for(*this,ptr,T) ++(*ptr);
+ return *this;
+ }
+
+ //! Operator++ (postfix)
+ CImg<T> operator++(int) {
+ const CImg<T> copy(*this,false);
+ ++*this;
+ return copy;
+ }
+
+ //! Operator-.
+ CImg<T> operator-() const {
+ return CImg<T>(width,height,depth,dim,0)-=*this;
+ }
+
+ //! Operator-=.
+#ifdef cimg_use_visualcpp6
+ CImg<T>& operator-=(const T val)
+#else
+ template<typename t>
+ CImg<T>& operator-=(const t val)
+#endif
+ {
+ cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)-val);
+ return *this;
+ }
+
+ //! Operator-=.
+ template<typename t>
+ CImg<T>& operator-=(const CImg<t>& img) {
+ if (is_overlapped(img)) return *this-=+img;
+ const unsigned int smin = cimg::min(size(),img.size());
+ t *ptrs = img.data+smin;
+ for (T *ptrd = data+smin; ptrd>data; --ptrd, (*ptrd) = (T)((*ptrd)-(*(--ptrs)))) {}
+ return *this;
+ }
+
+ //! Operator-- (prefix).
+ CImg<T>& operator--() {
+ cimg_for(*this,ptr,T) *ptr = *ptr-(T)1;
+ return *this;
+ }
+
+ //! Operator-- (postfix).
+ CImg<T> operator--(int) {
+ CImg<T> copy(*this,false);
+ --*this;
+ return copy;
+ }
+
+ //! Operator*=.
+#ifdef cimg_use_visualcpp6
+ CImg<T>& operator*=(const double val)
+#else
+ template<typename t>
+ CImg<T>& operator*=(const t val)
+#endif
+ {
+ cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)*val);
+ return *this;
+ }
+
+ //! Operator*=.
+ template<typename t>
+ CImg<T>& operator*=(const CImg<t>& img) {
+ return ((*this)*img).transfer_to(*this);
+ }
+
+ //! Operator/=.
+#ifdef cimg_use_visualcpp6
+ CImg<T>& operator/=(const double val)
+#else
+ template<typename t>
+ CImg<T>& operator/=(const t val)
+#endif
+ {
+ cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)/val);
+ return *this;
+ }
+
+ //! Operator/=.
+ template<typename t>
+ CImg<T>& operator/=(const CImg<t>& img) {
+ return assign(*this*img.get_invert());
+ }
+
+ //! Modulo.
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> operator%(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false)%=img;
+ }
+
+ //! Modulo.
+ CImg<T> operator%(const T val) const {
+ return (+*this)%=val;
+ }
+
+ //! In-place modulo.
+ CImg<T>& operator%=(const T val) {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg::mod(*ptr,val);
+ return *this;
+ }
+
+ //! In-place modulo.
+ template<typename t>
+ CImg<T>& operator%=(const CImg<t>& img) {
+ if (is_overlapped(img)) return *this%=+img;
+ typedef typename cimg::superset<T,t>::type Tt;
+ const unsigned int smin = cimg::min(size(),img.size());
+ const t *ptrs = img.data + smin;
+ for (T *ptrd = data + smin; ptrd>data; ) {
+ T& val = *(--ptrd);
+ val = (T)cimg::mod((Tt)val,(Tt)*(--ptrs));
+ }
+ return *this;
+ }
+
+ //! Bitwise AND.
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> operator&(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false)&=img;
+ }
+
+ //! Bitwise AND.
+ CImg<T> operator&(const T val) const {
+ return (+*this)&=val;
+ }
+
+ //! In-place bitwise AND.
+ template<typename t>
+ CImg<T>& operator&=(const CImg<t>& img) {
+ if (is_overlapped(img)) return *this&=+img;
+ const unsigned int smin = cimg::min(size(),img.size());
+ const t *ptrs = img.data + smin;
+ for (T *ptrd = data + smin; ptrd>data; ) {
+ T& val = *(--ptrd);
+ val = (T)((unsigned long)val & (unsigned long)*(--ptrs));
+ }
+ return *this;
+ }
+
+ //! In-place bitwise AND.
+ CImg<T>& operator&=(const T val) {
+ cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr & (unsigned long)val);
+ return *this;
+ }
+
+ //! Bitwise OR.
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> operator|(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false)|=img;
+ }
+
+ //! Bitwise OR.
+ CImg<T> operator|(const T val) const {
+ return (+*this)|=val;
+ }
+
+ //! In-place bitwise OR.
+ template<typename t>
+ CImg<T>& operator|=(const CImg<t>& img) {
+ if (is_overlapped(img)) return *this|=+img;
+ const unsigned int smin = cimg::min(size(),img.size());
+ const t *ptrs = img.data + smin;
+ for (T *ptrd = data + smin; ptrd>data; ) {
+ T& val = *(--ptrd);
+ val = (T)((unsigned long)val | (unsigned long)*(--ptrs));
+ }
+ return *this;
+ }
+
+ //! In-place bitwise OR.
+ CImg<T>& operator|=(const T val) {
+ cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr | (unsigned long)val);
+ return *this;
+ }
+
+ //! Bitwise XOR.
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> operator^(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false)^=img;
+ }
+
+ //! Bitwise XOR.
+ CImg<T> operator^(const T val) const {
+ return (+*this)^=val;
+ }
+
+ //! In-place bitwise XOR.
+ template<typename t>
+ CImg<T>& operator^=(const CImg<t>& img) {
+ if (is_overlapped(img)) return *this^=+img;
+ const unsigned int smin = cimg::min(size(),img.size());
+ const t *ptrs = img.data + smin;
+ for (T *ptrd = data+smin; ptrd>data; ) {
+ T& val = *(--ptrd);
+ val =(T)((unsigned long)val ^ (unsigned long)*(--ptrs));
+ }
+ return *this;
+ }
+
+ //! In-place bitwise XOR.
+ CImg<T>& operator^=(const T val) {
+ cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr ^ (unsigned long)val);
+ return *this;
+ }
+
+ //! Bitwise NOT.
+ CImg<T> operator~() const {
+ CImg<T> res(width,height,depth,dim);
+ const T *ptrs = end();
+ cimg_for(res,ptrd,T) { const unsigned long val = (unsigned long)*(--ptrs); *ptrd = (T)~val; }
+ return res;
+ }
+
+ //! Bitwise left shift.
+ CImg<T>& operator<<=(const int n) {
+ cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)<<n);
+ return *this;
+ }
+
+ //! Bitwise left shift.
+ CImg<T> operator<<(const int n) const {
+ return (+*this)<<=n;
+ }
+
+ //! Bitwise right shift.
+ CImg<T>& operator>>=(const int n) {
+ cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)>>n);
+ return *this;
+ }
+
+ //! Bitwise right shift.
+ CImg<T> operator>>(const int n) const {
+ return (+*this)>>=n;
+ }
+
+ //! Boolean equality.
+ template<typename t>
+ bool operator==(const CImg<t>& img) const {
+ const unsigned int siz = size();
+ bool vequal = true;
+ if (siz!=img.size()) return false;
+ t *ptrs = img.data + siz;
+ for (T *ptrd = data + siz; vequal && ptrd>data; vequal = vequal && ((*(--ptrd))==(*(--ptrs)))) {}
+ return vequal;
+ }
+
+ //! Boolean difference.
+ template<typename t>
+ bool operator!=(const CImg<t>& img) const {
+ return !((*this)==img);
+ }
+
+ //! Return a list of two images { *this, img }.
+ template<typename t>
+ CImgList<typename cimg::superset<T,t>::type> operator<<(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImgList<Tt>(*this,img);
+ }
+
+ //! Return a copy of \p list, where image *this has been inserted at first position.
+ template<typename t>
+ CImgList<typename cimg::superset<T,t>::type> operator<<(const CImgList<t>& list) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImgList<Tt>(list).insert(*this,0);
+ }
+
+ //! Return a list of two images { *this, img }.
+ template<typename t>
+ CImgList<typename cimg::superset<T,t>::type> operator>>(const CImg<t>& img) const {
+ return (*this)<<img;
+ }
+
+ //! Insert an image into the begining of an image list.
+ template<typename t>
+ CImgList<t>& operator>>(const CImgList<t>& list) const {
+ return list.insert(*this,0);
+ }
+
+ //! Display an image into a CImgDisplay.
+ const CImg<T>& operator>>(CImgDisplay& disp) const {
+ return display(disp);
+ }
+
+ //@}
+ //---------------------------------------
+ //
+ //! \name Usual Mathematics Functions
+ //@{
+ //---------------------------------------
+
+ //! Apply a R->R function on all pixel values.
+ template<typename t>
+ CImg<T>& apply(t& func) {
+ cimg_for(*this,ptr,T) *ptr = func(*ptr);
+ return *this;
+ }
+
+ template<typename t>
+ CImg<T> get_apply(t& func) const {
+ return (+*this).apply(func);
+ }
+
+ //! Pointwise multiplication between two images.
+ template<typename t>
+ CImg<T>& mul(const CImg<t>& img) {
+ if (is_overlapped(img)) return mul(+img);
+ t *ptrs = img.data;
+ T *ptrf = data + cimg::min(size(),img.size());
+ for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd*(*(ptrs++)));
+ return *this;
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> get_mul(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false).mul(img);
+ }
+
+ //! Pointwise division between two images.
+ template<typename t>
+ CImg<T>& div(const CImg<t>& img) {
+ if (is_overlapped(img)) return div(+img);
+ t *ptrs = img.data;
+ T *ptrf = data + cimg::min(size(),img.size());
+ for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd/(*(ptrs++)));
+ return *this;
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> get_div(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false).div(img);
+ }
+
+ //! Pointwise max operator between two images.
+ template<typename t>
+ CImg<T>& max(const CImg<t>& img) {
+ if (is_overlapped(img)) return max(+img);
+ t *ptrs = img.data;
+ T *ptrf = data + cimg::min(size(),img.size());
+ for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::max((T)*(ptrs++),*ptrd);
+ return *this;
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> get_max(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false).max(img);
+ }
+
+ //! Pointwise max operator between an image and a value.
+ CImg<T>& max(const T val) {
+ cimg_for(*this,ptr,T) (*ptr) = cimg::max(*ptr,val);
+ return *this;
+ }
+
+ CImg<T> get_max(const T val) const {
+ return (+*this).max(val);
+ }
+
+ //! Pointwise min operator between two images.
+ template<typename t>
+ CImg<T>& min(const CImg<t>& img) {
+ if (is_overlapped(img)) return min(+img);
+ t *ptrs = img.data;
+ T *ptrf = data + cimg::min(size(),img.size());
+ for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::min((T)*(ptrs++),*ptrd);
+ return *this;
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> get_min(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this,false).min(img);
+ }
+
+ //! Pointwise min operator between an image and a value.
+ CImg<T>& min(const T val) {
+ cimg_for(*this,ptr,T) (*ptr) = cimg::min(*ptr,val);
+ return *this;
+ }
+
+ CImg<T> get_min(const T val) const {
+ return (+*this).min(val);
+ }
+
+ //! Compute the square value of each pixel.
+ CImg<T>& sqr() {
+ cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)(val*val); };
+ return *this;
+ }
+
+ CImg<Tfloat> get_sqr() const {
+ return CImg<Tfloat>(*this,false).sqr();
+ }
+
+ //! Compute the square root of each pixel value.
+ CImg<T>& sqrt() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sqrt((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_sqrt() const {
+ return CImg<Tfloat>(*this,false).sqrt();
+ }
+
+ //! Compute the exponential of each pixel value.
+ CImg<T>& exp() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::exp((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_exp() const {
+ return CImg<Tfloat>(*this,false).exp();
+ }
+
+ //! Compute the log of each each pixel value.
+ CImg<T>& log() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_log() const {
+ return CImg<Tfloat>(*this,false).log();
+ }
+
+ //! Compute the log10 of each each pixel value.
+ CImg<T>& log10() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log10((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_log10() const {
+ return CImg<Tfloat>(*this,false).log10();
+ }
+
+ //! Compute the power by p of each pixel value.
+ CImg<T>& pow(const double p) {
+ if (p==0) return fill(1);
+ if (p==0.5) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)cimg_std::sqrt((double)val); } return *this; }
+ if (p==1) return *this;
+ if (p==2) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val; } return *this; }
+ if (p==3) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val; } return *this; }
+ if (p==4) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val*val; } return *this; }
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::pow((double)(*ptr),p);
+ return *this;
+ }
+
+ CImg<Tfloat> get_pow(const double p) const {
+ return CImg<Tfloat>(*this,false).pow(p);
+ }
+
+ //! Compute the power of each pixel value.
+ template<typename t>
+ CImg<T>& pow(const CImg<t>& img) {
+ if (is_overlapped(img)) return pow(+img);
+ t *ptrs = img.data;
+ T *ptrf = data + cimg::min(size(),img.size());
+ for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)cimg_std::pow((double)*ptrd,(double)(*(ptrs++)));
+ return *this;
+ }
+
+ template<typename t>
+ CImg<Tfloat> get_pow(const CImg<t>& img) const {
+ return CImg<Tfloat>(*this,false).pow(img);
+ }
+
+ //! Compute the absolute value of each pixel value.
+ CImg<T>& abs() {
+ cimg_for(*this,ptr,T) (*ptr) = cimg::abs(*ptr);
+ return *this;
+ }
+
+ CImg<Tfloat> get_abs() const {
+ return CImg<Tfloat>(*this,false).abs();
+ }
+
+ //! Compute the cosinus of each pixel value.
+ CImg<T>& cos() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::cos((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_cos() const {
+ return CImg<Tfloat>(*this,false).cos();
+ }
+
+ //! Compute the sinus of each pixel value.
+ CImg<T>& sin() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sin((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_sin() const {
+ return CImg<Tfloat>(*this,false).sin();
+ }
+
+ //! Compute the tangent of each pixel.
+ CImg<T>& tan() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::tan((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_tan() const {
+ return CImg<Tfloat>(*this,false).tan();
+ }
+
+ //! Compute the arc-cosine of each pixel value.
+ CImg<T>& acos() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::acos((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_acos() const {
+ return CImg<Tfloat>(*this,false).acos();
+ }
+
+ //! Compute the arc-sinus of each pixel value.
+ CImg<T>& asin() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::asin((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_asin() const {
+ return CImg<Tfloat>(*this,false).asin();
+ }
+
+ //! Compute the arc-tangent of each pixel.
+ CImg<T>& atan() {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::atan((double)(*ptr));
+ return *this;
+ }
+
+ CImg<Tfloat> get_atan() const {
+ return CImg<Tfloat>(*this,false).atan();
+ }
+
+ //! Compute image with rounded pixel values.
+ /**
+ \param x Rounding precision.
+ \param rounding_type Roundin type, can be 0 (nearest), 1 (forward), -1(backward).
+ **/
+ CImg<T>& round(const float x, const int rounding_type=0) {
+ cimg_for(*this,ptr,T) (*ptr) = (T)cimg::round(*ptr,x,rounding_type);
+ return *this;
+ }
+
+ CImg<T> get_round(const float x, const unsigned int rounding_type=0) const {
+ return (+*this).round(x,rounding_type);
+ }
+
+ //! Fill the instance image with random values between specified range.
+ CImg<T>& rand(const T val_min, const T val_max) {
+ const float delta = (float)val_max - (float)val_min;
+ cimg_for(*this,ptr,T) *ptr = (T)(val_min + cimg::rand()*delta);
+ return *this;
+ }
+
+ CImg<T> get_rand(const T val_min, const T val_max) const {
+ return (+*this).rand(val_min,val_max);
+ }
+
+ //@}
+ //-----------------------------------
+ //
+ //! \name Usual Image Transformations
+ //@{
+ //-----------------------------------
+
+ //! Fill an image by a value \p val.
+ /**
+ \param val = fill value
+ \note All pixel values of the instance image will be initialized by \p val.
+ **/
+ CImg<T>& fill(const T val) {
+ if (is_empty()) return *this;
+ if (val && sizeof(T)!=1) cimg_for(*this,ptr,T) *ptr = val;
+ else cimg_std::memset(data,(int)val,size()*sizeof(T));
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val) const {
+ return CImg<T>(width,height,depth,dim).fill(val);
+ }
+
+ //! Fill sequentially all pixel values with values \a val0 and \a val1 respectively.
+ CImg<T>& fill(const T val0, const T val1) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-1;
+ for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; }
+ if (ptr!=ptr_end+1) *(ptr++) = val0;
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1);
+ }
+
+ //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2.
+ CImg<T>& fill(const T val0, const T val1, const T val2) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-2;
+ for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; }
+ ptr_end+=2;
+ switch (ptr_end-ptr) {
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2);
+ }
+
+ //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-3;
+ for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; }
+ ptr_end+=3;
+ switch (ptr_end-ptr) {
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3);
+ }
+
+ //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-4;
+ for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; }
+ ptr_end+=4;
+ switch (ptr_end-ptr) {
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4);
+ }
+
+ //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4 and \a val5.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-5;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
+ }
+ ptr_end+=5;
+ switch (ptr_end-ptr) {
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-6;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6;
+ }
+ ptr_end+=6;
+ switch (ptr_end-ptr) {
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-7;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3;
+ *(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7;
+ }
+ ptr_end+=7;
+ switch (ptr_end-ptr) {
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-8;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2;
+ *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
+ *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8;
+ }
+ ptr_end+=8;
+ switch (ptr_end-ptr) {
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-9;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
+ *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
+ }
+ ptr_end+=9;
+ switch (ptr_end-ptr) {
+ case 9 : *(--ptr_end) = val8;
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-10;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
+ *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
+ *(ptr++) = val10;
+ }
+ ptr_end+=10;
+ switch (ptr_end-ptr) {
+ case 10 : *(--ptr_end) = val9;
+ case 9 : *(--ptr_end) = val8;
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-11;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
+ *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
+ }
+ ptr_end+=11;
+ switch (ptr_end-ptr) {
+ case 11 : *(--ptr_end) = val10;
+ case 10 : *(--ptr_end) = val9;
+ case 9 : *(--ptr_end) = val8;
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-12;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
+ *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
+ *(ptr++) = val12;
+ }
+ ptr_end+=12;
+ switch (ptr_end-ptr) {
+ case 12 : *(--ptr_end) = val11;
+ case 11 : *(--ptr_end) = val10;
+ case 10 : *(--ptr_end) = val9;
+ case 9 : *(--ptr_end) = val8;
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+ const T val13) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-13;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
+ *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
+ *(ptr++) = val12; *(ptr++) = val13;
+ }
+ ptr_end+=13;
+ switch (ptr_end-ptr) {
+ case 13 : *(--ptr_end) = val12;
+ case 12 : *(--ptr_end) = val11;
+ case 11 : *(--ptr_end) = val10;
+ case 10 : *(--ptr_end) = val9;
+ case 9 : *(--ptr_end) = val8;
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+ const T val13) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
+ val13);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+ const T val13, const T val14) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-14;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
+ *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
+ *(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14;
+ }
+ ptr_end+=14;
+ switch (ptr_end-ptr) {
+ case 14 : *(--ptr_end) = val13;
+ case 13 : *(--ptr_end) = val12;
+ case 12 : *(--ptr_end) = val11;
+ case 11 : *(--ptr_end) = val10;
+ case 10 : *(--ptr_end) = val9;
+ case 9 : *(--ptr_end) = val8;
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+ const T val13, const T val14) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
+ val13,val14);
+ }
+
+ //! Fill sequentially pixel values.
+ CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+ const T val13, const T val14, const T val15) {
+ if (is_empty()) return *this;
+ T *ptr, *ptr_end = end()-15;
+ for (ptr = data; ptr<ptr_end; ) {
+ *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
+ *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
+ *(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14; *(ptr++) = val15;
+ }
+ ptr_end+=15;
+ switch (ptr_end-ptr) {
+ case 15 : *(--ptr_end) = val14;
+ case 14 : *(--ptr_end) = val13;
+ case 13 : *(--ptr_end) = val12;
+ case 12 : *(--ptr_end) = val11;
+ case 11 : *(--ptr_end) = val10;
+ case 10 : *(--ptr_end) = val9;
+ case 9 : *(--ptr_end) = val8;
+ case 8 : *(--ptr_end) = val7;
+ case 7 : *(--ptr_end) = val6;
+ case 6 : *(--ptr_end) = val5;
+ case 5 : *(--ptr_end) = val4;
+ case 4 : *(--ptr_end) = val3;
+ case 3 : *(--ptr_end) = val2;
+ case 2 : *(--ptr_end) = val1;
+ case 1 : *(--ptr_end) = val0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+ const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+ const T val13, const T val14, const T val15) const {
+ return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
+ val13,val14,val15);
+ }
+
+ //! Fill image values according to the values found in the specified string.
+ CImg<T>& fill(const char *const values, const bool repeat_pattern) {
+ if (is_empty() || !values) return *this;
+ T *ptrd = data, *ptr_end = data + size();
+ const char *nvalues = values;
+ const unsigned int siz = size();
+ char cval[64] = { 0 }, sep = 0;
+ int err = 0; double val = 0; unsigned int nb = 0;
+ while ((err=cimg_std::sscanf(nvalues,"%63[ \n\t0-9e.+-]%c",cval,&sep))>0 &&
+ cimg_std::sscanf(cval,"%lf",&val)>0 && nb<siz) {
+ nvalues += cimg::strlen(cval);
+ *(ptrd++) = (T)val;
+ ++nb;
+ if (err!=2) break; else ++nvalues;
+ }
+ if (repeat_pattern && nb) for (T *ptrs = data; ptrd<ptr_end; ++ptrs) *(ptrd++) = *ptrs;
+ return *this;
+ }
+
+ CImg<T> get_fill(const char *const values, const bool repeat_pattern) const {
+ return repeat_pattern?CImg<T>(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern);
+ }
+
+ //! Fill image values according to the values found in the specified image.
+ template<typename t>
+ CImg<T>& fill(const CImg<t>& values, const bool repeat_pattern=true) {
+ if (is_empty() || !values) return *this;
+ T *ptrd = data, *ptrd_end = ptrd + size();
+ for (t *ptrs = values.data, *ptrs_end = ptrs + values.size(); ptrs<ptrs_end && ptrd<ptrd_end; ++ptrs) *(ptrd++) = (T)*ptrs;
+ if (repeat_pattern && ptrd<ptrd_end) for (T *ptrs = data; ptrd<ptrd_end; ++ptrs) *(ptrd++) = *ptrs;
+ return *this;
+ }
+
+ template<typename t>
+ CImg<T> get_fill(const CImg<t>& values, const bool repeat_pattern=true) const {
+ return repeat_pattern?CImg<T>(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern);
+ }
+
+ //! Fill image values along the X-axis at the specified pixel position (y,z,v).
+ CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const int a0, ...) {
+#define _cimg_fill1(x,y,z,v,off,siz,t) { \
+ va_list ap; va_start(ap,a0); T *ptrd = ptr(x,y,z,v); *ptrd = (T)a0; \
+ for (unsigned int k = 1; k<siz; ++k) { ptrd+=off; *ptrd = (T)va_arg(ap,t); } \
+ va_end(ap); }
+ if (y<height && z<depth && v<dim) _cimg_fill1(0,y,z,v,1,width,int);
+ return *this;
+ }
+
+ CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const double a0, ...) {
+ if (y<height && z<depth && v<dim) _cimg_fill1(0,y,z,v,1,width,double);
+ return *this;
+ }
+
+ //! Fill image values along the Y-axis at the specified pixel position (x,z,v).
+ CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const int a0, ...) {
+ if (x<width && z<depth && v<dim) _cimg_fill1(x,0,z,v,width,height,int);
+ return *this;
+ }
+
+ CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const double a0, ...) {
+ if (x<width && z<depth && v<dim) _cimg_fill1(x,0,z,v,width,height,double);
+ return *this;
+ }
+
+ //! Fill image values along the Z-axis at the specified pixel position (x,y,v).
+ CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const int a0, ...) {
+ const unsigned int wh = width*height;
+ if (x<width && y<height && v<dim) _cimg_fill1(x,y,0,v,wh,depth,int);
+ return *this;
+ }
+
+ CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const double a0, ...) {
+ const unsigned int wh = width*height;
+ if (x<width && y<height && v<dim) _cimg_fill1(x,y,0,v,wh,depth,double);
+ return *this;
+ }
+
+ //! Fill image values along the V-axis at the specified pixel position (x,y,z).
+ CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {
+ const unsigned int whz = width*height*depth;
+ if (x<width && y<height && z<depth) _cimg_fill1(x,y,z,0,whz,dim,int);
+ return *this;
+ }
+
+ CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) {
+ const unsigned int whz = width*height*depth;
+ if (x<width && y<height && z<depth) _cimg_fill1(x,y,z,0,whz,dim,double);
+ return *this;
+ }
+
+ //! Linear normalization of the pixel values between \a a and \a b.
+ CImg<T>& normalize(const T a, const T b) {
+ if (is_empty()) return *this;
+ const T na = a<b?a:b, nb = a<b?b:a;
+ T m, M = maxmin(m);
+ const Tfloat fm = (Tfloat)m, fM = (Tfloat)M;
+ if (m==M) return fill(0);
+ if (m!=na || M!=nb) cimg_for(*this,ptr,T) *ptr = (T)((*ptr-fm)/(fM-fm)*(nb-na)+na);
+ return *this;
+ }
+
+ CImg<T> get_normalize(const T a, const T b) const {
+ return (+*this).normalize(a,b);
+ }
+
+ //! Cut pixel values between \a a and \a b.
+ CImg<T>& cut(const T a, const T b) {
+ if (is_empty()) return *this;
+ const T na = a<b?a:b, nb = a<b?b:a;
+ cimg_for(*this,ptr,T) *ptr = (*ptr<na)?na:((*ptr>nb)?nb:*ptr);
+ return *this;
+ }
+
+ CImg<T> get_cut(const T a, const T b) const {
+ return (+*this).cut(a,b);
+ }
+
+ //! Quantize pixel values into \n levels.
+ CImg<T>& quantize(const unsigned int n, const bool keep_range=true) {
+ if (is_empty()) return *this;
+ if (!n)
+ throw CImgArgumentException("CImg<%s>::quantize() : Cannot quantize image to 0 values.",
+ pixel_type());
+ Tfloat m, M = (Tfloat)maxmin(m), range = M - m;
+ if (range>0) {
+ if (keep_range) cimg_for(*this,ptr,T) {
+ const unsigned int val = (unsigned int)((*ptr-m)*n/range);
+ *ptr = (T)(m + cimg::min(val,n-1)*range/n);
+ } else cimg_for(*this,ptr,T) {
+ const unsigned int val = (unsigned int)((*ptr-m)*n/range);
+ *ptr = (T)cimg::min(val,n-1);
+ }
+ }
+ return *this;
+ }
+
+ CImg<T> get_quantize(const unsigned int n, const bool keep_range=true) const {
+ return (+*this).quantize(n,keep_range);
+ }
+
+ //! Threshold the image.
+ /**
+ \param value Threshold value.
+ \param soft Enable soft thresholding.
+ \param strict Tells if the threshold is strict.
+ **/
+ CImg<T>& threshold(const T value, const bool soft=false, const bool strict=false) {
+ if (is_empty()) return *this;
+ if (strict) {
+ if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>value?(T)(v-value):v<-value?(T)(v+value):(T)0; }
+ else cimg_for(*this,ptr,T) *ptr = *ptr>value?(T)1:(T)0;
+ } else {
+ if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>=value?(T)(v-value):v<=-value?(T)(v+value):(T)0; }
+ else cimg_for(*this,ptr,T) *ptr = *ptr>=value?(T)1:(T)0;
+ }
+ return *this;
+ }
+
+ CImg<T> get_threshold(const T value, const bool soft=false, const bool strict=false) const {
+ return (+*this).threshold(value,soft,strict);
+ }
+
+ //! Rotate an image.
+ /**
+ \param angle = rotation angle (in degrees).
+ \param cond = rotation type. can be :
+ - 0 = zero-value at borders
+ - 1 = nearest pixel.
+ - 2 = Fourier style.
+ \note Returned image will probably have a different size than the instance image *this.
+ **/
+ CImg<T>& rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) {
+ return get_rotate(angle,border_conditions,interpolation).transfer_to(*this);
+ }
+
+ CImg<T> get_rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) const {
+ if (is_empty()) return *this;
+ CImg<T> dest;
+ const float nangle = cimg::mod(angle,360.0f);
+ if (border_conditions!=1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
+ const int wm1 = dimx()-1, hm1 = dimy()-1;
+ const int iangle = (int)nangle/90;
+ switch (iangle) {
+ case 1 : {
+ dest.assign(height,width,depth,dim);
+ cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(y,hm1-x,z,v);
+ } break;
+ case 2 : {
+ dest.assign(width,height,depth,dim);
+ cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-x,hm1-y,z,v);
+ } break;
+ case 3 : {
+ dest.assign(height,width,depth,dim);
+ cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-y,x,z,v);
+ } break;
+ default :
+ return *this;
+ }
+ } else { // generic version
+ const float
+ rad = (float)(nangle*cimg::valuePI/180.0),
+ ca = (float)cimg_std::cos(rad),
+ sa = (float)cimg_std::sin(rad),
+ ux = cimg::abs(width*ca), uy = cimg::abs(width*sa),
+ vx = cimg::abs(height*sa), vy = cimg::abs(height*ca),
+ w2 = 0.5f*width, h2 = 0.5f*height,
+ dw2 = 0.5f*(ux+vx), dh2 = 0.5f*(uy+vy);
+ dest.assign((int)(ux+vx), (int)(uy+vy),depth,dim);
+ switch (border_conditions) {
+ case 0 : {
+ switch (interpolation) {
+ case 2 : {
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0);
+ } break;
+ case 1 : {
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0);
+ } break;
+ default : {
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v,0);
+ }
+ }
+ } break;
+ case 1 : {
+ switch (interpolation) {
+ case 2 :
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v);
+ break;
+ case 1 :
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v);
+ break;
+ default :
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v);
+ }
+ } break;
+ case 2 : {
+ switch (interpolation) {
+ case 2 :
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)cubic_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()),
+ cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v);
+ break;
+ case 1 :
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)linear_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()),
+ cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v);
+ break;
+ default :
+ cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (*this)(cimg::mod((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),dimx()),
+ cimg::mod((int)(h2 - (x-dw2)*sa + (y-dh2)*ca),dimy()),z,v);
+ }
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid border conditions %d (should be 0,1 or 2).",
+ pixel_type(),border_conditions);
+ }
+ }
+ return dest;
+ }
+
+ //! Rotate an image around a center point (\c cx,\c cy).
+ /**
+ \param angle = rotation angle (in degrees).
+ \param cx = X-coordinate of the rotation center.
+ \param cy = Y-coordinate of the rotation center.
+ \param zoom = zoom.
+ \param cond = rotation type. can be :
+ - 0 = zero-value at borders
+ - 1 = repeat image at borders
+ - 2 = zero-value at borders and linear interpolation
+ **/
+ CImg<T>& rotate(const float angle, const float cx, const float cy, const float zoom,
+ const unsigned int border_conditions=3, const unsigned int interpolation=1) {
+ return get_rotate(angle,cx,cy,zoom,border_conditions,interpolation).transfer_to(*this);
+ }
+
+ CImg<T> get_rotate(const float angle, const float cx, const float cy, const float zoom,
+ const unsigned int border_conditions=3, const unsigned int interpolation=1) const {
+ if (interpolation>2)
+ throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid interpolation parameter %d (should be {0=none, 1=linear or 2=cubic}).",
+ pixel_type(),interpolation);
+ if (is_empty()) return *this;
+ CImg<T> dest(width,height,depth,dim);
+ const float nangle = cimg::mod(angle,360.0f);
+ if (border_conditions!=1 && zoom==1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
+ const int iangle = (int)nangle/90;
+ switch (iangle) {
+ case 1 : {
+ dest.fill(0);
+ const unsigned int
+ xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
+ ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
+ xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
+ yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
+ cimg_forZV(dest,z,v) for (unsigned int y = ymin; y<ymax; ++y) for (unsigned int x = xmin; x<xmax; ++x)
+ dest(x,y,z,v) = (*this)(y-yoff,height-1-x+xoff,z,v);
+ } break;
+ case 2 : {
+ cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(width-1-x,height-1-y,z,v);
+ } break;
+ case 3 : {
+ dest.fill(0);
+ const unsigned int
+ xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
+ ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
+ xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
+ yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
+ cimg_forZV(dest,z,v) for (unsigned int y = ymin; y<ymax; ++y) for (unsigned int x = xmin; x<xmax; ++x)
+ dest(x,y,z,v) = (*this)(width-1-y+yoff,x-xoff,z,v);
+ } break;
+ default :
+ return *this;
+ }
+ } else {
+ const float
+ rad = (float)((nangle*cimg::valuePI)/180.0),
+ ca = (float)cimg_std::cos(rad)/zoom,
+ sa = (float)cimg_std::sin(rad)/zoom;
+ switch (border_conditions) { // generic version
+ case 0 : {
+ switch (interpolation) {
+ case 2 : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v,0);
+ } break;
+ case 1 : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v,0);
+ } break;
+ default : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,v,0);
+ }
+ }
+ } break;
+ case 1 : {
+ switch (interpolation) {
+ case 2 : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v);
+ } break;
+ case 1 : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v);
+ } break;
+ default : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,v);
+ }
+ }
+ } break;
+ case 2 : {
+ switch (interpolation) {
+ case 2 : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)cubic_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)dimx()),
+ cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)dimy()),z,v);
+ } break;
+ case 1 : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (T)linear_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)dimx()),
+ cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)dimy()),z,v);
+ } break;
+ default : {
+ cimg_forXY(dest,x,y)
+ cimg_forZV(*this,z,v)
+ dest(x,y,z,v) = (*this)(cimg::mod((int)(cx + (x-cx)*ca + (y-cy)*sa),dimx()),
+ cimg::mod((int)(cy - (x-cx)*sa + (y-cy)*ca),dimy()),z,v);
+ }
+ }
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::get_rotate() : Incorrect border conditions %d (should be 0,1 or 2).",
+ pixel_type(),border_conditions);
+ }
+ }
+ return dest;
+ }
+
+ //! Resize an image.
+ /**
+ \param pdx Number of columns (new size along the X-axis).
+ \param pdy Number of rows (new size along the Y-axis).
+ \param pdz Number of slices (new size along the Z-axis).
+ \param pdv Number of vector-channels (new size along the V-axis).
+ \param interpolation_type Method of interpolation :
+ - -1 = no interpolation : raw memory resizing.
+ - 0 = no interpolation : additional space is filled according to \p border_condition.
+ - 1 = bloc interpolation (nearest point).
+ - 2 = moving average interpolation.
+ - 3 = linear interpolation.
+ - 4 = grid interpolation.
+ - 5 = bi-cubic interpolation.
+ \param border_condition Border condition type.
+ \param center Set centering type (only if \p interpolation_type=0).
+ \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
+ **/
+ CImg<T>& resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100,
+ const int interpolation_type=1, const int border_condition=-1, const bool center=false) {
+ if (!pdx || !pdy || !pdz || !pdv) return assign();
+ const unsigned int
+ tdx = pdx<0?-pdx*width/100:pdx,
+ tdy = pdy<0?-pdy*height/100:pdy,
+ tdz = pdz<0?-pdz*depth/100:pdz,
+ tdv = pdv<0?-pdv*dim/100:pdv,
+ dx = tdx?tdx:1,
+ dy = tdy?tdy:1,
+ dz = tdz?tdz:1,
+ dv = tdv?tdv:1;
+ if (width==dx && height==dy && depth==dz && dim==dv) return *this;
+ if (interpolation_type==-1 && dx*dy*dz*dv==size()) {
+ width = dx; height = dy; depth = dz; dim = dv;
+ return *this;
+ }
+ return get_resize(dx,dy,dz,dv,interpolation_type,border_condition,center).transfer_to(*this);
+ }
+
+ CImg<T> get_resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100,
+ const int interpolation_type=1, const int border_condition=-1, const bool center=false) const {
+ if (!pdx || !pdy || !pdz || !pdv) return CImg<T>();
+ const unsigned int
+ tdx = pdx<0?-pdx*width/100:pdx,
+ tdy = pdy<0?-pdy*height/100:pdy,
+ tdz = pdz<0?-pdz*depth/100:pdz,
+ tdv = pdv<0?-pdv*dim/100:pdv,
+ dx = tdx?tdx:1,
+ dy = tdy?tdy:1,
+ dz = tdz?tdz:1,
+ dv = tdv?tdv:1;
+ if (width==dx && height==dy && depth==dz && dim==dv) return +*this;
+ if (is_empty()) return CImg<T>(dx,dy,dz,dv,0);
+
+ CImg<T> res;
+
+ switch (interpolation_type) {
+ case -1 : // Raw resizing
+ cimg_std::memcpy(res.assign(dx,dy,dz,dv,0).data,data,sizeof(T)*cimg::min(size(),(long unsigned int)dx*dy*dz*dv));
+ break;
+
+ case 0 : { // No interpolation
+ const unsigned int bx = width-1, by = height-1, bz = depth-1, bv = dim-1;
+ res.assign(dx,dy,dz,dv);
+ switch (border_condition) {
+ case 1 : {
+ if (center) {
+ const int
+ x0 = (res.dimx()-dimx())/2,
+ y0 = (res.dimy()-dimy())/2,
+ z0 = (res.dimz()-dimz())/2,
+ v0 = (res.dimv()-dimv())/2,
+ x1 = x0 + (int)bx,
+ y1 = y0 + (int)by,
+ z1 = z0 + (int)bz,
+ v1 = v0 + (int)bv;
+ res.draw_image(x0,y0,z0,v0,*this);
+ cimg_for_outXYZV(res,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) res(x,y,z,v) = _atXYZV(x-x0,y-y0,z-z0,v-v0);
+ } else {
+ res.draw_image(*this);
+ cimg_for_outXYZV(res,0,0,0,0,bx,by,bz,bv,x,y,z,v) res(x,y,z,v) = _atXYZV(x,y,z,v);
+ }
+ } break;
+ case 2 : {
+ int nx0 = 0, ny0 = 0, nz0 = 0, nv0 = 0;
+ if (center) {
+ const int
+ x0 = (res.dimx()-dimx())/2,
+ y0 = (res.dimy()-dimy())/2,
+ z0 = (res.dimz()-dimz())/2,
+ v0 = (res.dimv()-dimv())/2;
+ nx0 = x0>0?x0-(1+x0/width)*width:x0;
+ ny0 = y0>0?y0-(1+y0/height)*height:y0;
+ nz0 = z0>0?z0-(1+z0/depth)*depth:z0;
+ nv0 = v0>0?v0-(1+v0/dim)*dim:v0;
+ }
+ for (int k = nv0; k<(int)dv; k+=dimv())
+ for (int z = nz0; z<(int)dz; z+=dimz())
+ for (int y = ny0; y<(int)dy; y+=dimy())
+ for (int x = nx0; x<(int)dx; x+=dimx()) res.draw_image(x,y,z,k,*this);
+ } break;
+ default : {
+ res.fill(0);
+ if (center) res.draw_image((res.dimx()-dimx())/2,(res.dimy()-dimy())/2,(res.dimz()-dimz())/2,(res.dimv()-dimv())/2,*this);
+ else res.draw_image(*this);
+ }
+ }
+ } break;
+
+ case 1 : { // Nearest-neighbor interpolation
+ res.assign(dx,dy,dz,dv);
+ unsigned int
+ *const offx = new unsigned int[dx],
+ *const offy = new unsigned int[dy+1],
+ *const offz = new unsigned int[dz+1],
+ *const offv = new unsigned int[dv+1],
+ *poffx, *poffy, *poffz, *poffv,
+ curr, old;
+ const unsigned int wh = width*height, whd = width*height*depth, rwh = dx*dy, rwhd = dx*dy*dz;
+ poffx = offx; curr = 0; { cimg_forX(res,x) { old=curr; curr=(x+1)*width/dx; *(poffx++) = (unsigned int)curr-(unsigned int)old; }}
+ poffy = offy; curr = 0; { cimg_forY(res,y) { old=curr; curr=(y+1)*height/dy; *(poffy++) = width*((unsigned int)curr-(unsigned int)old); }} *poffy=0;
+ poffz = offz; curr = 0; { cimg_forZ(res,z) { old=curr; curr=(z+1)*depth/dz; *(poffz++) = wh*((unsigned int)curr-(unsigned int)old); }} *poffz=0;
+ poffv = offv; curr = 0; { cimg_forV(res,k) { old=curr; curr=(k+1)*dim/dv; *(poffv++) = whd*((unsigned int)curr-(unsigned int)old); }} *poffv=0;
+ T *ptrd = res.data;
+ const T* ptrv = data;
+ poffv = offv;
+ for (unsigned int k=0; k<dv; ) {
+ const T *ptrz = ptrv;
+ poffz = offz;
+ for (unsigned int z=0; z<dz; ) {
+ const T *ptry = ptrz;
+ poffy = offy;
+ for (unsigned int y=0; y<dy; ) {
+ const T *ptrx = ptry;
+ poffx = offx;
+ cimg_forX(res,x) { *(ptrd++) = *ptrx; ptrx+=*(poffx++); }
+ ++y;
+ unsigned int dy = *(poffy++);
+ for (;!dy && y<dy; cimg_std::memcpy(ptrd, ptrd-dx, sizeof(T)*dx), ++y, ptrd+=dx, dy=*(poffy++)) {}
+ ptry+=dy;
+ }
+ ++z;
+ unsigned int dz = *(poffz++);
+ for (;!dz && z<dz; cimg_std::memcpy(ptrd, ptrd-rwh, sizeof(T)*rwh), ++z, ptrd+=rwh, dz=*(poffz++)) {}
+ ptrz+=dz;
+ }
+ ++k;
+ unsigned int dv = *(poffv++);
+ for (;!dv && k<dv; cimg_std::memcpy(ptrd, ptrd-rwhd, sizeof(T)*rwhd), ++k, ptrd+=rwhd, dv=*(poffv++)) {}
+ ptrv+=dv;
+ }
+ delete[] offx; delete[] offy; delete[] offz; delete[] offv;
+ } break;
+
+ case 2 : { // Moving average
+ bool instance_first = true;
+ if (dx!=width) {
+ CImg<Tfloat> tmp(dx,height,depth,dim,0);
+ for (unsigned int a = width*dx, b = width, c = dx, s = 0, t = 0; a; ) {
+ const unsigned int d = cimg::min(b,c);
+ a-=d; b-=d; c-=d;
+ cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d;
+ if (!b) { cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)/=width; ++t; b = width; }
+ if (!c) { ++s; c = dx; }
+ }
+ tmp.transfer_to(res);
+ instance_first = false;
+ }
+ if (dy!=height) {
+ CImg<Tfloat> tmp(dx,dy,depth,dim,0);
+ for (unsigned int a = height*dy, b = height, c = dy, s = 0, t = 0; a; ) {
+ const unsigned int d = cimg::min(b,c);
+ a-=d; b-=d; c-=d;
+ if (instance_first) cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d;
+ else cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d;
+ if (!b) { cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)/=height; ++t; b = height; }
+ if (!c) { ++s; c = dy; }
+ }
+ tmp.transfer_to(res);
+ instance_first = false;
+ }
+ if (dz!=depth) {
+ CImg<Tfloat> tmp(dx,dy,dz,dim,0);
+ for (unsigned int a = depth*dz, b = depth, c = dz, s = 0, t = 0; a; ) {
+ const unsigned int d = cimg::min(b,c);
+ a-=d; b-=d; c-=d;
+ if (instance_first) cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d;
+ else cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d;
+ if (!b) { cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)/=depth; ++t; b = depth; }
+ if (!c) { ++s; c = dz; }
+ }
+ tmp.transfer_to(res);
+ instance_first = false;
+ }
+ if (dv!=dim) {
+ CImg<Tfloat> tmp(dx,dy,dz,dv,0);
+ for (unsigned int a = dim*dv, b = dim, c = dv, s = 0, t = 0; a; ) {
+ const unsigned int d = cimg::min(b,c);
+ a-=d; b-=d; c-=d;
+ if (instance_first) cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d;
+ else cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d;
+ if (!b) { cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=dim; ++t; b = dim; }
+ if (!c) { ++s; c = dv; }
+ }
+ tmp.transfer_to(res);
+ instance_first = false;
+ }
+ } break;
+
+ case 3 : { // Linear interpolation
+ const unsigned int dimmax = cimg::max(dx,dy,dz,dv);
+ const float
+ sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
+ sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
+ sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
+ sv = (border_condition<0 && dv>dim )?(dv>1?(dim-1.0f)/(dv-1) :0):(float)dim/dv;
+
+ unsigned int *const off = new unsigned int[dimmax], *poff;
+ float *const foff = new float[dimmax], *pfoff, old, curr;
+ CImg<T> resx, resy, resz, resv;
+ T *ptrd;
+
+ if (dx!=width) {
+ if (width==1) resx = get_resize(dx,height,depth,dim,1,0);
+ else {
+ resx.assign(dx,height,depth,dim);
+ curr = old = 0; poff = off; pfoff = foff;
+ cimg_forX(resx,x) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sx; *(poff++) = (unsigned int)curr-(unsigned int)old; }
+ ptrd = resx.data;
+ const T *ptrs0 = data;
+ cimg_forYZV(resx,y,z,k) {
+ poff = off; pfoff = foff;
+ const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (width-1);
+ cimg_forX(resx,x) {
+ const float alpha = *(pfoff++);
+ const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+1):(border_condition?val1:(T)0);
+ *(ptrd++) = (T)((1-alpha)*val1 + alpha*val2);
+ ptrs+=*(poff++);
+ }
+ ptrs0+=width;
+ }
+ }
+ } else resx.assign(*this,true);
+
+ if (dy!=height) {
+ if (height==1) resy = resx.get_resize(dx,dy,depth,dim,1,0);
+ else {
+ resy.assign(dx,dy,depth,dim);
+ curr = old = 0; poff = off; pfoff = foff;
+ cimg_forY(resy,y) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sy; *(poff++) = dx*((unsigned int)curr-(unsigned int)old); }
+ cimg_forXZV(resy,x,z,k) {
+ ptrd = resy.ptr(x,0,z,k);
+ const T *ptrs = resx.ptr(x,0,z,k), *const ptrsmax = ptrs + (height-1)*dx;
+ poff = off; pfoff = foff;
+ cimg_forY(resy,y) {
+ const float alpha = *(pfoff++);
+ const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+dx):(border_condition?val1:(T)0);
+ *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+ ptrd+=dx;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ resx.assign();
+ } else resy.assign(resx,true);
+
+ if (dz!=depth) {
+ if (depth==1) resz = resy.get_resize(dx,dy,dz,dim,1,0);
+ else {
+ const unsigned int wh = dx*dy;
+ resz.assign(dx,dy,dz,dim);
+ curr = old = 0; poff = off; pfoff = foff;
+ cimg_forZ(resz,z) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sz; *(poff++) = wh*((unsigned int)curr-(unsigned int)old); }
+ cimg_forXYV(resz,x,y,k) {
+ ptrd = resz.ptr(x,y,0,k);
+ const T *ptrs = resy.ptr(x,y,0,k), *const ptrsmax = ptrs + (depth-1)*wh;
+ poff = off; pfoff = foff;
+ cimg_forZ(resz,z) {
+ const float alpha = *(pfoff++);
+ const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+wh):(border_condition?val1:(T)0);
+ *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+ ptrd+=wh;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ resy.assign();
+ } else resz.assign(resy,true);
+
+ if (dv!=dim) {
+ if (dim==1) resv = resz.get_resize(dx,dy,dz,dv,1,0);
+ else {
+ const unsigned int whd = dx*dy*dz;
+ resv.assign(dx,dy,dz,dv);
+ curr = old = 0; poff = off; pfoff = foff;
+ cimg_forV(resv,k) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sv; *(poff++) = whd*((unsigned int)curr-(unsigned int)old); }
+ cimg_forXYZ(resv,x,y,z) {
+ ptrd = resv.ptr(x,y,z,0);
+ const T *ptrs = resz.ptr(x,y,z,0), *const ptrsmax = ptrs + (dim-1)*whd;
+ poff = off; pfoff = foff;
+ cimg_forV(resv,k) {
+ const float alpha = *(pfoff++);
+ const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+whd):(border_condition?val1:(T)0);
+ *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+ ptrd+=whd;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ resz.assign();
+ } else resv.assign(resz,true);
+
+ delete[] off; delete[] foff;
+ return resv.is_shared?(resz.is_shared?(resy.is_shared?(resx.is_shared?(+(*this)):resx):resy):resz):resv;
+ } break;
+
+ case 4 : { // Grid filling
+ res.assign(dx,dy,dz,dv,0);
+ cimg_forXYZV(*this,x,y,z,k) res(x*dx/width,y*dy/height,z*dz/depth,k*dv/dim) = (*this)(x,y,z,k);
+ } break;
+
+ case 5 : { // Cubic interpolation
+ const float
+ sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
+ sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
+ sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
+ sv = (border_condition<0 && dv>dim )?(dv>1?(dim-1.0f)/(dv-1) :0):(float)dim/dv;
+ res.assign(dx,dy,dz,dv);
+ T *ptrd = res.ptr();
+ float cx, cy, cz, ck = 0;
+ cimg_forV(res,k) { cz = 0;
+ cimg_forZ(res,z) { cy = 0;
+ cimg_forY(res,y) { cx = 0;
+ cimg_forX(res,x) {
+ *(ptrd++) = (T)(border_condition?_cubic_atXY(cx,cy,(int)cz,(int)ck):cubic_atXY(cx,cy,(int)cz,(int)ck,0));
+ cx+=sx;
+ } cy+=sy;
+ } cz+=sz;
+ } ck+=sv;
+ }
+ } break;
+
+ default : // Invalid interpolation method
+ throw CImgArgumentException("CImg<%s>::resize() : Invalid interpolation_type %d "
+ "(should be { -1=raw, 0=zero, 1=nearest, 2=average, 3=linear, 4=grid, 5=bicubic}).",
+ pixel_type(),interpolation_type);
+ }
+ return res;
+ }
+
+ //! Resize an image.
+ /**
+ \param src Image giving the geometry of the resize.
+ \param interpolation_type Interpolation method :
+ - 1 = raw memory
+ - 0 = no interpolation : additional space is filled with 0.
+ - 1 = bloc interpolation (nearest point).
+ - 2 = mosaic : image is repeated if necessary.
+ - 3 = linear interpolation.
+ - 4 = grid interpolation.
+ - 5 = bi-cubic interpolation.
+ \param border_condition Border condition type.
+ \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
+ **/
+ template<typename t>
+ CImg<T>& resize(const CImg<t>& src, const int interpolation_type=1,
+ const int border_condition=-1, const bool center=false) {
+ return resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center);
+ }
+
+ template<typename t>
+ CImg<T> get_resize(const CImg<t>& src, const int interpolation_type=1,
+ const int border_condition=-1, const bool center=false) const {
+ return get_resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center);
+ }
+
+ //! Resize an image.
+ /**
+ \param disp = Display giving the geometry of the resize.
+ \param interpolation_type = Resizing type :
+ - 0 = no interpolation : additional space is filled with 0.
+ - 1 = bloc interpolation (nearest point).
+ - 2 = mosaic : image is repeated if necessary.
+ - 3 = linear interpolation.
+ - 4 = grid interpolation.
+ - 5 = bi-cubic interpolation.
+ - 6 = moving average (best quality for photographs)
+ \param border_condition Border condition type.
+ \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
+ **/
+ CImg<T>& resize(const CImgDisplay& disp, const int interpolation_type=1,
+ const int border_condition=-1, const bool center=false) {
+ return resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center);
+ }
+
+ CImg<T> get_resize(const CImgDisplay& disp, const int interpolation_type=1,
+ const int border_condition=-1, const bool center=false) const {
+ return get_resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center);
+ }
+
+ //! Half-resize an image, using a special optimized filter.
+ CImg<T>& resize_halfXY() {
+ return get_resize_halfXY().transfer_to(*this);
+ }
+
+ CImg<T> get_resize_halfXY() const {
+ if (is_empty()) return *this;
+ const Tfloat mask[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f,
+ 0.1231940459f, 0.1935127547f, 0.1231940459f,
+ 0.07842776544f, 0.1231940459f, 0.07842776544f };
+ T I[9] = { 0 };
+ CImg<T> dest(width/2,height/2,depth,dim);
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I)
+ if (x%2 && y%2) dest(x/2,y/2,z,k) = (T)
+ (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
+ I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
+ I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
+ return dest;
+ }
+
+ //! Upscale an image by a factor 2x.
+ /**
+ Use anisotropic upscaling algorithm described at
+ http://scale2x.sourceforge.net/algorithm.html
+ **/
+ CImg<T>& resize_doubleXY() {
+ return get_resize_doubleXY().transfer_to(*this);
+ }
+
+ CImg<T> get_resize_doubleXY() const {
+#define _cimg_gs2x_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ _p1##i = i++, ++_n1##i, ptrd1+=(res).width, ptrd2+=(res).width)
+
+#define _cimg_gs2x_for3x3(img,x,y,z,v,I) \
+ _cimg_gs2x_for3((img).height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[1] = (img)(0,_p1##y,z,v)), \
+ (I[3] = I[4] = (img)(0,y,z,v)), \
+ (I[7] = (img)(0,_n1##y,z,v)), \
+ 1>=(img).width?(int)((img).width)-1:1); \
+ (_n1##x<(int)((img).width) && ( \
+ (I[2] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[5] = (img)(_n1##x,y,z,v)), \
+ (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
+ x==--_n1##x; \
+ I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+ if (is_empty()) return *this;
+ CImg<T> res(2*width,2*height,depth,dim);
+ CImg_3x3(I,T);
+ cimg_forZV(*this,z,k) {
+ T
+ *ptrd1 = res.ptr(0,0,0,k),
+ *ptrd2 = ptrd1 + res.width;
+ _cimg_gs2x_for3x3(*this,x,y,0,k,I) {
+ if (Icp!=Icn && Ipc!=Inc) {
+ *(ptrd1++) = Ipc==Icp?Ipc:Icc;
+ *(ptrd1++) = Icp==Inc?Inc:Icc;
+ *(ptrd2++) = Ipc==Icn?Ipc:Icc;
+ *(ptrd2++) = Icn==Inc?Inc:Icc;
+ } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; }
+ }
+ }
+ return res;
+ }
+
+ //! Upscale an image by a factor 3x.
+ /**
+ Use anisotropic upscaling algorithm described at
+ http://scale2x.sourceforge.net/algorithm.html
+ **/
+ CImg<T>& resize_tripleXY() {
+ return get_resize_tripleXY().transfer_to(*this);
+ }
+
+ CImg<T> get_resize_tripleXY() const {
+#define _cimg_gs3x_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ _p1##i = i++, ++_n1##i, ptrd1+=2*(res).width, ptrd2+=2*(res).width, ptrd3+=2*(res).width)
+
+#define _cimg_gs3x_for3x3(img,x,y,z,v,I) \
+ _cimg_gs3x_for3((img).height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[0] = I[1] = (img)(0,_p1##y,z,v)), \
+ (I[3] = I[4] = (img)(0,y,z,v)), \
+ (I[6] = I[7] = (img)(0,_n1##y,z,v)), \
+ 1>=(img).width?(int)((img).width)-1:1); \
+ (_n1##x<(int)((img).width) && ( \
+ (I[2] = (img)(_n1##x,_p1##y,z,v)), \
+ (I[5] = (img)(_n1##x,y,z,v)), \
+ (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+ if (is_empty()) return *this;
+ CImg<T> res(3*width,3*height,depth,dim);
+ CImg_3x3(I,T);
+ cimg_forZV(*this,z,k) {
+ T
+ *ptrd1 = res.ptr(0,0,0,k),
+ *ptrd2 = ptrd1 + res.width,
+ *ptrd3 = ptrd2 + res.width;
+ _cimg_gs3x_for3x3(*this,x,y,0,k,I) {
+ if (Icp != Icn && Ipc != Inc) {
+ *(ptrd1++) = Ipc==Icp?Ipc:Icc;
+ *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc;
+ *(ptrd1++) = Icp==Inc?Inc:Icc;
+ *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc;
+ *(ptrd2++) = Icc;
+ *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc;
+ *(ptrd3++) = Ipc==Icn?Ipc:Icc;
+ *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc;
+ *(ptrd3++) = Icn==Inc?Inc:Icc;
+ } else {
+ *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc;
+ *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc;
+ *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc;
+ }
+ }
+ }
+ return res;
+ }
+
+ // Warp an image.
+ template<typename t>
+ CImg<T>& warp(const CImg<t>& warp, const bool relative=false,
+ const bool interpolation=true, const unsigned int border_conditions=0) {
+ return get_warp(warp,relative,interpolation,border_conditions).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<T> get_warp(const CImg<t>& warp, const bool relative=false,
+ const bool interpolation=true, const unsigned int border_conditions=0) const {
+ if (is_empty() || !warp) return *this;
+ if (!is_sameXYZ(warp))
+ throw CImgArgumentException("CImg<%s>::warp() : Instance image (%u,%u,%u,%u,%p) and warping field (%u,%u,%u,%u,%p) "
+ "have different XYZ dimensions.",
+ pixel_type(),width,height,depth,dim,data,
+ warp.width,warp.height,warp.depth,warp.dim,warp.data);
+ CImg<T> res(width,height,depth,dim);
+ switch (warp.dim) {
+ case 1 : // 1D warping.
+ if (relative) { // Relative warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atX(cimg::mod(x-(float)warp(x,y,z,0),(float)width),y,z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atX(x-(float)warp(x,y,z,0),y,z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atX(x-(float)warp(x,y,z,0),y,z,v,0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),y,z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atX(x-(int)warp(x,y,z,0),y,z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atX(x-(int)warp(x,y,z,0),y,z,v,0);
+ }
+ }
+ } else { // Absolute warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atX(cimg::mod((float)warp(x,y,z,0),(float)width),y,z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atX((float)warp(x,y,z,0),y,z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atX((float)warp(x,y,z,0),y,z,v,0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),y,z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atX((int)warp(x,y,z,0),y,z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atX((int)warp(x,y,z,0),y,z,v,0);
+ }
+ }
+ }
+ break;
+
+ case 2 : // 2D warping
+ if (relative) { // Relative warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXY(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
+ cimg::mod(y-(float)warp(x,y,z,1),(float)height),z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v,0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
+ cimg::mod(y-(int)warp(x,y,z,1),(int)height),z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v,0);
+ }
+ }
+ } else { // Absolute warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXY(cimg::mod((float)warp(x,y,z,0),(float)width),
+ cimg::mod((float)warp(x,y,z,1),(float)height),z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v,0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
+ cimg::mod((int)warp(x,y,z,1),(int)depth),z,v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v,0);
+ }
+ }
+ }
+ break;
+
+ case 3 : // 3D warping
+ if (relative) { // Relative warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
+ cimg::mod(y-(float)warp(x,y,z,1),(float)height),
+ cimg::mod(z-(float)warp(x,y,z,2),(float)depth),v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v,0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
+ cimg::mod(y-(int)warp(x,y,z,1),(int)height),
+ cimg::mod(z-(int)warp(x,y,z,2),(int)depth),v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v,0);
+ }
+ }
+ } else { // Absolute warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod((float)warp(x,y,z,0),(float)width),
+ cimg::mod((float)warp(x,y,z,1),(float)height),
+ cimg::mod((float)warp(x,y,z,2),(float)depth),v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v,0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
+ cimg::mod((int)warp(x,y,z,1),(int)height),
+ cimg::mod((int)warp(x,y,z,2),(int)depth),v);
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v);
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v,0);
+ }
+ }
+ }
+ break;
+
+ default : // 4D warping
+ if (relative) { // Relative warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
+ cimg::mod(y-(float)warp(x,y,z,1),(float)height),
+ cimg::mod(z-(float)warp(x,y,z,2),(float)depth),
+ cimg::mod(z-(float)warp(x,y,z,3),(float)dim));
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3));
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3),0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
+ cimg::mod(y-(int)warp(x,y,z,1),(int)height),
+ cimg::mod(z-(int)warp(x,y,z,2),(int)depth),
+ cimg::mod(v-(int)warp(x,y,z,3),(int)dim));
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atXYZV(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3));
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3),0);
+ }
+ }
+ } else { // Absolute warp coordinates
+ if (interpolation) switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod((float)warp(x,y,z,0),(float)width),
+ cimg::mod((float)warp(x,y,z,1),(float)height),
+ cimg::mod((float)warp(x,y,z,2),(float)depth),
+ cimg::mod((float)warp(x,y,z,3),(float)dim));
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)_linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3));
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (T)linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3),0);
+ }
+ } else switch (border_conditions) {
+ case 2 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
+ cimg::mod((int)warp(x,y,z,1),(int)height),
+ cimg::mod((int)warp(x,y,z,2),(int)depth),
+ cimg::mod((int)warp(x,y,z,3),(int)dim));
+ } break;
+ case 1 : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = _atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3));
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v)
+ res(x,y,z,v) = atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3),0);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ // Permute axes order (internal).
+ template<typename t>
+ CImg<t> _get_permute_axes(const char *permut, const t&) const {
+ if (is_empty() || !permut) return CImg<t>(*this,false);
+ CImg<t> res;
+ const T* ptrs = data;
+ if (!cimg::strncasecmp(permut,"xyzv",4)) return (+*this);
+ if (!cimg::strncasecmp(permut,"xyvz",4)) {
+ res.assign(width,height,dim,depth);
+ cimg_forXYZV(*this,x,y,z,v) res(x,y,v,z) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"xzyv",4)) {
+ res.assign(width,depth,height,dim);
+ cimg_forXYZV(*this,x,y,z,v) res(x,z,y,v) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"xzvy",4)) {
+ res.assign(width,depth,dim,height);
+ cimg_forXYZV(*this,x,y,z,v) res(x,z,v,y) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"xvyz",4)) {
+ res.assign(width,dim,height,depth);
+ cimg_forXYZV(*this,x,y,z,v) res(x,v,y,z) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"xvzy",4)) {
+ res.assign(width,dim,depth,height);
+ cimg_forXYZV(*this,x,y,z,v) res(x,v,z,y) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"yxzv",4)) {
+ res.assign(height,width,depth,dim);
+ cimg_forXYZV(*this,x,y,z,v) res(y,x,z,v) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"yxvz",4)) {
+ res.assign(height,width,dim,depth);
+ cimg_forXYZV(*this,x,y,z,v) res(y,x,v,z) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"yzxv",4)) {
+ res.assign(height,depth,width,dim);
+ cimg_forXYZV(*this,x,y,z,v) res(y,z,x,v) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"yzvx",4)) {
+ res.assign(height,depth,dim,width);
+ switch (width) {
+ case 1 : {
+ t *ptrR = res.ptr(0,0,0,0);
+ for (unsigned long siz = height*depth*dim; siz; --siz) {
+ *(ptrR++) = (t)*(ptrs++);
+ }
+ } break;
+ case 2 : {
+ t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1);
+ for (unsigned long siz = height*depth*dim; siz; --siz) {
+ *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++);
+ }
+ } break;
+ case 3 : { // Optimization for the classical conversion from interleaved RGB to planar RGB
+ t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2);
+ for (unsigned long siz = height*depth*dim; siz; --siz) {
+ *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++);
+ }
+ } break;
+ case 4 : { // Optimization for the classical conversion from interleaved RGBA to planar RGBA
+ t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2), *ptrA = res.ptr(0,0,0,3);
+ for (unsigned long siz = height*depth*dim; siz; --siz) {
+ *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++); *(ptrA++) = (t)*(ptrs++);
+ }
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v) res(y,z,v,x) = *(ptrs++);
+ return res;
+ }
+ }
+ }
+ if (!cimg::strncasecmp(permut,"yvxz",4)) {
+ res.assign(height,dim,width,depth);
+ cimg_forXYZV(*this,x,y,z,v) res(y,v,x,z) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"yvzx",4)) {
+ res.assign(height,dim,depth,width);
+ cimg_forXYZV(*this,x,y,z,v) res(y,v,z,x) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"zxyv",4)) {
+ res.assign(depth,width,height,dim);
+ cimg_forXYZV(*this,x,y,z,v) res(z,x,y,v) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"zxvy",4)) {
+ res.assign(depth,width,dim,height);
+ cimg_forXYZV(*this,x,y,z,v) res(z,x,v,y) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"zyxv",4)) {
+ res.assign(depth,height,width,dim);
+ cimg_forXYZV(*this,x,y,z,v) res(z,y,x,v) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"zyvx",4)) {
+ res.assign(depth,height,dim,width);
+ cimg_forXYZV(*this,x,y,z,v) res(z,y,v,x) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"zvxy",4)) {
+ res.assign(depth,dim,width,height);
+ cimg_forXYZV(*this,x,y,z,v) res(z,v,x,y) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"zvyx",4)) {
+ res.assign(depth,dim,height,width);
+ cimg_forXYZV(*this,x,y,z,v) res(z,v,y,x) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"vxyz",4)) {
+ res.assign(dim,width,height,depth);
+ switch (dim) {
+ case 1 : {
+ const T *ptrR = ptr(0,0,0,0);
+ t *ptrd = res.ptr();
+ for (unsigned long siz = width*height*depth; siz; --siz) {
+ *(ptrd++) = (t)*(ptrR++);
+ }
+ } break;
+ case 2 : {
+ const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1);
+ t *ptrd = res.ptr();
+ for (unsigned long siz = width*height*depth; siz; --siz) {
+ *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++);
+ }
+ } break;
+ case 3 : { // Optimization for the classical conversion from planar RGB to interleaved RGB
+ const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2);
+ t *ptrd = res.ptr();
+ for (unsigned long siz = width*height*depth; siz; --siz) {
+ *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++);
+ }
+ } break;
+ case 4 : { // Optimization for the classical conversion from planar RGBA to interleaved RGBA
+ const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2), *ptrA = ptr(0,0,0,3);
+ t *ptrd = res.ptr();
+ for (unsigned long siz = width*height*depth; siz; --siz) {
+ *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++); *(ptrd++) = (t)*(ptrA++);
+ }
+ } break;
+ default : {
+ cimg_forXYZV(*this,x,y,z,v) res(v,x,y,z) = (t)*(ptrs++);
+ }
+ }
+ }
+ if (!cimg::strncasecmp(permut,"vxzy",4)) {
+ res.assign(dim,width,depth,height);
+ cimg_forXYZV(*this,x,y,z,v) res(v,x,z,y) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"vyxz",4)) {
+ res.assign(dim,height,width,depth);
+ cimg_forXYZV(*this,x,y,z,v) res(v,y,x,z) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"vyzx",4)) {
+ res.assign(dim,height,depth,width);
+ cimg_forXYZV(*this,x,y,z,v) res(v,y,z,x) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"vzxy",4)) {
+ res.assign(dim,depth,width,height);
+ cimg_forXYZV(*this,x,y,z,v) res(v,z,x,y) = (t)*(ptrs++);
+ }
+ if (!cimg::strncasecmp(permut,"vzyx",4)) {
+ res.assign(dim,depth,height,width);
+ cimg_forXYZV(*this,x,y,z,v) res(v,z,y,x) = (t)*(ptrs++);
+ }
+ if (!res)
+ throw CImgArgumentException("CImg<%s>::permute_axes() : Invalid input permutation '%s'.",
+ pixel_type(),permut);
+ return res;
+ }
+
+ //! Permute axes order.
+ /**
+ This function permutes image axes.
+ \param permut = String describing the permutation (4 characters).
+ **/
+ CImg<T>& permute_axes(const char *order) {
+ return get_permute_axes(order).transfer_to(*this);
+ }
+
+ CImg<T> get_permute_axes(const char *order) const {
+ const T foo = (T)0;
+ return _get_permute_axes(order,foo);
+ }
+
+ //! Invert endianness.
+ CImg<T>& invert_endianness() {
+ cimg::invert_endianness(data,size());
+ return *this;
+ }
+
+ CImg<T> get_invert_endianness() const {
+ return (+*this).invert_endianness();
+ }
+
+ //! Mirror an image along the specified axis.
+ CImg<T>& mirror(const char axis) {
+ if (is_empty()) return *this;
+ T *pf, *pb, *buf = 0;
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ pf = data; pb = ptr(width-1);
+ const unsigned int width2 = width/2;
+ for (unsigned int yzv = 0; yzv<height*depth*dim; ++yzv) {
+ for (unsigned int x = 0; x<width2; ++x) { const T val = *pf; *(pf++) = *pb; *(pb--) = val; }
+ pf+=width - width2;
+ pb+=width + width2;
+ }
+ } break;
+ case 'y' : {
+ buf = new T[width];
+ pf = data; pb = ptr(0,height-1);
+ const unsigned int height2 = height/2;
+ for (unsigned int zv=0; zv<depth*dim; ++zv) {
+ for (unsigned int y=0; y<height2; ++y) {
+ cimg_std::memcpy(buf,pf,width*sizeof(T));
+ cimg_std::memcpy(pf,pb,width*sizeof(T));
+ cimg_std::memcpy(pb,buf,width*sizeof(T));
+ pf+=width;
+ pb-=width;
+ }
+ pf+=width*(height - height2);
+ pb+=width*(height + height2);
+ }
+ } break;
+ case 'z' : {
+ buf = new T[width*height];
+ pf = data; pb = ptr(0,0,depth-1);
+ const unsigned int depth2 = depth/2;
+ cimg_forV(*this,v) {
+ for (unsigned int z=0; z<depth2; ++z) {
+ cimg_std::memcpy(buf,pf,width*height*sizeof(T));
+ cimg_std::memcpy(pf,pb,width*height*sizeof(T));
+ cimg_std::memcpy(pb,buf,width*height*sizeof(T));
+ pf+=width*height;
+ pb-=width*height;
+ }
+ pf+=width*height*(depth - depth2);
+ pb+=width*height*(depth + depth2);
+ }
+ } break;
+ case 'v' : {
+ buf = new T[width*height*depth];
+ pf = data; pb = ptr(0,0,0,dim-1);
+ const unsigned int dim2 = dim/2;
+ for (unsigned int v=0; v<dim2; ++v) {
+ cimg_std::memcpy(buf,pf,width*height*depth*sizeof(T));
+ cimg_std::memcpy(pf,pb,width*height*depth*sizeof(T));
+ cimg_std::memcpy(pb,buf,width*height*depth*sizeof(T));
+ pf+=width*height*depth;
+ pb-=width*height*depth;
+ }
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::mirror() : unknow axis '%c', must be 'x','y','z' or 'v'.",
+ pixel_type(),axis);
+ }
+ if (buf) delete[] buf;
+ return *this;
+ }
+
+ CImg<T> get_mirror(const char axis) const {
+ return (+*this).mirror(axis);
+ }
+
+ //! Translate the image.
+ /**
+ \param deltax Amount of displacement along the X-axis.
+ \param deltay Amount of displacement along the Y-axis.
+ \param deltaz Amount of displacement along the Z-axis.
+ \param deltav Amount of displacement along the V-axis.
+ \param border_condition Border condition.
+
+ - \c border_condition can be :
+ - 0 : Zero border condition (Dirichlet).
+ - 1 : Nearest neighbors (Neumann).
+ - 2 : Repeat Pattern (Fourier style).
+ **/
+ CImg<T>& translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
+ const int border_condition=0) {
+ if (is_empty()) return *this;
+ if (deltax) // Translate along X-axis
+ switch (border_condition) {
+ case 0 :
+ if (cimg::abs(deltax)>=dimx()) return fill(0);
+ if (deltax>0) cimg_forYZV(*this,y,z,k) {
+ cimg_std::memmove(ptr(0,y,z,k),ptr(deltax,y,z,k),(width-deltax)*sizeof(T));
+ cimg_std::memset(ptr(width-deltax,y,z,k),0,deltax*sizeof(T));
+ } else cimg_forYZV(*this,y,z,k) {
+ cimg_std::memmove(ptr(-deltax,y,z,k),ptr(0,y,z,k),(width+deltax)*sizeof(T));
+ cimg_std::memset(ptr(0,y,z,k),0,-deltax*sizeof(T));
+ }
+ break;
+ case 1 :
+ if (deltax>0) {
+ const int ndeltax = (deltax>=dimx())?width-1:deltax;
+ if (!ndeltax) return *this;
+ cimg_forYZV(*this,y,z,k) {
+ cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
+ T *ptrd = ptr(width-1,y,z,k);
+ const T val = *ptrd;
+ for (int l = 0; l<ndeltax-1; ++l) *(--ptrd) = val;
+ }
+ } else {
+ const int ndeltax = (-deltax>=dimx())?width-1:-deltax;
+ if (!ndeltax) return *this;
+ cimg_forYZV(*this,y,z,k) {
+ cimg_std::memmove(ptr(ndeltax,y,z,k),ptr(0,y,z,k),(width-ndeltax)*sizeof(T));
+ T *ptrd = ptr(0,y,z,k);
+ const T val = *ptrd;
+ for (int l = 0; l<ndeltax-1; ++l) *(++ptrd) = val;
+ }
+ }
+ break;
+ case 2 : {
+ const int ml = cimg::mod(deltax,dimx()), ndeltax = (ml<=dimx()/2)?ml:(ml-dimx());
+ if (!ndeltax) return *this;
+ T* buf = new T[cimg::abs(ndeltax)];
+ if (ndeltax>0) cimg_forYZV(*this,y,z,k) {
+ cimg_std::memcpy(buf,ptr(0,y,z,k),ndeltax*sizeof(T));
+ cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
+ cimg_std::memcpy(ptr(width-ndeltax,y,z,k),buf,ndeltax*sizeof(T));
+ } else cimg_forYZV(*this,y,z,k) {
+ cimg_std::memcpy(buf,ptr(width+ndeltax,y,z,k),-ndeltax*sizeof(T));
+ cimg_std::memmove(ptr(-ndeltax,y,z,k),ptr(0,y,z,k),(width+ndeltax)*sizeof(T));
+ cimg_std::memcpy(ptr(0,y,z,k),buf,-ndeltax*sizeof(T));
+ }
+ delete[] buf;
+ } break;
+ }
+
+ if (deltay) // Translate along Y-axis
+ switch (border_condition) {
+ case 0 :
+ if (cimg::abs(deltay)>=dimy()) return fill(0);
+ if (deltay>0) cimg_forZV(*this,z,k) {
+ cimg_std::memmove(ptr(0,0,z,k),ptr(0,deltay,z,k),width*(height-deltay)*sizeof(T));
+ cimg_std::memset(ptr(0,height-deltay,z,k),0,width*deltay*sizeof(T));
+ } else cimg_forZV(*this,z,k) {
+ cimg_std::memmove(ptr(0,-deltay,z,k),ptr(0,0,z,k),width*(height+deltay)*sizeof(T));
+ cimg_std::memset(ptr(0,0,z,k),0,-deltay*width*sizeof(T));
+ }
+ break;
+ case 1 :
+ if (deltay>0) {
+ const int ndeltay = (deltay>=dimy())?height-1:deltay;
+ if (!ndeltay) return *this;
+ cimg_forZV(*this,z,k) {
+ cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
+ T *ptrd = ptr(0,height-ndeltay,z,k), *ptrs = ptr(0,height-1,z,k);
+ for (int l = 0; l<ndeltay-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
+ }
+ } else {
+ const int ndeltay = (-deltay>=dimy())?height-1:-deltay;
+ if (!ndeltay) return *this;
+ cimg_forZV(*this,z,k) {
+ cimg_std::memmove(ptr(0,ndeltay,z,k),ptr(0,0,z,k),width*(height-ndeltay)*sizeof(T));
+ T *ptrd = ptr(0,1,z,k), *ptrs = ptr(0,0,z,k);
+ for (int l = 0; l<ndeltay-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
+ }
+ }
+ break;
+ case 2 : {
+ const int ml = cimg::mod(deltay,dimy()), ndeltay = (ml<=dimy()/2)?ml:(ml-dimy());
+ if (!ndeltay) return *this;
+ T* buf = new T[width*cimg::abs(ndeltay)];
+ if (ndeltay>0) cimg_forZV(*this,z,k) {
+ cimg_std::memcpy(buf,ptr(0,0,z,k),width*ndeltay*sizeof(T));
+ cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
+ cimg_std::memcpy(ptr(0,height-ndeltay,z,k),buf,width*ndeltay*sizeof(T));
+ } else cimg_forZV(*this,z,k) {
+ cimg_std::memcpy(buf,ptr(0,height+ndeltay,z,k),-ndeltay*width*sizeof(T));
+ cimg_std::memmove(ptr(0,-ndeltay,z,k),ptr(0,0,z,k),width*(height+ndeltay)*sizeof(T));
+ cimg_std::memcpy(ptr(0,0,z,k),buf,-ndeltay*width*sizeof(T));
+ }
+ delete[] buf;
+ } break;
+ }
+
+ if (deltaz) // Translate along Z-axis
+ switch (border_condition) {
+ case 0 :
+ if (cimg::abs(deltaz)>=dimz()) return fill(0);
+ if (deltaz>0) cimg_forV(*this,k) {
+ cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,deltaz,k),width*height*(depth-deltaz)*sizeof(T));
+ cimg_std::memset(ptr(0,0,depth-deltaz,k),0,width*height*deltaz*sizeof(T));
+ } else cimg_forV(*this,k) {
+ cimg_std::memmove(ptr(0,0,-deltaz,k),ptr(0,0,0,k),width*height*(depth+deltaz)*sizeof(T));
+ cimg_std::memset(ptr(0,0,0,k),0,-deltaz*width*height*sizeof(T));
+ }
+ break;
+ case 1 :
+ if (deltaz>0) {
+ const int ndeltaz = (deltaz>=dimz())?depth-1:deltaz;
+ if (!ndeltaz) return *this;
+ cimg_forV(*this,k) {
+ cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
+ T *ptrd = ptr(0,0,depth-ndeltaz,k), *ptrs = ptr(0,0,depth-1,k);
+ for (int l = 0; l<ndeltaz-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
+ }
+ } else {
+ const int ndeltaz = (-deltaz>=dimz())?depth-1:-deltaz;
+ if (!ndeltaz) return *this;
+ cimg_forV(*this,k) {
+ cimg_std::memmove(ptr(0,0,ndeltaz,k),ptr(0,0,0,k),width*height*(depth-ndeltaz)*sizeof(T));
+ T *ptrd = ptr(0,0,1,k), *ptrs = ptr(0,0,0,k);
+ for (int l = 0; l<ndeltaz-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
+ }
+ }
+ break;
+ case 2 : {
+ const int ml = cimg::mod(deltaz,dimz()), ndeltaz = (ml<=dimz()/2)?ml:(ml-dimz());
+ if (!ndeltaz) return *this;
+ T* buf = new T[width*height*cimg::abs(ndeltaz)];
+ if (ndeltaz>0) cimg_forV(*this,k) {
+ cimg_std::memcpy(buf,ptr(0,0,0,k),width*height*ndeltaz*sizeof(T));
+ cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
+ cimg_std::memcpy(ptr(0,0,depth-ndeltaz,k),buf,width*height*ndeltaz*sizeof(T));
+ } else cimg_forV(*this,k) {
+ cimg_std::memcpy(buf,ptr(0,0,depth+ndeltaz,k),-ndeltaz*width*height*sizeof(T));
+ cimg_std::memmove(ptr(0,0,-ndeltaz,k),ptr(0,0,0,k),width*height*(depth+ndeltaz)*sizeof(T));
+ cimg_std::memcpy(ptr(0,0,0,k),buf,-ndeltaz*width*height*sizeof(T));
+ }
+ delete[] buf;
+ } break;
+ }
+
+ if (deltav) // Translate along V-axis
+ switch (border_condition) {
+ case 0 :
+ if (cimg::abs(deltav)>=dimv()) return fill(0);
+ if (deltav>0) {
+ cimg_std::memmove(data,ptr(0,0,0,deltav),width*height*depth*(dim-deltav)*sizeof(T));
+ cimg_std::memset(ptr(0,0,0,dim-deltav),0,width*height*depth*deltav*sizeof(T));
+ } else cimg_forV(*this,k) {
+ cimg_std::memmove(ptr(0,0,0,-deltav),data,width*height*depth*(dim+deltav)*sizeof(T));
+ cimg_std::memset(data,0,-deltav*width*height*depth*sizeof(T));
+ }
+ break;
+ case 1 :
+ if (deltav>0) {
+ const int ndeltav = (deltav>=dimv())?dim-1:deltav;
+ if (!ndeltav) return *this;
+ cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
+ T *ptrd = ptr(0,0,0,dim-ndeltav), *ptrs = ptr(0,0,0,dim-1);
+ for (int l = 0; l<ndeltav-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
+ } else {
+ const int ndeltav = (-deltav>=dimv())?dim-1:-deltav;
+ if (!ndeltav) return *this;
+ cimg_std::memmove(ptr(0,0,0,ndeltav),data,width*height*depth*(dim-ndeltav)*sizeof(T));
+ T *ptrd = ptr(0,0,0,1);
+ for (int l = 0; l<ndeltav-1; ++l) { cimg_std::memcpy(ptrd,data,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
+ }
+ break;
+ case 2 : {
+ const int ml = cimg::mod(deltav,dimv()), ndeltav = (ml<=dimv()/2)?ml:(ml-dimv());
+ if (!ndeltav) return *this;
+ T* buf = new T[width*height*depth*cimg::abs(ndeltav)];
+ if (ndeltav>0) {
+ cimg_std::memcpy(buf,data,width*height*depth*ndeltav*sizeof(T));
+ cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
+ cimg_std::memcpy(ptr(0,0,0,dim-ndeltav),buf,width*height*depth*ndeltav*sizeof(T));
+ } else {
+ cimg_std::memcpy(buf,ptr(0,0,0,dim+ndeltav),-ndeltav*width*height*depth*sizeof(T));
+ cimg_std::memmove(ptr(0,0,0,-ndeltav),data,width*height*depth*(dim+ndeltav)*sizeof(T));
+ cimg_std::memcpy(data,buf,-ndeltav*width*height*depth*sizeof(T));
+ }
+ delete[] buf;
+ } break;
+ }
+ return *this;
+ }
+
+ CImg<T> get_translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
+ const int border_condition=0) const {
+ return (+*this).translate(deltax,deltay,deltaz,deltav,border_condition);
+ }
+
+ //! Get a square region of the image.
+ /**
+ \param x0 = X-coordinate of the upper-left crop rectangle corner.
+ \param y0 = Y-coordinate of the upper-left crop rectangle corner.
+ \param z0 = Z-coordinate of the upper-left crop rectangle corner.
+ \param v0 = V-coordinate of the upper-left crop rectangle corner.
+ \param x1 = X-coordinate of the lower-right crop rectangle corner.
+ \param y1 = Y-coordinate of the lower-right crop rectangle corner.
+ \param z1 = Z-coordinate of the lower-right crop rectangle corner.
+ \param v1 = V-coordinate of the lower-right crop rectangle corner.
+ \param border_condition = Dirichlet (false) or Neumann border conditions.
+ **/
+ CImg<T>& crop(const int x0, const int y0, const int z0, const int v0,
+ const int x1, const int y1, const int z1, const int v1,
+ const bool border_condition=false) {
+ return get_crop(x0,y0,z0,v0,x1,y1,z1,v1,border_condition).transfer_to(*this);
+ }
+
+ CImg<T> get_crop(const int x0, const int y0, const int z0, const int v0,
+ const int x1, const int y1, const int z1, const int v1,
+ const bool border_condition=false) const {
+ if (is_empty()) return *this;
+ const int
+ nx0 = x0<x1?x0:x1, nx1 = x0^x1^nx0,
+ ny0 = y0<y1?y0:y1, ny1 = y0^y1^ny0,
+ nz0 = z0<z1?z0:z1, nz1 = z0^z1^nz0,
+ nv0 = v0<v1?v0:v1, nv1 = v0^v1^nv0;
+ CImg<T> dest(1U+nx1-nx0,1U+ny1-ny0,1U+nz1-nz0,1U+nv1-nv0);
+ if (nx0<0 || nx1>=dimx() || ny0<0 || ny1>=dimy() || nz0<0 || nz1>=dimz() || nv0<0 || nv1>=dimv()) {
+ if (border_condition) cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = _atXYZV(nx0+x,ny0+y,nz0+z,nv0+v);
+ else dest.fill(0).draw_image(-nx0,-ny0,-nz0,-nv0,*this);
+ } else dest.draw_image(-nx0,-ny0,-nz0,-nv0,*this);
+ return dest;
+ }
+
+ //! Get a rectangular part of the instance image.
+ /**
+ \param x0 = X-coordinate of the upper-left crop rectangle corner.
+ \param y0 = Y-coordinate of the upper-left crop rectangle corner.
+ \param z0 = Z-coordinate of the upper-left crop rectangle corner.
+ \param x1 = X-coordinate of the lower-right crop rectangle corner.
+ \param y1 = Y-coordinate of the lower-right crop rectangle corner.
+ \param z1 = Z-coordinate of the lower-right crop rectangle corner.
+ \param border_condition = determine the type of border condition if
+ some of the desired region is outside the image.
+ **/
+ CImg<T>& crop(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const bool border_condition=false) {
+ return crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
+ }
+
+ CImg<T> get_crop(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const bool border_condition=false) const {
+ return get_crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
+ }
+
+ //! Get a rectangular part of the instance image.
+ /**
+ \param x0 = X-coordinate of the upper-left crop rectangle corner.
+ \param y0 = Y-coordinate of the upper-left crop rectangle corner.
+ \param x1 = X-coordinate of the lower-right crop rectangle corner.
+ \param y1 = Y-coordinate of the lower-right crop rectangle corner.
+ \param border_condition = determine the type of border condition if
+ some of the desired region is outside the image.
+ **/
+ CImg<T>& crop(const int x0, const int y0,
+ const int x1, const int y1,
+ const bool border_condition=false) {
+ return crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
+ }
+
+ CImg<T> get_crop(const int x0, const int y0,
+ const int x1, const int y1,
+ const bool border_condition=false) const {
+ return get_crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
+ }
+
+ //! Get a rectangular part of the instance image.
+ /**
+ \param x0 = X-coordinate of the upper-left crop rectangle corner.
+ \param x1 = X-coordinate of the lower-right crop rectangle corner.
+ \param border_condition = determine the type of border condition if
+ some of the desired region is outside the image.
+ **/
+ CImg<T>& crop(const int x0, const int x1, const bool border_condition=false) {
+ return crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
+ }
+
+ CImg<T> get_crop(const int x0, const int x1, const bool border_condition=false) const {
+ return get_crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
+ }
+
+ //! Autocrop an image, regarding of the specified backround value.
+ CImg<T>& autocrop(const T value, const char *const axes="vzyx") {
+ if (is_empty()) return *this;
+ const int lmax = cimg::strlen(axes);
+ for (int l = 0; l<lmax; ++l) autocrop(value,axes[l]);
+ return *this;
+ }
+
+ CImg<T> get_autocrop(const T value, const char *const axes="vzyx") const {
+ return (+*this).autocrop(value,axes);
+ }
+
+ //! Autocrop an image, regarding of the specified backround color.
+ CImg<T>& autocrop(const T *const color, const char *const axes="zyx") {
+ if (is_empty()) return *this;
+ const int lmax = cimg::strlen(axes);
+ for (int l = 0; l<lmax; ++l) autocrop(color,axes[l]);
+ return *this;
+ }
+
+ CImg<T> get_autocrop(const T *const color, const char *const axes="zyx") const {
+ return (+*this).autocrop(color,axes);
+ }
+
+ //! Autocrop an image, regarding of the specified backround color.
+ template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char *const axes="zyx") {
+ return get_autocrop(color,axes).transfer_to(*this);
+ }
+
+ template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char *const axes="zyx") const {
+ return get_autocrop(color.data,axes);
+ }
+
+ //! Autocrop an image along specified axis, regarding of the specified backround value.
+ CImg<T>& autocrop(const T value, const char axis) {
+ return get_autocrop(value,axis).transfer_to(*this);
+ }
+
+ CImg<T> get_autocrop(const T value, const char axis) const {
+ if (is_empty()) return *this;
+ CImg<T> res;
+ const CImg<intT> coords = _get_autocrop(value,axis);
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ const int x0 = coords[0], x1 = coords[1];
+ if (x0>=0 && x1>=0) res = get_crop(x0,x1);
+ } break;
+ case 'y' : {
+ const int y0 = coords[0], y1 = coords[1];
+ if (y0>=0 && y1>=0) res = get_crop(0,y0,width-1,y1);
+ } break;
+ case 'z' : {
+ const int z0 = coords[0], z1 = coords[1];
+ if (z0>=0 && z1>=0) res = get_crop(0,0,z0,width-1,height-1,z1);
+ } break;
+ case 'v' : {
+ const int v0 = coords[0], v1 = coords[1];
+ if (v0>=0 && v1>=0) res = get_crop(0,0,0,v0,width-1,height-1,depth-1,v1);
+ } break;
+ }
+ return res;
+ }
+
+ //! Autocrop an image along specified axis, regarding of the specified backround color.
+ CImg<T>& autocrop(const T *const color, const char axis) {
+ return get_autocrop(color,axis).transfer_to(*this);
+ }
+
+ CImg<T> get_autocrop(const T *const color, const char axis) const {
+ if (is_empty()) return *this;
+ CImg<T> res;
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ int x0 = width, x1 = -1;
+ cimg_forV(*this,k) {
+ const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
+ const int nx0 = coords[0], nx1 = coords[1];
+ if (nx0>=0 && nx1>=0) { x0 = cimg::min(x0,nx0); x1 = cimg::max(x1,nx1); }
+ }
+ if (x0<=x1) res = get_crop(x0,x1);
+ } break;
+ case 'y' : {
+ int y0 = height, y1 = -1;
+ cimg_forV(*this,k) {
+ const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
+ const int ny0 = coords[0], ny1 = coords[1];
+ if (ny0>=0 && ny1>=0) { y0 = cimg::min(y0,ny0); y1 = cimg::max(y1,ny1); }
+ }
+ if (y0<=y1) res = get_crop(0,y0,width-1,y1);
+ } break;
+ case 'z' : {
+ int z0 = depth, z1 = -1;
+ cimg_forV(*this,k) {
+ const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
+ const int nz0 = coords[0], nz1 = coords[1];
+ if (nz0>=0 && nz1>=0) { z0 = cimg::min(z0,nz0); z1 = cimg::max(z1,nz1); }
+ }
+ if (z0<=z1) res = get_crop(0,0,z0,width-1,height-1,z1);
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::autocrop() : Invalid axis '%c', must be 'x','y' or 'z'.",
+ pixel_type(),axis);
+ }
+ return res;
+ }
+
+ //! Autocrop an image along specified axis, regarding of the specified backround color.
+ template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char axis) {
+ return get_autocrop(color,axis).transfer_to(*this);
+ }
+
+ template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char axis) const {
+ return get_autocrop(color.data,axis);
+ }
+
+ CImg<intT> _get_autocrop(const T value, const char axis) const {
+ CImg<intT> res;
+ int x0 = -1, y0 = -1, z0 = -1, v0 = -1, x1 = -1, y1 = -1, z1 = -1, v1 = -1;
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ cimg_forX(*this,x) cimg_forYZV(*this,y,z,v)
+ if ((*this)(x,y,z,v)!=value) { x0 = x; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
+ if (x0>=0) {
+ for (int x = dimx()-1; x>=0; --x) cimg_forYZV(*this,y,z,v)
+ if ((*this)(x,y,z,v)!=value) { x1 = x; x = 0; y = dimy(); z = dimz(); v = dimv(); }
+ }
+ res = CImg<intT>::vector(x0,x1);
+ } break;
+ case 'y' : {
+ cimg_forY(*this,y) cimg_forXZV(*this,x,z,v)
+ if ((*this)(x,y,z,v)!=value) { y0 = y; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
+ if (y0>=0) {
+ for (int y = dimy()-1; y>=0; --y) cimg_forXZV(*this,x,z,v)
+ if ((*this)(x,y,z,v)!=value) { y1 = y; x = dimx(); y = 0; z = dimz(); v = dimv(); }
+ }
+ res = CImg<intT>::vector(y0,y1);
+ } break;
+ case 'z' : {
+ cimg_forZ(*this,z) cimg_forXYV(*this,x,y,v)
+ if ((*this)(x,y,z,v)!=value) { z0 = z; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
+ if (z0>=0) {
+ for (int z = dimz()-1; z>=0; --z) cimg_forXYV(*this,x,y,v)
+ if ((*this)(x,y,z,v)!=value) { z1 = z; x = dimx(); y = dimy(); z = 0; v = dimv(); }
+ }
+ res = CImg<intT>::vector(z0,z1);
+ } break;
+ case 'v' : {
+ cimg_forV(*this,v) cimg_forXYZ(*this,x,y,z)
+ if ((*this)(x,y,z,v)!=value) { v0 = v; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
+ if (v0>=0) {
+ for (int v = dimv()-1; v>=0; --v) cimg_forXYZ(*this,x,y,z)
+ if ((*this)(x,y,z,v)!=value) { v1 = v; x = dimx(); y = dimy(); z = dimz(); v = 0; }
+ }
+ res = CImg<intT>::vector(v0,v1);
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::autocrop() : unknow axis '%c', must be 'x','y','z' or 'v'",
+ pixel_type(),axis);
+ }
+ return res;
+ }
+
+ //! Get a set of columns.
+ CImg<T>& columns(const unsigned int x0, const unsigned int x1) {
+ return get_columns(x0,x1).transfer_to(*this);
+ }
+
+ CImg<T> get_columns(const unsigned int x0, const unsigned int x1) const {
+ return get_crop((int)x0,0,0,0,(int)x1,dimy()-1,dimz()-1,dimv()-1);
+ }
+
+ //! Get one column.
+ CImg<T>& column(const unsigned int x0) {
+ return columns(x0,x0);
+ }
+
+ CImg<T> get_column(const unsigned int x0) const {
+ return get_columns(x0,x0);
+ }
+
+ //! Get a set of lines.
+ CImg<T>& lines(const unsigned int y0, const unsigned int y1) {
+ return get_lines(y0,y1).transfer_to(*this);
+ }
+
+ CImg<T> get_lines(const unsigned int y0, const unsigned int y1) const {
+ return get_crop(0,(int)y0,0,0,dimx()-1,(int)y1,dimz()-1,dimv()-1);
+ }
+
+ //! Get a line.
+ CImg<T>& line(const unsigned int y0) {
+ return lines(y0,y0);
+ }
+
+ CImg<T> get_line(const unsigned int y0) const {
+ return get_lines(y0,y0);
+ }
+
+ //! Get a set of slices.
+ CImg<T>& slices(const unsigned int z0, const unsigned int z1) {
+ return get_slices(z0,z1).transfer_to(*this);
+ }
+
+ CImg<T> get_slices(const unsigned int z0, const unsigned int z1) const {
+ return get_crop(0,0,(int)z0,0,dimx()-1,dimy()-1,(int)z1,dimv()-1);
+ }
+
+ //! Get a slice.
+ CImg<T>& slice(const unsigned int z0) {
+ return slices(z0,z0);
+ }
+
+ CImg<T> get_slice(const unsigned int z0) const {
+ return get_slices(z0,z0);
+ }
+
+ //! Get a set of channels.
+ CImg<T>& channels(const unsigned int v0, const unsigned int v1) {
+ return get_channels(v0,v1).transfer_to(*this);
+ }
+
+ CImg<T> get_channels(const unsigned int v0, const unsigned int v1) const {
+ return get_crop(0,0,0,(int)v0,dimx()-1,dimy()-1,dimz()-1,(int)v1);
+ }
+
+ //! Get a channel.
+ CImg<T>& channel(const unsigned int v0) {
+ return channels(v0,v0);
+ }
+
+ CImg<T> get_channel(const unsigned int v0) const {
+ return get_channels(v0,v0);
+ }
+
+ //! Get a shared-memory image referencing a set of points of the instance image.
+ CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
+ const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) {
+ const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
+ return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
+ }
+
+ const CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
+ const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) const {
+ const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
+ return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
+ }
+
+ //! Return a shared-memory image referencing a set of lines of the instance image.
+ CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
+ const unsigned int z0=0, const unsigned int v0=0) {
+ const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
+ return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
+ }
+
+ const CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
+ const unsigned int z0=0, const unsigned int v0=0) const {
+ const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
+ return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
+ }
+
+ //! Return a shared-memory image referencing one particular line (y0,z0,v0) of the instance image.
+ CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) {
+ return get_shared_lines(y0,y0,z0,v0);
+ }
+
+ const CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) const {
+ return get_shared_lines(y0,y0,z0,v0);
+ }
+
+ //! Return a shared memory image referencing a set of planes (z0->z1,v0) of the instance image.
+ CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) {
+ const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
+ return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
+ }
+
+ const CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) const {
+ const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
+ return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
+ }
+
+ //! Return a shared-memory image referencing one plane (z0,v0) of the instance image.
+ CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) {
+ return get_shared_planes(z0,z0,v0);
+ }
+
+ const CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) const {
+ return get_shared_planes(z0,z0,v0);
+ }
+
+ //! Return a shared-memory image referencing a set of channels (v0->v1) of the instance image.
+ CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) {
+ const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
+ return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
+ }
+
+ const CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) const {
+ const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
+ "a (%u,%u,%u,%u) image.",
+ pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
+ return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
+ }
+
+ //! Return a shared-memory image referencing one channel v0 of the instance image.
+ CImg<T> get_shared_channel(const unsigned int v0) {
+ return get_shared_channels(v0,v0);
+ }
+
+ const CImg<T> get_shared_channel(const unsigned int v0) const {
+ return get_shared_channels(v0,v0);
+ }
+
+ //! Return a shared version of the instance image.
+ CImg<T> get_shared() {
+ return CImg<T>(data,width,height,depth,dim,true);
+ }
+
+ const CImg<T> get_shared() const {
+ return CImg<T>(data,width,height,depth,dim,true);
+ }
+
+ //! Return a 2D representation of a 3D image, with three slices.
+ CImg<T>& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
+ const int dx=-100, const int dy=-100, const int dz=-100) {
+ return get_projections2d(x0,y0,z0,dx,dy,dz).transfer_to(*this);
+ }
+
+ CImg<T> get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
+ const int dx=-100, const int dy=-100, const int dz=-100) const {
+ if (is_empty()) return *this;
+ const unsigned int
+ nx0 = (x0>=width)?width-1:x0,
+ ny0 = (y0>=height)?height-1:y0,
+ nz0 = (z0>=depth)?depth-1:z0;
+ CImg<T>
+ imgxy(width,height,1,dim),
+ imgzy(depth,height,1,dim),
+ imgxz(width,depth,1,dim);
+ { cimg_forXYV(*this,x,y,k) imgxy(x,y,k) = (*this)(x,y,nz0,k); }
+ { cimg_forYZV(*this,y,z,k) imgzy(z,y,k) = (*this)(nx0,y,z,k); }
+ { cimg_forXZV(*this,x,z,k) imgxz(x,z,k) = (*this)(x,ny0,z,k); }
+ imgxy.resize(dx,dy,1,dim,1);
+ imgzy.resize(dz,dy,1,dim,1);
+ imgxz.resize(dx,dz,1,dim,1);
+ return CImg<T>(imgxy.width+imgzy.width,imgxy.height+imgxz.height,1,dim,0).
+ draw_image(imgxy).draw_image(imgxy.width,imgzy).draw_image(0,imgxy.height,imgxz);
+ }
+
+ //! Compute the image histogram.
+ /**
+ The histogram H of an image I is a 1D-function where H(x) is the number of
+ occurences of the value x in I.
+ \param nblevels = Number of different levels of the computed histogram.
+ For classical images, this value is 256. You should specify more levels
+ if you are working with CImg<float> or images with high range of pixel values.
+ \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
+ won't be counted.
+ \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
+ won't be counted.
+ \note If val_min==val_max==0 (default values), the function first estimates the minimum and maximum
+ pixel values of the current image, then uses these values for the histogram computation.
+ \result The histogram is returned as a 1D CImg<float> image H, having a size of (nblevels,1,1,1) such that
+ H(0) and H(nblevels-1) are respectively equal to the number of occurences of the values val_min and val_max in I.
+ \note Histogram computation always returns a 1D function. Histogram of multi-valued (such as color) images
+ are not multi-dimensional.
+ **/
+ CImg<T>& histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) {
+ return get_histogram(nblevels,val_min,val_max).transfer_to(*this);
+ }
+
+ CImg<floatT> get_histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const {
+ if (is_empty()) return CImg<floatT>();
+ if (!nblevels)
+ throw CImgArgumentException("CImg<%s>::get_histogram() : Can't compute an histogram with 0 levels",
+ pixel_type());
+ T vmin = val_min, vmax = val_max;
+ CImg<floatT> res(nblevels,1,1,1,0);
+ if (vmin>=vmax && vmin==0) vmin = minmax(vmax);
+ if (vmin<vmax) cimg_for(*this,ptr,T) {
+ const int pos = (int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
+ if (pos>=0 && pos<(int)nblevels) ++res[pos];
+ } else res[0]+=size();
+ return res;
+ }
+
+ //! Compute the histogram-equalized version of the instance image.
+ /**
+ The histogram equalization is a classical image processing algorithm that enhances the image contrast
+ by expanding its histogram.
+ \param nblevels = Number of different levels of the computed histogram.
+ For classical images, this value is 256. You should specify more levels
+ if you are working with CImg<float> or images with high range of pixel values.
+ \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
+ won't be changed.
+ \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
+ won't be changed.
+ \note If val_min==val_max==0 (default values), the function acts on all pixel values of the image.
+ \return A new image with same size is returned, where pixels have been equalized.
+ **/
+ CImg<T>& equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) {
+ if (is_empty()) return *this;
+ T vmin = val_min, vmax = val_max;
+ if (vmin==vmax && vmin==0) vmin = minmax(vmax);
+ if (vmin<vmax) {
+ CImg<floatT> hist = get_histogram(nblevels,vmin,vmax);
+ float cumul = 0;
+ cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos]=cumul; }
+ cimg_for(*this,ptr,T) {
+ const int pos = (unsigned int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
+ if (pos>=0 && pos<(int)nblevels) *ptr = (T)(vmin + (vmax-vmin)*hist[pos]/size());
+ }
+ }
+ return *this;
+ }
+
+ CImg<T> get_equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const {
+ return (+*this).equalize(nblevels,val_min,val_max);
+ }
+
+ //! Get a label map of disconnected regions with same intensities.
+ CImg<T>& label_regions() {
+ return get_label_regions().transfer_to(*this);
+ }
+
+ CImg<uintT> get_label_regions() const {
+#define _cimg_get_label_test(p,q) { \
+ flag = true; \
+ const T *ptr1 = ptr(x,y) + siz, *ptr2 = ptr(p,q) + siz; \
+ for (unsigned int i = dim; flag && i; --i) { ptr1-=wh; ptr2-=wh; flag = (*ptr1==*ptr2); } \
+}
+ if (depth>1)
+ throw CImgInstanceException("CImg<%s>::label_regions() : Instance image must be a 2D image");
+ CImg<uintT> res(width,height,depth,1,0);
+ unsigned int label = 1;
+ const unsigned int wh = width*height, siz = width*height*dim;
+ const int W1 = dimx()-1, H1 = dimy()-1;
+ bool flag;
+ cimg_forXY(*this,x,y) {
+ bool done = false;
+ if (y) {
+ _cimg_get_label_test(x,y-1);
+ if (flag) {
+ const unsigned int lab = (res(x,y) = res(x,y-1));
+ done = true;
+ if (x && res(x-1,y)!=lab) {
+ _cimg_get_label_test(x-1,y);
+ if (flag) {
+ const unsigned int lold = res(x-1,y), *const cptr = res.ptr(x,y);
+ for (unsigned int *ptr = res.ptr(); ptr<cptr; ++ptr) if (*ptr==lold) *ptr = lab;
+ }
+ }
+ }
+ }
+ if (x && !done) { _cimg_get_label_test(x-1,y); if (flag) { res(x,y) = res(x-1,y); done = true; }}
+ if (!done) res(x,y) = label++;
+ }
+ { for (int y = H1; y>=0; --y) for (int x=W1; x>=0; --x) {
+ bool done = false;
+ if (y<H1) {
+ _cimg_get_label_test(x,y+1);
+ if (flag) {
+ const unsigned int lab = (res(x,y) = res(x,y+1));
+ done = true;
+ if (x<W1 && res(x+1,y)!=lab) {
+ _cimg_get_label_test(x+1,y);
+ if (flag) {
+ const unsigned int lold = res(x+1,y), *const cptr = res.ptr(x,y);
+ for (unsigned int *ptr = res.ptr()+res.size()-1; ptr>cptr; --ptr) if (*ptr==lold) *ptr = lab;
+ }
+ }
+ }
+ }
+ if (x<W1 && !done) { _cimg_get_label_test(x+1,y); if (flag) res(x,y) = res(x+1,y); done = true; }
+ }}
+ const unsigned int lab0 = res.max()+1;
+ label = lab0;
+ cimg_foroff(res,off) { // Relabel regions
+ const unsigned int lab = res[off];
+ if (lab<lab0) { cimg_for(res,ptr,unsigned int) if (*ptr==lab) *ptr = label; ++label; }
+ }
+ return (res-=lab0);
+ }
+
+ //! Compute the scalar image of vector norms.
+ /**
+ When dealing with vector-valued images (i.e images with dimv()>1), this function computes the L1,L2 or Linf norm of each
+ vector-valued pixel.
+ \param norm_type = Type of the norm being computed (1 = L1, 2 = L2, -1 = Linf).
+ \return A scalar-valued image CImg<float> with size (dimx(),dimy(),dimz(),1), where each pixel is the norm
+ of the corresponding pixels in the original vector-valued image.
+ **/
+ CImg<T>& pointwise_norm(int norm_type=2) {
+ return get_pointwise_norm(norm_type).transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_pointwise_norm(int norm_type=2) const {
+ if (is_empty()) return *this;
+ if (dim==1) return get_abs();
+ CImg<Tfloat> res(width,height,depth);
+ switch (norm_type) {
+ case -1 : { // Linf norm
+ cimg_forXYZ(*this,x,y,z) {
+ Tfloat n = 0; cimg_forV(*this,v) {
+ const Tfloat tmp = (Tfloat)cimg::abs((*this)(x,y,z,v));
+ if (tmp>n) n=tmp; res(x,y,z) = n;
+ }
+ }
+ } break;
+ case 1 : { // L1 norm
+ cimg_forXYZ(*this,x,y,z) {
+ Tfloat n = 0; cimg_forV(*this,v) n+=cimg::abs((*this)(x,y,z,v)); res(x,y,z) = n;
+ }
+ } break;
+ default : { // L2 norm
+ cimg_forXYZ(*this,x,y,z) {
+ Tfloat n = 0; cimg_forV(*this,v) n+=(*this)(x,y,z,v)*(*this)(x,y,z,v); res(x,y,z) = (Tfloat)cimg_std::sqrt((double)n);
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Compute the image of normalized vectors.
+ /**
+ When dealing with vector-valued images (i.e images with dimv()>1), this function return the image of normalized vectors
+ (unit vectors). Null vectors are unchanged. The L2-norm is computed for the normalization.
+ \return A new vector-valued image with same size, where each vector-valued pixels have been normalized.
+ **/
+ CImg<T>& pointwise_orientation() {
+ cimg_forXYZ(*this,x,y,z) {
+ float n = 0;
+ cimg_forV(*this,v) n+=(float)((*this)(x,y,z,v)*(*this)(x,y,z,v));
+ n = (float)cimg_std::sqrt(n);
+ if (n>0) cimg_forV(*this,v) (*this)(x,y,z,v) = (T)((*this)(x,y,z,v)/n);
+ else cimg_forV(*this,v) (*this)(x,y,z,v) = 0;
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_pointwise_orientation() const {
+ if (is_empty()) return *this;
+ return CImg<Tfloat>(*this,false).pointwise_orientation();
+ }
+
+ //! Split image into a list.
+ CImgList<T> get_split(const char axis, const unsigned int nb=0) const {
+ if (is_empty()) return CImgList<T>();
+ CImgList<T> res;
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ if (nb>width)
+ throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'x' into %u images.",
+ pixel_type(),width,height,depth,dim,data,nb);
+ res.assign(nb?nb:width);
+ const unsigned int delta = (unsigned int)cimg::round((float)width/res.size,1);
+ unsigned int l, x;
+ for (l = 0, x = 0; l<res.size-1; ++l, x+=delta) res[l] = get_crop(x,0,0,0,x+delta-1,height-1,depth-1,dim-1);
+ res[res.size-1] = get_crop(x,0,0,0,width-1,height-1,depth-1,dim-1);
+ } break;
+ case 'y' : {
+ if (nb>height)
+ throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'y' into %u images.",
+ pixel_type(),width,height,depth,dim,data,nb);
+ res.assign(nb?nb:height);
+ const unsigned int delta = (unsigned int)cimg::round((float)height/res.size,1);
+ unsigned int l, y;
+ for (l = 0, y = 0; l<res.size-1; ++l, y+=delta) res[l] = get_crop(0,y,0,0,width-1,y+delta-1,depth-1,dim-1);
+ res[res.size-1] = get_crop(0,y,0,0,width-1,height-1,depth-1,dim-1);
+ } break;
+ case 'z' : {
+ if (nb>depth)
+ throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'z' into %u images.",
+ pixel_type(),width,height,depth,dim,data,nb);
+ res.assign(nb?nb:depth);
+ const unsigned int delta = (unsigned int)cimg::round((float)depth/res.size,1);
+ unsigned int l, z;
+ for (l = 0, z = 0; l<res.size-1; ++l, z+=delta) res[l] = get_crop(0,0,z,0,width-1,height-1,z+delta-1,dim-1);
+ res[res.size-1] = get_crop(0,0,z,0,width-1,height-1,depth-1,dim-1);
+ } break;
+ case 'v' : {
+ if (nb>dim)
+ throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'v' into %u images.",
+ pixel_type(),width,height,depth,dim,data,nb);
+ res.assign(nb?nb:dim);
+ const unsigned int delta = (unsigned int)cimg::round((float)dim/res.size,1);
+ unsigned int l, v;
+ for (l = 0, v = 0; l<res.size-1; ++l, v+=delta) res[l] = get_crop(0,0,0,v,width-1,height-1,depth-1,v+delta-1);
+ res[res.size-1] = get_crop(0,0,0,v,width-1,height-1,depth-1,dim-1);
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::get_split() : Unknow axis '%c', must be 'x','y','z' or 'v'",
+ pixel_type(),axis);
+ }
+ return res;
+ }
+
+ // Split image into a list of vectors, according to a given splitting value.
+ CImgList<T> get_split(const T value, const bool keep_values, const bool shared) const {
+ CImgList<T> res;
+ const T *ptr0 = data, *const ptr_end = data + size();
+ while (ptr0<ptr_end) {
+ const T *ptr1 = ptr0;
+ while (ptr1<ptr_end && *ptr1==value) ++ptr1;
+ const unsigned int siz0 = ptr1 - ptr0;
+ if (siz0 && keep_values) res.insert(CImg<T>(ptr0,1,siz0,1,1,shared));
+ ptr0 = ptr1;
+ while (ptr1<ptr_end && *ptr1!=value) ++ptr1;
+ const unsigned int siz1 = ptr1 - ptr0;
+ if (siz1) res.insert(CImg<T>(ptr0,1,siz1,1,1,shared),~0U,shared);
+ ptr0 = ptr1;
+ }
+ return res;
+ }
+
+ //! Append an image to another one.
+ CImg<T>& append(const CImg<T>& img, const char axis, const char align='p') {
+ if (!img) return *this;
+ if (is_empty()) return (*this=img);
+ return get_append(img,axis,align).transfer_to(*this);
+ }
+
+ CImg<T> get_append(const CImg<T>& img, const char axis, const char align='p') const {
+ if (!img) return *this;
+ if (is_empty()) return img;
+ CImgList<T> temp(2);
+ temp[0].width = width; temp[0].height = height; temp[0].depth = depth;
+ temp[0].dim = dim; temp[0].data = data;
+ temp[1].width = img.width; temp[1].height = img.height; temp[1].depth = img.depth;
+ temp[1].dim = img.dim; temp[1].data = img.data;
+ const CImg<T> res = temp.get_append(axis,align);
+ temp[0].width = temp[0].height = temp[0].depth = temp[0].dim = 0; temp[0].data = 0;
+ temp[1].width = temp[1].height = temp[1].depth = temp[1].dim = 0; temp[1].data = 0;
+ return res;
+ }
+
+ //! Compute the list of images, corresponding to the XY-gradients of an image.
+ /**
+ \param scheme = Numerical scheme used for the gradient computation :
+ - -1 = Backward finite differences
+ - 0 = Centered finite differences
+ - 1 = Forward finite differences
+ - 2 = Using Sobel masks
+ - 3 = Using rotation invariant masks
+ - 4 = Using Deriche recusrsive filter.
+ **/
+ CImgList<Tfloat> get_gradient(const char *const axes=0, const int scheme=3) const {
+ CImgList<Tfloat> grad(2,width,height,depth,dim);
+ bool threed = false;
+ if (axes) {
+ for (unsigned int a = 0; axes[a]; ++a) {
+ const char axis = cimg::uncase(axes[a]);
+ switch (axis) {
+ case 'x' : case 'y' : break;
+ case 'z' : threed = true; break;
+ default :
+ throw CImgArgumentException("CImg<%s>::get_gradient() : Unknown specified axis '%c'.",
+ pixel_type(),axis);
+ }
+ }
+ } else threed = (depth>1);
+ if (threed) {
+ grad.insert(1); grad[2].assign(width,height,depth,dim);
+ switch (scheme) { // Compute 3D gradient
+ case -1 : { // backward finite differences
+ CImg_3x3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
+ grad[0](x,y,z,k) = (Tfloat)Iccc - Ipcc;
+ grad[1](x,y,z,k) = (Tfloat)Iccc - Icpc;
+ grad[2](x,y,z,k) = (Tfloat)Iccc - Iccp;
+ }
+ } break;
+ case 1 : { // forward finite differences
+ CImg_2x2x2(I,T);
+ cimg_forV(*this,k) cimg_for2x2x2(*this,x,y,z,k,I) {
+ grad[0](x,y,z,k) = (Tfloat)Incc - Iccc;
+ grad[1](x,y,z,k) = (Tfloat)Icnc - Iccc;
+ grad[2](x,y,z,k) = (Tfloat)Iccn - Iccc;
+ }
+ } break;
+ case 4 : { // using Deriche filter with low standard variation
+ grad[0] = get_deriche(0,1,'x');
+ grad[1] = get_deriche(0,1,'y');
+ grad[2] = get_deriche(0,1,'z');
+ } break;
+ default : { // central finite differences
+ CImg_3x3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
+ grad[0](x,y,z,k) = 0.5f*((Tfloat)Incc - Ipcc);
+ grad[1](x,y,z,k) = 0.5f*((Tfloat)Icnc - Icpc);
+ grad[2](x,y,z,k) = 0.5f*((Tfloat)Iccn - Iccp);
+ }
+ }
+ }
+ } else switch (scheme) { // Compute 2D-gradient
+ case -1 : { // backward finite differences
+ CImg_3x3(I,T);
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
+ grad[0](x,y,z,k) = (Tfloat)Icc - Ipc;
+ grad[1](x,y,z,k) = (Tfloat)Icc - Icp;
+ }
+ } break;
+ case 1 : { // forward finite differences
+ CImg_2x2(I,T);
+ cimg_forZV(*this,z,k) cimg_for2x2(*this,x,y,z,k,I) {
+ grad[0](x,y,0,k) = (Tfloat)Inc - Icc;
+ grad[1](x,y,z,k) = (Tfloat)Icn - Icc;
+ }
+ } break;
+ case 2 : { // using Sobel mask
+ CImg_3x3(I,T);
+ const Tfloat a = 1, b = 2;
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
+ grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
+ grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
+ }
+ } break;
+ case 3 : { // using rotation invariant mask
+ CImg_3x3(I,T);
+ const Tfloat a = (Tfloat)(0.25f*(2-cimg_std::sqrt(2.0f))), b = (Tfloat)(0.5f*(cimg_std::sqrt(2.0f)-1));
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
+ grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
+ grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
+ }
+ } break;
+ case 4 : { // using Deriche filter with low standard variation
+ grad[0] = get_deriche(0,1,'x');
+ grad[1] = get_deriche(0,1,'y');
+ } break;
+ default : { // central finite differences
+ CImg_3x3(I,T);
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
+ grad[0](x,y,z,k) = 0.5f*((Tfloat)Inc - Ipc);
+ grad[1](x,y,z,k) = 0.5f*((Tfloat)Icn - Icp);
+ }
+ }
+ }
+ if (!axes) return grad;
+ CImgList<Tfloat> res;
+ for (unsigned int l = 0; axes[l]; ++l) {
+ const char axis = cimg::uncase(axes[l]);
+ switch (axis) {
+ case 'x' : res.insert(grad[0]); break;
+ case 'y' : res.insert(grad[1]); break;
+ case 'z' : res.insert(grad[2]); break;
+ }
+ }
+ grad.assign();
+ return res;
+ }
+
+ //! Compute the structure tensor field of an image.
+ CImg<T>& structure_tensor(const bool central_scheme=false) {
+ return get_structure_tensor(central_scheme).transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_structure_tensor(const bool central_scheme=false) const {
+ if (is_empty()) return *this;
+ CImg<Tfloat> res;
+ if (depth>1) { // 3D version
+ res.assign(width,height,depth,6,0);
+ CImg_3x3x3(I,T);
+ if (central_scheme) cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // classical central finite differences
+ const Tfloat
+ ix = 0.5f*((Tfloat)Incc - Ipcc),
+ iy = 0.5f*((Tfloat)Icnc - Icpc),
+ iz = 0.5f*((Tfloat)Iccn - Iccp);
+ res(x,y,z,0)+=ix*ix;
+ res(x,y,z,1)+=ix*iy;
+ res(x,y,z,2)+=ix*iz;
+ res(x,y,z,3)+=iy*iy;
+ res(x,y,z,4)+=iy*iz;
+ res(x,y,z,5)+=iz*iz;
+ } else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // Precise forward/backward finite differences
+ const Tfloat
+ ixf = (Tfloat)Incc - Iccc, ixb = (Tfloat)Iccc - Ipcc,
+ iyf = (Tfloat)Icnc - Iccc, iyb = (Tfloat)Iccc - Icpc,
+ izf = (Tfloat)Iccn - Iccc, izb = (Tfloat)Iccc - Iccp;
+ res(x,y,z,0) += 0.5f*(ixf*ixf + ixb*ixb);
+ res(x,y,z,1) += 0.25f*(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb);
+ res(x,y,z,2) += 0.25f*(ixf*izf + ixf*izb + ixb*izf + ixb*izb);
+ res(x,y,z,3) += 0.5f*(iyf*iyf + iyb*iyb);
+ res(x,y,z,4) += 0.25f*(iyf*izf + iyf*izb + iyb*izf + iyb*izb);
+ res(x,y,z,5) += 0.5f*(izf*izf + izb*izb);
+ }
+ } else { // 2D version
+ res.assign(width,height,depth,3,0);
+ CImg_3x3(I,T);
+ if (central_scheme) cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // classical central finite differences
+ const Tfloat
+ ix = 0.5f*((Tfloat)Inc - Ipc),
+ iy = 0.5f*((Tfloat)Icn - Icp);
+ res(x,y,0,0)+=ix*ix;
+ res(x,y,0,1)+=ix*iy;
+ res(x,y,0,2)+=iy*iy;
+ } else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // Precise forward/backward finite differences
+ const Tfloat
+ ixf = (Tfloat)Inc - Icc, ixb = (Tfloat)Icc - Ipc,
+ iyf = (Tfloat)Icn - Icc, iyb = (Tfloat)Icc - Icp;
+ res(x,y,0,0) += 0.5f*(ixf*ixf+ixb*ixb);
+ res(x,y,0,1) += 0.25f*(ixf*iyf+ixf*iyb+ixb*iyf+ixb*iyb);
+ res(x,y,0,2) += 0.5f*(iyf*iyf+iyb*iyb);
+ }
+ }
+ return res;
+ }
+
+ //! Get components of the Hessian matrix of an image.
+ CImgList<Tfloat> get_hessian(const char *const axes=0) const {
+ const char *naxes = axes, *const def_axes2d = "xxxyyy", *const def_axes3d = "xxxyxzyyyzzz";
+ if (!axes) naxes = depth>1?def_axes3d:def_axes2d;
+ CImgList<Tfloat> res;
+ const int lmax = cimg::strlen(naxes);
+ if (lmax%2)
+ throw CImgArgumentException("CImg<%s>::get_hessian() : Incomplete parameter axes = '%s'.",
+ pixel_type(),naxes);
+ res.assign(lmax/2,width,height,depth,dim);
+ if (!cimg::strcasecmp(naxes,def_axes3d)) { // Default 3D version
+ CImg_3x3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
+ res[0](x,y,z,k) = (Tfloat)Ipcc + Incc - 2*Iccc; // Ixx
+ res[1](x,y,z,k) = 0.25f*((Tfloat)Ippc + Innc - Ipnc - Inpc); // Ixy
+ res[2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp); // Ixz
+ res[3](x,y,z,k) = (Tfloat)Icpc + Icnc - 2*Iccc; // Iyy
+ res[4](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp); // Iyz
+ res[5](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc; // Izz
+ }
+ } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // Default 2D version
+ CImg_3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
+ res[0](x,y,0,k) = (Tfloat)Ipc + Inc - 2*Icc; // Ixx
+ res[1](x,y,0,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp); // Ixy
+ res[2](x,y,0,k) = (Tfloat)Icp + Icn - 2*Icc; // Iyy
+ }
+ } else for (int l = 0; l<lmax; ) { // Version with custom axes.
+ const int l2 = l/2;
+ char axis1 = naxes[l++], axis2 = naxes[l++];
+ if (axis1>axis2) cimg::swap(axis1,axis2);
+ bool valid_axis = false;
+ if (axis1=='x' && axis2=='x') { // Ixx
+ valid_axis = true; CImg_3x3(I,T);
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Ipc + Inc - 2*Icc;
+ }
+ else if (axis1=='x' && axis2=='y') { // Ixy
+ valid_axis = true; CImg_3x3(I,T);
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp);
+ }
+ else if (axis1=='x' && axis2=='z') { // Ixz
+ valid_axis = true; CImg_3x3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp);
+ }
+ else if (axis1=='y' && axis2=='y') { // Iyy
+ valid_axis = true; CImg_3x3(I,T);
+ cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Icp + Icn - 2*Icc;
+ }
+ else if (axis1=='y' && axis2=='z') { // Iyz
+ valid_axis = true; CImg_3x3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp);
+ }
+ else if (axis1=='z' && axis2=='z') { // Izz
+ valid_axis = true; CImg_3x3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc;
+ }
+ else if (!valid_axis) throw CImgArgumentException("CImg<%s>::get_hessian() : Invalid parameter axes = '%s'.",
+ pixel_type(),naxes);
+ }
+ return res;
+ }
+
+ //! Compute distance function from 0-valued isophotes by the application of an Hamilton-Jacobi PDE.
+ CImg<T>& distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) {
+ if (is_empty()) return *this;
+ CImg<Tfloat> veloc(*this);
+ for (unsigned int iter = 0; iter<nb_iter; ++iter) {
+ veloc.fill(0);
+ if (depth>1) { // 3D version
+ CImg_3x3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) if (band_size<=0 || cimg::abs(Iccc)<band_size) {
+ const Tfloat
+ gx = 0.5f*((Tfloat)Incc - Ipcc),
+ gy = 0.5f*((Tfloat)Icnc - Icpc),
+ gz = 0.5f*((Tfloat)Iccn - Iccp),
+ sgn = -cimg::sign((Tfloat)Iccc),
+ ix = gx*sgn>0?(Tfloat)Incc - Iccc:(Tfloat)Iccc - Ipcc,
+ iy = gy*sgn>0?(Tfloat)Icnc - Iccc:(Tfloat)Iccc - Icpc,
+ iz = gz*sgn>0?(Tfloat)Iccn - Iccc:(Tfloat)Iccc - Iccp,
+ ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy + gz*gz),
+ ngx = gx/ng,
+ ngy = gy/ng,
+ ngz = gz/ng;
+ veloc(x,y,z,k) = sgn*(ngx*ix + ngy*iy + ngz*iz - 1);
+ }
+ } else { // 2D version
+ CImg_3x3(I,T);
+ cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) if (band_size<=0 || cimg::abs(Icc)<band_size) {
+ const Tfloat
+ gx = 0.5f*((Tfloat)Inc - Ipc),
+ gy = 0.5f*((Tfloat)Icn - Icp),
+ sgn = -cimg::sign((Tfloat)Icc),
+ ix = gx*sgn>0?(Tfloat)Inc - Icc:(Tfloat)Icc - Ipc,
+ iy = gy*sgn>0?(Tfloat)Icn - Icc:(Tfloat)Icc - Icp,
+ ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy),
+ ngx = gx/ng,
+ ngy = gy/ng;
+ veloc(x,y,k) = sgn*(ngx*ix + ngy*iy - 1);
+ }
+ }
+ float m, M = (float)veloc.maxmin(m), xdt = precision/(float)cimg::max(cimg::abs(m),cimg::abs(M));
+ *this+=(veloc*=xdt);
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) const {
+ return CImg<Tfloat>(*this,false).distance_hamilton(nb_iter,band_size,precision);
+ }
+
+ //! Compute the Euclidean distance map to a shape of specified isovalue.
+ CImg<T>& distance(const T isovalue,
+ const float sizex=1, const float sizey=1, const float sizez=1,
+ const bool compute_sqrt=true) {
+ return get_distance(isovalue,sizex,sizey,sizez,compute_sqrt).transfer_to(*this);
+ }
+
+ CImg<floatT> get_distance(const T isovalue,
+ const float sizex=1, const float sizey=1, const float sizez=1,
+ const bool compute_sqrt=true) const {
+ if (is_empty()) return *this;
+ const int dx = dimx(), dy = dimy(), dz = dimz();
+ CImg<floatT> res(dx,dy,dz,dim);
+ const float maxdist = (float)cimg_std::sqrt((float)dx*dx + dy*dy + dz*dz);
+ cimg_forV(*this,k) {
+ bool is_isophote = false;
+
+ if (depth>1) { // 3D version
+ { cimg_forYZ(*this,y,z) {
+ if ((*this)(0,y,z,k)==isovalue) { is_isophote = true; res(0,y,z,k) = 0; } else res(0,y,z,k) = maxdist;
+ for (int x = 1; x<dx; ++x) if ((*this)(x,y,z,k)==isovalue) { is_isophote = true; res(x,y,z,k) = 0; }
+ else res(x,y,z,k) = res(x-1,y,z,k) + sizex;
+ { for (int x = dx-2; x>=0; --x) if (res(x+1,y,z,k)<res(x,y,z,k)) res(x,y,z,k) = res(x+1,y,z,k) + sizex; }
+ }}
+ if (!is_isophote) { res.get_shared_channel(k).fill(cimg::type<float>::max()); continue; }
+ CImg<floatT> tmp(cimg::max(dy,dz));
+ CImg<intT> s(tmp.width), t(s.width);
+ { cimg_forXZ(*this,x,z) {
+ { cimg_forY(*this,y) tmp[y] = res(x,y,z,k); }
+ int q = s[0] = t[0] = 0;
+ { for (int y = 1; y<dy; ++y) {
+ const float val = tmp[y], val2 = val*val;
+ while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizey)>_distance_f(t[q],y,val2,sizey)) --q;
+ if (q<0) { q = 0; s[0] = y; }
+ else {
+ const int w = 1 + _distance_sep(s[q],y,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizey);
+ if (w<dy) { s[++q] = y; t[q] = w; }
+ }
+ }}
+ { for (int y = dy - 1; y>=0; --y) {
+ res(x,y,z,k) = _distance_f(y,s[q],cimg::sqr(tmp[s[q]]),sizey);
+ if (y==t[q]) --q;
+ }}
+ }}
+ { cimg_forXY(*this,x,y) {
+ { cimg_forZ(*this,z) tmp[z] = res(x,y,z,k); }
+ int q = s[0] = t[0] = 0;
+ { for (int z = 1; z<dz; ++z) {
+ const float val = tmp[z];
+ while (q>=0 && _distance_f(t(q),s[q],tmp[s[q]],sizez)>_distance_f(t[q],z,tmp[z],sizez)) --q;
+ if (q<0) { q = 0; s[0] = z; }
+ else {
+ const int w = 1 + _distance_sep(s[q],z,(int)tmp[s[q]],(int)val,sizez);
+ if (w<dz) { s[++q] = z; t[q] = w; }
+ }
+ }}
+ { for (int z = dz - 1; z>=0; --z) {
+ const float val = _distance_f(z,s[q],tmp[s[q]],sizez);
+ res(x,y,z,k) = compute_sqrt?(float)cimg_std::sqrt(val):val;
+ if (z==t[q]) --q;
+ }}
+ }}
+ } else { // 2D version (with small optimizations)
+ cimg_forX(*this,x) {
+ const T *ptrs = ptr(x,0,0,k);
+ float *ptrd = res.ptr(x,0,0,k), d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:maxdist;
+ for (int y = 1; y<dy; ++y) { ptrs+=width; ptrd+=width; d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:d+sizey; }
+ { for (int y = dy - 2; y>=0; --y) { ptrd-=width; if (d<*ptrd) *ptrd = (d+=sizey); else d = *ptrd; }}
+ }
+ if (!is_isophote) { res.get_shared_channel(k).fill(cimg::type<float>::max()); continue; }
+ CImg<floatT> tmp(dx);
+ CImg<intT> s(dx), t(dx);
+ cimg_forY(*this,y) {
+ float *ptmp = tmp.ptr();
+ cimg_std::memcpy(ptmp,res.ptr(0,y,0,k),sizeof(float)*dx);
+ int q = s[0] = t[0] = 0;
+ for (int x = 1; x<dx; ++x) {
+ const float val = *(++ptmp), val2 = val*val;
+ while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizex)>_distance_f(t[q],x,val2,sizex)) --q;
+ if (q<0) { q = 0; s[0] = x; }
+ else {
+ const int w = 1 + _distance_sep(s[q],x,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizex);
+ if (w<dx) { q++; s[q] = x; t[q] = w; }
+ }
+ }
+ float *pres = res.ptr(0,y,0,k) + width;
+ { for (int x = dx - 1; x>=0; --x) {
+ const float val = _distance_f(x,s[q],cimg::sqr(tmp[s[q]]),sizex);
+ *(--pres) = compute_sqrt?(float)cimg_std::sqrt(val):val;
+ if (x==t[q]) --q;
+ }}
+ }
+ }
+ }
+ return res;
+ }
+
+ static float _distance_f(const int x, const int i, const float gi2, const float fact) {
+ const float xmi = fact*((float)x - i);
+ return xmi*xmi + gi2;
+ }
+ static int _distance_sep(const int i, const int u, const int gi2, const int gu2, const float fact) {
+ const float fact2 = fact*fact;
+ return (int)(fact2*(u*u - i*i) + gu2 - gi2)/(int)(2*fact2*(u - i));
+ }
+
+ //! Compute minimal path in a graph, using the Dijkstra algorithm.
+ /**
+ \param distance An object having operator()(unsigned int i, unsigned int j) which returns distance between two nodes (i,j).
+ \param nb_nodes Number of graph nodes.
+ \param starting_node Indice of the starting node.
+ \param ending_node Indice of the ending node (set to ~0U to ignore ending node).
+ \param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
+ \return Array of distances of each node to the starting node.
+ **/
+ template<typename tf, typename t>
+ static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
+ const unsigned int starting_node, const unsigned int ending_node,
+ CImg<t>& previous) {
+
+ CImg<T> dist(1,nb_nodes,1,1,cimg::type<T>::max());
+ dist(starting_node) = 0;
+ previous.assign(1,nb_nodes,1,1,(t)-1);
+ previous(starting_node) = (t)starting_node;
+ CImg<uintT> Q(nb_nodes);
+ cimg_forX(Q,u) Q(u) = u;
+ cimg::swap(Q(starting_node),Q(0));
+ unsigned int sizeQ = nb_nodes;
+ while (sizeQ) {
+ // Update neighbors from minimal vertex
+ const unsigned int umin = Q(0);
+ if (umin==ending_node) sizeQ = 0;
+ else {
+ const T dmin = dist(umin);
+ const T infty = cimg::type<T>::max();
+ for (unsigned int q=1; q<sizeQ; ++q) {
+ const unsigned int v = Q(q);
+ const T d = (T)distance(v,umin);
+ if (d<infty) {
+ const T alt = dmin + d;
+ if (alt<dist(v)) {
+ dist(v) = alt;
+ previous(v) = (t)umin;
+ const T distpos = dist(Q(q));
+ for (unsigned int pos = q, par = 0; pos && distpos<dist(Q(par=(pos+1)/2-1)); pos=par) cimg::swap(Q(pos),Q(par));
+ }
+ }
+ }
+ // Remove minimal vertex from queue
+ Q(0) = Q(--sizeQ);
+ const T distpos = dist(Q(0));
+ for (unsigned int pos = 0, left = 0, right = 0;
+ ((right=2*(pos+1),(left=right-1))<sizeQ && distpos>dist(Q(left))) || (right<sizeQ && distpos>dist(Q(right)));) {
+ if (right<sizeQ) {
+ if (dist(Q(left))<dist(Q(right))) { cimg::swap(Q(pos),Q(left)); pos = left; }
+ else { cimg::swap(Q(pos),Q(right)); pos = right; }
+ } else { cimg::swap(Q(pos),Q(left)); pos = left; }
+ }
+ }
+ }
+ return dist;
+ }
+
+ //! Return minimal path in a graph, using the Dijkstra algorithm.
+ template<typename tf, typename t>
+ static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
+ const unsigned int starting_node, const unsigned int ending_node=~0U) {
+ CImg<uintT> foo;
+ return dijkstra(distance,nb_nodes,starting_node,ending_node,foo);
+ }
+
+ //! Return minimal path in a graph, using the Dijkstra algorithm.
+ /**
+ Instance image corresponds to the adjacency matrix of the graph.
+ \param starting_node Indice of the starting node.
+ \param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
+ \return Array of distances of each node to the starting node.
+ **/
+ template<typename t>
+ CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) {
+ return get_dijkstra(starting_node,ending_node,previous).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<T> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) const {
+ if (width!=height || depth!=1 || dim!=1)
+ throw CImgInstanceException("CImg<%s>::dijkstra() : Instance image (%u,%u,%u,%u,%p) is not a graph adjacency matrix",
+ pixel_type(),width,height,depth,dim,data);
+ return dijkstra(*this,width,starting_node,ending_node,previous);
+ }
+
+ //! Return minimal path in a graph, using the Dijkstra algorithm.
+ CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) {
+ return get_dijkstra(starting_node,ending_node).transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const {
+ CImg<uintT> foo;
+ return get_dijkstra(starting_node,ending_node,foo);
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Meshes and Triangulations
+ //@{
+ //-------------------------------------
+
+ //! Return a 3D centered cube.
+ template<typename tf>
+ static CImg<floatT> cube3d(CImgList<tf>& primitives, const float size=100) {
+ const double s = size/2.0;
+ primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
+ return CImg<floatT>(8,3,1,1,
+ -s,s,s,-s,-s,s,s,-s,
+ -s,-s,s,s,-s,-s,s,s,
+ -s,-s,-s,-s,s,s,s,s);
+ }
+
+ //! Return a 3D centered cuboid.
+ template<typename tf>
+ static CImg<floatT> cuboid3d(CImgList<tf>& primitives, const float sizex=200,
+ const float sizey=100, const float sizez=100) {
+ const double sx = sizex/2.0, sy = sizey/2.0, sz = sizez/2.0;
+ primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
+ return CImg<floatT>(8,3,1,1,
+ -sx,sx,sx,-sx,-sx,sx,sx,-sx,
+ -sy,-sy,sy,sy,-sy,-sy,sy,sy,
+ -sz,-sz,-sz,-sz,sz,sz,sz,sz);
+ }
+
+ //! Return a 3D centered cone.
+ template<typename tf>
+ static CImg<floatT> cone3d(CImgList<tf>& primitives, const float radius=50, const float height=100,
+ const unsigned int subdivisions=24, const bool symetrize=false) {
+ primitives.assign();
+ if (!subdivisions) return CImg<floatT>();
+ const double r = (double)radius, h = (double)height/2;
+ CImgList<floatT> points(2,1,3,1,1,
+ 0.0,0.0,h,
+ 0.0,0.0,-h);
+ const float delta = 360.0f/subdivisions, nh = symetrize?0:-(float)h;
+ for (float angle = 0; angle<360; angle+=delta) {
+ const float a = (float)(angle*cimg::valuePI/180);
+ points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),nh));
+ }
+ const unsigned int nbr = points.size-2;
+ for (unsigned int p = 0; p<nbr; ++p) {
+ const unsigned int curr = 2+p, next = 2+((p+1)%nbr);
+ primitives.insert(CImg<tf>::vector(1,next,curr)).
+ insert(CImg<tf>::vector(0,curr,next));
+ }
+ return points.get_append('x');
+ }
+
+ //! Return a 3D centered cylinder.
+ template<typename tf>
+ static CImg<floatT> cylinder3d(CImgList<tf>& primitives, const float radius=50, const float height=100,
+ const unsigned int subdivisions=24) {
+ primitives.assign();
+ if (!subdivisions) return CImg<floatT>();
+ const double r = (double)radius, h = (double)height/2;
+ CImgList<floatT> points(2,1,3,1,1,
+ 0.0,0.0,-h,
+ 0.0,0.0,h);
+
+ const float delta = 360.0f/subdivisions;
+ for (float angle = 0; angle<360; angle+=delta) {
+ const float a = (float)(angle*cimg::valuePI/180);
+ points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),-(float)h));
+ points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),(float)h));
+ }
+ const unsigned int nbr = (points.size-2)/2;
+ for (unsigned int p = 0; p<nbr; ++p) {
+ const unsigned int curr = 2+2*p, next = 2+(2*((p+1)%nbr));
+ primitives.insert(CImg<tf>::vector(0,next,curr)).
+ insert(CImg<tf>::vector(1,curr+1,next+1)).
+ insert(CImg<tf>::vector(curr,next,next+1,curr+1));
+ }
+ return points.get_append('x');
+ }
+
+ //! Return a 3D centered torus.
+ template<typename tf>
+ static CImg<floatT> torus3d(CImgList<tf>& primitives, const float radius1=100, const float radius2=30,
+ const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) {
+ primitives.assign();
+ if (!subdivisions1 || !subdivisions2) return CImg<floatT>();
+ CImgList<floatT> points;
+ for (unsigned int v = 0; v<subdivisions1; ++v) {
+ const float
+ beta = (float)(v*2*cimg::valuePI/subdivisions1),
+ xc = radius1*(float)cimg_std::cos(beta),
+ yc = radius1*(float)cimg_std::sin(beta);
+ for (unsigned int u=0; u<subdivisions2; ++u) {
+ const float
+ alpha = (float)(u*2*cimg::valuePI/subdivisions2),
+ x = xc + radius2*(float)(cimg_std::cos(alpha)*cimg_std::cos(beta)),
+ y = yc + radius2*(float)(cimg_std::cos(alpha)*cimg_std::sin(beta)),
+ z = radius2*(float)cimg_std::sin(alpha);
+ points.insert(CImg<floatT>::vector(x,y,z));
+ }
+ }
+ for (unsigned int vv = 0; vv<subdivisions1; ++vv) {
+ const unsigned int nv = (vv+1)%subdivisions1;
+ for (unsigned int uu = 0; uu<subdivisions2; ++uu) {
+ const unsigned int nu = (uu+1)%subdivisions2, svv = subdivisions2*vv, snv = subdivisions2*nv;
+ primitives.insert(CImg<tf>::vector(svv+nu,svv+uu,snv+uu));
+ primitives.insert(CImg<tf>::vector(svv+nu,snv+uu,snv+nu));
+ }
+ }
+ return points.get_append('x');
+ }
+
+ //! Return a 3D centered XY plane.
+ template<typename tf>
+ static CImg<floatT> plane3d(CImgList<tf>& primitives, const float sizex=100, const float sizey=100,
+ const unsigned int subdivisionsx=3, const unsigned int subdivisionsy=3,
+ const bool double_sided=false) {
+ primitives.assign();
+ if (!subdivisionsx || !subdivisionsy) return CImg<floatT>();
+ CImgList<floatT> points;
+ const unsigned int w = subdivisionsx + 1, h = subdivisionsy + 1;
+ const float w2 = subdivisionsx/2.0f, h2 = subdivisionsy/2.0f, fx = (float)sizex/w, fy = (float)sizey/h;
+ for (unsigned int yy = 0; yy<h; ++yy)
+ for (unsigned int xx = 0; xx<w; ++xx)
+ points.insert(CImg<floatT>::vector(fx*(xx-w2),fy*(yy-h2),0));
+ for (unsigned int y = 0; y<subdivisionsy; ++y) for (unsigned int x = 0; x<subdivisionsx; ++x) {
+ const int off1 = x+y*w, off2 = x+1+y*w, off3 = x+1+(y+1)*w, off4 = x+(y+1)*w;
+ primitives.insert(CImg<tf>::vector(off1,off4,off3,off2));
+ if (double_sided) primitives.insert(CImg<tf>::vector(off1,off2,off3,off4));
+ }
+ return points.get_append('x');
+ }
+
+ //! Return a 3D centered sphere.
+ template<typename tf>
+ static CImg<floatT> sphere3d(CImgList<tf>& primitives, const float radius=50, const unsigned int subdivisions=3) {
+
+ // Create initial icosahedron
+ primitives.assign();
+ if (!subdivisions) return CImg<floatT>();
+ const double tmp = (1+cimg_std::sqrt(5.0f))/2, a = 1.0/cimg_std::sqrt(1+tmp*tmp), b = tmp*a;
+ CImgList<floatT> points(12,1,3,1,1, b,a,0.0, -b,a,0.0, -b,-a,0.0, b,-a,0.0, a,0.0,b, a,0.0,-b,
+ -a,0.0,-b, -a,0.0,b, 0.0,b,a, 0.0,-b,a, 0.0,-b,-a, 0.0,b,-a);
+ primitives.assign(20,1,3,1,1, 4,8,7, 4,7,9, 5,6,11, 5,10,6, 0,4,3, 0,3,5, 2,7,1, 2,1,6,
+ 8,0,11, 8,11,1, 9,10,3, 9,2,10, 8,4,0, 11,0,5, 4,9,3,
+ 5,3,10, 7,8,1, 6,1,11, 7,2,9, 6,10,2);
+
+ // Recurse subdivisions
+ for (unsigned int i = 0; i<subdivisions; ++i) {
+ const unsigned int L = primitives.size;
+ for (unsigned int l = 0; l<L; ++l) {
+ const unsigned int
+ p0 = (unsigned int)primitives(0,0), p1 = (unsigned int)primitives(0,1), p2 = (unsigned int)primitives(0,2);
+ const float
+ x0 = points(p0,0), y0 = points(p0,1), z0 = points(p0,2),
+ x1 = points(p1,0), y1 = points(p1,1), z1 = points(p1,2),
+ x2 = points(p2,0), y2 = points(p2,1), z2 = points(p2,2),
+ tnx0 = (x0+x1)/2, tny0 = (y0+y1)/2, tnz0 = (z0+z1)/2, nn0 = (float)cimg_std::sqrt(tnx0*tnx0+tny0*tny0+tnz0*tnz0),
+ tnx1 = (x0+x2)/2, tny1 = (y0+y2)/2, tnz1 = (z0+z2)/2, nn1 = (float)cimg_std::sqrt(tnx1*tnx1+tny1*tny1+tnz1*tnz1),
+ tnx2 = (x1+x2)/2, tny2 = (y1+y2)/2, tnz2 = (z1+z2)/2, nn2 = (float)cimg_std::sqrt(tnx2*tnx2+tny2*tny2+tnz2*tnz2),
+ nx0 = tnx0/nn0, ny0 = tny0/nn0, nz0 = tnz0/nn0,
+ nx1 = tnx1/nn1, ny1 = tny1/nn1, nz1 = tnz1/nn1,
+ nx2 = tnx2/nn2, ny2 = tny2/nn2, nz2 = tnz2/nn2;
+ int i0 = -1, i1 = -1, i2 = -1;
+ cimglist_for(points,p) {
+ const float x = (float)points(p,0), y = (float)points(p,1), z = (float)points(p,2);
+ if (x==nx0 && y==ny0 && z==nz0) i0 = p;
+ if (x==nx1 && y==ny1 && z==nz1) i1 = p;
+ if (x==nx2 && y==ny2 && z==nz2) i2 = p;
+ }
+ if (i0<0) { points.insert(CImg<floatT>::vector(nx0,ny0,nz0)); i0 = points.size-1; }
+ if (i1<0) { points.insert(CImg<floatT>::vector(nx1,ny1,nz1)); i1 = points.size-1; }
+ if (i2<0) { points.insert(CImg<floatT>::vector(nx2,ny2,nz2)); i2 = points.size-1; }
+ primitives.remove(0);
+ primitives.insert(CImg<tf>::vector(p0,i0,i1)).
+ insert(CImg<tf>::vector((tf)i0,(tf)p1,(tf)i2)).
+ insert(CImg<tf>::vector((tf)i1,(tf)i2,(tf)p2)).
+ insert(CImg<tf>::vector((tf)i1,(tf)i0,(tf)i2));
+ }
+ }
+ return points.get_append('x')*=radius;
+ }
+
+ //! Return a 3D centered ellipsoid.
+ template<typename tf, typename t>
+ static CImg<floatT> ellipsoid3d(CImgList<tf>& primitives, const CImg<t>& tensor,
+ const unsigned int subdivisions=3) {
+ primitives.assign();
+ if (!subdivisions) return CImg<floatT>();
+ typedef typename cimg::superset<t,float>::type tfloat;
+ CImg<tfloat> S,V;
+ tensor.symmetric_eigen(S,V);
+ const tfloat l0 = S[0], l1 = S[1], l2 = S[2];
+ CImg<floatT> points = sphere(primitives,subdivisions);
+ cimg_forX(points,p) {
+ points(p,0) = (float)(points(p,0)*l0);
+ points(p,1) = (float)(points(p,1)*l1);
+ points(p,2) = (float)(points(p,2)*l2);
+ }
+ V.transpose();
+ points = V*points;
+ return points;
+ }
+
+ //! Return a 3D elevation object of the instance image.
+ template<typename tf, typename tc, typename te>
+ CImg<floatT> get_elevation3d(CImgList<tf>& primitives, CImgList<tc>& colors, const CImg<te>& elevation) const {
+ primitives.assign();
+ colors.assign();
+ if (is_empty()) return *this;
+ if (depth>1)
+ throw CImgInstanceException("CImg<%s>::get_elevation3d() : Instance image (%u,%u,%u,%u,%p) is not a 2D image.",
+ pixel_type(),width,height,depth,dim,data);
+ if (!is_sameXY(elevation))
+ throw CImgArgumentException("CImg<%s>::get_elevation3d() : Elevation image (%u,%u,%u,%u,%p) and instance image (%u,%u,%u,%u,%p) "
+ "have different sizes.",pixel_type(),
+ elevation.width,elevation.height,elevation.depth,elevation.dim,elevation.data,
+ width,height,depth,dim,data,pixel_type());
+ float m, M = (float)maxmin(m);
+ if (M==m) ++M;
+ const unsigned int w = width + 1, h = height + 1;
+ CImg<floatT> points(w*h,3);
+ cimg_forXY(*this,x,y) {
+ const int yw = y*w, xpyw = x + yw, xpyww = xpyw + w;
+ points(xpyw,0) = points(xpyw+1,0) = points(xpyww+1,0) = points(xpyww,0) = (float)x;
+ points(xpyw,1) = points(xpyw+1,1) = points(xpyww+1,1) = points(xpyww,1) = (float)y;
+ points(xpyw,2) = points(xpyw+1,2) = points(xpyww+1,2) = points(xpyww,2) = (float)elevation(x,y);
+ primitives.insert(CImg<tf>::vector(xpyw,xpyw+1,xpyww+1,xpyww));
+ const unsigned char
+ r = (unsigned char)(((*this)(x,y,0) - m)*255/(M-m)),
+ g = dim>1?(unsigned char)(((*this)(x,y,1) - m)*255/(M-m)):r,
+ b = dim>2?(unsigned char)(((*this)(x,y,2) - m)*255/(M-m)):(dim>1?0:r);
+ colors.insert(CImg<tc>::vector((tc)r,(tc)g,(tc)b));
+ }
+ return points;
+ }
+
+ // Inner routine used by the Marching square algorithm.
+ template<typename t>
+ static int _marching_squares_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
+ const unsigned int x, const unsigned int nx) {
+ switch (edge) {
+ case 0 : return (int)indices1(x,0);
+ case 1 : return (int)indices1(nx,1);
+ case 2 : return (int)indices2(x,0);
+ case 3 : return (int)indices1(x,1);
+ }
+ return 0;
+ }
+
+ //! Polygonize an implicit 2D function by the marching squares algorithm.
+ template<typename tf, typename tfunc>
+ static CImg<floatT> marching_squares(CImgList<tf>& primitives, const tfunc& func, const float isovalue,
+ const float x0, const float y0,
+ const float x1, const float y1,
+ const float resx, const float resy) {
+ static unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 };
+ static int segments[16][4] = { { -1,-1,-1,-1 }, { 0,3,-1,-1 }, { 0,1,-1,-1 }, { 1,3,-1,-1 },
+ { 1,2,-1,-1 }, { 0,1,2,3 }, { 0,2,-1,-1 }, { 2,3,-1,-1 },
+ { 2,3,-1,-1 }, { 0,2,-1,-1}, { 0,3,1,2 }, { 1,2,-1,-1 },
+ { 1,3,-1,-1 }, { 0,1,-1,-1}, { 0,3,-1,-1}, { -1,-1,-1,-1 } };
+ const unsigned int
+ nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
+ ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1;
+ if (!nxm1 || !nym1) return CImg<floatT>();
+
+ primitives.assign();
+ CImgList<floatT> points;
+ CImg<intT> indices1(nx,1,1,2,-1), indices2(nx,1,1,2);
+ CImg<floatT> values1(nx), values2(nx);
+ float X = 0, Y = 0, nX = 0, nY = 0;
+
+ // Fill first line with values
+ cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=resx; }
+
+ // Run the marching squares algorithm
+ Y = y0; nY = Y + resy;
+ for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y=nY, nY+=resy) {
+ X = x0; nX = X + resx;
+ indices2.fill(-1);
+ for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X=nX, nX+=resx) {
+
+ // Determine cube configuration
+ const float
+ val0 = values1(xi), val1 = values1(nxi),
+ val2 = values2(nxi) = (float)func(nX,nY),
+ val3 = values2(xi) = (float)func(X,nY);
+
+ const unsigned int configuration = (val0<isovalue?1:0) | (val1<isovalue?2:0) | (val2<isovalue?4:0) | (val3<isovalue?8:0),
+ edge = edges[configuration];
+
+ // Compute intersection points
+ if (edge) {
+ if ((edge&1) && indices1(xi,0)<0) {
+ const float Xi = X + (isovalue-val0)*resx/(val1-val0);
+ indices1(xi,0) = points.size;
+ points.insert(CImg<floatT>::vector(Xi,Y));
+ }
+ if ((edge&2) && indices1(nxi,1)<0) {
+ const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
+ indices1(nxi,1) = points.size;
+ points.insert(CImg<floatT>::vector(nX,Yi));
+ }
+ if ((edge&4) && indices2(xi,0)<0) {
+ const float Xi = X + (isovalue-val3)*resx/(val2-val3);
+ indices2(xi,0) = points.size;
+ points.insert(CImg<floatT>::vector(Xi,nY));
+ }
+ if ((edge&8) && indices1(xi,1)<0) {
+ const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
+ indices1(xi,1) = points.size;
+ points.insert(CImg<floatT>::vector(X,Yi));
+ }
+
+ // Create segments
+ for (int *segment = segments[configuration]; *segment!=-1; ) {
+ const unsigned int p0 = *(segment++), p1 = *(segment++);
+ const tf
+ i0 = (tf)(_marching_squares_indice(p0,indices1,indices2,xi,nxi)),
+ i1 = (tf)(_marching_squares_indice(p1,indices1,indices2,xi,nxi));
+ primitives.insert(CImg<tf>::vector(i0,i1));
+ }
+ }
+ }
+ values1.swap(values2);
+ indices1.swap(indices2);
+ }
+ return points.get_append('x');
+ }
+
+ // Inner routine used by the Marching cube algorithm.
+ template<typename t>
+ static int _marching_cubes_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
+ const unsigned int x, const unsigned int y, const unsigned int nx, const unsigned int ny) {
+ switch (edge) {
+ case 0 : return indices1(x,y,0);
+ case 1 : return indices1(nx,y,1);
+ case 2 : return indices1(x,ny,0);
+ case 3 : return indices1(x,y,1);
+ case 4 : return indices2(x,y,0);
+ case 5 : return indices2(nx,y,1);
+ case 6 : return indices2(x,ny,0);
+ case 7 : return indices2(x,y,1);
+ case 8 : return indices1(x,y,2);
+ case 9 : return indices1(nx,y,2);
+ case 10 : return indices1(nx,ny,2);
+ case 11 : return indices1(x,ny,2);
+ }
+ return 0;
+ }
+
+ //! Polygonize an implicit function
+ // This function uses the Marching Cubes Tables published on the web page :
+ // http://astronomy.swin.edu.au/~pbourke/modelling/polygonise/
+ template<typename tf, typename tfunc>
+ static CImg<floatT> marching_cubes(CImgList<tf>& primitives,
+ const tfunc& func, const float isovalue,
+ const float x0, const float y0, const float z0,
+ const float x1, const float y1, const float z1,
+ const float resx, const float resy, const float resz,
+ const bool invert_faces=false) {
+
+ static unsigned int edges[256] = {
+ 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
+ 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
+ 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
+ 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
+ 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
+ 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
+ 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
+ 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
+ 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
+ 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
+ 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
+ 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
+ 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
+ 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
+ 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
+ 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 };
+
+ static int triangles[256][16] =
+ {{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
+ { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 },
+ { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 },
+ { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 },
+ { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 },
+ { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 },
+ { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 },
+ { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
+ { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 },
+ { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 },
+ { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 },
+ { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
+ { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
+ { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 },
+ { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 },
+ { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
+ { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 },
+ { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 },
+ { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 },
+ { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
+ { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 },
+ { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 },
+ { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 },
+ { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 },
+ { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 },
+ { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 },
+ { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 },
+ { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 },
+ { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 },
+ { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 },
+ { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 },
+ { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
+ { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 },
+ { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 },
+ { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 },
+ { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 },
+ { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 },
+ { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 },
+ { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 },
+ { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 },
+ { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 },
+ { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 },
+ { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 },
+ { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 },
+ { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 },
+ { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 },
+ { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 },
+ { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 },
+ { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 },
+ { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 },
+ { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 },
+ { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 },
+ { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 },
+ { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 },
+ { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 },
+ { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 },
+ { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 },
+ { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 },
+ { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 },
+ { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 },
+ { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 },
+ { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 },
+ { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }};
+
+ const unsigned int
+ nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
+ ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1,
+ nz = (unsigned int)((z1-z0+1)/resz), nzm1 = nz-1;
+ if (!nxm1 || !nym1 || !nzm1) return CImg<floatT>();
+
+ primitives.assign();
+ CImgList<floatT> points;
+ CImg<intT> indices1(nx,ny,1,3,-1), indices2(indices1);
+ CImg<floatT> values1(nx,ny), values2(nx,ny);
+ float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0;
+
+ // Fill the first plane with function values
+ Y = y0;
+ cimg_forY(values1,y) {
+ X = x0;
+ cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=resx; }
+ Y+=resy;
+ }
+
+ // Run Marching Cubes algorithm
+ Z = z0; nZ = Z + resz;
+ for (unsigned int zi = 0; zi<nzm1; ++zi, Z = nZ, nZ+=resz) {
+ Y = y0; nY = Y + resy;
+ indices2.fill(-1);
+ for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y = nY, nY+=resy) {
+ X = x0; nX = X + resx;
+ for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X = nX, nX+=resx) {
+
+ // Determine cube configuration
+ const float
+ val0 = values1(xi,yi), val1 = values1(nxi,yi), val2 = values1(nxi,nyi), val3 = values1(xi,nyi),
+ val4 = values2(xi,yi) = (float)func(X,Y,nZ),
+ val5 = values2(nxi,yi) = (float)func(nX,Y,nZ),
+ val6 = values2(nxi,nyi) = (float)func(nX,nY,nZ),
+ val7 = values2(xi,nyi) = (float)func(X,nY,nZ);
+
+ const unsigned int configuration =
+ (val0<isovalue?1:0) | (val1<isovalue?2:0) | (val2<isovalue?4:0) | (val3<isovalue?8:0) |
+ (val4<isovalue?16:0) | (val5<isovalue?32:0) | (val6<isovalue?64:0) | (val7<isovalue?128:0),
+ edge = edges[configuration];
+
+ // Compute intersection points
+ if (edge) {
+ if ((edge&1) && indices1(xi,yi,0)<0) {
+ const float Xi = X + (isovalue-val0)*resx/(val1-val0);
+ indices1(xi,yi,0) = points.size;
+ points.insert(CImg<floatT>::vector(Xi,Y,Z));
+ }
+ if ((edge&2) && indices1(nxi,yi,1)<0) {
+ const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
+ indices1(nxi,yi,1) = points.size;
+ points.insert(CImg<floatT>::vector(nX,Yi,Z));
+ }
+ if ((edge&4) && indices1(xi,nyi,0)<0) {
+ const float Xi = X + (isovalue-val3)*resx/(val2-val3);
+ indices1(xi,nyi,0) = points.size;
+ points.insert(CImg<floatT>::vector(Xi,nY,Z));
+ }
+ if ((edge&8) && indices1(xi,yi,1)<0) {
+ const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
+ indices1(xi,yi,1) = points.size;
+ points.insert(CImg<floatT>::vector(X,Yi,Z));
+ }
+ if ((edge&16) && indices2(xi,yi,0)<0) {
+ const float Xi = X + (isovalue-val4)*resx/(val5-val4);
+ indices2(xi,yi,0) = points.size;
+ points.insert(CImg<floatT>::vector(Xi,Y,nZ));
+ }
+ if ((edge&32) && indices2(nxi,yi,1)<0) {
+ const float Yi = Y + (isovalue-val5)*resy/(val6-val5);
+ indices2(nxi,yi,1) = points.size;
+ points.insert(CImg<floatT>::vector(nX,Yi,nZ));
+ }
+ if ((edge&64) && indices2(xi,nyi,0)<0) {
+ const float Xi = X + (isovalue-val7)*resx/(val6-val7);
+ indices2(xi,nyi,0) = points.size;
+ points.insert(CImg<floatT>::vector(Xi,nY,nZ));
+ }
+ if ((edge&128) && indices2(xi,yi,1)<0) {
+ const float Yi = Y + (isovalue-val4)*resy/(val7-val4);
+ indices2(xi,yi,1) = points.size;
+ points.insert(CImg<floatT>::vector(X,Yi,nZ));
+ }
+ if ((edge&256) && indices1(xi,yi,2)<0) {
+ const float Zi = Z+ (isovalue-val0)*resz/(val4-val0);
+ indices1(xi,yi,2) = points.size;
+ points.insert(CImg<floatT>::vector(X,Y,Zi));
+ }
+ if ((edge&512) && indices1(nxi,yi,2)<0) {
+ const float Zi = Z + (isovalue-val1)*resz/(val5-val1);
+ indices1(nxi,yi,2) = points.size;
+ points.insert(CImg<floatT>::vector(nX,Y,Zi));
+ }
+ if ((edge&1024) && indices1(nxi,nyi,2)<0) {
+ const float Zi = Z + (isovalue-val2)*resz/(val6-val2);
+ indices1(nxi,nyi,2) = points.size;
+ points.insert(CImg<floatT>::vector(nX,nY,Zi));
+ }
+ if ((edge&2048) && indices1(xi,nyi,2)<0) {
+ const float Zi = Z + (isovalue-val3)*resz/(val7-val3);
+ indices1(xi,nyi,2) = points.size;
+ points.insert(CImg<floatT>::vector(X,nY,Zi));
+ }
+
+ // Create triangles
+ for (int *triangle = triangles[configuration]; *triangle!=-1; ) {
+ const unsigned int p0 = *(triangle++), p1 = *(triangle++), p2 = *(triangle++);
+ const tf
+ i0 = (tf)(_marching_cubes_indice(p0,indices1,indices2,xi,yi,nxi,nyi)),
+ i1 = (tf)(_marching_cubes_indice(p1,indices1,indices2,xi,yi,nxi,nyi)),
+ i2 = (tf)(_marching_cubes_indice(p2,indices1,indices2,xi,yi,nxi,nyi));
+ if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
+ else primitives.insert(CImg<tf>::vector(i0,i2,i1));
+ }
+ }
+ }
+ }
+ cimg::swap(values1,values2);
+ cimg::swap(indices1,indices2);
+ }
+ return points.get_append('x');
+ }
+
+ struct _marching_squares_func {
+ const CImg<T>& ref;
+ _marching_squares_func(const CImg<T>& pref):ref(pref) {}
+ float operator()(const float x, const float y) const {
+ return (float)ref((int)x,(int)y);
+ }
+ };
+
+ struct _marching_cubes_func {
+ const CImg<T>& ref;
+ _marching_cubes_func(const CImg<T>& pref):ref(pref) {}
+ float operator()(const float x, const float y, const float z) const {
+ return (float)ref((int)x,(int)y,(int)z);
+ }
+ };
+
+ struct _marching_squares_func_float {
+ const CImg<T>& ref;
+ _marching_squares_func_float(const CImg<T>& pref):ref(pref) {}
+ float operator()(const float x, const float y) const {
+ return (float)ref._linear_atXY(x,y);
+ }
+ };
+
+ struct _marching_cubes_func_float {
+ const CImg<T>& ref;
+ _marching_cubes_func_float(const CImg<T>& pref):ref(pref) {}
+ float operator()(const float x, const float y, const float z) const {
+ return (float)ref._linear_atXYZ(x,y,z);
+ }
+ };
+
+ //! Compute a vectorization of an implicit function.
+ template<typename tf>
+ CImg<floatT> get_isovalue3d(CImgList<tf>& primitives, const float isovalue,
+ const float resx=1, const float resy=1, const float resz=1,
+ const bool invert_faces=false) const {
+ primitives.assign();
+ if (is_empty()) return *this;
+ if (dim>1)
+ throw CImgInstanceException("CImg<%s>::get_isovalue3d() : Instance image (%u,%u,%u,%u,%p) is not a scalar image.",
+ pixel_type(),width,height,depth,dim,data);
+ CImg<floatT> points;
+ if (depth>1) {
+ if (resx==1 && resy==1 && resz==1) {
+ const _marching_cubes_func func(*this);
+ points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces);
+ } else {
+ const _marching_cubes_func_float func(*this);
+ points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces);
+ }
+ } else {
+ if (resx==1 && resy==1) {
+ const _marching_squares_func func(*this);
+ points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy);
+ } else {
+ const _marching_squares_func_float func(*this);
+ points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy);
+ }
+ if (points) points.resize(-100,3,1,1,0);
+ }
+ return points;
+ }
+
+ //! Translate a 3D object.
+ CImg<T>& translate_object3d(const float tx, const float ty=0, const float tz=0) {
+ get_shared_line(0)+=tx; get_shared_line(1)+=ty; get_shared_line(2)+=tz;
+ return *this;
+ }
+
+ CImg<Tfloat> get_translate_object3d(const float tx, const float ty=0, const float tz=0) const {
+ return CImg<Tfloat>(*this,false).translate_object3d(tx,ty,tz);
+ }
+
+ //! Translate a 3D object so that it becomes centered.
+ CImg<T>& translate_object3d() {
+ CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+ float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
+ xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2;
+ return *this;
+ }
+
+ CImg<Tfloat> get_translate_object3d() const {
+ return CImg<Tfloat>(*this,false).translate_object3d();
+ }
+
+ //! Resize a 3D object.
+ CImg<T>& resize_object3d(const float sx, const float sy=-100, const float sz=-100) {
+ CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+ float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
+ if (xm<xM) { if (sx>0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; }
+ if (ym<yM) { if (sy>0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; }
+ if (zm<zM) { if (sz>0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; }
+ return *this;
+ }
+
+ CImg<Tfloat> get_resize_object3d(const float sx, const float sy=-100, const float sz=-100) const {
+ return CImg<Tfloat>(*this,false).resize_object3d(sx,sy,sz);
+ }
+
+ // Resize a 3D object so that its max dimension if one.
+ CImg<T> resize_object3d() const {
+ CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+ float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
+ const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz);
+ if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; }
+ return *this;
+ }
+
+ CImg<Tfloat> get_resize_object3d() const {
+ return CImg<Tfloat>(*this,false).resize_object3d();
+ }
+
+ //! Append a 3D object to another one.
+ template<typename tf, typename tp, typename tff>
+ CImg<T>& append_object3d(CImgList<tf>& primitives, const CImg<tp>& obj_points, const CImgList<tff>& obj_primitives) {
+ const unsigned int P = width;
+ append(obj_points,'x');
+ const unsigned int N = primitives.size;
+ primitives.insert(obj_primitives);
+ for (unsigned int i = N; i<primitives.size; ++i) {
+ CImg<tf> &p = primitives[i];
+ if (p.size()!=5) p+=P;
+ else { p[0]+=P; if (p[2]==0) p[1]+=P; }
+ }
+ return *this;
+ }
+
+ //@}
+ //----------------------------
+ //
+ //! \name Color bases
+ //@{
+ //----------------------------
+
+ //! Return a default indexed color palette with 256 (R,G,B) entries.
+ /**
+ The default color palette is used by %CImg when displaying images on 256 colors displays.
+ It consists in the quantification of the (R,G,B) color space using 3:3:2 bits for color coding
+ (i.e 8 levels for the Red and Green and 4 levels for the Blue).
+ \return a 1x256x1x3 color image defining the palette entries.
+ **/
+ static CImg<Tuchar> default_LUT8() {
+ static CImg<Tuchar> palette;
+ if (!palette) {
+ palette.assign(1,256,1,3);
+ for (unsigned int index = 0, r = 16; r<256; r+=32)
+ for (unsigned int g = 16; g<256; g+=32)
+ for (unsigned int b = 32; b<256; b+=64) {
+ palette(0,index,0) = (Tuchar)r;
+ palette(0,index,1) = (Tuchar)g;
+ palette(0,index++,2) = (Tuchar)b;
+ }
+ }
+ return palette;
+ }
+
+ //! Return a rainbow color palette with 256 (R,G,B) entries.
+ static CImg<Tuchar> rainbow_LUT8() {
+ static CImg<Tuchar> palette;
+ if (!palette) {
+ CImg<Tint> tmp(1,256,1,3,1);
+ tmp.get_shared_channel(0).sequence(0,359);
+ palette = tmp.HSVtoRGB();
+ }
+ return palette;
+ }
+
+ //! Return a contrasted color palette with 256 (R,G,B) entries.
+ static CImg<Tuchar> contrast_LUT8() {
+ static const unsigned char pal[] = {
+ 217,62,88,75,1,237,240,12,56,160,165,116,1,1,204,2,15,248,148,185,133,141,46,246,222,116,16,5,207,226,
+ 17,114,247,1,214,53,238,0,95,55,233,235,109,0,17,54,33,0,90,30,3,0,94,27,19,0,68,212,166,130,0,15,7,119,
+ 238,2,246,198,0,3,16,10,13,2,25,28,12,6,2,99,18,141,30,4,3,140,12,4,30,233,7,10,0,136,35,160,168,184,20,
+ 233,0,1,242,83,90,56,180,44,41,0,6,19,207,5,31,214,4,35,153,180,75,21,76,16,202,218,22,17,2,136,71,74,
+ 81,251,244,148,222,17,0,234,24,0,200,16,239,15,225,102,230,186,58,230,110,12,0,7,129,249,22,241,37,219,
+ 1,3,254,210,3,212,113,131,197,162,123,252,90,96,209,60,0,17,0,180,249,12,112,165,43,27,229,77,40,195,12,
+ 87,1,210,148,47,80,5,9,1,137,2,40,57,205,244,40,8,252,98,0,40,43,206,31,187,0,180,1,69,70,227,131,108,0,
+ 223,94,228,35,248,243,4,16,0,34,24,2,9,35,73,91,12,199,51,1,249,12,103,131,20,224,2,70,32,
+ 233,1,165,3,8,154,246,233,196,5,0,6,183,227,247,195,208,36,0,0,226,160,210,198,69,153,210,1,23,8,192,2,4,
+ 137,1,0,52,2,249,241,129,0,0,234,7,238,71,7,32,15,157,157,252,158,2,250,6,13,30,11,162,0,199,21,11,27,224,
+ 4,157,20,181,111,187,218,3,0,11,158,230,196,34,223,22,248,135,254,210,157,219,0,117,239,3,255,4,227,5,247,
+ 11,4,3,188,111,11,105,195,2,0,14,1,21,219,192,0,183,191,113,241,1,12,17,248,0,48,7,19,1,254,212,0,239,246,
+ 0,23,0,250,165,194,194,17,3,253,0,24,6,0,141,167,221,24,212,2,235,243,0,0,205,1,251,133,204,28,4,6,1,10,
+ 141,21,74,12,236,254,228,19,1,0,214,1,186,13,13,6,13,16,27,209,6,216,11,207,251,59,32,9,155,23,19,235,143,
+ 116,6,213,6,75,159,23,6,0,228,4,10,245,249,1,7,44,234,4,102,174,0,19,239,103,16,15,18,8,214,22,4,47,244,
+ 255,8,0,251,173,1,212,252,250,251,252,6,0,29,29,222,233,246,5,149,0,182,180,13,151,0,203,183,0,35,149,0,
+ 235,246,254,78,9,17,203,73,11,195,0,3,5,44,0,0,237,5,106,6,130,16,214,20,168,247,168,4,207,11,5,1,232,251,
+ 129,210,116,231,217,223,214,27,45,38,4,177,186,249,7,215,172,16,214,27,249,230,236,2,34,216,217,0,175,30,
+ 243,225,244,182,20,212,2,226,21,255,20,0,2,13,62,13,191,14,76,64,20,121,4,118,0,216,1,147,0,2,210,1,215,
+ 95,210,236,225,184,46,0,248,24,11,1,9,141,250,243,9,221,233,160,11,147,2,55,8,23,12,253,9,0,54,0,231,6,3,
+ 141,8,2,246,9,180,5,11,8,227,8,43,110,242,1,130,5,97,36,10,6,219,86,133,11,108,6,1,5,244,67,19,28,0,174,
+ 154,16,127,149,252,188,196,196,228,244,9,249,0,0,0,37,170,32,250,0,73,255,23,3,224,234,38,195,198,0,255,87,
+ 33,221,174,31,3,0,189,228,6,153,14,144,14,108,197,0,9,206,245,254,3,16,253,178,248,0,95,125,8,0,3,168,21,
+ 23,168,19,50,240,244,185,0,1,144,10,168,31,82,1,13 };
+ static const CImg<Tuchar> palette(pal,1,256,1,3,false);
+ return palette;
+ }
+
+ //! Convert (R,G,B) color image to indexed color image.
+ template<typename t>
+ CImg<T>& RGBtoLUT(const CImg<t>& palette, const bool dithering=true, const bool indexing=false) {
+ return get_RGBtoLUT(palette,dithering,indexing).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<t> get_RGBtoLUT(const CImg<t>& palette, const bool dithering=true, const bool indexing=false) const {
+ if (is_empty()) return CImg<t>();
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoLUT() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image.",
+ pixel_type(),dim);
+ if (palette.data && palette.dim!=3)
+ throw CImgArgumentException("CImg<%s>::RGBtoLUT() : Given palette dimension is dim=%u, "
+ "should be a (R,G,B) palette",
+ pixel_type(),palette.dim);
+ CImg<t> res(width,height,depth,indexing?1:3);
+ float *line1 = new float[3*width], *line2 = new float[3*width];
+ t *pRd = res.ptr(0,0,0,0), *pGd = indexing?pRd:res.ptr(0,0,0,1), *pBd = indexing?pRd:res.ptr(0,0,0,2);
+ cimg_forZ(*this,z) {
+ const T *pRs = ptr(0,0,z,0), *pGs = ptr(0,0,z,1), *pBs = ptr(0,0,z,2);
+ float *ptrd = line2; cimg_forX(*this,x) { *(ptrd++) = (float)*(pRs++); *(ptrd++) = (float)*(pGs++); *(ptrd++) = (float)*(pBs++); }
+ cimg_forY(*this,y) {
+ cimg::swap(line1,line2);
+ if (y<dimy()-1) {
+ const int ny = y + 1;
+ const T *pRs = ptr(0,ny,z,0), *pGs = ptr(0,ny,z,1), *pBs = ptr(0,ny,z,2);
+ float *ptrd = line2; cimg_forX(*this,x) { *(ptrd++) = (float)*(pRs++); *(ptrd++) = (float)*(pGs++); *(ptrd++) = (float)*(pBs++); }
+ }
+ float *ptr1 = line1, *ptr2 = line2;
+ cimg_forX(*this,x) {
+ float R = *(ptr1++), G = *(ptr1++), B = *(ptr1++);
+ R = R<0?0:(R>255?255:R); G = G<0?0:(G>255?255:G); B = B<0?0:(B>255?255:B);
+ t Rbest = 0, Gbest = 0, Bbest = 0;
+ int best_index = 0;
+ if (palette) { // find best match in given color palette
+ const t *pRs = palette.ptr(0,0,0,0), *pGs = palette.ptr(0,0,0,1), *pBs = palette.ptr(0,0,0,2);
+ const unsigned int Npal = palette.width*palette.height*palette.depth;
+ float min = cimg::type<float>::max();
+ for (unsigned int off = 0; off<Npal; ++off) {
+ const t Rp = *(pRs++), Gp = *(pGs++), Bp = *(pBs++);
+ const float error = cimg::sqr((float)Rp-(float)R) + cimg::sqr((float)Gp-(float)G) + cimg::sqr((float)Bp-(float)B);
+ if (error<min) { min = error; best_index = off; Rbest = Rp; Gbest = Gp; Bbest = Bp; }
+ }
+ } else {
+ Rbest = (t)((unsigned char)R&0xe0); Gbest = (t)((unsigned char)G&0xe0); Bbest = (t)((unsigned char)B&0xc0);
+ best_index = (unsigned char)Rbest | ((unsigned char)Gbest>>3) | ((unsigned char)Bbest>>6);
+ }
+ if (indexing) *(pRd++) = (t)best_index; else { *(pRd++) = Rbest; *(pGd++) = Gbest; *(pBd++) = Bbest; }
+ if (dithering) { // apply dithering to neighborhood pixels if needed
+ const float dR = (float)(R-Rbest), dG = (float)(G-Gbest), dB = (float)(B-Bbest);
+ if (x<dimx()-1) { *(ptr1++)+= dR*7/16; *(ptr1++)+= dG*7/16; *(ptr1++)+= dB*7/16; ptr1-=3; }
+ if (y<dimy()-1) {
+ *(ptr2++)+= dR*5/16; *(ptr2++)+= dG*5/16; *ptr2+= dB*5/16; ptr2-=2;
+ if (x>0) { *(--ptr2)+= dB*3/16; *(--ptr2)+= dG*3/16; *(--ptr2)+= dR*3/16; ptr2+=3; }
+ if (x<dimx()-1) { ptr2+=3; *(ptr2++)+= dR/16; *(ptr2++)+= dG/16; *ptr2+= dB/16; ptr2-=5; }
+ }
+ }
+ ptr2+=3;
+ }
+ }
+ }
+ delete[] line1; delete[] line2;
+ return res;
+ }
+
+ //! Convert color pixels from (R,G,B) to match the default palette.
+ CImg<T>& RGBtoLUT(const bool dithering=true, const bool indexing=false) {
+ return get_RGBtoLUT(dithering,indexing).transfer_to(*this);
+ }
+
+ CImg<Tuchar> get_RGBtoLUT(const bool dithering=true, const bool indexing=false) const {
+ static const CImg<Tuchar> empty;
+ return get_RGBtoLUT(empty,dithering,indexing);
+ }
+
+ //! Convert an indexed image to a (R,G,B) image using the specified color palette.
+ CImg<T>& LUTtoRGB(const CImg<T>& palette) {
+ return get_LUTtoRGB(palette).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<t> get_LUTtoRGB(const CImg<t>& palette) const {
+ if (is_empty()) return CImg<t>();
+ if (dim!=1)
+ throw CImgInstanceException("CImg<%s>::LUTtoRGB() : Input image dimension is dim=%u, "
+ "should be a LUT image",
+ pixel_type(),dim);
+ if (palette.data && palette.dim!=3)
+ throw CImgArgumentException("CImg<%s>::LUTtoRGB() : Given palette dimension is dim=%u, "
+ "should be a (R,G,B) palette",
+ pixel_type(),palette.dim);
+ const CImg<t> pal = palette.data?palette:CImg<t>(default_LUT8());
+ CImg<t> res(width,height,depth,3);
+ const t *pRs = pal.ptr(0,0,0,0), *pGs = pal.ptr(0,0,0,1), *pBs = pal.ptr(0,0,0,2);
+ t *pRd = res.ptr(0,0,0,1), *pGd = pRd + width*height*depth, *pBd = pGd + width*height*depth;
+ const unsigned int Npal = palette.width*palette.height*palette.depth;
+ cimg_for(*this,ptr,T) {
+ const unsigned int index = ((unsigned int)*ptr)%Npal;
+ *(--pRd) = pRs[index]; *(--pGd) = pGs[index]; *(--pBd) = pBs[index];
+ }
+ return res;
+ }
+
+ //! Convert an indexed image (with the default palette) to a (R,G,B) image.
+ CImg<T>& LUTtoRGB() {
+ return get_LUTtoRGB().transfer_to(*this);
+ }
+
+ CImg<Tuchar> get_LUTtoRGB() const {
+ static const CImg<Tuchar> empty;
+ return get_LUTtoRGB(empty);
+ }
+
+ //! Convert color pixels from (R,G,B) to (H,S,V).
+ CImg<T>& RGBtoHSV() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoHSV() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image.",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ R = (Tfloat)*p1,
+ G = (Tfloat)*p2,
+ B = (Tfloat)*p3,
+ nR = (R<0?0:(R>255?255:R))/255,
+ nG = (G<0?0:(G>255?255:G))/255,
+ nB = (B<0?0:(B>255?255:B))/255,
+ m = cimg::min(nR,nG,nB),
+ M = cimg::max(nR,nG,nB);
+ Tfloat H = 0, S = 0;
+ if (M!=m) {
+ const Tfloat
+ f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
+ i = (Tfloat)((nR==m)?3:((nG==m)?5:1));
+ H = (i-f/(M-m));
+ if (H>=6) H-=6;
+ H*=60;
+ S = (M-m)/M;
+ }
+ *(p1++) = (T)H;
+ *(p2++) = (T)S;
+ *(p3++) = (T)M;
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_RGBtoHSV() const {
+ return CImg<Tfloat>(*this,false).RGBtoHSV();
+ }
+
+ //! Convert color pixels from (H,S,V) to (R,G,B).
+ CImg<T>& HSVtoRGB() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::HSVtoRGB() : Input image dimension is dim=%u, "
+ "should be a (H,S,V) image",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ Tfloat
+ H = (Tfloat)*p1,
+ S = (Tfloat)*p2,
+ V = (Tfloat)*p3,
+ R = 0, G = 0, B = 0;
+ if (H==0 && S==0) R = G = B = V;
+ else {
+ H/=60;
+ const int i = (int)cimg_std::floor(H);
+ const Tfloat
+ f = (i&1)?(H-i):(1-H+i),
+ m = V*(1-S),
+ n = V*(1-S*f);
+ switch (i) {
+ case 6 :
+ case 0 : R = V; G = n; B = m; break;
+ case 1 : R = n; G = V; B = m; break;
+ case 2 : R = m; G = V; B = n; break;
+ case 3 : R = m; G = n; B = V; break;
+ case 4 : R = n; G = m; B = V; break;
+ case 5 : R = V; G = m; B = n; break;
+ }
+ }
+ R*=255; G*=255; B*=255;
+ *(p1++) = (T)(R<0?0:(R>255?255:R));
+ *(p2++) = (T)(G<0?0:(G>255?255:G));
+ *(p3++) = (T)(B<0?0:(B>255?255:B));
+ }
+ return *this;
+ }
+
+ CImg<Tuchar> get_HSVtoRGB() const {
+ return CImg<Tuchar>(*this,false).HSVtoRGB();
+ }
+
+ //! Convert color pixels from (R,G,B) to (H,S,L).
+ CImg<T>& RGBtoHSL() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoHSL() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image.",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ R = (Tfloat)*p1,
+ G = (Tfloat)*p2,
+ B = (Tfloat)*p3,
+ nR = (R<0?0:(R>255?255:R))/255,
+ nG = (G<0?0:(G>255?255:G))/255,
+ nB = (B<0?0:(B>255?255:B))/255,
+ m = cimg::min(nR,nG,nB),
+ M = cimg::max(nR,nG,nB),
+ L = (m+M)/2;
+ Tfloat H = 0, S = 0;
+ if (M==m) H = S = 0;
+ else {
+ const Tfloat
+ f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
+ i = (nR==m)?3.0f:((nG==m)?5.0f:1.0f);
+ H = (i-f/(M-m));
+ if (H>=6) H-=6;
+ H*=60;
+ S = (2*L<=1)?((M-m)/(M+m)):((M-m)/(2-M-m));
+ }
+ *(p1++) = (T)H;
+ *(p2++) = (T)S;
+ *(p3++) = (T)L;
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_RGBtoHSL() const {
+ return CImg< Tfloat>(*this,false).RGBtoHSL();
+ }
+
+ //! Convert color pixels from (H,S,L) to (R,G,B).
+ CImg<T>& HSLtoRGB() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::HSLtoRGB() : Input image dimension is dim=%u, "
+ "should be a (H,S,V) image",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ H = (Tfloat)*p1,
+ S = (Tfloat)*p2,
+ L = (Tfloat)*p3,
+ q = 2*L<1?L*(1+S):(L+S-L*S),
+ p = 2*L-q,
+ h = H/360,
+ tr = h + 1.0f/3,
+ tg = h,
+ tb = h - 1.0f/3,
+ ntr = tr<0?tr+1:(tr>1?tr-1:tr),
+ ntg = tg<0?tg+1:(tg>1?tg-1:tg),
+ ntb = tb<0?tb+1:(tb>1?tb-1:tb),
+ R = 255*(6*ntr<1?p+(q-p)*6*ntr:(2*ntr<1?q:(3*ntr<2?p+(q-p)*6*(2.0f/3-ntr):p))),
+ G = 255*(6*ntg<1?p+(q-p)*6*ntg:(2*ntg<1?q:(3*ntg<2?p+(q-p)*6*(2.0f/3-ntg):p))),
+ B = 255*(6*ntb<1?p+(q-p)*6*ntb:(2*ntb<1?q:(3*ntb<2?p+(q-p)*6*(2.0f/3-ntb):p)));
+ *(p1++) = (T)(R<0?0:(R>255?255:R));
+ *(p2++) = (T)(G<0?0:(G>255?255:G));
+ *(p3++) = (T)(B<0?0:(B>255?255:B));
+ }
+ return *this;
+ }
+
+ CImg<Tuchar> get_HSLtoRGB() const {
+ return CImg<Tuchar>(*this,false).HSLtoRGB();
+ }
+
+ //! Convert color pixels from (R,G,B) to (H,S,I).
+ //! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002.
+ CImg<T>& RGBtoHSI() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoHSI() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image.",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ R = (Tfloat)*p1,
+ G = (Tfloat)*p2,
+ B = (Tfloat)*p3,
+ nR = (R<0?0:(R>255?255:R))/255,
+ nG = (G<0?0:(G>255?255:G))/255,
+ nB = (B<0?0:(B>255?255:B))/255,
+ m = cimg::min(nR,nG,nB),
+ theta = (Tfloat)(cimg_std::acos(0.5f*((nR-nG)+(nR-nB))/cimg_std::sqrt(cimg_std::pow(nR-nG,2)+(nR-nB)*(nG-nB)))*180/cimg::valuePI),
+ sum = nR + nG + nB;
+ Tfloat H = 0, S = 0, I = 0;
+ if (theta>0) H = (nB<=nG)?theta:360-theta;
+ if (sum>0) S = 1 - 3/sum*m;
+ I = sum/3;
+ *(p1++) = (T)H;
+ *(p2++) = (T)S;
+ *(p3++) = (T)I;
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_RGBtoHSI() const {
+ return CImg<Tfloat>(*this,false).RGBtoHSI();
+ }
+
+ //! Convert color pixels from (H,S,I) to (R,G,B).
+ CImg<T>& HSItoRGB() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::HSItoRGB() : Input image dimension is dim=%u, "
+ "should be a (H,S,I) image",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ Tfloat
+ H = (Tfloat)*p1,
+ S = (Tfloat)*p2,
+ I = (Tfloat)*p3,
+ a = I*(1-S),
+ R = 0, G = 0, B = 0;
+ if (H<120) {
+ B = a;
+ R = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
+ G = 3*I-(R+B);
+ } else if (H<240) {
+ H-=120;
+ R = a;
+ G = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
+ B = 3*I-(R+G);
+ } else {
+ H-=240;
+ G = a;
+ B = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
+ R = 3*I-(G+B);
+ }
+ R*=255; G*=255; B*=255;
+ *(p1++) = (T)(R<0?0:(R>255?255:R));
+ *(p2++) = (T)(G<0?0:(G>255?255:G));
+ *(p3++) = (T)(B<0?0:(B>255?255:B));
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_HSItoRGB() const {
+ return CImg< Tuchar>(*this,false).HSItoRGB();
+ }
+
+ //! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8.
+ CImg<T>& RGBtoYCbCr() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoYCbCr() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ R = (Tfloat)*p1,
+ G = (Tfloat)*p2,
+ B = (Tfloat)*p3,
+ Y = (66*R + 129*G + 25*B + 128)/256 + 16,
+ Cb = (-38*R - 74*G + 112*B + 128)/256 + 128,
+ Cr = (112*R - 94*G - 18*B + 128)/256 + 128;
+ *(p1++) = (T)(Y<0?0:(Y>255?255:Y));
+ *(p2++) = (T)(Cb<0?0:(Cb>255?255:Cb));
+ *(p3++) = (T)(Cr<0?0:(Cr>255?255:Cr));
+ }
+ return *this;
+ }
+
+ CImg<Tuchar> get_RGBtoYCbCr() const {
+ return CImg<Tuchar>(*this,false).RGBtoYCbCr();
+ }
+
+ //! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8.
+ CImg<T>& YCbCrtoRGB() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::YCbCrtoRGB() : Input image dimension is dim=%u, "
+ "should be a (Y,Cb,Cr)_8 image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ Y = (Tfloat)*p1 - 16,
+ Cb = (Tfloat)*p2 - 128,
+ Cr = (Tfloat)*p3 - 128,
+ R = (298*Y + 409*Cr + 128)/256,
+ G = (298*Y - 100*Cb - 208*Cr + 128)/256,
+ B = (298*Y + 516*Cb + 128)/256;
+ *(p1++) = (T)(R<0?0:(R>255?255:R));
+ *(p2++) = (T)(G<0?0:(G>255?255:G));
+ *(p3++) = (T)(B<0?0:(B>255?255:B));
+ }
+ return *this;
+ }
+
+ CImg<Tuchar> get_YCbCrtoRGB() const {
+ return CImg<Tuchar>(*this,false).YCbCrtoRGB();
+ }
+
+ //! Convert color pixels from (R,G,B) to (Y,U,V).
+ CImg<T>& RGBtoYUV() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoYUV() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ R = (Tfloat)*p1/255,
+ G = (Tfloat)*p2/255,
+ B = (Tfloat)*p3/255,
+ Y = 0.299f*R + 0.587f*G + 0.114f*B;
+ *(p1++) = (T)Y;
+ *(p2++) = (T)(0.492f*(B-Y));
+ *(p3++) = (T)(0.877*(R-Y));
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_RGBtoYUV() const {
+ return CImg<Tfloat>(*this,false).RGBtoYUV();
+ }
+
+ //! Convert color pixels from (Y,U,V) to (R,G,B).
+ CImg<T>& YUVtoRGB() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::YUVtoRGB() : Input image dimension is dim=%u, "
+ "should be a (Y,U,V) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ Y = (Tfloat)*p1,
+ U = (Tfloat)*p2,
+ V = (Tfloat)*p3,
+ R = (Y + 1.140f*V)*255,
+ G = (Y - 0.395f*U - 0.581f*V)*255,
+ B = (Y + 2.032f*U)*255;
+ *(p1++) = (T)(R<0?0:(R>255?255:R));
+ *(p2++) = (T)(G<0?0:(G>255?255:G));
+ *(p3++) = (T)(B<0?0:(B>255?255:B));
+ }
+ return *this;
+ }
+
+ CImg<Tuchar> get_YUVtoRGB() const {
+ return CImg< Tuchar>(*this,false).YUVtoRGB();
+ }
+
+ //! Convert color pixels from (R,G,B) to (C,M,Y).
+ CImg<T>& RGBtoCMY() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoCMY() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ R = (Tfloat)*p1/255,
+ G = (Tfloat)*p2/255,
+ B = (Tfloat)*p3/255;
+ *(p1++) = (T)(1 - R);
+ *(p2++) = (T)(1 - G);
+ *(p3++) = (T)(1 - B);
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_RGBtoCMY() const {
+ return CImg<Tfloat>(*this,false).RGBtoCMY();
+ }
+
+ //! Convert (C,M,Y) pixels of a color image into the (R,G,B) color space.
+ CImg<T>& CMYtoRGB() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::CMYtoRGB() : Input image dimension is dim=%u, "
+ "should be a (C,M,Y) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ C = (Tfloat)*p1,
+ M = (Tfloat)*p2,
+ Y = (Tfloat)*p3,
+ R = 255*(1 - C),
+ G = 255*(1 - M),
+ B = 255*(1 - Y);
+ *(p1++) = (T)(R<0?0:(R>255?255:R));
+ *(p2++) = (T)(G<0?0:(G>255?255:G));
+ *(p3++) = (T)(B<0?0:(B>255?255:B));
+ }
+ return *this;
+ }
+
+ CImg<Tuchar> get_CMYtoRGB() const {
+ return CImg<Tuchar>(*this,false).CMYtoRGB();
+ }
+
+ //! Convert color pixels from (C,M,Y) to (C,M,Y,K).
+ CImg<T>& CMYtoCMYK() {
+ return get_CMYtoCMYK().transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_CMYtoCMYK() const {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::CMYtoCMYK() : Input image dimension is dim=%u, "
+ "should be a (C,M,Y) image (dim=3)",
+ pixel_type(),dim);
+ CImg<Tfloat> res(width,height,depth,4);
+ const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2);
+ Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2), *pd4 = res.ptr(0,0,0,3);
+ for (unsigned long N = width*height*depth; N; --N) {
+ Tfloat
+ C = (Tfloat)*(ps1++),
+ M = (Tfloat)*(ps2++),
+ Y = (Tfloat)*(ps3++),
+ K = cimg::min(C,M,Y);
+ if (K==1) C = M = Y = 0;
+ else { const Tfloat K1 = 1 - K; C = (C - K)/K1; M = (M - K)/K1; Y = (Y - K)/K1; }
+ *(pd1++) = C;
+ *(pd2++) = M;
+ *(pd3++) = Y;
+ *(pd4++) = K;
+ }
+ return res;
+ }
+
+ //! Convert (C,M,Y,K) pixels of a color image into the (C,M,Y) color space.
+ CImg<T>& CMYKtoCMY() {
+ return get_CMYKtoCMY().transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_CMYKtoCMY() const {
+ if (is_empty()) return *this;
+ if (dim!=4)
+ throw CImgInstanceException("CImg<%s>::CMYKtoCMY() : Input image dimension is dim=%u, "
+ "should be a (C,M,Y,K) image (dim=4)",
+ pixel_type(),dim);
+ CImg<Tfloat> res(width,height,depth,3);
+ const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2), *ps4 = ptr(0,0,0,3);
+ Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ C = (Tfloat)*ps1,
+ M = (Tfloat)*ps2,
+ Y = (Tfloat)*ps3,
+ K = (Tfloat)*ps4,
+ K1 = 1 - K;
+ *(pd1++) = C*K1 + K;
+ *(pd2++) = M*K1 + K;
+ *(pd3++) = Y*K1 + K;
+ }
+ return res;
+ }
+
+ //! Convert color pixels from (R,G,B) to (X,Y,Z)_709.
+ CImg<T>& RGBtoXYZ() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoXYZ() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ R = (Tfloat)*p1/255,
+ G = (Tfloat)*p2/255,
+ B = (Tfloat)*p3/255;
+ *(p1++) = (T)(0.412453f*R + 0.357580f*G + 0.180423f*B);
+ *(p2++) = (T)(0.212671f*R + 0.715160f*G + 0.072169f*B);
+ *(p3++) = (T)(0.019334f*R + 0.119193f*G + 0.950227f*B);
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_RGBtoXYZ() const {
+ return CImg<Tfloat>(*this,false).RGBtoXYZ();
+ }
+
+ //! Convert (X,Y,Z)_709 pixels of a color image into the (R,G,B) color space.
+ CImg<T>& XYZtoRGB() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::XYZtoRGB() : Input image dimension is dim=%u, "
+ "should be a (X,Y,Z) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ X = (Tfloat)*p1*255,
+ Y = (Tfloat)*p2*255,
+ Z = (Tfloat)*p3*255,
+ R = 3.240479f*X - 1.537150f*Y - 0.498535f*Z,
+ G = -0.969256f*X + 1.875992f*Y + 0.041556f*Z,
+ B = 0.055648f*X - 0.204043f*Y + 1.057311f*Z;
+ *(p1++) = (T)(R<0?0:(R>255?255:R));
+ *(p2++) = (T)(G<0?0:(G>255?255:G));
+ *(p3++) = (T)(B<0?0:(B>255?255:B));
+ }
+ return *this;
+ }
+
+ CImg<Tuchar> get_XYZtoRGB() const {
+ return CImg<Tuchar>(*this,false).XYZtoRGB();
+ }
+
+ //! Convert (X,Y,Z)_709 pixels of a color image into the (L*,a*,b*) color space.
+ CImg<T>& XYZtoLab() {
+#define _cimg_Labf(x) ((x)>=0.008856f?(cimg_std::pow(x,(Tfloat)1/3)):(7.787f*(x)+16.0f/116))
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::XYZtoLab() : Input image dimension is dim=%u, "
+ "should be a (X,Y,Z) image (dim=3)",
+ pixel_type(),dim);
+ const Tfloat
+ Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
+ Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
+ Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ X = (Tfloat)*p1,
+ Y = (Tfloat)*p2,
+ Z = (Tfloat)*p3,
+ XXn = X/Xn, YYn = Y/Yn, ZZn = Z/Zn,
+ fX = (Tfloat)_cimg_Labf(XXn),
+ fY = (Tfloat)_cimg_Labf(YYn),
+ fZ = (Tfloat)_cimg_Labf(ZZn);
+ *(p1++) = (T)(116*fY - 16);
+ *(p2++) = (T)(500*(fX - fY));
+ *(p3++) = (T)(200*(fY - fZ));
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_XYZtoLab() const {
+ return CImg<Tfloat>(*this,false).XYZtoLab();
+ }
+
+ //! Convert (L,a,b) pixels of a color image into the (X,Y,Z) color space.
+ CImg<T>& LabtoXYZ() {
+#define _cimg_Labfi(x) ((x)>=0.206893f?((x)*(x)*(x)):(((x)-16.0f/116)/7.787f))
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::LabtoXYZ() : Input image dimension is dim=%u, "
+ "should be a (X,Y,Z) image (dim=3)",
+ pixel_type(),dim);
+ const Tfloat
+ Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
+ Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
+ Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ L = (Tfloat)*p1,
+ a = (Tfloat)*p2,
+ b = (Tfloat)*p3,
+ cY = (L + 16)/116,
+ Y = (Tfloat)(Yn*_cimg_Labfi(cY)),
+ pY = (Tfloat)cimg_std::pow(Y/Yn,(Tfloat)1/3),
+ cX = a/500 + pY,
+ X = Xn*cX*cX*cX,
+ cZ = pY - b/200,
+ Z = Zn*cZ*cZ*cZ;
+ *(p1++) = (T)(X);
+ *(p2++) = (T)(Y);
+ *(p3++) = (T)(Z);
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_LabtoXYZ() const {
+ return CImg<Tfloat>(*this,false).LabtoXYZ();
+ }
+
+ //! Convert (X,Y,Z)_709 pixels of a color image into the (x,y,Y) color space.
+ CImg<T>& XYZtoxyY() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::XYZtoxyY() : Input image dimension is dim=%u, "
+ "should be a (X,Y,Z) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ X = (Tfloat)*p1,
+ Y = (Tfloat)*p2,
+ Z = (Tfloat)*p3,
+ sum = (X+Y+Z),
+ nsum = sum>0?sum:1;
+ *(p1++) = (T)(X/nsum);
+ *(p2++) = (T)(Y/nsum);
+ *(p3++) = (T)Y;
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_XYZtoxyY() const {
+ return CImg<Tfloat>(*this,false).XYZtoxyY();
+ }
+
+ //! Convert (x,y,Y) pixels of a color image into the (X,Y,Z)_709 color space.
+ CImg<T>& xyYtoXYZ() {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::xyYtoXYZ() : Input image dimension is dim=%u, "
+ "should be a (x,y,Y) image (dim=3)",
+ pixel_type(),dim);
+ T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
+ for (unsigned long N = width*height*depth; N; --N) {
+ const Tfloat
+ px = (Tfloat)*p1,
+ py = (Tfloat)*p2,
+ Y = (Tfloat)*p3,
+ ny = py>0?py:1;
+ *(p1++) = (T)(px*Y/ny);
+ *(p2++) = (T)Y;
+ *(p3++) = (T)((1-px-py)*Y/ny);
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_xyYtoXYZ() const {
+ return CImg<Tfloat>(*this,false).xyYtoXYZ();
+ }
+
+ //! Convert a (R,G,B) image to a (L,a,b) one.
+ CImg<T>& RGBtoLab() {
+ return RGBtoXYZ().XYZtoLab();
+ }
+
+ CImg<Tfloat> get_RGBtoLab() const {
+ return CImg<Tfloat>(*this,false).RGBtoLab();
+ }
+
+ //! Convert a (L,a,b) image to a (R,G,B) one.
+ CImg<T>& LabtoRGB() {
+ return LabtoXYZ().XYZtoRGB();
+ }
+
+ CImg<Tuchar> get_LabtoRGB() const {
+ return CImg<Tuchar>(*this,false).LabtoRGB();
+ }
+
+ //! Convert a (R,G,B) image to a (x,y,Y) one.
+ CImg<T>& RGBtoxyY() {
+ return RGBtoXYZ().XYZtoxyY();
+ }
+
+ CImg<Tfloat> get_RGBtoxyY() const {
+ return CImg<Tfloat>(*this,false).RGBtoxyY();
+ }
+
+ //! Convert a (x,y,Y) image to a (R,G,B) one.
+ CImg<T>& xyYtoRGB() {
+ return xyYtoXYZ().XYZtoRGB();
+ }
+
+ CImg<Tuchar> get_xyYtoRGB() const {
+ return CImg<Tuchar>(*this,false).xyYtoRGB();
+ }
+
+ //! Convert a (R,G,B) image to a (C,M,Y,K) one.
+ CImg<T>& RGBtoCMYK() {
+ return RGBtoCMY().CMYtoCMYK();
+ }
+
+ CImg<Tfloat> get_RGBtoCMYK() const {
+ return CImg<Tfloat>(*this,false).RGBtoCMYK();
+ }
+
+ //! Convert a (C,M,Y,K) image to a (R,G,B) one.
+ CImg<T>& CMYKtoRGB() {
+ return CMYKtoCMY().CMYtoRGB();
+ }
+
+ CImg<Tuchar> get_CMYKtoRGB() const {
+ return CImg<Tuchar>(*this,false).CMYKtoRGB();
+ }
+
+ //! Convert a (R,G,B) image to a Bayer-coded representation.
+ /**
+ \note First (upper-left) pixel if the red component of the pixel color.
+ **/
+ CImg<T>& RGBtoBayer() {
+ return get_RGBtoBayer().transfer_to(*this);
+ }
+
+ CImg<T> get_RGBtoBayer() const {
+ if (is_empty()) return *this;
+ if (dim!=3)
+ throw CImgInstanceException("CImg<%s>::RGBtoBayer() : Input image dimension is dim=%u, "
+ "should be a (R,G,B) image (dim=3)",
+ pixel_type(),dim);
+ CImg<T> res(width,height,depth,1);
+ const T *pR = ptr(0,0,0,0), *pG = ptr(0,0,0,1), *pB = ptr(0,0,0,2);
+ T *ptrd = res.data;
+ cimg_forXYZ(*this,x,y,z) {
+ if (y%2) {
+ if (x%2) *(ptrd++) = *pB;
+ else *(ptrd++) = *pG;
+ } else {
+ if (x%2) *(ptrd++) = *pG;
+ else *(ptrd++) = *pR;
+ }
+ ++pR; ++pG; ++pB;
+ }
+ return res;
+ }
+
+ //! Convert a Bayer-coded image to a (R,G,B) color image.
+ CImg<T>& BayertoRGB(const unsigned int interpolation_type=3) {
+ return get_BayertoRGB(interpolation_type).transfer_to(*this);
+ }
+
+ CImg<Tuchar> get_BayertoRGB(const unsigned int interpolation_type=3) const {
+ if (is_empty()) return *this;
+ if (dim!=1)
+ throw CImgInstanceException("CImg<%s>::BayertoRGB() : Input image dimension is dim=%u, "
+ "should be a Bayer image (dim=1)",
+ pixel_type(),dim);
+ CImg<Tuchar> res(width,height,depth,3);
+ CImg_3x3(I,T);
+ Tuchar *pR = res.ptr(0,0,0,0), *pG = res.ptr(0,0,0,1), *pB = res.ptr(0,0,0,2);
+ switch (interpolation_type) {
+ case 3 : { // Edge-directed
+ CImg_3x3(R,T);
+ CImg_3x3(G,T);
+ CImg_3x3(B,T);
+ cimg_forXYZ(*this,x,y,z) {
+ const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
+ cimg_get3x3(*this,x,y,z,0,I);
+ if (y%2) {
+ if (x%2) {
+ const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
+ *pG = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
+ } else *pG = (Tuchar)Icc;
+ } else {
+ if (x%2) *pG = (Tuchar)Icc;
+ else {
+ const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
+ *pG = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
+ }
+ }
+ ++pG;
+ }
+ cimg_forXYZ(*this,x,y,z) {
+ const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
+ cimg_get3x3(*this,x,y,z,0,I);
+ cimg_get3x3(res,x,y,z,1,G);
+ if (y%2) {
+ if (x%2) *pB = (Tuchar)Icc;
+ else { *pR = (Tuchar)((Icn+Icp)/2); *pB = (Tuchar)((Inc+Ipc)/2); }
+ } else {
+ if (x%2) { *pR = (Tuchar)((Inc+Ipc)/2); *pB = (Tuchar)((Icn+Icp)/2); }
+ else *pR = (Tuchar)Icc;
+ }
+ ++pR; ++pB;
+ }
+ pR = res.ptr(0,0,0,0);
+ pG = res.ptr(0,0,0,1);
+ pB = res.ptr(0,0,0,2);
+ cimg_forXYZ(*this,x,y,z) {
+ const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
+ cimg_get3x3(res,x,y,z,0,R);
+ cimg_get3x3(res,x,y,z,1,G);
+ cimg_get3x3(res,x,y,z,2,B);
+ if (y%2) {
+ if (x%2) {
+ const float alpha = (float)cimg::sqr(Rnc-Rpc), beta = (float)cimg::sqr(Rcn-Rcp), cx = 1/(1+alpha), cy = 1/(1+beta);
+ *pR = (Tuchar)((cx*(Rnc+Rpc) + cy*(Rcn+Rcp))/(2*(cx+cy)));
+ }
+ } else {
+ if (!(x%2)) {
+ const float alpha = (float)cimg::sqr(Bnc-Bpc), beta = (float)cimg::sqr(Bcn-Bcp), cx = 1/(1+alpha), cy = 1/(1+beta);
+ *pB = (Tuchar)((cx*(Bnc+Bpc) + cy*(Bcn+Bcp))/(2*(cx+cy)));
+ }
+ }
+ ++pR; ++pG; ++pB;
+ }
+ } break;
+ case 2 : { // Linear interpolation
+ cimg_forXYZ(*this,x,y,z) {
+ const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
+ cimg_get3x3(*this,x,y,z,0,I);
+ if (y%2) {
+ if (x%2) { *pR = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); *pG = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *pB = (Tuchar)Icc; }
+ else { *pR = (Tuchar)((Icp+Icn)/2); *pG = (Tuchar)Icc; *pB = (Tuchar)((Inc+Ipc)/2); }
+ } else {
+ if (x%2) { *pR = (Tuchar)((Ipc+Inc)/2); *pG = (Tuchar)Icc; *pB = (Tuchar)((Icn+Icp)/2); }
+ else { *pR = (Tuchar)Icc; *pG = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *pB = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); }
+ }
+ ++pR; ++pG; ++pB;
+ }
+ } break;
+ case 1 : { // Nearest neighbor interpolation
+ cimg_forXYZ(*this,x,y,z) {
+ const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
+ cimg_get3x3(*this,x,y,z,0,I);
+ if (y%2) {
+ if (x%2) { *pR = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); *pG = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *pB = (Tuchar)Icc; }
+ else { *pR = (Tuchar)cimg::min(Icn,Icp); *pG = (Tuchar)Icc; *pB = (Tuchar)cimg::min(Inc,Ipc); }
+ } else {
+ if (x%2) { *pR = (Tuchar)cimg::min(Inc,Ipc); *pG = (Tuchar)Icc; *pB = (Tuchar)cimg::min(Icn,Icp); }
+ else { *pR = (Tuchar)Icc; *pG = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *pB = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); }
+ }
+ ++pR; ++pG; ++pB;
+ }
+ } break;
+ default : { // 0-filling interpolation
+ const T *ptrs = data;
+ res.fill(0);
+ cimg_forXYZ(*this,x,y,z) {
+ const T val = *(ptrs++);
+ if (y%2) { if (x%2) *pB = val; else *pG = val; } else { if (x%2) *pG = val; else *pR = val; }
+ ++pR; ++pG; ++pB;
+ }
+ }
+ }
+ return res;
+ }
+
+ //@}
+ //-------------------
+ //
+ //! \name Drawing
+ //@{
+ //-------------------
+
+ // The following _draw_scanline() routines are *non user-friendly functions*, used only for internal purpose.
+ // Pre-requisites : x0<x1, y-coordinate is valid, col is valid.
+ template<typename tc>
+ CImg<T>& _draw_scanline(const int x0, const int x1, const int y,
+ const tc *const color, const float opacity=1,
+ const float brightness=1, const bool init=false) {
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ static float nopacity = 0, copacity = 0;
+ static unsigned int whz = 0;
+ static const tc *col = 0;
+ if (init) {
+ nopacity = cimg::abs(opacity);
+ copacity = 1 - cimg::max(opacity,0);
+ whz = width*height*depth;
+ } else {
+ const int nx0 = x0>0?x0:0, nx1 = x1<dimx()?x1:dimx()-1, dx = nx1 - nx0;
+ if (dx>=0) {
+ col = color;
+ const unsigned int off = whz-dx-1;
+ T *ptrd = ptr(nx0,y);
+ if (opacity>=1) { // ** Opaque drawing **
+ if (brightness==1) { // Brightness==1
+ if (sizeof(T)!=1) cimg_forV(*this,k) {
+ const T val = (T)*(col++);
+ for (int x = dx; x>=0; --x) *(ptrd++) = val;
+ ptrd+=off;
+ } else cimg_forV(*this,k) {
+ const T val = (T)*(col++);
+ cimg_std::memset(ptrd,(int)val,dx+1);
+ ptrd+=whz;
+ }
+ } else if (brightness<1) { // Brightness<1
+ if (sizeof(T)!=1) cimg_forV(*this,k) {
+ const T val = (T)(*(col++)*brightness);
+ for (int x = dx; x>=0; --x) *(ptrd++) = val;
+ ptrd+=off;
+ } else cimg_forV(*this,k) {
+ const T val = (T)(*(col++)*brightness);
+ cimg_std::memset(ptrd,(int)val,dx+1);
+ ptrd+=whz;
+ }
+ } else { // Brightness>1
+ if (sizeof(T)!=1) cimg_forV(*this,k) {
+ const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+ for (int x = dx; x>=0; --x) *(ptrd++) = val;
+ ptrd+=off;
+ } else cimg_forV(*this,k) {
+ const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+ cimg_std::memset(ptrd,(int)val,dx+1);
+ ptrd+=whz;
+ }
+ }
+ } else { // ** Transparent drawing **
+ if (brightness==1) { // Brightness==1
+ cimg_forV(*this,k) {
+ const T val = (T)*(col++);
+ for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
+ ptrd+=off;
+ }
+ } else if (brightness<=1) { // Brightness<1
+ cimg_forV(*this,k) {
+ const T val = (T)(*(col++)*brightness);
+ for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
+ ptrd+=off;
+ }
+ } else { // Brightness>1
+ cimg_forV(*this,k) {
+ const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+ for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
+ ptrd+=off;
+ }
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ template<typename tc>
+ CImg<T>& _draw_scanline(const tc *const color, const float opacity=1) {
+ return _draw_scanline(0,0,0,color,opacity,0,true);
+ }
+
+ //! Draw a 2D colored point (pixel).
+ /**
+ \param x0 X-coordinate of the point.
+ \param y0 Y-coordinate of the point.
+ \param color Pointer to \c dimv() consecutive values, defining the color values.
+ \param opacity Drawing opacity (optional).
+ \note
+ - Clipping is supported.
+ - To set pixel values without clipping needs, you should use the faster CImg::operator()() function.
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0);
+ const unsigned char color[] = { 255,128,64 };
+ img.draw_point(50,50,color);
+ \endcode
+ **/
+ template<typename tc>
+ CImg<T>& draw_point(const int x0, const int y0,
+ const tc *const color, const float opacity=1) {
+ return draw_point(x0,y0,0,color,opacity);
+ }
+
+ //! Draw a 2D colored point (pixel).
+ template<typename tc>
+ CImg<T>& draw_point(const int x0, const int y0,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_point(x0,y0,color.data,opacity);
+ }
+
+ //! Draw a 3D colored point (voxel).
+ template<typename tc>
+ CImg<T>& draw_point(const int x0, const int y0, const int z0,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_point() : Specified color is (null)",
+ pixel_type());
+ if (x0>=0 && y0>=0 && z0>=0 && x0<dimx() && y0<dimy() && z0<dimz()) {
+ const unsigned int whz = width*height*depth;
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ T *ptrd = ptr(x0,y0,z0,0);
+ const tc *col = color;
+ if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
+ else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
+ }
+ return *this;
+ }
+
+ //! Draw a 3D colored point (voxel).
+ template<typename tc>
+ CImg<T>& draw_point(const int x0, const int y0, const int z0,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_point(x0,y0,z0,color.data,opacity);
+ }
+
+ // Draw a cloud of colored point (internal).
+ template<typename t, typename tc>
+ CImg<T>& _draw_point(const t& points, const unsigned int W, const unsigned int H,
+ const tc *const color, const float opacity) {
+ if (is_empty() || !points || !W) return *this;
+ switch (H) {
+ case 0 : case 1 :
+ throw CImgArgumentException("CImg<%s>::draw_point() : Given list of points is not valid.",
+ pixel_type());
+ case 2 : {
+ for (unsigned int i = 0; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1);
+ draw_point(x,y,color,opacity);
+ }
+ } break;
+ default : {
+ for (unsigned int i = 0; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
+ draw_point(x,y,z,color,opacity);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a cloud of colored points.
+ /**
+ \param points Coordinates of vertices, stored as a list of vectors.
+ \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity (optional).
+ \note
+ - This function uses several call to the single CImg::draw_point() procedure,
+ depending on the vectors size in \p points.
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0);
+ const unsigned char color[] = { 255,128,64 };
+ CImgList<int> points;
+ points.insert(CImg<int>::vector(0,0)).
+ .insert(CImg<int>::vector(70,10)).
+ .insert(CImg<int>::vector(80,60)).
+ .insert(CImg<int>::vector(10,90));
+ img.draw_point(points,color);
+ \endcode
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_point(const CImgList<t>& points,
+ const tc *const color, const float opacity=1) {
+ unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
+ return _draw_point(points,points.size,H,color,opacity);
+ }
+
+ //! Draw a cloud of colored points.
+ template<typename t, typename tc>
+ CImg<T>& draw_point(const CImgList<t>& points,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_point(points,color.data,opacity);
+ }
+
+ //! Draw a cloud of colored points.
+ /**
+ \note
+ - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
+ (sequence of vectors aligned along the x-axis).
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_point(const CImg<t>& points,
+ const tc *const color, const float opacity=1) {
+ return _draw_point(points,points.width,points.height,color,opacity);
+ }
+
+ //! Draw a cloud of colored points.
+ template<typename t, typename tc>
+ CImg<T>& draw_point(const CImg<t>& points,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_point(points,color.data,opacity);
+ }
+
+ //! Draw a 2D colored line.
+ /**
+ \param x0 X-coordinate of the starting line point.
+ \param y0 Y-coordinate of the starting line point.
+ \param x1 X-coordinate of the ending line point.
+ \param y1 Y-coordinate of the ending line point.
+ \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity (optional).
+ \param pattern An integer whose bits describe the line pattern (optional).
+ \param init_hatch Flag telling if a reinitialization of the hash state must be done (optional).
+ \note
+ - Clipping is supported.
+ - Line routine uses Bresenham's algorithm.
+ - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern.
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0);
+ const unsigned char color[] = { 255,128,64 };
+ img.draw_line(40,40,80,70,color);
+ \endcode
+ **/
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0,
+ const int x1, const int y1,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",
+ pixel_type());
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ if (xright<0 || xleft>=dimx()) return *this;
+ if (xleft<0) { yleft-=xleft*(yright - yleft)/(xright - xleft); xleft = 0; }
+ if (xright>=dimx()) { yright-=(xright - dimx())*(yright - yleft)/(xright - xleft); xright = dimx()-1; }
+ if (ydown<0 || yup>=dimy()) return *this;
+ if (yup<0) { xup-=yup*(xdown - xup)/(ydown - yup); yup = 0; }
+ if (ydown>=dimy()) { xdown-=(ydown - dimy())*(xdown - xup)/(ydown - yup); ydown = dimy()-1; }
+ T *ptrd0 = ptr(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const int
+ offx = (nx0<nx1?1:-1)*(steep?width:1),
+ offy = (ny0<ny1?1:-1)*(steep?1:width),
+ wh = width*height;
+ if (opacity>=1) {
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ if (pattern&hatch) { T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }}
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ if (pattern&hatch) {
+ T *ptrd = ptrd0; const tc* col = color;
+ cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D colored line.
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0,
+ const int x1, const int y1,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_line(x0,y0,x1,y1,color.data,opacity,pattern,init_hatch);
+ }
+
+ //! Draw a 2D colored line, with z-buffering.
+ template<typename tc>
+ CImg<T>& draw_line(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (!is_empty() && z0>0 && z1>0) {
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null).",
+ pixel_type());
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ float
+ Z0 = 1/z0, Z1 = 1/z1, nz0 = Z0, nz1 = Z1, dz = Z1 - Z0,
+ &zleft = xdir?nz0:nz1,
+ &zright = xdir?nz1:nz0,
+ &zup = ydir?nz0:nz1,
+ &zdown = ydir?nz1:nz0;
+ if (xright<0 || xleft>=dimx()) return *this;
+ if (xleft<0) {
+ const int D = xright - xleft;
+ yleft-=xleft*(yright - yleft)/D;
+ zleft-=xleft*(zright - zleft)/D;
+ xleft = 0;
+ }
+ if (xright>=dimx()) {
+ const int d = xright - dimx(), D = xright - xleft;
+ yright-=d*(yright - yleft)/D;
+ zright-=d*(zright - zleft)/D;
+ xright = dimx()-1;
+ }
+ if (ydown<0 || yup>=dimy()) return *this;
+ if (yup<0) {
+ const int D = ydown - yup;
+ xup-=yup*(xdown - xup)/D;
+ zup-=yup*(zdown - zup)/D;
+ yup = 0;
+ }
+ if (ydown>=dimy()) {
+ const int d = ydown - dimy(), D = ydown - yup;
+ xdown-=d*(xdown - xup)/D;
+ zdown-=d*(zdown - zup)/D;
+ ydown = dimy()-1;
+ }
+ T *ptrd0 = ptr(nx0,ny0);
+ float *ptrz = zbuffer + nx0 + ny0*width;
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const int
+ offx = (nx0<nx1?1:-1)*(steep?width:1),
+ offy = (ny0<ny1?1:-1)*(steep?1:width),
+ wh = width*height,
+ ndx = dx>0?dx:1;
+ if (opacity>=1) {
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz && pattern&hatch) {
+ *ptrz = z;
+ T *ptrd = ptrd0; const tc *col = color;
+ cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz) {
+ *ptrz = z;
+ T *ptrd = ptrd0; const tc *col = color;
+ cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
+ }
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz && pattern&hatch) {
+ *ptrz = z;
+ T *ptrd = ptrd0; const tc *col = color;
+ cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz) {
+ *ptrz = z;
+ T *ptrd = ptrd0; const tc *col = color;
+ cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+ }
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D colored line, with z-buffering.
+ template<typename tc>
+ CImg<T>& draw_line(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch);
+ }
+
+ //! Draw a 3D colored line.
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",
+ pixel_type());
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ int nx0 = x0, ny0 = y0, nz0 = z0, nx1 = x1, ny1 = y1, nz1 = z1;
+ if (nx0>nx1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+ if (nx1<0 || nx0>=dimx()) return *this;
+ if (nx0<0) { const int D = 1 + nx1 - nx0; ny0-=nx0*(1 + ny1 - ny0)/D; nz0-=nx0*(1 + nz1 - nz0)/D; nx0 = 0; }
+ if (nx1>=dimx()) { const int d = nx1-dimx(), D = 1 + nx1 - nx0; ny1+=d*(1 + ny0 - ny1)/D; nz1+=d*(1 + nz0 - nz1)/D; nx1 = dimx()-1; }
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+ if (ny1<0 || ny0>=dimy()) return *this;
+ if (ny0<0) { const int D = 1 + ny1 - ny0; nx0-=ny0*(1 + nx1 - nx0)/D; nz0-=ny0*(1 + nz1 - nz0)/D; ny0 = 0; }
+ if (ny1>=dimy()) { const int d = ny1-dimy(), D = 1 + ny1 - ny0; nx1+=d*(1 + nx0 - nx1)/D; nz1+=d*(1 + nz0 - nz1)/D; ny1 = dimy()-1; }
+ if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+ if (nz1<0 || nz0>=dimz()) return *this;
+ if (nz0<0) { const int D = 1 + nz1 - nz0; nx0-=nz0*(1 + nx1 - nx0)/D; ny0-=nz0*(1 + ny1 - ny0)/D; nz0 = 0; }
+ if (nz1>=dimz()) { const int d = nz1-dimz(), D = 1 + nz1 - nz0; nx1+=d*(1 + nx0 - nx1)/D; ny1+=d*(1 + ny0 - ny1)/D; nz1 = dimz()-1; }
+ const unsigned int dmax = cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0), whz = width*height*depth;
+ const float px = (nx1 - nx0)/(float)dmax, py = (ny1 - ny0)/(float)dmax, pz = (nz1 - nz0)/(float)dmax;
+ float x = (float)nx0, y = (float)ny0, z = (float)nz0;
+ if (opacity>=1) for (unsigned int t = 0; t<=dmax; ++t) {
+ if (!(~pattern) || (~pattern && pattern&hatch)) {
+ T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z);
+ const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
+ }
+ x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ for (unsigned int t = 0; t<=dmax; ++t) {
+ if (!(~pattern) || (~pattern && pattern&hatch)) {
+ T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z);
+ const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
+ }
+ x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 3D colored line.
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_line(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch);
+ }
+
+ //! Draw a 2D textured line.
+ /**
+ \param x0 X-coordinate of the starting line point.
+ \param y0 Y-coordinate of the starting line point.
+ \param x1 X-coordinate of the ending line point.
+ \param y1 Y-coordinate of the ending line point.
+ \param texture Texture image defining the pixel colors.
+ \param tx0 X-coordinate of the starting texture point.
+ \param ty0 Y-coordinate of the starting texture point.
+ \param tx1 X-coordinate of the ending texture point.
+ \param ty1 Y-coordinate of the ending texture point.
+ \param opacity Drawing opacity (optional).
+ \param pattern An integer whose bits describe the line pattern (optional).
+ \param init_hatch Flag telling if the hash variable must be reinitialized (optional).
+ \note
+ - Clipping is supported but not for texture coordinates.
+ - Line routine uses the well known Bresenham's algorithm.
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0), texture("texture256x256.ppm");
+ const unsigned char color[] = { 255,128,64 };
+ img.draw_line(40,40,80,70,texture,0,0,255,255);
+ \endcode
+ **/
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0,
+ const int x1, const int y1,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ dtx = tx1-tx0, dty = ty1-ty0,
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ tnx0 = tx0, tnx1 = tx1, tny0 = ty0, tny1 = ty1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1, &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1, &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0,
+ &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+ if (xright<0 || xleft>=dimx()) return *this;
+ if (xleft<0) {
+ const int D = xright - xleft;
+ yleft-=xleft*(yright - yleft)/D;
+ txleft-=xleft*(txright - txleft)/D;
+ tyleft-=xleft*(tyright - tyleft)/D;
+ xleft = 0;
+ }
+ if (xright>=dimx()) {
+ const int d = xright - dimx(), D = xright - xleft;
+ yright-=d*(yright - yleft)/D;
+ txright-=d*(txright - txleft)/D;
+ tyright-=d*(tyright - tyleft)/D;
+ xright = dimx()-1;
+ }
+ if (ydown<0 || yup>=dimy()) return *this;
+ if (yup<0) {
+ const int D = ydown - yup;
+ xup-=yup*(xdown - xup)/D;
+ txup-=yup*(txdown - txup)/D;
+ tyup-=yup*(tydown - tyup)/D;
+ yup = 0;
+ }
+ if (ydown>=dimy()) {
+ const int d = ydown - dimy(), D = ydown - yup;
+ xdown-=d*(xdown - xup)/D;
+ txdown-=d*(txdown - txup)/D;
+ tydown-=d*(tydown - tyup)/D;
+ ydown = dimy()-1;
+ }
+ T *ptrd0 = ptr(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const int
+ offx = (nx0<nx1?1:-1)*(steep?width:1),
+ offy = (ny0<ny1?1:-1)*(steep?1:width),
+ wh = width*height,
+ ndx = dx>0?dx:1;
+ if (opacity>=1) {
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ if (pattern&hatch) {
+ T *ptrd = ptrd0;
+ const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+ cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ T *ptrd = ptrd0;
+ const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+ cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ T *ptrd = ptrd0;
+ if (pattern&hatch) {
+ const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+ cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ T *ptrd = ptrd0;
+ const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+ cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D textured line, with perspective correction.
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty() && z0<=0 && z1<=0) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ float
+ Tx0 = tx0/z0, Tx1 = tx1/z1,
+ Ty0 = ty0/z0, Ty1 = ty1/z1,
+ Z0 = 1/z0, Z1 = 1/z1,
+ dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
+ tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
+ &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
+ &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+ &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
+ &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+ if (xright<0 || xleft>=dimx()) return *this;
+ if (xleft<0) {
+ const int D = xright - xleft;
+ yleft-=xleft*(yright - yleft)/D;
+ zleft-=xleft*(zright - zleft)/D;
+ txleft-=xleft*(txright - txleft)/D;
+ tyleft-=xleft*(tyright - tyleft)/D;
+ xleft = 0;
+ }
+ if (xright>=dimx()) {
+ const int d = xright - dimx(), D = xright - xleft;
+ yright-=d*(yright - yleft)/D;
+ zright-=d*(zright - zleft)/D;
+ txright-=d*(txright - txleft)/D;
+ tyright-=d*(tyright - tyleft)/D;
+ xright = dimx()-1;
+ }
+ if (ydown<0 || yup>=dimy()) return *this;
+ if (yup<0) {
+ const int D = ydown - yup;
+ xup-=yup*(xdown - xup)/D;
+ zup-=yup*(zdown - zup)/D;
+ txup-=yup*(txdown - txup)/D;
+ tyup-=yup*(tydown - tyup)/D;
+ yup = 0;
+ }
+ if (ydown>=dimy()) {
+ const int d = ydown - dimy(), D = ydown - yup;
+ xdown-=d*(xdown - xup)/D;
+ zdown-=d*(zdown - zup)/D;
+ txdown-=d*(txdown - txup)/D;
+ tydown-=d*(tydown - tyup)/D;
+ ydown = dimy()-1;
+ }
+ T *ptrd0 = ptr(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const int
+ offx = (nx0<nx1?1:-1)*(steep?width:1),
+ offy = (ny0<ny1?1:-1)*(steep?1:width),
+ wh = width*height,
+ ndx = dx>0?dx:1;
+ if (opacity>=1) {
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ if (pattern&hatch) {
+ const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ if (pattern&hatch) {
+ const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0;
+ cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D textured line, with z-buffering and perspective correction.
+ template<typename tc>
+ CImg<T>& draw_line(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (!is_empty() && z0>0 && z1>0) {
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ float
+ Tx0 = tx0/z0, Tx1 = tx1/z1,
+ Ty0 = ty0/z0, Ty1 = ty1/z1,
+ Z0 = 1/z0, Z1 = 1/z1,
+ dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
+ tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
+ &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
+ &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+ &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
+ &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+ if (xright<0 || xleft>=dimx()) return *this;
+ if (xleft<0) {
+ const int D = xright - xleft;
+ yleft-=xleft*(yright - yleft)/D;
+ zleft-=xleft*(zright - zleft)/D;
+ txleft-=xleft*(txright - txleft)/D;
+ tyleft-=xleft*(tyright - tyleft)/D;
+ xleft = 0;
+ }
+ if (xright>=dimx()) {
+ const int d = xright - dimx(), D = xright - xleft;
+ yright-=d*(yright - yleft)/D;
+ zright-=d*(zright - zleft)/D;
+ txright-=d*(txright - txleft)/D;
+ tyright-=d*(tyright - tyleft)/D;
+ xright = dimx()-1;
+ }
+ if (ydown<0 || yup>=dimy()) return *this;
+ if (yup<0) {
+ const int D = ydown - yup;
+ xup-=yup*(xdown - xup)/D;
+ zup-=yup*(zdown - zup)/D;
+ txup-=yup*(txdown - txup)/D;
+ tyup-=yup*(tydown - tyup)/D;
+ yup = 0;
+ }
+ if (ydown>=dimy()) {
+ const int d = ydown - dimy(), D = ydown - yup;
+ xdown-=d*(xdown - xup)/D;
+ zdown-=d*(zdown - zup)/D;
+ txdown-=d*(txdown - txup)/D;
+ tydown-=d*(tydown - tyup)/D;
+ ydown = dimy()-1;
+ }
+ T *ptrd0 = ptr(nx0,ny0);
+ float *ptrz = zbuffer + nx0 + ny0*width;
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const int
+ offx = (nx0<nx1?1:-1)*(steep?width:1),
+ offy = (ny0<ny1?1:-1)*(steep?1:width),
+ wh = width*height,
+ ndx = dx>0?dx:1;
+ if (opacity>=1) {
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ if (pattern&hatch) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz) {
+ *ptrz = z;
+ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
+ }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz) {
+ *ptrz = z;
+ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
+ }
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ if (pattern&hatch) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz) {
+ *ptrz = z;
+ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
+ }
+ }
+ hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ const float z = Z0 + x*dz/ndx;
+ if (z>*ptrz) {
+ *ptrz = z;
+ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+ T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
+ }
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offx; error+=dx; }
+ }
+ }
+ }
+ return *this;
+ }
+
+ // Inner routine for drawing set of consecutive lines with generic type for coordinates.
+ template<typename t, typename tc>
+ CImg<T>& _draw_line(const t& points, const unsigned int W, const unsigned int H,
+ const tc *const color, const float opacity,
+ const unsigned int pattern, const bool init_hatch) {
+ if (is_empty() || !points || W<2) return *this;
+ bool ninit_hatch = init_hatch;
+ switch (H) {
+ case 0 : case 1 :
+ throw CImgArgumentException("CImg<%s>::draw_line() : Given list of points is not valid.",
+ pixel_type());
+ case 2 : {
+ const int x0 = (int)points(0,0), y0 = (int)points(0,1);
+ int ox = x0, oy = y0;
+ for (unsigned int i = 1; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1);
+ draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = x; oy = y;
+ }
+ } break;
+ default : {
+ const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
+ int ox = x0, oy = y0, oz = z0;
+ for (unsigned int i = 1; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
+ draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = x; oy = y; oz = z;
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a set of consecutive colored lines in the instance image.
+ /**
+ \param points Coordinates of vertices, stored as a list of vectors.
+ \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity (optional).
+ \param pattern An integer whose bits describe the line pattern (optional).
+ \param init_hatch If set to true, init hatch motif.
+ \note
+ - This function uses several call to the single CImg::draw_line() procedure,
+ depending on the vectors size in \p points.
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0);
+ const unsigned char color[] = { 255,128,64 };
+ CImgList<int> points;
+ points.insert(CImg<int>::vector(0,0)).
+ .insert(CImg<int>::vector(70,10)).
+ .insert(CImg<int>::vector(80,60)).
+ .insert(CImg<int>::vector(10,90));
+ img.draw_line(points,color);
+ \endcode
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_line(const CImgList<t>& points,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
+ return _draw_line(points,points.size,H,color,opacity,pattern,init_hatch);
+ }
+
+ //! Draw a set of consecutive colored lines in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_line(const CImgList<t>& points,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_line(points,color.data,opacity,pattern,init_hatch);
+ }
+
+ //! Draw a set of consecutive colored lines in the instance image.
+ /**
+ \note
+ - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
+ (sequence of vectors aligned along the x-axis).
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_line(const CImg<t>& points,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return _draw_line(points,points.width,points.height,color,opacity,pattern,init_hatch);
+ }
+
+ //! Draw a set of consecutive colored lines in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_line(const CImg<t>& points,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_line(points,color.data,opacity,pattern,init_hatch);
+ }
+
+ // Inner routine for a drawing filled polygon with generic type for coordinates.
+ template<typename t, typename tc>
+ CImg<T>& _draw_polygon(const t& points, const unsigned int N,
+ const tc *const color, const float opacity) {
+ if (is_empty() || !points || N<3) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_polygon() : Specified color is (null).",
+ pixel_type());
+ _draw_scanline(color,opacity);
+ int xmin = (int)(~0U>>1), xmax = 0, ymin = (int)(~0U>>1), ymax = 0;
+ { for (unsigned int p = 0; p<N; ++p) {
+ const int x = (int)points(p,0), y = (int)points(p,1);
+ if (x<xmin) xmin = x;
+ if (x>xmax) xmax = x;
+ if (y<ymin) ymin = y;
+ if (y>ymax) ymax = y;
+ }}
+ if (xmax<0 || xmin>=dimx() || ymax<0 || ymin>=dimy()) return *this;
+ const unsigned int
+ nymin = ymin<0?0:(unsigned int)ymin,
+ nymax = ymax>=dimy()?height-1:(unsigned int)ymax,
+ dy = 1 + nymax - nymin;
+ CImg<intT> X(1+2*N,dy,1,1,0), tmp;
+ int cx = (int)points(0,0), cy = (int)points(0,1);
+ for (unsigned int cp = 0, p = 0; p<N; ++p) {
+ const unsigned int np = (p!=N-1)?p+1:0, ap = (np!=N-1)?np+1:0;
+ const int
+ nx = (int)points(np,0), ny = (int)points(np,1), ay = (int)points(ap,1),
+ y0 = cy - nymin, y1 = ny - nymin;
+ if (y0!=y1) {
+ const int countermin = ((ny<ay && cy<ny) || (ny>ay && cy>ny))?1:0;
+ for (int x = cx, y = y0, _sx = 1, _sy = 1,
+ _dx = nx>cx?nx-cx:((_sx=-1),cx-nx),
+ _dy = y1>y0?y1-y0:((_sy=-1),y0-y1),
+ _counter = ((_dx-=_dy?_dy*(_dx/_dy):0),_dy),
+ _err = _dx>>1,
+ _rx = _dy?(nx-cx)/_dy:0;
+ _counter>=countermin;
+ --_counter, y+=_sy, x+=_rx + ((_err-=_dx)<0?_err+=_dy,_sx:0))
+ if (y>=0 && y<(int)dy) X(++X(0,y),y) = x;
+ cp = np; cx = nx; cy = ny;
+ } else {
+ const int pp = (cp?cp-1:N-1), py = (int)points(pp,1);
+ if ((cy>py && ay>cy) || (cy<py && ay<cy)) X(++X(0,y0),y0) = nx;
+ if (cy!=ay) { cp = np; cx = nx; cy = ny; }
+ }
+ }
+ for (int y = 0; y<(int)dy; ++y) {
+ tmp.assign(X.ptr(1,y),X(0,y),1,1,1,true).sort();
+ for (int i = 1; i<=X(0,y); ) {
+ const int xb = X(i++,y), xe = X(i++,y);
+ _draw_scanline(xb,xe,nymin+y,color,opacity);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a filled polygon in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImgList<t>& points,
+ const tc *const color, const float opacity=1) {
+ if (!points.is_sameY(2))
+ throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
+ pixel_type());
+ return _draw_polygon(points,points.size,color,opacity);
+ }
+
+ //! Draw a filled polygon in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImgList<t>& points,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_polygon(points,color.data,opacity);
+ }
+
+ //! Draw a filled polygon in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImg<t>& points,
+ const tc *const color, const float opacity=1) {
+ if (points.height<2)
+ throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
+ pixel_type());
+ return _draw_polygon(points,points.width,color,opacity);
+ }
+
+ //! Draw a filled polygon in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImg<t>& points,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_polygon(points,color.data,opacity);
+ }
+
+ // Inner routine for drawing an outlined polygon with generic point coordinates.
+ template<typename t, typename tc>
+ CImg<T>& _draw_polygon(const t& points, const unsigned int W, const unsigned int H,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ if (is_empty() || !points || W<3) return *this;
+ bool ninit_hatch = true;
+ switch (H) {
+ case 0 : case 1 :
+ throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
+ pixel_type());
+ case 2 : {
+ const int x0 = (int)points(0,0), y0 = (int)points(0,1);
+ int ox = x0, oy = y0;
+ for (unsigned int i = 1; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1);
+ draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = x; oy = y;
+ }
+ draw_line(ox,oy,x0,y0,color,opacity,pattern,false);
+ } break;
+ default : {
+ const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
+ int ox = x0, oy = y0, oz = z0;
+ for (unsigned int i = 1; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
+ draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = x; oy = y; oz = z;
+ }
+ draw_line(ox,oy,oz,x0,y0,z0,color,opacity,pattern,false);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a polygon outline.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImgList<t>& points,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
+ return _draw_polygon(points,points.size,H,color,opacity,pattern);
+ }
+
+ //! Draw a polygon outline.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImgList<t>& points,
+ const CImg<tc>& color, const float opacity,
+ const unsigned int pattern) {
+ return draw_polygon(points,color.data,opacity,pattern);
+ }
+
+ //! Draw a polygon outline.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImg<t>& points,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ return _draw_polygon(points,points.width,points.height,color,opacity,pattern);
+ }
+
+ //! Draw a polygon outline.
+ template<typename t, typename tc>
+ CImg<T>& draw_polygon(const CImg<t>& points,
+ const CImg<tc>& color, const float opacity,
+ const unsigned int pattern) {
+ return draw_polygon(points,color.data,opacity,pattern);
+ }
+
+ //! Draw a cubic spline curve in the instance image.
+ /**
+ \param x0 X-coordinate of the starting curve point
+ \param y0 Y-coordinate of the starting curve point
+ \param u0 X-coordinate of the starting velocity
+ \param v0 Y-coordinate of the starting velocity
+ \param x1 X-coordinate of the ending curve point
+ \param y1 Y-coordinate of the ending curve point
+ \param u1 X-coordinate of the ending velocity
+ \param v1 Y-coordinate of the ending velocity
+ \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
+ \param precision Curve drawing precision (optional).
+ \param opacity Drawing opacity (optional).
+ \param pattern An integer whose bits describe the line pattern (optional).
+ \param init_hatch If \c true, init hatch motif.
+ \note
+ - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points
+ and corresponding velocity vectors.
+ - The spline is drawn as a serie of connected segments. The \p precision parameter sets the
+ average number of pixels in each drawn segment.
+ - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), (\p xb,\p yb), (\p x1,\p y1) }
+ where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point and (\p xa,\p ya), (\p xb,\p yb) are two
+ \e control points.
+ The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from the control points as
+ \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb).
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0);
+ const unsigned char color[] = { 255,255,255 };
+ img.draw_spline(30,30,0,100,90,40,0,-100,color);
+ \endcode
+ **/
+ template<typename tc>
+ CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
+ const int x1, const int y1, const float u1, const float v1,
+ const tc *const color, const float opacity=1,
+ const float precision=4, const unsigned int pattern=~0U,
+ const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",
+ pixel_type());
+ bool ninit_hatch = init_hatch;
+ const float
+ dx = (float)(x1 - x0),
+ dy = (float)(y1 - y0),
+ dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
+ ax = -2*dx + u0 + u1,
+ bx = 3*dx - 2*u0 - u1,
+ ay = -2*dy + v0 + v1,
+ by = 3*dy - 2*v0 - v1,
+ xprecision = dmax>0?precision/dmax:1.0f,
+ tmax = 1 + (dmax>0?xprecision:0.0f);
+ int ox = x0, oy = y0;
+ for (float t = 0; t<tmax; t+=xprecision) {
+ const float
+ t2 = t*t,
+ t3 = t2*t;
+ const int
+ nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
+ ny = (int)(ay*t3 + by*t2 + v0*t + y0);
+ draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = nx; oy = ny;
+ }
+ return *this;
+ }
+
+ //! Draw a cubic spline curve in the instance image.
+ template<typename tc>
+ CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
+ const int x1, const int y1, const float u1, const float v1,
+ const CImg<tc>& color, const float opacity=1,
+ const float precision=4, const unsigned int pattern=~0U,
+ const bool init_hatch=true) {
+ return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,color.data,opacity,precision,pattern,init_hatch);
+ }
+
+ //! Draw a cubic spline curve in the instance image (for volumetric images).
+ /**
+ \note
+ - Similar to CImg::draw_spline() for a 3D spline in a volumetric image.
+ **/
+ template<typename tc>
+ CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
+ const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
+ const tc *const color, const float opacity=1,
+ const float precision=4, const unsigned int pattern=~0U,
+ const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",
+ pixel_type());
+ bool ninit_hatch = init_hatch;
+ const float
+ dx = (float)(x1 - x0),
+ dy = (float)(y1 - y0),
+ dz = (float)(z1 - z0),
+ dmax = cimg::max(cimg::abs(dx),cimg::abs(dy),cimg::abs(dz)),
+ ax = -2*dx + u0 + u1,
+ bx = 3*dx - 2*u0 - u1,
+ ay = -2*dy + v0 + v1,
+ by = 3*dy - 2*v0 - v1,
+ az = -2*dz + w0 + w1,
+ bz = 3*dz - 2*w0 - w1,
+ xprecision = dmax>0?precision/dmax:1.0f,
+ tmax = 1 + (dmax>0?xprecision:0.0f);
+ int ox = x0, oy = y0, oz = z0;
+ for (float t = 0; t<tmax; t+=xprecision) {
+ const float
+ t2 = t*t,
+ t3 = t2*t;
+ const int
+ nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
+ ny = (int)(ay*t3 + by*t2 + v0*t + y0),
+ nz = (int)(az*t3 + bz*t2 + w0*t + z0);
+ draw_line(ox,oy,oz,nx,ny,nz,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = nx; oy = ny; oz = nz;
+ }
+ return *this;
+ }
+
+ //! Draw a cubic spline curve in the instance image (for volumetric images).
+ template<typename tc>
+ CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
+ const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
+ const CImg<tc>& color, const float opacity=1,
+ const float precision=4, const unsigned int pattern=~0U,
+ const bool init_hatch=true) {
+ return draw_spline(x0,y0,z0,u0,v0,w0,x1,y1,z1,u1,v1,w1,color.data,opacity,precision,pattern,init_hatch);
+ }
+
+ //! Draw a cubic spline curve in the instance image.
+ /**
+ \param x0 X-coordinate of the starting curve point
+ \param y0 Y-coordinate of the starting curve point
+ \param u0 X-coordinate of the starting velocity
+ \param v0 Y-coordinate of the starting velocity
+ \param x1 X-coordinate of the ending curve point
+ \param y1 Y-coordinate of the ending curve point
+ \param u1 X-coordinate of the ending velocity
+ \param v1 Y-coordinate of the ending velocity
+ \param texture Texture image defining line pixel colors.
+ \param tx0 X-coordinate of the starting texture point.
+ \param ty0 Y-coordinate of the starting texture point.
+ \param tx1 X-coordinate of the ending texture point.
+ \param ty1 Y-coordinate of the ending texture point.
+ \param precision Curve drawing precision (optional).
+ \param opacity Drawing opacity (optional).
+ \param pattern An integer whose bits describe the line pattern (optional).
+ \param init_hatch if \c true, reinit hatch motif.
+ **/
+ template<typename t>
+ CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
+ const int x1, const int y1, const float u1, const float v1,
+ const CImg<t>& texture,
+ const int tx0, const int ty0, const int tx1, const int ty1,
+ const float opacity=1,
+ const float precision=4, const unsigned int pattern=~0U,
+ const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch);
+ bool ninit_hatch = true;
+ const float
+ dx = (float)(x1 - x0),
+ dy = (float)(y1 - y0),
+ dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
+ ax = -2*dx + u0 + u1,
+ bx = 3*dx - 2*u0 - u1,
+ ay = -2*dy + v0 + v1,
+ by = 3*dy - 2*v0 - v1,
+ xprecision = dmax>0?precision/dmax:1.0f,
+ tmax = 1 + (dmax>0?xprecision:0.0f);
+ int ox = x0, oy = y0, otx = tx0, oty = ty0;
+ for (float t1 = 0; t1<tmax; t1+=xprecision) {
+ const float
+ t2 = t1*t1,
+ t3 = t2*t1;
+ const int
+ nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0),
+ ny = (int)(ay*t3 + by*t2 + v0*t1 + y0),
+ ntx = tx0 + (int)((tx1-tx0)*t1/tmax),
+ nty = ty0 + (int)((ty1-ty0)*t1/tmax);
+ draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = nx; oy = ny; otx = ntx; oty = nty;
+ }
+ return *this;
+ }
+
+ // Draw a set of connected spline curves in the instance image (internal).
+ template<typename tp, typename tt, typename tc>
+ CImg<T>& _draw_spline(const tp& points, const tt& tangents, const unsigned int W, const unsigned int H,
+ const tc *const color, const float opacity,
+ const bool close_set, const float precision,
+ const unsigned int pattern, const bool init_hatch) {
+ if (is_empty() || !points || !tangents || W<2) return *this;
+ bool ninit_hatch = init_hatch;
+ switch (H) {
+ case 0 : case 1 :
+ throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.",
+ pixel_type());
+ case 2 : {
+ const int x0 = (int)points(0,0), y0 = (int)points(0,1);
+ const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1);
+ int ox = x0, oy = y0;
+ float ou = u0, ov = v0;
+ for (unsigned int i = 1; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1);
+ const float u = (float)tangents(i,0), v = (float)tangents(i,1);
+ draw_spline(ox,oy,ou,ov,x,y,u,v,color,precision,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = x; oy = y; ou = u; ov = v;
+ }
+ if (close_set) draw_spline(ox,oy,ou,ov,x0,y0,u0,v0,color,precision,opacity,pattern,false);
+ } break;
+ default : {
+ const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
+ const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1), w0 = (float)tangents(0,2);
+ int ox = x0, oy = y0, oz = z0;
+ float ou = u0, ov = v0, ow = w0;
+ for (unsigned int i = 1; i<W; ++i) {
+ const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
+ const float u = (float)tangents(i,0), v = (float)tangents(i,1), w = (float)tangents(i,2);
+ draw_spline(ox,oy,oz,ou,ov,ow,x,y,z,u,v,w,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = x; oy = y; oz = z; ou = u; ov = v; ow = w;
+ }
+ if (close_set) draw_spline(ox,oy,oz,ou,ov,ow,x0,y0,z0,u0,v0,w0,color,precision,opacity,pattern,false);
+ }
+ }
+ return *this;
+ }
+
+ // Draw a set of connected spline curves in the instance image (internal).
+ template<typename tp, typename tc>
+ CImg<T>& _draw_spline(const tp& points, const unsigned int W, const unsigned int H,
+ const tc *const color, const float opacity,
+ const bool close_set, const float precision,
+ const unsigned int pattern, const bool init_hatch) {
+ if (is_empty() || !points || W<2) return *this;
+ CImg<Tfloat> tangents;
+ switch (H) {
+ case 0 : case 1 :
+ throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.",
+ pixel_type());
+ case 2 : {
+ tangents.assign(W,H);
+ for (unsigned int p = 0; p<W; ++p) {
+ const unsigned int
+ p0 = close_set?(p+W-1)%W:(p?p-1:0),
+ p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
+ const float
+ x = (float)points(p,0),
+ y = (float)points(p,1),
+ x0 = (float)points(p0,0),
+ y0 = (float)points(p0,1),
+ x1 = (float)points(p1,0),
+ y1 = (float)points(p1,1),
+ u0 = x - x0,
+ v0 = y - y0,
+ n0 = 1e-8f + (float)cimg_std::sqrt(u0*u0 + v0*v0),
+ u1 = x1 - x,
+ v1 = y1 - y,
+ n1 = 1e-8f + (float)cimg_std::sqrt(u1*u1 + v1*v1),
+ u = u0/n0 + u1/n1,
+ v = v0/n0 + v1/n1,
+ n = 1e-8f + (float)cimg_std::sqrt(u*u + v*v),
+ fact = 0.5f*(n0 + n1);
+ tangents(p,0) = (Tfloat)(fact*u/n);
+ tangents(p,1) = (Tfloat)(fact*v/n);
+ }
+ } break;
+ default : {
+ tangents.assign(W,H);
+ for (unsigned int p = 0; p<W; ++p) {
+ const unsigned int
+ p0 = close_set?(p+W-1)%W:(p?p-1:0),
+ p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
+ const float
+ x = (float)points(p,0),
+ y = (float)points(p,1),
+ z = (float)points(p,2),
+ x0 = (float)points(p0,0),
+ y0 = (float)points(p0,1),
+ z0 = (float)points(p0,2),
+ x1 = (float)points(p1,0),
+ y1 = (float)points(p1,1),
+ z1 = (float)points(p1,2),
+ u0 = x - x0,
+ v0 = y - y0,
+ w0 = z - z0,
+ n0 = 1e-8f + (float)cimg_std::sqrt(u0*u0 + v0*v0 + w0*w0),
+ u1 = x1 - x,
+ v1 = y1 - y,
+ w1 = z1 - z,
+ n1 = 1e-8f + (float)cimg_std::sqrt(u1*u1 + v1*v1 + w1*w1),
+ u = u0/n0 + u1/n1,
+ v = v0/n0 + v1/n1,
+ w = w0/n0 + w1/n1,
+ n = 1e-8f + (float)cimg_std::sqrt(u*u + v*v + w*w),
+ fact = 0.5f*(n0 + n1);
+ tangents(p,0) = (Tfloat)(fact*u/n);
+ tangents(p,1) = (Tfloat)(fact*v/n);
+ tangents(p,2) = (Tfloat)(fact*w/n);
+ }
+ }
+ }
+ return _draw_spline(points,tangents,W,H,color,opacity,close_set,precision,pattern,init_hatch);
+ }
+
+ //! Draw a set of consecutive colored splines in the instance image.
+ template<typename tp, typename tt, typename tc>
+ CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
+ const tc *const color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()),(unsigned int)(tangents[p].size()));
+ return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.size,H);
+ }
+
+ //! Draw a set of consecutive colored splines in the instance image.
+ template<typename tp, typename tt, typename tc>
+ CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
+ const CImg<tc>& color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch);
+ }
+
+ //! Draw a set of consecutive colored splines in the instance image.
+ template<typename tp, typename tt, typename tc>
+ CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
+ const tc *const color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height);
+ }
+
+ //! Draw a set of consecutive colored splines in the instance image.
+ template<typename tp, typename tt, typename tc>
+ CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
+ const CImg<tc>& color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch);
+ }
+
+ //! Draw a set of consecutive colored splines in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_spline(const CImgList<t>& points,
+ const tc *const color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ unsigned int H = ~0U;
+ cimglist_for(points,p) { const unsigned int s = points[p].size(); if (s<H) H = s; }
+ return _draw_spline(points,color,opacity,close_set,precision,pattern,init_hatch,points.size,H);
+ }
+
+ //! Draw a set of consecutive colored splines in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_spline(const CImgList<t>& points,
+ CImg<tc>& color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch);
+ }
+
+ //! Draw a set of consecutive colored lines in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_spline(const CImg<t>& points,
+ const tc *const color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return _draw_spline(points,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height);
+ }
+
+ //! Draw a set of consecutive colored lines in the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_spline(const CImg<t>& points,
+ const CImg<tc>& color, const float opacity=1,
+ const bool close_set=false, const float precision=4,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch);
+ }
+
+ //! Draw a colored arrow in the instance image.
+ /**
+ \param x0 X-coordinate of the starting arrow point (tail).
+ \param y0 Y-coordinate of the starting arrow point (tail).
+ \param x1 X-coordinate of the ending arrow point (head).
+ \param y1 Y-coordinate of the ending arrow point (head).
+ \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
+ \param angle Aperture angle of the arrow head (optional).
+ \param length Length of the arrow head. If negative, describes a percentage of the arrow length (optional).
+ \param opacity Drawing opacity (optional).
+ \param pattern An integer whose bits describe the line pattern (optional).
+ \note
+ - Clipping is supported.
+ **/
+ template<typename tc>
+ CImg<T>& draw_arrow(const int x0, const int y0,
+ const int x1, const int y1,
+ const tc *const color, const float opacity=1,
+ const float angle=30, const float length=-10,
+ const unsigned int pattern=~0U) {
+ if (is_empty()) return *this;
+ const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v,
+ deg = (float)(angle*cimg::valuePI/180), ang = (sq>0)?(float)cimg_std::atan2(v,u):0.0f,
+ l = (length>=0)?length:-length*(float)cimg_std::sqrt(sq)/100;
+ if (sq>0) {
+ const float
+ cl = (float)cimg_std::cos(ang - deg), sl = (float)cimg_std::sin(ang - deg),
+ cr = (float)cimg_std::cos(ang + deg), sr = (float)cimg_std::sin(ang + deg);
+ const int
+ xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl),
+ xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr),
+ xc = x1 + (int)((l+1)*(cl+cr))/2, yc = y1 + (int)((l+1)*(sl+sr))/2;
+ draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity);
+ } else draw_point(x0,y0,color,opacity);
+ return *this;
+ }
+
+ //! Draw a colored arrow in the instance image.
+ template<typename tc>
+ CImg<T>& draw_arrow(const int x0, const int y0,
+ const int x1, const int y1,
+ const CImg<tc>& color, const float opacity=1,
+ const float angle=30, const float length=-10,
+ const unsigned int pattern=~0U) {
+ return draw_arrow(x0,y0,x1,y1,color.data,opacity,angle,length,pattern);
+ }
+
+ //! Draw an image.
+ /**
+ \param sprite Sprite image.
+ \param x0 X-coordinate of the sprite position.
+ \param y0 Y-coordinate of the sprite position.
+ \param z0 Z-coordinate of the sprite position.
+ \param v0 V-coordinate of the sprite position.
+ \param opacity Drawing opacity (optional).
+ \note
+ - Clipping is supported.
+ **/
+ template<typename t>
+ CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
+ const CImg<t>& sprite, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!sprite)
+ throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
+ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity);
+ const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
+ const int
+ lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
+ lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
+ lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
+ lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
+ const t
+ *ptrs = sprite.data -
+ (bx?x0:0) -
+ (by?y0*sprite.dimx():0) -
+ (bz?z0*sprite.dimx()*sprite.dimy():0) -
+ (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
+ const unsigned int
+ offX = width - lX, soffX = sprite.width - lX,
+ offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY),
+ offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ if (lX>0 && lY>0 && lZ>0 && lV>0) {
+ T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
+ for (int v = 0; v<lV; ++v) {
+ for (int z = 0; z<lZ; ++z) {
+ for (int y = 0; y<lY; ++y) {
+ if (opacity>=1) for (int x = 0; x<lX; ++x) *(ptrd++) = (T)*(ptrs++);
+ else for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
+ ptrd+=offX; ptrs+=soffX;
+ }
+ ptrd+=offY; ptrs+=soffY;
+ }
+ ptrd+=offZ; ptrs+=soffZ;
+ }
+ }
+ return *this;
+ }
+
+#ifndef cimg_use_visualcpp6
+ // Otimized version (internal).
+ CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
+ const CImg<T>& sprite, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!sprite)
+ throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
+ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity);
+ const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
+ const int
+ lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
+ lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
+ lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
+ lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
+ const T
+ *ptrs = sprite.data -
+ (bx?x0:0) -
+ (by?y0*sprite.dimx():0) -
+ (bz?z0*sprite.dimx()*sprite.dimy():0) -
+ (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
+ const unsigned int
+ offX = width - lX, soffX = sprite.width - lX,
+ offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY),
+ offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ),
+ slX = lX*sizeof(T);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ if (lX>0 && lY>0 && lZ>0 && lV>0) {
+ T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
+ for (int v = 0; v<lV; ++v) {
+ for (int z = 0; z<lZ; ++z) {
+ if (opacity>=1) for (int y = 0; y<lY; ++y) { cimg_std::memcpy(ptrd,ptrs,slX); ptrd+=width; ptrs+=sprite.width; }
+ else for (int y = 0; y<lY; ++y) {
+ for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
+ ptrd+=offX; ptrs+=soffX;
+ }
+ ptrd+=offY; ptrs+=soffY;
+ }
+ ptrd+=offZ; ptrs+=soffZ;
+ }
+ }
+ return *this;
+ }
+#endif
+
+ //! Draw an image.
+ template<typename t>
+ CImg<T>& draw_image(const int x0, const int y0, const int z0,
+ const CImg<t>& sprite, const float opacity=1) {
+ return draw_image(x0,y0,z0,0,sprite,opacity);
+ }
+
+ //! Draw an image.
+ template<typename t>
+ CImg<T>& draw_image(const int x0, const int y0,
+ const CImg<t>& sprite, const float opacity=1) {
+ return draw_image(x0,y0,0,sprite,opacity);
+ }
+
+ //! Draw an image.
+ template<typename t>
+ CImg<T>& draw_image(const int x0,
+ const CImg<t>& sprite, const float opacity=1) {
+ return draw_image(x0,0,sprite,opacity);
+ }
+
+ //! Draw an image.
+ template<typename t>
+ CImg<T>& draw_image(const CImg<t>& sprite, const float opacity=1) {
+ return draw_image(0,sprite,opacity);
+ }
+
+ //! Draw a sprite image in the instance image (masked version).
+ /**
+ \param sprite Sprite image.
+ \param mask Mask image.
+ \param x0 X-coordinate of the sprite position in the instance image.
+ \param y0 Y-coordinate of the sprite position in the instance image.
+ \param z0 Z-coordinate of the sprite position in the instance image.
+ \param v0 V-coordinate of the sprite position in the instance image.
+ \param mask_valmax Maximum pixel value of the mask image \c mask (optional).
+ \param opacity Drawing opacity.
+ \note
+ - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite.
+ - Clipping is supported.
+ - Dimensions along x,y and z of \p sprite and \p mask must be the same.
+ **/
+ template<typename ti, typename tm>
+ CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
+ const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+ const float mask_valmax=1) {
+ if (is_empty()) return *this;
+ if (!sprite)
+ throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
+ if (!mask)
+ throw CImgArgumentException("CImg<%s>::draw_image() : Specified mask image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
+ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,mask,opacity,mask_valmax);
+ if (is_overlapped(mask)) return draw_image(x0,y0,z0,v0,sprite,+mask,opacity,mask_valmax);
+ if (mask.width!=sprite.width || mask.height!=sprite.height || mask.depth!=sprite.depth)
+ throw CImgArgumentException("CImg<%s>::draw_image() : Mask dimension is (%u,%u,%u,%u), while sprite is (%u,%u,%u,%u)",
+ pixel_type(),mask.width,mask.height,mask.depth,mask.dim,sprite.width,sprite.height,sprite.depth,sprite.dim);
+ const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
+ const int
+ lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
+ lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
+ lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
+ lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
+ const int
+ coff = -(bx?x0:0)-(by?y0*mask.dimx():0)-(bz?z0*mask.dimx()*mask.dimy():0)-(bv?v0*mask.dimx()*mask.dimy()*mask.dimz():0),
+ ssize = mask.dimx()*mask.dimy()*mask.dimz();
+ const ti *ptrs = sprite.data + coff;
+ const tm *ptrm = mask.data + coff;
+ const unsigned int
+ offX = width - lX, soffX = sprite.width - lX,
+ offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY),
+ offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ);
+ if (lX>0 && lY>0 && lZ>0 && lV>0) {
+ T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
+ for (int v = 0; v<lV; ++v) {
+ ptrm = mask.data + (ptrm - mask.data)%ssize;
+ for (int z = 0; z<lZ; ++z) {
+ for (int y = 0; y<lY; ++y) {
+ for (int x=0; x<lX; ++x) {
+ const float mopacity = (float)(*(ptrm++)*opacity),
+ nopacity = cimg::abs(mopacity), copacity = mask_valmax - cimg::max(mopacity,0);
+ *ptrd = (T)((nopacity*(*(ptrs++)) + *ptrd*copacity)/mask_valmax);
+ ++ptrd;
+ }
+ ptrd+=offX; ptrs+=soffX; ptrm+=soffX;
+ }
+ ptrd+=offY; ptrs+=soffY; ptrm+=soffY;
+ }
+ ptrd+=offZ; ptrs+=soffZ; ptrm+=soffZ;
+ }
+ }
+ return *this;
+ }
+
+ //! Draw an image.
+ template<typename ti, typename tm>
+ CImg<T>& draw_image(const int x0, const int y0, const int z0,
+ const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+ const float mask_valmax=1) {
+ return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_valmax);
+ }
+
+ //! Draw an image.
+ template<typename ti, typename tm>
+ CImg<T>& draw_image(const int x0, const int y0,
+ const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+ const float mask_valmax=1) {
+ return draw_image(x0,y0,0,sprite,mask,opacity,mask_valmax);
+ }
+
+ //! Draw an image.
+ template<typename ti, typename tm>
+ CImg<T>& draw_image(const int x0,
+ const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+ const float mask_valmax=1) {
+ return draw_image(x0,0,sprite,mask,opacity,mask_valmax);
+ }
+
+ //! Draw an image.
+ template<typename ti, typename tm>
+ CImg<T>& draw_image(const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+ const float mask_valmax=1) {
+ return draw_image(0,sprite,mask,opacity,mask_valmax);
+ }
+
+ //! Draw a 4D filled rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0,\c v0)-(\c x1,\c y1,\c z1,\c v1).
+ /**
+ \param x0 X-coordinate of the upper-left rectangle corner.
+ \param y0 Y-coordinate of the upper-left rectangle corner.
+ \param z0 Z-coordinate of the upper-left rectangle corner.
+ \param v0 V-coordinate of the upper-left rectangle corner.
+ \param x1 X-coordinate of the lower-right rectangle corner.
+ \param y1 Y-coordinate of the lower-right rectangle corner.
+ \param z1 Z-coordinate of the lower-right rectangle corner.
+ \param v1 V-coordinate of the lower-right rectangle corner.
+ \param val Scalar value used to fill the rectangle area.
+ \param opacity Drawing opacity (optional).
+ \note
+ - Clipping is supported.
+ **/
+ CImg<T>& draw_rectangle(const int x0, const int y0, const int z0, const int v0,
+ const int x1, const int y1, const int z1, const int v1,
+ const T val, const float opacity=1) {
+ if (is_empty()) return *this;
+ const bool bx = (x0<x1), by = (y0<y1), bz = (z0<z1), bv = (v0<v1);
+ const int
+ nx0 = bx?x0:x1, nx1 = bx?x1:x0,
+ ny0 = by?y0:y1, ny1 = by?y1:y0,
+ nz0 = bz?z0:z1, nz1 = bz?z1:z0,
+ nv0 = bv?v0:v1, nv1 = bv?v1:v0;
+ const int
+ lX = (1 + nx1 - nx0) + (nx1>=dimx()?dimx() - 1 - nx1:0) + (nx0<0?nx0:0),
+ lY = (1 + ny1 - ny0) + (ny1>=dimy()?dimy() - 1 - ny1:0) + (ny0<0?ny0:0),
+ lZ = (1 + nz1 - nz0) + (nz1>=dimz()?dimz() - 1 - nz1:0) + (nz0<0?nz0:0),
+ lV = (1 + nv1 - nv0) + (nv1>=dimv()?dimv() - 1 - nv1:0) + (nv0<0?nv0:0);
+ const unsigned int offX = width - lX, offY = width*(height - lY), offZ = width*height*(depth - lZ);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ T *ptrd = ptr(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nv0<0?0:nv0);
+ if (lX>0 && lY>0 && lZ>0 && lV>0)
+ for (int v = 0; v<lV; ++v) {
+ for (int z = 0; z<lZ; ++z) {
+ for (int y = 0; y<lY; ++y) {
+ if (opacity>=1) {
+ if (sizeof(T)!=1) { for (int x = 0; x<lX; ++x) *(ptrd++) = val; ptrd+=offX; }
+ else { cimg_std::memset(ptrd,(int)val,lX); ptrd+=width; }
+ } else { for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ptrd; } ptrd+=offX; }
+ }
+ ptrd+=offY;
+ }
+ ptrd+=offZ;
+ }
+ return *this;
+ }
+
+ //! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1).
+ /**
+ \param x0 X-coordinate of the upper-left rectangle corner.
+ \param y0 Y-coordinate of the upper-left rectangle corner.
+ \param z0 Z-coordinate of the upper-left rectangle corner.
+ \param x1 X-coordinate of the lower-right rectangle corner.
+ \param y1 Y-coordinate of the lower-right rectangle corner.
+ \param z1 Z-coordinate of the lower-right rectangle corner.
+ \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity (optional).
+ \note
+ - Clipping is supported.
+ **/
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const tc *const color, const float opacity=1) {
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_rectangle : specified color is (null)",
+ pixel_type());
+ cimg_forV(*this,k) draw_rectangle(x0,y0,z0,k,x1,y1,z1,k,color[k],opacity);
+ return *this;
+ }
+
+ //! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1).
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity);
+ }
+
+ //! Draw a 3D outlined colored rectangle in the instance image.
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ return draw_line(x0,y0,z0,x1,y0,z0,color,opacity,pattern,true).
+ draw_line(x1,y0,z0,x1,y1,z0,color,opacity,pattern,false).
+ draw_line(x1,y1,z0,x0,y1,z0,color,opacity,pattern,false).
+ draw_line(x0,y1,z0,x0,y0,z0,color,opacity,pattern,false).
+ draw_line(x0,y0,z1,x1,y0,z1,color,opacity,pattern,true).
+ draw_line(x1,y0,z1,x1,y1,z1,color,opacity,pattern,false).
+ draw_line(x1,y1,z1,x0,y1,z1,color,opacity,pattern,false).
+ draw_line(x0,y1,z1,x0,y0,z1,color,opacity,pattern,false).
+ draw_line(x0,y0,z0,x0,y0,z1,color,opacity,pattern,true).
+ draw_line(x1,y0,z0,x1,y0,z1,color,opacity,pattern,true).
+ draw_line(x1,y1,z0,x1,y1,z1,color,opacity,pattern,true).
+ draw_line(x0,y1,z0,x0,y1,z1,color,opacity,pattern,true);
+ }
+
+ //! Draw a 3D outlined colored rectangle in the instance image.
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const CImg<tc>& color, const float opacity,
+ const unsigned int pattern) {
+ return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern);
+ }
+
+ //! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1).
+ /**
+ \param x0 X-coordinate of the upper-left rectangle corner.
+ \param y0 Y-coordinate of the upper-left rectangle corner.
+ \param x1 X-coordinate of the lower-right rectangle corner.
+ \param y1 Y-coordinate of the lower-right rectangle corner.
+ \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity (optional).
+ \note
+ - Clipping is supported.
+ **/
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const tc *const color, const float opacity=1) {
+ return draw_rectangle(x0,y0,0,x1,y1,depth-1,color,opacity);
+ }
+
+ //! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1).
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_rectangle(x0,y0,x1,y1,color.data,opacity);
+ }
+
+ //! Draw a 2D outlined colored rectangle.
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ if (is_empty()) return *this;
+ if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true);
+ if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true);
+ const bool bx = (x0<x1), by = (y0<y1);
+ const int
+ nx0 = bx?x0:x1, nx1 = bx?x1:x0,
+ ny0 = by?y0:y1, ny1 = by?y1:y0;
+ if (ny1==ny0+1) return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
+ draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false);
+ return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
+ draw_line(nx1,ny0+1,nx1,ny1-1,color,opacity,pattern,false).
+ draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false).
+ draw_line(nx0,ny1-1,nx0,ny0+1,color,opacity,pattern,false);
+ }
+
+ //! Draw a 2D outlined colored rectangle.
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const CImg<tc>& color, const float opacity,
+ const unsigned int pattern) {
+ return draw_rectangle(x0,y0,x1,y1,color.data,opacity,pattern);
+ }
+
+ // Inner macro for drawing triangles.
+#define _cimg_for_triangle1(img,xl,xr,y,x0,y0,x1,y1,x2,y2) \
+ for (int y = y0<0?0:y0, \
+ xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+ xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+ _sxn=1, \
+ _sxr=1, \
+ _sxl=1, \
+ _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+ _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+ _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+ _dyn = y2-y1, \
+ _dyr = y2-y0, \
+ _dyl = y1-y0, \
+ _counter = (_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+ _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+ _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+ cimg::min((int)(img).height-y-1,y2-y)), \
+ _errn = _dyn/2, \
+ _errr = _dyr/2, \
+ _errl = _dyl/2, \
+ _rxn = _dyn?(x2-x1)/_dyn:0, \
+ _rxr = _dyr?(x2-x0)/_dyr:0, \
+ _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+ (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn); \
+ _counter>=0; --_counter, ++y, \
+ xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+ xl+=(y!=y1)?_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0): \
+ (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle2(img,xl,cl,xr,cr,y,x0,y0,c0,x1,y1,c1,x2,y2,c2) \
+ for (int y = y0<0?0:y0, \
+ xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+ cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
+ xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+ cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
+ _sxn=1, _scn=1, \
+ _sxr=1, _scr=1, \
+ _sxl=1, _scl=1, \
+ _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+ _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+ _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+ _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
+ _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
+ _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
+ _dyn = y2-y1, \
+ _dyr = y2-y0, \
+ _dyl = y1-y0, \
+ _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+ _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+ _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+ _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
+ _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
+ _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
+ cimg::min((int)(img).height-y-1,y2-y)), \
+ _errn = _dyn/2, _errcn = _errn, \
+ _errr = _dyr/2, _errcr = _errr, \
+ _errl = _dyl/2, _errcl = _errl, \
+ _rxn = _dyn?(x2-x1)/_dyn:0, \
+ _rcn = _dyn?(c2-c1)/_dyn:0, \
+ _rxr = _dyr?(x2-x0)/_dyr:0, \
+ _rcr = _dyr?(c2-c0)/_dyr:0, \
+ _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+ (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+ _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
+ (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ); \
+ _counter>=0; --_counter, ++y, \
+ xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+ cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
+ xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
+ _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+ (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
+ _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle3(img,xl,txl,tyl,xr,txr,tyr,y,x0,y0,tx0,ty0,x1,y1,tx1,ty1,x2,y2,tx2,ty2) \
+ for (int y = y0<0?0:y0, \
+ xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+ txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
+ tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
+ xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+ txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
+ tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
+ _sxn=1, _stxn=1, _styn=1, \
+ _sxr=1, _stxr=1, _styr=1, \
+ _sxl=1, _stxl=1, _styl=1, \
+ _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+ _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+ _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+ _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
+ _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
+ _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
+ _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
+ _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
+ _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
+ _dyn = y2-y1, \
+ _dyr = y2-y0, \
+ _dyl = y1-y0, \
+ _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+ _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+ _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+ _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
+ _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
+ _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
+ _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
+ _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
+ _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
+ cimg::min((int)(img).height-y-1,y2-y)), \
+ _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, \
+ _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, \
+ _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, \
+ _rxn = _dyn?(x2-x1)/_dyn:0, \
+ _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
+ _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
+ _rxr = _dyr?(x2-x0)/_dyr:0, \
+ _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
+ _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
+ _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+ (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+ _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
+ (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
+ _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
+ (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
+ _counter>=0; --_counter, ++y, \
+ xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+ txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
+ tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
+ xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
+ tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
+ _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+ (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
+ _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1,\
+ _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle4(img,xl,cl,txl,tyl,xr,cr,txr,tyr,y,x0,y0,c0,tx0,ty0,x1,y1,c1,tx1,ty1,x2,y2,c2,tx2,ty2) \
+ for (int y = y0<0?0:y0, \
+ xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+ cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
+ txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
+ tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
+ xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+ cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
+ txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
+ tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
+ _sxn=1, _scn=1, _stxn=1, _styn=1, \
+ _sxr=1, _scr=1, _stxr=1, _styr=1, \
+ _sxl=1, _scl=1, _stxl=1, _styl=1, \
+ _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+ _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+ _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+ _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
+ _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
+ _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
+ _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
+ _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
+ _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
+ _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
+ _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
+ _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
+ _dyn = y2-y1, \
+ _dyr = y2-y0, \
+ _dyl = y1-y0, \
+ _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+ _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+ _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+ _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
+ _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
+ _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
+ _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
+ _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
+ _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
+ _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
+ _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
+ _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
+ cimg::min((int)(img).height-y-1,y2-y)), \
+ _errn = _dyn/2, _errcn = _errn, _errtxn = _errn, _errtyn = _errn, \
+ _errr = _dyr/2, _errcr = _errr, _errtxr = _errr, _errtyr = _errr, \
+ _errl = _dyl/2, _errcl = _errl, _errtxl = _errl, _errtyl = _errl, \
+ _rxn = _dyn?(x2-x1)/_dyn:0, \
+ _rcn = _dyn?(c2-c1)/_dyn:0, \
+ _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
+ _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
+ _rxr = _dyr?(x2-x0)/_dyr:0, \
+ _rcr = _dyr?(c2-c0)/_dyr:0, \
+ _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
+ _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
+ _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+ (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+ _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
+ (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ), \
+ _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
+ (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
+ _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
+ (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
+ _counter>=0; --_counter, ++y, \
+ xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+ cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
+ txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
+ tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
+ xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
+ txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
+ tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
+ _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+ (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
+ _errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
+ _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
+ _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle5(img,xl,txl,tyl,lxl,lyl,xr,txr,tyr,lxr,lyr,y,x0,y0,tx0,ty0,lx0,ly0,x1,y1,tx1,ty1,lx1,ly1,x2,y2,tx2,ty2,lx2,ly2) \
+ for (int y = y0<0?0:y0, \
+ xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+ txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
+ tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
+ lxr = y0>=0?lx0:(lx0-y0*(lx2-lx0)/(y2-y0)), \
+ lyr = y0>=0?ly0:(ly0-y0*(ly2-ly0)/(y2-y0)), \
+ xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+ txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
+ tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
+ lxl = y1>=0?(y0>=0?(y0==y1?lx1:lx0):(lx0-y0*(lx1-lx0)/(y1-y0))):(lx1-y1*(lx2-lx1)/(y2-y1)), \
+ lyl = y1>=0?(y0>=0?(y0==y1?ly1:ly0):(ly0-y0*(ly1-ly0)/(y1-y0))):(ly1-y1*(ly2-ly1)/(y2-y1)), \
+ _sxn=1, _stxn=1, _styn=1, _slxn=1, _slyn=1, \
+ _sxr=1, _stxr=1, _styr=1, _slxr=1, _slyr=1, \
+ _sxl=1, _stxl=1, _styl=1, _slxl=1, _slyl=1, \
+ _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), _dyn = y2-y1, \
+ _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), _dyr = y2-y0, \
+ _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), _dyl = y1-y0, \
+ _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
+ _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
+ _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
+ _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
+ _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
+ _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
+ _dlxn = lx2>lx1?lx2-lx1:(_slxn=-1,lx1-lx2), \
+ _dlxr = lx2>lx0?lx2-lx0:(_slxr=-1,lx0-lx2), \
+ _dlxl = lx1>lx0?lx1-lx0:(_slxl=-1,lx0-lx1), \
+ _dlyn = ly2>ly1?ly2-ly1:(_slyn=-1,ly1-ly2), \
+ _dlyr = ly2>ly0?ly2-ly0:(_slyr=-1,ly0-ly2), \
+ _dlyl = ly1>ly0?ly1-ly0:(_slyl=-1,ly0-ly1), \
+ _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+ _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+ _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+ _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
+ _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
+ _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
+ _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
+ _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
+ _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
+ _dlxn-=_dyn?_dyn*(_dlxn/_dyn):0, \
+ _dlxr-=_dyr?_dyr*(_dlxr/_dyr):0, \
+ _dlxl-=_dyl?_dyl*(_dlxl/_dyl):0, \
+ _dlyn-=_dyn?_dyn*(_dlyn/_dyn):0, \
+ _dlyr-=_dyr?_dyr*(_dlyr/_dyr):0, \
+ _dlyl-=_dyl?_dyl*(_dlyl/_dyl):0, \
+ cimg::min((int)(img).height-y-1,y2-y)), \
+ _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, _errlxn = _errn, _errlyn = _errn, \
+ _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, _errlxr = _errr, _errlyr = _errr, \
+ _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, _errlxl = _errl, _errlyl = _errl, \
+ _rxn = _dyn?(x2-x1)/_dyn:0, \
+ _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
+ _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
+ _rlxn = _dyn?(lx2-lx1)/_dyn:0, \
+ _rlyn = _dyn?(ly2-ly1)/_dyn:0, \
+ _rxr = _dyr?(x2-x0)/_dyr:0, \
+ _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
+ _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
+ _rlxr = _dyr?(lx2-lx0)/_dyr:0, \
+ _rlyr = _dyr?(ly2-ly0)/_dyr:0, \
+ _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+ (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+ _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
+ (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
+ _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
+ (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ), \
+ _rlxl = (y0!=y1 && y1>0)?(_dyl?(lx1-lx0)/_dyl:0): \
+ (_errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxn ), \
+ _rlyl = (y0!=y1 && y1>0)?(_dyl?(ly1-ly0)/_dyl:0): \
+ (_errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyn ); \
+ _counter>=0; --_counter, ++y, \
+ xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+ txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
+ tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
+ lxr+=_rlxr+((_errlxr-=_dlxr)<0?_errlxr+=_dyr,_slxr:0), \
+ lyr+=_rlyr+((_errlyr-=_dlyr)<0?_errlyr+=_dyr,_slyr:0), \
+ xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
+ tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
+ lxl+=_rlxl+((_errlxl-=_dlxl)<0?(_errlxl+=_dyl,_slxl):0), \
+ lyl+=_rlyl+((_errlyl-=_dlyl)<0?(_errlyl+=_dyl,_slyl):0), \
+ _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+ (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
+ _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
+ _errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxl=_rlxn, lxl=lx1, \
+ _errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyl=_rlyn, lyl=ly1, \
+ _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+ // Draw a colored triangle (inner routine, uses bresenham's algorithm).
+ template<typename tc>
+ CImg<T>& _draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color, const float opacity,
+ const float brightness) {
+ _draw_scanline(color,opacity);
+ const float nbrightness = brightness<0?0:(brightness>2?2:brightness);
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2);
+ if (ny0<dimy() && ny2>=0) {
+ if ((nx1 - nx0)*(ny2 - ny0) - (nx2 - nx0)*(ny1 - ny0)<0)
+ _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xl,xr,y,color,opacity,nbrightness);
+ else
+ _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xr,xl,y,color,opacity,nbrightness);
+ }
+ return *this;
+ }
+
+ //! Draw a 2D filled colored triangle.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
+ pixel_type());
+ _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1);
+ return *this;
+ }
+
+ //! Draw a 2D filled colored triangle.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity);
+ }
+
+ //! Draw a 2D outlined colored triangle.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
+ pixel_type());
+ draw_line(x0,y0,x1,y1,color,opacity,pattern,true).
+ draw_line(x1,y1,x2,y2,color,opacity,pattern,false).
+ draw_line(x2,y2,x0,y0,color,opacity,pattern,false);
+ return *this;
+ }
+
+ //! Draw a 2D outlined colored triangle.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& color, const float opacity,
+ const unsigned int pattern) {
+ return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity,pattern);
+ }
+
+ //! Draw a 2D filled colored triangle, with z-buffering.
+ template<typename tc>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const tc *const color, const float opacity=1,
+ const float brightness=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
+ pixel_type());
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+ nbrightness = brightness<0?0:(brightness>2?2:brightness);
+ const int whz = width*height*depth, offx = dim*whz;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+ float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+ _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
+ if (y==ny1) { zl = nz1; pzl = pzn; }
+ int xleft = xleft0, xright = xright0;
+ float zleft = zl, zright = zr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright);
+ const int dx = xright - xleft;
+ const float pentez = (zright - zleft)/dx;
+ if (xleft<0 && dx) zleft-=xleft*(zright - zleft)/dx;
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ float *ptrz = zbuffer + xleft + y*width;
+ if (opacity>=1) {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nbrightness*(*col++)); ptrd+=whz; }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); ptrd+=whz; }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ }
+ } else {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=whz; }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity*nbrightness**(col++) + *ptrd*copacity); ptrd+=whz; }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ }
+ }
+ zr+=pzr; zl+=pzl;
+ }
+ return *this;
+ }
+
+ //! Draw a 2D filled colored triangle, with z-buffering.
+ template<typename tc>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& color, const float opacity=1,
+ const float brightness=1) {
+ return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opacity,brightness);
+ }
+
+ //! Draw a 2D Gouraud-shaded colored triangle.
+ /**
+ \param x0 = X-coordinate of the first corner in the instance image.
+ \param y0 = Y-coordinate of the first corner in the instance image.
+ \param x1 = X-coordinate of the second corner in the instance image.
+ \param y1 = Y-coordinate of the second corner in the instance image.
+ \param x2 = X-coordinate of the third corner in the instance image.
+ \param y2 = Y-coordinate of the third corner in the instance image.
+ \param color = array of dimv() values of type \c T, defining the global drawing color.
+ \param brightness0 = brightness of the first corner (in [0,2]).
+ \param brightness1 = brightness of the second corner (in [0,2]).
+ \param brightness2 = brightness of the third corner (in [0,2]).
+ \param opacity = opacity of the drawing.
+ \note Clipping is supported.
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
+ pixel_type());
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, offx = dim*whz-1;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
+ nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
+ nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nc0,nc1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nc0,nc2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nc1,nc2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+ int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
+ if (xright<xleft) cimg::swap(xleft,xright,cleft,cright);
+ const int
+ dx = xright - xleft,
+ dc = cright>cleft?cright - cleft:cleft - cright,
+ rc = dx?(cright - cleft)/dx:0,
+ sc = cright>cleft?1:-1,
+ ndc = dc-(dx?dx*(dc/dx):0);
+ int errc = dx>>1;
+ if (xleft<0 && dx) cleft-=xleft*(cright - cleft)/dx;
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y);
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Gouraud-shaded colored triangle.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& color,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,brightness0,brightness1,brightness2,opacity);
+ }
+
+ //! Draw a 2D Gouraud-shaded colored triangle, with z-buffering.
+ template<typename tc>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const tc *const color,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
+ pixel_type());
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, offx = dim*whz;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
+ nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
+ nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
+ float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1,nc0,nc1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2,nc0,nc2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2,nc1,nc2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+ _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+ if (y==ny1) { zl = nz1; pzl = pzn; }
+ int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
+ float zleft = zl, zright = zr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,cleft,cright);
+ const int
+ dx = xright - xleft,
+ dc = cright>cleft?cright - cleft:cleft - cright,
+ rc = dx?(cright-cleft)/dx:0,
+ sc = cright>cleft?1:-1,
+ ndc = dc-(dx?dx*(dc/dx):0);
+ const float pentez = (zright - zleft)/dx;
+ int errc = dx>>1;
+ if (xleft<0 && dx) {
+ cleft-=xleft*(cright - cleft)/dx;
+ zleft-=xleft*(zright - zleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T *ptrd = ptr(xleft,y);
+ float *ptrz = zbuffer + xleft + y*width;
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ }
+ zr+=pzr; zl+=pzl;
+ }
+ return *this;
+ }
+
+ //! Draw a Gouraud triangle with z-buffer consideration.
+ template<typename tc>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& color,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,brightness0,brightness1,brightness2,opacity);
+ }
+
+ //! Draw a 2D textured triangle.
+ /**
+ \param x0 = X-coordinate of the first corner in the instance image.
+ \param y0 = Y-coordinate of the first corner in the instance image.
+ \param x1 = X-coordinate of the second corner in the instance image.
+ \param y1 = Y-coordinate of the second corner in the instance image.
+ \param x2 = X-coordinate of the third corner in the instance image.
+ \param y2 = Y-coordinate of the third corner in the instance image.
+ \param texture = texture image used to fill the triangle.
+ \param tx0 = X-coordinate of the first corner in the texture image.
+ \param ty0 = Y-coordinate of the first corner in the texture image.
+ \param tx1 = X-coordinate of the second corner in the texture image.
+ \param ty1 = Y-coordinate of the second corner in the texture image.
+ \param tx2 = X-coordinate of the third corner in the texture image.
+ \param ty2 = Y-coordinate of the third corner in the texture image.
+ \param opacity = opacity of the drawing.
+ \param brightness = brightness of the drawing (in [0,2]).
+ \note Clipping is supported, but texture coordinates do not support clipping.
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float opacity=1,
+ const float brightness=1) {
+ if (is_empty()) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+ nbrightness = brightness<0?0:(brightness>2?2:brightness);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y,
+ nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) {
+ int
+ xleft = xleft0, xright = xright0,
+ txleft = txleft0, txright = txright0,
+ tyleft = tyleft0, tyright = tyright0;
+ if (xright<xleft) cimg::swap(xleft,xright,txleft,txright,tyleft,tyright);
+ const int
+ dx = xright - xleft,
+ dtx = txright>txleft?txright - txleft:txleft - txright,
+ dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
+ rtx = dx?(txright - txleft)/dx:0,
+ rty = dx?(tyright - tyleft)/dx:0,
+ stx = txright>txleft?1:-1,
+ sty = tyright>tyleft?1:-1,
+ ndtx = dtx - (dx?dx*(dtx/dx):0),
+ ndty = dty - (dx?dx*(dty/dx):0);
+ int errtx = dx>>1, errty = errtx;
+ if (xleft<0 && dx) {
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ if (opacity>=1) {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ *ptrd = (T)*col;
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nbrightness**col);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ }
+ } else {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nopacity**col + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D textured triangle, with perspective correction.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float opacity=1,
+ const float brightness=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+ nbrightness = brightness<0?0:(brightness>2?2:brightness);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+ float
+ ntx0 = tx0/z0, nty0 = ty0/z0,
+ ntx1 = tx1/z1, nty1 = ty1/z1,
+ ntx2 = tx2/z2, nty2 = ty2/z2,
+ nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+ ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+ ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+ ptyl = (nty1 - nty0)/(ny1 - ny0),
+ ptyr = (nty2 - nty0)/(ny2 - ny0),
+ ptyn = (nty2 - nty1)/(ny2 - ny1),
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+ txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+ tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+ _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
+ if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+ int xleft = xleft0, xright = xright0;
+ float
+ zleft = zl, zright = zr,
+ txleft = txl, txright = txr,
+ tyleft = tyl, tyright = tyr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
+ const int dx = xright - xleft;
+ const float
+ pentez = (zright - zleft)/dx,
+ pentetx = (txright - txleft)/dx,
+ pentety = (tyright - tyleft)/dx;
+ if (xleft<0 && dx) {
+ zleft-=xleft*(zright - zleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ if (opacity>=1) {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)*col;
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nbrightness**col);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ }
+ } else {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nopacity**col + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ }
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ //! Draw a 2D textured triangle, with z-buffering and perspective correction.
+ template<typename tc>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float opacity=1,
+ const float brightness=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+ nbrightness = brightness<0?0:(brightness>2?2:brightness);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+ float
+ ntx0 = tx0/z0, nty0 = ty0/z0,
+ ntx1 = tx1/z1, nty1 = ty1/z1,
+ ntx2 = tx2/z2, nty2 = ty2/z2,
+ nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+ ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+ ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+ ptyl = (nty1 - nty0)/(ny1 - ny0),
+ ptyr = (nty2 - nty0)/(ny2 - ny0),
+ ptyn = (nty2 - nty1)/(ny2 - ny1),
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+ txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+ tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+ _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
+ if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+ int xleft = xleft0, xright = xright0;
+ float
+ zleft = zl, zright = zr,
+ txleft = txl, txright = txr,
+ tyleft = tyl, tyright = tyr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
+ const int dx = xright - xleft;
+ const float
+ pentez = (zright - zleft)/dx,
+ pentetx = (txright - txleft)/dx,
+ pentety = (tyright - tyleft)/dx;
+ if (xleft<0 && dx) {
+ zleft-=xleft*(zright - zleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T *ptrd = ptr(xleft,y,0,0);
+ float *ptrz = zbuffer + xleft + y*width;
+ if (opacity>=1) {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)*col;
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nbrightness**col);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ }
+ } else {
+ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nopacity**col + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ }
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Pseudo-Phong-shaded triangle.
+ /**
+ \param x0 = X-coordinate of the first corner in the instance image.
+ \param y0 = Y-coordinate of the first corner in the instance image.
+ \param x1 = X-coordinate of the second corner in the instance image.
+ \param y1 = Y-coordinate of the second corner in the instance image.
+ \param x2 = X-coordinate of the third corner in the instance image.
+ \param y2 = Y-coordinate of the third corner in the instance image.
+ \param color = array of dimv() values of type \c T, defining the global drawing color.
+ \param light = light image.
+ \param lx0 = X-coordinate of the first corner in the light image.
+ \param ly0 = Y-coordinate of the first corner in the light image.
+ \param lx1 = X-coordinate of the second corner in the light image.
+ \param ly1 = Y-coordinate of the second corner in the light image.
+ \param lx2 = X-coordinate of the third corner in the light image.
+ \param ly2 = Y-coordinate of the third corner in the light image.
+ \param opacity = opacity of the drawing.
+ \note Clipping is supported, but texture coordinates do not support clipping.
+ **/
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
+ pixel_type());
+ if (!light)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
+ if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+ const int whz = width*height*depth, offx = dim*whz-1;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+ nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+ int
+ xleft = xleft0, xright = xright0,
+ lxleft = lxleft0, lxright = lxright0,
+ lyleft = lyleft0, lyright = lyright0;
+ if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright);
+ const int
+ dx = xright - xleft,
+ dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+ dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+ rlx = dx?(lxright - lxleft)/dx:0,
+ rly = dx?(lyright - lyleft)/dx:0,
+ slx = lxright>lxleft?1:-1,
+ sly = lyright>lyleft?1:-1,
+ ndlx = dlx - (dx?dx*(dlx/dx):0),
+ ndly = dly - (dx?dx*(dly/dx):0);
+ int errlx = dx>>1, errly = errlx;
+ if (xleft<0 && dx) {
+ lxleft-=xleft*(lxright - lxleft)/dx;
+ lyleft-=xleft*(lyright - lyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+ const tl l = light(lxleft,lyleft);
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ *ptrd = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const tl l = light(lxleft,lyleft);
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ const T val = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Pseudo-Phong-shaded triangle.
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& color,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ }
+
+ //! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering.
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const tc *const color,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
+ pixel_type());
+ if (!light)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
+ if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,
+ +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, offx = dim*whz;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+ float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+ _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+ nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+ if (y==ny1) { zl = nz1; pzl = pzn; }
+ int
+ xleft = xleft0, xright = xright0,
+ lxleft = lxleft0, lxright = lxright0,
+ lyleft = lyleft0, lyright = lyright0;
+ float zleft = zl, zright = zr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,lxleft,lxright,lyleft,lyright);
+ const int
+ dx = xright - xleft,
+ dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+ dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+ rlx = dx?(lxright - lxleft)/dx:0,
+ rly = dx?(lyright - lyleft)/dx:0,
+ slx = lxright>lxleft?1:-1,
+ sly = lyright>lyleft?1:-1,
+ ndlx = dlx - (dx?dx*(dlx/dx):0),
+ ndly = dly - (dx?dx*(dly/dx):0);
+ const float pentez = (zright - zleft)/dx;
+ int errlx = dx>>1, errly = errlx;
+ if (xleft<0 && dx) {
+ zleft-=xleft*(zright - zleft)/dx;
+ lxleft-=xleft*(lxright - lxleft)/dx;
+ lyleft-=xleft*(lyright - lyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T *ptrd = ptr(xleft,y,0,0);
+ float *ptrz = zbuffer + xleft + y*width;
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tl l = light(lxleft,lyleft);
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ const tc cval = *(col++);
+ *ptrd = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const tl l = light(lxleft,lyleft);
+ const tc *col = color;
+ cimg_forV(*this,k) {
+ const tc cval = *(col++);
+ const T val = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ }
+ zr+=pzr; zl+=pzl;
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering.
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& color,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ }
+
+ //! Draw a 2D Gouraud-shaded textured triangle.
+ /**
+ \param x0 = X-coordinate of the first corner in the instance image.
+ \param y0 = Y-coordinate of the first corner in the instance image.
+ \param x1 = X-coordinate of the second corner in the instance image.
+ \param y1 = Y-coordinate of the second corner in the instance image.
+ \param x2 = X-coordinate of the third corner in the instance image.
+ \param y2 = Y-coordinate of the third corner in the instance image.
+ \param texture = texture image used to fill the triangle.
+ \param tx0 = X-coordinate of the first corner in the texture image.
+ \param ty0 = Y-coordinate of the first corner in the texture image.
+ \param tx1 = X-coordinate of the second corner in the texture image.
+ \param ty1 = Y-coordinate of the second corner in the texture image.
+ \param tx2 = X-coordinate of the third corner in the texture image.
+ \param ty2 = Y-coordinate of the third corner in the texture image.
+ \param brightness0 = brightness value of the first corner.
+ \param brightness1 = brightness value of the second corner.
+ \param brightness2 = brightness value of the third corner.
+ \param opacity = opacity of the drawing.
+ \note Clipping is supported, but texture coordinates do not support clipping.
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture))
+ return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,brightness0,brightness1,brightness2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
+ nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
+ nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
+ nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y,
+ nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) {
+ int
+ xleft = xleft0, xright = xright0,
+ cleft = cleft0, cright = cright0,
+ txleft = txleft0, txright = txright0,
+ tyleft = tyleft0, tyright = tyright0;
+ if (xright<xleft) cimg::swap(xleft,xright,cleft,cright,txleft,txright,tyleft,tyright);
+ const int
+ dx = xright - xleft,
+ dc = cright>cleft?cright - cleft:cleft - cright,
+ dtx = txright>txleft?txright - txleft:txleft - txright,
+ dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
+ rc = dx?(cright - cleft)/dx:0,
+ rtx = dx?(txright - txleft)/dx:0,
+ rty = dx?(tyright - tyleft)/dx:0,
+ sc = cright>cleft?1:-1,
+ stx = txright>txleft?1:-1,
+ sty = tyright>tyleft?1:-1,
+ ndc = dc - (dx?dx*(dc/dx):0),
+ ndtx = dtx - (dx?dx*(dtx/dx):0),
+ ndty = dty - (dx?dx*(dty/dx):0);
+ int errc = dx>>1, errtx = errc, errty = errc;
+ if (xleft<0 && dx) {
+ cleft-=xleft*(cright - cleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Gouraud-shaded textured triangle, with perspective correction.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
+ brightness0,brightness1,brightness2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
+ nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
+ nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
+ float
+ ntx0 = tx0/z0, nty0 = ty0/z0,
+ ntx1 = tx1/z1, nty1 = ty1/z1,
+ ntx2 = tx2/z2, nty2 = ty2/z2,
+ nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+ ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+ ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+ ptyl = (nty1 - nty0)/(ny1 - ny0),
+ ptyr = (nty2 - nty0)/(ny2 - ny0),
+ ptyn = (nty2 - nty1)/(ny2 - ny1),
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+ txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+ tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+ _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+ if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+ int
+ xleft = xleft0, xright = xright0,
+ cleft = cleft0, cright = cright0;
+ float
+ zleft = zl, zright = zr,
+ txleft = txl, txright = txr,
+ tyleft = tyl, tyright = tyr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
+ const int
+ dx = xright - xleft,
+ dc = cright>cleft?cright - cleft:cleft - cright,
+ rc = dx?(cright - cleft)/dx:0,
+ sc = cright>cleft?1:-1,
+ ndc = dc - (dx?dx*(dc/dx):0);
+ const float
+ pentez = (zright - zleft)/dx,
+ pentetx = (txright - txleft)/dx,
+ pentety = (tyright - tyleft)/dx;
+ int errc = dx>>1;
+ if (xleft<0 && dx) {
+ cleft-=xleft*(cright - cleft)/dx;
+ zleft-=xleft*(zright - zleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Gouraud-shaded textured triangle, with z-buffering and perspective correction.
+ template<typename tc>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
+ brightness0,brightness1,brightness2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
+ nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
+ nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
+ float
+ ntx0 = tx0/z0, nty0 = ty0/z0,
+ ntx1 = tx1/z1, nty1 = ty1/z1,
+ ntx2 = tx2/z2, nty2 = ty2/z2,
+ nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+ ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+ ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+ ptyl = (nty1 - nty0)/(ny1 - ny0),
+ ptyr = (nty2 - nty0)/(ny2 - ny0),
+ ptyn = (nty2 - nty1)/(ny2 - ny1),
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+ txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+ tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+ _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+ if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+ int
+ xleft = xleft0, xright = xright0,
+ cleft = cleft0, cright = cright0;
+ float
+ zleft = zl, zright = zr,
+ txleft = txl, txright = txr,
+ tyleft = tyl, tyright = tyr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
+ const int
+ dx = xright - xleft,
+ dc = cright>cleft?cright - cleft:cleft - cright,
+ rc = dx?(cright - cleft)/dx:0,
+ sc = cright>cleft?1:-1,
+ ndc = dc - (dx?dx*(dc/dx):0);
+ const float
+ pentez = (zright - zleft)/dx,
+ pentetx = (txright - txleft)/dx,
+ pentety = (tyright - tyleft)/dx;
+ int errc = dx>>1;
+ if (xleft<0 && dx) {
+ cleft-=xleft*(cright - cleft)/dx;
+ zleft-=xleft*(zright - zleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y);
+ float *ptrz = zbuffer + xleft + y*width;
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Pseudo-Phong-shaded textured triangle.
+ /**
+ \param x0 = X-coordinate of the first corner in the instance image.
+ \param y0 = Y-coordinate of the first corner in the instance image.
+ \param x1 = X-coordinate of the second corner in the instance image.
+ \param y1 = Y-coordinate of the second corner in the instance image.
+ \param x2 = X-coordinate of the third corner in the instance image.
+ \param y2 = Y-coordinate of the third corner in the instance image.
+ \param texture = texture image used to fill the triangle.
+ \param tx0 = X-coordinate of the first corner in the texture image.
+ \param ty0 = Y-coordinate of the first corner in the texture image.
+ \param tx1 = X-coordinate of the second corner in the texture image.
+ \param ty1 = Y-coordinate of the second corner in the texture image.
+ \param tx2 = X-coordinate of the third corner in the texture image.
+ \param ty2 = Y-coordinate of the third corner in the texture image.
+ \param light = light image.
+ \param lx0 = X-coordinate of the first corner in the light image.
+ \param ly0 = Y-coordinate of the first corner in the light image.
+ \param lx1 = X-coordinate of the second corner in the light image.
+ \param ly1 = Y-coordinate of the second corner in the light image.
+ \param lx2 = X-coordinate of the third corner in the light image.
+ \param ly2 = Y-coordinate of the third corner in the light image.
+ \param opacity = opacity of the drawing.
+ \note Clipping is supported, but texture coordinates do not support clipping.
+ **/
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (!light)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
+ if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
+ nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y,
+ nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) {
+ int
+ xleft = xleft0, xright = xright0,
+ lxleft = lxleft0, lxright = lxright0,
+ lyleft = lyleft0, lyright = lyright0,
+ txleft = txleft0, txright = txright0,
+ tyleft = tyleft0, tyright = tyright0;
+ if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright,txleft,txright,tyleft,tyright);
+ const int
+ dx = xright - xleft,
+ dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+ dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+ dtx = txright>txleft?txright - txleft:txleft - txright,
+ dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
+ rlx = dx?(lxright - lxleft)/dx:0,
+ rly = dx?(lyright - lyleft)/dx:0,
+ rtx = dx?(txright - txleft)/dx:0,
+ rty = dx?(tyright - tyleft)/dx:0,
+ slx = lxright>lxleft?1:-1,
+ sly = lyright>lyleft?1:-1,
+ stx = txright>txleft?1:-1,
+ sty = tyright>tyleft?1:-1,
+ ndlx = dlx - (dx?dx*(dlx/dx):0),
+ ndly = dly - (dx?dx*(dly/dx):0),
+ ndtx = dtx - (dx?dx*(dtx/dx):0),
+ ndty = dty - (dx?dx*(dty/dx):0);
+ int errlx = dx>>1, errly = errlx, errtx = errlx, errty = errlx;
+ if (xleft<0 && dx) {
+ lxleft-=xleft*(lxright - lxleft)/dx;
+ lyleft-=xleft*(lyright - lyleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+ const tl l = light(lxleft,lyleft);
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const tl l = light(lxleft,lyleft);
+ const tc *col = texture.ptr(txleft,tyleft);
+ cimg_forV(*this,k) {
+ const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Pseudo-Phong-shaded textured triangle, with perspective correction.
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (!light)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
+ if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ if (is_overlapped(light)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+ float
+ ntx0 = tx0/z0, nty0 = ty0/z0,
+ ntx1 = tx1/z1, nty1 = ty1/z1,
+ ntx2 = tx2/z2, nty2 = ty2/z2,
+ nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+ ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+ ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+ ptyl = (nty1 - nty0)/(ny1 - ny0),
+ ptyr = (nty2 - nty0)/(ny2 - ny0),
+ ptyn = (nty2 - nty1)/(ny2 - ny1),
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+ txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+ tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+ _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+ nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+ if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+ int
+ xleft = xleft0, xright = xright0,
+ lxleft = lxleft0, lxright = lxright0,
+ lyleft = lyleft0, lyright = lyright0;
+ float
+ zleft = zl, zright = zr,
+ txleft = txl, txright = txr,
+ tyleft = tyl, tyright = tyr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
+ const int
+ dx = xright - xleft,
+ dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+ dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+ rlx = dx?(lxright - lxleft)/dx:0,
+ rly = dx?(lyright - lyleft)/dx:0,
+ slx = lxright>lxleft?1:-1,
+ sly = lyright>lyleft?1:-1,
+ ndlx = dlx - (dx?dx*(dlx/dx):0),
+ ndly = dly - (dx?dx*(dly/dx):0);
+ const float
+ pentez = (zright - zleft)/dx,
+ pentetx = (txright - txleft)/dx,
+ pentety = (tyright - tyleft)/dx;
+ int errlx = dx>>1, errly = errlx;
+ if (xleft<0 && dx) {
+ zleft-=xleft*(zright - zleft)/dx;
+ lxleft-=xleft*(lxright - lxleft)/dx;
+ lyleft-=xleft*(lyright - lyleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y,0,0);
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tl l = light(lxleft,lyleft);
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ } else for (int x = xleft; x<=xright; ++x) {
+ const float invz = 1/zleft;
+ const tl l = light(lxleft,lyleft);
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ //! Draw a 2D Pseudo-Phong-shaded textured triangle, with z-buffering and perspective correction.
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(float *const zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!texture || texture.dim<dim)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
+ pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
+ if (!light)
+ throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
+ if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
+ +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
+ texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+ float
+ ntx0 = tx0/z0, nty0 = ty0/z0,
+ ntx1 = tx1/z1, nty1 = ty1/z1,
+ ntx2 = tx2/z2, nty2 = ty2/z2,
+ nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
+ if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
+ if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
+ if (ny0>=dimy() || ny2<0) return *this;
+ float
+ ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+ ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+ ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+ ptyl = (nty1 - nty0)/(ny1 - ny0),
+ ptyr = (nty2 - nty0)/(ny2 - ny0),
+ ptyn = (nty2 - nty1)/(ny2 - ny1),
+ pzl = (nz1 - nz0)/(ny1 - ny0),
+ pzr = (nz2 - nz0)/(ny2 - ny0),
+ pzn = (nz2 - nz1)/(ny2 - ny1),
+ zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+ zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+ txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+ tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+ _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+ nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+ if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+ int
+ xleft = xleft0, xright = xright0,
+ lxleft = lxleft0, lxright = lxright0,
+ lyleft = lyleft0, lyright = lyright0;
+ float
+ zleft = zl, zright = zr,
+ txleft = txl, txright = txr,
+ tyleft = tyl, tyright = tyr;
+ if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
+ const int
+ dx = xright - xleft,
+ dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+ dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+ rlx = dx?(lxright - lxleft)/dx:0,
+ rly = dx?(lyright - lyleft)/dx:0,
+ slx = lxright>lxleft?1:-1,
+ sly = lyright>lyleft?1:-1,
+ ndlx = dlx - (dx?dx*(dlx/dx):0),
+ ndly = dly - (dx?dx*(dly/dx):0);
+ const float
+ pentez = (zright - zleft)/dx,
+ pentetx = (txright - txleft)/dx,
+ pentety = (tyright - tyleft)/dx;
+ int errlx = dx>>1, errly = errlx;
+ if (xleft<0 && dx) {
+ zleft-=xleft*(zright - zleft)/dx;
+ lxleft-=xleft*(lxright - lxleft)/dx;
+ lyleft-=xleft*(lyright - lyleft)/dx;
+ txleft-=xleft*(txright - txleft)/dx;
+ tyleft-=xleft*(tyright - tyleft)/dx;
+ }
+ if (xleft<0) xleft = 0;
+ if (xright>=dimx()-1) xright = dimx()-1;
+ T* ptrd = ptr(xleft,y);
+ float *ptrz = zbuffer + xleft + y*width;
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tl l = light(lxleft,lyleft);
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>*ptrz) {
+ *ptrz = zleft;
+ const float invz = 1/zleft;
+ const tl l = light(lxleft,lyleft);
+ const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forV(*this,k) {
+ const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whz; col+=twhz;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ // Draw a 2D ellipse (inner routine).
+ template<typename tc>
+ CImg<T>& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_ellipse : Specified color is (null).",
+ pixel_type());
+ _draw_scanline(color,opacity);
+ const float
+ nr1 = cimg::abs(r1), nr2 = cimg::abs(r2),
+ norm = (float)cimg_std::sqrt(ru*ru+rv*rv),
+ u = norm>0?ru/norm:1,
+ v = norm>0?rv/norm:0,
+ rmax = cimg::max(nr1,nr2),
+ l1 = (float)cimg_std::pow(rmax/(nr1>0?nr1:1e-6),2),
+ l2 = (float)cimg_std::pow(rmax/(nr2>0?nr2:1e-6),2),
+ a = l1*u*u + l2*v*v,
+ b = u*v*(l1-l2),
+ c = l1*v*v + l2*u*u;
+ const int
+ yb = (int)cimg_std::sqrt(a*rmax*rmax/(a*c-b*b)),
+ tymin = y0 - yb - 1,
+ tymax = y0 + yb + 1,
+ ymin = tymin<0?0:tymin,
+ ymax = tymax>=dimy()?height-1:tymax;
+ int oxmin = 0, oxmax = 0;
+ bool first_line = true;
+ for (int y = ymin; y<=ymax; ++y) {
+ const float
+ Y = y-y0 + (y<y0?0.5f:-0.5f),
+ delta = b*b*Y*Y-a*(c*Y*Y-rmax*rmax),
+ sdelta = delta>0?(float)cimg_std::sqrt(delta)/a:0.0f,
+ bY = b*Y/a,
+ fxmin = x0-0.5f-bY-sdelta,
+ fxmax = x0+0.5f-bY+sdelta;
+ const int xmin = (int)fxmin, xmax = (int)fxmax;
+ if (!pattern) _draw_scanline(xmin,xmax,y,color,opacity);
+ else {
+ if (first_line) {
+ if (y0-yb>=0) _draw_scanline(xmin,xmax,y,color,opacity);
+ else draw_point(xmin,y,color,opacity).draw_point(xmax,y,color,opacity);
+ first_line = false;
+ } else {
+ if (xmin<oxmin) _draw_scanline(xmin,oxmin-1,y,color,opacity);
+ else _draw_scanline(oxmin+(oxmin==xmin?0:1),xmin,y,color,opacity);
+ if (xmax<oxmax) _draw_scanline(xmax,oxmax-1,y,color,opacity);
+ else _draw_scanline(oxmax+(oxmax==xmax?0:1),xmax,y,color,opacity);
+ if (y==tymax) _draw_scanline(xmin+1,xmax-1,y,color,opacity);
+ }
+ }
+ oxmin = xmin; oxmax = xmax;
+ }
+ return *this;
+ }
+
+ //! Draw a filled ellipse.
+ /**
+ \param x0 = X-coordinate of the ellipse center.
+ \param y0 = Y-coordinate of the ellipse center.
+ \param r1 = First radius of the ellipse.
+ \param r2 = Second radius of the ellipse.
+ \param ru = X-coordinate of the orientation vector related to the first radius.
+ \param rv = Y-coordinate of the orientation vector related to the first radius.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
+ const tc *const color, const float opacity=1) {
+ return _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,0U);
+ }
+
+ //! Draw a filled ellipse.
+ template<typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity);
+ }
+
+ //! Draw a filled ellipse.
+ /**
+ \param x0 = X-coordinate of the ellipse center.
+ \param y0 = Y-coordinate of the ellipse center.
+ \param tensor = Diffusion tensor describing the ellipse.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
+ const tc *const color, const float opacity=1) {
+ CImgList<t> eig = tensor.get_symmetric_eigen();
+ const CImg<t> &val = eig[0], &vec = eig[1];
+ return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity);
+ }
+
+ //! Draw a filled ellipse.
+ template<typename t, typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_ellipse(x0,y0,tensor,color.data,opacity);
+ }
+
+ //! Draw an outlined ellipse.
+ /**
+ \param x0 = X-coordinate of the ellipse center.
+ \param y0 = Y-coordinate of the ellipse center.
+ \param r1 = First radius of the ellipse.
+ \param r2 = Second radius of the ellipse.
+ \param ru = X-coordinate of the orientation vector related to the first radius.
+ \param rv = Y-coordinate of the orientation vector related to the first radius.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ if (pattern) _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,pattern);
+ return *this;
+ }
+
+ //! Draw an outlined ellipse.
+ template<typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
+ const CImg<tc>& color, const float opacity,
+ const unsigned int pattern) {
+ return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity,pattern);
+ }
+
+ //! Draw an outlined ellipse.
+ /**
+ \param x0 = X-coordinate of the ellipse center.
+ \param y0 = Y-coordinate of the ellipse center.
+ \param tensor = Diffusion tensor describing the ellipse.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ CImgList<t> eig = tensor.get_symmetric_eigen();
+ const CImg<t> &val = eig[0], &vec = eig[1];
+ return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity,pattern);
+ }
+
+ //! Draw an outlined ellipse.
+ template<typename t, typename tc>
+ CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
+ const CImg<tc>& color, const float opacity,
+ const unsigned int pattern) {
+ return draw_ellipse(x0,y0,tensor,color.data,opacity,pattern);
+ }
+
+ //! Draw a filled circle.
+ /**
+ \param x0 X-coordinate of the circle center.
+ \param y0 Y-coordinate of the circle center.
+ \param radius Circle radius.
+ \param color Array of dimv() values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ \note
+ - Circle version of the Bresenham's algorithm is used.
+ **/
+ template<typename tc>
+ CImg<T>& draw_circle(const int x0, const int y0, int radius,
+ const tc *const color, const float opacity=1) {
+ if (!is_empty()) {
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",
+ pixel_type());
+ _draw_scanline(color,opacity);
+ if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
+ if (y0>=0 && y0<dimy()) _draw_scanline(x0-radius,x0+radius,y0,color,opacity);
+ for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
+ if (f>=0) {
+ const int x1 = x0-x, x2 = x0+x, y1 = y0-y, y2 = y0+y;
+ if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
+ if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
+ f+=(ddFy+=2); --y;
+ }
+ const bool no_diag = y!=(x++);
+ ++(f+=(ddFx+=2));
+ const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x;
+ if (no_diag) {
+ if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
+ if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a filled circle.
+ template<typename tc>
+ CImg<T>& draw_circle(const int x0, const int y0, int radius,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_circle(x0,y0,radius,color.data,opacity);
+ }
+
+ //! Draw an outlined circle.
+ /**
+ \param x0 X-coordinate of the circle center.
+ \param y0 Y-coordinate of the circle center.
+ \param radius Circle radius.
+ \param color Array of dimv() values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ template<typename tc>
+ CImg<T>& draw_circle(const int x0, const int y0, int radius,
+ const tc *const color, const float opacity,
+ const unsigned int) {
+ if (!is_empty()) {
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",
+ pixel_type());
+ if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
+ if (!radius) return draw_point(x0,y0,color,opacity);
+ draw_point(x0-radius,y0,color,opacity).draw_point(x0+radius,y0,color,opacity).
+ draw_point(x0,y0-radius,color,opacity).draw_point(x0,y0+radius,color,opacity);
+ if (radius==1) return *this;
+ for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
+ if (f>=0) { f+=(ddFy+=2); --y; }
+ ++x; ++(f+=(ddFx+=2));
+ if (x!=y+1) {
+ const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x, x3 = x0-x, x4 = x0+x, y3 = y0-y, y4 = y0+y;
+ draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity).
+ draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity);
+ if (x!=y)
+ draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity).
+ draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw an outlined circle.
+ template<typename tc>
+ CImg<T>& draw_circle(const int x0, const int y0, int radius, const CImg<tc>& color,
+ const float opacity,
+ const unsigned int pattern) {
+ return draw_circle(x0,y0,radius,color.data,opacity,pattern);
+ }
+
+ // Draw a text (internal).
+ template<typename tc1, typename tc2, typename t>
+ CImg<T>& _draw_text(const int x0, const int y0, const char *const text,
+ const tc1 *const foreground_color, const tc2 *const background_color,
+ const float opacity, const CImgList<t>& font) {
+ if (!text) return *this;
+ if (!font)
+ throw CImgArgumentException("CImg<%s>::draw_text() : Specified font (%u,%p) is empty.",
+ pixel_type(),font.size,font.data);
+ const int text_length = cimg::strlen(text);
+
+ if (is_empty()) {
+ // If needed, pre-compute necessary size of the image
+ int x = 0, y = 0, w = 0;
+ unsigned char c = 0;
+ for (int i = 0; i<text_length; ++i) {
+ c = text[i];
+ switch (c) {
+ case '\n' : y+=font[' '].height; if (x>w) w = x; x = 0; break;
+ case '\t' : x+=4*font[' '].width; break;
+ default : if (c<font.size) x+=font[c].width;
+ }
+ }
+ if (x!=0 || c=='\n') {
+ if (x>w) w=x;
+ y+=font[' '].height;
+ }
+ assign(x0+w,y0+y,1,font[' '].dim,0);
+ if (background_color) cimg_forV(*this,k) get_shared_channel(k).fill((T)background_color[k]);
+ }
+
+ int x = x0, y = y0;
+ CImg<T> letter;
+ for (int i = 0; i<text_length; ++i) {
+ const unsigned char c = text[i];
+ switch (c) {
+ case '\n' : y+=font[' '].height; x = x0; break;
+ case '\t' : x+=4*font[' '].width; break;
+ default : if (c<font.size) {
+ letter = font[c];
+ const CImg<T>& mask = (c+256)<(int)font.size?font[c+256]:font[c];
+ if (foreground_color) for (unsigned int p = 0; p<letter.width*letter.height; ++p)
+ if (mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)(letter(p,0,0,k)*foreground_color[k]);
+ if (background_color) for (unsigned int p = 0; p<letter.width*letter.height; ++p)
+ if (!mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)background_color[k];
+ if (!background_color && font.size>=512) draw_image(x,y,letter,mask,opacity,(T)1);
+ else draw_image(x,y,letter,opacity);
+ x+=letter.width;
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a text.
+ /**
+ \param x0 X-coordinate of the text in the instance image.
+ \param y0 Y-coordinate of the text in the instance image.
+ \param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
+ \param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
+ \param font Font used for drawing text.
+ \param opacity Drawing opacity.
+ \param format 'printf'-style format string, followed by arguments.
+ \note Clipping is supported.
+ **/
+ template<typename tc1, typename tc2, typename t>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const tc1 *const foreground_color, const tc2 *const background_color,
+ const float opacity, const CImgList<t>& font, ...) {
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
+ cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
+ }
+
+ //! Draw a text.
+ template<typename tc1, typename tc2, typename t>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const CImg<tc1>& foreground_color, const CImg<tc2>& background_color,
+ const float opacity, const CImgList<t>& font, ...) {
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
+ cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
+ }
+
+ //! Draw a text.
+ template<typename tc, typename t>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const tc *const foreground_color, const int background_color,
+ const float opacity, const CImgList<t>& font, ...) {
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
+ cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color,(tc*)background_color,opacity,font);
+ }
+
+ //! Draw a text.
+ template<typename tc, typename t>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const int foreground_color, const tc *const background_color,
+ const float opacity, const CImgList<t>& font, ...) {
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
+ cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font);
+ }
+
+ //! Draw a text.
+ /**
+ \param x0 X-coordinate of the text in the instance image.
+ \param y0 Y-coordinate of the text in the instance image.
+ \param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
+ \param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
+ \param font_size Size of the font (nearest match).
+ \param opacity Drawing opacity.
+ \param format 'printf'-style format string, followed by arguments.
+ \note Clipping is supported.
+ **/
+ template<typename tc1, typename tc2>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const tc1 *const foreground_color, const tc2 *const background_color,
+ const float opacity=1, const unsigned int font_size=11, ...) {
+ static CImgList<T> font;
+ static unsigned int fsize = 0;
+ if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
+ }
+
+ //! Draw a text.
+ template<typename tc1, typename tc2>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const CImg<tc1>& foreground_color, const CImg<tc2>& background_color,
+ const float opacity=1, const unsigned int font_size=11, ...) {
+ static CImgList<T> font;
+ static unsigned int fsize = 0;
+ if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color.data,background_color.data,opacity,font);
+ }
+
+ //! Draw a text.
+ template<typename tc>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const tc *const foreground_color, const int background_color=0,
+ const float opacity=1, const unsigned int font_size=11, ...) {
+ static CImgList<T> font;
+ static unsigned int fsize = 0;
+ if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color,(const tc*)background_color,opacity,font);
+ }
+
+ //! Draw a text.
+ template<typename tc>
+ CImg<T>& draw_text(const int x0, const int y0, const char *const text,
+ const int foreground_color, const tc *const background_color,
+ const float opacity=1, const unsigned int font_size=11, ...) {
+ static CImgList<T> font;
+ static unsigned int fsize = 0;
+ if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
+ char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font);
+ }
+
+ //! Draw a vector field in the instance image, using a colormap.
+ /**
+ \param flow Image of 2d vectors used as input data.
+ \param color Image of dimv()-D vectors corresponding to the color of each arrow.
+ \param sampling Length (in pixels) between each arrow.
+ \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
+ \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
+ \param opacity Opacity of the drawing.
+ \param pattern Used pattern to draw lines.
+ \note Clipping is supported.
+ **/
+ template<typename t1, typename t2>
+ CImg<T>& draw_quiver(const CImg<t1>& flow,
+ const t2 *const color, const float opacity=1,
+ const unsigned int sampling=25, const float factor=-20,
+ const int quiver_type=0, const unsigned int pattern=~0U) {
+ return draw_quiver(flow,CImg<t2>(color,dim,1,1,1,true),opacity,sampling,factor,quiver_type,pattern);
+ }
+
+ //! Draw a vector field in the instance image, using a colormap.
+ /**
+ \param flow Image of 2d vectors used as input data.
+ \param color Image of dimv()-D vectors corresponding to the color of each arrow.
+ \param sampling Length (in pixels) between each arrow.
+ \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
+ \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
+ \param opacity Opacity of the drawing.
+ \param pattern Used pattern to draw lines.
+ \note Clipping is supported.
+ **/
+ template<typename t1, typename t2>
+ CImg<T>& draw_quiver(const CImg<t1>& flow,
+ const CImg<t2>& color, const float opacity=1,
+ const unsigned int sampling=25, const float factor=-20,
+ const int quiver_type=0, const unsigned int pattern=~0U) {
+ if (!is_empty()) {
+ if (!flow || flow.dim!=2)
+ throw CImgArgumentException("CImg<%s>::draw_quiver() : Specified flow (%u,%u,%u,%u,%p) has wrong dimensions.",
+ pixel_type(),flow.width,flow.height,flow.depth,flow.dim,flow.data);
+ if (sampling<=0)
+ throw CImgArgumentException("CImg<%s>::draw_quiver() : Incorrect sampling value = %g",
+ pixel_type(),sampling);
+ const bool colorfield = (color.width==flow.width && color.height==flow.height && color.depth==1 && color.dim==dim);
+ if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,quiver_type,pattern);
+
+ float vmax,fact;
+ if (factor<=0) {
+ float m, M = (float)flow.get_pointwise_norm(2).maxmin(m);
+ vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
+ fact = -factor;
+ } else { fact = factor; vmax = 1; }
+
+ for (unsigned int y=sampling/2; y<height; y+=sampling)
+ for (unsigned int x=sampling/2; x<width; x+=sampling) {
+ const unsigned int X = x*flow.width/width, Y = y*flow.height/height;
+ float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax;
+ if (!quiver_type) {
+ const int xx = x+(int)u, yy = y+(int)v;
+ if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y).data,opacity,45,sampling/5.0f,pattern);
+ else draw_arrow(x,y,xx,yy,color,opacity,45,sampling/5.0f,pattern);
+ } else {
+ if (colorfield) draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color.get_vector_at(X,Y),opacity,pattern);
+ else draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color,opacity,pattern);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 1D graph on the instance image.
+ /**
+ \param data Image containing the graph values I = f(x).
+ \param color Array of dimv() values of type \c T, defining the drawing color.
+ \param gtype Define the type of the plot :
+ - 0 = Plot using points clouds.
+ - 1 = Plot using linear interpolation (segments).
+ - 2 = Plot with bars.
+ - 3 = Plot using cubic interpolation (3-polynomials).
+ - 4 = Plot using cross clouds.
+ \param ymin Lower bound of the y-range.
+ \param ymax Upper bound of the y-range.
+ \param opacity Drawing opacity.
+ \param pattern Drawing pattern.
+ \note
+ - if \c ymin==ymax==0, the y-range is computed automatically from the input sample.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_graph(const CImg<t>& data,
+ const tc *const color, const float opacity=1,
+ const unsigned int plot_type=1, const unsigned int vertex_type=1,
+ const double ymin=0, const double ymax=0,
+ const unsigned int pattern=~0U) {
+ if (is_empty() || height<=1) return *this;;
+ const unsigned long siz = data.size();
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_graph() : Specified color is (null)",
+ pixel_type());
+ tc *color1 = 0, *color2 = 0;
+ if (plot_type==3) {
+ color1 = new tc[dim]; color2 = new tc[dim];
+ cimg_forV(*this,k) { color1[k] = (tc)(color[k]*0.6f); color2[k] = (tc)(color[k]*0.3f); }
+ }
+
+ double m = ymin, M = ymax;
+ if (ymin==ymax) m = (double)data.maxmin(M);
+ if (m==M) { --m; ++M; }
+ const float ca = (float)(M-m)/(height-1);
+ bool init_hatch = true;
+
+ // Draw graph edges
+ switch (plot_type%4) {
+ case 1 : { // Segments
+ int oX = 0, oY = (int)((data[0]-m)/ca);
+ for (unsigned long off = 1; off<siz; ++off) {
+ const int
+ X = (int)(off*width/siz),
+ Y = (int)((data[off]-m)/ca);
+ draw_line(oX,oY,X,Y,color,opacity,pattern,init_hatch);
+ oX = X; oY = Y;
+ init_hatch = false;
+ }
+ } break;
+ case 2 : { // Spline
+ const CImg<t> ndata = data.get_shared_points(0,siz-1);
+ int oY = (int)((data[0]-m)/ca);
+ cimg_forX(*this,x) {
+ const int Y = (int)((ndata._cubic_atX((float)x*ndata.width/width)-m)/ca);
+ if (x>0) draw_line(x,oY,x+1,Y,color,opacity,pattern,init_hatch);
+ init_hatch = false;
+ oY = Y;
+ }
+ } break;
+ case 3 : { // Bars
+ const int Y0 = (int)(-m/ca);
+ int oX = 0;
+ cimg_foroff(data,off) {
+ const int
+ X = (off+1)*width/siz-1,
+ Y = (int)((data[off]-m)/ca);
+ draw_rectangle(oX,Y0,X,Y,color1,opacity).
+ draw_line(oX,Y,oX,Y0,color2,opacity).
+ draw_line(oX,Y0,X,Y0,Y<=Y0?color2:color,opacity).
+ draw_line(X,Y,X,Y0,color,opacity).
+ draw_line(oX,Y,X,Y,Y<=Y0?color:color2,opacity);
+ oX = X+1;
+ }
+ } break;
+ default : break; // No edges
+ }
+
+ // Draw graph points
+ switch (vertex_type%8) {
+ case 1 : { // Point
+ cimg_foroff(data,off) {
+ const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
+ draw_point(X,Y,color,opacity);
+ }
+ } break;
+ case 2 : { // Standard Cross
+ cimg_foroff(data,off) {
+ const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
+ draw_line(X-3,Y,X+3,Y,color,opacity).draw_line(X,Y-3,X,Y+3,color,opacity);
+ }
+ } break;
+ case 3 : { // Rotated Cross
+ cimg_foroff(data,off) {
+ const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
+ draw_line(X-3,Y-3,X+3,Y+3,color,opacity).draw_line(X-3,Y+3,X+3,Y-3,color,opacity);
+ }
+ } break;
+ case 4 : { // Filled Circle
+ cimg_foroff(data,off) {
+ const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
+ draw_circle(X,Y,3,color,opacity);
+ }
+ } break;
+ case 5 : { // Outlined circle
+ cimg_foroff(data,off) {
+ const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
+ draw_circle(X,Y,3,color,opacity,0U);
+ }
+ } break;
+ case 6 : { // Square
+ cimg_foroff(data,off) {
+ const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
+ draw_rectangle(X-3,Y-3,X+3,Y+3,color,opacity,~0U);
+ }
+ } break;
+ case 7 : { // Diamond
+ cimg_foroff(data,off) {
+ const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
+ draw_line(X,Y-4,X+4,Y,color,opacity).
+ draw_line(X+4,Y,X,Y+4,color,opacity).
+ draw_line(X,Y+4,X-4,Y,color,opacity).
+ draw_line(X-4,Y,X,Y-4,color,opacity);
+ }
+ } break;
+ default : break; // No vertices
+ }
+
+ if (color1) delete[] color1; if (color2) delete[] color2;
+ return *this;
+ }
+
+ //! Draw a 1D graph on the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_graph(const CImg<t>& data,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int plot_type=1, const unsigned int vertex_type=1,
+ const double ymin=0, const double ymax=0,
+ const unsigned int pattern=~0U) {
+ return draw_graph(data,color.data,opacity,plot_type,vertex_type,ymin,ymax,pattern);
+ }
+
+ //! Draw a labeled horizontal axis on the instance image.
+ /**
+ \param xvalues Lower bound of the x-range.
+ \param y Y-coordinate of the horizontal axis in the instance image.
+ \param color Array of dimv() values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern Drawing pattern.
+ \param opacity_out Drawing opacity of 'outside' axes.
+ \note if \c precision==0, precision of the labels is automatically computed.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U) {
+ if (!is_empty()) {
+ int siz = (int)xvalues.size()-1;
+ if (siz<=0) draw_line(0,y,width-1,y,color,opacity,pattern);
+ else {
+ if (xvalues[0]<xvalues[siz]) draw_arrow(0,y,width-1,y,color,opacity,30,5,pattern);
+ else draw_arrow(width-1,y,0,y,color,opacity,30,5,pattern);
+ const int yt = (y+14)<dimy()?(y+3):(y-14);
+ char txt[32];
+ cimg_foroff(xvalues,x) {
+ cimg_std::sprintf(txt,"%g",(double)xvalues(x));
+ const int xi = (int)(x*(width-1)/siz), xt = xi-(int)cimg::strlen(txt)*3;
+ draw_point(xi,y-1,color,opacity).draw_point(xi,y+1,color,opacity).
+ draw_text(xt<0?0:xt,yt,txt,color,(tc*)0,opacity,11);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a labeled horizontal axis on the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int pattern=~0U) {
+ return draw_axis(xvalues,y,color.data,opacity,pattern);
+ }
+
+ //! Draw a labeled vertical axis on the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U) {
+ if (!is_empty()) {
+ int siz = (int)yvalues.size()-1;
+ if (siz<=0) draw_line(x,0,x,height-1,color,opacity,pattern);
+ else {
+ if (yvalues[0]<yvalues[siz]) draw_arrow(x,0,x,height-1,color,opacity,30,5,pattern);
+ else draw_arrow(x,height-1,x,0,color,opacity,30,5,pattern);
+ char txt[32];
+ cimg_foroff(yvalues,y) {
+ cimg_std::sprintf(txt,"%g",(double)yvalues(y));
+ const int
+ yi = (int)(y*(height-1)/siz),
+ tmp = yi-5,
+ nyi = tmp<0?0:(tmp>=dimy()-11?dimy()-11:tmp),
+ xt = x-(int)cimg::strlen(txt)*7;
+ draw_point(x-1,yi,color,opacity).draw_point(x+1,yi,color,opacity);
+ if (xt>0) draw_text(xt,nyi,txt,color,(tc*)0,opacity,11);
+ else draw_text(x+3,nyi,txt,color,(tc*)0,opacity,11);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a labeled vertical axis on the instance image.
+ template<typename t, typename tc>
+ CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int pattern=~0U) {
+ return draw_axis(x,yvalues,color.data,opacity,pattern);
+ }
+
+ //! Draw a labeled horizontal+vertical axis on the instance image.
+ template<typename tx, typename ty, typename tc>
+ CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
+ const tc *const color, const float opacity=1,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ if (!is_empty()) {
+ const CImg<tx> nxvalues(xvalues.data,xvalues.size(),1,1,1,true);
+ const int sizx = (int)xvalues.size()-1, wm1 = (int)(width)-1;
+ if (sizx>0) {
+ float ox = (float)nxvalues[0];
+ for (unsigned int x = 1; x<width; ++x) {
+ const float nx = (float)nxvalues._linear_atX((float)x*sizx/wm1);
+ if (nx*ox<=0) { draw_axis(nx==0?x:x-1,yvalues,color,opacity,patterny); break; }
+ ox = nx;
+ }
+ }
+ const CImg<ty> nyvalues(yvalues.data,yvalues.size(),1,1,1,true);
+ const int sizy = (int)yvalues.size()-1, hm1 = (int)(height)-1;
+ if (sizy>0) {
+ float oy = (float)nyvalues[0];
+ for (unsigned int y = 1; y<height; ++y) {
+ const float ny = (float)nyvalues._linear_atX((float)y*sizy/hm1);
+ if (ny*oy<=0) { draw_axis(xvalues,ny==0?y:y-1,color,opacity,patternx); break; }
+ oy = ny;
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a labeled horizontal+vertical axis on the instance image.
+ template<typename tx, typename ty, typename tc>
+ CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ return draw_axis(xvalues,yvalues,color.data,opacity,patternx,patterny);
+ }
+
+ //! Draw a labeled horizontal+vertical axis on the instance image.
+ template<typename tc>
+ CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
+ const tc *const color, const float opacity=1,
+ const int subdivisionx=-60, const int subdivisiony=-60,
+ const float precisionx=0, const float precisiony=0,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ if (!is_empty()) {
+ const float
+ dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0),
+ px = (precisionx==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0):precisionx,
+ py = (precisiony==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0):precisiony;
+ draw_axis(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-dimx()/subdivisionx,x0,x1).round(px),
+ CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-dimy()/subdivisiony,y0,y1).round(py),
+ color,opacity,patternx,patterny);
+ }
+ return *this;
+ }
+
+ //! Draw a labeled horizontal+vertical axis on the instance image.
+ template<typename tc>
+ CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
+ const CImg<tc>& color, const float opacity=1,
+ const int subdivisionx=-60, const int subdivisiony=-60,
+ const float precisionx=0, const float precisiony=0,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ return draw_axis(x0,x1,y0,y1,color.data,opacity,subdivisionx,subdivisiony,precisionx,precisiony,patternx,patterny);
+ }
+
+ //! Draw grid.
+ template<typename tx, typename ty, typename tc>
+ CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
+ const tc *const color, const float opacity=1,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ if (!is_empty()) {
+ if (xvalues) cimg_foroff(xvalues,x) {
+ const int xi = (int)xvalues[x];
+ if (xi>=0 && xi<dimx()) draw_line(xi,0,xi,height-1,color,opacity,patternx);
+ }
+ if (yvalues) cimg_foroff(yvalues,y) {
+ const int yi = (int)yvalues[y];
+ if (yi>=0 && yi<dimy()) draw_line(0,yi,width-1,yi,color,opacity,patterny);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw grid.
+ template<typename tx, typename ty, typename tc>
+ CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ return draw_grid(xvalues,yvalues,color.data,opacity,patternx,patterny);
+ }
+
+ //! Draw grid.
+ template<typename tc>
+ CImg<T>& draw_grid(const float deltax, const float deltay,
+ const float offsetx, const float offsety,
+ const bool invertx, const bool inverty,
+ const tc *const color, const float opacity=1,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ CImg<uintT> seqx, seqy;
+ if (deltax!=0) {
+ const float dx = deltax>0?deltax:width*-deltax/100;
+ const unsigned int nx = (unsigned int)(width/dx);
+ seqx = CImg<uintT>::sequence(1+nx,0,(unsigned int)(dx*nx));
+ if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x)+offsetx,(float)width);
+ if (invertx) cimg_foroff(seqx,x) seqx(x) = width-1-seqx(x);
+ }
+
+ if (deltay!=0) {
+ const float dy = deltay>0?deltay:height*-deltay/100;
+ const unsigned int ny = (unsigned int)(height/dy);
+ seqy = CImg<uintT>::sequence(1+ny,0,(unsigned int)(dy*ny));
+ if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y)+offsety,(float)height);
+ if (inverty) cimg_foroff(seqy,y) seqy(y) = height-1-seqy(y);
+ }
+ return draw_grid(seqx,seqy,color,opacity,patternx,patterny);
+ }
+
+ //! Draw grid.
+ template<typename tc>
+ CImg<T>& draw_grid(const float deltax, const float deltay,
+ const float offsetx, const float offsety,
+ const bool invertx, const bool inverty,
+ const CImg<tc>& color, const float opacity=1,
+ const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+ return draw_grid(deltax,deltay,offsetx,offsety,invertx,inverty,color.data,opacity,patternx,patterny);
+ }
+
+ //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
+ /**
+ \param x X-coordinate of the starting point of the region to fill.
+ \param y Y-coordinate of the starting point of the region to fill.
+ \param z Z-coordinate of the starting point of the region to fill.
+ \param color An array of dimv() values of type \c T, defining the drawing color.
+ \param region Image that will contain the mask of the filled region mask, as an output.
+ \param sigma Tolerance concerning neighborhood values.
+ \param opacity Opacity of the drawing.
+ \param high_connexity Tells if 8-connexity must be used (only for 2D images).
+ \return \p region is initialized with the binary mask of the filled region.
+ **/
+ template<typename tc, typename t>
+ CImg<T>& draw_fill(const int x, const int y, const int z,
+ const tc *const color, const float opacity,
+ CImg<t>& region, const float sigma=0,
+ const bool high_connexity=false) {
+
+#define _cimg_draw_fill_test(x,y,z,res) if (region(x,y,z)) res = false; else { \
+ res = true; \
+ const T *reference_col = reference_color.ptr() + dim, *ptrs = ptr(x,y,z) + siz; \
+ for (unsigned int i = dim; res && i; --i) { ptrs-=whz; res = (cimg::abs(*ptrs - *(--reference_col))<=sigma); } \
+ region(x,y,z) = (t)(res?1:noregion); \
+}
+
+#define _cimg_draw_fill_set(x,y,z) { \
+ const tc *col = color; \
+ T *ptrd = ptr(x,y,z); \
+ if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } \
+ else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; } \
+}
+
+#define _cimg_draw_fill_insert(x,y,z) { \
+ if (posr1>=remaining.height) remaining.resize(3,remaining.height<<1,1,1,0); \
+ unsigned int *ptrr = remaining.ptr(0,posr1); \
+ *(ptrr++) = x; *(ptrr++) = y; *(ptrr++) = z; ++posr1; \
+}
+
+#define _cimg_draw_fill_test_neighbor(x,y,z,cond) if (cond) { \
+ const unsigned int tx = x, ty = y, tz = z; \
+ _cimg_draw_fill_test(tx,ty,tz,res); if (res) _cimg_draw_fill_insert(tx,ty,tz); \
+}
+
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_fill() : Specified color is (null).",
+ pixel_type());
+ region.assign(width,height,depth,1,(t)0);
+ if (x>=0 && x<dimx() && y>=0 && y<dimy() && z>=0 && z<dimz()) {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const unsigned int whz = width*height*depth, siz = dim*whz, W1 = width-1, H1 = height-1, D1 = depth-1;
+ const bool threed = depth>1;
+ const CImg<T> reference_color = get_vector_at(x,y,z);
+ CImg<uintT> remaining(3,512,1,1,0);
+ remaining(0,0) = x; remaining(1,0) = y; remaining(2,0) = z;
+ unsigned int posr0 = 0, posr1 = 1;
+ region(x,y,z) = (t)1;
+ const t noregion = ((t)1==(t)2)?(t)0:(t)(-1);
+ if (threed) do { // 3D version of the filling algorithm
+ const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++), zc = *(pcurr++);
+ if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
+ bool cont, res;
+ unsigned int nxc = xc;
+ do { // X-backward
+ _cimg_draw_fill_set(nxc,yc,zc);
+ _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
+ _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
+ _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
+ _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
+ if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
+ } while (cont);
+ nxc = xc;
+ do { // X-forward
+ if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(nxc,yc,zc);
+ _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
+ _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
+ _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
+ _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
+ }
+ } while (cont);
+ unsigned int nyc = yc;
+ do { // Y-backward
+ if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(xc,nyc,zc);
+ _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
+ _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
+ _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
+ _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
+ }
+ } while (cont);
+ nyc = yc;
+ do { // Y-forward
+ if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(xc,nyc,zc);
+ _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
+ _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
+ _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
+ _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
+ }
+ } while (cont);
+ unsigned int nzc = zc;
+ do { // Z-backward
+ if (nzc) { --nzc; _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(xc,yc,nzc);
+ _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
+ _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
+ _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
+ _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
+ }
+ } while (cont);
+ nzc = zc;
+ do { // Z-forward
+ if ((++nzc)<=D1) { _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(xc,nyc,zc);
+ _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
+ _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
+ _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
+ _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
+ }
+ } while (cont);
+ } while (posr1>posr0);
+ else do { // 2D version of the filling algorithm
+ const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++);
+ if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
+ bool cont, res;
+ unsigned int nxc = xc;
+ do { // X-backward
+ _cimg_draw_fill_set(nxc,yc,0);
+ _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
+ _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
+ if (high_connexity) {
+ _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
+ _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
+ _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
+ _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
+ }
+ if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
+ } while (cont);
+ nxc = xc;
+ do { // X-forward
+ if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(nxc,yc,0);
+ _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
+ _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
+ if (high_connexity) {
+ _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
+ _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
+ _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
+ _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
+ }
+ }
+ } while (cont);
+ unsigned int nyc = yc;
+ do { // Y-backward
+ if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(xc,nyc,0);
+ _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
+ _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
+ if (high_connexity) {
+ _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
+ _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
+ _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
+ _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
+ }
+ }
+ } while (cont);
+ nyc = yc;
+ do { // Y-forward
+ if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
+ if (cont) {
+ _cimg_draw_fill_set(xc,nyc,0);
+ _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
+ _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
+ if (high_connexity) {
+ _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
+ _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
+ _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
+ _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
+ }
+ }
+ } while (cont);
+ } while (posr1>posr0);
+ if (noregion) cimg_for(region,ptr,t) if (*ptr==noregion) *ptr = (t)0;
+ }
+ return *this;
+ }
+
+ //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
+ template<typename tc, typename t>
+ CImg<T>& draw_fill(const int x, const int y, const int z,
+ const CImg<tc>& color, const float opacity,
+ CImg<t>& region, const float sigma=0, const bool high_connexity=false) {
+ return draw_fill(x,y,z,color.data,opacity,region,sigma,high_connexity);
+ }
+
+ //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
+ /**
+ \param x = X-coordinate of the starting point of the region to fill.
+ \param y = Y-coordinate of the starting point of the region to fill.
+ \param z = Z-coordinate of the starting point of the region to fill.
+ \param color = an array of dimv() values of type \c T, defining the drawing color.
+ \param sigma = tolerance concerning neighborhood values.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename tc>
+ CImg<T>& draw_fill(const int x, const int y, const int z,
+ const tc *const color, const float opacity=1,
+ const float sigma=0, const bool high_connexity=false) {
+ CImg<boolT> tmp;
+ return draw_fill(x,y,z,color,opacity,tmp,sigma,high_connexity);
+ }
+
+ //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
+ template<typename tc>
+ CImg<T>& draw_fill(const int x, const int y, const int z,
+ const CImg<tc>& color, const float opacity=1,
+ const float sigma=0, const bool high_connexity=false) {
+ return draw_fill(x,y,z,color.data,opacity,sigma,high_connexity);
+ }
+
+ //! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image.
+ /**
+ \param x = X-coordinate of the starting point of the region to fill.
+ \param y = Y-coordinate of the starting point of the region to fill.
+ \param color = an array of dimv() values of type \c T, defining the drawing color.
+ \param sigma = tolerance concerning neighborhood values.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename tc>
+ CImg<T>& draw_fill(const int x, const int y,
+ const tc *const color, const float opacity=1,
+ const float sigma=0, const bool high_connexity=false) {
+ CImg<boolT> tmp;
+ return draw_fill(x,y,0,color,opacity,tmp,sigma,high_connexity);
+ }
+
+ //! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image.
+ template<typename tc>
+ CImg<T>& draw_fill(const int x, const int y,
+ const CImg<tc>& color, const float opacity=1,
+ const float sigma=0, const bool high_connexity=false) {
+ return draw_fill(x,y,color.data,opacity,sigma,high_connexity);
+ }
+
+ //! Draw a plasma random texture.
+ /**
+ \param x0 = X-coordinate of the upper-left corner of the plasma.
+ \param y0 = Y-coordinate of the upper-left corner of the plasma.
+ \param x1 = X-coordinate of the lower-right corner of the plasma.
+ \param y1 = Y-coordinate of the lower-right corner of the plasma.
+ \param alpha = Alpha-parameter of the plasma.
+ \param beta = Beta-parameter of the plasma.
+ \param opacity = opacity of the drawing.
+ **/
+ CImg<T>& draw_plasma(const int x0, const int y0, const int x1, const int y1,
+ const float alpha=1, const float beta=1,
+ const float opacity=1) {
+ if (!is_empty()) {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ int nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1;
+ if (nx1<nx0) cimg::swap(nx0,nx1);
+ if (ny1<ny0) cimg::swap(ny0,ny1);
+ if (nx0<0) nx0 = 0;
+ if (nx1>=dimx()) nx1 = width-1;
+ if (ny0<0) ny0 = 0;
+ if (ny1>=dimy()) ny1 = height-1;
+ const int xc = (nx0+nx1)/2, yc = (ny0+ny1)/2, dx = (xc-nx0), dy = (yc-ny0);
+ const Tfloat dc = (Tfloat)(cimg_std::sqrt((float)(dx*dx+dy*dy))*alpha + beta);
+ Tfloat val = 0;
+ cimg_forV(*this,k) {
+ if (opacity>=1) {
+ const Tfloat
+ val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)),
+ val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k));
+ (*this)(xc,ny0,0,k) = (T)((val0+val1)/2);
+ (*this)(xc,ny1,0,k) = (T)((val2+val3)/2);
+ (*this)(nx0,yc,0,k) = (T)((val0+val2)/2);
+ (*this)(nx1,yc,0,k) = (T)((val1+val3)/2);
+ do {
+ val = (Tfloat)(0.25f*((Tfloat)((*this)(nx0,ny0,0,k)) +
+ (Tfloat)((*this)(nx1,ny0,0,k)) +
+ (Tfloat)((*this)(nx1,ny1,0,k)) +
+ (Tfloat)((*this)(nx0,ny1,0,k))) +
+ dc*cimg::grand());
+ } while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
+ (*this)(xc,yc,0,k) = (T)val;
+ } else {
+ const Tfloat
+ val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)),
+ val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k));
+ (*this)(xc,ny0,0,k) = (T)(((val0+val1)*nopacity + copacity*(*this)(xc,ny0,0,k))/2);
+ (*this)(xc,ny1,0,k) = (T)(((val2+val3)*nopacity + copacity*(*this)(xc,ny1,0,k))/2);
+ (*this)(nx0,yc,0,k) = (T)(((val0+val2)*nopacity + copacity*(*this)(nx0,yc,0,k))/2);
+ (*this)(nx1,yc,0,k) = (T)(((val1+val3)*nopacity + copacity*(*this)(nx1,yc,0,k))/2);
+ do {
+ val = (Tfloat)(0.25f*(((Tfloat)((*this)(nx0,ny0,0,k)) +
+ (Tfloat)((*this)(nx1,ny0,0,k)) +
+ (Tfloat)((*this)(nx1,ny1,0,k)) +
+ (Tfloat)((*this)(nx0,ny1,0,k))) +
+ dc*cimg::grand())*nopacity + copacity*(*this)(xc,yc,0,k));
+ } while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
+ (*this)(xc,yc,0,k) = (T)val;
+ }
+ }
+ if (xc!=nx0 || yc!=ny0) {
+ draw_plasma(nx0,ny0,xc,yc,alpha,beta,opacity);
+ draw_plasma(xc,ny0,nx1,yc,alpha,beta,opacity);
+ draw_plasma(nx0,yc,xc,ny1,alpha,beta,opacity);
+ draw_plasma(xc,yc,nx1,ny1,alpha,beta,opacity);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a plasma random texture.
+ /**
+ \param alpha = Alpha-parameter of the plasma.
+ \param beta = Beta-parameter of the plasma.
+ \param opacity = opacity of the drawing.
+ **/
+ CImg<T>& draw_plasma(const float alpha=1, const float beta=1,
+ const float opacity=1) {
+ return draw_plasma(0,0,width-1,height-1,alpha,beta,opacity);
+ }
+
+ //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
+ template<typename tc>
+ CImg<T>& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1,
+ const CImg<tc>& color_palette, const float opacity=1,
+ const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
+ const unsigned int itermax=255,
+ const bool normalized_iteration=false,
+ const bool julia_set=false,
+ const double paramr=0, const double parami=0) {
+ if (is_empty()) return *this;
+ CImg<tc> palette;
+ if (color_palette) palette.assign(color_palette.data,color_palette.size()/color_palette.dim,1,1,color_palette.dim,true);
+ if (palette && palette.dim!=dim)
+ throw CImgArgumentException("CImg<%s>::draw_mandelbrot() : Specified color palette (%u,%u,%u,%u,%p) is not \n"
+ "compatible with instance image (%u,%u,%u,%u,%p).",
+ pixel_type(),color_palette.width,color_palette.height,color_palette.depth,color_palette.dim,
+ color_palette.data,width,height,depth,dim,data);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), ln2 = (float)cimg_std::log(2.0);
+ unsigned int iter = 0;
+ cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
+ const double x = z0r + p*(z1r-z0r)/width, y = z0i + q*(z1i-z0i)/height;
+ double zr, zi, cr, ci;
+ if (julia_set) { zr = x; zi = y; cr = paramr; ci = parami; }
+ else { zr = paramr; zi = parami; cr = x; ci = y; }
+ for (iter=1; zr*zr + zi*zi<=4 && iter<=itermax; ++iter) {
+ const double temp = zr*zr - zi*zi + cr;
+ zi = 2*zr*zi + ci;
+ zr = temp;
+ }
+ if (iter>itermax) {
+ if (palette) {
+ if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette(0,k);
+ else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(0,k)*nopacity + (*this)(p,q,0,k)*copacity);
+ } else {
+ if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)0;
+ else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)((*this)(p,q,0,k)*copacity);
+ }
+ } else if (normalized_iteration) {
+ const float
+ normz = (float)cimg::abs(zr*zr+zi*zi),
+ niter = (float)(iter + 1 - cimg_std::log(cimg_std::log(normz))/ln2);
+ if (palette) {
+ if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._linear_atX(niter,k);
+ else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette._linear_atX(niter,k)*nopacity + (*this)(p,q,0,k)*copacity);
+ } else {
+ if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)niter;
+ else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(niter*nopacity + (*this)(p,q,0,k)*copacity);
+ }
+ } else {
+ if (palette) {
+ if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._atX(iter,k);
+ else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(iter,k)*nopacity + (*this)(p,q,0,k)*copacity);
+ } else {
+ if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)iter;
+ else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(iter*nopacity + (*this)(p,q,0,k)*copacity);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
+ template<typename tc>
+ CImg<T>& draw_mandelbrot(const CImg<tc>& color_palette, const float opacity=1,
+ const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
+ const unsigned int itermax=255,
+ const bool normalized_iteration=false,
+ const bool julia_set=false,
+ const double paramr=0, const double parami=0) {
+ return draw_mandelbrot(0,0,width-1,height-1,color_palette,opacity,z0r,z0i,z1r,z1i,itermax,normalized_iteration,julia_set,paramr,parami);
+ }
+
+ //! Draw a 1D gaussian function in the instance image.
+ /**
+ \param xc = X-coordinate of the gaussian center.
+ \param sigma = Standard variation of the gaussian distribution.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float sigma,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",
+ pixel_type());
+ const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const unsigned int whz = width*height*depth;
+ const tc *col = color;
+ cimg_forX(*this,x) {
+ const float dx = (x - xc), val = (float)cimg_std::exp(-dx*dx/sigma2);
+ T *ptrd = ptr(x,0,0,0);
+ if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
+ else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
+ col-=dim;
+ }
+ return *this;
+ }
+
+ //! Draw a 1D gaussian function in the instance image.
+ template<typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float sigma,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_gaussian(xc,sigma,color.data,opacity);
+ }
+
+ //! Draw an anisotropic 2D gaussian function.
+ /**
+ \param xc = X-coordinate of the gaussian center.
+ \param yc = Y-coordinate of the gaussian center.
+ \param tensor = 2x2 covariance matrix.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ typedef typename cimg::superset<t,float>::type tfloat;
+ if (tensor.width!=2 || tensor.height!=2 || tensor.depth!=1 || tensor.dim!=1)
+ throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 2x2 matrix.",
+ pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
+ if (!color)
+ throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",
+ pixel_type());
+ const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
+ const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = invT2(1,1);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const unsigned int whz = width*height*depth;
+ const tc *col = color;
+ float dy = -yc;
+ cimg_forY(*this,y) {
+ float dx = -xc;
+ cimg_forX(*this,x) {
+ const float val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dy*dy);
+ T *ptrd = ptr(x,y,0,0);
+ if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
+ else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
+ col-=dim;
+ ++dx;
+ }
+ ++dy;
+ }
+ return *this;
+ }
+
+ //! Draw an anisotropic 2D gaussian function.
+ template<typename t, typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_gaussian(xc,yc,tensor,color.data,opacity);
+ }
+
+ //! Draw an anisotropic 2D gaussian function.
+ template<typename tc>
+ CImg<T>& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv,
+ const tc *const color, const float opacity=1) {
+ const double
+ a = r1*ru*ru + r2*rv*rv,
+ b = (r1-r2)*ru*rv,
+ c = r1*rv*rv + r2*ru*ru;
+ const CImg<Tfloat> tensor(2,2,1,1, a,b,b,c);
+ return draw_gaussian(xc,yc,tensor,color,opacity);
+ }
+
+ //! Draw an anisotropic 2D gaussian function.
+ template<typename tc>
+ CImg<T>& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_gaussian(xc,yc,r1,r2,ru,rv,color.data,opacity);
+ }
+
+ //! Draw an isotropic 2D gaussian function.
+ /**
+ \param xc = X-coordinate of the gaussian center.
+ \param yc = Y-coordinate of the gaussian center.
+ \param sigma = standard variation of the gaussian distribution.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma,
+ const tc *const color, const float opacity=1) {
+ return draw_gaussian(xc,yc,CImg<floatT>::diagonal(sigma,sigma),color,opacity);
+ }
+
+ //! Draw an isotropic 2D gaussian function.
+ template<typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_gaussian(xc,yc,sigma,color.data,opacity);
+ }
+
+ //! Draw an anisotropic 3D gaussian function.
+ /**
+ \param xc = X-coordinate of the gaussian center.
+ \param yc = Y-coordinate of the gaussian center.
+ \param zc = Z-coordinate of the gaussian center.
+ \param tensor = 3x3 covariance matrix.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ typedef typename cimg::superset<t,float>::type tfloat;
+ if (tensor.width!=3 || tensor.height!=3 || tensor.depth!=1 || tensor.dim!=1)
+ throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 3x3 matrix.",
+ pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
+ const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
+ const tfloat a = invT(0,0), b = 2*invT(1,0), c = 2*invT(2,0), d = invT(1,1), e = 2*invT(2,1), f = invT(2,2);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+ const unsigned int whz = width*height*depth;
+ const tc *col = color;
+ cimg_forXYZ(*this,x,y,z) {
+ const float
+ dx = (x - xc), dy = (y - yc), dz = (z - zc),
+ val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz);
+ T *ptrd = ptr(x,y,z,0);
+ if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
+ else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
+ col-=dim;
+ }
+ return *this;
+ }
+
+ //! Draw an anisotropic 3D gaussian function.
+ template<typename t, typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_gaussian(xc,yc,zc,tensor,color.data,opacity);
+ }
+
+ //! Draw an isotropic 3D gaussian function.
+ /**
+ \param xc = X-coordinate of the gaussian center.
+ \param yc = Y-coordinate of the gaussian center.
+ \param zc = Z-coordinate of the gaussian center.
+ \param sigma = standard variation of the gaussian distribution.
+ \param color = array of dimv() values of type \c T, defining the drawing color.
+ \param opacity = opacity of the drawing.
+ **/
+ template<typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const float sigma,
+ const tc *const color, const float opacity=1) {
+ return draw_gaussian(xc,yc,zc,CImg<floatT>::diagonal(sigma,sigma,sigma),color,opacity);
+ }
+
+ //! Draw an isotropic 3D gaussian function.
+ template<typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const float sigma,
+ const CImg<tc>& color, const float opacity=1) {
+ return draw_gaussian(xc,yc,zc,sigma,color.data,opacity);
+ }
+
+ // Draw a 3D object (internal)
+ template<typename tc, typename to>
+ void _draw_object3d_sprite(const int x, const int y,
+ const CImg<tc>& color, const CImg<to>& opacity, const CImg<T>& sprite) {
+ if (opacity.width==color.width && opacity.height==color.height)
+ draw_image(x,y,sprite,opacity.get_resize(sprite.width,sprite.height,1,sprite.dim,1));
+ else
+ draw_image(x,y,sprite,opacity(0));
+ }
+
+ template<typename tc>
+ void _draw_object3d_sprite(const int x, const int y,
+ const CImg<tc>& color, const float opacity, const CImg<T>& sprite) {
+ if (color) draw_image(x,y,sprite,opacity);
+ }
+
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& _draw_object3d(void *const pboard, float *const zbuffer,
+ const float X, const float Y, const float Z,
+ const tp& points, const unsigned int nb_points,
+ const CImgList<tf>& primitives,
+ const CImgList<tc>& colors,
+ const to& opacities, const unsigned int nb_opacities,
+ const unsigned int render_type,
+ const bool double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_light, const float specular_shine) {
+ if (is_empty()) return *this;
+#ifndef cimg_use_board
+ if (pboard) return *this;
+#endif
+ const float
+ nspec = 1-(specular_light<0?0:(specular_light>1?1:specular_light)),
+ nspec2 = 1+(specular_shine<0?0:specular_shine),
+ nsl1 = (nspec2-1)/cimg::sqr(nspec-1),
+ nsl2 = (1-2*nsl1*nspec),
+ nsl3 = nspec2-nsl1-nsl2;
+
+ // Create light texture for phong-like rendering
+ static CImg<floatT> light_texture;
+ if (render_type==5) {
+ if (colors.size>primitives.size) light_texture.assign(colors[primitives.size])/=255;
+ else {
+ static float olightx = 0, olighty = 0, olightz = 0, ospecular_shine = 0;
+ if (!light_texture || lightx!=olightx || lighty!=olighty || lightz!=olightz || specular_shine!=ospecular_shine) {
+ light_texture.assign(512,512);
+ const float white[] = { 1 },
+ dlx = lightx-X, dly = lighty-Y, dlz = lightz-Z,
+ nl = (float)cimg_std::sqrt(dlx*dlx+dly*dly+dlz*dlz),
+ nlx = light_texture.width/2*(1+dlx/nl),
+ nly = light_texture.height/2*(1+dly/nl);
+ light_texture.draw_gaussian(nlx,nly,light_texture.width/3.0f,white);
+ cimg_forXY(light_texture,x,y) {
+ const float factor = light_texture(x,y);
+ if (factor>nspec) light_texture(x,y) = cimg::min(2,nsl1*factor*factor+nsl2*factor+nsl3);
+ }
+ olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shine = specular_shine;
+ }
+ }
+ }
+
+ // Compute 3D to 2D projection
+ CImg<floatT> projections(nb_points,2);
+ cimg_forX(projections,l) {
+ const float
+ x = (float)points(l,0),
+ y = (float)points(l,1),
+ z = (float)points(l,2);
+ const float projectedz = z + Z + focale;
+ projections(l,1) = Y + focale*y/projectedz;
+ projections(l,0) = X + focale*x/projectedz;
+ }
+
+ // Compute and sort visible primitives
+ CImg<uintT> visibles(primitives.size);
+ CImg<floatT> zrange(primitives.size);
+ unsigned int nb_visibles = 0;
+ const float zmin = -focale+1.5f;
+ { cimglist_for(primitives,l) {
+ const CImg<tf>& primitive = primitives[l];
+ switch (primitive.size()) {
+
+ case 1 : { // Point
+ const unsigned int i0 = (unsigned int)primitive(0);
+ const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
+ if (z0>zmin && x0>=0 && x0<width && y0>=0 && y0<height) {
+ visibles(nb_visibles) = (unsigned int)l;
+ zrange(nb_visibles++) = z0;
+ }
+ } break;
+ case 5 : { // Sphere
+ const unsigned int
+ i0 = (unsigned int)primitive(0),
+ i1 = (unsigned int)primitive(1),
+ i2 = (unsigned int)primitive(2);
+ const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
+ int radius;
+ if (i2) radius = (int)(i2*focale/(z0+focale));
+ else {
+ const float x1 = projections(i1,0), y1 = projections(i1,1);
+ const int deltax = (int)(x1-x0), deltay = (int)(y1-y0);
+ radius = (int)cimg_std::sqrt((float)(deltax*deltax + deltay*deltay));
+ }
+ if (z0>zmin && x0+radius>=0 && x0-radius<width && y0+radius>=0 && y0-radius<height) {
+ visibles(nb_visibles) = (unsigned int)l;
+ zrange(nb_visibles++) = z0;
+ }
+ } break;
+ case 2 : // Line
+ case 6 : {
+ const unsigned int
+ i0 = (unsigned int)primitive(0),
+ i1 = (unsigned int)primitive(1);
+ const float
+ x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
+ x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2));
+ float xm, xM, ym, yM;
+ if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
+ if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
+ if (z0>zmin && z1>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
+ visibles(nb_visibles) = (unsigned int)l;
+ zrange(nb_visibles++) = 0.5f*(z0+z1);
+ }
+ } break;
+ case 3 : // Triangle
+ case 9 : {
+ const unsigned int
+ i0 = (unsigned int)primitive(0),
+ i1 = (unsigned int)primitive(1),
+ i2 = (unsigned int)primitive(2);
+ const float
+ x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
+ x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
+ x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2));
+ float xm, xM, ym, yM;
+ if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
+ if (x2<xm) xm = x2;
+ if (x2>xM) xM = x2;
+ if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
+ if (y2<ym) ym = y2;
+ if (y2>yM) yM = y2;
+ if (z0>zmin && z1>zmin && z2>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
+ const float d = (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0);
+ if (double_sided || d<0) {
+ visibles(nb_visibles) = (unsigned int)l;
+ zrange(nb_visibles++) = (z0+z1+z2)/3;
+ }
+ }
+ } break;
+ case 4 : // Rectangle
+ case 12 : {
+ const unsigned int
+ i0 = (unsigned int)primitive(0),
+ i1 = (unsigned int)primitive(1),
+ i2 = (unsigned int)primitive(2),
+ i3 = (unsigned int)primitive(3);
+ const float
+ x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
+ x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
+ x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2)),
+ x3 = projections(i3,0), y3 = projections(i3,1), z3 = (float)(Z+points(i3,2));
+ float xm, xM, ym, yM;
+ if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
+ if (x2<xm) xm = x2;
+ if (x2>xM) xM = x2;
+ if (x3<xm) xm = x3;
+ if (x3>xM) xM = x3;
+ if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
+ if (y2<ym) ym = y2;
+ if (y2>yM) yM = y2;
+ if (y3<ym) ym = y3;
+ if (y3>yM) yM = y3;
+ if (z0>zmin && z1>zmin && z2>zmin && z3>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
+ const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0);
+ if (double_sided || d<0) {
+ visibles(nb_visibles) = (unsigned int)l;
+ zrange(nb_visibles++) = (z0 + z1 + z2 + z3)/4;
+ }
+ }
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::draw_object3d() : Primitive %u is invalid (size = %u, can be 1,2,3,4,5,6,9 or 12)",
+ pixel_type(),l,primitive.size());
+ }}
+ }
+ if (nb_visibles<=0) return *this;
+ CImg<uintT> permutations;
+ CImg<floatT>(zrange.data,nb_visibles,1,1,1,true).sort(permutations,false);
+
+ // Compute light properties
+ CImg<floatT> lightprops;
+ switch (render_type) {
+ case 3 : { // Flat Shading
+ lightprops.assign(nb_visibles);
+ cimg_forX(lightprops,l) {
+ const CImg<tf>& primitive = primitives(visibles(permutations(l)));
+ const unsigned int psize = primitive.size();
+ if (psize==3 || psize==4 || psize==9 || psize==12) {
+ const unsigned int
+ i0 = (unsigned int)primitive(0),
+ i1 = (unsigned int)primitive(1),
+ i2 = (unsigned int)primitive(2);
+ const float
+ x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
+ x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
+ x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
+ dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
+ dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
+ nx = dy1*dz2 - dz1*dy2,
+ ny = dz1*dx2 - dx1*dz2,
+ nz = dx1*dy2 - dy1*dx2,
+ norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
+ lx = X + (x0 + x1 + x2)/3 - lightx,
+ ly = Y + (y0 + y1 + y2)/3 - lighty,
+ lz = Z + (z0 + z1 + z2)/3 - lightz,
+ nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
+ factor = cimg::max(cimg::abs(-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
+ lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
+ } else lightprops[l] = 1;
+ }
+ } break;
+
+ case 4 : // Gouraud Shading
+ case 5 : { // Phong-Shading
+ CImg<floatT> points_normals(nb_points,3,1,1,0);
+ for (unsigned int l=0; l<nb_visibles; ++l) {
+ const CImg<tf>& primitive = primitives[visibles(l)];
+ const unsigned int psize = primitive.size();
+ const bool
+ triangle_flag = (psize==3) || (psize==9),
+ rectangle_flag = (psize==4) || (psize==12);
+ if (triangle_flag || rectangle_flag) {
+ const unsigned int
+ i0 = (unsigned int)primitive(0),
+ i1 = (unsigned int)primitive(1),
+ i2 = (unsigned int)primitive(2),
+ i3 = rectangle_flag?(unsigned int)primitive(3):0;
+ const float
+ x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
+ x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
+ x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
+ dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
+ dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
+ nnx = dy1*dz2 - dz1*dy2,
+ nny = dz1*dx2 - dx1*dz2,
+ nnz = dx1*dy2 - dy1*dx2,
+ norm = 1e-5f + (float)cimg_std::sqrt(nnx*nnx + nny*nny + nnz*nnz),
+ nx = nnx/norm,
+ ny = nny/norm,
+ nz = nnz/norm;
+ points_normals(i0,0)+=nx; points_normals(i0,1)+=ny; points_normals(i0,2)+=nz;
+ points_normals(i1,0)+=nx; points_normals(i1,1)+=ny; points_normals(i1,2)+=nz;
+ points_normals(i2,0)+=nx; points_normals(i2,1)+=ny; points_normals(i2,2)+=nz;
+ if (rectangle_flag) { points_normals(i3,0)+=nx; points_normals(i3,1)+=ny; points_normals(i3,2)+=nz; }
+ }
+ }
+
+ if (double_sided) cimg_forX(points_normals,p) if (points_normals(p,2)>0) {
+ points_normals(p,0) = -points_normals(p,0);
+ points_normals(p,1) = -points_normals(p,1);
+ points_normals(p,2) = -points_normals(p,2);
+ }
+
+ if (render_type==4) {
+ lightprops.assign(nb_points);
+ cimg_forX(lightprops,ll) {
+ const float
+ nx = points_normals(ll,0),
+ ny = points_normals(ll,1),
+ nz = points_normals(ll,2),
+ norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
+ lx = (float)(X + points(ll,0) - lightx),
+ ly = (float)(Y + points(ll,1) - lighty),
+ lz = (float)(Z + points(ll,2) - lightz),
+ nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
+ factor = cimg::max((-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
+ lightprops[ll] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
+ }
+ } else {
+ const unsigned int
+ lw2 = light_texture.width/2 - 1,
+ lh2 = light_texture.height/2 - 1;
+ lightprops.assign(nb_points,2);
+ cimg_forX(lightprops,ll) {
+ const float
+ nx = points_normals(ll,0),
+ ny = points_normals(ll,1),
+ nz = points_normals(ll,2),
+ norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
+ nnx = nx/norm,
+ nny = ny/norm;
+ lightprops(ll,0) = lw2*(1 + nnx);
+ lightprops(ll,1) = lh2*(1 + nny);
+ }
+ }
+ } break;
+ }
+
+ // Draw visible primitives
+ const CImg<tc> default_color(1,dim,1,1,(tc)200);
+ { for (unsigned int l = 0; l<nb_visibles; ++l) {
+ const unsigned int n_primitive = visibles(permutations(l));
+ const CImg<tf>& primitive = primitives[n_primitive];
+ const CImg<tc>& color = n_primitive<colors.size?colors[n_primitive]:default_color;
+ const float opac = n_primitive<nb_opacities?opacities(n_primitive,0):1.0f;
+#ifdef cimg_use_board
+ BoardLib::Board &board = *(BoardLib::Board*)pboard;
+#endif
+
+ switch (primitive.size()) {
+ case 1 : { // Colored point or sprite
+ const unsigned int n0 = (unsigned int)primitive[0];
+ const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
+ if (color.size()==dim) {
+ draw_point(x0,y0,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.fillCircle((float)x0,dimy()-(float)y0,0);
+ }
+#endif
+ } else {
+ const float z = Z + points(n0,2);
+ const int
+ factor = (int)(focale*100/(z+focale)),
+ sw = color.width*factor/200,
+ sh = color.height*factor/200;
+ if (x0+sw>=0 && x0-sw<dimx() && y0+sh>=0 && y0-sh<dimy()) {
+ const CImg<T> sprite = color.get_resize(-factor,-factor,1,-100,render_type<=3?1:3);
+ _draw_object3d_sprite(x0-sw,y0-sh,color,opacities[n_primitive%nb_opacities],sprite);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128);
+ board.setFillColor(BoardLib::Color::none);
+ board.drawRectangle((float)x0-sw,dimy()-(float)y0+sh,sw,sh);
+ }
+#endif
+ }
+ }
+ } break;
+ case 2 : { // Colored line
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1];
+ const int
+ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
+ const float
+ z0 = points(n0,2) + Z + focale,
+ z1 = points(n1,2) + Z + focale;
+ if (render_type) {
+ if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac);
+ else draw_line(x0,y0,x1,y1,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.drawLine((float)x0,dimy()-(float)y0,x1,dimy()-(float)y1);
+ }
+#endif
+ } else {
+ draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.drawCircle((float)x0,dimy()-(float)y0,0);
+ board.drawCircle((float)x1,dimy()-(float)y1,0);
+ }
+#endif
+ }
+ } break;
+ case 5 : { // Colored sphere
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ n2 = (unsigned int)primitive[2];
+ const int
+ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
+ int radius;
+ if (n2) radius = (int)(n2*focale/(Z+points(n0,2)+focale));
+ else {
+ const int
+ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+ deltax = x1-x0, deltay = y1-y0;
+ radius = (int)cimg_std::sqrt((float)(deltax*deltax + deltay*deltay));
+ }
+ switch (render_type) {
+ case 0 :
+ draw_point(x0,y0,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.fillCircle((float)x0,dimy()-(float)y0,0);
+ }
+#endif
+ break;
+ case 1 :
+ draw_circle(x0,y0,radius,color,opac,~0U);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.setFillColor(BoardLib::Color::none);
+ board.drawCircle((float)x0,dimy()-(float)y0,(float)radius);
+ }
+#endif
+ break;
+ default :
+ draw_circle(x0,y0,radius,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.fillCircle((float)x0,dimy()-(float)y0,(float)radius);
+ }
+#endif
+ break;
+ }
+ } break;
+ case 6 : { // Textured line
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ tx0 = (unsigned int)primitive[2],
+ ty0 = (unsigned int)primitive[3],
+ tx1 = (unsigned int)primitive[4],
+ ty1 = (unsigned int)primitive[5];
+ const int
+ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
+ const float
+ z0 = points(n0,2) + Z + focale,
+ z1 = points(n1,2) + Z + focale;
+ if (render_type) {
+ if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac);
+ else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
+ }
+#endif
+ } else {
+ draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
+ draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.drawCircle((float)x0,dimy()-(float)y0,0);
+ board.drawCircle((float)x1,dimy()-(float)y1,0);
+ }
+#endif
+ }
+ } break;
+ case 3 : { // Colored triangle
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ n2 = (unsigned int)primitive[2];
+ const int
+ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+ x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
+ const float
+ z0 = points(n0,2) + Z + focale,
+ z1 = points(n1,2) + Z + focale,
+ z2 = points(n2,2) + Z + focale;
+ switch (render_type) {
+ case 0 :
+ draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).draw_point(x2,y2,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.drawCircle((float)x0,dimy()-(float)y0,0);
+ board.drawCircle((float)x1,dimy()-(float)y1,0);
+ board.drawCircle((float)x2,dimy()-(float)y2,0);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,opac).
+ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac);
+ else
+ draw_line(x0,y0,x1,y1,color,opac).draw_line(x0,y0,x2,y2,color,opac).
+ draw_line(x1,y1,x2,y2,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
+ board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2);
+ board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ }
+#endif
+ break;
+ case 2 :
+ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac);
+ else draw_triangle(x0,y0,x1,y1,x2,y2,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ }
+#endif
+ break;
+ case 3 :
+ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l));
+ else _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = cimg::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(color[0]*lp),
+ (unsigned char)(color[1]*lp),
+ (unsigned char)(color[2]*lp),
+ (unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ }
+#endif
+ break;
+ case 4 :
+ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+ else draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi((unsigned char)(color[0]),
+ (unsigned char)(color[1]),
+ (unsigned char)(color[2]),
+ (unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0),
+ (float)x1,dimy()-(float)y1,lightprops(n1),
+ (float)x2,dimy()-(float)y2,lightprops(n2));
+ }
+#endif
+ break;
+ case 5 : {
+ const unsigned int
+ lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
+ lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
+ lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1);
+ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
+ else draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ const float
+ l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))),
+ l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))),
+ l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1))));
+ board.setPenColorRGBi((unsigned char)(color[0]),
+ (unsigned char)(color[1]),
+ (unsigned char)(color[2]),
+ (unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
+ (float)x1,dimy()-(float)y1,l1,
+ (float)x2,dimy()-(float)y2,l2);
+ }
+#endif
+ } break;
+ }
+ } break;
+ case 4 : { // Colored rectangle
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ n2 = (unsigned int)primitive[2],
+ n3 = (unsigned int)primitive[3];
+ const int
+ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+ x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
+ x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
+ const float
+ z0 = points(n0,2) + Z + focale,
+ z1 = points(n1,2) + Z + focale,
+ z2 = points(n2,2) + Z + focale,
+ z3 = points(n3,2) + Z + focale;
+ switch (render_type) {
+ case 0 :
+ draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).
+ draw_point(x2,y2,color,opac).draw_point(x3,y3,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.drawCircle((float)x0,dimy()-(float)y0,0);
+ board.drawCircle((float)x1,dimy()-(float)y1,0);
+ board.drawCircle((float)x2,dimy()-(float)y2,0);
+ board.drawCircle((float)x3,dimy()-(float)y3,0);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac).
+ draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,opac).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,opac);
+ else
+ draw_line(x0,y0,x1,y1,color,opac).draw_line(x1,y1,x2,y2,color,opac).
+ draw_line(x2,y2,x3,y3,color,opac).draw_line(x3,y3,x0,y0,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
+ board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
+ board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0);
+ }
+#endif
+ break;
+ case 2 :
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac).draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,opac);
+ else
+ draw_triangle(x0,y0,x1,y1,x2,y2,color,opac).draw_triangle(x0,y0,x2,y2,x3,y3,color,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
+ }
+#endif
+ break;
+ case 3 :
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l)).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color.data,opac,lightprops(l));
+ else
+ _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l)).
+ _draw_triangle(x0,y0,x2,y2,x3,y3,color.data,opac,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = cimg::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(color[0]*lp),
+ (unsigned char)(color[1]*lp),
+ (unsigned char)(color[2]*lp),(unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
+ }
+#endif
+ break;
+ case 4 : {
+ const float
+ lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
+ lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprop0,lightprop1,lightprop2,opac).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,lightprop0,lightprop2,lightprop3,opac);
+ else
+ draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprop0,lightprop1,lightprop2,opac).
+ draw_triangle(x0,y0,x2,y2,x3,y3,color,lightprop0,lightprop2,lightprop3,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi((unsigned char)(color[0]),
+ (unsigned char)(color[1]),
+ (unsigned char)(color[2]),
+ (unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
+ (float)x1,dimy()-(float)y1,lightprop1,
+ (float)x2,dimy()-(float)y2,lightprop2);
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
+ (float)x2,dimy()-(float)y2,lightprop2,
+ (float)x3,dimy()-(float)y3,lightprop3);
+ }
+#endif
+ } break;
+ case 5 : {
+ const unsigned int
+ lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
+ lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
+ lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
+ lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+ else
+ draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+ draw_triangle(x0,y0,x2,y2,x3,y3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ const float
+ l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))),
+ l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))),
+ l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))),
+ l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3)));
+ board.setPenColorRGBi((unsigned char)(color[0]),
+ (unsigned char)(color[1]),
+ (unsigned char)(color[2]),
+ (unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
+ (float)x1,dimy()-(float)y1,l1,
+ (float)x2,dimy()-(float)y2,l2);
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
+ (float)x2,dimy()-(float)y2,l2,
+ (float)x3,dimy()-(float)y3,l3);
+ }
+#endif
+ } break;
+ }
+ } break;
+ case 9 : { // Textured triangle
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ n2 = (unsigned int)primitive[2],
+ tx0 = (unsigned int)primitive[3],
+ ty0 = (unsigned int)primitive[4],
+ tx1 = (unsigned int)primitive[5],
+ ty1 = (unsigned int)primitive[6],
+ tx2 = (unsigned int)primitive[7],
+ ty2 = (unsigned int)primitive[8];
+ const int
+ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+ x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
+ const float
+ z0 = points(n0,2) + Z + focale,
+ z1 = points(n1,2) + Z + focale,
+ z2 = points(n2,2) + Z + focale;
+ switch (render_type) {
+ case 0 :
+ draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
+ draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
+ draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.drawCircle((float)x0,dimy()-(float)y0,0);
+ board.drawCircle((float)x1,dimy()-(float)y1,0);
+ board.drawCircle((float)x2,dimy()-(float)y2,0);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+ draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
+ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
+ else
+ draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+ draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
+ draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
+ board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2);
+ board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ }
+#endif
+ break;
+ case 2 :
+ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
+ else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ }
+#endif
+ break;
+ case 3 :
+ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
+ else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = cimg::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ }
+#endif
+ break;
+ case 4 :
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0),
+ (float)x1,dimy()-(float)y1,lightprops(n1),
+ (float)x2,dimy()-(float)y2,lightprops(n2));
+ }
+#endif
+ break;
+ case 5 :
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
+ (unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1),
+ (unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1),
+ (unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1),
+ opac);
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
+ (unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1),
+ (unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1),
+ (unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1),
+ opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ const float
+ l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))),
+ l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))),
+ l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1))));
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,(float)x1,dimy()-(float)y1,l1,(float)x2,dimy()-(float)y2,l2);
+ }
+#endif
+ break;
+ }
+ } break;
+ case 12 : { // Textured rectangle
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ n2 = (unsigned int)primitive[2],
+ n3 = (unsigned int)primitive[3],
+ tx0 = (unsigned int)primitive[4],
+ ty0 = (unsigned int)primitive[5],
+ tx1 = (unsigned int)primitive[6],
+ ty1 = (unsigned int)primitive[7],
+ tx2 = (unsigned int)primitive[8],
+ ty2 = (unsigned int)primitive[9],
+ tx3 = (unsigned int)primitive[10],
+ ty3 = (unsigned int)primitive[11];
+ const int
+ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+ x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
+ x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
+ const float
+ z0 = points(n0,2) + Z + focale,
+ z1 = points(n1,2) + Z + focale,
+ z2 = points(n2,2) + Z + focale,
+ z3 = points(n3,2) + Z + focale;
+ switch (render_type) {
+ case 0 :
+ draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
+ draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
+ draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac).
+ draw_point(x3,y3,color.get_vector_at(tx3,ty3),opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.drawCircle((float)x0,dimy()-(float)y0,0);
+ board.drawCircle((float)x1,dimy()-(float)y1,0);
+ board.drawCircle((float)x2,dimy()-(float)y2,0);
+ board.drawCircle((float)x3,dimy()-(float)y3,0);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
+ draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
+ draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
+ else
+ draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+ draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
+ draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
+ draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
+ board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
+ board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0);
+ }
+#endif
+ break;
+ case 2 :
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
+ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
+ }
+#endif
+ break;
+ case 3 :
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
+ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = cimg::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(opac*255));
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
+ board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
+ }
+#endif
+ break;
+ case 4 : {
+ const float
+ lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
+ lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
+ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
+ (float)x1,dimy()-(float)y1,lightprop1,
+ (float)x2,dimy()-(float)y2,lightprop2);
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
+ (float)x2,dimy()-(float)y2,lightprop2,
+ (float)x3,dimy()-(float)y3,lightprop3);
+ }
+#endif
+ } break;
+ case 5 : {
+ const unsigned int
+ lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
+ lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
+ lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
+ lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
+ if (zbuffer)
+ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+#ifdef cimg_use_board
+ if (pboard) {
+ const float
+ l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))),
+ l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))),
+ l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))),
+ l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3)));
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
+ (float)x1,dimy()-(float)y1,l1,
+ (float)x2,dimy()-(float)y2,l2);
+ board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
+ (float)x2,dimy()-(float)y2,l2,
+ (float)x3,dimy()-(float)y3,l3);
+ }
+#endif
+ } break;
+ }
+ } break;
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 3D object.
+ /**
+ \param X = X-coordinate of the 3d object position
+ \param Y = Y-coordinate of the 3d object position
+ \param Z = Z-coordinate of the 3d object position
+ \param points = Image N*3 describing 3D point coordinates
+ \param primitives = List of P primitives
+ \param colors = List of P color (or textures)
+ \param opacities = Image of P opacities
+ \param render_type = Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud)
+ \param double_sided = Tell if object faces have two sides or are oriented.
+ \param focale = length of the focale
+ \param lightx = X-coordinate of the light
+ \param lighty = Y-coordinate of the light
+ \param lightz = Z-coordinate of the light
+ \param specular_shine = Shininess of the object
+ **/
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+ const CImg<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImgList<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
+ primitives,colors,opacities,opacities.size,
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+
+#ifdef cimg_use_board
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(BoardLib::Board& board,
+ const float x0, const float y0, const float z0,
+ const CImg<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImgList<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
+ primitives,colors,opacities,opacities.size,
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+#endif
+
+ //! Draw a 3D object.
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+ const CImgList<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImgList<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size,
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+
+#ifdef cimg_use_board
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(BoardLib::Board& board,
+ const float x0, const float y0, const float z0,
+ const CImgList<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImgList<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size,
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+#endif
+
+ //! Draw a 3D object.
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+ const CImg<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImg<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
+ primitives,colors,opacities,opacities.size(),
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+
+#ifdef cimg_use_board
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(BoardLib::Board& board,
+ const float x0, const float y0, const float z0,
+ const CImg<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImg<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width
+ ,primitives,colors,opacities,opacities.size(),
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+#endif
+
+ //! Draw a 3D object.
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+ const CImgList<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImg<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(),
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+
+#ifdef cimg_use_board
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(BoardLib::Board& board,
+ const float x0, const float y0, const float z0,
+ const CImgList<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const CImg<to>& opacities,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ if (!points) return *this;
+ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(),
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
+ }
+#endif
+
+ //! Draw a 3D object.
+ template<typename tp, typename tf, typename tc>
+ CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+ const tp& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ static const CImg<floatT> opacities;
+ return draw_object3d(x0,y0,z0,points,primitives,colors,opacities,
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
+ }
+
+#ifdef cimg_use_board
+ template<typename tp, typename tf, typename tc, typename to>
+ CImg<T>& draw_object3d(BoardLib::Board& board,
+ const float x0, const float y0, const float z0,
+ const tp& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors,
+ const unsigned int render_type=4,
+ const bool double_sided=false, const float focale=500,
+ const float lightx=0, const float lighty=0, const float lightz=-5000,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ float *const zbuffer=0) {
+ static const CImg<floatT> opacities;
+ return draw_object3d(x0,y0,z0,points,primitives,colors,opacities,
+ render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
+ }
+#endif
+
+ //@}
+ //----------------------------
+ //
+ //! \name Image Filtering
+ //@{
+ //----------------------------
+
+ //! Compute the correlation of the instance image by a mask.
+ /**
+ The correlation of the instance image \p *this by the mask \p mask is defined to be :
+
+ res(x,y,z) = sum_{i,j,k} (*this)(x+i,y+j,z+k)*mask(i,j,k)
+
+ \param mask = the correlation kernel.
+ \param cond = the border condition type (0=zero, 1=dirichlet)
+ \param weighted_correl = enable local normalization.
+ **/
+ template<typename t>
+ CImg<T>& correlate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_correl=false) {
+ return get_correlate(mask,cond,weighted_correl).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset2<T,t,float>::type> get_correlate(const CImg<t>& mask, const unsigned int cond=1,
+ const bool weighted_correl=false) const {
+ typedef typename cimg::superset2<T,t,float>::type Ttfloat;
+ if (is_empty()) return *this;
+ if (!mask || mask.dim!=1)
+ throw CImgArgumentException("CImg<%s>::correlate() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
+ pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
+ CImg<Ttfloat> dest(width,height,depth,dim);
+ if (cond && mask.width==mask.height && ((mask.depth==1 && mask.width<=5) || (mask.depth==mask.width && mask.width<=3))) {
+ // A special optimization is done for 2x2, 3x3, 4x4, 5x5, 2x2x2 and 3x3x3 mask (with cond=1)
+ switch (mask.depth) {
+ case 3 : {
+ T I[27] = { 0 };
+ cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
+ (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] +
+ I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
+ I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] +
+ I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
+ I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
+ I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
+ I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] +
+ I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
+ I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26]);
+ if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) {
+ const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] +
+ I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
+ I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] +
+ I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
+ I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
+ I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
+ I[18]*I[18] + I[19]*I[19] + I[20]*I[20] +
+ I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
+ I[24]*I[24] + I[25]*I[25] + I[26]*I[26]);
+ if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
+ }
+ } break;
+ case 2 : {
+ T I[8] = { 0 };
+ cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
+ (I[0]*mask[0] + I[1]*mask[1] +
+ I[2]*mask[2] + I[3]*mask[3] +
+ I[4]*mask[4] + I[5]*mask[5] +
+ I[6]*mask[6] + I[7]*mask[7]);
+ if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) {
+ const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
+ I[2]*I[2] + I[3]*I[3] +
+ I[4]*I[4] + I[5]*I[5] +
+ I[6]*I[6] + I[7]*I[7]);
+ if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
+ }
+ } break;
+ default :
+ case 1 :
+ switch (mask.width) {
+ case 6 : {
+ T I[36] = { 0 };
+ cimg_forZV(*this,z,v) cimg_for6x6(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
+ (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
+ I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
+ I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
+ I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
+ I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26] + I[27]*mask[27] + I[28]*mask[28] + I[29]*mask[29] +
+ I[30]*mask[30] + I[31]*mask[31] + I[32]*mask[32] + I[33]*mask[33] + I[34]*mask[34] + I[35]*mask[35]);
+ if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
+ const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
+ I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
+ I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
+ I[18]*I[18] + I[19]*I[19] + I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
+ I[24]*I[24] + I[25]*I[25] + I[26]*I[26] + I[27]*I[27] + I[28]*I[28] + I[29]*I[29] +
+ I[30]*I[30] + I[31]*I[31] + I[32]*I[32] + I[33]*I[33] + I[34]*I[34] + I[35]*I[35]);
+ if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
+ }
+ } break;
+ case 5 : {
+ T I[25] = { 0 };
+ cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
+ (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] +
+ I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] +
+ I[10]*mask[10] + I[11]*mask[11] + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
+ I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + I[18]*mask[18] + I[19]*mask[19] +
+ I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + I[24]*mask[24]);
+ if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
+ const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] +
+ I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] +
+ I[10]*I[10] + I[11]*I[11] + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
+ I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + I[18]*I[18] + I[19]*I[19] +
+ I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + I[24]*I[24]);
+ if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
+ }
+ } break;
+ case 4 : {
+ T I[16] = { 0 };
+ cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
+ (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] +
+ I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] +
+ I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
+ I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15]);
+ if (weighted_correl) cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) {
+ const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] +
+ I[ 4]*I[ 4] + I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] +
+ I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
+ I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15]);
+ if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
+ }
+ } break;
+ case 3 : {
+ T I[9] = { 0 };
+ cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
+ (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
+ I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
+ I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
+ if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) {
+ const double weight = (double)(I[0]*I[0] + I[1]*I[1] + I[2]*I[2] +
+ I[3]*I[3] + I[4]*I[4] + I[5]*I[5] +
+ I[6]*I[6] + I[7]*I[7] + I[8]*I[8]);
+ if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
+ }
+ } break;
+ case 2 : {
+ T I[4] = { 0 };
+ cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
+ (I[0]*mask[0] + I[1]*mask[1] +
+ I[2]*mask[2] + I[3]*mask[3]);
+ if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) {
+ const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
+ I[2]*I[2] + I[3]*I[3]);
+ if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
+ }
+ } break;
+ case 1 : (dest.assign(*this))*=mask(0); break;
+ }
+ }
+ } else { // Generic version for other masks
+ const int
+ mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
+ mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
+ mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
+ cimg_forV(*this,v)
+ if (!weighted_correl) { // Classical correlation
+ for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+ Ttfloat val = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
+ val+=(*this)(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
+ dest(x,y,z,v) = (Ttfloat)val;
+ }
+ if (cond)
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
+ val+=_atXYZ(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
+ dest(x,y,z,v) = (Ttfloat)val;
+ }
+ else
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
+ val+=atXYZ(x+xm,y+ym,z+zm,v,0)*mask(mx1+xm,my1+ym,mz1+zm);
+ dest(x,y,z,v) = (Ttfloat)val;
+ }
+ } else { // Weighted correlation
+ for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+ Ttfloat val = 0, weight = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Ttfloat cval = (Ttfloat)(*this)(x+xm,y+ym,z+zm,v);
+ val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
+ weight+=cval*cval;
+ }
+ dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
+ }
+ if (cond)
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0, weight = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Ttfloat cval = (Ttfloat)_atXYZ(x+xm,y+ym,z+zm,v);
+ val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
+ weight+=cval*cval;
+ }
+ dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
+ }
+ else
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0, weight = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Ttfloat cval = (Ttfloat)atXYZ(x+xm,y+ym,z+zm,v,0);
+ val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
+ weight+=cval*cval;
+ }
+ dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
+ }
+ }
+ }
+ return dest;
+ }
+
+ //! Compute the convolution of the image by a mask.
+ /**
+ The result \p res of the convolution of an image \p img by a mask \p mask is defined to be :
+
+ res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*mask(i,j,k)
+
+ \param mask = the correlation kernel.
+ \param cond = the border condition type (0=zero, 1=dirichlet)
+ \param weighted_convol = enable local normalization.
+ **/
+ template<typename t>
+ CImg<T>& convolve(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_convol=false) {
+ return get_convolve(mask,cond,weighted_convol).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset2<T,t,float>::type> get_convolve(const CImg<t>& mask, const unsigned int cond=1,
+ const bool weighted_convol=false) const {
+ typedef typename cimg::superset2<T,t,float>::type Ttfloat;
+ if (is_empty()) return *this;
+ if (!mask || mask.dim!=1)
+ throw CImgArgumentException("CImg<%s>::convolve() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
+ pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
+ return get_correlate(CImg<t>(mask.ptr(),mask.size(),1,1,1,true).get_mirror('x').resize(mask,-1),cond,weighted_convol);
+ }
+
+ //! Return the erosion of the image by a structuring element.
+ template<typename t>
+ CImg<T>& erode(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_erosion=false) {
+ return get_erode(mask,cond,weighted_erosion).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> get_erode(const CImg<t>& mask, const unsigned int cond=1,
+ const bool weighted_erosion=false) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ if (is_empty()) return *this;
+ if (!mask || mask.dim!=1)
+ throw CImgArgumentException("CImg<%s>::erode() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
+ pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
+ CImg<Tt> dest(width,height,depth,dim);
+ const int
+ mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
+ mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
+ mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
+ cimg_forV(*this,v)
+ if (!weighted_erosion) { // Classical erosion
+ for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+ Tt min_val = cimg::type<Tt>::max();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v);
+ if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+ }
+ dest(x,y,z,v) = min_val;
+ }
+ if (cond)
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt min_val = cimg::type<Tt>::max();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v);
+ if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+ }
+ dest(x,y,z,v) = min_val;
+ }
+ else
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt min_val = cimg::type<Tt>::max();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0);
+ if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+ }
+ dest(x,y,z,v) = min_val;
+ }
+ } else { // Weighted erosion
+ for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+ Tt min_val = cimg::type<Tt>::max();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+ const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) + mval);
+ if (mval && cval<min_val) min_val = cval;
+ }
+ dest(x,y,z,v) = min_val;
+ }
+ if (cond)
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt min_val = cimg::type<Tt>::max();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+ const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) + mval);
+ if (mval && cval<min_val) min_val = cval;
+ }
+ dest(x,y,z,v) = min_val;
+ }
+ else
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt min_val = cimg::type<Tt>::max();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+ const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) + mval);
+ if (mval && cval<min_val) min_val = cval;
+ }
+ dest(x,y,z,v) = min_val;
+ }
+ }
+ return dest;
+ }
+
+ //! Erode the image by a square structuring element of size n.
+ CImg<T>& erode(const unsigned int n, const unsigned int cond=1) {
+ if (n<2) return *this;
+ return get_erode(n,cond).transfer_to(*this);
+ }
+
+ CImg<T> get_erode(const unsigned int n, const unsigned int cond=1) const {
+ static CImg<T> mask;
+ if (n<2) return *this;
+ if (mask.width!=n) mask.assign(n,n,1,1,1);
+ const CImg<T> res = get_erode(mask,cond,false);
+ if (n>20) mask.assign();
+ return res;
+ }
+
+ //! Dilate the image by a structuring element.
+ template<typename t>
+ CImg<T>& dilate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_dilatation=false) {
+ return get_dilate(mask,cond,weighted_dilatation).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> get_dilate(const CImg<t>& mask, const unsigned int cond=1,
+ const bool weighted_dilatation=false) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ if (is_empty()) return *this;
+ if (!mask || mask.dim!=1)
+ throw CImgArgumentException("CImg<%s>::dilate() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
+ pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
+ CImg<Tt> dest(width,height,depth,dim);
+ const int
+ mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
+ mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
+ mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
+ cimg_forV(*this,v)
+ if (!weighted_dilatation) { // Classical dilatation
+ for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+ Tt max_val = cimg::type<Tt>::min();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v);
+ if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+ }
+ dest(x,y,z,v) = max_val;
+ }
+ if (cond)
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt max_val = cimg::type<Tt>::min();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v);
+ if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+ }
+ dest(x,y,z,v) = max_val;
+ }
+ else
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt max_val = cimg::type<Tt>::min();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0);
+ if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+ }
+ dest(x,y,z,v) = max_val;
+ }
+ } else { // Weighted dilatation
+ for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+ Tt max_val = cimg::type<Tt>::min();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+ const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) - mval);
+ if (mval && cval>max_val) max_val = cval;
+ }
+ dest(x,y,z,v) = max_val;
+ }
+ if (cond)
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt max_val = cimg::type<Tt>::min();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+ const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) - mval);
+ if (mval && cval>max_val) max_val = cval;
+ }
+ dest(x,y,z,v) = max_val;
+ }
+ else
+ cimg_forYZV(*this,y,z,v)
+ for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+ Tt max_val = cimg::type<Tt>::min();
+ for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+ const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+ const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) - mval);
+ if (mval && cval>max_val) max_val = cval;
+ }
+ dest(x,y,z,v) = max_val;
+ }
+ }
+ return dest;
+ }
+
+ //! Dilate the image by a square structuring element of size n.
+ CImg<T>& dilate(const unsigned int n, const unsigned int cond=1) {
+ if (n<2) return *this;
+ return get_dilate(n,cond).transfer_to(*this);
+ }
+
+ CImg<T> get_dilate(const unsigned int n, const unsigned int cond=1) const {
+ static CImg<T> mask;
+ if (n<2) return *this;
+ if (mask.width!=n) mask.assign(n,n,1,1,1);
+ const CImg<T> res = get_dilate(mask,cond,false);
+ if (n>20) mask.assign();
+ return res;
+ }
+
+ //! Add noise to the image.
+ /**
+ \param sigma = power of the noise. if sigma<0, it corresponds to the percentage of the maximum image value.
+ \param ntype = noise type. can be 0=gaussian, 1=uniform or 2=Salt and Pepper, 3=Poisson, 4=Rician.
+ \return A noisy version of the instance image.
+ **/
+ CImg<T>& noise(const double sigma, const unsigned int noise_type=0) {
+ if (!is_empty()) {
+ double nsigma = sigma, max = (double)cimg::type<T>::max(), min = (double)cimg::type<T>::min();
+ Tfloat m = 0, M = 0;
+ if (nsigma==0 && noise_type!=3) return *this;
+ if (nsigma<0 || noise_type==2) m = (Tfloat)minmax(M);
+ if (nsigma<0) nsigma = -nsigma*(M-m)/100.0;
+ switch (noise_type) {
+ case 0 : { // Gaussian noise
+ cimg_for(*this,ptr,T) {
+ double val = *ptr + nsigma*cimg::grand();
+ if (val>max) val = max;
+ if (val<min) val = min;
+ *ptr = (T)val;
+ }
+ } break;
+ case 1 : { // Uniform noise
+ cimg_for(*this,ptr,T) {
+ double val = *ptr + nsigma*cimg::crand();
+ if (val>max) val = max;
+ if (val<min) val = min;
+ *ptr = (T)val;
+ }
+ } break;
+ case 2 : { // Salt & Pepper noise
+ if (nsigma<0) nsigma = -nsigma;
+ if (M==m) { m = 0; M = (float)(cimg::type<T>::is_float()?1:cimg::type<T>::max()); }
+ cimg_for(*this,ptr,T) if (cimg::rand()*100<nsigma) *ptr = (T)(cimg::rand()<0.5?M:m);
+ } break;
+
+ case 3 : { // Poisson Noise
+ cimg_for(*this,ptr,T) *ptr = (T)cimg::prand(*ptr);
+ } break;
+
+ case 4 : { // Rice noise
+ const double sqrt2 = (double)cimg_std::sqrt(2.0);
+ cimg_for(*this,ptr,T) {
+ const double
+ val0 = (double)*ptr/sqrt2,
+ re = val0 + nsigma*cimg::grand(),
+ im = val0 + nsigma*cimg::grand();
+ double val = cimg_std::sqrt(re*re + im*im);
+ if (val>max) val = max;
+ if (val<min) val = min;
+ *ptr = (T)val;
+ }
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::noise() : Invalid noise type %d "
+ "(should be {0=Gaussian, 1=Uniform, 2=Salt&Pepper, 3=Poisson}).",pixel_type(),noise_type);
+ }
+ }
+ return *this;
+ }
+
+ CImg<T> get_noise(const double sigma, const unsigned int noise_type=0) const {
+ return (+*this).noise(sigma,noise_type);
+ }
+
+ //! Compute the result of the Deriche filter.
+ /**
+ The Canny-Deriche filter is a recursive algorithm allowing to compute blurred derivatives of
+ order 0,1 or 2 of an image.
+ **/
+ CImg<T>& deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) {
+#define _cimg_deriche2_apply \
+ Tfloat *ptrY = Y.data, yb = 0, yp = 0; \
+ T xp = (T)0; \
+ if (cond) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \
+ for (int m=0; m<N; ++m) { \
+ const T xc = *ptrX; ptrX+=off; \
+ const Tfloat yc = *(ptrY++) = (Tfloat)(a0*xc + a1*xp - b1*yp - b2*yb); \
+ xp = xc; yb = yp; yp = yc; \
+ } \
+ T xn = (T)0, xa = (T)0; \
+ Tfloat yn = 0, ya = 0; \
+ if (cond) { xn = xa = *(ptrX-off); yn = ya = (Tfloat)coefn*xn; } \
+ for (int n=N-1; n>=0; --n) { \
+ const T xc = *(ptrX-=off); \
+ const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \
+ xa = xn; xn = xc; ya = yn; yn = yc; \
+ *ptrX = (T)(*(--ptrY)+yc); \
+ }
+ if (sigma<0)
+ throw CImgArgumentException("CImg<%s>::deriche() : Given filter variance (sigma = %g) is negative",
+ pixel_type(),sigma);
+ if (is_empty() || (sigma<0.1 && !order)) return *this;
+ const float
+ nsigma = sigma<0.1f?0.1f:sigma,
+ alpha = 1.695f/nsigma,
+ ema = (float)cimg_std::exp(-alpha),
+ ema2 = (float)cimg_std::exp(-2*alpha),
+ b1 = -2*ema,
+ b2 = ema2;
+ float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0;
+ switch (order) {
+ case 0 : {
+ const float k = (1-ema)*(1-ema)/(1+2*alpha*ema-ema2);
+ a0 = k;
+ a1 = k*(alpha-1)*ema;
+ a2 = k*(alpha+1)*ema;
+ a3 = -k*ema2;
+ } break;
+ case 1 : {
+ const float k = (1-ema)*(1-ema)/ema;
+ a0 = k*ema;
+ a1 = a3 = 0;
+ a2 = -a0;
+ } break;
+ case 2 : {
+ const float
+ ea = (float)cimg_std::exp(-alpha),
+ k = -(ema2-1)/(2*alpha*ema),
+ kn = (-2*(-1+3*ea-3*ea*ea+ea*ea*ea)/(3*ea+1+3*ea*ea+ea*ea*ea));
+ a0 = kn;
+ a1 = -kn*(1+k*alpha)*ema;
+ a2 = kn*(1-k*alpha)*ema;
+ a3 = -kn*ema2;
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::deriche() : Given filter order (order = %u) must be 0,1 or 2",
+ pixel_type(),order);
+ }
+ coefp = (a0+a1)/(1+b1+b2);
+ coefn = (a2+a3)/(1+b1+b2);
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ const int N = width, off = 1;
+ CImg<Tfloat> Y(N);
+ cimg_forYZV(*this,y,z,v) { T *ptrX = ptr(0,y,z,v); _cimg_deriche2_apply; }
+ } break;
+ case 'y' : {
+ const int N = height, off = width;
+ CImg<Tfloat> Y(N);
+ cimg_forXZV(*this,x,z,v) { T *ptrX = ptr(x,0,z,v); _cimg_deriche2_apply; }
+ } break;
+ case 'z' : {
+ const int N = depth, off = width*height;
+ CImg<Tfloat> Y(N);
+ cimg_forXYV(*this,x,y,v) { T *ptrX = ptr(x,y,0,v); _cimg_deriche2_apply; }
+ } break;
+ case 'v' : {
+ const int N = dim, off = width*height*depth;
+ CImg<Tfloat> Y(N);
+ cimg_forXYZ(*this,x,y,z) { T *ptrX = ptr(x,y,z,0); _cimg_deriche2_apply; }
+ } break;
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) const {
+ return CImg<Tfloat>(*this,false).deriche(sigma,order,axis,cond);
+ }
+
+ //! Return a blurred version of the image, using a Canny-Deriche filter.
+ /**
+ Blur the image with an anisotropic exponential filter (Deriche filter of order 0).
+ **/
+ CImg<T>& blur(const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) {
+ if (!is_empty()) {
+ if (width>1 && sigmax>0) deriche(sigmax,0,'x',cond);
+ if (height>1 && sigmay>0) deriche(sigmay,0,'y',cond);
+ if (depth>1 && sigmaz>0) deriche(sigmaz,0,'z',cond);
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_blur(const float sigmax, const float sigmay, const float sigmaz,
+ const bool cond=true) const {
+ return CImg<Tfloat>(*this,false).blur(sigmax,sigmay,sigmaz,cond);
+ }
+
+ //! Return a blurred version of the image, using a Canny-Deriche filter.
+ CImg<T>& blur(const float sigma, const bool cond=true) {
+ return blur(sigma,sigma,sigma,cond);
+ }
+
+ CImg<Tfloat> get_blur(const float sigma, const bool cond=true) const {
+ return CImg<Tfloat>(*this,false).blur(sigma,cond);
+ }
+
+ //! Blur the image anisotropically following a field of diffusion tensors.
+ /**
+ \param G = Field of square roots of diffusion tensors used to drive the smoothing.
+ \param amplitude = amplitude of the smoothing.
+ \param dl = spatial discretization.
+ \param da = angular discretization.
+ \param gauss_prec = precision of the gaussian function.
+ \param interpolation Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
+ \param fast_approx = Tell to use the fast approximation or not.
+ **/
+ template<typename t>
+ CImg<T>& blur_anisotropic(const CImg<t>& G, const float amplitude=60, const float dl=0.8f, const float da=30,
+ const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) {
+#define _cimg_valign2d(i,j) \
+ { Tfloat &u = W(i,j,0,0), &v = W(i,j,0,1); \
+ if (u*curru + v*currv<0) { u=-u; v=-v; }}
+#define _cimg_valign3d(i,j,k) \
+ { Tfloat &u = W(i,j,k,0), &v = W(i,j,k,1), &w = W(i,j,k,2); \
+ if (u*curru + v*currv + w*currw<0) { u=-u; v=-v; w=-w; }}
+
+ // Check arguments and init variables
+ if (!is_empty() && amplitude>0) {
+ if (!G || (G.dim!=3 && G.dim!=6) || G.width!=width || G.height!=height || G.depth!=depth)
+ throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Specified tensor field (%u,%u,%u,%u) is not valid.",
+ pixel_type(),G.width,G.height,G.depth,G.dim);
+
+ const float sqrt2amplitude = (float)cimg_std::sqrt(2*amplitude);
+ const bool threed = (G.dim>=6);
+ const int
+ dx1 = dimx()-1,
+ dy1 = dimy()-1,
+ dz1 = dimz()-1;
+ CImg<Tfloat>
+ dest(width,height,depth,dim,0),
+ W(width,height,depth,threed?4:3),
+ tmp(dim);
+ int N = 0;
+
+ if (threed)
+ // 3D version of the algorithm
+ for (float phi=(180%(int)da)/2.0f; phi<=180; phi+=da) {
+ const float
+ phir = (float)(phi*cimg::valuePI/180),
+ datmp = (float)(da/cimg_std::cos(phir)),
+ da2 = datmp<1?360.0f:datmp;
+
+ for (float theta=0; theta<360; (theta+=da2),++N) {
+ const float
+ thetar = (float)(theta*cimg::valuePI/180),
+ vx = (float)(cimg_std::cos(thetar)*cimg_std::cos(phir)),
+ vy = (float)(cimg_std::sin(thetar)*cimg_std::cos(phir)),
+ vz = (float)cimg_std::sin(phir);
+ const t
+ *pa = G.ptr(0,0,0,0),
+ *pb = G.ptr(0,0,0,1),
+ *pc = G.ptr(0,0,0,2),
+ *pd = G.ptr(0,0,0,3),
+ *pe = G.ptr(0,0,0,4),
+ *pf = G.ptr(0,0,0,5);
+ Tfloat
+ *pd0 = W.ptr(0,0,0,0),
+ *pd1 = W.ptr(0,0,0,1),
+ *pd2 = W.ptr(0,0,0,2),
+ *pd3 = W.ptr(0,0,0,3);
+ cimg_forXYZ(G,xg,yg,zg) {
+ const t
+ a = *(pa++), b = *(pb++), c = *(pc++),
+ d = *(pd++), e = *(pe++), f = *(pf++);
+ const float
+ u = (float)(a*vx + b*vy + c*vz),
+ v = (float)(b*vx + d*vy + e*vz),
+ w = (float)(c*vx + e*vy + f*vz),
+ n = (float)cimg_std::sqrt(1e-5+u*u+v*v+w*w),
+ dln = dl/n;
+ *(pd0++) = (Tfloat)(u*dln);
+ *(pd1++) = (Tfloat)(v*dln);
+ *(pd2++) = (Tfloat)(w*dln);
+ *(pd3++) = (Tfloat)n;
+ }
+
+ cimg_forXYZ(*this,x,y,z) {
+ tmp.fill(0);
+ const float
+ cu = (float)W(x,y,z,0),
+ cv = (float)W(x,y,z,1),
+ cw = (float)W(x,y,z,2),
+ n = (float)W(x,y,z,3),
+ fsigma = (float)(n*sqrt2amplitude),
+ length = gauss_prec*fsigma,
+ fsigma2 = 2*fsigma*fsigma;
+ float
+ S = 0,
+ pu = cu,
+ pv = cv,
+ pw = cw,
+ X = (float)x,
+ Y = (float)y,
+ Z = (float)z;
+
+ switch (interpolation_type) {
+ case 0 : {
+ // Nearest neighbor
+ for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
+ const int
+ cx = (int)(X+0.5f),
+ cy = (int)(Y+0.5f),
+ cz = (int)(Z+0.5f);
+ float
+ u = (float)W(cx,cy,cz,0),
+ v = (float)W(cx,cy,cz,1),
+ w = (float)W(cx,cy,cz,2);
+ if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
+ if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,cz,k); ++S; }
+ else {
+ const float coef = (float)cimg_std::exp(-l*l/fsigma2);
+ cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,cz,k));
+ S+=coef;
+ }
+ X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
+ }
+ } break;
+
+ case 1 : {
+ // Linear interpolation
+ for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
+ const int
+ cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
+ cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
+ cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
+ const float
+ curru = (float)W(cx,cy,cz,0),
+ currv = (float)W(cx,cy,cz,1),
+ currw = (float)W(cx,cy,cz,2);
+ _cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz);
+ _cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz);
+ _cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz);
+ _cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz);
+ _cimg_valign3d(px,cy,cz); _cimg_valign3d(nx,cy,cz);
+ _cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz);
+ _cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz);
+ _cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz);
+ _cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz);
+ float
+ u = (float)(W._linear_atXYZ(X,Y,Z,0)),
+ v = (float)(W._linear_atXYZ(X,Y,Z,1)),
+ w = (float)(W._linear_atXYZ(X,Y,Z,2));
+ if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
+ if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; }
+ else {
+ const float coef = (float)cimg_std::exp(-l*l/fsigma2);
+ cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k));
+ S+=coef;
+ }
+ X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
+ }
+ } break;
+
+ default : {
+ // 2nd order Runge Kutta
+ for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
+ const int
+ cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
+ cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
+ cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
+ const float
+ curru = (float)W(cx,cy,cz,0),
+ currv = (float)W(cx,cy,cz,1),
+ currw = (float)W(cx,cy,cz,2);
+ _cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz);
+ _cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz);
+ _cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz);
+ _cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz);
+ _cimg_valign3d(px,cy,cz); _cimg_valign3d(nx,cy,cz);
+ _cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz);
+ _cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz);
+ _cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz);
+ _cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz);
+ const float
+ u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)),
+ v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)),
+ w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2));
+ float
+ u = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,0)),
+ v = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,1)),
+ w = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,2));
+ if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
+ if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; }
+ else {
+ const float coef = (float)cimg_std::exp(-l*l/fsigma2);
+ cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k));
+ S+=coef;
+ }
+ X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
+ }
+ } break;
+ }
+ if (S>0) cimg_forV(dest,k) dest(x,y,z,k)+=tmp[k]/S;
+ else cimg_forV(dest,k) dest(x,y,z,k)+=(Tfloat)((*this)(x,y,z,k));
+ cimg_plugin_greycstoration_count;
+ }
+ }
+ } else
+ // 2D version of the algorithm
+ for (float theta=(360%(int)da)/2.0f; theta<360; (theta+=da),++N) {
+ const float
+ thetar = (float)(theta*cimg::valuePI/180),
+ vx = (float)(cimg_std::cos(thetar)),
+ vy = (float)(cimg_std::sin(thetar));
+ const t
+ *pa = G.ptr(0,0,0,0),
+ *pb = G.ptr(0,0,0,1),
+ *pc = G.ptr(0,0,0,2);
+ Tfloat
+ *pd0 = W.ptr(0,0,0,0),
+ *pd1 = W.ptr(0,0,0,1),
+ *pd2 = W.ptr(0,0,0,2);
+ cimg_forXY(G,xg,yg) {
+ const t a = *(pa++), b = *(pb++), c = *(pc++);
+ const float
+ u = (float)(a*vx + b*vy),
+ v = (float)(b*vx + c*vy),
+ n = (float)cimg_std::sqrt(1e-5+u*u+v*v),
+ dln = dl/n;
+ *(pd0++) = (Tfloat)(u*dln);
+ *(pd1++) = (Tfloat)(v*dln);
+ *(pd2++) = (Tfloat)n;
+ }
+
+ cimg_forXY(*this,x,y) {
+ tmp.fill(0);
+ const float
+ cu = (float)W(x,y,0,0),
+ cv = (float)W(x,y,0,1),
+ n = (float)W(x,y,0,2),
+ fsigma = (float)(n*sqrt2amplitude),
+ length = gauss_prec*fsigma,
+ fsigma2 = 2*fsigma*fsigma;
+ float
+ S = 0,
+ pu = cu,
+ pv = cv,
+ X = (float)x,
+ Y = (float)y;
+
+ switch (interpolation_type) {
+
+ case 0 : {
+ // Nearest-neighbor interpolation for 2D images
+ for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+ const int
+ cx = (int)(X+0.5f),
+ cy = (int)(Y+0.5f);
+ float
+ u = (float)W(cx,cy,0,0),
+ v = (float)W(cx,cy,0,1);
+ if ((pu*u + pv*v)<0) { u=-u; v=-v; }
+ if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,0,k); ++S; }
+ else {
+ const float coef = (float)cimg_std::exp(-l*l/fsigma2);
+ cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,0,k));
+ S+=coef;
+ }
+ X+=(pu=u); Y+=(pv=v);
+ }
+ } break;
+
+ case 1 : {
+ // Linear interpolation for 2D images
+ for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+ const int
+ cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
+ cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
+ const float
+ curru = (float)W(cx,cy,0,0),
+ currv = (float)W(cx,cy,0,1);
+ _cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py);
+ _cimg_valign2d(px,cy); _cimg_valign2d(nx,cy);
+ _cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny);
+ float
+ u = (float)(W._linear_atXY(X,Y,0,0)),
+ v = (float)(W._linear_atXY(X,Y,0,1));
+ if ((pu*u + pv*v)<0) { u=-u; v=-v; }
+ if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; }
+ else {
+ const float coef = (float)cimg_std::exp(-l*l/fsigma2);
+ cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k));
+ S+=coef;
+ }
+ X+=(pu=u); Y+=(pv=v);
+ }
+ } break;
+
+ default : {
+ // 2nd-order Runge-kutta interpolation for 2D images
+ for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+ const int
+ cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
+ cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
+ const float
+ curru = (float)W(cx,cy,0,0),
+ currv = (float)W(cx,cy,0,1);
+ _cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py);
+ _cimg_valign2d(px,cy); _cimg_valign2d(nx,cy);
+ _cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny);
+ const float
+ u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)),
+ v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1));
+ float
+ u = (float)(W._linear_atXY(X+u0,Y+v0,0,0)),
+ v = (float)(W._linear_atXY(X+u0,Y+v0,0,1));
+ if ((pu*u + pv*v)<0) { u=-u; v=-v; }
+ if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; }
+ else {
+ const float coef = (float)cimg_std::exp(-l*l/fsigma2);
+ cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k));
+ S+=coef;
+ }
+ X+=(pu=u); Y+=(pv=v);
+ }
+ }
+ }
+ if (S>0) cimg_forV(dest,k) dest(x,y,0,k)+=tmp[k]/S;
+ else cimg_forV(dest,k) dest(x,y,0,k)+=(Tfloat)((*this)(x,y,0,k));
+ cimg_plugin_greycstoration_count;
+ }
+ }
+ const Tfloat *ptrs = dest.data+dest.size();
+ const T m = cimg::type<T>::min(), M = cimg::type<T>::max();
+ cimg_for(*this,ptrd,T) { const Tfloat val = *(--ptrs)/N; *ptrd = val<m?m:(val>M?M:(T)val); }
+ }
+ return *this;
+ }
+
+ template<typename t>
+ CImg<T> get_blur_anisotropic(const CImg<t>& G, const float amplitude=60, const float dl=0.8f, const float da=30,
+ const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) const {
+ return (+*this).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
+ }
+
+ //! Blur an image in an anisotropic way.
+ /**
+ \param mask Binary mask.
+ \param amplitude Amplitude of the anisotropic blur.
+ \param sharpness Contour preservation.
+ \param anisotropy Smoothing anisotropy.
+ \param alpha Image pre-blurring (gaussian).
+ \param sigma Regularity of the tensor-valued geometry.
+ \param dl Spatial discretization.
+ \param da Angular discretization.
+ \param gauss_prec Precision of the gaussian function.
+ \param interpolation_type Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
+ \param fast_approx Tell to use the fast approximation or not
+ \param geom_factor Geometry factor.
+ **/
+ template<typename tm>
+ CImg<T>& blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
+ const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30,
+ const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true,
+ const float geom_factor=1) {
+ if (!is_empty() && amplitude>0) {
+ if (amplitude==0) return *this;
+ if (amplitude<0 || sharpness<0 || anisotropy<0 || anisotropy>1 || alpha<0 || sigma<0 || dl<0 || da<0 || gauss_prec<0)
+ throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Given parameters are amplitude(%g), sharpness(%g), "
+ "anisotropy(%g), alpha(%g), sigma(%g), dl(%g), da(%g), gauss_prec(%g).\n"
+ "Admissible parameters are in the range : amplitude>0, sharpness>0, anisotropy in [0,1], "
+ "alpha>0, sigma>0, dl>0, da>0, gauss_prec>0.",
+ pixel_type(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec);
+ const bool threed = (depth>1), no_mask = mask.is_empty();
+ const float nsharpness = cimg::max(sharpness,1e-5f), power1 = 0.5f*nsharpness, power2 = power1/(1e-7f+1-anisotropy);
+ CImg<floatT> blurred = CImg<floatT>(*this,false).blur(alpha);
+ if (geom_factor>0) blurred*=geom_factor;
+ else blurred.normalize(0,-geom_factor);
+
+ if (threed) { // Field for 3D volumes
+ cimg_plugin_greycstoration_lock;
+ CImg<floatT> val(3), vec(3,3), G(blurred.get_structure_tensor());
+ if (sigma>0) G.blur(sigma);
+ cimg_forXYZ(*this,x,y,z) {
+ if (no_mask || mask(x,y,z)) {
+ G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
+ const float l1 = val[2], l2 = val[1], l3 = val[0],
+ ux = vec(0,0), uy = vec(0,1), uz = vec(0,2),
+ vx = vec(1,0), vy = vec(1,1), vz = vec(1,2),
+ wx = vec(2,0), wy = vec(2,1), wz = vec(2,2),
+ n1 = (float)cimg_std::pow(1+l1+l2+l3,-power1),
+ n2 = (float)cimg_std::pow(1+l1+l2+l3,-power2);
+ G(x,y,z,0) = n1*(ux*ux + vx*vx) + n2*wx*wx;
+ G(x,y,z,1) = n1*(ux*uy + vx*vy) + n2*wx*wy;
+ G(x,y,z,2) = n1*(ux*uz + vx*vz) + n2*wx*wz;
+ G(x,y,z,3) = n1*(uy*uy + vy*vy) + n2*wy*wy;
+ G(x,y,z,4) = n1*(uy*uz + vy*vz) + n2*wy*wz;
+ G(x,y,z,5) = n1*(uz*uz + vz*vz) + n2*wz*wz;
+ } else G(x,y,z,0) = G(x,y,z,1) = G(x,y,z,2) = G(x,y,z,3) = G(x,y,z,4) = G(x,y,z,5) = 0;
+ cimg_plugin_greycstoration_count;
+ }
+ cimg_plugin_greycstoration_unlock;
+ blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
+ } else { // Field for 2D images
+ cimg_plugin_greycstoration_lock;
+ CImg<floatT> val(2), vec(2,2), G(blurred.get_structure_tensor());
+ if (sigma>0) G.blur(sigma);
+ cimg_forXY(*this,x,y) {
+ if (no_mask || mask(x,y)) {
+ G.get_tensor_at(x,y).symmetric_eigen(val,vec);
+ const float l1 = val[1], l2 = val[0],
+ ux = vec(1,0), uy = vec(1,1),
+ vx = vec(0,0), vy = vec(0,1),
+ n1 = (float)cimg_std::pow(1+l1+l2,-power1),
+ n2 = (float)cimg_std::pow(1+l1+l2,-power2);
+ G(x,y,0,0) = n1*ux*ux + n2*vx*vx;
+ G(x,y,0,1) = n1*ux*uy + n2*vx*vy;
+ G(x,y,0,2) = n1*uy*uy + n2*vy*vy;
+ } else G(x,y,0,0) = G(x,y,0,1) = G(x,y,0,2) = 0;
+ cimg_plugin_greycstoration_count;
+ }
+ cimg_plugin_greycstoration_unlock;
+ blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
+ }
+ }
+ return *this;
+ }
+
+ template<typename tm>
+ CImg<T> get_blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
+ const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
+ const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0,
+ const bool fast_approx=true, const float geom_factor=1) const {
+ return (+*this).blur_anisotropic(mask,amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
+ }
+
+ //! Blur an image following in an anisotropic way.
+ CImg<T>& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
+ const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30,
+ const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true,
+ const float geom_factor=1) {
+ return blur_anisotropic(CImg<T>(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
+ }
+
+ CImg<T> get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
+ const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
+ const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0,
+ const bool fast_approx=true, const float geom_factor=1) const {
+ return (+*this).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
+ }
+
+ //! Blur an image using the bilateral filter.
+ /**
+ \param sigmax Amount of blur along the X-axis.
+ \param sigmay Amount of blur along the Y-axis.
+ \param sigmaz Amount of blur along the Z-axis.
+ \param sigmar Amount of blur along the range axis.
+ \param bgridx Size of the bilateral grid along the X-axis.
+ \param bgridy Size of the bilateral grid along the Y-axis.
+ \param bgridz Size of the bilateral grid along the Z-axis.
+ \param bgridr Size of the bilateral grid along the range axis.
+ \param interpolation_type Use interpolation for image slicing.
+ \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006
+ (extended for 3D volumetric images).
+ **/
+ CImg<T>& blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
+ const int bgridx, const int bgridy, const int bgridz, const int bgridr,
+ const bool interpolation_type=true) {
+ T m, M = maxmin(m);
+ const float range = (float)(1.0f+M-m);
+ const unsigned int
+ bx0 = bgridx>=0?bgridx:width*(-bgridx)/100,
+ by0 = bgridy>=0?bgridy:height*(-bgridy)/100,
+ bz0 = bgridz>=0?bgridz:depth*(-bgridz)/100,
+ br0 = bgridr>=0?bgridr:(int)(-range*bgridr/100),
+ bx = bx0>0?bx0:1,
+ by = by0>0?by0:1,
+ bz = bz0>0?bz0:1,
+ br = br0>0?br0:1;
+ const float
+ nsigmax = sigmax*bx/width,
+ nsigmay = sigmay*by/height,
+ nsigmaz = sigmaz*bz/depth,
+ nsigmar = sigmar*br/range;
+ if (nsigmax>0 || nsigmay>0 || nsigmaz>0 || nsigmar>0) {
+ const bool threed = depth>1;
+ if (threed) { // 3d version of the algorithm
+ CImg<floatT> bgrid(bx,by,bz,br), bgridw(bx,by,bz,br);
+ cimg_forV(*this,k) {
+ bgrid.fill(0); bgridw.fill(0);
+ cimg_forXYZ(*this,x,y,z) {
+ const T val = (*this)(x,y,z,k);
+ const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
+ bgrid(X,Y,Z,R) = (float)val;
+ bgridw(X,Y,Z,R) = 1;
+ }
+ bgrid.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
+ bgridw.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
+ if (interpolation_type) cimg_forXYZ(*this,x,y,z) {
+ const T val = (*this)(x,y,z,k);
+ const float X = (float)x*bx/width, Y = (float)y*by/height, Z = (float)z*bz/depth, R = (float)((val-m)*br/range),
+ bval0 = bgrid._linear_atXYZV(X,Y,Z,R), bval1 = bgridw._linear_atXYZV(X,Y,Z,R);
+ (*this)(x,y,z,k) = (T)(bval0/bval1);
+ } else cimg_forXYZ(*this,x,y,z) {
+ const T val = (*this)(x,y,z,k);
+ const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
+ const float bval0 = bgrid(X,Y,Z,R), bval1 = bgridw(X,Y,Z,R);
+ (*this)(x,y,z,k) = (T)(bval0/bval1);
+ }
+ }
+ } else { // 2d version of the algorithm
+ CImg<floatT> bgrid(bx,by,br,2);
+ cimg_forV(*this,k) {
+ bgrid.fill(0);
+ cimg_forXY(*this,x,y) {
+ const T val = (*this)(x,y,k);
+ const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
+ bgrid(X,Y,R,0) = (float)val;
+ bgrid(X,Y,R,1) = 1;
+ }
+ bgrid.blur(nsigmax,nsigmay,0,true).blur(0,0,nsigmar,false);
+ if (interpolation_type) cimg_forXY(*this,x,y) {
+ const T val = (*this)(x,y,k);
+ const float X = (float)x*bx/width, Y = (float)y*by/height, R = (float)((val-m)*br/range),
+ bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1);
+ (*this)(x,y,k) = (T)(bval0/bval1);
+ } else cimg_forXY(*this,x,y) {
+ const T val = (*this)(x,y,k);
+ const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
+ const float bval0 = bgrid(X,Y,R,0), bval1 = bgrid(X,Y,R,1);
+ (*this)(x,y,k) = (T)(bval0/bval1);
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ CImg<T> get_blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
+ const int bgridx, const int bgridy, const int bgridz, const int bgridr,
+ const bool interpolation_type=true) const {
+ return (+*this).blur_bilateral(sigmax,sigmay,sigmaz,sigmar,bgridx,bgridy,bgridz,bgridr,interpolation_type);
+ }
+
+ //! Blur an image using the bilateral filter.
+ CImg<T>& blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
+ const bool interpolation_type=true) {
+ return blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type);
+ }
+
+ CImg<T> get_blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
+ const bool interpolation_type=true) const {
+ return (+*this).blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type);
+ }
+
+ //! Blur an image in its patch-based space.
+ CImg<T>& blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10,
+ const unsigned int lookup_size=4, const bool fast_approx=true) {
+
+#define _cimg_blur_patch_fastfunc(x) ((x)>3?0:1)
+#define _cimg_blur_patch_slowfunc(x) cimg_std::exp(-(x))
+#define _cimg_blur_patch3d(N,func) { \
+ const unsigned int N3 = N*N*N; \
+ cimg_for##N##XYZ(*this,x,y,z) { \
+ cimg_plugin_greycstoration_count; \
+ cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,x,y,z,k,P.ptr(N3*k)); \
+ const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \
+ float sum_weights = 0; \
+ cimg_for_in##N##XYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) { \
+ cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,p,q,r,k,Q.ptr(N3*k)); \
+ float distance2 = 0; \
+ const T *pQ = Q.end(); \
+ cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \
+ distance2/=Pnorm; \
+ const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \
+ alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)func(alldist); \
+ sum_weights+=weight; \
+ { cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k); } \
+ } \
+ if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k)); \
+ }}
+#define _cimg_blur_patch2d(N,func) { \
+ const unsigned int N2 = N*N; \
+ cimg_for##N##XY(*this,x,y) { \
+ cimg_plugin_greycstoration_count; \
+ cimg_forV(*this,k) cimg_get##N##x##N(*this,x,y,0,k,P.ptr(N2*k)); \
+ const int x0 = x-rsize1, y0 = y-rsize1, x1 = x+rsize2, y1 = y+rsize2; \
+ float sum_weights = 0; \
+ cimg_for_in##N##XY(*this,x0,y0,x1,y1,p,q) { \
+ cimg_forV(*this,k) cimg_get##N##x##N(*this,p,q,0,k,Q.ptr(N2*k)); \
+ float distance2 = 0; \
+ const T *pQ = Q.end(); \
+ cimg_for(P,pP,T) { const float dI = (float)*pP-(float)*(--pQ); distance2+=dI*dI; } \
+ distance2/=Pnorm; \
+ const float dx = (float)p-x, dy = (float)q-y, \
+ alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)func(alldist); \
+ sum_weights+=weight; \
+ { cimg_forV(*this,k) res(x,y,k)+=weight*(*this)(p,q,k); } \
+ } \
+ if (sum_weights>0) cimg_forV(*this,k) res(x,y,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,k) = (Tfloat)((*this)(x,y,k)); \
+ }}
+
+ CImg<Tfloat> res(width,height,depth,dim,0);
+ CImg<T> P(patch_size*patch_size*dim), Q(P);
+ const float sigma_s2 = sigma_s*sigma_s, sigma_p2 = sigma_p*sigma_p, Pnorm = P.size()*sigma_p2;
+ const int rsize2 = (int)lookup_size/2, rsize1 = rsize2-1+(lookup_size%2);
+ if (depth>1) switch (patch_size) { // 3D version
+ case 2 :
+ if (fast_approx) { _cimg_blur_patch3d(2,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch3d(2,_cimg_blur_patch_slowfunc); }
+ break;
+ case 3 :
+ if (fast_approx) { _cimg_blur_patch3d(3,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch3d(3,_cimg_blur_patch_slowfunc); }
+ break;
+ default : {
+ const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
+ cimg_forXYZ(*this,x,y,z) {
+ cimg_plugin_greycstoration_count;
+ P = get_crop(x - psize0,y - psize0,z - psize0,x + psize1,y + psize1,z + psize1,true);
+ const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2;
+ float sum_weights = 0;
+ cimg_for_inXYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) {
+ (Q = get_crop(p - psize0,q - psize0,r - psize0,p + psize1,q + psize1,r + psize1,true))-=P;
+ const float
+ dx = (float)x - p, dy = (float)y - q, dz = (float)z - r,
+ distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2),
+ weight = (float)cimg_std::exp(-distance2);
+ sum_weights+=weight;
+ cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k);
+ }
+ if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k));
+ }
+ }
+ } else switch (patch_size) { // 2D version
+ case 2 :
+ if (fast_approx) { _cimg_blur_patch2d(2,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(2,_cimg_blur_patch_slowfunc); }
+ break;
+ case 3 :
+ if (fast_approx) { _cimg_blur_patch2d(3,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(3,_cimg_blur_patch_slowfunc); }
+ break;
+ case 4 :
+ if (fast_approx) { _cimg_blur_patch2d(4,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(4,_cimg_blur_patch_slowfunc); }
+ break;
+ case 5 :
+ if (fast_approx) { _cimg_blur_patch2d(5,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(5,_cimg_blur_patch_slowfunc); }
+ break;
+ case 6 :
+ if (fast_approx) { _cimg_blur_patch2d(6,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(6,_cimg_blur_patch_slowfunc); }
+ break;
+ case 7 :
+ if (fast_approx) { _cimg_blur_patch2d(7,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(7,_cimg_blur_patch_slowfunc); }
+ break;
+ case 8 :
+ if (fast_approx) { _cimg_blur_patch2d(8,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(8,_cimg_blur_patch_slowfunc); }
+ break;
+ case 9 :
+ if (fast_approx) { _cimg_blur_patch2d(9,_cimg_blur_patch_fastfunc); }
+ else { _cimg_blur_patch2d(9,_cimg_blur_patch_slowfunc); }
+ break;
+ default : {
+ const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
+ cimg_forXY(*this,x,y) {
+ cimg_plugin_greycstoration_count;
+ P = get_crop(x - psize0,y - psize0,x + psize1,y + psize1,true);
+ const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2;
+ float sum_weights = 0;
+ cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
+ (Q = get_crop(p - psize0,q - psize0,p + psize1,q + psize1,true))-=P;
+ const float
+ dx = (float)x - p, dy = (float)y - q,
+ distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2),
+ weight = (float)cimg_std::exp(-distance2);
+ sum_weights+=weight;
+ cimg_forV(*this,k) res(x,y,0,k)+=weight*(*this)(p,q,0,k);
+ }
+ if (sum_weights>0) cimg_forV(*this,k) res(x,y,0,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,0,k) = (Tfloat)((*this)(x,y,0,k));
+ }
+ }
+ }
+ return res.transfer_to(*this);
+ }
+
+ CImg<T> get_blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10,
+ const unsigned int lookup_size=4, const bool fast_approx=true) const {
+ return (+*this).blur_patch(patch_size,sigma_p,sigma_s,lookup_size,fast_approx);
+ }
+
+ //! Compute the Fast Fourier Transform of an image (along a specified axis).
+ CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
+ return CImgList<Tfloat>(*this).FFT(axis,invert);
+ }
+
+ //! Compute the Fast Fourier Transform on an image.
+ CImgList<Tfloat> get_FFT(const bool invert=false) const {
+ return CImgList<Tfloat>(*this).FFT(invert);
+ }
+
+ //! Apply a median filter.
+ CImg<T>& blur_median(const unsigned int n) {
+ return get_blur_median(n).transfer_to(*this);
+ }
+
+ CImg<T> get_blur_median(const unsigned int n) {
+ CImg<T> res(width,height,depth,dim);
+ if (!n || n==1) return *this;
+ const int hl=n/2, hr=hl-1+n%2;
+ if (res.depth!=1) { // 3D median filter
+ CImg<T> vois;
+ cimg_forXYZV(*this,x,y,z,k) {
+ const int
+ x0 = x - hl, y0 = y - hl, z0 = z-hl, x1 = x + hr, y1 = y + hr, z1 = z+hr,
+ nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, nz0 = z0<0?0:z0,
+ nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1, nz1 = z1>=dimz()?dimz()-1:z1;
+ vois = get_crop(nx0,ny0,nz0,k,nx1,ny1,nz1,k);
+ res(x,y,z,k) = vois.median();
+ }
+ } else {
+#define _cimg_median_sort(a,b) if ((a)>(b)) cimg::swap(a,b)
+ if (res.height!=1) switch (n) { // 2D median filter
+ case 3 : {
+ T I[9] = { 0 };
+ CImg_3x3(J,T);
+ cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
+ cimg_std::memcpy(J,I,9*sizeof(T));
+ _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
+ _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jpn, Jcn);
+ _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
+ _cimg_median_sort(Jpp, Jpc); _cimg_median_sort(Jnc, Jnn); _cimg_median_sort(Jcc, Jcn);
+ _cimg_median_sort(Jpc, Jpn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jnp, Jnc);
+ _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcc, Jnp); _cimg_median_sort(Jpn, Jcc);
+ _cimg_median_sort(Jcc, Jnp);
+ res(x,y,0,k) = Jcc;
+ }
+ } break;
+ case 5 : {
+ T I[25] = { 0 };
+ CImg_5x5(J,T);
+ cimg_forV(*this,k) cimg_for5x5(*this,x,y,0,k,I) {
+ cimg_std::memcpy(J,I,25*sizeof(T));
+ _cimg_median_sort(Jbb, Jpb); _cimg_median_sort(Jnb, Jab); _cimg_median_sort(Jcb, Jab); _cimg_median_sort(Jcb, Jnb);
+ _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbp, Jcp); _cimg_median_sort(Jbp, Jpp); _cimg_median_sort(Jap, Jbc);
+ _cimg_median_sort(Jnp, Jbc); _cimg_median_sort(Jnp, Jap); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jpc, Jnc);
+ _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jbn, Jpn); _cimg_median_sort(Jac, Jpn); _cimg_median_sort(Jac, Jbn);
+ _cimg_median_sort(Jnn, Jan); _cimg_median_sort(Jcn, Jan); _cimg_median_sort(Jcn, Jnn); _cimg_median_sort(Jpa, Jca);
+ _cimg_median_sort(Jba, Jca); _cimg_median_sort(Jba, Jpa); _cimg_median_sort(Jna, Jaa); _cimg_median_sort(Jcb, Jbp);
+ _cimg_median_sort(Jnb, Jpp); _cimg_median_sort(Jbb, Jpp); _cimg_median_sort(Jbb, Jnb); _cimg_median_sort(Jab, Jcp);
+ _cimg_median_sort(Jpb, Jcp); _cimg_median_sort(Jpb, Jab); _cimg_median_sort(Jpc, Jac); _cimg_median_sort(Jnp, Jac);
+ _cimg_median_sort(Jnp, Jpc); _cimg_median_sort(Jcc, Jbn); _cimg_median_sort(Jap, Jbn); _cimg_median_sort(Jap, Jcc);
+ _cimg_median_sort(Jnc, Jpn); _cimg_median_sort(Jbc, Jpn); _cimg_median_sort(Jbc, Jnc); _cimg_median_sort(Jba, Jna);
+ _cimg_median_sort(Jcn, Jna); _cimg_median_sort(Jcn, Jba); _cimg_median_sort(Jpa, Jaa); _cimg_median_sort(Jnn, Jaa);
+ _cimg_median_sort(Jnn, Jpa); _cimg_median_sort(Jan, Jca); _cimg_median_sort(Jnp, Jcn); _cimg_median_sort(Jap, Jnn);
+ _cimg_median_sort(Jbb, Jnn); _cimg_median_sort(Jbb, Jap); _cimg_median_sort(Jbc, Jan); _cimg_median_sort(Jpb, Jan);
+ _cimg_median_sort(Jpb, Jbc); _cimg_median_sort(Jpc, Jba); _cimg_median_sort(Jcb, Jba); _cimg_median_sort(Jcb, Jpc);
+ _cimg_median_sort(Jcc, Jpa); _cimg_median_sort(Jnb, Jpa); _cimg_median_sort(Jnb, Jcc); _cimg_median_sort(Jnc, Jca);
+ _cimg_median_sort(Jab, Jca); _cimg_median_sort(Jab, Jnc); _cimg_median_sort(Jac, Jna); _cimg_median_sort(Jbp, Jna);
+ _cimg_median_sort(Jbp, Jac); _cimg_median_sort(Jbn, Jaa); _cimg_median_sort(Jpp, Jaa); _cimg_median_sort(Jpp, Jbn);
+ _cimg_median_sort(Jcp, Jpn); _cimg_median_sort(Jcp, Jan); _cimg_median_sort(Jnc, Jpa); _cimg_median_sort(Jbn, Jna);
+ _cimg_median_sort(Jcp, Jnc); _cimg_median_sort(Jcp, Jbn); _cimg_median_sort(Jpb, Jap); _cimg_median_sort(Jnb, Jpc);
+ _cimg_median_sort(Jbp, Jcn); _cimg_median_sort(Jpc, Jcn); _cimg_median_sort(Jap, Jcn); _cimg_median_sort(Jab, Jbc);
+ _cimg_median_sort(Jpp, Jcc); _cimg_median_sort(Jcp, Jac); _cimg_median_sort(Jab, Jpp); _cimg_median_sort(Jab, Jcp);
+ _cimg_median_sort(Jcc, Jac); _cimg_median_sort(Jbc, Jac); _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbc, Jcc);
+ _cimg_median_sort(Jpp, Jbc); _cimg_median_sort(Jpp, Jcn); _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcp, Jcn);
+ _cimg_median_sort(Jcp, Jbc); _cimg_median_sort(Jcc, Jnn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jbc, Jnn);
+ _cimg_median_sort(Jcc, Jba); _cimg_median_sort(Jbc, Jba); _cimg_median_sort(Jbc, Jcc);
+ res(x,y,0,k) = Jcc;
+ }
+ } break;
+ default : {
+ CImg<T> vois;
+ cimg_forXYV(*this,x,y,k) {
+ const int
+ x0 = x - hl, y0 = y - hl, x1 = x + hr, y1 = y + hr,
+ nx0 = x0<0?0:x0, ny0 = y0<0?0:y0,
+ nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1;
+ vois = get_crop(nx0,ny0,0,k,nx1,ny1,0,k);
+ res(x,y,0,k) = vois.median();
+ }
+ }
+ } else switch (n) { // 1D median filter
+ case 2 : {
+ T I[4] = { 0 };
+ cimg_forV(*this,k) cimg_for2x2(*this,x,y,0,k,I) res(x,0,0,k) = (T)(0.5f*(I[0]+I[1]));
+ } break;
+ case 3 : {
+ T I[9] = { 0 };
+ cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
+ res(x,0,0,k) = I[3]<I[4]?
+ (I[4]<I[5]?I[4]:
+ (I[3]<I[5]?I[5]:I[3])):
+ (I[3]<I[5]?I[3]:
+ (I[4]<I[5]?I[5]:I[4]));
+ }
+ } break;
+ default : {
+ CImg<T> vois;
+ cimg_forXV(*this,x,k) {
+ const int
+ x0 = x - hl, x1 = x + hr,
+ nx0 = x0<0?0:x0, nx1 = x1>=dimx()?dimx()-1:x1;
+ vois = get_crop(nx0,0,0,k,nx1,0,0,k);
+ res(x,0,0,k) = vois.median();
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Sharpen image using anisotropic shock filters or inverse diffusion.
+ CImg<T>& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) {
+ if (is_empty()) return *this;
+ T valm, valM = maxmin(valm);
+ const bool threed = (depth>1);
+ const float nedge = 0.5f*edge;
+ CImg<Tfloat> val, vec, veloc(width,height,depth,dim);
+
+ if (threed) {
+ CImg_3x3x3(I,T);
+ if (sharpen_type) { // 3D Shock filter.
+ CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor());
+ if (sigma>0) G.blur(sigma);
+
+ cimg_forXYZ(G,x,y,z) {
+ G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
+ G(x,y,z,0) = vec(0,0);
+ G(x,y,z,1) = vec(0,1);
+ G(x,y,z,2) = vec(0,2);
+ G(x,y,z,3) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1]+val[2],-(Tfloat)nedge);
+ }
+ cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
+ const Tfloat
+ u = G(x,y,z,0),
+ v = G(x,y,z,1),
+ w = G(x,y,z,2),
+ amp = G(x,y,z,3),
+ ixx = (Tfloat)Incc + Ipcc - 2*Iccc,
+ ixy = 0.25f*((Tfloat)Innc + Ippc - Inpc - Ipnc),
+ ixz = 0.25f*((Tfloat)Incn + Ipcp - Incp - Ipcn),
+ iyy = (Tfloat)Icnc + Icpc - 2*Iccc,
+ iyz = 0.25f*((Tfloat)Icnn + Icpp - Icnp - Icpn),
+ izz = (Tfloat)Iccn + Iccp - 2*Iccc,
+ ixf = (Tfloat)Incc - Iccc,
+ ixb = (Tfloat)Iccc - Ipcc,
+ iyf = (Tfloat)Icnc - Iccc,
+ iyb = (Tfloat)Iccc - Icpc,
+ izf = (Tfloat)Iccn - Iccc,
+ izb = (Tfloat)Iccc - Iccp,
+ itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz,
+ it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb);
+ veloc(x,y,z,k) = -amp*cimg::sign(itt)*cimg::abs(it);
+ }
+ } else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) veloc(x,y,z,k) = -(Tfloat)Ipcc-Incc-Icpc-Icnc-Iccp-Iccn+6*Iccc; // 3D Inverse diffusion.
+ } else {
+ CImg_3x3(I,T);
+ if (sharpen_type) { // 2D Shock filter.
+ CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor());
+ if (sigma>0) G.blur(sigma);
+ cimg_forXY(G,x,y) {
+ G.get_tensor_at(x,y).symmetric_eigen(val,vec);
+ G(x,y,0) = vec(0,0);
+ G(x,y,1) = vec(0,1);
+ G(x,y,2) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1],-(Tfloat)nedge);
+ }
+ cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
+ const Tfloat
+ u = G(x,y,0),
+ v = G(x,y,1),
+ amp = G(x,y,2),
+ ixx = (Tfloat)Inc + Ipc - 2*Icc,
+ ixy = 0.25f*((Tfloat)Inn + Ipp - Inp - Ipn),
+ iyy = (Tfloat)Icn + Icp - 2*Icc,
+ ixf = (Tfloat)Inc - Icc,
+ ixb = (Tfloat)Icc - Ipc,
+ iyf = (Tfloat)Icn - Icc,
+ iyb = (Tfloat)Icc - Icp,
+ itt = u*u*ixx + v*v*iyy + 2*u*v*ixy,
+ it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb);
+ veloc(x,y,k) = -amp*cimg::sign(itt)*cimg::abs(it);
+ }
+ } else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) veloc(x,y,k) = -(Tfloat)Ipc-Inc-Icp-Icn+4*Icc; // 3D Inverse diffusion.
+ }
+ float m, M = (float)veloc.maxmin(m);
+ const float vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
+ if (vmax!=0) { veloc*=amplitude/vmax; (*this)+=veloc; }
+ return cut(valm,valM);
+ }
+
+ CImg<T> get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) const {
+ return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma);
+ }
+
+ //! Compute the Haar multiscale wavelet transform (monodimensional version).
+ /**
+ \param axis Axis considered for the transform.
+ \param invert Set inverse of direct transform.
+ \param nb_scales Number of scales used for the transform.
+ **/
+ CImg<T>& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) {
+ return get_haar(axis,invert,nb_scales).transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) const {
+ if (is_empty() || !nb_scales) return *this;
+ CImg<Tfloat> res;
+
+ if (nb_scales==1) {
+ switch (cimg::uncase(axis)) { // Single scale transform
+ case 'x' : {
+ const unsigned int w = width/2;
+ if (w) {
+ if (w%2)
+ throw CImgInstanceException("CImg<%s>::haar() : Sub-image width = %u is not even at a particular scale (=%u).",
+ pixel_type(),w);
+ res.assign(width,height,depth,dim);
+ if (invert) cimg_forYZV(*this,y,z,v) { // Inverse transform along X
+ for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
+ const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(xw,y,z,v);
+ res(x2++,y,z,v) = val0 - val1;
+ res(x2++,y,z,v) = val0 + val1;
+ }
+ } else cimg_forYZV(*this,y,z,v) { // Direct transform along X
+ for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
+ const Tfloat val0 = (Tfloat)(*this)(x2++,y,z,v), val1 = (Tfloat)(*this)(x2++,y,z,v);
+ res(x,y,z,v) = (val0 + val1)/2;
+ res(xw,y,z,v) = (val1 - val0)/2;
+ }
+ }
+ } else return *this;
+ } break;
+ case 'y' : {
+ const unsigned int h = height/2;
+ if (h) {
+ if (h%2)
+ throw CImgInstanceException("CImg<%s>::haar() : Sub-image height = %u is not even at a particular scale.",
+ pixel_type(),h);
+ res.assign(width,height,depth,dim);
+ if (invert) cimg_forXZV(*this,x,z,v) { // Inverse transform along Y
+ for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) {
+ const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(x,yh,z,v);
+ res(x,y2++,z,v) = val0 - val1;
+ res(x,y2++,z,v) = val0 + val1;
+ }
+ } else cimg_forXZV(*this,x,z,v) {
+ for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) { // Direct transform along Y
+ const Tfloat val0 = (Tfloat)(*this)(x,y2++,z,v), val1 = (Tfloat)(*this)(x,y2++,z,v);
+ res(x,y,z,v) = (val0 + val1)/2;
+ res(x,yh,z,v) = (val1 - val0)/2;
+ }
+ }
+ } else return *this;
+ } break;
+ case 'z' : {
+ const unsigned int d = depth/2;
+ if (d) {
+ if (d%2)
+ throw CImgInstanceException("CImg<%s>::haar() : Sub-image depth = %u is not even at a particular scale.",
+ pixel_type(),d);
+ res.assign(width,height,depth,dim);
+ if (invert) cimg_forXYV(*this,x,y,v) { // Inverse transform along Z
+ for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) {
+ const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(x,y,zd,v);
+ res(x,y,z2++,v) = val0 - val1;
+ res(x,y,z2++,v) = val0 + val1;
+ }
+ } else cimg_forXYV(*this,x,y,v) {
+ for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) { // Direct transform along Z
+ const Tfloat val0 = (Tfloat)(*this)(x,y,z2++,v), val1 = (Tfloat)(*this)(x,y,z2++,v);
+ res(x,y,z,v) = (val0 + val1)/2;
+ res(x,y,zd,v) = (val1 - val0)/2;
+ }
+ }
+ } else return *this;
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
+ pixel_type(),axis);
+ }
+ } else { // Multi-scale version
+ if (invert) {
+ res.assign(*this);
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ unsigned int w = width;
+ for (unsigned int s=1; w && s<nb_scales; ++s) w/=2;
+ for (w=w?w:1; w<=width; w*=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',true,1));
+ } break;
+ case 'y' : {
+ unsigned int h = width;
+ for (unsigned int s=1; h && s<nb_scales; ++s) h/=2;
+ for (h=h?h:1; h<=height; h*=2) res.draw_image(res.get_crop(0,0,width-1,h-1).get_haar('y',true,1));
+ } break;
+ case 'z' : {
+ unsigned int d = depth;
+ for (unsigned int s=1; d && s<nb_scales; ++s) d/=2;
+ for (d=d?d:1; d<=depth; d*=2) res.draw_image(res.get_crop(0,0,0,width-1,height-1,d-1).get_haar('z',true,1));
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
+ pixel_type(),axis);
+ }
+ } else { // Direct transform
+ res = get_haar(axis,false,1);
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ for (unsigned int s=1, w=width/2; w && s<nb_scales; ++s, w/=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',false,1));
+ } break;
+ case 'y' : {
+ for (unsigned int s=1, h=height/2; h && s<nb_scales; ++s, h/=2) res.draw_image(res.get_crop(0,0,width-1,h-1).get_haar('y',false,1));
+ } break;
+ case 'z' : {
+ for (unsigned int s=1, d=depth/2; d && s<nb_scales; ++s, d/=2) res.draw_image(res.get_crop(0,0,0,width-1,height-1,d-1).get_haar('z',false,1));
+ } break;
+ default :
+ throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
+ pixel_type(),axis);
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Compute the Haar multiscale wavelet transform.
+ /**
+ \param invert Set inverse of direct transform.
+ \param nb_scales Number of scales used for the transform.
+ **/
+ CImg<T>& haar(const bool invert=false, const unsigned int nb_scales=1) {
+ return get_haar(invert,nb_scales).transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_haar(const bool invert=false, const unsigned int nb_scales=1) const {
+ CImg<Tfloat> res;
+
+ if (nb_scales==1) { // Single scale transform
+ if (width>1) get_haar('x',invert,1).transfer_to(res);
+ if (height>1) { if (res) res.get_haar('y',invert,1).transfer_to(res); else get_haar('y',invert,1).transfer_to(res); }
+ if (depth>1) { if (res) res.get_haar('z',invert,1).transfer_to(res); else get_haar('z',invert,1).transfer_to(res); }
+ if (res) return res;
+ } else { // Multi-scale transform
+ if (invert) { // Inverse transform
+ res.assign(*this);
+ if (width>1) {
+ if (height>1) {
+ if (depth>1) {
+ unsigned int w = width, h = height, d = depth; for (unsigned int s=1; w && h && d && s<nb_scales; ++s) { w/=2; h/=2; d/=2; }
+ for (w=w?w:1, h=h?h:1, d=d?d:1; w<=width && h<=height && d<=depth; w*=2, h*=2, d*=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).get_haar(true,1));
+ } else {
+ unsigned int w = width, h = height; for (unsigned int s=1; w && h && s<nb_scales; ++s) { w/=2; h/=2; }
+ for (w=w?w:1, h=h?h:1; w<=width && h<=height; w*=2, h*=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).get_haar(true,1));
+ }
+ } else {
+ if (depth>1) {
+ unsigned int w = width, d = depth; for (unsigned int s=1; w && d && s<nb_scales; ++s) { w/=2; d/=2; }
+ for (w=w?w:1, d=d?d:1; w<=width && d<=depth; w*=2, d*=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).get_haar(true,1));
+ } else {
+ unsigned int w = width; for (unsigned int s=1; w && s<nb_scales; ++s) w/=2;
+ for (w=w?w:1; w<=width; w*=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,0,0).get_haar(true,1));
+ }
+ }
+ } else {
+ if (height>1) {
+ if (depth>1) {
+ unsigned int h = height, d = depth; for (unsigned int s=1; h && d && s<nb_scales; ++s) { h/=2; d/=2; }
+ for (h=h?h:1, d=d?d:1; h<=height && d<=depth; h*=2, d*=2)
+ res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).get_haar(true,1));
+ } else {
+ unsigned int h = height; for (unsigned int s=1; h && s<nb_scales; ++s) h/=2;
+ for (h=h?h:1; h<=height; h*=2)
+ res.draw_image(res.get_crop(0,0,0,0,h-1,0).get_haar(true,1));
+ }
+ } else {
+ if (depth>1) {
+ unsigned int d = depth; for (unsigned int s=1; d && s<nb_scales; ++s) d/=2;
+ for (d=d?d:1; d<=depth; d*=2)
+ res.draw_image(res.get_crop(0,0,0,0,0,d-1).get_haar(true,1));
+ } else return *this;
+ }
+ }
+ } else { // Direct transform
+ res = get_haar(false,1);
+ if (width>1) {
+ if (height>1) {
+ if (depth>1) for (unsigned int s=1, w=width/2, h=height/2, d=depth/2; w && h && d && s<nb_scales; ++s, w/=2, h/=2, d/=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).haar(false,1));
+ else for (unsigned int s=1, w=width/2, h=height/2; w && h && s<nb_scales; ++s, w/=2, h/=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).haar(false,1));
+ } else {
+ if (depth>1) for (unsigned int s=1, w=width/2, d=depth/2; w && d && s<nb_scales; ++s, w/=2, d/=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).haar(false,1));
+ else for (unsigned int s=1, w=width/2; w && s<nb_scales; ++s, w/=2)
+ res.draw_image(res.get_crop(0,0,0,w-1,0,0).haar(false,1));
+ }
+ } else {
+ if (height>1) {
+ if (depth>1) for (unsigned int s=1, h=height/2, d=depth/2; h && d && s<nb_scales; ++s, h/=2, d/=2)
+ res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).haar(false,1));
+ else for (unsigned int s=1, h=height/2; h && s<nb_scales; ++s, h/=2)
+ res.draw_image(res.get_crop(0,0,0,0,h-1,0).haar(false,1));
+ } else {
+ if (depth>1) for (unsigned int s=1, d=depth/2; d && s<nb_scales; ++s, d/=2)
+ res.draw_image(res.get_crop(0,0,0,0,0,d-1).haar(false,1));
+ else return *this;
+ }
+ }
+ }
+ return res;
+ }
+ return *this;
+ }
+
+ //! Estimate a displacement field between instance image and given target image.
+ CImg<T>& displacement_field(const CImg<T>& target, const float smooth=0.1f, const float precision=0.1f,
+ const unsigned int nb_scales=0, const unsigned int itermax=10000) {
+ return get_displacement_field(target,smooth,precision,nb_scales,itermax).transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_displacement_field(const CImg<T>& target,
+ const float smoothness=0.1f, const float precision=0.1f,
+ const unsigned int nb_scales=0, const unsigned int itermax=10000) const {
+ if (is_empty() || !target) return *this;
+ if (!is_sameXYZV(target))
+ throw CImgArgumentException("CImg<%s>::displacement_field() : Instance image (%u,%u,%u,%u,%p) and target image (%u,%u,%u,%u,%p) "
+ "have different size.",
+ pixel_type(),width,height,depth,dim,data,
+ target.width,target.height,target.depth,target.dim,target.data);
+ if (smoothness<0)
+ throw CImgArgumentException("CImg<%s>::displacement_field() : Smoothness parameter %g is negative.",
+ pixel_type(),smoothness);
+ if (precision<0)
+ throw CImgArgumentException("CImg<%s>::displacement_field() : Precision parameter %g is negative.",
+ pixel_type(),precision);
+
+ const unsigned int nscales = nb_scales>0?nb_scales:(unsigned int)(2*cimg_std::log((double)(cimg::max(width,height,depth))));
+ Tfloat m1, M1 = (Tfloat)maxmin(m1), m2, M2 = (Tfloat)target.maxmin(m2);
+ const Tfloat factor = cimg::max(cimg::abs(m1),cimg::abs(M1),cimg::abs(m2),cimg::abs(M2));
+ CImg<Tfloat> U0;
+ const bool threed = (depth>1);
+
+ // Begin multi-scale motion estimation
+ for (int scale = (int)nscales-1; scale>=0; --scale) {
+ const float sfactor = (float)cimg_std::pow(1.5f,(float)scale), sprecision = (float)(precision/cimg_std::pow(2.25,1+scale));
+ const int
+ sw = (int)(width/sfactor), sh = (int)(height/sfactor), sd = (int)(depth/sfactor),
+ swidth = sw?sw:1, sheight = sh?sh:1, sdepth = sd?sd:1;
+ CImg<Tfloat>
+ I1 = get_resize(swidth,sheight,sdepth,-100,2),
+ I2 = target.get_resize(swidth,sheight,sdepth,-100,2);
+ I1/=factor; I2/=factor;
+ CImg<Tfloat> U;
+ if (U0) U = (U0*=1.5f).get_resize(I1.dimx(),I1.dimy(),I1.dimz(),-100,3);
+ else U.assign(I1.dimx(),I1.dimy(),I1.dimz(),threed?3:2,0);
+
+ // Begin single-scale motion estimation
+ CImg<Tfloat> veloc(U);
+ float dt = 2, Energy = cimg::type<float>::max();
+ const CImgList<Tfloat> dI = I2.get_gradient();
+ for (unsigned int iter=0; iter<itermax; iter++) {
+ veloc.fill(0);
+ float nEnergy = 0;
+ if (threed) {
+ cimg_for3XYZ(U,x,y,z) {
+ const float X = (float)(x + U(x,y,z,0)), Y = (float)(y + U(x,y,z,1)), Z = (float)(z + U(x,y,z,2));
+ cimg_forV(U,k) {
+ const Tfloat
+ Ux = 0.5f*(U(_n1x,y,z,k) - U(_p1x,y,z,k)),
+ Uy = 0.5f*(U(x,_n1y,z,k) - U(x,_p1y,z,k)),
+ Uz = 0.5f*(U(x,y,_n1z,k) - U(x,y,_p1z,k)),
+ Uxx = U(_n1x,y,z,k) + U(_p1x,y,z,k) - 2*U(x,y,z,k),
+ Uyy = U(x,_n1y,z,k) + U(x,_p1y,z,k) - 2*U(x,y,z,k),
+ Uzz = U(x,y,_n1z,k) + U(x,y,_n1z,k) - 2*U(x,y,z,k);
+ nEnergy += (float)(smoothness*(Ux*Ux + Uy*Uy + Uz*Uz));
+ Tfloat deltaIgrad = 0;
+ cimg_forV(I1,i) {
+ const Tfloat deltaIi = (float)(I2._linear_atXYZ(X,Y,Z,i) - I1(x,y,z,i));
+ nEnergy += (float)(deltaIi*deltaIi/2);
+ deltaIgrad+=-deltaIi*dI[k]._linear_atXYZ(X,Y,Z,i);
+ }
+ veloc(x,y,z,k) = deltaIgrad + smoothness*(Uxx + Uyy + Uzz);
+ }
+ }
+ } else {
+ cimg_for3XY(U,x,y) {
+ const float X = (float)(x + U(x,y,0)), Y = (float)(y + U(x,y,1));
+ cimg_forV(U,k) {
+ const Tfloat
+ Ux = 0.5f*(U(_n1x,y,k) - U(_p1x,y,k)),
+ Uy = 0.5f*(U(x,_n1y,k) - U(x,_p1y,k)),
+ Uxx = U(_n1x,y,k) + U(_p1x,y,k) - 2*U(x,y,k),
+ Uyy = U(x,_n1y,k) + U(x,_p1y,k) - 2*U(x,y,k);
+ nEnergy += (float)(smoothness*(Ux*Ux + Uy*Uy));
+ Tfloat deltaIgrad = 0;
+ cimg_forV(I1,i) {
+ const Tfloat deltaIi = (float)(I2._linear_atXY(X,Y,i) - I1(x,y,i));
+ nEnergy += (float)(deltaIi*deltaIi/2);
+ deltaIgrad+=-deltaIi*dI[k]._linear_atXY(X,Y,i);
+ }
+ veloc(x,y,k) = deltaIgrad + smoothness*(Uxx + Uyy);
+ }
+ }
+ }
+ const Tfloat vmax = cimg::max(cimg::abs(veloc.min()), cimg::abs(veloc.max()));
+ U+=(veloc*=dt/vmax);
+ if (cimg::abs(nEnergy-Energy)<sprecision) break;
+ if (nEnergy<Energy) dt*=0.5f;
+ Energy = nEnergy;
+ }
+ U.transfer_to(U0);
+ }
+ return U0;
+ }
+
+ //@}
+ //-----------------------------
+ //
+ //! \name Matrix and Vectors
+ //@{
+ //-----------------------------
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0) {
+ static CImg<T> r(1,1); r[0] = a0;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1) {
+ static CImg<T> r(1,2); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2) {
+ static CImg<T> r(1,3); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3) {
+ static CImg<T> r(1,4); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
+ static CImg<T> r(1,5); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) {
+ static CImg<T> r(1,6); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6) {
+ static CImg<T> r(1,7); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7) {
+ static CImg<T> r(1,8); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8) {
+ static CImg<T> r(1,9); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9) {
+ static CImg<T> r(1,10); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10) {
+ static CImg<T> r(1,11); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11) {
+ static CImg<T> r(1,12); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12) {
+ static CImg<T> r(1,13); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+ *(ptr++) = a12;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13) {
+ static CImg<T> r(1,14); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+ *(ptr++) = a12; *(ptr++) = a13;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13, const T& a14) {
+ static CImg<T> r(1,15); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+ *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
+ return r;
+ }
+
+ //! Return a vector with specified coefficients.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13, const T& a14, const T& a15) {
+ static CImg<T> r(1,16); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+ *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
+ return r;
+ }
+
+ //! Return a 1x1 square matrix with specified coefficients.
+ static CImg<T> matrix(const T& a0) {
+ return vector(a0);
+ }
+
+ //! Return a 2x2 square matrix with specified coefficients.
+ static CImg<T> matrix(const T& a0, const T& a1,
+ const T& a2, const T& a3) {
+ static CImg<T> r(2,2); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1;
+ *(ptr++) = a2; *(ptr++) = a3;
+ return r;
+ }
+
+ //! Return a 3x3 square matrix with specified coefficients.
+ static CImg<T> matrix(const T& a0, const T& a1, const T& a2,
+ const T& a3, const T& a4, const T& a5,
+ const T& a6, const T& a7, const T& a8) {
+ static CImg<T> r(3,3); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
+ *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
+ *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8;
+ return r;
+ }
+
+ //! Return a 4x4 square matrix with specified coefficients.
+ static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13, const T& a14, const T& a15) {
+ static CImg<T> r(4,4); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+ *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+ *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
+ return r;
+ }
+
+ //! Return a 5x5 square matrix with specified coefficients.
+ static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4,
+ const T& a5, const T& a6, const T& a7, const T& a8, const T& a9,
+ const T& a10, const T& a11, const T& a12, const T& a13, const T& a14,
+ const T& a15, const T& a16, const T& a17, const T& a18, const T& a19,
+ const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) {
+ static CImg<T> r(5,5); T *ptr = r.data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
+ *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9;
+ *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
+ *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19;
+ *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24;
+ return r;
+ }
+
+ //! Return a 1x1 symmetric matrix with specified coefficients.
+ static CImg<T> tensor(const T& a1) {
+ return matrix(a1);
+ }
+
+ //! Return a 2x2 symmetric matrix tensor with specified coefficients.
+ static CImg<T> tensor(const T& a1, const T& a2, const T& a3) {
+ return matrix(a1,a2,a2,a3);
+ }
+
+ //! Return a 3x3 symmetric matrix with specified coefficients.
+ static CImg<T> tensor(const T& a1, const T& a2, const T& a3, const T& a4, const T& a5, const T& a6) {
+ return matrix(a1,a2,a3,a2,a4,a5,a3,a5,a6);
+ }
+
+ //! Return a 1x1 diagonal matrix with specified coefficients.
+ static CImg<T> diagonal(const T& a0) {
+ return matrix(a0);
+ }
+
+ //! Return a 2x2 diagonal matrix with specified coefficients.
+ static CImg<T> diagonal(const T& a0, const T& a1) {
+ return matrix(a0,0,0,a1);
+ }
+
+ //! Return a 3x3 diagonal matrix with specified coefficients.
+ static CImg<T> diagonal(const T& a0, const T& a1, const T& a2) {
+ return matrix(a0,0,0,0,a1,0,0,0,a2);
+ }
+
+ //! Return a 4x4 diagonal matrix with specified coefficients.
+ static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3) {
+ return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3);
+ }
+
+ //! Return a 5x5 diagonal matrix with specified coefficients.
+ static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
+ return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4);
+ }
+
+ //! Return a NxN identity matrix.
+ static CImg<T> identity_matrix(const unsigned int N) {
+ CImg<T> res(N,N,1,1,0);
+ cimg_forX(res,x) res(x,x) = 1;
+ return res;
+ }
+
+ //! Return a N-numbered sequence vector from \p a0 to \p a1.
+ static CImg<T> sequence(const unsigned int N, const T a0, const T a1) {
+ if (N) return CImg<T>(1,N).sequence(a0,a1);
+ return CImg<T>();
+ }
+
+ //! Return a 3x3 rotation matrix along the (x,y,z)-axis with an angle w.
+ static CImg<T> rotation_matrix(const float x, const float y, const float z, const float w, const bool quaternion_data=false) {
+ float X,Y,Z,W;
+ if (!quaternion_data) {
+ const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z),
+ nx = norm>0?x/norm:0,
+ ny = norm>0?y/norm:0,
+ nz = norm>0?z/norm:1,
+ nw = norm>0?w:0,
+ sina = (float)cimg_std::sin(nw/2),
+ cosa = (float)cimg_std::cos(nw/2);
+ X = nx*sina;
+ Y = ny*sina;
+ Z = nz*sina;
+ W = cosa;
+ } else {
+ const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z + w*w);
+ if (norm>0) { X = x/norm; Y = y/norm; Z = z/norm; W = w/norm; }
+ else { X = Y = Z = 0; W = 1; }
+ }
+ const float xx = X*X, xy = X*Y, xz = X*Z, xw = X*W, yy = Y*Y, yz = Y*Z, yw = Y*W, zz = Z*Z, zw = Z*W;
+ return CImg<T>::matrix((T)(1-2*(yy+zz)), (T)(2*(xy+zw)), (T)(2*(xz-yw)),
+ (T)(2*(xy-zw)), (T)(1-2*(xx+zz)), (T)(2*(yz+xw)),
+ (T)(2*(xz+yw)), (T)(2*(yz-xw)), (T)(1-2*(xx+yy)));
+ }
+
+ //! Return a new image corresponding to the vector located at (\p x,\p y,\p z) of the current vector-valued image.
+ CImg<T> get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
+ static CImg<T> dest;
+ if (dest.height!=dim) dest.assign(1,dim);
+ const unsigned int whz = width*height*depth;
+ const T *ptrs = ptr(x,y,z);
+ T *ptrd = dest.data;
+ cimg_forV(*this,k) { *(ptrd++) = *ptrs; ptrs+=whz; }
+ return dest;
+ }
+
+ //! Set the image \p vec as the \a vector \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
+ template<typename t>
+ CImg<T>& set_vector_at(const CImg<t>& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) {
+ if (x<width && y<height && z<depth) {
+ const unsigned int whz = width*height*depth;
+ const t *ptrs = vec.data;
+ T *ptrd = ptr(x,y,z);
+ for (unsigned int k=cimg::min((unsigned int)vec.size(),dim); k; --k) { *ptrd = (T)*(ptrs++); ptrd+=whz; }
+ }
+ return *this;
+ }
+
+ //! Return a new image corresponding to the \a square \a matrix located at (\p x,\p y,\p z) of the current vector-valued image.
+ CImg<T> get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const {
+ const int n = (int)cimg_std::sqrt((double)dim);
+ CImg<T> dest(n,n);
+ cimg_forV(*this,k) dest[k]=(*this)(x,y,z,k);
+ return dest;
+ }
+
+ //! Set the image \p vec as the \a square \a matrix-valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
+ template<typename t>
+ CImg<T>& set_matrix_at(const CImg<t>& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
+ return set_vector_at(mat,x,y,z);
+ }
+
+ //! Return a new image corresponding to the \a diffusion \a tensor located at (\p x,\p y,\p z) of the current vector-valued image.
+ CImg<T> get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
+ if (dim==6) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2),
+ (*this)(x,y,z,3),(*this)(x,y,z,4),(*this)(x,y,z,5));
+ if (dim==3) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2));
+ return tensor((*this)(x,y,z,0));
+ }
+
+ //! Set the image \p vec as the \a tensor \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
+ template<typename t>
+ CImg<T>& set_tensor_at(const CImg<t>& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
+ if (ten.height==2) {
+ (*this)(x,y,z,0) = (T)ten[0];
+ (*this)(x,y,z,1) = (T)ten[1];
+ (*this)(x,y,z,2) = (T)ten[3];
+ }
+ else {
+ (*this)(x,y,z,0) = (T)ten[0];
+ (*this)(x,y,z,1) = (T)ten[1];
+ (*this)(x,y,z,2) = (T)ten[2];
+ (*this)(x,y,z,3) = (T)ten[4];
+ (*this)(x,y,z,4) = (T)ten[5];
+ (*this)(x,y,z,5) = (T)ten[8];
+ }
+ return *this;
+ }
+
+ //! Unroll all images values into a one-column vector.
+ CImg<T>& vector() {
+ return unroll('y');
+ }
+
+ CImg<T> get_vector() const {
+ return get_unroll('y');
+ }
+
+ //! Realign pixel values of the instance image as a square matrix
+ CImg<T>& matrix() {
+ const unsigned int siz = size();
+ switch (siz) {
+ case 1 : break;
+ case 4 : width = height = 2; break;
+ case 9 : width = height = 3; break;
+ case 16 : width = height = 4; break;
+ case 25 : width = height = 5; break;
+ case 36 : width = height = 6; break;
+ case 49 : width = height = 7; break;
+ case 64 : width = height = 8; break;
+ case 81 : width = height = 9; break;
+ case 100 : width = height = 10; break;
+ default : {
+ unsigned int i = 11, i2 = i*i;
+ while (i2<siz) { i2+=2*i+1; ++i; }
+ if (i2==siz) width = height = i;
+ else throw CImgInstanceException("CImg<%s>::matrix() : Image size = %u is not a square number",
+ pixel_type(),siz);
+ }
+ }
+ return *this;
+ }
+
+ CImg<T> get_matrix() const {
+ return (+*this).matrix();
+ }
+
+ //! Realign pixel values of the instance image as a symmetric tensor.
+ CImg<T>& tensor() {
+ return get_tensor().transfer_to(*this);
+ }
+
+ CImg<T> get_tensor() const {
+ CImg<T> res;
+ const unsigned int siz = size();
+ switch (siz) {
+ case 1 : break;
+ case 3 :
+ res.assign(2,2);
+ res(0,0) = (*this)(0);
+ res(1,0) = res(0,1) = (*this)(1);
+ res(1,1) = (*this)(2);
+ break;
+ case 6 :
+ res.assign(3,3);
+ res(0,0) = (*this)(0);
+ res(1,0) = res(0,1) = (*this)(1);
+ res(2,0) = res(0,2) = (*this)(2);
+ res(1,1) = (*this)(3);
+ res(2,1) = res(1,2) = (*this)(4);
+ res(2,2) = (*this)(5);
+ break;
+ default :
+ throw CImgInstanceException("CImg<%s>::tensor() : Wrong vector dimension = %u in instance image.",
+ pixel_type(), dim);
+ }
+ return res;
+ }
+
+ //! Unroll all images values into specified axis.
+ CImg<T>& unroll(const char axis) {
+ const unsigned int siz = size();
+ if (siz) switch (axis) {
+ case 'x' : width = siz; height=depth=dim=1; break;
+ case 'y' : height = siz; width=depth=dim=1; break;
+ case 'z' : depth = siz; width=height=dim=1; break;
+ case 'v' : dim = siz; width=height=depth=1; break;
+ default :
+ throw CImgArgumentException("CImg<%s>::unroll() : Given axis is '%c' which is not 'x','y','z' or 'v'",
+ pixel_type(),axis);
+ }
+ return *this;
+ }
+
+ CImg<T> get_unroll(const char axis) const {
+ return (+*this).unroll(axis);
+ }
+
+ //! Get a diagonal matrix, whose diagonal coefficients are the coefficients of the input image.
+ CImg<T>& diagonal() {
+ return get_diagonal().transfer_to(*this);
+ }
+
+ CImg<T> get_diagonal() const {
+ if (is_empty()) return *this;
+ CImg<T> res(size(),size(),1,1,0);
+ cimg_foroff(*this,off) res(off,off) = (*this)(off);
+ return res;
+ }
+
+ //! Get an identity matrix having same dimension than instance image.
+ CImg<T>& identity_matrix() {
+ return identity_matrix(cimg::max(width,height)).transfer_to(*this);
+ }
+
+ CImg<T> get_identity_matrix() const {
+ return identity_matrix(cimg::max(width,height));
+ }
+
+ //! Return a N-numbered sequence vector from \p a0 to \p a1.
+ CImg<T>& sequence(const T a0, const T a1) {
+ if (is_empty()) return *this;
+ const unsigned int siz = size() - 1;
+ T* ptr = data;
+ if (siz) {
+ const Tfloat delta = (Tfloat)a1 - a0;
+ cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz);
+ } else *ptr = a0;
+ return *this;
+ }
+
+ CImg<T> get_sequence(const T a0, const T a1) const {
+ return (+*this).sequence(a0,a1);
+ }
+
+ //! Transpose the current matrix.
+ CImg<T>& transpose() {
+ if (width==1) { width=height; height=1; return *this; }
+ if (height==1) { height=width; width=1; return *this; }
+ if (width==height) {
+ cimg_forYZV(*this,y,z,v) for (int x=y; x<dimx(); ++x) cimg::swap((*this)(x,y,z,v),(*this)(y,x,z,v));
+ return *this;
+ }
+ return get_transpose().transfer_to(*this);
+ }
+
+ CImg<T> get_transpose() const {
+ return get_permute_axes("yxzv");
+ }
+
+ //! Invert the current matrix.
+ CImg<T>& invert(const bool use_LU=true) {
+ if (!is_empty()) {
+ if (width!=height || depth!=1 || dim!=1)
+ throw CImgInstanceException("CImg<%s>::invert() : Instance matrix (%u,%u,%u,%u,%p) is not square.",
+ pixel_type(),width,height,depth,dim,data);
+#ifdef cimg_use_lapack
+ int INFO = (int)use_LU, N = width, LWORK = 4*N, *IPIV = new int[N];
+ Tfloat
+ *lapA = new Tfloat[N*N],
+ *WORK = new Tfloat[LWORK];
+ cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
+ cimg::getrf(N,lapA,IPIV,INFO);
+ if (INFO)
+ cimg::warn("CImg<%s>::invert() : LAPACK library function dgetrf_() returned error code %d.",
+ pixel_type(),INFO);
+ else {
+ cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO);
+ if (INFO)
+ cimg::warn("CImg<%s>::invert() : LAPACK library function dgetri_() returned Error code %d",
+ pixel_type(),INFO);
+ }
+ if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N+l]); else fill(0);
+ delete[] IPIV; delete[] lapA; delete[] WORK;
+#else
+ const double dete = width>3?-1.0:det();
+ if (dete!=0.0 && width==2) {
+ const double
+ a = data[0], c = data[1],
+ b = data[2], d = data[3];
+ data[0] = (T)(d/dete); data[1] = (T)(-c/dete);
+ data[2] = (T)(-b/dete); data[3] = (T)(a/dete);
+ } else if (dete!=0.0 && width==3) {
+ const double
+ a = data[0], d = data[1], g = data[2],
+ b = data[3], e = data[4], h = data[5],
+ c = data[6], f = data[7], i = data[8];
+ data[0] = (T)((i*e-f*h)/dete), data[1] = (T)((g*f-i*d)/dete), data[2] = (T)((d*h-g*e)/dete);
+ data[3] = (T)((h*c-i*b)/dete), data[4] = (T)((i*a-c*g)/dete), data[5] = (T)((g*b-a*h)/dete);
+ data[6] = (T)((b*f-e*c)/dete), data[7] = (T)((d*c-a*f)/dete), data[8] = (T)((a*e-d*b)/dete);
+ } else {
+ if (use_LU) { // LU-based inverse computation
+ CImg<Tfloat> A(*this), indx, col(1,width);
+ bool d;
+ A._LU(indx,d);
+ cimg_forX(*this,j) {
+ col.fill(0);
+ col(j) = 1;
+ col._solve(A,indx);
+ cimg_forX(*this,i) (*this)(j,i) = (T)col(i);
+ }
+ } else { // SVD-based inverse computation
+ CImg<Tfloat> U(width,width), S(1,width), V(width,width);
+ SVD(U,S,V,false);
+ U.transpose();
+ cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k];
+ S.diagonal();
+ *this = V*S*U;
+ }
+ }
+#endif
+ }
+ return *this;
+ }
+
+ CImg<Tfloat> get_invert(const bool use_LU=true) const {
+ return CImg<Tfloat>(*this,false).invert(use_LU);
+ }
+
+ //! Compute the pseudo-inverse (Moore-Penrose) of the matrix.
+ CImg<T>& pseudoinvert() {
+ return get_pseudoinvert().transfer_to(*this);
+ }
+
+ CImg<Tfloat> get_pseudoinvert() const {
+ CImg<Tfloat> U, S, V;
+ SVD(U,S,V);
+ cimg_forX(V,x) {
+ const Tfloat s = S(x), invs = s!=0?1/s:(Tfloat)0;
+ cimg_forY(V,y) V(x,y)*=invs;
+ }
+ return V*U.transpose();
+ }
+
+ //! Compute the cross product between two 3d vectors.
+ template<typename t>
+ CImg<T>& cross(const CImg<t>& img) {
+ if (width!=1 || height<3 || img.width!=1 || img.height<3)
+ throw CImgInstanceException("CImg<%s>::cross() : Arguments (%u,%u,%u,%u,%p) and (%u,%u,%u,%u,%p) must be both 3d vectors.",
+ pixel_type(),width,height,depth,dim,data,img.width,img.height,img.depth,img.dim,img.data);
+ const T x = (*this)[0], y = (*this)[1], z = (*this)[2];
+ (*this)[0] = (T)(y*img[2]-z*img[1]);
+ (*this)[1] = (T)(z*img[0]-x*img[2]);
+ (*this)[2] = (T)(x*img[1]-y*img[0]);
+ return *this;
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset<T,t>::type> get_cross(const CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImg<Tt>(*this).cross(img);
+ }
+
+ //! Solve a linear system AX=B where B=*this.
+ template<typename t>
+ CImg<T>& solve(const CImg<t>& A) {
+ if (width!=1 || depth!=1 || dim!=1 || height!=A.height || A.depth!=1 || A.dim!=1)
+ throw CImgArgumentException("CImg<%s>::solve() : Instance matrix size is (%u,%u,%u,%u) while "
+ "size of given matrix A is (%u,%u,%u,%u).",
+ pixel_type(),width,height,depth,dim,A.width,A.height,A.depth,A.dim);
+
+ typedef typename cimg::superset2<T,t,float>::type Ttfloat;
+ if (A.width==A.height) {
+#ifdef cimg_use_lapack
+ char TRANS='N';
+ int INFO, N = height, LWORK = 4*N, one = 1, *IPIV = new int[N];
+ Ttfloat
+ *lapA = new Ttfloat[N*N],
+ *lapB = new Ttfloat[N],
+ *WORK = new Ttfloat[LWORK];
+ cimg_forXY(A,k,l) lapA[k*N+l] = (Ttfloat)(A(k,l));
+ cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i));
+ cimg::getrf(N,lapA,IPIV,INFO);
+ if (INFO)
+ cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrf_() returned error code %d.",
+ pixel_type(),INFO);
+ if (!INFO) {
+ cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
+ if (INFO)
+ cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrs_() returned Error code %d",
+ pixel_type(),INFO);
+ }
+ if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0);
+ delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
+#else
+ CImg<Ttfloat> lu(A);
+ CImg<Ttfloat> indx;
+ bool d;
+ lu._LU(indx,d);
+ _solve(lu,indx);
+#endif
+ } else assign(A.get_pseudoinvert()*(*this));
+ return *this;
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset2<T,t,float>::type> get_solve(const CImg<t>& A) const {
+ typedef typename cimg::superset2<T,t,float>::type Ttfloat;
+ return CImg<Ttfloat>(*this,false).solve(A);
+ }
+
+ template<typename t, typename ti>
+ CImg<T>& _solve(const CImg<t>& A, const CImg<ti>& indx) {
+ typedef typename cimg::superset2<T,t,float>::type Ttfloat;
+ const int N = size();
+ int ii = -1;
+ Ttfloat sum;
+ for (int i=0; i<N; ++i) {
+ const int ip = (int)indx[i];
+ Ttfloat sum = (*this)(ip);
+ (*this)(ip) = (*this)(i);
+ if (ii>=0) for (int j=ii; j<=i-1; ++j) sum-=A(j,i)*(*this)(j);
+ else if (sum!=0) ii=i;
+ (*this)(i) = (T)sum;
+ }
+ { for (int i=N-1; i>=0; --i) {
+ sum = (*this)(i);
+ for (int j=i+1; j<N; ++j) sum-=A(j,i)*(*this)(j);
+ (*this)(i) = (T)(sum/A(i,i));
+ }}
+ return *this;
+ }
+
+ //! Solve a linear system AX=B where B=*this and A is a tridiagonal matrix A = [ b0,c0,0,...; a1,b1,c1,0,... ; ... ; ...,0,aN,bN ].
+ // (Use the Thomas Algorithm).
+ template<typename t>
+ CImg<T>& solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) {
+ const int siz = (int)size();
+ if ((int)a.size()!=siz || (int)b.size()!=siz || (int)c.size()!=siz)
+ throw CImgArgumentException("CImg<%s>::solve_tridiagonal() : arrays of triagonal coefficients have different size.",pixel_type);
+ typedef typename cimg::superset2<T,t,float>::type Ttfloat;
+ CImg<Ttfloat> nc(siz);
+ const T *ptra = a.data, *ptrb = b.data, *ptrc = c.data;
+ T *ptrnc = nc.data, *ptrd = data;
+ const Ttfloat valb0 = (Ttfloat)*(ptrb++);
+ *ptrnc = *(ptrc++)/valb0;
+ Ttfloat vald = (Ttfloat)(*(ptrd++)/=valb0);
+ for (int i = 1; i<siz; ++i) {
+ const Ttfloat
+ vala = (Tfloat)*(ptra++),
+ id = 1/(*(ptrb++) - *(ptrnc++)*vala);
+ *ptrnc = *(ptrc++)*id;
+ vald = ((*ptrd-=vala*vald)*=id);
+ ++ptrd;
+ }
+ vald = *(--ptrd);
+ for (int i = siz-2; i>=0; --i) vald = (*(--ptrd)-=*(--ptrnc)*vald);
+ return *this;
+ }
+
+ template<typename t>
+ CImg<typename cimg::superset2<T,t,float>::type> get_solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) const {
+ typedef typename cimg::superset2<T,t,float>::type Ttfloat;
+ return CImg<Ttfloat>(*this,false).solve_tridiagonal(a,b,c);
+ }
+
+ //! Sort values of a vector and get permutations.
+ template<typename t>
+ CImg<T>& sort(CImg<t>& permutations, const bool increasing=true) {
+ if (is_empty()) permutations.assign();
+ else {
+ if (permutations.size()!=size()) permutations.assign(size());
+ cimg_foroff(permutations,off) permutations[off] = (t)off;
+ _quicksort(0,size()-1,permutations,increasing);
+ }
+ return *this;
+ }
+
+ template<typename t>
+ CImg<T> get_sort(CImg<t>& permutations, const bool increasing=true) const {
+ return (+*this).sort(permutations,increasing);
+ }
+
+ // Sort image values.
+ CImg<T>& sort(const bool increasing=true) {
+ CImg<T> foo;
+ return sort(foo,increasing);
+ }
+
+ CImg<T> get_sort(const bool increasing=true) const {
+ return (+*this).sort(increasing);
+ }
+
+ template<typename t>
+ CImg<T>& _quicksort(const int min, const int max, CImg<t>& permutations, const bool increasing) {
+ if (min<max) {
+ const int mid = (min+max)/2;
+ if (increasing) {
+ if ((*this)[min]>(*this)[mid]) {
+ cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
+ if ((*this)[mid]>(*this)[max]) {
+ cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
+ if ((*this)[min]>(*this)[mid]) {
+ cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
+ } else {
+ if ((*this)[min]<(*this)[mid]) {
+ cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
+ if ((*this)[mid]<(*this)[max]) {
+ cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
+ if ((*this)[min]<(*this)[mid]) {
+ cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
+ }
+ if (max-min>=3) {
+ const T pivot = (*this)[mid];
+ int i = min, j = max;
+ if (increasing) {
+ do {
+ while ((*this)[i]<pivot) ++i;
+ while ((*this)[j]>pivot) --j;
+ if (i<=j) {
+ cimg::swap((*this)[i],(*this)[j]);
+ cimg::swap(permutations[i++],permutations[j--]);
+ }
+ } while (i<=j);
+ } else {
+ do {
+ while ((*this)[i]>pivot) ++i;
+ while ((*this)[j]<pivot) --j;
+ if (i<=j) {
+ cimg::swap((*this)[i],(*this)[j]);
+ cimg::swap(permutations[i++],permutations[j--]);
+ }
+ } while (i<=j);
+ }
+ if (min<j) _quicksort(min,j,permutations,increasing);
+ if (i<max) _quicksort(i,max,permutations,increasing);
+ }
+ }
+ return *this;
+ }
+
+ //! Get a permutation of the pixels.
+ template<typename t>
+ CImg<T>& permute(const CImg<t>& permutation) {
+ return get_permute(permutation).transfer_to(*this);
+ }
+
+ template<typename t>
+ CImg<T> get_permute(const CImg<t>& permutation) const {
+ if (permutation.size()!=size())
+ throw CImgArgumentException("CImg<%s>::permute() : Instance image (%u,%u,%u,%u,%p) and permutation (%u,%u,%u,%u,%p)"
+ "have different sizes.",
+ pixel_type(),width,height,depth,dim,data,
+ permutation.width,permutation.height,permutation.depth,permutation.dim,permutation.data);
+ CImg<T> res(width,height,depth,dim);
+ const t *p = permutation.ptr(permutation.size());
+ cimg_for(res,ptr,T) *ptr = (*this)[*(--p)];
+ return res;
+ }
+
+ //! Compute the SVD of a general matrix.
+ template<typename t>
+ const CImg<T>& SVD(CImg<t>& U, CImg<t>& S, CImg<t>& V,
+ const bool sorting=true, const unsigned int max_iter=40, const float lambda=0) const {
+ if (is_empty()) { U.assign(); S.assign(); V.assign(); }
+ else {
+ U = *this;
+ if (lambda!=0) {
+ const unsigned int delta = cimg::min(U.width,U.height);
+ for (unsigned int i=0; i<delta; ++i) U(i,i) = (t)(U(i,i) + lambda);
+ }
+ if (S.size()<width) S.assign(1,width);
+ if (V.width<width || V.height<height) V.assign(width,width);
+ CImg<t> rv1(width);
+ t anorm = 0, c, f, g = 0, h, s, scale = 0;
+ int l = 0, nm = 0;
+
+ cimg_forX(U,i) {
+ l = i+1; rv1[i] = scale*g; g = s = scale = 0;
+ if (i<dimy()) {
+ for (int k=i; k<dimy(); ++k) scale+= cimg::abs(U(i,k));
+ if (scale) {
+ for (int k=i; k<dimy(); ++k) { U(i,k)/=scale; s+= U(i,k)*U(i,k); }
+ f = U(i,i); g = (t)((f>=0?-1:1)*cimg_std::sqrt(s)); h=f*g-s; U(i,i) = f-g;
+ for (int j=l; j<dimx(); ++j) {
+ s = 0; for (int k=i; k<dimy(); ++k) s+= U(i,k)*U(j,k);
+ f = s/h;
+ { for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
+ }
+ { for (int k=i; k<dimy(); ++k) U(i,k)*= scale; }
+ }
+ }
+ S[i]=scale*g;
+
+ g = s = scale = 0;
+ if (i<dimy() && i!=dimx()-1) {
+ for (int k=l; k<dimx(); ++k) scale += cimg::abs(U(k,i));
+ if (scale) {
+ for (int k=l; k<dimx(); ++k) { U(k,i)/= scale; s+= U(k,i)*U(k,i); }
+ f = U(l,i); g = (t)((f>=0?-1:1)*cimg_std::sqrt(s)); h = f*g-s; U(l,i) = f-g;
+ { for (int k=l; k<dimx(); ++k) rv1[k]=U(k,i)/h; }
+ for (int j=l; j<dimy(); ++j) {
+ s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,j)*U(k,i);
+ { for (int k=l; k<dimx(); ++k) U(k,j)+= s*rv1[k]; }
+ }
+ { for (int k=l; k<dimx(); ++k) U(k,i)*= scale; }
+ }
+ }
+ anorm = (t)cimg::max((float)anorm,(float)(cimg::abs(S[i])+cimg::abs(rv1[i])));
+ }
+
+ { for (int i=dimx()-1; i>=0; --i) {
+ if (i<dimx()-1) {
+ if (g) {
+ { for (int j=l; j<dimx(); ++j) V(i,j) =(U(j,i)/U(l,i))/g; }
+ for (int j=l; j<dimx(); ++j) {
+ s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,i)*V(j,k);
+ { for (int k=l; k<dimx(); ++k) V(j,k)+= s*V(i,k); }
+ }
+ }
+ for (int j=l; j<dimx(); ++j) V(j,i) = V(i,j) = (t)0.0;
+ }
+ V(i,i) = (t)1.0; g = rv1[i]; l = i;
+ }
+ }
+
+ { for (int i=cimg::min(dimx(),dimy())-1; i>=0; --i) {
+ l = i+1; g = S[i];
+ for (int j=l; j<dimx(); ++j) U(j,i) = 0;
+ if (g) {
+ g = 1/g;
+ for (int j=l; j<dimx(); ++j) {
+ s = 0; for (int k=l; k<dimy(); ++k) s+= U(i,k)*U(j,k);
+ f = (s/U(i,i))*g;
+ { for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
+ }
+ { for (int j=i; j<dimy(); ++j) U(i,j)*= g; }
+ } else for (int j=i; j<dimy(); ++j) U(i,j) = 0;
+ ++U(i,i);
+ }
+ }
+
+ for (int k=dimx()-1; k>=0; --k) {
+ for (unsigned int its=0; its<max_iter; ++its) {
+ bool flag = true;
+ for (l=k; l>=1; --l) {
+ nm = l-1;
+ if ((cimg::abs(rv1[l])+anorm)==anorm) { flag = false; break; }
+ if ((cimg::abs(S[nm])+anorm)==anorm) break;
+ }
+ if (flag) {
+ c = 0; s = 1;
+ for (int i=l; i<=k; ++i) {
+ f = s*rv1[i]; rv1[i] = c*rv1[i];
+ if ((cimg::abs(f)+anorm)==anorm) break;
+ g = S[i]; h = (t)cimg::_pythagore(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h;
+ cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c+z*s; U(i,j) = z*c-y*s; }
+ }
+ }
+ const t z = S[k];
+ if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; }
+ nm = k-1;
+ t x = S[l], y = S[nm];
+ g = rv1[nm]; h = rv1[k];
+ f = ((y-z)*(y+z)+(g-h)*(g+h))/(2*h*y);
+ g = (t)cimg::_pythagore(f,1.0);
+ f = ((x-z)*(x+z)+h*((y/(f+ (f>=0?g:-g)))-h))/x;
+ c = s = 1;
+ for (int j=l; j<=nm; ++j) {
+ const int i = j+1;
+ g = rv1[i]; h = s*g; g = c*g;
+ t y = S[i];
+ t z = (t)cimg::_pythagore(f,h);
+ rv1[j] = z; c = f/z; s = h/z;
+ f = x*c+g*s; g = g*c-x*s; h = y*s; y*=c;
+ cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c+z*s; V(i,jj) = z*c-x*s; }
+ z = (t)cimg::_pythagore(f,h); S[j] = z;
+ if (z) { z = 1/z; c = f*z; s = h*z; }
+ f = c*g+s*y; x = c*y-s*g;
+ { cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c+z*s; U(i,jj) = z*c-y*s; }}
+ }
+ rv1[l] = 0; rv1[k]=f; S[k]=x;
+ }
+ }
+
+ if (sorting) {
+ CImg<intT> permutations(width);
+ CImg<t> tmp(width);
+ S.sort(permutations,false);
+ cimg_forY(U,k) {
+ cimg_forX(permutations,x) tmp(x) = U(permutations(x),k);
+ cimg_std::memcpy(U.ptr(0,k),tmp.data,sizeof(t)*width);
+ }
+ { cimg_forY(V,k) {
+ cimg_forX(permutations,x) tmp(x) = V(permutations(x),k);
+ cimg_std::memcpy(V.ptr(0,k),tmp.data,sizeof(t)*width);
+ }}
+ }
+ }
+ return *this;
+ }
+
+ //! Compute the SVD of a general matrix.
+ template<typename t>
+ const CImg<T>& SVD(CImgList<t>& USV) const {
+ if (USV.size<3) USV.assign(3);
+ return SVD(USV[0],USV[1],USV[2]);
+ }
+
+ //! Compute the SVD of a general matrix.
+ CImgList<Tfloat> get_SVD(const bool sorting=true) const {
+ CImgList<Tfloat> res(3);
+ SVD(res[0],res[1],res[2],sorting);
+ return res;
+ }
+
+ // INNER ROUTINE : Compute the LU decomposition of a permuted matrix (c.f. numerical recipies)
+ template<typename t>
+ CImg<T>& _LU(CImg<t>& indx, bool& d) {
+ const int N = dimx();
+ int imax = 0;
+ CImg<Tfloat> vv(N);
+ indx.assign(N);
+ d = true;
+ cimg_forX(*this,i) {
+ Tfloat vmax = 0;
+ cimg_forX(*this,j) {
+ const Tfloat tmp = cimg::abs((*this)(j,i));
+ if (tmp>vmax) vmax = tmp;
+ }
+ if (vmax==0) { indx.fill(0); return fill(0); }
+ vv[i] = 1/vmax;
+ }
+ cimg_forX(*this,j) {
+ for (int i=0; i<j; ++i) {
+ Tfloat sum=(*this)(j,i);
+ for (int k=0; k<i; ++k) sum-=(*this)(k,i)*(*this)(j,k);
+ (*this)(j,i) = (T)sum;
+ }
+ Tfloat vmax = 0;
+ { for (int i=j; i<dimx(); ++i) {
+ Tfloat sum=(*this)(j,i);
+ for (int k=0; k<j; ++k) sum-=(*this)(k,i)*(*this)(j,k);
+ (*this)(j,i) = (T)sum;
+ const Tfloat tmp = vv[i]*cimg::abs(sum);
+ if (tmp>=vmax) { vmax=tmp; imax=i; }
+ }}
+ if (j!=imax) {
+ cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j));
+ d =!d;
+ vv[imax] = vv[j];
+ }
+ indx[j] = (t)imax;
+ if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20;
+ if (j<N) {
+ const Tfloat tmp = 1/(Tfloat)(*this)(j,j);
+ for (int i=j+1; i<N; ++i) (*this)(j,i) = (T)((*this)(j,i)*tmp);
+ }
+ }
+ return *this;
+ }
+
+ //! Compute the eigenvalues and eigenvectors of a matrix.
+ template<typename t>
+ const CImg<T>& eigen(CImg<t>& val, CImg<t> &vec) const {
+ if (is_empty()) { val.assign(); vec.assign(); }
+ else {
+ if (width!=height || depth>1 || dim>1)
+ throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ if (val.size()<width) val.assign(1,width);
+ if (vec.size()<width*width) vec.assign(width,width);
+ switch (width) {
+ case 1 : { val[0]=(t)(*this)[0]; vec[0]=(t)1; } break;
+ case 2 : {
+ const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a+d;
+ double f = e*e-4*(a*d-b*c);
+ if (f<0)
+ cimg::warn("CImg<%s>::eigen() : Complex eigenvalues",
+ pixel_type());
+ f = cimg_std::sqrt(f);
+ const double l1 = 0.5*(e-f), l2 = 0.5*(e+f);
+ const double theta1 = cimg_std::atan2(l2-a,b), theta2 = cimg_std::atan2(l1-a,b);
+ val[0]=(t)l2;
+ val[1]=(t)l1;
+ vec(0,0) = (t)cimg_std::cos(theta1);
+ vec(0,1) = (t)cimg_std::sin(theta1);
+ vec(1,0) = (t)cimg_std::cos(theta2);
+ vec(1,1) = (t)cimg_std::sin(theta2);
+ } break;
+ default :
+ throw CImgInstanceException("CImg<%s>::eigen() : Eigenvalues computation of general matrices is limited"
+ "to 2x2 matrices (given is %ux%u)",
+ pixel_type(),width,height);
+ }
+ }
+ return *this;
+ }
+
+ //! Compute the eigenvalues and eigenvectors of a matrix.
+ CImgList<Tfloat> get_eigen() const {
+ CImgList<Tfloat> res(2);
+ eigen(res[0],res[1]);
+ return res;
+ }
+
+ //! Compute the eigenvalues and eigenvectors of a symmetric matrix.
+ template<typename t>
+ const CImg<T>& symmetric_eigen(CImg<t>& val, CImg<t>& vec) const {
+ if (is_empty()) { val.assign(); vec.assign(); }
+ else {
+#ifdef cimg_use_lapack
+ char JOB = 'V', UPLO = 'U';
+ int N = width, LWORK = 4*N, INFO;
+ Tfloat
+ *lapA = new Tfloat[N*N],
+ *lapW = new Tfloat[N],
+ *WORK = new Tfloat[LWORK];
+ cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
+ cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO);
+ if (INFO)
+ cimg::warn("CImg<%s>::symmetric_eigen() : LAPACK library function dsyev_() returned error code %d.",
+ pixel_type(),INFO);
+ val.assign(1,N);
+ vec.assign(N,N);
+ if (!INFO) {
+ cimg_forY(val,i) val(i) = (T)lapW[N-1-i];
+ cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N-1-k)*N+l]);
+ } else { val.fill(0); vec.fill(0); }
+ delete[] lapA; delete[] lapW; delete[] WORK;
+#else
+ if (width!=height || depth>1 || dim>1)
+ throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ val.assign(1,width);
+ if (vec.data) vec.assign(width,width);
+ if (width<3) return eigen(val,vec);
+ CImg<t> V(width,width);
+ SVD(vec,val,V,false);
+ bool ambiguous = false;
+ float eig = 0;
+ cimg_forY(val,p) { // check for ambiguous cases.
+ if (val[p]>eig) eig = (float)val[p];
+ t scal = 0;
+ cimg_forY(vec,y) scal+=vec(p,y)*V(p,y);
+ if (cimg::abs(scal)<0.9f) ambiguous = true;
+ if (scal<0) val[p] = -val[p];
+ }
+ if (ambiguous) {
+ (eig*=2)++;
+ SVD(vec,val,V,false,40,eig);
+ val-=eig;
+ }
+ CImg<intT> permutations(width); // sort eigenvalues in decreasing order
+ CImg<t> tmp(width);
+ val.sort(permutations,false);
+ cimg_forY(vec,k) {
+ cimg_forX(permutations,x) tmp(x) = vec(permutations(x),k);
+ cimg_std::memcpy(vec.ptr(0,k),tmp.data,sizeof(t)*width);
+ }
+#endif
+ }
+ return *this;
+ }
+
+ //! Compute the eigenvalues and eigenvectors of a symmetric matrix.
+ CImgList<Tfloat> get_symmetric_eigen() const {
+ CImgList<Tfloat> res(2);
+ symmetric_eigen(res[0],res[1]);
+ return res;
+ }
+
+ //@}
+ //-------------------
+ //
+ //! \name Display
+ //@{
+ //-------------------
+
+ //! Display an image into a CImgDisplay window.
+ const CImg<T>& display(CImgDisplay& disp) const {
+ disp.display(*this);
+ return *this;
+ }
+
+ //! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n
+ const CImg<T>& display(CImgDisplay &disp, const bool display_info) const {
+ return _display(disp,0,display_info);
+ }
+
+ //! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n
+ const CImg<T>& display(const char *const title=0, const bool display_info=true) const {
+ CImgDisplay disp;
+ return _display(disp,title,display_info);
+ }
+
+ const CImg<T>& _display(CImgDisplay &disp, const char *const title, const bool display_info) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::display() : Instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ unsigned int oldw = 0, oldh = 0, XYZ[3], key = 0, mkey = 0;
+ int x0 = 0, y0 = 0, z0 = 0, x1 = dimx()-1, y1 = dimy()-1, z1 = dimz()-1;
+ float frametiming = 5;
+
+ char ntitle[256] = { 0 };
+ if (!disp) {
+ if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
+ disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
+ }
+ cimg_std::strncpy(ntitle,disp.title,255);
+ if (display_info) print(ntitle);
+
+ CImg<T> zoom;
+ for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) {
+ if (reset_view) {
+ XYZ[0] = (x0 + x1)/2; XYZ[1] = (y0 + y1)/2; XYZ[2] = (z0 + z1)/2;
+ x0 = 0; y0 = 0; z0 = 0; x1 = width-1; y1 = height-1; z1 = depth-1;
+ oldw = disp.width; oldh = disp.height;
+ reset_view = false;
+ }
+ if (!x0 && !y0 && !z0 && x1==dimx()-1 && y1==dimy()-1 && z1==dimz()-1) zoom.assign();
+ else zoom = get_crop(x0,y0,z0,x1,y1,z1);
+
+ const unsigned int
+ dx = 1 + x1 - x0, dy = 1 + y1 - y0, dz = 1 + z1 - z0,
+ tw = dx + (dz>1?dz:0), th = dy + (dz>1?dz:0);
+ if (resize_disp) {
+ const unsigned int
+ ttw = tw*disp.width/oldw, tth = th*disp.height/oldh,
+ dM = cimg::max(ttw,tth), diM = cimg::max(disp.width,disp.height),
+ imgw = cimg::max(16U,ttw*diM/dM), imgh = cimg::max(16U,tth*diM/dM);
+ disp.normalscreen().resize(cimg_fitscreen(imgw,imgh,1),false);
+ resize_disp = false;
+ }
+ oldw = tw; oldh = th;
+
+ bool
+ go_up = false, go_down = false, go_left = false, go_right = false,
+ go_inc = false, go_dec = false, go_in = false, go_out = false,
+ go_in_center = false;
+ const CImg<T>& visu = zoom?zoom:*this;
+ const CImg<intT> selection = visu._get_select(disp,0,2,XYZ,0,x0,y0,z0);
+ if (disp.wheel) {
+ if (disp.is_keyCTRLLEFT) { if (!mkey || mkey==1) go_out = !(go_in = disp.wheel>0); go_in_center = false; mkey = 1; }
+ else if (disp.is_keySHIFTLEFT) { if (!mkey || mkey==2) go_right = !(go_left = disp.wheel>0); mkey = 2; }
+ else if (disp.is_keyALT || depth==1) { if (!mkey || mkey==3) go_down = !(go_up = disp.wheel>0); mkey = 3; }
+ else mkey = 0;
+ disp.wheel = 0;
+ } else mkey = 0;
+ const int
+ sx0 = selection(0), sy0 = selection(1), sz0 = selection(2),
+ sx1 = selection(3), sy1 = selection(4), sz1 = selection(5);
+ if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) {
+ x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; x0+=sx0; y0+=sy0; z0+=sz0;
+ if (sx0==sx1 && sy0==sy1 && sz0==sz1) reset_view = true;
+ resize_disp = true;
+ } else switch (key = disp.key) {
+ case 0 : case cimg::keyCTRLLEFT : case cimg::keyPAD5 : case cimg::keySHIFTLEFT : case cimg::keyALT : disp.key = key = 0; break;
+ case cimg::keyP : if (visu.depth>1 && disp.is_keyCTRLLEFT) { // Special mode : play stack of frames
+ const unsigned int
+ w1 = visu.width*disp.width/(visu.width+(visu.depth>1?visu.depth:0)),
+ h1 = visu.height*disp.height/(visu.height+(visu.depth>1?visu.depth:0));
+ disp.resize(cimg_fitscreen(w1,h1,1),false).key = disp.wheel = key = 0;
+ for (unsigned int timer = 0; !key && !disp.is_closed && !disp.button; ) {
+ if (disp.is_resized) disp.resize();
+ if (!timer) {
+ visu.get_slice(XYZ[2]).display(disp.set_title("%s | z=%d",ntitle,XYZ[2]));
+ if (++XYZ[2]>=visu.depth) XYZ[2] = 0;
+ }
+ if (++timer>(unsigned int)frametiming) timer = 0;
+ if (disp.wheel) { frametiming-=disp.wheel/3.0f; disp.wheel = 0; }
+ switch (key = disp.key) {
+ case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
+ case cimg::keyPAGEUP : frametiming-=0.3f; key = 0; break;
+ case cimg::keyPAGEDOWN : frametiming+=0.3f; key = 0; break;
+ case cimg::keyD : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
+ CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false);
+ disp.key = key = 0;
+ } break;
+ case cimg::keyC : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false);
+ disp.key = key = 0;
+ } break;
+ case cimg::keyR : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false);
+ disp.key = key = 0;
+ } break;
+ case cimg::keyF : if (disp.is_keyCTRLLEFT) {
+ disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen();
+ disp.key = key = 0;
+ } break;
+ }
+ frametiming = frametiming<1?1:(frametiming>39?39:frametiming);
+ disp.wait(20);
+ }
+ const unsigned int
+ w2 = (visu.width + (visu.depth>1?visu.depth:0))*disp.width/visu.width,
+ h2 = (visu.height + (visu.depth>1?visu.depth:0))*disp.height/visu.height;
+ disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(ntitle);
+ key = disp.key = disp.button = disp.wheel = 0;
+ } break;
+ case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break;
+ case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break;
+ case cimg::keyPADSUB : go_out = true; key = 0; break;
+ case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break;
+ case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break;
+ case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break;
+ case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break;
+ case cimg::keyPAD7 : go_up = go_left = true; key = 0; break;
+ case cimg::keyPAD9 : go_up = go_right = true; key = 0; break;
+ case cimg::keyPAD1 : go_down = go_left = true; key = 0; break;
+ case cimg::keyPAD3 : go_down = go_right = true; key = 0; break;
+ case cimg::keyPAGEUP : go_inc = true; key = 0; break;
+ case cimg::keyPAGEDOWN : go_dec = true; key = 0; break;
+ }
+ if (go_in) {
+ const int
+ mx = go_in_center?disp.dimx()/2:disp.mouse_x,
+ my = go_in_center?disp.dimy()/2:disp.mouse_y,
+ mX = mx*(width+(depth>1?depth:0))/disp.width,
+ mY = my*(height+(depth>1?depth:0))/disp.height;
+ int X = XYZ[0], Y = XYZ[1], Z = XYZ[2];
+ if (mX<dimx() && mY<dimy()) { X = x0 + mX*(1+x1-x0)/width; Y = y0 + mY*(1+y1-y0)/height; Z = XYZ[2]; }
+ if (mX<dimx() && mY>=dimy()) { X = x0 + mX*(1+x1-x0)/width; Z = z0 + (mY-height)*(1+z1-z0)/depth; Y = XYZ[1]; }
+ if (mX>=dimx() && mY<dimy()) { Y = y0 + mY*(1+y1-y0)/height; Z = z0 + (mX-width)*(1+z1-z0)/depth; X = XYZ[0]; }
+ if (x1-x0>4) { x0 = X - 7*(X-x0)/8; x1 = X + 7*(x1-X)/8; }
+ if (y1-y0>4) { y0 = Y - 7*(Y-y0)/8; y1 = Y + 7*(y1-Y)/8; }
+ if (z1-z0>4) { z0 = Z - 7*(Z-z0)/8; z1 = Z + 7*(z1-Z)/8; }
+ }
+ if (go_out) {
+ const int
+ deltax = (x1-x0)/8, deltay = (y1-y0)/8, deltaz = (z1-z0)/8,
+ ndeltax = deltax?deltax:(width>1?1:0),
+ ndeltay = deltay?deltay:(height>1?1:0),
+ ndeltaz = deltaz?deltaz:(depth>1?1:0);
+ x0-=ndeltax; y0-=ndeltay; z0-=ndeltaz;
+ x1+=ndeltax; y1+=ndeltay; z1+=ndeltaz;
+ if (x0<0) { x1-=x0; x0 = 0; if (x1>=dimx()) x1 = dimx()-1; }
+ if (y0<0) { y1-=y0; y0 = 0; if (y1>=dimy()) y1 = dimy()-1; }
+ if (z0<0) { z1-=z0; z0 = 0; if (z1>=dimz()) z1 = dimz()-1; }
+ if (x1>=dimx()) { x0-=(x1-dimx()+1); x1 = dimx()-1; if (x0<0) x0 = 0; }
+ if (y1>=dimy()) { y0-=(y1-dimy()+1); y1 = dimy()-1; if (y0<0) y0 = 0; }
+ if (z1>=dimz()) { z0-=(z1-dimz()+1); z1 = dimz()-1; if (z0<0) z0 = 0; }
+ }
+ if (go_left) {
+ const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
+ if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
+ else { x1-=x0; x0 = 0; }
+ }
+ if (go_right) {
+ const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
+ if (x1+ndelta<dimx()) { x0+=ndelta; x1+=ndelta; }
+ else { x0+=(dimx()-1-x1); x1 = dimx()-1; }
+ }
+ if (go_up) {
+ const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
+ if (y0-ndelta>=0) { y0-=ndelta; y1-=ndelta; }
+ else { y1-=y0; y0 = 0; }
+ }
+ if (go_down) {
+ const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
+ if (y1+ndelta<dimy()) { y0+=ndelta; y1+=ndelta; }
+ else { y0+=(dimy()-1-y1); y1 = dimy()-1; }
+ }
+ if (go_inc) {
+ const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0);
+ if (z0-ndelta>=0) { z0-=ndelta; z1-=ndelta; }
+ else { z1-=z0; z0 = 0; }
+ }
+ if (go_dec) {
+ const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0);
+ if (z1+ndelta<dimz()) { z0+=ndelta; z1+=ndelta; }
+ else { z0+=(depth-1-z1); z1 = depth-1; }
+ }
+ }
+ disp.key = key;
+ return *this;
+ }
+
+ //! Simple interface to select a shape from an image.
+ /**
+ \param selection Array of 6 values containing the selection result
+ \param coords_type Determine shape type to select (0=point, 1=vector, 2=rectangle, 3=circle)
+ \param disp Display window used to make the selection
+ \param XYZ Initial XYZ position (for volumetric images only)
+ \param color Color of the shape selector.
+ **/
+ CImg<T>& select(CImgDisplay &disp,
+ const int select_type=2, unsigned int *const XYZ=0,
+ const unsigned char *const color=0) {
+ return get_select(disp,select_type,XYZ,color).transfer_to(*this);
+ }
+
+ //! Simple interface to select a shape from an image.
+ CImg<T>& select(const char *const title,
+ const int select_type=2, unsigned int *const XYZ=0,
+ const unsigned char *const color=0) {
+ return get_select(title,select_type,XYZ,color).transfer_to(*this);
+ }
+
+ //! Simple interface to select a shape from an image.
+ CImg<intT> get_select(CImgDisplay &disp,
+ const int select_type=2, unsigned int *const XYZ=0,
+ const unsigned char *const color=0) const {
+ return _get_select(disp,0,select_type,XYZ,color,0,0,0);
+ }
+
+ //! Simple interface to select a shape from an image.
+ CImg<intT> get_select(const char *const title,
+ const int select_type=2, unsigned int *const XYZ=0,
+ const unsigned char *const color=0) const {
+ CImgDisplay disp;
+ return _get_select(disp,title,select_type,XYZ,color,0,0,0);
+ }
+
+ CImg<intT> _get_select(CImgDisplay &disp, const char *const title,
+ const int coords_type, unsigned int *const XYZ,
+ const unsigned char *const color,
+ const int origX, const int origY, const int origZ) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::select() : Instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ if (!disp) {
+ char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); }
+ disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
+ }
+
+ const unsigned int
+ old_normalization = disp.normalization,
+ hatch = 0x55555555;
+
+ bool old_is_resized = disp.is_resized;
+ disp.normalization = 0;
+ disp.show().key = 0;
+
+ unsigned char foreground_color[] = { 255,255,105 }, background_color[] = { 0,0,0 };
+ if (color) cimg_std::memcpy(foreground_color,color,sizeof(unsigned char)*cimg::min(3,dimv()));
+
+ int area = 0, clicked_area = 0, phase = 0,
+ X0 = (int)((XYZ?XYZ[0]:width/2)%width), Y0 = (int)((XYZ?XYZ[1]:height/2)%height), Z0 = (int)((XYZ?XYZ[2]:depth/2)%depth),
+ X1 =-1, Y1 = -1, Z1 = -1,
+ X = -1, Y = -1, Z = -1,
+ oX = X, oY = Y, oZ = Z;
+ unsigned int old_button = 0, key = 0;
+
+ bool shape_selected = false, text_down = false;
+ CImg<ucharT> visu, visu0;
+ char text[1024] = { 0 };
+
+ while (!key && !disp.is_closed && !shape_selected) {
+
+ // Handle mouse motion and selection
+ oX = X; oY = Y; oZ = Z;
+ int mx = disp.mouse_x, my = disp.mouse_y;
+ const int mX = mx*(width+(depth>1?depth:0))/disp.width, mY = my*(height+(depth>1?depth:0))/disp.height;
+
+ area = 0;
+ if (mX<dimx() && mY<dimy()) { area = 1; X = mX; Y = mY; Z = phase?Z1:Z0; }
+ if (mX<dimx() && mY>=dimy()) { area = 2; X = mX; Z = mY-height; Y = phase?Y1:Y0; }
+ if (mX>=dimx() && mY<dimy()) { area = 3; Y = mY; Z = mX-width; X = phase?X1:X0; }
+
+ switch (key = disp.key) {
+ case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
+ case cimg::keyPAGEUP : if (disp.is_keyCTRLLEFT) { ++disp.wheel; key = 0; } break;
+ case cimg::keyPAGEDOWN : if (disp.is_keyCTRLLEFT) { --disp.wheel; key = 0; } break;
+ case cimg::keyD : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
+ CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
+ disp.key = key = 0;
+ } break;
+ case cimg::keyC : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
+ disp.key = key = 0; visu0.assign();
+ } break;
+ case cimg::keyR : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false).is_resized = true;
+ disp.key = key = 0; visu0.assign();
+ } break;
+ case cimg::keyF : if (disp.is_keyCTRLLEFT) {
+ disp.resize(disp.screen_dimx(),disp.screen_dimy(),false).toggle_fullscreen().is_resized = true;
+ disp.key = key = 0; visu0.assign();
+ } break;
+ case cimg::keyS : if (disp.is_keyCTRLLEFT) {
+ static unsigned int snap_number = 0;
+ char filename[32] = { 0 };
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
+ if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
+ } while (file);
+ if (visu0) {
+ visu.draw_text(2,2,"Saving snapshot...",foreground_color,background_color,0.8f,11).display(disp);
+ visu0.save(filename);
+ visu.draw_text(2,2,"Snapshot '%s' saved.",foreground_color,background_color,0.8f,11,filename).display(disp);
+ }
+ disp.key = key = 0;
+ } break;
+ case cimg::keyO : if (disp.is_keyCTRLLEFT) {
+ static unsigned int snap_number = 0;
+ char filename[32] = { 0 };
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filename,"CImg_%.4u.cimg",snap_number++);
+ if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
+ } while (file);
+ visu.draw_text(2,2,"Saving instance...",foreground_color,background_color,0.8f,11).display(disp);
+ save(filename);
+ visu.draw_text(2,2,"Instance '%s' saved.",foreground_color,background_color,0.8f,11,filename).display(disp);
+ disp.key = key = 0;
+ } break;
+ }
+
+ if (!area) mx = my = X = Y = Z = -1;
+ else {
+ if (disp.button&1 && phase<2) { X1 = X; Y1 = Y; Z1 = Z; }
+ if (!(disp.button&1) && phase>=2) {
+ switch (clicked_area) {
+ case 1 : Z1 = Z; break;
+ case 2 : Y1 = Y; break;
+ case 3 : X1 = X; break;
+ }
+ }
+ if (disp.button&2) { if (phase) { X1 = X; Y1 = Y; Z1 = Z; } else { X0 = X; Y0 = Y; Z0 = Z; } }
+ if (disp.button&4) { oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = 0; visu.assign(); }
+ if (disp.wheel) {
+ if (depth>1 && !disp.is_keyCTRLLEFT && !disp.is_keySHIFTLEFT && !disp.is_keyALT) {
+ switch (area) {
+ case 1 : if (phase) Z = (Z1+=disp.wheel); else Z = (Z0+=disp.wheel); break;
+ case 2 : if (phase) Y = (Y1+=disp.wheel); else Y = (Y0+=disp.wheel); break;
+ case 3 : if (phase) X = (X1+=disp.wheel); else X = (X0+=disp.wheel); break;
+ }
+ disp.wheel = 0;
+ } else key = ~0U;
+ }
+ if ((disp.button&1)!=old_button) {
+ switch (phase++) {
+ case 0 : X0 = X1 = X; Y0 = Y1 = Y; Z0 = Z1 = Z; clicked_area = area; break;
+ case 1 : X1 = X; Y1 = Y; Z1 = Z; break;
+ }
+ old_button = disp.button&1;
+ }
+ if (depth>1 && (X!=oX || Y!=oY || Z!=oZ)) visu0.assign();
+ }
+
+ if (phase) {
+ if (!coords_type) shape_selected = phase?true:false;
+ else {
+ if (depth>1) shape_selected = (phase==3)?true:false;
+ else shape_selected = (phase==2)?true:false;
+ }
+ }
+
+ if (X0<0) X0 = 0; if (X0>=dimx()) X0 = dimx()-1; if (Y0<0) Y0 = 0; if (Y0>=dimy()) Y0 = dimy()-1;
+ if (Z0<0) Z0 = 0; if (Z0>=dimz()) Z0 = dimz()-1;
+ if (X1<1) X1 = 0; if (X1>=dimx()) X1 = dimx()-1; if (Y1<0) Y1 = 0; if (Y1>=dimy()) Y1 = dimy()-1;
+ if (Z1<0) Z1 = 0; if (Z1>=dimz()) Z1 = dimz()-1;
+
+ // Draw visualization image on the display
+ if (oX!=X || oY!=Y || oZ!=Z || !visu0) {
+ if (!visu0) {
+ CImg<Tuchar> tmp, tmp0;
+ if (depth!=1) {
+ tmp0 = (!phase)?get_projections2d(X0,Y0,Z0):get_projections2d(X1,Y1,Z1);
+ tmp = tmp0.get_channels(0,cimg::min(2U,dim-1));
+ } else tmp = get_channels(0,cimg::min(2U,dim-1));
+ switch (old_normalization) {
+ case 0 : visu0 = tmp; break;
+ case 3 :
+ if (cimg::type<T>::is_float()) visu0 = tmp.normalize(0,(T)255);
+ else {
+ const float m = (float)cimg::type<T>::min(), M = (float)cimg::type<T>::max();
+ visu0.assign(tmp.width,tmp.height,1,tmp.dim);
+ unsigned char *ptrd = visu0.end();
+ cimg_for(tmp,ptrs,Tuchar) *(--ptrd) = (unsigned char)((*ptrs-m)*255.0f/(M-m));
+ } break;
+ default : visu0 = tmp.normalize(0,255);
+ }
+ visu0.resize(disp);
+ }
+ visu = visu0;
+ if (!color) {
+ if (visu.mean()<200) {
+ foreground_color[0] = foreground_color[1] = foreground_color[2] = 255;
+ background_color[0] = background_color[1] = background_color[2] = 0;
+ } else {
+ foreground_color[0] = foreground_color[1] = foreground_color[2] = 0;
+ background_color[0] = background_color[1] = background_color[2] = 255;
+ }
+ }
+
+ const int d = (depth>1)?depth:0;
+ if (phase) switch (coords_type) {
+ case 1 : {
+ const int
+ x0 = (int)((X0+0.5f)*disp.width/(width+d)),
+ y0 = (int)((Y0+0.5f)*disp.height/(height+d)),
+ x1 = (int)((X1+0.5f)*disp.width/(width+d)),
+ y1 = (int)((Y1+0.5f)*disp.height/(height+d));
+ visu.draw_arrow(x0,y0,x1,y1,foreground_color,0.6f,30,5,hatch);
+ if (d) {
+ const int
+ zx0 = (int)((width+Z0+0.5f)*disp.width/(width+d)),
+ zx1 = (int)((width+Z1+0.5f)*disp.width/(width+d)),
+ zy0 = (int)((height+Z0+0.5f)*disp.height/(height+d)),
+ zy1 = (int)((height+Z1+0.5f)*disp.height/(height+d));
+ visu.draw_arrow(zx0,y0,zx1,y1,foreground_color,0.6f,30,5,hatch).
+ draw_arrow(x0,zy0,x1,zy1,foreground_color,0.6f,30,5,hatch);
+ }
+ } break;
+ case 2 : {
+ const int
+ x0 = (X0<X1?X0:X1)*disp.width/(width+d), y0 = (Y0<Y1?Y0:Y1)*disp.height/(height+d),
+ x1 = ((X0<X1?X1:X0)+1)*disp.width/(width+d)-1, y1 = ((Y0<Y1?Y1:Y0)+1)*disp.height/(height+d)-1;
+ visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.2f).draw_rectangle(x0,y0,x1,y1,foreground_color,0.6f,hatch);
+ if (d) {
+ const int
+ zx0 = (int)((width+(Z0<Z1?Z0:Z1))*disp.width/(width+d)),
+ zy0 = (int)((height+(Z0<Z1?Z0:Z1))*disp.height/(height+d)),
+ zx1 = (int)((width+(Z0<Z1?Z1:Z0)+1)*disp.width/(width+d))-1,
+ zy1 = (int)((height+(Z0<Z1?Z1:Z0)+1)*disp.height/(height+d))-1;
+ visu.draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.2f).draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.6f,hatch);
+ visu.draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.2f).draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.6f,hatch);
+ }
+ } break;
+ case 3 : {
+ const int
+ x0 = X0*disp.width/(width+d),
+ y0 = Y0*disp.height/(height+d),
+ x1 = X1*disp.width/(width+d)-1,
+ y1 = Y1*disp.height/(height+d)-1;
+ visu.draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1,0,foreground_color,0.2f).
+ draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1,0,foreground_color,0.6f,hatch);
+ if (d) {
+ const int
+ zx0 = (int)((width+Z0)*disp.width/(width+d)),
+ zy0 = (int)((height+Z0)*disp.height/(height+d)),
+ zx1 = (int)((width+Z1+1)*disp.width/(width+d))-1,
+ zy1 = (int)((height+Z1+1)*disp.height/(height+d))-1;
+ visu.draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1,0,foreground_color,0.2f).
+ draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1,0,foreground_color,0.6f,hatch).
+ draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1,0,foreground_color,0.2f).
+ draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1,0,foreground_color,0.6f,hatch);
+ }
+ } break;
+ } else {
+ const int
+ x0 = X*disp.width/(width+d),
+ y0 = Y*disp.height/(height+d),
+ x1 = (X+1)*disp.width/(width+d)-1,
+ y1 = (Y+1)*disp.height/(height+d)-1;
+ if (x1-x0>=4 && y1-y0>=4) visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.4f,~0U);
+ }
+
+ if (my<12) text_down = true;
+ if (my>=visu.dimy()-11) text_down = false;
+ if (!coords_type || !phase) {
+ if (X>=0 && Y>=0 && Z>=0 && X<dimx() && Y<dimy() && Z<dimz()) {
+ if (depth>1) cimg_std::sprintf(text,"Point (%d,%d,%d) = [ ",origX+X,origY+Y,origZ+Z);
+ else cimg_std::sprintf(text,"Point (%d,%d) = [ ",origX+X,origY+Y);
+ char *ctext = text + cimg::strlen(text), *const ltext = text + 512;
+ for (unsigned int k=0; k<dim && ctext<ltext; ++k) {
+ cimg_std::sprintf(ctext,cimg::type<T>::format(),cimg::type<T>::format((*this)(X,Y,Z,k)));
+ ctext = text + cimg::strlen(text);
+ *(ctext++) = ' '; *ctext = '\0';
+ }
+ cimg_std::sprintf(text + cimg::strlen(text),"]");
+ }
+ } else switch (coords_type) {
+ case 1 : {
+ const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), norm = cimg_std::sqrt(dX*dX+dY*dY+dZ*dZ);
+ if (depth>1) cimg_std::sprintf(text,"Vect (%d,%d,%d)-(%d,%d,%d), Norm = %g",
+ origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,norm);
+ else cimg_std::sprintf(text,"Vect (%d,%d)-(%d,%d), Norm = %g",
+ origX+X0,origY+Y0,origX+X1,origY+Y1,norm);
+ } break;
+ case 2 :
+ if (depth>1) cimg_std::sprintf(text,"Box (%d,%d,%d)-(%d,%d,%d), Size = (%d,%d,%d)",
+ origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origZ+(Z0<Z1?Z0:Z1),
+ origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),origZ+(Z0<Z1?Z1:Z0),
+ 1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
+ else cimg_std::sprintf(text,"Box (%d,%d)-(%d,%d), Size = (%d,%d)",
+ origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),
+ 1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
+ break;
+ default :
+ if (depth>1) cimg_std::sprintf(text,"Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d)",
+ origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,
+ 1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
+ else cimg_std::sprintf(text,"Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d)",
+ origX+X0,origY+Y0,origX+X1,origY+Y1,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
+
+ }
+ if (phase || (mx>=0 && my>=0)) visu.draw_text(0,text_down?visu.dimy()-11:0,text,foreground_color,background_color,0.7f,11);
+ disp.display(visu).wait(25);
+ } else if (!shape_selected) disp.wait();
+
+ if (disp.is_resized) { disp.resize(false); old_is_resized = true; disp.is_resized = false; visu0.assign(); }
+ }
+
+ // Return result
+ CImg<intT> res(1,6,1,1,-1);
+ if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; }
+ if (shape_selected) {
+ if (coords_type==2) {
+ if (X0>X1) cimg::swap(X0,X1);
+ if (Y0>Y1) cimg::swap(Y0,Y1);
+ if (Z0>Z1) cimg::swap(Z0,Z1);
+ }
+ if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1;
+ switch (coords_type) {
+ case 1 :
+ case 2 : res[3] = X1; res[4] = Y1; res[5] = Z1;
+ default : res[0] = X0; res[1] = Y0; res[2] = Z0;
+ }
+ }
+ disp.button = 0;
+ disp.normalization = old_normalization;
+ disp.is_resized = old_is_resized;
+ if (key!=~0U) disp.key = key;
+ return res;
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf, typename tc, typename to>
+ const CImg<T>& display_object3d(CImgDisplay& disp,
+ const CImg<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const to& opacities,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return _display_object3d(disp,0,points,points.width,primitives,colors,opacities,centering,render_static,
+ render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf, typename tc, typename to>
+ const CImg<T>& display_object3d(const char *const title,
+ const CImg<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const to& opacities,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ CImgDisplay disp;
+ return _display_object3d(disp,title,points,points.width,primitives,colors,opacities,centering,render_static,
+ render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf, typename tc, typename to>
+ const CImg<T>& display_object3d(CImgDisplay& disp,
+ const CImgList<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const to& opacities,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return _display_object3d(disp,0,points,points.size,primitives,colors,opacities,centering,render_static,
+ render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf, typename tc, typename to>
+ const CImg<T>& display_object3d(const char *const title,
+ const CImgList<tp>& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const to& opacities,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ CImgDisplay disp;
+ return _display_object3d(disp,title,points,points.size,primitives,colors,opacities,centering,render_static,
+ render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf, typename tc>
+ const CImg<T>& display_object3d(CImgDisplay &disp,
+ const tp& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return display_object3d(disp,points,primitives,colors,CImg<floatT>(),centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf, typename tc>
+ const CImg<T>& display_object3d(const char *const title,
+ const tp& points, const CImgList<tf>& primitives,
+ const CImgList<tc>& colors,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return display_object3d(title,points,primitives,colors,CImg<floatT>(),centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf>
+ const CImg<T>& display_object3d(CImgDisplay &disp,
+ const tp& points, const CImgList<tf>& primitives,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return display_object3d(disp,points,primitives,CImgList<T>(),centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp, typename tf>
+ const CImg<T>& display_object3d(const char *const title,
+ const tp& points, const CImgList<tf>& primitives,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return display_object3d(title,points,primitives,CImgList<T>(),centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp>
+ const CImg<T>& display_object3d(CImgDisplay &disp,
+ const tp& points,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return display_object3d(disp,points,CImgList<uintT>(),centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ //! High-level interface for displaying a 3d object.
+ template<typename tp>
+ const CImg<T>& display_object3d(const char *const title,
+ const tp& points,
+ const bool centering=true,
+ const int render_static=4, const int render_motion=1,
+ const bool double_sided=false, const float focale=500,
+ const float specular_light=0.2f, const float specular_shine=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0) const {
+ return display_object3d(title,points,CImgList<uintT>(),centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+
+ T _display_object3d_at2(const int i, const int j) const {
+ return atXY(i,j,0,0,0);
+ }
+
+ template<typename tp, typename tf, typename tc, typename to>
+ const CImg<T>& _display_object3d(CImgDisplay& disp, const char *const title,
+ const tp& points, const unsigned int Npoints,
+ const CImgList<tf>& primitives,
+ const CImgList<tc>& colors, const to& opacities,
+ const bool centering,
+ const int render_static, const int render_motion,
+ const bool double_sided, const float focale,
+ const float specular_light, const float specular_shine,
+ const bool display_axes, float *const pose_matrix) const {
+
+ // Check input arguments
+ if (!points || !Npoints)
+ throw CImgArgumentException("CImg<%s>::display_object3d() : Given points are empty.",
+ pixel_type());
+ if (is_empty()) {
+ if (disp) return CImg<T>(disp.width,disp.height,1,colors[0].size(),0).
+ _display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ else return CImg<T>(cimg_fitscreen(640,480,1),1,colors[0].size(),0).
+ _display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering,
+ render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+ if (!primitives) {
+ CImgList<tf> nprimitives(Npoints,1,1,1,1);
+ cimglist_for(nprimitives,l) nprimitives(l,0) = l;
+ return _display_object3d(disp,title,points,Npoints,nprimitives,colors,opacities,
+ centering,render_static,render_motion,double_sided,focale,specular_light,specular_shine,
+ display_axes,pose_matrix);
+ }
+ if (!disp) {
+ char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); }
+ disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
+ }
+
+ CImgList<tc> _colors;
+ if (!colors) _colors.insert(primitives.size,CImg<tc>::vector(200,200,200));
+ const CImgList<tc> &ncolors = colors?colors:_colors;
+
+ // Init 3D objects and compute object statistics
+ CImg<floatT>
+ pose, rot_mat, zbuffer,
+ centered_points = centering?CImg<floatT>(Npoints,3):CImg<floatT>(),
+ rotated_points(Npoints,3),
+ bbox_points, rotated_bbox_points,
+ axes_points, rotated_axes_points,
+ bbox_opacities, axes_opacities;
+ CImgList<uintT> bbox_primitives, axes_primitives;
+ CImgList<T> bbox_colors, bbox_colors2, axes_colors;
+ float dx = 0, dy = 0, dz = 0, ratio = 1;
+
+ T minval = (T)0, maxval = (T)255;
+ if (disp.normalization && colors) {
+ minval = colors.minmax(maxval);
+ if (minval==maxval) { minval = (T)0; maxval = (T)255; }
+ }
+ const float meanval = (float)mean();
+ bool color_model = true;
+ if (cimg::abs(meanval-minval)>cimg::abs(meanval-maxval)) color_model = false;
+ const CImg<T>
+ background_color(1,1,1,dim,color_model?minval:maxval),
+ foreground_color(1,1,1,dim,color_model?maxval:minval);
+
+ float xm = cimg::type<float>::max(), xM = 0, ym = xm, yM = 0, zm = xm, zM = 0;
+ for (unsigned int i = 0; i<Npoints; ++i) {
+ const float
+ x = points._display_object3d_at2(i,0),
+ y = points._display_object3d_at2(i,1),
+ z = points._display_object3d_at2(i,2);
+ if (x<xm) xm = x;
+ if (x>xM) xM = x;
+ if (y<ym) ym = y;
+ if (y>yM) yM = y;
+ if (z<zm) zm = z;
+ if (z>zM) zM = z;
+ }
+ const float delta = cimg::max(xM-xm,yM-ym,zM-zm);
+
+ if (display_axes) {
+ rotated_axes_points = axes_points.assign(7,3,1,1,
+ 0,20,0,0,22,-6,-6,
+ 0,0,20,0,-6,22,-6,
+ 0,0,0,20,0,0,22);
+ axes_opacities.assign(3,1,1,1,1);
+ axes_colors.assign(3,dim,1,1,1,foreground_color[0]);
+ axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3);
+ }
+
+ // Begin user interaction loop
+ CImg<T> visu0(*this), visu;
+ bool init = true, clicked = false, redraw = true;
+ unsigned int key = 0;
+ int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
+ disp.show().flush();
+
+ while (!disp.is_closed && !key) {
+
+ // Init object position and scale if necessary
+ if (init) {
+ ratio = delta>0?(2.0f*cimg::min(disp.width,disp.height)/(3.0f*delta)):0;
+ dx = 0.5f*(xM + xm); dy = 0.5f*(yM + ym); dz = 0.5f*(zM + zm);
+ if (centering) {
+ cimg_forX(centered_points,l) {
+ centered_points(l,0) = (float)((points(l,0) - dx)*ratio);
+ centered_points(l,1) = (float)((points(l,1) - dy)*ratio);
+ centered_points(l,2) = (float)((points(l,2) - dz)*ratio);
+ }
+ }
+
+ if (render_static<0 || render_motion<0) {
+ rotated_bbox_points = bbox_points.assign(8,3,1,1,
+ xm,xM,xM,xm,xm,xM,xM,xm,
+ ym,ym,yM,yM,ym,ym,yM,yM,
+ zm,zm,zm,zm,zM,zM,zM,zM);
+ bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6);
+ bbox_colors.assign(6,dim,1,1,1,background_color[0]);
+ bbox_colors2.assign(6,dim,1,1,1,foreground_color[0]);
+ bbox_opacities.assign(bbox_colors.size,1,1,1,0.3f);
+ }
+
+ if (!pose) {
+ if (pose_matrix) pose = CImg<floatT>(pose_matrix,4,4,1,1,false);
+ else pose = CImg<floatT>::identity_matrix(4);
+ }
+ init = false;
+ redraw = true;
+ }
+
+ // Rotate and Draw 3D object
+ if (redraw) {
+ const float
+ r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0),
+ r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1),
+ r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2);
+ if ((clicked && render_motion>=0) || (!clicked && render_static>=0)) {
+ if (centering) cimg_forX(centered_points,l) {
+ const float x = centered_points(l,0), y = centered_points(l,1), z = centered_points(l,2);
+ rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
+ rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
+ rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
+ } else for (unsigned int l = 0; l<Npoints; ++l) {
+ const float
+ x = (float)points._display_object3d_at2(l,0),
+ y = (float)points._display_object3d_at2(l,1),
+ z = (float)points._display_object3d_at2(l,2);
+ rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
+ rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
+ rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
+ }
+ } else {
+ if (!centering) cimg_forX(bbox_points,l) {
+ const float x = bbox_points(l,0), y = bbox_points(l,1), z = bbox_points(l,2);
+ rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
+ rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
+ rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
+ } else cimg_forX(bbox_points,l) {
+ const float x = (bbox_points(l,0)-dx)*ratio, y = (bbox_points(l,1)-dy)*ratio, z = (bbox_points(l,2)-dz)*ratio;
+ rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
+ rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
+ rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
+ }
+ }
+
+ // Draw object
+ visu = visu0;
+ if ((clicked && render_motion<0) || (!clicked && render_static<0))
+ visu.draw_object3d(visu.width/2.0f,visu.height/2.0f,0,rotated_bbox_points,bbox_primitives,bbox_colors,bbox_opacities,2,false,focale).
+ draw_object3d(visu.width/2.0f,visu.height/2.0f,0,rotated_bbox_points,bbox_primitives,bbox_colors2,1,false,focale);
+ else visu.draw_object3d(visu.width/2.0f,visu.height/2.0f,0,
+ rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
+ double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
+ (!clicked && render_static>0)?zbuffer.fill(0).ptr():0);
+
+ // Draw axes
+ if (display_axes) {
+ const float Xaxes = 25, Yaxes = visu.height - 35.0f;
+ cimg_forX(axes_points,l) {
+ const float x = axes_points(l,0), y = axes_points(l,1), z = axes_points(l,2);
+ rotated_axes_points(l,0) = r00*x + r10*y + r20*z;
+ rotated_axes_points(l,1) = r01*x + r11*y + r21*z;
+ rotated_axes_points(l,2) = r02*x + r12*y + r22*z;
+ }
+ axes_opacities(0,0) = (rotated_axes_points(1,2)>0)?0.5f:1.0f;
+ axes_opacities(1,0) = (rotated_axes_points(2,2)>0)?0.5f:1.0f;
+ axes_opacities(2,0) = (rotated_axes_points(3,2)>0)?0.5f:1.0f;
+ visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_points,axes_primitives,axes_colors,axes_opacities,1,false,focale).
+ draw_text((int)(Xaxes+rotated_axes_points(4,0)),
+ (int)(Yaxes+rotated_axes_points(4,1)),
+ "X",axes_colors[0].data,0,axes_opacities(0,0),11).
+ draw_text((int)(Xaxes+rotated_axes_points(5,0)),
+ (int)(Yaxes+rotated_axes_points(5,1)),
+ "Y",axes_colors[1].data,0,axes_opacities(1,0),11).
+ draw_text((int)(Xaxes+rotated_axes_points(6,0)),
+ (int)(Yaxes+rotated_axes_points(6,1)),
+ "Z",axes_colors[2].data,0,axes_opacities(2,0),11);
+ }
+ visu.display(disp);
+ if (!clicked || render_motion==render_static) redraw = false;
+ }
+
+ // Handle user interaction
+ disp.wait();
+ if ((disp.button || disp.wheel) && disp.mouse_x>=0 && disp.mouse_y>=0) {
+ redraw = true;
+ if (!clicked) { x0 = x1 = disp.mouse_x; y0 = y1 = disp.mouse_y; if (!disp.wheel) clicked = true; }
+ else { x1 = disp.mouse_x; y1 = disp.mouse_y; }
+ if (disp.button&1) {
+ const float
+ R = 0.45f*cimg::min(disp.width,disp.height),
+ R2 = R*R,
+ u0 = (float)(x0-disp.dimx()/2),
+ v0 = (float)(y0-disp.dimy()/2),
+ u1 = (float)(x1-disp.dimx()/2),
+ v1 = (float)(y1-disp.dimy()/2),
+ n0 = (float)cimg_std::sqrt(u0*u0+v0*v0),
+ n1 = (float)cimg_std::sqrt(u1*u1+v1*v1),
+ nu0 = n0>R?(u0*R/n0):u0,
+ nv0 = n0>R?(v0*R/n0):v0,
+ nw0 = (float)cimg_std::sqrt(cimg::max(0,R2-nu0*nu0-nv0*nv0)),
+ nu1 = n1>R?(u1*R/n1):u1,
+ nv1 = n1>R?(v1*R/n1):v1,
+ nw1 = (float)cimg_std::sqrt(cimg::max(0,R2-nu1*nu1-nv1*nv1)),
+ u = nv0*nw1-nw0*nv1,
+ v = nw0*nu1-nu0*nw1,
+ w = nv0*nu1-nu0*nv1,
+ n = (float)cimg_std::sqrt(u*u+v*v+w*w),
+ alpha = (float)cimg_std::asin(n/R2);
+ rot_mat = CImg<floatT>::rotation_matrix(u,v,w,alpha);
+ rot_mat *= pose.get_crop(0,0,2,2);
+ pose.draw_image(rot_mat);
+ x0=x1; y0=y1;
+ }
+ if (disp.button&2) { pose(3,2)+=(y1-y0); x0 = x1; y0 = y1; }
+ if (disp.wheel) { pose(3,2)-=focale*disp.wheel/10; disp.wheel = 0; }
+ if (disp.button&4) { pose(3,0)+=(x1-x0); pose(3,1)+=(y1-y0); x0 = x1; y0 = y1; }
+ if ((disp.button&1) && (disp.button&2)) { init = true; disp.button = 0; x0 = x1; y0 = y1; pose = CImg<floatT>::identity_matrix(4); }
+ } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; }
+
+ switch (key = disp.key) {
+ case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
+ case cimg::keyD: if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
+ CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
+ disp.key = key = 0;
+ } break;
+ case cimg::keyC : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
+ disp.key = key = 0;
+ } break;
+ case cimg::keyR : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false).is_resized = true;
+ disp.key = key = 0;
+ } break;
+ case cimg::keyF : if (disp.is_keyCTRLLEFT) {
+ disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true;
+ disp.key = key = 0;
+ } break;
+ case cimg::keyZ : if (disp.is_keyCTRLLEFT) { // Enable/Disable Z-buffer
+ if (zbuffer) zbuffer.assign();
+ else zbuffer.assign(disp.width,disp.height);
+ disp.key = key = 0; redraw = true;
+ } break;
+ case cimg::keyS : if (disp.is_keyCTRLLEFT) { // Save snapshot
+ static unsigned int snap_number = 0;
+ char filename[32] = { 0 };
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
+ if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
+ } while (file);
+ (+visu).draw_text(2,2,"Saving BMP snapshot...",foreground_color,background_color,1,11).display(disp);
+ visu.save(filename);
+ visu.draw_text(2,2,"Snapshot '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
+ disp.key = key = 0;
+ } break;
+ case cimg::keyO : if (disp.is_keyCTRLLEFT) { // Save object as an .OFF file
+ static unsigned int snap_number = 0;
+ char filename[32] = { 0 };
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filename,"CImg_%.4u.off",snap_number++);
+ if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
+ } while (file);
+ visu.draw_text(2,2,"Saving object...",foreground_color,background_color,1,11).display(disp);
+ points.save_off(filename,primitives,ncolors);
+ visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
+ disp.key = key = 0;
+ } break;
+#ifdef cimg_use_board
+ case cimg::keyP : if (disp.is_keyCTRLLEFT) { // Save object as a .EPS file
+ static unsigned int snap_number = 0;
+ char filename[32] = { 0 };
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filename,"CImg_%.4u.eps",snap_number++);
+ if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
+ } while (file);
+ visu.draw_text(2,2,"Saving EPS snapshot...",foreground_color,background_color,1,11).display(disp);
+ BoardLib::Board board;
+ (+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0,
+ rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
+ double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
+ zbuffer.fill(0).ptr());
+ board.saveEPS(filename);
+ visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
+ disp.key = key = 0;
+ } break;
+ case cimg::keyV : if (disp.is_keyCTRLLEFT) { // Save object as a .SVG file
+ static unsigned int snap_number = 0;
+ char filename[32] = { 0 };
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filename,"CImg_%.4u.svg",snap_number++);
+ if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
+ } while (file);
+ visu.draw_text(2,2,"Saving SVG snapshot...",foreground_color,background_color,1,11).display(disp);
+ BoardLib::Board board;
+ (+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0,
+ rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
+ double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
+ zbuffer.fill(0).ptr());
+ board.saveSVG(filename);
+ visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
+ disp.key = key = 0;
+ } break;
+#endif
+ }
+ if (disp.is_resized) { disp.resize(false); visu0 = get_resize(disp,1); if (zbuffer) zbuffer.assign(disp.width,disp.height); redraw = true; }
+ }
+ if (pose_matrix) cimg_std::memcpy(pose_matrix,pose.data,16*sizeof(float));
+ disp.button = 0;
+ disp.key = key;
+ return *this;
+ }
+
+ //! High-level interface for displaying a graph.
+ const CImg<T>& display_graph(CImgDisplay &disp,
+ const unsigned int plot_type=1, const unsigned int vertex_type=1,
+ const char *const labelx=0, const double xmin=0, const double xmax=0,
+ const char *const labely=0, const double ymin=0, const double ymax=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ const unsigned int siz = width*height*depth, onormalization = disp.normalization;
+ if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
+ disp.show().flush().normalization = 0;
+ double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax;
+ if (nxmin==nxmax) { nxmin = 0; nxmax = siz - 1.0; }
+ int x0 = 0, x1 = size()/dimv()-1, key = 0;
+
+ for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) {
+ if (reset_view) { x0 = 0; x1 = size()/dimv()-1; y0 = ymin; y1 = ymax; reset_view = false; }
+ CImg<T> zoom(x1-x0+1,1,1,dimv());
+ cimg_forV(*this,k) zoom.get_shared_channel(k) = CImg<T>(ptr(x0,0,0,k),x1-x0+1,1,1,1,true);
+
+ if (y0==y1) y0 = zoom.minmax(y1);
+ if (y0==y1) { --y0; ++y1; }
+ const CImg<intT> selection = zoom.get_select_graph(disp,plot_type,vertex_type,
+ labelx,nxmin + x0*(nxmax-nxmin)/siz,nxmin + x1*(nxmax-nxmin)/siz,
+ labely,y0,y1);
+
+ const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y;
+ if (selection[0]>=0 && selection[2]>=0) {
+ x1 = x0 + selection[2];
+ x0 += selection[0];
+ if (x0==x1) reset_view = true;
+ if (selection[1]>=0 && selection[3]>=0) {
+ y0 = y1 - selection[3]*(y1-y0)/(disp.dimy()-32);
+ y1 -= selection[1]*(y1-y0)/(disp.dimy()-32);
+ }
+ } else {
+ bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false;
+ switch (key = disp.key) {
+ case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break;
+ case cimg::keyPADADD : go_in = true; key = 0; break;
+ case cimg::keyPADSUB : go_out = true; key = 0; break;
+ case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; key = 0; break;
+ case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; key = 0; break;
+ case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; key = 0; break;
+ case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; key = 0; break;
+ case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; break;
+ case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; break;
+ case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; break;
+ case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; break;
+ }
+ if (disp.wheel) go_out = !(go_in = disp.wheel>0);
+
+ if (go_in) {
+ const int
+ xsiz = x1 - x0,
+ mx = (mouse_x-16)*xsiz/(disp.dimx()-32),
+ cx = x0 + (mx<0?0:(mx>=xsiz?xsiz:mx));
+ if (x1-x0>4) {
+ x0 = cx - 7*(cx-x0)/8; x1 = cx + 7*(x1-cx)/8;
+ if (disp.is_keyCTRLLEFT) {
+ const double
+ ysiz = y1 - y0,
+ my = (mouse_y-16)*ysiz/(disp.dimy()-32),
+ cy = y1 - (my<0?0:(my>=ysiz?ysiz:my));
+ y0 = cy - 7*(cy-y0)/8; y1 = cy + 7*(y1-cy)/8;
+ } else y0 = y1 = 0;
+ }
+ }
+ if (go_out) {
+ const int deltax = (x1-x0)/8, ndeltax = deltax?deltax:(siz>1?1:0);
+ x0-=ndeltax; x1+=ndeltax;
+ if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz-1; }
+ if (x1>=(int)siz) { x0-=(x1-siz+1); x1 = (int)siz-1; if (x0<0) x0 = 0; }
+ if (disp.is_keyCTRLLEFT) {
+ const double deltay = (y1-y0)/8, ndeltay = deltay?deltay:0.01;
+ y0-=ndeltay; y1+=ndeltay;
+ }
+ }
+ if (go_left) {
+ const int delta = (x1-x0)/5, ndelta = delta?delta:1;
+ if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
+ else { x1-=x0; x0 = 0; }
+ go_left = false;
+ }
+ if (go_right) {
+ const int delta = (x1-x0)/5, ndelta = delta?delta:1;
+ if (x1+ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; }
+ else { x0+=(siz-1-x1); x1 = siz-1; }
+ go_right = false;
+ }
+ if (go_up) {
+ const double delta = (y1-y0)/10, ndelta = delta?delta:1;
+ y0+=ndelta; y1+=ndelta;
+ go_up = false;
+ }
+ if (go_down) {
+ const double delta = (y1-y0)/10, ndelta = delta?delta:1;
+ y0-=ndelta; y1-=ndelta;
+ go_down = false;
+ }
+ }
+ }
+ disp.normalization = onormalization;
+ return *this;
+ }
+
+ //! High-level interface for displaying a graph.
+ const CImg<T>& display_graph(const char *const title=0,
+ const unsigned int plot_type=1, const unsigned int vertex_type=1,
+ const char *const labelx=0, const double xmin=0, const double xmax=0,
+ const char *const labely=0, const double ymin=0, const double ymax=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ char ntitle[64] = { 0 }; if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
+ CImgDisplay disp(cimg_fitscreen(640,480,1),title?title:ntitle,0);
+ return display_graph(disp,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax);
+ }
+
+ //! Select sub-graph in a graph.
+ CImg<intT> get_select_graph(CImgDisplay &disp,
+ const unsigned int plot_type=1, const unsigned int vertex_type=1,
+ const char *const labelx=0, const double xmin=0, const double xmax=0,
+ const char *const labely=0, const double ymin=0, const double ymax=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),width,height,depth,dim,data);
+ const unsigned int siz = width*height*depth, onormalization = disp.normalization;
+ if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
+ disp.show().key = disp.normalization = disp.button = disp.wheel = 0; // Must keep 'key' field unchanged.
+ double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax;
+ if (nymin==nymax) nymin = (Tfloat)minmax(nymax);
+ if (nymin==nymax) { --nymin; ++nymax; }
+ if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.0; }
+
+ const unsigned char black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 220,220,220 };
+ const unsigned char gray2[] = { 110,110,110 }, ngray[] = { 35,35,35 };
+ static unsigned int odimv = 0;
+ static CImg<ucharT> palette;
+ if (odimv!=dim) {
+ odimv = dim;
+ palette = CImg<ucharT>(3,dim,1,1,120).noise(70,1);
+ if (dim==1) { palette[0] = palette[1] = 120; palette[2] = 200; }
+ else {
+ palette(0,0) = 220; palette(1,0) = 10; palette(2,0) = 10;
+ if (dim>1) { palette(0,1) = 10; palette(1,1) = 220; palette(2,1) = 10; }
+ if (dim>2) { palette(0,2) = 10; palette(1,2) = 10; palette(2,2) = 220; }
+ }
+ }
+
+ CImg<ucharT> visu0, visu, graph, text, axes;
+ const unsigned int whz = width*height*depth;
+ int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2;
+ char message[1024] = { 0 };
+ unsigned int okey = 0, obutton = 0;
+ CImg_3x3(I,unsigned char);
+
+ for (bool selected = false; !selected && !disp.is_closed && !okey && !disp.wheel; ) {
+ const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y;
+ const unsigned int key = disp.key, button = disp.button;
+
+ // Generate graph representation.
+ if (!visu0) {
+ visu0.assign(disp.dimx(),disp.dimy(),1,3,220);
+ const int gdimx = disp.dimx() - 32, gdimy = disp.dimy() - 32;
+ if (gdimx>0 && gdimy>0) {
+ graph.assign(gdimx,gdimy,1,3,255);
+ graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333);
+ cimg_forV(*this,k) graph.draw_graph(get_shared_channel(k),&palette(0,k),(plot_type!=3 || dim==1)?1:0.6f,
+ plot_type,vertex_type,nymax,nymin);
+
+ axes.assign(gdimx,gdimy,1,1,0);
+ const float
+ dx = (float)cimg::abs(nxmax-nxmin), dy = (float)cimg::abs(nymax-nymin),
+ px = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0),
+ py = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0);
+ const CImg<Tdouble>
+ seqx = CImg<Tdouble>::sequence(1 + gdimx/60,nxmin,nxmax).round(px),
+ seqy = CImg<Tdouble>::sequence(1 + gdimy/60,nymax,nymin).round(py);
+ axes.draw_axis(seqx,seqy,white);
+ if (nymin>0) axes.draw_axis(seqx,gdimy-1,gray);
+ if (nymax<0) axes.draw_axis(seqx,0,gray);
+ if (nxmin>0) axes.draw_axis(0,seqy,gray);
+ if (nxmax<0) axes.draw_axis(gdimx-1,seqy,gray);
+
+ cimg_for3x3(axes,x,y,0,0,I)
+ if (Icc) {
+ if (Icc==255) cimg_forV(graph,k) graph(x,y,k) = 0;
+ else cimg_forV(graph,k) graph(x,y,k) = (unsigned char)(2*graph(x,y,k)/3);
+ }
+ else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) cimg_forV(graph,k) graph(x,y,k) = (graph(x,y,k)+255)/2;
+
+ visu0.draw_image(16,16,graph);
+ visu0.draw_line(15,15,16+gdimx,15,gray2).draw_line(16+gdimx,15,16+gdimx,16+gdimy,gray2).
+ draw_line(16+gdimx,16+gdimy,15,16+gdimy,white).draw_line(15,16+gdimy,15,15,white);
+ } else graph.assign();
+ text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1);
+ visu0.draw_image((visu0.dimx()-text.dimx())/2,visu0.dimy()-14,~text);
+ text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1).rotate(-90);
+ visu0.draw_image(2,(visu0.dimy()-text.dimy())/2,~text);
+ visu.assign();
+ }
+
+ // Generate and display current view.
+ if (!visu) {
+ visu.assign(visu0);
+ if (graph && x0>=0 && x1>=0) {
+ const int
+ nx0 = x0<=x1?x0:x1,
+ nx1 = x0<=x1?x1:x0,
+ ny0 = y0<=y1?y0:y1,
+ ny1 = y0<=y1?y1:y0,
+ sx0 = 16 + nx0*(visu.dimx()-32)/whz,
+ sx1 = 15 + (nx1+1)*(visu.dimx()-32)/whz,
+ sy0 = 16 + ny0,
+ sy1 = 16 + ny1;
+
+ if (y0>=0 && y1>=0)
+ visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU);
+ else visu.draw_rectangle(sx0,0,sx1,visu.dimy()-17,gray,0.5f).
+ draw_line(sx0,16,sx0,visu.dimy()-17,black,0.5f,0xCCCCCCCCU).
+ draw_line(sx1,16,sx1,visu.dimy()-17,black,0.5f,0xCCCCCCCCU);
+ }
+ if (mouse_x>=16 && mouse_y>=16 && mouse_x<visu.dimx()-16 && mouse_y<visu.dimy()-16) {
+ if (graph) visu.draw_line(mouse_x,16,mouse_x,visu.dimy()-17,black,0.5f,0x55555555U);
+ const unsigned x = (mouse_x-16)*whz/(disp.dimx()-32);
+ const double cx = nxmin + x*(nxmax-nxmin)/whz;
+ if (dim>=7)
+ cimg_std::sprintf(message,"Value[%g] = ( %g %g %g ... %g %g %g )",cx,
+ (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2),
+ (double)(*this)(x,0,0,dim-4),(double)(*this)(x,0,0,dim-3),(double)(*this)(x,0,0,dim-1));
+ else {
+ cimg_std::sprintf(message,"Value[%g] = ( ",cx);
+ cimg_forV(*this,k) cimg_std::sprintf(message+cimg::strlen(message),"%g ",(double)(*this)(x,0,0,k));
+ cimg_std::sprintf(message+cimg::strlen(message),")");
+ }
+ if (x0>=0 && x1>=0) {
+ const int
+ nx0 = x0<=x1?x0:x1,
+ nx1 = x0<=x1?x1:x0,
+ ny0 = y0<=y1?y0:y1,
+ ny1 = y0<=y1?y1:y0;
+ const double
+ cx0 = nxmin + nx0*(nxmax-nxmin)/(visu.dimx()-32),
+ cx1 = nxmin + nx1*(nxmax-nxmin)/(visu.dimx()-32),
+ cy0 = nymax - ny0*(nymax-nymin)/(visu.dimy()-32),
+ cy1 = nymax - ny1*(nymax-nymin)/(visu.dimy()-32);
+ if (y0>=0 && y1>=0)
+ cimg_std::sprintf(message+cimg::strlen(message)," - Range ( %g, %g ) - ( %g, %g )",cx0,cy0,cx1,cy1);
+ else
+ cimg_std::sprintf(message+cimg::strlen(message)," - Range [ %g - %g ]",cx0,cx1);
+ }
+ text.assign().draw_text(0,0,message,white,ngray,1);
+ visu.draw_image((visu.dimx()-text.dimx())/2,2,~text);
+ }
+ visu.display(disp);
+ }
+
+ // Test keys.
+ switch (okey = key) {
+ case cimg::keyCTRLLEFT : okey = 0; break;
+ case cimg::keyD : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
+ CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
+ disp.key = okey = 0;
+ } break;
+ case cimg::keyC : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
+ disp.key = okey = 0;
+ } break;
+ case cimg::keyR : if (disp.is_keyCTRLLEFT) {
+ disp.normalscreen().resize(cimg_fitscreen(640,480,1),false).is_resized = true;
+ disp.key = okey = 0;
+ } break;
+ case cimg::keyF : if (disp.is_keyCTRLLEFT) {
+ disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true;
+ disp.key = okey = 0;
+ } break;
+ case cimg::keyS : if (disp.is_keyCTRLLEFT) {
+ static unsigned int snap_number = 0;
+ if (visu || visu0) {
+ CImg<ucharT> &screen = visu?visu:visu0;
+ char filename[32] = { 0 };
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
+ if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
+ } while (file);
+ (+screen).draw_text(2,2,"Saving BMP snapshot...",black,gray,1,11).display(disp);
+ screen.save(filename);
+ screen.draw_text(2,2,"Snapshot '%s' saved.",black,gray,1,11,filename).display(disp);
+ }
+ disp.key = okey = 0;
+ } break;
+ }
+
+ // Handle mouse motion and mouse buttons
+ if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) {
+ visu.assign();
+ if (disp.mouse_x>=0 && disp.mouse_y>=0) {
+ const int
+ mx = (mouse_x-16)*(int)whz/(disp.dimx()-32),
+ cx = mx<0?0:(mx>=(int)whz?whz-1:mx),
+ my = mouse_y-16,
+ cy = my<=0?0:(my>=(disp.dimy()-32)?(disp.dimy()-32):my);
+ if (button&1) { if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; }}
+ else if (button&2) { if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; }}
+ else if (obutton) { x1 = cx; y1 = y1>=0?cy:-1; selected = true; }
+ } else if (!button && obutton) selected = true;
+ obutton = button; omouse_x = mouse_x; omouse_y = mouse_y;
+ }
+ if (disp.is_resized) { disp.resize(false); visu0.assign(); }
+ if (visu && visu0) disp.wait();
+ }
+ disp.normalization = onormalization;
+ if (x1<x0) cimg::swap(x0,x1);
+ if (y1<y0) cimg::swap(y0,y1);
+ disp.key = okey;
+ return CImg<intT>(4,1,1,1,x0,y0,x1,y1);
+ }
+
+ //@}
+ //---------------------------
+ //
+ //! \name Image File Loading
+ //@{
+ //---------------------------
+
+ //! Load an image from a file.
+ /**
+ \param filename is the name of the image file to load.
+ \note The extension of \c filename defines the file format. If no filename
+ extension is provided, CImg<T>::get_load() will try to load a .cimg file.
+ **/
+ CImg<T>& load(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load() : Cannot load (null) filename.",
+ pixel_type());
+ const char *ext = cimg::split_filename(filename);
+ const unsigned int odebug = cimg::exception_mode();
+ cimg::exception_mode() = 0;
+ assign();
+ try {
+#ifdef cimg_load_plugin
+ cimg_load_plugin(filename);
+#endif
+#ifdef cimg_load_plugin1
+ cimg_load_plugin1(filename);
+#endif
+#ifdef cimg_load_plugin2
+ cimg_load_plugin2(filename);
+#endif
+#ifdef cimg_load_plugin3
+ cimg_load_plugin3(filename);
+#endif
+#ifdef cimg_load_plugin4
+ cimg_load_plugin4(filename);
+#endif
+#ifdef cimg_load_plugin5
+ cimg_load_plugin5(filename);
+#endif
+#ifdef cimg_load_plugin6
+ cimg_load_plugin6(filename);
+#endif
+#ifdef cimg_load_plugin7
+ cimg_load_plugin7(filename);
+#endif
+#ifdef cimg_load_plugin8
+ cimg_load_plugin8(filename);
+#endif
+ // ASCII formats
+ if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename);
+ if (!cimg::strcasecmp(ext,"dlm") ||
+ !cimg::strcasecmp(ext,"txt")) load_dlm(filename);
+
+ // 2D binary formats
+ if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename);
+ if (!cimg::strcasecmp(ext,"jpg") ||
+ !cimg::strcasecmp(ext,"jpeg") ||
+ !cimg::strcasecmp(ext,"jpe") ||
+ !cimg::strcasecmp(ext,"jfif") ||
+ !cimg::strcasecmp(ext,"jif")) load_jpeg(filename);
+ if (!cimg::strcasecmp(ext,"png")) load_png(filename);
+ if (!cimg::strcasecmp(ext,"ppm") ||
+ !cimg::strcasecmp(ext,"pgm") ||
+ !cimg::strcasecmp(ext,"pnm")) load_pnm(filename);
+ if (!cimg::strcasecmp(ext,"tif") ||
+ !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+ if (!cimg::strcasecmp(ext,"cr2") ||
+ !cimg::strcasecmp(ext,"crw") ||
+ !cimg::strcasecmp(ext,"dcr") ||
+ !cimg::strcasecmp(ext,"mrw") ||
+ !cimg::strcasecmp(ext,"nef") ||
+ !cimg::strcasecmp(ext,"orf") ||
+ !cimg::strcasecmp(ext,"pix") ||
+ !cimg::strcasecmp(ext,"ptx") ||
+ !cimg::strcasecmp(ext,"raf") ||
+ !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename);
+
+ // 3D binary formats
+ if (!cimg::strcasecmp(ext,"dcm") ||
+ !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename);
+ if (!cimg::strcasecmp(ext,"hdr") ||
+ !cimg::strcasecmp(ext,"nii")) load_analyze(filename);
+ if (!cimg::strcasecmp(ext,"par") ||
+ !cimg::strcasecmp(ext,"rec")) load_parrec(filename);
+ if (!cimg::strcasecmp(ext,"inr")) load_inr(filename);
+ if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename);
+ if (!cimg::strcasecmp(ext,"cimg") ||
+ !cimg::strcasecmp(ext,"cimgz") ||
+ *ext=='\0') return load_cimg(filename);
+
+ // Archive files
+ if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+
+ // Image sequences
+ if (!cimg::strcasecmp(ext,"avi") ||
+ !cimg::strcasecmp(ext,"mov") ||
+ !cimg::strcasecmp(ext,"asf") ||
+ !cimg::strcasecmp(ext,"divx") ||
+ !cimg::strcasecmp(ext,"flv") ||
+ !cimg::strcasecmp(ext,"mpg") ||
+ !cimg::strcasecmp(ext,"m1v") ||
+ !cimg::strcasecmp(ext,"m2v") ||
+ !cimg::strcasecmp(ext,"m4v") ||
+ !cimg::strcasecmp(ext,"mjp") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
+ if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type());
+ } catch (CImgException& e) {
+ if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) {
+ cimg::exception_mode() = odebug;
+ throw CImgIOException("CImg<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename);
+ } else try {
+ const char *const ftype = cimg::file_type(0,filename);
+ assign();
+ if (!cimg::strcmp(ftype,"pnm")) load_pnm(filename);
+ if (!cimg::strcmp(ftype,"bmp")) load_bmp(filename);
+ if (!cimg::strcmp(ftype,"jpeg")) load_jpeg(filename);
+ if (!cimg::strcmp(ftype,"pan")) load_pandore(filename);
+ if (!cimg::strcmp(ftype,"png")) load_png(filename);
+ if (!cimg::strcmp(ftype,"tiff")) load_tiff(filename);
+ if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type());
+ } catch (CImgException&) {
+ try {
+ load_other(filename);
+ } catch (CImgException&) {
+ assign();
+ }
+ }
+ }
+ cimg::exception_mode() = odebug;
+ if (is_empty())
+ throw CImgIOException("CImg<%s>::load() : File '%s', format not recognized.",pixel_type(),filename);
+ return *this;
+ }
+
+ static CImg<T> get_load(const char *const filename) {
+ return CImg<T>().load(filename);
+ }
+
+ //! Load an image from an ASCII file.
+ CImg<T>& load_ascii(const char *const filename) {
+ return _load_ascii(0,filename);
+ }
+
+ static CImg<T> get_load_ascii(const char *const filename) {
+ return CImg<T>().load_ascii(filename);
+ }
+
+ //! Load an image from an ASCII file.
+ CImg<T>& load_ascii(cimg_std::FILE *const file) {
+ return _load_ascii(file,0);
+ }
+
+ static CImg<T> get_load_ascii(cimg_std::FILE *const file) {
+ return CImg<T>().load_ascii(file);
+ }
+
+ CImg<T>& _load_ascii(cimg_std::FILE *const file, const char *const filename) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_ascii() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ char line[256] = { 0 };
+ int err = cimg_std::fscanf(nfile,"%*[^0-9]%255[^\n]",line);
+ unsigned int off, dx = 0, dy = 1, dz = 1, dv = 1;
+ cimg_std::sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dv);
+ err = cimg_std::fscanf(nfile,"%*[^0-9.+-]");
+ if (!dx || !dy || !dz || !dv) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_ascii() : File '%s', invalid .ASC header, specified image dimensions are (%u,%u,%u,%u).",
+ pixel_type(),filename?filename:"(FILE*)",dx,dy,dz,dv);
+ }
+ assign(dx,dy,dz,dv);
+ const unsigned long siz = size();
+ double val;
+ T *ptr = data;
+ for (err = 1, off = 0; off<siz && err==1; ++off) {
+ err = cimg_std::fscanf(nfile,"%lf%*[^0-9.+-]",&val);
+ *(ptr++) = (T)val;
+ }
+ if (err!=1)
+ cimg::warn("CImg<%s>::load_ascii() : File '%s', only %u/%lu values read.",
+ pixel_type(),filename?filename:"(FILE*)",off-1,siz);
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a DLM file.
+ CImg<T>& load_dlm(const char *const filename) {
+ return _load_dlm(0,filename);
+ }
+
+ static CImg<T> get_load_dlm(const char *const filename) {
+ return CImg<T>().load_dlm(filename);
+ }
+
+ //! Load an image from a DLM file.
+ CImg<T>& load_dlm(cimg_std::FILE *const file) {
+ return _load_dlm(file,0);
+ }
+
+ static CImg<T> get_load_dlm(cimg_std::FILE *const file) {
+ return CImg<T>().load_dlm(file);
+ }
+
+ CImg<T>& _load_dlm(cimg_std::FILE *const file, const char *const filename) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_dlm() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
+ assign(256,256);
+ char c, delimiter[256] = { 0 }, tmp[256];
+ unsigned int cdx = 0, dx = 0, dy = 0;
+ int oerr = 0, err;
+ double val;
+ while ((err = cimg_std::fscanf(nfile,"%lf%255[^0-9.+-]",&val,delimiter))!=EOF) {
+ oerr = err;
+ if (err>0) (*this)(cdx++,dy) = (T)val;
+ if (cdx>=width) resize(width+256,1,1,1,0);
+ c = 0; if (!cimg_std::sscanf(delimiter,"%255[^\n]%c",tmp,&c) || c=='\n') {
+ dx = cimg::max(cdx,dx);
+ ++dy;
+ if (dy>=height) resize(width,height+256,1,1,0);
+ cdx = 0;
+ }
+ }
+ if (cdx && oerr==1) { dx=cdx; ++dy; }
+ if (!dx || !dy) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_dlm() : File '%s', invalid DLM file, specified image dimensions are (%u,%u).",
+ pixel_type(),filename?filename:"(FILE*)",dx,dy);
+ }
+ resize(dx,dy,1,1,0);
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a BMP file.
+ CImg<T>& load_bmp(const char *const filename) {
+ return _load_bmp(0,filename);
+ }
+
+ static CImg<T> get_load_bmp(const char *const filename) {
+ return CImg<T>().load_bmp(filename);
+ }
+
+ //! Load an image from a BMP file.
+ CImg<T>& load_bmp(cimg_std::FILE *const file) {
+ return _load_bmp(file,0);
+ }
+
+ static CImg<T> get_load_bmp(cimg_std::FILE *const file) {
+ return CImg<T>().load_bmp(file);
+ }
+
+ CImg<T>& _load_bmp(cimg_std::FILE *const file, const char *const filename) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_bmp() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ unsigned char header[64];
+ cimg::fread(header,54,nfile);
+ if (header[0]!='B' || header[1]!='M') {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_bmp() : Invalid valid BMP file (filename '%s').",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ assign();
+
+ // Read header and pixel buffer
+ int
+ file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24),
+ offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24),
+ dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24),
+ dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24),
+ compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24),
+ nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24),
+ bpp = header[0x1C] + (header[0x1D]<<8),
+ *palette = 0;
+ const int
+ dx_bytes = (bpp==1)?(dx/8+(dx%8?1:0)):((bpp==4)?(dx/2+(dx%2?1:0)):(dx*bpp/8)),
+ align = (4-dx_bytes%4)%4,
+ buf_size = cimg::min(cimg::abs(dy)*(dx_bytes+align),file_size-offset);
+
+ if (bpp<16) { if (!nb_colors) nb_colors=1<<bpp; } else nb_colors = 0;
+ if (nb_colors) { palette = new int[nb_colors]; cimg::fread(palette,nb_colors,nfile); }
+ const int xoffset = offset-54-4*nb_colors;
+ if (xoffset>0) cimg_std::fseek(nfile,xoffset,SEEK_CUR);
+ unsigned char *buffer = new unsigned char[buf_size], *ptrs = buffer;
+ cimg::fread(buffer,buf_size,nfile);
+ if (!file) cimg::fclose(nfile);
+
+ // Decompress buffer (if necessary)
+ if (compression) {
+ delete[] buffer;
+ if (file) {
+ throw CImgIOException("CImg<%s>::load_bmp() : Not able to read a compressed BMP file using a *FILE input",
+ pixel_type());
+ } else return load_other(filename);
+ }
+
+ // Read pixel data
+ assign(dx,cimg::abs(dy),1,3);
+ switch (bpp) {
+ case 1 : { // Monochrome
+ for (int y=height-1; y>=0; --y) {
+ unsigned char mask = 0x80, val = 0;
+ cimg_forX(*this,x) {
+ if (mask==0x80) val = *(ptrs++);
+ const unsigned char *col = (unsigned char*)(palette+(val&mask?1:0));
+ (*this)(x,y,2) = (T)*(col++);
+ (*this)(x,y,1) = (T)*(col++);
+ (*this)(x,y,0) = (T)*(col++);
+ mask = cimg::ror(mask);
+ } ptrs+=align; }
+ } break;
+ case 4 : { // 16 colors
+ for (int y=height-1; y>=0; --y) {
+ unsigned char mask = 0xF0, val = 0;
+ cimg_forX(*this,x) {
+ if (mask==0xF0) val = *(ptrs++);
+ const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4));
+ unsigned char *col = (unsigned char*)(palette+color);
+ (*this)(x,y,2) = (T)*(col++);
+ (*this)(x,y,1) = (T)*(col++);
+ (*this)(x,y,0) = (T)*(col++);
+ mask = cimg::ror(mask,4);
+ } ptrs+=align; }
+ } break;
+ case 8 : { // 256 colors
+ for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
+ const unsigned char *col = (unsigned char*)(palette+*(ptrs++));
+ (*this)(x,y,2) = (T)*(col++);
+ (*this)(x,y,1) = (T)*(col++);
+ (*this)(x,y,0) = (T)*(col++);
+ } ptrs+=align; }
+ } break;
+ case 16 : { // 16 bits colors
+ for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
+ const unsigned char c1 = *(ptrs++), c2 = *(ptrs++);
+ const unsigned short col = (unsigned short)(c1|(c2<<8));
+ (*this)(x,y,2) = (T)(col&0x1F);
+ (*this)(x,y,1) = (T)((col>>5)&0x1F);
+ (*this)(x,y,0) = (T)((col>>10)&0x1F);
+ } ptrs+=align; }
+ } break;
+ case 24 : { // 24 bits colors
+ for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
+ (*this)(x,y,2) = (T)*(ptrs++);
+ (*this)(x,y,1) = (T)*(ptrs++);
+ (*this)(x,y,0) = (T)*(ptrs++);
+ } ptrs+=align; }
+ } break;
+ case 32 : { // 32 bits colors
+ for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
+ (*this)(x,y,2) = (T)*(ptrs++);
+ (*this)(x,y,1) = (T)*(ptrs++);
+ (*this)(x,y,0) = (T)*(ptrs++);
+ ++ptrs;
+ } ptrs+=align; }
+ } break;
+ }
+ if (palette) delete[] palette;
+ delete[] buffer;
+ if (dy<0) mirror('y');
+ return *this;
+ }
+
+ //! Load an image from a JPEG file.
+ CImg<T>& load_jpeg(const char *const filename) {
+ return _load_jpeg(0,filename);
+ }
+
+ static CImg<T> get_load_jpeg(const char *const filename) {
+ return CImg<T>().load_jpeg(filename);
+ }
+
+ //! Load an image from a JPEG file.
+ CImg<T>& load_jpeg(cimg_std::FILE *const file) {
+ return _load_jpeg(file,0);
+ }
+
+ static CImg<T> get_load_jpeg(cimg_std::FILE *const file) {
+ return CImg<T>().load_jpeg(file);
+ }
+
+ CImg<T>& _load_jpeg(cimg_std::FILE *const file, const char *const filename) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_jpeg() : Cannot load (null) filename.",
+ pixel_type());
+#ifndef cimg_use_jpeg
+ if (file)
+ throw CImgIOException("CImg<%s>::load_jpeg() : File '(FILE*)' cannot be read without using libjpeg.",
+ pixel_type());
+ else return load_other(filename);
+#else
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jpeg_stdio_src(&cinfo,nfile);
+ jpeg_read_header(&cinfo,TRUE);
+ jpeg_start_decompress(&cinfo);
+
+ if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) {
+ cimg::warn("CImg<%s>::load_jpeg() : Don't know how to read image '%s' with libpeg, trying ImageMagick's convert",
+ pixel_type(),filename?filename:"(FILE*)");
+ if (!file) return load_other(filename);
+ else {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_jpeg() : Cannot read JPEG image '%s' using a *FILE input.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ }
+
+ const unsigned int row_stride = cinfo.output_width * cinfo.output_components;
+ unsigned char *buf = new unsigned char[cinfo.output_width*cinfo.output_height*cinfo.output_components], *buf2 = buf;
+ JSAMPROW row_pointer[1];
+ while (cinfo.output_scanline < cinfo.output_height) {
+ row_pointer[0] = &buf[cinfo.output_scanline*row_stride];
+ jpeg_read_scanlines(&cinfo,row_pointer,1);
+ }
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ if (!file) cimg::fclose(nfile);
+
+ assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components);
+ switch (dim) {
+ case 1 : {
+ T *ptr_g = data;
+ cimg_forXY(*this,x,y) *(ptr_g++) = (T)*(buf2++);
+ } break;
+ case 3 : {
+ T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
+ cimg_forXY(*this,x,y) {
+ *(ptr_r++) = (T)*(buf2++);
+ *(ptr_g++) = (T)*(buf2++);
+ *(ptr_b++) = (T)*(buf2++);
+ }
+ } break;
+ case 4 : {
+ T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1),
+ *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
+ cimg_forXY(*this,x,y) {
+ *(ptr_r++) = (T)*(buf2++);
+ *(ptr_g++) = (T)*(buf2++);
+ *(ptr_b++) = (T)*(buf2++);
+ *(ptr_a++) = (T)*(buf2++);
+ }
+ } break;
+ }
+ delete[] buf;
+ return *this;
+#endif
+ }
+
+ //! Load an image from a file, using Magick++ library.
+ // Added April/may 2006 by Christoph Hormann <chris_hormann@gmx.de>
+ // This is experimental code, not much tested, use with care.
+ CImg<T>& load_magick(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load_magick() : Cannot load (null) filename.",
+ pixel_type());
+#ifdef cimg_use_magick
+ Magick::Image image(filename);
+ const unsigned int W = image.size().width(), H = image.size().height();
+ switch (image.type()) {
+ case Magick::PaletteMatteType :
+ case Magick::TrueColorMatteType :
+ case Magick::ColorSeparationType : {
+ assign(W,H,1,4);
+ T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2), *adata = ptr(0,0,0,3);
+ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+ for (unsigned int off = W*H; off; --off) {
+ *(rdata++) = (T)(pixels->red);
+ *(gdata++) = (T)(pixels->green);
+ *(bdata++) = (T)(pixels->blue);
+ *(adata++) = (T)(pixels->opacity);
+ ++pixels;
+ }
+ } break;
+ case Magick::PaletteType :
+ case Magick::TrueColorType : {
+ assign(W,H,1,3);
+ T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
+ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+ for (unsigned int off = W*H; off; --off) {
+ *(rdata++) = (T)(pixels->red);
+ *(gdata++) = (T)(pixels->green);
+ *(bdata++) = (T)(pixels->blue);
+ ++pixels;
+ }
+ } break;
+ case Magick::GrayscaleMatteType : {
+ assign(W,H,1,2);
+ T *data = ptr(0,0,0,0), *adata = ptr(0,0,0,1);
+ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+ for (unsigned int off = W*H; off; --off) {
+ *(data++) = (T)(pixels->red);
+ *(adata++) = (T)(pixels->opacity);
+ ++pixels;
+ }
+ } break;
+ default : {
+ assign(W,H,1,1);
+ T *data = ptr(0,0,0,0);
+ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+ for (unsigned int off = W*H; off; --off) {
+ *(data++) = (T)(pixels->red);
+ ++pixels;
+ }
+ }
+ }
+#else
+ throw CImgIOException("CImg<%s>::load_magick() : File '%s', Magick++ library has not been linked.",
+ pixel_type(),filename);
+#endif
+ return *this;
+ }
+
+ static CImg<T> get_load_magick(const char *const filename) {
+ return CImg<T>().load_magick(filename);
+ }
+
+ //! Load an image from a PNG file.
+ CImg<T>& load_png(const char *const filename) {
+ return _load_png(0,filename);
+ }
+
+ static CImg<T> get_load_png(const char *const filename) {
+ return CImg<T>().load_png(filename);
+ }
+
+ //! Load an image from a PNG file.
+ CImg<T>& load_png(cimg_std::FILE *const file) {
+ return _load_png(file,0);
+ }
+
+ static CImg<T> get_load_png(cimg_std::FILE *const file) {
+ return CImg<T>().load_png(file);
+ }
+
+ // (Note : Most of this function has been written by Eric Fausett)
+ CImg<T>& _load_png(cimg_std::FILE *const file, const char *const filename) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_png() : Cannot load (null) filename.",
+ pixel_type());
+#ifndef cimg_use_png
+ if (file)
+ throw CImgIOException("CImg<%s>::load_png() : File '(FILE*)' cannot be read without using libpng.",
+ pixel_type());
+ else return load_other(filename);
+#else
+ // Open file and check for PNG validity
+ const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
+ cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb");
+
+ unsigned char pngCheck[8];
+ cimg::fread(pngCheck,8,(cimg_std::FILE*)nfile);
+ if (png_sig_cmp(pngCheck,0,8)) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_png() : File '%s' is not a valid PNG file.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+
+ // Setup PNG structures for read
+ png_voidp user_error_ptr = 0;
+ png_error_ptr user_error_fn = 0, user_warning_fn = 0;
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn);
+ if (!png_ptr) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'png_ptr' data structure.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0);
+ throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'info_ptr' data structure.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+ png_infop end_info = png_create_info_struct(png_ptr);
+ if (!end_info) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0);
+ throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'end_info' data structure.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+
+ // Error handling callback for png file reading
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ if (!file) cimg::fclose((cimg_std::FILE*)nfile);
+ png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
+ throw CImgIOException("CImg<%s>::load_png() : File '%s', unknown fatal error.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+ png_init_io(png_ptr, nfile);
+ png_set_sig_bytes(png_ptr, 8);
+
+ // Get PNG Header Info up to data block
+ png_read_info(png_ptr,info_ptr);
+ png_uint_32 W, H;
+ int bit_depth, color_type, interlace_type;
+ png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,int_p_NULL,int_p_NULL);
+ int new_bit_depth = bit_depth;
+ int new_color_type = color_type;
+
+ // Transforms to unify image data
+ if (new_color_type == PNG_COLOR_TYPE_PALETTE){
+ png_set_palette_to_rgb(png_ptr);
+ new_color_type -= PNG_COLOR_MASK_PALETTE;
+ new_bit_depth = 8;
+ }
+ if (new_color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8){
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+ new_bit_depth = 8;
+ }
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
+ png_set_tRNS_to_alpha(png_ptr);
+ if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA){
+ png_set_gray_to_rgb(png_ptr);
+ new_color_type |= PNG_COLOR_MASK_COLOR;
+ }
+ if (new_color_type == PNG_COLOR_TYPE_RGB)
+ png_set_filler(png_ptr, 0xffffU, PNG_FILLER_AFTER);
+ png_read_update_info(png_ptr,info_ptr);
+ if (!(new_bit_depth==8 || new_bit_depth==16)) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
+ throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong bit coding (bit_depth=%u)",
+ pixel_type(),nfilename?nfilename:"(FILE*)",new_bit_depth);
+ }
+ const int byte_depth = new_bit_depth>>3;
+
+ // Allocate Memory for Image Read
+ png_bytep *imgData = new png_bytep[H];
+ for (unsigned int row = 0; row<H; ++row) imgData[row] = new png_byte[byte_depth*4*W];
+ png_read_image(png_ptr,imgData);
+ png_read_end(png_ptr,end_info);
+
+ // Read pixel data
+ if (!(new_color_type==PNG_COLOR_TYPE_RGB || new_color_type==PNG_COLOR_TYPE_RGB_ALPHA)) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0);
+ throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong color coding (new_color_type=%u)",
+ pixel_type(),nfilename?nfilename:"(FILE*)",new_color_type);
+ }
+ const bool no_alpha_channel = (new_color_type==PNG_COLOR_TYPE_RGB);
+ assign(W,H,1,no_alpha_channel?3:4);
+ T *ptr1 = ptr(0,0,0,0), *ptr2 = ptr(0,0,0,1), *ptr3 = ptr(0,0,0,2), *ptr4 = ptr(0,0,0,3);
+ switch (new_bit_depth) {
+ case 8 : {
+ cimg_forY(*this,y){
+ const unsigned char *ptrs = (unsigned char*)imgData[y];
+ cimg_forX(*this,x){
+ *(ptr1++) = (T)*(ptrs++);
+ *(ptr2++) = (T)*(ptrs++);
+ *(ptr3++) = (T)*(ptrs++);
+ if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
+ }
+ }
+ } break;
+ case 16 : {
+ cimg_forY(*this,y){
+ const unsigned short *ptrs = (unsigned short*)(imgData[y]);
+ if (!cimg::endianness()) cimg::invert_endianness(ptrs,4*width);
+ cimg_forX(*this,x){
+ *(ptr1++) = (T)*(ptrs++);
+ *(ptr2++) = (T)*(ptrs++);
+ *(ptr3++) = (T)*(ptrs++);
+ if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
+ }
+ }
+ } break;
+ }
+ png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+
+ // Deallocate Image Read Memory
+ cimg_forY(*this,n) delete[] imgData[n];
+ delete[] imgData;
+ if (!file) cimg::fclose(nfile);
+ return *this;
+#endif
+ }
+
+ //! Load an image from a PNM file.
+ CImg<T>& load_pnm(const char *const filename) {
+ return _load_pnm(0,filename);
+ }
+
+ static CImg<T> get_load_pnm(const char *const filename) {
+ return CImg<T>().load_pnm(filename);
+ }
+
+ //! Load an image from a PNM file.
+ CImg<T>& load_pnm(cimg_std::FILE *const file) {
+ return _load_pnm(file,0);
+ }
+
+ static CImg<T> get_load_pnm(cimg_std::FILE *const file) {
+ return CImg<T>().load_pnm(file);
+ }
+
+ CImg<T>& _load_pnm(cimg_std::FILE *const file, const char *const filename) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_pnm() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ unsigned int ppm_type, W, H, colormax = 255;
+ char item[1024] = { 0 };
+ int err, rval, gval, bval;
+ const int cimg_iobuffer = 12*1024*1024;
+ while ((err=cimg_std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
+ if (cimg_std::sscanf(item," P%u",&ppm_type)!=1) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PNM header 'P?' not found.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
+ if ((err=cimg_std::sscanf(item," %u %u %u",&W,&H,&colormax))<2) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_pnm() : File '%s', WIDTH and HEIGHT fields are not defined in PNM header.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ if (err==2) {
+ while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
+ if (cimg_std::sscanf(item,"%u",&colormax)!=1)
+ cimg::warn("CImg<%s>::load_pnm() : File '%s', COLORMAX field is not defined in PNM header.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ cimg_std::fgetc(nfile);
+ assign();
+
+ switch (ppm_type) {
+ case 2 : { // Grey Ascii
+ assign(W,H,1,1);
+ T* rdata = data;
+ cimg_foroff(*this,off) { if (cimg_std::fscanf(nfile,"%d",&rval)>0) *(rdata++) = (T)rval; else break; }
+ } break;
+ case 3 : { // Color Ascii
+ assign(W,H,1,3);
+ T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
+ cimg_forXY(*this,x,y) {
+ if (cimg_std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { *(rdata++) = (T)rval; *(gdata++) = (T)gval; *(bdata++) = (T)bval; }
+ else break;
+ }
+ } break;
+ case 5 : { // Grey Binary
+ if (colormax<256) { // 8 bits
+ CImg<ucharT> raw;
+ assign(W,H,1,1);
+ T *ptrd = ptr(0,0,0,0);
+ for (int toread = (int)size(); toread>0; ) {
+ raw.assign(cimg::min(toread,cimg_iobuffer));
+ cimg::fread(raw.data,raw.width,nfile);
+ toread-=raw.width;
+ const unsigned char *ptrs = raw.data;
+ for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
+ }
+ } else { // 16 bits
+ CImg<ushortT> raw;
+ assign(W,H,1,1);
+ T *ptrd = ptr(0,0,0,0);
+ for (int toread = (int)size(); toread>0; ) {
+ raw.assign(cimg::min(toread,cimg_iobuffer/2));
+ cimg::fread(raw.data,raw.width,nfile);
+ if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width);
+ toread-=raw.width;
+ const unsigned short *ptrs = raw.data;
+ for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
+ }
+ }
+ } break;
+ case 6 : { // Color Binary
+ if (colormax<256) { // 8 bits
+ CImg<ucharT> raw;
+ assign(W,H,1,3);
+ T
+ *ptr_r = ptr(0,0,0,0),
+ *ptr_g = ptr(0,0,0,1),
+ *ptr_b = ptr(0,0,0,2);
+ for (int toread = (int)size(); toread>0; ) {
+ raw.assign(cimg::min(toread,cimg_iobuffer));
+ cimg::fread(raw.data,raw.width,nfile);
+ toread-=raw.width;
+ const unsigned char *ptrs = raw.data;
+ for (unsigned int off = raw.width/3; off; --off) {
+ *(ptr_r++) = (T)*(ptrs++);
+ *(ptr_g++) = (T)*(ptrs++);
+ *(ptr_b++) = (T)*(ptrs++);
+ }
+ }
+ } else { // 16 bits
+ CImg<ushortT> raw;
+ assign(W,H,1,3);
+ T
+ *ptr_r = ptr(0,0,0,0),
+ *ptr_g = ptr(0,0,0,1),
+ *ptr_b = ptr(0,0,0,2);
+ for (int toread = (int)size(); toread>0; ) {
+ raw.assign(cimg::min(toread,cimg_iobuffer/2));
+ cimg::fread(raw.data,raw.width,nfile);
+ if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width);
+ toread-=raw.width;
+ const unsigned short *ptrs = raw.data;
+ for (unsigned int off = raw.width/3; off; --off) {
+ *(ptr_r++) = (T)*(ptrs++);
+ *(ptr_g++) = (T)*(ptrs++);
+ *(ptr_b++) = (T)*(ptrs++);
+ }
+ }
+ }
+ } break;
+ default :
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PPM type 'P%d' not supported.",
+ pixel_type(),filename?filename:"(FILE*)",ppm_type);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a RGB file.
+ CImg<T>& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgb(0,filename,dimw,dimh);
+ }
+
+ static CImg<T> get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+ return CImg<T>().load_rgb(filename,dimw,dimh);
+ }
+
+ //! Load an image from a RGB file.
+ CImg<T>& load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgb(file,0,dimw,dimh);
+ }
+
+ static CImg<T> get_load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+ return CImg<T>().load_rgb(file,dimw,dimh);
+ }
+
+ CImg<T>& _load_rgb(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_rgb() : Cannot load (null) filename.",
+ pixel_type());
+ if (!dimw || !dimh) return assign();
+ const int cimg_iobuffer = 12*1024*1024;
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ CImg<ucharT> raw;
+ assign(dimw,dimh,1,3);
+ T
+ *ptr_r = ptr(0,0,0,0),
+ *ptr_g = ptr(0,0,0,1),
+ *ptr_b = ptr(0,0,0,2);
+ for (int toread = (int)size(); toread>0; ) {
+ raw.assign(cimg::min(toread,cimg_iobuffer));
+ cimg::fread(raw.data,raw.width,nfile);
+ toread-=raw.width;
+ const unsigned char *ptrs = raw.data;
+ for (unsigned int off = raw.width/3; off; --off) {
+ *(ptr_r++) = (T)*(ptrs++);
+ *(ptr_g++) = (T)*(ptrs++);
+ *(ptr_b++) = (T)*(ptrs++);
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a RGBA file.
+ CImg<T>& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgba(0,filename,dimw,dimh);
+ }
+
+ static CImg<T> get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+ return CImg<T>().load_rgba(filename,dimw,dimh);
+ }
+
+ //! Load an image from a RGBA file.
+ CImg<T>& load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgba(file,0,dimw,dimh);
+ }
+
+ static CImg<T> get_load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+ return CImg<T>().load_rgba(file,dimw,dimh);
+ }
+
+ CImg<T>& _load_rgba(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_rgba() : Cannot load (null) filename.",
+ pixel_type());
+ if (!dimw || !dimh) return assign();
+ const int cimg_iobuffer = 12*1024*1024;
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ CImg<ucharT> raw;
+ assign(dimw,dimh,1,4);
+ T
+ *ptr_r = ptr(0,0,0,0),
+ *ptr_g = ptr(0,0,0,1),
+ *ptr_b = ptr(0,0,0,2),
+ *ptr_a = ptr(0,0,0,3);
+ for (int toread = (int)size(); toread>0; ) {
+ raw.assign(cimg::min(toread,cimg_iobuffer));
+ cimg::fread(raw.data,raw.width,nfile);
+ toread-=raw.width;
+ const unsigned char *ptrs = raw.data;
+ for (unsigned int off = raw.width/4; off; --off) {
+ *(ptr_r++) = (T)*(ptrs++);
+ *(ptr_g++) = (T)*(ptrs++);
+ *(ptr_b++) = (T)*(ptrs++);
+ *(ptr_a++) = (T)*(ptrs++);
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a TIFF file.
+ CImg<T>& load_tiff(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load_tiff() : Cannot load (null) filename.",
+ pixel_type());
+ const unsigned int
+ nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+ nstep_frame = step_frame?step_frame:1;
+ unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
+
+#ifndef cimg_use_tiff
+ if (nfirst_frame || nlast_frame!=~0U || nstep_frame>1)
+ throw CImgArgumentException("CImg<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
+ "('cimg_use_tiff' must be defined).",
+ pixel_type(),filename);
+ return load_other(filename);
+#else
+ TIFF *tif = TIFFOpen(filename,"r");
+ if (tif) {
+ unsigned int nb_images = 0;
+ do ++nb_images; while (TIFFReadDirectory(tif));
+ if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
+ cimg::warn("CImg<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
+ pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
+ if (nfirst_frame>=nb_images) return assign();
+ if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
+ TIFFSetDirectory(tif,0);
+ CImg<T> frame;
+ for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) {
+ frame._load_tiff(tif,l);
+ if (l==nfirst_frame) assign(frame.width,frame.height,1+(nlast_frame-nfirst_frame)/nstep_frame,frame.dim);
+ if (frame.width>width || frame.height>height || frame.dim>dim)
+ resize(cimg::max(frame.width,width),cimg::max(frame.height,height),-100,cimg::max(frame.dim,dim),0);
+ draw_image(0,0,(l-nfirst_frame)/nstep_frame,frame);
+ }
+ TIFFClose(tif);
+ } else throw CImgException("CImg<%s>::load_tiff() : File '%s' cannot be opened.",
+ pixel_type(),filename);
+ return *this;
+#endif
+ }
+
+ static CImg<T> get_load_tiff(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1) {
+ return CImg<T>().load_tiff(filename,first_frame,last_frame,step_frame);
+ }
+
+ // (Original contribution by Jerome Boulanger).
+#ifdef cimg_use_tiff
+ CImg<T>& _load_tiff(TIFF *tif, const unsigned int directory) {
+ if (!TIFFSetDirectory(tif,directory)) return assign();
+ uint16 samplesperpixel, bitspersample;
+ uint32 nx,ny;
+ const char *const filename = TIFFFileName(tif);
+ TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx);
+ TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny);
+ TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel);
+ if (samplesperpixel!=1 && samplesperpixel!=3 && samplesperpixel!=4) {
+ cimg::warn("CImg<%s>::load_tiff() : File '%s', unknow value for tag : TIFFTAG_SAMPLESPERPIXEL, will force it to 1.",
+ pixel_type(),filename);
+ samplesperpixel = 1;
+ }
+ TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample);
+ assign(nx,ny,1,samplesperpixel);
+ if (bitspersample!=8 || !(samplesperpixel==3 || samplesperpixel==4)) {
+ uint16 photo, config;
+ TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config);
+ TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo);
+ if (TIFFIsTiled(tif)) {
+ uint32 tw, th;
+ TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw);
+ TIFFGetField(tif,TIFFTAG_TILELENGTH,&th);
+ if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
+ case 8 : {
+ unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ pixel_type(),filename);
+ } else {
+ unsigned char *ptr = buf;
+ for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
+ }
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 16 : {
+ unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ pixel_type(),filename);
+ } else {
+ unsigned short *ptr = buf;
+ for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
+ }
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 32 : {
+ float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ pixel_type(),filename);
+ } else {
+ float *ptr = buf;
+ for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
+ }
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ } else switch (bitspersample) {
+ case 8 : {
+ unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ pixel_type(),filename);
+ } else {
+ unsigned char *ptr = buf;
+ for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+ (*this)(cc,rr,vv) = (T)(float)*(ptr++);
+ }
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 16 : {
+ unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ pixel_type(),filename);
+ } else {
+ unsigned short *ptr = buf;
+ for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+ (*this)(cc,rr,vv) = (T)(float)*(ptr++);
+ }
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 32 : {
+ float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ pixel_type(),filename);
+ } else {
+ float *ptr = buf;
+ for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+ (*this)(cc,rr,vv) = (T)(float)*(ptr++);
+ }
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ }
+ } else {
+ if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
+ case 8 : {
+ unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, 0);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
+ pixel_type(),filename);
+ }
+ unsigned char *ptr = buf;
+ for (unsigned int rr = 0; rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<nx; ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 16 : {
+ unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, 0);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
+ pixel_type(),filename);
+ }
+ unsigned short *ptr = buf;
+ for (unsigned int rr = 0; rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<nx; ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 32 : {
+ float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, 0);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
+ pixel_type(),filename);
+ }
+ float *ptr = buf;
+ for (unsigned int rr = 0; rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<nx; ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ } else switch (bitspersample){
+ case 8 : {
+ unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (unsigned int vv=0; vv<samplesperpixel; ++vv)
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, vv);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
+ pixel_type(),filename);
+ }
+ unsigned char *ptr = buf;
+ for (unsigned int rr = 0;rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<nx; ++cc)
+ (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 16 : {
+ unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, vv);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
+ pixel_type(),filename);
+ }
+ unsigned short *ptr = buf;
+ for (unsigned int rr = 0; rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<nx; ++cc)
+ (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ case 32 : {
+ float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, vv);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
+ pixel_type(),filename);
+ }
+ float *ptr = buf;
+ for (unsigned int rr = 0; rr<nrow; ++rr) for (unsigned int cc = 0; cc<nx; ++cc)
+ (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ } break;
+ }
+ }
+ } else {
+ uint32* raster = (uint32*)_TIFFmalloc(nx * ny * sizeof (uint32));
+ if (!raster) {
+ _TIFFfree(raster); TIFFClose(tif);
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', not enough memory for buffer allocation.",
+ pixel_type(),filename);
+ }
+ TIFFReadRGBAImage(tif,nx,ny,raster,0);
+ switch (samplesperpixel) {
+ case 1 : {
+ cimg_forXY(*this,x,y) (*this)(x,y) = (T)(float)((raster[nx*(ny-1-y)+x]+ 128) / 257);
+ } break;
+ case 3 : {
+ cimg_forXY(*this,x,y) {
+ (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
+ (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
+ (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
+ }
+ } break;
+ case 4 : {
+ cimg_forXY(*this,x,y) {
+ (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
+ (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
+ (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
+ (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny-1-y)+x]);
+ }
+ } break;
+ }
+ _TIFFfree(raster);
+ }
+ return *this;
+ }
+#endif
+
+ //! Load an image from an ANALYZE7.5/NIFTI file.
+ CImg<T>& load_analyze(const char *const filename, float *const voxsize=0) {
+ return _load_analyze(0,filename,voxsize);
+ }
+
+ static CImg<T> get_load_analyze(const char *const filename, float *const voxsize=0) {
+ return CImg<T>().load_analyze(filename,voxsize);
+ }
+
+ //! Load an image from an ANALYZE7.5/NIFTI file.
+ CImg<T>& load_analyze(cimg_std::FILE *const file, float *const voxsize=0) {
+ return _load_analyze(file,0,voxsize);
+ }
+
+ static CImg<T> get_load_analyze(cimg_std::FILE *const file, float *const voxsize=0) {
+ return CImg<T>().load_analyze(file,voxsize);
+ }
+
+ CImg<T>& _load_analyze(cimg_std::FILE *const file, const char *const filename, float *const voxsize=0) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_analyze() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *nfile_header = 0, *nfile = 0;
+ if (!file) {
+ char body[1024];
+ const char *ext = cimg::split_filename(filename,body);
+ if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file.
+ nfile_header = cimg::fopen(filename,"rb");
+ cimg_std::sprintf(body+cimg::strlen(body),".img");
+ nfile = cimg::fopen(body,"rb");
+ } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file.
+ nfile = cimg::fopen(filename,"rb");
+ cimg_std::sprintf(body+cimg::strlen(body),".hdr");
+ nfile_header = cimg::fopen(body,"rb");
+ } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file.
+ } else nfile_header = nfile = file; // File is a Niftii file.
+ if (!nfile || !nfile_header)
+ throw CImgIOException("CImg<%s>::load_analyze() : File '%s', not recognized as an Analyze7.5 or NIFTI file.",
+ pixel_type(),filename?filename:"(FILE*)");
+
+ // Read header.
+ bool endian = false;
+ unsigned int header_size;
+ cimg::fread(&header_size,1,nfile_header);
+ if (!header_size)
+ throw CImgIOException("CImg<%s>::load_analyze() : File '%s', zero-sized header found.",
+ pixel_type(),filename?filename:"(FILE*)");
+ if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); }
+ unsigned char *header = new unsigned char[header_size];
+ cimg::fread(header+4,header_size-4,nfile_header);
+ if (!file && nfile_header!=nfile) cimg::fclose(nfile_header);
+ if (endian) {
+ cimg::invert_endianness((short*)(header+40),5);
+ cimg::invert_endianness((short*)(header+70),1);
+ cimg::invert_endianness((short*)(header+72),1);
+ cimg::invert_endianness((float*)(header+76),4);
+ cimg::invert_endianness((float*)(header+112),1);
+ }
+ unsigned short *dim = (unsigned short*)(header+40), dimx = 1, dimy = 1, dimz = 1, dimv = 1;
+ if (!dim[0])
+ cimg::warn("CImg<%s>::load_analyze() : File '%s', tells that image has zero dimensions.",
+ pixel_type(),filename?filename:"(FILE*)");
+ if (dim[0]>4)
+ cimg::warn("CImg<%s>::load_analyze() : File '%s', number of image dimension is %u, reading only the 4 first dimensions",
+ pixel_type(),filename?filename:"(FILE*)",dim[0]);
+ if (dim[0]>=1) dimx = dim[1];
+ if (dim[0]>=2) dimy = dim[2];
+ if (dim[0]>=3) dimz = dim[3];
+ if (dim[0]>=4) dimv = dim[4];
+ float scalefactor = *(float*)(header+112); if (scalefactor==0) scalefactor=1;
+ const unsigned short datatype = *(short*)(header+70);
+ if (voxsize) {
+ const float *vsize = (float*)(header+76);
+ voxsize[0] = vsize[1]; voxsize[1] = vsize[2]; voxsize[2] = vsize[3];
+ }
+ delete[] header;
+
+ // Read pixel data.
+ assign(dimx,dimy,dimz,dimv);
+ switch (datatype) {
+ case 2 : {
+ unsigned char *buffer = new unsigned char[dimx*dimy*dimz*dimv];
+ cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+ cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 4 : {
+ short *buffer = new short[dimx*dimy*dimz*dimv];
+ cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+ if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+ cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 8 : {
+ int *buffer = new int[dimx*dimy*dimz*dimv];
+ cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+ if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+ cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 16 : {
+ float *buffer = new float[dimx*dimy*dimz*dimv];
+ cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+ if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+ cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 64 : {
+ double *buffer = new double[dimx*dimy*dimz*dimv];
+ cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+ if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+ cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ default :
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_analyze() : File '%s', cannot read images with 'datatype = %d'",
+ pixel_type(),filename?filename:"(FILE*)",datatype);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image (list) from a .cimg file.
+ CImg<T>& load_cimg(const char *const filename, const char axis='z', const char align='p') {
+ CImgList<T> list;
+ list.load_cimg(filename);
+ if (list.size==1) return list[0].transfer_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ static CImg<T> get_load_cimg(const char *const filename, const char axis='z', const char align='p') {
+ return CImg<T>().load_cimg(filename,axis,align);
+ }
+
+ //! Load an image (list) from a .cimg file.
+ CImg<T>& load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') {
+ CImgList<T> list;
+ list.load_cimg(file);
+ if (list.size==1) return list[0].transfer_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ static CImg<T> get_load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') {
+ return CImg<T>().load_cimg(file,axis,align);
+ }
+
+ //! Load a sub-image (list) from a .cimg file.
+ CImg<T>& load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
+ const char axis='z', const char align='p') {
+ CImgList<T> list;
+ list.load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
+ if (list.size==1) return list[0].transfer_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ static CImg<T> get_load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
+ const char axis='z', const char align='p') {
+ return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
+ }
+
+ //! Load a sub-image (list) from a non-compressed .cimg file.
+ CImg<T>& load_cimg(cimg_std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
+ const char axis='z', const char align='p') {
+ CImgList<T> list;
+ list.load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
+ if (list.size==1) return list[0].transfer_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ static CImg<T> get_load_cimg(cimg_std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
+ const char axis='z', const char align='p') {
+ return CImg<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
+ }
+
+ //! Load an image from an INRIMAGE-4 file.
+ CImg<T>& load_inr(const char *const filename, float *const voxsize=0) {
+ return _load_inr(0,filename,voxsize);
+ }
+
+ static CImg<T> get_load_inr(const char *const filename, float *const voxsize=0) {
+ return CImg<T>().load_inr(filename,voxsize);
+ }
+
+ //! Load an image from an INRIMAGE-4 file.
+ CImg<T>& load_inr(cimg_std::FILE *const file, float *const voxsize=0) {
+ return _load_inr(file,0,voxsize);
+ }
+
+ static CImg<T> get_load_inr(cimg_std::FILE *const file, float *voxsize=0) {
+ return CImg<T>().load_inr(file,voxsize);
+ }
+
+ // Load an image from an INRIMAGE-4 file (internal).
+ static void _load_inr_header(cimg_std::FILE *file, int out[8], float *const voxsize) {
+ char item[1024], tmp1[64], tmp2[64];
+ out[0] = cimg_std::fscanf(file,"%63s",item);
+ out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1;
+ if(cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0)
+ throw CImgIOException("CImg<%s>::load_inr() : File does not appear to be a valid INR file.\n"
+ "(INRIMAGE-4 identifier not found)",
+ pixel_type());
+ while (cimg_std::fscanf(file," %63[^\n]%*c",item)!=EOF && cimg::strncmp(item,"##}",3)) {
+ cimg_std::sscanf(item," XDIM%*[^0-9]%d",out);
+ cimg_std::sscanf(item," YDIM%*[^0-9]%d",out+1);
+ cimg_std::sscanf(item," ZDIM%*[^0-9]%d",out+2);
+ cimg_std::sscanf(item," VDIM%*[^0-9]%d",out+3);
+ cimg_std::sscanf(item," PIXSIZE%*[^0-9]%d",out+6);
+ if (voxsize) {
+ cimg_std::sscanf(item," VX%*[^0-9.+-]%f",voxsize);
+ cimg_std::sscanf(item," VY%*[^0-9.+-]%f",voxsize+1);
+ cimg_std::sscanf(item," VZ%*[^0-9.+-]%f",voxsize+2);
+ }
+ if (cimg_std::sscanf(item," CPU%*[ =]%s",tmp1)) out[7]=cimg::strncasecmp(tmp1,"sun",3)?0:1;
+ switch (cimg_std::sscanf(item," TYPE%*[ =]%s %s",tmp1,tmp2)) {
+ case 0 : break;
+ case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; cimg_std::strcpy(tmp1,tmp2);
+ case 1 :
+ if (!cimg::strncasecmp(tmp1,"int",3) || !cimg::strncasecmp(tmp1,"fixed",5)) out[4] = 0;
+ if (!cimg::strncasecmp(tmp1,"float",5) || !cimg::strncasecmp(tmp1,"double",6)) out[4] = 1;
+ if (!cimg::strncasecmp(tmp1,"packed",6)) out[4] = 2;
+ if (out[4]>=0) break;
+ default :
+ throw CImgIOException("cimg::inr_header_read() : Invalid TYPE '%s'",tmp2);
+ }
+ }
+ if(out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0)
+ throw CImgIOException("CImg<%s>::load_inr() : Bad dimensions in .inr file = ( %d , %d , %d , %d )",
+ pixel_type(),out[0],out[1],out[2],out[3]);
+ if(out[4]<0 || out[5]<0)
+ throw CImgIOException("CImg<%s>::load_inr() : TYPE is not fully defined",
+ pixel_type());
+ if(out[6]<0)
+ throw CImgIOException("CImg<%s>::load_inr() : PIXSIZE is not fully defined",
+ pixel_type());
+ if(out[7]<0)
+ throw CImgIOException("CImg<%s>::load_inr() : Big/Little Endian coding type is not defined",
+ pixel_type());
+ }
+
+ CImg<T>& _load_inr(cimg_std::FILE *const file, const char *const filename, float *const voxsize) {
+#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \
+ if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \
+ Ts *xval, *val = new Ts[fopt[0]*fopt[3]]; \
+ cimg_forYZ(*this,y,z) { \
+ cimg::fread(val,fopt[0]*fopt[3],nfile); \
+ if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \
+ xval = val; cimg_forX(*this,x) cimg_forV(*this,k) (*this)(x,y,z,k) = (T)*(xval++); \
+ } \
+ delete[] val; \
+ loaded = true; \
+ }
+
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_inr() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ int fopt[8], endian=cimg::endianness()?1:0;
+ bool loaded = false;
+ if (voxsize) voxsize[0]=voxsize[1]=voxsize[2]=1;
+ _load_inr_header(nfile,fopt,voxsize);
+ assign(fopt[0],fopt[1],fopt[2],fopt[3]);
+ _cimg_load_inr_case(0,0,8, unsigned char);
+ _cimg_load_inr_case(0,1,8, char);
+ _cimg_load_inr_case(0,0,16,unsigned short);
+ _cimg_load_inr_case(0,1,16,short);
+ _cimg_load_inr_case(0,0,32,unsigned int);
+ _cimg_load_inr_case(0,1,32,int);
+ _cimg_load_inr_case(1,0,32,float);
+ _cimg_load_inr_case(1,1,32,float);
+ _cimg_load_inr_case(1,0,64,double);
+ _cimg_load_inr_case(1,1,64,double);
+ if (!loaded) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_inr() : File '%s', cannot read images of the type specified in the file",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a PANDORE file.
+ CImg<T>& load_pandore(const char *const filename) {
+ return _load_pandore(0,filename);
+ }
+
+ static CImg<T> get_load_pandore(const char *const filename) {
+ return CImg<T>().load_pandore(filename);
+ }
+
+ //! Load an image from a PANDORE file.
+ CImg<T>& load_pandore(cimg_std::FILE *const file) {
+ return _load_pandore(file,0);
+ }
+
+ static CImg<T> get_load_pandore(cimg_std::FILE *const file) {
+ return CImg<T>().load_pandore(file);
+ }
+
+ CImg<T>& _load_pandore(cimg_std::FILE *const file, const char *const filename) {
+#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \
+ cimg::fread(dims,nbdim,nfile); \
+ if (endian) cimg::invert_endianness(dims,nbdim); \
+ assign(nwidth,nheight,ndepth,ndim); \
+ const unsigned int siz = size(); \
+ stype *buffer = new stype[siz]; \
+ cimg::fread(buffer,siz,nfile); \
+ if (endian) cimg::invert_endianness(buffer,siz); \
+ T *ptrd = data; \
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \
+ buffer-=siz; \
+ delete[] buffer
+
+#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \
+ if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \
+ else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \
+ else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \
+ else throw CImgIOException("CImg<%s>::load_pandore() : File '%s' cannot be read, datatype not supported on this architecture.", \
+ pixel_type(),filename?filename:"(FILE*)"); }
+
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_pandore() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ typedef unsigned char uchar;
+ typedef unsigned short ushort;
+ typedef unsigned int uint;
+ typedef unsigned long ulong;
+ char header[32];
+ cimg::fread(header,12,nfile);
+ if (cimg::strncasecmp("PANDORE",header,7)) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_pandore() : File '%s' is not a valid PANDORE file, "
+ "(PANDORE identifier not found).",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ unsigned int imageid, dims[8];
+ cimg::fread(&imageid,1,nfile);
+ const bool endian = (imageid>255);
+ if (endian) cimg::invert_endianness(imageid);
+ cimg::fread(header,20,nfile);
+
+ switch (imageid) {
+ case 2: _cimg_load_pandore_case(2,dims[1],1,1,1,uchar,uchar,uchar,1); break;
+ case 3: _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break;
+ case 4: _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break;
+ case 5: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,uchar,uchar,uchar,1); break;
+ case 6: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break;
+ case 7: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break;
+ case 8: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,uchar,uchar,uchar,1); break;
+ case 9: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break;
+ case 10: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break;
+ case 11 : { // Region 1D
+ cimg::fread(dims,3,nfile);
+ if (endian) cimg::invert_endianness(dims,3);
+ assign(dims[1],1,1,1);
+ const unsigned siz = size();
+ if (dims[2]<256) {
+ unsigned char *buffer = new unsigned char[siz];
+ cimg::fread(buffer,siz,nfile);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ } else {
+ if (dims[2]<65536) {
+ unsigned short *buffer = new unsigned short[siz];
+ cimg::fread(buffer,siz,nfile);
+ if (endian) cimg::invert_endianness(buffer,siz);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ } else {
+ unsigned int *buffer = new unsigned int[siz];
+ cimg::fread(buffer,siz,nfile);
+ if (endian) cimg::invert_endianness(buffer,siz);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ }
+ }
+ }
+ break;
+ case 12 : { // Region 2D
+ cimg::fread(dims,4,nfile);
+ if (endian) cimg::invert_endianness(dims,4);
+ assign(dims[2],dims[1],1,1);
+ const unsigned int siz = size();
+ if (dims[3]<256) {
+ unsigned char *buffer = new unsigned char[siz];
+ cimg::fread(buffer,siz,nfile);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ } else {
+ if (dims[3]<65536) {
+ unsigned short *buffer = new unsigned short[siz];
+ cimg::fread(buffer,siz,nfile);
+ if (endian) cimg::invert_endianness(buffer,siz);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ } else {
+ unsigned long *buffer = new unsigned long[siz];
+ cimg::fread(buffer,siz,nfile);
+ if (endian) cimg::invert_endianness(buffer,siz);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ }
+ }
+ }
+ break;
+ case 13 : { // Region 3D
+ cimg::fread(dims,5,nfile);
+ if (endian) cimg::invert_endianness(dims,5);
+ assign(dims[3],dims[2],dims[1],1);
+ const unsigned int siz = size();
+ if (dims[4]<256) {
+ unsigned char *buffer = new unsigned char[siz];
+ cimg::fread(buffer,siz,nfile);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ } else {
+ if (dims[4]<65536) {
+ unsigned short *buffer = new unsigned short[siz];
+ cimg::fread(buffer,siz,nfile);
+ if (endian) cimg::invert_endianness(buffer,siz);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ } else {
+ unsigned int *buffer = new unsigned int[siz];
+ cimg::fread(buffer,siz,nfile);
+ if (endian) cimg::invert_endianness(buffer,siz);
+ T *ptrd = data;
+ cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+ buffer-=siz;
+ delete[] buffer;
+ }
+ }
+ }
+ break;
+ case 16: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,uchar,uchar,uchar,1); break;
+ case 17: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break;
+ case 18: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break;
+ case 19: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,uchar,uchar,uchar,1); break;
+ case 20: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break;
+ case 21: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break;
+ case 22: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],uchar,uchar,uchar,1); break;
+ case 23: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4);
+ case 24: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],ulong,uint,ushort,4); break;
+ case 25: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break;
+ case 26: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],uchar,uchar,uchar,1); break;
+ case 27: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break;
+ case 28: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],ulong,uint,ushort,4); break;
+ case 29: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break;
+ case 30: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],uchar,uchar,uchar,1); break;
+ case 31: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break;
+ case 32: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],ulong,uint,ushort,4); break;
+ case 33: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break;
+ case 34 : { // Points 1D
+ int ptbuf[4];
+ cimg::fread(ptbuf,1,nfile);
+ if (endian) cimg::invert_endianness(ptbuf,1);
+ assign(1); (*this)(0) = (T)ptbuf[0];
+ } break;
+ case 35 : { // Points 2D
+ int ptbuf[4];
+ cimg::fread(ptbuf,2,nfile);
+ if (endian) cimg::invert_endianness(ptbuf,2);
+ assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0];
+ } break;
+ case 36 : { // Points 3D
+ int ptbuf[4];
+ cimg::fread(ptbuf,3,nfile);
+ if (endian) cimg::invert_endianness(ptbuf,3);
+ assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0];
+ } break;
+ default :
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_pandore() : File '%s', cannot read images with ID_type = %u",
+ pixel_type(),filename?filename:"(FILE*)",imageid);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a PAR-REC (Philips) file.
+ CImg<T>& load_parrec(const char *const filename, const char axis='v', const char align='p') {
+ CImgList<T> list;
+ list.load_parrec(filename);
+ if (list.size==1) return list[0].transfer_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ static CImg<T> get_load_parrec(const char *const filename, const char axis='v', const char align='p') {
+ return CImg<T>().load_parrec(filename,axis,align);
+ }
+
+ //! Load an image from a .RAW file.
+ CImg<T>& load_raw(const char *const filename,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int sizez=1, const unsigned int sizev=1,
+ const bool multiplexed=false, const bool invert_endianness=false) {
+ return _load_raw(0,filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+ }
+
+ static CImg<T> get_load_raw(const char *const filename,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int sizez=1, const unsigned int sizev=1,
+ const bool multiplexed=false, const bool invert_endianness=false) {
+ return CImg<T>().load_raw(filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+ }
+
+ //! Load an image from a .RAW file.
+ CImg<T>& load_raw(cimg_std::FILE *const file,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int sizez=1, const unsigned int sizev=1,
+ const bool multiplexed=false, const bool invert_endianness=false) {
+ return _load_raw(file,0,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+ }
+
+ static CImg<T> get_load_raw(cimg_std::FILE *const file,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int sizez=1, const unsigned int sizev=1,
+ const bool multiplexed=false, const bool invert_endianness=false) {
+ return CImg<T>().load_raw(file,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+ }
+
+ CImg<T>& _load_raw(cimg_std::FILE *const file, const char *const filename,
+ const unsigned int sizex, const unsigned int sizey,
+ const unsigned int sizez, const unsigned int sizev,
+ const bool multiplexed, const bool invert_endianness) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_raw() : Cannot load (null) filename.",
+ pixel_type());
+ assign(sizex,sizey,sizez,sizev,0);
+ const unsigned int siz = size();
+ if (siz) {
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ if (!multiplexed) {
+ cimg::fread(data,siz,nfile);
+ if (invert_endianness) cimg::invert_endianness(data,siz);
+ }
+ else {
+ CImg<T> buf(1,1,1,sizev);
+ cimg_forXYZ(*this,x,y,z) {
+ cimg::fread(buf.data,sizev,nfile);
+ if (invert_endianness) cimg::invert_endianness(buf.data,sizev);
+ set_vector_at(buf,x,y,z); }
+ }
+ if (!file) cimg::fclose(nfile);
+ }
+ return *this;
+ }
+
+ //! Load a video sequence using FFMPEG av's libraries.
+ CImg<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
+ const char axis='z', const char align='p') {
+ return get_load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume,axis,align).transfer_to(*this);
+ }
+
+ static CImg<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
+ const char axis='z', const char align='p') {
+ return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume).get_append(axis,align);
+ }
+
+ //! Load an image sequence from a YUV file.
+ CImg<T>& load_yuv(const char *const filename,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+ return get_load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
+ }
+
+ static CImg<T> get_load_yuv(const char *const filename,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+ return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
+ }
+
+ //! Load an image sequence from a YUV file.
+ CImg<T>& load_yuv(cimg_std::FILE *const file,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+ return get_load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
+ }
+
+ static CImg<T> get_load_yuv(cimg_std::FILE *const file,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+ return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
+ }
+
+ //! Load a 3D object from a .OFF file.
+ template<typename tf, typename tc>
+ CImg<T>& load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces=false) {
+ return _load_off(0,filename,primitives,colors,invert_faces);
+ }
+
+ template<typename tf, typename tc>
+ static CImg<T> get_load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors,
+ const bool invert_faces=false) {
+ return CImg<T>().load_off(filename,primitives,colors,invert_faces);
+ }
+
+ //! Load a 3D object from a .OFF file.
+ template<typename tf, typename tc>
+ CImg<T>& load_off(cimg_std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces=false) {
+ return _load_off(file,0,primitives,colors,invert_faces);
+ }
+
+ template<typename tf, typename tc>
+ static CImg<T> get_load_off(cimg_std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors,
+ const bool invert_faces=false) {
+ return CImg<T>().load_off(file,primitives,colors,invert_faces);
+ }
+
+ template<typename tf, typename tc>
+ CImg<T>& _load_off(cimg_std::FILE *const file, const char *const filename,
+ CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImg<%s>::load_off() : Cannot load (null) filename.",
+ pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
+ unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0;
+ char line[256] = { 0 };
+ int err;
+
+ // Skip comments, and read magic string OFF
+ do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
+ if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_off() : File '%s', keyword 'OFF' not found.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
+ if ((err = cimg_std::sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_off() : File '%s', invalid vertices/primitives numbers.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+
+ // Read points data
+ assign(nb_points,3);
+ float X = 0, Y = 0, Z = 0;
+ cimg_forX(*this,l) {
+ do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
+ if ((err = cimg_std::sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::load_off() : File '%s', cannot read point %u/%u.\n",
+ pixel_type(),filename?filename:"(FILE*)",l+1,nb_points);
+ }
+ (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z;
+ }
+
+ // Read primitive data
+ primitives.assign();
+ colors.assign();
+ bool stopflag = false;
+ while (!stopflag) {
+ float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f;
+ unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0;
+ line[0]='\0';
+ if ((err = cimg_std::fscanf(nfile,"%u",&prim))!=1) stopflag=true;
+ else {
+ ++nb_read;
+ switch (prim) {
+ case 1 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%255[^\n] ",&i0,line))<2) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ primitives.insert(CImg<tf>::vector(i0));
+ colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+ }
+ } break;
+ case 2 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line))<2) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ primitives.insert(CImg<tf>::vector(i0,i1));
+ colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+ }
+ } break;
+ case 3 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line))<3) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
+ else primitives.insert(CImg<tf>::vector(i0,i2,i1));
+ colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+ }
+ } break;
+ case 4 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line))<4) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
+ else primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
+ colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+ }
+ } break;
+ case 5 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line))<5) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ if (invert_faces) {
+ primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
+ primitives.insert(CImg<tf>::vector(i0,i3,i4));
+ }
+ else {
+ primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
+ primitives.insert(CImg<tf>::vector(i0,i4,i3));
+ }
+ colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+ ++nb_primitives;
+ }
+ } break;
+ case 6 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line))<6) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ if (invert_faces) {
+ primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
+ primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
+ }
+ else {
+ primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
+ primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
+ }
+ colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+ ++nb_primitives;
+ }
+ } break;
+ case 7 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line))<7) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ if (invert_faces) {
+ primitives.insert(CImg<tf>::vector(i0,i1,i3,i4));
+ primitives.insert(CImg<tf>::vector(i0,i4,i5,i6));
+ primitives.insert(CImg<tf>::vector(i1,i2,i3));
+ }
+ else {
+ primitives.insert(CImg<tf>::vector(i0,i4,i3,i1));
+ primitives.insert(CImg<tf>::vector(i0,i6,i5,i4));
+ primitives.insert(CImg<tf>::vector(i3,i2,i1));
+ }
+ colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+ ++(++nb_primitives);
+ }
+ } break;
+ case 8 : {
+ if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line))<7) {
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ } else {
+ err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ if (invert_faces) {
+ primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
+ primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
+ primitives.insert(CImg<tf>::vector(i0,i5,i6,i7));
+ }
+ else {
+ primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
+ primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
+ primitives.insert(CImg<tf>::vector(i0,i7,i6,i5));
+ }
+ colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+ ++(++nb_primitives);
+ }
+ } break;
+ default :
+ cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u (%u vertices).",
+ pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives,prim);
+ err = cimg_std::fscanf(nfile,"%*[^\n] ");
+ }
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ if (primitives.size!=nb_primitives)
+ cimg::warn("CImg<%s>::load_off() : File '%s', read only %u primitives instead of %u as claimed in the header.",
+ pixel_type(),filename?filename:"(FILE*)",primitives.size,nb_primitives);
+ return *this;
+ }
+
+ //! Load a video sequence using FFMPEG's external tool 'ffmpeg'.
+ CImg<T>& load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
+ return get_load_ffmpeg_external(filename,axis,align).transfer_to(*this);
+ }
+
+ static CImg<T> get_load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
+ return CImgList<T>().load_ffmpeg_external(filename).get_append(axis,align);
+ }
+
+ //! Load an image using GraphicsMagick's external tool 'gm'.
+ CImg<T>& load_graphicsmagick_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load_graphicsmagick_external() : Cannot load (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512];
+ cimg_std::FILE *file = 0;
+#if cimg_OS==1
+ cimg_std::sprintf(command,"%s convert \"%s\" ppm:-",cimg::graphicsmagick_path(),filename);
+ file = popen(command,"r");
+ if (file) { load_pnm(file); pclose(file); return *this; }
+#endif
+ do {
+ cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ cimg_std::sprintf(command,"%s convert \"%s\" %s",cimg::graphicsmagick_path(),filename,filetmp);
+ cimg::system(command,cimg::graphicsmagick_path());
+ if (!(file = cimg_std::fopen(filetmp,"rb"))) {
+ cimg::fclose(cimg::fopen(filename,"r"));
+ throw CImgIOException("CImg<%s>::load_graphicsmagick_external() : Failed to open image '%s'.\n\n"
+ "Path of 'GraphicsMagick's gm' : \"%s\"\n"
+ "Path of temporary filename : \"%s\"",
+ pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
+ } else cimg::fclose(file);
+ load_pnm(filetmp);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ static CImg<T> get_load_graphicsmagick_external(const char *const filename) {
+ return CImg<T>().load_graphicsmagick_external(filename);
+ }
+
+ //! Load a gzipped image file, using external tool 'gunzip'.
+ CImg<T>& load_gzip_external(const char *const filename) {
+ if (!filename)
+ throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512], body[512];
+ const char
+ *ext = cimg::split_filename(filename,body),
+ *ext2 = cimg::split_filename(body,0);
+ cimg_std::FILE *file = 0;
+ do {
+ if (!cimg::strcasecmp(ext,"gz")) {
+ if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext2);
+ else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ } else {
+ if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext);
+ else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ }
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
+ cimg::system(command);
+ if (!(file = cimg_std::fopen(filetmp,"rb"))) {
+ cimg::fclose(cimg::fopen(filename,"r"));
+ throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.",
+ pixel_type(),filename);
+ } else cimg::fclose(file);
+ load(filetmp);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ static CImg<T> get_load_gzip_external(const char *const filename) {
+ return CImg<T>().load_gzip_external(filename);
+ }
+
+ //! Load an image using ImageMagick's external tool 'convert'.
+ CImg<T>& load_imagemagick_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load_imagemagick_external() : Cannot load (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512];
+ cimg_std::FILE *file = 0;
+#if cimg_OS==1
+ cimg_std::sprintf(command,"%s \"%s\" ppm:-",cimg::imagemagick_path(),filename);
+ file = popen(command,"r");
+ if (file) { load_pnm(file); pclose(file); return *this; }
+#endif
+ do {
+ cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ cimg_std::sprintf(command,"%s \"%s\" %s",cimg::imagemagick_path(),filename,filetmp);
+ cimg::system(command,cimg::imagemagick_path());
+ if (!(file = cimg_std::fopen(filetmp,"rb"))) {
+ cimg::fclose(cimg::fopen(filename,"r"));
+ throw CImgIOException("CImg<%s>::load_imagemagick_external() : Failed to open image '%s'.\n\n"
+ "Path of 'ImageMagick's convert' : \"%s\"\n"
+ "Path of temporary filename : \"%s\"",
+ pixel_type(),filename,cimg::imagemagick_path(),filetmp);
+ } else cimg::fclose(file);
+ load_pnm(filetmp);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ static CImg<T> get_load_imagemagick_external(const char *const filename) {
+ return CImg<T>().load_imagemagick_external(filename);
+ }
+
+ //! Load a DICOM image file, using XMedcon's external tool 'medcon'.
+ CImg<T>& load_medcon_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load_medcon_external() : Cannot load (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512], body[512];
+ cimg::fclose(cimg::fopen(filename,"r"));
+ cimg_std::FILE *file = 0;
+ do {
+ cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ cimg_std::sprintf(command,"%s -w -c anlz -o %s -f %s",cimg::medcon_path(),filetmp,filename);
+ cimg::system(command);
+ cimg::split_filename(filetmp,body);
+ cimg_std::sprintf(command,"m000-%s.hdr",body);
+ file = cimg_std::fopen(command,"rb");
+ if (!file) {
+ throw CImgIOException("CImg<%s>::load_medcon_external() : Failed to open image '%s'.\n\n"
+ "Path of 'medcon' : \"%s\"\n"
+ "Path of temporary filename : \"%s\"",
+ pixel_type(),filename,cimg::medcon_path(),filetmp);
+ } else cimg::fclose(file);
+ load_analyze(command);
+ cimg_std::remove(command);
+ cimg_std::sprintf(command,"m000-%s.img",body);
+ cimg_std::remove(command);
+ return *this;
+ }
+
+ static CImg<T> get_load_medcon_external(const char *const filename) {
+ return CImg<T>().load_medcon_external(filename);
+ }
+
+ //! Load a RAW Color Camera image file, using external tool 'dcraw'.
+ CImg<T>& load_dcraw_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load_dcraw_external() : Cannot load (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512];
+ cimg_std::FILE *file = 0;
+#if cimg_OS==1
+ cimg_std::sprintf(command,"%s -4 -c \"%s\"",cimg::dcraw_path(),filename);
+ file = popen(command,"r");
+ if (file) { load_pnm(file); pclose(file); return *this; }
+#endif
+ do {
+ cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ cimg_std::sprintf(command,"%s -4 -c \"%s\" > %s",cimg::dcraw_path(),filename,filetmp);
+ cimg::system(command,cimg::dcraw_path());
+ if (!(file = cimg_std::fopen(filetmp,"rb"))) {
+ cimg::fclose(cimg::fopen(filename,"r"));
+ throw CImgIOException("CImg<%s>::load_dcraw_external() : Failed to open image '%s'.\n\n"
+ "Path of 'dcraw' : \"%s\"\n"
+ "Path of temporary filename : \"%s\"",
+ pixel_type(),filename,cimg::dcraw_path(),filetmp);
+ } else cimg::fclose(file);
+ load_pnm(filetmp);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ static CImg<T> get_load_dcraw_external(const char *const filename) {
+ return CImg<T>().load_dcraw_external(filename);
+ }
+
+ //! Load an image using ImageMagick's or GraphicsMagick's executables.
+ CImg<T>& load_other(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::load_other() : Cannot load (null) filename.",
+ pixel_type());
+ const unsigned int odebug = cimg::exception_mode();
+ cimg::exception_mode() = 0;
+ try { load_magick(filename); }
+ catch (CImgException&) {
+ try { load_imagemagick_external(filename); }
+ catch (CImgException&) {
+ try { load_graphicsmagick_external(filename); }
+ catch (CImgException&) {
+ assign();
+ }
+ }
+ }
+ cimg::exception_mode() = odebug;
+ if (is_empty())
+ throw CImgIOException("CImg<%s>::load_other() : File '%s' cannot be opened.",
+ pixel_type(),filename);
+ return *this;
+ }
+
+ static CImg<T> get_load_other(const char *const filename) {
+ return CImg<T>().load_other(filename);
+ }
+
+ //@}
+ //---------------------------
+ //
+ //! \name Image File Saving
+ //@{
+ //---------------------------
+
+ //! Save the image as a file.
+ /**
+ The used file format is defined by the file extension in the filename \p filename.
+ Parameter \p number can be used to add a 6-digit number to the filename before saving.
+ **/
+ const CImg<T>& save(const char *const filename, const int number=-1) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save() : Instance image (%u,%u,%u,%u,%p) cannot be saved as a (null) filename.",
+ pixel_type(),width,height,depth,dim,data);
+ const char *ext = cimg::split_filename(filename);
+ char nfilename[1024];
+ const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
+#ifdef cimg_save_plugin
+ cimg_save_plugin(fn);
+#endif
+#ifdef cimg_save_plugin1
+ cimg_save_plugin1(fn);
+#endif
+#ifdef cimg_save_plugin2
+ cimg_save_plugin2(fn);
+#endif
+#ifdef cimg_save_plugin3
+ cimg_save_plugin3(fn);
+#endif
+#ifdef cimg_save_plugin4
+ cimg_save_plugin4(fn);
+#endif
+#ifdef cimg_save_plugin5
+ cimg_save_plugin5(fn);
+#endif
+#ifdef cimg_save_plugin6
+ cimg_save_plugin6(fn);
+#endif
+#ifdef cimg_save_plugin7
+ cimg_save_plugin7(fn);
+#endif
+#ifdef cimg_save_plugin8
+ cimg_save_plugin8(fn);
+#endif
+ // ASCII formats
+ if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn);
+ if (!cimg::strcasecmp(ext,"dlm") ||
+ !cimg::strcasecmp(ext,"txt")) return save_dlm(fn);
+ if (!cimg::strcasecmp(ext,"cpp") ||
+ !cimg::strcasecmp(ext,"hpp") ||
+ !cimg::strcasecmp(ext,"h") ||
+ !cimg::strcasecmp(ext,"c")) return save_cpp(fn);
+
+ // 2D binary formats
+ if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn);
+ if (!cimg::strcasecmp(ext,"jpg") ||
+ !cimg::strcasecmp(ext,"jpeg") ||
+ !cimg::strcasecmp(ext,"jpe") ||
+ !cimg::strcasecmp(ext,"jfif") ||
+ !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn);
+ if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn);
+ if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn);
+ if (!cimg::strcasecmp(ext,"png")) return save_png(fn);
+ if (!cimg::strcasecmp(ext,"pgm") ||
+ !cimg::strcasecmp(ext,"ppm") ||
+ !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn);
+ if (!cimg::strcasecmp(ext,"tif") ||
+ !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
+
+ // 3D binary formats
+ if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
+ if (!cimg::strcasecmp(ext,"cimg") || ext[0]=='\0') return save_cimg(fn,false);
+ if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn);
+ if (!cimg::strcasecmp(ext,"hdr") ||
+ !cimg::strcasecmp(ext,"nii")) return save_analyze(fn);
+ if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn);
+ if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn);
+ if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn);
+
+ // Archive files
+ if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
+
+ // Image sequences
+ if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
+ if (!cimg::strcasecmp(ext,"avi") ||
+ !cimg::strcasecmp(ext,"mov") ||
+ !cimg::strcasecmp(ext,"asf") ||
+ !cimg::strcasecmp(ext,"divx") ||
+ !cimg::strcasecmp(ext,"flv") ||
+ !cimg::strcasecmp(ext,"mpg") ||
+ !cimg::strcasecmp(ext,"m1v") ||
+ !cimg::strcasecmp(ext,"m2v") ||
+ !cimg::strcasecmp(ext,"m4v") ||
+ !cimg::strcasecmp(ext,"mjp") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
+ return save_other(fn);
+ }
+
+ // Save the image as an ASCII file (ASCII Raw + simple header) (internal).
+ const CImg<T>& _save_ascii(cimg_std::FILE *const file, const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_ascii() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_ascii() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+ cimg_std::fprintf(nfile,"%u %u %u %u\n",width,height,depth,dim);
+ const T* ptrs = data;
+ cimg_forYZV(*this,y,z,v) {
+ cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g ",(double)*(ptrs++));
+ cimg_std::fputc('\n',nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as an ASCII file (ASCII Raw + simple header).
+ const CImg<T>& save_ascii(const char *const filename) const {
+ return _save_ascii(0,filename);
+ }
+
+ //! Save the image as an ASCII file (ASCII Raw + simple header).
+ const CImg<T>& save_ascii(cimg_std::FILE *const file) const {
+ return _save_ascii(file,0);
+ }
+
+ // Save the image as a C or CPP source file (internal).
+ const CImg<T>& _save_cpp(cimg_std::FILE *const file, const char *const filename) const {
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_cpp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_cpp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+ char varname[1024] = { 0 };
+ if (filename) cimg_std::sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname);
+ if (varname[0]=='\0') cimg_std::sprintf(varname,"unnamed");
+ cimg_std::fprintf(nfile,
+ "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n"
+ "%s data_%s[] = { \n ",
+ varname,width,height,depth,dim,pixel_type(),pixel_type(),varname);
+ for (unsigned long off = 0, siz = size()-1; off<=siz; ++off) {
+ cimg_std::fprintf(nfile,cimg::type<T>::format(),cimg::type<T>::format((*this)[off]));
+ if (off==siz) cimg_std::fprintf(nfile," };\n");
+ else if (!((off+1)%16)) cimg_std::fprintf(nfile,",\n ");
+ else cimg_std::fprintf(nfile,", ");
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as a CPP source file.
+ const CImg<T>& save_cpp(const char *const filename) const {
+ return _save_cpp(0,filename);
+ }
+
+ //! Save the image as a CPP source file.
+ const CImg<T>& save_cpp(cimg_std::FILE *const file) const {
+ return _save_cpp(file,0);
+ }
+
+ // Save the image as a DLM file (internal).
+ const CImg<T>& _save_dlm(cimg_std::FILE *const file, const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (depth>1)
+ cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Pixel values along Z will be unrolled.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (dim>1)
+ cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. "
+ "Pixel values along V will be unrolled.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+ const T* ptrs = data;
+ cimg_forYZV(*this,y,z,v) {
+ cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g%s",(double)*(ptrs++),(x==dimx()-1)?"":",");
+ cimg_std::fputc('\n',nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as a DLM file.
+ const CImg<T>& save_dlm(const char *const filename) const {
+ return _save_dlm(0,filename);
+ }
+
+ //! Save the image as a DLM file.
+ const CImg<T>& save_dlm(cimg_std::FILE *const file) const {
+ return _save_dlm(file,0);
+ }
+
+ // Save the image as a BMP file (internal).
+ const CImg<T>& _save_bmp(cimg_std::FILE *const file, const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (depth>1)
+ cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (dim>3)
+ cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ unsigned char header[54] = { 0 }, align_buf[4] = { 0 };
+ const unsigned int
+ align = (4 - (3*width)%4)%4,
+ buf_size = (3*width+align)*dimy(),
+ file_size = 54 + buf_size;
+ header[0] = 'B'; header[1] = 'M';
+ header[0x02] = file_size&0xFF;
+ header[0x03] = (file_size>>8)&0xFF;
+ header[0x04] = (file_size>>16)&0xFF;
+ header[0x05] = (file_size>>24)&0xFF;
+ header[0x0A] = 0x36;
+ header[0x0E] = 0x28;
+ header[0x12] = width&0xFF;
+ header[0x13] = (width>>8)&0xFF;
+ header[0x14] = (width>>16)&0xFF;
+ header[0x15] = (width>>24)&0xFF;
+ header[0x16] = height&0xFF;
+ header[0x17] = (height>>8)&0xFF;
+ header[0x18] = (height>>16)&0xFF;
+ header[0x19] = (height>>24)&0xFF;
+ header[0x1A] = 1;
+ header[0x1B] = 0;
+ header[0x1C] = 24;
+ header[0x1D] = 0;
+ header[0x22] = buf_size&0xFF;
+ header[0x23] = (buf_size>>8)&0xFF;
+ header[0x24] = (buf_size>>16)&0xFF;
+ header[0x25] = (buf_size>>24)&0xFF;
+ header[0x27] = 0x1;
+ header[0x2B] = 0x1;
+ cimg::fwrite(header,54,nfile);
+
+ const T
+ *pR = ptr(0,height-1,0,0),
+ *pG = (dim>=2)?ptr(0,height-1,0,1):0,
+ *pB = (dim>=3)?ptr(0,height-1,0,2):0;
+
+ switch (dim) {
+ case 1 : {
+ cimg_forY(*this,y) { cimg_forX(*this,x) {
+ const unsigned char val = (unsigned char)*(pR++);
+ cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile);
+ }
+ cimg::fwrite(align_buf,align,nfile);
+ pR-=2*width;
+ }} break;
+ case 2 : {
+ cimg_forY(*this,y) { cimg_forX(*this,x) {
+ cimg_std::fputc(0,nfile);
+ cimg_std::fputc((unsigned char)(*(pG++)),nfile);
+ cimg_std::fputc((unsigned char)(*(pR++)),nfile);
+ }
+ cimg::fwrite(align_buf,align,nfile);
+ pR-=2*width; pG-=2*width;
+ }} break;
+ default : {
+ cimg_forY(*this,y) { cimg_forX(*this,x) {
+ cimg_std::fputc((unsigned char)(*(pB++)),nfile);
+ cimg_std::fputc((unsigned char)(*(pG++)),nfile);
+ cimg_std::fputc((unsigned char)(*(pR++)),nfile);
+ }
+ cimg::fwrite(align_buf,align,nfile);
+ pR-=2*width; pG-=2*width; pB-=2*width;
+ }
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as a BMP file.
+ const CImg<T>& save_bmp(const char *const filename) const {
+ return _save_bmp(0,filename);
+ }
+
+ //! Save the image as a BMP file.
+ const CImg<T>& save_bmp(cimg_std::FILE *const file) const {
+ return _save_bmp(file,0);
+ }
+
+ // Save a file in JPEG format (internal).
+ const CImg<T>& _save_jpeg(cimg_std::FILE *const file, const char *const filename, const unsigned int quality) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_jpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_jpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (depth>1)
+ cimg::warn("CImg<%s>::save_jpeg() : File '%s, instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+#ifndef cimg_use_jpeg
+ if (!file) return save_other(filename,quality);
+ else throw CImgIOException("CImg<%s>::save_jpeg() : Cannot save a JPEG image in a *FILE output. Use libjpeg instead.",
+ pixel_type());
+#else
+ // Fill pixel buffer
+ unsigned char *buf;
+ unsigned int dimbuf = 0;
+ J_COLOR_SPACE colortype = JCS_RGB;
+ switch (dim) {
+ case 1 : { // Greyscale images
+ unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=1)];
+ colortype = JCS_GRAYSCALE;
+ const T *ptr_g = data;
+ cimg_forXY(*this,x,y) *(buf2++) = (unsigned char)*(ptr_g++);
+ } break;
+ case 2 : { // RG images
+ unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
+ const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1);
+ colortype = JCS_RGB;
+ cimg_forXY(*this,x,y) {
+ *(buf2++) = (unsigned char)*(ptr_r++);
+ *(buf2++) = (unsigned char)*(ptr_g++);
+ *(buf2++) = 0;
+ }
+ } break;
+ case 3 : { // RGB images
+ unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
+ const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
+ colortype = JCS_RGB;
+ cimg_forXY(*this,x,y) {
+ *(buf2++) = (unsigned char)*(ptr_r++);
+ *(buf2++) = (unsigned char)*(ptr_g++);
+ *(buf2++) = (unsigned char)*(ptr_b++);
+ }
+ } break;
+ default : { // CMYK images
+ unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=4)];
+ const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
+ colortype = JCS_CMYK;
+ cimg_forXY(*this,x,y) {
+ *(buf2++) = (unsigned char)*(ptr_r++);
+ *(buf2++) = (unsigned char)*(ptr_g++);
+ *(buf2++) = (unsigned char)*(ptr_b++);
+ *(buf2++) = (unsigned char)*(ptr_a++);
+ }
+ }
+ }
+
+ // Call libjpeg functions
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ jpeg_stdio_dest(&cinfo,nfile);
+ cinfo.image_width = width;
+ cinfo.image_height = height;
+ cinfo.input_components = dimbuf;
+ cinfo.in_color_space = colortype;
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE);
+ jpeg_start_compress(&cinfo,TRUE);
+
+ const unsigned int row_stride = width*dimbuf;
+ JSAMPROW row_pointer[1];
+ while (cinfo.next_scanline < cinfo.image_height) {
+ row_pointer[0] = &buf[cinfo.next_scanline*row_stride];
+ jpeg_write_scanlines(&cinfo,row_pointer,1);
+ }
+ jpeg_finish_compress(&cinfo);
+
+ delete[] buf;
+ if (!file) cimg::fclose(nfile);
+ jpeg_destroy_compress(&cinfo);
+ return *this;
+#endif
+ }
+
+ //! Save a file in JPEG format.
+ const CImg<T>& save_jpeg(const char *const filename, const unsigned int quality=100) const {
+ return _save_jpeg(0,filename,quality);
+ }
+
+ //! Save a file in JPEG format.
+ const CImg<T>& save_jpeg(cimg_std::FILE *const file, const unsigned int quality=100) const {
+ return _save_jpeg(file,0,quality);
+ }
+
+ //! Save the image using built-in ImageMagick++ library.
+ const CImg<T>& save_magick(const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_magick() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_magick() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+#ifdef cimg_use_magick
+ Magick::Image image(Magick::Geometry(width,height),"black");
+ image.type(Magick::TrueColorType);
+ const T
+ *rdata = ptr(0,0,0,0),
+ *gdata = dim>1?ptr(0,0,0,1):0,
+ *bdata = dim>2?ptr(0,0,0,2):0;
+ Magick::PixelPacket *pixels = image.getPixels(0,0,width,height);
+ switch (dim) {
+ case 1 : // Scalar images
+ for (unsigned int off = width*height; off; --off) {
+ pixels->red = pixels->green = pixels->blue = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
+ ++pixels;
+ }
+ break;
+ case 2 : // RG images
+ for (unsigned int off = width*height; off; --off) {
+ pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
+ pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
+ pixels->blue = 0;
+ ++pixels;
+ }
+ break;
+ default : // RGB images
+ for (unsigned int off = width*height; off; --off) {
+ pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
+ pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
+ pixels->blue = Magick::Color::scaleDoubleToQuantum(*(bdata++)/255.0);
+ ++pixels;
+ }
+ }
+ image.syncPixels();
+ image.write(filename);
+#else
+ throw CImgIOException("CImg<%s>::save_magick() : File '%s', Magick++ library has not been linked.",
+ pixel_type(),filename);
+#endif
+ return *this;
+ }
+
+ // Save an image to a PNG file (internal).
+ // Most of this function has been written by Eric Fausett
+ const CImg<T>& _save_png(cimg_std::FILE *const file, const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_png() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (depth>1)
+ cimg::warn("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+#ifndef cimg_use_png
+ if (!file) return save_other(filename);
+ else throw CImgIOException("CImg<%s>::save_png() : Cannot save a PNG image in a *FILE output. You must use 'libpng' to do this instead.",
+ pixel_type());
+#else
+ const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
+ cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb");
+
+ // Setup PNG structures for write
+ png_voidp user_error_ptr = 0;
+ png_error_ptr user_error_fn = 0, user_warning_fn = 0;
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, user_warning_fn);
+ if(!png_ptr){
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'png_ptr' data structure.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_write_struct(&png_ptr,(png_infopp)0);
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'info_ptr' data structure.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::save_png() : File '%s', unknown fatal error.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+ png_init_io(png_ptr, nfile);
+ png_uint_32 width = dimx(), height = dimy();
+ float vmin, vmax = (float)maxmin(vmin);
+ const int bit_depth = (vmin<0 || vmax>=256)?16:8;
+ int color_type;
+ switch (dimv()) {
+ case 1 : color_type = PNG_COLOR_TYPE_GRAY; break;
+ case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
+ case 3 : color_type = PNG_COLOR_TYPE_RGB; break;
+ default : color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ const int interlace_type = PNG_INTERLACE_NONE;
+ const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
+ const int filter_method = PNG_FILTER_TYPE_DEFAULT;
+ png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, interlace_type,compression_type, filter_method);
+ png_write_info(png_ptr, info_ptr);
+ const int byte_depth = bit_depth>>3;
+ const int numChan = dimv()>4?4:dimv();
+ const int pixel_bit_depth_flag = numChan * (bit_depth-1);
+
+ // Allocate Memory for Image Save and Fill pixel data
+ png_bytep *imgData = new png_byte*[height];
+ for (unsigned int row = 0; row<height; ++row) imgData[row] = new png_byte[byte_depth*numChan*width];
+ const T *pC0 = ptr(0,0,0,0);
+ switch (pixel_bit_depth_flag) {
+ case 7 : { // Gray 8-bit
+ cimg_forY(*this,y) {
+ unsigned char *ptrd = imgData[y];
+ cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++);
+ }
+ } break;
+ case 14 : { // Gray w/ Alpha 8-bit
+ const T *pC1 = ptr(0,0,0,1);
+ cimg_forY(*this,y) {
+ unsigned char *ptrd = imgData[y];
+ cimg_forX(*this,x) {
+ *(ptrd++) = (unsigned char)*(pC0++);
+ *(ptrd++) = (unsigned char)*(pC1++);
+ }
+ }
+ } break;
+ case 21 : { // RGB 8-bit
+ const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2);
+ cimg_forY(*this,y) {
+ unsigned char *ptrd = imgData[y];
+ cimg_forX(*this,x) {
+ *(ptrd++) = (unsigned char)*(pC0++);
+ *(ptrd++) = (unsigned char)*(pC1++);
+ *(ptrd++) = (unsigned char)*(pC2++);
+ }
+ }
+ } break;
+ case 28 : { // RGB x/ Alpha 8-bit
+ const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2), *pC3 = ptr(0,0,0,3);
+ cimg_forY(*this,y){
+ unsigned char *ptrd = imgData[y];
+ cimg_forX(*this,x){
+ *(ptrd++) = (unsigned char)*(pC0++);
+ *(ptrd++) = (unsigned char)*(pC1++);
+ *(ptrd++) = (unsigned char)*(pC2++);
+ *(ptrd++) = (unsigned char)*(pC3++);
+ }
+ }
+ } break;
+ case 15 : { // Gray 16-bit
+ cimg_forY(*this,y){
+ unsigned short *ptrd = (unsigned short*)(imgData[y]);
+ cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++);
+ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],width);
+ }
+ } break;
+ case 30 : { // Gray w/ Alpha 16-bit
+ const T *pC1 = ptr(0,0,0,1);
+ cimg_forY(*this,y){
+ unsigned short *ptrd = (unsigned short*)(imgData[y]);
+ cimg_forX(*this,x) {
+ *(ptrd++) = (unsigned short)*(pC0++);
+ *(ptrd++) = (unsigned short)*(pC1++);
+ }
+ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*width);
+ }
+ } break;
+ case 45 : { // RGB 16-bit
+ const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2);
+ cimg_forY(*this,y) {
+ unsigned short *ptrd = (unsigned short*)(imgData[y]);
+ cimg_forX(*this,x) {
+ *(ptrd++) = (unsigned short)*(pC0++);
+ *(ptrd++) = (unsigned short)*(pC1++);
+ *(ptrd++) = (unsigned short)*(pC2++);
+ }
+ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*width);
+ }
+ } break;
+ case 60 : { // RGB w/ Alpha 16-bit
+ const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2), *pC3 = ptr(0,0,0,3);
+ cimg_forY(*this,y) {
+ unsigned short *ptrd = (unsigned short*)(imgData[y]);
+ cimg_forX(*this,x) {
+ *(ptrd++) = (unsigned short)*(pC0++);
+ *(ptrd++) = (unsigned short)*(pC1++);
+ *(ptrd++) = (unsigned short)*(pC2++);
+ *(ptrd++) = (unsigned short)*(pC3++);
+ }
+ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*width);
+ }
+ } break;
+ default :
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImg<%s>::save_png() : File '%s', unknown fatal error.",
+ pixel_type(),nfilename?nfilename:"(FILE*)");
+ }
+ png_write_image(png_ptr, imgData);
+ png_write_end(png_ptr, info_ptr);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ // Deallocate Image Write Memory
+ cimg_forY(*this,n) delete[] imgData[n];
+ delete[] imgData;
+ if (!file) cimg::fclose(nfile);
+ return *this;
+#endif
+ }
+
+ //! Save a file in PNG format
+ const CImg<T>& save_png(const char *const filename) const {
+ return _save_png(0,filename);
+ }
+
+ //! Save a file in PNG format
+ const CImg<T>& save_png(cimg_std::FILE *const file) const {
+ return _save_png(file,0);
+ }
+
+ // Save the image as a PNM file (internal function).
+ const CImg<T>& _save_pnm(cimg_std::FILE *const file, const char *const filename) const {
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ double stmin, stmax = (double)maxmin(stmin);
+ if (depth>1)
+ cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (dim>3)
+ cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (stmin<0 || stmax>65535)
+ cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) has pixel values in [%g,%g]. Probable type overflow.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data,stmin,stmax);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const T
+ *ptrR = ptr(0,0,0,0),
+ *ptrG = (dim>=2)?ptr(0,0,0,1):0,
+ *ptrB = (dim>=3)?ptr(0,0,0,2):0;
+ const unsigned int buf_size = width*height*(dim==1?1:3);
+
+ cimg_std::fprintf(nfile,"P%c\n# CREATOR: CImg Library (original size = %ux%ux%ux%u)\n%u %u\n%u\n",
+ (dim==1?'5':'6'),width,height,depth,dim,width,height,stmax<256?255:(stmax<4096?4095:65535));
+
+ switch (dim) {
+ case 1 : { // Scalar image
+ if (stmax<256) { // Binary PGM 8 bits
+ unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
+ cimg_forXY(*this,x,y) *(xptrd++) = (unsigned char)*(ptrR++);
+ cimg::fwrite(ptrd,buf_size,nfile);
+ delete[] ptrd;
+ } else { // Binary PGM 16 bits
+ unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
+ cimg_forXY(*this,x,y) *(xptrd++) = (unsigned short)*(ptrR++);
+ if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
+ cimg::fwrite(ptrd,buf_size,nfile);
+ delete[] ptrd;
+ }
+ } break;
+ case 2 : { // RG image
+ if (stmax<256) { // Binary PPM 8 bits
+ unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
+ cimg_forXY(*this,x,y) {
+ *(xptrd++) = (unsigned char)*(ptrR++);
+ *(xptrd++) = (unsigned char)*(ptrG++);
+ *(xptrd++) = 0;
+ }
+ cimg::fwrite(ptrd,buf_size,nfile);
+ delete[] ptrd;
+ } else { // Binary PPM 16 bits
+ unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
+ cimg_forXY(*this,x,y) {
+ *(xptrd++) = (unsigned short)*(ptrR++);
+ *(xptrd++) = (unsigned short)*(ptrG++);
+ *(xptrd++) = 0;
+ }
+ if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
+ cimg::fwrite(ptrd,buf_size,nfile);
+ delete[] ptrd;
+ }
+ } break;
+ default : { // RGB image
+ if (stmax<256) { // Binary PPM 8 bits
+ unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
+ cimg_forXY(*this,x,y) {
+ *(xptrd++) = (unsigned char)*(ptrR++);
+ *(xptrd++) = (unsigned char)*(ptrG++);
+ *(xptrd++) = (unsigned char)*(ptrB++);
+ }
+ cimg::fwrite(ptrd,buf_size,nfile);
+ delete[] ptrd;
+ } else { // Binary PPM 16 bits
+ unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
+ cimg_forXY(*this,x,y) {
+ *(xptrd++) = (unsigned short)*(ptrR++);
+ *(xptrd++) = (unsigned short)*(ptrG++);
+ *(xptrd++) = (unsigned short)*(ptrB++);
+ }
+ if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
+ cimg::fwrite(ptrd,buf_size,nfile);
+ delete[] ptrd;
+ }
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as a PNM file.
+ const CImg<T>& save_pnm(const char *const filename) const {
+ return _save_pnm(0,filename);
+ }
+
+ //! Save the image as a PNM file.
+ const CImg<T>& save_pnm(cimg_std::FILE *const file) const {
+ return _save_pnm(file,0);
+ }
+
+ // Save the image as a RGB file (internal).
+ const CImg<T>& _save_rgb(cimg_std::FILE *const file, const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_rgb() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (dim!=3)
+ cimg::warn("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) has not exactly 3 channels.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const unsigned int wh = width*height;
+ unsigned char *buffer = new unsigned char[3*wh], *nbuffer=buffer;
+ const T
+ *ptr1 = ptr(0,0,0,0),
+ *ptr2 = dim>1?ptr(0,0,0,1):0,
+ *ptr3 = dim>2?ptr(0,0,0,2):0;
+ switch (dim) {
+ case 1 : { // Scalar image
+ for (unsigned int k=0; k<wh; ++k) {
+ const unsigned char val = (unsigned char)*(ptr1++);
+ *(nbuffer++) = val;
+ *(nbuffer++) = val;
+ *(nbuffer++) = val;
+ }} break;
+ case 2 : { // RG image
+ for (unsigned int k=0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = 0;
+ }} break;
+ default : { // RGB image
+ for (unsigned int k=0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = (unsigned char)(*(ptr3++));
+ }
+ }
+ }
+ cimg::fwrite(buffer,3*wh,nfile);
+ if (!file) cimg::fclose(nfile);
+ delete[] buffer;
+ return *this;
+ }
+
+ //! Save the image as a RGB file.
+ const CImg<T>& save_rgb(const char *const filename) const {
+ return _save_rgb(0,filename);
+ }
+
+ //! Save the image as a RGB file.
+ const CImg<T>& save_rgb(cimg_std::FILE *const file) const {
+ return _save_rgb(file,0);
+ }
+
+ // Save the image as a RGBA file (internal).
+ const CImg<T>& _save_rgba(cimg_std::FILE *const file, const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_rgba() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_rgba() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (dim!=4)
+ cimg::warn("CImg<%s>::save_rgba() : File '%s, instance image (%u,%u,%u,%u,%p) has not exactly 4 channels.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const unsigned int wh = width*height;
+ unsigned char *buffer = new unsigned char[4*wh], *nbuffer=buffer;
+ const T
+ *ptr1 = ptr(0,0,0,0),
+ *ptr2 = dim>1?ptr(0,0,0,1):0,
+ *ptr3 = dim>2?ptr(0,0,0,2):0,
+ *ptr4 = dim>3?ptr(0,0,0,3):0;
+ switch (dim) {
+ case 1 : { // Scalar images
+ for (unsigned int k=0; k<wh; ++k) {
+ const unsigned char val = (unsigned char)*(ptr1++);
+ *(nbuffer++) = val;
+ *(nbuffer++) = val;
+ *(nbuffer++) = val;
+ *(nbuffer++) = 255;
+ }} break;
+ case 2 : { // RG images
+ for (unsigned int k=0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = 0;
+ *(nbuffer++) = 255;
+ }} break;
+ case 3 : { // RGB images
+ for (unsigned int k=0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = (unsigned char)(*(ptr3++));
+ *(nbuffer++) = 255;
+ }} break;
+ default : { // RGBA images
+ for (unsigned int k=0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = (unsigned char)(*(ptr3++));
+ *(nbuffer++) = (unsigned char)(*(ptr4++));
+ }
+ }
+ }
+ cimg::fwrite(buffer,4*wh,nfile);
+ if (!file) cimg::fclose(nfile);
+ delete[] buffer;
+ return *this;
+ }
+
+ //! Save the image as a RGBA file.
+ const CImg<T>& save_rgba(const char *const filename) const {
+ return _save_rgba(0,filename);
+ }
+
+ //! Save the image as a RGBA file.
+ const CImg<T>& save_rgba(cimg_std::FILE *const file) const {
+ return _save_rgba(file,0);
+ }
+
+ // Save a plane into a tiff file
+#ifdef cimg_use_tiff
+ const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory) const {
+ if (is_empty() || !tif) return *this;
+ const char *const filename = TIFFFileName(tif);
+ uint32 rowsperstrip = (uint32)-1;
+ uint16 spp = dim, bpp = sizeof(T)*8, photometric, compression = COMPRESSION_NONE;
+ if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB;
+ else photometric = PHOTOMETRIC_MINISBLACK;
+ TIFFSetDirectory(tif,directory);
+ TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,width);
+ TIFFSetField(tif,TIFFTAG_IMAGELENGTH,height);
+ TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT);
+ TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp);
+ if (cimg::type<T>::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3);
+ else if (cimg::type<T>::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1);
+ else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2);
+ TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp);
+ TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG);
+ TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric);
+ TIFFSetField(tif,TIFFTAG_COMPRESSION,compression);
+ rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip);
+ TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip);
+ TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB);
+ TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg");
+ T *buf = (T*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf){
+ for (unsigned int row = 0; row<height; row+=rowsperstrip) {
+ uint32 nrow = (row+rowsperstrip>height?height-row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif,row,0);
+ tsize_t i = 0;
+ for (unsigned int rr = 0; rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<width; ++cc)
+ for (unsigned int vv = 0; vv<spp; ++vv)
+ buf[i++] = (*this)(cc,row+rr,vv);
+ if (TIFFWriteEncodedStrip(tif,strip,buf,i*sizeof(T))<0)
+ throw CImgException("CImg<%s>::save_tiff() : File '%s', an error has occured while writing a strip.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ _TIFFfree(buf);
+ }
+ TIFFWriteDirectory(tif);
+ return (*this);
+ }
+#endif
+
+ //! Save a file in TIFF format.
+ const CImg<T>& save_tiff(const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_tiff() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_tiff() : Specified filename is (null) for instance image (%u,%u,%u,%u,%p).",
+ pixel_type(),width,height,depth,dim,data);
+#ifdef cimg_use_tiff
+ TIFF *tif = TIFFOpen(filename,"w");
+ if (tif) {
+ cimg_forZ(*this,z) get_slice(z)._save_tiff(tif,z);
+ TIFFClose(tif);
+ } else throw CImgException("CImg<%s>::save_tiff() : File '%s', error while opening file stream for writing.",
+ pixel_type(),filename);
+#else
+ return save_other(filename);
+#endif
+ return *this;
+ }
+
+ //! Save the image as an ANALYZE7.5 or NIFTI file.
+ const CImg<T>& save_analyze(const char *const filename, const float *const voxsize=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_analyze() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_analyze() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ cimg_std::FILE *file;
+ char header[348], hname[1024], iname[1024];
+ const char *ext = cimg::split_filename(filename);
+ short datatype=-1;
+ cimg_std::memset(header,0,348);
+ if (!ext[0]) { cimg_std::sprintf(hname,"%s.hdr",filename); cimg_std::sprintf(iname,"%s.img",filename); }
+ if (!cimg::strncasecmp(ext,"hdr",3)) {
+ cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(iname+cimg::strlen(iname)-3,"img");
+ }
+ if (!cimg::strncasecmp(ext,"img",3)) {
+ cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(hname+cimg::strlen(iname)-3,"hdr");
+ }
+ if (!cimg::strncasecmp(ext,"nii",3)) {
+ cimg_std::strcpy(hname,filename); iname[0] = 0;
+ }
+ ((int*)(header))[0] = 348;
+ cimg_std::sprintf(header+4,"CImg");
+ cimg_std::sprintf(header+14," ");
+ ((short*)(header+36))[0] = 4096;
+ ((char*)(header+38))[0] = 114;
+ ((short*)(header+40))[0] = 4;
+ ((short*)(header+40))[1] = width;
+ ((short*)(header+40))[2] = height;
+ ((short*)(header+40))[3] = depth;
+ ((short*)(header+40))[4] = dim;
+ if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2;
+ if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2;
+ if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2;
+ if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4;
+ if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4;
+ if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8;
+ if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8;
+ if (!cimg::strcasecmp(pixel_type(),"unsigned long")) datatype = 8;
+ if (!cimg::strcasecmp(pixel_type(),"long")) datatype = 8;
+ if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16;
+ if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64;
+ if (datatype<0)
+ throw CImgIOException("CImg<%s>::save_analyze() : Cannot save image '%s' since pixel type (%s)"
+ "is not handled in Analyze7.5 specifications.\n",
+ pixel_type(),filename,pixel_type());
+ ((short*)(header+70))[0] = datatype;
+ ((short*)(header+72))[0] = sizeof(T);
+ ((float*)(header+112))[0] = 1;
+ ((float*)(header+76))[0] = 0;
+ if (voxsize) {
+ ((float*)(header+76))[1] = voxsize[0];
+ ((float*)(header+76))[2] = voxsize[1];
+ ((float*)(header+76))[3] = voxsize[2];
+ } else ((float*)(header+76))[1] = ((float*)(header+76))[2] = ((float*)(header+76))[3] = 1;
+ file = cimg::fopen(hname,"wb");
+ cimg::fwrite(header,348,file);
+ if (iname[0]) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); }
+ cimg::fwrite(data,size(),file);
+ cimg::fclose(file);
+ return *this;
+ }
+
+ //! Save the image as a .cimg file.
+ const CImg<T>& save_cimg(const char *const filename, const bool compress=false) const {
+ CImgList<T>(*this,true).save_cimg(filename,compress);
+ return *this;
+ }
+
+ // Save the image as a .cimg file.
+ const CImg<T>& save_cimg(cimg_std::FILE *const file, const bool compress=false) const {
+ CImgList<T>(*this,true).save_cimg(file,compress);
+ return *this;
+ }
+
+ //! Insert the image into an existing .cimg file, at specified coordinates.
+ const CImg<T>& save_cimg(const char *const filename,
+ const unsigned int n0,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int v0) const {
+ CImgList<T>(*this,true).save_cimg(filename,n0,x0,y0,z0,v0);
+ return *this;
+ }
+
+ //! Insert the image into an existing .cimg file, at specified coordinates.
+ const CImg<T>& save_cimg(cimg_std::FILE *const file,
+ const unsigned int n0,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int v0) const {
+ CImgList<T>(*this,true).save_cimg(file,n0,x0,y0,z0,v0);
+ return *this;
+ }
+
+ //! Save an empty .cimg file with specified dimensions.
+ static void save_empty_cimg(const char *const filename,
+ const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1) {
+ return CImgList<T>::save_empty_cimg(filename,1,dx,dy,dz,dv);
+ }
+
+ //! Save an empty .cimg file with specified dimensions.
+ static void save_empty_cimg(cimg_std::FILE *const file,
+ const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1) {
+ return CImgList<T>::save_empty_cimg(file,1,dx,dy,dz,dv);
+ }
+
+ // Save the image as an INRIMAGE-4 file (internal).
+ const CImg<T>& _save_inr(cimg_std::FILE *const file, const char *const filename, const float *const voxsize) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_inr() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_inr() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ int inrpixsize=-1;
+ const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0";
+ if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
+ if (!cimg::strcasecmp(pixel_type(),"char")) { inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
+ if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; }
+ if (!cimg::strcasecmp(pixel_type(),"short")) { inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; }
+ if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; }
+ if (!cimg::strcasecmp(pixel_type(),"int")) { inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; }
+ if (!cimg::strcasecmp(pixel_type(),"float")) { inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; }
+ if (!cimg::strcasecmp(pixel_type(),"double")) { inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; }
+ if (inrpixsize<=0)
+ throw CImgIOException("CImg<%s>::save_inr() : Don't know how to save images of '%s'",
+ pixel_type(),pixel_type());
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ char header[257];
+ int err = cimg_std::sprintf(header,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",width,height,depth,dim);
+ if (voxsize) err += cimg_std::sprintf(header+err,"VX=%g\nVY=%g\nVZ=%g\n",voxsize[0],voxsize[1],voxsize[2]);
+ err += cimg_std::sprintf(header+err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm");
+ cimg_std::memset(header+err,'\n',252-err);
+ cimg_std::memcpy(header+252,"##}\n",4);
+ cimg::fwrite(header,256,nfile);
+ cimg_forXYZ(*this,x,y,z) cimg_forV(*this,k) cimg::fwrite(&((*this)(x,y,z,k)),1,nfile);
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as an INRIMAGE-4 file.
+ const CImg<T>& save_inr(const char *const filename, const float *const voxsize=0) const {
+ return _save_inr(0,filename,voxsize);
+ }
+
+ //! Save the image as an INRIMAGE-4 file.
+ const CImg<T>& save_inr(cimg_std::FILE *const file, const float *const voxsize=0) const {
+ return _save_inr(file,0,voxsize);
+ }
+
+ // Save the image as a PANDORE-5 file (internal).
+ unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const {
+ unsigned int nbdims = 0;
+ if (id==2 || id==3 || id==4) { dims[0] = 1; dims[1] = width; nbdims = 2; }
+ if (id==5 || id==6 || id==7) { dims[0] = 1; dims[1] = height; dims[2] = width; nbdims=3; }
+ if (id==8 || id==9 || id==10) { dims[0] = dim; dims[1] = depth; dims[2] = height; dims[3] = width; nbdims = 4; }
+ if (id==16 || id==17 || id==18) { dims[0] = 3; dims[1] = height; dims[2] = width; dims[3] = colorspace; nbdims = 4; }
+ if (id==19 || id==20 || id==21) { dims[0] = 3; dims[1] = depth; dims[2] = height; dims[3] = width; dims[4] = colorspace; nbdims = 5; }
+ if (id==22 || id==23 || id==25) { dims[0] = dim; dims[1] = width; nbdims = 2; }
+ if (id==26 || id==27 || id==29) { dims[0] = dim; dims[1] = height; dims[2] = width; nbdims=3; }
+ if (id==30 || id==31 || id==33) { dims[0] = dim; dims[1] = depth; dims[2] = height; dims[3] = width; nbdims = 4; }
+ return nbdims;
+ }
+
+ const CImg<T>& _save_pandore(cimg_std::FILE *const file, const char *const filename, const unsigned int colorspace) const {
+ typedef unsigned char uchar;
+ typedef unsigned short ushort;
+ typedef unsigned int uint;
+ typedef unsigned long ulong;
+
+#define __cimg_save_pandore_case(dtype) \
+ dtype *buffer = new dtype[size()]; \
+ const T *ptrs = data; \
+ cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \
+ buffer-=size(); \
+ cimg::fwrite(buffer,size(),nfile); \
+ delete[] buffer
+
+#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \
+ if (!saved && (sy?(sy==height):true) && (sz?(sz==depth):true) && (sv?(sv==dim):true) && !cimg::strcmp(stype,pixel_type())) { \
+ unsigned int *iheader = (unsigned int*)(header+12); \
+ nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \
+ cimg::fwrite(header,36,nfile); \
+ if (sizeof(ulong)==4) { ulong ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ulong)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+ else if (sizeof(uint)==4) { uint ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (uint)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+ else if (sizeof(ushort)==4) { ushort ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ushort)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+ else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
+ "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
+ depth,dim,data); \
+ if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \
+ __cimg_save_pandore_case(uchar); \
+ } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \
+ if (sizeof(ulong)==4) { __cimg_save_pandore_case(ulong); } \
+ else if (sizeof(uint)==4) { __cimg_save_pandore_case(uint); } \
+ else if (sizeof(ushort)==4) { __cimg_save_pandore_case(ushort); } \
+ else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
+ "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
+ depth,dim,data); \
+ } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \
+ if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \
+ else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \
+ else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
+ "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
+ depth,dim,data); \
+ } \
+ saved = true; \
+ }
+
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_pandore() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0,
+ 0,0,0,0,'C','I','m','g',0,0,0,0,0,'N','o',' ','d','a','t','e',0,0,0,0 };
+ unsigned int nbdims, dims[5];
+ bool saved = false;
+ _cimg_save_pandore_case(1,1,1,"unsigned char",2);
+ _cimg_save_pandore_case(1,1,1,"char",3);
+ _cimg_save_pandore_case(1,1,1,"short",3);
+ _cimg_save_pandore_case(1,1,1,"unsigned short",3);
+ _cimg_save_pandore_case(1,1,1,"unsigned int",3);
+ _cimg_save_pandore_case(1,1,1,"int",3);
+ _cimg_save_pandore_case(1,1,1,"unsigned long",4);
+ _cimg_save_pandore_case(1,1,1,"long",3);
+ _cimg_save_pandore_case(1,1,1,"float",4);
+ _cimg_save_pandore_case(1,1,1,"double",4);
+
+ _cimg_save_pandore_case(0,1,1,"unsigned char",5);
+ _cimg_save_pandore_case(0,1,1,"char",6);
+ _cimg_save_pandore_case(0,1,1,"short",6);
+ _cimg_save_pandore_case(0,1,1,"unsigned short",6);
+ _cimg_save_pandore_case(0,1,1,"unsigned int",6);
+ _cimg_save_pandore_case(0,1,1,"int",6);
+ _cimg_save_pandore_case(0,1,1,"unsigned long",7);
+ _cimg_save_pandore_case(0,1,1,"long",6);
+ _cimg_save_pandore_case(0,1,1,"float",7);
+ _cimg_save_pandore_case(0,1,1,"double",7);
+
+ _cimg_save_pandore_case(0,0,1,"unsigned char",8);
+ _cimg_save_pandore_case(0,0,1,"char",9);
+ _cimg_save_pandore_case(0,0,1,"short",9);
+ _cimg_save_pandore_case(0,0,1,"unsigned short",9);
+ _cimg_save_pandore_case(0,0,1,"unsigned int",9);
+ _cimg_save_pandore_case(0,0,1,"int",9);
+ _cimg_save_pandore_case(0,0,1,"unsigned long",10);
+ _cimg_save_pandore_case(0,0,1,"long",9);
+ _cimg_save_pandore_case(0,0,1,"float",10);
+ _cimg_save_pandore_case(0,0,1,"double",10);
+
+ _cimg_save_pandore_case(0,1,3,"unsigned char",16);
+ _cimg_save_pandore_case(0,1,3,"char",17);
+ _cimg_save_pandore_case(0,1,3,"short",17);
+ _cimg_save_pandore_case(0,1,3,"unsigned short",17);
+ _cimg_save_pandore_case(0,1,3,"unsigned int",17);
+ _cimg_save_pandore_case(0,1,3,"int",17);
+ _cimg_save_pandore_case(0,1,3,"unsigned long",18);
+ _cimg_save_pandore_case(0,1,3,"long",17);
+ _cimg_save_pandore_case(0,1,3,"float",18);
+ _cimg_save_pandore_case(0,1,3,"double",18);
+
+ _cimg_save_pandore_case(0,0,3,"unsigned char",19);
+ _cimg_save_pandore_case(0,0,3,"char",20);
+ _cimg_save_pandore_case(0,0,3,"short",20);
+ _cimg_save_pandore_case(0,0,3,"unsigned short",20);
+ _cimg_save_pandore_case(0,0,3,"unsigned int",20);
+ _cimg_save_pandore_case(0,0,3,"int",20);
+ _cimg_save_pandore_case(0,0,3,"unsigned long",21);
+ _cimg_save_pandore_case(0,0,3,"long",20);
+ _cimg_save_pandore_case(0,0,3,"float",21);
+ _cimg_save_pandore_case(0,0,3,"double",21);
+
+ _cimg_save_pandore_case(1,1,0,"unsigned char",22);
+ _cimg_save_pandore_case(1,1,0,"char",23);
+ _cimg_save_pandore_case(1,1,0,"short",23);
+ _cimg_save_pandore_case(1,1,0,"unsigned short",23);
+ _cimg_save_pandore_case(1,1,0,"unsigned int",23);
+ _cimg_save_pandore_case(1,1,0,"int",23);
+ _cimg_save_pandore_case(1,1,0,"unsigned long",25);
+ _cimg_save_pandore_case(1,1,0,"long",23);
+ _cimg_save_pandore_case(1,1,0,"float",25);
+ _cimg_save_pandore_case(1,1,0,"double",25);
+
+ _cimg_save_pandore_case(0,1,0,"unsigned char",26);
+ _cimg_save_pandore_case(0,1,0,"char",27);
+ _cimg_save_pandore_case(0,1,0,"short",27);
+ _cimg_save_pandore_case(0,1,0,"unsigned short",27);
+ _cimg_save_pandore_case(0,1,0,"unsigned int",27);
+ _cimg_save_pandore_case(0,1,0,"int",27);
+ _cimg_save_pandore_case(0,1,0,"unsigned long",29);
+ _cimg_save_pandore_case(0,1,0,"long",27);
+ _cimg_save_pandore_case(0,1,0,"float",29);
+ _cimg_save_pandore_case(0,1,0,"double",29);
+
+ _cimg_save_pandore_case(0,0,0,"unsigned char",30);
+ _cimg_save_pandore_case(0,0,0,"char",31);
+ _cimg_save_pandore_case(0,0,0,"short",31);
+ _cimg_save_pandore_case(0,0,0,"unsigned short",31);
+ _cimg_save_pandore_case(0,0,0,"unsigned int",31);
+ _cimg_save_pandore_case(0,0,0,"int",31);
+ _cimg_save_pandore_case(0,0,0,"unsigned long",33);
+ _cimg_save_pandore_case(0,0,0,"long",31);
+ _cimg_save_pandore_case(0,0,0,"float",33);
+ _cimg_save_pandore_case(0,0,0,"double",33);
+
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as a PANDORE-5 file.
+ const CImg<T>& save_pandore(const char *const filename, const unsigned int colorspace=0) const {
+ return _save_pandore(0,filename,colorspace);
+ }
+
+ //! Save the image as a PANDORE-5 file.
+ const CImg<T>& save_pandore(cimg_std::FILE *const file, const unsigned int colorspace=0) const {
+ return _save_pandore(file,0,colorspace);
+ }
+
+ // Save the image as a RAW file (internal).
+ const CImg<T>& _save_raw(cimg_std::FILE *const file, const char *const filename, const bool multiplexed) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_raw() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_raw() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ if (!multiplexed) cimg::fwrite(data,size(),nfile);
+ else {
+ CImg<T> buf(dim);
+ cimg_forXYZ(*this,x,y,z) {
+ cimg_forV(*this,k) buf[k] = (*this)(x,y,z,k);
+ cimg::fwrite(buf.data,dim,nfile);
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save the image as a RAW file.
+ const CImg<T>& save_raw(const char *const filename, const bool multiplexed=false) const {
+ return _save_raw(0,filename,multiplexed);
+ }
+
+ //! Save the image as a RAW file.
+ const CImg<T>& save_raw(cimg_std::FILE *const file, const bool multiplexed=false) const {
+ return _save_raw(file,0,multiplexed);
+ }
+
+ //! Save the image as a video sequence file, using FFMPEG library.
+ const CImg<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int fps=25) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_ffmpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_ffmpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ if (!fps)
+ throw CImgArgumentException("CImg<%s>::save_ffmpeg() : File '%s', specified framerate is 0.",
+ pixel_type(),filename);
+#ifndef cimg_use_ffmpeg
+ return save_ffmpeg_external(filename,first_frame,last_frame);
+#else
+ get_split('z').save_ffmpeg(filename,first_frame,last_frame,fps);
+#endif
+ return *this;
+ }
+
+ //! Save the image as a YUV video sequence file.
+ const CImg<T>& save_yuv(const char *const filename, const bool rgb2yuv=true) const {
+ get_split('z').save_yuv(filename,rgb2yuv);
+ return *this;
+ }
+
+ //! Save the image as a YUV video sequence file.
+ const CImg<T>& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const {
+ get_split('z').save_yuv(file,rgb2yuv);
+ return *this;
+ }
+
+ // Save OFF files (internal).
+ template<typename tf, typename tc>
+ const CImg<T>& _save_off(cimg_std::FILE *const file, const char *const filename,
+ const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_off() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_off() : Specified filename is (null).",
+ pixel_type());
+ if (height<3) return get_resize(-100,3,1,1,0)._save_off(file,filename,primitives,colors,invert_faces);
+ CImgList<tc> _colors;
+ if (!colors) _colors.insert(primitives.size,CImg<tc>::vector(200,200,200));
+ const CImgList<tc>& ncolors = colors?colors:_colors;
+
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+ cimg_std::fprintf(nfile,"OFF\n%u %u %u\n",width,primitives.size,3*primitives.size);
+ cimg_forX(*this,i) cimg_std::fprintf(nfile,"%f %f %f\n",(float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2)));
+ cimglist_for(primitives,l) {
+ const unsigned int prim = primitives[l].size();
+ const bool textured = (prim>4);
+ const CImg<tc>& color = ncolors[l];
+ const unsigned int s = textured?color.dimv():color.size();
+ const float
+ r = textured?(s>0?(float)(color.get_shared_channel(0).mean()/255.0f):1.0f):(s>0?(float)(color(0)/255.0f):1.0f),
+ g = textured?(s>1?(float)(color.get_shared_channel(1).mean()/255.0f):r) :(s>1?(float)(color(1)/255.0f):r),
+ b = textured?(s>2?(float)(color.get_shared_channel(2).mean()/255.0f):r) :(s>2?(float)(color(2)/255.0f):r);
+
+ switch (prim) {
+ case 1 :
+ cimg_std::fprintf(nfile,"1 %u %f %f %f\n",(unsigned int)primitives(l,0),r,g,b);
+ break;
+ case 2 : case 6 :
+ cimg_std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b);
+ break;
+ case 3 : case 9 :
+ if (invert_faces)
+ cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),r,g,b);
+ else
+ cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
+ break;
+ case 4 : case 12 :
+ if (invert_faces)
+ cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),(unsigned int)primitives(l,3),r,g,b);
+ else
+ cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,3),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
+ break;
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save OFF files.
+ template<typename tf, typename tc>
+ const CImg<T>& save_off(const char *const filename,
+ const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
+ return _save_off(0,filename,primitives,colors,invert_faces);
+ }
+
+ //! Save OFF files.
+ template<typename tf, typename tc>
+ const CImg<T>& save_off(cimg_std::FILE *const file,
+ const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
+ return _save_off(file,0,primitives,colors,invert_faces);
+ }
+
+ //! Save the image as a video sequence file, using the external tool 'ffmpeg'.
+ const CImg<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const char *const codec="mpeg2video") const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_ffmpeg_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_ffmpeg_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ get_split('z').save_ffmpeg_external(filename,first_frame,last_frame,codec);
+ return *this;
+ }
+
+ //! Save the image using GraphicsMagick's gm.
+ /** Function that saves the image for other file formats that are not natively handled by CImg,
+ using the tool 'gm' from the GraphicsMagick package.\n
+ This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
+ the GraphicsMagick package in order to get
+ this function working properly (see http://www.graphicsmagick.org ).
+ **/
+ const CImg<T>& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_graphicsmagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_graphicsmagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ char command[1024],filetmp[512];
+ cimg_std::FILE *file;
+ do {
+ if (dim==1) cimg_std::sprintf(filetmp,"%s%s%s.pgm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
+ else cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ save_pnm(filetmp);
+ cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::graphicsmagick_path(),quality,filetmp,filename);
+ cimg::system(command);
+ file = cimg_std::fopen(filename,"rb");
+ if (!file)
+ throw CImgIOException("CImg<%s>::save_graphicsmagick_external() : Failed to save image '%s'.\n\n"
+ "Path of 'gm' : \"%s\"\n"
+ "Path of temporary filename : \"%s\"\n",
+ pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
+ if (file) cimg::fclose(file);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ //! Save an image as a gzipped file, using external tool 'gzip'.
+ const CImg<T>& save_gzip_external(const char *const filename) const {
+ if (!filename)
+ throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512], body[512];
+ const char
+ *ext = cimg::split_filename(filename,body),
+ *ext2 = cimg::split_filename(body,0);
+ cimg_std::FILE *file;
+ do {
+ if (!cimg::strcasecmp(ext,"gz")) {
+ if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext2);
+ else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ } else {
+ if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext);
+ else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ }
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ save(filetmp);
+ cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
+ cimg::system(command);
+ file = cimg_std::fopen(filename,"rb");
+ if (!file)
+ throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.",
+ pixel_type(),filename);
+ else cimg::fclose(file);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ //! Save the image using ImageMagick's convert.
+ /** Function that saves the image for other file formats that are not natively handled by CImg,
+ using the tool 'convert' from the ImageMagick package.\n
+ This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
+ the ImageMagick package in order to get
+ this function working properly (see http://www.imagemagick.org ).
+ **/
+ const CImg<T>& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_imagemagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_imagemagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+ char command[1024], filetmp[512];
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand(),dim==1?"pgm":"ppm");
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ save_pnm(filetmp);
+ cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::imagemagick_path(),quality,filetmp,filename);
+ cimg::system(command);
+ file = cimg_std::fopen(filename,"rb");
+ if (!file)
+ throw CImgIOException("CImg<%s>::save_imagemagick_external() : Failed to save image '%s'.\n\n"
+ "Path of 'convert' : \"%s\"\n"
+ "Path of temporary filename : \"%s\"\n",
+ pixel_type(),filename,cimg::imagemagick_path(),filetmp);
+ if (file) cimg::fclose(file);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ //! Save an image as a Dicom file (need '(X)Medcon' : http://xmedcon.sourceforge.net )
+ const CImg<T>& save_medcon_external(const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_medcon_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save_medcon_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type(),width,height,depth,dim,data);
+
+ char command[1024], filetmp[512], body[512];
+ cimg_std::FILE *file;
+ do {
+ cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ save_analyze(filetmp);
+ cimg_std::sprintf(command,"%s -w -c dicom -o %s -f %s",cimg::medcon_path(),filename,filetmp);
+ cimg::system(command);
+ cimg_std::remove(filetmp);
+ cimg::split_filename(filetmp,body);
+ cimg_std::sprintf(filetmp,"%s.img",body);
+ cimg_std::remove(filetmp);
+ cimg_std::sprintf(command,"m000-%s",filename);
+ file = cimg_std::fopen(command,"rb");
+ if (!file) {
+ cimg::fclose(cimg::fopen(filename,"r"));
+ throw CImgIOException("CImg<%s>::save_medcon_external() : Failed to save image '%s'.\n\n"
+ "Path of 'medcon' : \"%s\"\n"
+ "Path of temporary filename : \"%s\"",
+ pixel_type(),filename,cimg::medcon_path(),filetmp);
+ } else cimg::fclose(file);
+ cimg_std::rename(command,filename);
+ return *this;
+ }
+
+ // Try to save the image if other extension is provided.
+ const CImg<T>& save_other(const char *const filename, const unsigned int quality=100) const {
+ if (is_empty())
+ throw CImgInstanceException("CImg<%s>::save_other() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
+ if (!filename)
+ throw CImgIOException("CImg<%s>::save_other() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
+ pixel_type());
+ const unsigned int odebug = cimg::exception_mode();
+ bool is_saved = true;
+ cimg::exception_mode() = 0;
+ try { save_magick(filename); }
+ catch (CImgException&) {
+ try { save_imagemagick_external(filename,quality); }
+ catch (CImgException&) {
+ try { save_graphicsmagick_external(filename,quality); }
+ catch (CImgException&) {
+ is_saved = false;
+ }
+ }
+ }
+ cimg::exception_mode() = odebug;
+ if (!is_saved)
+ throw CImgIOException("CImg<%s>::save_other() : File '%s' cannot be saved.\n"
+ "Check you have either the ImageMagick or GraphicsMagick package installed.",
+ pixel_type(),filename);
+ return *this;
+ }
+
+ // Get a 40x38 color logo of a 'danger' item (internal).
+ static CImg<T> logo40x38() {
+ static bool first_time = true;
+ static CImg<T> res(40,38,1,3);
+ if (first_time) {
+ const unsigned char *ptrs = cimg::logo40x38;
+ T *ptr1 = res.ptr(0,0,0,0), *ptr2 = res.ptr(0,0,0,1), *ptr3 = res.ptr(0,0,0,2);
+ for (unsigned int off = 0; off<res.width*res.height;) {
+ const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++);
+ for (unsigned int l=0; l<n; ++off, ++l) { *(ptr1++) = (T)r; *(ptr2++) = (T)g; *(ptr3++) = (T)b; }
+ }
+ first_time = false;
+ }
+ return res;
+ }
+
+ };
+
+ /*
+ #-----------------------------------------
+ #
+ #
+ #
+ # Definition of the CImgList<> structure
+ #
+ #
+ #
+ #------------------------------------------
+ */
+
+ //! Class representing list of images CImg<T>.
+ template<typename T>
+ struct CImgList {
+
+ //! Size of the list (number of elements inside).
+ unsigned int size;
+
+ //! Allocation size of the list.
+ unsigned int allocsize;
+
+ //! Pointer to the first list element.
+ CImg<T> *data;
+
+ //! Define a CImgList<T>::iterator.
+ typedef CImg<T>* iterator;
+
+ //! Define a CImgList<T>::const_iterator.
+ typedef const CImg<T>* const_iterator;
+
+ //! Get value type.
+ typedef T value_type;
+
+ // Define common T-dependant types.
+ typedef typename cimg::superset<T,bool>::type Tbool;
+ typedef typename cimg::superset<T,unsigned char>::type Tuchar;
+ typedef typename cimg::superset<T,char>::type Tchar;
+ typedef typename cimg::superset<T,unsigned short>::type Tushort;
+ typedef typename cimg::superset<T,short>::type Tshort;
+ typedef typename cimg::superset<T,unsigned int>::type Tuint;
+ typedef typename cimg::superset<T,int>::type Tint;
+ typedef typename cimg::superset<T,unsigned long>::type Tulong;
+ typedef typename cimg::superset<T,long>::type Tlong;
+ typedef typename cimg::superset<T,float>::type Tfloat;
+ typedef typename cimg::superset<T,double>::type Tdouble;
+ typedef typename cimg::last<T,bool>::type boolT;
+ typedef typename cimg::last<T,unsigned char>::type ucharT;
+ typedef typename cimg::last<T,char>::type charT;
+ typedef typename cimg::last<T,unsigned short>::type ushortT;
+ typedef typename cimg::last<T,short>::type shortT;
+ typedef typename cimg::last<T,unsigned int>::type uintT;
+ typedef typename cimg::last<T,int>::type intT;
+ typedef typename cimg::last<T,unsigned long>::type ulongT;
+ typedef typename cimg::last<T,long>::type longT;
+ typedef typename cimg::last<T,float>::type floatT;
+ typedef typename cimg::last<T,double>::type doubleT;
+
+ //@}
+ //---------------------------
+ //
+ //! \name Plugins
+ //@{
+ //---------------------------
+#ifdef cimglist_plugin
+#include cimglist_plugin
+#endif
+#ifdef cimglist_plugin1
+#include cimglist_plugin1
+#endif
+#ifdef cimglist_plugin2
+#include cimglist_plugin2
+#endif
+#ifdef cimglist_plugin3
+#include cimglist_plugin3
+#endif
+#ifdef cimglist_plugin4
+#include cimglist_plugin4
+#endif
+#ifdef cimglist_plugin5
+#include cimglist_plugin5
+#endif
+#ifdef cimglist_plugin6
+#include cimglist_plugin6
+#endif
+#ifdef cimglist_plugin7
+#include cimglist_plugin7
+#endif
+#ifdef cimglist_plugin8
+#include cimglist_plugin8
+#endif
+ //@}
+
+ //------------------------------------------
+ //
+ //! \name Constructors - Destructor - Copy
+ //@{
+ //------------------------------------------
+
+ //! Destructor.
+ ~CImgList() {
+ if (data) delete[] data;
+ }
+
+ //! Default constructor.
+ CImgList():
+ size(0),allocsize(0),data(0) {}
+
+ //! Construct an image list containing n empty images.
+ explicit CImgList(const unsigned int n):
+ size(n) {
+ data = new CImg<T>[allocsize = cimg::max(16UL,cimg::nearest_pow2(n))];
+ }
+
+ //! Default copy constructor.
+ template<typename t>
+ CImgList(const CImgList<t>& list):
+ size(0),allocsize(0),data(0) {
+ assign(list.size);
+ cimglist_for(*this,l) data[l].assign(list[l],false);
+ }
+
+ CImgList(const CImgList<T>& list):
+ size(0),allocsize(0),data(0) {
+ assign(list.size);
+ cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
+ }
+
+ //! Advanced copy constructor.
+ template<typename t>
+ CImgList(const CImgList<t>& list, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(list.size);
+ if (shared)
+ throw CImgArgumentException("CImgList<%s>::CImgList() : Cannot construct a list instance with shared images from "
+ "a CImgList<%s> (different pixel types).",
+ pixel_type(),CImgList<t>::pixel_type());
+ cimglist_for(*this,l) data[l].assign(list[l],false);
+ }
+
+ CImgList(const CImgList<T>& list, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(list.size);
+ cimglist_for(*this,l) data[l].assign(list[l],shared);
+ }
+
+ //! Construct an image list containing n images with specified size.
+ CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1,
+ const unsigned int depth=1, const unsigned int dim=1):
+ size(0),allocsize(0),data(0) {
+ assign(n);
+ cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
+ }
+
+ //! Construct an image list containing n images with specified size, filled with specified value.
+ CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int dim, const T val):
+ size(0),allocsize(0),data(0) {
+ assign(n);
+ cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
+ }
+
+ //! Construct an image list containing n images with specified size and specified pixel values (int version).
+ CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...):
+ size(0),allocsize(0),data(0) {
+#define _CImgList_stdarg(t) { \
+ assign(n,width,height,depth,dim); \
+ const unsigned int siz = width*height*depth*dim, nsiz = siz*n; \
+ T *ptrd = data->data; \
+ va_list ap; \
+ va_start(ap,val1); \
+ for (unsigned int l=0, s=0, i=0; i<nsiz; ++i) { \
+ *(ptrd++) = (T)(i==0?val0:(i==1?val1:va_arg(ap,t))); \
+ if ((++s)==siz) { ptrd = data[++l].data; s=0; } \
+ } \
+ va_end(ap); \
+ }
+ _CImgList_stdarg(int);
+ }
+
+ //! Construct an image list containing n images with specified size and specified pixel values (double version).
+ CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...):
+ size(0),allocsize(0),data(0) {
+ _CImgList_stdarg(double);
+ }
+
+ //! Construct a list containing n copies of the image img.
+ template<typename t>
+ CImgList(const unsigned int n, const CImg<t>& img):
+ size(0),allocsize(0),data(0) {
+ assign(n);
+ cimglist_for(*this,l) data[l].assign(img,img.is_shared);
+ }
+
+ //! Construct a list containing n copies of the image img, forcing the shared state.
+ template<typename t>
+ CImgList(const unsigned int n, const CImg<t>& img, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(n);
+ cimglist_for(*this,l) data[l].assign(img,shared);
+ }
+
+ //! Construct an image list from one image.
+ template<typename t>
+ explicit CImgList(const CImg<t>& img):
+ size(0),allocsize(0),data(0) {
+ assign(1);
+ data[0].assign(img,img.is_shared);
+ }
+
+ //! Construct an image list from one image, forcing the shared state.
+ template<typename t>
+ explicit CImgList(const CImg<t>& img, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(1);
+ data[0].assign(img,shared);
+ }
+
+ //! Construct an image list from two images.
+ template<typename t1, typename t2>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2):
+ size(0),allocsize(0),data(0) {
+ assign(2);
+ data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared);
+ }
+
+ //! Construct an image list from two images, forcing the shared state.
+ template<typename t1, typename t2>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(2);
+ data[0].assign(img1,shared); data[1].assign(img2,shared);
+ }
+
+ //! Construct an image list from three images.
+ template<typename t1, typename t2, typename t3>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3):
+ size(0),allocsize(0),data(0) {
+ assign(3);
+ data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared);
+ }
+
+ //! Construct an image list from three images, forcing the shared state.
+ template<typename t1, typename t2, typename t3>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(3);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
+ }
+
+ //! Construct an image list from four images.
+ template<typename t1, typename t2, typename t3, typename t4>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4):
+ size(0),allocsize(0),data(0) {
+ assign(4);
+ data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
+ }
+
+ //! Construct an image list from four images, forcing the shared state.
+ template<typename t1, typename t2, typename t3, typename t4>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(4);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ }
+
+ //! Construct an image list from five images.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5):
+ size(0),allocsize(0),data(0) {
+ assign(5);
+ data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
+ data[4].assign(img5,img5.is_shared);
+ }
+
+ //! Construct an image list from five images, forcing the shared state.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(5);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared);
+ }
+
+ //! Construct an image list from six images.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6):
+ size(0),allocsize(0),data(0) {
+ assign(6);
+ data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
+ data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared);
+ }
+
+ //! Construct an image list from six images, forcing the shared state.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(6);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared); data[5].assign(img6,shared);
+ }
+
+ //! Construct an image list from seven images.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7):
+ size(0),allocsize(0),data(0) {
+ assign(7);
+ data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
+ data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared);
+ }
+
+ //! Construct an image list from seven images, forcing the shared state.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(7);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
+ }
+
+ //! Construct an image list from eight images.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8):
+ size(0),allocsize(0),data(0) {
+ assign(8);
+ data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
+ data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared); data[7].assign(img8,img8.is_shared);
+ }
+
+ //! Construct an image list from eight images, forcing the shared state.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared):
+ size(0),allocsize(0),data(0) {
+ assign(8);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
+ }
+
+ //! Construct an image list from a filename.
+ CImgList(const char *const filename):
+ size(0),allocsize(0),data(0) {
+ assign(filename);
+ }
+
+ //! In-place version of the default constructor and default destructor.
+ CImgList<T>& assign() {
+ if (data) delete[] data;
+ size = allocsize = 0;
+ data = 0;
+ return *this;
+ }
+
+ //! Equivalent to assign() (STL-compliant name).
+ CImgList<T>& clear() {
+ return assign();
+ }
+
+ //! In-place version of the corresponding constructor.
+ CImgList<T>& assign(const unsigned int n) {
+ if (n) {
+ if (allocsize<n || allocsize>(n<<2)) {
+ if (data) delete[] data;
+ data = new CImg<T>[allocsize=cimg::max(16UL,cimg::nearest_pow2(n))];
+ }
+ size = n;
+ } else assign();
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height=1,
+ const unsigned int depth=1, const unsigned int dim=1) {
+ assign(n);
+ cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int dim, const T val) {
+ assign(n);
+ cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...) {
+ _CImgList_stdarg(int);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...) {
+ _CImgList_stdarg(double);
+ return *this;
+ }
+
+ //! In-place version of the copy constructor.
+ template<typename t>
+ CImgList<T>& assign(const CImgList<t>& list) {
+ assign(list.size);
+ cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
+ return *this;
+ }
+
+ //! In-place version of the copy constructor.
+ template<typename t>
+ CImgList<T>& assign(const CImgList<t>& list, const bool shared) {
+ assign(list.size);
+ cimglist_for(*this,l) data[l].assign(list[l],shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t>
+ CImgList<T>& assign(const unsigned int n, const CImg<t>& img, const bool shared=false) {
+ assign(n);
+ cimglist_for(*this,l) data[l].assign(img,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t>
+ CImgList<T>& assign(const CImg<t>& img, const bool shared=false) {
+ assign(1);
+ data[0].assign(img,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t1, typename t2>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared=false) {
+ assign(2);
+ data[0].assign(img1,shared); data[1].assign(img2,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t1, typename t2, typename t3>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared=false) {
+ assign(3);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t1, typename t2, typename t3, typename t4>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const bool shared=false) {
+ assign(4);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const bool shared=false) {
+ assign(5);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const bool shared=false) {
+ assign(6);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared); data[5].assign(img6,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared=false) {
+ assign(7);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared=false) {
+ assign(8);
+ data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
+ data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
+ return *this;
+ }
+
+ //! In-place version of the corresponding constructor.
+ CImgList<T>& assign(const char *const filename) {
+ return load(filename);
+ }
+
+ //! Transfer the content of the instance image list into another one.
+ template<typename t>
+ CImgList<T>& transfer_to(CImgList<t>& list) {
+ list.assign(*this);
+ assign();
+ return list;
+ }
+
+ CImgList<T>& transfer_to(CImgList<T>& list) {
+ list.assign();
+ return swap(list);
+ }
+
+ //! Swap all fields of two CImgList instances (use with care !)
+ CImgList<T>& swap(CImgList<T>& list) {
+ cimg::swap(size,list.size);
+ cimg::swap(allocsize,list.allocsize);
+ cimg::swap(data,list.data);
+ return list;
+ }
+
+ //! Return a string describing the type of the image pixels in the list (template parameter \p T).
+ static const char* pixel_type() {
+ return cimg::type<T>::string();
+ }
+
+ //! Return \p true if list is empty.
+ bool is_empty() const {
+ return (!data || !size);
+ }
+
+ //! Return \p true if list is not empty.
+ operator bool() const {
+ return !is_empty();
+ }
+
+ //! Return \p true if list if of specified size.
+ bool is_sameN(const unsigned int n) const {
+ return (size==n);
+ }
+
+ //! Return \p true if list if of specified size.
+ template<typename t>
+ bool is_sameN(const CImgList<t>& list) const {
+ return (size==list.size);
+ }
+
+ // Define useful dimension check functions.
+ // (not documented because they are macro-generated).
+#define _cimglist_def_is_same1(axis) \
+ bool is_same##axis(const unsigned int val) const { \
+ bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis(val); return res; \
+ } \
+ bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \
+ return is_sameN(n) && is_same##axis(val); \
+ } \
+
+#define _cimglist_def_is_same2(axis1,axis2) \
+ bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \
+ bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis1##axis2(val1,val2); return res; \
+ } \
+ bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \
+ return is_sameN(n) && is_same##axis1##axis2(val1,val2); \
+ } \
+
+#define _cimglist_def_is_same3(axis1,axis2,axis3) \
+ bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
+ bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis1##axis2##axis3(val1,val2,val3); return res; \
+ } \
+ bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
+ return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \
+ } \
+
+#define _cimglist_def_is_same(axis) \
+ template<typename t> bool is_same##axis(const CImg<t>& img) const { \
+ bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis(img); return res; \
+ } \
+ template<typename t> bool is_same##axis(const CImgList<t>& list) const { \
+ const unsigned int lmin = cimg::min(size,list.size); \
+ bool res = true; for (unsigned int l = 0; l<lmin && res; ++l) res = data[l].is_same##axis(list[l]); return res; \
+ } \
+ template<typename t> bool is_sameN##axis(const unsigned int n, const CImg<t>& img) const { \
+ return (is_sameN(n) && is_same##axis(img)); \
+ } \
+ template<typename t> bool is_sameN##axis(const CImgList<t>& list) const { \
+ return (is_sameN(list) && is_same##axis(list)); \
+ }
+
+ _cimglist_def_is_same(XY)
+ _cimglist_def_is_same(XZ)
+ _cimglist_def_is_same(XV)
+ _cimglist_def_is_same(YZ)
+ _cimglist_def_is_same(YV)
+ _cimglist_def_is_same(XYZ)
+ _cimglist_def_is_same(XYV)
+ _cimglist_def_is_same(YZV)
+ _cimglist_def_is_same(XYZV)
+ _cimglist_def_is_same1(X)
+ _cimglist_def_is_same1(Y)
+ _cimglist_def_is_same1(Z)
+ _cimglist_def_is_same1(V)
+ _cimglist_def_is_same2(X,Y)
+ _cimglist_def_is_same2(X,Z)
+ _cimglist_def_is_same2(X,V)
+ _cimglist_def_is_same2(Y,Z)
+ _cimglist_def_is_same2(Y,V)
+ _cimglist_def_is_same2(Z,V)
+ _cimglist_def_is_same3(X,Y,Z)
+ _cimglist_def_is_same3(X,Y,V)
+ _cimglist_def_is_same3(X,Z,V)
+ _cimglist_def_is_same3(Y,Z,V)
+
+ bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
+ bool res = true;
+ for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_sameXYZV(dx,dy,dz,dv);
+ return res;
+ }
+
+ bool is_sameNXYZV(const unsigned int n, const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
+ return is_sameN(n) && is_sameXYZV(dx,dy,dz,dv);
+ }
+
+ //! Return \c true if the list contains the pixel (n,x,y,z,v).
+ bool containsNXYZV(const int n, const int x=0, const int y=0, const int z=0, const int v=0) const {
+ if (is_empty()) return false;
+ return n>=0 && n<(int)size && x>=0 && x<data[n].dimx() && y>=0 && y<data[n].dimy() && z>=0 && z<data[n].dimz() && v>=0 && v<data[n].dimv();
+ }
+
+ //! Return \c true if the list contains the image (n).
+ bool containsN(const int n) const {
+ if (is_empty()) return false;
+ return n>=0 && n<(int)size;
+ }
+
+ //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z,v).
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& v) const {
+ if (is_empty()) return false;
+ cimglist_for(*this,l) if (data[l].contains(pixel,x,y,z,v)) { n = (t)l; return true; }
+ return false;
+ }
+
+ //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z).
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x, t&y, t& z) const {
+ t v;
+ return contains(pixel,n,x,y,z,v);
+ }
+
+ //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y).
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x, t&y) const {
+ t z,v;
+ return contains(pixel,n,x,y,z,v);
+ }
+
+ //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x).
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x) const {
+ t y,z,v;
+ return contains(pixel,n,x,y,z,v);
+ }
+
+ //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n).
+ template<typename t>
+ bool contains(const T& pixel, t& n) const {
+ t x,y,z,v;
+ return contains(pixel,n,x,y,z,v);
+ }
+
+ //! Return \c true if one of the image list contains the specified referenced value.
+ bool contains(const T& pixel) const {
+ unsigned int n,x,y,z,v;
+ return contains(pixel,n,x,y,z,v);
+ }
+
+ //! Return \c true if the list contains the image 'img'. If true, returns the position (n) of the image in the list.
+ template<typename t>
+ bool contains(const CImg<T>& img, t& n) const {
+ if (is_empty()) return false;
+ const CImg<T> *const ptr = &img;
+ cimglist_for(*this,i) if (data+i==ptr) { n = (t)i; return true; }
+ return false;
+ }
+
+ //! Return \c true if the list contains the image img.
+ bool contains(const CImg<T>& img) const {
+ unsigned int n;
+ return contains(img,n);
+ }
+
+ //@}
+ //------------------------------
+ //
+ //! \name Arithmetics Operators
+ //@{
+ //------------------------------
+
+ //! Assignment operator
+ template<typename t>
+ CImgList<T>& operator=(const CImgList<t>& list) {
+ return assign(list);
+ }
+
+ CImgList<T>& operator=(const CImgList<T>& list) {
+ return assign(list);
+ }
+
+ //! Assignment operator.
+ template<typename t>
+ CImgList<T>& operator=(const CImg<t>& img) {
+ cimglist_for(*this,l) data[l] = img;
+ return *this;
+ }
+
+ //! Assignment operator.
+ CImgList<T>& operator=(const T val) {
+ cimglist_for(*this,l) data[l].fill(val);
+ return *this;
+ }
+
+ //! Operator+.
+ CImgList<T> operator+() const {
+ return CImgList<T>(*this);
+ }
+
+ //! Operator+=.
+#ifdef cimg_use_visualcpp6
+ CImgList<T>& operator+=(const T val)
+#else
+ template<typename t>
+ CImgList<T>& operator+=(const t val)
+#endif
+ {
+ cimglist_for(*this,l) (*this)[l]+=val;
+ return *this;
+ }
+
+ //! Operator+=.
+ template<typename t>
+ CImgList<T>& operator+=(const CImgList<t>& list) {
+ const unsigned int sizemax = cimg::min(size,list.size);
+ for (unsigned int l=0; l<sizemax; ++l) (*this)[l]+=list[l];
+ return *this;
+ }
+
+ //! Operator++ (prefix).
+ CImgList<T>& operator++() {
+ cimglist_for(*this,l) ++(*this)[l];
+ return *this;
+ }
+
+ //! Operator++ (postfix).
+ CImgList<T> operator++(int) {
+ CImgList<T> copy(*this);
+ ++*this;
+ return copy;
+ }
+
+ //! Operator-.
+ CImgList<T> operator-() const {
+ CImgList<T> res(size);
+ cimglist_for(res,l) res[l].assign(-data[l]);
+ return res;
+ }
+
+ //! Operator-=.
+#ifdef cimg_use_visualcpp6
+ CImgList<T>& operator-=(const T val)
+#else
+ template<typename t>
+ CImgList<T>& operator-=(const t val)
+#endif
+ {
+ cimglist_for(*this,l) (*this)[l]-=val;
+ return *this;
+ }
+
+ //! Operator-=.
+ template<typename t>
+ CImgList<T>& operator-=(const CImgList<t>& list) {
+ const unsigned int sizemax = min(size,list.size);
+ for (unsigned int l=0; l<sizemax; ++l) (*this)[l]-=list[l];
+ return *this;
+ }
+
+ //! Operator-- (prefix).
+ CImgList<T>& operator--() {
+ cimglist_for(*this,l) --(*this)[l];
+ return *this;
+ }
+
+ //! Operator-- (postfix).
+ CImgList<T> operator--(int) {
+ CImgList<T> copy(*this);
+ --*this;
+ return copy;
+ }
+
+ //! Operator*=.
+#ifdef cimg_use_visualcpp6
+ CImgList<T>& operator*=(const double val)
+#else
+ template<typename t>
+ CImgList<T>& operator*=(const t val)
+#endif
+ {
+ cimglist_for(*this,l) (*this)[l]*=val;
+ return *this;
+ }
+
+ //! Operator*=.
+ template<typename t>
+ CImgList<T>& operator*=(const CImgList<t>& list) {
+ const unsigned int N = cimg::min(size,list.size);
+ for (unsigned int l=0; l<N; ++l) (*this)[l]*=list[l];
+ return this;
+ }
+
+ //! Operator/=.
+#ifdef cimg_use_visualcpp6
+ CImgList<T>& operator/=(const double val)
+#else
+ template<typename t>
+ CImgList<T>& operator/=(const t val)
+#endif
+ {
+ cimglist_for(*this,l) (*this)[l]/=val;
+ return *this;
+ }
+
+ //! Operator/=.
+ template<typename t>
+ CImgList<T>& operator/=(const CImgList<t>& list) {
+ const unsigned int N = cimg::min(size,list.size);
+ for (unsigned int l=0; l<N; ++l) (*this)[l]/=list[l];
+ return this;
+ }
+
+ //! Return a reference to the maximum pixel value of the instance list.
+ const T& max() const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::max() : Instance image list is empty.",
+ pixel_type());
+ const T *ptrmax = data->data;
+ T max_value = *ptrmax;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
+ }
+ return *ptrmax;
+ }
+
+ //! Return a reference to the maximum pixel value of the instance list.
+ T& max() {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::max() : Instance image list is empty.",
+ pixel_type());
+ T *ptrmax = data->data;
+ T max_value = *ptrmax;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
+ }
+ return *ptrmax;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list.
+ const T& min() const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::min() : Instance image list is empty.",
+ pixel_type());
+ const T *ptrmin = data->data;
+ T min_value = *ptrmin;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
+ }
+ return *ptrmin;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list.
+ T& min() {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::min() : Instance image list is empty.",
+ pixel_type());
+ T *ptrmin = data->data;
+ T min_value = *ptrmin;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
+ }
+ return *ptrmin;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list.
+ template<typename t>
+ const T& minmax(t& max_val) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.",
+ pixel_type());
+ const T *ptrmin = data->data;
+ T min_value = *ptrmin, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) {
+ const T val = *ptr;
+ if (val<min_value) { min_value = val; ptrmin = ptr; }
+ if (val>max_value) max_value = val;
+ }
+ }
+ max_val = (t)max_value;
+ return *ptrmin;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list.
+ template<typename t>
+ T& minmax(t& max_val) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.",
+ pixel_type());
+ T *ptrmin = data->data;
+ T min_value = *ptrmin, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) {
+ const T val = *ptr;
+ if (val<min_value) { min_value = val; ptrmin = ptr; }
+ if (val>max_value) max_value = val;
+ }
+ }
+ max_val = (t)max_value;
+ return *ptrmin;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list.
+ template<typename t>
+ const T& maxmin(t& min_val) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.",
+ pixel_type());
+ const T *ptrmax = data->data;
+ T min_value = *ptrmax, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) {
+ const T val = *ptr;
+ if (val>max_value) { max_value = val; ptrmax = ptr; }
+ if (val<min_value) min_value = val;
+ }
+ }
+ min_val = (t)min_value;
+ return *ptrmax;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list.
+ template<typename t>
+ T& maxmin(t& min_val) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.",
+ pixel_type());
+ T *ptrmax = data->data;
+ T min_value = *ptrmax, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) {
+ const T val = *ptr;
+ if (val>max_value) { max_value = val; ptrmax = ptr; }
+ if (val<min_value) min_value = val;
+ }
+ }
+ min_val = (t)min_value;
+ return *ptrmax;
+ }
+
+ //! Return the mean pixel value of the instance list.
+ double mean() const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::mean() : Instance image list is empty.",
+ pixel_type());
+ double val = 0;
+ unsigned int siz = 0;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) val+=(double)*ptr;
+ siz+=img.size();
+ }
+ return val/siz;
+ }
+
+ //! Return the variance of the instance list.
+ double variance() {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::variance() : Instance image list is empty.",
+ pixel_type());
+ double res = 0;
+ unsigned int siz = 0;
+ double S = 0, S2 = 0;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_for(img,ptr,T) { const double val = (double)*ptr; S+=val; S2+=val*val; }
+ siz+=img.size();
+ }
+ res = (S2 - S*S/siz)/siz;
+ return res;
+ }
+
+ //! Compute a list of statistics vectors (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax).
+ CImgList<T>& stats(const unsigned int variance_method=1) {
+ if (is_empty()) return *this;
+ cimglist_for(*this,l) data[l].stats(variance_method);
+ return *this;
+ }
+
+ CImgList<Tfloat> get_stats(const unsigned int variance_method=1) const {
+ CImgList<Tfloat> res(size);
+ cimglist_for(*this,l) res[l] = data[l].get_stats(variance_method);
+ return res;
+ }
+
+ //@}
+ //-------------------------
+ //
+ //! \name List Manipulation
+ //@{
+ //-------------------------
+
+ //! Return a reference to the i-th element of the image list.
+ CImg<T>& operator[](const unsigned int pos) {
+#if cimg_debug>=3
+ if (pos>=size) {
+ cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",
+ pixel_type(),pos,size);
+ return *data;
+ }
+#endif
+ return data[pos];
+ }
+
+ const CImg<T>& operator[](const unsigned int pos) const {
+#if cimg_debug>=3
+ if (pos>=size) {
+ cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",
+ pixel_type(),pos,size);
+ return *data;
+ }
+#endif
+ return data[pos];
+ }
+
+ //! Equivalent to CImgList<T>::operator[]
+ CImg<T>& operator()(const unsigned int pos) {
+ return (*this)[pos];
+ }
+
+ const CImg<T>& operator()(const unsigned int pos) const {
+ return (*this)[pos];
+ }
+
+ //! Return a reference to (x,y,z,v) pixel of the pos-th image of the list
+ T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
+ const unsigned int z=0, const unsigned int v=0) {
+ return (*this)[pos](x,y,z,v);
+ }
+ const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
+ const unsigned int z=0, const unsigned int v=0) const {
+ return (*this)[pos](x,y,z,v);
+ }
+
+ // This function is only here for template tricks.
+ T _display_object3d_at2(const int i, const int j) const {
+ return atNXY(i,0,j,0,0,0);
+ }
+
+ //! Read an image in specified position.
+ CImg<T>& at(const int pos) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::at() : Instance list is empty.",
+ pixel_type());
+ return data[pos<0?0:pos>=(int)size?(int)size-1:pos];
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions.
+ T& atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
+ return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZV(x,y,z,v,out_val);
+ }
+
+ T atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
+ return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZV(x,y,z,v,out_val);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions.
+ T& atNXYZV(const int pos, const int x, const int y, const int z, const int v) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.",
+ pixel_type());
+ return _atNXYZV(pos,x,y,z,v);
+ }
+
+ T atNXYZV(const int pos, const int x, const int y, const int z, const int v) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.",
+ pixel_type());
+ return _atNXYZV(pos,x,y,z,v);
+ }
+
+ T& _atNXYZV(const int pos, const int x, const int y, const int z, const int v) {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v);
+ }
+
+ T _atNXYZV(const int pos, const int x, const int y, const int z, const int v) const {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v);
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
+ T& atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
+ return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZ(x,y,z,v,out_val);
+ }
+
+ T atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
+ return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZ(x,y,z,v,out_val);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
+ T& atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.",
+ pixel_type());
+ return _atNXYZ(pos,x,y,z,v);
+ }
+
+ T atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.",
+ pixel_type());
+ return _atNXYZ(pos,x,y,z,v);
+ }
+
+ T& _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v);
+ }
+
+ T _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v);
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y).
+ T& atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
+ return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXY(x,y,z,v,out_val);
+ }
+
+ T atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
+ return (pos<0 || pos>=(int)size)?out_val:data[pos].atXY(x,y,z,v,out_val);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c pos, \c x,\c y).
+ T& atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.",
+ pixel_type());
+ return _atNXY(pos,x,y,z,v);
+ }
+
+ T atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.",
+ pixel_type());
+ return _atNXY(pos,x,y,z,v);
+ }
+
+ T& _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v);
+ }
+
+ T _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v);
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c pos,\c x).
+ T& atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
+ return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atX(x,y,z,v,out_val);
+ }
+
+ T atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
+ return (pos<0 || pos>=(int)size)?out_val:data[pos].atX(x,y,z,v,out_val);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c pos, \c x).
+ T& atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.",
+ pixel_type());
+ return _atNX(pos,x,y,z,v);
+ }
+
+ T atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.",
+ pixel_type());
+ return _atNX(pos,x,y,z,v);
+ }
+
+ T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v);
+ }
+
+ T _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v);
+ }
+
+ //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c pos).
+ T& atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
+ return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):(*this)(pos,x,y,z,v);
+ }
+
+ T atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
+ return (pos<0 || pos>=(int)size)?out_val:(*this)(pos,x,y,z,v);
+ }
+
+ //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c pos).
+ T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.",
+ pixel_type());
+ return _atN(pos,x,y,z,v);
+ }
+
+ T atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.",
+ pixel_type());
+ return _atN(pos,x,y,z,v);
+ }
+
+ T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v);
+ }
+
+ T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const {
+ return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v);
+ }
+
+ //! Returns a reference to the last element.
+ CImg<T>& back() {
+ return (*this)(size-1);
+ }
+
+ const CImg<T>& back() const {
+ return (*this)(size-1);
+ }
+
+ //! Returns a reference to the first element.
+ CImg<T>& front() {
+ return *data;
+ }
+
+ const CImg<T>& front() const {
+ return *data;
+ }
+
+ //! Returns an iterator to the beginning of the vector.
+ iterator begin() {
+ return data;
+ }
+
+ const_iterator begin() const {
+ return data;
+ }
+
+ //! Return a reference to the first image.
+ const CImg<T>& first() const {
+ return *data;
+ }
+
+ CImg<T>& first() {
+ return *data;
+ }
+
+ //! Returns an iterator just past the last element.
+ iterator end() {
+ return data + size;
+ }
+
+ const_iterator end() const {
+ return data + size;
+ }
+
+ //! Return a reference to the last image.
+ const CImg<T>& last() const {
+ return data[size - 1];
+ }
+
+ CImg<T>& last() {
+ return data[size - 1];
+ }
+
+ //! Insert a copy of the image \p img into the current image list, at position \p pos.
+ template<typename t>
+ CImgList<T>& insert(const CImg<t>& img, const unsigned int pos, const bool shared) {
+ const unsigned int npos = pos==~0U?size:pos;
+ if (npos>size)
+ throw CImgArgumentException("CImgList<%s>::insert() : Cannot insert at position %u into a list with %u elements",
+ pixel_type(),npos,size);
+ if (shared)
+ throw CImgArgumentException("CImgList<%s>::insert(): Cannot insert a shared image CImg<%s> into a CImgList<%s>",
+ pixel_type(),img.pixel_type(),pixel_type());
+ CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
+ if (!size || !data) {
+ data = new_data;
+ *data = img;
+ } else {
+ if (new_data) {
+ if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
+ if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
+ cimg_std::memset(data,0,sizeof(CImg<T>)*(size-1));
+ delete[] data;
+ data = new_data;
+ }
+ else if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
+ data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
+ data[npos] = img;
+ }
+ return *this;
+ }
+
+ CImgList<T>& insert(const CImg<T>& img, const unsigned int pos, const bool shared) {
+ const unsigned int npos = pos==~0U?size:pos;
+ if (npos>size)
+ throw CImgArgumentException("CImgList<%s>::insert() : Can't insert at position %u into a list with %u elements",
+ pixel_type(),npos,size);
+ if (&img>=data && &img<data+size) return insert(+img,pos,shared);
+ CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
+ if (!size || !data) {
+ data = new_data;
+ if (shared && img) {
+ data->width = img.width; data->height = img.height; data->depth = img.depth; data->dim = img.dim;
+ data->is_shared = true; data->data = img.data;
+ } else *data = img;
+ }
+ else {
+ if (new_data) {
+ if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
+ if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
+ if (shared && img) {
+ new_data[npos].width = img.width; new_data[npos].height = img.height; new_data[npos].depth = img.depth;
+ new_data[npos].dim = img.dim; new_data[npos].is_shared = true; new_data[npos].data = img.data;
+ } else {
+ new_data[npos].width = new_data[npos].height = new_data[npos].depth = new_data[npos].dim = 0; new_data[npos].data = 0;
+ new_data[npos] = img;
+ }
+ cimg_std::memset(data,0,sizeof(CImg<T>)*(size-1));
+ delete[] data;
+ data = new_data;
+ } else {
+ if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
+ if (shared && img) {
+ data[npos].width = img.width; data[npos].height = img.height; data[npos].depth = img.depth; data[npos].dim = img.dim;
+ data[npos].is_shared = true; data[npos].data = img.data;
+ } else {
+ data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
+ data[npos] = img;
+ }
+ }
+ }
+ return *this;
+ }
+
+ // The two functions below are necessary due to Visual C++ 6.0 function overloading bugs, when
+ // default parameters are used in function signatures.
+ template<typename t>
+ CImgList<T>& insert(const CImg<t>& img, const unsigned int pos) {
+ return insert(img,pos,false);
+ }
+
+ //! Insert a copy of the image \p img into the current image list, at position \p pos.
+ template<typename t>
+ CImgList<T>& insert(const CImg<t>& img) {
+ return insert(img,~0U,false);
+ }
+
+ template<typename t>
+ CImgList<T> get_insert(const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
+ return (+*this).insert(img,pos,shared);
+ }
+
+ //! Insert n empty images img into the current image list, at position \p pos.
+ CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
+ CImg<T> foo;
+ if (!n) return *this;
+ const unsigned int npos = pos==~0U?size:pos;
+ for (unsigned int i=0; i<n; ++i) insert(foo,npos+i);
+ return *this;
+ }
+
+ CImgList<T> get_insert(const unsigned int n, const unsigned int pos=~0U) const {
+ return (+*this).insert(n,pos);
+ }
+
+ //! Insert n copies of the image \p img into the current image list, at position \p pos.
+ template<typename t>
+ CImgList<T>& insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) {
+ if (!n) return *this;
+ const unsigned int npos = pos==~0U?size:pos;
+ insert(img,npos,shared);
+ for (unsigned int i=1; i<n; ++i) insert(data[npos],npos+i,shared);
+ return *this;
+ }
+
+ template<typename t>
+ CImgList<T> get_insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
+ return (+*this).insert(n,img,pos,shared);
+ }
+
+ //! Insert a copy of the image list \p list into the current image list, starting from position \p pos.
+ template<typename t>
+ CImgList<T>& insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
+ const unsigned int npos = pos==~0U?size:pos;
+ if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos+l,shared);
+ else insert(CImgList<T>(list),npos,shared);
+ return *this;
+ }
+
+ template<typename t>
+ CImgList<T> get_insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
+ return (+*this).insert(list,pos,shared);
+ }
+
+ //! Insert n copies of the list \p list at position \p pos of the current list.
+ template<typename t>
+ CImgList<T>& insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
+ if (!n) return *this;
+ const unsigned int npos = pos==~0U?size:pos;
+ for (unsigned int i=0; i<n; ++i) insert(list,npos,shared);
+ return *this;
+ }
+
+ template<typename t>
+ CImgList<T> get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
+ return (+*this).insert(n,list,pos,shared);
+ }
+
+ //! Insert a copy of the image \p img at the end of the current image list.
+ template<typename t>
+ CImgList<T>& operator<<(const CImg<t>& img) {
+ return insert(img);
+ }
+
+ //! Insert a copy of the image list \p list at the end of the current image list.
+ template<typename t>
+ CImgList<T>& operator<<(const CImgList<t>& list) {
+ return insert(list);
+ }
+
+ //! Return a copy of the current image list, where the image \p img has been inserted at the end.
+ template<typename t>
+ CImgList<T>& operator>>(CImg<t>& img) const {
+ typedef typename cimg::superset<T,t>::type Tt;
+ return CImgList<Tt>(*this).insert(img);
+ }
+
+ //! Insert a copy of the current image list at the beginning of the image list \p list.
+ template<typename t>
+ CImgList<T>& operator>>(CImgList<t>& list) const {
+ return list.insert(*this,0);
+ }
+
+ //! Remove the images at positions \p pos1 to \p pos2 from the image list.
+ CImgList<T>& remove(const unsigned int pos1, const unsigned int pos2) {
+ const unsigned int
+ npos1 = pos1<pos2?pos1:pos2,
+ tpos2 = pos1<pos2?pos2:pos1,
+ npos2 = tpos2<size?tpos2:size-1;
+ if (npos1>=size)
+ cimg::warn("CImgList<%s>::remove() : Cannot remove images from a list (%p,%u), at positions %u->%u.",
+ pixel_type(),data,size,npos1,tpos2);
+ else {
+ if (tpos2>=size)
+ cimg::warn("CImgList<%s>::remove() : Cannot remove all images from a list (%p,%u), at positions %u->%u.",
+ pixel_type(),data,size,npos1,tpos2);
+ for (unsigned int k = npos1; k<=npos2; ++k) data[k].assign();
+ const unsigned int nb = 1 + npos2 - npos1;
+ if (!(size-=nb)) return assign();
+ if (size>(allocsize>>2) || allocsize<=8) { // Removing items without reallocation.
+ if (npos1!=size) cimg_std::memmove(data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
+ cimg_std::memset(data+size,0,sizeof(CImg<T>)*nb);
+ } else { // Removing items with reallocation.
+ allocsize>>=2;
+ while (allocsize>8 && size<(allocsize>>1)) allocsize>>=1;
+ CImg<T> *new_data = new CImg<T>[allocsize];
+ if (npos1) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos1);
+ if (npos1!=size) cimg_std::memcpy(new_data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
+ if (size!=allocsize) cimg_std::memset(new_data+size,0,sizeof(allocsize-size));
+ cimg_std::memset(data,0,sizeof(CImg<T>)*(size+nb));
+ delete[] data;
+ data = new_data;
+ }
+ }
+ return *this;
+ }
+
+ CImgList<T> get_remove(const unsigned int pos1, const unsigned int pos2) const {
+ return (+*this).remove(pos1,pos2);
+ }
+
+ //! Remove the image at position \p pos from the image list.
+ CImgList<T>& remove(const unsigned int pos) {
+ return remove(pos,pos);
+ }
+
+ CImgList<T> get_remove(const unsigned int pos) const {
+ return (+*this).remove(pos);
+ }
+
+ //! Remove the last image from the image list.
+ CImgList<T>& remove() {
+ if (size) return remove(size-1);
+ else cimg::warn("CImgList<%s>::remove() : List is empty",
+ pixel_type());
+ return *this;
+ }
+
+ CImgList<T> get_remove() const {
+ return (+*this).remove();
+ }
+
+ //! Reverse list order.
+ CImgList<T>& reverse() {
+ for (unsigned int l=0; l<size/2; ++l) (*this)[l].swap((*this)[size-1-l]);
+ return *this;
+ }
+
+ CImgList<T> get_reverse() const {
+ return (+*this).reverse();
+ }
+
+ //! Get a sub-list.
+ CImgList<T>& crop(const unsigned int i0, const unsigned int i1, const bool shared=false) {
+ return get_crop(i0,i1,shared).transfer_to(*this);
+ }
+
+ CImgList<T> get_crop(const unsigned int i0, const unsigned int i1, const bool shared=false) const {
+ if (i0>i1 || i1>=size)
+ throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
+ pixel_type(),i0,i1,size,data);
+ CImgList<T> res(i1-i0+1);
+ cimglist_for(res,l) res[l].assign((*this)[i0+l],shared);
+ return res;
+ }
+
+ //! Get sub-images of a sublist.
+ CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int y0, const int z0, const int v0,
+ const int x1, const int y1, const int z1, const int v1) {
+ return get_crop(i0,i1,x0,y0,z0,v0,x1,y1,z1,v1).transfer_to(*this);
+ }
+
+ CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int y0, const int z0, const int v0,
+ const int x1, const int y1, const int z1, const int v1) const {
+ if (i0>i1 || i1>=size)
+ throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
+ pixel_type(),i0,i1,size,data);
+ CImgList<T> res(i1-i0+1);
+ cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,v0,x1,y1,z1,v1);
+ return res;
+ }
+
+ //! Get sub-images of a sublist.
+ CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1) {
+ return get_crop(i0,i1,x0,y0,z0,x1,y1,z1).transfer_to(*this);
+ }
+
+ CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1) const {
+ if (i0>i1 || i1>=size)
+ throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
+ pixel_type(),i0,i1,size,data);
+ CImgList<T> res(i1-i0+1);
+ cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,x1,y1,z1);
+ return res;
+ }
+
+ //! Get sub-images of a sublist.
+ CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int y0,
+ const int x1, const int y1) {
+ return get_crop(i0,i1,x0,y0,x1,y1).transfer_to(*this);
+ }
+
+ CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int y0,
+ const int x1, const int y1) const {
+ if (i0>i1 || i1>=size)
+ throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
+ pixel_type(),i0,i1,size,data);
+ CImgList<T> res(i1-i0+1);
+ cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,x1,y1);
+ return res;
+ }
+
+ //! Get sub-images of a sublist.
+ CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int x1) {
+ return get_crop(i0,i1,x0,x1).transfer_to(*this);
+ }
+
+ CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
+ const int x0, const int x1) const {
+ if (i0>i1 || i1>=size)
+ throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
+ pixel_type(),i0,i1,size,data);
+ CImgList<T> res(i1-i0+1);
+ cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,x1);
+ return res;
+ }
+
+ //! Display an image list into a CImgDisplay.
+ const CImgList<T>& operator>>(CImgDisplay& disp) const {
+ return display(disp);
+ }
+
+ //! Insert image \p img at the end of the list.
+ template<typename t>
+ CImgList<T>& push_back(const CImg<t>& img) {
+ return insert(img);
+ }
+
+ //! Insert image \p img at the front of the list.
+ template<typename t>
+ CImgList<T>& push_front(const CImg<t>& img) {
+ return insert(img,0);
+ }
+
+ //! Insert list \p list at the end of the current list.
+ template<typename t>
+ CImgList<T>& push_back(const CImgList<t>& list) {
+ return insert(list);
+ }
+
+ //! Insert list \p list at the front of the current list.
+ template<typename t>
+ CImgList<T>& push_front(const CImgList<t>& list) {
+ return insert(list,0);
+ }
+
+ //! Remove last element of the list.
+ CImgList<T>& pop_back() {
+ return remove(size-1);
+ }
+
+ //! Remove first element of the list.
+ CImgList<T>& pop_front() {
+ return remove(0);
+ }
+
+ //! Remove the element pointed by iterator \p iter.
+ CImgList<T>& erase(const iterator iter) {
+ return remove(iter-data);
+ }
+
+ //@}
+ //----------------------------
+ //
+ //! \name Fourier Transforms
+ //@{
+ //----------------------------
+
+ //! Compute the Fast Fourier Transform (along the specified axis).
+ CImgList<T>& FFT(const char axis, const bool invert=false) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",
+ pixel_type(),size,data);
+ if (!data[0])
+ throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) is empty",
+ pixel_type(),data[0].width,data[0].height,data[0].depth,data[0].dim,data[0].data);
+ if (size>2)
+ cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",
+ pixel_type(),size,data);
+ if (size==1) insert(CImg<T>(data[0].width,data[0].height,data[0].depth,data[0].dim,0));
+ CImg<T> &Ir = data[0], &Ii = data[1];
+ if (Ir.width!=Ii.width || Ir.height!=Ii.height || Ir.depth!=Ii.depth || Ir.dim!=Ii.dim)
+ throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p)"
+ "have different dimensions",
+ pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);
+
+#ifdef cimg_use_fftw3
+ fftw_complex *data_in;
+ fftw_plan data_plan;
+
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*Ir.width);
+ data_plan = fftw_plan_dft_1d(Ir.width,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+ cimg_forYZV(Ir,y,z,k) {
+ T *ptrr = Ir.ptr(0,y,z,k), *ptri = Ii.ptr(0,y,z,k);
+ double *ptrd = (double*)data_in;
+ cimg_forX(Ir,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); }
+ fftw_execute(data_plan);
+ const unsigned int fact = Ir.width;
+ if (invert) { cimg_forX(Ir,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); }}
+ else { cimg_forX(Ir,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }}
+ }
+ } break;
+
+ case 'y' : {
+ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.height);
+ data_plan = fftw_plan_dft_1d(Ir.height,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+ const unsigned int off = Ir.width;
+ cimg_forXZV(Ir,x,z,k) {
+ T *ptrr = Ir.ptr(x,0,z,k), *ptri = Ii.ptr(x,0,z,k);
+ double *ptrd = (double*)data_in;
+ cimg_forY(Ir,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+ fftw_execute(data_plan);
+ const unsigned int fact = Ir.height;
+ if (invert) { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
+ else { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
+ }
+ } break;
+
+ case 'z' : {
+ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.depth);
+ data_plan = fftw_plan_dft_1d(Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+ const unsigned int off = Ir.width*Ir.height;
+ cimg_forXYV(Ir,x,y,k) {
+ T *ptrr = Ir.ptr(x,y,0,k), *ptri = Ii.ptr(x,y,0,k);
+ double *ptrd = (double*)data_in;
+ cimg_forZ(Ir,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+ fftw_execute(data_plan);
+ const unsigned int fact = Ir.depth;
+ if (invert) { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
+ else { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
+ }
+ } break;
+
+ case 'v' : {
+ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.dim);
+ data_plan = fftw_plan_dft_1d(Ir.dim,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+ const unsigned int off = Ir.width*Ir.height*Ir.depth;
+ cimg_forXYZ(Ir,x,y,z) {
+ T *ptrr = Ir.ptr(x,y,z,0), *ptri = Ii.ptr(x,y,z,0);
+ double *ptrd = (double*)data_in;
+ cimg_forV(Ir,k) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+ fftw_execute(data_plan);
+ const unsigned int fact = Ir.dim;
+ if (invert) { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
+ else { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
+ }
+ } break;
+ }
+
+ fftw_destroy_plan(data_plan);
+ fftw_free(data_in);
+#else
+ switch (cimg::uncase(axis)) {
+ case 'x' : { // Fourier along X
+ const unsigned int N = Ir.width, N2 = (N>>1);
+ if (((N-1)&N) && N!=1)
+ throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image along 'x' is %d != 2^N",
+ pixel_type(),N);
+ for (unsigned int i=0, j=0; i<N2; ++i) {
+ if (j>i) cimg_forYZV(Ir,y,z,v) { cimg::swap(Ir(i,y,z,v),Ir(j,y,z,v)); cimg::swap(Ii(i,y,z,v),Ii(j,y,z,v));
+ if (j<N2) {
+ const unsigned int ri = N-1-i, rj = N-1-j;
+ cimg::swap(Ir(ri,y,z,v),Ir(rj,y,z,v)); cimg::swap(Ii(ri,y,z,v),Ii(rj,y,z,v));
+ }}
+ for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
+ }
+ for (unsigned int delta=2; delta<=N; delta<<=1) {
+ const unsigned int delta2 = (delta>>1);
+ for (unsigned int i=0; i<N; i+=delta) {
+ float wr = 1, wi = 0;
+ const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
+ ca = (float)cimg_std::cos(angle),
+ sa = (float)cimg_std::sin(angle);
+ for (unsigned int k=0; k<delta2; ++k) {
+ const unsigned int j = i + k, nj = j + delta2;
+ cimg_forYZV(Ir,y,z,k) {
+ T &ir = Ir(j,y,z,k), &ii = Ii(j,y,z,k), &nir = Ir(nj,y,z,k), &nii = Ii(nj,y,z,k);
+ const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
+ nir = (T)(ir - tmpr);
+ nii = (T)(ii - tmpi);
+ ir += (T)tmpr;
+ ii += (T)tmpi;
+ }
+ const float nwr = wr*ca-wi*sa;
+ wi = wi*ca + wr*sa;
+ wr = nwr;
+ }
+ }
+ }
+ if (invert) (*this)/=N;
+ } break;
+
+ case 'y' : { // Fourier along Y
+ const unsigned int N = Ir.height, N2 = (N>>1);
+ if (((N-1)&N) && N!=1)
+ throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'y' is %d != 2^N",
+ pixel_type(),N);
+ for (unsigned int i=0, j=0; i<N2; ++i) {
+ if (j>i) cimg_forXZV(Ir,x,z,v) { cimg::swap(Ir(x,i,z,v),Ir(x,j,z,v)); cimg::swap(Ii(x,i,z,v),Ii(x,j,z,v));
+ if (j<N2) {
+ const unsigned int ri = N-1-i, rj = N-1-j;
+ cimg::swap(Ir(x,ri,z,v),Ir(x,rj,z,v)); cimg::swap(Ii(x,ri,z,v),Ii(x,rj,z,v));
+ }}
+ for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
+ }
+ for (unsigned int delta=2; delta<=N; delta<<=1) {
+ const unsigned int delta2 = (delta>>1);
+ for (unsigned int i=0; i<N; i+=delta) {
+ float wr = 1, wi = 0;
+ const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
+ ca = (float)cimg_std::cos(angle), sa = (float)cimg_std::sin(angle);
+ for (unsigned int k=0; k<delta2; ++k) {
+ const unsigned int j = i + k, nj = j + delta2;
+ cimg_forXZV(Ir,x,z,k) {
+ T &ir = Ir(x,j,z,k), &ii = Ii(x,j,z,k), &nir = Ir(x,nj,z,k), &nii = Ii(x,nj,z,k);
+ const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
+ nir = (T)(ir - tmpr);
+ nii = (T)(ii - tmpi);
+ ir += (T)tmpr;
+ ii += (T)tmpi;
+ }
+ const float nwr = wr*ca-wi*sa;
+ wi = wi*ca + wr*sa;
+ wr = nwr;
+ }
+ }
+ }
+ if (invert) (*this)/=N;
+ } break;
+
+ case 'z' : { // Fourier along Z
+ const unsigned int N = Ir.depth, N2 = (N>>1);
+ if (((N-1)&N) && N!=1)
+ throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'z' is %d != 2^N",
+ pixel_type(),N);
+ for (unsigned int i=0, j=0; i<N2; ++i) {
+ if (j>i) cimg_forXYV(Ir,x,y,v) { cimg::swap(Ir(x,y,i,v),Ir(x,y,j,v)); cimg::swap(Ii(x,y,i,v),Ii(x,y,j,v));
+ if (j<N2) {
+ const unsigned int ri = N-1-i, rj = N-1-j;
+ cimg::swap(Ir(x,y,ri,v),Ir(x,y,rj,v)); cimg::swap(Ii(x,y,ri,v),Ii(x,y,rj,v));
+ }}
+ for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
+ }
+ for (unsigned int delta=2; delta<=N; delta<<=1) {
+ const unsigned int delta2 = (delta>>1);
+ for (unsigned int i=0; i<N; i+=delta) {
+ float wr = 1, wi = 0;
+ const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
+ ca = (float)cimg_std::cos(angle), sa = (float)cimg_std::sin(angle);
+ for (unsigned int k=0; k<delta2; ++k) {
+ const unsigned int j = i + k, nj = j + delta2;
+ cimg_forXYV(Ir,x,y,k) {
+ T &ir = Ir(x,y,j,k), &ii = Ii(x,y,j,k), &nir = Ir(x,y,nj,k), &nii = Ii(x,y,nj,k);
+ const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
+ nir = (T)(ir - tmpr);
+ nii = (T)(ii - tmpi);
+ ir += (T)tmpr;
+ ii += (T)tmpi;
+ }
+ const float nwr = wr*ca-wi*sa;
+ wi = wi*ca + wr*sa;
+ wr = nwr;
+ }
+ }
+ }
+ if (invert) (*this)/=N;
+ } break;
+
+ default :
+ throw CImgArgumentException("CImgList<%s>::FFT() : Invalid axis '%c', must be 'x','y' or 'z'.");
+ }
+#endif
+ return *this;
+ }
+
+ CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
+ return CImgList<Tfloat>(*this).FFT(axis,invert);
+ }
+
+ //! Compute the Fast Fourier Transform of a complex image.
+ CImgList<T>& FFT(const bool invert=false) {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",
+ pixel_type(),size,data);
+ if (size>2)
+ cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",
+ pixel_type(),size,data);
+ if (size==1) insert(CImg<T>(data->width,data->height,data->depth,data->dim,0));
+ CImg<T> &Ir = data[0], &Ii = data[1];
+ if (Ii.width!=Ir.width || Ii.height!=Ir.height || Ii.depth!=Ir.depth || Ii.dim!=Ir.dim)
+ throw CImgInstanceException("CImgList<%s>::FFT() : Real (%u,%u,%u,%u,%p) and Imaginary (%u,%u,%u,%u,%p) parts "
+ "of the instance image have different dimensions",
+ pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,
+ Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);
+#ifdef cimg_use_fftw3
+ fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.width*Ir.height*Ir.depth);
+ fftw_plan data_plan;
+ const unsigned int w = Ir.width, wh = w*Ir.height, whd = wh*Ir.depth;
+ data_plan = fftw_plan_dft_3d(Ir.width,Ir.height,Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+ cimg_forV(Ir,k) {
+ T *ptrr = Ir.ptr(0,0,0,k), *ptri = Ii.ptr(0,0,0,k);
+ double *ptrd = (double*)data_in;
+ for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
+ for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
+ for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
+ *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri;
+ }
+ fftw_execute(data_plan);
+ ptrd = (double*)data_in;
+ ptrr = Ir.ptr(0,0,0,k);
+ ptri = Ii.ptr(0,0,0,k);
+ if (!invert) for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
+ for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
+ for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
+ *ptrr = (T)*(ptrd++); *ptri = (T)*(ptrd++);
+ }
+ else for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
+ for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
+ for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
+ *ptrr = (T)(*(ptrd++)/whd); *ptri = (T)(*(ptrd++)/whd);
+ }
+ }
+ fftw_destroy_plan(data_plan);
+ fftw_free(data_in);
+#else
+ if (Ir.depth>1) FFT('z',invert);
+ if (Ir.height>1) FFT('y',invert);
+ if (Ir.width>1) FFT('x',invert);
+#endif
+ return *this;
+ }
+
+ CImgList<Tfloat> get_FFT(const bool invert=false) const {
+ return CImgList<Tfloat>(*this).FFT(invert);
+ }
+
+ // Return a list where each image has been split along the specified axis.
+ CImgList<T>& split(const char axis) {
+ return get_split(axis).transfer_to(*this);
+ }
+
+ CImgList<T> get_split(const char axis) const {
+ CImgList<T> res;
+ cimglist_for(*this,l) {
+ CImgList<T> tmp = data[l].get_split(axis);
+ const unsigned int pos = res.size;
+ res.insert(tmp.size);
+ cimglist_for(tmp,i) tmp[i].transfer_to(data[pos+i]);
+ }
+ return res;
+ }
+
+ //! Return a single image which is the concatenation of all images of the current CImgList instance.
+ /**
+ \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
+ \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
+ \return A CImg<T> image corresponding to the concatenation is returned.
+ **/
+ CImg<T> get_append(const char axis, const char align='p') const {
+ if (is_empty()) return CImg<T>();
+ if (size==1) return +((*this)[0]);
+ unsigned int dx = 0, dy = 0, dz = 0, dv = 0, pos = 0;
+ CImg<T> res;
+ switch (cimg::uncase(axis)) {
+ case 'x' : {
+ switch (cimg::uncase(align)) {
+ case 'x' : { dy = dz = dv = 1; cimglist_for(*this,l) dx+=(*this)[l].size(); } break;
+ case 'y' : { dx = size; dz = dv = 1; cimglist_for(*this,l) dy = cimg::max(dy,(unsigned int)(*this)[l].size()); } break;
+ case 'z' : { dx = size; dy = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
+ case 'v' : { dx = size; dy = dz = 1; cimglist_for(*this,l) dv = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
+ default :
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ dx += img.width;
+ dy = cimg::max(dy,img.height);
+ dz = cimg::max(dz,img.depth);
+ dv = cimg::max(dv,img.dim);
+ }
+ }
+ res.assign(dx,dy,dz,dv,0);
+ switch (cimg::uncase(align)) {
+ case 'x' : {
+ cimglist_for(*this,l) {
+ res.draw_image(pos,CImg<T>((*this)[l],true).unroll('x'));
+ pos+=(*this)[l].size();
+ }
+ } break;
+ case 'y' : {
+ cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('y'));
+ } break;
+ case 'z' : {
+ cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('z'));
+ } break;
+ case 'v' : {
+ cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('v'));
+ } break;
+ case 'p' : {
+ cimglist_for(*this,l) { res.draw_image(pos,(*this)[l]); pos+=(*this)[l].width; }
+ } break;
+ case 'n' : {
+ cimglist_for(*this,l) {
+ res.draw_image(pos,dy-(*this)[l].height,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]);
+ pos+=(*this)[l].width;
+ }
+ } break;
+ default : {
+ cimglist_for(*this,l) {
+ res.draw_image(pos,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]);
+ pos+=(*this)[l].width;
+ }
+ } break;
+ }
+ } break;
+
+ case 'y' : {
+ switch (cimg::uncase(align)) {
+ case 'x' : { dy = size; dz = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
+ case 'y' : { dx = dz = dv = 1; cimglist_for(*this,l) dy+=(*this)[l].size(); } break;
+ case 'z' : { dy = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
+ case 'v' : { dy = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
+ default :
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ dx = cimg::max(dx,img.width);
+ dy += img.height;
+ dz = cimg::max(dz,img.depth);
+ dv = cimg::max(dv,img.dim);
+ }
+ }
+ res.assign(dx,dy,dz,dv,0);
+ switch (cimg::uncase(align)) {
+ case 'x' : {
+ cimglist_for(*this,l) res.draw_image(0,++pos,CImg<T>((*this)[l],true).unroll('x'));
+ } break;
+ case 'y' : {
+ cimglist_for(*this,l) {
+ res.draw_image(0,pos,CImg<T>((*this)[l],true).unroll('y'));
+ pos+=(*this)[l].size();
+ }
+ } break;
+ case 'z' : {
+ cimglist_for(*this,l) res.draw_image(0,pos++,CImg<T>((*this)[l],true).unroll('z'));
+ } break;
+ case 'v' : {
+ cimglist_for(*this,l) res.draw_image(0,pos++,CImg<T>((*this)[l],true).unroll('v'));
+ } break;
+ case 'p' : {
+ cimglist_for(*this,l) { res.draw_image(0,pos,(*this)[l]); pos+=(*this)[l].height; }
+ } break;
+ case 'n' : {
+ cimglist_for(*this,l) {
+ res.draw_image(dx-(*this)[l].width,pos,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]);
+ pos+=(*this)[l].height;
+ }
+ } break;
+ default : {
+ cimglist_for(*this,l) {
+ res.draw_image((dx-(*this)[l].width)/2,pos,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]);
+ pos+=(*this)[l].height;
+ }
+ } break;
+ }
+ } break;
+
+ case 'z' : {
+ switch (cimg::uncase(align)) {
+ case 'x' : { dz = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
+ case 'y' : { dz = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
+ case 'z' : { dx = dy = dv = 1; cimglist_for(*this,l) dz+=(*this)[l].size(); } break;
+ case 'v' : { dz = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
+ default :
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ dx = cimg::max(dx,img.width);
+ dy = cimg::max(dy,img.height);
+ dz += img.depth;
+ dv = cimg::max(dv,img.dim);
+ }
+ }
+ res.assign(dx,dy,dz,dv,0);
+ switch (cimg::uncase(align)) {
+ case 'x' : {
+ cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('x'));
+ } break;
+ case 'y' : {
+ cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('y'));
+ } break;
+ case 'z' : {
+ cimglist_for(*this,l) {
+ res.draw_image(0,0,pos,CImg<T>((*this)[l],true).unroll('z'));
+ pos+=(*this)[l].size();
+ }
+ } break;
+ case 'v' : {
+ cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('v'));
+ } break;
+ case 'p' : {
+ cimglist_for(*this,l) { res.draw_image(0,0,pos,(*this)[l]); pos+=(*this)[l].depth; }
+ } break;
+ case 'n' : {
+ cimglist_for(*this,l) {
+ res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,pos,dv-(*this)[l].dim,(*this)[l]);
+ pos+=(*this)[l].depth;
+ }
+ } break;
+ case 'c' : {
+ cimglist_for(*this,l) {
+ res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,pos,(dv-(*this)[l].dim)/2,(*this)[l]);
+ pos+=(*this)[l].depth;
+ }
+ } break;
+ }
+ } break;
+
+ case 'v' : {
+ switch (cimg::uncase(align)) {
+ case 'x' : { dv = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
+ case 'y' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
+ case 'z' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
+ case 'v' : { dx = dy = dz = 1; cimglist_for(*this,l) dv+=(*this)[l].size(); } break;
+ default :
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ dx = cimg::max(dx,img.width);
+ dy = cimg::max(dy,img.height);
+ dz = cimg::max(dz,img.depth);
+ dv += img.dim;
+ }
+ }
+ res.assign(dx,dy,dz,dv,0);
+ switch (cimg::uncase(align)) {
+ case 'x' : {
+ cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('x'));
+ } break;
+ case 'y' : {
+ cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('y'));
+ } break;
+ case 'z' : {
+ cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('v'));
+ } break;
+ case 'v' : {
+ cimglist_for(*this,l) {
+ res.draw_image(0,0,0,pos,CImg<T>((*this)[l],true).unroll('z'));
+ pos+=(*this)[l].size();
+ }
+ } break;
+ case 'p' : {
+ cimglist_for(*this,l) { res.draw_image(0,0,0,pos,(*this)[l]); pos+=(*this)[l].dim; }
+ } break;
+ case 'n' : {
+ cimglist_for(*this,l) {
+ res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,dz-(*this)[l].depth,pos,(*this)[l]);
+ pos+=(*this)[l].dim;
+ }
+ } break;
+ case 'c' : {
+ cimglist_for(*this,l) {
+ res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,pos,(*this)[l]);
+ pos+=(*this)[l].dim;
+ }
+ } break;
+ }
+ } break;
+ default :
+ throw CImgArgumentException("CImgList<%s>::get_append() : unknow axis '%c', must be 'x','y','z' or 'v'",
+ pixel_type(),axis);
+ }
+ return res;
+ }
+
+ //! Create an auto-cropped font (along the X axis) from a input font \p font.
+ CImgList<T>& crop_font() {
+ return get_crop_font().transfer_to(*this);
+ }
+
+ CImgList<T> get_crop_font() const {
+ CImgList<T> res;
+ cimglist_for(*this,l) {
+ const CImg<T>& letter = (*this)[l];
+ int xmin = letter.width, xmax = 0;
+ cimg_forXY(letter,x,y) if (letter(x,y)) { if (x<xmin) xmin=x; if (x>xmax) xmax=x; }
+ if (xmin>xmax) res.insert(CImg<T>(letter.width,letter.height,1,letter.dim,0));
+ else res.insert(letter.get_crop(xmin,0,xmax,letter.height-1));
+ }
+ res[' '].resize(res['f'].width);
+ res[' '+256].resize(res['f'].width);
+ return res;
+ }
+
+ //! Invert primitives orientation of a 3D object.
+ CImgList<T>& invert_object3d() {
+ cimglist_for(*this,l) {
+ CImg<T>& p = data[l];
+ const unsigned int siz = p.size();
+ if (siz==2 || siz==3 || siz==6 || siz==9) cimg::swap(p[0],p[1]);
+ else if (siz==4 || siz==12) cimg::swap(p[0],p[3],p[1],p[2]);
+ }
+ return *this;
+ }
+
+ CImgList<T> get_invert_object3d() const {
+ return (+*this).invert_object3d();
+ }
+
+ //! Return a CImg pre-defined font with desired size.
+ /**
+ \param font_height = height of the desired font (can be 11,13,24,38 or 57)
+ \param fixed_size = tell if the font has a fixed or variable width.
+ **/
+ static CImgList<T> font(const unsigned int font_width, const bool variable_size=true) {
+ if (font_width<=11) {
+ static CImgList<T> font7x11, nfont7x11;
+ if (!variable_size && !font7x11) font7x11 = _font(cimg::font7x11,7,11,1,0,false);
+ if (variable_size && !nfont7x11) nfont7x11 = _font(cimg::font7x11,7,11,1,0,true);
+ return variable_size?nfont7x11:font7x11;
+ }
+ if (font_width<=13) {
+ static CImgList<T> font10x13, nfont10x13;
+ if (!variable_size && !font10x13) font10x13 = _font(cimg::font10x13,10,13,1,0,false);
+ if (variable_size && !nfont10x13) nfont10x13 = _font(cimg::font10x13,10,13,1,0,true);
+ return variable_size?nfont10x13:font10x13;
+ }
+ if (font_width<=17) {
+ static CImgList<T> font8x17, nfont8x17;
+ if (!variable_size && !font8x17) font8x17 = _font(cimg::font8x17,8,17,1,0,false);
+ if (variable_size && !nfont8x17) nfont8x17 = _font(cimg::font8x17,8,17,1,0,true);
+ return variable_size?nfont8x17:font8x17;
+ }
+ if (font_width<=19) {
+ static CImgList<T> font10x19, nfont10x19;
+ if (!variable_size && !font10x19) font10x19 = _font(cimg::font10x19,10,19,2,0,false);
+ if (variable_size && !nfont10x19) nfont10x19 = _font(cimg::font10x19,10,19,2,0,true);
+ return variable_size?nfont10x19:font10x19;
+ }
+ if (font_width<=24) {
+ static CImgList<T> font12x24, nfont12x24;
+ if (!variable_size && !font12x24) font12x24 = _font(cimg::font12x24,12,24,2,0,false);
+ if (variable_size && !nfont12x24) nfont12x24 = _font(cimg::font12x24,12,24,2,0,true);
+ return variable_size?nfont12x24:font12x24;
+ }
+ if (font_width<=32) {
+ static CImgList<T> font16x32, nfont16x32;
+ if (!variable_size && !font16x32) font16x32 = _font(cimg::font16x32,16,32,2,0,false);
+ if (variable_size && !nfont16x32) nfont16x32 = _font(cimg::font16x32,16,32,2,0,true);
+ return variable_size?nfont16x32:font16x32;
+ }
+ if (font_width<=38) {
+ static CImgList<T> font19x38, nfont19x38;
+ if (!variable_size && !font19x38) font19x38 = _font(cimg::font19x38,19,38,3,0,false);
+ if (variable_size && !nfont19x38) nfont19x38 = _font(cimg::font19x38,19,38,3,0,true);
+ return variable_size?nfont19x38:font19x38;
+ }
+ static CImgList<T> font29x57, nfont29x57;
+ if (!variable_size && !font29x57) font29x57 = _font(cimg::font29x57,29,57,5,0,false);
+ if (variable_size && !nfont29x57) nfont29x57 = _font(cimg::font29x57,29,57,5,0,true);
+ return variable_size?nfont29x57:font29x57;
+ }
+
+ static CImgList<T> _font(const unsigned int *const font, const unsigned int w, const unsigned int h,
+ const unsigned int paddingx, const unsigned int paddingy, const bool variable_size=true) {
+ CImgList<T> res = CImgList<T>(256,w,h,1,3).insert(CImgList<T>(256,w,h,1,1));
+ const unsigned int *ptr = font;
+ unsigned int m = 0, val = 0;
+ for (unsigned int y=0; y<h; ++y)
+ for (unsigned int x=0; x<256*w; ++x) {
+ m>>=1; if (!m) { m = 0x80000000; val = *(ptr++); }
+ CImg<T>& img = res[x/w], &mask = res[x/w+256];
+ unsigned int xm = x%w;
+ img(xm,y,0) = img(xm,y,1) = img(xm,y,2) = mask(xm,y,0) = (T)((val&m)?1:0);
+ }
+ if (variable_size) res.crop_font();
+ if (paddingx || paddingy) cimglist_for(res,l) res[l].resize(res[l].dimx()+paddingx, res[l].dimy()+paddingy,1,-100,0);
+ return res;
+ }
+
+ //! Display the current CImgList instance in an existing CImgDisplay window (by reference).
+ /**
+ This function displays the list images of the current CImgList instance into an existing CImgDisplay window.
+ Images of the list are concatenated in a single temporarly image for visualization purposes.
+ The function returns immediately.
+ \param disp : reference to an existing CImgDisplay instance, where the current image list will be displayed.
+ \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
+ \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
+ \return A reference to the current CImgList instance is returned.
+ **/
+ const CImgList<T>& display(CImgDisplay& disp, const char axis='x', const char align='p') const {
+ get_append(axis,align).display(disp);
+ return *this;
+ }
+
+ //! Display the current CImgList instance in a new display window.
+ /**
+ This function opens a new window with a specific title and displays the list images of the current CImgList instance into it.
+ Images of the list are concatenated in a single temporarly image for visualization purposes.
+ The function returns when a key is pressed or the display window is closed by the user.
+ \param title : specify the title of the opening display window.
+ \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
+ \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
+ \return A reference to the current CImgList instance is returned.
+ **/
+ const CImgList<T>& display(CImgDisplay &disp,
+ const bool display_info, const char axis='x', const char align='p') const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::display() : Instance list (%u,%u) is empty.",
+ pixel_type(),size,data);
+ const CImg<T> visu = get_append(axis,align);
+ if (display_info) print(disp.title);
+ visu.display(disp,false);
+ return *this;
+ }
+
+ //! Display the current CImgList instance in a new display window.
+ const CImgList<T>& display(const char *const title=0,
+ const bool display_info=true, const char axis='x', const char align='p') const {
+ const CImg<T> visu = get_append(axis,align);
+ char ntitle[64] = { 0 };
+ if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type());
+ if (display_info) print(title?title:ntitle);
+ visu.display(title?title:ntitle,false);
+ return *this;
+ }
+
+ //@}
+ //----------------------------------
+ //
+ //! \name Input-Output
+ //@{
+ //----------------------------------
+
+ //! Return a C-string containing the values of all images in the instance list.
+ CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
+ if (is_empty()) return CImg<ucharT>(1,1,1,1,0);
+ CImgList<charT> items;
+ for (unsigned int l = 0; l<size-1; ++l) {
+ CImg<charT> item = data[l].value_string(separator,0);
+ item[item.size()-1] = separator;
+ items.insert(item);
+ }
+ items.insert(data[size-1].value_string(separator,0));
+ CImg<charT> res = items.get_append('x');
+ if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
+ return res;
+ }
+
+ //! Print informations about the list on the standard output.
+ const CImgList<T>& print(const char* title=0, const bool display_stats=true) const {
+ unsigned long msiz = 0;
+ cimglist_for(*this,l) msiz += data[l].size();
+ msiz*=sizeof(T);
+ const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2);
+ char ntitle[64] = { 0 };
+ if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type());
+ cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = %u [%lu %s], data = (CImg<%s>*)%p.\n",
+ title?title:ntitle,(void*)this,size,
+ mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
+ mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
+ pixel_type(),(void*)data);
+ char tmp[16] = { 0 };
+ cimglist_for(*this,ll) {
+ cimg_std::sprintf(tmp,"[%d]",ll);
+ cimg_std::fprintf(cimg_stdout," ");
+ data[ll].print(tmp,display_stats);
+ if (ll==3 && size>8) { ll = size-5; cimg_std::fprintf(cimg_stdout," ...\n"); }
+ }
+ return *this;
+ }
+
+ //! Load an image list from a file.
+ CImgList<T>& load(const char *const filename) {
+ const char *ext = cimg::split_filename(filename);
+ const unsigned int odebug = cimg::exception_mode();
+ cimg::exception_mode() = 0;
+ assign();
+ try {
+#ifdef cimglist_load_plugin
+ cimglist_load_plugin(filename);
+#endif
+#ifdef cimglist_load_plugin1
+ cimglist_load_plugin1(filename);
+#endif
+#ifdef cimglist_load_plugin2
+ cimglist_load_plugin2(filename);
+#endif
+#ifdef cimglist_load_plugin3
+ cimglist_load_plugin3(filename);
+#endif
+#ifdef cimglist_load_plugin4
+ cimglist_load_plugin4(filename);
+#endif
+#ifdef cimglist_load_plugin5
+ cimglist_load_plugin5(filename);
+#endif
+#ifdef cimglist_load_plugin6
+ cimglist_load_plugin6(filename);
+#endif
+#ifdef cimglist_load_plugin7
+ cimglist_load_plugin7(filename);
+#endif
+#ifdef cimglist_load_plugin8
+ cimglist_load_plugin8(filename);
+#endif
+ if (!cimg::strcasecmp(ext,"tif") ||
+ !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+ if (!cimg::strcasecmp(ext,"cimg") ||
+ !cimg::strcasecmp(ext,"cimgz") ||
+ !ext[0]) load_cimg(filename);
+ if (!cimg::strcasecmp(ext,"rec") ||
+ !cimg::strcasecmp(ext,"par")) load_parrec(filename);
+ if (!cimg::strcasecmp(ext,"avi") ||
+ !cimg::strcasecmp(ext,"mov") ||
+ !cimg::strcasecmp(ext,"asf") ||
+ !cimg::strcasecmp(ext,"divx") ||
+ !cimg::strcasecmp(ext,"flv") ||
+ !cimg::strcasecmp(ext,"mpg") ||
+ !cimg::strcasecmp(ext,"m1v") ||
+ !cimg::strcasecmp(ext,"m2v") ||
+ !cimg::strcasecmp(ext,"m4v") ||
+ !cimg::strcasecmp(ext,"mjp") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
+ if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+ if (is_empty()) throw CImgIOException("CImgList<%s>::load()",pixel_type());
+ } catch (CImgIOException& e) {
+ if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) {
+ cimg::exception_mode() = odebug;
+ throw CImgIOException("CImgList<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename);
+ } else try {
+ assign(1);
+ data->load(filename);
+ } catch (CImgException&) {
+ assign();
+ }
+ }
+ cimg::exception_mode() = odebug;
+ if (is_empty())
+ throw CImgIOException("CImgList<%s>::load() : File '%s', format not recognized.",pixel_type(),filename);
+ return *this;
+ }
+
+ static CImgList<T> get_load(const char *const filename) {
+ return CImgList<T>().load(filename);
+ }
+
+ //! Load an image list from a .cimg file.
+ CImgList<T>& load_cimg(const char *const filename) {
+ return _load_cimg(0,filename);
+ }
+
+ static CImgList<T> get_load_cimg(const char *const filename) {
+ return CImgList<T>().load_cimg(filename);
+ }
+
+ //! Load an image list from a .cimg file.
+ CImgList<T>& load_cimg(cimg_std::FILE *const file) {
+ return _load_cimg(file,0);
+ }
+
+ static CImgList<T> get_load_cimg(cimg_std::FILE *const file) {
+ return CImgList<T>().load_cimg(file);
+ }
+
+ CImgList<T>& _load_cimg(cimg_std::FILE *const file, const char *const filename) {
+#ifdef cimg_use_zlib
+#define _cimgz_load_cimg_case(Tss) { \
+ Bytef *const cbuf = new Bytef[csiz]; \
+ cimg::fread(cbuf,csiz,nfile); \
+ raw.assign(W,H,D,V); \
+ unsigned long destlen = raw.size()*sizeof(T); \
+ uncompress((Bytef*)raw.data,&destlen,cbuf,csiz); \
+ delete[] cbuf; \
+ const Tss *ptrs = raw.data; \
+ for (unsigned int off = raw.size(); off; --off) *(ptrd++) = (T)*(ptrs++); \
+}
+#else
+#define _cimgz_load_cimg_case(Tss) \
+ throw CImgIOException("CImgList<%s>::load_cimg() : File '%s' contains compressed data, zlib must be used",\
+ pixel_type(),filename?filename:"(FILE*)");
+#endif
+
+#define _cimg_load_cimg_case(Ts,Tss) \
+ if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+ for (unsigned int l = 0; l<N; ++l) { \
+ j = 0; while ((i=cimg_std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = '\0'; \
+ W = H = D = V = 0; csiz = 0; \
+ if ((err = cimg_std::sscanf(tmp,"%u %u %u %u #%u",&W,&H,&D,&V,&csiz))<4) \
+ throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
+ pixel_type(),filename?filename:("(FILE*)"),W,H,D,V); \
+ if (W*H*D*V>0) { \
+ CImg<Tss> raw; \
+ CImg<T> &img = data[l]; \
+ img.assign(W,H,D,V); \
+ T *ptrd = img.data; \
+ if (err==5) _cimgz_load_cimg_case(Tss) \
+ else for (int toread = (int)img.size(); toread>0; ) { \
+ raw.assign(cimg::min(toread,cimg_iobuffer)); \
+ cimg::fread(raw.data,raw.width,nfile); \
+ if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \
+ toread-=raw.width; \
+ const Tss *ptrs = raw.data; \
+ for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \
+ } \
+ } \
+ } \
+ loaded = true; \
+ }
+
+ if (!filename && !file)
+ throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.",
+ pixel_type());
+ typedef unsigned char uchar;
+ typedef unsigned short ushort;
+ typedef unsigned int uint;
+ typedef unsigned long ulong;
+ const int cimg_iobuffer = 12*1024*1024;
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ bool loaded = false, endian = cimg::endianness();
+ char tmp[256], str_pixeltype[256], str_endian[256];
+ unsigned int j, err, N = 0, W, H, D, V, csiz;
+ int i;
+ j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
+ err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
+ if (err<2) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
+ else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
+ assign(N);
+ _cimg_load_cimg_case("bool",bool);
+ _cimg_load_cimg_case("unsigned_char",uchar);
+ _cimg_load_cimg_case("uchar",uchar);
+ _cimg_load_cimg_case("char",char);
+ _cimg_load_cimg_case("unsigned_short",ushort);
+ _cimg_load_cimg_case("ushort",ushort);
+ _cimg_load_cimg_case("short",short);
+ _cimg_load_cimg_case("unsigned_int",uint);
+ _cimg_load_cimg_case("uint",uint);
+ _cimg_load_cimg_case("int",int);
+ _cimg_load_cimg_case("unsigned_long",ulong);
+ _cimg_load_cimg_case("ulong",ulong);
+ _cimg_load_cimg_case("long",long);
+ _cimg_load_cimg_case("float",float);
+ _cimg_load_cimg_case("double",double);
+ if (!loaded) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
+ pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load a sub-image list from a non compressed .cimg file.
+ CImgList<T>& load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
+ return _load_cimg(0,filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
+ }
+
+ static CImgList<T> get_load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
+ return CImgList<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
+ }
+
+ //! Load a sub-image list from a non compressed .cimg file.
+ CImgList<T>& load_cimg(cimg_std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
+ return _load_cimg(file,0,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
+ }
+
+ static CImgList<T> get_load_cimg(cimg_std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
+ return CImgList<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
+ }
+
+ CImgList<T>& _load_cimg(cimg_std::FILE *const file, const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
+ const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
+#define _cimg_load_cimg_case2(Ts,Tss) \
+ if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+ for (unsigned int l = 0; l<=nn1; ++l) { \
+ j = 0; while ((i=cimg_std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = '\0'; \
+ W = H = D = V = 0; \
+ if (cimg_std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
+ throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
+ pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
+ if (W*H*D*V>0) { \
+ if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
+ else { \
+ const unsigned int \
+ nx1 = x1>=W?W-1:x1, \
+ ny1 = y1>=H?H-1:y1, \
+ nz1 = z1>=D?D-1:z1, \
+ nv1 = v1>=V?V-1:v1; \
+ CImg<Tss> raw(1+nx1-x0); \
+ CImg<T> &img = data[l-n0]; \
+ img.assign(1+nx1-x0,1+ny1-y0,1+nz1-z0,1+nv1-v0); \
+ T *ptrd = img.data; \
+ const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
+ if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \
+ for (unsigned int v=1+nv1-v0; v; --v) { \
+ const unsigned int skipzb = z0*W*H*sizeof(Tss); \
+ if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \
+ for (unsigned int z=1+nz1-z0; z; --z) { \
+ const unsigned int skipyb = y0*W*sizeof(Tss); \
+ if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \
+ for (unsigned int y=1+ny1-y0; y; --y) { \
+ const unsigned int skipxb = x0*sizeof(Tss); \
+ if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \
+ cimg::fread(raw.data,raw.width,nfile); \
+ if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \
+ const Tss *ptrs = raw.data; \
+ for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \
+ const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \
+ if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \
+ } \
+ const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
+ if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \
+ } \
+ const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
+ if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \
+ } \
+ const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \
+ if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \
+ } \
+ } \
+ } \
+ loaded = true; \
+ }
+
+ if (!filename && !file)
+ throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.",
+ pixel_type());
+ typedef unsigned char uchar;
+ typedef unsigned short ushort;
+ typedef unsigned int uint;
+ typedef unsigned long ulong;
+ if (n1<n0 || x1<x0 || y1<y0 || z1<z0 || v1<v0)
+ throw CImgArgumentException("CImgList<%s>::load_cimg() : File '%s', Bad sub-region coordinates [%u->%u] "
+ "(%u,%u,%u,%u)->(%u,%u,%u,%u).",
+ pixel_type(),filename?filename:"(FILE*)",
+ n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ bool loaded = false, endian = cimg::endianness();
+ char tmp[256], str_pixeltype[256], str_endian[256];
+ unsigned int j, err, N, W, H, D, V;
+ int i;
+ j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
+ err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
+ if (err<2) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
+ else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
+ const unsigned int nn1 = n1>=N?N-1:n1;
+ assign(1+nn1-n0);
+ _cimg_load_cimg_case2("bool",bool);
+ _cimg_load_cimg_case2("unsigned_char",uchar);
+ _cimg_load_cimg_case2("uchar",uchar);
+ _cimg_load_cimg_case2("char",char);
+ _cimg_load_cimg_case2("unsigned_short",ushort);
+ _cimg_load_cimg_case2("ushort",ushort);
+ _cimg_load_cimg_case2("short",short);
+ _cimg_load_cimg_case2("unsigned_int",uint);
+ _cimg_load_cimg_case2("uint",uint);
+ _cimg_load_cimg_case2("int",int);
+ _cimg_load_cimg_case2("unsigned_long",ulong);
+ _cimg_load_cimg_case2("ulong",ulong);
+ _cimg_load_cimg_case2("long",long);
+ _cimg_load_cimg_case2("float",float);
+ _cimg_load_cimg_case2("double",double);
+ if (!loaded) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
+ pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image list from a PAR/REC (Philips) file.
+ CImgList<T>& load_parrec(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImgList<%s>::load_parrec() : Cannot load (null) filename.",
+ pixel_type());
+ char body[1024], filenamepar[1024], filenamerec[1024];
+ const char *ext = cimg::split_filename(filename,body);
+ if (!cimg::strcmp(ext,"par")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.rec",body); }
+ if (!cimg::strcmp(ext,"PAR")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.REC",body); }
+ if (!cimg::strcmp(ext,"rec")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.par",body); }
+ if (!cimg::strcmp(ext,"REC")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.PAR",body); }
+ cimg_std::FILE *file = cimg::fopen(filenamepar,"r");
+
+ // Parse header file
+ CImgList<floatT> st_slices;
+ CImgList<uintT> st_global;
+ int err;
+ char line[256] = { 0 };
+ do { err=cimg_std::fscanf(file,"%255[^\n]%*c",line); } while (err!=EOF && (line[0]=='#' || line[0]=='.'));
+ do {
+ unsigned int sn,sizex,sizey,pixsize;
+ float rs,ri,ss;
+ err = cimg_std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&sizex,&sizey,&ri,&rs,&ss);
+ if (err==7) {
+ st_slices.insert(CImg<floatT>::vector((float)sn,(float)pixsize,(float)sizex,(float)sizey,
+ ri,rs,ss,0));
+ unsigned int i; for (i=0; i<st_global.size && sn<=st_global[i][2]; ++i) {}
+ if (i==st_global.size) st_global.insert(CImg<uintT>::vector(sizex,sizey,sn));
+ else {
+ CImg<uintT> &vec = st_global[i];
+ if (sizex>vec[0]) vec[0] = sizex;
+ if (sizey>vec[1]) vec[1] = sizey;
+ vec[2] = sn;
+ }
+ st_slices[st_slices.size-1][7] = (float)i;
+ }
+ } while (err==7);
+
+ // Read data
+ cimg_std::FILE *file2 = cimg::fopen(filenamerec,"rb");
+ { cimglist_for(st_global,l) {
+ const CImg<uintT>& vec = st_global[l];
+ insert(CImg<T>(vec[0],vec[1],vec[2]));
+ }}
+
+ cimglist_for(st_slices,l) {
+ const CImg<floatT>& vec = st_slices[l];
+ const unsigned int
+ sn = (unsigned int)vec[0]-1,
+ pixsize = (unsigned int)vec[1],
+ sizex = (unsigned int)vec[2],
+ sizey = (unsigned int)vec[3],
+ imn = (unsigned int)vec[7];
+ const float ri = vec[4], rs = vec[5], ss = vec[6];
+ switch (pixsize) {
+ case 8 : {
+ CImg<ucharT> buf(sizex,sizey);
+ cimg::fread(buf.data,sizex*sizey,file2);
+ if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
+ CImg<T>& img = (*this)[imn];
+ cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
+ } break;
+ case 16 : {
+ CImg<ushortT> buf(sizex,sizey);
+ cimg::fread(buf.data,sizex*sizey,file2);
+ if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
+ CImg<T>& img = (*this)[imn];
+ cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
+ } break;
+ case 32 : {
+ CImg<uintT> buf(sizex,sizey);
+ cimg::fread(buf.data,sizex*sizey,file2);
+ if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
+ CImg<T>& img = (*this)[imn];
+ cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
+ } break;
+ default :
+ cimg::fclose(file);
+ cimg::fclose(file2);
+ throw CImgIOException("CImg<%s>::load_parrec() : File '%s', cannot handle image with pixsize = %d bits.",
+ pixel_type(),filename,pixsize);
+ }
+ }
+ cimg::fclose(file);
+ cimg::fclose(file2);
+ if (!size)
+ throw CImgIOException("CImg<%s>::load_parrec() : File '%s' does not appear to be a valid PAR-REC file.",
+ pixel_type(),filename);
+ return *this;
+ }
+
+ static CImgList<T> get_load_parrec(const char *const filename) {
+ return CImgList<T>().load_parrec(filename);
+ }
+
+ //! Load an image sequence from a YUV file.
+ CImgList<T>& load_yuv(const char *const filename,
+ const unsigned int sizex, const unsigned int sizey,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return _load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ static CImgList<T> get_load_yuv(const char *const filename,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ //! Load an image sequence from a YUV file.
+ CImgList<T>& load_yuv(cimg_std::FILE *const file,
+ const unsigned int sizex, const unsigned int sizey,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return _load_yuv(file,0,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ static CImgList<T> get_load_yuv(cimg_std::FILE *const file,
+ const unsigned int sizex, const unsigned int sizey=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ CImgList<T>& _load_yuv(cimg_std::FILE *const file, const char *const filename,
+ const unsigned int sizex, const unsigned int sizey,
+ const unsigned int first_frame, const unsigned int last_frame,
+ const unsigned int step_frame, const bool yuv2rgb) {
+ if (!filename && !file)
+ throw CImgArgumentException("CImgList<%s>::load_yuv() : Cannot load (null) filename.",
+ pixel_type());
+ if (sizex%2 || sizey%2)
+ throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', image dimensions along X and Y must be "
+ "even numbers (given are %ux%u)\n",
+ pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
+ if (!sizex || !sizey)
+ throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', given image sequence size (%u,%u) is invalid",
+ pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
+
+ const unsigned int
+ nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+ nlast_frame = first_frame<last_frame?last_frame:first_frame,
+ nstep_frame = step_frame?step_frame:1;
+
+ CImg<ucharT> tmp(sizex,sizey,1,3), UV(sizex/2,sizey/2,1,2);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ bool stopflag = false;
+ int err;
+ if (nfirst_frame) {
+ err = cimg_std::fseek(nfile,nfirst_frame*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
+ if (err) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImgList<%s>::load_yuv() : File '%s' doesn't contain frame number %u "
+ "(out of range error).",
+ pixel_type(),filename?filename:"(FILE*)",nfirst_frame);
+ }
+ }
+ unsigned int frame;
+ for (frame = nfirst_frame; !stopflag && frame<=nlast_frame; frame+=nstep_frame) {
+ tmp.fill(0);
+ // *TRY* to read the luminance part, do not replace by cimg::fread !
+ err = (int)cimg_std::fread((void*)(tmp.data),1,(size_t)(tmp.width*tmp.height),nfile);
+ if (err!=(int)(tmp.width*tmp.height)) {
+ stopflag = true;
+ if (err>0)
+ cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data,"
+ " or given image dimensions (%u,%u) are incorrect.",
+ pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
+ } else {
+ UV.fill(0);
+ // *TRY* to read the luminance part, do not replace by cimg::fread !
+ err = (int)cimg_std::fread((void*)(UV.data),1,(size_t)(UV.size()),nfile);
+ if (err!=(int)(UV.size())) {
+ stopflag = true;
+ if (err>0)
+ cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data,"
+ " or given image dimensions (%u,%u) are incorrect.",
+ pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
+ } else {
+ cimg_forXY(UV,x,y) {
+ const int x2 = x*2, y2 = y*2;
+ tmp(x2,y2,1) = tmp(x2+1,y2,1) = tmp(x2,y2+1,1) = tmp(x2+1,y2+1,1) = UV(x,y,0);
+ tmp(x2,y2,2) = tmp(x2+1,y2,2) = tmp(x2,y2+1,2) = tmp(x2+1,y2+1,2) = UV(x,y,1);
+ }
+ if (yuv2rgb) tmp.YCbCrtoRGB();
+ insert(tmp);
+ if (nstep_frame>1) cimg_std::fseek(nfile,(nstep_frame-1)*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
+ }
+ }
+ }
+ if (stopflag && nlast_frame!=~0U && frame!=nlast_frame)
+ cimg::warn("CImgList<%s>::load_yuv() : File '%s', frame %d not reached since only %u frames were found in the file.",
+ pixel_type(),filename?filename:"(FILE*)",nlast_frame,frame-1,filename);
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load an image from a video file, using ffmpeg libraries.
+ // This piece of code has been firstly created by David Starweather (starkdg(at)users(dot)sourceforge(dot)net)
+ // I modified it afterwards for direct inclusion in the library core.
+ CImgList<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false) {
+ if (!filename)
+ throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : Cannot load (null) filename.",
+ pixel_type());
+ const unsigned int
+ nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+ nlast_frame = first_frame<last_frame?last_frame:first_frame,
+ nstep_frame = step_frame?step_frame:1;
+ assign();
+
+#ifndef cimg_use_ffmpeg
+ if ((nfirst_frame || nlast_frame!=~0U || nstep_frame>1) || (resume && (pixel_format || !pixel_format)))
+ throw CImgArgumentException("CImg<%s>::load_ffmpeg() : File '%s', reading sub-frames from a video file requires the use of ffmpeg.\n"
+ "('cimg_use_ffmpeg' must be defined).",
+ pixel_type(),filename);
+ return load_ffmpeg_external(filename);
+#else
+ const unsigned int ffmpeg_pixfmt = pixel_format?PIX_FMT_RGB24:PIX_FMT_GRAY8;
+ avcodec_register_all();
+ av_register_all();
+ static AVFormatContext *format_ctx = 0;
+ static AVCodecContext *codec_ctx = 0;
+ static AVCodec *codec = 0;
+ static AVFrame *avframe = avcodec_alloc_frame(), *converted_frame = avcodec_alloc_frame();
+ static int vstream = 0;
+
+ if (resume) {
+ if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame)
+ throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : File '%s', cannot resume due to unallocated FFMPEG structures.",
+ pixel_type(),filename);
+ } else {
+ // Open video file, find main video stream and codec.
+ if (format_ctx) av_close_input_file(format_ctx);
+ if (av_open_input_file(&format_ctx,filename,0,0,0)!=0)
+ throw CImgIOException("CImgList<%s>::load_ffmpeg() : File '%s' cannot be opened.",
+ pixel_type(),filename);
+ if (!avframe || !converted_frame || av_find_stream_info(format_ctx)<0) {
+ av_close_input_file(format_ctx); format_ctx = 0;
+ cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve stream information.\n"
+ "Trying with external ffmpeg executable.",
+ pixel_type(),filename);
+ return load_ffmpeg_external(filename);
+ }
+#if cimg_debug>=3
+ dump_format(format_ctx,0,0,0);
+#endif
+
+ // Special command : Return informations on main video stream.
+ // as a vector 1x4 containing : (nb_frames,width,height,fps).
+ if (!first_frame && !last_frame && !step_frame) {
+ for (vstream = 0; vstream<(int)(format_ctx->nb_streams); ++vstream)
+ if (format_ctx->streams[vstream]->codec->codec_type==CODEC_TYPE_VIDEO) break;
+ if (vstream==(int)format_ctx->nb_streams) assign();
+ else {
+ CImgList<doubleT> timestamps;
+ int nb_frames;
+ AVPacket packet;
+ // Count frames and store timestamps.
+ for (nb_frames = 0; av_read_frame(format_ctx,&packet)>=0; av_free_packet(&packet))
+ if (packet.stream_index==vstream) {
+ timestamps.insert(CImg<doubleT>::vector((double)packet.pts));
+ ++nb_frames;
+ }
+ // Get frame with, height and fps.
+ const int
+ framew = format_ctx->streams[vstream]->codec->width,
+ frameh = format_ctx->streams[vstream]->codec->height;
+ const float
+ num = (float)(format_ctx->streams[vstream]->r_frame_rate).num,
+ den = (float)(format_ctx->streams[vstream]->r_frame_rate).den,
+ fps = num/den;
+ // Return infos as a list.
+ assign(2);
+ (*this)[0].assign(1,4).fill((T)nb_frames,(T)framew,(T)frameh,(T)fps);
+ (*this)[1] = timestamps.get_append('y');
+ }
+ av_close_input_file(format_ctx); format_ctx = 0;
+ return *this;
+ }
+
+ for (vstream = 0; vstream<(int)(format_ctx->nb_streams) &&
+ format_ctx->streams[vstream]->codec->codec_type!=CODEC_TYPE_VIDEO; ) ++vstream;
+ if (vstream==(int)format_ctx->nb_streams) {
+ cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve video stream.\n"
+ "Trying with external ffmpeg executable.",
+ pixel_type(),filename);
+ av_close_input_file(format_ctx); format_ctx = 0;
+ return load_ffmpeg_external(filename);
+ }
+ codec_ctx = format_ctx->streams[vstream]->codec;
+ codec = avcodec_find_decoder(codec_ctx->codec_id);
+ if (!codec) {
+ cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot find video codec.\n"
+ "Trying with external ffmpeg executable.",
+ pixel_type(),filename);
+ return load_ffmpeg_external(filename);
+ }
+ if (avcodec_open(codec_ctx,codec)<0) { // Open codec
+ cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot open video codec.\n"
+ "Trying with external ffmpeg executable.",
+ pixel_type(),filename);
+ return load_ffmpeg_external(filename);
+ }
+ }
+
+ // Read video frames
+ const unsigned int numBytes = avpicture_get_size(ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
+ uint8_t *const buffer = new uint8_t[numBytes];
+ avpicture_fill((AVPicture *)converted_frame,buffer,ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
+ const T foo = (T)0;
+ AVPacket packet;
+ for (unsigned int frame = 0, next_frame = nfirst_frame; frame<=nlast_frame && av_read_frame(format_ctx,&packet)>=0; ) {
+ if (packet.stream_index==(int)vstream) {
+ int decoded = 0;
+ avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data,packet.size);
+ if (decoded) {
+ if (frame==next_frame) {
+ SwsContext *c = sws_getContext(codec_ctx->width,codec_ctx->height,codec_ctx->pix_fmt,codec_ctx->width,
+ codec_ctx->height,ffmpeg_pixfmt,1,0,0,0);
+ sws_scale(c,avframe->data,avframe->linesize,0,codec_ctx->height,converted_frame->data,converted_frame->linesize);
+ if (ffmpeg_pixfmt==PIX_FMT_RGB24) {
+ CImg<ucharT> next_image(*converted_frame->data,3,codec_ctx->width,codec_ctx->height,1,true);
+ insert(next_image._get_permute_axes("yzvx",foo));
+ } else {
+ CImg<ucharT> next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true);
+ insert(next_image._get_permute_axes("yzvx",foo));
+ }
+ next_frame+=nstep_frame;
+ }
+ ++frame;
+ }
+ av_free_packet(&packet);
+ if (next_frame>nlast_frame) break;
+ }
+ }
+ delete[] buffer;
+#endif
+ return *this;
+ }
+
+ static CImgList<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool pixel_format=true) {
+ return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format);
+ }
+
+ //! Load an image from a video file (MPEG,AVI) using the external tool 'ffmpeg'.
+ CImgList<T>& load_ffmpeg_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException("CImgList<%s>::load_ffmpeg_external() : Cannot load (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512], filetmp2[512];
+ cimg_std::FILE *file = 0;
+ do {
+ cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
+ cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
+ if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ cimg_std::sprintf(filetmp2,"%s_%%6d.ppm",filetmp);
+#if cimg_OS!=2
+ cimg_std::sprintf(command,"%s -i \"%s\" %s >/dev/null 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
+#else
+ cimg_std::sprintf(command,"\"%s -i \"%s\" %s\" >NUL 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
+#endif
+ cimg::system(command,0);
+ const unsigned int odebug = cimg::exception_mode();
+ cimg::exception_mode() = 0;
+ assign();
+ unsigned int i = 1;
+ for (bool stopflag = false; !stopflag; ++i) {
+ cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,i);
+ CImg<T> img;
+ try { img.load_pnm(filetmp2); }
+ catch (CImgException&) { stopflag = true; }
+ if (img) { insert(img); cimg_std::remove(filetmp2); }
+ }
+ cimg::exception_mode() = odebug;
+ if (is_empty())
+ throw CImgIOException("CImgList<%s>::load_ffmpeg_external() : Failed to open image sequence '%s'.\n"
+ "Check the filename and if the 'ffmpeg' tool is installed on your system.",
+ pixel_type(),filename);
+ return *this;
+ }
+
+ static CImgList<T> get_load_ffmpeg_external(const char *const filename) {
+ return CImgList<T>().load_ffmpeg_external(filename);
+ }
+
+ //! Load a gzipped list, using external tool 'gunzip'.
+ CImgList<T>& load_gzip_external(const char *const filename) {
+ if (!filename)
+ throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512], body[512];
+ const char
+ *ext = cimg::split_filename(filename,body),
+ *ext2 = cimg::split_filename(body,0);
+ cimg_std::FILE *file = 0;
+ do {
+ if (!cimg::strcasecmp(ext,"gz")) {
+ if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext2);
+ else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ } else {
+ if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext);
+ else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ }
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
+ cimg::system(command);
+ if (!(file = cimg_std::fopen(filetmp,"rb"))) {
+ cimg::fclose(cimg::fopen(filename,"r"));
+ throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.",
+ pixel_type(),filename);
+ } else cimg::fclose(file);
+ load(filetmp);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ static CImgList<T> get_load_gzip_external(const char *const filename) {
+ return CImgList<T>().load_gzip_external(filename);
+ }
+
+ //! Load a 3D object from a .OFF file.
+ template<typename tf, typename tc>
+ CImgList<T>& load_off(const char *const filename,
+ CImgList<tf>& primitives, CImgList<tc>& colors,
+ const bool invert_faces=false) {
+ return get_load_off(filename,primitives,colors,invert_faces).transfer_to(*this);
+ }
+
+ template<typename tf, typename tc>
+ static CImgList<T> get_load_off(const char *const filename,
+ CImgList<tf>& primitives, CImgList<tc>& colors,
+ const bool invert_faces=false) {
+ return CImg<T>().load_off(filename,primitives,colors,invert_faces).get_split('x');
+ }
+
+ //! Load a TIFF file.
+ CImgList<T>& load_tiff(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1) {
+ const unsigned int
+ nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+ nstep_frame = step_frame?step_frame:1;
+ unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
+#ifndef cimg_use_tiff
+ if (nfirst_frame || nlast_frame!=~0U || nstep_frame!=1)
+ throw CImgArgumentException("CImgList<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
+ "('cimg_use_tiff' must be defined).",
+ pixel_type(),filename);
+ return assign(CImg<T>::get_load_tiff(filename));
+#else
+ TIFF *tif = TIFFOpen(filename,"r");
+ if (tif) {
+ unsigned int nb_images = 0;
+ do ++nb_images; while (TIFFReadDirectory(tif));
+ if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
+ cimg::warn("CImgList<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
+ pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
+ if (nfirst_frame>=nb_images) return assign();
+ if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
+ assign(1+(nlast_frame-nfirst_frame)/nstep_frame);
+ TIFFSetDirectory(tif,0);
+#if cimg_debug>=3
+ TIFFSetWarningHandler(0);
+ TIFFSetErrorHandler(0);
+#endif
+ cimglist_for(*this,l) data[l]._load_tiff(tif,nfirst_frame+l*nstep_frame);
+ TIFFClose(tif);
+ } else throw CImgException("CImgList<%s>::load_tiff() : File '%s' cannot be opened.",
+ pixel_type(),filename);
+ return *this;
+#endif
+ }
+
+ static CImgList<T> get_load_tiff(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1) {
+ return CImgList<T>().load_tiff(filename,first_frame,last_frame,step_frame);
+ }
+
+ //! Save an image list into a file.
+ /**
+ Depending on the extension of the given filename, a file format is chosen for the output file.
+ **/
+ const CImgList<T>& save(const char *const filename, const int number=-1) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::save() : File '%s, instance list (%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",size,data);
+ if (!filename)
+ throw CImgArgumentException("CImg<%s>::save() : Instance list (%u,%p), specified filename is (null).",
+ pixel_type(),size,data);
+ const char *ext = cimg::split_filename(filename);
+ char nfilename[1024];
+ const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
+#ifdef cimglist_save_plugin
+ cimglist_save_plugin(fn);
+#endif
+#ifdef cimglist_save_plugin1
+ cimglist_save_plugin1(fn);
+#endif
+#ifdef cimglist_save_plugin2
+ cimglist_save_plugin2(fn);
+#endif
+#ifdef cimglist_save_plugin3
+ cimglist_save_plugin3(fn);
+#endif
+#ifdef cimglist_save_plugin4
+ cimglist_save_plugin4(fn);
+#endif
+#ifdef cimglist_save_plugin5
+ cimglist_save_plugin5(fn);
+#endif
+#ifdef cimglist_save_plugin6
+ cimglist_save_plugin6(fn);
+#endif
+#ifdef cimglist_save_plugin7
+ cimglist_save_plugin7(fn);
+#endif
+#ifdef cimglist_save_plugin8
+ cimglist_save_plugin8(fn);
+#endif
+#ifdef cimg_use_tiff
+ if (!cimg::strcasecmp(ext,"tif") ||
+ !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
+#endif
+ if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
+ if (!cimg::strcasecmp(ext,"cimg") || !ext[0]) return save_cimg(fn,false);
+ if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
+ if (!cimg::strcasecmp(ext,"avi") ||
+ !cimg::strcasecmp(ext,"mov") ||
+ !cimg::strcasecmp(ext,"asf") ||
+ !cimg::strcasecmp(ext,"divx") ||
+ !cimg::strcasecmp(ext,"flv") ||
+ !cimg::strcasecmp(ext,"mpg") ||
+ !cimg::strcasecmp(ext,"m1v") ||
+ !cimg::strcasecmp(ext,"m2v") ||
+ !cimg::strcasecmp(ext,"m4v") ||
+ !cimg::strcasecmp(ext,"mjp") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
+ if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
+ if (size==1) data[0].save(fn,-1); else cimglist_for(*this,l) data[l].save(fn,l);
+ return *this;
+ }
+
+ //! Save an image sequence, using FFMPEG library.
+ // This piece of code has been originally written by David. G. Starkweather.
+ const CImgList<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int fps=25) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', instance list (%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",size,data);
+ if (!filename)
+ throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : Instance list (%u,%p), specified filename is (null).",
+ pixel_type(),size,data);
+ if (!fps)
+ throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified framerate is 0.",
+ pixel_type(),filename);
+ const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame;
+ if (first_frame>=size || nlast_frame>=size)
+ throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified frames [%u,%u] are out of list range (%u elements).",
+ pixel_type(),filename,first_frame,last_frame,size);
+ for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0]))
+ throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', images of the sequence have different dimensions.",
+ pixel_type(),filename);
+
+#ifndef cimg_use_ffmpeg
+ return save_ffmpeg_external(filename,first_frame,last_frame);
+#else
+ avcodec_register_all();
+ av_register_all();
+ const int
+ frame_dimx = data[first_frame].dimx(),
+ frame_dimy = data[first_frame].dimy(),
+ frame_dimv = data[first_frame].dimv();
+ if (frame_dimv!=1 && frame_dimv!=3)
+ throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', image[0] (%u,%u,%u,%u,%p) has not 1 or 3 channels.",
+ pixel_type(),filename,data[0].width,data[0].height,data[0].depth,data[0].dim,data);
+
+ PixelFormat dest_pxl_fmt = PIX_FMT_YUV420P;
+ PixelFormat src_pxl_fmt = (frame_dimv == 3)?PIX_FMT_RGB24:PIX_FMT_GRAY8;
+
+ int sws_flags = SWS_FAST_BILINEAR; // Interpolation method (keeping same size images for now).
+ AVOutputFormat *fmt = 0;
+ fmt = guess_format(0,filename,0);
+ if (!fmt) fmt = guess_format("mpeg",0,0); // Default format "mpeg".
+ if (!fmt)
+ throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', could not determine file format from filename.",
+ pixel_type(),filename);
+
+ AVFormatContext *oc = 0;
+ oc = av_alloc_format_context();
+ if (!oc) // Failed to allocate format context.
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate structure for format context.",
+ pixel_type(),filename);
+
+ AVCodec *codec = 0;
+ AVFrame *picture = 0;
+ AVFrame *tmp_pict = 0;
+ oc->oformat = fmt;
+ cimg_std::sprintf(oc->filename,"%s",filename);
+
+ // Add video stream.
+ int stream_index = 0;
+ AVStream *video_str = 0;
+ if (fmt->video_codec!=CODEC_ID_NONE) {
+ video_str = av_new_stream(oc,stream_index);
+ if (!video_str) { // Failed to allocate stream.
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate video stream structure.",
+ pixel_type(),filename);
+ }
+ } else { // No codec identified.
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no proper codec identified.",
+ pixel_type(),filename);
+ }
+
+ AVCodecContext *c = video_str->codec;
+ c->codec_id = fmt->video_codec;
+ c->codec_type = CODEC_TYPE_VIDEO;
+ c->bit_rate = 400000;
+ c->width = frame_dimx;
+ c->height = frame_dimy;
+ c->time_base.num = 1;
+ c->time_base.den = fps;
+ c->gop_size = 12;
+ c->pix_fmt = dest_pxl_fmt;
+ if (c->codec_id == CODEC_ID_MPEG2VIDEO) c->max_b_frames = 2;
+ if (c->codec_id == CODEC_ID_MPEG1VIDEO) c->mb_decision = 2;
+
+ if (av_set_parameters(oc,0)<0) { // Parameters not properly set.
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', parameters for avcodec not properly set.",
+ pixel_type(),filename);
+ }
+
+ // Open codecs and alloc buffers.
+ codec = avcodec_find_encoder(c->codec_id);
+ if (!codec) { // Failed to find codec.
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no codec found.",
+ pixel_type(),filename);
+ }
+ if (avcodec_open(c,codec)<0) // Failed to open codec.
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to open codec.",
+ pixel_type(),filename);
+ tmp_pict = avcodec_alloc_frame();
+ if (!tmp_pict) { // Failed to allocate memory for tmp_pict frame.
+ avcodec_close(video_str->codec);
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.",
+ pixel_type(),filename);
+ }
+ tmp_pict->linesize[0] = (src_pxl_fmt==PIX_FMT_RGB24)?3*frame_dimx:frame_dimx;
+ tmp_pict->type = FF_BUFFER_TYPE_USER;
+ int tmp_size = avpicture_get_size(src_pxl_fmt,frame_dimx,frame_dimy);
+ uint8_t *tmp_buffer = (uint8_t*)av_malloc(tmp_size);
+ if (!tmp_buffer) { // Failed to allocate memory for tmp buffer.
+ av_free(tmp_pict);
+ avcodec_close(video_str->codec);
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.",
+ pixel_type(),filename);
+ }
+
+ // Associate buffer with tmp_pict.
+ avpicture_fill((AVPicture*)tmp_pict,tmp_buffer,src_pxl_fmt,frame_dimx,frame_dimy);
+ picture = avcodec_alloc_frame();
+ if (!picture) { // Failed to allocate picture frame.
+ av_free(tmp_pict->data[0]);
+ av_free(tmp_pict);
+ avcodec_close(video_str->codec);
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame.",
+ pixel_type(),filename);
+ }
+
+ int size = avpicture_get_size(c->pix_fmt,frame_dimx,frame_dimy);
+ uint8_t *buffer = (uint8_t*)av_malloc(size);
+ if (!buffer) { // Failed to allocate picture frame buffer.
+ av_free(picture);
+ av_free(tmp_pict->data[0]);
+ av_free(tmp_pict);
+ avcodec_close(video_str->codec);
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame buffer.",
+ pixel_type(),filename);
+ }
+
+ // Associate the buffer with picture.
+ avpicture_fill((AVPicture*)picture,buffer,c->pix_fmt,frame_dimx,frame_dimy);
+
+ // Open file.
+ if (!(fmt->flags&AVFMT_NOFILE)) {
+ if (url_fopen(&oc->pb,filename,URL_WRONLY)<0)
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s' cannot be opened.",
+ pixel_type(),filename);
+ }
+
+ if (av_write_header(oc)<0)
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', could not write header.",
+ pixel_type(),filename);
+ double video_pts;
+ SwsContext *img_convert_context = 0;
+ img_convert_context = sws_getContext(frame_dimx,frame_dimy,src_pxl_fmt,
+ c->width,c->height,c->pix_fmt,sws_flags,0,0,0);
+ if (!img_convert_context) { // Failed to get swscale context.
+ // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
+ av_free(picture->data);
+ av_free(picture);
+ av_free(tmp_pict->data[0]);
+ av_free(tmp_pict);
+ avcodec_close(video_str->codec);
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%', failed to get conversion context.",
+ pixel_type(),filename);
+ }
+ int ret = 0, out_size;
+ uint8_t *video_outbuf = 0;
+ int video_outbuf_size = 1000000;
+ video_outbuf = (uint8_t*)av_malloc(video_outbuf_size);
+ if (!video_outbuf) {
+ // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
+ av_free(picture->data);
+ av_free(picture);
+ av_free(tmp_pict->data[0]);
+ av_free(tmp_pict);
+ avcodec_close(video_str->codec);
+ av_free(oc);
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', memory allocation error.",
+ pixel_type(),filename);
+ }
+
+ // Loop through each desired image in list.
+ for (unsigned int i = first_frame; i<=nlast_frame; ++i) {
+ CImg<uint8_t> currentIm = data[i], red, green, blue, gray;
+ if (src_pxl_fmt == PIX_FMT_RGB24) {
+ red = currentIm.get_shared_channel(0);
+ green = currentIm.get_shared_channel(1);
+ blue = currentIm.get_shared_channel(2);
+ cimg_forXY(currentIm,X,Y) { // Assign pizel values to data buffer in interlaced RGBRGB ... format.
+ tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X] = red(X,Y);
+ tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 1] = green(X,Y);
+ tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 2] = blue(X,Y);
+ }
+ } else {
+ gray = currentIm.get_shared_channel(0);
+ cimg_forXY(currentIm,X,Y) tmp_pict->data[0][Y*tmp_pict->linesize[0] + X] = gray(X,Y);
+ }
+
+ if (video_str) video_pts = (video_str->pts.val * video_str->time_base.num)/(video_str->time_base.den);
+ else video_pts = 0.0;
+ if (!video_str) break;
+ if (sws_scale(img_convert_context,tmp_pict->data,tmp_pict->linesize,0,c->height,picture->data,picture->linesize)<0) break;
+ out_size = avcodec_encode_video(c,video_outbuf,video_outbuf_size,picture);
+ if (out_size>0) {
+ AVPacket pkt;
+ av_init_packet(&pkt);
+ pkt.pts = av_rescale_q(c->coded_frame->pts,c->time_base,video_str->time_base);
+ if (c->coded_frame->key_frame) pkt.flags|=PKT_FLAG_KEY;
+ pkt.stream_index = video_str->index;
+ pkt.data = video_outbuf;
+ pkt.size = out_size;
+ ret = av_write_frame(oc,&pkt);
+ } else if (out_size<0) break;
+ if (ret) break; // Error occured in writing frame.
+ }
+
+ // Close codec.
+ if (video_str) {
+ avcodec_close(video_str->codec);
+ av_free(picture->data[0]);
+ av_free(picture);
+ av_free(tmp_pict->data[0]);
+ av_free(tmp_pict);
+ }
+ if (av_write_trailer(oc)<0)
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to write trailer.",
+ pixel_type(),filename);
+ av_freep(&oc->streams[stream_index]->codec);
+ av_freep(&oc->streams[stream_index]);
+ if (!(fmt->flags&AVFMT_NOFILE)) {
+ /*if (url_fclose(oc->pb)<0)
+ throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to close file.",
+ pixel_type(),filename);
+ */
+ }
+ av_free(oc);
+ av_free(video_outbuf);
+#endif
+ return *this;
+ }
+
+ // Save an image sequence into a YUV file (internal).
+ const CImgList<T>& _save_yuv(cimg_std::FILE *const file, const char *const filename, const bool rgb2yuv) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', instance list (%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",size,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_yuv() : Instance list (%u,%p), specified file is (null).",
+ pixel_type(),size,data);
+ if ((*this)[0].dimx()%2 || (*this)[0].dimy()%2)
+ throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', image dimensions must be even numbers (current are %ux%u).",
+ pixel_type(),filename?filename:"(FILE*)",(*this)[0].dimx(),(*this)[0].dimy());
+
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ cimglist_for(*this,l) {
+ CImg<ucharT> YCbCr((*this)[l]);
+ if (rgb2yuv) YCbCr.RGBtoYCbCr();
+ cimg::fwrite(YCbCr.data,YCbCr.width*YCbCr.height,nfile);
+ cimg::fwrite(YCbCr.get_resize(YCbCr.width/2, YCbCr.height/2,1,3,3).ptr(0,0,0,1),
+ YCbCr.width*YCbCr.height/2,nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save an image sequence into a YUV file.
+ const CImgList<T>& save_yuv(const char *const filename=0, const bool rgb2yuv=true) const {
+ return _save_yuv(0,filename,rgb2yuv);
+ }
+
+ //! Save an image sequence into a YUV file.
+ const CImgList<T>& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const {
+ return _save_yuv(file,0,rgb2yuv);
+ }
+
+ //! Save an image list into a .cimg file.
+ /**
+ A CImg RAW file is a simple uncompressed binary file that may be used to save list of CImg<T> images.
+ \param filename : name of the output file.
+ \return A reference to the current CImgList instance is returned.
+ **/
+ const CImgList<T>& _save_cimg(cimg_std::FILE *const file, const char *const filename, const bool compression) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",size,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
+ pixel_type(),size,data);
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little";
+ if (cimg_std::strstr(ptype,"unsigned")==ptype) cimg_std::fprintf(nfile,"%u unsigned_%s %s_endian\n",size,ptype+9,etype);
+ else cimg_std::fprintf(nfile,"%u %s %s_endian\n",size,ptype,etype);
+ cimglist_for(*this,l) {
+ const CImg<T>& img = data[l];
+ cimg_std::fprintf(nfile,"%u %u %u %u",img.width,img.height,img.depth,img.dim);
+ if (img.data) {
+ CImg<T> tmp;
+ if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp.data,tmp.size()); }
+ const CImg<T>& ref = cimg::endianness()?tmp:img;
+ bool compressed = false;
+ if (compression) {
+#ifdef cimg_use_zlib
+ const unsigned long siz = sizeof(T)*ref.size();
+ unsigned long csiz = siz + siz/10 + 16;
+ Bytef *const cbuf = new Bytef[csiz];
+ if (compress(cbuf,&csiz,(Bytef*)ref.data,siz)) {
+ cimg::warn("CImgList<%s>::save_cimg() : File '%s', failed to save compressed data.\n Data will be saved uncompressed.",
+ pixel_type(),filename?filename:"(FILE*)");
+ compressed = false;
+ } else {
+ cimg_std::fprintf(nfile," #%lu\n",csiz);
+ cimg::fwrite(cbuf,csiz,nfile);
+ delete[] cbuf;
+ compressed = true;
+ }
+#else
+ cimg::warn("CImgList<%s>::save_cimg() : File '%s', cannot save compressed data unless zlib is used "
+ "('cimg_use_zlib' must be defined).\n Data will be saved uncompressed.",
+ pixel_type(),filename?filename:"(FILE*)");
+ compressed = false;
+#endif
+ }
+ if (!compressed) {
+ cimg_std::fputc('\n',nfile);
+ cimg::fwrite(ref.data,ref.size(),nfile);
+ }
+ } else cimg_std::fputc('\n',nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save an image list into a CImg file (RAW binary file + simple header)
+ const CImgList<T>& save_cimg(cimg_std::FILE *file, const bool compress=false) const {
+ return _save_cimg(file,0,compress);
+ }
+
+ //! Save an image list into a CImg file (RAW binary file + simple header)
+ const CImgList<T>& save_cimg(const char *const filename, const bool compress=false) const {
+ return _save_cimg(0,filename,compress);
+ }
+
+ // Insert the instance image into into an existing .cimg file, at specified coordinates.
+ const CImgList<T>& _save_cimg(cimg_std::FILE *const file, const char *const filename,
+ const unsigned int n0,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int v0) const {
+#define _cimg_save_cimg_case(Ts,Tss) \
+ if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+ for (unsigned int l=0; l<lmax; ++l) { \
+ j = 0; while((i=cimg_std::fgetc(nfile))!='\n') tmp[j++]=(char)i; tmp[j]='\0'; \
+ W = H = D = V = 0; \
+ if (cimg_std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
+ throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
+ pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
+ if (W*H*D*V>0) { \
+ if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
+ else { \
+ const CImg<T>& img = (*this)[l-n0]; \
+ const T *ptrs = img.data; \
+ const unsigned int \
+ x1 = x0 + img.width - 1, \
+ y1 = y0 + img.height - 1, \
+ z1 = z0 + img.depth - 1, \
+ v1 = v0 + img.dim - 1, \
+ nx1 = x1>=W?W-1:x1, \
+ ny1 = y1>=H?H-1:y1, \
+ nz1 = z1>=D?D-1:z1, \
+ nv1 = v1>=V?V-1:v1; \
+ CImg<Tss> raw(1+nx1-x0); \
+ const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
+ if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \
+ for (unsigned int v=1+nv1-v0; v; --v) { \
+ const unsigned int skipzb = z0*W*H*sizeof(Tss); \
+ if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \
+ for (unsigned int z=1+nz1-z0; z; --z) { \
+ const unsigned int skipyb = y0*W*sizeof(Tss); \
+ if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \
+ for (unsigned int y=1+ny1-y0; y; --y) { \
+ const unsigned int skipxb = x0*sizeof(Tss); \
+ if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \
+ raw.assign(ptrs, raw.width); \
+ ptrs+=img.width; \
+ if (endian) cimg::invert_endianness(raw.data,raw.width); \
+ cimg::fwrite(raw.data,raw.width,nfile); \
+ const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \
+ if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \
+ } \
+ const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
+ if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \
+ } \
+ const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
+ if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \
+ } \
+ const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \
+ if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \
+ } \
+ } \
+ } \
+ saved = true; \
+ }
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.",
+ pixel_type(),filename?filename:"(FILE*)",size,data);
+ if (!file && !filename)
+ throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
+ pixel_type(),size,data);
+ typedef unsigned char uchar;
+ typedef unsigned short ushort;
+ typedef unsigned int uint;
+ typedef unsigned long ulong;
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+");
+ bool saved = false, endian = cimg::endianness();
+ char tmp[256], str_pixeltype[256], str_endian[256];
+ unsigned int j, err, N, W, H, D, V;
+ int i;
+ j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
+ err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
+ if (err<2) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Unknow CImg RAW header.",
+ pixel_type(),filename?filename:"(FILE*)");
+ }
+ if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
+ else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
+ const unsigned int lmax = cimg::min(N,n0+size);
+ _cimg_save_cimg_case("bool",bool);
+ _cimg_save_cimg_case("unsigned_char",uchar);
+ _cimg_save_cimg_case("uchar",uchar);
+ _cimg_save_cimg_case("char",char);
+ _cimg_save_cimg_case("unsigned_short",ushort);
+ _cimg_save_cimg_case("ushort",ushort);
+ _cimg_save_cimg_case("short",short);
+ _cimg_save_cimg_case("unsigned_int",uint);
+ _cimg_save_cimg_case("uint",uint);
+ _cimg_save_cimg_case("int",int);
+ _cimg_save_cimg_case("unsigned_long",ulong);
+ _cimg_save_cimg_case("ulong",ulong);
+ _cimg_save_cimg_case("long",long);
+ _cimg_save_cimg_case("float",float);
+ _cimg_save_cimg_case("double",double);
+ if (!saved) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', cannot save images of pixels coded as '%s'.",
+ pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Insert the instance image into into an existing .cimg file, at specified coordinates.
+ const CImgList<T>& save_cimg(const char *const filename,
+ const unsigned int n0,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int v0) const {
+ return _save_cimg(0,filename,n0,x0,y0,z0,v0);
+ }
+
+ //! Insert the instance image into into an existing .cimg file, at specified coordinates.
+ const CImgList<T>& save_cimg(cimg_std::FILE *const file,
+ const unsigned int n0,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int v0) const {
+ return _save_cimg(file,0,n0,x0,y0,z0,v0);
+ }
+
+ // Create an empty .cimg file with specified dimensions (internal)
+ static void _save_empty_cimg(cimg_std::FILE *const file, const char *const filename,
+ const unsigned int nb,
+ const unsigned int dx, const unsigned int dy,
+ const unsigned int dz, const unsigned int dv) {
+ cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const unsigned int siz = dx*dy*dz*dv*sizeof(T);
+ cimg_std::fprintf(nfile,"%u %s\n",nb,pixel_type());
+ for (unsigned int i=nb; i; --i) {
+ cimg_std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dv);
+ for (unsigned int off=siz; off; --off) cimg_std::fputc(0,nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ }
+
+ //! Create an empty .cimg file with specified dimensions.
+ static void save_empty_cimg(const char *const filename,
+ const unsigned int nb,
+ const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1) {
+ return _save_empty_cimg(0,filename,nb,dx,dy,dz,dv);
+ }
+
+ //! Create an empty .cimg file with specified dimensions.
+ static void save_empty_cimg(cimg_std::FILE *const file,
+ const unsigned int nb,
+ const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dv=1) {
+ return _save_empty_cimg(file,0,nb,dx,dy,dz,dv);
+ }
+
+ //! Save a file in TIFF format.
+#ifdef cimg_use_tiff
+ const CImgList<T>& save_tiff(const char *const filename) const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::save_tiff() : File '%s', instance list (%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",size,data);
+ if (!filename)
+ throw CImgArgumentException("CImgList<%s>::save_tiff() : Specified filename is (null) for instance list (%u,%p).",
+ pixel_type(),size,data);
+ TIFF *tif = TIFFOpen(filename,"w");
+ if (tif) {
+ for (unsigned int dir=0, l=0; l<size; ++l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) {
+ if (img.depth==1) img._save_tiff(tif,dir++);
+ else cimg_forZ(img,z) img.get_slice(z)._save_tiff(tif,dir++);
+ }
+ }
+ TIFFClose(tif);
+ } else
+ throw CImgException("CImgList<%s>::save_tiff() : File '%s', error while opening stream for tiff file.",
+ pixel_type(),filename);
+ return *this;
+ }
+#endif
+
+ //! Save an image list as a gzipped file, using external tool 'gzip'.
+ const CImgList<T>& save_gzip_external(const char *const filename) const {
+ if (!filename)
+ throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.",
+ pixel_type());
+ char command[1024], filetmp[512], body[512];
+ const char
+ *ext = cimg::split_filename(filename,body),
+ *ext2 = cimg::split_filename(body,0);
+ cimg_std::FILE *file;
+ do {
+ if (!cimg::strcasecmp(ext,"gz")) {
+ if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext2);
+ else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ } else {
+ if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand(),ext);
+ else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
+ cimg::filenamerand());
+ }
+ if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ save(filetmp);
+ cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
+ cimg::system(command);
+ file = cimg_std::fopen(filename,"rb");
+ if (!file)
+ throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.",
+ pixel_type(),filename);
+ else cimg::fclose(file);
+ cimg_std::remove(filetmp);
+ return *this;
+ }
+
+ //! Save an image list into a OFF file.
+ template<typename tf, typename tc>
+ const CImgList<T>& save_off(const char *const filename,
+ const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
+ get_append('x','y').save_off(filename,primitives,colors,invert_faces);
+ return *this;
+ }
+
+ //! Save an image list into a OFF file.
+ template<typename tf, typename tc>
+ const CImgList<T>& save_off(cimg_std::FILE *const file,
+ const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
+ get_append('x','y').save_off(file,primitives,colors,invert_faces);
+ return *this;
+ }
+
+ //! Save an image sequence using the external tool 'ffmpeg'.
+ const CImgList<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const char *const codec="mpeg2video") const {
+ if (is_empty())
+ throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', instance list (%u,%p) is empty.",
+ pixel_type(),filename?filename:"(null)",size,data);
+ if (!filename)
+ throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : Instance list (%u,%p), specified filename is (null).",
+ pixel_type(),size,data);
+ char command[1024], filetmp[512], filetmp2[512];
+ cimg_std::FILE *file = 0;
+ const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame;
+ if (first_frame>=size || nlast_frame>=size)
+ throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : File '%s', specified frames [%u,%u] are out of list range (%u elements).",
+ pixel_type(),filename,first_frame,last_frame,size);
+ for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0]))
+ throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', all images of the sequence must be of the same dimension.",
+ pixel_type(),filename);
+ do {
+ cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
+ cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
+ if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file);
+ } while (file);
+ for (unsigned int l = first_frame; l<=nlast_frame; ++l) {
+ cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,l+1);
+ if (data[l].depth>1 || data[l].dim!=3) data[l].get_resize(-100,-100,1,3).save_pnm(filetmp2);
+ else data[l].save_pnm(filetmp2);
+ }
+#if cimg_OS!=2
+ cimg_std::sprintf(command,"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\" >/dev/null 2>&1",filetmp,codec,filename);
+#else
+ cimg_std::sprintf(command,"\"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\"\" >NUL 2>&1",filetmp,codec,filename);
+#endif
+ cimg::system(command);
+ file = cimg_std::fopen(filename,"rb");
+ if (!file)
+ throw CImgIOException("CImg<%s>::save_ffmpeg_external() : Failed to save image sequence '%s'.\n\n",
+ pixel_type(),filename);
+ else cimg::fclose(file);
+ cimglist_for(*this,lll) { cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,lll+1); cimg_std::remove(filetmp2); }
+ return *this;
+ }
+
+ };
+
+ /*
+ #---------------------------------------------
+ #
+ # Completion of previously declared functions
+ #
+ #----------------------------------------------
+ */
+
+namespace cimg {
+
+ //! Display a dialog box, where a user can click standard buttons.
+ /**
+ Up to 6 buttons can be defined in the dialog window.
+ This function returns when a user clicked one of the button or closed the dialog window.
+ \param title = Title of the dialog window.
+ \param msg = Main message displayed inside the dialog window.
+ \param button1_txt = Label of the 1st button.
+ \param button2_txt = Label of the 2nd button.
+ \param button3_txt = Label of the 3rd button.
+ \param button4_txt = Label of the 4th button.
+ \param button5_txt = Label of the 5th button.
+ \param button6_txt = Label of the 6th button.
+ \param logo = Logo image displayed at the left of the main message. This parameter is optional.
+ \param centering = Tell to center the dialog window on the screen.
+ \return The button number (from 0 to 5), or -1 if the dialog window has been closed by the user.
+ \note If a button text is set to 0, then the corresponding button (and the followings) won't appear in
+ the dialog box. At least one button is necessary.
+ **/
+
+ template<typename t>
+ inline int dialog(const char *title, const char *msg,
+ const char *button1_txt, const char *button2_txt,
+ const char *button3_txt, const char *button4_txt,
+ const char *button5_txt, const char *button6_txt,
+ const CImg<t>& logo, const bool centering = false) {
+#if cimg_display!=0
+ const unsigned char
+ black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 200,200,200 }, gray2[] = { 150,150,150 };
+
+ // Create buttons and canvas graphics
+ CImgList<unsigned char> buttons, cbuttons, sbuttons;
+ if (button1_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button1_txt,black,gray,1,13));
+ if (button2_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button2_txt,black,gray,1,13));
+ if (button3_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button3_txt,black,gray,1,13));
+ if (button4_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button4_txt,black,gray,1,13));
+ if (button5_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button5_txt,black,gray,1,13));
+ if (button6_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button6_txt,black,gray,1,13));
+ }}}}}}
+ if (!buttons.size)
+ throw CImgArgumentException("cimg::dialog() : No buttons have been defined. At least one is necessary");
+
+ unsigned int bw = 0, bh = 0;
+ cimglist_for(buttons,l) { bw = cimg::max(bw,buttons[l].width); bh = cimg::max(bh,buttons[l].height); }
+ bw+=8; bh+=8;
+ if (bw<64) bw=64;
+ if (bw>128) bw=128;
+ if (bh<24) bh=24;
+ if (bh>48) bh=48;
+
+ CImg<unsigned char> button(bw,bh,1,3);
+ button.draw_rectangle(0,0,bw-1,bh-1,gray);
+ button.draw_line(0,0,bw-1,0,white).draw_line(0,bh-1,0,0,white);
+ button.draw_line(bw-1,0,bw-1,bh-1,black).draw_line(bw-1,bh-1,0,bh-1,black);
+ button.draw_line(1,bh-2,bw-2,bh-2,gray2).draw_line(bw-2,bh-2,bw-2,1,gray2);
+ CImg<unsigned char> sbutton(bw,bh,1,3);
+ sbutton.draw_rectangle(0,0,bw-1,bh-1,gray);
+ sbutton.draw_line(0,0,bw-1,0,black).draw_line(bw-1,0,bw-1,bh-1,black);
+ sbutton.draw_line(bw-1,bh-1,0,bh-1,black).draw_line(0,bh-1,0,0,black);
+ sbutton.draw_line(1,1,bw-2,1,white).draw_line(1,bh-2,1,1,white);
+ sbutton.draw_line(bw-2,1,bw-2,bh-2,black).draw_line(bw-2,bh-2,1,bh-2,black);
+ sbutton.draw_line(2,bh-3,bw-3,bh-3,gray2).draw_line(bw-3,bh-3,bw-3,2,gray2);
+ sbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
+ sbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);
+ CImg<unsigned char> cbutton(bw,bh,1,3);
+ cbutton.draw_rectangle(0,0,bw-1,bh-1,black).draw_rectangle(1,1,bw-2,bh-2,gray2).draw_rectangle(2,2,bw-3,bh-3,gray);
+ cbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
+ cbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);
+
+ cimglist_for(buttons,ll) {
+ cbuttons.insert(CImg<unsigned char>(cbutton).draw_image(1+(bw-buttons[ll].dimx())/2,1+(bh-buttons[ll].dimy())/2,buttons[ll]));
+ sbuttons.insert(CImg<unsigned char>(sbutton).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll]));
+ buttons[ll] = CImg<unsigned char>(button).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll]);
+ }
+
+ CImg<unsigned char> canvas;
+ if (msg) canvas = CImg<unsigned char>().draw_text(0,0,msg,black,gray,1,13);
+ const unsigned int
+ bwall = (buttons.size-1)*(12+bw) + bw,
+ w = cimg::max(196U,36+logo.width+canvas.width, 24+bwall),
+ h = cimg::max(96U,36+canvas.height+bh,36+logo.height+bh),
+ lx = 12 + (canvas.data?0:((w-24-logo.width)/2)),
+ ly = (h-12-bh-logo.height)/2,
+ tx = lx+logo.width+12,
+ ty = (h-12-bh-canvas.height)/2,
+ bx = (w-bwall)/2,
+ by = h-12-bh;
+
+ if (canvas.data)
+ canvas = CImg<unsigned char>(w,h,1,3).
+ draw_rectangle(0,0,w-1,h-1,gray).
+ draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
+ draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black).
+ draw_image(tx,ty,canvas);
+ else
+ canvas = CImg<unsigned char>(w,h,1,3).
+ draw_rectangle(0,0,w-1,h-1,gray).
+ draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
+ draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black);
+ if (logo.data) canvas.draw_image(lx,ly,logo);
+
+ unsigned int xbuttons[6];
+ cimglist_for(buttons,lll) { xbuttons[lll] = bx+(bw+12)*lll; canvas.draw_image(xbuttons[lll],by,buttons[lll]); }
+
+ // Open window and enter events loop
+ CImgDisplay disp(canvas,title?title:" ",0,false,centering?true:false);
+ if (centering) disp.move((CImgDisplay::screen_dimx()-disp.dimx())/2,
+ (CImgDisplay::screen_dimy()-disp.dimy())/2);
+ bool stopflag = false, refresh = false;
+ int oselected = -1, oclicked = -1, selected = -1, clicked = -1;
+ while (!disp.is_closed && !stopflag) {
+ if (refresh) {
+ if (clicked>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp);
+ else {
+ if (selected>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[selected],by,sbuttons[selected]).display(disp);
+ else canvas.display(disp);
+ }
+ refresh = false;
+ }
+ disp.wait(15);
+ if (disp.is_resized) disp.resize(disp);
+
+ if (disp.button&1) {
+ oclicked = clicked;
+ clicked = -1;
+ cimglist_for(buttons,l)
+ if (disp.mouse_y>=(int)by && disp.mouse_y<(int)(by+bh) &&
+ disp.mouse_x>=(int)xbuttons[l] && disp.mouse_x<(int)(xbuttons[l]+bw)) {
+ clicked = selected = l;
+ refresh = true;
+ }
+ if (clicked!=oclicked) refresh = true;
+ } else if (clicked>=0) stopflag = true;
+
+ if (disp.key) {
+ oselected = selected;
+ switch (disp.key) {
+ case cimg::keyESC : selected=-1; stopflag=true; break;
+ case cimg::keyENTER : if (selected<0) selected = 0; stopflag = true; break;
+ case cimg::keyTAB :
+ case cimg::keyARROWRIGHT :
+ case cimg::keyARROWDOWN : selected = (selected+1)%buttons.size; break;
+ case cimg::keyARROWLEFT :
+ case cimg::keyARROWUP : selected = (selected+buttons.size-1)%buttons.size; break;
+ }
+ disp.key = 0;
+ if (selected!=oselected) refresh = true;
+ }
+ }
+ if (!disp) selected = -1;
+ return selected;
+#else
+ cimg_std::fprintf(cimg_stdout,"<%s>\n\n%s\n\n",title,msg);
+ return -1+0*(int)(button1_txt-button2_txt+button3_txt-button4_txt+button5_txt-button6_txt+logo.width+(int)centering);
+#endif
+ }
+
+ inline int dialog(const char *title, const char *msg,
+ const char *button1_txt, const char *button2_txt, const char *button3_txt,
+ const char *button4_txt, const char *button5_txt, const char *button6_txt,
+ const bool centering) {
+ return dialog(title,msg,button1_txt,button2_txt,button3_txt,button4_txt,button5_txt,button6_txt,
+ CImg<unsigned char>::logo40x38(),centering);
+ }
+
+ // End of cimg:: namespace
+}
+
+ // End of cimg_library:: namespace
+}
+
+#ifdef _cimg_redefine_min
+#define min(a,b) (((a)<(b))?(a):(b))
+#endif
+#ifdef _cimg_redefine_max
+#define max(a,b) (((a)>(b))?(a):(b))
+#endif
+
+#endif
diff --git a/src/libs/greycstoration/LICENSE.txt b/src/libs/greycstoration/LICENSE.txt
new file mode 100644
index 00000000..a36324de
--- /dev/null
+++ b/src/libs/greycstoration/LICENSE.txt
@@ -0,0 +1,505 @@
+
+ CeCILL FREE SOFTWARE LICENSE AGREEMENT
+
+
+ Notice
+
+This Agreement is a Free Software license agreement that is the result
+of discussions between its authors in order to ensure compliance with
+the two main principles guiding its drafting:
+
+ * firstly, compliance with the principles governing the distribution
+ of Free Software: access to source code, broad rights granted to
+ users,
+ * secondly, the election of a governing law, French law, with which
+ it is conformant, both as regards the law of torts and
+ intellectual property law, and the protection that it offers to
+ both authors and holders of the economic rights over software.
+
+The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[logiciel] L[ibre])
+license are:
+
+Commissariat l'Energie Atomique - CEA, a public scientific, technical
+and industrial establishment, having its principal place of business at
+31-33 rue de la Fdration, 75752 Paris cedex 15, France.
+
+Centre National de la Recherche Scientifique - CNRS, a public scientific
+and technological establishment, having its principal place of business
+at 3 rue Michel-Ange 75794 Paris cedex 16, France.
+
+Institut National de Recherche en Informatique et en Automatique -
+INRIA, a public scientific and technological establishment, having its
+principal place of business at Domaine de Voluceau, Rocquencourt, BP
+105, 78153 Le Chesnay cedex, France.
+
+
+ Preamble
+
+The purpose of this Free Software license agreement is to grant users
+the right to modify and redistribute the software governed by this
+license within the framework of an open source distribution model.
+
+The exercising of these rights is conditional upon certain obligations
+for users so as to preserve this status for all subsequent redistributions.
+
+In consideration of access to the source code and the rights to copy,
+modify and redistribute granted by the license, users are provided only
+with a limited warranty and the software's author, the holder of the
+economic rights, and the successive licensors only have limited liability.
+
+In this respect, the risks associated with loading, using, modifying
+and/or developing or reproducing the software by the user are brought to
+the user's attention, given its Free Software status, which may make it
+complicated to use, with the result that its use is reserved for
+developers and experienced professionals having in-depth computer
+knowledge. Users are therefore encouraged to load and test the
+Software's suitability as regards their requirements in conditions
+enabling the security of their systems and/or data to be ensured and,
+more generally, to use and operate it in the same conditions of
+security. This Agreement may be freely reproduced and published,
+provided it is not altered, and that no provisions are either added or
+removed herefrom.
+
+This Agreement may apply to any or all software for which the holder of
+the economic rights decides to submit the use thereof to its provisions.
+
+
+ Article 1 - DEFINITIONS
+
+For the purpose of this Agreement, when the following expressions
+commence with a capital letter, they shall have the following meaning:
+
+Agreement: means this license agreement, and its possible subsequent
+versions and annexes.
+
+Software: means the software in its Object Code and/or Source Code form
+and, where applicable, its documentation, "as is" when the Licensee
+accepts the Agreement.
+
+Initial Software: means the Software in its Source Code and possibly its
+Object Code form and, where applicable, its documentation, "as is" when
+it is first distributed under the terms and conditions of the Agreement.
+
+Modified Software: means the Software modified by at least one
+Contribution.
+
+Source Code: means all the Software's instructions and program lines to
+which access is required so as to modify the Software.
+
+Object Code: means the binary files originating from the compilation of
+the Source Code.
+
+Holder: means the holder(s) of the economic rights over the Initial
+Software.
+
+Licensee: means the Software user(s) having accepted the Agreement.
+
+Contributor: means a Licensee having made at least one Contribution.
+
+Licensor: means the Holder, or any other individual or legal entity, who
+distributes the Software under the Agreement.
+
+Contribution: means any or all modifications, corrections, translations,
+adaptations and/or new functions integrated into the Software by any or
+all Contributors, as well as any or all Internal Modules.
+
+Module: means a set of sources files including their documentation that
+enables supplementary functions or services in addition to those offered
+by the Software.
+
+External Module: means any or all Modules, not derived from the
+Software, so that this Module and the Software run in separate address
+spaces, with one calling the other when they are run.
+
+Internal Module: means any or all Module, connected to the Software so
+that they both execute in the same address space.
+
+GNU GPL: means the GNU General Public License version 2 or any
+subsequent version, as published by the Free Software Foundation Inc.
+
+Parties: mean both the Licensee and the Licensor.
+
+These expressions may be used both in singular and plural form.
+
+
+ Article 2 - PURPOSE
+
+The purpose of the Agreement is the grant by the Licensor to the
+Licensee of a non-exclusive, transferable and worldwide license for the
+Software as set forth in Article 5 hereinafter for the whole term of the
+protection granted by the rights over said Software.
+
+
+ Article 3 - ACCEPTANCE
+
+3.1 The Licensee shall be deemed as having accepted the terms and
+conditions of this Agreement upon the occurrence of the first of the
+following events:
+
+ * (i) loading the Software by any or all means, notably, by
+ downloading from a remote server, or by loading from a physical
+ medium;
+ * (ii) the first time the Licensee exercises any of the rights
+ granted hereunder.
+
+3.2 One copy of the Agreement, containing a notice relating to the
+characteristics of the Software, to the limited warranty, and to the
+fact that its use is restricted to experienced users has been provided
+to the Licensee prior to its acceptance as set forth in Article 3.1
+hereinabove, and the Licensee hereby acknowledges that it has read and
+understood it.
+
+
+ Article 4 - EFFECTIVE DATE AND TERM
+
+
+ 4.1 EFFECTIVE DATE
+
+The Agreement shall become effective on the date when it is accepted by
+the Licensee as set forth in Article 3.1.
+
+
+ 4.2 TERM
+
+The Agreement shall remain in force for the entire legal term of
+protection of the economic rights over the Software.
+
+
+ Article 5 - SCOPE OF RIGHTS GRANTED
+
+The Licensor hereby grants to the Licensee, who accepts, the following
+rights over the Software for any or all use, and for the term of the
+Agreement, on the basis of the terms and conditions set forth hereinafter.
+
+Besides, if the Licensor owns or comes to own one or more patents
+protecting all or part of the functions of the Software or of its
+components, the Licensor undertakes not to enforce the rights granted by
+these patents against successive Licensees using, exploiting or
+modifying the Software. If these patents are transferred, the Licensor
+undertakes to have the transferees subscribe to the obligations set
+forth in this paragraph.
+
+
+ 5.1 RIGHT OF USE
+
+The Licensee is authorized to use the Software, without any limitation
+as to its fields of application, with it being hereinafter specified
+that this comprises:
+
+ 1. permanent or temporary reproduction of all or part of the Software
+ by any or all means and in any or all form.
+
+ 2. loading, displaying, running, or storing the Software on any or
+ all medium.
+
+ 3. entitlement to observe, study or test its operation so as to
+ determine the ideas and principles behind any or all constituent
+ elements of said Software. This shall apply when the Licensee
+ carries out any or all loading, displaying, running, transmission
+ or storage operation as regards the Software, that it is entitled
+ to carry out hereunder.
+
+
+ 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS
+
+The right to make Contributions includes the right to translate, adapt,
+arrange, or make any or all modifications to the Software, and the right
+to reproduce the resulting Software.
+
+The Licensee is authorized to make any or all Contributions to the
+Software provided that it includes an explicit notice that it is the
+author of said Contribution and indicates the date of the creation thereof.
+
+
+ 5.3 RIGHT OF DISTRIBUTION
+
+In particular, the right of distribution includes the right to publish,
+transmit and communicate the Software to the general public on any or
+all medium, and by any or all means, and the right to market, either in
+consideration of a fee, or free of charge, one or more copies of the
+Software by any means.
+
+The Licensee is further authorized to distribute copies of the modified
+or unmodified Software to third parties according to the terms and
+conditions set forth hereinafter.
+
+
+ 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION
+
+The Licensee is authorized to distribute true copies of the Software in
+Source Code or Object Code form, provided that said distribution
+complies with all the provisions of the Agreement and is accompanied by:
+
+ 1. a copy of the Agreement,
+
+ 2. a notice relating to the limitation of both the Licensor's
+ warranty and liability as set forth in Articles 8 and 9,
+
+and that, in the event that only the Object Code of the Software is
+redistributed, the Licensee allows future Licensees unhindered access to
+the full Source Code of the Software by indicating how to access it, it
+being understood that the additional cost of acquiring the Source Code
+shall not exceed the cost of transferring the data.
+
+
+ 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE
+
+When the Licensee makes a Contribution to the Software, the terms and
+conditions for the distribution of the Modified Software become subject
+to all the provisions of this Agreement.
+
+The Licensee is authorized to distribute the Modified Software, in
+Source Code or Object Code form, provided that said distribution
+complies with all the provisions of the Agreement and is accompanied by:
+
+ 1. a copy of the Agreement,
+
+ 2. a notice relating to the limitation of both the Licensor's
+ warranty and liability as set forth in Articles 8 and 9,
+
+and that, in the event that only the Object Code of the Modified
+Software is redistributed, the Licensee allows future Licensees
+unhindered access to the full Source Code of the Modified Software by
+indicating how to access it, it being understood that the additional
+cost of acquiring the Source Code shall not exceed the cost of
+transferring the data.
+
+
+ 5.3.3 DISTRIBUTION OF EXTERNAL MODULES
+
+When the Licensee has developed an External Module, the terms and
+conditions of this Agreement do not apply to said External Module, that
+may be distributed under a separate license agreement.
+
+
+ 5.3.4 COMPATIBILITY WITH THE GNU GPL
+
+The Licensee can include a code that is subject to the provisions of one
+of the versions of the GNU GPL in the Modified or unmodified Software,
+and distribute that entire code under the terms of the same version of
+the GNU GPL.
+
+The Licensee can include the Modified or unmodified Software in a code
+that is subject to the provisions of one of the versions of the GNU GPL,
+and distribute that entire code under the terms of the same version of
+the GNU GPL.
+
+
+ Article 6 - INTELLECTUAL PROPERTY
+
+
+ 6.1 OVER THE INITIAL SOFTWARE
+
+The Holder owns the economic rights over the Initial Software. Any or
+all use of the Initial Software is subject to compliance with the terms
+and conditions under which the Holder has elected to distribute its work
+and no one shall be entitled to modify the terms and conditions for the
+distribution of said Initial Software.
+
+The Holder undertakes that the Initial Software will remain ruled at
+least by the current license, for the duration set forth in article 4.2.
+
+
+ 6.2 OVER THE CONTRIBUTIONS
+
+A Licensee who develops a Contribution is the owner of the intellectual
+property rights over this Contribution as defined by applicable law.
+
+
+ 6.3 OVER THE EXTERNAL MODULES
+
+A Licensee who develops an External Module is the owner of the
+intellectual property rights over this External Module as defined by
+applicable law and is free to choose the type of agreement that shall
+govern its distribution.
+
+
+ 6.4 JOINT PROVISIONS
+
+The Licensee expressly undertakes:
+
+ 1. not to remove, or modify, in any manner, the intellectual property
+ notices attached to the Software;
+
+ 2. to reproduce said notices, in an identical manner, in the copies
+ of the Software modified or not.
+
+The Licensee undertakes not to directly or indirectly infringe the
+intellectual property rights of the Holder and/or Contributors on the
+Software and to take, where applicable, vis--vis its staff, any and all
+measures required to ensure respect of said intellectual property rights
+of the Holder and/or Contributors.
+
+
+ Article 7 - RELATED SERVICES
+
+7.1 Under no circumstances shall the Agreement oblige the Licensor to
+provide technical assistance or maintenance services for the Software.
+
+However, the Licensor is entitled to offer this type of services. The
+terms and conditions of such technical assistance, and/or such
+maintenance, shall be set forth in a separate instrument. Only the
+Licensor offering said maintenance and/or technical assistance services
+shall incur liability therefor.
+
+7.2 Similarly, any Licensor is entitled to offer to its licensees, under
+its sole responsibility, a warranty, that shall only be binding upon
+itself, for the redistribution of the Software and/or the Modified
+Software, under terms and conditions that it is free to decide. Said
+warranty, and the financial terms and conditions of its application,
+shall be subject of a separate instrument executed between the Licensor
+and the Licensee.
+
+
+ Article 8 - LIABILITY
+
+8.1 Subject to the provisions of Article 8.2, the Licensee shall be
+entitled to claim compensation for any direct loss it may have suffered
+from the Software as a result of a fault on the part of the relevant
+Licensor, subject to providing evidence thereof.
+
+8.2 The Licensor's liability is limited to the commitments made under
+this Agreement and shall not be incurred as a result of in particular:
+(i) loss due the Licensee's total or partial failure to fulfill its
+obligations, (ii) direct or consequential loss that is suffered by the
+Licensee due to the use or performance of the Software, and (iii) more
+generally, any consequential loss. In particular the Parties expressly
+agree that any or all pecuniary or business loss (i.e. loss of data,
+loss of profits, operating loss, loss of customers or orders,
+opportunity cost, any disturbance to business activities) or any or all
+legal proceedings instituted against the Licensee by a third party,
+shall constitute consequential loss and shall not provide entitlement to
+any or all compensation from the Licensor.
+
+
+ Article 9 - WARRANTY
+
+9.1 The Licensee acknowledges that the scientific and technical
+state-of-the-art when the Software was distributed did not enable all
+possible uses to be tested and verified, nor for the presence of
+possible defects to be detected. In this respect, the Licensee's
+attention has been drawn to the risks associated with loading, using,
+modifying and/or developing and reproducing the Software which are
+reserved for experienced users.
+
+The Licensee shall be responsible for verifying, by any or all means,
+the product's suitability for its requirements, its good working order,
+and for ensuring that it shall not cause damage to either persons or
+properties.
+
+9.2 The Licensor hereby represents, in good faith, that it is entitled
+to grant all the rights over the Software (including in particular the
+rights set forth in Article 5).
+
+9.3 The Licensee acknowledges that the Software is supplied "as is" by
+the Licensor without any other express or tacit warranty, other than
+that provided for in Article 9.2 and, in particular, without any warranty
+as to its commercial value, its secured, safe, innovative or relevant
+nature.
+
+Specifically, the Licensor does not warrant that the Software is free
+from any error, that it will operate without interruption, that it will
+be compatible with the Licensee's own equipment and software
+configuration, nor that it will meet the Licensee's requirements.
+
+9.4 The Licensor does not either expressly or tacitly warrant that the
+Software does not infringe any third party intellectual property right
+relating to a patent, software or any other property right. Therefore,
+the Licensor disclaims any and all liability towards the Licensee
+arising out of any or all proceedings for infringement that may be
+instituted in respect of the use, modification and redistribution of the
+Software. Nevertheless, should such proceedings be instituted against
+the Licensee, the Licensor shall provide it with technical and legal
+assistance for its defense. Such technical and legal assistance shall be
+decided on a case-by-case basis between the relevant Licensor and the
+Licensee pursuant to a memorandum of understanding. The Licensor
+disclaims any and all liability as regards the Licensee's use of the
+name of the Software. No warranty is given as regards the existence of
+prior rights over the name of the Software or as regards the existence
+of a trademark.
+
+
+ Article 10 - TERMINATION
+
+10.1 In the event of a breach by the Licensee of its obligations
+hereunder, the Licensor may automatically terminate this Agreement
+thirty (30) days after notice has been sent to the Licensee and has
+remained ineffective.
+
+10.2 A Licensee whose Agreement is terminated shall no longer be
+authorized to use, modify or distribute the Software. However, any
+licenses that it may have granted prior to termination of the Agreement
+shall remain valid subject to their having been granted in compliance
+with the terms and conditions hereof.
+
+
+ Article 11 - MISCELLANEOUS
+
+
+ 11.1 EXCUSABLE EVENTS
+
+Neither Party shall be liable for any or all delay, or failure to
+perform the Agreement, that may be attributable to an event of force
+majeure, an act of God or an outside cause, such as defective
+functioning or interruptions of the electricity or telecommunications
+networks, network paralysis following a virus attack, intervention by
+government authorities, natural disasters, water damage, earthquakes,
+fire, explosions, strikes and labor unrest, war, etc.
+
+11.2 Any Failure by either Party, on one or more occasions, to invoke
+one or more of the provisions hereof, shall under no circumstances be
+interpreted as being a waiver by the interested Party of its right to
+invoke said provision(s) subsequently.
+
+11.3 The Agreement cancels and replaces any or all previous agreements,
+whether written or oral, between the Parties and having the same
+purpose, and constitutes the entirety of the agreement between said
+Parties concerning said purpose. No supplement or modification to the
+terms and conditions hereof shall be effective as between the Parties
+unless it is made in writing and signed by their duly authorized
+representatives.
+
+11.4 In the event that one or more of the provisions hereof were to
+conflict with a current or future applicable act or legislative text,
+said act or legislative text shall prevail, and the Parties shall make
+the necessary amendments so as to comply with said act or legislative
+text. All other provisions shall remain effective. Similarly, invalidity
+of a provision of the Agreement, for any reason whatsoever, shall not
+cause the Agreement as a whole to be invalid.
+
+
+ 11.5 LANGUAGE
+
+The Agreement is drafted in both French and English and both versions
+are deemed authentic.
+
+
+ Article 12 - NEW VERSIONS OF THE AGREEMENT
+
+12.1 Any person is authorized to duplicate and distribute copies of this
+Agreement.
+
+12.2 So as to ensure coherence, the wording of this Agreement is
+protected and may only be modified by the authors of the License, who
+reserve the right to periodically publish updates or new versions of the
+Agreement, each with a separate number. These subsequent versions may
+address new issues encountered by Free Software.
+
+12.3 Any Software distributed under a given version of the Agreement may
+only be subsequently distributed under the same version of the Agreement
+or a subsequent version, subject to the provisions of Article 5.3.4.
+
+
+ Article 13 - GOVERNING LAW AND JURISDICTION
+
+13.1 The Agreement is governed by French law. The Parties agree to
+endeavor to seek an amicable solution to any disagreements or disputes
+that may arise during the performance of the Agreement.
+
+13.2 Failing an amicable solution within two (2) months as from their
+occurrence, and unless emergency proceedings are necessary, the
+disagreements or disputes shall be referred to the Paris Courts having
+jurisdiction, by the more diligent Party.
+
+
+Version 2.0 dated 2005-05-21.
diff --git a/src/libs/greycstoration/Makefile.am b/src/libs/greycstoration/Makefile.am
new file mode 100644
index 00000000..f7cc569f
--- /dev/null
+++ b/src/libs/greycstoration/Makefile.am
@@ -0,0 +1,20 @@
+METASOURCES = AUTO
+
+KDE_CXXFLAGS = $(USE_EXCEPTIONS) -w
+
+noinst_LTLIBRARIES = libgreycstoration.la
+
+libgreycstoration_la_SOURCES = greycstorationiface.cpp greycstorationwidget.cpp
+
+libgreycstoration_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikaminclude_HEADERS = greycstorationiface.h greycstorationwidget.h greycstorationsettings.h
+
+digikamincludedir = $(includedir)/digikam
+
diff --git a/src/libs/greycstoration/greycstoration.h b/src/libs/greycstoration/greycstoration.h
new file mode 100644
index 00000000..36b2c0e2
--- /dev/null
+++ b/src/libs/greycstoration/greycstoration.h
@@ -0,0 +1,481 @@
+/*
+ #
+ # File : greycstoration.h
+ # ( C++ header file - CImg plug-in )
+ #
+ # Description : GREYCstoration plug-in allowing easy integration in
+ # third parties softwares.
+ # ( http://www.greyc.ensicaen.fr/~dtschump/greycstoration/ )
+ # This file is a part of the CImg Library project.
+ # ( http://cimg.sourceforge.net )
+ #
+ # THIS PLUG-IN IS INTENDED FOR DEVELOPERS ONLY. IT EASES THE INTEGRATION ALGORITHM IN
+ # THIRD PARTIES SOFTWARES. IF YOU ARE A USER OF GREYCSTORATION, PLEASE LOOK
+ # AT THE FILE 'greycstoration.cpp' WHICH IS THE SOURCE OF THE COMPLETE
+ # COMMAND LINE GREYCSTORATION TOOL.
+ #
+ # Copyright : David Tschumperle
+ # ( http://www.greyc.ensicaen.fr/~dtschump/ )
+ #
+ # License : CeCILL v2.0
+ # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
+ #
+ # This software is governed by the CeCILL license under French law and
+ # abiding by the rules of distribution of free software. You can use,
+ # modify and/ or redistribute the software under the terms of the CeCILL
+ # license as circulated by CEA, CNRS and INRIA at the following URL
+ # "http://www.cecill.info".
+ #
+ # As a counterpart to the access to the source code and rights to copy,
+ # modify and redistribute granted by the license, users are provided only
+ # with a limited warranty and the software's author, the holder of the
+ # economic rights, and the successive licensors have only limited
+ # liability.
+ #
+ # In this respect, the user's attention is drawn to the risks associated
+ # with loading, using, modifying and/or developing or reproducing the
+ # software by the user in light of its specific status of free software,
+ # that may mean that it is complicated to manipulate, and that also
+ # therefore means that it is reserved for developers and experienced
+ # professionals having in-depth computer knowledge. Users are therefore
+ # encouraged to load and test the software's suitability as regards their
+ # requirements in conditions enabling the security of their systems and/or
+ # data to be ensured and, more generally, to use and operate it in the
+ # same conditions as regards security.
+ #
+ # The fact that you are presently reading this means that you have had
+ # knowledge of the CeCILL license and that you accept its terms.
+ #
+*/
+
+#ifndef cimg_plugin_greycstoration
+#define cimg_plugin_greycstoration
+
+//------------------------------------------------------------------------------
+// GREYCstoration parameter structure, storing important informations about
+// algorithm parameters and computing threads.
+// ** This structure has not to be manipulated by the API user, so please just
+// ignore it if you want to **
+//-------------------------------------------------------------------------------
+struct _greycstoration_params {
+
+ // Tell if the patch-based algorithm is selected
+ bool patch_based;
+
+ // Parameters specific to the non-patch regularization algorithm
+ float amplitude;
+ float sharpness;
+ float anisotropy;
+ float alpha;
+ float sigma;
+ float gfact;
+ float dl;
+ float da;
+ float gauss_prec;
+ unsigned int interpolation;
+
+ // Parameters specific to the patch-based regularization algorithm
+ unsigned int patch_size;
+ float sigma_s;
+ float sigma_p;
+ unsigned int lookup_size;
+
+ // Non-specific parameters of the algorithms.
+ CImg<T> *source;
+ const CImg<unsigned char> *mask;
+ CImg<T> *temporary;
+ unsigned long *counter;
+ unsigned int tile;
+ unsigned int tile_border;
+ unsigned int thread;
+ unsigned int nb_threads;
+ bool fast_approx;
+ bool is_running;
+ bool *stop_request;
+#if cimg_OS==1 && defined(_PTHREAD_H)
+ pthread_mutex_t
+ *mutex;
+#elif cimg_OS==2
+ HANDLE mutex;
+#else
+ void *mutex;
+#endif
+
+ // Default constructor
+ _greycstoration_params():patch_based(false),amplitude(0),sharpness(0),anisotropy(0),alpha(0),sigma(0),gfact(1),
+ dl(0),da(0),gauss_prec(0),interpolation(0),patch_size(0),
+ sigma_s(0),sigma_p(0),lookup_size(0),source(0),mask(0),temporary(0),counter(0),tile(0),
+ tile_border(0),thread(0),nb_threads(0),fast_approx(false),is_running(false), stop_request(0), mutex(0) {}
+};
+
+_greycstoration_params greycstoration_params[16];
+
+//----------------------------------------------------------
+// Public functions of the GREYCstoration API.
+// Use the functions below for integrating GREYCstoration
+// in your own C++ code.
+//----------------------------------------------------------
+
+//! Test if GREYCstoration threads are still running.
+bool greycstoration_is_running() const {
+ return greycstoration_params->is_running;
+}
+
+//! Force the GREYCstoration threads to stop.
+CImg& greycstoration_stop() {
+ if (greycstoration_is_running()) {
+ *(greycstoration_params->stop_request) = true;
+ while (greycstoration_params->is_running) cimg::wait(50);
+ }
+ return *this;
+}
+
+//! Return the GREYCstoration progress bar indice (between 0 and 100).
+float greycstoration_progress() const {
+ if (!greycstoration_is_running()) return 0.0f;
+ const unsigned long counter = greycstoration_params->counter?*(greycstoration_params->counter):0;
+ const float
+ da = greycstoration_params->da,
+ factor = greycstoration_params->patch_based?1:(1+360/da);
+ float maxcounter = 0;
+ if (greycstoration_params->tile==0) maxcounter = width*height*depth*factor;
+ else {
+ const unsigned int
+ t = greycstoration_params->tile,
+ b = greycstoration_params->tile_border,
+ n = (1+(width-1)/t)*(1+(height-1)/t)*(1+(depth-1)/t);
+ maxcounter = (width*height*depth + n*4*b*(b + t))*factor;
+ }
+ return cimg::min(counter*99.9f/maxcounter,99.9f);
+}
+
+//! Run the non-patch version of the GREYCstoration algorithm on the instance image, using a mask.
+CImg& greycstoration_run(const CImg<unsigned char>& mask,
+ const float amplitude=60, const float sharpness=0.7f, const float anisotropy=0.3f,
+ const float alpha=0.6f, const float sigma=1.1f, const float gfact=1.0f,
+ const float dl=0.8f, const float da=30.0f,
+ const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true,
+ const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) {
+
+ if (greycstoration_is_running())
+ throw CImgInstanceException("CImg<T>::greycstoration_run() : A GREYCstoration thread is already running on"
+ " the instance image (%u,%u,%u,%u,%p).",width,height,depth,dim,data);
+
+ else {
+ if (!mask.is_empty() && !mask.is_sameXY(*this))
+ throw CImgArgumentException("CImg<%s>::greycstoration_run() : Given mask (%u,%u,%u,%u,%p) and instance image "
+ "(%u,%u,%u,%u,%p) have different dimensions.",
+ pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data,width,height,depth,dim,data);
+ if (nb_threads>16) cimg::warn("CImg<%s>::greycstoration_run() : Multi-threading mode limited to 16 threads max.");
+ const unsigned int
+ ntile = (tile && (tile<width || tile<height || (depth>1 && tile<depth)))?tile:0,
+#if cimg_OS==1 && !defined(_PTHREAD_H)
+ nthreads = 0;
+#else
+ nthreads = ntile?cimg::min(nb_threads,16U):cimg::min(nb_threads,1U);
+#endif
+
+ CImg<T> *const temporary = ntile?new CImg<T>(*this):0;
+ unsigned long *const counter = new unsigned long;
+ *counter = 0;
+ bool *const stop_request = new bool;
+ *stop_request = false;
+
+ for (unsigned int k=0; k<(nthreads?nthreads:1); k++) {
+ greycstoration_params[k].patch_based = false;
+ greycstoration_params[k].amplitude = amplitude;
+ greycstoration_params[k].sharpness = sharpness;
+ greycstoration_params[k].anisotropy = anisotropy;
+ greycstoration_params[k].alpha = alpha;
+ greycstoration_params[k].sigma = sigma;
+ greycstoration_params[k].gfact = gfact;
+ greycstoration_params[k].dl = dl;
+ greycstoration_params[k].da = da;
+ greycstoration_params[k].gauss_prec = gauss_prec;
+ greycstoration_params[k].interpolation = interpolation;
+ greycstoration_params[k].fast_approx = fast_approx;
+ greycstoration_params[k].source = this;
+ greycstoration_params[k].mask = &mask;
+ greycstoration_params[k].temporary = temporary;
+ greycstoration_params[k].counter = counter;
+ greycstoration_params[k].tile = ntile;
+ greycstoration_params[k].tile_border = tile_border;
+ greycstoration_params[k].thread = k;
+ greycstoration_params[k].nb_threads = nthreads;
+ greycstoration_params[k].is_running = true;
+ greycstoration_params[k].stop_request = stop_request;
+ if (k) greycstoration_params[k].mutex = greycstoration_params[0].mutex;
+ else greycstoration_mutex_create(greycstoration_params[0]);
+ }
+ if (nthreads) { // Threaded version
+#if cimg_OS==1
+#ifdef _PTHREAD_H
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ for (unsigned int k=0; k<greycstoration_params->nb_threads; k++) {
+ pthread_t thread;
+ const int err = pthread_create(&thread, &attr, greycstoration_thread, (void*)(greycstoration_params+k));
+ if (err) throw CImgException("CImg<%s>::greycstoration_run() : pthread_create returned error %d",
+ pixel_type(), err);
+ }
+#endif
+#elif cimg_OS==2
+ for (unsigned int k=0; k<greycstoration_params->nb_threads; k++) {
+ unsigned long ThreadID = 0;
+ CreateThread(0,0,greycstoration_thread,(void*)(greycstoration_params+k),0,&ThreadID);
+ }
+#else
+ throw CImgInstanceException("CImg<T>::greycstoration_run() : Threads are not supported, please define cimg_OS first.");
+#endif
+ } else greycstoration_thread((void*)greycstoration_params); // Non-threaded version
+ }
+ return *this;
+}
+
+//! Run the non-patch version of the GREYCstoration algorithm on the instance image.
+CImg& greycstoration_run(const float amplitude=50, const float sharpness=0.7f, const float anisotropy=0.3f,
+ const float alpha=0.6f, const float sigma=1.1f, const float gfact=1.0f,
+ const float dl=0.8f, const float da=30.0f,
+ const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true,
+ const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) {
+ static const CImg<unsigned char> empty_mask;
+ return greycstoration_run(empty_mask,amplitude,sharpness,anisotropy,alpha,sigma,gfact,dl,da,gauss_prec,
+ interpolation,fast_approx,tile,tile_border,nb_threads);
+}
+
+//! Run the patch-based version of the GREYCstoration algorithm on the instance image.
+CImg& greycstoration_patch_run(const unsigned int patch_size=5, const float sigma_p=10, const float sigma_s=100,
+ const unsigned int lookup_size=20, const bool fast_approx=true,
+ const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) {
+
+ static const CImg<unsigned char> empty_mask;
+ if (greycstoration_is_running())
+ throw CImgInstanceException("CImg<T>::greycstoration_run() : A GREYCstoration thread is already running on"
+ " the instance image (%u,%u,%u,%u,%p).",width,height,depth,dim,data);
+
+ else {
+ if (nb_threads>16) cimg::warn("CImg<%s>::greycstoration_run() : Multi-threading mode limited to 16 threads max.");
+ const unsigned int
+ ntile = (tile && (tile<width || tile<height || (depth>1 && tile<depth)))?tile:0,
+#if cimg_OS==1 && !defined(_PTHREAD_H)
+ nthreads = 0;
+#else
+ nthreads = ntile?cimg::min(nb_threads,16U):cimg::min(nb_threads,1U);
+#endif
+
+ CImg<T> *const temporary = ntile?new CImg<T>(*this):0;
+ unsigned long *const counter = new unsigned long;
+ *counter = 0;
+ bool *const stop_request = new bool;
+ *stop_request = false;
+
+ for (unsigned int k=0; k<(nthreads?nthreads:1); k++) {
+ greycstoration_params[k].patch_based = true;
+ greycstoration_params[k].patch_size = patch_size;
+ greycstoration_params[k].sigma_s = sigma_s;
+ greycstoration_params[k].sigma_p = sigma_p;
+ greycstoration_params[k].lookup_size = lookup_size;
+ greycstoration_params[k].source = this;
+ greycstoration_params[k].mask = &empty_mask;
+ greycstoration_params[k].temporary = temporary;
+ greycstoration_params[k].counter = counter;
+ greycstoration_params[k].tile = ntile;
+ greycstoration_params[k].tile_border = tile_border;
+ greycstoration_params[k].thread = k;
+ greycstoration_params[k].nb_threads = nthreads;
+ greycstoration_params[k].fast_approx = fast_approx;
+ greycstoration_params[k].is_running = true;
+ greycstoration_params[k].stop_request = stop_request;
+ if (k) greycstoration_params[k].mutex = greycstoration_params[0].mutex;
+ else greycstoration_mutex_create(greycstoration_params[0]);
+ }
+ if (nthreads) { // Threaded version
+#if cimg_OS==1
+#ifdef _PTHREAD_H
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ for (unsigned int k=0; k<greycstoration_params->nb_threads; k++) {
+ pthread_t thread;
+ const int err = pthread_create(&thread, &attr, greycstoration_thread, (void*)(greycstoration_params+k));
+ if (err) throw CImgException("CImg<%s>::greycstoration_run() : pthread_create returned error %d",
+ pixel_type(), err);
+ }
+#endif
+#elif cimg_OS==2
+ for (unsigned int k=0; k<greycstoration_params->nb_threads; k++) {
+ unsigned long ThreadID = 0;
+ CreateThread(0,0,greycstoration_thread,(void*)(greycstoration_params+k),0,&ThreadID);
+ }
+#else
+ throw CImgInstanceException("CImg<T>::greycstoration_run() : Threads support have not been enabled in this version of GREYCstoration.");
+#endif
+ } else greycstoration_thread((void*)greycstoration_params); // Non-threaded version
+ }
+ return *this;
+}
+
+//------------------------------------------------------------------------------
+// GREYCstoration private functions.
+// Should not be used directly by the API user.
+//-------------------------------------------------------------------------------
+
+static void greycstoration_mutex_create(_greycstoration_params &p) {
+ if (p.nb_threads>1) {
+#if cimg_OS==1 && defined(_PTHREAD_H)
+ p.mutex = new pthread_mutex_t;
+ pthread_mutex_init(p.mutex,0);
+#elif cimg_OS==2
+ p.mutex = CreateMutex(0,FALSE,0);
+#endif
+ }
+}
+
+static void greycstoration_mutex_lock(_greycstoration_params &p) {
+ if (p.nb_threads>1) {
+#if cimg_OS==1 && defined(_PTHREAD_H)
+ if (p.mutex) pthread_mutex_lock(p.mutex);
+#elif cimg_OS==2
+ WaitForSingleObject(p.mutex,INFINITE);
+#endif
+ }
+}
+
+static void greycstoration_mutex_unlock(_greycstoration_params &p) {
+ if (p.nb_threads>1) {
+#if cimg_OS==1 && defined(_PTHREAD_H)
+ if (p.mutex) pthread_mutex_unlock(p.mutex);
+#elif cimg_OS==2
+ ReleaseMutex(p.mutex);
+#endif
+ }
+}
+
+static void greycstoration_mutex_destroy(_greycstoration_params &p) {
+ if (p.nb_threads>1) {
+#if cimg_OS==1 && defined(_PTHREAD_H)
+ if (p.mutex) pthread_mutex_destroy(p.mutex);
+#elif cimg_OS==2
+ CloseHandle(p.mutex);
+#endif
+ p.mutex = 0;
+ }
+}
+
+#if cimg_OS==1
+static void* greycstoration_thread(void *arg) {
+#elif cimg_OS==2
+ static DWORD WINAPI greycstoration_thread(void *arg) {
+#endif
+ _greycstoration_params &p = *(_greycstoration_params*)arg;
+ greycstoration_mutex_lock(p);
+ const CImg<unsigned char> &mask = *(p.mask);
+ CImg<T> &source = *(p.source);
+
+ if (!p.tile) {
+
+ // Non-tiled version
+ //------------------
+ if (p.patch_based) source.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx);
+ else source.blur_anisotropic(mask,p.amplitude,p.sharpness,p.anisotropy,p.alpha,p.sigma,p.dl,p.da,p.gauss_prec,
+ p.interpolation,p.fast_approx,p.gfact);
+
+ } else {
+
+ // Tiled version
+ //---------------
+ CImg<T> &temporary = *(p.temporary);
+ const bool threed = (source.depth>1);
+ const unsigned int b = p.tile_border;
+ unsigned int ctile = 0;
+ if (threed) {
+ for (unsigned int z=0; z<source.depth && !*(p.stop_request); z+=p.tile)
+ for (unsigned int y=0; y<source.height && !*(p.stop_request); y+=p.tile)
+ for (unsigned int x=0; x<source.width && !*(p.stop_request); x+=p.tile)
+ if (!p.nb_threads || ((ctile++)%p.nb_threads)==p.thread) {
+ const unsigned int
+ x1 = x+p.tile-1,
+ y1 = y+p.tile-1,
+ z1 = z+p.tile-1,
+ xe = x1<source.width?x1:source.width-1,
+ ye = y1<source.height?y1:source.height-1,
+ ze = z1<source.depth?z1:source.depth-1;
+ CImg<T> img = source.get_crop(x-b,y-b,z-b,xe+b,ye+b,ze+b,true);
+ CImg<unsigned char> mask_tile = mask.is_empty()?mask:mask.get_crop(x-b,y-b,z-b,xe+b,ye+b,ze+b,true);
+ img.greycstoration_params[0] = p;
+ greycstoration_mutex_unlock(p);
+ if (p.patch_based) img.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx);
+ else img.blur_anisotropic(mask_tile,p.amplitude,p.sharpness,p.anisotropy,
+ p.alpha,p.sigma,p.dl,p.da,p.gauss_prec,p.interpolation,p.fast_approx,p.gfact);
+ greycstoration_mutex_lock(p);
+ temporary.draw_image(x,y,z,img.crop(b,b,b,img.width-b,img.height-b,img.depth-b));
+ }
+ } else {
+ for (unsigned int y=0; y<source.height && !*(p.stop_request); y+=p.tile)
+ for (unsigned int x=0; x<source.width && !*(p.stop_request); x+=p.tile)
+ if (!p.nb_threads || ((ctile++)%p.nb_threads)==p.thread) {
+ const unsigned int
+ x1 = x+p.tile-1,
+ y1 = y+p.tile-1,
+ xe = x1<source.width?x1:source.width-1,
+ ye = y1<source.height?y1:source.height-1;
+ CImg<T> img = source.get_crop(x-b,y-b,xe+b,ye+b,true);
+ CImg<unsigned char> mask_tile = mask.is_empty()?mask:mask.get_crop(x-b,y-b,xe+b,ye+b,true);
+ img.greycstoration_params[0] = p;
+ greycstoration_mutex_unlock(p);
+ if (p.patch_based) img.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx);
+ else img.blur_anisotropic(mask_tile,p.amplitude,p.sharpness,p.anisotropy,
+ p.alpha,p.sigma,p.dl,p.da,p.gauss_prec,p.interpolation,p.fast_approx,p.gfact);
+ temporary.draw_image(x,y,img.crop(b,b,img.width-b,img.height-b));
+ greycstoration_mutex_lock(p);
+ }
+ }
+ }
+ greycstoration_mutex_unlock(p);
+
+ if (!p.thread) {
+ if (p.nb_threads>1) {
+ bool stopflag = true;
+ do {
+ stopflag = true;
+ for (unsigned int k=1; k<p.nb_threads; k++) if (source.greycstoration_params[k].is_running) stopflag = false;
+ if (!stopflag) cimg::wait(50);
+ } while (!stopflag);
+ }
+ if (p.counter) delete p.counter;
+ if (p.temporary) { source = *(p.temporary); delete p.temporary; }
+ if (p.stop_request) delete p.stop_request;
+ p.mask = 0;
+ p.amplitude = p.sharpness = p.anisotropy = p.alpha = p.sigma = p.gfact = p.dl = p.da = p.gauss_prec = p.sigma_s = p.sigma_p = 0;
+ p.patch_size = p.interpolation = p.lookup_size = 0;
+ p.fast_approx = false;
+ p.source = 0;
+ p.temporary = 0;
+ p.counter = 0;
+ p.tile = p.tile_border = p.thread = p.nb_threads = 0;
+ p.stop_request = 0;
+ greycstoration_mutex_destroy(p);
+ }
+ p.is_running = false;
+
+ if (p.nb_threads) {
+#if cimg_OS==1 && defined(_PTHREAD_H)
+ pthread_exit(arg);
+ return arg;
+#elif cimg_OS==2
+ ExitThread(0);
+#endif
+ }
+ return 0;
+ }
+
+
+#define cimg_plugin_greycstoration_count \
+ if (!*(greycstoration_params->stop_request)) ++(*greycstoration_params->counter); else return *this;
+#define cimg_plugin_greycstoration_lock \
+ greycstoration_mutex_lock(greycstoration_params[0]);
+#define cimg_plugin_greycstoration_unlock \
+ greycstoration_mutex_unlock(greycstoration_params[0]);
+
+#endif
diff --git a/src/libs/greycstoration/greycstorationiface.cpp b/src/libs/greycstoration/greycstorationiface.cpp
new file mode 100644
index 00000000..32f60514
--- /dev/null
+++ b/src/libs/greycstoration/greycstorationiface.cpp
@@ -0,0 +1,473 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-12-03
+ * Description : Greycstoration interface.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** Don't use CImg interface (keyboard/mouse interaction) */
+#define cimg_display 0
+/** Only print debug information on the console */
+#define cimg_debug 1
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// C++ includes.
+
+#include <cassert>
+
+// Local includes.
+
+#define cimg_plugin "greycstoration.h"
+// Unix-like (Linux, Solaris, BSD, MacOSX, Irix,...).
+#if defined(unix) || defined(__unix) || defined(__unix__) \
+ || defined(linux) || defined(__linux) || defined(__linux__) \
+ || defined(sun) || defined(__sun) \
+ || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined __DragonFly__ \
+ || defined(__MACOSX__) || defined(__APPLE__) \
+ || defined(sgi) || defined(__sgi) \
+ || defined(__CYGWIN__)
+#include <pthread.h>
+#endif
+
+/** Number of children threads used to run Greystoration algorithm
+ For the moment we use only one thread. See B.K.O #186642 for details.
+ Multithreading management need to be fixed into CImg.
+ */
+#define COMPUTATION_THREAD 2
+
+/** Uncomment this line if you use future GreycStoration implementation with GFact parameter
+ */
+#define GREYSTORATION_USING_GFACT 1
+
+// Local includes.
+
+#include "ddebug.h"
+#include "greycstorationsettings.h"
+#include "greycstorationiface.h"
+
+// CImg includes.
+
+#include "CImg.h"
+
+using namespace cimg_library;
+
+namespace Digikam
+{
+
+class GreycstorationIfacePriv
+{
+
+public:
+
+ GreycstorationIfacePriv()
+ {
+ mode = GreycstorationIface::Restore;
+ gfact = 1.0;
+ }
+
+ float gfact;
+
+ int mode; // The interface running mode.
+
+ TQImage inPaintingMask; // Mask for inpainting.
+
+ GreycstorationSettings settings; // Current Greycstoraion algorithm settings.
+
+ CImg<> img; // Main image.
+ CImg<uchar> mask; // The mask used with inpaint or resize mode
+};
+
+GreycstorationIface::GreycstorationIface(DImg *orgImage,
+ GreycstorationSettings settings,
+ int mode,
+ int newWidth, int newHeight,
+ const TQImage& inPaintingMask,
+ TQObject *parent)
+ : DImgThreadedFilter(orgImage, parent)
+{
+ d = new GreycstorationIfacePriv;
+ d->settings = settings;
+ d->mode = mode;
+ d->inPaintingMask = inPaintingMask;
+
+ if (m_orgImage.sixteenBit()) // 16 bits image.
+ d->gfact = 1.0/256.0;
+
+ if (d->mode == Resize || d->mode == SimpleResize)
+ {
+ m_destImage = DImg(newWidth, newHeight,
+ m_orgImage.sixteenBit(), m_orgImage.hasAlpha());
+ DDebug() << "GreycstorationIface::Resize: new size: ("
+ << newWidth << ", " << newHeight << ")" << endl;
+ }
+ else
+ {
+ m_destImage = DImg(m_orgImage.width(), m_orgImage.height(),
+ m_orgImage.sixteenBit(), m_orgImage.hasAlpha());
+ }
+
+ initFilter();
+}
+
+GreycstorationIface::~GreycstorationIface()
+{
+ delete d;
+}
+
+// We need to re-implemente this method from DImgThreadedFilter class because
+// target image size can be different from original if d->mode = Resize.
+
+void GreycstorationIface::initFilter()
+{
+ if (m_orgImage.width() && m_orgImage.height())
+ {
+ if (m_parent)
+ start(); // m_parent is valide, start thread ==> run()
+ else
+ startComputation(); // no parent : no using thread.
+ }
+ else // No image data
+ {
+ if (m_parent) // If parent then send event about a problem.
+ {
+ postProgress(0, false, false);
+ DDebug() << m_name << "::No valid image data !!! ..." << endl;
+ }
+ }
+}
+
+void GreycstorationIface::stopComputation()
+{
+ // Because Greycstoration algorithm run in a child thread, we need
+ // to stop it before to stop this thread.
+ if (d->img.greycstoration_is_running())
+ {
+ // If the user abort, we stop the algorithm.
+ DDebug() << "Stop Greycstoration computation..." << endl;
+ d->img.greycstoration_stop();
+ }
+
+ // And now when stop main loop and clean up all
+ DImgThreadedFilter::stopComputation();
+}
+
+void GreycstorationIface::filterImage()
+{
+ int x, y;
+
+ DDebug() << "GreycstorationIface::Initialization..." << endl;
+
+ // Copy the src image data into a CImg type image with three channels and no alpha.
+
+ uchar* imageData = m_orgImage.bits();
+ int imageWidth = m_orgImage.width();
+ int imageHeight = m_orgImage.height();
+ d->img = CImg<>(imageWidth, imageHeight, 1, 4);
+
+ if (!m_orgImage.sixteenBit()) // 8 bits image.
+ {
+ uchar *ptr = imageData;
+
+ for (y = 0; y < imageHeight; y++)
+ {
+ for (x = 0; x < imageWidth; x++)
+ {
+ d->img(x, y, 0) = ptr[0]; // Blue.
+ d->img(x, y, 1) = ptr[1]; // Green.
+ d->img(x, y, 2) = ptr[2]; // Red.
+ d->img(x, y, 3) = ptr[3]; // Alpha.
+ ptr += 4;
+ }
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short *ptr = (unsigned short *)imageData;
+
+ for (y = 0; y < imageHeight; y++)
+ {
+ for (x = 0; x < imageWidth; x++)
+ {
+ d->img(x, y, 0) = ptr[0]; // Blue.
+ d->img(x, y, 1) = ptr[1]; // Green.
+ d->img(x, y, 2) = ptr[2]; // Red.
+ d->img(x, y, 3) = ptr[3]; // Alpha.
+ ptr += 4;
+ }
+ }
+ }
+
+ DDebug() << "GreycstorationIface::Process Computation..." << endl;
+
+ try
+ {
+ switch (d->mode)
+ {
+ case Restore:
+ restoration();
+ break;
+
+ case InPainting:
+ inpainting();
+ break;
+
+ case Resize:
+ resize();
+ break;
+
+ case SimpleResize:
+ simpleResize();
+ break;
+ }
+ }
+ catch(...) // Everything went wrong.
+ {
+ DDebug() << "GreycstorationIface::Error during Greycstoration filter computation!" << endl;
+
+ if (m_parent)
+ postProgress( 0, false, false );
+
+ return;
+ }
+
+ if (m_cancel)
+ return;
+
+ // Copy CImg onto destination.
+
+ DDebug() << "GreycstorationIface::Finalization..." << endl;
+
+ uchar* newData = m_destImage.bits();
+ int newWidth = m_destImage.width();
+ int newHeight = m_destImage.height();
+
+ if (!m_orgImage.sixteenBit()) // 8 bits image.
+ {
+ uchar *ptr = newData;
+
+ for (y = 0; y < newHeight; y++)
+ {
+ for (x = 0; x < newWidth; x++)
+ {
+ // Overwrite RGB values to destination.
+ ptr[0] = static_cast<uchar>(d->img(x, y, 0)); // Blue
+ ptr[1] = static_cast<uchar>(d->img(x, y, 1)); // Green
+ ptr[2] = static_cast<uchar>(d->img(x, y, 2)); // Red
+ ptr[3] = static_cast<uchar>(d->img(x, y, 3)); // Alpha
+ ptr += 4;
+ }
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short *ptr = (unsigned short *)newData;
+
+ for (y = 0; y < newHeight; y++)
+ {
+ for (x = 0; x < newWidth; x++)
+ {
+ // Overwrite RGB values to destination.
+ ptr[0] = static_cast<unsigned short>(d->img(x, y, 0)); // Blue
+ ptr[1] = static_cast<unsigned short>(d->img(x, y, 1)); // Green
+ ptr[2] = static_cast<unsigned short>(d->img(x, y, 2)); // Red
+ ptr[3] = static_cast<unsigned short>(d->img(x, y, 3)); // Alpha
+ ptr += 4;
+ }
+ }
+ }
+}
+
+void GreycstorationIface::restoration()
+{
+ for (uint iter = 0 ; !m_cancel && (iter < d->settings.nbIter) ; iter++)
+ {
+ // This function will start a thread running one iteration of the GREYCstoration filter.
+ // It returns immediately, so you can do what you want after (update a progress bar for
+ // instance).
+ d->img.greycstoration_run(d->settings.amplitude,
+ d->settings.sharpness,
+ d->settings.anisotropy,
+ d->settings.alpha,
+ d->settings.sigma,
+#ifdef GREYSTORATION_USING_GFACT
+ d->gfact,
+#endif
+ d->settings.dl,
+ d->settings.da,
+ d->settings.gaussPrec,
+ d->settings.interp,
+ d->settings.fastApprox,
+ d->settings.tile,
+ d->settings.btile,
+ COMPUTATION_THREAD);
+
+ iterationLoop(iter);
+ }
+}
+
+void GreycstorationIface::inpainting()
+{
+ if (!d->inPaintingMask.isNull())
+ {
+ // Copy the inpainting image data into a CImg type image with three channels and no alpha.
+
+ int x, y;
+
+ d->mask = CImg<uchar>(d->inPaintingMask.width(), d->inPaintingMask.height(), 1, 3);
+ uchar *ptr = d->inPaintingMask.bits();
+
+ for (y = 0; y < d->inPaintingMask.height(); y++)
+ {
+ for (x = 0; x < d->inPaintingMask.width(); x++)
+ {
+ d->mask(x, y, 0) = ptr[2]; // blue.
+ d->mask(x, y, 1) = ptr[1]; // green.
+ d->mask(x, y, 2) = ptr[0]; // red.
+ ptr += 4;
+ }
+ }
+ }
+ else
+ {
+ DDebug() << "Inpainting image: mask is null!" << endl;
+ m_cancel = true;
+ return;
+ }
+
+ for (uint iter=0 ; !m_cancel && (iter < d->settings.nbIter) ; iter++)
+ {
+ // This function will start a thread running one iteration of the GREYCstoration filter.
+ // It returns immediately, so you can do what you want after (update a progress bar for
+ // instance).
+ d->img.greycstoration_run(d->mask,
+ d->settings.amplitude,
+ d->settings.sharpness,
+ d->settings.anisotropy,
+ d->settings.alpha,
+ d->settings.sigma,
+#ifdef GREYSTORATION_USING_GFACT
+ d->gfact,
+#endif
+ d->settings.dl,
+ d->settings.da,
+ d->settings.gaussPrec,
+ d->settings.interp,
+ d->settings.fastApprox,
+ d->settings.tile,
+ d->settings.btile,
+ COMPUTATION_THREAD);
+
+ iterationLoop(iter);
+ }
+}
+
+void GreycstorationIface::resize()
+{
+ const bool anchor = true; // Anchor original pixels.
+ const unsigned int init = 5; // Initial estimate (1=block, 3=linear, 5=bicubic).
+
+ int w = m_destImage.width();
+ int h = m_destImage.height();
+
+ d->mask.assign(d->img.dimx(), d->img.dimy(), 1, 1, 255);
+
+ if (!anchor)
+ d->mask.resize(w, h, 1, 1, 1);
+ else
+ d->mask = !d->mask.resize(w, h, 1, 1, 4);
+
+ d->img.resize(w, h, 1, -100, init);
+
+ for (uint iter = 0 ; !m_cancel && (iter < d->settings.nbIter) ; iter++)
+ {
+ // This function will start a thread running one iteration of the GREYCstoration filter.
+ // It returns immediately, so you can do what you want after (update a progress bar for
+ // instance).
+ d->img.greycstoration_run(d->mask,
+ d->settings.amplitude,
+ d->settings.sharpness,
+ d->settings.anisotropy,
+ d->settings.alpha,
+ d->settings.sigma,
+#ifdef GREYSTORATION_USING_GFACT
+ d->gfact,
+#endif
+ d->settings.dl,
+ d->settings.da,
+ d->settings.gaussPrec,
+ d->settings.interp,
+ d->settings.fastApprox,
+ d->settings.tile,
+ d->settings.btile,
+ COMPUTATION_THREAD);
+
+ iterationLoop(iter);
+ }
+}
+
+void GreycstorationIface::simpleResize()
+{
+ const unsigned int method = 3; // Initial estimate (0, none, 1=block, 3=linear, 4=grid, 5=bicubic).
+
+ int w = m_destImage.width();
+ int h = m_destImage.height();
+
+ while (d->img.dimx() > 2*w &&
+ d->img.dimy() > 2*h)
+ {
+ d->img.resize_halfXY();
+ }
+
+ d->img.resize(w, h, -100, -100, method);
+}
+
+void GreycstorationIface::iterationLoop(uint iter)
+{
+ uint mp = 0;
+ uint p = 0;
+
+ do
+ {
+ usleep(100000);
+
+ if (m_parent && !m_cancel)
+ {
+ // Update the progress bar in dialog. We simply computes the global
+ // progression index (including all iterations).
+
+ p = (uint)((iter*100 + d->img.greycstoration_progress())/d->settings.nbIter);
+
+ if (p > mp)
+ {
+ postProgress(p);
+ mp = p;
+ }
+ }
+ }
+ while (d->img.greycstoration_is_running() && !m_cancel);
+
+ // A delay is require here. I suspect a sync problem between threads
+ // used by GreycStoration algorithm.
+ usleep(100000);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/greycstoration/greycstorationiface.h b/src/libs/greycstoration/greycstorationiface.h
new file mode 100644
index 00000000..fd7b248b
--- /dev/null
+++ b/src/libs/greycstoration/greycstorationiface.h
@@ -0,0 +1,89 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-12-03
+ * Description : Greycstoration interface.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef GREYCSTORATIONIFACE_H
+#define GREYCSTORATIONIFACE_H
+
+// TQt includes.
+
+#include <tqimage.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgthreadedfilter.h"
+#include "greycstorationsettings.h"
+#include "digikam_export.h"
+
+class TQObject;
+
+namespace Digikam
+{
+
+class GreycstorationIfacePriv;
+
+class DIGIKAM_EXPORT GreycstorationIface : public DImgThreadedFilter
+{
+
+public:
+
+ enum MODE
+ {
+ Restore = 0,
+ InPainting,
+ Resize,
+ SimpleResize // Mode to resize image without to use Greycstoration algorithm.
+ };
+
+public:
+
+ GreycstorationIface(DImg *orgImage,
+ GreycstorationSettings settings,
+ int mode=Restore,
+ int newWidth=0, int newHeight=0,
+ const TQImage& inPaintingMask=TQImage(),
+ TQObject *parent=0);
+
+ ~GreycstorationIface();
+
+ void stopComputation();
+
+private:
+
+ void initFilter();
+ void filterImage();
+
+ void restoration();
+ void inpainting();
+ void resize();
+ void simpleResize();
+ void iterationLoop(uint iter);
+
+private:
+
+ GreycstorationIfacePriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif /* GREYCSTORATIONIFACE_H */
diff --git a/src/libs/greycstoration/greycstorationsettings.h b/src/libs/greycstoration/greycstorationsettings.h
new file mode 100644
index 00000000..e2686965
--- /dev/null
+++ b/src/libs/greycstoration/greycstorationsettings.h
@@ -0,0 +1,144 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-21-07
+ * Description : Greycstoration settings container.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * For a full settings description, look at this url :
+ * http://www.greyc.ensicaen.fr/~dtschump/greycstoration/guide.html
+ *
+ * For demonstration of settings, look at this url :
+ *
+ * http://www.greyc.ensicaen.fr/~dtschump/greycstoration/demonstration.html
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef GREYCSTORATIONSETTINGS_H
+#define GREYCSTORATIONSETTINGS_H
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT GreycstorationSettings
+{
+
+public:
+
+ enum INTERPOLATION
+ {
+ NearestNeighbor = 0,
+ Linear,
+ RungeKutta
+ };
+
+public:
+
+ GreycstorationSettings()
+ {
+ setRestorationDefaultSettings();
+ };
+
+ ~GreycstorationSettings(){};
+
+ void setRestorationDefaultSettings()
+ {
+ fastApprox = true;
+
+ tile = 256;
+ btile = 4;
+
+ nbIter = 1;
+ interp = NearestNeighbor;
+
+ amplitude = 60.0;
+ sharpness = 0.7;
+ anisotropy = 0.3;
+ alpha = 0.6;
+ sigma = 1.1;
+ gaussPrec = 2.0;
+ dl = 0.8;
+ da = 30.0;
+ };
+
+ void setInpaintingDefaultSettings()
+ {
+ fastApprox = true;
+
+ tile = 256;
+ btile = 4;
+
+ nbIter = 30;
+ interp = NearestNeighbor;
+
+ amplitude = 20.0;
+ sharpness = 0.3;
+ anisotropy = 1.0;
+ alpha = 0.8;
+ sigma = 2.0;
+ gaussPrec = 2.0;
+ dl = 0.8;
+ da = 30.0;
+ };
+
+ void setResizeDefaultSettings()
+ {
+ fastApprox = true;
+
+ tile = 256;
+ btile = 4;
+
+ nbIter = 3;
+ interp = NearestNeighbor;
+
+ amplitude = 20.0;
+ sharpness = 0.2;
+ anisotropy = 0.9;
+ alpha = 0.1;
+ sigma = 1.5;
+ gaussPrec = 2.0;
+ dl = 0.8;
+ da = 30.0;
+ };
+
+public:
+
+ bool fastApprox;
+
+ int tile;
+ int btile;
+
+ uint nbIter;
+ uint interp;
+
+ float amplitude;
+ float sharpness;
+ float anisotropy;
+ float alpha;
+ float sigma;
+ float gaussPrec;
+ float dl;
+ float da;
+};
+
+} // namespace Digikam
+
+#endif // GREYCSTORATIONSETTINGS_H
diff --git a/src/libs/greycstoration/greycstorationwidget.cpp b/src/libs/greycstoration/greycstorationwidget.cpp
new file mode 100644
index 00000000..93f25799
--- /dev/null
+++ b/src/libs/greycstoration/greycstorationwidget.cpp
@@ -0,0 +1,377 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-09-13
+ * Description : Greycstoration settings widgets
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqtabwidget.h>
+#include <tqtextstream.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <kdialog.h>
+#include <tdelocale.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+#include <libkdcraw/rcombobox.h>
+
+// Local includes.
+
+#include "greycstorationwidget.h"
+#include "greycstorationwidget.moc"
+
+using namespace KDcrawIface;
+
+namespace Digikam
+{
+
+class GreycstorationWidgetPriv
+{
+
+public:
+
+ GreycstorationWidgetPriv()
+ {
+ parent = 0;
+
+ advancedPage = 0;
+ alphaInput = 0;
+ alphaLabel = 0;
+ amplitudeInput = 0;
+ amplitudeLabel = 0;
+ anisotropyInput = 0;
+ anisotropyLabel = 0;
+ btileInput = 0;
+ btileLabel = 0;
+ daInput = 0;
+ daLabel = 0;
+ dlInput = 0;
+ dlLabel = 0;
+ fastApproxCBox = 0;
+ gaussianPrecInput = 0;
+ gaussianPrecLabel = 0;
+ generalPage = 0;
+ interpolationBox = 0;
+ interpolationLabel = 0;
+ iterationInput = 0;
+ iterationLabel = 0;
+ sharpnessInput = 0;
+ sharpnessLabel = 0;
+ sigmaInput = 0;
+ sigmaLabel = 0;
+ tileInput = 0;
+ tileLabel = 0;
+ }
+
+ TQLabel *alphaLabel;
+ TQLabel *amplitudeLabel;
+ TQLabel *anisotropyLabel;
+ TQLabel *btileLabel;
+ TQLabel *daLabel;
+ TQLabel *dlLabel;
+ TQLabel *gaussianPrecLabel;
+ TQLabel *interpolationLabel;
+ TQLabel *iterationLabel;
+ TQLabel *sharpnessLabel;
+ TQLabel *sigmaLabel;
+ TQLabel *tileLabel;
+
+ TQWidget *advancedPage;
+ TQWidget *generalPage;
+
+ TQCheckBox *fastApproxCBox;
+
+ TQTabWidget *parent;
+
+ RComboBox *interpolationBox;
+
+ RDoubleNumInput *alphaInput;
+ RDoubleNumInput *amplitudeInput;
+ RDoubleNumInput *anisotropyInput;
+ RDoubleNumInput *daInput;
+ RDoubleNumInput *dlInput;
+ RDoubleNumInput *gaussianPrecInput;
+ RDoubleNumInput *sharpnessInput;
+ RDoubleNumInput *sigmaInput;
+
+ RIntNumInput *btileInput;
+ RIntNumInput *iterationInput;
+ RIntNumInput *tileInput;
+};
+
+GreycstorationWidget::GreycstorationWidget(TQTabWidget *parent)
+ : TQObject(parent)
+{
+ d = new GreycstorationWidgetPriv;
+ d->parent = parent;
+
+ // -------------------------------------------------------------
+
+ d->generalPage = new TQWidget( parent );
+ TQGridLayout* grid1 = new TQGridLayout(d->generalPage, 6, 2, KDialog::spacingHint());
+ parent->addTab( d->generalPage, i18n("General") );
+
+ d->sharpnessLabel = new TQLabel(i18n("Detail preservation:"), d->generalPage);
+ d->sharpnessInput = new RDoubleNumInput(d->generalPage);
+ d->sharpnessInput->setPrecision(2);
+ d->sharpnessInput->setRange(0.01, 1.0, 0.1);
+ TQWhatsThis::add( d->sharpnessInput, i18n("<p>Preservation of details to set the sharpening level "
+ "of the small features in the target image. "
+ "Higher values leave details sharp."));
+ grid1->addMultiCellWidget(d->sharpnessLabel, 0, 0, 0, 0);
+ grid1->addMultiCellWidget(d->sharpnessInput, 0, 0, 1, 1);
+
+ d->anisotropyLabel = new TQLabel(i18n("Anisotropy:"), d->generalPage);
+ d->anisotropyInput = new RDoubleNumInput(d->generalPage);
+ d->anisotropyInput->setPrecision(2);
+ d->anisotropyInput->setRange(0.0, 1.0, 0.1);
+ TQWhatsThis::add( d->anisotropyInput, i18n("<p>Anisotropic (directional) modifier of the details. "
+ "Keep it small for Gaussian noise."));
+ grid1->addMultiCellWidget(d->anisotropyLabel, 1, 1, 0, 0);
+ grid1->addMultiCellWidget(d->anisotropyInput, 1, 1, 1, 1);
+
+ d->amplitudeLabel = new TQLabel(i18n("Smoothing:"), d->generalPage);
+ d->amplitudeInput = new RDoubleNumInput(d->generalPage);
+ d->amplitudeInput->setPrecision(2);
+ d->amplitudeInput->setRange(0.01, 500.0, 0.1);
+ TQWhatsThis::add( d->amplitudeInput, i18n("<p>Total smoothing power: if the Detail Factor sets the relative "
+ "smoothing and the Anisotropy Factor the direction, "
+ "the Smoothing Factor sets the overall effect."));
+ grid1->addMultiCellWidget(d->amplitudeLabel, 2, 2, 0, 0);
+ grid1->addMultiCellWidget(d->amplitudeInput, 2, 2, 1, 1);
+
+ d->sigmaLabel = new TQLabel(i18n("Regularity:"), d->generalPage);
+ d->sigmaInput = new RDoubleNumInput(d->generalPage);
+ d->sigmaInput->setPrecision(2);
+ d->sigmaInput->setRange(0.0, 10.0, 0.1);
+ TQWhatsThis::add( d->sigmaInput, i18n("<p>This value controls the evenness of smoothing to the image. "
+ "Do not use a high value here, or the "
+ "target image will be completely blurred."));
+ grid1->addMultiCellWidget(d->sigmaLabel, 3, 3, 0, 0);
+ grid1->addMultiCellWidget(d->sigmaInput, 3, 3, 1, 1);
+
+ d->iterationLabel = new TQLabel(i18n("Iterations:"), d->generalPage);
+ d->iterationInput = new RIntNumInput(d->generalPage);
+ d->iterationInput->setRange(1, 5000, 1);
+ TQWhatsThis::add( d->iterationInput, i18n("<p>Sets the number of times the filter is applied to "
+ "the image."));
+ grid1->addMultiCellWidget(d->iterationLabel, 4, 4, 0, 0);
+ grid1->addMultiCellWidget(d->iterationInput, 4, 4, 1, 1);
+
+ d->alphaLabel = new TQLabel(i18n("Noise:"), d->generalPage);
+ d->alphaInput = new RDoubleNumInput(d->generalPage);
+ d->alphaInput->setPrecision(2);
+ d->alphaInput->setRange(0.01, 1.0, 0.1);
+ TQWhatsThis::add( d->alphaInput, i18n("<p>Sets the noise scale."));
+ grid1->addMultiCellWidget(d->alphaLabel, 5, 5, 0, 0);
+ grid1->addMultiCellWidget(d->alphaInput, 5, 5, 1, 1);
+ grid1->setRowStretch(6, 10);
+
+ // -------------------------------------------------------------
+
+ d->advancedPage = new TQWidget( parent );
+ TQGridLayout* grid2 = new TQGridLayout(d->advancedPage, 6, 2, KDialog::spacingHint());
+ parent->addTab( d->advancedPage, i18n("Advanced Settings") );
+
+ d->daLabel = new TQLabel(i18n("Angular step:"), d->advancedPage);
+ d->daInput = new RDoubleNumInput(d->advancedPage);
+ d->daInput->setPrecision(2);
+ d->daInput->setRange(0.0, 90.0, 1.0);
+ TQWhatsThis::add( d->daInput, i18n("<p>Set here the angular integration step (in degrees) "
+ "analogous to anisotropy."));
+ grid2->addMultiCellWidget(d->daLabel, 0, 0, 0, 0);
+ grid2->addMultiCellWidget(d->daInput, 0, 0, 1, 1);
+
+ d->dlLabel = new TQLabel(i18n("Integral step:"), d->advancedPage);
+ d->dlInput = new RDoubleNumInput(d->advancedPage);
+ d->dlInput->setPrecision(2);
+ d->dlInput->setRange(0.0, 1.0, 0.1);
+ TQWhatsThis::add( d->dlInput, i18n("<p>Set here the spatial integral step."));
+ grid2->addMultiCellWidget(d->dlLabel, 1, 1, 0, 0);
+ grid2->addMultiCellWidget(d->dlInput, 1, 1, 1, 1);
+
+ d->gaussianPrecLabel = new TQLabel(i18n("Gaussian:"), d->advancedPage);
+ d->gaussianPrecInput = new RDoubleNumInput(d->advancedPage);
+ d->gaussianPrecInput->setPrecision(2);
+ d->gaussianPrecInput->setRange(0.01, 20.0, 0.01);
+ TQWhatsThis::add( d->gaussianPrecInput, i18n("<p>Set here the precision of the Gaussian function."));
+ grid2->addMultiCellWidget(d->gaussianPrecLabel, 2, 2, 0, 0);
+ grid2->addMultiCellWidget(d->gaussianPrecInput, 2, 2, 1, 1);
+
+ d->tileLabel = new TQLabel(i18n("Tile size:"), d->advancedPage);
+ d->tileInput = new RIntNumInput(d->advancedPage);
+ d->tileInput->setRange(0, 2000, 1);
+ TQWhatsThis::add( d->tileInput, i18n("<p>Sets the tile size."));
+ grid2->addMultiCellWidget(d->tileLabel, 3, 3, 0, 0);
+ grid2->addMultiCellWidget(d->tileInput, 3, 3, 1, 1);
+
+ d->btileLabel = new TQLabel(i18n("Tile border:"), d->advancedPage);
+ d->btileInput = new RIntNumInput(d->advancedPage);
+ d->btileInput->setRange(1, 20, 1);
+ TQWhatsThis::add( d->btileInput, i18n("<p>Sets the size of each tile border."));
+ grid2->addMultiCellWidget(d->btileLabel, 4, 4, 0, 0);
+ grid2->addMultiCellWidget(d->btileInput, 4, 4, 1, 1);
+
+ d->interpolationLabel = new TQLabel(i18n("Interpolation:"), d->advancedPage);
+ d->interpolationBox = new RComboBox(d->advancedPage);
+ d->interpolationBox->insertItem( i18n("Nearest Neighbor"), GreycstorationSettings::NearestNeighbor );
+ d->interpolationBox->insertItem( i18n("Linear"), GreycstorationSettings::Linear );
+ d->interpolationBox->insertItem( i18n("Runge-Kutta"), GreycstorationSettings::RungeKutta);
+ TQWhatsThis::add( d->interpolationBox, i18n("<p>Select the right interpolation method for the "
+ "desired image quality."));
+ grid2->addMultiCellWidget(d->interpolationLabel, 5, 5, 0, 0);
+ grid2->addMultiCellWidget(d->interpolationBox, 5, 5, 1, 1);
+
+ d->fastApproxCBox = new TQCheckBox(i18n("Fast approximation"), d->advancedPage);
+ TQWhatsThis::add( d->fastApproxCBox, i18n("<p>Enable fast approximation when rendering images."));
+ grid2->addMultiCellWidget(d->fastApproxCBox, 6, 6, 0, 1);
+}
+
+GreycstorationWidget::~GreycstorationWidget()
+{
+ delete d;
+}
+
+void GreycstorationWidget::setEnabled(bool b)
+{
+ d->generalPage->setEnabled(b);
+ d->advancedPage->setEnabled(b);
+ d->parent->setTabEnabled(d->generalPage, b);
+ d->parent->setTabEnabled(d->advancedPage, b);
+}
+
+void GreycstorationWidget::setSettings(GreycstorationSettings settings)
+{
+ blockSignals(true);
+ d->alphaInput->setValue(settings.alpha);
+ d->amplitudeInput->setValue(settings.amplitude);
+ d->anisotropyInput->setValue(settings.anisotropy);
+ d->btileInput->setValue(settings.btile);
+ d->daInput->setValue(settings.da);
+ d->dlInput->setValue(settings.dl);
+ d->fastApproxCBox->setChecked(settings.fastApprox);
+ d->gaussianPrecInput->setValue(settings.gaussPrec);
+ d->interpolationBox->setCurrentItem(settings.interp);
+ d->iterationInput->setValue(settings.nbIter);
+ d->sharpnessInput->setValue(settings.sharpness);
+ d->sigmaInput->setValue(settings.sigma);
+ d->tileInput->setValue(settings.tile);
+ blockSignals(false);
+}
+
+void GreycstorationWidget::setDefaultSettings(GreycstorationSettings settings)
+{
+ blockSignals(true);
+ d->alphaInput->setDefaultValue(settings.alpha);
+ d->amplitudeInput->setDefaultValue(settings.amplitude);
+ d->anisotropyInput->setDefaultValue(settings.anisotropy);
+ d->btileInput->setDefaultValue(settings.btile);
+ d->daInput->setDefaultValue(settings.da);
+ d->dlInput->setDefaultValue(settings.dl);
+ d->fastApproxCBox->setChecked(settings.fastApprox);
+ d->gaussianPrecInput->setDefaultValue(settings.gaussPrec);
+ d->interpolationBox->setDefaultItem(settings.interp);
+ d->iterationInput->setDefaultValue(settings.nbIter);
+ d->sharpnessInput->setDefaultValue(settings.sharpness);
+ d->sigmaInput->setDefaultValue(settings.sigma);
+ d->tileInput->setDefaultValue(settings.tile);
+ blockSignals(false);
+}
+
+GreycstorationSettings GreycstorationWidget::getSettings()
+{
+ GreycstorationSettings settings;
+
+ settings.fastApprox = d->fastApproxCBox->isChecked();
+ settings.interp = d->interpolationBox->currentItem();
+ settings.amplitude = d->amplitudeInput->value();
+ settings.sharpness = d->sharpnessInput->value();
+ settings.anisotropy = d->anisotropyInput->value();
+ settings.alpha = d->alphaInput->value();
+ settings.sigma = d->sigmaInput->value();
+ settings.gaussPrec = d->gaussianPrecInput->value();
+ settings.dl = d->dlInput->value();
+ settings.da = d->daInput->value();
+ settings.nbIter = d->iterationInput->value();
+ settings.tile = d->tileInput->value();
+ settings.btile = d->btileInput->value();
+
+ return settings;
+}
+
+bool GreycstorationWidget::loadSettings(TQFile& file, const TQString& header)
+{
+ TQTextStream stream( &file );
+
+ if (stream.readLine() != header)
+ return false;
+
+ blockSignals(true);
+
+ GreycstorationSettings settings;
+ settings.fastApprox = stream.readLine().toInt();
+ settings.interp = stream.readLine().toInt();
+ settings.amplitude = stream.readLine().toDouble();
+ settings.sharpness = stream.readLine().toDouble();
+ settings.anisotropy = stream.readLine().toDouble();
+ settings.alpha = stream.readLine().toDouble();
+ settings.sigma = stream.readLine().toDouble();
+ settings.gaussPrec = stream.readLine().toDouble();
+ settings.dl = stream.readLine().toDouble();
+ settings.da = stream.readLine().toDouble();
+ settings.nbIter = stream.readLine().toInt();
+ settings.tile = stream.readLine().toInt();
+ settings.btile = stream.readLine().toInt();
+ setSettings(settings);
+
+ blockSignals(false);
+ return true;
+}
+
+void GreycstorationWidget::saveSettings(TQFile& file, const TQString& header)
+{
+ GreycstorationSettings settings = getSettings();
+ TQTextStream stream( &file );
+ stream << header << "\n";
+ stream << settings.fastApprox << "\n";
+ stream << settings.interp << "\n";
+ stream << settings.amplitude << "\n";
+ stream << settings.sharpness << "\n";
+ stream << settings.anisotropy << "\n";
+ stream << settings.alpha << "\n";
+ stream << settings.sigma << "\n";
+ stream << settings.gaussPrec << "\n";
+ stream << settings.dl << "\n";
+ stream << settings.da << "\n";
+ stream << settings.nbIter << "\n";
+ stream << settings.tile << "\n";
+ stream << settings.btile << "\n";
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/greycstoration/greycstorationwidget.h b/src/libs/greycstoration/greycstorationwidget.h
new file mode 100644
index 00000000..22797071
--- /dev/null
+++ b/src/libs/greycstoration/greycstorationwidget.h
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-09-13
+ * Description : Greycstoration settings widgets
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef GREYCSTORATION_WIDGET_H
+#define GREYCSTORATION_WIDGET_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqfile.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "greycstorationsettings.h"
+
+class TQTabWidget;
+
+namespace Digikam
+{
+
+class GreycstorationWidgetPriv;
+
+class DIGIKAM_EXPORT GreycstorationWidget : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ GreycstorationWidget(TQTabWidget *parent);
+ ~GreycstorationWidget();
+
+ void setSettings(GreycstorationSettings settings);
+ void setDefaultSettings(GreycstorationSettings settings);
+ GreycstorationSettings getSettings();
+
+ bool loadSettings(TQFile& file, const TQString& header);
+ void saveSettings(TQFile& file, const TQString& header);
+
+ void setEnabled(bool);
+
+private:
+
+ GreycstorationWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* GREYCSTORATION_WIDGET_H */
diff --git a/src/libs/histogram/Makefile.am b/src/libs/histogram/Makefile.am
new file mode 100644
index 00000000..56ea96f6
--- /dev/null
+++ b/src/libs/histogram/Makefile.am
@@ -0,0 +1,16 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libhistogram.la
+
+libhistogram_la_SOURCES = imagehistogram.cpp
+
+libhistogram_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikaminclude_HEADERS = imagehistogram.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/histogram/imagehistogram.cpp b/src/libs/histogram/imagehistogram.cpp
new file mode 100644
index 00000000..bee55624
--- /dev/null
+++ b/src/libs/histogram/imagehistogram.cpp
@@ -0,0 +1,548 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-21
+ * Description : image histogram manipulation methods.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Some code parts are inspired from gimp 2.0
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+#include <cstring>
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqevent.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "imagehistogram.h"
+
+namespace Digikam
+{
+
+class ImageHistogramPriv
+{
+
+public:
+
+ // Using a structure instead a class is more fast
+ // (access with memset() and bytes manipulation).
+
+ struct double_packet
+ {
+ double value;
+ double red;
+ double green;
+ double blue;
+ double alpha;
+ };
+
+public:
+
+ ImageHistogramPriv()
+ {
+ parent = 0;
+ imageData = 0;
+ histogram = 0;
+ runningFlag = true;
+ }
+
+ /** The histogram data.*/
+ struct double_packet *histogram;
+
+ /** Image information.*/
+ uchar *imageData;
+ uint imageWidth;
+ uint imageHeight;
+
+ /** Numbers of histogram segments dependaing of image bytes depth*/
+ int histoSegments;
+
+ /** To post event from thread to parent.*/
+ TQObject *parent;
+
+ /** Used to stop thread during calculations.*/
+ bool runningFlag;
+};
+
+ImageHistogram::ImageHistogram(const DImg& image, TQObject *parent)
+ : TQThread()
+{
+ setup(image.bits(), image.width(), image.height(), image.sixteenBit(), parent);
+}
+
+ImageHistogram::ImageHistogram(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent)
+ : TQThread()
+{
+ setup(i_data, i_w, i_h, i_sixteenBits, parent);
+}
+
+void ImageHistogram::setup(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent)
+{
+ d = new ImageHistogramPriv;
+ d->imageData = i_data;
+ d->imageWidth = i_w;
+ d->imageHeight = i_h;
+ d->parent = parent;
+ d->histoSegments = i_sixteenBits ? 65536 : 256;
+
+ if (d->imageData && d->imageWidth && d->imageHeight)
+ {
+ if (d->parent)
+ start();
+ else
+ calcHistogramValues();
+ }
+ else
+ {
+ if (d->parent)
+ postProgress(false, false);
+ }
+}
+
+ImageHistogram::~ImageHistogram()
+{
+ stopCalcHistogramValues();
+
+ if (d->histogram)
+ delete [] d->histogram;
+
+ delete d;
+}
+
+int ImageHistogram::getHistogramSegment(void)
+{
+ return d->histoSegments;
+}
+
+void ImageHistogram::postProgress(bool starting, bool success)
+{
+ EventData *eventData = new EventData();
+ eventData->starting = starting;
+ eventData->success = success;
+ eventData->histogram = this;
+ TQApplication::postEvent(d->parent, new TQCustomEvent(TQEvent::User, eventData));
+}
+
+void ImageHistogram::stopCalcHistogramValues(void)
+{
+ d->runningFlag = false;
+ wait();
+}
+
+// List of threaded operations.
+
+void ImageHistogram::run()
+{
+ calcHistogramValues();
+}
+
+void ImageHistogram::calcHistogramValues()
+{
+ uint i;
+ int max;
+
+ if (d->parent)
+ postProgress(true, false);
+
+ d->histogram = new ImageHistogramPriv::double_packet[d->histoSegments];
+ memset(d->histogram, 0, d->histoSegments*sizeof(ImageHistogramPriv::double_packet));
+
+ if ( !d->histogram )
+ {
+ DWarning() << ("HistogramWidget::calcHistogramValues: Unable to allocate memory!") << endl;
+
+ if (d->parent)
+ postProgress(false, false);
+
+ return;
+ }
+
+ memset(d->histogram, 0, d->histoSegments*sizeof(struct ImageHistogramPriv::double_packet));
+
+ if (d->histoSegments == 65536) // 16 bits image.
+ {
+ unsigned short blue, green, red, alpha;
+ unsigned short *data = (unsigned short*)d->imageData;
+
+ for (i = 0 ; (i < d->imageHeight*d->imageWidth*4) && d->runningFlag ; i+=4)
+ {
+ blue = data[ i ];
+ green = data[i+1];
+ red = data[i+2];
+ alpha = data[i+3];
+
+ d->histogram[blue].blue++;
+ d->histogram[green].green++;
+ d->histogram[red].red++;
+ d->histogram[alpha].alpha++;
+
+ max = (blue > green) ? blue : green;
+
+ if (red > max)
+ d->histogram[red].value++;
+ else
+ d->histogram[max].value++;
+ }
+ }
+ else // 8 bits images.
+ {
+ uchar blue, green, red, alpha;
+ uchar *data = d->imageData;
+
+ for (i = 0 ; (i < d->imageHeight*d->imageWidth*4) && d->runningFlag ; i+=4)
+ {
+ blue = data[ i ];
+ green = data[i+1];
+ red = data[i+2];
+ alpha = data[i+3];
+
+ d->histogram[blue].blue++;
+ d->histogram[green].green++;
+ d->histogram[red].red++;
+ d->histogram[alpha].alpha++;
+
+ max = (blue > green) ? blue : green;
+
+ if (red > max)
+ d->histogram[red].value++;
+ else
+ d->histogram[max].value++;
+ }
+ }
+
+ if (d->parent && d->runningFlag)
+ postProgress(false, true);
+}
+
+double ImageHistogram::getCount(int channel, int start, int end)
+{
+ int i;
+ double count = 0.0;
+
+ if ( !d->histogram || start < 0 ||
+ end > d->histoSegments-1 || start > end )
+ return 0.0;
+
+ switch(channel)
+ {
+ case ImageHistogram::ValueChannel:
+ for (i = start ; i <= end ; i++)
+ count += d->histogram[i].value;
+ break;
+
+ case ImageHistogram::RedChannel:
+ for (i = start ; i <= end ; i++)
+ count += d->histogram[i].red;
+ break;
+
+ case ImageHistogram::GreenChannel:
+ for (i = start ; i <= end ; i++)
+ count += d->histogram[i].green;
+ break;
+
+ case ImageHistogram::BlueChannel:
+ for (i = start ; i <= end ; i++)
+ count += d->histogram[i].blue;
+ break;
+
+ case ImageHistogram::AlphaChannel:
+ for (i = start ; i <= end ; i++)
+ count += d->histogram[i].alpha;
+ break;
+
+ default:
+ return 0.0;
+ break;
+ }
+
+ return count;
+}
+
+double ImageHistogram::getPixels()
+{
+ if ( !d->histogram )
+ return 0.0;
+
+ return(d->imageWidth * d->imageHeight);
+}
+
+double ImageHistogram::getMean(int channel, int start, int end)
+{
+ int i;
+ double mean = 0.0;
+ double count;
+
+ if ( !d->histogram || start < 0 ||
+ end > d->histoSegments-1 || start > end )
+ return 0.0;
+
+ switch(channel)
+ {
+ case ImageHistogram::ValueChannel:
+ for (i = start ; i <= end ; i++)
+ mean += i * d->histogram[i].value;
+ break;
+
+ case ImageHistogram::RedChannel:
+ for (i = start ; i <= end ; i++)
+ mean += i * d->histogram[i].red;
+ break;
+
+ case ImageHistogram::GreenChannel:
+ for (i = start ; i <= end ; i++)
+ mean += i * d->histogram[i].green;
+ break;
+
+ case ImageHistogram::BlueChannel:
+ for (i = start ; i <= end ; i++)
+ mean += i * d->histogram[i].blue;
+ break;
+
+ case ImageHistogram::AlphaChannel:
+ for (i = start ; i <= end ; i++)
+ mean += i * d->histogram[i].alpha;
+ break;
+
+ default:
+ return 0.0;
+ break;
+ }
+
+ count = getCount(channel, start, end);
+
+ if (count > 0.0)
+ return mean / count;
+
+ return mean;
+}
+
+int ImageHistogram::getMedian(int channel, int start, int end)
+{
+ int i;
+ double sum = 0.0;
+ double count;
+
+ if ( !d->histogram || start < 0 ||
+ end > d->histoSegments-1 || start > end )
+ return 0;
+
+ count = getCount(channel, start, end);
+
+ switch(channel)
+ {
+ case ImageHistogram::ValueChannel:
+ for (i = start ; i <= end ; i++)
+ {
+ sum += d->histogram[i].value;
+ if (sum * 2 > count) return i;
+ }
+ break;
+
+ case ImageHistogram::RedChannel:
+ for (i = start ; i <= end ; i++)
+ {
+ sum += d->histogram[i].red;
+ if (sum * 2 > count) return i;
+ }
+ break;
+
+ case ImageHistogram::GreenChannel:
+ for (i = start ; i <= end ; i++)
+ {
+ sum += d->histogram[i].green;
+ if (sum * 2 > count) return i;
+ }
+ break;
+
+ case ImageHistogram::BlueChannel:
+ for (i = start ; i <= end ; i++)
+ {
+ sum += d->histogram[i].blue;
+ if (sum * 2 > count) return i;
+ }
+ break;
+
+ case ImageHistogram::AlphaChannel:
+ for (i = start ; i <= end ; i++)
+ {
+ sum += d->histogram[i].alpha;
+ if (sum * 2 > count) return i;
+ }
+ break;
+
+ default:
+ return 0;
+ break;
+ }
+
+ return -1;
+}
+
+double ImageHistogram::getStdDev(int channel, int start, int end)
+{
+ int i;
+ double dev = 0.0;
+ double count;
+ double mean;
+
+ if ( !d->histogram || start < 0 ||
+ end > d->histoSegments-1 || start > end )
+ return 0.0;
+
+ mean = getMean(channel, start, end);
+ count = getCount(channel, start, end);
+
+ if (count == 0.0)
+ count = 1.0;
+
+ switch(channel)
+ {
+ case ImageHistogram::ValueChannel:
+ for (i = start ; i <= end ; i++)
+ dev += (i - mean) * (i - mean) * d->histogram[i].value;
+ break;
+
+ case ImageHistogram::RedChannel:
+ for (i = start ; i <= end ; i++)
+ dev += (i - mean) * (i - mean) * d->histogram[i].red;
+ break;
+
+ case ImageHistogram::GreenChannel:
+ for (i = start ; i <= end ; i++)
+ dev += (i - mean) * (i - mean) * d->histogram[i].green;
+ break;
+
+ case ImageHistogram::BlueChannel:
+ for (i = start ; i <= end ; i++)
+ dev += (i - mean) * (i - mean) * d->histogram[i].blue;
+ break;
+
+ case ImageHistogram::AlphaChannel:
+ for (i = start ; i <= end ; i++)
+ dev += (i - mean) * (i - mean) * d->histogram[i].alpha;
+ break;
+
+ default:
+ return 0.0;
+ break;
+ }
+
+ return sqrt(dev / count);
+}
+
+double ImageHistogram::getValue(int channel, int bin)
+{
+ double value;
+
+ if ( !d->histogram || bin < 0 || bin > d->histoSegments-1 )
+ return 0.0;
+
+ switch(channel)
+ {
+ case ImageHistogram::ValueChannel:
+ value = d->histogram[bin].value;
+ break;
+
+ case ImageHistogram::RedChannel:
+ value = d->histogram[bin].red;
+ break;
+
+ case ImageHistogram::GreenChannel:
+ value = d->histogram[bin].green;
+ break;
+
+ case ImageHistogram::BlueChannel:
+ value = d->histogram[bin].blue;
+ break;
+
+ case ImageHistogram::AlphaChannel:
+ value = d->histogram[bin].alpha;
+ break;
+
+ default:
+ return 0.0;
+ break;
+ }
+
+ return value;
+}
+
+double ImageHistogram::getMaximum(int channel)
+{
+ double max = 0.0;
+ int x;
+
+ if ( !d->histogram )
+ return 0.0;
+
+ switch(channel)
+ {
+ case ImageHistogram::ValueChannel:
+ for (x = 0 ; x < d->histoSegments ; x++)
+ if (d->histogram[x].value > max)
+ max = d->histogram[x].value;
+ break;
+
+ case ImageHistogram::RedChannel:
+ for (x = 0 ; x < d->histoSegments ; x++)
+ if (d->histogram[x].red > max)
+ max = d->histogram[x].red;
+ break;
+
+ case ImageHistogram::GreenChannel:
+ for (x = 0 ; x < d->histoSegments ; x++)
+ if (d->histogram[x].green > max)
+ max = d->histogram[x].green;
+ break;
+
+ case ImageHistogram::BlueChannel:
+ for (x = 0 ; x < d->histoSegments ; x++)
+ if (d->histogram[x].blue > max)
+ max = d->histogram[x].blue;
+ break;
+
+ case ImageHistogram::AlphaChannel:
+ for (x = 0 ; x < d->histoSegments ; x++)
+ if (d->histogram[x].alpha > max)
+ max = d->histogram[x].alpha;
+ break;
+
+ default:
+ return 0.0;
+ break;
+ }
+
+ return max;
+}
+
+} // NameSpace Digikam
+
diff --git a/src/libs/histogram/imagehistogram.h b/src/libs/histogram/imagehistogram.h
new file mode 100644
index 00000000..6beb4919
--- /dev/null
+++ b/src/libs/histogram/imagehistogram.h
@@ -0,0 +1,113 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-21
+ * Description : image histogram manipulation methods.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef IMAGEHISTOGRAM_H
+#define IMAGEHISTOGRAM_H
+
+// TQt includes.
+
+#include <tqthread.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQObject;
+
+namespace Digikam
+{
+
+class ImageHistogramPriv;
+class DImg;
+
+class DIGIKAM_EXPORT ImageHistogram : public TQThread
+{
+
+public:
+
+enum HistogramChannelType
+{
+ ValueChannel = 0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel,
+ AlphaChannel
+};
+
+class EventData
+{
+public:
+
+ EventData()
+ {
+ starting = false;
+ success = false;
+ histogram = 0;
+ }
+
+ bool starting;
+ bool success;
+ ImageHistogram *histogram;
+};
+
+public:
+
+ ImageHistogram(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent=0);
+
+ ImageHistogram(const DImg& image, TQObject *parent=0);
+ ~ImageHistogram();
+
+ void setup(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent);
+
+ /** Method to stop threaded computations.*/
+ void stopCalcHistogramValues(void);
+
+ /** Methods for to manipulate the histogram data.*/
+ double getCount(int channel, int start, int end);
+ double getMean(int channel, int start, int end);
+ double getPixels();
+ double getStdDev(int channel, int start, int end);
+ double getValue(int channel, int bin);
+ double getMaximum(int channel);
+
+ int getHistogramSegment(void);
+ int getMedian(int channel, int start, int end);
+
+private:
+
+ ImageHistogramPriv* d;
+
+private:
+
+ void calcHistogramValues();
+ void postProgress(bool starting, bool success);
+
+protected:
+
+ virtual void run();
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEHISTOGRAM_H */
diff --git a/src/libs/imageproperties/Makefile.am b/src/libs/imageproperties/Makefile.am
new file mode 100644
index 00000000..2c4f021a
--- /dev/null
+++ b/src/libs/imageproperties/Makefile.am
@@ -0,0 +1,51 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libimagepropertiesshowfoto.la libimagepropertiesdigikam.la \
+ libimagepropertiescamgui.la
+
+# Image Properties SideBar for Camera GUI.
+
+libimagepropertiescamgui_la_SOURCES = imagepropertiessidebarcamgui.cpp cameraitempropertiestab.cpp
+
+libimagepropertiescamgui_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+# Image Properties SideBar for Showfoto (without digiKam database support).
+
+libimagepropertiesshowfoto_la_SOURCES = imagepropertiessidebar.cpp navigatebarwidget.cpp \
+ imagepropertiesmetadatatab.cpp imagepropertiescolorstab.cpp \
+ imagepropertiestab.cpp navigatebartab.cpp
+
+libimagepropertiesshowfoto_la_LIBADD = $(top_builddir)/src/libs/widgets/libwidgets.la \
+ $(top_builddir)/src/libs/dmetadata/libdmetadata.la \
+ $(top_builddir)/src/libs/dimg/libdimg.la \
+ $(top_builddir)/src/libs/threadimageio/libthreadimageio.la \
+ $(top_builddir)/src/libs/histogram/libhistogram.la
+
+libimagepropertiesshowfoto_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+# Image Properties SideBar for digiKam Main interface and Image Editor (digiKam database support).
+
+libimagepropertiesdigikam_la_SOURCES = imagedescedittab.cpp imagepropertiessidebar.cpp \
+ imagepropertiessidebardb.cpp \
+ talbumlistview.cpp imagepropertiesmetadatatab.cpp \
+ imagepropertiescolorstab.cpp \
+ navigatebarwidget.cpp imagepropertiestab.cpp navigatebartab.cpp
+
+libimagepropertiesdigikam_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/widgets/metadata \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/utilities/cameragui \
+ -I$(top_srcdir)/src/utilities/batch \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKEXIV2_CFLAGS) \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
diff --git a/src/libs/imageproperties/cameraitempropertiestab.cpp b/src/libs/imageproperties/cameraitempropertiestab.cpp
new file mode 100644
index 00000000..8593e9c7
--- /dev/null
+++ b/src/libs/imageproperties/cameraitempropertiestab.cpp
@@ -0,0 +1,555 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-08
+ * Description : A tab to display camera item information
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqstyle.h>
+#include <tqfile.h>
+#include <tqlabel.h>
+#include <tqpixmap.h>
+#include <tqcombobox.h>
+#include <tqwhatsthis.h>
+#include <tqframe.h>
+#include <tqscrollview.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kdialogbase.h>
+#include <tdefileitem.h>
+#include <kmimetype.h>
+#include <kseparator.h>
+#include <ksqueezedtextlabel.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "gpiteminfo.h"
+#include "navigatebarwidget.h"
+#include "cameraitempropertiestab.h"
+#include "cameraitempropertiestab.moc"
+
+namespace Digikam
+{
+
+class CameraItemPropertiesTabPriv
+{
+public:
+
+ CameraItemPropertiesTabPriv()
+ {
+ title = 0;
+ file = 0;
+ folder = 0;
+ date = 0;
+ size = 0;
+ isReadable = 0;
+ isWritable = 0;
+ mime = 0;
+ dimensions = 0;
+ newFileName = 0;
+ downloaded = 0;
+ settingsArea = 0;
+ title2 = 0;
+ make = 0;
+ model = 0;
+ photoDate = 0;
+ aperture = 0;
+ focalLength = 0;
+ exposureTime = 0;
+ sensitivity = 0;
+ exposureMode = 0;
+ flash = 0;
+ whiteBalance = 0;
+ labelFile = 0;
+ labelFolder = 0;
+ labelFileIsReadable = 0;
+ labelFileIsWritable = 0;
+ labelFileDate = 0;
+ labelFileSize = 0;
+ labelImageMime = 0;
+ labelImageDimensions = 0;
+ labelNewFileName = 0;
+ labelAlreadyDownloaded = 0;
+ labelPhotoMake = 0;
+ labelPhotoModel = 0;
+ labelPhotoDateTime = 0;
+ labelPhotoAperture = 0;
+ labelPhotoFocalLength = 0;
+ labelPhotoExposureTime = 0;
+ labelPhotoSensitivity = 0;
+ labelPhotoExposureMode = 0;
+ labelPhotoFlash = 0;
+ labelPhotoWhiteBalance = 0;
+ }
+
+ TQLabel *title;
+ TQLabel *file;
+ TQLabel *folder;
+ TQLabel *date;
+ TQLabel *size;
+ TQLabel *isReadable;
+ TQLabel *isWritable;
+ TQLabel *mime;
+ TQLabel *dimensions;
+ TQLabel *newFileName;
+ TQLabel *downloaded;
+
+ TQLabel *title2;
+ TQLabel *make;
+ TQLabel *model;
+ TQLabel *photoDate;
+ TQLabel *aperture;
+ TQLabel *focalLength;
+ TQLabel *exposureTime;
+ TQLabel *sensitivity;
+ TQLabel *exposureMode;
+ TQLabel *flash;
+ TQLabel *whiteBalance;
+
+ TQFrame *settingsArea;
+
+ KSqueezedTextLabel *labelFile;
+ KSqueezedTextLabel *labelFolder;
+ KSqueezedTextLabel *labelFileIsReadable;
+ KSqueezedTextLabel *labelFileIsWritable;
+ KSqueezedTextLabel *labelFileDate;
+ KSqueezedTextLabel *labelFileSize;
+ KSqueezedTextLabel *labelImageMime;
+ KSqueezedTextLabel *labelImageDimensions;
+ KSqueezedTextLabel *labelNewFileName;
+ KSqueezedTextLabel *labelAlreadyDownloaded;
+
+ KSqueezedTextLabel *labelPhotoMake;
+ KSqueezedTextLabel *labelPhotoModel;
+ KSqueezedTextLabel *labelPhotoDateTime;
+ KSqueezedTextLabel *labelPhotoAperture;
+ KSqueezedTextLabel *labelPhotoFocalLength;
+ KSqueezedTextLabel *labelPhotoExposureTime;
+ KSqueezedTextLabel *labelPhotoSensitivity;
+ KSqueezedTextLabel *labelPhotoExposureMode;
+ KSqueezedTextLabel *labelPhotoFlash;
+ KSqueezedTextLabel *labelPhotoWhiteBalance;
+};
+
+CameraItemPropertiesTab::CameraItemPropertiesTab(TQWidget* parent, bool navBar)
+ : NavigateBarTab(parent)
+{
+ d = new CameraItemPropertiesTabPriv;
+
+ setupNavigateBar(navBar);
+
+ TQScrollView *sv = new TQScrollView(this);
+ sv->viewport()->setBackgroundMode(TQt::PaletteBackground);
+ sv->setResizePolicy(TQScrollView::AutoOneFit);
+ sv->setFrameStyle(TQFrame::NoFrame);
+
+ d->settingsArea = new TQFrame(sv->viewport());
+ d->settingsArea->setFrameStyle( TQFrame::StyledPanel | TQFrame::Sunken );
+ d->settingsArea->setLineWidth( style().pixelMetric(TQStyle::PM_DefaultFrameWidth, this) );
+
+ sv->addChild(d->settingsArea);
+ m_navigateBarLayout->addWidget(sv);
+
+ // --------------------------------------------------
+
+ TQGridLayout *settingsLayout = new TQGridLayout(d->settingsArea, 27, 1, KDialog::spacingHint(), 0);
+
+ d->title = new TQLabel(i18n("<big><b>Camera File Properties</b></big>"), d->settingsArea);
+ d->file = new TQLabel(i18n("<b>File</b>:"), d->settingsArea);
+ d->folder = new TQLabel(i18n("<b>Folder</b>:"), d->settingsArea);
+ d->date = new TQLabel(i18n("<b>Date</b>:"), d->settingsArea);
+ d->size = new TQLabel(i18n("<b>Size</b>:"), d->settingsArea);
+ d->isReadable = new TQLabel(i18n("<b>Readable</b>:"), d->settingsArea);
+ d->isWritable = new TQLabel(i18n("<b>Writable</b>:"), d->settingsArea);
+ d->mime = new TQLabel(i18n("<b>Type</b>:"), d->settingsArea);
+ d->dimensions = new TQLabel(i18n("<b>Dimensions</b>:"), d->settingsArea);
+ d->newFileName = new TQLabel(i18n("<nobr><b>New Name</b></nobr>:"), d->settingsArea);
+ d->downloaded = new TQLabel(i18n("<b>Downloaded</b>:"), d->settingsArea);
+
+ KSeparator *line = new KSeparator(TQt::Horizontal, d->settingsArea);
+ d->title2 = new TQLabel(i18n("<big><b>Photograph Properties</b></big>"), d->settingsArea);
+ d->make = new TQLabel(i18n("<b>Make</b>:"), d->settingsArea);
+ d->model = new TQLabel(i18n("<b>Model</b>:"), d->settingsArea);
+ d->photoDate = new TQLabel(i18n("<b>Created</b>:"), d->settingsArea);
+ d->aperture = new TQLabel(i18n("<b>Aperture</b>:"), d->settingsArea);
+ d->focalLength = new TQLabel(i18n("<b>Focal</b>:"), d->settingsArea);
+ d->exposureTime = new TQLabel(i18n("<b>Exposure</b>:"), d->settingsArea);
+ d->sensitivity = new TQLabel(i18n("<b>Sensitivity</b>:"), d->settingsArea);
+ d->exposureMode = new TQLabel(i18n("<nobr><b>Mode/Program</b></nobr>:"), d->settingsArea);
+ d->flash = new TQLabel(i18n("<b>Flash</b>:"), d->settingsArea);
+ d->whiteBalance = new TQLabel(i18n("<nobr><b>White balance</b></nobr>:"), d->settingsArea);
+
+ d->labelFile = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFolder = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFileDate = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFileSize = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFileIsReadable = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFileIsWritable = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelImageMime = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelImageDimensions = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelNewFileName = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelAlreadyDownloaded = new KSqueezedTextLabel(0, d->settingsArea);
+
+ d->labelPhotoMake = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoModel = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoDateTime = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoAperture = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoFocalLength = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoExposureTime = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoSensitivity = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoExposureMode = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoFlash = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoWhiteBalance = new KSqueezedTextLabel(0, d->settingsArea);
+
+ int hgt = fontMetrics().height()-2;
+ d->title->setAlignment(TQt::AlignCenter);
+ d->file->setMaximumHeight(hgt);
+ d->folder->setMaximumHeight(hgt);
+ d->date->setMaximumHeight(hgt);
+ d->size->setMaximumHeight(hgt);
+ d->isReadable->setMaximumHeight(hgt);
+ d->isWritable->setMaximumHeight(hgt);
+ d->mime->setMaximumHeight(hgt);
+ d->dimensions->setMaximumHeight(hgt);
+ d->newFileName->setMaximumHeight(hgt);
+ d->downloaded->setMaximumHeight(hgt);
+ d->labelFile->setMaximumHeight(hgt);
+ d->labelFolder->setMaximumHeight(hgt);
+ d->labelFileDate->setMaximumHeight(hgt);
+ d->labelFileSize->setMaximumHeight(hgt);
+ d->labelFileIsReadable->setMaximumHeight(hgt);
+ d->labelFileIsWritable->setMaximumHeight(hgt);
+ d->labelImageMime->setMaximumHeight(hgt);
+ d->labelImageDimensions->setMaximumHeight(hgt);
+ d->labelNewFileName->setMaximumHeight(hgt);
+ d->labelAlreadyDownloaded->setMaximumHeight(hgt);
+
+ d->title2->setAlignment(TQt::AlignCenter);
+ d->make->setMaximumHeight(hgt);
+ d->model->setMaximumHeight(hgt);
+ d->photoDate->setMaximumHeight(hgt);
+ d->aperture->setMaximumHeight(hgt);
+ d->focalLength->setMaximumHeight(hgt);
+ d->exposureTime->setMaximumHeight(hgt);
+ d->sensitivity->setMaximumHeight(hgt);
+ d->exposureMode->setMaximumHeight(hgt);
+ d->flash->setMaximumHeight(hgt);
+ d->whiteBalance->setMaximumHeight(hgt);
+ d->labelPhotoMake->setMaximumHeight(hgt);
+ d->labelPhotoModel->setMaximumHeight(hgt);
+ d->labelPhotoDateTime->setMaximumHeight(hgt);
+ d->labelPhotoAperture->setMaximumHeight(hgt);
+ d->labelPhotoFocalLength->setMaximumHeight(hgt);
+ d->labelPhotoExposureTime->setMaximumHeight(hgt);
+ d->labelPhotoSensitivity->setMaximumHeight(hgt);
+ d->labelPhotoExposureMode->setMaximumHeight(hgt);
+ d->labelPhotoFlash->setMaximumHeight(hgt);
+ d->labelPhotoWhiteBalance->setMaximumHeight(hgt);
+
+ // --------------------------------------------------
+
+ settingsLayout->addMultiCellWidget(d->title, 0, 0, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 1, 1, 0, 1);
+ settingsLayout->addMultiCellWidget(d->file, 2, 2, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFile, 2, 2, 1, 1);
+ settingsLayout->addMultiCellWidget(d->folder, 3, 3, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFolder, 3, 3, 1, 1);
+ settingsLayout->addMultiCellWidget(d->date, 4, 4, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFileDate, 4, 4, 1, 1);
+ settingsLayout->addMultiCellWidget(d->size, 5, 5, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFileSize, 5, 5, 1, 1);
+ settingsLayout->addMultiCellWidget(d->isReadable, 6, 6, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFileIsReadable, 6, 6, 1, 1);
+ settingsLayout->addMultiCellWidget(d->isWritable, 7, 7, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFileIsWritable, 7, 7, 1, 1);
+ settingsLayout->addMultiCellWidget(d->mime, 8, 8, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelImageMime, 8, 8, 1, 1);
+ settingsLayout->addMultiCellWidget(d->dimensions, 9, 9, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelImageDimensions, 9, 9, 1, 1);
+ settingsLayout->addMultiCellWidget(d->newFileName, 10, 10, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelNewFileName, 10, 10, 1, 1);
+ settingsLayout->addMultiCellWidget(d->downloaded, 11, 11, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelAlreadyDownloaded, 11, 11, 1, 1);
+
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 12, 12, 0, 1);
+ settingsLayout->addMultiCellWidget(line, 13, 13, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 14, 14, 0, 1);
+
+ settingsLayout->addMultiCellWidget(d->title2, 15, 15, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 16, 16, 0, 1);
+ settingsLayout->addMultiCellWidget(d->make, 17, 17, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoMake, 17, 17, 1, 1);
+ settingsLayout->addMultiCellWidget(d->model, 18, 18, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoModel, 18, 18, 1, 1);
+ settingsLayout->addMultiCellWidget(d->photoDate, 19, 19, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoDateTime, 19, 19, 1, 1);
+ settingsLayout->addMultiCellWidget(d->aperture, 20, 20, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoAperture, 20, 20, 1, 1);
+ settingsLayout->addMultiCellWidget(d->focalLength, 21, 21, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoFocalLength, 21, 21, 1, 1);
+ settingsLayout->addMultiCellWidget(d->exposureTime, 22, 22, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoExposureTime, 22, 22, 1, 1);
+ settingsLayout->addMultiCellWidget(d->sensitivity, 23, 23, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoSensitivity, 23, 23, 1, 1);
+ settingsLayout->addMultiCellWidget(d->exposureMode, 24, 24, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoExposureMode, 24, 24, 1, 1);
+ settingsLayout->addMultiCellWidget(d->flash, 25, 25, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoFlash, 25, 25, 1, 1);
+ settingsLayout->addMultiCellWidget(d->whiteBalance, 26, 26, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoWhiteBalance, 26, 26, 1, 1);
+ settingsLayout->setRowStretch(27, 10);
+ settingsLayout->setColStretch(1, 10);
+}
+
+CameraItemPropertiesTab::~CameraItemPropertiesTab()
+{
+ delete d;
+}
+
+void CameraItemPropertiesTab::setCurrentItem(const GPItemInfo* itemInfo,
+ const TQString &newFileName, const TQByteArray& exifData,
+ const KURL &currentURL)
+{
+ if (!itemInfo)
+ {
+ d->labelFile->setText(TQString());
+ d->labelFolder->setText(TQString());
+ d->labelFileIsReadable->setText(TQString());
+ d->labelFileIsWritable->setText(TQString());
+ d->labelFileDate->setText(TQString());
+ d->labelFileSize->setText(TQString());
+ d->labelImageMime->setText(TQString());
+ d->labelImageDimensions->setText(TQString());
+ d->labelNewFileName->setText(TQString());
+ d->labelAlreadyDownloaded->setText(TQString());
+
+ d->labelPhotoMake->setText(TQString());
+ d->labelPhotoModel->setText(TQString());
+ d->labelPhotoDateTime->setText(TQString());
+ d->labelPhotoAperture->setText(TQString());
+ d->labelPhotoFocalLength->setText(TQString());
+ d->labelPhotoExposureTime->setText(TQString());
+ d->labelPhotoSensitivity->setText(TQString());
+ d->labelPhotoExposureMode->setText(TQString());
+ d->labelPhotoFlash->setText(TQString());
+ d->labelPhotoWhiteBalance->setText(TQString());
+
+ setEnabled(false);
+ return;
+ }
+
+ setEnabled(true);
+
+ TQString str;
+ TQString unknown(i18n("<i>unknown</i>"));
+
+ // -- Camera file system information ------------------------------------------
+
+ d->labelFile->setText(itemInfo->name);
+ d->labelFolder->setText(itemInfo->folder);
+
+ if (itemInfo->readPermissions < 0)
+ str = unknown;
+ else if (itemInfo->readPermissions == 0)
+ str = i18n("No");
+ else
+ str = i18n("Yes");
+
+ d->labelFileIsReadable->setText(str);
+
+ if (itemInfo->writePermissions < 0)
+ str = unknown;
+ else if (itemInfo->writePermissions == 0)
+ str = i18n("No");
+ else
+ str = i18n("Yes");
+
+ d->labelFileIsWritable->setText(str);
+
+ TQDateTime date;
+ date.setTime_t(itemInfo->mtime);
+ d->labelFileDate->setText(TDEGlobal::locale()->formatDateTime(date, true, true));
+
+ str = i18n("%1 (%2)").arg(TDEIO::convertSize(itemInfo->size))
+ .arg(TDEGlobal::locale()->formatNumber(itemInfo->size, 0));
+ d->labelFileSize->setText(str);
+
+ // -- Image Properties --------------------------------------------------
+
+ d->labelImageMime->setText( (itemInfo->mime == TQString("image/x-raw")) ?
+ i18n("RAW Image") : KMimeType::mimeType(itemInfo->mime)->comment() );
+
+ TQString mpixels;
+ TQSize dims;
+ if (itemInfo->width == -1 && itemInfo->height == -1 && !currentURL.isEmpty())
+ {
+ // delayed loading to list faster from UMSCamera
+ if (itemInfo->mime == TQString("image/x-raw"))
+ {
+ DMetadata metaData(currentURL.path());
+ dims = metaData.getImageDimensions();
+ }
+ else
+ {
+ KFileMetaInfo meta(currentURL.path());
+ if (meta.isValid())
+ {
+ if (meta.containsGroup("Jpeg EXIF Data"))
+ dims = meta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
+ else if (meta.containsGroup("General"))
+ dims = meta.group("General").item("Dimensions").value().toSize();
+ else if (meta.containsGroup("Technical"))
+ dims = meta.group("Technical").item("Dimensions").value().toSize();
+ }
+ }
+ }
+ else
+ {
+ // if available (GPCamera), take dimensions directly from itemInfo
+ dims = TQSize(itemInfo->width, itemInfo->height);
+ }
+ mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2);
+ str = (!dims.isValid()) ? unknown : i18n("%1x%2 (%3Mpx)")
+ .arg(dims.width()).arg(dims.height()).arg(mpixels);
+ d->labelImageDimensions->setText(str);
+
+ // -- Download information ------------------------------------------
+
+ d->labelNewFileName->setText(newFileName.isEmpty() ? i18n("<i>unchanged</i>") : newFileName);
+
+ if (itemInfo->downloaded == GPItemInfo::DownloadUnknow)
+ str = unknown;
+ else if (itemInfo->downloaded == GPItemInfo::DownloadedYes)
+ str = i18n("Yes");
+ else
+ str = i18n("No");
+
+ d->labelAlreadyDownloaded->setText(str);
+
+ // -- Photograph information ------------------------------------------
+ // NOTA: If something is changed here, please updated albumfiletip section too.
+
+ TQString unavailable(i18n("<i>unavailable</i>"));
+ DMetadata metaData;
+ metaData.setExif(exifData);
+ PhotoInfoContainer photoInfo = metaData.getPhotographInformations();
+
+ if (photoInfo.isEmpty())
+ {
+ d->title2->hide();
+ d->make->hide();
+ d->model->hide();
+ d->photoDate->hide();
+ d->aperture->hide();
+ d->focalLength->hide();
+ d->exposureTime->hide();
+ d->sensitivity->hide();
+ d->exposureMode->hide();
+ d->flash->hide();
+ d->whiteBalance->hide();
+ d->labelPhotoMake->hide();
+ d->labelPhotoModel->hide();
+ d->labelPhotoDateTime->hide();
+ d->labelPhotoAperture->hide();
+ d->labelPhotoFocalLength->hide();
+ d->labelPhotoExposureTime->hide();
+ d->labelPhotoSensitivity->hide();
+ d->labelPhotoExposureMode->hide();
+ d->labelPhotoFlash->hide();
+ d->labelPhotoWhiteBalance->hide();
+ }
+ else
+ {
+ d->title2->show();
+ d->make->show();
+ d->model->show();
+ d->photoDate->show();
+ d->aperture->show();
+ d->focalLength->show();
+ d->exposureTime->show();
+ d->sensitivity->show();
+ d->exposureMode->show();
+ d->flash->show();
+ d->whiteBalance->show();
+ d->labelPhotoMake->show();
+ d->labelPhotoModel->show();
+ d->labelPhotoDateTime->show();
+ d->labelPhotoAperture->show();
+ d->labelPhotoFocalLength->show();
+ d->labelPhotoExposureTime->show();
+ d->labelPhotoSensitivity->show();
+ d->labelPhotoExposureMode->show();
+ d->labelPhotoFlash->show();
+ d->labelPhotoWhiteBalance->show();
+ }
+
+ d->labelPhotoMake->setText(photoInfo.make.isEmpty() ? unavailable : photoInfo.make);
+ d->labelPhotoModel->setText(photoInfo.model.isEmpty() ? unavailable : photoInfo.model);
+
+ if (photoInfo.dateTime.isValid())
+ {
+ str = TDEGlobal::locale()->formatDateTime(photoInfo.dateTime, true, true);
+ d->labelPhotoDateTime->setText(str);
+ }
+ else
+ d->labelPhotoDateTime->setText(unavailable);
+
+ d->labelPhotoAperture->setText(photoInfo.aperture.isEmpty() ? unavailable : photoInfo.aperture);
+
+ if (photoInfo.focalLength35mm.isEmpty())
+ d->labelPhotoFocalLength->setText(photoInfo.focalLength.isEmpty() ? unavailable : photoInfo.focalLength);
+ else
+ {
+ str = i18n("%1 (35mm: %2)").arg(photoInfo.focalLength).arg(photoInfo.focalLength35mm);
+ d->labelPhotoFocalLength->setText(str);
+ }
+
+ d->labelPhotoExposureTime->setText(photoInfo.exposureTime.isEmpty() ? unavailable : photoInfo.exposureTime);
+ d->labelPhotoSensitivity->setText(photoInfo.sensitivity.isEmpty() ? unavailable : i18n("%1 ISO").arg(photoInfo.sensitivity));
+
+ if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ d->labelPhotoExposureMode->setText(unavailable);
+ else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ d->labelPhotoExposureMode->setText(photoInfo.exposureMode);
+ else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty())
+ d->labelPhotoExposureMode->setText(photoInfo.exposureProgram);
+ else
+ {
+ str = TQString("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram);
+ d->labelPhotoExposureMode->setText(str);
+ }
+
+ d->labelPhotoFlash->setText(photoInfo.flash.isEmpty() ? unavailable : photoInfo.flash);
+ d->labelPhotoWhiteBalance->setText(photoInfo.whiteBalance.isEmpty() ? unavailable : photoInfo.whiteBalance);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/imageproperties/cameraitempropertiestab.h b/src/libs/imageproperties/cameraitempropertiestab.h
new file mode 100644
index 00000000..badf4a70
--- /dev/null
+++ b/src/libs/imageproperties/cameraitempropertiestab.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-08
+ * Description : A tab to display camera item information
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAITEMPROPERTIESTAB_H
+#define CAMERAITEMPROPERTIESTAB_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "navigatebartab.h"
+
+namespace Digikam
+{
+
+class GPItemInfo;
+class CameraItemPropertiesTabPriv;
+
+class DIGIKAM_EXPORT CameraItemPropertiesTab : public NavigateBarTab
+{
+ TQ_OBJECT
+
+
+public:
+
+ CameraItemPropertiesTab(TQWidget* parent, bool navBar=true);
+ ~CameraItemPropertiesTab();
+
+ void setCurrentItem(const GPItemInfo* itemInfo=0,
+ const TQString &newFileName=TQString(),
+ const TQByteArray& exifData=TQByteArray(),
+ const KURL &currentURL = KURL());
+
+private:
+
+ CameraItemPropertiesTabPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* CAMERAITEMPROPERTIESTAB_H */
diff --git a/src/libs/imageproperties/imagedescedittab.cpp b/src/libs/imageproperties/imagedescedittab.cpp
new file mode 100644
index 00000000..f79b1d7c
--- /dev/null
+++ b/src/libs/imageproperties/imagedescedittab.cpp
@@ -0,0 +1,1763 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-03-09
+ * Description : Captions, Tags, and Rating properties editor
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2009 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqhbox.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqtoolbutton.h>
+#include <tqpushbutton.h>
+#include <tqiconset.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqscrollview.h>
+
+// KDE includes.
+
+#include <tdeabc/stdaddressbook.h>
+#include <tdepopupmenu.h>
+#include <tdelocale.h>
+#include <kurl.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdemessagebox.h>
+#include <ktextedit.h>
+#include <tdeconfig.h>
+#include <klineedit.h>
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "kdatetimeedit.h"
+#include "albumiconitem.h"
+#include "albumdb.h"
+#include "album.h"
+#include "albumsettings.h"
+#include "albumlister.h"
+#include "albumthumbnailloader.h"
+#include "tageditdlg.h"
+#include "navigatebarwidget.h"
+#include "ratingwidget.h"
+#include "talbumlistview.h"
+#include "tagfilterview.h"
+#include "imageinfo.h"
+#include "imageattributeswatch.h"
+#include "metadatahub.h"
+#include "statusprogressbar.h"
+#include "searchtextbar.h"
+#include "imagedescedittab.h"
+#include "imagedescedittab.moc"
+
+namespace Digikam
+{
+
+class ImageDescEditTabPriv
+{
+
+public:
+
+ ImageDescEditTabPriv()
+ {
+ modified = false;
+ ignoreImageAttributesWatch = false;
+ recentTagsBtn = 0;
+ commentsEdit = 0;
+ tagsSearchBar = 0;
+ dateTimeEdit = 0;
+ tagsView = 0;
+ ratingWidget = 0;
+ ABCMenu = 0;
+ assignedTagsBtn = 0;
+ applyBtn = 0;
+ revertBtn = 0;
+ newTagEdit = 0;
+ toggleAutoTags = TagFilterView::NoToggleAuto;
+ }
+
+ bool modified;
+ bool ignoreImageAttributesWatch;
+
+ TQToolButton *recentTagsBtn;
+ TQToolButton *assignedTagsBtn;
+ TQToolButton *revertBtn;
+
+ TQPopupMenu *ABCMenu;
+
+ TQPushButton *applyBtn;
+
+ TQPushButton *moreButton;
+ TQPopupMenu *moreMenu;
+
+ KTextEdit *commentsEdit;
+
+ KDateTimeEdit *dateTimeEdit;
+
+ SearchTextBar *tagsSearchBar;
+ SearchTextBar *newTagEdit;
+
+ TQPtrList<ImageInfo> currInfos;
+
+ TAlbumListView *tagsView;
+
+ RatingWidget *ratingWidget;
+
+ TagFilterView::ToggleAutoTags toggleAutoTags;
+
+ MetadataHub hub;
+};
+
+ImageDescEditTab::ImageDescEditTab(TQWidget *parent, bool navBar)
+ : NavigateBarTab(parent)
+{
+ d = new ImageDescEditTabPriv;
+
+ setupNavigateBar(navBar);
+
+ TQScrollView *sv = new TQScrollView(this);
+ sv->viewport()->setBackgroundMode(TQt::PaletteBackground);
+ sv->setResizePolicy(TQScrollView::AutoOneFit);
+ sv->setFrameStyle(TQFrame::NoFrame);
+
+ TQWidget *settingsArea = new TQWidget(sv->viewport());
+ sv->addChild(settingsArea);
+ m_navigateBarLayout->addWidget(sv);
+
+ TQGridLayout *settingsLayout = new TQGridLayout(settingsArea, 6, 1,
+ KDialog::spacingHint(), KDialog::spacingHint());
+
+ // Captions/Date/Rating view -----------------------------------
+
+ TQVBox *commentsBox = new TQVBox(settingsArea);
+ new TQLabel(i18n("Caption:"), commentsBox);
+ d->commentsEdit = new KTextEdit(commentsBox);
+ d->commentsEdit->setTextFormat(TQTextEdit::PlainText);
+ d->commentsEdit->setCheckSpellingEnabled(true);
+ d->commentsEdit->setFixedHeight(100);
+
+ TQHBox *dateBox = new TQHBox(settingsArea);
+ new TQLabel(i18n("Date:"), dateBox);
+ d->dateTimeEdit = new KDateTimeEdit(dateBox, "datepicker");
+
+ TQHBox *ratingBox = new TQHBox(settingsArea);
+ new TQLabel(i18n("Rating:"), ratingBox);
+ d->ratingWidget = new RatingWidget(ratingBox);
+
+ // Tags view ---------------------------------------------------
+
+ d->newTagEdit = new SearchTextBar(settingsArea, "ImageDescEditTabNewTagEdit", i18n("Enter new tag here..."));
+ TQWhatsThis::add(d->newTagEdit, i18n("Enter here the text used to create new tags. "
+ "'/' can be used here to create a hierarchy of tags. "
+ "',' can be used here to create more than one hierarchy at the same time."));
+
+ d->tagsView = new TAlbumListView(settingsArea);
+
+ TQHBox *tagsSearch = new TQHBox(settingsArea);
+ tagsSearch->setSpacing(KDialog::spacingHint());
+
+ d->tagsSearchBar = new SearchTextBar(tagsSearch, "ImageDescEditTabTagsSearchBar");
+
+ d->assignedTagsBtn = new TQToolButton(tagsSearch);
+ TQToolTip::add(d->assignedTagsBtn, i18n("Tags already assigned"));
+ d->assignedTagsBtn->setIconSet(kapp->iconLoader()->loadIcon("tag-assigned",
+ TDEIcon::NoGroup, TDEIcon::SizeSmall,
+ TDEIcon::DefaultState, 0, true));
+ d->assignedTagsBtn->setToggleButton(true);
+
+ d->recentTagsBtn = new TQToolButton(tagsSearch);
+ TQPopupMenu *popupMenu = new TQPopupMenu(d->recentTagsBtn);
+ TQToolTip::add(d->recentTagsBtn, i18n("Recent Tags"));
+ d->recentTagsBtn->setIconSet(kapp->iconLoader()->loadIcon("tag-recents",
+ TDEIcon::NoGroup, TDEIcon::SizeSmall,
+ TDEIcon::DefaultState, 0, true));
+ d->recentTagsBtn->setUsesBigPixmap(false);
+ d->recentTagsBtn->setPopup(popupMenu);
+ d->recentTagsBtn->setPopupDelay(1);
+
+ // Buttons -----------------------------------------
+
+ TQHBox *buttonsBox = new TQHBox(settingsArea);
+ buttonsBox->setSpacing(KDialog::spacingHint());
+
+ d->revertBtn = new TQToolButton(buttonsBox);
+ d->revertBtn->setIconSet(SmallIcon("reload_page"));
+ TQToolTip::add(d->revertBtn, i18n("Revert all changes"));
+ d->revertBtn->setEnabled(false);
+
+ d->applyBtn = new TQPushButton(i18n("Apply"), buttonsBox);
+ d->applyBtn->setIconSet(SmallIcon("button_ok"));
+ d->applyBtn->setEnabled(false);
+ TQToolTip::add(d->applyBtn, i18n("Apply all changes to images"));
+ buttonsBox->setStretchFactor(d->applyBtn, 10);
+
+ d->moreButton = new TQPushButton(i18n("More"), buttonsBox);
+ d->moreMenu = new TQPopupMenu(this);
+ d->moreButton->setPopup(d->moreMenu);
+
+ // --------------------------------------------------
+
+ settingsLayout->addMultiCellWidget(commentsBox, 0, 0, 0, 1);
+ settingsLayout->addMultiCellWidget(dateBox, 1, 1, 0, 1);
+ settingsLayout->addMultiCellWidget(ratingBox, 2, 2, 0, 1);
+ settingsLayout->addMultiCellWidget(d->newTagEdit, 3, 3, 0, 1);
+ settingsLayout->addMultiCellWidget(d->tagsView, 4, 4, 0, 1);
+ settingsLayout->addMultiCellWidget(tagsSearch, 5, 5, 0, 1);
+ settingsLayout->addMultiCellWidget(buttonsBox, 6, 6, 0, 1);
+ settingsLayout->setRowStretch(4, 10);
+
+ // --------------------------------------------------
+
+ connect(d->tagsView, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ this, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)));
+
+ connect(d->tagsView, TQ_SIGNAL(signalProgressValue(int)),
+ this, TQ_SIGNAL(signalProgressValue(int)));
+
+ connect(popupMenu, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotRecentTagsMenuActivated(int)));
+
+ connect(d->tagsView, TQ_SIGNAL(signalItemStateChanged(TAlbumCheckListItem *)),
+ this, TQ_SLOT(slotItemStateChanged(TAlbumCheckListItem *)));
+
+ connect(d->commentsEdit, TQ_SIGNAL(textChanged()),
+ this, TQ_SLOT(slotCommentChanged()));
+
+ connect(d->dateTimeEdit, TQ_SIGNAL(dateTimeChanged(const TQDateTime& )),
+ this, TQ_SLOT(slotDateTimeChanged(const TQDateTime&)));
+
+ connect(d->ratingWidget, TQ_SIGNAL(signalRatingChanged(int)),
+ this, TQ_SLOT(slotRatingChanged(int)));
+
+ connect(d->tagsView, TQ_SIGNAL(rightButtonClicked(TQListViewItem*, const TQPoint &, int)),
+ this, TQ_SLOT(slotRightButtonClicked(TQListViewItem*, const TQPoint&, int)));
+
+ connect(d->tagsSearchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ this, TQ_SLOT(slotTagsSearchChanged(const TQString&)));
+
+ connect(this, TQ_SIGNAL(signalTagFilterMatch(bool)),
+ d->tagsSearchBar, TQ_SLOT(slotSearchResult(bool)));
+
+ connect(d->assignedTagsBtn, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotAssignedTagsToggled(bool)));
+
+ connect(d->newTagEdit->lineEdit(), TQ_SIGNAL(returnPressed(const TQString&)),
+ this, TQ_SLOT(slotCreateNewTag()));
+
+ connect(d->applyBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotApplyAllChanges()));
+
+ connect(d->revertBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRevertAllChanges()));
+
+ connect(d->moreMenu, TQ_SIGNAL(aboutToShow()),
+ this, TQ_SLOT(slotMoreMenu()));
+
+ // Initialize ---------------------------------------------
+
+ d->commentsEdit->installEventFilter(this);
+ d->dateTimeEdit->installEventFilter(this);
+ d->ratingWidget->installEventFilter(this);
+ d->tagsView->installEventFilter(this);
+ updateRecentTags();
+
+ // Connect to album manager -----------------------------
+
+ AlbumManager* man = AlbumManager::instance();
+
+ connect(man, TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(man, TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(man, TQ_SIGNAL(signalAlbumRenamed(Album*)),
+ this, TQ_SLOT(slotAlbumRenamed(Album*)));
+
+ connect(man, TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(slotAlbumsCleared()));
+
+ connect(man, TQ_SIGNAL(signalAlbumIconChanged(Album*)),
+ this, TQ_SLOT(slotAlbumIconChanged(Album*)));
+
+ connect(man, TQ_SIGNAL(signalTAlbumMoved(TAlbum*, TAlbum*)),
+ this, TQ_SLOT(slotAlbumMoved(TAlbum*, TAlbum*)));
+
+ // Connect to thumbnail loader -----------------------------
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+
+ connect(loader, TQ_SIGNAL(signalThumbnail(Album *, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnailFromIcon(Album *, const TQPixmap&)));
+
+ connect(loader, TQ_SIGNAL(signalFailed(Album *)),
+ this, TQ_SLOT(slotThumbnailLost(Album *)));
+
+ connect(loader, TQ_SIGNAL(signalReloadThumbnails()),
+ this, TQ_SLOT(slotReloadThumbnails()));
+
+ // Connect to attribute watch ------------------------------
+
+ ImageAttributesWatch *watch = ImageAttributesWatch::instance();
+
+ connect(watch, TQ_SIGNAL(signalImageTagsChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageTagsChanged(TQ_LLONG)));
+
+ connect(watch, TQ_SIGNAL(signalImagesChanged(int)),
+ this, TQ_SLOT(slotImagesChanged(int)));
+
+ connect(watch, TQ_SIGNAL(signalImageRatingChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageRatingChanged(TQ_LLONG)));
+
+ connect(watch, TQ_SIGNAL(signalImageDateChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageDateChanged(TQ_LLONG)));
+
+ connect(watch, TQ_SIGNAL(signalImageCaptionChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageCaptionChanged(TQ_LLONG)));
+
+ // -- read config ---------------------------------------------------------
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("Tag List View");
+ d->toggleAutoTags = (TagFilterView::ToggleAutoTags)(config->readNumEntry("Toggle Auto Tags",
+ TagFilterView::NoToggleAuto));
+}
+
+ImageDescEditTab::~ImageDescEditTab()
+{
+ slotChangingItems();
+
+ /*
+ AlbumList tList = AlbumManager::instance()->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ (*it)->removeExtraData(this);
+ }
+ */
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("Tag List View");
+ config->writeEntry("Toggle Auto Tags", (int)(d->toggleAutoTags));
+ config->sync();
+
+ delete d;
+}
+
+bool ImageDescEditTab::singleSelection() const
+{
+ return (d->currInfos.count() == 1);
+}
+
+void ImageDescEditTab::slotChangingItems()
+{
+ if (!d->modified)
+ return;
+
+ if (d->currInfos.isEmpty())
+ return;
+
+ if (!AlbumSettings::instance()->getApplySidebarChangesDirectly())
+ {
+ KDialogBase *dialog = new KDialogBase(i18n("Apply changes?"),
+ KDialogBase::Yes | KDialogBase::No,
+ KDialogBase::Yes, KDialogBase::No,
+ this, "applyChanges",
+ true, true,
+ KStdGuiItem::yes(), KStdGuiItem::discard());
+
+ int changedFields = 0;
+ if (d->hub.commentChanged())
+ changedFields++;
+ if (d->hub.dateTimeChanged())
+ changedFields++;
+ if (d->hub.ratingChanged())
+ changedFields++;
+ if (d->hub.tagsChanged())
+ changedFields++;
+
+ TQString text;
+ if (changedFields == 1)
+ {
+ if (d->hub.commentChanged())
+ text = i18n("<qt><p>You have edited the comment of the image. ",
+ "<qt><p>You have edited the comment of %n images. ",
+ d->currInfos.count());
+ else if (d->hub.dateTimeChanged())
+ text = i18n("<qt><p>You have edited the date of the image. ",
+ "<qt><p>You have edited the date of %n images. ",
+ d->currInfos.count());
+ else if (d->hub.ratingChanged())
+ text = i18n("<qt><p>You have edited the rating of the image. ",
+ "<qt><p>You have edited the rating of %n images. ",
+ d->currInfos.count());
+ else if (d->hub.tagsChanged())
+ text = i18n("<qt><p>You have edited the tags of the image. ",
+ "<qt><p>You have edited the tags of %n images. ",
+ d->currInfos.count());
+
+ text += i18n("Do you want to apply your changes?</p></qt>");
+ }
+ else
+ {
+ text = i18n("<qt><p>You have edited the metadata of the image: </p><ul>",
+ "<qt><p>You have edited the metadata of %n images: </p><ul>",
+ d->currInfos.count());
+
+ if (d->hub.commentChanged())
+ text += i18n("<li>comment</li>");
+ if (d->hub.dateTimeChanged())
+ text += i18n("<li>date</li>");
+ if (d->hub.ratingChanged())
+ text += i18n("<li>rating</li>");
+ if (d->hub.tagsChanged())
+ text += i18n("<li>tags</li>");
+
+ text += "</ul><p>";
+
+ text += i18n("Do you want to apply your changes?</p></qt>");
+ }
+
+ bool alwaysApply = false;
+ int returnCode = KMessageBox::createKMessageBox
+ (dialog, TQMessageBox::Information,
+ text, TQStringList(),
+ i18n("Always apply changes without confirmation"),
+ &alwaysApply, KMessageBox::Notify);
+
+ if (alwaysApply)
+ AlbumSettings::instance()->setApplySidebarChangesDirectly(true);
+
+ if (returnCode == KDialogBase::User1)
+ return;
+ // otherwise apply
+ }
+
+ slotApplyAllChanges();
+}
+
+void ImageDescEditTab::slotApplyAllChanges()
+{
+ if (!d->modified)
+ return;
+
+ if (d->currInfos.isEmpty())
+ return;
+
+ bool progressInfo = (d->currInfos.count() > 1);
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Applying changes to images. Please wait..."));
+ MetadataWriteSettings writeSettings = MetadataHub::defaultWriteSettings();
+
+ // debugging - use this to indicate reentry from event loop (kapp->processEvents)
+ // remove before final release
+ if (d->ignoreImageAttributesWatch)
+ {
+ DWarning() << "ImageDescEditTab::slotApplyAllChanges(): re-entering from event loop!" << endl;
+ }
+
+ // we are now changing attributes ourselves
+ d->ignoreImageAttributesWatch = true;
+ AlbumLister::instance()->blockSignals(true);
+ AlbumManager::instance()->albumDB()->beginTransaction();
+ int i=0;
+ for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next())
+ {
+ // apply to database
+ d->hub.write(info);
+ // apply to file metadata
+ d->hub.write(info->filePath(), MetadataHub::FullWrite, writeSettings);
+
+ emit signalProgressValue((int)((i++/(float)d->currInfos.count())*100.0));
+ if (progressInfo)
+ kapp->processEvents();
+ }
+ AlbumLister::instance()->blockSignals(false);
+ AlbumManager::instance()->albumDB()->commitTransaction();
+
+ d->ignoreImageAttributesWatch = false;
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+
+ d->modified = false;
+ d->hub.resetChanged();
+ d->applyBtn->setEnabled(false);
+ d->revertBtn->setEnabled(false);
+
+ updateRecentTags();
+ updateTagsView();
+}
+
+void ImageDescEditTab::slotRevertAllChanges()
+{
+ if (!d->modified)
+ return;
+
+ if (d->currInfos.isEmpty())
+ return;
+
+ setInfos(d->currInfos);
+}
+
+void ImageDescEditTab::setItem(ImageInfo *info)
+{
+ slotChangingItems();
+ TQPtrList<ImageInfo> list;
+ if (info)
+ list.append(info);
+ setInfos(list);
+}
+
+void ImageDescEditTab::setItems(TQPtrList<ImageInfo> infos)
+{
+ slotChangingItems();
+ setInfos(infos);
+}
+
+void ImageDescEditTab::setInfos(TQPtrList<ImageInfo> infos)
+{
+ if (infos.isEmpty())
+ {
+ d->hub = MetadataHub();
+ d->commentsEdit->blockSignals(true);
+ d->commentsEdit->clear();
+ d->commentsEdit->blockSignals(false);
+ d->currInfos.clear();
+ setEnabled(false);
+ return;
+ }
+
+ setEnabled(true);
+ d->currInfos = infos;
+ d->modified = false;
+ d->hub = MetadataHub();
+ d->applyBtn->setEnabled(false);
+ d->revertBtn->setEnabled(false);
+
+ for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next())
+ {
+ d->hub.load(info);
+ }
+
+ updateComments();
+ updateRating();
+ updateDate();
+ updateTagsView();
+}
+
+void ImageDescEditTab::slotReadFromFileMetadataToDatabase()
+{
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Reading metadata from files. Please wait..."));
+
+ d->ignoreImageAttributesWatch = true;
+ AlbumManager::instance()->albumDB()->beginTransaction();
+ int i=0;
+ for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next())
+ {
+ // A batch operation: a hub for each single file, not the common hub
+ MetadataHub fileHub(MetadataHub::NewTagsImport);
+ // read in from DMetadata
+ fileHub.load(info->filePath());
+ // write out to database
+ fileHub.write(info);
+
+ emit signalProgressValue((int)((i++/(float)d->currInfos.count())*100.0));
+ kapp->processEvents();
+ }
+ AlbumManager::instance()->albumDB()->commitTransaction();
+ d->ignoreImageAttributesWatch = false;
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+
+ // reload everything
+ setInfos(d->currInfos);
+}
+
+void ImageDescEditTab::slotWriteToFileMetadataFromDatabase()
+{
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Writing metadata to files. Please wait..."));
+ MetadataWriteSettings writeSettings = MetadataHub::defaultWriteSettings();
+
+ int i=0;
+ for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next())
+ {
+ MetadataHub fileHub;
+ // read in from database
+ fileHub.load(info);
+ // write out to file DMetadata
+ fileHub.write(info->filePath());
+
+ emit signalProgressValue((int)((i++/(float)d->currInfos.count())*100.0));
+ kapp->processEvents();
+ }
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+}
+
+bool ImageDescEditTab::eventFilter(TQObject *, TQEvent *e)
+{
+ if ( e->type() == TQEvent::KeyPress )
+ {
+ TQKeyEvent *k = (TQKeyEvent *)e;
+ if (k->state() == TQt::ControlButton &&
+ (k->key() == TQt::Key_Enter || k->key() == TQt::Key_Return))
+ {
+ emit signalNextItem();
+ return true;
+ }
+ else if (k->state() == TQt::ShiftButton &&
+ (k->key() == TQt::Key_Enter || k->key() == TQt::Key_Return))
+ {
+ emit signalPrevItem();
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+void ImageDescEditTab::populateTags()
+{
+ d->tagsView->clear();
+
+ AlbumList tList = AlbumManager::instance()->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum *tag = (TAlbum*)(*it);
+ slotAlbumAdded(tag);
+ }
+
+ d->tagsView->loadViewState();
+}
+
+void ImageDescEditTab::slotItemStateChanged(TAlbumCheckListItem *item)
+{
+ TagFilterView::ToggleAutoTags oldAutoTags = d->toggleAutoTags;
+
+ switch(d->toggleAutoTags)
+ {
+ case TagFilterView::Children:
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(item->album(), item->isOn());
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ case TagFilterView::Parents:
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleParentTags(item->album(), item->isOn());
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ case TagFilterView::ChildrenAndParents:
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(item->album(), item->isOn());
+ toggleParentTags(item->album(), item->isOn());
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ default:
+ break;
+ }
+
+ d->hub.setTag(item->album(), item->isOn());
+
+ d->tagsView->blockSignals(true);
+ item->setStatus(d->hub.tagStatus(item->album()));
+ d->tagsView->blockSignals(false);
+
+ slotModified();
+}
+
+void ImageDescEditTab::slotCommentChanged()
+{
+ // we cannot trust that the text actually changed
+ // (there are bogus signals caused by spell checking, see bug 141663)
+ // so we have to check before marking the metadata as modified
+ if (d->hub.comment() == d->commentsEdit->text())
+ return;
+
+ d->hub.setComment(d->commentsEdit->text());
+ setMetadataWidgetStatus(d->hub.commentStatus(), d->commentsEdit);
+ slotModified();
+}
+
+void ImageDescEditTab::slotDateTimeChanged(const TQDateTime& dateTime)
+{
+ d->hub.setDateTime(dateTime);
+ setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit);
+ slotModified();
+}
+
+void ImageDescEditTab::slotRatingChanged(int rating)
+{
+ d->hub.setRating(rating);
+ // no handling for MetadataDisjoint needed for rating,
+ // we set it to 0 when disjoint, see below
+ slotModified();
+}
+
+void ImageDescEditTab::slotModified()
+{
+ d->modified = true;
+ d->applyBtn->setEnabled(true);
+ d->revertBtn->setEnabled(true);
+}
+
+void ImageDescEditTab::assignRating(int rating)
+{
+ d->ratingWidget->setRating(rating);
+}
+
+void ImageDescEditTab::updateTagsView()
+{
+ d->tagsView->blockSignals(true);
+
+ TQListViewItemIterator it( d->tagsView);
+ while (it.current())
+ {
+ TAlbumCheckListItem* tItem = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ if (tItem)
+ tItem->setStatus(d->hub.tagStatus(tItem->album()));
+ ++it;
+ }
+
+ // The condition is a temporary fix not to destroy name filtering on image change.
+ // See comments in these methods.
+ if (d->assignedTagsBtn->isOn())
+ slotAssignedTagsToggled(d->assignedTagsBtn->isOn());
+
+ d->tagsView->blockSignals(false);
+}
+
+void ImageDescEditTab::updateComments()
+{
+ d->commentsEdit->blockSignals(true);
+ d->commentsEdit->setText(d->hub.comment());
+ setMetadataWidgetStatus(d->hub.commentStatus(), d->commentsEdit);
+ d->commentsEdit->blockSignals(false);
+}
+
+void ImageDescEditTab::updateRating()
+{
+ d->ratingWidget->blockSignals(true);
+ if (d->hub.ratingStatus() == MetadataHub::MetadataDisjoint)
+ d->ratingWidget->setRating(0);
+ else
+ d->ratingWidget->setRating(d->hub.rating());
+ d->ratingWidget->blockSignals(false);
+}
+
+void ImageDescEditTab::updateDate()
+{
+ d->dateTimeEdit->blockSignals(true);
+ d->dateTimeEdit->setDateTime(d->hub.dateTime());
+ setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit);
+ d->dateTimeEdit->blockSignals(false);
+}
+
+void ImageDescEditTab::setMetadataWidgetStatus(int status, TQWidget *widget)
+{
+ if (status == MetadataHub::MetadataDisjoint)
+ {
+ // For text widgets: Set text color to color of disabled text
+ TQPalette palette = widget->palette();
+ palette.setColor(TQColorGroup::Text, palette.color(TQPalette::Disabled, TQColorGroup::Text));
+ widget->setPalette(palette);
+ }
+ else
+ {
+ widget->unsetPalette();
+ }
+}
+
+void ImageDescEditTab::slotRightButtonClicked(TQListViewItem *item, const TQPoint &, int )
+{
+ TAlbum *album;
+
+ if (!item)
+ {
+ album = AlbumManager::instance()->findTAlbum(0);
+ }
+ else
+ {
+ TAlbumCheckListItem* viewItem = dynamic_cast<TAlbumCheckListItem*>(item);
+
+ if(!viewItem)
+ album = AlbumManager::instance()->findTAlbum(0);
+ else
+ album = viewItem->album();
+ }
+
+ if(!album)
+ return;
+
+ d->ABCMenu = new TQPopupMenu;
+
+ connect(d->ABCMenu, TQ_SIGNAL( aboutToShow() ),
+ this, TQ_SLOT( slotABCContextMenu() ));
+
+ TDEPopupMenu popmenu(this);
+ popmenu.insertTitle(SmallIcon("digikam"), i18n("Tags"));
+ popmenu.insertItem(SmallIcon("tag-new"), i18n("New Tag..."), 10);
+ popmenu.insertItem(SmallIcon("tag-addressbook"), i18n("Create Tag From AddressBook"), d->ABCMenu);
+
+ if (!album->isRoot())
+ {
+ popmenu.insertItem(SmallIcon("tag-properties"), i18n("Edit Tag Properties..."), 11);
+ popmenu.insertItem(SmallIcon("tag-reset"), i18n("Reset Tag Icon"), 13);
+ popmenu.insertSeparator(-1);
+ popmenu.insertItem(SmallIcon("tag-delete"), i18n("Delete Tag"), 12);
+ }
+
+ popmenu.insertSeparator(-1);
+
+ TQPopupMenu selectTagsMenu;
+ selectTagsMenu.insertItem(i18n("All Tags"), 14);
+ if (!album->isRoot())
+ {
+ selectTagsMenu.insertSeparator(-1);
+ selectTagsMenu.insertItem(i18n("Children"), 17);
+ selectTagsMenu.insertItem(i18n("Parents"), 19);
+ }
+ popmenu.insertItem(i18n("Select"), &selectTagsMenu);
+
+ TQPopupMenu deselectTagsMenu;
+ deselectTagsMenu.insertItem(i18n("All Tags"), 15);
+ if (!album->isRoot())
+ {
+ deselectTagsMenu.insertSeparator(-1);
+ deselectTagsMenu.insertItem(i18n("Children"), 18);
+ deselectTagsMenu.insertItem(i18n("Parents"), 20);
+ }
+ popmenu.insertItem(i18n("Deselect"), &deselectTagsMenu);
+
+ popmenu.insertItem(i18n("Invert Selection"), 16);
+ popmenu.insertSeparator(-1);
+
+ TQPopupMenu toggleAutoMenu;
+ toggleAutoMenu.setCheckable(true);
+ toggleAutoMenu.insertItem(i18n("None"), 21);
+ toggleAutoMenu.insertSeparator(-1);
+ toggleAutoMenu.insertItem(i18n("Children"), 22);
+ toggleAutoMenu.insertItem(i18n("Parents"), 23);
+ toggleAutoMenu.insertItem(i18n("Both"), 24);
+ toggleAutoMenu.setItemChecked(21 + d->toggleAutoTags, true);
+ popmenu.insertItem(i18n("Toggle Auto"), &toggleAutoMenu);
+
+ TagFilterView::ToggleAutoTags oldAutoTags = d->toggleAutoTags;
+
+ int choice = popmenu.exec((TQCursor::pos()));
+ switch( choice )
+ {
+ case 10: // New Tag.
+ {
+ tagNew(album);
+ break;
+ }
+ case 11: // Edit Tag Properties.
+ {
+ if (!album->isRoot())
+ tagEdit(album);
+ break;
+ }
+ case 12: // Delete Tag.
+ {
+ if (!album->isRoot())
+ tagDelete(album);
+ break;
+ }
+ case 13: // Reset Tag Icon.
+ {
+ TQString errMsg;
+ AlbumManager::instance()->updateTAlbumIcon(album, TQString("tag"), 0, errMsg);
+ break;
+ }
+ case 14: // Select All Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ TQListViewItemIterator it(d->tagsView, TQListViewItemIterator::NotChecked);
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ if (item->isVisible())
+ item->setOn(true);
+ ++it;
+ }
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 15: // Deselect All Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ TQListViewItemIterator it(d->tagsView, TQListViewItemIterator::Checked);
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ if (item->isVisible())
+ item->setOn(false);
+ ++it;
+ }
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 16: // Invert All Tags Selection.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ TQListViewItemIterator it(d->tagsView);
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ if (item->isVisible())
+ item->setOn(!item->isOn());
+ ++it;
+ }
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 17: // Select Child Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(album, true);
+ TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+ item->setOn(true);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 18: // Deselect Child Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleChildTags(album, false);
+ TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+ item->setOn(false);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 19: // Select Parent Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleParentTags(album, true);
+ TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+ item->setOn(true);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 20: // Deselect Parent Tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ toggleParentTags(album, false);
+ TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+ item->setOn(false);
+ d->toggleAutoTags = oldAutoTags;
+ break;
+ }
+ case 21: // No toggle auto tags.
+ {
+ d->toggleAutoTags = TagFilterView::NoToggleAuto;
+ break;
+ }
+ case 22: // Toggle auto Children tags.
+ {
+ d->toggleAutoTags = TagFilterView::Children;
+ break;
+ }
+ case 23: // Toggle auto Parents tags.
+ {
+ d->toggleAutoTags = TagFilterView::Parents;
+ break;
+ }
+ case 24: // Toggle auto Children and Parents tags.
+ {
+ d->toggleAutoTags = TagFilterView::ChildrenAndParents;
+ break;
+ }
+ default:
+ break;
+ }
+
+ if ( choice > 100 )
+ {
+ tagNew(album, d->ABCMenu->text( choice ), "tag-people" );
+ }
+
+ delete d->ABCMenu;
+ d->ABCMenu = 0;
+}
+
+void ImageDescEditTab::slotABCContextMenu()
+{
+ d->ABCMenu->clear();
+
+ int counter = 100;
+ TDEABC::AddressBook* ab = TDEABC::StdAddressBook::self();
+ TQStringList names;
+ for ( TDEABC::AddressBook::Iterator it = ab->begin(); it != ab->end(); ++it )
+ {
+ names.push_back(it->formattedName());
+ }
+
+ qHeapSort(names);
+
+ for ( TQStringList::Iterator it = names.begin(); it != names.end(); ++it )
+ {
+ TQString name = *it;
+ if ( !name.isNull() )
+ d->ABCMenu->insertItem( name, ++counter );
+ }
+
+ if (counter == 100)
+ {
+ d->ABCMenu->insertItem( i18n("No AddressBook Entries Found"), ++counter );
+ d->ABCMenu->setItemEnabled( counter, false );
+ }
+}
+
+void ImageDescEditTab::slotMoreMenu()
+{
+ d->moreMenu->clear();
+
+ if (singleSelection())
+ {
+ d->moreMenu->insertItem(i18n("Read metadata from file to database"), this, TQ_SLOT(slotReadFromFileMetadataToDatabase()));
+ int writeActionId = d->moreMenu->insertItem(i18n("Write metadata to each file"), this, TQ_SLOT(slotWriteToFileMetadataFromDatabase()));
+ // we do not need a "Write to file" action here because the apply button will do just that
+ // if selection is a single file.
+ // Adding the option will confuse users: Does the apply button not write to file?
+ // Removing the option will confuse users: There is not option to write to file! (not visible in single selection)
+ // Disabling will confuse users: Why is it disabled?
+ d->moreMenu->setItemEnabled(writeActionId, false);
+ }
+ else
+ {
+ // We need to make clear that this action is different from the Apply button,
+ // which saves the same changes to all files. These batch operations operate on each single file.
+ d->moreMenu->insertItem(i18n("Read metadata from each file to database"), this, TQ_SLOT(slotReadFromFileMetadataToDatabase()));
+ d->moreMenu->insertItem(i18n("Write metadata to each file"), this, TQ_SLOT(slotWriteToFileMetadataFromDatabase()));
+ }
+}
+
+void ImageDescEditTab::tagNew(TAlbum* parAlbum, const TQString& _title, const TQString& _icon) const
+{
+ if (!parAlbum)
+ return;
+
+ TQString title = _title;
+ TQString icon = _icon;
+
+ if (title.isNull())
+ {
+ if (!TagEditDlg::tagCreate(kapp->activeWindow(), parAlbum, title, icon))
+ return;
+ }
+
+ TQMap<TQString, TQString> errMap;
+ AlbumList tList = TagEditDlg::createTAlbum(parAlbum, title, icon, errMap);
+ TagEditDlg::showtagsListCreationError(kapp->activeWindow(), errMap);
+
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbumCheckListItem* item = (TAlbumCheckListItem*)(*it)->extraData(d->tagsView);
+ if (item)
+ {
+ item->setOn(true);
+ d->tagsView->setSelected(item, true);
+ d->tagsView->ensureItemVisible(item);
+ }
+ }
+}
+
+void ImageDescEditTab::tagDelete(TAlbum *album)
+{
+ if (!album || album->isRoot())
+ return;
+
+ AlbumManager *albumMan = AlbumManager::instance();
+
+ if (album == albumMan->currentAlbum() ||
+ album->isAncestorOf(albumMan->currentAlbum()))
+ {
+ KMessageBox::error(this, i18n("You are currently viewing items in the "
+ "tag '%1' that you are about to delete. "
+ "You will need to apply change first "
+ "if you want to delete the tag." )
+ .arg(album->title()));
+ return;
+ }
+
+ // find number of subtags
+ int children = 0;
+ AlbumIterator iter(album);
+ while(iter.current())
+ {
+ children++;
+ ++iter;
+ }
+
+ if(children)
+ {
+ int result = KMessageBox::warningContinueCancel(this,
+ i18n("Tag '%1' has one subtag. "
+ "Deleting this will also delete "
+ "the subtag. "
+ "Do you want to continue?",
+ "Tag '%1' has %n subtags. "
+ "Deleting this will also delete "
+ "the subtags. "
+ "Do you want to continue?",
+ children).arg(album->title()));
+
+ if(result != KMessageBox::Continue)
+ return;
+ }
+
+ TQString message;
+ LLongList assignedItems = albumMan->albumDB()->getItemIDsInTag(album->id());
+ if (!assignedItems.isEmpty())
+ {
+ message = i18n("Tag '%1' is assigned to one item. "
+ "Do you want to continue?",
+ "Tag '%1' is assigned to %n items. "
+ "Do you want to continue?",
+ assignedItems.count()).arg(album->title());
+ }
+ else
+ {
+ message = i18n("Delete '%1' tag?").arg(album->title());
+ }
+
+ int result = KMessageBox::warningContinueCancel(this, message,
+ i18n("Delete Tag"),
+ KGuiItem(i18n("Delete"),
+ "edit-delete"));
+
+ if (result == KMessageBox::Continue)
+ {
+ TQString errMsg;
+ if (!albumMan->deleteTAlbum(album, errMsg))
+ KMessageBox::error(this, errMsg);
+ }
+}
+
+void ImageDescEditTab::tagEdit(TAlbum* album)
+{
+ if (!album || album->isRoot())
+ return;
+
+ TQString title;
+ TQString icon;
+
+ if (!TagEditDlg::tagEdit(kapp->activeWindow(), album, title, icon))
+ return;
+
+ AlbumManager *albumMan = AlbumManager::instance();
+ if (album->title() != title)
+ {
+ TQString errMsg;
+ if (!albumMan->renameTAlbum(album, title, errMsg))
+ {
+ KMessageBox::error(this, errMsg);
+ return;
+ }
+ }
+
+ if (album->icon() != icon)
+ {
+ TQString errMsg;
+ if (!albumMan->updateTAlbumIcon(album, icon, 0, errMsg))
+ {
+ KMessageBox::error(this, errMsg);
+ }
+ }
+}
+
+void ImageDescEditTab::slotAlbumAdded(Album* a)
+{
+ if (!a || a->type() != Album::TAG)
+ return;
+
+ TAlbumCheckListItem* viewItem = 0;
+
+ TAlbum* tag = dynamic_cast<TAlbum*>(a);
+ if (!tag)
+ return;
+
+ if (tag->isRoot())
+ {
+ viewItem = new TAlbumCheckListItem(d->tagsView, tag);
+ }
+ else
+ {
+ TAlbumCheckListItem* parent = (TAlbumCheckListItem*)(tag->parent()->extraData(d->tagsView));
+ if (!parent)
+ {
+ DWarning() << k_funcinfo << "Failed to find parent for Tag " << tag->title()
+ << endl;
+ return;
+ }
+
+ viewItem = new TAlbumCheckListItem(parent, tag);
+ d->tagsSearchBar->lineEdit()->completionObject()->addItem(tag->title());
+ d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath());
+ d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath().remove(0, 1)); // without root "/"
+ }
+
+ if (viewItem)
+ {
+ // commenting this out due to the issues described in bug 148166.
+ // viewItem->setOpen(true);
+ setTagThumbnail(tag);
+ }
+}
+
+void ImageDescEditTab::slotAlbumDeleted(Album* a)
+{
+ if (!a || a->isRoot() || a->type() != Album::TAG)
+ return;
+
+ TAlbum* album = (TAlbum*)a;
+
+ d->tagsSearchBar->lineEdit()->completionObject()->removeItem(album->title());
+ d->newTagEdit->lineEdit()->completionObject()->removeItem(album->tagPath());
+ d->newTagEdit->lineEdit()->completionObject()->removeItem(album->tagPath().remove(0, 1)); // without root "/"
+ TAlbumCheckListItem* viewItem = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+ delete viewItem;
+ album->removeExtraData(this);
+ d->hub.setTag(album, false, MetadataHub::MetadataDisjoint);
+}
+
+void ImageDescEditTab::slotAlbumsCleared()
+{
+ d->tagsView->clear();
+ d->tagsSearchBar->lineEdit()->completionObject()->clear();
+ d->newTagEdit->lineEdit()->completionObject()->clear();
+}
+
+void ImageDescEditTab::slotAlbumIconChanged(Album* a)
+{
+ if (!a || a->isRoot() || a->type() != Album::TAG)
+ return;
+
+ setTagThumbnail((TAlbum *)a);
+}
+
+void ImageDescEditTab::slotAlbumMoved(TAlbum* tag, TAlbum* newParent)
+{
+ if (!tag || !newParent)
+ return;
+
+ TAlbumCheckListItem* item = (TAlbumCheckListItem*)tag->extraData(d->tagsView);
+ if (!item)
+ return;
+
+ if (item->parent())
+ {
+ TQListViewItem* oldPItem = item->parent();
+ oldPItem->takeItem(item);
+ }
+ else
+ {
+ d->tagsView->takeItem(item);
+ }
+
+ TAlbumCheckListItem* newPItem = (TAlbumCheckListItem*)newParent->extraData(d->tagsView);
+ if (newPItem)
+ newPItem->insertItem(item);
+ else
+ d->tagsView->insertItem(item);
+}
+
+void ImageDescEditTab::slotAlbumRenamed(Album* album)
+{
+ if (!album || album->isRoot() || album->type() != Album::TAG)
+ return;
+
+ TAlbum* tag = (TAlbum*)album;
+ d->tagsSearchBar->lineEdit()->completionObject()->addItem(tag->title());
+ d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath());
+ d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath().remove(0, 1)); // without root "/"
+ slotTagsSearchChanged(d->tagsSearchBar->lineEdit()->text());
+ TAlbumCheckListItem* item = (TAlbumCheckListItem*)(tag->extraData(d->tagsView));
+ if (item)
+ item->refresh();
+}
+
+void ImageDescEditTab::toggleChildTags(TAlbum *album, bool b)
+{
+ if (!album)
+ return;
+
+ AlbumIterator it(album);
+ while ( it.current() )
+ {
+ TAlbum *ta = (TAlbum*)it.current();
+ TAlbumCheckListItem *item = (TAlbumCheckListItem*)(ta->extraData(d->tagsView));
+ if (item)
+ if (item->isVisible())
+ item->setOn(b);
+ ++it;
+ }
+}
+
+void ImageDescEditTab::toggleParentTags(TAlbum *album, bool b)
+{
+ if (!album)
+ return;
+
+ TQListViewItemIterator it(d->tagsView);
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ if (item->isVisible())
+ {
+ if (!item->album())
+ continue;
+ if (item->album() == album->parent())
+ {
+ item->setOn(b);
+ toggleParentTags(item->album() , b);
+ }
+ }
+ ++it;
+ }
+}
+
+void ImageDescEditTab::setTagThumbnail(TAlbum *album)
+{
+ if(!album)
+ return;
+
+ TAlbumCheckListItem* item = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+
+ if(!item)
+ return;
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ TQPixmap icon;
+ if (!loader->getTagThumbnail(album, icon))
+ {
+ if (icon.isNull())
+ {
+ item->setPixmap(0, loader->getStandardTagIcon(album));
+ }
+ else
+ {
+ TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), icon);
+ item->setPixmap(0, blendedIcon);
+ }
+ }
+}
+
+void ImageDescEditTab::slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail)
+{
+ if(!album || album->type() != Album::TAG)
+ return;
+
+ // update item in tags tree
+ TAlbumCheckListItem* item = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+ if(!item)
+ return;
+
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), thumbnail);
+ item->setPixmap(0, blendedIcon);
+
+ // update item in recent tags popup menu, if found there in
+ TQPopupMenu *menu = d->recentTagsBtn->popup();
+ if (menu->indexOf(album->id()) != -1)
+ {
+ menu->changeItem(album->id(), thumbnail, menu->text(album->id()));
+ }
+}
+
+void ImageDescEditTab::slotThumbnailLost(Album *)
+{
+ // we already set the standard icon before loading
+}
+
+void ImageDescEditTab::slotReloadThumbnails()
+{
+ AlbumList tList = AlbumManager::instance()->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum* tag = (TAlbum*)(*it);
+ setTagThumbnail(tag);
+ }
+}
+
+void ImageDescEditTab::slotImageTagsChanged(TQ_LLONG imageId)
+{
+ // don't lose modifications
+ if (d->ignoreImageAttributesWatch || d->modified)
+ return;
+
+ reloadForMetadataChange(imageId);
+}
+
+void ImageDescEditTab::slotImagesChanged(int albumId)
+{
+ if (d->ignoreImageAttributesWatch || d->modified)
+ return;
+
+ Album *a = AlbumManager::instance()->findAlbum(albumId);
+ if (d->currInfos.isEmpty() || !a || a->isRoot() || a->type() != Album::TAG)
+ return;
+
+ setInfos(d->currInfos);
+}
+
+void ImageDescEditTab::slotImageRatingChanged(TQ_LLONG imageId)
+{
+ if (d->ignoreImageAttributesWatch || d->modified)
+ return;
+
+ reloadForMetadataChange(imageId);
+}
+
+void ImageDescEditTab::slotImageCaptionChanged(TQ_LLONG imageId)
+{
+ if (d->ignoreImageAttributesWatch || d->modified)
+ return;
+
+ reloadForMetadataChange(imageId);
+}
+
+void ImageDescEditTab::slotImageDateChanged(TQ_LLONG imageId)
+{
+ if (d->ignoreImageAttributesWatch || d->modified)
+ return;
+
+ reloadForMetadataChange(imageId);
+}
+
+// private common code for above methods
+void ImageDescEditTab::reloadForMetadataChange(TQ_LLONG imageId)
+{
+ if (d->currInfos.isEmpty())
+ return;
+
+ if (singleSelection())
+ {
+ if (d->currInfos.first()->id() == imageId)
+ setInfos(d->currInfos);
+ }
+ else
+ {
+ // if image id is in our list, update
+ for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next())
+ {
+ if (info->id() == imageId)
+ {
+ setInfos(d->currInfos);
+ return;
+ }
+ }
+ }
+}
+
+void ImageDescEditTab::updateRecentTags()
+{
+ TQPopupMenu *menu = d->recentTagsBtn->popup();
+ menu->clear();
+
+ AlbumManager* albumMan = AlbumManager::instance();
+ IntList recentTags = albumMan->albumDB()->getRecentlyAssignedTags();
+
+ if (recentTags.isEmpty())
+ {
+ menu->insertItem(i18n("No Recently Assigned Tags"), 0);
+ menu->setItemEnabled(0, false);
+ }
+ else
+ {
+ for (IntList::const_iterator it = recentTags.begin();
+ it != recentTags.end(); ++it)
+ {
+ TAlbum* album = albumMan->findTAlbum(*it);
+ if (album)
+ {
+ AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance();
+ TQPixmap icon;
+ if (!loader->getTagThumbnail(album, icon))
+ {
+ if (icon.isNull())
+ {
+ icon = loader->getStandardTagIcon(album, AlbumThumbnailLoader::SmallerSize);
+ }
+ }
+ TQString text = album->title() + " (" + ((TAlbum*)album->parent())->prettyURL() + ')';
+ menu->insertItem(icon, text, album->id());
+ }
+ }
+ }
+}
+
+void ImageDescEditTab::slotRecentTagsMenuActivated(int id)
+{
+ AlbumManager* albumMan = AlbumManager::instance();
+
+ if (id > 0)
+ {
+ TAlbum* album = albumMan->findTAlbum(id);
+ if (album)
+ {
+ TAlbumCheckListItem* viewItem = (TAlbumCheckListItem*)album->extraData(d->tagsView);
+ if (viewItem)
+ {
+ viewItem->setOn(true);
+ d->tagsView->setSelected(viewItem, true);
+ d->tagsView->ensureItemVisible(viewItem);
+ }
+ }
+ }
+}
+
+void ImageDescEditTab::slotTagsSearchChanged(const TQString& filter)
+{
+ if (filter.isEmpty())
+ {
+ d->tagsView->collapseView(FolderView::OmitRoot);
+ return;
+ }
+
+ //TODO: this will destroy assigned-tags filtering. Unify in one method.
+ TQString search = filter.lower();
+
+ bool atleastOneMatch = false;
+
+ AlbumList tList = AlbumManager::instance()->allTAlbums();
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbum* tag = (TAlbum*)(*it);
+
+ // don't touch the root Tag
+ if (tag->isRoot())
+ continue;
+
+ bool match = tag->title().lower().contains(search);
+ bool doesExpand = false;
+ if (!match)
+ {
+ // check if any of the parents match the search
+ Album* parent = tag->parent();
+ while (parent && !parent->isRoot())
+ {
+ if (parent->title().lower().contains(search))
+ {
+ match = true;
+ break;
+ }
+
+ parent = parent->parent();
+ }
+ }
+
+ if (!match)
+ {
+ // check if any of the children match the search
+ AlbumIterator it(tag);
+ while (it.current())
+ {
+ if ((*it)->title().lower().contains(search))
+ {
+ match = true;
+ doesExpand = true;
+ break;
+ }
+ ++it;
+ }
+ }
+
+ TAlbumCheckListItem* viewItem = (TAlbumCheckListItem*)(tag->extraData(d->tagsView));
+
+ if (match)
+ {
+ atleastOneMatch = true;
+
+ if (viewItem)
+ {
+ viewItem->setVisible(true);
+ viewItem->setOpen(doesExpand);
+ }
+ }
+ else
+ {
+ if (viewItem)
+ {
+ viewItem->setVisible(false);
+ viewItem->setOpen(false);
+ }
+ }
+ }
+
+ if (search.isEmpty())
+ {
+ TAlbum* root = AlbumManager::instance()->findTAlbum(0);
+ TAlbumCheckListItem* rootItem = (TAlbumCheckListItem*)(root->extraData(d->tagsView));
+ if (rootItem)
+ rootItem->setText(0, root->title());
+ }
+ else
+ {
+ TAlbum* root = AlbumManager::instance()->findTAlbum(0);
+ TAlbumCheckListItem* rootItem = (TAlbumCheckListItem*)(root->extraData(d->tagsView));
+ if (rootItem)
+ rootItem->setText(0, i18n("Found Tags"));
+ }
+
+ emit signalTagFilterMatch(atleastOneMatch);
+}
+
+void ImageDescEditTab::slotAssignedTagsToggled(bool t)
+{
+ //TODO: this will destroy name filtering. Unify in one method.
+ TQListViewItemIterator it(d->tagsView);
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ TAlbum *tag = item->album();
+ if (tag)
+ {
+ if (!tag->isRoot())
+ {
+ if (t)
+ {
+ MetadataHub::TagStatus status = d->hub.tagStatus(item->album());
+ bool tagAssigned = (status == MetadataHub::MetadataAvailable && status.hasTag)
+ || status == MetadataHub::MetadataDisjoint;
+ item->setVisible(tagAssigned);
+
+ if (tagAssigned)
+ {
+ Album* parent = tag->parent();
+ while (parent && !parent->isRoot())
+ {
+ TAlbumCheckListItem *pitem = (TAlbumCheckListItem*)parent->extraData(d->tagsView);
+ pitem->setVisible(true);
+ parent = parent->parent();
+ }
+ }
+ }
+ else
+ {
+ item->setVisible(true);
+ }
+ }
+ }
+ ++it;
+ }
+
+ // correct visibilities afterwards:
+ // As TQListViewItem::setVisible works recursively on all it's children
+ // we have to correct this
+ if (t)
+ {
+ it = d->tagsView;
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ TAlbum *tag = item->album();
+ if (tag)
+ {
+ if (!tag->isRoot())
+ {
+ // only if the current item is not marked as tagged, check all children
+ MetadataHub::TagStatus status = d->hub.tagStatus(item->album());
+ bool tagAssigned = (status == MetadataHub::MetadataAvailable && status.hasTag)
+ || status == MetadataHub::MetadataDisjoint;
+ if (!tagAssigned)
+ {
+ bool somethingIsSet = false;
+ TQListViewItem* nextSibling = (*it)->nextSibling();
+ TQListViewItemIterator tmpIt = it;
+ ++tmpIt;
+ while (*tmpIt != nextSibling )
+ {
+ TAlbumCheckListItem* tmpItem = dynamic_cast<TAlbumCheckListItem*>(tmpIt.current());
+ MetadataHub::TagStatus tmpStatus = d->hub.tagStatus(tmpItem->album());
+ bool tmpTagAssigned = (tmpStatus == MetadataHub::MetadataAvailable && tmpStatus.hasTag)
+ || tmpStatus == MetadataHub::MetadataDisjoint;
+ if(tmpTagAssigned)
+ {
+ somethingIsSet = true;
+ }
+ ++tmpIt;
+ }
+ if (!somethingIsSet)
+ {
+ item->setVisible(false);
+ }
+ }
+ }
+ }
+ ++it;
+ }
+ }
+
+ TAlbum *root = AlbumManager::instance()->findTAlbum(0);
+ TAlbumCheckListItem *rootItem = (TAlbumCheckListItem*)(root->extraData(d->tagsView));
+ if (rootItem)
+ {
+ if (t)
+ rootItem->setText(0, i18n("Assigned Tags"));
+ else
+ rootItem->setText(0, root->title());
+ }
+}
+
+void ImageDescEditTab::refreshTagsView()
+{
+ d->tagsView->refresh();
+}
+
+void ImageDescEditTab::slotCreateNewTag()
+{
+ TQString tagStr = d->newTagEdit->text();
+ if (tagStr.isEmpty()) return;
+
+ TAlbum *mainRootAlbum = 0;
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(d->tagsView->selectedItem());
+ if (item)
+ mainRootAlbum = item->album();
+
+ TQMap<TQString, TQString> errMap;
+ AlbumList tList = TagEditDlg::createTAlbum(mainRootAlbum, tagStr, TQString("tag"), errMap);
+
+ for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TAlbumCheckListItem* item = (TAlbumCheckListItem*)(*it)->extraData(d->tagsView);
+ if (item)
+ {
+ item->setOn(true);
+ d->tagsView->ensureItemVisible(item);
+ }
+ }
+
+ d->newTagEdit->lineEdit()->clear();
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/imageproperties/imagedescedittab.h b/src/libs/imageproperties/imagedescedittab.h
new file mode 100644
index 00000000..617bd59b
--- /dev/null
+++ b/src/libs/imageproperties/imagedescedittab.h
@@ -0,0 +1,145 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-03-09
+ * Description : Captions, Tags, and Rating properties editor
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2009 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEDESCEDITTAB_H
+#define IMAGEDESCEDITTAB_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqpixmap.h>
+#include <tqptrlist.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "navigatebartab.h"
+#include "albummanager.h"
+
+class TQListViewItem;
+
+namespace Digikam
+{
+class TAlbumCheckListItem;
+class ImageInfo;
+class ImageDescEditTabPriv;
+
+class DIGIKAM_EXPORT ImageDescEditTab : public NavigateBarTab
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageDescEditTab(TQWidget *parent, bool navBar=true);
+ ~ImageDescEditTab();
+
+ void assignRating(int rating);
+ void setItem(ImageInfo *info=0);
+ void setItems(TQPtrList<ImageInfo> infos);
+ void populateTags();
+ void refreshTagsView();
+
+signals:
+
+ void signalProgressBarMode(int, const TQString&);
+ void signalProgressValue(int);
+ void signalTagFilterMatch(bool);
+
+protected:
+
+ bool eventFilter(TQObject *o, TQEvent *e);
+
+private:
+
+ void setInfos(TQPtrList<ImageInfo> infos);
+
+ void updateTagsView();
+ void updateComments();
+ void updateRating();
+ void updateDate();
+ void updateRecentTags();
+
+ void tagNew(TAlbum* parAlbum, const TQString& _title=TQString(), const TQString& _icon=TQString()) const;
+ void tagEdit(TAlbum* album);
+ void tagDelete(TAlbum *album);
+
+ void toggleChildTags(TAlbum *album, bool b);
+ void toggleParentTags(TAlbum *album, bool b);
+
+ void setTagThumbnail(TAlbum *album);
+
+ bool singleSelection() const;
+ void setMetadataWidgetStatus(int status, TQWidget *widget);
+ void reloadForMetadataChange(TQ_LLONG imageId);
+
+private slots:
+
+ void slotApplyAllChanges();
+ void slotCreateNewTag();
+ void slotRevertAllChanges();
+ void slotChangingItems();
+ void slotItemStateChanged(TAlbumCheckListItem *);
+ void slotCommentChanged();
+ void slotDateTimeChanged(const TQDateTime& dateTime);
+ void slotRatingChanged(int rating);
+ void slotModified();
+ void slotRightButtonClicked(TQListViewItem *, const TQPoint &, int);
+ void slotTagsSearchChanged(const TQString&);
+
+ void slotAlbumAdded(Album* a);
+ void slotAlbumDeleted(Album* a);
+ void slotAlbumIconChanged(Album* a);
+ void slotAlbumRenamed(Album* a);
+ void slotAlbumsCleared();
+ void slotAlbumMoved(TAlbum* tag, TAlbum* newParent);
+
+ void slotABCContextMenu();
+ void slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail);
+ void slotThumbnailLost(Album *album);
+ void slotReloadThumbnails();
+
+ void slotImageTagsChanged(TQ_LLONG imageId);
+ void slotImagesChanged(int albumId);
+ void slotImageRatingChanged(TQ_LLONG imageId);
+ void slotImageDateChanged(TQ_LLONG imageId);
+ void slotImageCaptionChanged(TQ_LLONG imageId);
+
+ void slotRecentTagsMenuActivated(int);
+ void slotAssignedTagsToggled(bool);
+
+ void slotMoreMenu();
+ void slotReadFromFileMetadataToDatabase();
+ void slotWriteToFileMetadataFromDatabase();
+
+private:
+
+ ImageDescEditTabPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif // IMAGEDESCEDITTAB_H
diff --git a/src/libs/imageproperties/imagepropertiescolorstab.cpp b/src/libs/imageproperties/imagepropertiescolorstab.cpp
new file mode 100644
index 00000000..af85953b
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiescolorstab.cpp
@@ -0,0 +1,799 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : a tab to display colors information of images
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqspinbox.h>
+#include <tqcombobox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqgroupbox.h>
+#include <tqhbuttongroup.h>
+#include <tqpushbutton.h>
+#include <tqtooltip.h>
+#include <tqvbox.h>
+#include <tqscrollview.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <ksqueezedtextlabel.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kdialogbase.h>
+#include <kstandarddirs.h>
+#include <ktabwidget.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "imagehistogram.h"
+#include "histogramwidget.h"
+#include "colorgradientwidget.h"
+#include "navigatebarwidget.h"
+#include "sharedloadsavethread.h"
+#include "iccprofilewidget.h"
+#include "cietonguewidget.h"
+#include "imagepropertiescolorstab.h"
+#include "imagepropertiescolorstab.moc"
+
+namespace Digikam
+{
+
+class ImagePropertiesColorsTabPriv
+{
+public:
+
+ enum MetadataTab
+ {
+ HISTOGRAM=0,
+ ICCPROFILE
+ };
+
+ ImagePropertiesColorsTabPriv()
+ {
+ imageLoaderThread = 0;
+ tab = 0;
+ channelCB = 0;
+ colorsCB = 0;
+ renderingCB = 0;
+ scaleBG = 0;
+ regionBG = 0;
+ minInterv = 0;
+ maxInterv = 0;
+ labelMeanValue = 0;
+ labelPixelsValue = 0;
+ labelStdDevValue = 0;
+ labelCountValue = 0;
+ labelMedianValue = 0;
+ labelPercentileValue = 0;
+ labelColorDepth = 0;
+ labelAlphaChannel = 0;
+
+ iccProfileWidget = 0;
+ hGradient = 0;
+ histogramWidget = 0;
+ imageLoaderThread = 0;
+
+ inLoadingProcess = false;
+ }
+
+ bool inLoadingProcess;
+
+ TQComboBox *channelCB;
+ TQComboBox *colorsCB;
+ TQComboBox *renderingCB;
+
+ TQHButtonGroup *scaleBG;
+ TQHButtonGroup *regionBG;
+
+ TQSpinBox *minInterv;
+ TQSpinBox *maxInterv;
+
+ TQLabel *labelMeanValue;
+ TQLabel *labelPixelsValue;
+ TQLabel *labelStdDevValue;
+ TQLabel *labelCountValue;
+ TQLabel *labelMedianValue;
+ TQLabel *labelPercentileValue;
+ TQLabel *labelColorDepth;
+ TQLabel *labelAlphaChannel;
+
+ TQString currentFilePath;
+ LoadingDescription currentLoadingDescription;
+
+ TQRect selectionArea;
+
+ TQByteArray embedded_profile;
+
+ KTabWidget *tab;
+
+ DImg image;
+ DImg imageSelection;
+
+ ICCProfileWidget *iccProfileWidget;
+ ColorGradientWidget *hGradient;
+ HistogramWidget *histogramWidget;
+ SharedLoadSaveThread *imageLoaderThread;
+};
+
+ImagePropertiesColorsTab::ImagePropertiesColorsTab(TQWidget* parent, bool navBar)
+ : NavigateBarTab(parent)
+{
+ d = new ImagePropertiesColorsTabPriv;
+
+ setupNavigateBar(navBar);
+ d->tab = new KTabWidget(this);
+ m_navigateBarLayout->addWidget(d->tab);
+
+ // Histogram tab area -----------------------------------------------------
+
+ TQScrollView *sv = new TQScrollView(d->tab);
+ sv->viewport()->setBackgroundMode(TQt::PaletteBackground);
+ sv->setResizePolicy(TQScrollView::AutoOneFit);
+ sv->setFrameStyle(TQFrame::NoFrame);
+
+ TQWidget* histogramPage = new TQWidget(sv->viewport());
+ TQGridLayout *topLayout = new TQGridLayout(histogramPage, 8, 3,
+ KDialog::spacingHint(), KDialog::spacingHint());
+ sv->addChild(histogramPage);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), histogramPage);
+ label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ d->channelCB = new TQComboBox( false, histogramPage );
+ d->channelCB->insertItem( i18n("Luminosity") );
+ d->channelCB->insertItem( i18n("Red") );
+ d->channelCB->insertItem( i18n("Green") );
+ d->channelCB->insertItem( i18n("Blue") );
+ d->channelCB->insertItem( i18n("Alpha") );
+ d->channelCB->insertItem( i18n("Colors") );
+ TQWhatsThis::add( d->channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: Display luminosity (perceived brightness) values.<p>"
+ "<b>Red</b>: Display the red image channel.<p>"
+ "<b>Green</b>: Display the green image channel.<p>"
+ "<b>Blue</b>: Display the blue image channel.<p>"
+ "<b>Alpha</b>: Display the alpha image channel. "
+ "This channel corresponds to the transparency value and "
+ "is supported by some image formats such as PNG or TIFF.<p>"
+ "<b>Colors</b>: Display all color channel values at the same time."));
+
+ d->scaleBG = new TQHButtonGroup(histogramPage);
+ d->scaleBG->setExclusive(true);
+ d->scaleBG->setFrameShape(TQFrame::NoFrame);
+ d->scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add( d->scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal values are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal values are big; "
+ "if it is used, all values (small and large) will be visible on the "
+ "graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( d->scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ d->scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( d->scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ d->scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQLabel *label10 = new TQLabel(i18n("Colors:"), histogramPage);
+ label10->setAlignment ( TQt::AlignRight | TQt::AlignVCenter );
+ d->colorsCB = new TQComboBox( false, histogramPage );
+ d->colorsCB->insertItem( i18n("Red") );
+ d->colorsCB->insertItem( i18n("Green") );
+ d->colorsCB->insertItem( i18n("Blue") );
+ d->colorsCB->setEnabled( false );
+ TQWhatsThis::add( d->colorsCB, i18n("<p>Select the main color displayed with Colors Channel mode here:<p>"
+ "<b>Red</b>: Draw the red image channel in the foreground.<p>"
+ "<b>Green</b>: Draw the green image channel in the foreground.<p>"
+ "<b>Blue</b>: Draw the blue image channel in the foreground.<p>"));
+
+ d->regionBG = new TQHButtonGroup(histogramPage);
+ d->regionBG->setExclusive(true);
+ d->regionBG->setFrameShape(TQFrame::NoFrame);
+ d->regionBG->setInsideMargin( 0 );
+ d->regionBG->hide();
+ TQWhatsThis::add( d->regionBG, i18n("<p>Select from which region the histogram will be computed here:<p>"
+ "<b>Full Image</b>: Compute histogram using the full image.<p>"
+ "<b>Selection</b>: Compute histogram using the current image "
+ "selection."));
+
+ TQPushButton *fullImageButton = new TQPushButton( d->regionBG );
+ TQToolTip::add( fullImageButton, i18n( "<p>Full Image" ) );
+ d->regionBG->insert(fullImageButton, HistogramWidget::FullImageHistogram);
+ TDEGlobal::dirs()->addResourceType("image-full", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("image-full", "image-full.png");
+ fullImageButton->setPixmap( TQPixmap( directory + "image-full.png" ) );
+ fullImageButton->setToggleButton(true);
+
+ TQPushButton *SelectionImageButton = new TQPushButton( d->regionBG );
+ TQToolTip::add( SelectionImageButton, i18n( "<p>Selection" ) );
+ d->regionBG->insert(SelectionImageButton, HistogramWidget::ImageSelectionHistogram);
+ TDEGlobal::dirs()->addResourceType("image-selection", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("image-selection", "image-selection.png");
+ SelectionImageButton->setPixmap( TQPixmap( directory + "image-selection.png" ) );
+ SelectionImageButton->setToggleButton(true);
+
+ // -------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(histogramPage);
+ d->histogramWidget = new HistogramWidget(256, 140, histoBox);
+ TQWhatsThis::add( d->histogramWidget, i18n("<p>This is the histogram drawing of the "
+ "selected image channel"));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ d->hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox);
+ d->hGradient->setColors(TQColor("black"), TQColor("white"));
+
+ // -------------------------------------------------------------
+
+ TQHBoxLayout *hlay2 = new TQHBoxLayout(KDialog::spacingHint());
+ TQLabel *label3 = new TQLabel(i18n("Range:"), histogramPage);
+ label3->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->minInterv = new TQSpinBox(0, 255, 1, histogramPage);
+ d->minInterv->setValue(0);
+ TQWhatsThis::add(d->minInterv, i18n("<p>Select the minimal intensity "
+ "value of the histogram selection here."));
+ d->maxInterv = new TQSpinBox(0, 255, 1, histogramPage);
+ d->maxInterv->setValue(255);
+ TQWhatsThis::add(d->minInterv, i18n("<p>Select the maximal intensity value "
+ "of the histogram selection here."));
+ hlay2->addWidget(label3);
+ hlay2->addWidget(d->minInterv);
+ hlay2->addWidget(d->maxInterv);
+
+ // -------------------------------------------------------------
+
+ TQGroupBox *gbox = new TQGroupBox(2, TQt::Horizontal, i18n("Statistics"), histogramPage);
+ TQWhatsThis::add( gbox, i18n("<p>Here you can see the statistical results calculated from the "
+ "selected histogram part. These values are available for all "
+ "channels."));
+
+ TQLabel *label5 = new TQLabel(i18n("Pixels:"), gbox);
+ label5->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelPixelsValue = new TQLabel(gbox);
+ d->labelPixelsValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+
+ TQLabel *label7 = new TQLabel(i18n("Count:"), gbox);
+ label7->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelCountValue = new TQLabel(gbox);
+ d->labelCountValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+
+ TQLabel *label4 = new TQLabel(i18n("Mean:"), gbox);
+ label4->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelMeanValue = new TQLabel(gbox);
+ d->labelMeanValue->setAlignment (TQt::AlignRight | TQt::AlignVCenter);
+
+ TQLabel *label6 = new TQLabel(i18n("Std. deviation:"), gbox);
+ label6->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelStdDevValue = new TQLabel(gbox);
+ d->labelStdDevValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+
+ TQLabel *label8 = new TQLabel(i18n("Median:"), gbox);
+ label8->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelMedianValue = new TQLabel(gbox);
+ d->labelMedianValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+
+ TQLabel *label9 = new TQLabel(i18n("Percentile:"), gbox);
+ label9->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelPercentileValue = new TQLabel(gbox);
+ d->labelPercentileValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+
+ TQLabel *label11 = new TQLabel(i18n("Color depth:"), gbox);
+ label11->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelColorDepth = new TQLabel(gbox);
+ d->labelColorDepth->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+
+ TQLabel *label12 = new TQLabel(i18n("Alpha Channel:"), gbox);
+ label12->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+ d->labelAlphaChannel = new TQLabel(gbox);
+ d->labelAlphaChannel->setAlignment(TQt::AlignRight | TQt::AlignVCenter);
+
+ topLayout->addMultiCellWidget(label1, 1, 1, 0, 0);
+ topLayout->addMultiCellWidget(d->channelCB, 1, 1, 1, 1);
+ topLayout->addMultiCellWidget(d->scaleBG, 1, 1, 3, 3);
+ topLayout->addMultiCellWidget(label10, 2, 2, 0, 0);
+ topLayout->addMultiCellWidget(d->colorsCB, 2, 2, 1, 1);
+ topLayout->addMultiCellWidget(d->regionBG, 2, 2, 3, 3);
+ topLayout->addMultiCellWidget(histoBox, 3, 4, 0, 3);
+ topLayout->addMultiCellLayout(hlay2, 5, 5, 0, 3);
+ topLayout->addMultiCellWidget(gbox, 6, 6, 0, 3);
+ topLayout->setColStretch(2, 10);
+ topLayout->setRowStretch(7, 10);
+
+ d->tab->insertTab(sv, i18n("Histogram"), ImagePropertiesColorsTabPriv::HISTOGRAM );
+
+ // ICC Profiles tab area ---------------------------------------
+
+ TQScrollView *sv2 = new TQScrollView(d->tab);
+ sv2->viewport()->setBackgroundMode(TQt::PaletteBackground);
+ sv2->setResizePolicy(TQScrollView::AutoOneFit);
+ sv2->setFrameStyle(TQFrame::NoFrame);
+
+ d->iccProfileWidget = new ICCProfileWidget(sv2->viewport());
+ sv2->addChild(d->iccProfileWidget);
+ d->tab->insertTab(sv2, i18n("ICC profile"), ImagePropertiesColorsTabPriv::ICCPROFILE);
+
+ // -------------------------------------------------------------
+
+ connect(d->channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(d->scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(d->colorsCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotColorsChanged(int)));
+
+ connect(d->regionBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotRenderingChanged(int)));
+
+ connect(d->histogramWidget, TQ_SIGNAL(signalIntervalChanged( int, int )),
+ this, TQ_SLOT(slotUpdateInterval(int, int)));
+
+ connect(d->histogramWidget, TQ_SIGNAL(signalMaximumValueChanged( int )),
+ this, TQ_SLOT(slotUpdateIntervRange(int)));
+
+ connect(d->histogramWidget, TQ_SIGNAL(signalHistogramComputationDone(bool)),
+ this, TQ_SLOT(slotRefreshOptions(bool)));
+
+ connect(d->histogramWidget, TQ_SIGNAL(signalHistogramComputationFailed(void)),
+ this, TQ_SLOT(slotHistogramComputationFailed(void)));
+
+ connect(d->minInterv, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotMinValueChanged(int)));
+
+ connect(d->maxInterv, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotMaxValueChanged(int)));
+
+ // -- read config ---------------------------------------------------------
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("Image Properties SideBar");
+ d->tab->setCurrentPage(config->readNumEntry("ImagePropertiesColors Tab",
+ ImagePropertiesColorsTabPriv::HISTOGRAM));
+ d->iccProfileWidget->setMode(config->readNumEntry("ICC Level", ICCProfileWidget::SIMPLE));
+ d->iccProfileWidget->setCurrentItemByKey(config->readEntry("Current ICC Item", TQString()));
+
+ d->channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity.
+ d->scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ d->colorsCB->setCurrentItem(config->readNumEntry("Histogram Color", 0)); // Red.
+ d->regionBG->setButton(config->readNumEntry("Histogram Rendering", HistogramWidget::FullImageHistogram));
+}
+
+ImagePropertiesColorsTab::~ImagePropertiesColorsTab()
+{
+ // If there is a currently histogram computation when dialog is closed,
+ // stop it before the d->image data are deleted automatically!
+ d->histogramWidget->stopHistogramComputation();
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("Image Properties SideBar");
+ config->writeEntry("ImagePropertiesColors Tab", d->tab->currentPageIndex());
+ config->writeEntry("Histogram Channel", d->channelCB->currentItem());
+ config->writeEntry("Histogram Scale", d->scaleBG->selectedId());
+ config->writeEntry("Histogram Color", d->colorsCB->currentItem());
+ config->writeEntry("Histogram Rendering", d->regionBG->selectedId());
+ config->writeEntry("ICC Level", d->iccProfileWidget->getMode());
+ config->writeEntry("Current ICC Item", d->iccProfileWidget->getCurrentItemKey());
+ config->sync();
+
+ if (d->imageLoaderThread)
+ delete d->imageLoaderThread;
+
+ if (d->histogramWidget)
+ delete d->histogramWidget;
+
+ if (d->hGradient)
+ delete d->hGradient;
+
+ delete d;
+}
+
+void ImagePropertiesColorsTab::setData(const KURL& url, const TQRect &selectionArea,
+ DImg *img)
+{
+ // We might be getting duplicate events from AlbumIconView,
+ // which will cause all sorts of duplicate work.
+ // More importantly, while the loading thread can handle this pretty well,
+ // this will completely mess up the timing of progress info in the histogram widget.
+ // So filter here, before the stopHistogramComputation!
+ if (!img && url.path() == d->currentFilePath && d->inLoadingProcess)
+ return;
+
+ // This is necessary to stop computation because d->image.bits() is currently used by
+ // threaded histogram algorithm.
+ d->histogramWidget->stopHistogramComputation();
+
+ d->currentFilePath = TQString();
+ d->currentLoadingDescription = LoadingDescription();
+ d->iccProfileWidget->loadFromURL(KURL());
+
+ // Clear information.
+ d->labelMeanValue->clear();
+ d->labelPixelsValue->clear();
+ d->labelStdDevValue->clear();
+ d->labelCountValue->clear();
+ d->labelMedianValue->clear();
+ d->labelPercentileValue->clear();
+ d->labelColorDepth->clear();
+ d->labelAlphaChannel->clear();
+
+ if (url.isEmpty())
+ {
+ setEnabled(false);
+ return;
+ }
+
+ d->selectionArea = selectionArea;
+ d->image.reset();
+ setEnabled(true);
+
+ if (!img)
+ {
+ loadImageFromUrl(url);
+ }
+ else
+ {
+ d->image = img->copy();
+
+ if ( !d->image.isNull() )
+ {
+ getICCData();
+
+ // If a selection area is done in Image Editor and if the current image is the same
+ // in Image Editor, then compute too the histogram for this selection.
+ if (d->selectionArea.isValid())
+ {
+ d->imageSelection = d->image.copy(d->selectionArea);
+ d->histogramWidget->updateData(d->image.bits(), d->image.width(), d->image.height(),
+ d->image.sixteenBit(), d->imageSelection.bits(),
+ d->imageSelection.width(), d->imageSelection.height());
+ d->regionBG->show();
+ updateInformations();
+ }
+ else
+ {
+ d->histogramWidget->updateData(d->image.bits(), d->image.width(),
+ d->image.height(), d->image.sixteenBit());
+ d->regionBG->hide();
+ updateInformations();
+ }
+ }
+ else
+ {
+ d->histogramWidget->setLoadingFailed();
+ d->iccProfileWidget->setLoadingFailed();
+ slotHistogramComputationFailed();
+ }
+ }
+}
+
+void ImagePropertiesColorsTab::loadImageFromUrl(const KURL& url)
+{
+ // create thread on demand
+ if (!d->imageLoaderThread)
+ {
+ d->imageLoaderThread = new SharedLoadSaveThread();
+
+ connect(d->imageLoaderThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg&)),
+ this, TQ_SLOT(slotLoadImageFromUrlComplete(const LoadingDescription &, const DImg&)));
+
+ connect(d->imageLoaderThread, TQ_SIGNAL(signalMoreCompleteLoadingAvailable(const LoadingDescription &, const LoadingDescription &)),
+ this, TQ_SLOT(slotMoreCompleteLoadingAvailable(const LoadingDescription &, const LoadingDescription &)));
+ }
+
+ LoadingDescription desc = LoadingDescription(url.path());
+
+ if (DImg::fileFormat(desc.filePath) == DImg::RAW)
+ {
+ // use raw settings optimized for speed
+
+ DRawDecoding rawDecodingSettings = DRawDecoding();
+ rawDecodingSettings.optimizeTimeLoading();
+ desc = LoadingDescription(desc.filePath, rawDecodingSettings);
+ }
+
+ if (d->currentLoadingDescription.equalsOrBetterThan(desc))
+ return;
+
+ d->currentFilePath = desc.filePath;
+ d->currentLoadingDescription = desc;
+ d->inLoadingProcess = true;
+
+ d->imageLoaderThread->load(d->currentLoadingDescription,
+ SharedLoadSaveThread::AccessModeRead,
+ SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious);
+
+ d->histogramWidget->setDataLoading();
+ d->iccProfileWidget->setDataLoading();
+}
+
+void ImagePropertiesColorsTab::slotLoadImageFromUrlComplete(const LoadingDescription &loadingDescription, const DImg& img)
+{
+ // Discard any leftover messages from previous, possibly aborted loads
+ if ( !loadingDescription.equalsOrBetterThan(d->currentLoadingDescription) )
+ return;
+
+ if ( !img.isNull() )
+ {
+ d->histogramWidget->updateData(img.bits(), img.width(), img.height(), img.sixteenBit());
+
+ // As a safety precaution, this must be changed only after updateData is called,
+ // which stops computation because d->image.bits() is currently used by threaded histogram algorithm.
+ d->image = img;
+ d->regionBG->hide();
+ updateInformations();
+ getICCData();
+ }
+ else
+ {
+ d->histogramWidget->setLoadingFailed();
+ d->iccProfileWidget->setLoadingFailed();
+ slotHistogramComputationFailed();
+ }
+ d->inLoadingProcess = false;
+}
+
+void ImagePropertiesColorsTab::slotMoreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription,
+ const LoadingDescription &newLoadingDescription)
+{
+ if (oldLoadingDescription == d->currentLoadingDescription &&
+ newLoadingDescription.equalsOrBetterThan(d->currentLoadingDescription))
+ {
+ // Yes, we do want to stop our old time-optimized loading and chain to the current, more complete loading.
+ // Even the time-optimized raw loading takes significant time, and we must avoid two dcraw instances running
+ // at a time.
+ d->currentLoadingDescription = newLoadingDescription;
+ d->inLoadingProcess = true;
+ d->imageLoaderThread->load(newLoadingDescription,
+ SharedLoadSaveThread::AccessModeRead,
+ SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious);
+ }
+}
+
+void ImagePropertiesColorsTab::setSelection(const TQRect &selectionArea)
+{
+ // This is necessary to stop computation because d->image.bits() is currently used by
+ // threaded histogram algorithm.
+
+ d->histogramWidget->stopHistogramComputation();
+ d->selectionArea = selectionArea;
+
+ if (d->selectionArea.isValid())
+ {
+ d->imageSelection = d->image.copy(d->selectionArea);
+ d->histogramWidget->updateSelectionData(d->imageSelection.bits(), d->imageSelection.width(),
+ d->imageSelection.height(), d->imageSelection.sixteenBit());
+ d->regionBG->show();
+ }
+ else
+ {
+ d->regionBG->hide();
+ slotRenderingChanged(HistogramWidget::FullImageHistogram);
+ }
+}
+
+void ImagePropertiesColorsTab::slotRefreshOptions(bool /*sixteenBit*/)
+{
+ slotChannelChanged(d->channelCB->currentItem());
+ slotScaleChanged(d->scaleBG->selectedId());
+ slotColorsChanged(d->colorsCB->currentItem());
+
+ if (d->selectionArea.isValid())
+ slotRenderingChanged(d->regionBG->selectedId());
+}
+
+void ImagePropertiesColorsTab::slotHistogramComputationFailed()
+{
+ d->imageSelection.reset();
+ d->image.reset();
+}
+
+void ImagePropertiesColorsTab::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case RedChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case GreenChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case BlueChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case AlphaChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::AlphaChannelHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case ColorChannels:
+ d->histogramWidget->m_channelType = HistogramWidget::ColorChannelsHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ d->colorsCB->setEnabled(true);
+ break;
+
+ default: // Luminosity.
+ d->histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+ }
+
+ d->histogramWidget->repaint(false);
+ updateStatistiques();
+}
+
+void ImagePropertiesColorsTab::slotScaleChanged(int scale)
+{
+ d->histogramWidget->m_scaleType = scale;
+ d->histogramWidget->repaint(false);
+}
+
+void ImagePropertiesColorsTab::slotColorsChanged(int color)
+{
+ switch(color)
+ {
+ case AllColorsGreen:
+ d->histogramWidget->m_colorType = HistogramWidget::GreenColor;
+ break;
+
+ case AllColorsBlue:
+ d->histogramWidget->m_colorType = HistogramWidget::BlueColor;
+ break;
+
+ default: // Red.
+ d->histogramWidget->m_colorType = HistogramWidget::RedColor;
+ break;
+ }
+
+ d->histogramWidget->repaint(false);
+ updateStatistiques();
+}
+
+void ImagePropertiesColorsTab::slotRenderingChanged(int rendering)
+{
+ d->histogramWidget->m_renderingType = rendering;
+ d->histogramWidget->repaint(false);
+ updateStatistiques();
+}
+
+void ImagePropertiesColorsTab::slotMinValueChanged(int min)
+{
+ // Called when user changes values of spin box.
+ // Communicate the change to histogram widget.
+
+ // make the one control "push" the other
+ if (min == d->maxInterv->value()+1)
+ d->maxInterv->setValue(min);
+ d->maxInterv->setMinValue(min-1);
+ d->histogramWidget->slotMinValueChanged(min);
+ updateStatistiques();
+}
+
+void ImagePropertiesColorsTab::slotMaxValueChanged(int max)
+{
+ if (max == d->minInterv->value()-1)
+ d->minInterv->setValue(max);
+ d->minInterv->setMaxValue(max+1);
+ d->histogramWidget->slotMaxValueChanged(max);
+ updateStatistiques();
+}
+
+void ImagePropertiesColorsTab::slotUpdateInterval(int min, int max)
+{
+ // Called when value is set from within histogram widget.
+ // Block signals to prevent slotMinValueChanged and
+ // slotMaxValueChanged being called.
+ d->minInterv->blockSignals(true);
+ d->minInterv->setMaxValue(max+1);
+ d->minInterv->setValue(min);
+ d->minInterv->blockSignals(false);
+
+ d->maxInterv->blockSignals(true);
+ d->maxInterv->setMinValue(min-1);
+ d->maxInterv->setValue(max);
+ d->maxInterv->blockSignals(false);
+
+ updateStatistiques();
+}
+
+void ImagePropertiesColorsTab::slotUpdateIntervRange(int range)
+{
+ d->maxInterv->setMaxValue( range );
+}
+
+void ImagePropertiesColorsTab::updateInformations()
+{
+ d->labelColorDepth->setText(d->image.sixteenBit() ? i18n("16 bits") : i18n("8 bits"));
+ d->labelAlphaChannel->setText(d->image.hasAlpha() ? i18n("Yes") : i18n("No"));
+}
+
+void ImagePropertiesColorsTab::updateStatistiques()
+{
+ TQString value;
+ int min = d->minInterv->value();
+ int max = d->maxInterv->value();
+ int channel = d->channelCB->currentItem();
+
+ if ( channel == HistogramWidget::ColorChannelsHistogram )
+ channel = d->colorsCB->currentItem()+1;
+
+ double mean = d->histogramWidget->m_imageHistogram->getMean(channel, min, max);
+ d->labelMeanValue->setText(value.setNum(mean, 'f', 1));
+
+ double pixels = d->histogramWidget->m_imageHistogram->getPixels();
+ d->labelPixelsValue->setText(value.setNum((float)pixels, 'f', 0));
+
+ double stddev = d->histogramWidget->m_imageHistogram->getStdDev(channel, min, max);
+ d->labelStdDevValue->setText(value.setNum(stddev, 'f', 1));
+
+ double counts = d->histogramWidget->m_imageHistogram->getCount(channel, min, max);
+ d->labelCountValue->setText(value.setNum((float)counts, 'f', 0));
+
+ double median = d->histogramWidget->m_imageHistogram->getMedian(channel, min, max);
+ d->labelMedianValue->setText(value.setNum(median, 'f', 1));
+
+ double percentile = (pixels > 0 ? (100.0 * counts / pixels) : 0.0);
+ d->labelPercentileValue->setText(value.setNum(percentile, 'f', 1));
+}
+
+void ImagePropertiesColorsTab::getICCData()
+{
+ if (d->image.getICCProfil().isNull())
+ {
+ d->iccProfileWidget->setLoadingFailed();
+ }
+ else
+ {
+ d->embedded_profile = d->image.getICCProfil();
+ d->iccProfileWidget->loadFromData(d->currentFilePath, d->embedded_profile);
+ }
+}
+
+} // NameSpace Digikam
+
diff --git a/src/libs/imageproperties/imagepropertiescolorstab.h b/src/libs/imageproperties/imagepropertiescolorstab.h
new file mode 100644
index 00000000..0e01cad3
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiescolorstab.h
@@ -0,0 +1,114 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : a tab to display colors information of images
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ * ============================================================ */
+
+#ifndef IMAGEPROPERTIESCOLORSTAB_H
+#define IMAGEPROPERTIESCOLORSTAB_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqcstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+#include "navigatebartab.h"
+
+class TQRect;
+
+namespace Digikam
+{
+
+class DImg;
+class LoadingDescription;
+class ImagePropertiesColorsTabPriv;
+
+class DIGIKAM_EXPORT ImagePropertiesColorsTab : public NavigateBarTab
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePropertiesColorsTab(TQWidget* parent, bool navBar=true);
+ ~ImagePropertiesColorsTab();
+
+ void setData(const KURL& url=KURL(), const TQRect &selectionArea = TQRect(),
+ DImg *img=0);
+
+ void setSelection(const TQRect &selectionArea);
+
+private:
+
+ void loadImageFromUrl(const KURL& url);
+ void updateInformations();
+ void updateStatistiques();
+ void getICCData();
+
+private slots:
+
+ void slotRefreshOptions(bool sixteenBit);
+ void slotHistogramComputationFailed(void);
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorsChanged(int color);
+ void slotRenderingChanged(int rendering);
+ void slotMinValueChanged(int);
+ void slotMaxValueChanged(int);
+
+ void slotUpdateInterval(int min, int max);
+ void slotUpdateIntervRange(int range);
+
+ void slotLoadImageFromUrlComplete(const LoadingDescription &loadingDescription, const DImg& img);
+ void slotMoreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription,
+ const LoadingDescription &newLoadingDescription);
+
+private:
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel,
+ AlphaChannel,
+ ColorChannels
+ };
+
+ enum AllColorsColorType
+ {
+ AllColorsRed=0,
+ AllColorsGreen,
+ AllColorsBlue
+ };
+
+ ImagePropertiesColorsTabPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEPROPERTIESCOLORSTAB_H */
diff --git a/src/libs/imageproperties/imagepropertiesmetadatatab.cpp b/src/libs/imageproperties/imagepropertiesmetadatatab.cpp
new file mode 100644
index 00000000..5a48aaec
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiesmetadatatab.cpp
@@ -0,0 +1,201 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : a tab to display metadata information of images
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqfile.h>
+#include <tqlabel.h>
+#include <tqpixmap.h>
+#include <tqfileinfo.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kdialogbase.h>
+#include <tdefileitem.h>
+#include <ktabwidget.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "exifwidget.h"
+#include "makernotewidget.h"
+#include "iptcwidget.h"
+#include "gpswidget.h"
+#include "navigatebarwidget.h"
+#include "imagepropertiesmetadatatab.h"
+#include "imagepropertiesmetadatatab.moc"
+
+namespace Digikam
+{
+
+class ImagePropertiesMetadataTabPriv
+{
+public:
+
+ enum MetadataTab
+ {
+ EXIF=0,
+ MAKERNOTE,
+ IPTC,
+ GPS
+ };
+
+ ImagePropertiesMetadataTabPriv()
+ {
+ exifWidget = 0;
+ makernoteWidget = 0;
+ iptcWidget = 0;
+ gpsWidget = 0;
+ tab = 0;
+ }
+
+ KTabWidget *tab;
+
+ ExifWidget *exifWidget;
+
+ MakerNoteWidget *makernoteWidget;
+
+ IptcWidget *iptcWidget;
+
+ GPSWidget *gpsWidget;
+};
+
+ImagePropertiesMetaDataTab::ImagePropertiesMetaDataTab(TQWidget* parent, bool navBar)
+ : NavigateBarTab(parent)
+{
+ d = new ImagePropertiesMetadataTabPriv;
+
+ setupNavigateBar(navBar);
+ d->tab = new KTabWidget(this);
+ m_navigateBarLayout->addWidget(d->tab);
+
+ // Exif tab area -----------------------------------------------------
+
+ d->exifWidget = new ExifWidget(d->tab);
+ d->tab->insertTab(d->exifWidget, i18n("EXIF"), ImagePropertiesMetadataTabPriv::EXIF);
+
+ // Makernote tab area -----------------------------------------------------
+
+ d->makernoteWidget = new MakerNoteWidget(d->tab);
+ d->tab->insertTab(d->makernoteWidget, i18n("Makernote"), ImagePropertiesMetadataTabPriv::MAKERNOTE);
+
+ // IPTC tab area ---------------------------------------
+
+ d->iptcWidget = new IptcWidget(d->tab);
+ d->tab->insertTab(d->iptcWidget, i18n("IPTC"), ImagePropertiesMetadataTabPriv::IPTC);
+
+ // GPS tab area ---------------------------------------
+
+ d->gpsWidget = new GPSWidget(d->tab);
+ d->tab->insertTab(d->gpsWidget, i18n("GPS"), ImagePropertiesMetadataTabPriv::GPS);
+
+ // -- read config ---------------------------------------------------------
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("Image Properties SideBar");
+ d->tab->setCurrentPage(config->readNumEntry("ImagePropertiesMetaData Tab",
+ ImagePropertiesMetadataTabPriv::EXIF));
+ d->exifWidget->setMode(config->readNumEntry("EXIF Level", ExifWidget::SIMPLE));
+ d->makernoteWidget->setMode(config->readNumEntry("MAKERNOTE Level", MakerNoteWidget::SIMPLE));
+ d->iptcWidget->setMode(config->readNumEntry("IPTC Level", IptcWidget::SIMPLE));
+ d->gpsWidget->setMode(config->readNumEntry("GPS Level", GPSWidget::SIMPLE));
+ d->exifWidget->setCurrentItemByKey(config->readEntry("Current EXIF Item", TQString()));
+ d->makernoteWidget->setCurrentItemByKey(config->readEntry("Current MAKERNOTE Item", TQString()));
+ d->iptcWidget->setCurrentItemByKey(config->readEntry("Current IPTC Item", TQString()));
+ d->gpsWidget->setCurrentItemByKey(config->readEntry("Current GPS Item", TQString()));
+ d->gpsWidget->setWebGPSLocator(config->readNumEntry("Current Web GPS Locator", GPSWidget::MapQuest));
+}
+
+ImagePropertiesMetaDataTab::~ImagePropertiesMetaDataTab()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Image Properties SideBar");
+ config->writeEntry("ImagePropertiesMetaData Tab", d->tab->currentPageIndex());
+ config->writeEntry("EXIF Level", d->exifWidget->getMode());
+ config->writeEntry("MAKERNOTE Level", d->makernoteWidget->getMode());
+ config->writeEntry("IPTC Level", d->iptcWidget->getMode());
+ config->writeEntry("GPS Level", d->gpsWidget->getMode());
+ config->writeEntry("Current EXIF Item", d->exifWidget->getCurrentItemKey());
+ config->writeEntry("Current MAKERNOTE Item", d->makernoteWidget->getCurrentItemKey());
+ config->writeEntry("Current IPTC Item", d->iptcWidget->getCurrentItemKey());
+ config->writeEntry("Current GPS Item", d->gpsWidget->getCurrentItemKey());
+ config->writeEntry("Current Web GPS Locator", d->gpsWidget->getWebGPSLocator());
+ config->sync();
+
+ delete d;
+}
+
+void ImagePropertiesMetaDataTab::setCurrentURL(const KURL& url)
+{
+ if (url.isEmpty())
+ {
+ d->exifWidget->loadFromURL(url);
+ d->makernoteWidget->loadFromURL(url);
+ d->iptcWidget->loadFromURL(url);
+ d->gpsWidget->loadFromURL(url);
+ setEnabled(false);
+ return;
+ }
+
+ setEnabled(true);
+ DMetadata metadata(url.path());
+
+ TQByteArray exifData = metadata.getExif();
+ TQByteArray iptcData = metadata.getIptc();
+
+ d->exifWidget->loadFromData(url.filename(), exifData);
+ d->makernoteWidget->loadFromData(url.filename(), exifData);
+ d->iptcWidget->loadFromData(url.filename(), iptcData);
+ d->gpsWidget->loadFromData(url.filename(), exifData);
+}
+
+void ImagePropertiesMetaDataTab::setCurrentData(const TQByteArray& exifData,
+ const TQByteArray& iptcData,
+ const TQString& filename)
+{
+ if (exifData.isEmpty() && iptcData.isEmpty())
+ {
+ d->exifWidget->loadFromData(filename, exifData);
+ d->makernoteWidget->loadFromData(filename, exifData);
+ d->iptcWidget->loadFromData(filename, iptcData);
+ d->gpsWidget->loadFromData(filename, exifData);
+ setEnabled(false);
+ return;
+ }
+
+ setEnabled(true);
+
+ d->exifWidget->loadFromData(filename, exifData);
+ d->makernoteWidget->loadFromData(filename, exifData);
+ d->iptcWidget->loadFromData(filename, iptcData);
+ d->gpsWidget->loadFromData(filename, exifData);
+}
+
+} // NameSpace Digikam
+
diff --git a/src/libs/imageproperties/imagepropertiesmetadatatab.h b/src/libs/imageproperties/imagepropertiesmetadatatab.h
new file mode 100644
index 00000000..95b8ecb8
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiesmetadatatab.h
@@ -0,0 +1,68 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : a tab to display metadata information of images
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPROPERTIESMETADATATAB_H
+#define IMAGEPROPERTIESMETADATATAB_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqcstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "navigatebartab.h"
+
+namespace Digikam
+{
+
+class ImagePropertiesMetadataTabPriv;
+
+class DIGIKAM_EXPORT ImagePropertiesMetaDataTab : public NavigateBarTab
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePropertiesMetaDataTab(TQWidget* parent, bool navBar=true);
+ ~ImagePropertiesMetaDataTab();
+
+ void setCurrentURL(const KURL& url=KURL());
+ void setCurrentData(const TQByteArray& exifData=TQByteArray(),
+ const TQByteArray& iptcData=TQByteArray(),
+ const TQString& filename=TQString());
+
+private:
+
+ ImagePropertiesMetadataTabPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEPROPERTIESMETADATATAB_H */
diff --git a/src/libs/imageproperties/imagepropertiessidebar.cpp b/src/libs/imageproperties/imagepropertiessidebar.cpp
new file mode 100644
index 00000000..67254388
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiessidebar.cpp
@@ -0,0 +1,150 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : simple image properties side bar (without support
+ * of digiKam database).
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqrect.h>
+#include <tqsplitter.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "imagepropertiestab.h"
+#include "imagepropertiesmetadatatab.h"
+#include "imagepropertiescolorstab.h"
+#include "imagepropertiessidebar.h"
+#include "imagepropertiessidebar.moc"
+
+namespace Digikam
+{
+
+ImagePropertiesSideBar::ImagePropertiesSideBar(TQWidget *parent, const char *name,
+ TQSplitter *splitter, Side side,
+ bool mimimizedDefault, bool navBar)
+ : Sidebar(parent, name, side, mimimizedDefault)
+{
+ m_image = 0;
+ m_currentRect = TQRect();
+ m_dirtyPropertiesTab = false;
+ m_dirtyMetadataTab = false;
+ m_dirtyColorTab = false;
+
+ m_propertiesTab = new ImagePropertiesTab(parent, navBar);
+ m_metadataTab = new ImagePropertiesMetaDataTab(parent, navBar);
+ m_colorTab = new ImagePropertiesColorsTab(parent, navBar);
+
+ setSplitter(splitter);
+
+ appendTab(m_propertiesTab, SmallIcon("application-vnd.tde.info"), i18n("Properties"));
+ appendTab(m_metadataTab, SmallIcon("exifinfo"), i18n("Metadata"));
+ appendTab(m_colorTab, SmallIcon("blend"), i18n("Colors"));
+
+ connect(this, TQ_SIGNAL(signalChangedTab(TQWidget*)),
+ this, TQ_SLOT(slotChangedTab(TQWidget*)));
+}
+
+ImagePropertiesSideBar::~ImagePropertiesSideBar()
+{
+}
+
+void ImagePropertiesSideBar::itemChanged(const KURL& url, const TQRect &rect, DImg *img)
+{
+ if (!url.isValid())
+ return;
+
+ m_currentURL = url;
+ m_currentRect = rect;
+ m_image = img;
+ m_dirtyPropertiesTab = false;
+ m_dirtyMetadataTab = false;
+ m_dirtyColorTab = false;
+
+ slotChangedTab( getActiveTab() );
+}
+
+void ImagePropertiesSideBar::slotNoCurrentItem(void)
+{
+ m_currentURL = KURL();
+
+ m_propertiesTab->setCurrentURL();
+ m_propertiesTab->setNavigateBarFileName();
+
+ m_metadataTab->setCurrentURL();
+ m_metadataTab->setNavigateBarFileName();
+
+ m_colorTab->setData();
+ m_colorTab->setNavigateBarFileName();
+
+ m_dirtyPropertiesTab = false;
+ m_dirtyMetadataTab = false;
+ m_dirtyColorTab = false;
+}
+
+void ImagePropertiesSideBar::slotImageSelectionChanged(const TQRect &rect)
+{
+ m_currentRect = rect;
+
+ if (m_dirtyColorTab)
+ m_colorTab->setSelection(rect);
+ else
+ slotChangedTab(m_colorTab);
+}
+
+void ImagePropertiesSideBar::slotChangedTab(TQWidget* tab)
+{
+ if (!m_currentURL.isValid())
+ return;
+
+ setCursor(KCursor::waitCursor());
+
+ if (tab == m_propertiesTab && !m_dirtyPropertiesTab)
+ {
+ m_propertiesTab->setCurrentURL(m_currentURL);
+ m_dirtyPropertiesTab = true;
+ }
+ else if (tab == m_metadataTab && !m_dirtyMetadataTab)
+ {
+ m_metadataTab->setCurrentURL(m_currentURL);
+ m_dirtyMetadataTab = true;
+ }
+ else if (tab == m_colorTab && !m_dirtyColorTab)
+ {
+ m_colorTab->setData(m_currentURL, m_currentRect, m_image);
+ m_dirtyColorTab = true;
+ }
+
+ unsetCursor();
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/imageproperties/imagepropertiessidebar.h b/src/libs/imageproperties/imagepropertiessidebar.h
new file mode 100644
index 00000000..43e08df0
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiessidebar.h
@@ -0,0 +1,93 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : simple image properties side bar (without support
+ * of digiKam database).
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPROPERTIESSIDEBAR_H
+#define IMAGEPROPERTIESSIDEBAR_H
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "sidebar.h"
+#include "digikam_export.h"
+
+class TQSplitter;
+class TQWidget;
+class TQRect;
+
+namespace Digikam
+{
+
+class DImg;
+class ImagePropertiesTab;
+class ImagePropertiesMetaDataTab;
+class ImagePropertiesColorsTab;
+
+class DIGIKAM_EXPORT ImagePropertiesSideBar : public Sidebar
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePropertiesSideBar(TQWidget* parent, const char *name, TQSplitter *splitter,
+ Side side=Left, bool mimimizedDefault=false, bool navBar=false);
+
+ ~ImagePropertiesSideBar();
+
+ virtual void itemChanged(const KURL& url, const TQRect &rect = TQRect(), DImg *img = 0);
+
+public slots:
+
+ void slotImageSelectionChanged(const TQRect &rect);
+ virtual void slotNoCurrentItem(void);
+
+
+protected slots:
+
+ virtual void slotChangedTab(TQWidget* tab);
+
+protected:
+
+ bool m_dirtyPropertiesTab;
+ bool m_dirtyMetadataTab;
+ bool m_dirtyColorTab;
+
+ TQRect m_currentRect;
+
+ KURL m_currentURL;
+
+ DImg *m_image;
+
+ ImagePropertiesTab *m_propertiesTab;
+ ImagePropertiesMetaDataTab *m_metadataTab;
+ ImagePropertiesColorsTab *m_colorTab;
+
+};
+
+} // NameSpace Digikam
+
+#endif // IMAGEPROPERTIESSIDEBAR_H
diff --git a/src/libs/imageproperties/imagepropertiessidebarcamgui.cpp b/src/libs/imageproperties/imagepropertiessidebarcamgui.cpp
new file mode 100644
index 00000000..942c33e5
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiessidebarcamgui.cpp
@@ -0,0 +1,209 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-08
+ * Description : simple image properties side bar used by
+ * camera gui.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqsplitter.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "gpiteminfo.h"
+#include "cameraiconview.h"
+#include "cameraiconitem.h"
+#include "cameraitempropertiestab.h"
+#include "imagepropertiesmetadatatab.h"
+#include "statusnavigatebar.h"
+#include "navigatebarwidget.h"
+#include "imagepropertiessidebarcamgui.h"
+#include "imagepropertiessidebarcamgui.moc"
+
+namespace Digikam
+{
+
+class ImagePropertiesSideBarCamGuiPriv
+{
+public:
+
+ ImagePropertiesSideBarCamGuiPriv()
+ {
+ dirtyMetadataTab = false;
+ dirtyCameraItemTab = false;
+ metadataTab = 0;
+ cameraItemTab = 0;
+ itemInfo = 0;
+ cameraView = 0;
+ cameraItem = 0;
+ exifData = TQByteArray();
+ currentURL = KURL();
+ }
+
+ bool dirtyMetadataTab;
+ bool dirtyCameraItemTab;
+
+ TQByteArray exifData;
+
+ KURL currentURL;
+
+ GPItemInfo *itemInfo;
+
+ ImagePropertiesMetaDataTab *metadataTab;
+
+ CameraIconView *cameraView;
+
+ CameraIconViewItem *cameraItem;
+
+ CameraItemPropertiesTab *cameraItemTab;
+};
+
+ImagePropertiesSideBarCamGui::ImagePropertiesSideBarCamGui(TQWidget *parent, const char *name,
+ TQSplitter *splitter, Side side,
+ bool mimimizedDefault)
+ : Sidebar(parent, name, side, mimimizedDefault)
+{
+ d = new ImagePropertiesSideBarCamGuiPriv;
+ d->cameraItemTab = new CameraItemPropertiesTab(parent, true);
+ d->metadataTab = new ImagePropertiesMetaDataTab(parent, true);
+
+ setSplitter(splitter);
+
+ appendTab(d->cameraItemTab, SmallIcon("application-vnd.tde.info"), i18n("Properties"));
+ appendTab(d->metadataTab, SmallIcon("exifinfo"), i18n("Metadata"));
+
+ // ----------------------------------------------------------
+
+ connectNavigateSignals(d->cameraItemTab);
+ connectNavigateSignals(d->metadataTab);
+
+ connect(this, TQ_SIGNAL(signalChangedTab(TQWidget*)),
+ this, TQ_SLOT(slotChangedTab(TQWidget*)));
+}
+
+ImagePropertiesSideBarCamGui::~ImagePropertiesSideBarCamGui()
+{
+ delete d;
+}
+
+void ImagePropertiesSideBarCamGui::connectNavigateSignals(NavigateBarTab *tab)
+{
+ connect(tab, TQ_SIGNAL(signalFirstItem()),
+ this, TQ_SIGNAL(signalFirstItem()));
+
+ connect(tab, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SIGNAL(signalPrevItem()));
+
+ connect(tab, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SIGNAL(signalNextItem()));
+
+ connect(tab, TQ_SIGNAL(signalLastItem()),
+ this, TQ_SIGNAL(signalLastItem()));
+}
+
+void ImagePropertiesSideBarCamGui::itemChanged(GPItemInfo* itemInfo, const KURL& url,
+ const TQByteArray& exifData,
+ CameraIconView* view, CameraIconViewItem* item)
+{
+ if (!itemInfo)
+ return;
+
+ d->exifData = exifData;
+ d->itemInfo = itemInfo;
+ d->currentURL = url;
+ d->dirtyMetadataTab = false;
+ d->dirtyCameraItemTab = false;
+ d->cameraView = view;
+ d->cameraItem = item;
+
+ if (d->exifData.isEmpty())
+ {
+ DMetadata metaData(d->currentURL.path());
+ d->exifData = metaData.getExif();
+ }
+
+ slotChangedTab( getActiveTab() );
+}
+
+void ImagePropertiesSideBarCamGui::slotNoCurrentItem(void)
+{
+ d->itemInfo = 0;
+ d->cameraItem = 0;
+ d->exifData = TQByteArray();
+ d->currentURL = KURL();
+ d->dirtyMetadataTab = false;
+ d->dirtyCameraItemTab = false;
+
+ d->cameraItemTab->setCurrentItem();
+ d->metadataTab->setCurrentURL();
+}
+
+void ImagePropertiesSideBarCamGui::slotChangedTab(TQWidget* tab)
+{
+ if (!d->itemInfo)
+ return;
+
+ setCursor(KCursor::waitCursor());
+
+ if (tab == d->cameraItemTab && !d->dirtyCameraItemTab)
+ {
+ d->cameraItemTab->setCurrentItem(d->itemInfo,
+ d->cameraItem->getDownloadName(), d->exifData,
+ d->currentURL);
+
+ d->dirtyCameraItemTab = true;
+ }
+ else if (tab == d->metadataTab && !d->dirtyMetadataTab)
+ {
+ d->metadataTab->setCurrentData(d->exifData, TQByteArray(),
+ d->itemInfo->name);
+
+ d->dirtyMetadataTab = true;
+ }
+
+ // setting of NavigateBar, common for all tabs
+ NavigateBarTab *navtab = dynamic_cast<NavigateBarTab *>(tab);
+ if (navtab)
+ {
+ int currentItemType = StatusNavigateBar::ItemCurrent;
+ if (d->cameraView->firstItem() == d->cameraItem)
+ currentItemType = StatusNavigateBar::ItemFirst;
+ else if (d->cameraView->lastItem() == d->cameraItem)
+ currentItemType = StatusNavigateBar::ItemLast;
+
+ navtab->setNavigateBarState(currentItemType);
+ navtab->setNavigateBarFileName();
+ }
+ unsetCursor();
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/imageproperties/imagepropertiessidebarcamgui.h b/src/libs/imageproperties/imagepropertiessidebarcamgui.h
new file mode 100644
index 00000000..3c2eafd0
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiessidebarcamgui.h
@@ -0,0 +1,87 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-08
+ * Description : simple image properties side bar used by
+ * camera gui.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPROPERTIESSIDEBARCAMGUI_H
+#define IMAGEPROPERTIESSIDEBARCAMGUI_H
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "sidebar.h"
+#include "digikam_export.h"
+
+class TQSplitter;
+class TQWidget;
+
+namespace Digikam
+{
+
+class GPItemInfo;
+class CameraIconView;
+class CameraIconViewItem;
+class NavigateBarTab;
+class ImagePropertiesSideBarCamGuiPriv;
+
+class DIGIKAM_EXPORT ImagePropertiesSideBarCamGui : public Sidebar
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePropertiesSideBarCamGui(TQWidget* parent, const char *name, TQSplitter *splitter,
+ Side side=Left, bool mimimizedDefault=false);
+
+ ~ImagePropertiesSideBarCamGui();
+
+ void itemChanged(GPItemInfo* itemInfo, const KURL& url, const TQByteArray& exifData=TQByteArray(),
+ CameraIconView* view=0, CameraIconViewItem* item=0);
+
+public slots:
+
+ virtual void slotNoCurrentItem(void);
+
+signals:
+
+ void signalFirstItem(void);
+ void signalPrevItem(void);
+ void signalNextItem(void);
+ void signalLastItem(void);
+
+private slots:
+
+ virtual void slotChangedTab(TQWidget* tab);
+
+private:
+
+ ImagePropertiesSideBarCamGuiPriv* d;
+ void connectNavigateSignals(NavigateBarTab *tab);
+};
+
+} // NameSpace Digikam
+
+#endif // IMAGEPROPERTIESSIDEBARCAMGUI_H
diff --git a/src/libs/imageproperties/imagepropertiessidebardb.cpp b/src/libs/imageproperties/imagepropertiessidebardb.cpp
new file mode 100644
index 00000000..f3dc5f7a
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiessidebardb.cpp
@@ -0,0 +1,368 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : image properties side bar using data from
+ * digiKam database.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqrect.h>
+#include <tqcolor.h>
+#include <tqsplitter.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageinfo.h"
+#include "imagedescedittab.h"
+#include "imageattributeswatch.h"
+#include "imagepropertiestab.h"
+#include "imagepropertiesmetadatatab.h"
+#include "imagepropertiescolorstab.h"
+#include "imagepropertiessidebardb.h"
+#include "imagepropertiessidebardb.moc"
+
+namespace Digikam
+{
+
+class ImagePropertiesSideBarDBPriv
+{
+public:
+
+ ImagePropertiesSideBarDBPriv()
+ {
+ desceditTab = 0;
+ dirtyDesceditTab = false;
+ hasPrevious = false;
+ hasNext = false;
+ hasImageInfoOwnership = false;
+ }
+
+ bool dirtyDesceditTab;
+
+ TQPtrList<ImageInfo> currentInfos;
+
+ ImageDescEditTab *desceditTab;
+
+ bool hasPrevious;
+ bool hasNext;
+
+ bool hasImageInfoOwnership;
+};
+
+ImagePropertiesSideBarDB::ImagePropertiesSideBarDB(TQWidget *parent, const char *name, TQSplitter *splitter,
+ Side side, bool mimimizedDefault)
+ : ImagePropertiesSideBar(parent, name, splitter, side, mimimizedDefault, false)
+{
+ // Navigate bar is disabled by passing false to parent class constructor, and tab constructors
+
+ d = new ImagePropertiesSideBarDBPriv;
+ d->desceditTab = new ImageDescEditTab(parent, false);
+
+ appendTab(d->desceditTab, SmallIcon("imagecomment"), i18n("Captions/Tags"));
+
+ // ----------------------------------------------------------
+
+ connect(this, TQ_SIGNAL(signalChangedTab(TQWidget*)),
+ this, TQ_SLOT(slotChangedTab(TQWidget*)));
+
+ connect(d->desceditTab, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)),
+ this, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)));
+
+ connect(d->desceditTab, TQ_SIGNAL(signalProgressValue(int)),
+ this, TQ_SIGNAL(signalProgressValue(int)));
+
+ ImageAttributesWatch *watch = ImageAttributesWatch::instance();
+
+ connect(watch, TQ_SIGNAL(signalFileMetadataChanged(const KURL &)),
+ this, TQ_SLOT(slotFileMetadataChanged(const KURL &)));
+}
+
+ImagePropertiesSideBarDB::~ImagePropertiesSideBarDB()
+{
+ delete d;
+}
+
+void ImagePropertiesSideBarDB::itemChanged(ImageInfo *info,
+ const TQRect &rect, DImg *img)
+{
+ itemChanged(info->kurl(), info, rect, img);
+}
+
+void ImagePropertiesSideBarDB::itemChanged(const KURL& url, const TQRect &rect, DImg *img)
+{
+ itemChanged(url, 0, rect, img);
+}
+
+void ImagePropertiesSideBarDB::itemChanged(const KURL& url, ImageInfo *info,
+ const TQRect &rect, DImg *img)
+{
+ if ( !url.isValid() )
+ return;
+
+ m_currentURL = url;
+
+ TQPtrList<ImageInfo> list;
+ if (info)
+ list.append(info);
+
+ itemChanged(list, rect, img);
+}
+
+void ImagePropertiesSideBarDB::itemChanged(TQPtrList<ImageInfo> infos)
+{
+ if (infos.isEmpty())
+ return;
+
+ m_currentURL = infos.first()->kurl();
+
+ itemChanged(infos, TQRect(), 0);
+}
+
+void ImagePropertiesSideBarDB::itemChanged(TQPtrList<ImageInfo> infos,
+ const TQRect &rect, DImg *img)
+{
+ m_currentRect = rect;
+ m_image = img;
+
+ // The list _may_ have autoDelete set to true.
+ // Keep old ImageInfo objects from being deleted
+ // until the tab has had the chance to save changes and clear lists.
+ TQPtrList<ImageInfo> temporaryList;
+ if (d->hasImageInfoOwnership)
+ {
+ temporaryList = d->currentInfos;
+ d->hasImageInfoOwnership = false;
+ }
+
+ d->currentInfos = infos;
+
+ m_dirtyPropertiesTab = false;
+ m_dirtyMetadataTab = false;
+ m_dirtyColorTab = false;
+ d->dirtyDesceditTab = false;
+
+ // All tabs that store the ImageInfo list and access it after selection change
+ // must release the image info here. slotChangedTab only handles the active tab!
+ d->desceditTab->setItem();
+
+ slotChangedTab( getActiveTab() );
+
+ // now delete old objects, after slotChangedTab
+ for (ImageInfo *info = temporaryList.first(); info; info = temporaryList.next())
+ {
+ delete info;
+ }
+}
+
+void ImagePropertiesSideBarDB::takeImageInfoOwnership(bool takeOwnership)
+{
+ d->hasImageInfoOwnership = takeOwnership;
+}
+
+
+void ImagePropertiesSideBarDB::slotNoCurrentItem(void)
+{
+ ImagePropertiesSideBar::slotNoCurrentItem();
+
+ // All tabs that store the ImageInfo list and access it after selection change
+ // must release the image info here. slotChangedTab only handles the active tab!
+ d->desceditTab->setItem();
+
+ if (d->hasImageInfoOwnership)
+ {
+ for (ImageInfo *info = d->currentInfos.first(); info; info = d->currentInfos.next())
+ {
+ delete info;
+ }
+ d->hasImageInfoOwnership = false;
+ }
+ d->currentInfos.clear();
+
+ d->desceditTab->setItem();
+ d->dirtyDesceditTab = false;
+}
+
+void ImagePropertiesSideBarDB::populateTags(void)
+{
+ d->desceditTab->populateTags();
+}
+
+void ImagePropertiesSideBarDB::slotChangedTab(TQWidget* tab)
+{
+ setCursor(KCursor::waitCursor());
+
+ // No database data available, for example in the case of image editor is
+ // started from camera GUI.
+ if (d->currentInfos.isEmpty())
+ {
+ if (tab == m_propertiesTab && !m_dirtyPropertiesTab)
+ {
+ m_propertiesTab->setCurrentURL(m_currentURL);
+ m_dirtyPropertiesTab = true;
+ }
+ else if (tab == m_metadataTab && !m_dirtyMetadataTab)
+ {
+ if (m_image)
+ m_metadataTab->setCurrentData(m_image->getExif(), m_image->getIptc(),
+ m_currentURL.fileName());
+ else
+ m_metadataTab->setCurrentURL(m_currentURL);
+
+ m_dirtyMetadataTab = true;
+ }
+ else if (tab == m_colorTab && !m_dirtyColorTab)
+ {
+ m_colorTab->setData(m_currentURL, m_currentRect, m_image);
+ m_dirtyColorTab = true;
+ }
+ else if (tab == d->desceditTab && !d->dirtyDesceditTab)
+ {
+ // Do nothing here. We cannot get data from database !
+ d->desceditTab->setItem();
+ d->dirtyDesceditTab = true;
+ }
+ }
+ else if (d->currentInfos.count() == 1) // Data from database available...
+ {
+ if (tab == m_propertiesTab && !m_dirtyPropertiesTab)
+ {
+ m_propertiesTab->setCurrentURL(m_currentURL);
+ m_dirtyPropertiesTab = true;
+ }
+ else if (tab == m_metadataTab && !m_dirtyMetadataTab)
+ {
+ if (m_image)
+ m_metadataTab->setCurrentData(m_image->getExif(), m_image->getIptc(),
+ m_currentURL.fileName());
+ else
+ m_metadataTab->setCurrentURL(m_currentURL);
+
+ m_dirtyMetadataTab = true;
+ }
+ else if (tab == m_colorTab && !m_dirtyColorTab)
+ {
+ m_colorTab->setData(m_currentURL, m_currentRect, m_image);
+ m_dirtyColorTab = true;
+ }
+ else if (tab == d->desceditTab && !d->dirtyDesceditTab)
+ {
+ d->desceditTab->setItem(d->currentInfos.first());
+ d->dirtyDesceditTab = true;
+ }
+ }
+ else // Data from database available, multiple selection
+ {
+ if (tab == m_propertiesTab && !m_dirtyPropertiesTab)
+ {
+ //TODO
+ m_propertiesTab->setCurrentURL(m_currentURL);
+ m_dirtyPropertiesTab = true;
+ }
+ else if (tab == m_metadataTab && !m_dirtyMetadataTab)
+ {
+ // any ideas?
+ m_metadataTab->setCurrentURL();
+ m_dirtyMetadataTab = true;
+ }
+ else if (tab == m_colorTab && !m_dirtyColorTab)
+ {
+ // any ideas?
+ m_colorTab->setData();
+ m_dirtyColorTab = true;
+ }
+ else if (tab == d->desceditTab && !d->dirtyDesceditTab)
+ {
+ d->desceditTab->setItems(d->currentInfos);
+ d->dirtyDesceditTab = true;
+ }
+ }
+
+ unsetCursor();
+}
+
+void ImagePropertiesSideBarDB::slotFileMetadataChanged(const KURL &url)
+{
+ if (url == m_currentURL)
+ {
+ // trigger an update
+ m_dirtyMetadataTab = false;
+
+ if (getActiveTab() == m_metadataTab)
+ {
+ // update now - reuse code form slotChangedTab
+ slotChangedTab( getActiveTab() );
+ }
+ }
+}
+
+void ImagePropertiesSideBarDB::slotAssignRating(int rating)
+{
+ d->desceditTab->assignRating(rating);
+}
+
+void ImagePropertiesSideBarDB::slotAssignRatingNoStar()
+{
+ d->desceditTab->assignRating(0);
+}
+
+void ImagePropertiesSideBarDB::slotAssignRatingOneStar()
+{
+ d->desceditTab->assignRating(1);
+}
+
+void ImagePropertiesSideBarDB::slotAssignRatingTwoStar()
+{
+ d->desceditTab->assignRating(2);
+}
+
+void ImagePropertiesSideBarDB::slotAssignRatingThreeStar()
+{
+ d->desceditTab->assignRating(3);
+}
+
+void ImagePropertiesSideBarDB::slotAssignRatingFourStar()
+{
+ d->desceditTab->assignRating(4);
+}
+
+void ImagePropertiesSideBarDB::slotAssignRatingFiveStar()
+{
+ d->desceditTab->assignRating(5);
+}
+
+void ImagePropertiesSideBarDB::refreshTagsView()
+{
+ d->desceditTab->refreshTagsView();
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/imageproperties/imagepropertiessidebardb.h b/src/libs/imageproperties/imagepropertiessidebardb.h
new file mode 100644
index 00000000..7037b648
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiessidebardb.h
@@ -0,0 +1,117 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-17
+ * Description : image properties side bar using data from
+ * digiKam database.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPROPERTIESSIDEBARDB_H
+#define IMAGEPROPERTIESSIDEBARDB_H
+
+// TQt includes.
+
+#include <tqptrlist.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "imagepropertiessidebar.h"
+#include "digikam_export.h"
+
+class TQSplitter;
+class TQWidget;
+class TQRect;
+
+namespace Digikam
+{
+
+class DImg;
+class AlbumIconView;
+class AlbumIconItem;
+class ImageInfo;
+class NavigateBarTab;
+class ImagePropertiesSideBarDBPriv;
+
+class DIGIKAM_EXPORT ImagePropertiesSideBarDB : public ImagePropertiesSideBar
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePropertiesSideBarDB(TQWidget* parent, const char *name, TQSplitter *splitter, Side side=Left,
+ bool mimimizedDefault=false);
+
+ ~ImagePropertiesSideBarDB();
+
+ virtual void itemChanged(const KURL& url, const TQRect &rect = TQRect(), DImg *img = 0);
+
+ virtual void itemChanged(ImageInfo *info, const TQRect &rect = TQRect(), DImg *img = 0);
+ virtual void itemChanged(TQPtrList<ImageInfo> infos);
+
+ void takeImageInfoOwnership(bool takeOwnership);
+
+ void populateTags(void);
+ void refreshTagsView();
+
+signals:
+
+ void signalFirstItem(void);
+ void signalPrevItem(void);
+ void signalNextItem(void);
+ void signalLastItem(void);
+ void signalProgressBarMode(int, const TQString&);
+ void signalProgressValue(int);
+
+public slots:
+
+ void slotAssignRating(int rating);
+ void slotAssignRatingNoStar();
+ void slotAssignRatingOneStar();
+ void slotAssignRatingTwoStar();
+ void slotAssignRatingThreeStar();
+ void slotAssignRatingFourStar();
+ void slotAssignRatingFiveStar();
+
+ virtual void slotNoCurrentItem(void);
+
+private slots:
+
+ void slotChangedTab(TQWidget* tab);
+ void slotFileMetadataChanged(const KURL &url);
+
+private:
+
+ void itemChanged(const KURL& url, ImageInfo *info,
+ const TQRect &rect, DImg *img);
+ void itemChanged(TQPtrList<ImageInfo> infos, const TQRect &rect, DImg *img);
+
+private:
+
+ ImagePropertiesSideBarDBPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif // IMAGEPROPERTIESSIDEBARDB_H
diff --git a/src/libs/imageproperties/imagepropertiestab.cpp b/src/libs/imageproperties/imagepropertiestab.cpp
new file mode 100644
index 00000000..7141c039
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiestab.cpp
@@ -0,0 +1,601 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-04-19
+ * Description : A tab to display general image information
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqstyle.h>
+#include <tqfile.h>
+#include <tqlabel.h>
+#include <tqpixmap.h>
+#include <tqfileinfo.h>
+#include <tqwhatsthis.h>
+#include <tqframe.h>
+#include <tqscrollview.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialogbase.h>
+#include <tdefileitem.h>
+#include <ksqueezedtextlabel.h>
+#include <kseparator.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "navigatebarwidget.h"
+#include "imagepropertiestab.h"
+#include "imagepropertiestab.moc"
+
+namespace Digikam
+{
+
+class ImagePropertiesTabPriv
+{
+public:
+
+ ImagePropertiesTabPriv()
+ {
+ settingsArea = 0;
+ title = 0;
+ file = 0;
+ folder = 0;
+ modifiedDate = 0;
+ size = 0;
+ owner = 0;
+ permissions = 0;
+ title2 = 0;
+ mime = 0;
+ dimensions = 0;
+ compression = 0;
+ bitDepth = 0;
+ colorMode = 0;
+ title3 = 0;
+ make = 0;
+ model = 0;
+ photoDate = 0;
+ aperture = 0;
+ focalLength = 0;
+ exposureTime = 0;
+ sensitivity = 0;
+ exposureMode = 0;
+ flash = 0;
+ whiteBalance = 0;
+ labelFile = 0;
+ labelFolder = 0;
+ labelFileModifiedDate = 0;
+ labelFileSize = 0;
+ labelFileOwner = 0;
+ labelFilePermissions = 0;
+ labelImageMime = 0;
+ labelImageDimensions = 0;
+ labelImageCompression = 0;
+ labelImageBitDepth = 0;
+ labelImageColorMode = 0;
+ labelPhotoMake = 0;
+ labelPhotoModel = 0;
+ labelPhotoDateTime = 0;
+ labelPhotoAperture = 0;
+ labelPhotoFocalLength = 0;
+ labelPhotoExposureTime = 0;
+ labelPhotoSensitivity = 0;
+ labelPhotoExposureMode = 0;
+ labelPhotoFlash = 0;
+ labelPhotoWhiteBalance = 0;
+ }
+
+ TQLabel *title;
+ TQLabel *file;
+ TQLabel *folder;
+ TQLabel *modifiedDate;
+ TQLabel *size;
+ TQLabel *owner;
+ TQLabel *permissions;
+
+ TQLabel *title2;
+ TQLabel *mime;
+ TQLabel *dimensions;
+ TQLabel *compression;
+ TQLabel *bitDepth;
+ TQLabel *colorMode;
+
+ TQLabel *title3;
+ TQLabel *make;
+ TQLabel *model;
+ TQLabel *photoDate;
+ TQLabel *aperture;
+ TQLabel *focalLength;
+ TQLabel *exposureTime;
+ TQLabel *sensitivity;
+ TQLabel *exposureMode;
+ TQLabel *flash;
+ TQLabel *whiteBalance;
+
+ TQFrame *settingsArea;
+
+ KSqueezedTextLabel *labelFile;
+ KSqueezedTextLabel *labelFolder;
+ KSqueezedTextLabel *labelFileModifiedDate;
+ KSqueezedTextLabel *labelFileSize;
+ KSqueezedTextLabel *labelFileOwner;
+ KSqueezedTextLabel *labelFilePermissions;
+
+ KSqueezedTextLabel *labelImageMime;
+ KSqueezedTextLabel *labelImageDimensions;
+ KSqueezedTextLabel *labelImageCompression;
+ KSqueezedTextLabel *labelImageBitDepth;
+ KSqueezedTextLabel *labelImageColorMode;
+
+ KSqueezedTextLabel *labelPhotoMake;
+ KSqueezedTextLabel *labelPhotoModel;
+ KSqueezedTextLabel *labelPhotoDateTime;
+ KSqueezedTextLabel *labelPhotoAperture;
+ KSqueezedTextLabel *labelPhotoFocalLength;
+ KSqueezedTextLabel *labelPhotoExposureTime;
+ KSqueezedTextLabel *labelPhotoSensitivity;
+ KSqueezedTextLabel *labelPhotoExposureMode;
+ KSqueezedTextLabel *labelPhotoFlash;
+ KSqueezedTextLabel *labelPhotoWhiteBalance;
+};
+
+ImagePropertiesTab::ImagePropertiesTab(TQWidget* parent, bool navBar)
+ : NavigateBarTab(parent)
+{
+ d = new ImagePropertiesTabPriv;
+
+ setupNavigateBar(navBar);
+
+ TQScrollView *sv = new TQScrollView(this);
+ sv->viewport()->setBackgroundMode(TQt::PaletteBackground);
+ sv->setResizePolicy(TQScrollView::AutoOneFit);
+ sv->setFrameStyle(TQFrame::NoFrame);
+
+ d->settingsArea = new TQFrame(sv->viewport());
+ d->settingsArea->setFrameStyle( TQFrame::StyledPanel | TQFrame::Sunken );
+ d->settingsArea->setLineWidth( style().pixelMetric(TQStyle::PM_DefaultFrameWidth, this) );
+
+ sv->addChild(d->settingsArea);
+ m_navigateBarLayout->addWidget(sv);
+
+ // --------------------------------------------------
+
+ TQGridLayout *settingsLayout = new TQGridLayout(d->settingsArea, 33, 1, KDialog::spacingHint(), 0);
+
+ // --------------------------------------------------
+
+ d->title = new TQLabel(i18n("<big><b>File Properties</b></big>"), d->settingsArea);
+ d->file = new TQLabel(i18n("<b>File</b>:"), d->settingsArea);
+ d->folder = new TQLabel(i18n("<b>Folder</b>:"), d->settingsArea);
+ d->modifiedDate = new TQLabel(i18n("<b>Modified</b>:"), d->settingsArea);
+ d->size = new TQLabel(i18n("<b>Size</b>:"), d->settingsArea);
+ d->owner = new TQLabel(i18n("<b>Owner</b>:"), d->settingsArea);
+ d->permissions = new TQLabel(i18n("<b>Permissions</b>:"), d->settingsArea);
+
+ KSeparator *line = new KSeparator(TQt::Horizontal, d->settingsArea);
+ d->title2 = new TQLabel(i18n("<big><b>Image Properties</b></big>"), d->settingsArea);
+ d->mime = new TQLabel(i18n("<b>Type</b>:"), d->settingsArea);
+ d->dimensions = new TQLabel(i18n("<b>Dimensions</b>:"), d->settingsArea);
+ d->compression = new TQLabel(i18n("<b>Compression</b>:"), d->settingsArea);
+ d->bitDepth = new TQLabel(i18n("<nobr><b>Bit depth</b></nobr>:"), d->settingsArea);
+ d->colorMode = new TQLabel(i18n("<nobr><b>Color mode</b></nobr>:"), d->settingsArea);
+
+ KSeparator *line2 = new KSeparator(TQt::Horizontal, d->settingsArea);
+ d->title3 = new TQLabel(i18n("<big><b>Photograph Properties</b></big>"), d->settingsArea);
+ d->make = new TQLabel(i18n("<b>Make</b>:"), d->settingsArea);
+ d->model = new TQLabel(i18n("<b>Model</b>:"), d->settingsArea);
+ d->photoDate = new TQLabel(i18n("<b>Created</b>:"), d->settingsArea);
+ d->aperture = new TQLabel(i18n("<b>Aperture</b>:"), d->settingsArea);
+ d->focalLength = new TQLabel(i18n("<b>Focal</b>:"), d->settingsArea);
+ d->exposureTime = new TQLabel(i18n("<b>Exposure</b>:"), d->settingsArea);
+ d->sensitivity = new TQLabel(i18n("<b>Sensitivity</b>:"), d->settingsArea);
+ d->exposureMode = new TQLabel(i18n("<nobr><b>Mode/Program</b></nobr>:"), d->settingsArea);
+ d->flash = new TQLabel(i18n("<b>Flash</b>:"), d->settingsArea);
+ d->whiteBalance = new TQLabel(i18n("<nobr><b>White balance</b></nobr>:"), d->settingsArea);
+
+ d->labelFile = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFolder = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFileModifiedDate = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFileSize = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFileOwner = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelFilePermissions = new KSqueezedTextLabel(0, d->settingsArea);
+
+ d->labelImageMime = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelImageDimensions = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelImageCompression = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelImageBitDepth = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelImageColorMode = new KSqueezedTextLabel(0, d->settingsArea);
+
+ d->labelPhotoMake = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoModel = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoDateTime = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoAperture = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoFocalLength = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoExposureTime = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoSensitivity = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoExposureMode = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoFlash = new KSqueezedTextLabel(0, d->settingsArea);
+ d->labelPhotoWhiteBalance = new KSqueezedTextLabel(0, d->settingsArea);
+
+ int hgt = fontMetrics().height()-2;
+ d->title->setAlignment(TQt::AlignCenter);
+ d->file->setMaximumHeight(hgt);
+ d->folder->setMaximumHeight(hgt);
+ d->modifiedDate->setMaximumHeight(hgt);
+ d->size->setMaximumHeight(hgt);
+ d->owner->setMaximumHeight(hgt);
+ d->permissions->setMaximumHeight(hgt);
+ d->labelFile->setMaximumHeight(hgt);
+ d->labelFolder->setMaximumHeight(hgt);
+ d->labelFileModifiedDate->setMaximumHeight(hgt);
+ d->labelFileSize->setMaximumHeight(hgt);
+ d->labelFileOwner->setMaximumHeight(hgt);
+ d->labelFilePermissions->setMaximumHeight(hgt);
+
+ d->title2->setAlignment(TQt::AlignCenter);
+ d->mime->setMaximumHeight(hgt);
+ d->dimensions->setMaximumHeight(hgt);
+ d->compression->setMaximumHeight(hgt);
+ d->bitDepth->setMaximumHeight(hgt);
+ d->colorMode->setMaximumHeight(hgt);
+ d->labelImageMime->setMaximumHeight(hgt);
+ d->labelImageDimensions->setMaximumHeight(hgt);
+ d->labelImageCompression->setMaximumHeight(hgt);
+ d->labelImageBitDepth->setMaximumHeight(hgt);
+ d->labelImageColorMode->setMaximumHeight(hgt);
+
+ d->title3->setAlignment(TQt::AlignCenter);
+ d->make->setMaximumHeight(hgt);
+ d->model->setMaximumHeight(hgt);
+ d->photoDate->setMaximumHeight(hgt);
+ d->aperture->setMaximumHeight(hgt);
+ d->focalLength->setMaximumHeight(hgt);
+ d->exposureTime->setMaximumHeight(hgt);
+ d->sensitivity->setMaximumHeight(hgt);
+ d->exposureMode->setMaximumHeight(hgt);
+ d->flash->setMaximumHeight(hgt);
+ d->whiteBalance->setMaximumHeight(hgt);
+ d->labelPhotoMake->setMaximumHeight(hgt);
+ d->labelPhotoModel->setMaximumHeight(hgt);
+ d->labelPhotoDateTime->setMaximumHeight(hgt);
+ d->labelPhotoAperture->setMaximumHeight(hgt);
+ d->labelPhotoFocalLength->setMaximumHeight(hgt);
+ d->labelPhotoExposureTime->setMaximumHeight(hgt);
+ d->labelPhotoSensitivity->setMaximumHeight(hgt);
+ d->labelPhotoExposureMode->setMaximumHeight(hgt);
+ d->labelPhotoFlash->setMaximumHeight(hgt);
+ d->labelPhotoWhiteBalance->setMaximumHeight(hgt);
+
+ // --------------------------------------------------
+
+ settingsLayout->addMultiCellWidget(d->title, 0, 0, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 1, 1, 0, 1);
+ settingsLayout->addMultiCellWidget(d->file, 2, 2, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFile, 2, 2, 1, 1);
+ settingsLayout->addMultiCellWidget(d->folder, 3, 3, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFolder, 3, 3, 1, 1);
+ settingsLayout->addMultiCellWidget(d->modifiedDate, 4, 4, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFileModifiedDate, 4, 4, 1, 1);
+ settingsLayout->addMultiCellWidget(d->size, 5, 5, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFileSize, 5, 5, 1, 1);
+ settingsLayout->addMultiCellWidget(d->owner, 6, 6, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFileOwner, 6, 6, 1, 1);
+ settingsLayout->addMultiCellWidget(d->permissions, 7, 7, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelFilePermissions, 7, 7, 1, 1);
+
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 8, 8, 0, 1);
+ settingsLayout->addMultiCellWidget(line, 9, 9, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 10, 10, 0, 1);
+
+ settingsLayout->addMultiCellWidget(d->title2, 11, 11, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 12, 12, 0, 1);
+ settingsLayout->addMultiCellWidget(d->mime, 13, 13, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelImageMime, 13, 13, 1, 1);
+ settingsLayout->addMultiCellWidget(d->dimensions, 14, 14, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelImageDimensions, 14, 14, 1, 1);
+ settingsLayout->addMultiCellWidget(d->compression, 15, 15, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelImageCompression, 15, 15, 1, 1);
+ settingsLayout->addMultiCellWidget(d->bitDepth, 16, 16, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelImageBitDepth, 16, 16, 1, 1);
+ settingsLayout->addMultiCellWidget(d->colorMode, 17, 17, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelImageColorMode, 17, 17, 1, 1);
+
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 18, 18, 0, 1);
+ settingsLayout->addMultiCellWidget(line2, 19, 19, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 20, 20, 0, 1);
+
+ settingsLayout->addMultiCellWidget(d->title3, 21, 21, 0, 1);
+ settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 22, 22, 0, 1);
+ settingsLayout->addMultiCellWidget(d->make, 23, 23, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoMake, 23, 23, 1, 1);
+ settingsLayout->addMultiCellWidget(d->model, 24, 24, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoModel, 24, 24, 1, 1);
+ settingsLayout->addMultiCellWidget(d->photoDate, 25, 25, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoDateTime, 25, 25, 1, 1);
+ settingsLayout->addMultiCellWidget(d->aperture, 26, 26, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoAperture, 26, 26, 1, 1);
+ settingsLayout->addMultiCellWidget(d->focalLength, 27, 27, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoFocalLength, 27, 27, 1, 1);
+ settingsLayout->addMultiCellWidget(d->exposureTime, 28, 28, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoExposureTime, 28, 28, 1, 1);
+ settingsLayout->addMultiCellWidget(d->sensitivity, 29, 29, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoSensitivity, 29, 29, 1, 1);
+ settingsLayout->addMultiCellWidget(d->exposureMode, 30, 30, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoExposureMode, 30, 30, 1, 1);
+ settingsLayout->addMultiCellWidget(d->flash, 31, 31, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoFlash, 31, 31, 1, 1);
+ settingsLayout->addMultiCellWidget(d->whiteBalance, 32, 32, 0, 0);
+ settingsLayout->addMultiCellWidget(d->labelPhotoWhiteBalance, 32, 32, 1, 1);
+
+ settingsLayout->setRowStretch(33, 10);
+ settingsLayout->setColStretch(1, 10);
+}
+
+ImagePropertiesTab::~ImagePropertiesTab()
+{
+ delete d;
+}
+
+void ImagePropertiesTab::setCurrentURL(const KURL& url)
+{
+ if (url.isEmpty())
+ {
+ setNavigateBarFileName();
+
+ d->labelFile->setText(TQString());
+ d->labelFolder->setText(TQString());
+ d->labelFileModifiedDate->setText(TQString());
+ d->labelFileSize->setText(TQString());
+ d->labelFileOwner->setText(TQString());
+ d->labelFilePermissions->setText(TQString());
+
+ d->labelImageMime->setText(TQString());
+ d->labelImageDimensions->setText(TQString());
+ d->labelImageCompression->setText(TQString());
+ d->labelImageBitDepth->setText(TQString());
+ d->labelImageColorMode->setText(TQString());
+
+ d->labelPhotoMake->setText(TQString());
+ d->labelPhotoModel->setText(TQString());
+ d->labelPhotoDateTime->setText(TQString());
+ d->labelPhotoAperture->setText(TQString());
+ d->labelPhotoFocalLength->setText(TQString());
+ d->labelPhotoExposureTime->setText(TQString());
+ d->labelPhotoSensitivity->setText(TQString());
+ d->labelPhotoExposureMode->setText(TQString());
+ d->labelPhotoFlash->setText(TQString());
+ d->labelPhotoWhiteBalance->setText(TQString());
+
+ setEnabled(false);
+ return;
+ }
+
+ setEnabled(true);
+
+ TQString str;
+ TQString unavailable(i18n("<i>unavailable</i>"));
+
+ KFileItem fi(KFileItem::Unknown, KFileItem::Unknown, url);
+ TQFileInfo fileInfo(url.path());
+ DMetadata metaData(url.path());
+
+ // -- File system information ------------------------------------------
+
+ d->labelFile->setText(url.fileName());
+ d->labelFolder->setText(url.directory());
+
+ TQDateTime modifiedDate = fileInfo.lastModified();
+ str = TDEGlobal::locale()->formatDateTime(modifiedDate, true, true);
+ d->labelFileModifiedDate->setText(str);
+
+ str = TQString("%1 (%2)").arg(TDEIO::convertSize(fi.size()))
+ .arg(TDEGlobal::locale()->formatNumber(fi.size(), 0));
+ d->labelFileSize->setText(str);
+
+ d->labelFileOwner->setText( TQString("%1 - %2").arg(fi.user()).arg(fi.group()) );
+ d->labelFilePermissions->setText( fi.permissionsString() );
+
+ // -- Image Properties --------------------------------------------------
+
+ TQSize dims;
+ TQString compression, bitDepth, colorMode;
+#if KDCRAW_VERSION < 0x000106
+ TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
+#endif
+ TQString ext = fileInfo.extension(false).upper();
+
+ if (!ext.isEmpty() && rawFilesExt.upper().contains(ext))
+ {
+ d->labelImageMime->setText(i18n("RAW Image"));
+ compression = i18n("None");
+ bitDepth = "48";
+ dims = metaData.getImageDimensions();
+ colorMode = i18n("Uncalibrated");
+ }
+ else
+ {
+ d->labelImageMime->setText(fi.mimeComment());
+
+ KFileMetaInfo meta = fi.metaInfo();
+ if (meta.isValid())
+ {
+ if (meta.containsGroup("Jpeg EXIF Data")) // JPEG image ?
+ {
+ dims = meta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
+
+ TQString quality = meta.group("Jpeg EXIF Data").item("JPEG quality").value().toString();
+ quality.isEmpty() ? compression = unavailable :
+ compression = i18n("JPEG quality %1").arg(quality);
+ bitDepth = meta.group("Jpeg EXIF Data").item("BitDepth").value().toString();
+ colorMode = meta.group("Jpeg EXIF Data").item("ColorMode").value().toString();
+ }
+
+ if (meta.containsGroup("General"))
+ {
+ if (dims.isEmpty() )
+ dims = meta.group("General").item("Dimensions").value().toSize();
+ if (compression.isEmpty())
+ compression = meta.group("General").item("Compression").value().toString();
+ if (bitDepth.isEmpty())
+ bitDepth = meta.group("General").item("BitDepth").value().toString();
+ if (colorMode.isEmpty())
+ colorMode = meta.group("General").item("ColorMode").value().toString();
+ }
+
+ if (meta.containsGroup("Technical"))
+ {
+ if (dims.isEmpty())
+ dims = meta.group("Technical").item("Dimensions").value().toSize();
+ if (compression.isEmpty())
+ compression = meta.group("Technical").item("Compression").value().toString();
+ if (bitDepth.isEmpty())
+ bitDepth = meta.group("Technical").item("BitDepth").value().toString();
+ if (colorMode.isEmpty())
+ colorMode = meta.group("Technical").item("ColorMode").value().toString();
+ }
+ }
+ }
+
+ TQString mpixels;
+ mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2);
+ str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)")
+ .arg(dims.width()).arg(dims.height()).arg(mpixels);
+ d->labelImageDimensions->setText(str);
+ d->labelImageCompression->setText(compression.isEmpty() ? unavailable : compression);
+ d->labelImageBitDepth->setText(bitDepth.isEmpty() ? unavailable : i18n("%1 bpp").arg(bitDepth));
+ d->labelImageColorMode->setText(colorMode.isEmpty() ? unavailable : colorMode);
+
+ // -- Photograph information ------------------------------------------
+ // NOTA: If something is changed here, please updated albumfiletip section too.
+
+ PhotoInfoContainer photoInfo = metaData.getPhotographInformations();
+
+ if (photoInfo.isEmpty())
+ {
+ d->title3->hide();
+ d->make->hide();
+ d->model->hide();
+ d->photoDate->hide();
+ d->aperture->hide();
+ d->focalLength->hide();
+ d->exposureTime->hide();
+ d->sensitivity->hide();
+ d->exposureMode->hide();
+ d->flash->hide();
+ d->whiteBalance->hide();
+ d->labelPhotoMake->hide();
+ d->labelPhotoModel->hide();
+ d->labelPhotoDateTime->hide();
+ d->labelPhotoAperture->hide();
+ d->labelPhotoFocalLength->hide();
+ d->labelPhotoExposureTime->hide();
+ d->labelPhotoSensitivity->hide();
+ d->labelPhotoExposureMode->hide();
+ d->labelPhotoFlash->hide();
+ d->labelPhotoWhiteBalance->hide();
+ }
+ else
+ {
+ d->title3->show();
+ d->make->show();
+ d->model->show();
+ d->photoDate->show();
+ d->aperture->show();
+ d->focalLength->show();
+ d->exposureTime->show();
+ d->sensitivity->show();
+ d->exposureMode->show();
+ d->flash->show();
+ d->whiteBalance->show();
+ d->labelPhotoMake->show();
+ d->labelPhotoModel->show();
+ d->labelPhotoDateTime->show();
+ d->labelPhotoAperture->show();
+ d->labelPhotoFocalLength->show();
+ d->labelPhotoExposureTime->show();
+ d->labelPhotoSensitivity->show();
+ d->labelPhotoExposureMode->show();
+ d->labelPhotoFlash->show();
+ d->labelPhotoWhiteBalance->show();
+ }
+
+ d->labelPhotoMake->setText(photoInfo.make.isEmpty() ? unavailable : photoInfo.make);
+ d->labelPhotoModel->setText(photoInfo.model.isEmpty() ? unavailable : photoInfo.model);
+
+ if (photoInfo.dateTime.isValid())
+ {
+ str = TDEGlobal::locale()->formatDateTime(photoInfo.dateTime, true, true);
+ d->labelPhotoDateTime->setText(str);
+ }
+ else
+ d->labelPhotoDateTime->setText(unavailable);
+
+ d->labelPhotoAperture->setText(photoInfo.aperture.isEmpty() ? unavailable : photoInfo.aperture);
+
+ if (photoInfo.focalLength35mm.isEmpty())
+ d->labelPhotoFocalLength->setText(photoInfo.focalLength.isEmpty() ? unavailable : photoInfo.focalLength);
+ else
+ {
+ str = i18n("%1 (35mm: %2)").arg(photoInfo.focalLength).arg(photoInfo.focalLength35mm);
+ d->labelPhotoFocalLength->setText(str);
+ }
+
+ d->labelPhotoExposureTime->setText(photoInfo.exposureTime.isEmpty() ? unavailable : photoInfo.exposureTime);
+ d->labelPhotoSensitivity->setText(photoInfo.sensitivity.isEmpty() ? unavailable : i18n("%1 ISO").arg(photoInfo.sensitivity));
+
+ if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ d->labelPhotoExposureMode->setText(unavailable);
+ else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ d->labelPhotoExposureMode->setText(photoInfo.exposureMode);
+ else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty())
+ d->labelPhotoExposureMode->setText(photoInfo.exposureProgram);
+ else
+ {
+ str = TQString("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram);
+ d->labelPhotoExposureMode->setText(str);
+ }
+
+ d->labelPhotoFlash->setText(photoInfo.flash.isEmpty() ? unavailable : photoInfo.flash);
+ d->labelPhotoWhiteBalance->setText(photoInfo.whiteBalance.isEmpty() ? unavailable : photoInfo.whiteBalance);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/imageproperties/imagepropertiestab.h b/src/libs/imageproperties/imagepropertiestab.h
new file mode 100644
index 00000000..2a69ce97
--- /dev/null
+++ b/src/libs/imageproperties/imagepropertiestab.h
@@ -0,0 +1,66 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-04-19
+ * Description : A tab to display general image information
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPROPERTIESTAB_H
+#define IMAGEPROPERTIESTAB_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+#include <tqcolor.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "navigatebartab.h"
+
+namespace Digikam
+{
+
+class ImagePropertiesTabPriv;
+
+class DIGIKAM_EXPORT ImagePropertiesTab : public NavigateBarTab
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePropertiesTab(TQWidget* parent, bool navBar=true);
+ ~ImagePropertiesTab();
+
+ void setCurrentURL(const KURL& url=KURL());
+
+private:
+
+ ImagePropertiesTabPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEPROPERTIESTAB_H */
diff --git a/src/libs/imageproperties/navigatebartab.cpp b/src/libs/imageproperties/navigatebartab.cpp
new file mode 100644
index 00000000..d40ae134
--- /dev/null
+++ b/src/libs/imageproperties/navigatebartab.cpp
@@ -0,0 +1,146 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-04
+ * Description : A parent tab class with a navigation bar
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqwidgetstack.h>
+#include <tqlabel.h>
+
+// Local includes.
+
+#include "statusnavigatebar.h"
+#include "navigatebarwidget.h"
+#include "navigatebartab.h"
+#include "navigatebartab.moc"
+
+namespace Digikam
+{
+
+class NavigateBarTabPriv
+{
+public:
+
+ NavigateBarTabPriv()
+ {
+ stack = 0;
+ navigateBar = 0;
+ label = 0;
+ }
+
+ TQWidgetStack *stack;
+
+ TQLabel *label;
+
+ NavigateBarWidget *navigateBar;
+};
+
+NavigateBarTab::NavigateBarTab(TQWidget* parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new NavigateBarTabPriv;
+ m_navigateBarLayout = 0;
+}
+
+NavigateBarTab::~NavigateBarTab()
+{
+ delete d;
+}
+
+void NavigateBarTab::setupNavigateBar(bool withBar)
+{
+ m_navigateBarLayout = new TQVBoxLayout(this);
+
+ if (withBar)
+ {
+ d->stack = new TQWidgetStack(this);
+ m_navigateBarLayout->addWidget(d->stack);
+
+ d->navigateBar = new NavigateBarWidget(d->stack, withBar);
+ d->stack->addWidget(d->navigateBar);
+
+ connect(d->navigateBar, TQ_SIGNAL(signalFirstItem()),
+ this, TQ_SIGNAL(signalFirstItem()));
+
+ connect(d->navigateBar, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SIGNAL(signalPrevItem()));
+
+ connect(d->navigateBar, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SIGNAL(signalNextItem()));
+
+ connect(d->navigateBar, TQ_SIGNAL(signalLastItem()),
+ this, TQ_SIGNAL(signalLastItem()));
+
+ d->label = new TQLabel(d->stack);
+ d->label->setAlignment(TQt::AlignCenter);
+ d->stack->addWidget(d->label);
+ }
+}
+
+void NavigateBarTab::setNavigateBarState(bool hasPrevious, bool hasNext)
+{
+ if (!d->navigateBar)
+ return;
+
+ d->stack->raiseWidget(d->navigateBar);
+
+ if (hasPrevious && hasNext)
+ d->navigateBar->setButtonsState(StatusNavigateBar::ItemCurrent);
+ else if (!hasPrevious && hasNext)
+ d->navigateBar->setButtonsState(StatusNavigateBar::ItemFirst);
+ else if (hasPrevious && !hasNext)
+ d->navigateBar->setButtonsState(StatusNavigateBar::ItemLast);
+ else
+ d->navigateBar->setButtonsState(StatusNavigateBar::NoNavigation);
+}
+
+void NavigateBarTab::setNavigateBarState(int itemType)
+{
+ if (!d->navigateBar)
+ return;
+
+ d->stack->raiseWidget(d->navigateBar);
+ d->navigateBar->setButtonsState(itemType);
+}
+
+void NavigateBarTab::setNavigateBarFileName(const TQString &name)
+{
+ if (!d->navigateBar)
+ return;
+
+ d->stack->raiseWidget(d->navigateBar);
+ d->navigateBar->setFileName(name);
+}
+
+void NavigateBarTab::setLabelText(const TQString &text)
+{
+ if (!d->label)
+ return;
+
+ d->stack->raiseWidget(d->label);
+ d->label->setText(text);
+}
+
+} // NameSpace Digikam
+
diff --git a/src/libs/imageproperties/navigatebartab.h b/src/libs/imageproperties/navigatebartab.h
new file mode 100644
index 00000000..a93090e0
--- /dev/null
+++ b/src/libs/imageproperties/navigatebartab.h
@@ -0,0 +1,85 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-04
+ * Description : A parent tab class with a navigation bar
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef NAVIGATEBARTAB_H
+#define NAVIGATEBARTAB_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "imagepropertiessidebar.h"
+
+class TQVBoxLayout;
+
+namespace Digikam
+{
+
+class NavigateBarWidget;
+class NavigateBarTabPriv;
+
+class DIGIKAM_EXPORT NavigateBarTab : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ NavigateBarTab(TQWidget* parent);
+ ~NavigateBarTab();
+
+ void setNavigateBarState(bool hasPrevious, bool hasNext);
+ void setNavigateBarState(int itemType);
+ void setNavigateBarFileName(const TQString &name = TQString());
+ void setLabelText(const TQString &text);
+
+signals:
+
+ void signalFirstItem(void);
+ void signalPrevItem(void);
+ void signalNextItem(void);
+ void signalLastItem(void);
+
+protected:
+
+ void setupNavigateBar(bool withBar);
+
+protected:
+
+ TQVBoxLayout *m_navigateBarLayout;
+ NavigateBarTabPriv *d;
+
+};
+
+} // NameSpace Digikam
+
+#endif /* NAVIGATEBARTAB_H */
diff --git a/src/libs/imageproperties/navigatebarwidget.cpp b/src/libs/imageproperties/navigatebarwidget.cpp
new file mode 100644
index 00000000..9232c55e
--- /dev/null
+++ b/src/libs/imageproperties/navigatebarwidget.cpp
@@ -0,0 +1,112 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-07
+ * Description : a navigate bar with text
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <ksqueezedtextlabel.h>
+#include <kdialogbase.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "statusnavigatebar.h"
+#include "navigatebarwidget.h"
+#include "navigatebarwidget.moc"
+
+namespace Digikam
+{
+
+class NavigateBarWidgetPriv
+{
+public:
+
+ NavigateBarWidgetPriv()
+ {
+ filename = 0;
+ navBar = 0;
+ }
+
+ KSqueezedTextLabel *filename;
+
+ StatusNavigateBar *navBar;
+};
+
+NavigateBarWidget::NavigateBarWidget(TQWidget *parent, bool show)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new NavigateBarWidgetPriv;
+
+ TQHBoxLayout *lay = new TQHBoxLayout(this);
+ d->navBar = new StatusNavigateBar(this);
+ d->filename = new KSqueezedTextLabel(this);
+
+ lay->addWidget(d->navBar);
+ lay->addSpacing( KDialog::spacingHint() );
+ lay->addWidget(d->filename);
+
+ if (!show) hide();
+
+ connect(d->navBar, TQ_SIGNAL(signalFirstItem()),
+ this, TQ_SIGNAL(signalFirstItem()));
+
+ connect(d->navBar, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SIGNAL(signalPrevItem()));
+
+ connect(d->navBar, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SIGNAL(signalNextItem()));
+
+ connect(d->navBar, TQ_SIGNAL(signalLastItem()),
+ this, TQ_SIGNAL(signalLastItem()));
+}
+
+NavigateBarWidget::~NavigateBarWidget()
+{
+ delete d;
+}
+
+void NavigateBarWidget::setFileName(TQString filename)
+{
+ d->filename->setText(filename);
+}
+
+TQString NavigateBarWidget::getFileName()
+{
+ return (d->filename->text());
+}
+
+void NavigateBarWidget::setButtonsState(int itemType)
+{
+ d->navBar->setButtonsState(itemType);
+}
+
+int NavigateBarWidget::getButtonsState()
+{
+ return (d->navBar->getButtonsState());
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/imageproperties/navigatebarwidget.h b/src/libs/imageproperties/navigatebarwidget.h
new file mode 100644
index 00000000..7be088ed
--- /dev/null
+++ b/src/libs/imageproperties/navigatebarwidget.h
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-07
+ * Description : a navigate bar with text
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef NAVIGATEBARWIDGET_H
+#define NAVIGATEBARWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class NavigateBarWidgetPriv;
+
+class DIGIKAM_EXPORT NavigateBarWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ NavigateBarWidget(TQWidget *parent=0, bool show=true);
+ ~NavigateBarWidget();
+
+ void setFileName(TQString filename=TQString());
+ TQString getFileName();
+ void setButtonsState(int itemType);
+ int getButtonsState();
+
+signals:
+
+ void signalFirstItem(void);
+ void signalPrevItem(void);
+ void signalNextItem(void);
+ void signalLastItem(void);
+
+private :
+
+ NavigateBarWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* NAVIGATEBARWIDGET_H */
diff --git a/src/libs/imageproperties/talbumlistview.cpp b/src/libs/imageproperties/talbumlistview.cpp
new file mode 100644
index 00000000..1369d228
--- /dev/null
+++ b/src/libs/imageproperties/talbumlistview.cpp
@@ -0,0 +1,528 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-18-12
+ * Description : A list view to display digiKam Tags.
+ *
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqheader.h>
+
+// KDE includes.
+
+#include <tdepopupmenu.h>
+#include <tdelocale.h>
+#include <kurl.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdemessagebox.h>
+#include <tdeconfig.h>
+#include <tdeglobalsettings.h>
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "albumiconitem.h"
+#include "albumlister.h"
+#include "albummanager.h"
+#include "albumdb.h"
+#include "album.h"
+#include "albumsettings.h"
+#include "imageinfo.h"
+#include "navigatebarwidget.h"
+#include "dragobjects.h"
+#include "imageattributeswatch.h"
+#include "albumthumbnailloader.h"
+#include "statusprogressbar.h"
+#include "talbumlistview.h"
+#include "talbumlistview.moc"
+
+// X11 includes.
+
+extern "C"
+{
+#include <X11/Xlib.h>
+}
+
+namespace Digikam
+{
+
+TAlbumCheckListItem::TAlbumCheckListItem(TQListView* parent, TAlbum* album)
+ : FolderCheckListItem(parent, album->title(), TQCheckListItem::RadioButtonController)
+{
+ setDragEnabled(true);
+ m_album = album;
+ m_count = 0;
+
+ if (m_album)
+ m_album->setExtraData(listView(), this);
+}
+
+TAlbumCheckListItem::TAlbumCheckListItem(TQCheckListItem* parent, TAlbum* album)
+ : FolderCheckListItem(parent, album->title(), TQCheckListItem::CheckBox)
+{
+ setDragEnabled(true);
+ m_album = album;
+ m_count = 0;
+
+ if (m_album)
+ m_album->setExtraData(listView(), this);
+}
+
+void TAlbumCheckListItem::refresh()
+{
+ if (!m_album) return;
+
+ if (AlbumSettings::instance()->getShowFolderTreeViewItemsCount() &&
+ dynamic_cast<TAlbumCheckListItem*>(parent()))
+ {
+ if (isOpen())
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(m_count));
+ else
+ {
+ int countRecursive = m_count;
+ AlbumIterator it(m_album);
+ while ( it.current() )
+ {
+ TAlbumCheckListItem *item = (TAlbumCheckListItem*)it.current()->extraData(listView());
+ if (item)
+ countRecursive += item->count();
+ ++it;
+ }
+ setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(countRecursive));
+ }
+ }
+ else
+ {
+ setText(0, m_album->title());
+ }
+}
+
+void TAlbumCheckListItem::stateChange(bool val)
+{
+ TQCheckListItem::stateChange(val);
+ ((TAlbumListView*)listView())->stateChanged(this);
+}
+
+void TAlbumCheckListItem::setOpen(bool o)
+{
+ TQListViewItem::setOpen(o);
+ refresh();
+}
+
+TAlbum* TAlbumCheckListItem::album() const
+{
+ return m_album;
+}
+
+int TAlbumCheckListItem::id() const
+{
+ return m_album ? m_album->id() : 0;
+}
+
+void TAlbumCheckListItem::setCount(int count)
+{
+ m_count = count;
+ refresh();
+}
+
+int TAlbumCheckListItem::count()
+{
+ return m_count;
+}
+
+void TAlbumCheckListItem::setStatus(MetadataHub::TagStatus status)
+{
+ if (status == MetadataHub::MetadataDisjoint)
+ {
+ if (type() != TQCheckListItem::RadioButtonController) setTristate(true);
+ setState(TQCheckListItem::NoChange);
+ }
+ else
+ {
+ if (type() != TQCheckListItem::RadioButtonController) setTristate(false);
+ setOn(status.hasTag);
+ }
+}
+
+// ------------------------------------------------------------------------
+
+TAlbumListView::TAlbumListView(TQWidget* parent)
+ : FolderView(parent, "TAlbumListView")
+{
+ addColumn(i18n("Tags"));
+ header()->hide();
+ setResizeMode(TQListView::LastColumn);
+ setRootIsDecorated(true);
+
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalTAlbumsDirty(const TQMap<int, int>&)),
+ this, TQ_SLOT(slotRefresh(const TQMap<int, int>&)));
+}
+
+TAlbumListView::~TAlbumListView()
+{
+ saveViewState();
+}
+
+void TAlbumListView::stateChanged(TAlbumCheckListItem *item)
+{
+ emit signalItemStateChanged(item);
+}
+
+TQDragObject* TAlbumListView::dragObject()
+{
+ TAlbumCheckListItem *item = dynamic_cast<TAlbumCheckListItem*>(dragItem());
+ if(!item)
+ return 0;
+
+ if(!item->parent())
+ return 0;
+
+ TagDrag *t = new TagDrag(item->id(), this);
+ t->setPixmap(*item->pixmap(0));
+
+ return t;
+}
+
+bool TAlbumListView::acceptDrop(const TQDropEvent *e) const
+{
+ TQPoint vp = contentsToViewport(e->pos());
+ TAlbumCheckListItem *itemDrop = dynamic_cast<TAlbumCheckListItem*>(itemAt(vp));
+ TAlbumCheckListItem *itemDrag = dynamic_cast<TAlbumCheckListItem*>(dragItem());
+
+ if(TagDrag::canDecode(e) || TagListDrag::canDecode(e))
+ {
+ // Allow dragging at the root, to move the tag to the root
+ if(!itemDrop)
+ return true;
+
+ // Dragging an item on itself makes no sense
+ if(itemDrag == itemDrop)
+ return false;
+
+ // Dragging a parent on its child makes no sense
+ if(itemDrag && itemDrag->album()->isAncestorOf(itemDrop->album()))
+ return false;
+
+ return true;
+ }
+
+ if (ItemDrag::canDecode(e) && itemDrop && itemDrop->album()->parent())
+ {
+ // Only other possibility is image items being dropped
+ // And allow this only if there is a Tag to be dropped
+ // on and also the Tag is not root.
+ return true;
+ }
+
+ return false;
+}
+
+void TAlbumListView::contentsDropEvent(TQDropEvent *e)
+{
+ TQListView::contentsDropEvent(e);
+
+ if(!acceptDrop(e))
+ return;
+
+ TQPoint vp = contentsToViewport(e->pos());
+ TAlbumCheckListItem *itemDrop = dynamic_cast<TAlbumCheckListItem*>(itemAt(vp));
+
+ if(TagDrag::canDecode(e))
+ {
+ TQByteArray ba = e->encodedData("digikam/tag-id");
+ TQDataStream ds(ba, IO_ReadOnly);
+ int tagID;
+ ds >> tagID;
+
+ AlbumManager* man = AlbumManager::instance();
+ TAlbum* talbum = man->findTAlbum(tagID);
+
+ if(!talbum)
+ return;
+
+ if (talbum == itemDrop->album())
+ return;
+
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("Tags"));
+ popMenu.insertItem(SmallIcon("goto"), i18n("&Move Here"), 10);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem(SmallIcon("cancel"), i18n("C&ancel"), 20);
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+
+ if(id == 10)
+ {
+ TAlbum *newParentTag = 0;
+
+ if (!itemDrop)
+ {
+ // move dragItem to the root
+ newParentTag = AlbumManager::instance()->findTAlbum(0);
+ }
+ else
+ {
+ // move dragItem as child of dropItem
+ newParentTag = itemDrop->album();
+ }
+
+ TQString errMsg;
+ if (!AlbumManager::instance()->moveTAlbum(talbum, newParentTag, errMsg))
+ {
+ KMessageBox::error(this, errMsg);
+ }
+
+ if(itemDrop && !itemDrop->isOpen())
+ itemDrop->setOpen(true);
+ }
+
+ return;
+ }
+
+ if (ItemDrag::canDecode(e))
+ {
+ TAlbum *destAlbum = itemDrop->album();
+ TAlbum *srcAlbum;
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ if (!ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
+ return;
+
+ if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
+ return;
+
+ // all the albumids will be the same
+ int albumID = albumIDs.first();
+ srcAlbum = AlbumManager::instance()->findTAlbum(albumID);
+ if (!srcAlbum)
+ {
+ DWarning() << "Could not find source album of drag"
+ << endl;
+ return;
+ }
+
+ int id = 0;
+ char keys_return[32];
+ XQueryKeymap(x11Display(), keys_return);
+ int key_1 = XKeysymToKeycode(x11Display(), 0xFFE3);
+ int key_2 = XKeysymToKeycode(x11Display(), 0xFFE4);
+
+ if(srcAlbum == destAlbum)
+ {
+ // Setting the dropped image as the album thumbnail
+ // If the ctrl key is pressed, when dropping the image, the
+ // thumbnail is set without a popup menu
+ if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 12;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("Tags"));
+ popMenu.insertItem(i18n("Set as Tag Thumbnail"), 12);
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ if(id == 12)
+ {
+ TQString errMsg;
+ AlbumManager::instance()->updateTAlbumIcon(destAlbum, TQString(),
+ imageIDs.first(), errMsg);
+ }
+ return;
+ }
+
+ // If a ctrl key is pressed while dropping the drag object,
+ // the tag is assigned to the images without showing a
+ // popup menu.
+ if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) ||
+ ((keys_return[key_2 / 8]) && (1 << (key_2 % 8))))
+ {
+ id = 10;
+ }
+ else
+ {
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), i18n("Tags"));
+ popMenu.insertItem( SmallIcon("tag"), i18n("Assign Tag '%1' to Items")
+ .arg(destAlbum->prettyURL()), 10) ;
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+
+ popMenu.setMouseTracking(true);
+ id = popMenu.exec(TQCursor::pos());
+ }
+
+ if (id == 10)
+ {
+ emit signalProgressBarMode(StatusProgressBar::ProgressBarMode,
+ i18n("Assign tag to images. Please wait..."));
+
+ AlbumLister::instance()->blockSignals(true);
+ AlbumManager::instance()->albumDB()->beginTransaction();
+ int i=0;
+ for (TQValueList<int>::const_iterator it = imageIDs.begin();
+ it != imageIDs.end(); ++it)
+ {
+ // create temporary ImageInfo object
+ ImageInfo info(*it);
+
+ MetadataHub hub;
+ hub.load(&info);
+ hub.setTag(destAlbum, true);
+ hub.write(&info, MetadataHub::PartialWrite);
+ hub.write(info.filePath(), MetadataHub::FullWriteIfChanged);
+
+ emit signalProgressValue((int)((i++/(float)imageIDs.count())*100.0));
+ kapp->processEvents();
+ }
+ AlbumLister::instance()->blockSignals(false);
+ AlbumManager::instance()->albumDB()->commitTransaction();
+
+ ImageAttributesWatch::instance()->imagesChanged(destAlbum->id());
+
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ }
+ }
+}
+
+void TAlbumListView::refresh()
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(*it);
+ if (item)
+ item->refresh();
+ ++it;
+ }
+}
+
+void TAlbumListView::slotRefresh(const TQMap<int, int>& tagsStatMap)
+{
+ TQListViewItemIterator it(this);
+
+ while (it.current())
+ {
+ TAlbumCheckListItem* item = dynamic_cast<TAlbumCheckListItem*>(*it);
+ if (item)
+ {
+ if (item->album())
+ {
+ int id = item->id();
+ TQMap<int, int>::const_iterator it2 = tagsStatMap.find(id);
+ if ( it2 != tagsStatMap.end() )
+ item->setCount(it2.data());
+ }
+ }
+ ++it;
+ }
+
+ refresh();
+}
+
+void TAlbumListView::loadViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ int selectedItem = config->readNumEntry("LastSelectedItem", 0);
+
+ TQValueList<int> openFolders;
+ if(config->hasKey("OpenFolders"))
+ {
+ openFolders = config->readIntListEntry("OpenFolders");
+ }
+
+ TAlbumCheckListItem *item = 0;
+ TAlbumCheckListItem *foundItem = 0;
+ TQListViewItemIterator it(this->lastItem());
+
+ for( ; it.current(); --it)
+ {
+ item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ if(!item)
+ continue;
+
+ // Start the album root always open
+ if(openFolders.contains(item->id()) || item->id() == 0)
+ setOpen(item, true);
+ else
+ setOpen(item, false);
+
+ if(item->id() == selectedItem)
+ {
+ // Save the found selected item so that it can be made visible.
+ foundItem = item;
+ }
+ }
+
+ // Important note: this cannot be done inside the previous loop
+ // because opening folders prevents the visibility.
+ // Fixes bug #144815.
+ // (Looks a bit like a bug in TQt to me ...)
+ if (foundItem)
+ {
+ setSelected(foundItem, true);
+ ensureItemVisible(foundItem);
+ }
+}
+
+void TAlbumListView::saveViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name());
+
+ TAlbumCheckListItem *item = dynamic_cast<TAlbumCheckListItem*>(selectedItem());
+ if(item)
+ config->writeEntry("LastSelectedItem", item->id());
+ else
+ config->writeEntry("LastSelectedItem", 0);
+
+ TQValueList<int> openFolders;
+ TQListViewItemIterator it(this);
+ for( ; it.current(); ++it)
+ {
+ item = dynamic_cast<TAlbumCheckListItem*>(it.current());
+ if(item && isOpen(item))
+ openFolders.push_back(item->id());
+ }
+ config->writeEntry("OpenFolders", openFolders);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/imageproperties/talbumlistview.h b/src/libs/imageproperties/talbumlistview.h
new file mode 100644
index 00000000..b483706c
--- /dev/null
+++ b/src/libs/imageproperties/talbumlistview.h
@@ -0,0 +1,109 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-18-12
+ * Description : A list view to display digiKam Tags.
+ *
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2009 by Andi Clemens <andi dot clemens at gmx dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TALBUMLISTVIEW_H
+#define TALBUMLISTVIEW_H
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "metadatahub.h"
+#include "folderitem.h"
+#include "folderview.h"
+
+class TQDropEvent;
+class TQMouseEvent;
+
+namespace Digikam
+{
+class TAlbum;
+
+class DIGIKAM_EXPORT TAlbumCheckListItem : public FolderCheckListItem
+{
+public:
+
+ TAlbumCheckListItem(TQListView* parent, TAlbum* album);
+
+ TAlbumCheckListItem(TQCheckListItem* parent, TAlbum* album);
+
+ void setStatus(MetadataHub::TagStatus status);
+ void refresh();
+ void setOpen(bool o);
+ TAlbum* album() const;
+ int id() const;
+ void setCount(int count);
+ int count();
+
+private :
+
+ void stateChange(bool val);
+
+private :
+
+ int m_count;
+
+ TAlbum *m_album;
+};
+
+// ------------------------------------------------------------------------
+
+class DIGIKAM_EXPORT TAlbumListView : public FolderView
+{
+ TQ_OBJECT
+
+
+public:
+
+ TAlbumListView(TQWidget* parent);
+ ~TAlbumListView();
+
+ void stateChanged(TAlbumCheckListItem *item);
+ void refresh();
+ void loadViewState();
+
+signals:
+
+ void signalProgressBarMode(int, const TQString&);
+ void signalProgressValue(int);
+ void signalItemStateChanged(TAlbumCheckListItem *item);
+
+protected:
+
+ bool acceptDrop(const TQDropEvent *e) const;
+ void contentsDropEvent(TQDropEvent *e);
+
+ TQDragObject* dragObject();
+
+private slots:
+
+ void slotRefresh(const TQMap<int, int>&);
+
+private:
+
+ void saveViewState();
+};
+
+} // NameSpace Digikam
+
+#endif // TALBUMLISTVIEW_H
diff --git a/src/libs/jpegutils/Makefile.am b/src/libs/jpegutils/Makefile.am
new file mode 100644
index 00000000..3c07aa5a
--- /dev/null
+++ b/src/libs/jpegutils/Makefile.am
@@ -0,0 +1,22 @@
+METASOURCES = AUTO
+
+# --enable-final triggers: http://bugs.kde.org/show_bug.cgi?id=126326
+# digikam: camera download: auto-rotated images lose EXIF info ...
+# So make sure nofinal is always used here!
+KDE_OPTIONS = nofinal
+
+INCLUDES = $(all_includes) \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(LIBKEXIV2_CFLAGS)
+
+
+noinst_LTLIBRARIES = libjpegutils.la
+
+libjpegutils_la_SOURCES = jpegutils.cpp transupp.cpp
+
+libjpegutils_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+libjpegutils_la_LIBADD = $(LIBJPEG)
diff --git a/src/libs/jpegutils/jinclude.h b/src/libs/jpegutils/jinclude.h
new file mode 100644
index 00000000..adee51e0
--- /dev/null
+++ b/src/libs/jpegutils/jinclude.h
@@ -0,0 +1,90 @@
+/*
+ *
+ * Copyright (C) 1991-1994, Thomas G. Lane. <tgl@netcom.com>
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file exists to provide a single place to fix any problems with
+ * including the wrong system include files. (Common problems are taken
+ * care of by the standard jconfig symbols, but on really weird systems
+ * you may have to edit this file.)
+ *
+ * NOTE: this file is NOT intended to be included by applications using the
+ * JPEG library. Most applications need only include jpeglib.h.
+ */
+
+
+/* Include auto-config file to find out which system include files we need. */
+
+#include "jconfig.h" /* auto configuration options */
+#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */
+
+/*
+ * We need the NULL macro and size_t typedef.
+ * On an ANSI-conforming system it is sufficient to include <stddef.h>.
+ * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to
+ * pull in <sys/types.h> as well.
+ * Note that the core JPEG library does not require <stdio.h>;
+ * only the default error handler and data source/destination modules do.
+ * But we must pull it in because of the references to FILE in jpeglib.h.
+ * You can remove those references if you want to compile without <stdio.h>.
+ */
+
+#ifdef HAVE_STDDEF_H
+#include <stddef.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef NEED_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include <stdio.h>
+
+/*
+ * We need memory copying and zeroing functions, plus strncpy().
+ * ANSI and System V implementations declare these in <string.h>.
+ * BSD doesn't have the mem() functions, but it does have bcopy()/bzero().
+ * Some systems may declare memset and memcpy in <memory.h>.
+ *
+ * NOTE: we assume the size parameters to these functions are of type size_t.
+ * Change the casts in these macros if not!
+ */
+
+#ifdef NEED_BSD_STRINGS
+
+#include <strings.h>
+#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size))
+#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size))
+
+#else /* not BSD, assume ANSI/SysV string lib */
+
+#include <string.h>
+#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size))
+#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size))
+
+#endif
+
+/*
+ * In ANSI C, and indeed any rational implementation, size_t is also the
+ * type returned by sizeof(). However, it seems there are some irrational
+ * implementations out there, in which sizeof() returns an int even though
+ * size_t is defined as long or unsigned long. To ensure consistent results
+ * we always use this SIZEOF() macro in place of using sizeof() directly.
+ */
+
+#define SIZEOF(object) ((size_t) sizeof(object))
+
+/*
+ * The modules that use fread() and fwrite() always invoke them through
+ * these macros. On some systems you may need to twiddle the argument casts.
+ * CAUTION: argument order is different from underlying functions!
+ */
+
+#define JFREAD(file,buf,sizeofbuf) \
+ ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file)))
+#define JFWRITE(file,buf,sizeofbuf) \
+ ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file)))
diff --git a/src/libs/jpegutils/jpegint.h b/src/libs/jpegutils/jpegint.h
new file mode 100644
index 00000000..bf01aa3e
--- /dev/null
+++ b/src/libs/jpegutils/jpegint.h
@@ -0,0 +1,811 @@
+#if JPEG_LIB_VERSION >= 80
+
+/*
+ * jpegint.h
+ *
+ * Copyright (C) 1991-1997, Thomas G. Lane.
+ * Modified 1997-2009 by Guido Vollbeding.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file provides common declarations for the various JPEG modules.
+ * These declarations are considered internal to the JPEG library; most
+ * applications using the library shouldn't need to include this file.
+ */
+
+
+/* Ensuring definition INT32 */
+#ifndef INT32
+#define INT32 TQ_INT32
+#endif
+
+/* Declarations for both compression & decompression */
+
+typedef enum { /* Operating modes for buffer controllers */
+ JBUF_PASS_THRU, /* Plain stripwise operation */
+ /* Remaining modes require a full-image buffer to have been created */
+ JBUF_SAVE_SOURCE, /* Run source subobject only, save output */
+ JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */
+ JBUF_SAVE_AND_PASS /* Run both subobjects, save output */
+} J_BUF_MODE;
+
+/* Values of global_state field (jdapi.c has some dependencies on ordering!) */
+#define CSTATE_START 100 /* after create_compress */
+#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */
+#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */
+#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */
+#define DSTATE_START 200 /* after create_decompress */
+#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */
+#define DSTATE_READY 202 /* found SOS, ready for start_decompress */
+#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/
+#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */
+#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */
+#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */
+#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */
+#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */
+#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */
+#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */
+
+
+/* Declarations for compression modules */
+
+/* Master control module */
+struct jpeg_comp_master {
+ JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, pass_startup, (j_compress_ptr cinfo));
+ JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean call_pass_startup; /* True if pass_startup must be called */
+ boolean is_last_pass; /* True during last pass */
+};
+
+/* Main buffer control (downsampled-data buffer) */
+struct jpeg_c_main_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, process_data, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
+ JDIMENSION in_rows_avail));
+};
+
+/* Compression preprocessing (downsampling input buffer control) */
+struct jpeg_c_prep_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, pre_process_data, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf,
+ JDIMENSION *in_row_ctr,
+ JDIMENSION in_rows_avail,
+ JSAMPIMAGE output_buf,
+ JDIMENSION *out_row_group_ctr,
+ JDIMENSION out_row_groups_avail));
+};
+
+/* Coefficient buffer control */
+struct jpeg_c_coef_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(boolean, compress_data, (j_compress_ptr cinfo,
+ JSAMPIMAGE input_buf));
+};
+
+/* Colorspace conversion */
+struct jpeg_color_converter {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, color_convert, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
+ JDIMENSION output_row, int num_rows));
+};
+
+/* Downsampling */
+struct jpeg_downsampler {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, downsample, (j_compress_ptr cinfo,
+ JSAMPIMAGE input_buf, JDIMENSION in_row_index,
+ JSAMPIMAGE output_buf,
+ JDIMENSION out_row_group_index));
+
+ boolean need_context_rows; /* TRUE if need rows above & below */
+};
+
+/* Forward DCT (also controls coefficient quantization) */
+typedef JMETHOD(void, forward_DCT_ptr,
+ (j_compress_ptr cinfo, jpeg_component_info * compptr,
+ JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
+ JDIMENSION start_row, JDIMENSION start_col,
+ JDIMENSION num_blocks));
+
+struct jpeg_forward_dct {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ /* It is useful to allow each component to have a separate FDCT method. */
+ forward_DCT_ptr forward_DCT[MAX_COMPONENTS];
+};
+
+/* Entropy encoding */
+struct jpeg_entropy_encoder {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics));
+ JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data));
+ JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
+};
+
+/* Marker writing */
+struct jpeg_marker_writer {
+ JMETHOD(void, write_file_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_frame_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_scan_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo));
+ JMETHOD(void, write_tables_only, (j_compress_ptr cinfo));
+ /* These routines are exported to allow insertion of extra markers */
+ /* Probably only COM and APPn markers should be written this way */
+ JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker,
+ unsigned int datalen));
+ JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val));
+};
+
+
+/* Declarations for decompression modules */
+
+/* Master control module */
+struct jpeg_decomp_master {
+ JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */
+};
+
+/* Input control module */
+struct jpeg_input_controller {
+ JMETHOD(int, consume_input, (j_decompress_ptr cinfo));
+ JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo));
+ JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean has_multiple_scans; /* True if file has multiple scans */
+ boolean eoi_reached; /* True when EOI has been consumed */
+};
+
+/* Main buffer control (downsampled-data buffer) */
+struct jpeg_d_main_controller {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, process_data, (j_decompress_ptr cinfo,
+ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+};
+
+/* Coefficient buffer control */
+struct jpeg_d_coef_controller {
+ JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
+ JMETHOD(int, consume_data, (j_decompress_ptr cinfo));
+ JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo));
+ JMETHOD(int, decompress_data, (j_decompress_ptr cinfo,
+ JSAMPIMAGE output_buf));
+ /* Pointer to array of coefficient virtual arrays, or NULL if none */
+ jvirt_barray_ptr *coef_arrays;
+};
+
+/* Decompression postprocessing (color quantization buffer control) */
+struct jpeg_d_post_controller {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, post_process_data, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf,
+ JDIMENSION *in_row_group_ctr,
+ JDIMENSION in_row_groups_avail,
+ JSAMPARRAY output_buf,
+ JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+};
+
+/* Marker reading & parsing */
+struct jpeg_marker_reader {
+ JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo));
+ /* Read markers until SOS or EOI.
+ * Returns same codes as are defined for jpeg_consume_input:
+ * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI.
+ */
+ JMETHOD(int, read_markers, (j_decompress_ptr cinfo));
+ /* Read a restart marker --- exported for use by entropy decoder only */
+ jpeg_marker_parser_method read_restart_marker;
+
+ /* State of marker reader --- nominally internal, but applications
+ * supplying COM or APPn handlers might like to know the state.
+ */
+ boolean saw_SOI; /* found SOI? */
+ boolean saw_SOF; /* found SOF? */
+ int next_restart_num; /* next restart number expected (0-7) */
+ unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */
+};
+
+/* Entropy decoding */
+struct jpeg_entropy_decoder {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo,
+ JBLOCKROW *MCU_data));
+};
+
+/* Inverse DCT (also performs dequantization) */
+typedef JMETHOD(void, inverse_DCT_method_ptr,
+ (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+ JCOEFPTR coef_block,
+ JSAMPARRAY output_buf, JDIMENSION output_col));
+
+struct jpeg_inverse_dct {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ /* It is useful to allow each component to have a separate IDCT method. */
+ inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS];
+};
+
+/* Upsampling (note that upsampler must also call color converter) */
+struct jpeg_upsampler {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, upsample, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf,
+ JDIMENSION *in_row_group_ctr,
+ JDIMENSION in_row_groups_avail,
+ JSAMPARRAY output_buf,
+ JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+
+ boolean need_context_rows; /* TRUE if need rows above & below */
+};
+
+/* Colorspace conversion */
+struct jpeg_color_deconverter {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, color_convert, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf, JDIMENSION input_row,
+ JSAMPARRAY output_buf, int num_rows));
+};
+
+/* Color quantization or color precision reduction */
+struct jpeg_color_quantizer {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan));
+ JMETHOD(void, color_quantize, (j_decompress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPARRAY output_buf,
+ int num_rows));
+ JMETHOD(void, finish_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, new_color_map, (j_decompress_ptr cinfo));
+};
+
+
+/* Miscellaneous useful macros */
+
+#undef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#undef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+
+/* We assume that right shift corresponds to signed division by 2 with
+ * rounding towards minus infinity. This is correct for typical "arithmetic
+ * shift" instructions that shift in copies of the sign bit. But some
+ * C compilers implement >> with an unsigned shift. For these machines you
+ * must define RIGHT_SHIFT_IS_UNSIGNED.
+ * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity.
+ * It is only applied with constant shift counts. SHIFT_TEMPS must be
+ * included in the variables of any routine using RIGHT_SHIFT.
+ */
+
+#ifdef RIGHT_SHIFT_IS_UNSIGNED
+#define SHIFT_TEMPS INT32 shift_temp;
+#define RIGHT_SHIFT(x,shft) \
+ ((shift_temp = (x)) < 0 ? \
+ (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \
+ (shift_temp >> (shft)))
+#else
+#define SHIFT_TEMPS
+#define RIGHT_SHIFT(x,shft) ((x) >> (shft))
+#endif
+
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jinit_compress_master jICompress
+#define jinit_c_master_control jICMaster
+#define jinit_c_main_controller jICMainC
+#define jinit_c_prep_controller jICPrepC
+#define jinit_c_coef_controller jICCoefC
+#define jinit_color_converter jICColor
+#define jinit_downsampler jIDownsampler
+#define jinit_forward_dct jIFDCT
+#define jinit_huff_encoder jIHEncoder
+#define jinit_arith_encoder jIAEncoder
+#define jinit_marker_writer jIMWriter
+#define jinit_master_decompress jIDMaster
+#define jinit_d_main_controller jIDMainC
+#define jinit_d_coef_controller jIDCoefC
+#define jinit_d_post_controller jIDPostC
+#define jinit_input_controller jIInCtlr
+#define jinit_marker_reader jIMReader
+#define jinit_huff_decoder jIHDecoder
+#define jinit_arith_decoder jIADecoder
+#define jinit_inverse_dct jIIDCT
+#define jinit_upsampler jIUpsampler
+#define jinit_color_deconverter jIDColor
+#define jinit_1pass_quantizer jI1Quant
+#define jinit_2pass_quantizer jI2Quant
+#define jinit_merged_upsampler jIMUpsampler
+#define jinit_memory_mgr jIMemMgr
+#define jdiv_round_up jDivRound
+#define jround_up jRound
+#define jcopy_sample_rows jCopySamples
+#define jcopy_block_row jCopyBlocks
+#define jzero_far jZeroFar
+#define jpeg_zigzag_order jZIGTable
+#define jpeg_natural_order jZAGTable
+#define jpeg_natural_order7 jZAGTable7
+#define jpeg_natural_order6 jZAGTable6
+#define jpeg_natural_order5 jZAGTable5
+#define jpeg_natural_order4 jZAGTable4
+#define jpeg_natural_order3 jZAGTable3
+#define jpeg_natural_order2 jZAGTable2
+#define jpeg_aritab jAriTab
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+
+/* Compression module initialization routines */
+EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo,
+ boolean transcode_only));
+EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_arith_encoder JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo));
+/* Decompression module initialization routines */
+EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_arith_decoder JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo));
+/* Memory manager initialization */
+EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo));
+
+/* Utility routines in jutils.c */
+EXTERN(long) jdiv_round_up JPP((long a, long b));
+EXTERN(long) jround_up JPP((long a, long b));
+EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row,
+ JSAMPARRAY output_array, int dest_row,
+ int num_rows, JDIMENSION num_cols));
+EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row,
+ JDIMENSION num_blocks));
+EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero));
+/* Constant tables in jutils.c */
+#if 0 /* This table is not actually needed in v6a */
+extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */
+#endif
+extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */
+extern const int jpeg_natural_order7[]; /* zz to natural order for 7x7 block */
+extern const int jpeg_natural_order6[]; /* zz to natural order for 6x6 block */
+extern const int jpeg_natural_order5[]; /* zz to natural order for 5x5 block */
+extern const int jpeg_natural_order4[]; /* zz to natural order for 4x4 block */
+extern const int jpeg_natural_order3[]; /* zz to natural order for 3x3 block */
+extern const int jpeg_natural_order2[]; /* zz to natural order for 2x2 block */
+
+/* Arithmetic coding probability estimation tables in jaricom.c */
+extern const INT32 jpeg_aritab[];
+
+/* Suppress undefined-structure complaints if necessary. */
+
+#ifdef INCOMPLETE_TYPES_BROKEN
+#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */
+struct jvirt_sarray_control { long dummy; };
+struct jvirt_barray_control { long dummy; };
+#endif
+#endif /* INCOMPLETE_TYPES_BROKEN */
+
+
+#else // JPEG_LIB_VERSION >= 80
+
+/*
+ *
+ * Copyright (C) 1991-1997, Thomas G. Lane. <tgl@netcom.com>
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file provides common declarations for the various JPEG modules.
+ * These declarations are considered internal to the JPEG library; most
+ * applications using the library shouldn't need to include this file.
+ */
+
+
+/* Declarations for both compression & decompression */
+
+typedef enum { /* Operating modes for buffer controllers */
+ JBUF_PASS_THRU, /* Plain stripwise operation */
+ /* Remaining modes require a full-image buffer to have been created */
+ JBUF_SAVE_SOURCE, /* Run source subobject only, save output */
+ JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */
+ JBUF_SAVE_AND_PASS /* Run both subobjects, save output */
+} J_BUF_MODE;
+
+/* Values of global_state field (jdapi.c has some dependencies on ordering!) */
+#define CSTATE_START 100 /* after create_compress */
+#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */
+#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */
+#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */
+#define DSTATE_START 200 /* after create_decompress */
+#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */
+#define DSTATE_READY 202 /* found SOS, ready for start_decompress */
+#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/
+#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */
+#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */
+#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */
+#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */
+#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */
+#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */
+#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */
+
+
+/* Declarations for compression modules */
+
+/* Master control module */
+struct jpeg_comp_master {
+ JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, pass_startup, (j_compress_ptr cinfo));
+ JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean call_pass_startup; /* True if pass_startup must be called */
+ boolean is_last_pass; /* True during last pass */
+};
+
+/* Main buffer control (downsampled-data buffer) */
+struct jpeg_c_main_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, process_data, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
+ JDIMENSION in_rows_avail));
+};
+
+/* Compression preprocessing (downsampling input buffer control) */
+struct jpeg_c_prep_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, pre_process_data, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf,
+ JDIMENSION *in_row_ctr,
+ JDIMENSION in_rows_avail,
+ JSAMPIMAGE output_buf,
+ JDIMENSION *out_row_group_ctr,
+ JDIMENSION out_row_groups_avail));
+};
+
+/* Coefficient buffer control */
+struct jpeg_c_coef_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(boolean, compress_data, (j_compress_ptr cinfo,
+ JSAMPIMAGE input_buf));
+};
+
+/* Colorspace conversion */
+struct jpeg_color_converter {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, color_convert, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
+ JDIMENSION output_row, int num_rows));
+};
+
+/* Downsampling */
+struct jpeg_downsampler {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, downsample, (j_compress_ptr cinfo,
+ JSAMPIMAGE input_buf, JDIMENSION in_row_index,
+ JSAMPIMAGE output_buf,
+ JDIMENSION out_row_group_index));
+
+ boolean need_context_rows; /* TRUE if need rows above & below */
+};
+
+/* Forward DCT (also controls coefficient quantization) */
+struct jpeg_forward_dct {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ /* perhaps this should be an array??? */
+ JMETHOD(void, forward_DCT, (j_compress_ptr cinfo,
+ jpeg_component_info * compptr,
+ JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
+ JDIMENSION start_row, JDIMENSION start_col,
+ JDIMENSION num_blocks));
+};
+
+/* Entropy encoding */
+struct jpeg_entropy_encoder {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics));
+ JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data));
+ JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
+};
+
+/* Marker writing */
+struct jpeg_marker_writer {
+ JMETHOD(void, write_file_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_frame_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_scan_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo));
+ JMETHOD(void, write_tables_only, (j_compress_ptr cinfo));
+ /* These routines are exported to allow insertion of extra markers */
+ /* Probably only COM and APPn markers should be written this way */
+ JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker,
+ unsigned int datalen));
+ JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val));
+};
+
+
+/* Declarations for decompression modules */
+
+/* Master control module */
+struct jpeg_decomp_master {
+ JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */
+};
+
+/* Input control module */
+struct jpeg_input_controller {
+ JMETHOD(int, consume_input, (j_decompress_ptr cinfo));
+ JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo));
+ JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean has_multiple_scans; /* True if file has multiple scans */
+ boolean eoi_reached; /* True when EOI has been consumed */
+};
+
+/* Main buffer control (downsampled-data buffer) */
+struct jpeg_d_main_controller {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, process_data, (j_decompress_ptr cinfo,
+ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+};
+
+/* Coefficient buffer control */
+struct jpeg_d_coef_controller {
+ JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
+ JMETHOD(int, consume_data, (j_decompress_ptr cinfo));
+ JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo));
+ JMETHOD(int, decompress_data, (j_decompress_ptr cinfo,
+ JSAMPIMAGE output_buf));
+ /* Pointer to array of coefficient virtual arrays, or NULL if none */
+ jvirt_barray_ptr *coef_arrays;
+};
+
+/* Decompression postprocessing (color quantization buffer control) */
+struct jpeg_d_post_controller {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, post_process_data, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf,
+ JDIMENSION *in_row_group_ctr,
+ JDIMENSION in_row_groups_avail,
+ JSAMPARRAY output_buf,
+ JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+};
+
+/* Marker reading & parsing */
+struct jpeg_marker_reader {
+ JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo));
+ /* Read markers until SOS or EOI.
+ * Returns same codes as are defined for jpeg_consume_input:
+ * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI.
+ */
+ JMETHOD(int, read_markers, (j_decompress_ptr cinfo));
+ /* Read a restart marker --- exported for use by entropy decoder only */
+ jpeg_marker_parser_method read_restart_marker;
+
+ /* State of marker reader --- nominally internal, but applications
+ * supplying COM or APPn handlers might like to know the state.
+ */
+ boolean saw_SOI; /* found SOI? */
+ boolean saw_SOF; /* found SOF? */
+ int next_restart_num; /* next restart number expected (0-7) */
+ unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */
+};
+
+/* Entropy decoding */
+struct jpeg_entropy_decoder {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo,
+ JBLOCKROW *MCU_data));
+
+ /* This is here to share code between baseline and progressive decoders; */
+ /* other modules probably should not use it */
+ boolean insufficient_data; /* set TRUE after emitting warning */
+};
+
+/* Inverse DCT (also performs dequantization) */
+typedef JMETHOD(void, inverse_DCT_method_ptr,
+ (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+ JCOEFPTR coef_block,
+ JSAMPARRAY output_buf, JDIMENSION output_col));
+
+struct jpeg_inverse_dct {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ /* It is useful to allow each component to have a separate IDCT method. */
+ inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS];
+};
+
+/* Upsampling (note that upsampler must also call color converter) */
+struct jpeg_upsampler {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, upsample, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf,
+ JDIMENSION *in_row_group_ctr,
+ JDIMENSION in_row_groups_avail,
+ JSAMPARRAY output_buf,
+ JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+
+ boolean need_context_rows; /* TRUE if need rows above & below */
+};
+
+/* Colorspace conversion */
+struct jpeg_color_deconverter {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, color_convert, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf, JDIMENSION input_row,
+ JSAMPARRAY output_buf, int num_rows));
+};
+
+/* Color quantization or color precision reduction */
+struct jpeg_color_quantizer {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan));
+ JMETHOD(void, color_quantize, (j_decompress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPARRAY output_buf,
+ int num_rows));
+ JMETHOD(void, finish_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, new_color_map, (j_decompress_ptr cinfo));
+};
+
+
+/* Miscellaneous useful macros */
+
+#undef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#undef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+
+/* We assume that right shift corresponds to signed division by 2 with
+ * rounding towards minus infinity. This is correct for typical "arithmetic
+ * shift" instructions that shift in copies of the sign bit. But some
+ * C compilers implement >> with an unsigned shift. For these machines you
+ * must define RIGHT_SHIFT_IS_UNSIGNED.
+ * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity.
+ * It is only applied with constant shift counts. SHIFT_TEMPS must be
+ * included in the variables of any routine using RIGHT_SHIFT.
+ */
+
+#ifdef RIGHT_SHIFT_IS_UNSIGNED
+#define SHIFT_TEMPS INT32 shift_temp;
+#define RIGHT_SHIFT(x,shft) \
+ ((shift_temp = (x)) < 0 ? \
+ (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \
+ (shift_temp >> (shft)))
+#else
+#define SHIFT_TEMPS
+#define RIGHT_SHIFT(x,shft) ((x) >> (shft))
+#endif
+
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jinit_compress_master jICompress
+#define jinit_c_master_control jICMaster
+#define jinit_c_main_controller jICMainC
+#define jinit_c_prep_controller jICPrepC
+#define jinit_c_coef_controller jICCoefC
+#define jinit_color_converter jICColor
+#define jinit_downsampler jIDownsampler
+#define jinit_forward_dct jIFDCT
+#define jinit_huff_encoder jIHEncoder
+#define jinit_phuff_encoder jIPHEncoder
+#define jinit_marker_writer jIMWriter
+#define jinit_master_decompress jIDMaster
+#define jinit_d_main_controller jIDMainC
+#define jinit_d_coef_controller jIDCoefC
+#define jinit_d_post_controller jIDPostC
+#define jinit_input_controller jIInCtlr
+#define jinit_marker_reader jIMReader
+#define jinit_huff_decoder jIHDecoder
+#define jinit_phuff_decoder jIPHDecoder
+#define jinit_inverse_dct jIIDCT
+#define jinit_upsampler jIUpsampler
+#define jinit_color_deconverter jIDColor
+#define jinit_1pass_quantizer jI1Quant
+#define jinit_2pass_quantizer jI2Quant
+#define jinit_merged_upsampler jIMUpsampler
+#define jinit_memory_mgr jIMemMgr
+#define jdiv_round_up jDivRound
+#define jround_up jRound
+#define jcopy_sample_rows jCopySamples
+#define jcopy_block_row jCopyBlocks
+#define jzero_far jZeroFar
+#define jpeg_zigzag_order jZIGTable
+#define jpeg_natural_order jZAGTable
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+
+/* Compression module initialization routines */
+EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo,
+ boolean transcode_only));
+EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo));
+/* Decompression module initialization routines */
+EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo));
+/* Memory manager initialization */
+EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo));
+
+/* Utility routines in jutils.c */
+EXTERN(long) jdiv_round_up JPP((long a, long b));
+EXTERN(long) jround_up JPP((long a, long b));
+EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row,
+ JSAMPARRAY output_array, int dest_row,
+ int num_rows, JDIMENSION num_cols));
+EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row,
+ JDIMENSION num_blocks));
+EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero));
+/* Constant tables in jutils.c */
+#if 0 /* This table is not actually needed in v6a */
+extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */
+#endif
+extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */
+
+/* Suppress undefined-structure complaints if necessary. */
+
+#ifdef INCOMPLETE_TYPES_BROKEN
+#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */
+struct jvirt_sarray_control { long dummy; };
+struct jvirt_barray_control { long dummy; };
+#endif
+#endif /* INCOMPLETE_TYPES_BROKEN */
+
+#endif // JPEG_LIB_VERSION >= 80
diff --git a/src/libs/jpegutils/jpegutils.cpp b/src/libs/jpegutils/jpegutils.cpp
new file mode 100644
index 00000000..2f2aabb6
--- /dev/null
+++ b/src/libs/jpegutils/jpegutils.cpp
@@ -0,0 +1,532 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-29
+ * Description : perform lossless rotation/flip to JPEG file
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define XMD_H
+
+// C++ includes.
+
+#include <cstdio>
+#include <cstdlib>
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utime.h>
+#include <setjmp.h>
+#include <jpeglib.h>
+}
+
+// TQt includes.
+
+#include <tqcstring.h>
+#include <tqfile.h>
+#include <tqfileinfo.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "transupp.h"
+#include "jpegutils.h"
+
+namespace Digikam
+{
+
+// To manage Errors/Warnings handling provide by libjpeg
+
+//#define ENABLE_DEBUG_MESSAGES
+
+struct jpegutils_jpeg_error_mgr : public jpeg_error_mgr
+{
+ jmp_buf setjmp_buffer;
+};
+
+static void jpegutils_jpeg_error_exit(j_common_ptr cinfo);
+static void jpegutils_jpeg_emit_message(j_common_ptr cinfo, int msg_level);
+static void jpegutils_jpeg_output_message(j_common_ptr cinfo);
+
+static void jpegutils_jpeg_error_exit(j_common_ptr cinfo)
+{
+ jpegutils_jpeg_error_mgr* myerr = (jpegutils_jpeg_error_mgr*) cinfo->err;
+
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << buffer << endl;
+#endif
+
+ longjmp(myerr->setjmp_buffer, 1);
+}
+
+static void jpegutils_jpeg_emit_message(j_common_ptr cinfo, int msg_level)
+{
+ Q_UNUSED(msg_level)
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << buffer << " (" << msg_level << ")" << endl;
+#endif
+}
+
+static void jpegutils_jpeg_output_message(j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+
+#ifdef ENABLE_DEBUG_MESSAGES
+ DDebug() << k_funcinfo << buffer << endl;
+#endif
+}
+
+bool loadJPEGScaled(TQImage& image, const TQString& path, int maximumSize)
+{
+ TQString format = TQImageIO::imageFormat(path);
+ if (format !="JPEG") return false;
+
+ FILE* inputFile=fopen(TQFile::encodeName(path), "rb");
+ if(!inputFile)
+ return false;
+
+ struct jpeg_decompress_struct cinfo;
+ struct jpegutils_jpeg_error_mgr jerr;
+
+ // JPEG error handling - thanks to Marcus Meissner
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.err->error_exit = jpegutils_jpeg_error_exit;
+ cinfo.err->emit_message = jpegutils_jpeg_emit_message;
+ cinfo.err->output_message = jpegutils_jpeg_output_message;
+
+ if (setjmp(jerr.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&cinfo);
+ fclose(inputFile);
+ return false;
+ }
+
+ jpeg_create_decompress(&cinfo);
+ jpeg_stdio_src(&cinfo, inputFile);
+ jpeg_read_header(&cinfo, true);
+
+ int imgSize = TQMAX(cinfo.image_width, cinfo.image_height);
+
+ // libjpeg supports 1/1, 1/2, 1/4, 1/8
+ int scale=1;
+ while(maximumSize*scale*2<=imgSize)
+ {
+ scale*=2;
+ }
+ if(scale>8) scale=8;
+
+ cinfo.scale_num=1;
+ cinfo.scale_denom=scale;
+
+ switch (cinfo.jpeg_color_space)
+ {
+ case JCS_UNKNOWN:
+ break;
+ case JCS_GRAYSCALE:
+ case JCS_RGB:
+ case JCS_YCbCr:
+ cinfo.out_color_space = JCS_RGB;
+ break;
+ case JCS_CMYK:
+ case JCS_YCCK:
+ cinfo.out_color_space = JCS_CMYK;
+ break;
+ }
+
+ jpeg_start_decompress(&cinfo);
+
+ TQImage img;
+
+ // We only take RGB with 1 or 3 components, or CMYK with 4 components
+ if (!(
+ (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1))
+ || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4)
+ ))
+ {
+ jpeg_destroy_decompress(&cinfo);
+ fclose(inputFile);
+ return false;
+ }
+
+ switch(cinfo.output_components)
+ {
+ case 3:
+ case 4:
+ img.create( cinfo.output_width, cinfo.output_height, 32 );
+ break;
+ case 1: // B&W image
+ img.create( cinfo.output_width, cinfo.output_height, 8, 256 );
+ for (int i = 0 ; i < 256 ; i++)
+ img.setColor(i, tqRgb(i, i, i));
+ break;
+ }
+
+ uchar** lines = img.jumpTable();
+ while (cinfo.output_scanline < cinfo.output_height)
+ jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, cinfo.output_height);
+
+ jpeg_finish_decompress(&cinfo);
+
+ // Expand 24->32 bpp
+ if ( cinfo.output_components == 3 )
+ {
+ for (uint j=0; j<cinfo.output_height; j++)
+ {
+ uchar *in = img.scanLine(j) + cinfo.output_width*3;
+ TQRgb *out = (TQRgb*)( img.scanLine(j) );
+
+ for (uint i=cinfo.output_width; i--; )
+ {
+ in -= 3;
+ out[i] = tqRgb(in[0], in[1], in[2]);
+ }
+ }
+ }
+ else if ( cinfo.output_components == 4 )
+ {
+ // CMYK conversion
+ for (uint j=0; j<cinfo.output_height; j++)
+ {
+ uchar *in = img.scanLine(j) + cinfo.output_width*4;
+ TQRgb *out = (TQRgb*)( img.scanLine(j) );
+
+ for (uint i=cinfo.output_width; i--; )
+ {
+ in -= 4;
+ int k = in[3];
+ out[i] = tqRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255);
+ }
+ }
+ }
+
+ int newMax = TQMAX(cinfo.output_width, cinfo.output_height);
+ int newx = maximumSize*cinfo.output_width / newMax;
+ int newy = maximumSize*cinfo.output_height / newMax;
+
+ jpeg_destroy_decompress(&cinfo);
+ fclose(inputFile);
+
+ image = img;
+
+ return true;
+}
+
+bool exifRotate(const TQString& file, const TQString& documentName)
+{
+ TQFileInfo fi(file);
+ if (!fi.exists())
+ {
+ DDebug() << "ExifRotate: file do not exist: " << file << endl;
+ return false;
+ }
+
+ if (isJpegImage(file))
+ {
+ DMetadata metaData;
+ if (!metaData.load(file))
+ {
+ DDebug() << "ExifRotate: no Exif data found: " << file << endl;
+ return true;
+ }
+
+ TQString temp(fi.dirPath(true) + "/.digikam-exifrotate-");
+ temp.append(TQString::number(getpid()));
+ temp.append(TQString(".jpg"));
+
+ TQCString in = TQFile::encodeName(file);
+ TQCString out = TQFile::encodeName(temp);
+
+ JCOPY_OPTION copyoption = JCOPYOPT_ALL;
+ jpeg_transform_info transformoption;
+ memset(&transformoption, 0, sizeof(jpeg_transform_info));
+
+ transformoption.force_grayscale = false;
+ transformoption.trim = false;
+ transformoption.transform = JXFORM_NONE;
+
+ // we have the exif info. check the orientation
+
+ switch(metaData.getImageOrientation())
+ {
+ case(DMetadata::ORIENTATION_UNSPECIFIED):
+ case(DMetadata::ORIENTATION_NORMAL):
+ break;
+ case(DMetadata::ORIENTATION_HFLIP):
+ {
+ transformoption.transform = JXFORM_FLIP_H;
+ break;
+ }
+ case(DMetadata::ORIENTATION_ROT_180):
+ {
+ transformoption.transform = JXFORM_ROT_180;
+ break;
+ }
+ case(DMetadata::ORIENTATION_VFLIP):
+ {
+ transformoption.transform = JXFORM_FLIP_V;
+ break;
+ }
+ case(DMetadata::ORIENTATION_ROT_90_HFLIP):
+ {
+ transformoption.transform = JXFORM_TRANSPOSE;
+ break;
+ }
+ case(DMetadata::ORIENTATION_ROT_90):
+ {
+ transformoption.transform = JXFORM_ROT_90;
+ break;
+ }
+ case(DMetadata::ORIENTATION_ROT_90_VFLIP):
+ {
+ transformoption.transform = JXFORM_TRANSVERSE;
+ break;
+ }
+ case(DMetadata::ORIENTATION_ROT_270):
+ {
+ transformoption.transform = JXFORM_ROT_270;
+ break;
+ }
+ }
+
+ if (transformoption.transform == JXFORM_NONE)
+ {
+ DDebug() << "ExifRotate: no rotation to perform: " << file << endl;
+ return true;
+ }
+
+ struct jpeg_decompress_struct srcinfo;
+ struct jpeg_compress_struct dstinfo;
+ struct jpegutils_jpeg_error_mgr jsrcerr, jdsterr;
+ jvirt_barray_ptr* src_coef_arrays;
+ jvirt_barray_ptr* dst_coef_arrays;
+
+ // Initialize the JPEG decompression object with default error handling
+ srcinfo.err = jpeg_std_error(&jsrcerr);
+ srcinfo.err->error_exit = jpegutils_jpeg_error_exit;
+ srcinfo.err->emit_message = jpegutils_jpeg_emit_message;
+ srcinfo.err->output_message = jpegutils_jpeg_output_message;
+
+ // Initialize the JPEG compression object with default error handling
+ dstinfo.err = jpeg_std_error(&jdsterr);
+ dstinfo.err->error_exit = jpegutils_jpeg_error_exit;
+ dstinfo.err->emit_message = jpegutils_jpeg_emit_message;
+ dstinfo.err->output_message = jpegutils_jpeg_output_message;
+
+ FILE *input_file;
+ FILE *output_file;
+
+ input_file = fopen(in, "rb");
+ if (!input_file)
+ {
+ DWarning() << "ExifRotate: Error in opening input file: " << input_file << endl;
+ return false;
+ }
+
+ output_file = fopen(out, "wb");
+ if (!output_file)
+ {
+ fclose(input_file);
+ DWarning() << "ExifRotate: Error in opening output file: " << output_file << endl;
+ return false;
+ }
+
+ if (setjmp(jsrcerr.setjmp_buffer) || setjmp(jdsterr.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&srcinfo);
+ jpeg_destroy_compress(&dstinfo);
+ fclose(input_file);
+ fclose(output_file);
+ return false;
+ }
+
+ jpeg_create_decompress(&srcinfo);
+ jpeg_create_compress(&dstinfo);
+
+ jpeg_stdio_src(&srcinfo, input_file);
+ jcopy_markers_setup(&srcinfo, copyoption);
+
+ (void) jpeg_read_header(&srcinfo, true);
+
+ jtransform_request_workspace(&srcinfo, &transformoption);
+
+ // Read source file as DCT coefficients
+ src_coef_arrays = jpeg_read_coefficients(&srcinfo);
+
+ // Initialize destination compression parameters from source values
+ jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
+
+ dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo,
+ src_coef_arrays, &transformoption);
+
+ // Specify data destination for compression
+ jpeg_stdio_dest(&dstinfo, output_file);
+
+ // Start compressor (note no image data is actually written here)
+ jpeg_write_coefficients(&dstinfo, dst_coef_arrays);
+
+ // Copy to the output file any extra markers that we want to preserve
+ jcopy_markers_execute(&srcinfo, &dstinfo, copyoption);
+
+ jtransform_execute_transformation(&srcinfo, &dstinfo,
+ src_coef_arrays, &transformoption);
+
+ // Finish compression and release memory
+ jpeg_finish_compress(&dstinfo);
+ jpeg_destroy_compress(&dstinfo);
+ (void) jpeg_finish_decompress(&srcinfo);
+ jpeg_destroy_decompress(&srcinfo);
+
+ fclose(input_file);
+ fclose(output_file);
+
+ // -- Metadata operations ------------------------------------------------------
+
+ // Reset the Exif orientation tag of the temp image to normal
+ DDebug() << "ExifRotate: set Orientation tag to normal: " << file << endl;
+
+ metaData.load(temp);
+ metaData.setImageOrientation(DMetadata::ORIENTATION_NORMAL);
+ TQImage img(temp);
+
+ // Get the new image dimension of the temp image. Using a dummy TQImage objet here
+ // has a sense because the Exif dimension information can be missing from original image.
+ // Get new dimensions with TQImage will always work...
+ metaData.setImageDimensions(img.size());
+
+ // Update the image thumbnail.
+ TQImage thumb = img.scale(160, 120, TQImage::ScaleMin);
+ metaData.setExifThumbnail(thumb);
+
+ // Update Exif Document Name tag (the orinal file name from camera for example).
+ metaData.setExifTagString("Exif.Image.DocumentName", documentName);
+
+ // We update all new metadata now...
+ metaData.applyChanges();
+
+ // -----------------------------------------------------------------------------
+ // set the file modification time of the temp file to that
+ // of the original file
+ struct stat st;
+ stat(in, &st);
+
+ struct utimbuf ut;
+ ut.modtime = st.st_mtime;
+ ut.actime = st.st_atime;
+
+ utime(out, &ut);
+
+ // now overwrite the original file
+ if (rename(out, in) == 0)
+ {
+ return true;
+ }
+ else
+ {
+ // moving failed. unlink the temp file
+ unlink(out);
+ return false;
+ }
+ }
+
+ // Not a jpeg image.
+ DDebug() << "ExifRotate: not a JPEG file: " << file << endl;
+ return false;
+}
+
+bool jpegConvert(const TQString& src, const TQString& dest, const TQString& documentName, const TQString& format)
+{
+ TQFileInfo fi(src);
+ if (!fi.exists())
+ {
+ DDebug() << "JpegConvert: file do not exist: " << src << endl;
+ return false;
+ }
+
+ if (isJpegImage(src))
+ {
+ DImg image(src);
+
+ // Get image Exif/Iptc data.
+ DMetadata meta;
+ meta.setExif(image.getExif());
+ meta.setIptc(image.getIptc());
+
+ // Update Iptc preview.
+ TQImage preview = image.smoothScale(1280, 1024, TQSize::ScaleMin).copyTQImage();
+
+ // TODO: see B.K.O #130525. a JPEG segment is limited to 64K. If the IPTC byte array is
+ // bigger than 64K duing of image preview tag size, the target JPEG image will be
+ // broken. Note that IPTC image preview tag is limited to 256K!!!
+ // Temp. solution to disable IPTC preview record in JPEG file until a right solution
+ // will be found into Exiv2.
+ // Note : There is no limitation with TIFF and PNG about IPTC byte array size.
+
+ if (format.upper() != TQString("JPG") && format.upper() != TQString("JPEG") &&
+ format.upper() != TQString("JPE"))
+ meta.setImagePreview(preview);
+
+ // Update Exif thumbnail.
+ TQImage thumb = preview.smoothScale(160, 120, TQImage::ScaleMin);
+ meta.setExifThumbnail(thumb);
+
+ // Update Exif Document Name tag (the orinal file name from camera for example).
+ meta.setExifTagString("Exif.Image.DocumentName", documentName);
+
+ // Store new Exif/Iptc data into image.
+ image.setExif(meta.getExif());
+ image.setIptc(meta.getIptc());
+
+ // And now save the image to a new file format.
+
+ if ( format.upper() == TQString("PNG") )
+ image.setAttribute("quality", 9);
+
+ if ( format.upper() == TQString("TIFF") || format.upper() == TQString("TIF") )
+ image.setAttribute("compress", true);
+
+ return (image.save(dest, format));
+ }
+
+ return false;
+}
+
+bool isJpegImage(const TQString& file)
+{
+ // Check if the file is an JPEG image
+ TQString format = TQString(TQImage::imageFormat(file)).upper();
+ DDebug() << "mimetype = " << format << endl;
+ if (format !="JPEG") return false;
+
+ return true;
+}
+
+} // Namespace Digikam
diff --git a/src/libs/jpegutils/jpegutils.h b/src/libs/jpegutils/jpegutils.h
new file mode 100644
index 00000000..ad384770
--- /dev/null
+++ b/src/libs/jpegutils/jpegutils.h
@@ -0,0 +1,44 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-29
+ * Description : perform lossless rotation/flip to JPEG file
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef JPEGUTILS_H
+#define JPEGUTILS_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqimage.h>
+
+namespace Digikam
+{
+
+bool loadJPEGScaled(TQImage& image, const TQString& path, int maximumSize);
+bool exifRotate(const TQString& file, const TQString& documentName);
+bool jpegConvert(const TQString& src, const TQString& dest, const TQString& documentName,
+ const TQString& format=TQString("PNG"));
+bool isJpegImage(const TQString& file);
+
+}
+
+#endif /* JPEGUTILS_H */
diff --git a/src/libs/jpegutils/libjpeg62.README b/src/libs/jpegutils/libjpeg62.README
new file mode 100644
index 00000000..a18979ac
--- /dev/null
+++ b/src/libs/jpegutils/libjpeg62.README
@@ -0,0 +1,385 @@
+The Independent JPEG Group's JPEG software
+==========================================
+
+README for release 6b of 27-Mar-1998
+====================================
+
+This distribution contains the sixth public release of the Independent JPEG
+Group's free JPEG software. You are welcome to redistribute this software and
+to use it for any purpose, subject to the conditions under LEGAL ISSUES, below.
+
+Serious users of this software (particularly those incorporating it into
+larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to
+our electronic mailing list. Mailing list members are notified of updates
+and have a chance to participate in technical discussions, etc.
+
+This software is the work of Tom Lane, Philip Gladstone, Jim Boucher,
+Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi,
+Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG
+Group.
+
+IJG is not affiliated with the official ISO JPEG standards committee.
+
+
+DOCUMENTATION ROADMAP
+=====================
+
+This file contains the following sections:
+
+OVERVIEW General description of JPEG and the IJG software.
+LEGAL ISSUES Copyright, lack of warranty, terms of distribution.
+REFERENCES Where to learn more about JPEG.
+ARCHIVE LOCATIONS Where to find newer versions of this software.
+RELATED SOFTWARE Other stuff you should get.
+FILE FORMAT WARS Software *not* to get.
+TO DO Plans for future IJG releases.
+
+Other documentation files in the distribution are:
+
+User documentation:
+ install.doc How to configure and install the IJG software.
+ usage.doc Usage instructions for cjpeg, djpeg, jpegtran,
+ rdjpgcom, and wrjpgcom.
+ *.1 Unix-style man pages for programs (same info as usage.doc).
+ wizard.doc Advanced usage instructions for JPEG wizards only.
+ change.log Version-to-version change highlights.
+Programmer and internal documentation:
+ libjpeg.doc How to use the JPEG library in your own programs.
+ example.c Sample code for calling the JPEG library.
+ structure.doc Overview of the JPEG library's internal structure.
+ filelist.doc Road map of IJG files.
+ coderules.doc Coding style rules --- please read if you contribute code.
+
+Please read at least the files install.doc and usage.doc. Useful information
+can also be found in the JPEG FAQ (Frequently Asked Questions) article. See
+ARCHIVE LOCATIONS below to find out where to obtain the FAQ article.
+
+If you want to understand how the JPEG code works, we suggest reading one or
+more of the REFERENCES, then looking at the documentation files (in roughly
+the order listed) before diving into the code.
+
+
+OVERVIEW
+========
+
+This package contains C software to implement JPEG image compression and
+decompression. JPEG (pronounced "jay-peg") is a standardized compression
+method for full-color and gray-scale images. JPEG is intended for compressing
+"real-world" scenes; line drawings, cartoons and other non-realistic images
+are not its strong suit. JPEG is lossy, meaning that the output image is not
+exactly identical to the input image. Hence you must not use JPEG if you
+have to have identical output bits. However, on typical photographic images,
+very good compression levels can be obtained with no visible change, and
+remarkably high compression levels are possible if you can tolerate a
+low-quality image. For more details, see the references, or just experiment
+with various compression settings.
+
+This software implements JPEG baseline, extended-sequential, and progressive
+compression processes. Provision is made for supporting all variants of these
+processes, although some uncommon parameter settings aren't implemented yet.
+For legal reasons, we are not distributing code for the arithmetic-coding
+variants of JPEG; see LEGAL ISSUES. We have made no provision for supporting
+the hierarchical or lossless processes defined in the standard.
+
+We provide a set of library routines for reading and writing JPEG image files,
+plus two sample applications "cjpeg" and "djpeg", which use the library to
+perform conversion between JPEG and some other popular image file formats.
+The library is intended to be reused in other applications.
+
+In order to support file conversion and viewing software, we have included
+considerable functionality beyond the bare JPEG coding/decoding capability;
+for example, the color quantization modules are not strictly part of JPEG
+decoding, but they are essential for output to colormapped file formats or
+colormapped displays. These extra functions can be compiled out of the
+library if not required for a particular application. We have also included
+"jpegtran", a utility for lossless transcoding between different JPEG
+processes, and "rdjpgcom" and "wrjpgcom", two simple applications for
+inserting and extracting textual comments in JFIF files.
+
+The emphasis in designing this software has been on achieving portability and
+flexibility, while also making it fast enough to be useful. In particular,
+the software is not intended to be read as a tutorial on JPEG. (See the
+REFERENCES section for introductory material.) Rather, it is intended to
+be reliable, portable, industrial-strength code. We do not claim to have
+achieved that goal in every aspect of the software, but we strive for it.
+
+We welcome the use of this software as a component of commercial products.
+No royalty is required, but we do ask for an acknowledgement in product
+documentation, as described under LEGAL ISSUES.
+
+
+LEGAL ISSUES
+============
+
+In plain English:
+
+1. We don't promise that this software works. (But if you find any bugs,
+ please let us know!)
+2. You can use this software for whatever you want. You don't have to pay us.
+3. You may not pretend that you wrote this software. If you use it in a
+ program, you must acknowledge somewhere in your documentation that
+ you've used the IJG code.
+
+In legalese:
+
+The authors make NO WARRANTY or representation, either express or implied,
+with respect to this software, its quality, accuracy, merchantability, or
+fitness for a particular purpose. This software is provided "AS IS", and you,
+its user, assume the entire risk as to its quality and accuracy.
+
+This software is copyright (C) 1991-1998, Thomas G. Lane.
+All Rights Reserved except as specified below.
+
+Permission is hereby granted to use, copy, modify, and distribute this
+software (or portions thereof) for any purpose, without fee, subject to these
+conditions:
+(1) If any part of the source code for this software is distributed, then this
+README file must be included, with this copyright and no-warranty notice
+unaltered; and any additions, deletions, or changes to the original files
+must be clearly indicated in accompanying documentation.
+(2) If only executable code is distributed, then the accompanying
+documentation must state that "this software is based in part on the work of
+the Independent JPEG Group".
+(3) Permission for use of this software is granted only if the user accepts
+full responsibility for any undesirable consequences; the authors accept
+NO LIABILITY for damages of any kind.
+
+These conditions apply to any software derived from or based on the IJG code,
+not just to the unmodified library. If you use our work, you ought to
+acknowledge us.
+
+Permission is NOT granted for the use of any IJG author's name or company name
+in advertising or publicity relating to this software or products derived from
+it. This software may be referred to only as "the Independent JPEG Group's
+software".
+
+We specifically permit and encourage the use of this software as the basis of
+commercial products, provided that all warranty or liability claims are
+assumed by the product vendor.
+
+
+ansi2knr.c is included in this distribution by permission of L. Peter Deutsch,
+sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA.
+ansi2knr.c is NOT covered by the above copyright and conditions, but instead
+by the usual distribution terms of the Free Software Foundation; principally,
+that you must include source code if you redistribute it. (See the file
+ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part
+of any program generated from the IJG code, this does not limit you more than
+the foregoing paragraphs do.
+
+The Unix configuration script "configure" was produced with GNU Autoconf.
+It is copyright by the Free Software Foundation but is freely distributable.
+The same holds for its supporting scripts (config.guess, config.sub,
+ltconfig, ltmain.sh). Another support script, install-sh, is copyright
+by M.I.T. but is also freely distributable.
+
+It appears that the arithmetic coding option of the JPEG spec is covered by
+patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot
+legally be used without obtaining one or more licenses. For this reason,
+support for arithmetic coding has been removed from the free JPEG software.
+(Since arithmetic coding provides only a marginal gain over the unpatented
+Huffman mode, it is unlikely that very many implementations will support it.)
+So far as we are aware, there are no patent restrictions on the remaining
+code.
+
+The IJG distribution formerly included code to read and write GIF files.
+To avoid entanglement with the Unisys LZW patent, GIF reading support has
+been removed altogether, and the GIF writer has been simplified to produce
+"uncompressed GIFs". This technique does not use the LZW algorithm; the
+resulting GIF files are larger than usual, but are readable by all standard
+GIF decoders.
+
+We are required to state that
+ "The Graphics Interchange Format(c) is the Copyright property of
+ CompuServe Incorporated. GIF(sm) is a Service Mark property of
+ CompuServe Incorporated."
+
+
+REFERENCES
+==========
+
+We highly recommend reading one or more of these references before trying to
+understand the innards of the JPEG software.
+
+The best short technical introduction to the JPEG compression algorithm is
+ Wallace, Gregory K. "The JPEG Still Image Compression Standard",
+ Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44.
+(Adjacent articles in that issue discuss MPEG motion picture compression,
+applications of JPEG, and related topics.) If you don't have the CACM issue
+handy, a PostScript file containing a revised version of Wallace's article is
+available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz. The file (actually
+a preprint for an article that appeared in IEEE Trans. Consumer Electronics)
+omits the sample images that appeared in CACM, but it includes corrections
+and some added material. Note: the Wallace article is copyright ACM and IEEE,
+and it may not be used for commercial purposes.
+
+A somewhat less technical, more leisurely introduction to JPEG can be found in
+"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by
+M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides
+good explanations and example C code for a multitude of compression methods
+including JPEG. It is an excellent source if you are comfortable reading C
+code but don't know much about data compression in general. The book's JPEG
+sample code is far from industrial-strength, but when you are ready to look
+at a full implementation, you've got one here...
+
+The best full description of JPEG is the textbook "JPEG Still Image Data
+Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published
+by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. Price US$59.95, 638 pp.
+The book includes the complete text of the ISO JPEG standards (DIS 10918-1
+and draft DIS 10918-2). This is by far the most complete exposition of JPEG
+in existence, and we highly recommend it.
+
+The JPEG standard itself is not available electronically; you must order a
+paper copy through ISO or ITU. (Unless you feel a need to own a certified
+official copy, we recommend buying the Pennebaker and Mitchell book instead;
+it's much cheaper and includes a great deal of useful explanatory material.)
+In the USA, copies of the standard may be ordered from ANSI Sales at (212)
+642-4900, or from Global Engineering Documents at (800) 854-7179. (ANSI
+doesn't take credit card orders, but Global does.) It's not cheap: as of
+1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7%
+shipping/handling. The standard is divided into two parts, Part 1 being the
+actual specification, while Part 2 covers compliance testing methods. Part 1
+is titled "Digital Compression and Coding of Continuous-tone Still Images,
+Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS
+10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of
+Continuous-tone Still Images, Part 2: Compliance testing" and has document
+numbers ISO/IEC IS 10918-2, ITU-T T.83.
+
+Some extensions to the original JPEG standard are defined in JPEG Part 3,
+a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84. IJG
+currently does not support any Part 3 extensions.
+
+The JPEG standard does not specify all details of an interchangeable file
+format. For the omitted details we follow the "JFIF" conventions, revision
+1.02. A copy of the JFIF spec is available from:
+ Literature Department
+ C-Cube Microsystems, Inc.
+ 1778 McCarthy Blvd.
+ Milpitas, CA 95035
+ phone (408) 944-6300, fax (408) 944-6314
+A PostScript version of this document is available by FTP at
+ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz. There is also a plain text
+version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing
+the figures.
+
+The TIFF 6.0 file format specification can be obtained by FTP from
+ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme
+found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems.
+IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6).
+Instead, we recommend the JPEG design proposed by TIFF Technical Note #2
+(Compression tag 7). Copies of this Note can be obtained from ftp.sgi.com or
+from ftp://ftp.uu.net/graphics/jpeg/. It is expected that the next revision
+of the TIFF spec will replace the 6.0 JPEG design with the Note's design.
+Although IJG's own code does not support TIFF/JPEG, the free libtiff library
+uses our library to implement TIFF/JPEG per the Note. libtiff is available
+from ftp://ftp.sgi.com/graphics/tiff/.
+
+
+ARCHIVE LOCATIONS
+=================
+
+The "official" archive site for this software is ftp.uu.net (Internet
+address 192.48.96.9). The most recent released version can always be found
+there in directory graphics/jpeg. This particular version will be archived
+as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz. If you don't have
+direct Internet access, UUNET's archives are also available via UUCP; contact
+help@uunet.uu.net for information on retrieving files that way.
+
+Numerous Internet sites maintain copies of the UUNET files. However, only
+ftp.uu.net is guaranteed to have the latest official version.
+
+You can also obtain this software in DOS-compatible "zip" archive format from
+the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or
+on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12
+"JPEG Tools". Again, these versions may sometimes lag behind the ftp.uu.net
+release.
+
+The JPEG FAQ (Frequently Asked Questions) article is a useful source of
+general information about JPEG. It is updated constantly and therefore is
+not included in this distribution. The FAQ is posted every two weeks to
+Usenet newsgroups comp.graphics.misc, news.answers, and other groups.
+It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/
+and other news.answers archive sites, including the official news.answers
+archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/.
+If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu
+with body
+ send usenet/news.answers/jpeg-faq/part1
+ send usenet/news.answers/jpeg-faq/part2
+
+
+RELATED SOFTWARE
+================
+
+Numerous viewing and image manipulation programs now support JPEG. (Quite a
+few of them use this library to do so.) The JPEG FAQ described above lists
+some of the more popular free and shareware viewers, and tells where to
+obtain them on Internet.
+
+If you are on a Unix machine, we highly recommend Jef Poskanzer's free
+PBMPLUS software, which provides many useful operations on PPM-format image
+files. In particular, it can convert PPM images to and from a wide range of
+other formats, thus making cjpeg/djpeg considerably more useful. The latest
+version is distributed by the NetPBM group, and is available from numerous
+sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/.
+Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is;
+you are likely to have difficulty making it work on any non-Unix machine.
+
+A different free JPEG implementation, written by the PVRG group at Stanford,
+is available from ftp://havefun.stanford.edu/pub/jpeg/. This program
+is designed for research and experimentation rather than production use;
+it is slower, harder to use, and less portable than the IJG code, but it
+is easier to read and modify. Also, the PVRG code supports lossless JPEG,
+which we do not. (On the other hand, it doesn't do progressive JPEG.)
+
+
+FILE FORMAT WARS
+================
+
+Some JPEG programs produce files that are not compatible with our library.
+The root of the problem is that the ISO JPEG committee failed to specify a
+concrete file format. Some vendors "filled in the blanks" on their own,
+creating proprietary formats that no one else could read. (For example, none
+of the early commercial JPEG implementations for the Macintosh were able to
+exchange compressed files.)
+
+The file format we have adopted is called JFIF (see REFERENCES). This format
+has been agreed to by a number of major commercial JPEG vendors, and it has
+become the de facto standard. JFIF is a minimal or "low end" representation.
+We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF
+Technical Note #2) for "high end" applications that need to record a lot of
+additional data about an image. TIFF/JPEG is fairly new and not yet widely
+supported, unfortunately.
+
+The upcoming JPEG Part 3 standard defines a file format called SPIFF.
+SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should
+be able to read the most common variant of SPIFF. SPIFF has some technical
+advantages over JFIF, but its major claim to fame is simply that it is an
+official standard rather than an informal one. At this point it is unclear
+whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto
+standard. IJG intends to support SPIFF once the standard is frozen, but we
+have not decided whether it should become our default output format or not.
+(In any case, our decoder will remain capable of reading JFIF indefinitely.)
+
+Various proprietary file formats incorporating JPEG compression also exist.
+We have little or no sympathy for the existence of these formats. Indeed,
+one of the original reasons for developing this free software was to help
+force convergence on common, open format standards for JPEG files. Don't
+use a proprietary file format!
+
+
+TO DO
+=====
+
+The major thrust for v7 will probably be improvement of visual quality.
+The current method for scaling the quantization tables is known not to be
+very good at low Q values. We also intend to investigate block boundary
+smoothing, "poor man's variable quantization", and other means of improving
+quality-vs-file-size performance without sacrificing compatibility.
+
+In future versions, we are considering supporting some of the upcoming JPEG
+Part 3 extensions --- principally, variable quantization and the SPIFF file
+format.
+
+As always, speeding things up is of great interest.
+
+Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net.
diff --git a/src/libs/jpegutils/transupp.cpp b/src/libs/jpegutils/transupp.cpp
new file mode 100644
index 00000000..47a9aa8b
--- /dev/null
+++ b/src/libs/jpegutils/transupp.cpp
@@ -0,0 +1,2527 @@
+/* Although this file really shouldn't have access to the library internals,
+ * it's helpful to let it call jround_up() and jcopy_block_row().
+ */
+#define JPEG_INTERNALS
+
+// LibJPEG includes.
+
+extern "C"
+{
+#include "jinclude.h"
+#include "jpeglib.h"
+}
+
+#if JPEG_LIB_VERSION >= 80
+
+/*
+ * transupp.c
+ *
+ * Copyright (C) 1997-2009, Thomas G. Lane, Guido Vollbeding.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains image transformation routines and other utility code
+ * used by the jpegtran sample application. These are NOT part of the core
+ * JPEG library. But we keep these routines separate from jpegtran.c to
+ * ease the task of maintaining jpegtran-like programs that have other user
+ * interfaces.
+ */
+
+#include "transupp.h" /* My own external interface */
+#include <ctype.h> /* to declare isdigit() */
+
+namespace Digikam
+{
+
+#if TRANSFORMS_SUPPORTED
+
+/*
+ * Lossless image transformation routines. These routines work on DCT
+ * coefficient arrays and thus do not require any lossy decompression
+ * or recompression of the image.
+ * Thanks to Guido Vollbeding for the initial design and code of this feature,
+ * and to Ben Jackson for introducing the cropping feature.
+ *
+ * Horizontal flipping is done in-place, using a single top-to-bottom
+ * pass through the virtual source array. It will thus be much the
+ * fastest option for images larger than main memory.
+ *
+ * The other routines require a set of destination virtual arrays, so they
+ * need twice as much memory as jpegtran normally does. The destination
+ * arrays are always written in normal scan order (top to bottom) because
+ * the virtual array manager expects this. The source arrays will be scanned
+ * in the corresponding order, which means multiple passes through the source
+ * arrays for most of the transforms. That could result in much thrashing
+ * if the image is larger than main memory.
+ *
+ * If cropping or trimming is involved, the destination arrays may be smaller
+ * than the source arrays. Note it is not possible to do horizontal flip
+ * in-place when a nonzero Y crop offset is specified, since we'd have to move
+ * data from one block row to another but the virtual array manager doesn't
+ * guarantee we can touch more than one row at a time. So in that case,
+ * we have to use a separate destination array.
+ *
+ * Some notes about the operating environment of the individual transform
+ * routines:
+ * 1. Both the source and destination virtual arrays are allocated from the
+ * source JPEG object, and therefore should be manipulated by calling the
+ * source's memory manager.
+ * 2. The destination's component count should be used. It may be smaller
+ * than the source's when forcing to grayscale.
+ * 3. Likewise the destination's sampling factors should be used. When
+ * forcing to grayscale the destination's sampling factors will be all 1,
+ * and we may as well take that as the effective iMCU size.
+ * 4. When "trim" is in effect, the destination's dimensions will be the
+ * trimmed values but the source's will be untrimmed.
+ * 5. When "crop" is in effect, the destination's dimensions will be the
+ * cropped values but the source's will be uncropped. Each transform
+ * routine is responsible for picking up source data starting at the
+ * correct X and Y offset for the crop region. (The X and Y offsets
+ * passed to the transform routines are measured in iMCU blocks of the
+ * destination.)
+ * 6. All the routines assume that the source and destination buffers are
+ * padded out to a full iMCU boundary. This is true, although for the
+ * source buffer it is an undocumented property of jdcoefct.c.
+ */
+
+
+LOCAL(void)
+do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Crop. This is only used when no rotate/flip is requested with the crop. */
+{
+ JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks;
+ int ci, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ jpeg_component_info *compptr;
+
+ /* We simply have to copy the right amount of data (the destination's
+ * image size) starting at the given X and Y offsets in the source.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ jcopy_block_row(src_buffer[offset_y] + x_crop_blocks,
+ dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays)
+/* Horizontal flip; done in-place, so no separate dest array is required.
+ * NB: this only works when y_crop_offset is zero.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks;
+ int ci, k, offset_y;
+ JBLOCKARRAY buffer;
+ JCOEFPTR ptr1, ptr2;
+ JCOEF temp1, temp2;
+ jpeg_component_info *compptr;
+
+ /* Horizontal mirroring of DCT blocks is accomplished by swapping
+ * pairs of blocks in-place. Within a DCT block, we perform horizontal
+ * mirroring by changing the signs of odd-numbered columns.
+ * Partial iMCUs at the right edge are left untouched.
+ */
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ for (blk_y = 0; blk_y < compptr->height_in_blocks;
+ blk_y += compptr->v_samp_factor) {
+ buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ /* Do the mirroring */
+ for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) {
+ ptr1 = buffer[offset_y][blk_x];
+ ptr2 = buffer[offset_y][comp_width - blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ temp1 = *ptr1; /* swap even column */
+ temp2 = *ptr2;
+ *ptr1++ = temp2;
+ *ptr2++ = temp1;
+ temp1 = *ptr1; /* swap odd column with sign change */
+ temp2 = *ptr2;
+ *ptr1++ = -temp2;
+ *ptr2++ = -temp1;
+ }
+ }
+ if (x_crop_blocks > 0) {
+ /* Now left-justify the portion of the data to be kept.
+ * We can't use a single jcopy_block_row() call because that routine
+ * depends on memcpy(), whose behavior is unspecified for overlapping
+ * source and destination areas. Sigh.
+ */
+ for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) {
+ jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks,
+ buffer[offset_y] + blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Horizontal flip in general cropping case */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, k, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Here we must output into a separate array because we can't touch
+ * different rows of a single virtual array simultaneously. Otherwise,
+ * this is essentially the same as the routine above.
+ */
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[offset_y];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Do the mirrorable blocks */
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ *dst_ptr++ = *src_ptr++; /* copy even column */
+ *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */
+ }
+ } else {
+ /* Copy last partial block(s) verbatim */
+ jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks,
+ dst_row_ptr + dst_blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Vertical flip */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* We output into a separate array because we can't touch different
+ * rows of the source virtual array simultaneously. Otherwise, this
+ * is a pretty straightforward analog of horizontal flip.
+ * Within a DCT block, vertical mirroring is done by changing the signs
+ * of odd-numbered rows.
+ * Partial iMCUs at the bottom edge are copied verbatim.
+ */
+ MCU_rows = srcinfo->output_height /
+ (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - y_crop_blocks - dst_blk_y -
+ (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge blocks will be copied verbatim. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ src_row_ptr += x_crop_blocks;
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* copy even row */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ /* copy odd row with sign change */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ } else {
+ /* Just copy row verbatim. */
+ jcopy_block_row(src_buffer[offset_y] + x_crop_blocks,
+ dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transpose source into destination */
+{
+ JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Transposing pixels within a block just requires transposing the
+ * DCT coefficients.
+ * Partial iMCUs at the edges require no special treatment; we simply
+ * process all the available DCT blocks for every component.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 90 degree rotation is equivalent to
+ * 1. Transposing the image;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) right edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_cols = srcinfo->output_height /
+ (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_width - x_crop_blocks - dst_blk_x -
+ (JDIMENSION) compptr->h_samp_factor,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ }
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 270 degree rotation is equivalent to
+ * 1. Horizontal mirroring;
+ * 2. Transposing the image.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) bottom edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_rows = srcinfo->output_width /
+ (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 180 degree rotation is equivalent to
+ * 1. Vertical mirroring;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size);
+ MCU_rows = srcinfo->output_height /
+ (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the vertically mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - y_crop_blocks - dst_blk_y -
+ (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge rows are only mirrored horizontally. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ dst_row_ptr = dst_buffer[offset_y];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Process the blocks that can be mirrored both ways. */
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* For even row, negate every odd column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ /* For odd row, negate every even column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = - *src_ptr++;
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ } else {
+ /* Any remaining right-edge blocks are only mirrored vertically. */
+ src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ }
+ } else {
+ /* Remaining rows are just mirrored horizontally. */
+ src_row_ptr = src_buffer[offset_y];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Process the blocks that can be mirrored. */
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE2; i += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ } else {
+ /* Any remaining right-edge blocks are only copied. */
+ jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks,
+ dst_row_ptr + dst_blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transverse transpose is equivalent to
+ * 1. 180 degree rotation;
+ * 2. Transposition;
+ * or
+ * 1. Horizontal mirroring;
+ * 2. Transposition;
+ * 3. Horizontal mirroring.
+ * These steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = srcinfo->output_height /
+ (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size);
+ MCU_rows = srcinfo->output_width /
+ (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_width - x_crop_blocks - dst_blk_x -
+ (JDIMENSION) compptr->h_samp_factor,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ } else {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ }
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ i++;
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Right-edge blocks are mirrored in y only */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ } else {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Bottom-edge blocks are mirrored in x only */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* At lower right corner, just transpose, no mirroring */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/* Parse an unsigned integer: subroutine for jtransform_parse_crop_spec.
+ * Returns TRUE if valid integer found, FALSE if not.
+ * *strptr is advanced over the digit string, and *result is set to its value.
+ */
+
+LOCAL(boolean)
+jt_read_integer (const char ** strptr, JDIMENSION * result)
+{
+ const char * ptr = *strptr;
+ JDIMENSION val = 0;
+
+ for (; isdigit(*ptr); ptr++) {
+ val = val * 10 + (JDIMENSION) (*ptr - '0');
+ }
+ *result = val;
+ if (ptr == *strptr)
+ return FALSE; /* oops, no digits */
+ *strptr = ptr;
+ return TRUE;
+}
+
+
+/* Parse a crop specification (written in X11 geometry style).
+ * The routine returns TRUE if the spec string is valid, FALSE if not.
+ *
+ * The crop spec string should have the format
+ * <width>x<height>{+-}<xoffset>{+-}<yoffset>
+ * where width, height, xoffset, and yoffset are unsigned integers.
+ * Each of the elements can be omitted to indicate a default value.
+ * (A weakness of this style is that it is not possible to omit xoffset
+ * while specifying yoffset, since they look alike.)
+ *
+ * This code is loosely based on XParseGeometry from the X11 distribution.
+ */
+
+GLOBAL(boolean)
+jtransform_parse_crop_spec (jpeg_transform_info *info, const char *spec)
+{
+ info->crop = FALSE;
+ info->crop_width_set = JCROP_UNSET;
+ info->crop_height_set = JCROP_UNSET;
+ info->crop_xoffset_set = JCROP_UNSET;
+ info->crop_yoffset_set = JCROP_UNSET;
+
+ if (isdigit(*spec)) {
+ /* fetch width */
+ if (! jt_read_integer(&spec, &info->crop_width))
+ return FALSE;
+ info->crop_width_set = JCROP_POS;
+ }
+ if (*spec == 'x' || *spec == 'X') {
+ /* fetch height */
+ spec++;
+ if (! jt_read_integer(&spec, &info->crop_height))
+ return FALSE;
+ info->crop_height_set = JCROP_POS;
+ }
+ if (*spec == '+' || *spec == '-') {
+ /* fetch xoffset */
+ info->crop_xoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS;
+ spec++;
+ if (! jt_read_integer(&spec, &info->crop_xoffset))
+ return FALSE;
+ }
+ if (*spec == '+' || *spec == '-') {
+ /* fetch yoffset */
+ info->crop_yoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS;
+ spec++;
+ if (! jt_read_integer(&spec, &info->crop_yoffset))
+ return FALSE;
+ }
+ /* We had better have gotten to the end of the string. */
+ if (*spec != '\0')
+ return FALSE;
+ info->crop = TRUE;
+ return TRUE;
+}
+
+
+/* Trim off any partial iMCUs on the indicated destination edge */
+
+LOCAL(void)
+trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width)
+{
+ JDIMENSION MCU_cols;
+
+ MCU_cols = info->output_width / info->iMCU_sample_width;
+ if (MCU_cols > 0 && info->x_crop_offset + MCU_cols ==
+ full_width / info->iMCU_sample_width)
+ info->output_width = MCU_cols * info->iMCU_sample_width;
+}
+
+LOCAL(void)
+trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height)
+{
+ JDIMENSION MCU_rows;
+
+ MCU_rows = info->output_height / info->iMCU_sample_height;
+ if (MCU_rows > 0 && info->y_crop_offset + MCU_rows ==
+ full_height / info->iMCU_sample_height)
+ info->output_height = MCU_rows * info->iMCU_sample_height;
+}
+
+
+/* Request any required workspace.
+ *
+ * This routine figures out the size that the output image will be
+ * (which implies that all the transform parameters must be set before
+ * it is called).
+ *
+ * We allocate the workspace virtual arrays from the source decompression
+ * object, so that all the arrays (both the original data and the workspace)
+ * will be taken into account while making memory management decisions.
+ * Hence, this routine must be called after jpeg_read_header (which reads
+ * the image dimensions) and before jpeg_read_coefficients (which realizes
+ * the source's virtual arrays).
+ *
+ * This function returns FALSE right away if -perfect is given
+ * and transformation is not perfect. Otherwise returns TRUE.
+ */
+
+GLOBAL(boolean)
+jtransform_request_workspace (j_decompress_ptr srcinfo,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *coef_arrays;
+ boolean need_workspace, transpose_it;
+ jpeg_component_info *compptr;
+ JDIMENSION xoffset, yoffset;
+ JDIMENSION width_in_iMCUs, height_in_iMCUs;
+ JDIMENSION width_in_blocks, height_in_blocks;
+ int ci, h_samp_factor, v_samp_factor;
+
+ /* Determine number of components in output image */
+ if (info->force_grayscale &&
+ srcinfo->jpeg_color_space == JCS_YCbCr &&
+ srcinfo->num_components == 3)
+ /* We'll only process the first component */
+ info->num_components = 1;
+ else
+ /* Process all the components */
+ info->num_components = srcinfo->num_components;
+
+ /* Compute output image dimensions and related values. */
+ jpeg_core_output_dimensions(srcinfo);
+
+ /* Return right away if -perfect is given and transformation is not perfect.
+ */
+ if (info->perfect) {
+ if (info->num_components == 1) {
+ if (!jtransform_perfect_transform(srcinfo->output_width,
+ srcinfo->output_height,
+ srcinfo->min_DCT_h_scaled_size,
+ srcinfo->min_DCT_v_scaled_size,
+ info->transform))
+ return FALSE;
+ } else {
+ if (!jtransform_perfect_transform(srcinfo->output_width,
+ srcinfo->output_height,
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size,
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size,
+ info->transform))
+ return FALSE;
+ }
+ }
+
+ /* If there is only one output component, force the iMCU size to be 1;
+ * else use the source iMCU size. (This allows us to do the right thing
+ * when reducing color to grayscale, and also provides a handy way of
+ * cleaning up "funny" grayscale images whose sampling factors are not 1x1.)
+ */
+ switch (info->transform) {
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+ info->output_width = srcinfo->output_height;
+ info->output_height = srcinfo->output_width;
+ if (info->num_components == 1) {
+ info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size;
+ info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size;
+ } else {
+ info->iMCU_sample_width =
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size;
+ info->iMCU_sample_height =
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size;
+ }
+ break;
+ default:
+ info->output_width = srcinfo->output_width;
+ info->output_height = srcinfo->output_height;
+ if (info->num_components == 1) {
+ info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size;
+ info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size;
+ } else {
+ info->iMCU_sample_width =
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size;
+ info->iMCU_sample_height =
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size;
+ }
+ break;
+ }
+
+ /* If cropping has been requested, compute the crop area's position and
+ * dimensions, ensuring that its upper left corner falls at an iMCU boundary.
+ */
+ if (info->crop) {
+ /* Insert default values for unset crop parameters */
+ if (info->crop_xoffset_set == JCROP_UNSET)
+ info->crop_xoffset = 0; /* default to +0 */
+ if (info->crop_yoffset_set == JCROP_UNSET)
+ info->crop_yoffset = 0; /* default to +0 */
+ if (info->crop_xoffset >= info->output_width ||
+ info->crop_yoffset >= info->output_height)
+ ERREXIT(srcinfo, JERR_BAD_CROP_SPEC);
+ if (info->crop_width_set == JCROP_UNSET)
+ info->crop_width = info->output_width - info->crop_xoffset;
+ if (info->crop_height_set == JCROP_UNSET)
+ info->crop_height = info->output_height - info->crop_yoffset;
+ /* Ensure parameters are valid */
+ if (info->crop_width <= 0 || info->crop_width > info->output_width ||
+ info->crop_height <= 0 || info->crop_height > info->output_height ||
+ info->crop_xoffset > info->output_width - info->crop_width ||
+ info->crop_yoffset > info->output_height - info->crop_height)
+ ERREXIT(srcinfo, JERR_BAD_CROP_SPEC);
+ /* Convert negative crop offsets into regular offsets */
+ if (info->crop_xoffset_set == JCROP_NEG)
+ xoffset = info->output_width - info->crop_width - info->crop_xoffset;
+ else
+ xoffset = info->crop_xoffset;
+ if (info->crop_yoffset_set == JCROP_NEG)
+ yoffset = info->output_height - info->crop_height - info->crop_yoffset;
+ else
+ yoffset = info->crop_yoffset;
+ /* Now adjust so that upper left corner falls at an iMCU boundary */
+ info->output_width =
+ info->crop_width + (xoffset % info->iMCU_sample_width);
+ info->output_height =
+ info->crop_height + (yoffset % info->iMCU_sample_height);
+ /* Save x/y offsets measured in iMCUs */
+ info->x_crop_offset = xoffset / info->iMCU_sample_width;
+ info->y_crop_offset = yoffset / info->iMCU_sample_height;
+ } else {
+ info->x_crop_offset = 0;
+ info->y_crop_offset = 0;
+ }
+
+ /* Figure out whether we need workspace arrays,
+ * and if so whether they are transposed relative to the source.
+ */
+ need_workspace = FALSE;
+ transpose_it = FALSE;
+ switch (info->transform) {
+ case JXFORM_NONE:
+ if (info->x_crop_offset != 0 || info->y_crop_offset != 0)
+ need_workspace = TRUE;
+ /* No workspace needed if neither cropping nor transforming */
+ break;
+ case JXFORM_FLIP_H:
+ if (info->trim)
+ trim_right_edge(info, srcinfo->output_width);
+ if (info->y_crop_offset != 0)
+ need_workspace = TRUE;
+ /* do_flip_h_no_crop doesn't need a workspace array */
+ break;
+ case JXFORM_FLIP_V:
+ if (info->trim)
+ trim_bottom_edge(info, srcinfo->output_height);
+ /* Need workspace arrays having same dimensions as source image. */
+ need_workspace = TRUE;
+ break;
+ case JXFORM_TRANSPOSE:
+ /* transpose does NOT have to trim anything */
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_TRANSVERSE:
+ if (info->trim) {
+ trim_right_edge(info, srcinfo->output_height);
+ trim_bottom_edge(info, srcinfo->output_width);
+ }
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_ROT_90:
+ if (info->trim)
+ trim_right_edge(info, srcinfo->output_height);
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_ROT_180:
+ if (info->trim) {
+ trim_right_edge(info, srcinfo->output_width);
+ trim_bottom_edge(info, srcinfo->output_height);
+ }
+ /* Need workspace arrays having same dimensions as source image. */
+ need_workspace = TRUE;
+ break;
+ case JXFORM_ROT_270:
+ if (info->trim)
+ trim_bottom_edge(info, srcinfo->output_width);
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ }
+
+ /* Allocate workspace if needed.
+ * Note that we allocate arrays padded out to the next iMCU boundary,
+ * so that transform routines need not worry about missing edge blocks.
+ */
+ if (need_workspace) {
+ coef_arrays = (jvirt_barray_ptr *)
+ (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+ SIZEOF(jvirt_barray_ptr) * info->num_components);
+ width_in_iMCUs = (JDIMENSION)
+ jdiv_round_up((long) info->output_width,
+ (long) info->iMCU_sample_width);
+ height_in_iMCUs = (JDIMENSION)
+ jdiv_round_up((long) info->output_height,
+ (long) info->iMCU_sample_height);
+ for (ci = 0; ci < info->num_components; ci++) {
+ compptr = srcinfo->comp_info + ci;
+ if (info->num_components == 1) {
+ /* we're going to force samp factors to 1x1 in this case */
+ h_samp_factor = v_samp_factor = 1;
+ } else if (transpose_it) {
+ h_samp_factor = compptr->v_samp_factor;
+ v_samp_factor = compptr->h_samp_factor;
+ } else {
+ h_samp_factor = compptr->h_samp_factor;
+ v_samp_factor = compptr->v_samp_factor;
+ }
+ width_in_blocks = width_in_iMCUs * h_samp_factor;
+ height_in_blocks = height_in_iMCUs * v_samp_factor;
+ coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+ ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
+ width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor);
+ }
+ info->workspace_coef_arrays = coef_arrays;
+ } else
+ info->workspace_coef_arrays = NULL;
+
+ return TRUE;
+}
+
+
+/* Transpose destination image parameters */
+
+LOCAL(void)
+transpose_critical_parameters (j_compress_ptr dstinfo)
+{
+ int tblno, i, j, ci, itemp;
+ jpeg_component_info *compptr;
+ JQUANT_TBL *qtblptr;
+ JDIMENSION jtemp;
+ UINT16 qtemp;
+
+ /* Transpose image dimensions */
+ jtemp = dstinfo->image_width;
+ dstinfo->image_width = dstinfo->image_height;
+ dstinfo->image_height = jtemp;
+ itemp = dstinfo->min_DCT_h_scaled_size;
+ dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size;
+ dstinfo->min_DCT_v_scaled_size = itemp;
+
+ /* Transpose sampling factors */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ itemp = compptr->h_samp_factor;
+ compptr->h_samp_factor = compptr->v_samp_factor;
+ compptr->v_samp_factor = itemp;
+ }
+
+ /* Transpose quantization tables */
+ for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) {
+ qtblptr = dstinfo->quant_tbl_ptrs[tblno];
+ if (qtblptr != NULL) {
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < i; j++) {
+ qtemp = qtblptr->quantval[i*DCTSIZE+j];
+ qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i];
+ qtblptr->quantval[j*DCTSIZE+i] = qtemp;
+ }
+ }
+ }
+ }
+}
+
+
+/* Adjust Exif image parameters.
+ *
+ * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible.
+ */
+
+LOCAL(void)
+adjust_exif_parameters (JOCTET FAR * data, unsigned int length,
+ JDIMENSION new_width, JDIMENSION new_height)
+{
+ boolean is_motorola; /* Flag for byte order */
+ unsigned int number_of_tags, tagnum;
+ unsigned int firstoffset, offset;
+ JDIMENSION new_value;
+
+ if (length < 12) return; /* Length of an IFD entry */
+
+ /* Discover byte order */
+ if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49)
+ is_motorola = FALSE;
+ else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D)
+ is_motorola = TRUE;
+ else
+ return;
+
+ /* Check Tag Mark */
+ if (is_motorola) {
+ if (GETJOCTET(data[2]) != 0) return;
+ if (GETJOCTET(data[3]) != 0x2A) return;
+ } else {
+ if (GETJOCTET(data[3]) != 0) return;
+ if (GETJOCTET(data[2]) != 0x2A) return;
+ }
+
+ /* Get first IFD offset (offset to IFD0) */
+ if (is_motorola) {
+ if (GETJOCTET(data[4]) != 0) return;
+ if (GETJOCTET(data[5]) != 0) return;
+ firstoffset = GETJOCTET(data[6]);
+ firstoffset <<= 8;
+ firstoffset += GETJOCTET(data[7]);
+ } else {
+ if (GETJOCTET(data[7]) != 0) return;
+ if (GETJOCTET(data[6]) != 0) return;
+ firstoffset = GETJOCTET(data[5]);
+ firstoffset <<= 8;
+ firstoffset += GETJOCTET(data[4]);
+ }
+ if (firstoffset > length - 2) return; /* check end of data segment */
+
+ /* Get the number of directory entries contained in this IFD */
+ if (is_motorola) {
+ number_of_tags = GETJOCTET(data[firstoffset]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[firstoffset+1]);
+ } else {
+ number_of_tags = GETJOCTET(data[firstoffset+1]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[firstoffset]);
+ }
+ if (number_of_tags == 0) return;
+ firstoffset += 2;
+
+ /* Search for ExifSubIFD offset Tag in IFD0 */
+ for (;;) {
+ if (firstoffset > length - 12) return; /* check end of data segment */
+ /* Get Tag number */
+ if (is_motorola) {
+ tagnum = GETJOCTET(data[firstoffset]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[firstoffset+1]);
+ } else {
+ tagnum = GETJOCTET(data[firstoffset+1]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[firstoffset]);
+ }
+ if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */
+ if (--number_of_tags == 0) return;
+ firstoffset += 12;
+ }
+
+ /* Get the ExifSubIFD offset */
+ if (is_motorola) {
+ if (GETJOCTET(data[firstoffset+8]) != 0) return;
+ if (GETJOCTET(data[firstoffset+9]) != 0) return;
+ offset = GETJOCTET(data[firstoffset+10]);
+ offset <<= 8;
+ offset += GETJOCTET(data[firstoffset+11]);
+ } else {
+ if (GETJOCTET(data[firstoffset+11]) != 0) return;
+ if (GETJOCTET(data[firstoffset+10]) != 0) return;
+ offset = GETJOCTET(data[firstoffset+9]);
+ offset <<= 8;
+ offset += GETJOCTET(data[firstoffset+8]);
+ }
+ if (offset > length - 2) return; /* check end of data segment */
+
+ /* Get the number of directory entries contained in this SubIFD */
+ if (is_motorola) {
+ number_of_tags = GETJOCTET(data[offset]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[offset+1]);
+ } else {
+ number_of_tags = GETJOCTET(data[offset+1]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[offset]);
+ }
+ if (number_of_tags < 2) return;
+ offset += 2;
+
+ /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */
+ do {
+ if (offset > length - 12) return; /* check end of data segment */
+ /* Get Tag number */
+ if (is_motorola) {
+ tagnum = GETJOCTET(data[offset]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[offset+1]);
+ } else {
+ tagnum = GETJOCTET(data[offset+1]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[offset]);
+ }
+ if (tagnum == 0xA002 || tagnum == 0xA003) {
+ if (tagnum == 0xA002)
+ new_value = new_width; /* ExifImageWidth Tag */
+ else
+ new_value = new_height; /* ExifImageHeight Tag */
+ if (is_motorola) {
+ data[offset+2] = 0; /* Format = unsigned long (4 octets) */
+ data[offset+3] = 4;
+ data[offset+4] = 0; /* Number Of Components = 1 */
+ data[offset+5] = 0;
+ data[offset+6] = 0;
+ data[offset+7] = 1;
+ data[offset+8] = 0;
+ data[offset+9] = 0;
+ data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF);
+ data[offset+11] = (JOCTET)(new_value & 0xFF);
+ } else {
+ data[offset+2] = 4; /* Format = unsigned long (4 octets) */
+ data[offset+3] = 0;
+ data[offset+4] = 1; /* Number Of Components = 1 */
+ data[offset+5] = 0;
+ data[offset+6] = 0;
+ data[offset+7] = 0;
+ data[offset+8] = (JOCTET)(new_value & 0xFF);
+ data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF);
+ data[offset+10] = 0;
+ data[offset+11] = 0;
+ }
+ }
+ offset += 12;
+ } while (--number_of_tags);
+}
+
+
+/* Adjust output image parameters as needed.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ *
+ * The return value is the set of virtual coefficient arrays to be written
+ * (either the ones allocated by jtransform_request_workspace, or the
+ * original source data arrays). The caller will need to pass this value
+ * to jpeg_write_coefficients().
+ */
+
+GLOBAL(jvirt_barray_ptr *)
+jtransform_adjust_parameters (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ /* If force-to-grayscale is requested, adjust destination parameters */
+ if (info->force_grayscale) {
+ /* First, ensure we have YCbCr or grayscale data, and that the source's
+ * Y channel is full resolution. (No reasonable person would make Y
+ * be less than full resolution, so actually coping with that case
+ * isn't worth extra code space. But we check it to avoid crashing.)
+ */
+ if (((dstinfo->jpeg_color_space == JCS_YCbCr &&
+ dstinfo->num_components == 3) ||
+ (dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
+ dstinfo->num_components == 1)) &&
+ srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor &&
+ srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) {
+ /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
+ * properly. Among other things, it sets the target h_samp_factor &
+ * v_samp_factor to 1, which typically won't match the source.
+ * We have to preserve the source's quantization table number, however.
+ */
+ int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
+ jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
+ dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
+ } else {
+ /* Sorry, can't do it */
+ ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
+ }
+ } else if (info->num_components == 1) {
+ /* For a single-component source, we force the destination sampling factors
+ * to 1x1, with or without force_grayscale. This is useful because some
+ * decoders choke on grayscale images with other sampling factors.
+ */
+ dstinfo->comp_info[0].h_samp_factor = 1;
+ dstinfo->comp_info[0].v_samp_factor = 1;
+ }
+
+ /* Correct the destination's image dimensions as necessary
+ * for rotate/flip, resize, and crop operations.
+ */
+ dstinfo->jpeg_width = info->output_width;
+ dstinfo->jpeg_height = info->output_height;
+
+ /* Transpose destination image parameters */
+ switch (info->transform) {
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+ transpose_critical_parameters(dstinfo);
+ break;
+ default:
+ break;
+ }
+
+ /* Adjust Exif properties */
+ if (srcinfo->marker_list != NULL &&
+ srcinfo->marker_list->marker == JPEG_APP0+1 &&
+ srcinfo->marker_list->data_length >= 6 &&
+ GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 &&
+ GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 &&
+ GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 &&
+ GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 &&
+ GETJOCTET(srcinfo->marker_list->data[4]) == 0 &&
+ GETJOCTET(srcinfo->marker_list->data[5]) == 0) {
+ /* Suppress output of JFIF marker */
+ dstinfo->write_JFIF_header = FALSE;
+ /* Adjust Exif image parameters */
+ if (dstinfo->jpeg_width != srcinfo->image_width ||
+ dstinfo->jpeg_height != srcinfo->image_height)
+ /* Align data segment to start of TIFF structure for parsing */
+ adjust_exif_parameters(srcinfo->marker_list->data + 6,
+ srcinfo->marker_list->data_length - 6,
+ dstinfo->jpeg_width, dstinfo->jpeg_height);
+ }
+
+ /* Return the appropriate output data set */
+ if (info->workspace_coef_arrays != NULL)
+ return info->workspace_coef_arrays;
+ return src_coef_arrays;
+}
+
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+GLOBAL(void)
+jtransform_execute_transform (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;
+
+ /* Note: conditions tested here should match those in switch statement
+ * in jtransform_request_workspace()
+ */
+ switch (info->transform) {
+ case JXFORM_NONE:
+ if (info->x_crop_offset != 0 || info->y_crop_offset != 0)
+ do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_FLIP_H:
+ if (info->y_crop_offset != 0)
+ do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ else
+ do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset,
+ src_coef_arrays);
+ break;
+ case JXFORM_FLIP_V:
+ do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSPOSE:
+ do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSVERSE:
+ do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_90:
+ do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_180:
+ do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_270:
+ do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ }
+}
+
+/* jtransform_perfect_transform
+ *
+ * Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ *
+ * Inputs:
+ * image_width, image_height: source image dimensions.
+ * MCU_width, MCU_height: pixel dimensions of MCU.
+ * transform: transformation identifier.
+ * Parameter sources from initialized jpeg_struct
+ * (after reading source header):
+ * image_width = cinfo.image_width
+ * image_height = cinfo.image_height
+ * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size
+ * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size
+ * Result:
+ * TRUE = perfect transformation possible
+ * FALSE = perfect transformation not possible
+ * (may use custom action then)
+ */
+
+GLOBAL(boolean)
+jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform)
+{
+ boolean result = TRUE; /* initialize TRUE */
+
+ switch (transform) {
+ case JXFORM_FLIP_H:
+ case JXFORM_ROT_270:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ break;
+ case JXFORM_FLIP_V:
+ case JXFORM_ROT_90:
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_180:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/* Setup decompression object to save desired markers in memory.
+ * This must be called before jpeg_read_header() to have the desired effect.
+ */
+
+GLOBAL(void)
+jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
+{
+#ifdef SAVE_MARKERS_SUPPORTED
+ int m;
+
+ /* Save comments except under NONE option */
+ if (option != JCOPYOPT_NONE) {
+ jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
+ }
+ /* Save all types of APPn markers iff ALL option */
+ if (option == JCOPYOPT_ALL) {
+ for (m = 0; m < 16; m++)
+ jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
+ }
+#endif /* SAVE_MARKERS_SUPPORTED */
+}
+
+/* Copy markers saved in the given source object to the destination object.
+ * This should be called just after jpeg_start_compress() or
+ * jpeg_write_coefficients().
+ * Note that those routines will have written the SOI, and also the
+ * JFIF APP0 or Adobe APP14 markers if selected.
+ */
+
+GLOBAL(void)
+jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option)
+{
+ jpeg_saved_marker_ptr marker;
+
+ /* In the current implementation, we don't actually need to examine the
+ * option flag here; we just copy everything that got saved.
+ * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
+ * if the encoder library already wrote one.
+ */
+ for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (dstinfo->write_JFIF_header &&
+ marker->marker == JPEG_APP0 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x4A &&
+ GETJOCTET(marker->data[1]) == 0x46 &&
+ GETJOCTET(marker->data[2]) == 0x49 &&
+ GETJOCTET(marker->data[3]) == 0x46 &&
+ GETJOCTET(marker->data[4]) == 0)
+ continue; /* reject duplicate JFIF */
+ if (dstinfo->write_Adobe_marker &&
+ marker->marker == JPEG_APP0+14 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x41 &&
+ GETJOCTET(marker->data[1]) == 0x64 &&
+ GETJOCTET(marker->data[2]) == 0x6F &&
+ GETJOCTET(marker->data[3]) == 0x62 &&
+ GETJOCTET(marker->data[4]) == 0x65)
+ continue; /* reject duplicate Adobe */
+#ifdef NEED_FAR_POINTERS
+ /* We could use jpeg_write_marker if the data weren't FAR... */
+ {
+ unsigned int i;
+ jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
+ for (i = 0; i < marker->data_length; i++)
+ jpeg_write_m_byte(dstinfo, marker->data[i]);
+ }
+#else
+ jpeg_write_marker(dstinfo, marker->marker,
+ marker->data, marker->data_length);
+#endif
+ }
+}
+
+} // namespace Digikam
+
+#else // JPEG_LIB_VERSION >= 80
+
+/*
+ * transupp.c
+ *
+ * Copyright (C) 1997, Thomas G. Lane. <tgl@netcom.com>
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains image transformation routines and other utility code
+ * used by the jpegtran sample application. These are NOT part of the core
+ * JPEG library. But we keep these routines separate from jpegtran.c to
+ * ease the task of maintaining jpegtran-like programs that have other user
+ * interfaces.
+ */
+
+// Local includes.
+
+#include "transupp.h" /* My own external interface */
+
+namespace Digikam
+{
+
+#if TRANSFORMS_SUPPORTED
+
+/*
+ * Lossless image transformation routines. These routines work on DCT
+ * coefficient arrays and thus do not require any lossy decompression
+ * or recompression of the image.
+ * Thanks to Guido Vollbeding for the initial design and code of this feature.
+ *
+ * Horizontal flipping is done in-place, using a single top-to-bottom
+ * pass through the virtual source array. It will thus be much the
+ * fastest option for images larger than main memory.
+ *
+ * The other routines require a set of destination virtual arrays, so they
+ * need twice as much memory as jpegtran normally does. The destination
+ * arrays are always written in normal scan order (top to bottom) because
+ * the virtual array manager expects this. The source arrays will be scanned
+ * in the corresponding order, which means multiple passes through the source
+ * arrays for most of the transforms. That could result in much thrashing
+ * if the image is larger than main memory.
+ *
+ * Some notes about the operating environment of the individual transform
+ * routines:
+ * 1. Both the source and destination virtual arrays are allocated from the
+ * source JPEG object, and therefore should be manipulated by calling the
+ * source's memory manager.
+ * 2. The destination's component count should be used. It may be smaller
+ * than the source's when forcing to grayscale.
+ * 3. Likewise the destination's sampling factors should be used. When
+ * forcing to grayscale the destination's sampling factors will be all 1,
+ * and we may as well take that as the effective iMCU size.
+ * 4. When "trim" is in effect, the destination's dimensions will be the
+ * trimmed values but the source's will be untrimmed.
+ * 5. All the routines assume that the source and destination buffers are
+ * padded out to a full iMCU boundary. This is true, although for the
+ * source buffer it is an undocumented property of jdcoefct.c.
+ * Notes 2,3,4 boil down to this: generally we should use the destination's
+ * dimensions and ignore the source's.
+ */
+
+
+LOCAL(void)
+do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays)
+/* Horizontal flip; done in-place, so no separate dest array is required */
+{
+ JDIMENSION MCU_cols, comp_width, blk_x, blk_y;
+ int ci, k, offset_y;
+ JBLOCKARRAY buffer;
+ JCOEFPTR ptr1, ptr2;
+ JCOEF temp1, temp2;
+ jpeg_component_info *compptr;
+
+ /* Horizontal mirroring of DCT blocks is accomplished by swapping
+ * pairs of blocks in-place. Within a DCT block, we perform horizontal
+ * mirroring by changing the signs of odd-numbered columns.
+ * Partial iMCUs at the right edge are left untouched.
+ */
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ for (blk_y = 0; blk_y < compptr->height_in_blocks;
+ blk_y += compptr->v_samp_factor) {
+ buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y,
+ (JDIMENSION) compptr->v_samp_factor, true);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) {
+ ptr1 = buffer[offset_y][blk_x];
+ ptr2 = buffer[offset_y][comp_width - blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ temp1 = *ptr1; /* swap even column */
+ temp2 = *ptr2;
+ *ptr1++ = temp2;
+ *ptr2++ = temp1;
+ temp1 = *ptr1; /* swap odd column with sign change */
+ temp2 = *ptr2;
+ *ptr1++ = -temp2;
+ *ptr2++ = -temp1;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Vertical flip */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* We output into a separate array because we can't touch different
+ * rows of the source virtual array simultaneously. Otherwise, this
+ * is a pretty straightforward analog of horizontal flip.
+ * Within a DCT block, vertical mirroring is done by changing the signs
+ * of odd-numbered rows.
+ * Partial iMCUs at the bottom edge are copied verbatim.
+ */
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, true);
+ if (dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, false);
+ } else {
+ /* Bottom-edge blocks will be copied verbatim. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, false);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ if (dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* copy even row */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ /* copy odd row with sign change */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ } else {
+ /* Just copy row verbatim. */
+ jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transpose source into destination */
+{
+ JDIMENSION dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Transposing pixels within a block just requires transposing the
+ * DCT coefficients.
+ * Partial iMCUs at the edges require no special treatment; we simply
+ * process all the available DCT blocks for every component.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, true);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, false);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 90 degree rotation is equivalent to
+ * 1. Transposing the image;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) right edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, true);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, false);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ if (dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ dst_ptr = dst_buffer[offset_y]
+ [comp_width - dst_blk_x - offset_x - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 270 degree rotation is equivalent to
+ * 1. Horizontal mirroring;
+ * 2. Transposing the image.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) bottom edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, true);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, false);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (dst_blk_y < comp_height) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 180 degree rotation is equivalent to
+ * 1. Vertical mirroring;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, true);
+ if (dst_blk_y < comp_height) {
+ /* Row is within the vertically mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, false);
+ } else {
+ /* Bottom-edge rows are only mirrored horizontally. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, false);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ if (dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ /* Process the blocks that can be mirrored both ways. */
+ for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* For even row, negate every odd column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ /* For odd row, negate every even column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = - *src_ptr++;
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ }
+ /* Any remaining right-edge blocks are only mirrored vertically. */
+ for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ } else {
+ /* Remaining rows are just mirrored horizontally. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[offset_y];
+ /* Process the blocks that can be mirrored. */
+ for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE2; i += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ /* Any remaining right-edge blocks are only copied. */
+ for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE2; i++)
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transverse transpose is equivalent to
+ * 1. 180 degree rotation;
+ * 2. Transposition;
+ * or
+ * 1. Horizontal mirroring;
+ * 2. Transposition;
+ * 3. Horizontal mirroring.
+ * These steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, true);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, false);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ if (dst_blk_y < comp_height) {
+ src_ptr = src_buffer[offset_x]
+ [comp_height - dst_blk_y - offset_y - 1];
+ if (dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ dst_ptr = dst_buffer[offset_y]
+ [comp_width - dst_blk_x - offset_x - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ i++;
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Right-edge blocks are mirrored in y only */
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ } else {
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ if (dst_blk_x < comp_width) {
+ /* Bottom-edge blocks are mirrored in x only */
+ dst_ptr = dst_buffer[offset_y]
+ [comp_width - dst_blk_x - offset_x - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* At lower right corner, just transpose, no mirroring */
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/* Request any required workspace.
+ *
+ * We allocate the workspace virtual arrays from the source decompression
+ * object, so that all the arrays (both the original data and the workspace)
+ * will be taken into account while making memory management decisions.
+ * Hence, this routine must be called after jpeg_read_header (which reads
+ * the image dimensions) and before jpeg_read_coefficients (which realizes
+ * the source's virtual arrays).
+ */
+
+GLOBAL(void)
+jtransform_request_workspace (j_decompress_ptr srcinfo,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *coef_arrays = NULL;
+ jpeg_component_info *compptr;
+ int ci;
+
+ if (info->force_grayscale &&
+ srcinfo->jpeg_color_space == JCS_YCbCr &&
+ srcinfo->num_components == 3) {
+ /* We'll only process the first component */
+ info->num_components = 1;
+ } else {
+ /* Process all the components */
+ info->num_components = srcinfo->num_components;
+ }
+
+ switch (info->transform) {
+ case JXFORM_NONE:
+ case JXFORM_FLIP_H:
+ /* Don't need a workspace array */
+ break;
+ case JXFORM_FLIP_V:
+ case JXFORM_ROT_180:
+ /* Need workspace arrays having same dimensions as source image.
+ * Note that we allocate arrays padded out to the next iMCU boundary,
+ * so that transform routines need not worry about missing edge blocks.
+ */
+ coef_arrays = (jvirt_barray_ptr *)
+ (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+ SIZEOF(jvirt_barray_ptr) * info->num_components);
+ for (ci = 0; ci < info->num_components; ci++) {
+ compptr = srcinfo->comp_info + ci;
+ coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+ ((j_common_ptr) srcinfo, JPOOL_IMAGE, false,
+ (JDIMENSION) jround_up((long) compptr->width_in_blocks,
+ (long) compptr->h_samp_factor),
+ (JDIMENSION) jround_up((long) compptr->height_in_blocks,
+ (long) compptr->v_samp_factor),
+ (JDIMENSION) compptr->v_samp_factor);
+ }
+ break;
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+ /* Need workspace arrays having transposed dimensions.
+ * Note that we allocate arrays padded out to the next iMCU boundary,
+ * so that transform routines need not worry about missing edge blocks.
+ */
+ coef_arrays = (jvirt_barray_ptr *)
+ (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+ SIZEOF(jvirt_barray_ptr) * info->num_components);
+ for (ci = 0; ci < info->num_components; ci++) {
+ compptr = srcinfo->comp_info + ci;
+ coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+ ((j_common_ptr) srcinfo, JPOOL_IMAGE, false,
+ (JDIMENSION) jround_up((long) compptr->height_in_blocks,
+ (long) compptr->v_samp_factor),
+ (JDIMENSION) jround_up((long) compptr->width_in_blocks,
+ (long) compptr->h_samp_factor),
+ (JDIMENSION) compptr->h_samp_factor);
+ }
+ break;
+ }
+ info->workspace_coef_arrays = coef_arrays;
+}
+
+
+/* Transpose destination image parameters */
+
+LOCAL(void)
+transpose_critical_parameters (j_compress_ptr dstinfo)
+{
+ int tblno, i, j, ci, itemp;
+ jpeg_component_info *compptr;
+ JQUANT_TBL *qtblptr;
+ JDIMENSION dtemp;
+ UINT16 qtemp;
+
+ /* Transpose basic image dimensions */
+ dtemp = dstinfo->image_width;
+ dstinfo->image_width = dstinfo->image_height;
+ dstinfo->image_height = dtemp;
+
+ /* Transpose sampling factors */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ itemp = compptr->h_samp_factor;
+ compptr->h_samp_factor = compptr->v_samp_factor;
+ compptr->v_samp_factor = itemp;
+ }
+
+ /* Transpose quantization tables */
+ for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) {
+ qtblptr = dstinfo->quant_tbl_ptrs[tblno];
+ if (qtblptr != NULL) {
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < i; j++) {
+ qtemp = qtblptr->quantval[i*DCTSIZE+j];
+ qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i];
+ qtblptr->quantval[j*DCTSIZE+i] = qtemp;
+ }
+ }
+ }
+ }
+}
+
+
+/* Trim off any partial iMCUs on the indicated destination edge */
+
+LOCAL(void)
+trim_right_edge (j_compress_ptr dstinfo)
+{
+ int ci, max_h_samp_factor;
+ JDIMENSION MCU_cols;
+
+ /* We have to compute max_h_samp_factor ourselves,
+ * because it hasn't been set yet in the destination
+ * (and we don't want to use the source's value).
+ */
+ max_h_samp_factor = 1;
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor;
+ max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor);
+ }
+ MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE);
+ if (MCU_cols > 0) /* can't trim to 0 pixels */
+ dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE);
+}
+
+LOCAL(void)
+trim_bottom_edge (j_compress_ptr dstinfo)
+{
+ int ci, max_v_samp_factor;
+ JDIMENSION MCU_rows;
+
+ /* We have to compute max_v_samp_factor ourselves,
+ * because it hasn't been set yet in the destination
+ * (and we don't want to use the source's value).
+ */
+ max_v_samp_factor = 1;
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor;
+ max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor);
+ }
+ MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE);
+ if (MCU_rows > 0) /* can't trim to 0 pixels */
+ dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE);
+}
+
+
+/* Adjust output image parameters as needed.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ *
+ * The return value is the set of virtual coefficient arrays to be written
+ * (either the ones allocated by jtransform_request_workspace, or the
+ * original source data arrays). The caller will need to pass this value
+ * to jpeg_write_coefficients().
+ */
+
+GLOBAL(jvirt_barray_ptr *)
+jtransform_adjust_parameters (j_decompress_ptr /*srcinfo*/,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ /* If force-to-grayscale is requested, adjust destination parameters */
+ if (info->force_grayscale) {
+ /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
+ * properly. Among other things, the target h_samp_factor & v_samp_factor
+ * will get set to 1, which typically won't match the source.
+ * In fact we do this even if the source is already grayscale; that
+ * provides an easy way of coercing a grayscale JPEG with funny sampling
+ * factors to the customary 1,1. (Some decoders fail on other factors.)
+ */
+ if ((dstinfo->jpeg_color_space == JCS_YCbCr &&
+ dstinfo->num_components == 3) ||
+ (dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
+ dstinfo->num_components == 1)) {
+ /* We have to preserve the source's quantization table number. */
+ int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
+ jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
+ dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
+ } else {
+ /* Sorry, can't do it */
+ ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
+ }
+ }
+
+ /* Correct the destination's image dimensions etc if necessary */
+ switch (info->transform) {
+ case JXFORM_NONE:
+ /* Nothing to do */
+ break;
+ case JXFORM_FLIP_H:
+ if (info->trim)
+ trim_right_edge(dstinfo);
+ break;
+ case JXFORM_FLIP_V:
+ if (info->trim)
+ trim_bottom_edge(dstinfo);
+ break;
+ case JXFORM_TRANSPOSE:
+ transpose_critical_parameters(dstinfo);
+ /* transpose does NOT have to trim anything */
+ break;
+ case JXFORM_TRANSVERSE:
+ transpose_critical_parameters(dstinfo);
+ if (info->trim) {
+ trim_right_edge(dstinfo);
+ trim_bottom_edge(dstinfo);
+ }
+ break;
+ case JXFORM_ROT_90:
+ transpose_critical_parameters(dstinfo);
+ if (info->trim)
+ trim_right_edge(dstinfo);
+ break;
+ case JXFORM_ROT_180:
+ if (info->trim) {
+ trim_right_edge(dstinfo);
+ trim_bottom_edge(dstinfo);
+ }
+ break;
+ case JXFORM_ROT_270:
+ transpose_critical_parameters(dstinfo);
+ if (info->trim)
+ trim_bottom_edge(dstinfo);
+ break;
+ }
+
+ /* Return the appropriate output data set */
+ if (info->workspace_coef_arrays != NULL)
+ return info->workspace_coef_arrays;
+ return src_coef_arrays;
+}
+
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+GLOBAL(void)
+jtransform_execute_transformation (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;
+
+ switch (info->transform) {
+ case JXFORM_NONE:
+ break;
+ case JXFORM_FLIP_H:
+ do_flip_h(srcinfo, dstinfo, src_coef_arrays);
+ break;
+ case JXFORM_FLIP_V:
+ do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSPOSE:
+ do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSVERSE:
+ do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_90:
+ do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_180:
+ do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_270:
+ do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ }
+}
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/* Setup decompression object to save desired markers in memory.
+ * This must be called before jpeg_read_header() to have the desired effect.
+ */
+
+GLOBAL(void)
+jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
+{
+#ifdef SAVE_MARKERS_SUPPORTED
+ int m;
+
+ /* Save comments except under NONE option */
+ if (option != JCOPYOPT_NONE) {
+ jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
+ }
+ /* Save all types of APPn markers iff ALL option */
+ if (option == JCOPYOPT_ALL) {
+ for (m = 0; m < 16; m++)
+ jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
+ }
+#endif /* SAVE_MARKERS_SUPPORTED */
+}
+
+/* Copy markers saved in the given source object to the destination object.
+ * This should be called just after jpeg_start_compress() or
+ * jpeg_write_coefficients().
+ * Note that those routines will have written the SOI, and also the
+ * JFIF APP0 or Adobe APP14 markers if selected.
+ */
+
+GLOBAL(void)
+jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION /*option*/)
+{
+ jpeg_saved_marker_ptr marker;
+
+ /* In the current implementation, we don't actually need to examine the
+ * option flag here; we just copy everything that got saved.
+ * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
+ * if the encoder library already wrote one.
+ */
+ for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (dstinfo->write_JFIF_header &&
+ marker->marker == JPEG_APP0 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x4A &&
+ GETJOCTET(marker->data[1]) == 0x46 &&
+ GETJOCTET(marker->data[2]) == 0x49 &&
+ GETJOCTET(marker->data[3]) == 0x46 &&
+ GETJOCTET(marker->data[4]) == 0)
+ continue; /* reject duplicate JFIF */
+ if (dstinfo->write_Adobe_marker &&
+ marker->marker == JPEG_APP0+14 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x41 &&
+ GETJOCTET(marker->data[1]) == 0x64 &&
+ GETJOCTET(marker->data[2]) == 0x6F &&
+ GETJOCTET(marker->data[3]) == 0x62 &&
+ GETJOCTET(marker->data[4]) == 0x65)
+ continue; /* reject duplicate Adobe */
+#ifdef NEED_FAR_POINTERS
+ /* We could use jpeg_write_marker if the data weren't FAR... */
+ {
+ unsigned int i;
+ jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
+ for (i = 0; i < marker->data_length; i++)
+ jpeg_write_m_byte(dstinfo, marker->data[i]);
+ }
+#else
+ jpeg_write_marker(dstinfo, marker->marker,
+ marker->data, marker->data_length);
+#endif
+ }
+}
+
+} // namespace Digikam
+
+#endif // JPEG_LIB_VERSION >= 80
diff --git a/src/libs/jpegutils/transupp.h b/src/libs/jpegutils/transupp.h
new file mode 100644
index 00000000..7dd3fad6
--- /dev/null
+++ b/src/libs/jpegutils/transupp.h
@@ -0,0 +1,373 @@
+#if JPEG_LIB_VERSION >= 80
+
+/*
+ * transupp.h
+ *
+ * Copyright (C) 1997-2009, Thomas G. Lane, Guido Vollbeding.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains declarations for image transformation routines and
+ * other utility code used by the jpegtran sample application. These are
+ * NOT part of the core JPEG library. But we keep these routines separate
+ * from jpegtran.c to ease the task of maintaining jpegtran-like programs
+ * that have other user interfaces.
+ *
+ * NOTE: all the routines declared here have very specific requirements
+ * about when they are to be executed during the reading and writing of the
+ * source and destination files. See the comments in transupp.c, or see
+ * jpegtran.c for an example of correct usage.
+ */
+
+#ifndef TRANSUPP_H
+#define TRANSUPP_H
+
+namespace Digikam
+{
+
+/* If you happen not to want the image transform support, disable it here */
+#ifndef TRANSFORMS_SUPPORTED
+#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */
+#endif
+
+/*
+ * Although rotating and flipping data expressed as DCT coefficients is not
+ * hard, there is an asymmetry in the JPEG format specification for images
+ * whose dimensions aren't multiples of the iMCU size. The right and bottom
+ * image edges are padded out to the next iMCU boundary with junk data; but
+ * no padding is possible at the top and left edges. If we were to flip
+ * the whole image including the pad data, then pad garbage would become
+ * visible at the top and/or left, and real pixels would disappear into the
+ * pad margins --- perhaps permanently, since encoders & decoders may not
+ * bother to preserve DCT blocks that appear to be completely outside the
+ * nominal image area. So, we have to exclude any partial iMCUs from the
+ * basic transformation.
+ *
+ * Transpose is the only transformation that can handle partial iMCUs at the
+ * right and bottom edges completely cleanly. flip_h can flip partial iMCUs
+ * at the bottom, but leaves any partial iMCUs at the right edge untouched.
+ * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched.
+ * The other transforms are defined as combinations of these basic transforms
+ * and process edge blocks in a way that preserves the equivalence.
+ *
+ * The "trim" option causes untransformable partial iMCUs to be dropped;
+ * this is not strictly lossless, but it usually gives the best-looking
+ * result for odd-size images. Note that when this option is active,
+ * the expected mathematical equivalences between the transforms may not hold.
+ * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim
+ * followed by -rot 180 -trim trims both edges.)
+ *
+ * We also offer a lossless-crop option, which discards data outside a given
+ * image region but losslessly preserves what is inside. Like the rotate and
+ * flip transforms, lossless crop is restricted by the JPEG format: the upper
+ * left corner of the selected region must fall on an iMCU boundary. If this
+ * does not hold for the given crop parameters, we silently move the upper left
+ * corner up and/or left to make it so, simultaneously increasing the region
+ * dimensions to keep the lower right crop corner unchanged. (Thus, the
+ * output image covers at least the requested region, but may cover more.)
+ *
+ * We also provide a lossless-resize option, which is kind of a lossless-crop
+ * operation in the DCT coefficient block domain - it discards higher-order
+ * coefficients and losslessly preserves lower-order coefficients of a
+ * sub-block.
+ *
+ * Rotate/flip transform, resize, and crop can be requested together in a
+ * single invocation. The crop is applied last --- that is, the crop region
+ * is specified in terms of the destination image after transform/resize.
+ *
+ * We also offer a "force to grayscale" option, which simply discards the
+ * chrominance channels of a YCbCr image. This is lossless in the sense that
+ * the luminance channel is preserved exactly. It's not the same kind of
+ * thing as the rotate/flip transformations, but it's convenient to handle it
+ * as part of this package, mainly because the transformation routines have to
+ * be aware of the option to know how many components to work on.
+ */
+
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jtransform_parse_crop_spec jTrParCrop
+#define jtransform_request_workspace jTrRequest
+#define jtransform_adjust_parameters jTrAdjust
+#define jtransform_execute_transform jTrExec
+#define jtransform_perfect_transform jTrPerfect
+#define jcopy_markers_setup jCMrkSetup
+#define jcopy_markers_execute jCMrkExec
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+
+/*
+ * Codes for supported types of image transformations.
+ */
+
+typedef enum {
+ JXFORM_NONE, /* no transformation */
+ JXFORM_FLIP_H, /* horizontal flip */
+ JXFORM_FLIP_V, /* vertical flip */
+ JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */
+ JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */
+ JXFORM_ROT_90, /* 90-degree clockwise rotation */
+ JXFORM_ROT_180, /* 180-degree rotation */
+ JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+/*
+ * Codes for crop parameters, which can individually be unspecified,
+ * positive, or negative. (Negative width or height makes no sense, though.)
+ */
+
+typedef enum {
+ JCROP_UNSET,
+ JCROP_POS,
+ JCROP_NEG
+} JCROP_CODE;
+
+/*
+ * Transform parameters struct.
+ * NB: application must not change any elements of this struct after
+ * calling jtransform_request_workspace.
+ */
+
+typedef struct {
+ /* Options: set by caller */
+ JXFORM_CODE transform; /* image transform operator */
+ boolean perfect; /* if TRUE, fail if partial MCUs are requested */
+ boolean trim; /* if TRUE, trim partial MCUs as needed */
+ boolean force_grayscale; /* if TRUE, convert color image to grayscale */
+ boolean crop; /* if TRUE, crop source image */
+
+ /* Crop parameters: application need not set these unless crop is TRUE.
+ * These can be filled in by jtransform_parse_crop_spec().
+ */
+ JDIMENSION crop_width; /* Width of selected region */
+ JCROP_CODE crop_width_set;
+ JDIMENSION crop_height; /* Height of selected region */
+ JCROP_CODE crop_height_set;
+ JDIMENSION crop_xoffset; /* X offset of selected region */
+ JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */
+ JDIMENSION crop_yoffset; /* Y offset of selected region */
+ JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */
+
+ /* Internal workspace: caller should not touch these */
+ int num_components; /* # of components in workspace */
+ jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
+ JDIMENSION output_width; /* cropped destination dimensions */
+ JDIMENSION output_height;
+ JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */
+ JDIMENSION y_crop_offset;
+ int iMCU_sample_width; /* destination iMCU size */
+ int iMCU_sample_height;
+} jpeg_transform_info;
+
+
+#if TRANSFORMS_SUPPORTED
+
+/* Parse a crop specification (written in X11 geometry style) */
+EXTERN(boolean) jtransform_parse_crop_spec
+ JPP((jpeg_transform_info *info, const char *spec));
+/* Request any required workspace */
+EXTERN(boolean) jtransform_request_workspace
+ JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info));
+/* Adjust output image parameters */
+EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info));
+/* Execute the actual transformation, if any */
+EXTERN(void) jtransform_execute_transform
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info));
+/* Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ */
+EXTERN(boolean) jtransform_perfect_transform
+ JPP((JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform));
+
+/* jtransform_execute_transform used to be called
+ * jtransform_execute_transformation, but some compilers complain about
+ * routine names that long. This macro is here to avoid breaking any
+ * old source code that uses the original name...
+ */
+#define jtransform_execute_transformation jtransform_execute_transform
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/*
+ * Support for copying optional markers from source to destination file.
+ */
+
+typedef enum {
+ JCOPYOPT_NONE, /* copy no optional markers */
+ JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */
+ JCOPYOPT_ALL /* copy all optional markers */
+} JCOPY_OPTION;
+
+#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */
+
+/* Setup decompression object to save desired markers in memory */
+EXTERN(void) jcopy_markers_setup
+ JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option));
+/* Copy markers saved in the given source object to the destination object */
+EXTERN(void) jcopy_markers_execute
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option));
+
+
+} // namespace DigiKam
+
+#endif // TRANSUPP_H
+
+#else // JPEG_LIB_VERSION >= 80
+
+/*
+ * transupp.h
+ *
+ * Copyright (C) 1997, Thomas G. Lane. <tgl@netcom.com>
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains declarations for image transformation routines and
+ * other utility code used by the jpegtran sample application. These are
+ * NOT part of the core JPEG library. But we keep these routines separate
+ * from jpegtran.c to ease the task of maintaining jpegtran-like programs
+ * that have other user interfaces.
+ *
+ * NOTE: all the routines declared here have very specific requirements
+ * about when they are to be executed during the reading and writing of the
+ * source and destination files. See the comments in transupp.c, or see
+ * jpegtran.c for an example of correct usage.
+ */
+
+#ifndef TRANSUPP_H
+#define TRANSUPP_H
+
+namespace Digikam
+{
+
+/* If you happen not to want the image transform support, disable it here */
+#ifndef TRANSFORMS_SUPPORTED
+#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */
+#endif
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jtransform_request_workspace jTrRequest
+#define jtransform_adjust_parameters jTrAdjust
+#define jtransform_execute_transformation jTrExec
+#define jcopy_markers_setup jCMrkSetup
+#define jcopy_markers_execute jCMrkExec
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+
+/*
+ * Codes for supported types of image transformations.
+ */
+
+typedef enum {
+ JXFORM_NONE, /* no transformation */
+ JXFORM_FLIP_H, /* horizontal flip */
+ JXFORM_FLIP_V, /* vertical flip */
+ JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */
+ JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */
+ JXFORM_ROT_90, /* 90-degree clockwise rotation */
+ JXFORM_ROT_180, /* 180-degree rotation */
+ JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+/*
+ * Although rotating and flipping data expressed as DCT coefficients is not
+ * hard, there is an asymmetry in the JPEG format specification for images
+ * whose dimensions aren't multiples of the iMCU size. The right and bottom
+ * image edges are padded out to the next iMCU boundary with junk data; but
+ * no padding is possible at the top and left edges. If we were to flip
+ * the whole image including the pad data, then pad garbage would become
+ * visible at the top and/or left, and real pixels would disappear into the
+ * pad margins --- perhaps permanently, since encoders & decoders may not
+ * bother to preserve DCT blocks that appear to be completely outside the
+ * nominal image area. So, we have to exclude any partial iMCUs from the
+ * basic transformation.
+ *
+ * Transpose is the only transformation that can handle partial iMCUs at the
+ * right and bottom edges completely cleanly. flip_h can flip partial iMCUs
+ * at the bottom, but leaves any partial iMCUs at the right edge untouched.
+ * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched.
+ * The other transforms are defined as combinations of these basic transforms
+ * and process edge blocks in a way that preserves the equivalence.
+ *
+ * The "trim" option causes untransformable partial iMCUs to be dropped;
+ * this is not strictly lossless, but it usually gives the best-looking
+ * result for odd-size images. Note that when this option is active,
+ * the expected mathematical equivalences between the transforms may not hold.
+ * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim
+ * followed by -rot 180 -trim trims both edges.)
+ *
+ * We also offer a "force to grayscale" option, which simply discards the
+ * chrominance channels of a YCbCr image. This is lossless in the sense that
+ * the luminance channel is preserved exactly. It's not the same kind of
+ * thing as the rotate/flip transformations, but it's convenient to handle it
+ * as part of this package, mainly because the transformation routines have to
+ * be aware of the option to know how many components to work on.
+ */
+
+typedef struct {
+ /* Options: set by caller */
+ JXFORM_CODE transform; /* image transform operator */
+ boolean trim; /* if TRUE, trim partial MCUs as needed */
+ boolean force_grayscale; /* if TRUE, convert color image to grayscale */
+
+ /* Internal workspace: caller should not touch these */
+ int num_components; /* # of components in workspace */
+ jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
+} jpeg_transform_info;
+
+
+#if TRANSFORMS_SUPPORTED
+
+/* Request any required workspace */
+EXTERN(void) jtransform_request_workspace
+ JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info));
+/* Adjust output image parameters */
+EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info));
+/* Execute the actual transformation, if any */
+EXTERN(void) jtransform_execute_transformation
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info));
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/*
+ * Support for copying optional markers from source to destination file.
+ */
+
+typedef enum {
+ JCOPYOPT_NONE, /* copy no optional markers */
+ JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */
+ JCOPYOPT_ALL /* copy all optional markers */
+} JCOPY_OPTION;
+
+#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */
+
+/* Setup decompression object to save desired markers in memory */
+EXTERN(void) jcopy_markers_setup
+ JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option));
+/* Copy markers saved in the given source object to the destination object */
+EXTERN(void) jcopy_markers_execute
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option));
+
+} // namespace DigiKam
+
+#endif // TRANSUPP_H
+
+#endif // JPEG_LIB_VERSION >= 80
diff --git a/src/libs/levels/Makefile.am b/src/libs/levels/Makefile.am
new file mode 100644
index 00000000..d86db7c3
--- /dev/null
+++ b/src/libs/levels/Makefile.am
@@ -0,0 +1,17 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = liblevels.la
+
+liblevels_la_SOURCES = imagelevels.cpp
+
+liblevels_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/ \
+ -I$(top_srcdir)/src/digikam \
+ $(all_includes)
+
+digikaminclude_HEADERS = imagelevels.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/levels/imagelevels.cpp b/src/libs/levels/imagelevels.cpp
new file mode 100644
index 00000000..ac995927
--- /dev/null
+++ b/src/libs/levels/imagelevels.cpp
@@ -0,0 +1,715 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-29
+ * Description : image levels manipulation methods.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Some code parts are inspired from gimp 2.0
+ * app/base/levels.c, gimplut.c, and app/base/gimpleveltool.c
+ * source files.
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfile.h>
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+#include <cstring>
+#include <cstdlib>
+#include <cerrno>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "imagelevels.h"
+
+namespace Digikam
+{
+
+class ImageLevelsPriv
+{
+
+public:
+
+ enum PixelType
+ {
+ RedPixel = 0,
+ GreenPixel,
+ BluePixel,
+ AlphaPixel
+ };
+
+ struct _Levels
+ {
+ double gamma[5];
+
+ int low_input[5];
+ int high_input[5];
+
+ int low_output[5];
+ int high_output[5];
+ };
+
+ struct _Lut
+ {
+ unsigned short **luts;
+ int nchannels;
+ };
+
+public:
+
+ ImageLevelsPriv()
+ {
+ levels = 0;
+ lut = 0;
+ dirty = false;
+ }
+
+ // Levels data.
+ struct _Levels *levels;
+
+ // Lut data.
+ struct _Lut *lut;
+
+ bool sixteenBit;
+ bool dirty;
+};
+
+ImageLevels::ImageLevels(bool sixteenBit)
+{
+ d = new ImageLevelsPriv;
+ d->lut = new ImageLevelsPriv::_Lut;
+ d->levels = new ImageLevelsPriv::_Levels;
+ d->sixteenBit = sixteenBit;
+
+ memset(d->levels, 0, sizeof(struct ImageLevelsPriv::_Levels));
+ d->lut->luts = NULL;
+ d->lut->nchannels = 0;
+
+ reset();
+}
+
+ImageLevels::~ImageLevels()
+{
+ if (d->lut)
+ {
+ if (d->lut->luts)
+ {
+ for (int i = 0 ; i < d->lut->nchannels ; i++)
+ delete [] d->lut->luts[i];
+
+ delete [] d->lut->luts;
+ }
+
+ delete d->lut;
+ }
+
+ if (d->levels)
+ delete d->levels;
+
+ delete d;
+}
+
+bool ImageLevels::isDirty()
+{
+ return d->dirty;
+}
+
+bool ImageLevels::isSixteenBits()
+{
+ return d->sixteenBit;
+}
+
+void ImageLevels::reset()
+{
+ for (int channel = 0 ; channel < 5 ; channel++)
+ levelsChannelReset(channel);
+}
+
+void ImageLevels::levelsChannelReset(int channel)
+{
+ if (!d->levels) return;
+
+ d->levels->gamma[channel] = 1.0;
+ d->levels->low_input[channel] = 0;
+ d->levels->high_input[channel] = d->sixteenBit ? 65535 : 255;
+ d->levels->low_output[channel] = 0;
+ d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255;
+ d->dirty = false;
+}
+
+void ImageLevels::levelsAuto(ImageHistogram *hist)
+{
+ if (!d->levels || !hist) return;
+
+ levelsChannelReset(ImageHistogram::ValueChannel);
+
+ for (int channel = ImageHistogram::RedChannel ;
+ channel <= ImageHistogram::BlueChannel ;
+ channel++)
+ {
+ levelsChannelAuto(hist, channel);
+ }
+ d->dirty = true;
+}
+
+void ImageLevels::levelsChannelAuto(ImageHistogram *hist, int channel)
+{
+ int i;
+ double count, new_count, percentage, next_percentage;
+
+ if (!d->levels || !hist) return;
+
+ d->levels->gamma[channel] = 1.0;
+ d->levels->low_output[channel] = 0;
+ d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255;
+
+ count = hist->getCount(channel, 0, d->sixteenBit ? 65535 : 255);
+
+ if (count == 0.0)
+ {
+ d->levels->low_input[channel] = 0;
+ d->levels->high_input[channel] = 0;
+ }
+ else
+ {
+ // Set the low input
+
+ new_count = 0.0;
+
+ for (i = 0 ; i < (d->sixteenBit ? 65535 : 255) ; i++)
+ {
+ new_count += hist->getValue(channel, i);
+ percentage = new_count / count;
+ next_percentage = (new_count + hist->getValue(channel, i + 1)) / count;
+
+ if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
+ {
+ d->levels->low_input[channel] = i + 1;
+ break;
+ }
+ }
+
+ // Set the high input
+
+ new_count = 0.0;
+
+ for (i = (d->sixteenBit ? 65535 : 255) ; i > 0 ; i--)
+ {
+ new_count += hist->getValue(channel, i);
+ percentage = new_count / count;
+ next_percentage = (new_count + hist->getValue(channel, i - 1)) / count;
+
+ if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
+ {
+ d->levels->high_input[channel] = i - 1;
+ break;
+ }
+ }
+ }
+ d->dirty = true;
+}
+
+int ImageLevels::levelsInputFromColor(int channel, const DColor& color)
+{
+ switch (channel)
+ {
+ case ImageHistogram::ValueChannel:
+ return TQMAX (TQMAX (color.red(), color.green()), color.blue());
+
+ case ImageHistogram::RedChannel:
+ return color.red();
+
+ case ImageHistogram::GreenChannel:
+ return color.green();
+
+ case ImageHistogram::BlueChannel:
+ return color.blue();
+ }
+
+ return 0; // just to please the compiler.
+}
+
+void ImageLevels::levelsBlackToneAdjustByColors(int channel, const DColor& color)
+{
+ if (!d->levels) return;
+
+ d->levels->low_input[channel] = levelsInputFromColor(channel, color);
+ d->dirty = true;
+}
+
+void ImageLevels::levelsWhiteToneAdjustByColors(int channel, const DColor& color)
+{
+ if (!d->levels) return;
+
+ d->levels->high_input[channel] = levelsInputFromColor(channel, color);
+ d->dirty = true;
+}
+
+void ImageLevels::levelsGrayToneAdjustByColors(int channel, const DColor& color)
+{
+ if (!d->levels) return;
+
+ int input;
+ int range;
+ double inten;
+ double out_light;
+ unsigned short lightness;
+
+ // Calculate lightness value.
+
+ lightness = (unsigned short)LEVELS_RGB_INTENSITY (color.red(), color.green(), color.blue());
+
+ input = levelsInputFromColor(channel, color);
+
+ range = d->levels->high_input[channel] - d->levels->low_input[channel];
+
+ if (range <= 0)
+ return;
+
+ input -= d->levels->low_input[channel];
+
+ if (input < 0)
+ return;
+
+ // Normalize input and lightness.
+
+ inten = (double) input / (double) range;
+ out_light = (double) lightness/ (double) range;
+
+ if (out_light <= 0)
+ return;
+
+ // Map selected color to corresponding lightness.
+
+ d->levels->gamma[channel] = log (inten) / log (out_light);
+ d->dirty = true;
+}
+
+void ImageLevels::levelsCalculateTransfers()
+{
+ double inten;
+ int i, j;
+
+ if (!d->levels) return;
+
+ // Recalculate the levels arrays.
+
+ for (j = 0 ; j < 5 ; j++)
+ {
+ for (i = 0; i <= (d->sixteenBit ? 65535 : 255); i++)
+ {
+ // determine input intensity.
+
+ if (d->levels->high_input[j] != d->levels->low_input[j])
+ {
+ inten = ((double) (i - d->levels->low_input[j]) /
+ (double) (d->levels->high_input[j] - d->levels->low_input[j]));
+ }
+ else
+ {
+ inten = (double) (i - d->levels->low_input[j]);
+ }
+
+ inten = CLAMP (inten, 0.0, 1.0);
+
+ if (d->levels->gamma[j] != 0.0)
+ inten = pow (inten, (1.0 / d->levels->gamma[j]));
+ }
+ }
+}
+
+float ImageLevels::levelsLutFunc(int n_channels, int channel, float value)
+{
+ double inten;
+ int j;
+
+ if (!d->levels) return 0.0;
+
+ if (n_channels == 1)
+ j = 0;
+ else
+ j = channel + 1;
+
+ inten = value;
+
+ // For color images this runs through the loop with j = channel +1
+ // the first time and j = 0 the second time.
+ //
+ // For bw images this runs through the loop with j = 0 the first and
+ // only time.
+
+ for ( ; j >= 0 ; j -= (channel + 1) )
+ {
+ // Don't apply the overall curve to the alpha channel.
+
+ if (j == 0 && (n_channels == 2 || n_channels == 4)
+ && channel == n_channels -1)
+ return inten;
+
+ // Determine input intensity.
+
+ if (d->levels->high_input[j] != d->levels->low_input[j])
+ inten = ((double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]) /
+ (double) (d->levels->high_input[j] - d->levels->low_input[j]));
+ else
+ inten = (double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]);
+
+ if (d->levels->gamma[j] != 0.0)
+ {
+ if (inten >= 0.0)
+ inten = pow ( inten, (1.0 / d->levels->gamma[j]));
+ else
+ inten = -pow (-inten, (1.0 / d->levels->gamma[j]));
+ }
+
+ // determine the output intensity.
+
+ if (d->levels->high_output[j] >= d->levels->low_output[j])
+ inten = (double) (inten * (d->levels->high_output[j] -
+ d->levels->low_output[j]) + d->levels->low_output[j]);
+
+ else if (d->levels->high_output[j] < d->levels->low_output[j])
+ inten = (double) (d->levels->low_output[j] - inten *
+ (d->levels->low_output[j] - d->levels->high_output[j]));
+
+ inten /= (float)(d->sixteenBit ? 65535 : 255);
+ }
+
+ return inten;
+}
+
+void ImageLevels::levelsLutSetup(int nchannels)
+{
+ int i;
+ uint v;
+ double val;
+
+ if (d->lut->luts)
+ {
+ for (i = 0 ; i < d->lut->nchannels ; i++)
+ delete [] d->lut->luts[i];
+
+ delete [] d->lut->luts;
+ }
+
+ d->lut->nchannels = nchannels;
+ d->lut->luts = new unsigned short*[d->lut->nchannels];
+
+ for (i = 0 ; i < d->lut->nchannels ; i++)
+ {
+ d->lut->luts[i] = new unsigned short[(d->sixteenBit ? 65535 : 255) + 1];
+
+ for (v = 0 ; v <= (d->sixteenBit ? 65535 : 255) ; v++)
+ {
+ // to add gamma correction use func(v ^ g) ^ 1/g instead.
+
+ val = (float)(d->sixteenBit ? 65535 : 255) *
+ levelsLutFunc( d->lut->nchannels, i, v/(float)(d->sixteenBit ? 65535 : 255)) + 0.5;
+
+ d->lut->luts[i][v] = (unsigned short)CLAMP (val, 0, (d->sixteenBit ? 65535 : 255));
+ }
+ }
+}
+
+void ImageLevels::levelsLutProcess(uchar *srcPR, uchar *destPR, int w, int h)
+{
+ unsigned short *lut0 = NULL, *lut1 = NULL, *lut2 = NULL, *lut3 = NULL;
+
+ int i;
+
+ if (d->lut->nchannels > 0)
+ lut0 = d->lut->luts[0];
+ if (d->lut->nchannels > 1)
+ lut1 = d->lut->luts[1];
+ if (d->lut->nchannels > 2)
+ lut2 = d->lut->luts[2];
+ if (d->lut->nchannels > 3)
+ lut3 = d->lut->luts[3];
+
+ if (!d->sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue, alpha;
+ uchar *ptr = srcPR;
+ uchar *dst = destPR;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if ( d->lut->nchannels > 0 )
+ red = lut0[red];
+
+ if ( d->lut->nchannels > 1 )
+ green = lut1[green];
+
+ if ( d->lut->nchannels > 2 )
+ blue = lut2[blue];
+
+ if ( d->lut->nchannels > 3 )
+ alpha = lut3[alpha];
+
+ dst[0] = blue;
+ dst[1] = green;
+ dst[2] = red;
+ dst[3] = alpha;
+
+ ptr += 4;
+ dst += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue, alpha;
+ unsigned short *ptr = (unsigned short *)srcPR;
+ unsigned short *dst = (unsigned short *)destPR;
+
+ for (i = 0 ; i < w*h ; i++)
+ {
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+ alpha = ptr[3];
+
+ if ( d->lut->nchannels > 0 )
+ red = lut0[red];
+
+ if ( d->lut->nchannels > 1 )
+ green = lut1[green];
+
+ if ( d->lut->nchannels > 2 )
+ blue = lut2[blue];
+
+ if ( d->lut->nchannels > 3 )
+ alpha = lut3[alpha];
+
+ dst[0] = blue;
+ dst[1] = green;
+ dst[2] = red;
+ dst[3] = alpha;
+
+ ptr += 4;
+ dst += 4;
+ }
+ }
+}
+
+void ImageLevels::setLevelGammaValue(int Channel, double val)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ {
+ d->levels->gamma[Channel] = val;
+ d->dirty = true;
+ }
+}
+
+void ImageLevels::setLevelLowInputValue(int Channel, int val)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ {
+ d->levels->low_input[Channel] = val;
+ d->dirty = true;
+ }
+}
+
+void ImageLevels::setLevelHighInputValue(int Channel, int val)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ {
+ d->levels->high_input[Channel] = val;
+ d->dirty = true;
+ }
+}
+
+void ImageLevels::setLevelLowOutputValue(int Channel, int val)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ {
+ d->levels->low_output[Channel] = val;
+ d->dirty = true;
+ }
+}
+
+void ImageLevels::setLevelHighOutputValue(int Channel, int val)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ {
+ d->levels->high_output[Channel] = val;
+ d->dirty = true;
+ }
+}
+
+double ImageLevels::getLevelGammaValue(int Channel)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ return (d->levels->gamma[Channel]);
+
+ return 0.0;
+}
+
+int ImageLevels::getLevelLowInputValue(int Channel)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ return (d->levels->low_input[Channel]);
+
+ return 0;
+}
+
+int ImageLevels::getLevelHighInputValue(int Channel)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ return (d->levels->high_input[Channel]);
+
+ return 0;
+}
+
+int ImageLevels::getLevelLowOutputValue(int Channel)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ return (d->levels->low_output[Channel]);
+
+ return 0;
+}
+
+int ImageLevels::getLevelHighOutputValue(int Channel)
+{
+ if ( d->levels && Channel>=0 && Channel<5 )
+ return (d->levels->high_output[Channel]);
+
+ return 0;
+}
+
+bool ImageLevels::loadLevelsFromGimpLevelsFile(const KURL& fileUrl)
+{
+ // TODO : support KURL !
+
+ FILE *file;
+ int low_input[5];
+ int high_input[5];
+ int low_output[5];
+ int high_output[5];
+ double gamma[5];
+ int i, fields;
+ char buf[50];
+ char *nptr;
+
+ file = fopen(TQFile::encodeName(fileUrl.path()), "r");
+
+ if (!file)
+ return false;
+
+ if (! fgets (buf, sizeof (buf), file))
+ {
+ fclose(file);
+ return false;
+ }
+
+ if (strcmp (buf, "# GIMP Levels File\n") != 0)
+ {
+ fclose(file);
+ return false;
+ }
+
+ for (i = 0 ; i < 5 ; i++)
+ {
+ fields = fscanf (file, "%d %d %d %d ",
+ &low_input[i],
+ &high_input[i],
+ &low_output[i],
+ &high_output[i]);
+
+ if (fields != 4)
+ {
+ DWarning() << "Invalid Gimp levels file!" << endl;
+ fclose(file);
+ return false;
+ }
+
+ if (!fgets (buf, 50, file))
+ {
+ DWarning() << "Invalid Gimp levels file!" << endl;
+ fclose(file);
+ return false;
+ }
+
+ gamma[i] = strtod (buf, &nptr);
+
+ if (buf == nptr || errno == ERANGE)
+ {
+ DWarning() << "Invalid Gimp levels file!" << endl;
+ fclose(file);
+ return false;
+ }
+ }
+
+ for (i = 0 ; i < 5 ; i++)
+ {
+ setLevelGammaValue(i, gamma[i]);
+ setLevelLowInputValue(i, d->sixteenBit ? low_input[i]*255 : low_input[i]);
+ setLevelHighInputValue(i, d->sixteenBit ? high_input[i]*255 : high_input[i]);
+ setLevelLowOutputValue(i, d->sixteenBit ? low_output[i]*255 : low_output[i]);
+ setLevelHighOutputValue(i, d->sixteenBit ? high_output[i]*255 : high_output[i]);
+ }
+
+ fclose(file);
+ return true;
+}
+
+bool ImageLevels::saveLevelsToGimpLevelsFile(const KURL& fileUrl)
+{
+ // TODO : support KURL !
+
+ FILE *file;
+ int i;
+
+ file = fopen(TQFile::encodeName(fileUrl.path()), "w");
+
+ if (!file)
+ return false;
+
+ fprintf (file, "# GIMP Levels File\n");
+
+ for (i = 0 ; i < 5 ; i++)
+ {
+ char buf[256];
+ sprintf (buf, "%f", getLevelGammaValue(i));
+
+ fprintf (file, "%d %d %d %d %s\n",
+ d->sixteenBit ? getLevelLowInputValue(i)/255 : getLevelLowInputValue(i),
+ d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i),
+ d->sixteenBit ? getLevelLowOutputValue(i)/255 : getLevelLowOutputValue(i),
+ d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i),
+ buf);
+ }
+
+ fflush(file);
+ fclose(file);
+
+ return true;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/levels/imagelevels.h b/src/libs/levels/imagelevels.h
new file mode 100644
index 00000000..04d10d97
--- /dev/null
+++ b/src/libs/levels/imagelevels.h
@@ -0,0 +1,105 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-29
+ * Description : image levels manipulation methods.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGELEVELS_H
+#define IMAGELEVELS_H
+
+#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
+
+/* Map RGB to intensity */
+
+#define LEVELS_RGB_INTENSITY_RED 0.30
+#define LEVELS_RGB_INTENSITY_GREEN 0.59
+#define LEVELS_RGB_INTENSITY_BLUE 0.11
+#define LEVELS_RGB_INTENSITY(r,g,b) ((r) * LEVELS_RGB_INTENSITY_RED + \
+ (g) * LEVELS_RGB_INTENSITY_GREEN + \
+ (b) * LEVELS_RGB_INTENSITY_BLUE)
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "dcolor.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImageLevelsPriv;
+class ImageHistogram;
+
+class DIGIKAM_EXPORT ImageLevels
+{
+
+public:
+
+ ImageLevels(bool sixteenBit);
+ ~ImageLevels();
+
+ bool isDirty();
+ bool isSixteenBits();
+ void reset();
+
+ // Methods for to manipulate the levels data.
+
+ void levelsChannelReset(int channel);
+ void levelsAuto(ImageHistogram *hist);
+ void levelsChannelAuto(ImageHistogram *hist, int channel);
+ int levelsInputFromColor(int channel, const DColor& color);
+ void levelsBlackToneAdjustByColors(int channel, const DColor& color);
+ void levelsGrayToneAdjustByColors(int channel, const DColor& color);
+ void levelsWhiteToneAdjustByColors(int channel, const DColor& color);
+ void levelsCalculateTransfers();
+ float levelsLutFunc(int n_channels, int channel, float value);
+ void levelsLutSetup(int nchannels);
+ void levelsLutProcess(uchar *srcPR, uchar *destPR, int w, int h);
+
+ // Methods for to set manually the levels values.
+
+ void setLevelGammaValue(int Channel, double val);
+ void setLevelLowInputValue(int Channel, int val);
+ void setLevelHighInputValue(int Channel, int val);
+ void setLevelLowOutputValue(int Channel, int val);
+ void setLevelHighOutputValue(int Channel, int val);
+
+ double getLevelGammaValue(int Channel);
+ int getLevelLowInputValue(int Channel);
+ int getLevelHighInputValue(int Channel);
+ int getLevelLowOutputValue(int Channel);
+ int getLevelHighOutputValue(int Channel);
+
+ // Methods for to save/load the levels values to/from a Gimp levels text file.
+
+ bool saveLevelsToGimpLevelsFile(const KURL& fileUrl);
+ bool loadLevelsFromGimpLevelsFile(const KURL& fileUrl);
+
+private:
+
+ ImageLevelsPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGELEVELS_H */
diff --git a/src/libs/lprof/Makefile.am b/src/libs/lprof/Makefile.am
new file mode 100644
index 00000000..e7251d2c
--- /dev/null
+++ b/src/libs/lprof/Makefile.am
@@ -0,0 +1,15 @@
+# Gilles Caulier 12/01/06: lprof implementation is in C not C++ and do not
+# support 'nofinal' compilation option.
+KDE_OPTIONS = nofinal
+
+INCLUDES = $(all_includes)
+
+noinst_LTLIBRARIES = liblprof.la
+
+noinst_HEADERS = lcmsprf.h
+
+liblprof_la_CXXFLAGS = -w -fomit-frame-pointer
+
+liblprof_la_SOURCES = cmshull.cpp cmslm.cpp cmslnr.cpp cmsmatn.cpp \
+ cmsmkmsh.cpp cmsmntr.cpp cmsoutl.cpp cmspcoll.cpp \
+ cmsprf.cpp cmsreg.cpp cmsscn.cpp cmssheet.cpp
diff --git a/src/libs/lprof/cmshull.cpp b/src/libs/lprof/cmshull.cpp
new file mode 100644
index 00000000..05b8dfa3
--- /dev/null
+++ b/src/libs/lprof/cmshull.cpp
@@ -0,0 +1,1480 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.09a */
+
+
+#include "lcmsprf.h"
+
+/* Convex hull management */
+
+LCMSHANDLE cdecl cmsxHullInit(void);
+void cdecl cmsxHullDone(LCMSHANDLE hHull);
+BOOL cdecl cmsxHullAddPoint(LCMSHANDLE hHull, int x, int y, int z);
+BOOL cdecl cmsxHullComputeHull(LCMSHANDLE hHull);
+char cdecl cmsxHullCheckpoint(LCMSHANDLE hHull, int x, int y, int z);
+BOOL cdecl cmsxHullDumpVRML(LCMSHANDLE hHull, const char* fname);
+
+/* --------------------------------------------------------------------- */
+
+
+
+/* This method is described in "Computational Geometry in C" Chapter 4. */
+/* */
+/* -------------------------------------------------------------------- */
+/* This code is Copyright 1998 by Joseph O'Rourke. It may be freely */
+/* redistributed in its entirety provided that this copyright notice is */
+/* not removed. */
+/* -------------------------------------------------------------------- */
+
+#define SWAP(t,x,y) { t = x; x = y; y = t; }
+
+#define XFREE(p)
+/* if (p) { free ((char *) p); p = NULL; } */
+
+
+#define ADD( head, p ) if ( head ) { \
+ p->Next = head; \
+ p->Prev = head->Prev; \
+ head->Prev = p; \
+ p->Prev->Next = p; \
+ } \
+ else { \
+ head = p; \
+ head->Next = head->Prev = p; \
+ }
+
+#define XDELETE( head, p ) if ( head ) { \
+ if ( head == head->Next ) \
+ head = NULL; \
+ else if ( p == head ) \
+ head = head->Next; \
+ p->Next->Prev = p->Prev; \
+ p->Prev->Next = p->Next; \
+ XFREE( p ); \
+ }
+
+/* Define Vertex indices. */
+#define X 0
+#define Y 1
+#define Z 2
+
+/* Define structures for vertices, edges and faces */
+
+typedef struct _vertex_struct VERTEX,FAR *LPVERTEX;
+typedef struct _edge_struct EDGE, FAR *LPEDGE;
+typedef struct _face_struct FACE, FAR *LPFACE;
+
+
+struct _edge_struct {
+
+ LPFACE AdjFace[2];
+ LPVERTEX EndPts[2];
+ LPFACE NewFace; /* pointer to incident cone face. */
+ BOOL DoDelete; /* T iff Edge should be delete. */
+
+ LPEDGE Next, Prev;
+};
+
+struct _face_struct {
+
+ LPEDGE Edge[3];
+ LPVERTEX Vertex[3];
+ BOOL Visible; /* T iff face Visible from new point. */
+
+ LPFACE Next, Prev;
+};
+
+struct _vertex_struct {
+
+ int v[3];
+ int vnum;
+ LPEDGE duplicate; /* pointer to incident cone Edge (or NULL) */
+ BOOL onhull; /* T iff point on hull. */
+ BOOL mark; /* T iff point already processed. */
+
+ LPVERTEX Next, Prev;
+};
+
+/* Define flags */
+
+#define ONHULL true
+#define REMOVED true
+#define VISIBLE true
+#define PROCESSED true
+#define SAFE 1000000 /* Range of safe coord values. */
+
+#define DIM 3 /* Dimension of points */
+typedef int VEC3I[DIM]; /* Type integer point */
+
+#define PMAX 10000 /* Max # of pts */
+
+typedef struct {
+
+ /* Global variable definitions */
+ LPVERTEX vertices;
+ LPEDGE edges;
+ LPFACE faces;
+
+ VEC3I Vertices[PMAX]; /* All the points */
+ VEC3I Faces[PMAX]; /* Each triangle face is 3 indices */
+ VEC3I Box[PMAX][2]; /* Box around each face */
+
+
+ VEC3I bmin, bmax;
+ int radius;
+ int vnumCounter;
+
+ int nfaces;
+ int nvertex;
+
+} HULL, FAR* LPHULL;
+
+/* static HULL Global; */
+
+
+/*---------------------------------------------------------------------
+MakeNullVertex: Makes a Vertex, nulls out fields.
+---------------------------------------------------------------------*/
+
+static
+LPVERTEX MakeNullVertex(LPHULL hull)
+{
+ LPVERTEX v;
+
+ v = (LPVERTEX) malloc(sizeof(VERTEX));
+ if (!v) return NULL;
+
+ v->duplicate = NULL;
+ v->onhull = !ONHULL;
+ v->mark = !PROCESSED;
+ ADD( hull->vertices, v );
+
+ return v;
+}
+
+
+
+/*---------------------------------------------------------------------
+MakeNullEdge creates a new cell and initializes all pointers to NULL
+and sets all flags to off. It returns a pointer to the empty cell.
+---------------------------------------------------------------------*/
+static
+LPEDGE MakeNullEdge(LPHULL hull)
+{
+ LPEDGE e;
+
+ e = (LPEDGE) malloc(sizeof(EDGE));
+ if (!e) return NULL;
+
+ e->AdjFace[0] = e->AdjFace[1] = e->NewFace = NULL;
+ e->EndPts[0] = e->EndPts[1] = NULL;
+ e->DoDelete = !REMOVED;
+ ADD( hull->edges, e );
+ return e;
+}
+
+/*--------------------------------------------------------------------
+MakeNullFace creates a new face structure and initializes all of its
+flags to NULL and sets all the flags to off. It returns a pointer
+to the empty cell.
+---------------------------------------------------------------------*/
+static
+LPFACE MakeNullFace(LPHULL hull)
+{
+ LPFACE f;
+ int i;
+
+ f = (LPFACE) malloc(sizeof(FACE));
+ if (!f) return NULL;
+
+ for ( i=0; i < 3; ++i ) {
+ f->Edge[i] = NULL;
+ f->Vertex[i] = NULL;
+ }
+ f->Visible = !VISIBLE;
+ ADD( hull->faces, f );
+ return f;
+}
+
+
+
+/*---------------------------------------------------------------------
+MakeFace creates a new face structure from three vertices (in ccw
+order). It returns a pointer to the face.
+---------------------------------------------------------------------*/
+static
+LPFACE MakeFace(LPHULL hull, LPVERTEX v0, LPVERTEX v1, LPVERTEX v2, LPFACE fold)
+{
+ LPFACE f;
+ LPEDGE e0, e1, e2;
+
+ /* Create edges of the initial triangle. */
+ if( !fold ) {
+ e0 = MakeNullEdge(hull);
+ e1 = MakeNullEdge(hull);
+ e2 = MakeNullEdge(hull);
+ }
+ else { /* Copy from fold, in reverse order. */
+ e0 = fold->Edge[2];
+ e1 = fold->Edge[1];
+ e2 = fold->Edge[0];
+ }
+ e0->EndPts[0] = v0; e0->EndPts[1] = v1;
+ e1->EndPts[0] = v1; e1->EndPts[1] = v2;
+ e2->EndPts[0] = v2; e2->EndPts[1] = v0;
+
+ /* Create face for triangle. */
+ f = MakeNullFace(hull);
+ f->Edge[0] = e0; f->Edge[1] = e1; f->Edge[2] = e2;
+ f->Vertex[0] = v0; f->Vertex[1] = v1; f->Vertex[2] = v2;
+
+ /* Link edges to face. */
+ e0->AdjFace[0] = e1->AdjFace[0] = e2->AdjFace[0] = f;
+
+ return f;
+}
+
+/*---------------------------------------------------------------------
+Collinear checks to see if the three points given are collinear,
+by checking to see if each element of the cross product is zero.
+---------------------------------------------------------------------*/
+static
+BOOL Collinear( LPVERTEX a, LPVERTEX b, LPVERTEX c )
+{
+ return
+ ( c->v[Z] - a->v[Z] ) * ( b->v[Y] - a->v[Y] ) -
+ ( b->v[Z] - a->v[Z] ) * ( c->v[Y] - a->v[Y] ) == 0
+ && ( b->v[Z] - a->v[Z] ) * ( c->v[X] - a->v[X] ) -
+ ( b->v[X] - a->v[X] ) * ( c->v[Z] - a->v[Z] ) == 0
+ && ( b->v[X] - a->v[X] ) * ( c->v[Y] - a->v[Y] ) -
+ ( b->v[Y] - a->v[Y] ) * ( c->v[X] - a->v[X] ) == 0 ;
+}
+
+/*---------------------------------------------------------------------
+VolumeSign returns the sign of the volume of the tetrahedron determined by f
+and p. VolumeSign is +1 iff p is on the negative side of f,
+where the positive side is determined by the rh-rule. So the volume
+is positive if the ccw normal to f points outside the tetrahedron.
+The final fewer-multiplications form is due to Bob Williamson.
+---------------------------------------------------------------------*/
+int VolumeSign( LPFACE f, LPVERTEX p )
+{
+ double vol;
+ double ax, ay, az, bx, by, bz, cx, cy, cz;
+
+ ax = f->Vertex[0]->v[X] - p->v[X];
+ ay = f->Vertex[0]->v[Y] - p->v[Y];
+ az = f->Vertex[0]->v[Z] - p->v[Z];
+ bx = f->Vertex[1]->v[X] - p->v[X];
+ by = f->Vertex[1]->v[Y] - p->v[Y];
+ bz = f->Vertex[1]->v[Z] - p->v[Z];
+ cx = f->Vertex[2]->v[X] - p->v[X];
+ cy = f->Vertex[2]->v[Y] - p->v[Y];
+ cz = f->Vertex[2]->v[Z] - p->v[Z];
+
+ vol = ax * (by*cz - bz*cy)
+ + ay * (bz*cx - bx*cz)
+ + az * (bx*cy - by*cx);
+
+
+ /* The volume should be an integer. */
+ if ( vol > 0.5 ) return 1;
+ else if ( vol < -0.5 ) return -1;
+ else return 0;
+}
+
+
+
+/*---------------------------------------------------------------------
+CleanEdges runs through the Edge list and cleans up the structure.
+If there is a NewFace then it will put that face in place of the
+Visible face and NULL out NewFace. It also deletes so marked edges.
+---------------------------------------------------------------------*/
+static
+void CleanEdges(LPHULL hull)
+{
+ LPEDGE e; /* Primary index into Edge list. */
+ LPEDGE t; /* Temporary Edge pointer. */
+
+ /* Integrate the NewFace's into the data structure. */
+ /* Check every Edge. */
+
+ e = hull ->edges;
+ do {
+ if ( e->NewFace ) {
+
+ if ( e->AdjFace[0]->Visible )
+ e->AdjFace[0] = e->NewFace;
+ else
+ e->AdjFace[1] = e->NewFace;
+
+ e->NewFace = NULL;
+ }
+
+ e = e->Next;
+
+ } while ( e != hull ->edges );
+
+ /* Delete any edges marked for deletion. */
+ while ( hull ->edges && hull ->edges->DoDelete ) {
+
+ e = hull ->edges;
+
+ XDELETE( hull ->edges, e );
+ }
+
+ e = hull ->edges->Next;
+
+ do {
+ if ( e->DoDelete ) {
+
+ t = e;
+ e = e->Next;
+ XDELETE( hull ->edges, t );
+ }
+ else e = e->Next;
+
+ } while ( e != hull ->edges );
+}
+
+/*---------------------------------------------------------------------
+CleanFaces runs through the face list and deletes any face marked Visible.
+---------------------------------------------------------------------*/
+static
+void CleanFaces(LPHULL hull)
+{
+ LPFACE f; /* Primary pointer into face list. */
+ LPFACE t; /* Temporary pointer, for deleting. */
+
+
+ while ( hull ->faces && hull ->faces->Visible ) {
+
+ f = hull ->faces;
+ XDELETE( hull ->faces, f );
+ }
+
+ f = hull ->faces->Next;
+
+ do {
+ if ( f->Visible ) {
+
+ t = f;
+ f = f->Next;
+ XDELETE( hull ->faces, t );
+ }
+ else f = f->Next;
+
+ } while ( f != hull ->faces );
+}
+
+
+
+/*---------------------------------------------------------------------
+CleanVertices runs through the Vertex list and deletes the
+vertices that are marked as processed but are not incident to any
+undeleted edges.
+---------------------------------------------------------------------*/
+static
+void CleanVertices(LPHULL hull)
+{
+ LPEDGE e;
+ LPVERTEX v, t;
+
+ /* Mark all vertices incident to some undeleted Edge as on the hull. */
+
+ e = hull ->edges;
+ do {
+ e->EndPts[0]->onhull = e->EndPts[1]->onhull = ONHULL;
+ e = e->Next;
+
+ } while (e != hull ->edges);
+
+
+ /* Delete all vertices that have been processed but
+ are not on the hull. */
+
+ while ( hull ->vertices && hull->vertices->mark && !hull ->vertices->onhull ) {
+
+ v = hull ->vertices;
+ XDELETE(hull ->vertices, v );
+ }
+
+
+ v = hull ->vertices->Next;
+ do {
+ if (v->mark && !v->onhull ) {
+ t = v;
+ v = v->Next;
+ XDELETE(hull ->vertices, t )
+ }
+ else
+ v = v->Next;
+
+ } while ( v != hull ->vertices );
+
+
+ /* Reset flags. */
+
+ v = hull ->vertices;
+ do {
+ v->duplicate = NULL;
+ v->onhull = !ONHULL;
+ v = v->Next;
+
+ } while (v != hull->vertices );
+
+}
+
+
+
+
+/*---------------------------------------------------------------------
+MakeCcw puts the vertices in the face structure in counterclock wise
+order. We want to store the vertices in the same
+order as in the Visible face. The third Vertex is always p.
+
+Although no specific ordering of the edges of a face are used
+by the code, the following condition is maintained for each face f:
+one of the two endpoints of f->Edge[i] matches f->Vertex[i].
+But note that this does not imply that f->Edge[i] is between
+f->Vertex[i] and f->Vertex[(i+1)%3]. (Thanks to Bob Williamson.)
+---------------------------------------------------------------------*/
+
+static
+void MakeCcw(LPFACE f, LPEDGE e, LPVERTEX p)
+{
+ LPFACE fv; /* The Visible face adjacent to e */
+ int i; /* Index of e->endpoint[0] in fv. */
+ LPEDGE s; /* Temporary, for swapping */
+
+ if (e->AdjFace[0]->Visible)
+
+ fv = e->AdjFace[0];
+ else
+ fv = e->AdjFace[1];
+
+ /* Set Vertex[0] & [1] of f to have the same orientation
+ as do the corresponding vertices of fv. */
+
+ for ( i=0; fv->Vertex[i] != e->EndPts[0]; ++i )
+ ;
+
+ /* Orient f the same as fv. */
+
+ if ( fv->Vertex[ (i+1) % 3 ] != e->EndPts[1] ) {
+
+ f->Vertex[0] = e->EndPts[1];
+ f->Vertex[1] = e->EndPts[0];
+ }
+ else {
+ f->Vertex[0] = e->EndPts[0];
+ f->Vertex[1] = e->EndPts[1];
+ SWAP( s, f->Edge[1], f->Edge[2] );
+ }
+
+ /* This swap is tricky. e is Edge[0]. Edge[1] is based on endpt[0],
+ Edge[2] on endpt[1]. So if e is oriented "forwards," we
+ need to move Edge[1] to follow [0], because it precedes. */
+
+ f->Vertex[2] = p;
+}
+
+/*---------------------------------------------------------------------
+MakeConeFace makes a new face and two new edges between the
+Edge and the point that are passed to it. It returns a pointer to
+the new face.
+---------------------------------------------------------------------*/
+
+static
+LPFACE MakeConeFace(LPHULL hull, LPEDGE e, LPVERTEX p)
+{
+ LPEDGE new_edge[2];
+ LPFACE new_face;
+ int i, j;
+
+ /* Make two new edges (if don't already exist). */
+
+ for ( i=0; i < 2; ++i )
+ /* If the Edge exists, copy it into new_edge. */
+ if ( !( new_edge[i] = e->EndPts[i]->duplicate) ) {
+
+ /* Otherwise (duplicate is NULL), MakeNullEdge. */
+ new_edge[i] = MakeNullEdge(hull);
+ new_edge[i]->EndPts[0] = e->EndPts[i];
+ new_edge[i]->EndPts[1] = p;
+ e->EndPts[i]->duplicate = new_edge[i];
+ }
+
+ /* Make the new face. */
+ new_face = MakeNullFace(hull);
+ new_face->Edge[0] = e;
+ new_face->Edge[1] = new_edge[0];
+ new_face->Edge[2] = new_edge[1];
+ MakeCcw( new_face, e, p );
+
+ /* Set the adjacent face pointers. */
+ for ( i=0; i < 2; ++i )
+ for ( j=0; j < 2; ++j )
+ /* Only one NULL link should be set to new_face. */
+ if ( !new_edge[i]->AdjFace[j] ) {
+ new_edge[i]->AdjFace[j] = new_face;
+ break;
+ }
+
+ return new_face;
+}
+
+
+/*---------------------------------------------------------------------
+AddOne is passed a Vertex. It first determines all faces Visible from
+that point. If none are Visible then the point is marked as not
+onhull. Next is a loop over edges. If both faces adjacent to an Edge
+are Visible, then the Edge is marked for deletion. If just one of the
+adjacent faces is Visible then a new face is constructed.
+---------------------------------------------------------------------*/
+static
+BOOL AddOne(LPHULL hull, LPVERTEX p)
+{
+ LPFACE f;
+ LPEDGE e, temp;
+ int vol;
+ BOOL vis = false;
+
+
+ /* Mark faces Visible from p. */
+ f = hull -> faces;
+
+ do {
+
+ vol = VolumeSign(f, p);
+
+ if ( vol < 0 ) {
+ f->Visible = VISIBLE;
+ vis = true;
+ }
+
+ f = f->Next;
+
+ } while ( f != hull ->faces );
+
+ /* If no faces are Visible from p, then p is inside the hull. */
+
+ if ( !vis ) {
+
+ p->onhull = !ONHULL;
+ return false;
+ }
+
+ /* Mark edges in interior of Visible region for deletion.
+ Erect a NewFace based on each border Edge. */
+
+ e = hull ->edges;
+
+ do {
+
+ temp = e->Next;
+
+ if ( e->AdjFace[0]->Visible && e->AdjFace[1]->Visible )
+ /* e interior: mark for deletion. */
+ e->DoDelete = REMOVED;
+
+ else
+ if ( e->AdjFace[0]->Visible || e->AdjFace[1]->Visible )
+ /* e border: make a new face. */
+ e->NewFace = MakeConeFace(hull, e, p );
+
+ e = temp;
+
+ } while ( e != hull ->edges );
+
+ return true;
+}
+
+
+/*---------------------------------------------------------------------
+ DoubleTriangle builds the initial double triangle. It first finds 3
+ noncollinear points and makes two faces out of them, in opposite order.
+ It then finds a fourth point that is not coplanar with that face. The
+ vertices are stored in the face structure in counterclockwise order so
+ that the volume between the face and the point is negative. Lastly, the
+ 3 newfaces to the fourth point are constructed and the data structures
+ are cleaned up.
+---------------------------------------------------------------------*/
+
+static
+BOOL DoubleTriangle(LPHULL hull)
+{
+ LPVERTEX v0, v1, v2, v3;
+ LPFACE f0, f1 = NULL;
+ int vol;
+
+ /* Find 3 noncollinear points. */
+ v0 = hull ->vertices;
+ while ( Collinear( v0, v0->Next, v0->Next->Next ) )
+ if ( ( v0 = v0->Next ) == hull->vertices )
+ return false; /* All points are Collinear! */
+
+ v1 = v0->Next;
+ v2 = v1->Next;
+
+ /* Mark the vertices as processed. */
+ v0->mark = PROCESSED;
+ v1->mark = PROCESSED;
+ v2->mark = PROCESSED;
+
+ /* Create the two "twin" faces. */
+ f0 = MakeFace(hull, v0, v1, v2, f1 );
+ f1 = MakeFace(hull, v2, v1, v0, f0 );
+
+ /* Link adjacent face fields. */
+ f0->Edge[0]->AdjFace[1] = f1;
+ f0->Edge[1]->AdjFace[1] = f1;
+ f0->Edge[2]->AdjFace[1] = f1;
+ f1->Edge[0]->AdjFace[1] = f0;
+ f1->Edge[1]->AdjFace[1] = f0;
+ f1->Edge[2]->AdjFace[1] = f0;
+
+ /* Find a fourth, noncoplanar point to form tetrahedron. */
+ v3 = v2->Next;
+ vol = VolumeSign( f0, v3 );
+
+ while ( !vol ) {
+
+ if ( ( v3 = v3->Next ) == v0 )
+ return false; /* All points are coplanar! */
+
+ vol = VolumeSign( f0, v3 );
+ }
+
+ /* Insure that v3 will be the first added. */
+ hull ->vertices = v3;
+ return true;
+}
+
+
+
+/*---------------------------------------------------------------------
+ConstructHull adds the vertices to the hull one at a time. The hull
+vertices are those in the list marked as onhull.
+---------------------------------------------------------------------*/
+static
+void ConstructHull(LPHULL hull)
+{
+ LPVERTEX v, vnext;
+ BOOL changed; /* T if addition changes hull; not used. */
+
+ v = hull->vertices;
+
+ do {
+ vnext = v->Next;
+
+ changed = false;
+
+ if (!v->mark ) {
+
+ v->mark = PROCESSED;
+ changed = AddOne(hull, v );
+
+ CleanEdges(hull);
+ CleanFaces(hull);
+ CleanVertices(hull);
+ }
+
+ v = vnext;
+
+ } while (v != hull->vertices );
+
+}
+
+
+
+/*-------------------------------------------------------------------*/
+
+
+static
+void AddVec( VEC3I q, VEC3I ray )
+{
+ int i;
+
+ for( i = 0; i < DIM; i++ )
+ ray[i] = q[i] + ray[i];
+}
+
+/*---------------------------------------------------------------------
+a - b ==> c.
+---------------------------------------------------------------------*/
+static
+void SubVec( VEC3I a, VEC3I b, VEC3I c )
+{
+ int i;
+
+ for( i = 0; i < DIM; i++ )
+ c[i] = a[i] - b[i];
+}
+
+
+/*---------------------------------------------------------------------
+Returns the dot product of the two input vectors.
+---------------------------------------------------------------------*/
+static
+double Dot( VEC3I a, LPVEC3 b )
+{
+ int i;
+ double sum = 0.0;
+
+ for( i = 0; i < DIM; i++ )
+ sum += a[i] * b->n[i];
+
+ return sum;
+}
+
+/*---------------------------------------------------------------------
+Compute the cross product of (b-a)x(c-a) and place into N.
+---------------------------------------------------------------------*/
+static
+void NormalVec( VEC3I a, VEC3I b, VEC3I c, LPVEC3 N )
+{
+ N->n[X] = ( c[Z] - a[Z] ) * ( b[Y] - a[Y] ) -
+ ( b[Z] - a[Z] ) * ( c[Y] - a[Y] );
+ N->n[Y] = ( b[Z] - a[Z] ) * ( c[X] - a[X] ) -
+ ( b[X] - a[X] ) * ( c[Z] - a[Z] );
+ N->n[Z] = ( b[X] - a[X] ) * ( c[Y] - a[Y] ) -
+ ( b[Y] - a[Y] ) * ( c[X] - a[X] );
+}
+
+
+
+
+static
+int InBox( VEC3I q, VEC3I bmin, VEC3I bmax )
+{
+
+ if( ( bmin[X] <= q[X] ) && ( q[X] <= bmax[X] ) &&
+ ( bmin[Y] <= q[Y] ) && ( q[Y] <= bmax[Y] ) &&
+ ( bmin[Z] <= q[Z] ) && ( q[Z] <= bmax[Z] ) )
+ return true;
+
+ return false;
+}
+
+
+
+/*
+ This function returns a char:
+ '0': the segment [ab] does not intersect (completely misses) the
+ bounding box surrounding the n-th triangle T. It lies
+ strictly to one side of one of the six supporting planes.
+ '?': status unknown: the segment may or may not intersect T.
+*/
+
+static
+char BoxTest(LPHULL hull, int n, VEC3I a, VEC3I b)
+{
+ int i; /* Coordinate index */
+ int w;
+
+ for ( i=0; i < DIM; i++ ) {
+
+ w = hull ->Box[ n ][0][i]; /* min: lower left */
+
+ if ( (a[i] < w) && (b[i] < w) ) return '0';
+
+ w = hull ->Box[ n ][1][i]; /* max: upper right */
+
+ if ( (a[i] > w) && (b[i] > w) ) return '0';
+ }
+
+ return '?';
+}
+
+
+
+/* Return a random ray endpoint */
+
+static
+void RandomRay( VEC3I ray, int radius )
+{
+ double x, y, z, w, t;
+
+ /* Generate a random point on a sphere of radius 1. */
+ /* the sphere is sliced at z, and a random point at angle t
+ generated on the circle of intersection. */
+
+ z = 2.0 * (double) rand() / RAND_MAX - 1.0;
+ t = 2.0 * M_PI * (double) rand() / RAND_MAX;
+ w = sqrt( 1 - z*z );
+ x = w * cos( t );
+ y = w * sin( t );
+
+ ray[X] = (int) ( radius * x );
+ ray[Y] = (int) ( radius * y );
+ ray[Z] = (int) ( radius * z );
+}
+
+
+
+static
+int ComputeBox(LPHULL hull, int F, VEC3I bmin, VEC3I bmax )
+{
+ int i, j;
+ double radius;
+
+ for( i = 0; i < F; i++ )
+ for( j = 0; j < DIM; j++ ) {
+
+ if( hull ->Vertices[i][j] < bmin[j] )
+ bmin[j] = hull ->Vertices[i][j];
+
+ if( hull ->Vertices[i][j] > bmax[j] )
+ bmax[j] = hull ->Vertices[i][j];
+ }
+
+ radius = sqrt( pow( (double)(bmax[X] - bmin[X]), 2.0 ) +
+ pow( (double)(bmax[Y] - bmin[Y]), 2.0 ) +
+ pow( (double)(bmax[Z] - bmin[Z]), 2.0 ) );
+
+ return (int)( radius +1 ) + 1;
+}
+
+
+/*---------------------------------------------------------------------
+Computes N & D and returns index m of largest component.
+---------------------------------------------------------------------*/
+static
+int PlaneCoeff(LPHULL hull, VEC3I T, LPVEC3 N, double *D )
+{
+ int i;
+ double t; /* Temp storage */
+ double biggest = 0.0; /* Largest component of normal vector. */
+ int m = 0; /* Index of largest component. */
+
+ NormalVec(hull ->Vertices[T[0]], hull ->Vertices[T[1]], hull ->Vertices[T[2]], N );
+ *D = Dot( hull ->Vertices[T[0]], N );
+
+ /* Find the largest component of N. */
+ for ( i = 0; i < DIM; i++ ) {
+ t = fabs( N->n[i] );
+ if ( t > biggest ) {
+ biggest = t;
+ m = i;
+ }
+ }
+ return m;
+}
+
+/*---------------------------------------------------------------------
+ 'p': The segment lies wholly within the plane.
+ 'q': The q endpoint is on the plane (but not 'p').
+ 'r': The r endpoint is on the plane (but not 'p').
+ '0': The segment lies strictly to one side or the other of the plane.
+ '1': The segement intersects the plane, and 'p' does not hold.
+---------------------------------------------------------------------*/
+static
+char SegPlaneInt(LPHULL hull, VEC3I T, VEC3I q, VEC3I r, LPVEC3 p, int *m)
+{
+ VEC3 N; double D;
+ VEC3I rq;
+ double num, denom, t;
+ int i;
+
+ *m = PlaneCoeff(hull, T, &N, &D );
+ num = D - Dot( q, &N );
+ SubVec( r, q, rq );
+ denom = Dot( rq, &N );
+
+ if ( denom == 0.0 ) { /* Segment is parallel to plane. */
+ if ( num == 0.0 ) /* q is on plane. */
+ return 'p';
+ else
+ return '0';
+ }
+ else
+ t = num / denom;
+
+ for( i = 0; i < DIM; i++ )
+ p->n[i] = q[i] + t * ( r[i] - q[i] );
+
+ if ( (0.0 < t) && (t < 1.0) )
+ return '1';
+ else if ( num == 0.0 ) /* t == 0 */
+ return 'q';
+ else if ( num == denom ) /* t == 1 */
+ return 'r';
+ else return '0';
+}
+
+
+
+static
+int AreaSign( VEC3I a, VEC3I b, VEC3I c )
+{
+ double area2;
+
+ area2 = ( b[0] - a[0] ) * (double)( c[1] - a[1] ) -
+ ( c[0] - a[0] ) * (double)( b[1] - a[1] );
+
+ /* The area should be an integer. */
+ if ( area2 > 0.5 ) return 1;
+ else if ( area2 < -0.5 ) return -1;
+ else return 0;
+}
+
+
+static
+char InTri2D( VEC3I Tp[3], VEC3I pp )
+{
+ int area0, area1, area2;
+
+ /* compute three AreaSign() values for pp w.r.t. each Edge of the face in 2D */
+ area0 = AreaSign( pp, Tp[0], Tp[1] );
+ area1 = AreaSign( pp, Tp[1], Tp[2] );
+ area2 = AreaSign( pp, Tp[2], Tp[0] );
+
+ if ( (( area0 == 0 ) && ( area1 > 0 ) && ( area2 > 0 )) ||
+ (( area1 == 0 ) && ( area0 > 0 ) && ( area2 > 0 )) ||
+ (( area2 == 0 ) && ( area0 > 0 ) && ( area1 > 0 )) )
+ return 'E';
+
+ if ( (( area0 == 0 ) && ( area1 < 0 ) && ( area2 < 0 )) ||
+ (( area1 == 0 ) && ( area0 < 0 ) && ( area2 < 0 )) ||
+ (( area2 == 0 ) && ( area0 < 0 ) && ( area1 < 0 )))
+ return 'E';
+
+ if ( (( area0 > 0 ) && ( area1 > 0 ) && ( area2 > 0 )) ||
+ (( area0 < 0 ) && ( area1 < 0 ) && ( area2 < 0 )))
+ return 'F';
+
+ if ( ( area0 == 0 ) && ( area1 == 0 ) && ( area2 == 0 ) )
+ return '?'; /* Error in InTriD */
+
+ if ( (( area0 == 0 ) && ( area1 == 0 )) ||
+ (( area0 == 0 ) && ( area2 == 0 )) ||
+ (( area1 == 0 ) && ( area2 == 0 )) )
+ return 'V';
+
+ else
+ return '0';
+}
+
+/* Assumption: p lies in the plane containing T.
+ Returns a char:
+ 'V': the query point p coincides with a Vertex of triangle T.
+ 'E': the query point p is in the relative interior of an Edge of triangle T.
+ 'F': the query point p is in the relative interior of a Face of triangle T.
+ '0': the query point p does not intersect (misses) triangle T.
+*/
+
+static
+char InTri3D(LPHULL hull, VEC3I T, int m, VEC3I p )
+{
+ int i; /* Index for X,Y,Z */
+ int j; /* Index for X,Y */
+ int k; /* Index for triangle Vertex */
+ VEC3I pp; /* projected p */
+ VEC3I Tp[3]; /* projected T: three new vertices */
+
+ /* Project out coordinate m in both p and the triangular face */
+ j = 0;
+ for ( i = 0; i < DIM; i++ ) {
+ if ( i != m ) { /* skip largest coordinate */
+ pp[j] = p[i];
+ for ( k = 0; k < 3; k++ )
+ Tp[k][j] = hull->Vertices[T[k]][i];
+ j++;
+ }
+ }
+ return( InTri2D( Tp, pp ) );
+}
+
+
+
+static
+int VolumeSign2( VEC3I a, VEC3I b, VEC3I c, VEC3I d )
+{
+ double vol;
+ double ax, ay, az, bx, by, bz, cx, cy, cz, dx, dy, dz;
+ double bxdx, bydy, bzdz, cxdx, cydy, czdz;
+
+ ax = a[X];
+ ay = a[Y];
+ az = a[Z];
+ bx = b[X];
+ by = b[Y];
+ bz = b[Z];
+ cx = c[X];
+ cy = c[Y];
+ cz = c[Z];
+ dx = d[X];
+ dy = d[Y];
+ dz = d[Z];
+
+ bxdx=bx-dx;
+ bydy=by-dy;
+ bzdz=bz-dz;
+ cxdx=cx-dx;
+ cydy=cy-dy;
+ czdz=cz-dz;
+ vol = (az-dz) * (bxdx*cydy - bydy*cxdx)
+ + (ay-dy) * (bzdz*cxdx - bxdx*czdz)
+ + (ax-dx) * (bydy*czdz - bzdz*cydy);
+
+
+ /* The volume should be an integer. */
+ if ( vol > 0.5 ) return 1;
+ else if ( vol < -0.5 ) return -1;
+ else return 0;
+}
+
+
+
+
+/*---------------------------------------------------------------------
+The signed volumes of three tetrahedra are computed, determined
+by the segment qr, and each Edge of the triangle.
+Returns a char:
+ 'v': the open segment includes a Vertex of T.
+ 'e': the open segment includes a point in the relative interior of an Edge
+ of T.
+ 'f': the open segment includes a point in the relative interior of a face
+ of T.
+ '0': the open segment does not intersect triangle T.
+---------------------------------------------------------------------*/
+
+static
+char SegTriCross(LPHULL hull, VEC3I T, VEC3I q, VEC3I r )
+{
+ int vol0, vol1, vol2;
+
+ vol0 = VolumeSign2( q, hull->Vertices[ T[0] ], hull->Vertices[ T[1] ], r );
+ vol1 = VolumeSign2( q, hull->Vertices[ T[1] ], hull->Vertices[ T[2] ], r );
+ vol2 = VolumeSign2( q, hull->Vertices[ T[2] ], hull->Vertices[ T[0] ], r );
+
+
+ /* Same sign: segment intersects interior of triangle. */
+ if ( ( ( vol0 > 0 ) && ( vol1 > 0 ) && ( vol2 > 0 ) ) ||
+ ( ( vol0 < 0 ) && ( vol1 < 0 ) && ( vol2 < 0 ) ) )
+ return 'f';
+
+ /* Opposite sign: no intersection between segment and triangle */
+ if ( ( ( vol0 > 0 ) || ( vol1 > 0 ) || ( vol2 > 0 ) ) &&
+ ( ( vol0 < 0 ) || ( vol1 < 0 ) || ( vol2 < 0 ) ) )
+ return '0';
+
+ else if ( ( vol0 == 0 ) && ( vol1 == 0 ) && ( vol2 == 0 ) )
+ return '?'; /* Error 1 in SegTriCross */
+
+ /* Two zeros: segment intersects Vertex. */
+ else if ( ( ( vol0 == 0 ) && ( vol1 == 0 ) ) ||
+ ( ( vol0 == 0 ) && ( vol2 == 0 ) ) ||
+ ( ( vol1 == 0 ) && ( vol2 == 0 ) ) )
+ return 'v';
+
+ /* One zero: segment intersects Edge. */
+ else if ( ( vol0 == 0 ) || ( vol1 == 0 ) || ( vol2 == 0 ) )
+ return 'e';
+
+ else
+ return '?'; /* Error 2 in SegTriCross */
+}
+
+
+
+static
+char SegTriInt(LPHULL hull, VEC3I T, VEC3I q, VEC3I r, LPVEC3 p )
+{
+ int code;
+ int m = -1;
+
+ code = SegPlaneInt(hull, T, q, r, p, &m );
+
+ if ( code == '0') return '0';
+ else if ( code == 'q') return InTri3D(hull, T, m, q );
+ else if ( code == 'r') return InTri3D(hull, T, m, r );
+ else if ( code == 'p') return 'p';
+ else if ( code == '1' ) return SegTriCross(hull, T, q, r );
+ else
+ return code; /* Error */
+}
+
+
+
+
+/*
+ This function returns a char:
+ 'i': the query point a is strictly interior to polyhedron P.
+ 'o': the query point a is strictly exterior to( or outside of) polyhedron P.
+*/
+char InPolyhedron(LPHULL hull, VEC3I q)
+{
+ int F = hull->nfaces;
+ VEC3I Ray; /* Ray endpoint. */
+ VEC3 p; /* Intersection point; not used. */
+ int f, k = 0, crossings = 0;
+ char code = '?';
+
+
+ /* If query point is outside bounding box, finished. */
+ if ( !InBox( q, hull->bmin, hull->bmax ) )
+ return 'o';
+
+ LOOP:
+ while( k++ < F ) {
+
+ crossings = 0;
+
+ RandomRay(Ray, hull->radius );
+
+ AddVec( q, Ray );
+
+
+ for ( f = 0; f < F; f++ ) { /* Begin check each face */
+
+ if ( BoxTest(hull, f, q, Ray ) == '0' ) {
+ code = '0';
+
+ }
+ else code = SegTriInt(hull, hull->Faces[f], q, Ray, &p );
+
+
+ /* If ray is degenerate, then goto outer while to generate another. */
+ if ( code == 'p' || code == 'v' || code == 'e' ) {
+
+ goto LOOP;
+ }
+
+ /* If ray hits face at interior point, increment crossings. */
+ else if ( code == 'f' ) {
+ crossings++;
+
+ }
+
+ /* If query endpoint q sits on a V/E/F, return inside. */
+ else if ( code == 'V' || code == 'E' || code == 'F' )
+ return code; /* 'i'; MM2 */
+
+ /* If ray misses triangle, do nothing. */
+ else if ( code == '0' )
+ ;
+
+ else
+ return '?'; /* Error */
+
+ }
+ /* No degeneracies encountered: ray is generic, so finished. */
+ break;
+
+ } /* End while loop */
+
+
+ /* q strictly interior to polyhedron iff an odd number of crossings. */
+ if( ( crossings % 2 ) == 1 )
+ return 'i';
+
+ else return 'o';
+}
+
+
+/*/ ---------------------------------------------------------------------------------- */
+
+
+
+
+static
+void StoreResults(LPHULL hull)
+{
+
+ int i, w;
+ LPVERTEX v;
+ LPFACE f;
+ int V = 0, F = 0;
+ int j, k;
+
+ /* Vertices */
+
+ v = hull ->vertices;
+ V = 0;
+ do {
+
+ v -> vnum = V;
+ hull ->Vertices[V][X] = v -> v[X];
+ hull ->Vertices[V][Y] = v -> v[Y];
+ hull ->Vertices[V][Z] = v -> v[Z];
+
+ v = v->Next;
+ V++;
+
+ } while ( v != hull ->vertices );
+
+ hull ->nvertex = V;
+
+ /* Faces */
+ f = hull ->faces;
+ F = 0;
+ do {
+
+ hull ->Faces[F][0] = f->Vertex[0]->vnum;
+ hull ->Faces[F][1] = f->Vertex[1]->vnum;
+ hull ->Faces[F][2] = f->Vertex[2]->vnum;
+
+ for ( j=0; j < 3; j++ ) {
+
+ hull ->Box[F][0][j] = hull ->Vertices[ hull ->Faces[F][0] ][j];
+ hull ->Box[F][1][j] = hull ->Vertices[ hull ->Faces[F][0] ][j];
+ }
+
+ /* Check k=1,2 vertices of face. */
+ for ( k=1; k < 3; k++ )
+ for ( j=0; j < 3; j++ ) {
+
+ w = hull ->Vertices[ hull ->Faces[F][k] ][j];
+ if ( w < hull ->Box[F][0][j] ) hull ->Box[F][0][j] = w;
+ if ( w > hull ->Box[F][1][j] ) hull ->Box[F][1][j] = w;
+ }
+
+
+ f = f->Next; F++;
+
+ } while ( f != hull ->faces );
+
+
+ hull ->nfaces = F;
+
+
+ /* Initialize the bounding box */
+ for ( i = 0; i < DIM; i++ )
+ hull ->bmin[i] = hull ->bmax[i] = hull ->Vertices[0][i];
+
+ hull ->radius = ComputeBox(hull, V, hull ->bmin, hull ->bmax );
+
+
+}
+
+
+LCMSHANDLE cmsxHullInit(void)
+{
+ LPHULL hull = (LPHULL) malloc(sizeof(HULL));
+
+ ZeroMemory(hull, sizeof(HULL));
+
+ hull->vnumCounter = 0;
+ hull->vertices = NULL;
+ hull->edges = NULL;
+ hull->faces = NULL;
+ hull->nfaces = 0;
+ hull->nvertex = 0;
+
+ return (LCMSHANDLE) (LPSTR) hull;
+}
+
+
+void cmsxHullDone(LCMSHANDLE hHull)
+{
+ LPHULL hull = (LPHULL) (LPSTR) hHull;
+
+ if (hull)
+ free((LPVOID) hull);
+}
+
+
+BOOL cmsxHullAddPoint(LCMSHANDLE hHull, int x, int y, int z)
+{
+ LPVERTEX v;
+ LPHULL hull = (LPHULL) (LPSTR) hHull;
+
+
+ v = MakeNullVertex(hull);
+ v->v[X] = x;
+ v->v[Y] = y;
+ v->v[Z] = z;
+ v->vnum = hull->vnumCounter++;
+
+ return true;
+}
+
+BOOL cmsxHullComputeHull(LCMSHANDLE hHull)
+{
+
+ LPHULL hull = (LPHULL) (LPSTR) hHull;
+
+ if (!DoubleTriangle(hull)) return false;
+
+ ConstructHull(hull);
+ StoreResults(hull);
+
+ return true;
+}
+
+
+char cmsxHullCheckpoint(LCMSHANDLE hHull, int x, int y, int z)
+{
+ VEC3I q;
+ LPHULL hull = (LPHULL) (LPSTR) hHull;
+
+ q[X] = x; q[Y] = y; q[Z] = z;
+
+ return InPolyhedron(hull, q ) ;
+}
+
+
+BOOL cmsxHullDumpVRML(LCMSHANDLE hHull, const char* fname)
+{
+ FILE* fp;
+ int i;
+ LPHULL hull = (LPHULL) (LPSTR) hHull;
+
+ fp = fopen (fname, "wt");
+ if (fp == NULL)
+ return false;
+
+ fprintf (fp, "#VRML V2.0 utf8\n");
+
+ /* set the viewing orientation and distance */
+ fprintf (fp, "DEF CamTest Group {\n");
+ fprintf (fp, "\tchildren [\n");
+ fprintf (fp, "\t\tDEF Cameras Group {\n");
+ fprintf (fp, "\t\t\tchildren [\n");
+ fprintf (fp, "\t\t\t\tDEF DefaultView Viewpoint {\n");
+ fprintf (fp, "\t\t\t\t\tposition 0 0 340\n");
+ fprintf (fp, "\t\t\t\t\torientation 0 0 1 0\n");
+ fprintf (fp, "\t\t\t\t\tdescription \"default view\"\n");
+ fprintf (fp, "\t\t\t\t}\n");
+ fprintf (fp, "\t\t\t]\n");
+ fprintf (fp, "\t\t},\n");
+ fprintf (fp, "\t]\n");
+ fprintf (fp, "}\n");
+
+ /* Output the background stuff */
+ fprintf (fp, "Background {\n");
+ fprintf (fp, "\tskyColor [\n");
+ fprintf (fp, "\t\t.5 .5 .5\n");
+ fprintf (fp, "\t]\n");
+ fprintf (fp, "}\n");
+
+ /* Output the shape stuff */
+ fprintf (fp, "Transform {\n");
+ fprintf (fp, "\tscale 8 8 8\n");
+ fprintf (fp, "\tchildren [\n");
+
+ /* Draw the axes as a shape: */
+ fprintf (fp, "\t\tShape {\n");
+ fprintf (fp, "\t\t\tappearance Appearance {\n");
+ fprintf (fp, "\t\t\t\tmaterial Material {\n");
+ fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n");
+ fprintf (fp, "\t\t\t\t\temissiveColor 1.0 1.0 1.0\n");
+ fprintf (fp, "\t\t\t\t\tshininess 0.8\n");
+ fprintf (fp, "\t\t\t\t}\n");
+ fprintf (fp, "\t\t\t}\n");
+ fprintf (fp, "\t\t\tgeometry IndexedLineSet {\n");
+ fprintf (fp, "\t\t\t\tcoord Coordinate {\n");
+ fprintf (fp, "\t\t\t\t\tpoint [\n");
+ fprintf (fp, "\t\t\t\t\t0.0 0.0 0.0,\n");
+ fprintf (fp, "\t\t\t\t\t%f 0.0 0.0,\n", 255.0);
+ fprintf (fp, "\t\t\t\t\t0.0 %f 0.0,\n", 255.0);
+ fprintf (fp, "\t\t\t\t\t0.0 0.0 %f]\n", 255.0);
+ fprintf (fp, "\t\t\t\t}\n");
+ fprintf (fp, "\t\t\t\tcoordIndex [\n");
+ fprintf (fp, "\t\t\t\t\t0, 1, -1\n");
+ fprintf (fp, "\t\t\t\t\t0, 2, -1\n");
+ fprintf (fp, "\t\t\t\t\t0, 3, -1]\n");
+ fprintf (fp, "\t\t\t}\n");
+ fprintf (fp, "\t\t}\n");
+
+
+ /* Draw the triangles as a shape: */
+ fprintf (fp, "\t\tShape {\n");
+ fprintf (fp, "\t\t\tappearance Appearance {\n");
+ fprintf (fp, "\t\t\t\tmaterial Material {\n");
+ fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n");
+ fprintf (fp, "\t\t\t\t\temissiveColor 0 0 0\n");
+ fprintf (fp, "\t\t\t\t\tshininess 0.8\n");
+ fprintf (fp, "\t\t\t\t}\n");
+ fprintf (fp, "\t\t\t}\n");
+ fprintf (fp, "\t\t\tgeometry IndexedFaceSet {\n");
+ fprintf (fp, "\t\t\t\tsolid false\n");
+
+ /* fill in the points here */
+ fprintf (fp, "\t\t\t\tcoord Coordinate {\n");
+ fprintf (fp, "\t\t\t\t\tpoint [\n");
+
+ for (i = 0; i < hull->nvertex; ++i)
+ {
+ fprintf (fp, "\t\t\t\t\t%g %g %g%c\n",
+ (double) hull->Vertices[i][X], (double) hull->Vertices[i][Y], (double) hull->Vertices[i][Z],
+ i == hull->nvertex-1? ']': ',');
+ }
+ fprintf (fp, "\t\t\t\t}\n");
+
+ /* fill in the Vertex indices (followed by -1) */
+
+
+ fprintf (fp, "\t\t\t\tcoordIndex [\n");
+ for (i = 0; i < hull->nfaces; ++i)
+ {
+ fprintf (fp, "\t\t\t\t\t%d, %d, %d, -1\n",
+ hull->Faces[i][0], hull->Faces[i][1], hull->Faces[i][2]);
+
+ }
+ fprintf (fp, "]\n");
+
+
+ /* fill in the face colors */
+ fprintf (fp, "\t\t\t\tcolor Color {\n");
+ fprintf (fp, "\t\t\t\t\tcolor [\n");
+ for (i = 0; i < hull->nfaces; ++i)
+ {
+ int vx, vy, vz;
+ double r, g, b;
+
+ vx = hull->Faces[i][0]; vy = hull->Faces[i][1]; vz = hull->Faces[i][2];
+ r = (double) (hull->Vertices[vx][X] + hull->Vertices[vy][X] + hull->Vertices[vz][X]) / (3* 255);
+ g = (double) (hull->Vertices[vx][Y] + hull->Vertices[vy][Y] + hull->Vertices[vz][Y]) / (3* 255);
+ b = (double) (hull->Vertices[vx][Z] + hull->Vertices[vy][Z] + hull->Vertices[vz][Z]) / (3* 255);
+
+ fprintf (fp, "\t\t\t\t\t%g %g %g%c\n", r, g, b,
+ i == hull->nfaces-1? ']': ',');
+ }
+ fprintf (fp, "\t\t\t}\n");
+
+ fprintf (fp, "\t\t\tcolorPerVertex false\n");
+
+ fprintf (fp, "\t\t\t}\n");
+ fprintf (fp, "\t\t}\n");
+ fprintf (fp, "\t]\n");
+ fprintf (fp, "}\n");
+
+ fclose (fp);
+
+ return true;
+}
diff --git a/src/libs/lprof/cmslm.cpp b/src/libs/lprof/cmslm.cpp
new file mode 100644
index 00000000..81d86ba6
--- /dev/null
+++ b/src/libs/lprof/cmslm.cpp
@@ -0,0 +1,288 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, ma 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.09a */
+
+#include "lcmsprf.h"
+
+
+/* From "numerical recipes in C" */
+/* */
+/* Levenberg-Marquardt method, attempting to reduce the value X2 of a */
+/* fit between a set of data points x[1..ndata], y[1..ndata] with individual */
+/* standard deviations sig[1..ndata], and a nonlinear function dependent */
+/* on ma coefficients a[1..ma]. The input array ia[1..ma] */
+/* indicates by nonzero entries those components of a that should be */
+/* fitted for, and by zero entries those components that should be held */
+/* fixed at their input values. The program returns current best-fitt */
+/* values for the parameters a[1..ma], and chisq. The arrays */
+/* covar[1..ma][1..ma], alpha[1..ma][1..ma] are used as */
+/* working space during most iterations. Supply a routine */
+/* funcs(x, a, yfit, dyda, ma) */
+/* that evaluates the fitting function yfit, and its derivatives dyda[1..ma] */
+/* with respect to the fitting parameters a at x. On the first call provide */
+/* an initial guess for the parameters a, and set alamda<0 for initialization */
+/* (which then sets alamda=.001). If a step succeeds chisq becomes smaller */
+/* and alamda decreases by a factor of 10. If a step fails alamda grows by */
+/* a factor of 10. You must call this routine repeatedly until convergence */
+/* is achieved. Then, make one final call with alamda=0, so that */
+/* covar[1..ma][1..ma] returns the covar matrix, and alpha the */
+/* alpha matrix. (Parameters held fixed will return zero covariances.) */
+
+
+LCMSHANDLE cdecl cmsxLevenbergMarquardtInit(LPSAMPLEDCURVE x, LPSAMPLEDCURVE y, double sig,
+ double a[],
+ int ma,
+ void (*funcs)(double, double[], double*, double[], int)
+ );
+
+double cdecl cmsxLevenbergMarquardtAlamda(LCMSHANDLE hMRQ);
+double cdecl cmsxLevenbergMarquardtChiSq(LCMSHANDLE hMRQ);
+BOOL cdecl cmsxLevenbergMarquardtIterate(LCMSHANDLE hMRQ);
+BOOL cdecl cmsxLevenbergMarquardtFree(LCMSHANDLE hMRQ);
+
+/* ---------------------------------------------------------------------------- */
+
+
+
+typedef struct {
+
+ LPSAMPLEDCURVE x;
+ LPSAMPLEDCURVE y;
+ int ndata;
+ double* a;
+ int ma;
+ LPMATN covar;
+ LPMATN alpha;
+ double* atry;
+ LPMATN beta;
+ LPMATN oneda;
+ double* dyda;
+ double ochisq;
+ double sig;
+
+
+ void (*funcs)(double, double[], double*, double[], int);
+
+ double alamda;
+ double chisq;
+
+} LMRTQMIN, FAR* LPLMRTQMIN;
+
+
+
+
+static
+void mrqcof(LPLMRTQMIN pLM, double *a, LPMATN alpha, LPMATN beta, double *chisq)
+{
+ int i, j, k;
+ double ymod, wt, sig2i, dy;
+
+ for(j = 0; j < pLM->ma; j++)
+ {
+ for(k = 0; k <= j; k++)
+ alpha->Values[j][k] = 0.0;
+
+ beta->Values[j][0] = 0.0;
+ }
+
+ *chisq = 0.0;
+ sig2i = 1.0 / (pLM->sig * pLM->sig);
+
+ for(i = 0; i < pLM->ndata; i++)
+ {
+ (*(pLM->funcs))(pLM->x ->Values[i], a, &ymod, pLM->dyda, pLM->ma);
+
+ dy = pLM->y->Values[i] - ymod;
+
+ for(j = 0; j < pLM->ma; j++)
+ {
+ wt = pLM->dyda[j] * sig2i;
+
+ for(k = 0; k <= j; k++)
+ alpha->Values[j][k] += wt * pLM->dyda[k];
+
+ beta->Values[j][0] += dy * wt;
+ }
+
+ *chisq += dy * dy * sig2i;
+ }
+
+ for(j = 1; j < pLM->ma; j++) /* Fill in the symmetric side. */
+ for(k = 0; k < j; k++)
+ alpha->Values[k][j] = alpha->Values[j][k];
+}
+
+
+
+static
+void FreeStruct(LPLMRTQMIN pLM)
+{
+ if(pLM == NULL) return;
+
+ if(pLM->covar) MATNfree (pLM->covar);
+ if(pLM->alpha) MATNfree (pLM->alpha);
+ if(pLM->atry) free(pLM->atry);
+ if(pLM->beta) MATNfree (pLM->beta);
+ if(pLM->oneda) MATNfree (pLM->oneda);
+ if(pLM->dyda) free(pLM->dyda);
+ free(pLM);
+}
+
+
+
+LCMSHANDLE cmsxLevenbergMarquardtInit(LPSAMPLEDCURVE x, LPSAMPLEDCURVE y, double sig,
+ double a[],
+ int ma,
+ void (*funcs)(double, double[], double*, double[], int))
+
+{
+ int i;
+ LPLMRTQMIN pLM;
+
+ if (x ->nItems != y ->nItems) return NULL;
+
+ pLM = (LPLMRTQMIN) malloc(sizeof(LMRTQMIN));
+ if(!pLM)
+ return NULL;
+
+ ZeroMemory(pLM, sizeof(LMRTQMIN));
+
+ if((pLM->atry = (double*)malloc(ma * sizeof(double))) == NULL) goto failed;
+ if((pLM->beta = MATNalloc (ma, 1)) == NULL) goto failed;
+ if((pLM->oneda = MATNalloc (ma, 1)) == NULL) goto failed;
+
+
+
+ if((pLM->covar = MATNalloc(ma, ma)) == NULL) goto failed;
+ if((pLM->alpha = MATNalloc(ma, ma)) == NULL) goto failed;
+ if((pLM->dyda = (double*)malloc(ma * sizeof(double))) == NULL) goto failed;
+
+ pLM->alamda = 0.001;
+
+ pLM->ndata = x ->nItems;
+ pLM->x = x;
+ pLM->y = y;
+ pLM->ma = ma;
+ pLM->a = a;
+ pLM->funcs = funcs;
+ pLM->sig = sig;
+
+ mrqcof(pLM, a, pLM->alpha, pLM->beta, &pLM->chisq);
+ pLM->ochisq = (pLM->chisq);
+
+ for(i = 0; i < ma; i++) pLM->atry[i] = a[i];
+
+ return (LCMSHANDLE) pLM;
+
+failed:
+ FreeStruct(pLM);
+ return NULL;
+}
+
+
+BOOL cmsxLevenbergMarquardtFree(LCMSHANDLE hMRQ)
+{
+ LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ;
+ if(!pLM)
+ return false;
+
+ FreeStruct(pLM);
+ return true;
+}
+
+
+BOOL cmsxLevenbergMarquardtIterate(LCMSHANDLE hMRQ)
+{
+ int j, k;
+ BOOL sts;
+ LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ;
+ if(!pLM)
+ return false;
+
+ for(j = 0; j < pLM->ma; j++) /* Alter linearized fitting matrix, by augmenting diagonal elements. */
+ {
+ for(k = 0; k < pLM->ma; k++)
+ pLM->covar->Values[j][k] = pLM->alpha->Values[j][k];
+
+ pLM->covar->Values[j][j] = pLM->alpha->Values[j][j] * (1.0 + pLM ->alamda);
+ pLM->oneda->Values[j][0] = pLM->beta->Values[j][0];
+ }
+
+ if((sts = MATNsolve (pLM->covar, pLM->oneda)) != true) /* Matrix solution. */
+ return sts;
+
+ for(j = 0; j < pLM->ma; j++) /* Did the trial succeed? */
+ pLM->atry[j] = pLM->a[j] + pLM->oneda->Values[j][0];
+
+ mrqcof(pLM, pLM->atry, pLM->covar, pLM->oneda, &pLM -> chisq);
+
+ if (pLM->chisq < pLM->ochisq) { /* Success, accept the new solution. */
+
+ pLM->alamda *= 0.1;
+ pLM->ochisq = pLM->chisq;
+
+ for(j = 0; j < pLM->ma; j++)
+ {
+ for(k = 0; k < pLM->ma; k++)
+ pLM->alpha->Values[j][k] = pLM->covar->Values[j][k];
+
+ pLM->beta->Values[j][0] = pLM->oneda->Values[j][0];
+ }
+
+ for (j=0; j < pLM ->ma; j++) pLM->a[j] = pLM->atry[j];
+ }
+ else /* Failure, increase alamda and return. */
+ {
+ pLM -> alamda *= 10.0;
+ pLM->chisq = pLM->ochisq;
+ }
+
+ return true;
+}
+
+
+double cmsxLevenbergMarquardtAlamda(LCMSHANDLE hMRQ)
+{
+ LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ;
+
+ return pLM ->alamda;
+}
+
+double cmsxLevenbergMarquardtChiSq(LCMSHANDLE hMRQ)
+{
+ LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ;
+
+ return pLM ->chisq;
+}
diff --git a/src/libs/lprof/cmslnr.cpp b/src/libs/lprof/cmslnr.cpp
new file mode 100644
index 00000000..dddd8e38
--- /dev/null
+++ b/src/libs/lprof/cmslnr.cpp
@@ -0,0 +1,560 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.09a */
+
+
+#include "lcmsprf.h"
+
+
+LPGAMMATABLE cdecl cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints);
+void cdecl cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium);
+
+void cdecl cmsxComputeLinearizationTables(LPMEASUREMENT m,
+ int ColorSpace,
+ LPGAMMATABLE Lin[3],
+ int nResultingPoints,
+ int Medium);
+
+
+void cdecl cmsxApplyLinearizationTable(double In[3],
+ LPGAMMATABLE Gamma[3],
+ double Out[3]);
+
+void cdecl cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3]);
+
+
+
+/* ------------------------------------------------------------- Implementation */
+
+
+#define EPSILON 0.00005
+#define LEVENBERG_MARQUARDT_ITERATE_MAX 150
+
+/* In order to track linearization tables, we use following procedure */
+/* */
+/* We first assume R', G' and B' does exhibit a non-linear behaviour */
+/* that can be separated for each channel as Yr(R'), Yg(G'), Yb(B') */
+/* This is the shaper step */
+/* */
+/* R = Lr(R') */
+/* G = Lg(G') */
+/* B = Lb(B') (0.0) */
+/* */
+/* After this step, RGB is converted to XYZ by a matrix multiplication */
+/* */
+/* |X| |R| */
+/* |Y| = [M]|G| */
+/* |Z| |B| (1.0) */
+/* */
+/* In order to extract Lr,Lg,Lb tables, we are interested only on Y part */
+/* */
+/* Y = (m1 * R + m2 * G + m3 * B) (1.1) */
+/* */
+/* The total intensity for maximum RGB = (1, 1, 1) should be 1, */
+/* */
+/* 1 = m1 * 1 + m2 * 1 + m3 * 1, so */
+/* */
+/* m1 + m2 + m3 = 1.0 (1.2) */
+/* */
+/* We now impose that for neutral (gray) patches, RGB components must be equal */
+/* */
+/* R = G = B = Gray */
+/* */
+/* So, substituting in (1.1): */
+/* */
+/* Y = (m1 + m2 + m3) Gray */
+/* */
+/* and for (1.2), (m1+m2+m3) = 1, so */
+/* */
+/* Y = Gray = Lr(R') = Lg(G') = Lb(B') */
+/* */
+/* That is, after prelinearization, RGB of gray patches should give */
+/* same values for R, G and B. And this value is Y. */
+/* */
+/* */
+
+
+static
+LPSAMPLEDCURVE NormalizeTo(LPSAMPLEDCURVE X, double N, BOOL lAddEndPoint)
+{
+ int i, nItems;
+ LPSAMPLEDCURVE XNorm;
+
+ nItems = X ->nItems;
+ if (lAddEndPoint) nItems++;
+
+ XNorm = cmsAllocSampledCurve(nItems);
+
+ for (i=0; i < X ->nItems; i++) {
+
+ XNorm ->Values[i] = X ->Values[i] / N;
+ }
+
+ if (lAddEndPoint)
+ XNorm -> Values[X ->nItems] = 1.0;
+
+ return XNorm;
+}
+
+
+/* */
+/* ------------------------------------------------------------------------------ */
+/* */
+/* Our Monitor model. We assume gamma has a general expression of */
+/* */
+/* Fn(x) = (Gain * x + offset) ^ gamma | for x >= 0 */
+/* Fn(x) = 0 | for x < 0 */
+/* */
+/* First partial derivatives are */
+/* */
+/* dFn/dGamma = Fn * ln(Base) */
+/* dFn/dGain = gamma * x * ((Gain * x + Offset) ^ (gamma -1)) */
+/* dFn/dOffset = gamma * ((Gain * x + Offset) ^ (gamma -1)) */
+/* */
+
+static
+void GammaGainOffsetFn(double x, double *a, double *y, double *dyda, int na)
+{
+ double Gamma,Gain,Offset;
+ double Base;
+
+ Gamma = a[0];
+ Gain = a[1];
+ Offset = a[2];
+
+ Base = Gain * x + Offset;
+
+ if (Base < 0) {
+
+ Base = 0.0;
+ *y = 0.0;
+ dyda[0] = 0.0;
+ dyda[1] = 0.0;
+ dyda[2] = 0.0;
+
+
+ } else {
+
+
+ /* The function itself */
+ *y = pow(Base, Gamma);
+
+ /* dyda[0] is partial derivative across Gamma */
+ dyda[0] = *y * log(Base);
+
+ /* dyda[1] is partial derivative across gain */
+ dyda[1] = (x * Gamma) * pow(Base, Gamma-1.0);
+
+ /* dyda[2] is partial derivative across offset */
+ dyda[2] = Gamma * pow(Base, Gamma-1.0);
+ }
+}
+
+
+/* Fit curve to our gamma-gain-offset model. */
+
+static
+BOOL OneTry(LPSAMPLEDCURVE XNorm, LPSAMPLEDCURVE YNorm, double a[])
+{
+ LCMSHANDLE h;
+ double ChiSq, OldChiSq;
+ int i;
+ BOOL Status = true;
+
+ /* initial guesses */
+
+ a[0] = 3.0; /* gamma */
+ a[1] = 4.0; /* gain */
+ a[2] = 6.0; /* offset */
+ a[3] = 0.0; /* Thereshold */
+ a[4] = 0.0; /* Black */
+
+
+ /* Significance = 0.02 gives good results */
+
+ h = cmsxLevenbergMarquardtInit(XNorm, YNorm, 0.02, a, 3, GammaGainOffsetFn);
+ if (h == NULL) return false;
+
+
+ OldChiSq = cmsxLevenbergMarquardtChiSq(h);
+
+ for(i = 0; i < LEVENBERG_MARQUARDT_ITERATE_MAX; i++) {
+
+ if (!cmsxLevenbergMarquardtIterate(h)) {
+ Status = false;
+ break;
+ }
+
+ ChiSq = cmsxLevenbergMarquardtChiSq(h);
+
+ if(OldChiSq != ChiSq && (OldChiSq - ChiSq) < EPSILON)
+ break;
+
+ OldChiSq = ChiSq;
+ }
+
+ cmsxLevenbergMarquardtFree(h);
+
+ return Status;
+}
+
+/* Tries to fit gamma as per IEC 61966-2.1 using Levenberg-Marquardt method */
+/* */
+/* Y = (aX + b)^Gamma | X >= d */
+/* Y = cX | X < d */
+
+LPGAMMATABLE cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints)
+{
+ double a[5];
+ LPSAMPLEDCURVE XNorm, YNorm;
+ double e, Max;
+
+
+ /* Coarse approximation, to find maximum. */
+ /* We have only a portion of curve. It is likely */
+ /* maximum will not fall on exactly 100. */
+
+ if (!OneTry(X, Y, a))
+ return 0;
+
+ /* Got parameters. Compute maximum. */
+ e = a[1]* 255.0 + a[2];
+ if (e < 0) return 0;
+ Max = pow(e, a[0]);
+
+
+ /* Normalize values to maximum */
+ XNorm = NormalizeTo(X, 255.0, false);
+ YNorm = NormalizeTo(Y, Max, false);
+
+ /* Do the final fitting */
+ if (!OneTry(XNorm, YNorm, a))
+ return 0;
+
+ /* Type 3 = IEC 61966-2.1 (sRGB) */
+ /* Y = (aX + b)^Gamma | X >= d */
+ /* Y = cX | X < d */
+ return cmsBuildParametricGamma(nResultingPoints, 3, a);
+}
+
+
+
+
+
+/* A dumb bubble sort */
+
+static
+void Bubble(LPSAMPLEDCURVE C, LPSAMPLEDCURVE L)
+{
+#define SWAP(a, b) { tmp = (a); (a) = (b); (b) = tmp; }
+
+ BOOL lSwapped;
+ int i, nItems;
+ double tmp;
+
+ nItems = C -> nItems;
+ do {
+ lSwapped = false;
+
+ for (i= 0; i < nItems - 1; i++) {
+
+ if (C->Values[i] > C->Values[i+1]) {
+
+ SWAP(C->Values[i], C->Values[i+1]);
+ SWAP(L->Values[i], L->Values[i+1]);
+ lSwapped = true;
+ }
+ }
+
+ } while (lSwapped);
+
+#undef SWAP
+}
+
+
+
+/* Check for monotonicity. Force it if is not the case. */
+
+static
+void CheckForMonotonicSampledCurve(LPSAMPLEDCURVE t)
+{
+ int n = t ->nItems;
+ int i;
+ double last;
+
+ last = t ->Values[n-1];
+ for (i = n-2; i >= 0; --i) {
+
+ if (t ->Values[i] > last)
+
+ t ->Values[i] = last;
+ else
+ last = t ->Values[i];
+
+ }
+
+}
+
+/* The main gamma inferer. Tries first by gamma-gain-offset, */
+/* if not proper reverts to curve guessing. */
+
+static
+LPGAMMATABLE BuildGammaTable(LPSAMPLEDCURVE C, LPSAMPLEDCURVE L, int nResultingPoints)
+{
+ LPSAMPLEDCURVE Cw, Lw, Cn, Ln;
+ LPSAMPLEDCURVE out;
+ LPGAMMATABLE Result;
+ double Lmax, Lend, Cmax;
+
+ /* Try to see if it can be fitted */
+ Result = cmsxEstimateGamma(C, L, nResultingPoints);
+ if (Result)
+ return Result;
+
+
+ /* No... build curve from scratch. Since we have not */
+ /* endpoints, a coarse linear extrapolation should be */
+ /* applied in order to get the expected maximum. */
+
+ Cw = cmsDupSampledCurve(C);
+ Lw = cmsDupSampledCurve(L);
+
+ Bubble(Cw, Lw);
+
+ /* Get endpoint */
+ Lmax = Lw->Values[Lw ->nItems - 1];
+ Cmax = Cw->Values[Cw ->nItems - 1];
+
+ /* Linearly extrapolate */
+ Lend = (255 * Lmax) / Cmax;
+
+ Ln = NormalizeTo(Lw, Lend, true);
+ Cn = NormalizeTo(Cw, 255.0, true);
+
+ cmsFreeSampledCurve(Cw);
+ cmsFreeSampledCurve(Lw);
+
+ /* Add endpoint */
+ out = cmsJoinSampledCurves(Cn, Ln, nResultingPoints);
+
+ cmsFreeSampledCurve(Cn);
+ cmsFreeSampledCurve(Ln);
+
+ CheckForMonotonicSampledCurve(out);
+
+ cmsSmoothSampledCurve(out, nResultingPoints*4.);
+ cmsClampSampledCurve(out, 0, 1.0);
+
+ Result = cmsConvertSampledCurveToGamma(out, 1.0);
+
+ cmsFreeSampledCurve(out);
+ return Result;
+}
+
+
+
+
+void cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium)
+{
+ LPPATCH White;
+ cmsCIEXYZ WhiteXYZ;
+ int i;
+
+ if (Medium == MEDIUM_REFLECTIVE_D50)
+ {
+ WhiteXYZ.X = D50X * 100.;
+ WhiteXYZ.Y = D50Y * 100.;
+ WhiteXYZ.Z = D50Z * 100.;
+ }
+ else {
+
+ White = cmsxPCollFindWhite(m, Valids, NULL);
+ if (!White) return;
+
+ WhiteXYZ = White ->XYZ;
+ }
+
+ /* For all patches with XYZ and without Lab, add Lab values. */
+ /* Transmissive profiles does need to locate its own white */
+ /* point for device gray. Reflective does use D50 */
+
+ for (i=0; i < m -> nPatches; i++) {
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ if ((p ->dwFlags & PATCH_HAS_XYZ) &&
+ (!(p ->dwFlags & PATCH_HAS_Lab) || (Medium == MEDIUM_TRANSMISSIVE))) {
+
+ cmsXYZ2Lab(&WhiteXYZ, &p->Lab, &p->XYZ);
+ p -> dwFlags |= PATCH_HAS_Lab;
+ }
+ }
+ }
+}
+
+
+/* Compute linearization tables, trying to fit in a pure */
+/* exponential gamma. If gamma cannot be accurately infered, */
+/* then does build a smooth, monotonic curve that does the job. */
+
+void cmsxComputeLinearizationTables(LPMEASUREMENT m,
+ int ColorSpace,
+ LPGAMMATABLE Lin[3],
+ int nResultingPoints,
+ int Medium)
+
+{
+ LPSAMPLEDCURVE R, G, B, L;
+ LPGAMMATABLE gr, gg, gb;
+ SETOFPATCHES Neutrals;
+ int nGrays;
+ int i;
+
+ /* We need Lab for grays. */
+ cmsxCompleteLabOfPatches(m, m->Allowed, Medium);
+
+ /* Add neutrals, normalize to max */
+ Neutrals = cmsxPCollBuildSet(m, false);
+ cmsxPCollPatchesNearNeutral(m, m ->Allowed, 15, Neutrals);
+
+ nGrays = cmsxPCollCountSet(m, Neutrals);
+
+ R = cmsAllocSampledCurve(nGrays);
+ G = cmsAllocSampledCurve(nGrays);
+ B = cmsAllocSampledCurve(nGrays);
+ L = cmsAllocSampledCurve(nGrays);
+
+ nGrays = 0;
+
+ /* Collect patches */
+ for (i=0; i < m -> nPatches; i++) {
+
+ if (Neutrals[i]) {
+
+ LPPATCH gr = m -> Patches + i;
+
+
+ R -> Values[nGrays] = gr -> Colorant.RGB[0];
+ G -> Values[nGrays] = gr -> Colorant.RGB[1];
+ B -> Values[nGrays] = gr -> Colorant.RGB[2];
+ L -> Values[nGrays] = gr -> XYZ.Y;
+
+ nGrays++;
+ }
+
+ }
+
+
+ gr = BuildGammaTable(R, L, nResultingPoints);
+ gg = BuildGammaTable(G, L, nResultingPoints);
+ gb = BuildGammaTable(B, L, nResultingPoints);
+
+ cmsFreeSampledCurve(R);
+ cmsFreeSampledCurve(G);
+ cmsFreeSampledCurve(B);
+ cmsFreeSampledCurve(L);
+
+ if (ColorSpace == PT_Lab) {
+
+ LPGAMMATABLE Gamma3 = cmsBuildGamma(nResultingPoints, 3.0);
+
+ Lin[0] = cmsJoinGammaEx(gr, Gamma3, nResultingPoints);
+ Lin[1] = cmsJoinGammaEx(gg, Gamma3, nResultingPoints);
+ Lin[2] = cmsJoinGammaEx(gb, Gamma3, nResultingPoints);
+
+ cmsFreeGamma(gr); cmsFreeGamma(gg); cmsFreeGamma(gb);
+ cmsFreeGamma(Gamma3);
+ }
+ else {
+
+
+ LPGAMMATABLE Gamma1 = cmsBuildGamma(nResultingPoints, 1.0);
+
+ Lin[0] = cmsJoinGammaEx(gr, Gamma1, nResultingPoints);
+ Lin[1] = cmsJoinGammaEx(gg, Gamma1, nResultingPoints);
+ Lin[2] = cmsJoinGammaEx(gb, Gamma1, nResultingPoints);
+
+ cmsFreeGamma(gr); cmsFreeGamma(gg); cmsFreeGamma(gb);
+ cmsFreeGamma(Gamma1);
+
+ }
+
+}
+
+
+
+/* Apply linearization. WORD encoded version */
+
+void cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3])
+{
+ L16PARAMS Lut16;
+
+ cmsCalcL16Params(Gamma[0] -> nEntries, &Lut16);
+
+ Out[0] = cmsLinearInterpLUT16(In[0], Gamma[0] -> GammaTable, &Lut16);
+ Out[1] = cmsLinearInterpLUT16(In[1], Gamma[1] -> GammaTable, &Lut16);
+ Out[2] = cmsLinearInterpLUT16(In[2], Gamma[2] -> GammaTable, &Lut16);
+
+
+}
+
+
+
+/* Apply linearization. double version */
+
+void cmsxApplyLinearizationTable(double In[3], LPGAMMATABLE Gamma[3], double Out[3])
+{
+ WORD rw, gw, bw;
+ double rd, gd, bd;
+ L16PARAMS Lut16;
+
+
+ cmsCalcL16Params(Gamma[0] -> nEntries, &Lut16);
+
+ rw = (WORD) floor(_cmsxSaturate255To65535(In[0]) + .5);
+ gw = (WORD) floor(_cmsxSaturate255To65535(In[1]) + .5);
+ bw = (WORD) floor(_cmsxSaturate255To65535(In[2]) + .5);
+
+ rd = cmsLinearInterpLUT16(rw , Gamma[0] -> GammaTable, &Lut16);
+ gd = cmsLinearInterpLUT16(gw, Gamma[1] -> GammaTable, &Lut16);
+ bd = cmsLinearInterpLUT16(bw, Gamma[2] -> GammaTable, &Lut16);
+
+ Out[0] = _cmsxSaturate65535To255(rd); /* back to 0..255 */
+ Out[1] = _cmsxSaturate65535To255(gd);
+ Out[2] = _cmsxSaturate65535To255(bd);
+}
+
diff --git a/src/libs/lprof/cmsmatn.cpp b/src/libs/lprof/cmsmatn.cpp
new file mode 100644
index 00000000..bca52717
--- /dev/null
+++ b/src/libs/lprof/cmsmatn.cpp
@@ -0,0 +1,323 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.09a */
+
+
+#include "lcmsprf.h"
+
+
+LPMATN cdecl MATNalloc(int Rows, int Cols);
+void cdecl MATNfree (LPMATN mat);
+LPMATN cdecl MATNmult(LPMATN a1, LPMATN a2);
+double cdecl MATNcross(LPMATN a);
+void cdecl MATNscalar (LPMATN a, double scl, LPMATN b);
+LPMATN cdecl MATNtranspose (LPMATN a);
+BOOL cdecl MATNsolve(LPMATN a, LPMATN b);
+
+
+/* ------------------------------------------------------------ Implementation */
+
+/* Free matrix */
+
+void MATNfree(LPMATN mat)
+{
+ int i;
+
+ if (mat == NULL) return;
+
+ for (i = 0; i < mat->Rows; i++)
+ {
+ if (mat -> Values[i] != NULL)
+ free (mat->Values[i]);
+ }
+
+ free(mat->Values);
+ free(mat);
+}
+
+
+/* Allocate (and Zero) a new matrix */
+
+LPMATN MATNalloc(int Rows, int Cols)
+{
+ int i;
+
+ LPMATN mat = (LPMATN) malloc (sizeof (MATN));
+ if (mat == NULL) return mat;
+
+ ZeroMemory(mat, sizeof(MATN));
+
+ mat->Rows = Rows;
+ mat->Cols = Cols;
+ mat->Values = (double**) malloc(Rows * sizeof (double*));
+
+ if (mat->Values == NULL) {
+ free(mat);
+ return NULL;
+ }
+
+ ZeroMemory(mat -> Values, Rows * sizeof (double*));
+
+ for (i = 0; i < Rows; i++)
+ {
+ mat-> Values [i] = (double*) malloc(Cols * sizeof (double));
+ if (mat -> Values[i] == NULL) {
+ MATNfree(mat);
+ return NULL;
+ }
+
+ }
+
+ return mat;
+}
+
+#define DO_SWAP(a, b, tmp) { tmp = (a); (a) = (b); (b) = tmp; }
+
+/* Gauss-Jordan elimination. There is also a more */
+/* exahustive non-singular matrix checking part. */
+
+BOOL MATNsolve(LPMATN a, LPMATN b)
+{
+ BOOL status;
+ int n = a->Rows;
+ int i, iCol=0, iRow=0, j, k;
+ double fMax, fAbs, fSave, fInf, temp;
+ int* aiColIndex;
+ int* aiRowIndex=0;
+ int* aiPivoted=0;
+
+
+ if (a->Rows != a->Cols) return false;
+
+ status = false;
+ if((aiColIndex = (int*) malloc(n * sizeof(int))) == NULL)
+ goto GotError;
+
+ if((aiRowIndex = (int*) malloc(n * sizeof(int))) == NULL)
+ goto GotError;
+
+ if((aiPivoted = (int*) malloc(n * sizeof(int))) == NULL)
+ goto GotError;
+
+ ZeroMemory(aiPivoted, n * sizeof(int));
+
+
+ for(i = 0; i < n; i++) {
+
+ /* search matrix (excluding pivoted rows) for maximum absolute entry */
+
+ fMax = 0.0;
+ for (j = 0; j < n; j++)
+ if (aiPivoted[j] != 1)
+ for (k = 0; k < n; k++)
+ {
+ fAbs = fabs(a->Values[j][k]);
+ if (fAbs >= fMax) {
+
+ fMax = fAbs;
+ iRow = j;
+ iCol = k;
+ }
+ else
+ if (aiPivoted[k] > 1) {
+
+ status = false;
+ goto GotError;
+ }
+ }
+
+ aiPivoted[iCol]++;
+
+ /* swap rows so that A[iCol][iCol] contains the pivot entry */
+
+ if (iRow != iCol) {
+
+ for(j = 0; j < n; j++)
+ DO_SWAP(a->Values[iRow][j], a->Values[iCol][j], temp)
+
+ DO_SWAP(b->Values[iRow][0], b->Values[iCol][0], temp)
+ }
+
+ /* keep track of the permutations of the rows */
+
+ aiRowIndex[i] = iRow;
+ aiColIndex[i] = iCol;
+
+ if (a->Values[iCol][iCol] == 0.0)
+ {
+ status = false;
+ goto GotError;
+ }
+
+ /* scale the row so that the pivot entry is 1 */
+
+ fInf = 1.0 / a->Values[iCol][iCol];
+ a->Values[iCol][iCol] = 1.0;
+
+ for(j = 0; j < n; j++)
+ a->Values[iCol][j] *= fInf;
+
+ b->Values[iCol][0] *= fInf;
+
+ /* zero out the pivot column locations in the other rows */
+
+ for(j = 0; j < n; j++)
+ if (j != iCol) {
+
+ fSave = a->Values[j][iCol];
+ a->Values[j][iCol] = 0.0;
+
+ for(k = 0; k < n; k++)
+ a->Values[j][k] -= a->Values[iCol][k] * fSave;
+
+ b->Values[j][0] -= b->Values[iCol][0] * fSave;
+ }
+ }
+
+ /* reorder rows so that A[][] stores the inverse of the original matrix */
+
+ for(i = n - 1; i >= 0; i--) {
+
+ if(aiRowIndex[i] != aiColIndex[i])
+ for(j = 0; j < n; j++)
+ DO_SWAP(a->Values[j][aiRowIndex[i]], a->Values[j][aiColIndex[i]], temp)
+ }
+
+ status = true;
+
+GotError:
+ if(aiColIndex) free(aiColIndex);
+ if(aiRowIndex) free(aiRowIndex);
+ if(aiPivoted) free(aiPivoted);
+ return status;
+
+}
+
+#undef DO_SWAP
+
+
+LPMATN MATNmult(LPMATN a1, LPMATN a2)
+{
+ int i, j, k;
+ LPMATN b;
+
+ if (a1->Cols != a2->Rows)
+ return NULL;
+
+ b = MATNalloc (a1->Rows, a2->Cols);
+ if (b == NULL)
+ return NULL;
+
+ for (i = 0; i < b->Rows; i++) {
+
+ for (j = 0; j < b->Cols; j++) {
+
+ b->Values[i][j] = 0.0;
+
+ for (k = 0; k < a1->Cols; k++) {
+
+ b->Values[i][j] += a1->Values[i][k] * a2->Values[k][j];
+ }
+ }
+ }
+
+ return b;
+}
+
+
+double MATNcross(LPMATN a)
+{
+ int i;
+ double prod = 0.0;
+
+ for (i = 0; i < a->Rows; i++) {
+
+ prod += a->Values[i][0]*a->Values[i][0];
+ }
+ return prod;
+}
+
+
+void MATNscalar(LPMATN a, double scl, LPMATN b)
+{
+ int i, j;
+
+ if (a->Rows != b->Rows || a->Cols != b->Cols)
+ return;
+
+ for (i = 0; i < a->Rows; i++) {
+
+ for (j = 0; j < a->Cols; j++)
+ b->Values[i][j] = a->Values[i][j] * scl;
+ }
+}
+
+
+LPMATN MATNtranspose(LPMATN a)
+{
+ LPMATN b = MATNalloc(a->Cols, a->Rows);
+ if (b != NULL) {
+
+ int i, j;
+
+ for (i = 0; i < a->Rows; i++)
+ {
+ for (j = 0; j < a->Cols; j++)
+ b->Values[j][i] = a->Values [i][j];
+ }
+ }
+ return b;
+}
+
+
+
+/* Used for debug purposes */
+#ifdef DEBUG
+void MATNprintf(char* name, LPMATN mat)
+{
+ int i, j;
+
+ printf ("%s:\n", name);
+ for (i= 0; i < mat->Rows; i++) {
+
+ printf ("%3d", i);
+ for (j = 0; j < mat->Cols; j++)
+ printf (" %.5f", mat->Values[i][j]);
+ printf ("\n");
+ }
+}
+#endif
+
+
diff --git a/src/libs/lprof/cmsmkmsh.cpp b/src/libs/lprof/cmsmkmsh.cpp
new file mode 100644
index 00000000..c0e3d4c3
--- /dev/null
+++ b/src/libs/lprof/cmsmkmsh.cpp
@@ -0,0 +1,346 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.08a */
+
+
+#include "lcmsprf.h"
+
+
+BOOL cdecl cmsxComputeMatrixShaper(const char* ReferenceSheet,
+ const char* MeasurementSheet,
+ int Medium,
+ LPGAMMATABLE TransferCurves[3],
+ LPcmsCIEXYZ WhitePoint,
+ LPcmsCIEXYZ BlackPoint,
+ LPcmsCIExyYTRIPLE Primaries);
+
+
+
+/* ------------------------------------------------------------- Implementation */
+
+
+static
+void Div100(LPcmsCIEXYZ xyz)
+{
+ xyz -> X /= 100; xyz -> Y /= 100; xyz -> Z /= 100;
+}
+
+
+
+/* Compute endpoints */
+
+static
+BOOL ComputeWhiteAndBlackPoints(LPMEASUREMENT Linearized,
+ LPGAMMATABLE TransferCurves[3],
+ LPcmsCIEXYZ Black, LPcmsCIEXYZ White)
+{
+
+ double Zeroes[3], Ones[3], lmin[3], lmax[3];
+
+ SETOFPATCHES Neutrals = cmsxPCollBuildSet(Linearized, false);
+
+ cmsxPCollPatchesNearNeutral(Linearized, Linearized->Allowed,
+ 15, Neutrals);
+
+ Zeroes[0] = Zeroes[1] = Zeroes[2] = 0.0;
+ Ones[0] = Ones[1] = Ones[2] = 255.0;
+
+
+ cmsxApplyLinearizationTable(Zeroes, TransferCurves, lmin);
+ cmsxApplyLinearizationTable(Ones, TransferCurves, lmax);
+
+
+ /* Global regression to find White & Black points */
+ if (!cmsxRegressionInterpolatorRGB(Linearized, PT_XYZ,
+ 4,
+ true,
+ 12,
+ lmin[0], lmin[1], lmin[2],
+ Black)) return false;
+
+ if (!cmsxRegressionInterpolatorRGB(Linearized, PT_XYZ,
+ 4,
+ true,
+ 12,
+ lmax[0], lmax[1], lmax[2],
+ White)) return false;
+
+ _cmsxClampXYZ100(White);
+ _cmsxClampXYZ100(Black);
+
+ return true;
+
+}
+
+
+/* Study convergence of primary axis */
+
+static
+BOOL ComputePrimary(LPMEASUREMENT Linearized,
+ LPGAMMATABLE TransferCurves[3],
+ int n,
+ LPcmsCIExyY Primary)
+{
+
+ double Ones[3], lmax[3];
+ cmsCIEXYZ PrimXYZ;
+ SETOFPATCHES SetPrimary;
+ int nR;
+
+
+ /* At first, try to see if primaries are already in measurement */
+
+ SetPrimary = cmsxPCollBuildSet(Linearized, false);
+ nR = cmsxPCollPatchesNearPrimary(Linearized, Linearized->Allowed,
+ n, 32, SetPrimary);
+
+ Ones[0] = Ones[1] = Ones[2] = 0;
+ Ones[n] = 255.0;
+
+ cmsxApplyLinearizationTable(Ones, TransferCurves, lmax);
+
+ /* Do incremental regression to find primaries */
+ if (!cmsxRegressionInterpolatorRGB(Linearized, PT_XYZ,
+ 4,
+ false,
+ 12,
+ lmax[0], lmax[1], lmax[2],
+ &PrimXYZ)) return false;
+
+ _cmsxClampXYZ100(&PrimXYZ);
+ cmsXYZ2xyY(Primary, &PrimXYZ);
+ return true;
+
+
+}
+
+
+
+/* Does compute a matrix-shaper based on patches. */
+
+static
+double Clip(double d)
+{
+ return d > 0 ? d: 0;
+}
+
+
+BOOL cmsxComputeMatrixShaper(const char* ReferenceSheet,
+ const char* MeasurementSheet,
+ int Medium,
+ LPGAMMATABLE TransferCurves[3],
+ LPcmsCIEXYZ WhitePoint,
+ LPcmsCIEXYZ BlackPoint,
+ LPcmsCIExyYTRIPLE Primaries)
+{
+
+ MEASUREMENT Linearized;
+ cmsCIEXYZ Black, White;
+ cmsCIExyYTRIPLE PrimarySet;
+ LPPATCH PatchWhite, PatchBlack;
+ LPPATCH PatchRed, PatchGreen, PatchBlue;
+ double Distance;
+
+ /* Load sheets */
+
+ if (!cmsxPCollBuildMeasurement(&Linearized,
+ ReferenceSheet,
+ MeasurementSheet,
+ PATCH_HAS_XYZ|PATCH_HAS_RGB)) return false;
+
+
+
+ /* Any patch to deal of? */
+ if (cmsxPCollCountSet(&Linearized, Linearized.Allowed) <= 0) return false;
+
+
+ /* Try to see if proper primaries, white and black already present */
+ PatchWhite = cmsxPCollFindWhite(&Linearized, Linearized.Allowed, &Distance);
+ if (Distance != 0)
+ PatchWhite = NULL;
+
+ PatchBlack = cmsxPCollFindBlack(&Linearized, Linearized.Allowed, &Distance);
+ if (Distance != 0)
+ PatchBlack = NULL;
+
+ PatchRed = cmsxPCollFindPrimary(&Linearized, Linearized.Allowed, 0, &Distance);
+ if (Distance != 0)
+ PatchRed = NULL;
+
+ PatchGreen = cmsxPCollFindPrimary(&Linearized, Linearized.Allowed, 1, &Distance);
+ if (Distance != 0)
+ PatchGreen = NULL;
+
+ PatchBlue = cmsxPCollFindPrimary(&Linearized, Linearized.Allowed, 2, &Distance);
+ if (Distance != 0)
+ PatchBlue= NULL;
+
+ /* If we got primaries, then we can also get prelinearization */
+ /* by Levenberg-Marquardt. This applies on monitor profiles */
+
+ if (PatchWhite && PatchRed && PatchGreen && PatchBlue) {
+
+ /* Build matrix with primaries */
+
+ MAT3 Mat, MatInv;
+ LPSAMPLEDCURVE Xr,Yr, Xg, Yg, Xb, Yb;
+ int i, nRes, cnt;
+
+ VEC3init(&Mat.v[0], PatchRed->XYZ.X, PatchGreen->XYZ.X, PatchBlue->XYZ.X);
+ VEC3init(&Mat.v[1], PatchRed->XYZ.Y, PatchGreen->XYZ.Y, PatchBlue->XYZ.Y);
+ VEC3init(&Mat.v[2], PatchRed->XYZ.Z, PatchGreen->XYZ.Z, PatchBlue->XYZ.Z);
+
+ /* Invert matrix */
+ MAT3inverse(&Mat, &MatInv);
+
+ nRes = cmsxPCollCountSet(&Linearized, Linearized.Allowed);
+
+ Xr = cmsAllocSampledCurve(nRes);
+ Yr = cmsAllocSampledCurve(nRes);
+ Xg = cmsAllocSampledCurve(nRes);
+ Yg = cmsAllocSampledCurve(nRes);
+ Xb = cmsAllocSampledCurve(nRes);
+ Yb = cmsAllocSampledCurve(nRes);
+
+ /* Convert XYZ of all patches to RGB */
+ cnt = 0;
+ for (i=0; i < Linearized.nPatches; i++) {
+
+ if (Linearized.Allowed[i]) {
+
+ VEC3 RGBprime, XYZ;
+ LPPATCH p;
+
+ p = Linearized.Patches + i;
+ XYZ.n[0] = p -> XYZ.X;
+ XYZ.n[1] = p -> XYZ.Y;
+ XYZ.n[2] = p -> XYZ.Z;
+
+ MAT3eval(&RGBprime, &MatInv, &XYZ);
+
+ Xr ->Values[cnt] = p ->Colorant.RGB[0];
+ Yr ->Values[cnt] = Clip(RGBprime.n[0]);
+
+ Xg ->Values[cnt] = p ->Colorant.RGB[1];
+ Yg ->Values[cnt] = Clip(RGBprime.n[1]);
+
+ Xb ->Values[cnt] = p ->Colorant.RGB[2];
+ Yb ->Values[cnt] = Clip(RGBprime.n[2]);
+
+ cnt++;
+
+ }
+ }
+
+ TransferCurves[0] = cmsxEstimateGamma(Xr, Yr, 1024);
+ TransferCurves[1] = cmsxEstimateGamma(Xg, Yg, 1024);
+ TransferCurves[2] = cmsxEstimateGamma(Xb, Yb, 1024);
+
+ if (WhitePoint) {
+
+ WhitePoint->X = PatchWhite->XYZ.X;
+ WhitePoint->Y= PatchWhite ->XYZ.Y;
+ WhitePoint->Z= PatchWhite ->XYZ.Z;
+ }
+
+ if (BlackPoint && PatchBlack) {
+
+ BlackPoint->X = PatchBlack ->XYZ.X;
+ BlackPoint->Y = PatchBlack ->XYZ.Y;
+ BlackPoint->Z = PatchBlack ->XYZ.Z;
+ }
+
+ if (Primaries) {
+
+ cmsXYZ2xyY(&Primaries->Red, &PatchRed ->XYZ);
+ cmsXYZ2xyY(&Primaries->Green, &PatchGreen ->XYZ);
+ cmsXYZ2xyY(&Primaries->Blue, &PatchBlue ->XYZ);
+
+ }
+
+
+ cmsFreeSampledCurve(Xr);
+ cmsFreeSampledCurve(Yr);
+ cmsFreeSampledCurve(Xg);
+ cmsFreeSampledCurve(Yg);
+ cmsFreeSampledCurve(Xb);
+ cmsFreeSampledCurve(Yb);
+
+ cmsxPCollFreeMeasurements(&Linearized);
+
+ return true;
+ }
+
+
+
+
+ /* Compute prelinearization */
+ cmsxComputeLinearizationTables(&Linearized, PT_XYZ, TransferCurves, 1024, Medium);
+
+ /* Linearize measurements */
+ cmsxPCollLinearizePatches(&Linearized, Linearized.Allowed, TransferCurves);
+
+
+ /* Endpoints */
+ ComputeWhiteAndBlackPoints(&Linearized, TransferCurves, &Black, &White);
+
+ /* Primaries */
+ ComputePrimary(&Linearized, TransferCurves, 0, &PrimarySet.Red);
+ ComputePrimary(&Linearized, TransferCurves, 1, &PrimarySet.Green);
+ ComputePrimary(&Linearized, TransferCurves, 2, &PrimarySet.Blue);
+
+
+ if (BlackPoint) {
+ *BlackPoint = Black;
+ Div100(BlackPoint);
+ }
+
+ if (WhitePoint) {
+ *WhitePoint = White;
+ Div100(WhitePoint);
+ }
+
+
+ if (Primaries) {
+ *Primaries = PrimarySet;
+ }
+
+ cmsxPCollFreeMeasurements(&Linearized);
+
+ return true;
+}
+
+
+
diff --git a/src/libs/lprof/cmsmntr.cpp b/src/libs/lprof/cmsmntr.cpp
new file mode 100644
index 00000000..ed14ed50
--- /dev/null
+++ b/src/libs/lprof/cmsmntr.cpp
@@ -0,0 +1,371 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.09a */
+
+
+#include "lcmsprf.h"
+
+
+static
+void ClampRGB(LPVEC3 RGB)
+{
+ int i;
+
+ for (i=0; i < 3; i++) {
+
+ if (RGB->n[i] > 1.0)
+ RGB->n[i] = 1.0;
+ if (RGB->n[i] < 0)
+ RGB->n[i] = 0;
+ }
+}
+
+
+static
+int RegressionSamplerA2B(WORD In[], WORD Out[], LPVOID Cargo)
+{
+ cmsCIEXYZ xyz;
+ cmsCIELab Lab;
+ VEC3 RGB, RGBlinear, vxyz;
+ LPMONITORPROFILERDATA sys = (LPMONITORPROFILERDATA) Cargo;
+
+
+ RGB.n[0] = _cmsxSaturate65535To255(In[0]);
+ RGB.n[1] = _cmsxSaturate65535To255(In[1]);
+ RGB.n[2] = _cmsxSaturate65535To255(In[2]);
+
+ cmsxApplyLinearizationTable(RGB.n, sys->PreLab, RGBlinear.n);
+ cmsxApplyLinearizationTable(RGBlinear.n, sys->Prelinearization, RGBlinear.n);
+
+ RGBlinear.n[0] /= 255.;
+ RGBlinear.n[1] /= 255.;
+ RGBlinear.n[2] /= 255.;
+
+ MAT3eval(&vxyz, &sys->PrimariesMatrix, &RGBlinear);
+
+ xyz.X = vxyz.n[0];
+ xyz.Y = vxyz.n[1];
+ xyz.Z = vxyz.n[2];
+
+ cmsxChromaticAdaptationAndNormalization(&sys ->hdr, &xyz, false);
+
+
+ /* To PCS encoding */
+
+ cmsXYZ2Lab(NULL, &Lab, &xyz);
+ cmsFloat2LabEncoded(Out, &Lab);
+
+
+ return true; /* And done witch success */
+}
+
+
+
+
+static
+int RegressionSamplerB2A(WORD In[], WORD Out[], LPVOID Cargo)
+{
+ cmsCIELab Lab;
+ cmsCIEXYZ xyz;
+ VEC3 vxyz, RGB;
+ /* cmsJCh JCh; */
+ WORD Lin[3], Llab[3];
+ LPMONITORPROFILERDATA sys = (LPMONITORPROFILERDATA) Cargo;
+ double L;
+
+
+ /* Pass L back to 0..0xff00 domain */
+
+ L = (double) (In[0] * 65280.0) / 65535.0;
+ In[0] = (WORD) floor(L + .5);
+
+
+ /* To float values */
+ cmsLabEncoded2Float(&Lab, In);
+ cmsLab2XYZ(NULL, &xyz, &Lab);
+
+
+ cmsxChromaticAdaptationAndNormalization(&sys ->hdr, &xyz, true);
+ vxyz.n[0] = xyz.X;
+ vxyz.n[1] = xyz.Y;
+ vxyz.n[2] = xyz.Z;
+
+ MAT3eval(&RGB, &sys-> PrimariesMatrixRev, &vxyz);
+
+ /* Clamp RGB */
+ ClampRGB(&RGB);
+
+ /* Encode output */
+ Lin[0] = (WORD) ((double) RGB.n[0] * 65535. + .5);
+ Lin[1] = (WORD) ((double) RGB.n[1] * 65535. + .5);
+ Lin[2] = (WORD) ((double) RGB.n[2] * 65535. + .5);
+
+ cmsxApplyLinearizationGamma(Lin, sys ->ReverseTables, Llab);
+ cmsxApplyLinearizationGamma(Llab, sys ->PreLabRev, Out);
+
+
+ return true; /* And done witch success */
+}
+
+
+BOOL cmsxMonitorProfilerInit(LPMONITORPROFILERDATA sys)
+{
+
+
+ if (sys == NULL) return false;
+ ZeroMemory(sys, sizeof(MONITORPROFILERDATA));
+
+ sys->hdr.DeviceClass = icSigDisplayClass;
+ sys->hdr.ColorSpace = icSigRgbData;
+ sys->hdr.PCSType = PT_Lab;
+ sys->hdr.Medium = MEDIUM_TRANSMISSIVE;
+
+
+ /* Default values for generation */
+
+ sys -> hdr.lUseCIECAM97s = false;
+ sys -> hdr.CLUTPoints = 16;
+
+ /* Default viewing conditions */
+
+ sys -> hdr.device.Yb = 20;
+ sys -> hdr.device.La = 20;
+ sys -> hdr.device.surround = AVG_SURROUND;
+ sys -> hdr.device.D_value = 1; /* Complete adaptation */
+
+
+ /* Viewing conditions of PCS */
+ cmsxInitPCSViewingConditions(&sys ->hdr);
+
+ strcpy(sys -> hdr.Description, "unknown monitor");
+ strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set");
+ strcpy(sys -> hdr.Copyright, "No copyright, use freely");
+ strcpy(sys -> hdr.Model, "(unknown)");
+
+ sys -> hdr.ProfileVerbosityLevel = 0;
+
+ return true;
+}
+
+
+static
+void CreatePrimaryMatrices(LPMONITORPROFILERDATA sys)
+{
+ cmsCIExyY White;
+ MAT3 tmp;
+
+
+ cmsXYZ2xyY(&White, &sys->hdr.WhitePoint);
+ cmsBuildRGB2XYZtransferMatrix(&sys -> PrimariesMatrix, &White, &sys->hdr.Primaries);
+
+ CopyMemory(&tmp, &sys -> PrimariesMatrix, sizeof(MAT3));
+ MAT3inverse(&tmp, &sys->PrimariesMatrixRev);
+
+}
+
+
+static
+BOOL CreateLUTS(LPMONITORPROFILERDATA sys, LPLUT* A2B, LPLUT* B2A)
+{
+ LPLUT AToB0 = cmsAllocLUT();
+ LPLUT BToA0 = cmsAllocLUT();
+ LPGAMMATABLE LabG;
+ cmsCIExyY xyY;
+
+
+ cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3);
+ cmsAlloc3DGrid(BToA0, sys->hdr.CLUTPoints, 3, 3);
+
+ /* cmsAllocLinearTable(AToB0, sys -> Prelinearization, 1); */
+
+ sys->ReverseTables[0] = cmsReverseGamma(4096, sys ->Prelinearization[0]);
+ sys->ReverseTables[1] = cmsReverseGamma(4096, sys ->Prelinearization[1]);
+ sys->ReverseTables[2] = cmsReverseGamma(4096, sys ->Prelinearization[2]);
+
+ /* Prelinearization */
+
+ LabG = cmsBuildGamma(4096, 3.0);
+
+ sys -> PreLab[0] = cmsJoinGammaEx(LabG, sys ->Prelinearization[0], 4096);
+ sys -> PreLab[1] = cmsJoinGammaEx(LabG, sys ->Prelinearization[1], 4096);
+ sys -> PreLab[2] = cmsJoinGammaEx(LabG, sys ->Prelinearization[2], 4096);
+
+ sys -> PreLabRev[0] = cmsJoinGammaEx(sys ->Prelinearization[0], LabG, 4096);
+ sys -> PreLabRev[1] = cmsJoinGammaEx(sys ->Prelinearization[1], LabG, 4096);
+ sys -> PreLabRev[2] = cmsJoinGammaEx(sys ->Prelinearization[2], LabG, 4096);
+
+
+ cmsFreeGamma(LabG);
+
+
+ cmsAllocLinearTable(AToB0, sys->PreLabRev, 1);
+ cmsAllocLinearTable(BToA0, sys->PreLab, 2);
+
+
+ /* Set CIECAM97s parameters */
+
+ sys -> hdr.device.whitePoint.X = sys -> hdr.WhitePoint.X * 100.;
+ sys -> hdr.device.whitePoint.Y = sys -> hdr.WhitePoint.Y * 100.;
+ sys -> hdr.device.whitePoint.Z = sys -> hdr.WhitePoint.Z * 100.;
+
+
+ /* Normalize White point for CIECAM97s model */
+ cmsXYZ2xyY(&xyY, &sys -> hdr.device.whitePoint);
+ xyY.Y = 100.;
+ cmsxyY2XYZ(&sys -> hdr.device.whitePoint, &xyY);
+
+
+ sys->hdr.hDevice = cmsCIECAM97sInit(&sys->hdr.device);
+ sys->hdr.hPCS = cmsCIECAM97sInit(&sys->hdr.PCS);
+
+
+ cmsSample3DGrid(AToB0, RegressionSamplerA2B, sys, 0);
+ cmsSample3DGrid(BToA0, RegressionSamplerB2A, sys, 0);
+
+ cmsCIECAM97sDone(sys->hdr.hDevice);
+ cmsCIECAM97sDone(sys->hdr.hPCS);
+
+ cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0);
+ cmsAddTag(sys->hdr.hProfile, icSigBToA0Tag, BToA0);
+
+ /* This is the 0xff00 trick to map white at lattice point */
+ BToA0 ->Matrix.v[0].n[0] = DOUBLE_TO_FIXED((65535.0 / 65280.0));
+
+ *A2B = AToB0;
+ *B2A = BToA0;
+
+ cmsFreeGammaTriple(sys->ReverseTables);
+ cmsFreeGammaTriple(sys->PreLab);
+ cmsFreeGammaTriple(sys->PreLabRev);
+ return true;
+}
+
+
+
+BOOL cmsxMonitorProfilerDo(LPMONITORPROFILERDATA sys)
+{
+
+ cmsCIExyY White;
+ LPLUT AToB0, BToA0;
+
+ AToB0 = BToA0 = NULL;
+
+ if (!*sys -> hdr.OutputProfileFile)
+ return false;
+
+
+ if (sys->hdr.ReferenceSheet[0] || sys->hdr.MeasurementSheet[0]) {
+
+ if (sys->hdr.printf) {
+
+ sys->hdr.printf("Loading sheets...");
+
+ if (sys->hdr.ReferenceSheet[0])
+ sys->hdr.printf("Reference sheet: %s", sys->hdr.ReferenceSheet);
+ if (sys->hdr.MeasurementSheet[0])
+ sys->hdr.printf("Measurement sheet: %s", sys->hdr.MeasurementSheet);
+ }
+
+
+ if (!cmsxComputeMatrixShaper(sys -> hdr.ReferenceSheet,
+ sys -> hdr.MeasurementSheet,
+ MEDIUM_TRANSMISSIVE,
+ sys -> Prelinearization,
+ &sys -> hdr.WhitePoint,
+ &sys -> hdr.BlackPoint,
+ &sys -> hdr.Primaries)) return false;
+
+ if (sys->hdr.printf) {
+
+ char Buffer[1024];
+ _cmsIdentifyWhitePoint(Buffer, &sys ->hdr.WhitePoint);
+ sys->hdr.printf("%s", Buffer);
+
+ sys->hdr.printf("Primaries: R:%1.2g, %1.2g G:%1.2g, %1.2g B:%1.2g, %1.2g",
+ sys->hdr.Primaries.Red.x,sys->hdr.Primaries.Red.y,
+ sys->hdr.Primaries.Green.x, sys->hdr.Primaries.Green.y,
+ sys->hdr.Primaries.Blue.x, sys->hdr.Primaries.Blue.y);
+ }
+
+ }
+
+
+ CreatePrimaryMatrices(sys);
+
+
+ cmsXYZ2xyY(&White, &sys->hdr.WhitePoint);
+
+ sys->hdr.hProfile = cmsCreateRGBProfile(&White,
+ &sys-> hdr.Primaries,
+ sys -> Prelinearization);
+
+ cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass);
+
+ if (sys -> hdr.lUseCIECAM97s)
+ sys->hdr.PCSType = PT_Lab;
+ else
+ sys->hdr.PCSType = PT_XYZ;
+
+ cmsSetPCS(sys->hdr.hProfile, _cmsICCcolorSpace(sys->hdr.PCSType));
+
+ if (sys -> hdr.lUseCIECAM97s)
+ CreateLUTS(sys, &AToB0, &BToA0);
+
+
+ cmsxEmbedTextualInfo(&sys ->hdr);
+
+ cmsAddTag(sys->hdr.hProfile, icSigMediaWhitePointTag, &sys->hdr.WhitePoint);
+ cmsAddTag(sys->hdr.hProfile, icSigMediaBlackPointTag, &sys->hdr.BlackPoint);
+
+
+ if (sys->hdr.ProfileVerbosityLevel >= 2) {
+
+ cmsxEmbedCharTarget(&sys ->hdr);
+ }
+
+
+ _cmsSaveProfile(sys->hdr.hProfile, sys->hdr.OutputProfileFile);
+ cmsCloseProfile(sys->hdr.hProfile);
+ sys->hdr.hProfile = NULL;
+
+
+ if (AToB0) cmsFreeLUT(AToB0);
+ if (BToA0) cmsFreeLUT(BToA0);
+
+ if (sys ->Prelinearization[0])
+ cmsFreeGammaTriple(sys -> Prelinearization);
+
+ return true;
+}
diff --git a/src/libs/lprof/cmsoutl.cpp b/src/libs/lprof/cmsoutl.cpp
new file mode 100644
index 00000000..248aaa04
--- /dev/null
+++ b/src/libs/lprof/cmsoutl.cpp
@@ -0,0 +1,284 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.09a */
+/* */
+/* Incremental Interpolator */
+
+#include "lcmsprf.h"
+
+
+/* Res points to a result in XYZ or Lab */
+
+BOOL cdecl cmsxRegressionInterpolatorRGB(LPMEASUREMENT m,
+ int ColorSpace,
+ int RegressionTerms,
+ BOOL lUseLocalPatches,
+ int MinPatchesToCollect,
+ double r, double g, double b,
+ void* Res);
+
+
+/* -------------------------------------------------------------- Implementation */
+
+/* #define DEBUG 1 */
+
+/* Estimate regression matrix */
+static
+void EstimateRegression(LPMEASUREMENT m, double r, double g, double b,
+ int ColorSpace,
+ LPMATN* ptfm,
+ int nterms,
+ BOOL lIncludeAllPatches,
+ int MinPatchesToCollect)
+{
+ int nCollected;
+ MLRSTATISTICS maxAns;
+ int ToCollect;
+ SETOFPATCHES collected = cmsxPCollBuildSet(m, false);
+ SETOFPATCHES allowed = cmsxPCollBuildSet(m, true);
+ BOOL rc;
+ BOOL lPatchesExhausted = false;
+
+
+ CopyMemory(allowed, m -> Allowed, m->nPatches*sizeof(BOOL));
+
+ *ptfm = NULL;
+
+ ToCollect = max(MinPatchesToCollect, (nterms + 1));
+
+ do {
+
+ if (lIncludeAllPatches) {
+
+ CopyMemory(collected, allowed, m->nPatches*sizeof(BOOL));
+ lPatchesExhausted = true;
+ ToCollect = nCollected = m->nPatches;
+ }
+ else
+ {
+
+ nCollected = cmsxPCollPatchesNearRGB(m, m -> Allowed,
+ r, g, b,
+ ToCollect, collected);
+
+ if (nCollected < ToCollect) { /* No more patches available */
+ lPatchesExhausted = true;
+ }
+ else {
+ ToCollect = nCollected + 1; /* Start from here in next iteration */
+ }
+ }
+
+ /* We are going always 3 -> 3 for now.... */
+ rc = cmsxRegressionCreateMatrix(m, collected, nterms, ColorSpace, ptfm, &maxAns);
+
+
+ /* Does fit? */
+ if ((rc == false) || maxAns.R2adj < 0.95 || maxAns.R2adj > 1.0) {
+
+ maxAns.R2adj = -100; /* No, repeat */
+ }
+
+
+ } while (!lPatchesExhausted && maxAns.R2adj < 0.95);
+
+#ifdef DEBUG
+ printf("R2adj: %g, F: %g\n", maxAns.R2adj, maxAns.F);
+#endif
+
+ free(collected);
+ free(allowed);
+}
+
+
+
+BOOL cmsxRegressionInterpolatorRGB(LPMEASUREMENT m,
+ int ColorSpace,
+ int RegressionTerms,
+ BOOL lUseLocalPatches,
+ int MinPatchesToCollect,
+ double r, double g, double b,
+ void* Res)
+{
+ LPMATN tfm = NULL;
+
+
+ EstimateRegression(m, r, g, b, ColorSpace, &tfm, RegressionTerms,
+ !lUseLocalPatches, MinPatchesToCollect);
+
+ if (tfm == NULL) return false;
+
+ switch (ColorSpace) {
+
+ case PT_Lab:
+
+ if (!cmsxRegressionRGB2Lab(r, g, b, tfm, (LPcmsCIELab) Res)) return false;
+ break;
+
+ case PT_XYZ:
+ if (!cmsxRegressionRGB2XYZ(r, g, b, tfm, (LPcmsCIEXYZ) Res)) return false;
+ break;
+
+ default:
+ return false;
+ }
+
+ MATNfree(tfm);
+
+
+#ifdef DEBUG
+ printf("INTERPOLATED RGB %g,%g,%g Lab %g, %g, %g \n", r , g, b,
+ Lab->L, Lab->a, Lab->b);
+
+#endif
+ return true;
+}
+
+
+/* Check the results of a given regression matrix */
+
+static
+void CheckOneRegressionMatrix(LPPROFILERCOMMONDATA hdr, LPMATN Matrix,
+ double* Mean, double* Std, double* Max)
+{
+
+ cmsCIELab Lab;
+ cmsCIEXYZ XYZ;
+ double Hit, sum, sum2, n, dE;
+ int i;
+ cmsCIEXYZ D50;
+
+
+ D50.X = cmsD50_XYZ() -> X* 100.;
+ D50.Y = cmsD50_XYZ() -> Y* 100.;
+ D50.Z = cmsD50_XYZ() -> Z* 100.;
+
+ Hit = sum = sum2 = n = 0;
+ for (i=0; i < hdr -> m.nPatches; i++) {
+
+ if (hdr -> m.Allowed[i]) {
+
+ LPPATCH p = hdr -> m.Patches + i;
+
+ if (hdr -> PCSType == PT_Lab) {
+
+ WORD ProfileLabEncoded[3];
+
+ cmsxRegressionRGB2Lab(p -> Colorant.RGB[0],
+ p -> Colorant.RGB[1],
+ p -> Colorant.RGB[2],
+ Matrix, &Lab);
+
+ cmsFloat2LabEncoded(ProfileLabEncoded, &Lab);
+ cmsLabEncoded2Float(&Lab, ProfileLabEncoded);
+
+ dE = cmsDeltaE(&Lab, &p ->Lab);
+ }
+ else {
+ cmsCIELab Lab2;
+
+ cmsxRegressionRGB2XYZ(p -> Colorant.RGB[0],
+ p -> Colorant.RGB[1],
+ p -> Colorant.RGB[2],
+ Matrix, &XYZ);
+ _cmsxClampXYZ100(&XYZ);
+
+ cmsXYZ2Lab(&D50, &Lab, &XYZ);
+ cmsXYZ2Lab(&D50, &Lab2, &p ->XYZ);
+
+ dE = cmsDeltaE(&Lab, &Lab2);
+ }
+
+
+ if (dE > Hit)
+ Hit = dE;
+
+ sum += dE;
+ sum2 += dE * dE;
+ n = n + 1;
+
+ }
+ }
+
+ *Mean = sum / n;
+ *Std = sqrt((n * sum2 - sum * sum) / (n*(n-1)));
+ *Max = Hit;
+
+}
+
+
+/* Trial-and-error in order to get best number of terms. */
+
+int cmsxFindOptimumNumOfTerms(LPPROFILERCOMMONDATA hdr, int nMaxTerms, BOOL* lAllOk)
+{
+ int i, BestTerms;
+ BOOL rc;
+ LPMATN Matrix = NULL;
+ MLRSTATISTICS Stat;
+ double dEmean, dEStd, dEHit, Best;
+ BOOL lOneFound;
+
+
+ BestTerms = 4;
+ Best = 1000.;
+ lOneFound = false;
+
+ for (i=4; i <= nMaxTerms; i++) { /* 55 */
+
+ rc = cmsxRegressionCreateMatrix(&hdr -> m, hdr -> m.Allowed,
+ i, hdr -> PCSType, &Matrix, &Stat);
+
+ if (rc && Stat.R2adj < 1 && Stat.R2adj > 0.6) {
+
+ CheckOneRegressionMatrix(hdr, Matrix, &dEmean, &dEStd, &dEHit);
+
+ if (dEStd < Best && dEHit < 50.) {
+
+ Best = dEStd;
+ BestTerms = i;
+ lOneFound = true;
+ }
+
+ }
+ MATNfree(Matrix);
+ Matrix = NULL;
+ }
+
+ *lAllOk = lOneFound;
+
+ return BestTerms;
+}
+
+
diff --git a/src/libs/lprof/cmspcoll.cpp b/src/libs/lprof/cmspcoll.cpp
new file mode 100644
index 00000000..0b03fd5a
--- /dev/null
+++ b/src/libs/lprof/cmspcoll.cpp
@@ -0,0 +1,1045 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.08a */
+
+
+#include "lcmsprf.h"
+
+
+/* ----------------------------------------------------------------- Patch collections */
+
+BOOL cdecl cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet);
+
+BOOL cdecl cmsxPCollBuildMeasurement(LPMEASUREMENT m,
+ const char *ReferenceSheet,
+ const char *MeasurementSheet,
+ DWORD dwNeededSamplesType);
+
+LPPATCH cdecl cmsxPCollGetPatch(LPMEASUREMENT m, int n);
+
+LPPATCH cdecl cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* Name, int* lpPos);
+LPPATCH cdecl cmsxPCollGetPatchByPos(LPMEASUREMENT m, int row, int col);
+LPPATCH cdecl cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name,
+ double r, double g, double b,
+ LPcmsCIEXYZ XYZ, LPcmsCIELab Lab);
+
+/* Sets of patches */
+
+SETOFPATCHES cdecl cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault);
+int cdecl cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set);
+BOOL cdecl cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags);
+
+
+/* Collect "need" patches of the specific kind, return the number of collected (that */
+/* could be less if set of patches is exhausted) */
+
+
+void cdecl cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double r, double g, double b, int need, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids,
+ int need, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesNearPrimary(LPMEASUREMENT m, SETOFPATCHES Valids,
+ int nChannel, int need, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double Lmin, double LMax, double a, double b, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids,
+ LPLUT Gamut, SETOFPATCHES Result);
+
+LPPATCH cdecl cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
+LPPATCH cdecl cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
+LPPATCH cdecl cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* Distance);
+
+
+/* ------------------------------------------------------------- Implementation */
+
+#define IS(x) EqualsTo(c, x)
+
+/* A wrapper on stricmp() */
+
+static
+BOOL EqualsTo(const char* a, const char *b)
+{
+ return (stricmp(a, b) == 0);
+}
+
+
+/* Does return a bitwise mask holding the measurements contained in a Sheet */
+
+static
+DWORD MaskOfDataSet(LCMSHANDLE hSheet)
+{
+ char** Names;
+ int i, n;
+ DWORD dwMask = 0;
+
+ n = cmsxIT8EnumDataFormat(hSheet, &Names);
+
+ for (i=0; i < n; i++) {
+
+ char *c = Names[i];
+
+ if (IS("RGB_R") || IS("RGB_G") || IS("RGB_B"))
+ dwMask |= PATCH_HAS_RGB;
+ else
+ if (IS("XYZ_X") || IS("XYZ_Y") || IS("XYZ_Z"))
+ dwMask |= PATCH_HAS_XYZ;
+ else
+ if (IS("LAB_L") || IS("LAB_A") ||IS("LAB_B"))
+ dwMask |= PATCH_HAS_Lab;
+ else
+ if (IS("STDEV_DE"))
+ dwMask |= PATCH_HAS_STD_DE;
+ }
+
+ return dwMask;
+}
+
+
+/* Addition of a patch programatically */
+
+LPPATCH cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name,
+ double r, double g, double b,
+ LPcmsCIEXYZ XYZ, LPcmsCIELab Lab)
+{
+ LPPATCH p;
+
+ p = m->Patches + m->nPatches++;
+
+ strcpy(p -> Name, Name);
+
+ p -> Colorant.RGB[0] = r;
+ p -> Colorant.RGB[1] = g;
+ p -> Colorant.RGB[2] = b;
+ p -> dwFlags = PATCH_HAS_RGB;
+
+ if (XYZ) {
+
+ p -> XYZ = *XYZ;
+ p -> dwFlags |= PATCH_HAS_XYZ;
+ }
+
+ if (Lab) {
+ p -> Lab = *Lab;
+ p -> dwFlags |= PATCH_HAS_Lab;
+ }
+
+
+ return p;
+}
+
+/* Some vendors does store colorant data in a non-standard way, */
+/* i.e, from 0.0..1.0 or from 0.0..100.0 This routine tries to */
+/* detect such situations */
+
+static
+void NormalizeColorant(LPMEASUREMENT m)
+{
+ int i, j;
+ double MaxColorant=0;
+ double Normalize;
+
+
+ for (i=0; i < m -> nPatches; i++) {
+
+
+ LPPATCH p = m -> Patches + i;
+
+ for (j=0; j < MAXCHANNELS; j++) {
+ if (p ->Colorant.Hexa[j] > MaxColorant)
+ MaxColorant = p ->Colorant.Hexa[j];
+ }
+ }
+
+ /* Ok, some heuristics */
+
+ if (MaxColorant < 2)
+ Normalize = 255.0; /* goes 0..1 */
+ else
+ if (MaxColorant < 102)
+ Normalize = 2.55; /* goes 0..100 */
+ else
+ if (MaxColorant > 300)
+ Normalize = (255.0 / 65535.0); /* Goes 0..65535.0 */
+ else
+ return; /* Is ok */
+
+
+ /* Rearrange patches */
+ for (i=0; i < m -> nPatches; i++) {
+
+
+ LPPATCH p = m -> Patches + i;
+ for (j=0; j < MAXCHANNELS; j++)
+ p ->Colorant.Hexa[j] *= Normalize;
+ }
+
+}
+
+
+/* Load a collection from a Sheet */
+
+BOOL cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet)
+{
+ int i;
+ DWORD dwMask;
+
+
+ if (m -> nPatches == 0) {
+
+ m -> nPatches = (int) cmsxIT8GetPropertyDbl(hSheet, "NUMBER_OF_SETS");
+ m -> Patches = (PATCH*)calloc(m -> nPatches, sizeof(PATCH)); // C->C++ : cast
+
+ if (m -> Patches == NULL) {
+ cmsxIT8Free(hSheet);
+ return false;
+ }
+
+ for (i=0; i < m -> nPatches; i++) {
+
+ LPPATCH p = m -> Patches + i;
+ p -> dwFlags = 0;
+ cmsxIT8GetPatchName(hSheet, i, p ->Name);
+
+ }
+
+ }
+
+
+ /* Build mask according to data format */
+
+ dwMask = MaskOfDataSet(hSheet);
+
+
+ /* Read items. Set flags accordly. */
+ for (i = 0; i < m->nPatches; i++) {
+
+ LPPATCH Patch = m -> Patches + i;
+
+ /* Fill in data according to mask */
+
+ if (dwMask & PATCH_HAS_Lab) {
+
+ if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_L", &Patch -> Lab.L) &&
+ cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_A", &Patch -> Lab.a) &&
+ cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_B", &Patch -> Lab.b))
+
+ Patch -> dwFlags |= PATCH_HAS_Lab;
+ }
+
+ if (dwMask & PATCH_HAS_XYZ) {
+
+ if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_X", &Patch -> XYZ.X) &&
+ cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Y", &Patch -> XYZ.Y) &&
+ cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Z", &Patch -> XYZ.Z))
+
+ Patch -> dwFlags |= PATCH_HAS_XYZ;
+
+ }
+
+ if (dwMask & PATCH_HAS_RGB) {
+
+ if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_R", &Patch -> Colorant.RGB[0]) &&
+ cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_G", &Patch -> Colorant.RGB[1]) &&
+ cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_B", &Patch -> Colorant.RGB[2]))
+
+ Patch -> dwFlags |= PATCH_HAS_RGB;
+ }
+
+ if (dwMask & PATCH_HAS_STD_DE) {
+
+ if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "STDEV_DE", &Patch -> dEStd))
+
+ Patch -> dwFlags |= PATCH_HAS_STD_DE;
+
+ }
+
+ }
+
+ NormalizeColorant(m);
+ return true;
+}
+
+
+/* Does save parameters to a empty sheet */
+
+BOOL cmsxPCollSaveToSheet(LPMEASUREMENT m, LCMSHANDLE it8)
+{
+ int nNumberOfSets = cmsxPCollCountSet(m, m->Allowed);
+ int nNumberOfFields = 0;
+ DWORD dwMask = 0;
+ int i;
+
+ /* Find mask of fields */
+ for (i=0; i < m ->nPatches; i++) {
+ if (m ->Allowed[i]) {
+
+ LPPATCH p = m ->Patches + i;
+ dwMask |= p ->dwFlags;
+ }
+ }
+
+ nNumberOfFields = 1; /* SampleID */
+
+ if (dwMask & PATCH_HAS_RGB)
+ nNumberOfFields += 3;
+
+ if (dwMask & PATCH_HAS_XYZ)
+ nNumberOfFields += 3;
+
+ if (dwMask & PATCH_HAS_Lab)
+ nNumberOfFields += 3;
+
+
+ cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_SETS", nNumberOfSets);
+ cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_FIELDS", nNumberOfFields);
+
+ nNumberOfFields = 0;
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "SAMPLE_ID");
+
+ if (dwMask & PATCH_HAS_RGB) {
+
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_R");
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_G");
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_B");
+ }
+
+ if (dwMask & PATCH_HAS_XYZ) {
+
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_X");
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Y");
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Z");
+
+ }
+
+
+ if (dwMask & PATCH_HAS_XYZ) {
+
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_L");
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_A");
+ cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_B");
+
+ }
+
+ for (i=0; i < m ->nPatches; i++) {
+ if (m ->Allowed[i]) {
+
+ LPPATCH Patch = m ->Patches + i;
+
+ cmsxIT8SetDataSet(it8, Patch->Name, "SAMPLE_ID", Patch->Name);
+
+ if (dwMask & PATCH_HAS_RGB) {
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_R", Patch ->Colorant.RGB[0]);
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_G", Patch ->Colorant.RGB[1]);
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_B", Patch ->Colorant.RGB[2]);
+ }
+
+ if (dwMask & PATCH_HAS_XYZ) {
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_X", Patch ->XYZ.X);
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Y", Patch ->XYZ.Y);
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Z", Patch ->XYZ.Z);
+ }
+
+ if (dwMask & PATCH_HAS_Lab) {
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_L", Patch ->Lab.L);
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_A", Patch ->Lab.a);
+ cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_B", Patch ->Lab.b);
+
+ }
+ }
+ }
+
+ return true;
+}
+
+static
+void FixLabOnly(LPMEASUREMENT m)
+{
+ int i;
+
+ for (i=0; i < m ->nPatches; i++) {
+
+ LPPATCH p = m ->Patches + i;
+ if ((p ->dwFlags & PATCH_HAS_Lab) &&
+ !(p ->dwFlags & PATCH_HAS_XYZ))
+ {
+ cmsLab2XYZ(cmsD50_XYZ(), &p->XYZ, &p ->Lab);
+
+ p ->XYZ.X *= 100.;
+ p ->XYZ.Y *= 100.;
+ p ->XYZ.Z *= 100.;
+
+ p ->dwFlags |= PATCH_HAS_XYZ;
+ }
+
+ }
+
+}
+
+
+/* Higher level function. Does merge reference and measurement sheet into */
+/* a MEASUREMENT struct. Data to keep is described in dwNeededSamplesType */
+/* mask as follows: */
+/* */
+/* PATCH_HAS_Lab 0x00000001 */
+/* PATCH_HAS_XYZ 0x00000002 */
+/* PATCH_HAS_RGB 0x00000004 */
+/* PATCH_HAS_CMY 0x00000008 */
+/* PATCH_HAS_CMYK 0x00000010 */
+/* PATCH_HAS_HEXACRM 0x00000020 */
+/* PATCH_HAS_STD_Lab 0x00010000 */
+/* PATCH_HAS_STD_XYZ 0x00020000 */
+/* PATCH_HAS_STD_RGB 0x00040000 */
+/* PATCH_HAS_STD_CMY 0x00080000 */
+/* PATCH_HAS_STD_CMYK 0x00100000 */
+/* PATCH_HAS_STD_HEXACRM 0x00100000 */
+/* PATCH_HAS_MEAN_DE 0x01000000 */
+/* PATCH_HAS_STD_DE 0x02000000 */
+/* PATCH_HAS_CHISQ 0x04000000 */
+/* */
+/* See lprof.h for further info */
+
+
+BOOL cmsxPCollBuildMeasurement(LPMEASUREMENT m,
+ const char *ReferenceSheet,
+ const char *MeasurementSheet,
+ DWORD dwNeededSamplesType)
+{
+ LCMSHANDLE hSheet;
+ BOOL rc = true;
+
+ ZeroMemory(m, sizeof(MEASUREMENT));
+
+
+ if (ReferenceSheet != NULL && *ReferenceSheet) {
+
+ hSheet = cmsxIT8LoadFromFile(ReferenceSheet);
+ if (hSheet == NULL) return false;
+
+ rc = cmsxPCollLoadFromSheet(m, hSheet);
+ cmsxIT8Free(hSheet);
+ }
+
+ if (!rc) return false;
+
+ if (MeasurementSheet != NULL && *MeasurementSheet) {
+
+ hSheet = cmsxIT8LoadFromFile(MeasurementSheet);
+ if (hSheet == NULL) return false;
+
+ rc = cmsxPCollLoadFromSheet(m, hSheet);
+ cmsxIT8Free(hSheet);
+ }
+
+ if (!rc) return false;
+
+
+ /* Fix up -- If only Lab is present, then compute */
+ /* XYZ based on D50 */
+
+ FixLabOnly(m);
+
+ cmsxPCollValidatePatches(m, dwNeededSamplesType);
+ return true;
+}
+
+
+
+void cmsxPCollFreeMeasurements(LPMEASUREMENT m)
+{
+ if (m->Patches)
+ free(m->Patches);
+
+ m->Patches = NULL;
+ m->nPatches = 0;
+
+ if (m -> Allowed)
+ free(m -> Allowed);
+
+}
+
+/* Retrieval functions */
+
+LPPATCH cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* name, int* lpPos)
+{
+ int i;
+ for (i=0; i < m->nPatches; i++)
+ {
+ if (m -> Allowed)
+ if (!m -> Allowed[i])
+ continue;
+
+ if (EqualsTo(m->Patches[i].Name, name)) {
+ if (lpPos) *lpPos = i;
+ return m->Patches + i;
+ }
+ }
+
+ return NULL;
+}
+
+
+
+
+/* -------------------------------------------------------------------- Sets */
+
+
+SETOFPATCHES cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault)
+{
+ SETOFPATCHES Full = (SETOFPATCHES) malloc(m -> nPatches * sizeof(BOOL));
+ int i;
+
+ for (i=0; i < m -> nPatches; i++)
+ Full[i] = lDefault;
+
+ return Full;
+}
+
+int cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set)
+{
+ int i, Count = 0;
+
+ for (i = 0; i < m -> nPatches; i++) {
+
+ if (Set[i])
+ Count++;
+ }
+
+ return Count;
+}
+
+
+/* Validate patches */
+
+BOOL cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags)
+{
+ int i, n;
+
+ if (m->Allowed)
+ free(m->Allowed);
+
+ m -> Allowed = cmsxPCollBuildSet(m, true);
+
+ /* Check for flags */
+ for (i=n=0; i < m -> nPatches; i++) {
+
+ LPPATCH p = m -> Patches + i;
+ m -> Allowed[i] = ((p -> dwFlags & dwFlags) == dwFlags);
+
+ }
+
+ return true;
+}
+
+
+/* Several filters */
+
+
+/* This filter does validate patches placed on 'radius' distance of a */
+/* device-color space. Currently only RGB is supported */
+
+static
+void PatchesByRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double R, double G, double B, double radius, SETOFPATCHES Result)
+{
+ int i;
+ double ra, rmax = sqrt(radius / 255.);
+ double dR, dG, dB;
+ LPPATCH p;
+
+ for (i=0; i < m->nPatches; i++) {
+
+
+ if (Valids[i]) {
+
+ p = m->Patches + i;
+
+ dR = fabs(R - p -> Colorant.RGB[0]) / 255.;
+ dG = fabs(G - p -> Colorant.RGB[1]) / 255.;
+ dB = fabs(B - p -> Colorant.RGB[2]) / 255.;
+
+ ra = sqrt(dR*dR + dG*dG + dB*dB);
+
+ if (ra <= rmax)
+ Result[i] = true;
+ else
+ Result[i] = false;
+
+ }
+ }
+
+}
+
+
+/* This filter does validate patches placed at dEmax radius */
+/* in the device-independent side. */
+
+static
+void PatchesByLab(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double L, double a, double b, double dEmax, SETOFPATCHES Result)
+{
+ int i;
+ double dE, dEMaxSQR = sqrt(dEmax);
+ double dL, da, db;
+ LPPATCH p;
+
+
+ for (i=0; i < m->nPatches; i++) {
+
+
+ if (Valids[i]) {
+
+ p = m->Patches + i;
+
+ dL = fabs(L - p -> Lab.L);
+ da = fabs(a - p -> Lab.a);
+ db = fabs(b - p -> Lab.b);
+
+ dE = sqrt(dL*dL + da*da + db*db);
+
+ if (dE <= dEMaxSQR)
+ Result[i] = true;
+ else
+ Result[i] = false;
+ }
+ }
+}
+
+
+/* Restrict Lab in a cube of variable sides. Quick and dirty out-of-gamut */
+/* stripper used in estimations. */
+
+static
+void PatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double Lmin, double Lmax, double da, double db, SETOFPATCHES Result)
+{
+ int i;
+
+ for (i=0; i < m -> nPatches; i++) {
+
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ if ((p->Lab.L >= Lmin && p->Lab.L <= Lmax) &&
+ (fabs(p -> Lab.a) < da) &&
+ (fabs(p -> Lab.b) < db))
+
+ Result[i] = true;
+ else
+ Result[i] = false;
+ }
+ }
+
+}
+
+/* Restrict to low colorfullness */
+
+static
+void PatchesOfLowC(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double Cmax, SETOFPATCHES Result)
+{
+ int i;
+ cmsCIELCh LCh;
+
+ for (i=0; i < m -> nPatches; i++) {
+
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ cmsLab2LCh(&LCh, &p->Lab);
+
+
+ if (LCh.C < Cmax)
+ Result[i] = true;
+ else
+ Result[i] = false;
+ }
+ }
+
+}
+
+
+
+/* Primary can be -1 for specifying device gray. Does return patches */
+/* on device-space Colorants. dEMax is the maximum allowed ratio */
+
+static
+void PatchesPrimary(LPMEASUREMENT m, SETOFPATCHES Valids,
+ int nColorant, double dEMax, SETOFPATCHES Result)
+{
+ int i, j;
+ double n, dE;
+
+ for (i=0; i < m -> nPatches; i++) {
+
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+
+ if (nColorant < 0) /* device-grey? */
+ {
+ /* cross. */
+
+ double drg = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[1]) / 255.;
+ double drb = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[2]) / 255.;
+ double dbg = fabs(p -> Colorant.RGB[1] - p -> Colorant.RGB[2]) / 255.;
+
+ dE = (drg*drg + drb*drb + dbg*dbg);
+
+
+ }
+ else {
+ dE = 0.;
+ for (j=0; j < 3; j++) {
+
+ if (j != nColorant) {
+
+ n = p -> Colorant.RGB[j] / 255.;
+ dE += (n * n);
+
+
+ }
+ }
+ }
+
+
+
+ if (sqrt(dE) < dEMax)
+ Result[i] = true;
+ else
+ Result[i] = false;
+ }
+ }
+
+}
+
+
+/* The high level extractors ----------------------------------------------------- */
+
+int cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double r, double g, double b,
+ int need, SETOFPATCHES Result)
+{
+ double radius;
+ int nCollected;
+
+ /* Collect points inside of a sphere or radius 'radius' by RGB */
+
+ radius = 1;
+ do {
+ PatchesByRGB(m, Valids, r, g, b, radius, Result);
+
+ nCollected = cmsxPCollCountSet(m, Result);
+ if (nCollected <= need) {
+
+ radius += 1.0;
+ }
+
+ } while (nCollected <= need && radius < 256.);
+
+ return nCollected; /* Can be less than needed! */
+}
+
+
+int cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids,
+ int need, SETOFPATCHES Result)
+{
+ int nGrays;
+ double Cmax;
+
+ Cmax = 1.;
+ do {
+
+
+ PatchesOfLowC(m, Valids, Cmax, Result);
+
+ nGrays = cmsxPCollCountSet(m, Result);
+ if (nGrays <= need) {
+
+ Cmax += .2;
+ }
+
+ } while (nGrays <= need && Cmax < 10.);
+
+ return nGrays;
+}
+
+
+int cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double Lmin, double Lmax, double a, double b,
+ SETOFPATCHES Result)
+
+
+{
+ PatchesInLabCube(m, Valids, Lmin, Lmax, a, b, Result);
+ return cmsxPCollCountSet(m, Result);
+}
+
+
+
+
+int cmsxPCollPatchesNearPrimary(LPMEASUREMENT m,
+ SETOFPATCHES Valids,
+ int nChannel,
+ int need,
+ SETOFPATCHES Result)
+{
+ double radius;
+ int nCollected;
+
+ /* Collect points inside of a sphere or radius 'radius' by RGB */
+
+ radius = 0.05;
+ do {
+ PatchesPrimary(m, Valids, nChannel, radius, Result);
+
+ nCollected = cmsxPCollCountSet(m, Result);
+ if (nCollected <= need) {
+
+ radius += 0.01;
+ }
+
+ } while (nCollected <= need && radius < 256.);
+
+ return nCollected;
+
+}
+
+
+static
+void AddOneGray(LPMEASUREMENT m, int n, SETOFPATCHES Grays)
+{
+ LPPATCH p;
+ char Buffer[cmsxIT8_GRAYCOLS];
+ int pos;
+
+ if (n == 0) strcpy(Buffer, "DMIN");
+ else
+ if (n == cmsxIT8_GRAYCOLS - 1) strcpy(Buffer, "DMAX");
+ else
+ sprintf(Buffer, "GS%d", n);
+
+ p = cmsxPCollGetPatchByName(m, Buffer, &pos);
+
+ if (p)
+ Grays[pos] = true;
+}
+
+
+
+void cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result)
+{
+
+ int i;
+
+ for (i=0; i < cmsxIT8_GRAYCOLS; i++)
+ AddOneGray(m, i, Result);
+}
+
+
+
+/* Refresh RGB of all patches after prelinearization */
+
+void cmsxPCollLinearizePatches(LPMEASUREMENT m, SETOFPATCHES Valids, LPGAMMATABLE Gamma[3])
+{
+ int i;
+
+ for (i=0; i < m -> nPatches; i++) {
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ cmsxApplyLinearizationTable(p -> Colorant.RGB, Gamma, p -> Colorant.RGB);
+ }
+ }
+
+}
+
+
+int cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids,
+ LPLUT Gamut, SETOFPATCHES Result)
+{
+ int i;
+ int nCollected = 0;
+
+ for (i=0; i < m -> nPatches; i++) {
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+ WORD EncodedLab[3];
+ WORD dE;
+
+ cmsFloat2LabEncoded(EncodedLab, &p->Lab);
+ cmsEvalLUT(Gamut, EncodedLab, &dE);
+ Result[i] = (dE < 2) ? true : false;
+ if (Result[i]) nCollected++;
+ }
+ }
+
+ return nCollected;
+}
+
+LPPATCH cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance)
+{
+ int i;
+ LPPATCH Candidate = NULL;
+ double Distance, CandidateDistance = 255;
+ double dR, dG, dB;
+
+ Candidate = cmsxPCollGetPatchByName(m, "DMIN", NULL);
+ if (Candidate) {
+
+ if (TheDistance) *TheDistance = 0.0;
+ return Candidate;
+ }
+
+ for (i=0; i < m -> nPatches; i++) {
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ dR = fabs(255.0 - p -> Colorant.RGB[0]) / 255.0;
+ dG = fabs(255.0 - p -> Colorant.RGB[1]) / 255.0;
+ dB = fabs(255.0 - p -> Colorant.RGB[2]) / 255.0;
+
+ Distance = sqrt(dR*dR + dG*dG + dB*dB);
+
+ if (Distance < CandidateDistance) {
+ Candidate = p;
+ CandidateDistance = Distance;
+ }
+ }
+ }
+
+ if (TheDistance)
+ *TheDistance = floor(CandidateDistance * 255.0 + .5);
+
+ return Candidate;
+}
+
+LPPATCH cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance)
+{
+ int i;
+ LPPATCH Candidate = NULL;
+ double Distance, CandidateDistance = 255;
+ double dR, dG, dB;
+
+
+ Candidate = cmsxPCollGetPatchByName(m, "DMAX", NULL);
+ if (Candidate) {
+
+ if (TheDistance) *TheDistance = 0.0;
+ return Candidate;
+ }
+
+ for (i=0; i < m -> nPatches; i++) {
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ dR = (p -> Colorant.RGB[0]) / 255.0;
+ dG = (p -> Colorant.RGB[1]) / 255.0;
+ dB = (p -> Colorant.RGB[2]) / 255.0;
+
+ Distance = sqrt(dR*dR + dG*dG + dB*dB);
+
+ if (Distance < CandidateDistance) {
+ Candidate = p;
+ CandidateDistance = Distance;
+ }
+ }
+ }
+
+ if (TheDistance)
+ *TheDistance = floor(CandidateDistance * 255.0 + .5);
+
+ return Candidate;
+}
+
+
+LPPATCH cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* TheDistance)
+{
+ int i;
+ LPPATCH Candidate = NULL;
+ double Distance, CandidateDistance = 255;
+ double dR, dG, dB;
+ const struct {
+ double r, g, b;
+
+ } RGBPrimaries[3] = {
+ { 255.0, 0, 0},
+ { 0, 255.0, 0},
+ { 0, 0, 255 }};
+
+
+ for (i=0; i < m -> nPatches; i++) {
+
+ if (Valids[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ dR = fabs(RGBPrimaries[Channel].r - p -> Colorant.RGB[0]) / 255.0;
+ dG = fabs(RGBPrimaries[Channel].g - p -> Colorant.RGB[1]) / 255.0;
+ dB = fabs(RGBPrimaries[Channel].b - p -> Colorant.RGB[2]) / 255.0;
+
+ Distance = sqrt(dR*dR + dG*dG + dB*dB);
+
+ if (Distance < CandidateDistance) {
+ Candidate = p;
+ CandidateDistance = Distance;
+ }
+ }
+ }
+
+ if (TheDistance)
+ *TheDistance = floor(CandidateDistance * 255.0 + .5);
+
+ return Candidate;
+}
diff --git a/src/libs/lprof/cmsprf.cpp b/src/libs/lprof/cmsprf.cpp
new file mode 100644
index 00000000..527fc8e8
--- /dev/null
+++ b/src/libs/lprof/cmsprf.cpp
@@ -0,0 +1,439 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* */
+/* This library is free software; you can redistribute it and/or */
+/* modify it under the terms of the GNU Lesser 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 */
+/* Lesser General Public License for more details. */
+/* */
+/* You should have received a copy of the GNU Lesser General Public */
+/* License along with this library; if not, write to the Free Software */
+/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#include "lcmsprf.h"
+
+
+double cdecl _cmsxSaturate65535To255(double d);
+double cdecl _cmsxSaturate255To65535(double d);
+
+
+void cdecl _cmsxClampXYZ100(LPcmsCIEXYZ xyz);
+
+
+BOOL cdecl cmsxEmbedCharTarget(LPPROFILERCOMMONDATA hdr);
+BOOL cdecl cmsxEmbedMatrixShaper(LPPROFILERCOMMONDATA hdr);
+BOOL cdecl cmsxEmbedTextualInfo(LPPROFILERCOMMONDATA hdr);
+
+/* ----------------------------------------------------------------- Implementation */
+
+
+/* Convert from 0.0..65535.0 to 0.0..255.0 */
+
+double _cmsxSaturate65535To255(double d)
+{
+ double v;
+
+ v = d / 257.0;
+
+ if (v < 0) return 0;
+ if (v > 255.0) return 255.0;
+
+ return v;
+}
+
+
+double _cmsxSaturate255To65535(double d)
+{
+ double v;
+
+ v = d * 257.0;
+
+ if (v < 0) return 0;
+ if (v > 65535.0) return 65535.0;
+
+ return v;
+}
+
+
+
+/* Cut off absurd values */
+
+void _cmsxClampXYZ100(LPcmsCIEXYZ xyz)
+{
+
+ if (xyz->X > 199.996)
+ xyz->X = 199.996;
+
+ if (xyz->Y > 199.996)
+ xyz->Y = 199.996;
+
+ if (xyz->Z > 199.996)
+ xyz->Z = 199.996;
+
+ if (xyz->Y < 0)
+ xyz->Y = 0;
+
+ if (xyz->X < 0)
+ xyz->X = 0;
+
+ if (xyz->Z < 0)
+ xyz->Z = 0;
+
+}
+
+static
+int xfilelength(int fd)
+{
+#ifdef _MSC_VER
+ return _filelength(fd);
+#else
+ struct stat sb;
+ if (fstat(fd, &sb) < 0)
+ return(-1);
+ return(sb.st_size);
+#endif
+
+
+}
+
+
+BOOL cmsxEmbedCharTarget(LPPROFILERCOMMONDATA hdr)
+{
+ LCMSHANDLE it8 = cmsxIT8Alloc();
+ LPBYTE mem;
+ size_t size, readed;
+ FILE* f;
+ BOOL lFreeOnExit = false;
+
+
+ if (!hdr->m.Patches) {
+
+ if (!hdr ->ReferenceSheet[0] && !hdr->MeasurementSheet[0]) return false;
+
+ if (cmsxPCollBuildMeasurement(&hdr ->m,
+ hdr->ReferenceSheet,
+ hdr->MeasurementSheet,
+ PATCH_HAS_RGB|PATCH_HAS_XYZ) == false) return false;
+ lFreeOnExit = true;
+
+ }
+
+ cmsxIT8SetSheetType(it8,"LCMSEMBED");
+ cmsxIT8SetProperty(it8, "ORIGINATOR", (const char *) "Little cms");
+ cmsxIT8SetProperty(it8, "DESCRIPTOR", (const char *) hdr -> Description);
+ cmsxIT8SetProperty(it8, "MANUFACTURER", (const char *) hdr ->Manufacturer);
+
+ cmsxPCollSaveToSheet(&hdr->m, it8);
+ cmsxIT8SaveToFile(it8, "TMP00.IT8");
+ cmsxIT8Free(it8);
+
+ f = fopen("TMP00.IT8", "rb");
+ size = xfilelength(fileno(f));
+ mem = (unsigned char*) malloc(size + 1); // C->C++ : fixed cast
+ readed = fread(mem, 1, size, f);
+ fclose(f);
+
+ mem[readed] = 0;
+ unlink("TMP00.IT8");
+
+ cmsAddTag(hdr->hProfile, icSigCharTargetTag, mem);
+ free(mem);
+
+ if (lFreeOnExit) {
+
+ cmsxPCollFreeMeasurements(&hdr->m);
+ }
+
+ return true;
+}
+
+
+static
+BOOL ComputeColorantMatrix(LPcmsCIEXYZTRIPLE Colorants,
+ LPcmsCIExyY WhitePoint,
+ LPcmsCIExyYTRIPLE Primaries)
+{
+ MAT3 MColorants;
+
+ if (!cmsBuildRGB2XYZtransferMatrix(&MColorants, WhitePoint, Primaries))
+ {
+ return false;
+ }
+
+
+ cmsAdaptMatrixToD50(&MColorants, WhitePoint);
+
+ Colorants->Red.X = MColorants.v[0].n[0];
+ Colorants->Red.Y = MColorants.v[1].n[0];
+ Colorants->Red.Z = MColorants.v[2].n[0];
+
+ Colorants->Green.X = MColorants.v[0].n[1];
+ Colorants->Green.Y = MColorants.v[1].n[1];
+ Colorants->Green.Z = MColorants.v[2].n[1];
+
+ Colorants->Blue.X = MColorants.v[0].n[2];
+ Colorants->Blue.Y = MColorants.v[1].n[2];
+ Colorants->Blue.Z = MColorants.v[2].n[2];
+
+ return true;
+
+}
+
+
+BOOL cmsxEmbedMatrixShaper(LPPROFILERCOMMONDATA hdr)
+{
+ cmsCIEXYZTRIPLE Colorant;
+ cmsCIExyY MediaWhite;
+
+ cmsXYZ2xyY(&MediaWhite, &hdr ->WhitePoint);
+
+ if (ComputeColorantMatrix(&Colorant, &MediaWhite, &hdr ->Primaries)) {
+
+ cmsAddTag(hdr ->hProfile, icSigRedColorantTag, &Colorant.Red);
+ cmsAddTag(hdr ->hProfile, icSigGreenColorantTag, &Colorant.Green);
+ cmsAddTag(hdr ->hProfile, icSigBlueColorantTag, &Colorant.Blue);
+ }
+
+ cmsAddTag(hdr ->hProfile, icSigRedTRCTag, hdr ->Gamma[0]);
+ cmsAddTag(hdr ->hProfile, icSigGreenTRCTag, hdr ->Gamma[1]);
+ cmsAddTag(hdr ->hProfile, icSigBlueTRCTag, hdr ->Gamma[2]);
+
+ return true;
+}
+
+
+BOOL cmsxEmbedTextualInfo(LPPROFILERCOMMONDATA hdr)
+{
+ if (*hdr ->Description)
+ cmsAddTag(hdr ->hProfile, icSigProfileDescriptionTag, hdr ->Description);
+
+ if (*hdr ->Copyright)
+ cmsAddTag(hdr ->hProfile, icSigCopyrightTag, hdr ->Copyright);
+
+ if (*hdr ->Manufacturer)
+ cmsAddTag(hdr ->hProfile, icSigDeviceMfgDescTag, hdr ->Manufacturer);
+
+ if (*hdr ->Model)
+ cmsAddTag(hdr ->hProfile, icSigDeviceModelDescTag, hdr ->Model);
+
+ return true;
+}
+
+
+
+void cmsxChromaticAdaptationAndNormalization(LPPROFILERCOMMONDATA hdr, LPcmsCIEXYZ xyz, BOOL lReverse)
+{
+
+ if (hdr->lUseCIECAM97s) {
+
+ cmsJCh JCh;
+
+ /* Let's CIECAM97s to do the adaptation to D50 */
+
+ xyz->X *= 100.;
+ xyz->Y *= 100.;
+ xyz->Z *= 100.;
+
+ _cmsxClampXYZ100(xyz);
+
+ if (lReverse) {
+ cmsCIECAM97sForward(hdr->hPCS, xyz, &JCh);
+ cmsCIECAM97sReverse(hdr->hDevice, &JCh, xyz);
+ }
+ else {
+
+ cmsCIECAM97sForward(hdr->hDevice, xyz, &JCh);
+ cmsCIECAM97sReverse(hdr->hPCS, &JCh, xyz);
+ }
+
+ _cmsxClampXYZ100(xyz);
+
+ xyz -> X /= 100.;
+ xyz -> Y /= 100.;
+ xyz -> Z /= 100.;
+
+ }
+ else {
+
+ /* Else, use Bradford */
+
+ if (lReverse) {
+ cmsAdaptToIlluminant(xyz, cmsD50_XYZ(), &hdr->WhitePoint, xyz);
+ }
+ else {
+ cmsAdaptToIlluminant(xyz, &hdr->WhitePoint, cmsD50_XYZ(), xyz);
+ }
+
+ }
+
+}
+
+
+void cmsxInitPCSViewingConditions(LPPROFILERCOMMONDATA hdr)
+{
+
+ hdr->PCS.whitePoint.X = cmsD50_XYZ()->X * 100.;
+ hdr->PCS.whitePoint.Y = cmsD50_XYZ()->Y * 100.;
+ hdr->PCS.whitePoint.Z = cmsD50_XYZ()->Z * 100.;
+
+
+ hdr->PCS.Yb = 20; /* 20% of surround */
+ hdr->PCS.La = 20; /* Adapting field luminance */
+ hdr->PCS.surround = AVG_SURROUND;
+ hdr->PCS.D_value = 1.0; /* Complete adaptation */
+
+}
+
+
+/* Build gamut hull by geometric means */
+void cmsxComputeGamutHull(LPPROFILERCOMMONDATA hdr)
+{
+ int i;
+ int x0, y0, z0;
+ int Inside, Outside, Boundaries;
+ char code;
+
+
+ hdr -> hRGBHull = cmsxHullInit();
+
+ /* For all valid patches, mark RGB knots as 0 */
+ for (i=0; i < hdr ->m.nPatches; i++) {
+
+ if (hdr ->m.Allowed[i]) {
+
+ LPPATCH p = hdr ->m.Patches + i;
+
+
+ x0 = (int) floor(p->Colorant.RGB[0] + .5);
+ y0 = (int) floor(p->Colorant.RGB[1] + .5);
+ z0 = (int) floor(p->Colorant.RGB[2] + .5);
+
+ cmsxHullAddPoint(hdr->hRGBHull, x0, y0, z0);
+ }
+ }
+
+ cmsxHullComputeHull(hdr ->hRGBHull);
+
+/* #ifdef DEBUG */
+ cmsxHullDumpVRML(hdr -> hRGBHull, "rgbhull.wrl");
+/* #endif */
+
+
+
+ /* A check */
+
+ Inside = Outside = Boundaries = 0;
+ /* For all valid patches, mark RGB knots as 0 */
+ for (i=0; i < hdr ->m.nPatches; i++) {
+
+ if (hdr ->m.Allowed[i]) {
+
+ LPPATCH p = hdr ->m.Patches + i;
+
+ x0 = (int) floor(p->Colorant.RGB[0] + .5);
+ y0 = (int) floor(p->Colorant.RGB[1] + .5);
+ z0 = (int) floor(p->Colorant.RGB[2] + .5);
+
+ code = cmsxHullCheckpoint(hdr -> hRGBHull, x0, y0, z0);
+
+ switch (code) {
+
+ case 'i': Inside++; break;
+ case 'o': Outside++; break;
+ default: Boundaries++;
+ }
+
+ }
+ }
+
+ if (hdr ->printf)
+ hdr ->printf("Gamut hull: %d inside, %d outside, %d on boundaries", Inside, Outside, Boundaries);
+
+}
+
+BOOL cmsxChoosePCS(LPPROFILERCOMMONDATA hdr)
+{
+
+ double gamma_r, gamma_g, gamma_b;
+ cmsCIExyY SourceWhite;
+
+ /* At first, compute aproximation on matrix-shaper */
+ if (!cmsxComputeMatrixShaper(hdr ->ReferenceSheet,
+ hdr ->MeasurementSheet,
+ hdr -> Medium,
+ hdr ->Gamma,
+ &hdr ->WhitePoint,
+ &hdr ->BlackPoint,
+ &hdr ->Primaries)) return false;
+
+
+
+ cmsXYZ2xyY(&SourceWhite, &hdr ->WhitePoint);
+
+ gamma_r = cmsEstimateGamma(hdr ->Gamma[0]);
+ gamma_g = cmsEstimateGamma(hdr ->Gamma[1]);
+ gamma_b = cmsEstimateGamma(hdr ->Gamma[2]);
+
+
+
+ if (gamma_r > 1.8 || gamma_g > 1.8 || gamma_b > 1.8 ||
+ gamma_r == -1 || gamma_g == -1 || gamma_b == -1) {
+
+ hdr ->PCSType = PT_Lab;
+
+ if (hdr ->printf)
+ hdr ->printf("I have chosen Lab as PCS");
+
+ }
+ else {
+
+ hdr ->PCSType = PT_XYZ;
+
+ if (hdr ->printf)
+ hdr ->printf("I have chosen XYZ as PCS");
+ }
+
+
+
+ if (hdr ->printf) {
+
+ char Buffer[256] = "Infered ";
+
+ _cmsIdentifyWhitePoint(Buffer, &hdr ->WhitePoint);
+ hdr ->printf("%s", Buffer);
+ hdr ->printf("Primaries (x-y): [Red: %2.2f, %2.2f] [Green: %2.2f, %2.2f] [Blue: %2.2f, %2.2f]",
+ hdr ->Primaries.Red.x, hdr ->Primaries.Red.y,
+ hdr ->Primaries.Green.x, hdr ->Primaries.Green.y,
+ hdr ->Primaries.Blue.x, hdr ->Primaries.Blue.y);
+
+ if ((gamma_r != -1) && (gamma_g != -1) && (gamma_b != -1)) {
+
+ hdr ->printf("Estimated gamma: [Red: %2.2f] [Green: %2.2f] [Blue: %2.2f]",
+ gamma_r, gamma_g, gamma_b);
+ }
+
+
+ }
+
+
+
+ return true;
+}
diff --git a/src/libs/lprof/cmsreg.cpp b/src/libs/lprof/cmsreg.cpp
new file mode 100644
index 00000000..4e66a9ef
--- /dev/null
+++ b/src/libs/lprof/cmsreg.cpp
@@ -0,0 +1,558 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.09a */
+
+
+#include "lcmsprf.h"
+
+
+/* There are three kinds of lies: */
+/* */
+/* * lies */
+/* * damn lies */
+/* * statistics */
+/* */
+/* -Some Wag */
+/* */
+/* */
+/* This module handles multiple linear regression stuff */
+
+
+
+/* A measurement of error
+
+typedef struct {
+
+ double SSE; // The error sum of squares
+ double MSE; // The error mean sum of squares
+ double SSR; // The regression sum of squares
+ double MSR; // The regression mean sum of squares
+ double SSTO; // Total sum of squares
+ double F; // The Fisher-F value (MSR / MSE)
+ double R2; // Proportion of variability explained by the regression
+ // (root is Pearson correlation coefficient)
+
+ double R2adj; // The adjusted coefficient of multiple determination.
+ // R2-adjusted or R2adj. This is calculated as
+ // R2adj = 1 - (1-R2)(N-n-1)/(N-1)
+ // and used as multiple correlation coefficient
+ // (really, it should be square root)
+
+ } MLRSTATISTICS, FAR* LPMLRSTATISTICS;
+
+*/
+
+
+int cdecl cmsxRegressionCreateMatrix(LPMEASUREMENT m, SETOFPATCHES Allowed, int nterms,
+ int ColorSpace,
+ LPMATN* lpMat, LPMLRSTATISTICS Stat);
+
+BOOL cdecl cmsxRegressionRGB2Lab(double r, double g, double b,
+ LPMATN tfm, LPcmsCIELab Lab);
+
+BOOL cdecl cmsxRegressionRGB2XYZ(double r, double g, double b,
+ LPMATN tfm, LPcmsCIEXYZ XYZ);
+
+
+/* -------------------------------------------------------------- Implementation */
+
+/* #define DEBUG 1 */
+
+
+/* Multiple linear regression. Also keep track of error. */
+/* Returns false if something goes wrong, or true if all Ok. */
+
+static
+BOOL MultipleLinearRegression(const LPMATN xi, /* Dependent variable */
+ const LPMATN y, /* Independent variable */
+ int nvar, /* Number of samples */
+ int npar, /* Number of parameters (terms) */
+ double* coeff, /* Returned coefficients */
+ LPMATN vcb, /* Variance-covariance array */
+ double *tvl, /* T-Values */
+ LPMLRSTATISTICS ans) /* The returned statistics */
+{
+ LPMATN bt, xt, a, xy, yt, b;
+ double sum;
+ LPMATN temp1, temp2;
+ int i;
+
+
+ /* |xt| = |xi| T */
+ xt = MATNtranspose(xi);
+ if (xt == NULL) return false;
+
+
+ /* |a| = |xt|* |xi| */
+ a = MATNmult(xt, xi);
+ if (a == NULL) return false;
+
+
+ /* |xy| = |xy| * |y| */
+ xy = MATNmult (xt, y);
+ if (xy == NULL) return false;
+
+
+ /* solve system |a|*|xy| = 0 */
+ if (!MATNsolve(a, xy)) return false;
+
+ /* b will hold coefficients */
+ b = MATNalloc (xy->Rows, 1);
+ if (b == NULL) return false;
+
+ for (i = 0; i < npar; i++)
+ b->Values[i][0] = xy->Values[i][0];
+
+ /* Store a copy for later user */
+ for (i = 0; i < npar; i++)
+ coeff[i] = b->Values[i][0];
+
+ /* Error analysis. */
+
+ /* SSE and MSE. */
+ temp1 = MATNalloc (1,1);
+ if ((temp1->Values[0][0] = MATNcross(y)) == 0) return false;
+
+ /* |bt| = |b| T */
+ bt = MATNtranspose (b);
+ if (bt == NULL) return false;
+
+ /* |yt| = |bt| * |xt| */
+ yt = MATNmult (bt, xt);
+ if (yt == NULL) return false;
+
+
+ /* |temp2| = |yt|* |y| */
+ temp2 = MATNmult (yt, y);
+ if (temp2 == NULL) return false;
+
+ /* SSE, MSE */
+ ans->SSE = temp1 -> Values[0][0] - temp2 -> Values[0][0];
+ ans->MSE = ans->SSE / (double) (nvar - npar);
+
+ /* SSTO */
+ sum = 0;
+ for (i=0; i < nvar; i++)
+ sum += y->Values[i][0];
+
+ sum *= sum / (double) nvar;
+ ans->SSTO = temp1->Values[0][0] - sum;
+
+ /* SSR, MSR, and Fisher-F */
+ ans->SSR = temp2->Values[0][0] - sum;
+ ans->MSR = ans->SSR / (double) (npar - 1);
+ ans->F = ans->MSR / ans->MSE;
+
+ /* Correlation coefficients. */
+ ans->R2 = ans->SSR/ans->SSTO;
+ ans->R2adj = 1.0 - (ans->SSE/ans->SSTO)*((nvar-1.)/(nvar-npar));
+
+ /* Variance-covariance matrix */
+ /* */
+ /* In RGB->Lab, for example: */
+ /* */
+ /* Var(R) Cov(R,G) Cov(R,B) */
+ /* |vcb| = Cov(R,G) Var(G) Cov(G,B) */
+ /* Cov(R,B) Cov(G,B) Var(B) */
+ /* */
+
+ MATNscalar(a, ans->MSE, vcb);
+
+ /* Determine the T-values */
+
+ for (i=0; i < npar; i++) {
+
+ temp1->Values[0][0] = fabs(vcb->Values[i][0]);
+ if ( temp1->Values[0][0] == 0)
+ tvl[i] = 0; /* This should never happen */
+ else
+ tvl[i] = b->Values[i][0] / sqrt(temp1->Values[0][0]);
+ }
+
+
+ /* Ok, done */
+
+ MATNfree(a); MATNfree(xy); MATNfree(yt); MATNfree(b);
+ MATNfree(temp1); MATNfree(temp2); MATNfree(bt); MATNfree(xt);
+
+
+ return true;
+}
+
+
+
+/* Does create (so, it allocates) the regression matrix, */
+/* keeping track of error as well. */
+
+static
+BOOL CreateRegressionMatrix(const LPMATN Input, const LPMATN Output,
+ LPMATN* ptrMatrix, LPMLRSTATISTICS maxErrorMeas)
+{
+ double* coef;
+ double* tval;
+ LPMATN ivar, dvar, vcov;
+ MLRSTATISTICS ErrorMeas, PeakErrorMeas;
+ int i, j, nIn, nOut, NumOfPatches;
+
+ nIn = Input -> Cols;
+ nOut = Output -> Cols;
+ NumOfPatches = Input -> Rows;
+
+ /* Checkpoint */
+ if (Output -> Rows != NumOfPatches) {
+
+ cmsSignalError(LCMS_ERRC_ABORTED, "(internal) Regression matrix mismatch");
+ return false;
+ }
+
+ coef = (double*) malloc(nIn * sizeof(double));
+ if (coef == NULL) return false;
+
+ tval = (double*) malloc(nIn * sizeof(double));
+ if (tval == NULL) {
+ free(coef);
+ return false;
+ }
+
+ ivar = MATNalloc(NumOfPatches, nIn);
+ dvar = MATNalloc(NumOfPatches, 1);
+
+ /* Copy In to ivar, */
+ for (i = 0; i < NumOfPatches; i++) {
+
+ for (j = 0; j < nIn; j++)
+ ivar->Values[i][j] = Input->Values[i][j];
+ }
+
+ /* This is the (symmetric) Covariance matrix */
+ vcov = MATNalloc(nIn, nIn);
+
+ /* This is the regression matrix */
+ *ptrMatrix = MATNalloc(nIn, nOut);
+
+ PeakErrorMeas.R2adj = 0;
+ for (j = 0; j < nOut; ++j)
+ {
+ for (i = 0; i < NumOfPatches; ++i)
+ dvar->Values[i][0] = Output->Values[i][j];
+
+ if (MultipleLinearRegression(ivar, dvar, NumOfPatches, nIn, coef, vcov, tval, &ErrorMeas)) {
+
+ /* Ok so far... store values */
+ for (i = 0; i < nIn; i++)
+ (*ptrMatrix)->Values[i][j] = coef[i];
+ }
+ else {
+ /* Boo... got error. Discard whole point. */
+ MATNfree(ivar); MATNfree(dvar); MATNfree(vcov);
+ if (coef) free(coef);
+ if (tval) free(tval);
+ MATNfree(*ptrMatrix); *ptrMatrix = NULL;
+ return false;
+ }
+
+ /* Did this colorant got higer error? If so, this is */
+ /* the peak of all pixel */
+
+ if(fabs(ErrorMeas.R2adj) > fabs(PeakErrorMeas.R2adj))
+ PeakErrorMeas = ErrorMeas;
+ }
+
+ /* This is the peak error on all components */
+ *maxErrorMeas = PeakErrorMeas;
+
+
+#ifdef DEBUG
+ MATNprintf("Variance-Covariance", vcov);
+ printf("R2adj: %g, F: %g\n", PeakErrorMeas.R2adj, PeakErrorMeas.F);
+#endif
+
+ /* Free stuff. */
+ MATNfree(ivar); MATNfree(dvar); MATNfree(vcov);
+ if (coef) free(coef);
+ if (tval) free(tval);
+
+ return true;
+}
+
+
+/* Does compute the term of regression based on inputs. */
+
+static
+double Term(int n, double r, double g, double b)
+{
+
+ switch (n) {
+
+ /* 0 */
+ case 0 : return 255.0; /* 0 0 0 */
+
+ /* 1 */
+ case 1 : return r; /* 1 0 0 */
+ case 2 : return g; /* 0 1 0 */
+ case 3 : return b; /* 0 0 1 */
+
+ /* 2 */
+ case 4 : return r * g; /* 1 1 0 */
+ case 5 : return r * b; /* 1 0 1 */
+ case 6 : return g * b; /* 0 1 1 */
+ case 7 : return r * r; /* 2 0 0 */
+ case 8 : return g * g; /* 0 2 0 */
+ case 9 : return b * b; /* 0 0 2 */
+
+ /* 3 */
+ case 10: return r * g * b; /* 1 1 1 */
+ case 11: return r * r * r; /* 3 0 0 */
+ case 12: return g * g * g; /* 0 3 0 */
+ case 13: return b * b * b; /* 0 0 3 */
+ case 14: return r * g * g; /* 1 2 0 */
+ case 15: return r * r * g; /* 2 1 0 */
+ case 16: return g * g * b; /* 0 2 1 */
+ case 17: return b * r * r; /* 2 0 1 */
+ case 18: return b * b * r; /* 1 0 2 */
+
+ /* 4 */
+
+ case 19: return r * r * g * g; /* 2 2 0 */
+ case 20: return g * g * b * b; /* 0 2 2 */
+ case 21: return r * r * b * b; /* 2 0 2 */
+ case 22: return r * r * g * b; /* 2 1 1 */
+ case 23: return r * g * g * b; /* 1 2 1 */
+ case 24: return r * g * b * b; /* 1 1 2 */
+ case 25: return r * r * r * g; /* 3 1 0 */
+ case 26: return r * r * r * b; /* 3 0 1 */
+ case 27: return r * g * g * g; /* 1 3 0 */
+ case 28: return g * g * g * b; /* 0 3 1 */
+ case 29: return r * b * b * b; /* 1 0 3 */
+ case 30: return g * b * b * b; /* 0 1 3 */
+ case 31: return r * r * r * r; /* 4 0 0 */
+ case 32: return g * g * g * g; /* 0 4 0 */
+ case 33: return b * b * b * b; /* 0 0 4 */
+
+ /* 5 */
+
+ case 34: return r * r * g * g * b; /* 2 2 1 */
+ case 35: return r * g * g * b * b; /* 1 2 2 */
+ case 36: return r * r * g * b * b; /* 2 1 2 */
+ case 37: return r * r * r * g * g; /* 3 2 0 */
+ case 38: return r * r * r * g * b; /* 3 1 1 */
+ case 39: return r * r * r * b * b; /* 3 0 2 */
+ case 40: return g * g * g * b * b; /* 0 3 2 */
+ case 41: return r * r * g * g * g; /* 2 3 0 */
+ case 42: return r * g * g * g * b; /* 1 3 1 */
+ case 43: return r * r * b * b * b; /* 2 0 3 */
+ case 44: return g * g * b * b * b; /* 0 2 3 */
+ case 45: return r * g * b * b * b; /* 1 1 3 */
+ case 46: return r * r * r * r * g; /* 4 1 0 */
+ case 47: return r * r * r * r * b; /* 4 0 1 */
+ case 48: return r * g * g * g * g; /* 1 4 0 */
+ case 49: return g * g * g * g * b; /* 0 4 1 */
+ case 50: return r * b * b * b * b; /* 1 0 4 */
+ case 51: return g * b * b * b * b; /* 0 1 4 */
+ case 52: return r * r * r * r * r; /* 5 0 0 */
+ case 53: return g * g * g * g * g; /* 0 5 0 */
+ case 54: return b * b * b * b * b; /* 0 0 5 */
+
+
+ default: return 0;
+ }
+}
+
+
+
+int cmsxRegressionCreateMatrix(LPMEASUREMENT m, SETOFPATCHES Allowed, int nterms,
+ int ColorSpace,
+ LPMATN* lpMat, LPMLRSTATISTICS Stat)
+{
+ LPMATN Input, Output;
+ int nCollected = cmsxPCollCountSet(m, Allowed);
+ int i, j, n, rc;
+
+ /* We are going always 3 -> 3 for now.... */
+
+ Input = MATNalloc(nCollected, nterms);
+ Output = MATNalloc(nCollected, 3);
+
+ /* Set independent terms */
+
+ for (n = i = 0; i < m -> nPatches; i++)
+ {
+ if (Allowed[i]) {
+
+ LPPATCH p = m -> Patches + i;
+
+ for (j=0; j < nterms; j++)
+ Input -> Values[n][j] = Term(j, p -> Colorant.RGB[0], p -> Colorant.RGB[1], p->Colorant.RGB[2]);
+
+ switch (ColorSpace) {
+
+ case PT_Lab:
+
+ Output-> Values[n][0] = p -> Lab.L;
+ Output-> Values[n][1] = p -> Lab.a;
+ Output-> Values[n][2] = p -> Lab.b;
+ break;
+
+ case PT_XYZ:
+ Output-> Values[n][0] = p -> XYZ.X;
+ Output-> Values[n][1] = p -> XYZ.Y;
+ Output-> Values[n][2] = p -> XYZ.Z;
+ break;
+
+
+ default:
+ cmsSignalError(LCMS_ERRC_ABORTED, "Invalid colorspace");
+ }
+
+ n++;
+ }
+ }
+
+
+ /* Apply multiple linear regression */
+
+ if (*lpMat) MATNfree(*lpMat);
+ rc = CreateRegressionMatrix(Input, Output, lpMat, Stat);
+
+ /* Free variables */
+
+ MATNfree(Input);
+ MATNfree(Output);
+
+
+#ifdef DEBUG
+ if (rc == true)
+ MATNprintf("tfm", *lpMat);
+#endif
+
+ return rc;
+}
+
+
+/* Convert a RGB triplet to Lab by using regression matrix */
+
+BOOL cmsxRegressionRGB2Lab(double r, double g, double b, LPMATN tfm, LPcmsCIELab Lab)
+{
+ LPMATN inVec, outVec;
+ int i;
+
+ inVec = MATNalloc(1, tfm->Rows);
+ if (inVec == NULL)
+ return false;
+
+ /* Put terms */
+ for (i=0; i < tfm->Rows; i++)
+ inVec -> Values[0][i] = Term(i, r, g, b);
+
+ /* Across regression matrix */
+ outVec = MATNmult(inVec, tfm);
+
+ /* Store result */
+ if (outVec != NULL) {
+
+ Lab->L = outVec->Values[0][0];
+ Lab->a = outVec->Values[0][1];
+ Lab->b = outVec->Values[0][2];
+ MATNfree(outVec);
+ }
+
+ MATNfree(inVec);
+ return true;
+}
+
+
+/* Convert a RGB triplet to XYX by using regression matrix */
+
+BOOL cmsxRegressionRGB2XYZ(double r, double g, double b, LPMATN tfm, LPcmsCIEXYZ XYZ)
+{
+ LPMATN inVec, outVec;
+ int i;
+
+ inVec = MATNalloc(1, tfm->Rows);
+ if (inVec == NULL)
+ return false;
+
+ /* Put terms */
+ for (i=0; i < tfm->Rows; i++)
+ inVec -> Values[0][i] = Term(i, r, g, b);
+
+ /* Across regression matrix */
+ outVec = MATNmult(inVec, tfm);
+
+ /* Store result */
+ if (outVec != NULL) {
+
+ XYZ->X = outVec->Values[0][0];
+ XYZ->Y = outVec->Values[0][1];
+ XYZ->Z = outVec->Values[0][2];
+ MATNfree(outVec);
+ }
+
+ MATNfree(inVec);
+ return true;
+}
+
+
+/* Convert a RGB triplet to XYX by using regression matrix */
+
+BOOL cmsxRegressionXYZ2RGB(LPcmsCIEXYZ XYZ, LPMATN tfm, double RGB[3])
+{
+ LPMATN inVec, outVec;
+ int i;
+
+ inVec = MATNalloc(1, tfm->Rows);
+ if (inVec == NULL)
+ return false;
+
+ /* Put terms */
+ for (i=0; i < tfm->Rows; i++)
+ inVec -> Values[0][i] = Term(i, XYZ->X, XYZ->Y, XYZ->Z);
+
+ /* Across regression matrix */
+ outVec = MATNmult(inVec, tfm);
+
+ /* Store result */
+ if (outVec != NULL) {
+
+ RGB[0] = outVec->Values[0][0];
+ RGB[1] = outVec->Values[0][1];
+ RGB[2] = outVec->Values[0][2];
+ MATNfree(outVec);
+ }
+
+ MATNfree(inVec);
+ return true;
+}
+
diff --git a/src/libs/lprof/cmsscn.cpp b/src/libs/lprof/cmsscn.cpp
new file mode 100644
index 00000000..b99fed87
--- /dev/null
+++ b/src/libs/lprof/cmsscn.cpp
@@ -0,0 +1,422 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.08a */
+
+
+#include "lcmsprf.h"
+#include <stdio.h>
+
+/* The scanner profiler */
+
+
+BOOL cdecl cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys);
+BOOL cdecl cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys);
+
+/* ------------------------------------------------------------ Implementation */
+
+
+
+/* Does create regression matrix */
+
+static
+void ComputeGlobalRegression(LPSCANNERPROFILERDATA sys)
+{
+ BOOL lAllOk;
+ int nTerms;
+ MLRSTATISTICS Stat;
+
+ nTerms = cmsxFindOptimumNumOfTerms(&sys ->hdr, 55, &lAllOk);
+
+
+ if (!lAllOk) {
+ if (sys -> hdr.printf)
+ sys -> hdr.printf("*** WARNING: Inconsistence found, profile may be wrong. Check the target!");
+ nTerms = 4;
+ }
+
+ /* Create high terms matrix used by interpolation */
+ cmsxRegressionCreateMatrix(&sys -> hdr.m,
+ sys -> hdr.m.Allowed,
+ nTerms,
+ sys -> hdr.PCSType,
+ &sys -> HiTerms,
+ &Stat);
+
+ if (sys -> hdr.printf)
+ sys -> hdr.printf("Global regression: %d terms, R2Adj = %g", nTerms, Stat.R2adj);
+
+ /* Create low terms matrix used by extrapolation */
+ cmsxRegressionCreateMatrix(&sys -> hdr.m,
+ sys -> hdr.m.Allowed,
+ (nTerms > 10 ? 10 : nTerms),
+ sys -> hdr.PCSType,
+ &sys -> LoTerms,
+ &Stat);
+ if (sys -> hdr.printf)
+ sys -> hdr.printf("Extrapolation: R2Adj = %g", Stat.R2adj);
+
+}
+
+
+/* Fill struct with default values */
+
+BOOL cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys)
+{
+
+
+ if (sys == NULL) return false;
+ ZeroMemory(sys, sizeof(SCANNERPROFILERDATA));
+
+ sys->hdr.DeviceClass = icSigInputClass;
+ sys->hdr.ColorSpace = icSigRgbData;
+ sys->hdr.PCSType = PT_Lab;
+ sys->hdr.Medium = MEDIUM_REFLECTIVE_D50;
+
+ /* Default values for generation */
+
+ sys -> hdr.lUseCIECAM97s = false;
+ sys -> hdr.CLUTPoints = 16;
+
+
+ /* Default viewing conditions for scanner */
+
+ sys -> hdr.device.Yb = 20;
+ sys -> hdr.device.La = 20;
+ sys -> hdr.device.surround = AVG_SURROUND;
+ sys -> hdr.device.D_value = 1.0; /* Complete adaptation */
+
+
+ /* Viewing conditions of PCS */
+ cmsxInitPCSViewingConditions(&sys -> hdr);
+
+
+ sys -> HiTerms = NULL;
+ sys -> LoTerms = NULL;
+
+ strcpy(sys -> hdr.Description, "no description");
+ strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set");
+ strcpy(sys -> hdr.Copyright, "No copyright, use freely");
+ strcpy(sys -> hdr.Model, "(unknown)");
+
+ sys ->lLocalConvergenceExtrapolation = false;
+ sys ->hdr.ProfileVerbosityLevel = 0;
+
+
+ return true;
+}
+
+/* Auxiliar: take RGB and update gauge */
+static
+void GetRGB(LPPROFILERCOMMONDATA hdr, WORD In[], double* r, double* g, double* b)
+{
+ static int Count = 0, n_old = 0;
+ double R, G, B;
+ int n;
+
+
+ R = _cmsxSaturate65535To255(In[0]); /* Convert from the sheet notation */
+ G = _cmsxSaturate65535To255(In[1]); /* 0..255.0, to our notation */
+ B = _cmsxSaturate65535To255(In[2]); /* of 0..0xffff, 0xffff/255 = 257 */
+
+ if (R == 0 && G == 0 && B == 0) {
+ Count = 0; n_old = -1;
+ }
+
+ n = (int) (double) (100. * Count) / (hdr->CLUTPoints * hdr->CLUTPoints * hdr->CLUTPoints);
+ Count++;
+
+ if (n > n_old) {
+ if (hdr->Gauger) hdr->Gauger("", 0, 100, (int) n);
+ }
+
+ n_old = n;
+ *r = R; *g = G; *b = B;
+
+}
+
+
+
+
+
+/* The sampler for Lab */
+static
+int RegressionSamplerLab(WORD In[], WORD Out[], LPVOID Cargo)
+{
+ cmsCIEXYZ xyz;
+ cmsCIELab Lab;
+ double r, g, b;
+ LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo;
+ char code;
+
+
+ GetRGB(&sys->hdr, In, &r, &g, &b);
+
+
+ code = cmsxHullCheckpoint(sys->hdr.hRGBHull,
+ (int) floor(r + .5),
+ (int) floor(g + .5),
+ (int) floor(b + .5));
+
+
+ if (code == 'i') { /* Inside gamut */
+
+ if (!cmsxRegressionRGB2Lab(r, g, b, sys -> HiTerms, &Lab)) return false;
+ }
+ else
+ if (!sys -> lLocalConvergenceExtrapolation && code == 'o') { /* outside gamut */
+
+ if (!cmsxRegressionRGB2Lab(r, g, b, sys -> LoTerms, &Lab)) return false;
+ }
+ else { /* At gamut hull boundaries */
+
+ if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m,
+ PT_Lab,
+ 10,
+ true,
+ 30,
+ r, g, b,
+ &Lab)) return false;
+ }
+
+
+ /* Regression CAN deliver wrong values. Clamp these. */
+ cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128);
+
+ /* Normalize */
+ cmsLab2XYZ(cmsD50_XYZ(), &xyz, &Lab);
+ cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false);
+ cmsXYZ2Lab(cmsD50_XYZ(), &Lab, &xyz);
+
+ /* Clamping again, adaptation could move slightly values */
+ cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128);
+
+ /* To PCS encoding */
+ cmsFloat2LabEncoded(Out, &Lab);
+
+
+ return true; /* And done with success */
+}
+
+
+
+
+
+/* The sampler for XYZ */
+static
+int RegressionSamplerXYZ(WORD In[], WORD Out[], LPVOID Cargo)
+{
+ cmsCIEXYZ xyz;
+ double r, g, b;
+ LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo;
+ char code;
+
+ GetRGB(&sys -> hdr, In, &r, &g, &b);
+
+ code = cmsxHullCheckpoint(sys ->hdr.hRGBHull,
+ (int) floor(r + .5),
+ (int) floor(g + .5),
+ (int) floor(b + .5));
+
+ if (code == 'i') { /* Inside gamut */
+
+ if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> HiTerms, &xyz)) return false;
+ }
+ else
+ if (!sys -> lLocalConvergenceExtrapolation && code == 'o') { /* outside gamut */
+
+ if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> LoTerms, &xyz)) return false;
+ }
+
+ else { /* At gamut hull boundaries */
+
+ if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m,
+ PT_XYZ,
+ 10,
+ true,
+ 30,
+ r, g, b,
+ &xyz)) return false;
+ }
+
+
+ xyz.X /= 100.;
+ xyz.Y /= 100.;
+ xyz.Z /= 100.;
+
+ cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false);
+
+ /* To PCS encoding. It also claps bad values */
+ cmsFloat2XYZEncoded(Out, &xyz);
+
+ return true; /* And done witch success */
+}
+
+
+
+/* The main scanner profiler */
+BOOL cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys)
+{
+
+ LPLUT AToB0;
+ DWORD dwNeedSamples;
+
+
+ if (!*sys -> hdr.OutputProfileFile)
+ return false;
+
+
+ if (!cmsxChoosePCS(&sys->hdr))
+ return false;
+
+ dwNeedSamples = PATCH_HAS_RGB;
+ if (sys ->hdr.PCSType == PT_Lab)
+ dwNeedSamples |= PATCH_HAS_Lab;
+ else
+ dwNeedSamples |= PATCH_HAS_XYZ;
+
+
+ if (sys->hdr.printf) {
+
+ sys->hdr.printf("Loading sheets...");
+
+ if (sys->hdr.ReferenceSheet[0])
+ sys->hdr.printf("Reference sheet: %s", sys->hdr.ReferenceSheet);
+ if (sys->hdr.MeasurementSheet[0])
+ sys->hdr.printf("Measurement sheet: %s", sys->hdr.MeasurementSheet);
+ }
+
+
+ if (!cmsxPCollBuildMeasurement(&sys->hdr.m,
+ sys->hdr.ReferenceSheet,
+ sys->hdr.MeasurementSheet,
+ dwNeedSamples)) return false;
+
+
+
+ sys->hdr.hProfile = cmsCreateRGBProfile(NULL, NULL, NULL);
+
+
+ cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass);
+ cmsSetColorSpace(sys->hdr.hProfile, sys->hdr.ColorSpace);
+ cmsSetPCS(sys->hdr. hProfile, _cmsICCcolorSpace(sys->hdr.PCSType));
+
+ /* Save char target tag */
+ if (sys->hdr.ProfileVerbosityLevel >= 2) {
+
+ cmsxEmbedCharTarget(&sys ->hdr);
+ }
+
+ AToB0 = cmsAllocLUT();
+
+ cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3);
+
+ cmsxComputeLinearizationTables(&sys-> hdr.m,
+ sys -> hdr.PCSType,
+ sys -> Prelinearization,
+ 1024,
+ MEDIUM_REFLECTIVE_D50);
+
+ /* Refresh RGB of all patches. This converts all regression into */
+ /* near linear RGB->Lab or XYZ */
+
+ cmsxPCollLinearizePatches(&sys->hdr.m, sys -> hdr.m.Allowed, sys -> Prelinearization);
+
+ cmsxComputeGamutHull(&sys->hdr);
+ ComputeGlobalRegression(sys);
+
+ cmsAllocLinearTable(AToB0, sys -> Prelinearization, 1);
+
+ /* Set CIECAM97s parameters */
+
+ sys -> hdr.device.whitePoint.X = sys -> hdr.WhitePoint.X * 100.;
+ sys -> hdr.device.whitePoint.Y = sys -> hdr.WhitePoint.Y * 100.;
+ sys -> hdr.device.whitePoint.Z = sys -> hdr.WhitePoint.Z * 100.;
+
+
+ sys->hdr.hDevice = cmsCIECAM97sInit(&sys->hdr.device);
+ sys->hdr.hPCS = cmsCIECAM97sInit(&sys->hdr.PCS);
+
+
+ if (sys -> hdr.PCSType == PT_Lab)
+ cmsSample3DGrid(AToB0, RegressionSamplerLab, sys, 0);
+ else
+ cmsSample3DGrid(AToB0, RegressionSamplerXYZ, sys, 0);
+
+ cmsCIECAM97sDone(sys->hdr.hDevice);
+ cmsCIECAM97sDone(sys->hdr.hPCS);
+
+ cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0);
+
+
+ cmsxEmbedTextualInfo(&sys -> hdr);
+
+ cmsAddTag(sys->hdr.hProfile, icSigMediaWhitePointTag, &sys->hdr.WhitePoint);
+ cmsAddTag(sys->hdr.hProfile, icSigMediaBlackPointTag, &sys->hdr.BlackPoint);
+
+
+ /* Save primaries & gamma curves */
+ if (sys->hdr.ProfileVerbosityLevel >= 1) {
+
+ cmsxEmbedMatrixShaper(&sys ->hdr);
+ }
+
+ _cmsSaveProfile(sys->hdr.hProfile, sys->hdr.OutputProfileFile);
+
+ cmsCloseProfile(sys->hdr.hProfile);
+ sys->hdr.hProfile = NULL;
+
+ cmsxPCollFreeMeasurements(&sys->hdr.m);
+
+ cmsFreeLUT(AToB0);
+
+ if (sys -> HiTerms)
+ MATNfree(sys -> HiTerms);
+ sys -> HiTerms = NULL;
+
+
+ if (sys -> LoTerms)
+ MATNfree(sys -> LoTerms);
+ sys -> LoTerms = NULL;
+
+
+
+ if (sys ->Prelinearization[0])
+ cmsFreeGammaTriple(sys -> Prelinearization);
+
+ if (sys ->hdr.Gamma)
+ cmsFreeGammaTriple(sys->hdr.Gamma);
+
+ return true;
+}
diff --git a/src/libs/lprof/cmssheet.cpp b/src/libs/lprof/cmssheet.cpp
new file mode 100644
index 00000000..cf8c569e
--- /dev/null
+++ b/src/libs/lprof/cmssheet.cpp
@@ -0,0 +1,1746 @@
+/* */
+/* Little cms - profiler construction set */
+/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
+/* */
+/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
+/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
+/* */
+/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
+/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
+/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
+/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
+/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
+/* OF THIS SOFTWARE. */
+/* */
+/* This file is free software; you can redistribute it and/or modify it */
+/* under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+/* */
+/* As a special exception to the GNU General Public License, if you */
+/* distribute this file as part of a program that contains a */
+/* configuration script generated by Autoconf, you may include it under */
+/* the same distribution terms that you use for the rest of that program. */
+/* */
+/* Version 1.08a */
+
+
+#include "lcmsprf.h"
+
+/* #define _DEBUG 1 */
+
+
+
+/* IT8.7 / CGATS.17-200x handling */
+LCMSHANDLE cdecl cmsxIT8Alloc(void);
+void cdecl cmsxIT8Free(LCMSHANDLE IT8);
+
+/* Persistence */
+LCMSHANDLE cdecl cmsxIT8LoadFromFile(const char* cFileName);
+LCMSHANDLE cdecl cmsxIT8LoadFromMem(void *Ptr, size_t len);
+BOOL cdecl cmsxIT8SaveToFile(LCMSHANDLE IT8, const char* cFileName);
+
+/* Properties */
+const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8);
+BOOL cdecl cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type);
+
+BOOL cdecl cmsxIT8SetProperty(LCMSHANDLE hIT8, const char* cProp, const char *Str);
+BOOL cdecl cmsxIT8SetPropertyDbl(LCMSHANDLE hIT8, const char* cProp, double Val);
+
+const char* cdecl cmsxIT8GetProperty(LCMSHANDLE hIT8, const char* cProp);
+double cdecl cmsxIT8GetPropertyDbl(LCMSHANDLE hIT8, const char* cProp);
+int cdecl cmsxIT8EnumProperties(LCMSHANDLE IT8, char ***PropertyNames);
+
+
+/* Datasets */
+
+BOOL cdecl cmsxIT8GetDataSetByPos(LCMSHANDLE IT8, int col, int row, char* Val, int ValBufferLen);
+
+BOOL cdecl cmsxIT8GetDataSet(LCMSHANDLE IT8, const char* cPatch,
+ const char* cSample,
+ char* Val, int ValBufferLen);
+
+
+BOOL cdecl cmsxIT8GetDataSetDbl(LCMSHANDLE IT8, const char* cPatch, const char* cSample, double* d);
+
+BOOL cdecl cmsxIT8SetDataSet(LCMSHANDLE IT8, const char* cPatch,
+ const char* cSample,
+ char *Val);
+
+BOOL cdecl cmsxIT8SetDataFormat(LCMSHANDLE IT8, int n, const char *Sample);
+int cdecl cmsxIT8EnumDataFormat(LCMSHANDLE IT8, char ***SampleNames);
+
+const char *cdecl cmsxIT8GenericPatchName(int nPatch, char* buffer);
+int cdecl cmsxIT8GenericPatchNum(const char *Name);
+
+
+/* ------------------------------------------------------------- Implementation */
+
+
+#define MAXID 128 /* Max length of identifier */
+#define MAXSTR 255 /* Max length of string */
+
+#ifndef NON_WINDOWS
+#include <io.h>
+#endif
+
+/* Symbols */
+
+typedef enum { SNONE,
+ SINUM, /* Integer */
+ SDNUM, /* Real */
+ SIDENT, /* Identifier */
+ SSTRING, /* string */
+ SCOMMENT, /* comment */
+ SEOLN, /* End of line */
+ SEOF, /* End of stream */
+ SSYNERROR, /* Syntax error found on stream */
+
+ /* Keywords */
+
+ SBEGIN_DATA,
+ SBEGIN_DATA_FORMAT,
+ SEND_DATA,
+ SEND_DATA_FORMAT,
+ SKEYWORD,
+ SSTRING_SY
+
+ } SYMBOL;
+
+
+/* Linked list of variable names */
+
+typedef struct _KeyVal {
+
+ struct _KeyVal* Next;
+ char* Keyword; /* Name of variable */
+ char* Value; /* Points to value */
+
+ } KEYVALUE, FAR* LPKEYVALUE;
+
+/* Linked list of values (Memory sink) */
+
+typedef struct _OwnedMem {
+
+ struct _OwnedMem* Next;
+ void * Ptr; /* Point to value */
+
+ } OWNEDMEM, FAR* LPOWNEDMEM;
+
+
+/* This struct hold all information about an openened */
+/* IT8 handler. Only one dataset is allowed. */
+
+typedef struct {
+
+ int nSamples, nPatches; /* Rows, Cols */
+ int SampleID; /* Pos of ID */
+ LPKEYVALUE HeaderList; /* The properties */
+ char* FileBuffer; /* The ASCII stream */
+ char** DataFormat; /* The binary stream descriptor */
+ char** Data; /* The binary stream */
+ LPOWNEDMEM MemorySink; /* The storage bakend */
+
+ /* Parser state machine */
+
+ SYMBOL sy; /* Current symbol */
+ int ch; /* Current character */
+ char* Source; /* Points to loc. being parsed */
+ int inum; /* integer value */
+ double dnum; /* real value */
+ char id[MAXID]; /* identifier */
+ char str[MAXSTR]; /* string */
+
+ /* Allowed keywords & datasets */
+
+ LPKEYVALUE ValidKeywords;
+ LPKEYVALUE ValidSampleID;
+
+ char FileName[MAX_PATH];
+ int lineno; /* line counter for error reporting */
+
+ char SheetType[MAXSTR]; /* New 1.09 */
+
+ } IT8, FAR* LPIT8;
+
+
+
+/* ------------------------------------------------------ IT8 parsing routines */
+
+
+/* A keyword */
+typedef struct {
+ const char *id;
+ SYMBOL sy;
+
+ } KEYWORD;
+
+/* The keyword->symbol translation table. Sorting is required. */
+static const KEYWORD TabKeys[] = {
+
+ {"BEGIN_DATA", SBEGIN_DATA },
+ {"BEGIN_DATA_FORMAT", SBEGIN_DATA_FORMAT },
+ {"END_DATA", SEND_DATA},
+ {"END_DATA_FORMAT", SEND_DATA_FORMAT},
+ {"KEYWORD", SKEYWORD},
+ {"STRING", SSTRING_SY}};
+
+#define NUMKEYS (sizeof(TabKeys)/sizeof(KEYWORD))
+
+/* Predefined properties */
+static char* PredefinedProperties[] = {
+
+ "NUMBER_OF_FIELDS", /* Required - NUMBER OF FIELDS */
+ "NUMBER_OF_SETS", /* Required - NUMBER OF SETS */
+ "ORIGINATOR", /* Required - Identifies the specific system, organization or individual that created the data file. */
+ "CREATED", /* Required - Indicates date of creation of the data file. */
+ "DESCRIPTOR", /* Required - Describes the purpose or contents of the data file. */
+ "DIFFUSE_GEOMETRY", /* The diffuse geometry used. Allowed values are "sphere" or "opal". */
+ "MANUFACTURER",
+ "MANUFACTURE", /* Some broken Fuji targets does store this value */
+ "PROD_DATE", /* Identifies year and month of production of the target in the form yyyy:mm. */
+ "SERIAL", /* Uniquely identifies individual physical target. */
+
+ "MATERIAL", /* Identifies the material on which the target was produced using a code */
+ /* uniquely identifying th e material. Th is is intend ed to be used for IT8.7 */
+ /* physical targets only (i.e . IT8.7/1 a nd IT8.7/2). */
+
+ "INSTRUMENTATION", /* Used to report the specific instrumentation used (manufacturer and */
+ /* model number) to generate the data reported. This data will often */
+ /* provide more information about the particular data collected than an */
+ /* extensive list of specific details. This is particularly important for */
+ /* spectral data or data derived from spectrophotometry. */
+
+ "MEASUREMENT_SOURCE", /* Illumination used for spectral measurements. This data helps provide */
+ /* a guide to the potential for issues of paper fluorescence, etc. */
+
+ "PRINT_CONDITIONS", /* Used to define the ch aracteristics of the printed sheet being reported. */
+ /* Where standard conditions have been defined (e.g., SW OP at nominal) */
+ /* named conditions may suffice. Otherwise, detailed in formation is */
+ /* needed. */
+
+ "SAMPLE_BACKING", /* Identifies the backing material used behind the sample during */
+ /* measurement. Allowed values are black, white, or "na". */
+
+ "CHISQ_DOF" /* Degrees of freedom associated with the Chi squared statistic */
+ };
+
+#define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(char *))
+
+
+/* Predefined sample types on dataset */
+static char* PredefinedSampleID[] = {
+
+ "CMYK_C", /* Cyan component of CMYK data expressed as a percentage */
+ "CMYK_M", /* Magenta component of CMYK data expressed as a percentage */
+ "CMYK_Y", /* Yellow component of CMYK data expressed as a percentage */
+ "CMYK_K", /* Black component of CMYK data expressed as a percentage */
+ "D_RED", /* Red filter density */
+ "D_GREEN", /* Green filter density */
+ "D_BLUE", /* Blue filter density */
+ "D_VIS", /* Visual filter density */
+ "D_MAJOR_FILTER", /* Major filter d ensity */
+ "RGB_R", /* Red component of RGB data */
+ "RGB_G", /* Green component of RGB data */
+ "RGB_B", /* Blue com ponent of RGB data */
+ "SPECTRAL_NM", /* Wavelength of measurement expressed in nanometers */
+ "SPECTRAL_PCT", /* Percentage reflectance/transmittance */
+ "SPECTRAL_DEC", /* Reflectance/transmittance */
+ "XYZ_X", /* X component of tristimulus data */
+ "XYZ_Y", /* Y component of tristimulus data */
+ "XYZ_Z", /* Z component of tristimulus data */
+ "XYY_X" /* x component of chromaticity data */
+ "XYY_Y", /* y component of chromaticity data */
+ "XYY_CAPY", /* Y component of tristimulus data */
+ "LAB_L", /* L* component of Lab data */
+ "LAB_A", /* a* component of Lab data */
+ "LAB_B", /* b* component of Lab data */
+ "LAB_C", /* C*ab component of Lab data */
+ "LAB_H", /* hab component of Lab data */
+ "LAB_DE" /* CIE dE */
+ "LAB_DE_94", /* CIE dE using CIE 94 */
+ "LAB_DE_CMC", /* dE using CMC */
+ "LAB_DE_2000", /* CIE dE using CIE DE 2000 */
+ "MEAN_DE", /* Mean Delta E (LAB_DE) of samples compared to batch average */
+ /* (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets) */
+ "STDEV_X", /* Standard deviation of X (tristimulus data) */
+ "STDEV_Y", /* Standard deviation of Y (tristimulus data) */
+ "STDEV_Z", /* Standard deviation of Z (tristimulus data) */
+ "STDEV_L", /* Standard deviation of L* */
+ "STDEV_A" /* Standard deviation of a* */
+ "STDEV_B", /* Standard deviation of b* */
+ "STDEV_DE", /* Standard deviation of CIE dE */
+ "CHI_STQD_PAR"}; /* The average of the standard deviations of L*, a* and b*. It is */
+ /* used to derive an estimate of the chi-squared parameter which is */
+ /* recommended as the predictor of the variability of dE */
+
+#define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *))
+
+
+/* Checks whatsever if c is a valid identifier middle char. */
+static
+BOOL isidchar(int c)
+{
+ return (isalnum(c) || c == '$' || c == '%' || c == '&' || c == '/' || c == '.' || c == '_');
+
+}
+
+/* Checks whatsever if c is a valid identifier first char. */
+static
+BOOL isfirstidchar(int c)
+{
+ return !isdigit(c) && isidchar(c);
+}
+
+/* Checks if c is a separator */
+static
+BOOL isseparator(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\r');
+}
+
+/* a replacement for strupr(), just for compatibility sake */
+
+static
+void xstrupr(char *cp)
+{
+ for (;*cp;cp++)
+ if (*cp >= 'a' && *cp <= 'z')
+ *cp += 'A'-'a';
+}
+
+/* A replacement for (the nonstandard) filelength */
+
+static
+int xfilelength(int fd)
+{
+#ifdef _MSC_VER
+ return _filelength(fd);
+#else
+ struct stat sb;
+ if (fstat(fd, &sb) < 0)
+ return(-1);
+ return(sb.st_size);
+#endif
+
+
+}
+
+static
+BOOL SynError(LPIT8 it8, const char *Txt, ...)
+{
+ char Buffer[256], ErrMsg[1024];
+ va_list args;
+
+ va_start(args, Txt);
+ vsprintf(Buffer, Txt, args);
+ va_end(args);
+
+ sprintf(ErrMsg, "%s: Line %d, %s", it8->FileName, it8->lineno, Buffer);
+ it8->sy = SSYNERROR;
+ cmsSignalError(LCMS_ERRC_ABORTED, ErrMsg);
+ return false;
+}
+
+static
+BOOL Check(LPIT8 it8, SYMBOL sy, const char* Err)
+{
+ if (it8 -> sy != sy)
+ return SynError(it8, Err);
+ return true;
+}
+
+
+
+/* Read Next character from stream */
+static
+void NextCh(LPIT8 it8)
+{
+ it8->ch = *it8->Source;
+ if (it8->ch) it8->Source++;
+}
+
+
+/* Try to see if current identifier is a keyword, if so return the referred symbol */
+static
+SYMBOL BinSrchKey(const char *id)
+{
+ int l = 1;
+ int r = NUMKEYS;
+ int x, res;
+
+ while (r >= l)
+ {
+ x = (l+r)/2;
+ res = strcmp(id, TabKeys[x-1].id);
+ if (res == 0) return TabKeys[x-1].sy;
+ if (res < 0) r = x - 1;
+ else l = x + 1;
+ }
+
+ return SNONE;
+}
+
+
+/* 10 ^n */
+static
+double pow10(int n)
+{
+ return pow(10, n);
+}
+
+
+/* Reads a Real number, tries to follow from integer number */
+static
+void ReadReal(LPIT8 it8, int inum)
+{
+ it8->dnum = (double) inum;
+
+ while (isdigit(it8->ch)) {
+
+ it8->dnum = it8->dnum * 10.0 + (it8->ch - '0');
+ NextCh(it8);
+ }
+
+ if (it8->ch == '.') { /* Decimal point */
+
+ double frac = 0.0; /* fraction */
+ int prec = 0; /* precission */
+
+ NextCh(it8); /* Eats dec. point */
+
+ while (isdigit(it8->ch)) {
+
+ frac = frac * 10.0 + (it8->ch - '0');
+ prec++;
+ NextCh(it8);
+ }
+
+ it8->dnum = it8->dnum + (frac / pow10(prec));
+ }
+
+ /* Exponent, example 34.00E+20 */
+ if (toupper(it8->ch) == 'E') {
+
+ int e;
+ int sgn;
+
+ NextCh(it8); sgn = 1;
+
+ if (it8->ch == '-') {
+
+ sgn = -1; NextCh(it8);
+ }
+ else
+ if (it8->ch == '+') {
+
+ sgn = +1;
+ NextCh(it8);
+ }
+
+
+ e = 0;
+ while (isdigit(it8->ch)) {
+
+ if ((double) e * 10L < INT_MAX)
+ e = e * 10 + (it8->ch - '0');
+
+ NextCh(it8);
+ }
+
+ e = sgn*e;
+
+ it8 -> dnum = it8 -> dnum * pow10(e);
+ }
+}
+
+
+
+/* Reads next symbol */
+static
+void InSymbol(LPIT8 it8)
+{
+ char *idptr;
+ int k;
+ SYMBOL key;
+ int sng;
+
+ do {
+
+ while (isseparator(it8->ch))
+ NextCh(it8);
+
+ if (isfirstidchar(it8->ch)) { /* Identifier */
+
+
+ k = 0;
+ idptr = it8->id;
+
+ do {
+
+ if (++k < MAXID) *idptr++ = (char) it8->ch;
+
+ NextCh(it8);
+
+ } while (isidchar(it8->ch));
+
+ *idptr = '\0';
+ xstrupr(it8->id);
+
+ key = BinSrchKey(it8->id);
+ if (key == SNONE) it8->sy = SIDENT;
+ else it8->sy = key;
+
+ }
+ else /* Is a number? */
+ if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+')
+ {
+ int sign = 1;
+
+ if (it8->ch == '-') {
+ sign = -1;
+ NextCh(it8);
+ }
+
+ it8->inum = 0;
+ it8->sy = SINUM;
+
+ while (isdigit(it8->ch))
+ {
+ if ((long) it8->inum * 10L > (long) INT_MAX)
+ {
+ ReadReal(it8, it8->inum);
+ it8->sy = SDNUM;
+ it8->dnum *= sign;
+ return;
+ }
+
+ it8->inum = it8->inum * 10 + (it8->ch - '0');
+ NextCh(it8);
+ }
+
+ if (it8->ch == '.') {
+
+ ReadReal(it8, it8->inum);
+ it8->sy = SDNUM;
+ it8->dnum *= sign;
+ return;
+ }
+
+ it8 -> inum *= sign;
+ return;
+
+ }
+ else
+ switch ((int) it8->ch) {
+
+ case '\0':
+ case '\x1a':
+ it8->sy = SEOF;
+ break;
+
+
+
+ case '\n':
+ NextCh(it8);
+ it8->sy = SEOLN;
+ it8->lineno++;
+ break;
+
+ /* Comment */
+
+ case '#':
+ NextCh(it8);
+ while (it8->ch && it8->ch != '\n')
+ NextCh(it8);
+
+ it8->sy = SCOMMENT;
+ break;
+
+ /* String. I will support \", \n, \t and \\. */
+ /* But otherwise I hardly doubt these will be used ... */
+
+ case '\'':
+ case '\"':
+ idptr = it8->str;
+ sng = it8->ch;
+ k = 0;
+ NextCh(it8);
+
+ while (k < MAXSTR && it8->ch != sng) {
+
+ if (it8->ch == '\n'|| it8->ch == '\r') k = MAXSTR+1;
+ else {
+
+ if (it8->ch == '\\')
+ {
+ NextCh(it8);
+
+ switch (it8->ch) {
+
+ case 'n': *idptr++ = '\n'; break;
+ case 'r': *idptr++ = '\r'; break;
+ case 't': *idptr++ = '\t'; break;
+ case '\\': *idptr++ = '\\'; break;
+
+ default:
+ *idptr++ = (char) it8->ch;
+ }
+
+ NextCh(it8);
+ }
+ else
+ {
+ *idptr++ = (char) it8->ch;
+ NextCh(it8);
+ }
+
+ k++;
+ }
+ }
+
+ it8->sy = SSTRING;
+ *idptr = '\0';
+ NextCh(it8);
+ break;
+
+
+ default:
+ it8->sy = SSYNERROR;
+ NextCh(it8);
+
+ }
+
+ } while (it8->sy == SCOMMENT);
+}
+
+/* Checks end of line separator */
+static
+BOOL CheckEOLN(LPIT8 it8)
+{
+ if (!Check(it8, SEOLN, "Expected separator")) return false;
+ while (it8 -> sy == SEOLN)
+ InSymbol(it8);
+ return true;
+
+}
+
+/* Skip a symbol */
+
+static
+void Skip(LPIT8 it8, SYMBOL sy)
+{
+ if (it8->sy == sy && it8->sy != SEOF)
+ InSymbol(it8);
+}
+
+/* Returns a string holding current value */
+static
+BOOL GetVal(LPIT8 it8, char* Buffer)
+{
+ switch (it8->sy) {
+
+ case SIDENT: strncpy(Buffer, it8->id, MAXID-1); break;
+ case SINUM: sprintf(Buffer, "%d", it8 -> inum); break;
+ case SDNUM: sprintf(Buffer, "%g", it8 -> dnum); break;
+ case SSTRING: strncpy(Buffer, it8->str, MAXSTR-1); break;
+
+
+ default:
+ return SynError(it8, "Sample data expected");
+ }
+
+ return true;
+}
+
+/* ---------------------------------------------------------- Memory management */
+
+
+
+/* Frees an allocator and owned memory */
+void cmsxIT8Free(LCMSHANDLE hIT8)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+
+ if (it8 == NULL)
+ return;
+
+ if (it8->MemorySink) {
+
+ LPOWNEDMEM p;
+ LPOWNEDMEM n;
+
+ for (p = it8->MemorySink; p != NULL; p = n) {
+
+ n = p->Next;
+ if (p->Ptr) free(p->Ptr);
+ free(p);
+ }
+ }
+
+ if (it8->FileBuffer)
+ free(it8->FileBuffer);
+
+ free(it8);
+}
+
+
+/* Allocates a chunk of data, keep linked list */
+static
+void* AllocChunk(LPIT8 it8, size_t size)
+{
+ LPOWNEDMEM ptr1;
+ void* ptr = malloc(size);
+
+ if (ptr) {
+
+ ZeroMemory(ptr, size);
+ ptr1 = (LPOWNEDMEM) malloc(sizeof(OWNEDMEM));
+
+ if (ptr1 == NULL) {
+
+ free(ptr);
+ return NULL;
+ }
+
+ ZeroMemory(ptr1, sizeof(OWNEDMEM));
+
+ ptr1-> Ptr = ptr;
+ ptr1-> Next = it8 -> MemorySink;
+ it8 -> MemorySink = ptr1;
+ }
+
+ return ptr;
+}
+
+
+/* Allocates a string */
+static
+char *AllocString(LPIT8 it8, const char* str)
+{
+ int Size = strlen(str)+1;
+ char *ptr;
+ ptr = (char *) AllocChunk(it8, Size);
+ if (ptr) strncpy (ptr, str, Size);
+ return ptr;
+}
+
+/* Searches through linked list */
+
+static
+BOOL IsAvailableOnList(LPKEYVALUE p, const char* Key, LPKEYVALUE* LastPtr)
+{
+ for (; p != NULL; p = p->Next) {
+
+ if (LastPtr) *LastPtr = p;
+ if (stricmp(Key, p->Keyword) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+
+
+/* Add a property into a linked list */
+static
+BOOL AddToList(LPIT8 it8, LPKEYVALUE* Head, const char *Key, const char* Value)
+{
+ LPKEYVALUE p;
+ LPKEYVALUE last;
+
+
+ /* Check if property is already in list (this is an error) */
+
+ if (IsAvailableOnList(*Head, Key, &last)) {
+ cmsSignalError(LCMS_ERRC_ABORTED, "duplicate key <%s>", Key);
+ return false;
+ }
+
+ /* Allocate the container */
+ p = (LPKEYVALUE) AllocChunk(it8, sizeof(KEYVALUE));
+ if (p == NULL)
+ {
+ cmsSignalError(LCMS_ERRC_ABORTED, "AddToList: out of memory");
+ return false;
+ }
+
+ /* Store name and value */
+ p->Keyword = AllocString(it8, Key);
+
+ if (Value)
+ p->Value = AllocString(it8, Value);
+ else
+ p->Value = NULL;
+
+ p->Next = NULL;
+
+ /* Keep the container in our list */
+ if (*Head == NULL)
+ *Head = p;
+ else
+ last->Next = p;
+
+ return true;
+}
+
+static
+BOOL AddAvailableProperty(LPIT8 it8, const char* Key)
+{
+ return AddToList(it8, &it8->ValidKeywords, Key, NULL);
+}
+
+
+static
+BOOL AddAvailableSampleID(LPIT8 it8, const char* Key)
+{
+ return AddToList(it8, &it8->ValidSampleID, Key, NULL);
+}
+
+
+
+/* Init an empty container */
+LCMSHANDLE cmsxIT8Alloc(void)
+{
+ LPIT8 it8;
+ int i;
+
+ it8 = (LPIT8) malloc(sizeof(IT8));
+ if (it8 == NULL) return NULL;
+
+ ZeroMemory(it8, sizeof(IT8));
+
+ it8->HeaderList = NULL;
+ it8->FileBuffer = NULL;
+ it8->DataFormat = NULL;
+ it8->Data = NULL;
+ it8->MemorySink = NULL;
+ it8->ValidKeywords = NULL;
+ it8->ValidSampleID = NULL;
+
+ it8 -> sy = SNONE;
+ it8 -> ch = ' ';
+ it8 -> Source = NULL;
+ it8 -> inum = 0;
+ it8 -> dnum = 0.0;
+
+ it8 -> lineno = 1;
+
+ strcpy(it8->SheetType, "IT8.7/2");
+
+ /* Initialize predefined properties & data */
+
+ for (i=0; i < NUMPREDEFINEDPROPS; i++)
+ AddAvailableProperty(it8, PredefinedProperties[i]);
+
+ for (i=0; i < NUMPREDEFINEDSAMPLEID; i++)
+ AddAvailableSampleID(it8, PredefinedSampleID[i]);
+
+
+ return (LCMSHANDLE) it8;
+}
+
+
+const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+
+ return it8 ->SheetType;
+
+}
+
+BOOL cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+
+ strncpy(it8 ->SheetType, Type, MAXSTR-1);
+ return true;
+}
+
+
+
+/* Sets a property */
+BOOL cmsxIT8SetProperty(LCMSHANDLE hIT8, const char* Key, const char *Val)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+
+ if (!Val) return false;
+ if (!*Val) return false;
+
+ return AddToList(it8, &it8 -> HeaderList, Key, Val);
+}
+
+
+BOOL cmsxIT8SetPropertyDbl(LCMSHANDLE hIT8, const char* cProp, double Val)
+{
+ char Buffer[256];
+
+ sprintf(Buffer, "%g", Val);
+ return cmsxIT8SetProperty(hIT8, cProp, Buffer);
+}
+
+/* Gets a property */
+const char* cmsxIT8GetProperty(LCMSHANDLE hIT8, const char* Key)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+ LPKEYVALUE p;
+
+ if (IsAvailableOnList(it8 -> HeaderList, Key, &p))
+ {
+ return p -> Value;
+ }
+ return NULL;
+}
+
+
+double cmsxIT8GetPropertyDbl(LCMSHANDLE hIT8, const char* cProp)
+{
+ const char *v = cmsxIT8GetProperty(hIT8, cProp);
+ if (v) return atof(v);
+ else return 0.0;
+}
+
+/* ----------------------------------------------------------------- Datasets */
+
+
+static
+void AllocateDataFormat(LPIT8 it8)
+{
+ if (it8 -> DataFormat) return; /* Already allocated */
+
+ it8 -> nSamples = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
+
+ if (it8 -> nSamples <= 0) {
+
+ cmsSignalError(LCMS_ERRC_WARNING, "AllocateDataFormat: Unknown NUMBER_OF_FIELDS, assuming 10");
+ it8 -> nSamples = 10;
+ }
+
+ it8 -> DataFormat = (char**) AllocChunk (it8, (it8->nSamples + 1) * sizeof(char *));
+ if (it8->DataFormat == NULL)
+ {
+ cmsSignalError(LCMS_ERRC_ABORTED, "AllocateDataFormat: Unable to allocate dataFormat array");
+ }
+
+}
+
+static
+const char *GetDataFormat(LPIT8 it8, int n)
+{
+ if (it8->DataFormat)
+ return it8->DataFormat[n];
+
+ return NULL;
+}
+
+static
+BOOL SetDataFormat(LPIT8 it8, int n, const char *label)
+{
+ if (n > it8 -> nSamples) return false;
+
+ if (!it8->DataFormat)
+ AllocateDataFormat(it8);
+
+ if (it8->DataFormat) {
+
+ it8->DataFormat[n] = AllocString(it8, label);
+ }
+
+ return true;
+}
+
+
+BOOL cmsxIT8SetDataFormat(LCMSHANDLE h, int n, const char *Sample)
+{
+ LPIT8 it8 = (LPIT8) h;
+ return SetDataFormat(it8, n, Sample);
+}
+
+static
+void AllocateDataSet(LPIT8 it8)
+{
+ if (it8 -> Data) return; /* Already allocated */
+
+ it8-> nSamples = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
+ it8-> nPatches = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_SETS"));
+ it8-> Data = (char**)AllocChunk (it8, (it8->nSamples + 1) * (it8->nPatches + 1) *sizeof (char*));
+ if (it8->Data == NULL)
+ {
+ cmsSignalError(-1, "AllocateDataSet: Unable to allocate data array");
+ }
+
+}
+
+static
+char* GetData(LPIT8 it8, int nSet, int nField)
+{
+ int nSamples = it8 -> nSamples;
+ int nPatches = it8 -> nPatches;
+
+ if (nSet >= nPatches || nField >= nSamples)
+ return NULL;
+
+ if (!it8->Data) return NULL;
+ return it8->Data [nSet * nSamples + nField];
+}
+
+static
+BOOL SetData(LPIT8 it8, int nSet, int nField, char *Val)
+{
+ if (!it8->Data)
+ AllocateDataSet(it8);
+
+ if (!it8->Data) return false;
+
+
+ if (nSet > it8 -> nPatches) {
+
+ SynError(it8, "Patch %d out of range, there are %d datasets", nSet, it8 -> nPatches);
+ return false;
+ }
+
+ if (nField > it8 ->nSamples) {
+ SynError(it8, "Sample %d out of range, there are %d datasets", nField, it8 ->nSamples);
+ return false;
+ }
+
+
+ it8->Data [nSet * it8 -> nSamples + nField] = AllocString(it8, Val);
+ return true;
+}
+
+
+/* --------------------------------------------------------------- File I/O */
+
+
+/* Writes a string to file */
+static
+void WriteStr(FILE *f, char *str)
+{
+ if (str == NULL)
+ fwrite(" ", 1, 1, f);
+ else
+ fwrite(str, 1, strlen(str), f);
+}
+
+
+/* Writes full header */
+static
+void WriteHeader(LPIT8 it8, FILE *fp)
+{
+ LPKEYVALUE p;
+
+ WriteStr(fp, it8->SheetType);
+ WriteStr(fp, "\n");
+ for (p = it8->HeaderList; (p != NULL); p = p->Next)
+ {
+ if (!IsAvailableOnList(it8-> ValidKeywords, p->Keyword, NULL)) {
+
+ WriteStr(fp, "KEYWORD\t\"");
+ WriteStr(fp, p->Keyword);
+ WriteStr(fp, "\"\n");
+
+ }
+
+ WriteStr(fp, p->Keyword);
+ if (p->Value) {
+
+ WriteStr(fp, "\t\"");
+ WriteStr(fp, p->Value);
+ WriteStr(fp, "\"");
+ }
+ WriteStr (fp, "\n");
+ }
+
+}
+
+
+/* Writes the data format */
+static
+void WriteDataFormat(FILE *fp, LPIT8 it8)
+{
+ int i, nSamples;
+
+ if (!it8 -> DataFormat) return;
+
+ WriteStr(fp, "BEGIN_DATA_FORMAT\n");
+ nSamples = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
+
+ for (i = 0; i < nSamples; i++) {
+
+ WriteStr(fp, it8->DataFormat[i]);
+ WriteStr(fp, (char*)((i == (nSamples-1)) ? "\n" : "\t")); // C->C++ : cast
+ }
+
+ WriteStr (fp, "END_DATA_FORMAT\n");
+}
+
+
+/* Writes data array */
+static
+void WriteData(FILE *fp, LPIT8 it8)
+{
+ int i, j;
+
+ if (!it8->Data) return;
+
+ WriteStr (fp, "BEGIN_DATA\n");
+
+ it8->nPatches = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_SETS"));
+
+ for (i = 0; i < it8-> nPatches; i++) {
+
+ for (j = 0; j < it8->nSamples; j++) {
+
+ char *ptr = it8->Data[i*it8->nSamples+j];
+
+ WriteStr(fp, (char*)((ptr == NULL) ? "0.00" : ptr)); // C->C++ : cast
+ WriteStr(fp, (char*)((j == (it8->nSamples-1)) ? "\n" : "\t")); // C->C++ : cast
+ }
+ }
+ WriteStr (fp, "END_DATA\n");
+}
+
+
+
+/* Saves whole file */
+BOOL cmsxIT8SaveToFile(LCMSHANDLE hIT8, const char* cFileName)
+{
+ FILE *fp;
+ LPIT8 it8 = (LPIT8) hIT8;
+
+ fp = fopen(cFileName, "wt");
+ if (!fp) return false;
+ WriteHeader(it8, fp);
+ WriteDataFormat(fp, it8);
+ WriteData(fp, it8);
+ fclose(fp);
+
+ return true;
+}
+
+
+/* Reads whole file in a memory block */
+static
+BOOL ReadFileInMemory(const char *cFileName, char **Buffer, size_t *Len)
+{
+ FILE *fp;
+ size_t Size;
+ char *Ptr;
+
+ fp = fopen(cFileName, "rt");
+ if (!fp) return false;
+
+ Size = xfilelength(fileno(fp));
+ if (Size <= 0) {
+ fclose(fp);
+ return false;
+ }
+
+ Ptr = (char*)malloc(Size+1); // C->C++ : cast
+
+ Size = fread(Ptr, 1, Size, fp);
+ fclose(fp);
+ Ptr[Size] = '\0';
+
+ *Buffer = Ptr;
+ *Len = Size;
+ return true;
+}
+
+
+/* -------------------------------------------------------------- Higer lever parsing */
+
+static
+BOOL DataFormatSection(LPIT8 it8)
+{
+ int iField = 0;
+ BOOL Ignoring = false;
+
+ InSymbol(it8); /* Eats "BEGIN_DATA_FORMAT" */
+ CheckEOLN(it8);
+
+ while (it8->sy != SEND_DATA_FORMAT &&
+ it8->sy != SEOLN &&
+ it8->sy != SEOF &&
+ it8->sy != SSYNERROR)
+ {
+
+ if (it8->sy != SIDENT) {
+
+ cmsSignalError(LCMS_ERRC_ABORTED, "Sample type expected");
+ it8->sy = SSYNERROR;
+ return false;
+ }
+
+ if (!Ignoring && iField > it8->nSamples) {
+ cmsSignalError(LCMS_ERRC_WARNING, "More than NUMBER_OF_FIELDS fields. Extra is ignored\n");
+ Ignoring = true;
+ }
+ else {
+ if (!SetDataFormat(it8, iField, it8->id)) return false;
+ iField++;
+ }
+
+ InSymbol(it8);
+ Skip(it8, SEOLN);
+ }
+
+ Skip(it8, SEOLN);
+ Skip(it8, SEND_DATA_FORMAT);
+ Skip(it8, SEOLN);
+ return true;
+}
+
+
+
+static
+BOOL DataSection (LPIT8 it8)
+{
+ int iField = 0;
+ int iSet = 0;
+ char Buffer[256];
+
+ InSymbol(it8); /* Eats "BEGIN_DATA" */
+ CheckEOLN(it8);
+
+ while (it8->sy != SEND_DATA && it8->sy != SEOF)
+ {
+ if (iField >= it8 -> nSamples) {
+ iField = 0;
+ iSet++;
+ if (!CheckEOLN(it8))
+ return false;
+ }
+
+ if (it8->sy != SEND_DATA && it8->sy != SEOF) {
+
+ if (!GetVal(it8, Buffer))
+ return false;
+
+ if (!SetData(it8, iSet, iField, Buffer))
+ return false;
+
+ iField++;
+
+ Skip(it8, SEOLN);
+ InSymbol(it8);
+ }
+ }
+
+ Skip(it8, SEOLN);
+ Skip(it8, SEND_DATA);
+ Skip(it8, SEOLN);
+ return true;
+}
+
+
+
+
+
+
+static
+BOOL HeaderSection (LPIT8 it8)
+{
+ char VarName[MAXID];
+ char Buffer[MAXSTR];
+
+ while (it8->sy != SEOF &&
+ it8->sy != SSYNERROR &&
+ it8->sy != SBEGIN_DATA_FORMAT &&
+ it8->sy != SBEGIN_DATA) {
+
+
+ switch (it8 -> sy) {
+
+ case SKEYWORD:
+ InSymbol(it8);
+ if (!Check(it8, SSTRING, "Keyword expected")) return false;
+ if (!AddAvailableProperty(it8, it8 -> str)) return false;
+ InSymbol(it8);
+ break;
+
+
+ case SIDENT:
+ strncpy(VarName, it8->id, MAXID-1);
+ if (!IsAvailableOnList(it8-> ValidKeywords, VarName, NULL))
+ return SynError(it8, "Undefined keyword '%s'", VarName);
+
+ InSymbol(it8);
+ GetVal(it8, Buffer);
+ cmsxIT8SetProperty((LCMSHANDLE) it8, VarName, Buffer);
+ InSymbol(it8);
+ break;
+
+
+ case SEOLN: break;
+
+ default:
+ return SynError(it8, "expected keyword or identifier");
+ }
+
+ Skip(it8, SEOLN);
+ }
+
+ return true;
+
+}
+
+
+static
+BOOL ParseIT8(LPIT8 it8)
+{
+
+ InSymbol(it8);
+
+ if (it8->sy == SIDENT) {
+
+ strncpy(it8->SheetType, it8->id, MAXSTR-1);
+ InSymbol(it8);
+
+ /* if (!AddAvailableProperty(it8, it8 -> id)) return false; */
+ /* cmsxIT8SetProperty((LCMSHANDLE) it8, it8->id, NULL); */
+ }
+
+ Skip(it8, SEOLN);
+
+ while (it8-> sy != SEOF &&
+ it8-> sy != SSYNERROR) {
+
+ switch (it8 -> sy) {
+
+ case SBEGIN_DATA_FORMAT:
+ if (!DataFormatSection(it8)) return false;
+ break;
+
+ case SBEGIN_DATA:
+ if (!DataSection(it8)) return false;
+ break;
+
+ case SEOLN:
+ Skip(it8, SEOLN);
+ break;
+
+ default:
+ if (!HeaderSection(it8)) return false;
+ }
+
+ }
+
+ return true;
+}
+
+
+static
+void CleanPatchName(char *cell)
+{
+ char cleaned[256], Buffer[256], ident[256];
+ char *orig = cell, *id;
+ int n, lOneNum;
+
+
+ id = ident;
+ while (*cell && isalpha(*cell))
+ {
+ *id++ = (char) toupper(*cell);
+ cell++;
+ }
+ *id = 0;
+ strcpy(cleaned, ident);
+
+
+ n = 0;
+ lOneNum = false;
+ while (*cell && isdigit(*cell))
+ {
+ n = n * 10 + (*cell -'0');
+ cell++;
+ lOneNum = true;
+ }
+
+ if (lOneNum) {
+
+ sprintf(Buffer, "%d", n);
+ strcat(cleaned, Buffer);
+ }
+
+ if (strcmp(cleaned, "GS0") == 0)
+ strcpy(orig, "DMIN");
+ else
+ if (strcmp(cleaned, "GS23") == 0)
+ strcpy(orig, "DMAX");
+ else
+ strcpy(orig, cleaned);
+}
+
+
+/* Init useful pointers */
+
+static
+void CookPointers(LPIT8 it8)
+{
+ int idField, i;
+ char* Fld;
+
+ it8 -> SampleID = 0;
+ for (idField = 0; idField < it8 -> nSamples; idField++)
+ {
+ Fld = it8->DataFormat[idField];
+ if (!Fld) continue;
+
+ if (strcmp(Fld, "SAMPLE_ID") == 0) {
+ it8 -> SampleID = idField;
+
+
+ for (i=0; i < it8 -> nPatches; i++) {
+
+ char *Data = GetData(it8, i, idField);
+ if (Data) {
+ char Buffer[256];
+
+ strncpy(Buffer, Data, 255);
+ CleanPatchName(Buffer);
+
+ if (strlen(Buffer) <= strlen(Data))
+ strcpy(Data, Buffer);
+ else
+ SetData(it8, i, idField, Buffer);
+
+ }
+ }
+
+ }
+ }
+
+}
+
+
+/* ---------------------------------------------------------- Exported routines */
+
+
+LCMSHANDLE cmsxIT8LoadFromMem(void *Ptr, size_t len)
+{
+ LCMSHANDLE hIT8 = cmsxIT8Alloc();
+ LPIT8 it8 = (LPIT8) hIT8;
+
+ if (!hIT8) return NULL;
+ it8 ->FileBuffer = (char*) malloc(len + 1);
+
+ strncpy(it8 ->FileBuffer, (const char*)Ptr, len); // C->C++ : cast
+ strncpy(it8->FileName, "", MAX_PATH-1);
+ it8-> Source = it8 -> FileBuffer;
+
+ ParseIT8(it8);
+ CookPointers(it8);
+
+ free(it8->FileBuffer);
+ it8 -> FileBuffer = NULL;
+
+ return hIT8;
+
+
+}
+
+
+LCMSHANDLE cmsxIT8LoadFromFile(const char* cFileName)
+{
+
+ LCMSHANDLE hIT8 = cmsxIT8Alloc();
+ LPIT8 it8 = (LPIT8) hIT8;
+ size_t Len;
+
+ if (!hIT8) return NULL;
+ if (!ReadFileInMemory(cFileName, &it8->FileBuffer, &Len)) return NULL;
+
+ strncpy(it8->FileName, cFileName, MAX_PATH-1);
+ it8-> Source = it8 -> FileBuffer;
+
+ ParseIT8(it8);
+ CookPointers(it8);
+
+ free(it8->FileBuffer);
+ it8 -> FileBuffer = NULL;
+
+ return hIT8;
+
+}
+
+int cmsxIT8EnumDataFormat(LCMSHANDLE hIT8, char ***SampleNames)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+
+ *SampleNames = it8 -> DataFormat;
+ return it8 -> nSamples;
+}
+
+
+int cmsxIT8EnumProperties(LCMSHANDLE hIT8, char ***PropertyNames)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+ LPKEYVALUE p;
+ int n;
+ char **Props;
+
+ /* Pass#1 - count properties */
+
+ n = 0;
+ for (p = it8 -> HeaderList; p != NULL; p = p->Next) {
+ n++;
+ }
+
+
+ Props = (char **) malloc(sizeof(char *) * n);
+
+ /* Pass#2 - Fill pointers */
+ n = 0;
+ for (p = it8 -> HeaderList; p != NULL; p = p->Next) {
+ Props[n++] = p -> Keyword;
+ }
+
+ *PropertyNames = Props;
+ return n;
+}
+
+static
+int LocatePatch(LPIT8 it8, const char* cPatch)
+{
+ int i;
+ const char *data;
+
+ for (i=0; i < it8-> nPatches; i++) {
+
+ data = GetData(it8, i, it8->SampleID);
+
+ if (data != NULL) {
+
+ if (stricmp(data, cPatch) == 0)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+
+static
+int LocateEmptyPatch(LPIT8 it8, const char* cPatch)
+{
+ int i;
+ const char *data;
+
+ for (i=0; i < it8-> nPatches; i++) {
+
+ data = GetData(it8, i, it8->SampleID);
+
+ if (data == NULL)
+ return i;
+
+ }
+
+ return -1;
+}
+
+static
+int LocateSample(LPIT8 it8, const char* cSample)
+{
+ int i;
+ const char *fld;
+
+ for (i=0; i < it8->nSamples; i++) {
+
+ fld = GetDataFormat(it8, i);
+ if (stricmp(fld, cSample) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+
+BOOL cmsxIT8GetDataSetByPos(LCMSHANDLE hIT8, int col, int row, char* Val, int ValBufferLen)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+ const char *data = GetData(it8, row, col);
+
+ if (!data)
+ {
+ *Val = '\0';
+ return false;
+ }
+
+ strncpy(Val, data, ValBufferLen-1);
+ return true;
+}
+
+
+
+BOOL cmsxIT8GetDataSet(LCMSHANDLE hIT8, const char* cPatch,
+ const char* cSample,
+ char* Val, int ValBuffLen)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+ int iField, iSet;
+
+
+ iField = LocateSample(it8, cSample);
+ if (iField < 0) {
+ /* cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find data field %s\n", cSample); */
+ return false;
+ }
+
+
+ iSet = LocatePatch(it8, cPatch);
+ if (iSet < 0) {
+
+ /* cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find patch '%s'\n", cPatch); */
+ return false;
+ }
+
+ strncpy(Val, GetData(it8, iSet, iField), ValBuffLen-1);
+ return true;
+}
+
+
+BOOL cmsxIT8GetDataSetDbl(LCMSHANDLE it8, const char* cPatch, const char* cSample, double* v)
+{
+ char Buffer[20];
+
+ if (cmsxIT8GetDataSet(it8, cPatch, cSample, Buffer, 20)) {
+
+ *v = atof(Buffer);
+ return true;
+ } else
+ return false;
+}
+
+
+
+BOOL cmsxIT8SetDataSet(LCMSHANDLE hIT8, const char* cPatch,
+ const char* cSample,
+ char *Val)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+ int iField, iSet;
+
+
+ iField = LocateSample(it8, cSample);
+
+ if (iField < 0) {
+
+ cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find data field %s\n", cSample);
+ return false;
+ }
+
+
+ if (it8-> nPatches == 0) {
+
+ AllocateDataFormat(it8);
+ AllocateDataSet(it8);
+ CookPointers(it8);
+ }
+
+
+ if (stricmp(cSample, "SAMPLE_ID") == 0)
+ {
+
+ iSet = LocateEmptyPatch(it8, cPatch);
+ if (iSet < 0) {
+ cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't add more patches '%s'\n", cPatch);
+ return false;
+ }
+ iField = it8 -> SampleID;
+ }
+ else {
+ iSet = LocatePatch(it8, cPatch);
+ if (iSet < 0) {
+
+ cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find patch '%s'\n", cPatch);
+ return false;
+ }
+ }
+
+ return SetData(it8, iSet, iField, Val);
+}
+
+
+BOOL cmsxIT8SetDataSetDbl(LCMSHANDLE hIT8, const char* cPatch,
+ const char* cSample,
+ double Val)
+{
+ char Buff[256];
+
+ sprintf(Buff, "%g", Val);
+ return cmsxIT8SetDataSet(hIT8, cPatch, cSample, Buff);
+
+}
+
+
+const char* cmsxIT8GetPatchName(LCMSHANDLE hIT8, int nPatch, char* buffer)
+{
+ LPIT8 it8 = (LPIT8) hIT8;
+ char* Data = GetData(it8, nPatch, it8->SampleID);
+ if (!Data) return NULL;
+
+ strcpy(buffer, Data);
+ return buffer;
+}
+
+
+const char* cmsxIT8GenericPatchName(int nPatch, char* buffer)
+{
+ int row, col;
+
+ if (nPatch >= cmsxIT8_NORMAL_PATCHES)
+ return "$CUSTOM";
+
+ if (nPatch >= (cmsxIT8_ROWS * cmsxIT8_COLS)) {
+
+ nPatch -= cmsxIT8_ROWS * cmsxIT8_COLS;
+ if (nPatch == 0)
+ return "DMIN";
+ else
+ if (nPatch == cmsxIT8_GRAYCOLS - 1)
+ return "DMAX";
+ else
+ sprintf(buffer, "GS%d", nPatch);
+ return buffer;
+ }
+
+
+ row = nPatch / cmsxIT8_COLS;
+ col = nPatch % cmsxIT8_COLS;
+
+ sprintf (buffer, "%c%d", 'A'+row, col+1);
+ return buffer;
+}
+
+
+
+
+int cmsxIT8GenericPatchNum(const char *name)
+{
+ int i;
+ char Buff[256];
+
+
+ for (i=0; i < cmsxIT8_TOTAL_PATCHES; i++)
+ if (stricmp(cmsxIT8GenericPatchName(i, Buff), name) == 0)
+ return i;
+
+ return -1;
+}
+
+
+
+
+
diff --git a/src/libs/lprof/lcmsprf.h b/src/libs/lprof/lcmsprf.h
new file mode 100644
index 00000000..00c7ac40
--- /dev/null
+++ b/src/libs/lprof/lcmsprf.h
@@ -0,0 +1,485 @@
+/*
+Little cms - profiler construction set
+Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com>
+
+THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
+LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+OF THIS SOFTWARE.
+
+This file is free software; you can redistribute it and/or modify it
+under the terms of the 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+As a special exception to the GNU General Public License, if you
+distribute this file as part of a program that contains a
+configuration script generated by Autoconf, you may include it under
+the same distribution terms that you use for the rest of that program.
+*/
+
+/* Version 1.09a */
+
+#ifndef __cmsprf_H
+
+#include <config.h>
+#include LCMS_HEADER
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef NON_WINDOWS
+# ifndef stricmp
+# define stricmp strcasecmp
+# endif
+#endif
+
+#ifndef max
+#define max(a,b) ((a) > (b)?(a):(b))
+#endif
+
+
+/* Matrix operations - arbitrary size ----------------------------------------------------- */
+
+typedef struct {
+
+ int Cols, Rows;
+ double** Values;
+
+ } MATN,FAR* LPMATN;
+
+// See B.K.O #148930: compile with lcms v.1.17
+#if (LCMS_VERSION > 116)
+typedef LCMSBOOL BOOL;
+#endif
+
+LPMATN cdecl MATNalloc(int Rows, int Cols);
+void cdecl MATNfree (LPMATN mat);
+LPMATN cdecl MATNmult(LPMATN a1, LPMATN a2);
+double cdecl MATNcross(LPMATN a);
+void cdecl MATNscalar (LPMATN a, double scl, LPMATN b);
+LPMATN cdecl MATNtranspose (LPMATN a);
+BOOL cdecl MATNsolve(LPMATN a, LPMATN b);
+
+
+/* IT8.7 / CGATS.17-200x handling -------------------------------------------------------- */
+
+#define cmsxIT8_ROWS 12
+#define cmsxIT8_COLS 22
+#define cmsxIT8_GRAYCOLS 24
+#define cmsxIT8_NORMAL_PATCHES (cmsxIT8_ROWS*cmsxIT8_COLS + cmsxIT8_GRAYCOLS)
+#define cmsxIT8_CUSTOM_PATCHES 10
+#define cmsxIT8_TOTAL_PATCHES (cmsxIT8_NORMAL_PATCHES + cmsxIT8_CUSTOM_PATCHES)
+
+
+LCMSHANDLE cdecl cmsxIT8Alloc(void);
+void cdecl cmsxIT8Free(LCMSHANDLE cmsxIT8);
+LCMSHANDLE cdecl cmsxIT8LoadFromFile(const char* cFileName);
+LCMSHANDLE cdecl cmsxIT8LoadFromMem(void *Ptr, size_t len);
+BOOL cdecl cmsxIT8SaveToFile(LCMSHANDLE cmsxIT8, const char* cFileName);
+const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8);
+BOOL cdecl cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type);
+const char* cdecl cmsxIT8GetPatchName(LCMSHANDLE hIT8, int nPatch, char* buffer);
+BOOL cdecl cmsxIT8SetProperty(LCMSHANDLE hcmsxIT8, const char* cProp, const char *Str);
+BOOL cdecl cmsxIT8SetPropertyDbl(LCMSHANDLE hcmsxIT8, const char* cProp, double Val);
+const char* cdecl cmsxIT8GetProperty(LCMSHANDLE hcmsxIT8, const char* cProp);
+double cdecl cmsxIT8GetPropertyDbl(LCMSHANDLE hcmsxIT8, const char* cProp);
+int cdecl cmsxIT8EnumProperties(LCMSHANDLE cmsxIT8, char ***PropertyNames);
+int cdecl cmsxIT8EnumDataFormat(LCMSHANDLE cmsxIT8, char ***SampleNames);
+BOOL cdecl cmsxIT8SetDataFormat(LCMSHANDLE cmsxIT8, int n, const char *Sample);
+
+BOOL cdecl cmsxIT8GetDataSetByPos(LCMSHANDLE IT8, int col, int row, char* Val, int ValBufferLen);
+
+BOOL cdecl cmsxIT8GetDataSet(LCMSHANDLE cmsxIT8, const char* cPatch,
+ const char* cSample,
+ char* Val, int ValBuffLen);
+
+BOOL cdecl cmsxIT8GetDataSetDbl(LCMSHANDLE cmsxIT8, const char* cPatch, const char* cSample, double* v);
+
+BOOL cdecl cmsxIT8SetDataSet(LCMSHANDLE cmsxIT8, const char* cPatch,
+ const char* cSample,
+ char *Val);
+
+BOOL cdecl cmsxIT8SetDataSetDbl(LCMSHANDLE cmsxIT8, const char* cPatch, const char* cSample, double Val);
+
+const char *cdecl cmsxIT8GenericPatchName(int nPatch, char* buffer);
+
+
+
+/* Patch collections (measurement lists) -------------------------------------------------- */
+
+#define PATCH_HAS_Lab 0x00000001
+#define PATCH_HAS_XYZ 0x00000002
+#define PATCH_HAS_RGB 0x00000004
+#define PATCH_HAS_CMY 0x00000008
+#define PATCH_HAS_CMYK 0x00000010
+#define PATCH_HAS_HEXACRM 0x00000020
+#define PATCH_HAS_STD_Lab 0x00010000
+#define PATCH_HAS_STD_XYZ 0x00020000
+#define PATCH_HAS_XYZ_PROOF 0x00100000
+#define PATCH_HAS_MEAN_DE 0x01000000
+#define PATCH_HAS_STD_DE 0x02000000
+#define PATCH_HAS_CHISQ 0x04000000
+
+
+#define MAXPATCHNAMELEN 20
+/* A patch in memory */
+
+typedef struct {
+
+ DWORD dwFlags; /* Is quite possible to have colorant in only */
+ /* some patches of sheet, so mark each entry with */
+ /* the values it has. */
+
+ char Name[MAXPATCHNAMELEN];
+
+ cmsCIELab Lab; /* The tristimulus values of target */
+ cmsCIEXYZ XYZ;
+
+ cmsCIEXYZ XYZProof; /* The absolute XYZ value returned by profile */
+ /* (gamut constrained to device) */
+
+ union { /* The possible colorants. Only one space is */
+ /* allowed...obviously only one set of */
+ /* device-dependent values per patch does make sense. */
+ double RGB[3];
+ double CMY[3];
+ double CMYK[4];
+ double Hexa[MAXCHANNELS];
+
+ } Colorant;
+
+ double dEStd; /* Standard deviation */
+ double ChiSq; /* Chi-square parameter (mean of STD of colorants) */
+ double dEMean; /* Mean dE */
+
+ } PATCH, FAR* LPPATCH;
+
+
+
+/* A set of patches is simply an array of bools, TRUE if the patch */
+/* belong to the set, false otherwise. */
+
+typedef BOOL* SETOFPATCHES;
+
+/* This struct holds whole Patches collection */
+
+typedef struct _measurement {
+
+ int nPatches;
+ LPPATCH Patches;
+ SETOFPATCHES Allowed;
+
+ } MEASUREMENT,FAR *LPMEASUREMENT;
+
+
+void cdecl cmsxPCollFreeMeasurements(LPMEASUREMENT m);
+SETOFPATCHES cdecl cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault);
+
+BOOL cdecl cmsxPCollBuildMeasurement(LPMEASUREMENT m,
+ const char *ReferenceSheet,
+ const char *MeasurementSheet,
+ DWORD dwNeededSamplesType);
+
+int cdecl cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set);
+BOOL cdecl cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags);
+
+BOOL cdecl cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet);
+BOOL cdecl cmsxPCollSaveToSheet(LPMEASUREMENT m, LCMSHANDLE it8);
+
+LPPATCH cdecl cmsxPCollGetPatch(LPMEASUREMENT m, int n);
+LPPATCH cdecl cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* Name, int* lpPos);
+LPPATCH cdecl cmsxPCollGetPatchByPos(LPMEASUREMENT m, int row, int col);
+LPPATCH cdecl cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name,
+ double r, double g, double b,
+ LPcmsCIEXYZ XYZ, LPcmsCIELab Lab);
+
+void cdecl cmsxPCollLinearizePatches(LPMEASUREMENT m, SETOFPATCHES Valids,
+ LPGAMMATABLE Gamma[3]);
+
+/* Extraction utilities */
+
+/* Collect "need" patches of the specific kind, return the number of collected (that */
+/* could be less if set of patches is exhausted) */
+
+void cdecl cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double r, double g, double b, int need, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids,
+ int need, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesNearPrimary(LPMEASUREMENT m, SETOFPATCHES Valids,
+ int nChannel, int need, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids,
+ double Lmin, double LMax, double a, double b, SETOFPATCHES Result);
+
+int cdecl cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids,
+ LPLUT Gamut, SETOFPATCHES Result);
+
+/* Find important values */
+
+LPPATCH cdecl cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
+LPPATCH cdecl cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
+LPPATCH cdecl cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* Distance);
+
+/* Multiple linear regression stuff ---------------------------------------- */
+
+
+/* A measurement of error */
+
+typedef struct {
+
+ double SSE; /* The error sum of squares */
+ double MSE; /* The error mean sum of squares */
+ double SSR; /* The regression sum of squares */
+ double MSR; /* The regression mean sum of squares */
+ double SSTO; /* Total sum of squares */
+ double F; /* The Fisher-F value (MSR / MSE) */
+ double R2; /* Proportion of variability explained by the regression */
+ /* (root is Pearson correlation coefficient) */
+
+ double R2adj; /* The adjusted coefficient of multiple determination. */
+ /* R2-adjusted or R2adj. This is calculated as */
+ /* R2adj = 1 - (1-R2)(N-n-1)/(N-1) */
+ /* and used as multiple correlation coefficient */
+ /* (really, it should be square root) */
+
+ } MLRSTATISTICS, FAR* LPMLRSTATISTICS;
+
+
+int cdecl cmsxRegressionCreateMatrix(LPMEASUREMENT m, SETOFPATCHES Allowed, int nterms,
+ int ColorSpace,
+ LPMATN* lpMat, LPMLRSTATISTICS Stat);
+
+BOOL cdecl cmsxRegressionRGB2Lab(double r, double g, double b,
+ LPMATN tfm, LPcmsCIELab Lab);
+
+BOOL cdecl cmsxRegressionRGB2XYZ(double r, double g, double b,
+ LPMATN tfm, LPcmsCIEXYZ XYZ);
+
+BOOL cdecl cmsxRegressionInterpolatorRGB(LPMEASUREMENT m,
+ int ColorSpace,
+ int RegressionTerms,
+ BOOL lUseLocalPatches,
+ int MinPatchesToCollect,
+ double r, double g, double b,
+ void* Res);
+
+
+
+/* Levenberg-Marquardt ---------------------------------------------------------------------- */
+
+LCMSHANDLE cdecl cmsxLevenbergMarquardtInit(LPSAMPLEDCURVE x, LPSAMPLEDCURVE y, double sig,
+ double a[],
+ int ma,
+ void (*funcs)(double, double[], double*, double[], int)
+ );
+
+double cdecl cmsxLevenbergMarquardtAlamda(LCMSHANDLE hMRQ);
+double cdecl cmsxLevenbergMarquardtChiSq(LCMSHANDLE hMRQ);
+BOOL cdecl cmsxLevenbergMarquardtIterate(LCMSHANDLE hMRQ);
+BOOL cdecl cmsxLevenbergMarquardtFree(LCMSHANDLE hMRQ);
+
+
+/* Convex hull geometric routines ------------------------------------------------------------ */
+
+LCMSHANDLE cdecl cmsxHullInit(void);
+void cdecl cmsxHullDone(LCMSHANDLE hHull);
+BOOL cdecl cmsxHullAddPoint(LCMSHANDLE hHull, int x, int y, int z);
+BOOL cdecl cmsxHullComputeHull(LCMSHANDLE hHull);
+char cdecl cmsxHullCheckpoint(LCMSHANDLE hHull, int x, int y, int z);
+BOOL cdecl cmsxHullDumpVRML(LCMSHANDLE hHull, const char* fname);
+
+
+/* Linearization ---------------------------------------------------------------------------- */
+
+
+#define MEDIUM_REFLECTIVE_D50 0 /* Used for scanner targets */
+#define MEDIUM_TRANSMISSIVE 1 /* Used for monitors & projectors */
+
+void cdecl cmsxComputeLinearizationTables(LPMEASUREMENT m,
+ int ColorSpace,
+ LPGAMMATABLE Lin[3],
+ int nResultingPoints,
+ int Medium);
+
+
+void cdecl cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium);
+LPGAMMATABLE cdecl cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints);
+
+void cdecl cmsxApplyLinearizationTable(double In[3], LPGAMMATABLE Gamma[3], double Out[3]);
+void cdecl cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3]);
+
+/* Support routines ---------------------------------------------------------------------- */
+
+double cdecl _cmsxSaturate65535To255(double d);
+double cdecl _cmsxSaturate255To65535(double d);
+void cdecl _cmsxClampXYZ100(LPcmsCIEXYZ xyz);
+
+/* Matrix shaper profiler API ------------------------------------------------------------- */
+
+
+BOOL cdecl cmsxComputeMatrixShaper(const char* ReferenceSheet,
+ const char* MeasurementSheet,
+ int Medium,
+ LPGAMMATABLE TransferCurves[3],
+ LPcmsCIEXYZ WhitePoint,
+ LPcmsCIEXYZ BlackPoint,
+ LPcmsCIExyYTRIPLE Primaries);
+
+
+/* Common to all profilers ------------------------------------------------------------------- */
+
+#define MAX_STR 256
+
+typedef int (* cmsxGAUGER)(const char *Label, int nMin, int nMax, int Pos);
+typedef int (* cmsxPRINTF)(const char *Frm, ...);
+
+typedef struct {
+
+ /* Files */
+ char ReferenceSheet[MAX_PATH];
+ char MeasurementSheet[MAX_PATH];
+ char OutputProfileFile[MAX_PATH];
+
+ /* Some infos */
+ char Description[MAX_STR];
+ char Manufacturer[MAX_STR];
+ char Model[MAX_STR];
+ char Copyright[MAX_STR];
+
+ /* Callbacks */
+ cmsxGAUGER Gauger;
+ cmsxPRINTF printf;
+
+ /* EndPoints */
+ cmsCIEXYZ WhitePoint; /* Black point in 0.xxx notation */
+ cmsCIEXYZ BlackPoint; /* Black point in 0.xxx notation */
+ cmsCIExyYTRIPLE Primaries; /* The primaries */
+ LPGAMMATABLE Gamma[3]; /* Gamma curves */
+
+ /* Profile */
+ cmsHPROFILE hProfile; /* handle to profile */
+
+ icProfileClassSignature DeviceClass;
+ icColorSpaceSignature ColorSpace;
+
+ int PCSType; /* PT_XYZ or PT_Lab */
+ int CLUTPoints; /* Final CLUT resolution */
+ int ProfileVerbosityLevel; /* 0=minimum, 1=additional, 2=Verbose, 3=Any suitable */
+
+
+ /* Measurement */
+ MEASUREMENT m; /* Contains list of available patches */
+ int Medium;
+
+
+ /* RGB Gamut hull */
+ LCMSHANDLE hRGBHull; /* Contains bobbin of valid RGB values */
+
+ /* CIECAM97s */
+ BOOL lUseCIECAM97s; /* Use CIECAM97s for chromatic adaptation? */
+
+ cmsViewingConditions device; /* Viewing condition of source */
+ cmsViewingConditions PCS; /* Viewing condition of PCS */
+
+ LCMSHANDLE hDevice; /* CIECAM97s models used for adaptation */
+ LCMSHANDLE hPCS; /* and viewing conditions */
+
+
+ } PROFILERCOMMONDATA,FAR* LPPROFILERCOMMONDATA;
+
+
+/* Shared routines */
+
+BOOL cdecl cmsxEmbedCharTarget(LPPROFILERCOMMONDATA hdr);
+BOOL cdecl cmsxEmbedMatrixShaper(LPPROFILERCOMMONDATA hdr);
+BOOL cdecl cmsxEmbedTextualInfo(LPPROFILERCOMMONDATA hdr);
+
+int cdecl cmsxFindOptimumNumOfTerms(LPPROFILERCOMMONDATA hdr, int nMaxTerms, BOOL* lAllOk);
+void cdecl cmsxChromaticAdaptationAndNormalization(LPPROFILERCOMMONDATA hdr, LPcmsCIEXYZ xyz, BOOL lReverse);
+void cdecl cmsxInitPCSViewingConditions(LPPROFILERCOMMONDATA hdr);
+void cdecl cmsxComputeGamutHull(LPPROFILERCOMMONDATA hdr);
+BOOL cdecl cmsxChoosePCS(LPPROFILERCOMMONDATA hdr);
+
+/* Monitor profiler API ------------------------------------------------------------------- */
+
+typedef struct {
+
+ PROFILERCOMMONDATA hdr;
+
+
+ LPGAMMATABLE Prelinearization[3]; /* Canonic gamma */
+ LPGAMMATABLE ReverseTables[3]; /* Reverse (direct) gamma */
+ LPGAMMATABLE PreLab[3];
+ LPGAMMATABLE PreLabRev[3];
+
+
+ MAT3 PrimariesMatrix;
+ MAT3 PrimariesMatrixRev;
+
+
+ } MONITORPROFILERDATA,FAR* LPMONITORPROFILERDATA;
+
+
+
+BOOL cdecl cmsxMonitorProfilerInit(LPMONITORPROFILERDATA sys);
+BOOL cdecl cmsxMonitorProfilerDo(LPMONITORPROFILERDATA sys);
+
+
+/* Scanner profiler API ------------------------------------------------------------------- */
+
+
+typedef struct {
+
+ PROFILERCOMMONDATA hdr;
+
+ LPGAMMATABLE Prelinearization[3];
+
+ LPMATN HiTerms; /* Regression matrix of many terms */
+ LPMATN LoTerms; /* Low order regression matrix used for extrapolation */
+
+ BOOL lLocalConvergenceExtrapolation;
+
+
+ } SCANNERPROFILERDATA,FAR* LPSCANNERPROFILERDATA;
+
+
+
+
+BOOL cdecl cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys);
+BOOL cdecl cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys);
+
+/* ----------------------------------------------------------- end of profilers */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#define __cmsprf_H
+#endif
diff --git a/src/libs/sqlite2/Makefile.am b/src/libs/sqlite2/Makefile.am
new file mode 100644
index 00000000..dca86fe0
--- /dev/null
+++ b/src/libs/sqlite2/Makefile.am
@@ -0,0 +1,43 @@
+#stolen Makefile.am from amarok
+
+noinst_LTLIBRARIES = libsqlite2.la
+
+INCLUDES = $(all_includes)
+
+libsqlite2_la_CFLAGS = -w
+
+libsqlite2_la_LDFLAGS = $(LIBPTHREAD)
+
+libsqlite2_la_SOURCES = \
+ attach.c \
+ auth.c \
+ btree.c \
+ btree_rb.c \
+ build.c \
+ copy.c \
+ date.c \
+ delete.c \
+ encode.c \
+ expr.c \
+ func.c \
+ hash.c \
+ insert.c \
+ main.c \
+ opcodes.c \
+ os.c \
+ pager.c \
+ parse.c \
+ pragma.c \
+ printf.c \
+ random.c \
+ select.c \
+ shell.c \
+ table.c \
+ tokenize.c \
+ trigger.c \
+ update.c \
+ util.c \
+ vacuum.c \
+ vdbe.c \
+ vdbeaux.c \
+ where.c
diff --git a/src/libs/sqlite2/README b/src/libs/sqlite2/README
new file mode 100644
index 00000000..eefbd49e
--- /dev/null
+++ b/src/libs/sqlite2/README
@@ -0,0 +1,2 @@
+This folder contents sqlite version 2 source code used to backport old
+digiKam database < 0.8.0 to new database based on sqlite version 3 \ No newline at end of file
diff --git a/src/libs/sqlite2/attach.c b/src/libs/sqlite2/attach.c
new file mode 100644
index 00000000..316d0d2a
--- /dev/null
+++ b/src/libs/sqlite2/attach.c
@@ -0,0 +1,311 @@
+/*
+** 2003 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the ATTACH and DETACH commands.
+**
+** $Id: attach.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+
+/*
+** This routine is called by the parser to process an ATTACH statement:
+**
+** ATTACH DATABASE filename AS dbname
+**
+** The pFilename and pDbname arguments are the tokens that define the
+** filename and dbname in the ATTACH statement.
+*/
+void sqliteAttach(Parse *pParse, Token *pFilename, Token *pDbname, Token *pKey){
+ Db *aNew;
+ int rc, i;
+ char *zFile, *zName;
+ sqlite *db;
+ Vdbe *v;
+
+ v = sqliteGetVdbe(pParse);
+ sqliteVdbeAddOp(v, OP_Halt, 0, 0);
+ if( pParse->explain ) return;
+ db = pParse->db;
+ if( db->file_format<4 ){
+ sqliteErrorMsg(pParse, "cannot attach auxiliary databases to an "
+ "older format master database", 0);
+ pParse->rc = SQLITE_ERROR;
+ return;
+ }
+ if( db->nDb>=MAX_ATTACHED+2 ){
+ sqliteErrorMsg(pParse, "too many attached databases - max %d",
+ MAX_ATTACHED);
+ pParse->rc = SQLITE_ERROR;
+ return;
+ }
+
+ zFile = 0;
+ sqliteSetNString(&zFile, pFilename->z, pFilename->n, 0);
+ if( zFile==0 ) return;
+ sqliteDequote(zFile);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqliteAuthCheck(pParse, SQLITE_ATTACH, zFile, 0, 0)!=SQLITE_OK ){
+ sqliteFree(zFile);
+ return;
+ }
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+
+ zName = 0;
+ sqliteSetNString(&zName, pDbname->z, pDbname->n, 0);
+ if( zName==0 ) return;
+ sqliteDequote(zName);
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].zName && sqliteStrICmp(db->aDb[i].zName, zName)==0 ){
+ sqliteErrorMsg(pParse, "database %z is already in use", zName);
+ pParse->rc = SQLITE_ERROR;
+ sqliteFree(zFile);
+ return;
+ }
+ }
+
+ if( db->aDb==db->aDbStatic ){
+ aNew = sqliteMalloc( sizeof(db->aDb[0])*3 );
+ if( aNew==0 ) return;
+ memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
+ }else{
+ aNew = sqliteRealloc(db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
+ if( aNew==0 ) return;
+ }
+ db->aDb = aNew;
+ aNew = &db->aDb[db->nDb++];
+ memset(aNew, 0, sizeof(*aNew));
+ sqliteHashInit(&aNew->tblHash, SQLITE_HASH_STRING, 0);
+ sqliteHashInit(&aNew->idxHash, SQLITE_HASH_STRING, 0);
+ sqliteHashInit(&aNew->trigHash, SQLITE_HASH_STRING, 0);
+ sqliteHashInit(&aNew->aFKey, SQLITE_HASH_STRING, 1);
+ aNew->zName = zName;
+ rc = sqliteBtreeFactory(db, zFile, 0, MAX_PAGES, &aNew->pBt);
+ if( rc ){
+ sqliteErrorMsg(pParse, "unable to open database: %s", zFile);
+ }
+#if SQLITE_HAS_CODEC
+ {
+ extern int sqliteCodecAttach(sqlite*, int, void*, int);
+ char *zKey = 0;
+ int nKey;
+ if( pKey && pKey->z && pKey->n ){
+ sqliteSetNString(&zKey, pKey->z, pKey->n, 0);
+ sqliteDequote(zKey);
+ nKey = strlen(zKey);
+ }else{
+ zKey = 0;
+ nKey = 0;
+ }
+ sqliteCodecAttach(db, db->nDb-1, zKey, nKey);
+ }
+#endif
+ sqliteFree(zFile);
+ db->flags &= ~SQLITE_Initialized;
+ if( pParse->nErr ) return;
+ if( rc==SQLITE_OK ){
+ rc = sqliteInit(pParse->db, &pParse->zErrMsg);
+ }
+ if( rc ){
+ int i = db->nDb - 1;
+ assert( i>=2 );
+ if( db->aDb[i].pBt ){
+ sqliteBtreeClose(db->aDb[i].pBt);
+ db->aDb[i].pBt = 0;
+ }
+ sqliteResetInternalSchema(db, 0);
+ pParse->nErr++;
+ pParse->rc = SQLITE_ERROR;
+ }
+}
+
+/*
+** This routine is called by the parser to process a DETACH statement:
+**
+** DETACH DATABASE dbname
+**
+** The pDbname argument is the name of the database in the DETACH statement.
+*/
+void sqliteDetach(Parse *pParse, Token *pDbname){
+ int i;
+ sqlite *db;
+ Vdbe *v;
+ Db *pDb;
+
+ v = sqliteGetVdbe(pParse);
+ sqliteVdbeAddOp(v, OP_Halt, 0, 0);
+ if( pParse->explain ) return;
+ db = pParse->db;
+ for(i=0; i<db->nDb; i++){
+ pDb = &db->aDb[i];
+ if( pDb->pBt==0 || pDb->zName==0 ) continue;
+ if( strlen(pDb->zName)!=pDbname->n ) continue;
+ if( sqliteStrNICmp(pDb->zName, pDbname->z, pDbname->n)==0 ) break;
+ }
+ if( i>=db->nDb ){
+ sqliteErrorMsg(pParse, "no such database: %T", pDbname);
+ return;
+ }
+ if( i<2 ){
+ sqliteErrorMsg(pParse, "cannot detach database %T", pDbname);
+ return;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqliteAuthCheck(pParse,SQLITE_DETACH,db->aDb[i].zName,0,0)!=SQLITE_OK ){
+ return;
+ }
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+ sqliteBtreeClose(pDb->pBt);
+ pDb->pBt = 0;
+ sqliteFree(pDb->zName);
+ sqliteResetInternalSchema(db, i);
+ if( pDb->pAux && pDb->xFreeAux ) pDb->xFreeAux(pDb->pAux);
+ db->nDb--;
+ if( i<db->nDb ){
+ db->aDb[i] = db->aDb[db->nDb];
+ memset(&db->aDb[db->nDb], 0, sizeof(db->aDb[0]));
+ sqliteResetInternalSchema(db, i);
+ }
+}
+
+/*
+** Initialize a DbFixer structure. This routine must be called prior
+** to passing the structure to one of the sqliteFixAAAA() routines below.
+**
+** The return value indicates whether or not fixation is required. TRUE
+** means we do need to fix the database references, FALSE means we do not.
+*/
+int sqliteFixInit(
+ DbFixer *pFix, /* The fixer to be initialized */
+ Parse *pParse, /* Error messages will be written here */
+ int iDb, /* This is the database that must must be used */
+ const char *zType, /* "view", "trigger", or "index" */
+ const Token *pName /* Name of the view, trigger, or index */
+){
+ sqlite *db;
+
+ if( iDb<0 || iDb==1 ) return 0;
+ db = pParse->db;
+ assert( db->nDb>iDb );
+ pFix->pParse = pParse;
+ pFix->zDb = db->aDb[iDb].zName;
+ pFix->zType = zType;
+ pFix->pName = pName;
+ return 1;
+}
+
+/*
+** The following set of routines walk through the parse tree and assign
+** a specific database to all table references where the database name
+** was left unspecified in the original SQL statement. The pFix structure
+** must have been initialized by a prior call to sqliteFixInit().
+**
+** These routines are used to make sure that an index, trigger, or
+** view in one database does not refer to objects in a different database.
+** (Exception: indices, triggers, and views in the TEMP database are
+** allowed to refer to anything.) If a reference is explicitly made
+** to an object in a different database, an error message is added to
+** pParse->zErrMsg and these routines return non-zero. If everything
+** checks out, these routines return 0.
+*/
+int sqliteFixSrcList(
+ DbFixer *pFix, /* Context of the fixation */
+ SrcList *pList /* The Source list to check and modify */
+){
+ int i;
+ const char *zDb;
+
+ if( pList==0 ) return 0;
+ zDb = pFix->zDb;
+ for(i=0; i<pList->nSrc; i++){
+ if( pList->a[i].zDatabase==0 ){
+ pList->a[i].zDatabase = sqliteStrDup(zDb);
+ }else if( sqliteStrICmp(pList->a[i].zDatabase,zDb)!=0 ){
+ sqliteErrorMsg(pFix->pParse,
+ "%s %z cannot reference objects in database %s",
+ pFix->zType, sqliteStrNDup(pFix->pName->z, pFix->pName->n),
+ pList->a[i].zDatabase);
+ return 1;
+ }
+ if( sqliteFixSelect(pFix, pList->a[i].pSelect) ) return 1;
+ if( sqliteFixExpr(pFix, pList->a[i].pOn) ) return 1;
+ }
+ return 0;
+}
+int sqliteFixSelect(
+ DbFixer *pFix, /* Context of the fixation */
+ Select *pSelect /* The SELECT statement to be fixed to one database */
+){
+ while( pSelect ){
+ if( sqliteFixExprList(pFix, pSelect->pEList) ){
+ return 1;
+ }
+ if( sqliteFixSrcList(pFix, pSelect->pSrc) ){
+ return 1;
+ }
+ if( sqliteFixExpr(pFix, pSelect->pWhere) ){
+ return 1;
+ }
+ if( sqliteFixExpr(pFix, pSelect->pHaving) ){
+ return 1;
+ }
+ pSelect = pSelect->pPrior;
+ }
+ return 0;
+}
+int sqliteFixExpr(
+ DbFixer *pFix, /* Context of the fixation */
+ Expr *pExpr /* The expression to be fixed to one database */
+){
+ while( pExpr ){
+ if( sqliteFixSelect(pFix, pExpr->pSelect) ){
+ return 1;
+ }
+ if( sqliteFixExprList(pFix, pExpr->pList) ){
+ return 1;
+ }
+ if( sqliteFixExpr(pFix, pExpr->pRight) ){
+ return 1;
+ }
+ pExpr = pExpr->pLeft;
+ }
+ return 0;
+}
+int sqliteFixExprList(
+ DbFixer *pFix, /* Context of the fixation */
+ ExprList *pList /* The expression to be fixed to one database */
+){
+ int i;
+ if( pList==0 ) return 0;
+ for(i=0; i<pList->nExpr; i++){
+ if( sqliteFixExpr(pFix, pList->a[i].pExpr) ){
+ return 1;
+ }
+ }
+ return 0;
+}
+int sqliteFixTriggerStep(
+ DbFixer *pFix, /* Context of the fixation */
+ TriggerStep *pStep /* The trigger step be fixed to one database */
+){
+ while( pStep ){
+ if( sqliteFixSelect(pFix, pStep->pSelect) ){
+ return 1;
+ }
+ if( sqliteFixExpr(pFix, pStep->pWhere) ){
+ return 1;
+ }
+ if( sqliteFixExprList(pFix, pStep->pExprList) ){
+ return 1;
+ }
+ pStep = pStep->pNext;
+ }
+ return 0;
+}
diff --git a/src/libs/sqlite2/auth.c b/src/libs/sqlite2/auth.c
new file mode 100644
index 00000000..9147f148
--- /dev/null
+++ b/src/libs/sqlite2/auth.c
@@ -0,0 +1,219 @@
+/*
+** 2003 January 11
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the sqlite_set_authorizer()
+** API. This facility is an optional feature of the library. Embedded
+** systems that do not need this facility may omit it by recompiling
+** the library with -DSQLITE_OMIT_AUTHORIZATION=1
+**
+** $Id: auth.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+
+/*
+** All of the code in this file may be omitted by defining a single
+** macro.
+*/
+#ifndef SQLITE_OMIT_AUTHORIZATION
+
+/*
+** Set or clear the access authorization function.
+**
+** The access authorization function is be called during the compilation
+** phase to verify that the user has read and/or write access permission on
+** various fields of the database. The first argument to the auth function
+** is a copy of the 3rd argument to this routine. The second argument
+** to the auth function is one of these constants:
+**
+** SQLITE_COPY
+** SQLITE_CREATE_INDEX
+** SQLITE_CREATE_TABLE
+** SQLITE_CREATE_TEMP_INDEX
+** SQLITE_CREATE_TEMP_TABLE
+** SQLITE_CREATE_TEMP_TRIGGER
+** SQLITE_CREATE_TEMP_VIEW
+** SQLITE_CREATE_TRIGGER
+** SQLITE_CREATE_VIEW
+** SQLITE_DELETE
+** SQLITE_DROP_INDEX
+** SQLITE_DROP_TABLE
+** SQLITE_DROP_TEMP_INDEX
+** SQLITE_DROP_TEMP_TABLE
+** SQLITE_DROP_TEMP_TRIGGER
+** SQLITE_DROP_TEMP_VIEW
+** SQLITE_DROP_TRIGGER
+** SQLITE_DROP_VIEW
+** SQLITE_INSERT
+** SQLITE_PRAGMA
+** SQLITE_READ
+** SQLITE_SELECT
+** SQLITE_TRANSACTION
+** SQLITE_UPDATE
+**
+** The third and fourth arguments to the auth function are the name of
+** the table and the column that are being accessed. The auth function
+** should return either SQLITE_OK, SQLITE_DENY, or SQLITE_IGNORE. If
+** SQLITE_OK is returned, it means that access is allowed. SQLITE_DENY
+** means that the SQL statement will never-run - the sqlite_exec() call
+** will return with an error. SQLITE_IGNORE means that the SQL statement
+** should run but attempts to read the specified column will return NULL
+** and attempts to write the column will be ignored.
+**
+** Setting the auth function to NULL disables this hook. The default
+** setting of the auth function is NULL.
+*/
+int sqlite_set_authorizer(
+ sqlite *db,
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pArg
+){
+ db->xAuth = xAuth;
+ db->pAuthArg = pArg;
+ return SQLITE_OK;
+}
+
+/*
+** Write an error message into pParse->zErrMsg that explains that the
+** user-supplied authorization function returned an illegal value.
+*/
+static void sqliteAuthBadReturnCode(Parse *pParse, int rc){
+ sqliteErrorMsg(pParse, "illegal return value (%d) from the "
+ "authorization function - should be SQLITE_OK, SQLITE_IGNORE, "
+ "or SQLITE_DENY", rc);
+ pParse->rc = SQLITE_MISUSE;
+}
+
+/*
+** The pExpr should be a TK_COLUMN expression. The table referred to
+** is in pTabList or else it is the NEW or OLD table of a trigger.
+** Check to see if it is OK to read this particular column.
+**
+** If the auth function returns SQLITE_IGNORE, change the TK_COLUMN
+** instruction into a TK_NULL. If the auth function returns SQLITE_DENY,
+** then generate an error.
+*/
+void sqliteAuthRead(
+ Parse *pParse, /* The parser context */
+ Expr *pExpr, /* The expression to check authorization on */
+ SrcList *pTabList /* All table that pExpr might refer to */
+){
+ sqlite *db = pParse->db;
+ int rc;
+ Table *pTab; /* The table being read */
+ const char *zCol; /* Name of the column of the table */
+ int iSrc; /* Index in pTabList->a[] of table being read */
+ const char *zDBase; /* Name of database being accessed */
+ TriggerStack *pStack; /* The stack of current triggers */
+
+ if( db->xAuth==0 ) return;
+ assert( pExpr->op==TK_COLUMN );
+ for(iSrc=0; iSrc<pTabList->nSrc; iSrc++){
+ if( pExpr->iTable==pTabList->a[iSrc].iCursor ) break;
+ }
+ if( iSrc>=0 && iSrc<pTabList->nSrc ){
+ pTab = pTabList->a[iSrc].pTab;
+ }else if( (pStack = pParse->trigStack)!=0 ){
+ /* This must be an attempt to read the NEW or OLD pseudo-tables
+ ** of a trigger.
+ */
+ assert( pExpr->iTable==pStack->newIdx || pExpr->iTable==pStack->oldIdx );
+ pTab = pStack->pTab;
+ }else{
+ return;
+ }
+ if( pTab==0 ) return;
+ if( pExpr->iColumn>=0 ){
+ assert( pExpr->iColumn<pTab->nCol );
+ zCol = pTab->aCol[pExpr->iColumn].zName;
+ }else if( pTab->iPKey>=0 ){
+ assert( pTab->iPKey<pTab->nCol );
+ zCol = pTab->aCol[pTab->iPKey].zName;
+ }else{
+ zCol = "ROWID";
+ }
+ assert( pExpr->iDb<db->nDb );
+ zDBase = db->aDb[pExpr->iDb].zName;
+ rc = db->xAuth(db->pAuthArg, SQLITE_READ, pTab->zName, zCol, zDBase,
+ pParse->zAuthContext);
+ if( rc==SQLITE_IGNORE ){
+ pExpr->op = TK_NULL;
+ }else if( rc==SQLITE_DENY ){
+ if( db->nDb>2 || pExpr->iDb!=0 ){
+ sqliteErrorMsg(pParse, "access to %s.%s.%s is prohibited",
+ zDBase, pTab->zName, zCol);
+ }else{
+ sqliteErrorMsg(pParse, "access to %s.%s is prohibited", pTab->zName,zCol);
+ }
+ pParse->rc = SQLITE_AUTH;
+ }else if( rc!=SQLITE_OK ){
+ sqliteAuthBadReturnCode(pParse, rc);
+ }
+}
+
+/*
+** Do an authorization check using the code and arguments given. Return
+** either SQLITE_OK (zero) or SQLITE_IGNORE or SQLITE_DENY. If SQLITE_DENY
+** is returned, then the error count and error message in pParse are
+** modified appropriately.
+*/
+int sqliteAuthCheck(
+ Parse *pParse,
+ int code,
+ const char *zArg1,
+ const char *zArg2,
+ const char *zArg3
+){
+ sqlite *db = pParse->db;
+ int rc;
+
+ if( db->init.busy || db->xAuth==0 ){
+ return SQLITE_OK;
+ }
+ rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext);
+ if( rc==SQLITE_DENY ){
+ sqliteErrorMsg(pParse, "not authorized");
+ pParse->rc = SQLITE_AUTH;
+ }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){
+ rc = SQLITE_DENY;
+ sqliteAuthBadReturnCode(pParse, rc);
+ }
+ return rc;
+}
+
+/*
+** Push an authorization context. After this routine is called, the
+** zArg3 argument to authorization callbacks will be zContext until
+** popped. Or if pParse==0, this routine is a no-op.
+*/
+void sqliteAuthContextPush(
+ Parse *pParse,
+ AuthContext *pContext,
+ const char *zContext
+){
+ pContext->pParse = pParse;
+ if( pParse ){
+ pContext->zAuthContext = pParse->zAuthContext;
+ pParse->zAuthContext = zContext;
+ }
+}
+
+/*
+** Pop an authorization context that was previously pushed
+** by sqliteAuthContextPush
+*/
+void sqliteAuthContextPop(AuthContext *pContext){
+ if( pContext->pParse ){
+ pContext->pParse->zAuthContext = pContext->zAuthContext;
+ pContext->pParse = 0;
+ }
+}
+
+#endif /* SQLITE_OMIT_AUTHORIZATION */
diff --git a/src/libs/sqlite2/btree.c b/src/libs/sqlite2/btree.c
new file mode 100644
index 00000000..745bdda2
--- /dev/null
+++ b/src/libs/sqlite2/btree.c
@@ -0,0 +1,3584 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** $Id: btree.c 875429 2008-10-24 12:20:41Z cgilles $
+**
+** This file implements a external (disk-based) database using BTrees.
+** For a detailed discussion of BTrees, refer to
+**
+** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
+** "Sorting And Searching", pages 473-480. Addison-Wesley
+** Publishing Company, Reading, Massachusetts.
+**
+** The basic idea is that each page of the file contains N database
+** entries and N+1 pointers to subpages.
+**
+** ----------------------------------------------------------------
+** | Ptr(0) | Key(0) | Ptr(1) | Key(1) | ... | Key(N) | Ptr(N+1) |
+** ----------------------------------------------------------------
+**
+** All of the keys on the page that Ptr(0) points to have values less
+** than Key(0). All of the keys on page Ptr(1) and its subpages have
+** values greater than Key(0) and less than Key(1). All of the keys
+** on Ptr(N+1) and its subpages have values greater than Key(N). And
+** so forth.
+**
+** Finding a particular key requires reading O(log(M)) pages from the
+** disk where M is the number of entries in the tree.
+**
+** In this implementation, a single file can hold one or more separate
+** BTrees. Each BTree is identified by the index of its root page. The
+** key and data for any entry are combined to form the "payload". Up to
+** MX_LOCAL_PAYLOAD bytes of payload can be carried directly on the
+** database page. If the payload is larger than MX_LOCAL_PAYLOAD bytes
+** then surplus bytes are stored on overflow pages. The payload for an
+** entry and the preceding pointer are combined to form a "Cell". Each
+** page has a small header which contains the Ptr(N+1) pointer.
+**
+** The first page of the file contains a magic string used to verify that
+** the file really is a valid BTree database, a pointer to a list of unused
+** pages in the file, and some meta information. The root of the first
+** BTree begins on page 2 of the file. (Pages are numbered beginning with
+** 1, not 0.) Thus a minimum database contains 2 pages.
+*/
+#include "sqliteInt.h"
+#include "pager.h"
+#include "btree.h"
+#include <assert.h>
+
+/* Forward declarations */
+static BtOps sqliteBtreeOps;
+static BtCursorOps sqliteBtreeCursorOps;
+
+/*
+** Macros used for byteswapping. B is a pointer to the Btree
+** structure. This is needed to access the Btree.needSwab boolean
+** in order to tell if byte swapping is needed or not.
+** X is an unsigned integer. SWAB16 byte swaps a 16-bit integer.
+** SWAB32 byteswaps a 32-bit integer.
+*/
+#define SWAB16(B,X) ((B)->needSwab? swab16((u16)X) : ((u16)X))
+#define SWAB32(B,X) ((B)->needSwab? swab32(X) : (X))
+#define SWAB_ADD(B,X,A) \
+ if((B)->needSwab){ X=swab32(swab32(X)+A); }else{ X += (A); }
+
+/*
+** The following global variable - available only if SQLITE_TEST is
+** defined - is used to determine whether new databases are created in
+** native byte order or in non-native byte order. Non-native byte order
+** databases are created for testing purposes only. Under normal operation,
+** only native byte-order databases should be created, but we should be
+** able to read or write existing databases regardless of the byteorder.
+*/
+#ifdef SQLITE_TEST
+int btree_native_byte_order = 1;
+#else
+# define btree_native_byte_order 1
+#endif
+
+/*
+** Forward declarations of structures used only in this file.
+*/
+typedef struct PageOne PageOne;
+typedef struct MemPage MemPage;
+typedef struct PageHdr PageHdr;
+typedef struct Cell Cell;
+typedef struct CellHdr CellHdr;
+typedef struct FreeBlk FreeBlk;
+typedef struct OverflowPage OverflowPage;
+typedef struct FreelistInfo FreelistInfo;
+
+/*
+** All structures on a database page are aligned to 4-byte boundries.
+** This routine rounds up a number of bytes to the next multiple of 4.
+**
+** This might need to change for computer architectures that require
+** and 8-byte alignment boundry for structures.
+*/
+#define ROUNDUP(X) ((X+3) & ~3)
+
+/*
+** This is a magic string that appears at the beginning of every
+** SQLite database in order to identify the file as a real database.
+*/
+static const char zMagicHeader[] =
+ "** This file contains an SQLite 2.1 database **";
+#define MAGIC_SIZE (sizeof(zMagicHeader))
+
+/*
+** This is a magic integer also used to test the integrity of the database
+** file. This integer is used in addition to the string above so that
+** if the file is written on a little-endian architecture and read
+** on a big-endian architectures (or vice versa) we can detect the
+** problem.
+**
+** The number used was obtained at random and has no special
+** significance other than the fact that it represents a different
+** integer on little-endian and big-endian machines.
+*/
+#define MAGIC 0xdae37528
+
+/*
+** The first page of the database file contains a magic header string
+** to identify the file as an SQLite database file. It also contains
+** a pointer to the first free page of the file. Page 2 contains the
+** root of the principle BTree. The file might contain other BTrees
+** rooted on pages above 2.
+**
+** The first page also contains SQLITE_N_BTREE_META integers that
+** can be used by higher-level routines.
+**
+** Remember that pages are numbered beginning with 1. (See pager.c
+** for additional information.) Page 0 does not exist and a page
+** number of 0 is used to mean "no such page".
+*/
+struct PageOne {
+ char zMagic[MAGIC_SIZE]; /* String that identifies the file as a database */
+ int iMagic; /* Integer to verify correct byte order */
+ Pgno freeList; /* First free page in a list of all free pages */
+ int nFree; /* Number of pages on the free list */
+ int aMeta[SQLITE_N_BTREE_META-1]; /* User defined integers */
+};
+
+/*
+** Each database page has a header that is an instance of this
+** structure.
+**
+** PageHdr.firstFree is 0 if there is no free space on this page.
+** Otherwise, PageHdr.firstFree is the index in MemPage.u.aDisk[] of a
+** FreeBlk structure that describes the first block of free space.
+** All free space is defined by a linked list of FreeBlk structures.
+**
+** Data is stored in a linked list of Cell structures. PageHdr.firstCell
+** is the index into MemPage.u.aDisk[] of the first cell on the page. The
+** Cells are kept in sorted order.
+**
+** A Cell contains all information about a database entry and a pointer
+** to a child page that contains other entries less than itself. In
+** other words, the i-th Cell contains both Ptr(i) and Key(i). The
+** right-most pointer of the page is contained in PageHdr.rightChild.
+*/
+struct PageHdr {
+ Pgno rightChild; /* Child page that comes after all cells on this page */
+ u16 firstCell; /* Index in MemPage.u.aDisk[] of the first cell */
+ u16 firstFree; /* Index in MemPage.u.aDisk[] of the first free block */
+};
+
+/*
+** Entries on a page of the database are called "Cells". Each Cell
+** has a header and data. This structure defines the header. The
+** key and data (collectively the "payload") follow this header on
+** the database page.
+**
+** A definition of the complete Cell structure is given below. The
+** header for the cell must be defined first in order to do some
+** of the sizing #defines that follow.
+*/
+struct CellHdr {
+ Pgno leftChild; /* Child page that comes before this cell */
+ u16 nKey; /* Number of bytes in the key */
+ u16 iNext; /* Index in MemPage.u.aDisk[] of next cell in sorted order */
+ u8 nKeyHi; /* Upper 8 bits of key size for keys larger than 64K bytes */
+ u8 nDataHi; /* Upper 8 bits of data size when the size is more than 64K */
+ u16 nData; /* Number of bytes of data */
+};
+
+/*
+** The key and data size are split into a lower 16-bit segment and an
+** upper 8-bit segment in order to pack them together into a smaller
+** space. The following macros reassembly a key or data size back
+** into an integer.
+*/
+#define NKEY(b,h) (SWAB16(b,h.nKey) + h.nKeyHi*65536)
+#define NDATA(b,h) (SWAB16(b,h.nData) + h.nDataHi*65536)
+
+/*
+** The minimum size of a complete Cell. The Cell must contain a header
+** and at least 4 bytes of payload.
+*/
+#define MIN_CELL_SIZE (sizeof(CellHdr)+4)
+
+/*
+** The maximum number of database entries that can be held in a single
+** page of the database.
+*/
+#define MX_CELL ((SQLITE_USABLE_SIZE-sizeof(PageHdr))/MIN_CELL_SIZE)
+
+/*
+** The amount of usable space on a single page of the BTree. This is the
+** page size minus the overhead of the page header.
+*/
+#define USABLE_SPACE (SQLITE_USABLE_SIZE - sizeof(PageHdr))
+
+/*
+** The maximum amount of payload (in bytes) that can be stored locally for
+** a database entry. If the entry contains more data than this, the
+** extra goes onto overflow pages.
+**
+** This number is chosen so that at least 4 cells will fit on every page.
+*/
+#define MX_LOCAL_PAYLOAD ((USABLE_SPACE/4-(sizeof(CellHdr)+sizeof(Pgno)))&~3)
+
+/*
+** Data on a database page is stored as a linked list of Cell structures.
+** Both the key and the data are stored in aPayload[]. The key always comes
+** first. The aPayload[] field grows as necessary to hold the key and data,
+** up to a maximum of MX_LOCAL_PAYLOAD bytes. If the size of the key and
+** data combined exceeds MX_LOCAL_PAYLOAD bytes, then Cell.ovfl is the
+** page number of the first overflow page.
+**
+** Though this structure is fixed in size, the Cell on the database
+** page varies in size. Every cell has a CellHdr and at least 4 bytes
+** of payload space. Additional payload bytes (up to the maximum of
+** MX_LOCAL_PAYLOAD) and the Cell.ovfl value are allocated only as
+** needed.
+*/
+struct Cell {
+ CellHdr h; /* The cell header */
+ char aPayload[MX_LOCAL_PAYLOAD]; /* Key and data */
+ Pgno ovfl; /* The first overflow page */
+};
+
+/*
+** Free space on a page is remembered using a linked list of the FreeBlk
+** structures. Space on a database page is allocated in increments of
+** at least 4 bytes and is always aligned to a 4-byte boundry. The
+** linked list of FreeBlks is always kept in order by address.
+*/
+struct FreeBlk {
+ u16 iSize; /* Number of bytes in this block of free space */
+ u16 iNext; /* Index in MemPage.u.aDisk[] of the next free block */
+};
+
+/*
+** The number of bytes of payload that will fit on a single overflow page.
+*/
+#define OVERFLOW_SIZE (SQLITE_USABLE_SIZE-sizeof(Pgno))
+
+/*
+** When the key and data for a single entry in the BTree will not fit in
+** the MX_LOCAL_PAYLOAD bytes of space available on the database page,
+** then all extra bytes are written to a linked list of overflow pages.
+** Each overflow page is an instance of the following structure.
+**
+** Unused pages in the database are also represented by instances of
+** the OverflowPage structure. The PageOne.freeList field is the
+** page number of the first page in a linked list of unused database
+** pages.
+*/
+struct OverflowPage {
+ Pgno iNext;
+ char aPayload[OVERFLOW_SIZE];
+};
+
+/*
+** The PageOne.freeList field points to a linked list of overflow pages
+** hold information about free pages. The aPayload section of each
+** overflow page contains an instance of the following structure. The
+** aFree[] array holds the page number of nFree unused pages in the disk
+** file.
+*/
+struct FreelistInfo {
+ int nFree;
+ Pgno aFree[(OVERFLOW_SIZE-sizeof(int))/sizeof(Pgno)];
+};
+
+/*
+** For every page in the database file, an instance of the following structure
+** is stored in memory. The u.aDisk[] array contains the raw bits read from
+** the disk. The rest is auxiliary information held in memory only. The
+** auxiliary info is only valid for regular database pages - it is not
+** used for overflow pages and pages on the freelist.
+**
+** Of particular interest in the auxiliary info is the apCell[] entry. Each
+** apCell[] entry is a pointer to a Cell structure in u.aDisk[]. The cells are
+** put in this array so that they can be accessed in constant time, rather
+** than in linear time which would be needed if we had to walk the linked
+** list on every access.
+**
+** Note that apCell[] contains enough space to hold up to two more Cells
+** than can possibly fit on one page. In the steady state, every apCell[]
+** points to memory inside u.aDisk[]. But in the middle of an insert
+** operation, some apCell[] entries may temporarily point to data space
+** outside of u.aDisk[]. This is a transient situation that is quickly
+** resolved. But while it is happening, it is possible for a database
+** page to hold as many as two more cells than it might otherwise hold.
+** The extra two entries in apCell[] are an allowance for this situation.
+**
+** The pParent field points back to the parent page. This allows us to
+** walk up the BTree from any leaf to the root. Care must be taken to
+** unref() the parent page pointer when this page is no longer referenced.
+** The pageDestructor() routine handles that chore.
+*/
+struct MemPage {
+ union u_page_data {
+ char aDisk[SQLITE_PAGE_SIZE]; /* Page data stored on disk */
+ PageHdr hdr; /* Overlay page header */
+ } u;
+ u8 isInit; /* True if auxiliary data is initialized */
+ u8 idxShift; /* True if apCell[] indices have changed */
+ u8 isOverfull; /* Some apCell[] points outside u.aDisk[] */
+ MemPage *pParent; /* The parent of this page. NULL for root */
+ int idxParent; /* Index in pParent->apCell[] of this node */
+ int nFree; /* Number of free bytes in u.aDisk[] */
+ int nCell; /* Number of entries on this page */
+ Cell *apCell[MX_CELL+2]; /* All data entires in sorted order */
+};
+
+/*
+** The in-memory image of a disk page has the auxiliary information appended
+** to the end. EXTRA_SIZE is the number of bytes of space needed to hold
+** that extra information.
+*/
+#define EXTRA_SIZE (sizeof(MemPage)-sizeof(union u_page_data))
+
+/*
+** Everything we need to know about an open database
+*/
+struct Btree {
+ BtOps *pOps; /* Function table */
+ Pager *pPager; /* The page cache */
+ BtCursor *pCursor; /* A list of all open cursors */
+ PageOne *page1; /* First page of the database */
+ u8 inTrans; /* True if a transaction is in progress */
+ u8 inCkpt; /* True if there is a checkpoint on the transaction */
+ u8 readOnly; /* True if the underlying file is readonly */
+ u8 needSwab; /* Need to byte-swapping */
+};
+typedef Btree Bt;
+
+/*
+** A cursor is a pointer to a particular entry in the BTree.
+** The entry is identified by its MemPage and the index in
+** MemPage.apCell[] of the entry.
+*/
+struct BtCursor {
+ BtCursorOps *pOps; /* Function table */
+ Btree *pBt; /* The Btree to which this cursor belongs */
+ BtCursor *pNext, *pPrev; /* Forms a linked list of all cursors */
+ BtCursor *pShared; /* Loop of cursors with the same root page */
+ Pgno pgnoRoot; /* The root page of this tree */
+ MemPage *pPage; /* Page that contains the entry */
+ int idx; /* Index of the entry in pPage->apCell[] */
+ u8 wrFlag; /* True if writable */
+ u8 eSkip; /* Determines if next step operation is a no-op */
+ u8 iMatch; /* compare result from last sqliteBtreeMoveto() */
+};
+
+/*
+** Legal values for BtCursor.eSkip.
+*/
+#define SKIP_NONE 0 /* Always step the cursor */
+#define SKIP_NEXT 1 /* The next sqliteBtreeNext() is a no-op */
+#define SKIP_PREV 2 /* The next sqliteBtreePrevious() is a no-op */
+#define SKIP_INVALID 3 /* Calls to Next() and Previous() are invalid */
+
+/* Forward declarations */
+static int fileBtreeCloseCursor(BtCursor *pCur);
+
+/*
+** Routines for byte swapping.
+*/
+u16 swab16(u16 x){
+ return ((x & 0xff)<<8) | ((x>>8)&0xff);
+}
+u32 swab32(u32 x){
+ return ((x & 0xff)<<24) | ((x & 0xff00)<<8) |
+ ((x>>8) & 0xff00) | ((x>>24)&0xff);
+}
+
+/*
+** Compute the total number of bytes that a Cell needs on the main
+** database page. The number returned includes the Cell header,
+** local payload storage, and the pointer to overflow pages (if
+** applicable). Additional space allocated on overflow pages
+** is NOT included in the value returned from this routine.
+*/
+static int cellSize(Btree *pBt, Cell *pCell){
+ int n = NKEY(pBt, pCell->h) + NDATA(pBt, pCell->h);
+ if( n>MX_LOCAL_PAYLOAD ){
+ n = MX_LOCAL_PAYLOAD + sizeof(Pgno);
+ }else{
+ n = ROUNDUP(n);
+ }
+ n += sizeof(CellHdr);
+ return n;
+}
+
+/*
+** Defragment the page given. All Cells are moved to the
+** beginning of the page and all free space is collected
+** into one big FreeBlk at the end of the page.
+*/
+static void defragmentPage(Btree *pBt, MemPage *pPage){
+ int pc, i, n;
+ FreeBlk *pFBlk;
+ char newPage[SQLITE_USABLE_SIZE];
+
+ assert( sqlitepager_iswriteable(pPage) );
+ assert( pPage->isInit );
+ pc = sizeof(PageHdr);
+ pPage->u.hdr.firstCell = SWAB16(pBt, pc);
+ memcpy(newPage, pPage->u.aDisk, pc);
+ for(i=0; i<pPage->nCell; i++){
+ Cell *pCell = pPage->apCell[i];
+
+ /* This routine should never be called on an overfull page. The
+ ** following asserts verify that constraint. */
+ assert( Addr(pCell) > Addr(pPage) );
+ assert( Addr(pCell) < Addr(pPage) + SQLITE_USABLE_SIZE );
+
+ n = cellSize(pBt, pCell);
+ pCell->h.iNext = SWAB16(pBt, pc + n);
+ memcpy(&newPage[pc], pCell, n);
+ pPage->apCell[i] = (Cell*)&pPage->u.aDisk[pc];
+ pc += n;
+ }
+ assert( pPage->nFree==SQLITE_USABLE_SIZE-pc );
+ memcpy(pPage->u.aDisk, newPage, pc);
+ if( pPage->nCell>0 ){
+ pPage->apCell[pPage->nCell-1]->h.iNext = 0;
+ }
+ pFBlk = (FreeBlk*)&pPage->u.aDisk[pc];
+ pFBlk->iSize = SWAB16(pBt, SQLITE_USABLE_SIZE - pc);
+ pFBlk->iNext = 0;
+ pPage->u.hdr.firstFree = SWAB16(pBt, pc);
+ memset(&pFBlk[1], 0, SQLITE_USABLE_SIZE - pc - sizeof(FreeBlk));
+}
+
+/*
+** Allocate nByte bytes of space on a page. nByte must be a
+** multiple of 4.
+**
+** Return the index into pPage->u.aDisk[] of the first byte of
+** the new allocation. Or return 0 if there is not enough free
+** space on the page to satisfy the allocation request.
+**
+** If the page contains nBytes of free space but does not contain
+** nBytes of contiguous free space, then this routine automatically
+** calls defragementPage() to consolidate all free space before
+** allocating the new chunk.
+*/
+static int allocateSpace(Btree *pBt, MemPage *pPage, int nByte){
+ FreeBlk *p;
+ u16 *pIdx;
+ int start;
+ int iSize;
+#ifndef NDEBUG
+ int cnt = 0;
+#endif
+
+ assert( sqlitepager_iswriteable(pPage) );
+ assert( nByte==ROUNDUP(nByte) );
+ assert( pPage->isInit );
+ if( pPage->nFree<nByte || pPage->isOverfull ) return 0;
+ pIdx = &pPage->u.hdr.firstFree;
+ p = (FreeBlk*)&pPage->u.aDisk[SWAB16(pBt, *pIdx)];
+ while( (iSize = SWAB16(pBt, p->iSize))<nByte ){
+ assert( cnt++ < SQLITE_USABLE_SIZE/4 );
+ if( p->iNext==0 ){
+ defragmentPage(pBt, pPage);
+ pIdx = &pPage->u.hdr.firstFree;
+ }else{
+ pIdx = &p->iNext;
+ }
+ p = (FreeBlk*)&pPage->u.aDisk[SWAB16(pBt, *pIdx)];
+ }
+ if( iSize==nByte ){
+ start = SWAB16(pBt, *pIdx);
+ *pIdx = p->iNext;
+ }else{
+ FreeBlk *pNew;
+ start = SWAB16(pBt, *pIdx);
+ pNew = (FreeBlk*)&pPage->u.aDisk[start + nByte];
+ pNew->iNext = p->iNext;
+ pNew->iSize = SWAB16(pBt, iSize - nByte);
+ *pIdx = SWAB16(pBt, start + nByte);
+ }
+ pPage->nFree -= nByte;
+ return start;
+}
+
+/*
+** Return a section of the MemPage.u.aDisk[] to the freelist.
+** The first byte of the new free block is pPage->u.aDisk[start]
+** and the size of the block is "size" bytes. Size must be
+** a multiple of 4.
+**
+** Most of the effort here is involved in coalesing adjacent
+** free blocks into a single big free block.
+*/
+static void freeSpace(Btree *pBt, MemPage *pPage, int start, int size){
+ int end = start + size;
+ u16 *pIdx, idx;
+ FreeBlk *pFBlk;
+ FreeBlk *pNew;
+ FreeBlk *pNext;
+ int iSize;
+
+ assert( sqlitepager_iswriteable(pPage) );
+ assert( size == ROUNDUP(size) );
+ assert( start == ROUNDUP(start) );
+ assert( pPage->isInit );
+ pIdx = &pPage->u.hdr.firstFree;
+ idx = SWAB16(pBt, *pIdx);
+ while( idx!=0 && idx<start ){
+ pFBlk = (FreeBlk*)&pPage->u.aDisk[idx];
+ iSize = SWAB16(pBt, pFBlk->iSize);
+ if( idx + iSize == start ){
+ pFBlk->iSize = SWAB16(pBt, iSize + size);
+ if( idx + iSize + size == SWAB16(pBt, pFBlk->iNext) ){
+ pNext = (FreeBlk*)&pPage->u.aDisk[idx + iSize + size];
+ if( pBt->needSwab ){
+ pFBlk->iSize = swab16((u16)swab16(pNext->iSize)+iSize+size);
+ }else{
+ pFBlk->iSize += pNext->iSize;
+ }
+ pFBlk->iNext = pNext->iNext;
+ }
+ pPage->nFree += size;
+ return;
+ }
+ pIdx = &pFBlk->iNext;
+ idx = SWAB16(pBt, *pIdx);
+ }
+ pNew = (FreeBlk*)&pPage->u.aDisk[start];
+ if( idx != end ){
+ pNew->iSize = SWAB16(pBt, size);
+ pNew->iNext = SWAB16(pBt, idx);
+ }else{
+ pNext = (FreeBlk*)&pPage->u.aDisk[idx];
+ pNew->iSize = SWAB16(pBt, size + SWAB16(pBt, pNext->iSize));
+ pNew->iNext = pNext->iNext;
+ }
+ *pIdx = SWAB16(pBt, start);
+ pPage->nFree += size;
+}
+
+/*
+** Initialize the auxiliary information for a disk block.
+**
+** The pParent parameter must be a pointer to the MemPage which
+** is the parent of the page being initialized. The root of the
+** BTree (usually page 2) has no parent and so for that page,
+** pParent==NULL.
+**
+** Return SQLITE_OK on success. If we see that the page does
+** not contain a well-formed database page, then return
+** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not
+** guarantee that the page is well-formed. It only shows that
+** we failed to detect any corruption.
+*/
+static int initPage(Bt *pBt, MemPage *pPage, Pgno pgnoThis, MemPage *pParent){
+ int idx; /* An index into pPage->u.aDisk[] */
+ Cell *pCell; /* A pointer to a Cell in pPage->u.aDisk[] */
+ FreeBlk *pFBlk; /* A pointer to a free block in pPage->u.aDisk[] */
+ int sz; /* The size of a Cell in bytes */
+ int freeSpace; /* Amount of free space on the page */
+
+ if( pPage->pParent ){
+ assert( pPage->pParent==pParent );
+ return SQLITE_OK;
+ }
+ if( pParent ){
+ pPage->pParent = pParent;
+ sqlitepager_ref(pParent);
+ }
+ if( pPage->isInit ) return SQLITE_OK;
+ pPage->isInit = 1;
+ pPage->nCell = 0;
+ freeSpace = USABLE_SPACE;
+ idx = SWAB16(pBt, pPage->u.hdr.firstCell);
+ while( idx!=0 ){
+ if( idx>SQLITE_USABLE_SIZE-MIN_CELL_SIZE ) goto page_format_error;
+ if( idx<sizeof(PageHdr) ) goto page_format_error;
+ if( idx!=ROUNDUP(idx) ) goto page_format_error;
+ pCell = (Cell*)&pPage->u.aDisk[idx];
+ sz = cellSize(pBt, pCell);
+ if( idx+sz > SQLITE_USABLE_SIZE ) goto page_format_error;
+ freeSpace -= sz;
+ pPage->apCell[pPage->nCell++] = pCell;
+ idx = SWAB16(pBt, pCell->h.iNext);
+ }
+ pPage->nFree = 0;
+ idx = SWAB16(pBt, pPage->u.hdr.firstFree);
+ while( idx!=0 ){
+ int iNext;
+ if( idx>SQLITE_USABLE_SIZE-sizeof(FreeBlk) ) goto page_format_error;
+ if( idx<sizeof(PageHdr) ) goto page_format_error;
+ pFBlk = (FreeBlk*)&pPage->u.aDisk[idx];
+ pPage->nFree += SWAB16(pBt, pFBlk->iSize);
+ iNext = SWAB16(pBt, pFBlk->iNext);
+ if( iNext>0 && iNext <= idx ) goto page_format_error;
+ idx = iNext;
+ }
+ if( pPage->nCell==0 && pPage->nFree==0 ){
+ /* As a special case, an uninitialized root page appears to be
+ ** an empty database */
+ return SQLITE_OK;
+ }
+ if( pPage->nFree!=freeSpace ) goto page_format_error;
+ return SQLITE_OK;
+
+page_format_error:
+ return SQLITE_CORRUPT;
+}
+
+/*
+** Set up a raw page so that it looks like a database page holding
+** no entries.
+*/
+static void zeroPage(Btree *pBt, MemPage *pPage){
+ PageHdr *pHdr;
+ FreeBlk *pFBlk;
+ assert( sqlitepager_iswriteable(pPage) );
+ memset(pPage, 0, SQLITE_USABLE_SIZE);
+ pHdr = &pPage->u.hdr;
+ pHdr->firstCell = 0;
+ pHdr->firstFree = SWAB16(pBt, sizeof(*pHdr));
+ pFBlk = (FreeBlk*)&pHdr[1];
+ pFBlk->iNext = 0;
+ pPage->nFree = SQLITE_USABLE_SIZE - sizeof(*pHdr);
+ pFBlk->iSize = SWAB16(pBt, pPage->nFree);
+ pPage->nCell = 0;
+ pPage->isOverfull = 0;
+}
+
+/*
+** This routine is called when the reference count for a page
+** reaches zero. We need to unref the pParent pointer when that
+** happens.
+*/
+static void pageDestructor(void *pData){
+ MemPage *pPage = (MemPage*)pData;
+ if( pPage->pParent ){
+ MemPage *pParent = pPage->pParent;
+ pPage->pParent = 0;
+ sqlitepager_unref(pParent);
+ }
+}
+
+/*
+** Open a new database.
+**
+** Actually, this routine just sets up the internal data structures
+** for accessing the database. We do not open the database file
+** until the first page is loaded.
+**
+** zFilename is the name of the database file. If zFilename is NULL
+** a new database with a random name is created. This randomly named
+** database file will be deleted when sqliteBtreeClose() is called.
+*/
+int sqliteBtreeOpen(
+ const char *zFilename, /* Name of the file containing the BTree database */
+ int omitJournal, /* if TRUE then do not journal this file */
+ int nCache, /* How many pages in the page cache */
+ Btree **ppBtree /* Pointer to new Btree object written here */
+){
+ Btree *pBt;
+ int rc;
+
+ /*
+ ** The following asserts make sure that structures used by the btree are
+ ** the right size. This is to guard against size changes that result
+ ** when compiling on a different architecture.
+ */
+ assert( sizeof(u32)==4 );
+ assert( sizeof(u16)==2 );
+ assert( sizeof(Pgno)==4 );
+ assert( sizeof(PageHdr)==8 );
+ assert( sizeof(CellHdr)==12 );
+ assert( sizeof(FreeBlk)==4 );
+ assert( sizeof(OverflowPage)==SQLITE_USABLE_SIZE );
+ assert( sizeof(FreelistInfo)==OVERFLOW_SIZE );
+ assert( sizeof(ptr)==sizeof(char*) );
+ assert( sizeof(uptr)==sizeof(ptr) );
+
+ pBt = sqliteMalloc( sizeof(*pBt) );
+ if( pBt==0 ){
+ *ppBtree = 0;
+ return SQLITE_NOMEM;
+ }
+ if( nCache<10 ) nCache = 10;
+ rc = sqlitepager_open(&pBt->pPager, zFilename, nCache, EXTRA_SIZE,
+ !omitJournal);
+ if( rc!=SQLITE_OK ){
+ if( pBt->pPager ) sqlitepager_close(pBt->pPager);
+ sqliteFree(pBt);
+ *ppBtree = 0;
+ return rc;
+ }
+ sqlitepager_set_destructor(pBt->pPager, pageDestructor);
+ pBt->pCursor = 0;
+ pBt->page1 = 0;
+ pBt->readOnly = sqlitepager_isreadonly(pBt->pPager);
+ pBt->pOps = &sqliteBtreeOps;
+ *ppBtree = pBt;
+ return SQLITE_OK;
+}
+
+/*
+** Close an open database and invalidate all cursors.
+*/
+static int fileBtreeClose(Btree *pBt){
+ while( pBt->pCursor ){
+ fileBtreeCloseCursor(pBt->pCursor);
+ }
+ sqlitepager_close(pBt->pPager);
+ sqliteFree(pBt);
+ return SQLITE_OK;
+}
+
+/*
+** Change the limit on the number of pages allowed in the cache.
+**
+** The maximum number of cache pages is set to the absolute
+** value of mxPage. If mxPage is negative, the pager will
+** operate asynchronously - it will not stop to do fsync()s
+** to insure data is written to the disk surface before
+** continuing. Transactions still work if synchronous is off,
+** and the database cannot be corrupted if this program
+** crashes. But if the operating system crashes or there is
+** an abrupt power failure when synchronous is off, the database
+** could be left in an inconsistent and unrecoverable state.
+** Synchronous is on by default so database corruption is not
+** normally a worry.
+*/
+static int fileBtreeSetCacheSize(Btree *pBt, int mxPage){
+ sqlitepager_set_cachesize(pBt->pPager, mxPage);
+ return SQLITE_OK;
+}
+
+/*
+** Change the way data is synced to disk in order to increase or decrease
+** how well the database resists damage due to OS crashes and power
+** failures. Level 1 is the same as asynchronous (no syncs() occur and
+** there is a high probability of damage) Level 2 is the default. There
+** is a very low but non-zero probability of damage. Level 3 reduces the
+** probability of damage to near zero but with a write performance reduction.
+*/
+static int fileBtreeSetSafetyLevel(Btree *pBt, int level){
+ sqlitepager_set_safety_level(pBt->pPager, level);
+ return SQLITE_OK;
+}
+
+/*
+** Get a reference to page1 of the database file. This will
+** also acquire a readlock on that file.
+**
+** SQLITE_OK is returned on success. If the file is not a
+** well-formed database file, then SQLITE_CORRUPT is returned.
+** SQLITE_BUSY is returned if the database is locked. SQLITE_NOMEM
+** is returned if we run out of memory. SQLITE_PROTOCOL is returned
+** if there is a locking protocol violation.
+*/
+static int lockBtree(Btree *pBt){
+ int rc;
+ if( pBt->page1 ) return SQLITE_OK;
+ rc = sqlitepager_get(pBt->pPager, 1, (void**)&pBt->page1);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Do some checking to help insure the file we opened really is
+ ** a valid database file.
+ */
+ if( sqlitepager_pagecount(pBt->pPager)>0 ){
+ PageOne *pP1 = pBt->page1;
+ if( strcmp(pP1->zMagic,zMagicHeader)!=0 ||
+ (pP1->iMagic!=MAGIC && swab32(pP1->iMagic)!=MAGIC) ){
+ rc = SQLITE_NOTADB;
+ goto page1_init_failed;
+ }
+ pBt->needSwab = pP1->iMagic!=MAGIC;
+ }
+ return rc;
+
+page1_init_failed:
+ sqlitepager_unref(pBt->page1);
+ pBt->page1 = 0;
+ return rc;
+}
+
+/*
+** If there are no outstanding cursors and we are not in the middle
+** of a transaction but there is a read lock on the database, then
+** this routine unrefs the first page of the database file which
+** has the effect of releasing the read lock.
+**
+** If there are any outstanding cursors, this routine is a no-op.
+**
+** If there is a transaction in progress, this routine is a no-op.
+*/
+static void unlockBtreeIfUnused(Btree *pBt){
+ if( pBt->inTrans==0 && pBt->pCursor==0 && pBt->page1!=0 ){
+ sqlitepager_unref(pBt->page1);
+ pBt->page1 = 0;
+ pBt->inTrans = 0;
+ pBt->inCkpt = 0;
+ }
+}
+
+/*
+** Create a new database by initializing the first two pages of the
+** file.
+*/
+static int newDatabase(Btree *pBt){
+ MemPage *pRoot;
+ PageOne *pP1;
+ int rc;
+ if( sqlitepager_pagecount(pBt->pPager)>1 ) return SQLITE_OK;
+ pP1 = pBt->page1;
+ rc = sqlitepager_write(pBt->page1);
+ if( rc ) return rc;
+ rc = sqlitepager_get(pBt->pPager, 2, (void**)&pRoot);
+ if( rc ) return rc;
+ rc = sqlitepager_write(pRoot);
+ if( rc ){
+ sqlitepager_unref(pRoot);
+ return rc;
+ }
+ strcpy(pP1->zMagic, zMagicHeader);
+ if( btree_native_byte_order ){
+ pP1->iMagic = MAGIC;
+ pBt->needSwab = 0;
+ }else{
+ pP1->iMagic = swab32(MAGIC);
+ pBt->needSwab = 1;
+ }
+ zeroPage(pBt, pRoot);
+ sqlitepager_unref(pRoot);
+ return SQLITE_OK;
+}
+
+/*
+** Attempt to start a new transaction.
+**
+** A transaction must be started before attempting any changes
+** to the database. None of the following routines will work
+** unless a transaction is started first:
+**
+** sqliteBtreeCreateTable()
+** sqliteBtreeCreateIndex()
+** sqliteBtreeClearTable()
+** sqliteBtreeDropTable()
+** sqliteBtreeInsert()
+** sqliteBtreeDelete()
+** sqliteBtreeUpdateMeta()
+*/
+static int fileBtreeBeginTrans(Btree *pBt){
+ int rc;
+ if( pBt->inTrans ) return SQLITE_ERROR;
+ if( pBt->readOnly ) return SQLITE_READONLY;
+ if( pBt->page1==0 ){
+ rc = lockBtree(pBt);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ rc = sqlitepager_begin(pBt->page1);
+ if( rc==SQLITE_OK ){
+ rc = newDatabase(pBt);
+ }
+ if( rc==SQLITE_OK ){
+ pBt->inTrans = 1;
+ pBt->inCkpt = 0;
+ }else{
+ unlockBtreeIfUnused(pBt);
+ }
+ return rc;
+}
+
+/*
+** Commit the transaction currently in progress.
+**
+** This will release the write lock on the database file. If there
+** are no active cursors, it also releases the read lock.
+*/
+static int fileBtreeCommit(Btree *pBt){
+ int rc;
+ rc = pBt->readOnly ? SQLITE_OK : sqlitepager_commit(pBt->pPager);
+ pBt->inTrans = 0;
+ pBt->inCkpt = 0;
+ unlockBtreeIfUnused(pBt);
+ return rc;
+}
+
+/*
+** Rollback the transaction in progress. All cursors will be
+** invalided by this operation. Any attempt to use a cursor
+** that was open at the beginning of this operation will result
+** in an error.
+**
+** This will release the write lock on the database file. If there
+** are no active cursors, it also releases the read lock.
+*/
+static int fileBtreeRollback(Btree *pBt){
+ int rc;
+ BtCursor *pCur;
+ if( pBt->inTrans==0 ) return SQLITE_OK;
+ pBt->inTrans = 0;
+ pBt->inCkpt = 0;
+ rc = pBt->readOnly ? SQLITE_OK : sqlitepager_rollback(pBt->pPager);
+ for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+ if( pCur->pPage && pCur->pPage->isInit==0 ){
+ sqlitepager_unref(pCur->pPage);
+ pCur->pPage = 0;
+ }
+ }
+ unlockBtreeIfUnused(pBt);
+ return rc;
+}
+
+/*
+** Set the checkpoint for the current transaction. The checkpoint serves
+** as a sub-transaction that can be rolled back independently of the
+** main transaction. You must start a transaction before starting a
+** checkpoint. The checkpoint is ended automatically if the transaction
+** commits or rolls back.
+**
+** Only one checkpoint may be active at a time. It is an error to try
+** to start a new checkpoint if another checkpoint is already active.
+*/
+static int fileBtreeBeginCkpt(Btree *pBt){
+ int rc;
+ if( !pBt->inTrans || pBt->inCkpt ){
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ rc = pBt->readOnly ? SQLITE_OK : sqlitepager_ckpt_begin(pBt->pPager);
+ pBt->inCkpt = 1;
+ return rc;
+}
+
+
+/*
+** Commit a checkpoint to transaction currently in progress. If no
+** checkpoint is active, this is a no-op.
+*/
+static int fileBtreeCommitCkpt(Btree *pBt){
+ int rc;
+ if( pBt->inCkpt && !pBt->readOnly ){
+ rc = sqlitepager_ckpt_commit(pBt->pPager);
+ }else{
+ rc = SQLITE_OK;
+ }
+ pBt->inCkpt = 0;
+ return rc;
+}
+
+/*
+** Rollback the checkpoint to the current transaction. If there
+** is no active checkpoint or transaction, this routine is a no-op.
+**
+** All cursors will be invalided by this operation. Any attempt
+** to use a cursor that was open at the beginning of this operation
+** will result in an error.
+*/
+static int fileBtreeRollbackCkpt(Btree *pBt){
+ int rc;
+ BtCursor *pCur;
+ if( pBt->inCkpt==0 || pBt->readOnly ) return SQLITE_OK;
+ rc = sqlitepager_ckpt_rollback(pBt->pPager);
+ for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+ if( pCur->pPage && pCur->pPage->isInit==0 ){
+ sqlitepager_unref(pCur->pPage);
+ pCur->pPage = 0;
+ }
+ }
+ pBt->inCkpt = 0;
+ return rc;
+}
+
+/*
+** Create a new cursor for the BTree whose root is on the page
+** iTable. The act of acquiring a cursor gets a read lock on
+** the database file.
+**
+** If wrFlag==0, then the cursor can only be used for reading.
+** If wrFlag==1, then the cursor can be used for reading or for
+** writing if other conditions for writing are also met. These
+** are the conditions that must be met in order for writing to
+** be allowed:
+**
+** 1: The cursor must have been opened with wrFlag==1
+**
+** 2: No other cursors may be open with wrFlag==0 on the same table
+**
+** 3: The database must be writable (not on read-only media)
+**
+** 4: There must be an active transaction.
+**
+** Condition 2 warrants further discussion. If any cursor is opened
+** on a table with wrFlag==0, that prevents all other cursors from
+** writing to that table. This is a kind of "read-lock". When a cursor
+** is opened with wrFlag==0 it is guaranteed that the table will not
+** change as long as the cursor is open. This allows the cursor to
+** do a sequential scan of the table without having to worry about
+** entries being inserted or deleted during the scan. Cursors should
+** be opened with wrFlag==0 only if this read-lock property is needed.
+** That is to say, cursors should be opened with wrFlag==0 only if they
+** intend to use the sqliteBtreeNext() system call. All other cursors
+** should be opened with wrFlag==1 even if they never really intend
+** to write.
+**
+** No checking is done to make sure that page iTable really is the
+** root page of a b-tree. If it is not, then the cursor acquired
+** will not work correctly.
+*/
+static
+int fileBtreeCursor(Btree *pBt, int iTable, int wrFlag, BtCursor **ppCur){
+ int rc;
+ BtCursor *pCur, *pRing;
+
+ if( pBt->readOnly && wrFlag ){
+ *ppCur = 0;
+ return SQLITE_READONLY;
+ }
+ if( pBt->page1==0 ){
+ rc = lockBtree(pBt);
+ if( rc!=SQLITE_OK ){
+ *ppCur = 0;
+ return rc;
+ }
+ }
+ pCur = sqliteMalloc( sizeof(*pCur) );
+ if( pCur==0 ){
+ rc = SQLITE_NOMEM;
+ goto create_cursor_exception;
+ }
+ pCur->pgnoRoot = (Pgno)iTable;
+ rc = sqlitepager_get(pBt->pPager, pCur->pgnoRoot, (void**)&pCur->pPage);
+ if( rc!=SQLITE_OK ){
+ goto create_cursor_exception;
+ }
+ rc = initPage(pBt, pCur->pPage, pCur->pgnoRoot, 0);
+ if( rc!=SQLITE_OK ){
+ goto create_cursor_exception;
+ }
+ pCur->pOps = &sqliteBtreeCursorOps;
+ pCur->pBt = pBt;
+ pCur->wrFlag = wrFlag;
+ pCur->idx = 0;
+ pCur->eSkip = SKIP_INVALID;
+ pCur->pNext = pBt->pCursor;
+ if( pCur->pNext ){
+ pCur->pNext->pPrev = pCur;
+ }
+ pCur->pPrev = 0;
+ pRing = pBt->pCursor;
+ while( pRing && pRing->pgnoRoot!=pCur->pgnoRoot ){ pRing = pRing->pNext; }
+ if( pRing ){
+ pCur->pShared = pRing->pShared;
+ pRing->pShared = pCur;
+ }else{
+ pCur->pShared = pCur;
+ }
+ pBt->pCursor = pCur;
+ *ppCur = pCur;
+ return SQLITE_OK;
+
+create_cursor_exception:
+ *ppCur = 0;
+ if( pCur ){
+ if( pCur->pPage ) sqlitepager_unref(pCur->pPage);
+ sqliteFree(pCur);
+ }
+ unlockBtreeIfUnused(pBt);
+ return rc;
+}
+
+/*
+** Close a cursor. The read lock on the database file is released
+** when the last cursor is closed.
+*/
+static int fileBtreeCloseCursor(BtCursor *pCur){
+ Btree *pBt = pCur->pBt;
+ if( pCur->pPrev ){
+ pCur->pPrev->pNext = pCur->pNext;
+ }else{
+ pBt->pCursor = pCur->pNext;
+ }
+ if( pCur->pNext ){
+ pCur->pNext->pPrev = pCur->pPrev;
+ }
+ if( pCur->pPage ){
+ sqlitepager_unref(pCur->pPage);
+ }
+ if( pCur->pShared!=pCur ){
+ BtCursor *pRing = pCur->pShared;
+ while( pRing->pShared!=pCur ){ pRing = pRing->pShared; }
+ pRing->pShared = pCur->pShared;
+ }
+ unlockBtreeIfUnused(pBt);
+ sqliteFree(pCur);
+ return SQLITE_OK;
+}
+
+/*
+** Make a temporary cursor by filling in the fields of pTempCur.
+** The temporary cursor is not on the cursor list for the Btree.
+*/
+static void getTempCursor(BtCursor *pCur, BtCursor *pTempCur){
+ memcpy(pTempCur, pCur, sizeof(*pCur));
+ pTempCur->pNext = 0;
+ pTempCur->pPrev = 0;
+ if( pTempCur->pPage ){
+ sqlitepager_ref(pTempCur->pPage);
+ }
+}
+
+/*
+** Delete a temporary cursor such as was made by the CreateTemporaryCursor()
+** function above.
+*/
+static void releaseTempCursor(BtCursor *pCur){
+ if( pCur->pPage ){
+ sqlitepager_unref(pCur->pPage);
+ }
+}
+
+/*
+** Set *pSize to the number of bytes of key in the entry the
+** cursor currently points to. Always return SQLITE_OK.
+** Failure is not possible. If the cursor is not currently
+** pointing to an entry (which can happen, for example, if
+** the database is empty) then *pSize is set to 0.
+*/
+static int fileBtreeKeySize(BtCursor *pCur, int *pSize){
+ Cell *pCell;
+ MemPage *pPage;
+
+ pPage = pCur->pPage;
+ assert( pPage!=0 );
+ if( pCur->idx >= pPage->nCell ){
+ *pSize = 0;
+ }else{
+ pCell = pPage->apCell[pCur->idx];
+ *pSize = NKEY(pCur->pBt, pCell->h);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Read payload information from the entry that the pCur cursor is
+** pointing to. Begin reading the payload at "offset" and read
+** a total of "amt" bytes. Put the result in zBuf.
+**
+** This routine does not make a distinction between key and data.
+** It just reads bytes from the payload area.
+*/
+static int getPayload(BtCursor *pCur, int offset, int amt, char *zBuf){
+ char *aPayload;
+ Pgno nextPage;
+ int rc;
+ Btree *pBt = pCur->pBt;
+ assert( pCur!=0 && pCur->pPage!=0 );
+ assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
+ aPayload = pCur->pPage->apCell[pCur->idx]->aPayload;
+ if( offset<MX_LOCAL_PAYLOAD ){
+ int a = amt;
+ if( a+offset>MX_LOCAL_PAYLOAD ){
+ a = MX_LOCAL_PAYLOAD - offset;
+ }
+ memcpy(zBuf, &aPayload[offset], a);
+ if( a==amt ){
+ return SQLITE_OK;
+ }
+ offset = 0;
+ zBuf += a;
+ amt -= a;
+ }else{
+ offset -= MX_LOCAL_PAYLOAD;
+ }
+ if( amt>0 ){
+ nextPage = SWAB32(pBt, pCur->pPage->apCell[pCur->idx]->ovfl);
+ }
+ while( amt>0 && nextPage ){
+ OverflowPage *pOvfl;
+ rc = sqlitepager_get(pBt->pPager, nextPage, (void**)&pOvfl);
+ if( rc!=0 ){
+ return rc;
+ }
+ nextPage = SWAB32(pBt, pOvfl->iNext);
+ if( offset<OVERFLOW_SIZE ){
+ int a = amt;
+ if( a + offset > OVERFLOW_SIZE ){
+ a = OVERFLOW_SIZE - offset;
+ }
+ memcpy(zBuf, &pOvfl->aPayload[offset], a);
+ offset = 0;
+ amt -= a;
+ zBuf += a;
+ }else{
+ offset -= OVERFLOW_SIZE;
+ }
+ sqlitepager_unref(pOvfl);
+ }
+ if( amt>0 ){
+ return SQLITE_CORRUPT;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Read part of the key associated with cursor pCur. A maximum
+** of "amt" bytes will be transfered into zBuf[]. The transfer
+** begins at "offset". The number of bytes actually read is
+** returned.
+**
+** Change: It used to be that the amount returned will be smaller
+** than the amount requested if there are not enough bytes in the key
+** to satisfy the request. But now, it must be the case that there
+** is enough data available to satisfy the request. If not, an exception
+** is raised. The change was made in an effort to boost performance
+** by eliminating unneeded tests.
+*/
+static int fileBtreeKey(BtCursor *pCur, int offset, int amt, char *zBuf){
+ MemPage *pPage;
+
+ assert( amt>=0 );
+ assert( offset>=0 );
+ assert( pCur->pPage!=0 );
+ pPage = pCur->pPage;
+ if( pCur->idx >= pPage->nCell ){
+ return 0;
+ }
+ assert( amt+offset <= NKEY(pCur->pBt, pPage->apCell[pCur->idx]->h) );
+ getPayload(pCur, offset, amt, zBuf);
+ return amt;
+}
+
+/*
+** Set *pSize to the number of bytes of data in the entry the
+** cursor currently points to. Always return SQLITE_OK.
+** Failure is not possible. If the cursor is not currently
+** pointing to an entry (which can happen, for example, if
+** the database is empty) then *pSize is set to 0.
+*/
+static int fileBtreeDataSize(BtCursor *pCur, int *pSize){
+ Cell *pCell;
+ MemPage *pPage;
+
+ pPage = pCur->pPage;
+ assert( pPage!=0 );
+ if( pCur->idx >= pPage->nCell ){
+ *pSize = 0;
+ }else{
+ pCell = pPage->apCell[pCur->idx];
+ *pSize = NDATA(pCur->pBt, pCell->h);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Read part of the data associated with cursor pCur. A maximum
+** of "amt" bytes will be transfered into zBuf[]. The transfer
+** begins at "offset". The number of bytes actually read is
+** returned. The amount returned will be smaller than the
+** amount requested if there are not enough bytes in the data
+** to satisfy the request.
+*/
+static int fileBtreeData(BtCursor *pCur, int offset, int amt, char *zBuf){
+ Cell *pCell;
+ MemPage *pPage;
+
+ assert( amt>=0 );
+ assert( offset>=0 );
+ assert( pCur->pPage!=0 );
+ pPage = pCur->pPage;
+ if( pCur->idx >= pPage->nCell ){
+ return 0;
+ }
+ pCell = pPage->apCell[pCur->idx];
+ assert( amt+offset <= NDATA(pCur->pBt, pCell->h) );
+ getPayload(pCur, offset + NKEY(pCur->pBt, pCell->h), amt, zBuf);
+ return amt;
+}
+
+/*
+** Compare an external key against the key on the entry that pCur points to.
+**
+** The external key is pKey and is nKey bytes long. The last nIgnore bytes
+** of the key associated with pCur are ignored, as if they do not exist.
+** (The normal case is for nIgnore to be zero in which case the entire
+** internal key is used in the comparison.)
+**
+** The comparison result is written to *pRes as follows:
+**
+** *pRes<0 This means pCur<pKey
+**
+** *pRes==0 This means pCur==pKey for all nKey bytes
+**
+** *pRes>0 This means pCur>pKey
+**
+** When one key is an exact prefix of the other, the shorter key is
+** considered less than the longer one. In order to be equal the
+** keys must be exactly the same length. (The length of the pCur key
+** is the actual key length minus nIgnore bytes.)
+*/
+static int fileBtreeKeyCompare(
+ BtCursor *pCur, /* Pointer to entry to compare against */
+ const void *pKey, /* Key to compare against entry that pCur points to */
+ int nKey, /* Number of bytes in pKey */
+ int nIgnore, /* Ignore this many bytes at the end of pCur */
+ int *pResult /* Write the result here */
+){
+ Pgno nextPage;
+ int n, c, rc, nLocal;
+ Cell *pCell;
+ Btree *pBt = pCur->pBt;
+ const char *zKey = (const char*)pKey;
+
+ assert( pCur->pPage );
+ assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
+ pCell = pCur->pPage->apCell[pCur->idx];
+ nLocal = NKEY(pBt, pCell->h) - nIgnore;
+ if( nLocal<0 ) nLocal = 0;
+ n = nKey<nLocal ? nKey : nLocal;
+ if( n>MX_LOCAL_PAYLOAD ){
+ n = MX_LOCAL_PAYLOAD;
+ }
+ c = memcmp(pCell->aPayload, zKey, n);
+ if( c!=0 ){
+ *pResult = c;
+ return SQLITE_OK;
+ }
+ zKey += n;
+ nKey -= n;
+ nLocal -= n;
+ nextPage = SWAB32(pBt, pCell->ovfl);
+ while( nKey>0 && nLocal>0 ){
+ OverflowPage *pOvfl;
+ if( nextPage==0 ){
+ return SQLITE_CORRUPT;
+ }
+ rc = sqlitepager_get(pBt->pPager, nextPage, (void**)&pOvfl);
+ if( rc ){
+ return rc;
+ }
+ nextPage = SWAB32(pBt, pOvfl->iNext);
+ n = nKey<nLocal ? nKey : nLocal;
+ if( n>OVERFLOW_SIZE ){
+ n = OVERFLOW_SIZE;
+ }
+ c = memcmp(pOvfl->aPayload, zKey, n);
+ sqlitepager_unref(pOvfl);
+ if( c!=0 ){
+ *pResult = c;
+ return SQLITE_OK;
+ }
+ nKey -= n;
+ nLocal -= n;
+ zKey += n;
+ }
+ if( c==0 ){
+ c = nLocal - nKey;
+ }
+ *pResult = c;
+ return SQLITE_OK;
+}
+
+/*
+** Move the cursor down to a new child page. The newPgno argument is the
+** page number of the child page in the byte order of the disk image.
+*/
+static int moveToChild(BtCursor *pCur, int newPgno){
+ int rc;
+ MemPage *pNewPage;
+ Btree *pBt = pCur->pBt;
+
+ newPgno = SWAB32(pBt, newPgno);
+ rc = sqlitepager_get(pBt->pPager, newPgno, (void**)&pNewPage);
+ if( rc ) return rc;
+ rc = initPage(pBt, pNewPage, newPgno, pCur->pPage);
+ if( rc ) return rc;
+ assert( pCur->idx>=pCur->pPage->nCell
+ || pCur->pPage->apCell[pCur->idx]->h.leftChild==SWAB32(pBt,newPgno) );
+ assert( pCur->idx<pCur->pPage->nCell
+ || pCur->pPage->u.hdr.rightChild==SWAB32(pBt,newPgno) );
+ pNewPage->idxParent = pCur->idx;
+ pCur->pPage->idxShift = 0;
+ sqlitepager_unref(pCur->pPage);
+ pCur->pPage = pNewPage;
+ pCur->idx = 0;
+ if( pNewPage->nCell<1 ){
+ return SQLITE_CORRUPT;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Move the cursor up to the parent page.
+**
+** pCur->idx is set to the cell index that contains the pointer
+** to the page we are coming from. If we are coming from the
+** right-most child page then pCur->idx is set to one more than
+** the largest cell index.
+*/
+static void moveToParent(BtCursor *pCur){
+ Pgno oldPgno;
+ MemPage *pParent;
+ MemPage *pPage;
+ int idxParent;
+ pPage = pCur->pPage;
+ assert( pPage!=0 );
+ pParent = pPage->pParent;
+ assert( pParent!=0 );
+ idxParent = pPage->idxParent;
+ sqlitepager_ref(pParent);
+ sqlitepager_unref(pPage);
+ pCur->pPage = pParent;
+ assert( pParent->idxShift==0 );
+ if( pParent->idxShift==0 ){
+ pCur->idx = idxParent;
+#ifndef NDEBUG
+ /* Verify that pCur->idx is the correct index to point back to the child
+ ** page we just came from
+ */
+ oldPgno = SWAB32(pCur->pBt, sqlitepager_pagenumber(pPage));
+ if( pCur->idx<pParent->nCell ){
+ assert( pParent->apCell[idxParent]->h.leftChild==oldPgno );
+ }else{
+ assert( pParent->u.hdr.rightChild==oldPgno );
+ }
+#endif
+ }else{
+ /* The MemPage.idxShift flag indicates that cell indices might have
+ ** changed since idxParent was set and hence idxParent might be out
+ ** of date. So recompute the parent cell index by scanning all cells
+ ** and locating the one that points to the child we just came from.
+ */
+ int i;
+ pCur->idx = pParent->nCell;
+ oldPgno = SWAB32(pCur->pBt, sqlitepager_pagenumber(pPage));
+ for(i=0; i<pParent->nCell; i++){
+ if( pParent->apCell[i]->h.leftChild==oldPgno ){
+ pCur->idx = i;
+ break;
+ }
+ }
+ }
+}
+
+/*
+** Move the cursor to the root page
+*/
+static int moveToRoot(BtCursor *pCur){
+ MemPage *pNew;
+ int rc;
+ Btree *pBt = pCur->pBt;
+
+ rc = sqlitepager_get(pBt->pPager, pCur->pgnoRoot, (void**)&pNew);
+ if( rc ) return rc;
+ rc = initPage(pBt, pNew, pCur->pgnoRoot, 0);
+ if( rc ) return rc;
+ sqlitepager_unref(pCur->pPage);
+ pCur->pPage = pNew;
+ pCur->idx = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Move the cursor down to the left-most leaf entry beneath the
+** entry to which it is currently pointing.
+*/
+static int moveToLeftmost(BtCursor *pCur){
+ Pgno pgno;
+ int rc;
+
+ while( (pgno = pCur->pPage->apCell[pCur->idx]->h.leftChild)!=0 ){
+ rc = moveToChild(pCur, pgno);
+ if( rc ) return rc;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Move the cursor down to the right-most leaf entry beneath the
+** page to which it is currently pointing. Notice the difference
+** between moveToLeftmost() and moveToRightmost(). moveToLeftmost()
+** finds the left-most entry beneath the *entry* whereas moveToRightmost()
+** finds the right-most entry beneath the *page*.
+*/
+static int moveToRightmost(BtCursor *pCur){
+ Pgno pgno;
+ int rc;
+
+ while( (pgno = pCur->pPage->u.hdr.rightChild)!=0 ){
+ pCur->idx = pCur->pPage->nCell;
+ rc = moveToChild(pCur, pgno);
+ if( rc ) return rc;
+ }
+ pCur->idx = pCur->pPage->nCell - 1;
+ return SQLITE_OK;
+}
+
+/* Move the cursor to the first entry in the table. Return SQLITE_OK
+** on success. Set *pRes to 0 if the cursor actually points to something
+** or set *pRes to 1 if the table is empty.
+*/
+static int fileBtreeFirst(BtCursor *pCur, int *pRes){
+ int rc;
+ if( pCur->pPage==0 ) return SQLITE_ABORT;
+ rc = moveToRoot(pCur);
+ if( rc ) return rc;
+ if( pCur->pPage->nCell==0 ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ *pRes = 0;
+ rc = moveToLeftmost(pCur);
+ pCur->eSkip = SKIP_NONE;
+ return rc;
+}
+
+/* Move the cursor to the last entry in the table. Return SQLITE_OK
+** on success. Set *pRes to 0 if the cursor actually points to something
+** or set *pRes to 1 if the table is empty.
+*/
+static int fileBtreeLast(BtCursor *pCur, int *pRes){
+ int rc;
+ if( pCur->pPage==0 ) return SQLITE_ABORT;
+ rc = moveToRoot(pCur);
+ if( rc ) return rc;
+ assert( pCur->pPage->isInit );
+ if( pCur->pPage->nCell==0 ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ *pRes = 0;
+ rc = moveToRightmost(pCur);
+ pCur->eSkip = SKIP_NONE;
+ return rc;
+}
+
+/* Move the cursor so that it points to an entry near pKey.
+** Return a success code.
+**
+** If an exact match is not found, then the cursor is always
+** left pointing at a leaf page which would hold the entry if it
+** were present. The cursor might point to an entry that comes
+** before or after the key.
+**
+** The result of comparing the key with the entry to which the
+** cursor is left pointing is stored in pCur->iMatch. The same
+** value is also written to *pRes if pRes!=NULL. The meaning of
+** this value is as follows:
+**
+** *pRes<0 The cursor is left pointing at an entry that
+** is smaller than pKey or if the table is empty
+** and the cursor is therefore left point to nothing.
+**
+** *pRes==0 The cursor is left pointing at an entry that
+** exactly matches pKey.
+**
+** *pRes>0 The cursor is left pointing at an entry that
+** is larger than pKey.
+*/
+static
+int fileBtreeMoveto(BtCursor *pCur, const void *pKey, int nKey, int *pRes){
+ int rc;
+ if( pCur->pPage==0 ) return SQLITE_ABORT;
+ pCur->eSkip = SKIP_NONE;
+ rc = moveToRoot(pCur);
+ if( rc ) return rc;
+ for(;;){
+ int lwr, upr;
+ Pgno chldPg;
+ MemPage *pPage = pCur->pPage;
+ int c = -1; /* pRes return if table is empty must be -1 */
+ lwr = 0;
+ upr = pPage->nCell-1;
+ while( lwr<=upr ){
+ pCur->idx = (lwr+upr)/2;
+ rc = fileBtreeKeyCompare(pCur, pKey, nKey, 0, &c);
+ if( rc ) return rc;
+ if( c==0 ){
+ pCur->iMatch = c;
+ if( pRes ) *pRes = 0;
+ return SQLITE_OK;
+ }
+ if( c<0 ){
+ lwr = pCur->idx+1;
+ }else{
+ upr = pCur->idx-1;
+ }
+ }
+ assert( lwr==upr+1 );
+ assert( pPage->isInit );
+ if( lwr>=pPage->nCell ){
+ chldPg = pPage->u.hdr.rightChild;
+ }else{
+ chldPg = pPage->apCell[lwr]->h.leftChild;
+ }
+ if( chldPg==0 ){
+ pCur->iMatch = c;
+ if( pRes ) *pRes = c;
+ return SQLITE_OK;
+ }
+ pCur->idx = lwr;
+ rc = moveToChild(pCur, chldPg);
+ if( rc ) return rc;
+ }
+ /* NOT REACHED */
+}
+
+/*
+** Advance the cursor to the next entry in the database. If
+** successful then set *pRes=0. If the cursor
+** was already pointing to the last entry in the database before
+** this routine was called, then set *pRes=1.
+*/
+static int fileBtreeNext(BtCursor *pCur, int *pRes){
+ int rc;
+ MemPage *pPage = pCur->pPage;
+ assert( pRes!=0 );
+ if( pPage==0 ){
+ *pRes = 1;
+ return SQLITE_ABORT;
+ }
+ assert( pPage->isInit );
+ assert( pCur->eSkip!=SKIP_INVALID );
+ if( pPage->nCell==0 ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ assert( pCur->idx<pPage->nCell );
+ if( pCur->eSkip==SKIP_NEXT ){
+ pCur->eSkip = SKIP_NONE;
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ pCur->eSkip = SKIP_NONE;
+ pCur->idx++;
+ if( pCur->idx>=pPage->nCell ){
+ if( pPage->u.hdr.rightChild ){
+ rc = moveToChild(pCur, pPage->u.hdr.rightChild);
+ if( rc ) return rc;
+ rc = moveToLeftmost(pCur);
+ *pRes = 0;
+ return rc;
+ }
+ do{
+ if( pPage->pParent==0 ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ moveToParent(pCur);
+ pPage = pCur->pPage;
+ }while( pCur->idx>=pPage->nCell );
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ *pRes = 0;
+ if( pPage->u.hdr.rightChild==0 ){
+ return SQLITE_OK;
+ }
+ rc = moveToLeftmost(pCur);
+ return rc;
+}
+
+/*
+** Step the cursor to the back to the previous entry in the database. If
+** successful then set *pRes=0. If the cursor
+** was already pointing to the first entry in the database before
+** this routine was called, then set *pRes=1.
+*/
+static int fileBtreePrevious(BtCursor *pCur, int *pRes){
+ int rc;
+ Pgno pgno;
+ MemPage *pPage;
+ pPage = pCur->pPage;
+ if( pPage==0 ){
+ *pRes = 1;
+ return SQLITE_ABORT;
+ }
+ assert( pPage->isInit );
+ assert( pCur->eSkip!=SKIP_INVALID );
+ if( pPage->nCell==0 ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ if( pCur->eSkip==SKIP_PREV ){
+ pCur->eSkip = SKIP_NONE;
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ pCur->eSkip = SKIP_NONE;
+ assert( pCur->idx>=0 );
+ if( (pgno = pPage->apCell[pCur->idx]->h.leftChild)!=0 ){
+ rc = moveToChild(pCur, pgno);
+ if( rc ) return rc;
+ rc = moveToRightmost(pCur);
+ }else{
+ while( pCur->idx==0 ){
+ if( pPage->pParent==0 ){
+ if( pRes ) *pRes = 1;
+ return SQLITE_OK;
+ }
+ moveToParent(pCur);
+ pPage = pCur->pPage;
+ }
+ pCur->idx--;
+ rc = SQLITE_OK;
+ }
+ *pRes = 0;
+ return rc;
+}
+
+/*
+** Allocate a new page from the database file.
+**
+** The new page is marked as dirty. (In other words, sqlitepager_write()
+** has already been called on the new page.) The new page has also
+** been referenced and the calling routine is responsible for calling
+** sqlitepager_unref() on the new page when it is done.
+**
+** SQLITE_OK is returned on success. Any other return value indicates
+** an error. *ppPage and *pPgno are undefined in the event of an error.
+** Do not invoke sqlitepager_unref() on *ppPage if an error is returned.
+**
+** If the "nearby" parameter is not 0, then a (feeble) effort is made to
+** locate a page close to the page number "nearby". This can be used in an
+** attempt to keep related pages close to each other in the database file,
+** which in turn can make database access faster.
+*/
+static int allocatePage(Btree *pBt, MemPage **ppPage, Pgno *pPgno, Pgno nearby){
+ PageOne *pPage1 = pBt->page1;
+ int rc;
+ if( pPage1->freeList ){
+ OverflowPage *pOvfl;
+ FreelistInfo *pInfo;
+
+ rc = sqlitepager_write(pPage1);
+ if( rc ) return rc;
+ SWAB_ADD(pBt, pPage1->nFree, -1);
+ rc = sqlitepager_get(pBt->pPager, SWAB32(pBt, pPage1->freeList),
+ (void**)&pOvfl);
+ if( rc ) return rc;
+ rc = sqlitepager_write(pOvfl);
+ if( rc ){
+ sqlitepager_unref(pOvfl);
+ return rc;
+ }
+ pInfo = (FreelistInfo*)pOvfl->aPayload;
+ if( pInfo->nFree==0 ){
+ *pPgno = SWAB32(pBt, pPage1->freeList);
+ pPage1->freeList = pOvfl->iNext;
+ *ppPage = (MemPage*)pOvfl;
+ }else{
+ int closest, n;
+ n = SWAB32(pBt, pInfo->nFree);
+ if( n>1 && nearby>0 ){
+ int i, dist;
+ closest = 0;
+ dist = SWAB32(pBt, pInfo->aFree[0]) - nearby;
+ if( dist<0 ) dist = -dist;
+ for(i=1; i<n; i++){
+ int d2 = SWAB32(pBt, pInfo->aFree[i]) - nearby;
+ if( d2<0 ) d2 = -d2;
+ if( d2<dist ) closest = i;
+ }
+ }else{
+ closest = 0;
+ }
+ SWAB_ADD(pBt, pInfo->nFree, -1);
+ *pPgno = SWAB32(pBt, pInfo->aFree[closest]);
+ pInfo->aFree[closest] = pInfo->aFree[n-1];
+ rc = sqlitepager_get(pBt->pPager, *pPgno, (void**)ppPage);
+ sqlitepager_unref(pOvfl);
+ if( rc==SQLITE_OK ){
+ sqlitepager_dont_rollback(*ppPage);
+ rc = sqlitepager_write(*ppPage);
+ }
+ }
+ }else{
+ *pPgno = sqlitepager_pagecount(pBt->pPager) + 1;
+ rc = sqlitepager_get(pBt->pPager, *pPgno, (void**)ppPage);
+ if( rc ) return rc;
+ rc = sqlitepager_write(*ppPage);
+ }
+ return rc;
+}
+
+/*
+** Add a page of the database file to the freelist. Either pgno or
+** pPage but not both may be 0.
+**
+** sqlitepager_unref() is NOT called for pPage.
+*/
+static int freePage(Btree *pBt, void *pPage, Pgno pgno){
+ PageOne *pPage1 = pBt->page1;
+ OverflowPage *pOvfl = (OverflowPage*)pPage;
+ int rc;
+ int needUnref = 0;
+ MemPage *pMemPage;
+
+ if( pgno==0 ){
+ assert( pOvfl!=0 );
+ pgno = sqlitepager_pagenumber(pOvfl);
+ }
+ assert( pgno>2 );
+ assert( sqlitepager_pagenumber(pOvfl)==pgno );
+ pMemPage = (MemPage*)pPage;
+ pMemPage->isInit = 0;
+ if( pMemPage->pParent ){
+ sqlitepager_unref(pMemPage->pParent);
+ pMemPage->pParent = 0;
+ }
+ rc = sqlitepager_write(pPage1);
+ if( rc ){
+ return rc;
+ }
+ SWAB_ADD(pBt, pPage1->nFree, 1);
+ if( pPage1->nFree!=0 && pPage1->freeList!=0 ){
+ OverflowPage *pFreeIdx;
+ rc = sqlitepager_get(pBt->pPager, SWAB32(pBt, pPage1->freeList),
+ (void**)&pFreeIdx);
+ if( rc==SQLITE_OK ){
+ FreelistInfo *pInfo = (FreelistInfo*)pFreeIdx->aPayload;
+ int n = SWAB32(pBt, pInfo->nFree);
+ if( n<(sizeof(pInfo->aFree)/sizeof(pInfo->aFree[0])) ){
+ rc = sqlitepager_write(pFreeIdx);
+ if( rc==SQLITE_OK ){
+ pInfo->aFree[n] = SWAB32(pBt, pgno);
+ SWAB_ADD(pBt, pInfo->nFree, 1);
+ sqlitepager_unref(pFreeIdx);
+ sqlitepager_dont_write(pBt->pPager, pgno);
+ return rc;
+ }
+ }
+ sqlitepager_unref(pFreeIdx);
+ }
+ }
+ if( pOvfl==0 ){
+ assert( pgno>0 );
+ rc = sqlitepager_get(pBt->pPager, pgno, (void**)&pOvfl);
+ if( rc ) return rc;
+ needUnref = 1;
+ }
+ rc = sqlitepager_write(pOvfl);
+ if( rc ){
+ if( needUnref ) sqlitepager_unref(pOvfl);
+ return rc;
+ }
+ pOvfl->iNext = pPage1->freeList;
+ pPage1->freeList = SWAB32(pBt, pgno);
+ memset(pOvfl->aPayload, 0, OVERFLOW_SIZE);
+ if( needUnref ) rc = sqlitepager_unref(pOvfl);
+ return rc;
+}
+
+/*
+** Erase all the data out of a cell. This involves returning overflow
+** pages back the freelist.
+*/
+static int clearCell(Btree *pBt, Cell *pCell){
+ Pager *pPager = pBt->pPager;
+ OverflowPage *pOvfl;
+ Pgno ovfl, nextOvfl;
+ int rc;
+
+ if( NKEY(pBt, pCell->h) + NDATA(pBt, pCell->h) <= MX_LOCAL_PAYLOAD ){
+ return SQLITE_OK;
+ }
+ ovfl = SWAB32(pBt, pCell->ovfl);
+ pCell->ovfl = 0;
+ while( ovfl ){
+ rc = sqlitepager_get(pPager, ovfl, (void**)&pOvfl);
+ if( rc ) return rc;
+ nextOvfl = SWAB32(pBt, pOvfl->iNext);
+ rc = freePage(pBt, pOvfl, ovfl);
+ if( rc ) return rc;
+ sqlitepager_unref(pOvfl);
+ ovfl = nextOvfl;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Create a new cell from key and data. Overflow pages are allocated as
+** necessary and linked to this cell.
+*/
+static int fillInCell(
+ Btree *pBt, /* The whole Btree. Needed to allocate pages */
+ Cell *pCell, /* Populate this Cell structure */
+ const void *pKey, int nKey, /* The key */
+ const void *pData,int nData /* The data */
+){
+ OverflowPage *pOvfl, *pPrior;
+ Pgno *pNext;
+ int spaceLeft;
+ int n, rc;
+ int nPayload;
+ const char *pPayload;
+ char *pSpace;
+ Pgno nearby = 0;
+
+ pCell->h.leftChild = 0;
+ pCell->h.nKey = SWAB16(pBt, nKey & 0xffff);
+ pCell->h.nKeyHi = nKey >> 16;
+ pCell->h.nData = SWAB16(pBt, nData & 0xffff);
+ pCell->h.nDataHi = nData >> 16;
+ pCell->h.iNext = 0;
+
+ pNext = &pCell->ovfl;
+ pSpace = pCell->aPayload;
+ spaceLeft = MX_LOCAL_PAYLOAD;
+ pPayload = pKey;
+ pKey = 0;
+ nPayload = nKey;
+ pPrior = 0;
+ while( nPayload>0 ){
+ if( spaceLeft==0 ){
+ rc = allocatePage(pBt, (MemPage**)&pOvfl, pNext, nearby);
+ if( rc ){
+ *pNext = 0;
+ }else{
+ nearby = *pNext;
+ }
+ if( pPrior ) sqlitepager_unref(pPrior);
+ if( rc ){
+ clearCell(pBt, pCell);
+ return rc;
+ }
+ if( pBt->needSwab ) *pNext = swab32(*pNext);
+ pPrior = pOvfl;
+ spaceLeft = OVERFLOW_SIZE;
+ pSpace = pOvfl->aPayload;
+ pNext = &pOvfl->iNext;
+ }
+ n = nPayload;
+ if( n>spaceLeft ) n = spaceLeft;
+ memcpy(pSpace, pPayload, n);
+ nPayload -= n;
+ if( nPayload==0 && pData ){
+ pPayload = pData;
+ nPayload = nData;
+ pData = 0;
+ }else{
+ pPayload += n;
+ }
+ spaceLeft -= n;
+ pSpace += n;
+ }
+ *pNext = 0;
+ if( pPrior ){
+ sqlitepager_unref(pPrior);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Change the MemPage.pParent pointer on the page whose number is
+** given in the second argument so that MemPage.pParent holds the
+** pointer in the third argument.
+*/
+static void reparentPage(Pager *pPager, Pgno pgno, MemPage *pNewParent,int idx){
+ MemPage *pThis;
+
+ if( pgno==0 ) return;
+ assert( pPager!=0 );
+ pThis = sqlitepager_lookup(pPager, pgno);
+ if( pThis && pThis->isInit ){
+ if( pThis->pParent!=pNewParent ){
+ if( pThis->pParent ) sqlitepager_unref(pThis->pParent);
+ pThis->pParent = pNewParent;
+ if( pNewParent ) sqlitepager_ref(pNewParent);
+ }
+ pThis->idxParent = idx;
+ sqlitepager_unref(pThis);
+ }
+}
+
+/*
+** Reparent all children of the given page to be the given page.
+** In other words, for every child of pPage, invoke reparentPage()
+** to make sure that each child knows that pPage is its parent.
+**
+** This routine gets called after you memcpy() one page into
+** another.
+*/
+static void reparentChildPages(Btree *pBt, MemPage *pPage){
+ int i;
+ Pager *pPager = pBt->pPager;
+ for(i=0; i<pPage->nCell; i++){
+ reparentPage(pPager, SWAB32(pBt, pPage->apCell[i]->h.leftChild), pPage, i);
+ }
+ reparentPage(pPager, SWAB32(pBt, pPage->u.hdr.rightChild), pPage, i);
+ pPage->idxShift = 0;
+}
+
+/*
+** Remove the i-th cell from pPage. This routine effects pPage only.
+** The cell content is not freed or deallocated. It is assumed that
+** the cell content has been copied someplace else. This routine just
+** removes the reference to the cell from pPage.
+**
+** "sz" must be the number of bytes in the cell.
+**
+** Do not bother maintaining the integrity of the linked list of Cells.
+** Only the pPage->apCell[] array is important. The relinkCellList()
+** routine will be called soon after this routine in order to rebuild
+** the linked list.
+*/
+static void dropCell(Btree *pBt, MemPage *pPage, int idx, int sz){
+ int j;
+ assert( idx>=0 && idx<pPage->nCell );
+ assert( sz==cellSize(pBt, pPage->apCell[idx]) );
+ assert( sqlitepager_iswriteable(pPage) );
+ freeSpace(pBt, pPage, Addr(pPage->apCell[idx]) - Addr(pPage), sz);
+ for(j=idx; j<pPage->nCell-1; j++){
+ pPage->apCell[j] = pPage->apCell[j+1];
+ }
+ pPage->nCell--;
+ pPage->idxShift = 1;
+}
+
+/*
+** Insert a new cell on pPage at cell index "i". pCell points to the
+** content of the cell.
+**
+** If the cell content will fit on the page, then put it there. If it
+** will not fit, then just make pPage->apCell[i] point to the content
+** and set pPage->isOverfull.
+**
+** Do not bother maintaining the integrity of the linked list of Cells.
+** Only the pPage->apCell[] array is important. The relinkCellList()
+** routine will be called soon after this routine in order to rebuild
+** the linked list.
+*/
+static void insertCell(Btree *pBt, MemPage *pPage, int i, Cell *pCell, int sz){
+ int idx, j;
+ assert( i>=0 && i<=pPage->nCell );
+ assert( sz==cellSize(pBt, pCell) );
+ assert( sqlitepager_iswriteable(pPage) );
+ idx = allocateSpace(pBt, pPage, sz);
+ for(j=pPage->nCell; j>i; j--){
+ pPage->apCell[j] = pPage->apCell[j-1];
+ }
+ pPage->nCell++;
+ if( idx<=0 ){
+ pPage->isOverfull = 1;
+ pPage->apCell[i] = pCell;
+ }else{
+ memcpy(&pPage->u.aDisk[idx], pCell, sz);
+ pPage->apCell[i] = (Cell*)&pPage->u.aDisk[idx];
+ }
+ pPage->idxShift = 1;
+}
+
+/*
+** Rebuild the linked list of cells on a page so that the cells
+** occur in the order specified by the pPage->apCell[] array.
+** Invoke this routine once to repair damage after one or more
+** invocations of either insertCell() or dropCell().
+*/
+static void relinkCellList(Btree *pBt, MemPage *pPage){
+ int i;
+ u16 *pIdx;
+ assert( sqlitepager_iswriteable(pPage) );
+ pIdx = &pPage->u.hdr.firstCell;
+ for(i=0; i<pPage->nCell; i++){
+ int idx = Addr(pPage->apCell[i]) - Addr(pPage);
+ assert( idx>0 && idx<SQLITE_USABLE_SIZE );
+ *pIdx = SWAB16(pBt, idx);
+ pIdx = &pPage->apCell[i]->h.iNext;
+ }
+ *pIdx = 0;
+}
+
+/*
+** Make a copy of the contents of pFrom into pTo. The pFrom->apCell[]
+** pointers that point into pFrom->u.aDisk[] must be adjusted to point
+** into pTo->u.aDisk[] instead. But some pFrom->apCell[] entries might
+** not point to pFrom->u.aDisk[]. Those are unchanged.
+*/
+static void copyPage(MemPage *pTo, MemPage *pFrom){
+ uptr from, to;
+ int i;
+ memcpy(pTo->u.aDisk, pFrom->u.aDisk, SQLITE_USABLE_SIZE);
+ pTo->pParent = 0;
+ pTo->isInit = 1;
+ pTo->nCell = pFrom->nCell;
+ pTo->nFree = pFrom->nFree;
+ pTo->isOverfull = pFrom->isOverfull;
+ to = Addr(pTo);
+ from = Addr(pFrom);
+ for(i=0; i<pTo->nCell; i++){
+ uptr x = Addr(pFrom->apCell[i]);
+ if( x>from && x<from+SQLITE_USABLE_SIZE ){
+ *((uptr*)&pTo->apCell[i]) = x + to - from;
+ }else{
+ pTo->apCell[i] = pFrom->apCell[i];
+ }
+ }
+}
+
+/*
+** The following parameters determine how many adjacent pages get involved
+** in a balancing operation. NN is the number of neighbors on either side
+** of the page that participate in the balancing operation. NB is the
+** total number of pages that participate, including the target page and
+** NN neighbors on either side.
+**
+** The minimum value of NN is 1 (of course). Increasing NN above 1
+** (to 2 or 3) gives a modest improvement in SELECT and DELETE performance
+** in exchange for a larger degradation in INSERT and UPDATE performance.
+** The value of NN appears to give the best results overall.
+*/
+#define NN 1 /* Number of neighbors on either side of pPage */
+#define NB (NN*2+1) /* Total pages involved in the balance */
+
+/*
+** This routine redistributes Cells on pPage and up to two siblings
+** of pPage so that all pages have about the same amount of free space.
+** Usually one sibling on either side of pPage is used in the balancing,
+** though both siblings might come from one side if pPage is the first
+** or last child of its parent. If pPage has fewer than two siblings
+** (something which can only happen if pPage is the root page or a
+** child of root) then all available siblings participate in the balancing.
+**
+** The number of siblings of pPage might be increased or decreased by
+** one in an effort to keep pages between 66% and 100% full. The root page
+** is special and is allowed to be less than 66% full. If pPage is
+** the root page, then the depth of the tree might be increased
+** or decreased by one, as necessary, to keep the root page from being
+** overfull or empty.
+**
+** This routine calls relinkCellList() on its input page regardless of
+** whether or not it does any real balancing. Client routines will typically
+** invoke insertCell() or dropCell() before calling this routine, so we
+** need to call relinkCellList() to clean up the mess that those other
+** routines left behind.
+**
+** pCur is left pointing to the same cell as when this routine was called
+** even if that cell gets moved to a different page. pCur may be NULL.
+** Set the pCur parameter to NULL if you do not care about keeping track
+** of a cell as that will save this routine the work of keeping track of it.
+**
+** Note that when this routine is called, some of the Cells on pPage
+** might not actually be stored in pPage->u.aDisk[]. This can happen
+** if the page is overfull. Part of the job of this routine is to
+** make sure all Cells for pPage once again fit in pPage->u.aDisk[].
+**
+** In the course of balancing the siblings of pPage, the parent of pPage
+** might become overfull or underfull. If that happens, then this routine
+** is called recursively on the parent.
+**
+** If this routine fails for any reason, it might leave the database
+** in a corrupted state. So if this routine fails, the database should
+** be rolled back.
+*/
+static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
+ MemPage *pParent; /* The parent of pPage */
+ int nCell; /* Number of cells in apCell[] */
+ int nOld; /* Number of pages in apOld[] */
+ int nNew; /* Number of pages in apNew[] */
+ int nDiv; /* Number of cells in apDiv[] */
+ int i, j, k; /* Loop counters */
+ int idx; /* Index of pPage in pParent->apCell[] */
+ int nxDiv; /* Next divider slot in pParent->apCell[] */
+ int rc; /* The return code */
+ int iCur; /* apCell[iCur] is the cell of the cursor */
+ MemPage *pOldCurPage; /* The cursor originally points to this page */
+ int subtotal; /* Subtotal of bytes in cells on one page */
+ MemPage *extraUnref = 0; /* A page that needs to be unref-ed */
+ MemPage *apOld[NB]; /* pPage and up to two siblings */
+ Pgno pgnoOld[NB]; /* Page numbers for each page in apOld[] */
+ MemPage *apNew[NB+1]; /* pPage and up to NB siblings after balancing */
+ Pgno pgnoNew[NB+1]; /* Page numbers for each page in apNew[] */
+ int idxDiv[NB]; /* Indices of divider cells in pParent */
+ Cell *apDiv[NB]; /* Divider cells in pParent */
+ Cell aTemp[NB]; /* Temporary holding area for apDiv[] */
+ int cntNew[NB+1]; /* Index in apCell[] of cell after i-th page */
+ int szNew[NB+1]; /* Combined size of cells place on i-th page */
+ MemPage aOld[NB]; /* Temporary copies of pPage and its siblings */
+ Cell *apCell[(MX_CELL+2)*NB]; /* All cells from pages being balanced */
+ int szCell[(MX_CELL+2)*NB]; /* Local size of all cells */
+
+ /*
+ ** Return without doing any work if pPage is neither overfull nor
+ ** underfull.
+ */
+ assert( sqlitepager_iswriteable(pPage) );
+ if( !pPage->isOverfull && pPage->nFree<SQLITE_USABLE_SIZE/2
+ && pPage->nCell>=2){
+ relinkCellList(pBt, pPage);
+ return SQLITE_OK;
+ }
+
+ /*
+ ** Find the parent of the page to be balanceed.
+ ** If there is no parent, it means this page is the root page and
+ ** special rules apply.
+ */
+ pParent = pPage->pParent;
+ if( pParent==0 ){
+ Pgno pgnoChild;
+ MemPage *pChild;
+ assert( pPage->isInit );
+ if( pPage->nCell==0 ){
+ if( pPage->u.hdr.rightChild ){
+ /*
+ ** The root page is empty. Copy the one child page
+ ** into the root page and return. This reduces the depth
+ ** of the BTree by one.
+ */
+ pgnoChild = SWAB32(pBt, pPage->u.hdr.rightChild);
+ rc = sqlitepager_get(pBt->pPager, pgnoChild, (void**)&pChild);
+ if( rc ) return rc;
+ memcpy(pPage, pChild, SQLITE_USABLE_SIZE);
+ pPage->isInit = 0;
+ rc = initPage(pBt, pPage, sqlitepager_pagenumber(pPage), 0);
+ assert( rc==SQLITE_OK );
+ reparentChildPages(pBt, pPage);
+ if( pCur && pCur->pPage==pChild ){
+ sqlitepager_unref(pChild);
+ pCur->pPage = pPage;
+ sqlitepager_ref(pPage);
+ }
+ freePage(pBt, pChild, pgnoChild);
+ sqlitepager_unref(pChild);
+ }else{
+ relinkCellList(pBt, pPage);
+ }
+ return SQLITE_OK;
+ }
+ if( !pPage->isOverfull ){
+ /* It is OK for the root page to be less than half full.
+ */
+ relinkCellList(pBt, pPage);
+ return SQLITE_OK;
+ }
+ /*
+ ** If we get to here, it means the root page is overfull.
+ ** When this happens, Create a new child page and copy the
+ ** contents of the root into the child. Then make the root
+ ** page an empty page with rightChild pointing to the new
+ ** child. Then fall thru to the code below which will cause
+ ** the overfull child page to be split.
+ */
+ rc = sqlitepager_write(pPage);
+ if( rc ) return rc;
+ rc = allocatePage(pBt, &pChild, &pgnoChild, sqlitepager_pagenumber(pPage));
+ if( rc ) return rc;
+ assert( sqlitepager_iswriteable(pChild) );
+ copyPage(pChild, pPage);
+ pChild->pParent = pPage;
+ pChild->idxParent = 0;
+ sqlitepager_ref(pPage);
+ pChild->isOverfull = 1;
+ if( pCur && pCur->pPage==pPage ){
+ sqlitepager_unref(pPage);
+ pCur->pPage = pChild;
+ }else{
+ extraUnref = pChild;
+ }
+ zeroPage(pBt, pPage);
+ pPage->u.hdr.rightChild = SWAB32(pBt, pgnoChild);
+ pParent = pPage;
+ pPage = pChild;
+ }
+ rc = sqlitepager_write(pParent);
+ if( rc ) return rc;
+ assert( pParent->isInit );
+
+ /*
+ ** Find the Cell in the parent page whose h.leftChild points back
+ ** to pPage. The "idx" variable is the index of that cell. If pPage
+ ** is the rightmost child of pParent then set idx to pParent->nCell
+ */
+ if( pParent->idxShift ){
+ Pgno pgno, swabPgno;
+ pgno = sqlitepager_pagenumber(pPage);
+ swabPgno = SWAB32(pBt, pgno);
+ for(idx=0; idx<pParent->nCell; idx++){
+ if( pParent->apCell[idx]->h.leftChild==swabPgno ){
+ break;
+ }
+ }
+ assert( idx<pParent->nCell || pParent->u.hdr.rightChild==swabPgno );
+ }else{
+ idx = pPage->idxParent;
+ }
+
+ /*
+ ** Initialize variables so that it will be safe to jump
+ ** directly to balance_cleanup at any moment.
+ */
+ nOld = nNew = 0;
+ sqlitepager_ref(pParent);
+
+ /*
+ ** Find sibling pages to pPage and the Cells in pParent that divide
+ ** the siblings. An attempt is made to find NN siblings on either
+ ** side of pPage. More siblings are taken from one side, however, if
+ ** pPage there are fewer than NN siblings on the other side. If pParent
+ ** has NB or fewer children then all children of pParent are taken.
+ */
+ nxDiv = idx - NN;
+ if( nxDiv + NB > pParent->nCell ){
+ nxDiv = pParent->nCell - NB + 1;
+ }
+ if( nxDiv<0 ){
+ nxDiv = 0;
+ }
+ nDiv = 0;
+ for(i=0, k=nxDiv; i<NB; i++, k++){
+ if( k<pParent->nCell ){
+ idxDiv[i] = k;
+ apDiv[i] = pParent->apCell[k];
+ nDiv++;
+ pgnoOld[i] = SWAB32(pBt, apDiv[i]->h.leftChild);
+ }else if( k==pParent->nCell ){
+ pgnoOld[i] = SWAB32(pBt, pParent->u.hdr.rightChild);
+ }else{
+ break;
+ }
+ rc = sqlitepager_get(pBt->pPager, pgnoOld[i], (void**)&apOld[i]);
+ if( rc ) goto balance_cleanup;
+ rc = initPage(pBt, apOld[i], pgnoOld[i], pParent);
+ if( rc ) goto balance_cleanup;
+ apOld[i]->idxParent = k;
+ nOld++;
+ }
+
+ /*
+ ** Set iCur to be the index in apCell[] of the cell that the cursor
+ ** is pointing to. We will need this later on in order to keep the
+ ** cursor pointing at the same cell. If pCur points to a page that
+ ** has no involvement with this rebalancing, then set iCur to a large
+ ** number so that the iCur==j tests always fail in the main cell
+ ** distribution loop below.
+ */
+ if( pCur ){
+ iCur = 0;
+ for(i=0; i<nOld; i++){
+ if( pCur->pPage==apOld[i] ){
+ iCur += pCur->idx;
+ break;
+ }
+ iCur += apOld[i]->nCell;
+ if( i<nOld-1 && pCur->pPage==pParent && pCur->idx==idxDiv[i] ){
+ break;
+ }
+ iCur++;
+ }
+ pOldCurPage = pCur->pPage;
+ }
+
+ /*
+ ** Make copies of the content of pPage and its siblings into aOld[].
+ ** The rest of this function will use data from the copies rather
+ ** that the original pages since the original pages will be in the
+ ** process of being overwritten.
+ */
+ for(i=0; i<nOld; i++){
+ copyPage(&aOld[i], apOld[i]);
+ }
+
+ /*
+ ** Load pointers to all cells on sibling pages and the divider cells
+ ** into the local apCell[] array. Make copies of the divider cells
+ ** into aTemp[] and remove the the divider Cells from pParent.
+ */
+ nCell = 0;
+ for(i=0; i<nOld; i++){
+ MemPage *pOld = &aOld[i];
+ for(j=0; j<pOld->nCell; j++){
+ apCell[nCell] = pOld->apCell[j];
+ szCell[nCell] = cellSize(pBt, apCell[nCell]);
+ nCell++;
+ }
+ if( i<nOld-1 ){
+ szCell[nCell] = cellSize(pBt, apDiv[i]);
+ memcpy(&aTemp[i], apDiv[i], szCell[nCell]);
+ apCell[nCell] = &aTemp[i];
+ dropCell(pBt, pParent, nxDiv, szCell[nCell]);
+ assert( SWAB32(pBt, apCell[nCell]->h.leftChild)==pgnoOld[i] );
+ apCell[nCell]->h.leftChild = pOld->u.hdr.rightChild;
+ nCell++;
+ }
+ }
+
+ /*
+ ** Figure out the number of pages needed to hold all nCell cells.
+ ** Store this number in "k". Also compute szNew[] which is the total
+ ** size of all cells on the i-th page and cntNew[] which is the index
+ ** in apCell[] of the cell that divides path i from path i+1.
+ ** cntNew[k] should equal nCell.
+ **
+ ** This little patch of code is critical for keeping the tree
+ ** balanced.
+ */
+ for(subtotal=k=i=0; i<nCell; i++){
+ subtotal += szCell[i];
+ if( subtotal > USABLE_SPACE ){
+ szNew[k] = subtotal - szCell[i];
+ cntNew[k] = i;
+ subtotal = 0;
+ k++;
+ }
+ }
+ szNew[k] = subtotal;
+ cntNew[k] = nCell;
+ k++;
+ for(i=k-1; i>0; i--){
+ while( szNew[i]<USABLE_SPACE/2 ){
+ cntNew[i-1]--;
+ assert( cntNew[i-1]>0 );
+ szNew[i] += szCell[cntNew[i-1]];
+ szNew[i-1] -= szCell[cntNew[i-1]-1];
+ }
+ }
+ assert( cntNew[0]>0 );
+
+ /*
+ ** Allocate k new pages. Reuse old pages where possible.
+ */
+ for(i=0; i<k; i++){
+ if( i<nOld ){
+ apNew[i] = apOld[i];
+ pgnoNew[i] = pgnoOld[i];
+ apOld[i] = 0;
+ sqlitepager_write(apNew[i]);
+ }else{
+ rc = allocatePage(pBt, &apNew[i], &pgnoNew[i], pgnoNew[i-1]);
+ if( rc ) goto balance_cleanup;
+ }
+ nNew++;
+ zeroPage(pBt, apNew[i]);
+ apNew[i]->isInit = 1;
+ }
+
+ /* Free any old pages that were not reused as new pages.
+ */
+ while( i<nOld ){
+ rc = freePage(pBt, apOld[i], pgnoOld[i]);
+ if( rc ) goto balance_cleanup;
+ sqlitepager_unref(apOld[i]);
+ apOld[i] = 0;
+ i++;
+ }
+
+ /*
+ ** Put the new pages in accending order. This helps to
+ ** keep entries in the disk file in order so that a scan
+ ** of the table is a linear scan through the file. That
+ ** in turn helps the operating system to deliver pages
+ ** from the disk more rapidly.
+ **
+ ** An O(n^2) insertion sort algorithm is used, but since
+ ** n is never more than NB (a small constant), that should
+ ** not be a problem.
+ **
+ ** When NB==3, this one optimization makes the database
+ ** about 25% faster for large insertions and deletions.
+ */
+ for(i=0; i<k-1; i++){
+ int minV = pgnoNew[i];
+ int minI = i;
+ for(j=i+1; j<k; j++){
+ if( pgnoNew[j]<(unsigned)minV ){
+ minI = j;
+ minV = pgnoNew[j];
+ }
+ }
+ if( minI>i ){
+ int t;
+ MemPage *pT;
+ t = pgnoNew[i];
+ pT = apNew[i];
+ pgnoNew[i] = pgnoNew[minI];
+ apNew[i] = apNew[minI];
+ pgnoNew[minI] = t;
+ apNew[minI] = pT;
+ }
+ }
+
+ /*
+ ** Evenly distribute the data in apCell[] across the new pages.
+ ** Insert divider cells into pParent as necessary.
+ */
+ j = 0;
+ for(i=0; i<nNew; i++){
+ MemPage *pNew = apNew[i];
+ while( j<cntNew[i] ){
+ assert( pNew->nFree>=szCell[j] );
+ if( pCur && iCur==j ){ pCur->pPage = pNew; pCur->idx = pNew->nCell; }
+ insertCell(pBt, pNew, pNew->nCell, apCell[j], szCell[j]);
+ j++;
+ }
+ assert( pNew->nCell>0 );
+ assert( !pNew->isOverfull );
+ relinkCellList(pBt, pNew);
+ if( i<nNew-1 && j<nCell ){
+ pNew->u.hdr.rightChild = apCell[j]->h.leftChild;
+ apCell[j]->h.leftChild = SWAB32(pBt, pgnoNew[i]);
+ if( pCur && iCur==j ){ pCur->pPage = pParent; pCur->idx = nxDiv; }
+ insertCell(pBt, pParent, nxDiv, apCell[j], szCell[j]);
+ j++;
+ nxDiv++;
+ }
+ }
+ assert( j==nCell );
+ apNew[nNew-1]->u.hdr.rightChild = aOld[nOld-1].u.hdr.rightChild;
+ if( nxDiv==pParent->nCell ){
+ pParent->u.hdr.rightChild = SWAB32(pBt, pgnoNew[nNew-1]);
+ }else{
+ pParent->apCell[nxDiv]->h.leftChild = SWAB32(pBt, pgnoNew[nNew-1]);
+ }
+ if( pCur ){
+ if( j<=iCur && pCur->pPage==pParent && pCur->idx>idxDiv[nOld-1] ){
+ assert( pCur->pPage==pOldCurPage );
+ pCur->idx += nNew - nOld;
+ }else{
+ assert( pOldCurPage!=0 );
+ sqlitepager_ref(pCur->pPage);
+ sqlitepager_unref(pOldCurPage);
+ }
+ }
+
+ /*
+ ** Reparent children of all cells.
+ */
+ for(i=0; i<nNew; i++){
+ reparentChildPages(pBt, apNew[i]);
+ }
+ reparentChildPages(pBt, pParent);
+
+ /*
+ ** balance the parent page.
+ */
+ rc = balance(pBt, pParent, pCur);
+
+ /*
+ ** Cleanup before returning.
+ */
+balance_cleanup:
+ if( extraUnref ){
+ sqlitepager_unref(extraUnref);
+ }
+ for(i=0; i<nOld; i++){
+ if( apOld[i]!=0 && apOld[i]!=&aOld[i] ) sqlitepager_unref(apOld[i]);
+ }
+ for(i=0; i<nNew; i++){
+ sqlitepager_unref(apNew[i]);
+ }
+ if( pCur && pCur->pPage==0 ){
+ pCur->pPage = pParent;
+ pCur->idx = 0;
+ }else{
+ sqlitepager_unref(pParent);
+ }
+ return rc;
+}
+
+/*
+** This routine checks all cursors that point to the same table
+** as pCur points to. If any of those cursors were opened with
+** wrFlag==0 then this routine returns SQLITE_LOCKED. If all
+** cursors point to the same table were opened with wrFlag==1
+** then this routine returns SQLITE_OK.
+**
+** In addition to checking for read-locks (where a read-lock
+** means a cursor opened with wrFlag==0) this routine also moves
+** all cursors other than pCur so that they are pointing to the
+** first Cell on root page. This is necessary because an insert
+** or delete might change the number of cells on a page or delete
+** a page entirely and we do not want to leave any cursors
+** pointing to non-existant pages or cells.
+*/
+static int checkReadLocks(BtCursor *pCur){
+ BtCursor *p;
+ assert( pCur->wrFlag );
+ for(p=pCur->pShared; p!=pCur; p=p->pShared){
+ assert( p );
+ assert( p->pgnoRoot==pCur->pgnoRoot );
+ if( p->wrFlag==0 ) return SQLITE_LOCKED;
+ if( sqlitepager_pagenumber(p->pPage)!=p->pgnoRoot ){
+ moveToRoot(p);
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Insert a new record into the BTree. The key is given by (pKey,nKey)
+** and the data is given by (pData,nData). The cursor is used only to
+** define what database the record should be inserted into. The cursor
+** is left pointing at the new record.
+*/
+static int fileBtreeInsert(
+ BtCursor *pCur, /* Insert data into the table of this cursor */
+ const void *pKey, int nKey, /* The key of the new record */
+ const void *pData, int nData /* The data of the new record */
+){
+ Cell newCell;
+ int rc;
+ int loc;
+ int szNew;
+ MemPage *pPage;
+ Btree *pBt = pCur->pBt;
+
+ if( pCur->pPage==0 ){
+ return SQLITE_ABORT; /* A rollback destroyed this cursor */
+ }
+ if( !pBt->inTrans || nKey+nData==0 ){
+ /* Must start a transaction before doing an insert */
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ assert( !pBt->readOnly );
+ if( !pCur->wrFlag ){
+ return SQLITE_PERM; /* Cursor not open for writing */
+ }
+ if( checkReadLocks(pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+ rc = fileBtreeMoveto(pCur, pKey, nKey, &loc);
+ if( rc ) return rc;
+ pPage = pCur->pPage;
+ assert( pPage->isInit );
+ rc = sqlitepager_write(pPage);
+ if( rc ) return rc;
+ rc = fillInCell(pBt, &newCell, pKey, nKey, pData, nData);
+ if( rc ) return rc;
+ szNew = cellSize(pBt, &newCell);
+ if( loc==0 ){
+ newCell.h.leftChild = pPage->apCell[pCur->idx]->h.leftChild;
+ rc = clearCell(pBt, pPage->apCell[pCur->idx]);
+ if( rc ) return rc;
+ dropCell(pBt, pPage, pCur->idx, cellSize(pBt, pPage->apCell[pCur->idx]));
+ }else if( loc<0 && pPage->nCell>0 ){
+ assert( pPage->u.hdr.rightChild==0 ); /* Must be a leaf page */
+ pCur->idx++;
+ }else{
+ assert( pPage->u.hdr.rightChild==0 ); /* Must be a leaf page */
+ }
+ insertCell(pBt, pPage, pCur->idx, &newCell, szNew);
+ rc = balance(pCur->pBt, pPage, pCur);
+ /* sqliteBtreePageDump(pCur->pBt, pCur->pgnoRoot, 1); */
+ /* fflush(stdout); */
+ pCur->eSkip = SKIP_INVALID;
+ return rc;
+}
+
+/*
+** Delete the entry that the cursor is pointing to.
+**
+** The cursor is left pointing at either the next or the previous
+** entry. If the cursor is left pointing to the next entry, then
+** the pCur->eSkip flag is set to SKIP_NEXT which forces the next call to
+** sqliteBtreeNext() to be a no-op. That way, you can always call
+** sqliteBtreeNext() after a delete and the cursor will be left
+** pointing to the first entry after the deleted entry. Similarly,
+** pCur->eSkip is set to SKIP_PREV is the cursor is left pointing to
+** the entry prior to the deleted entry so that a subsequent call to
+** sqliteBtreePrevious() will always leave the cursor pointing at the
+** entry immediately before the one that was deleted.
+*/
+static int fileBtreeDelete(BtCursor *pCur){
+ MemPage *pPage = pCur->pPage;
+ Cell *pCell;
+ int rc;
+ Pgno pgnoChild;
+ Btree *pBt = pCur->pBt;
+
+ assert( pPage->isInit );
+ if( pCur->pPage==0 ){
+ return SQLITE_ABORT; /* A rollback destroyed this cursor */
+ }
+ if( !pBt->inTrans ){
+ /* Must start a transaction before doing a delete */
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ assert( !pBt->readOnly );
+ if( pCur->idx >= pPage->nCell ){
+ return SQLITE_ERROR; /* The cursor is not pointing to anything */
+ }
+ if( !pCur->wrFlag ){
+ return SQLITE_PERM; /* Did not open this cursor for writing */
+ }
+ if( checkReadLocks(pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+ rc = sqlitepager_write(pPage);
+ if( rc ) return rc;
+ pCell = pPage->apCell[pCur->idx];
+ pgnoChild = SWAB32(pBt, pCell->h.leftChild);
+ clearCell(pBt, pCell);
+ if( pgnoChild ){
+ /*
+ ** The entry we are about to delete is not a leaf so if we do not
+ ** do something we will leave a hole on an internal page.
+ ** We have to fill the hole by moving in a cell from a leaf. The
+ ** next Cell after the one to be deleted is guaranteed to exist and
+ ** to be a leaf so we can use it.
+ */
+ BtCursor leafCur;
+ Cell *pNext;
+ int szNext;
+ int notUsed;
+ getTempCursor(pCur, &leafCur);
+ rc = fileBtreeNext(&leafCur, &notUsed);
+ if( rc!=SQLITE_OK ){
+ if( rc!=SQLITE_NOMEM ) rc = SQLITE_CORRUPT;
+ return rc;
+ }
+ rc = sqlitepager_write(leafCur.pPage);
+ if( rc ) return rc;
+ dropCell(pBt, pPage, pCur->idx, cellSize(pBt, pCell));
+ pNext = leafCur.pPage->apCell[leafCur.idx];
+ szNext = cellSize(pBt, pNext);
+ pNext->h.leftChild = SWAB32(pBt, pgnoChild);
+ insertCell(pBt, pPage, pCur->idx, pNext, szNext);
+ rc = balance(pBt, pPage, pCur);
+ if( rc ) return rc;
+ pCur->eSkip = SKIP_NEXT;
+ dropCell(pBt, leafCur.pPage, leafCur.idx, szNext);
+ rc = balance(pBt, leafCur.pPage, pCur);
+ releaseTempCursor(&leafCur);
+ }else{
+ dropCell(pBt, pPage, pCur->idx, cellSize(pBt, pCell));
+ if( pCur->idx>=pPage->nCell ){
+ pCur->idx = pPage->nCell-1;
+ if( pCur->idx<0 ){
+ pCur->idx = 0;
+ pCur->eSkip = SKIP_NEXT;
+ }else{
+ pCur->eSkip = SKIP_PREV;
+ }
+ }else{
+ pCur->eSkip = SKIP_NEXT;
+ }
+ rc = balance(pBt, pPage, pCur);
+ }
+ return rc;
+}
+
+/*
+** Create a new BTree table. Write into *piTable the page
+** number for the root page of the new table.
+**
+** In the current implementation, BTree tables and BTree indices are the
+** the same. In the future, we may change this so that BTree tables
+** are restricted to having a 4-byte integer key and arbitrary data and
+** BTree indices are restricted to having an arbitrary key and no data.
+** But for now, this routine also serves to create indices.
+*/
+static int fileBtreeCreateTable(Btree *pBt, int *piTable){
+ MemPage *pRoot;
+ Pgno pgnoRoot;
+ int rc;
+ if( !pBt->inTrans ){
+ /* Must start a transaction first */
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ if( pBt->readOnly ){
+ return SQLITE_READONLY;
+ }
+ rc = allocatePage(pBt, &pRoot, &pgnoRoot, 0);
+ if( rc ) return rc;
+ assert( sqlitepager_iswriteable(pRoot) );
+ zeroPage(pBt, pRoot);
+ sqlitepager_unref(pRoot);
+ *piTable = (int)pgnoRoot;
+ return SQLITE_OK;
+}
+
+/*
+** Erase the given database page and all its children. Return
+** the page to the freelist.
+*/
+static int clearDatabasePage(Btree *pBt, Pgno pgno, int freePageFlag){
+ MemPage *pPage;
+ int rc;
+ Cell *pCell;
+ int idx;
+
+ rc = sqlitepager_get(pBt->pPager, pgno, (void**)&pPage);
+ if( rc ) return rc;
+ rc = sqlitepager_write(pPage);
+ if( rc ) return rc;
+ rc = initPage(pBt, pPage, pgno, 0);
+ if( rc ) return rc;
+ idx = SWAB16(pBt, pPage->u.hdr.firstCell);
+ while( idx>0 ){
+ pCell = (Cell*)&pPage->u.aDisk[idx];
+ idx = SWAB16(pBt, pCell->h.iNext);
+ if( pCell->h.leftChild ){
+ rc = clearDatabasePage(pBt, SWAB32(pBt, pCell->h.leftChild), 1);
+ if( rc ) return rc;
+ }
+ rc = clearCell(pBt, pCell);
+ if( rc ) return rc;
+ }
+ if( pPage->u.hdr.rightChild ){
+ rc = clearDatabasePage(pBt, SWAB32(pBt, pPage->u.hdr.rightChild), 1);
+ if( rc ) return rc;
+ }
+ if( freePageFlag ){
+ rc = freePage(pBt, pPage, pgno);
+ }else{
+ zeroPage(pBt, pPage);
+ }
+ sqlitepager_unref(pPage);
+ return rc;
+}
+
+/*
+** Delete all information from a single table in the database.
+*/
+static int fileBtreeClearTable(Btree *pBt, int iTable){
+ int rc;
+ BtCursor *pCur;
+ if( !pBt->inTrans ){
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+ if( pCur->pgnoRoot==(Pgno)iTable ){
+ if( pCur->wrFlag==0 ) return SQLITE_LOCKED;
+ moveToRoot(pCur);
+ }
+ }
+ rc = clearDatabasePage(pBt, (Pgno)iTable, 0);
+ if( rc ){
+ fileBtreeRollback(pBt);
+ }
+ return rc;
+}
+
+/*
+** Erase all information in a table and add the root of the table to
+** the freelist. Except, the root of the principle table (the one on
+** page 2) is never added to the freelist.
+*/
+static int fileBtreeDropTable(Btree *pBt, int iTable){
+ int rc;
+ MemPage *pPage;
+ BtCursor *pCur;
+ if( !pBt->inTrans ){
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+ if( pCur->pgnoRoot==(Pgno)iTable ){
+ return SQLITE_LOCKED; /* Cannot drop a table that has a cursor */
+ }
+ }
+ rc = sqlitepager_get(pBt->pPager, (Pgno)iTable, (void**)&pPage);
+ if( rc ) return rc;
+ rc = fileBtreeClearTable(pBt, iTable);
+ if( rc ) return rc;
+ if( iTable>2 ){
+ rc = freePage(pBt, pPage, iTable);
+ }else{
+ zeroPage(pBt, pPage);
+ }
+ sqlitepager_unref(pPage);
+ return rc;
+}
+
+#if 0 /* UNTESTED */
+/*
+** Copy all cell data from one database file into another.
+** pages back the freelist.
+*/
+static int copyCell(Btree *pBtFrom, BTree *pBtTo, Cell *pCell){
+ Pager *pFromPager = pBtFrom->pPager;
+ OverflowPage *pOvfl;
+ Pgno ovfl, nextOvfl;
+ Pgno *pPrev;
+ int rc = SQLITE_OK;
+ MemPage *pNew, *pPrevPg;
+ Pgno new;
+
+ if( NKEY(pBtTo, pCell->h) + NDATA(pBtTo, pCell->h) <= MX_LOCAL_PAYLOAD ){
+ return SQLITE_OK;
+ }
+ pPrev = &pCell->ovfl;
+ pPrevPg = 0;
+ ovfl = SWAB32(pBtTo, pCell->ovfl);
+ while( ovfl && rc==SQLITE_OK ){
+ rc = sqlitepager_get(pFromPager, ovfl, (void**)&pOvfl);
+ if( rc ) return rc;
+ nextOvfl = SWAB32(pBtFrom, pOvfl->iNext);
+ rc = allocatePage(pBtTo, &pNew, &new, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlitepager_write(pNew);
+ if( rc==SQLITE_OK ){
+ memcpy(pNew, pOvfl, SQLITE_USABLE_SIZE);
+ *pPrev = SWAB32(pBtTo, new);
+ if( pPrevPg ){
+ sqlitepager_unref(pPrevPg);
+ }
+ pPrev = &pOvfl->iNext;
+ pPrevPg = pNew;
+ }
+ }
+ sqlitepager_unref(pOvfl);
+ ovfl = nextOvfl;
+ }
+ if( pPrevPg ){
+ sqlitepager_unref(pPrevPg);
+ }
+ return rc;
+}
+#endif
+
+
+#if 0 /* UNTESTED */
+/*
+** Copy a page of data from one database over to another.
+*/
+static int copyDatabasePage(
+ Btree *pBtFrom,
+ Pgno pgnoFrom,
+ Btree *pBtTo,
+ Pgno *pTo
+){
+ MemPage *pPageFrom, *pPage;
+ Pgno to;
+ int rc;
+ Cell *pCell;
+ int idx;
+
+ rc = sqlitepager_get(pBtFrom->pPager, pgno, (void**)&pPageFrom);
+ if( rc ) return rc;
+ rc = allocatePage(pBt, &pPage, pTo, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlitepager_write(pPage);
+ }
+ if( rc==SQLITE_OK ){
+ memcpy(pPage, pPageFrom, SQLITE_USABLE_SIZE);
+ idx = SWAB16(pBt, pPage->u.hdr.firstCell);
+ while( idx>0 ){
+ pCell = (Cell*)&pPage->u.aDisk[idx];
+ idx = SWAB16(pBt, pCell->h.iNext);
+ if( pCell->h.leftChild ){
+ Pgno newChld;
+ rc = copyDatabasePage(pBtFrom, SWAB32(pBtFrom, pCell->h.leftChild),
+ pBtTo, &newChld);
+ if( rc ) return rc;
+ pCell->h.leftChild = SWAB32(pBtFrom, newChld);
+ }
+ rc = copyCell(pBtFrom, pBtTo, pCell);
+ if( rc ) return rc;
+ }
+ if( pPage->u.hdr.rightChild ){
+ Pgno newChld;
+ rc = copyDatabasePage(pBtFrom, SWAB32(pBtFrom, pPage->u.hdr.rightChild),
+ pBtTo, &newChld);
+ if( rc ) return rc;
+ pPage->u.hdr.rightChild = SWAB32(pBtTo, newChild);
+ }
+ }
+ sqlitepager_unref(pPage);
+ return rc;
+}
+#endif
+
+/*
+** Read the meta-information out of a database file.
+*/
+static int fileBtreeGetMeta(Btree *pBt, int *aMeta){
+ PageOne *pP1;
+ int rc;
+ int i;
+
+ rc = sqlitepager_get(pBt->pPager, 1, (void**)&pP1);
+ if( rc ) return rc;
+ aMeta[0] = SWAB32(pBt, pP1->nFree);
+ for(i=0; i<sizeof(pP1->aMeta)/sizeof(pP1->aMeta[0]); i++){
+ aMeta[i+1] = SWAB32(pBt, pP1->aMeta[i]);
+ }
+ sqlitepager_unref(pP1);
+ return SQLITE_OK;
+}
+
+/*
+** Write meta-information back into the database.
+*/
+static int fileBtreeUpdateMeta(Btree *pBt, int *aMeta){
+ PageOne *pP1;
+ int rc, i;
+ if( !pBt->inTrans ){
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ pP1 = pBt->page1;
+ rc = sqlitepager_write(pP1);
+ if( rc ) return rc;
+ for(i=0; i<sizeof(pP1->aMeta)/sizeof(pP1->aMeta[0]); i++){
+ pP1->aMeta[i] = SWAB32(pBt, aMeta[i+1]);
+ }
+ return SQLITE_OK;
+}
+
+/******************************************************************************
+** The complete implementation of the BTree subsystem is above this line.
+** All the code the follows is for testing and troubleshooting the BTree
+** subsystem. None of the code that follows is used during normal operation.
+******************************************************************************/
+
+/*
+** Print a disassembly of the given page on standard output. This routine
+** is used for debugging and testing only.
+*/
+#ifdef SQLITE_TEST
+static int fileBtreePageDump(Btree *pBt, int pgno, int recursive){
+ int rc;
+ MemPage *pPage;
+ int i, j;
+ int nFree;
+ u16 idx;
+ char range[20];
+ unsigned char payload[20];
+ rc = sqlitepager_get(pBt->pPager, (Pgno)pgno, (void**)&pPage);
+ if( rc ){
+ return rc;
+ }
+ if( recursive ) printf("PAGE %d:\n", pgno);
+ i = 0;
+ idx = SWAB16(pBt, pPage->u.hdr.firstCell);
+ while( idx>0 && idx<=SQLITE_USABLE_SIZE-MIN_CELL_SIZE ){
+ Cell *pCell = (Cell*)&pPage->u.aDisk[idx];
+ int sz = cellSize(pBt, pCell);
+ sprintf(range,"%d..%d", idx, idx+sz-1);
+ sz = NKEY(pBt, pCell->h) + NDATA(pBt, pCell->h);
+ if( sz>sizeof(payload)-1 ) sz = sizeof(payload)-1;
+ memcpy(payload, pCell->aPayload, sz);
+ for(j=0; j<sz; j++){
+ if( payload[j]<0x20 || payload[j]>0x7f ) payload[j] = '.';
+ }
+ payload[sz] = 0;
+ printf(
+ "cell %2d: i=%-10s chld=%-4d nk=%-4d nd=%-4d payload=%s\n",
+ i, range, (int)pCell->h.leftChild,
+ NKEY(pBt, pCell->h), NDATA(pBt, pCell->h),
+ payload
+ );
+ if( pPage->isInit && pPage->apCell[i]!=pCell ){
+ printf("**** apCell[%d] does not match on prior entry ****\n", i);
+ }
+ i++;
+ idx = SWAB16(pBt, pCell->h.iNext);
+ }
+ if( idx!=0 ){
+ printf("ERROR: next cell index out of range: %d\n", idx);
+ }
+ printf("right_child: %d\n", SWAB32(pBt, pPage->u.hdr.rightChild));
+ nFree = 0;
+ i = 0;
+ idx = SWAB16(pBt, pPage->u.hdr.firstFree);
+ while( idx>0 && idx<SQLITE_USABLE_SIZE ){
+ FreeBlk *p = (FreeBlk*)&pPage->u.aDisk[idx];
+ sprintf(range,"%d..%d", idx, idx+p->iSize-1);
+ nFree += SWAB16(pBt, p->iSize);
+ printf("freeblock %2d: i=%-10s size=%-4d total=%d\n",
+ i, range, SWAB16(pBt, p->iSize), nFree);
+ idx = SWAB16(pBt, p->iNext);
+ i++;
+ }
+ if( idx!=0 ){
+ printf("ERROR: next freeblock index out of range: %d\n", idx);
+ }
+ if( recursive && pPage->u.hdr.rightChild!=0 ){
+ idx = SWAB16(pBt, pPage->u.hdr.firstCell);
+ while( idx>0 && idx<SQLITE_USABLE_SIZE-MIN_CELL_SIZE ){
+ Cell *pCell = (Cell*)&pPage->u.aDisk[idx];
+ fileBtreePageDump(pBt, SWAB32(pBt, pCell->h.leftChild), 1);
+ idx = SWAB16(pBt, pCell->h.iNext);
+ }
+ fileBtreePageDump(pBt, SWAB32(pBt, pPage->u.hdr.rightChild), 1);
+ }
+ sqlitepager_unref(pPage);
+ return SQLITE_OK;
+}
+#endif
+
+#ifdef SQLITE_TEST
+/*
+** Fill aResult[] with information about the entry and page that the
+** cursor is pointing to.
+**
+** aResult[0] = The page number
+** aResult[1] = The entry number
+** aResult[2] = Total number of entries on this page
+** aResult[3] = Size of this entry
+** aResult[4] = Number of free bytes on this page
+** aResult[5] = Number of free blocks on the page
+** aResult[6] = Page number of the left child of this entry
+** aResult[7] = Page number of the right child for the whole page
+**
+** This routine is used for testing and debugging only.
+*/
+static int fileBtreeCursorDump(BtCursor *pCur, int *aResult){
+ int cnt, idx;
+ MemPage *pPage = pCur->pPage;
+ Btree *pBt = pCur->pBt;
+ aResult[0] = sqlitepager_pagenumber(pPage);
+ aResult[1] = pCur->idx;
+ aResult[2] = pPage->nCell;
+ if( pCur->idx>=0 && pCur->idx<pPage->nCell ){
+ aResult[3] = cellSize(pBt, pPage->apCell[pCur->idx]);
+ aResult[6] = SWAB32(pBt, pPage->apCell[pCur->idx]->h.leftChild);
+ }else{
+ aResult[3] = 0;
+ aResult[6] = 0;
+ }
+ aResult[4] = pPage->nFree;
+ cnt = 0;
+ idx = SWAB16(pBt, pPage->u.hdr.firstFree);
+ while( idx>0 && idx<SQLITE_USABLE_SIZE ){
+ cnt++;
+ idx = SWAB16(pBt, ((FreeBlk*)&pPage->u.aDisk[idx])->iNext);
+ }
+ aResult[5] = cnt;
+ aResult[7] = SWAB32(pBt, pPage->u.hdr.rightChild);
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** Return the pager associated with a BTree. This routine is used for
+** testing and debugging only.
+*/
+static Pager *fileBtreePager(Btree *pBt){
+ return pBt->pPager;
+}
+
+/*
+** This structure is passed around through all the sanity checking routines
+** in order to keep track of some global state information.
+*/
+typedef struct IntegrityCk IntegrityCk;
+struct IntegrityCk {
+ Btree *pBt; /* The tree being checked out */
+ Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */
+ int nPage; /* Number of pages in the database */
+ int *anRef; /* Number of times each page is referenced */
+ char *zErrMsg; /* An error message. NULL of no errors seen. */
+};
+
+/*
+** Append a message to the error message string.
+*/
+static void checkAppendMsg(IntegrityCk *pCheck, char *zMsg1, char *zMsg2){
+ if( pCheck->zErrMsg ){
+ char *zOld = pCheck->zErrMsg;
+ pCheck->zErrMsg = 0;
+ sqliteSetString(&pCheck->zErrMsg, zOld, "\n", zMsg1, zMsg2, (char*)0);
+ sqliteFree(zOld);
+ }else{
+ sqliteSetString(&pCheck->zErrMsg, zMsg1, zMsg2, (char*)0);
+ }
+}
+
+/*
+** Add 1 to the reference count for page iPage. If this is the second
+** reference to the page, add an error message to pCheck->zErrMsg.
+** Return 1 if there are 2 ore more references to the page and 0 if
+** if this is the first reference to the page.
+**
+** Also check that the page number is in bounds.
+*/
+static int checkRef(IntegrityCk *pCheck, int iPage, char *zContext){
+ if( iPage==0 ) return 1;
+ if( iPage>pCheck->nPage || iPage<0 ){
+ char zBuf[100];
+ sprintf(zBuf, "invalid page number %d", iPage);
+ checkAppendMsg(pCheck, zContext, zBuf);
+ return 1;
+ }
+ if( pCheck->anRef[iPage]==1 ){
+ char zBuf[100];
+ sprintf(zBuf, "2nd reference to page %d", iPage);
+ checkAppendMsg(pCheck, zContext, zBuf);
+ return 1;
+ }
+ return (pCheck->anRef[iPage]++)>1;
+}
+
+/*
+** Check the integrity of the freelist or of an overflow page list.
+** Verify that the number of pages on the list is N.
+*/
+static void checkList(
+ IntegrityCk *pCheck, /* Integrity checking context */
+ int isFreeList, /* True for a freelist. False for overflow page list */
+ int iPage, /* Page number for first page in the list */
+ int N, /* Expected number of pages in the list */
+ char *zContext /* Context for error messages */
+){
+ int i;
+ char zMsg[100];
+ while( N-- > 0 ){
+ OverflowPage *pOvfl;
+ if( iPage<1 ){
+ sprintf(zMsg, "%d pages missing from overflow list", N+1);
+ checkAppendMsg(pCheck, zContext, zMsg);
+ break;
+ }
+ if( checkRef(pCheck, iPage, zContext) ) break;
+ if( sqlitepager_get(pCheck->pPager, (Pgno)iPage, (void**)&pOvfl) ){
+ sprintf(zMsg, "failed to get page %d", iPage);
+ checkAppendMsg(pCheck, zContext, zMsg);
+ break;
+ }
+ if( isFreeList ){
+ FreelistInfo *pInfo = (FreelistInfo*)pOvfl->aPayload;
+ int n = SWAB32(pCheck->pBt, pInfo->nFree);
+ for(i=0; i<n; i++){
+ checkRef(pCheck, SWAB32(pCheck->pBt, pInfo->aFree[i]), zContext);
+ }
+ N -= n;
+ }
+ iPage = SWAB32(pCheck->pBt, pOvfl->iNext);
+ sqlitepager_unref(pOvfl);
+ }
+}
+
+/*
+** Return negative if zKey1<zKey2.
+** Return zero if zKey1==zKey2.
+** Return positive if zKey1>zKey2.
+*/
+static int keyCompare(
+ const char *zKey1, int nKey1,
+ const char *zKey2, int nKey2
+){
+ int min = nKey1>nKey2 ? nKey2 : nKey1;
+ int c = memcmp(zKey1, zKey2, min);
+ if( c==0 ){
+ c = nKey1 - nKey2;
+ }
+ return c;
+}
+
+/*
+** Do various sanity checks on a single page of a tree. Return
+** the tree depth. Root pages return 0. Parents of root pages
+** return 1, and so forth.
+**
+** These checks are done:
+**
+** 1. Make sure that cells and freeblocks do not overlap
+** but combine to completely cover the page.
+** 2. Make sure cell keys are in order.
+** 3. Make sure no key is less than or equal to zLowerBound.
+** 4. Make sure no key is greater than or equal to zUpperBound.
+** 5. Check the integrity of overflow pages.
+** 6. Recursively call checkTreePage on all children.
+** 7. Verify that the depth of all children is the same.
+** 8. Make sure this page is at least 33% full or else it is
+** the root of the tree.
+*/
+static int checkTreePage(
+ IntegrityCk *pCheck, /* Context for the sanity check */
+ int iPage, /* Page number of the page to check */
+ MemPage *pParent, /* Parent page */
+ char *zParentContext, /* Parent context */
+ char *zLowerBound, /* All keys should be greater than this, if not NULL */
+ int nLower, /* Number of characters in zLowerBound */
+ char *zUpperBound, /* All keys should be less than this, if not NULL */
+ int nUpper /* Number of characters in zUpperBound */
+){
+ MemPage *pPage;
+ int i, rc, depth, d2, pgno;
+ char *zKey1, *zKey2;
+ int nKey1, nKey2;
+ BtCursor cur;
+ Btree *pBt;
+ char zMsg[100];
+ char zContext[100];
+ char hit[SQLITE_USABLE_SIZE];
+
+ /* Check that the page exists
+ */
+ cur.pBt = pBt = pCheck->pBt;
+ if( iPage==0 ) return 0;
+ if( checkRef(pCheck, iPage, zParentContext) ) return 0;
+ sprintf(zContext, "On tree page %d: ", iPage);
+ if( (rc = sqlitepager_get(pCheck->pPager, (Pgno)iPage, (void**)&pPage))!=0 ){
+ sprintf(zMsg, "unable to get the page. error code=%d", rc);
+ checkAppendMsg(pCheck, zContext, zMsg);
+ return 0;
+ }
+ if( (rc = initPage(pBt, pPage, (Pgno)iPage, pParent))!=0 ){
+ sprintf(zMsg, "initPage() returns error code %d", rc);
+ checkAppendMsg(pCheck, zContext, zMsg);
+ sqlitepager_unref(pPage);
+ return 0;
+ }
+
+ /* Check out all the cells.
+ */
+ depth = 0;
+ if( zLowerBound ){
+ zKey1 = sqliteMalloc( nLower+1 );
+ memcpy(zKey1, zLowerBound, nLower);
+ zKey1[nLower] = 0;
+ }else{
+ zKey1 = 0;
+ }
+ nKey1 = nLower;
+ cur.pPage = pPage;
+ for(i=0; i<pPage->nCell; i++){
+ Cell *pCell = pPage->apCell[i];
+ int sz;
+
+ /* Check payload overflow pages
+ */
+ nKey2 = NKEY(pBt, pCell->h);
+ sz = nKey2 + NDATA(pBt, pCell->h);
+ sprintf(zContext, "On page %d cell %d: ", iPage, i);
+ if( sz>MX_LOCAL_PAYLOAD ){
+ int nPage = (sz - MX_LOCAL_PAYLOAD + OVERFLOW_SIZE - 1)/OVERFLOW_SIZE;
+ checkList(pCheck, 0, SWAB32(pBt, pCell->ovfl), nPage, zContext);
+ }
+
+ /* Check that keys are in the right order
+ */
+ cur.idx = i;
+ zKey2 = sqliteMallocRaw( nKey2+1 );
+ getPayload(&cur, 0, nKey2, zKey2);
+ if( zKey1 && keyCompare(zKey1, nKey1, zKey2, nKey2)>=0 ){
+ checkAppendMsg(pCheck, zContext, "Key is out of order");
+ }
+
+ /* Check sanity of left child page.
+ */
+ pgno = SWAB32(pBt, pCell->h.leftChild);
+ d2 = checkTreePage(pCheck, pgno, pPage, zContext, zKey1,nKey1,zKey2,nKey2);
+ if( i>0 && d2!=depth ){
+ checkAppendMsg(pCheck, zContext, "Child page depth differs");
+ }
+ depth = d2;
+ sqliteFree(zKey1);
+ zKey1 = zKey2;
+ nKey1 = nKey2;
+ }
+ pgno = SWAB32(pBt, pPage->u.hdr.rightChild);
+ sprintf(zContext, "On page %d at right child: ", iPage);
+ checkTreePage(pCheck, pgno, pPage, zContext, zKey1,nKey1,zUpperBound,nUpper);
+ sqliteFree(zKey1);
+
+ /* Check for complete coverage of the page
+ */
+ memset(hit, 0, sizeof(hit));
+ memset(hit, 1, sizeof(PageHdr));
+ for(i=SWAB16(pBt, pPage->u.hdr.firstCell); i>0 && i<SQLITE_USABLE_SIZE; ){
+ Cell *pCell = (Cell*)&pPage->u.aDisk[i];
+ int j;
+ for(j=i+cellSize(pBt, pCell)-1; j>=i; j--) hit[j]++;
+ i = SWAB16(pBt, pCell->h.iNext);
+ }
+ for(i=SWAB16(pBt,pPage->u.hdr.firstFree); i>0 && i<SQLITE_USABLE_SIZE; ){
+ FreeBlk *pFBlk = (FreeBlk*)&pPage->u.aDisk[i];
+ int j;
+ for(j=i+SWAB16(pBt,pFBlk->iSize)-1; j>=i; j--) hit[j]++;
+ i = SWAB16(pBt,pFBlk->iNext);
+ }
+ for(i=0; i<SQLITE_USABLE_SIZE; i++){
+ if( hit[i]==0 ){
+ sprintf(zMsg, "Unused space at byte %d of page %d", i, iPage);
+ checkAppendMsg(pCheck, zMsg, 0);
+ break;
+ }else if( hit[i]>1 ){
+ sprintf(zMsg, "Multiple uses for byte %d of page %d", i, iPage);
+ checkAppendMsg(pCheck, zMsg, 0);
+ break;
+ }
+ }
+
+ /* Check that free space is kept to a minimum
+ */
+#if 0
+ if( pParent && pParent->nCell>2 && pPage->nFree>3*SQLITE_USABLE_SIZE/4 ){
+ sprintf(zMsg, "free space (%d) greater than max (%d)", pPage->nFree,
+ SQLITE_USABLE_SIZE/3);
+ checkAppendMsg(pCheck, zContext, zMsg);
+ }
+#endif
+
+ sqlitepager_unref(pPage);
+ return depth;
+}
+
+/*
+** This routine does a complete check of the given BTree file. aRoot[] is
+** an array of pages numbers were each page number is the root page of
+** a table. nRoot is the number of entries in aRoot.
+**
+** If everything checks out, this routine returns NULL. If something is
+** amiss, an error message is written into memory obtained from malloc()
+** and a pointer to that error message is returned. The calling function
+** is responsible for freeing the error message when it is done.
+*/
+char *fileBtreeIntegrityCheck(Btree *pBt, int *aRoot, int nRoot){
+ int i;
+ int nRef;
+ IntegrityCk sCheck;
+
+ nRef = *sqlitepager_stats(pBt->pPager);
+ if( lockBtree(pBt)!=SQLITE_OK ){
+ return sqliteStrDup("Unable to acquire a read lock on the database");
+ }
+ sCheck.pBt = pBt;
+ sCheck.pPager = pBt->pPager;
+ sCheck.nPage = sqlitepager_pagecount(sCheck.pPager);
+ if( sCheck.nPage==0 ){
+ unlockBtreeIfUnused(pBt);
+ return 0;
+ }
+ sCheck.anRef = sqliteMallocRaw( (sCheck.nPage+1)*sizeof(sCheck.anRef[0]) );
+ sCheck.anRef[1] = 1;
+ for(i=2; i<=sCheck.nPage; i++){ sCheck.anRef[i] = 0; }
+ sCheck.zErrMsg = 0;
+
+ /* Check the integrity of the freelist
+ */
+ checkList(&sCheck, 1, SWAB32(pBt, pBt->page1->freeList),
+ SWAB32(pBt, pBt->page1->nFree), "Main freelist: ");
+
+ /* Check all the tables.
+ */
+ for(i=0; i<nRoot; i++){
+ if( aRoot[i]==0 ) continue;
+ checkTreePage(&sCheck, aRoot[i], 0, "List of tree roots: ", 0,0,0,0);
+ }
+
+ /* Make sure every page in the file is referenced
+ */
+ for(i=1; i<=sCheck.nPage; i++){
+ if( sCheck.anRef[i]==0 ){
+ char zBuf[100];
+ sprintf(zBuf, "Page %d is never used", i);
+ checkAppendMsg(&sCheck, zBuf, 0);
+ }
+ }
+
+ /* Make sure this analysis did not leave any unref() pages
+ */
+ unlockBtreeIfUnused(pBt);
+ if( nRef != *sqlitepager_stats(pBt->pPager) ){
+ char zBuf[100];
+ sprintf(zBuf,
+ "Outstanding page count goes from %d to %d during this analysis",
+ nRef, *sqlitepager_stats(pBt->pPager)
+ );
+ checkAppendMsg(&sCheck, zBuf, 0);
+ }
+
+ /* Clean up and report errors.
+ */
+ sqliteFree(sCheck.anRef);
+ return sCheck.zErrMsg;
+}
+
+/*
+** Return the full pathname of the underlying database file.
+*/
+static const char *fileBtreeGetFilename(Btree *pBt){
+ assert( pBt->pPager!=0 );
+ return sqlitepager_filename(pBt->pPager);
+}
+
+/*
+** Copy the complete content of pBtFrom into pBtTo. A transaction
+** must be active for both files.
+**
+** The size of file pBtFrom may be reduced by this operation.
+** If anything goes wrong, the transaction on pBtFrom is rolled back.
+*/
+static int fileBtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){
+ int rc = SQLITE_OK;
+ Pgno i, nPage, nToPage;
+
+ if( !pBtTo->inTrans || !pBtFrom->inTrans ) return SQLITE_ERROR;
+ if( pBtTo->needSwab!=pBtFrom->needSwab ) return SQLITE_ERROR;
+ if( pBtTo->pCursor ) return SQLITE_BUSY;
+ memcpy(pBtTo->page1, pBtFrom->page1, SQLITE_USABLE_SIZE);
+ rc = sqlitepager_overwrite(pBtTo->pPager, 1, pBtFrom->page1);
+ nToPage = sqlitepager_pagecount(pBtTo->pPager);
+ nPage = sqlitepager_pagecount(pBtFrom->pPager);
+ for(i=2; rc==SQLITE_OK && i<=nPage; i++){
+ void *pPage;
+ rc = sqlitepager_get(pBtFrom->pPager, i, &pPage);
+ if( rc ) break;
+ rc = sqlitepager_overwrite(pBtTo->pPager, i, pPage);
+ if( rc ) break;
+ sqlitepager_unref(pPage);
+ }
+ for(i=nPage+1; rc==SQLITE_OK && i<=nToPage; i++){
+ void *pPage;
+ rc = sqlitepager_get(pBtTo->pPager, i, &pPage);
+ if( rc ) break;
+ rc = sqlitepager_write(pPage);
+ sqlitepager_unref(pPage);
+ sqlitepager_dont_write(pBtTo->pPager, i);
+ }
+ if( !rc && nPage<nToPage ){
+ rc = sqlitepager_truncate(pBtTo->pPager, nPage);
+ }
+ if( rc ){
+ fileBtreeRollback(pBtTo);
+ }
+ return rc;
+}
+
+/*
+** The following tables contain pointers to all of the interface
+** routines for this implementation of the B*Tree backend. To
+** substitute a different implemention of the backend, one has merely
+** to provide pointers to alternative functions in similar tables.
+*/
+static BtOps sqliteBtreeOps = {
+ fileBtreeClose,
+ fileBtreeSetCacheSize,
+ fileBtreeSetSafetyLevel,
+ fileBtreeBeginTrans,
+ fileBtreeCommit,
+ fileBtreeRollback,
+ fileBtreeBeginCkpt,
+ fileBtreeCommitCkpt,
+ fileBtreeRollbackCkpt,
+ fileBtreeCreateTable,
+ fileBtreeCreateTable, /* Really sqliteBtreeCreateIndex() */
+ fileBtreeDropTable,
+ fileBtreeClearTable,
+ fileBtreeCursor,
+ fileBtreeGetMeta,
+ fileBtreeUpdateMeta,
+ fileBtreeIntegrityCheck,
+ fileBtreeGetFilename,
+ fileBtreeCopyFile,
+ fileBtreePager,
+#ifdef SQLITE_TEST
+ fileBtreePageDump,
+#endif
+};
+static BtCursorOps sqliteBtreeCursorOps = {
+ fileBtreeMoveto,
+ fileBtreeDelete,
+ fileBtreeInsert,
+ fileBtreeFirst,
+ fileBtreeLast,
+ fileBtreeNext,
+ fileBtreePrevious,
+ fileBtreeKeySize,
+ fileBtreeKey,
+ fileBtreeKeyCompare,
+ fileBtreeDataSize,
+ fileBtreeData,
+ fileBtreeCloseCursor,
+#ifdef SQLITE_TEST
+ fileBtreeCursorDump,
+#endif
+};
diff --git a/src/libs/sqlite2/btree.h b/src/libs/sqlite2/btree.h
new file mode 100644
index 00000000..5a11b60e
--- /dev/null
+++ b/src/libs/sqlite2/btree.h
@@ -0,0 +1,156 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the sqlite B-Tree file
+** subsystem. See comments in the source code for a detailed description
+** of what each interface routine does.
+**
+** @(#) $Id: btree.h 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#ifndef _BTREE_H_
+#define _BTREE_H_
+
+/*
+** Forward declarations of structure
+*/
+typedef struct Btree Btree;
+typedef struct BtCursor BtCursor;
+typedef struct BtOps BtOps;
+typedef struct BtCursorOps BtCursorOps;
+
+
+/*
+** An instance of the following structure contains pointers to all
+** methods against an open BTree. Alternative BTree implementations
+** (examples: file based versus in-memory) can be created by substituting
+** different methods. Users of the BTree cannot tell the difference.
+**
+** In C++ we could do this by defining a virtual base class and then
+** creating subclasses for each different implementation. But this is
+** C not C++ so we have to be a little more explicit.
+*/
+struct BtOps {
+ int (*Close)(Btree*);
+ int (*SetCacheSize)(Btree*, int);
+ int (*SetSafetyLevel)(Btree*, int);
+ int (*BeginTrans)(Btree*);
+ int (*Commit)(Btree*);
+ int (*Rollback)(Btree*);
+ int (*BeginCkpt)(Btree*);
+ int (*CommitCkpt)(Btree*);
+ int (*RollbackCkpt)(Btree*);
+ int (*CreateTable)(Btree*, int*);
+ int (*CreateIndex)(Btree*, int*);
+ int (*DropTable)(Btree*, int);
+ int (*ClearTable)(Btree*, int);
+ int (*Cursor)(Btree*, int iTable, int wrFlag, BtCursor **ppCur);
+ int (*GetMeta)(Btree*, int*);
+ int (*UpdateMeta)(Btree*, int*);
+ char *(*IntegrityCheck)(Btree*, int*, int);
+ const char *(*GetFilename)(Btree*);
+ int (*Copyfile)(Btree*,Btree*);
+ struct Pager *(*Pager)(Btree*);
+#ifdef SQLITE_TEST
+ int (*PageDump)(Btree*, int, int);
+#endif
+};
+
+/*
+** An instance of this structure defines all of the methods that can
+** be executed against a cursor.
+*/
+struct BtCursorOps {
+ int (*Moveto)(BtCursor*, const void *pKey, int nKey, int *pRes);
+ int (*Delete)(BtCursor*);
+ int (*Insert)(BtCursor*, const void *pKey, int nKey,
+ const void *pData, int nData);
+ int (*First)(BtCursor*, int *pRes);
+ int (*Last)(BtCursor*, int *pRes);
+ int (*Next)(BtCursor*, int *pRes);
+ int (*Previous)(BtCursor*, int *pRes);
+ int (*KeySize)(BtCursor*, int *pSize);
+ int (*Key)(BtCursor*, int offset, int amt, char *zBuf);
+ int (*KeyCompare)(BtCursor*, const void *pKey, int nKey,
+ int nIgnore, int *pRes);
+ int (*DataSize)(BtCursor*, int *pSize);
+ int (*Data)(BtCursor*, int offset, int amt, char *zBuf);
+ int (*CloseCursor)(BtCursor*);
+#ifdef SQLITE_TEST
+ int (*CursorDump)(BtCursor*, int*);
+#endif
+};
+
+/*
+** The number of 4-byte "meta" values contained on the first page of each
+** database file.
+*/
+#define SQLITE_N_BTREE_META 10
+
+int sqliteBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree);
+int sqliteRbtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree);
+
+#define btOps(pBt) (*((BtOps **)(pBt)))
+#define btCOps(pCur) (*((BtCursorOps **)(pCur)))
+
+#define sqliteBtreeClose(pBt) (btOps(pBt)->Close(pBt))
+#define sqliteBtreeSetCacheSize(pBt, sz) (btOps(pBt)->SetCacheSize(pBt, sz))
+#define sqliteBtreeSetSafetyLevel(pBt, sl) (btOps(pBt)->SetSafetyLevel(pBt, sl))
+#define sqliteBtreeBeginTrans(pBt) (btOps(pBt)->BeginTrans(pBt))
+#define sqliteBtreeCommit(pBt) (btOps(pBt)->Commit(pBt))
+#define sqliteBtreeRollback(pBt) (btOps(pBt)->Rollback(pBt))
+#define sqliteBtreeBeginCkpt(pBt) (btOps(pBt)->BeginCkpt(pBt))
+#define sqliteBtreeCommitCkpt(pBt) (btOps(pBt)->CommitCkpt(pBt))
+#define sqliteBtreeRollbackCkpt(pBt) (btOps(pBt)->RollbackCkpt(pBt))
+#define sqliteBtreeCreateTable(pBt,piTable)\
+ (btOps(pBt)->CreateTable(pBt,piTable))
+#define sqliteBtreeCreateIndex(pBt, piIndex)\
+ (btOps(pBt)->CreateIndex(pBt, piIndex))
+#define sqliteBtreeDropTable(pBt, iTable) (btOps(pBt)->DropTable(pBt, iTable))
+#define sqliteBtreeClearTable(pBt, iTable)\
+ (btOps(pBt)->ClearTable(pBt, iTable))
+#define sqliteBtreeCursor(pBt, iTable, wrFlag, ppCur)\
+ (btOps(pBt)->Cursor(pBt, iTable, wrFlag, ppCur))
+#define sqliteBtreeMoveto(pCur, pKey, nKey, pRes)\
+ (btCOps(pCur)->Moveto(pCur, pKey, nKey, pRes))
+#define sqliteBtreeDelete(pCur) (btCOps(pCur)->Delete(pCur))
+#define sqliteBtreeInsert(pCur, pKey, nKey, pData, nData) \
+ (btCOps(pCur)->Insert(pCur, pKey, nKey, pData, nData))
+#define sqliteBtreeFirst(pCur, pRes) (btCOps(pCur)->First(pCur, pRes))
+#define sqliteBtreeLast(pCur, pRes) (btCOps(pCur)->Last(pCur, pRes))
+#define sqliteBtreeNext(pCur, pRes) (btCOps(pCur)->Next(pCur, pRes))
+#define sqliteBtreePrevious(pCur, pRes) (btCOps(pCur)->Previous(pCur, pRes))
+#define sqliteBtreeKeySize(pCur, pSize) (btCOps(pCur)->KeySize(pCur, pSize) )
+#define sqliteBtreeKey(pCur, offset, amt, zBuf)\
+ (btCOps(pCur)->Key(pCur, offset, amt, zBuf))
+#define sqliteBtreeKeyCompare(pCur, pKey, nKey, nIgnore, pRes)\
+ (btCOps(pCur)->KeyCompare(pCur, pKey, nKey, nIgnore, pRes))
+#define sqliteBtreeDataSize(pCur, pSize) (btCOps(pCur)->DataSize(pCur, pSize))
+#define sqliteBtreeData(pCur, offset, amt, zBuf)\
+ (btCOps(pCur)->Data(pCur, offset, amt, zBuf))
+#define sqliteBtreeCloseCursor(pCur) (btCOps(pCur)->CloseCursor(pCur))
+#define sqliteBtreeGetMeta(pBt, aMeta) (btOps(pBt)->GetMeta(pBt, aMeta))
+#define sqliteBtreeUpdateMeta(pBt, aMeta) (btOps(pBt)->UpdateMeta(pBt, aMeta))
+#define sqliteBtreeIntegrityCheck(pBt, aRoot, nRoot)\
+ (btOps(pBt)->IntegrityCheck(pBt, aRoot, nRoot))
+#define sqliteBtreeGetFilename(pBt) (btOps(pBt)->GetFilename(pBt))
+#define sqliteBtreeCopyFile(pBt1, pBt2) (btOps(pBt1)->Copyfile(pBt1, pBt2))
+#define sqliteBtreePager(pBt) (btOps(pBt)->Pager(pBt))
+
+#ifdef SQLITE_TEST
+#define sqliteBtreePageDump(pBt, pgno, recursive)\
+ (btOps(pBt)->PageDump(pBt, pgno, recursive))
+#define sqliteBtreeCursorDump(pCur, aResult)\
+ (btCOps(pCur)->CursorDump(pCur, aResult))
+int btree_native_byte_order;
+#endif /* SQLITE_TEST */
+
+
+#endif /* _BTREE_H_ */
diff --git a/src/libs/sqlite2/btree_rb.c b/src/libs/sqlite2/btree_rb.c
new file mode 100644
index 00000000..18e49b81
--- /dev/null
+++ b/src/libs/sqlite2/btree_rb.c
@@ -0,0 +1,1488 @@
+/*
+** 2003 Feb 4
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** $Id: btree_rb.c 875429 2008-10-24 12:20:41Z cgilles $
+**
+** This file implements an in-core database using Red-Black balanced
+** binary trees.
+**
+** It was contributed to SQLite by anonymous on 2003-Feb-04 23:24:49 UTC.
+*/
+#include "btree.h"
+#include "sqliteInt.h"
+#include <assert.h>
+
+/*
+** Omit this whole file if the SQLITE_OMIT_INMEMORYDB macro is
+** defined. This allows a lot of code to be omitted for installations
+** that do not need it.
+*/
+#ifndef SQLITE_OMIT_INMEMORYDB
+
+
+typedef struct BtRbTree BtRbTree;
+typedef struct BtRbNode BtRbNode;
+typedef struct BtRollbackOp BtRollbackOp;
+typedef struct Rbtree Rbtree;
+typedef struct RbtCursor RbtCursor;
+
+/* Forward declarations */
+static BtOps sqliteRbtreeOps;
+static BtCursorOps sqliteRbtreeCursorOps;
+
+/*
+ * During each transaction (or checkpoint), a linked-list of
+ * "rollback-operations" is accumulated. If the transaction is rolled back,
+ * then the list of operations must be executed (to restore the database to
+ * it's state before the transaction started). If the transaction is to be
+ * committed, just delete the list.
+ *
+ * Each operation is represented as follows, depending on the value of eOp:
+ *
+ * ROLLBACK_INSERT -> Need to insert (pKey, pData) into table iTab.
+ * ROLLBACK_DELETE -> Need to delete the record (pKey) into table iTab.
+ * ROLLBACK_CREATE -> Need to create table iTab.
+ * ROLLBACK_DROP -> Need to drop table iTab.
+ */
+struct BtRollbackOp {
+ u8 eOp;
+ int iTab;
+ int nKey;
+ void *pKey;
+ int nData;
+ void *pData;
+ BtRollbackOp *pNext;
+};
+
+/*
+** Legal values for BtRollbackOp.eOp:
+*/
+#define ROLLBACK_INSERT 1 /* Insert a record */
+#define ROLLBACK_DELETE 2 /* Delete a record */
+#define ROLLBACK_CREATE 3 /* Create a table */
+#define ROLLBACK_DROP 4 /* Drop a table */
+
+struct Rbtree {
+ BtOps *pOps; /* Function table */
+ int aMetaData[SQLITE_N_BTREE_META];
+
+ int next_idx; /* next available table index */
+ Hash tblHash; /* All created tables, by index */
+ u8 isAnonymous; /* True if this Rbtree is to be deleted when closed */
+ u8 eTransState; /* State of this Rbtree wrt transactions */
+
+ BtRollbackOp *pTransRollback;
+ BtRollbackOp *pCheckRollback;
+ BtRollbackOp *pCheckRollbackTail;
+};
+
+/*
+** Legal values for Rbtree.eTransState.
+*/
+#define TRANS_NONE 0 /* No transaction is in progress */
+#define TRANS_INTRANSACTION 1 /* A transaction is in progress */
+#define TRANS_INCHECKPOINT 2 /* A checkpoint is in progress */
+#define TRANS_ROLLBACK 3 /* We are currently rolling back a checkpoint or
+ * transaction. */
+
+struct RbtCursor {
+ BtCursorOps *pOps; /* Function table */
+ Rbtree *pRbtree;
+ BtRbTree *pTree;
+ int iTree; /* Index of pTree in pRbtree */
+ BtRbNode *pNode;
+ RbtCursor *pShared; /* List of all cursors on the same Rbtree */
+ u8 eSkip; /* Determines if next step operation is a no-op */
+ u8 wrFlag; /* True if this cursor is open for writing */
+};
+
+/*
+** Legal values for RbtCursor.eSkip.
+*/
+#define SKIP_NONE 0 /* Always step the cursor */
+#define SKIP_NEXT 1 /* The next sqliteRbtreeNext() is a no-op */
+#define SKIP_PREV 2 /* The next sqliteRbtreePrevious() is a no-op */
+#define SKIP_INVALID 3 /* Calls to Next() and Previous() are invalid */
+
+struct BtRbTree {
+ RbtCursor *pCursors; /* All cursors pointing to this tree */
+ BtRbNode *pHead; /* Head of the tree, or NULL */
+};
+
+struct BtRbNode {
+ int nKey;
+ void *pKey;
+ int nData;
+ void *pData;
+ u8 isBlack; /* true for a black node, 0 for a red node */
+ BtRbNode *pParent; /* Nodes parent node, NULL for the tree head */
+ BtRbNode *pLeft; /* Nodes left child, or NULL */
+ BtRbNode *pRight; /* Nodes right child, or NULL */
+
+ int nBlackHeight; /* Only used during the red-black integrity check */
+};
+
+/* Forward declarations */
+static int memRbtreeMoveto(
+ RbtCursor* pCur,
+ const void *pKey,
+ int nKey,
+ int *pRes
+);
+static int memRbtreeClearTable(Rbtree* tree, int n);
+static int memRbtreeNext(RbtCursor* pCur, int *pRes);
+static int memRbtreeLast(RbtCursor* pCur, int *pRes);
+static int memRbtreePrevious(RbtCursor* pCur, int *pRes);
+
+
+/*
+** This routine checks all cursors that point to the same table
+** as pCur points to. If any of those cursors were opened with
+** wrFlag==0 then this routine returns SQLITE_LOCKED. If all
+** cursors point to the same table were opened with wrFlag==1
+** then this routine returns SQLITE_OK.
+**
+** In addition to checking for read-locks (where a read-lock
+** means a cursor opened with wrFlag==0) this routine also NULLs
+** out the pNode field of all other cursors.
+** This is necessary because an insert
+** or delete might change erase the node out from under
+** another cursor.
+*/
+static int checkReadLocks(RbtCursor *pCur){
+ RbtCursor *p;
+ assert( pCur->wrFlag );
+ for(p=pCur->pTree->pCursors; p; p=p->pShared){
+ if( p!=pCur ){
+ if( p->wrFlag==0 ) return SQLITE_LOCKED;
+ p->pNode = 0;
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+ * The key-compare function for the red-black trees. Returns as follows:
+ *
+ * (key1 < key2) -1
+ * (key1 == key2) 0
+ * (key1 > key2) 1
+ *
+ * Keys are compared using memcmp(). If one key is an exact prefix of the
+ * other, then the shorter key is less than the longer key.
+ */
+static int key_compare(void const*pKey1, int nKey1, void const*pKey2, int nKey2)
+{
+ int mcmp = memcmp(pKey1, pKey2, (nKey1 <= nKey2)?nKey1:nKey2);
+ if( mcmp == 0){
+ if( nKey1 == nKey2 ) return 0;
+ return ((nKey1 < nKey2)?-1:1);
+ }
+ return ((mcmp>0)?1:-1);
+}
+
+/*
+ * Perform the LEFT-rotate transformation on node X of tree pTree. This
+ * transform is part of the red-black balancing code.
+ *
+ * | |
+ * X Y
+ * / \ / \
+ * a Y X c
+ * / \ / \
+ * b c a b
+ *
+ * BEFORE AFTER
+ */
+static void leftRotate(BtRbTree *pTree, BtRbNode *pX)
+{
+ BtRbNode *pY;
+ BtRbNode *pb;
+ pY = pX->pRight;
+ pb = pY->pLeft;
+
+ pY->pParent = pX->pParent;
+ if( pX->pParent ){
+ if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY;
+ else pX->pParent->pRight = pY;
+ }
+ pY->pLeft = pX;
+ pX->pParent = pY;
+ pX->pRight = pb;
+ if( pb ) pb->pParent = pX;
+ if( pTree->pHead == pX ) pTree->pHead = pY;
+}
+
+/*
+ * Perform the RIGHT-rotate transformation on node X of tree pTree. This
+ * transform is part of the red-black balancing code.
+ *
+ * | |
+ * X Y
+ * / \ / \
+ * Y c a X
+ * / \ / \
+ * a b b c
+ *
+ * BEFORE AFTER
+ */
+static void rightRotate(BtRbTree *pTree, BtRbNode *pX)
+{
+ BtRbNode *pY;
+ BtRbNode *pb;
+ pY = pX->pLeft;
+ pb = pY->pRight;
+
+ pY->pParent = pX->pParent;
+ if( pX->pParent ){
+ if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY;
+ else pX->pParent->pRight = pY;
+ }
+ pY->pRight = pX;
+ pX->pParent = pY;
+ pX->pLeft = pb;
+ if( pb ) pb->pParent = pX;
+ if( pTree->pHead == pX ) pTree->pHead = pY;
+}
+
+/*
+ * A string-manipulation helper function for check_redblack_tree(). If (orig ==
+ * NULL) a copy of val is returned. If (orig != NULL) then a copy of the *
+ * concatenation of orig and val is returned. The original orig is deleted
+ * (using sqliteFree()).
+ */
+static char *append_val(char * orig, char const * val){
+ char *z;
+ if( !orig ){
+ z = sqliteStrDup( val );
+ } else{
+ z = 0;
+ sqliteSetString(&z, orig, val, (char*)0);
+ sqliteFree( orig );
+ }
+ return z;
+}
+
+/*
+ * Append a string representation of the entire node to orig and return it.
+ * This is used to produce debugging information if check_redblack_tree() finds
+ * a problem with a red-black binary tree.
+ */
+static char *append_node(char * orig, BtRbNode *pNode, int indent)
+{
+ char buf[128];
+ int i;
+
+ for( i=0; i<indent; i++ ){
+ orig = append_val(orig, " ");
+ }
+
+ sprintf(buf, "%p", pNode);
+ orig = append_val(orig, buf);
+
+ if( pNode ){
+ indent += 3;
+ if( pNode->isBlack ){
+ orig = append_val(orig, " B \n");
+ }else{
+ orig = append_val(orig, " R \n");
+ }
+ orig = append_node( orig, pNode->pLeft, indent );
+ orig = append_node( orig, pNode->pRight, indent );
+ }else{
+ orig = append_val(orig, "\n");
+ }
+ return orig;
+}
+
+/*
+ * Print a representation of a node to stdout. This function is only included
+ * so you can call it from within a debugger if things get really bad. It
+ * is not called from anyplace in the code.
+ */
+static void print_node(BtRbNode *pNode)
+{
+ char * str = append_node(0, pNode, 0);
+ printf("%s", str);
+
+ /* Suppress a warning message about print_node() being unused */
+ (void)print_node;
+}
+
+/*
+ * Check the following properties of the red-black tree:
+ * (1) - If a node is red, both of it's children are black
+ * (2) - Each path from a given node to a leaf (NULL) node passes thru the
+ * same number of black nodes
+ *
+ * If there is a problem, append a description (using append_val() ) to *msg.
+ */
+static void check_redblack_tree(BtRbTree * tree, char ** msg)
+{
+ BtRbNode *pNode;
+
+ /* 0 -> came from parent
+ * 1 -> came from left
+ * 2 -> came from right */
+ int prev_step = 0;
+
+ pNode = tree->pHead;
+ while( pNode ){
+ switch( prev_step ){
+ case 0:
+ if( pNode->pLeft ){
+ pNode = pNode->pLeft;
+ }else{
+ prev_step = 1;
+ }
+ break;
+ case 1:
+ if( pNode->pRight ){
+ pNode = pNode->pRight;
+ prev_step = 0;
+ }else{
+ prev_step = 2;
+ }
+ break;
+ case 2:
+ /* Check red-black property (1) */
+ if( !pNode->isBlack &&
+ ( (pNode->pLeft && !pNode->pLeft->isBlack) ||
+ (pNode->pRight && !pNode->pRight->isBlack) )
+ ){
+ char buf[128];
+ sprintf(buf, "Red node with red child at %p\n", pNode);
+ *msg = append_val(*msg, buf);
+ *msg = append_node(*msg, tree->pHead, 0);
+ *msg = append_val(*msg, "\n");
+ }
+
+ /* Check red-black property (2) */
+ {
+ int leftHeight = 0;
+ int rightHeight = 0;
+ if( pNode->pLeft ){
+ leftHeight += pNode->pLeft->nBlackHeight;
+ leftHeight += (pNode->pLeft->isBlack?1:0);
+ }
+ if( pNode->pRight ){
+ rightHeight += pNode->pRight->nBlackHeight;
+ rightHeight += (pNode->pRight->isBlack?1:0);
+ }
+ if( leftHeight != rightHeight ){
+ char buf[128];
+ sprintf(buf, "Different black-heights at %p\n", pNode);
+ *msg = append_val(*msg, buf);
+ *msg = append_node(*msg, tree->pHead, 0);
+ *msg = append_val(*msg, "\n");
+ }
+ pNode->nBlackHeight = leftHeight;
+ }
+
+ if( pNode->pParent ){
+ if( pNode == pNode->pParent->pLeft ) prev_step = 1;
+ else prev_step = 2;
+ }
+ pNode = pNode->pParent;
+ break;
+ default: assert(0);
+ }
+ }
+}
+
+/*
+ * Node pX has just been inserted into pTree (by code in sqliteRbtreeInsert()).
+ * It is possible that pX is a red node with a red parent, which is a violation
+ * of the red-black tree properties. This function performs rotations and
+ * color changes to rebalance the tree
+ */
+static void do_insert_balancing(BtRbTree *pTree, BtRbNode *pX)
+{
+ /* In the first iteration of this loop, pX points to the red node just
+ * inserted in the tree. If the parent of pX exists (pX is not the root
+ * node) and is red, then the properties of the red-black tree are
+ * violated.
+ *
+ * At the start of any subsequent iterations, pX points to a red node
+ * with a red parent. In all other respects the tree is a legal red-black
+ * binary tree. */
+ while( pX != pTree->pHead && !pX->pParent->isBlack ){
+ BtRbNode *pUncle;
+ BtRbNode *pGrandparent;
+
+ /* Grandparent of pX must exist and must be black. */
+ pGrandparent = pX->pParent->pParent;
+ assert( pGrandparent );
+ assert( pGrandparent->isBlack );
+
+ /* Uncle of pX may or may not exist. */
+ if( pX->pParent == pGrandparent->pLeft )
+ pUncle = pGrandparent->pRight;
+ else
+ pUncle = pGrandparent->pLeft;
+
+ /* If the uncle of pX exists and is red, we do the following:
+ * | |
+ * G(b) G(r)
+ * / \ / \
+ * U(r) P(r) U(b) P(b)
+ * \ \
+ * X(r) X(r)
+ *
+ * BEFORE AFTER
+ * pX is then set to G. If the parent of G is red, then the while loop
+ * will run again. */
+ if( pUncle && !pUncle->isBlack ){
+ pGrandparent->isBlack = 0;
+ pUncle->isBlack = 1;
+ pX->pParent->isBlack = 1;
+ pX = pGrandparent;
+ }else{
+
+ if( pX->pParent == pGrandparent->pLeft ){
+ if( pX == pX->pParent->pRight ){
+ /* If pX is a right-child, do the following transform, essentially
+ * to change pX into a left-child:
+ * | |
+ * G(b) G(b)
+ * / \ / \
+ * P(r) U(b) X(r) U(b)
+ * \ /
+ * X(r) P(r) <-- new X
+ *
+ * BEFORE AFTER
+ */
+ pX = pX->pParent;
+ leftRotate(pTree, pX);
+ }
+
+ /* Do the following transform, which balances the tree :)
+ * | |
+ * G(b) P(b)
+ * / \ / \
+ * P(r) U(b) X(r) G(r)
+ * / \
+ * X(r) U(b)
+ *
+ * BEFORE AFTER
+ */
+ assert( pGrandparent == pX->pParent->pParent );
+ pGrandparent->isBlack = 0;
+ pX->pParent->isBlack = 1;
+ rightRotate( pTree, pGrandparent );
+
+ }else{
+ /* This code is symetric to the illustrated case above. */
+ if( pX == pX->pParent->pLeft ){
+ pX = pX->pParent;
+ rightRotate(pTree, pX);
+ }
+ assert( pGrandparent == pX->pParent->pParent );
+ pGrandparent->isBlack = 0;
+ pX->pParent->isBlack = 1;
+ leftRotate( pTree, pGrandparent );
+ }
+ }
+ }
+ pTree->pHead->isBlack = 1;
+}
+
+/*
+ * A child of pParent, which in turn had child pX, has just been removed from
+ * pTree (the figure below depicts the operation, Z is being removed). pParent
+ * or pX, or both may be NULL.
+ * | |
+ * P P
+ * / \ / \
+ * Z X
+ * / \
+ * X nil
+ *
+ * This function is only called if Z was black. In this case the red-black tree
+ * properties have been violated, and pX has an "extra black". This function
+ * performs rotations and color-changes to re-balance the tree.
+ */
+static
+void do_delete_balancing(BtRbTree *pTree, BtRbNode *pX, BtRbNode *pParent)
+{
+ BtRbNode *pSib;
+
+ /* TODO: Comment this code! */
+ while( pX != pTree->pHead && (!pX || pX->isBlack) ){
+ if( pX == pParent->pLeft ){
+ pSib = pParent->pRight;
+ if( pSib && !(pSib->isBlack) ){
+ pSib->isBlack = 1;
+ pParent->isBlack = 0;
+ leftRotate(pTree, pParent);
+ pSib = pParent->pRight;
+ }
+ if( !pSib ){
+ pX = pParent;
+ }else if(
+ (!pSib->pLeft || pSib->pLeft->isBlack) &&
+ (!pSib->pRight || pSib->pRight->isBlack) ) {
+ pSib->isBlack = 0;
+ pX = pParent;
+ }else{
+ if( (!pSib->pRight || pSib->pRight->isBlack) ){
+ if( pSib->pLeft ) pSib->pLeft->isBlack = 1;
+ pSib->isBlack = 0;
+ rightRotate( pTree, pSib );
+ pSib = pParent->pRight;
+ }
+ pSib->isBlack = pParent->isBlack;
+ pParent->isBlack = 1;
+ if( pSib->pRight ) pSib->pRight->isBlack = 1;
+ leftRotate(pTree, pParent);
+ pX = pTree->pHead;
+ }
+ }else{
+ pSib = pParent->pLeft;
+ if( pSib && !(pSib->isBlack) ){
+ pSib->isBlack = 1;
+ pParent->isBlack = 0;
+ rightRotate(pTree, pParent);
+ pSib = pParent->pLeft;
+ }
+ if( !pSib ){
+ pX = pParent;
+ }else if(
+ (!pSib->pLeft || pSib->pLeft->isBlack) &&
+ (!pSib->pRight || pSib->pRight->isBlack) ){
+ pSib->isBlack = 0;
+ pX = pParent;
+ }else{
+ if( (!pSib->pLeft || pSib->pLeft->isBlack) ){
+ if( pSib->pRight ) pSib->pRight->isBlack = 1;
+ pSib->isBlack = 0;
+ leftRotate( pTree, pSib );
+ pSib = pParent->pLeft;
+ }
+ pSib->isBlack = pParent->isBlack;
+ pParent->isBlack = 1;
+ if( pSib->pLeft ) pSib->pLeft->isBlack = 1;
+ rightRotate(pTree, pParent);
+ pX = pTree->pHead;
+ }
+ }
+ pParent = pX->pParent;
+ }
+ if( pX ) pX->isBlack = 1;
+}
+
+/*
+ * Create table n in tree pRbtree. Table n must not exist.
+ */
+static void btreeCreateTable(Rbtree* pRbtree, int n)
+{
+ BtRbTree *pNewTbl = sqliteMalloc(sizeof(BtRbTree));
+ sqliteHashInsert(&pRbtree->tblHash, 0, n, pNewTbl);
+}
+
+/*
+ * Log a single "rollback-op" for the given Rbtree. See comments for struct
+ * BtRollbackOp.
+ */
+static void btreeLogRollbackOp(Rbtree* pRbtree, BtRollbackOp *pRollbackOp)
+{
+ assert( pRbtree->eTransState == TRANS_INCHECKPOINT ||
+ pRbtree->eTransState == TRANS_INTRANSACTION );
+ if( pRbtree->eTransState == TRANS_INTRANSACTION ){
+ pRollbackOp->pNext = pRbtree->pTransRollback;
+ pRbtree->pTransRollback = pRollbackOp;
+ }
+ if( pRbtree->eTransState == TRANS_INCHECKPOINT ){
+ if( !pRbtree->pCheckRollback ){
+ pRbtree->pCheckRollbackTail = pRollbackOp;
+ }
+ pRollbackOp->pNext = pRbtree->pCheckRollback;
+ pRbtree->pCheckRollback = pRollbackOp;
+ }
+}
+
+int sqliteRbtreeOpen(
+ const char *zFilename,
+ int mode,
+ int nPg,
+ Btree **ppBtree
+){
+ Rbtree **ppRbtree = (Rbtree**)ppBtree;
+ *ppRbtree = (Rbtree *)sqliteMalloc(sizeof(Rbtree));
+ if( sqlite_malloc_failed ) goto open_no_mem;
+ sqliteHashInit(&(*ppRbtree)->tblHash, SQLITE_HASH_INT, 0);
+
+ /* Create a binary tree for the SQLITE_MASTER table at location 2 */
+ btreeCreateTable(*ppRbtree, 2);
+ if( sqlite_malloc_failed ) goto open_no_mem;
+ (*ppRbtree)->next_idx = 3;
+ (*ppRbtree)->pOps = &sqliteRbtreeOps;
+ /* Set file type to 4; this is so that "attach ':memory:' as ...." does not
+ ** think that the database in uninitialised and refuse to attach
+ */
+ (*ppRbtree)->aMetaData[2] = 4;
+
+ return SQLITE_OK;
+
+open_no_mem:
+ *ppBtree = 0;
+ return SQLITE_NOMEM;
+}
+
+/*
+ * Create a new table in the supplied Rbtree. Set *n to the new table number.
+ * Return SQLITE_OK if the operation is a success.
+ */
+static int memRbtreeCreateTable(Rbtree* tree, int* n)
+{
+ assert( tree->eTransState != TRANS_NONE );
+
+ *n = tree->next_idx++;
+ btreeCreateTable(tree, *n);
+ if( sqlite_malloc_failed ) return SQLITE_NOMEM;
+
+ /* Set up the rollback structure (if we are not doing this as part of a
+ * rollback) */
+ if( tree->eTransState != TRANS_ROLLBACK ){
+ BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp));
+ if( pRollbackOp==0 ) return SQLITE_NOMEM;
+ pRollbackOp->eOp = ROLLBACK_DROP;
+ pRollbackOp->iTab = *n;
+ btreeLogRollbackOp(tree, pRollbackOp);
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+ * Delete table n from the supplied Rbtree.
+ */
+static int memRbtreeDropTable(Rbtree* tree, int n)
+{
+ BtRbTree *pTree;
+ assert( tree->eTransState != TRANS_NONE );
+
+ memRbtreeClearTable(tree, n);
+ pTree = sqliteHashInsert(&tree->tblHash, 0, n, 0);
+ assert(pTree);
+ assert( pTree->pCursors==0 );
+ sqliteFree(pTree);
+
+ if( tree->eTransState != TRANS_ROLLBACK ){
+ BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp));
+ if( pRollbackOp==0 ) return SQLITE_NOMEM;
+ pRollbackOp->eOp = ROLLBACK_CREATE;
+ pRollbackOp->iTab = n;
+ btreeLogRollbackOp(tree, pRollbackOp);
+ }
+
+ return SQLITE_OK;
+}
+
+static int memRbtreeKeyCompare(RbtCursor* pCur, const void *pKey, int nKey,
+ int nIgnore, int *pRes)
+{
+ assert(pCur);
+
+ if( !pCur->pNode ) {
+ *pRes = -1;
+ } else {
+ if( (pCur->pNode->nKey - nIgnore) < 0 ){
+ *pRes = -1;
+ }else{
+ *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey-nIgnore,
+ pKey, nKey);
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+ * Get a new cursor for table iTable of the supplied Rbtree. The wrFlag
+ * parameter indicates that the cursor is open for writing.
+ *
+ * Note that RbtCursor.eSkip and RbtCursor.pNode both initialize to 0.
+ */
+static int memRbtreeCursor(
+ Rbtree* tree,
+ int iTable,
+ int wrFlag,
+ RbtCursor **ppCur
+){
+ RbtCursor *pCur;
+ assert(tree);
+ pCur = *ppCur = sqliteMalloc(sizeof(RbtCursor));
+ if( sqlite_malloc_failed ) return SQLITE_NOMEM;
+ pCur->pTree = sqliteHashFind(&tree->tblHash, 0, iTable);
+ assert( pCur->pTree );
+ pCur->pRbtree = tree;
+ pCur->iTree = iTable;
+ pCur->pOps = &sqliteRbtreeCursorOps;
+ pCur->wrFlag = wrFlag;
+ pCur->pShared = pCur->pTree->pCursors;
+ pCur->pTree->pCursors = pCur;
+
+ assert( (*ppCur)->pTree );
+ return SQLITE_OK;
+}
+
+/*
+ * Insert a new record into the Rbtree. The key is given by (pKey,nKey)
+ * and the data is given by (pData,nData). The cursor is used only to
+ * define what database the record should be inserted into. The cursor
+ * is left pointing at the new record.
+ *
+ * If the key exists already in the tree, just replace the data.
+ */
+static int memRbtreeInsert(
+ RbtCursor* pCur,
+ const void *pKey,
+ int nKey,
+ const void *pDataInput,
+ int nData
+){
+ void * pData;
+ int match;
+
+ /* It is illegal to call sqliteRbtreeInsert() if we are
+ ** not in a transaction */
+ assert( pCur->pRbtree->eTransState != TRANS_NONE );
+
+ /* Make sure some other cursor isn't trying to read this same table */
+ if( checkReadLocks(pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+
+ /* Take a copy of the input data now, in case we need it for the
+ * replace case */
+ pData = sqliteMallocRaw(nData);
+ if( sqlite_malloc_failed ) return SQLITE_NOMEM;
+ memcpy(pData, pDataInput, nData);
+
+ /* Move the cursor to a node near the key to be inserted. If the key already
+ * exists in the table, then (match == 0). In this case we can just replace
+ * the data associated with the entry, we don't need to manipulate the tree.
+ *
+ * If there is no exact match, then the cursor points at what would be either
+ * the predecessor (match == -1) or successor (match == 1) of the
+ * searched-for key, were it to be inserted. The new node becomes a child of
+ * this node.
+ *
+ * The new node is initially red.
+ */
+ memRbtreeMoveto( pCur, pKey, nKey, &match);
+ if( match ){
+ BtRbNode *pNode = sqliteMalloc(sizeof(BtRbNode));
+ if( pNode==0 ) return SQLITE_NOMEM;
+ pNode->nKey = nKey;
+ pNode->pKey = sqliteMallocRaw(nKey);
+ if( sqlite_malloc_failed ) return SQLITE_NOMEM;
+ memcpy(pNode->pKey, pKey, nKey);
+ pNode->nData = nData;
+ pNode->pData = pData;
+ if( pCur->pNode ){
+ switch( match ){
+ case -1:
+ assert( !pCur->pNode->pRight );
+ pNode->pParent = pCur->pNode;
+ pCur->pNode->pRight = pNode;
+ break;
+ case 1:
+ assert( !pCur->pNode->pLeft );
+ pNode->pParent = pCur->pNode;
+ pCur->pNode->pLeft = pNode;
+ break;
+ default:
+ assert(0);
+ }
+ }else{
+ pCur->pTree->pHead = pNode;
+ }
+
+ /* Point the cursor at the node just inserted, as per SQLite requirements */
+ pCur->pNode = pNode;
+
+ /* A new node has just been inserted, so run the balancing code */
+ do_insert_balancing(pCur->pTree, pNode);
+
+ /* Set up a rollback-op in case we have to roll this operation back */
+ if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){
+ BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) );
+ if( pOp==0 ) return SQLITE_NOMEM;
+ pOp->eOp = ROLLBACK_DELETE;
+ pOp->iTab = pCur->iTree;
+ pOp->nKey = pNode->nKey;
+ pOp->pKey = sqliteMallocRaw( pOp->nKey );
+ if( sqlite_malloc_failed ) return SQLITE_NOMEM;
+ memcpy( pOp->pKey, pNode->pKey, pOp->nKey );
+ btreeLogRollbackOp(pCur->pRbtree, pOp);
+ }
+
+ }else{
+ /* No need to insert a new node in the tree, as the key already exists.
+ * Just clobber the current nodes data. */
+
+ /* Set up a rollback-op in case we have to roll this operation back */
+ if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){
+ BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) );
+ if( pOp==0 ) return SQLITE_NOMEM;
+ pOp->iTab = pCur->iTree;
+ pOp->nKey = pCur->pNode->nKey;
+ pOp->pKey = sqliteMallocRaw( pOp->nKey );
+ if( sqlite_malloc_failed ) return SQLITE_NOMEM;
+ memcpy( pOp->pKey, pCur->pNode->pKey, pOp->nKey );
+ pOp->nData = pCur->pNode->nData;
+ pOp->pData = pCur->pNode->pData;
+ pOp->eOp = ROLLBACK_INSERT;
+ btreeLogRollbackOp(pCur->pRbtree, pOp);
+ }else{
+ sqliteFree( pCur->pNode->pData );
+ }
+
+ /* Actually clobber the nodes data */
+ pCur->pNode->pData = pData;
+ pCur->pNode->nData = nData;
+ }
+
+ return SQLITE_OK;
+}
+
+/* Move the cursor so that it points to an entry near pKey.
+** Return a success code.
+**
+** *pRes<0 The cursor is left pointing at an entry that
+** is smaller than pKey or if the table is empty
+** and the cursor is therefore left point to nothing.
+**
+** *pRes==0 The cursor is left pointing at an entry that
+** exactly matches pKey.
+**
+** *pRes>0 The cursor is left pointing at an entry that
+** is larger than pKey.
+*/
+static int memRbtreeMoveto(
+ RbtCursor* pCur,
+ const void *pKey,
+ int nKey,
+ int *pRes
+){
+ BtRbNode *pTmp = 0;
+
+ pCur->pNode = pCur->pTree->pHead;
+ *pRes = -1;
+ while( pCur->pNode && *pRes ) {
+ *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey, pKey, nKey);
+ pTmp = pCur->pNode;
+ switch( *pRes ){
+ case 1: /* cursor > key */
+ pCur->pNode = pCur->pNode->pLeft;
+ break;
+ case -1: /* cursor < key */
+ pCur->pNode = pCur->pNode->pRight;
+ break;
+ }
+ }
+
+ /* If (pCur->pNode == NULL), then we have failed to find a match. Set
+ * pCur->pNode to pTmp, which is either NULL (if the tree is empty) or the
+ * last node traversed in the search. In either case the relation ship
+ * between pTmp and the searched for key is already stored in *pRes. pTmp is
+ * either the successor or predecessor of the key we tried to move to. */
+ if( !pCur->pNode ) pCur->pNode = pTmp;
+ pCur->eSkip = SKIP_NONE;
+
+ return SQLITE_OK;
+}
+
+
+/*
+** Delete the entry that the cursor is pointing to.
+**
+** The cursor is left pointing at either the next or the previous
+** entry. If the cursor is left pointing to the next entry, then
+** the pCur->eSkip flag is set to SKIP_NEXT which forces the next call to
+** sqliteRbtreeNext() to be a no-op. That way, you can always call
+** sqliteRbtreeNext() after a delete and the cursor will be left
+** pointing to the first entry after the deleted entry. Similarly,
+** pCur->eSkip is set to SKIP_PREV is the cursor is left pointing to
+** the entry prior to the deleted entry so that a subsequent call to
+** sqliteRbtreePrevious() will always leave the cursor pointing at the
+** entry immediately before the one that was deleted.
+*/
+static int memRbtreeDelete(RbtCursor* pCur)
+{
+ BtRbNode *pZ; /* The one being deleted */
+ BtRbNode *pChild; /* The child of the spliced out node */
+
+ /* It is illegal to call sqliteRbtreeDelete() if we are
+ ** not in a transaction */
+ assert( pCur->pRbtree->eTransState != TRANS_NONE );
+
+ /* Make sure some other cursor isn't trying to read this same table */
+ if( checkReadLocks(pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+
+ pZ = pCur->pNode;
+ if( !pZ ){
+ return SQLITE_OK;
+ }
+
+ /* If we are not currently doing a rollback, set up a rollback op for this
+ * deletion */
+ if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){
+ BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) );
+ if( pOp==0 ) return SQLITE_NOMEM;
+ pOp->iTab = pCur->iTree;
+ pOp->nKey = pZ->nKey;
+ pOp->pKey = pZ->pKey;
+ pOp->nData = pZ->nData;
+ pOp->pData = pZ->pData;
+ pOp->eOp = ROLLBACK_INSERT;
+ btreeLogRollbackOp(pCur->pRbtree, pOp);
+ }
+
+ /* First do a standard binary-tree delete (node pZ is to be deleted). How
+ * to do this depends on how many children pZ has:
+ *
+ * If pZ has no children or one child, then splice out pZ. If pZ has two
+ * children, splice out the successor of pZ and replace the key and data of
+ * pZ with the key and data of the spliced out successor. */
+ if( pZ->pLeft && pZ->pRight ){
+ BtRbNode *pTmp;
+ int dummy;
+ pCur->eSkip = SKIP_NONE;
+ memRbtreeNext(pCur, &dummy);
+ assert( dummy == 0 );
+ if( pCur->pRbtree->eTransState == TRANS_ROLLBACK ){
+ sqliteFree(pZ->pKey);
+ sqliteFree(pZ->pData);
+ }
+ pZ->pData = pCur->pNode->pData;
+ pZ->nData = pCur->pNode->nData;
+ pZ->pKey = pCur->pNode->pKey;
+ pZ->nKey = pCur->pNode->nKey;
+ pTmp = pZ;
+ pZ = pCur->pNode;
+ pCur->pNode = pTmp;
+ pCur->eSkip = SKIP_NEXT;
+ }else{
+ int res;
+ pCur->eSkip = SKIP_NONE;
+ memRbtreeNext(pCur, &res);
+ pCur->eSkip = SKIP_NEXT;
+ if( res ){
+ memRbtreeLast(pCur, &res);
+ memRbtreePrevious(pCur, &res);
+ pCur->eSkip = SKIP_PREV;
+ }
+ if( pCur->pRbtree->eTransState == TRANS_ROLLBACK ){
+ sqliteFree(pZ->pKey);
+ sqliteFree(pZ->pData);
+ }
+ }
+
+ /* pZ now points at the node to be spliced out. This block does the
+ * splicing. */
+ {
+ BtRbNode **ppParentSlot = 0;
+ assert( !pZ->pLeft || !pZ->pRight ); /* pZ has at most one child */
+ pChild = ((pZ->pLeft)?pZ->pLeft:pZ->pRight);
+ if( pZ->pParent ){
+ assert( pZ == pZ->pParent->pLeft || pZ == pZ->pParent->pRight );
+ ppParentSlot = ((pZ == pZ->pParent->pLeft)
+ ?&pZ->pParent->pLeft:&pZ->pParent->pRight);
+ *ppParentSlot = pChild;
+ }else{
+ pCur->pTree->pHead = pChild;
+ }
+ if( pChild ) pChild->pParent = pZ->pParent;
+ }
+
+ /* pZ now points at the spliced out node. pChild is the only child of pZ, or
+ * NULL if pZ has no children. If pZ is black, and not the tree root, then we
+ * will have violated the "same number of black nodes in every path to a
+ * leaf" property of the red-black tree. The code in do_delete_balancing()
+ * repairs this. */
+ if( pZ->isBlack ){
+ do_delete_balancing(pCur->pTree, pChild, pZ->pParent);
+ }
+
+ sqliteFree(pZ);
+ return SQLITE_OK;
+}
+
+/*
+ * Empty table n of the Rbtree.
+ */
+static int memRbtreeClearTable(Rbtree* tree, int n)
+{
+ BtRbTree *pTree;
+ BtRbNode *pNode;
+
+ pTree = sqliteHashFind(&tree->tblHash, 0, n);
+ assert(pTree);
+
+ pNode = pTree->pHead;
+ while( pNode ){
+ if( pNode->pLeft ){
+ pNode = pNode->pLeft;
+ }
+ else if( pNode->pRight ){
+ pNode = pNode->pRight;
+ }
+ else {
+ BtRbNode *pTmp = pNode->pParent;
+ if( tree->eTransState == TRANS_ROLLBACK ){
+ sqliteFree( pNode->pKey );
+ sqliteFree( pNode->pData );
+ }else{
+ BtRollbackOp *pRollbackOp = sqliteMallocRaw(sizeof(BtRollbackOp));
+ if( pRollbackOp==0 ) return SQLITE_NOMEM;
+ pRollbackOp->eOp = ROLLBACK_INSERT;
+ pRollbackOp->iTab = n;
+ pRollbackOp->nKey = pNode->nKey;
+ pRollbackOp->pKey = pNode->pKey;
+ pRollbackOp->nData = pNode->nData;
+ pRollbackOp->pData = pNode->pData;
+ btreeLogRollbackOp(tree, pRollbackOp);
+ }
+ sqliteFree( pNode );
+ if( pTmp ){
+ if( pTmp->pLeft == pNode ) pTmp->pLeft = 0;
+ else if( pTmp->pRight == pNode ) pTmp->pRight = 0;
+ }
+ pNode = pTmp;
+ }
+ }
+
+ pTree->pHead = 0;
+ return SQLITE_OK;
+}
+
+static int memRbtreeFirst(RbtCursor* pCur, int *pRes)
+{
+ if( pCur->pTree->pHead ){
+ pCur->pNode = pCur->pTree->pHead;
+ while( pCur->pNode->pLeft ){
+ pCur->pNode = pCur->pNode->pLeft;
+ }
+ }
+ if( pCur->pNode ){
+ *pRes = 0;
+ }else{
+ *pRes = 1;
+ }
+ pCur->eSkip = SKIP_NONE;
+ return SQLITE_OK;
+}
+
+static int memRbtreeLast(RbtCursor* pCur, int *pRes)
+{
+ if( pCur->pTree->pHead ){
+ pCur->pNode = pCur->pTree->pHead;
+ while( pCur->pNode->pRight ){
+ pCur->pNode = pCur->pNode->pRight;
+ }
+ }
+ if( pCur->pNode ){
+ *pRes = 0;
+ }else{
+ *pRes = 1;
+ }
+ pCur->eSkip = SKIP_NONE;
+ return SQLITE_OK;
+}
+
+/*
+** Advance the cursor to the next entry in the database. If
+** successful then set *pRes=0. If the cursor
+** was already pointing to the last entry in the database before
+** this routine was called, then set *pRes=1.
+*/
+static int memRbtreeNext(RbtCursor* pCur, int *pRes)
+{
+ if( pCur->pNode && pCur->eSkip != SKIP_NEXT ){
+ if( pCur->pNode->pRight ){
+ pCur->pNode = pCur->pNode->pRight;
+ while( pCur->pNode->pLeft )
+ pCur->pNode = pCur->pNode->pLeft;
+ }else{
+ BtRbNode * pX = pCur->pNode;
+ pCur->pNode = pX->pParent;
+ while( pCur->pNode && (pCur->pNode->pRight == pX) ){
+ pX = pCur->pNode;
+ pCur->pNode = pX->pParent;
+ }
+ }
+ }
+ pCur->eSkip = SKIP_NONE;
+
+ if( !pCur->pNode ){
+ *pRes = 1;
+ }else{
+ *pRes = 0;
+ }
+
+ return SQLITE_OK;
+}
+
+static int memRbtreePrevious(RbtCursor* pCur, int *pRes)
+{
+ if( pCur->pNode && pCur->eSkip != SKIP_PREV ){
+ if( pCur->pNode->pLeft ){
+ pCur->pNode = pCur->pNode->pLeft;
+ while( pCur->pNode->pRight )
+ pCur->pNode = pCur->pNode->pRight;
+ }else{
+ BtRbNode * pX = pCur->pNode;
+ pCur->pNode = pX->pParent;
+ while( pCur->pNode && (pCur->pNode->pLeft == pX) ){
+ pX = pCur->pNode;
+ pCur->pNode = pX->pParent;
+ }
+ }
+ }
+ pCur->eSkip = SKIP_NONE;
+
+ if( !pCur->pNode ){
+ *pRes = 1;
+ }else{
+ *pRes = 0;
+ }
+
+ return SQLITE_OK;
+}
+
+static int memRbtreeKeySize(RbtCursor* pCur, int *pSize)
+{
+ if( pCur->pNode ){
+ *pSize = pCur->pNode->nKey;
+ }else{
+ *pSize = 0;
+ }
+ return SQLITE_OK;
+}
+
+static int memRbtreeKey(RbtCursor* pCur, int offset, int amt, char *zBuf)
+{
+ if( !pCur->pNode ) return 0;
+ if( !pCur->pNode->pKey || ((amt + offset) <= pCur->pNode->nKey) ){
+ memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, amt);
+ }else{
+ memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, pCur->pNode->nKey-offset);
+ amt = pCur->pNode->nKey-offset;
+ }
+ return amt;
+}
+
+static int memRbtreeDataSize(RbtCursor* pCur, int *pSize)
+{
+ if( pCur->pNode ){
+ *pSize = pCur->pNode->nData;
+ }else{
+ *pSize = 0;
+ }
+ return SQLITE_OK;
+}
+
+static int memRbtreeData(RbtCursor *pCur, int offset, int amt, char *zBuf)
+{
+ if( !pCur->pNode ) return 0;
+ if( (amt + offset) <= pCur->pNode->nData ){
+ memcpy(zBuf, ((char*)pCur->pNode->pData)+offset, amt);
+ }else{
+ memcpy(zBuf, ((char*)pCur->pNode->pData)+offset ,pCur->pNode->nData-offset);
+ amt = pCur->pNode->nData-offset;
+ }
+ return amt;
+}
+
+static int memRbtreeCloseCursor(RbtCursor* pCur)
+{
+ if( pCur->pTree->pCursors==pCur ){
+ pCur->pTree->pCursors = pCur->pShared;
+ }else{
+ RbtCursor *p = pCur->pTree->pCursors;
+ while( p && p->pShared!=pCur ){ p = p->pShared; }
+ assert( p!=0 );
+ if( p ){
+ p->pShared = pCur->pShared;
+ }
+ }
+ sqliteFree(pCur);
+ return SQLITE_OK;
+}
+
+static int memRbtreeGetMeta(Rbtree* tree, int* aMeta)
+{
+ memcpy( aMeta, tree->aMetaData, sizeof(int) * SQLITE_N_BTREE_META );
+ return SQLITE_OK;
+}
+
+static int memRbtreeUpdateMeta(Rbtree* tree, int* aMeta)
+{
+ memcpy( tree->aMetaData, aMeta, sizeof(int) * SQLITE_N_BTREE_META );
+ return SQLITE_OK;
+}
+
+/*
+ * Check that each table in the Rbtree meets the requirements for a red-black
+ * binary tree. If an error is found, return an explanation of the problem in
+ * memory obtained from sqliteMalloc(). Parameters aRoot and nRoot are ignored.
+ */
+static char *memRbtreeIntegrityCheck(Rbtree* tree, int* aRoot, int nRoot)
+{
+ char * msg = 0;
+ HashElem *p;
+
+ for(p=sqliteHashFirst(&tree->tblHash); p; p=sqliteHashNext(p)){
+ BtRbTree *pTree = sqliteHashData(p);
+ check_redblack_tree(pTree, &msg);
+ }
+
+ return msg;
+}
+
+static int memRbtreeSetCacheSize(Rbtree* tree, int sz)
+{
+ return SQLITE_OK;
+}
+
+static int memRbtreeSetSafetyLevel(Rbtree *pBt, int level){
+ return SQLITE_OK;
+}
+
+static int memRbtreeBeginTrans(Rbtree* tree)
+{
+ if( tree->eTransState != TRANS_NONE )
+ return SQLITE_ERROR;
+
+ assert( tree->pTransRollback == 0 );
+ tree->eTransState = TRANS_INTRANSACTION;
+ return SQLITE_OK;
+}
+
+/*
+** Delete a linked list of BtRollbackOp structures.
+*/
+static void deleteRollbackList(BtRollbackOp *pOp){
+ while( pOp ){
+ BtRollbackOp *pTmp = pOp->pNext;
+ sqliteFree(pOp->pData);
+ sqliteFree(pOp->pKey);
+ sqliteFree(pOp);
+ pOp = pTmp;
+ }
+}
+
+static int memRbtreeCommit(Rbtree* tree){
+ /* Just delete pTransRollback and pCheckRollback */
+ deleteRollbackList(tree->pCheckRollback);
+ deleteRollbackList(tree->pTransRollback);
+ tree->pTransRollback = 0;
+ tree->pCheckRollback = 0;
+ tree->pCheckRollbackTail = 0;
+ tree->eTransState = TRANS_NONE;
+ return SQLITE_OK;
+}
+
+/*
+ * Close the supplied Rbtree. Delete everything associated with it.
+ */
+static int memRbtreeClose(Rbtree* tree)
+{
+ HashElem *p;
+ memRbtreeCommit(tree);
+ while( (p=sqliteHashFirst(&tree->tblHash))!=0 ){
+ tree->eTransState = TRANS_ROLLBACK;
+ memRbtreeDropTable(tree, sqliteHashKeysize(p));
+ }
+ sqliteHashClear(&tree->tblHash);
+ sqliteFree(tree);
+ return SQLITE_OK;
+}
+
+/*
+ * Execute and delete the supplied rollback-list on pRbtree.
+ */
+static void execute_rollback_list(Rbtree *pRbtree, BtRollbackOp *pList)
+{
+ BtRollbackOp *pTmp;
+ RbtCursor cur;
+ int res;
+
+ cur.pRbtree = pRbtree;
+ cur.wrFlag = 1;
+ while( pList ){
+ switch( pList->eOp ){
+ case ROLLBACK_INSERT:
+ cur.pTree = sqliteHashFind( &pRbtree->tblHash, 0, pList->iTab );
+ assert(cur.pTree);
+ cur.iTree = pList->iTab;
+ cur.eSkip = SKIP_NONE;
+ memRbtreeInsert( &cur, pList->pKey,
+ pList->nKey, pList->pData, pList->nData );
+ break;
+ case ROLLBACK_DELETE:
+ cur.pTree = sqliteHashFind( &pRbtree->tblHash, 0, pList->iTab );
+ assert(cur.pTree);
+ cur.iTree = pList->iTab;
+ cur.eSkip = SKIP_NONE;
+ memRbtreeMoveto(&cur, pList->pKey, pList->nKey, &res);
+ assert(res == 0);
+ memRbtreeDelete( &cur );
+ break;
+ case ROLLBACK_CREATE:
+ btreeCreateTable(pRbtree, pList->iTab);
+ break;
+ case ROLLBACK_DROP:
+ memRbtreeDropTable(pRbtree, pList->iTab);
+ break;
+ default:
+ assert(0);
+ }
+ sqliteFree(pList->pKey);
+ sqliteFree(pList->pData);
+ pTmp = pList->pNext;
+ sqliteFree(pList);
+ pList = pTmp;
+ }
+}
+
+static int memRbtreeRollback(Rbtree* tree)
+{
+ tree->eTransState = TRANS_ROLLBACK;
+ execute_rollback_list(tree, tree->pCheckRollback);
+ execute_rollback_list(tree, tree->pTransRollback);
+ tree->pTransRollback = 0;
+ tree->pCheckRollback = 0;
+ tree->pCheckRollbackTail = 0;
+ tree->eTransState = TRANS_NONE;
+ return SQLITE_OK;
+}
+
+static int memRbtreeBeginCkpt(Rbtree* tree)
+{
+ if( tree->eTransState != TRANS_INTRANSACTION )
+ return SQLITE_ERROR;
+
+ assert( tree->pCheckRollback == 0 );
+ assert( tree->pCheckRollbackTail == 0 );
+ tree->eTransState = TRANS_INCHECKPOINT;
+ return SQLITE_OK;
+}
+
+static int memRbtreeCommitCkpt(Rbtree* tree)
+{
+ if( tree->eTransState == TRANS_INCHECKPOINT ){
+ if( tree->pCheckRollback ){
+ tree->pCheckRollbackTail->pNext = tree->pTransRollback;
+ tree->pTransRollback = tree->pCheckRollback;
+ tree->pCheckRollback = 0;
+ tree->pCheckRollbackTail = 0;
+ }
+ tree->eTransState = TRANS_INTRANSACTION;
+ }
+ return SQLITE_OK;
+}
+
+static int memRbtreeRollbackCkpt(Rbtree* tree)
+{
+ if( tree->eTransState != TRANS_INCHECKPOINT ) return SQLITE_OK;
+ tree->eTransState = TRANS_ROLLBACK;
+ execute_rollback_list(tree, tree->pCheckRollback);
+ tree->pCheckRollback = 0;
+ tree->pCheckRollbackTail = 0;
+ tree->eTransState = TRANS_INTRANSACTION;
+ return SQLITE_OK;
+}
+
+#ifdef SQLITE_TEST
+static int memRbtreePageDump(Rbtree* tree, int pgno, int rec)
+{
+ assert(!"Cannot call sqliteRbtreePageDump");
+ return SQLITE_OK;
+}
+
+static int memRbtreeCursorDump(RbtCursor* pCur, int* aRes)
+{
+ assert(!"Cannot call sqliteRbtreeCursorDump");
+ return SQLITE_OK;
+}
+#endif
+
+static struct Pager *memRbtreePager(Rbtree* tree)
+{
+ return 0;
+}
+
+/*
+** Return the full pathname of the underlying database file.
+*/
+static const char *memRbtreeGetFilename(Rbtree *pBt){
+ return 0; /* A NULL return indicates there is no underlying file */
+}
+
+/*
+** The copy file function is not implemented for the in-memory database
+*/
+static int memRbtreeCopyFile(Rbtree *pBt, Rbtree *pBt2){
+ return SQLITE_INTERNAL; /* Not implemented */
+}
+
+static BtOps sqliteRbtreeOps = {
+ (int(*)(Btree*)) memRbtreeClose,
+ (int(*)(Btree*,int)) memRbtreeSetCacheSize,
+ (int(*)(Btree*,int)) memRbtreeSetSafetyLevel,
+ (int(*)(Btree*)) memRbtreeBeginTrans,
+ (int(*)(Btree*)) memRbtreeCommit,
+ (int(*)(Btree*)) memRbtreeRollback,
+ (int(*)(Btree*)) memRbtreeBeginCkpt,
+ (int(*)(Btree*)) memRbtreeCommitCkpt,
+ (int(*)(Btree*)) memRbtreeRollbackCkpt,
+ (int(*)(Btree*,int*)) memRbtreeCreateTable,
+ (int(*)(Btree*,int*)) memRbtreeCreateTable,
+ (int(*)(Btree*,int)) memRbtreeDropTable,
+ (int(*)(Btree*,int)) memRbtreeClearTable,
+ (int(*)(Btree*,int,int,BtCursor**)) memRbtreeCursor,
+ (int(*)(Btree*,int*)) memRbtreeGetMeta,
+ (int(*)(Btree*,int*)) memRbtreeUpdateMeta,
+ (char*(*)(Btree*,int*,int)) memRbtreeIntegrityCheck,
+ (const char*(*)(Btree*)) memRbtreeGetFilename,
+ (int(*)(Btree*,Btree*)) memRbtreeCopyFile,
+ (struct Pager*(*)(Btree*)) memRbtreePager,
+#ifdef SQLITE_TEST
+ (int(*)(Btree*,int,int)) memRbtreePageDump,
+#endif
+};
+
+static BtCursorOps sqliteRbtreeCursorOps = {
+ (int(*)(BtCursor*,const void*,int,int*)) memRbtreeMoveto,
+ (int(*)(BtCursor*)) memRbtreeDelete,
+ (int(*)(BtCursor*,const void*,int,const void*,int)) memRbtreeInsert,
+ (int(*)(BtCursor*,int*)) memRbtreeFirst,
+ (int(*)(BtCursor*,int*)) memRbtreeLast,
+ (int(*)(BtCursor*,int*)) memRbtreeNext,
+ (int(*)(BtCursor*,int*)) memRbtreePrevious,
+ (int(*)(BtCursor*,int*)) memRbtreeKeySize,
+ (int(*)(BtCursor*,int,int,char*)) memRbtreeKey,
+ (int(*)(BtCursor*,const void*,int,int,int*)) memRbtreeKeyCompare,
+ (int(*)(BtCursor*,int*)) memRbtreeDataSize,
+ (int(*)(BtCursor*,int,int,char*)) memRbtreeData,
+ (int(*)(BtCursor*)) memRbtreeCloseCursor,
+#ifdef SQLITE_TEST
+ (int(*)(BtCursor*,int*)) memRbtreeCursorDump,
+#endif
+
+};
+
+#endif /* SQLITE_OMIT_INMEMORYDB */
diff --git a/src/libs/sqlite2/build.c b/src/libs/sqlite2/build.c
new file mode 100644
index 00000000..6c17f140
--- /dev/null
+++ b/src/libs/sqlite2/build.c
@@ -0,0 +1,2156 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the SQLite parser
+** when syntax rules are reduced. The routines in this file handle the
+** following kinds of SQL syntax:
+**
+** CREATE TABLE
+** DROP TABLE
+** CREATE INDEX
+** DROP INDEX
+** creating ID lists
+** BEGIN TRANSACTION
+** COMMIT
+** ROLLBACK
+** PRAGMA
+**
+** $Id: build.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+#include <ctype.h>
+
+/*
+** This routine is called when a new SQL statement is beginning to
+** be parsed. Check to see if the schema for the database needs
+** to be read from the SQLITE_MASTER and SQLITE_TEMP_MASTER tables.
+** If it does, then read it.
+*/
+void sqliteBeginParse(Parse *pParse, int explainFlag){
+ sqlite *db = pParse->db;
+ int i;
+ pParse->explain = explainFlag;
+ if((db->flags & SQLITE_Initialized)==0 && db->init.busy==0 ){
+ int rc = sqliteInit(db, &pParse->zErrMsg);
+ if( rc!=SQLITE_OK ){
+ pParse->rc = rc;
+ pParse->nErr++;
+ }
+ }
+ for(i=0; i<db->nDb; i++){
+ DbClearProperty(db, i, DB_Locked);
+ if( !db->aDb[i].inTrans ){
+ DbClearProperty(db, i, DB_Cookie);
+ }
+ }
+ pParse->nVar = 0;
+}
+
+/*
+** This routine is called after a single SQL statement has been
+** parsed and we want to execute the VDBE code to implement
+** that statement. Prior action routines should have already
+** constructed VDBE code to do the work of the SQL statement.
+** This routine just has to execute the VDBE code.
+**
+** Note that if an error occurred, it might be the case that
+** no VDBE code was generated.
+*/
+void sqliteExec(Parse *pParse){
+ sqlite *db = pParse->db;
+ Vdbe *v = pParse->pVdbe;
+
+ if( v==0 && (v = sqliteGetVdbe(pParse))!=0 ){
+ sqliteVdbeAddOp(v, OP_Halt, 0, 0);
+ }
+ if( sqlite_malloc_failed ) return;
+ if( v && pParse->nErr==0 ){
+ FILE *trace = (db->flags & SQLITE_VdbeTrace)!=0 ? stdout : 0;
+ sqliteVdbeTrace(v, trace);
+ sqliteVdbeMakeReady(v, pParse->nVar, pParse->explain);
+ pParse->rc = pParse->nErr ? SQLITE_ERROR : SQLITE_DONE;
+ pParse->colNamesSet = 0;
+ }else if( pParse->rc==SQLITE_OK ){
+ pParse->rc = SQLITE_ERROR;
+ }
+ pParse->nTab = 0;
+ pParse->nMem = 0;
+ pParse->nSet = 0;
+ pParse->nAgg = 0;
+ pParse->nVar = 0;
+}
+
+/*
+** Locate the in-memory structure that describes
+** a particular database table given the name
+** of that table and (optionally) the name of the database
+** containing the table. Return NULL if not found.
+**
+** If zDatabase is 0, all databases are searched for the
+** table and the first matching table is returned. (No checking
+** for duplicate table names is done.) The search order is
+** TEMP first, then MAIN, then any auxiliary databases added
+** using the ATTACH command.
+**
+** See also sqliteLocateTable().
+*/
+Table *sqliteFindTable(sqlite *db, const char *zName, const char *zDatabase){
+ Table *p = 0;
+ int i;
+ for(i=0; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ if( zDatabase!=0 && sqliteStrICmp(zDatabase, db->aDb[j].zName) ) continue;
+ p = sqliteHashFind(&db->aDb[j].tblHash, zName, strlen(zName)+1);
+ if( p ) break;
+ }
+ return p;
+}
+
+/*
+** Locate the in-memory structure that describes
+** a particular database table given the name
+** of that table and (optionally) the name of the database
+** containing the table. Return NULL if not found.
+** Also leave an error message in pParse->zErrMsg.
+**
+** The difference between this routine and sqliteFindTable()
+** is that this routine leaves an error message in pParse->zErrMsg
+** where sqliteFindTable() does not.
+*/
+Table *sqliteLocateTable(Parse *pParse, const char *zName, const char *zDbase){
+ Table *p;
+
+ p = sqliteFindTable(pParse->db, zName, zDbase);
+ if( p==0 ){
+ if( zDbase ){
+ sqliteErrorMsg(pParse, "no such table: %s.%s", zDbase, zName);
+ }else if( sqliteFindTable(pParse->db, zName, 0)!=0 ){
+ sqliteErrorMsg(pParse, "table \"%s\" is not in database \"%s\"",
+ zName, zDbase);
+ }else{
+ sqliteErrorMsg(pParse, "no such table: %s", zName);
+ }
+ }
+ return p;
+}
+
+/*
+** Locate the in-memory structure that describes
+** a particular index given the name of that index
+** and the name of the database that contains the index.
+** Return NULL if not found.
+**
+** If zDatabase is 0, all databases are searched for the
+** table and the first matching index is returned. (No checking
+** for duplicate index names is done.) The search order is
+** TEMP first, then MAIN, then any auxiliary databases added
+** using the ATTACH command.
+*/
+Index *sqliteFindIndex(sqlite *db, const char *zName, const char *zDb){
+ Index *p = 0;
+ int i;
+ for(i=0; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ if( zDb && sqliteStrICmp(zDb, db->aDb[j].zName) ) continue;
+ p = sqliteHashFind(&db->aDb[j].idxHash, zName, strlen(zName)+1);
+ if( p ) break;
+ }
+ return p;
+}
+
+/*
+** Remove the given index from the index hash table, and free
+** its memory structures.
+**
+** The index is removed from the database hash tables but
+** it is not unlinked from the Table that it indexes.
+** Unlinking from the Table must be done by the calling function.
+*/
+static void sqliteDeleteIndex(sqlite *db, Index *p){
+ Index *pOld;
+
+ assert( db!=0 && p->zName!=0 );
+ pOld = sqliteHashInsert(&db->aDb[p->iDb].idxHash, p->zName,
+ strlen(p->zName)+1, 0);
+ if( pOld!=0 && pOld!=p ){
+ sqliteHashInsert(&db->aDb[p->iDb].idxHash, pOld->zName,
+ strlen(pOld->zName)+1, pOld);
+ }
+ sqliteFree(p);
+}
+
+/*
+** Unlink the given index from its table, then remove
+** the index from the index hash table and free its memory
+** structures.
+*/
+void sqliteUnlinkAndDeleteIndex(sqlite *db, Index *pIndex){
+ if( pIndex->pTable->pIndex==pIndex ){
+ pIndex->pTable->pIndex = pIndex->pNext;
+ }else{
+ Index *p;
+ for(p=pIndex->pTable->pIndex; p && p->pNext!=pIndex; p=p->pNext){}
+ if( p && p->pNext==pIndex ){
+ p->pNext = pIndex->pNext;
+ }
+ }
+ sqliteDeleteIndex(db, pIndex);
+}
+
+/*
+** Erase all schema information from the in-memory hash tables of
+** database connection. This routine is called to reclaim memory
+** before the connection closes. It is also called during a rollback
+** if there were schema changes during the transaction.
+**
+** If iDb<=0 then reset the internal schema tables for all database
+** files. If iDb>=2 then reset the internal schema for only the
+** single file indicated.
+*/
+void sqliteResetInternalSchema(sqlite *db, int iDb){
+ HashElem *pElem;
+ Hash temp1;
+ Hash temp2;
+ int i, j;
+
+ assert( iDb>=0 && iDb<db->nDb );
+ db->flags &= ~SQLITE_Initialized;
+ for(i=iDb; i<db->nDb; i++){
+ Db *pDb = &db->aDb[i];
+ temp1 = pDb->tblHash;
+ temp2 = pDb->trigHash;
+ sqliteHashInit(&pDb->trigHash, SQLITE_HASH_STRING, 0);
+ sqliteHashClear(&pDb->aFKey);
+ sqliteHashClear(&pDb->idxHash);
+ for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
+ Trigger *pTrigger = sqliteHashData(pElem);
+ sqliteDeleteTrigger(pTrigger);
+ }
+ sqliteHashClear(&temp2);
+ sqliteHashInit(&pDb->tblHash, SQLITE_HASH_STRING, 0);
+ for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
+ Table *pTab = sqliteHashData(pElem);
+ sqliteDeleteTable(db, pTab);
+ }
+ sqliteHashClear(&temp1);
+ DbClearProperty(db, i, DB_SchemaLoaded);
+ if( iDb>0 ) return;
+ }
+ assert( iDb==0 );
+ db->flags &= ~SQLITE_InternChanges;
+
+ /* If one or more of the auxiliary database files has been closed,
+ ** then remove then from the auxiliary database list. We take the
+ ** opportunity to do this here since we have just deleted all of the
+ ** schema hash tables and therefore do not have to make any changes
+ ** to any of those tables.
+ */
+ for(i=0; i<db->nDb; i++){
+ struct Db *pDb = &db->aDb[i];
+ if( pDb->pBt==0 ){
+ if( pDb->pAux && pDb->xFreeAux ) pDb->xFreeAux(pDb->pAux);
+ pDb->pAux = 0;
+ }
+ }
+ for(i=j=2; i<db->nDb; i++){
+ struct Db *pDb = &db->aDb[i];
+ if( pDb->pBt==0 ){
+ sqliteFree(pDb->zName);
+ pDb->zName = 0;
+ continue;
+ }
+ if( j<i ){
+ db->aDb[j] = db->aDb[i];
+ }
+ j++;
+ }
+ memset(&db->aDb[j], 0, (db->nDb-j)*sizeof(db->aDb[j]));
+ db->nDb = j;
+ if( db->nDb<=2 && db->aDb!=db->aDbStatic ){
+ memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0]));
+ sqliteFree(db->aDb);
+ db->aDb = db->aDbStatic;
+ }
+}
+
+/*
+** This routine is called whenever a rollback occurs. If there were
+** schema changes during the transaction, then we have to reset the
+** internal hash tables and reload them from disk.
+*/
+void sqliteRollbackInternalChanges(sqlite *db){
+ if( db->flags & SQLITE_InternChanges ){
+ sqliteResetInternalSchema(db, 0);
+ }
+}
+
+/*
+** This routine is called when a commit occurs.
+*/
+void sqliteCommitInternalChanges(sqlite *db){
+ db->aDb[0].schema_cookie = db->next_cookie;
+ db->flags &= ~SQLITE_InternChanges;
+}
+
+/*
+** Remove the memory data structures associated with the given
+** Table. No changes are made to disk by this routine.
+**
+** This routine just deletes the data structure. It does not unlink
+** the table data structure from the hash table. Nor does it remove
+** foreign keys from the sqlite.aFKey hash table. But it does destroy
+** memory structures of the indices and foreign keys associated with
+** the table.
+**
+** Indices associated with the table are unlinked from the "db"
+** data structure if db!=NULL. If db==NULL, indices attached to
+** the table are deleted, but it is assumed they have already been
+** unlinked.
+*/
+void sqliteDeleteTable(sqlite *db, Table *pTable){
+ int i;
+ Index *pIndex, *pNext;
+ FKey *pFKey, *pNextFKey;
+
+ if( pTable==0 ) return;
+
+ /* Delete all indices associated with this table
+ */
+ for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){
+ pNext = pIndex->pNext;
+ assert( pIndex->iDb==pTable->iDb || (pTable->iDb==0 && pIndex->iDb==1) );
+ sqliteDeleteIndex(db, pIndex);
+ }
+
+ /* Delete all foreign keys associated with this table. The keys
+ ** should have already been unlinked from the db->aFKey hash table
+ */
+ for(pFKey=pTable->pFKey; pFKey; pFKey=pNextFKey){
+ pNextFKey = pFKey->pNextFrom;
+ assert( pTable->iDb<db->nDb );
+ assert( sqliteHashFind(&db->aDb[pTable->iDb].aFKey,
+ pFKey->zTo, strlen(pFKey->zTo)+1)!=pFKey );
+ sqliteFree(pFKey);
+ }
+
+ /* Delete the Table structure itself.
+ */
+ for(i=0; i<pTable->nCol; i++){
+ sqliteFree(pTable->aCol[i].zName);
+ sqliteFree(pTable->aCol[i].zDflt);
+ sqliteFree(pTable->aCol[i].zType);
+ }
+ sqliteFree(pTable->zName);
+ sqliteFree(pTable->aCol);
+ sqliteSelectDelete(pTable->pSelect);
+ sqliteFree(pTable);
+}
+
+/*
+** Unlink the given table from the hash tables and the delete the
+** table structure with all its indices and foreign keys.
+*/
+static void sqliteUnlinkAndDeleteTable(sqlite *db, Table *p){
+ Table *pOld;
+ FKey *pF1, *pF2;
+ int i = p->iDb;
+ assert( db!=0 );
+ pOld = sqliteHashInsert(&db->aDb[i].tblHash, p->zName, strlen(p->zName)+1, 0);
+ assert( pOld==0 || pOld==p );
+ for(pF1=p->pFKey; pF1; pF1=pF1->pNextFrom){
+ int nTo = strlen(pF1->zTo) + 1;
+ pF2 = sqliteHashFind(&db->aDb[i].aFKey, pF1->zTo, nTo);
+ if( pF2==pF1 ){
+ sqliteHashInsert(&db->aDb[i].aFKey, pF1->zTo, nTo, pF1->pNextTo);
+ }else{
+ while( pF2 && pF2->pNextTo!=pF1 ){ pF2=pF2->pNextTo; }
+ if( pF2 ){
+ pF2->pNextTo = pF1->pNextTo;
+ }
+ }
+ }
+ sqliteDeleteTable(db, p);
+}
+
+/*
+** Construct the name of a user table or index from a token.
+**
+** Space to hold the name is obtained from sqliteMalloc() and must
+** be freed by the calling function.
+*/
+char *sqliteTableNameFromToken(Token *pName){
+ char *zName = sqliteStrNDup(pName->z, pName->n);
+ sqliteDequote(zName);
+ return zName;
+}
+
+/*
+** Generate code to open the appropriate master table. The table
+** opened will be SQLITE_MASTER for persistent tables and
+** SQLITE_TEMP_MASTER for temporary tables. The table is opened
+** on cursor 0.
+*/
+void sqliteOpenMasterTable(Vdbe *v, int isTemp){
+ sqliteVdbeAddOp(v, OP_Integer, isTemp, 0);
+ sqliteVdbeAddOp(v, OP_OpenWrite, 0, 2);
+}
+
+/*
+** Begin constructing a new table representation in memory. This is
+** the first of several action routines that get called in response
+** to a CREATE TABLE statement. In particular, this routine is called
+** after seeing tokens "CREATE" and "TABLE" and the table name. The
+** pStart token is the CREATE and pName is the table name. The isTemp
+** flag is true if the table should be stored in the auxiliary database
+** file instead of in the main database file. This is normally the case
+** when the "TEMP" or "TEMPORARY" keyword occurs in between
+** CREATE and TABLE.
+**
+** The new table record is initialized and put in pParse->pNewTable.
+** As more of the CREATE TABLE statement is parsed, additional action
+** routines will be called to add more information to this record.
+** At the end of the CREATE TABLE statement, the sqliteEndTable() routine
+** is called to complete the construction of the new table record.
+*/
+void sqliteStartTable(
+ Parse *pParse, /* Parser context */
+ Token *pStart, /* The "CREATE" token */
+ Token *pName, /* Name of table or view to create */
+ int isTemp, /* True if this is a TEMP table */
+ int isView /* True if this is a VIEW */
+){
+ Table *pTable;
+ Index *pIdx;
+ char *zName;
+ sqlite *db = pParse->db;
+ Vdbe *v;
+ int iDb;
+
+ pParse->sFirstToken = *pStart;
+ zName = sqliteTableNameFromToken(pName);
+ if( zName==0 ) return;
+ if( db->init.iDb==1 ) isTemp = 1;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ assert( (isTemp & 1)==isTemp );
+ {
+ int code;
+ char *zDb = isTemp ? "temp" : "main";
+ if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){
+ sqliteFree(zName);
+ return;
+ }
+ if( isView ){
+ if( isTemp ){
+ code = SQLITE_CREATE_TEMP_VIEW;
+ }else{
+ code = SQLITE_CREATE_VIEW;
+ }
+ }else{
+ if( isTemp ){
+ code = SQLITE_CREATE_TEMP_TABLE;
+ }else{
+ code = SQLITE_CREATE_TABLE;
+ }
+ }
+ if( sqliteAuthCheck(pParse, code, zName, 0, zDb) ){
+ sqliteFree(zName);
+ return;
+ }
+ }
+#endif
+
+
+ /* Before trying to create a temporary table, make sure the Btree for
+ ** holding temporary tables is open.
+ */
+ if( isTemp && db->aDb[1].pBt==0 && !pParse->explain ){
+ int rc = sqliteBtreeFactory(db, 0, 0, MAX_PAGES, &db->aDb[1].pBt);
+ if( rc!=SQLITE_OK ){
+ sqliteErrorMsg(pParse, "unable to open a temporary database "
+ "file for storing temporary tables");
+ pParse->nErr++;
+ return;
+ }
+ if( db->flags & SQLITE_InTrans ){
+ rc = sqliteBtreeBeginTrans(db->aDb[1].pBt);
+ if( rc!=SQLITE_OK ){
+ sqliteErrorMsg(pParse, "unable to get a write lock on "
+ "the temporary database file");
+ return;
+ }
+ }
+ }
+
+ /* Make sure the new table name does not collide with an existing
+ ** index or table name. Issue an error message if it does.
+ **
+ ** If we are re-reading the sqlite_master table because of a schema
+ ** change and a new permanent table is found whose name collides with
+ ** an existing temporary table, that is not an error.
+ */
+ pTable = sqliteFindTable(db, zName, 0);
+ iDb = isTemp ? 1 : db->init.iDb;
+ if( pTable!=0 && (pTable->iDb==iDb || !db->init.busy) ){
+ sqliteErrorMsg(pParse, "table %T already exists", pName);
+ sqliteFree(zName);
+ return;
+ }
+ if( (pIdx = sqliteFindIndex(db, zName, 0))!=0 &&
+ (pIdx->iDb==0 || !db->init.busy) ){
+ sqliteErrorMsg(pParse, "there is already an index named %s", zName);
+ sqliteFree(zName);
+ return;
+ }
+ pTable = sqliteMalloc( sizeof(Table) );
+ if( pTable==0 ){
+ sqliteFree(zName);
+ return;
+ }
+ pTable->zName = zName;
+ pTable->nCol = 0;
+ pTable->aCol = 0;
+ pTable->iPKey = -1;
+ pTable->pIndex = 0;
+ pTable->iDb = iDb;
+ if( pParse->pNewTable ) sqliteDeleteTable(db, pParse->pNewTable);
+ pParse->pNewTable = pTable;
+
+ /* Begin generating the code that will insert the table record into
+ ** the SQLITE_MASTER table. Note in particular that we must go ahead
+ ** and allocate the record number for the table entry now. Before any
+ ** PRIMARY KEY or UNIQUE keywords are parsed. Those keywords will cause
+ ** indices to be created and the table record must come before the
+ ** indices. Hence, the record number for the table must be allocated
+ ** now.
+ */
+ if( !db->init.busy && (v = sqliteGetVdbe(pParse))!=0 ){
+ sqliteBeginWriteOperation(pParse, 0, isTemp);
+ if( !isTemp ){
+ sqliteVdbeAddOp(v, OP_Integer, db->file_format, 0);
+ sqliteVdbeAddOp(v, OP_SetCookie, 0, 1);
+ }
+ sqliteOpenMasterTable(v, isTemp);
+ sqliteVdbeAddOp(v, OP_NewRecno, 0, 0);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0);
+ }
+}
+
+/*
+** Add a new column to the table currently being constructed.
+**
+** The parser calls this routine once for each column declaration
+** in a CREATE TABLE statement. sqliteStartTable() gets called
+** first to get things going. Then this routine is called for each
+** column.
+*/
+void sqliteAddColumn(Parse *pParse, Token *pName){
+ Table *p;
+ int i;
+ char *z = 0;
+ Column *pCol;
+ if( (p = pParse->pNewTable)==0 ) return;
+ sqliteSetNString(&z, pName->z, pName->n, 0);
+ if( z==0 ) return;
+ sqliteDequote(z);
+ for(i=0; i<p->nCol; i++){
+ if( sqliteStrICmp(z, p->aCol[i].zName)==0 ){
+ sqliteErrorMsg(pParse, "duplicate column name: %s", z);
+ sqliteFree(z);
+ return;
+ }
+ }
+ if( (p->nCol & 0x7)==0 ){
+ Column *aNew;
+ aNew = sqliteRealloc( p->aCol, (p->nCol+8)*sizeof(p->aCol[0]));
+ if( aNew==0 ) return;
+ p->aCol = aNew;
+ }
+ pCol = &p->aCol[p->nCol];
+ memset(pCol, 0, sizeof(p->aCol[0]));
+ pCol->zName = z;
+ pCol->sortOrder = SQLITE_SO_NUM;
+ p->nCol++;
+}
+
+/*
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement. A "NOT NULL" constraint has
+** been seen on a column. This routine sets the notNull flag on
+** the column currently under construction.
+*/
+void sqliteAddNotNull(Parse *pParse, int onError){
+ Table *p;
+ int i;
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+ if( i>=0 ) p->aCol[i].notNull = onError;
+}
+
+/*
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement. The pFirst token is the first
+** token in the sequence of tokens that describe the type of the
+** column currently under construction. pLast is the last token
+** in the sequence. Use this information to construct a string
+** that contains the typename of the column and store that string
+** in zType.
+*/
+void sqliteAddColumnType(Parse *pParse, Token *pFirst, Token *pLast){
+ Table *p;
+ int i, j;
+ int n;
+ char *z, **pz;
+ Column *pCol;
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+ if( i<0 ) return;
+ pCol = &p->aCol[i];
+ pz = &pCol->zType;
+ n = pLast->n + Addr(pLast->z) - Addr(pFirst->z);
+ sqliteSetNString(pz, pFirst->z, n, 0);
+ z = *pz;
+ if( z==0 ) return;
+ for(i=j=0; z[i]; i++){
+ int c = z[i];
+ if( isspace(c) ) continue;
+ z[j++] = c;
+ }
+ z[j] = 0;
+ if( pParse->db->file_format>=4 ){
+ pCol->sortOrder = sqliteCollateType(z, n);
+ }else{
+ pCol->sortOrder = SQLITE_SO_NUM;
+ }
+}
+
+/*
+** The given token is the default value for the last column added to
+** the table currently under construction. If "minusFlag" is true, it
+** means the value token was preceded by a minus sign.
+**
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement.
+*/
+void sqliteAddDefaultValue(Parse *pParse, Token *pVal, int minusFlag){
+ Table *p;
+ int i;
+ char **pz;
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+ if( i<0 ) return;
+ pz = &p->aCol[i].zDflt;
+ if( minusFlag ){
+ sqliteSetNString(pz, "-", 1, pVal->z, pVal->n, 0);
+ }else{
+ sqliteSetNString(pz, pVal->z, pVal->n, 0);
+ }
+ sqliteDequote(*pz);
+}
+
+/*
+** Designate the PRIMARY KEY for the table. pList is a list of names
+** of columns that form the primary key. If pList is NULL, then the
+** most recently added column of the table is the primary key.
+**
+** A table can have at most one primary key. If the table already has
+** a primary key (and this is the second primary key) then create an
+** error.
+**
+** If the PRIMARY KEY is on a single column whose datatype is INTEGER,
+** then we will try to use that column as the row id. (Exception:
+** For backwards compatibility with older databases, do not do this
+** if the file format version number is less than 1.) Set the Table.iPKey
+** field of the table under construction to be the index of the
+** INTEGER PRIMARY KEY column. Table.iPKey is set to -1 if there is
+** no INTEGER PRIMARY KEY.
+**
+** If the key is not an INTEGER PRIMARY KEY, then create a unique
+** index for the key. No index is created for INTEGER PRIMARY KEYs.
+*/
+void sqliteAddPrimaryKey(Parse *pParse, IdList *pList, int onError){
+ Table *pTab = pParse->pNewTable;
+ char *zType = 0;
+ int iCol = -1, i;
+ if( pTab==0 ) goto primary_key_exit;
+ if( pTab->hasPrimKey ){
+ sqliteErrorMsg(pParse,
+ "table \"%s\" has more than one primary key", pTab->zName);
+ goto primary_key_exit;
+ }
+ pTab->hasPrimKey = 1;
+ if( pList==0 ){
+ iCol = pTab->nCol - 1;
+ pTab->aCol[iCol].isPrimKey = 1;
+ }else{
+ for(i=0; i<pList->nId; i++){
+ for(iCol=0; iCol<pTab->nCol; iCol++){
+ if( sqliteStrICmp(pList->a[i].zName, pTab->aCol[iCol].zName)==0 ) break;
+ }
+ if( iCol<pTab->nCol ) pTab->aCol[iCol].isPrimKey = 1;
+ }
+ if( pList->nId>1 ) iCol = -1;
+ }
+ if( iCol>=0 && iCol<pTab->nCol ){
+ zType = pTab->aCol[iCol].zType;
+ }
+ if( pParse->db->file_format>=1 &&
+ zType && sqliteStrICmp(zType, "INTEGER")==0 ){
+ pTab->iPKey = iCol;
+ pTab->keyConf = onError;
+ }else{
+ sqliteCreateIndex(pParse, 0, 0, pList, onError, 0, 0);
+ pList = 0;
+ }
+
+primary_key_exit:
+ sqliteIdListDelete(pList);
+ return;
+}
+
+/*
+** Return the appropriate collating type given a type name.
+**
+** The collation type is text (SQLITE_SO_TEXT) if the type
+** name contains the character stream "text" or "blob" or
+** "clob". Any other type name is collated as numeric
+** (SQLITE_SO_NUM).
+*/
+int sqliteCollateType(const char *zType, int nType){
+ int i;
+ for(i=0; i<nType-3; i++){
+ int c = *(zType++) | 0x60;
+ if( (c=='b' || c=='c') && sqliteStrNICmp(zType, "lob", 3)==0 ){
+ return SQLITE_SO_TEXT;
+ }
+ if( c=='c' && sqliteStrNICmp(zType, "har", 3)==0 ){
+ return SQLITE_SO_TEXT;
+ }
+ if( c=='t' && sqliteStrNICmp(zType, "ext", 3)==0 ){
+ return SQLITE_SO_TEXT;
+ }
+ }
+ return SQLITE_SO_NUM;
+}
+
+/*
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement. A "COLLATE" clause has
+** been seen on a column. This routine sets the Column.sortOrder on
+** the column currently under construction.
+*/
+void sqliteAddCollateType(Parse *pParse, int collType){
+ Table *p;
+ int i;
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+ if( i>=0 ) p->aCol[i].sortOrder = collType;
+}
+
+/*
+** Come up with a new random value for the schema cookie. Make sure
+** the new value is different from the old.
+**
+** The schema cookie is used to determine when the schema for the
+** database changes. After each schema change, the cookie value
+** changes. When a process first reads the schema it records the
+** cookie. Thereafter, whenever it goes to access the database,
+** it checks the cookie to make sure the schema has not changed
+** since it was last read.
+**
+** This plan is not completely bullet-proof. It is possible for
+** the schema to change multiple times and for the cookie to be
+** set back to prior value. But schema changes are infrequent
+** and the probability of hitting the same cookie value is only
+** 1 chance in 2^32. So we're safe enough.
+*/
+void sqliteChangeCookie(sqlite *db, Vdbe *v){
+ if( db->next_cookie==db->aDb[0].schema_cookie ){
+ unsigned char r;
+ sqliteRandomness(1, &r);
+ db->next_cookie = db->aDb[0].schema_cookie + r + 1;
+ db->flags |= SQLITE_InternChanges;
+ sqliteVdbeAddOp(v, OP_Integer, db->next_cookie, 0);
+ sqliteVdbeAddOp(v, OP_SetCookie, 0, 0);
+ }
+}
+
+/*
+** Measure the number of characters needed to output the given
+** identifier. The number returned includes any quotes used
+** but does not include the null terminator.
+*/
+static int identLength(const char *z){
+ int n;
+ int needQuote = 0;
+ for(n=0; *z; n++, z++){
+ if( *z=='\'' ){ n++; needQuote=1; }
+ }
+ return n + needQuote*2;
+}
+
+/*
+** Write an identifier onto the end of the given string. Add
+** quote characters as needed.
+*/
+static void identPut(char *z, int *pIdx, char *zIdent){
+ int i, j, needQuote;
+ i = *pIdx;
+ for(j=0; zIdent[j]; j++){
+ if( !isalnum(zIdent[j]) && zIdent[j]!='_' ) break;
+ }
+ needQuote = zIdent[j]!=0 || isdigit(zIdent[0])
+ || sqliteKeywordCode(zIdent, j)!=TK_ID;
+ if( needQuote ) z[i++] = '\'';
+ for(j=0; zIdent[j]; j++){
+ z[i++] = zIdent[j];
+ if( zIdent[j]=='\'' ) z[i++] = '\'';
+ }
+ if( needQuote ) z[i++] = '\'';
+ z[i] = 0;
+ *pIdx = i;
+}
+
+/*
+** Generate a CREATE TABLE statement appropriate for the given
+** table. Memory to hold the text of the statement is obtained
+** from sqliteMalloc() and must be freed by the calling function.
+*/
+static char *createTableStmt(Table *p){
+ int i, k, n;
+ char *zStmt;
+ char *zSep, *zSep2, *zEnd;
+ n = 0;
+ for(i=0; i<p->nCol; i++){
+ n += identLength(p->aCol[i].zName);
+ }
+ n += identLength(p->zName);
+ if( n<40 ){
+ zSep = "";
+ zSep2 = ",";
+ zEnd = ")";
+ }else{
+ zSep = "\n ";
+ zSep2 = ",\n ";
+ zEnd = "\n)";
+ }
+ n += 35 + 6*p->nCol;
+ zStmt = sqliteMallocRaw( n );
+ if( zStmt==0 ) return 0;
+ strcpy(zStmt, p->iDb==1 ? "CREATE TEMP TABLE " : "CREATE TABLE ");
+ k = strlen(zStmt);
+ identPut(zStmt, &k, p->zName);
+ zStmt[k++] = '(';
+ for(i=0; i<p->nCol; i++){
+ strcpy(&zStmt[k], zSep);
+ k += strlen(&zStmt[k]);
+ zSep = zSep2;
+ identPut(zStmt, &k, p->aCol[i].zName);
+ }
+ strcpy(&zStmt[k], zEnd);
+ return zStmt;
+}
+
+/*
+** This routine is called to report the final ")" that terminates
+** a CREATE TABLE statement.
+**
+** The table structure that other action routines have been building
+** is added to the internal hash tables, assuming no errors have
+** occurred.
+**
+** An entry for the table is made in the master table on disk, unless
+** this is a temporary table or db->init.busy==1. When db->init.busy==1
+** it means we are reading the sqlite_master table because we just
+** connected to the database or because the sqlite_master table has
+** recently changes, so the entry for this table already exists in
+** the sqlite_master table. We do not want to create it again.
+**
+** If the pSelect argument is not NULL, it means that this routine
+** was called to create a table generated from a
+** "CREATE TABLE ... AS SELECT ..." statement. The column names of
+** the new table will match the result set of the SELECT.
+*/
+void sqliteEndTable(Parse *pParse, Token *pEnd, Select *pSelect){
+ Table *p;
+ sqlite *db = pParse->db;
+
+ if( (pEnd==0 && pSelect==0) || pParse->nErr || sqlite_malloc_failed ) return;
+ p = pParse->pNewTable;
+ if( p==0 ) return;
+
+ /* If the table is generated from a SELECT, then construct the
+ ** list of columns and the text of the table.
+ */
+ if( pSelect ){
+ Table *pSelTab = sqliteResultSetOfSelect(pParse, 0, pSelect);
+ if( pSelTab==0 ) return;
+ assert( p->aCol==0 );
+ p->nCol = pSelTab->nCol;
+ p->aCol = pSelTab->aCol;
+ pSelTab->nCol = 0;
+ pSelTab->aCol = 0;
+ sqliteDeleteTable(0, pSelTab);
+ }
+
+ /* If the db->init.busy is 1 it means we are reading the SQL off the
+ ** "sqlite_master" or "sqlite_temp_master" table on the disk.
+ ** So do not write to the disk again. Extract the root page number
+ ** for the table from the db->init.newTnum field. (The page number
+ ** should have been put there by the sqliteOpenCb routine.)
+ */
+ if( db->init.busy ){
+ p->tnum = db->init.newTnum;
+ }
+
+ /* If not initializing, then create a record for the new table
+ ** in the SQLITE_MASTER table of the database. The record number
+ ** for the new table entry should already be on the stack.
+ **
+ ** If this is a TEMPORARY table, write the entry into the auxiliary
+ ** file instead of into the main database file.
+ */
+ if( !db->init.busy ){
+ int n;
+ Vdbe *v;
+
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) return;
+ if( p->pSelect==0 ){
+ /* A regular table */
+ sqliteVdbeOp3(v, OP_CreateTable, 0, p->iDb, (char*)&p->tnum, P3_POINTER);
+ }else{
+ /* A view */
+ sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ }
+ p->tnum = 0;
+ sqliteVdbeAddOp(v, OP_Pull, 1, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, p->pSelect==0?"table":"view", P3_STATIC);
+ sqliteVdbeOp3(v, OP_String, 0, 0, p->zName, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, p->zName, 0);
+ sqliteVdbeAddOp(v, OP_Dup, 4, 0);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ if( pSelect ){
+ char *z = createTableStmt(p);
+ n = z ? strlen(z) : 0;
+ sqliteVdbeChangeP3(v, -1, z, n);
+ sqliteFree(z);
+ }else{
+ assert( pEnd!=0 );
+ n = Addr(pEnd->z) - Addr(pParse->sFirstToken.z) + 1;
+ sqliteVdbeChangeP3(v, -1, pParse->sFirstToken.z, n);
+ }
+ sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0);
+ if( !p->iDb ){
+ sqliteChangeCookie(db, v);
+ }
+ sqliteVdbeAddOp(v, OP_Close, 0, 0);
+ if( pSelect ){
+ sqliteVdbeAddOp(v, OP_Integer, p->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenWrite, 1, 0);
+ pParse->nTab = 2;
+ sqliteSelect(pParse, pSelect, SRT_Table, 1, 0, 0, 0);
+ }
+ sqliteEndWriteOperation(pParse);
+ }
+
+ /* Add the table to the in-memory representation of the database.
+ */
+ if( pParse->explain==0 && pParse->nErr==0 ){
+ Table *pOld;
+ FKey *pFKey;
+ pOld = sqliteHashInsert(&db->aDb[p->iDb].tblHash,
+ p->zName, strlen(p->zName)+1, p);
+ if( pOld ){
+ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */
+ return;
+ }
+ for(pFKey=p->pFKey; pFKey; pFKey=pFKey->pNextFrom){
+ int nTo = strlen(pFKey->zTo) + 1;
+ pFKey->pNextTo = sqliteHashFind(&db->aDb[p->iDb].aFKey, pFKey->zTo, nTo);
+ sqliteHashInsert(&db->aDb[p->iDb].aFKey, pFKey->zTo, nTo, pFKey);
+ }
+ pParse->pNewTable = 0;
+ db->nTable++;
+ db->flags |= SQLITE_InternChanges;
+ }
+}
+
+/*
+** The parser calls this routine in order to create a new VIEW
+*/
+void sqliteCreateView(
+ Parse *pParse, /* The parsing context */
+ Token *pBegin, /* The CREATE token that begins the statement */
+ Token *pName, /* The token that holds the name of the view */
+ Select *pSelect, /* A SELECT statement that will become the new view */
+ int isTemp /* TRUE for a TEMPORARY view */
+){
+ Table *p;
+ int n;
+ const char *z;
+ Token sEnd;
+ DbFixer sFix;
+
+ sqliteStartTable(pParse, pBegin, pName, isTemp, 1);
+ p = pParse->pNewTable;
+ if( p==0 || pParse->nErr ){
+ sqliteSelectDelete(pSelect);
+ return;
+ }
+ if( sqliteFixInit(&sFix, pParse, p->iDb, "view", pName)
+ && sqliteFixSelect(&sFix, pSelect)
+ ){
+ sqliteSelectDelete(pSelect);
+ return;
+ }
+
+ /* Make a copy of the entire SELECT statement that defines the view.
+ ** This will force all the Expr.token.z values to be dynamically
+ ** allocated rather than point to the input string - which means that
+ ** they will persist after the current sqlite_exec() call returns.
+ */
+ p->pSelect = sqliteSelectDup(pSelect);
+ sqliteSelectDelete(pSelect);
+ if( !pParse->db->init.busy ){
+ sqliteViewGetColumnNames(pParse, p);
+ }
+
+ /* Locate the end of the CREATE VIEW statement. Make sEnd point to
+ ** the end.
+ */
+ sEnd = pParse->sLastToken;
+ if( sEnd.z[0]!=0 && sEnd.z[0]!=';' ){
+ sEnd.z += sEnd.n;
+ }
+ sEnd.n = 0;
+ n = sEnd.z - pBegin->z;
+ z = pBegin->z;
+ while( n>0 && (z[n-1]==';' || isspace(z[n-1])) ){ n--; }
+ sEnd.z = &z[n-1];
+ sEnd.n = 1;
+
+ /* Use sqliteEndTable() to add the view to the SQLITE_MASTER table */
+ sqliteEndTable(pParse, &sEnd, 0);
+ return;
+}
+
+/*
+** The Table structure pTable is really a VIEW. Fill in the names of
+** the columns of the view in the pTable structure. Return the number
+** of errors. If an error is seen leave an error message in pParse->zErrMsg.
+*/
+int sqliteViewGetColumnNames(Parse *pParse, Table *pTable){
+ ExprList *pEList;
+ Select *pSel;
+ Table *pSelTab;
+ int nErr = 0;
+
+ assert( pTable );
+
+ /* A positive nCol means the columns names for this view are
+ ** already known.
+ */
+ if( pTable->nCol>0 ) return 0;
+
+ /* A negative nCol is a special marker meaning that we are currently
+ ** trying to compute the column names. If we enter this routine with
+ ** a negative nCol, it means two or more views form a loop, like this:
+ **
+ ** CREATE VIEW one AS SELECT * FROM two;
+ ** CREATE VIEW two AS SELECT * FROM one;
+ **
+ ** Actually, this error is caught previously and so the following test
+ ** should always fail. But we will leave it in place just to be safe.
+ */
+ if( pTable->nCol<0 ){
+ sqliteErrorMsg(pParse, "view %s is circularly defined", pTable->zName);
+ return 1;
+ }
+
+ /* If we get this far, it means we need to compute the table names.
+ */
+ assert( pTable->pSelect ); /* If nCol==0, then pTable must be a VIEW */
+ pSel = pTable->pSelect;
+
+ /* Note that the call to sqliteResultSetOfSelect() will expand any
+ ** "*" elements in this list. But we will need to restore the list
+ ** back to its original configuration afterwards, so we save a copy of
+ ** the original in pEList.
+ */
+ pEList = pSel->pEList;
+ pSel->pEList = sqliteExprListDup(pEList);
+ if( pSel->pEList==0 ){
+ pSel->pEList = pEList;
+ return 1; /* Malloc failed */
+ }
+ pTable->nCol = -1;
+ pSelTab = sqliteResultSetOfSelect(pParse, 0, pSel);
+ if( pSelTab ){
+ assert( pTable->aCol==0 );
+ pTable->nCol = pSelTab->nCol;
+ pTable->aCol = pSelTab->aCol;
+ pSelTab->nCol = 0;
+ pSelTab->aCol = 0;
+ sqliteDeleteTable(0, pSelTab);
+ DbSetProperty(pParse->db, pTable->iDb, DB_UnresetViews);
+ }else{
+ pTable->nCol = 0;
+ nErr++;
+ }
+ sqliteSelectUnbind(pSel);
+ sqliteExprListDelete(pSel->pEList);
+ pSel->pEList = pEList;
+ return nErr;
+}
+
+/*
+** Clear the column names from the VIEW pTable.
+**
+** This routine is called whenever any other table or view is modified.
+** The view passed into this routine might depend directly or indirectly
+** on the modified or deleted table so we need to clear the old column
+** names so that they will be recomputed.
+*/
+static void sqliteViewResetColumnNames(Table *pTable){
+ int i;
+ Column *pCol;
+ assert( pTable!=0 && pTable->pSelect!=0 );
+ for(i=0, pCol=pTable->aCol; i<pTable->nCol; i++, pCol++){
+ sqliteFree(pCol->zName);
+ sqliteFree(pCol->zDflt);
+ sqliteFree(pCol->zType);
+ }
+ sqliteFree(pTable->aCol);
+ pTable->aCol = 0;
+ pTable->nCol = 0;
+}
+
+/*
+** Clear the column names from every VIEW in database idx.
+*/
+static void sqliteViewResetAll(sqlite *db, int idx){
+ HashElem *i;
+ if( !DbHasProperty(db, idx, DB_UnresetViews) ) return;
+ for(i=sqliteHashFirst(&db->aDb[idx].tblHash); i; i=sqliteHashNext(i)){
+ Table *pTab = sqliteHashData(i);
+ if( pTab->pSelect ){
+ sqliteViewResetColumnNames(pTab);
+ }
+ }
+ DbClearProperty(db, idx, DB_UnresetViews);
+}
+
+/*
+** Given a token, look up a table with that name. If not found, leave
+** an error for the parser to find and return NULL.
+*/
+Table *sqliteTableFromToken(Parse *pParse, Token *pTok){
+ char *zName;
+ Table *pTab;
+ zName = sqliteTableNameFromToken(pTok);
+ if( zName==0 ) return 0;
+ pTab = sqliteFindTable(pParse->db, zName, 0);
+ sqliteFree(zName);
+ if( pTab==0 ){
+ sqliteErrorMsg(pParse, "no such table: %T", pTok);
+ }
+ return pTab;
+}
+
+/*
+** This routine is called to do the work of a DROP TABLE statement.
+** pName is the name of the table to be dropped.
+*/
+void sqliteDropTable(Parse *pParse, Token *pName, int isView){
+ Table *pTable;
+ Vdbe *v;
+ int base;
+ sqlite *db = pParse->db;
+ int iDb;
+
+ if( pParse->nErr || sqlite_malloc_failed ) return;
+ pTable = sqliteTableFromToken(pParse, pName);
+ if( pTable==0 ) return;
+ iDb = pTable->iDb;
+ assert( iDb>=0 && iDb<db->nDb );
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code;
+ const char *zTab = SCHEMA_TABLE(pTable->iDb);
+ const char *zDb = db->aDb[pTable->iDb].zName;
+ if( sqliteAuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){
+ return;
+ }
+ if( isView ){
+ if( iDb==1 ){
+ code = SQLITE_DROP_TEMP_VIEW;
+ }else{
+ code = SQLITE_DROP_VIEW;
+ }
+ }else{
+ if( iDb==1 ){
+ code = SQLITE_DROP_TEMP_TABLE;
+ }else{
+ code = SQLITE_DROP_TABLE;
+ }
+ }
+ if( sqliteAuthCheck(pParse, code, pTable->zName, 0, zDb) ){
+ return;
+ }
+ if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTable->zName, 0, zDb) ){
+ return;
+ }
+ }
+#endif
+ if( pTable->readOnly ){
+ sqliteErrorMsg(pParse, "table %s may not be dropped", pTable->zName);
+ pParse->nErr++;
+ return;
+ }
+ if( isView && pTable->pSelect==0 ){
+ sqliteErrorMsg(pParse, "use DROP TABLE to delete table %s", pTable->zName);
+ return;
+ }
+ if( !isView && pTable->pSelect ){
+ sqliteErrorMsg(pParse, "use DROP VIEW to delete view %s", pTable->zName);
+ return;
+ }
+
+ /* Generate code to remove the table from the master table
+ ** on disk.
+ */
+ v = sqliteGetVdbe(pParse);
+ if( v ){
+ static VdbeOpList dropTable[] = {
+ { OP_Rewind, 0, ADDR(8), 0},
+ { OP_String, 0, 0, 0}, /* 1 */
+ { OP_MemStore, 1, 1, 0},
+ { OP_MemLoad, 1, 0, 0}, /* 3 */
+ { OP_Column, 0, 2, 0},
+ { OP_Ne, 0, ADDR(7), 0},
+ { OP_Delete, 0, 0, 0},
+ { OP_Next, 0, ADDR(3), 0}, /* 7 */
+ };
+ Index *pIdx;
+ Trigger *pTrigger;
+ sqliteBeginWriteOperation(pParse, 0, pTable->iDb);
+
+ /* Drop all triggers associated with the table being dropped */
+ pTrigger = pTable->pTrigger;
+ while( pTrigger ){
+ assert( pTrigger->iDb==pTable->iDb || pTrigger->iDb==1 );
+ sqliteDropTriggerPtr(pParse, pTrigger, 1);
+ if( pParse->explain ){
+ pTrigger = pTrigger->pNext;
+ }else{
+ pTrigger = pTable->pTrigger;
+ }
+ }
+
+ /* Drop all SQLITE_MASTER entries that refer to the table */
+ sqliteOpenMasterTable(v, pTable->iDb);
+ base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable);
+ sqliteVdbeChangeP3(v, base+1, pTable->zName, 0);
+
+ /* Drop all SQLITE_TEMP_MASTER entries that refer to the table */
+ if( pTable->iDb!=1 ){
+ sqliteOpenMasterTable(v, 1);
+ base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable);
+ sqliteVdbeChangeP3(v, base+1, pTable->zName, 0);
+ }
+
+ if( pTable->iDb==0 ){
+ sqliteChangeCookie(db, v);
+ }
+ sqliteVdbeAddOp(v, OP_Close, 0, 0);
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Destroy, pTable->tnum, pTable->iDb);
+ for(pIdx=pTable->pIndex; pIdx; pIdx=pIdx->pNext){
+ sqliteVdbeAddOp(v, OP_Destroy, pIdx->tnum, pIdx->iDb);
+ }
+ }
+ sqliteEndWriteOperation(pParse);
+ }
+
+ /* Delete the in-memory description of the table.
+ **
+ ** Exception: if the SQL statement began with the EXPLAIN keyword,
+ ** then no changes should be made.
+ */
+ if( !pParse->explain ){
+ sqliteUnlinkAndDeleteTable(db, pTable);
+ db->flags |= SQLITE_InternChanges;
+ }
+ sqliteViewResetAll(db, iDb);
+}
+
+/*
+** This routine constructs a P3 string suitable for an OP_MakeIdxKey
+** opcode and adds that P3 string to the most recently inserted instruction
+** in the virtual machine. The P3 string consists of a single character
+** for each column in the index pIdx of table pTab. If the column uses
+** a numeric sort order, then the P3 string character corresponding to
+** that column is 'n'. If the column uses a text sort order, then the
+** P3 string is 't'. See the OP_MakeIdxKey opcode documentation for
+** additional information. See also the sqliteAddKeyType() routine.
+*/
+void sqliteAddIdxKeyType(Vdbe *v, Index *pIdx){
+ char *zType;
+ Table *pTab;
+ int i, n;
+ assert( pIdx!=0 && pIdx->pTable!=0 );
+ pTab = pIdx->pTable;
+ n = pIdx->nColumn;
+ zType = sqliteMallocRaw( n+1 );
+ if( zType==0 ) return;
+ for(i=0; i<n; i++){
+ int iCol = pIdx->aiColumn[i];
+ assert( iCol>=0 && iCol<pTab->nCol );
+ if( (pTab->aCol[iCol].sortOrder & SQLITE_SO_TYPEMASK)==SQLITE_SO_TEXT ){
+ zType[i] = 't';
+ }else{
+ zType[i] = 'n';
+ }
+ }
+ zType[n] = 0;
+ sqliteVdbeChangeP3(v, -1, zType, n);
+ sqliteFree(zType);
+}
+
+/*
+** This routine is called to create a new foreign key on the table
+** currently under construction. pFromCol determines which columns
+** in the current table point to the foreign key. If pFromCol==0 then
+** connect the key to the last column inserted. pTo is the name of
+** the table referred to. pToCol is a list of tables in the other
+** pTo table that the foreign key points to. flags contains all
+** information about the conflict resolution algorithms specified
+** in the ON DELETE, ON UPDATE and ON INSERT clauses.
+**
+** An FKey structure is created and added to the table currently
+** under construction in the pParse->pNewTable field. The new FKey
+** is not linked into db->aFKey at this point - that does not happen
+** until sqliteEndTable().
+**
+** The foreign key is set for IMMEDIATE processing. A subsequent call
+** to sqliteDeferForeignKey() might change this to DEFERRED.
+*/
+void sqliteCreateForeignKey(
+ Parse *pParse, /* Parsing context */
+ IdList *pFromCol, /* Columns in this table that point to other table */
+ Token *pTo, /* Name of the other table */
+ IdList *pToCol, /* Columns in the other table */
+ int flags /* Conflict resolution algorithms. */
+){
+ Table *p = pParse->pNewTable;
+ int nByte;
+ int i;
+ int nCol;
+ char *z;
+ FKey *pFKey = 0;
+
+ assert( pTo!=0 );
+ if( p==0 || pParse->nErr ) goto fk_end;
+ if( pFromCol==0 ){
+ int iCol = p->nCol-1;
+ if( iCol<0 ) goto fk_end;
+ if( pToCol && pToCol->nId!=1 ){
+ sqliteErrorMsg(pParse, "foreign key on %s"
+ " should reference only one column of table %T",
+ p->aCol[iCol].zName, pTo);
+ goto fk_end;
+ }
+ nCol = 1;
+ }else if( pToCol && pToCol->nId!=pFromCol->nId ){
+ sqliteErrorMsg(pParse,
+ "number of columns in foreign key does not match the number of "
+ "columns in the referenced table");
+ goto fk_end;
+ }else{
+ nCol = pFromCol->nId;
+ }
+ nByte = sizeof(*pFKey) + nCol*sizeof(pFKey->aCol[0]) + pTo->n + 1;
+ if( pToCol ){
+ for(i=0; i<pToCol->nId; i++){
+ nByte += strlen(pToCol->a[i].zName) + 1;
+ }
+ }
+ pFKey = sqliteMalloc( nByte );
+ if( pFKey==0 ) goto fk_end;
+ pFKey->pFrom = p;
+ pFKey->pNextFrom = p->pFKey;
+ z = (char*)&pFKey[1];
+ pFKey->aCol = (struct sColMap*)z;
+ z += sizeof(struct sColMap)*nCol;
+ pFKey->zTo = z;
+ memcpy(z, pTo->z, pTo->n);
+ z[pTo->n] = 0;
+ z += pTo->n+1;
+ pFKey->pNextTo = 0;
+ pFKey->nCol = nCol;
+ if( pFromCol==0 ){
+ pFKey->aCol[0].iFrom = p->nCol-1;
+ }else{
+ for(i=0; i<nCol; i++){
+ int j;
+ for(j=0; j<p->nCol; j++){
+ if( sqliteStrICmp(p->aCol[j].zName, pFromCol->a[i].zName)==0 ){
+ pFKey->aCol[i].iFrom = j;
+ break;
+ }
+ }
+ if( j>=p->nCol ){
+ sqliteErrorMsg(pParse,
+ "unknown column \"%s\" in foreign key definition",
+ pFromCol->a[i].zName);
+ goto fk_end;
+ }
+ }
+ }
+ if( pToCol ){
+ for(i=0; i<nCol; i++){
+ int n = strlen(pToCol->a[i].zName);
+ pFKey->aCol[i].zCol = z;
+ memcpy(z, pToCol->a[i].zName, n);
+ z[n] = 0;
+ z += n+1;
+ }
+ }
+ pFKey->isDeferred = 0;
+ pFKey->deleteConf = flags & 0xff;
+ pFKey->updateConf = (flags >> 8 ) & 0xff;
+ pFKey->insertConf = (flags >> 16 ) & 0xff;
+
+ /* Link the foreign key to the table as the last step.
+ */
+ p->pFKey = pFKey;
+ pFKey = 0;
+
+fk_end:
+ sqliteFree(pFKey);
+ sqliteIdListDelete(pFromCol);
+ sqliteIdListDelete(pToCol);
+}
+
+/*
+** This routine is called when an INITIALLY IMMEDIATE or INITIALLY DEFERRED
+** clause is seen as part of a foreign key definition. The isDeferred
+** parameter is 1 for INITIALLY DEFERRED and 0 for INITIALLY IMMEDIATE.
+** The behavior of the most recently created foreign key is adjusted
+** accordingly.
+*/
+void sqliteDeferForeignKey(Parse *pParse, int isDeferred){
+ Table *pTab;
+ FKey *pFKey;
+ if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return;
+ pFKey->isDeferred = isDeferred;
+}
+
+/*
+** Create a new index for an SQL table. pIndex is the name of the index
+** and pTable is the name of the table that is to be indexed. Both will
+** be NULL for a primary key or an index that is created to satisfy a
+** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable
+** as the table to be indexed. pParse->pNewTable is a table that is
+** currently being constructed by a CREATE TABLE statement.
+**
+** pList is a list of columns to be indexed. pList will be NULL if this
+** is a primary key or unique-constraint on the most recent column added
+** to the table currently under construction.
+*/
+void sqliteCreateIndex(
+ Parse *pParse, /* All information about this parse */
+ Token *pName, /* Name of the index. May be NULL */
+ SrcList *pTable, /* Name of the table to index. Use pParse->pNewTable if 0 */
+ IdList *pList, /* A list of columns to be indexed */
+ int onError, /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
+ Token *pStart, /* The CREATE token that begins a CREATE TABLE statement */
+ Token *pEnd /* The ")" that closes the CREATE INDEX statement */
+){
+ Table *pTab; /* Table to be indexed */
+ Index *pIndex; /* The index to be created */
+ char *zName = 0;
+ int i, j;
+ Token nullId; /* Fake token for an empty ID list */
+ DbFixer sFix; /* For assigning database names to pTable */
+ int isTemp; /* True for a temporary index */
+ sqlite *db = pParse->db;
+
+ if( pParse->nErr || sqlite_malloc_failed ) goto exit_create_index;
+ if( db->init.busy
+ && sqliteFixInit(&sFix, pParse, db->init.iDb, "index", pName)
+ && sqliteFixSrcList(&sFix, pTable)
+ ){
+ goto exit_create_index;
+ }
+
+ /*
+ ** Find the table that is to be indexed. Return early if not found.
+ */
+ if( pTable!=0 ){
+ assert( pName!=0 );
+ assert( pTable->nSrc==1 );
+ pTab = sqliteSrcListLookup(pParse, pTable);
+ }else{
+ assert( pName==0 );
+ pTab = pParse->pNewTable;
+ }
+ if( pTab==0 || pParse->nErr ) goto exit_create_index;
+ if( pTab->readOnly ){
+ sqliteErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
+ goto exit_create_index;
+ }
+ if( pTab->iDb>=2 && db->init.busy==0 ){
+ sqliteErrorMsg(pParse, "table %s may not have indices added", pTab->zName);
+ goto exit_create_index;
+ }
+ if( pTab->pSelect ){
+ sqliteErrorMsg(pParse, "views may not be indexed");
+ goto exit_create_index;
+ }
+ isTemp = pTab->iDb==1;
+
+ /*
+ ** Find the name of the index. Make sure there is not already another
+ ** index or table with the same name.
+ **
+ ** Exception: If we are reading the names of permanent indices from the
+ ** sqlite_master table (because some other process changed the schema) and
+ ** one of the index names collides with the name of a temporary table or
+ ** index, then we will continue to process this index.
+ **
+ ** If pName==0 it means that we are
+ ** dealing with a primary key or UNIQUE constraint. We have to invent our
+ ** own name.
+ */
+ if( pName && !db->init.busy ){
+ Index *pISameName; /* Another index with the same name */
+ Table *pTSameName; /* A table with same name as the index */
+ zName = sqliteTableNameFromToken(pName);
+ if( zName==0 ) goto exit_create_index;
+ if( (pISameName = sqliteFindIndex(db, zName, 0))!=0 ){
+ sqliteErrorMsg(pParse, "index %s already exists", zName);
+ goto exit_create_index;
+ }
+ if( (pTSameName = sqliteFindTable(db, zName, 0))!=0 ){
+ sqliteErrorMsg(pParse, "there is already a table named %s", zName);
+ goto exit_create_index;
+ }
+ }else if( pName==0 ){
+ char zBuf[30];
+ int n;
+ Index *pLoop;
+ for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){}
+ sprintf(zBuf,"%d)",n);
+ zName = 0;
+ sqliteSetString(&zName, "(", pTab->zName, " autoindex ", zBuf, (char*)0);
+ if( zName==0 ) goto exit_create_index;
+ }else{
+ zName = sqliteTableNameFromToken(pName);
+ }
+
+ /* Check for authorization to create an index.
+ */
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ const char *zDb = db->aDb[pTab->iDb].zName;
+
+ assert( pTab->iDb==db->init.iDb || isTemp );
+ if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){
+ goto exit_create_index;
+ }
+ i = SQLITE_CREATE_INDEX;
+ if( isTemp ) i = SQLITE_CREATE_TEMP_INDEX;
+ if( sqliteAuthCheck(pParse, i, zName, pTab->zName, zDb) ){
+ goto exit_create_index;
+ }
+ }
+#endif
+
+ /* If pList==0, it means this routine was called to make a primary
+ ** key out of the last column added to the table under construction.
+ ** So create a fake list to simulate this.
+ */
+ if( pList==0 ){
+ nullId.z = pTab->aCol[pTab->nCol-1].zName;
+ nullId.n = strlen(nullId.z);
+ pList = sqliteIdListAppend(0, &nullId);
+ if( pList==0 ) goto exit_create_index;
+ }
+
+ /*
+ ** Allocate the index structure.
+ */
+ pIndex = sqliteMalloc( sizeof(Index) + strlen(zName) + 1 +
+ sizeof(int)*pList->nId );
+ if( pIndex==0 ) goto exit_create_index;
+ pIndex->aiColumn = (int*)&pIndex[1];
+ pIndex->zName = (char*)&pIndex->aiColumn[pList->nId];
+ strcpy(pIndex->zName, zName);
+ pIndex->pTable = pTab;
+ pIndex->nColumn = pList->nId;
+ pIndex->onError = onError;
+ pIndex->autoIndex = pName==0;
+ pIndex->iDb = isTemp ? 1 : db->init.iDb;
+
+ /* Scan the names of the columns of the table to be indexed and
+ ** load the column indices into the Index structure. Report an error
+ ** if any column is not found.
+ */
+ for(i=0; i<pList->nId; i++){
+ for(j=0; j<pTab->nCol; j++){
+ if( sqliteStrICmp(pList->a[i].zName, pTab->aCol[j].zName)==0 ) break;
+ }
+ if( j>=pTab->nCol ){
+ sqliteErrorMsg(pParse, "table %s has no column named %s",
+ pTab->zName, pList->a[i].zName);
+ sqliteFree(pIndex);
+ goto exit_create_index;
+ }
+ pIndex->aiColumn[i] = j;
+ }
+
+ /* Link the new Index structure to its table and to the other
+ ** in-memory database structures.
+ */
+ if( !pParse->explain ){
+ Index *p;
+ p = sqliteHashInsert(&db->aDb[pIndex->iDb].idxHash,
+ pIndex->zName, strlen(pIndex->zName)+1, pIndex);
+ if( p ){
+ assert( p==pIndex ); /* Malloc must have failed */
+ sqliteFree(pIndex);
+ goto exit_create_index;
+ }
+ db->flags |= SQLITE_InternChanges;
+ }
+
+ /* When adding an index to the list of indices for a table, make
+ ** sure all indices labeled OE_Replace come after all those labeled
+ ** OE_Ignore. This is necessary for the correct operation of UPDATE
+ ** and INSERT.
+ */
+ if( onError!=OE_Replace || pTab->pIndex==0
+ || pTab->pIndex->onError==OE_Replace){
+ pIndex->pNext = pTab->pIndex;
+ pTab->pIndex = pIndex;
+ }else{
+ Index *pOther = pTab->pIndex;
+ while( pOther->pNext && pOther->pNext->onError!=OE_Replace ){
+ pOther = pOther->pNext;
+ }
+ pIndex->pNext = pOther->pNext;
+ pOther->pNext = pIndex;
+ }
+
+ /* If the db->init.busy is 1 it means we are reading the SQL off the
+ ** "sqlite_master" table on the disk. So do not write to the disk
+ ** again. Extract the table number from the db->init.newTnum field.
+ */
+ if( db->init.busy && pTable!=0 ){
+ pIndex->tnum = db->init.newTnum;
+ }
+
+ /* If the db->init.busy is 0 then create the index on disk. This
+ ** involves writing the index into the master table and filling in the
+ ** index with the current table contents.
+ **
+ ** The db->init.busy is 0 when the user first enters a CREATE INDEX
+ ** command. db->init.busy is 1 when a database is opened and
+ ** CREATE INDEX statements are read out of the master table. In
+ ** the latter case the index already exists on disk, which is why
+ ** we don't want to recreate it.
+ **
+ ** If pTable==0 it means this index is generated as a primary key
+ ** or UNIQUE constraint of a CREATE TABLE statement. Since the table
+ ** has just been created, it contains no data and the index initialization
+ ** step can be skipped.
+ */
+ else if( db->init.busy==0 ){
+ int n;
+ Vdbe *v;
+ int lbl1, lbl2;
+ int i;
+ int addr;
+
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) goto exit_create_index;
+ if( pTable!=0 ){
+ sqliteBeginWriteOperation(pParse, 0, isTemp);
+ sqliteOpenMasterTable(v, isTemp);
+ }
+ sqliteVdbeAddOp(v, OP_NewRecno, 0, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, "index", P3_STATIC);
+ sqliteVdbeOp3(v, OP_String, 0, 0, pIndex->zName, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, pTab->zName, 0);
+ sqliteVdbeOp3(v, OP_CreateIndex, 0, isTemp,(char*)&pIndex->tnum,P3_POINTER);
+ pIndex->tnum = 0;
+ if( pTable ){
+ sqliteVdbeCode(v,
+ OP_Dup, 0, 0,
+ OP_Integer, isTemp, 0,
+ OP_OpenWrite, 1, 0,
+ 0);
+ }
+ addr = sqliteVdbeAddOp(v, OP_String, 0, 0);
+ if( pStart && pEnd ){
+ n = Addr(pEnd->z) - Addr(pStart->z) + 1;
+ sqliteVdbeChangeP3(v, addr, pStart->z, n);
+ }
+ sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0);
+ if( pTable ){
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenRead, 2, pTab->tnum, pTab->zName, 0);
+ lbl2 = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_Rewind, 2, lbl2);
+ lbl1 = sqliteVdbeAddOp(v, OP_Recno, 2, 0);
+ for(i=0; i<pIndex->nColumn; i++){
+ int iCol = pIndex->aiColumn[i];
+ if( pTab->iPKey==iCol ){
+ sqliteVdbeAddOp(v, OP_Dup, i, 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_Column, 2, iCol);
+ }
+ }
+ sqliteVdbeAddOp(v, OP_MakeIdxKey, pIndex->nColumn, 0);
+ if( db->file_format>=4 ) sqliteAddIdxKeyType(v, pIndex);
+ sqliteVdbeOp3(v, OP_IdxPut, 1, pIndex->onError!=OE_None,
+ "indexed columns are not unique", P3_STATIC);
+ sqliteVdbeAddOp(v, OP_Next, 2, lbl1);
+ sqliteVdbeResolveLabel(v, lbl2);
+ sqliteVdbeAddOp(v, OP_Close, 2, 0);
+ sqliteVdbeAddOp(v, OP_Close, 1, 0);
+ }
+ if( pTable!=0 ){
+ if( !isTemp ){
+ sqliteChangeCookie(db, v);
+ }
+ sqliteVdbeAddOp(v, OP_Close, 0, 0);
+ sqliteEndWriteOperation(pParse);
+ }
+ }
+
+ /* Clean up before exiting */
+exit_create_index:
+ sqliteIdListDelete(pList);
+ sqliteSrcListDelete(pTable);
+ sqliteFree(zName);
+ return;
+}
+
+/*
+** This routine will drop an existing named index. This routine
+** implements the DROP INDEX statement.
+*/
+void sqliteDropIndex(Parse *pParse, SrcList *pName){
+ Index *pIndex;
+ Vdbe *v;
+ sqlite *db = pParse->db;
+
+ if( pParse->nErr || sqlite_malloc_failed ) return;
+ assert( pName->nSrc==1 );
+ pIndex = sqliteFindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
+ if( pIndex==0 ){
+ sqliteErrorMsg(pParse, "no such index: %S", pName, 0);
+ goto exit_drop_index;
+ }
+ if( pIndex->autoIndex ){
+ sqliteErrorMsg(pParse, "index associated with UNIQUE "
+ "or PRIMARY KEY constraint cannot be dropped", 0);
+ goto exit_drop_index;
+ }
+ if( pIndex->iDb>1 ){
+ sqliteErrorMsg(pParse, "cannot alter schema of attached "
+ "databases", 0);
+ goto exit_drop_index;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code = SQLITE_DROP_INDEX;
+ Table *pTab = pIndex->pTable;
+ const char *zDb = db->aDb[pIndex->iDb].zName;
+ const char *zTab = SCHEMA_TABLE(pIndex->iDb);
+ if( sqliteAuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
+ goto exit_drop_index;
+ }
+ if( pIndex->iDb ) code = SQLITE_DROP_TEMP_INDEX;
+ if( sqliteAuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){
+ goto exit_drop_index;
+ }
+ }
+#endif
+
+ /* Generate code to remove the index and from the master table */
+ v = sqliteGetVdbe(pParse);
+ if( v ){
+ static VdbeOpList dropIndex[] = {
+ { OP_Rewind, 0, ADDR(9), 0},
+ { OP_String, 0, 0, 0}, /* 1 */
+ { OP_MemStore, 1, 1, 0},
+ { OP_MemLoad, 1, 0, 0}, /* 3 */
+ { OP_Column, 0, 1, 0},
+ { OP_Eq, 0, ADDR(8), 0},
+ { OP_Next, 0, ADDR(3), 0},
+ { OP_Goto, 0, ADDR(9), 0},
+ { OP_Delete, 0, 0, 0}, /* 8 */
+ };
+ int base;
+
+ sqliteBeginWriteOperation(pParse, 0, pIndex->iDb);
+ sqliteOpenMasterTable(v, pIndex->iDb);
+ base = sqliteVdbeAddOpList(v, ArraySize(dropIndex), dropIndex);
+ sqliteVdbeChangeP3(v, base+1, pIndex->zName, 0);
+ if( pIndex->iDb==0 ){
+ sqliteChangeCookie(db, v);
+ }
+ sqliteVdbeAddOp(v, OP_Close, 0, 0);
+ sqliteVdbeAddOp(v, OP_Destroy, pIndex->tnum, pIndex->iDb);
+ sqliteEndWriteOperation(pParse);
+ }
+
+ /* Delete the in-memory description of this index.
+ */
+ if( !pParse->explain ){
+ sqliteUnlinkAndDeleteIndex(db, pIndex);
+ db->flags |= SQLITE_InternChanges;
+ }
+
+exit_drop_index:
+ sqliteSrcListDelete(pName);
+}
+
+/*
+** Append a new element to the given IdList. Create a new IdList if
+** need be.
+**
+** A new IdList is returned, or NULL if malloc() fails.
+*/
+IdList *sqliteIdListAppend(IdList *pList, Token *pToken){
+ if( pList==0 ){
+ pList = sqliteMalloc( sizeof(IdList) );
+ if( pList==0 ) return 0;
+ pList->nAlloc = 0;
+ }
+ if( pList->nId>=pList->nAlloc ){
+ struct IdList_item *a;
+ pList->nAlloc = pList->nAlloc*2 + 5;
+ a = sqliteRealloc(pList->a, pList->nAlloc*sizeof(pList->a[0]) );
+ if( a==0 ){
+ sqliteIdListDelete(pList);
+ return 0;
+ }
+ pList->a = a;
+ }
+ memset(&pList->a[pList->nId], 0, sizeof(pList->a[0]));
+ if( pToken ){
+ char **pz = &pList->a[pList->nId].zName;
+ sqliteSetNString(pz, pToken->z, pToken->n, 0);
+ if( *pz==0 ){
+ sqliteIdListDelete(pList);
+ return 0;
+ }else{
+ sqliteDequote(*pz);
+ }
+ }
+ pList->nId++;
+ return pList;
+}
+
+/*
+** Append a new table name to the given SrcList. Create a new SrcList if
+** need be. A new entry is created in the SrcList even if pToken is NULL.
+**
+** A new SrcList is returned, or NULL if malloc() fails.
+**
+** If pDatabase is not null, it means that the table has an optional
+** database name prefix. Like this: "database.table". The pDatabase
+** points to the table name and the pTable points to the database name.
+** The SrcList.a[].zName field is filled with the table name which might
+** come from pTable (if pDatabase is NULL) or from pDatabase.
+** SrcList.a[].zDatabase is filled with the database name from pTable,
+** or with NULL if no database is specified.
+**
+** In other words, if call like this:
+**
+** sqliteSrcListAppend(A,B,0);
+**
+** Then B is a table name and the database name is unspecified. If called
+** like this:
+**
+** sqliteSrcListAppend(A,B,C);
+**
+** Then C is the table name and B is the database name.
+*/
+SrcList *sqliteSrcListAppend(SrcList *pList, Token *pTable, Token *pDatabase){
+ if( pList==0 ){
+ pList = sqliteMalloc( sizeof(SrcList) );
+ if( pList==0 ) return 0;
+ pList->nAlloc = 1;
+ }
+ if( pList->nSrc>=pList->nAlloc ){
+ SrcList *pNew;
+ pList->nAlloc *= 2;
+ pNew = sqliteRealloc(pList,
+ sizeof(*pList) + (pList->nAlloc-1)*sizeof(pList->a[0]) );
+ if( pNew==0 ){
+ sqliteSrcListDelete(pList);
+ return 0;
+ }
+ pList = pNew;
+ }
+ memset(&pList->a[pList->nSrc], 0, sizeof(pList->a[0]));
+ if( pDatabase && pDatabase->z==0 ){
+ pDatabase = 0;
+ }
+ if( pDatabase && pTable ){
+ Token *pTemp = pDatabase;
+ pDatabase = pTable;
+ pTable = pTemp;
+ }
+ if( pTable ){
+ char **pz = &pList->a[pList->nSrc].zName;
+ sqliteSetNString(pz, pTable->z, pTable->n, 0);
+ if( *pz==0 ){
+ sqliteSrcListDelete(pList);
+ return 0;
+ }else{
+ sqliteDequote(*pz);
+ }
+ }
+ if( pDatabase ){
+ char **pz = &pList->a[pList->nSrc].zDatabase;
+ sqliteSetNString(pz, pDatabase->z, pDatabase->n, 0);
+ if( *pz==0 ){
+ sqliteSrcListDelete(pList);
+ return 0;
+ }else{
+ sqliteDequote(*pz);
+ }
+ }
+ pList->a[pList->nSrc].iCursor = -1;
+ pList->nSrc++;
+ return pList;
+}
+
+/*
+** Assign cursors to all tables in a SrcList
+*/
+void sqliteSrcListAssignCursors(Parse *pParse, SrcList *pList){
+ int i;
+ for(i=0; i<pList->nSrc; i++){
+ if( pList->a[i].iCursor<0 ){
+ pList->a[i].iCursor = pParse->nTab++;
+ }
+ }
+}
+
+/*
+** Add an alias to the last identifier on the given identifier list.
+*/
+void sqliteSrcListAddAlias(SrcList *pList, Token *pToken){
+ if( pList && pList->nSrc>0 ){
+ int i = pList->nSrc - 1;
+ sqliteSetNString(&pList->a[i].zAlias, pToken->z, pToken->n, 0);
+ sqliteDequote(pList->a[i].zAlias);
+ }
+}
+
+/*
+** Delete an IdList.
+*/
+void sqliteIdListDelete(IdList *pList){
+ int i;
+ if( pList==0 ) return;
+ for(i=0; i<pList->nId; i++){
+ sqliteFree(pList->a[i].zName);
+ }
+ sqliteFree(pList->a);
+ sqliteFree(pList);
+}
+
+/*
+** Return the index in pList of the identifier named zId. Return -1
+** if not found.
+*/
+int sqliteIdListIndex(IdList *pList, const char *zName){
+ int i;
+ if( pList==0 ) return -1;
+ for(i=0; i<pList->nId; i++){
+ if( sqliteStrICmp(pList->a[i].zName, zName)==0 ) return i;
+ }
+ return -1;
+}
+
+/*
+** Delete an entire SrcList including all its substructure.
+*/
+void sqliteSrcListDelete(SrcList *pList){
+ int i;
+ if( pList==0 ) return;
+ for(i=0; i<pList->nSrc; i++){
+ sqliteFree(pList->a[i].zDatabase);
+ sqliteFree(pList->a[i].zName);
+ sqliteFree(pList->a[i].zAlias);
+ if( pList->a[i].pTab && pList->a[i].pTab->isTransient ){
+ sqliteDeleteTable(0, pList->a[i].pTab);
+ }
+ sqliteSelectDelete(pList->a[i].pSelect);
+ sqliteExprDelete(pList->a[i].pOn);
+ sqliteIdListDelete(pList->a[i].pUsing);
+ }
+ sqliteFree(pList);
+}
+
+/*
+** Begin a transaction
+*/
+void sqliteBeginTransaction(Parse *pParse, int onError){
+ sqlite *db;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || sqlite_malloc_failed ) return;
+ if( sqliteAuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ) return;
+ if( db->flags & SQLITE_InTrans ){
+ sqliteErrorMsg(pParse, "cannot start a transaction within a transaction");
+ return;
+ }
+ sqliteBeginWriteOperation(pParse, 0, 0);
+ if( !pParse->explain ){
+ db->flags |= SQLITE_InTrans;
+ db->onError = onError;
+ }
+}
+
+/*
+** Commit a transaction
+*/
+void sqliteCommitTransaction(Parse *pParse){
+ sqlite *db;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || sqlite_malloc_failed ) return;
+ if( sqliteAuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ) return;
+ if( (db->flags & SQLITE_InTrans)==0 ){
+ sqliteErrorMsg(pParse, "cannot commit - no transaction is active");
+ return;
+ }
+ if( !pParse->explain ){
+ db->flags &= ~SQLITE_InTrans;
+ }
+ sqliteEndWriteOperation(pParse);
+ if( !pParse->explain ){
+ db->onError = OE_Default;
+ }
+}
+
+/*
+** Rollback a transaction
+*/
+void sqliteRollbackTransaction(Parse *pParse){
+ sqlite *db;
+ Vdbe *v;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || sqlite_malloc_failed ) return;
+ if( sqliteAuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ) return;
+ if( (db->flags & SQLITE_InTrans)==0 ){
+ sqliteErrorMsg(pParse, "cannot rollback - no transaction is active");
+ return;
+ }
+ v = sqliteGetVdbe(pParse);
+ if( v ){
+ sqliteVdbeAddOp(v, OP_Rollback, 0, 0);
+ }
+ if( !pParse->explain ){
+ db->flags &= ~SQLITE_InTrans;
+ db->onError = OE_Default;
+ }
+}
+
+/*
+** Generate VDBE code that will verify the schema cookie for all
+** named database files.
+*/
+void sqliteCodeVerifySchema(Parse *pParse, int iDb){
+ sqlite *db = pParse->db;
+ Vdbe *v = sqliteGetVdbe(pParse);
+ assert( iDb>=0 && iDb<db->nDb );
+ assert( db->aDb[iDb].pBt!=0 );
+ if( iDb!=1 && !DbHasProperty(db, iDb, DB_Cookie) ){
+ sqliteVdbeAddOp(v, OP_VerifyCookie, iDb, db->aDb[iDb].schema_cookie);
+ DbSetProperty(db, iDb, DB_Cookie);
+ }
+}
+
+/*
+** Generate VDBE code that prepares for doing an operation that
+** might change the database.
+**
+** This routine starts a new transaction if we are not already within
+** a transaction. If we are already within a transaction, then a checkpoint
+** is set if the setCheckpoint parameter is true. A checkpoint should
+** be set for operations that might fail (due to a constraint) part of
+** the way through and which will need to undo some writes without having to
+** rollback the whole transaction. For operations where all constraints
+** can be checked before any changes are made to the database, it is never
+** necessary to undo a write and the checkpoint should not be set.
+**
+** Only database iDb and the temp database are made writable by this call.
+** If iDb==0, then the main and temp databases are made writable. If
+** iDb==1 then only the temp database is made writable. If iDb>1 then the
+** specified auxiliary database and the temp database are made writable.
+*/
+void sqliteBeginWriteOperation(Parse *pParse, int setCheckpoint, int iDb){
+ Vdbe *v;
+ sqlite *db = pParse->db;
+ if( DbHasProperty(db, iDb, DB_Locked) ) return;
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) return;
+ if( !db->aDb[iDb].inTrans ){
+ sqliteVdbeAddOp(v, OP_Transaction, iDb, 0);
+ DbSetProperty(db, iDb, DB_Locked);
+ sqliteCodeVerifySchema(pParse, iDb);
+ if( iDb!=1 ){
+ sqliteBeginWriteOperation(pParse, setCheckpoint, 1);
+ }
+ }else if( setCheckpoint ){
+ sqliteVdbeAddOp(v, OP_Checkpoint, iDb, 0);
+ DbSetProperty(db, iDb, DB_Locked);
+ }
+}
+
+/*
+** Generate code that concludes an operation that may have changed
+** the database. If a statement transaction was started, then emit
+** an OP_Commit that will cause the changes to be committed to disk.
+**
+** Note that checkpoints are automatically committed at the end of
+** a statement. Note also that there can be multiple calls to
+** sqliteBeginWriteOperation() but there should only be a single
+** call to sqliteEndWriteOperation() at the conclusion of the statement.
+*/
+void sqliteEndWriteOperation(Parse *pParse){
+ Vdbe *v;
+ sqlite *db = pParse->db;
+ if( pParse->trigStack ) return; /* if this is in a trigger */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) return;
+ if( db->flags & SQLITE_InTrans ){
+ /* A BEGIN has executed. Do not commit until we see an explicit
+ ** COMMIT statement. */
+ }else{
+ sqliteVdbeAddOp(v, OP_Commit, 0, 0);
+ }
+}
diff --git a/src/libs/sqlite2/copy.c b/src/libs/sqlite2/copy.c
new file mode 100644
index 00000000..a289a7be
--- /dev/null
+++ b/src/libs/sqlite2/copy.c
@@ -0,0 +1,110 @@
+/*
+** 2003 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the COPY command.
+**
+** $Id: copy.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+
+/*
+** The COPY command is for compatibility with PostgreSQL and specificially
+** for the ability to read the output of pg_dump. The format is as
+** follows:
+**
+** COPY table FROM file [USING DELIMITERS string]
+**
+** "table" is an existing table name. We will read lines of code from
+** file to fill this table with data. File might be "stdin". The optional
+** delimiter string identifies the field separators. The default is a tab.
+*/
+void sqliteCopy(
+ Parse *pParse, /* The parser context */
+ SrcList *pTableName, /* The name of the table into which we will insert */
+ Token *pFilename, /* The file from which to obtain information */
+ Token *pDelimiter, /* Use this as the field delimiter */
+ int onError /* What to do if a constraint fails */
+){
+ Table *pTab;
+ int i;
+ Vdbe *v;
+ int addr, end;
+ char *zFile = 0;
+ const char *zDb;
+ sqlite *db = pParse->db;
+
+
+ if( sqlite_malloc_failed ) goto copy_cleanup;
+ assert( pTableName->nSrc==1 );
+ pTab = sqliteSrcListLookup(pParse, pTableName);
+ if( pTab==0 || sqliteIsReadOnly(pParse, pTab, 0) ) goto copy_cleanup;
+ zFile = sqliteStrNDup(pFilename->z, pFilename->n);
+ sqliteDequote(zFile);
+ assert( pTab->iDb<db->nDb );
+ zDb = db->aDb[pTab->iDb].zName;
+ if( sqliteAuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb)
+ || sqliteAuthCheck(pParse, SQLITE_COPY, pTab->zName, zFile, zDb) ){
+ goto copy_cleanup;
+ }
+ v = sqliteGetVdbe(pParse);
+ if( v ){
+ sqliteBeginWriteOperation(pParse, 1, pTab->iDb);
+ addr = sqliteVdbeOp3(v, OP_FileOpen, 0, 0, pFilename->z, pFilename->n);
+ sqliteVdbeDequoteP3(v, addr);
+ sqliteOpenTableAndIndices(pParse, pTab, 0);
+ if( db->flags & SQLITE_CountRows ){
+ sqliteVdbeAddOp(v, OP_Integer, 0, 0); /* Initialize the row count */
+ }
+ end = sqliteVdbeMakeLabel(v);
+ addr = sqliteVdbeAddOp(v, OP_FileRead, pTab->nCol, end);
+ if( pDelimiter ){
+ sqliteVdbeChangeP3(v, addr, pDelimiter->z, pDelimiter->n);
+ sqliteVdbeDequoteP3(v, addr);
+ }else{
+ sqliteVdbeChangeP3(v, addr, "\t", 1);
+ }
+ if( pTab->iPKey>=0 ){
+ sqliteVdbeAddOp(v, OP_FileColumn, pTab->iPKey, 0);
+ sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_NewRecno, 0, 0);
+ }
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ /* The integer primary key column is filled with NULL since its
+ ** value is always pulled from the record number */
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_FileColumn, i, 0);
+ }
+ }
+ sqliteGenerateConstraintChecks(pParse, pTab, 0, 0, pTab->iPKey>=0,
+ 0, onError, addr);
+ sqliteCompleteInsertion(pParse, pTab, 0, 0, 0, 0, -1);
+ if( (db->flags & SQLITE_CountRows)!=0 ){
+ sqliteVdbeAddOp(v, OP_AddImm, 1, 0); /* Increment row count */
+ }
+ sqliteVdbeAddOp(v, OP_Goto, 0, addr);
+ sqliteVdbeResolveLabel(v, end);
+ sqliteVdbeAddOp(v, OP_Noop, 0, 0);
+ sqliteEndWriteOperation(pParse);
+ if( db->flags & SQLITE_CountRows ){
+ sqliteVdbeAddOp(v, OP_ColumnName, 0, 1);
+ sqliteVdbeChangeP3(v, -1, "rows inserted", P3_STATIC);
+ sqliteVdbeAddOp(v, OP_Callback, 1, 0);
+ }
+ }
+
+copy_cleanup:
+ sqliteSrcListDelete(pTableName);
+ sqliteFree(zFile);
+ return;
+}
diff --git a/src/libs/sqlite2/date.c b/src/libs/sqlite2/date.c
new file mode 100644
index 00000000..9198b26f
--- /dev/null
+++ b/src/libs/sqlite2/date.c
@@ -0,0 +1,875 @@
+/*
+** 2003 October 31
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement date and time
+** functions for SQLite.
+**
+** There is only one exported symbol in this file - the function
+** sqliteRegisterDateTimeFunctions() found at the bottom of the file.
+** All other code has file scope.
+**
+** $Id: date.c 875429 2008-10-24 12:20:41Z cgilles $
+**
+** NOTES:
+**
+** SQLite processes all times and dates as Julian Day numbers. The
+** dates and times are stored as the number of days since noon
+** in Greenwich on November 24, 4714 B.C. according to the Gregorian
+** calendar system.
+**
+** 1970-01-01 00:00:00 is JD 2440587.5
+** 2000-01-01 00:00:00 is JD 2451544.5
+**
+** This implemention requires years to be expressed as a 4-digit number
+** which means that only dates between 0000-01-01 and 9999-12-31 can
+** be represented, even though julian day numbers allow a much wider
+** range of dates.
+**
+** The Gregorian calendar system is used for all dates and times,
+** even those that predate the Gregorian calendar. Historians usually
+** use the Julian calendar for dates prior to 1582-10-15 and for some
+** dates afterwards, depending on locale. Beware of this difference.
+**
+** The conversion algorithms are implemented based on descriptions
+** in the following text:
+**
+** Jean Meeus
+** Astronomical Algorithms, 2nd Edition, 1998
+** ISBM 0-943396-61-1
+** Willmann-Bell, Inc
+** Richmond, Virginia (USA)
+*/
+#include "os.h"
+#include "sqliteInt.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <time.h>
+
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
+
+/*
+** A structure for holding a single date and time.
+*/
+typedef struct DateTime DateTime;
+struct DateTime {
+ double rJD; /* The julian day number */
+ int Y, M, D; /* Year, month, and day */
+ int h, m; /* Hour and minutes */
+ int tz; /* Timezone offset in minutes */
+ double s; /* Seconds */
+ char validYMD; /* True if Y,M,D are valid */
+ char validHMS; /* True if h,m,s are valid */
+ char validJD; /* True if rJD is valid */
+ char validTZ; /* True if tz is valid */
+};
+
+
+/*
+** Convert zDate into one or more integers. Additional arguments
+** come in groups of 5 as follows:
+**
+** N number of digits in the integer
+** min minimum allowed value of the integer
+** max maximum allowed value of the integer
+** nextC first character after the integer
+** pVal where to write the integers value.
+**
+** Conversions continue until one with nextC==0 is encountered.
+** The function returns the number of successful conversions.
+*/
+static int getDigits(const char *zDate, ...){
+ va_list ap;
+ int val;
+ int N;
+ int min;
+ int max;
+ int nextC;
+ int *pVal;
+ int cnt = 0;
+ va_start(ap, zDate);
+ do{
+ N = va_arg(ap, int);
+ min = va_arg(ap, int);
+ max = va_arg(ap, int);
+ nextC = va_arg(ap, int);
+ pVal = va_arg(ap, int*);
+ val = 0;
+ while( N-- ){
+ if( !isdigit(*zDate) ){
+ return cnt;
+ }
+ val = val*10 + *zDate - '0';
+ zDate++;
+ }
+ if( val<min || val>max || (nextC!=0 && nextC!=*zDate) ){
+ return cnt;
+ }
+ *pVal = val;
+ zDate++;
+ cnt++;
+ }while( nextC );
+ return cnt;
+}
+
+/*
+** Read text from z[] and convert into a floating point number. Return
+** the number of digits converted.
+*/
+static int getValue(const char *z, double *pR){
+ const char *zEnd;
+ *pR = sqliteAtoF(z, &zEnd);
+ return zEnd - z;
+}
+
+/*
+** Parse a timezone extension on the end of a date-time.
+** The extension is of the form:
+**
+** (+/-)HH:MM
+**
+** If the parse is successful, write the number of minutes
+** of change in *pnMin and return 0. If a parser error occurs,
+** return 0.
+**
+** A missing specifier is not considered an error.
+*/
+static int parseTimezone(const char *zDate, DateTime *p){
+ int sgn = 0;
+ int nHr, nMn;
+ while( isspace(*zDate) ){ zDate++; }
+ p->tz = 0;
+ if( *zDate=='-' ){
+ sgn = -1;
+ }else if( *zDate=='+' ){
+ sgn = +1;
+ }else{
+ return *zDate!=0;
+ }
+ zDate++;
+ if( getDigits(zDate, 2, 0, 14, ':', &nHr, 2, 0, 59, 0, &nMn)!=2 ){
+ return 1;
+ }
+ zDate += 5;
+ p->tz = sgn*(nMn + nHr*60);
+ while( isspace(*zDate) ){ zDate++; }
+ return *zDate!=0;
+}
+
+/*
+** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF.
+** The HH, MM, and SS must each be exactly 2 digits. The
+** fractional seconds FFFF can be one or more digits.
+**
+** Return 1 if there is a parsing error and 0 on success.
+*/
+static int parseHhMmSs(const char *zDate, DateTime *p){
+ int h, m, s;
+ double ms = 0.0;
+ if( getDigits(zDate, 2, 0, 24, ':', &h, 2, 0, 59, 0, &m)!=2 ){
+ return 1;
+ }
+ zDate += 5;
+ if( *zDate==':' ){
+ zDate++;
+ if( getDigits(zDate, 2, 0, 59, 0, &s)!=1 ){
+ return 1;
+ }
+ zDate += 2;
+ if( *zDate=='.' && isdigit(zDate[1]) ){
+ double rScale = 1.0;
+ zDate++;
+ while( isdigit(*zDate) ){
+ ms = ms*10.0 + *zDate - '0';
+ rScale *= 10.0;
+ zDate++;
+ }
+ ms /= rScale;
+ }
+ }else{
+ s = 0;
+ }
+ p->validJD = 0;
+ p->validHMS = 1;
+ p->h = h;
+ p->m = m;
+ p->s = s + ms;
+ if( parseTimezone(zDate, p) ) return 1;
+ p->validTZ = p->tz!=0;
+ return 0;
+}
+
+/*
+** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume
+** that the YYYY-MM-DD is according to the Gregorian calendar.
+**
+** Reference: Meeus page 61
+*/
+static void computeJD(DateTime *p){
+ int Y, M, D, A, B, X1, X2;
+
+ if( p->validJD ) return;
+ if( p->validYMD ){
+ Y = p->Y;
+ M = p->M;
+ D = p->D;
+ }else{
+ Y = 2000; /* If no YMD specified, assume 2000-Jan-01 */
+ M = 1;
+ D = 1;
+ }
+ if( M<=2 ){
+ Y--;
+ M += 12;
+ }
+ A = Y/100;
+ B = 2 - A + (A/4);
+ X1 = 365.25*(Y+4716);
+ X2 = 30.6001*(M+1);
+ p->rJD = X1 + X2 + D + B - 1524.5;
+ p->validJD = 1;
+ p->validYMD = 0;
+ if( p->validHMS ){
+ p->rJD += (p->h*3600.0 + p->m*60.0 + p->s)/86400.0;
+ if( p->validTZ ){
+ p->rJD += p->tz*60/86400.0;
+ p->validHMS = 0;
+ p->validTZ = 0;
+ }
+ }
+}
+
+/*
+** Parse dates of the form
+**
+** YYYY-MM-DD HH:MM:SS.FFF
+** YYYY-MM-DD HH:MM:SS
+** YYYY-MM-DD HH:MM
+** YYYY-MM-DD
+**
+** Write the result into the DateTime structure and return 0
+** on success and 1 if the input string is not a well-formed
+** date.
+*/
+static int parseYyyyMmDd(const char *zDate, DateTime *p){
+ int Y, M, D, neg;
+
+ if( zDate[0]=='-' ){
+ zDate++;
+ neg = 1;
+ }else{
+ neg = 0;
+ }
+ if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 ){
+ return 1;
+ }
+ zDate += 10;
+ while( isspace(*zDate) ){ zDate++; }
+ if( parseHhMmSs(zDate, p)==0 ){
+ /* We got the time */
+ }else if( *zDate==0 ){
+ p->validHMS = 0;
+ }else{
+ return 1;
+ }
+ p->validJD = 0;
+ p->validYMD = 1;
+ p->Y = neg ? -Y : Y;
+ p->M = M;
+ p->D = D;
+ if( p->validTZ ){
+ computeJD(p);
+ }
+ return 0;
+}
+
+/*
+** Attempt to parse the given string into a Julian Day Number. Return
+** the number of errors.
+**
+** The following are acceptable forms for the input string:
+**
+** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM
+** DDDD.DD
+** now
+**
+** In the first form, the +/-HH:MM is always optional. The fractional
+** seconds extension (the ".FFF") is optional. The seconds portion
+** (":SS.FFF") is option. The year and date can be omitted as long
+** as there is a time string. The time string can be omitted as long
+** as there is a year and date.
+*/
+static int parseDateOrTime(const char *zDate, DateTime *p){
+ memset(p, 0, sizeof(*p));
+ if( parseYyyyMmDd(zDate,p)==0 ){
+ return 0;
+ }else if( parseHhMmSs(zDate, p)==0 ){
+ return 0;
+ }else if( sqliteStrICmp(zDate,"now")==0){
+ double r;
+ if( sqliteOsCurrentTime(&r)==0 ){
+ p->rJD = r;
+ p->validJD = 1;
+ return 0;
+ }
+ return 1;
+ }else if( sqliteIsNumber(zDate) ){
+ p->rJD = sqliteAtoF(zDate, 0);
+ p->validJD = 1;
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** Compute the Year, Month, and Day from the julian day number.
+*/
+static void computeYMD(DateTime *p){
+ int Z, A, B, C, D, E, X1;
+ if( p->validYMD ) return;
+ if( !p->validJD ){
+ p->Y = 2000;
+ p->M = 1;
+ p->D = 1;
+ }else{
+ Z = p->rJD + 0.5;
+ A = (Z - 1867216.25)/36524.25;
+ A = Z + 1 + A - (A/4);
+ B = A + 1524;
+ C = (B - 122.1)/365.25;
+ D = 365.25*C;
+ E = (B-D)/30.6001;
+ X1 = 30.6001*E;
+ p->D = B - D - X1;
+ p->M = E<14 ? E-1 : E-13;
+ p->Y = p->M>2 ? C - 4716 : C - 4715;
+ }
+ p->validYMD = 1;
+}
+
+/*
+** Compute the Hour, Minute, and Seconds from the julian day number.
+*/
+static void computeHMS(DateTime *p){
+ int Z, s;
+ if( p->validHMS ) return;
+ Z = p->rJD + 0.5;
+ s = (p->rJD + 0.5 - Z)*86400000.0 + 0.5;
+ p->s = 0.001*s;
+ s = p->s;
+ p->s -= s;
+ p->h = s/3600;
+ s -= p->h*3600;
+ p->m = s/60;
+ p->s += s - p->m*60;
+ p->validHMS = 1;
+}
+
+/*
+** Compute both YMD and HMS
+*/
+static void computeYMD_HMS(DateTime *p){
+ computeYMD(p);
+ computeHMS(p);
+}
+
+/*
+** Clear the YMD and HMS and the TZ
+*/
+static void clearYMD_HMS_TZ(DateTime *p){
+ p->validYMD = 0;
+ p->validHMS = 0;
+ p->validTZ = 0;
+}
+
+/*
+** Compute the difference (in days) between localtime and UTC (a.k.a. GMT)
+** for the time value p where p is in UTC.
+*/
+static double localtimeOffset(DateTime *p){
+ DateTime x, y;
+ time_t t;
+ struct tm *pTm;
+ x = *p;
+ computeYMD_HMS(&x);
+ if( x.Y<1971 || x.Y>=2038 ){
+ x.Y = 2000;
+ x.M = 1;
+ x.D = 1;
+ x.h = 0;
+ x.m = 0;
+ x.s = 0.0;
+ } else {
+ int s = x.s + 0.5;
+ x.s = s;
+ }
+ x.tz = 0;
+ x.validJD = 0;
+ computeJD(&x);
+ t = (x.rJD-2440587.5)*86400.0 + 0.5;
+ sqliteOsEnterMutex();
+ pTm = localtime(&t);
+ y.Y = pTm->tm_year + 1900;
+ y.M = pTm->tm_mon + 1;
+ y.D = pTm->tm_mday;
+ y.h = pTm->tm_hour;
+ y.m = pTm->tm_min;
+ y.s = pTm->tm_sec;
+ sqliteOsLeaveMutex();
+ y.validYMD = 1;
+ y.validHMS = 1;
+ y.validJD = 0;
+ y.validTZ = 0;
+ computeJD(&y);
+ return y.rJD - x.rJD;
+}
+
+/*
+** Process a modifier to a date-time stamp. The modifiers are
+** as follows:
+**
+** NNN days
+** NNN hours
+** NNN minutes
+** NNN.NNNN seconds
+** NNN months
+** NNN years
+** start of month
+** start of year
+** start of week
+** start of day
+** weekday N
+** unixepoch
+** localtime
+** utc
+**
+** Return 0 on success and 1 if there is any kind of error.
+*/
+static int parseModifier(const char *zMod, DateTime *p){
+ int rc = 1;
+ int n;
+ double r;
+ char *z, zBuf[30];
+ z = zBuf;
+ for(n=0; n<sizeof(zBuf)-1 && zMod[n]; n++){
+ z[n] = tolower(zMod[n]);
+ }
+ z[n] = 0;
+ switch( z[0] ){
+ case 'l': {
+ /* localtime
+ **
+ ** Assuming the current time value is UTC (a.k.a. GMT), shift it to
+ ** show local time.
+ */
+ if( strcmp(z, "localtime")==0 ){
+ computeJD(p);
+ p->rJD += localtimeOffset(p);
+ clearYMD_HMS_TZ(p);
+ rc = 0;
+ }
+ break;
+ }
+ case 'u': {
+ /*
+ ** unixepoch
+ **
+ ** Treat the current value of p->rJD as the number of
+ ** seconds since 1970. Convert to a real julian day number.
+ */
+ if( strcmp(z, "unixepoch")==0 && p->validJD ){
+ p->rJD = p->rJD/86400.0 + 2440587.5;
+ clearYMD_HMS_TZ(p);
+ rc = 0;
+ }else if( strcmp(z, "utc")==0 ){
+ double c1;
+ computeJD(p);
+ c1 = localtimeOffset(p);
+ p->rJD -= c1;
+ clearYMD_HMS_TZ(p);
+ p->rJD += c1 - localtimeOffset(p);
+ rc = 0;
+ }
+ break;
+ }
+ case 'w': {
+ /*
+ ** weekday N
+ **
+ ** Move the date to the same time on the next occurrance of
+ ** weekday N where 0==Sunday, 1==Monday, and so forth. If the
+ ** date is already on the appropriate weekday, this is a no-op.
+ */
+ if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0
+ && (n=r)==r && n>=0 && r<7 ){
+ int Z;
+ computeYMD_HMS(p);
+ p->validTZ = 0;
+ p->validJD = 0;
+ computeJD(p);
+ Z = p->rJD + 1.5;
+ Z %= 7;
+ if( Z>n ) Z -= 7;
+ p->rJD += n - Z;
+ clearYMD_HMS_TZ(p);
+ rc = 0;
+ }
+ break;
+ }
+ case 's': {
+ /*
+ ** start of TTTTT
+ **
+ ** Move the date backwards to the beginning of the current day,
+ ** or month or year.
+ */
+ if( strncmp(z, "start of ", 9)!=0 ) break;
+ z += 9;
+ computeYMD(p);
+ p->validHMS = 1;
+ p->h = p->m = 0;
+ p->s = 0.0;
+ p->validTZ = 0;
+ p->validJD = 0;
+ if( strcmp(z,"month")==0 ){
+ p->D = 1;
+ rc = 0;
+ }else if( strcmp(z,"year")==0 ){
+ computeYMD(p);
+ p->M = 1;
+ p->D = 1;
+ rc = 0;
+ }else if( strcmp(z,"day")==0 ){
+ rc = 0;
+ }
+ break;
+ }
+ case '+':
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ n = getValue(z, &r);
+ if( n<=0 ) break;
+ if( z[n]==':' ){
+ /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the
+ ** specified number of hours, minutes, seconds, and fractional seconds
+ ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be
+ ** omitted.
+ */
+ const char *z2 = z;
+ DateTime tx;
+ int day;
+ if( !isdigit(*z2) ) z2++;
+ memset(&tx, 0, sizeof(tx));
+ if( parseHhMmSs(z2, &tx) ) break;
+ computeJD(&tx);
+ tx.rJD -= 0.5;
+ day = (int)tx.rJD;
+ tx.rJD -= day;
+ if( z[0]=='-' ) tx.rJD = -tx.rJD;
+ computeJD(p);
+ clearYMD_HMS_TZ(p);
+ p->rJD += tx.rJD;
+ rc = 0;
+ break;
+ }
+ z += n;
+ while( isspace(z[0]) ) z++;
+ n = strlen(z);
+ if( n>10 || n<3 ) break;
+ if( z[n-1]=='s' ){ z[n-1] = 0; n--; }
+ computeJD(p);
+ rc = 0;
+ if( n==3 && strcmp(z,"day")==0 ){
+ p->rJD += r;
+ }else if( n==4 && strcmp(z,"hour")==0 ){
+ p->rJD += r/24.0;
+ }else if( n==6 && strcmp(z,"minute")==0 ){
+ p->rJD += r/(24.0*60.0);
+ }else if( n==6 && strcmp(z,"second")==0 ){
+ p->rJD += r/(24.0*60.0*60.0);
+ }else if( n==5 && strcmp(z,"month")==0 ){
+ int x, y;
+ computeYMD_HMS(p);
+ p->M += r;
+ x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12;
+ p->Y += x;
+ p->M -= x*12;
+ p->validJD = 0;
+ computeJD(p);
+ y = r;
+ if( y!=r ){
+ p->rJD += (r - y)*30.0;
+ }
+ }else if( n==4 && strcmp(z,"year")==0 ){
+ computeYMD_HMS(p);
+ p->Y += r;
+ p->validJD = 0;
+ computeJD(p);
+ }else{
+ rc = 1;
+ }
+ clearYMD_HMS_TZ(p);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ return rc;
+}
+
+/*
+** Process time function arguments. argv[0] is a date-time stamp.
+** argv[1] and following are modifiers. Parse them all and write
+** the resulting time into the DateTime structure p. Return 0
+** on success and 1 if there are any errors.
+*/
+static int isDate(int argc, const char **argv, DateTime *p){
+ int i;
+ if( argc==0 ) return 1;
+ if( argv[0]==0 || parseDateOrTime(argv[0], p) ) return 1;
+ for(i=1; i<argc; i++){
+ if( argv[i]==0 || parseModifier(argv[i], p) ) return 1;
+ }
+ return 0;
+}
+
+
+/*
+** The following routines implement the various date and time functions
+** of SQLite.
+*/
+
+/*
+** julianday( TIMESTRING, MOD, MOD, ...)
+**
+** Return the julian day number of the date specified in the arguments
+*/
+static void juliandayFunc(sqlite_func *context, int argc, const char **argv){
+ DateTime x;
+ if( isDate(argc, argv, &x)==0 ){
+ computeJD(&x);
+ sqlite_set_result_double(context, x.rJD);
+ }
+}
+
+/*
+** datetime( TIMESTRING, MOD, MOD, ...)
+**
+** Return YYYY-MM-DD HH:MM:SS
+*/
+static void datetimeFunc(sqlite_func *context, int argc, const char **argv){
+ DateTime x;
+ if( isDate(argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeYMD_HMS(&x);
+ sprintf(zBuf, "%04d-%02d-%02d %02d:%02d:%02d",x.Y, x.M, x.D, x.h, x.m,
+ (int)(x.s));
+ sqlite_set_result_string(context, zBuf, -1);
+ }
+}
+
+/*
+** time( TIMESTRING, MOD, MOD, ...)
+**
+** Return HH:MM:SS
+*/
+static void timeFunc(sqlite_func *context, int argc, const char **argv){
+ DateTime x;
+ if( isDate(argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeHMS(&x);
+ sprintf(zBuf, "%02d:%02d:%02d", x.h, x.m, (int)x.s);
+ sqlite_set_result_string(context, zBuf, -1);
+ }
+}
+
+/*
+** date( TIMESTRING, MOD, MOD, ...)
+**
+** Return YYYY-MM-DD
+*/
+static void dateFunc(sqlite_func *context, int argc, const char **argv){
+ DateTime x;
+ if( isDate(argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeYMD(&x);
+ sprintf(zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D);
+ sqlite_set_result_string(context, zBuf, -1);
+ }
+}
+
+/*
+** strftime( FORMAT, TIMESTRING, MOD, MOD, ...)
+**
+** Return a string described by FORMAT. Conversions as follows:
+**
+** %d day of month
+** %f ** fractional seconds SS.SSS
+** %H hour 00-24
+** %j day of year 000-366
+** %J ** Julian day number
+** %m month 01-12
+** %M minute 00-59
+** %s seconds since 1970-01-01
+** %S seconds 00-59
+** %w day of week 0-6 sunday==0
+** %W week of year 00-53
+** %Y year 0000-9999
+** %% %
+*/
+static void strftimeFunc(sqlite_func *context, int argc, const char **argv){
+ DateTime x;
+ int n, i, j;
+ char *z;
+ const char *zFmt = argv[0];
+ char zBuf[100];
+ if( argv[0]==0 || isDate(argc-1, argv+1, &x) ) return;
+ for(i=0, n=1; zFmt[i]; i++, n++){
+ if( zFmt[i]=='%' ){
+ switch( zFmt[i+1] ){
+ case 'd':
+ case 'H':
+ case 'm':
+ case 'M':
+ case 'S':
+ case 'W':
+ n++;
+ /* fall thru */
+ case 'w':
+ case '%':
+ break;
+ case 'f':
+ n += 8;
+ break;
+ case 'j':
+ n += 3;
+ break;
+ case 'Y':
+ n += 8;
+ break;
+ case 's':
+ case 'J':
+ n += 50;
+ break;
+ default:
+ return; /* ERROR. return a NULL */
+ }
+ i++;
+ }
+ }
+ if( n<sizeof(zBuf) ){
+ z = zBuf;
+ }else{
+ z = sqliteMalloc( n );
+ if( z==0 ) return;
+ }
+ computeJD(&x);
+ computeYMD_HMS(&x);
+ for(i=j=0; zFmt[i]; i++){
+ if( zFmt[i]!='%' ){
+ z[j++] = zFmt[i];
+ }else{
+ i++;
+ switch( zFmt[i] ){
+ case 'd': sprintf(&z[j],"%02d",x.D); j+=2; break;
+ case 'f': {
+ int s = x.s;
+ int ms = (x.s - s)*1000.0;
+ sprintf(&z[j],"%02d.%03d",s,ms);
+ j += strlen(&z[j]);
+ break;
+ }
+ case 'H': sprintf(&z[j],"%02d",x.h); j+=2; break;
+ case 'W': /* Fall thru */
+ case 'j': {
+ int n; /* Number of days since 1st day of year */
+ DateTime y = x;
+ y.validJD = 0;
+ y.M = 1;
+ y.D = 1;
+ computeJD(&y);
+ n = x.rJD - y.rJD;
+ if( zFmt[i]=='W' ){
+ int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
+ wd = ((int)(x.rJD+0.5)) % 7;
+ sprintf(&z[j],"%02d",(n+7-wd)/7);
+ j += 2;
+ }else{
+ sprintf(&z[j],"%03d",n+1);
+ j += 3;
+ }
+ break;
+ }
+ case 'J': sprintf(&z[j],"%.16g",x.rJD); j+=strlen(&z[j]); break;
+ case 'm': sprintf(&z[j],"%02d",x.M); j+=2; break;
+ case 'M': sprintf(&z[j],"%02d",x.m); j+=2; break;
+ case 's': {
+ sprintf(&z[j],"%d",(int)((x.rJD-2440587.5)*86400.0 + 0.5));
+ j += strlen(&z[j]);
+ break;
+ }
+ case 'S': sprintf(&z[j],"%02d",(int)(x.s+0.5)); j+=2; break;
+ case 'w': z[j++] = (((int)(x.rJD+1.5)) % 7) + '0'; break;
+ case 'Y': sprintf(&z[j],"%04d",x.Y); j+=strlen(&z[j]); break;
+ case '%': z[j++] = '%'; break;
+ }
+ }
+ }
+ z[j] = 0;
+ sqlite_set_result_string(context, z, -1);
+ if( z!=zBuf ){
+ sqliteFree(z);
+ }
+}
+
+
+#endif /* !defined(SQLITE_OMIT_DATETIME_FUNCS) */
+
+/*
+** This function registered all of the above C functions as SQL
+** functions. This should be the only routine in this file with
+** external linkage.
+*/
+void sqliteRegisterDateTimeFunctions(sqlite *db){
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
+ static struct {
+ char *zName;
+ int nArg;
+ int dataType;
+ void (*xFunc)(sqlite_func*,int,const char**);
+ } aFuncs[] = {
+ { "julianday", -1, SQLITE_NUMERIC, juliandayFunc },
+ { "date", -1, SQLITE_TEXT, dateFunc },
+ { "time", -1, SQLITE_TEXT, timeFunc },
+ { "datetime", -1, SQLITE_TEXT, datetimeFunc },
+ { "strftime", -1, SQLITE_TEXT, strftimeFunc },
+ };
+ int i;
+
+ for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+ sqlite_create_function(db, aFuncs[i].zName,
+ aFuncs[i].nArg, aFuncs[i].xFunc, 0);
+ if( aFuncs[i].xFunc ){
+ sqlite_function_type(db, aFuncs[i].zName, aFuncs[i].dataType);
+ }
+ }
+#endif
+}
diff --git a/src/libs/sqlite2/delete.c b/src/libs/sqlite2/delete.c
new file mode 100644
index 00000000..51054628
--- /dev/null
+++ b/src/libs/sqlite2/delete.c
@@ -0,0 +1,393 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** to handle DELETE FROM statements.
+**
+** $Id: delete.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+
+/*
+** Look up every table that is named in pSrc. If any table is not found,
+** add an error message to pParse->zErrMsg and return NULL. If all tables
+** are found, return a pointer to the last table.
+*/
+Table *sqliteSrcListLookup(Parse *pParse, SrcList *pSrc){
+ Table *pTab = 0;
+ int i;
+ for(i=0; i<pSrc->nSrc; i++){
+ const char *zTab = pSrc->a[i].zName;
+ const char *zDb = pSrc->a[i].zDatabase;
+ pTab = sqliteLocateTable(pParse, zTab, zDb);
+ pSrc->a[i].pTab = pTab;
+ }
+ return pTab;
+}
+
+/*
+** Check to make sure the given table is writable. If it is not
+** writable, generate an error message and return 1. If it is
+** writable return 0;
+*/
+int sqliteIsReadOnly(Parse *pParse, Table *pTab, int viewOk){
+ if( pTab->readOnly ){
+ sqliteErrorMsg(pParse, "table %s may not be modified", pTab->zName);
+ return 1;
+ }
+ if( !viewOk && pTab->pSelect ){
+ sqliteErrorMsg(pParse, "cannot modify %s because it is a view",pTab->zName);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** Process a DELETE FROM statement.
+*/
+void sqliteDeleteFrom(
+ Parse *pParse, /* The parser context */
+ SrcList *pTabList, /* The table from which we should delete things */
+ Expr *pWhere /* The WHERE clause. May be null */
+){
+ Vdbe *v; /* The virtual database engine */
+ Table *pTab; /* The table from which records will be deleted */
+ const char *zDb; /* Name of database holding pTab */
+ int end, addr; /* A couple addresses of generated code */
+ int i; /* Loop counter */
+ WhereInfo *pWInfo; /* Information about the WHERE clause */
+ Index *pIdx; /* For looping over indices of the table */
+ int iCur; /* VDBE Cursor number for pTab */
+ sqlite *db; /* Main database structure */
+ int isView; /* True if attempting to delete from a view */
+ AuthContext sContext; /* Authorization context */
+
+ int row_triggers_exist = 0; /* True if any triggers exist */
+ int before_triggers; /* True if there are BEFORE triggers */
+ int after_triggers; /* True if there are AFTER triggers */
+ int oldIdx = -1; /* Cursor for the OLD table of AFTER triggers */
+
+ sContext.pParse = 0;
+ if( pParse->nErr || sqlite_malloc_failed ){
+ pTabList = 0;
+ goto delete_from_cleanup;
+ }
+ db = pParse->db;
+ assert( pTabList->nSrc==1 );
+
+ /* Locate the table which we want to delete. This table has to be
+ ** put in an SrcList structure because some of the subroutines we
+ ** will be calling are designed to work with multiple tables and expect
+ ** an SrcList* parameter instead of just a Table* parameter.
+ */
+ pTab = sqliteSrcListLookup(pParse, pTabList);
+ if( pTab==0 ) goto delete_from_cleanup;
+ before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger,
+ TK_DELETE, TK_BEFORE, TK_ROW, 0);
+ after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger,
+ TK_DELETE, TK_AFTER, TK_ROW, 0);
+ row_triggers_exist = before_triggers || after_triggers;
+ isView = pTab->pSelect!=0;
+ if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){
+ goto delete_from_cleanup;
+ }
+ assert( pTab->iDb<db->nDb );
+ zDb = db->aDb[pTab->iDb].zName;
+ if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){
+ goto delete_from_cleanup;
+ }
+
+ /* If pTab is really a view, make sure it has been initialized.
+ */
+ if( isView && sqliteViewGetColumnNames(pParse, pTab) ){
+ goto delete_from_cleanup;
+ }
+
+ /* Allocate a cursor used to store the old.* data for a trigger.
+ */
+ if( row_triggers_exist ){
+ oldIdx = pParse->nTab++;
+ }
+
+ /* Resolve the column names in all the expressions.
+ */
+ assert( pTabList->nSrc==1 );
+ iCur = pTabList->a[0].iCursor = pParse->nTab++;
+ if( pWhere ){
+ if( sqliteExprResolveIds(pParse, pTabList, 0, pWhere) ){
+ goto delete_from_cleanup;
+ }
+ if( sqliteExprCheck(pParse, pWhere, 0, 0) ){
+ goto delete_from_cleanup;
+ }
+ }
+
+ /* Start the view context
+ */
+ if( isView ){
+ sqliteAuthContextPush(pParse, &sContext, pTab->zName);
+ }
+
+ /* Begin generating code.
+ */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ){
+ goto delete_from_cleanup;
+ }
+ sqliteBeginWriteOperation(pParse, row_triggers_exist, pTab->iDb);
+
+ /* If we are trying to delete from a view, construct that view into
+ ** a temporary table.
+ */
+ if( isView ){
+ Select *pView = sqliteSelectDup(pTab->pSelect);
+ sqliteSelect(pParse, pView, SRT_TempTable, iCur, 0, 0, 0);
+ sqliteSelectDelete(pView);
+ }
+
+ /* Initialize the counter of the number of rows deleted, if
+ ** we are counting rows.
+ */
+ if( db->flags & SQLITE_CountRows ){
+ sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ }
+
+ /* Special case: A DELETE without a WHERE clause deletes everything.
+ ** It is easier just to erase the whole table. Note, however, that
+ ** this means that the row change count will be incorrect.
+ */
+ if( pWhere==0 && !row_triggers_exist ){
+ if( db->flags & SQLITE_CountRows ){
+ /* If counting rows deleted, just count the total number of
+ ** entries in the table. */
+ int endOfLoop = sqliteVdbeMakeLabel(v);
+ int addr;
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum);
+ }
+ sqliteVdbeAddOp(v, OP_Rewind, iCur, sqliteVdbeCurrentAddr(v)+2);
+ addr = sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
+ sqliteVdbeAddOp(v, OP_Next, iCur, addr);
+ sqliteVdbeResolveLabel(v, endOfLoop);
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ }
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb);
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ sqliteVdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb);
+ }
+ }
+ }
+
+ /* The usual case: There is a WHERE clause so we have to scan through
+ ** the table and pick which records to delete.
+ */
+ else{
+ /* Begin the database scan
+ */
+ pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1, 0);
+ if( pWInfo==0 ) goto delete_from_cleanup;
+
+ /* Remember the key of every item to be deleted.
+ */
+ sqliteVdbeAddOp(v, OP_ListWrite, 0, 0);
+ if( db->flags & SQLITE_CountRows ){
+ sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
+ }
+
+ /* End the database scan loop.
+ */
+ sqliteWhereEnd(pWInfo);
+
+ /* Open the pseudo-table used to store OLD if there are triggers.
+ */
+ if( row_triggers_exist ){
+ sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0);
+ }
+
+ /* Delete every item whose key was written to the list during the
+ ** database scan. We have to delete items after the scan is complete
+ ** because deleting an item can change the scan order.
+ */
+ sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
+ end = sqliteVdbeMakeLabel(v);
+
+ /* This is the beginning of the delete loop when there are
+ ** row triggers.
+ */
+ if( row_triggers_exist ){
+ addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum);
+ }
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0);
+
+ sqliteVdbeAddOp(v, OP_Recno, iCur, 0);
+ sqliteVdbeAddOp(v, OP_RowData, iCur, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, oldIdx, 0);
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ }
+
+ sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1,
+ oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default,
+ addr);
+ }
+
+ if( !isView ){
+ /* Open cursors for the table we are deleting from and all its
+ ** indices. If there are row triggers, this happens inside the
+ ** OP_ListRead loop because the cursor have to all be closed
+ ** before the trigger fires. If there are no row triggers, the
+ ** cursors are opened only once on the outside the loop.
+ */
+ pParse->nTab = iCur + 1;
+ sqliteOpenTableAndIndices(pParse, pTab, iCur);
+
+ /* This is the beginning of the delete loop when there are no
+ ** row triggers */
+ if( !row_triggers_exist ){
+ addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end);
+ }
+
+ /* Delete the row */
+ sqliteGenerateRowDelete(db, v, pTab, iCur, pParse->trigStack==0);
+ }
+
+ /* If there are row triggers, close all cursors then invoke
+ ** the AFTER triggers
+ */
+ if( row_triggers_exist ){
+ if( !isView ){
+ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
+ sqliteVdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum);
+ }
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ }
+ sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1,
+ oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default,
+ addr);
+ }
+
+ /* End of the delete loop */
+ sqliteVdbeAddOp(v, OP_Goto, 0, addr);
+ sqliteVdbeResolveLabel(v, end);
+ sqliteVdbeAddOp(v, OP_ListReset, 0, 0);
+
+ /* Close the cursors after the loop if there are no row triggers */
+ if( !row_triggers_exist ){
+ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
+ sqliteVdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum);
+ }
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ pParse->nTab = iCur;
+ }
+ }
+ sqliteVdbeAddOp(v, OP_SetCounts, 0, 0);
+ sqliteEndWriteOperation(pParse);
+
+ /*
+ ** Return the number of rows that were deleted.
+ */
+ if( db->flags & SQLITE_CountRows ){
+ sqliteVdbeAddOp(v, OP_ColumnName, 0, 1);
+ sqliteVdbeChangeP3(v, -1, "rows deleted", P3_STATIC);
+ sqliteVdbeAddOp(v, OP_Callback, 1, 0);
+ }
+
+delete_from_cleanup:
+ sqliteAuthContextPop(&sContext);
+ sqliteSrcListDelete(pTabList);
+ sqliteExprDelete(pWhere);
+ return;
+}
+
+/*
+** This routine generates VDBE code that causes a single row of a
+** single table to be deleted.
+**
+** The VDBE must be in a particular state when this routine is called.
+** These are the requirements:
+**
+** 1. A read/write cursor pointing to pTab, the table containing the row
+** to be deleted, must be opened as cursor number "base".
+**
+** 2. Read/write cursors for all indices of pTab must be open as
+** cursor number base+i for the i-th index.
+**
+** 3. The record number of the row to be deleted must be on the top
+** of the stack.
+**
+** This routine pops the top of the stack to remove the record number
+** and then generates code to remove both the table record and all index
+** entries that point to that record.
+*/
+void sqliteGenerateRowDelete(
+ sqlite *db, /* The database containing the index */
+ Vdbe *v, /* Generate code into this VDBE */
+ Table *pTab, /* Table containing the row to be deleted */
+ int iCur, /* Cursor number for the table */
+ int count /* Increment the row change counter */
+){
+ int addr;
+ addr = sqliteVdbeAddOp(v, OP_NotExists, iCur, 0);
+ sqliteGenerateRowIndexDelete(db, v, pTab, iCur, 0);
+ sqliteVdbeAddOp(v, OP_Delete, iCur,
+ (count?OPFLAG_NCHANGE:0) | OPFLAG_CSCHANGE);
+ sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v));
+}
+
+/*
+** This routine generates VDBE code that causes the deletion of all
+** index entries associated with a single row of a single table.
+**
+** The VDBE must be in a particular state when this routine is called.
+** These are the requirements:
+**
+** 1. A read/write cursor pointing to pTab, the table containing the row
+** to be deleted, must be opened as cursor number "iCur".
+**
+** 2. Read/write cursors for all indices of pTab must be open as
+** cursor number iCur+i for the i-th index.
+**
+** 3. The "iCur" cursor must be pointing to the row that is to be
+** deleted.
+*/
+void sqliteGenerateRowIndexDelete(
+ sqlite *db, /* The database containing the index */
+ Vdbe *v, /* Generate code into this VDBE */
+ Table *pTab, /* Table containing the row to be deleted */
+ int iCur, /* Cursor number for the table */
+ char *aIdxUsed /* Only delete if aIdxUsed!=0 && aIdxUsed[i]!=0 */
+){
+ int i;
+ Index *pIdx;
+
+ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
+ int j;
+ if( aIdxUsed!=0 && aIdxUsed[i-1]==0 ) continue;
+ sqliteVdbeAddOp(v, OP_Recno, iCur, 0);
+ for(j=0; j<pIdx->nColumn; j++){
+ int idx = pIdx->aiColumn[j];
+ if( idx==pTab->iPKey ){
+ sqliteVdbeAddOp(v, OP_Dup, j, 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_Column, iCur, idx);
+ }
+ }
+ sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0);
+ if( db->file_format>=4 ) sqliteAddIdxKeyType(v, pIdx);
+ sqliteVdbeAddOp(v, OP_IdxDelete, iCur+i, 0);
+ }
+}
diff --git a/src/libs/sqlite2/encode.c b/src/libs/sqlite2/encode.c
new file mode 100644
index 00000000..7799b8b0
--- /dev/null
+++ b/src/libs/sqlite2/encode.c
@@ -0,0 +1,257 @@
+/*
+** 2002 April 25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains helper routines used to translate binary data into
+** a null-terminated string (suitable for use in SQLite) and back again.
+** These are convenience routines for use by people who want to store binary
+** data in an SQLite database. The code in this file is not used by any other
+** part of the SQLite library.
+**
+** $Id: encode.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include <string.h>
+#include <assert.h>
+
+/*
+** How This Encoder Works
+**
+** The output is allowed to contain any character except 0x27 (') and
+** 0x00. This is accomplished by using an escape character to encode
+** 0x27 and 0x00 as a two-byte sequence. The escape character is always
+** 0x01. An 0x00 is encoded as the two byte sequence 0x01 0x01. The
+** 0x27 character is encoded as the two byte sequence 0x01 0x28. Finally,
+** the escape character itself is encoded as the two-character sequence
+** 0x01 0x02.
+**
+** To summarize, the encoder works by using an escape sequences as follows:
+**
+** 0x00 -> 0x01 0x01
+** 0x01 -> 0x01 0x02
+** 0x27 -> 0x01 0x28
+**
+** If that were all the encoder did, it would work, but in certain cases
+** it could double the size of the encoded string. For example, to
+** encode a string of 100 0x27 characters would require 100 instances of
+** the 0x01 0x03 escape sequence resulting in a 200-character output.
+** We would prefer to keep the size of the encoded string smaller than
+** this.
+**
+** To minimize the encoding size, we first add a fixed offset value to each
+** byte in the sequence. The addition is modulo 256. (That is to say, if
+** the sum of the original character value and the offset exceeds 256, then
+** the higher order bits are truncated.) The offset is chosen to minimize
+** the number of characters in the string that need to be escaped. For
+** example, in the case above where the string was composed of 100 0x27
+** characters, the offset might be 0x01. Each of the 0x27 characters would
+** then be converted into an 0x28 character which would not need to be
+** escaped at all and so the 100 character input string would be converted
+** into just 100 characters of output. Actually 101 characters of output -
+** we have to record the offset used as the first byte in the sequence so
+** that the string can be decoded. Since the offset value is stored as
+** part of the output string and the output string is not allowed to contain
+** characters 0x00 or 0x27, the offset cannot be 0x00 or 0x27.
+**
+** Here, then, are the encoding steps:
+**
+** (1) Choose an offset value and make it the first character of
+** output.
+**
+** (2) Copy each input character into the output buffer, one by
+** one, adding the offset value as you copy.
+**
+** (3) If the value of an input character plus offset is 0x00, replace
+** that one character by the two-character sequence 0x01 0x01.
+** If the sum is 0x01, replace it with 0x01 0x02. If the sum
+** is 0x27, replace it with 0x01 0x03.
+**
+** (4) Put a 0x00 terminator at the end of the output.
+**
+** Decoding is obvious:
+**
+** (5) Copy encoded characters except the first into the decode
+** buffer. Set the first encoded character aside for use as
+** the offset in step 7 below.
+**
+** (6) Convert each 0x01 0x01 sequence into a single character 0x00.
+** Convert 0x01 0x02 into 0x01. Convert 0x01 0x28 into 0x27.
+**
+** (7) Subtract the offset value that was the first character of
+** the encoded buffer from all characters in the output buffer.
+**
+** The only tricky part is step (1) - how to compute an offset value to
+** minimize the size of the output buffer. This is accomplished by testing
+** all offset values and picking the one that results in the fewest number
+** of escapes. To do that, we first scan the entire input and count the
+** number of occurances of each character value in the input. Suppose
+** the number of 0x00 characters is N(0), the number of occurances of 0x01
+** is N(1), and so forth up to the number of occurances of 0xff is N(255).
+** An offset of 0 is not allowed so we don't have to test it. The number
+** of escapes required for an offset of 1 is N(1)+N(2)+N(40). The number
+** of escapes required for an offset of 2 is N(2)+N(3)+N(41). And so forth.
+** In this way we find the offset that gives the minimum number of escapes,
+** and thus minimizes the length of the output string.
+*/
+
+/*
+** Encode a binary buffer "in" of size n bytes so that it contains
+** no instances of characters '\'' or '\000'. The output is
+** null-terminated and can be used as a string value in an INSERT
+** or UPDATE statement. Use sqlite_decode_binary() to convert the
+** string back into its original binary.
+**
+** The result is written into a preallocated output buffer "out".
+** "out" must be able to hold at least 2 +(257*n)/254 bytes.
+** In other words, the output will be expanded by as much as 3
+** bytes for every 254 bytes of input plus 2 bytes of fixed overhead.
+** (This is approximately 2 + 1.0118*n or about a 1.2% size increase.)
+**
+** The return value is the number of characters in the encoded
+** string, excluding the "\000" terminator.
+**
+** If out==NULL then no output is generated but the routine still returns
+** the number of characters that would have been generated if out had
+** not been NULL.
+*/
+int sqlite_encode_binary(const unsigned char *in, int n, unsigned char *out){
+ int i, j, e, m;
+ unsigned char x;
+ int cnt[256];
+ if( n<=0 ){
+ if( out ){
+ out[0] = 'x';
+ out[1] = 0;
+ }
+ return 1;
+ }
+ memset(cnt, 0, sizeof(cnt));
+ for(i=n-1; i>=0; i--){ cnt[in[i]]++; }
+ m = n;
+ for(i=1; i<256; i++){
+ int sum;
+ if( i=='\'' ) continue;
+ sum = cnt[i] + cnt[(i+1)&0xff] + cnt[(i+'\'')&0xff];
+ if( sum<m ){
+ m = sum;
+ e = i;
+ if( m==0 ) break;
+ }
+ }
+ if( out==0 ){
+ return n+m+1;
+ }
+ out[0] = e;
+ j = 1;
+ for(i=0; i<n; i++){
+ x = in[i] - e;
+ if( x==0 || x==1 || x=='\''){
+ out[j++] = 1;
+ x++;
+ }
+ out[j++] = x;
+ }
+ out[j] = 0;
+ assert( j==n+m+1 );
+ return j;
+}
+
+/*
+** 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, e;
+ unsigned char c;
+ e = *(in++);
+ if (e == 0) {
+ return 0;
+ }
+ i = 0;
+ while( (c = *(in++))!=0 ){
+ if (c == 1) {
+ c = *(in++) - 1;
+ }
+ out[i++] = c + e;
+ }
+ return i;
+}
+
+#ifdef ENCODER_TEST
+#include <stdio.h>
+/*
+** The subroutines above are not tested by the usual test suite. To test
+** these routines, compile just this one file with a -DENCODER_TEST=1 option
+** and run the result.
+*/
+int main(int argc, char **argv){
+ int i, j, n, m, nOut, nByteIn, nByteOut;
+ unsigned char in[30000];
+ unsigned char out[33000];
+
+ nByteIn = nByteOut = 0;
+ for(i=0; i<sizeof(in); i++){
+ printf("Test %d: ", i+1);
+ n = rand() % (i+1);
+ if( i%100==0 ){
+ int k;
+ for(j=k=0; j<n; j++){
+ /* if( k==0 || k=='\'' ) k++; */
+ in[j] = k;
+ k = (k+1)&0xff;
+ }
+ }else{
+ for(j=0; j<n; j++) in[j] = rand() & 0xff;
+ }
+ nByteIn += n;
+ nOut = sqlite_encode_binary(in, n, out);
+ nByteOut += nOut;
+ if( nOut!=strlen(out) ){
+ printf(" ERROR return value is %d instead of %d\n", nOut, strlen(out));
+ exit(1);
+ }
+ if( nOut!=sqlite_encode_binary(in, n, 0) ){
+ printf(" ERROR actual output size disagrees with predicted size\n");
+ exit(1);
+ }
+ m = (256*n + 1262)/253;
+ printf("size %d->%d (max %d)", n, strlen(out)+1, m);
+ if( strlen(out)+1>m ){
+ printf(" ERROR output too big\n");
+ exit(1);
+ }
+ for(j=0; out[j]; j++){
+ if( out[j]=='\'' ){
+ printf(" ERROR contains (')\n");
+ exit(1);
+ }
+ }
+ j = sqlite_decode_binary(out, out);
+ if( j!=n ){
+ printf(" ERROR decode size %d\n", j);
+ exit(1);
+ }
+ if( memcmp(in, out, n)!=0 ){
+ printf(" ERROR decode mismatch\n");
+ exit(1);
+ }
+ printf(" OK\n");
+ }
+ fprintf(stderr,"Finished. Total encoding: %d->%d bytes\n",
+ nByteIn, nByteOut);
+ fprintf(stderr,"Avg size increase: %.3f%%\n",
+ (nByteOut-nByteIn)*100.0/(double)nByteIn);
+}
+#endif /* ENCODER_TEST */
diff --git a/src/libs/sqlite2/expr.c b/src/libs/sqlite2/expr.c
new file mode 100644
index 00000000..af4aa596
--- /dev/null
+++ b/src/libs/sqlite2/expr.c
@@ -0,0 +1,1662 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains routines used for analyzing expressions and
+** for generating VDBE code that evaluates expressions in SQLite.
+**
+** $Id: expr.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+#include <ctype.h>
+
+/*
+** Construct a new expression node and return a pointer to it. Memory
+** for this node is obtained from sqliteMalloc(). The calling function
+** is responsible for making sure the node eventually gets freed.
+*/
+Expr *sqliteExpr(int op, Expr *pLeft, Expr *pRight, Token *pToken){
+ Expr *pNew;
+ pNew = sqliteMalloc( sizeof(Expr) );
+ if( pNew==0 ){
+ /* When malloc fails, we leak memory from pLeft and pRight */
+ return 0;
+ }
+ pNew->op = op;
+ pNew->pLeft = pLeft;
+ pNew->pRight = pRight;
+ if( pToken ){
+ assert( pToken->dyn==0 );
+ pNew->token = *pToken;
+ pNew->span = *pToken;
+ }else{
+ assert( pNew->token.dyn==0 );
+ assert( pNew->token.z==0 );
+ assert( pNew->token.n==0 );
+ if( pLeft && pRight ){
+ sqliteExprSpan(pNew, &pLeft->span, &pRight->span);
+ }else{
+ pNew->span = pNew->token;
+ }
+ }
+ return pNew;
+}
+
+/*
+** Set the Expr.span field of the given expression to span all
+** text between the two given tokens.
+*/
+void sqliteExprSpan(Expr *pExpr, Token *pLeft, Token *pRight){
+ assert( pRight!=0 );
+ assert( pLeft!=0 );
+ /* Note: pExpr might be NULL due to a prior malloc failure */
+ if( pExpr && pRight->z && pLeft->z ){
+ if( pLeft->dyn==0 && pRight->dyn==0 ){
+ pExpr->span.z = pLeft->z;
+ pExpr->span.n = pRight->n + Addr(pRight->z) - Addr(pLeft->z);
+ }else{
+ pExpr->span.z = 0;
+ }
+ }
+}
+
+/*
+** Construct a new expression node for a function with multiple
+** arguments.
+*/
+Expr *sqliteExprFunction(ExprList *pList, Token *pToken){
+ Expr *pNew;
+ pNew = sqliteMalloc( sizeof(Expr) );
+ if( pNew==0 ){
+ /* sqliteExprListDelete(pList); // Leak pList when malloc fails */
+ return 0;
+ }
+ pNew->op = TK_FUNCTION;
+ pNew->pList = pList;
+ if( pToken ){
+ assert( pToken->dyn==0 );
+ pNew->token = *pToken;
+ }else{
+ pNew->token.z = 0;
+ }
+ pNew->span = pNew->token;
+ return pNew;
+}
+
+/*
+** Recursively delete an expression tree.
+*/
+void sqliteExprDelete(Expr *p){
+ if( p==0 ) return;
+ if( p->span.dyn ) sqliteFree((char*)p->span.z);
+ if( p->token.dyn ) sqliteFree((char*)p->token.z);
+ sqliteExprDelete(p->pLeft);
+ sqliteExprDelete(p->pRight);
+ sqliteExprListDelete(p->pList);
+ sqliteSelectDelete(p->pSelect);
+ sqliteFree(p);
+}
+
+
+/*
+** The following group of routines make deep copies of expressions,
+** expression lists, ID lists, and select statements. The copies can
+** be deleted (by being passed to their respective ...Delete() routines)
+** without effecting the originals.
+**
+** The expression list, ID, and source lists return by sqliteExprListDup(),
+** sqliteIdListDup(), and sqliteSrcListDup() can not be further expanded
+** by subsequent calls to sqlite*ListAppend() routines.
+**
+** Any tables that the SrcList might point to are not duplicated.
+*/
+Expr *sqliteExprDup(Expr *p){
+ Expr *pNew;
+ if( p==0 ) return 0;
+ pNew = sqliteMallocRaw( sizeof(*p) );
+ if( pNew==0 ) return 0;
+ memcpy(pNew, p, sizeof(*pNew));
+ if( p->token.z!=0 ){
+ pNew->token.z = sqliteStrNDup(p->token.z, p->token.n);
+ pNew->token.dyn = 1;
+ }else{
+ assert( pNew->token.z==0 );
+ }
+ pNew->span.z = 0;
+ pNew->pLeft = sqliteExprDup(p->pLeft);
+ pNew->pRight = sqliteExprDup(p->pRight);
+ pNew->pList = sqliteExprListDup(p->pList);
+ pNew->pSelect = sqliteSelectDup(p->pSelect);
+ return pNew;
+}
+void sqliteTokenCopy(Token *pTo, Token *pFrom){
+ if( pTo->dyn ) sqliteFree((char*)pTo->z);
+ if( pFrom->z ){
+ pTo->n = pFrom->n;
+ pTo->z = sqliteStrNDup(pFrom->z, pFrom->n);
+ pTo->dyn = 1;
+ }else{
+ pTo->z = 0;
+ }
+}
+ExprList *sqliteExprListDup(ExprList *p){
+ ExprList *pNew;
+ struct ExprList_item *pItem;
+ int i;
+ if( p==0 ) return 0;
+ pNew = sqliteMalloc( sizeof(*pNew) );
+ if( pNew==0 ) return 0;
+ pNew->nExpr = pNew->nAlloc = p->nExpr;
+ pNew->a = pItem = sqliteMalloc( p->nExpr*sizeof(p->a[0]) );
+ if( pItem==0 ){
+ sqliteFree(pNew);
+ return 0;
+ }
+ for(i=0; i<p->nExpr; i++, pItem++){
+ Expr *pNewExpr, *pOldExpr;
+ pItem->pExpr = pNewExpr = sqliteExprDup(pOldExpr = p->a[i].pExpr);
+ if( pOldExpr->span.z!=0 && pNewExpr ){
+ /* Always make a copy of the span for top-level expressions in the
+ ** expression list. The logic in SELECT processing that determines
+ ** the names of columns in the result set needs this information */
+ sqliteTokenCopy(&pNewExpr->span, &pOldExpr->span);
+ }
+ assert( pNewExpr==0 || pNewExpr->span.z!=0
+ || pOldExpr->span.z==0 || sqlite_malloc_failed );
+ pItem->zName = sqliteStrDup(p->a[i].zName);
+ pItem->sortOrder = p->a[i].sortOrder;
+ pItem->isAgg = p->a[i].isAgg;
+ pItem->done = 0;
+ }
+ return pNew;
+}
+SrcList *sqliteSrcListDup(SrcList *p){
+ SrcList *pNew;
+ int i;
+ int nByte;
+ if( p==0 ) return 0;
+ nByte = sizeof(*p) + (p->nSrc>0 ? sizeof(p->a[0]) * (p->nSrc-1) : 0);
+ pNew = sqliteMallocRaw( nByte );
+ if( pNew==0 ) return 0;
+ pNew->nSrc = pNew->nAlloc = p->nSrc;
+ for(i=0; i<p->nSrc; i++){
+ struct SrcList_item *pNewItem = &pNew->a[i];
+ struct SrcList_item *pOldItem = &p->a[i];
+ pNewItem->zDatabase = sqliteStrDup(pOldItem->zDatabase);
+ pNewItem->zName = sqliteStrDup(pOldItem->zName);
+ pNewItem->zAlias = sqliteStrDup(pOldItem->zAlias);
+ pNewItem->jointype = pOldItem->jointype;
+ pNewItem->iCursor = pOldItem->iCursor;
+ pNewItem->pTab = 0;
+ pNewItem->pSelect = sqliteSelectDup(pOldItem->pSelect);
+ pNewItem->pOn = sqliteExprDup(pOldItem->pOn);
+ pNewItem->pUsing = sqliteIdListDup(pOldItem->pUsing);
+ }
+ return pNew;
+}
+IdList *sqliteIdListDup(IdList *p){
+ IdList *pNew;
+ int i;
+ if( p==0 ) return 0;
+ pNew = sqliteMallocRaw( sizeof(*pNew) );
+ if( pNew==0 ) return 0;
+ pNew->nId = pNew->nAlloc = p->nId;
+ pNew->a = sqliteMallocRaw( p->nId*sizeof(p->a[0]) );
+ if( pNew->a==0 ) return 0;
+ for(i=0; i<p->nId; i++){
+ struct IdList_item *pNewItem = &pNew->a[i];
+ struct IdList_item *pOldItem = &p->a[i];
+ pNewItem->zName = sqliteStrDup(pOldItem->zName);
+ pNewItem->idx = pOldItem->idx;
+ }
+ return pNew;
+}
+Select *sqliteSelectDup(Select *p){
+ Select *pNew;
+ if( p==0 ) return 0;
+ pNew = sqliteMallocRaw( sizeof(*p) );
+ if( pNew==0 ) return 0;
+ pNew->isDistinct = p->isDistinct;
+ pNew->pEList = sqliteExprListDup(p->pEList);
+ pNew->pSrc = sqliteSrcListDup(p->pSrc);
+ pNew->pWhere = sqliteExprDup(p->pWhere);
+ pNew->pGroupBy = sqliteExprListDup(p->pGroupBy);
+ pNew->pHaving = sqliteExprDup(p->pHaving);
+ pNew->pOrderBy = sqliteExprListDup(p->pOrderBy);
+ pNew->op = p->op;
+ pNew->pPrior = sqliteSelectDup(p->pPrior);
+ pNew->nLimit = p->nLimit;
+ pNew->nOffset = p->nOffset;
+ pNew->zSelect = 0;
+ pNew->iLimit = -1;
+ pNew->iOffset = -1;
+ return pNew;
+}
+
+
+/*
+** Add a new element to the end of an expression list. If pList is
+** initially NULL, then create a new expression list.
+*/
+ExprList *sqliteExprListAppend(ExprList *pList, Expr *pExpr, Token *pName){
+ if( pList==0 ){
+ pList = sqliteMalloc( sizeof(ExprList) );
+ if( pList==0 ){
+ /* sqliteExprDelete(pExpr); // Leak memory if malloc fails */
+ return 0;
+ }
+ assert( pList->nAlloc==0 );
+ }
+ if( pList->nAlloc<=pList->nExpr ){
+ pList->nAlloc = pList->nAlloc*2 + 4;
+ pList->a = sqliteRealloc(pList->a, pList->nAlloc*sizeof(pList->a[0]));
+ if( pList->a==0 ){
+ /* sqliteExprDelete(pExpr); // Leak memory if malloc fails */
+ pList->nExpr = pList->nAlloc = 0;
+ return pList;
+ }
+ }
+ assert( pList->a!=0 );
+ if( pExpr || pName ){
+ struct ExprList_item *pItem = &pList->a[pList->nExpr++];
+ memset(pItem, 0, sizeof(*pItem));
+ pItem->pExpr = pExpr;
+ if( pName ){
+ sqliteSetNString(&pItem->zName, pName->z, pName->n, 0);
+ sqliteDequote(pItem->zName);
+ }
+ }
+ return pList;
+}
+
+/*
+** Delete an entire expression list.
+*/
+void sqliteExprListDelete(ExprList *pList){
+ int i;
+ if( pList==0 ) return;
+ assert( pList->a!=0 || (pList->nExpr==0 && pList->nAlloc==0) );
+ assert( pList->nExpr<=pList->nAlloc );
+ for(i=0; i<pList->nExpr; i++){
+ sqliteExprDelete(pList->a[i].pExpr);
+ sqliteFree(pList->a[i].zName);
+ }
+ sqliteFree(pList->a);
+ sqliteFree(pList);
+}
+
+/*
+** Walk an expression tree. Return 1 if the expression is constant
+** and 0 if it involves variables.
+**
+** For the purposes of this function, a double-quoted string (ex: "abc")
+** is considered a variable but a single-quoted string (ex: 'abc') is
+** a constant.
+*/
+int sqliteExprIsConstant(Expr *p){
+ switch( p->op ){
+ case TK_ID:
+ case TK_COLUMN:
+ case TK_DOT:
+ case TK_FUNCTION:
+ return 0;
+ case TK_NULL:
+ case TK_STRING:
+ case TK_INTEGER:
+ case TK_FLOAT:
+ case TK_VARIABLE:
+ return 1;
+ default: {
+ if( p->pLeft && !sqliteExprIsConstant(p->pLeft) ) return 0;
+ if( p->pRight && !sqliteExprIsConstant(p->pRight) ) return 0;
+ if( p->pList ){
+ int i;
+ for(i=0; i<p->pList->nExpr; i++){
+ if( !sqliteExprIsConstant(p->pList->a[i].pExpr) ) return 0;
+ }
+ }
+ return p->pLeft!=0 || p->pRight!=0 || (p->pList && p->pList->nExpr>0);
+ }
+ }
+ return 0;
+}
+
+/*
+** If the given expression codes a constant integer that is small enough
+** to fit in a 32-bit integer, return 1 and put the value of the integer
+** in *pValue. If the expression is not an integer or if it is too big
+** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
+*/
+int sqliteExprIsInteger(Expr *p, int *pValue){
+ switch( p->op ){
+ case TK_INTEGER: {
+ if( sqliteFitsIn32Bits(p->token.z) ){
+ *pValue = atoi(p->token.z);
+ return 1;
+ }
+ break;
+ }
+ case TK_STRING: {
+ const char *z = p->token.z;
+ int n = p->token.n;
+ if( n>0 && z[0]=='-' ){ z++; n--; }
+ while( n>0 && *z && isdigit(*z) ){ z++; n--; }
+ if( n==0 && sqliteFitsIn32Bits(p->token.z) ){
+ *pValue = atoi(p->token.z);
+ return 1;
+ }
+ break;
+ }
+ case TK_UPLUS: {
+ return sqliteExprIsInteger(p->pLeft, pValue);
+ }
+ case TK_UMINUS: {
+ int v;
+ if( sqliteExprIsInteger(p->pLeft, &v) ){
+ *pValue = -v;
+ return 1;
+ }
+ break;
+ }
+ default: break;
+ }
+ return 0;
+}
+
+/*
+** Return TRUE if the given string is a row-id column name.
+*/
+int sqliteIsRowid(const char *z){
+ if( sqliteStrICmp(z, "_ROWID_")==0 ) return 1;
+ if( sqliteStrICmp(z, "ROWID")==0 ) return 1;
+ if( sqliteStrICmp(z, "OID")==0 ) return 1;
+ return 0;
+}
+
+/*
+** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
+** that name in the set of source tables in pSrcList and make the pExpr
+** expression node refer back to that source column. The following changes
+** are made to pExpr:
+**
+** pExpr->iDb Set the index in db->aDb[] of the database holding
+** the table.
+** pExpr->iTable Set to the cursor number for the table obtained
+** from pSrcList.
+** pExpr->iColumn Set to the column number within the table.
+** pExpr->dataType Set to the appropriate data type for the column.
+** pExpr->op Set to TK_COLUMN.
+** pExpr->pLeft Any expression this points to is deleted
+** pExpr->pRight Any expression this points to is deleted.
+**
+** The pDbToken is the name of the database (the "X"). This value may be
+** NULL meaning that name is of the form Y.Z or Z. Any available database
+** can be used. The pTableToken is the name of the table (the "Y"). This
+** value can be NULL if pDbToken is also NULL. If pTableToken is NULL it
+** means that the form of the name is Z and that columns from any table
+** can be used.
+**
+** If the name cannot be resolved unambiguously, leave an error message
+** in pParse and return non-zero. Return zero on success.
+*/
+static int lookupName(
+ Parse *pParse, /* The parsing context */
+ Token *pDbToken, /* Name of the database containing table, or NULL */
+ Token *pTableToken, /* Name of table containing column, or NULL */
+ Token *pColumnToken, /* Name of the column. */
+ SrcList *pSrcList, /* List of tables used to resolve column names */
+ ExprList *pEList, /* List of expressions used to resolve "AS" */
+ Expr *pExpr /* Make this EXPR node point to the selected column */
+){
+ char *zDb = 0; /* Name of the database. The "X" in X.Y.Z */
+ char *zTab = 0; /* Name of the table. The "Y" in X.Y.Z or Y.Z */
+ char *zCol = 0; /* Name of the column. The "Z" */
+ int i, j; /* Loop counters */
+ int cnt = 0; /* Number of matching column names */
+ int cntTab = 0; /* Number of matching table names */
+ sqlite *db = pParse->db; /* The database */
+
+ assert( pColumnToken && pColumnToken->z ); /* The Z in X.Y.Z cannot be NULL */
+ if( pDbToken && pDbToken->z ){
+ zDb = sqliteStrNDup(pDbToken->z, pDbToken->n);
+ sqliteDequote(zDb);
+ }else{
+ zDb = 0;
+ }
+ if( pTableToken && pTableToken->z ){
+ zTab = sqliteStrNDup(pTableToken->z, pTableToken->n);
+ sqliteDequote(zTab);
+ }else{
+ assert( zDb==0 );
+ zTab = 0;
+ }
+ zCol = sqliteStrNDup(pColumnToken->z, pColumnToken->n);
+ sqliteDequote(zCol);
+ if( sqlite_malloc_failed ){
+ return 1; /* Leak memory (zDb and zTab) if malloc fails */
+ }
+ assert( zTab==0 || pEList==0 );
+
+ pExpr->iTable = -1;
+ for(i=0; i<pSrcList->nSrc; i++){
+ struct SrcList_item *pItem = &pSrcList->a[i];
+ Table *pTab = pItem->pTab;
+ Column *pCol;
+
+ if( pTab==0 ) continue;
+ assert( pTab->nCol>0 );
+ if( zTab ){
+ if( pItem->zAlias ){
+ char *zTabName = pItem->zAlias;
+ if( sqliteStrICmp(zTabName, zTab)!=0 ) continue;
+ }else{
+ char *zTabName = pTab->zName;
+ if( zTabName==0 || sqliteStrICmp(zTabName, zTab)!=0 ) continue;
+ if( zDb!=0 && sqliteStrICmp(db->aDb[pTab->iDb].zName, zDb)!=0 ){
+ continue;
+ }
+ }
+ }
+ if( 0==(cntTab++) ){
+ pExpr->iTable = pItem->iCursor;
+ pExpr->iDb = pTab->iDb;
+ }
+ for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){
+ if( sqliteStrICmp(pCol->zName, zCol)==0 ){
+ cnt++;
+ pExpr->iTable = pItem->iCursor;
+ pExpr->iDb = pTab->iDb;
+ /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
+ pExpr->iColumn = j==pTab->iPKey ? -1 : j;
+ pExpr->dataType = pCol->sortOrder & SQLITE_SO_TYPEMASK;
+ break;
+ }
+ }
+ }
+
+ /* If we have not already resolved the name, then maybe
+ ** it is a new.* or old.* trigger argument reference
+ */
+ if( zDb==0 && zTab!=0 && cnt==0 && pParse->trigStack!=0 ){
+ TriggerStack *pTriggerStack = pParse->trigStack;
+ Table *pTab = 0;
+ if( pTriggerStack->newIdx != -1 && sqliteStrICmp("new", zTab) == 0 ){
+ pExpr->iTable = pTriggerStack->newIdx;
+ assert( pTriggerStack->pTab );
+ pTab = pTriggerStack->pTab;
+ }else if( pTriggerStack->oldIdx != -1 && sqliteStrICmp("old", zTab) == 0 ){
+ pExpr->iTable = pTriggerStack->oldIdx;
+ assert( pTriggerStack->pTab );
+ pTab = pTriggerStack->pTab;
+ }
+
+ if( pTab ){
+ int j;
+ Column *pCol = pTab->aCol;
+
+ pExpr->iDb = pTab->iDb;
+ cntTab++;
+ for(j=0; j < pTab->nCol; j++, pCol++) {
+ if( sqliteStrICmp(pCol->zName, zCol)==0 ){
+ cnt++;
+ pExpr->iColumn = j==pTab->iPKey ? -1 : j;
+ pExpr->dataType = pCol->sortOrder & SQLITE_SO_TYPEMASK;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ ** Perhaps the name is a reference to the ROWID
+ */
+ if( cnt==0 && cntTab==1 && sqliteIsRowid(zCol) ){
+ cnt = 1;
+ pExpr->iColumn = -1;
+ pExpr->dataType = SQLITE_SO_NUM;
+ }
+
+ /*
+ ** If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z
+ ** might refer to an result-set alias. This happens, for example, when
+ ** we are resolving names in the WHERE clause of the following command:
+ **
+ ** SELECT a+b AS x FROM table WHERE x<10;
+ **
+ ** In cases like this, replace pExpr with a copy of the expression that
+ ** forms the result set entry ("a+b" in the example) and return immediately.
+ ** Note that the expression in the result set should have already been
+ ** resolved by the time the WHERE clause is resolved.
+ */
+ if( cnt==0 && pEList!=0 ){
+ for(j=0; j<pEList->nExpr; j++){
+ char *zAs = pEList->a[j].zName;
+ if( zAs!=0 && sqliteStrICmp(zAs, zCol)==0 ){
+ assert( pExpr->pLeft==0 && pExpr->pRight==0 );
+ pExpr->op = TK_AS;
+ pExpr->iColumn = j;
+ pExpr->pLeft = sqliteExprDup(pEList->a[j].pExpr);
+ sqliteFree(zCol);
+ assert( zTab==0 && zDb==0 );
+ return 0;
+ }
+ }
+ }
+
+ /*
+ ** If X and Y are NULL (in other words if only the column name Z is
+ ** supplied) and the value of Z is enclosed in double-quotes, then
+ ** Z is a string literal if it doesn't match any column names. In that
+ ** case, we need to return right away and not make any changes to
+ ** pExpr.
+ */
+ if( cnt==0 && zTab==0 && pColumnToken->z[0]=='"' ){
+ sqliteFree(zCol);
+ return 0;
+ }
+
+ /*
+ ** cnt==0 means there was not match. cnt>1 means there were two or
+ ** more matches. Either way, we have an error.
+ */
+ if( cnt!=1 ){
+ char *z = 0;
+ char *zErr;
+ zErr = cnt==0 ? "no such column: %s" : "ambiguous column name: %s";
+ if( zDb ){
+ sqliteSetString(&z, zDb, ".", zTab, ".", zCol, 0);
+ }else if( zTab ){
+ sqliteSetString(&z, zTab, ".", zCol, 0);
+ }else{
+ z = sqliteStrDup(zCol);
+ }
+ sqliteErrorMsg(pParse, zErr, z);
+ sqliteFree(z);
+ }
+
+ /* Clean up and return
+ */
+ sqliteFree(zDb);
+ sqliteFree(zTab);
+ sqliteFree(zCol);
+ sqliteExprDelete(pExpr->pLeft);
+ pExpr->pLeft = 0;
+ sqliteExprDelete(pExpr->pRight);
+ pExpr->pRight = 0;
+ pExpr->op = TK_COLUMN;
+ sqliteAuthRead(pParse, pExpr, pSrcList);
+ return cnt!=1;
+}
+
+/*
+** This routine walks an expression tree and resolves references to
+** table columns. Nodes of the form ID.ID or ID resolve into an
+** index to the table in the table list and a column offset. The
+** Expr.opcode for such nodes is changed to TK_COLUMN. The Expr.iTable
+** value is changed to the index of the referenced table in pTabList
+** plus the "base" value. The base value will ultimately become the
+** VDBE cursor number for a cursor that is pointing into the referenced
+** table. The Expr.iColumn value is changed to the index of the column
+** of the referenced table. The Expr.iColumn value for the special
+** ROWID column is -1. Any INTEGER PRIMARY KEY column is tried as an
+** alias for ROWID.
+**
+** We also check for instances of the IN operator. IN comes in two
+** forms:
+**
+** expr IN (exprlist)
+** and
+** expr IN (SELECT ...)
+**
+** The first form is handled by creating a set holding the list
+** of allowed values. The second form causes the SELECT to generate
+** a temporary table.
+**
+** This routine also looks for scalar SELECTs that are part of an expression.
+** If it finds any, it generates code to write the value of that select
+** into a memory cell.
+**
+** Unknown columns or tables provoke an error. The function returns
+** the number of errors seen and leaves an error message on pParse->zErrMsg.
+*/
+int sqliteExprResolveIds(
+ Parse *pParse, /* The parser context */
+ SrcList *pSrcList, /* List of tables used to resolve column names */
+ ExprList *pEList, /* List of expressions used to resolve "AS" */
+ Expr *pExpr /* The expression to be analyzed. */
+){
+ int i;
+
+ if( pExpr==0 || pSrcList==0 ) return 0;
+ for(i=0; i<pSrcList->nSrc; i++){
+ assert( pSrcList->a[i].iCursor>=0 && pSrcList->a[i].iCursor<pParse->nTab );
+ }
+ switch( pExpr->op ){
+ /* Double-quoted strings (ex: "abc") are used as identifiers if
+ ** possible. Otherwise they remain as strings. Single-quoted
+ ** strings (ex: 'abc') are always string literals.
+ */
+ case TK_STRING: {
+ if( pExpr->token.z[0]=='\'' ) break;
+ /* Fall thru into the TK_ID case if this is a double-quoted string */
+ }
+ /* A lone identifier is the name of a columnd.
+ */
+ case TK_ID: {
+ if( lookupName(pParse, 0, 0, &pExpr->token, pSrcList, pEList, pExpr) ){
+ return 1;
+ }
+ break;
+ }
+
+ /* A table name and column name: ID.ID
+ ** Or a database, table and column: ID.ID.ID
+ */
+ case TK_DOT: {
+ Token *pColumn;
+ Token *pTable;
+ Token *pDb;
+ Expr *pRight;
+
+ pRight = pExpr->pRight;
+ if( pRight->op==TK_ID ){
+ pDb = 0;
+ pTable = &pExpr->pLeft->token;
+ pColumn = &pRight->token;
+ }else{
+ assert( pRight->op==TK_DOT );
+ pDb = &pExpr->pLeft->token;
+ pTable = &pRight->pLeft->token;
+ pColumn = &pRight->pRight->token;
+ }
+ if( lookupName(pParse, pDb, pTable, pColumn, pSrcList, 0, pExpr) ){
+ return 1;
+ }
+ break;
+ }
+
+ case TK_IN: {
+ Vdbe *v = sqliteGetVdbe(pParse);
+ if( v==0 ) return 1;
+ if( sqliteExprResolveIds(pParse, pSrcList, pEList, pExpr->pLeft) ){
+ return 1;
+ }
+ if( pExpr->pSelect ){
+ /* Case 1: expr IN (SELECT ...)
+ **
+ ** Generate code to write the results of the select into a temporary
+ ** table. The cursor number of the temporary table has already
+ ** been put in iTable by sqliteExprResolveInSelect().
+ */
+ pExpr->iTable = pParse->nTab++;
+ sqliteVdbeAddOp(v, OP_OpenTemp, pExpr->iTable, 1);
+ sqliteSelect(pParse, pExpr->pSelect, SRT_Set, pExpr->iTable, 0,0,0);
+ }else if( pExpr->pList ){
+ /* Case 2: expr IN (exprlist)
+ **
+ ** Create a set to put the exprlist values in. The Set id is stored
+ ** in iTable.
+ */
+ int i, iSet;
+ for(i=0; i<pExpr->pList->nExpr; i++){
+ Expr *pE2 = pExpr->pList->a[i].pExpr;
+ if( !sqliteExprIsConstant(pE2) ){
+ sqliteErrorMsg(pParse,
+ "right-hand side of IN operator must be constant");
+ return 1;
+ }
+ if( sqliteExprCheck(pParse, pE2, 0, 0) ){
+ return 1;
+ }
+ }
+ iSet = pExpr->iTable = pParse->nSet++;
+ for(i=0; i<pExpr->pList->nExpr; i++){
+ Expr *pE2 = pExpr->pList->a[i].pExpr;
+ switch( pE2->op ){
+ case TK_FLOAT:
+ case TK_INTEGER:
+ case TK_STRING: {
+ int addr;
+ assert( pE2->token.z );
+ addr = sqliteVdbeOp3(v, OP_SetInsert, iSet, 0,
+ pE2->token.z, pE2->token.n);
+ sqliteVdbeDequoteP3(v, addr);
+ break;
+ }
+ default: {
+ sqliteExprCode(pParse, pE2);
+ sqliteVdbeAddOp(v, OP_SetInsert, iSet, 0);
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case TK_SELECT: {
+ /* This has to be a scalar SELECT. Generate code to put the
+ ** value of this select in a memory cell and record the number
+ ** of the memory cell in iColumn.
+ */
+ pExpr->iColumn = pParse->nMem++;
+ if( sqliteSelect(pParse, pExpr->pSelect, SRT_Mem, pExpr->iColumn,0,0,0) ){
+ return 1;
+ }
+ break;
+ }
+
+ /* For all else, just recursively walk the tree */
+ default: {
+ if( pExpr->pLeft
+ && sqliteExprResolveIds(pParse, pSrcList, pEList, pExpr->pLeft) ){
+ return 1;
+ }
+ if( pExpr->pRight
+ && sqliteExprResolveIds(pParse, pSrcList, pEList, pExpr->pRight) ){
+ return 1;
+ }
+ if( pExpr->pList ){
+ int i;
+ ExprList *pList = pExpr->pList;
+ for(i=0; i<pList->nExpr; i++){
+ Expr *pArg = pList->a[i].pExpr;
+ if( sqliteExprResolveIds(pParse, pSrcList, pEList, pArg) ){
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** pExpr is a node that defines a function of some kind. It might
+** be a syntactic function like "count(x)" or it might be a function
+** that implements an operator, like "a LIKE b".
+**
+** This routine makes *pzName point to the name of the function and
+** *pnName hold the number of characters in the function name.
+*/
+static void getFunctionName(Expr *pExpr, const char **pzName, int *pnName){
+ switch( pExpr->op ){
+ case TK_FUNCTION: {
+ *pzName = pExpr->token.z;
+ *pnName = pExpr->token.n;
+ break;
+ }
+ case TK_LIKE: {
+ *pzName = "like";
+ *pnName = 4;
+ break;
+ }
+ case TK_GLOB: {
+ *pzName = "glob";
+ *pnName = 4;
+ break;
+ }
+ default: {
+ *pzName = "can't happen";
+ *pnName = 12;
+ break;
+ }
+ }
+}
+
+/*
+** Error check the functions in an expression. Make sure all
+** function names are recognized and all functions have the correct
+** number of arguments. Leave an error message in pParse->zErrMsg
+** if anything is amiss. Return the number of errors.
+**
+** if pIsAgg is not null and this expression is an aggregate function
+** (like count(*) or max(value)) then write a 1 into *pIsAgg.
+*/
+int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){
+ int nErr = 0;
+ if( pExpr==0 ) return 0;
+ switch( pExpr->op ){
+ case TK_GLOB:
+ case TK_LIKE:
+ case TK_FUNCTION: {
+ int n = pExpr->pList ? pExpr->pList->nExpr : 0; /* Number of arguments */
+ int no_such_func = 0; /* True if no such function exists */
+ int wrong_num_args = 0; /* True if wrong number of arguments */
+ int is_agg = 0; /* True if is an aggregate function */
+ int i;
+ int nId; /* Number of characters in function name */
+ const char *zId; /* The function name. */
+ FuncDef *pDef;
+
+ getFunctionName(pExpr, &zId, &nId);
+ pDef = sqliteFindFunction(pParse->db, zId, nId, n, 0);
+ if( pDef==0 ){
+ pDef = sqliteFindFunction(pParse->db, zId, nId, -1, 0);
+ if( pDef==0 ){
+ no_such_func = 1;
+ }else{
+ wrong_num_args = 1;
+ }
+ }else{
+ is_agg = pDef->xFunc==0;
+ }
+ if( is_agg && !allowAgg ){
+ sqliteErrorMsg(pParse, "misuse of aggregate function %.*s()", nId, zId);
+ nErr++;
+ is_agg = 0;
+ }else if( no_such_func ){
+ sqliteErrorMsg(pParse, "no such function: %.*s", nId, zId);
+ nErr++;
+ }else if( wrong_num_args ){
+ sqliteErrorMsg(pParse,"wrong number of arguments to function %.*s()",
+ nId, zId);
+ nErr++;
+ }
+ if( is_agg ){
+ pExpr->op = TK_AGG_FUNCTION;
+ if( pIsAgg ) *pIsAgg = 1;
+ }
+ for(i=0; nErr==0 && i<n; i++){
+ nErr = sqliteExprCheck(pParse, pExpr->pList->a[i].pExpr,
+ allowAgg && !is_agg, pIsAgg);
+ }
+ if( pDef==0 ){
+ /* Already reported an error */
+ }else if( pDef->dataType>=0 ){
+ if( pDef->dataType<n ){
+ pExpr->dataType =
+ sqliteExprType(pExpr->pList->a[pDef->dataType].pExpr);
+ }else{
+ pExpr->dataType = SQLITE_SO_NUM;
+ }
+ }else if( pDef->dataType==SQLITE_ARGS ){
+ pDef->dataType = SQLITE_SO_TEXT;
+ for(i=0; i<n; i++){
+ if( sqliteExprType(pExpr->pList->a[i].pExpr)==SQLITE_SO_NUM ){
+ pExpr->dataType = SQLITE_SO_NUM;
+ break;
+ }
+ }
+ }else if( pDef->dataType==SQLITE_NUMERIC ){
+ pExpr->dataType = SQLITE_SO_NUM;
+ }else{
+ pExpr->dataType = SQLITE_SO_TEXT;
+ }
+ }
+ default: {
+ if( pExpr->pLeft ){
+ nErr = sqliteExprCheck(pParse, pExpr->pLeft, allowAgg, pIsAgg);
+ }
+ if( nErr==0 && pExpr->pRight ){
+ nErr = sqliteExprCheck(pParse, pExpr->pRight, allowAgg, pIsAgg);
+ }
+ if( nErr==0 && pExpr->pList ){
+ int n = pExpr->pList->nExpr;
+ int i;
+ for(i=0; nErr==0 && i<n; i++){
+ Expr *pE2 = pExpr->pList->a[i].pExpr;
+ nErr = sqliteExprCheck(pParse, pE2, allowAgg, pIsAgg);
+ }
+ }
+ break;
+ }
+ }
+ return nErr;
+}
+
+/*
+** Return either SQLITE_SO_NUM or SQLITE_SO_TEXT to indicate whether the
+** given expression should sort as numeric values or as text.
+**
+** The sqliteExprResolveIds() and sqliteExprCheck() routines must have
+** both been called on the expression before it is passed to this routine.
+*/
+int sqliteExprType(Expr *p){
+ if( p==0 ) return SQLITE_SO_NUM;
+ while( p ) switch( p->op ){
+ case TK_PLUS:
+ case TK_MINUS:
+ case TK_STAR:
+ case TK_SLASH:
+ case TK_AND:
+ case TK_OR:
+ case TK_ISNULL:
+ case TK_NOTNULL:
+ case TK_NOT:
+ case TK_UMINUS:
+ case TK_UPLUS:
+ case TK_BITAND:
+ case TK_BITOR:
+ case TK_BITNOT:
+ case TK_LSHIFT:
+ case TK_RSHIFT:
+ case TK_REM:
+ case TK_INTEGER:
+ case TK_FLOAT:
+ case TK_IN:
+ case TK_BETWEEN:
+ case TK_GLOB:
+ case TK_LIKE:
+ return SQLITE_SO_NUM;
+
+ case TK_STRING:
+ case TK_NULL:
+ case TK_CONCAT:
+ case TK_VARIABLE:
+ return SQLITE_SO_TEXT;
+
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ:
+ if( sqliteExprType(p->pLeft)==SQLITE_SO_NUM ){
+ return SQLITE_SO_NUM;
+ }
+ p = p->pRight;
+ break;
+
+ case TK_AS:
+ p = p->pLeft;
+ break;
+
+ case TK_COLUMN:
+ case TK_FUNCTION:
+ case TK_AGG_FUNCTION:
+ return p->dataType;
+
+ case TK_SELECT:
+ assert( p->pSelect );
+ assert( p->pSelect->pEList );
+ assert( p->pSelect->pEList->nExpr>0 );
+ p = p->pSelect->pEList->a[0].pExpr;
+ break;
+
+ case TK_CASE: {
+ if( p->pRight && sqliteExprType(p->pRight)==SQLITE_SO_NUM ){
+ return SQLITE_SO_NUM;
+ }
+ if( p->pList ){
+ int i;
+ ExprList *pList = p->pList;
+ for(i=1; i<pList->nExpr; i+=2){
+ if( sqliteExprType(pList->a[i].pExpr)==SQLITE_SO_NUM ){
+ return SQLITE_SO_NUM;
+ }
+ }
+ }
+ return SQLITE_SO_TEXT;
+ }
+
+ default:
+ assert( p->op==TK_ABORT ); /* Can't Happen */
+ break;
+ }
+ return SQLITE_SO_NUM;
+}
+
+/*
+** Generate code into the current Vdbe to evaluate the given
+** expression and leave the result on the top of stack.
+*/
+void sqliteExprCode(Parse *pParse, Expr *pExpr){
+ Vdbe *v = pParse->pVdbe;
+ int op;
+ if( v==0 || pExpr==0 ) return;
+ switch( pExpr->op ){
+ case TK_PLUS: op = OP_Add; break;
+ case TK_MINUS: op = OP_Subtract; break;
+ case TK_STAR: op = OP_Multiply; break;
+ case TK_SLASH: op = OP_Divide; break;
+ case TK_AND: op = OP_And; break;
+ case TK_OR: op = OP_Or; break;
+ case TK_LT: op = OP_Lt; break;
+ case TK_LE: op = OP_Le; break;
+ case TK_GT: op = OP_Gt; break;
+ case TK_GE: op = OP_Ge; break;
+ case TK_NE: op = OP_Ne; break;
+ case TK_EQ: op = OP_Eq; break;
+ case TK_ISNULL: op = OP_IsNull; break;
+ case TK_NOTNULL: op = OP_NotNull; break;
+ case TK_NOT: op = OP_Not; break;
+ case TK_UMINUS: op = OP_Negative; break;
+ case TK_BITAND: op = OP_BitAnd; break;
+ case TK_BITOR: op = OP_BitOr; break;
+ case TK_BITNOT: op = OP_BitNot; break;
+ case TK_LSHIFT: op = OP_ShiftLeft; break;
+ case TK_RSHIFT: op = OP_ShiftRight; break;
+ case TK_REM: op = OP_Remainder; break;
+ default: break;
+ }
+ switch( pExpr->op ){
+ case TK_COLUMN: {
+ if( pParse->useAgg ){
+ sqliteVdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg);
+ }else if( pExpr->iColumn>=0 ){
+ sqliteVdbeAddOp(v, OP_Column, pExpr->iTable, pExpr->iColumn);
+ }else{
+ sqliteVdbeAddOp(v, OP_Recno, pExpr->iTable, 0);
+ }
+ break;
+ }
+ case TK_STRING:
+ case TK_FLOAT:
+ case TK_INTEGER: {
+ if( pExpr->op==TK_INTEGER && sqliteFitsIn32Bits(pExpr->token.z) ){
+ sqliteVdbeAddOp(v, OP_Integer, atoi(pExpr->token.z), 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ }
+ assert( pExpr->token.z );
+ sqliteVdbeChangeP3(v, -1, pExpr->token.z, pExpr->token.n);
+ sqliteVdbeDequoteP3(v, -1);
+ break;
+ }
+ case TK_NULL: {
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ break;
+ }
+ case TK_VARIABLE: {
+ sqliteVdbeAddOp(v, OP_Variable, pExpr->iTable, 0);
+ break;
+ }
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ: {
+ if( pParse->db->file_format>=4 && sqliteExprType(pExpr)==SQLITE_SO_TEXT ){
+ op += 6; /* Convert numeric opcodes to text opcodes */
+ }
+ /* Fall through into the next case */
+ }
+ case TK_AND:
+ case TK_OR:
+ case TK_PLUS:
+ case TK_STAR:
+ case TK_MINUS:
+ case TK_REM:
+ case TK_BITAND:
+ case TK_BITOR:
+ case TK_SLASH: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteExprCode(pParse, pExpr->pRight);
+ sqliteVdbeAddOp(v, op, 0, 0);
+ break;
+ }
+ case TK_LSHIFT:
+ case TK_RSHIFT: {
+ sqliteExprCode(pParse, pExpr->pRight);
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, op, 0, 0);
+ break;
+ }
+ case TK_CONCAT: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteExprCode(pParse, pExpr->pRight);
+ sqliteVdbeAddOp(v, OP_Concat, 2, 0);
+ break;
+ }
+ case TK_UMINUS: {
+ assert( pExpr->pLeft );
+ if( pExpr->pLeft->op==TK_FLOAT || pExpr->pLeft->op==TK_INTEGER ){
+ Token *p = &pExpr->pLeft->token;
+ char *z = sqliteMalloc( p->n + 2 );
+ sprintf(z, "-%.*s", p->n, p->z);
+ if( pExpr->pLeft->op==TK_INTEGER && sqliteFitsIn32Bits(z) ){
+ sqliteVdbeAddOp(v, OP_Integer, atoi(z), 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ }
+ sqliteVdbeChangeP3(v, -1, z, p->n+1);
+ sqliteFree(z);
+ break;
+ }
+ /* Fall through into TK_NOT */
+ }
+ case TK_BITNOT:
+ case TK_NOT: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, op, 0, 0);
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ int dest;
+ sqliteVdbeAddOp(v, OP_Integer, 1, 0);
+ sqliteExprCode(pParse, pExpr->pLeft);
+ dest = sqliteVdbeCurrentAddr(v) + 2;
+ sqliteVdbeAddOp(v, op, 1, dest);
+ sqliteVdbeAddOp(v, OP_AddImm, -1, 0);
+ break;
+ }
+ case TK_AGG_FUNCTION: {
+ sqliteVdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg);
+ break;
+ }
+ case TK_GLOB:
+ case TK_LIKE:
+ case TK_FUNCTION: {
+ ExprList *pList = pExpr->pList;
+ int nExpr = pList ? pList->nExpr : 0;
+ FuncDef *pDef;
+ int nId;
+ const char *zId;
+ getFunctionName(pExpr, &zId, &nId);
+ pDef = sqliteFindFunction(pParse->db, zId, nId, nExpr, 0);
+ assert( pDef!=0 );
+ nExpr = sqliteExprCodeExprList(pParse, pList, pDef->includeTypes);
+ sqliteVdbeOp3(v, OP_Function, nExpr, 0, (char*)pDef, P3_POINTER);
+ break;
+ }
+ case TK_SELECT: {
+ sqliteVdbeAddOp(v, OP_MemLoad, pExpr->iColumn, 0);
+ break;
+ }
+ case TK_IN: {
+ int addr;
+ sqliteVdbeAddOp(v, OP_Integer, 1, 0);
+ sqliteExprCode(pParse, pExpr->pLeft);
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, addr+4);
+ sqliteVdbeAddOp(v, OP_Pop, 2, 0);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, addr+6);
+ if( pExpr->pSelect ){
+ sqliteVdbeAddOp(v, OP_Found, pExpr->iTable, addr+6);
+ }else{
+ sqliteVdbeAddOp(v, OP_SetFound, pExpr->iTable, addr+6);
+ }
+ sqliteVdbeAddOp(v, OP_AddImm, -1, 0);
+ break;
+ }
+ case TK_BETWEEN: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ sqliteExprCode(pParse, pExpr->pList->a[0].pExpr);
+ sqliteVdbeAddOp(v, OP_Ge, 0, 0);
+ sqliteVdbeAddOp(v, OP_Pull, 1, 0);
+ sqliteExprCode(pParse, pExpr->pList->a[1].pExpr);
+ sqliteVdbeAddOp(v, OP_Le, 0, 0);
+ sqliteVdbeAddOp(v, OP_And, 0, 0);
+ break;
+ }
+ case TK_UPLUS:
+ case TK_AS: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ break;
+ }
+ case TK_CASE: {
+ int expr_end_label;
+ int jumpInst;
+ int addr;
+ int nExpr;
+ int i;
+
+ assert(pExpr->pList);
+ assert((pExpr->pList->nExpr % 2) == 0);
+ assert(pExpr->pList->nExpr > 0);
+ nExpr = pExpr->pList->nExpr;
+ expr_end_label = sqliteVdbeMakeLabel(v);
+ if( pExpr->pLeft ){
+ sqliteExprCode(pParse, pExpr->pLeft);
+ }
+ for(i=0; i<nExpr; i=i+2){
+ sqliteExprCode(pParse, pExpr->pList->a[i].pExpr);
+ if( pExpr->pLeft ){
+ sqliteVdbeAddOp(v, OP_Dup, 1, 1);
+ jumpInst = sqliteVdbeAddOp(v, OP_Ne, 1, 0);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ }else{
+ jumpInst = sqliteVdbeAddOp(v, OP_IfNot, 1, 0);
+ }
+ sqliteExprCode(pParse, pExpr->pList->a[i+1].pExpr);
+ sqliteVdbeAddOp(v, OP_Goto, 0, expr_end_label);
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeChangeP2(v, jumpInst, addr);
+ }
+ if( pExpr->pLeft ){
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ }
+ if( pExpr->pRight ){
+ sqliteExprCode(pParse, pExpr->pRight);
+ }else{
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ }
+ sqliteVdbeResolveLabel(v, expr_end_label);
+ break;
+ }
+ case TK_RAISE: {
+ if( !pParse->trigStack ){
+ sqliteErrorMsg(pParse,
+ "RAISE() may only be used within a trigger-program");
+ pParse->nErr++;
+ return;
+ }
+ if( pExpr->iColumn == OE_Rollback ||
+ pExpr->iColumn == OE_Abort ||
+ pExpr->iColumn == OE_Fail ){
+ sqliteVdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, pExpr->iColumn,
+ pExpr->token.z, pExpr->token.n);
+ sqliteVdbeDequoteP3(v, -1);
+ } else {
+ assert( pExpr->iColumn == OE_Ignore );
+ sqliteVdbeOp3(v, OP_Goto, 0, pParse->trigStack->ignoreJump,
+ "(IGNORE jump)", 0);
+ }
+ }
+ break;
+ }
+}
+
+/*
+** Generate code that pushes the value of every element of the given
+** expression list onto the stack. If the includeTypes flag is true,
+** then also push a string that is the datatype of each element onto
+** the stack after the value.
+**
+** Return the number of elements pushed onto the stack.
+*/
+int sqliteExprCodeExprList(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* The expression list to be coded */
+ int includeTypes /* TRUE to put datatypes on the stack too */
+){
+ struct ExprList_item *pItem;
+ int i, n;
+ Vdbe *v;
+ if( pList==0 ) return 0;
+ v = sqliteGetVdbe(pParse);
+ n = pList->nExpr;
+ for(pItem=pList->a, i=0; i<n; i++, pItem++){
+ sqliteExprCode(pParse, pItem->pExpr);
+ if( includeTypes ){
+ sqliteVdbeOp3(v, OP_String, 0, 0,
+ sqliteExprType(pItem->pExpr)==SQLITE_SO_NUM ? "numeric" : "text",
+ P3_STATIC);
+ }
+ }
+ return includeTypes ? n*2 : n;
+}
+
+/*
+** Generate code for a boolean expression such that a jump is made
+** to the label "dest" if the expression is true but execution
+** continues straight thru if the expression is false.
+**
+** If the expression evaluates to NULL (neither true nor false), then
+** take the jump if the jumpIfNull flag is true.
+*/
+void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+ Vdbe *v = pParse->pVdbe;
+ int op = 0;
+ if( v==0 || pExpr==0 ) return;
+ switch( pExpr->op ){
+ case TK_LT: op = OP_Lt; break;
+ case TK_LE: op = OP_Le; break;
+ case TK_GT: op = OP_Gt; break;
+ case TK_GE: op = OP_Ge; break;
+ case TK_NE: op = OP_Ne; break;
+ case TK_EQ: op = OP_Eq; break;
+ case TK_ISNULL: op = OP_IsNull; break;
+ case TK_NOTNULL: op = OP_NotNull; break;
+ default: break;
+ }
+ switch( pExpr->op ){
+ case TK_AND: {
+ int d2 = sqliteVdbeMakeLabel(v);
+ sqliteExprIfFalse(pParse, pExpr->pLeft, d2, !jumpIfNull);
+ sqliteExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ sqliteVdbeResolveLabel(v, d2);
+ break;
+ }
+ case TK_OR: {
+ sqliteExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ sqliteExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ break;
+ }
+ case TK_NOT: {
+ sqliteExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ break;
+ }
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteExprCode(pParse, pExpr->pRight);
+ if( pParse->db->file_format>=4 && sqliteExprType(pExpr)==SQLITE_SO_TEXT ){
+ op += 6; /* Convert numeric opcodes to text opcodes */
+ }
+ sqliteVdbeAddOp(v, op, jumpIfNull, dest);
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, op, 1, dest);
+ break;
+ }
+ case TK_IN: {
+ int addr;
+ sqliteExprCode(pParse, pExpr->pLeft);
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, addr+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, jumpIfNull ? dest : addr+4);
+ if( pExpr->pSelect ){
+ sqliteVdbeAddOp(v, OP_Found, pExpr->iTable, dest);
+ }else{
+ sqliteVdbeAddOp(v, OP_SetFound, pExpr->iTable, dest);
+ }
+ break;
+ }
+ case TK_BETWEEN: {
+ int addr;
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ sqliteExprCode(pParse, pExpr->pList->a[0].pExpr);
+ addr = sqliteVdbeAddOp(v, OP_Lt, !jumpIfNull, 0);
+ sqliteExprCode(pParse, pExpr->pList->a[1].pExpr);
+ sqliteVdbeAddOp(v, OP_Le, jumpIfNull, dest);
+ sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v));
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ break;
+ }
+ default: {
+ sqliteExprCode(pParse, pExpr);
+ sqliteVdbeAddOp(v, OP_If, jumpIfNull, dest);
+ break;
+ }
+ }
+}
+
+/*
+** Generate code for a boolean expression such that a jump is made
+** to the label "dest" if the expression is false but execution
+** continues straight thru if the expression is true.
+**
+** If the expression evaluates to NULL (neither true nor false) then
+** jump if jumpIfNull is true or fall through if jumpIfNull is false.
+*/
+void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+ Vdbe *v = pParse->pVdbe;
+ int op = 0;
+ if( v==0 || pExpr==0 ) return;
+ switch( pExpr->op ){
+ case TK_LT: op = OP_Ge; break;
+ case TK_LE: op = OP_Gt; break;
+ case TK_GT: op = OP_Le; break;
+ case TK_GE: op = OP_Lt; break;
+ case TK_NE: op = OP_Eq; break;
+ case TK_EQ: op = OP_Ne; break;
+ case TK_ISNULL: op = OP_NotNull; break;
+ case TK_NOTNULL: op = OP_IsNull; break;
+ default: break;
+ }
+ switch( pExpr->op ){
+ case TK_AND: {
+ sqliteExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ sqliteExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ break;
+ }
+ case TK_OR: {
+ int d2 = sqliteVdbeMakeLabel(v);
+ sqliteExprIfTrue(pParse, pExpr->pLeft, d2, !jumpIfNull);
+ sqliteExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ sqliteVdbeResolveLabel(v, d2);
+ break;
+ }
+ case TK_NOT: {
+ sqliteExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ break;
+ }
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ: {
+ if( pParse->db->file_format>=4 && sqliteExprType(pExpr)==SQLITE_SO_TEXT ){
+ /* Convert numeric comparison opcodes into text comparison opcodes.
+ ** This step depends on the fact that the text comparision opcodes are
+ ** always 6 greater than their corresponding numeric comparison
+ ** opcodes.
+ */
+ assert( OP_Eq+6 == OP_StrEq );
+ op += 6;
+ }
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteExprCode(pParse, pExpr->pRight);
+ sqliteVdbeAddOp(v, op, jumpIfNull, dest);
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, op, 1, dest);
+ break;
+ }
+ case TK_IN: {
+ int addr;
+ sqliteExprCode(pParse, pExpr->pLeft);
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, addr+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, jumpIfNull ? dest : addr+4);
+ if( pExpr->pSelect ){
+ sqliteVdbeAddOp(v, OP_NotFound, pExpr->iTable, dest);
+ }else{
+ sqliteVdbeAddOp(v, OP_SetNotFound, pExpr->iTable, dest);
+ }
+ break;
+ }
+ case TK_BETWEEN: {
+ int addr;
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ sqliteExprCode(pParse, pExpr->pList->a[0].pExpr);
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_Ge, !jumpIfNull, addr+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, dest);
+ sqliteExprCode(pParse, pExpr->pList->a[1].pExpr);
+ sqliteVdbeAddOp(v, OP_Gt, jumpIfNull, dest);
+ break;
+ }
+ default: {
+ sqliteExprCode(pParse, pExpr);
+ sqliteVdbeAddOp(v, OP_IfNot, jumpIfNull, dest);
+ break;
+ }
+ }
+}
+
+/*
+** Do a deep comparison of two expression trees. Return TRUE (non-zero)
+** if they are identical and return FALSE if they differ in any way.
+*/
+int sqliteExprCompare(Expr *pA, Expr *pB){
+ int i;
+ if( pA==0 ){
+ return pB==0;
+ }else if( pB==0 ){
+ return 0;
+ }
+ if( pA->op!=pB->op ) return 0;
+ if( !sqliteExprCompare(pA->pLeft, pB->pLeft) ) return 0;
+ if( !sqliteExprCompare(pA->pRight, pB->pRight) ) return 0;
+ if( pA->pList ){
+ if( pB->pList==0 ) return 0;
+ if( pA->pList->nExpr!=pB->pList->nExpr ) return 0;
+ for(i=0; i<pA->pList->nExpr; i++){
+ if( !sqliteExprCompare(pA->pList->a[i].pExpr, pB->pList->a[i].pExpr) ){
+ return 0;
+ }
+ }
+ }else if( pB->pList ){
+ return 0;
+ }
+ if( pA->pSelect || pB->pSelect ) return 0;
+ if( pA->iTable!=pB->iTable || pA->iColumn!=pB->iColumn ) return 0;
+ if( pA->token.z ){
+ if( pB->token.z==0 ) return 0;
+ if( pB->token.n!=pA->token.n ) return 0;
+ if( sqliteStrNICmp(pA->token.z, pB->token.z, pB->token.n)!=0 ) return 0;
+ }
+ return 1;
+}
+
+/*
+** Add a new element to the pParse->aAgg[] array and return its index.
+*/
+static int appendAggInfo(Parse *pParse){
+ if( (pParse->nAgg & 0x7)==0 ){
+ int amt = pParse->nAgg + 8;
+ AggExpr *aAgg = sqliteRealloc(pParse->aAgg, amt*sizeof(pParse->aAgg[0]));
+ if( aAgg==0 ){
+ return -1;
+ }
+ pParse->aAgg = aAgg;
+ }
+ memset(&pParse->aAgg[pParse->nAgg], 0, sizeof(pParse->aAgg[0]));
+ return pParse->nAgg++;
+}
+
+/*
+** Analyze the given expression looking for aggregate functions and
+** for variables that need to be added to the pParse->aAgg[] array.
+** Make additional entries to the pParse->aAgg[] array as necessary.
+**
+** This routine should only be called after the expression has been
+** analyzed by sqliteExprResolveIds() and sqliteExprCheck().
+**
+** If errors are seen, leave an error message in zErrMsg and return
+** the number of errors.
+*/
+int sqliteExprAnalyzeAggregates(Parse *pParse, Expr *pExpr){
+ int i;
+ AggExpr *aAgg;
+ int nErr = 0;
+
+ if( pExpr==0 ) return 0;
+ switch( pExpr->op ){
+ case TK_COLUMN: {
+ aAgg = pParse->aAgg;
+ for(i=0; i<pParse->nAgg; i++){
+ if( aAgg[i].isAgg ) continue;
+ if( aAgg[i].pExpr->iTable==pExpr->iTable
+ && aAgg[i].pExpr->iColumn==pExpr->iColumn ){
+ break;
+ }
+ }
+ if( i>=pParse->nAgg ){
+ i = appendAggInfo(pParse);
+ if( i<0 ) return 1;
+ pParse->aAgg[i].isAgg = 0;
+ pParse->aAgg[i].pExpr = pExpr;
+ }
+ pExpr->iAgg = i;
+ break;
+ }
+ case TK_AGG_FUNCTION: {
+ aAgg = pParse->aAgg;
+ for(i=0; i<pParse->nAgg; i++){
+ if( !aAgg[i].isAgg ) continue;
+ if( sqliteExprCompare(aAgg[i].pExpr, pExpr) ){
+ break;
+ }
+ }
+ if( i>=pParse->nAgg ){
+ i = appendAggInfo(pParse);
+ if( i<0 ) return 1;
+ pParse->aAgg[i].isAgg = 1;
+ pParse->aAgg[i].pExpr = pExpr;
+ pParse->aAgg[i].pFunc = sqliteFindFunction(pParse->db,
+ pExpr->token.z, pExpr->token.n,
+ pExpr->pList ? pExpr->pList->nExpr : 0, 0);
+ }
+ pExpr->iAgg = i;
+ break;
+ }
+ default: {
+ if( pExpr->pLeft ){
+ nErr = sqliteExprAnalyzeAggregates(pParse, pExpr->pLeft);
+ }
+ if( nErr==0 && pExpr->pRight ){
+ nErr = sqliteExprAnalyzeAggregates(pParse, pExpr->pRight);
+ }
+ if( nErr==0 && pExpr->pList ){
+ int n = pExpr->pList->nExpr;
+ int i;
+ for(i=0; nErr==0 && i<n; i++){
+ nErr = sqliteExprAnalyzeAggregates(pParse, pExpr->pList->a[i].pExpr);
+ }
+ }
+ break;
+ }
+ }
+ return nErr;
+}
+
+/*
+** Locate a user function given a name and a number of arguments.
+** Return a pointer to the FuncDef structure that defines that
+** function, or return NULL if the function does not exist.
+**
+** If the createFlag argument is true, then a new (blank) FuncDef
+** structure is created and liked into the "db" structure if a
+** no matching function previously existed. When createFlag is true
+** and the nArg parameter is -1, then only a function that accepts
+** any number of arguments will be returned.
+**
+** If createFlag is false and nArg is -1, then the first valid
+** function found is returned. A function is valid if either xFunc
+** or xStep is non-zero.
+*/
+FuncDef *sqliteFindFunction(
+ sqlite *db, /* An open database */
+ const char *zName, /* Name of the function. Not null-terminated */
+ int nName, /* Number of characters in the name */
+ int nArg, /* Number of arguments. -1 means any number */
+ int createFlag /* Create new entry if true and does not otherwise exist */
+){
+ FuncDef *pFirst, *p, *pMaybe;
+ pFirst = p = (FuncDef*)sqliteHashFind(&db->aFunc, zName, nName);
+ if( p && !createFlag && nArg<0 ){
+ while( p && p->xFunc==0 && p->xStep==0 ){ p = p->pNext; }
+ return p;
+ }
+ pMaybe = 0;
+ while( p && p->nArg!=nArg ){
+ if( p->nArg<0 && !createFlag && (p->xFunc || p->xStep) ) pMaybe = p;
+ p = p->pNext;
+ }
+ if( p && !createFlag && p->xFunc==0 && p->xStep==0 ){
+ return 0;
+ }
+ if( p==0 && pMaybe ){
+ assert( createFlag==0 );
+ return pMaybe;
+ }
+ if( p==0 && createFlag && (p = sqliteMalloc(sizeof(*p)))!=0 ){
+ p->nArg = nArg;
+ p->pNext = pFirst;
+ p->dataType = pFirst ? pFirst->dataType : SQLITE_NUMERIC;
+ sqliteHashInsert(&db->aFunc, zName, nName, (void*)p);
+ }
+ return p;
+}
diff --git a/src/libs/sqlite2/func.c b/src/libs/sqlite2/func.c
new file mode 100644
index 00000000..c86a75a3
--- /dev/null
+++ b/src/libs/sqlite2/func.c
@@ -0,0 +1,658 @@
+/*
+** 2002 February 23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement various SQL
+** functions of SQLite.
+**
+** There is only one exported symbol in this file - the function
+** sqliteRegisterBuildinFunctions() found at the bottom of the file.
+** All other code has file scope.
+**
+** $Id: func.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "sqliteInt.h"
+#include "os.h"
+
+/*
+** Implementation of the non-aggregate min() and max() functions
+*/
+static void minmaxFunc(sqlite_func *context, int argc, const char **argv){
+ const char *zBest;
+ int i;
+ int (*xCompare)(const char*, const char*);
+ int mask; /* 0 for min() or 0xffffffff for max() */
+
+ if( argc==0 ) return;
+ mask = (int)sqlite_user_data(context);
+ zBest = argv[0];
+ if( zBest==0 ) return;
+ if( argv[1][0]=='n' ){
+ xCompare = sqliteCompare;
+ }else{
+ xCompare = strcmp;
+ }
+ for(i=2; i<argc; i+=2){
+ if( argv[i]==0 ) return;
+ if( (xCompare(argv[i], zBest)^mask)<0 ){
+ zBest = argv[i];
+ }
+ }
+ sqlite_set_result_string(context, zBest, -1);
+}
+
+/*
+** Return the type of the argument.
+*/
+static void typeofFunc(sqlite_func *context, int argc, const char **argv){
+ assert( argc==2 );
+ sqlite_set_result_string(context, argv[1], -1);
+}
+
+/*
+** Implementation of the length() function
+*/
+static void lengthFunc(sqlite_func *context, int argc, const char **argv){
+ const char *z;
+ int len;
+
+ assert( argc==1 );
+ z = argv[0];
+ if( z==0 ) return;
+#ifdef SQLITE_UTF8
+ for(len=0; *z; z++){ if( (0xc0&*z)!=0x80 ) len++; }
+#else
+ len = strlen(z);
+#endif
+ sqlite_set_result_int(context, len);
+}
+
+/*
+** Implementation of the abs() function
+*/
+static void absFunc(sqlite_func *context, int argc, const char **argv){
+ const char *z;
+ assert( argc==1 );
+ z = argv[0];
+ if( z==0 ) return;
+ if( z[0]=='-' && isdigit(z[1]) ) z++;
+ sqlite_set_result_string(context, z, -1);
+}
+
+/*
+** Implementation of the substr() function
+*/
+static void substrFunc(sqlite_func *context, int argc, const char **argv){
+ const char *z;
+#ifdef SQLITE_UTF8
+ const char *z2;
+ int i;
+#endif
+ int p1, p2, len;
+ assert( argc==3 );
+ z = argv[0];
+ if( z==0 ) return;
+ p1 = atoi(argv[1]?argv[1]:0);
+ p2 = atoi(argv[2]?argv[2]:0);
+#ifdef SQLITE_UTF8
+ for(len=0, z2=z; *z2; z2++){ if( (0xc0&*z2)!=0x80 ) len++; }
+#else
+ len = strlen(z);
+#endif
+ if( p1<0 ){
+ p1 += len;
+ if( p1<0 ){
+ p2 += p1;
+ p1 = 0;
+ }
+ }else if( p1>0 ){
+ p1--;
+ }
+ if( p1+p2>len ){
+ p2 = len-p1;
+ }
+#ifdef SQLITE_UTF8
+ for(i=0; i<p1 && z[i]; i++){
+ if( (z[i]&0xc0)==0x80 ) p1++;
+ }
+ while( z[i] && (z[i]&0xc0)==0x80 ){ i++; p1++; }
+ for(; i<p1+p2 && z[i]; i++){
+ if( (z[i]&0xc0)==0x80 ) p2++;
+ }
+ while( z[i] && (z[i]&0xc0)==0x80 ){ i++; p2++; }
+#endif
+ if( p2<0 ) p2 = 0;
+ sqlite_set_result_string(context, &z[p1], p2);
+}
+
+/*
+** Implementation of the round() function
+*/
+static void roundFunc(sqlite_func *context, int argc, const char **argv){
+ int n;
+ double r;
+ char zBuf[100];
+ assert( argc==1 || argc==2 );
+ if( argv[0]==0 || (argc==2 && argv[1]==0) ) return;
+ n = argc==2 ? atoi(argv[1]) : 0;
+ if( n>30 ) n = 30;
+ if( n<0 ) n = 0;
+ r = sqliteAtoF(argv[0], 0);
+ sprintf(zBuf,"%.*f",n,r);
+ sqlite_set_result_string(context, zBuf, -1);
+}
+
+/*
+** Implementation of the upper() and lower() SQL functions.
+*/
+static void upperFunc(sqlite_func *context, int argc, const char **argv){
+ unsigned char *z;
+ int i;
+ if( argc<1 || argv[0]==0 ) return;
+ z = (unsigned char*)sqlite_set_result_string(context, argv[0], -1);
+ if( z==0 ) return;
+ for(i=0; z[i]; i++){
+ if( islower(z[i]) ) z[i] = toupper(z[i]);
+ }
+}
+static void lowerFunc(sqlite_func *context, int argc, const char **argv){
+ unsigned char *z;
+ int i;
+ if( argc<1 || argv[0]==0 ) return;
+ z = (unsigned char*)sqlite_set_result_string(context, argv[0], -1);
+ if( z==0 ) return;
+ for(i=0; z[i]; i++){
+ if( isupper(z[i]) ) z[i] = tolower(z[i]);
+ }
+}
+
+/*
+** Implementation of the IFNULL(), NVL(), and COALESCE() functions.
+** All three do the same thing. They return the first non-NULL
+** argument.
+*/
+static void ifnullFunc(sqlite_func *context, int argc, const char **argv){
+ int i;
+ for(i=0; i<argc; i++){
+ if( argv[i] ){
+ sqlite_set_result_string(context, argv[i], -1);
+ break;
+ }
+ }
+}
+
+/*
+** Implementation of random(). Return a random integer.
+*/
+static void randomFunc(sqlite_func *context, int argc, const char **argv){
+ int r;
+ sqliteRandomness(sizeof(r), &r);
+ sqlite_set_result_int(context, r);
+}
+
+/*
+** Implementation of the last_insert_rowid() SQL function. The return
+** value is the same as the sqlite_last_insert_rowid() API function.
+*/
+static void last_insert_rowid(sqlite_func *context, int arg, const char **argv){
+ sqlite *db = sqlite_user_data(context);
+ sqlite_set_result_int(context, sqlite_last_insert_rowid(db));
+}
+
+/*
+** Implementation of the change_count() SQL function. The return
+** value is the same as the sqlite_changes() API function.
+*/
+static void change_count(sqlite_func *context, int arg, const char **argv){
+ sqlite *db = sqlite_user_data(context);
+ sqlite_set_result_int(context, sqlite_changes(db));
+}
+
+/*
+** Implementation of the last_statement_change_count() SQL function. The
+** return value is the same as the sqlite_last_statement_changes() API function.
+*/
+static void last_statement_change_count(sqlite_func *context, int arg,
+ const char **argv){
+ sqlite *db = sqlite_user_data(context);
+ sqlite_set_result_int(context, sqlite_last_statement_changes(db));
+}
+
+/*
+** Implementation of the like() SQL function. This function implements
+** the build-in LIKE operator. The first argument to the function is the
+** string and the second argument is the pattern. So, the SQL statements:
+**
+** A LIKE B
+**
+** is implemented as like(A,B).
+*/
+static void likeFunc(sqlite_func *context, int arg, const char **argv){
+ if( argv[0]==0 || argv[1]==0 ) return;
+ sqlite_set_result_int(context,
+ sqliteLikeCompare((const unsigned char*)argv[0],
+ (const unsigned char*)argv[1]));
+}
+
+/*
+** Implementation of the glob() SQL function. This function implements
+** the build-in GLOB operator. The first argument to the function is the
+** string and the second argument is the pattern. So, the SQL statements:
+**
+** A GLOB B
+**
+** is implemented as glob(A,B).
+*/
+static void globFunc(sqlite_func *context, int arg, const char **argv){
+ if( argv[0]==0 || argv[1]==0 ) return;
+ sqlite_set_result_int(context,
+ sqliteGlobCompare((const unsigned char*)argv[0],
+ (const unsigned char*)argv[1]));
+}
+
+/*
+** Implementation of the NULLIF(x,y) function. The result is the first
+** argument if the arguments are different. The result is NULL if the
+** arguments are equal to each other.
+*/
+static void nullifFunc(sqlite_func *context, int argc, const char **argv){
+ if( argv[0]!=0 && sqliteCompare(argv[0],argv[1])!=0 ){
+ sqlite_set_result_string(context, argv[0], -1);
+ }
+}
+
+/*
+** Implementation of the VERSION(*) function. The result is the version
+** of the SQLite library that is running.
+*/
+static void versionFunc(sqlite_func *context, int argc, const char **argv){
+ sqlite_set_result_string(context, sqlite_version, -1);
+}
+
+/*
+** EXPERIMENTAL - This is not an official function. The interface may
+** change. This function may disappear. Do not write code that depends
+** on this function.
+**
+** Implementation of the QUOTE() function. This function takes a single
+** argument. If the argument is numeric, the return value is the same as
+** the argument. If the argument is NULL, the return value is the string
+** "NULL". Otherwise, the argument is enclosed in single quotes with
+** single-quote escapes.
+*/
+static void quoteFunc(sqlite_func *context, int argc, const char **argv){
+ if( argc<1 ) return;
+ if( argv[0]==0 ){
+ sqlite_set_result_string(context, "NULL", 4);
+ }else if( sqliteIsNumber(argv[0]) ){
+ sqlite_set_result_string(context, argv[0], -1);
+ }else{
+ int i,j,n;
+ char *z;
+ for(i=n=0; argv[0][i]; i++){ if( argv[0][i]=='\'' ) n++; }
+ z = sqliteMalloc( i+n+3 );
+ if( z==0 ) return;
+ z[0] = '\'';
+ for(i=0, j=1; argv[0][i]; i++){
+ z[j++] = argv[0][i];
+ if( argv[0][i]=='\'' ){
+ z[j++] = '\'';
+ }
+ }
+ z[j++] = '\'';
+ z[j] = 0;
+ sqlite_set_result_string(context, z, j);
+ sqliteFree(z);
+ }
+}
+
+#ifdef SQLITE_SOUNDEX
+/*
+** Compute the soundex encoding of a word.
+*/
+static void soundexFunc(sqlite_func *context, int argc, const char **argv){
+ char zResult[8];
+ const char *zIn;
+ int i, j;
+ static const unsigned char iCode[] = {
+ 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,
+ 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,
+ 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
+ 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
+ 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
+ };
+ assert( argc==1 );
+ zIn = argv[0];
+ for(i=0; zIn[i] && !isalpha(zIn[i]); i++){}
+ if( zIn[i] ){
+ zResult[0] = toupper(zIn[i]);
+ for(j=1; j<4 && zIn[i]; i++){
+ int code = iCode[zIn[i]&0x7f];
+ if( code>0 ){
+ zResult[j++] = code + '0';
+ }
+ }
+ while( j<4 ){
+ zResult[j++] = '0';
+ }
+ zResult[j] = 0;
+ sqlite_set_result_string(context, zResult, 4);
+ }else{
+ sqlite_set_result_string(context, "?000", 4);
+ }
+}
+#endif
+
+#ifdef SQLITE_TEST
+/*
+** This function generates a string of random characters. Used for
+** generating test data.
+*/
+static void randStr(sqlite_func *context, int argc, const char **argv){
+ static const unsigned char zSrc[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ ".-!,:*^+=_|?/<> ";
+ int iMin, iMax, n, r, i;
+ unsigned char zBuf[1000];
+ if( argc>=1 ){
+ iMin = atoi(argv[0]);
+ if( iMin<0 ) iMin = 0;
+ if( iMin>=sizeof(zBuf) ) iMin = sizeof(zBuf)-1;
+ }else{
+ iMin = 1;
+ }
+ if( argc>=2 ){
+ iMax = atoi(argv[1]);
+ if( iMax<iMin ) iMax = iMin;
+ if( iMax>=sizeof(zBuf) ) iMax = sizeof(zBuf)-1;
+ }else{
+ iMax = 50;
+ }
+ n = iMin;
+ if( iMax>iMin ){
+ sqliteRandomness(sizeof(r), &r);
+ r &= 0x7fffffff;
+ n += r%(iMax + 1 - iMin);
+ }
+ assert( n<sizeof(zBuf) );
+ sqliteRandomness(n, zBuf);
+ for(i=0; i<n; i++){
+ zBuf[i] = zSrc[zBuf[i]%(sizeof(zSrc)-1)];
+ }
+ zBuf[n] = 0;
+ sqlite_set_result_string(context, zBuf, n);
+}
+#endif
+
+/*
+** An instance of the following structure holds the context of a
+** sum() or avg() aggregate computation.
+*/
+typedef struct SumCtx SumCtx;
+struct SumCtx {
+ double sum; /* Sum of terms */
+ int cnt; /* Number of elements summed */
+};
+
+/*
+** Routines used to compute the sum or average.
+*/
+static void sumStep(sqlite_func *context, int argc, const char **argv){
+ SumCtx *p;
+ if( argc<1 ) return;
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ if( p && argv[0] ){
+ p->sum += sqliteAtoF(argv[0], 0);
+ p->cnt++;
+ }
+}
+static void sumFinalize(sqlite_func *context){
+ SumCtx *p;
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ sqlite_set_result_double(context, p ? p->sum : 0.0);
+}
+static void avgFinalize(sqlite_func *context){
+ SumCtx *p;
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ if( p && p->cnt>0 ){
+ sqlite_set_result_double(context, p->sum/(double)p->cnt);
+ }
+}
+
+/*
+** An instance of the following structure holds the context of a
+** variance or standard deviation computation.
+*/
+typedef struct StdDevCtx StdDevCtx;
+struct StdDevCtx {
+ double sum; /* Sum of terms */
+ double sum2; /* Sum of the squares of terms */
+ int cnt; /* Number of terms counted */
+};
+
+#if 0 /* Omit because math library is required */
+/*
+** Routines used to compute the standard deviation as an aggregate.
+*/
+static void stdDevStep(sqlite_func *context, int argc, const char **argv){
+ StdDevCtx *p;
+ double x;
+ if( argc<1 ) return;
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ if( p && argv[0] ){
+ x = sqliteAtoF(argv[0], 0);
+ p->sum += x;
+ p->sum2 += x*x;
+ p->cnt++;
+ }
+}
+static void stdDevFinalize(sqlite_func *context){
+ double rN = sqlite_aggregate_count(context);
+ StdDevCtx *p = sqlite_aggregate_context(context, sizeof(*p));
+ if( p && p->cnt>1 ){
+ double rCnt = cnt;
+ sqlite_set_result_double(context,
+ sqrt((p->sum2 - p->sum*p->sum/rCnt)/(rCnt-1.0)));
+ }
+}
+#endif
+
+/*
+** The following structure keeps track of state information for the
+** count() aggregate function.
+*/
+typedef struct CountCtx CountCtx;
+struct CountCtx {
+ int n;
+};
+
+/*
+** Routines to implement the count() aggregate function.
+*/
+static void countStep(sqlite_func *context, int argc, const char **argv){
+ CountCtx *p;
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ if( (argc==0 || argv[0]) && p ){
+ p->n++;
+ }
+}
+static void countFinalize(sqlite_func *context){
+ CountCtx *p;
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ sqlite_set_result_int(context, p ? p->n : 0);
+}
+
+/*
+** This function tracks state information for the min() and max()
+** aggregate functions.
+*/
+typedef struct MinMaxCtx MinMaxCtx;
+struct MinMaxCtx {
+ char *z; /* The best so far */
+ char zBuf[28]; /* Space that can be used for storage */
+};
+
+/*
+** Routines to implement min() and max() aggregate functions.
+*/
+static void minmaxStep(sqlite_func *context, int argc, const char **argv){
+ MinMaxCtx *p;
+ int (*xCompare)(const char*, const char*);
+ int mask; /* 0 for min() or 0xffffffff for max() */
+
+ assert( argc==2 );
+ if( argv[0]==0 ) return; /* Ignore NULL values */
+ if( argv[1][0]=='n' ){
+ xCompare = sqliteCompare;
+ }else{
+ xCompare = strcmp;
+ }
+ mask = (int)sqlite_user_data(context);
+ assert( mask==0 || mask==-1 );
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ if( p==0 || argc<1 ) return;
+ if( p->z==0 || (xCompare(argv[0],p->z)^mask)<0 ){
+ int len;
+ if( p->zBuf[0] ){
+ sqliteFree(p->z);
+ }
+ len = strlen(argv[0]);
+ if( len < sizeof(p->zBuf)-1 ){
+ p->z = &p->zBuf[1];
+ p->zBuf[0] = 0;
+ }else{
+ p->z = sqliteMalloc( len+1 );
+ p->zBuf[0] = 1;
+ if( p->z==0 ) return;
+ }
+ strcpy(p->z, argv[0]);
+ }
+}
+static void minMaxFinalize(sqlite_func *context){
+ MinMaxCtx *p;
+ p = sqlite_aggregate_context(context, sizeof(*p));
+ if( p && p->z && p->zBuf[0]<2 ){
+ sqlite_set_result_string(context, p->z, strlen(p->z));
+ }
+ if( p && p->zBuf[0] ){
+ sqliteFree(p->z);
+ }
+}
+
+/*
+** This function registered all of the above C functions as SQL
+** functions. This should be the only routine in this file with
+** external linkage.
+*/
+void sqliteRegisterBuiltinFunctions(sqlite *db){
+ static struct {
+ char *zName;
+ signed char nArg;
+ signed char dataType;
+ u8 argType; /* 0: none. 1: db 2: (-1) */
+ void (*xFunc)(sqlite_func*,int,const char**);
+ } aFuncs[] = {
+ { "min", -1, SQLITE_ARGS, 0, minmaxFunc },
+ { "min", 0, 0, 0, 0 },
+ { "max", -1, SQLITE_ARGS, 2, minmaxFunc },
+ { "max", 0, 0, 2, 0 },
+ { "typeof", 1, SQLITE_TEXT, 0, typeofFunc },
+ { "length", 1, SQLITE_NUMERIC, 0, lengthFunc },
+ { "substr", 3, SQLITE_TEXT, 0, substrFunc },
+ { "abs", 1, SQLITE_NUMERIC, 0, absFunc },
+ { "round", 1, SQLITE_NUMERIC, 0, roundFunc },
+ { "round", 2, SQLITE_NUMERIC, 0, roundFunc },
+ { "upper", 1, SQLITE_TEXT, 0, upperFunc },
+ { "lower", 1, SQLITE_TEXT, 0, lowerFunc },
+ { "coalesce", -1, SQLITE_ARGS, 0, ifnullFunc },
+ { "coalesce", 0, 0, 0, 0 },
+ { "coalesce", 1, 0, 0, 0 },
+ { "ifnull", 2, SQLITE_ARGS, 0, ifnullFunc },
+ { "random", -1, SQLITE_NUMERIC, 0, randomFunc },
+ { "like", 2, SQLITE_NUMERIC, 0, likeFunc },
+ { "glob", 2, SQLITE_NUMERIC, 0, globFunc },
+ { "nullif", 2, SQLITE_ARGS, 0, nullifFunc },
+ { "sqlite_version",0,SQLITE_TEXT, 0, versionFunc},
+ { "quote", 1, SQLITE_ARGS, 0, quoteFunc },
+ { "last_insert_rowid", 0, SQLITE_NUMERIC, 1, last_insert_rowid },
+ { "change_count", 0, SQLITE_NUMERIC, 1, change_count },
+ { "last_statement_change_count",
+ 0, SQLITE_NUMERIC, 1, last_statement_change_count },
+#ifdef SQLITE_SOUNDEX
+ { "soundex", 1, SQLITE_TEXT, 0, soundexFunc},
+#endif
+#ifdef SQLITE_TEST
+ { "randstr", 2, SQLITE_TEXT, 0, randStr },
+#endif
+ };
+ static struct {
+ char *zName;
+ signed char nArg;
+ signed char dataType;
+ u8 argType;
+ void (*xStep)(sqlite_func*,int,const char**);
+ void (*xFinalize)(sqlite_func*);
+ } aAggs[] = {
+ { "min", 1, 0, 0, minmaxStep, minMaxFinalize },
+ { "max", 1, 0, 2, minmaxStep, minMaxFinalize },
+ { "sum", 1, SQLITE_NUMERIC, 0, sumStep, sumFinalize },
+ { "avg", 1, SQLITE_NUMERIC, 0, sumStep, avgFinalize },
+ { "count", 0, SQLITE_NUMERIC, 0, countStep, countFinalize },
+ { "count", 1, SQLITE_NUMERIC, 0, countStep, countFinalize },
+#if 0
+ { "stddev", 1, SQLITE_NUMERIC, 0, stdDevStep, stdDevFinalize },
+#endif
+ };
+ static const char *azTypeFuncs[] = { "min", "max", "typeof" };
+ int i;
+
+ for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+ void *pArg;
+ switch( aFuncs[i].argType ){
+ case 0: pArg = 0; break;
+ case 1: pArg = db; break;
+ case 2: pArg = (void*)(-1); break;
+ }
+ sqlite_create_function(db, aFuncs[i].zName,
+ aFuncs[i].nArg, aFuncs[i].xFunc, pArg);
+ if( aFuncs[i].xFunc ){
+ sqlite_function_type(db, aFuncs[i].zName, aFuncs[i].dataType);
+ }
+ }
+ for(i=0; i<sizeof(aAggs)/sizeof(aAggs[0]); i++){
+ void *pArg;
+ switch( aAggs[i].argType ){
+ case 0: pArg = 0; break;
+ case 1: pArg = db; break;
+ case 2: pArg = (void*)(-1); break;
+ }
+ sqlite_create_aggregate(db, aAggs[i].zName,
+ aAggs[i].nArg, aAggs[i].xStep, aAggs[i].xFinalize, pArg);
+ sqlite_function_type(db, aAggs[i].zName, aAggs[i].dataType);
+ }
+ for(i=0; i<sizeof(azTypeFuncs)/sizeof(azTypeFuncs[0]); i++){
+ int n = strlen(azTypeFuncs[i]);
+ FuncDef *p = sqliteHashFind(&db->aFunc, azTypeFuncs[i], n);
+ while( p ){
+ p->includeTypes = 1;
+ p = p->pNext;
+ }
+ }
+ sqliteRegisterDateTimeFunctions(db);
+}
diff --git a/src/libs/sqlite2/hash.c b/src/libs/sqlite2/hash.c
new file mode 100644
index 00000000..e0137cb3
--- /dev/null
+++ b/src/libs/sqlite2/hash.c
@@ -0,0 +1,356 @@
+/*
+** 2001 September 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the implementation of generic hash-tables
+** used in SQLite.
+**
+** $Id: hash.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+#include <assert.h>
+
+/* Turn bulk memory into a hash table object by initializing the
+** fields of the Hash structure.
+**
+** "new" is a pointer to the hash table that is to be initialized.
+** keyClass is one of the constants SQLITE_HASH_INT, SQLITE_HASH_POINTER,
+** SQLITE_HASH_BINARY, or SQLITE_HASH_STRING. The value of keyClass
+** determines what kind of key the hash table will use. "copyKey" is
+** true if the hash table should make its own private copy of keys and
+** false if it should just use the supplied pointer. CopyKey only makes
+** sense for SQLITE_HASH_STRING and SQLITE_HASH_BINARY and is ignored
+** for other key classes.
+*/
+void sqliteHashInit(Hash *new, int keyClass, int copyKey){
+ assert( new!=0 );
+ assert( keyClass>=SQLITE_HASH_INT && keyClass<=SQLITE_HASH_BINARY );
+ new->keyClass = keyClass;
+ new->copyKey = copyKey &&
+ (keyClass==SQLITE_HASH_STRING || keyClass==SQLITE_HASH_BINARY);
+ new->first = 0;
+ new->count = 0;
+ new->htsize = 0;
+ new->ht = 0;
+}
+
+/* Remove all entries from a hash table. Reclaim all memory.
+** Call this routine to delete a hash table or to reset a hash table
+** to the empty state.
+*/
+void sqliteHashClear(Hash *pH){
+ HashElem *elem; /* For looping over all elements of the table */
+
+ assert( pH!=0 );
+ elem = pH->first;
+ pH->first = 0;
+ if( pH->ht ) sqliteFree(pH->ht);
+ pH->ht = 0;
+ pH->htsize = 0;
+ while( elem ){
+ HashElem *next_elem = elem->next;
+ if( pH->copyKey && elem->pKey ){
+ sqliteFree(elem->pKey);
+ }
+ sqliteFree(elem);
+ elem = next_elem;
+ }
+ pH->count = 0;
+}
+
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_INT
+*/
+static int intHash(const void *pKey, int nKey){
+ return nKey ^ (nKey<<8) ^ (nKey>>8);
+}
+static int intCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ return n2 - n1;
+}
+
+#if 0 /* NOT USED */
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_POINTER
+*/
+static int ptrHash(const void *pKey, int nKey){
+ uptr x = Addr(pKey);
+ return x ^ (x<<8) ^ (x>>8);
+}
+static int ptrCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( pKey1==pKey2 ) return 0;
+ if( pKey1<pKey2 ) return -1;
+ return 1;
+}
+#endif
+
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_STRING
+*/
+static int strHash(const void *pKey, int nKey){
+ return sqliteHashNoCase((const char*)pKey, nKey);
+}
+static int strCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( n1!=n2 ) return n2-n1;
+ return sqliteStrNICmp((const char*)pKey1,(const char*)pKey2,n1);
+}
+
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_BINARY
+*/
+static int binHash(const void *pKey, int nKey){
+ int h = 0;
+ const char *z = (const char *)pKey;
+ while( nKey-- > 0 ){
+ h = (h<<3) ^ h ^ *(z++);
+ }
+ return h & 0x7fffffff;
+}
+static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( n1!=n2 ) return n2-n1;
+ return memcmp(pKey1,pKey2,n1);
+}
+
+/*
+** Return a pointer to the appropriate hash function given the key class.
+**
+** The C syntax in this function definition may be unfamilar to some
+** programmers, so we provide the following additional explanation:
+**
+** The name of the function is "hashFunction". The function takes a
+** single parameter "keyClass". The return value of hashFunction()
+** is a pointer to another function. Specifically, the return value
+** of hashFunction() is a pointer to a function that takes two parameters
+** with types "const void*" and "int" and returns an "int".
+*/
+static int (*hashFunction(int keyClass))(const void*,int){
+ switch( keyClass ){
+ case SQLITE_HASH_INT: return &intHash;
+ /* case SQLITE_HASH_POINTER: return &ptrHash; // NOT USED */
+ case SQLITE_HASH_STRING: return &strHash;
+ case SQLITE_HASH_BINARY: return &binHash;;
+ default: break;
+ }
+ return 0;
+}
+
+/*
+** Return a pointer to the appropriate hash function given the key class.
+**
+** For help in interpreted the obscure C code in the function definition,
+** see the header comment on the previous function.
+*/
+static int (*compareFunction(int keyClass))(const void*,int,const void*,int){
+ switch( keyClass ){
+ case SQLITE_HASH_INT: return &intCompare;
+ /* case SQLITE_HASH_POINTER: return &ptrCompare; // NOT USED */
+ case SQLITE_HASH_STRING: return &strCompare;
+ case SQLITE_HASH_BINARY: return &binCompare;
+ default: break;
+ }
+ return 0;
+}
+
+
+/* Resize the hash table so that it cantains "new_size" buckets.
+** "new_size" must be a power of 2. The hash table might fail
+** to resize if sqliteMalloc() fails.
+*/
+static void rehash(Hash *pH, int new_size){
+ struct _ht *new_ht; /* The new hash table */
+ HashElem *elem, *next_elem; /* For looping over existing elements */
+ HashElem *x; /* Element being copied to new hash table */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ assert( (new_size & (new_size-1))==0 );
+ new_ht = (struct _ht *)sqliteMalloc( new_size*sizeof(struct _ht) );
+ if( new_ht==0 ) return;
+ if( pH->ht ) sqliteFree(pH->ht);
+ pH->ht = new_ht;
+ pH->htsize = new_size;
+ xHash = hashFunction(pH->keyClass);
+ for(elem=pH->first, pH->first=0; elem; elem = next_elem){
+ int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1);
+ next_elem = elem->next;
+ x = new_ht[h].chain;
+ if( x ){
+ elem->next = x;
+ elem->prev = x->prev;
+ if( x->prev ) x->prev->next = elem;
+ else pH->first = elem;
+ x->prev = elem;
+ }else{
+ elem->next = pH->first;
+ if( pH->first ) pH->first->prev = elem;
+ elem->prev = 0;
+ pH->first = elem;
+ }
+ new_ht[h].chain = elem;
+ new_ht[h].count++;
+ }
+}
+
+/* This function (for internal use only) locates an element in an
+** hash table that matches the given key. The hash for this key has
+** already been computed and is passed as the 4th parameter.
+*/
+static HashElem *findElementGivenHash(
+ const Hash *pH, /* The pH to be searched */
+ const void *pKey, /* The key we are searching for */
+ int nKey,
+ int h /* The hash for this key. */
+){
+ HashElem *elem; /* Used to loop thru the element list */
+ int count; /* Number of elements left to test */
+ int (*xCompare)(const void*,int,const void*,int); /* comparison function */
+
+ if( pH->ht ){
+ elem = pH->ht[h].chain;
+ count = pH->ht[h].count;
+ xCompare = compareFunction(pH->keyClass);
+ while( count-- && elem ){
+ if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){
+ return elem;
+ }
+ elem = elem->next;
+ }
+ }
+ return 0;
+}
+
+/* Remove a single entry from the hash table given a pointer to that
+** element and a hash on the element's key.
+*/
+static void removeElementGivenHash(
+ Hash *pH, /* The pH containing "elem" */
+ HashElem* elem, /* The element to be removed from the pH */
+ int h /* Hash value for the element */
+){
+ if( elem->prev ){
+ elem->prev->next = elem->next;
+ }else{
+ pH->first = elem->next;
+ }
+ if( elem->next ){
+ elem->next->prev = elem->prev;
+ }
+ if( pH->ht[h].chain==elem ){
+ pH->ht[h].chain = elem->next;
+ }
+ pH->ht[h].count--;
+ if( pH->ht[h].count<=0 ){
+ pH->ht[h].chain = 0;
+ }
+ if( pH->copyKey && elem->pKey ){
+ sqliteFree(elem->pKey);
+ }
+ sqliteFree( elem );
+ pH->count--;
+}
+
+/* Attempt to locate an element of the hash table pH with a key
+** that matches pKey,nKey. Return the data for this element if it is
+** found, or NULL if there is no match.
+*/
+void *sqliteHashFind(const Hash *pH, const void *pKey, int nKey){
+ int h; /* A hash on key */
+ HashElem *elem; /* The element that matches key */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ if( pH==0 || pH->ht==0 ) return 0;
+ xHash = hashFunction(pH->keyClass);
+ assert( xHash!=0 );
+ h = (*xHash)(pKey,nKey);
+ assert( (pH->htsize & (pH->htsize-1))==0 );
+ elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1));
+ return elem ? elem->data : 0;
+}
+
+/* Insert an element into the hash table pH. The key is pKey,nKey
+** and the data is "data".
+**
+** If no element exists with a matching key, then a new
+** element is created. A copy of the key is made if the copyKey
+** flag is set. NULL is returned.
+**
+** If another element already exists with the same key, then the
+** new data replaces the old data and the old data is returned.
+** The key is not copied in this instance. If a malloc fails, then
+** the new data is returned and the hash table is unchanged.
+**
+** If the "data" parameter to this function is NULL, then the
+** element corresponding to "key" is removed from the hash table.
+*/
+void *sqliteHashInsert(Hash *pH, const void *pKey, int nKey, void *data){
+ int hraw; /* Raw hash value of the key */
+ int h; /* the hash of the key modulo hash table size */
+ HashElem *elem; /* Used to loop thru the element list */
+ HashElem *new_elem; /* New element added to the pH */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ assert( pH!=0 );
+ xHash = hashFunction(pH->keyClass);
+ assert( xHash!=0 );
+ hraw = (*xHash)(pKey, nKey);
+ assert( (pH->htsize & (pH->htsize-1))==0 );
+ h = hraw & (pH->htsize-1);
+ elem = findElementGivenHash(pH,pKey,nKey,h);
+ if( elem ){
+ void *old_data = elem->data;
+ if( data==0 ){
+ removeElementGivenHash(pH,elem,h);
+ }else{
+ elem->data = data;
+ }
+ return old_data;
+ }
+ if( data==0 ) return 0;
+ new_elem = (HashElem*)sqliteMalloc( sizeof(HashElem) );
+ if( new_elem==0 ) return data;
+ if( pH->copyKey && pKey!=0 ){
+ new_elem->pKey = sqliteMallocRaw( nKey );
+ if( new_elem->pKey==0 ){
+ sqliteFree(new_elem);
+ return data;
+ }
+ memcpy((void*)new_elem->pKey, pKey, nKey);
+ }else{
+ new_elem->pKey = (void*)pKey;
+ }
+ new_elem->nKey = nKey;
+ pH->count++;
+ if( pH->htsize==0 ) rehash(pH,8);
+ if( pH->htsize==0 ){
+ pH->count = 0;
+ sqliteFree(new_elem);
+ return data;
+ }
+ if( pH->count > pH->htsize ){
+ rehash(pH,pH->htsize*2);
+ }
+ assert( (pH->htsize & (pH->htsize-1))==0 );
+ h = hraw & (pH->htsize-1);
+ elem = pH->ht[h].chain;
+ if( elem ){
+ new_elem->next = elem;
+ new_elem->prev = elem->prev;
+ if( elem->prev ){ elem->prev->next = new_elem; }
+ else { pH->first = new_elem; }
+ elem->prev = new_elem;
+ }else{
+ new_elem->next = pH->first;
+ new_elem->prev = 0;
+ if( pH->first ){ pH->first->prev = new_elem; }
+ pH->first = new_elem;
+ }
+ pH->ht[h].count++;
+ pH->ht[h].chain = new_elem;
+ new_elem->data = data;
+ return 0;
+}
diff --git a/src/libs/sqlite2/hash.h b/src/libs/sqlite2/hash.h
new file mode 100644
index 00000000..27dd30dc
--- /dev/null
+++ b/src/libs/sqlite2/hash.h
@@ -0,0 +1,109 @@
+/*
+** 2001 September 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the header file for the generic hash-table implemenation
+** used in SQLite.
+**
+** $Id: hash.h 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#ifndef _SQLITE_HASH_H_
+#define _SQLITE_HASH_H_
+
+/* Forward declarations of structures. */
+typedef struct Hash Hash;
+typedef struct HashElem HashElem;
+
+/* A complete hash table is an instance of the following structure.
+** The internals of this structure are intended to be opaque -- client
+** code should not attempt to access or modify the fields of this structure
+** directly. Change this structure only by using the routines below.
+** However, many of the "procedures" and "functions" for modifying and
+** accessing this structure are really macros, so we can't really make
+** this structure opaque.
+*/
+struct Hash {
+ char keyClass; /* SQLITE_HASH_INT, _POINTER, _STRING, _BINARY */
+ char copyKey; /* True if copy of key made on insert */
+ int count; /* Number of entries in this table */
+ HashElem *first; /* The first element of the array */
+ int htsize; /* Number of buckets in the hash table */
+ struct _ht { /* the hash table */
+ int count; /* Number of entries with this hash */
+ HashElem *chain; /* Pointer to first entry with this hash */
+ } *ht;
+};
+
+/* Each element in the hash table is an instance of the following
+** structure. All elements are stored on a single doubly-linked list.
+**
+** Again, this structure is intended to be opaque, but it can't really
+** be opaque because it is used by macros.
+*/
+struct HashElem {
+ HashElem *next, *prev; /* Next and previous elements in the table */
+ void *data; /* Data associated with this element */
+ void *pKey; int nKey; /* Key associated with this element */
+};
+
+/*
+** There are 4 different modes of operation for a hash table:
+**
+** SQLITE_HASH_INT nKey is used as the key and pKey is ignored.
+**
+** SQLITE_HASH_POINTER pKey is used as the key and nKey is ignored.
+**
+** SQLITE_HASH_STRING pKey points to a string that is nKey bytes long
+** (including the null-terminator, if any). Case
+** is ignored in comparisons.
+**
+** SQLITE_HASH_BINARY pKey points to binary data nKey bytes long.
+** memcmp() is used to compare keys.
+**
+** A copy of the key is made for SQLITE_HASH_STRING and SQLITE_HASH_BINARY
+** if the copyKey parameter to HashInit is 1.
+*/
+#define SQLITE_HASH_INT 1
+/* #define SQLITE_HASH_POINTER 2 // NOT USED */
+#define SQLITE_HASH_STRING 3
+#define SQLITE_HASH_BINARY 4
+
+/*
+** Access routines. To delete, insert a NULL pointer.
+*/
+void sqliteHashInit(Hash*, int keytype, int copyKey);
+void *sqliteHashInsert(Hash*, const void *pKey, int nKey, void *pData);
+void *sqliteHashFind(const Hash*, const void *pKey, int nKey);
+void sqliteHashClear(Hash*);
+
+/*
+** Macros for looping over all elements of a hash table. The idiom is
+** like this:
+**
+** Hash h;
+** HashElem *p;
+** ...
+** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){
+** SomeStructure *pData = sqliteHashData(p);
+** // do something with pData
+** }
+*/
+#define sqliteHashFirst(H) ((H)->first)
+#define sqliteHashNext(E) ((E)->next)
+#define sqliteHashData(E) ((E)->data)
+#define sqliteHashKey(E) ((E)->pKey)
+#define sqliteHashKeysize(E) ((E)->nKey)
+
+/*
+** Number of entries in a hash table
+*/
+#define sqliteHashCount(H) ((H)->count)
+
+#endif /* _SQLITE_HASH_H_ */
diff --git a/src/libs/sqlite2/insert.c b/src/libs/sqlite2/insert.c
new file mode 100644
index 00000000..2f73db4a
--- /dev/null
+++ b/src/libs/sqlite2/insert.c
@@ -0,0 +1,919 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** to handle INSERT statements in SQLite.
+**
+** $Id: insert.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+
+/*
+** This routine is call to handle SQL of the following forms:
+**
+** insert into TABLE (IDLIST) values(EXPRLIST)
+** insert into TABLE (IDLIST) select
+**
+** The IDLIST following the table name is always optional. If omitted,
+** then a list of all columns for the table is substituted. The IDLIST
+** appears in the pColumn parameter. pColumn is NULL if IDLIST is omitted.
+**
+** The pList parameter holds EXPRLIST in the first form of the INSERT
+** statement above, and pSelect is NULL. For the second form, pList is
+** NULL and pSelect is a pointer to the select statement used to generate
+** data for the insert.
+**
+** The code generated follows one of three templates. For a simple
+** select with data coming from a VALUES clause, the code executes
+** once straight down through. The template looks like this:
+**
+** open write cursor to <table> and its indices
+** puts VALUES clause expressions onto the stack
+** write the resulting record into <table>
+** cleanup
+**
+** If the statement is of the form
+**
+** INSERT INTO <table> SELECT ...
+**
+** And the SELECT clause does not read from <table> at any time, then
+** the generated code follows this template:
+**
+** goto B
+** A: setup for the SELECT
+** loop over the tables in the SELECT
+** gosub C
+** end loop
+** cleanup after the SELECT
+** goto D
+** B: open write cursor to <table> and its indices
+** goto A
+** C: insert the select result into <table>
+** return
+** D: cleanup
+**
+** The third template is used if the insert statement takes its
+** values from a SELECT but the data is being inserted into a table
+** that is also read as part of the SELECT. In the third form,
+** we have to use a intermediate table to store the results of
+** the select. The template is like this:
+**
+** goto B
+** A: setup for the SELECT
+** loop over the tables in the SELECT
+** gosub C
+** end loop
+** cleanup after the SELECT
+** goto D
+** C: insert the select result into the intermediate table
+** return
+** B: open a cursor to an intermediate table
+** goto A
+** D: open write cursor to <table> and its indices
+** loop over the intermediate table
+** transfer values form intermediate table into <table>
+** end the loop
+** cleanup
+*/
+void sqliteInsert(
+ Parse *pParse, /* Parser context */
+ SrcList *pTabList, /* Name of table into which we are inserting */
+ ExprList *pList, /* List of values to be inserted */
+ Select *pSelect, /* A SELECT statement to use as the data source */
+ IdList *pColumn, /* Column names corresponding to IDLIST. */
+ int onError /* How to handle constraint errors */
+){
+ Table *pTab; /* The table to insert into */
+ char *zTab; /* Name of the table into which we are inserting */
+ const char *zDb; /* Name of the database holding this table */
+ int i, j, idx; /* Loop counters */
+ Vdbe *v; /* Generate code into this virtual machine */
+ Index *pIdx; /* For looping over indices of the table */
+ int nColumn; /* Number of columns in the data */
+ int base; /* VDBE Cursor number for pTab */
+ int iCont, iBreak; /* Beginning and end of the loop over srcTab */
+ sqlite *db; /* The main database structure */
+ int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */
+ int endOfLoop; /* Label for the end of the insertion loop */
+ int useTempTable; /* Store SELECT results in intermediate table */
+ int srcTab; /* Data comes from this temporary cursor if >=0 */
+ int iSelectLoop; /* Address of code that implements the SELECT */
+ int iCleanup; /* Address of the cleanup code */
+ int iInsertBlock; /* Address of the subroutine used to insert data */
+ int iCntMem; /* Memory cell used for the row counter */
+ int isView; /* True if attempting to insert into a view */
+
+ int row_triggers_exist = 0; /* True if there are FOR EACH ROW triggers */
+ int before_triggers; /* True if there are BEFORE triggers */
+ int after_triggers; /* True if there are AFTER triggers */
+ int newIdx = -1; /* Cursor for the NEW table */
+
+ if( pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup;
+ db = pParse->db;
+
+ /* Locate the table into which we will be inserting new information.
+ */
+ assert( pTabList->nSrc==1 );
+ zTab = pTabList->a[0].zName;
+ if( zTab==0 ) goto insert_cleanup;
+ pTab = sqliteSrcListLookup(pParse, pTabList);
+ if( pTab==0 ){
+ goto insert_cleanup;
+ }
+ assert( pTab->iDb<db->nDb );
+ zDb = db->aDb[pTab->iDb].zName;
+ if( sqliteAuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){
+ goto insert_cleanup;
+ }
+
+ /* Ensure that:
+ * (a) the table is not read-only,
+ * (b) that if it is a view then ON INSERT triggers exist
+ */
+ before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT,
+ TK_BEFORE, TK_ROW, 0);
+ after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT,
+ TK_AFTER, TK_ROW, 0);
+ row_triggers_exist = before_triggers || after_triggers;
+ isView = pTab->pSelect!=0;
+ if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){
+ goto insert_cleanup;
+ }
+ if( pTab==0 ) goto insert_cleanup;
+
+ /* If pTab is really a view, make sure it has been initialized.
+ */
+ if( isView && sqliteViewGetColumnNames(pParse, pTab) ){
+ goto insert_cleanup;
+ }
+
+ /* Allocate a VDBE
+ */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) goto insert_cleanup;
+ sqliteBeginWriteOperation(pParse, pSelect || row_triggers_exist, pTab->iDb);
+
+ /* if there are row triggers, allocate a temp table for new.* references. */
+ if( row_triggers_exist ){
+ newIdx = pParse->nTab++;
+ }
+
+ /* Figure out how many columns of data are supplied. If the data
+ ** is coming from a SELECT statement, then this step also generates
+ ** all the code to implement the SELECT statement and invoke a subroutine
+ ** to process each row of the result. (Template 2.) If the SELECT
+ ** statement uses the the table that is being inserted into, then the
+ ** subroutine is also coded here. That subroutine stores the SELECT
+ ** results in a temporary table. (Template 3.)
+ */
+ if( pSelect ){
+ /* Data is coming from a SELECT. Generate code to implement that SELECT
+ */
+ int rc, iInitCode;
+ iInitCode = sqliteVdbeAddOp(v, OP_Goto, 0, 0);
+ iSelectLoop = sqliteVdbeCurrentAddr(v);
+ iInsertBlock = sqliteVdbeMakeLabel(v);
+ rc = sqliteSelect(pParse, pSelect, SRT_Subroutine, iInsertBlock, 0,0,0);
+ if( rc || pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup;
+ iCleanup = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_Goto, 0, iCleanup);
+ assert( pSelect->pEList );
+ nColumn = pSelect->pEList->nExpr;
+
+ /* Set useTempTable to TRUE if the result of the SELECT statement
+ ** should be written into a temporary table. Set to FALSE if each
+ ** row of the SELECT can be written directly into the result table.
+ **
+ ** A temp table must be used if the table being updated is also one
+ ** of the tables being read by the SELECT statement. Also use a
+ ** temp table in the case of row triggers.
+ */
+ if( row_triggers_exist ){
+ useTempTable = 1;
+ }else{
+ int addr = sqliteVdbeFindOp(v, OP_OpenRead, pTab->tnum);
+ useTempTable = 0;
+ if( addr>0 ){
+ VdbeOp *pOp = sqliteVdbeGetOp(v, addr-2);
+ if( pOp->opcode==OP_Integer && pOp->p1==pTab->iDb ){
+ useTempTable = 1;
+ }
+ }
+ }
+
+ if( useTempTable ){
+ /* Generate the subroutine that SELECT calls to process each row of
+ ** the result. Store the result in a temporary table
+ */
+ srcTab = pParse->nTab++;
+ sqliteVdbeResolveLabel(v, iInsertBlock);
+ sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ sqliteVdbeAddOp(v, OP_NewRecno, srcTab, 0);
+ sqliteVdbeAddOp(v, OP_Pull, 1, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, srcTab, 0);
+ sqliteVdbeAddOp(v, OP_Return, 0, 0);
+
+ /* The following code runs first because the GOTO at the very top
+ ** of the program jumps to it. Create the temporary table, then jump
+ ** back up and execute the SELECT code above.
+ */
+ sqliteVdbeChangeP2(v, iInitCode, sqliteVdbeCurrentAddr(v));
+ sqliteVdbeAddOp(v, OP_OpenTemp, srcTab, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, iSelectLoop);
+ sqliteVdbeResolveLabel(v, iCleanup);
+ }else{
+ sqliteVdbeChangeP2(v, iInitCode, sqliteVdbeCurrentAddr(v));
+ }
+ }else{
+ /* This is the case if the data for the INSERT is coming from a VALUES
+ ** clause
+ */
+ SrcList dummy;
+ assert( pList!=0 );
+ srcTab = -1;
+ useTempTable = 0;
+ assert( pList );
+ nColumn = pList->nExpr;
+ dummy.nSrc = 0;
+ for(i=0; i<nColumn; i++){
+ if( sqliteExprResolveIds(pParse, &dummy, 0, pList->a[i].pExpr) ){
+ goto insert_cleanup;
+ }
+ if( sqliteExprCheck(pParse, pList->a[i].pExpr, 0, 0) ){
+ goto insert_cleanup;
+ }
+ }
+ }
+
+ /* Make sure the number of columns in the source data matches the number
+ ** of columns to be inserted into the table.
+ */
+ if( pColumn==0 && nColumn!=pTab->nCol ){
+ sqliteErrorMsg(pParse,
+ "table %S has %d columns but %d values were supplied",
+ pTabList, 0, pTab->nCol, nColumn);
+ goto insert_cleanup;
+ }
+ if( pColumn!=0 && nColumn!=pColumn->nId ){
+ sqliteErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId);
+ goto insert_cleanup;
+ }
+
+ /* If the INSERT statement included an IDLIST term, then make sure
+ ** all elements of the IDLIST really are columns of the table and
+ ** remember the column indices.
+ **
+ ** If the table has an INTEGER PRIMARY KEY column and that column
+ ** is named in the IDLIST, then record in the keyColumn variable
+ ** the index into IDLIST of the primary key column. keyColumn is
+ ** the index of the primary key as it appears in IDLIST, not as
+ ** is appears in the original table. (The index of the primary
+ ** key in the original table is pTab->iPKey.)
+ */
+ if( pColumn ){
+ for(i=0; i<pColumn->nId; i++){
+ pColumn->a[i].idx = -1;
+ }
+ for(i=0; i<pColumn->nId; i++){
+ for(j=0; j<pTab->nCol; j++){
+ if( sqliteStrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){
+ pColumn->a[i].idx = j;
+ if( j==pTab->iPKey ){
+ keyColumn = i;
+ }
+ break;
+ }
+ }
+ if( j>=pTab->nCol ){
+ if( sqliteIsRowid(pColumn->a[i].zName) ){
+ keyColumn = i;
+ }else{
+ sqliteErrorMsg(pParse, "table %S has no column named %s",
+ pTabList, 0, pColumn->a[i].zName);
+ pParse->nErr++;
+ goto insert_cleanup;
+ }
+ }
+ }
+ }
+
+ /* If there is no IDLIST term but the table has an integer primary
+ ** key, the set the keyColumn variable to the primary key column index
+ ** in the original table definition.
+ */
+ if( pColumn==0 ){
+ keyColumn = pTab->iPKey;
+ }
+
+ /* Open the temp table for FOR EACH ROW triggers
+ */
+ if( row_triggers_exist ){
+ sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0);
+ }
+
+ /* Initialize the count of rows to be inserted
+ */
+ if( db->flags & SQLITE_CountRows ){
+ iCntMem = pParse->nMem++;
+ sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, iCntMem, 1);
+ }
+
+ /* Open tables and indices if there are no row triggers */
+ if( !row_triggers_exist ){
+ base = pParse->nTab;
+ idx = sqliteOpenTableAndIndices(pParse, pTab, base);
+ pParse->nTab += idx;
+ }
+
+ /* If the data source is a temporary table, then we have to create
+ ** a loop because there might be multiple rows of data. If the data
+ ** source is a subroutine call from the SELECT statement, then we need
+ ** to launch the SELECT statement processing.
+ */
+ if( useTempTable ){
+ iBreak = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_Rewind, srcTab, iBreak);
+ iCont = sqliteVdbeCurrentAddr(v);
+ }else if( pSelect ){
+ sqliteVdbeAddOp(v, OP_Goto, 0, iSelectLoop);
+ sqliteVdbeResolveLabel(v, iInsertBlock);
+ }
+
+ /* Run the BEFORE and INSTEAD OF triggers, if there are any
+ */
+ endOfLoop = sqliteVdbeMakeLabel(v);
+ if( before_triggers ){
+
+ /* build the NEW.* reference row. Note that if there is an INTEGER
+ ** PRIMARY KEY into which a NULL is being inserted, that NULL will be
+ ** translated into a unique ID for the row. But on a BEFORE trigger,
+ ** we do not know what the unique ID will be (because the insert has
+ ** not happened yet) so we substitute a rowid of -1
+ */
+ if( keyColumn<0 ){
+ sqliteVdbeAddOp(v, OP_Integer, -1, 0);
+ }else if( useTempTable ){
+ sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn);
+ }else if( pSelect ){
+ sqliteVdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1);
+ }else{
+ sqliteExprCode(pParse, pList->a[keyColumn].pExpr);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Integer, -1, 0);
+ sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
+ }
+
+ /* Create the new column data
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( pColumn==0 ){
+ j = i;
+ }else{
+ for(j=0; j<pColumn->nId; j++){
+ if( pColumn->a[j].idx==i ) break;
+ }
+ }
+ if( pColumn && j>=pColumn->nId ){
+ sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zDflt, P3_STATIC);
+ }else if( useTempTable ){
+ sqliteVdbeAddOp(v, OP_Column, srcTab, j);
+ }else if( pSelect ){
+ sqliteVdbeAddOp(v, OP_Dup, nColumn-j-1, 1);
+ }else{
+ sqliteExprCode(pParse, pList->a[j].pExpr);
+ }
+ }
+ sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0);
+
+ /* Fire BEFORE or INSTEAD OF triggers */
+ if( sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_BEFORE, pTab,
+ newIdx, -1, onError, endOfLoop) ){
+ goto insert_cleanup;
+ }
+ }
+
+ /* If any triggers exists, the opening of tables and indices is deferred
+ ** until now.
+ */
+ if( row_triggers_exist && !isView ){
+ base = pParse->nTab;
+ idx = sqliteOpenTableAndIndices(pParse, pTab, base);
+ pParse->nTab += idx;
+ }
+
+ /* Push the record number for the new entry onto the stack. The
+ ** record number is a randomly generate integer created by NewRecno
+ ** except when the table has an INTEGER PRIMARY KEY column, in which
+ ** case the record number is the same as that column.
+ */
+ if( !isView ){
+ if( keyColumn>=0 ){
+ if( useTempTable ){
+ sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn);
+ }else if( pSelect ){
+ sqliteVdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1);
+ }else{
+ sqliteExprCode(pParse, pList->a[keyColumn].pExpr);
+ }
+ /* If the PRIMARY KEY expression is NULL, then use OP_NewRecno
+ ** to generate a unique primary key value.
+ */
+ sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
+ sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
+ }
+
+ /* Push onto the stack, data for all columns of the new entry, beginning
+ ** with the first column.
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ /* The value of the INTEGER PRIMARY KEY column is always a NULL.
+ ** Whenever this column is read, the record number will be substituted
+ ** in its place. So will fill this column with a NULL to avoid
+ ** taking up data space with information that will never be used. */
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ continue;
+ }
+ if( pColumn==0 ){
+ j = i;
+ }else{
+ for(j=0; j<pColumn->nId; j++){
+ if( pColumn->a[j].idx==i ) break;
+ }
+ }
+ if( pColumn && j>=pColumn->nId ){
+ sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zDflt, P3_STATIC);
+ }else if( useTempTable ){
+ sqliteVdbeAddOp(v, OP_Column, srcTab, j);
+ }else if( pSelect ){
+ sqliteVdbeAddOp(v, OP_Dup, i+nColumn-j, 1);
+ }else{
+ sqliteExprCode(pParse, pList->a[j].pExpr);
+ }
+ }
+
+ /* Generate code to check constraints and generate index keys and
+ ** do the insertion.
+ */
+ sqliteGenerateConstraintChecks(pParse, pTab, base, 0, keyColumn>=0,
+ 0, onError, endOfLoop);
+ sqliteCompleteInsertion(pParse, pTab, base, 0,0,0,
+ after_triggers ? newIdx : -1);
+ }
+
+ /* Update the count of rows that are inserted
+ */
+ if( (db->flags & SQLITE_CountRows)!=0 ){
+ sqliteVdbeAddOp(v, OP_MemIncr, iCntMem, 0);
+ }
+
+ if( row_triggers_exist ){
+ /* Close all tables opened */
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Close, base, 0);
+ for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
+ sqliteVdbeAddOp(v, OP_Close, idx+base, 0);
+ }
+ }
+
+ /* Code AFTER triggers */
+ if( sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_AFTER, pTab, newIdx, -1,
+ onError, endOfLoop) ){
+ goto insert_cleanup;
+ }
+ }
+
+ /* The bottom of the loop, if the data source is a SELECT statement
+ */
+ sqliteVdbeResolveLabel(v, endOfLoop);
+ if( useTempTable ){
+ sqliteVdbeAddOp(v, OP_Next, srcTab, iCont);
+ sqliteVdbeResolveLabel(v, iBreak);
+ sqliteVdbeAddOp(v, OP_Close, srcTab, 0);
+ }else if( pSelect ){
+ sqliteVdbeAddOp(v, OP_Pop, nColumn, 0);
+ sqliteVdbeAddOp(v, OP_Return, 0, 0);
+ sqliteVdbeResolveLabel(v, iCleanup);
+ }
+
+ if( !row_triggers_exist ){
+ /* Close all tables opened */
+ sqliteVdbeAddOp(v, OP_Close, base, 0);
+ for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
+ sqliteVdbeAddOp(v, OP_Close, idx+base, 0);
+ }
+ }
+
+ sqliteVdbeAddOp(v, OP_SetCounts, 0, 0);
+ sqliteEndWriteOperation(pParse);
+
+ /*
+ ** Return the number of rows inserted.
+ */
+ if( db->flags & SQLITE_CountRows ){
+ sqliteVdbeOp3(v, OP_ColumnName, 0, 1, "rows inserted", P3_STATIC);
+ sqliteVdbeAddOp(v, OP_MemLoad, iCntMem, 0);
+ sqliteVdbeAddOp(v, OP_Callback, 1, 0);
+ }
+
+insert_cleanup:
+ sqliteSrcListDelete(pTabList);
+ if( pList ) sqliteExprListDelete(pList);
+ if( pSelect ) sqliteSelectDelete(pSelect);
+ sqliteIdListDelete(pColumn);
+}
+
+/*
+** Generate code to do a constraint check prior to an INSERT or an UPDATE.
+**
+** When this routine is called, the stack contains (from bottom to top)
+** the following values:
+**
+** 1. The recno of the row to be updated before the update. This
+** value is omitted unless we are doing an UPDATE that involves a
+** change to the record number.
+**
+** 2. The recno of the row after the update.
+**
+** 3. The data in the first column of the entry after the update.
+**
+** i. Data from middle columns...
+**
+** N. The data in the last column of the entry after the update.
+**
+** The old recno shown as entry (1) above is omitted unless both isUpdate
+** and recnoChng are 1. isUpdate is true for UPDATEs and false for
+** INSERTs and recnoChng is true if the record number is being changed.
+**
+** The code generated by this routine pushes additional entries onto
+** the stack which are the keys for new index entries for the new record.
+** The order of index keys is the same as the order of the indices on
+** the pTable->pIndex list. A key is only created for index i if
+** aIdxUsed!=0 and aIdxUsed[i]!=0.
+**
+** This routine also generates code to check constraints. NOT NULL,
+** CHECK, and UNIQUE constraints are all checked. If a constraint fails,
+** then the appropriate action is performed. There are five possible
+** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE.
+**
+** Constraint type Action What Happens
+** --------------- ---------- ----------------------------------------
+** any ROLLBACK The current transaction is rolled back and
+** sqlite_exec() returns immediately with a
+** return code of SQLITE_CONSTRAINT.
+**
+** any ABORT Back out changes from the current command
+** only (do not do a complete rollback) then
+** cause sqlite_exec() to return immediately
+** with SQLITE_CONSTRAINT.
+**
+** any FAIL Sqlite_exec() returns immediately with a
+** return code of SQLITE_CONSTRAINT. The
+** transaction is not rolled back and any
+** prior changes are retained.
+**
+** any IGNORE The record number and data is popped from
+** the stack and there is an immediate jump
+** to label ignoreDest.
+**
+** NOT NULL REPLACE The NULL value is replace by the default
+** value for that column. If the default value
+** is NULL, the action is the same as ABORT.
+**
+** UNIQUE REPLACE The other row that conflicts with the row
+** being inserted is removed.
+**
+** CHECK REPLACE Illegal. The results in an exception.
+**
+** Which action to take is determined by the overrideError parameter.
+** Or if overrideError==OE_Default, then the pParse->onError parameter
+** is used. Or if pParse->onError==OE_Default then the onError value
+** for the constraint is used.
+**
+** The calling routine must open a read/write cursor for pTab with
+** cursor number "base". All indices of pTab must also have open
+** read/write cursors with cursor number base+i for the i-th cursor.
+** Except, if there is no possibility of a REPLACE action then
+** cursors do not need to be open for indices where aIdxUsed[i]==0.
+**
+** If the isUpdate flag is true, it means that the "base" cursor is
+** initially pointing to an entry that is being updated. The isUpdate
+** flag causes extra code to be generated so that the "base" cursor
+** is still pointing at the same entry after the routine returns.
+** Without the isUpdate flag, the "base" cursor might be moved.
+*/
+void sqliteGenerateConstraintChecks(
+ Parse *pParse, /* The parser context */
+ Table *pTab, /* the table into which we are inserting */
+ int base, /* Index of a read/write cursor pointing at pTab */
+ char *aIdxUsed, /* Which indices are used. NULL means all are used */
+ int recnoChng, /* True if the record number will change */
+ int isUpdate, /* True for UPDATE, False for INSERT */
+ int overrideError, /* Override onError to this if not OE_Default */
+ int ignoreDest /* Jump to this label on an OE_Ignore resolution */
+){
+ int i;
+ Vdbe *v;
+ int nCol;
+ int onError;
+ int addr;
+ int extra;
+ int iCur;
+ Index *pIdx;
+ int seenReplace = 0;
+ int jumpInst1, jumpInst2;
+ int contAddr;
+ int hasTwoRecnos = (isUpdate && recnoChng);
+
+ v = sqliteGetVdbe(pParse);
+ assert( v!=0 );
+ assert( pTab->pSelect==0 ); /* This table is not a VIEW */
+ nCol = pTab->nCol;
+
+ /* Test all NOT NULL constraints.
+ */
+ for(i=0; i<nCol; i++){
+ if( i==pTab->iPKey ){
+ continue;
+ }
+ onError = pTab->aCol[i].notNull;
+ if( onError==OE_None ) continue;
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( pParse->db->onError!=OE_Default ){
+ onError = pParse->db->onError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+ if( onError==OE_Replace && pTab->aCol[i].zDflt==0 ){
+ onError = OE_Abort;
+ }
+ sqliteVdbeAddOp(v, OP_Dup, nCol-1-i, 1);
+ addr = sqliteVdbeAddOp(v, OP_NotNull, 1, 0);
+ switch( onError ){
+ case OE_Rollback:
+ case OE_Abort:
+ case OE_Fail: {
+ char *zMsg = 0;
+ sqliteVdbeAddOp(v, OP_Halt, SQLITE_CONSTRAINT, onError);
+ sqliteSetString(&zMsg, pTab->zName, ".", pTab->aCol[i].zName,
+ " may not be NULL", (char*)0);
+ sqliteVdbeChangeP3(v, -1, zMsg, P3_DYNAMIC);
+ break;
+ }
+ case OE_Ignore: {
+ sqliteVdbeAddOp(v, OP_Pop, nCol+1+hasTwoRecnos, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ case OE_Replace: {
+ sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zDflt, P3_STATIC);
+ sqliteVdbeAddOp(v, OP_Push, nCol-i, 0);
+ break;
+ }
+ default: assert(0);
+ }
+ sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v));
+ }
+
+ /* Test all CHECK constraints
+ */
+ /**** TBD ****/
+
+ /* If we have an INTEGER PRIMARY KEY, make sure the primary key
+ ** of the new record does not previously exist. Except, if this
+ ** is an UPDATE and the primary key is not changing, that is OK.
+ */
+ if( recnoChng ){
+ onError = pTab->keyConf;
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( pParse->db->onError!=OE_Default ){
+ onError = pParse->db->onError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+
+ if( isUpdate ){
+ sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1);
+ sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1);
+ jumpInst1 = sqliteVdbeAddOp(v, OP_Eq, 0, 0);
+ }
+ sqliteVdbeAddOp(v, OP_Dup, nCol, 1);
+ jumpInst2 = sqliteVdbeAddOp(v, OP_NotExists, base, 0);
+ switch( onError ){
+ default: {
+ onError = OE_Abort;
+ /* Fall thru into the next case */
+ }
+ case OE_Rollback:
+ case OE_Abort:
+ case OE_Fail: {
+ sqliteVdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError,
+ "PRIMARY KEY must be unique", P3_STATIC);
+ break;
+ }
+ case OE_Replace: {
+ sqliteGenerateRowIndexDelete(pParse->db, v, pTab, base, 0);
+ if( isUpdate ){
+ sqliteVdbeAddOp(v, OP_Dup, nCol+hasTwoRecnos, 1);
+ sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
+ }
+ seenReplace = 1;
+ break;
+ }
+ case OE_Ignore: {
+ assert( seenReplace==0 );
+ sqliteVdbeAddOp(v, OP_Pop, nCol+1+hasTwoRecnos, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ }
+ contAddr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeChangeP2(v, jumpInst2, contAddr);
+ if( isUpdate ){
+ sqliteVdbeChangeP2(v, jumpInst1, contAddr);
+ sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1);
+ sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
+ }
+ }
+
+ /* Test all UNIQUE constraints by creating entries for each UNIQUE
+ ** index and making sure that duplicate entries do not already exist.
+ ** Add the new records to the indices as we go.
+ */
+ extra = -1;
+ for(iCur=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, iCur++){
+ if( aIdxUsed && aIdxUsed[iCur]==0 ) continue; /* Skip unused indices */
+ extra++;
+
+ /* Create a key for accessing the index entry */
+ sqliteVdbeAddOp(v, OP_Dup, nCol+extra, 1);
+ for(i=0; i<pIdx->nColumn; i++){
+ int idx = pIdx->aiColumn[i];
+ if( idx==pTab->iPKey ){
+ sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol+1, 1);
+ }else{
+ sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1);
+ }
+ }
+ jumpInst1 = sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0);
+ if( pParse->db->file_format>=4 ) sqliteAddIdxKeyType(v, pIdx);
+
+ /* Find out what action to take in case there is an indexing conflict */
+ onError = pIdx->onError;
+ if( onError==OE_None ) continue; /* pIdx is not a UNIQUE index */
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( pParse->db->onError!=OE_Default ){
+ onError = pParse->db->onError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+ if( seenReplace ){
+ if( onError==OE_Ignore ) onError = OE_Replace;
+ else if( onError==OE_Fail ) onError = OE_Abort;
+ }
+
+
+ /* Check to see if the new index entry will be unique */
+ sqliteVdbeAddOp(v, OP_Dup, extra+nCol+1+hasTwoRecnos, 1);
+ jumpInst2 = sqliteVdbeAddOp(v, OP_IsUnique, base+iCur+1, 0);
+
+ /* Generate code that executes if the new index entry is not unique */
+ switch( onError ){
+ case OE_Rollback:
+ case OE_Abort:
+ case OE_Fail: {
+ int j, n1, n2;
+ char zErrMsg[200];
+ strcpy(zErrMsg, pIdx->nColumn>1 ? "columns " : "column ");
+ n1 = strlen(zErrMsg);
+ for(j=0; j<pIdx->nColumn && n1<sizeof(zErrMsg)-30; j++){
+ char *zCol = pTab->aCol[pIdx->aiColumn[j]].zName;
+ n2 = strlen(zCol);
+ if( j>0 ){
+ strcpy(&zErrMsg[n1], ", ");
+ n1 += 2;
+ }
+ if( n1+n2>sizeof(zErrMsg)-30 ){
+ strcpy(&zErrMsg[n1], "...");
+ n1 += 3;
+ break;
+ }else{
+ strcpy(&zErrMsg[n1], zCol);
+ n1 += n2;
+ }
+ }
+ strcpy(&zErrMsg[n1],
+ pIdx->nColumn>1 ? " are not unique" : " is not unique");
+ sqliteVdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError, zErrMsg, 0);
+ break;
+ }
+ case OE_Ignore: {
+ assert( seenReplace==0 );
+ sqliteVdbeAddOp(v, OP_Pop, nCol+extra+3+hasTwoRecnos, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ case OE_Replace: {
+ sqliteGenerateRowDelete(pParse->db, v, pTab, base, 0);
+ if( isUpdate ){
+ sqliteVdbeAddOp(v, OP_Dup, nCol+extra+1+hasTwoRecnos, 1);
+ sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
+ }
+ seenReplace = 1;
+ break;
+ }
+ default: assert(0);
+ }
+ contAddr = sqliteVdbeCurrentAddr(v);
+#if NULL_DISTINCT_FOR_UNIQUE
+ sqliteVdbeChangeP2(v, jumpInst1, contAddr);
+#endif
+ sqliteVdbeChangeP2(v, jumpInst2, contAddr);
+ }
+}
+
+/*
+** This routine generates code to finish the INSERT or UPDATE operation
+** that was started by a prior call to sqliteGenerateConstraintChecks.
+** The stack must contain keys for all active indices followed by data
+** and the recno for the new entry. This routine creates the new
+** entries in all indices and in the main table.
+**
+** The arguments to this routine should be the same as the first six
+** arguments to sqliteGenerateConstraintChecks.
+*/
+void sqliteCompleteInsertion(
+ Parse *pParse, /* The parser context */
+ Table *pTab, /* the table into which we are inserting */
+ int base, /* Index of a read/write cursor pointing at pTab */
+ char *aIdxUsed, /* Which indices are used. NULL means all are used */
+ int recnoChng, /* True if the record number will change */
+ int isUpdate, /* True for UPDATE, False for INSERT */
+ int newIdx /* Index of NEW table for triggers. -1 if none */
+){
+ int i;
+ Vdbe *v;
+ int nIdx;
+ Index *pIdx;
+
+ v = sqliteGetVdbe(pParse);
+ assert( v!=0 );
+ assert( pTab->pSelect==0 ); /* This table is not a VIEW */
+ for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){}
+ for(i=nIdx-1; i>=0; i--){
+ if( aIdxUsed && aIdxUsed[i]==0 ) continue;
+ sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 0);
+ }
+ sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+ if( newIdx>=0 ){
+ sqliteVdbeAddOp(v, OP_Dup, 1, 0);
+ sqliteVdbeAddOp(v, OP_Dup, 1, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0);
+ }
+ sqliteVdbeAddOp(v, OP_PutIntKey, base,
+ (pParse->trigStack?0:OPFLAG_NCHANGE) |
+ (isUpdate?0:OPFLAG_LASTROWID) | OPFLAG_CSCHANGE);
+ if( isUpdate && recnoChng ){
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ }
+}
+
+/*
+** Generate code that will open write cursors for a table and for all
+** indices of that table. The "base" parameter is the cursor number used
+** for the table. Indices are opened on subsequent cursors.
+**
+** Return the total number of cursors opened. This is always at least
+** 1 (for the main table) plus more for each cursor.
+*/
+int sqliteOpenTableAndIndices(Parse *pParse, Table *pTab, int base){
+ int i;
+ Index *pIdx;
+ Vdbe *v = sqliteGetVdbe(pParse);
+ assert( v!=0 );
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenWrite, base, pTab->tnum, pTab->zName, P3_STATIC);
+ for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenWrite, i+base, pIdx->tnum, pIdx->zName, P3_STATIC);
+ }
+ return i;
+}
diff --git a/src/libs/sqlite2/main.c b/src/libs/sqlite2/main.c
new file mode 100644
index 00000000..7541b171
--- /dev/null
+++ b/src/libs/sqlite2/main.c
@@ -0,0 +1,1143 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Main file for the SQLite library. The routines in this file
+** implement the programmer interface to the library. Routines in
+** other files are for internal use by SQLite and should not be
+** accessed by users of the library.
+**
+** $Id: main.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+
+/*
+** A pointer to this structure is used to communicate information
+** from sqliteInit into the sqliteInitCallback.
+*/
+typedef struct {
+ sqlite *db; /* The database being initialized */
+ char **pzErrMsg; /* Error message stored here */
+} InitData;
+
+/*
+** Fill the InitData structure with an error message that indicates
+** that the database is corrupt.
+*/
+static void corruptSchema(InitData *pData, const char *zExtra){
+ sqliteSetString(pData->pzErrMsg, "malformed database schema",
+ zExtra!=0 && zExtra[0]!=0 ? " - " : (char*)0, zExtra, (char*)0);
+}
+
+/*
+** This is the callback routine for the code that initializes the
+** database. See sqliteInit() below for additional information.
+**
+** Each callback contains the following information:
+**
+** argv[0] = "file-format" or "schema-cookie" or "table" or "index"
+** argv[1] = table or index name or meta statement type.
+** argv[2] = root page number for table or index. NULL for meta.
+** argv[3] = SQL text for a CREATE TABLE or CREATE INDEX statement.
+** argv[4] = "1" for temporary files, "0" for main database, "2" or more
+** for auxiliary database files.
+**
+*/
+static
+int sqliteInitCallback(void *pInit, int argc, char **argv, char **azColName){
+ InitData *pData = (InitData*)pInit;
+ int nErr = 0;
+
+ assert( argc==5 );
+ if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */
+ if( argv[0]==0 ){
+ corruptSchema(pData, 0);
+ return 1;
+ }
+ switch( argv[0][0] ){
+ case 'v':
+ case 'i':
+ case 't': { /* CREATE TABLE, CREATE INDEX, or CREATE VIEW statements */
+ sqlite *db = pData->db;
+ if( argv[2]==0 || argv[4]==0 ){
+ corruptSchema(pData, 0);
+ return 1;
+ }
+ if( argv[3] && argv[3][0] ){
+ /* Call the parser to process a CREATE TABLE, INDEX or VIEW.
+ ** But because db->init.busy is set to 1, no VDBE code is generated
+ ** or executed. All the parser does is build the internal data
+ ** structures that describe the table, index, or view.
+ */
+ char *zErr;
+ assert( db->init.busy );
+ db->init.iDb = atoi(argv[4]);
+ assert( db->init.iDb>=0 && db->init.iDb<db->nDb );
+ db->init.newTnum = atoi(argv[2]);
+ if( sqlite_exec(db, argv[3], 0, 0, &zErr) ){
+ corruptSchema(pData, zErr);
+ sqlite_freemem(zErr);
+ }
+ db->init.iDb = 0;
+ }else{
+ /* If the SQL column is blank it means this is an index that
+ ** was created to be the PRIMARY KEY or to fulfill a UNIQUE
+ ** constraint for a CREATE TABLE. The index should have already
+ ** been created when we processed the CREATE TABLE. All we have
+ ** to do here is record the root page number for that index.
+ */
+ int iDb;
+ Index *pIndex;
+
+ iDb = atoi(argv[4]);
+ assert( iDb>=0 && iDb<db->nDb );
+ pIndex = sqliteFindIndex(db, argv[1], db->aDb[iDb].zName);
+ if( pIndex==0 || pIndex->tnum!=0 ){
+ /* This can occur if there exists an index on a TEMP table which
+ ** has the same name as another index on a permanent index. Since
+ ** the permanent table is hidden by the TEMP table, we can also
+ ** safely ignore the index on the permanent table.
+ */
+ /* Do Nothing */;
+ }else{
+ pIndex->tnum = atoi(argv[2]);
+ }
+ }
+ break;
+ }
+ default: {
+ /* This can not happen! */
+ nErr = 1;
+ assert( nErr==0 );
+ }
+ }
+ return nErr;
+}
+
+/*
+** This is a callback procedure used to reconstruct a table. The
+** name of the table to be reconstructed is passed in as argv[0].
+**
+** This routine is used to automatically upgrade a database from
+** format version 1 or 2 to version 3. The correct operation of
+** this routine relys on the fact that no indices are used when
+** copying a table out to a temporary file.
+**
+** The change from version 2 to version 3 occurred between SQLite
+** version 2.5.6 and 2.6.0 on 2002-July-18.
+*/
+static
+int upgrade_3_callback(void *pInit, int argc, char **argv, char **NotUsed){
+ InitData *pData = (InitData*)pInit;
+ int rc;
+ Table *pTab;
+ Trigger *pTrig;
+ char *zErr = 0;
+
+ pTab = sqliteFindTable(pData->db, argv[0], 0);
+ assert( pTab!=0 );
+ assert( sqliteStrICmp(pTab->zName, argv[0])==0 );
+ if( pTab ){
+ pTrig = pTab->pTrigger;
+ pTab->pTrigger = 0; /* Disable all triggers before rebuilding the table */
+ }
+ rc = sqlite_exec_printf(pData->db,
+ "CREATE TEMP TABLE sqlite_x AS SELECT * FROM '%q'; "
+ "DELETE FROM '%q'; "
+ "INSERT INTO '%q' SELECT * FROM sqlite_x; "
+ "DROP TABLE sqlite_x;",
+ 0, 0, &zErr, argv[0], argv[0], argv[0]);
+ if( zErr ){
+ if( *pData->pzErrMsg ) sqlite_freemem(*pData->pzErrMsg);
+ *pData->pzErrMsg = zErr;
+ }
+
+ /* If an error occurred in the SQL above, then the transaction will
+ ** rollback which will delete the internal symbol tables. This will
+ ** cause the structure that pTab points to be deleted. In case that
+ ** happened, we need to refetch pTab.
+ */
+ pTab = sqliteFindTable(pData->db, argv[0], 0);
+ if( pTab ){
+ assert( sqliteStrICmp(pTab->zName, argv[0])==0 );
+ pTab->pTrigger = pTrig; /* Re-enable triggers */
+ }
+ return rc!=SQLITE_OK;
+}
+
+
+
+/*
+** Attempt to read the database schema and initialize internal
+** data structures for a single database file. The index of the
+** database file is given by iDb. iDb==0 is used for the main
+** database. iDb==1 should never be used. iDb>=2 is used for
+** auxiliary databases. Return one of the SQLITE_ error codes to
+** indicate success or failure.
+*/
+static int sqliteInitOne(sqlite *db, int iDb, char **pzErrMsg){
+ int rc;
+ BtCursor *curMain;
+ int size;
+ Table *pTab;
+ char const *azArg[6];
+ char zDbNum[30];
+ int meta[SQLITE_N_BTREE_META];
+ InitData initData;
+ char const *zMasterSchema;
+ char const *zMasterName;
+ char *zSql = 0;
+
+ /*
+ ** The master database table has a structure like this
+ */
+ static char master_schema[] =
+ "CREATE TABLE sqlite_master(\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")"
+ ;
+ static char temp_master_schema[] =
+ "CREATE TEMP TABLE sqlite_temp_master(\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")"
+ ;
+
+ assert( iDb>=0 && iDb<db->nDb );
+
+ /* zMasterSchema and zInitScript are set to point at the master schema
+ ** and initialisation script appropriate for the database being
+ ** initialised. zMasterName is the name of the master table.
+ */
+ if( iDb==1 ){
+ zMasterSchema = temp_master_schema;
+ zMasterName = TEMP_MASTER_NAME;
+ }else{
+ zMasterSchema = master_schema;
+ zMasterName = MASTER_NAME;
+ }
+
+ /* Construct the schema table.
+ */
+ sqliteSafetyOff(db);
+ azArg[0] = "table";
+ azArg[1] = zMasterName;
+ azArg[2] = "2";
+ azArg[3] = zMasterSchema;
+ sprintf(zDbNum, "%d", iDb);
+ azArg[4] = zDbNum;
+ azArg[5] = 0;
+ initData.db = db;
+ initData.pzErrMsg = pzErrMsg;
+ sqliteInitCallback(&initData, 5, (char **)azArg, 0);
+ pTab = sqliteFindTable(db, zMasterName, db->aDb[iDb].zName);
+ if( pTab ){
+ pTab->readOnly = 1;
+ }else{
+ return SQLITE_NOMEM;
+ }
+ sqliteSafetyOn(db);
+
+ /* Create a cursor to hold the database open
+ */
+ if( db->aDb[iDb].pBt==0 ) return SQLITE_OK;
+ rc = sqliteBtreeCursor(db->aDb[iDb].pBt, 2, 0, &curMain);
+ if( rc ){
+ sqliteSetString(pzErrMsg, sqlite_error_string(rc), (char*)0);
+ return rc;
+ }
+
+ /* Get the database meta information
+ */
+ rc = sqliteBtreeGetMeta(db->aDb[iDb].pBt, meta);
+ if( rc ){
+ sqliteSetString(pzErrMsg, sqlite_error_string(rc), (char*)0);
+ sqliteBtreeCloseCursor(curMain);
+ return rc;
+ }
+ db->aDb[iDb].schema_cookie = meta[1];
+ if( iDb==0 ){
+ db->next_cookie = meta[1];
+ db->file_format = meta[2];
+ size = meta[3];
+ if( size==0 ){ size = MAX_PAGES; }
+ db->cache_size = size;
+ db->safety_level = meta[4];
+ if( meta[6]>0 && meta[6]<=2 && db->temp_store==0 ){
+ db->temp_store = meta[6];
+ }
+ if( db->safety_level==0 ) db->safety_level = 2;
+
+ /*
+ ** file_format==1 Version 2.1.0.
+ ** file_format==2 Version 2.2.0. Add support for INTEGER PRIMARY KEY.
+ ** file_format==3 Version 2.6.0. Fix empty-string index bug.
+ ** file_format==4 Version 2.7.0. Add support for separate numeric and
+ ** text datatypes.
+ */
+ if( db->file_format==0 ){
+ /* This happens if the database was initially empty */
+ db->file_format = 4;
+ }else if( db->file_format>4 ){
+ sqliteBtreeCloseCursor(curMain);
+ sqliteSetString(pzErrMsg, "unsupported file format", (char*)0);
+ return SQLITE_ERROR;
+ }
+ }else if( iDb!=1 && (db->file_format!=meta[2] || db->file_format<4) ){
+ assert( db->file_format>=4 );
+ if( meta[2]==0 ){
+ sqliteSetString(pzErrMsg, "cannot attach empty database: ",
+ db->aDb[iDb].zName, (char*)0);
+ }else{
+ sqliteSetString(pzErrMsg, "incompatible file format in auxiliary "
+ "database: ", db->aDb[iDb].zName, (char*)0);
+ }
+ sqliteBtreeClose(db->aDb[iDb].pBt);
+ db->aDb[iDb].pBt = 0;
+ return SQLITE_FORMAT;
+ }
+ sqliteBtreeSetCacheSize(db->aDb[iDb].pBt, db->cache_size);
+ sqliteBtreeSetSafetyLevel(db->aDb[iDb].pBt, meta[4]==0 ? 2 : meta[4]);
+
+ /* Read the schema information out of the schema tables
+ */
+ assert( db->init.busy );
+ sqliteSafetyOff(db);
+
+ /* The following SQL will read the schema from the master tables.
+ ** The first version works with SQLite file formats 2 or greater.
+ ** The second version is for format 1 files.
+ **
+ ** Beginning with file format 2, the rowid for new table entries
+ ** (including entries in sqlite_master) is an increasing integer.
+ ** So for file format 2 and later, we can play back sqlite_master
+ ** and all the CREATE statements will appear in the right order.
+ ** But with file format 1, table entries were random and so we
+ ** have to make sure the CREATE TABLEs occur before their corresponding
+ ** CREATE INDEXs. (We don't have to deal with CREATE VIEW or
+ ** CREATE TRIGGER in file format 1 because those constructs did
+ ** not exist then.)
+ */
+ if( db->file_format>=2 ){
+ sqliteSetString(&zSql,
+ "SELECT type, name, rootpage, sql, ", zDbNum, " FROM \"",
+ db->aDb[iDb].zName, "\".", zMasterName, (char*)0);
+ }else{
+ sqliteSetString(&zSql,
+ "SELECT type, name, rootpage, sql, ", zDbNum, " FROM \"",
+ db->aDb[iDb].zName, "\".", zMasterName,
+ " WHERE type IN ('table', 'index')"
+ " ORDER BY CASE type WHEN 'table' THEN 0 ELSE 1 END", (char*)0);
+ }
+ rc = sqlite_exec(db, zSql, sqliteInitCallback, &initData, 0);
+
+ sqliteFree(zSql);
+ sqliteSafetyOn(db);
+ sqliteBtreeCloseCursor(curMain);
+ if( sqlite_malloc_failed ){
+ sqliteSetString(pzErrMsg, "out of memory", (char*)0);
+ rc = SQLITE_NOMEM;
+ sqliteResetInternalSchema(db, 0);
+ }
+ if( rc==SQLITE_OK ){
+ DbSetProperty(db, iDb, DB_SchemaLoaded);
+ }else{
+ sqliteResetInternalSchema(db, iDb);
+ }
+ return rc;
+}
+
+/*
+** Initialize all database files - the main database file, the file
+** used to store temporary tables, and any additional database files
+** created using ATTACH statements. Return a success code. If an
+** error occurs, write an error message into *pzErrMsg.
+**
+** After the database is initialized, the SQLITE_Initialized
+** bit is set in the flags field of the sqlite structure. An
+** attempt is made to initialize the database as soon as it
+** is opened. If that fails (perhaps because another process
+** has the sqlite_master table locked) than another attempt
+** is made the first time the database is accessed.
+*/
+int sqliteInit(sqlite *db, char **pzErrMsg){
+ int i, rc;
+
+ if( db->init.busy ) return SQLITE_OK;
+ assert( (db->flags & SQLITE_Initialized)==0 );
+ rc = SQLITE_OK;
+ db->init.busy = 1;
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+ if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue;
+ rc = sqliteInitOne(db, i, pzErrMsg);
+ if( rc ){
+ sqliteResetInternalSchema(db, i);
+ }
+ }
+
+ /* Once all the other databases have been initialised, load the schema
+ ** for the TEMP database. This is loaded last, as the TEMP database
+ ** schema may contain references to objects in other databases.
+ */
+ if( rc==SQLITE_OK && db->nDb>1 && !DbHasProperty(db, 1, DB_SchemaLoaded) ){
+ rc = sqliteInitOne(db, 1, pzErrMsg);
+ if( rc ){
+ sqliteResetInternalSchema(db, 1);
+ }
+ }
+
+ db->init.busy = 0;
+ if( rc==SQLITE_OK ){
+ db->flags |= SQLITE_Initialized;
+ sqliteCommitInternalChanges(db);
+ }
+
+ /* If the database is in formats 1 or 2, then upgrade it to
+ ** version 3. This will reconstruct all indices. If the
+ ** upgrade fails for any reason (ex: out of disk space, database
+ ** is read only, interrupt received, etc.) then fail the init.
+ */
+ if( rc==SQLITE_OK && db->file_format<3 ){
+ char *zErr = 0;
+ InitData initData;
+ int meta[SQLITE_N_BTREE_META];
+
+ db->magic = SQLITE_MAGIC_OPEN;
+ initData.db = db;
+ initData.pzErrMsg = &zErr;
+ db->file_format = 3;
+ rc = sqlite_exec(db,
+ "BEGIN; SELECT name FROM sqlite_master WHERE type='table';",
+ upgrade_3_callback,
+ &initData,
+ &zErr);
+ if( rc==SQLITE_OK ){
+ sqliteBtreeGetMeta(db->aDb[0].pBt, meta);
+ meta[2] = 4;
+ sqliteBtreeUpdateMeta(db->aDb[0].pBt, meta);
+ sqlite_exec(db, "COMMIT", 0, 0, 0);
+ }
+ if( rc!=SQLITE_OK ){
+ sqliteSetString(pzErrMsg,
+ "unable to upgrade database to the version 2.6 format",
+ zErr ? ": " : 0, zErr, (char*)0);
+ }
+ sqlite_freemem(zErr);
+ }
+
+ if( rc!=SQLITE_OK ){
+ db->flags &= ~SQLITE_Initialized;
+ }
+ return rc;
+}
+
+/*
+** The version of the library
+*/
+const char rcsid[] = "@(#) \044Id: SQLite version " SQLITE_VERSION " $";
+const char sqlite_version[] = SQLITE_VERSION;
+
+/*
+** Does the library expect data to be encoded as UTF-8 or iso8859? The
+** following global constant always lets us know.
+*/
+#ifdef SQLITE_UTF8
+const char sqlite_encoding[] = "UTF-8";
+#else
+const char sqlite_encoding[] = "iso8859";
+#endif
+
+/*
+** Open a new SQLite database. Construct an "sqlite" structure to define
+** the state of this database and return a pointer to that structure.
+**
+** An attempt is made to initialize the in-memory data structures that
+** hold the database schema. But if this fails (because the schema file
+** is locked) then that step is deferred until the first call to
+** sqlite_exec().
+*/
+sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
+ sqlite *db;
+ int rc, i;
+
+ /* Allocate the sqlite data structure */
+ db = sqliteMalloc( sizeof(sqlite) );
+ if( pzErrMsg ) *pzErrMsg = 0;
+ if( db==0 ) goto no_mem_on_open;
+ db->onError = OE_Default;
+ db->priorNewRowid = 0;
+ db->magic = SQLITE_MAGIC_BUSY;
+ db->nDb = 2;
+ db->aDb = db->aDbStatic;
+ /* db->flags |= SQLITE_ShortColNames; */
+ sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1);
+ for(i=0; i<db->nDb; i++){
+ sqliteHashInit(&db->aDb[i].tblHash, SQLITE_HASH_STRING, 0);
+ sqliteHashInit(&db->aDb[i].idxHash, SQLITE_HASH_STRING, 0);
+ sqliteHashInit(&db->aDb[i].trigHash, SQLITE_HASH_STRING, 0);
+ sqliteHashInit(&db->aDb[i].aFKey, SQLITE_HASH_STRING, 1);
+ }
+
+ /* Open the backend database driver */
+ if( zFilename[0]==':' && strcmp(zFilename,":memory:")==0 ){
+ db->temp_store = 2;
+ }
+ rc = sqliteBtreeFactory(db, zFilename, 0, MAX_PAGES, &db->aDb[0].pBt);
+ if( rc!=SQLITE_OK ){
+ switch( rc ){
+ default: {
+ sqliteSetString(pzErrMsg, "unable to open database: ",
+ zFilename, (char*)0);
+ }
+ }
+ sqliteFree(db);
+ sqliteStrRealloc(pzErrMsg);
+ return 0;
+ }
+ db->aDb[0].zName = "main";
+ db->aDb[1].zName = "temp";
+
+ /* Attempt to read the schema */
+ sqliteRegisterBuiltinFunctions(db);
+ rc = sqliteInit(db, pzErrMsg);
+ db->magic = SQLITE_MAGIC_OPEN;
+ if( sqlite_malloc_failed ){
+ sqlite_close(db);
+ goto no_mem_on_open;
+ }else if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
+ sqlite_close(db);
+ sqliteStrRealloc(pzErrMsg);
+ return 0;
+ }else if( pzErrMsg ){
+ sqliteFree(*pzErrMsg);
+ *pzErrMsg = 0;
+ }
+
+ /* Return a pointer to the newly opened database structure */
+ return db;
+
+no_mem_on_open:
+ sqliteSetString(pzErrMsg, "out of memory", (char*)0);
+ sqliteStrRealloc(pzErrMsg);
+ return 0;
+}
+
+/*
+** Return the ROWID of the most recent insert
+*/
+int sqlite_last_insert_rowid(sqlite *db){
+ return db->lastRowid;
+}
+
+/*
+** Return the number of changes in the most recent call to sqlite_exec().
+*/
+int sqlite_changes(sqlite *db){
+ return db->nChange;
+}
+
+/*
+** Return the number of changes produced by the last INSERT, UPDATE, or
+** DELETE statement to complete execution. The count does not include
+** changes due to SQL statements executed in trigger programs that were
+** triggered by that statement
+*/
+int sqlite_last_statement_changes(sqlite *db){
+ return db->lsChange;
+}
+
+/*
+** Close an existing SQLite database
+*/
+void sqlite_close(sqlite *db){
+ HashElem *i;
+ int j;
+ db->want_to_close = 1;
+ if( sqliteSafetyCheck(db) || sqliteSafetyOn(db) ){
+ /* printf("DID NOT CLOSE\n"); fflush(stdout); */
+ return;
+ }
+ db->magic = SQLITE_MAGIC_CLOSED;
+ for(j=0; j<db->nDb; j++){
+ struct Db *pDb = &db->aDb[j];
+ if( pDb->pBt ){
+ sqliteBtreeClose(pDb->pBt);
+ pDb->pBt = 0;
+ }
+ }
+ sqliteResetInternalSchema(db, 0);
+ assert( db->nDb<=2 );
+ assert( db->aDb==db->aDbStatic );
+ for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){
+ FuncDef *pFunc, *pNext;
+ for(pFunc = (FuncDef*)sqliteHashData(i); pFunc; pFunc=pNext){
+ pNext = pFunc->pNext;
+ sqliteFree(pFunc);
+ }
+ }
+ sqliteHashClear(&db->aFunc);
+ sqliteFree(db);
+}
+
+/*
+** Rollback all database files.
+*/
+void sqliteRollbackAll(sqlite *db){
+ int i;
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt ){
+ sqliteBtreeRollback(db->aDb[i].pBt);
+ db->aDb[i].inTrans = 0;
+ }
+ }
+ sqliteResetInternalSchema(db, 0);
+ /* sqliteRollbackInternalChanges(db); */
+}
+
+/*
+** Execute SQL code. Return one of the SQLITE_ success/failure
+** codes. Also write an error message into memory obtained from
+** malloc() and make *pzErrMsg point to that message.
+**
+** If the SQL is a query, then for each row in the query result
+** the xCallback() function is called. pArg becomes the first
+** argument to xCallback(). If xCallback=NULL then no callback
+** is invoked, even for queries.
+*/
+int sqlite_exec(
+ sqlite *db, /* The database on which the SQL executes */
+ const char *zSql, /* The SQL to be executed */
+ sqlite_callback xCallback, /* Invoke this callback routine */
+ void *pArg, /* First argument to xCallback() */
+ char **pzErrMsg /* Write error messages here */
+){
+ int rc = SQLITE_OK;
+ const char *zLeftover;
+ sqlite_vm *pVm;
+ int nRetry = 0;
+ int nChange = 0;
+ int nCallback;
+
+ if( zSql==0 ) return SQLITE_OK;
+ while( rc==SQLITE_OK && zSql[0] ){
+ pVm = 0;
+ rc = sqlite_compile(db, zSql, &zLeftover, &pVm, pzErrMsg);
+ if( rc!=SQLITE_OK ){
+ assert( pVm==0 || sqlite_malloc_failed );
+ return rc;
+ }
+ if( pVm==0 ){
+ /* This happens if the zSql input contained only whitespace */
+ break;
+ }
+ db->nChange += nChange;
+ nCallback = 0;
+ while(1){
+ int nArg;
+ char **azArg, **azCol;
+ rc = sqlite_step(pVm, &nArg, (const char***)&azArg,(const char***)&azCol);
+ if( rc==SQLITE_ROW ){
+ if( xCallback!=0 && xCallback(pArg, nArg, azArg, azCol) ){
+ sqlite_finalize(pVm, 0);
+ return SQLITE_ABORT;
+ }
+ nCallback++;
+ }else{
+ if( rc==SQLITE_DONE && nCallback==0
+ && (db->flags & SQLITE_NullCallback)!=0 && xCallback!=0 ){
+ xCallback(pArg, nArg, azArg, azCol);
+ }
+ rc = sqlite_finalize(pVm, pzErrMsg);
+ if( rc==SQLITE_SCHEMA && nRetry<2 ){
+ nRetry++;
+ rc = SQLITE_OK;
+ break;
+ }
+ if( db->pVdbe==0 ){
+ nChange = db->nChange;
+ }
+ nRetry = 0;
+ zSql = zLeftover;
+ while( isspace(zSql[0]) ) zSql++;
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/*
+** Compile a single statement of SQL into a virtual machine. Return one
+** of the SQLITE_ success/failure codes. Also write an error message into
+** memory obtained from malloc() and make *pzErrMsg point to that message.
+*/
+int sqlite_compile(
+ sqlite *db, /* The database on which the SQL executes */
+ const char *zSql, /* The SQL to be executed */
+ const char **pzTail, /* OUT: Next statement after the first */
+ sqlite_vm **ppVm, /* OUT: The virtual machine */
+ char **pzErrMsg /* OUT: Write error messages here */
+){
+ Parse sParse;
+
+ if( pzErrMsg ) *pzErrMsg = 0;
+ if( sqliteSafetyOn(db) ) goto exec_misuse;
+ if( !db->init.busy ){
+ if( (db->flags & SQLITE_Initialized)==0 ){
+ int rc, cnt = 1;
+ while( (rc = sqliteInit(db, pzErrMsg))==SQLITE_BUSY
+ && db->xBusyCallback
+ && db->xBusyCallback(db->pBusyArg, "", cnt++)!=0 ){}
+ if( rc!=SQLITE_OK ){
+ sqliteStrRealloc(pzErrMsg);
+ sqliteSafetyOff(db);
+ return rc;
+ }
+ if( pzErrMsg ){
+ sqliteFree(*pzErrMsg);
+ *pzErrMsg = 0;
+ }
+ }
+ if( db->file_format<3 ){
+ sqliteSafetyOff(db);
+ sqliteSetString(pzErrMsg, "obsolete database file format", (char*)0);
+ return SQLITE_ERROR;
+ }
+ }
+ assert( (db->flags & SQLITE_Initialized)!=0 || db->init.busy );
+ if( db->pVdbe==0 ){ db->nChange = 0; }
+ memset(&sParse, 0, sizeof(sParse));
+ sParse.db = db;
+ sqliteRunParser(&sParse, zSql, pzErrMsg);
+ if( db->xTrace && !db->init.busy ){
+ /* Trace only the statment that was compiled.
+ ** Make a copy of that part of the SQL string since zSQL is const
+ ** and we must pass a zero terminated string to the trace function
+ ** The copy is unnecessary if the tail pointer is pointing at the
+ ** beginnig or end of the SQL string.
+ */
+ if( sParse.zTail && sParse.zTail!=zSql && *sParse.zTail ){
+ char *tmpSql = sqliteStrNDup(zSql, sParse.zTail - zSql);
+ if( tmpSql ){
+ db->xTrace(db->pTraceArg, tmpSql);
+ free(tmpSql);
+ }else{
+ /* If a memory error occurred during the copy,
+ ** trace entire SQL string and fall through to the
+ ** sqlite_malloc_failed test to report the error.
+ */
+ db->xTrace(db->pTraceArg, zSql);
+ }
+ }else{
+ db->xTrace(db->pTraceArg, zSql);
+ }
+ }
+ if( sqlite_malloc_failed ){
+ sqliteSetString(pzErrMsg, "out of memory", (char*)0);
+ sParse.rc = SQLITE_NOMEM;
+ sqliteRollbackAll(db);
+ sqliteResetInternalSchema(db, 0);
+ db->flags &= ~SQLITE_InTrans;
+ }
+ if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;
+ if( sParse.rc!=SQLITE_OK && pzErrMsg && *pzErrMsg==0 ){
+ sqliteSetString(pzErrMsg, sqlite_error_string(sParse.rc), (char*)0);
+ }
+ sqliteStrRealloc(pzErrMsg);
+ if( sParse.rc==SQLITE_SCHEMA ){
+ sqliteResetInternalSchema(db, 0);
+ }
+ assert( ppVm );
+ *ppVm = (sqlite_vm*)sParse.pVdbe;
+ if( pzTail ) *pzTail = sParse.zTail;
+ if( sqliteSafetyOff(db) ) goto exec_misuse;
+ return sParse.rc;
+
+exec_misuse:
+ if( pzErrMsg ){
+ *pzErrMsg = 0;
+ sqliteSetString(pzErrMsg, sqlite_error_string(SQLITE_MISUSE), (char*)0);
+ sqliteStrRealloc(pzErrMsg);
+ }
+ return SQLITE_MISUSE;
+}
+
+
+/*
+** The following routine destroys a virtual machine that is created by
+** the sqlite_compile() routine.
+**
+** The integer returned is an SQLITE_ success/failure code that describes
+** the result of executing the virtual machine. An error message is
+** written into memory obtained from malloc and *pzErrMsg is made to
+** point to that error if pzErrMsg is not NULL. The calling routine
+** should use sqlite_freemem() to delete the message when it has finished
+** with it.
+*/
+int sqlite_finalize(
+ sqlite_vm *pVm, /* The virtual machine to be destroyed */
+ char **pzErrMsg /* OUT: Write error messages here */
+){
+ int rc = sqliteVdbeFinalize((Vdbe*)pVm, pzErrMsg);
+ sqliteStrRealloc(pzErrMsg);
+ return rc;
+}
+
+/*
+** Terminate the current execution of a virtual machine then
+** reset the virtual machine back to its starting state so that it
+** can be reused. Any error message resulting from the prior execution
+** is written into *pzErrMsg. A success code from the prior execution
+** is returned.
+*/
+int sqlite_reset(
+ sqlite_vm *pVm, /* The virtual machine to be destroyed */
+ char **pzErrMsg /* OUT: Write error messages here */
+){
+ int rc = sqliteVdbeReset((Vdbe*)pVm, pzErrMsg);
+ sqliteVdbeMakeReady((Vdbe*)pVm, -1, 0);
+ sqliteStrRealloc(pzErrMsg);
+ return rc;
+}
+
+/*
+** Return a static string that describes the kind of error specified in the
+** argument.
+*/
+const char *sqlite_error_string(int rc){
+ const char *z;
+ switch( rc ){
+ case SQLITE_OK: z = "not an error"; break;
+ case SQLITE_ERROR: z = "SQL logic error or missing database"; break;
+ case SQLITE_INTERNAL: z = "internal SQLite implementation flaw"; break;
+ case SQLITE_PERM: z = "access permission denied"; break;
+ case SQLITE_ABORT: z = "callback requested query abort"; break;
+ case SQLITE_BUSY: z = "database is locked"; break;
+ case SQLITE_LOCKED: z = "database table is locked"; break;
+ case SQLITE_NOMEM: z = "out of memory"; break;
+ case SQLITE_READONLY: z = "attempt to write a readonly database"; break;
+ case SQLITE_INTERRUPT: z = "interrupted"; break;
+ case SQLITE_IOERR: z = "disk I/O error"; break;
+ case SQLITE_CORRUPT: z = "database disk image is malformed"; break;
+ case SQLITE_NOTFOUND: z = "table or record not found"; break;
+ case SQLITE_FULL: z = "database is full"; break;
+ case SQLITE_CANTOPEN: z = "unable to open database file"; break;
+ case SQLITE_PROTOCOL: z = "database locking protocol failure"; break;
+ case SQLITE_EMPTY: z = "table contains no data"; break;
+ case SQLITE_SCHEMA: z = "database schema has changed"; break;
+ case SQLITE_TOOBIG: z = "too much data for one table row"; break;
+ case SQLITE_CONSTRAINT: z = "constraint failed"; break;
+ case SQLITE_MISMATCH: z = "datatype mismatch"; break;
+ case SQLITE_MISUSE: z = "library routine called out of sequence";break;
+ case SQLITE_NOLFS: z = "kernel lacks large file support"; break;
+ case SQLITE_AUTH: z = "authorization denied"; break;
+ case SQLITE_FORMAT: z = "auxiliary database format error"; break;
+ case SQLITE_RANGE: z = "bind index out of range"; break;
+ case SQLITE_NOTADB: z = "file is encrypted or is not a database";break;
+ default: z = "unknown error"; break;
+ }
+ return z;
+}
+
+/*
+** This routine implements a busy callback that sleeps and tries
+** again until a timeout value is reached. The timeout value is
+** an integer number of milliseconds passed in as the first
+** argument.
+*/
+static int sqliteDefaultBusyCallback(
+ void *Timeout, /* Maximum amount of time to wait */
+ const char *NotUsed, /* The name of the table that is busy */
+ int count /* Number of times table has been busy */
+){
+#if SQLITE_MIN_SLEEP_MS==1
+ static const char delays[] =
+ { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 50, 100};
+ static const short int totals[] =
+ { 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228, 287};
+# define NDELAY (sizeof(delays)/sizeof(delays[0]))
+ int timeout = (int)(long)Timeout;
+ int delay, prior;
+
+ if( count <= NDELAY ){
+ delay = delays[count-1];
+ prior = totals[count-1];
+ }else{
+ delay = delays[NDELAY-1];
+ prior = totals[NDELAY-1] + delay*(count-NDELAY-1);
+ }
+ if( prior + delay > timeout ){
+ delay = timeout - prior;
+ if( delay<=0 ) return 0;
+ }
+ sqliteOsSleep(delay);
+ return 1;
+#else
+ int timeout = (int)(long)Timeout;
+ if( (count+1)*1000 > timeout ){
+ return 0;
+ }
+ sqliteOsSleep(1000);
+ return 1;
+#endif
+}
+
+/*
+** This routine sets the busy callback for an Sqlite database to the
+** given callback function with the given argument.
+*/
+void sqlite_busy_handler(
+ sqlite *db,
+ int (*xBusy)(void*,const char*,int),
+ void *pArg
+){
+ db->xBusyCallback = xBusy;
+ db->pBusyArg = pArg;
+}
+
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+/*
+** This routine sets the progress callback for an Sqlite database to the
+** given callback function with the given argument. The progress callback will
+** be invoked every nOps opcodes.
+*/
+void sqlite_progress_handler(
+ sqlite *db,
+ int nOps,
+ int (*xProgress)(void*),
+ void *pArg
+){
+ if( nOps>0 ){
+ db->xProgress = xProgress;
+ db->nProgressOps = nOps;
+ db->pProgressArg = pArg;
+ }else{
+ db->xProgress = 0;
+ db->nProgressOps = 0;
+ db->pProgressArg = 0;
+ }
+}
+#endif
+
+
+/*
+** This routine installs a default busy handler that waits for the
+** specified number of milliseconds before returning 0.
+*/
+void sqlite_busy_timeout(sqlite *db, int ms){
+ if( ms>0 ){
+ sqlite_busy_handler(db, sqliteDefaultBusyCallback, (void*)(long)ms);
+ }else{
+ sqlite_busy_handler(db, 0, 0);
+ }
+}
+
+/*
+** Cause any pending operation to stop at its earliest opportunity.
+*/
+void sqlite_interrupt(sqlite *db){
+ db->flags |= SQLITE_Interrupt;
+}
+
+/*
+** Windows systems should call this routine to free memory that
+** is returned in the in the errmsg parameter of sqlite_open() when
+** SQLite is a DLL. For some reason, it does not work to call free()
+** directly.
+**
+** Note that we need to call free() not sqliteFree() here, since every
+** string that is exported from SQLite should have already passed through
+** sqliteStrRealloc().
+*/
+void sqlite_freemem(void *p){ free(p); }
+
+/*
+** Windows systems need functions to call to return the sqlite_version
+** and sqlite_encoding strings since they are unable to access constants
+** within DLLs.
+*/
+const char *sqlite_libversion(void){ return sqlite_version; }
+const char *sqlite_libencoding(void){ return sqlite_encoding; }
+
+/*
+** Create new user-defined functions. The sqlite_create_function()
+** routine creates a regular function and sqlite_create_aggregate()
+** creates an aggregate function.
+**
+** Passing a NULL xFunc argument or NULL xStep and xFinalize arguments
+** disables the function. Calling sqlite_create_function() with the
+** same name and number of arguments as a prior call to
+** sqlite_create_aggregate() disables the prior call to
+** sqlite_create_aggregate(), and vice versa.
+**
+** If nArg is -1 it means that this function will accept any number
+** of arguments, including 0. The maximum allowed value of nArg is 127.
+*/
+int sqlite_create_function(
+ sqlite *db, /* Add the function to this database connection */
+ const char *zName, /* Name of the function to add */
+ int nArg, /* Number of arguments */
+ void (*xFunc)(sqlite_func*,int,const char**), /* The implementation */
+ void *pUserData /* User data */
+){
+ FuncDef *p;
+ int nName;
+ if( db==0 || zName==0 || sqliteSafetyCheck(db) ) return 1;
+ if( nArg<-1 || nArg>127 ) return 1;
+ nName = strlen(zName);
+ if( nName>255 ) return 1;
+ p = sqliteFindFunction(db, zName, nName, nArg, 1);
+ if( p==0 ) return 1;
+ p->xFunc = xFunc;
+ p->xStep = 0;
+ p->xFinalize = 0;
+ p->pUserData = pUserData;
+ return 0;
+}
+int sqlite_create_aggregate(
+ sqlite *db, /* Add the function to this database connection */
+ const char *zName, /* Name of the function to add */
+ int nArg, /* Number of arguments */
+ void (*xStep)(sqlite_func*,int,const char**), /* The step function */
+ void (*xFinalize)(sqlite_func*), /* The finalizer */
+ void *pUserData /* User data */
+){
+ FuncDef *p;
+ int nName;
+ if( db==0 || zName==0 || sqliteSafetyCheck(db) ) return 1;
+ if( nArg<-1 || nArg>127 ) return 1;
+ nName = strlen(zName);
+ if( nName>255 ) return 1;
+ p = sqliteFindFunction(db, zName, nName, nArg, 1);
+ if( p==0 ) return 1;
+ p->xFunc = 0;
+ p->xStep = xStep;
+ p->xFinalize = xFinalize;
+ p->pUserData = pUserData;
+ return 0;
+}
+
+/*
+** Change the datatype for all functions with a given name. See the
+** header comment for the prototype of this function in sqlite.h for
+** additional information.
+*/
+int sqlite_function_type(sqlite *db, const char *zName, int dataType){
+ FuncDef *p = (FuncDef*)sqliteHashFind(&db->aFunc, zName, strlen(zName));
+ while( p ){
+ p->dataType = dataType;
+ p = p->pNext;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Register a trace function. The pArg from the previously registered trace
+** is returned.
+**
+** A NULL trace function means that no tracing is executes. A non-NULL
+** trace is a pointer to a function that is invoked at the start of each
+** sqlite_exec().
+*/
+void *sqlite_trace(sqlite *db, void (*xTrace)(void*,const char*), void *pArg){
+ void *pOld = db->pTraceArg;
+ db->xTrace = xTrace;
+ db->pTraceArg = pArg;
+ return pOld;
+}
+
+/*** EXPERIMENTAL ***
+**
+** Register a function to be invoked when a transaction comments.
+** If either function returns non-zero, then the commit becomes a
+** rollback.
+*/
+void *sqlite_commit_hook(
+ sqlite *db, /* Attach the hook to this database */
+ int (*xCallback)(void*), /* Function to invoke on each commit */
+ void *pArg /* Argument to the function */
+){
+ void *pOld = db->pCommitArg;
+ db->xCommitCallback = xCallback;
+ db->pCommitArg = pArg;
+ return pOld;
+}
+
+
+/*
+** This routine is called to create a connection to a database BTree
+** driver. If zFilename is the name of a file, then that file is
+** opened and used. If zFilename is the magic name ":memory:" then
+** the database is stored in memory (and is thus forgotten as soon as
+** the connection is closed.) If zFilename is NULL then the database
+** is for temporary use only and is deleted as soon as the connection
+** is closed.
+**
+** A temporary database can be either a disk file (that is automatically
+** deleted when the file is closed) or a set of red-black trees held in memory,
+** depending on the values of the TEMP_STORE compile-time macro and the
+** db->temp_store variable, according to the following chart:
+**
+** TEMP_STORE db->temp_store Location of temporary database
+** ---------- -------------- ------------------------------
+** 0 any file
+** 1 1 file
+** 1 2 memory
+** 1 0 file
+** 2 1 file
+** 2 2 memory
+** 2 0 memory
+** 3 any memory
+*/
+int sqliteBtreeFactory(
+ const sqlite *db, /* Main database when opening aux otherwise 0 */
+ const char *zFilename, /* Name of the file containing the BTree database */
+ int omitJournal, /* if TRUE then do not journal this file */
+ int nCache, /* How many pages in the page cache */
+ Btree **ppBtree){ /* Pointer to new Btree object written here */
+
+ assert( ppBtree != 0);
+
+#ifndef SQLITE_OMIT_INMEMORYDB
+ if( zFilename==0 ){
+ if (TEMP_STORE == 0) {
+ /* Always use file based temporary DB */
+ return sqliteBtreeOpen(0, omitJournal, nCache, ppBtree);
+ } else if (TEMP_STORE == 1 || TEMP_STORE == 2) {
+ /* Switch depending on compile-time and/or runtime settings. */
+ int location = db->temp_store==0 ? TEMP_STORE : db->temp_store;
+
+ if (location == 1) {
+ return sqliteBtreeOpen(zFilename, omitJournal, nCache, ppBtree);
+ } else {
+ return sqliteRbtreeOpen(0, 0, 0, ppBtree);
+ }
+ } else {
+ /* Always use in-core DB */
+ return sqliteRbtreeOpen(0, 0, 0, ppBtree);
+ }
+ }else if( zFilename[0]==':' && strcmp(zFilename,":memory:")==0 ){
+ return sqliteRbtreeOpen(0, 0, 0, ppBtree);
+ }else
+#endif
+ {
+ return sqliteBtreeOpen(zFilename, omitJournal, nCache, ppBtree);
+ }
+}
diff --git a/src/libs/sqlite2/opcodes.c b/src/libs/sqlite2/opcodes.c
new file mode 100644
index 00000000..0907e0e7
--- /dev/null
+++ b/src/libs/sqlite2/opcodes.c
@@ -0,0 +1,140 @@
+/* Automatically generated file. Do not edit */
+char *sqliteOpcodeNames[] = { "???",
+ "Goto",
+ "Gosub",
+ "Return",
+ "Halt",
+ "Integer",
+ "String",
+ "Variable",
+ "Pop",
+ "Dup",
+ "Pull",
+ "Push",
+ "ColumnName",
+ "Callback",
+ "Concat",
+ "Add",
+ "Subtract",
+ "Multiply",
+ "Divide",
+ "Remainder",
+ "Function",
+ "BitAnd",
+ "BitOr",
+ "ShiftLeft",
+ "ShiftRight",
+ "AddImm",
+ "ForceInt",
+ "MustBeInt",
+ "Eq",
+ "Ne",
+ "Lt",
+ "Le",
+ "Gt",
+ "Ge",
+ "StrEq",
+ "StrNe",
+ "StrLt",
+ "StrLe",
+ "StrGt",
+ "StrGe",
+ "And",
+ "Or",
+ "Negative",
+ "AbsValue",
+ "Not",
+ "BitNot",
+ "Noop",
+ "If",
+ "IfNot",
+ "IsNull",
+ "NotNull",
+ "MakeRecord",
+ "MakeIdxKey",
+ "MakeKey",
+ "IncrKey",
+ "Checkpoint",
+ "Transaction",
+ "Commit",
+ "Rollback",
+ "ReadCookie",
+ "SetCookie",
+ "VerifyCookie",
+ "OpenRead",
+ "OpenWrite",
+ "OpenTemp",
+ "OpenPseudo",
+ "Close",
+ "MoveLt",
+ "MoveTo",
+ "Distinct",
+ "NotFound",
+ "Found",
+ "IsUnique",
+ "NotExists",
+ "NewRecno",
+ "PutIntKey",
+ "PutStrKey",
+ "Delete",
+ "SetCounts",
+ "KeyAsData",
+ "RowKey",
+ "RowData",
+ "Column",
+ "Recno",
+ "FullKey",
+ "NullRow",
+ "Last",
+ "Rewind",
+ "Prev",
+ "Next",
+ "IdxPut",
+ "IdxDelete",
+ "IdxRecno",
+ "IdxLT",
+ "IdxGT",
+ "IdxGE",
+ "IdxIsNull",
+ "Destroy",
+ "Clear",
+ "CreateIndex",
+ "CreateTable",
+ "IntegrityCk",
+ "ListWrite",
+ "ListRewind",
+ "ListRead",
+ "ListReset",
+ "ListPush",
+ "ListPop",
+ "ContextPush",
+ "ContextPop",
+ "SortPut",
+ "SortMakeRec",
+ "SortMakeKey",
+ "Sort",
+ "SortNext",
+ "SortCallback",
+ "SortReset",
+ "FileOpen",
+ "FileRead",
+ "FileColumn",
+ "MemStore",
+ "MemLoad",
+ "MemIncr",
+ "AggReset",
+ "AggInit",
+ "AggFunc",
+ "AggFocus",
+ "AggSet",
+ "AggGet",
+ "AggNext",
+ "SetInsert",
+ "SetFound",
+ "SetNotFound",
+ "SetFirst",
+ "SetNext",
+ "Vacuum",
+ "StackDepth",
+ "StackReset",
+};
diff --git a/src/libs/sqlite2/opcodes.h b/src/libs/sqlite2/opcodes.h
new file mode 100644
index 00000000..35e05069
--- /dev/null
+++ b/src/libs/sqlite2/opcodes.h
@@ -0,0 +1,138 @@
+/* Automatically generated file. Do not edit */
+#define OP_Goto 1
+#define OP_Gosub 2
+#define OP_Return 3
+#define OP_Halt 4
+#define OP_Integer 5
+#define OP_String 6
+#define OP_Variable 7
+#define OP_Pop 8
+#define OP_Dup 9
+#define OP_Pull 10
+#define OP_Push 11
+#define OP_ColumnName 12
+#define OP_Callback 13
+#define OP_Concat 14
+#define OP_Add 15
+#define OP_Subtract 16
+#define OP_Multiply 17
+#define OP_Divide 18
+#define OP_Remainder 19
+#define OP_Function 20
+#define OP_BitAnd 21
+#define OP_BitOr 22
+#define OP_ShiftLeft 23
+#define OP_ShiftRight 24
+#define OP_AddImm 25
+#define OP_ForceInt 26
+#define OP_MustBeInt 27
+#define OP_Eq 28
+#define OP_Ne 29
+#define OP_Lt 30
+#define OP_Le 31
+#define OP_Gt 32
+#define OP_Ge 33
+#define OP_StrEq 34
+#define OP_StrNe 35
+#define OP_StrLt 36
+#define OP_StrLe 37
+#define OP_StrGt 38
+#define OP_StrGe 39
+#define OP_And 40
+#define OP_Or 41
+#define OP_Negative 42
+#define OP_AbsValue 43
+#define OP_Not 44
+#define OP_BitNot 45
+#define OP_Noop 46
+#define OP_If 47
+#define OP_IfNot 48
+#define OP_IsNull 49
+#define OP_NotNull 50
+#define OP_MakeRecord 51
+#define OP_MakeIdxKey 52
+#define OP_MakeKey 53
+#define OP_IncrKey 54
+#define OP_Checkpoint 55
+#define OP_Transaction 56
+#define OP_Commit 57
+#define OP_Rollback 58
+#define OP_ReadCookie 59
+#define OP_SetCookie 60
+#define OP_VerifyCookie 61
+#define OP_OpenRead 62
+#define OP_OpenWrite 63
+#define OP_OpenTemp 64
+#define OP_OpenPseudo 65
+#define OP_Close 66
+#define OP_MoveLt 67
+#define OP_MoveTo 68
+#define OP_Distinct 69
+#define OP_NotFound 70
+#define OP_Found 71
+#define OP_IsUnique 72
+#define OP_NotExists 73
+#define OP_NewRecno 74
+#define OP_PutIntKey 75
+#define OP_PutStrKey 76
+#define OP_Delete 77
+#define OP_SetCounts 78
+#define OP_KeyAsData 79
+#define OP_RowKey 80
+#define OP_RowData 81
+#define OP_Column 82
+#define OP_Recno 83
+#define OP_FullKey 84
+#define OP_NullRow 85
+#define OP_Last 86
+#define OP_Rewind 87
+#define OP_Prev 88
+#define OP_Next 89
+#define OP_IdxPut 90
+#define OP_IdxDelete 91
+#define OP_IdxRecno 92
+#define OP_IdxLT 93
+#define OP_IdxGT 94
+#define OP_IdxGE 95
+#define OP_IdxIsNull 96
+#define OP_Destroy 97
+#define OP_Clear 98
+#define OP_CreateIndex 99
+#define OP_CreateTable 100
+#define OP_IntegrityCk 101
+#define OP_ListWrite 102
+#define OP_ListRewind 103
+#define OP_ListRead 104
+#define OP_ListReset 105
+#define OP_ListPush 106
+#define OP_ListPop 107
+#define OP_ContextPush 108
+#define OP_ContextPop 109
+#define OP_SortPut 110
+#define OP_SortMakeRec 111
+#define OP_SortMakeKey 112
+#define OP_Sort 113
+#define OP_SortNext 114
+#define OP_SortCallback 115
+#define OP_SortReset 116
+#define OP_FileOpen 117
+#define OP_FileRead 118
+#define OP_FileColumn 119
+#define OP_MemStore 120
+#define OP_MemLoad 121
+#define OP_MemIncr 122
+#define OP_AggReset 123
+#define OP_AggInit 124
+#define OP_AggFunc 125
+#define OP_AggFocus 126
+#define OP_AggSet 127
+#define OP_AggGet 128
+#define OP_AggNext 129
+#define OP_SetInsert 130
+#define OP_SetFound 131
+#define OP_SetNotFound 132
+#define OP_SetFirst 133
+#define OP_SetNext 134
+#define OP_Vacuum 135
+#define OP_StackDepth 136
+#define OP_StackReset 137
diff --git a/src/libs/sqlite2/os.c b/src/libs/sqlite2/os.c
new file mode 100644
index 00000000..dccd65f1
--- /dev/null
+++ b/src/libs/sqlite2/os.c
@@ -0,0 +1,1850 @@
+/*
+** 2001 September 16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains code that is specific to particular operating
+** systems. The purpose of this file is to provide a uniform abstraction
+** on which the rest of SQLite can operate.
+*/
+#include "os.h" /* Must be first to enable large file support */
+#include "sqliteInt.h"
+
+#if OS_UNIX
+# include <time.h>
+# include <errno.h>
+# include <unistd.h>
+# ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+# endif
+# ifdef SQLITE_DISABLE_LFS
+# undef O_LARGEFILE
+# define O_LARGEFILE 0
+# endif
+# ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+# endif
+# ifndef O_BINARY
+# define O_BINARY 0
+# endif
+#endif
+
+
+#if OS_WIN
+# include <winbase.h>
+#endif
+
+#if OS_MAC
+# include <extras.h>
+# include <path2fss.h>
+# include <TextUtils.h>
+# include <FinderRegistry.h>
+# include <Folders.h>
+# include <Timer.h>
+# include <OSUtils.h>
+#endif
+
+/*
+** The DJGPP compiler environment looks mostly like Unix, but it
+** lacks the fcntl() system call. So redefine fcntl() to be something
+** that always succeeds. This means that locking does not occur under
+** DJGPP. But its DOS - what did you expect?
+*/
+#ifdef __DJGPP__
+# define fcntl(A,B,C) 0
+#endif
+
+/*
+** Macros used to determine whether or not to use threads. The
+** SQLITE_UNIX_THREADS macro is defined if we are synchronizing for
+** Posix threads and SQLITE_W32_THREADS is defined if we are
+** synchronizing using Win32 threads.
+*/
+#if OS_UNIX && defined(THREADSAFE) && THREADSAFE
+# include <pthread.h>
+# define SQLITE_UNIX_THREADS 1
+#endif
+#if OS_WIN && defined(THREADSAFE) && THREADSAFE
+# define SQLITE_W32_THREADS 1
+#endif
+#if OS_MAC && defined(THREADSAFE) && THREADSAFE
+# include <Multiprocessing.h>
+# define SQLITE_MACOS_MULTITASKING 1
+#endif
+
+/*
+** Macros for performance tracing. Normally turned off
+*/
+#if 0
+static int last_page = 0;
+__inline__ unsigned long long int hwtime(void){
+ unsigned long long int x;
+ __asm__("rdtsc\n\t"
+ "mov %%edx, %%ecx\n\t"
+ :"=A" (x));
+ return x;
+}
+static unsigned long long int g_start;
+static unsigned int elapse;
+#define TIMER_START g_start=hwtime()
+#define TIMER_END elapse=hwtime()-g_start
+#define SEEK(X) last_page=(X)
+#define TRACE1(X) fprintf(stderr,X)
+#define TRACE2(X,Y) fprintf(stderr,X,Y)
+#define TRACE3(X,Y,Z) fprintf(stderr,X,Y,Z)
+#define TRACE4(X,Y,Z,A) fprintf(stderr,X,Y,Z,A)
+#define TRACE5(X,Y,Z,A,B) fprintf(stderr,X,Y,Z,A,B)
+#else
+#define TIMER_START
+#define TIMER_END
+#define SEEK(X)
+#define TRACE1(X)
+#define TRACE2(X,Y)
+#define TRACE3(X,Y,Z)
+#define TRACE4(X,Y,Z,A)
+#define TRACE5(X,Y,Z,A,B)
+#endif
+
+
+#if OS_UNIX
+/*
+** Here is the dirt on POSIX advisory locks: ANSI STD 1003.1 (1996)
+** section 6.5.2.2 lines 483 through 490 specify that when a process
+** sets or clears a lock, that operation overrides any prior locks set
+** by the same process. It does not explicitly say so, but this implies
+** that it overrides locks set by the same process using a different
+** file descriptor. Consider this test case:
+**
+** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644);
+** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644);
+**
+** Suppose ./file1 and ./file2 are really the same file (because
+** one is a hard or symbolic link to the other) then if you set
+** an exclusive lock on fd1, then try to get an exclusive lock
+** on fd2, it works. I would have expected the second lock to
+** fail since there was already a lock on the file due to fd1.
+** But not so. Since both locks came from the same process, the
+** second overrides the first, even though they were on different
+** file descriptors opened on different file names.
+**
+** Bummer. If you ask me, this is broken. Badly broken. It means
+** that we cannot use POSIX locks to synchronize file access among
+** competing threads of the same process. POSIX locks will work fine
+** to synchronize access for threads in separate processes, but not
+** threads within the same process.
+**
+** To work around the problem, SQLite has to manage file locks internally
+** on its own. Whenever a new database is opened, we have to find the
+** specific inode of the database file (the inode is determined by the
+** st_dev and st_ino fields of the stat structure that fstat() fills in)
+** and check for locks already existing on that inode. When locks are
+** created or removed, we have to look at our own internal record of the
+** locks to see if another thread has previously set a lock on that same
+** inode.
+**
+** The OsFile structure for POSIX is no longer just an integer file
+** descriptor. It is now a structure that holds the integer file
+** descriptor and a pointer to a structure that describes the internal
+** locks on the corresponding inode. There is one locking structure
+** per inode, so if the same inode is opened twice, both OsFile structures
+** point to the same locking structure. The locking structure keeps
+** a reference count (so we will know when to delete it) and a "cnt"
+** field that tells us its internal lock status. cnt==0 means the
+** file is unlocked. cnt==-1 means the file has an exclusive lock.
+** cnt>0 means there are cnt shared locks on the file.
+**
+** Any attempt to lock or unlock a file first checks the locking
+** structure. The fcntl() system call is only invoked to set a
+** POSIX lock if the internal lock structure transitions between
+** a locked and an unlocked state.
+**
+** 2004-Jan-11:
+** More recent discoveries about POSIX advisory locks. (The more
+** I discover, the more I realize the a POSIX advisory locks are
+** an abomination.)
+**
+** If you close a file descriptor that points to a file that has locks,
+** all locks on that file that are owned by the current process are
+** released. To work around this problem, each OsFile structure contains
+** a pointer to an openCnt structure. There is one openCnt structure
+** per open inode, which means that multiple OsFiles can point to a single
+** openCnt. When an attempt is made to close an OsFile, if there are
+** other OsFiles open on the same inode that are holding locks, the call
+** to close() the file descriptor is deferred until all of the locks clear.
+** The openCnt structure keeps a list of file descriptors that need to
+** be closed and that list is walked (and cleared) when the last lock
+** clears.
+**
+** First, under Linux threads, because each thread has a separate
+** process ID, lock operations in one thread do not override locks
+** to the same file in other threads. Linux threads behave like
+** separate processes in this respect. But, if you close a file
+** descriptor in linux threads, all locks are cleared, even locks
+** on other threads and even though the other threads have different
+** process IDs. Linux threads is inconsistent in this respect.
+** (I'm beginning to think that linux threads is an abomination too.)
+** The consequence of this all is that the hash table for the lockInfo
+** structure has to include the process id as part of its key because
+** locks in different threads are treated as distinct. But the
+** openCnt structure should not include the process id in its
+** key because close() clears lock on all threads, not just the current
+** thread. Were it not for this goofiness in linux threads, we could
+** combine the lockInfo and openCnt structures into a single structure.
+*/
+
+/*
+** An instance of the following structure serves as the key used
+** to locate a particular lockInfo structure given its inode. Note
+** that we have to include the process ID as part of the key. On some
+** threading implementations (ex: linux), each thread has a separate
+** process ID.
+*/
+struct lockKey {
+ dev_t dev; /* Device number */
+ ino_t ino; /* Inode number */
+ pid_t pid; /* Process ID */
+};
+
+/*
+** An instance of the following structure is allocated for each open
+** inode on each thread with a different process ID. (Threads have
+** different process IDs on linux, but not on most other unixes.)
+**
+** A single inode can have multiple file descriptors, so each OsFile
+** structure contains a pointer to an instance of this object and this
+** object keeps a count of the number of OsFiles pointing to it.
+*/
+struct lockInfo {
+ struct lockKey key; /* The lookup key */
+ int cnt; /* 0: unlocked. -1: write lock. 1...: read lock. */
+ int nRef; /* Number of pointers to this structure */
+};
+
+/*
+** An instance of the following structure serves as the key used
+** to locate a particular openCnt structure given its inode. This
+** is the same as the lockKey except that the process ID is omitted.
+*/
+struct openKey {
+ dev_t dev; /* Device number */
+ ino_t ino; /* Inode number */
+};
+
+/*
+** An instance of the following structure is allocated for each open
+** inode. This structure keeps track of the number of locks on that
+** inode. If a close is attempted against an inode that is holding
+** locks, the close is deferred until all locks clear by adding the
+** file descriptor to be closed to the pending list.
+*/
+struct openCnt {
+ struct openKey key; /* The lookup key */
+ int nRef; /* Number of pointers to this structure */
+ int nLock; /* Number of outstanding locks */
+ int nPending; /* Number of pending close() operations */
+ int *aPending; /* Malloced space holding fd's awaiting a close() */
+};
+
+/*
+** These hash table maps inodes and process IDs into lockInfo and openCnt
+** structures. Access to these hash tables must be protected by a mutex.
+*/
+static Hash lockHash = { SQLITE_HASH_BINARY, 0, 0, 0, 0, 0 };
+static Hash openHash = { SQLITE_HASH_BINARY, 0, 0, 0, 0, 0 };
+
+/*
+** Release a lockInfo structure previously allocated by findLockInfo().
+*/
+static void releaseLockInfo(struct lockInfo *pLock){
+ pLock->nRef--;
+ if( pLock->nRef==0 ){
+ sqliteHashInsert(&lockHash, &pLock->key, sizeof(pLock->key), 0);
+ sqliteFree(pLock);
+ }
+}
+
+/*
+** Release a openCnt structure previously allocated by findLockInfo().
+*/
+static void releaseOpenCnt(struct openCnt *pOpen){
+ pOpen->nRef--;
+ if( pOpen->nRef==0 ){
+ sqliteHashInsert(&openHash, &pOpen->key, sizeof(pOpen->key), 0);
+ sqliteFree(pOpen->aPending);
+ sqliteFree(pOpen);
+ }
+}
+
+/*
+** Given a file descriptor, locate lockInfo and openCnt structures that
+** describes that file descriptor. Create a new ones if necessary. The
+** return values might be unset if an error occurs.
+**
+** Return the number of errors.
+*/
+int findLockInfo(
+ int fd, /* The file descriptor used in the key */
+ struct lockInfo **ppLock, /* Return the lockInfo structure here */
+ struct openCnt **ppOpen /* Return the openCnt structure here */
+){
+ int rc;
+ struct lockKey key1;
+ struct openKey key2;
+ struct stat statbuf;
+ struct lockInfo *pLock;
+ struct openCnt *pOpen;
+ rc = fstat(fd, &statbuf);
+ if( rc!=0 ) return 1;
+ memset(&key1, 0, sizeof(key1));
+ key1.dev = statbuf.st_dev;
+ key1.ino = statbuf.st_ino;
+ key1.pid = getpid();
+ memset(&key2, 0, sizeof(key2));
+ key2.dev = statbuf.st_dev;
+ key2.ino = statbuf.st_ino;
+ pLock = (struct lockInfo*)sqliteHashFind(&lockHash, &key1, sizeof(key1));
+ if( pLock==0 ){
+ struct lockInfo *pOld;
+ pLock = sqliteMallocRaw( sizeof(*pLock) );
+ if( pLock==0 ) return 1;
+ pLock->key = key1;
+ pLock->nRef = 1;
+ pLock->cnt = 0;
+ pOld = sqliteHashInsert(&lockHash, &pLock->key, sizeof(key1), pLock);
+ if( pOld!=0 ){
+ assert( pOld==pLock );
+ sqliteFree(pLock);
+ return 1;
+ }
+ }else{
+ pLock->nRef++;
+ }
+ *ppLock = pLock;
+ pOpen = (struct openCnt*)sqliteHashFind(&openHash, &key2, sizeof(key2));
+ if( pOpen==0 ){
+ struct openCnt *pOld;
+ pOpen = sqliteMallocRaw( sizeof(*pOpen) );
+ if( pOpen==0 ){
+ releaseLockInfo(pLock);
+ return 1;
+ }
+ pOpen->key = key2;
+ pOpen->nRef = 1;
+ pOpen->nLock = 0;
+ pOpen->nPending = 0;
+ pOpen->aPending = 0;
+ pOld = sqliteHashInsert(&openHash, &pOpen->key, sizeof(key2), pOpen);
+ if( pOld!=0 ){
+ assert( pOld==pOpen );
+ sqliteFree(pOpen);
+ releaseLockInfo(pLock);
+ return 1;
+ }
+ }else{
+ pOpen->nRef++;
+ }
+ *ppOpen = pOpen;
+ return 0;
+}
+
+#endif /** POSIX advisory lock work-around **/
+
+/*
+** If we compile with the SQLITE_TEST macro set, then the following block
+** of code will give us the ability to simulate a disk I/O error. This
+** is used for testing the I/O recovery logic.
+*/
+#ifdef SQLITE_TEST
+int sqlite_io_error_pending = 0;
+#define SimulateIOError(A) \
+ if( sqlite_io_error_pending ) \
+ if( sqlite_io_error_pending-- == 1 ){ local_ioerr(); return A; }
+static void local_ioerr(){
+ sqlite_io_error_pending = 0; /* Really just a place to set a breakpoint */
+}
+#else
+#define SimulateIOError(A)
+#endif
+
+/*
+** When testing, keep a count of the number of open files.
+*/
+#ifdef SQLITE_TEST
+int sqlite_open_file_count = 0;
+#define OpenCounter(X) sqlite_open_file_count+=(X)
+#else
+#define OpenCounter(X)
+#endif
+
+
+/*
+** Delete the named file
+*/
+int sqliteOsDelete(const char *zFilename){
+#if OS_UNIX
+ unlink(zFilename);
+#endif
+#if OS_WIN
+ DeleteFile(zFilename);
+#endif
+#if OS_MAC
+ unlink(zFilename);
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the named file exists.
+*/
+int sqliteOsFileExists(const char *zFilename){
+#if OS_UNIX
+ return access(zFilename, 0)==0;
+#endif
+#if OS_WIN
+ return GetFileAttributes(zFilename) != 0xffffffff;
+#endif
+#if OS_MAC
+ return access(zFilename, 0)==0;
+#endif
+}
+
+
+#if 0 /* NOT USED */
+/*
+** Change the name of an existing file.
+*/
+int sqliteOsFileRename(const char *zOldName, const char *zNewName){
+#if OS_UNIX
+ if( link(zOldName, zNewName) ){
+ return SQLITE_ERROR;
+ }
+ unlink(zOldName);
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ if( !MoveFile(zOldName, zNewName) ){
+ return SQLITE_ERROR;
+ }
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+ /**** FIX ME ***/
+ return SQLITE_ERROR;
+#endif
+}
+#endif /* NOT USED */
+
+/*
+** Attempt to open a file for both reading and writing. If that
+** fails, try opening it read-only. If the file does not exist,
+** try to create it.
+**
+** On success, a handle for the open file is written to *id
+** and *pReadonly is set to 0 if the file was opened for reading and
+** writing or 1 if the file was opened read-only. The function returns
+** SQLITE_OK.
+**
+** On failure, the function returns SQLITE_CANTOPEN and leaves
+** *id and *pReadonly unchanged.
+*/
+int sqliteOsOpenReadWrite(
+ const char *zFilename,
+ OsFile *id,
+ int *pReadonly
+){
+#if OS_UNIX
+ int rc;
+ id->dirfd = -1;
+ id->fd = open(zFilename, O_RDWR|O_CREAT|O_LARGEFILE|O_BINARY, 0644);
+ if( id->fd<0 ){
+#ifdef EISDIR
+ if( errno==EISDIR ){
+ return SQLITE_CANTOPEN;
+ }
+#endif
+ id->fd = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY);
+ if( id->fd<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ *pReadonly = 1;
+ }else{
+ *pReadonly = 0;
+ }
+ sqliteOsEnterMutex();
+ rc = findLockInfo(id->fd, &id->pLock, &id->pOpen);
+ sqliteOsLeaveMutex();
+ if( rc ){
+ close(id->fd);
+ return SQLITE_NOMEM;
+ }
+ id->locked = 0;
+ TRACE3("OPEN %-3d %s\n", id->fd, zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ HANDLE h = CreateFile(zFilename,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ if( h==INVALID_HANDLE_VALUE ){
+ h = CreateFile(zFilename,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ if( h==INVALID_HANDLE_VALUE ){
+ return SQLITE_CANTOPEN;
+ }
+ *pReadonly = 1;
+ }else{
+ *pReadonly = 0;
+ }
+ id->h = h;
+ id->locked = 0;
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+ FSSpec fsSpec;
+# ifdef _LARGE_FILE
+ HFSUniStr255 dfName;
+ FSRef fsRef;
+ if( __path2fss(zFilename, &fsSpec) != noErr ){
+ if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr )
+ return SQLITE_CANTOPEN;
+ }
+ if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr )
+ return SQLITE_CANTOPEN;
+ FSGetDataForkName(&dfName);
+ if( FSOpenFork(&fsRef, dfName.length, dfName.unicode,
+ fsRdWrShPerm, &(id->refNum)) != noErr ){
+ if( FSOpenFork(&fsRef, dfName.length, dfName.unicode,
+ fsRdWrPerm, &(id->refNum)) != noErr ){
+ if (FSOpenFork(&fsRef, dfName.length, dfName.unicode,
+ fsRdPerm, &(id->refNum)) != noErr )
+ return SQLITE_CANTOPEN;
+ else
+ *pReadonly = 1;
+ } else
+ *pReadonly = 0;
+ } else
+ *pReadonly = 0;
+# else
+ __path2fss(zFilename, &fsSpec);
+ if( !sqliteOsFileExists(zFilename) ){
+ if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr )
+ return SQLITE_CANTOPEN;
+ }
+ if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNum)) != noErr ){
+ if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrPerm, &(id->refNum)) != noErr ){
+ if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdPerm, &(id->refNum)) != noErr )
+ return SQLITE_CANTOPEN;
+ else
+ *pReadonly = 1;
+ } else
+ *pReadonly = 0;
+ } else
+ *pReadonly = 0;
+# endif
+ if( HOpenRF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNumRF)) != noErr){
+ id->refNumRF = -1;
+ }
+ id->locked = 0;
+ id->delOnClose = 0;
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+}
+
+
+/*
+** Attempt to open a new file for exclusive access by this process.
+** The file will be opened for both reading and writing. To avoid
+** a potential security problem, we do not allow the file to have
+** previously existed. Nor do we allow the file to be a symbolic
+** link.
+**
+** If delFlag is true, then make arrangements to automatically delete
+** the file when it is closed.
+**
+** On success, write the file handle into *id and return SQLITE_OK.
+**
+** On failure, return SQLITE_CANTOPEN.
+*/
+int sqliteOsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){
+#if OS_UNIX
+ int rc;
+ if( access(zFilename, 0)==0 ){
+ return SQLITE_CANTOPEN;
+ }
+ id->dirfd = -1;
+ id->fd = open(zFilename,
+ O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW|O_LARGEFILE|O_BINARY, 0600);
+ if( id->fd<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ sqliteOsEnterMutex();
+ rc = findLockInfo(id->fd, &id->pLock, &id->pOpen);
+ sqliteOsLeaveMutex();
+ if( rc ){
+ close(id->fd);
+ unlink(zFilename);
+ return SQLITE_NOMEM;
+ }
+ id->locked = 0;
+ if( delFlag ){
+ unlink(zFilename);
+ }
+ TRACE3("OPEN-EX %-3d %s\n", id->fd, zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ HANDLE h;
+ int fileflags;
+ if( delFlag ){
+ fileflags = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_RANDOM_ACCESS
+ | FILE_FLAG_DELETE_ON_CLOSE;
+ }else{
+ fileflags = FILE_FLAG_RANDOM_ACCESS;
+ }
+ h = CreateFile(zFilename,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ fileflags,
+ NULL
+ );
+ if( h==INVALID_HANDLE_VALUE ){
+ return SQLITE_CANTOPEN;
+ }
+ id->h = h;
+ id->locked = 0;
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+ FSSpec fsSpec;
+# ifdef _LARGE_FILE
+ HFSUniStr255 dfName;
+ FSRef fsRef;
+ __path2fss(zFilename, &fsSpec);
+ if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr )
+ return SQLITE_CANTOPEN;
+ if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr )
+ return SQLITE_CANTOPEN;
+ FSGetDataForkName(&dfName);
+ if( FSOpenFork(&fsRef, dfName.length, dfName.unicode,
+ fsRdWrPerm, &(id->refNum)) != noErr )
+ return SQLITE_CANTOPEN;
+# else
+ __path2fss(zFilename, &fsSpec);
+ if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr )
+ return SQLITE_CANTOPEN;
+ if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrPerm, &(id->refNum)) != noErr )
+ return SQLITE_CANTOPEN;
+# endif
+ id->refNumRF = -1;
+ id->locked = 0;
+ id->delOnClose = delFlag;
+ if (delFlag)
+ id->pathToDel = sqliteOsFullPathname(zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+}
+
+/*
+** Attempt to open a new file for read-only access.
+**
+** On success, write the file handle into *id and return SQLITE_OK.
+**
+** On failure, return SQLITE_CANTOPEN.
+*/
+int sqliteOsOpenReadOnly(const char *zFilename, OsFile *id){
+#if OS_UNIX
+ int rc;
+ id->dirfd = -1;
+ id->fd = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY);
+ if( id->fd<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ sqliteOsEnterMutex();
+ rc = findLockInfo(id->fd, &id->pLock, &id->pOpen);
+ sqliteOsLeaveMutex();
+ if( rc ){
+ close(id->fd);
+ return SQLITE_NOMEM;
+ }
+ id->locked = 0;
+ TRACE3("OPEN-RO %-3d %s\n", id->fd, zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ HANDLE h = CreateFile(zFilename,
+ GENERIC_READ,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ if( h==INVALID_HANDLE_VALUE ){
+ return SQLITE_CANTOPEN;
+ }
+ id->h = h;
+ id->locked = 0;
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+ FSSpec fsSpec;
+# ifdef _LARGE_FILE
+ HFSUniStr255 dfName;
+ FSRef fsRef;
+ if( __path2fss(zFilename, &fsSpec) != noErr )
+ return SQLITE_CANTOPEN;
+ if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr )
+ return SQLITE_CANTOPEN;
+ FSGetDataForkName(&dfName);
+ if( FSOpenFork(&fsRef, dfName.length, dfName.unicode,
+ fsRdPerm, &(id->refNum)) != noErr )
+ return SQLITE_CANTOPEN;
+# else
+ __path2fss(zFilename, &fsSpec);
+ if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdPerm, &(id->refNum)) != noErr )
+ return SQLITE_CANTOPEN;
+# endif
+ if( HOpenRF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNumRF)) != noErr){
+ id->refNumRF = -1;
+ }
+ id->locked = 0;
+ id->delOnClose = 0;
+ OpenCounter(+1);
+ return SQLITE_OK;
+#endif
+}
+
+/*
+** Attempt to open a file descriptor for the directory that contains a
+** file. This file descriptor can be used to fsync() the directory
+** in order to make sure the creation of a new file is actually written
+** to disk.
+**
+** This routine is only meaningful for Unix. It is a no-op under
+** windows since windows does not support hard links.
+**
+** On success, a handle for a previously open file is at *id is
+** updated with the new directory file descriptor and SQLITE_OK is
+** returned.
+**
+** On failure, the function returns SQLITE_CANTOPEN and leaves
+** *id unchanged.
+*/
+int sqliteOsOpenDirectory(
+ const char *zDirname,
+ OsFile *id
+){
+#if OS_UNIX
+ if( id->fd<0 ){
+ /* Do not open the directory if the corresponding file is not already
+ ** open. */
+ return SQLITE_CANTOPEN;
+ }
+ assert( id->dirfd<0 );
+ id->dirfd = open(zDirname, O_RDONLY|O_BINARY, 0644);
+ if( id->dirfd<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ TRACE3("OPENDIR %-3d %s\n", id->dirfd, zDirname);
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** If the following global variable points to a string which is the
+** name of a directory, then that directory will be used to store
+** temporary files.
+*/
+const char *sqlite_temp_directory = 0;
+
+/*
+** Create a temporary file name in zBuf. zBuf must be big enough to
+** hold at least SQLITE_TEMPNAME_SIZE characters.
+*/
+int sqliteOsTempFileName(char *zBuf){
+#if OS_UNIX
+ static const char *azDirs[] = {
+ 0,
+ "/var/tmp",
+ "/usr/tmp",
+ "/tmp",
+ ".",
+ };
+ static unsigned char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ struct stat buf;
+ const char *zDir = ".";
+ azDirs[0] = sqlite_temp_directory;
+ for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); i++){
+ if( azDirs[i]==0 ) continue;
+ if( stat(azDirs[i], &buf) ) continue;
+ if( !S_ISDIR(buf.st_mode) ) continue;
+ if( access(azDirs[i], 07) ) continue;
+ zDir = azDirs[i];
+ break;
+ }
+ do{
+ sprintf(zBuf, "%s/"TEMP_FILE_PREFIX, zDir);
+ j = strlen(zBuf);
+ sqliteRandomness(15, &zBuf[j]);
+ for(i=0; i<15; i++, j++){
+ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
+ }
+ zBuf[j] = 0;
+ }while( access(zBuf,0)==0 );
+#endif
+#if OS_WIN
+ static char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ const char *zDir;
+ char zTempPath[SQLITE_TEMPNAME_SIZE];
+ if( sqlite_temp_directory==0 ){
+ GetTempPath(SQLITE_TEMPNAME_SIZE-30, zTempPath);
+ for(i=strlen(zTempPath); i>0 && zTempPath[i-1]=='\\'; i--){}
+ zTempPath[i] = 0;
+ zDir = zTempPath;
+ }else{
+ zDir = sqlite_temp_directory;
+ }
+ for(;;){
+ sprintf(zBuf, "%s\\"TEMP_FILE_PREFIX, zDir);
+ j = strlen(zBuf);
+ sqliteRandomness(15, &zBuf[j]);
+ for(i=0; i<15; i++, j++){
+ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
+ }
+ zBuf[j] = 0;
+ if( !sqliteOsFileExists(zBuf) ) break;
+ }
+#endif
+#if OS_MAC
+ static char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ char *zDir;
+ char zTempPath[SQLITE_TEMPNAME_SIZE];
+ char zdirName[32];
+ CInfoPBRec infoRec;
+ Str31 dirName;
+ memset(&infoRec, 0, sizeof(infoRec));
+ memset(zTempPath, 0, SQLITE_TEMPNAME_SIZE);
+ if( sqlite_temp_directory!=0 ){
+ zDir = sqlite_temp_directory;
+ }else if( FindFolder(kOnSystemDisk, kTemporaryFolderType, kCreateFolder,
+ &(infoRec.dirInfo.ioVRefNum), &(infoRec.dirInfo.ioDrParID)) == noErr ){
+ infoRec.dirInfo.ioNamePtr = dirName;
+ do{
+ infoRec.dirInfo.ioFDirIndex = -1;
+ infoRec.dirInfo.ioDrDirID = infoRec.dirInfo.ioDrParID;
+ if( PBGetCatInfoSync(&infoRec) == noErr ){
+ CopyPascalStringToC(dirName, zdirName);
+ i = strlen(zdirName);
+ memmove(&(zTempPath[i+1]), zTempPath, strlen(zTempPath));
+ strcpy(zTempPath, zdirName);
+ zTempPath[i] = ':';
+ }else{
+ *zTempPath = 0;
+ break;
+ }
+ } while( infoRec.dirInfo.ioDrDirID != fsRtDirID );
+ zDir = zTempPath;
+ }
+ if( zDir[0]==0 ){
+ getcwd(zTempPath, SQLITE_TEMPNAME_SIZE-24);
+ zDir = zTempPath;
+ }
+ for(;;){
+ sprintf(zBuf, "%s"TEMP_FILE_PREFIX, zDir);
+ j = strlen(zBuf);
+ sqliteRandomness(15, &zBuf[j]);
+ for(i=0; i<15; i++, j++){
+ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
+ }
+ zBuf[j] = 0;
+ if( !sqliteOsFileExists(zBuf) ) break;
+ }
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** Close a file.
+*/
+int sqliteOsClose(OsFile *id){
+#if OS_UNIX
+ sqliteOsUnlock(id);
+ if( id->dirfd>=0 ) close(id->dirfd);
+ id->dirfd = -1;
+ sqliteOsEnterMutex();
+ if( id->pOpen->nLock ){
+ /* If there are outstanding locks, do not actually close the file just
+ ** yet because that would clear those locks. Instead, add the file
+ ** descriptor to pOpen->aPending. It will be automatically closed when
+ ** the last lock is cleared.
+ */
+ int *aNew;
+ struct openCnt *pOpen = id->pOpen;
+ pOpen->nPending++;
+ aNew = sqliteRealloc( pOpen->aPending, pOpen->nPending*sizeof(int) );
+ if( aNew==0 ){
+ /* If a malloc fails, just leak the file descriptor */
+ }else{
+ pOpen->aPending = aNew;
+ pOpen->aPending[pOpen->nPending-1] = id->fd;
+ }
+ }else{
+ /* There are no outstanding locks so we can close the file immediately */
+ close(id->fd);
+ }
+ releaseLockInfo(id->pLock);
+ releaseOpenCnt(id->pOpen);
+ sqliteOsLeaveMutex();
+ TRACE2("CLOSE %-3d\n", id->fd);
+ OpenCounter(-1);
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ CloseHandle(id->h);
+ OpenCounter(-1);
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+ if( id->refNumRF!=-1 )
+ FSClose(id->refNumRF);
+# ifdef _LARGE_FILE
+ FSCloseFork(id->refNum);
+# else
+ FSClose(id->refNum);
+# endif
+ if( id->delOnClose ){
+ unlink(id->pathToDel);
+ sqliteFree(id->pathToDel);
+ }
+ OpenCounter(-1);
+ return SQLITE_OK;
+#endif
+}
+
+/*
+** Read data from a file into a buffer. Return SQLITE_OK if all
+** bytes were read successfully and SQLITE_IOERR if anything goes
+** wrong.
+*/
+int sqliteOsRead(OsFile *id, void *pBuf, int amt){
+#if OS_UNIX
+ int got;
+ SimulateIOError(SQLITE_IOERR);
+ TIMER_START;
+ got = read(id->fd, pBuf, amt);
+ TIMER_END;
+ TRACE4("READ %-3d %7d %d\n", id->fd, last_page, elapse);
+ SEEK(0);
+ /* if( got<0 ) got = 0; */
+ if( got==amt ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+#endif
+#if OS_WIN
+ DWORD got;
+ SimulateIOError(SQLITE_IOERR);
+ TRACE2("READ %d\n", last_page);
+ if( !ReadFile(id->h, pBuf, amt, &got, 0) ){
+ got = 0;
+ }
+ if( got==(DWORD)amt ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+#endif
+#if OS_MAC
+ int got;
+ SimulateIOError(SQLITE_IOERR);
+ TRACE2("READ %d\n", last_page);
+# ifdef _LARGE_FILE
+ FSReadFork(id->refNum, fsAtMark, 0, (ByteCount)amt, pBuf, (ByteCount*)&got);
+# else
+ got = amt;
+ FSRead(id->refNum, &got, pBuf);
+# endif
+ if( got==amt ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+#endif
+}
+
+/*
+** Write data from a buffer into a file. Return SQLITE_OK on success
+** or some other error code on failure.
+*/
+int sqliteOsWrite(OsFile *id, const void *pBuf, int amt){
+#if OS_UNIX
+ int wrote = 0;
+ SimulateIOError(SQLITE_IOERR);
+ TIMER_START;
+ while( amt>0 && (wrote = write(id->fd, pBuf, amt))>0 ){
+ amt -= wrote;
+ pBuf = &((char*)pBuf)[wrote];
+ }
+ TIMER_END;
+ TRACE4("WRITE %-3d %7d %d\n", id->fd, last_page, elapse);
+ SEEK(0);
+ if( amt>0 ){
+ return SQLITE_FULL;
+ }
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ int rc;
+ DWORD wrote;
+ SimulateIOError(SQLITE_IOERR);
+ TRACE2("WRITE %d\n", last_page);
+ while( amt>0 && (rc = WriteFile(id->h, pBuf, amt, &wrote, 0))!=0 && wrote>0 ){
+ amt -= wrote;
+ pBuf = &((char*)pBuf)[wrote];
+ }
+ if( !rc || amt>(int)wrote ){
+ return SQLITE_FULL;
+ }
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+ OSErr oserr;
+ int wrote = 0;
+ SimulateIOError(SQLITE_IOERR);
+ TRACE2("WRITE %d\n", last_page);
+ while( amt>0 ){
+# ifdef _LARGE_FILE
+ oserr = FSWriteFork(id->refNum, fsAtMark, 0,
+ (ByteCount)amt, pBuf, (ByteCount*)&wrote);
+# else
+ wrote = amt;
+ oserr = FSWrite(id->refNum, &wrote, pBuf);
+# endif
+ if( wrote == 0 || oserr != noErr)
+ break;
+ amt -= wrote;
+ pBuf = &((char*)pBuf)[wrote];
+ }
+ if( oserr != noErr || amt>wrote ){
+ return SQLITE_FULL;
+ }
+ return SQLITE_OK;
+#endif
+}
+
+/*
+** Move the read/write pointer in a file.
+*/
+int sqliteOsSeek(OsFile *id, off_t offset){
+ SEEK(offset/1024 + 1);
+#if OS_UNIX
+ lseek(id->fd, offset, SEEK_SET);
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ {
+ LONG upperBits = offset>>32;
+ LONG lowerBits = offset & 0xffffffff;
+ DWORD rc;
+ rc = SetFilePointer(id->h, lowerBits, &upperBits, FILE_BEGIN);
+ /* TRACE3("SEEK rc=0x%x upper=0x%x\n", rc, upperBits); */
+ }
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+ {
+ off_t curSize;
+ if( sqliteOsFileSize(id, &curSize) != SQLITE_OK ){
+ return SQLITE_IOERR;
+ }
+ if( offset >= curSize ){
+ if( sqliteOsTruncate(id, offset+1) != SQLITE_OK ){
+ return SQLITE_IOERR;
+ }
+ }
+# ifdef _LARGE_FILE
+ if( FSSetForkPosition(id->refNum, fsFromStart, offset) != noErr ){
+# else
+ if( SetFPos(id->refNum, fsFromStart, offset) != noErr ){
+# endif
+ return SQLITE_IOERR;
+ }else{
+ return SQLITE_OK;
+ }
+ }
+#endif
+}
+
+#ifdef SQLITE_NOSYNC
+# define fsync(X) 0
+#endif
+
+/*
+** Make sure all writes to a particular file are committed to disk.
+**
+** Under Unix, also make sure that the directory entry for the file
+** has been created by fsync-ing the directory that contains the file.
+** If we do not do this and we encounter a power failure, the directory
+** entry for the journal might not exist after we reboot. The next
+** SQLite to access the file will not know that the journal exists (because
+** the directory entry for the journal was never created) and the transaction
+** will not roll back - possibly leading to database corruption.
+*/
+int sqliteOsSync(OsFile *id){
+#if OS_UNIX
+ SimulateIOError(SQLITE_IOERR);
+ TRACE2("SYNC %-3d\n", id->fd);
+ if( fsync(id->fd) ){
+ return SQLITE_IOERR;
+ }else{
+ if( id->dirfd>=0 ){
+ TRACE2("DIRSYNC %-3d\n", id->dirfd);
+ fsync(id->dirfd);
+ close(id->dirfd); /* Only need to sync once, so close the directory */
+ id->dirfd = -1; /* when we are done. */
+ }
+ return SQLITE_OK;
+ }
+#endif
+#if OS_WIN
+ if( FlushFileBuffers(id->h) ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+#endif
+#if OS_MAC
+# ifdef _LARGE_FILE
+ if( FSFlushFork(id->refNum) != noErr ){
+# else
+ ParamBlockRec params;
+ memset(&params, 0, sizeof(ParamBlockRec));
+ params.ioParam.ioRefNum = id->refNum;
+ if( PBFlushFileSync(&params) != noErr ){
+# endif
+ return SQLITE_IOERR;
+ }else{
+ return SQLITE_OK;
+ }
+#endif
+}
+
+/*
+** Truncate an open file to a specified size
+*/
+int sqliteOsTruncate(OsFile *id, off_t nByte){
+ SimulateIOError(SQLITE_IOERR);
+#if OS_UNIX
+ return ftruncate(id->fd, nByte)==0 ? SQLITE_OK : SQLITE_IOERR;
+#endif
+#if OS_WIN
+ {
+ LONG upperBits = nByte>>32;
+ SetFilePointer(id->h, nByte, &upperBits, FILE_BEGIN);
+ SetEndOfFile(id->h);
+ }
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+# ifdef _LARGE_FILE
+ if( FSSetForkSize(id->refNum, fsFromStart, nByte) != noErr){
+# else
+ if( SetEOF(id->refNum, nByte) != noErr ){
+# endif
+ return SQLITE_IOERR;
+ }else{
+ return SQLITE_OK;
+ }
+#endif
+}
+
+/*
+** Determine the current size of a file in bytes
+*/
+int sqliteOsFileSize(OsFile *id, off_t *pSize){
+#if OS_UNIX
+ struct stat buf;
+ SimulateIOError(SQLITE_IOERR);
+ if( fstat(id->fd, &buf)!=0 ){
+ return SQLITE_IOERR;
+ }
+ *pSize = buf.st_size;
+ return SQLITE_OK;
+#endif
+#if OS_WIN
+ DWORD upperBits, lowerBits;
+ SimulateIOError(SQLITE_IOERR);
+ lowerBits = GetFileSize(id->h, &upperBits);
+ *pSize = (((off_t)upperBits)<<32) + lowerBits;
+ return SQLITE_OK;
+#endif
+#if OS_MAC
+# ifdef _LARGE_FILE
+ if( FSGetForkSize(id->refNum, pSize) != noErr){
+# else
+ if( GetEOF(id->refNum, pSize) != noErr ){
+# endif
+ return SQLITE_IOERR;
+ }else{
+ return SQLITE_OK;
+ }
+#endif
+}
+
+#if OS_WIN
+/*
+** Return true (non-zero) if we are running under WinNT, Win2K or WinXP.
+** Return false (zero) for Win95, Win98, or WinME.
+**
+** Here is an interesting observation: Win95, Win98, and WinME lack
+** the LockFileEx() API. But we can still statically link against that
+** API as long as we don't call it win running Win95/98/ME. A call to
+** this routine is used to determine if the host is Win95/98/ME or
+** WinNT/2K/XP so that we will know whether or not we can safely call
+** the LockFileEx() API.
+*/
+int isNT(void){
+ static int osType = 0; /* 0=unknown 1=win95 2=winNT */
+ if( osType==0 ){
+ OSVERSIONINFO sInfo;
+ sInfo.dwOSVersionInfoSize = sizeof(sInfo);
+ GetVersionEx(&sInfo);
+ osType = sInfo.dwPlatformId==VER_PLATFORM_WIN32_NT ? 2 : 1;
+ }
+ return osType==2;
+}
+#endif
+
+/*
+** Windows file locking notes: [similar issues apply to MacOS]
+**
+** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because
+** those functions are not available. So we use only LockFile() and
+** UnlockFile().
+**
+** LockFile() prevents not just writing but also reading by other processes.
+** (This is a design error on the part of Windows, but there is nothing
+** we can do about that.) So the region used for locking is at the
+** end of the file where it is unlikely to ever interfere with an
+** actual read attempt.
+**
+** A database read lock is obtained by locking a single randomly-chosen
+** byte out of a specific range of bytes. The lock byte is obtained at
+** random so two separate readers can probably access the file at the
+** same time, unless they are unlucky and choose the same lock byte.
+** A database write lock is obtained by locking all bytes in the range.
+** There can only be one writer.
+**
+** A lock is obtained on the first byte of the lock range before acquiring
+** either a read lock or a write lock. This prevents two processes from
+** attempting to get a lock at a same time. The semantics of
+** sqliteOsReadLock() require that if there is already a write lock, that
+** lock is converted into a read lock atomically. The lock on the first
+** byte allows us to drop the old write lock and get the read lock without
+** another process jumping into the middle and messing us up. The same
+** argument applies to sqliteOsWriteLock().
+**
+** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available,
+** which means we can use reader/writer locks. When reader writer locks
+** are used, the lock is placed on the same range of bytes that is used
+** for probabilistic locking in Win95/98/ME. Hence, the locking scheme
+** will support two or more Win95 readers or two or more WinNT readers.
+** But a single Win95 reader will lock out all WinNT readers and a single
+** WinNT reader will lock out all other Win95 readers.
+**
+** Note: On MacOS we use the resource fork for locking.
+**
+** The following #defines specify the range of bytes used for locking.
+** N_LOCKBYTE is the number of bytes available for doing the locking.
+** The first byte used to hold the lock while the lock is changing does
+** not count toward this number. FIRST_LOCKBYTE is the address of
+** the first byte in the range of bytes used for locking.
+*/
+#define N_LOCKBYTE 10239
+#if OS_MAC
+# define FIRST_LOCKBYTE (0x000fffff - N_LOCKBYTE)
+#else
+# define FIRST_LOCKBYTE (0xffffffff - N_LOCKBYTE)
+#endif
+
+/*
+** Change the status of the lock on the file "id" to be a readlock.
+** If the file was write locked, then this reduces the lock to a read.
+** If the file was read locked, then this acquires a new read lock.
+**
+** Return SQLITE_OK on success and SQLITE_BUSY on failure. If this
+** library was compiled with large file support (LFS) but LFS is not
+** available on the host, then an SQLITE_NOLFS is returned.
+*/
+int sqliteOsReadLock(OsFile *id){
+#if OS_UNIX
+ int rc;
+ sqliteOsEnterMutex();
+ if( id->pLock->cnt>0 ){
+ if( !id->locked ){
+ id->pLock->cnt++;
+ id->locked = 1;
+ id->pOpen->nLock++;
+ }
+ rc = SQLITE_OK;
+ }else if( id->locked || id->pLock->cnt==0 ){
+ struct flock lock;
+ int s;
+ lock.l_type = F_RDLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = lock.l_len = 0L;
+ s = fcntl(id->fd, F_SETLK, &lock);
+ if( s!=0 ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ }else{
+ rc = SQLITE_OK;
+ if( !id->locked ){
+ id->pOpen->nLock++;
+ id->locked = 1;
+ }
+ id->pLock->cnt = 1;
+ }
+ }else{
+ rc = SQLITE_BUSY;
+ }
+ sqliteOsLeaveMutex();
+ return rc;
+#endif
+#if OS_WIN
+ int rc;
+ if( id->locked>0 ){
+ rc = SQLITE_OK;
+ }else{
+ int lk;
+ int res;
+ int cnt = 100;
+ sqliteRandomness(sizeof(lk), &lk);
+ lk = (lk & 0x7fffffff)%N_LOCKBYTE + 1;
+ while( cnt-->0 && (res = LockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0))==0 ){
+ Sleep(1);
+ }
+ if( res ){
+ UnlockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0);
+ if( isNT() ){
+ OVERLAPPED ovlp;
+ ovlp.Offset = FIRST_LOCKBYTE+1;
+ ovlp.OffsetHigh = 0;
+ ovlp.hEvent = 0;
+ res = LockFileEx(id->h, LOCKFILE_FAIL_IMMEDIATELY,
+ 0, N_LOCKBYTE, 0, &ovlp);
+ }else{
+ res = LockFile(id->h, FIRST_LOCKBYTE+lk, 0, 1, 0);
+ }
+ UnlockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0);
+ }
+ if( res ){
+ id->locked = lk;
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_BUSY;
+ }
+ }
+ return rc;
+#endif
+#if OS_MAC
+ int rc;
+ if( id->locked>0 || id->refNumRF == -1 ){
+ rc = SQLITE_OK;
+ }else{
+ int lk;
+ OSErr res;
+ int cnt = 5;
+ ParamBlockRec params;
+ sqliteRandomness(sizeof(lk), &lk);
+ lk = (lk & 0x7fffffff)%N_LOCKBYTE + 1;
+ memset(&params, 0, sizeof(params));
+ params.ioParam.ioRefNum = id->refNumRF;
+ params.ioParam.ioPosMode = fsFromStart;
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE;
+ params.ioParam.ioReqCount = 1;
+ while( cnt-->0 && (res = PBLockRangeSync(&params))!=noErr ){
+ UInt32 finalTicks;
+ Delay(1, &finalTicks); /* 1/60 sec */
+ }
+ if( res == noErr ){
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1;
+ params.ioParam.ioReqCount = N_LOCKBYTE;
+ PBUnlockRangeSync(&params);
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE+lk;
+ params.ioParam.ioReqCount = 1;
+ res = PBLockRangeSync(&params);
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE;
+ params.ioParam.ioReqCount = 1;
+ PBUnlockRangeSync(&params);
+ }
+ if( res == noErr ){
+ id->locked = lk;
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_BUSY;
+ }
+ }
+ return rc;
+#endif
+}
+
+/*
+** Change the lock status to be an exclusive or write lock. Return
+** SQLITE_OK on success and SQLITE_BUSY on a failure. If this
+** library was compiled with large file support (LFS) but LFS is not
+** available on the host, then an SQLITE_NOLFS is returned.
+*/
+int sqliteOsWriteLock(OsFile *id){
+#if OS_UNIX
+ int rc;
+ sqliteOsEnterMutex();
+ if( id->pLock->cnt==0 || (id->pLock->cnt==1 && id->locked==1) ){
+ struct flock lock;
+ int s;
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = lock.l_len = 0L;
+ s = fcntl(id->fd, F_SETLK, &lock);
+ if( s!=0 ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ }else{
+ rc = SQLITE_OK;
+ if( !id->locked ){
+ id->pOpen->nLock++;
+ id->locked = 1;
+ }
+ id->pLock->cnt = -1;
+ }
+ }else{
+ rc = SQLITE_BUSY;
+ }
+ sqliteOsLeaveMutex();
+ return rc;
+#endif
+#if OS_WIN
+ int rc;
+ if( id->locked<0 ){
+ rc = SQLITE_OK;
+ }else{
+ int res;
+ int cnt = 100;
+ while( cnt-->0 && (res = LockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0))==0 ){
+ Sleep(1);
+ }
+ if( res ){
+ if( id->locked>0 ){
+ if( isNT() ){
+ UnlockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0);
+ }else{
+ res = UnlockFile(id->h, FIRST_LOCKBYTE + id->locked, 0, 1, 0);
+ }
+ }
+ if( res ){
+ res = LockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0);
+ }else{
+ res = 0;
+ }
+ UnlockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0);
+ }
+ if( res ){
+ id->locked = -1;
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_BUSY;
+ }
+ }
+ return rc;
+#endif
+#if OS_MAC
+ int rc;
+ if( id->locked<0 || id->refNumRF == -1 ){
+ rc = SQLITE_OK;
+ }else{
+ OSErr res;
+ int cnt = 5;
+ ParamBlockRec params;
+ memset(&params, 0, sizeof(params));
+ params.ioParam.ioRefNum = id->refNumRF;
+ params.ioParam.ioPosMode = fsFromStart;
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE;
+ params.ioParam.ioReqCount = 1;
+ while( cnt-->0 && (res = PBLockRangeSync(&params))!=noErr ){
+ UInt32 finalTicks;
+ Delay(1, &finalTicks); /* 1/60 sec */
+ }
+ if( res == noErr ){
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE + id->locked;
+ params.ioParam.ioReqCount = 1;
+ if( id->locked==0
+ || PBUnlockRangeSync(&params)==noErr ){
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1;
+ params.ioParam.ioReqCount = N_LOCKBYTE;
+ res = PBLockRangeSync(&params);
+ }else{
+ res = afpRangeNotLocked;
+ }
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE;
+ params.ioParam.ioReqCount = 1;
+ PBUnlockRangeSync(&params);
+ }
+ if( res == noErr ){
+ id->locked = -1;
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_BUSY;
+ }
+ }
+ return rc;
+#endif
+}
+
+/*
+** Unlock the given file descriptor. If the file descriptor was
+** not previously locked, then this routine is a no-op. If this
+** library was compiled with large file support (LFS) but LFS is not
+** available on the host, then an SQLITE_NOLFS is returned.
+*/
+int sqliteOsUnlock(OsFile *id){
+#if OS_UNIX
+ int rc;
+ if( !id->locked ) return SQLITE_OK;
+ sqliteOsEnterMutex();
+ assert( id->pLock->cnt!=0 );
+ if( id->pLock->cnt>1 ){
+ id->pLock->cnt--;
+ rc = SQLITE_OK;
+ }else{
+ struct flock lock;
+ int s;
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = lock.l_len = 0L;
+ s = fcntl(id->fd, F_SETLK, &lock);
+ if( s!=0 ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ }else{
+ rc = SQLITE_OK;
+ id->pLock->cnt = 0;
+ }
+ }
+ if( rc==SQLITE_OK ){
+ /* Decrement the count of locks against this same file. When the
+ ** count reaches zero, close any other file descriptors whose close
+ ** was deferred because of outstanding locks.
+ */
+ struct openCnt *pOpen = id->pOpen;
+ pOpen->nLock--;
+ assert( pOpen->nLock>=0 );
+ if( pOpen->nLock==0 && pOpen->nPending>0 ){
+ int i;
+ for(i=0; i<pOpen->nPending; i++){
+ close(pOpen->aPending[i]);
+ }
+ sqliteFree(pOpen->aPending);
+ pOpen->nPending = 0;
+ pOpen->aPending = 0;
+ }
+ }
+ sqliteOsLeaveMutex();
+ id->locked = 0;
+ return rc;
+#endif
+#if OS_WIN
+ int rc;
+ if( id->locked==0 ){
+ rc = SQLITE_OK;
+ }else if( isNT() || id->locked<0 ){
+ UnlockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0);
+ rc = SQLITE_OK;
+ id->locked = 0;
+ }else{
+ UnlockFile(id->h, FIRST_LOCKBYTE+id->locked, 0, 1, 0);
+ rc = SQLITE_OK;
+ id->locked = 0;
+ }
+ return rc;
+#endif
+#if OS_MAC
+ int rc;
+ ParamBlockRec params;
+ memset(&params, 0, sizeof(params));
+ params.ioParam.ioRefNum = id->refNumRF;
+ params.ioParam.ioPosMode = fsFromStart;
+ if( id->locked==0 || id->refNumRF == -1 ){
+ rc = SQLITE_OK;
+ }else if( id->locked<0 ){
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1;
+ params.ioParam.ioReqCount = N_LOCKBYTE;
+ PBUnlockRangeSync(&params);
+ rc = SQLITE_OK;
+ id->locked = 0;
+ }else{
+ params.ioParam.ioPosOffset = FIRST_LOCKBYTE+id->locked;
+ params.ioParam.ioReqCount = 1;
+ PBUnlockRangeSync(&params);
+ rc = SQLITE_OK;
+ id->locked = 0;
+ }
+ return rc;
+#endif
+}
+
+/*
+** Get information to seed the random number generator. The seed
+** is written into the buffer zBuf[256]. The calling function must
+** supply a sufficiently large buffer.
+*/
+int sqliteOsRandomSeed(char *zBuf){
+ /* We have to initialize zBuf to prevent valgrind from reporting
+ ** errors. The reports issued by valgrind are incorrect - we would
+ ** prefer that the randomness be increased by making use of the
+ ** uninitialized space in zBuf - but valgrind errors tend to worry
+ ** some users. Rather than argue, it seems easier just to initialize
+ ** the whole array and silence valgrind, even if that means less randomness
+ ** in the random seed.
+ **
+ ** When testing, initializing zBuf[] to zero is all we do. That means
+ ** that we always use the same random number sequence.* This makes the
+ ** tests repeatable.
+ */
+ memset(zBuf, 0, 256);
+#if OS_UNIX && !defined(SQLITE_TEST)
+ {
+ int pid;
+ time((time_t*)zBuf);
+ pid = getpid();
+ memcpy(&zBuf[sizeof(time_t)], &pid, sizeof(pid));
+ }
+#endif
+#if OS_WIN && !defined(SQLITE_TEST)
+ GetSystemTime((LPSYSTEMTIME)zBuf);
+#endif
+#if OS_MAC
+ {
+ int pid;
+ Microseconds((UnsignedWide*)zBuf);
+ pid = getpid();
+ memcpy(&zBuf[sizeof(UnsignedWide)], &pid, sizeof(pid));
+ }
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** Sleep for a little while. Return the amount of time slept.
+*/
+int sqliteOsSleep(int ms){
+#if OS_UNIX
+#if defined(HAVE_USLEEP) && HAVE_USLEEP
+ usleep(ms*1000);
+ return ms;
+#else
+ sleep((ms+999)/1000);
+ return 1000*((ms+999)/1000);
+#endif
+#endif
+#if OS_WIN
+ Sleep(ms);
+ return ms;
+#endif
+#if OS_MAC
+ UInt32 finalTicks;
+ UInt32 ticks = (((UInt32)ms+16)*3)/50; /* 1/60 sec per tick */
+ Delay(ticks, &finalTicks);
+ return (int)((ticks*50)/3);
+#endif
+}
+
+/*
+** Static variables used for thread synchronization
+*/
+static int inMutex = 0;
+#ifdef SQLITE_UNIX_THREADS
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+#ifdef SQLITE_W32_THREADS
+ static CRITICAL_SECTION cs;
+#endif
+#ifdef SQLITE_MACOS_MULTITASKING
+ static MPCriticalRegionID criticalRegion;
+#endif
+
+/*
+** The following pair of routine implement mutual exclusion for
+** multi-threaded processes. Only a single thread is allowed to
+** executed code that is surrounded by EnterMutex() and LeaveMutex().
+**
+** SQLite uses only a single Mutex. There is not much critical
+** code and what little there is executes quickly and without blocking.
+*/
+void sqliteOsEnterMutex(){
+#ifdef SQLITE_UNIX_THREADS
+ pthread_mutex_lock(&mutex);
+#endif
+#ifdef SQLITE_W32_THREADS
+ static int isInit = 0;
+ while( !isInit ){
+ static long lock = 0;
+ if( InterlockedIncrement(&lock)==1 ){
+ InitializeCriticalSection(&cs);
+ isInit = 1;
+ }else{
+ Sleep(1);
+ }
+ }
+ EnterCriticalSection(&cs);
+#endif
+#ifdef SQLITE_MACOS_MULTITASKING
+ static volatile int notInit = 1;
+ if( notInit ){
+ if( notInit == 2 ) /* as close as you can get to thread safe init */
+ MPYield();
+ else{
+ notInit = 2;
+ MPCreateCriticalRegion(&criticalRegion);
+ notInit = 0;
+ }
+ }
+ MPEnterCriticalRegion(criticalRegion, kDurationForever);
+#endif
+ assert( !inMutex );
+ inMutex = 1;
+}
+void sqliteOsLeaveMutex(){
+ assert( inMutex );
+ inMutex = 0;
+#ifdef SQLITE_UNIX_THREADS
+ pthread_mutex_unlock(&mutex);
+#endif
+#ifdef SQLITE_W32_THREADS
+ LeaveCriticalSection(&cs);
+#endif
+#ifdef SQLITE_MACOS_MULTITASKING
+ MPExitCriticalRegion(criticalRegion);
+#endif
+}
+
+/*
+** Turn a relative pathname into a full pathname. Return a pointer
+** to the full pathname stored in space obtained from sqliteMalloc().
+** The calling function is responsible for freeing this space once it
+** is no longer needed.
+*/
+char *sqliteOsFullPathname(const char *zRelative){
+#if OS_UNIX
+ char *zFull = 0;
+ if( zRelative[0]=='/' ){
+ sqliteSetString(&zFull, zRelative, (char*)0);
+ }else{
+ char zBuf[5000];
+ zBuf[0] = 0;
+ sqliteSetString(&zFull, getcwd(zBuf, sizeof(zBuf)), "/", zRelative,
+ (char*)0);
+ }
+ return zFull;
+#endif
+#if OS_WIN
+ char *zNotUsed;
+ char *zFull;
+ int nByte;
+ nByte = GetFullPathName(zRelative, 0, 0, &zNotUsed) + 1;
+ zFull = sqliteMalloc( nByte );
+ if( zFull==0 ) return 0;
+ GetFullPathName(zRelative, nByte, zFull, &zNotUsed);
+ return zFull;
+#endif
+#if OS_MAC
+ char *zFull = 0;
+ if( zRelative[0]==':' ){
+ char zBuf[_MAX_PATH+1];
+ sqliteSetString(&zFull, getcwd(zBuf, sizeof(zBuf)), &(zRelative[1]),
+ (char*)0);
+ }else{
+ if( strchr(zRelative, ':') ){
+ sqliteSetString(&zFull, zRelative, (char*)0);
+ }else{
+ char zBuf[_MAX_PATH+1];
+ sqliteSetString(&zFull, getcwd(zBuf, sizeof(zBuf)), zRelative, (char*)0);
+ }
+ }
+ return zFull;
+#endif
+}
+
+/*
+** The following variable, if set to a non-zero value, becomes the result
+** returned from sqliteOsCurrentTime(). This is used for testing.
+*/
+#ifdef SQLITE_TEST
+int sqlite_current_time = 0;
+#endif
+
+/*
+** Find the current time (in Universal Coordinated Time). Write the
+** current time and date as a Julian Day number into *prNow and
+** return 0. Return 1 if the time and date cannot be found.
+*/
+int sqliteOsCurrentTime(double *prNow){
+#if OS_UNIX
+ time_t t;
+ time(&t);
+ *prNow = t/86400.0 + 2440587.5;
+#endif
+#if OS_WIN
+ FILETIME ft;
+ /* FILETIME structure is a 64-bit value representing the number of
+ 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5).
+ */
+ double now;
+ GetSystemTimeAsFileTime( &ft );
+ now = ((double)ft.dwHighDateTime) * 4294967296.0;
+ *prNow = (now + ft.dwLowDateTime)/864000000000.0 + 2305813.5;
+#endif
+#ifdef SQLITE_TEST
+ if( sqlite_current_time ){
+ *prNow = sqlite_current_time/86400.0 + 2440587.5;
+ }
+#endif
+ return 0;
+}
diff --git a/src/libs/sqlite2/os.h b/src/libs/sqlite2/os.h
new file mode 100644
index 00000000..d11198c9
--- /dev/null
+++ b/src/libs/sqlite2/os.h
@@ -0,0 +1,191 @@
+/*
+** 2001 September 16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This header file (together with is companion C source-code file
+** "os.c") attempt to abstract the underlying operating system so that
+** the SQLite library will work on both POSIX and windows systems.
+*/
+#ifndef _SQLITE_OS_H_
+#define _SQLITE_OS_H_
+
+/*
+** Helpful hint: To get this to compile on HP/UX, add -D_INCLUDE_POSIX_SOURCE
+** to the compiler command line.
+*/
+
+/*
+** These #defines should enable >2GB file support on Posix if the
+** underlying operating system supports it. If the OS lacks
+** large file support, or if the OS is windows, these should be no-ops.
+**
+** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch
+** on the compiler command line. This is necessary if you are compiling
+** on a recent machine (ex: RedHat 7.2) but you want your code to work
+** on an older machine (ex: RedHat 6.0). If you compile on RedHat 7.2
+** without this option, LFS is enable. But LFS does not exist in the kernel
+** in RedHat 6.0, so the code won't work. Hence, for maximum binary
+** portability you should omit LFS.
+**
+** Similar is true for MacOS. LFS is only supported on MacOS 9 and later.
+*/
+#ifndef SQLITE_DISABLE_LFS
+# define _LARGE_FILE 1
+# ifndef _FILE_OFFSET_BITS
+# define _FILE_OFFSET_BITS 64
+# endif
+# define _LARGEFILE_SOURCE 1
+#endif
+
+/*
+** Temporary files are named starting with this prefix followed by 16 random
+** alphanumeric characters, and no file extension. They are stored in the
+** OS's standard temporary file directory, and are deleted prior to exit.
+** If sqlite is being embedded in another program, you may wish to change the
+** prefix to reflect your program's name, so that if your program exits
+** prematurely, old temporary files can be easily identified. This can be done
+** using -DTEMP_FILE_PREFIX=myprefix_ on the compiler command line.
+*/
+#ifndef TEMP_FILE_PREFIX
+# define TEMP_FILE_PREFIX "sqlite_"
+#endif
+
+/*
+** Figure out if we are dealing with Unix, Windows or MacOS.
+**
+** N.B. MacOS means Mac Classic (or Carbon). Treat Darwin (OS X) as Unix.
+** The MacOS build is designed to use CodeWarrior (tested with v8)
+*/
+#ifndef OS_UNIX
+# ifndef OS_WIN
+# ifndef OS_MAC
+# if defined(__MACOS__)
+# define OS_MAC 1
+# define OS_WIN 0
+# define OS_UNIX 0
+# elif defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
+# define OS_MAC 0
+# define OS_WIN 1
+# define OS_UNIX 0
+# else
+# define OS_MAC 0
+# define OS_WIN 0
+# define OS_UNIX 1
+# endif
+# else
+# define OS_WIN 0
+# define OS_UNIX 0
+# endif
+# else
+# define OS_MAC 0
+# define OS_UNIX 0
+# endif
+#else
+# define OS_MAC 0
+# ifndef OS_WIN
+# define OS_WIN 0
+# endif
+#endif
+
+/*
+** A handle for an open file is stored in an OsFile object.
+*/
+#if OS_UNIX
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <unistd.h>
+ typedef struct OsFile OsFile;
+ struct OsFile {
+ struct openCnt *pOpen; /* Info about all open fd's on this inode */
+ struct lockInfo *pLock; /* Info about locks on this inode */
+ int fd; /* The file descriptor */
+ int locked; /* True if this instance holds the lock */
+ int dirfd; /* File descriptor for the directory */
+ };
+# define SQLITE_TEMPNAME_SIZE 200
+# if defined(HAVE_USLEEP) && HAVE_USLEEP
+# define SQLITE_MIN_SLEEP_MS 1
+# else
+# define SQLITE_MIN_SLEEP_MS 1000
+# endif
+#endif
+
+#if OS_WIN
+#include <windows.h>
+#include <winbase.h>
+ typedef struct OsFile OsFile;
+ struct OsFile {
+ HANDLE h; /* Handle for accessing the file */
+ int locked; /* 0: unlocked, <0: write lock, >0: read lock */
+ };
+# if defined(_MSC_VER)
+ typedef __int64 off_t;
+# else
+# if !defined(_CYGWIN_TYPES_H)
+ typedef long long off_t;
+# if defined(__MINGW32__)
+# define _OFF_T_
+# endif
+# endif
+# endif
+# define SQLITE_TEMPNAME_SIZE (MAX_PATH+50)
+# define SQLITE_MIN_SLEEP_MS 1
+#endif
+
+#if OS_MAC
+# include <unistd.h>
+# include <Files.h>
+ typedef struct OsFile OsFile;
+ struct OsFile {
+ SInt16 refNum; /* Data fork/file reference number */
+ SInt16 refNumRF; /* Resource fork reference number (for locking) */
+ int locked; /* 0: unlocked, <0: write lock, >0: read lock */
+ int delOnClose; /* True if file is to be deleted on close */
+ char *pathToDel; /* Name of file to delete on close */
+ };
+# ifdef _LARGE_FILE
+ typedef SInt64 off_t;
+# else
+ typedef SInt32 off_t;
+# endif
+# define SQLITE_TEMPNAME_SIZE _MAX_PATH
+# define SQLITE_MIN_SLEEP_MS 17
+#endif
+
+int sqliteOsDelete(const char*);
+int sqliteOsFileExists(const char*);
+int sqliteOsFileRename(const char*, const char*);
+int sqliteOsOpenReadWrite(const char*, OsFile*, int*);
+int sqliteOsOpenExclusive(const char*, OsFile*, int);
+int sqliteOsOpenReadOnly(const char*, OsFile*);
+int sqliteOsOpenDirectory(const char*, OsFile*);
+int sqliteOsTempFileName(char*);
+int sqliteOsClose(OsFile*);
+int sqliteOsRead(OsFile*, void*, int amt);
+int sqliteOsWrite(OsFile*, const void*, int amt);
+int sqliteOsSeek(OsFile*, off_t offset);
+int sqliteOsSync(OsFile*);
+int sqliteOsTruncate(OsFile*, off_t size);
+int sqliteOsFileSize(OsFile*, off_t *pSize);
+int sqliteOsReadLock(OsFile*);
+int sqliteOsWriteLock(OsFile*);
+int sqliteOsUnlock(OsFile*);
+int sqliteOsRandomSeed(char*);
+int sqliteOsSleep(int ms);
+int sqliteOsCurrentTime(double*);
+void sqliteOsEnterMutex(void);
+void sqliteOsLeaveMutex(void);
+char *sqliteOsFullPathname(const char*);
+
+
+
+#endif /* _SQLITE_OS_H_ */
diff --git a/src/libs/sqlite2/pager.c b/src/libs/sqlite2/pager.c
new file mode 100644
index 00000000..409f9201
--- /dev/null
+++ b/src/libs/sqlite2/pager.c
@@ -0,0 +1,2220 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the implementation of the page cache subsystem or "pager".
+**
+** The pager is used to access a database disk file. It implements
+** atomic commit and rollback through the use of a journal file that
+** is separate from the database file. The pager also implements file
+** locking to prevent two processes from writing the same database
+** file simultaneously, or one process from reading the database while
+** another is writing.
+**
+** @(#) $Id: pager.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "os.h" /* Must be first to enable large file support */
+#include "sqliteInt.h"
+#include "pager.h"
+#include <assert.h>
+#include <string.h>
+
+/*
+** Macros for troubleshooting. Normally turned off
+*/
+#if 0
+static Pager *mainPager = 0;
+#define SET_PAGER(X) if( mainPager==0 ) mainPager = (X)
+#define CLR_PAGER(X) if( mainPager==(X) ) mainPager = 0
+#define TRACE1(X) if( pPager==mainPager ) fprintf(stderr,X)
+#define TRACE2(X,Y) if( pPager==mainPager ) fprintf(stderr,X,Y)
+#define TRACE3(X,Y,Z) if( pPager==mainPager ) fprintf(stderr,X,Y,Z)
+#else
+#define SET_PAGER(X)
+#define CLR_PAGER(X)
+#define TRACE1(X)
+#define TRACE2(X,Y)
+#define TRACE3(X,Y,Z)
+#endif
+
+
+/*
+** The page cache as a whole is always in one of the following
+** states:
+**
+** SQLITE_UNLOCK The page cache is not currently reading or
+** writing the database file. There is no
+** data held in memory. This is the initial
+** state.
+**
+** SQLITE_READLOCK The page cache is reading the database.
+** Writing is not permitted. There can be
+** multiple readers accessing the same database
+** file at the same time.
+**
+** SQLITE_WRITELOCK The page cache is writing the database.
+** Access is exclusive. No other processes or
+** threads can be reading or writing while one
+** process is writing.
+**
+** The page cache comes up in SQLITE_UNLOCK. The first time a
+** sqlite_page_get() occurs, the state transitions to SQLITE_READLOCK.
+** After all pages have been released using sqlite_page_unref(),
+** the state transitions back to SQLITE_UNLOCK. The first time
+** that sqlite_page_write() is called, the state transitions to
+** SQLITE_WRITELOCK. (Note that sqlite_page_write() can only be
+** called on an outstanding page which means that the pager must
+** be in SQLITE_READLOCK before it transitions to SQLITE_WRITELOCK.)
+** The sqlite_page_rollback() and sqlite_page_commit() functions
+** transition the state from SQLITE_WRITELOCK back to SQLITE_READLOCK.
+*/
+#define SQLITE_UNLOCK 0
+#define SQLITE_READLOCK 1
+#define SQLITE_WRITELOCK 2
+
+
+/*
+** Each in-memory image of a page begins with the following header.
+** This header is only visible to this pager module. The client
+** code that calls pager sees only the data that follows the header.
+**
+** Client code should call sqlitepager_write() on a page prior to making
+** any modifications to that page. The first time sqlitepager_write()
+** is called, the original page contents are written into the rollback
+** journal and PgHdr.inJournal and PgHdr.needSync are set. Later, once
+** the journal page has made it onto the disk surface, PgHdr.needSync
+** is cleared. The modified page cannot be written back into the original
+** database file until the journal pages has been synced to disk and the
+** PgHdr.needSync has been cleared.
+**
+** The PgHdr.dirty flag is set when sqlitepager_write() is called and
+** is cleared again when the page content is written back to the original
+** database file.
+*/
+typedef struct PgHdr PgHdr;
+struct PgHdr {
+ Pager *pPager; /* The pager to which this page belongs */
+ Pgno pgno; /* The page number for this page */
+ PgHdr *pNextHash, *pPrevHash; /* Hash collision chain for PgHdr.pgno */
+ int nRef; /* Number of users of this page */
+ PgHdr *pNextFree, *pPrevFree; /* Freelist of pages where nRef==0 */
+ PgHdr *pNextAll, *pPrevAll; /* A list of all pages */
+ PgHdr *pNextCkpt, *pPrevCkpt; /* List of pages in the checkpoint journal */
+ u8 inJournal; /* TRUE if has been written to journal */
+ u8 inCkpt; /* TRUE if written to the checkpoint journal */
+ u8 dirty; /* TRUE if we need to write back changes */
+ u8 needSync; /* Sync journal before writing this page */
+ u8 alwaysRollback; /* Disable dont_rollback() for this page */
+ PgHdr *pDirty; /* Dirty pages sorted by PgHdr.pgno */
+ /* SQLITE_PAGE_SIZE bytes of page data follow this header */
+ /* Pager.nExtra bytes of local data follow the page data */
+};
+
+
+/*
+** A macro used for invoking the codec if there is one
+*/
+#ifdef SQLITE_HAS_CODEC
+# define CODEC(P,D,N,X) if( P->xCodec ){ P->xCodec(P->pCodecArg,D,N,X); }
+#else
+# define CODEC(P,D,N,X)
+#endif
+
+/*
+** Convert a pointer to a PgHdr into a pointer to its data
+** and back again.
+*/
+#define PGHDR_TO_DATA(P) ((void*)(&(P)[1]))
+#define DATA_TO_PGHDR(D) (&((PgHdr*)(D))[-1])
+#define PGHDR_TO_EXTRA(P) ((void*)&((char*)(&(P)[1]))[SQLITE_PAGE_SIZE])
+
+/*
+** How big to make the hash table used for locating in-memory pages
+** by page number.
+*/
+#define N_PG_HASH 2048
+
+/*
+** Hash a page number
+*/
+#define pager_hash(PN) ((PN)&(N_PG_HASH-1))
+
+/*
+** A open page cache is an instance of the following structure.
+*/
+struct Pager {
+ char *zFilename; /* Name of the database file */
+ char *zJournal; /* Name of the journal file */
+ char *zDirectory; /* Directory hold database and journal files */
+ OsFile fd, jfd; /* File descriptors for database and journal */
+ OsFile cpfd; /* File descriptor for the checkpoint journal */
+ int dbSize; /* Number of pages in the file */
+ int origDbSize; /* dbSize before the current change */
+ int ckptSize; /* Size of database (in pages) at ckpt_begin() */
+ off_t ckptJSize; /* Size of journal at ckpt_begin() */
+ int nRec; /* Number of pages written to the journal */
+ u32 cksumInit; /* Quasi-random value added to every checksum */
+ int ckptNRec; /* Number of records in the checkpoint journal */
+ int nExtra; /* Add this many bytes to each in-memory page */
+ void (*xDestructor)(void*); /* Call this routine when freeing pages */
+ int nPage; /* Total number of in-memory pages */
+ int nRef; /* Number of in-memory pages with PgHdr.nRef>0 */
+ int mxPage; /* Maximum number of pages to hold in cache */
+ int nHit, nMiss, nOvfl; /* Cache hits, missing, and LRU overflows */
+ void (*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */
+ void *pCodecArg; /* First argument to xCodec() */
+ u8 journalOpen; /* True if journal file descriptors is valid */
+ u8 journalStarted; /* True if header of journal is synced */
+ u8 useJournal; /* Use a rollback journal on this file */
+ u8 ckptOpen; /* True if the checkpoint journal is open */
+ u8 ckptInUse; /* True we are in a checkpoint */
+ u8 ckptAutoopen; /* Open ckpt journal when main journal is opened*/
+ u8 noSync; /* Do not sync the journal if true */
+ u8 fullSync; /* Do extra syncs of the journal for robustness */
+ u8 state; /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */
+ u8 errMask; /* One of several kinds of errors */
+ u8 tempFile; /* zFilename is a temporary file */
+ u8 readOnly; /* True for a read-only database */
+ u8 needSync; /* True if an fsync() is needed on the journal */
+ u8 dirtyFile; /* True if database file has changed in any way */
+ u8 alwaysRollback; /* Disable dont_rollback() for all pages */
+ u8 *aInJournal; /* One bit for each page in the database file */
+ u8 *aInCkpt; /* One bit for each page in the database */
+ PgHdr *pFirst, *pLast; /* List of free pages */
+ PgHdr *pFirstSynced; /* First free page with PgHdr.needSync==0 */
+ PgHdr *pAll; /* List of all pages */
+ PgHdr *pCkpt; /* List of pages in the checkpoint journal */
+ PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number of PgHdr */
+};
+
+/*
+** These are bits that can be set in Pager.errMask.
+*/
+#define PAGER_ERR_FULL 0x01 /* a write() failed */
+#define PAGER_ERR_MEM 0x02 /* malloc() failed */
+#define PAGER_ERR_LOCK 0x04 /* error in the locking protocol */
+#define PAGER_ERR_CORRUPT 0x08 /* database or journal corruption */
+#define PAGER_ERR_DISK 0x10 /* general disk I/O error - bad hard drive? */
+
+/*
+** The journal file contains page records in the following
+** format.
+**
+** Actually, this structure is the complete page record for pager
+** formats less than 3. Beginning with format 3, this record is surrounded
+** by two checksums.
+*/
+typedef struct PageRecord PageRecord;
+struct PageRecord {
+ Pgno pgno; /* The page number */
+ char aData[SQLITE_PAGE_SIZE]; /* Original data for page pgno */
+};
+
+/*
+** Journal files begin with the following magic string. The data
+** was obtained from /dev/random. It is used only as a sanity check.
+**
+** There are three journal formats (so far). The 1st journal format writes
+** 32-bit integers in the byte-order of the host machine. New
+** formats writes integers as big-endian. All new journals use the
+** new format, but we have to be able to read an older journal in order
+** to rollback journals created by older versions of the library.
+**
+** The 3rd journal format (added for 2.8.0) adds additional sanity
+** checking information to the journal. If the power fails while the
+** journal is being written, semi-random garbage data might appear in
+** the journal file after power is restored. If an attempt is then made
+** to roll the journal back, the database could be corrupted. The additional
+** sanity checking data is an attempt to discover the garbage in the
+** journal and ignore it.
+**
+** The sanity checking information for the 3rd journal format consists
+** of a 32-bit checksum on each page of data. The checksum covers both
+** the page number and the SQLITE_PAGE_SIZE bytes of data for the page.
+** This cksum is initialized to a 32-bit random value that appears in the
+** journal file right after the header. The random initializer is important,
+** because garbage data that appears at the end of a journal is likely
+** data that was once in other files that have now been deleted. If the
+** garbage data came from an obsolete journal file, the checksums might
+** be correct. But by initializing the checksum to random value which
+** is different for every journal, we minimize that risk.
+*/
+static const unsigned char aJournalMagic1[] = {
+ 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd4,
+};
+static const unsigned char aJournalMagic2[] = {
+ 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd5,
+};
+static const unsigned char aJournalMagic3[] = {
+ 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd6,
+};
+#define JOURNAL_FORMAT_1 1
+#define JOURNAL_FORMAT_2 2
+#define JOURNAL_FORMAT_3 3
+
+/*
+** The following integer determines what format to use when creating
+** new primary journal files. By default we always use format 3.
+** When testing, we can set this value to older journal formats in order to
+** make sure that newer versions of the library are able to rollback older
+** journal files.
+**
+** Note that checkpoint journals always use format 2 and omit the header.
+*/
+#ifdef SQLITE_TEST
+int journal_format = 3;
+#else
+# define journal_format 3
+#endif
+
+/*
+** The size of the header and of each page in the journal varies according
+** to which journal format is being used. The following macros figure out
+** the sizes based on format numbers.
+*/
+#define JOURNAL_HDR_SZ(X) \
+ (sizeof(aJournalMagic1) + sizeof(Pgno) + ((X)>=3)*2*sizeof(u32))
+#define JOURNAL_PG_SZ(X) \
+ (SQLITE_PAGE_SIZE + sizeof(Pgno) + ((X)>=3)*sizeof(u32))
+
+/*
+** Enable reference count tracking here:
+*/
+#ifdef SQLITE_TEST
+ int pager_refinfo_enable = 0;
+ static void pager_refinfo(PgHdr *p){
+ static int cnt = 0;
+ if( !pager_refinfo_enable ) return;
+ printf(
+ "REFCNT: %4d addr=0x%08x nRef=%d\n",
+ p->pgno, (int)PGHDR_TO_DATA(p), p->nRef
+ );
+ cnt++; /* Something to set a breakpoint on */
+ }
+# define REFINFO(X) pager_refinfo(X)
+#else
+# define REFINFO(X)
+#endif
+
+/*
+** Read a 32-bit integer from the given file descriptor. Store the integer
+** that is read in *pRes. Return SQLITE_OK if everything worked, or an
+** error code is something goes wrong.
+**
+** If the journal format is 2 or 3, read a big-endian integer. If the
+** journal format is 1, read an integer in the native byte-order of the
+** host machine.
+*/
+static int read32bits(int format, OsFile *fd, u32 *pRes){
+ u32 res;
+ int rc;
+ rc = sqliteOsRead(fd, &res, sizeof(res));
+ if( rc==SQLITE_OK && format>JOURNAL_FORMAT_1 ){
+ unsigned char ac[4];
+ memcpy(ac, &res, 4);
+ res = (ac[0]<<24) | (ac[1]<<16) | (ac[2]<<8) | ac[3];
+ }
+ *pRes = res;
+ return rc;
+}
+
+/*
+** Write a 32-bit integer into the given file descriptor. Return SQLITE_OK
+** on success or an error code is something goes wrong.
+**
+** If the journal format is 2 or 3, write the integer as 4 big-endian
+** bytes. If the journal format is 1, write the integer in the native
+** byte order. In normal operation, only formats 2 and 3 are used.
+** Journal format 1 is only used for testing.
+*/
+static int write32bits(OsFile *fd, u32 val){
+ unsigned char ac[4];
+ if( journal_format<=1 ){
+ return sqliteOsWrite(fd, &val, 4);
+ }
+ ac[0] = (val>>24) & 0xff;
+ ac[1] = (val>>16) & 0xff;
+ ac[2] = (val>>8) & 0xff;
+ ac[3] = val & 0xff;
+ return sqliteOsWrite(fd, ac, 4);
+}
+
+/*
+** Write a 32-bit integer into a page header right before the
+** page data. This will overwrite the PgHdr.pDirty pointer.
+**
+** The integer is big-endian for formats 2 and 3 and native byte order
+** for journal format 1.
+*/
+static void store32bits(u32 val, PgHdr *p, int offset){
+ unsigned char *ac;
+ ac = &((unsigned char*)PGHDR_TO_DATA(p))[offset];
+ if( journal_format<=1 ){
+ memcpy(ac, &val, 4);
+ }else{
+ ac[0] = (val>>24) & 0xff;
+ ac[1] = (val>>16) & 0xff;
+ ac[2] = (val>>8) & 0xff;
+ ac[3] = val & 0xff;
+ }
+}
+
+
+/*
+** Convert the bits in the pPager->errMask into an approprate
+** return code.
+*/
+static int pager_errcode(Pager *pPager){
+ int rc = SQLITE_OK;
+ if( pPager->errMask & PAGER_ERR_LOCK ) rc = SQLITE_PROTOCOL;
+ if( pPager->errMask & PAGER_ERR_DISK ) rc = SQLITE_IOERR;
+ if( pPager->errMask & PAGER_ERR_FULL ) rc = SQLITE_FULL;
+ if( pPager->errMask & PAGER_ERR_MEM ) rc = SQLITE_NOMEM;
+ if( pPager->errMask & PAGER_ERR_CORRUPT ) rc = SQLITE_CORRUPT;
+ return rc;
+}
+
+/*
+** Add or remove a page from the list of all pages that are in the
+** checkpoint journal.
+**
+** The Pager keeps a separate list of pages that are currently in
+** the checkpoint journal. This helps the sqlitepager_ckpt_commit()
+** routine run MUCH faster for the common case where there are many
+** pages in memory but only a few are in the checkpoint journal.
+*/
+static void page_add_to_ckpt_list(PgHdr *pPg){
+ Pager *pPager = pPg->pPager;
+ if( pPg->inCkpt ) return;
+ assert( pPg->pPrevCkpt==0 && pPg->pNextCkpt==0 );
+ pPg->pPrevCkpt = 0;
+ if( pPager->pCkpt ){
+ pPager->pCkpt->pPrevCkpt = pPg;
+ }
+ pPg->pNextCkpt = pPager->pCkpt;
+ pPager->pCkpt = pPg;
+ pPg->inCkpt = 1;
+}
+static void page_remove_from_ckpt_list(PgHdr *pPg){
+ if( !pPg->inCkpt ) return;
+ if( pPg->pPrevCkpt ){
+ assert( pPg->pPrevCkpt->pNextCkpt==pPg );
+ pPg->pPrevCkpt->pNextCkpt = pPg->pNextCkpt;
+ }else{
+ assert( pPg->pPager->pCkpt==pPg );
+ pPg->pPager->pCkpt = pPg->pNextCkpt;
+ }
+ if( pPg->pNextCkpt ){
+ assert( pPg->pNextCkpt->pPrevCkpt==pPg );
+ pPg->pNextCkpt->pPrevCkpt = pPg->pPrevCkpt;
+ }
+ pPg->pNextCkpt = 0;
+ pPg->pPrevCkpt = 0;
+ pPg->inCkpt = 0;
+}
+
+/*
+** Find a page in the hash table given its page number. Return
+** a pointer to the page or NULL if not found.
+*/
+static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){
+ PgHdr *p = pPager->aHash[pager_hash(pgno)];
+ while( p && p->pgno!=pgno ){
+ p = p->pNextHash;
+ }
+ return p;
+}
+
+/*
+** Unlock the database and clear the in-memory cache. This routine
+** sets the state of the pager back to what it was when it was first
+** opened. Any outstanding pages are invalidated and subsequent attempts
+** to access those pages will likely result in a coredump.
+*/
+static void pager_reset(Pager *pPager){
+ PgHdr *pPg, *pNext;
+ for(pPg=pPager->pAll; pPg; pPg=pNext){
+ pNext = pPg->pNextAll;
+ sqliteFree(pPg);
+ }
+ pPager->pFirst = 0;
+ pPager->pFirstSynced = 0;
+ pPager->pLast = 0;
+ pPager->pAll = 0;
+ memset(pPager->aHash, 0, sizeof(pPager->aHash));
+ pPager->nPage = 0;
+ if( pPager->state>=SQLITE_WRITELOCK ){
+ sqlitepager_rollback(pPager);
+ }
+ sqliteOsUnlock(&pPager->fd);
+ pPager->state = SQLITE_UNLOCK;
+ pPager->dbSize = -1;
+ pPager->nRef = 0;
+ assert( pPager->journalOpen==0 );
+}
+
+/*
+** When this routine is called, the pager has the journal file open and
+** a write lock on the database. This routine releases the database
+** write lock and acquires a read lock in its place. The journal file
+** is deleted and closed.
+**
+** TODO: Consider keeping the journal file open for temporary databases.
+** This might give a performance improvement on windows where opening
+** a file is an expensive operation.
+*/
+static int pager_unwritelock(Pager *pPager){
+ int rc;
+ PgHdr *pPg;
+ if( pPager->state<SQLITE_WRITELOCK ) return SQLITE_OK;
+ sqlitepager_ckpt_commit(pPager);
+ if( pPager->ckptOpen ){
+ sqliteOsClose(&pPager->cpfd);
+ pPager->ckptOpen = 0;
+ }
+ if( pPager->journalOpen ){
+ sqliteOsClose(&pPager->jfd);
+ pPager->journalOpen = 0;
+ sqliteOsDelete(pPager->zJournal);
+ sqliteFree( pPager->aInJournal );
+ pPager->aInJournal = 0;
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ pPg->inJournal = 0;
+ pPg->dirty = 0;
+ pPg->needSync = 0;
+ }
+ }else{
+ assert( pPager->dirtyFile==0 || pPager->useJournal==0 );
+ }
+ rc = sqliteOsReadLock(&pPager->fd);
+ if( rc==SQLITE_OK ){
+ pPager->state = SQLITE_READLOCK;
+ }else{
+ /* This can only happen if a process does a BEGIN, then forks and the
+ ** child process does the COMMIT. Because of the semantics of unix
+ ** file locking, the unlock will fail.
+ */
+ pPager->state = SQLITE_UNLOCK;
+ }
+ return rc;
+}
+
+/*
+** Compute and return a checksum for the page of data.
+**
+** This is not a real checksum. It is really just the sum of the
+** random initial value and the page number. We considered do a checksum
+** of the database, but that was found to be too slow.
+*/
+static u32 pager_cksum(Pager *pPager, Pgno pgno, const char *aData){
+ u32 cksum = pPager->cksumInit + pgno;
+ return cksum;
+}
+
+/*
+** Read a single page from the journal file opened on file descriptor
+** jfd. Playback this one page.
+**
+** There are three different journal formats. The format parameter determines
+** which format is used by the journal that is played back.
+*/
+static int pager_playback_one_page(Pager *pPager, OsFile *jfd, int format){
+ int rc;
+ PgHdr *pPg; /* An existing page in the cache */
+ PageRecord pgRec;
+ u32 cksum;
+
+ rc = read32bits(format, jfd, &pgRec.pgno);
+ if( rc!=SQLITE_OK ) return rc;
+ rc = sqliteOsRead(jfd, &pgRec.aData, sizeof(pgRec.aData));
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Sanity checking on the page. This is more important that I originally
+ ** thought. If a power failure occurs while the journal is being written,
+ ** it could cause invalid data to be written into the journal. We need to
+ ** detect this invalid data (with high probability) and ignore it.
+ */
+ if( pgRec.pgno==0 ){
+ return SQLITE_DONE;
+ }
+ if( pgRec.pgno>(unsigned)pPager->dbSize ){
+ return SQLITE_OK;
+ }
+ if( format>=JOURNAL_FORMAT_3 ){
+ rc = read32bits(format, jfd, &cksum);
+ if( rc ) return rc;
+ if( pager_cksum(pPager, pgRec.pgno, pgRec.aData)!=cksum ){
+ return SQLITE_DONE;
+ }
+ }
+
+ /* Playback the page. Update the in-memory copy of the page
+ ** at the same time, if there is one.
+ */
+ pPg = pager_lookup(pPager, pgRec.pgno);
+ TRACE2("PLAYBACK %d\n", pgRec.pgno);
+ sqliteOsSeek(&pPager->fd, (pgRec.pgno-1)*(off_t)SQLITE_PAGE_SIZE);
+ rc = sqliteOsWrite(&pPager->fd, pgRec.aData, SQLITE_PAGE_SIZE);
+ if( pPg ){
+ /* No page should ever be rolled back that is in use, except for page
+ ** 1 which is held in use in order to keep the lock on the database
+ ** active.
+ */
+ assert( pPg->nRef==0 || pPg->pgno==1 );
+ memcpy(PGHDR_TO_DATA(pPg), pgRec.aData, SQLITE_PAGE_SIZE);
+ memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra);
+ pPg->dirty = 0;
+ pPg->needSync = 0;
+ CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3);
+ }
+ return rc;
+}
+
+/*
+** Playback the journal and thus restore the database file to
+** the state it was in before we started making changes.
+**
+** The journal file format is as follows:
+**
+** * 8 byte prefix. One of the aJournalMagic123 vectors defined
+** above. The format of the journal file is determined by which
+** of the three prefix vectors is seen.
+** * 4 byte big-endian integer which is the number of valid page records
+** in the journal. If this value is 0xffffffff, then compute the
+** number of page records from the journal size. This field appears
+** in format 3 only.
+** * 4 byte big-endian integer which is the initial value for the
+** sanity checksum. This field appears in format 3 only.
+** * 4 byte integer which is the number of pages to truncate the
+** database to during a rollback.
+** * Zero or more pages instances, each as follows:
+** + 4 byte page number.
+** + SQLITE_PAGE_SIZE bytes of data.
+** + 4 byte checksum (format 3 only)
+**
+** When we speak of the journal header, we mean the first 4 bullets above.
+** Each entry in the journal is an instance of the 5th bullet. Note that
+** bullets 2 and 3 only appear in format-3 journals.
+**
+** Call the value from the second bullet "nRec". nRec is the number of
+** valid page entries in the journal. In most cases, you can compute the
+** value of nRec from the size of the journal file. But if a power
+** failure occurred while the journal was being written, it could be the
+** case that the size of the journal file had already been increased but
+** the extra entries had not yet made it safely to disk. In such a case,
+** the value of nRec computed from the file size would be too large. For
+** that reason, we always use the nRec value in the header.
+**
+** If the nRec value is 0xffffffff it means that nRec should be computed
+** from the file size. This value is used when the user selects the
+** no-sync option for the journal. A power failure could lead to corruption
+** in this case. But for things like temporary table (which will be
+** deleted when the power is restored) we don't care.
+**
+** Journal formats 1 and 2 do not have an nRec value in the header so we
+** have to compute nRec from the file size. This has risks (as described
+** above) which is why all persistent tables have been changed to use
+** format 3.
+**
+** If the file opened as the journal file is not a well-formed
+** journal file then the database will likely already be
+** corrupted, so the PAGER_ERR_CORRUPT bit is set in pPager->errMask
+** and SQLITE_CORRUPT is returned. If it all works, then this routine
+** returns SQLITE_OK.
+*/
+static int pager_playback(Pager *pPager, int useJournalSize){
+ off_t szJ; /* Size of the journal file in bytes */
+ int nRec; /* Number of Records in the journal */
+ int i; /* Loop counter */
+ Pgno mxPg = 0; /* Size of the original file in pages */
+ int format; /* Format of the journal file. */
+ unsigned char aMagic[sizeof(aJournalMagic1)];
+ int rc;
+
+ /* Figure out how many records are in the journal. Abort early if
+ ** the journal is empty.
+ */
+ assert( pPager->journalOpen );
+ sqliteOsSeek(&pPager->jfd, 0);
+ rc = sqliteOsFileSize(&pPager->jfd, &szJ);
+ if( rc!=SQLITE_OK ){
+ goto end_playback;
+ }
+
+ /* If the journal file is too small to contain a complete header,
+ ** it must mean that the process that created the journal was just
+ ** beginning to write the journal file when it died. In that case,
+ ** the database file should have still been completely unchanged.
+ ** Nothing needs to be rolled back. We can safely ignore this journal.
+ */
+ if( szJ < sizeof(aMagic)+sizeof(Pgno) ){
+ goto end_playback;
+ }
+
+ /* Read the beginning of the journal and truncate the
+ ** database file back to its original size.
+ */
+ rc = sqliteOsRead(&pPager->jfd, aMagic, sizeof(aMagic));
+ if( rc!=SQLITE_OK ){
+ rc = SQLITE_PROTOCOL;
+ goto end_playback;
+ }
+ if( memcmp(aMagic, aJournalMagic3, sizeof(aMagic))==0 ){
+ format = JOURNAL_FORMAT_3;
+ }else if( memcmp(aMagic, aJournalMagic2, sizeof(aMagic))==0 ){
+ format = JOURNAL_FORMAT_2;
+ }else if( memcmp(aMagic, aJournalMagic1, sizeof(aMagic))==0 ){
+ format = JOURNAL_FORMAT_1;
+ }else{
+ rc = SQLITE_PROTOCOL;
+ goto end_playback;
+ }
+ if( format>=JOURNAL_FORMAT_3 ){
+ if( szJ < sizeof(aMagic) + 3*sizeof(u32) ){
+ /* Ignore the journal if it is too small to contain a complete
+ ** header. We already did this test once above, but at the prior
+ ** test, we did not know the journal format and so we had to assume
+ ** the smallest possible header. Now we know the header is bigger
+ ** than the minimum so we test again.
+ */
+ goto end_playback;
+ }
+ rc = read32bits(format, &pPager->jfd, (u32*)&nRec);
+ if( rc ) goto end_playback;
+ rc = read32bits(format, &pPager->jfd, &pPager->cksumInit);
+ if( rc ) goto end_playback;
+ if( nRec==0xffffffff || useJournalSize ){
+ nRec = (szJ - JOURNAL_HDR_SZ(3))/JOURNAL_PG_SZ(3);
+ }
+ }else{
+ nRec = (szJ - JOURNAL_HDR_SZ(2))/JOURNAL_PG_SZ(2);
+ assert( nRec*JOURNAL_PG_SZ(2)+JOURNAL_HDR_SZ(2)==szJ );
+ }
+ rc = read32bits(format, &pPager->jfd, &mxPg);
+ if( rc!=SQLITE_OK ){
+ goto end_playback;
+ }
+ assert( pPager->origDbSize==0 || pPager->origDbSize==mxPg );
+ rc = sqliteOsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)mxPg);
+ if( rc!=SQLITE_OK ){
+ goto end_playback;
+ }
+ pPager->dbSize = mxPg;
+
+ /* Copy original pages out of the journal and back into the database file.
+ */
+ for(i=0; i<nRec; i++){
+ rc = pager_playback_one_page(pPager, &pPager->jfd, format);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ }
+ break;
+ }
+ }
+
+ /* Pages that have been written to the journal but never synced
+ ** where not restored by the loop above. We have to restore those
+ ** pages by reading them back from the original database.
+ */
+ if( rc==SQLITE_OK ){
+ PgHdr *pPg;
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ char zBuf[SQLITE_PAGE_SIZE];
+ if( !pPg->dirty ) continue;
+ if( (int)pPg->pgno <= pPager->origDbSize ){
+ sqliteOsSeek(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)(pPg->pgno-1));
+ rc = sqliteOsRead(&pPager->fd, zBuf, SQLITE_PAGE_SIZE);
+ TRACE2("REFETCH %d\n", pPg->pgno);
+ CODEC(pPager, zBuf, pPg->pgno, 2);
+ if( rc ) break;
+ }else{
+ memset(zBuf, 0, SQLITE_PAGE_SIZE);
+ }
+ if( pPg->nRef==0 || memcmp(zBuf, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE) ){
+ memcpy(PGHDR_TO_DATA(pPg), zBuf, SQLITE_PAGE_SIZE);
+ memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra);
+ }
+ pPg->needSync = 0;
+ pPg->dirty = 0;
+ }
+ }
+
+end_playback:
+ if( rc!=SQLITE_OK ){
+ pager_unwritelock(pPager);
+ pPager->errMask |= PAGER_ERR_CORRUPT;
+ rc = SQLITE_CORRUPT;
+ }else{
+ rc = pager_unwritelock(pPager);
+ }
+ return rc;
+}
+
+/*
+** Playback the checkpoint journal.
+**
+** This is similar to playing back the transaction journal but with
+** a few extra twists.
+**
+** (1) The number of pages in the database file at the start of
+** the checkpoint is stored in pPager->ckptSize, not in the
+** journal file itself.
+**
+** (2) In addition to playing back the checkpoint journal, also
+** playback all pages of the transaction journal beginning
+** at offset pPager->ckptJSize.
+*/
+static int pager_ckpt_playback(Pager *pPager){
+ off_t szJ; /* Size of the full journal */
+ int nRec; /* Number of Records */
+ int i; /* Loop counter */
+ int rc;
+
+ /* Truncate the database back to its original size.
+ */
+ rc = sqliteOsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)pPager->ckptSize);
+ pPager->dbSize = pPager->ckptSize;
+
+ /* Figure out how many records are in the checkpoint journal.
+ */
+ assert( pPager->ckptInUse && pPager->journalOpen );
+ sqliteOsSeek(&pPager->cpfd, 0);
+ nRec = pPager->ckptNRec;
+
+ /* Copy original pages out of the checkpoint journal and back into the
+ ** database file. Note that the checkpoint journal always uses format
+ ** 2 instead of format 3 since it does not need to be concerned with
+ ** power failures corrupting the journal and can thus omit the checksums.
+ */
+ for(i=nRec-1; i>=0; i--){
+ rc = pager_playback_one_page(pPager, &pPager->cpfd, 2);
+ assert( rc!=SQLITE_DONE );
+ if( rc!=SQLITE_OK ) goto end_ckpt_playback;
+ }
+
+ /* Figure out how many pages need to be copied out of the transaction
+ ** journal.
+ */
+ rc = sqliteOsSeek(&pPager->jfd, pPager->ckptJSize);
+ if( rc!=SQLITE_OK ){
+ goto end_ckpt_playback;
+ }
+ rc = sqliteOsFileSize(&pPager->jfd, &szJ);
+ if( rc!=SQLITE_OK ){
+ goto end_ckpt_playback;
+ }
+ nRec = (szJ - pPager->ckptJSize)/JOURNAL_PG_SZ(journal_format);
+ for(i=nRec-1; i>=0; i--){
+ rc = pager_playback_one_page(pPager, &pPager->jfd, journal_format);
+ if( rc!=SQLITE_OK ){
+ assert( rc!=SQLITE_DONE );
+ goto end_ckpt_playback;
+ }
+ }
+
+end_ckpt_playback:
+ if( rc!=SQLITE_OK ){
+ pPager->errMask |= PAGER_ERR_CORRUPT;
+ rc = SQLITE_CORRUPT;
+ }
+ return rc;
+}
+
+/*
+** Change the maximum number of in-memory pages that are allowed.
+**
+** The maximum number is the absolute value of the mxPage parameter.
+** If mxPage is negative, the noSync flag is also set. noSync bypasses
+** calls to sqliteOsSync(). The pager runs much faster with noSync on,
+** but if the operating system crashes or there is an abrupt power
+** failure, the database file might be left in an inconsistent and
+** unrepairable state.
+*/
+void sqlitepager_set_cachesize(Pager *pPager, int mxPage){
+ if( mxPage>=0 ){
+ pPager->noSync = pPager->tempFile;
+ if( pPager->noSync==0 ) pPager->needSync = 0;
+ }else{
+ pPager->noSync = 1;
+ mxPage = -mxPage;
+ }
+ if( mxPage>10 ){
+ pPager->mxPage = mxPage;
+ }
+}
+
+/*
+** Adjust the robustness of the database to damage due to OS crashes
+** or power failures by changing the number of syncs()s when writing
+** the rollback journal. There are three levels:
+**
+** OFF sqliteOsSync() is never called. This is the default
+** for temporary and transient files.
+**
+** NORMAL The journal is synced once before writes begin on the
+** database. This is normally adequate protection, but
+** it is theoretically possible, though very unlikely,
+** that an inopertune power failure could leave the journal
+** in a state which would cause damage to the database
+** when it is rolled back.
+**
+** FULL The journal is synced twice before writes begin on the
+** database (with some additional information - the nRec field
+** of the journal header - being written in between the two
+** syncs). If we assume that writing a
+** single disk sector is atomic, then this mode provides
+** assurance that the journal will not be corrupted to the
+** point of causing damage to the database during rollback.
+**
+** Numeric values associated with these states are OFF==1, NORMAL=2,
+** and FULL=3.
+*/
+void sqlitepager_set_safety_level(Pager *pPager, int level){
+ pPager->noSync = level==1 || pPager->tempFile;
+ pPager->fullSync = level==3 && !pPager->tempFile;
+ if( pPager->noSync==0 ) pPager->needSync = 0;
+}
+
+/*
+** Open a temporary file. Write the name of the file into zName
+** (zName must be at least SQLITE_TEMPNAME_SIZE bytes long.) Write
+** the file descriptor into *fd. Return SQLITE_OK on success or some
+** other error code if we fail.
+**
+** The OS will automatically delete the temporary file when it is
+** closed.
+*/
+static int sqlitepager_opentemp(char *zFile, OsFile *fd){
+ int cnt = 8;
+ int rc;
+ do{
+ cnt--;
+ sqliteOsTempFileName(zFile);
+ rc = sqliteOsOpenExclusive(zFile, fd, 1);
+ }while( cnt>0 && rc!=SQLITE_OK );
+ return rc;
+}
+
+/*
+** Create a new page cache and put a pointer to the page cache in *ppPager.
+** The file to be cached need not exist. The file is not locked until
+** the first call to sqlitepager_get() and is only held open until the
+** last page is released using sqlitepager_unref().
+**
+** If zFilename is NULL then a randomly-named temporary file is created
+** and used as the file to be cached. The file will be deleted
+** automatically when it is closed.
+*/
+int sqlitepager_open(
+ Pager **ppPager, /* Return the Pager structure here */
+ const char *zFilename, /* Name of the database file to open */
+ int mxPage, /* Max number of in-memory cache pages */
+ int nExtra, /* Extra bytes append to each in-memory page */
+ int useJournal /* TRUE to use a rollback journal on this file */
+){
+ Pager *pPager;
+ char *zFullPathname;
+ int nameLen;
+ OsFile fd;
+ int rc, i;
+ int tempFile;
+ int readOnly = 0;
+ char zTemp[SQLITE_TEMPNAME_SIZE];
+
+ *ppPager = 0;
+ if( sqlite_malloc_failed ){
+ return SQLITE_NOMEM;
+ }
+ if( zFilename && zFilename[0] ){
+ zFullPathname = sqliteOsFullPathname(zFilename);
+ rc = sqliteOsOpenReadWrite(zFullPathname, &fd, &readOnly);
+ tempFile = 0;
+ }else{
+ rc = sqlitepager_opentemp(zTemp, &fd);
+ zFilename = zTemp;
+ zFullPathname = sqliteOsFullPathname(zFilename);
+ tempFile = 1;
+ }
+ if( sqlite_malloc_failed ){
+ return SQLITE_NOMEM;
+ }
+ if( rc!=SQLITE_OK ){
+ sqliteFree(zFullPathname);
+ return SQLITE_CANTOPEN;
+ }
+ nameLen = strlen(zFullPathname);
+ pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 );
+ if( pPager==0 ){
+ sqliteOsClose(&fd);
+ sqliteFree(zFullPathname);
+ return SQLITE_NOMEM;
+ }
+ SET_PAGER(pPager);
+ pPager->zFilename = (char*)&pPager[1];
+ pPager->zDirectory = &pPager->zFilename[nameLen+1];
+ pPager->zJournal = &pPager->zDirectory[nameLen+1];
+ strcpy(pPager->zFilename, zFullPathname);
+ strcpy(pPager->zDirectory, zFullPathname);
+ for(i=nameLen; i>0 && pPager->zDirectory[i-1]!='/'; i--){}
+ if( i>0 ) pPager->zDirectory[i-1] = 0;
+ strcpy(pPager->zJournal, zFullPathname);
+ sqliteFree(zFullPathname);
+ strcpy(&pPager->zJournal[nameLen], "-journal");
+ pPager->fd = fd;
+ pPager->journalOpen = 0;
+ pPager->useJournal = useJournal;
+ pPager->ckptOpen = 0;
+ pPager->ckptInUse = 0;
+ pPager->nRef = 0;
+ pPager->dbSize = -1;
+ pPager->ckptSize = 0;
+ pPager->ckptJSize = 0;
+ pPager->nPage = 0;
+ pPager->mxPage = mxPage>5 ? mxPage : 10;
+ pPager->state = SQLITE_UNLOCK;
+ pPager->errMask = 0;
+ pPager->tempFile = tempFile;
+ pPager->readOnly = readOnly;
+ pPager->needSync = 0;
+ pPager->noSync = pPager->tempFile || !useJournal;
+ pPager->pFirst = 0;
+ pPager->pFirstSynced = 0;
+ pPager->pLast = 0;
+ pPager->nExtra = nExtra;
+ memset(pPager->aHash, 0, sizeof(pPager->aHash));
+ *ppPager = pPager;
+ return SQLITE_OK;
+}
+
+/*
+** Set the destructor for this pager. If not NULL, the destructor is called
+** when the reference count on each page reaches zero. The destructor can
+** be used to clean up information in the extra segment appended to each page.
+**
+** The destructor is not called as a result sqlitepager_close().
+** Destructors are only called by sqlitepager_unref().
+*/
+void sqlitepager_set_destructor(Pager *pPager, void (*xDesc)(void*)){
+ pPager->xDestructor = xDesc;
+}
+
+/*
+** Return the total number of pages in the disk file associated with
+** pPager.
+*/
+int sqlitepager_pagecount(Pager *pPager){
+ off_t n;
+ assert( pPager!=0 );
+ if( pPager->dbSize>=0 ){
+ return pPager->dbSize;
+ }
+ if( sqliteOsFileSize(&pPager->fd, &n)!=SQLITE_OK ){
+ pPager->errMask |= PAGER_ERR_DISK;
+ return 0;
+ }
+ n /= SQLITE_PAGE_SIZE;
+ if( pPager->state!=SQLITE_UNLOCK ){
+ pPager->dbSize = n;
+ }
+ return n;
+}
+
+/*
+** Forward declaration
+*/
+static int syncJournal(Pager*);
+
+/*
+** Truncate the file to the number of pages specified.
+*/
+int sqlitepager_truncate(Pager *pPager, Pgno nPage){
+ int rc;
+ if( pPager->dbSize<0 ){
+ sqlitepager_pagecount(pPager);
+ }
+ if( pPager->errMask!=0 ){
+ rc = pager_errcode(pPager);
+ return rc;
+ }
+ if( nPage>=(unsigned)pPager->dbSize ){
+ return SQLITE_OK;
+ }
+ syncJournal(pPager);
+ rc = sqliteOsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)nPage);
+ if( rc==SQLITE_OK ){
+ pPager->dbSize = nPage;
+ }
+ return rc;
+}
+
+/*
+** Shutdown the page cache. Free all memory and close all files.
+**
+** If a transaction was in progress when this routine is called, that
+** transaction is rolled back. All outstanding pages are invalidated
+** and their memory is freed. Any attempt to use a page associated
+** with this page cache after this function returns will likely
+** result in a coredump.
+*/
+int sqlitepager_close(Pager *pPager){
+ PgHdr *pPg, *pNext;
+ switch( pPager->state ){
+ case SQLITE_WRITELOCK: {
+ sqlitepager_rollback(pPager);
+ sqliteOsUnlock(&pPager->fd);
+ assert( pPager->journalOpen==0 );
+ break;
+ }
+ case SQLITE_READLOCK: {
+ sqliteOsUnlock(&pPager->fd);
+ break;
+ }
+ default: {
+ /* Do nothing */
+ break;
+ }
+ }
+ for(pPg=pPager->pAll; pPg; pPg=pNext){
+ pNext = pPg->pNextAll;
+ sqliteFree(pPg);
+ }
+ sqliteOsClose(&pPager->fd);
+ assert( pPager->journalOpen==0 );
+ /* Temp files are automatically deleted by the OS
+ ** if( pPager->tempFile ){
+ ** sqliteOsDelete(pPager->zFilename);
+ ** }
+ */
+ CLR_PAGER(pPager);
+ if( pPager->zFilename!=(char*)&pPager[1] ){
+ assert( 0 ); /* Cannot happen */
+ sqliteFree(pPager->zFilename);
+ sqliteFree(pPager->zJournal);
+ sqliteFree(pPager->zDirectory);
+ }
+ sqliteFree(pPager);
+ return SQLITE_OK;
+}
+
+/*
+** Return the page number for the given page data.
+*/
+Pgno sqlitepager_pagenumber(void *pData){
+ PgHdr *p = DATA_TO_PGHDR(pData);
+ return p->pgno;
+}
+
+/*
+** Increment the reference count for a page. If the page is
+** currently on the freelist (the reference count is zero) then
+** remove it from the freelist.
+*/
+#define page_ref(P) ((P)->nRef==0?_page_ref(P):(void)(P)->nRef++)
+static void _page_ref(PgHdr *pPg){
+ if( pPg->nRef==0 ){
+ /* The page is currently on the freelist. Remove it. */
+ if( pPg==pPg->pPager->pFirstSynced ){
+ PgHdr *p = pPg->pNextFree;
+ while( p && p->needSync ){ p = p->pNextFree; }
+ pPg->pPager->pFirstSynced = p;
+ }
+ if( pPg->pPrevFree ){
+ pPg->pPrevFree->pNextFree = pPg->pNextFree;
+ }else{
+ pPg->pPager->pFirst = pPg->pNextFree;
+ }
+ if( pPg->pNextFree ){
+ pPg->pNextFree->pPrevFree = pPg->pPrevFree;
+ }else{
+ pPg->pPager->pLast = pPg->pPrevFree;
+ }
+ pPg->pPager->nRef++;
+ }
+ pPg->nRef++;
+ REFINFO(pPg);
+}
+
+/*
+** Increment the reference count for a page. The input pointer is
+** a reference to the page data.
+*/
+int sqlitepager_ref(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ page_ref(pPg);
+ return SQLITE_OK;
+}
+
+/*
+** Sync the journal. In other words, make sure all the pages that have
+** been written to the journal have actually reached the surface of the
+** disk. It is not safe to modify the original database file until after
+** the journal has been synced. If the original database is modified before
+** the journal is synced and a power failure occurs, the unsynced journal
+** data would be lost and we would be unable to completely rollback the
+** database changes. Database corruption would occur.
+**
+** This routine also updates the nRec field in the header of the journal.
+** (See comments on the pager_playback() routine for additional information.)
+** If the sync mode is FULL, two syncs will occur. First the whole journal
+** is synced, then the nRec field is updated, then a second sync occurs.
+**
+** For temporary databases, we do not care if we are able to rollback
+** after a power failure, so sync occurs.
+**
+** This routine clears the needSync field of every page current held in
+** memory.
+*/
+static int syncJournal(Pager *pPager){
+ PgHdr *pPg;
+ int rc = SQLITE_OK;
+
+ /* Sync the journal before modifying the main database
+ ** (assuming there is a journal and it needs to be synced.)
+ */
+ if( pPager->needSync ){
+ if( !pPager->tempFile ){
+ assert( pPager->journalOpen );
+ /* assert( !pPager->noSync ); // noSync might be set if synchronous
+ ** was turned off after the transaction was started. Ticket #615 */
+#ifndef NDEBUG
+ {
+ /* Make sure the pPager->nRec counter we are keeping agrees
+ ** with the nRec computed from the size of the journal file.
+ */
+ off_t hdrSz, pgSz, jSz;
+ hdrSz = JOURNAL_HDR_SZ(journal_format);
+ pgSz = JOURNAL_PG_SZ(journal_format);
+ rc = sqliteOsFileSize(&pPager->jfd, &jSz);
+ if( rc!=0 ) return rc;
+ assert( pPager->nRec*pgSz+hdrSz==jSz );
+ }
+#endif
+ if( journal_format>=3 ){
+ /* Write the nRec value into the journal file header */
+ off_t szJ;
+ if( pPager->fullSync ){
+ TRACE1("SYNC\n");
+ rc = sqliteOsSync(&pPager->jfd);
+ if( rc!=0 ) return rc;
+ }
+ sqliteOsSeek(&pPager->jfd, sizeof(aJournalMagic1));
+ rc = write32bits(&pPager->jfd, pPager->nRec);
+ if( rc ) return rc;
+ szJ = JOURNAL_HDR_SZ(journal_format) +
+ pPager->nRec*JOURNAL_PG_SZ(journal_format);
+ sqliteOsSeek(&pPager->jfd, szJ);
+ }
+ TRACE1("SYNC\n");
+ rc = sqliteOsSync(&pPager->jfd);
+ if( rc!=0 ) return rc;
+ pPager->journalStarted = 1;
+ }
+ pPager->needSync = 0;
+
+ /* Erase the needSync flag from every page.
+ */
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ pPg->needSync = 0;
+ }
+ pPager->pFirstSynced = pPager->pFirst;
+ }
+
+#ifndef NDEBUG
+ /* If the Pager.needSync flag is clear then the PgHdr.needSync
+ ** flag must also be clear for all pages. Verify that this
+ ** invariant is true.
+ */
+ else{
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ assert( pPg->needSync==0 );
+ }
+ assert( pPager->pFirstSynced==pPager->pFirst );
+ }
+#endif
+
+ return rc;
+}
+
+/*
+** Given a list of pages (connected by the PgHdr.pDirty pointer) write
+** every one of those pages out to the database file and mark them all
+** as clean.
+*/
+static int pager_write_pagelist(PgHdr *pList){
+ Pager *pPager;
+ int rc;
+
+ if( pList==0 ) return SQLITE_OK;
+ pPager = pList->pPager;
+ while( pList ){
+ assert( pList->dirty );
+ sqliteOsSeek(&pPager->fd, (pList->pgno-1)*(off_t)SQLITE_PAGE_SIZE);
+ CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6);
+ TRACE2("STORE %d\n", pList->pgno);
+ rc = sqliteOsWrite(&pPager->fd, PGHDR_TO_DATA(pList), SQLITE_PAGE_SIZE);
+ CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 0);
+ if( rc ) return rc;
+ pList->dirty = 0;
+ pList = pList->pDirty;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Collect every dirty page into a dirty list and
+** return a pointer to the head of that list. All pages are
+** collected even if they are still in use.
+*/
+static PgHdr *pager_get_all_dirty_pages(Pager *pPager){
+ PgHdr *p, *pList;
+ pList = 0;
+ for(p=pPager->pAll; p; p=p->pNextAll){
+ if( p->dirty ){
+ p->pDirty = pList;
+ pList = p;
+ }
+ }
+ return pList;
+}
+
+/*
+** Acquire a page.
+**
+** A read lock on the disk file is obtained when the first page is acquired.
+** This read lock is dropped when the last page is released.
+**
+** A _get works for any page number greater than 0. If the database
+** file is smaller than the requested page, then no actual disk
+** read occurs and the memory image of the page is initialized to
+** all zeros. The extra data appended to a page is always initialized
+** to zeros the first time a page is loaded into memory.
+**
+** The acquisition might fail for several reasons. In all cases,
+** an appropriate error code is returned and *ppPage is set to NULL.
+**
+** See also sqlitepager_lookup(). Both this routine and _lookup() attempt
+** to find a page in the in-memory cache first. If the page is not already
+** in memory, this routine goes to disk to read it in whereas _lookup()
+** just returns 0. This routine acquires a read-lock the first time it
+** has to go to disk, and could also playback an old journal if necessary.
+** Since _lookup() never goes to disk, it never has to deal with locks
+** or journal files.
+*/
+int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){
+ PgHdr *pPg;
+ int rc;
+
+ /* Make sure we have not hit any critical errors.
+ */
+ assert( pPager!=0 );
+ assert( pgno!=0 );
+ *ppPage = 0;
+ if( pPager->errMask & ~(PAGER_ERR_FULL) ){
+ return pager_errcode(pPager);
+ }
+
+ /* If this is the first page accessed, then get a read lock
+ ** on the database file.
+ */
+ if( pPager->nRef==0 ){
+ rc = sqliteOsReadLock(&pPager->fd);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pPager->state = SQLITE_READLOCK;
+
+ /* If a journal file exists, try to play it back.
+ */
+ if( pPager->useJournal && sqliteOsFileExists(pPager->zJournal) ){
+ int rc;
+
+ /* Get a write lock on the database
+ */
+ rc = sqliteOsWriteLock(&pPager->fd);
+ if( rc!=SQLITE_OK ){
+ if( sqliteOsUnlock(&pPager->fd)!=SQLITE_OK ){
+ /* This should never happen! */
+ rc = SQLITE_INTERNAL;
+ }
+ return rc;
+ }
+ pPager->state = SQLITE_WRITELOCK;
+
+ /* Open the journal for reading only. Return SQLITE_BUSY if
+ ** we are unable to open the journal file.
+ **
+ ** The journal file does not need to be locked itself. The
+ ** journal file is never open unless the main database file holds
+ ** a write lock, so there is never any chance of two or more
+ ** processes opening the journal at the same time.
+ */
+ rc = sqliteOsOpenReadOnly(pPager->zJournal, &pPager->jfd);
+ if( rc!=SQLITE_OK ){
+ rc = sqliteOsUnlock(&pPager->fd);
+ assert( rc==SQLITE_OK );
+ return SQLITE_BUSY;
+ }
+ pPager->journalOpen = 1;
+ pPager->journalStarted = 0;
+
+ /* Playback and delete the journal. Drop the database write
+ ** lock and reacquire the read lock.
+ */
+ rc = pager_playback(pPager, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ pPg = 0;
+ }else{
+ /* Search for page in cache */
+ pPg = pager_lookup(pPager, pgno);
+ }
+ if( pPg==0 ){
+ /* The requested page is not in the page cache. */
+ int h;
+ pPager->nMiss++;
+ if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 ){
+ /* Create a new page */
+ pPg = sqliteMallocRaw( sizeof(*pPg) + SQLITE_PAGE_SIZE
+ + sizeof(u32) + pPager->nExtra );
+ if( pPg==0 ){
+ pager_unwritelock(pPager);
+ pPager->errMask |= PAGER_ERR_MEM;
+ return SQLITE_NOMEM;
+ }
+ memset(pPg, 0, sizeof(*pPg));
+ pPg->pPager = pPager;
+ pPg->pNextAll = pPager->pAll;
+ if( pPager->pAll ){
+ pPager->pAll->pPrevAll = pPg;
+ }
+ pPg->pPrevAll = 0;
+ pPager->pAll = pPg;
+ pPager->nPage++;
+ }else{
+ /* Find a page to recycle. Try to locate a page that does not
+ ** require us to do an fsync() on the journal.
+ */
+ pPg = pPager->pFirstSynced;
+
+ /* If we could not find a page that does not require an fsync()
+ ** on the journal file then fsync the journal file. This is a
+ ** very slow operation, so we work hard to avoid it. But sometimes
+ ** it can't be helped.
+ */
+ if( pPg==0 ){
+ int rc = syncJournal(pPager);
+ if( rc!=0 ){
+ sqlitepager_rollback(pPager);
+ return SQLITE_IOERR;
+ }
+ pPg = pPager->pFirst;
+ }
+ assert( pPg->nRef==0 );
+
+ /* Write the page to the database file if it is dirty.
+ */
+ if( pPg->dirty ){
+ assert( pPg->needSync==0 );
+ pPg->pDirty = 0;
+ rc = pager_write_pagelist( pPg );
+ if( rc!=SQLITE_OK ){
+ sqlitepager_rollback(pPager);
+ return SQLITE_IOERR;
+ }
+ }
+ assert( pPg->dirty==0 );
+
+ /* If the page we are recycling is marked as alwaysRollback, then
+ ** set the global alwaysRollback flag, thus disabling the
+ ** sqlite_dont_rollback() optimization for the rest of this transaction.
+ ** It is necessary to do this because the page marked alwaysRollback
+ ** might be reloaded at a later time but at that point we won't remember
+ ** that is was marked alwaysRollback. This means that all pages must
+ ** be marked as alwaysRollback from here on out.
+ */
+ if( pPg->alwaysRollback ){
+ pPager->alwaysRollback = 1;
+ }
+
+ /* Unlink the old page from the free list and the hash table
+ */
+ if( pPg==pPager->pFirstSynced ){
+ PgHdr *p = pPg->pNextFree;
+ while( p && p->needSync ){ p = p->pNextFree; }
+ pPager->pFirstSynced = p;
+ }
+ if( pPg->pPrevFree ){
+ pPg->pPrevFree->pNextFree = pPg->pNextFree;
+ }else{
+ assert( pPager->pFirst==pPg );
+ pPager->pFirst = pPg->pNextFree;
+ }
+ if( pPg->pNextFree ){
+ pPg->pNextFree->pPrevFree = pPg->pPrevFree;
+ }else{
+ assert( pPager->pLast==pPg );
+ pPager->pLast = pPg->pPrevFree;
+ }
+ pPg->pNextFree = pPg->pPrevFree = 0;
+ if( pPg->pNextHash ){
+ pPg->pNextHash->pPrevHash = pPg->pPrevHash;
+ }
+ if( pPg->pPrevHash ){
+ pPg->pPrevHash->pNextHash = pPg->pNextHash;
+ }else{
+ h = pager_hash(pPg->pgno);
+ assert( pPager->aHash[h]==pPg );
+ pPager->aHash[h] = pPg->pNextHash;
+ }
+ pPg->pNextHash = pPg->pPrevHash = 0;
+ pPager->nOvfl++;
+ }
+ pPg->pgno = pgno;
+ if( pPager->aInJournal && (int)pgno<=pPager->origDbSize ){
+ sqliteCheckMemory(pPager->aInJournal, pgno/8);
+ assert( pPager->journalOpen );
+ pPg->inJournal = (pPager->aInJournal[pgno/8] & (1<<(pgno&7)))!=0;
+ pPg->needSync = 0;
+ }else{
+ pPg->inJournal = 0;
+ pPg->needSync = 0;
+ }
+ if( pPager->aInCkpt && (int)pgno<=pPager->ckptSize
+ && (pPager->aInCkpt[pgno/8] & (1<<(pgno&7)))!=0 ){
+ page_add_to_ckpt_list(pPg);
+ }else{
+ page_remove_from_ckpt_list(pPg);
+ }
+ pPg->dirty = 0;
+ pPg->nRef = 1;
+ REFINFO(pPg);
+ pPager->nRef++;
+ h = pager_hash(pgno);
+ pPg->pNextHash = pPager->aHash[h];
+ pPager->aHash[h] = pPg;
+ if( pPg->pNextHash ){
+ assert( pPg->pNextHash->pPrevHash==0 );
+ pPg->pNextHash->pPrevHash = pPg;
+ }
+ if( pPager->nExtra>0 ){
+ memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra);
+ }
+ if( pPager->dbSize<0 ) sqlitepager_pagecount(pPager);
+ if( pPager->errMask!=0 ){
+ sqlitepager_unref(PGHDR_TO_DATA(pPg));
+ rc = pager_errcode(pPager);
+ return rc;
+ }
+ if( pPager->dbSize<(int)pgno ){
+ memset(PGHDR_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE);
+ }else{
+ int rc;
+ sqliteOsSeek(&pPager->fd, (pgno-1)*(off_t)SQLITE_PAGE_SIZE);
+ rc = sqliteOsRead(&pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
+ TRACE2("FETCH %d\n", pPg->pgno);
+ CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3);
+ if( rc!=SQLITE_OK ){
+ off_t fileSize;
+ if( sqliteOsFileSize(&pPager->fd,&fileSize)!=SQLITE_OK
+ || fileSize>=pgno*SQLITE_PAGE_SIZE ){
+ sqlitepager_unref(PGHDR_TO_DATA(pPg));
+ return rc;
+ }else{
+ memset(PGHDR_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE);
+ }
+ }
+ }
+ }else{
+ /* The requested page is in the page cache. */
+ pPager->nHit++;
+ page_ref(pPg);
+ }
+ *ppPage = PGHDR_TO_DATA(pPg);
+ return SQLITE_OK;
+}
+
+/*
+** Acquire a page if it is already in the in-memory cache. Do
+** not read the page from disk. Return a pointer to the page,
+** or 0 if the page is not in cache.
+**
+** See also sqlitepager_get(). The difference between this routine
+** and sqlitepager_get() is that _get() will go to the disk and read
+** in the page if the page is not already in cache. This routine
+** returns NULL if the page is not in cache or if a disk I/O error
+** has ever happened.
+*/
+void *sqlitepager_lookup(Pager *pPager, Pgno pgno){
+ PgHdr *pPg;
+
+ assert( pPager!=0 );
+ assert( pgno!=0 );
+ if( pPager->errMask & ~(PAGER_ERR_FULL) ){
+ return 0;
+ }
+ /* if( pPager->nRef==0 ){
+ ** return 0;
+ ** }
+ */
+ pPg = pager_lookup(pPager, pgno);
+ if( pPg==0 ) return 0;
+ page_ref(pPg);
+ return PGHDR_TO_DATA(pPg);
+}
+
+/*
+** Release a page.
+**
+** If the number of references to the page drop to zero, then the
+** page is added to the LRU list. When all references to all pages
+** are released, a rollback occurs and the lock on the database is
+** removed.
+*/
+int sqlitepager_unref(void *pData){
+ PgHdr *pPg;
+
+ /* Decrement the reference count for this page
+ */
+ pPg = DATA_TO_PGHDR(pData);
+ assert( pPg->nRef>0 );
+ pPg->nRef--;
+ REFINFO(pPg);
+
+ /* When the number of references to a page reach 0, call the
+ ** destructor and add the page to the freelist.
+ */
+ if( pPg->nRef==0 ){
+ Pager *pPager;
+ pPager = pPg->pPager;
+ pPg->pNextFree = 0;
+ pPg->pPrevFree = pPager->pLast;
+ pPager->pLast = pPg;
+ if( pPg->pPrevFree ){
+ pPg->pPrevFree->pNextFree = pPg;
+ }else{
+ pPager->pFirst = pPg;
+ }
+ if( pPg->needSync==0 && pPager->pFirstSynced==0 ){
+ pPager->pFirstSynced = pPg;
+ }
+ if( pPager->xDestructor ){
+ pPager->xDestructor(pData);
+ }
+
+ /* When all pages reach the freelist, drop the read lock from
+ ** the database file.
+ */
+ pPager->nRef--;
+ assert( pPager->nRef>=0 );
+ if( pPager->nRef==0 ){
+ pager_reset(pPager);
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Create a journal file for pPager. There should already be a write
+** lock on the database file when this routine is called.
+**
+** Return SQLITE_OK if everything. Return an error code and release the
+** write lock if anything goes wrong.
+*/
+static int pager_open_journal(Pager *pPager){
+ int rc;
+ assert( pPager->state==SQLITE_WRITELOCK );
+ assert( pPager->journalOpen==0 );
+ assert( pPager->useJournal );
+ sqlitepager_pagecount(pPager);
+ pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 );
+ if( pPager->aInJournal==0 ){
+ sqliteOsReadLock(&pPager->fd);
+ pPager->state = SQLITE_READLOCK;
+ return SQLITE_NOMEM;
+ }
+ rc = sqliteOsOpenExclusive(pPager->zJournal, &pPager->jfd,pPager->tempFile);
+ if( rc!=SQLITE_OK ){
+ sqliteFree(pPager->aInJournal);
+ pPager->aInJournal = 0;
+ sqliteOsReadLock(&pPager->fd);
+ pPager->state = SQLITE_READLOCK;
+ return SQLITE_CANTOPEN;
+ }
+ sqliteOsOpenDirectory(pPager->zDirectory, &pPager->jfd);
+ pPager->journalOpen = 1;
+ pPager->journalStarted = 0;
+ pPager->needSync = 0;
+ pPager->alwaysRollback = 0;
+ pPager->nRec = 0;
+ if( pPager->errMask!=0 ){
+ rc = pager_errcode(pPager);
+ return rc;
+ }
+ pPager->origDbSize = pPager->dbSize;
+ if( journal_format==JOURNAL_FORMAT_3 ){
+ rc = sqliteOsWrite(&pPager->jfd, aJournalMagic3, sizeof(aJournalMagic3));
+ if( rc==SQLITE_OK ){
+ rc = write32bits(&pPager->jfd, pPager->noSync ? 0xffffffff : 0);
+ }
+ if( rc==SQLITE_OK ){
+ sqliteRandomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
+ rc = write32bits(&pPager->jfd, pPager->cksumInit);
+ }
+ }else if( journal_format==JOURNAL_FORMAT_2 ){
+ rc = sqliteOsWrite(&pPager->jfd, aJournalMagic2, sizeof(aJournalMagic2));
+ }else{
+ assert( journal_format==JOURNAL_FORMAT_1 );
+ rc = sqliteOsWrite(&pPager->jfd, aJournalMagic1, sizeof(aJournalMagic1));
+ }
+ if( rc==SQLITE_OK ){
+ rc = write32bits(&pPager->jfd, pPager->dbSize);
+ }
+ if( pPager->ckptAutoopen && rc==SQLITE_OK ){
+ rc = sqlitepager_ckpt_begin(pPager);
+ }
+ if( rc!=SQLITE_OK ){
+ rc = pager_unwritelock(pPager);
+ if( rc==SQLITE_OK ){
+ rc = SQLITE_FULL;
+ }
+ }
+ return rc;
+}
+
+/*
+** Acquire a write-lock on the database. The lock is removed when
+** the any of the following happen:
+**
+** * sqlitepager_commit() is called.
+** * sqlitepager_rollback() is called.
+** * sqlitepager_close() is called.
+** * sqlitepager_unref() is called to on every outstanding page.
+**
+** The parameter to this routine is a pointer to any open page of the
+** database file. Nothing changes about the page - it is used merely
+** to acquire a pointer to the Pager structure and as proof that there
+** is already a read-lock on the database.
+**
+** A journal file is opened if this is not a temporary file. For
+** temporary files, the opening of the journal file is deferred until
+** there is an actual need to write to the journal.
+**
+** If the database is already write-locked, this routine is a no-op.
+*/
+int sqlitepager_begin(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ Pager *pPager = pPg->pPager;
+ int rc = SQLITE_OK;
+ assert( pPg->nRef>0 );
+ assert( pPager->state!=SQLITE_UNLOCK );
+ if( pPager->state==SQLITE_READLOCK ){
+ assert( pPager->aInJournal==0 );
+ rc = sqliteOsWriteLock(&pPager->fd);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pPager->state = SQLITE_WRITELOCK;
+ pPager->dirtyFile = 0;
+ TRACE1("TRANSACTION\n");
+ if( pPager->useJournal && !pPager->tempFile ){
+ rc = pager_open_journal(pPager);
+ }
+ }
+ return rc;
+}
+
+/*
+** Mark a data page as writeable. The page is written into the journal
+** if it is not there already. This routine must be called before making
+** changes to a page.
+**
+** The first time this routine is called, the pager creates a new
+** journal and acquires a write lock on the database. If the write
+** lock could not be acquired, this routine returns SQLITE_BUSY. The
+** calling routine must check for that return value and be careful not to
+** change any page data until this routine returns SQLITE_OK.
+**
+** If the journal file could not be written because the disk is full,
+** then this routine returns SQLITE_FULL and does an immediate rollback.
+** All subsequent write attempts also return SQLITE_FULL until there
+** is a call to sqlitepager_commit() or sqlitepager_rollback() to
+** reset.
+*/
+int sqlitepager_write(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ Pager *pPager = pPg->pPager;
+ int rc = SQLITE_OK;
+
+ /* Check for errors
+ */
+ if( pPager->errMask ){
+ return pager_errcode(pPager);
+ }
+ if( pPager->readOnly ){
+ return SQLITE_PERM;
+ }
+
+ /* Mark the page as dirty. If the page has already been written
+ ** to the journal then we can return right away.
+ */
+ pPg->dirty = 1;
+ if( pPg->inJournal && (pPg->inCkpt || pPager->ckptInUse==0) ){
+ pPager->dirtyFile = 1;
+ return SQLITE_OK;
+ }
+
+ /* If we get this far, it means that the page needs to be
+ ** written to the transaction journal or the ckeckpoint journal
+ ** or both.
+ **
+ ** First check to see that the transaction journal exists and
+ ** create it if it does not.
+ */
+ assert( pPager->state!=SQLITE_UNLOCK );
+ rc = sqlitepager_begin(pData);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( pPager->state==SQLITE_WRITELOCK );
+ if( !pPager->journalOpen && pPager->useJournal ){
+ rc = pager_open_journal(pPager);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ assert( pPager->journalOpen || !pPager->useJournal );
+ pPager->dirtyFile = 1;
+
+ /* The transaction journal now exists and we have a write lock on the
+ ** main database file. Write the current page to the transaction
+ ** journal if it is not there already.
+ */
+ if( !pPg->inJournal && pPager->useJournal ){
+ if( (int)pPg->pgno <= pPager->origDbSize ){
+ int szPg;
+ u32 saved;
+ if( journal_format>=JOURNAL_FORMAT_3 ){
+ u32 cksum = pager_cksum(pPager, pPg->pgno, pData);
+ saved = *(u32*)PGHDR_TO_EXTRA(pPg);
+ store32bits(cksum, pPg, SQLITE_PAGE_SIZE);
+ szPg = SQLITE_PAGE_SIZE+8;
+ }else{
+ szPg = SQLITE_PAGE_SIZE+4;
+ }
+ store32bits(pPg->pgno, pPg, -4);
+ CODEC(pPager, pData, pPg->pgno, 7);
+ rc = sqliteOsWrite(&pPager->jfd, &((char*)pData)[-4], szPg);
+ TRACE3("JOURNAL %d %d\n", pPg->pgno, pPg->needSync);
+ CODEC(pPager, pData, pPg->pgno, 0);
+ if( journal_format>=JOURNAL_FORMAT_3 ){
+ *(u32*)PGHDR_TO_EXTRA(pPg) = saved;
+ }
+ if( rc!=SQLITE_OK ){
+ sqlitepager_rollback(pPager);
+ pPager->errMask |= PAGER_ERR_FULL;
+ return rc;
+ }
+ pPager->nRec++;
+ assert( pPager->aInJournal!=0 );
+ pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ pPg->needSync = !pPager->noSync;
+ pPg->inJournal = 1;
+ if( pPager->ckptInUse ){
+ pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_ckpt_list(pPg);
+ }
+ }else{
+ pPg->needSync = !pPager->journalStarted && !pPager->noSync;
+ TRACE3("APPEND %d %d\n", pPg->pgno, pPg->needSync);
+ }
+ if( pPg->needSync ){
+ pPager->needSync = 1;
+ }
+ }
+
+ /* If the checkpoint journal is open and the page is not in it,
+ ** then write the current page to the checkpoint journal. Note that
+ ** the checkpoint journal always uses the simplier format 2 that lacks
+ ** checksums. The header is also omitted from the checkpoint journal.
+ */
+ if( pPager->ckptInUse && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){
+ assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize );
+ store32bits(pPg->pgno, pPg, -4);
+ CODEC(pPager, pData, pPg->pgno, 7);
+ rc = sqliteOsWrite(&pPager->cpfd, &((char*)pData)[-4], SQLITE_PAGE_SIZE+4);
+ TRACE2("CKPT-JOURNAL %d\n", pPg->pgno);
+ CODEC(pPager, pData, pPg->pgno, 0);
+ if( rc!=SQLITE_OK ){
+ sqlitepager_rollback(pPager);
+ pPager->errMask |= PAGER_ERR_FULL;
+ return rc;
+ }
+ pPager->ckptNRec++;
+ assert( pPager->aInCkpt!=0 );
+ pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_ckpt_list(pPg);
+ }
+
+ /* Update the database size and return.
+ */
+ if( pPager->dbSize<(int)pPg->pgno ){
+ pPager->dbSize = pPg->pgno;
+ }
+ return rc;
+}
+
+/*
+** Return TRUE if the page given in the argument was previously passed
+** to sqlitepager_write(). In other words, return TRUE if it is ok
+** to change the content of the page.
+*/
+int sqlitepager_iswriteable(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ return pPg->dirty;
+}
+
+/*
+** Replace the content of a single page with the information in the third
+** argument.
+*/
+int sqlitepager_overwrite(Pager *pPager, Pgno pgno, void *pData){
+ void *pPage;
+ int rc;
+
+ rc = sqlitepager_get(pPager, pgno, &pPage);
+ if( rc==SQLITE_OK ){
+ rc = sqlitepager_write(pPage);
+ if( rc==SQLITE_OK ){
+ memcpy(pPage, pData, SQLITE_PAGE_SIZE);
+ }
+ sqlitepager_unref(pPage);
+ }
+ return rc;
+}
+
+/*
+** A call to this routine tells the pager that it is not necessary to
+** write the information on page "pgno" back to the disk, even though
+** that page might be marked as dirty.
+**
+** The overlying software layer calls this routine when all of the data
+** on the given page is unused. The pager marks the page as clean so
+** that it does not get written to disk.
+**
+** Tests show that this optimization, together with the
+** sqlitepager_dont_rollback() below, more than double the speed
+** of large INSERT operations and quadruple the speed of large DELETEs.
+**
+** When this routine is called, set the alwaysRollback flag to true.
+** Subsequent calls to sqlitepager_dont_rollback() for the same page
+** will thereafter be ignored. This is necessary to avoid a problem
+** where a page with data is added to the freelist during one part of
+** a transaction then removed from the freelist during a later part
+** of the same transaction and reused for some other purpose. When it
+** is first added to the freelist, this routine is called. When reused,
+** the dont_rollback() routine is called. But because the page contains
+** critical data, we still need to be sure it gets rolled back in spite
+** of the dont_rollback() call.
+*/
+void sqlitepager_dont_write(Pager *pPager, Pgno pgno){
+ PgHdr *pPg;
+
+ pPg = pager_lookup(pPager, pgno);
+ pPg->alwaysRollback = 1;
+ if( pPg && pPg->dirty && !pPager->ckptInUse ){
+ if( pPager->dbSize==(int)pPg->pgno && pPager->origDbSize<pPager->dbSize ){
+ /* If this pages is the last page in the file and the file has grown
+ ** during the current transaction, then do NOT mark the page as clean.
+ ** When the database file grows, we must make sure that the last page
+ ** gets written at least once so that the disk file will be the correct
+ ** size. If you do not write this page and the size of the file
+ ** on the disk ends up being too small, that can lead to database
+ ** corruption during the next transaction.
+ */
+ }else{
+ TRACE2("DONT_WRITE %d\n", pgno);
+ pPg->dirty = 0;
+ }
+ }
+}
+
+/*
+** A call to this routine tells the pager that if a rollback occurs,
+** it is not necessary to restore the data on the given page. This
+** means that the pager does not have to record the given page in the
+** rollback journal.
+*/
+void sqlitepager_dont_rollback(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ Pager *pPager = pPg->pPager;
+
+ if( pPager->state!=SQLITE_WRITELOCK || pPager->journalOpen==0 ) return;
+ if( pPg->alwaysRollback || pPager->alwaysRollback ) return;
+ if( !pPg->inJournal && (int)pPg->pgno <= pPager->origDbSize ){
+ assert( pPager->aInJournal!=0 );
+ pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ pPg->inJournal = 1;
+ if( pPager->ckptInUse ){
+ pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_ckpt_list(pPg);
+ }
+ TRACE2("DONT_ROLLBACK %d\n", pPg->pgno);
+ }
+ if( pPager->ckptInUse && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){
+ assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize );
+ assert( pPager->aInCkpt!=0 );
+ pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_ckpt_list(pPg);
+ }
+}
+
+/*
+** Commit all changes to the database and release the write lock.
+**
+** If the commit fails for any reason, a rollback attempt is made
+** and an error code is returned. If the commit worked, SQLITE_OK
+** is returned.
+*/
+int sqlitepager_commit(Pager *pPager){
+ int rc;
+ PgHdr *pPg;
+
+ if( pPager->errMask==PAGER_ERR_FULL ){
+ rc = sqlitepager_rollback(pPager);
+ if( rc==SQLITE_OK ){
+ rc = SQLITE_FULL;
+ }
+ return rc;
+ }
+ if( pPager->errMask!=0 ){
+ rc = pager_errcode(pPager);
+ return rc;
+ }
+ if( pPager->state!=SQLITE_WRITELOCK ){
+ return SQLITE_ERROR;
+ }
+ TRACE1("COMMIT\n");
+ if( pPager->dirtyFile==0 ){
+ /* Exit early (without doing the time-consuming sqliteOsSync() calls)
+ ** if there have been no changes to the database file. */
+ assert( pPager->needSync==0 );
+ rc = pager_unwritelock(pPager);
+ pPager->dbSize = -1;
+ return rc;
+ }
+ assert( pPager->journalOpen );
+ rc = syncJournal(pPager);
+ if( rc!=SQLITE_OK ){
+ goto commit_abort;
+ }
+ pPg = pager_get_all_dirty_pages(pPager);
+ if( pPg ){
+ rc = pager_write_pagelist(pPg);
+ if( rc || (!pPager->noSync && sqliteOsSync(&pPager->fd)!=SQLITE_OK) ){
+ goto commit_abort;
+ }
+ }
+ rc = pager_unwritelock(pPager);
+ pPager->dbSize = -1;
+ return rc;
+
+ /* Jump here if anything goes wrong during the commit process.
+ */
+commit_abort:
+ rc = sqlitepager_rollback(pPager);
+ if( rc==SQLITE_OK ){
+ rc = SQLITE_FULL;
+ }
+ return rc;
+}
+
+/*
+** Rollback all changes. The database falls back to read-only mode.
+** All in-memory cache pages revert to their original data contents.
+** The journal is deleted.
+**
+** This routine cannot fail unless some other process is not following
+** the correct locking protocol (SQLITE_PROTOCOL) or unless some other
+** process is writing trash into the journal file (SQLITE_CORRUPT) or
+** unless a prior malloc() failed (SQLITE_NOMEM). Appropriate error
+** codes are returned for all these occasions. Otherwise,
+** SQLITE_OK is returned.
+*/
+int sqlitepager_rollback(Pager *pPager){
+ int rc;
+ TRACE1("ROLLBACK\n");
+ if( !pPager->dirtyFile || !pPager->journalOpen ){
+ rc = pager_unwritelock(pPager);
+ pPager->dbSize = -1;
+ return rc;
+ }
+
+ if( pPager->errMask!=0 && pPager->errMask!=PAGER_ERR_FULL ){
+ if( pPager->state>=SQLITE_WRITELOCK ){
+ pager_playback(pPager, 1);
+ }
+ return pager_errcode(pPager);
+ }
+ if( pPager->state!=SQLITE_WRITELOCK ){
+ return SQLITE_OK;
+ }
+ rc = pager_playback(pPager, 1);
+ if( rc!=SQLITE_OK ){
+ rc = SQLITE_CORRUPT;
+ pPager->errMask |= PAGER_ERR_CORRUPT;
+ }
+ pPager->dbSize = -1;
+ return rc;
+}
+
+/*
+** Return TRUE if the database file is opened read-only. Return FALSE
+** if the database is (in theory) writable.
+*/
+int sqlitepager_isreadonly(Pager *pPager){
+ return pPager->readOnly;
+}
+
+/*
+** This routine is used for testing and analysis only.
+*/
+int *sqlitepager_stats(Pager *pPager){
+ static int a[9];
+ a[0] = pPager->nRef;
+ a[1] = pPager->nPage;
+ a[2] = pPager->mxPage;
+ a[3] = pPager->dbSize;
+ a[4] = pPager->state;
+ a[5] = pPager->errMask;
+ a[6] = pPager->nHit;
+ a[7] = pPager->nMiss;
+ a[8] = pPager->nOvfl;
+ return a;
+}
+
+/*
+** Set the checkpoint.
+**
+** This routine should be called with the transaction journal already
+** open. A new checkpoint journal is created that can be used to rollback
+** changes of a single SQL command within a larger transaction.
+*/
+int sqlitepager_ckpt_begin(Pager *pPager){
+ int rc;
+ char zTemp[SQLITE_TEMPNAME_SIZE];
+ if( !pPager->journalOpen ){
+ pPager->ckptAutoopen = 1;
+ return SQLITE_OK;
+ }
+ assert( pPager->journalOpen );
+ assert( !pPager->ckptInUse );
+ pPager->aInCkpt = sqliteMalloc( pPager->dbSize/8 + 1 );
+ if( pPager->aInCkpt==0 ){
+ sqliteOsReadLock(&pPager->fd);
+ return SQLITE_NOMEM;
+ }
+#ifndef NDEBUG
+ rc = sqliteOsFileSize(&pPager->jfd, &pPager->ckptJSize);
+ if( rc ) goto ckpt_begin_failed;
+ assert( pPager->ckptJSize ==
+ pPager->nRec*JOURNAL_PG_SZ(journal_format)+JOURNAL_HDR_SZ(journal_format) );
+#endif
+ pPager->ckptJSize = pPager->nRec*JOURNAL_PG_SZ(journal_format)
+ + JOURNAL_HDR_SZ(journal_format);
+ pPager->ckptSize = pPager->dbSize;
+ if( !pPager->ckptOpen ){
+ rc = sqlitepager_opentemp(zTemp, &pPager->cpfd);
+ if( rc ) goto ckpt_begin_failed;
+ pPager->ckptOpen = 1;
+ pPager->ckptNRec = 0;
+ }
+ pPager->ckptInUse = 1;
+ return SQLITE_OK;
+
+ckpt_begin_failed:
+ if( pPager->aInCkpt ){
+ sqliteFree(pPager->aInCkpt);
+ pPager->aInCkpt = 0;
+ }
+ return rc;
+}
+
+/*
+** Commit a checkpoint.
+*/
+int sqlitepager_ckpt_commit(Pager *pPager){
+ if( pPager->ckptInUse ){
+ PgHdr *pPg, *pNext;
+ sqliteOsSeek(&pPager->cpfd, 0);
+ /* sqliteOsTruncate(&pPager->cpfd, 0); */
+ pPager->ckptNRec = 0;
+ pPager->ckptInUse = 0;
+ sqliteFree( pPager->aInCkpt );
+ pPager->aInCkpt = 0;
+ for(pPg=pPager->pCkpt; pPg; pPg=pNext){
+ pNext = pPg->pNextCkpt;
+ assert( pPg->inCkpt );
+ pPg->inCkpt = 0;
+ pPg->pPrevCkpt = pPg->pNextCkpt = 0;
+ }
+ pPager->pCkpt = 0;
+ }
+ pPager->ckptAutoopen = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Rollback a checkpoint.
+*/
+int sqlitepager_ckpt_rollback(Pager *pPager){
+ int rc;
+ if( pPager->ckptInUse ){
+ rc = pager_ckpt_playback(pPager);
+ sqlitepager_ckpt_commit(pPager);
+ }else{
+ rc = SQLITE_OK;
+ }
+ pPager->ckptAutoopen = 0;
+ return rc;
+}
+
+/*
+** Return the full pathname of the database file.
+*/
+const char *sqlitepager_filename(Pager *pPager){
+ return pPager->zFilename;
+}
+
+/*
+** Set the codec for this pager
+*/
+void sqlitepager_set_codec(
+ Pager *pPager,
+ void (*xCodec)(void*,void*,Pgno,int),
+ void *pCodecArg
+){
+ pPager->xCodec = xCodec;
+ pPager->pCodecArg = pCodecArg;
+}
+
+#ifdef SQLITE_TEST
+/*
+** Print a listing of all referenced pages and their ref count.
+*/
+void sqlitepager_refdump(Pager *pPager){
+ PgHdr *pPg;
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ if( pPg->nRef<=0 ) continue;
+ printf("PAGE %3d addr=0x%08x nRef=%d\n",
+ pPg->pgno, (int)PGHDR_TO_DATA(pPg), pPg->nRef);
+ }
+}
+#endif
diff --git a/src/libs/sqlite2/pager.h b/src/libs/sqlite2/pager.h
new file mode 100644
index 00000000..96f9d406
--- /dev/null
+++ b/src/libs/sqlite2/pager.h
@@ -0,0 +1,107 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the sqlite page cache
+** subsystem. The page cache subsystem reads and writes a file a page
+** at a time and provides a journal for rollback.
+**
+** @(#) $Id: pager.h 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+
+/*
+** The size of one page
+**
+** You can change this value to another (reasonable) value you want.
+** It need not be a power of two, though the interface to the disk
+** will likely be faster if it is.
+**
+** Experiments show that a page size of 1024 gives the best speed
+** for common usages. The speed differences for different sizes
+** such as 512, 2048, 4096, an so forth, is minimal. Note, however,
+** that changing the page size results in a completely imcompatible
+** file format.
+*/
+#ifndef SQLITE_PAGE_SIZE
+#define SQLITE_PAGE_SIZE 1024
+#endif
+
+/*
+** Number of extra bytes of data allocated at the end of each page and
+** stored on disk but not used by the higher level btree layer. Changing
+** this value results in a completely incompatible file format.
+*/
+#ifndef SQLITE_PAGE_RESERVE
+#define SQLITE_PAGE_RESERVE 0
+#endif
+
+/*
+** The total number of usable bytes stored on disk for each page.
+** The usable bytes come at the beginning of the page and the reserve
+** bytes come at the end.
+*/
+#define SQLITE_USABLE_SIZE (SQLITE_PAGE_SIZE-SQLITE_PAGE_RESERVE)
+
+/*
+** Maximum number of pages in one database. (This is a limitation of
+** imposed by 4GB files size limits.)
+*/
+#define SQLITE_MAX_PAGE 1073741823
+
+/*
+** The type used to represent a page number. The first page in a file
+** is called page 1. 0 is used to represent "not a page".
+*/
+typedef unsigned int Pgno;
+
+/*
+** Each open file is managed by a separate instance of the "Pager" structure.
+*/
+typedef struct Pager Pager;
+
+/*
+** See source code comments for a detailed description of the following
+** routines:
+*/
+int sqlitepager_open(Pager **ppPager, const char *zFilename,
+ int nPage, int nExtra, int useJournal);
+void sqlitepager_set_destructor(Pager*, void(*)(void*));
+void sqlitepager_set_cachesize(Pager*, int);
+int sqlitepager_close(Pager *pPager);
+int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage);
+void *sqlitepager_lookup(Pager *pPager, Pgno pgno);
+int sqlitepager_ref(void*);
+int sqlitepager_unref(void*);
+Pgno sqlitepager_pagenumber(void*);
+int sqlitepager_write(void*);
+int sqlitepager_iswriteable(void*);
+int sqlitepager_overwrite(Pager *pPager, Pgno pgno, void*);
+int sqlitepager_pagecount(Pager*);
+int sqlitepager_truncate(Pager*,Pgno);
+int sqlitepager_begin(void*);
+int sqlitepager_commit(Pager*);
+int sqlitepager_rollback(Pager*);
+int sqlitepager_isreadonly(Pager*);
+int sqlitepager_ckpt_begin(Pager*);
+int sqlitepager_ckpt_commit(Pager*);
+int sqlitepager_ckpt_rollback(Pager*);
+void sqlitepager_dont_rollback(void*);
+void sqlitepager_dont_write(Pager*, Pgno);
+int *sqlitepager_stats(Pager*);
+void sqlitepager_set_safety_level(Pager*,int);
+const char *sqlitepager_filename(Pager*);
+int sqlitepager_rename(Pager*, const char *zNewName);
+void sqlitepager_set_codec(Pager*,void(*)(void*,void*,Pgno,int),void*);
+
+#ifdef SQLITE_TEST
+void sqlitepager_refdump(Pager*);
+int pager_refinfo_enable;
+int journal_format;
+#endif
diff --git a/src/libs/sqlite2/parse.c b/src/libs/sqlite2/parse.c
new file mode 100644
index 00000000..46353691
--- /dev/null
+++ b/src/libs/sqlite2/parse.c
@@ -0,0 +1,4035 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is include which follows the "include" declaration
+** in the input file. */
+#include <stdio.h>
+#line 33 "parse.y"
+
+#include "sqliteInt.h"
+#include "parse.h"
+
+/*
+** An instance of this structure holds information about the
+** LIMIT clause of a SELECT statement.
+*/
+struct LimitVal {
+ int limit; /* The LIMIT value. -1 if there is no limit */
+ int offset; /* The OFFSET. 0 if there is none */
+};
+
+/*
+** An instance of the following structure describes the event of a
+** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT,
+** TK_DELETE, or TK_INSTEAD. If the event is of the form
+**
+** UPDATE ON (a,b,c)
+**
+** Then the "b" IdList records the list "a,b,c".
+*/
+struct TrigEvent { int a; IdList * b; };
+
+
+#line 34 "parse.c"
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/*
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands.
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+** YYCODETYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 terminals
+** and nonterminals. "int" is used otherwise.
+** YYNOCODE is a number of type YYCODETYPE which corresponds
+** to no legal terminal or nonterminal number. This
+** number is used to fill in empty slots of the hash
+** table.
+** YYFALLBACK If defined, this indicates that one or more tokens
+** have fall-back values which should be used if the
+** original value of the token will not parse.
+** YYACTIONTYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 rules and
+** states combined. "int" is used otherwise.
+** sqliteParserTOKENTYPE is the data type used for minor tokens given
+** directly to the parser from the tokenizer.
+** YYMINORTYPE is the data type used for all minor tokens.
+** This is typically a union of many types, one of
+** which is sqliteParserTOKENTYPE. The entry in the union
+** for base tokens is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack.
+** sqliteParserARG_SDECL A static variable declaration for the %extra_argument
+** sqliteParserARG_PDECL A parameter declaration for the %extra_argument
+** sqliteParserARG_STORE Code to store %extra_argument into yypParser
+** sqliteParserARG_FETCH Code to extract %extra_argument from yypParser
+** YYNSTATE the combined number of states.
+** YYNRULE the number of rules in the grammar
+** YYERRORSYMBOL is the code number of the error symbol. If not
+** defined, then do no error processing.
+*/
+/*  */
+#define YYCODETYPE unsigned char
+#define YYNOCODE 221
+#define YYACTIONTYPE unsigned short int
+#define sqliteParserTOKENTYPE Token
+typedef union {
+ sqliteParserTOKENTYPE yy0;
+ TriggerStep * yy19;
+ struct LimitVal yy124;
+ Select* yy179;
+ Expr * yy182;
+ Expr* yy242;
+ struct TrigEvent yy290;
+ Token yy298;
+ SrcList* yy307;
+ IdList* yy320;
+ ExprList* yy322;
+ int yy372;
+ struct {int value; int mask;} yy407;
+ int yy441;
+} YYMINORTYPE;
+#define YYSTACKDEPTH 100
+#define sqliteParserARG_SDECL Parse *pParse;
+#define sqliteParserARG_PDECL ,Parse *pParse
+#define sqliteParserARG_FETCH Parse *pParse = yypParser->pParse
+#define sqliteParserARG_STORE yypParser->pParse = pParse
+#define YYNSTATE 563
+#define YYNRULE 293
+#define YYERRORSYMBOL 131
+#define YYERRSYMDT yy441
+#define YYFALLBACK 1
+#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
+
+/* Next are that tables used to determine what action to take based on the
+** current state and lookahead token. These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.
+**
+** Suppose the action integer is N. Then the action is determined as
+** follows
+**
+** 0 <= N < YYNSTATE Shift N. That is, push the lookahead
+** token onto the stack and goto state N.
+**
+** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE.
+**
+** N == YYNSTATE+YYNRULE A syntax error has occurred.
+**
+** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
+**
+** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
+** slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+** yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol. If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+** yy_action[] A single table containing all actions.
+** yy_lookahead[] A table containing the lookahead for each entry in
+** yy_action. Used to detect hash collisions.
+** yy_shift_ofst[] For each state, the offset into yy_action for
+** shifting terminals.
+** yy_reduce_ofst[] For each state, the offset into yy_action for
+** shifting non-terminals after a reduce.
+** yy_default[] Default action for each state.
+*/
+static YYACTIONTYPE yy_action[] = {
+ /* 0 */ 264, 5, 262, 119, 123, 117, 121, 129, 131, 133,
+ /* 10 */ 135, 144, 146, 148, 150, 152, 154, 568, 106, 106,
+ /* 20 */ 143, 857, 1, 562, 3, 142, 129, 131, 133, 135,
+ /* 30 */ 144, 146, 148, 150, 152, 154, 174, 103, 8, 115,
+ /* 40 */ 104, 139, 127, 125, 156, 161, 157, 162, 166, 119,
+ /* 50 */ 123, 117, 121, 129, 131, 133, 135, 144, 146, 148,
+ /* 60 */ 150, 152, 154, 31, 361, 392, 263, 143, 363, 369,
+ /* 70 */ 374, 97, 142, 148, 150, 152, 154, 68, 75, 377,
+ /* 80 */ 167, 64, 218, 46, 20, 289, 115, 104, 139, 127,
+ /* 90 */ 125, 156, 161, 157, 162, 166, 119, 123, 117, 121,
+ /* 100 */ 129, 131, 133, 135, 144, 146, 148, 150, 152, 154,
+ /* 110 */ 193, 41, 336, 563, 44, 54, 60, 62, 308, 331,
+ /* 120 */ 175, 20, 560, 561, 572, 333, 640, 18, 359, 144,
+ /* 130 */ 146, 148, 150, 152, 154, 143, 181, 179, 303, 18,
+ /* 140 */ 142, 84, 86, 20, 177, 66, 67, 111, 21, 22,
+ /* 150 */ 112, 105, 83, 792, 115, 104, 139, 127, 125, 156,
+ /* 160 */ 161, 157, 162, 166, 119, 123, 117, 121, 129, 131,
+ /* 170 */ 133, 135, 144, 146, 148, 150, 152, 154, 790, 560,
+ /* 180 */ 561, 46, 13, 113, 183, 21, 22, 534, 361, 2,
+ /* 190 */ 3, 14, 363, 369, 374, 338, 361, 690, 544, 542,
+ /* 200 */ 363, 369, 374, 377, 836, 143, 15, 21, 22, 16,
+ /* 210 */ 142, 377, 44, 54, 60, 62, 308, 331, 396, 535,
+ /* 220 */ 17, 9, 191, 333, 115, 104, 139, 127, 125, 156,
+ /* 230 */ 161, 157, 162, 166, 119, 123, 117, 121, 129, 131,
+ /* 240 */ 133, 135, 144, 146, 148, 150, 152, 154, 571, 230,
+ /* 250 */ 340, 343, 143, 20, 536, 537, 538, 142, 402, 337,
+ /* 260 */ 398, 339, 357, 68, 346, 347, 32, 64, 266, 391,
+ /* 270 */ 37, 115, 104, 139, 127, 125, 156, 161, 157, 162,
+ /* 280 */ 166, 119, 123, 117, 121, 129, 131, 133, 135, 144,
+ /* 290 */ 146, 148, 150, 152, 154, 839, 193, 651, 291, 298,
+ /* 300 */ 300, 221, 357, 43, 173, 689, 175, 251, 330, 36,
+ /* 310 */ 37, 106, 232, 40, 335, 58, 137, 21, 22, 330,
+ /* 320 */ 411, 143, 181, 179, 47, 59, 142, 358, 390, 174,
+ /* 330 */ 177, 66, 67, 111, 448, 49, 112, 105, 583, 213,
+ /* 340 */ 115, 104, 139, 127, 125, 156, 161, 157, 162, 166,
+ /* 350 */ 119, 123, 117, 121, 129, 131, 133, 135, 144, 146,
+ /* 360 */ 148, 150, 152, 154, 306, 301, 106, 249, 259, 113,
+ /* 370 */ 183, 793, 70, 253, 281, 219, 20, 106, 20, 11,
+ /* 380 */ 106, 482, 454, 444, 299, 143, 169, 10, 171, 172,
+ /* 390 */ 142, 169, 73, 171, 172, 103, 688, 69, 174, 169,
+ /* 400 */ 252, 171, 172, 12, 115, 104, 139, 127, 125, 156,
+ /* 410 */ 161, 157, 162, 166, 119, 123, 117, 121, 129, 131,
+ /* 420 */ 133, 135, 144, 146, 148, 150, 152, 154, 95, 237,
+ /* 430 */ 313, 20, 143, 295, 244, 424, 169, 142, 171, 172,
+ /* 440 */ 21, 22, 21, 22, 219, 386, 316, 323, 325, 837,
+ /* 450 */ 19, 115, 104, 139, 127, 125, 156, 161, 157, 162,
+ /* 460 */ 166, 119, 123, 117, 121, 129, 131, 133, 135, 144,
+ /* 470 */ 146, 148, 150, 152, 154, 106, 661, 20, 264, 143,
+ /* 480 */ 262, 844, 315, 169, 142, 171, 172, 333, 38, 842,
+ /* 490 */ 10, 356, 348, 184, 421, 21, 22, 282, 115, 104,
+ /* 500 */ 139, 127, 125, 156, 161, 157, 162, 166, 119, 123,
+ /* 510 */ 117, 121, 129, 131, 133, 135, 144, 146, 148, 150,
+ /* 520 */ 152, 154, 69, 254, 262, 251, 143, 639, 663, 35,
+ /* 530 */ 65, 142, 726, 313, 283, 259, 185, 417, 419, 418,
+ /* 540 */ 284, 21, 22, 690, 263, 115, 104, 139, 127, 125,
+ /* 550 */ 156, 161, 157, 162, 166, 119, 123, 117, 121, 129,
+ /* 560 */ 131, 133, 135, 144, 146, 148, 150, 152, 154, 256,
+ /* 570 */ 20, 791, 424, 143, 169, 52, 171, 172, 142, 169,
+ /* 580 */ 24, 171, 172, 247, 53, 315, 26, 169, 263, 171,
+ /* 590 */ 172, 253, 115, 164, 139, 127, 125, 156, 161, 157,
+ /* 600 */ 162, 166, 119, 123, 117, 121, 129, 131, 133, 135,
+ /* 610 */ 144, 146, 148, 150, 152, 154, 426, 349, 252, 425,
+ /* 620 */ 143, 262, 575, 297, 591, 142, 169, 296, 171, 172,
+ /* 630 */ 169, 471, 171, 172, 21, 22, 427, 221, 91, 115,
+ /* 640 */ 227, 139, 127, 125, 156, 161, 157, 162, 166, 119,
+ /* 650 */ 123, 117, 121, 129, 131, 133, 135, 144, 146, 148,
+ /* 660 */ 150, 152, 154, 388, 312, 106, 89, 143, 720, 376,
+ /* 670 */ 387, 170, 142, 487, 666, 248, 320, 216, 319, 217,
+ /* 680 */ 28, 459, 30, 305, 189, 263, 209, 104, 139, 127,
+ /* 690 */ 125, 156, 161, 157, 162, 166, 119, 123, 117, 121,
+ /* 700 */ 129, 131, 133, 135, 144, 146, 148, 150, 152, 154,
+ /* 710 */ 106, 106, 809, 494, 143, 489, 106, 816, 33, 142,
+ /* 720 */ 395, 234, 273, 217, 274, 420, 20, 545, 114, 481,
+ /* 730 */ 137, 429, 576, 321, 116, 139, 127, 125, 156, 161,
+ /* 740 */ 157, 162, 166, 119, 123, 117, 121, 129, 131, 133,
+ /* 750 */ 135, 144, 146, 148, 150, 152, 154, 7, 322, 23,
+ /* 760 */ 25, 27, 394, 68, 415, 416, 10, 64, 197, 477,
+ /* 770 */ 577, 533, 266, 548, 578, 831, 276, 201, 520, 4,
+ /* 780 */ 6, 245, 430, 557, 29, 266, 491, 106, 441, 497,
+ /* 790 */ 21, 22, 205, 168, 443, 195, 193, 531, 276, 448,
+ /* 800 */ 276, 808, 267, 272, 529, 174, 175, 318, 440, 341,
+ /* 810 */ 344, 106, 342, 345, 69, 286, 68, 582, 69, 69,
+ /* 820 */ 64, 540, 181, 179, 541, 328, 302, 366, 217, 118,
+ /* 830 */ 177, 66, 67, 111, 34, 143, 112, 105, 445, 510,
+ /* 840 */ 142, 215, 278, 800, 467, 276, 498, 503, 444, 193,
+ /* 850 */ 106, 219, 486, 443, 42, 73, 231, 73, 45, 175,
+ /* 860 */ 449, 39, 225, 229, 278, 451, 278, 68, 174, 113,
+ /* 870 */ 183, 64, 371, 55, 106, 181, 179, 292, 69, 276,
+ /* 880 */ 276, 69, 48, 177, 66, 67, 111, 224, 276, 112,
+ /* 890 */ 105, 106, 481, 393, 106, 106, 63, 106, 106, 106,
+ /* 900 */ 193, 653, 106, 467, 233, 51, 380, 437, 526, 120,
+ /* 910 */ 175, 278, 122, 124, 219, 126, 128, 130, 69, 453,
+ /* 920 */ 132, 106, 113, 183, 451, 106, 181, 179, 159, 106,
+ /* 930 */ 106, 106, 518, 106, 177, 66, 67, 111, 106, 134,
+ /* 940 */ 112, 105, 422, 136, 106, 278, 278, 138, 141, 145,
+ /* 950 */ 720, 147, 106, 329, 275, 274, 149, 106, 852, 158,
+ /* 960 */ 106, 106, 151, 106, 106, 351, 106, 352, 106, 464,
+ /* 970 */ 153, 106, 106, 113, 183, 155, 106, 106, 163, 165,
+ /* 980 */ 106, 176, 178, 106, 180, 106, 182, 106, 401, 190,
+ /* 990 */ 192, 106, 106, 293, 210, 212, 106, 367, 214, 274,
+ /* 1000 */ 372, 226, 274, 228, 381, 241, 274, 106, 106, 246,
+ /* 1010 */ 280, 290, 106, 69, 375, 438, 472, 274, 422, 832,
+ /* 1020 */ 106, 73, 474, 73, 458, 412, 462, 480, 464, 478,
+ /* 1030 */ 466, 690, 515, 519, 475, 478, 516, 50, 479, 221,
+ /* 1040 */ 690, 221, 56, 57, 61, 592, 71, 69, 593, 73,
+ /* 1050 */ 72, 74, 245, 242, 93, 81, 76, 69, 77, 240,
+ /* 1060 */ 78, 82, 79, 245, 85, 554, 80, 88, 87, 90,
+ /* 1070 */ 92, 94, 96, 102, 100, 99, 101, 107, 109, 160,
+ /* 1080 */ 154, 667, 98, 508, 108, 668, 110, 220, 211, 669,
+ /* 1090 */ 137, 140, 188, 194, 186, 196, 187, 199, 198, 200,
+ /* 1100 */ 203, 204, 202, 207, 206, 208, 221, 223, 222, 235,
+ /* 1110 */ 236, 239, 238, 217, 250, 258, 243, 261, 279, 270,
+ /* 1120 */ 271, 255, 257, 260, 269, 265, 285, 294, 277, 268,
+ /* 1130 */ 287, 304, 309, 307, 327, 312, 288, 354, 389, 314,
+ /* 1140 */ 364, 365, 370, 378, 379, 382, 310, 49, 311, 362,
+ /* 1150 */ 368, 373, 317, 324, 326, 332, 350, 355, 383, 400,
+ /* 1160 */ 353, 397, 399, 403, 404, 334, 405, 406, 407, 384,
+ /* 1170 */ 413, 409, 824, 414, 360, 385, 829, 423, 410, 431,
+ /* 1180 */ 428, 432, 830, 433, 434, 436, 439, 798, 799, 447,
+ /* 1190 */ 442, 450, 727, 728, 446, 823, 452, 838, 455, 445,
+ /* 1200 */ 456, 457, 408, 435, 460, 461, 463, 840, 465, 468,
+ /* 1210 */ 470, 469, 476, 841, 483, 485, 843, 660, 662, 493,
+ /* 1220 */ 806, 496, 473, 849, 499, 719, 501, 484, 488, 490,
+ /* 1230 */ 492, 502, 504, 495, 500, 507, 505, 506, 509, 722,
+ /* 1240 */ 513, 511, 512, 514, 517, 725, 528, 522, 524, 525,
+ /* 1250 */ 527, 523, 807, 530, 810, 532, 811, 812, 813, 814,
+ /* 1260 */ 817, 819, 539, 820, 818, 815, 521, 543, 546, 552,
+ /* 1270 */ 556, 550, 850, 547, 549, 851, 555, 558, 551, 855,
+ /* 1280 */ 553, 559,
+};
+static YYCODETYPE yy_lookahead[] = {
+ /* 0 */ 21, 9, 23, 70, 71, 72, 73, 74, 75, 76,
+ /* 10 */ 77, 78, 79, 80, 81, 82, 83, 9, 140, 140,
+ /* 20 */ 41, 132, 133, 134, 135, 46, 74, 75, 76, 77,
+ /* 30 */ 78, 79, 80, 81, 82, 83, 158, 158, 138, 60,
+ /* 40 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
+ /* 50 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ /* 60 */ 81, 82, 83, 19, 90, 21, 87, 41, 94, 95,
+ /* 70 */ 96, 192, 46, 80, 81, 82, 83, 19, 174, 105,
+ /* 80 */ 19, 23, 204, 62, 23, 181, 60, 61, 62, 63,
+ /* 90 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
+ /* 100 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ /* 110 */ 52, 90, 91, 0, 93, 94, 95, 96, 97, 98,
+ /* 120 */ 62, 23, 9, 10, 9, 104, 20, 12, 22, 78,
+ /* 130 */ 79, 80, 81, 82, 83, 41, 78, 79, 80, 12,
+ /* 140 */ 46, 78, 79, 23, 86, 87, 88, 89, 87, 88,
+ /* 150 */ 92, 93, 89, 127, 60, 61, 62, 63, 64, 65,
+ /* 160 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 170 */ 76, 77, 78, 79, 80, 81, 82, 83, 14, 9,
+ /* 180 */ 10, 62, 15, 125, 126, 87, 88, 140, 90, 134,
+ /* 190 */ 135, 24, 94, 95, 96, 23, 90, 9, 78, 79,
+ /* 200 */ 94, 95, 96, 105, 11, 41, 39, 87, 88, 42,
+ /* 210 */ 46, 105, 93, 94, 95, 96, 97, 98, 17, 99,
+ /* 220 */ 53, 139, 128, 104, 60, 61, 62, 63, 64, 65,
+ /* 230 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 240 */ 76, 77, 78, 79, 80, 81, 82, 83, 9, 19,
+ /* 250 */ 78, 79, 41, 23, 207, 208, 209, 46, 57, 87,
+ /* 260 */ 59, 89, 140, 19, 92, 93, 144, 23, 152, 147,
+ /* 270 */ 148, 60, 61, 62, 63, 64, 65, 66, 67, 68,
+ /* 280 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
+ /* 290 */ 79, 80, 81, 82, 83, 14, 52, 9, 182, 20,
+ /* 300 */ 20, 113, 140, 156, 20, 20, 62, 22, 161, 147,
+ /* 310 */ 148, 140, 20, 155, 156, 26, 200, 87, 88, 161,
+ /* 320 */ 127, 41, 78, 79, 93, 36, 46, 165, 166, 158,
+ /* 330 */ 86, 87, 88, 89, 53, 104, 92, 93, 9, 128,
+ /* 340 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+ /* 350 */ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ /* 360 */ 80, 81, 82, 83, 20, 194, 140, 183, 184, 125,
+ /* 370 */ 126, 127, 146, 88, 19, 204, 23, 140, 23, 31,
+ /* 380 */ 140, 100, 101, 102, 158, 41, 107, 99, 109, 110,
+ /* 390 */ 46, 107, 111, 109, 110, 158, 20, 171, 158, 107,
+ /* 400 */ 115, 109, 110, 170, 60, 61, 62, 63, 64, 65,
+ /* 410 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 420 */ 76, 77, 78, 79, 80, 81, 82, 83, 191, 192,
+ /* 430 */ 47, 23, 41, 80, 194, 140, 107, 46, 109, 110,
+ /* 440 */ 87, 88, 87, 88, 204, 62, 100, 101, 102, 11,
+ /* 450 */ 140, 60, 61, 62, 63, 64, 65, 66, 67, 68,
+ /* 460 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
+ /* 470 */ 79, 80, 81, 82, 83, 140, 9, 23, 21, 41,
+ /* 480 */ 23, 9, 99, 107, 46, 109, 110, 104, 149, 9,
+ /* 490 */ 99, 152, 153, 158, 199, 87, 88, 146, 60, 61,
+ /* 500 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
+ /* 510 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
+ /* 520 */ 82, 83, 171, 115, 23, 22, 41, 20, 9, 22,
+ /* 530 */ 19, 46, 9, 47, 183, 184, 201, 100, 101, 102,
+ /* 540 */ 189, 87, 88, 19, 87, 60, 61, 62, 63, 64,
+ /* 550 */ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
+ /* 560 */ 75, 76, 77, 78, 79, 80, 81, 82, 83, 115,
+ /* 570 */ 23, 14, 140, 41, 107, 34, 109, 110, 46, 107,
+ /* 580 */ 138, 109, 110, 22, 43, 99, 138, 107, 87, 109,
+ /* 590 */ 110, 88, 60, 61, 62, 63, 64, 65, 66, 67,
+ /* 600 */ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
+ /* 610 */ 78, 79, 80, 81, 82, 83, 25, 19, 115, 28,
+ /* 620 */ 41, 23, 9, 108, 113, 46, 107, 112, 109, 110,
+ /* 630 */ 107, 199, 109, 110, 87, 88, 45, 113, 22, 60,
+ /* 640 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
+ /* 650 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ /* 660 */ 81, 82, 83, 161, 162, 140, 50, 41, 9, 139,
+ /* 670 */ 168, 108, 46, 17, 111, 114, 91, 20, 93, 22,
+ /* 680 */ 138, 22, 142, 158, 127, 87, 129, 61, 62, 63,
+ /* 690 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
+ /* 700 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ /* 710 */ 140, 140, 9, 57, 41, 59, 140, 9, 145, 46,
+ /* 720 */ 143, 20, 20, 22, 22, 49, 23, 19, 158, 158,
+ /* 730 */ 200, 18, 9, 29, 158, 62, 63, 64, 65, 66,
+ /* 740 */ 67, 68, 69, 70, 71, 72, 73, 74, 75, 76,
+ /* 750 */ 77, 78, 79, 80, 81, 82, 83, 11, 54, 13,
+ /* 760 */ 14, 15, 16, 19, 55, 56, 99, 23, 15, 198,
+ /* 770 */ 9, 63, 152, 27, 9, 99, 140, 24, 32, 136,
+ /* 780 */ 137, 122, 205, 37, 141, 152, 130, 140, 211, 146,
+ /* 790 */ 87, 88, 39, 146, 146, 42, 52, 51, 140, 53,
+ /* 800 */ 140, 9, 182, 167, 58, 158, 62, 103, 95, 89,
+ /* 810 */ 89, 140, 92, 92, 171, 182, 19, 9, 171, 171,
+ /* 820 */ 23, 89, 78, 79, 92, 167, 20, 167, 22, 158,
+ /* 830 */ 86, 87, 88, 89, 20, 41, 92, 93, 60, 196,
+ /* 840 */ 46, 194, 206, 130, 196, 140, 100, 101, 102, 52,
+ /* 850 */ 140, 204, 106, 146, 140, 111, 146, 111, 139, 62,
+ /* 860 */ 212, 150, 68, 69, 206, 217, 206, 19, 158, 125,
+ /* 870 */ 126, 23, 167, 48, 140, 78, 79, 80, 171, 140,
+ /* 880 */ 140, 171, 139, 86, 87, 88, 89, 93, 140, 92,
+ /* 890 */ 93, 140, 158, 146, 140, 140, 19, 140, 140, 140,
+ /* 900 */ 52, 123, 140, 196, 194, 44, 167, 167, 116, 158,
+ /* 910 */ 62, 206, 158, 158, 204, 158, 158, 158, 171, 212,
+ /* 920 */ 158, 140, 125, 126, 217, 140, 78, 79, 62, 140,
+ /* 930 */ 140, 140, 198, 140, 86, 87, 88, 89, 140, 158,
+ /* 940 */ 92, 93, 22, 158, 140, 206, 206, 158, 158, 158,
+ /* 950 */ 9, 158, 140, 20, 206, 22, 158, 140, 9, 93,
+ /* 960 */ 140, 140, 158, 140, 140, 20, 140, 22, 140, 140,
+ /* 970 */ 158, 140, 140, 125, 126, 158, 140, 140, 158, 158,
+ /* 980 */ 140, 158, 158, 140, 158, 140, 158, 140, 146, 158,
+ /* 990 */ 158, 140, 140, 140, 158, 158, 140, 20, 158, 22,
+ /* 1000 */ 20, 158, 22, 158, 20, 158, 22, 140, 140, 158,
+ /* 1010 */ 158, 158, 140, 171, 158, 20, 20, 22, 22, 99,
+ /* 1020 */ 140, 111, 146, 111, 195, 158, 158, 20, 140, 22,
+ /* 1030 */ 158, 103, 146, 20, 124, 22, 124, 164, 158, 113,
+ /* 1040 */ 114, 113, 157, 139, 139, 113, 172, 171, 113, 111,
+ /* 1050 */ 171, 173, 122, 119, 117, 180, 175, 171, 176, 120,
+ /* 1060 */ 177, 121, 178, 122, 89, 116, 179, 154, 89, 154,
+ /* 1070 */ 154, 118, 22, 151, 98, 157, 23, 113, 113, 93,
+ /* 1080 */ 83, 111, 193, 195, 140, 111, 140, 140, 127, 111,
+ /* 1090 */ 200, 200, 14, 19, 202, 20, 203, 140, 22, 20,
+ /* 1100 */ 140, 20, 22, 140, 22, 20, 113, 186, 140, 140,
+ /* 1110 */ 186, 157, 193, 22, 185, 115, 118, 186, 99, 116,
+ /* 1120 */ 19, 140, 140, 140, 188, 140, 20, 113, 157, 187,
+ /* 1130 */ 187, 20, 140, 139, 19, 162, 188, 20, 166, 140,
+ /* 1140 */ 48, 19, 19, 48, 19, 97, 159, 104, 160, 140,
+ /* 1150 */ 139, 139, 163, 163, 163, 151, 154, 152, 140, 21,
+ /* 1160 */ 154, 140, 140, 140, 213, 164, 214, 99, 140, 159,
+ /* 1170 */ 40, 215, 11, 38, 166, 160, 99, 140, 216, 130,
+ /* 1180 */ 49, 140, 99, 99, 140, 19, 139, 9, 130, 169,
+ /* 1190 */ 11, 14, 123, 123, 170, 9, 9, 14, 169, 60,
+ /* 1200 */ 140, 103, 186, 186, 140, 63, 176, 9, 63, 123,
+ /* 1210 */ 19, 140, 19, 9, 114, 176, 9, 9, 9, 186,
+ /* 1220 */ 9, 186, 197, 9, 114, 9, 186, 140, 140, 140,
+ /* 1230 */ 140, 176, 169, 140, 140, 103, 140, 186, 176, 9,
+ /* 1240 */ 186, 123, 140, 197, 19, 9, 87, 140, 114, 140,
+ /* 1250 */ 35, 186, 9, 140, 9, 152, 9, 9, 9, 9,
+ /* 1260 */ 9, 9, 210, 9, 9, 9, 169, 210, 140, 140,
+ /* 1270 */ 33, 152, 9, 20, 218, 9, 152, 218, 21, 9,
+ /* 1280 */ 219, 140,
+};
+#define YY_SHIFT_USE_DFLT (-68)
+static short yy_shift_ofst[] = {
+ /* 0 */ 170, 113, -68, 746, -8, -68, 8, 127, 288, 239,
+ /* 10 */ 348, 167, -68, -68, -68, -68, -68, -68, 547, -68,
+ /* 20 */ -68, -68, -68, 115, 613, 115, 723, 115, 761, 44,
+ /* 30 */ 765, 547, 507, 814, 808, 98, -68, 501, -68, 21,
+ /* 40 */ -68, 547, 119, -68, 667, -68, 231, 667, -68, 861,
+ /* 50 */ -68, 541, -68, -68, 825, 289, 667, -68, -68, -68,
+ /* 60 */ 667, -68, 877, 848, 511, 58, 932, 935, 744, -68,
+ /* 70 */ 279, 938, -68, 515, -68, 561, 930, 934, 939, 937,
+ /* 80 */ 940, -68, 63, -68, 975, -68, 979, -68, 616, 63,
+ /* 90 */ -68, 63, -68, 953, 848, 1050, 848, 976, 289, -68,
+ /* 100 */ 1053, -68, -68, 485, 848, -68, 964, 547, 965, 547,
+ /* 110 */ -68, -68, -68, -68, 673, 848, 626, 848, -48, 848,
+ /* 120 */ -48, 848, -48, 848, -48, 848, -67, 848, -67, 848,
+ /* 130 */ 51, 848, 51, 848, 51, 848, 51, 848, -67, 794,
+ /* 140 */ 848, -67, -68, -68, 848, -7, 848, -7, 848, 997,
+ /* 150 */ 848, 997, 848, 997, 848, -68, -68, 866, -68, 986,
+ /* 160 */ -68, -68, 848, 532, 848, -67, 61, 744, 284, 563,
+ /* 170 */ 970, 974, 978, -68, 485, 848, 673, 848, -68, 848,
+ /* 180 */ -68, 848, -68, 244, 26, 961, 557, 1078, -68, 848,
+ /* 190 */ 94, 848, 485, 1074, 753, 1075, -68, 1076, 547, 1079,
+ /* 200 */ -68, 1080, 547, 1081, -68, 1082, 547, 1085, -68, 848,
+ /* 210 */ 164, 848, 211, 848, 485, 657, -68, 848, -68, -68,
+ /* 220 */ 993, 547, -68, -68, -68, 848, 579, 848, 673, 230,
+ /* 230 */ 744, 292, -68, 701, -68, 993, -68, 976, 289, -68,
+ /* 240 */ 848, 485, 998, 848, 1091, 848, 485, -68, -68, 503,
+ /* 250 */ -68, -68, -68, 408, -68, 454, -68, 1000, -68, 355,
+ /* 260 */ 993, 457, -68, -68, 547, -68, -68, 1019, 1003, -68,
+ /* 270 */ 1101, 547, 702, -68, 547, -68, 289, -68, -68, 848,
+ /* 280 */ 485, 938, 376, 285, 1106, 457, 1019, 1003, -68, 797,
+ /* 290 */ -21, -68, -68, 1014, 353, -68, -68, -68, -68, 280,
+ /* 300 */ -68, 806, -68, 1111, -68, 344, 667, -68, 547, 1115,
+ /* 310 */ -68, 486, -68, 547, -68, 346, 704, -68, 585, -68,
+ /* 320 */ -68, -68, -68, 704, -68, 704, -68, 547, 933, -68,
+ /* 330 */ -68, 1053, -68, 861, -68, -68, 172, -68, -68, -68,
+ /* 340 */ 720, -68, -68, 721, -68, -68, -68, -68, 598, 63,
+ /* 350 */ 945, -68, 63, 1117, -68, -68, -68, -68, 106, -26,
+ /* 360 */ -68, 547, -68, 1092, 1122, 547, 977, 667, -68, 1123,
+ /* 370 */ 547, 980, 667, -68, 848, 391, -68, 1095, 1125, 547,
+ /* 380 */ 984, 1048, 547, 1115, -68, 383, 1043, -68, -68, -68,
+ /* 390 */ -68, -68, 938, 329, 713, 201, 547, -68, 547, 1138,
+ /* 400 */ 938, 467, 547, 591, 437, 1068, 547, 993, 1130, 193,
+ /* 410 */ 1161, 848, 438, 1135, 709, -68, -68, 1077, 1083, 676,
+ /* 420 */ 547, 920, 547, -68, -68, -68, -68, 1131, -68, -68,
+ /* 430 */ 1049, 547, 1084, 547, 524, 1166, 547, 995, 288, 1178,
+ /* 440 */ 1058, 1179, 281, 472, 778, 167, -68, 1069, 1070, 1177,
+ /* 450 */ 1186, 1187, 281, 1183, 1139, 547, 1098, 547, 659, 547,
+ /* 460 */ 1142, 848, 485, 1198, 1145, 848, 485, 1086, 547, 1191,
+ /* 470 */ 547, 996, -68, 910, 480, 1193, 848, 1007, 848, 485,
+ /* 480 */ 1204, 485, 1100, 547, 941, 1207, 656, 547, 1208, 547,
+ /* 490 */ 1209, 547, 188, 1211, 547, 188, 1214, 519, 1110, 547,
+ /* 500 */ 993, 941, 1216, 1139, 547, 928, 1132, 547, 659, 1230,
+ /* 510 */ 1118, 547, 993, 1191, 912, 523, 1225, 848, 1013, 1236,
+ /* 520 */ 1139, 547, 926, 1134, 547, 792, 1215, 1159, 1243, 703,
+ /* 530 */ 1245, 501, 708, 120, 1247, 1248, 1249, 1250, 732, 1251,
+ /* 540 */ 1252, 1254, 732, 1255, -68, 547, 1253, 1256, 1237, 501,
+ /* 550 */ 1257, 547, 949, 1263, 501, 1266, -68, 1237, 547, 1270,
+ /* 560 */ -68, -68, -68,
+};
+#define YY_REDUCE_USE_DFLT (-123)
+static short yy_reduce_ofst[] = {
+ /* 0 */ -111, 55, -123, 643, -123, -123, -123, -100, 82, -123,
+ /* 10 */ -123, 233, -123, -123, -123, -123, -123, -123, 310, -123,
+ /* 20 */ -123, -123, -123, 442, -123, 448, -123, 542, -123, 540,
+ /* 30 */ -123, 122, 573, -123, -123, 162, -123, 339, 711, 158,
+ /* 40 */ -123, 714, 147, -123, 719, -123, -123, 743, -123, 873,
+ /* 50 */ -123, -123, -123, -123, -123, 885, 904, -123, -123, -123,
+ /* 60 */ 905, -123, -123, 525, -123, 171, -123, -123, 226, -123,
+ /* 70 */ 874, 879, -123, 878, -96, 881, 882, 883, 884, 887,
+ /* 80 */ 875, -123, 913, -123, -123, -123, -123, -123, -123, 915,
+ /* 90 */ -123, 916, -123, -123, 237, -123, -121, 889, 918, -123,
+ /* 100 */ 922, -123, -123, 890, 570, -123, -123, 944, -123, 946,
+ /* 110 */ -123, -123, -123, -123, 890, 576, 890, 671, 890, 751,
+ /* 120 */ 890, 754, 890, 755, 890, 757, 890, 758, 890, 759,
+ /* 130 */ 890, 762, 890, 781, 890, 785, 890, 789, 890, 891,
+ /* 140 */ 790, 890, -123, -123, 791, 890, 793, 890, 798, 890,
+ /* 150 */ 804, 890, 812, 890, 817, 890, -123, -123, -123, -123,
+ /* 160 */ -123, -123, 820, 890, 821, 890, 947, 647, 874, -123,
+ /* 170 */ -123, -123, -123, -123, 890, 823, 890, 824, 890, 826,
+ /* 180 */ 890, 828, 890, 335, 890, 892, 893, -123, -123, 831,
+ /* 190 */ 890, 832, 890, -123, -123, -123, -123, -123, 957, -123,
+ /* 200 */ -123, -123, 960, -123, -123, -123, 963, -123, -123, 836,
+ /* 210 */ 890, 837, 890, 840, 890, -123, -123, -122, -123, -123,
+ /* 220 */ 921, 968, -123, -123, -123, 843, 890, 845, 890, 969,
+ /* 230 */ 710, 874, -123, -123, -123, 924, -123, 919, 954, -123,
+ /* 240 */ 847, 890, -123, 240, -123, 851, 890, -123, 184, 929,
+ /* 250 */ -123, -123, -123, 981, -123, 982, -123, -123, -123, 983,
+ /* 260 */ 931, 620, -123, -123, 985, -123, -123, 942, 936, -123,
+ /* 270 */ -123, 636, -123, -123, 748, -123, 971, -123, -123, 852,
+ /* 280 */ 890, 351, 874, 929, -123, 633, 943, 948, -123, 853,
+ /* 290 */ 116, -123, -123, -123, 944, -123, -123, -123, -123, 890,
+ /* 300 */ -123, -123, -123, -123, -123, 890, 994, -123, 992, 987,
+ /* 310 */ 988, 973, -123, 999, -123, -123, 989, -123, -123, -123,
+ /* 320 */ -123, -123, -123, 990, -123, 991, -123, 658, -123, -123,
+ /* 330 */ -123, 1004, -123, 1001, -123, -123, -123, -123, -123, -123,
+ /* 340 */ -123, -123, -123, -123, -123, -123, -123, -123, 1005, 1002,
+ /* 350 */ -123, -123, 1006, -123, -123, -123, -123, -123, 972, 1008,
+ /* 360 */ -123, 1009, -123, -123, -123, 660, -123, 1011, -123, -123,
+ /* 370 */ 705, -123, 1012, -123, 856, 530, -123, -123, -123, 739,
+ /* 380 */ -123, -123, 1018, 1010, 1015, 502, -123, -123, -123, -123,
+ /* 390 */ -123, -123, 747, 874, 577, -123, 1021, -123, 1022, -123,
+ /* 400 */ 842, 874, 1023, 951, 952, -123, 1028, 1016, 956, 962,
+ /* 410 */ -123, 867, 890, -123, -123, -123, -123, -123, -123, -123,
+ /* 420 */ 295, -123, 1037, -123, -123, -123, -123, -123, -123, -123,
+ /* 430 */ -123, 1041, -123, 1044, 1017, -123, 740, -123, 1047, -123,
+ /* 440 */ -123, -123, 648, 874, 1020, 1024, -123, -123, -123, -123,
+ /* 450 */ -123, -123, 707, -123, 1029, 1060, -123, 829, 1030, 1064,
+ /* 460 */ -123, 868, 890, -123, -123, 872, 890, -123, 1071, 1025,
+ /* 470 */ 432, -123, -123, 876, 874, -123, 571, -123, 880, 890,
+ /* 480 */ -123, 890, -123, 1087, 1039, -123, -123, 1088, -123, 1089,
+ /* 490 */ -123, 1090, 1033, -123, 1093, 1035, -123, 874, -123, 1094,
+ /* 500 */ 1040, 1055, -123, 1063, 1096, 1051, -123, 888, 1062, -123,
+ /* 510 */ -123, 1102, 1054, 1046, 886, 874, -123, 734, -123, -123,
+ /* 520 */ 1097, 1107, 1065, -123, 1109, -123, -123, -123, -123, 1113,
+ /* 530 */ -123, 1103, -123, 47, -123, -123, -123, -123, 1052, -123,
+ /* 540 */ -123, -123, 1057, -123, -123, 1128, -123, -123, 1056, 1119,
+ /* 550 */ -123, 1129, 1061, -123, 1124, -123, -123, 1059, 1141, -123,
+ /* 560 */ -123, -123, -123,
+};
+static YYACTIONTYPE yy_default[] = {
+ /* 0 */ 570, 570, 564, 856, 856, 566, 856, 572, 856, 856,
+ /* 10 */ 856, 856, 652, 655, 656, 657, 658, 659, 573, 574,
+ /* 20 */ 591, 592, 593, 856, 856, 856, 856, 856, 856, 856,
+ /* 30 */ 856, 856, 856, 856, 856, 856, 584, 594, 604, 586,
+ /* 40 */ 603, 856, 856, 605, 651, 616, 856, 651, 617, 636,
+ /* 50 */ 634, 856, 637, 638, 856, 708, 651, 618, 706, 707,
+ /* 60 */ 651, 619, 856, 856, 737, 797, 743, 738, 856, 664,
+ /* 70 */ 856, 856, 665, 673, 675, 682, 720, 711, 713, 701,
+ /* 80 */ 715, 670, 856, 600, 856, 601, 856, 602, 716, 856,
+ /* 90 */ 717, 856, 718, 856, 856, 702, 856, 709, 708, 703,
+ /* 100 */ 856, 588, 710, 705, 856, 736, 856, 856, 739, 856,
+ /* 110 */ 740, 741, 742, 744, 747, 856, 748, 856, 749, 856,
+ /* 120 */ 750, 856, 751, 856, 752, 856, 753, 856, 754, 856,
+ /* 130 */ 755, 856, 756, 856, 757, 856, 758, 856, 759, 856,
+ /* 140 */ 856, 760, 761, 762, 856, 763, 856, 764, 856, 765,
+ /* 150 */ 856, 766, 856, 767, 856, 768, 769, 856, 770, 856,
+ /* 160 */ 773, 771, 856, 856, 856, 779, 856, 797, 856, 856,
+ /* 170 */ 856, 856, 856, 782, 796, 856, 774, 856, 775, 856,
+ /* 180 */ 776, 856, 777, 856, 856, 856, 856, 856, 787, 856,
+ /* 190 */ 856, 856, 788, 856, 856, 856, 845, 856, 856, 856,
+ /* 200 */ 846, 856, 856, 856, 847, 856, 856, 856, 848, 856,
+ /* 210 */ 856, 856, 856, 856, 789, 856, 781, 797, 794, 795,
+ /* 220 */ 690, 856, 691, 785, 772, 856, 856, 856, 780, 856,
+ /* 230 */ 797, 856, 784, 856, 783, 690, 786, 709, 708, 704,
+ /* 240 */ 856, 714, 856, 797, 712, 856, 721, 674, 685, 683,
+ /* 250 */ 684, 692, 693, 856, 694, 856, 695, 856, 696, 856,
+ /* 260 */ 690, 681, 589, 590, 856, 679, 680, 698, 700, 686,
+ /* 270 */ 856, 856, 856, 699, 856, 803, 708, 805, 804, 856,
+ /* 280 */ 697, 685, 856, 856, 856, 681, 698, 700, 687, 856,
+ /* 290 */ 681, 676, 677, 856, 856, 678, 671, 672, 778, 856,
+ /* 300 */ 735, 856, 745, 856, 746, 856, 651, 620, 856, 801,
+ /* 310 */ 624, 621, 625, 856, 626, 856, 856, 627, 856, 630,
+ /* 320 */ 631, 632, 633, 856, 628, 856, 629, 856, 856, 802,
+ /* 330 */ 622, 856, 623, 636, 635, 606, 856, 607, 608, 609,
+ /* 340 */ 856, 610, 613, 856, 611, 614, 612, 615, 595, 856,
+ /* 350 */ 856, 596, 856, 856, 597, 599, 598, 587, 856, 856,
+ /* 360 */ 641, 856, 644, 856, 856, 856, 856, 651, 645, 856,
+ /* 370 */ 856, 856, 651, 646, 856, 651, 647, 856, 856, 856,
+ /* 380 */ 856, 856, 856, 801, 624, 649, 856, 648, 650, 642,
+ /* 390 */ 643, 585, 856, 856, 581, 856, 856, 579, 856, 856,
+ /* 400 */ 856, 856, 856, 828, 856, 856, 856, 690, 833, 856,
+ /* 410 */ 856, 856, 856, 856, 856, 834, 835, 856, 856, 856,
+ /* 420 */ 856, 856, 856, 733, 734, 825, 826, 856, 827, 580,
+ /* 430 */ 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
+ /* 440 */ 856, 856, 856, 856, 856, 856, 654, 856, 856, 856,
+ /* 450 */ 856, 856, 856, 856, 653, 856, 856, 856, 856, 856,
+ /* 460 */ 856, 856, 723, 856, 856, 856, 724, 856, 856, 731,
+ /* 470 */ 856, 856, 732, 856, 856, 856, 856, 856, 856, 729,
+ /* 480 */ 856, 730, 856, 856, 856, 856, 856, 856, 856, 856,
+ /* 490 */ 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
+ /* 500 */ 690, 856, 856, 653, 856, 856, 856, 856, 856, 856,
+ /* 510 */ 856, 856, 690, 731, 856, 856, 856, 856, 856, 856,
+ /* 520 */ 653, 856, 856, 856, 856, 856, 856, 856, 856, 856,
+ /* 530 */ 856, 856, 856, 822, 856, 856, 856, 856, 856, 856,
+ /* 540 */ 856, 856, 856, 856, 821, 856, 856, 856, 854, 856,
+ /* 550 */ 856, 856, 856, 856, 856, 856, 853, 854, 856, 856,
+ /* 560 */ 567, 569, 565,
+};
+#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens. If a construct
+** like the following:
+**
+** %fallback ID X Y Z.
+**
+** appears in the grammer, then ID becomes a fallback token for X, Y,
+** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+ 0, /* $ => nothing */
+ 0, /* END_OF_FILE => nothing */
+ 0, /* ILLEGAL => nothing */
+ 0, /* SPACE => nothing */
+ 0, /* UNCLOSED_STRING => nothing */
+ 0, /* COMMENT => nothing */
+ 0, /* FUNCTION => nothing */
+ 0, /* COLUMN => nothing */
+ 0, /* AGG_FUNCTION => nothing */
+ 0, /* SEMI => nothing */
+ 23, /* EXPLAIN => ID */
+ 23, /* BEGIN => ID */
+ 0, /* TRANSACTION => nothing */
+ 0, /* COMMIT => nothing */
+ 23, /* END => ID */
+ 0, /* ROLLBACK => nothing */
+ 0, /* CREATE => nothing */
+ 0, /* TABLE => nothing */
+ 23, /* TEMP => ID */
+ 0, /* LP => nothing */
+ 0, /* RP => nothing */
+ 0, /* AS => nothing */
+ 0, /* COMMA => nothing */
+ 0, /* ID => nothing */
+ 23, /* ABORT => ID */
+ 23, /* AFTER => ID */
+ 23, /* ASC => ID */
+ 23, /* ATTACH => ID */
+ 23, /* BEFORE => ID */
+ 23, /* CASCADE => ID */
+ 23, /* CLUSTER => ID */
+ 23, /* CONFLICT => ID */
+ 23, /* COPY => ID */
+ 23, /* DATABASE => ID */
+ 23, /* DEFERRED => ID */
+ 23, /* DELIMITERS => ID */
+ 23, /* DESC => ID */
+ 23, /* DETACH => ID */
+ 23, /* EACH => ID */
+ 23, /* FAIL => ID */
+ 23, /* FOR => ID */
+ 23, /* GLOB => ID */
+ 23, /* IGNORE => ID */
+ 23, /* IMMEDIATE => ID */
+ 23, /* INITIALLY => ID */
+ 23, /* INSTEAD => ID */
+ 23, /* LIKE => ID */
+ 23, /* MATCH => ID */
+ 23, /* KEY => ID */
+ 23, /* OF => ID */
+ 23, /* OFFSET => ID */
+ 23, /* PRAGMA => ID */
+ 23, /* RAISE => ID */
+ 23, /* REPLACE => ID */
+ 23, /* RESTRICT => ID */
+ 23, /* ROW => ID */
+ 23, /* STATEMENT => ID */
+ 23, /* TRIGGER => ID */
+ 23, /* VACUUM => ID */
+ 23, /* VIEW => ID */
+ 0, /* OR => nothing */
+ 0, /* AND => nothing */
+ 0, /* NOT => nothing */
+ 0, /* EQ => nothing */
+ 0, /* NE => nothing */
+ 0, /* ISNULL => nothing */
+ 0, /* NOTNULL => nothing */
+ 0, /* IS => nothing */
+ 0, /* BETWEEN => nothing */
+ 0, /* IN => nothing */
+ 0, /* GT => nothing */
+ 0, /* GE => nothing */
+ 0, /* LT => nothing */
+ 0, /* LE => nothing */
+ 0, /* BITAND => nothing */
+ 0, /* BITOR => nothing */
+ 0, /* LSHIFT => nothing */
+ 0, /* RSHIFT => nothing */
+ 0, /* PLUS => nothing */
+ 0, /* MINUS => nothing */
+ 0, /* STAR => nothing */
+ 0, /* SLASH => nothing */
+ 0, /* REM => nothing */
+ 0, /* CONCAT => nothing */
+ 0, /* UMINUS => nothing */
+ 0, /* UPLUS => nothing */
+ 0, /* BITNOT => nothing */
+ 0, /* STRING => nothing */
+ 0, /* JOIN_KW => nothing */
+ 0, /* INTEGER => nothing */
+ 0, /* CONSTRAINT => nothing */
+ 0, /* DEFAULT => nothing */
+ 0, /* FLOAT => nothing */
+ 0, /* NULL => nothing */
+ 0, /* PRIMARY => nothing */
+ 0, /* UNIQUE => nothing */
+ 0, /* CHECK => nothing */
+ 0, /* REFERENCES => nothing */
+ 0, /* COLLATE => nothing */
+ 0, /* ON => nothing */
+ 0, /* DELETE => nothing */
+ 0, /* UPDATE => nothing */
+ 0, /* INSERT => nothing */
+ 0, /* SET => nothing */
+ 0, /* DEFERRABLE => nothing */
+ 0, /* FOREIGN => nothing */
+ 0, /* DROP => nothing */
+ 0, /* UNION => nothing */
+ 0, /* ALL => nothing */
+ 0, /* INTERSECT => nothing */
+ 0, /* EXCEPT => nothing */
+ 0, /* SELECT => nothing */
+ 0, /* DISTINCT => nothing */
+ 0, /* DOT => nothing */
+ 0, /* FROM => nothing */
+ 0, /* JOIN => nothing */
+ 0, /* USING => nothing */
+ 0, /* ORDER => nothing */
+ 0, /* BY => nothing */
+ 0, /* GROUP => nothing */
+ 0, /* HAVING => nothing */
+ 0, /* LIMIT => nothing */
+ 0, /* WHERE => nothing */
+ 0, /* INTO => nothing */
+ 0, /* VALUES => nothing */
+ 0, /* VARIABLE => nothing */
+ 0, /* CASE => nothing */
+ 0, /* WHEN => nothing */
+ 0, /* THEN => nothing */
+ 0, /* ELSE => nothing */
+ 0, /* INDEX => nothing */
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack. Information stored includes:
+**
+** + The state number for the parser at this level of the stack.
+**
+** + The value of the token stored at this level of the stack.
+** (In other words, the "major" token.)
+**
+** + The semantic value stored at this level of the stack. This is
+** the information used by the action routines in the grammar.
+** It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+ int stateno; /* The state-number */
+ int major; /* The major token value. This is the code
+ ** number for the token at this stack level */
+ YYMINORTYPE minor; /* The user-supplied minor token value. This
+ ** is the value of the token */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+ int yyidx; /* Index of top element in stack */
+ int yyerrcnt; /* Shifts left before out of the error */
+ sqliteParserARG_SDECL /* A place to hold %extra_argument */
+ yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/*
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message. Tracing is turned off
+** by making either argument NULL
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void sqliteParserTrace(FILE *TraceFILE, char *zTracePrompt){
+ yyTraceFILE = TraceFILE;
+ yyTracePrompt = zTracePrompt;
+ if( yyTraceFILE==0 ) yyTracePrompt = 0;
+ else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required. The following table supplies these names */
+static const char *yyTokenName[] = {
+ "$", "END_OF_FILE", "ILLEGAL", "SPACE",
+ "UNCLOSED_STRING", "COMMENT", "FUNCTION", "COLUMN",
+ "AGG_FUNCTION", "SEMI", "EXPLAIN", "BEGIN",
+ "TRANSACTION", "COMMIT", "END", "ROLLBACK",
+ "CREATE", "TABLE", "TEMP", "LP",
+ "RP", "AS", "COMMA", "ID",
+ "ABORT", "AFTER", "ASC", "ATTACH",
+ "BEFORE", "CASCADE", "CLUSTER", "CONFLICT",
+ "COPY", "DATABASE", "DEFERRED", "DELIMITERS",
+ "DESC", "DETACH", "EACH", "FAIL",
+ "FOR", "GLOB", "IGNORE", "IMMEDIATE",
+ "INITIALLY", "INSTEAD", "LIKE", "MATCH",
+ "KEY", "OF", "OFFSET", "PRAGMA",
+ "RAISE", "REPLACE", "RESTRICT", "ROW",
+ "STATEMENT", "TRIGGER", "VACUUM", "VIEW",
+ "OR", "AND", "NOT", "EQ",
+ "NE", "ISNULL", "NOTNULL", "IS",
+ "BETWEEN", "IN", "GT", "GE",
+ "LT", "LE", "BITAND", "BITOR",
+ "LSHIFT", "RSHIFT", "PLUS", "MINUS",
+ "STAR", "SLASH", "REM", "CONCAT",
+ "UMINUS", "UPLUS", "BITNOT", "STRING",
+ "JOIN_KW", "INTEGER", "CONSTRAINT", "DEFAULT",
+ "FLOAT", "NULL", "PRIMARY", "UNIQUE",
+ "CHECK", "REFERENCES", "COLLATE", "ON",
+ "DELETE", "UPDATE", "INSERT", "SET",
+ "DEFERRABLE", "FOREIGN", "DROP", "UNION",
+ "ALL", "INTERSECT", "EXCEPT", "SELECT",
+ "DISTINCT", "DOT", "FROM", "JOIN",
+ "USING", "ORDER", "BY", "GROUP",
+ "HAVING", "LIMIT", "WHERE", "INTO",
+ "VALUES", "VARIABLE", "CASE", "WHEN",
+ "THEN", "ELSE", "INDEX", "error",
+ "input", "cmdlist", "ecmd", "explain",
+ "cmdx", "cmd", "trans_opt", "onconf",
+ "nm", "create_table", "create_table_args", "temp",
+ "columnlist", "conslist_opt", "select", "column",
+ "columnid", "type", "carglist", "id",
+ "ids", "typename", "signed", "carg",
+ "ccons", "sortorder", "expr", "idxlist_opt",
+ "refargs", "defer_subclause", "refarg", "refact",
+ "init_deferred_pred_opt", "conslist", "tcons", "idxlist",
+ "defer_subclause_opt", "orconf", "resolvetype", "oneselect",
+ "multiselect_op", "distinct", "selcollist", "from",
+ "where_opt", "groupby_opt", "having_opt", "orderby_opt",
+ "limit_opt", "sclp", "as", "seltablist",
+ "stl_prefix", "joinop", "dbnm", "on_opt",
+ "using_opt", "seltablist_paren", "joinop2", "sortlist",
+ "sortitem", "collate", "exprlist", "setlist",
+ "insert_cmd", "inscollist_opt", "itemlist", "inscollist",
+ "likeop", "case_operand", "case_exprlist", "case_else",
+ "expritem", "uniqueflag", "idxitem", "plus_num",
+ "minus_num", "plus_opt", "number", "trigger_decl",
+ "trigger_cmd_list", "trigger_time", "trigger_event", "foreach_clause",
+ "when_clause", "trigger_cmd", "database_kw_opt", "key_opt",
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *yyRuleName[] = {
+ /* 0 */ "input ::= cmdlist",
+ /* 1 */ "cmdlist ::= cmdlist ecmd",
+ /* 2 */ "cmdlist ::= ecmd",
+ /* 3 */ "ecmd ::= explain cmdx SEMI",
+ /* 4 */ "ecmd ::= SEMI",
+ /* 5 */ "cmdx ::= cmd",
+ /* 6 */ "explain ::= EXPLAIN",
+ /* 7 */ "explain ::=",
+ /* 8 */ "cmd ::= BEGIN trans_opt onconf",
+ /* 9 */ "trans_opt ::=",
+ /* 10 */ "trans_opt ::= TRANSACTION",
+ /* 11 */ "trans_opt ::= TRANSACTION nm",
+ /* 12 */ "cmd ::= COMMIT trans_opt",
+ /* 13 */ "cmd ::= END trans_opt",
+ /* 14 */ "cmd ::= ROLLBACK trans_opt",
+ /* 15 */ "cmd ::= create_table create_table_args",
+ /* 16 */ "create_table ::= CREATE temp TABLE nm",
+ /* 17 */ "temp ::= TEMP",
+ /* 18 */ "temp ::=",
+ /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP",
+ /* 20 */ "create_table_args ::= AS select",
+ /* 21 */ "columnlist ::= columnlist COMMA column",
+ /* 22 */ "columnlist ::= column",
+ /* 23 */ "column ::= columnid type carglist",
+ /* 24 */ "columnid ::= nm",
+ /* 25 */ "id ::= ID",
+ /* 26 */ "ids ::= ID",
+ /* 27 */ "ids ::= STRING",
+ /* 28 */ "nm ::= ID",
+ /* 29 */ "nm ::= STRING",
+ /* 30 */ "nm ::= JOIN_KW",
+ /* 31 */ "type ::=",
+ /* 32 */ "type ::= typename",
+ /* 33 */ "type ::= typename LP signed RP",
+ /* 34 */ "type ::= typename LP signed COMMA signed RP",
+ /* 35 */ "typename ::= ids",
+ /* 36 */ "typename ::= typename ids",
+ /* 37 */ "signed ::= INTEGER",
+ /* 38 */ "signed ::= PLUS INTEGER",
+ /* 39 */ "signed ::= MINUS INTEGER",
+ /* 40 */ "carglist ::= carglist carg",
+ /* 41 */ "carglist ::=",
+ /* 42 */ "carg ::= CONSTRAINT nm ccons",
+ /* 43 */ "carg ::= ccons",
+ /* 44 */ "carg ::= DEFAULT STRING",
+ /* 45 */ "carg ::= DEFAULT ID",
+ /* 46 */ "carg ::= DEFAULT INTEGER",
+ /* 47 */ "carg ::= DEFAULT PLUS INTEGER",
+ /* 48 */ "carg ::= DEFAULT MINUS INTEGER",
+ /* 49 */ "carg ::= DEFAULT FLOAT",
+ /* 50 */ "carg ::= DEFAULT PLUS FLOAT",
+ /* 51 */ "carg ::= DEFAULT MINUS FLOAT",
+ /* 52 */ "carg ::= DEFAULT NULL",
+ /* 53 */ "ccons ::= NULL onconf",
+ /* 54 */ "ccons ::= NOT NULL onconf",
+ /* 55 */ "ccons ::= PRIMARY KEY sortorder onconf",
+ /* 56 */ "ccons ::= UNIQUE onconf",
+ /* 57 */ "ccons ::= CHECK LP expr RP onconf",
+ /* 58 */ "ccons ::= REFERENCES nm idxlist_opt refargs",
+ /* 59 */ "ccons ::= defer_subclause",
+ /* 60 */ "ccons ::= COLLATE id",
+ /* 61 */ "refargs ::=",
+ /* 62 */ "refargs ::= refargs refarg",
+ /* 63 */ "refarg ::= MATCH nm",
+ /* 64 */ "refarg ::= ON DELETE refact",
+ /* 65 */ "refarg ::= ON UPDATE refact",
+ /* 66 */ "refarg ::= ON INSERT refact",
+ /* 67 */ "refact ::= SET NULL",
+ /* 68 */ "refact ::= SET DEFAULT",
+ /* 69 */ "refact ::= CASCADE",
+ /* 70 */ "refact ::= RESTRICT",
+ /* 71 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt",
+ /* 72 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt",
+ /* 73 */ "init_deferred_pred_opt ::=",
+ /* 74 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED",
+ /* 75 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE",
+ /* 76 */ "conslist_opt ::=",
+ /* 77 */ "conslist_opt ::= COMMA conslist",
+ /* 78 */ "conslist ::= conslist COMMA tcons",
+ /* 79 */ "conslist ::= conslist tcons",
+ /* 80 */ "conslist ::= tcons",
+ /* 81 */ "tcons ::= CONSTRAINT nm",
+ /* 82 */ "tcons ::= PRIMARY KEY LP idxlist RP onconf",
+ /* 83 */ "tcons ::= UNIQUE LP idxlist RP onconf",
+ /* 84 */ "tcons ::= CHECK expr onconf",
+ /* 85 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt",
+ /* 86 */ "defer_subclause_opt ::=",
+ /* 87 */ "defer_subclause_opt ::= defer_subclause",
+ /* 88 */ "onconf ::=",
+ /* 89 */ "onconf ::= ON CONFLICT resolvetype",
+ /* 90 */ "orconf ::=",
+ /* 91 */ "orconf ::= OR resolvetype",
+ /* 92 */ "resolvetype ::= ROLLBACK",
+ /* 93 */ "resolvetype ::= ABORT",
+ /* 94 */ "resolvetype ::= FAIL",
+ /* 95 */ "resolvetype ::= IGNORE",
+ /* 96 */ "resolvetype ::= REPLACE",
+ /* 97 */ "cmd ::= DROP TABLE nm",
+ /* 98 */ "cmd ::= CREATE temp VIEW nm AS select",
+ /* 99 */ "cmd ::= DROP VIEW nm",
+ /* 100 */ "cmd ::= select",
+ /* 101 */ "select ::= oneselect",
+ /* 102 */ "select ::= select multiselect_op oneselect",
+ /* 103 */ "multiselect_op ::= UNION",
+ /* 104 */ "multiselect_op ::= UNION ALL",
+ /* 105 */ "multiselect_op ::= INTERSECT",
+ /* 106 */ "multiselect_op ::= EXCEPT",
+ /* 107 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt",
+ /* 108 */ "distinct ::= DISTINCT",
+ /* 109 */ "distinct ::= ALL",
+ /* 110 */ "distinct ::=",
+ /* 111 */ "sclp ::= selcollist COMMA",
+ /* 112 */ "sclp ::=",
+ /* 113 */ "selcollist ::= sclp expr as",
+ /* 114 */ "selcollist ::= sclp STAR",
+ /* 115 */ "selcollist ::= sclp nm DOT STAR",
+ /* 116 */ "as ::= AS nm",
+ /* 117 */ "as ::= ids",
+ /* 118 */ "as ::=",
+ /* 119 */ "from ::=",
+ /* 120 */ "from ::= FROM seltablist",
+ /* 121 */ "stl_prefix ::= seltablist joinop",
+ /* 122 */ "stl_prefix ::=",
+ /* 123 */ "seltablist ::= stl_prefix nm dbnm as on_opt using_opt",
+ /* 124 */ "seltablist ::= stl_prefix LP seltablist_paren RP as on_opt using_opt",
+ /* 125 */ "seltablist_paren ::= select",
+ /* 126 */ "seltablist_paren ::= seltablist",
+ /* 127 */ "dbnm ::=",
+ /* 128 */ "dbnm ::= DOT nm",
+ /* 129 */ "joinop ::= COMMA",
+ /* 130 */ "joinop ::= JOIN",
+ /* 131 */ "joinop ::= JOIN_KW JOIN",
+ /* 132 */ "joinop ::= JOIN_KW nm JOIN",
+ /* 133 */ "joinop ::= JOIN_KW nm nm JOIN",
+ /* 134 */ "on_opt ::= ON expr",
+ /* 135 */ "on_opt ::=",
+ /* 136 */ "using_opt ::= USING LP idxlist RP",
+ /* 137 */ "using_opt ::=",
+ /* 138 */ "orderby_opt ::=",
+ /* 139 */ "orderby_opt ::= ORDER BY sortlist",
+ /* 140 */ "sortlist ::= sortlist COMMA sortitem collate sortorder",
+ /* 141 */ "sortlist ::= sortitem collate sortorder",
+ /* 142 */ "sortitem ::= expr",
+ /* 143 */ "sortorder ::= ASC",
+ /* 144 */ "sortorder ::= DESC",
+ /* 145 */ "sortorder ::=",
+ /* 146 */ "collate ::=",
+ /* 147 */ "collate ::= COLLATE id",
+ /* 148 */ "groupby_opt ::=",
+ /* 149 */ "groupby_opt ::= GROUP BY exprlist",
+ /* 150 */ "having_opt ::=",
+ /* 151 */ "having_opt ::= HAVING expr",
+ /* 152 */ "limit_opt ::=",
+ /* 153 */ "limit_opt ::= LIMIT signed",
+ /* 154 */ "limit_opt ::= LIMIT signed OFFSET signed",
+ /* 155 */ "limit_opt ::= LIMIT signed COMMA signed",
+ /* 156 */ "cmd ::= DELETE FROM nm dbnm where_opt",
+ /* 157 */ "where_opt ::=",
+ /* 158 */ "where_opt ::= WHERE expr",
+ /* 159 */ "cmd ::= UPDATE orconf nm dbnm SET setlist where_opt",
+ /* 160 */ "setlist ::= setlist COMMA nm EQ expr",
+ /* 161 */ "setlist ::= nm EQ expr",
+ /* 162 */ "cmd ::= insert_cmd INTO nm dbnm inscollist_opt VALUES LP itemlist RP",
+ /* 163 */ "cmd ::= insert_cmd INTO nm dbnm inscollist_opt select",
+ /* 164 */ "insert_cmd ::= INSERT orconf",
+ /* 165 */ "insert_cmd ::= REPLACE",
+ /* 166 */ "itemlist ::= itemlist COMMA expr",
+ /* 167 */ "itemlist ::= expr",
+ /* 168 */ "inscollist_opt ::=",
+ /* 169 */ "inscollist_opt ::= LP inscollist RP",
+ /* 170 */ "inscollist ::= inscollist COMMA nm",
+ /* 171 */ "inscollist ::= nm",
+ /* 172 */ "expr ::= LP expr RP",
+ /* 173 */ "expr ::= NULL",
+ /* 174 */ "expr ::= ID",
+ /* 175 */ "expr ::= JOIN_KW",
+ /* 176 */ "expr ::= nm DOT nm",
+ /* 177 */ "expr ::= nm DOT nm DOT nm",
+ /* 178 */ "expr ::= INTEGER",
+ /* 179 */ "expr ::= FLOAT",
+ /* 180 */ "expr ::= STRING",
+ /* 181 */ "expr ::= VARIABLE",
+ /* 182 */ "expr ::= ID LP exprlist RP",
+ /* 183 */ "expr ::= ID LP STAR RP",
+ /* 184 */ "expr ::= expr AND expr",
+ /* 185 */ "expr ::= expr OR expr",
+ /* 186 */ "expr ::= expr LT expr",
+ /* 187 */ "expr ::= expr GT expr",
+ /* 188 */ "expr ::= expr LE expr",
+ /* 189 */ "expr ::= expr GE expr",
+ /* 190 */ "expr ::= expr NE expr",
+ /* 191 */ "expr ::= expr EQ expr",
+ /* 192 */ "expr ::= expr BITAND expr",
+ /* 193 */ "expr ::= expr BITOR expr",
+ /* 194 */ "expr ::= expr LSHIFT expr",
+ /* 195 */ "expr ::= expr RSHIFT expr",
+ /* 196 */ "expr ::= expr likeop expr",
+ /* 197 */ "expr ::= expr NOT likeop expr",
+ /* 198 */ "likeop ::= LIKE",
+ /* 199 */ "likeop ::= GLOB",
+ /* 200 */ "expr ::= expr PLUS expr",
+ /* 201 */ "expr ::= expr MINUS expr",
+ /* 202 */ "expr ::= expr STAR expr",
+ /* 203 */ "expr ::= expr SLASH expr",
+ /* 204 */ "expr ::= expr REM expr",
+ /* 205 */ "expr ::= expr CONCAT expr",
+ /* 206 */ "expr ::= expr ISNULL",
+ /* 207 */ "expr ::= expr IS NULL",
+ /* 208 */ "expr ::= expr NOTNULL",
+ /* 209 */ "expr ::= expr NOT NULL",
+ /* 210 */ "expr ::= expr IS NOT NULL",
+ /* 211 */ "expr ::= NOT expr",
+ /* 212 */ "expr ::= BITNOT expr",
+ /* 213 */ "expr ::= MINUS expr",
+ /* 214 */ "expr ::= PLUS expr",
+ /* 215 */ "expr ::= LP select RP",
+ /* 216 */ "expr ::= expr BETWEEN expr AND expr",
+ /* 217 */ "expr ::= expr NOT BETWEEN expr AND expr",
+ /* 218 */ "expr ::= expr IN LP exprlist RP",
+ /* 219 */ "expr ::= expr IN LP select RP",
+ /* 220 */ "expr ::= expr NOT IN LP exprlist RP",
+ /* 221 */ "expr ::= expr NOT IN LP select RP",
+ /* 222 */ "expr ::= expr IN nm dbnm",
+ /* 223 */ "expr ::= expr NOT IN nm dbnm",
+ /* 224 */ "expr ::= CASE case_operand case_exprlist case_else END",
+ /* 225 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
+ /* 226 */ "case_exprlist ::= WHEN expr THEN expr",
+ /* 227 */ "case_else ::= ELSE expr",
+ /* 228 */ "case_else ::=",
+ /* 229 */ "case_operand ::= expr",
+ /* 230 */ "case_operand ::=",
+ /* 231 */ "exprlist ::= exprlist COMMA expritem",
+ /* 232 */ "exprlist ::= expritem",
+ /* 233 */ "expritem ::= expr",
+ /* 234 */ "expritem ::=",
+ /* 235 */ "cmd ::= CREATE uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf",
+ /* 236 */ "uniqueflag ::= UNIQUE",
+ /* 237 */ "uniqueflag ::=",
+ /* 238 */ "idxlist_opt ::=",
+ /* 239 */ "idxlist_opt ::= LP idxlist RP",
+ /* 240 */ "idxlist ::= idxlist COMMA idxitem",
+ /* 241 */ "idxlist ::= idxitem",
+ /* 242 */ "idxitem ::= nm sortorder",
+ /* 243 */ "cmd ::= DROP INDEX nm dbnm",
+ /* 244 */ "cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS STRING",
+ /* 245 */ "cmd ::= COPY orconf nm dbnm FROM nm",
+ /* 246 */ "cmd ::= VACUUM",
+ /* 247 */ "cmd ::= VACUUM nm",
+ /* 248 */ "cmd ::= PRAGMA ids EQ nm",
+ /* 249 */ "cmd ::= PRAGMA ids EQ ON",
+ /* 250 */ "cmd ::= PRAGMA ids EQ plus_num",
+ /* 251 */ "cmd ::= PRAGMA ids EQ minus_num",
+ /* 252 */ "cmd ::= PRAGMA ids LP nm RP",
+ /* 253 */ "cmd ::= PRAGMA ids",
+ /* 254 */ "plus_num ::= plus_opt number",
+ /* 255 */ "minus_num ::= MINUS number",
+ /* 256 */ "number ::= INTEGER",
+ /* 257 */ "number ::= FLOAT",
+ /* 258 */ "plus_opt ::= PLUS",
+ /* 259 */ "plus_opt ::=",
+ /* 260 */ "cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list END",
+ /* 261 */ "trigger_decl ::= temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause",
+ /* 262 */ "trigger_time ::= BEFORE",
+ /* 263 */ "trigger_time ::= AFTER",
+ /* 264 */ "trigger_time ::= INSTEAD OF",
+ /* 265 */ "trigger_time ::=",
+ /* 266 */ "trigger_event ::= DELETE",
+ /* 267 */ "trigger_event ::= INSERT",
+ /* 268 */ "trigger_event ::= UPDATE",
+ /* 269 */ "trigger_event ::= UPDATE OF inscollist",
+ /* 270 */ "foreach_clause ::=",
+ /* 271 */ "foreach_clause ::= FOR EACH ROW",
+ /* 272 */ "foreach_clause ::= FOR EACH STATEMENT",
+ /* 273 */ "when_clause ::=",
+ /* 274 */ "when_clause ::= WHEN expr",
+ /* 275 */ "trigger_cmd_list ::= trigger_cmd SEMI trigger_cmd_list",
+ /* 276 */ "trigger_cmd_list ::=",
+ /* 277 */ "trigger_cmd ::= UPDATE orconf nm SET setlist where_opt",
+ /* 278 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt VALUES LP itemlist RP",
+ /* 279 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt select",
+ /* 280 */ "trigger_cmd ::= DELETE FROM nm where_opt",
+ /* 281 */ "trigger_cmd ::= select",
+ /* 282 */ "expr ::= RAISE LP IGNORE RP",
+ /* 283 */ "expr ::= RAISE LP ROLLBACK COMMA nm RP",
+ /* 284 */ "expr ::= RAISE LP ABORT COMMA nm RP",
+ /* 285 */ "expr ::= RAISE LP FAIL COMMA nm RP",
+ /* 286 */ "cmd ::= DROP TRIGGER nm dbnm",
+ /* 287 */ "cmd ::= ATTACH database_kw_opt ids AS nm key_opt",
+ /* 288 */ "key_opt ::= USING ids",
+ /* 289 */ "key_opt ::=",
+ /* 290 */ "database_kw_opt ::= DATABASE",
+ /* 291 */ "database_kw_opt ::=",
+ /* 292 */ "cmd ::= DETACH database_kw_opt nm",
+};
+#endif /* NDEBUG */
+
+/*
+** This function returns the symbolic name associated with a token
+** value.
+*/
+const char *sqliteParserTokenName(int tokenType){
+#ifndef NDEBUG
+ if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){
+ return yyTokenName[tokenType];
+ }else{
+ return "Unknown";
+ }
+#else
+ return "";
+#endif
+}
+
+/*
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser. This pointer is used in subsequent calls
+** to sqliteParser and sqliteParserFree.
+*/
+void *sqliteParserAlloc(void *(*mallocProc)(size_t)){
+ yyParser *pParser;
+ pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+ if( pParser ){
+ pParser->yyidx = -1;
+ }
+ return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol. The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){
+ switch( yymajor ){
+ /* Here is inserted the actions which take place when a
+ ** terminal or non-terminal is destroyed. This can happen
+ ** when the symbol is popped from the stack during a
+ ** reduce or during error processing or when a parser is
+ ** being destroyed before it is finished parsing.
+ **
+ ** Note: during a reduce, the only symbols destroyed are those
+ ** which appear on the RHS of the rule, but which are not used
+ ** inside the C code.
+ */
+ case 146:
+#line 286 "parse.y"
+{sqliteSelectDelete((yypminor->yy179));}
+#line 1235 "parse.c"
+ break;
+ case 158:
+#line 533 "parse.y"
+{sqliteExprDelete((yypminor->yy242));}
+#line 1240 "parse.c"
+ break;
+ case 159:
+#line 746 "parse.y"
+{sqliteIdListDelete((yypminor->yy320));}
+#line 1245 "parse.c"
+ break;
+ case 167:
+#line 744 "parse.y"
+{sqliteIdListDelete((yypminor->yy320));}
+#line 1250 "parse.c"
+ break;
+ case 171:
+#line 288 "parse.y"
+{sqliteSelectDelete((yypminor->yy179));}
+#line 1255 "parse.c"
+ break;
+ case 174:
+#line 322 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1260 "parse.c"
+ break;
+ case 175:
+#line 353 "parse.y"
+{sqliteSrcListDelete((yypminor->yy307));}
+#line 1265 "parse.c"
+ break;
+ case 176:
+#line 483 "parse.y"
+{sqliteExprDelete((yypminor->yy242));}
+#line 1270 "parse.c"
+ break;
+ case 177:
+#line 459 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1275 "parse.c"
+ break;
+ case 178:
+#line 464 "parse.y"
+{sqliteExprDelete((yypminor->yy242));}
+#line 1280 "parse.c"
+ break;
+ case 179:
+#line 431 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1285 "parse.c"
+ break;
+ case 181:
+#line 324 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1290 "parse.c"
+ break;
+ case 183:
+#line 349 "parse.y"
+{sqliteSrcListDelete((yypminor->yy307));}
+#line 1295 "parse.c"
+ break;
+ case 184:
+#line 351 "parse.y"
+{sqliteSrcListDelete((yypminor->yy307));}
+#line 1300 "parse.c"
+ break;
+ case 187:
+#line 420 "parse.y"
+{sqliteExprDelete((yypminor->yy242));}
+#line 1305 "parse.c"
+ break;
+ case 188:
+#line 425 "parse.y"
+{sqliteIdListDelete((yypminor->yy320));}
+#line 1310 "parse.c"
+ break;
+ case 189:
+#line 400 "parse.y"
+{sqliteSelectDelete((yypminor->yy179));}
+#line 1315 "parse.c"
+ break;
+ case 191:
+#line 433 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1320 "parse.c"
+ break;
+ case 192:
+#line 435 "parse.y"
+{sqliteExprDelete((yypminor->yy242));}
+#line 1325 "parse.c"
+ break;
+ case 194:
+#line 719 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1330 "parse.c"
+ break;
+ case 195:
+#line 489 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1335 "parse.c"
+ break;
+ case 197:
+#line 520 "parse.y"
+{sqliteIdListDelete((yypminor->yy320));}
+#line 1340 "parse.c"
+ break;
+ case 198:
+#line 514 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1345 "parse.c"
+ break;
+ case 199:
+#line 522 "parse.y"
+{sqliteIdListDelete((yypminor->yy320));}
+#line 1350 "parse.c"
+ break;
+ case 202:
+#line 702 "parse.y"
+{sqliteExprListDelete((yypminor->yy322));}
+#line 1355 "parse.c"
+ break;
+ case 204:
+#line 721 "parse.y"
+{sqliteExprDelete((yypminor->yy242));}
+#line 1360 "parse.c"
+ break;
+ case 212:
+#line 828 "parse.y"
+{sqliteDeleteTriggerStep((yypminor->yy19));}
+#line 1365 "parse.c"
+ break;
+ case 214:
+#line 812 "parse.y"
+{sqliteIdListDelete((yypminor->yy290).b);}
+#line 1370 "parse.c"
+ break;
+ case 217:
+#line 836 "parse.y"
+{sqliteDeleteTriggerStep((yypminor->yy19));}
+#line 1375 "parse.c"
+ break;
+ default: break; /* If no destructor action specified: do nothing */
+ }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+ YYCODETYPE yymajor;
+ yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+ if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+ if( yyTraceFILE && pParser->yyidx>=0 ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[yytos->major]);
+ }
+#endif
+ yymajor = yytos->major;
+ yy_destructor( yymajor, &yytos->minor);
+ pParser->yyidx--;
+ return yymajor;
+}
+
+/*
+** Deallocate and destroy a parser. Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from sqliteParserAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+void sqliteParserFree(
+ void *p, /* The parser to be deleted */
+ void (*freeProc)(void*) /* Function used to reclaim memory */
+){
+ yyParser *pParser = (yyParser*)p;
+ if( pParser==0 ) return;
+ while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+ (*freeProc)((void*)pParser);
+}
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */
+ i = yy_shift_ofst[stateno];
+ if( i==YY_SHIFT_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+ int iFallback; /* Fallback token */
+ if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+ }
+#endif
+ return yy_find_shift_action(pParser, iFallback);
+ }
+#endif
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ i = yy_reduce_ofst[stateno];
+ if( i==YY_REDUCE_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+ yyParser *yypParser, /* The parser to be shifted */
+ int yyNewState, /* The new state to shift in */
+ int yyMajor, /* The major token to shift in */
+ YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */
+){
+ yyStackEntry *yytos;
+ yypParser->yyidx++;
+ if( yypParser->yyidx>=YYSTACKDEPTH ){
+ sqliteParserARG_FETCH;
+ yypParser->yyidx--;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will execute if the parser
+ ** stack every overflows */
+ sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument var */
+ return;
+ }
+ yytos = &yypParser->yystack[yypParser->yyidx];
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
+ yytos->minor = *yypMinor;
+#ifndef NDEBUG
+ if( yyTraceFILE && yypParser->yyidx>0 ){
+ int i;
+ fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+ fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+ for(i=1; i<=yypParser->yyidx; i++)
+ fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+ fprintf(yyTraceFILE,"\n");
+ }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static struct {
+ YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
+ unsigned char nrhs; /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+ { 132, 1 },
+ { 133, 2 },
+ { 133, 1 },
+ { 134, 3 },
+ { 134, 1 },
+ { 136, 1 },
+ { 135, 1 },
+ { 135, 0 },
+ { 137, 3 },
+ { 138, 0 },
+ { 138, 1 },
+ { 138, 2 },
+ { 137, 2 },
+ { 137, 2 },
+ { 137, 2 },
+ { 137, 2 },
+ { 141, 4 },
+ { 143, 1 },
+ { 143, 0 },
+ { 142, 4 },
+ { 142, 2 },
+ { 144, 3 },
+ { 144, 1 },
+ { 147, 3 },
+ { 148, 1 },
+ { 151, 1 },
+ { 152, 1 },
+ { 152, 1 },
+ { 140, 1 },
+ { 140, 1 },
+ { 140, 1 },
+ { 149, 0 },
+ { 149, 1 },
+ { 149, 4 },
+ { 149, 6 },
+ { 153, 1 },
+ { 153, 2 },
+ { 154, 1 },
+ { 154, 2 },
+ { 154, 2 },
+ { 150, 2 },
+ { 150, 0 },
+ { 155, 3 },
+ { 155, 1 },
+ { 155, 2 },
+ { 155, 2 },
+ { 155, 2 },
+ { 155, 3 },
+ { 155, 3 },
+ { 155, 2 },
+ { 155, 3 },
+ { 155, 3 },
+ { 155, 2 },
+ { 156, 2 },
+ { 156, 3 },
+ { 156, 4 },
+ { 156, 2 },
+ { 156, 5 },
+ { 156, 4 },
+ { 156, 1 },
+ { 156, 2 },
+ { 160, 0 },
+ { 160, 2 },
+ { 162, 2 },
+ { 162, 3 },
+ { 162, 3 },
+ { 162, 3 },
+ { 163, 2 },
+ { 163, 2 },
+ { 163, 1 },
+ { 163, 1 },
+ { 161, 3 },
+ { 161, 2 },
+ { 164, 0 },
+ { 164, 2 },
+ { 164, 2 },
+ { 145, 0 },
+ { 145, 2 },
+ { 165, 3 },
+ { 165, 2 },
+ { 165, 1 },
+ { 166, 2 },
+ { 166, 6 },
+ { 166, 5 },
+ { 166, 3 },
+ { 166, 10 },
+ { 168, 0 },
+ { 168, 1 },
+ { 139, 0 },
+ { 139, 3 },
+ { 169, 0 },
+ { 169, 2 },
+ { 170, 1 },
+ { 170, 1 },
+ { 170, 1 },
+ { 170, 1 },
+ { 170, 1 },
+ { 137, 3 },
+ { 137, 6 },
+ { 137, 3 },
+ { 137, 1 },
+ { 146, 1 },
+ { 146, 3 },
+ { 172, 1 },
+ { 172, 2 },
+ { 172, 1 },
+ { 172, 1 },
+ { 171, 9 },
+ { 173, 1 },
+ { 173, 1 },
+ { 173, 0 },
+ { 181, 2 },
+ { 181, 0 },
+ { 174, 3 },
+ { 174, 2 },
+ { 174, 4 },
+ { 182, 2 },
+ { 182, 1 },
+ { 182, 0 },
+ { 175, 0 },
+ { 175, 2 },
+ { 184, 2 },
+ { 184, 0 },
+ { 183, 6 },
+ { 183, 7 },
+ { 189, 1 },
+ { 189, 1 },
+ { 186, 0 },
+ { 186, 2 },
+ { 185, 1 },
+ { 185, 1 },
+ { 185, 2 },
+ { 185, 3 },
+ { 185, 4 },
+ { 187, 2 },
+ { 187, 0 },
+ { 188, 4 },
+ { 188, 0 },
+ { 179, 0 },
+ { 179, 3 },
+ { 191, 5 },
+ { 191, 3 },
+ { 192, 1 },
+ { 157, 1 },
+ { 157, 1 },
+ { 157, 0 },
+ { 193, 0 },
+ { 193, 2 },
+ { 177, 0 },
+ { 177, 3 },
+ { 178, 0 },
+ { 178, 2 },
+ { 180, 0 },
+ { 180, 2 },
+ { 180, 4 },
+ { 180, 4 },
+ { 137, 5 },
+ { 176, 0 },
+ { 176, 2 },
+ { 137, 7 },
+ { 195, 5 },
+ { 195, 3 },
+ { 137, 9 },
+ { 137, 6 },
+ { 196, 2 },
+ { 196, 1 },
+ { 198, 3 },
+ { 198, 1 },
+ { 197, 0 },
+ { 197, 3 },
+ { 199, 3 },
+ { 199, 1 },
+ { 158, 3 },
+ { 158, 1 },
+ { 158, 1 },
+ { 158, 1 },
+ { 158, 3 },
+ { 158, 5 },
+ { 158, 1 },
+ { 158, 1 },
+ { 158, 1 },
+ { 158, 1 },
+ { 158, 4 },
+ { 158, 4 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 4 },
+ { 200, 1 },
+ { 200, 1 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 3 },
+ { 158, 2 },
+ { 158, 3 },
+ { 158, 2 },
+ { 158, 3 },
+ { 158, 4 },
+ { 158, 2 },
+ { 158, 2 },
+ { 158, 2 },
+ { 158, 2 },
+ { 158, 3 },
+ { 158, 5 },
+ { 158, 6 },
+ { 158, 5 },
+ { 158, 5 },
+ { 158, 6 },
+ { 158, 6 },
+ { 158, 4 },
+ { 158, 5 },
+ { 158, 5 },
+ { 202, 5 },
+ { 202, 4 },
+ { 203, 2 },
+ { 203, 0 },
+ { 201, 1 },
+ { 201, 0 },
+ { 194, 3 },
+ { 194, 1 },
+ { 204, 1 },
+ { 204, 0 },
+ { 137, 11 },
+ { 205, 1 },
+ { 205, 0 },
+ { 159, 0 },
+ { 159, 3 },
+ { 167, 3 },
+ { 167, 1 },
+ { 206, 2 },
+ { 137, 4 },
+ { 137, 9 },
+ { 137, 6 },
+ { 137, 1 },
+ { 137, 2 },
+ { 137, 4 },
+ { 137, 4 },
+ { 137, 4 },
+ { 137, 4 },
+ { 137, 5 },
+ { 137, 2 },
+ { 207, 2 },
+ { 208, 2 },
+ { 210, 1 },
+ { 210, 1 },
+ { 209, 1 },
+ { 209, 0 },
+ { 137, 5 },
+ { 211, 10 },
+ { 213, 1 },
+ { 213, 1 },
+ { 213, 2 },
+ { 213, 0 },
+ { 214, 1 },
+ { 214, 1 },
+ { 214, 1 },
+ { 214, 3 },
+ { 215, 0 },
+ { 215, 3 },
+ { 215, 3 },
+ { 216, 0 },
+ { 216, 2 },
+ { 212, 3 },
+ { 212, 0 },
+ { 217, 6 },
+ { 217, 8 },
+ { 217, 5 },
+ { 217, 4 },
+ { 217, 1 },
+ { 158, 4 },
+ { 158, 6 },
+ { 158, 6 },
+ { 158, 6 },
+ { 137, 4 },
+ { 137, 6 },
+ { 219, 2 },
+ { 219, 0 },
+ { 218, 1 },
+ { 218, 0 },
+ { 137, 3 },
+};
+
+static void yy_accept(yyParser*); /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+ yyParser *yypParser, /* The parser */
+ int yyruleno /* Number of the rule by which to reduce */
+){
+ int yygoto; /* The next state */
+ int yyact; /* The next action */
+ YYMINORTYPE yygotominor; /* The LHS of the rule reduced */
+ yyStackEntry *yymsp; /* The top of the parser's stack */
+ int yysize; /* Amount to pop the stack */
+ sqliteParserARG_FETCH;
+ yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+ if( yyTraceFILE && yyruleno>=0
+ && yyruleno<sizeof(yyRuleName)/sizeof(yyRuleName[0]) ){
+ fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+ yyRuleName[yyruleno]);
+ }
+#endif /* NDEBUG */
+
+ switch( yyruleno ){
+ /* Beginning here are the reduction cases. A typical example
+ ** follows:
+ ** case 0:
+ ** #line <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** break;
+ */
+ case 0:
+ /* No destructor defined for cmdlist */
+ break;
+ case 1:
+ /* No destructor defined for cmdlist */
+ /* No destructor defined for ecmd */
+ break;
+ case 2:
+ /* No destructor defined for ecmd */
+ break;
+ case 3:
+ /* No destructor defined for explain */
+ /* No destructor defined for cmdx */
+ /* No destructor defined for SEMI */
+ break;
+ case 4:
+ /* No destructor defined for SEMI */
+ break;
+ case 5:
+#line 72 "parse.y"
+{ sqliteExec(pParse); }
+#line 1901 "parse.c"
+ /* No destructor defined for cmd */
+ break;
+ case 6:
+#line 73 "parse.y"
+{ sqliteBeginParse(pParse, 1); }
+#line 1907 "parse.c"
+ /* No destructor defined for EXPLAIN */
+ break;
+ case 7:
+#line 74 "parse.y"
+{ sqliteBeginParse(pParse, 0); }
+#line 1913 "parse.c"
+ break;
+ case 8:
+#line 79 "parse.y"
+{sqliteBeginTransaction(pParse,yymsp[0].minor.yy372);}
+#line 1918 "parse.c"
+ /* No destructor defined for BEGIN */
+ /* No destructor defined for trans_opt */
+ break;
+ case 9:
+ break;
+ case 10:
+ /* No destructor defined for TRANSACTION */
+ break;
+ case 11:
+ /* No destructor defined for TRANSACTION */
+ /* No destructor defined for nm */
+ break;
+ case 12:
+#line 83 "parse.y"
+{sqliteCommitTransaction(pParse);}
+#line 1934 "parse.c"
+ /* No destructor defined for COMMIT */
+ /* No destructor defined for trans_opt */
+ break;
+ case 13:
+#line 84 "parse.y"
+{sqliteCommitTransaction(pParse);}
+#line 1941 "parse.c"
+ /* No destructor defined for END */
+ /* No destructor defined for trans_opt */
+ break;
+ case 14:
+#line 85 "parse.y"
+{sqliteRollbackTransaction(pParse);}
+#line 1948 "parse.c"
+ /* No destructor defined for ROLLBACK */
+ /* No destructor defined for trans_opt */
+ break;
+ case 15:
+ /* No destructor defined for create_table */
+ /* No destructor defined for create_table_args */
+ break;
+ case 16:
+#line 90 "parse.y"
+{
+ sqliteStartTable(pParse,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy298,yymsp[-2].minor.yy372,0);
+}
+#line 1961 "parse.c"
+ /* No destructor defined for TABLE */
+ break;
+ case 17:
+#line 94 "parse.y"
+{yygotominor.yy372 = 1;}
+#line 1967 "parse.c"
+ /* No destructor defined for TEMP */
+ break;
+ case 18:
+#line 95 "parse.y"
+{yygotominor.yy372 = 0;}
+#line 1973 "parse.c"
+ break;
+ case 19:
+#line 96 "parse.y"
+{
+ sqliteEndTable(pParse,&yymsp[0].minor.yy0,0);
+}
+#line 1980 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for columnlist */
+ /* No destructor defined for conslist_opt */
+ break;
+ case 20:
+#line 99 "parse.y"
+{
+ sqliteEndTable(pParse,0,yymsp[0].minor.yy179);
+ sqliteSelectDelete(yymsp[0].minor.yy179);
+}
+#line 1991 "parse.c"
+ /* No destructor defined for AS */
+ break;
+ case 21:
+ /* No destructor defined for columnlist */
+ /* No destructor defined for COMMA */
+ /* No destructor defined for column */
+ break;
+ case 22:
+ /* No destructor defined for column */
+ break;
+ case 23:
+ /* No destructor defined for columnid */
+ /* No destructor defined for type */
+ /* No destructor defined for carglist */
+ break;
+ case 24:
+#line 111 "parse.y"
+{sqliteAddColumn(pParse,&yymsp[0].minor.yy298);}
+#line 2010 "parse.c"
+ break;
+ case 25:
+#line 117 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 2015 "parse.c"
+ break;
+ case 26:
+#line 149 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 2020 "parse.c"
+ break;
+ case 27:
+#line 150 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 2025 "parse.c"
+ break;
+ case 28:
+#line 155 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 2030 "parse.c"
+ break;
+ case 29:
+#line 156 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 2035 "parse.c"
+ break;
+ case 30:
+#line 157 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 2040 "parse.c"
+ break;
+ case 31:
+ break;
+ case 32:
+#line 160 "parse.y"
+{sqliteAddColumnType(pParse,&yymsp[0].minor.yy298,&yymsp[0].minor.yy298);}
+#line 2047 "parse.c"
+ break;
+ case 33:
+#line 161 "parse.y"
+{sqliteAddColumnType(pParse,&yymsp[-3].minor.yy298,&yymsp[0].minor.yy0);}
+#line 2052 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for signed */
+ break;
+ case 34:
+#line 163 "parse.y"
+{sqliteAddColumnType(pParse,&yymsp[-5].minor.yy298,&yymsp[0].minor.yy0);}
+#line 2059 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for signed */
+ /* No destructor defined for COMMA */
+ /* No destructor defined for signed */
+ break;
+ case 35:
+#line 165 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy298;}
+#line 2068 "parse.c"
+ break;
+ case 36:
+#line 166 "parse.y"
+{yygotominor.yy298 = yymsp[-1].minor.yy298;}
+#line 2073 "parse.c"
+ /* No destructor defined for ids */
+ break;
+ case 37:
+#line 168 "parse.y"
+{ yygotominor.yy372 = atoi(yymsp[0].minor.yy0.z); }
+#line 2079 "parse.c"
+ break;
+ case 38:
+#line 169 "parse.y"
+{ yygotominor.yy372 = atoi(yymsp[0].minor.yy0.z); }
+#line 2084 "parse.c"
+ /* No destructor defined for PLUS */
+ break;
+ case 39:
+#line 170 "parse.y"
+{ yygotominor.yy372 = -atoi(yymsp[0].minor.yy0.z); }
+#line 2090 "parse.c"
+ /* No destructor defined for MINUS */
+ break;
+ case 40:
+ /* No destructor defined for carglist */
+ /* No destructor defined for carg */
+ break;
+ case 41:
+ break;
+ case 42:
+ /* No destructor defined for CONSTRAINT */
+ /* No destructor defined for nm */
+ /* No destructor defined for ccons */
+ break;
+ case 43:
+ /* No destructor defined for ccons */
+ break;
+ case 44:
+#line 175 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);}
+#line 2110 "parse.c"
+ /* No destructor defined for DEFAULT */
+ break;
+ case 45:
+#line 176 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);}
+#line 2116 "parse.c"
+ /* No destructor defined for DEFAULT */
+ break;
+ case 46:
+#line 177 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);}
+#line 2122 "parse.c"
+ /* No destructor defined for DEFAULT */
+ break;
+ case 47:
+#line 178 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);}
+#line 2128 "parse.c"
+ /* No destructor defined for DEFAULT */
+ /* No destructor defined for PLUS */
+ break;
+ case 48:
+#line 179 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,1);}
+#line 2135 "parse.c"
+ /* No destructor defined for DEFAULT */
+ /* No destructor defined for MINUS */
+ break;
+ case 49:
+#line 180 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);}
+#line 2142 "parse.c"
+ /* No destructor defined for DEFAULT */
+ break;
+ case 50:
+#line 181 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);}
+#line 2148 "parse.c"
+ /* No destructor defined for DEFAULT */
+ /* No destructor defined for PLUS */
+ break;
+ case 51:
+#line 182 "parse.y"
+{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,1);}
+#line 2155 "parse.c"
+ /* No destructor defined for DEFAULT */
+ /* No destructor defined for MINUS */
+ break;
+ case 52:
+ /* No destructor defined for DEFAULT */
+ /* No destructor defined for NULL */
+ break;
+ case 53:
+ /* No destructor defined for NULL */
+ /* No destructor defined for onconf */
+ break;
+ case 54:
+#line 189 "parse.y"
+{sqliteAddNotNull(pParse, yymsp[0].minor.yy372);}
+#line 2170 "parse.c"
+ /* No destructor defined for NOT */
+ /* No destructor defined for NULL */
+ break;
+ case 55:
+#line 190 "parse.y"
+{sqliteAddPrimaryKey(pParse,0,yymsp[0].minor.yy372);}
+#line 2177 "parse.c"
+ /* No destructor defined for PRIMARY */
+ /* No destructor defined for KEY */
+ /* No destructor defined for sortorder */
+ break;
+ case 56:
+#line 191 "parse.y"
+{sqliteCreateIndex(pParse,0,0,0,yymsp[0].minor.yy372,0,0);}
+#line 2185 "parse.c"
+ /* No destructor defined for UNIQUE */
+ break;
+ case 57:
+ /* No destructor defined for CHECK */
+ /* No destructor defined for LP */
+ yy_destructor(158,&yymsp[-2].minor);
+ /* No destructor defined for RP */
+ /* No destructor defined for onconf */
+ break;
+ case 58:
+#line 194 "parse.y"
+{sqliteCreateForeignKey(pParse,0,&yymsp[-2].minor.yy298,yymsp[-1].minor.yy320,yymsp[0].minor.yy372);}
+#line 2198 "parse.c"
+ /* No destructor defined for REFERENCES */
+ break;
+ case 59:
+#line 195 "parse.y"
+{sqliteDeferForeignKey(pParse,yymsp[0].minor.yy372);}
+#line 2204 "parse.c"
+ break;
+ case 60:
+#line 196 "parse.y"
+{
+ sqliteAddCollateType(pParse, sqliteCollateType(yymsp[0].minor.yy298.z, yymsp[0].minor.yy298.n));
+}
+#line 2211 "parse.c"
+ /* No destructor defined for COLLATE */
+ break;
+ case 61:
+#line 206 "parse.y"
+{ yygotominor.yy372 = OE_Restrict * 0x010101; }
+#line 2217 "parse.c"
+ break;
+ case 62:
+#line 207 "parse.y"
+{ yygotominor.yy372 = (yymsp[-1].minor.yy372 & yymsp[0].minor.yy407.mask) | yymsp[0].minor.yy407.value; }
+#line 2222 "parse.c"
+ break;
+ case 63:
+#line 209 "parse.y"
+{ yygotominor.yy407.value = 0; yygotominor.yy407.mask = 0x000000; }
+#line 2227 "parse.c"
+ /* No destructor defined for MATCH */
+ /* No destructor defined for nm */
+ break;
+ case 64:
+#line 210 "parse.y"
+{ yygotominor.yy407.value = yymsp[0].minor.yy372; yygotominor.yy407.mask = 0x0000ff; }
+#line 2234 "parse.c"
+ /* No destructor defined for ON */
+ /* No destructor defined for DELETE */
+ break;
+ case 65:
+#line 211 "parse.y"
+{ yygotominor.yy407.value = yymsp[0].minor.yy372<<8; yygotominor.yy407.mask = 0x00ff00; }
+#line 2241 "parse.c"
+ /* No destructor defined for ON */
+ /* No destructor defined for UPDATE */
+ break;
+ case 66:
+#line 212 "parse.y"
+{ yygotominor.yy407.value = yymsp[0].minor.yy372<<16; yygotominor.yy407.mask = 0xff0000; }
+#line 2248 "parse.c"
+ /* No destructor defined for ON */
+ /* No destructor defined for INSERT */
+ break;
+ case 67:
+#line 214 "parse.y"
+{ yygotominor.yy372 = OE_SetNull; }
+#line 2255 "parse.c"
+ /* No destructor defined for SET */
+ /* No destructor defined for NULL */
+ break;
+ case 68:
+#line 215 "parse.y"
+{ yygotominor.yy372 = OE_SetDflt; }
+#line 2262 "parse.c"
+ /* No destructor defined for SET */
+ /* No destructor defined for DEFAULT */
+ break;
+ case 69:
+#line 216 "parse.y"
+{ yygotominor.yy372 = OE_Cascade; }
+#line 2269 "parse.c"
+ /* No destructor defined for CASCADE */
+ break;
+ case 70:
+#line 217 "parse.y"
+{ yygotominor.yy372 = OE_Restrict; }
+#line 2275 "parse.c"
+ /* No destructor defined for RESTRICT */
+ break;
+ case 71:
+#line 219 "parse.y"
+{yygotominor.yy372 = yymsp[0].minor.yy372;}
+#line 2281 "parse.c"
+ /* No destructor defined for NOT */
+ /* No destructor defined for DEFERRABLE */
+ break;
+ case 72:
+#line 220 "parse.y"
+{yygotominor.yy372 = yymsp[0].minor.yy372;}
+#line 2288 "parse.c"
+ /* No destructor defined for DEFERRABLE */
+ break;
+ case 73:
+#line 222 "parse.y"
+{yygotominor.yy372 = 0;}
+#line 2294 "parse.c"
+ break;
+ case 74:
+#line 223 "parse.y"
+{yygotominor.yy372 = 1;}
+#line 2299 "parse.c"
+ /* No destructor defined for INITIALLY */
+ /* No destructor defined for DEFERRED */
+ break;
+ case 75:
+#line 224 "parse.y"
+{yygotominor.yy372 = 0;}
+#line 2306 "parse.c"
+ /* No destructor defined for INITIALLY */
+ /* No destructor defined for IMMEDIATE */
+ break;
+ case 76:
+ break;
+ case 77:
+ /* No destructor defined for COMMA */
+ /* No destructor defined for conslist */
+ break;
+ case 78:
+ /* No destructor defined for conslist */
+ /* No destructor defined for COMMA */
+ /* No destructor defined for tcons */
+ break;
+ case 79:
+ /* No destructor defined for conslist */
+ /* No destructor defined for tcons */
+ break;
+ case 80:
+ /* No destructor defined for tcons */
+ break;
+ case 81:
+ /* No destructor defined for CONSTRAINT */
+ /* No destructor defined for nm */
+ break;
+ case 82:
+#line 236 "parse.y"
+{sqliteAddPrimaryKey(pParse,yymsp[-2].minor.yy320,yymsp[0].minor.yy372);}
+#line 2335 "parse.c"
+ /* No destructor defined for PRIMARY */
+ /* No destructor defined for KEY */
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 83:
+#line 238 "parse.y"
+{sqliteCreateIndex(pParse,0,0,yymsp[-2].minor.yy320,yymsp[0].minor.yy372,0,0);}
+#line 2344 "parse.c"
+ /* No destructor defined for UNIQUE */
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 84:
+ /* No destructor defined for CHECK */
+ yy_destructor(158,&yymsp[-1].minor);
+ /* No destructor defined for onconf */
+ break;
+ case 85:
+#line 241 "parse.y"
+{
+ sqliteCreateForeignKey(pParse, yymsp[-6].minor.yy320, &yymsp[-3].minor.yy298, yymsp[-2].minor.yy320, yymsp[-1].minor.yy372);
+ sqliteDeferForeignKey(pParse, yymsp[0].minor.yy372);
+}
+#line 2360 "parse.c"
+ /* No destructor defined for FOREIGN */
+ /* No destructor defined for KEY */
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ /* No destructor defined for REFERENCES */
+ break;
+ case 86:
+#line 246 "parse.y"
+{yygotominor.yy372 = 0;}
+#line 2370 "parse.c"
+ break;
+ case 87:
+#line 247 "parse.y"
+{yygotominor.yy372 = yymsp[0].minor.yy372;}
+#line 2375 "parse.c"
+ break;
+ case 88:
+#line 255 "parse.y"
+{ yygotominor.yy372 = OE_Default; }
+#line 2380 "parse.c"
+ break;
+ case 89:
+#line 256 "parse.y"
+{ yygotominor.yy372 = yymsp[0].minor.yy372; }
+#line 2385 "parse.c"
+ /* No destructor defined for ON */
+ /* No destructor defined for CONFLICT */
+ break;
+ case 90:
+#line 257 "parse.y"
+{ yygotominor.yy372 = OE_Default; }
+#line 2392 "parse.c"
+ break;
+ case 91:
+#line 258 "parse.y"
+{ yygotominor.yy372 = yymsp[0].minor.yy372; }
+#line 2397 "parse.c"
+ /* No destructor defined for OR */
+ break;
+ case 92:
+#line 259 "parse.y"
+{ yygotominor.yy372 = OE_Rollback; }
+#line 2403 "parse.c"
+ /* No destructor defined for ROLLBACK */
+ break;
+ case 93:
+#line 260 "parse.y"
+{ yygotominor.yy372 = OE_Abort; }
+#line 2409 "parse.c"
+ /* No destructor defined for ABORT */
+ break;
+ case 94:
+#line 261 "parse.y"
+{ yygotominor.yy372 = OE_Fail; }
+#line 2415 "parse.c"
+ /* No destructor defined for FAIL */
+ break;
+ case 95:
+#line 262 "parse.y"
+{ yygotominor.yy372 = OE_Ignore; }
+#line 2421 "parse.c"
+ /* No destructor defined for IGNORE */
+ break;
+ case 96:
+#line 263 "parse.y"
+{ yygotominor.yy372 = OE_Replace; }
+#line 2427 "parse.c"
+ /* No destructor defined for REPLACE */
+ break;
+ case 97:
+#line 267 "parse.y"
+{sqliteDropTable(pParse,&yymsp[0].minor.yy298,0);}
+#line 2433 "parse.c"
+ /* No destructor defined for DROP */
+ /* No destructor defined for TABLE */
+ break;
+ case 98:
+#line 271 "parse.y"
+{
+ sqliteCreateView(pParse, &yymsp[-5].minor.yy0, &yymsp[-2].minor.yy298, yymsp[0].minor.yy179, yymsp[-4].minor.yy372);
+}
+#line 2442 "parse.c"
+ /* No destructor defined for VIEW */
+ /* No destructor defined for AS */
+ break;
+ case 99:
+#line 274 "parse.y"
+{
+ sqliteDropTable(pParse, &yymsp[0].minor.yy298, 1);
+}
+#line 2451 "parse.c"
+ /* No destructor defined for DROP */
+ /* No destructor defined for VIEW */
+ break;
+ case 100:
+#line 280 "parse.y"
+{
+ sqliteSelect(pParse, yymsp[0].minor.yy179, SRT_Callback, 0, 0, 0, 0);
+ sqliteSelectDelete(yymsp[0].minor.yy179);
+}
+#line 2461 "parse.c"
+ break;
+ case 101:
+#line 290 "parse.y"
+{yygotominor.yy179 = yymsp[0].minor.yy179;}
+#line 2466 "parse.c"
+ break;
+ case 102:
+#line 291 "parse.y"
+{
+ if( yymsp[0].minor.yy179 ){
+ yymsp[0].minor.yy179->op = yymsp[-1].minor.yy372;
+ yymsp[0].minor.yy179->pPrior = yymsp[-2].minor.yy179;
+ }
+ yygotominor.yy179 = yymsp[0].minor.yy179;
+}
+#line 2477 "parse.c"
+ break;
+ case 103:
+#line 299 "parse.y"
+{yygotominor.yy372 = TK_UNION;}
+#line 2482 "parse.c"
+ /* No destructor defined for UNION */
+ break;
+ case 104:
+#line 300 "parse.y"
+{yygotominor.yy372 = TK_ALL;}
+#line 2488 "parse.c"
+ /* No destructor defined for UNION */
+ /* No destructor defined for ALL */
+ break;
+ case 105:
+#line 301 "parse.y"
+{yygotominor.yy372 = TK_INTERSECT;}
+#line 2495 "parse.c"
+ /* No destructor defined for INTERSECT */
+ break;
+ case 106:
+#line 302 "parse.y"
+{yygotominor.yy372 = TK_EXCEPT;}
+#line 2501 "parse.c"
+ /* No destructor defined for EXCEPT */
+ break;
+ case 107:
+#line 304 "parse.y"
+{
+ yygotominor.yy179 = sqliteSelectNew(yymsp[-6].minor.yy322,yymsp[-5].minor.yy307,yymsp[-4].minor.yy242,yymsp[-3].minor.yy322,yymsp[-2].minor.yy242,yymsp[-1].minor.yy322,yymsp[-7].minor.yy372,yymsp[0].minor.yy124.limit,yymsp[0].minor.yy124.offset);
+}
+#line 2509 "parse.c"
+ /* No destructor defined for SELECT */
+ break;
+ case 108:
+#line 312 "parse.y"
+{yygotominor.yy372 = 1;}
+#line 2515 "parse.c"
+ /* No destructor defined for DISTINCT */
+ break;
+ case 109:
+#line 313 "parse.y"
+{yygotominor.yy372 = 0;}
+#line 2521 "parse.c"
+ /* No destructor defined for ALL */
+ break;
+ case 110:
+#line 314 "parse.y"
+{yygotominor.yy372 = 0;}
+#line 2527 "parse.c"
+ break;
+ case 111:
+#line 325 "parse.y"
+{yygotominor.yy322 = yymsp[-1].minor.yy322;}
+#line 2532 "parse.c"
+ /* No destructor defined for COMMA */
+ break;
+ case 112:
+#line 326 "parse.y"
+{yygotominor.yy322 = 0;}
+#line 2538 "parse.c"
+ break;
+ case 113:
+#line 327 "parse.y"
+{
+ yygotominor.yy322 = sqliteExprListAppend(yymsp[-2].minor.yy322,yymsp[-1].minor.yy242,yymsp[0].minor.yy298.n?&yymsp[0].minor.yy298:0);
+}
+#line 2545 "parse.c"
+ break;
+ case 114:
+#line 330 "parse.y"
+{
+ yygotominor.yy322 = sqliteExprListAppend(yymsp[-1].minor.yy322, sqliteExpr(TK_ALL, 0, 0, 0), 0);
+}
+#line 2552 "parse.c"
+ /* No destructor defined for STAR */
+ break;
+ case 115:
+#line 333 "parse.y"
+{
+ Expr *pRight = sqliteExpr(TK_ALL, 0, 0, 0);
+ Expr *pLeft = sqliteExpr(TK_ID, 0, 0, &yymsp[-2].minor.yy298);
+ yygotominor.yy322 = sqliteExprListAppend(yymsp[-3].minor.yy322, sqliteExpr(TK_DOT, pLeft, pRight, 0), 0);
+}
+#line 2562 "parse.c"
+ /* No destructor defined for DOT */
+ /* No destructor defined for STAR */
+ break;
+ case 116:
+#line 343 "parse.y"
+{ yygotominor.yy298 = yymsp[0].minor.yy298; }
+#line 2569 "parse.c"
+ /* No destructor defined for AS */
+ break;
+ case 117:
+#line 344 "parse.y"
+{ yygotominor.yy298 = yymsp[0].minor.yy298; }
+#line 2575 "parse.c"
+ break;
+ case 118:
+#line 345 "parse.y"
+{ yygotominor.yy298.n = 0; }
+#line 2580 "parse.c"
+ break;
+ case 119:
+#line 357 "parse.y"
+{yygotominor.yy307 = sqliteMalloc(sizeof(*yygotominor.yy307));}
+#line 2585 "parse.c"
+ break;
+ case 120:
+#line 358 "parse.y"
+{yygotominor.yy307 = yymsp[0].minor.yy307;}
+#line 2590 "parse.c"
+ /* No destructor defined for FROM */
+ break;
+ case 121:
+#line 363 "parse.y"
+{
+ yygotominor.yy307 = yymsp[-1].minor.yy307;
+ if( yygotominor.yy307 && yygotominor.yy307->nSrc>0 ) yygotominor.yy307->a[yygotominor.yy307->nSrc-1].jointype = yymsp[0].minor.yy372;
+}
+#line 2599 "parse.c"
+ break;
+ case 122:
+#line 367 "parse.y"
+{yygotominor.yy307 = 0;}
+#line 2604 "parse.c"
+ break;
+ case 123:
+#line 368 "parse.y"
+{
+ yygotominor.yy307 = sqliteSrcListAppend(yymsp[-5].minor.yy307,&yymsp[-4].minor.yy298,&yymsp[-3].minor.yy298);
+ if( yymsp[-2].minor.yy298.n ) sqliteSrcListAddAlias(yygotominor.yy307,&yymsp[-2].minor.yy298);
+ if( yymsp[-1].minor.yy242 ){
+ if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pOn = yymsp[-1].minor.yy242; }
+ else { sqliteExprDelete(yymsp[-1].minor.yy242); }
+ }
+ if( yymsp[0].minor.yy320 ){
+ if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pUsing = yymsp[0].minor.yy320; }
+ else { sqliteIdListDelete(yymsp[0].minor.yy320); }
+ }
+}
+#line 2620 "parse.c"
+ break;
+ case 124:
+#line 381 "parse.y"
+{
+ yygotominor.yy307 = sqliteSrcListAppend(yymsp[-6].minor.yy307,0,0);
+ yygotominor.yy307->a[yygotominor.yy307->nSrc-1].pSelect = yymsp[-4].minor.yy179;
+ if( yymsp[-2].minor.yy298.n ) sqliteSrcListAddAlias(yygotominor.yy307,&yymsp[-2].minor.yy298);
+ if( yymsp[-1].minor.yy242 ){
+ if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pOn = yymsp[-1].minor.yy242; }
+ else { sqliteExprDelete(yymsp[-1].minor.yy242); }
+ }
+ if( yymsp[0].minor.yy320 ){
+ if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pUsing = yymsp[0].minor.yy320; }
+ else { sqliteIdListDelete(yymsp[0].minor.yy320); }
+ }
+}
+#line 2637 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 125:
+#line 401 "parse.y"
+{yygotominor.yy179 = yymsp[0].minor.yy179;}
+#line 2644 "parse.c"
+ break;
+ case 126:
+#line 402 "parse.y"
+{
+ yygotominor.yy179 = sqliteSelectNew(0,yymsp[0].minor.yy307,0,0,0,0,0,-1,0);
+}
+#line 2651 "parse.c"
+ break;
+ case 127:
+#line 407 "parse.y"
+{yygotominor.yy298.z=0; yygotominor.yy298.n=0;}
+#line 2656 "parse.c"
+ break;
+ case 128:
+#line 408 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy298;}
+#line 2661 "parse.c"
+ /* No destructor defined for DOT */
+ break;
+ case 129:
+#line 412 "parse.y"
+{ yygotominor.yy372 = JT_INNER; }
+#line 2667 "parse.c"
+ /* No destructor defined for COMMA */
+ break;
+ case 130:
+#line 413 "parse.y"
+{ yygotominor.yy372 = JT_INNER; }
+#line 2673 "parse.c"
+ /* No destructor defined for JOIN */
+ break;
+ case 131:
+#line 414 "parse.y"
+{ yygotominor.yy372 = sqliteJoinType(pParse,&yymsp[-1].minor.yy0,0,0); }
+#line 2679 "parse.c"
+ /* No destructor defined for JOIN */
+ break;
+ case 132:
+#line 415 "parse.y"
+{ yygotominor.yy372 = sqliteJoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy298,0); }
+#line 2685 "parse.c"
+ /* No destructor defined for JOIN */
+ break;
+ case 133:
+#line 417 "parse.y"
+{ yygotominor.yy372 = sqliteJoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy298,&yymsp[-1].minor.yy298); }
+#line 2691 "parse.c"
+ /* No destructor defined for JOIN */
+ break;
+ case 134:
+#line 421 "parse.y"
+{yygotominor.yy242 = yymsp[0].minor.yy242;}
+#line 2697 "parse.c"
+ /* No destructor defined for ON */
+ break;
+ case 135:
+#line 422 "parse.y"
+{yygotominor.yy242 = 0;}
+#line 2703 "parse.c"
+ break;
+ case 136:
+#line 426 "parse.y"
+{yygotominor.yy320 = yymsp[-1].minor.yy320;}
+#line 2708 "parse.c"
+ /* No destructor defined for USING */
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 137:
+#line 427 "parse.y"
+{yygotominor.yy320 = 0;}
+#line 2716 "parse.c"
+ break;
+ case 138:
+#line 437 "parse.y"
+{yygotominor.yy322 = 0;}
+#line 2721 "parse.c"
+ break;
+ case 139:
+#line 438 "parse.y"
+{yygotominor.yy322 = yymsp[0].minor.yy322;}
+#line 2726 "parse.c"
+ /* No destructor defined for ORDER */
+ /* No destructor defined for BY */
+ break;
+ case 140:
+#line 439 "parse.y"
+{
+ yygotominor.yy322 = sqliteExprListAppend(yymsp[-4].minor.yy322,yymsp[-2].minor.yy242,0);
+ if( yygotominor.yy322 ) yygotominor.yy322->a[yygotominor.yy322->nExpr-1].sortOrder = yymsp[-1].minor.yy372+yymsp[0].minor.yy372;
+}
+#line 2736 "parse.c"
+ /* No destructor defined for COMMA */
+ break;
+ case 141:
+#line 443 "parse.y"
+{
+ yygotominor.yy322 = sqliteExprListAppend(0,yymsp[-2].minor.yy242,0);
+ if( yygotominor.yy322 ) yygotominor.yy322->a[0].sortOrder = yymsp[-1].minor.yy372+yymsp[0].minor.yy372;
+}
+#line 2745 "parse.c"
+ break;
+ case 142:
+#line 447 "parse.y"
+{yygotominor.yy242 = yymsp[0].minor.yy242;}
+#line 2750 "parse.c"
+ break;
+ case 143:
+#line 452 "parse.y"
+{yygotominor.yy372 = SQLITE_SO_ASC;}
+#line 2755 "parse.c"
+ /* No destructor defined for ASC */
+ break;
+ case 144:
+#line 453 "parse.y"
+{yygotominor.yy372 = SQLITE_SO_DESC;}
+#line 2761 "parse.c"
+ /* No destructor defined for DESC */
+ break;
+ case 145:
+#line 454 "parse.y"
+{yygotominor.yy372 = SQLITE_SO_ASC;}
+#line 2767 "parse.c"
+ break;
+ case 146:
+#line 455 "parse.y"
+{yygotominor.yy372 = SQLITE_SO_UNK;}
+#line 2772 "parse.c"
+ break;
+ case 147:
+#line 456 "parse.y"
+{yygotominor.yy372 = sqliteCollateType(yymsp[0].minor.yy298.z, yymsp[0].minor.yy298.n);}
+#line 2777 "parse.c"
+ /* No destructor defined for COLLATE */
+ break;
+ case 148:
+#line 460 "parse.y"
+{yygotominor.yy322 = 0;}
+#line 2783 "parse.c"
+ break;
+ case 149:
+#line 461 "parse.y"
+{yygotominor.yy322 = yymsp[0].minor.yy322;}
+#line 2788 "parse.c"
+ /* No destructor defined for GROUP */
+ /* No destructor defined for BY */
+ break;
+ case 150:
+#line 465 "parse.y"
+{yygotominor.yy242 = 0;}
+#line 2795 "parse.c"
+ break;
+ case 151:
+#line 466 "parse.y"
+{yygotominor.yy242 = yymsp[0].minor.yy242;}
+#line 2800 "parse.c"
+ /* No destructor defined for HAVING */
+ break;
+ case 152:
+#line 469 "parse.y"
+{yygotominor.yy124.limit = -1; yygotominor.yy124.offset = 0;}
+#line 2806 "parse.c"
+ break;
+ case 153:
+#line 470 "parse.y"
+{yygotominor.yy124.limit = yymsp[0].minor.yy372; yygotominor.yy124.offset = 0;}
+#line 2811 "parse.c"
+ /* No destructor defined for LIMIT */
+ break;
+ case 154:
+#line 472 "parse.y"
+{yygotominor.yy124.limit = yymsp[-2].minor.yy372; yygotominor.yy124.offset = yymsp[0].minor.yy372;}
+#line 2817 "parse.c"
+ /* No destructor defined for LIMIT */
+ /* No destructor defined for OFFSET */
+ break;
+ case 155:
+#line 474 "parse.y"
+{yygotominor.yy124.limit = yymsp[0].minor.yy372; yygotominor.yy124.offset = yymsp[-2].minor.yy372;}
+#line 2824 "parse.c"
+ /* No destructor defined for LIMIT */
+ /* No destructor defined for COMMA */
+ break;
+ case 156:
+#line 478 "parse.y"
+{
+ sqliteDeleteFrom(pParse, sqliteSrcListAppend(0,&yymsp[-2].minor.yy298,&yymsp[-1].minor.yy298), yymsp[0].minor.yy242);
+}
+#line 2833 "parse.c"
+ /* No destructor defined for DELETE */
+ /* No destructor defined for FROM */
+ break;
+ case 157:
+#line 485 "parse.y"
+{yygotominor.yy242 = 0;}
+#line 2840 "parse.c"
+ break;
+ case 158:
+#line 486 "parse.y"
+{yygotominor.yy242 = yymsp[0].minor.yy242;}
+#line 2845 "parse.c"
+ /* No destructor defined for WHERE */
+ break;
+ case 159:
+#line 494 "parse.y"
+{sqliteUpdate(pParse,sqliteSrcListAppend(0,&yymsp[-4].minor.yy298,&yymsp[-3].minor.yy298),yymsp[-1].minor.yy322,yymsp[0].minor.yy242,yymsp[-5].minor.yy372);}
+#line 2851 "parse.c"
+ /* No destructor defined for UPDATE */
+ /* No destructor defined for SET */
+ break;
+ case 160:
+#line 497 "parse.y"
+{yygotominor.yy322 = sqliteExprListAppend(yymsp[-4].minor.yy322,yymsp[0].minor.yy242,&yymsp[-2].minor.yy298);}
+#line 2858 "parse.c"
+ /* No destructor defined for COMMA */
+ /* No destructor defined for EQ */
+ break;
+ case 161:
+#line 498 "parse.y"
+{yygotominor.yy322 = sqliteExprListAppend(0,yymsp[0].minor.yy242,&yymsp[-2].minor.yy298);}
+#line 2865 "parse.c"
+ /* No destructor defined for EQ */
+ break;
+ case 162:
+#line 504 "parse.y"
+{sqliteInsert(pParse, sqliteSrcListAppend(0,&yymsp[-6].minor.yy298,&yymsp[-5].minor.yy298), yymsp[-1].minor.yy322, 0, yymsp[-4].minor.yy320, yymsp[-8].minor.yy372);}
+#line 2871 "parse.c"
+ /* No destructor defined for INTO */
+ /* No destructor defined for VALUES */
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 163:
+#line 506 "parse.y"
+{sqliteInsert(pParse, sqliteSrcListAppend(0,&yymsp[-3].minor.yy298,&yymsp[-2].minor.yy298), 0, yymsp[0].minor.yy179, yymsp[-1].minor.yy320, yymsp[-5].minor.yy372);}
+#line 2880 "parse.c"
+ /* No destructor defined for INTO */
+ break;
+ case 164:
+#line 509 "parse.y"
+{yygotominor.yy372 = yymsp[0].minor.yy372;}
+#line 2886 "parse.c"
+ /* No destructor defined for INSERT */
+ break;
+ case 165:
+#line 510 "parse.y"
+{yygotominor.yy372 = OE_Replace;}
+#line 2892 "parse.c"
+ /* No destructor defined for REPLACE */
+ break;
+ case 166:
+#line 516 "parse.y"
+{yygotominor.yy322 = sqliteExprListAppend(yymsp[-2].minor.yy322,yymsp[0].minor.yy242,0);}
+#line 2898 "parse.c"
+ /* No destructor defined for COMMA */
+ break;
+ case 167:
+#line 517 "parse.y"
+{yygotominor.yy322 = sqliteExprListAppend(0,yymsp[0].minor.yy242,0);}
+#line 2904 "parse.c"
+ break;
+ case 168:
+#line 524 "parse.y"
+{yygotominor.yy320 = 0;}
+#line 2909 "parse.c"
+ break;
+ case 169:
+#line 525 "parse.y"
+{yygotominor.yy320 = yymsp[-1].minor.yy320;}
+#line 2914 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 170:
+#line 526 "parse.y"
+{yygotominor.yy320 = sqliteIdListAppend(yymsp[-2].minor.yy320,&yymsp[0].minor.yy298);}
+#line 2921 "parse.c"
+ /* No destructor defined for COMMA */
+ break;
+ case 171:
+#line 527 "parse.y"
+{yygotominor.yy320 = sqliteIdListAppend(0,&yymsp[0].minor.yy298);}
+#line 2927 "parse.c"
+ break;
+ case 172:
+#line 535 "parse.y"
+{yygotominor.yy242 = yymsp[-1].minor.yy242; sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); }
+#line 2932 "parse.c"
+ break;
+ case 173:
+#line 536 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_NULL, 0, 0, &yymsp[0].minor.yy0);}
+#line 2937 "parse.c"
+ break;
+ case 174:
+#line 537 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy0);}
+#line 2942 "parse.c"
+ break;
+ case 175:
+#line 538 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy0);}
+#line 2947 "parse.c"
+ break;
+ case 176:
+#line 539 "parse.y"
+{
+ Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &yymsp[-2].minor.yy298);
+ Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy298);
+ yygotominor.yy242 = sqliteExpr(TK_DOT, temp1, temp2, 0);
+}
+#line 2956 "parse.c"
+ /* No destructor defined for DOT */
+ break;
+ case 177:
+#line 544 "parse.y"
+{
+ Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &yymsp[-4].minor.yy298);
+ Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &yymsp[-2].minor.yy298);
+ Expr *temp3 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy298);
+ Expr *temp4 = sqliteExpr(TK_DOT, temp2, temp3, 0);
+ yygotominor.yy242 = sqliteExpr(TK_DOT, temp1, temp4, 0);
+}
+#line 2968 "parse.c"
+ /* No destructor defined for DOT */
+ /* No destructor defined for DOT */
+ break;
+ case 178:
+#line 551 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_INTEGER, 0, 0, &yymsp[0].minor.yy0);}
+#line 2975 "parse.c"
+ break;
+ case 179:
+#line 552 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_FLOAT, 0, 0, &yymsp[0].minor.yy0);}
+#line 2980 "parse.c"
+ break;
+ case 180:
+#line 553 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_STRING, 0, 0, &yymsp[0].minor.yy0);}
+#line 2985 "parse.c"
+ break;
+ case 181:
+#line 554 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_VARIABLE, 0, 0, &yymsp[0].minor.yy0);
+ if( yygotominor.yy242 ) yygotominor.yy242->iTable = ++pParse->nVar;
+}
+#line 2993 "parse.c"
+ break;
+ case 182:
+#line 558 "parse.y"
+{
+ yygotominor.yy242 = sqliteExprFunction(yymsp[-1].minor.yy322, &yymsp[-3].minor.yy0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0);
+}
+#line 3001 "parse.c"
+ /* No destructor defined for LP */
+ break;
+ case 183:
+#line 562 "parse.y"
+{
+ yygotominor.yy242 = sqliteExprFunction(0, &yymsp[-3].minor.yy0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0);
+}
+#line 3010 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for STAR */
+ break;
+ case 184:
+#line 566 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_AND, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3017 "parse.c"
+ /* No destructor defined for AND */
+ break;
+ case 185:
+#line 567 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_OR, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3023 "parse.c"
+ /* No destructor defined for OR */
+ break;
+ case 186:
+#line 568 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_LT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3029 "parse.c"
+ /* No destructor defined for LT */
+ break;
+ case 187:
+#line 569 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_GT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3035 "parse.c"
+ /* No destructor defined for GT */
+ break;
+ case 188:
+#line 570 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_LE, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3041 "parse.c"
+ /* No destructor defined for LE */
+ break;
+ case 189:
+#line 571 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_GE, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3047 "parse.c"
+ /* No destructor defined for GE */
+ break;
+ case 190:
+#line 572 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_NE, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3053 "parse.c"
+ /* No destructor defined for NE */
+ break;
+ case 191:
+#line 573 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_EQ, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3059 "parse.c"
+ /* No destructor defined for EQ */
+ break;
+ case 192:
+#line 574 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_BITAND, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3065 "parse.c"
+ /* No destructor defined for BITAND */
+ break;
+ case 193:
+#line 575 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_BITOR, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3071 "parse.c"
+ /* No destructor defined for BITOR */
+ break;
+ case 194:
+#line 576 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_LSHIFT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3077 "parse.c"
+ /* No destructor defined for LSHIFT */
+ break;
+ case 195:
+#line 577 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_RSHIFT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3083 "parse.c"
+ /* No destructor defined for RSHIFT */
+ break;
+ case 196:
+#line 578 "parse.y"
+{
+ ExprList *pList = sqliteExprListAppend(0, yymsp[0].minor.yy242, 0);
+ pList = sqliteExprListAppend(pList, yymsp[-2].minor.yy242, 0);
+ yygotominor.yy242 = sqliteExprFunction(pList, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->op = yymsp[-1].minor.yy372;
+ sqliteExprSpan(yygotominor.yy242, &yymsp[-2].minor.yy242->span, &yymsp[0].minor.yy242->span);
+}
+#line 3095 "parse.c"
+ break;
+ case 197:
+#line 585 "parse.y"
+{
+ ExprList *pList = sqliteExprListAppend(0, yymsp[0].minor.yy242, 0);
+ pList = sqliteExprListAppend(pList, yymsp[-3].minor.yy242, 0);
+ yygotominor.yy242 = sqliteExprFunction(pList, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->op = yymsp[-1].minor.yy372;
+ yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy242->span,&yymsp[0].minor.yy242->span);
+}
+#line 3107 "parse.c"
+ /* No destructor defined for NOT */
+ break;
+ case 198:
+#line 594 "parse.y"
+{yygotominor.yy372 = TK_LIKE;}
+#line 3113 "parse.c"
+ /* No destructor defined for LIKE */
+ break;
+ case 199:
+#line 595 "parse.y"
+{yygotominor.yy372 = TK_GLOB;}
+#line 3119 "parse.c"
+ /* No destructor defined for GLOB */
+ break;
+ case 200:
+#line 596 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_PLUS, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3125 "parse.c"
+ /* No destructor defined for PLUS */
+ break;
+ case 201:
+#line 597 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_MINUS, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3131 "parse.c"
+ /* No destructor defined for MINUS */
+ break;
+ case 202:
+#line 598 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_STAR, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3137 "parse.c"
+ /* No destructor defined for STAR */
+ break;
+ case 203:
+#line 599 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_SLASH, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3143 "parse.c"
+ /* No destructor defined for SLASH */
+ break;
+ case 204:
+#line 600 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_REM, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3149 "parse.c"
+ /* No destructor defined for REM */
+ break;
+ case 205:
+#line 601 "parse.y"
+{yygotominor.yy242 = sqliteExpr(TK_CONCAT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);}
+#line 3155 "parse.c"
+ /* No destructor defined for CONCAT */
+ break;
+ case 206:
+#line 602 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_ISNULL, yymsp[-1].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3164 "parse.c"
+ break;
+ case 207:
+#line 606 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_ISNULL, yymsp[-2].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3172 "parse.c"
+ /* No destructor defined for IS */
+ break;
+ case 208:
+#line 610 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_NOTNULL, yymsp[-1].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3181 "parse.c"
+ break;
+ case 209:
+#line 614 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_NOTNULL, yymsp[-2].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3189 "parse.c"
+ /* No destructor defined for NOT */
+ break;
+ case 210:
+#line 618 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_NOTNULL, yymsp[-3].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3198 "parse.c"
+ /* No destructor defined for IS */
+ /* No destructor defined for NOT */
+ break;
+ case 211:
+#line 622 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_NOT, yymsp[0].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span);
+}
+#line 3208 "parse.c"
+ break;
+ case 212:
+#line 626 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_BITNOT, yymsp[0].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span);
+}
+#line 3216 "parse.c"
+ break;
+ case 213:
+#line 630 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_UMINUS, yymsp[0].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span);
+}
+#line 3224 "parse.c"
+ break;
+ case 214:
+#line 634 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_UPLUS, yymsp[0].minor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span);
+}
+#line 3232 "parse.c"
+ break;
+ case 215:
+#line 638 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_SELECT, 0, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pSelect = yymsp[-1].minor.yy179;
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);
+}
+#line 3241 "parse.c"
+ break;
+ case 216:
+#line 643 "parse.y"
+{
+ ExprList *pList = sqliteExprListAppend(0, yymsp[-2].minor.yy242, 0);
+ pList = sqliteExprListAppend(pList, yymsp[0].minor.yy242, 0);
+ yygotominor.yy242 = sqliteExpr(TK_BETWEEN, yymsp[-4].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pList = pList;
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,&yymsp[0].minor.yy242->span);
+}
+#line 3252 "parse.c"
+ /* No destructor defined for BETWEEN */
+ /* No destructor defined for AND */
+ break;
+ case 217:
+#line 650 "parse.y"
+{
+ ExprList *pList = sqliteExprListAppend(0, yymsp[-2].minor.yy242, 0);
+ pList = sqliteExprListAppend(pList, yymsp[0].minor.yy242, 0);
+ yygotominor.yy242 = sqliteExpr(TK_BETWEEN, yymsp[-5].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pList = pList;
+ yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-5].minor.yy242->span,&yymsp[0].minor.yy242->span);
+}
+#line 3266 "parse.c"
+ /* No destructor defined for NOT */
+ /* No destructor defined for BETWEEN */
+ /* No destructor defined for AND */
+ break;
+ case 218:
+#line 658 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-4].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pList = yymsp[-1].minor.yy322;
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3278 "parse.c"
+ /* No destructor defined for IN */
+ /* No destructor defined for LP */
+ break;
+ case 219:
+#line 663 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-4].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pSelect = yymsp[-1].minor.yy179;
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3289 "parse.c"
+ /* No destructor defined for IN */
+ /* No destructor defined for LP */
+ break;
+ case 220:
+#line 668 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-5].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pList = yymsp[-1].minor.yy322;
+ yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-5].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3301 "parse.c"
+ /* No destructor defined for NOT */
+ /* No destructor defined for IN */
+ /* No destructor defined for LP */
+ break;
+ case 221:
+#line 674 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-5].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pSelect = yymsp[-1].minor.yy179;
+ yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-5].minor.yy242->span,&yymsp[0].minor.yy0);
+}
+#line 3314 "parse.c"
+ /* No destructor defined for NOT */
+ /* No destructor defined for IN */
+ /* No destructor defined for LP */
+ break;
+ case 222:
+#line 680 "parse.y"
+{
+ SrcList *pSrc = sqliteSrcListAppend(0, &yymsp[-1].minor.yy298, &yymsp[0].minor.yy298);
+ yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-3].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pSelect = sqliteSelectNew(0,pSrc,0,0,0,0,0,-1,0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy242->span,yymsp[0].minor.yy298.z?&yymsp[0].minor.yy298:&yymsp[-1].minor.yy298);
+}
+#line 3327 "parse.c"
+ /* No destructor defined for IN */
+ break;
+ case 223:
+#line 686 "parse.y"
+{
+ SrcList *pSrc = sqliteSrcListAppend(0, &yymsp[-1].minor.yy298, &yymsp[0].minor.yy298);
+ yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-4].minor.yy242, 0, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pSelect = sqliteSelectNew(0,pSrc,0,0,0,0,0,-1,0);
+ yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0);
+ sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,yymsp[0].minor.yy298.z?&yymsp[0].minor.yy298:&yymsp[-1].minor.yy298);
+}
+#line 3339 "parse.c"
+ /* No destructor defined for NOT */
+ /* No destructor defined for IN */
+ break;
+ case 224:
+#line 696 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_CASE, yymsp[-3].minor.yy242, yymsp[-1].minor.yy242, 0);
+ if( yygotominor.yy242 ) yygotominor.yy242->pList = yymsp[-2].minor.yy322;
+ sqliteExprSpan(yygotominor.yy242, &yymsp[-4].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 3350 "parse.c"
+ break;
+ case 225:
+#line 703 "parse.y"
+{
+ yygotominor.yy322 = sqliteExprListAppend(yymsp[-4].minor.yy322, yymsp[-2].minor.yy242, 0);
+ yygotominor.yy322 = sqliteExprListAppend(yygotominor.yy322, yymsp[0].minor.yy242, 0);
+}
+#line 3358 "parse.c"
+ /* No destructor defined for WHEN */
+ /* No destructor defined for THEN */
+ break;
+ case 226:
+#line 707 "parse.y"
+{
+ yygotominor.yy322 = sqliteExprListAppend(0, yymsp[-2].minor.yy242, 0);
+ yygotominor.yy322 = sqliteExprListAppend(yygotominor.yy322, yymsp[0].minor.yy242, 0);
+}
+#line 3368 "parse.c"
+ /* No destructor defined for WHEN */
+ /* No destructor defined for THEN */
+ break;
+ case 227:
+#line 712 "parse.y"
+{yygotominor.yy242 = yymsp[0].minor.yy242;}
+#line 3375 "parse.c"
+ /* No destructor defined for ELSE */
+ break;
+ case 228:
+#line 713 "parse.y"
+{yygotominor.yy242 = 0;}
+#line 3381 "parse.c"
+ break;
+ case 229:
+#line 715 "parse.y"
+{yygotominor.yy242 = yymsp[0].minor.yy242;}
+#line 3386 "parse.c"
+ break;
+ case 230:
+#line 716 "parse.y"
+{yygotominor.yy242 = 0;}
+#line 3391 "parse.c"
+ break;
+ case 231:
+#line 724 "parse.y"
+{yygotominor.yy322 = sqliteExprListAppend(yymsp[-2].minor.yy322,yymsp[0].minor.yy242,0);}
+#line 3396 "parse.c"
+ /* No destructor defined for COMMA */
+ break;
+ case 232:
+#line 725 "parse.y"
+{yygotominor.yy322 = sqliteExprListAppend(0,yymsp[0].minor.yy242,0);}
+#line 3402 "parse.c"
+ break;
+ case 233:
+#line 726 "parse.y"
+{yygotominor.yy242 = yymsp[0].minor.yy242;}
+#line 3407 "parse.c"
+ break;
+ case 234:
+#line 727 "parse.y"
+{yygotominor.yy242 = 0;}
+#line 3412 "parse.c"
+ break;
+ case 235:
+#line 732 "parse.y"
+{
+ SrcList *pSrc = sqliteSrcListAppend(0, &yymsp[-5].minor.yy298, &yymsp[-4].minor.yy298);
+ if( yymsp[-9].minor.yy372!=OE_None ) yymsp[-9].minor.yy372 = yymsp[0].minor.yy372;
+ if( yymsp[-9].minor.yy372==OE_Default) yymsp[-9].minor.yy372 = OE_Abort;
+ sqliteCreateIndex(pParse, &yymsp[-7].minor.yy298, pSrc, yymsp[-2].minor.yy320, yymsp[-9].minor.yy372, &yymsp[-10].minor.yy0, &yymsp[-1].minor.yy0);
+}
+#line 3422 "parse.c"
+ /* No destructor defined for INDEX */
+ /* No destructor defined for ON */
+ /* No destructor defined for LP */
+ break;
+ case 236:
+#line 740 "parse.y"
+{ yygotominor.yy372 = OE_Abort; }
+#line 3430 "parse.c"
+ /* No destructor defined for UNIQUE */
+ break;
+ case 237:
+#line 741 "parse.y"
+{ yygotominor.yy372 = OE_None; }
+#line 3436 "parse.c"
+ break;
+ case 238:
+#line 749 "parse.y"
+{yygotominor.yy320 = 0;}
+#line 3441 "parse.c"
+ break;
+ case 239:
+#line 750 "parse.y"
+{yygotominor.yy320 = yymsp[-1].minor.yy320;}
+#line 3446 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 240:
+#line 751 "parse.y"
+{yygotominor.yy320 = sqliteIdListAppend(yymsp[-2].minor.yy320,&yymsp[0].minor.yy298);}
+#line 3453 "parse.c"
+ /* No destructor defined for COMMA */
+ break;
+ case 241:
+#line 752 "parse.y"
+{yygotominor.yy320 = sqliteIdListAppend(0,&yymsp[0].minor.yy298);}
+#line 3459 "parse.c"
+ break;
+ case 242:
+#line 753 "parse.y"
+{yygotominor.yy298 = yymsp[-1].minor.yy298;}
+#line 3464 "parse.c"
+ /* No destructor defined for sortorder */
+ break;
+ case 243:
+#line 758 "parse.y"
+{
+ sqliteDropIndex(pParse, sqliteSrcListAppend(0,&yymsp[-1].minor.yy298,&yymsp[0].minor.yy298));
+}
+#line 3472 "parse.c"
+ /* No destructor defined for DROP */
+ /* No destructor defined for INDEX */
+ break;
+ case 244:
+#line 766 "parse.y"
+{sqliteCopy(pParse,sqliteSrcListAppend(0,&yymsp[-6].minor.yy298,&yymsp[-5].minor.yy298),&yymsp[-3].minor.yy298,&yymsp[0].minor.yy0,yymsp[-7].minor.yy372);}
+#line 3479 "parse.c"
+ /* No destructor defined for COPY */
+ /* No destructor defined for FROM */
+ /* No destructor defined for USING */
+ /* No destructor defined for DELIMITERS */
+ break;
+ case 245:
+#line 768 "parse.y"
+{sqliteCopy(pParse,sqliteSrcListAppend(0,&yymsp[-3].minor.yy298,&yymsp[-2].minor.yy298),&yymsp[0].minor.yy298,0,yymsp[-4].minor.yy372);}
+#line 3488 "parse.c"
+ /* No destructor defined for COPY */
+ /* No destructor defined for FROM */
+ break;
+ case 246:
+#line 772 "parse.y"
+{sqliteVacuum(pParse,0);}
+#line 3495 "parse.c"
+ /* No destructor defined for VACUUM */
+ break;
+ case 247:
+#line 773 "parse.y"
+{sqliteVacuum(pParse,&yymsp[0].minor.yy298);}
+#line 3501 "parse.c"
+ /* No destructor defined for VACUUM */
+ break;
+ case 248:
+#line 777 "parse.y"
+{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy298,0);}
+#line 3507 "parse.c"
+ /* No destructor defined for PRAGMA */
+ /* No destructor defined for EQ */
+ break;
+ case 249:
+#line 778 "parse.y"
+{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy0,0);}
+#line 3514 "parse.c"
+ /* No destructor defined for PRAGMA */
+ /* No destructor defined for EQ */
+ break;
+ case 250:
+#line 779 "parse.y"
+{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy298,0);}
+#line 3521 "parse.c"
+ /* No destructor defined for PRAGMA */
+ /* No destructor defined for EQ */
+ break;
+ case 251:
+#line 780 "parse.y"
+{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy298,1);}
+#line 3528 "parse.c"
+ /* No destructor defined for PRAGMA */
+ /* No destructor defined for EQ */
+ break;
+ case 252:
+#line 781 "parse.y"
+{sqlitePragma(pParse,&yymsp[-3].minor.yy298,&yymsp[-1].minor.yy298,0);}
+#line 3535 "parse.c"
+ /* No destructor defined for PRAGMA */
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 253:
+#line 782 "parse.y"
+{sqlitePragma(pParse,&yymsp[0].minor.yy298,&yymsp[0].minor.yy298,0);}
+#line 3543 "parse.c"
+ /* No destructor defined for PRAGMA */
+ break;
+ case 254:
+#line 783 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy298;}
+#line 3549 "parse.c"
+ /* No destructor defined for plus_opt */
+ break;
+ case 255:
+#line 784 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy298;}
+#line 3555 "parse.c"
+ /* No destructor defined for MINUS */
+ break;
+ case 256:
+#line 785 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 3561 "parse.c"
+ break;
+ case 257:
+#line 786 "parse.y"
+{yygotominor.yy298 = yymsp[0].minor.yy0;}
+#line 3566 "parse.c"
+ break;
+ case 258:
+ /* No destructor defined for PLUS */
+ break;
+ case 259:
+ break;
+ case 260:
+#line 792 "parse.y"
+{
+ Token all;
+ all.z = yymsp[-4].minor.yy0.z;
+ all.n = (yymsp[0].minor.yy0.z - yymsp[-4].minor.yy0.z) + yymsp[0].minor.yy0.n;
+ sqliteFinishTrigger(pParse, yymsp[-1].minor.yy19, &all);
+}
+#line 3581 "parse.c"
+ /* No destructor defined for trigger_decl */
+ /* No destructor defined for BEGIN */
+ break;
+ case 261:
+#line 800 "parse.y"
+{
+ SrcList *pTab = sqliteSrcListAppend(0, &yymsp[-3].minor.yy298, &yymsp[-2].minor.yy298);
+ sqliteBeginTrigger(pParse, &yymsp[-7].minor.yy298, yymsp[-6].minor.yy372, yymsp[-5].minor.yy290.a, yymsp[-5].minor.yy290.b, pTab, yymsp[-1].minor.yy372, yymsp[0].minor.yy182, yymsp[-9].minor.yy372);
+}
+#line 3591 "parse.c"
+ /* No destructor defined for TRIGGER */
+ /* No destructor defined for ON */
+ break;
+ case 262:
+#line 806 "parse.y"
+{ yygotominor.yy372 = TK_BEFORE; }
+#line 3598 "parse.c"
+ /* No destructor defined for BEFORE */
+ break;
+ case 263:
+#line 807 "parse.y"
+{ yygotominor.yy372 = TK_AFTER; }
+#line 3604 "parse.c"
+ /* No destructor defined for AFTER */
+ break;
+ case 264:
+#line 808 "parse.y"
+{ yygotominor.yy372 = TK_INSTEAD;}
+#line 3610 "parse.c"
+ /* No destructor defined for INSTEAD */
+ /* No destructor defined for OF */
+ break;
+ case 265:
+#line 809 "parse.y"
+{ yygotominor.yy372 = TK_BEFORE; }
+#line 3617 "parse.c"
+ break;
+ case 266:
+#line 813 "parse.y"
+{ yygotominor.yy290.a = TK_DELETE; yygotominor.yy290.b = 0; }
+#line 3622 "parse.c"
+ /* No destructor defined for DELETE */
+ break;
+ case 267:
+#line 814 "parse.y"
+{ yygotominor.yy290.a = TK_INSERT; yygotominor.yy290.b = 0; }
+#line 3628 "parse.c"
+ /* No destructor defined for INSERT */
+ break;
+ case 268:
+#line 815 "parse.y"
+{ yygotominor.yy290.a = TK_UPDATE; yygotominor.yy290.b = 0;}
+#line 3634 "parse.c"
+ /* No destructor defined for UPDATE */
+ break;
+ case 269:
+#line 816 "parse.y"
+{yygotominor.yy290.a = TK_UPDATE; yygotominor.yy290.b = yymsp[0].minor.yy320; }
+#line 3640 "parse.c"
+ /* No destructor defined for UPDATE */
+ /* No destructor defined for OF */
+ break;
+ case 270:
+#line 819 "parse.y"
+{ yygotominor.yy372 = TK_ROW; }
+#line 3647 "parse.c"
+ break;
+ case 271:
+#line 820 "parse.y"
+{ yygotominor.yy372 = TK_ROW; }
+#line 3652 "parse.c"
+ /* No destructor defined for FOR */
+ /* No destructor defined for EACH */
+ /* No destructor defined for ROW */
+ break;
+ case 272:
+#line 821 "parse.y"
+{ yygotominor.yy372 = TK_STATEMENT; }
+#line 3660 "parse.c"
+ /* No destructor defined for FOR */
+ /* No destructor defined for EACH */
+ /* No destructor defined for STATEMENT */
+ break;
+ case 273:
+#line 824 "parse.y"
+{ yygotominor.yy182 = 0; }
+#line 3668 "parse.c"
+ break;
+ case 274:
+#line 825 "parse.y"
+{ yygotominor.yy182 = yymsp[0].minor.yy242; }
+#line 3673 "parse.c"
+ /* No destructor defined for WHEN */
+ break;
+ case 275:
+#line 829 "parse.y"
+{
+ yymsp[-2].minor.yy19->pNext = yymsp[0].minor.yy19;
+ yygotominor.yy19 = yymsp[-2].minor.yy19;
+}
+#line 3682 "parse.c"
+ /* No destructor defined for SEMI */
+ break;
+ case 276:
+#line 833 "parse.y"
+{ yygotominor.yy19 = 0; }
+#line 3688 "parse.c"
+ break;
+ case 277:
+#line 839 "parse.y"
+{ yygotominor.yy19 = sqliteTriggerUpdateStep(&yymsp[-3].minor.yy298, yymsp[-1].minor.yy322, yymsp[0].minor.yy242, yymsp[-4].minor.yy372); }
+#line 3693 "parse.c"
+ /* No destructor defined for UPDATE */
+ /* No destructor defined for SET */
+ break;
+ case 278:
+#line 844 "parse.y"
+{yygotominor.yy19 = sqliteTriggerInsertStep(&yymsp[-5].minor.yy298, yymsp[-4].minor.yy320, yymsp[-1].minor.yy322, 0, yymsp[-7].minor.yy372);}
+#line 3700 "parse.c"
+ /* No destructor defined for INTO */
+ /* No destructor defined for VALUES */
+ /* No destructor defined for LP */
+ /* No destructor defined for RP */
+ break;
+ case 279:
+#line 847 "parse.y"
+{yygotominor.yy19 = sqliteTriggerInsertStep(&yymsp[-2].minor.yy298, yymsp[-1].minor.yy320, 0, yymsp[0].minor.yy179, yymsp[-4].minor.yy372);}
+#line 3709 "parse.c"
+ /* No destructor defined for INTO */
+ break;
+ case 280:
+#line 851 "parse.y"
+{yygotominor.yy19 = sqliteTriggerDeleteStep(&yymsp[-1].minor.yy298, yymsp[0].minor.yy242);}
+#line 3715 "parse.c"
+ /* No destructor defined for DELETE */
+ /* No destructor defined for FROM */
+ break;
+ case 281:
+#line 854 "parse.y"
+{yygotominor.yy19 = sqliteTriggerSelectStep(yymsp[0].minor.yy179); }
+#line 3722 "parse.c"
+ break;
+ case 282:
+#line 857 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, 0);
+ yygotominor.yy242->iColumn = OE_Ignore;
+ sqliteExprSpan(yygotominor.yy242, &yymsp[-3].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 3731 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for IGNORE */
+ break;
+ case 283:
+#line 862 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy298);
+ yygotominor.yy242->iColumn = OE_Rollback;
+ sqliteExprSpan(yygotominor.yy242, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 3742 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for ROLLBACK */
+ /* No destructor defined for COMMA */
+ break;
+ case 284:
+#line 867 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy298);
+ yygotominor.yy242->iColumn = OE_Abort;
+ sqliteExprSpan(yygotominor.yy242, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 3754 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for ABORT */
+ /* No destructor defined for COMMA */
+ break;
+ case 285:
+#line 872 "parse.y"
+{
+ yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy298);
+ yygotominor.yy242->iColumn = OE_Fail;
+ sqliteExprSpan(yygotominor.yy242, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 3766 "parse.c"
+ /* No destructor defined for LP */
+ /* No destructor defined for FAIL */
+ /* No destructor defined for COMMA */
+ break;
+ case 286:
+#line 879 "parse.y"
+{
+ sqliteDropTrigger(pParse,sqliteSrcListAppend(0,&yymsp[-1].minor.yy298,&yymsp[0].minor.yy298));
+}
+#line 3776 "parse.c"
+ /* No destructor defined for DROP */
+ /* No destructor defined for TRIGGER */
+ break;
+ case 287:
+#line 884 "parse.y"
+{
+ sqliteAttach(pParse, &yymsp[-3].minor.yy298, &yymsp[-1].minor.yy298, &yymsp[0].minor.yy298);
+}
+#line 3785 "parse.c"
+ /* No destructor defined for ATTACH */
+ /* No destructor defined for database_kw_opt */
+ /* No destructor defined for AS */
+ break;
+ case 288:
+#line 888 "parse.y"
+{ yygotominor.yy298 = yymsp[0].minor.yy298; }
+#line 3793 "parse.c"
+ /* No destructor defined for USING */
+ break;
+ case 289:
+#line 889 "parse.y"
+{ yygotominor.yy298.z = 0; yygotominor.yy298.n = 0; }
+#line 3799 "parse.c"
+ break;
+ case 290:
+ /* No destructor defined for DATABASE */
+ break;
+ case 291:
+ break;
+ case 292:
+#line 895 "parse.y"
+{
+ sqliteDetach(pParse, &yymsp[0].minor.yy298);
+}
+#line 3811 "parse.c"
+ /* No destructor defined for DETACH */
+ /* No destructor defined for database_kw_opt */
+ break;
+ };
+ yygoto = yyRuleInfo[yyruleno].lhs;
+ yysize = yyRuleInfo[yyruleno].nrhs;
+ yypParser->yyidx -= yysize;
+ yyact = yy_find_reduce_action(yypParser,yygoto);
+ if( yyact < YYNSTATE ){
+ yy_shift(yypParser,yyact,yygoto,&yygotominor);
+ }else if( yyact == YYNSTATE + YYNRULE + 1 ){
+ yy_accept(yypParser);
+ }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+static void yy_parse_failed(
+ yyParser *yypParser /* The parser */
+){
+ sqliteParserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser fails */
+ sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+ yyParser *yypParser, /* The parser */
+ int yymajor, /* The major type of the error token */
+ YYMINORTYPE yyminor /* The minor type of the error token */
+){
+ sqliteParserARG_FETCH;
+#define TOKEN (yyminor.yy0)
+#line 23 "parse.y"
+
+ if( pParse->zErrMsg==0 ){
+ if( TOKEN.z[0] ){
+ sqliteErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
+ }else{
+ sqliteErrorMsg(pParse, "incomplete SQL statement");
+ }
+ }
+
+#line 3865 "parse.c"
+ sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ sqliteParserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser accepts */
+ sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "sqliteParserAlloc" which describes the current state of the parser.
+** The second argument is the major token number. The third is
+** the minor token. The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void sqliteParser(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ sqliteParserTOKENTYPE yyminor /* The value for the token */
+ sqliteParserARG_PDECL /* Optional %extra_argument parameter */
+){
+ YYMINORTYPE yyminorunion;
+ int yyact; /* The parser action. */
+ int yyendofinput; /* True if we are at the end of input */
+ int yyerrorhit = 0; /* True if yymajor has invoked an error */
+ yyParser *yypParser; /* The parser */
+
+ /* (re)initialize the parser, if necessary */
+ yypParser = (yyParser*)yyp;
+ if( yypParser->yyidx<0 ){
+ if( yymajor==0 ) return;
+ yypParser->yyidx = 0;
+ yypParser->yyerrcnt = -1;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+ }
+ yyminorunion.yy0 = yyminor;
+ yyendofinput = (yymajor==0);
+ sqliteParserARG_STORE;
+
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+
+ do{
+ yyact = yy_find_shift_action(yypParser,yymajor);
+ if( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ if( yyendofinput && yypParser->yyidx>=0 ){
+ yymajor = 0;
+ }else{
+ yymajor = YYNOCODE;
+ }
+ }else if( yyact < YYNSTATE + YYNRULE ){
+ yy_reduce(yypParser,yyact-YYNSTATE);
+ }else if( yyact == YY_ERROR_ACTION ){
+ int yymx;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+ }
+#endif
+#ifdef YYERRORSYMBOL
+ /* A syntax error has occurred.
+ ** The response to an error depends upon whether or not the
+ ** grammar defines an error token "ERROR".
+ **
+ ** This is what we do if the grammar does define ERROR:
+ **
+ ** * Call the %syntax_error function.
+ **
+ ** * Begin popping the stack until we enter a state where
+ ** it is legal to shift the error symbol, then shift
+ ** the error symbol.
+ **
+ ** * Set the error count to three.
+ **
+ ** * Begin accepting and shifting new tokens. No new error
+ ** processing will occur until three tokens have been
+ ** shifted successfully.
+ **
+ */
+ if( yypParser->yyerrcnt<0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yymx = yypParser->yystack[yypParser->yyidx].major;
+ if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+ yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+ yy_destructor(yymajor,&yyminorunion);
+ yymajor = YYNOCODE;
+ }else{
+ while(
+ yypParser->yyidx >= 0 &&
+ yymx != YYERRORSYMBOL &&
+ (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE
+ ){
+ yy_pop_parser_stack(yypParser);
+ }
+ if( yypParser->yyidx < 0 || yymajor==0 ){
+ yy_destructor(yymajor,&yyminorunion);
+ yy_parse_failed(yypParser);
+ yymajor = YYNOCODE;
+ }else if( yymx!=YYERRORSYMBOL ){
+ YYMINORTYPE u2;
+ u2.YYERRSYMDT = 0;
+ yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+ }
+ }
+ yypParser->yyerrcnt = 3;
+ yyerrorhit = 1;
+#else /* YYERRORSYMBOL is not defined */
+ /* This is what we do if the grammar does not define ERROR:
+ **
+ ** * Report an error message, and throw away the input token.
+ **
+ ** * If the input token is $, then fail the parse.
+ **
+ ** As before, subsequent error messages are suppressed until
+ ** three input tokens have been successfully shifted.
+ */
+ if( yypParser->yyerrcnt<=0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yypParser->yyerrcnt = 3;
+ yy_destructor(yymajor,&yyminorunion);
+ if( yyendofinput ){
+ yy_parse_failed(yypParser);
+ }
+ yymajor = YYNOCODE;
+#endif
+ }else{
+ yy_accept(yypParser);
+ yymajor = YYNOCODE;
+ }
+ }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+ return;
+}
diff --git a/src/libs/sqlite2/parse.h b/src/libs/sqlite2/parse.h
new file mode 100644
index 00000000..188a336c
--- /dev/null
+++ b/src/libs/sqlite2/parse.h
@@ -0,0 +1,130 @@
+#define TK_END_OF_FILE 1
+#define TK_ILLEGAL 2
+#define TK_SPACE 3
+#define TK_UNCLOSED_STRING 4
+#define TK_COMMENT 5
+#define TK_FUNCTION 6
+#define TK_COLUMN 7
+#define TK_AGG_FUNCTION 8
+#define TK_SEMI 9
+#define TK_EXPLAIN 10
+#define TK_BEGIN 11
+#define TK_TRANSACTION 12
+#define TK_COMMIT 13
+#define TK_END 14
+#define TK_ROLLBACK 15
+#define TK_CREATE 16
+#define TK_TABLE 17
+#define TK_TEMP 18
+#define TK_LP 19
+#define TK_RP 20
+#define TK_AS 21
+#define TK_COMMA 22
+#define TK_ID 23
+#define TK_ABORT 24
+#define TK_AFTER 25
+#define TK_ASC 26
+#define TK_ATTACH 27
+#define TK_BEFORE 28
+#define TK_CASCADE 29
+#define TK_CLUSTER 30
+#define TK_CONFLICT 31
+#define TK_COPY 32
+#define TK_DATABASE 33
+#define TK_DEFERRED 34
+#define TK_DELIMITERS 35
+#define TK_DESC 36
+#define TK_DETACH 37
+#define TK_EACH 38
+#define TK_FAIL 39
+#define TK_FOR 40
+#define TK_GLOB 41
+#define TK_IGNORE 42
+#define TK_IMMEDIATE 43
+#define TK_INITIALLY 44
+#define TK_INSTEAD 45
+#define TK_LIKE 46
+#define TK_MATCH 47
+#define TK_KEY 48
+#define TK_OF 49
+#define TK_OFFSET 50
+#define TK_PRAGMA 51
+#define TK_RAISE 52
+#define TK_REPLACE 53
+#define TK_RESTRICT 54
+#define TK_ROW 55
+#define TK_STATEMENT 56
+#define TK_TRIGGER 57
+#define TK_VACUUM 58
+#define TK_VIEW 59
+#define TK_OR 60
+#define TK_AND 61
+#define TK_NOT 62
+#define TK_EQ 63
+#define TK_NE 64
+#define TK_ISNULL 65
+#define TK_NOTNULL 66
+#define TK_IS 67
+#define TK_BETWEEN 68
+#define TK_IN 69
+#define TK_GT 70
+#define TK_GE 71
+#define TK_LT 72
+#define TK_LE 73
+#define TK_BITAND 74
+#define TK_BITOR 75
+#define TK_LSHIFT 76
+#define TK_RSHIFT 77
+#define TK_PLUS 78
+#define TK_MINUS 79
+#define TK_STAR 80
+#define TK_SLASH 81
+#define TK_REM 82
+#define TK_CONCAT 83
+#define TK_UMINUS 84
+#define TK_UPLUS 85
+#define TK_BITNOT 86
+#define TK_STRING 87
+#define TK_JOIN_KW 88
+#define TK_INTEGER 89
+#define TK_CONSTRAINT 90
+#define TK_DEFAULT 91
+#define TK_FLOAT 92
+#define TK_NULL 93
+#define TK_PRIMARY 94
+#define TK_UNIQUE 95
+#define TK_CHECK 96
+#define TK_REFERENCES 97
+#define TK_COLLATE 98
+#define TK_ON 99
+#define TK_DELETE 100
+#define TK_UPDATE 101
+#define TK_INSERT 102
+#define TK_SET 103
+#define TK_DEFERRABLE 104
+#define TK_FOREIGN 105
+#define TK_DROP 106
+#define TK_UNION 107
+#define TK_ALL 108
+#define TK_INTERSECT 109
+#define TK_EXCEPT 110
+#define TK_SELECT 111
+#define TK_DISTINCT 112
+#define TK_DOT 113
+#define TK_FROM 114
+#define TK_JOIN 115
+#define TK_USING 116
+#define TK_ORDER 117
+#define TK_BY 118
+#define TK_GROUP 119
+#define TK_HAVING 120
+#define TK_LIMIT 121
+#define TK_WHERE 122
+#define TK_INTO 123
+#define TK_VALUES 124
+#define TK_VARIABLE 125
+#define TK_CASE 126
+#define TK_WHEN 127
+#define TK_THEN 128
+#define TK_ELSE 129
+#define TK_INDEX 130
diff --git a/src/libs/sqlite2/pragma.c b/src/libs/sqlite2/pragma.c
new file mode 100644
index 00000000..7cb637fd
--- /dev/null
+++ b/src/libs/sqlite2/pragma.c
@@ -0,0 +1,712 @@
+/*
+** 2003 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the PRAGMA command.
+**
+** $Id: pragma.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+#include <ctype.h>
+
+/*
+** Interpret the given string as a boolean value.
+*/
+static int getBoolean(const char *z){
+ static char *azTrue[] = { "yes", "on", "true" };
+ int i;
+ if( z[0]==0 ) return 0;
+ if( isdigit(z[0]) || (z[0]=='-' && isdigit(z[1])) ){
+ return atoi(z);
+ }
+ for(i=0; i<sizeof(azTrue)/sizeof(azTrue[0]); i++){
+ if( sqliteStrICmp(z,azTrue[i])==0 ) return 1;
+ }
+ return 0;
+}
+
+/*
+** Interpret the given string as a safety level. Return 0 for OFF,
+** 1 for ON or NORMAL and 2 for FULL. Return 1 for an empty or
+** unrecognized string argument.
+**
+** Note that the values returned are one less that the values that
+** should be passed into sqliteBtreeSetSafetyLevel(). The is done
+** to support legacy SQL code. The safety level used to be boolean
+** and older scripts may have used numbers 0 for OFF and 1 for ON.
+*/
+static int getSafetyLevel(char *z){
+ static const struct {
+ const char *zWord;
+ int val;
+ } aKey[] = {
+ { "no", 0 },
+ { "off", 0 },
+ { "false", 0 },
+ { "yes", 1 },
+ { "on", 1 },
+ { "true", 1 },
+ { "full", 2 },
+ };
+ int i;
+ if( z[0]==0 ) return 1;
+ if( isdigit(z[0]) || (z[0]=='-' && isdigit(z[1])) ){
+ return atoi(z);
+ }
+ for(i=0; i<sizeof(aKey)/sizeof(aKey[0]); i++){
+ if( sqliteStrICmp(z,aKey[i].zWord)==0 ) return aKey[i].val;
+ }
+ return 1;
+}
+
+/*
+** Interpret the given string as a temp db location. Return 1 for file
+** backed temporary databases, 2 for the Red-Black tree in memory database
+** and 0 to use the compile-time default.
+*/
+static int getTempStore(const char *z){
+ if( z[0]>='0' && z[0]<='2' ){
+ return z[0] - '0';
+ }else if( sqliteStrICmp(z, "file")==0 ){
+ return 1;
+ }else if( sqliteStrICmp(z, "memory")==0 ){
+ return 2;
+ }else{
+ return 0;
+ }
+}
+
+/*
+** If the TEMP database is open, close it and mark the database schema
+** as needing reloading. This must be done when using the TEMP_STORE
+** or DEFAULT_TEMP_STORE pragmas.
+*/
+static int changeTempStorage(Parse *pParse, const char *zStorageType){
+ int ts = getTempStore(zStorageType);
+ sqlite *db = pParse->db;
+ if( db->temp_store==ts ) return SQLITE_OK;
+ if( db->aDb[1].pBt!=0 ){
+ if( db->flags & SQLITE_InTrans ){
+ sqliteErrorMsg(pParse, "temporary storage cannot be changed "
+ "from within a transaction");
+ return SQLITE_ERROR;
+ }
+ sqliteBtreeClose(db->aDb[1].pBt);
+ db->aDb[1].pBt = 0;
+ sqliteResetInternalSchema(db, 0);
+ }
+ db->temp_store = ts;
+ return SQLITE_OK;
+}
+
+/*
+** Check to see if zRight and zLeft refer to a pragma that queries
+** or changes one of the flags in db->flags. Return 1 if so and 0 if not.
+** Also, implement the pragma.
+*/
+static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){
+ static const struct {
+ const char *zName; /* Name of the pragma */
+ int mask; /* Mask for the db->flags value */
+ } aPragma[] = {
+ { "vdbe_trace", SQLITE_VdbeTrace },
+ { "full_column_names", SQLITE_FullColNames },
+ { "short_column_names", SQLITE_ShortColNames },
+ { "show_datatypes", SQLITE_ReportTypes },
+ { "count_changes", SQLITE_CountRows },
+ { "empty_result_callbacks", SQLITE_NullCallback },
+ };
+ int i;
+ for(i=0; i<sizeof(aPragma)/sizeof(aPragma[0]); i++){
+ if( sqliteStrICmp(zLeft, aPragma[i].zName)==0 ){
+ sqlite *db = pParse->db;
+ Vdbe *v;
+ if( strcmp(zLeft,zRight)==0 && (v = sqliteGetVdbe(pParse))!=0 ){
+ sqliteVdbeOp3(v, OP_ColumnName, 0, 1, aPragma[i].zName, P3_STATIC);
+ sqliteVdbeOp3(v, OP_ColumnName, 1, 0, "boolean", P3_STATIC);
+ sqliteVdbeCode(v, OP_Integer, (db->flags & aPragma[i].mask)!=0, 0,
+ OP_Callback, 1, 0,
+ 0);
+ }else if( getBoolean(zRight) ){
+ db->flags |= aPragma[i].mask;
+ }else{
+ db->flags &= ~aPragma[i].mask;
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** Process a pragma statement.
+**
+** Pragmas are of this form:
+**
+** PRAGMA id = value
+**
+** The identifier might also be a string. The value is a string, and
+** identifier, or a number. If minusFlag is true, then the value is
+** a number that was preceded by a minus sign.
+*/
+void sqlitePragma(Parse *pParse, Token *pLeft, Token *pRight, int minusFlag){
+ char *zLeft = 0;
+ char *zRight = 0;
+ sqlite *db = pParse->db;
+ Vdbe *v = sqliteGetVdbe(pParse);
+ if( v==0 ) return;
+
+ zLeft = sqliteStrNDup(pLeft->z, pLeft->n);
+ sqliteDequote(zLeft);
+ if( minusFlag ){
+ zRight = 0;
+ sqliteSetNString(&zRight, "-", 1, pRight->z, pRight->n, 0);
+ }else{
+ zRight = sqliteStrNDup(pRight->z, pRight->n);
+ sqliteDequote(zRight);
+ }
+ if( sqliteAuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, 0) ){
+ sqliteFree(zLeft);
+ sqliteFree(zRight);
+ return;
+ }
+
+ /*
+ ** PRAGMA default_cache_size
+ ** PRAGMA default_cache_size=N
+ **
+ ** The first form reports the current persistent setting for the
+ ** page cache size. The value returned is the maximum number of
+ ** pages in the page cache. The second form sets both the current
+ ** page cache size value and the persistent page cache size value
+ ** stored in the database file.
+ **
+ ** The default cache size is stored in meta-value 2 of page 1 of the
+ ** database file. The cache size is actually the absolute value of
+ ** this memory location. The sign of meta-value 2 determines the
+ ** synchronous setting. A negative value means synchronous is off
+ ** and a positive value means synchronous is on.
+ */
+ if( sqliteStrICmp(zLeft,"default_cache_size")==0 ){
+ static VdbeOpList getCacheSize[] = {
+ { OP_ReadCookie, 0, 2, 0},
+ { OP_AbsValue, 0, 0, 0},
+ { OP_Dup, 0, 0, 0},
+ { OP_Integer, 0, 0, 0},
+ { OP_Ne, 0, 6, 0},
+ { OP_Integer, 0, 0, 0}, /* 5 */
+ { OP_ColumnName, 0, 1, "cache_size"},
+ { OP_Callback, 1, 0, 0},
+ };
+ int addr;
+ if( pRight->z==pLeft->z ){
+ addr = sqliteVdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize);
+ sqliteVdbeChangeP1(v, addr+5, MAX_PAGES);
+ }else{
+ int size = atoi(zRight);
+ if( size<0 ) size = -size;
+ sqliteBeginWriteOperation(pParse, 0, 0);
+ sqliteVdbeAddOp(v, OP_Integer, size, 0);
+ sqliteVdbeAddOp(v, OP_ReadCookie, 0, 2);
+ addr = sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ sqliteVdbeAddOp(v, OP_Ge, 0, addr+3);
+ sqliteVdbeAddOp(v, OP_Negative, 0, 0);
+ sqliteVdbeAddOp(v, OP_SetCookie, 0, 2);
+ sqliteEndWriteOperation(pParse);
+ db->cache_size = db->cache_size<0 ? -size : size;
+ sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size);
+ }
+ }else
+
+ /*
+ ** PRAGMA cache_size
+ ** PRAGMA cache_size=N
+ **
+ ** The first form reports the current local setting for the
+ ** page cache size. The local setting can be different from
+ ** the persistent cache size value that is stored in the database
+ ** file itself. The value returned is the maximum number of
+ ** pages in the page cache. The second form sets the local
+ ** page cache size value. It does not change the persistent
+ ** cache size stored on the disk so the cache size will revert
+ ** to its default value when the database is closed and reopened.
+ ** N should be a positive integer.
+ */
+ if( sqliteStrICmp(zLeft,"cache_size")==0 ){
+ static VdbeOpList getCacheSize[] = {
+ { OP_ColumnName, 0, 1, "cache_size"},
+ { OP_Callback, 1, 0, 0},
+ };
+ if( pRight->z==pLeft->z ){
+ int size = db->cache_size;;
+ if( size<0 ) size = -size;
+ sqliteVdbeAddOp(v, OP_Integer, size, 0);
+ sqliteVdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize);
+ }else{
+ int size = atoi(zRight);
+ if( size<0 ) size = -size;
+ if( db->cache_size<0 ) size = -size;
+ db->cache_size = size;
+ sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size);
+ }
+ }else
+
+ /*
+ ** PRAGMA default_synchronous
+ ** PRAGMA default_synchronous=ON|OFF|NORMAL|FULL
+ **
+ ** The first form returns the persistent value of the "synchronous" setting
+ ** that is stored in the database. This is the synchronous setting that
+ ** is used whenever the database is opened unless overridden by a separate
+ ** "synchronous" pragma. The second form changes the persistent and the
+ ** local synchronous setting to the value given.
+ **
+ ** If synchronous is OFF, SQLite does not attempt any fsync() systems calls
+ ** to make sure data is committed to disk. Write operations are very fast,
+ ** but a power failure can leave the database in an inconsistent state.
+ ** If synchronous is ON or NORMAL, SQLite will do an fsync() system call to
+ ** make sure data is being written to disk. The risk of corruption due to
+ ** a power loss in this mode is negligible but non-zero. If synchronous
+ ** is FULL, extra fsync()s occur to reduce the risk of corruption to near
+ ** zero, but with a write performance penalty. The default mode is NORMAL.
+ */
+ if( sqliteStrICmp(zLeft,"default_synchronous")==0 ){
+ static VdbeOpList getSync[] = {
+ { OP_ColumnName, 0, 1, "synchronous"},
+ { OP_ReadCookie, 0, 3, 0},
+ { OP_Dup, 0, 0, 0},
+ { OP_If, 0, 0, 0}, /* 3 */
+ { OP_ReadCookie, 0, 2, 0},
+ { OP_Integer, 0, 0, 0},
+ { OP_Lt, 0, 5, 0},
+ { OP_AddImm, 1, 0, 0},
+ { OP_Callback, 1, 0, 0},
+ { OP_Halt, 0, 0, 0},
+ { OP_AddImm, -1, 0, 0}, /* 10 */
+ { OP_Callback, 1, 0, 0}
+ };
+ if( pRight->z==pLeft->z ){
+ int addr = sqliteVdbeAddOpList(v, ArraySize(getSync), getSync);
+ sqliteVdbeChangeP2(v, addr+3, addr+10);
+ }else{
+ int addr;
+ int size = db->cache_size;
+ if( size<0 ) size = -size;
+ sqliteBeginWriteOperation(pParse, 0, 0);
+ sqliteVdbeAddOp(v, OP_ReadCookie, 0, 2);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ addr = sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ sqliteVdbeAddOp(v, OP_Ne, 0, addr+3);
+ sqliteVdbeAddOp(v, OP_AddImm, MAX_PAGES, 0);
+ sqliteVdbeAddOp(v, OP_AbsValue, 0, 0);
+ db->safety_level = getSafetyLevel(zRight)+1;
+ if( db->safety_level==1 ){
+ sqliteVdbeAddOp(v, OP_Negative, 0, 0);
+ size = -size;
+ }
+ sqliteVdbeAddOp(v, OP_SetCookie, 0, 2);
+ sqliteVdbeAddOp(v, OP_Integer, db->safety_level, 0);
+ sqliteVdbeAddOp(v, OP_SetCookie, 0, 3);
+ sqliteEndWriteOperation(pParse);
+ db->cache_size = size;
+ sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size);
+ sqliteBtreeSetSafetyLevel(db->aDb[0].pBt, db->safety_level);
+ }
+ }else
+
+ /*
+ ** PRAGMA synchronous
+ ** PRAGMA synchronous=OFF|ON|NORMAL|FULL
+ **
+ ** Return or set the local value of the synchronous flag. Changing
+ ** the local value does not make changes to the disk file and the
+ ** default value will be restored the next time the database is
+ ** opened.
+ */
+ if( sqliteStrICmp(zLeft,"synchronous")==0 ){
+ static VdbeOpList getSync[] = {
+ { OP_ColumnName, 0, 1, "synchronous"},
+ { OP_Callback, 1, 0, 0},
+ };
+ if( pRight->z==pLeft->z ){
+ sqliteVdbeAddOp(v, OP_Integer, db->safety_level-1, 0);
+ sqliteVdbeAddOpList(v, ArraySize(getSync), getSync);
+ }else{
+ int size = db->cache_size;
+ if( size<0 ) size = -size;
+ db->safety_level = getSafetyLevel(zRight)+1;
+ if( db->safety_level==1 ) size = -size;
+ db->cache_size = size;
+ sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size);
+ sqliteBtreeSetSafetyLevel(db->aDb[0].pBt, db->safety_level);
+ }
+ }else
+
+#ifndef NDEBUG
+ if( sqliteStrICmp(zLeft, "trigger_overhead_test")==0 ){
+ if( getBoolean(zRight) ){
+ always_code_trigger_setup = 1;
+ }else{
+ always_code_trigger_setup = 0;
+ }
+ }else
+#endif
+
+ if( flagPragma(pParse, zLeft, zRight) ){
+ /* The flagPragma() call also generates any necessary code */
+ }else
+
+ if( sqliteStrICmp(zLeft, "table_info")==0 ){
+ Table *pTab;
+ pTab = sqliteFindTable(db, zRight, 0);
+ if( pTab ){
+ static VdbeOpList tableInfoPreface[] = {
+ { OP_ColumnName, 0, 0, "cid"},
+ { OP_ColumnName, 1, 0, "name"},
+ { OP_ColumnName, 2, 0, "type"},
+ { OP_ColumnName, 3, 0, "notnull"},
+ { OP_ColumnName, 4, 0, "dflt_value"},
+ { OP_ColumnName, 5, 1, "pk"},
+ };
+ int i;
+ sqliteVdbeAddOpList(v, ArraySize(tableInfoPreface), tableInfoPreface);
+ sqliteViewGetColumnNames(pParse, pTab);
+ for(i=0; i<pTab->nCol; i++){
+ sqliteVdbeAddOp(v, OP_Integer, i, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zName, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0,
+ pTab->aCol[i].zType ? pTab->aCol[i].zType : "numeric", 0);
+ sqliteVdbeAddOp(v, OP_Integer, pTab->aCol[i].notNull, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0,
+ pTab->aCol[i].zDflt, P3_STATIC);
+ sqliteVdbeAddOp(v, OP_Integer, pTab->aCol[i].isPrimKey, 0);
+ sqliteVdbeAddOp(v, OP_Callback, 6, 0);
+ }
+ }
+ }else
+
+ if( sqliteStrICmp(zLeft, "index_info")==0 ){
+ Index *pIdx;
+ Table *pTab;
+ pIdx = sqliteFindIndex(db, zRight, 0);
+ if( pIdx ){
+ static VdbeOpList tableInfoPreface[] = {
+ { OP_ColumnName, 0, 0, "seqno"},
+ { OP_ColumnName, 1, 0, "cid"},
+ { OP_ColumnName, 2, 1, "name"},
+ };
+ int i;
+ pTab = pIdx->pTable;
+ sqliteVdbeAddOpList(v, ArraySize(tableInfoPreface), tableInfoPreface);
+ for(i=0; i<pIdx->nColumn; i++){
+ int cnum = pIdx->aiColumn[i];
+ sqliteVdbeAddOp(v, OP_Integer, i, 0);
+ sqliteVdbeAddOp(v, OP_Integer, cnum, 0);
+ assert( pTab->nCol>cnum );
+ sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[cnum].zName, 0);
+ sqliteVdbeAddOp(v, OP_Callback, 3, 0);
+ }
+ }
+ }else
+
+ if( sqliteStrICmp(zLeft, "index_list")==0 ){
+ Index *pIdx;
+ Table *pTab;
+ pTab = sqliteFindTable(db, zRight, 0);
+ if( pTab ){
+ v = sqliteGetVdbe(pParse);
+ pIdx = pTab->pIndex;
+ }
+ if( pTab && pIdx ){
+ int i = 0;
+ static VdbeOpList indexListPreface[] = {
+ { OP_ColumnName, 0, 0, "seq"},
+ { OP_ColumnName, 1, 0, "name"},
+ { OP_ColumnName, 2, 1, "unique"},
+ };
+
+ sqliteVdbeAddOpList(v, ArraySize(indexListPreface), indexListPreface);
+ while(pIdx){
+ sqliteVdbeAddOp(v, OP_Integer, i, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, pIdx->zName, 0);
+ sqliteVdbeAddOp(v, OP_Integer, pIdx->onError!=OE_None, 0);
+ sqliteVdbeAddOp(v, OP_Callback, 3, 0);
+ ++i;
+ pIdx = pIdx->pNext;
+ }
+ }
+ }else
+
+ if( sqliteStrICmp(zLeft, "foreign_key_list")==0 ){
+ FKey *pFK;
+ Table *pTab;
+ pTab = sqliteFindTable(db, zRight, 0);
+ if( pTab ){
+ v = sqliteGetVdbe(pParse);
+ pFK = pTab->pFKey;
+ }
+ if( pTab && pFK ){
+ int i = 0;
+ static VdbeOpList indexListPreface[] = {
+ { OP_ColumnName, 0, 0, "id"},
+ { OP_ColumnName, 1, 0, "seq"},
+ { OP_ColumnName, 2, 0, "table"},
+ { OP_ColumnName, 3, 0, "from"},
+ { OP_ColumnName, 4, 1, "to"},
+ };
+
+ sqliteVdbeAddOpList(v, ArraySize(indexListPreface), indexListPreface);
+ while(pFK){
+ int j;
+ for(j=0; j<pFK->nCol; j++){
+ sqliteVdbeAddOp(v, OP_Integer, i, 0);
+ sqliteVdbeAddOp(v, OP_Integer, j, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, pFK->zTo, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0,
+ pTab->aCol[pFK->aCol[j].iFrom].zName, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, pFK->aCol[j].zCol, 0);
+ sqliteVdbeAddOp(v, OP_Callback, 5, 0);
+ }
+ ++i;
+ pFK = pFK->pNextFrom;
+ }
+ }
+ }else
+
+ if( sqliteStrICmp(zLeft, "database_list")==0 ){
+ int i;
+ static VdbeOpList indexListPreface[] = {
+ { OP_ColumnName, 0, 0, "seq"},
+ { OP_ColumnName, 1, 0, "name"},
+ { OP_ColumnName, 2, 1, "file"},
+ };
+
+ sqliteVdbeAddOpList(v, ArraySize(indexListPreface), indexListPreface);
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt==0 ) continue;
+ assert( db->aDb[i].zName!=0 );
+ sqliteVdbeAddOp(v, OP_Integer, i, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0, db->aDb[i].zName, 0);
+ sqliteVdbeOp3(v, OP_String, 0, 0,
+ sqliteBtreeGetFilename(db->aDb[i].pBt), 0);
+ sqliteVdbeAddOp(v, OP_Callback, 3, 0);
+ }
+ }else
+
+
+ /*
+ ** PRAGMA temp_store
+ ** PRAGMA temp_store = "default"|"memory"|"file"
+ **
+ ** Return or set the local value of the temp_store flag. Changing
+ ** the local value does not make changes to the disk file and the default
+ ** value will be restored the next time the database is opened.
+ **
+ ** Note that it is possible for the library compile-time options to
+ ** override this setting
+ */
+ if( sqliteStrICmp(zLeft, "temp_store")==0 ){
+ static VdbeOpList getTmpDbLoc[] = {
+ { OP_ColumnName, 0, 1, "temp_store"},
+ { OP_Callback, 1, 0, 0},
+ };
+ if( pRight->z==pLeft->z ){
+ sqliteVdbeAddOp(v, OP_Integer, db->temp_store, 0);
+ sqliteVdbeAddOpList(v, ArraySize(getTmpDbLoc), getTmpDbLoc);
+ }else{
+ changeTempStorage(pParse, zRight);
+ }
+ }else
+
+ /*
+ ** PRAGMA default_temp_store
+ ** PRAGMA default_temp_store = "default"|"memory"|"file"
+ **
+ ** Return or set the value of the persistent temp_store flag. Any
+ ** change does not take effect until the next time the database is
+ ** opened.
+ **
+ ** Note that it is possible for the library compile-time options to
+ ** override this setting
+ */
+ if( sqliteStrICmp(zLeft, "default_temp_store")==0 ){
+ static VdbeOpList getTmpDbLoc[] = {
+ { OP_ColumnName, 0, 1, "temp_store"},
+ { OP_ReadCookie, 0, 5, 0},
+ { OP_Callback, 1, 0, 0}};
+ if( pRight->z==pLeft->z ){
+ sqliteVdbeAddOpList(v, ArraySize(getTmpDbLoc), getTmpDbLoc);
+ }else{
+ sqliteBeginWriteOperation(pParse, 0, 0);
+ sqliteVdbeAddOp(v, OP_Integer, getTempStore(zRight), 0);
+ sqliteVdbeAddOp(v, OP_SetCookie, 0, 5);
+ sqliteEndWriteOperation(pParse);
+ }
+ }else
+
+#ifndef NDEBUG
+ if( sqliteStrICmp(zLeft, "parser_trace")==0 ){
+ extern void sqliteParserTrace(FILE*, char *);
+ if( getBoolean(zRight) ){
+ sqliteParserTrace(stdout, "parser: ");
+ }else{
+ sqliteParserTrace(0, 0);
+ }
+ }else
+#endif
+
+ if( sqliteStrICmp(zLeft, "integrity_check")==0 ){
+ int i, j, addr;
+
+ /* Code that initializes the integrity check program. Set the
+ ** error count 0
+ */
+ static VdbeOpList initCode[] = {
+ { OP_Integer, 0, 0, 0},
+ { OP_MemStore, 0, 1, 0},
+ { OP_ColumnName, 0, 1, "integrity_check"},
+ };
+
+ /* Code to do an BTree integrity check on a single database file.
+ */
+ static VdbeOpList checkDb[] = {
+ { OP_SetInsert, 0, 0, "2"},
+ { OP_Integer, 0, 0, 0}, /* 1 */
+ { OP_OpenRead, 0, 2, 0},
+ { OP_Rewind, 0, 7, 0}, /* 3 */
+ { OP_Column, 0, 3, 0}, /* 4 */
+ { OP_SetInsert, 0, 0, 0},
+ { OP_Next, 0, 4, 0}, /* 6 */
+ { OP_IntegrityCk, 0, 0, 0}, /* 7 */
+ { OP_Dup, 0, 1, 0},
+ { OP_String, 0, 0, "ok"},
+ { OP_StrEq, 0, 12, 0}, /* 10 */
+ { OP_MemIncr, 0, 0, 0},
+ { OP_String, 0, 0, "*** in database "},
+ { OP_String, 0, 0, 0}, /* 13 */
+ { OP_String, 0, 0, " ***\n"},
+ { OP_Pull, 3, 0, 0},
+ { OP_Concat, 4, 1, 0},
+ { OP_Callback, 1, 0, 0},
+ };
+
+ /* Code that appears at the end of the integrity check. If no error
+ ** messages have been generated, output OK. Otherwise output the
+ ** error message
+ */
+ static VdbeOpList endCode[] = {
+ { OP_MemLoad, 0, 0, 0},
+ { OP_Integer, 0, 0, 0},
+ { OP_Ne, 0, 0, 0}, /* 2 */
+ { OP_String, 0, 0, "ok"},
+ { OP_Callback, 1, 0, 0},
+ };
+
+ /* Initialize the VDBE program */
+ sqliteVdbeAddOpList(v, ArraySize(initCode), initCode);
+
+ /* Do an integrity check on each database file */
+ for(i=0; i<db->nDb; i++){
+ HashElem *x;
+
+ /* Do an integrity check of the B-Tree
+ */
+ addr = sqliteVdbeAddOpList(v, ArraySize(checkDb), checkDb);
+ sqliteVdbeChangeP1(v, addr+1, i);
+ sqliteVdbeChangeP2(v, addr+3, addr+7);
+ sqliteVdbeChangeP2(v, addr+6, addr+4);
+ sqliteVdbeChangeP2(v, addr+7, i);
+ sqliteVdbeChangeP2(v, addr+10, addr+ArraySize(checkDb));
+ sqliteVdbeChangeP3(v, addr+13, db->aDb[i].zName, P3_STATIC);
+
+ /* Make sure all the indices are constructed correctly.
+ */
+ sqliteCodeVerifySchema(pParse, i);
+ for(x=sqliteHashFirst(&db->aDb[i].tblHash); x; x=sqliteHashNext(x)){
+ Table *pTab = sqliteHashData(x);
+ Index *pIdx;
+ int loopTop;
+
+ if( pTab->pIndex==0 ) continue;
+ sqliteVdbeAddOp(v, OP_Integer, i, 0);
+ sqliteVdbeOp3(v, OP_OpenRead, 1, pTab->tnum, pTab->zName, 0);
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ if( pIdx->tnum==0 ) continue;
+ sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenRead, j+2, pIdx->tnum, pIdx->zName, 0);
+ }
+ sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, 1, 1);
+ loopTop = sqliteVdbeAddOp(v, OP_Rewind, 1, 0);
+ sqliteVdbeAddOp(v, OP_MemIncr, 1, 0);
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ int k, jmp2;
+ static VdbeOpList idxErr[] = {
+ { OP_MemIncr, 0, 0, 0},
+ { OP_String, 0, 0, "rowid "},
+ { OP_Recno, 1, 0, 0},
+ { OP_String, 0, 0, " missing from index "},
+ { OP_String, 0, 0, 0}, /* 4 */
+ { OP_Concat, 4, 0, 0},
+ { OP_Callback, 1, 0, 0},
+ };
+ sqliteVdbeAddOp(v, OP_Recno, 1, 0);
+ for(k=0; k<pIdx->nColumn; k++){
+ int idx = pIdx->aiColumn[k];
+ if( idx==pTab->iPKey ){
+ sqliteVdbeAddOp(v, OP_Recno, 1, 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_Column, 1, idx);
+ }
+ }
+ sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0);
+ if( db->file_format>=4 ) sqliteAddIdxKeyType(v, pIdx);
+ jmp2 = sqliteVdbeAddOp(v, OP_Found, j+2, 0);
+ addr = sqliteVdbeAddOpList(v, ArraySize(idxErr), idxErr);
+ sqliteVdbeChangeP3(v, addr+4, pIdx->zName, P3_STATIC);
+ sqliteVdbeChangeP2(v, jmp2, sqliteVdbeCurrentAddr(v));
+ }
+ sqliteVdbeAddOp(v, OP_Next, 1, loopTop+1);
+ sqliteVdbeChangeP2(v, loopTop, sqliteVdbeCurrentAddr(v));
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ static VdbeOpList cntIdx[] = {
+ { OP_Integer, 0, 0, 0},
+ { OP_MemStore, 2, 1, 0},
+ { OP_Rewind, 0, 0, 0}, /* 2 */
+ { OP_MemIncr, 2, 0, 0},
+ { OP_Next, 0, 0, 0}, /* 4 */
+ { OP_MemLoad, 1, 0, 0},
+ { OP_MemLoad, 2, 0, 0},
+ { OP_Eq, 0, 0, 0}, /* 7 */
+ { OP_MemIncr, 0, 0, 0},
+ { OP_String, 0, 0, "wrong # of entries in index "},
+ { OP_String, 0, 0, 0}, /* 10 */
+ { OP_Concat, 2, 0, 0},
+ { OP_Callback, 1, 0, 0},
+ };
+ if( pIdx->tnum==0 ) continue;
+ addr = sqliteVdbeAddOpList(v, ArraySize(cntIdx), cntIdx);
+ sqliteVdbeChangeP1(v, addr+2, j+2);
+ sqliteVdbeChangeP2(v, addr+2, addr+5);
+ sqliteVdbeChangeP1(v, addr+4, j+2);
+ sqliteVdbeChangeP2(v, addr+4, addr+3);
+ sqliteVdbeChangeP2(v, addr+7, addr+ArraySize(cntIdx));
+ sqliteVdbeChangeP3(v, addr+10, pIdx->zName, P3_STATIC);
+ }
+ }
+ }
+ addr = sqliteVdbeAddOpList(v, ArraySize(endCode), endCode);
+ sqliteVdbeChangeP2(v, addr+2, addr+ArraySize(endCode));
+ }else
+
+ {}
+ sqliteFree(zLeft);
+ sqliteFree(zRight);
+}
diff --git a/src/libs/sqlite2/printf.c b/src/libs/sqlite2/printf.c
new file mode 100644
index 00000000..a5445f60
--- /dev/null
+++ b/src/libs/sqlite2/printf.c
@@ -0,0 +1,858 @@
+/*
+** The "printf" code that follows dates from the 1980's. It is in
+** the public domain. The original comments are included here for
+** completeness. They are very out-of-date but might be useful as
+** an historical reference. Most of the "enhancements" have been backed
+** out so that the functionality is now the same as standard printf().
+**
+**************************************************************************
+**
+** The following modules is an enhanced replacement for the "printf" subroutines
+** found in the standard C library. The following enhancements are
+** supported:
+**
+** + Additional functions. The standard set of "printf" functions
+** includes printf, fprintf, sprintf, vprintf, vfprintf, and
+** vsprintf. This module adds the following:
+**
+** * snprintf -- Works like sprintf, but has an extra argument
+** which is the size of the buffer written to.
+**
+** * mprintf -- Similar to sprintf. Writes output to memory
+** obtained from malloc.
+**
+** * xprintf -- Calls a function to dispose of output.
+**
+** * nprintf -- No output, but returns the number of characters
+** that would have been output by printf.
+**
+** * A v- version (ex: vsnprintf) of every function is also
+** supplied.
+**
+** + A few extensions to the formatting notation are supported:
+**
+** * The "=" flag (similar to "-") causes the output to be
+** be centered in the appropriately sized field.
+**
+** * The %b field outputs an integer in binary notation.
+**
+** * The %c field now accepts a precision. The character output
+** is repeated by the number of times the precision specifies.
+**
+** * The %' field works like %c, but takes as its character the
+** next character of the format string, instead of the next
+** argument. For example, printf("%.78'-") prints 78 minus
+** signs, the same as printf("%.78c",'-').
+**
+** + When compiled using GCC on a SPARC, this version of printf is
+** faster than the library printf for SUN OS 4.1.
+**
+** + All functions are fully reentrant.
+**
+*/
+#include "sqliteInt.h"
+
+/*
+** Conversion types fall into various categories as defined by the
+** following enumeration.
+*/
+#define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */
+#define etFLOAT 2 /* Floating point. %f */
+#define etEXP 3 /* Exponentional notation. %e and %E */
+#define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */
+#define etSIZE 5 /* Return number of characters processed so far. %n */
+#define etSTRING 6 /* Strings. %s */
+#define etDYNSTRING 7 /* Dynamically allocated strings. %z */
+#define etPERCENT 8 /* Percent symbol. %% */
+#define etCHARX 9 /* Characters. %c */
+#define etERROR 10 /* Used to indicate no such conversion type */
+/* The rest are extensions, not normally found in printf() */
+#define etCHARLIT 11 /* Literal characters. %' */
+#define etSQLESCAPE 12 /* Strings with '\'' doubled. %q */
+#define etSQLESCAPE2 13 /* Strings with '\'' doubled and enclosed in '',
+ NULL pointers replaced by SQL NULL. %Q */
+#define etTOKEN 14 /* a pointer to a Token structure */
+#define etSRCLIST 15 /* a pointer to a SrcList */
+
+
+/*
+** An "etByte" is an 8-bit unsigned value.
+*/
+typedef unsigned char etByte;
+
+/*
+** Each builtin conversion character (ex: the 'd' in "%d") is described
+** by an instance of the following structure
+*/
+typedef struct et_info { /* Information about each format field */
+ char fmttype; /* The format field code letter */
+ etByte base; /* The base for radix conversion */
+ etByte flags; /* One or more of FLAG_ constants below */
+ etByte type; /* Conversion paradigm */
+ char *charset; /* The character set for conversion */
+ char *prefix; /* Prefix on non-zero values in alt format */
+} et_info;
+
+/*
+** Allowed values for et_info.flags
+*/
+#define FLAG_SIGNED 1 /* True if the value to convert is signed */
+#define FLAG_INTERN 2 /* True if for internal use only */
+
+
+/*
+** The following table is searched linearly, so it is good to put the
+** most frequently used conversion types first.
+*/
+static et_info fmtinfo[] = {
+ { 'd', 10, 1, etRADIX, "0123456789", 0 },
+ { 's', 0, 0, etSTRING, 0, 0 },
+ { 'z', 0, 2, etDYNSTRING, 0, 0 },
+ { 'q', 0, 0, etSQLESCAPE, 0, 0 },
+ { 'Q', 0, 0, etSQLESCAPE2, 0, 0 },
+ { 'c', 0, 0, etCHARX, 0, 0 },
+ { 'o', 8, 0, etRADIX, "01234567", "0" },
+ { 'u', 10, 0, etRADIX, "0123456789", 0 },
+ { 'x', 16, 0, etRADIX, "0123456789abcdef", "x0" },
+ { 'X', 16, 0, etRADIX, "0123456789ABCDEF", "X0" },
+ { 'f', 0, 1, etFLOAT, 0, 0 },
+ { 'e', 0, 1, etEXP, "e", 0 },
+ { 'E', 0, 1, etEXP, "E", 0 },
+ { 'g', 0, 1, etGENERIC, "e", 0 },
+ { 'G', 0, 1, etGENERIC, "E", 0 },
+ { 'i', 10, 1, etRADIX, "0123456789", 0 },
+ { 'n', 0, 0, etSIZE, 0, 0 },
+ { '%', 0, 0, etPERCENT, 0, 0 },
+ { 'p', 10, 0, etRADIX, "0123456789", 0 },
+ { 'T', 0, 2, etTOKEN, 0, 0 },
+ { 'S', 0, 2, etSRCLIST, 0, 0 },
+};
+#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0]))
+
+/*
+** If NOFLOATINGPOINT is defined, then none of the floating point
+** conversions will work.
+*/
+#ifndef etNOFLOATINGPOINT
+/*
+** "*val" is a double such that 0.1 <= *val < 10.0
+** Return the ascii code for the leading digit of *val, then
+** multiply "*val" by 10.0 to renormalize.
+**
+** Example:
+** input: *val = 3.14159
+** output: *val = 1.4159 function return = '3'
+**
+** The counter *cnt is incremented each time. After counter exceeds
+** 16 (the number of significant digits in a 64-bit float) '0' is
+** always returned.
+*/
+static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){
+ int digit;
+ LONGDOUBLE_TYPE d;
+ if( (*cnt)++ >= 16 ) return '0';
+ digit = (int)*val;
+ d = digit;
+ digit += '0';
+ *val = (*val - d)*10.0;
+ return digit;
+}
+#endif
+
+#define etBUFSIZE 1000 /* Size of the output buffer */
+
+/*
+** The root program. All variations call this core.
+**
+** INPUTS:
+** func This is a pointer to a function taking three arguments
+** 1. A pointer to anything. Same as the "arg" parameter.
+** 2. A pointer to the list of characters to be output
+** (Note, this list is NOT null terminated.)
+** 3. An integer number of characters to be output.
+** (Note: This number might be zero.)
+**
+** arg This is the pointer to anything which will be passed as the
+** first argument to "func". Use it for whatever you like.
+**
+** fmt This is the format string, as in the usual print.
+**
+** ap This is a pointer to a list of arguments. Same as in
+** vfprint.
+**
+** OUTPUTS:
+** The return value is the total number of characters sent to
+** the function "func". Returns -1 on a error.
+**
+** Note that the order in which automatic variables are declared below
+** seems to make a big difference in determining how fast this beast
+** will run.
+*/
+static int vxprintf(
+ void (*func)(void*,const char*,int), /* Consumer of text */
+ void *arg, /* First argument to the consumer */
+ int useExtended, /* Allow extended %-conversions */
+ const char *fmt, /* Format string */
+ va_list ap /* arguments */
+){
+ int c; /* Next character in the format string */
+ char *bufpt; /* Pointer to the conversion buffer */
+ int precision; /* Precision of the current field */
+ int length; /* Length of the field */
+ int idx; /* A general purpose loop counter */
+ int count; /* Total number of characters output */
+ int width; /* Width of the current field */
+ etByte flag_leftjustify; /* True if "-" flag is present */
+ etByte flag_plussign; /* True if "+" flag is present */
+ etByte flag_blanksign; /* True if " " flag is present */
+ etByte flag_alternateform; /* True if "#" flag is present */
+ etByte flag_zeropad; /* True if field width constant starts with zero */
+ etByte flag_long; /* True if "l" flag is present */
+ unsigned long longvalue; /* Value for integer types */
+ LONGDOUBLE_TYPE realvalue; /* Value for real types */
+ et_info *infop; /* Pointer to the appropriate info structure */
+ char buf[etBUFSIZE]; /* Conversion buffer */
+ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
+ etByte errorflag = 0; /* True if an error is encountered */
+ etByte xtype; /* Conversion paradigm */
+ char *zExtra; /* Extra memory used for etTCLESCAPE conversions */
+ static char spaces[] = " ";
+#define etSPACESIZE (sizeof(spaces)-1)
+#ifndef etNOFLOATINGPOINT
+ int exp; /* exponent of real numbers */
+ double rounder; /* Used for rounding floating point values */
+ etByte flag_dp; /* True if decimal point should be shown */
+ etByte flag_rtz; /* True if trailing zeros should be removed */
+ etByte flag_exp; /* True to force display of the exponent */
+ int nsd; /* Number of significant digits returned */
+#endif
+
+ func(arg,"",0);
+ count = length = 0;
+ bufpt = 0;
+ for(; (c=(*fmt))!=0; ++fmt){
+ if( c!='%' ){
+ int amt;
+ bufpt = (char *)fmt;
+ amt = 1;
+ while( (c=(*++fmt))!='%' && c!=0 ) amt++;
+ (*func)(arg,bufpt,amt);
+ count += amt;
+ if( c==0 ) break;
+ }
+ if( (c=(*++fmt))==0 ){
+ errorflag = 1;
+ (*func)(arg,"%",1);
+ count++;
+ break;
+ }
+ /* Find out what flags are present */
+ flag_leftjustify = flag_plussign = flag_blanksign =
+ flag_alternateform = flag_zeropad = 0;
+ do{
+ switch( c ){
+ case '-': flag_leftjustify = 1; c = 0; break;
+ case '+': flag_plussign = 1; c = 0; break;
+ case ' ': flag_blanksign = 1; c = 0; break;
+ case '#': flag_alternateform = 1; c = 0; break;
+ case '0': flag_zeropad = 1; c = 0; break;
+ default: break;
+ }
+ }while( c==0 && (c=(*++fmt))!=0 );
+ /* Get the field width */
+ width = 0;
+ if( c=='*' ){
+ width = va_arg(ap,int);
+ if( width<0 ){
+ flag_leftjustify = 1;
+ width = -width;
+ }
+ c = *++fmt;
+ }else{
+ while( c>='0' && c<='9' ){
+ width = width*10 + c - '0';
+ c = *++fmt;
+ }
+ }
+ if( width > etBUFSIZE-10 ){
+ width = etBUFSIZE-10;
+ }
+ /* Get the precision */
+ if( c=='.' ){
+ precision = 0;
+ c = *++fmt;
+ if( c=='*' ){
+ precision = va_arg(ap,int);
+ if( precision<0 ) precision = -precision;
+ c = *++fmt;
+ }else{
+ while( c>='0' && c<='9' ){
+ precision = precision*10 + c - '0';
+ c = *++fmt;
+ }
+ }
+ /* Limit the precision to prevent overflowing buf[] during conversion */
+ if( precision>etBUFSIZE-40 ) precision = etBUFSIZE-40;
+ }else{
+ precision = -1;
+ }
+ /* Get the conversion type modifier */
+ if( c=='l' ){
+ flag_long = 1;
+ c = *++fmt;
+ }else{
+ flag_long = 0;
+ }
+ /* Fetch the info entry for the field */
+ infop = 0;
+ xtype = etERROR;
+ for(idx=0; idx<etNINFO; idx++){
+ if( c==fmtinfo[idx].fmttype ){
+ infop = &fmtinfo[idx];
+ if( useExtended || (infop->flags & FLAG_INTERN)==0 ){
+ xtype = infop->type;
+ }
+ break;
+ }
+ }
+ zExtra = 0;
+
+ /*
+ ** At this point, variables are initialized as follows:
+ **
+ ** flag_alternateform TRUE if a '#' is present.
+ ** flag_plussign TRUE if a '+' is present.
+ ** flag_leftjustify TRUE if a '-' is present or if the
+ ** field width was negative.
+ ** flag_zeropad TRUE if the width began with 0.
+ ** flag_long TRUE if the letter 'l' (ell) prefixed
+ ** the conversion character.
+ ** flag_blanksign TRUE if a ' ' is present.
+ ** width The specified field width. This is
+ ** always non-negative. Zero is the default.
+ ** precision The specified precision. The default
+ ** is -1.
+ ** xtype The class of the conversion.
+ ** infop Pointer to the appropriate info struct.
+ */
+ switch( xtype ){
+ case etRADIX:
+ if( flag_long ) longvalue = va_arg(ap,long);
+ else longvalue = va_arg(ap,int);
+#if 1
+ /* For the format %#x, the value zero is printed "0" not "0x0".
+ ** I think this is stupid. */
+ if( longvalue==0 ) flag_alternateform = 0;
+#else
+ /* More sensible: turn off the prefix for octal (to prevent "00"),
+ ** but leave the prefix for hex. */
+ if( longvalue==0 && infop->base==8 ) flag_alternateform = 0;
+#endif
+ if( infop->flags & FLAG_SIGNED ){
+ if( *(long*)&longvalue<0 ){
+ longvalue = -*(long*)&longvalue;
+ prefix = '-';
+ }else if( flag_plussign ) prefix = '+';
+ else if( flag_blanksign ) prefix = ' ';
+ else prefix = 0;
+ }else prefix = 0;
+ if( flag_zeropad && precision<width-(prefix!=0) ){
+ precision = width-(prefix!=0);
+ }
+ bufpt = &buf[etBUFSIZE-1];
+ {
+ char *cset; /* Use registers for speed */
+ int base;
+ cset = infop->charset;
+ base = infop->base;
+ do{ /* Convert to ascii */
+ *(--bufpt) = cset[longvalue%base];
+ longvalue = longvalue/base;
+ }while( longvalue>0 );
+ }
+ length = &buf[etBUFSIZE-1]-bufpt;
+ for(idx=precision-length; idx>0; idx--){
+ *(--bufpt) = '0'; /* Zero pad */
+ }
+ if( prefix ) *(--bufpt) = prefix; /* Add sign */
+ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */
+ char *pre, x;
+ pre = infop->prefix;
+ if( *bufpt!=pre[0] ){
+ for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x;
+ }
+ }
+ length = &buf[etBUFSIZE-1]-bufpt;
+ break;
+ case etFLOAT:
+ case etEXP:
+ case etGENERIC:
+ realvalue = va_arg(ap,double);
+#ifndef etNOFLOATINGPOINT
+ if( precision<0 ) precision = 6; /* Set default precision */
+ if( precision>etBUFSIZE-10 ) precision = etBUFSIZE-10;
+ if( realvalue<0.0 ){
+ realvalue = -realvalue;
+ prefix = '-';
+ }else{
+ if( flag_plussign ) prefix = '+';
+ else if( flag_blanksign ) prefix = ' ';
+ else prefix = 0;
+ }
+ if( infop->type==etGENERIC && precision>0 ) precision--;
+ rounder = 0.0;
+#if 0
+ /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */
+ for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1);
+#else
+ /* It makes more sense to use 0.5 */
+ for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1);
+#endif
+ if( infop->type==etFLOAT ) realvalue += rounder;
+ /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
+ exp = 0;
+ if( realvalue>0.0 ){
+ while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
+ while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
+ while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; }
+ while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; }
+ if( exp>350 || exp<-350 ){
+ bufpt = "NaN";
+ length = 3;
+ break;
+ }
+ }
+ bufpt = buf;
+ /*
+ ** If the field type is etGENERIC, then convert to either etEXP
+ ** or etFLOAT, as appropriate.
+ */
+ flag_exp = xtype==etEXP;
+ if( xtype!=etFLOAT ){
+ realvalue += rounder;
+ if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
+ }
+ if( xtype==etGENERIC ){
+ flag_rtz = !flag_alternateform;
+ if( exp<-4 || exp>precision ){
+ xtype = etEXP;
+ }else{
+ precision = precision - exp;
+ xtype = etFLOAT;
+ }
+ }else{
+ flag_rtz = 0;
+ }
+ /*
+ ** The "exp+precision" test causes output to be of type etEXP if
+ ** the precision is too large to fit in buf[].
+ */
+ nsd = 0;
+ if( xtype==etFLOAT && exp+precision<etBUFSIZE-30 ){
+ flag_dp = (precision>0 || flag_alternateform);
+ if( prefix ) *(bufpt++) = prefix; /* Sign */
+ if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */
+ else for(; exp>=0; exp--) *(bufpt++) = et_getdigit(&realvalue,&nsd);
+ if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */
+ for(exp++; exp<0 && precision>0; precision--, exp++){
+ *(bufpt++) = '0';
+ }
+ while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd);
+ *(bufpt--) = 0; /* Null terminate */
+ if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */
+ while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
+ if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
+ }
+ bufpt++; /* point to next free slot */
+ }else{ /* etEXP or etGENERIC */
+ flag_dp = (precision>0 || flag_alternateform);
+ if( prefix ) *(bufpt++) = prefix; /* Sign */
+ *(bufpt++) = et_getdigit(&realvalue,&nsd); /* First digit */
+ if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */
+ while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd);
+ bufpt--; /* point to last digit */
+ if( flag_rtz && flag_dp ){ /* Remove tail zeros */
+ while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
+ if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
+ }
+ bufpt++; /* point to next free slot */
+ if( exp || flag_exp ){
+ *(bufpt++) = infop->charset[0];
+ if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */
+ else { *(bufpt++) = '+'; }
+ if( exp>=100 ){
+ *(bufpt++) = (exp/100)+'0'; /* 100's digit */
+ exp %= 100;
+ }
+ *(bufpt++) = exp/10+'0'; /* 10's digit */
+ *(bufpt++) = exp%10+'0'; /* 1's digit */
+ }
+ }
+ /* The converted number is in buf[] and zero terminated. Output it.
+ ** Note that the number is in the usual order, not reversed as with
+ ** integer conversions. */
+ length = bufpt-buf;
+ bufpt = buf;
+
+ /* Special case: Add leading zeros if the flag_zeropad flag is
+ ** set and we are not left justified */
+ if( flag_zeropad && !flag_leftjustify && length < width){
+ int i;
+ int nPad = width - length;
+ for(i=width; i>=nPad; i--){
+ bufpt[i] = bufpt[i-nPad];
+ }
+ i = prefix!=0;
+ while( nPad-- ) bufpt[i++] = '0';
+ length = width;
+ }
+#endif
+ break;
+ case etSIZE:
+ *(va_arg(ap,int*)) = count;
+ length = width = 0;
+ break;
+ case etPERCENT:
+ buf[0] = '%';
+ bufpt = buf;
+ length = 1;
+ break;
+ case etCHARLIT:
+ case etCHARX:
+ c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
+ if( precision>=0 ){
+ for(idx=1; idx<precision; idx++) buf[idx] = c;
+ length = precision;
+ }else{
+ length =1;
+ }
+ bufpt = buf;
+ break;
+ case etSTRING:
+ case etDYNSTRING:
+ bufpt = va_arg(ap,char*);
+ if( bufpt==0 ){
+ bufpt = "";
+ }else if( xtype==etDYNSTRING ){
+ zExtra = bufpt;
+ }
+ length = strlen(bufpt);
+ if( precision>=0 && precision<length ) length = precision;
+ break;
+ case etSQLESCAPE:
+ case etSQLESCAPE2:
+ {
+ int i, j, n, c, isnull;
+ char *arg = va_arg(ap,char*);
+ isnull = arg==0;
+ if( isnull ) arg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
+ for(i=n=0; (c=arg[i])!=0; i++){
+ if( c=='\'' ) n++;
+ }
+ n += i + 1 + ((!isnull && xtype==etSQLESCAPE2) ? 2 : 0);
+ if( n>etBUFSIZE ){
+ bufpt = zExtra = sqliteMalloc( n );
+ if( bufpt==0 ) return -1;
+ }else{
+ bufpt = buf;
+ }
+ j = 0;
+ if( !isnull && xtype==etSQLESCAPE2 ) bufpt[j++] = '\'';
+ for(i=0; (c=arg[i])!=0; i++){
+ bufpt[j++] = c;
+ if( c=='\'' ) bufpt[j++] = c;
+ }
+ if( !isnull && xtype==etSQLESCAPE2 ) bufpt[j++] = '\'';
+ bufpt[j] = 0;
+ length = j;
+ if( precision>=0 && precision<length ) length = precision;
+ }
+ break;
+ case etTOKEN: {
+ Token *pToken = va_arg(ap, Token*);
+ (*func)(arg, pToken->z, pToken->n);
+ length = width = 0;
+ break;
+ }
+ case etSRCLIST: {
+ SrcList *pSrc = va_arg(ap, SrcList*);
+ int k = va_arg(ap, int);
+ struct SrcList_item *pItem = &pSrc->a[k];
+ assert( k>=0 && k<pSrc->nSrc );
+ if( pItem->zDatabase && pItem->zDatabase[0] ){
+ (*func)(arg, pItem->zDatabase, strlen(pItem->zDatabase));
+ (*func)(arg, ".", 1);
+ }
+ (*func)(arg, pItem->zName, strlen(pItem->zName));
+ length = width = 0;
+ break;
+ }
+ case etERROR:
+ buf[0] = '%';
+ buf[1] = c;
+ errorflag = 0;
+ idx = 1+(c!=0);
+ (*func)(arg,"%",idx);
+ count += idx;
+ if( c==0 ) fmt--;
+ break;
+ }/* End switch over the format type */
+ /*
+ ** The text of the conversion is pointed to by "bufpt" and is
+ ** "length" characters long. The field width is "width". Do
+ ** the output.
+ */
+ if( !flag_leftjustify ){
+ int nspace;
+ nspace = width-length;
+ if( nspace>0 ){
+ count += nspace;
+ while( nspace>=etSPACESIZE ){
+ (*func)(arg,spaces,etSPACESIZE);
+ nspace -= etSPACESIZE;
+ }
+ if( nspace>0 ) (*func)(arg,spaces,nspace);
+ }
+ }
+ if( length>0 ){
+ (*func)(arg,bufpt,length);
+ count += length;
+ }
+ if( flag_leftjustify ){
+ int nspace;
+ nspace = width-length;
+ if( nspace>0 ){
+ count += nspace;
+ while( nspace>=etSPACESIZE ){
+ (*func)(arg,spaces,etSPACESIZE);
+ nspace -= etSPACESIZE;
+ }
+ if( nspace>0 ) (*func)(arg,spaces,nspace);
+ }
+ }
+ if( zExtra ){
+ sqliteFree(zExtra);
+ }
+ }/* End for loop over the format string */
+ return errorflag ? -1 : count;
+} /* End of function */
+
+
+/* This structure is used to store state information about the
+** write to memory that is currently in progress.
+*/
+struct sgMprintf {
+ char *zBase; /* A base allocation */
+ char *zText; /* The string collected so far */
+ int nChar; /* Length of the string so far */
+ int nTotal; /* Output size if unconstrained */
+ int nAlloc; /* Amount of space allocated in zText */
+ void *(*xRealloc)(void*,int); /* Function used to realloc memory */
+};
+
+/*
+** This function implements the callback from vxprintf.
+**
+** This routine add nNewChar characters of text in zNewText to
+** the sgMprintf structure pointed to by "arg".
+*/
+static void mout(void *arg, const char *zNewText, int nNewChar){
+ struct sgMprintf *pM = (struct sgMprintf*)arg;
+ pM->nTotal += nNewChar;
+ if( pM->nChar + nNewChar + 1 > pM->nAlloc ){
+ if( pM->xRealloc==0 ){
+ nNewChar = pM->nAlloc - pM->nChar - 1;
+ }else{
+ pM->nAlloc = pM->nChar + nNewChar*2 + 1;
+ if( pM->zText==pM->zBase ){
+ pM->zText = pM->xRealloc(0, pM->nAlloc);
+ if( pM->zText && pM->nChar ){
+ memcpy(pM->zText, pM->zBase, pM->nChar);
+ }
+ }else{
+ pM->zText = pM->xRealloc(pM->zText, pM->nAlloc);
+ }
+ }
+ }
+ if( pM->zText ){
+ if( nNewChar>0 ){
+ memcpy(&pM->zText[pM->nChar], zNewText, nNewChar);
+ pM->nChar += nNewChar;
+ }
+ pM->zText[pM->nChar] = 0;
+ }
+}
+
+/*
+** This routine is a wrapper around xprintf() that invokes mout() as
+** the consumer.
+*/
+static char *base_vprintf(
+ void *(*xRealloc)(void*,int), /* Routine to realloc memory. May be NULL */
+ int useInternal, /* Use internal %-conversions if true */
+ char *zInitBuf, /* Initially write here, before mallocing */
+ int nInitBuf, /* Size of zInitBuf[] */
+ const char *zFormat, /* format string */
+ va_list ap /* arguments */
+){
+ struct sgMprintf sM;
+ sM.zBase = sM.zText = zInitBuf;
+ sM.nChar = sM.nTotal = 0;
+ sM.nAlloc = nInitBuf;
+ sM.xRealloc = xRealloc;
+ vxprintf(mout, &sM, useInternal, zFormat, ap);
+ if( xRealloc ){
+ if( sM.zText==sM.zBase ){
+ sM.zText = xRealloc(0, sM.nChar+1);
+ memcpy(sM.zText, sM.zBase, sM.nChar+1);
+ }else if( sM.nAlloc>sM.nChar+10 ){
+ sM.zText = xRealloc(sM.zText, sM.nChar+1);
+ }
+ }
+ return sM.zText;
+}
+
+/*
+** Realloc that is a real function, not a macro.
+*/
+static void *printf_realloc(void *old, int size){
+ return sqliteRealloc(old,size);
+}
+
+/*
+** Print into memory obtained from sqliteMalloc(). Use the internal
+** %-conversion extensions.
+*/
+char *sqliteVMPrintf(const char *zFormat, va_list ap){
+ char zBase[1000];
+ return base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap);
+}
+
+/*
+** Print into memory obtained from sqliteMalloc(). Use the internal
+** %-conversion extensions.
+*/
+char *sqliteMPrintf(const char *zFormat, ...){
+ va_list ap;
+ char *z;
+ char zBase[1000];
+ va_start(ap, zFormat);
+ z = base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap);
+ va_end(ap);
+ return z;
+}
+
+/*
+** Print into memory obtained from malloc(). Do not use the internal
+** %-conversion extensions. This routine is for use by external users.
+*/
+char *sqlite_mprintf(const char *zFormat, ...){
+ va_list ap;
+ char *z;
+ char zBuf[200];
+
+ va_start(ap,zFormat);
+ z = base_vprintf((void*(*)(void*,int))realloc, 0,
+ zBuf, sizeof(zBuf), zFormat, ap);
+ va_end(ap);
+ return z;
+}
+
+/* This is the varargs version of sqlite_mprintf.
+*/
+char *sqlite_vmprintf(const char *zFormat, va_list ap){
+ char zBuf[200];
+ return base_vprintf((void*(*)(void*,int))realloc, 0,
+ zBuf, sizeof(zBuf), zFormat, ap);
+}
+
+/*
+** sqlite_snprintf() works like snprintf() except that it ignores the
+** current locale settings. This is important for SQLite because we
+** are not able to use a "," as the decimal point in place of "." as
+** specified by some locales.
+*/
+char *sqlite_snprintf(int n, char *zBuf, const char *zFormat, ...){
+ char *z;
+ va_list ap;
+
+ va_start(ap,zFormat);
+ z = base_vprintf(0, 0, zBuf, n, zFormat, ap);
+ va_end(ap);
+ return z;
+}
+
+/*
+** The following four routines implement the varargs versions of the
+** sqlite_exec() and sqlite_get_table() interfaces. See the sqlite.h
+** header files for a more detailed description of how these interfaces
+** work.
+**
+** These routines are all just simple wrappers.
+*/
+int sqlite_exec_printf(
+ sqlite *db, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ sqlite_callback xCallback, /* Callback function */
+ void *pArg, /* 1st argument to callback function */
+ char **errmsg, /* Error msg written here */
+ ... /* Arguments to the format string. */
+){
+ va_list ap;
+ int rc;
+
+ va_start(ap, errmsg);
+ rc = sqlite_exec_vprintf(db, sqlFormat, xCallback, pArg, errmsg, ap);
+ va_end(ap);
+ return rc;
+}
+int sqlite_exec_vprintf(
+ sqlite *db, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ sqlite_callback xCallback, /* Callback function */
+ void *pArg, /* 1st argument to callback function */
+ char **errmsg, /* Error msg written here */
+ va_list ap /* Arguments to the format string. */
+){
+ char *zSql;
+ int rc;
+
+ zSql = sqlite_vmprintf(sqlFormat, ap);
+ rc = sqlite_exec(db, zSql, xCallback, pArg, errmsg);
+ free(zSql);
+ return rc;
+}
+int sqlite_get_table_printf(
+ sqlite *db, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ char ***resultp, /* Result written to a char *[] that this points to */
+ int *nrow, /* Number of result rows written here */
+ int *ncol, /* Number of result columns written here */
+ char **errmsg, /* Error msg written here */
+ ... /* Arguments to the format string */
+){
+ va_list ap;
+ int rc;
+
+ va_start(ap, errmsg);
+ rc = sqlite_get_table_vprintf(db, sqlFormat, resultp, nrow, ncol, errmsg, ap);
+ va_end(ap);
+ return rc;
+}
+int sqlite_get_table_vprintf(
+ sqlite *db, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ char ***resultp, /* Result written to a char *[] that this points to */
+ int *nrow, /* Number of result rows written here */
+ int *ncolumn, /* Number of result columns written here */
+ char **errmsg, /* Error msg written here */
+ va_list ap /* Arguments to the format string */
+){
+ char *zSql;
+ int rc;
+
+ zSql = sqlite_vmprintf(sqlFormat, ap);
+ rc = sqlite_get_table(db, zSql, resultp, nrow, ncolumn, errmsg);
+ free(zSql);
+ return rc;
+}
diff --git a/src/libs/sqlite2/random.c b/src/libs/sqlite2/random.c
new file mode 100644
index 00000000..0d0a5447
--- /dev/null
+++ b/src/libs/sqlite2/random.c
@@ -0,0 +1,97 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code to implement a pseudo-random number
+** generator (PRNG) for SQLite.
+**
+** Random numbers are used by some of the database backends in order
+** to generate random integer keys for tables or random filenames.
+**
+** $Id: random.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+
+
+/*
+** Get a single 8-bit random value from the RC4 PRNG. The Mutex
+** must be held while executing this routine.
+**
+** Why not just use a library random generator like lrand48() for this?
+** Because the OP_NewRecno opcode in the VDBE depends on having a very
+** good source of random numbers. The lrand48() library function may
+** well be good enough. But maybe not. Or maybe lrand48() has some
+** subtle problems on some systems that could cause problems. It is hard
+** to know. To minimize the risk of problems due to bad lrand48()
+** implementations, SQLite uses this random number generator based
+** on RC4, which we know works very well.
+*/
+static int randomByte(){
+ unsigned char t;
+
+ /* All threads share a single random number generator.
+ ** This structure is the current state of the generator.
+ */
+ static struct {
+ unsigned char isInit; /* True if initialized */
+ unsigned char i, j; /* State variables */
+ unsigned char s[256]; /* State variables */
+ } prng;
+
+ /* Initialize the state of the random number generator once,
+ ** the first time this routine is called. The seed value does
+ ** not need to contain a lot of randomness since we are not
+ ** trying to do secure encryption or anything like that...
+ **
+ ** Nothing in this file or anywhere else in SQLite does any kind of
+ ** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
+ ** number generator) not as an encryption device.
+ */
+ if( !prng.isInit ){
+ int i;
+ char k[256];
+ prng.j = 0;
+ prng.i = 0;
+ sqliteOsRandomSeed(k);
+ for(i=0; i<256; i++){
+ prng.s[i] = i;
+ }
+ for(i=0; i<256; i++){
+ prng.j += prng.s[i] + k[i];
+ t = prng.s[prng.j];
+ prng.s[prng.j] = prng.s[i];
+ prng.s[i] = t;
+ }
+ prng.isInit = 1;
+ }
+
+ /* Generate and return single random byte
+ */
+ prng.i++;
+ t = prng.s[prng.i];
+ prng.j += t;
+ prng.s[prng.i] = prng.s[prng.j];
+ prng.s[prng.j] = t;
+ t += prng.s[prng.i];
+ return prng.s[t];
+}
+
+/*
+** Return N random bytes.
+*/
+void sqliteRandomness(int N, void *pBuf){
+ unsigned char *zBuf = pBuf;
+ sqliteOsEnterMutex();
+ while( N-- ){
+ *(zBuf++) = randomByte();
+ }
+ sqliteOsLeaveMutex();
+}
diff --git a/src/libs/sqlite2/select.c b/src/libs/sqlite2/select.c
new file mode 100644
index 00000000..4cf03606
--- /dev/null
+++ b/src/libs/sqlite2/select.c
@@ -0,0 +1,2434 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** to handle SELECT statements in SQLite.
+**
+** $Id: select.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+
+
+/*
+** Allocate a new Select structure and return a pointer to that
+** structure.
+*/
+Select *sqliteSelectNew(
+ ExprList *pEList, /* which columns to include in the result */
+ SrcList *pSrc, /* the FROM clause -- which tables to scan */
+ Expr *pWhere, /* the WHERE clause */
+ ExprList *pGroupBy, /* the GROUP BY clause */
+ Expr *pHaving, /* the HAVING clause */
+ ExprList *pOrderBy, /* the ORDER BY clause */
+ int isDistinct, /* true if the DISTINCT keyword is present */
+ int nLimit, /* LIMIT value. -1 means not used */
+ int nOffset /* OFFSET value. 0 means no offset */
+){
+ Select *pNew;
+ pNew = sqliteMalloc( sizeof(*pNew) );
+ if( pNew==0 ){
+ sqliteExprListDelete(pEList);
+ sqliteSrcListDelete(pSrc);
+ sqliteExprDelete(pWhere);
+ sqliteExprListDelete(pGroupBy);
+ sqliteExprDelete(pHaving);
+ sqliteExprListDelete(pOrderBy);
+ }else{
+ if( pEList==0 ){
+ pEList = sqliteExprListAppend(0, sqliteExpr(TK_ALL,0,0,0), 0);
+ }
+ pNew->pEList = pEList;
+ pNew->pSrc = pSrc;
+ pNew->pWhere = pWhere;
+ pNew->pGroupBy = pGroupBy;
+ pNew->pHaving = pHaving;
+ pNew->pOrderBy = pOrderBy;
+ pNew->isDistinct = isDistinct;
+ pNew->op = TK_SELECT;
+ pNew->nLimit = nLimit;
+ pNew->nOffset = nOffset;
+ pNew->iLimit = -1;
+ pNew->iOffset = -1;
+ }
+ return pNew;
+}
+
+/*
+** Given 1 to 3 identifiers preceeding the JOIN keyword, determine the
+** type of join. Return an integer constant that expresses that type
+** in terms of the following bit values:
+**
+** JT_INNER
+** JT_OUTER
+** JT_NATURAL
+** JT_LEFT
+** JT_RIGHT
+**
+** A full outer join is the combination of JT_LEFT and JT_RIGHT.
+**
+** If an illegal or unsupported join type is seen, then still return
+** a join type, but put an error in the pParse structure.
+*/
+int sqliteJoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){
+ int jointype = 0;
+ Token *apAll[3];
+ Token *p;
+ static struct {
+ const char *zKeyword;
+ int nChar;
+ int code;
+ } keywords[] = {
+ { "natural", 7, JT_NATURAL },
+ { "left", 4, JT_LEFT|JT_OUTER },
+ { "right", 5, JT_RIGHT|JT_OUTER },
+ { "full", 4, JT_LEFT|JT_RIGHT|JT_OUTER },
+ { "outer", 5, JT_OUTER },
+ { "inner", 5, JT_INNER },
+ { "cross", 5, JT_INNER },
+ };
+ int i, j;
+ apAll[0] = pA;
+ apAll[1] = pB;
+ apAll[2] = pC;
+ for(i=0; i<3 && apAll[i]; i++){
+ p = apAll[i];
+ for(j=0; j<sizeof(keywords)/sizeof(keywords[0]); j++){
+ if( p->n==keywords[j].nChar
+ && sqliteStrNICmp(p->z, keywords[j].zKeyword, p->n)==0 ){
+ jointype |= keywords[j].code;
+ break;
+ }
+ }
+ if( j>=sizeof(keywords)/sizeof(keywords[0]) ){
+ jointype |= JT_ERROR;
+ break;
+ }
+ }
+ if(
+ (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) ||
+ (jointype & JT_ERROR)!=0
+ ){
+ static Token dummy = { 0, 0 };
+ char *zSp1 = " ", *zSp2 = " ";
+ if( pB==0 ){ pB = &dummy; zSp1 = 0; }
+ if( pC==0 ){ pC = &dummy; zSp2 = 0; }
+ sqliteSetNString(&pParse->zErrMsg, "unknown or unsupported join type: ", 0,
+ pA->z, pA->n, zSp1, 1, pB->z, pB->n, zSp2, 1, pC->z, pC->n, 0);
+ pParse->nErr++;
+ jointype = JT_INNER;
+ }else if( jointype & JT_RIGHT ){
+ sqliteErrorMsg(pParse,
+ "RIGHT and FULL OUTER JOINs are not currently supported");
+ jointype = JT_INNER;
+ }
+ return jointype;
+}
+
+/*
+** Return the index of a column in a table. Return -1 if the column
+** is not contained in the table.
+*/
+static int columnIndex(Table *pTab, const char *zCol){
+ int i;
+ for(i=0; i<pTab->nCol; i++){
+ if( sqliteStrICmp(pTab->aCol[i].zName, zCol)==0 ) return i;
+ }
+ return -1;
+}
+
+/*
+** Add a term to the WHERE expression in *ppExpr that requires the
+** zCol column to be equal in the two tables pTab1 and pTab2.
+*/
+static void addWhereTerm(
+ const char *zCol, /* Name of the column */
+ const Table *pTab1, /* First table */
+ const Table *pTab2, /* Second table */
+ Expr **ppExpr /* Add the equality term to this expression */
+){
+ Token dummy;
+ Expr *pE1a, *pE1b, *pE1c;
+ Expr *pE2a, *pE2b, *pE2c;
+ Expr *pE;
+
+ dummy.z = zCol;
+ dummy.n = strlen(zCol);
+ dummy.dyn = 0;
+ pE1a = sqliteExpr(TK_ID, 0, 0, &dummy);
+ pE2a = sqliteExpr(TK_ID, 0, 0, &dummy);
+ dummy.z = pTab1->zName;
+ dummy.n = strlen(dummy.z);
+ pE1b = sqliteExpr(TK_ID, 0, 0, &dummy);
+ dummy.z = pTab2->zName;
+ dummy.n = strlen(dummy.z);
+ pE2b = sqliteExpr(TK_ID, 0, 0, &dummy);
+ pE1c = sqliteExpr(TK_DOT, pE1b, pE1a, 0);
+ pE2c = sqliteExpr(TK_DOT, pE2b, pE2a, 0);
+ pE = sqliteExpr(TK_EQ, pE1c, pE2c, 0);
+ ExprSetProperty(pE, EP_FromJoin);
+ if( *ppExpr ){
+ *ppExpr = sqliteExpr(TK_AND, *ppExpr, pE, 0);
+ }else{
+ *ppExpr = pE;
+ }
+}
+
+/*
+** Set the EP_FromJoin property on all terms of the given expression.
+**
+** The EP_FromJoin property is used on terms of an expression to tell
+** the LEFT OUTER JOIN processing logic that this term is part of the
+** join restriction specified in the ON or USING clause and not a part
+** of the more general WHERE clause. These terms are moved over to the
+** WHERE clause during join processing but we need to remember that they
+** originated in the ON or USING clause.
+*/
+static void setJoinExpr(Expr *p){
+ while( p ){
+ ExprSetProperty(p, EP_FromJoin);
+ setJoinExpr(p->pLeft);
+ p = p->pRight;
+ }
+}
+
+/*
+** This routine processes the join information for a SELECT statement.
+** ON and USING clauses are converted into extra terms of the WHERE clause.
+** NATURAL joins also create extra WHERE clause terms.
+**
+** This routine returns the number of errors encountered.
+*/
+static int sqliteProcessJoin(Parse *pParse, Select *p){
+ SrcList *pSrc;
+ int i, j;
+ pSrc = p->pSrc;
+ for(i=0; i<pSrc->nSrc-1; i++){
+ struct SrcList_item *pTerm = &pSrc->a[i];
+ struct SrcList_item *pOther = &pSrc->a[i+1];
+
+ if( pTerm->pTab==0 || pOther->pTab==0 ) continue;
+
+ /* When the NATURAL keyword is present, add WHERE clause terms for
+ ** every column that the two tables have in common.
+ */
+ if( pTerm->jointype & JT_NATURAL ){
+ Table *pTab;
+ if( pTerm->pOn || pTerm->pUsing ){
+ sqliteErrorMsg(pParse, "a NATURAL join may not have "
+ "an ON or USING clause", 0);
+ return 1;
+ }
+ pTab = pTerm->pTab;
+ for(j=0; j<pTab->nCol; j++){
+ if( columnIndex(pOther->pTab, pTab->aCol[j].zName)>=0 ){
+ addWhereTerm(pTab->aCol[j].zName, pTab, pOther->pTab, &p->pWhere);
+ }
+ }
+ }
+
+ /* Disallow both ON and USING clauses in the same join
+ */
+ if( pTerm->pOn && pTerm->pUsing ){
+ sqliteErrorMsg(pParse, "cannot have both ON and USING "
+ "clauses in the same join");
+ return 1;
+ }
+
+ /* Add the ON clause to the end of the WHERE clause, connected by
+ ** and AND operator.
+ */
+ if( pTerm->pOn ){
+ setJoinExpr(pTerm->pOn);
+ if( p->pWhere==0 ){
+ p->pWhere = pTerm->pOn;
+ }else{
+ p->pWhere = sqliteExpr(TK_AND, p->pWhere, pTerm->pOn, 0);
+ }
+ pTerm->pOn = 0;
+ }
+
+ /* Create extra terms on the WHERE clause for each column named
+ ** in the USING clause. Example: If the two tables to be joined are
+ ** A and B and the USING clause names X, Y, and Z, then add this
+ ** to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z
+ ** Report an error if any column mentioned in the USING clause is
+ ** not contained in both tables to be joined.
+ */
+ if( pTerm->pUsing ){
+ IdList *pList;
+ int j;
+ assert( i<pSrc->nSrc-1 );
+ pList = pTerm->pUsing;
+ for(j=0; j<pList->nId; j++){
+ if( columnIndex(pTerm->pTab, pList->a[j].zName)<0 ||
+ columnIndex(pOther->pTab, pList->a[j].zName)<0 ){
+ sqliteErrorMsg(pParse, "cannot join using column %s - column "
+ "not present in both tables", pList->a[j].zName);
+ return 1;
+ }
+ addWhereTerm(pList->a[j].zName, pTerm->pTab, pOther->pTab, &p->pWhere);
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Delete the given Select structure and all of its substructures.
+*/
+void sqliteSelectDelete(Select *p){
+ if( p==0 ) return;
+ sqliteExprListDelete(p->pEList);
+ sqliteSrcListDelete(p->pSrc);
+ sqliteExprDelete(p->pWhere);
+ sqliteExprListDelete(p->pGroupBy);
+ sqliteExprDelete(p->pHaving);
+ sqliteExprListDelete(p->pOrderBy);
+ sqliteSelectDelete(p->pPrior);
+ sqliteFree(p->zSelect);
+ sqliteFree(p);
+}
+
+/*
+** Delete the aggregate information from the parse structure.
+*/
+static void sqliteAggregateInfoReset(Parse *pParse){
+ sqliteFree(pParse->aAgg);
+ pParse->aAgg = 0;
+ pParse->nAgg = 0;
+ pParse->useAgg = 0;
+}
+
+/*
+** Insert code into "v" that will push the record on the top of the
+** stack into the sorter.
+*/
+static void pushOntoSorter(Parse *pParse, Vdbe *v, ExprList *pOrderBy){
+ char *zSortOrder;
+ int i;
+ zSortOrder = sqliteMalloc( pOrderBy->nExpr + 1 );
+ if( zSortOrder==0 ) return;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ int order = pOrderBy->a[i].sortOrder;
+ int type;
+ int c;
+ if( (order & SQLITE_SO_TYPEMASK)==SQLITE_SO_TEXT ){
+ type = SQLITE_SO_TEXT;
+ }else if( (order & SQLITE_SO_TYPEMASK)==SQLITE_SO_NUM ){
+ type = SQLITE_SO_NUM;
+ }else if( pParse->db->file_format>=4 ){
+ type = sqliteExprType(pOrderBy->a[i].pExpr);
+ }else{
+ type = SQLITE_SO_NUM;
+ }
+ if( (order & SQLITE_SO_DIRMASK)==SQLITE_SO_ASC ){
+ c = type==SQLITE_SO_TEXT ? 'A' : '+';
+ }else{
+ c = type==SQLITE_SO_TEXT ? 'D' : '-';
+ }
+ zSortOrder[i] = c;
+ sqliteExprCode(pParse, pOrderBy->a[i].pExpr);
+ }
+ zSortOrder[pOrderBy->nExpr] = 0;
+ sqliteVdbeOp3(v, OP_SortMakeKey, pOrderBy->nExpr, 0, zSortOrder, P3_DYNAMIC);
+ sqliteVdbeAddOp(v, OP_SortPut, 0, 0);
+}
+
+/*
+** This routine adds a P3 argument to the last VDBE opcode that was
+** inserted. The P3 argument added is a string suitable for the
+** OP_MakeKey or OP_MakeIdxKey opcodes. The string consists of
+** characters 't' or 'n' depending on whether or not the various
+** fields of the key to be generated should be treated as numeric
+** or as text. See the OP_MakeKey and OP_MakeIdxKey opcode
+** documentation for additional information about the P3 string.
+** See also the sqliteAddIdxKeyType() routine.
+*/
+void sqliteAddKeyType(Vdbe *v, ExprList *pEList){
+ int nColumn = pEList->nExpr;
+ char *zType = sqliteMalloc( nColumn+1 );
+ int i;
+ if( zType==0 ) return;
+ for(i=0; i<nColumn; i++){
+ zType[i] = sqliteExprType(pEList->a[i].pExpr)==SQLITE_SO_NUM ? 'n' : 't';
+ }
+ zType[i] = 0;
+ sqliteVdbeChangeP3(v, -1, zType, P3_DYNAMIC);
+}
+
+/*
+** Add code to implement the OFFSET and LIMIT
+*/
+static void codeLimiter(
+ Vdbe *v, /* Generate code into this VM */
+ Select *p, /* The SELECT statement being coded */
+ int iContinue, /* Jump here to skip the current record */
+ int iBreak, /* Jump here to end the loop */
+ int nPop /* Number of times to pop stack when jumping */
+){
+ if( p->iOffset>=0 ){
+ int addr = sqliteVdbeCurrentAddr(v) + 2;
+ if( nPop>0 ) addr++;
+ sqliteVdbeAddOp(v, OP_MemIncr, p->iOffset, addr);
+ if( nPop>0 ){
+ sqliteVdbeAddOp(v, OP_Pop, nPop, 0);
+ }
+ sqliteVdbeAddOp(v, OP_Goto, 0, iContinue);
+ }
+ if( p->iLimit>=0 ){
+ sqliteVdbeAddOp(v, OP_MemIncr, p->iLimit, iBreak);
+ }
+}
+
+/*
+** This routine generates the code for the inside of the inner loop
+** of a SELECT.
+**
+** If srcTab and nColumn are both zero, then the pEList expressions
+** are evaluated in order to get the data for this row. If nColumn>0
+** then data is pulled from srcTab and pEList is used only to get the
+** datatypes for each column.
+*/
+static int selectInnerLoop(
+ Parse *pParse, /* The parser context */
+ Select *p, /* The complete select statement being coded */
+ ExprList *pEList, /* List of values being extracted */
+ int srcTab, /* Pull data from this table */
+ int nColumn, /* Number of columns in the source table */
+ ExprList *pOrderBy, /* If not NULL, sort results using this key */
+ int distinct, /* If >=0, make sure results are distinct */
+ int eDest, /* How to dispose of the results */
+ int iParm, /* An argument to the disposal method */
+ int iContinue, /* Jump here to continue with next row */
+ int iBreak /* Jump here to break out of the inner loop */
+){
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ int hasDistinct; /* True if the DISTINCT keyword is present */
+
+ if( v==0 ) return 0;
+ assert( pEList!=0 );
+
+ /* If there was a LIMIT clause on the SELECT statement, then do the check
+ ** to see if this row should be output.
+ */
+ hasDistinct = distinct>=0 && pEList && pEList->nExpr>0;
+ if( pOrderBy==0 && !hasDistinct ){
+ codeLimiter(v, p, iContinue, iBreak, 0);
+ }
+
+ /* Pull the requested columns.
+ */
+ if( nColumn>0 ){
+ for(i=0; i<nColumn; i++){
+ sqliteVdbeAddOp(v, OP_Column, srcTab, i);
+ }
+ }else{
+ nColumn = pEList->nExpr;
+ for(i=0; i<pEList->nExpr; i++){
+ sqliteExprCode(pParse, pEList->a[i].pExpr);
+ }
+ }
+
+ /* If the DISTINCT keyword was present on the SELECT statement
+ ** and this row has been seen before, then do not make this row
+ ** part of the result.
+ */
+ if( hasDistinct ){
+#if NULL_ALWAYS_DISTINCT
+ sqliteVdbeAddOp(v, OP_IsNull, -pEList->nExpr, sqliteVdbeCurrentAddr(v)+7);
+#endif
+ sqliteVdbeAddOp(v, OP_MakeKey, pEList->nExpr, 1);
+ if( pParse->db->file_format>=4 ) sqliteAddKeyType(v, pEList);
+ sqliteVdbeAddOp(v, OP_Distinct, distinct, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_Pop, pEList->nExpr+1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, iContinue);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_PutStrKey, distinct, 0);
+ if( pOrderBy==0 ){
+ codeLimiter(v, p, iContinue, iBreak, nColumn);
+ }
+ }
+
+ switch( eDest ){
+ /* In this mode, write each query result to the key of the temporary
+ ** table iParm.
+ */
+ case SRT_Union: {
+ sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0);
+ break;
+ }
+
+ /* Store the result as data using a unique key.
+ */
+ case SRT_Table:
+ case SRT_TempTable: {
+ sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ if( pOrderBy ){
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else{
+ sqliteVdbeAddOp(v, OP_NewRecno, iParm, 0);
+ sqliteVdbeAddOp(v, OP_Pull, 1, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, iParm, 0);
+ }
+ break;
+ }
+
+ /* Construct a record from the query result, but instead of
+ ** saving that record, use it as a key to delete elements from
+ ** the temporary table iParm.
+ */
+ case SRT_Except: {
+ int addr;
+ addr = sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT);
+ sqliteVdbeAddOp(v, OP_NotFound, iParm, addr+3);
+ sqliteVdbeAddOp(v, OP_Delete, iParm, 0);
+ break;
+ }
+
+ /* If we are creating a set for an "expr IN (SELECT ...)" construct,
+ ** then there should be a single item on the stack. Write this
+ ** item into the set table with bogus data.
+ */
+ case SRT_Set: {
+ int addr1 = sqliteVdbeCurrentAddr(v);
+ int addr2;
+ assert( nColumn==1 );
+ sqliteVdbeAddOp(v, OP_NotNull, -1, addr1+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ addr2 = sqliteVdbeAddOp(v, OP_Goto, 0, 0);
+ if( pOrderBy ){
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else{
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0);
+ }
+ sqliteVdbeChangeP2(v, addr2, sqliteVdbeCurrentAddr(v));
+ break;
+ }
+
+ /* If this is a scalar select that is part of an expression, then
+ ** store the results in the appropriate memory cell and break out
+ ** of the scan loop.
+ */
+ case SRT_Mem: {
+ assert( nColumn==1 );
+ if( pOrderBy ){
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else{
+ sqliteVdbeAddOp(v, OP_MemStore, iParm, 1);
+ sqliteVdbeAddOp(v, OP_Goto, 0, iBreak);
+ }
+ break;
+ }
+
+ /* Send the data to the callback function.
+ */
+ case SRT_Callback:
+ case SRT_Sorter: {
+ if( pOrderBy ){
+ sqliteVdbeAddOp(v, OP_SortMakeRec, nColumn, 0);
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else{
+ assert( eDest==SRT_Callback );
+ sqliteVdbeAddOp(v, OP_Callback, nColumn, 0);
+ }
+ break;
+ }
+
+ /* Invoke a subroutine to handle the results. The subroutine itself
+ ** is responsible for popping the results off of the stack.
+ */
+ case SRT_Subroutine: {
+ if( pOrderBy ){
+ sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else{
+ sqliteVdbeAddOp(v, OP_Gosub, 0, iParm);
+ }
+ break;
+ }
+
+ /* Discard the results. This is used for SELECT statements inside
+ ** the body of a TRIGGER. The purpose of such selects is to call
+ ** user-defined functions that have side effects. We do not care
+ ** about the actual results of the select.
+ */
+ default: {
+ assert( eDest==SRT_Discard );
+ sqliteVdbeAddOp(v, OP_Pop, nColumn, 0);
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
+** If the inner loop was generated using a non-null pOrderBy argument,
+** then the results were placed in a sorter. After the loop is terminated
+** we need to run the sorter and output the results. The following
+** routine generates the code needed to do that.
+*/
+static void generateSortTail(
+ Select *p, /* The SELECT statement */
+ Vdbe *v, /* Generate code into this VDBE */
+ int nColumn, /* Number of columns of data */
+ int eDest, /* Write the sorted results here */
+ int iParm /* Optional parameter associated with eDest */
+){
+ int end1 = sqliteVdbeMakeLabel(v);
+ int end2 = sqliteVdbeMakeLabel(v);
+ int addr;
+ if( eDest==SRT_Sorter ) return;
+ sqliteVdbeAddOp(v, OP_Sort, 0, 0);
+ addr = sqliteVdbeAddOp(v, OP_SortNext, 0, end1);
+ codeLimiter(v, p, addr, end2, 1);
+ switch( eDest ){
+ case SRT_Callback: {
+ sqliteVdbeAddOp(v, OP_SortCallback, nColumn, 0);
+ break;
+ }
+ case SRT_Table:
+ case SRT_TempTable: {
+ sqliteVdbeAddOp(v, OP_NewRecno, iParm, 0);
+ sqliteVdbeAddOp(v, OP_Pull, 1, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, iParm, 0);
+ break;
+ }
+ case SRT_Set: {
+ assert( nColumn==1 );
+ sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0);
+ break;
+ }
+ case SRT_Mem: {
+ assert( nColumn==1 );
+ sqliteVdbeAddOp(v, OP_MemStore, iParm, 1);
+ sqliteVdbeAddOp(v, OP_Goto, 0, end1);
+ break;
+ }
+ case SRT_Subroutine: {
+ int i;
+ for(i=0; i<nColumn; i++){
+ sqliteVdbeAddOp(v, OP_Column, -1-i, i);
+ }
+ sqliteVdbeAddOp(v, OP_Gosub, 0, iParm);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ break;
+ }
+ default: {
+ /* Do nothing */
+ break;
+ }
+ }
+ sqliteVdbeAddOp(v, OP_Goto, 0, addr);
+ sqliteVdbeResolveLabel(v, end2);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeResolveLabel(v, end1);
+ sqliteVdbeAddOp(v, OP_SortReset, 0, 0);
+}
+
+/*
+** Generate code that will tell the VDBE the datatypes of
+** columns in the result set.
+**
+** This routine only generates code if the "PRAGMA show_datatypes=on"
+** has been executed. The datatypes are reported out in the azCol
+** parameter to the callback function. The first N azCol[] entries
+** are the names of the columns, and the second N entries are the
+** datatypes for the columns.
+**
+** The "datatype" for a result that is a column of a type is the
+** datatype definition extracted from the CREATE TABLE statement.
+** The datatype for an expression is either TEXT or NUMERIC. The
+** datatype for a ROWID field is INTEGER.
+*/
+static void generateColumnTypes(
+ Parse *pParse, /* Parser context */
+ SrcList *pTabList, /* List of tables */
+ ExprList *pEList /* Expressions defining the result set */
+){
+ Vdbe *v = pParse->pVdbe;
+ int i, j;
+ for(i=0; i<pEList->nExpr; i++){
+ Expr *p = pEList->a[i].pExpr;
+ char *zType = 0;
+ if( p==0 ) continue;
+ if( p->op==TK_COLUMN && pTabList ){
+ Table *pTab;
+ int iCol = p->iColumn;
+ for(j=0; j<pTabList->nSrc && pTabList->a[j].iCursor!=p->iTable; j++){}
+ assert( j<pTabList->nSrc );
+ pTab = pTabList->a[j].pTab;
+ if( iCol<0 ) iCol = pTab->iPKey;
+ assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) );
+ if( iCol<0 ){
+ zType = "INTEGER";
+ }else{
+ zType = pTab->aCol[iCol].zType;
+ }
+ }else{
+ if( sqliteExprType(p)==SQLITE_SO_TEXT ){
+ zType = "TEXT";
+ }else{
+ zType = "NUMERIC";
+ }
+ }
+ sqliteVdbeOp3(v, OP_ColumnName, i + pEList->nExpr, 0, zType, 0);
+ }
+}
+
+/*
+** Generate code that will tell the VDBE the names of columns
+** in the result set. This information is used to provide the
+** azCol[] values in the callback.
+*/
+static void generateColumnNames(
+ Parse *pParse, /* Parser context */
+ SrcList *pTabList, /* List of tables */
+ ExprList *pEList /* Expressions defining the result set */
+){
+ Vdbe *v = pParse->pVdbe;
+ int i, j;
+ sqlite *db = pParse->db;
+ int fullNames, shortNames;
+
+ assert( v!=0 );
+ if( pParse->colNamesSet || v==0 || sqlite_malloc_failed ) return;
+ pParse->colNamesSet = 1;
+ fullNames = (db->flags & SQLITE_FullColNames)!=0;
+ shortNames = (db->flags & SQLITE_ShortColNames)!=0;
+ for(i=0; i<pEList->nExpr; i++){
+ Expr *p;
+ int p2 = i==pEList->nExpr-1;
+ p = pEList->a[i].pExpr;
+ if( p==0 ) continue;
+ if( pEList->a[i].zName ){
+ char *zName = pEList->a[i].zName;
+ sqliteVdbeOp3(v, OP_ColumnName, i, p2, zName, 0);
+ continue;
+ }
+ if( p->op==TK_COLUMN && pTabList ){
+ Table *pTab;
+ char *zCol;
+ int iCol = p->iColumn;
+ for(j=0; j<pTabList->nSrc && pTabList->a[j].iCursor!=p->iTable; j++){}
+ assert( j<pTabList->nSrc );
+ pTab = pTabList->a[j].pTab;
+ if( iCol<0 ) iCol = pTab->iPKey;
+ assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) );
+ if( iCol<0 ){
+ zCol = "_ROWID_";
+ }else{
+ zCol = pTab->aCol[iCol].zName;
+ }
+ if( !shortNames && !fullNames && p->span.z && p->span.z[0] ){
+ int addr = sqliteVdbeOp3(v,OP_ColumnName, i, p2, p->span.z, p->span.n);
+ sqliteVdbeCompressSpace(v, addr);
+ }else if( fullNames || (!shortNames && pTabList->nSrc>1) ){
+ char *zName = 0;
+ char *zTab;
+
+ zTab = pTabList->a[j].zAlias;
+ if( fullNames || zTab==0 ) zTab = pTab->zName;
+ sqliteSetString(&zName, zTab, ".", zCol, 0);
+ sqliteVdbeOp3(v, OP_ColumnName, i, p2, zName, P3_DYNAMIC);
+ }else{
+ sqliteVdbeOp3(v, OP_ColumnName, i, p2, zCol, 0);
+ }
+ }else if( p->span.z && p->span.z[0] ){
+ int addr = sqliteVdbeOp3(v,OP_ColumnName, i, p2, p->span.z, p->span.n);
+ sqliteVdbeCompressSpace(v, addr);
+ }else{
+ char zName[30];
+ assert( p->op!=TK_COLUMN || pTabList==0 );
+ sprintf(zName, "column%d", i+1);
+ sqliteVdbeOp3(v, OP_ColumnName, i, p2, zName, 0);
+ }
+ }
+}
+
+/*
+** Name of the connection operator, used for error messages.
+*/
+static const char *selectOpName(int id){
+ char *z;
+ switch( id ){
+ case TK_ALL: z = "UNION ALL"; break;
+ case TK_INTERSECT: z = "INTERSECT"; break;
+ case TK_EXCEPT: z = "EXCEPT"; break;
+ default: z = "UNION"; break;
+ }
+ return z;
+}
+
+/*
+** Forward declaration
+*/
+static int fillInColumnList(Parse*, Select*);
+
+/*
+** Given a SELECT statement, generate a Table structure that describes
+** the result set of that SELECT.
+*/
+Table *sqliteResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){
+ Table *pTab;
+ int i, j;
+ ExprList *pEList;
+ Column *aCol;
+
+ if( fillInColumnList(pParse, pSelect) ){
+ return 0;
+ }
+ pTab = sqliteMalloc( sizeof(Table) );
+ if( pTab==0 ){
+ return 0;
+ }
+ pTab->zName = zTabName ? sqliteStrDup(zTabName) : 0;
+ pEList = pSelect->pEList;
+ pTab->nCol = pEList->nExpr;
+ assert( pTab->nCol>0 );
+ pTab->aCol = aCol = sqliteMalloc( sizeof(pTab->aCol[0])*pTab->nCol );
+ for(i=0; i<pTab->nCol; i++){
+ Expr *p, *pR;
+ if( pEList->a[i].zName ){
+ aCol[i].zName = sqliteStrDup(pEList->a[i].zName);
+ }else if( (p=pEList->a[i].pExpr)->op==TK_DOT
+ && (pR=p->pRight)!=0 && pR->token.z && pR->token.z[0] ){
+ int cnt;
+ sqliteSetNString(&aCol[i].zName, pR->token.z, pR->token.n, 0);
+ for(j=cnt=0; j<i; j++){
+ if( sqliteStrICmp(aCol[j].zName, aCol[i].zName)==0 ){
+ int n;
+ char zBuf[30];
+ sprintf(zBuf,"_%d",++cnt);
+ n = strlen(zBuf);
+ sqliteSetNString(&aCol[i].zName, pR->token.z, pR->token.n, zBuf, n,0);
+ j = -1;
+ }
+ }
+ }else if( p->span.z && p->span.z[0] ){
+ sqliteSetNString(&pTab->aCol[i].zName, p->span.z, p->span.n, 0);
+ }else{
+ char zBuf[30];
+ sprintf(zBuf, "column%d", i+1);
+ aCol[i].zName = sqliteStrDup(zBuf);
+ }
+ sqliteDequote(aCol[i].zName);
+ }
+ pTab->iPKey = -1;
+ return pTab;
+}
+
+/*
+** For the given SELECT statement, do three things.
+**
+** (1) Fill in the pTabList->a[].pTab fields in the SrcList that
+** defines the set of tables that should be scanned. For views,
+** fill pTabList->a[].pSelect with a copy of the SELECT statement
+** that implements the view. A copy is made of the view's SELECT
+** statement so that we can freely modify or delete that statement
+** without worrying about messing up the presistent representation
+** of the view.
+**
+** (2) Add terms to the WHERE clause to accomodate the NATURAL keyword
+** on joins and the ON and USING clause of joins.
+**
+** (3) Scan the list of columns in the result set (pEList) looking
+** for instances of the "*" operator or the TABLE.* operator.
+** If found, expand each "*" to be every column in every table
+** and TABLE.* to be every column in TABLE.
+**
+** Return 0 on success. If there are problems, leave an error message
+** in pParse and return non-zero.
+*/
+static int fillInColumnList(Parse *pParse, Select *p){
+ int i, j, k, rc;
+ SrcList *pTabList;
+ ExprList *pEList;
+ Table *pTab;
+
+ if( p==0 || p->pSrc==0 ) return 1;
+ pTabList = p->pSrc;
+ pEList = p->pEList;
+
+ /* Look up every table in the table list.
+ */
+ for(i=0; i<pTabList->nSrc; i++){
+ if( pTabList->a[i].pTab ){
+ /* This routine has run before! No need to continue */
+ return 0;
+ }
+ if( pTabList->a[i].zName==0 ){
+ /* A sub-query in the FROM clause of a SELECT */
+ assert( pTabList->a[i].pSelect!=0 );
+ if( pTabList->a[i].zAlias==0 ){
+ char zFakeName[60];
+ sprintf(zFakeName, "sqlite_subquery_%p_",
+ (void*)pTabList->a[i].pSelect);
+ sqliteSetString(&pTabList->a[i].zAlias, zFakeName, 0);
+ }
+ pTabList->a[i].pTab = pTab =
+ sqliteResultSetOfSelect(pParse, pTabList->a[i].zAlias,
+ pTabList->a[i].pSelect);
+ if( pTab==0 ){
+ return 1;
+ }
+ /* The isTransient flag indicates that the Table structure has been
+ ** dynamically allocated and may be freed at any time. In other words,
+ ** pTab is not pointing to a persistent table structure that defines
+ ** part of the schema. */
+ pTab->isTransient = 1;
+ }else{
+ /* An ordinary table or view name in the FROM clause */
+ pTabList->a[i].pTab = pTab =
+ sqliteLocateTable(pParse,pTabList->a[i].zName,pTabList->a[i].zDatabase);
+ if( pTab==0 ){
+ return 1;
+ }
+ if( pTab->pSelect ){
+ /* We reach here if the named table is a really a view */
+ if( sqliteViewGetColumnNames(pParse, pTab) ){
+ return 1;
+ }
+ /* If pTabList->a[i].pSelect!=0 it means we are dealing with a
+ ** view within a view. The SELECT structure has already been
+ ** copied by the outer view so we can skip the copy step here
+ ** in the inner view.
+ */
+ if( pTabList->a[i].pSelect==0 ){
+ pTabList->a[i].pSelect = sqliteSelectDup(pTab->pSelect);
+ }
+ }
+ }
+ }
+
+ /* Process NATURAL keywords, and ON and USING clauses of joins.
+ */
+ if( sqliteProcessJoin(pParse, p) ) return 1;
+
+ /* For every "*" that occurs in the column list, insert the names of
+ ** all columns in all tables. And for every TABLE.* insert the names
+ ** of all columns in TABLE. The parser inserted a special expression
+ ** with the TK_ALL operator for each "*" that it found in the column list.
+ ** The following code just has to locate the TK_ALL expressions and expand
+ ** each one to the list of all columns in all tables.
+ **
+ ** The first loop just checks to see if there are any "*" operators
+ ** that need expanding.
+ */
+ for(k=0; k<pEList->nExpr; k++){
+ Expr *pE = pEList->a[k].pExpr;
+ if( pE->op==TK_ALL ) break;
+ if( pE->op==TK_DOT && pE->pRight && pE->pRight->op==TK_ALL
+ && pE->pLeft && pE->pLeft->op==TK_ID ) break;
+ }
+ rc = 0;
+ if( k<pEList->nExpr ){
+ /*
+ ** If we get here it means the result set contains one or more "*"
+ ** operators that need to be expanded. Loop through each expression
+ ** in the result set and expand them one by one.
+ */
+ struct ExprList_item *a = pEList->a;
+ ExprList *pNew = 0;
+ for(k=0; k<pEList->nExpr; k++){
+ Expr *pE = a[k].pExpr;
+ if( pE->op!=TK_ALL &&
+ (pE->op!=TK_DOT || pE->pRight==0 || pE->pRight->op!=TK_ALL) ){
+ /* This particular expression does not need to be expanded.
+ */
+ pNew = sqliteExprListAppend(pNew, a[k].pExpr, 0);
+ pNew->a[pNew->nExpr-1].zName = a[k].zName;
+ a[k].pExpr = 0;
+ a[k].zName = 0;
+ }else{
+ /* This expression is a "*" or a "TABLE.*" and needs to be
+ ** expanded. */
+ int tableSeen = 0; /* Set to 1 when TABLE matches */
+ char *zTName; /* text of name of TABLE */
+ if( pE->op==TK_DOT && pE->pLeft ){
+ zTName = sqliteTableNameFromToken(&pE->pLeft->token);
+ }else{
+ zTName = 0;
+ }
+ for(i=0; i<pTabList->nSrc; i++){
+ Table *pTab = pTabList->a[i].pTab;
+ char *zTabName = pTabList->a[i].zAlias;
+ if( zTabName==0 || zTabName[0]==0 ){
+ zTabName = pTab->zName;
+ }
+ if( zTName && (zTabName==0 || zTabName[0]==0 ||
+ sqliteStrICmp(zTName, zTabName)!=0) ){
+ continue;
+ }
+ tableSeen = 1;
+ for(j=0; j<pTab->nCol; j++){
+ Expr *pExpr, *pLeft, *pRight;
+ char *zName = pTab->aCol[j].zName;
+
+ if( i>0 && (pTabList->a[i-1].jointype & JT_NATURAL)!=0 &&
+ columnIndex(pTabList->a[i-1].pTab, zName)>=0 ){
+ /* In a NATURAL join, omit the join columns from the
+ ** table on the right */
+ continue;
+ }
+ if( i>0 && sqliteIdListIndex(pTabList->a[i-1].pUsing, zName)>=0 ){
+ /* In a join with a USING clause, omit columns in the
+ ** using clause from the table on the right. */
+ continue;
+ }
+ pRight = sqliteExpr(TK_ID, 0, 0, 0);
+ if( pRight==0 ) break;
+ pRight->token.z = zName;
+ pRight->token.n = strlen(zName);
+ pRight->token.dyn = 0;
+ if( zTabName && pTabList->nSrc>1 ){
+ pLeft = sqliteExpr(TK_ID, 0, 0, 0);
+ pExpr = sqliteExpr(TK_DOT, pLeft, pRight, 0);
+ if( pExpr==0 ) break;
+ pLeft->token.z = zTabName;
+ pLeft->token.n = strlen(zTabName);
+ pLeft->token.dyn = 0;
+ sqliteSetString((char**)&pExpr->span.z, zTabName, ".", zName, 0);
+ pExpr->span.n = strlen(pExpr->span.z);
+ pExpr->span.dyn = 1;
+ pExpr->token.z = 0;
+ pExpr->token.n = 0;
+ pExpr->token.dyn = 0;
+ }else{
+ pExpr = pRight;
+ pExpr->span = pExpr->token;
+ }
+ pNew = sqliteExprListAppend(pNew, pExpr, 0);
+ }
+ }
+ if( !tableSeen ){
+ if( zTName ){
+ sqliteErrorMsg(pParse, "no such table: %s", zTName);
+ }else{
+ sqliteErrorMsg(pParse, "no tables specified");
+ }
+ rc = 1;
+ }
+ sqliteFree(zTName);
+ }
+ }
+ sqliteExprListDelete(pEList);
+ p->pEList = pNew;
+ }
+ return rc;
+}
+
+/*
+** This routine recursively unlinks the Select.pSrc.a[].pTab pointers
+** in a select structure. It just sets the pointers to NULL. This
+** routine is recursive in the sense that if the Select.pSrc.a[].pSelect
+** pointer is not NULL, this routine is called recursively on that pointer.
+**
+** This routine is called on the Select structure that defines a
+** VIEW in order to undo any bindings to tables. This is necessary
+** because those tables might be DROPed by a subsequent SQL command.
+** If the bindings are not removed, then the Select.pSrc->a[].pTab field
+** will be left pointing to a deallocated Table structure after the
+** DROP and a coredump will occur the next time the VIEW is used.
+*/
+void sqliteSelectUnbind(Select *p){
+ int i;
+ SrcList *pSrc = p->pSrc;
+ Table *pTab;
+ if( p==0 ) return;
+ for(i=0; i<pSrc->nSrc; i++){
+ if( (pTab = pSrc->a[i].pTab)!=0 ){
+ if( pTab->isTransient ){
+ sqliteDeleteTable(0, pTab);
+ }
+ pSrc->a[i].pTab = 0;
+ if( pSrc->a[i].pSelect ){
+ sqliteSelectUnbind(pSrc->a[i].pSelect);
+ }
+ }
+ }
+}
+
+/*
+** This routine associates entries in an ORDER BY expression list with
+** columns in a result. For each ORDER BY expression, the opcode of
+** the top-level node is changed to TK_COLUMN and the iColumn value of
+** the top-level node is filled in with column number and the iTable
+** value of the top-level node is filled with iTable parameter.
+**
+** If there are prior SELECT clauses, they are processed first. A match
+** in an earlier SELECT takes precedence over a later SELECT.
+**
+** Any entry that does not match is flagged as an error. The number
+** of errors is returned.
+**
+** This routine does NOT correctly initialize the Expr.dataType field
+** of the ORDER BY expressions. The multiSelectSortOrder() routine
+** must be called to do that after the individual select statements
+** have all been analyzed. This routine is unable to compute Expr.dataType
+** because it must be called before the individual select statements
+** have been analyzed.
+*/
+static int matchOrderbyToColumn(
+ Parse *pParse, /* A place to leave error messages */
+ Select *pSelect, /* Match to result columns of this SELECT */
+ ExprList *pOrderBy, /* The ORDER BY values to match against columns */
+ int iTable, /* Insert this value in iTable */
+ int mustComplete /* If TRUE all ORDER BYs must match */
+){
+ int nErr = 0;
+ int i, j;
+ ExprList *pEList;
+
+ if( pSelect==0 || pOrderBy==0 ) return 1;
+ if( mustComplete ){
+ for(i=0; i<pOrderBy->nExpr; i++){ pOrderBy->a[i].done = 0; }
+ }
+ if( fillInColumnList(pParse, pSelect) ){
+ return 1;
+ }
+ if( pSelect->pPrior ){
+ if( matchOrderbyToColumn(pParse, pSelect->pPrior, pOrderBy, iTable, 0) ){
+ return 1;
+ }
+ }
+ pEList = pSelect->pEList;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ Expr *pE = pOrderBy->a[i].pExpr;
+ int iCol = -1;
+ if( pOrderBy->a[i].done ) continue;
+ if( sqliteExprIsInteger(pE, &iCol) ){
+ if( iCol<=0 || iCol>pEList->nExpr ){
+ sqliteErrorMsg(pParse,
+ "ORDER BY position %d should be between 1 and %d",
+ iCol, pEList->nExpr);
+ nErr++;
+ break;
+ }
+ if( !mustComplete ) continue;
+ iCol--;
+ }
+ for(j=0; iCol<0 && j<pEList->nExpr; j++){
+ if( pEList->a[j].zName && (pE->op==TK_ID || pE->op==TK_STRING) ){
+ char *zName, *zLabel;
+ zName = pEList->a[j].zName;
+ assert( pE->token.z );
+ zLabel = sqliteStrNDup(pE->token.z, pE->token.n);
+ sqliteDequote(zLabel);
+ if( sqliteStrICmp(zName, zLabel)==0 ){
+ iCol = j;
+ }
+ sqliteFree(zLabel);
+ }
+ if( iCol<0 && sqliteExprCompare(pE, pEList->a[j].pExpr) ){
+ iCol = j;
+ }
+ }
+ if( iCol>=0 ){
+ pE->op = TK_COLUMN;
+ pE->iColumn = iCol;
+ pE->iTable = iTable;
+ pOrderBy->a[i].done = 1;
+ }
+ if( iCol<0 && mustComplete ){
+ sqliteErrorMsg(pParse,
+ "ORDER BY term number %d does not match any result column", i+1);
+ nErr++;
+ break;
+ }
+ }
+ return nErr;
+}
+
+/*
+** Get a VDBE for the given parser context. Create a new one if necessary.
+** If an error occurs, return NULL and leave a message in pParse.
+*/
+Vdbe *sqliteGetVdbe(Parse *pParse){
+ Vdbe *v = pParse->pVdbe;
+ if( v==0 ){
+ v = pParse->pVdbe = sqliteVdbeCreate(pParse->db);
+ }
+ return v;
+}
+
+/*
+** This routine sets the Expr.dataType field on all elements of
+** the pOrderBy expression list. The pOrderBy list will have been
+** set up by matchOrderbyToColumn(). Hence each expression has
+** a TK_COLUMN as its root node. The Expr.iColumn refers to a
+** column in the result set. The datatype is set to SQLITE_SO_TEXT
+** if the corresponding column in p and every SELECT to the left of
+** p has a datatype of SQLITE_SO_TEXT. If the cooressponding column
+** in p or any of the left SELECTs is SQLITE_SO_NUM, then the datatype
+** of the order-by expression is set to SQLITE_SO_NUM.
+**
+** Examples:
+**
+** CREATE TABLE one(a INTEGER, b TEXT);
+** CREATE TABLE two(c VARCHAR(5), d FLOAT);
+**
+** SELECT b, b FROM one UNION SELECT d, c FROM two ORDER BY 1, 2;
+**
+** The primary sort key will use SQLITE_SO_NUM because the "d" in
+** the second SELECT is numeric. The 1st column of the first SELECT
+** is text but that does not matter because a numeric always overrides
+** a text.
+**
+** The secondary key will use the SQLITE_SO_TEXT sort order because
+** both the (second) "b" in the first SELECT and the "c" in the second
+** SELECT have a datatype of text.
+*/
+static void multiSelectSortOrder(Select *p, ExprList *pOrderBy){
+ int i;
+ ExprList *pEList;
+ if( pOrderBy==0 ) return;
+ if( p==0 ){
+ for(i=0; i<pOrderBy->nExpr; i++){
+ pOrderBy->a[i].pExpr->dataType = SQLITE_SO_TEXT;
+ }
+ return;
+ }
+ multiSelectSortOrder(p->pPrior, pOrderBy);
+ pEList = p->pEList;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ Expr *pE = pOrderBy->a[i].pExpr;
+ if( pE->dataType==SQLITE_SO_NUM ) continue;
+ assert( pE->iColumn>=0 );
+ if( pEList->nExpr>pE->iColumn ){
+ pE->dataType = sqliteExprType(pEList->a[pE->iColumn].pExpr);
+ }
+ }
+}
+
+/*
+** Compute the iLimit and iOffset fields of the SELECT based on the
+** nLimit and nOffset fields. nLimit and nOffset hold the integers
+** that appear in the original SQL statement after the LIMIT and OFFSET
+** keywords. Or that hold -1 and 0 if those keywords are omitted.
+** iLimit and iOffset are the integer memory register numbers for
+** counters used to compute the limit and offset. If there is no
+** limit and/or offset, then iLimit and iOffset are negative.
+**
+** This routine changes the values if iLimit and iOffset only if
+** a limit or offset is defined by nLimit and nOffset. iLimit and
+** iOffset should have been preset to appropriate default values
+** (usually but not always -1) prior to calling this routine.
+** Only if nLimit>=0 or nOffset>0 do the limit registers get
+** redefined. The UNION ALL operator uses this property to force
+** the reuse of the same limit and offset registers across multiple
+** SELECT statements.
+*/
+static void computeLimitRegisters(Parse *pParse, Select *p){
+ /*
+ ** If the comparison is p->nLimit>0 then "LIMIT 0" shows
+ ** all rows. It is the same as no limit. If the comparision is
+ ** p->nLimit>=0 then "LIMIT 0" show no rows at all.
+ ** "LIMIT -1" always shows all rows. There is some
+ ** contraversy about what the correct behavior should be.
+ ** The current implementation interprets "LIMIT 0" to mean
+ ** no rows.
+ */
+ if( p->nLimit>=0 ){
+ int iMem = pParse->nMem++;
+ Vdbe *v = sqliteGetVdbe(pParse);
+ if( v==0 ) return;
+ sqliteVdbeAddOp(v, OP_Integer, -p->nLimit, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, iMem, 1);
+ p->iLimit = iMem;
+ }
+ if( p->nOffset>0 ){
+ int iMem = pParse->nMem++;
+ Vdbe *v = sqliteGetVdbe(pParse);
+ if( v==0 ) return;
+ sqliteVdbeAddOp(v, OP_Integer, -p->nOffset, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, iMem, 1);
+ p->iOffset = iMem;
+ }
+}
+
+/*
+** This routine is called to process a query that is really the union
+** or intersection of two or more separate queries.
+**
+** "p" points to the right-most of the two queries. the query on the
+** left is p->pPrior. The left query could also be a compound query
+** in which case this routine will be called recursively.
+**
+** The results of the total query are to be written into a destination
+** of type eDest with parameter iParm.
+**
+** Example 1: Consider a three-way compound SQL statement.
+**
+** SELECT a FROM t1 UNION SELECT b FROM t2 UNION SELECT c FROM t3
+**
+** This statement is parsed up as follows:
+**
+** SELECT c FROM t3
+** |
+** `-----> SELECT b FROM t2
+** |
+** `------> SELECT a FROM t1
+**
+** The arrows in the diagram above represent the Select.pPrior pointer.
+** So if this routine is called with p equal to the t3 query, then
+** pPrior will be the t2 query. p->op will be TK_UNION in this case.
+**
+** Notice that because of the way SQLite parses compound SELECTs, the
+** individual selects always group from left to right.
+*/
+static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
+ int rc; /* Success code from a subroutine */
+ Select *pPrior; /* Another SELECT immediately to our left */
+ Vdbe *v; /* Generate code to this VDBE */
+
+ /* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only
+ ** the last SELECT in the series may have an ORDER BY or LIMIT.
+ */
+ if( p==0 || p->pPrior==0 ) return 1;
+ pPrior = p->pPrior;
+ if( pPrior->pOrderBy ){
+ sqliteErrorMsg(pParse,"ORDER BY clause should come after %s not before",
+ selectOpName(p->op));
+ return 1;
+ }
+ if( pPrior->nLimit>=0 || pPrior->nOffset>0 ){
+ sqliteErrorMsg(pParse,"LIMIT clause should come after %s not before",
+ selectOpName(p->op));
+ return 1;
+ }
+
+ /* Make sure we have a valid query engine. If not, create a new one.
+ */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) return 1;
+
+ /* Create the destination temporary table if necessary
+ */
+ if( eDest==SRT_TempTable ){
+ sqliteVdbeAddOp(v, OP_OpenTemp, iParm, 0);
+ eDest = SRT_Table;
+ }
+
+ /* Generate code for the left and right SELECT statements.
+ */
+ switch( p->op ){
+ case TK_ALL: {
+ if( p->pOrderBy==0 ){
+ pPrior->nLimit = p->nLimit;
+ pPrior->nOffset = p->nOffset;
+ rc = sqliteSelect(pParse, pPrior, eDest, iParm, 0, 0, 0);
+ if( rc ) return rc;
+ p->pPrior = 0;
+ p->iLimit = pPrior->iLimit;
+ p->iOffset = pPrior->iOffset;
+ p->nLimit = -1;
+ p->nOffset = 0;
+ rc = sqliteSelect(pParse, p, eDest, iParm, 0, 0, 0);
+ p->pPrior = pPrior;
+ if( rc ) return rc;
+ break;
+ }
+ /* For UNION ALL ... ORDER BY fall through to the next case */
+ }
+ case TK_EXCEPT:
+ case TK_UNION: {
+ int unionTab; /* Cursor number of the temporary table holding result */
+ int op; /* One of the SRT_ operations to apply to self */
+ int priorOp; /* The SRT_ operation to apply to prior selects */
+ int nLimit, nOffset; /* Saved values of p->nLimit and p->nOffset */
+ ExprList *pOrderBy; /* The ORDER BY clause for the right SELECT */
+
+ priorOp = p->op==TK_ALL ? SRT_Table : SRT_Union;
+ if( eDest==priorOp && p->pOrderBy==0 && p->nLimit<0 && p->nOffset==0 ){
+ /* We can reuse a temporary table generated by a SELECT to our
+ ** right.
+ */
+ unionTab = iParm;
+ }else{
+ /* We will need to create our own temporary table to hold the
+ ** intermediate results.
+ */
+ unionTab = pParse->nTab++;
+ if( p->pOrderBy
+ && matchOrderbyToColumn(pParse, p, p->pOrderBy, unionTab, 1) ){
+ return 1;
+ }
+ if( p->op!=TK_ALL ){
+ sqliteVdbeAddOp(v, OP_OpenTemp, unionTab, 1);
+ sqliteVdbeAddOp(v, OP_KeyAsData, unionTab, 1);
+ }else{
+ sqliteVdbeAddOp(v, OP_OpenTemp, unionTab, 0);
+ }
+ }
+
+ /* Code the SELECT statements to our left
+ */
+ rc = sqliteSelect(pParse, pPrior, priorOp, unionTab, 0, 0, 0);
+ if( rc ) return rc;
+
+ /* Code the current SELECT statement
+ */
+ switch( p->op ){
+ case TK_EXCEPT: op = SRT_Except; break;
+ case TK_UNION: op = SRT_Union; break;
+ case TK_ALL: op = SRT_Table; break;
+ }
+ p->pPrior = 0;
+ pOrderBy = p->pOrderBy;
+ p->pOrderBy = 0;
+ nLimit = p->nLimit;
+ p->nLimit = -1;
+ nOffset = p->nOffset;
+ p->nOffset = 0;
+ rc = sqliteSelect(pParse, p, op, unionTab, 0, 0, 0);
+ p->pPrior = pPrior;
+ p->pOrderBy = pOrderBy;
+ p->nLimit = nLimit;
+ p->nOffset = nOffset;
+ if( rc ) return rc;
+
+ /* Convert the data in the temporary table into whatever form
+ ** it is that we currently need.
+ */
+ if( eDest!=priorOp || unionTab!=iParm ){
+ int iCont, iBreak, iStart;
+ assert( p->pEList );
+ if( eDest==SRT_Callback ){
+ generateColumnNames(pParse, 0, p->pEList);
+ generateColumnTypes(pParse, p->pSrc, p->pEList);
+ }
+ iBreak = sqliteVdbeMakeLabel(v);
+ iCont = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_Rewind, unionTab, iBreak);
+ computeLimitRegisters(pParse, p);
+ iStart = sqliteVdbeCurrentAddr(v);
+ multiSelectSortOrder(p, p->pOrderBy);
+ rc = selectInnerLoop(pParse, p, p->pEList, unionTab, p->pEList->nExpr,
+ p->pOrderBy, -1, eDest, iParm,
+ iCont, iBreak);
+ if( rc ) return 1;
+ sqliteVdbeResolveLabel(v, iCont);
+ sqliteVdbeAddOp(v, OP_Next, unionTab, iStart);
+ sqliteVdbeResolveLabel(v, iBreak);
+ sqliteVdbeAddOp(v, OP_Close, unionTab, 0);
+ if( p->pOrderBy ){
+ generateSortTail(p, v, p->pEList->nExpr, eDest, iParm);
+ }
+ }
+ break;
+ }
+ case TK_INTERSECT: {
+ int tab1, tab2;
+ int iCont, iBreak, iStart;
+ int nLimit, nOffset;
+
+ /* INTERSECT is different from the others since it requires
+ ** two temporary tables. Hence it has its own case. Begin
+ ** by allocating the tables we will need.
+ */
+ tab1 = pParse->nTab++;
+ tab2 = pParse->nTab++;
+ if( p->pOrderBy && matchOrderbyToColumn(pParse,p,p->pOrderBy,tab1,1) ){
+ return 1;
+ }
+ sqliteVdbeAddOp(v, OP_OpenTemp, tab1, 1);
+ sqliteVdbeAddOp(v, OP_KeyAsData, tab1, 1);
+
+ /* Code the SELECTs to our left into temporary table "tab1".
+ */
+ rc = sqliteSelect(pParse, pPrior, SRT_Union, tab1, 0, 0, 0);
+ if( rc ) return rc;
+
+ /* Code the current SELECT into temporary table "tab2"
+ */
+ sqliteVdbeAddOp(v, OP_OpenTemp, tab2, 1);
+ sqliteVdbeAddOp(v, OP_KeyAsData, tab2, 1);
+ p->pPrior = 0;
+ nLimit = p->nLimit;
+ p->nLimit = -1;
+ nOffset = p->nOffset;
+ p->nOffset = 0;
+ rc = sqliteSelect(pParse, p, SRT_Union, tab2, 0, 0, 0);
+ p->pPrior = pPrior;
+ p->nLimit = nLimit;
+ p->nOffset = nOffset;
+ if( rc ) return rc;
+
+ /* Generate code to take the intersection of the two temporary
+ ** tables.
+ */
+ assert( p->pEList );
+ if( eDest==SRT_Callback ){
+ generateColumnNames(pParse, 0, p->pEList);
+ generateColumnTypes(pParse, p->pSrc, p->pEList);
+ }
+ iBreak = sqliteVdbeMakeLabel(v);
+ iCont = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_Rewind, tab1, iBreak);
+ computeLimitRegisters(pParse, p);
+ iStart = sqliteVdbeAddOp(v, OP_FullKey, tab1, 0);
+ sqliteVdbeAddOp(v, OP_NotFound, tab2, iCont);
+ multiSelectSortOrder(p, p->pOrderBy);
+ rc = selectInnerLoop(pParse, p, p->pEList, tab1, p->pEList->nExpr,
+ p->pOrderBy, -1, eDest, iParm,
+ iCont, iBreak);
+ if( rc ) return 1;
+ sqliteVdbeResolveLabel(v, iCont);
+ sqliteVdbeAddOp(v, OP_Next, tab1, iStart);
+ sqliteVdbeResolveLabel(v, iBreak);
+ sqliteVdbeAddOp(v, OP_Close, tab2, 0);
+ sqliteVdbeAddOp(v, OP_Close, tab1, 0);
+ if( p->pOrderBy ){
+ generateSortTail(p, v, p->pEList->nExpr, eDest, iParm);
+ }
+ break;
+ }
+ }
+ assert( p->pEList && pPrior->pEList );
+ if( p->pEList->nExpr!=pPrior->pEList->nExpr ){
+ sqliteErrorMsg(pParse, "SELECTs to the left and right of %s"
+ " do not have the same number of result columns", selectOpName(p->op));
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** Scan through the expression pExpr. Replace every reference to
+** a column in table number iTable with a copy of the iColumn-th
+** entry in pEList. (But leave references to the ROWID column
+** unchanged.)
+**
+** This routine is part of the flattening procedure. A subquery
+** whose result set is defined by pEList appears as entry in the
+** FROM clause of a SELECT such that the VDBE cursor assigned to that
+** FORM clause entry is iTable. This routine make the necessary
+** changes to pExpr so that it refers directly to the source table
+** of the subquery rather the result set of the subquery.
+*/
+static void substExprList(ExprList*,int,ExprList*); /* Forward Decl */
+static void substExpr(Expr *pExpr, int iTable, ExprList *pEList){
+ if( pExpr==0 ) return;
+ if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){
+ if( pExpr->iColumn<0 ){
+ pExpr->op = TK_NULL;
+ }else{
+ Expr *pNew;
+ assert( pEList!=0 && pExpr->iColumn<pEList->nExpr );
+ assert( pExpr->pLeft==0 && pExpr->pRight==0 && pExpr->pList==0 );
+ pNew = pEList->a[pExpr->iColumn].pExpr;
+ assert( pNew!=0 );
+ pExpr->op = pNew->op;
+ pExpr->dataType = pNew->dataType;
+ assert( pExpr->pLeft==0 );
+ pExpr->pLeft = sqliteExprDup(pNew->pLeft);
+ assert( pExpr->pRight==0 );
+ pExpr->pRight = sqliteExprDup(pNew->pRight);
+ assert( pExpr->pList==0 );
+ pExpr->pList = sqliteExprListDup(pNew->pList);
+ pExpr->iTable = pNew->iTable;
+ pExpr->iColumn = pNew->iColumn;
+ pExpr->iAgg = pNew->iAgg;
+ sqliteTokenCopy(&pExpr->token, &pNew->token);
+ sqliteTokenCopy(&pExpr->span, &pNew->span);
+ }
+ }else{
+ substExpr(pExpr->pLeft, iTable, pEList);
+ substExpr(pExpr->pRight, iTable, pEList);
+ substExprList(pExpr->pList, iTable, pEList);
+ }
+}
+static void
+substExprList(ExprList *pList, int iTable, ExprList *pEList){
+ int i;
+ if( pList==0 ) return;
+ for(i=0; i<pList->nExpr; i++){
+ substExpr(pList->a[i].pExpr, iTable, pEList);
+ }
+}
+
+/*
+** This routine attempts to flatten subqueries in order to speed
+** execution. It returns 1 if it makes changes and 0 if no flattening
+** occurs.
+**
+** To understand the concept of flattening, consider the following
+** query:
+**
+** SELECT a FROM (SELECT x+y AS a FROM t1 WHERE z<100) WHERE a>5
+**
+** The default way of implementing this query is to execute the
+** subquery first and store the results in a temporary table, then
+** run the outer query on that temporary table. This requires two
+** passes over the data. Furthermore, because the temporary table
+** has no indices, the WHERE clause on the outer query cannot be
+** optimized.
+**
+** This routine attempts to rewrite queries such as the above into
+** a single flat select, like this:
+**
+** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5
+**
+** The code generated for this simpification gives the same result
+** but only has to scan the data once. And because indices might
+** exist on the table t1, a complete scan of the data might be
+** avoided.
+**
+** Flattening is only attempted if all of the following are true:
+**
+** (1) The subquery and the outer query do not both use aggregates.
+**
+** (2) The subquery is not an aggregate or the outer query is not a join.
+**
+** (3) The subquery is not the right operand of a left outer join, or
+** the subquery is not itself a join. (Ticket #306)
+**
+** (4) The subquery is not DISTINCT or the outer query is not a join.
+**
+** (5) The subquery is not DISTINCT or the outer query does not use
+** aggregates.
+**
+** (6) The subquery does not use aggregates or the outer query is not
+** DISTINCT.
+**
+** (7) The subquery has a FROM clause.
+**
+** (8) The subquery does not use LIMIT or the outer query is not a join.
+**
+** (9) The subquery does not use LIMIT or the outer query does not use
+** aggregates.
+**
+** (10) The subquery does not use aggregates or the outer query does not
+** use LIMIT.
+**
+** (11) The subquery and the outer query do not both have ORDER BY clauses.
+**
+** (12) The subquery is not the right term of a LEFT OUTER JOIN or the
+** subquery has no WHERE clause. (added by ticket #350)
+**
+** In this routine, the "p" parameter is a pointer to the outer query.
+** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query
+** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates.
+**
+** If flattening is not attempted, this routine is a no-op and returns 0.
+** If flattening is attempted this routine returns 1.
+**
+** All of the expression analysis must occur on both the outer query and
+** the subquery before this routine runs.
+*/
+static int flattenSubquery(
+ Parse *pParse, /* The parsing context */
+ Select *p, /* The parent or outer SELECT statement */
+ int iFrom, /* Index in p->pSrc->a[] of the inner subquery */
+ int isAgg, /* True if outer SELECT uses aggregate functions */
+ int subqueryIsAgg /* True if the subquery uses aggregate functions */
+){
+ Select *pSub; /* The inner query or "subquery" */
+ SrcList *pSrc; /* The FROM clause of the outer query */
+ SrcList *pSubSrc; /* The FROM clause of the subquery */
+ ExprList *pList; /* The result set of the outer query */
+ int iParent; /* VDBE cursor number of the pSub result set temp table */
+ int i;
+ Expr *pWhere;
+
+ /* Check to see if flattening is permitted. Return 0 if not.
+ */
+ if( p==0 ) return 0;
+ pSrc = p->pSrc;
+ assert( pSrc && iFrom>=0 && iFrom<pSrc->nSrc );
+ pSub = pSrc->a[iFrom].pSelect;
+ assert( pSub!=0 );
+ if( isAgg && subqueryIsAgg ) return 0;
+ if( subqueryIsAgg && pSrc->nSrc>1 ) return 0;
+ pSubSrc = pSub->pSrc;
+ assert( pSubSrc );
+ if( pSubSrc->nSrc==0 ) return 0;
+ if( (pSub->isDistinct || pSub->nLimit>=0) && (pSrc->nSrc>1 || isAgg) ){
+ return 0;
+ }
+ if( (p->isDistinct || p->nLimit>=0) && subqueryIsAgg ) return 0;
+ if( p->pOrderBy && pSub->pOrderBy ) return 0;
+
+ /* Restriction 3: If the subquery is a join, make sure the subquery is
+ ** not used as the right operand of an outer join. Examples of why this
+ ** is not allowed:
+ **
+ ** t1 LEFT OUTER JOIN (t2 JOIN t3)
+ **
+ ** If we flatten the above, we would get
+ **
+ ** (t1 LEFT OUTER JOIN t2) JOIN t3
+ **
+ ** which is not at all the same thing.
+ */
+ if( pSubSrc->nSrc>1 && iFrom>0 && (pSrc->a[iFrom-1].jointype & JT_OUTER)!=0 ){
+ return 0;
+ }
+
+ /* Restriction 12: If the subquery is the right operand of a left outer
+ ** join, make sure the subquery has no WHERE clause.
+ ** An examples of why this is not allowed:
+ **
+ ** t1 LEFT OUTER JOIN (SELECT * FROM t2 WHERE t2.x>0)
+ **
+ ** If we flatten the above, we would get
+ **
+ ** (t1 LEFT OUTER JOIN t2) WHERE t2.x>0
+ **
+ ** But the t2.x>0 test will always fail on a NULL row of t2, which
+ ** effectively converts the OUTER JOIN into an INNER JOIN.
+ */
+ if( iFrom>0 && (pSrc->a[iFrom-1].jointype & JT_OUTER)!=0
+ && pSub->pWhere!=0 ){
+ return 0;
+ }
+
+ /* If we reach this point, it means flattening is permitted for the
+ ** iFrom-th entry of the FROM clause in the outer query.
+ */
+
+ /* Move all of the FROM elements of the subquery into the
+ ** the FROM clause of the outer query. Before doing this, remember
+ ** the cursor number for the original outer query FROM element in
+ ** iParent. The iParent cursor will never be used. Subsequent code
+ ** will scan expressions looking for iParent references and replace
+ ** those references with expressions that resolve to the subquery FROM
+ ** elements we are now copying in.
+ */
+ iParent = pSrc->a[iFrom].iCursor;
+ {
+ int nSubSrc = pSubSrc->nSrc;
+ int jointype = pSrc->a[iFrom].jointype;
+
+ if( pSrc->a[iFrom].pTab && pSrc->a[iFrom].pTab->isTransient ){
+ sqliteDeleteTable(0, pSrc->a[iFrom].pTab);
+ }
+ sqliteFree(pSrc->a[iFrom].zDatabase);
+ sqliteFree(pSrc->a[iFrom].zName);
+ sqliteFree(pSrc->a[iFrom].zAlias);
+ if( nSubSrc>1 ){
+ int extra = nSubSrc - 1;
+ for(i=1; i<nSubSrc; i++){
+ pSrc = sqliteSrcListAppend(pSrc, 0, 0);
+ }
+ p->pSrc = pSrc;
+ for(i=pSrc->nSrc-1; i-extra>=iFrom; i--){
+ pSrc->a[i] = pSrc->a[i-extra];
+ }
+ }
+ for(i=0; i<nSubSrc; i++){
+ pSrc->a[i+iFrom] = pSubSrc->a[i];
+ memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
+ }
+ pSrc->a[iFrom+nSubSrc-1].jointype = jointype;
+ }
+
+ /* Now begin substituting subquery result set expressions for
+ ** references to the iParent in the outer query.
+ **
+ ** Example:
+ **
+ ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b;
+ ** \ \_____________ subquery __________/ /
+ ** \_____________________ outer query ______________________________/
+ **
+ ** We look at every expression in the outer query and every place we see
+ ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10".
+ */
+ substExprList(p->pEList, iParent, pSub->pEList);
+ pList = p->pEList;
+ for(i=0; i<pList->nExpr; i++){
+ Expr *pExpr;
+ if( pList->a[i].zName==0 && (pExpr = pList->a[i].pExpr)->span.z!=0 ){
+ pList->a[i].zName = sqliteStrNDup(pExpr->span.z, pExpr->span.n);
+ }
+ }
+ if( isAgg ){
+ substExprList(p->pGroupBy, iParent, pSub->pEList);
+ substExpr(p->pHaving, iParent, pSub->pEList);
+ }
+ if( pSub->pOrderBy ){
+ assert( p->pOrderBy==0 );
+ p->pOrderBy = pSub->pOrderBy;
+ pSub->pOrderBy = 0;
+ }else if( p->pOrderBy ){
+ substExprList(p->pOrderBy, iParent, pSub->pEList);
+ }
+ if( pSub->pWhere ){
+ pWhere = sqliteExprDup(pSub->pWhere);
+ }else{
+ pWhere = 0;
+ }
+ if( subqueryIsAgg ){
+ assert( p->pHaving==0 );
+ p->pHaving = p->pWhere;
+ p->pWhere = pWhere;
+ substExpr(p->pHaving, iParent, pSub->pEList);
+ if( pSub->pHaving ){
+ Expr *pHaving = sqliteExprDup(pSub->pHaving);
+ if( p->pHaving ){
+ p->pHaving = sqliteExpr(TK_AND, p->pHaving, pHaving, 0);
+ }else{
+ p->pHaving = pHaving;
+ }
+ }
+ assert( p->pGroupBy==0 );
+ p->pGroupBy = sqliteExprListDup(pSub->pGroupBy);
+ }else if( p->pWhere==0 ){
+ p->pWhere = pWhere;
+ }else{
+ substExpr(p->pWhere, iParent, pSub->pEList);
+ if( pWhere ){
+ p->pWhere = sqliteExpr(TK_AND, p->pWhere, pWhere, 0);
+ }
+ }
+
+ /* The flattened query is distinct if either the inner or the
+ ** outer query is distinct.
+ */
+ p->isDistinct = p->isDistinct || pSub->isDistinct;
+
+ /* Transfer the limit expression from the subquery to the outer
+ ** query.
+ */
+ if( pSub->nLimit>=0 ){
+ if( p->nLimit<0 ){
+ p->nLimit = pSub->nLimit;
+ }else if( p->nLimit+p->nOffset > pSub->nLimit+pSub->nOffset ){
+ p->nLimit = pSub->nLimit + pSub->nOffset - p->nOffset;
+ }
+ }
+ p->nOffset += pSub->nOffset;
+
+ /* Finially, delete what is left of the subquery and return
+ ** success.
+ */
+ sqliteSelectDelete(pSub);
+ return 1;
+}
+
+/*
+** Analyze the SELECT statement passed in as an argument to see if it
+** is a simple min() or max() query. If it is and this query can be
+** satisfied using a single seek to the beginning or end of an index,
+** then generate the code for this SELECT and return 1. If this is not a
+** simple min() or max() query, then return 0;
+**
+** A simply min() or max() query looks like this:
+**
+** SELECT min(a) FROM table;
+** SELECT max(a) FROM table;
+**
+** The query may have only a single table in its FROM argument. There
+** can be no GROUP BY or HAVING or WHERE clauses. The result set must
+** be the min() or max() of a single column of the table. The column
+** in the min() or max() function must be indexed.
+**
+** The parameters to this routine are the same as for sqliteSelect().
+** See the header comment on that routine for additional information.
+*/
+static int simpleMinMaxQuery(Parse *pParse, Select *p, int eDest, int iParm){
+ Expr *pExpr;
+ int iCol;
+ Table *pTab;
+ Index *pIdx;
+ int base;
+ Vdbe *v;
+ int seekOp;
+ int cont;
+ ExprList *pEList, *pList, eList;
+ struct ExprList_item eListItem;
+ SrcList *pSrc;
+
+
+ /* Check to see if this query is a simple min() or max() query. Return
+ ** zero if it is not.
+ */
+ if( p->pGroupBy || p->pHaving || p->pWhere ) return 0;
+ pSrc = p->pSrc;
+ if( pSrc->nSrc!=1 ) return 0;
+ pEList = p->pEList;
+ if( pEList->nExpr!=1 ) return 0;
+ pExpr = pEList->a[0].pExpr;
+ if( pExpr->op!=TK_AGG_FUNCTION ) return 0;
+ pList = pExpr->pList;
+ if( pList==0 || pList->nExpr!=1 ) return 0;
+ if( pExpr->token.n!=3 ) return 0;
+ if( sqliteStrNICmp(pExpr->token.z,"min",3)==0 ){
+ seekOp = OP_Rewind;
+ }else if( sqliteStrNICmp(pExpr->token.z,"max",3)==0 ){
+ seekOp = OP_Last;
+ }else{
+ return 0;
+ }
+ pExpr = pList->a[0].pExpr;
+ if( pExpr->op!=TK_COLUMN ) return 0;
+ iCol = pExpr->iColumn;
+ pTab = pSrc->a[0].pTab;
+
+ /* If we get to here, it means the query is of the correct form.
+ ** Check to make sure we have an index and make pIdx point to the
+ ** appropriate index. If the min() or max() is on an INTEGER PRIMARY
+ ** key column, no index is necessary so set pIdx to NULL. If no
+ ** usable index is found, return 0.
+ */
+ if( iCol<0 ){
+ pIdx = 0;
+ }else{
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ assert( pIdx->nColumn>=1 );
+ if( pIdx->aiColumn[0]==iCol ) break;
+ }
+ if( pIdx==0 ) return 0;
+ }
+
+ /* Identify column types if we will be using the callback. This
+ ** step is skipped if the output is going to a table or a memory cell.
+ ** The column names have already been generated in the calling function.
+ */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) return 0;
+ if( eDest==SRT_Callback ){
+ generateColumnTypes(pParse, p->pSrc, p->pEList);
+ }
+
+ /* If the output is destined for a temporary table, open that table.
+ */
+ if( eDest==SRT_TempTable ){
+ sqliteVdbeAddOp(v, OP_OpenTemp, iParm, 0);
+ }
+
+ /* Generating code to find the min or the max. Basically all we have
+ ** to do is find the first or the last entry in the chosen index. If
+ ** the min() or max() is on the INTEGER PRIMARY KEY, then find the first
+ ** or last entry in the main table.
+ */
+ sqliteCodeVerifySchema(pParse, pTab->iDb);
+ base = pSrc->a[0].iCursor;
+ computeLimitRegisters(pParse, p);
+ if( pSrc->a[0].pSelect==0 ){
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenRead, base, pTab->tnum, pTab->zName, 0);
+ }
+ cont = sqliteVdbeMakeLabel(v);
+ if( pIdx==0 ){
+ sqliteVdbeAddOp(v, seekOp, base, 0);
+ }else{
+ sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenRead, base+1, pIdx->tnum, pIdx->zName, P3_STATIC);
+ if( seekOp==OP_Rewind ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_MakeKey, 1, 0);
+ sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
+ seekOp = OP_MoveTo;
+ }
+ sqliteVdbeAddOp(v, seekOp, base+1, 0);
+ sqliteVdbeAddOp(v, OP_IdxRecno, base+1, 0);
+ sqliteVdbeAddOp(v, OP_Close, base+1, 0);
+ sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
+ }
+ eList.nExpr = 1;
+ memset(&eListItem, 0, sizeof(eListItem));
+ eList.a = &eListItem;
+ eList.a[0].pExpr = pExpr;
+ selectInnerLoop(pParse, p, &eList, 0, 0, 0, -1, eDest, iParm, cont, cont);
+ sqliteVdbeResolveLabel(v, cont);
+ sqliteVdbeAddOp(v, OP_Close, base, 0);
+
+ return 1;
+}
+
+/*
+** Generate code for the given SELECT statement.
+**
+** The results are distributed in various ways depending on the
+** value of eDest and iParm.
+**
+** eDest Value Result
+** ------------ -------------------------------------------
+** SRT_Callback Invoke the callback for each row of the result.
+**
+** SRT_Mem Store first result in memory cell iParm
+**
+** SRT_Set Store results as keys of a table with cursor iParm
+**
+** SRT_Union Store results as a key in a temporary table iParm
+**
+** SRT_Except Remove results from the temporary table iParm.
+**
+** SRT_Table Store results in temporary table iParm
+**
+** The table above is incomplete. Additional eDist value have be added
+** since this comment was written. See the selectInnerLoop() function for
+** a complete listing of the allowed values of eDest and their meanings.
+**
+** This routine returns the number of errors. If any errors are
+** encountered, then an appropriate error message is left in
+** pParse->zErrMsg.
+**
+** This routine does NOT free the Select structure passed in. The
+** calling function needs to do that.
+**
+** The pParent, parentTab, and *pParentAgg fields are filled in if this
+** SELECT is a subquery. This routine may try to combine this SELECT
+** with its parent to form a single flat query. In so doing, it might
+** change the parent query from a non-aggregate to an aggregate query.
+** For that reason, the pParentAgg flag is passed as a pointer, so it
+** can be changed.
+**
+** Example 1: The meaning of the pParent parameter.
+**
+** SELECT * FROM t1 JOIN (SELECT x, count(*) FROM t2) JOIN t3;
+** \ \_______ subquery _______/ /
+** \ /
+** \____________________ outer query ___________________/
+**
+** This routine is called for the outer query first. For that call,
+** pParent will be NULL. During the processing of the outer query, this
+** routine is called recursively to handle the subquery. For the recursive
+** call, pParent will point to the outer query. Because the subquery is
+** the second element in a three-way join, the parentTab parameter will
+** be 1 (the 2nd value of a 0-indexed array.)
+*/
+int sqliteSelect(
+ Parse *pParse, /* The parser context */
+ Select *p, /* The SELECT statement being coded. */
+ int eDest, /* How to dispose of the results */
+ int iParm, /* A parameter used by the eDest disposal method */
+ Select *pParent, /* Another SELECT for which this is a sub-query */
+ int parentTab, /* Index in pParent->pSrc of this query */
+ int *pParentAgg /* True if pParent uses aggregate functions */
+){
+ int i;
+ WhereInfo *pWInfo;
+ Vdbe *v;
+ int isAgg = 0; /* True for select lists like "count(*)" */
+ ExprList *pEList; /* List of columns to extract. */
+ SrcList *pTabList; /* List of tables to select from */
+ Expr *pWhere; /* The WHERE clause. May be NULL */
+ ExprList *pOrderBy; /* The ORDER BY clause. May be NULL */
+ ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */
+ Expr *pHaving; /* The HAVING clause. May be NULL */
+ int isDistinct; /* True if the DISTINCT keyword is present */
+ int distinct; /* Table to use for the distinct set */
+ int rc = 1; /* Value to return from this function */
+
+ if( sqlite_malloc_failed || pParse->nErr || p==0 ) return 1;
+ if( sqliteAuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
+
+ /* If there is are a sequence of queries, do the earlier ones first.
+ */
+ if( p->pPrior ){
+ return multiSelect(pParse, p, eDest, iParm);
+ }
+
+ /* Make local copies of the parameters for this query.
+ */
+ pTabList = p->pSrc;
+ pWhere = p->pWhere;
+ pOrderBy = p->pOrderBy;
+ pGroupBy = p->pGroupBy;
+ pHaving = p->pHaving;
+ isDistinct = p->isDistinct;
+
+ /* Allocate VDBE cursors for each table in the FROM clause
+ */
+ sqliteSrcListAssignCursors(pParse, pTabList);
+
+ /*
+ ** Do not even attempt to generate any code if we have already seen
+ ** errors before this routine starts.
+ */
+ if( pParse->nErr>0 ) goto select_end;
+
+ /* Expand any "*" terms in the result set. (For example the "*" in
+ ** "SELECT * FROM t1") The fillInColumnlist() routine also does some
+ ** other housekeeping - see the header comment for details.
+ */
+ if( fillInColumnList(pParse, p) ){
+ goto select_end;
+ }
+ pWhere = p->pWhere;
+ pEList = p->pEList;
+ if( pEList==0 ) goto select_end;
+
+ /* If writing to memory or generating a set
+ ** only a single column may be output.
+ */
+ if( (eDest==SRT_Mem || eDest==SRT_Set) && pEList->nExpr>1 ){
+ sqliteErrorMsg(pParse, "only a single result allowed for "
+ "a SELECT that is part of an expression");
+ goto select_end;
+ }
+
+ /* ORDER BY is ignored for some destinations.
+ */
+ switch( eDest ){
+ case SRT_Union:
+ case SRT_Except:
+ case SRT_Discard:
+ pOrderBy = 0;
+ break;
+ default:
+ break;
+ }
+
+ /* At this point, we should have allocated all the cursors that we
+ ** need to handle subquerys and temporary tables.
+ **
+ ** Resolve the column names and do a semantics check on all the expressions.
+ */
+ for(i=0; i<pEList->nExpr; i++){
+ if( sqliteExprResolveIds(pParse, pTabList, 0, pEList->a[i].pExpr) ){
+ goto select_end;
+ }
+ if( sqliteExprCheck(pParse, pEList->a[i].pExpr, 1, &isAgg) ){
+ goto select_end;
+ }
+ }
+ if( pWhere ){
+ if( sqliteExprResolveIds(pParse, pTabList, pEList, pWhere) ){
+ goto select_end;
+ }
+ if( sqliteExprCheck(pParse, pWhere, 0, 0) ){
+ goto select_end;
+ }
+ }
+ if( pHaving ){
+ if( pGroupBy==0 ){
+ sqliteErrorMsg(pParse, "a GROUP BY clause is required before HAVING");
+ goto select_end;
+ }
+ if( sqliteExprResolveIds(pParse, pTabList, pEList, pHaving) ){
+ goto select_end;
+ }
+ if( sqliteExprCheck(pParse, pHaving, 1, &isAgg) ){
+ goto select_end;
+ }
+ }
+ if( pOrderBy ){
+ for(i=0; i<pOrderBy->nExpr; i++){
+ int iCol;
+ Expr *pE = pOrderBy->a[i].pExpr;
+ if( sqliteExprIsInteger(pE, &iCol) && iCol>0 && iCol<=pEList->nExpr ){
+ sqliteExprDelete(pE);
+ pE = pOrderBy->a[i].pExpr = sqliteExprDup(pEList->a[iCol-1].pExpr);
+ }
+ if( sqliteExprResolveIds(pParse, pTabList, pEList, pE) ){
+ goto select_end;
+ }
+ if( sqliteExprCheck(pParse, pE, isAgg, 0) ){
+ goto select_end;
+ }
+ if( sqliteExprIsConstant(pE) ){
+ if( sqliteExprIsInteger(pE, &iCol)==0 ){
+ sqliteErrorMsg(pParse,
+ "ORDER BY terms must not be non-integer constants");
+ goto select_end;
+ }else if( iCol<=0 || iCol>pEList->nExpr ){
+ sqliteErrorMsg(pParse,
+ "ORDER BY column number %d out of range - should be "
+ "between 1 and %d", iCol, pEList->nExpr);
+ goto select_end;
+ }
+ }
+ }
+ }
+ if( pGroupBy ){
+ for(i=0; i<pGroupBy->nExpr; i++){
+ int iCol;
+ Expr *pE = pGroupBy->a[i].pExpr;
+ if( sqliteExprIsInteger(pE, &iCol) && iCol>0 && iCol<=pEList->nExpr ){
+ sqliteExprDelete(pE);
+ pE = pGroupBy->a[i].pExpr = sqliteExprDup(pEList->a[iCol-1].pExpr);
+ }
+ if( sqliteExprResolveIds(pParse, pTabList, pEList, pE) ){
+ goto select_end;
+ }
+ if( sqliteExprCheck(pParse, pE, isAgg, 0) ){
+ goto select_end;
+ }
+ if( sqliteExprIsConstant(pE) ){
+ if( sqliteExprIsInteger(pE, &iCol)==0 ){
+ sqliteErrorMsg(pParse,
+ "GROUP BY terms must not be non-integer constants");
+ goto select_end;
+ }else if( iCol<=0 || iCol>pEList->nExpr ){
+ sqliteErrorMsg(pParse,
+ "GROUP BY column number %d out of range - should be "
+ "between 1 and %d", iCol, pEList->nExpr);
+ goto select_end;
+ }
+ }
+ }
+ }
+
+ /* Begin generating code.
+ */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) goto select_end;
+
+ /* Identify column names if we will be using them in a callback. This
+ ** step is skipped if the output is going to some other destination.
+ */
+ if( eDest==SRT_Callback ){
+ generateColumnNames(pParse, pTabList, pEList);
+ }
+
+ /* Generate code for all sub-queries in the FROM clause
+ */
+ for(i=0; i<pTabList->nSrc; i++){
+ const char *zSavedAuthContext;
+ int needRestoreContext;
+
+ if( pTabList->a[i].pSelect==0 ) continue;
+ if( pTabList->a[i].zName!=0 ){
+ zSavedAuthContext = pParse->zAuthContext;
+ pParse->zAuthContext = pTabList->a[i].zName;
+ needRestoreContext = 1;
+ }else{
+ needRestoreContext = 0;
+ }
+ sqliteSelect(pParse, pTabList->a[i].pSelect, SRT_TempTable,
+ pTabList->a[i].iCursor, p, i, &isAgg);
+ if( needRestoreContext ){
+ pParse->zAuthContext = zSavedAuthContext;
+ }
+ pTabList = p->pSrc;
+ pWhere = p->pWhere;
+ if( eDest!=SRT_Union && eDest!=SRT_Except && eDest!=SRT_Discard ){
+ pOrderBy = p->pOrderBy;
+ }
+ pGroupBy = p->pGroupBy;
+ pHaving = p->pHaving;
+ isDistinct = p->isDistinct;
+ }
+
+ /* Check for the special case of a min() or max() function by itself
+ ** in the result set.
+ */
+ if( simpleMinMaxQuery(pParse, p, eDest, iParm) ){
+ rc = 0;
+ goto select_end;
+ }
+
+ /* Check to see if this is a subquery that can be "flattened" into its parent.
+ ** If flattening is a possiblity, do so and return immediately.
+ */
+ if( pParent && pParentAgg &&
+ flattenSubquery(pParse, pParent, parentTab, *pParentAgg, isAgg) ){
+ if( isAgg ) *pParentAgg = 1;
+ return rc;
+ }
+
+ /* Set the limiter.
+ */
+ computeLimitRegisters(pParse, p);
+
+ /* Identify column types if we will be using a callback. This
+ ** step is skipped if the output is going to a destination other
+ ** than a callback.
+ **
+ ** We have to do this separately from the creation of column names
+ ** above because if the pTabList contains views then they will not
+ ** have been resolved and we will not know the column types until
+ ** now.
+ */
+ if( eDest==SRT_Callback ){
+ generateColumnTypes(pParse, pTabList, pEList);
+ }
+
+ /* If the output is destined for a temporary table, open that table.
+ */
+ if( eDest==SRT_TempTable ){
+ sqliteVdbeAddOp(v, OP_OpenTemp, iParm, 0);
+ }
+
+ /* Do an analysis of aggregate expressions.
+ */
+ sqliteAggregateInfoReset(pParse);
+ if( isAgg || pGroupBy ){
+ assert( pParse->nAgg==0 );
+ isAgg = 1;
+ for(i=0; i<pEList->nExpr; i++){
+ if( sqliteExprAnalyzeAggregates(pParse, pEList->a[i].pExpr) ){
+ goto select_end;
+ }
+ }
+ if( pGroupBy ){
+ for(i=0; i<pGroupBy->nExpr; i++){
+ if( sqliteExprAnalyzeAggregates(pParse, pGroupBy->a[i].pExpr) ){
+ goto select_end;
+ }
+ }
+ }
+ if( pHaving && sqliteExprAnalyzeAggregates(pParse, pHaving) ){
+ goto select_end;
+ }
+ if( pOrderBy ){
+ for(i=0; i<pOrderBy->nExpr; i++){
+ if( sqliteExprAnalyzeAggregates(pParse, pOrderBy->a[i].pExpr) ){
+ goto select_end;
+ }
+ }
+ }
+ }
+
+ /* Reset the aggregator
+ */
+ if( isAgg ){
+ sqliteVdbeAddOp(v, OP_AggReset, 0, pParse->nAgg);
+ for(i=0; i<pParse->nAgg; i++){
+ FuncDef *pFunc;
+ if( (pFunc = pParse->aAgg[i].pFunc)!=0 && pFunc->xFinalize!=0 ){
+ sqliteVdbeOp3(v, OP_AggInit, 0, i, (char*)pFunc, P3_POINTER);
+ }
+ }
+ if( pGroupBy==0 ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_AggFocus, 0, 0);
+ }
+ }
+
+ /* Initialize the memory cell to NULL
+ */
+ if( eDest==SRT_Mem ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, iParm, 1);
+ }
+
+ /* Open a temporary table to use for the distinct set.
+ */
+ if( isDistinct ){
+ distinct = pParse->nTab++;
+ sqliteVdbeAddOp(v, OP_OpenTemp, distinct, 1);
+ }else{
+ distinct = -1;
+ }
+
+ /* Begin the database scan
+ */
+ pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 0,
+ pGroupBy ? 0 : &pOrderBy);
+ if( pWInfo==0 ) goto select_end;
+
+ /* Use the standard inner loop if we are not dealing with
+ ** aggregates
+ */
+ if( !isAgg ){
+ if( selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, distinct, eDest,
+ iParm, pWInfo->iContinue, pWInfo->iBreak) ){
+ goto select_end;
+ }
+ }
+
+ /* If we are dealing with aggregates, then do the special aggregate
+ ** processing.
+ */
+ else{
+ AggExpr *pAgg;
+ if( pGroupBy ){
+ int lbl1;
+ for(i=0; i<pGroupBy->nExpr; i++){
+ sqliteExprCode(pParse, pGroupBy->a[i].pExpr);
+ }
+ sqliteVdbeAddOp(v, OP_MakeKey, pGroupBy->nExpr, 0);
+ if( pParse->db->file_format>=4 ) sqliteAddKeyType(v, pGroupBy);
+ lbl1 = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_AggFocus, 0, lbl1);
+ for(i=0, pAgg=pParse->aAgg; i<pParse->nAgg; i++, pAgg++){
+ if( pAgg->isAgg ) continue;
+ sqliteExprCode(pParse, pAgg->pExpr);
+ sqliteVdbeAddOp(v, OP_AggSet, 0, i);
+ }
+ sqliteVdbeResolveLabel(v, lbl1);
+ }
+ for(i=0, pAgg=pParse->aAgg; i<pParse->nAgg; i++, pAgg++){
+ Expr *pE;
+ int nExpr;
+ FuncDef *pDef;
+ if( !pAgg->isAgg ) continue;
+ assert( pAgg->pFunc!=0 );
+ assert( pAgg->pFunc->xStep!=0 );
+ pDef = pAgg->pFunc;
+ pE = pAgg->pExpr;
+ assert( pE!=0 );
+ assert( pE->op==TK_AGG_FUNCTION );
+ nExpr = sqliteExprCodeExprList(pParse, pE->pList, pDef->includeTypes);
+ sqliteVdbeAddOp(v, OP_Integer, i, 0);
+ sqliteVdbeOp3(v, OP_AggFunc, 0, nExpr, (char*)pDef, P3_POINTER);
+ }
+ }
+
+ /* End the database scan loop.
+ */
+ sqliteWhereEnd(pWInfo);
+
+ /* If we are processing aggregates, we need to set up a second loop
+ ** over all of the aggregate values and process them.
+ */
+ if( isAgg ){
+ int endagg = sqliteVdbeMakeLabel(v);
+ int startagg;
+ startagg = sqliteVdbeAddOp(v, OP_AggNext, 0, endagg);
+ pParse->useAgg = 1;
+ if( pHaving ){
+ sqliteExprIfFalse(pParse, pHaving, startagg, 1);
+ }
+ if( selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, distinct, eDest,
+ iParm, startagg, endagg) ){
+ goto select_end;
+ }
+ sqliteVdbeAddOp(v, OP_Goto, 0, startagg);
+ sqliteVdbeResolveLabel(v, endagg);
+ sqliteVdbeAddOp(v, OP_Noop, 0, 0);
+ pParse->useAgg = 0;
+ }
+
+ /* If there is an ORDER BY clause, then we need to sort the results
+ ** and send them to the callback one by one.
+ */
+ if( pOrderBy ){
+ generateSortTail(p, v, pEList->nExpr, eDest, iParm);
+ }
+
+ /* If this was a subquery, we have now converted the subquery into a
+ ** temporary table. So delete the subquery structure from the parent
+ ** to prevent this subquery from being evaluated again and to force the
+ ** the use of the temporary table.
+ */
+ if( pParent ){
+ assert( pParent->pSrc->nSrc>parentTab );
+ assert( pParent->pSrc->a[parentTab].pSelect==p );
+ sqliteSelectDelete(p);
+ pParent->pSrc->a[parentTab].pSelect = 0;
+ }
+
+ /* The SELECT was successfully coded. Set the return code to 0
+ ** to indicate no errors.
+ */
+ rc = 0;
+
+ /* Control jumps to here if an error is encountered above, or upon
+ ** successful coding of the SELECT.
+ */
+select_end:
+ sqliteAggregateInfoReset(pParse);
+ return rc;
+}
diff --git a/src/libs/sqlite2/shell.c b/src/libs/sqlite2/shell.c
new file mode 100644
index 00000000..89898ab4
--- /dev/null
+++ b/src/libs/sqlite2/shell.c
@@ -0,0 +1,1354 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code to implement the "sqlite" command line
+** utility for accessing SQLite databases.
+**
+** $Id: shell.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "sqlite.h"
+#include <ctype.h>
+
+#if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__)
+# include <signal.h>
+# include <pwd.h>
+# include <unistd.h>
+# include <sys/types.h>
+#endif
+
+#ifdef __MACOS__
+# include <console.h>
+# include <signal.h>
+# include <unistd.h>
+# include <extras.h>
+# include <Files.h>
+# include <Folders.h>
+#endif
+
+#if defined(HAVE_READLINE) && HAVE_READLINE==1
+# include <readline/readline.h>
+# include <readline/history.h>
+#else
+# define readline(p) local_getline(p,stdin)
+# define add_history(X)
+# define read_history(X)
+# define write_history(X)
+# define stifle_history(X)
+#endif
+
+/* Make sure isatty() has a prototype.
+*/
+extern int isatty();
+
+/*
+** The following is the open SQLite database. We make a pointer
+** to this database a static variable so that it can be accessed
+** by the SIGINT handler to interrupt database processing.
+*/
+static sqlite *db = 0;
+
+/*
+** True if an interrupt (Control-C) has been received.
+*/
+static int seenInterrupt = 0;
+
+/*
+** This is the name of our program. It is set in main(), used
+** in a number of other places, mostly for error messages.
+*/
+static char *Argv0;
+
+/*
+** Prompt strings. Initialized in main. Settable with
+** .prompt main continue
+*/
+static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/
+static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */
+
+
+/*
+** Determines if a string is a number of not.
+*/
+extern int sqliteIsNumber(const char*);
+
+/*
+** This routine reads a line of text from standard input, stores
+** the text in memory obtained from malloc() and returns a pointer
+** to the text. NULL is returned at end of file, or if malloc()
+** fails.
+**
+** The interface is like "readline" but no command-line editing
+** is done.
+*/
+static char *local_getline(char *zPrompt, FILE *in){
+ char *zLine;
+ int nLine;
+ int n;
+ int eol;
+
+ if( zPrompt && *zPrompt ){
+ printf("%s",zPrompt);
+ fflush(stdout);
+ }
+ nLine = 100;
+ zLine = malloc( nLine );
+ if( zLine==0 ) return 0;
+ n = 0;
+ eol = 0;
+ while( !eol ){
+ if( n+100>nLine ){
+ nLine = nLine*2 + 100;
+ zLine = realloc(zLine, nLine);
+ if( zLine==0 ) return 0;
+ }
+ if( fgets(&zLine[n], nLine - n, in)==0 ){
+ if( n==0 ){
+ free(zLine);
+ return 0;
+ }
+ zLine[n] = 0;
+ eol = 1;
+ break;
+ }
+ while( zLine[n] ){ n++; }
+ if( n>0 && zLine[n-1]=='\n' ){
+ n--;
+ zLine[n] = 0;
+ eol = 1;
+ }
+ }
+ zLine = realloc( zLine, n+1 );
+ return zLine;
+}
+
+/*
+** Retrieve a single line of input text. "isatty" is true if text
+** is coming from a terminal. In that case, we issue a prompt and
+** attempt to use "readline" for command-line editing. If "isatty"
+** is false, use "local_getline" instead of "readline" and issue no prompt.
+**
+** zPrior is a string of prior text retrieved. If not the empty
+** string, then issue a continuation prompt.
+*/
+static char *one_input_line(const char *zPrior, FILE *in){
+ char *zPrompt;
+ char *zResult;
+ if( in!=0 ){
+ return local_getline(0, in);
+ }
+ if( zPrior && zPrior[0] ){
+ zPrompt = continuePrompt;
+ }else{
+ zPrompt = mainPrompt;
+ }
+ zResult = readline(zPrompt);
+ if( zResult ) add_history(zResult);
+ return zResult;
+}
+
+struct previous_mode_data {
+ int valid; /* Is there legit data in here? */
+ int mode;
+ int showHeader;
+ int colWidth[100];
+};
+/*
+** An pointer to an instance of this structure is passed from
+** the main program to the callback. This is used to communicate
+** state and mode information.
+*/
+struct callback_data {
+ sqlite *db; /* The database */
+ int echoOn; /* True to echo input commands */
+ int cnt; /* Number of records displayed so far */
+ FILE *out; /* Write results here */
+ int mode; /* An output mode setting */
+ int showHeader; /* True to show column names in List or Column mode */
+ char *zDestTable; /* Name of destination table when MODE_Insert */
+ char separator[20]; /* Separator character for MODE_List */
+ int colWidth[100]; /* Requested width of each column when in column mode*/
+ int actualWidth[100]; /* Actual width of each column */
+ char nullvalue[20]; /* The text to print when a NULL comes back from
+ ** the database */
+ struct previous_mode_data explainPrev;
+ /* Holds the mode information just before
+ ** .explain ON */
+ char outfile[FILENAME_MAX]; /* Filename for *out */
+ const char *zDbFilename; /* name of the database file */
+ char *zKey; /* Encryption key */
+};
+
+/*
+** These are the allowed modes.
+*/
+#define MODE_Line 0 /* One column per line. Blank line between records */
+#define MODE_Column 1 /* One record per line in neat columns */
+#define MODE_List 2 /* One record per line with a separator */
+#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */
+#define MODE_Html 4 /* Generate an XHTML table */
+#define MODE_Insert 5 /* Generate SQL "insert" statements */
+#define MODE_NUM_OF 6 /* The number of modes (not a mode itself) */
+
+char *modeDescr[MODE_NUM_OF] = {
+ "line",
+ "column",
+ "list",
+ "semi",
+ "html",
+ "insert"
+};
+
+/*
+** Number of elements in an array
+*/
+#define ArraySize(X) (sizeof(X)/sizeof(X[0]))
+
+/*
+** Output the given string as a quoted string using SQL quoting conventions.
+*/
+static void output_quoted_string(FILE *out, const char *z){
+ int i;
+ int nSingle = 0;
+ for(i=0; z[i]; i++){
+ if( z[i]=='\'' ) nSingle++;
+ }
+ if( nSingle==0 ){
+ fprintf(out,"'%s'",z);
+ }else{
+ fprintf(out,"'");
+ while( *z ){
+ for(i=0; z[i] && z[i]!='\''; i++){}
+ if( i==0 ){
+ fprintf(out,"''");
+ z++;
+ }else if( z[i]=='\'' ){
+ fprintf(out,"%.*s''",i,z);
+ z += i+1;
+ }else{
+ fprintf(out,"%s",z);
+ break;
+ }
+ }
+ fprintf(out,"'");
+ }
+}
+
+/*
+** Output the given string with characters that are special to
+** HTML escaped.
+*/
+static void output_html_string(FILE *out, const char *z){
+ int i;
+ while( *z ){
+ for(i=0; z[i] && z[i]!='<' && z[i]!='&'; i++){}
+ if( i>0 ){
+ fprintf(out,"%.*s",i,z);
+ }
+ if( z[i]=='<' ){
+ fprintf(out,"&lt;");
+ }else if( z[i]=='&' ){
+ fprintf(out,"&amp;");
+ }else{
+ break;
+ }
+ z += i + 1;
+ }
+}
+
+/*
+** This routine runs when the user presses Ctrl-C
+*/
+static void interrupt_handler(int NotUsed){
+ seenInterrupt = 1;
+ if( db ) sqlite_interrupt(db);
+}
+
+/*
+** This is the callback routine that the SQLite library
+** invokes for each row of a query result.
+*/
+static int callback(void *pArg, int nArg, char **azArg, char **azCol){
+ int i;
+ struct callback_data *p = (struct callback_data*)pArg;
+ switch( p->mode ){
+ case MODE_Line: {
+ int w = 5;
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ int len = strlen(azCol[i]);
+ if( len>w ) w = len;
+ }
+ if( p->cnt++>0 ) fprintf(p->out,"\n");
+ for(i=0; i<nArg; i++){
+ fprintf(p->out,"%*s = %s\n", w, azCol[i],
+ azArg[i] ? azArg[i] : p->nullvalue);
+ }
+ break;
+ }
+ case MODE_Column: {
+ if( p->cnt++==0 ){
+ for(i=0; i<nArg; i++){
+ int w, n;
+ if( i<ArraySize(p->colWidth) ){
+ w = p->colWidth[i];
+ }else{
+ w = 0;
+ }
+ if( w<=0 ){
+ w = strlen(azCol[i] ? azCol[i] : "");
+ if( w<10 ) w = 10;
+ n = strlen(azArg && azArg[i] ? azArg[i] : p->nullvalue);
+ if( w<n ) w = n;
+ }
+ if( i<ArraySize(p->actualWidth) ){
+ p->actualWidth[i] = w;
+ }
+ if( p->showHeader ){
+ fprintf(p->out,"%-*.*s%s",w,w,azCol[i], i==nArg-1 ? "\n": " ");
+ }
+ }
+ if( p->showHeader ){
+ for(i=0; i<nArg; i++){
+ int w;
+ if( i<ArraySize(p->actualWidth) ){
+ w = p->actualWidth[i];
+ }else{
+ w = 10;
+ }
+ fprintf(p->out,"%-*.*s%s",w,w,"-----------------------------------"
+ "----------------------------------------------------------",
+ i==nArg-1 ? "\n": " ");
+ }
+ }
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ int w;
+ if( i<ArraySize(p->actualWidth) ){
+ w = p->actualWidth[i];
+ }else{
+ w = 10;
+ }
+ fprintf(p->out,"%-*.*s%s",w,w,
+ azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": " ");
+ }
+ break;
+ }
+ case MODE_Semi:
+ case MODE_List: {
+ if( p->cnt++==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ fprintf(p->out,"%s%s",azCol[i], i==nArg-1 ? "\n" : p->separator);
+ }
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ char *z = azArg[i];
+ if( z==0 ) z = p->nullvalue;
+ fprintf(p->out, "%s", z);
+ if( i<nArg-1 ){
+ fprintf(p->out, "%s", p->separator);
+ }else if( p->mode==MODE_Semi ){
+ fprintf(p->out, ";\n");
+ }else{
+ fprintf(p->out, "\n");
+ }
+ }
+ break;
+ }
+ case MODE_Html: {
+ if( p->cnt++==0 && p->showHeader ){
+ fprintf(p->out,"<TR>");
+ for(i=0; i<nArg; i++){
+ fprintf(p->out,"<TH>%s</TH>",azCol[i]);
+ }
+ fprintf(p->out,"</TR>\n");
+ }
+ if( azArg==0 ) break;
+ fprintf(p->out,"<TR>");
+ for(i=0; i<nArg; i++){
+ fprintf(p->out,"<TD>");
+ output_html_string(p->out, azArg[i] ? azArg[i] : p->nullvalue);
+ fprintf(p->out,"</TD>\n");
+ }
+ fprintf(p->out,"</TR>\n");
+ break;
+ }
+ case MODE_Insert: {
+ if( azArg==0 ) break;
+ fprintf(p->out,"INSERT INTO %s VALUES(",p->zDestTable);
+ for(i=0; i<nArg; i++){
+ char *zSep = i>0 ? ",": "";
+ if( azArg[i]==0 ){
+ fprintf(p->out,"%sNULL",zSep);
+ }else if( sqliteIsNumber(azArg[i]) ){
+ fprintf(p->out,"%s%s",zSep, azArg[i]);
+ }else{
+ if( zSep[0] ) fprintf(p->out,"%s",zSep);
+ output_quoted_string(p->out, azArg[i]);
+ }
+ }
+ fprintf(p->out,");\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
+** Set the destination table field of the callback_data structure to
+** the name of the table given. Escape any quote characters in the
+** table name.
+*/
+static void set_table_name(struct callback_data *p, const char *zName){
+ int i, n;
+ int needQuote;
+ char *z;
+
+ if( p->zDestTable ){
+ free(p->zDestTable);
+ p->zDestTable = 0;
+ }
+ if( zName==0 ) return;
+ needQuote = !isalpha(*zName) && *zName!='_';
+ for(i=n=0; zName[i]; i++, n++){
+ if( !isalnum(zName[i]) && zName[i]!='_' ){
+ needQuote = 1;
+ if( zName[i]=='\'' ) n++;
+ }
+ }
+ if( needQuote ) n += 2;
+ z = p->zDestTable = malloc( n+1 );
+ if( z==0 ){
+ fprintf(stderr,"Out of memory!\n");
+ exit(1);
+ }
+ n = 0;
+ if( needQuote ) z[n++] = '\'';
+ for(i=0; zName[i]; i++){
+ z[n++] = zName[i];
+ if( zName[i]=='\'' ) z[n++] = '\'';
+ }
+ if( needQuote ) z[n++] = '\'';
+ z[n] = 0;
+}
+
+/*
+** This is a different callback routine used for dumping the database.
+** Each row received by this callback consists of a table name,
+** the table type ("index" or "table") and SQL to create the table.
+** This routine should print text sufficient to recreate the table.
+*/
+static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
+ struct callback_data *p = (struct callback_data *)pArg;
+ if( nArg!=3 ) return 1;
+ fprintf(p->out, "%s;\n", azArg[2]);
+ if( strcmp(azArg[1],"table")==0 ){
+ struct callback_data d2;
+ d2 = *p;
+ d2.mode = MODE_Insert;
+ d2.zDestTable = 0;
+ set_table_name(&d2, azArg[0]);
+ sqlite_exec_printf(p->db,
+ "SELECT * FROM '%q'",
+ callback, &d2, 0, azArg[0]
+ );
+ set_table_name(&d2, 0);
+ }
+ return 0;
+}
+
+/*
+** Text of a help message
+*/
+static char zHelp[] =
+ ".databases List names and files of attached databases\n"
+ ".dump ?TABLE? ... Dump the database in a text format\n"
+ ".echo ON|OFF Turn command echo on or off\n"
+ ".exit Exit this program\n"
+ ".explain ON|OFF Turn output mode suitable for EXPLAIN on or off.\n"
+ ".header(s) ON|OFF Turn display of headers on or off\n"
+ ".help Show this message\n"
+ ".indices TABLE Show names of all indices on TABLE\n"
+ ".mode MODE Set mode to one of \"line(s)\", \"column(s)\", \n"
+ " \"insert\", \"list\", or \"html\"\n"
+ ".mode insert TABLE Generate SQL insert statements for TABLE\n"
+ ".nullvalue STRING Print STRING instead of nothing for NULL data\n"
+ ".output FILENAME Send output to FILENAME\n"
+ ".output stdout Send output to the screen\n"
+ ".prompt MAIN CONTINUE Replace the standard prompts\n"
+ ".quit Exit this program\n"
+ ".read FILENAME Execute SQL in FILENAME\n"
+#ifdef SQLITE_HAS_CODEC
+ ".rekey OLD NEW NEW Change the encryption key\n"
+#endif
+ ".schema ?TABLE? Show the CREATE statements\n"
+ ".separator STRING Change separator string for \"list\" mode\n"
+ ".show Show the current values for various settings\n"
+ ".tables ?PATTERN? List names of tables matching a pattern\n"
+ ".timeout MS Try opening locked tables for MS milliseconds\n"
+ ".width NUM NUM ... Set column widths for \"column\" mode\n"
+;
+
+/* Forward reference */
+static void process_input(struct callback_data *p, FILE *in);
+
+/*
+** Make sure the database is open. If it is not, then open it. If
+** the database fails to open, print an error message and exit.
+*/
+static void open_db(struct callback_data *p){
+ if( p->db==0 ){
+ char *zErrMsg = 0;
+#ifdef SQLITE_HAS_CODEC
+ int n = p->zKey ? strlen(p->zKey) : 0;
+ db = p->db = sqlite_open_encrypted(p->zDbFilename, p->zKey, n, 0, &zErrMsg);
+#else
+ db = p->db = sqlite_open(p->zDbFilename, 0, &zErrMsg);
+#endif
+ if( p->db==0 ){
+ if( zErrMsg ){
+ fprintf(stderr,"Unable to open database \"%s\": %s\n",
+ p->zDbFilename, zErrMsg);
+ }else{
+ fprintf(stderr,"Unable to open database %s\n", p->zDbFilename);
+ }
+ exit(1);
+ }
+ }
+}
+
+/*
+** If an input line begins with "." then invoke this routine to
+** process that line.
+**
+** Return 1 to exit and 0 to continue.
+*/
+static int do_meta_command(char *zLine, struct callback_data *p){
+ int i = 1;
+ int nArg = 0;
+ int n, c;
+ int rc = 0;
+ char *azArg[50];
+
+ /* Parse the input line into tokens.
+ */
+ while( zLine[i] && nArg<ArraySize(azArg) ){
+ while( isspace(zLine[i]) ){ i++; }
+ if( zLine[i]==0 ) break;
+ if( zLine[i]=='\'' || zLine[i]=='"' ){
+ int delim = zLine[i++];
+ azArg[nArg++] = &zLine[i];
+ while( zLine[i] && zLine[i]!=delim ){ i++; }
+ if( zLine[i]==delim ){
+ zLine[i++] = 0;
+ }
+ }else{
+ azArg[nArg++] = &zLine[i];
+ while( zLine[i] && !isspace(zLine[i]) ){ i++; }
+ if( zLine[i] ) zLine[i++] = 0;
+ }
+ }
+
+ /* Process the input line.
+ */
+ if( nArg==0 ) return rc;
+ n = strlen(azArg[0]);
+ c = azArg[0][0];
+ if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){
+ struct callback_data data;
+ char *zErrMsg = 0;
+ open_db(p);
+ memcpy(&data, p, sizeof(data));
+ data.showHeader = 1;
+ data.mode = MODE_Column;
+ data.colWidth[0] = 3;
+ data.colWidth[1] = 15;
+ data.colWidth[2] = 58;
+ sqlite_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg);
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite_freemem(zErrMsg);
+ }
+ }else
+
+ if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
+ char *zErrMsg = 0;
+ open_db(p);
+ fprintf(p->out, "BEGIN TRANSACTION;\n");
+ if( nArg==1 ){
+ sqlite_exec(p->db,
+ "SELECT name, type, sql FROM sqlite_master "
+ "WHERE type!='meta' AND sql NOT NULL "
+ "ORDER BY substr(type,2,1), rowid",
+ dump_callback, p, &zErrMsg
+ );
+ }else{
+ int i;
+ for(i=1; i<nArg && zErrMsg==0; i++){
+ sqlite_exec_printf(p->db,
+ "SELECT name, type, sql FROM sqlite_master "
+ "WHERE tbl_name LIKE '%q' AND type!='meta' AND sql NOT NULL "
+ "ORDER BY substr(type,2,1), rowid",
+ dump_callback, p, &zErrMsg, azArg[i]
+ );
+ }
+ }
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite_freemem(zErrMsg);
+ }else{
+ fprintf(p->out, "COMMIT;\n");
+ }
+ }else
+
+ if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 ){
+ int j;
+ char *z = azArg[1];
+ int val = atoi(azArg[1]);
+ for(j=0; z[j]; j++){
+ if( isupper(z[j]) ) z[j] = tolower(z[j]);
+ }
+ if( strcmp(z,"on")==0 ){
+ val = 1;
+ }else if( strcmp(z,"yes")==0 ){
+ val = 1;
+ }
+ p->echoOn = val;
+ }else
+
+ if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
+ rc = 1;
+ }else
+
+ if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){
+ int j;
+ char *z = nArg>=2 ? azArg[1] : "1";
+ int val = atoi(z);
+ for(j=0; z[j]; j++){
+ if( isupper(z[j]) ) z[j] = tolower(z[j]);
+ }
+ if( strcmp(z,"on")==0 ){
+ val = 1;
+ }else if( strcmp(z,"yes")==0 ){
+ val = 1;
+ }
+ if(val == 1) {
+ if(!p->explainPrev.valid) {
+ p->explainPrev.valid = 1;
+ p->explainPrev.mode = p->mode;
+ p->explainPrev.showHeader = p->showHeader;
+ memcpy(p->explainPrev.colWidth,p->colWidth,sizeof(p->colWidth));
+ }
+ /* We could put this code under the !p->explainValid
+ ** condition so that it does not execute if we are already in
+ ** explain mode. However, always executing it allows us an easy
+ ** was to reset to explain mode in case the user previously
+ ** did an .explain followed by a .width, .mode or .header
+ ** command.
+ */
+ p->mode = MODE_Column;
+ p->showHeader = 1;
+ memset(p->colWidth,0,ArraySize(p->colWidth));
+ p->colWidth[0] = 4;
+ p->colWidth[1] = 12;
+ p->colWidth[2] = 10;
+ p->colWidth[3] = 10;
+ p->colWidth[4] = 35;
+ }else if (p->explainPrev.valid) {
+ p->explainPrev.valid = 0;
+ p->mode = p->explainPrev.mode;
+ p->showHeader = p->explainPrev.showHeader;
+ memcpy(p->colWidth,p->explainPrev.colWidth,sizeof(p->colWidth));
+ }
+ }else
+
+ if( c=='h' && (strncmp(azArg[0], "header", n)==0
+ ||
+ strncmp(azArg[0], "headers", n)==0 )&& nArg>1 ){
+ int j;
+ char *z = azArg[1];
+ int val = atoi(azArg[1]);
+ for(j=0; z[j]; j++){
+ if( isupper(z[j]) ) z[j] = tolower(z[j]);
+ }
+ if( strcmp(z,"on")==0 ){
+ val = 1;
+ }else if( strcmp(z,"yes")==0 ){
+ val = 1;
+ }
+ p->showHeader = val;
+ }else
+
+ if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
+ fprintf(stderr, "%s", zHelp);
+ }else
+
+ if( c=='i' && strncmp(azArg[0], "indices", n)==0 && nArg>1 ){
+ struct callback_data data;
+ char *zErrMsg = 0;
+ open_db(p);
+ memcpy(&data, p, sizeof(data));
+ data.showHeader = 0;
+ data.mode = MODE_List;
+ sqlite_exec_printf(p->db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type='index' AND tbl_name LIKE '%q' "
+ "UNION ALL "
+ "SELECT name FROM sqlite_temp_master "
+ "WHERE type='index' AND tbl_name LIKE '%q' "
+ "ORDER BY 1",
+ callback, &data, &zErrMsg, azArg[1], azArg[1]
+ );
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite_freemem(zErrMsg);
+ }
+ }else
+
+ if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg>=2 ){
+ int n2 = strlen(azArg[1]);
+ if( strncmp(azArg[1],"line",n2)==0
+ ||
+ strncmp(azArg[1],"lines",n2)==0 ){
+ p->mode = MODE_Line;
+ }else if( strncmp(azArg[1],"column",n2)==0
+ ||
+ strncmp(azArg[1],"columns",n2)==0 ){
+ p->mode = MODE_Column;
+ }else if( strncmp(azArg[1],"list",n2)==0 ){
+ p->mode = MODE_List;
+ }else if( strncmp(azArg[1],"html",n2)==0 ){
+ p->mode = MODE_Html;
+ }else if( strncmp(azArg[1],"insert",n2)==0 ){
+ p->mode = MODE_Insert;
+ if( nArg>=3 ){
+ set_table_name(p, azArg[2]);
+ }else{
+ set_table_name(p, "table");
+ }
+ }else {
+ fprintf(stderr,"mode should be on of: column html insert line list\n");
+ }
+ }else
+
+ if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 && nArg==2 ) {
+ sprintf(p->nullvalue, "%.*s", (int)ArraySize(p->nullvalue)-1, azArg[1]);
+ }else
+
+ if( c=='o' && strncmp(azArg[0], "output", n)==0 && nArg==2 ){
+ if( p->out!=stdout ){
+ fclose(p->out);
+ }
+ if( strcmp(azArg[1],"stdout")==0 ){
+ p->out = stdout;
+ strcpy(p->outfile,"stdout");
+ }else{
+ p->out = fopen(azArg[1], "wb");
+ if( p->out==0 ){
+ fprintf(stderr,"can't write to \"%s\"\n", azArg[1]);
+ p->out = stdout;
+ } else {
+ strcpy(p->outfile,azArg[1]);
+ }
+ }
+ }else
+
+ if( c=='p' && strncmp(azArg[0], "prompt", n)==0 && (nArg==2 || nArg==3)){
+ if( nArg >= 2) {
+ strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
+ }
+ if( nArg >= 3) {
+ strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
+ }
+ }else
+
+ if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
+ rc = 1;
+ }else
+
+ if( c=='r' && strncmp(azArg[0], "read", n)==0 && nArg==2 ){
+ FILE *alt = fopen(azArg[1], "rb");
+ if( alt==0 ){
+ fprintf(stderr,"can't open \"%s\"\n", azArg[1]);
+ }else{
+ process_input(p, alt);
+ fclose(alt);
+ }
+ }else
+
+#ifdef SQLITE_HAS_CODEC
+ if( c=='r' && strncmp(azArg[0],"rekey", n)==0 && nArg==4 ){
+ char *zOld = p->zKey;
+ if( zOld==0 ) zOld = "";
+ if( strcmp(azArg[1],zOld) ){
+ fprintf(stderr,"old key is incorrect\n");
+ }else if( strcmp(azArg[2], azArg[3]) ){
+ fprintf(stderr,"2nd copy of new key does not match the 1st\n");
+ }else{
+ sqlite_freemem(p->zKey);
+ p->zKey = sqlite_mprintf("%s", azArg[2]);
+ sqlite_rekey(p->db, p->zKey, strlen(p->zKey));
+ }
+ }else
+#endif
+
+ if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){
+ struct callback_data data;
+ char *zErrMsg = 0;
+ open_db(p);
+ memcpy(&data, p, sizeof(data));
+ data.showHeader = 0;
+ data.mode = MODE_Semi;
+ if( nArg>1 ){
+ extern int sqliteStrICmp(const char*,const char*);
+ if( sqliteStrICmp(azArg[1],"sqlite_master")==0 ){
+ char *new_argv[2], *new_colv[2];
+ new_argv[0] = "CREATE TABLE sqlite_master (\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")";
+ new_argv[1] = 0;
+ new_colv[0] = "sql";
+ new_colv[1] = 0;
+ callback(&data, 1, new_argv, new_colv);
+ }else if( sqliteStrICmp(azArg[1],"sqlite_temp_master")==0 ){
+ char *new_argv[2], *new_colv[2];
+ new_argv[0] = "CREATE TEMP TABLE sqlite_temp_master (\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")";
+ new_argv[1] = 0;
+ new_colv[0] = "sql";
+ new_colv[1] = 0;
+ callback(&data, 1, new_argv, new_colv);
+ }else{
+ sqlite_exec_printf(p->db,
+ "SELECT sql FROM "
+ " (SELECT * FROM sqlite_master UNION ALL"
+ " SELECT * FROM sqlite_temp_master) "
+ "WHERE tbl_name LIKE '%q' AND type!='meta' AND sql NOTNULL "
+ "ORDER BY substr(type,2,1), name",
+ callback, &data, &zErrMsg, azArg[1]);
+ }
+ }else{
+ sqlite_exec(p->db,
+ "SELECT sql FROM "
+ " (SELECT * FROM sqlite_master UNION ALL"
+ " SELECT * FROM sqlite_temp_master) "
+ "WHERE type!='meta' AND sql NOTNULL "
+ "ORDER BY substr(type,2,1), name",
+ callback, &data, &zErrMsg
+ );
+ }
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite_freemem(zErrMsg);
+ }
+ }else
+
+ if( c=='s' && strncmp(azArg[0], "separator", n)==0 && nArg==2 ){
+ sprintf(p->separator, "%.*s", (int)ArraySize(p->separator)-1, azArg[1]);
+ }else
+
+ if( c=='s' && strncmp(azArg[0], "show", n)==0){
+ int i;
+ fprintf(p->out,"%9.9s: %s\n","echo", p->echoOn ? "on" : "off");
+ fprintf(p->out,"%9.9s: %s\n","explain", p->explainPrev.valid ? "on" :"off");
+ fprintf(p->out,"%9.9s: %s\n","headers", p->showHeader ? "on" : "off");
+ fprintf(p->out,"%9.9s: %s\n","mode", modeDescr[p->mode]);
+ fprintf(p->out,"%9.9s: %s\n","nullvalue", p->nullvalue);
+ fprintf(p->out,"%9.9s: %s\n","output",
+ strlen(p->outfile) ? p->outfile : "stdout");
+ fprintf(p->out,"%9.9s: %s\n","separator", p->separator);
+ fprintf(p->out,"%9.9s: ","width");
+ for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) {
+ fprintf(p->out,"%d ",p->colWidth[i]);
+ }
+ fprintf(p->out,"\n\n");
+ }else
+
+ if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){
+ char **azResult;
+ int nRow, rc;
+ char *zErrMsg;
+ open_db(p);
+ if( nArg==1 ){
+ rc = sqlite_get_table(p->db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type IN ('table','view') "
+ "UNION ALL "
+ "SELECT name FROM sqlite_temp_master "
+ "WHERE type IN ('table','view') "
+ "ORDER BY 1",
+ &azResult, &nRow, 0, &zErrMsg
+ );
+ }else{
+ rc = sqlite_get_table_printf(p->db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type IN ('table','view') AND name LIKE '%%%q%%' "
+ "UNION ALL "
+ "SELECT name FROM sqlite_temp_master "
+ "WHERE type IN ('table','view') AND name LIKE '%%%q%%' "
+ "ORDER BY 1",
+ &azResult, &nRow, 0, &zErrMsg, azArg[1], azArg[1]
+ );
+ }
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite_freemem(zErrMsg);
+ }
+ if( rc==SQLITE_OK ){
+ int len, maxlen = 0;
+ int i, j;
+ int nPrintCol, nPrintRow;
+ for(i=1; i<=nRow; i++){
+ if( azResult[i]==0 ) continue;
+ len = strlen(azResult[i]);
+ if( len>maxlen ) maxlen = len;
+ }
+ nPrintCol = 80/(maxlen+2);
+ if( nPrintCol<1 ) nPrintCol = 1;
+ nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
+ for(i=0; i<nPrintRow; i++){
+ for(j=i+1; j<=nRow; j+=nPrintRow){
+ char *zSp = j<=nPrintRow ? "" : " ";
+ printf("%s%-*s", zSp, maxlen, azResult[j] ? azResult[j] : "");
+ }
+ printf("\n");
+ }
+ }
+ sqlite_free_table(azResult);
+ }else
+
+ if( c=='t' && n>1 && strncmp(azArg[0], "timeout", n)==0 && nArg>=2 ){
+ open_db(p);
+ sqlite_busy_timeout(p->db, atoi(azArg[1]));
+ }else
+
+ if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
+ int j;
+ for(j=1; j<nArg && j<ArraySize(p->colWidth); j++){
+ p->colWidth[j-1] = atoi(azArg[j]);
+ }
+ }else
+
+ {
+ fprintf(stderr, "unknown command or invalid arguments: "
+ " \"%s\". Enter \".help\" for help\n", azArg[0]);
+ }
+
+ return rc;
+}
+
+/*
+** Return TRUE if the last non-whitespace character in z[] is a semicolon.
+** z[] is N characters long.
+*/
+static int _ends_with_semicolon(const char *z, int N){
+ while( N>0 && isspace(z[N-1]) ){ N--; }
+ return N>0 && z[N-1]==';';
+}
+
+/*
+** Test to see if a line consists entirely of whitespace.
+*/
+static int _all_whitespace(const char *z){
+ for(; *z; z++){
+ if( isspace(*z) ) continue;
+ if( *z=='/' && z[1]=='*' ){
+ z += 2;
+ while( *z && (*z!='*' || z[1]!='/') ){ z++; }
+ if( *z==0 ) return 0;
+ z++;
+ continue;
+ }
+ if( *z=='-' && z[1]=='-' ){
+ z += 2;
+ while( *z && *z!='\n' ){ z++; }
+ if( *z==0 ) return 1;
+ continue;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** Return TRUE if the line typed in is an SQL command terminator other
+** than a semi-colon. The SQL Server style "go" command is understood
+** as is the Oracle "/".
+*/
+static int _is_command_terminator(const char *zLine){
+ extern int sqliteStrNICmp(const char*,const char*,int);
+ while( isspace(*zLine) ){ zLine++; };
+ if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ) return 1; /* Oracle */
+ if( sqliteStrNICmp(zLine,"go",2)==0 && _all_whitespace(&zLine[2]) ){
+ return 1; /* SQL Server */
+ }
+ return 0;
+}
+
+/*
+** Read input from *in and process it. If *in==0 then input
+** is interactive - the user is typing it it. Otherwise, input
+** is coming from a file or device. A prompt is issued and history
+** is saved only if input is interactive. An interrupt signal will
+** cause this routine to exit immediately, unless input is interactive.
+*/
+static void process_input(struct callback_data *p, FILE *in){
+ char *zLine;
+ char *zSql = 0;
+ int nSql = 0;
+ char *zErrMsg;
+ int rc;
+ while( fflush(p->out), (zLine = one_input_line(zSql, in))!=0 ){
+ if( seenInterrupt ){
+ if( in!=0 ) break;
+ seenInterrupt = 0;
+ }
+ if( p->echoOn ) printf("%s\n", zLine);
+ if( (zSql==0 || zSql[0]==0) && _all_whitespace(zLine) ) continue;
+ if( zLine && zLine[0]=='.' && nSql==0 ){
+ int rc = do_meta_command(zLine, p);
+ free(zLine);
+ if( rc ) break;
+ continue;
+ }
+ if( _is_command_terminator(zLine) ){
+ strcpy(zLine,";");
+ }
+ if( zSql==0 ){
+ int i;
+ for(i=0; zLine[i] && isspace(zLine[i]); i++){}
+ if( zLine[i]!=0 ){
+ nSql = strlen(zLine);
+ zSql = malloc( nSql+1 );
+ strcpy(zSql, zLine);
+ }
+ }else{
+ int len = strlen(zLine);
+ zSql = realloc( zSql, nSql + len + 2 );
+ if( zSql==0 ){
+ fprintf(stderr,"%s: out of memory!\n", Argv0);
+ exit(1);
+ }
+ strcpy(&zSql[nSql++], "\n");
+ strcpy(&zSql[nSql], zLine);
+ nSql += len;
+ }
+ free(zLine);
+ if( zSql && _ends_with_semicolon(zSql, nSql) && sqlite_complete(zSql) ){
+ p->cnt = 0;
+ open_db(p);
+ rc = sqlite_exec(p->db, zSql, callback, p, &zErrMsg);
+ if( rc || zErrMsg ){
+ if( in!=0 && !p->echoOn ) printf("%s\n",zSql);
+ if( zErrMsg!=0 ){
+ printf("SQL error: %s\n", zErrMsg);
+ sqlite_freemem(zErrMsg);
+ zErrMsg = 0;
+ }else{
+ printf("SQL error: %s\n", sqlite_error_string(rc));
+ }
+ }
+ free(zSql);
+ zSql = 0;
+ nSql = 0;
+ }
+ }
+ if( zSql ){
+ if( !_all_whitespace(zSql) ) printf("Incomplete SQL: %s\n", zSql);
+ free(zSql);
+ }
+}
+
+/*
+** Return a pathname which is the user's home directory. A
+** 0 return indicates an error of some kind. Space to hold the
+** resulting string is obtained from malloc(). The calling
+** function should free the result.
+*/
+static char *find_home_dir(void){
+ char *home_dir = NULL;
+
+#if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__)
+ struct passwd *pwent;
+ uid_t uid = getuid();
+ if( (pwent=getpwuid(uid)) != NULL) {
+ home_dir = pwent->pw_dir;
+ }
+#endif
+
+#ifdef __MACOS__
+ char home_path[_MAX_PATH+1];
+ home_dir = getcwd(home_path, _MAX_PATH);
+#endif
+
+ if (!home_dir) {
+ home_dir = getenv("HOME");
+ if (!home_dir) {
+ home_dir = getenv("HOMEPATH"); /* Windows? */
+ }
+ }
+
+#if defined(_WIN32) || defined(WIN32)
+ if (!home_dir) {
+ home_dir = "c:";
+ }
+#endif
+
+ if( home_dir ){
+ char *z = malloc( strlen(home_dir)+1 );
+ if( z ) strcpy(z, home_dir);
+ home_dir = z;
+ }
+
+ return home_dir;
+}
+
+/*
+** Read input from the file given by sqliterc_override. Or if that
+** parameter is NULL, take input from ~/.sqliterc
+*/
+static void process_sqliterc(
+ struct callback_data *p, /* Configuration data */
+ const char *sqliterc_override /* Name of config file. NULL to use default */
+){
+ char *home_dir = NULL;
+ const char *sqliterc = sqliterc_override;
+ char *zBuf;
+ FILE *in = NULL;
+
+ if (sqliterc == NULL) {
+ home_dir = find_home_dir();
+ if( home_dir==0 ){
+ fprintf(stderr,"%s: cannot locate your home directory!\n", Argv0);
+ return;
+ }
+ zBuf = malloc(strlen(home_dir) + 15);
+ if( zBuf==0 ){
+ fprintf(stderr,"%s: out of memory!\n", Argv0);
+ exit(1);
+ }
+ sprintf(zBuf,"%s/.sqliterc",home_dir);
+ free(home_dir);
+ sqliterc = (const char*)zBuf;
+ }
+ in = fopen(sqliterc,"rb");
+ if( in ){
+ if( isatty(fileno(stdout)) ){
+ printf("Loading resources from %s\n",sqliterc);
+ }
+ process_input(p,in);
+ fclose(in);
+ }
+ return;
+}
+
+/*
+** Show available command line options
+*/
+static const char zOptions[] =
+ " -init filename read/process named file\n"
+ " -echo print commands before execution\n"
+ " -[no]header turn headers on or off\n"
+ " -column set output mode to 'column'\n"
+ " -html set output mode to HTML\n"
+#ifdef SQLITE_HAS_CODEC
+ " -key KEY encryption key\n"
+#endif
+ " -line set output mode to 'line'\n"
+ " -list set output mode to 'list'\n"
+ " -separator 'x' set output field separator (|)\n"
+ " -nullvalue 'text' set text string for NULL values\n"
+ " -version show SQLite version\n"
+ " -help show this text, also show dot-commands\n"
+;
+static void usage(int showDetail){
+ fprintf(stderr, "Usage: %s [OPTIONS] FILENAME [SQL]\n", Argv0);
+ if( showDetail ){
+ fprintf(stderr, "Options are:\n%s", zOptions);
+ }else{
+ fprintf(stderr, "Use the -help option for additional information\n");
+ }
+ exit(1);
+}
+
+/*
+** Initialize the state information in data
+*/
+void main_init(struct callback_data *data) {
+ memset(data, 0, sizeof(*data));
+ data->mode = MODE_List;
+ strcpy(data->separator,"|");
+ data->showHeader = 0;
+ strcpy(mainPrompt,"sqlite> ");
+ strcpy(continuePrompt," ...> ");
+}
+
+int main(int argc, char **argv){
+ char *zErrMsg = 0;
+ struct callback_data data;
+ const char *zInitFile = 0;
+ char *zFirstCmd = 0;
+ int i;
+ extern int sqliteOsFileExists(const char*);
+
+#ifdef __MACOS__
+ argc = ccommand(&argv);
+#endif
+
+ Argv0 = argv[0];
+ main_init(&data);
+
+ /* Make sure we have a valid signal handler early, before anything
+ ** else is done.
+ */
+#ifdef SIGINT
+ signal(SIGINT, interrupt_handler);
+#endif
+
+ /* Do an initial pass through the command-line argument to locate
+ ** the name of the database file, the name of the initialization file,
+ ** and the first command to execute.
+ */
+ for(i=1; i<argc-1; i++){
+ if( argv[i][0]!='-' ) break;
+ if( strcmp(argv[i],"-separator")==0 || strcmp(argv[i],"-nullvalue")==0 ){
+ i++;
+ }else if( strcmp(argv[i],"-init")==0 ){
+ i++;
+ zInitFile = argv[i];
+ }else if( strcmp(argv[i],"-key")==0 ){
+ i++;
+ data.zKey = sqlite_mprintf("%s",argv[i]);
+ }
+ }
+ if( i<argc ){
+ data.zDbFilename = argv[i++];
+ }else{
+ data.zDbFilename = ":memory:";
+ }
+ if( i<argc ){
+ zFirstCmd = argv[i++];
+ }
+ data.out = stdout;
+
+ /* Go ahead and open the database file if it already exists. If the
+ ** file does not exist, delay opening it. This prevents empty database
+ ** files from being created if a user mistypes the database name argument
+ ** to the sqlite command-line tool.
+ */
+ if( sqliteOsFileExists(data.zDbFilename) ){
+ open_db(&data);
+ }
+
+ /* Process the initialization file if there is one. If no -init option
+ ** is given on the command line, look for a file named ~/.sqliterc and
+ ** try to process it.
+ */
+ process_sqliterc(&data,zInitFile);
+
+ /* Make a second pass through the command-line argument and set
+ ** options. This second pass is delayed until after the initialization
+ ** file is processed so that the command-line arguments will override
+ ** settings in the initialization file.
+ */
+ for(i=1; i<argc && argv[i][0]=='-'; i++){
+ char *z = argv[i];
+ if( strcmp(z,"-init")==0 || strcmp(z,"-key")==0 ){
+ i++;
+ }else if( strcmp(z,"-html")==0 ){
+ data.mode = MODE_Html;
+ }else if( strcmp(z,"-list")==0 ){
+ data.mode = MODE_List;
+ }else if( strcmp(z,"-line")==0 ){
+ data.mode = MODE_Line;
+ }else if( strcmp(z,"-column")==0 ){
+ data.mode = MODE_Column;
+ }else if( strcmp(z,"-separator")==0 ){
+ i++;
+ sprintf(data.separator,"%.*s",(int)sizeof(data.separator)-1,argv[i]);
+ }else if( strcmp(z,"-nullvalue")==0 ){
+ i++;
+ sprintf(data.nullvalue,"%.*s",(int)sizeof(data.nullvalue)-1,argv[i]);
+ }else if( strcmp(z,"-header")==0 ){
+ data.showHeader = 1;
+ }else if( strcmp(z,"-noheader")==0 ){
+ data.showHeader = 0;
+ }else if( strcmp(z,"-echo")==0 ){
+ data.echoOn = 1;
+ }else if( strcmp(z,"-version")==0 ){
+ printf("%s\n", sqlite_version);
+ return 1;
+ }else if( strcmp(z,"-help")==0 ){
+ usage(1);
+ }else{
+ fprintf(stderr,"%s: unknown option: %s\n", Argv0, z);
+ fprintf(stderr,"Use -help for a list of options.\n");
+ return 1;
+ }
+ }
+
+ if( zFirstCmd ){
+ /* Run just the command that follows the database name
+ */
+ if( zFirstCmd[0]=='.' ){
+ do_meta_command(zFirstCmd, &data);
+ exit(0);
+ }else{
+ int rc;
+ open_db(&data);
+ rc = sqlite_exec(data.db, zFirstCmd, callback, &data, &zErrMsg);
+ if( rc!=0 && zErrMsg!=0 ){
+ fprintf(stderr,"SQL error: %s\n", zErrMsg);
+ exit(1);
+ }
+ }
+ }else{
+ /* Run commands received from standard input
+ */
+ if( isatty(fileno(stdout)) && isatty(fileno(stdin)) ){
+ char *zHome;
+ char *zHistory = 0;
+ printf(
+ "SQLite version %s\n"
+ "Enter \".help\" for instructions\n",
+ sqlite_version
+ );
+ zHome = find_home_dir();
+ if( zHome && (zHistory = malloc(strlen(zHome)+20))!=0 ){
+ sprintf(zHistory,"%s/.sqlite_history", zHome);
+ }
+ if( zHistory ) read_history(zHistory);
+ process_input(&data, 0);
+ if( zHistory ){
+ stifle_history(100);
+ write_history(zHistory);
+ }
+ }else{
+ process_input(&data, stdin);
+ }
+ }
+ set_table_name(&data, 0);
+ if( db ) sqlite_close(db);
+ return 0;
+}
diff --git a/src/libs/sqlite2/sqlite.h b/src/libs/sqlite2/sqlite.h
new file mode 100644
index 00000000..ef461bf7
--- /dev/null
+++ b/src/libs/sqlite2/sqlite.h
@@ -0,0 +1,872 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the SQLite library
+** presents to client programs.
+**
+** @(#) $Id: sqlite.h 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#ifndef _SQLITE_H_
+#define _SQLITE_H_
+#include <stdarg.h> /* Needed for the definition of va_list */
+
+/*
+** Make sure we can call this stuff from C++.
+*/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** The version of the SQLite library.
+*/
+#define SQLITE_VERSION "2.8.14"
+
+/*
+** The version string is also compiled into the library so that a program
+** can check to make sure that the lib*.a file and the *.h file are from
+** the same version.
+*/
+extern const char sqlite_version[];
+
+/*
+** The SQLITE_UTF8 macro is defined if the library expects to see
+** UTF-8 encoded data. The SQLITE_ISO8859 macro is defined if the
+** iso8859 encoded should be used.
+*/
+/* #define SQLITE_ISO8859 1 */
+
+/* DigiKam customizations */
+#define SQLITE_UTF8 1
+#define THREADSAFE 1
+
+/*
+** The following constant holds one of two strings, "UTF-8" or "iso8859",
+** depending on which character encoding the SQLite library expects to
+** see. The character encoding makes a difference for the LIKE and GLOB
+** operators and for the LENGTH() and SUBSTR() functions.
+*/
+extern const char sqlite_encoding[];
+
+/*
+** Each open sqlite database is represented by an instance of the
+** following opaque structure.
+*/
+typedef struct sqlite sqlite;
+
+/*
+** A function to open a new sqlite database.
+**
+** If the database does not exist and mode indicates write
+** permission, then a new database is created. If the database
+** does not exist and mode does not indicate write permission,
+** then the open fails, an error message generated (if errmsg!=0)
+** and the function returns 0.
+**
+** If mode does not indicates user write permission, then the
+** database is opened read-only.
+**
+** The Truth: As currently implemented, all databases are opened
+** for writing all the time. Maybe someday we will provide the
+** ability to open a database readonly. The mode parameters is
+** provided in anticipation of that enhancement.
+*/
+sqlite *sqlite_open(const char *filename, int mode, char **errmsg);
+
+/*
+** A function to close the database.
+**
+** Call this function with a pointer to a structure that was previously
+** returned from sqlite_open() and the corresponding database will by closed.
+*/
+void sqlite_close(sqlite *);
+
+/*
+** The type for a callback function.
+*/
+typedef int (*sqlite_callback)(void*,int,char**, char**);
+
+/*
+** A function to executes one or more statements of SQL.
+**
+** If one or more of the SQL statements are queries, then
+** the callback function specified by the 3rd parameter is
+** invoked once for each row of the query result. This callback
+** should normally return 0. If the callback returns a non-zero
+** value then the query is aborted, all subsequent SQL statements
+** are skipped and the sqlite_exec() function returns the SQLITE_ABORT.
+**
+** The 4th parameter is an arbitrary pointer that is passed
+** to the callback function as its first parameter.
+**
+** The 2nd parameter to the callback function is the number of
+** columns in the query result. The 3rd parameter to the callback
+** is an array of strings holding the values for each column.
+** The 4th parameter to the callback is an array of strings holding
+** the names of each column.
+**
+** The callback function may be NULL, even for queries. A NULL
+** callback is not an error. It just means that no callback
+** will be invoked.
+**
+** If an error occurs while parsing or evaluating the SQL (but
+** not while executing the callback) then an appropriate error
+** message is written into memory obtained from malloc() and
+** *errmsg is made to point to that message. The calling function
+** is responsible for freeing the memory that holds the error
+** message. Use sqlite_freemem() for this. If errmsg==NULL,
+** then no error message is ever written.
+**
+** The return value is is SQLITE_OK if there are no errors and
+** some other return code if there is an error. The particular
+** return value depends on the type of error.
+**
+** If the query could not be executed because a database file is
+** locked or busy, then this function returns SQLITE_BUSY. (This
+** behavior can be modified somewhat using the sqlite_busy_handler()
+** and sqlite_busy_timeout() functions below.)
+*/
+int sqlite_exec(
+ sqlite*, /* An open database */
+ const char *sql, /* SQL to be executed */
+ sqlite_callback, /* Callback function */
+ void *, /* 1st argument to callback function */
+ char **errmsg /* Error msg written here */
+);
+
+/*
+** Return values for sqlite_exec() and sqlite_step()
+*/
+#define SQLITE_OK 0 /* Successful result */
+#define SQLITE_ERROR 1 /* SQL error or missing database */
+#define SQLITE_INTERNAL 2 /* An internal logic error in SQLite */
+#define SQLITE_PERM 3 /* Access permission denied */
+#define SQLITE_ABORT 4 /* Callback routine requested an abort */
+#define SQLITE_BUSY 5 /* The database file is locked */
+#define SQLITE_LOCKED 6 /* A table in the database is locked */
+#define SQLITE_NOMEM 7 /* A malloc() failed */
+#define SQLITE_READONLY 8 /* Attempt to write a readonly database */
+#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite_interrupt() */
+#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */
+#define SQLITE_CORRUPT 11 /* The database disk image is malformed */
+#define SQLITE_NOTFOUND 12 /* (Internal Only) Table or record not found */
+#define SQLITE_FULL 13 /* Insertion failed because database is full */
+#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
+#define SQLITE_PROTOCOL 15 /* Database lock protocol error */
+#define SQLITE_EMPTY 16 /* (Internal Only) Database table is empty */
+#define SQLITE_SCHEMA 17 /* The database schema changed */
+#define SQLITE_TOOBIG 18 /* Too much data for one row of a table */
+#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
+#define SQLITE_MISMATCH 20 /* Data type mismatch */
+#define SQLITE_MISUSE 21 /* Library used incorrectly */
+#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */
+#define SQLITE_AUTH 23 /* Authorization denied */
+#define SQLITE_FORMAT 24 /* Auxiliary database format error */
+#define SQLITE_RANGE 25 /* 2nd parameter to sqlite_bind out of range */
+#define SQLITE_NOTADB 26 /* File opened that is not a database file */
+#define SQLITE_ROW 100 /* sqlite_step() has another row ready */
+#define SQLITE_DONE 101 /* sqlite_step() has finished executing */
+
+/*
+** Each entry in an SQLite table has a unique integer key. (The key is
+** the value of the INTEGER PRIMARY KEY column if there is such a column,
+** otherwise the key is generated at random. The unique key is always
+** available as the ROWID, OID, or _ROWID_ column.) The following routine
+** returns the integer key of the most recent insert in the database.
+**
+** This function is similar to the mysql_insert_id() function from MySQL.
+*/
+int sqlite_last_insert_rowid(sqlite*);
+
+/*
+** This function returns the number of database rows that were changed
+** (or inserted or deleted) by the most recent called sqlite_exec().
+**
+** All changes are counted, even if they were later undone by a
+** ROLLBACK or ABORT. Except, changes associated with creating and
+** dropping tables are not counted.
+**
+** If a callback invokes sqlite_exec() recursively, then the changes
+** in the inner, recursive call are counted together with the changes
+** in the outer call.
+**
+** SQLite implements the command "DELETE FROM table" without a WHERE clause
+** by dropping and recreating the table. (This is much faster than going
+** through and deleting individual elements form the table.) Because of
+** this optimization, the change count for "DELETE FROM table" will be
+** zero regardless of the number of elements that were originally in the
+** table. To get an accurate count of the number of rows deleted, use
+** "DELETE FROM table WHERE 1" instead.
+*/
+int sqlite_changes(sqlite*);
+
+/*
+** This function returns the number of database rows that were changed
+** by the last INSERT, UPDATE, or DELETE statement executed by sqlite_exec(),
+** or by the last VM to run to completion. The change count is not updated
+** by SQL statements other than INSERT, UPDATE or DELETE.
+**
+** Changes are counted, even if they are later undone by a ROLLBACK or
+** ABORT. Changes associated with trigger programs that execute as a
+** result of the INSERT, UPDATE, or DELETE statement are not counted.
+**
+** If a callback invokes sqlite_exec() recursively, then the changes
+** in the inner, recursive call are counted together with the changes
+** in the outer call.
+**
+** SQLite implements the command "DELETE FROM table" without a WHERE clause
+** by dropping and recreating the table. (This is much faster than going
+** through and deleting individual elements form the table.) Because of
+** this optimization, the change count for "DELETE FROM table" will be
+** zero regardless of the number of elements that were originally in the
+** table. To get an accurate count of the number of rows deleted, use
+** "DELETE FROM table WHERE 1" instead.
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+int sqlite_last_statement_changes(sqlite*);
+
+/* If the parameter to this routine is one of the return value constants
+** defined above, then this routine returns a constant text string which
+** describes (in English) the meaning of the return value.
+*/
+const char *sqlite_error_string(int);
+#define sqliteErrStr sqlite_error_string /* Legacy. Do not use in new code. */
+
+/* This function causes any pending database operation to abort and
+** return at its earliest opportunity. This routine is typically
+** called in response to a user action such as pressing "Cancel"
+** or Ctrl-C where the user wants a long query operation to halt
+** immediately.
+*/
+void sqlite_interrupt(sqlite*);
+
+
+/* This function returns true if the given input string comprises
+** one or more complete SQL statements.
+**
+** The algorithm is simple. If the last token other than spaces
+** and comments is a semicolon, then return true. otherwise return
+** false.
+*/
+int sqlite_complete(const char *sql);
+
+/*
+** This routine identifies a callback function that is invoked
+** whenever an attempt is made to open a database table that is
+** currently locked by another process or thread. If the busy callback
+** is NULL, then sqlite_exec() returns SQLITE_BUSY immediately if
+** it finds a locked table. If the busy callback is not NULL, then
+** sqlite_exec() invokes the callback with three arguments. The
+** second argument is the name of the locked table and the third
+** argument is the number of times the table has been busy. If the
+** busy callback returns 0, then sqlite_exec() immediately returns
+** SQLITE_BUSY. If the callback returns non-zero, then sqlite_exec()
+** tries to open the table again and the cycle repeats.
+**
+** The default busy callback is NULL.
+**
+** Sqlite is re-entrant, so the busy handler may start a new query.
+** (It is not clear why anyone would every want to do this, but it
+** is allowed, in theory.) But the busy handler may not close the
+** database. Closing the database from a busy handler will delete
+** data structures out from under the executing query and will
+** probably result in a coredump.
+*/
+void sqlite_busy_handler(sqlite*, int(*)(void*,const char*,int), void*);
+
+/*
+** This routine sets a busy handler that sleeps for a while when a
+** table is locked. The handler will sleep multiple times until
+** at least "ms" milliseconds of sleeping have been done. After
+** "ms" milliseconds of sleeping, the handler returns 0 which
+** causes sqlite_exec() to return SQLITE_BUSY.
+**
+** Calling this routine with an argument less than or equal to zero
+** turns off all busy handlers.
+*/
+void sqlite_busy_timeout(sqlite*, int ms);
+
+/*
+** This next routine is really just a wrapper around sqlite_exec().
+** Instead of invoking a user-supplied callback for each row of the
+** result, this routine remembers each row of the result in memory
+** obtained from malloc(), then returns all of the result after the
+** query has finished.
+**
+** As an example, suppose the query result where this table:
+**
+** Name | Age
+** -----------------------
+** Alice | 43
+** Bob | 28
+** Cindy | 21
+**
+** If the 3rd argument were &azResult then after the function returns
+** azResult will contain the following data:
+**
+** azResult[0] = "Name";
+** azResult[1] = "Age";
+** azResult[2] = "Alice";
+** azResult[3] = "43";
+** azResult[4] = "Bob";
+** azResult[5] = "28";
+** azResult[6] = "Cindy";
+** azResult[7] = "21";
+**
+** Notice that there is an extra row of data containing the column
+** headers. But the *nrow return value is still 3. *ncolumn is
+** set to 2. In general, the number of values inserted into azResult
+** will be ((*nrow) + 1)*(*ncolumn).
+**
+** After the calling function has finished using the result, it should
+** pass the result data pointer to sqlite_free_table() in order to
+** release the memory that was malloc-ed. Because of the way the
+** malloc() happens, the calling function must not try to call
+** malloc() directly. Only sqlite_free_table() is able to release
+** the memory properly and safely.
+**
+** The return value of this routine is the same as from sqlite_exec().
+*/
+int sqlite_get_table(
+ sqlite*, /* An open database */
+ const char *sql, /* SQL to be executed */
+ char ***resultp, /* Result written to a char *[] that this points to */
+ int *nrow, /* Number of result rows written here */
+ int *ncolumn, /* Number of result columns written here */
+ char **errmsg /* Error msg written here */
+);
+
+/*
+** Call this routine to free the memory that sqlite_get_table() allocated.
+*/
+void sqlite_free_table(char **result);
+
+/*
+** The following routines are wrappers around sqlite_exec() and
+** sqlite_get_table(). The only difference between the routines that
+** follow and the originals is that the second argument to the
+** routines that follow is really a printf()-style format
+** string describing the SQL to be executed. Arguments to the format
+** string appear at the end of the argument list.
+**
+** All of the usual printf formatting options apply. In addition, there
+** is a "%q" option. %q works like %s in that it substitutes a null-terminated
+** string from the argument list. But %q also doubles every '\'' character.
+** %q is designed for use inside a string literal. By doubling each '\''
+** character it escapes that character and allows it to be inserted into
+** the string.
+**
+** For example, so some string variable contains text as follows:
+**
+** char *zText = "It's a happy day!";
+**
+** We can use this text in an SQL statement as follows:
+**
+** sqlite_exec_printf(db, "INSERT INTO table VALUES('%q')",
+** callback1, 0, 0, zText);
+**
+** Because the %q format string is used, the '\'' character in zText
+** is escaped and the SQL generated is as follows:
+**
+** INSERT INTO table1 VALUES('It''s a happy day!')
+**
+** This is correct. Had we used %s instead of %q, the generated SQL
+** would have looked like this:
+**
+** INSERT INTO table1 VALUES('It's a happy day!');
+**
+** This second example is an SQL syntax error. As a general rule you
+** should always use %q instead of %s when inserting text into a string
+** literal.
+*/
+int sqlite_exec_printf(
+ sqlite*, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ sqlite_callback, /* Callback function */
+ void *, /* 1st argument to callback function */
+ char **errmsg, /* Error msg written here */
+ ... /* Arguments to the format string. */
+);
+int sqlite_exec_vprintf(
+ sqlite*, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ sqlite_callback, /* Callback function */
+ void *, /* 1st argument to callback function */
+ char **errmsg, /* Error msg written here */
+ va_list ap /* Arguments to the format string. */
+);
+int sqlite_get_table_printf(
+ sqlite*, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ char ***resultp, /* Result written to a char *[] that this points to */
+ int *nrow, /* Number of result rows written here */
+ int *ncolumn, /* Number of result columns written here */
+ char **errmsg, /* Error msg written here */
+ ... /* Arguments to the format string */
+);
+int sqlite_get_table_vprintf(
+ sqlite*, /* An open database */
+ const char *sqlFormat, /* printf-style format string for the SQL */
+ char ***resultp, /* Result written to a char *[] that this points to */
+ int *nrow, /* Number of result rows written here */
+ int *ncolumn, /* Number of result columns written here */
+ char **errmsg, /* Error msg written here */
+ va_list ap /* Arguments to the format string */
+);
+char *sqlite_mprintf(const char*,...);
+char *sqlite_vmprintf(const char*, va_list);
+
+/*
+** Windows systems should call this routine to free memory that
+** is returned in the in the errmsg parameter of sqlite_open() when
+** SQLite is a DLL. For some reason, it does not work to call free()
+** directly.
+*/
+void sqlite_freemem(void *p);
+
+/*
+** Windows systems need functions to call to return the sqlite_version
+** and sqlite_encoding strings.
+*/
+const char *sqlite_libversion(void);
+const char *sqlite_libencoding(void);
+
+/*
+** A pointer to the following structure is used to communicate with
+** the implementations of user-defined functions.
+*/
+typedef struct sqlite_func sqlite_func;
+
+/*
+** Use the following routines to create new user-defined functions. See
+** the documentation for details.
+*/
+int sqlite_create_function(
+ sqlite*, /* Database where the new function is registered */
+ const char *zName, /* Name of the new function */
+ int nArg, /* Number of arguments. -1 means any number */
+ void (*xFunc)(sqlite_func*,int,const char**), /* C code to implement */
+ void *pUserData /* Available via the sqlite_user_data() call */
+);
+int sqlite_create_aggregate(
+ sqlite*, /* Database where the new function is registered */
+ const char *zName, /* Name of the function */
+ int nArg, /* Number of arguments */
+ void (*xStep)(sqlite_func*,int,const char**), /* Called for each row */
+ void (*xFinalize)(sqlite_func*), /* Called once to get final result */
+ void *pUserData /* Available via the sqlite_user_data() call */
+);
+
+/*
+** Use the following routine to define the datatype returned by a
+** user-defined function. The second argument can be one of the
+** constants SQLITE_NUMERIC, SQLITE_TEXT, or SQLITE_ARGS or it
+** can be an integer greater than or equal to zero. When the datatype
+** parameter is non-negative, the type of the result will be the
+** same as the datatype-th argument. If datatype==SQLITE_NUMERIC
+** then the result is always numeric. If datatype==SQLITE_TEXT then
+** the result is always text. If datatype==SQLITE_ARGS then the result
+** is numeric if any argument is numeric and is text otherwise.
+*/
+int sqlite_function_type(
+ sqlite *db, /* The database there the function is registered */
+ const char *zName, /* Name of the function */
+ int datatype /* The datatype for this function */
+);
+#define SQLITE_NUMERIC (-1)
+#define SQLITE_TEXT (-2)
+#define SQLITE_ARGS (-3)
+
+/*
+** The user function implementations call one of the following four routines
+** in order to return their results. The first parameter to each of these
+** routines is a copy of the first argument to xFunc() or xFinialize().
+** The second parameter to these routines is the result to be returned.
+** A NULL can be passed as the second parameter to sqlite_set_result_string()
+** in order to return a NULL result.
+**
+** The 3rd argument to _string and _error is the number of characters to
+** take from the string. If this argument is negative, then all characters
+** up to and including the first '\000' are used.
+**
+** The sqlite_set_result_string() function allocates a buffer to hold the
+** result and returns a pointer to this buffer. The calling routine
+** (that is, the implementation of a user function) can alter the content
+** of this buffer if desired.
+*/
+char *sqlite_set_result_string(sqlite_func*,const char*,int);
+void sqlite_set_result_int(sqlite_func*,int);
+void sqlite_set_result_double(sqlite_func*,double);
+void sqlite_set_result_error(sqlite_func*,const char*,int);
+
+/*
+** The pUserData parameter to the sqlite_create_function() and
+** sqlite_create_aggregate() routines used to register user functions
+** is available to the implementation of the function using this
+** call.
+*/
+void *sqlite_user_data(sqlite_func*);
+
+/*
+** Aggregate functions use the following routine to allocate
+** a structure for storing their state. The first time this routine
+** is called for a particular aggregate, a new structure of size nBytes
+** is allocated, zeroed, and returned. On subsequent calls (for the
+** same aggregate instance) the same buffer is returned. The implementation
+** of the aggregate can use the returned buffer to accumulate data.
+**
+** The buffer allocated is freed automatically be SQLite.
+*/
+void *sqlite_aggregate_context(sqlite_func*, int nBytes);
+
+/*
+** The next routine returns the number of calls to xStep for a particular
+** aggregate function instance. The current call to xStep counts so this
+** routine always returns at least 1.
+*/
+int sqlite_aggregate_count(sqlite_func*);
+
+/*
+** This routine registers a callback with the SQLite library. The
+** callback is invoked (at compile-time, not at run-time) for each
+** attempt to access a column of a table in the database. The callback
+** returns SQLITE_OK if access is allowed, SQLITE_DENY if the entire
+** SQL statement should be aborted with an error and SQLITE_IGNORE
+** if the column should be treated as a NULL value.
+*/
+int sqlite_set_authorizer(
+ sqlite*,
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pUserData
+);
+
+/*
+** The second parameter to the access authorization function above will
+** be one of the values below. These values signify what kind of operation
+** is to be authorized. The 3rd and 4th parameters to the authorization
+** function will be parameters or NULL depending on which of the following
+** codes is used as the second parameter. The 5th parameter is the name
+** of the database ("main", "temp", etc.) if applicable. The 6th parameter
+** is the name of the inner-most trigger or view that is responsible for
+** the access attempt or NULL if this access attempt is directly from
+** input SQL code.
+**
+** Arg-3 Arg-4
+*/
+#define SQLITE_COPY 0 /* Table Name File Name */
+#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */
+#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */
+#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */
+#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */
+#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */
+#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */
+#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */
+#define SQLITE_CREATE_VIEW 8 /* View Name NULL */
+#define SQLITE_DELETE 9 /* Table Name NULL */
+#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */
+#define SQLITE_DROP_TABLE 11 /* Table Name NULL */
+#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */
+#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */
+#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */
+#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */
+#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */
+#define SQLITE_DROP_VIEW 17 /* View Name NULL */
+#define SQLITE_INSERT 18 /* Table Name NULL */
+#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */
+#define SQLITE_READ 20 /* Table Name Column Name */
+#define SQLITE_SELECT 21 /* NULL NULL */
+#define SQLITE_TRANSACTION 22 /* NULL NULL */
+#define SQLITE_UPDATE 23 /* Table Name Column Name */
+#define SQLITE_ATTACH 24 /* Filename NULL */
+#define SQLITE_DETACH 25 /* Database Name NULL */
+
+
+/*
+** The return value of the authorization function should be one of the
+** following constants:
+*/
+/* #define SQLITE_OK 0 // Allow access (This is actually defined above) */
+#define SQLITE_DENY 1 /* Abort the SQL statement with an error */
+#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
+
+/*
+** Register a function that is called at every invocation of sqlite_exec()
+** or sqlite_compile(). This function can be used (for example) to generate
+** a log file of all SQL executed against a database.
+*/
+void *sqlite_trace(sqlite*, void(*xTrace)(void*,const char*), void*);
+
+/*** The Callback-Free API
+**
+** The following routines implement a new way to access SQLite that does not
+** involve the use of callbacks.
+**
+** An sqlite_vm is an opaque object that represents a single SQL statement
+** that is ready to be executed.
+*/
+typedef struct sqlite_vm sqlite_vm;
+
+/*
+** To execute an SQLite query without the use of callbacks, you first have
+** to compile the SQL using this routine. The 1st parameter "db" is a pointer
+** to an sqlite object obtained from sqlite_open(). The 2nd parameter
+** "zSql" is the text of the SQL to be compiled. The remaining parameters
+** are all outputs.
+**
+** *pzTail is made to point to the first character past the end of the first
+** SQL statement in zSql. This routine only compiles the first statement
+** in zSql, so *pzTail is left pointing to what remains uncompiled.
+**
+** *ppVm is left pointing to a "virtual machine" that can be used to execute
+** the compiled statement. Or if there is an error, *ppVm may be set to NULL.
+** If the input text contained no SQL (if the input is and empty string or
+** a comment) then *ppVm is set to NULL.
+**
+** If any errors are detected during compilation, an error message is written
+** into space obtained from malloc() and *pzErrMsg is made to point to that
+** error message. The calling routine is responsible for freeing the text
+** of this message when it has finished with it. Use sqlite_freemem() to
+** free the message. pzErrMsg may be NULL in which case no error message
+** will be generated.
+**
+** On success, SQLITE_OK is returned. Otherwise and error code is returned.
+*/
+int sqlite_compile(
+ sqlite *db, /* The open database */
+ const char *zSql, /* SQL statement to be compiled */
+ const char **pzTail, /* OUT: uncompiled tail of zSql */
+ sqlite_vm **ppVm, /* OUT: the virtual machine to execute zSql */
+ char **pzErrmsg /* OUT: Error message. */
+);
+
+/*
+** After an SQL statement has been compiled, it is handed to this routine
+** to be executed. This routine executes the statement as far as it can
+** go then returns. The return value will be one of SQLITE_DONE,
+** SQLITE_ERROR, SQLITE_BUSY, SQLITE_ROW, or SQLITE_MISUSE.
+**
+** SQLITE_DONE means that the execute of the SQL statement is complete
+** an no errors have occurred. sqlite_step() should not be called again
+** for the same virtual machine. *pN is set to the number of columns in
+** the result set and *pazColName is set to an array of strings that
+** describe the column names and datatypes. The name of the i-th column
+** is (*pazColName)[i] and the datatype of the i-th column is
+** (*pazColName)[i+*pN]. *pazValue is set to NULL.
+**
+** SQLITE_ERROR means that the virtual machine encountered a run-time
+** error. sqlite_step() should not be called again for the same
+** virtual machine. *pN is set to 0 and *pazColName and *pazValue are set
+** to NULL. Use sqlite_finalize() to obtain the specific error code
+** and the error message text for the error.
+**
+** SQLITE_BUSY means that an attempt to open the database failed because
+** another thread or process is holding a lock. The calling routine
+** can try again to open the database by calling sqlite_step() again.
+** The return code will only be SQLITE_BUSY if no busy handler is registered
+** using the sqlite_busy_handler() or sqlite_busy_timeout() routines. If
+** a busy handler callback has been registered but returns 0, then this
+** routine will return SQLITE_ERROR and sqltie_finalize() will return
+** SQLITE_BUSY when it is called.
+**
+** SQLITE_ROW means that a single row of the result is now available.
+** The data is contained in *pazValue. The value of the i-th column is
+** (*azValue)[i]. *pN and *pazColName are set as described in SQLITE_DONE.
+** Invoke sqlite_step() again to advance to the next row.
+**
+** SQLITE_MISUSE is returned if sqlite_step() is called incorrectly.
+** For example, if you call sqlite_step() after the virtual machine
+** has halted (after a prior call to sqlite_step() has returned SQLITE_DONE)
+** or if you call sqlite_step() with an incorrectly initialized virtual
+** machine or a virtual machine that has been deleted or that is associated
+** with an sqlite structure that has been closed.
+*/
+int sqlite_step(
+ sqlite_vm *pVm, /* The virtual machine to execute */
+ int *pN, /* OUT: Number of columns in result */
+ const char ***pazValue, /* OUT: Column data */
+ const char ***pazColName /* OUT: Column names and datatypes */
+);
+
+/*
+** This routine is called to delete a virtual machine after it has finished
+** executing. The return value is the result code. SQLITE_OK is returned
+** if the statement executed successfully and some other value is returned if
+** there was any kind of error. If an error occurred and pzErrMsg is not
+** NULL, then an error message is written into memory obtained from malloc()
+** and *pzErrMsg is made to point to that error message. The calling routine
+** should use sqlite_freemem() to delete this message when it has finished
+** with it.
+**
+** This routine can be called at any point during the execution of the
+** virtual machine. If the virtual machine has not completed execution
+** when this routine is called, that is like encountering an error or
+** an interrupt. (See sqlite_interrupt().) Incomplete updates may be
+** rolled back and transactions canceled, depending on the circumstances,
+** and the result code returned will be SQLITE_ABORT.
+*/
+int sqlite_finalize(sqlite_vm*, char **pzErrMsg);
+
+/*
+** This routine deletes the virtual machine, writes any error message to
+** *pzErrMsg and returns an SQLite return code in the same way as the
+** sqlite_finalize() function.
+**
+** Additionally, if ppVm is not NULL, *ppVm is left pointing to a new virtual
+** machine loaded with the compiled version of the original query ready for
+** execution.
+**
+** If sqlite_reset() returns SQLITE_SCHEMA, then *ppVm is set to NULL.
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+int sqlite_reset(sqlite_vm*, char **pzErrMsg);
+
+/*
+** If the SQL that was handed to sqlite_compile contains variables that
+** are represented in the SQL text by a question mark ('?'). This routine
+** is used to assign values to those variables.
+**
+** The first parameter is a virtual machine obtained from sqlite_compile().
+** The 2nd "idx" parameter determines which variable in the SQL statement
+** to bind the value to. The left most '?' is 1. The 3rd parameter is
+** the value to assign to that variable. The 4th parameter is the number
+** of bytes in the value, including the terminating \000 for strings.
+** Finally, the 5th "copy" parameter is TRUE if SQLite should make its
+** own private copy of this value, or false if the space that the 3rd
+** parameter points to will be unchanging and can be used directly by
+** SQLite.
+**
+** Unbound variables are treated as having a value of NULL. To explicitly
+** set a variable to NULL, call this routine with the 3rd parameter as a
+** NULL pointer.
+**
+** If the 4th "len" parameter is -1, then strlen() is used to find the
+** length.
+**
+** This routine can only be called immediately after sqlite_compile()
+** or sqlite_reset() and before any calls to sqlite_step().
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+int sqlite_bind(sqlite_vm*, int idx, const char *value, int len, int copy);
+
+/*
+** This routine configures a callback function - the progress callback - that
+** is invoked periodically during long running calls to sqlite_exec(),
+** sqlite_step() and sqlite_get_table(). An example use for this API is to keep
+** a GUI updated during a large query.
+**
+** The progress callback is invoked once for every N virtual machine opcodes,
+** where N is the second argument to this function. The progress callback
+** itself is identified by the third argument to this function. The fourth
+** argument to this function is a void pointer passed to the progress callback
+** function each time it is invoked.
+**
+** If a call to sqlite_exec(), sqlite_step() or sqlite_get_table() results
+** in less than N opcodes being executed, then the progress callback is not
+** invoked.
+**
+** Calling this routine overwrites any previously installed progress callback.
+** To remove the progress callback altogether, pass NULL as the third
+** argument to this function.
+**
+** If the progress callback returns a result other than 0, then the current
+** query is immediately terminated and any database changes rolled back. If the
+** query was part of a larger transaction, then the transaction is not rolled
+** back and remains active. The sqlite_exec() call returns SQLITE_ABORT.
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+void sqlite_progress_handler(sqlite*, int, int(*)(void*), void*);
+
+/*
+** Register a callback function to be invoked whenever a new transaction
+** is committed. The pArg argument is passed through to the callback.
+** callback. If the callback function returns non-zero, then the commit
+** is converted into a rollback.
+**
+** If another function was previously registered, its pArg value is returned.
+** Otherwise NULL is returned.
+**
+** Registering a NULL function disables the callback.
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+void *sqlite_commit_hook(sqlite*, int(*)(void*), void*);
+
+/*
+** Open an encrypted SQLite database. If pKey==0 or nKey==0, this routine
+** is the same as sqlite_open().
+**
+** The code to implement this API is not available in the public release
+** of SQLite.
+*/
+sqlite *sqlite_open_encrypted(
+ const char *zFilename, /* Name of the encrypted database */
+ const void *pKey, /* Pointer to the key */
+ int nKey, /* Number of bytes in the key */
+ int *pErrcode, /* Write error code here */
+ char **pzErrmsg /* Write error message here */
+);
+
+/*
+** Change the key on an open database. If the current database is not
+** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the
+** database is decrypted.
+**
+** The code to implement this API is not available in the public release
+** of SQLite.
+*/
+int sqlite_rekey(
+ sqlite *db, /* Database to be re-keyed */
+ const void *pKey, int nKey /* The new key */
+);
+
+/*
+** Encode a binary buffer "in" of size n bytes so that it contains
+** no instances of characters '\'' or '\000'. The output is
+** null-terminated and can be used as a string value in an INSERT
+** or UPDATE statement. Use sqlite_decode_binary() to convert the
+** string back into its original binary.
+**
+** The result is written into a preallocated output buffer "out".
+** "out" must be able to hold at least 2 +(257*n)/254 bytes.
+** In other words, the output will be expanded by as much as 3
+** bytes for every 254 bytes of input plus 2 bytes of fixed overhead.
+** (This is approximately 2 + 1.0118*n or about a 1.2% size increase.)
+**
+** The return value is the number of characters in the encoded
+** string, excluding the "\000" terminator.
+**
+** If out==NULL then no output is generated but the routine still returns
+** the number of characters that would have been generated if out had
+** not been NULL.
+*/
+int sqlite_encode_binary(const unsigned char *in, int n, unsigned char *out);
+
+/*
+** 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);
+
+#ifdef __cplusplus
+} /* End of the 'extern "C"' block */
+#endif
+
+#endif /* _SQLITE_H_ */
diff --git a/src/libs/sqlite2/sqliteInt.h b/src/libs/sqlite2/sqliteInt.h
new file mode 100644
index 00000000..3c4d818a
--- /dev/null
+++ b/src/libs/sqlite2/sqliteInt.h
@@ -0,0 +1,1274 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Internal interface definitions for SQLite.
+**
+** @(#) $Id: sqliteInt.h 875675 2008-10-25 07:31:30Z cgilles $
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sqlite.h"
+#include "hash.h"
+#include "parse.h"
+#include "btree.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/*
+** The maximum number of in-memory pages to use for the main database
+** table and for temporary tables.
+*/
+#define MAX_PAGES 2000
+#define TEMP_PAGES 500
+
+/*
+** If the following macro is set to 1, then NULL values are considered
+** distinct for the SELECT DISTINCT statement and for UNION or EXCEPT
+** compound queries. No other SQL database engine (among those tested)
+** works this way except for OCELOT. But the SQL92 spec implies that
+** this is how things should work.
+**
+** If the following macro is set to 0, then NULLs are indistinct for
+** SELECT DISTINCT and for UNION.
+*/
+#define NULL_ALWAYS_DISTINCT 0
+
+/*
+** If the following macro is set to 1, then NULL values are considered
+** distinct when determining whether or not two entries are the same
+** in a UNIQUE index. This is the way PostgreSQL, Oracle, DB2, MySQL,
+** OCELOT, and Firebird all work. The SQL92 spec explicitly says this
+** is the way things are suppose to work.
+**
+** If the following macro is set to 0, the NULLs are indistinct for
+** a UNIQUE index. In this mode, you can only have a single NULL entry
+** for a column declared UNIQUE. This is the way Informix and SQL Server
+** work.
+*/
+#define NULL_DISTINCT_FOR_UNIQUE 1
+
+/*
+** The maximum number of attached databases. This must be at least 2
+** in order to support the main database file (0) and the file used to
+** hold temporary tables (1). And it must be less than 256 because
+** an unsigned character is used to stored the database index.
+*/
+#define MAX_ATTACHED 10
+
+/*
+** The next macro is used to determine where TEMP tables and indices
+** are stored. Possible values:
+**
+** 0 Always use a temporary files
+** 1 Use a file unless overridden by "PRAGMA temp_store"
+** 2 Use memory unless overridden by "PRAGMA temp_store"
+** 3 Always use memory
+*/
+#ifndef TEMP_STORE
+# define TEMP_STORE 1
+#endif
+
+/*
+** When building SQLite for embedded systems where memory is scarce,
+** you can define one or more of the following macros to omit extra
+** features of the library and thus keep the size of the library to
+** a minimum.
+*/
+/* #define SQLITE_OMIT_AUTHORIZATION 1 */
+/* #define SQLITE_OMIT_INMEMORYDB 1 */
+/* #define SQLITE_OMIT_VACUUM 1 */
+/* #define SQLITE_OMIT_DATETIME_FUNCS 1 */
+/* #define SQLITE_OMIT_PROGRESS_CALLBACK 1 */
+
+/*
+** Integers of known sizes. These typedefs might change for architectures
+** where the sizes very. Preprocessor macros are available so that the
+** types can be conveniently redefined at compile-type. Like this:
+**
+** cc '-DUINTPTR_TYPE=long long int' ...
+*/
+#ifndef UINT32_TYPE
+# define UINT32_TYPE unsigned int
+#endif
+#ifndef UINT16_TYPE
+# define UINT16_TYPE unsigned short int
+#endif
+#ifndef INT16_TYPE
+# define INT16_TYPE short int
+#endif
+#ifndef UINT8_TYPE
+# define UINT8_TYPE unsigned char
+#endif
+#ifndef INT8_TYPE
+# define INT8_TYPE signed char
+#endif
+#ifndef INTPTR_TYPE
+# if SQLITE_PTR_SZ==4
+# define INTPTR_TYPE int
+# else
+# define INTPTR_TYPE long long
+# endif
+#endif
+typedef UINT32_TYPE u32; /* 4-byte unsigned integer */
+typedef UINT16_TYPE u16; /* 2-byte unsigned integer */
+typedef INT16_TYPE i16; /* 2-byte signed integer */
+typedef UINT8_TYPE u8; /* 1-byte unsigned integer */
+typedef UINT8_TYPE i8; /* 1-byte signed integer */
+typedef INTPTR_TYPE ptr; /* Big enough to hold a pointer */
+typedef unsigned INTPTR_TYPE uptr; /* Big enough to hold a pointer */
+
+/*
+** Defer sourcing vdbe.h until after the "u8" typedef is defined.
+*/
+#include "vdbe.h"
+
+/*
+** Most C compilers these days recognize "long double", don't they?
+** Just in case we encounter one that does not, we will create a macro
+** for long double so that it can be easily changed to just "double".
+*/
+#ifndef LONGDOUBLE_TYPE
+# define LONGDOUBLE_TYPE long double
+#endif
+
+/*
+** This macro casts a pointer to an integer. Useful for doing
+** pointer arithmetic.
+*/
+#define Addr(X) ((uptr)X)
+
+/*
+** The maximum number of bytes of data that can be put into a single
+** row of a single table. The upper bound on this limit is 16777215
+** bytes (or 16MB-1). We have arbitrarily set the limit to just 1MB
+** here because the overflow page chain is inefficient for really big
+** records and we want to discourage people from thinking that
+** multi-megabyte records are OK. If your needs are different, you can
+** change this define and recompile to increase or decrease the record
+** size.
+**
+** The 16777198 is computed as follows: 238 bytes of payload on the
+** original pages plus 16448 overflow pages each holding 1020 bytes of
+** data.
+*/
+#define MAX_BYTES_PER_ROW 1048576
+/* #define MAX_BYTES_PER_ROW 16777198 */
+
+/*
+** If memory allocation problems are found, recompile with
+**
+** -DMEMORY_DEBUG=1
+**
+** to enable some sanity checking on malloc() and free(). To
+** check for memory leaks, recompile with
+**
+** -DMEMORY_DEBUG=2
+**
+** and a line of text will be written to standard error for
+** each malloc() and free(). This output can be analyzed
+** by an AWK script to determine if there are any leaks.
+*/
+#ifdef MEMORY_DEBUG
+# define sqliteMalloc(X) sqliteMalloc_(X,1,__FILE__,__LINE__)
+# define sqliteMallocRaw(X) sqliteMalloc_(X,0,__FILE__,__LINE__)
+# define sqliteFree(X) sqliteFree_(X,__FILE__,__LINE__)
+# define sqliteRealloc(X,Y) sqliteRealloc_(X,Y,__FILE__,__LINE__)
+# define sqliteStrDup(X) sqliteStrDup_(X,__FILE__,__LINE__)
+# define sqliteStrNDup(X,Y) sqliteStrNDup_(X,Y,__FILE__,__LINE__)
+ void sqliteStrRealloc(char**);
+#else
+# define sqliteRealloc_(X,Y) sqliteRealloc(X,Y)
+# define sqliteStrRealloc(X)
+#endif
+
+/*
+** This variable gets set if malloc() ever fails. After it gets set,
+** the SQLite library shuts down permanently.
+*/
+extern int sqlite_malloc_failed;
+
+/*
+** The following global variables are used for testing and debugging
+** only. They only work if MEMORY_DEBUG is defined.
+*/
+#ifdef MEMORY_DEBUG
+extern int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
+extern int sqlite_nFree; /* Number of sqliteFree() calls */
+extern int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
+#endif
+
+/*
+** Name of the master database table. The master database table
+** is a special table that holds the names and attributes of all
+** user tables and indices.
+*/
+#define MASTER_NAME "sqlite_master"
+#define TEMP_MASTER_NAME "sqlite_temp_master"
+
+/*
+** The name of the schema table.
+*/
+#define SCHEMA_TABLE(x) (x?TEMP_MASTER_NAME:MASTER_NAME)
+
+/*
+** A convenience macro that returns the number of elements in
+** an array.
+*/
+#define ArraySize(X) (sizeof(X)/sizeof(X[0]))
+
+/*
+** Forward references to structures
+*/
+typedef struct Column Column;
+typedef struct Table Table;
+typedef struct Index Index;
+typedef struct Instruction Instruction;
+typedef struct Expr Expr;
+typedef struct ExprList ExprList;
+typedef struct Parse Parse;
+typedef struct Token Token;
+typedef struct IdList IdList;
+typedef struct SrcList SrcList;
+typedef struct WhereInfo WhereInfo;
+typedef struct WhereLevel WhereLevel;
+typedef struct Select Select;
+typedef struct AggExpr AggExpr;
+typedef struct FuncDef FuncDef;
+typedef struct Trigger Trigger;
+typedef struct TriggerStep TriggerStep;
+typedef struct TriggerStack TriggerStack;
+typedef struct FKey FKey;
+typedef struct Db Db;
+typedef struct AuthContext AuthContext;
+
+/*
+** Each database file to be accessed by the system is an instance
+** of the following structure. There are normally two of these structures
+** in the sqlite.aDb[] array. aDb[0] is the main database file and
+** aDb[1] is the database file used to hold temporary tables. Additional
+** databases may be attached.
+*/
+struct Db {
+ char *zName; /* Name of this database */
+ Btree *pBt; /* The B*Tree structure for this database file */
+ int schema_cookie; /* Database schema version number for this file */
+ Hash tblHash; /* All tables indexed by name */
+ Hash idxHash; /* All (named) indices indexed by name */
+ Hash trigHash; /* All triggers indexed by name */
+ Hash aFKey; /* Foreign keys indexed by to-table */
+ u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */
+ u16 flags; /* Flags associated with this database */
+ void *pAux; /* Auxiliary data. Usually NULL */
+ void (*xFreeAux)(void*); /* Routine to free pAux */
+};
+
+/*
+** These macros can be used to test, set, or clear bits in the
+** Db.flags field.
+*/
+#define DbHasProperty(D,I,P) (((D)->aDb[I].flags&(P))==(P))
+#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].flags&(P))!=0)
+#define DbSetProperty(D,I,P) (D)->aDb[I].flags|=(P)
+#define DbClearProperty(D,I,P) (D)->aDb[I].flags&=~(P)
+
+/*
+** Allowed values for the DB.flags field.
+**
+** The DB_Locked flag is set when the first OP_Transaction or OP_Checkpoint
+** opcode is emitted for a database. This prevents multiple occurances
+** of those opcodes for the same database in the same program. Similarly,
+** the DB_Cookie flag is set when the OP_VerifyCookie opcode is emitted,
+** and prevents duplicate OP_VerifyCookies from taking up space and slowing
+** down execution.
+**
+** The DB_SchemaLoaded flag is set after the database schema has been
+** read into internal hash tables.
+**
+** DB_UnresetViews means that one or more views have column names that
+** have been filled out. If the schema changes, these column names might
+** changes and so the view will need to be reset.
+*/
+#define DB_Locked 0x0001 /* OP_Transaction opcode has been emitted */
+#define DB_Cookie 0x0002 /* OP_VerifyCookie opcode has been emiited */
+#define DB_SchemaLoaded 0x0004 /* The schema has been loaded */
+#define DB_UnresetViews 0x0008 /* Some views have defined column names */
+
+
+/*
+** Each database is an instance of the following structure.
+**
+** The sqlite.file_format is initialized by the database file
+** and helps determines how the data in the database file is
+** represented. This field allows newer versions of the library
+** to read and write older databases. The various file formats
+** are as follows:
+**
+** file_format==1 Version 2.1.0.
+** file_format==2 Version 2.2.0. Add support for INTEGER PRIMARY KEY.
+** file_format==3 Version 2.6.0. Fix empty-string index bug.
+** file_format==4 Version 2.7.0. Add support for separate numeric and
+** text datatypes.
+**
+** The sqlite.temp_store determines where temporary database files
+** are stored. If 1, then a file is created to hold those tables. If
+** 2, then they are held in memory. 0 means use the default value in
+** the TEMP_STORE macro.
+**
+** The sqlite.lastRowid records the last insert rowid generated by an
+** insert statement. Inserts on views do not affect its value. Each
+** trigger has its own context, so that lastRowid can be updated inside
+** triggers as usual. The previous value will be restored once the trigger
+** exits. Upon entering a before or instead of trigger, lastRowid is no
+** longer (since after version 2.8.12) reset to -1.
+**
+** The sqlite.nChange does not count changes within triggers and keeps no
+** context. It is reset at start of sqlite_exec.
+** The sqlite.lsChange represents the number of changes made by the last
+** insert, update, or delete statement. It remains constant throughout the
+** length of a statement and is then updated by OP_SetCounts. It keeps a
+** context stack just like lastRowid so that the count of changes
+** within a trigger is not seen outside the trigger. Changes to views do not
+** affect the value of lsChange.
+** The sqlite.csChange keeps track of the number of current changes (since
+** the last statement) and is used to update sqlite_lsChange.
+*/
+struct sqlite {
+ int nDb; /* Number of backends currently in use */
+ Db *aDb; /* All backends */
+ Db aDbStatic[2]; /* Static space for the 2 default backends */
+ int flags; /* Miscellanous flags. See below */
+ u8 file_format; /* What file format version is this database? */
+ u8 safety_level; /* How aggressive at synching data to disk */
+ u8 want_to_close; /* Close after all VDBEs are deallocated */
+ u8 temp_store; /* 1=file, 2=memory, 0=compile-time default */
+ u8 onError; /* Default conflict algorithm */
+ int next_cookie; /* Next value of aDb[0].schema_cookie */
+ int cache_size; /* Number of pages to use in the cache */
+ int nTable; /* Number of tables in the database */
+ void *pBusyArg; /* 1st Argument to the busy callback */
+ int (*xBusyCallback)(void *,const char*,int); /* The busy callback */
+ void *pCommitArg; /* Argument to xCommitCallback() */
+ int (*xCommitCallback)(void*);/* Invoked at every commit. */
+ Hash aFunc; /* All functions that can be in SQL exprs */
+ int lastRowid; /* ROWID of most recent insert (see above) */
+ int priorNewRowid; /* Last randomly generated ROWID */
+ int magic; /* Magic number for detect library misuse */
+ int nChange; /* Number of rows changed (see above) */
+ int lsChange; /* Last statement change count (see above) */
+ int csChange; /* Current statement change count (see above) */
+ struct sqliteInitInfo { /* Information used during initialization */
+ int iDb; /* When back is being initialized */
+ int newTnum; /* Rootpage of table being initialized */
+ u8 busy; /* TRUE if currently initializing */
+ } init;
+ struct Vdbe *pVdbe; /* List of active virtual machines */
+ void (*xTrace)(void*,const char*); /* Trace function */
+ void *pTraceArg; /* Argument to the trace function */
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
+ /* Access authorization function */
+ void *pAuthArg; /* 1st argument to the access auth function */
+#endif
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ int (*xProgress)(void *); /* The progress callback */
+ void *pProgressArg; /* Argument to the progress callback */
+ int nProgressOps; /* Number of opcodes for progress callback */
+#endif
+};
+
+/*
+** Possible values for the sqlite.flags and or Db.flags fields.
+**
+** On sqlite.flags, the SQLITE_InTrans value means that we have
+** executed a BEGIN. On Db.flags, SQLITE_InTrans means a statement
+** transaction is active on that particular database file.
+*/
+#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */
+#define SQLITE_Initialized 0x00000002 /* True after initialization */
+#define SQLITE_Interrupt 0x00000004 /* Cancel current operation */
+#define SQLITE_InTrans 0x00000008 /* True if in a transaction */
+#define SQLITE_InternChanges 0x00000010 /* Uncommitted Hash table changes */
+#define SQLITE_FullColNames 0x00000020 /* Show full column names on SELECT */
+#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */
+#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */
+ /* DELETE, or UPDATE and return */
+ /* the count using a callback. */
+#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */
+ /* result set is empty */
+#define SQLITE_ReportTypes 0x00000200 /* Include information on datatypes */
+ /* in 4th argument of callback */
+
+/*
+** Possible values for the sqlite.magic field.
+** The numbers are obtained at random and have no special meaning, other
+** than being distinct from one another.
+*/
+#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */
+#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */
+#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */
+#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */
+
+/*
+** Each SQL function is defined by an instance of the following
+** structure. A pointer to this structure is stored in the sqlite.aFunc
+** hash table. When multiple functions have the same name, the hash table
+** points to a linked list of these structures.
+*/
+struct FuncDef {
+ void (*xFunc)(sqlite_func*,int,const char**); /* Regular function */
+ void (*xStep)(sqlite_func*,int,const char**); /* Aggregate function step */
+ void (*xFinalize)(sqlite_func*); /* Aggregate function finializer */
+ signed char nArg; /* Number of arguments. -1 means unlimited */
+ signed char dataType; /* Arg that determines datatype. -1=NUMERIC, */
+ /* -2=TEXT. -3=SQLITE_ARGS */
+ u8 includeTypes; /* Add datatypes to args of xFunc and xStep */
+ void *pUserData; /* User data parameter */
+ FuncDef *pNext; /* Next function with same name */
+};
+
+/*
+** information about each column of an SQL table is held in an instance
+** of this structure.
+*/
+struct Column {
+ char *zName; /* Name of this column */
+ char *zDflt; /* Default value of this column */
+ char *zType; /* Data type for this column */
+ u8 notNull; /* True if there is a NOT NULL constraint */
+ u8 isPrimKey; /* True if this column is part of the PRIMARY KEY */
+ u8 sortOrder; /* Some combination of SQLITE_SO_... values */
+ u8 dottedName; /* True if zName contains a "." character */
+};
+
+/*
+** The allowed sort orders.
+**
+** The TEXT and NUM values use bits that do not overlap with DESC and ASC.
+** That way the two can be combined into a single number.
+*/
+#define SQLITE_SO_UNK 0 /* Use the default collating type. (SCT_NUM) */
+#define SQLITE_SO_TEXT 2 /* Sort using memcmp() */
+#define SQLITE_SO_NUM 4 /* Sort using sqliteCompare() */
+#define SQLITE_SO_TYPEMASK 6 /* Mask to extract the collating sequence */
+#define SQLITE_SO_ASC 0 /* Sort in ascending order */
+#define SQLITE_SO_DESC 1 /* Sort in descending order */
+#define SQLITE_SO_DIRMASK 1 /* Mask to extract the sort direction */
+
+/*
+** Each SQL table is represented in memory by an instance of the
+** following structure.
+**
+** Table.zName is the name of the table. The case of the original
+** CREATE TABLE statement is stored, but case is not significant for
+** comparisons.
+**
+** Table.nCol is the number of columns in this table. Table.aCol is a
+** pointer to an array of Column structures, one for each column.
+**
+** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of
+** the column that is that key. Otherwise Table.iPKey is negative. Note
+** that the datatype of the PRIMARY KEY must be INTEGER for this field to
+** be set. An INTEGER PRIMARY KEY is used as the rowid for each row of
+** the table. If a table has no INTEGER PRIMARY KEY, then a random rowid
+** is generated for each row of the table. Table.hasPrimKey is true if
+** the table has any PRIMARY KEY, INTEGER or otherwise.
+**
+** Table.tnum is the page number for the root BTree page of the table in the
+** database file. If Table.iDb is the index of the database table backend
+** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that
+** holds temporary tables and indices. If Table.isTransient
+** is true, then the table is stored in a file that is automatically deleted
+** when the VDBE cursor to the table is closed. In this case Table.tnum
+** refers VDBE cursor number that holds the table open, not to the root
+** page number. Transient tables are used to hold the results of a
+** sub-query that appears instead of a real table name in the FROM clause
+** of a SELECT statement.
+*/
+struct Table {
+ char *zName; /* Name of the table */
+ int nCol; /* Number of columns in this table */
+ Column *aCol; /* Information about each column */
+ int iPKey; /* If not less then 0, use aCol[iPKey] as the primary key */
+ Index *pIndex; /* List of SQL indexes on this table. */
+ int tnum; /* Root BTree node for this table (see note above) */
+ Select *pSelect; /* NULL for tables. Points to definition if a view. */
+ u8 readOnly; /* True if this table should not be written by the user */
+ u8 iDb; /* Index into sqlite.aDb[] of the backend for this table */
+ u8 isTransient; /* True if automatically deleted when VDBE finishes */
+ u8 hasPrimKey; /* True if there exists a primary key */
+ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */
+ Trigger *pTrigger; /* List of SQL triggers on this table */
+ FKey *pFKey; /* Linked list of all foreign keys in this table */
+};
+
+/*
+** Each foreign key constraint is an instance of the following structure.
+**
+** A foreign key is associated with two tables. The "from" table is
+** the table that contains the REFERENCES clause that creates the foreign
+** key. The "to" table is the table that is named in the REFERENCES clause.
+** Consider this example:
+**
+** CREATE TABLE ex1(
+** a INTEGER PRIMARY KEY,
+** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x)
+** );
+**
+** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2".
+**
+** Each REFERENCES clause generates an instance of the following structure
+** which is attached to the from-table. The to-table need not exist when
+** the from-table is created. The existance of the to-table is not checked
+** until an attempt is made to insert data into the from-table.
+**
+** The sqlite.aFKey hash table stores pointers to this structure
+** given the name of a to-table. For each to-table, all foreign keys
+** associated with that table are on a linked list using the FKey.pNextTo
+** field.
+*/
+struct FKey {
+ Table *pFrom; /* The table that constains the REFERENCES clause */
+ FKey *pNextFrom; /* Next foreign key in pFrom */
+ char *zTo; /* Name of table that the key points to */
+ FKey *pNextTo; /* Next foreign key that points to zTo */
+ int nCol; /* Number of columns in this key */
+ struct sColMap { /* Mapping of columns in pFrom to columns in zTo */
+ int iFrom; /* Index of column in pFrom */
+ char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */
+ } *aCol; /* One entry for each of nCol column s */
+ u8 isDeferred; /* True if constraint checking is deferred till COMMIT */
+ u8 updateConf; /* How to resolve conflicts that occur on UPDATE */
+ u8 deleteConf; /* How to resolve conflicts that occur on DELETE */
+ u8 insertConf; /* How to resolve conflicts that occur on INSERT */
+};
+
+/*
+** SQLite supports many different ways to resolve a contraint
+** error. ROLLBACK processing means that a constraint violation
+** causes the operation in process to fail and for the current transaction
+** to be rolled back. ABORT processing means the operation in process
+** fails and any prior changes from that one operation are backed out,
+** but the transaction is not rolled back. FAIL processing means that
+** the operation in progress stops and returns an error code. But prior
+** changes due to the same operation are not backed out and no rollback
+** occurs. IGNORE means that the particular row that caused the constraint
+** error is not inserted or updated. Processing continues and no error
+** is returned. REPLACE means that preexisting database rows that caused
+** a UNIQUE constraint violation are removed so that the new insert or
+** update can proceed. Processing continues and no error is reported.
+**
+** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys.
+** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the
+** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign
+** key is set to NULL. CASCADE means that a DELETE or UPDATE of the
+** referenced table row is propagated into the row that holds the
+** foreign key.
+**
+** The following symbolic values are used to record which type
+** of action to take.
+*/
+#define OE_None 0 /* There is no constraint to check */
+#define OE_Rollback 1 /* Fail the operation and rollback the transaction */
+#define OE_Abort 2 /* Back out changes but do no rollback transaction */
+#define OE_Fail 3 /* Stop the operation but leave all prior changes */
+#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */
+#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */
+
+#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */
+#define OE_SetNull 7 /* Set the foreign key value to NULL */
+#define OE_SetDflt 8 /* Set the foreign key value to its default */
+#define OE_Cascade 9 /* Cascade the changes */
+
+#define OE_Default 99 /* Do whatever the default action is */
+
+/*
+** Each SQL index is represented in memory by an
+** instance of the following structure.
+**
+** The columns of the table that are to be indexed are described
+** by the aiColumn[] field of this structure. For example, suppose
+** we have the following table and index:
+**
+** CREATE TABLE Ex1(c1 int, c2 int, c3 text);
+** CREATE INDEX Ex2 ON Ex1(c3,c1);
+**
+** In the Table structure describing Ex1, nCol==3 because there are
+** three columns in the table. In the Index structure describing
+** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed.
+** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the
+** first column to be indexed (c3) has an index of 2 in Ex1.aCol[].
+** The second column to be indexed (c1) has an index of 0 in
+** Ex1.aCol[], hence Ex2.aiColumn[1]==0.
+**
+** The Index.onError field determines whether or not the indexed columns
+** must be unique and what to do if they are not. When Index.onError=OE_None,
+** it means this is not a unique index. Otherwise it is a unique index
+** and the value of Index.onError indicate the which conflict resolution
+** algorithm to employ whenever an attempt is made to insert a non-unique
+** element.
+*/
+struct Index {
+ char *zName; /* Name of this index */
+ int nColumn; /* Number of columns in the table used by this index */
+ int *aiColumn; /* Which columns are used by this index. 1st is 0 */
+ Table *pTable; /* The SQL table being indexed */
+ int tnum; /* Page containing root of this index in database file */
+ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
+ u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */
+ u8 iDb; /* Index in sqlite.aDb[] of where this index is stored */
+ Index *pNext; /* The next index associated with the same table */
+};
+
+/*
+** Each token coming out of the lexer is an instance of
+** this structure. Tokens are also used as part of an expression.
+**
+** Note if Token.z==0 then Token.dyn and Token.n are undefined and
+** may contain random values. Do not make any assuptions about Token.dyn
+** and Token.n when Token.z==0.
+*/
+struct Token {
+ const char *z; /* Text of the token. Not NULL-terminated! */
+ unsigned dyn : 1; /* True for malloced memory, false for static */
+ unsigned n : 31; /* Number of characters in this token */
+};
+
+/*
+** Each node of an expression in the parse tree is an instance
+** of this structure.
+**
+** Expr.op is the opcode. The integer parser token codes are reused
+** as opcodes here. For example, the parser defines TK_GE to be an integer
+** code representing the ">=" operator. This same integer code is reused
+** to represent the greater-than-or-equal-to operator in the expression
+** tree.
+**
+** Expr.pRight and Expr.pLeft are subexpressions. Expr.pList is a list
+** of argument if the expression is a function.
+**
+** Expr.token is the operator token for this node. For some expressions
+** that have subexpressions, Expr.token can be the complete text that gave
+** rise to the Expr. In the latter case, the token is marked as being
+** a compound token.
+**
+** An expression of the form ID or ID.ID refers to a column in a table.
+** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is
+** the integer cursor number of a VDBE cursor pointing to that table and
+** Expr.iColumn is the column number for the specific column. If the
+** expression is used as a result in an aggregate SELECT, then the
+** value is also stored in the Expr.iAgg column in the aggregate so that
+** it can be accessed after all aggregates are computed.
+**
+** If the expression is a function, the Expr.iTable is an integer code
+** representing which function. If the expression is an unbound variable
+** marker (a question mark character '?' in the original SQL) then the
+** Expr.iTable holds the index number for that variable.
+**
+** The Expr.pSelect field points to a SELECT statement. The SELECT might
+** be the right operand of an IN operator. Or, if a scalar SELECT appears
+** in an expression the opcode is TK_SELECT and Expr.pSelect is the only
+** operand.
+*/
+struct Expr {
+ u8 op; /* Operation performed by this node */
+ u8 dataType; /* Either SQLITE_SO_TEXT or SQLITE_SO_NUM */
+ u8 iDb; /* Database referenced by this expression */
+ u8 flags; /* Various flags. See below */
+ Expr *pLeft, *pRight; /* Left and right subnodes */
+ ExprList *pList; /* A list of expressions used as function arguments
+ ** or in "<expr> IN (<expr-list)" */
+ Token token; /* An operand token */
+ Token span; /* Complete text of the expression */
+ int iTable, iColumn; /* When op==TK_COLUMN, then this expr node means the
+ ** iColumn-th field of the iTable-th table. */
+ int iAgg; /* When op==TK_COLUMN and pParse->useAgg==TRUE, pull
+ ** result from the iAgg-th element of the aggregator */
+ Select *pSelect; /* When the expression is a sub-select. Also the
+ ** right side of "<expr> IN (<select>)" */
+};
+
+/*
+** The following are the meanings of bits in the Expr.flags field.
+*/
+#define EP_FromJoin 0x0001 /* Originated in ON or USING clause of a join */
+
+/*
+** These macros can be used to test, set, or clear bits in the
+** Expr.flags field.
+*/
+#define ExprHasProperty(E,P) (((E)->flags&(P))==(P))
+#define ExprHasAnyProperty(E,P) (((E)->flags&(P))!=0)
+#define ExprSetProperty(E,P) (E)->flags|=(P)
+#define ExprClearProperty(E,P) (E)->flags&=~(P)
+
+/*
+** A list of expressions. Each expression may optionally have a
+** name. An expr/name combination can be used in several ways, such
+** as the list of "expr AS ID" fields following a "SELECT" or in the
+** list of "ID = expr" items in an UPDATE. A list of expressions can
+** also be used as the argument to a function, in which case the a.zName
+** field is not used.
+*/
+struct ExprList {
+ int nExpr; /* Number of expressions on the list */
+ int nAlloc; /* Number of entries allocated below */
+ struct ExprList_item {
+ Expr *pExpr; /* The list of expressions */
+ char *zName; /* Token associated with this expression */
+ u8 sortOrder; /* 1 for DESC or 0 for ASC */
+ u8 isAgg; /* True if this is an aggregate like count(*) */
+ u8 done; /* A flag to indicate when processing is finished */
+ } *a; /* One entry for each expression */
+};
+
+/*
+** An instance of this structure can hold a simple list of identifiers,
+** such as the list "a,b,c" in the following statements:
+**
+** INSERT INTO t(a,b,c) VALUES ...;
+** CREATE INDEX idx ON t(a,b,c);
+** CREATE TRIGGER trig BEFORE UPDATE ON t(a,b,c) ...;
+**
+** The IdList.a.idx field is used when the IdList represents the list of
+** column names after a table name in an INSERT statement. In the statement
+**
+** INSERT INTO t(a,b,c) ...
+**
+** If "a" is the k-th column of table "t", then IdList.a[0].idx==k.
+*/
+struct IdList {
+ int nId; /* Number of identifiers on the list */
+ int nAlloc; /* Number of entries allocated for a[] below */
+ struct IdList_item {
+ char *zName; /* Name of the identifier */
+ int idx; /* Index in some Table.aCol[] of a column named zName */
+ } *a;
+};
+
+/*
+** The following structure describes the FROM clause of a SELECT statement.
+** Each table or subquery in the FROM clause is a separate element of
+** the SrcList.a[] array.
+**
+** With the addition of multiple database support, the following structure
+** can also be used to describe a particular table such as the table that
+** is modified by an INSERT, DELETE, or UPDATE statement. In standard SQL,
+** such a table must be a simple name: ID. But in SQLite, the table can
+** now be identified by a database name, a dot, then the table name: ID.ID.
+*/
+struct SrcList {
+ i16 nSrc; /* Number of tables or subqueries in the FROM clause */
+ i16 nAlloc; /* Number of entries allocated in a[] below */
+ struct SrcList_item {
+ char *zDatabase; /* Name of database holding this table */
+ char *zName; /* Name of the table */
+ char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */
+ Table *pTab; /* An SQL table corresponding to zName */
+ Select *pSelect; /* A SELECT statement used in place of a table name */
+ int jointype; /* Type of join between this table and the next */
+ int iCursor; /* The VDBE cursor number used to access this table */
+ Expr *pOn; /* The ON clause of a join */
+ IdList *pUsing; /* The USING clause of a join */
+ } a[1]; /* One entry for each identifier on the list */
+};
+
+/*
+** Permitted values of the SrcList.a.jointype field
+*/
+#define JT_INNER 0x0001 /* Any kind of inner or cross join */
+#define JT_NATURAL 0x0002 /* True for a "natural" join */
+#define JT_LEFT 0x0004 /* Left outer join */
+#define JT_RIGHT 0x0008 /* Right outer join */
+#define JT_OUTER 0x0010 /* The "OUTER" keyword is present */
+#define JT_ERROR 0x0020 /* unknown or unsupported join type */
+
+/*
+** For each nested loop in a WHERE clause implementation, the WhereInfo
+** structure contains a single instance of this structure. This structure
+** is intended to be private the the where.c module and should not be
+** access or modified by other modules.
+*/
+struct WhereLevel {
+ int iMem; /* Memory cell used by this level */
+ Index *pIdx; /* Index used */
+ int iCur; /* Cursor number used for this index */
+ int score; /* How well this indexed scored */
+ int brk; /* Jump here to break out of the loop */
+ int cont; /* Jump here to continue with the next loop cycle */
+ int op, p1, p2; /* Opcode used to terminate the loop */
+ int iLeftJoin; /* Memory cell used to implement LEFT OUTER JOIN */
+ int top; /* First instruction of interior of the loop */
+ int inOp, inP1, inP2;/* Opcode used to implement an IN operator */
+ int bRev; /* Do the scan in the reverse direction */
+};
+
+/*
+** The WHERE clause processing routine has two halves. The
+** first part does the start of the WHERE loop and the second
+** half does the tail of the WHERE loop. An instance of
+** this structure is returned by the first half and passed
+** into the second half to give some continuity.
+*/
+struct WhereInfo {
+ Parse *pParse;
+ SrcList *pTabList; /* List of tables in the join */
+ int iContinue; /* Jump here to continue with next record */
+ int iBreak; /* Jump here to break out of the loop */
+ int nLevel; /* Number of nested loop */
+ int savedNTab; /* Value of pParse->nTab before WhereBegin() */
+ int peakNTab; /* Value of pParse->nTab after WhereBegin() */
+ WhereLevel a[1]; /* Information about each nest loop in the WHERE */
+};
+
+/*
+** An instance of the following structure contains all information
+** needed to generate code for a single SELECT statement.
+**
+** The zSelect field is used when the Select structure must be persistent.
+** Normally, the expression tree points to tokens in the original input
+** string that encodes the select. But if the Select structure must live
+** longer than its input string (for example when it is used to describe
+** a VIEW) we have to make a copy of the input string so that the nodes
+** of the expression tree will have something to point to. zSelect is used
+** to hold that copy.
+**
+** nLimit is set to -1 if there is no LIMIT clause. nOffset is set to 0.
+** If there is a LIMIT clause, the parser sets nLimit to the value of the
+** limit and nOffset to the value of the offset (or 0 if there is not
+** offset). But later on, nLimit and nOffset become the memory locations
+** in the VDBE that record the limit and offset counters.
+*/
+struct Select {
+ ExprList *pEList; /* The fields of the result */
+ u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */
+ u8 isDistinct; /* True if the DISTINCT keyword is present */
+ SrcList *pSrc; /* The FROM clause */
+ Expr *pWhere; /* The WHERE clause */
+ ExprList *pGroupBy; /* The GROUP BY clause */
+ Expr *pHaving; /* The HAVING clause */
+ ExprList *pOrderBy; /* The ORDER BY clause */
+ Select *pPrior; /* Prior select in a compound select statement */
+ int nLimit, nOffset; /* LIMIT and OFFSET values. -1 means not used */
+ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */
+ char *zSelect; /* Complete text of the SELECT command */
+};
+
+/*
+** The results of a select can be distributed in several ways.
+*/
+#define SRT_Callback 1 /* Invoke a callback with each row of result */
+#define SRT_Mem 2 /* Store result in a memory cell */
+#define SRT_Set 3 /* Store result as unique keys in a table */
+#define SRT_Union 5 /* Store result as keys in a table */
+#define SRT_Except 6 /* Remove result from a UNION table */
+#define SRT_Table 7 /* Store result as data with a unique key */
+#define SRT_TempTable 8 /* Store result in a trasient table */
+#define SRT_Discard 9 /* Do not save the results anywhere */
+#define SRT_Sorter 10 /* Store results in the sorter */
+#define SRT_Subroutine 11 /* Call a subroutine to handle results */
+
+/*
+** When a SELECT uses aggregate functions (like "count(*)" or "avg(f1)")
+** we have to do some additional analysis of expressions. An instance
+** of the following structure holds information about a single subexpression
+** somewhere in the SELECT statement. An array of these structures holds
+** all the information we need to generate code for aggregate
+** expressions.
+**
+** Note that when analyzing a SELECT containing aggregates, both
+** non-aggregate field variables and aggregate functions are stored
+** in the AggExpr array of the Parser structure.
+**
+** The pExpr field points to an expression that is part of either the
+** field list, the GROUP BY clause, the HAVING clause or the ORDER BY
+** clause. The expression will be freed when those clauses are cleaned
+** up. Do not try to delete the expression attached to AggExpr.pExpr.
+**
+** If AggExpr.pExpr==0, that means the expression is "count(*)".
+*/
+struct AggExpr {
+ int isAgg; /* if TRUE contains an aggregate function */
+ Expr *pExpr; /* The expression */
+ FuncDef *pFunc; /* Information about the aggregate function */
+};
+
+/*
+** An SQL parser context. A copy of this structure is passed through
+** the parser and down into all the parser action routine in order to
+** carry around information that is global to the entire parse.
+*/
+struct Parse {
+ sqlite *db; /* The main database structure */
+ int rc; /* Return code from execution */
+ char *zErrMsg; /* An error message */
+ Token sErrToken; /* The token at which the error occurred */
+ Token sFirstToken; /* The first token parsed */
+ Token sLastToken; /* The last token parsed */
+ const char *zTail; /* All SQL text past the last semicolon parsed */
+ Table *pNewTable; /* A table being constructed by CREATE TABLE */
+ Vdbe *pVdbe; /* An engine for executing database bytecode */
+ u8 colNamesSet; /* TRUE after OP_ColumnName has been issued to pVdbe */
+ u8 explain; /* True if the EXPLAIN flag is found on the query */
+ u8 nameClash; /* A permanent table name clashes with temp table name */
+ u8 useAgg; /* If true, extract field values from the aggregator
+ ** while generating expressions. Normally false */
+ int nErr; /* Number of errors seen */
+ int nTab; /* Number of previously allocated VDBE cursors */
+ int nMem; /* Number of memory cells used so far */
+ int nSet; /* Number of sets used so far */
+ int nAgg; /* Number of aggregate expressions */
+ int nVar; /* Number of '?' variables seen in the SQL so far */
+ AggExpr *aAgg; /* An array of aggregate expressions */
+ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */
+ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */
+ TriggerStack *trigStack; /* Trigger actions being coded */
+};
+
+/*
+** An instance of the following structure can be declared on a stack and used
+** to save the Parse.zAuthContext value so that it can be restored later.
+*/
+struct AuthContext {
+ const char *zAuthContext; /* Put saved Parse.zAuthContext here */
+ Parse *pParse; /* The Parse structure */
+};
+
+/*
+** Bitfield flags for P2 value in OP_PutIntKey and OP_Delete
+*/
+#define OPFLAG_NCHANGE 1 /* Set to update db->nChange */
+#define OPFLAG_LASTROWID 2 /* Set to update db->lastRowid */
+#define OPFLAG_CSCHANGE 4 /* Set to update db->csChange */
+
+/*
+ * Each trigger present in the database schema is stored as an instance of
+ * struct Trigger.
+ *
+ * Pointers to instances of struct Trigger are stored in two ways.
+ * 1. In the "trigHash" hash table (part of the sqlite* that represents the
+ * database). This allows Trigger structures to be retrieved by name.
+ * 2. All triggers associated with a single table form a linked list, using the
+ * pNext member of struct Trigger. A pointer to the first element of the
+ * linked list is stored as the "pTrigger" member of the associated
+ * struct Table.
+ *
+ * The "step_list" member points to the first element of a linked list
+ * containing the SQL statements specified as the trigger program.
+ */
+struct Trigger {
+ char *name; /* The name of the trigger */
+ char *table; /* The table or view to which the trigger applies */
+ u8 iDb; /* Database containing this trigger */
+ u8 iTabDb; /* Database containing Trigger.table */
+ u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */
+ u8 tr_tm; /* One of TK_BEFORE, TK_AFTER */
+ Expr *pWhen; /* The WHEN clause of the expresion (may be NULL) */
+ IdList *pColumns; /* If this is an UPDATE OF <column-list> trigger,
+ the <column-list> is stored here */
+ int foreach; /* One of TK_ROW or TK_STATEMENT */
+ Token nameToken; /* Token containing zName. Use during parsing only */
+
+ TriggerStep *step_list; /* Link list of trigger program steps */
+ Trigger *pNext; /* Next trigger associated with the table */
+};
+
+/*
+ * An instance of struct TriggerStep is used to store a single SQL statement
+ * that is a part of a trigger-program.
+ *
+ * Instances of struct TriggerStep are stored in a singly linked list (linked
+ * using the "pNext" member) referenced by the "step_list" member of the
+ * associated struct Trigger instance. The first element of the linked list is
+ * the first step of the trigger-program.
+ *
+ * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or
+ * "SELECT" statement. The meanings of the other members is determined by the
+ * value of "op" as follows:
+ *
+ * (op == TK_INSERT)
+ * orconf -> stores the ON CONFLICT algorithm
+ * pSelect -> If this is an INSERT INTO ... SELECT ... statement, then
+ * this stores a pointer to the SELECT statement. Otherwise NULL.
+ * target -> A token holding the name of the table to insert into.
+ * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then
+ * this stores values to be inserted. Otherwise NULL.
+ * pIdList -> If this is an INSERT INTO ... (<column-names>) VALUES ...
+ * statement, then this stores the column-names to be
+ * inserted into.
+ *
+ * (op == TK_DELETE)
+ * target -> A token holding the name of the table to delete from.
+ * pWhere -> The WHERE clause of the DELETE statement if one is specified.
+ * Otherwise NULL.
+ *
+ * (op == TK_UPDATE)
+ * target -> A token holding the name of the table to update rows of.
+ * pWhere -> The WHERE clause of the UPDATE statement if one is specified.
+ * Otherwise NULL.
+ * pExprList -> A list of the columns to update and the expressions to update
+ * them to. See sqliteUpdate() documentation of "pChanges"
+ * argument.
+ *
+ */
+struct TriggerStep {
+ int op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
+ int orconf; /* OE_Rollback etc. */
+ Trigger *pTrig; /* The trigger that this step is a part of */
+
+ Select *pSelect; /* Valid for SELECT and sometimes
+ INSERT steps (when pExprList == 0) */
+ Token target; /* Valid for DELETE, UPDATE, INSERT steps */
+ Expr *pWhere; /* Valid for DELETE, UPDATE steps */
+ ExprList *pExprList; /* Valid for UPDATE statements and sometimes
+ INSERT steps (when pSelect == 0) */
+ IdList *pIdList; /* Valid for INSERT statements only */
+
+ TriggerStep * pNext; /* Next in the link-list */
+};
+
+/*
+ * An instance of struct TriggerStack stores information required during code
+ * generation of a single trigger program. While the trigger program is being
+ * coded, its associated TriggerStack instance is pointed to by the
+ * "pTriggerStack" member of the Parse structure.
+ *
+ * The pTab member points to the table that triggers are being coded on. The
+ * newIdx member contains the index of the vdbe cursor that points at the temp
+ * table that stores the new.* references. If new.* references are not valid
+ * for the trigger being coded (for example an ON DELETE trigger), then newIdx
+ * is set to -1. The oldIdx member is analogous to newIdx, for old.* references.
+ *
+ * The ON CONFLICT policy to be used for the trigger program steps is stored
+ * as the orconf member. If this is OE_Default, then the ON CONFLICT clause
+ * specified for individual triggers steps is used.
+ *
+ * struct TriggerStack has a "pNext" member, to allow linked lists to be
+ * constructed. When coding nested triggers (triggers fired by other triggers)
+ * each nested trigger stores its parent trigger's TriggerStack as the "pNext"
+ * pointer. Once the nested trigger has been coded, the pNext value is restored
+ * to the pTriggerStack member of the Parse stucture and coding of the parent
+ * trigger continues.
+ *
+ * Before a nested trigger is coded, the linked list pointed to by the
+ * pTriggerStack is scanned to ensure that the trigger is not about to be coded
+ * recursively. If this condition is detected, the nested trigger is not coded.
+ */
+struct TriggerStack {
+ Table *pTab; /* Table that triggers are currently being coded on */
+ int newIdx; /* Index of vdbe cursor to "new" temp table */
+ int oldIdx; /* Index of vdbe cursor to "old" temp table */
+ int orconf; /* Current orconf policy */
+ int ignoreJump; /* where to jump to for a RAISE(IGNORE) */
+ Trigger *pTrigger; /* The trigger currently being coded */
+ TriggerStack *pNext; /* Next trigger down on the trigger stack */
+};
+
+/*
+** The following structure contains information used by the sqliteFix...
+** routines as they walk the parse tree to make database references
+** explicit.
+*/
+typedef struct DbFixer DbFixer;
+struct DbFixer {
+ Parse *pParse; /* The parsing context. Error messages written here */
+ const char *zDb; /* Make sure all objects are contained in this database */
+ const char *zType; /* Type of the container - used for error messages */
+ const Token *pName; /* Name of the container - used for error messages */
+};
+
+/*
+ * This global flag is set for performance testing of triggers. When it is set
+ * SQLite will perform the overhead of building new and old trigger references
+ * even when no triggers exist
+ */
+extern int always_code_trigger_setup;
+
+/*
+** Internal function prototypes
+*/
+int sqliteStrICmp(const char *, const char *);
+int sqliteStrNICmp(const char *, const char *, int);
+int sqliteHashNoCase(const char *, int);
+int sqliteIsNumber(const char*);
+int sqliteCompare(const char *, const char *);
+int sqliteSortCompare(const char *, const char *);
+void sqliteRealToSortable(double r, char *);
+#ifdef MEMORY_DEBUG
+ void *sqliteMalloc_(int,int,char*,int);
+ void sqliteFree_(void*,char*,int);
+ void *sqliteRealloc_(void*,int,char*,int);
+ char *sqliteStrDup_(const char*,char*,int);
+ char *sqliteStrNDup_(const char*, int,char*,int);
+ void sqliteCheckMemory(void*,int);
+#else
+ void *sqliteMalloc(int);
+ void *sqliteMallocRaw(int);
+ void sqliteFree(void*);
+ void *sqliteRealloc(void*,int);
+ char *sqliteStrDup(const char*);
+ char *sqliteStrNDup(const char*, int);
+# define sqliteCheckMemory(a,b)
+#endif
+char *sqliteMPrintf(const char*, ...);
+char *sqliteVMPrintf(const char*, va_list);
+void sqliteSetString(char **, ...);
+void sqliteSetNString(char **, ...);
+void sqliteErrorMsg(Parse*, const char*, ...);
+void sqliteDequote(char*);
+int sqliteKeywordCode(const char*, int);
+int sqliteRunParser(Parse*, const char*, char **);
+void sqliteExec(Parse*);
+Expr *sqliteExpr(int, Expr*, Expr*, Token*);
+void sqliteExprSpan(Expr*,Token*,Token*);
+Expr *sqliteExprFunction(ExprList*, Token*);
+void sqliteExprDelete(Expr*);
+ExprList *sqliteExprListAppend(ExprList*,Expr*,Token*);
+void sqliteExprListDelete(ExprList*);
+int sqliteInit(sqlite*, char**);
+void sqlitePragma(Parse*,Token*,Token*,int);
+void sqliteResetInternalSchema(sqlite*, int);
+void sqliteBeginParse(Parse*,int);
+void sqliteRollbackInternalChanges(sqlite*);
+void sqliteCommitInternalChanges(sqlite*);
+Table *sqliteResultSetOfSelect(Parse*,char*,Select*);
+void sqliteOpenMasterTable(Vdbe *v, int);
+void sqliteStartTable(Parse*,Token*,Token*,int,int);
+void sqliteAddColumn(Parse*,Token*);
+void sqliteAddNotNull(Parse*, int);
+void sqliteAddPrimaryKey(Parse*, IdList*, int);
+void sqliteAddColumnType(Parse*,Token*,Token*);
+void sqliteAddDefaultValue(Parse*,Token*,int);
+int sqliteCollateType(const char*, int);
+void sqliteAddCollateType(Parse*, int);
+void sqliteEndTable(Parse*,Token*,Select*);
+void sqliteCreateView(Parse*,Token*,Token*,Select*,int);
+int sqliteViewGetColumnNames(Parse*,Table*);
+void sqliteDropTable(Parse*, Token*, int);
+void sqliteDeleteTable(sqlite*, Table*);
+void sqliteInsert(Parse*, SrcList*, ExprList*, Select*, IdList*, int);
+IdList *sqliteIdListAppend(IdList*, Token*);
+int sqliteIdListIndex(IdList*,const char*);
+SrcList *sqliteSrcListAppend(SrcList*, Token*, Token*);
+void sqliteSrcListAddAlias(SrcList*, Token*);
+void sqliteSrcListAssignCursors(Parse*, SrcList*);
+void sqliteIdListDelete(IdList*);
+void sqliteSrcListDelete(SrcList*);
+void sqliteCreateIndex(Parse*,Token*,SrcList*,IdList*,int,Token*,Token*);
+void sqliteDropIndex(Parse*, SrcList*);
+void sqliteAddKeyType(Vdbe*, ExprList*);
+void sqliteAddIdxKeyType(Vdbe*, Index*);
+int sqliteSelect(Parse*, Select*, int, int, Select*, int, int*);
+Select *sqliteSelectNew(ExprList*,SrcList*,Expr*,ExprList*,Expr*,ExprList*,
+ int,int,int);
+void sqliteSelectDelete(Select*);
+void sqliteSelectUnbind(Select*);
+Table *sqliteSrcListLookup(Parse*, SrcList*);
+int sqliteIsReadOnly(Parse*, Table*, int);
+void sqliteDeleteFrom(Parse*, SrcList*, Expr*);
+void sqliteUpdate(Parse*, SrcList*, ExprList*, Expr*, int);
+WhereInfo *sqliteWhereBegin(Parse*, SrcList*, Expr*, int, ExprList**);
+void sqliteWhereEnd(WhereInfo*);
+void sqliteExprCode(Parse*, Expr*);
+int sqliteExprCodeExprList(Parse*, ExprList*, int);
+void sqliteExprIfTrue(Parse*, Expr*, int, int);
+void sqliteExprIfFalse(Parse*, Expr*, int, int);
+Table *sqliteFindTable(sqlite*,const char*, const char*);
+Table *sqliteLocateTable(Parse*,const char*, const char*);
+Index *sqliteFindIndex(sqlite*,const char*, const char*);
+void sqliteUnlinkAndDeleteIndex(sqlite*,Index*);
+void sqliteCopy(Parse*, SrcList*, Token*, Token*, int);
+void sqliteVacuum(Parse*, Token*);
+int sqliteRunVacuum(char**, sqlite*);
+int sqliteGlobCompare(const unsigned char*,const unsigned char*);
+int sqliteLikeCompare(const unsigned char*,const unsigned char*);
+char *sqliteTableNameFromToken(Token*);
+int sqliteExprCheck(Parse*, Expr*, int, int*);
+int sqliteExprType(Expr*);
+int sqliteExprCompare(Expr*, Expr*);
+int sqliteFuncId(Token*);
+int sqliteExprResolveIds(Parse*, SrcList*, ExprList*, Expr*);
+int sqliteExprAnalyzeAggregates(Parse*, Expr*);
+Vdbe *sqliteGetVdbe(Parse*);
+void sqliteRandomness(int, void*);
+void sqliteRollbackAll(sqlite*);
+void sqliteCodeVerifySchema(Parse*, int);
+void sqliteBeginTransaction(Parse*, int);
+void sqliteCommitTransaction(Parse*);
+void sqliteRollbackTransaction(Parse*);
+int sqliteExprIsConstant(Expr*);
+int sqliteExprIsInteger(Expr*, int*);
+int sqliteIsRowid(const char*);
+void sqliteGenerateRowDelete(sqlite*, Vdbe*, Table*, int, int);
+void sqliteGenerateRowIndexDelete(sqlite*, Vdbe*, Table*, int, char*);
+void sqliteGenerateConstraintChecks(Parse*,Table*,int,char*,int,int,int,int);
+void sqliteCompleteInsertion(Parse*, Table*, int, char*, int, int, int);
+int sqliteOpenTableAndIndices(Parse*, Table*, int);
+void sqliteBeginWriteOperation(Parse*, int, int);
+void sqliteEndWriteOperation(Parse*);
+Expr *sqliteExprDup(Expr*);
+void sqliteTokenCopy(Token*, Token*);
+ExprList *sqliteExprListDup(ExprList*);
+SrcList *sqliteSrcListDup(SrcList*);
+IdList *sqliteIdListDup(IdList*);
+Select *sqliteSelectDup(Select*);
+FuncDef *sqliteFindFunction(sqlite*,const char*,int,int,int);
+void sqliteRegisterBuiltinFunctions(sqlite*);
+void sqliteRegisterDateTimeFunctions(sqlite*);
+int sqliteSafetyOn(sqlite*);
+int sqliteSafetyOff(sqlite*);
+int sqliteSafetyCheck(sqlite*);
+void sqliteChangeCookie(sqlite*, Vdbe*);
+void sqliteBeginTrigger(Parse*, Token*,int,int,IdList*,SrcList*,int,Expr*,int);
+void sqliteFinishTrigger(Parse*, TriggerStep*, Token*);
+void sqliteDropTrigger(Parse*, SrcList*);
+void sqliteDropTriggerPtr(Parse*, Trigger*, int);
+int sqliteTriggersExist(Parse* , Trigger* , int , int , int, ExprList*);
+int sqliteCodeRowTrigger(Parse*, int, ExprList*, int, Table *, int, int,
+ int, int);
+void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*);
+void sqliteDeleteTriggerStep(TriggerStep*);
+TriggerStep *sqliteTriggerSelectStep(Select*);
+TriggerStep *sqliteTriggerInsertStep(Token*, IdList*, ExprList*, Select*, int);
+TriggerStep *sqliteTriggerUpdateStep(Token*, ExprList*, Expr*, int);
+TriggerStep *sqliteTriggerDeleteStep(Token*, Expr*);
+void sqliteDeleteTrigger(Trigger*);
+int sqliteJoinType(Parse*, Token*, Token*, Token*);
+void sqliteCreateForeignKey(Parse*, IdList*, Token*, IdList*, int);
+void sqliteDeferForeignKey(Parse*, int);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ void sqliteAuthRead(Parse*,Expr*,SrcList*);
+ int sqliteAuthCheck(Parse*,int, const char*, const char*, const char*);
+ void sqliteAuthContextPush(Parse*, AuthContext*, const char*);
+ void sqliteAuthContextPop(AuthContext*);
+#else
+# define sqliteAuthRead(a,b,c)
+# define sqliteAuthCheck(a,b,c,d,e) SQLITE_OK
+# define sqliteAuthContextPush(a,b,c)
+# define sqliteAuthContextPop(a) ((void)(a))
+#endif
+void sqliteAttach(Parse*, Token*, Token*, Token*);
+void sqliteDetach(Parse*, Token*);
+int sqliteBtreeFactory(const sqlite *db, const char *zFilename,
+ int mode, int nPg, Btree **ppBtree);
+int sqliteFixInit(DbFixer*, Parse*, int, const char*, const Token*);
+int sqliteFixSrcList(DbFixer*, SrcList*);
+int sqliteFixSelect(DbFixer*, Select*);
+int sqliteFixExpr(DbFixer*, Expr*);
+int sqliteFixExprList(DbFixer*, ExprList*);
+int sqliteFixTriggerStep(DbFixer*, TriggerStep*);
+double sqliteAtoF(const char *z, const char **);
+char *sqlite_snprintf(int,char*,const char*,...);
+int sqliteFitsIn32Bits(const char *);
diff --git a/src/libs/sqlite2/table.c b/src/libs/sqlite2/table.c
new file mode 100644
index 00000000..48c852d4
--- /dev/null
+++ b/src/libs/sqlite2/table.c
@@ -0,0 +1,203 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the sqlite_get_table() and sqlite_free_table()
+** interface routines. These are just wrappers around the main
+** interface routine of sqlite_exec().
+**
+** These routines are in a separate files so that they will not be linked
+** if they are not used.
+*/
+#include <stdlib.h>
+#include <string.h>
+#include "sqliteInt.h"
+
+/*
+** This structure is used to pass data from sqlite_get_table() through
+** to the callback function is uses to build the result.
+*/
+typedef struct TabResult {
+ char **azResult;
+ char *zErrMsg;
+ int nResult;
+ int nAlloc;
+ int nRow;
+ int nColumn;
+ long nData;
+ int rc;
+} TabResult;
+
+/*
+** This routine is called once for each row in the result table. Its job
+** is to fill in the TabResult structure appropriately, allocating new
+** memory as necessary.
+*/
+static int sqlite_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
+ TabResult *p = (TabResult*)pArg;
+ int need;
+ int i;
+ char *z;
+
+ /* Make sure there is enough space in p->azResult to hold everything
+ ** we need to remember from this invocation of the callback.
+ */
+ if( p->nRow==0 && argv!=0 ){
+ need = nCol*2;
+ }else{
+ need = nCol;
+ }
+ if( p->nData + need >= p->nAlloc ){
+ char **azNew;
+ p->nAlloc = p->nAlloc*2 + need + 1;
+ azNew = realloc( p->azResult, sizeof(char*)*p->nAlloc );
+ if( azNew==0 ){
+ p->rc = SQLITE_NOMEM;
+ return 1;
+ }
+ p->azResult = azNew;
+ }
+
+ /* If this is the first row, then generate an extra row containing
+ ** the names of all columns.
+ */
+ if( p->nRow==0 ){
+ p->nColumn = nCol;
+ for(i=0; i<nCol; i++){
+ if( colv[i]==0 ){
+ z = 0;
+ }else{
+ z = malloc( strlen(colv[i])+1 );
+ if( z==0 ){
+ p->rc = SQLITE_NOMEM;
+ return 1;
+ }
+ strcpy(z, colv[i]);
+ }
+ p->azResult[p->nData++] = z;
+ }
+ }else if( p->nColumn!=nCol ){
+ sqliteSetString(&p->zErrMsg,
+ "sqlite_get_table() called with two or more incompatible queries",
+ (char*)0);
+ p->rc = SQLITE_ERROR;
+ return 1;
+ }
+
+ /* Copy over the row data
+ */
+ if( argv!=0 ){
+ for(i=0; i<nCol; i++){
+ if( argv[i]==0 ){
+ z = 0;
+ }else{
+ z = malloc( strlen(argv[i])+1 );
+ if( z==0 ){
+ p->rc = SQLITE_NOMEM;
+ return 1;
+ }
+ strcpy(z, argv[i]);
+ }
+ p->azResult[p->nData++] = z;
+ }
+ p->nRow++;
+ }
+ return 0;
+}
+
+/*
+** Query the database. But instead of invoking a callback for each row,
+** malloc() for space to hold the result and return the entire results
+** at the conclusion of the call.
+**
+** The result that is written to ***pazResult is held in memory obtained
+** from malloc(). But the caller cannot free this memory directly.
+** Instead, the entire table should be passed to sqlite_free_table() when
+** the calling procedure is finished using it.
+*/
+int sqlite_get_table(
+ sqlite *db, /* The database on which the SQL executes */
+ const char *zSql, /* The SQL to be executed */
+ char ***pazResult, /* Write the result table here */
+ int *pnRow, /* Write the number of rows in the result here */
+ int *pnColumn, /* Write the number of columns of result here */
+ char **pzErrMsg /* Write error messages here */
+){
+ int rc;
+ TabResult res;
+ if( pazResult==0 ){ return SQLITE_ERROR; }
+ *pazResult = 0;
+ if( pnColumn ) *pnColumn = 0;
+ if( pnRow ) *pnRow = 0;
+ res.zErrMsg = 0;
+ res.nResult = 0;
+ res.nRow = 0;
+ res.nColumn = 0;
+ res.nData = 1;
+ res.nAlloc = 20;
+ res.rc = SQLITE_OK;
+ res.azResult = malloc( sizeof(char*)*res.nAlloc );
+ if( res.azResult==0 ){
+ return SQLITE_NOMEM;
+ }
+ res.azResult[0] = 0;
+ rc = sqlite_exec(db, zSql, sqlite_get_table_cb, &res, pzErrMsg);
+ if( res.azResult ){
+ res.azResult[0] = (char*)res.nData;
+ }
+ if( rc==SQLITE_ABORT ){
+ sqlite_free_table(&res.azResult[1]);
+ if( res.zErrMsg ){
+ if( pzErrMsg ){
+ free(*pzErrMsg);
+ *pzErrMsg = res.zErrMsg;
+ sqliteStrRealloc(pzErrMsg);
+ }else{
+ sqliteFree(res.zErrMsg);
+ }
+ }
+ return res.rc;
+ }
+ sqliteFree(res.zErrMsg);
+ if( rc!=SQLITE_OK ){
+ sqlite_free_table(&res.azResult[1]);
+ return rc;
+ }
+ if( res.nAlloc>res.nData ){
+ char **azNew;
+ azNew = realloc( res.azResult, sizeof(char*)*(res.nData+1) );
+ if( azNew==0 ){
+ sqlite_free_table(&res.azResult[1]);
+ return SQLITE_NOMEM;
+ }
+ res.nAlloc = res.nData+1;
+ res.azResult = azNew;
+ }
+ *pazResult = &res.azResult[1];
+ if( pnColumn ) *pnColumn = res.nColumn;
+ if( pnRow ) *pnRow = res.nRow;
+ return rc;
+}
+
+/*
+** This routine frees the space the sqlite_get_table() malloced.
+*/
+void sqlite_free_table(
+ char **azResult /* Result returned from from sqlite_get_table() */
+){
+ if( azResult ){
+ int i, n;
+ azResult--;
+ if( azResult==0 ) return;
+ n = (int)(long)azResult[0];
+ for(i=1; i<n; i++){ if( azResult[i] ) free(azResult[i]); }
+ free(azResult);
+ }
+}
diff --git a/src/libs/sqlite2/tokenize.c b/src/libs/sqlite2/tokenize.c
new file mode 100644
index 00000000..1044e8a5
--- /dev/null
+++ b/src/libs/sqlite2/tokenize.c
@@ -0,0 +1,679 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** An tokenizer for SQL
+**
+** This file contains C code that splits an SQL input string up into
+** individual tokens and sends those tokens one-by-one over to the
+** parser for analysis.
+**
+** $Id: tokenize.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+#include <stdlib.h>
+
+/*
+** All the keywords of the SQL language are stored as in a hash
+** table composed of instances of the following structure.
+*/
+typedef struct Keyword Keyword;
+struct Keyword {
+ char *zName; /* The keyword name */
+ u8 tokenType; /* Token value for this keyword */
+ u8 len; /* Length of this keyword */
+ u8 iNext; /* Index in aKeywordTable[] of next with same hash */
+};
+
+/*
+** These are the keywords
+*/
+static Keyword aKeywordTable[] = {
+ { "ABORT", TK_ABORT, },
+ { "AFTER", TK_AFTER, },
+ { "ALL", TK_ALL, },
+ { "AND", TK_AND, },
+ { "AS", TK_AS, },
+ { "ASC", TK_ASC, },
+ { "ATTACH", TK_ATTACH, },
+ { "BEFORE", TK_BEFORE, },
+ { "BEGIN", TK_BEGIN, },
+ { "BETWEEN", TK_BETWEEN, },
+ { "BY", TK_BY, },
+ { "CASCADE", TK_CASCADE, },
+ { "CASE", TK_CASE, },
+ { "CHECK", TK_CHECK, },
+ { "CLUSTER", TK_CLUSTER, },
+ { "COLLATE", TK_COLLATE, },
+ { "COMMIT", TK_COMMIT, },
+ { "CONFLICT", TK_CONFLICT, },
+ { "CONSTRAINT", TK_CONSTRAINT, },
+ { "COPY", TK_COPY, },
+ { "CREATE", TK_CREATE, },
+ { "CROSS", TK_JOIN_KW, },
+ { "DATABASE", TK_DATABASE, },
+ { "DEFAULT", TK_DEFAULT, },
+ { "DEFERRED", TK_DEFERRED, },
+ { "DEFERRABLE", TK_DEFERRABLE, },
+ { "DELETE", TK_DELETE, },
+ { "DELIMITERS", TK_DELIMITERS, },
+ { "DESC", TK_DESC, },
+ { "DETACH", TK_DETACH, },
+ { "DISTINCT", TK_DISTINCT, },
+ { "DROP", TK_DROP, },
+ { "END", TK_END, },
+ { "EACH", TK_EACH, },
+ { "ELSE", TK_ELSE, },
+ { "EXCEPT", TK_EXCEPT, },
+ { "EXPLAIN", TK_EXPLAIN, },
+ { "FAIL", TK_FAIL, },
+ { "FOR", TK_FOR, },
+ { "FOREIGN", TK_FOREIGN, },
+ { "FROM", TK_FROM, },
+ { "FULL", TK_JOIN_KW, },
+ { "GLOB", TK_GLOB, },
+ { "GROUP", TK_GROUP, },
+ { "HAVING", TK_HAVING, },
+ { "IGNORE", TK_IGNORE, },
+ { "IMMEDIATE", TK_IMMEDIATE, },
+ { "IN", TK_IN, },
+ { "INDEX", TK_INDEX, },
+ { "INITIALLY", TK_INITIALLY, },
+ { "INNER", TK_JOIN_KW, },
+ { "INSERT", TK_INSERT, },
+ { "INSTEAD", TK_INSTEAD, },
+ { "INTERSECT", TK_INTERSECT, },
+ { "INTO", TK_INTO, },
+ { "IS", TK_IS, },
+ { "ISNULL", TK_ISNULL, },
+ { "JOIN", TK_JOIN, },
+ { "KEY", TK_KEY, },
+ { "LEFT", TK_JOIN_KW, },
+ { "LIKE", TK_LIKE, },
+ { "LIMIT", TK_LIMIT, },
+ { "MATCH", TK_MATCH, },
+ { "NATURAL", TK_JOIN_KW, },
+ { "NOT", TK_NOT, },
+ { "NOTNULL", TK_NOTNULL, },
+ { "NULL", TK_NULL, },
+ { "OF", TK_OF, },
+ { "OFFSET", TK_OFFSET, },
+ { "ON", TK_ON, },
+ { "OR", TK_OR, },
+ { "ORDER", TK_ORDER, },
+ { "OUTER", TK_JOIN_KW, },
+ { "PRAGMA", TK_PRAGMA, },
+ { "PRIMARY", TK_PRIMARY, },
+ { "RAISE", TK_RAISE, },
+ { "REFERENCES", TK_REFERENCES, },
+ { "REPLACE", TK_REPLACE, },
+ { "RESTRICT", TK_RESTRICT, },
+ { "RIGHT", TK_JOIN_KW, },
+ { "ROLLBACK", TK_ROLLBACK, },
+ { "ROW", TK_ROW, },
+ { "SELECT", TK_SELECT, },
+ { "SET", TK_SET, },
+ { "STATEMENT", TK_STATEMENT, },
+ { "TABLE", TK_TABLE, },
+ { "TEMP", TK_TEMP, },
+ { "TEMPORARY", TK_TEMP, },
+ { "THEN", TK_THEN, },
+ { "TRANSACTION", TK_TRANSACTION, },
+ { "TRIGGER", TK_TRIGGER, },
+ { "UNION", TK_UNION, },
+ { "UNIQUE", TK_UNIQUE, },
+ { "UPDATE", TK_UPDATE, },
+ { "USING", TK_USING, },
+ { "VACUUM", TK_VACUUM, },
+ { "VALUES", TK_VALUES, },
+ { "VIEW", TK_VIEW, },
+ { "WHEN", TK_WHEN, },
+ { "WHERE", TK_WHERE, },
+};
+
+/*
+** This is the hash table
+*/
+#define KEY_HASH_SIZE 101
+static u8 aiHashTable[KEY_HASH_SIZE];
+
+
+/*
+** This function looks up an identifier to determine if it is a
+** keyword. If it is a keyword, the token code of that keyword is
+** returned. If the input is not a keyword, TK_ID is returned.
+*/
+int sqliteKeywordCode(const char *z, int n){
+ int h, i;
+ Keyword *p;
+ static char needInit = 1;
+ if( needInit ){
+ /* Initialize the keyword hash table */
+ sqliteOsEnterMutex();
+ if( needInit ){
+ int nk;
+ nk = sizeof(aKeywordTable)/sizeof(aKeywordTable[0]);
+ for(i=0; i<nk; i++){
+ aKeywordTable[i].len = strlen(aKeywordTable[i].zName);
+ h = sqliteHashNoCase(aKeywordTable[i].zName, aKeywordTable[i].len);
+ h %= KEY_HASH_SIZE;
+ aKeywordTable[i].iNext = aiHashTable[h];
+ aiHashTable[h] = i+1;
+ }
+ needInit = 0;
+ }
+ sqliteOsLeaveMutex();
+ }
+ h = sqliteHashNoCase(z, n) % KEY_HASH_SIZE;
+ for(i=aiHashTable[h]; i; i=p->iNext){
+ p = &aKeywordTable[i-1];
+ if( p->len==n && sqliteStrNICmp(p->zName, z, n)==0 ){
+ return p->tokenType;
+ }
+ }
+ return TK_ID;
+}
+
+
+/*
+** If X is a character that can be used in an identifier and
+** X&0x80==0 then isIdChar[X] will be 1. If X&0x80==0x80 then
+** X is always an identifier character. (Hence all UTF-8
+** characters can be part of an identifier). isIdChar[X] will
+** be 0 for every character in the lower 128 ASCII characters
+** that cannot be used as part of an identifier.
+**
+** In this implementation, an identifier can be a string of
+** alphabetic characters, digits, and "_" plus any character
+** with the high-order bit set. The latter rule means that
+** any sequence of UTF-8 characters or characters taken from
+** an extended ISO8859 character set can form an identifier.
+*/
+static const char isIdChar[] = {
+/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */
+};
+
+
+/*
+** Return the length of the token that begins at z[0].
+** Store the token type in *tokenType before returning.
+*/
+static int sqliteGetToken(const unsigned char *z, int *tokenType){
+ int i;
+ switch( *z ){
+ case ' ': case '\t': case '\n': case '\f': case '\r': {
+ for(i=1; isspace(z[i]); i++){}
+ *tokenType = TK_SPACE;
+ return i;
+ }
+ case '-': {
+ if( z[1]=='-' ){
+ for(i=2; z[i] && z[i]!='\n'; i++){}
+ *tokenType = TK_COMMENT;
+ return i;
+ }
+ *tokenType = TK_MINUS;
+ return 1;
+ }
+ case '(': {
+ *tokenType = TK_LP;
+ return 1;
+ }
+ case ')': {
+ *tokenType = TK_RP;
+ return 1;
+ }
+ case ';': {
+ *tokenType = TK_SEMI;
+ return 1;
+ }
+ case '+': {
+ *tokenType = TK_PLUS;
+ return 1;
+ }
+ case '*': {
+ *tokenType = TK_STAR;
+ return 1;
+ }
+ case '/': {
+ if( z[1]!='*' || z[2]==0 ){
+ *tokenType = TK_SLASH;
+ return 1;
+ }
+ for(i=3; z[i] && (z[i]!='/' || z[i-1]!='*'); i++){}
+ if( z[i] ) i++;
+ *tokenType = TK_COMMENT;
+ return i;
+ }
+ case '%': {
+ *tokenType = TK_REM;
+ return 1;
+ }
+ case '=': {
+ *tokenType = TK_EQ;
+ return 1 + (z[1]=='=');
+ }
+ case '<': {
+ if( z[1]=='=' ){
+ *tokenType = TK_LE;
+ return 2;
+ }else if( z[1]=='>' ){
+ *tokenType = TK_NE;
+ return 2;
+ }else if( z[1]=='<' ){
+ *tokenType = TK_LSHIFT;
+ return 2;
+ }else{
+ *tokenType = TK_LT;
+ return 1;
+ }
+ }
+ case '>': {
+ if( z[1]=='=' ){
+ *tokenType = TK_GE;
+ return 2;
+ }else if( z[1]=='>' ){
+ *tokenType = TK_RSHIFT;
+ return 2;
+ }else{
+ *tokenType = TK_GT;
+ return 1;
+ }
+ }
+ case '!': {
+ if( z[1]!='=' ){
+ *tokenType = TK_ILLEGAL;
+ return 2;
+ }else{
+ *tokenType = TK_NE;
+ return 2;
+ }
+ }
+ case '|': {
+ if( z[1]!='|' ){
+ *tokenType = TK_BITOR;
+ return 1;
+ }else{
+ *tokenType = TK_CONCAT;
+ return 2;
+ }
+ }
+ case ',': {
+ *tokenType = TK_COMMA;
+ return 1;
+ }
+ case '&': {
+ *tokenType = TK_BITAND;
+ return 1;
+ }
+ case '~': {
+ *tokenType = TK_BITNOT;
+ return 1;
+ }
+ case '\'': case '"': {
+ int delim = z[0];
+ for(i=1; z[i]; i++){
+ if( z[i]==delim ){
+ if( z[i+1]==delim ){
+ i++;
+ }else{
+ break;
+ }
+ }
+ }
+ if( z[i] ) i++;
+ *tokenType = TK_STRING;
+ return i;
+ }
+ case '.': {
+ *tokenType = TK_DOT;
+ return 1;
+ }
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9': {
+ *tokenType = TK_INTEGER;
+ for(i=1; isdigit(z[i]); i++){}
+ if( z[i]=='.' && isdigit(z[i+1]) ){
+ i += 2;
+ while( isdigit(z[i]) ){ i++; }
+ *tokenType = TK_FLOAT;
+ }
+ if( (z[i]=='e' || z[i]=='E') &&
+ ( isdigit(z[i+1])
+ || ((z[i+1]=='+' || z[i+1]=='-') && isdigit(z[i+2]))
+ )
+ ){
+ i += 2;
+ while( isdigit(z[i]) ){ i++; }
+ *tokenType = TK_FLOAT;
+ }
+ return i;
+ }
+ case '[': {
+ for(i=1; z[i] && z[i-1]!=']'; i++){}
+ *tokenType = TK_ID;
+ return i;
+ }
+ case '?': {
+ *tokenType = TK_VARIABLE;
+ return 1;
+ }
+ default: {
+ if( (*z&0x80)==0 && !isIdChar[*z] ){
+ break;
+ }
+ for(i=1; (z[i]&0x80)!=0 || isIdChar[z[i]]; i++){}
+ *tokenType = sqliteKeywordCode((char*)z, i);
+ return i;
+ }
+ }
+ *tokenType = TK_ILLEGAL;
+ return 1;
+}
+
+/*
+** Run the parser on the given SQL string. The parser structure is
+** passed in. An SQLITE_ status code is returned. If an error occurs
+** and pzErrMsg!=NULL then an error message might be written into
+** memory obtained from malloc() and *pzErrMsg made to point to that
+** error message. Or maybe not.
+*/
+int sqliteRunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
+ int nErr = 0;
+ int i;
+ void *pEngine;
+ int tokenType;
+ int lastTokenParsed = -1;
+ sqlite *db = pParse->db;
+ extern void *sqliteParserAlloc(void*(*)(int));
+ extern void sqliteParserFree(void*, void(*)(void*));
+ extern int sqliteParser(void*, int, Token, Parse*);
+
+ db->flags &= ~SQLITE_Interrupt;
+ pParse->rc = SQLITE_OK;
+ i = 0;
+ pEngine = sqliteParserAlloc((void*(*)(int))malloc);
+ if( pEngine==0 ){
+ sqliteSetString(pzErrMsg, "out of memory", (char*)0);
+ return 1;
+ }
+ pParse->sLastToken.dyn = 0;
+ pParse->zTail = zSql;
+ while( sqlite_malloc_failed==0 && zSql[i]!=0 ){
+ assert( i>=0 );
+ pParse->sLastToken.z = &zSql[i];
+ assert( pParse->sLastToken.dyn==0 );
+ pParse->sLastToken.n = sqliteGetToken((unsigned char*)&zSql[i], &tokenType);
+ i += pParse->sLastToken.n;
+ switch( tokenType ){
+ case TK_SPACE:
+ case TK_COMMENT: {
+ if( (db->flags & SQLITE_Interrupt)!=0 ){
+ pParse->rc = SQLITE_INTERRUPT;
+ sqliteSetString(pzErrMsg, "interrupt", (char*)0);
+ goto abort_parse;
+ }
+ break;
+ }
+ case TK_ILLEGAL: {
+ sqliteSetNString(pzErrMsg, "unrecognized token: \"", -1,
+ pParse->sLastToken.z, pParse->sLastToken.n, "\"", 1, 0);
+ nErr++;
+ goto abort_parse;
+ }
+ case TK_SEMI: {
+ pParse->zTail = &zSql[i];
+ /* Fall thru into the default case */
+ }
+ default: {
+ sqliteParser(pEngine, tokenType, pParse->sLastToken, pParse);
+ lastTokenParsed = tokenType;
+ if( pParse->rc!=SQLITE_OK ){
+ goto abort_parse;
+ }
+ break;
+ }
+ }
+ }
+abort_parse:
+ if( zSql[i]==0 && nErr==0 && pParse->rc==SQLITE_OK ){
+ if( lastTokenParsed!=TK_SEMI ){
+ sqliteParser(pEngine, TK_SEMI, pParse->sLastToken, pParse);
+ pParse->zTail = &zSql[i];
+ }
+ sqliteParser(pEngine, 0, pParse->sLastToken, pParse);
+ }
+ sqliteParserFree(pEngine, free);
+ if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){
+ sqliteSetString(&pParse->zErrMsg, sqlite_error_string(pParse->rc),
+ (char*)0);
+ }
+ if( pParse->zErrMsg ){
+ if( pzErrMsg && *pzErrMsg==0 ){
+ *pzErrMsg = pParse->zErrMsg;
+ }else{
+ sqliteFree(pParse->zErrMsg);
+ }
+ pParse->zErrMsg = 0;
+ if( !nErr ) nErr++;
+ }
+ if( pParse->pVdbe && pParse->nErr>0 ){
+ sqliteVdbeDelete(pParse->pVdbe);
+ pParse->pVdbe = 0;
+ }
+ if( pParse->pNewTable ){
+ sqliteDeleteTable(pParse->db, pParse->pNewTable);
+ pParse->pNewTable = 0;
+ }
+ if( pParse->pNewTrigger ){
+ sqliteDeleteTrigger(pParse->pNewTrigger);
+ pParse->pNewTrigger = 0;
+ }
+ if( nErr>0 && (pParse->rc==SQLITE_OK || pParse->rc==SQLITE_DONE) ){
+ pParse->rc = SQLITE_ERROR;
+ }
+ return nErr;
+}
+
+/*
+** Token types used by the sqlite_complete() routine. See the header
+** comments on that procedure for additional information.
+*/
+#define tkEXPLAIN 0
+#define tkCREATE 1
+#define tkTEMP 2
+#define tkTRIGGER 3
+#define tkEND 4
+#define tkSEMI 5
+#define tkWS 6
+#define tkOTHER 7
+
+/*
+** Return TRUE if the given SQL string ends in a semicolon.
+**
+** Special handling is require for CREATE TRIGGER statements.
+** Whenever the CREATE TRIGGER keywords are seen, the statement
+** must end with ";END;".
+**
+** This implementation uses a state machine with 7 states:
+**
+** (0) START At the beginning or end of an SQL statement. This routine
+** returns 1 if it ends in the START state and 0 if it ends
+** in any other state.
+**
+** (1) EXPLAIN The keyword EXPLAIN has been seen at the beginning of
+** a statement.
+**
+** (2) CREATE The keyword CREATE has been seen at the beginning of a
+** statement, possibly preceeded by EXPLAIN and/or followed by
+** TEMP or TEMPORARY
+**
+** (3) NORMAL We are in the middle of statement which ends with a single
+** semicolon.
+**
+** (4) TRIGGER We are in the middle of a trigger definition that must be
+** ended by a semicolon, the keyword END, and another semicolon.
+**
+** (5) SEMI We've seen the first semicolon in the ";END;" that occurs at
+** the end of a trigger definition.
+**
+** (6) END We've seen the ";END" of the ";END;" that occurs at the end
+** of a trigger difinition.
+**
+** Transitions between states above are determined by tokens extracted
+** from the input. The following tokens are significant:
+**
+** (0) tkEXPLAIN The "explain" keyword.
+** (1) tkCREATE The "create" keyword.
+** (2) tkTEMP The "temp" or "temporary" keyword.
+** (3) tkTRIGGER The "trigger" keyword.
+** (4) tkEND The "end" keyword.
+** (5) tkSEMI A semicolon.
+** (6) tkWS Whitespace
+** (7) tkOTHER Any other SQL token.
+**
+** Whitespace never causes a state transition and is always ignored.
+*/
+int sqlite_complete(const char *zSql){
+ u8 state = 0; /* Current state, using numbers defined in header comment */
+ u8 token; /* Value of the next token */
+
+ /* The following matrix defines the transition from one state to another
+ ** according to what token is seen. trans[state][token] returns the
+ ** next state.
+ */
+ static const u8 trans[7][8] = {
+ /* Token: */
+ /* State: ** EXPLAIN CREATE TEMP TRIGGER END SEMI WS OTHER */
+ /* 0 START: */ { 1, 2, 3, 3, 3, 0, 0, 3, },
+ /* 1 EXPLAIN: */ { 3, 2, 3, 3, 3, 0, 1, 3, },
+ /* 2 CREATE: */ { 3, 3, 2, 4, 3, 0, 2, 3, },
+ /* 3 NORMAL: */ { 3, 3, 3, 3, 3, 0, 3, 3, },
+ /* 4 TRIGGER: */ { 4, 4, 4, 4, 4, 5, 4, 4, },
+ /* 5 SEMI: */ { 4, 4, 4, 4, 6, 5, 5, 4, },
+ /* 6 END: */ { 4, 4, 4, 4, 4, 0, 6, 4, },
+ };
+
+ while( *zSql ){
+ switch( *zSql ){
+ case ';': { /* A semicolon */
+ token = tkSEMI;
+ break;
+ }
+ case ' ':
+ case '\r':
+ case '\t':
+ case '\n':
+ case '\f': { /* White space is ignored */
+ token = tkWS;
+ break;
+ }
+ case '/': { /* C-style comments */
+ if( zSql[1]!='*' ){
+ token = tkOTHER;
+ break;
+ }
+ zSql += 2;
+ while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; }
+ if( zSql[0]==0 ) return 0;
+ zSql++;
+ token = tkWS;
+ break;
+ }
+ case '-': { /* SQL-style comments from "--" to end of line */
+ if( zSql[1]!='-' ){
+ token = tkOTHER;
+ break;
+ }
+ while( *zSql && *zSql!='\n' ){ zSql++; }
+ if( *zSql==0 ) return state==0;
+ token = tkWS;
+ break;
+ }
+ case '[': { /* Microsoft-style identifiers in [...] */
+ zSql++;
+ while( *zSql && *zSql!=']' ){ zSql++; }
+ if( *zSql==0 ) return 0;
+ token = tkOTHER;
+ break;
+ }
+ case '"': /* single- and double-quoted strings */
+ case '\'': {
+ int c = *zSql;
+ zSql++;
+ while( *zSql && *zSql!=c ){ zSql++; }
+ if( *zSql==0 ) return 0;
+ token = tkOTHER;
+ break;
+ }
+ default: {
+ if( isIdChar[(u8)*zSql] ){
+ /* Keywords and unquoted identifiers */
+ int nId;
+ for(nId=1; isIdChar[(u8)zSql[nId]]; nId++){}
+ switch( *zSql ){
+ case 'c': case 'C': {
+ if( nId==6 && sqliteStrNICmp(zSql, "create", 6)==0 ){
+ token = tkCREATE;
+ }else{
+ token = tkOTHER;
+ }
+ break;
+ }
+ case 't': case 'T': {
+ if( nId==7 && sqliteStrNICmp(zSql, "trigger", 7)==0 ){
+ token = tkTRIGGER;
+ }else if( nId==4 && sqliteStrNICmp(zSql, "temp", 4)==0 ){
+ token = tkTEMP;
+ }else if( nId==9 && sqliteStrNICmp(zSql, "temporary", 9)==0 ){
+ token = tkTEMP;
+ }else{
+ token = tkOTHER;
+ }
+ break;
+ }
+ case 'e': case 'E': {
+ if( nId==3 && sqliteStrNICmp(zSql, "end", 3)==0 ){
+ token = tkEND;
+ }else if( nId==7 && sqliteStrNICmp(zSql, "explain", 7)==0 ){
+ token = tkEXPLAIN;
+ }else{
+ token = tkOTHER;
+ }
+ break;
+ }
+ default: {
+ token = tkOTHER;
+ break;
+ }
+ }
+ zSql += nId-1;
+ }else{
+ /* Operators and special symbols */
+ token = tkOTHER;
+ }
+ break;
+ }
+ }
+ state = trans[state][token];
+ zSql++;
+ }
+ return state==0;
+}
diff --git a/src/libs/sqlite2/trigger.c b/src/libs/sqlite2/trigger.c
new file mode 100644
index 00000000..8442bb5d
--- /dev/null
+++ b/src/libs/sqlite2/trigger.c
@@ -0,0 +1,764 @@
+/*
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+*
+*/
+#include "sqliteInt.h"
+
+/*
+** Delete a linked list of TriggerStep structures.
+*/
+void sqliteDeleteTriggerStep(TriggerStep *pTriggerStep){
+ while( pTriggerStep ){
+ TriggerStep * pTmp = pTriggerStep;
+ pTriggerStep = pTriggerStep->pNext;
+
+ if( pTmp->target.dyn ) sqliteFree((char*)pTmp->target.z);
+ sqliteExprDelete(pTmp->pWhere);
+ sqliteExprListDelete(pTmp->pExprList);
+ sqliteSelectDelete(pTmp->pSelect);
+ sqliteIdListDelete(pTmp->pIdList);
+
+ sqliteFree(pTmp);
+ }
+}
+
+/*
+** This is called by the parser when it sees a CREATE TRIGGER statement
+** up to the point of the BEGIN before the trigger actions. A Trigger
+** structure is generated based on the information available and stored
+** in pParse->pNewTrigger. After the trigger actions have been parsed, the
+** sqliteFinishTrigger() function is called to complete the trigger
+** construction process.
+*/
+void sqliteBeginTrigger(
+ Parse *pParse, /* The parse context of the CREATE TRIGGER statement */
+ Token *pName, /* The name of the trigger */
+ int tr_tm, /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */
+ int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
+ IdList *pColumns, /* column list if this is an UPDATE OF trigger */
+ SrcList *pTableName,/* The name of the table/view the trigger applies to */
+ int foreach, /* One of TK_ROW or TK_STATEMENT */
+ Expr *pWhen, /* WHEN clause */
+ int isTemp /* True if the TEMPORARY keyword is present */
+){
+ Trigger *nt;
+ Table *tab;
+ char *zName = 0; /* Name of the trigger */
+ sqlite *db = pParse->db;
+ int iDb; /* When database to store the trigger in */
+ DbFixer sFix;
+
+ /* Check that:
+ ** 1. the trigger name does not already exist.
+ ** 2. the table (or view) does exist in the same database as the trigger.
+ ** 3. that we are not trying to create a trigger on the sqlite_master table
+ ** 4. That we are not trying to create an INSTEAD OF trigger on a table.
+ ** 5. That we are not trying to create a BEFORE or AFTER trigger on a view.
+ */
+ if( sqlite_malloc_failed ) goto trigger_cleanup;
+ assert( pTableName->nSrc==1 );
+ if( db->init.busy
+ && sqliteFixInit(&sFix, pParse, db->init.iDb, "trigger", pName)
+ && sqliteFixSrcList(&sFix, pTableName)
+ ){
+ goto trigger_cleanup;
+ }
+ tab = sqliteSrcListLookup(pParse, pTableName);
+ if( !tab ){
+ goto trigger_cleanup;
+ }
+ iDb = isTemp ? 1 : tab->iDb;
+ if( iDb>=2 && !db->init.busy ){
+ sqliteErrorMsg(pParse, "triggers may not be added to auxiliary "
+ "database %s", db->aDb[tab->iDb].zName);
+ goto trigger_cleanup;
+ }
+
+ zName = sqliteStrNDup(pName->z, pName->n);
+ sqliteDequote(zName);
+ if( sqliteHashFind(&(db->aDb[iDb].trigHash), zName,pName->n+1) ){
+ sqliteErrorMsg(pParse, "trigger %T already exists", pName);
+ goto trigger_cleanup;
+ }
+ if( sqliteStrNICmp(tab->zName, "sqlite_", 7)==0 ){
+ sqliteErrorMsg(pParse, "cannot create trigger on system table");
+ pParse->nErr++;
+ goto trigger_cleanup;
+ }
+ if( tab->pSelect && tr_tm != TK_INSTEAD ){
+ sqliteErrorMsg(pParse, "cannot create %s trigger on view: %S",
+ (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0);
+ goto trigger_cleanup;
+ }
+ if( !tab->pSelect && tr_tm == TK_INSTEAD ){
+ sqliteErrorMsg(pParse, "cannot create INSTEAD OF"
+ " trigger on table: %S", pTableName, 0);
+ goto trigger_cleanup;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code = SQLITE_CREATE_TRIGGER;
+ const char *zDb = db->aDb[tab->iDb].zName;
+ const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb;
+ if( tab->iDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
+ if( sqliteAuthCheck(pParse, code, zName, tab->zName, zDbTrig) ){
+ goto trigger_cleanup;
+ }
+ if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(tab->iDb), 0, zDb)){
+ goto trigger_cleanup;
+ }
+ }
+#endif
+
+ /* INSTEAD OF triggers can only appear on views and BEGIN triggers
+ ** cannot appear on views. So we might as well translate every
+ ** INSTEAD OF trigger into a BEFORE trigger. It simplifies code
+ ** elsewhere.
+ */
+ if (tr_tm == TK_INSTEAD){
+ tr_tm = TK_BEFORE;
+ }
+
+ /* Build the Trigger object */
+ nt = (Trigger*)sqliteMalloc(sizeof(Trigger));
+ if( nt==0 ) goto trigger_cleanup;
+ nt->name = zName;
+ zName = 0;
+ nt->table = sqliteStrDup(pTableName->a[0].zName);
+ if( sqlite_malloc_failed ) goto trigger_cleanup;
+ nt->iDb = iDb;
+ nt->iTabDb = tab->iDb;
+ nt->op = op;
+ nt->tr_tm = tr_tm;
+ nt->pWhen = sqliteExprDup(pWhen);
+ nt->pColumns = sqliteIdListDup(pColumns);
+ nt->foreach = foreach;
+ sqliteTokenCopy(&nt->nameToken,pName);
+ assert( pParse->pNewTrigger==0 );
+ pParse->pNewTrigger = nt;
+
+trigger_cleanup:
+ sqliteFree(zName);
+ sqliteSrcListDelete(pTableName);
+ sqliteIdListDelete(pColumns);
+ sqliteExprDelete(pWhen);
+}
+
+/*
+** This routine is called after all of the trigger actions have been parsed
+** in order to complete the process of building the trigger.
+*/
+void sqliteFinishTrigger(
+ Parse *pParse, /* Parser context */
+ TriggerStep *pStepList, /* The triggered program */
+ Token *pAll /* Token that describes the complete CREATE TRIGGER */
+){
+ Trigger *nt = 0; /* The trigger whose construction is finishing up */
+ sqlite *db = pParse->db; /* The database */
+ DbFixer sFix;
+
+ if( pParse->nErr || pParse->pNewTrigger==0 ) goto triggerfinish_cleanup;
+ nt = pParse->pNewTrigger;
+ pParse->pNewTrigger = 0;
+ nt->step_list = pStepList;
+ while( pStepList ){
+ pStepList->pTrig = nt;
+ pStepList = pStepList->pNext;
+ }
+ if( sqliteFixInit(&sFix, pParse, nt->iDb, "trigger", &nt->nameToken)
+ && sqliteFixTriggerStep(&sFix, nt->step_list) ){
+ goto triggerfinish_cleanup;
+ }
+
+ /* if we are not initializing, and this trigger is not on a TEMP table,
+ ** build the sqlite_master entry
+ */
+ if( !db->init.busy ){
+ static VdbeOpList insertTrig[] = {
+ { OP_NewRecno, 0, 0, 0 },
+ { OP_String, 0, 0, "trigger" },
+ { OP_String, 0, 0, 0 }, /* 2: trigger name */
+ { OP_String, 0, 0, 0 }, /* 3: table name */
+ { OP_Integer, 0, 0, 0 },
+ { OP_String, 0, 0, 0 }, /* 5: SQL */
+ { OP_MakeRecord, 5, 0, 0 },
+ { OP_PutIntKey, 0, 0, 0 },
+ };
+ int addr;
+ Vdbe *v;
+
+ /* Make an entry in the sqlite_master table */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) goto triggerfinish_cleanup;
+ sqliteBeginWriteOperation(pParse, 0, 0);
+ sqliteOpenMasterTable(v, nt->iDb);
+ addr = sqliteVdbeAddOpList(v, ArraySize(insertTrig), insertTrig);
+ sqliteVdbeChangeP3(v, addr+2, nt->name, 0);
+ sqliteVdbeChangeP3(v, addr+3, nt->table, 0);
+ sqliteVdbeChangeP3(v, addr+5, pAll->z, pAll->n);
+ if( nt->iDb==0 ){
+ sqliteChangeCookie(db, v);
+ }
+ sqliteVdbeAddOp(v, OP_Close, 0, 0);
+ sqliteEndWriteOperation(pParse);
+ }
+
+ if( !pParse->explain ){
+ Table *pTab;
+ sqliteHashInsert(&db->aDb[nt->iDb].trigHash,
+ nt->name, strlen(nt->name)+1, nt);
+ pTab = sqliteLocateTable(pParse, nt->table, db->aDb[nt->iTabDb].zName);
+ assert( pTab!=0 );
+ nt->pNext = pTab->pTrigger;
+ pTab->pTrigger = nt;
+ nt = 0;
+ }
+
+triggerfinish_cleanup:
+ sqliteDeleteTrigger(nt);
+ sqliteDeleteTrigger(pParse->pNewTrigger);
+ pParse->pNewTrigger = 0;
+ sqliteDeleteTriggerStep(pStepList);
+}
+
+/*
+** Make a copy of all components of the given trigger step. This has
+** the effect of copying all Expr.token.z values into memory obtained
+** from sqliteMalloc(). As initially created, the Expr.token.z values
+** all point to the input string that was fed to the parser. But that
+** string is ephemeral - it will go away as soon as the sqlite_exec()
+** call that started the parser exits. This routine makes a persistent
+** copy of all the Expr.token.z strings so that the TriggerStep structure
+** will be valid even after the sqlite_exec() call returns.
+*/
+static void sqlitePersistTriggerStep(TriggerStep *p){
+ if( p->target.z ){
+ p->target.z = sqliteStrNDup(p->target.z, p->target.n);
+ p->target.dyn = 1;
+ }
+ if( p->pSelect ){
+ Select *pNew = sqliteSelectDup(p->pSelect);
+ sqliteSelectDelete(p->pSelect);
+ p->pSelect = pNew;
+ }
+ if( p->pWhere ){
+ Expr *pNew = sqliteExprDup(p->pWhere);
+ sqliteExprDelete(p->pWhere);
+ p->pWhere = pNew;
+ }
+ if( p->pExprList ){
+ ExprList *pNew = sqliteExprListDup(p->pExprList);
+ sqliteExprListDelete(p->pExprList);
+ p->pExprList = pNew;
+ }
+ if( p->pIdList ){
+ IdList *pNew = sqliteIdListDup(p->pIdList);
+ sqliteIdListDelete(p->pIdList);
+ p->pIdList = pNew;
+ }
+}
+
+/*
+** Turn a SELECT statement (that the pSelect parameter points to) into
+** a trigger step. Return a pointer to a TriggerStep structure.
+**
+** The parser calls this routine when it finds a SELECT statement in
+** body of a TRIGGER.
+*/
+TriggerStep *sqliteTriggerSelectStep(Select *pSelect){
+ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
+ if( pTriggerStep==0 ) return 0;
+
+ pTriggerStep->op = TK_SELECT;
+ pTriggerStep->pSelect = pSelect;
+ pTriggerStep->orconf = OE_Default;
+ sqlitePersistTriggerStep(pTriggerStep);
+
+ return pTriggerStep;
+}
+
+/*
+** Build a trigger step out of an INSERT statement. Return a pointer
+** to the new trigger step.
+**
+** The parser calls this routine when it sees an INSERT inside the
+** body of a trigger.
+*/
+TriggerStep *sqliteTriggerInsertStep(
+ Token *pTableName, /* Name of the table into which we insert */
+ IdList *pColumn, /* List of columns in pTableName to insert into */
+ ExprList *pEList, /* The VALUE clause: a list of values to be inserted */
+ Select *pSelect, /* A SELECT statement that supplies values */
+ int orconf /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
+){
+ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
+ if( pTriggerStep==0 ) return 0;
+
+ assert(pEList == 0 || pSelect == 0);
+ assert(pEList != 0 || pSelect != 0);
+
+ pTriggerStep->op = TK_INSERT;
+ pTriggerStep->pSelect = pSelect;
+ pTriggerStep->target = *pTableName;
+ pTriggerStep->pIdList = pColumn;
+ pTriggerStep->pExprList = pEList;
+ pTriggerStep->orconf = orconf;
+ sqlitePersistTriggerStep(pTriggerStep);
+
+ return pTriggerStep;
+}
+
+/*
+** Construct a trigger step that implements an UPDATE statement and return
+** a pointer to that trigger step. The parser calls this routine when it
+** sees an UPDATE statement inside the body of a CREATE TRIGGER.
+*/
+TriggerStep *sqliteTriggerUpdateStep(
+ Token *pTableName, /* Name of the table to be updated */
+ ExprList *pEList, /* The SET clause: list of column and new values */
+ Expr *pWhere, /* The WHERE clause */
+ int orconf /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
+){
+ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
+ if( pTriggerStep==0 ) return 0;
+
+ pTriggerStep->op = TK_UPDATE;
+ pTriggerStep->target = *pTableName;
+ pTriggerStep->pExprList = pEList;
+ pTriggerStep->pWhere = pWhere;
+ pTriggerStep->orconf = orconf;
+ sqlitePersistTriggerStep(pTriggerStep);
+
+ return pTriggerStep;
+}
+
+/*
+** Construct a trigger step that implements a DELETE statement and return
+** a pointer to that trigger step. The parser calls this routine when it
+** sees a DELETE statement inside the body of a CREATE TRIGGER.
+*/
+TriggerStep *sqliteTriggerDeleteStep(Token *pTableName, Expr *pWhere){
+ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
+ if( pTriggerStep==0 ) return 0;
+
+ pTriggerStep->op = TK_DELETE;
+ pTriggerStep->target = *pTableName;
+ pTriggerStep->pWhere = pWhere;
+ pTriggerStep->orconf = OE_Default;
+ sqlitePersistTriggerStep(pTriggerStep);
+
+ return pTriggerStep;
+}
+
+/*
+** Recursively delete a Trigger structure
+*/
+void sqliteDeleteTrigger(Trigger *pTrigger){
+ if( pTrigger==0 ) return;
+ sqliteDeleteTriggerStep(pTrigger->step_list);
+ sqliteFree(pTrigger->name);
+ sqliteFree(pTrigger->table);
+ sqliteExprDelete(pTrigger->pWhen);
+ sqliteIdListDelete(pTrigger->pColumns);
+ if( pTrigger->nameToken.dyn ) sqliteFree((char*)pTrigger->nameToken.z);
+ sqliteFree(pTrigger);
+}
+
+/*
+ * This function is called to drop a trigger from the database schema.
+ *
+ * This may be called directly from the parser and therefore identifies
+ * the trigger by name. The sqliteDropTriggerPtr() routine does the
+ * same job as this routine except it take a spointer to the trigger
+ * instead of the trigger name.
+ *
+ * Note that this function does not delete the trigger entirely. Instead it
+ * removes it from the internal schema and places it in the trigDrop hash
+ * table. This is so that the trigger can be restored into the database schema
+ * if the transaction is rolled back.
+ */
+void sqliteDropTrigger(Parse *pParse, SrcList *pName){
+ Trigger *pTrigger;
+ int i;
+ const char *zDb;
+ const char *zName;
+ int nName;
+ sqlite *db = pParse->db;
+
+ if( sqlite_malloc_failed ) goto drop_trigger_cleanup;
+ assert( pName->nSrc==1 );
+ zDb = pName->a[0].zDatabase;
+ zName = pName->a[0].zName;
+ nName = strlen(zName);
+ for(i=0; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ if( zDb && sqliteStrICmp(db->aDb[j].zName, zDb) ) continue;
+ pTrigger = sqliteHashFind(&(db->aDb[j].trigHash), zName, nName+1);
+ if( pTrigger ) break;
+ }
+ if( !pTrigger ){
+ sqliteErrorMsg(pParse, "no such trigger: %S", pName, 0);
+ goto drop_trigger_cleanup;
+ }
+ sqliteDropTriggerPtr(pParse, pTrigger, 0);
+
+drop_trigger_cleanup:
+ sqliteSrcListDelete(pName);
+}
+
+/*
+** Drop a trigger given a pointer to that trigger. If nested is false,
+** then also generate code to remove the trigger from the SQLITE_MASTER
+** table.
+*/
+void sqliteDropTriggerPtr(Parse *pParse, Trigger *pTrigger, int nested){
+ Table *pTable;
+ Vdbe *v;
+ sqlite *db = pParse->db;
+
+ assert( pTrigger->iDb<db->nDb );
+ if( pTrigger->iDb>=2 ){
+ sqliteErrorMsg(pParse, "triggers may not be removed from "
+ "auxiliary database %s", db->aDb[pTrigger->iDb].zName);
+ return;
+ }
+ pTable = sqliteFindTable(db, pTrigger->table,db->aDb[pTrigger->iTabDb].zName);
+ assert(pTable);
+ assert( pTable->iDb==pTrigger->iDb || pTrigger->iDb==1 );
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code = SQLITE_DROP_TRIGGER;
+ const char *zDb = db->aDb[pTrigger->iDb].zName;
+ const char *zTab = SCHEMA_TABLE(pTrigger->iDb);
+ if( pTrigger->iDb ) code = SQLITE_DROP_TEMP_TRIGGER;
+ if( sqliteAuthCheck(pParse, code, pTrigger->name, pTable->zName, zDb) ||
+ sqliteAuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
+ return;
+ }
+ }
+#endif
+
+ /* Generate code to destroy the database record of the trigger.
+ */
+ if( pTable!=0 && !nested && (v = sqliteGetVdbe(pParse))!=0 ){
+ int base;
+ static VdbeOpList dropTrigger[] = {
+ { OP_Rewind, 0, ADDR(9), 0},
+ { OP_String, 0, 0, 0}, /* 1 */
+ { OP_Column, 0, 1, 0},
+ { OP_Ne, 0, ADDR(8), 0},
+ { OP_String, 0, 0, "trigger"},
+ { OP_Column, 0, 0, 0},
+ { OP_Ne, 0, ADDR(8), 0},
+ { OP_Delete, 0, 0, 0},
+ { OP_Next, 0, ADDR(1), 0}, /* 8 */
+ };
+
+ sqliteBeginWriteOperation(pParse, 0, 0);
+ sqliteOpenMasterTable(v, pTrigger->iDb);
+ base = sqliteVdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger);
+ sqliteVdbeChangeP3(v, base+1, pTrigger->name, 0);
+ if( pTrigger->iDb==0 ){
+ sqliteChangeCookie(db, v);
+ }
+ sqliteVdbeAddOp(v, OP_Close, 0, 0);
+ sqliteEndWriteOperation(pParse);
+ }
+
+ /*
+ * If this is not an "explain", then delete the trigger structure.
+ */
+ if( !pParse->explain ){
+ const char *zName = pTrigger->name;
+ int nName = strlen(zName);
+ if( pTable->pTrigger == pTrigger ){
+ pTable->pTrigger = pTrigger->pNext;
+ }else{
+ Trigger *cc = pTable->pTrigger;
+ while( cc ){
+ if( cc->pNext == pTrigger ){
+ cc->pNext = cc->pNext->pNext;
+ break;
+ }
+ cc = cc->pNext;
+ }
+ assert(cc);
+ }
+ sqliteHashInsert(&(db->aDb[pTrigger->iDb].trigHash), zName, nName+1, 0);
+ sqliteDeleteTrigger(pTrigger);
+ }
+}
+
+/*
+** pEList is the SET clause of an UPDATE statement. Each entry
+** in pEList is of the format <id>=<expr>. If any of the entries
+** in pEList have an <id> which matches an identifier in pIdList,
+** then return TRUE. If pIdList==NULL, then it is considered a
+** wildcard that matches anything. Likewise if pEList==NULL then
+** it matches anything so always return true. Return false only
+** if there is no match.
+*/
+static int checkColumnOverLap(IdList *pIdList, ExprList *pEList){
+ int e;
+ if( !pIdList || !pEList ) return 1;
+ for(e=0; e<pEList->nExpr; e++){
+ if( sqliteIdListIndex(pIdList, pEList->a[e].zName)>=0 ) return 1;
+ }
+ return 0;
+}
+
+/* A global variable that is TRUE if we should always set up temp tables for
+ * for triggers, even if there are no triggers to code. This is used to test
+ * how much overhead the triggers algorithm is causing.
+ *
+ * This flag can be set or cleared using the "trigger_overhead_test" pragma.
+ * The pragma is not documented since it is not really part of the interface
+ * to SQLite, just the test procedure.
+*/
+int always_code_trigger_setup = 0;
+
+/*
+ * Returns true if a trigger matching op, tr_tm and foreach that is NOT already
+ * on the Parse objects trigger-stack (to prevent recursive trigger firing) is
+ * found in the list specified as pTrigger.
+ */
+int sqliteTriggersExist(
+ Parse *pParse, /* Used to check for recursive triggers */
+ Trigger *pTrigger, /* A list of triggers associated with a table */
+ int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
+ int tr_tm, /* one of TK_BEFORE, TK_AFTER */
+ int foreach, /* one of TK_ROW or TK_STATEMENT */
+ ExprList *pChanges /* Columns that change in an UPDATE statement */
+){
+ Trigger * pTriggerCursor;
+
+ if( always_code_trigger_setup ){
+ return 1;
+ }
+
+ pTriggerCursor = pTrigger;
+ while( pTriggerCursor ){
+ if( pTriggerCursor->op == op &&
+ pTriggerCursor->tr_tm == tr_tm &&
+ pTriggerCursor->foreach == foreach &&
+ checkColumnOverLap(pTriggerCursor->pColumns, pChanges) ){
+ TriggerStack * ss;
+ ss = pParse->trigStack;
+ while( ss && ss->pTrigger != pTrigger ){
+ ss = ss->pNext;
+ }
+ if( !ss )return 1;
+ }
+ pTriggerCursor = pTriggerCursor->pNext;
+ }
+
+ return 0;
+}
+
+/*
+** Convert the pStep->target token into a SrcList and return a pointer
+** to that SrcList.
+**
+** This routine adds a specific database name, if needed, to the target when
+** forming the SrcList. This prevents a trigger in one database from
+** referring to a target in another database. An exception is when the
+** trigger is in TEMP in which case it can refer to any other database it
+** wants.
+*/
+static SrcList *targetSrcList(
+ Parse *pParse, /* The parsing context */
+ TriggerStep *pStep /* The trigger containing the target token */
+){
+ Token sDb; /* Dummy database name token */
+ int iDb; /* Index of the database to use */
+ SrcList *pSrc; /* SrcList to be returned */
+
+ iDb = pStep->pTrig->iDb;
+ if( iDb==0 || iDb>=2 ){
+ assert( iDb<pParse->db->nDb );
+ sDb.z = pParse->db->aDb[iDb].zName;
+ sDb.n = strlen(sDb.z);
+ pSrc = sqliteSrcListAppend(0, &sDb, &pStep->target);
+ } else {
+ pSrc = sqliteSrcListAppend(0, &pStep->target, 0);
+ }
+ return pSrc;
+}
+
+/*
+** Generate VDBE code for zero or more statements inside the body of a
+** trigger.
+*/
+static int codeTriggerProgram(
+ Parse *pParse, /* The parser context */
+ TriggerStep *pStepList, /* List of statements inside the trigger body */
+ int orconfin /* Conflict algorithm. (OE_Abort, etc) */
+){
+ TriggerStep * pTriggerStep = pStepList;
+ int orconf;
+
+ while( pTriggerStep ){
+ int saveNTab = pParse->nTab;
+
+ orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
+ pParse->trigStack->orconf = orconf;
+ switch( pTriggerStep->op ){
+ case TK_SELECT: {
+ Select * ss = sqliteSelectDup(pTriggerStep->pSelect);
+ assert(ss);
+ assert(ss->pSrc);
+ sqliteSelect(pParse, ss, SRT_Discard, 0, 0, 0, 0);
+ sqliteSelectDelete(ss);
+ break;
+ }
+ case TK_UPDATE: {
+ SrcList *pSrc;
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqliteVdbeAddOp(pParse->pVdbe, OP_ListPush, 0, 0);
+ sqliteUpdate(pParse, pSrc,
+ sqliteExprListDup(pTriggerStep->pExprList),
+ sqliteExprDup(pTriggerStep->pWhere), orconf);
+ sqliteVdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0);
+ break;
+ }
+ case TK_INSERT: {
+ SrcList *pSrc;
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqliteInsert(pParse, pSrc,
+ sqliteExprListDup(pTriggerStep->pExprList),
+ sqliteSelectDup(pTriggerStep->pSelect),
+ sqliteIdListDup(pTriggerStep->pIdList), orconf);
+ break;
+ }
+ case TK_DELETE: {
+ SrcList *pSrc;
+ sqliteVdbeAddOp(pParse->pVdbe, OP_ListPush, 0, 0);
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqliteDeleteFrom(pParse, pSrc, sqliteExprDup(pTriggerStep->pWhere));
+ sqliteVdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0);
+ break;
+ }
+ default:
+ assert(0);
+ }
+ pParse->nTab = saveNTab;
+ pTriggerStep = pTriggerStep->pNext;
+ }
+
+ return 0;
+}
+
+/*
+** This is called to code FOR EACH ROW triggers.
+**
+** When the code that this function generates is executed, the following
+** must be true:
+**
+** 1. No cursors may be open in the main database. (But newIdx and oldIdx
+** can be indices of cursors in temporary tables. See below.)
+**
+** 2. If the triggers being coded are ON INSERT or ON UPDATE triggers, then
+** a temporary vdbe cursor (index newIdx) must be open and pointing at
+** a row containing values to be substituted for new.* expressions in the
+** trigger program(s).
+**
+** 3. If the triggers being coded are ON DELETE or ON UPDATE triggers, then
+** a temporary vdbe cursor (index oldIdx) must be open and pointing at
+** a row containing values to be substituted for old.* expressions in the
+** trigger program(s).
+**
+*/
+int sqliteCodeRowTrigger(
+ Parse *pParse, /* Parse context */
+ int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
+ ExprList *pChanges, /* Changes list for any UPDATE OF triggers */
+ int tr_tm, /* One of TK_BEFORE, TK_AFTER */
+ Table *pTab, /* The table to code triggers from */
+ int newIdx, /* The indice of the "new" row to access */
+ int oldIdx, /* The indice of the "old" row to access */
+ int orconf, /* ON CONFLICT policy */
+ int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */
+){
+ Trigger * pTrigger;
+ TriggerStack * pTriggerStack;
+
+ assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE);
+ assert(tr_tm == TK_BEFORE || tr_tm == TK_AFTER );
+
+ assert(newIdx != -1 || oldIdx != -1);
+
+ pTrigger = pTab->pTrigger;
+ while( pTrigger ){
+ int fire_this = 0;
+
+ /* determine whether we should code this trigger */
+ if( pTrigger->op == op && pTrigger->tr_tm == tr_tm &&
+ pTrigger->foreach == TK_ROW ){
+ fire_this = 1;
+ pTriggerStack = pParse->trigStack;
+ while( pTriggerStack ){
+ if( pTriggerStack->pTrigger == pTrigger ){
+ fire_this = 0;
+ }
+ pTriggerStack = pTriggerStack->pNext;
+ }
+ if( op == TK_UPDATE && pTrigger->pColumns &&
+ !checkColumnOverLap(pTrigger->pColumns, pChanges) ){
+ fire_this = 0;
+ }
+ }
+
+ if( fire_this && (pTriggerStack = sqliteMalloc(sizeof(TriggerStack)))!=0 ){
+ int endTrigger;
+ SrcList dummyTablist;
+ Expr * whenExpr;
+ AuthContext sContext;
+
+ dummyTablist.nSrc = 0;
+
+ /* Push an entry on to the trigger stack */
+ pTriggerStack->pTrigger = pTrigger;
+ pTriggerStack->newIdx = newIdx;
+ pTriggerStack->oldIdx = oldIdx;
+ pTriggerStack->pTab = pTab;
+ pTriggerStack->pNext = pParse->trigStack;
+ pTriggerStack->ignoreJump = ignoreJump;
+ pParse->trigStack = pTriggerStack;
+ sqliteAuthContextPush(pParse, &sContext, pTrigger->name);
+
+ /* code the WHEN clause */
+ endTrigger = sqliteVdbeMakeLabel(pParse->pVdbe);
+ whenExpr = sqliteExprDup(pTrigger->pWhen);
+ if( sqliteExprResolveIds(pParse, &dummyTablist, 0, whenExpr) ){
+ pParse->trigStack = pParse->trigStack->pNext;
+ sqliteFree(pTriggerStack);
+ sqliteExprDelete(whenExpr);
+ return 1;
+ }
+ sqliteExprIfFalse(pParse, whenExpr, endTrigger, 1);
+ sqliteExprDelete(whenExpr);
+
+ sqliteVdbeAddOp(pParse->pVdbe, OP_ContextPush, 0, 0);
+ codeTriggerProgram(pParse, pTrigger->step_list, orconf);
+ sqliteVdbeAddOp(pParse->pVdbe, OP_ContextPop, 0, 0);
+
+ /* Pop the entry off the trigger stack */
+ pParse->trigStack = pParse->trigStack->pNext;
+ sqliteAuthContextPop(&sContext);
+ sqliteFree(pTriggerStack);
+
+ sqliteVdbeResolveLabel(pParse->pVdbe, endTrigger);
+ }
+ pTrigger = pTrigger->pNext;
+ }
+
+ return 0;
+}
diff --git a/src/libs/sqlite2/update.c b/src/libs/sqlite2/update.c
new file mode 100644
index 00000000..b36d0c21
--- /dev/null
+++ b/src/libs/sqlite2/update.c
@@ -0,0 +1,459 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** to handle UPDATE statements.
+**
+** $Id: update.c 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#include "sqliteInt.h"
+
+/*
+** Process an UPDATE statement.
+**
+** UPDATE OR IGNORE table_wxyz SET a=b, c=d WHERE e<5 AND f NOT NULL;
+** \_______/ \________/ \______/ \________________/
+* onError pTabList pChanges pWhere
+*/
+void sqliteUpdate(
+ Parse *pParse, /* The parser context */
+ SrcList *pTabList, /* The table in which we should change things */
+ ExprList *pChanges, /* Things to be changed */
+ Expr *pWhere, /* The WHERE clause. May be null */
+ int onError /* How to handle constraint errors */
+){
+ int i, j; /* Loop counters */
+ Table *pTab; /* The table to be updated */
+ int loopStart; /* VDBE instruction address of the start of the loop */
+ int jumpInst; /* Addr of VDBE instruction to jump out of loop */
+ WhereInfo *pWInfo; /* Information about the WHERE clause */
+ Vdbe *v; /* The virtual database engine */
+ Index *pIdx; /* For looping over indices */
+ int nIdx; /* Number of indices that need updating */
+ int nIdxTotal; /* Total number of indices */
+ int iCur; /* VDBE Cursor number of pTab */
+ sqlite *db; /* The database structure */
+ Index **apIdx = 0; /* An array of indices that need updating too */
+ char *aIdxUsed = 0; /* aIdxUsed[i]==1 if the i-th index is used */
+ int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the
+ ** an expression for the i-th column of the table.
+ ** aXRef[i]==-1 if the i-th column is not changed. */
+ int chngRecno; /* True if the record number is being changed */
+ Expr *pRecnoExpr; /* Expression defining the new record number */
+ int openAll; /* True if all indices need to be opened */
+ int isView; /* Trying to update a view */
+ int iStackDepth; /* Index of memory cell holding stack depth */
+ AuthContext sContext; /* The authorization context */
+
+ int before_triggers; /* True if there are any BEFORE triggers */
+ int after_triggers; /* True if there are any AFTER triggers */
+ int row_triggers_exist = 0; /* True if any row triggers exist */
+
+ int newIdx = -1; /* index of trigger "new" temp table */
+ int oldIdx = -1; /* index of trigger "old" temp table */
+
+ sContext.pParse = 0;
+ if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup;
+ db = pParse->db;
+ assert( pTabList->nSrc==1 );
+ iStackDepth = pParse->nMem++;
+
+ /* Locate the table which we want to update.
+ */
+ pTab = sqliteSrcListLookup(pParse, pTabList);
+ if( pTab==0 ) goto update_cleanup;
+ before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger,
+ TK_UPDATE, TK_BEFORE, TK_ROW, pChanges);
+ after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger,
+ TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
+ row_triggers_exist = before_triggers || after_triggers;
+ isView = pTab->pSelect!=0;
+ if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){
+ goto update_cleanup;
+ }
+ if( isView ){
+ if( sqliteViewGetColumnNames(pParse, pTab) ){
+ goto update_cleanup;
+ }
+ }
+ aXRef = sqliteMalloc( sizeof(int) * pTab->nCol );
+ if( aXRef==0 ) goto update_cleanup;
+ for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;
+
+ /* If there are FOR EACH ROW triggers, allocate cursors for the
+ ** special OLD and NEW tables
+ */
+ if( row_triggers_exist ){
+ newIdx = pParse->nTab++;
+ oldIdx = pParse->nTab++;
+ }
+
+ /* Allocate a cursors for the main database table and for all indices.
+ ** The index cursors might not be used, but if they are used they
+ ** need to occur right after the database cursor. So go ahead and
+ ** allocate enough space, just in case.
+ */
+ pTabList->a[0].iCursor = iCur = pParse->nTab++;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ pParse->nTab++;
+ }
+
+ /* Resolve the column names in all the expressions of the
+ ** of the UPDATE statement. Also find the column index
+ ** for each column to be updated in the pChanges array. For each
+ ** column to be updated, make sure we have authorization to change
+ ** that column.
+ */
+ chngRecno = 0;
+ for(i=0; i<pChanges->nExpr; i++){
+ if( sqliteExprResolveIds(pParse, pTabList, 0, pChanges->a[i].pExpr) ){
+ goto update_cleanup;
+ }
+ if( sqliteExprCheck(pParse, pChanges->a[i].pExpr, 0, 0) ){
+ goto update_cleanup;
+ }
+ for(j=0; j<pTab->nCol; j++){
+ if( sqliteStrICmp(pTab->aCol[j].zName, pChanges->a[i].zName)==0 ){
+ if( j==pTab->iPKey ){
+ chngRecno = 1;
+ pRecnoExpr = pChanges->a[i].pExpr;
+ }
+ aXRef[j] = i;
+ break;
+ }
+ }
+ if( j>=pTab->nCol ){
+ if( sqliteIsRowid(pChanges->a[i].zName) ){
+ chngRecno = 1;
+ pRecnoExpr = pChanges->a[i].pExpr;
+ }else{
+ sqliteErrorMsg(pParse, "no such column: %s", pChanges->a[i].zName);
+ goto update_cleanup;
+ }
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int rc;
+ rc = sqliteAuthCheck(pParse, SQLITE_UPDATE, pTab->zName,
+ pTab->aCol[j].zName, db->aDb[pTab->iDb].zName);
+ if( rc==SQLITE_DENY ){
+ goto update_cleanup;
+ }else if( rc==SQLITE_IGNORE ){
+ aXRef[j] = -1;
+ }
+ }
+#endif
+ }
+
+ /* Allocate memory for the array apIdx[] and fill it with pointers to every
+ ** index that needs to be updated. Indices only need updating if their
+ ** key includes one of the columns named in pChanges or if the record
+ ** number of the original table entry is changing.
+ */
+ for(nIdx=nIdxTotal=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdxTotal++){
+ if( chngRecno ){
+ i = 0;
+ }else {
+ for(i=0; i<pIdx->nColumn; i++){
+ if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
+ }
+ }
+ if( i<pIdx->nColumn ) nIdx++;
+ }
+ if( nIdxTotal>0 ){
+ apIdx = sqliteMalloc( sizeof(Index*) * nIdx + nIdxTotal );
+ if( apIdx==0 ) goto update_cleanup;
+ aIdxUsed = (char*)&apIdx[nIdx];
+ }
+ for(nIdx=j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ if( chngRecno ){
+ i = 0;
+ }else{
+ for(i=0; i<pIdx->nColumn; i++){
+ if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
+ }
+ }
+ if( i<pIdx->nColumn ){
+ apIdx[nIdx++] = pIdx;
+ aIdxUsed[j] = 1;
+ }else{
+ aIdxUsed[j] = 0;
+ }
+ }
+
+ /* Resolve the column names in all the expressions in the
+ ** WHERE clause.
+ */
+ if( pWhere ){
+ if( sqliteExprResolveIds(pParse, pTabList, 0, pWhere) ){
+ goto update_cleanup;
+ }
+ if( sqliteExprCheck(pParse, pWhere, 0, 0) ){
+ goto update_cleanup;
+ }
+ }
+
+ /* Start the view context
+ */
+ if( isView ){
+ sqliteAuthContextPush(pParse, &sContext, pTab->zName);
+ }
+
+ /* Begin generating code.
+ */
+ v = sqliteGetVdbe(pParse);
+ if( v==0 ) goto update_cleanup;
+ sqliteBeginWriteOperation(pParse, 1, pTab->iDb);
+
+ /* If we are trying to update a view, construct that view into
+ ** a temporary table.
+ */
+ if( isView ){
+ Select *pView;
+ pView = sqliteSelectDup(pTab->pSelect);
+ sqliteSelect(pParse, pView, SRT_TempTable, iCur, 0, 0, 0);
+ sqliteSelectDelete(pView);
+ }
+
+ /* Begin the database scan
+ */
+ pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1, 0);
+ if( pWInfo==0 ) goto update_cleanup;
+
+ /* Remember the index of every item to be updated.
+ */
+ sqliteVdbeAddOp(v, OP_ListWrite, 0, 0);
+
+ /* End the database scan loop.
+ */
+ sqliteWhereEnd(pWInfo);
+
+ /* Initialize the count of updated rows
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
+ sqliteVdbeAddOp(v, OP_Integer, 0, 0);
+ }
+
+ if( row_triggers_exist ){
+ /* Create pseudo-tables for NEW and OLD
+ */
+ sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0);
+ sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0);
+
+ /* The top of the update loop for when there are triggers.
+ */
+ sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
+ sqliteVdbeAddOp(v, OP_StackDepth, 0, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, iStackDepth, 1);
+ loopStart = sqliteVdbeAddOp(v, OP_MemLoad, iStackDepth, 0);
+ sqliteVdbeAddOp(v, OP_StackReset, 0, 0);
+ jumpInst = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+
+ /* Open a cursor and make it point to the record that is
+ ** being updated.
+ */
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum);
+ }
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0);
+
+ /* Generate the OLD table
+ */
+ sqliteVdbeAddOp(v, OP_Recno, iCur, 0);
+ sqliteVdbeAddOp(v, OP_RowData, iCur, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, oldIdx, 0);
+
+ /* Generate the NEW table
+ */
+ if( chngRecno ){
+ sqliteExprCode(pParse, pRecnoExpr);
+ }else{
+ sqliteVdbeAddOp(v, OP_Recno, iCur, 0);
+ }
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ continue;
+ }
+ j = aXRef[i];
+ if( j<0 ){
+ sqliteVdbeAddOp(v, OP_Column, iCur, i);
+ }else{
+ sqliteExprCode(pParse, pChanges->a[j].pExpr);
+ }
+ }
+ sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+ sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0);
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ }
+
+ /* Fire the BEFORE and INSTEAD OF triggers
+ */
+ if( sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_BEFORE, pTab,
+ newIdx, oldIdx, onError, loopStart) ){
+ goto update_cleanup;
+ }
+ }
+
+ if( !isView ){
+ /*
+ ** Open every index that needs updating. Note that if any
+ ** index could potentially invoke a REPLACE conflict resolution
+ ** action, then we need to open all indices because we might need
+ ** to be deleting some records.
+ */
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenWrite, iCur, pTab->tnum);
+ if( onError==OE_Replace ){
+ openAll = 1;
+ }else{
+ openAll = 0;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( pIdx->onError==OE_Replace ){
+ openAll = 1;
+ break;
+ }
+ }
+ }
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aIdxUsed[i] ){
+ sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenWrite, iCur+i+1, pIdx->tnum);
+ assert( pParse->nTab>iCur+i+1 );
+ }
+ }
+
+ /* Loop over every record that needs updating. We have to load
+ ** the old data for each record to be updated because some columns
+ ** might not change and we will need to copy the old value.
+ ** Also, the old data is needed to delete the old index entires.
+ ** So make the cursor point at the old record.
+ */
+ if( !row_triggers_exist ){
+ sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
+ jumpInst = loopStart = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ }
+ sqliteVdbeAddOp(v, OP_NotExists, iCur, loopStart);
+
+ /* If the record number will change, push the record number as it
+ ** will be after the update. (The old record number is currently
+ ** on top of the stack.)
+ */
+ if( chngRecno ){
+ sqliteExprCode(pParse, pRecnoExpr);
+ sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
+ }
+
+ /* Compute new data for this record.
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ continue;
+ }
+ j = aXRef[i];
+ if( j<0 ){
+ sqliteVdbeAddOp(v, OP_Column, iCur, i);
+ }else{
+ sqliteExprCode(pParse, pChanges->a[j].pExpr);
+ }
+ }
+
+ /* Do constraint checks
+ */
+ sqliteGenerateConstraintChecks(pParse, pTab, iCur, aIdxUsed, chngRecno, 1,
+ onError, loopStart);
+
+ /* Delete the old indices for the current record.
+ */
+ sqliteGenerateRowIndexDelete(db, v, pTab, iCur, aIdxUsed);
+
+ /* If changing the record number, delete the old record.
+ */
+ if( chngRecno ){
+ sqliteVdbeAddOp(v, OP_Delete, iCur, 0);
+ }
+
+ /* Create the new index entries and the new record.
+ */
+ sqliteCompleteInsertion(pParse, pTab, iCur, aIdxUsed, chngRecno, 1, -1);
+ }
+
+ /* Increment the row counter
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack){
+ sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
+ }
+
+ /* If there are triggers, close all the cursors after each iteration
+ ** through the loop. The fire the after triggers.
+ */
+ if( row_triggers_exist ){
+ if( !isView ){
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aIdxUsed[i] )
+ sqliteVdbeAddOp(v, OP_Close, iCur+i+1, 0);
+ }
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ pParse->nTab = iCur;
+ }
+ if( sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_AFTER, pTab,
+ newIdx, oldIdx, onError, loopStart) ){
+ goto update_cleanup;
+ }
+ }
+
+ /* Repeat the above with the next record to be updated, until
+ ** all record selected by the WHERE clause have been updated.
+ */
+ sqliteVdbeAddOp(v, OP_Goto, 0, loopStart);
+ sqliteVdbeChangeP2(v, jumpInst, sqliteVdbeCurrentAddr(v));
+ sqliteVdbeAddOp(v, OP_ListReset, 0, 0);
+
+ /* Close all tables if there were no FOR EACH ROW triggers */
+ if( !row_triggers_exist ){
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aIdxUsed[i] ){
+ sqliteVdbeAddOp(v, OP_Close, iCur+i+1, 0);
+ }
+ }
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ pParse->nTab = iCur;
+ }else{
+ sqliteVdbeAddOp(v, OP_Close, newIdx, 0);
+ sqliteVdbeAddOp(v, OP_Close, oldIdx, 0);
+ }
+
+ sqliteVdbeAddOp(v, OP_SetCounts, 0, 0);
+ sqliteEndWriteOperation(pParse);
+
+ /*
+ ** Return the number of rows that were changed.
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
+ sqliteVdbeOp3(v, OP_ColumnName, 0, 1, "rows updated", P3_STATIC);
+ sqliteVdbeAddOp(v, OP_Callback, 1, 0);
+ }
+
+update_cleanup:
+ sqliteAuthContextPop(&sContext);
+ sqliteFree(apIdx);
+ sqliteFree(aXRef);
+ sqliteSrcListDelete(pTabList);
+ sqliteExprListDelete(pChanges);
+ sqliteExprDelete(pWhere);
+ return;
+}
diff --git a/src/libs/sqlite2/util.c b/src/libs/sqlite2/util.c
new file mode 100644
index 00000000..579bf753
--- /dev/null
+++ b/src/libs/sqlite2/util.c
@@ -0,0 +1,1134 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Utility functions used throughout sqlite.
+**
+** This file contains functions for allocating memory, comparing
+** strings, and stuff like that.
+**
+** $Id: util.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+#include <stdarg.h>
+#include <ctype.h>
+
+/*
+** If malloc() ever fails, this global variable gets set to 1.
+** This causes the library to abort and never again function.
+*/
+int sqlite_malloc_failed = 0;
+
+/*
+** If MEMORY_DEBUG is defined, then use versions of malloc() and
+** free() that track memory usage and check for buffer overruns.
+*/
+#ifdef MEMORY_DEBUG
+
+/*
+** For keeping track of the number of mallocs and frees. This
+** is used to check for memory leaks.
+*/
+int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
+int sqlite_nFree; /* Number of sqliteFree() calls */
+int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
+#if MEMORY_DEBUG>1
+static int memcnt = 0;
+#endif
+
+/*
+** Number of 32-bit guard words
+*/
+#define N_GUARD 1
+
+/*
+** Allocate new memory and set it to zero. Return NULL if
+** no memory is available.
+*/
+void *sqliteMalloc_(int n, int bZero, char *zFile, int line){
+ void *p;
+ int *pi;
+ int i, k;
+ if( sqlite_iMallocFail>=0 ){
+ sqlite_iMallocFail--;
+ if( sqlite_iMallocFail==0 ){
+ sqlite_malloc_failed++;
+#if MEMORY_DEBUG>1
+ fprintf(stderr,"**** failed to allocate %d bytes at %s:%d\n",
+ n, zFile,line);
+#endif
+ sqlite_iMallocFail--;
+ return 0;
+ }
+ }
+ if( n==0 ) return 0;
+ k = (n+sizeof(int)-1)/sizeof(int);
+ pi = malloc( (N_GUARD*2+1+k)*sizeof(int));
+ if( pi==0 ){
+ sqlite_malloc_failed++;
+ return 0;
+ }
+ sqlite_nMalloc++;
+ for(i=0; i<N_GUARD; i++) pi[i] = 0xdead1122;
+ pi[N_GUARD] = n;
+ for(i=0; i<N_GUARD; i++) pi[k+1+N_GUARD+i] = 0xdead3344;
+ p = &pi[N_GUARD+1];
+ memset(p, bZero==0, n);
+#if MEMORY_DEBUG>1
+ fprintf(stderr,"%06d malloc %d bytes at 0x%x from %s:%d\n",
+ ++memcnt, n, (int)p, zFile,line);
+#endif
+ return p;
+}
+
+/*
+** Check to see if the given pointer was obtained from sqliteMalloc()
+** and is able to hold at least N bytes. Raise an exception if this
+** is not the case.
+**
+** This routine is used for testing purposes only.
+*/
+void sqliteCheckMemory(void *p, int N){
+ int *pi = p;
+ int n, i, k;
+ pi -= N_GUARD+1;
+ for(i=0; i<N_GUARD; i++){
+ assert( pi[i]==0xdead1122 );
+ }
+ n = pi[N_GUARD];
+ assert( N>=0 && N<n );
+ k = (n+sizeof(int)-1)/sizeof(int);
+ for(i=0; i<N_GUARD; i++){
+ assert( pi[k+N_GUARD+1+i]==0xdead3344 );
+ }
+}
+
+/*
+** Free memory previously obtained from sqliteMalloc()
+*/
+void sqliteFree_(void *p, char *zFile, int line){
+ if( p ){
+ int *pi, i, k, n;
+ pi = p;
+ pi -= N_GUARD+1;
+ sqlite_nFree++;
+ for(i=0; i<N_GUARD; i++){
+ if( pi[i]!=0xdead1122 ){
+ fprintf(stderr,"Low-end memory corruption at 0x%x\n", (int)p);
+ return;
+ }
+ }
+ n = pi[N_GUARD];
+ k = (n+sizeof(int)-1)/sizeof(int);
+ for(i=0; i<N_GUARD; i++){
+ if( pi[k+N_GUARD+1+i]!=0xdead3344 ){
+ fprintf(stderr,"High-end memory corruption at 0x%x\n", (int)p);
+ return;
+ }
+ }
+ memset(pi, 0xff, (k+N_GUARD*2+1)*sizeof(int));
+#if MEMORY_DEBUG>1
+ fprintf(stderr,"%06d free %d bytes at 0x%x from %s:%d\n",
+ ++memcnt, n, (int)p, zFile,line);
+#endif
+ free(pi);
+ }
+}
+
+/*
+** Resize a prior allocation. If p==0, then this routine
+** works just like sqliteMalloc(). If n==0, then this routine
+** works just like sqliteFree().
+*/
+void *sqliteRealloc_(void *oldP, int n, char *zFile, int line){
+ int *oldPi, *pi, i, k, oldN, oldK;
+ void *p;
+ if( oldP==0 ){
+ return sqliteMalloc_(n,1,zFile,line);
+ }
+ if( n==0 ){
+ sqliteFree_(oldP,zFile,line);
+ return 0;
+ }
+ oldPi = oldP;
+ oldPi -= N_GUARD+1;
+ if( oldPi[0]!=0xdead1122 ){
+ fprintf(stderr,"Low-end memory corruption in realloc at 0x%x\n", (int)oldP);
+ return 0;
+ }
+ oldN = oldPi[N_GUARD];
+ oldK = (oldN+sizeof(int)-1)/sizeof(int);
+ for(i=0; i<N_GUARD; i++){
+ if( oldPi[oldK+N_GUARD+1+i]!=0xdead3344 ){
+ fprintf(stderr,"High-end memory corruption in realloc at 0x%x\n",
+ (int)oldP);
+ return 0;
+ }
+ }
+ k = (n + sizeof(int) - 1)/sizeof(int);
+ pi = malloc( (k+N_GUARD*2+1)*sizeof(int) );
+ if( pi==0 ){
+ sqlite_malloc_failed++;
+ return 0;
+ }
+ for(i=0; i<N_GUARD; i++) pi[i] = 0xdead1122;
+ pi[N_GUARD] = n;
+ for(i=0; i<N_GUARD; i++) pi[k+N_GUARD+1+i] = 0xdead3344;
+ p = &pi[N_GUARD+1];
+ memcpy(p, oldP, n>oldN ? oldN : n);
+ if( n>oldN ){
+ memset(&((char*)p)[oldN], 0, n-oldN);
+ }
+ memset(oldPi, 0xab, (oldK+N_GUARD+2)*sizeof(int));
+ free(oldPi);
+#if MEMORY_DEBUG>1
+ fprintf(stderr,"%06d realloc %d to %d bytes at 0x%x to 0x%x at %s:%d\n",
+ ++memcnt, oldN, n, (int)oldP, (int)p, zFile, line);
+#endif
+ return p;
+}
+
+/*
+** Make a duplicate of a string into memory obtained from malloc()
+** Free the original string using sqliteFree().
+**
+** This routine is called on all strings that are passed outside of
+** the SQLite library. That way clients can free the string using free()
+** rather than having to call sqliteFree().
+*/
+void sqliteStrRealloc(char **pz){
+ char *zNew;
+ if( pz==0 || *pz==0 ) return;
+ zNew = malloc( strlen(*pz) + 1 );
+ if( zNew==0 ){
+ sqlite_malloc_failed++;
+ sqliteFree(*pz);
+ *pz = 0;
+ }
+ strcpy(zNew, *pz);
+ sqliteFree(*pz);
+ *pz = zNew;
+}
+
+/*
+** Make a copy of a string in memory obtained from sqliteMalloc()
+*/
+char *sqliteStrDup_(const char *z, char *zFile, int line){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqliteMalloc_(strlen(z)+1, 0, zFile, line);
+ if( zNew ) strcpy(zNew, z);
+ return zNew;
+}
+char *sqliteStrNDup_(const char *z, int n, char *zFile, int line){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqliteMalloc_(n+1, 0, zFile, line);
+ if( zNew ){
+ memcpy(zNew, z, n);
+ zNew[n] = 0;
+ }
+ return zNew;
+}
+#endif /* MEMORY_DEBUG */
+
+/*
+** The following versions of malloc() and free() are for use in a
+** normal build.
+*/
+#if !defined(MEMORY_DEBUG)
+
+/*
+** Allocate new memory and set it to zero. Return NULL if
+** no memory is available. See also sqliteMallocRaw().
+*/
+void *sqliteMalloc(int n){
+ void *p;
+ if( (p = malloc(n))==0 ){
+ if( n>0 ) sqlite_malloc_failed++;
+ }else{
+ memset(p, 0, n);
+ }
+ return p;
+}
+
+/*
+** Allocate new memory but do not set it to zero. Return NULL if
+** no memory is available. See also sqliteMalloc().
+*/
+void *sqliteMallocRaw(int n){
+ void *p;
+ if( (p = malloc(n))==0 ){
+ if( n>0 ) sqlite_malloc_failed++;
+ }
+ return p;
+}
+
+/*
+** Free memory previously obtained from sqliteMalloc()
+*/
+void sqliteFree(void *p){
+ if( p ){
+ free(p);
+ }
+}
+
+/*
+** Resize a prior allocation. If p==0, then this routine
+** works just like sqliteMalloc(). If n==0, then this routine
+** works just like sqliteFree().
+*/
+void *sqliteRealloc(void *p, int n){
+ void *p2;
+ if( p==0 ){
+ return sqliteMalloc(n);
+ }
+ if( n==0 ){
+ sqliteFree(p);
+ return 0;
+ }
+ p2 = realloc(p, n);
+ if( p2==0 ){
+ sqlite_malloc_failed++;
+ }
+ return p2;
+}
+
+/*
+** Make a copy of a string in memory obtained from sqliteMalloc()
+*/
+char *sqliteStrDup(const char *z){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqliteMallocRaw(strlen(z)+1);
+ if( zNew ) strcpy(zNew, z);
+ return zNew;
+}
+char *sqliteStrNDup(const char *z, int n){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqliteMallocRaw(n+1);
+ if( zNew ){
+ memcpy(zNew, z, n);
+ zNew[n] = 0;
+ }
+ return zNew;
+}
+#endif /* !defined(MEMORY_DEBUG) */
+
+/*
+** Create a string from the 2nd and subsequent arguments (up to the
+** first NULL argument), store the string in memory obtained from
+** sqliteMalloc() and make the pointer indicated by the 1st argument
+** point to that string. The 1st argument must either be NULL or
+** point to memory obtained from sqliteMalloc().
+*/
+void sqliteSetString(char **pz, ...){
+ va_list ap;
+ int nByte;
+ const char *z;
+ char *zResult;
+
+ if( pz==0 ) return;
+ nByte = 1;
+ va_start(ap, pz);
+ while( (z = va_arg(ap, const char*))!=0 ){
+ nByte += strlen(z);
+ }
+ va_end(ap);
+ sqliteFree(*pz);
+ *pz = zResult = sqliteMallocRaw( nByte );
+ if( zResult==0 ){
+ return;
+ }
+ *zResult = 0;
+ va_start(ap, pz);
+ while( (z = va_arg(ap, const char*))!=0 ){
+ strcpy(zResult, z);
+ zResult += strlen(zResult);
+ }
+ va_end(ap);
+#ifdef MEMORY_DEBUG
+#if MEMORY_DEBUG>1
+ fprintf(stderr,"string at 0x%x is %s\n", (int)*pz, *pz);
+#endif
+#endif
+}
+
+/*
+** Works like sqliteSetString, but each string is now followed by
+** a length integer which specifies how much of the source string
+** to copy (in bytes). -1 means use the whole string. The 1st
+** argument must either be NULL or point to memory obtained from
+** sqliteMalloc().
+*/
+void sqliteSetNString(char **pz, ...){
+ va_list ap;
+ int nByte;
+ const char *z;
+ char *zResult;
+ int n;
+
+ if( pz==0 ) return;
+ nByte = 0;
+ va_start(ap, pz);
+ while( (z = va_arg(ap, const char*))!=0 ){
+ n = va_arg(ap, int);
+ if( n<=0 ) n = strlen(z);
+ nByte += n;
+ }
+ va_end(ap);
+ sqliteFree(*pz);
+ *pz = zResult = sqliteMallocRaw( nByte + 1 );
+ if( zResult==0 ) return;
+ va_start(ap, pz);
+ while( (z = va_arg(ap, const char*))!=0 ){
+ n = va_arg(ap, int);
+ if( n<=0 ) n = strlen(z);
+ strncpy(zResult, z, n);
+ zResult += n;
+ }
+ *zResult = 0;
+#ifdef MEMORY_DEBUG
+#if MEMORY_DEBUG>1
+ fprintf(stderr,"string at 0x%x is %s\n", (int)*pz, *pz);
+#endif
+#endif
+ va_end(ap);
+}
+
+/*
+** Add an error message to pParse->zErrMsg and increment pParse->nErr.
+** The following formatting characters are allowed:
+**
+** %s Insert a string
+** %z A string that should be freed after use
+** %d Insert an integer
+** %T Insert a token
+** %S Insert the first element of a SrcList
+*/
+void sqliteErrorMsg(Parse *pParse, const char *zFormat, ...){
+ va_list ap;
+ pParse->nErr++;
+ sqliteFree(pParse->zErrMsg);
+ va_start(ap, zFormat);
+ pParse->zErrMsg = sqliteVMPrintf(zFormat, ap);
+ va_end(ap);
+}
+
+/*
+** Convert an SQL-style quoted string into a normal string by removing
+** the quote characters. The conversion is done in-place. If the
+** input does not begin with a quote character, then this routine
+** is a no-op.
+**
+** 2002-Feb-14: This routine is extended to remove MS-Access style
+** brackets from around identifers. For example: "[a-b-c]" becomes
+** "a-b-c".
+*/
+void sqliteDequote(char *z){
+ int quote;
+ int i, j;
+ if( z==0 ) return;
+ quote = z[0];
+ switch( quote ){
+ case '\'': break;
+ case '"': break;
+ case '[': quote = ']'; break;
+ default: return;
+ }
+ for(i=1, j=0; z[i]; i++){
+ if( z[i]==quote ){
+ if( z[i+1]==quote ){
+ z[j++] = quote;
+ i++;
+ }else{
+ z[j++] = 0;
+ break;
+ }
+ }else{
+ z[j++] = z[i];
+ }
+ }
+}
+
+/* An array to map all upper-case characters into their corresponding
+** lower-case character.
+*/
+static unsigned char UpperToLower[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103,
+ 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,
+ 122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,
+ 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,
+ 126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,
+ 162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,
+ 180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,
+ 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,
+ 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,
+ 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,
+ 252,253,254,255
+};
+
+/*
+** This function computes a hash on the name of a keyword.
+** Case is not significant.
+*/
+int sqliteHashNoCase(const char *z, int n){
+ int h = 0;
+ if( n<=0 ) n = strlen(z);
+ while( n > 0 ){
+ h = (h<<3) ^ h ^ UpperToLower[(unsigned char)*z++];
+ n--;
+ }
+ return h & 0x7fffffff;
+}
+
+/*
+** Some systems have stricmp(). Others have strcasecmp(). Because
+** there is no consistency, we will define our own.
+*/
+int sqliteStrICmp(const char *zLeft, const char *zRight){
+ unsigned char *a, *b;
+ a = (unsigned char *)zLeft;
+ b = (unsigned char *)zRight;
+ while( *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; }
+ return UpperToLower[*a] - UpperToLower[*b];
+}
+int sqliteStrNICmp(const char *zLeft, const char *zRight, int N){
+ unsigned char *a, *b;
+ a = (unsigned char *)zLeft;
+ b = (unsigned char *)zRight;
+ while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; }
+ return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b];
+}
+
+/*
+** Return TRUE if z is a pure numeric string. Return FALSE if the
+** string contains any character which is not part of a number.
+**
+** Am empty string is considered non-numeric.
+*/
+int sqliteIsNumber(const char *z){
+ if( *z=='-' || *z=='+' ) z++;
+ if( !isdigit(*z) ){
+ return 0;
+ }
+ z++;
+ while( isdigit(*z) ){ z++; }
+ if( *z=='.' ){
+ z++;
+ if( !isdigit(*z) ) return 0;
+ while( isdigit(*z) ){ z++; }
+ }
+ if( *z=='e' || *z=='E' ){
+ z++;
+ if( *z=='+' || *z=='-' ) z++;
+ if( !isdigit(*z) ) return 0;
+ while( isdigit(*z) ){ z++; }
+ }
+ return *z==0;
+}
+
+/*
+** The string z[] is an ascii representation of a real number.
+** Convert this string to a double.
+**
+** This routine assumes that z[] really is a valid number. If it
+** is not, the result is undefined.
+**
+** This routine is used instead of the library atof() function because
+** the library atof() might want to use "," as the decimal point instead
+** of "." depending on how locale is set. But that would cause problems
+** for SQL. So this routine always uses "." regardless of locale.
+*/
+double sqliteAtoF(const char *z, const char **pzEnd){
+ int sign = 1;
+ LONGDOUBLE_TYPE v1 = 0.0;
+ if( *z=='-' ){
+ sign = -1;
+ z++;
+ }else if( *z=='+' ){
+ z++;
+ }
+ while( isdigit(*z) ){
+ v1 = v1*10.0 + (*z - '0');
+ z++;
+ }
+ if( *z=='.' ){
+ LONGDOUBLE_TYPE divisor = 1.0;
+ z++;
+ while( isdigit(*z) ){
+ v1 = v1*10.0 + (*z - '0');
+ divisor *= 10.0;
+ z++;
+ }
+ v1 /= divisor;
+ }
+ if( *z=='e' || *z=='E' ){
+ int esign = 1;
+ int eval = 0;
+ LONGDOUBLE_TYPE scale = 1.0;
+ z++;
+ if( *z=='-' ){
+ esign = -1;
+ z++;
+ }else if( *z=='+' ){
+ z++;
+ }
+ while( isdigit(*z) ){
+ eval = eval*10 + *z - '0';
+ z++;
+ }
+ while( eval>=64 ){ scale *= 1.0e+64; eval -= 64; }
+ while( eval>=16 ){ scale *= 1.0e+16; eval -= 16; }
+ while( eval>=4 ){ scale *= 1.0e+4; eval -= 4; }
+ while( eval>=1 ){ scale *= 1.0e+1; eval -= 1; }
+ if( esign<0 ){
+ v1 /= scale;
+ }else{
+ v1 *= scale;
+ }
+ }
+ if( pzEnd ) *pzEnd = z;
+ return sign<0 ? -v1 : v1;
+}
+
+/*
+** The string zNum represents an integer. There might be some other
+** information following the integer too, but that part is ignored.
+** If the integer that the prefix of zNum represents will fit in a
+** 32-bit signed integer, return TRUE. Otherwise return FALSE.
+**
+** This routine returns FALSE for the string -2147483648 even that
+** that number will, in theory fit in a 32-bit integer. But positive
+** 2147483648 will not fit in 32 bits. So it seems safer to return
+** false.
+*/
+int sqliteFitsIn32Bits(const char *zNum){
+ int i, c;
+ if( *zNum=='-' || *zNum=='+' ) zNum++;
+ for(i=0; (c=zNum[i])>='0' && c<='9'; i++){}
+ return i<10 || (i==10 && memcmp(zNum,"2147483647",10)<=0);
+}
+
+/* This comparison routine is what we use for comparison operations
+** between numeric values in an SQL expression. "Numeric" is a little
+** bit misleading here. What we mean is that the strings have a
+** type of "numeric" from the point of view of SQL. The strings
+** do not necessarily contain numbers. They could contain text.
+**
+** If the input strings both look like actual numbers then they
+** compare in numerical order. Numerical strings are always less
+** than non-numeric strings so if one input string looks like a
+** number and the other does not, then the one that looks like
+** a number is the smaller. Non-numeric strings compare in
+** lexigraphical order (the same order as strcmp()).
+*/
+int sqliteCompare(const char *atext, const char *btext){
+ int result;
+ int isNumA, isNumB;
+ if( atext==0 ){
+ return -1;
+ }else if( btext==0 ){
+ return 1;
+ }
+ isNumA = sqliteIsNumber(atext);
+ isNumB = sqliteIsNumber(btext);
+ if( isNumA ){
+ if( !isNumB ){
+ result = -1;
+ }else{
+ double rA, rB;
+ rA = sqliteAtoF(atext, 0);
+ rB = sqliteAtoF(btext, 0);
+ if( rA<rB ){
+ result = -1;
+ }else if( rA>rB ){
+ result = +1;
+ }else{
+ result = 0;
+ }
+ }
+ }else if( isNumB ){
+ result = +1;
+ }else {
+ result = strcmp(atext, btext);
+ }
+ return result;
+}
+
+/*
+** This routine is used for sorting. Each key is a list of one or more
+** null-terminated elements. The list is terminated by two nulls in
+** a row. For example, the following text is a key with three elements
+**
+** Aone\000Dtwo\000Athree\000\000
+**
+** All elements begin with one of the characters "+-AD" and end with "\000"
+** with zero or more text elements in between. Except, NULL elements
+** consist of the special two-character sequence "N\000".
+**
+** Both arguments will have the same number of elements. This routine
+** returns negative, zero, or positive if the first argument is less
+** than, equal to, or greater than the first. (Result is a-b).
+**
+** Each element begins with one of the characters "+", "-", "A", "D".
+** This character determines the sort order and collating sequence:
+**
+** + Sort numerically in ascending order
+** - Sort numerically in descending order
+** A Sort as strings in ascending order
+** D Sort as strings in descending order.
+**
+** For the "+" and "-" sorting, pure numeric strings (strings for which the
+** isNum() function above returns TRUE) always compare less than strings
+** that are not pure numerics. Non-numeric strings compare in memcmp()
+** order. This is the same sort order as the sqliteCompare() function
+** above generates.
+**
+** The last point is a change from version 2.6.3 to version 2.7.0. In
+** version 2.6.3 and earlier, substrings of digits compare in numerical
+** and case was used only to break a tie.
+**
+** Elements that begin with 'A' or 'D' compare in memcmp() order regardless
+** of whether or not they look like a number.
+**
+** Note that the sort order imposed by the rules above is the same
+** from the ordering defined by the "<", "<=", ">", and ">=" operators
+** of expressions and for indices. This was not the case for version
+** 2.6.3 and earlier.
+*/
+int sqliteSortCompare(const char *a, const char *b){
+ int res = 0;
+ int isNumA, isNumB;
+ int dir = 0;
+
+ while( res==0 && *a && *b ){
+ if( a[0]=='N' || b[0]=='N' ){
+ if( a[0]==b[0] ){
+ a += 2;
+ b += 2;
+ continue;
+ }
+ if( a[0]=='N' ){
+ dir = b[0];
+ res = -1;
+ }else{
+ dir = a[0];
+ res = +1;
+ }
+ break;
+ }
+ assert( a[0]==b[0] );
+ if( (dir=a[0])=='A' || a[0]=='D' ){
+ res = strcmp(&a[1],&b[1]);
+ if( res ) break;
+ }else{
+ isNumA = sqliteIsNumber(&a[1]);
+ isNumB = sqliteIsNumber(&b[1]);
+ if( isNumA ){
+ double rA, rB;
+ if( !isNumB ){
+ res = -1;
+ break;
+ }
+ rA = sqliteAtoF(&a[1], 0);
+ rB = sqliteAtoF(&b[1], 0);
+ if( rA<rB ){
+ res = -1;
+ break;
+ }
+ if( rA>rB ){
+ res = +1;
+ break;
+ }
+ }else if( isNumB ){
+ res = +1;
+ break;
+ }else{
+ res = strcmp(&a[1],&b[1]);
+ if( res ) break;
+ }
+ }
+ a += strlen(&a[1]) + 2;
+ b += strlen(&b[1]) + 2;
+ }
+ if( dir=='-' || dir=='D' ) res = -res;
+ return res;
+}
+
+/*
+** Some powers of 64. These constants are needed in the
+** sqliteRealToSortable() routine below.
+*/
+#define _64e3 (64.0 * 64.0 * 64.0)
+#define _64e4 (64.0 * 64.0 * 64.0 * 64.0)
+#define _64e15 (_64e3 * _64e4 * _64e4 * _64e4)
+#define _64e16 (_64e4 * _64e4 * _64e4 * _64e4)
+#define _64e63 (_64e15 * _64e16 * _64e16 * _64e16)
+#define _64e64 (_64e16 * _64e16 * _64e16 * _64e16)
+
+/*
+** The following procedure converts a double-precision floating point
+** number into a string. The resulting string has the property that
+** two such strings comparied using strcmp() or memcmp() will give the
+** same results as a numeric comparison of the original floating point
+** numbers.
+**
+** This routine is used to generate database keys from floating point
+** numbers such that the keys sort in the same order as the original
+** floating point numbers even though the keys are compared using
+** memcmp().
+**
+** The calling function should have allocated at least 14 characters
+** of space for the buffer z[].
+*/
+void sqliteRealToSortable(double r, char *z){
+ int neg;
+ int exp;
+ int cnt = 0;
+
+ /* This array maps integers between 0 and 63 into base-64 digits.
+ ** The digits must be chosen such at their ASCII codes are increasing.
+ ** This means we can not use the traditional base-64 digit set. */
+ static const char zDigit[] =
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "|~";
+ if( r<0.0 ){
+ neg = 1;
+ r = -r;
+ *z++ = '-';
+ } else {
+ neg = 0;
+ *z++ = '0';
+ }
+ exp = 0;
+
+ if( r==0.0 ){
+ exp = -1024;
+ }else if( r<(0.5/64.0) ){
+ while( r < 0.5/_64e64 && exp > -961 ){ r *= _64e64; exp -= 64; }
+ while( r < 0.5/_64e16 && exp > -1009 ){ r *= _64e16; exp -= 16; }
+ while( r < 0.5/_64e4 && exp > -1021 ){ r *= _64e4; exp -= 4; }
+ while( r < 0.5/64.0 && exp > -1024 ){ r *= 64.0; exp -= 1; }
+ }else if( r>=0.5 ){
+ while( r >= 0.5*_64e63 && exp < 960 ){ r *= 1.0/_64e64; exp += 64; }
+ while( r >= 0.5*_64e15 && exp < 1008 ){ r *= 1.0/_64e16; exp += 16; }
+ while( r >= 0.5*_64e3 && exp < 1020 ){ r *= 1.0/_64e4; exp += 4; }
+ while( r >= 0.5 && exp < 1023 ){ r *= 1.0/64.0; exp += 1; }
+ }
+ if( neg ){
+ exp = -exp;
+ r = -r;
+ }
+ exp += 1024;
+ r += 0.5;
+ if( exp<0 ) return;
+ if( exp>=2048 || r>=1.0 ){
+ strcpy(z, "~~~~~~~~~~~~");
+ return;
+ }
+ *z++ = zDigit[(exp>>6)&0x3f];
+ *z++ = zDigit[exp & 0x3f];
+ while( r>0.0 && cnt<10 ){
+ int digit;
+ r *= 64.0;
+ digit = (int)r;
+ assert( digit>=0 && digit<64 );
+ *z++ = zDigit[digit & 0x3f];
+ r -= digit;
+ cnt++;
+ }
+ *z = 0;
+}
+
+#ifdef SQLITE_UTF8
+/*
+** X is a pointer to the first byte of a UTF-8 character. Increment
+** X so that it points to the next character. This only works right
+** if X points to a well-formed UTF-8 string.
+*/
+#define sqliteNextChar(X) while( (0xc0&*++(X))==0x80 ){}
+#define sqliteCharVal(X) sqlite_utf8_to_int(X)
+
+#else /* !defined(SQLITE_UTF8) */
+/*
+** For iso8859 encoding, the next character is just the next byte.
+*/
+#define sqliteNextChar(X) (++(X));
+#define sqliteCharVal(X) ((int)*(X))
+
+#endif /* defined(SQLITE_UTF8) */
+
+
+#ifdef SQLITE_UTF8
+/*
+** Convert the UTF-8 character to which z points into a 31-bit
+** UCS character. This only works right if z points to a well-formed
+** UTF-8 string.
+*/
+static int sqlite_utf8_to_int(const unsigned char *z){
+ int c;
+ static const int initVal[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
+ 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
+ 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 0, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0,
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 0, 1, 254,
+ 255,
+ };
+ c = initVal[*(z++)];
+ while( (0xc0&*z)==0x80 ){
+ c = (c<<6) | (0x3f&*(z++));
+ }
+ return c;
+}
+#endif
+
+/*
+** Compare two UTF-8 strings for equality where the first string can
+** potentially be a "glob" expression. Return true (1) if they
+** are the same and false (0) if they are different.
+**
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** With the [...] and [^...] matching, a ']' character can be included
+** in the list by making it the first character after '[' or '^'. A
+** range of characters can be specified using '-'. Example:
+** "[a-z]" matches any single lower-case letter. To match a '-', make
+** it the last character in the list.
+**
+** This routine is usually quick, but can be N**2 in the worst case.
+**
+** Hints: to match '*' or '?', put them in "[]". Like this:
+**
+** abc[*]xyz Matches "abc*xyz" only
+*/
+int
+sqliteGlobCompare(const unsigned char *zPattern, const unsigned char *zString){
+ int c;
+ int invert;
+ int seen;
+ int c2;
+
+ while( (c = *zPattern)!=0 ){
+ switch( c ){
+ case '*':
+ while( (c=zPattern[1]) == '*' || c == '?' ){
+ if( c=='?' ){
+ if( *zString==0 ) return 0;
+ sqliteNextChar(zString);
+ }
+ zPattern++;
+ }
+ if( c==0 ) return 1;
+ if( c=='[' ){
+ while( *zString && sqliteGlobCompare(&zPattern[1],zString)==0 ){
+ sqliteNextChar(zString);
+ }
+ return *zString!=0;
+ }else{
+ while( (c2 = *zString)!=0 ){
+ while( c2 != 0 && c2 != c ){ c2 = *++zString; }
+ if( c2==0 ) return 0;
+ if( sqliteGlobCompare(&zPattern[1],zString) ) return 1;
+ sqliteNextChar(zString);
+ }
+ return 0;
+ }
+ case '?': {
+ if( *zString==0 ) return 0;
+ sqliteNextChar(zString);
+ zPattern++;
+ break;
+ }
+ case '[': {
+ int prior_c = 0;
+ seen = 0;
+ invert = 0;
+ c = sqliteCharVal(zString);
+ if( c==0 ) return 0;
+ c2 = *++zPattern;
+ if( c2=='^' ){ invert = 1; c2 = *++zPattern; }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = *++zPattern;
+ }
+ while( (c2 = sqliteCharVal(zPattern))!=0 && c2!=']' ){
+ if( c2=='-' && zPattern[1]!=']' && zPattern[1]!=0 && prior_c>0 ){
+ zPattern++;
+ c2 = sqliteCharVal(zPattern);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else if( c==c2 ){
+ seen = 1;
+ prior_c = c2;
+ }else{
+ prior_c = c2;
+ }
+ sqliteNextChar(zPattern);
+ }
+ if( c2==0 || (seen ^ invert)==0 ) return 0;
+ sqliteNextChar(zString);
+ zPattern++;
+ break;
+ }
+ default: {
+ if( c != *zString ) return 0;
+ zPattern++;
+ zString++;
+ break;
+ }
+ }
+ }
+ return *zString==0;
+}
+
+/*
+** Compare two UTF-8 strings for equality using the "LIKE" operator of
+** SQL. The '%' character matches any sequence of 0 or more
+** characters and '_' matches any single character. Case is
+** not significant.
+**
+** This routine is just an adaptation of the sqliteGlobCompare()
+** routine above.
+*/
+int
+sqliteLikeCompare(const unsigned char *zPattern, const unsigned char *zString){
+ int c;
+ int c2;
+
+ while( (c = UpperToLower[*zPattern])!=0 ){
+ switch( c ){
+ case '%': {
+ while( (c=zPattern[1]) == '%' || c == '_' ){
+ if( c=='_' ){
+ if( *zString==0 ) return 0;
+ sqliteNextChar(zString);
+ }
+ zPattern++;
+ }
+ if( c==0 ) return 1;
+ c = UpperToLower[c];
+ while( (c2=UpperToLower[*zString])!=0 ){
+ while( c2 != 0 && c2 != c ){ c2 = UpperToLower[*++zString]; }
+ if( c2==0 ) return 0;
+ if( sqliteLikeCompare(&zPattern[1],zString) ) return 1;
+ sqliteNextChar(zString);
+ }
+ return 0;
+ }
+ case '_': {
+ if( *zString==0 ) return 0;
+ sqliteNextChar(zString);
+ zPattern++;
+ break;
+ }
+ default: {
+ if( c != UpperToLower[*zString] ) return 0;
+ zPattern++;
+ zString++;
+ break;
+ }
+ }
+ }
+ return *zString==0;
+}
+
+/*
+** Change the sqlite.magic from SQLITE_MAGIC_OPEN to SQLITE_MAGIC_BUSY.
+** Return an error (non-zero) if the magic was not SQLITE_MAGIC_OPEN
+** when this routine is called.
+**
+** This routine is a attempt to detect if two threads use the
+** same sqlite* pointer at the same time. There is a race
+** condition so it is possible that the error is not detected.
+** But usually the problem will be seen. The result will be an
+** error which can be used to debug the application that is
+** using SQLite incorrectly.
+**
+** Ticket #202: If db->magic is not a valid open value, take care not
+** to modify the db structure at all. It could be that db is a stale
+** pointer. In other words, it could be that there has been a prior
+** call to sqlite_close(db) and db has been deallocated. And we do
+** not want to write into deallocated memory.
+*/
+int sqliteSafetyOn(sqlite *db){
+ if( db->magic==SQLITE_MAGIC_OPEN ){
+ db->magic = SQLITE_MAGIC_BUSY;
+ return 0;
+ }else if( db->magic==SQLITE_MAGIC_BUSY || db->magic==SQLITE_MAGIC_ERROR
+ || db->want_to_close ){
+ db->magic = SQLITE_MAGIC_ERROR;
+ db->flags |= SQLITE_Interrupt;
+ }
+ return 1;
+}
+
+/*
+** Change the magic from SQLITE_MAGIC_BUSY to SQLITE_MAGIC_OPEN.
+** Return an error (non-zero) if the magic was not SQLITE_MAGIC_BUSY
+** when this routine is called.
+*/
+int sqliteSafetyOff(sqlite *db){
+ if( db->magic==SQLITE_MAGIC_BUSY ){
+ db->magic = SQLITE_MAGIC_OPEN;
+ return 0;
+ }else if( db->magic==SQLITE_MAGIC_OPEN || db->magic==SQLITE_MAGIC_ERROR
+ || db->want_to_close ){
+ db->magic = SQLITE_MAGIC_ERROR;
+ db->flags |= SQLITE_Interrupt;
+ }
+ return 1;
+}
+
+/*
+** Check to make sure we are not currently executing an sqlite_exec().
+** If we are currently in an sqlite_exec(), return true and set
+** sqlite.magic to SQLITE_MAGIC_ERROR. This will cause a complete
+** shutdown of the database.
+**
+** This routine is used to try to detect when API routines are called
+** at the wrong time or in the wrong sequence.
+*/
+int sqliteSafetyCheck(sqlite *db){
+ if( db->pVdbe!=0 ){
+ db->magic = SQLITE_MAGIC_ERROR;
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/libs/sqlite2/vacuum.c b/src/libs/sqlite2/vacuum.c
new file mode 100644
index 00000000..21556c3d
--- /dev/null
+++ b/src/libs/sqlite2/vacuum.c
@@ -0,0 +1,305 @@
+/*
+** 2003 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the VACUUM command.
+**
+** Most of the code in this file may be omitted by defining the
+** SQLITE_OMIT_VACUUM macro.
+**
+** $Id: vacuum.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+
+/*
+** A structure for holding a dynamic string - a string that can grow
+** without bound.
+*/
+typedef struct dynStr dynStr;
+struct dynStr {
+ char *z; /* Text of the string in space obtained from sqliteMalloc() */
+ int nAlloc; /* Amount of space allocated to z[] */
+ int nUsed; /* Next unused slot in z[] */
+};
+
+/*
+** A structure that holds the vacuum context
+*/
+typedef struct vacuumStruct vacuumStruct;
+struct vacuumStruct {
+ sqlite *dbOld; /* Original database */
+ sqlite *dbNew; /* New database */
+ char **pzErrMsg; /* Write errors here */
+ int rc; /* Set to non-zero on an error */
+ const char *zTable; /* Name of a table being copied */
+ const char *zPragma; /* Pragma to execute with results */
+ dynStr s1, s2; /* Two dynamic strings */
+};
+
+#if !defined(SQLITE_OMIT_VACUUM) || SQLITE_OMIT_VACUUM
+/*
+** Append text to a dynamic string
+*/
+static void appendText(dynStr *p, const char *zText, int nText){
+ if( nText<0 ) nText = strlen(zText);
+ if( p->z==0 || p->nUsed + nText + 1 >= p->nAlloc ){
+ char *zNew;
+ p->nAlloc = p->nUsed + nText + 1000;
+ zNew = sqliteRealloc(p->z, p->nAlloc);
+ if( zNew==0 ){
+ sqliteFree(p->z);
+ memset(p, 0, sizeof(*p));
+ return;
+ }
+ p->z = zNew;
+ }
+ memcpy(&p->z[p->nUsed], zText, nText+1);
+ p->nUsed += nText;
+}
+
+/*
+** Append text to a dynamic string, having first put the text in quotes.
+*/
+static void appendQuoted(dynStr *p, const char *zText){
+ int i, j;
+ appendText(p, "'", 1);
+ for(i=j=0; zText[i]; i++){
+ if( zText[i]=='\'' ){
+ appendText(p, &zText[j], i-j+1);
+ j = i + 1;
+ appendText(p, "'", 1);
+ }
+ }
+ if( j<i ){
+ appendText(p, &zText[j], i-j);
+ }
+ appendText(p, "'", 1);
+}
+
+/*
+** Execute statements of SQL. If an error occurs, write the error
+** message into *pzErrMsg and return non-zero.
+*/
+static int execsql(char **pzErrMsg, sqlite *db, const char *zSql){
+ char *zErrMsg = 0;
+ int rc;
+
+ /* printf("***** executing *****\n%s\n", zSql); */
+ rc = sqlite_exec(db, zSql, 0, 0, &zErrMsg);
+ if( zErrMsg ){
+ sqliteSetString(pzErrMsg, zErrMsg, (char*)0);
+ sqlite_freemem(zErrMsg);
+ }
+ return rc;
+}
+
+/*
+** This is the second stage callback. Each invocation contains all the
+** data for a single row of a single table in the original database. This
+** routine must write that information into the new database.
+*/
+static int vacuumCallback2(void *pArg, int argc, char **argv, char **NotUsed){
+ vacuumStruct *p = (vacuumStruct*)pArg;
+ const char *zSep = "(";
+ int i;
+
+ if( argv==0 ) return 0;
+ p->s2.nUsed = 0;
+ appendText(&p->s2, "INSERT INTO ", -1);
+ appendQuoted(&p->s2, p->zTable);
+ appendText(&p->s2, " VALUES", -1);
+ for(i=0; i<argc; i++){
+ appendText(&p->s2, zSep, 1);
+ zSep = ",";
+ if( argv[i]==0 ){
+ appendText(&p->s2, "NULL", 4);
+ }else{
+ appendQuoted(&p->s2, argv[i]);
+ }
+ }
+ appendText(&p->s2,")", 1);
+ p->rc = execsql(p->pzErrMsg, p->dbNew, p->s2.z);
+ return p->rc;
+}
+
+/*
+** This is the first stage callback. Each invocation contains three
+** arguments where are taken from the SQLITE_MASTER table of the original
+** database: (1) the entry type, (2) the entry name, and (3) the SQL for
+** the entry. In all cases, execute the SQL of the third argument.
+** For tables, run a query to select all entries in that table and
+** transfer them to the second-stage callback.
+*/
+static int vacuumCallback1(void *pArg, int argc, char **argv, char **NotUsed){
+ vacuumStruct *p = (vacuumStruct*)pArg;
+ int rc = 0;
+ assert( argc==3 );
+ if( argv==0 ) return 0;
+ assert( argv[0]!=0 );
+ assert( argv[1]!=0 );
+ assert( argv[2]!=0 );
+ rc = execsql(p->pzErrMsg, p->dbNew, argv[2]);
+ if( rc==SQLITE_OK && strcmp(argv[0],"table")==0 ){
+ char *zErrMsg = 0;
+ p->s1.nUsed = 0;
+ appendText(&p->s1, "SELECT * FROM ", -1);
+ appendQuoted(&p->s1, argv[1]);
+ p->zTable = argv[1];
+ rc = sqlite_exec(p->dbOld, p->s1.z, vacuumCallback2, p, &zErrMsg);
+ if( zErrMsg ){
+ sqliteSetString(p->pzErrMsg, zErrMsg, (char*)0);
+ sqlite_freemem(zErrMsg);
+ }
+ }
+ if( rc!=SQLITE_ABORT ) p->rc = rc;
+ return rc;
+}
+
+/*
+** Generate a random name of 20 character in length.
+*/
+static void randomName(unsigned char *zBuf){
+ static const unsigned char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789";
+ int i;
+ sqliteRandomness(20, zBuf);
+ for(i=0; i<20; i++){
+ zBuf[i] = zChars[ zBuf[i]%(sizeof(zChars)-1) ];
+ }
+}
+#endif
+
+/*
+** The non-standard VACUUM command is used to clean up the database,
+** collapse free space, etc. It is modelled after the VACUUM command
+** in PostgreSQL.
+**
+** In version 1.0.x of SQLite, the VACUUM command would call
+** gdbm_reorganize() on all the database tables. But beginning
+** with 2.0.0, SQLite no longer uses GDBM so this command has
+** become a no-op.
+*/
+void sqliteVacuum(Parse *pParse, Token *pTableName){
+ Vdbe *v = sqliteGetVdbe(pParse);
+ sqliteVdbeAddOp(v, OP_Vacuum, 0, 0);
+ return;
+}
+
+/*
+** This routine implements the OP_Vacuum opcode of the VDBE.
+*/
+int sqliteRunVacuum(char **pzErrMsg, sqlite *db){
+#if !defined(SQLITE_OMIT_VACUUM) || SQLITE_OMIT_VACUUM
+ const char *zFilename; /* full pathname of the database file */
+ int nFilename; /* number of characters in zFilename[] */
+ char *zTemp = 0; /* a temporary file in same directory as zFilename */
+ sqlite *dbNew = 0; /* The new vacuumed database */
+ int rc = SQLITE_OK; /* Return code from service routines */
+ int i; /* Loop counter */
+ char *zErrMsg; /* Error message */
+ vacuumStruct sVac; /* Information passed to callbacks */
+
+ if( db->flags & SQLITE_InTrans ){
+ sqliteSetString(pzErrMsg, "cannot VACUUM from within a transaction",
+ (char*)0);
+ return SQLITE_ERROR;
+ }
+ if( db->flags & SQLITE_Interrupt ){
+ return SQLITE_INTERRUPT;
+ }
+ memset(&sVac, 0, sizeof(sVac));
+
+ /* Get the full pathname of the database file and create two
+ ** temporary filenames in the same directory as the original file.
+ */
+ zFilename = sqliteBtreeGetFilename(db->aDb[0].pBt);
+ if( zFilename==0 ){
+ /* This only happens with the in-memory database. VACUUM is a no-op
+ ** there, so just return */
+ return SQLITE_OK;
+ }
+ nFilename = strlen(zFilename);
+ zTemp = sqliteMalloc( nFilename+100 );
+ if( zTemp==0 ) return SQLITE_NOMEM;
+ strcpy(zTemp, zFilename);
+ for(i=0; i<10; i++){
+ zTemp[nFilename] = '-';
+ randomName((unsigned char*)&zTemp[nFilename+1]);
+ if( !sqliteOsFileExists(zTemp) ) break;
+ }
+ if( i>=10 ){
+ sqliteSetString(pzErrMsg, "unable to create a temporary database file "
+ "in the same directory as the original database", (char*)0);
+ goto end_of_vacuum;
+ }
+
+
+ dbNew = sqlite_open(zTemp, 0, &zErrMsg);
+ if( dbNew==0 ){
+ sqliteSetString(pzErrMsg, "unable to open a temporary database at ",
+ zTemp, " - ", zErrMsg, (char*)0);
+ goto end_of_vacuum;
+ }
+ if( (rc = execsql(pzErrMsg, db, "BEGIN"))!=0 ) goto end_of_vacuum;
+ if( (rc = execsql(pzErrMsg, dbNew, "PRAGMA synchronous=off; BEGIN"))!=0 ){
+ goto end_of_vacuum;
+ }
+
+ sVac.dbOld = db;
+ sVac.dbNew = dbNew;
+ sVac.pzErrMsg = pzErrMsg;
+ if( rc==SQLITE_OK ){
+ rc = sqlite_exec(db,
+ "SELECT type, name, sql FROM sqlite_master "
+ "WHERE sql NOT NULL AND type!='view' "
+ "UNION ALL "
+ "SELECT type, name, sql FROM sqlite_master "
+ "WHERE sql NOT NULL AND type=='view'",
+ vacuumCallback1, &sVac, &zErrMsg);
+ }
+ if( rc==SQLITE_OK ){
+ int meta1[SQLITE_N_BTREE_META];
+ int meta2[SQLITE_N_BTREE_META];
+ sqliteBtreeGetMeta(db->aDb[0].pBt, meta1);
+ sqliteBtreeGetMeta(dbNew->aDb[0].pBt, meta2);
+ meta2[1] = meta1[1]+1;
+ meta2[3] = meta1[3];
+ meta2[4] = meta1[4];
+ meta2[6] = meta1[6];
+ rc = sqliteBtreeUpdateMeta(dbNew->aDb[0].pBt, meta2);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqliteBtreeCopyFile(db->aDb[0].pBt, dbNew->aDb[0].pBt);
+ sqlite_exec(db, "COMMIT", 0, 0, 0);
+ sqliteResetInternalSchema(db, 0);
+ }
+
+end_of_vacuum:
+ if( rc && zErrMsg!=0 ){
+ sqliteSetString(pzErrMsg, "unable to vacuum database - ",
+ zErrMsg, (char*)0);
+ }
+ sqlite_exec(db, "ROLLBACK", 0, 0, 0);
+ if( (dbNew && (dbNew->flags & SQLITE_Interrupt))
+ || (db->flags & SQLITE_Interrupt) ){
+ rc = SQLITE_INTERRUPT;
+ }
+ if( dbNew ) sqlite_close(dbNew);
+ sqliteOsDelete(zTemp);
+ sqliteFree(zTemp);
+ sqliteFree(sVac.s1.z);
+ sqliteFree(sVac.s2.z);
+ if( zErrMsg ) sqlite_freemem(zErrMsg);
+ if( rc==SQLITE_ABORT && sVac.rc!=SQLITE_INTERRUPT ) sVac.rc = SQLITE_ERROR;
+ return sVac.rc;
+#endif
+}
diff --git a/src/libs/sqlite2/vdbe.c b/src/libs/sqlite2/vdbe.c
new file mode 100644
index 00000000..1838691c
--- /dev/null
+++ b/src/libs/sqlite2/vdbe.c
@@ -0,0 +1,4921 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** The code in this file implements execution method of the
+** Virtual Database Engine (VDBE). A separate file ("vdbeaux.c")
+** handles housekeeping details such as creating and deleting
+** VDBE instances. This file is solely interested in executing
+** the VDBE program.
+**
+** In the external interface, an "sqlite_vm*" is an opaque pointer
+** to a VDBE.
+**
+** The SQL parser generates a program which is then executed by
+** the VDBE to do the work of the SQL statement. VDBE programs are
+** similar in form to assembly language. The program consists of
+** a linear sequence of operations. Each operation has an opcode
+** and 3 operands. Operands P1 and P2 are integers. Operand P3
+** is a null-terminated string. The P2 operand must be non-negative.
+** Opcodes will typically ignore one or more operands. Many opcodes
+** ignore all three operands.
+**
+** Computation results are stored on a stack. Each entry on the
+** stack is either an integer, a null-terminated string, a floating point
+** number, or the SQL "NULL" value. An inplicit conversion from one
+** type to the other occurs as necessary.
+**
+** Most of the code in this file is taken up by the sqliteVdbeExec()
+** function which does the work of interpreting a VDBE program.
+** But other routines are also provided to help in building up
+** a program instruction by instruction.
+**
+** Various scripts scan this source file in order to generate HTML
+** documentation, headers files, or other derived files. The formatting
+** of the code in this file is, therefore, important. See other comments
+** in this file for details. If in doubt, do not deviate from existing
+** commenting and indentation practices when changing or adding code.
+**
+** $Id: vdbe.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+#include "vdbeInt.h"
+
+/*
+** The following global variable is incremented every time a cursor
+** moves, either by the OP_MoveTo or the OP_Next opcode. The test
+** procedures use this information to make sure that indices are
+** working correctly. This variable has no function other than to
+** help verify the correct operation of the library.
+*/
+int sqlite_search_count = 0;
+
+/*
+** When this global variable is positive, it gets decremented once before
+** each instruction in the VDBE. When reaches zero, the SQLITE_Interrupt
+** of the db.flags field is set in order to simulate an interrupt.
+**
+** This facility is used for testing purposes only. It does not function
+** in an ordinary build.
+*/
+int sqlite_interrupt_count = 0;
+
+/*
+** Advance the virtual machine to the next output row.
+**
+** The return vale will be either SQLITE_BUSY, SQLITE_DONE,
+** SQLITE_ROW, SQLITE_ERROR, or SQLITE_MISUSE.
+**
+** SQLITE_BUSY means that the virtual machine attempted to open
+** a locked database and there is no busy callback registered.
+** Call sqlite_step() again to retry the open. *pN is set to 0
+** and *pazColName and *pazValue are both set to NULL.
+**
+** SQLITE_DONE means that the virtual machine has finished
+** executing. sqlite_step() should not be called again on this
+** virtual machine. *pN and *pazColName are set appropriately
+** but *pazValue is set to NULL.
+**
+** SQLITE_ROW means that the virtual machine has generated another
+** row of the result set. *pN is set to the number of columns in
+** the row. *pazColName is set to the names of the columns followed
+** by the column datatypes. *pazValue is set to the values of each
+** column in the row. The value of the i-th column is (*pazValue)[i].
+** The name of the i-th column is (*pazColName)[i] and the datatype
+** of the i-th column is (*pazColName)[i+*pN].
+**
+** SQLITE_ERROR means that a run-time error (such as a constraint
+** violation) has occurred. The details of the error will be returned
+** by the next call to sqlite_finalize(). sqlite_step() should not
+** be called again on the VM.
+**
+** SQLITE_MISUSE means that the this routine was called inappropriately.
+** Perhaps it was called on a virtual machine that had already been
+** finalized or on one that had previously returned SQLITE_ERROR or
+** SQLITE_DONE. Or it could be the case the the same database connection
+** is being used simulataneously by two or more threads.
+*/
+int sqlite_step(
+ sqlite_vm *pVm, /* The virtual machine to execute */
+ int *pN, /* OUT: Number of columns in result */
+ const char ***pazValue, /* OUT: Column data */
+ const char ***pazColName /* OUT: Column names and datatypes */
+){
+ Vdbe *p = (Vdbe*)pVm;
+ sqlite *db;
+ int rc;
+
+ if( p->magic!=VDBE_MAGIC_RUN ){
+ return SQLITE_MISUSE;
+ }
+ db = p->db;
+ if( sqliteSafetyOn(db) ){
+ p->rc = SQLITE_MISUSE;
+ return SQLITE_MISUSE;
+ }
+ if( p->explain ){
+ rc = sqliteVdbeList(p);
+ }else{
+ rc = sqliteVdbeExec(p);
+ }
+ if( rc==SQLITE_DONE || rc==SQLITE_ROW ){
+ if( pazColName ) *pazColName = (const char**)p->azColName;
+ if( pN ) *pN = p->nResColumn;
+ }else{
+ if( pazColName) *pazColName = 0;
+ if( pN ) *pN = 0;
+ }
+ if( pazValue ){
+ if( rc==SQLITE_ROW ){
+ *pazValue = (const char**)p->azResColumn;
+ }else{
+ *pazValue = 0;
+ }
+ }
+ if( sqliteSafetyOff(db) ){
+ return SQLITE_MISUSE;
+ }
+ return rc;
+}
+
+/*
+** Insert a new aggregate element and make it the element that
+** has focus.
+**
+** Return 0 on success and 1 if memory is exhausted.
+*/
+static int AggInsert(Agg *p, char *zKey, int nKey){
+ AggElem *pElem, *pOld;
+ int i;
+ Mem *pMem;
+ pElem = sqliteMalloc( sizeof(AggElem) + nKey +
+ (p->nMem-1)*sizeof(pElem->aMem[0]) );
+ if( pElem==0 ) return 1;
+ pElem->zKey = (char*)&pElem->aMem[p->nMem];
+ memcpy(pElem->zKey, zKey, nKey);
+ pElem->nKey = nKey;
+ pOld = sqliteHashInsert(&p->hash, pElem->zKey, pElem->nKey, pElem);
+ if( pOld!=0 ){
+ assert( pOld==pElem ); /* Malloc failed on insert */
+ sqliteFree(pOld);
+ return 0;
+ }
+ for(i=0, pMem=pElem->aMem; i<p->nMem; i++, pMem++){
+ pMem->flags = MEM_Null;
+ }
+ p->pCurrent = pElem;
+ return 0;
+}
+
+/*
+** Get the AggElem currently in focus
+*/
+#define AggInFocus(P) ((P).pCurrent ? (P).pCurrent : _AggInFocus(&(P)))
+static AggElem *_AggInFocus(Agg *p){
+ HashElem *pElem = sqliteHashFirst(&p->hash);
+ if( pElem==0 ){
+ AggInsert(p,"",1);
+ pElem = sqliteHashFirst(&p->hash);
+ }
+ return pElem ? sqliteHashData(pElem) : 0;
+}
+
+/*
+** Convert the given stack entity into a string if it isn't one
+** already.
+*/
+#define Stringify(P) if(((P)->flags & MEM_Str)==0){hardStringify(P);}
+static int hardStringify(Mem *pStack){
+ int fg = pStack->flags;
+ if( fg & MEM_Real ){
+ sqlite_snprintf(sizeof(pStack->zShort),pStack->zShort,"%.15g",pStack->r);
+ }else if( fg & MEM_Int ){
+ sqlite_snprintf(sizeof(pStack->zShort),pStack->zShort,"%d",pStack->i);
+ }else{
+ pStack->zShort[0] = 0;
+ }
+ pStack->z = pStack->zShort;
+ pStack->n = strlen(pStack->zShort)+1;
+ pStack->flags = MEM_Str | MEM_Short;
+ return 0;
+}
+
+/*
+** Convert the given stack entity into a string that has been obtained
+** from sqliteMalloc(). This is different from Stringify() above in that
+** Stringify() will use the NBFS bytes of static string space if the string
+** will fit but this routine always mallocs for space.
+** Return non-zero if we run out of memory.
+*/
+#define Dynamicify(P) (((P)->flags & MEM_Dyn)==0 ? hardDynamicify(P):0)
+static int hardDynamicify(Mem *pStack){
+ int fg = pStack->flags;
+ char *z;
+ if( (fg & MEM_Str)==0 ){
+ hardStringify(pStack);
+ }
+ assert( (fg & MEM_Dyn)==0 );
+ z = sqliteMallocRaw( pStack->n );
+ if( z==0 ) return 1;
+ memcpy(z, pStack->z, pStack->n);
+ pStack->z = z;
+ pStack->flags |= MEM_Dyn;
+ return 0;
+}
+
+/*
+** An ephemeral string value (signified by the MEM_Ephem flag) contains
+** a pointer to a dynamically allocated string where some other entity
+** is responsible for deallocating that string. Because the stack entry
+** does not control the string, it might be deleted without the stack
+** entry knowing it.
+**
+** This routine converts an ephemeral string into a dynamically allocated
+** string that the stack entry itself controls. In other words, it
+** converts an MEM_Ephem string into an MEM_Dyn string.
+*/
+#define Deephemeralize(P) \
+ if( ((P)->flags&MEM_Ephem)!=0 && hardDeephem(P) ){ goto no_mem;}
+static int hardDeephem(Mem *pStack){
+ char *z;
+ assert( (pStack->flags & MEM_Ephem)!=0 );
+ z = sqliteMallocRaw( pStack->n );
+ if( z==0 ) return 1;
+ memcpy(z, pStack->z, pStack->n);
+ pStack->z = z;
+ pStack->flags &= ~MEM_Ephem;
+ pStack->flags |= MEM_Dyn;
+ return 0;
+}
+
+/*
+** Release the memory associated with the given stack level. This
+** leaves the Mem.flags field in an inconsistent state.
+*/
+#define Release(P) if((P)->flags&MEM_Dyn){ sqliteFree((P)->z); }
+
+/*
+** Pop the stack N times.
+*/
+static void popStack(Mem **ppTos, int N){
+ Mem *pTos = *ppTos;
+ while( N>0 ){
+ N--;
+ Release(pTos);
+ pTos--;
+ }
+ *ppTos = pTos;
+}
+
+/*
+** Return TRUE if zNum is a 32-bit signed integer and write
+** the value of the integer into *pNum. If zNum is not an integer
+** or is an integer that is too large to be expressed with just 32
+** bits, then return false.
+**
+** Under Linux (RedHat 7.2) this routine is much faster than atoi()
+** for converting strings into integers.
+*/
+static int toInt(const char *zNum, int *pNum){
+ int v = 0;
+ int neg;
+ int i, c;
+ if( *zNum=='-' ){
+ neg = 1;
+ zNum++;
+ }else if( *zNum=='+' ){
+ neg = 0;
+ zNum++;
+ }else{
+ neg = 0;
+ }
+ for(i=0; (c=zNum[i])>='0' && c<='9'; i++){
+ v = v*10 + c - '0';
+ }
+ *pNum = neg ? -v : v;
+ return c==0 && i>0 && (i<10 || (i==10 && memcmp(zNum,"2147483647",10)<=0));
+}
+
+/*
+** Convert the given stack entity into a integer if it isn't one
+** already.
+**
+** Any prior string or real representation is invalidated.
+** NULLs are converted into 0.
+*/
+#define Integerify(P) if(((P)->flags&MEM_Int)==0){ hardIntegerify(P); }
+static void hardIntegerify(Mem *pStack){
+ if( pStack->flags & MEM_Real ){
+ pStack->i = (int)pStack->r;
+ Release(pStack);
+ }else if( pStack->flags & MEM_Str ){
+ toInt(pStack->z, &pStack->i);
+ Release(pStack);
+ }else{
+ pStack->i = 0;
+ }
+ pStack->flags = MEM_Int;
+}
+
+/*
+** Get a valid Real representation for the given stack element.
+**
+** Any prior string or integer representation is retained.
+** NULLs are converted into 0.0.
+*/
+#define Realify(P) if(((P)->flags&MEM_Real)==0){ hardRealify(P); }
+static void hardRealify(Mem *pStack){
+ if( pStack->flags & MEM_Str ){
+ pStack->r = sqliteAtoF(pStack->z, 0);
+ }else if( pStack->flags & MEM_Int ){
+ pStack->r = pStack->i;
+ }else{
+ pStack->r = 0.0;
+ }
+ pStack->flags |= MEM_Real;
+}
+
+/*
+** The parameters are pointers to the head of two sorted lists
+** of Sorter structures. Merge these two lists together and return
+** a single sorted list. This routine forms the core of the merge-sort
+** algorithm.
+**
+** In the case of a tie, left sorts in front of right.
+*/
+static Sorter *Merge(Sorter *pLeft, Sorter *pRight){
+ Sorter sHead;
+ Sorter *pTail;
+ pTail = &sHead;
+ pTail->pNext = 0;
+ while( pLeft && pRight ){
+ int c = sqliteSortCompare(pLeft->zKey, pRight->zKey);
+ if( c<=0 ){
+ pTail->pNext = pLeft;
+ pLeft = pLeft->pNext;
+ }else{
+ pTail->pNext = pRight;
+ pRight = pRight->pNext;
+ }
+ pTail = pTail->pNext;
+ }
+ if( pLeft ){
+ pTail->pNext = pLeft;
+ }else if( pRight ){
+ pTail->pNext = pRight;
+ }
+ return sHead.pNext;
+}
+
+/*
+** The following routine works like a replacement for the standard
+** library routine fgets(). The difference is in how end-of-line (EOL)
+** is handled. Standard fgets() uses LF for EOL under unix, CRLF
+** under windows, and CR under mac. This routine accepts any of these
+** character sequences as an EOL mark. The EOL mark is replaced by
+** a single LF character in zBuf.
+*/
+static char *vdbe_fgets(char *zBuf, int nBuf, FILE *in){
+ int i, c;
+ for(i=0; i<nBuf-1 && (c=getc(in))!=EOF; i++){
+ zBuf[i] = c;
+ if( c=='\r' || c=='\n' ){
+ if( c=='\r' ){
+ zBuf[i] = '\n';
+ c = getc(in);
+ if( c!=EOF && c!='\n' ) ungetc(c, in);
+ }
+ i++;
+ break;
+ }
+ }
+ zBuf[i] = 0;
+ return i>0 ? zBuf : 0;
+}
+
+/*
+** Make sure there is space in the Vdbe structure to hold at least
+** mxCursor cursors. If there is not currently enough space, then
+** allocate more.
+**
+** If a memory allocation error occurs, return 1. Return 0 if
+** everything works.
+*/
+static int expandCursorArraySize(Vdbe *p, int mxCursor){
+ if( mxCursor>=p->nCursor ){
+ Cursor *aCsr = sqliteRealloc( p->aCsr, (mxCursor+1)*sizeof(Cursor) );
+ if( aCsr==0 ) return 1;
+ p->aCsr = aCsr;
+ memset(&p->aCsr[p->nCursor], 0, sizeof(Cursor)*(mxCursor+1-p->nCursor));
+ p->nCursor = mxCursor+1;
+ }
+ return 0;
+}
+
+#ifdef VDBE_PROFILE
+/*
+** The following routine only works on pentium-class processors.
+** It uses the RDTSC opcode to read cycle count value out of the
+** processor and returns that value. This can be used for high-res
+** profiling.
+*/
+__inline__ unsigned long long int hwtime(void){
+ unsigned long long int x;
+ __asm__("rdtsc\n\t"
+ "mov %%edx, %%ecx\n\t"
+ :"=A" (x));
+ return x;
+}
+#endif
+
+/*
+** The CHECK_FOR_INTERRUPT macro defined here looks to see if the
+** sqlite_interrupt() routine has been called. If it has been, then
+** processing of the VDBE program is interrupted.
+**
+** This macro added to every instruction that does a jump in order to
+** implement a loop. This test used to be on every single instruction,
+** but that meant we more testing that we needed. By only testing the
+** flag on jump instructions, we get a (small) speed improvement.
+*/
+#define CHECK_FOR_INTERRUPT \
+ if( db->flags & SQLITE_Interrupt ) goto abort_due_to_interrupt;
+
+
+/*
+** Execute as much of a VDBE program as we can then return.
+**
+** sqliteVdbeMakeReady() must be called before this routine in order to
+** close the program with a final OP_Halt and to set up the callbacks
+** and the error message pointer.
+**
+** Whenever a row or result data is available, this routine will either
+** invoke the result callback (if there is one) or return with
+** SQLITE_ROW.
+**
+** If an attempt is made to open a locked database, then this routine
+** will either invoke the busy callback (if there is one) or it will
+** return SQLITE_BUSY.
+**
+** If an error occurs, an error message is written to memory obtained
+** from sqliteMalloc() and p->zErrMsg is made to point to that memory.
+** The error code is stored in p->rc and this routine returns SQLITE_ERROR.
+**
+** If the callback ever returns non-zero, then the program exits
+** immediately. There will be no error message but the p->rc field is
+** set to SQLITE_ABORT and this routine will return SQLITE_ERROR.
+**
+** A memory allocation error causes p->rc to be set to SQLITE_NOMEM and this
+** routine to return SQLITE_ERROR.
+**
+** Other fatal errors return SQLITE_ERROR.
+**
+** After this routine has finished, sqliteVdbeFinalize() should be
+** used to clean up the mess that was left behind.
+*/
+int sqliteVdbeExec(
+ Vdbe *p /* The VDBE */
+){
+ int pc; /* The program counter */
+ Op *pOp; /* Current operation */
+ int rc = SQLITE_OK; /* Value to return */
+ sqlite *db = p->db; /* The database */
+ Mem *pTos; /* Top entry in the operand stack */
+ char zBuf[100]; /* Space to sprintf() an integer */
+#ifdef VDBE_PROFILE
+ unsigned long long start; /* CPU clock count at start of opcode */
+ int origPc; /* Program counter at start of opcode */
+#endif
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ int nProgressOps = 0; /* Opcodes executed since progress callback. */
+#endif
+
+ if( p->magic!=VDBE_MAGIC_RUN ) return SQLITE_MISUSE;
+ assert( db->magic==SQLITE_MAGIC_BUSY );
+ assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY );
+ p->rc = SQLITE_OK;
+ assert( p->explain==0 );
+ if( sqlite_malloc_failed ) goto no_mem;
+ pTos = p->pTos;
+ if( p->popStack ){
+ popStack(&pTos, p->popStack);
+ p->popStack = 0;
+ }
+ CHECK_FOR_INTERRUPT;
+ for(pc=p->pc; rc==SQLITE_OK; pc++){
+ assert( pc>=0 && pc<p->nOp );
+ assert( pTos<=&p->aStack[pc] );
+#ifdef VDBE_PROFILE
+ origPc = pc;
+ start = hwtime();
+#endif
+ pOp = &p->aOp[pc];
+
+ /* Only allow tracing if NDEBUG is not defined.
+ */
+#ifndef NDEBUG
+ if( p->trace ){
+ sqliteVdbePrintOp(p->trace, pc, pOp);
+ }
+#endif
+
+ /* Check to see if we need to simulate an interrupt. This only happens
+ ** if we have a special test build.
+ */
+#ifdef SQLITE_TEST
+ if( sqlite_interrupt_count>0 ){
+ sqlite_interrupt_count--;
+ if( sqlite_interrupt_count==0 ){
+ sqlite_interrupt(db);
+ }
+ }
+#endif
+
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ /* Call the progress callback if it is configured and the required number
+ ** of VDBE ops have been executed (either since this invocation of
+ ** sqliteVdbeExec() or since last time the progress callback was called).
+ ** If the progress callback returns non-zero, exit the virtual machine with
+ ** a return code SQLITE_ABORT.
+ */
+ if( db->xProgress ){
+ if( db->nProgressOps==nProgressOps ){
+ if( db->xProgress(db->pProgressArg)!=0 ){
+ rc = SQLITE_ABORT;
+ continue; /* skip to the next iteration of the for loop */
+ }
+ nProgressOps = 0;
+ }
+ nProgressOps++;
+ }
+#endif
+
+ switch( pOp->opcode ){
+
+/*****************************************************************************
+** What follows is a massive switch statement where each case implements a
+** separate instruction in the virtual machine. If we follow the usual
+** indentation conventions, each case should be indented by 6 spaces. But
+** that is a lot of wasted space on the left margin. So the code within
+** the switch statement will break with convention and be flush-left. Another
+** big comment (similar to this one) will mark the point in the code where
+** we transition back to normal indentation.
+**
+** The formatting of each case is important. The makefile for SQLite
+** generates two C files "opcodes.h" and "opcodes.c" by scanning this
+** file looking for lines that begin with "case OP_". The opcodes.h files
+** will be filled with #defines that give unique integer values to each
+** opcode and the opcodes.c file is filled with an array of strings where
+** each string is the symbolic name for the corresponding opcode.
+**
+** Documentation about VDBE opcodes is generated by scanning this file
+** for lines of that contain "Opcode:". That line and all subsequent
+** comment lines are used in the generation of the opcode.html documentation
+** file.
+**
+** SUMMARY:
+**
+** Formatting is important to scripts that scan this file.
+** Do not deviate from the formatting style currently in use.
+**
+*****************************************************************************/
+
+/* Opcode: Goto * P2 *
+**
+** An unconditional jump to address P2.
+** The next instruction executed will be
+** the one at index P2 from the beginning of
+** the program.
+*/
+case OP_Goto: {
+ CHECK_FOR_INTERRUPT;
+ pc = pOp->p2 - 1;
+ break;
+}
+
+/* Opcode: Gosub * P2 *
+**
+** Push the current address plus 1 onto the return address stack
+** and then jump to address P2.
+**
+** The return address stack is of limited depth. If too many
+** OP_Gosub operations occur without intervening OP_Returns, then
+** the return address stack will fill up and processing will abort
+** with a fatal error.
+*/
+case OP_Gosub: {
+ if( p->returnDepth>=sizeof(p->returnStack)/sizeof(p->returnStack[0]) ){
+ sqliteSetString(&p->zErrMsg, "return address stack overflow", (char*)0);
+ p->rc = SQLITE_INTERNAL;
+ return SQLITE_ERROR;
+ }
+ p->returnStack[p->returnDepth++] = pc+1;
+ pc = pOp->p2 - 1;
+ break;
+}
+
+/* Opcode: Return * * *
+**
+** Jump immediately to the next instruction after the last unreturned
+** OP_Gosub. If an OP_Return has occurred for all OP_Gosubs, then
+** processing aborts with a fatal error.
+*/
+case OP_Return: {
+ if( p->returnDepth<=0 ){
+ sqliteSetString(&p->zErrMsg, "return address stack underflow", (char*)0);
+ p->rc = SQLITE_INTERNAL;
+ return SQLITE_ERROR;
+ }
+ p->returnDepth--;
+ pc = p->returnStack[p->returnDepth] - 1;
+ break;
+}
+
+/* Opcode: Halt P1 P2 *
+**
+** Exit immediately. All open cursors, Lists, Sorts, etc are closed
+** automatically.
+**
+** P1 is the result code returned by sqlite_exec(). For a normal
+** halt, this should be SQLITE_OK (0). For errors, it can be some
+** other value. If P1!=0 then P2 will determine whether or not to
+** rollback the current transaction. Do not rollback if P2==OE_Fail.
+** Do the rollback if P2==OE_Rollback. If P2==OE_Abort, then back
+** out all changes that have occurred during this execution of the
+** VDBE, but do not rollback the transaction.
+**
+** There is an implied "Halt 0 0 0" instruction inserted at the very end of
+** every program. So a jump past the last instruction of the program
+** is the same as executing Halt.
+*/
+case OP_Halt: {
+ p->magic = VDBE_MAGIC_HALT;
+ p->pTos = pTos;
+ if( pOp->p1!=SQLITE_OK ){
+ p->rc = pOp->p1;
+ p->errorAction = pOp->p2;
+ if( pOp->p3 ){
+ sqliteSetString(&p->zErrMsg, pOp->p3, (char*)0);
+ }
+ return SQLITE_ERROR;
+ }else{
+ p->rc = SQLITE_OK;
+ return SQLITE_DONE;
+ }
+}
+
+/* Opcode: Integer P1 * P3
+**
+** The integer value P1 is pushed onto the stack. If P3 is not zero
+** then it is assumed to be a string representation of the same integer.
+*/
+case OP_Integer: {
+ pTos++;
+ pTos->i = pOp->p1;
+ pTos->flags = MEM_Int;
+ if( pOp->p3 ){
+ pTos->z = pOp->p3;
+ pTos->flags |= MEM_Str | MEM_Static;
+ pTos->n = strlen(pOp->p3)+1;
+ }
+ break;
+}
+
+/* Opcode: String * * P3
+**
+** The string value P3 is pushed onto the stack. If P3==0 then a
+** NULL is pushed onto the stack.
+*/
+case OP_String: {
+ char *z = pOp->p3;
+ pTos++;
+ if( z==0 ){
+ pTos->flags = MEM_Null;
+ }else{
+ pTos->z = z;
+ pTos->n = strlen(z) + 1;
+ pTos->flags = MEM_Str | MEM_Static;
+ }
+ break;
+}
+
+/* Opcode: Variable P1 * *
+**
+** Push the value of variable P1 onto the stack. A variable is
+** an unknown in the original SQL string as handed to sqlite_compile().
+** Any occurance of the '?' character in the original SQL is considered
+** a variable. Variables in the SQL string are number from left to
+** right beginning with 1. The values of variables are set using the
+** sqlite_bind() API.
+*/
+case OP_Variable: {
+ int j = pOp->p1 - 1;
+ pTos++;
+ if( j>=0 && j<p->nVar && p->azVar[j]!=0 ){
+ pTos->z = p->azVar[j];
+ pTos->n = p->anVar[j];
+ pTos->flags = MEM_Str | MEM_Static;
+ }else{
+ pTos->flags = MEM_Null;
+ }
+ break;
+}
+
+/* Opcode: Pop P1 * *
+**
+** P1 elements are popped off of the top of stack and discarded.
+*/
+case OP_Pop: {
+ assert( pOp->p1>=0 );
+ popStack(&pTos, pOp->p1);
+ assert( pTos>=&p->aStack[-1] );
+ break;
+}
+
+/* Opcode: Dup P1 P2 *
+**
+** A copy of the P1-th element of the stack
+** is made and pushed onto the top of the stack.
+** The top of the stack is element 0. So the
+** instruction "Dup 0 0 0" will make a copy of the
+** top of the stack.
+**
+** If the content of the P1-th element is a dynamically
+** allocated string, then a new copy of that string
+** is made if P2==0. If P2!=0, then just a pointer
+** to the string is copied.
+**
+** Also see the Pull instruction.
+*/
+case OP_Dup: {
+ Mem *pFrom = &pTos[-pOp->p1];
+ assert( pFrom<=pTos && pFrom>=p->aStack );
+ pTos++;
+ memcpy(pTos, pFrom, sizeof(*pFrom)-NBFS);
+ if( pTos->flags & MEM_Str ){
+ if( pOp->p2 && (pTos->flags & (MEM_Dyn|MEM_Ephem)) ){
+ pTos->flags &= ~MEM_Dyn;
+ pTos->flags |= MEM_Ephem;
+ }else if( pTos->flags & MEM_Short ){
+ memcpy(pTos->zShort, pFrom->zShort, pTos->n);
+ pTos->z = pTos->zShort;
+ }else if( (pTos->flags & MEM_Static)==0 ){
+ pTos->z = sqliteMallocRaw(pFrom->n);
+ if( sqlite_malloc_failed ) goto no_mem;
+ memcpy(pTos->z, pFrom->z, pFrom->n);
+ pTos->flags &= ~(MEM_Static|MEM_Ephem|MEM_Short);
+ pTos->flags |= MEM_Dyn;
+ }
+ }
+ break;
+}
+
+/* Opcode: Pull P1 * *
+**
+** The P1-th element is removed from its current location on
+** the stack and pushed back on top of the stack. The
+** top of the stack is element 0, so "Pull 0 0 0" is
+** a no-op. "Pull 1 0 0" swaps the top two elements of
+** the stack.
+**
+** See also the Dup instruction.
+*/
+case OP_Pull: {
+ Mem *pFrom = &pTos[-pOp->p1];
+ int i;
+ Mem ts;
+
+ ts = *pFrom;
+ Deephemeralize(pTos);
+ for(i=0; i<pOp->p1; i++, pFrom++){
+ Deephemeralize(&pFrom[1]);
+ *pFrom = pFrom[1];
+ assert( (pFrom->flags & MEM_Ephem)==0 );
+ if( pFrom->flags & MEM_Short ){
+ assert( pFrom->flags & MEM_Str );
+ assert( pFrom->z==pFrom[1].zShort );
+ pFrom->z = pFrom->zShort;
+ }
+ }
+ *pTos = ts;
+ if( pTos->flags & MEM_Short ){
+ assert( pTos->flags & MEM_Str );
+ assert( pTos->z==pTos[-pOp->p1].zShort );
+ pTos->z = pTos->zShort;
+ }
+ break;
+}
+
+/* Opcode: Push P1 * *
+**
+** Overwrite the value of the P1-th element down on the
+** stack (P1==0 is the top of the stack) with the value
+** of the top of the stack. Then pop the top of the stack.
+*/
+case OP_Push: {
+ Mem *pTo = &pTos[-pOp->p1];
+
+ assert( pTo>=p->aStack );
+ Deephemeralize(pTos);
+ Release(pTo);
+ *pTo = *pTos;
+ if( pTo->flags & MEM_Short ){
+ assert( pTo->z==pTos->zShort );
+ pTo->z = pTo->zShort;
+ }
+ pTos--;
+ break;
+}
+
+
+/* Opcode: ColumnName P1 P2 P3
+**
+** P3 becomes the P1-th column name (first is 0). An array of pointers
+** to all column names is passed as the 4th parameter to the callback.
+** If P2==1 then this is the last column in the result set and thus the
+** number of columns in the result set will be P1. There must be at least
+** one OP_ColumnName with a P2==1 before invoking OP_Callback and the
+** number of columns specified in OP_Callback must one more than the P1
+** value of the OP_ColumnName that has P2==1.
+*/
+case OP_ColumnName: {
+ assert( pOp->p1>=0 && pOp->p1<p->nOp );
+ p->azColName[pOp->p1] = pOp->p3;
+ p->nCallback = 0;
+ if( pOp->p2 ) p->nResColumn = pOp->p1+1;
+ break;
+}
+
+/* Opcode: Callback P1 * *
+**
+** Pop P1 values off the stack and form them into an array. Then
+** invoke the callback function using the newly formed array as the
+** 3rd parameter.
+*/
+case OP_Callback: {
+ int i;
+ char **azArgv = p->zArgv;
+ Mem *pCol;
+
+ pCol = &pTos[1-pOp->p1];
+ assert( pCol>=p->aStack );
+ for(i=0; i<pOp->p1; i++, pCol++){
+ if( pCol->flags & MEM_Null ){
+ azArgv[i] = 0;
+ }else{
+ Stringify(pCol);
+ azArgv[i] = pCol->z;
+ }
+ }
+ azArgv[i] = 0;
+ p->nCallback++;
+ p->azResColumn = azArgv;
+ assert( p->nResColumn==pOp->p1 );
+ p->popStack = pOp->p1;
+ p->pc = pc + 1;
+ p->pTos = pTos;
+ return SQLITE_ROW;
+}
+
+/* Opcode: Concat P1 P2 P3
+**
+** Look at the first P1 elements of the stack. Append them all
+** together with the lowest element first. Use P3 as a separator.
+** Put the result on the top of the stack. The original P1 elements
+** are popped from the stack if P2==0 and retained if P2==1. If
+** any element of the stack is NULL, then the result is NULL.
+**
+** If P3 is NULL, then use no separator. When P1==1, this routine
+** makes a copy of the top stack element into memory obtained
+** from sqliteMalloc().
+*/
+case OP_Concat: {
+ char *zNew;
+ int nByte;
+ int nField;
+ int i, j;
+ char *zSep;
+ int nSep;
+ Mem *pTerm;
+
+ nField = pOp->p1;
+ zSep = pOp->p3;
+ if( zSep==0 ) zSep = "";
+ nSep = strlen(zSep);
+ assert( &pTos[1-nField] >= p->aStack );
+ nByte = 1 - nSep;
+ pTerm = &pTos[1-nField];
+ for(i=0; i<nField; i++, pTerm++){
+ if( pTerm->flags & MEM_Null ){
+ nByte = -1;
+ break;
+ }else{
+ Stringify(pTerm);
+ nByte += pTerm->n - 1 + nSep;
+ }
+ }
+ if( nByte<0 ){
+ if( pOp->p2==0 ){
+ popStack(&pTos, nField);
+ }
+ pTos++;
+ pTos->flags = MEM_Null;
+ break;
+ }
+ zNew = sqliteMallocRaw( nByte );
+ if( zNew==0 ) goto no_mem;
+ j = 0;
+ pTerm = &pTos[1-nField];
+ for(i=j=0; i<nField; i++, pTerm++){
+ assert( pTerm->flags & MEM_Str );
+ memcpy(&zNew[j], pTerm->z, pTerm->n-1);
+ j += pTerm->n-1;
+ if( nSep>0 && i<nField-1 ){
+ memcpy(&zNew[j], zSep, nSep);
+ j += nSep;
+ }
+ }
+ zNew[j] = 0;
+ if( pOp->p2==0 ){
+ popStack(&pTos, nField);
+ }
+ pTos++;
+ pTos->n = nByte;
+ pTos->flags = MEM_Str|MEM_Dyn;
+ pTos->z = zNew;
+ break;
+}
+
+/* Opcode: Add * * *
+**
+** Pop the top two elements from the stack, add them together,
+** and push the result back onto the stack. If either element
+** is a string then it is converted to a double using the atof()
+** function before the addition.
+** If either operand is NULL, the result is NULL.
+*/
+/* Opcode: Multiply * * *
+**
+** Pop the top two elements from the stack, multiply them together,
+** and push the result back onto the stack. If either element
+** is a string then it is converted to a double using the atof()
+** function before the multiplication.
+** If either operand is NULL, the result is NULL.
+*/
+/* Opcode: Subtract * * *
+**
+** Pop the top two elements from the stack, subtract the
+** first (what was on top of the stack) from the second (the
+** next on stack)
+** and push the result back onto the stack. If either element
+** is a string then it is converted to a double using the atof()
+** function before the subtraction.
+** If either operand is NULL, the result is NULL.
+*/
+/* Opcode: Divide * * *
+**
+** Pop the top two elements from the stack, divide the
+** first (what was on top of the stack) from the second (the
+** next on stack)
+** and push the result back onto the stack. If either element
+** is a string then it is converted to a double using the atof()
+** function before the division. Division by zero returns NULL.
+** If either operand is NULL, the result is NULL.
+*/
+/* Opcode: Remainder * * *
+**
+** Pop the top two elements from the stack, divide the
+** first (what was on top of the stack) from the second (the
+** next on stack)
+** and push the remainder after division onto the stack. If either element
+** is a string then it is converted to a double using the atof()
+** function before the division. Division by zero returns NULL.
+** If either operand is NULL, the result is NULL.
+*/
+case OP_Add:
+case OP_Subtract:
+case OP_Multiply:
+case OP_Divide:
+case OP_Remainder: {
+ Mem *pNos = &pTos[-1];
+ assert( pNos>=p->aStack );
+ if( ((pTos->flags | pNos->flags) & MEM_Null)!=0 ){
+ Release(pTos);
+ pTos--;
+ Release(pTos);
+ pTos->flags = MEM_Null;
+ }else if( (pTos->flags & pNos->flags & MEM_Int)==MEM_Int ){
+ int a, b;
+ a = pTos->i;
+ b = pNos->i;
+ switch( pOp->opcode ){
+ case OP_Add: b += a; break;
+ case OP_Subtract: b -= a; break;
+ case OP_Multiply: b *= a; break;
+ case OP_Divide: {
+ if( a==0 ) goto divide_by_zero;
+ b /= a;
+ break;
+ }
+ default: {
+ if( a==0 ) goto divide_by_zero;
+ b %= a;
+ break;
+ }
+ }
+ Release(pTos);
+ pTos--;
+ Release(pTos);
+ pTos->i = b;
+ pTos->flags = MEM_Int;
+ }else{
+ double a, b;
+ Realify(pTos);
+ Realify(pNos);
+ a = pTos->r;
+ b = pNos->r;
+ switch( pOp->opcode ){
+ case OP_Add: b += a; break;
+ case OP_Subtract: b -= a; break;
+ case OP_Multiply: b *= a; break;
+ case OP_Divide: {
+ if( a==0.0 ) goto divide_by_zero;
+ b /= a;
+ break;
+ }
+ default: {
+ int ia = (int)a;
+ int ib = (int)b;
+ if( ia==0.0 ) goto divide_by_zero;
+ b = ib % ia;
+ break;
+ }
+ }
+ Release(pTos);
+ pTos--;
+ Release(pTos);
+ pTos->r = b;
+ pTos->flags = MEM_Real;
+ }
+ break;
+
+divide_by_zero:
+ Release(pTos);
+ pTos--;
+ Release(pTos);
+ pTos->flags = MEM_Null;
+ break;
+}
+
+/* Opcode: Function P1 * P3
+**
+** Invoke a user function (P3 is a pointer to a Function structure that
+** defines the function) with P1 string arguments taken from the stack.
+** Pop all arguments from the stack and push back the result.
+**
+** See also: AggFunc
+*/
+case OP_Function: {
+ int n, i;
+ Mem *pArg;
+ char **azArgv;
+ sqlite_func ctx;
+
+ n = pOp->p1;
+ pArg = &pTos[1-n];
+ azArgv = p->zArgv;
+ for(i=0; i<n; i++, pArg++){
+ if( pArg->flags & MEM_Null ){
+ azArgv[i] = 0;
+ }else{
+ Stringify(pArg);
+ azArgv[i] = pArg->z;
+ }
+ }
+ ctx.pFunc = (FuncDef*)pOp->p3;
+ ctx.s.flags = MEM_Null;
+ ctx.s.z = 0;
+ ctx.isError = 0;
+ ctx.isStep = 0;
+ if( sqliteSafetyOff(db) ) goto abort_due_to_misuse;
+ (*ctx.pFunc->xFunc)(&ctx, n, (const char**)azArgv);
+ if( sqliteSafetyOn(db) ) goto abort_due_to_misuse;
+ popStack(&pTos, n);
+ pTos++;
+ *pTos = ctx.s;
+ if( pTos->flags & MEM_Short ){
+ pTos->z = pTos->zShort;
+ }
+ if( ctx.isError ){
+ sqliteSetString(&p->zErrMsg,
+ (pTos->flags & MEM_Str)!=0 ? pTos->z : "user function error", (char*)0);
+ rc = SQLITE_ERROR;
+ }
+ break;
+}
+
+/* Opcode: BitAnd * * *
+**
+** Pop the top two elements from the stack. Convert both elements
+** to integers. Push back onto the stack the bit-wise AND of the
+** two elements.
+** If either operand is NULL, the result is NULL.
+*/
+/* Opcode: BitOr * * *
+**
+** Pop the top two elements from the stack. Convert both elements
+** to integers. Push back onto the stack the bit-wise OR of the
+** two elements.
+** If either operand is NULL, the result is NULL.
+*/
+/* Opcode: ShiftLeft * * *
+**
+** Pop the top two elements from the stack. Convert both elements
+** to integers. Push back onto the stack the top element shifted
+** left by N bits where N is the second element on the stack.
+** If either operand is NULL, the result is NULL.
+*/
+/* Opcode: ShiftRight * * *
+**
+** Pop the top two elements from the stack. Convert both elements
+** to integers. Push back onto the stack the top element shifted
+** right by N bits where N is the second element on the stack.
+** If either operand is NULL, the result is NULL.
+*/
+case OP_BitAnd:
+case OP_BitOr:
+case OP_ShiftLeft:
+case OP_ShiftRight: {
+ Mem *pNos = &pTos[-1];
+ int a, b;
+
+ assert( pNos>=p->aStack );
+ if( (pTos->flags | pNos->flags) & MEM_Null ){
+ popStack(&pTos, 2);
+ pTos++;
+ pTos->flags = MEM_Null;
+ break;
+ }
+ Integerify(pTos);
+ Integerify(pNos);
+ a = pTos->i;
+ b = pNos->i;
+ switch( pOp->opcode ){
+ case OP_BitAnd: a &= b; break;
+ case OP_BitOr: a |= b; break;
+ case OP_ShiftLeft: a <<= b; break;
+ case OP_ShiftRight: a >>= b; break;
+ default: /* CANT HAPPEN */ break;
+ }
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ assert( (pNos->flags & MEM_Dyn)==0 );
+ pTos--;
+ Release(pTos);
+ pTos->i = a;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: AddImm P1 * *
+**
+** Add the value P1 to whatever is on top of the stack. The result
+** is always an integer.
+**
+** To force the top of the stack to be an integer, just add 0.
+*/
+case OP_AddImm: {
+ assert( pTos>=p->aStack );
+ Integerify(pTos);
+ pTos->i += pOp->p1;
+ break;
+}
+
+/* Opcode: ForceInt P1 P2 *
+**
+** Convert the top of the stack into an integer. If the current top of
+** the stack is not numeric (meaning that is is a NULL or a string that
+** does not look like an integer or floating point number) then pop the
+** stack and jump to P2. If the top of the stack is numeric then
+** convert it into the least integer that is greater than or equal to its
+** current value if P1==0, or to the least integer that is strictly
+** greater than its current value if P1==1.
+*/
+case OP_ForceInt: {
+ int v;
+ assert( pTos>=p->aStack );
+ if( (pTos->flags & (MEM_Int|MEM_Real))==0
+ && ((pTos->flags & MEM_Str)==0 || sqliteIsNumber(pTos->z)==0) ){
+ Release(pTos);
+ pTos--;
+ pc = pOp->p2 - 1;
+ break;
+ }
+ if( pTos->flags & MEM_Int ){
+ v = pTos->i + (pOp->p1!=0);
+ }else{
+ Realify(pTos);
+ v = (int)pTos->r;
+ if( pTos->r>(double)v ) v++;
+ if( pOp->p1 && pTos->r==(double)v ) v++;
+ }
+ Release(pTos);
+ pTos->i = v;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: MustBeInt P1 P2 *
+**
+** Force the top of the stack to be an integer. If the top of the
+** stack is not an integer and cannot be converted into an integer
+** with out data loss, then jump immediately to P2, or if P2==0
+** raise an SQLITE_MISMATCH exception.
+**
+** If the top of the stack is not an integer and P2 is not zero and
+** P1 is 1, then the stack is popped. In all other cases, the depth
+** of the stack is unchanged.
+*/
+case OP_MustBeInt: {
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Int ){
+ /* Do nothing */
+ }else if( pTos->flags & MEM_Real ){
+ int i = (int)pTos->r;
+ double r = (double)i;
+ if( r!=pTos->r ){
+ goto mismatch;
+ }
+ pTos->i = i;
+ }else if( pTos->flags & MEM_Str ){
+ int v;
+ if( !toInt(pTos->z, &v) ){
+ double r;
+ if( !sqliteIsNumber(pTos->z) ){
+ goto mismatch;
+ }
+ Realify(pTos);
+ v = (int)pTos->r;
+ r = (double)v;
+ if( r!=pTos->r ){
+ goto mismatch;
+ }
+ }
+ pTos->i = v;
+ }else{
+ goto mismatch;
+ }
+ Release(pTos);
+ pTos->flags = MEM_Int;
+ break;
+
+mismatch:
+ if( pOp->p2==0 ){
+ rc = SQLITE_MISMATCH;
+ goto abort_due_to_error;
+ }else{
+ if( pOp->p1 ) popStack(&pTos, 1);
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: Eq P1 P2 *
+**
+** Pop the top two elements from the stack. If they are equal, then
+** jump to instruction P2. Otherwise, continue to the next instruction.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If both values are numeric, they are converted to doubles using atof()
+** and compared for equality that way. Otherwise the strcmp() library
+** routine is used for the comparison. For a pure text comparison
+** use OP_StrEq.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: Ne P1 P2 *
+**
+** Pop the top two elements from the stack. If they are not equal, then
+** jump to instruction P2. Otherwise, continue to the next instruction.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If both values are numeric, they are converted to doubles using atof()
+** and compared in that format. Otherwise the strcmp() library
+** routine is used for the comparison. For a pure text comparison
+** use OP_StrNe.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: Lt P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the
+** next on stack) is less than the first (the top of stack), then
+** jump to instruction P2. Otherwise, continue to the next instruction.
+** In other words, jump if NOS<TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If both values are numeric, they are converted to doubles using atof()
+** and compared in that format. Numeric values are always less than
+** non-numeric values. If both operands are non-numeric, the strcmp() library
+** routine is used for the comparison. For a pure text comparison
+** use OP_StrLt.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: Le P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the
+** next on stack) is less than or equal to the first (the top of stack),
+** then jump to instruction P2. In other words, jump if NOS<=TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If both values are numeric, they are converted to doubles using atof()
+** and compared in that format. Numeric values are always less than
+** non-numeric values. If both operands are non-numeric, the strcmp() library
+** routine is used for the comparison. For a pure text comparison
+** use OP_StrLe.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: Gt P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the
+** next on stack) is greater than the first (the top of stack),
+** then jump to instruction P2. In other words, jump if NOS>TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If both values are numeric, they are converted to doubles using atof()
+** and compared in that format. Numeric values are always less than
+** non-numeric values. If both operands are non-numeric, the strcmp() library
+** routine is used for the comparison. For a pure text comparison
+** use OP_StrGt.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: Ge P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the next
+** on stack) is greater than or equal to the first (the top of stack),
+** then jump to instruction P2. In other words, jump if NOS>=TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If both values are numeric, they are converted to doubles using atof()
+** and compared in that format. Numeric values are always less than
+** non-numeric values. If both operands are non-numeric, the strcmp() library
+** routine is used for the comparison. For a pure text comparison
+** use OP_StrGe.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+case OP_Eq:
+case OP_Ne:
+case OP_Lt:
+case OP_Le:
+case OP_Gt:
+case OP_Ge: {
+ Mem *pNos = &pTos[-1];
+ int c, v;
+ int ft, fn;
+ assert( pNos>=p->aStack );
+ ft = pTos->flags;
+ fn = pNos->flags;
+ if( (ft | fn) & MEM_Null ){
+ popStack(&pTos, 2);
+ if( pOp->p2 ){
+ if( pOp->p1 ) pc = pOp->p2-1;
+ }else{
+ pTos++;
+ pTos->flags = MEM_Null;
+ }
+ break;
+ }else if( (ft & fn & MEM_Int)==MEM_Int ){
+ c = pNos->i - pTos->i;
+ }else if( (ft & MEM_Int)!=0 && (fn & MEM_Str)!=0 && toInt(pNos->z,&v) ){
+ c = v - pTos->i;
+ }else if( (fn & MEM_Int)!=0 && (ft & MEM_Str)!=0 && toInt(pTos->z,&v) ){
+ c = pNos->i - v;
+ }else{
+ Stringify(pTos);
+ Stringify(pNos);
+ c = sqliteCompare(pNos->z, pTos->z);
+ }
+ switch( pOp->opcode ){
+ case OP_Eq: c = c==0; break;
+ case OP_Ne: c = c!=0; break;
+ case OP_Lt: c = c<0; break;
+ case OP_Le: c = c<=0; break;
+ case OP_Gt: c = c>0; break;
+ default: c = c>=0; break;
+ }
+ popStack(&pTos, 2);
+ if( pOp->p2 ){
+ if( c ) pc = pOp->p2-1;
+ }else{
+ pTos++;
+ pTos->i = c;
+ pTos->flags = MEM_Int;
+ }
+ break;
+}
+/* INSERT NO CODE HERE!
+**
+** The opcode numbers are extracted from this source file by doing
+**
+** grep '^case OP_' vdbe.c | ... >opcodes.h
+**
+** The opcodes are numbered in the order that they appear in this file.
+** But in order for the expression generating code to work right, the
+** string comparison operators that follow must be numbered exactly 6
+** greater than the numeric comparison opcodes above. So no other
+** cases can appear between the two.
+*/
+/* Opcode: StrEq P1 P2 *
+**
+** Pop the top two elements from the stack. If they are equal, then
+** jump to instruction P2. Otherwise, continue to the next instruction.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** The strcmp() library routine is used for the comparison. For a
+** numeric comparison, use OP_Eq.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: StrNe P1 P2 *
+**
+** Pop the top two elements from the stack. If they are not equal, then
+** jump to instruction P2. Otherwise, continue to the next instruction.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** The strcmp() library routine is used for the comparison. For a
+** numeric comparison, use OP_Ne.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: StrLt P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the
+** next on stack) is less than the first (the top of stack), then
+** jump to instruction P2. Otherwise, continue to the next instruction.
+** In other words, jump if NOS<TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** The strcmp() library routine is used for the comparison. For a
+** numeric comparison, use OP_Lt.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: StrLe P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the
+** next on stack) is less than or equal to the first (the top of stack),
+** then jump to instruction P2. In other words, jump if NOS<=TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** The strcmp() library routine is used for the comparison. For a
+** numeric comparison, use OP_Le.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: StrGt P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the
+** next on stack) is greater than the first (the top of stack),
+** then jump to instruction P2. In other words, jump if NOS>TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** The strcmp() library routine is used for the comparison. For a
+** numeric comparison, use OP_Gt.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+/* Opcode: StrGe P1 P2 *
+**
+** Pop the top two elements from the stack. If second element (the next
+** on stack) is greater than or equal to the first (the top of stack),
+** then jump to instruction P2. In other words, jump if NOS>=TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** The strcmp() library routine is used for the comparison. For a
+** numeric comparison, use OP_Ge.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
+*/
+case OP_StrEq:
+case OP_StrNe:
+case OP_StrLt:
+case OP_StrLe:
+case OP_StrGt:
+case OP_StrGe: {
+ Mem *pNos = &pTos[-1];
+ int c;
+ assert( pNos>=p->aStack );
+ if( (pNos->flags | pTos->flags) & MEM_Null ){
+ popStack(&pTos, 2);
+ if( pOp->p2 ){
+ if( pOp->p1 ) pc = pOp->p2-1;
+ }else{
+ pTos++;
+ pTos->flags = MEM_Null;
+ }
+ break;
+ }else{
+ Stringify(pTos);
+ Stringify(pNos);
+ c = strcmp(pNos->z, pTos->z);
+ }
+ /* The asserts on each case of the following switch are there to verify
+ ** that string comparison opcodes are always exactly 6 greater than the
+ ** corresponding numeric comparison opcodes. The code generator depends
+ ** on this fact.
+ */
+ switch( pOp->opcode ){
+ case OP_StrEq: c = c==0; assert( pOp->opcode-6==OP_Eq ); break;
+ case OP_StrNe: c = c!=0; assert( pOp->opcode-6==OP_Ne ); break;
+ case OP_StrLt: c = c<0; assert( pOp->opcode-6==OP_Lt ); break;
+ case OP_StrLe: c = c<=0; assert( pOp->opcode-6==OP_Le ); break;
+ case OP_StrGt: c = c>0; assert( pOp->opcode-6==OP_Gt ); break;
+ default: c = c>=0; assert( pOp->opcode-6==OP_Ge ); break;
+ }
+ popStack(&pTos, 2);
+ if( pOp->p2 ){
+ if( c ) pc = pOp->p2-1;
+ }else{
+ pTos++;
+ pTos->flags = MEM_Int;
+ pTos->i = c;
+ }
+ break;
+}
+
+/* Opcode: And * * *
+**
+** Pop two values off the stack. Take the logical AND of the
+** two values and push the resulting boolean value back onto the
+** stack.
+*/
+/* Opcode: Or * * *
+**
+** Pop two values off the stack. Take the logical OR of the
+** two values and push the resulting boolean value back onto the
+** stack.
+*/
+case OP_And:
+case OP_Or: {
+ Mem *pNos = &pTos[-1];
+ int v1, v2; /* 0==TRUE, 1==FALSE, 2==UNKNOWN or NULL */
+
+ assert( pNos>=p->aStack );
+ if( pTos->flags & MEM_Null ){
+ v1 = 2;
+ }else{
+ Integerify(pTos);
+ v1 = pTos->i==0;
+ }
+ if( pNos->flags & MEM_Null ){
+ v2 = 2;
+ }else{
+ Integerify(pNos);
+ v2 = pNos->i==0;
+ }
+ if( pOp->opcode==OP_And ){
+ static const unsigned char and_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 };
+ v1 = and_logic[v1*3+v2];
+ }else{
+ static const unsigned char or_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 };
+ v1 = or_logic[v1*3+v2];
+ }
+ popStack(&pTos, 2);
+ pTos++;
+ if( v1==2 ){
+ pTos->flags = MEM_Null;
+ }else{
+ pTos->i = v1==0;
+ pTos->flags = MEM_Int;
+ }
+ break;
+}
+
+/* Opcode: Negative * * *
+**
+** Treat the top of the stack as a numeric quantity. Replace it
+** with its additive inverse. If the top of the stack is NULL
+** its value is unchanged.
+*/
+/* Opcode: AbsValue * * *
+**
+** Treat the top of the stack as a numeric quantity. Replace it
+** with its absolute value. If the top of the stack is NULL
+** its value is unchanged.
+*/
+case OP_Negative:
+case OP_AbsValue: {
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Real ){
+ Release(pTos);
+ if( pOp->opcode==OP_Negative || pTos->r<0.0 ){
+ pTos->r = -pTos->r;
+ }
+ pTos->flags = MEM_Real;
+ }else if( pTos->flags & MEM_Int ){
+ Release(pTos);
+ if( pOp->opcode==OP_Negative || pTos->i<0 ){
+ pTos->i = -pTos->i;
+ }
+ pTos->flags = MEM_Int;
+ }else if( pTos->flags & MEM_Null ){
+ /* Do nothing */
+ }else{
+ Realify(pTos);
+ Release(pTos);
+ if( pOp->opcode==OP_Negative || pTos->r<0.0 ){
+ pTos->r = -pTos->r;
+ }
+ pTos->flags = MEM_Real;
+ }
+ break;
+}
+
+/* Opcode: Not * * *
+**
+** Interpret the top of the stack as a boolean value. Replace it
+** with its complement. If the top of the stack is NULL its value
+** is unchanged.
+*/
+case OP_Not: {
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break; /* Do nothing to NULLs */
+ Integerify(pTos);
+ Release(pTos);
+ pTos->i = !pTos->i;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: BitNot * * *
+**
+** Interpret the top of the stack as an value. Replace it
+** with its ones-complement. If the top of the stack is NULL its
+** value is unchanged.
+*/
+case OP_BitNot: {
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break; /* Do nothing to NULLs */
+ Integerify(pTos);
+ Release(pTos);
+ pTos->i = ~pTos->i;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: Noop * * *
+**
+** Do nothing. This instruction is often useful as a jump
+** destination.
+*/
+case OP_Noop: {
+ break;
+}
+
+/* Opcode: If P1 P2 *
+**
+** Pop a single boolean from the stack. If the boolean popped is
+** true, then jump to p2. Otherwise continue to the next instruction.
+** An integer is false if zero and true otherwise. A string is
+** false if it has zero length and true otherwise.
+**
+** If the value popped of the stack is NULL, then take the jump if P1
+** is true and fall through if P1 is false.
+*/
+/* Opcode: IfNot P1 P2 *
+**
+** Pop a single boolean from the stack. If the boolean popped is
+** false, then jump to p2. Otherwise continue to the next instruction.
+** An integer is false if zero and true otherwise. A string is
+** false if it has zero length and true otherwise.
+**
+** If the value popped of the stack is NULL, then take the jump if P1
+** is true and fall through if P1 is false.
+*/
+case OP_If:
+case OP_IfNot: {
+ int c;
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ){
+ c = pOp->p1;
+ }else{
+ Integerify(pTos);
+ c = pTos->i;
+ if( pOp->opcode==OP_IfNot ) c = !c;
+ }
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ pTos--;
+ if( c ) pc = pOp->p2-1;
+ break;
+}
+
+/* Opcode: IsNull P1 P2 *
+**
+** If any of the top abs(P1) values on the stack are NULL, then jump
+** to P2. Pop the stack P1 times if P1>0. If P1<0 leave the stack
+** unchanged.
+*/
+case OP_IsNull: {
+ int i, cnt;
+ Mem *pTerm;
+ cnt = pOp->p1;
+ if( cnt<0 ) cnt = -cnt;
+ pTerm = &pTos[1-cnt];
+ assert( pTerm>=p->aStack );
+ for(i=0; i<cnt; i++, pTerm++){
+ if( pTerm->flags & MEM_Null ){
+ pc = pOp->p2-1;
+ break;
+ }
+ }
+ if( pOp->p1>0 ) popStack(&pTos, cnt);
+ break;
+}
+
+/* Opcode: NotNull P1 P2 *
+**
+** Jump to P2 if the top P1 values on the stack are all not NULL. Pop the
+** stack if P1 times if P1 is greater than zero. If P1 is less than
+** zero then leave the stack unchanged.
+*/
+case OP_NotNull: {
+ int i, cnt;
+ cnt = pOp->p1;
+ if( cnt<0 ) cnt = -cnt;
+ assert( &pTos[1-cnt] >= p->aStack );
+ for(i=0; i<cnt && (pTos[1+i-cnt].flags & MEM_Null)==0; i++){}
+ if( i>=cnt ) pc = pOp->p2-1;
+ if( pOp->p1>0 ) popStack(&pTos, cnt);
+ break;
+}
+
+/* Opcode: MakeRecord P1 P2 *
+**
+** Convert the top P1 entries of the stack into a single entry
+** suitable for use as a data record in a database table. The
+** details of the format are irrelavant as long as the OP_Column
+** opcode can decode the record later. Refer to source code
+** comments for the details of the record format.
+**
+** If P2 is true (non-zero) and one or more of the P1 entries
+** that go into building the record is NULL, then add some extra
+** bytes to the record to make it distinct for other entries created
+** during the same run of the VDBE. The extra bytes added are a
+** counter that is reset with each run of the VDBE, so records
+** created this way will not necessarily be distinct across runs.
+** But they should be distinct for transient tables (created using
+** OP_OpenTemp) which is what they are intended for.
+**
+** (Later:) The P2==1 option was intended to make NULLs distinct
+** for the UNION operator. But I have since discovered that NULLs
+** are indistinct for UNION. So this option is never used.
+*/
+case OP_MakeRecord: {
+ char *zNewRecord;
+ int nByte;
+ int nField;
+ int i, j;
+ int idxWidth;
+ u32 addr;
+ Mem *pRec;
+ int addUnique = 0; /* True to cause bytes to be added to make the
+ ** generated record distinct */
+ char zTemp[NBFS]; /* Temp space for small records */
+
+ /* Assuming the record contains N fields, the record format looks
+ ** like this:
+ **
+ ** -------------------------------------------------------------------
+ ** | idx0 | idx1 | ... | idx(N-1) | idx(N) | data0 | ... | data(N-1) |
+ ** -------------------------------------------------------------------
+ **
+ ** All data fields are converted to strings before being stored and
+ ** are stored with their null terminators. NULL entries omit the
+ ** null terminator. Thus an empty string uses 1 byte and a NULL uses
+ ** zero bytes. Data(0) is taken from the lowest element of the stack
+ ** and data(N-1) is the top of the stack.
+ **
+ ** Each of the idx() entries is either 1, 2, or 3 bytes depending on
+ ** how big the total record is. Idx(0) contains the offset to the start
+ ** of data(0). Idx(k) contains the offset to the start of data(k).
+ ** Idx(N) contains the total number of bytes in the record.
+ */
+ nField = pOp->p1;
+ pRec = &pTos[1-nField];
+ assert( pRec>=p->aStack );
+ nByte = 0;
+ for(i=0; i<nField; i++, pRec++){
+ if( pRec->flags & MEM_Null ){
+ addUnique = pOp->p2;
+ }else{
+ Stringify(pRec);
+ nByte += pRec->n;
+ }
+ }
+ if( addUnique ) nByte += sizeof(p->uniqueCnt);
+ if( nByte + nField + 1 < 256 ){
+ idxWidth = 1;
+ }else if( nByte + 2*nField + 2 < 65536 ){
+ idxWidth = 2;
+ }else{
+ idxWidth = 3;
+ }
+ nByte += idxWidth*(nField + 1);
+ if( nByte>MAX_BYTES_PER_ROW ){
+ rc = SQLITE_TOOBIG;
+ goto abort_due_to_error;
+ }
+ if( nByte<=NBFS ){
+ zNewRecord = zTemp;
+ }else{
+ zNewRecord = sqliteMallocRaw( nByte );
+ if( zNewRecord==0 ) goto no_mem;
+ }
+ j = 0;
+ addr = idxWidth*(nField+1) + addUnique*sizeof(p->uniqueCnt);
+ for(i=0, pRec=&pTos[1-nField]; i<nField; i++, pRec++){
+ zNewRecord[j++] = addr & 0xff;
+ if( idxWidth>1 ){
+ zNewRecord[j++] = (addr>>8)&0xff;
+ if( idxWidth>2 ){
+ zNewRecord[j++] = (addr>>16)&0xff;
+ }
+ }
+ if( (pRec->flags & MEM_Null)==0 ){
+ addr += pRec->n;
+ }
+ }
+ zNewRecord[j++] = addr & 0xff;
+ if( idxWidth>1 ){
+ zNewRecord[j++] = (addr>>8)&0xff;
+ if( idxWidth>2 ){
+ zNewRecord[j++] = (addr>>16)&0xff;
+ }
+ }
+ if( addUnique ){
+ memcpy(&zNewRecord[j], &p->uniqueCnt, sizeof(p->uniqueCnt));
+ p->uniqueCnt++;
+ j += sizeof(p->uniqueCnt);
+ }
+ for(i=0, pRec=&pTos[1-nField]; i<nField; i++, pRec++){
+ if( (pRec->flags & MEM_Null)==0 ){
+ memcpy(&zNewRecord[j], pRec->z, pRec->n);
+ j += pRec->n;
+ }
+ }
+ popStack(&pTos, nField);
+ pTos++;
+ pTos->n = nByte;
+ if( nByte<=NBFS ){
+ assert( zNewRecord==zTemp );
+ memcpy(pTos->zShort, zTemp, nByte);
+ pTos->z = pTos->zShort;
+ pTos->flags = MEM_Str | MEM_Short;
+ }else{
+ assert( zNewRecord!=zTemp );
+ pTos->z = zNewRecord;
+ pTos->flags = MEM_Str | MEM_Dyn;
+ }
+ break;
+}
+
+/* Opcode: MakeKey P1 P2 P3
+**
+** Convert the top P1 entries of the stack into a single entry suitable
+** for use as the key in an index. The top P1 records are
+** converted to strings and merged. The null-terminators
+** are retained and used as separators.
+** The lowest entry in the stack is the first field and the top of the
+** stack becomes the last.
+**
+** If P2 is not zero, then the original entries remain on the stack
+** and the new key is pushed on top. If P2 is zero, the original
+** data is popped off the stack first then the new key is pushed
+** back in its place.
+**
+** P3 is a string that is P1 characters long. Each character is either
+** an 'n' or a 't' to indicates if the argument should be intepreted as
+** numeric or text type. The first character of P3 corresponds to the
+** lowest element on the stack. If P3 is NULL then all arguments are
+** assumed to be of the numeric type.
+**
+** The type makes a difference in that text-type fields may not be
+** introduced by 'b' (as described in the next paragraph). The
+** first character of a text-type field must be either 'a' (if it is NULL)
+** or 'c'. Numeric fields will be introduced by 'b' if their content
+** looks like a well-formed number. Otherwise the 'a' or 'c' will be
+** used.
+**
+** The key is a concatenation of fields. Each field is terminated by
+** a single 0x00 character. A NULL field is introduced by an 'a' and
+** is followed immediately by its 0x00 terminator. A numeric field is
+** introduced by a single character 'b' and is followed by a sequence
+** of characters that represent the number such that a comparison of
+** the character string using memcpy() sorts the numbers in numerical
+** order. The character strings for numbers are generated using the
+** sqliteRealToSortable() function. A text field is introduced by a
+** 'c' character and is followed by the exact text of the field. The
+** use of an 'a', 'b', or 'c' character at the beginning of each field
+** guarantees that NULLs sort before numbers and that numbers sort
+** before text. 0x00 characters do not occur except as separators
+** between fields.
+**
+** See also: MakeIdxKey, SortMakeKey
+*/
+/* Opcode: MakeIdxKey P1 P2 P3
+**
+** Convert the top P1 entries of the stack into a single entry suitable
+** for use as the key in an index. In addition, take one additional integer
+** off of the stack, treat that integer as a four-byte record number, and
+** append the four bytes to the key. Thus a total of P1+1 entries are
+** popped from the stack for this instruction and a single entry is pushed
+** back. The first P1 entries that are popped are strings and the last
+** entry (the lowest on the stack) is an integer record number.
+**
+** The converstion of the first P1 string entries occurs just like in
+** MakeKey. Each entry is separated from the others by a null.
+** The entire concatenation is null-terminated. The lowest entry
+** in the stack is the first field and the top of the stack becomes the
+** last.
+**
+** If P2 is not zero and one or more of the P1 entries that go into the
+** generated key is NULL, then jump to P2 after the new key has been
+** pushed on the stack. In other words, jump to P2 if the key is
+** guaranteed to be unique. This jump can be used to skip a subsequent
+** uniqueness test.
+**
+** P3 is a string that is P1 characters long. Each character is either
+** an 'n' or a 't' to indicates if the argument should be numeric or
+** text. The first character corresponds to the lowest element on the
+** stack. If P3 is null then all arguments are assumed to be numeric.
+**
+** See also: MakeKey, SortMakeKey
+*/
+case OP_MakeIdxKey:
+case OP_MakeKey: {
+ char *zNewKey;
+ int nByte;
+ int nField;
+ int addRowid;
+ int i, j;
+ int containsNull = 0;
+ Mem *pRec;
+ char zTemp[NBFS];
+
+ addRowid = pOp->opcode==OP_MakeIdxKey;
+ nField = pOp->p1;
+ pRec = &pTos[1-nField];
+ assert( pRec>=p->aStack );
+ nByte = 0;
+ for(j=0, i=0; i<nField; i++, j++, pRec++){
+ int flags = pRec->flags;
+ int len;
+ char *z;
+ if( flags & MEM_Null ){
+ nByte += 2;
+ containsNull = 1;
+ }else if( pOp->p3 && pOp->p3[j]=='t' ){
+ Stringify(pRec);
+ pRec->flags &= ~(MEM_Int|MEM_Real);
+ nByte += pRec->n+1;
+ }else if( (flags & (MEM_Real|MEM_Int))!=0 || sqliteIsNumber(pRec->z) ){
+ if( (flags & (MEM_Real|MEM_Int))==MEM_Int ){
+ pRec->r = pRec->i;
+ }else if( (flags & (MEM_Real|MEM_Int))==0 ){
+ pRec->r = sqliteAtoF(pRec->z, 0);
+ }
+ Release(pRec);
+ z = pRec->zShort;
+ sqliteRealToSortable(pRec->r, z);
+ len = strlen(z);
+ pRec->z = 0;
+ pRec->flags = MEM_Real;
+ pRec->n = len+1;
+ nByte += pRec->n+1;
+ }else{
+ nByte += pRec->n+1;
+ }
+ }
+ if( nByte+sizeof(u32)>MAX_BYTES_PER_ROW ){
+ rc = SQLITE_TOOBIG;
+ goto abort_due_to_error;
+ }
+ if( addRowid ) nByte += sizeof(u32);
+ if( nByte<=NBFS ){
+ zNewKey = zTemp;
+ }else{
+ zNewKey = sqliteMallocRaw( nByte );
+ if( zNewKey==0 ) goto no_mem;
+ }
+ j = 0;
+ pRec = &pTos[1-nField];
+ for(i=0; i<nField; i++, pRec++){
+ if( pRec->flags & MEM_Null ){
+ zNewKey[j++] = 'a';
+ zNewKey[j++] = 0;
+ }else if( pRec->flags==MEM_Real ){
+ zNewKey[j++] = 'b';
+ memcpy(&zNewKey[j], pRec->zShort, pRec->n);
+ j += pRec->n;
+ }else{
+ assert( pRec->flags & MEM_Str );
+ zNewKey[j++] = 'c';
+ memcpy(&zNewKey[j], pRec->z, pRec->n);
+ j += pRec->n;
+ }
+ }
+ if( addRowid ){
+ u32 iKey;
+ pRec = &pTos[-nField];
+ assert( pRec>=p->aStack );
+ Integerify(pRec);
+ iKey = intToKey(pRec->i);
+ memcpy(&zNewKey[j], &iKey, sizeof(u32));
+ popStack(&pTos, nField+1);
+ if( pOp->p2 && containsNull ) pc = pOp->p2 - 1;
+ }else{
+ if( pOp->p2==0 ) popStack(&pTos, nField);
+ }
+ pTos++;
+ pTos->n = nByte;
+ if( nByte<=NBFS ){
+ assert( zNewKey==zTemp );
+ pTos->z = pTos->zShort;
+ memcpy(pTos->zShort, zTemp, nByte);
+ pTos->flags = MEM_Str | MEM_Short;
+ }else{
+ pTos->z = zNewKey;
+ pTos->flags = MEM_Str | MEM_Dyn;
+ }
+ break;
+}
+
+/* Opcode: IncrKey * * *
+**
+** The top of the stack should contain an index key generated by
+** The MakeKey opcode. This routine increases the least significant
+** byte of that key by one. This is used so that the MoveTo opcode
+** will move to the first entry greater than the key rather than to
+** the key itself.
+*/
+case OP_IncrKey: {
+ assert( pTos>=p->aStack );
+ /* The IncrKey opcode is only applied to keys generated by
+ ** MakeKey or MakeIdxKey and the results of those operands
+ ** are always dynamic strings or zShort[] strings. So we
+ ** are always free to modify the string in place.
+ */
+ assert( pTos->flags & (MEM_Dyn|MEM_Short) );
+ pTos->z[pTos->n-1]++;
+ break;
+}
+
+/* Opcode: Checkpoint P1 * *
+**
+** Begin a checkpoint. A checkpoint is the beginning of a operation that
+** is part of a larger transaction but which might need to be rolled back
+** itself without effecting the containing transaction. A checkpoint will
+** be automatically committed or rollback when the VDBE halts.
+**
+** The checkpoint is begun on the database file with index P1. The main
+** database file has an index of 0 and the file used for temporary tables
+** has an index of 1.
+*/
+case OP_Checkpoint: {
+ int i = pOp->p1;
+ if( i>=0 && i<db->nDb && db->aDb[i].pBt && db->aDb[i].inTrans==1 ){
+ rc = sqliteBtreeBeginCkpt(db->aDb[i].pBt);
+ if( rc==SQLITE_OK ) db->aDb[i].inTrans = 2;
+ }
+ break;
+}
+
+/* Opcode: Transaction P1 * *
+**
+** Begin a transaction. The transaction ends when a Commit or Rollback
+** opcode is encountered. Depending on the ON CONFLICT setting, the
+** transaction might also be rolled back if an error is encountered.
+**
+** P1 is the index of the database file on which the transaction is
+** started. Index 0 is the main database file and index 1 is the
+** file used for temporary tables.
+**
+** A write lock is obtained on the database file when a transaction is
+** started. No other process can read or write the file while the
+** transaction is underway. Starting a transaction also creates a
+** rollback journal. A transaction must be started before any changes
+** can be made to the database.
+*/
+case OP_Transaction: {
+ int busy = 1;
+ int i = pOp->p1;
+ assert( i>=0 && i<db->nDb );
+ if( db->aDb[i].inTrans ) break;
+ while( db->aDb[i].pBt!=0 && busy ){
+ rc = sqliteBtreeBeginTrans(db->aDb[i].pBt);
+ switch( rc ){
+ case SQLITE_BUSY: {
+ if( db->xBusyCallback==0 ){
+ p->pc = pc;
+ p->undoTransOnError = 1;
+ p->rc = SQLITE_BUSY;
+ p->pTos = pTos;
+ return SQLITE_BUSY;
+ }else if( (*db->xBusyCallback)(db->pBusyArg, "", busy++)==0 ){
+ sqliteSetString(&p->zErrMsg, sqlite_error_string(rc), (char*)0);
+ busy = 0;
+ }
+ break;
+ }
+ case SQLITE_READONLY: {
+ rc = SQLITE_OK;
+ /* Fall thru into the next case */
+ }
+ case SQLITE_OK: {
+ p->inTempTrans = 0;
+ busy = 0;
+ break;
+ }
+ default: {
+ goto abort_due_to_error;
+ }
+ }
+ }
+ db->aDb[i].inTrans = 1;
+ p->undoTransOnError = 1;
+ break;
+}
+
+/* Opcode: Commit * * *
+**
+** Cause all modifications to the database that have been made since the
+** last Transaction to actually take effect. No additional modifications
+** are allowed until another transaction is started. The Commit instruction
+** deletes the journal file and releases the write lock on the database.
+** A read lock continues to be held if there are still cursors open.
+*/
+case OP_Commit: {
+ int i;
+ if( db->xCommitCallback!=0 ){
+ if( sqliteSafetyOff(db) ) goto abort_due_to_misuse;
+ if( db->xCommitCallback(db->pCommitArg)!=0 ){
+ rc = SQLITE_CONSTRAINT;
+ }
+ if( sqliteSafetyOn(db) ) goto abort_due_to_misuse;
+ }
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+ if( db->aDb[i].inTrans ){
+ rc = sqliteBtreeCommit(db->aDb[i].pBt);
+ db->aDb[i].inTrans = 0;
+ }
+ }
+ if( rc==SQLITE_OK ){
+ sqliteCommitInternalChanges(db);
+ }else{
+ sqliteRollbackAll(db);
+ }
+ break;
+}
+
+/* Opcode: Rollback P1 * *
+**
+** Cause all modifications to the database that have been made since the
+** last Transaction to be undone. The database is restored to its state
+** before the Transaction opcode was executed. No additional modifications
+** are allowed until another transaction is started.
+**
+** P1 is the index of the database file that is committed. An index of 0
+** is used for the main database and an index of 1 is used for the file used
+** to hold temporary tables.
+**
+** This instruction automatically closes all cursors and releases both
+** the read and write locks on the indicated database.
+*/
+case OP_Rollback: {
+ sqliteRollbackAll(db);
+ break;
+}
+
+/* Opcode: ReadCookie P1 P2 *
+**
+** Read cookie number P2 from database P1 and push it onto the stack.
+** P2==0 is the schema version. P2==1 is the database format.
+** P2==2 is the recommended pager cache size, and so forth. P1==0 is
+** the main database file and P1==1 is the database file used to store
+** temporary tables.
+**
+** There must be a read-lock on the database (either a transaction
+** must be started or there must be an open cursor) before
+** executing this instruction.
+*/
+case OP_ReadCookie: {
+ int aMeta[SQLITE_N_BTREE_META];
+ assert( pOp->p2<SQLITE_N_BTREE_META );
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( db->aDb[pOp->p1].pBt!=0 );
+ rc = sqliteBtreeGetMeta(db->aDb[pOp->p1].pBt, aMeta);
+ pTos++;
+ pTos->i = aMeta[1+pOp->p2];
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: SetCookie P1 P2 *
+**
+** Write the top of the stack into cookie number P2 of database P1.
+** P2==0 is the schema version. P2==1 is the database format.
+** P2==2 is the recommended pager cache size, and so forth. P1==0 is
+** the main database file and P1==1 is the database file used to store
+** temporary tables.
+**
+** A transaction must be started before executing this opcode.
+*/
+case OP_SetCookie: {
+ int aMeta[SQLITE_N_BTREE_META];
+ assert( pOp->p2<SQLITE_N_BTREE_META );
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( db->aDb[pOp->p1].pBt!=0 );
+ assert( pTos>=p->aStack );
+ Integerify(pTos)
+ rc = sqliteBtreeGetMeta(db->aDb[pOp->p1].pBt, aMeta);
+ if( rc==SQLITE_OK ){
+ aMeta[1+pOp->p2] = pTos->i;
+ rc = sqliteBtreeUpdateMeta(db->aDb[pOp->p1].pBt, aMeta);
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: VerifyCookie P1 P2 *
+**
+** Check the value of global database parameter number 0 (the
+** schema version) and make sure it is equal to P2.
+** P1 is the database number which is 0 for the main database file
+** and 1 for the file holding temporary tables and some higher number
+** for auxiliary databases.
+**
+** The cookie changes its value whenever the database schema changes.
+** This operation is used to detect when that the cookie has changed
+** and that the current process needs to reread the schema.
+**
+** Either a transaction needs to have been started or an OP_Open needs
+** to be executed (to establish a read lock) before this opcode is
+** invoked.
+*/
+case OP_VerifyCookie: {
+ int aMeta[SQLITE_N_BTREE_META];
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ rc = sqliteBtreeGetMeta(db->aDb[pOp->p1].pBt, aMeta);
+ if( rc==SQLITE_OK && aMeta[1]!=pOp->p2 ){
+ sqliteSetString(&p->zErrMsg, "database schema has changed", (char*)0);
+ rc = SQLITE_SCHEMA;
+ }
+ break;
+}
+
+/* Opcode: OpenRead P1 P2 P3
+**
+** Open a read-only cursor for the database table whose root page is
+** P2 in a database file. The database file is determined by an
+** integer from the top of the stack. 0 means the main database and
+** 1 means the database used for temporary tables. Give the new
+** cursor an identifier of P1. The P1 values need not be contiguous
+** but all P1 values should be small integers. It is an error for
+** P1 to be negative.
+**
+** If P2==0 then take the root page number from the next of the stack.
+**
+** There will be a read lock on the database whenever there is an
+** open cursor. If the database was unlocked prior to this instruction
+** then a read lock is acquired as part of this instruction. A read
+** lock allows other processes to read the database but prohibits
+** any other process from modifying the database. The read lock is
+** released when all cursors are closed. If this instruction attempts
+** to get a read lock but fails, the script terminates with an
+** SQLITE_BUSY error code.
+**
+** The P3 value is the name of the table or index being opened.
+** The P3 value is not actually used by this opcode and may be
+** omitted. But the code generator usually inserts the index or
+** table name into P3 to make the code easier to read.
+**
+** See also OpenWrite.
+*/
+/* Opcode: OpenWrite P1 P2 P3
+**
+** Open a read/write cursor named P1 on the table or index whose root
+** page is P2. If P2==0 then take the root page number from the stack.
+**
+** The P3 value is the name of the table or index being opened.
+** The P3 value is not actually used by this opcode and may be
+** omitted. But the code generator usually inserts the index or
+** table name into P3 to make the code easier to read.
+**
+** This instruction works just like OpenRead except that it opens the cursor
+** in read/write mode. For a given table, there can be one or more read-only
+** cursors or a single read/write cursor but not both.
+**
+** See also OpenRead.
+*/
+case OP_OpenRead:
+case OP_OpenWrite: {
+ int busy = 0;
+ int i = pOp->p1;
+ int p2 = pOp->p2;
+ int wrFlag;
+ Btree *pX;
+ int iDb;
+
+ assert( pTos>=p->aStack );
+ Integerify(pTos);
+ iDb = pTos->i;
+ pTos--;
+ assert( iDb>=0 && iDb<db->nDb );
+ pX = db->aDb[iDb].pBt;
+ assert( pX!=0 );
+ wrFlag = pOp->opcode==OP_OpenWrite;
+ if( p2<=0 ){
+ assert( pTos>=p->aStack );
+ Integerify(pTos);
+ p2 = pTos->i;
+ pTos--;
+ if( p2<2 ){
+ sqliteSetString(&p->zErrMsg, "root page number less than 2", (char*)0);
+ rc = SQLITE_INTERNAL;
+ break;
+ }
+ }
+ assert( i>=0 );
+ if( expandCursorArraySize(p, i) ) goto no_mem;
+ sqliteVdbeCleanupCursor(&p->aCsr[i]);
+ memset(&p->aCsr[i], 0, sizeof(Cursor));
+ p->aCsr[i].nullRow = 1;
+ if( pX==0 ) break;
+ do{
+ rc = sqliteBtreeCursor(pX, p2, wrFlag, &p->aCsr[i].pCursor);
+ switch( rc ){
+ case SQLITE_BUSY: {
+ if( db->xBusyCallback==0 ){
+ p->pc = pc;
+ p->rc = SQLITE_BUSY;
+ p->pTos = &pTos[1 + (pOp->p2<=0)]; /* Operands must remain on stack */
+ return SQLITE_BUSY;
+ }else if( (*db->xBusyCallback)(db->pBusyArg, pOp->p3, ++busy)==0 ){
+ sqliteSetString(&p->zErrMsg, sqlite_error_string(rc), (char*)0);
+ busy = 0;
+ }
+ break;
+ }
+ case SQLITE_OK: {
+ busy = 0;
+ break;
+ }
+ default: {
+ goto abort_due_to_error;
+ }
+ }
+ }while( busy );
+ break;
+}
+
+/* Opcode: OpenTemp P1 P2 *
+**
+** Open a new cursor to a transient table.
+** The transient cursor is always opened read/write even if
+** the main database is read-only. The transient table is deleted
+** automatically when the cursor is closed.
+**
+** The cursor points to a BTree table if P2==0 and to a BTree index
+** if P2==1. A BTree table must have an integer key and can have arbitrary
+** data. A BTree index has no data but can have an arbitrary key.
+**
+** This opcode is used for tables that exist for the duration of a single
+** SQL statement only. Tables created using CREATE TEMPORARY TABLE
+** are opened using OP_OpenRead or OP_OpenWrite. "Temporary" in the
+** context of this opcode means for the duration of a single SQL statement
+** whereas "Temporary" in the context of CREATE TABLE means for the duration
+** of the connection to the database. Same word; different meanings.
+*/
+case OP_OpenTemp: {
+ int i = pOp->p1;
+ Cursor *pCx;
+ assert( i>=0 );
+ if( expandCursorArraySize(p, i) ) goto no_mem;
+ pCx = &p->aCsr[i];
+ sqliteVdbeCleanupCursor(pCx);
+ memset(pCx, 0, sizeof(*pCx));
+ pCx->nullRow = 1;
+ rc = sqliteBtreeFactory(db, 0, 1, TEMP_PAGES, &pCx->pBt);
+
+ if( rc==SQLITE_OK ){
+ rc = sqliteBtreeBeginTrans(pCx->pBt);
+ }
+ if( rc==SQLITE_OK ){
+ if( pOp->p2 ){
+ int pgno;
+ rc = sqliteBtreeCreateIndex(pCx->pBt, &pgno);
+ if( rc==SQLITE_OK ){
+ rc = sqliteBtreeCursor(pCx->pBt, pgno, 1, &pCx->pCursor);
+ }
+ }else{
+ rc = sqliteBtreeCursor(pCx->pBt, 2, 1, &pCx->pCursor);
+ }
+ }
+ break;
+}
+
+/* Opcode: OpenPseudo P1 * *
+**
+** Open a new cursor that points to a fake table that contains a single
+** row of data. Any attempt to write a second row of data causes the
+** first row to be deleted. All data is deleted when the cursor is
+** closed.
+**
+** A pseudo-table created by this opcode is useful for holding the
+** NEW or OLD tables in a trigger.
+*/
+case OP_OpenPseudo: {
+ int i = pOp->p1;
+ Cursor *pCx;
+ assert( i>=0 );
+ if( expandCursorArraySize(p, i) ) goto no_mem;
+ pCx = &p->aCsr[i];
+ sqliteVdbeCleanupCursor(pCx);
+ memset(pCx, 0, sizeof(*pCx));
+ pCx->nullRow = 1;
+ pCx->pseudoTable = 1;
+ break;
+}
+
+/* Opcode: Close P1 * *
+**
+** Close a cursor previously opened as P1. If P1 is not
+** currently open, this instruction is a no-op.
+*/
+case OP_Close: {
+ int i = pOp->p1;
+ if( i>=0 && i<p->nCursor ){
+ sqliteVdbeCleanupCursor(&p->aCsr[i]);
+ }
+ break;
+}
+
+/* Opcode: MoveTo P1 P2 *
+**
+** Pop the top of the stack and use its value as a key. Reposition
+** cursor P1 so that it points to an entry with a matching key. If
+** the table contains no record with a matching key, then the cursor
+** is left pointing at the first record that is greater than the key.
+** If there are no records greater than the key and P2 is not zero,
+** then an immediate jump to P2 is made.
+**
+** See also: Found, NotFound, Distinct, MoveLt
+*/
+/* Opcode: MoveLt P1 P2 *
+**
+** Pop the top of the stack and use its value as a key. Reposition
+** cursor P1 so that it points to the entry with the largest key that is
+** less than the key popped from the stack.
+** If there are no records less than than the key and P2
+** is not zero then an immediate jump to P2 is made.
+**
+** See also: MoveTo
+*/
+case OP_MoveLt:
+case OP_MoveTo: {
+ int i = pOp->p1;
+ Cursor *pC;
+
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ pC = &p->aCsr[i];
+ if( pC->pCursor!=0 ){
+ int res, oc;
+ pC->nullRow = 0;
+ if( pTos->flags & MEM_Int ){
+ int iKey = intToKey(pTos->i);
+ if( pOp->p2==0 && pOp->opcode==OP_MoveTo ){
+ pC->movetoTarget = iKey;
+ pC->deferredMoveto = 1;
+ Release(pTos);
+ pTos--;
+ break;
+ }
+ sqliteBtreeMoveto(pC->pCursor, (char*)&iKey, sizeof(int), &res);
+ pC->lastRecno = pTos->i;
+ pC->recnoIsValid = res==0;
+ }else{
+ Stringify(pTos);
+ sqliteBtreeMoveto(pC->pCursor, pTos->z, pTos->n, &res);
+ pC->recnoIsValid = 0;
+ }
+ pC->deferredMoveto = 0;
+ sqlite_search_count++;
+ oc = pOp->opcode;
+ if( oc==OP_MoveTo && res<0 ){
+ sqliteBtreeNext(pC->pCursor, &res);
+ pC->recnoIsValid = 0;
+ if( res && pOp->p2>0 ){
+ pc = pOp->p2 - 1;
+ }
+ }else if( oc==OP_MoveLt ){
+ if( res>=0 ){
+ sqliteBtreePrevious(pC->pCursor, &res);
+ pC->recnoIsValid = 0;
+ }else{
+ /* res might be negative because the table is empty. Check to
+ ** see if this is the case.
+ */
+ int keysize;
+ res = sqliteBtreeKeySize(pC->pCursor,&keysize)!=0 || keysize==0;
+ }
+ if( res && pOp->p2>0 ){
+ pc = pOp->p2 - 1;
+ }
+ }
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: Distinct P1 P2 *
+**
+** Use the top of the stack as a string key. If a record with that key does
+** not exist in the table of cursor P1, then jump to P2. If the record
+** does already exist, then fall thru. The cursor is left pointing
+** at the record if it exists. The key is not popped from the stack.
+**
+** This operation is similar to NotFound except that this operation
+** does not pop the key from the stack.
+**
+** See also: Found, NotFound, MoveTo, IsUnique, NotExists
+*/
+/* Opcode: Found P1 P2 *
+**
+** Use the top of the stack as a string key. If a record with that key
+** does exist in table of P1, then jump to P2. If the record
+** does not exist, then fall thru. The cursor is left pointing
+** to the record if it exists. The key is popped from the stack.
+**
+** See also: Distinct, NotFound, MoveTo, IsUnique, NotExists
+*/
+/* Opcode: NotFound P1 P2 *
+**
+** Use the top of the stack as a string key. If a record with that key
+** does not exist in table of P1, then jump to P2. If the record
+** does exist, then fall thru. The cursor is left pointing to the
+** record if it exists. The key is popped from the stack.
+**
+** The difference between this operation and Distinct is that
+** Distinct does not pop the key from the stack.
+**
+** See also: Distinct, Found, MoveTo, NotExists, IsUnique
+*/
+case OP_Distinct:
+case OP_NotFound:
+case OP_Found: {
+ int i = pOp->p1;
+ int alreadyExists = 0;
+ Cursor *pC;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ if( (pC = &p->aCsr[i])->pCursor!=0 ){
+ int res, rx;
+ Stringify(pTos);
+ rx = sqliteBtreeMoveto(pC->pCursor, pTos->z, pTos->n, &res);
+ alreadyExists = rx==SQLITE_OK && res==0;
+ pC->deferredMoveto = 0;
+ }
+ if( pOp->opcode==OP_Found ){
+ if( alreadyExists ) pc = pOp->p2 - 1;
+ }else{
+ if( !alreadyExists ) pc = pOp->p2 - 1;
+ }
+ if( pOp->opcode!=OP_Distinct ){
+ Release(pTos);
+ pTos--;
+ }
+ break;
+}
+
+/* Opcode: IsUnique P1 P2 *
+**
+** The top of the stack is an integer record number. Call this
+** record number R. The next on the stack is an index key created
+** using MakeIdxKey. Call it K. This instruction pops R from the
+** stack but it leaves K unchanged.
+**
+** P1 is an index. So all but the last four bytes of K are an
+** index string. The last four bytes of K are a record number.
+**
+** This instruction asks if there is an entry in P1 where the
+** index string matches K but the record number is different
+** from R. If there is no such entry, then there is an immediate
+** jump to P2. If any entry does exist where the index string
+** matches K but the record number is not R, then the record
+** number for that entry is pushed onto the stack and control
+** falls through to the next instruction.
+**
+** See also: Distinct, NotFound, NotExists, Found
+*/
+case OP_IsUnique: {
+ int i = pOp->p1;
+ Mem *pNos = &pTos[-1];
+ BtCursor *pCrsr;
+ int R;
+
+ /* Pop the value R off the top of the stack
+ */
+ assert( pNos>=p->aStack );
+ Integerify(pTos);
+ R = pTos->i;
+ pTos--;
+ assert( i>=0 && i<=p->nCursor );
+ if( (pCrsr = p->aCsr[i].pCursor)!=0 ){
+ int res, rc;
+ int v; /* The record number on the P1 entry that matches K */
+ char *zKey; /* The value of K */
+ int nKey; /* Number of bytes in K */
+
+ /* Make sure K is a string and make zKey point to K
+ */
+ Stringify(pNos);
+ zKey = pNos->z;
+ nKey = pNos->n;
+ assert( nKey >= 4 );
+
+ /* Search for an entry in P1 where all but the last four bytes match K.
+ ** If there is no such entry, jump immediately to P2.
+ */
+ assert( p->aCsr[i].deferredMoveto==0 );
+ rc = sqliteBtreeMoveto(pCrsr, zKey, nKey-4, &res);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ if( res<0 ){
+ rc = sqliteBtreeNext(pCrsr, &res);
+ if( res ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+ }
+ rc = sqliteBtreeKeyCompare(pCrsr, zKey, nKey-4, 4, &res);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ if( res>0 ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+
+ /* At this point, pCrsr is pointing to an entry in P1 where all but
+ ** the last for bytes of the key match K. Check to see if the last
+ ** four bytes of the key are different from R. If the last four
+ ** bytes equal R then jump immediately to P2.
+ */
+ sqliteBtreeKey(pCrsr, nKey - 4, 4, (char*)&v);
+ v = keyToInt(v);
+ if( v==R ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+
+ /* The last four bytes of the key are different from R. Convert the
+ ** last four bytes of the key into an integer and push it onto the
+ ** stack. (These bytes are the record number of an entry that
+ ** violates a UNIQUE constraint.)
+ */
+ pTos++;
+ pTos->i = v;
+ pTos->flags = MEM_Int;
+ }
+ break;
+}
+
+/* Opcode: NotExists P1 P2 *
+**
+** Use the top of the stack as a integer key. If a record with that key
+** does not exist in table of P1, then jump to P2. If the record
+** does exist, then fall thru. The cursor is left pointing to the
+** record if it exists. The integer key is popped from the stack.
+**
+** The difference between this operation and NotFound is that this
+** operation assumes the key is an integer and NotFound assumes it
+** is a string.
+**
+** See also: Distinct, Found, MoveTo, NotFound, IsUnique
+*/
+case OP_NotExists: {
+ int i = pOp->p1;
+ BtCursor *pCrsr;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ if( (pCrsr = p->aCsr[i].pCursor)!=0 ){
+ int res, rx, iKey;
+ assert( pTos->flags & MEM_Int );
+ iKey = intToKey(pTos->i);
+ rx = sqliteBtreeMoveto(pCrsr, (char*)&iKey, sizeof(int), &res);
+ p->aCsr[i].lastRecno = pTos->i;
+ p->aCsr[i].recnoIsValid = res==0;
+ p->aCsr[i].nullRow = 0;
+ if( rx!=SQLITE_OK || res!=0 ){
+ pc = pOp->p2 - 1;
+ p->aCsr[i].recnoIsValid = 0;
+ }
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: NewRecno P1 * *
+**
+** Get a new integer record number used as the key to a table.
+** The record number is not previously used as a key in the database
+** table that cursor P1 points to. The new record number is pushed
+** onto the stack.
+*/
+case OP_NewRecno: {
+ int i = pOp->p1;
+ int v = 0;
+ Cursor *pC;
+ assert( i>=0 && i<p->nCursor );
+ if( (pC = &p->aCsr[i])->pCursor==0 ){
+ v = 0;
+ }else{
+ /* The next rowid or record number (different terms for the same
+ ** thing) is obtained in a two-step algorithm.
+ **
+ ** First we attempt to find the largest existing rowid and add one
+ ** to that. But if the largest existing rowid is already the maximum
+ ** positive integer, we have to fall through to the second
+ ** probabilistic algorithm
+ **
+ ** The second algorithm is to select a rowid at random and see if
+ ** it already exists in the table. If it does not exist, we have
+ ** succeeded. If the random rowid does exist, we select a new one
+ ** and try again, up to 1000 times.
+ **
+ ** For a table with less than 2 billion entries, the probability
+ ** of not finding a unused rowid is about 1.0e-300. This is a
+ ** non-zero probability, but it is still vanishingly small and should
+ ** never cause a problem. You are much, much more likely to have a
+ ** hardware failure than for this algorithm to fail.
+ **
+ ** The analysis in the previous paragraph assumes that you have a good
+ ** source of random numbers. Is a library function like lrand48()
+ ** good enough? Maybe. Maybe not. It's hard to know whether there
+ ** might be subtle bugs is some implementations of lrand48() that
+ ** could cause problems. To avoid uncertainty, SQLite uses its own
+ ** random number generator based on the RC4 algorithm.
+ **
+ ** To promote locality of reference for repetitive inserts, the
+ ** first few attempts at chosing a random rowid pick values just a little
+ ** larger than the previous rowid. This has been shown experimentally
+ ** to double the speed of the COPY operation.
+ */
+ int res, rx, cnt, x;
+ cnt = 0;
+ if( !pC->useRandomRowid ){
+ if( pC->nextRowidValid ){
+ v = pC->nextRowid;
+ }else{
+ rx = sqliteBtreeLast(pC->pCursor, &res);
+ if( res ){
+ v = 1;
+ }else{
+ sqliteBtreeKey(pC->pCursor, 0, sizeof(v), (void*)&v);
+ v = keyToInt(v);
+ if( v==0x7fffffff ){
+ pC->useRandomRowid = 1;
+ }else{
+ v++;
+ }
+ }
+ }
+ if( v<0x7fffffff ){
+ pC->nextRowidValid = 1;
+ pC->nextRowid = v+1;
+ }else{
+ pC->nextRowidValid = 0;
+ }
+ }
+ if( pC->useRandomRowid ){
+ v = db->priorNewRowid;
+ cnt = 0;
+ do{
+ if( v==0 || cnt>2 ){
+ sqliteRandomness(sizeof(v), &v);
+ if( cnt<5 ) v &= 0xffffff;
+ }else{
+ unsigned char r;
+ sqliteRandomness(1, &r);
+ v += r + 1;
+ }
+ if( v==0 ) continue;
+ x = intToKey(v);
+ rx = sqliteBtreeMoveto(pC->pCursor, &x, sizeof(int), &res);
+ cnt++;
+ }while( cnt<1000 && rx==SQLITE_OK && res==0 );
+ db->priorNewRowid = v;
+ if( rx==SQLITE_OK && res==0 ){
+ rc = SQLITE_FULL;
+ goto abort_due_to_error;
+ }
+ }
+ pC->recnoIsValid = 0;
+ pC->deferredMoveto = 0;
+ }
+ pTos++;
+ pTos->i = v;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: PutIntKey P1 P2 *
+**
+** Write an entry into the table of cursor P1. A new entry is
+** created if it doesn't already exist or the data for an existing
+** entry is overwritten. The data is the value on the top of the
+** stack. The key is the next value down on the stack. The key must
+** be an integer. The stack is popped twice by this instruction.
+**
+** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is
+** incremented (otherwise not). If the OPFLAG_CSCHANGE flag is set,
+** then the current statement change count is incremented (otherwise not).
+** If the OPFLAG_LASTROWID flag of P2 is set, then rowid is
+** stored for subsequent return by the sqlite_last_insert_rowid() function
+** (otherwise it's unmodified).
+*/
+/* Opcode: PutStrKey P1 * *
+**
+** Write an entry into the table of cursor P1. A new entry is
+** created if it doesn't already exist or the data for an existing
+** entry is overwritten. The data is the value on the top of the
+** stack. The key is the next value down on the stack. The key must
+** be a string. The stack is popped twice by this instruction.
+**
+** P1 may not be a pseudo-table opened using the OpenPseudo opcode.
+*/
+case OP_PutIntKey:
+case OP_PutStrKey: {
+ Mem *pNos = &pTos[-1];
+ int i = pOp->p1;
+ Cursor *pC;
+ assert( pNos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ if( ((pC = &p->aCsr[i])->pCursor!=0 || pC->pseudoTable) ){
+ char *zKey;
+ int nKey, iKey;
+ if( pOp->opcode==OP_PutStrKey ){
+ Stringify(pNos);
+ nKey = pNos->n;
+ zKey = pNos->z;
+ }else{
+ assert( pNos->flags & MEM_Int );
+ nKey = sizeof(int);
+ iKey = intToKey(pNos->i);
+ zKey = (char*)&iKey;
+ if( pOp->p2 & OPFLAG_NCHANGE ) db->nChange++;
+ if( pOp->p2 & OPFLAG_LASTROWID ) db->lastRowid = pNos->i;
+ if( pOp->p2 & OPFLAG_CSCHANGE ) db->csChange++;
+ if( pC->nextRowidValid && pTos->i>=pC->nextRowid ){
+ pC->nextRowidValid = 0;
+ }
+ }
+ if( pTos->flags & MEM_Null ){
+ pTos->z = 0;
+ pTos->n = 0;
+ }else{
+ assert( pTos->flags & MEM_Str );
+ }
+ if( pC->pseudoTable ){
+ /* PutStrKey does not work for pseudo-tables.
+ ** The following assert makes sure we are not trying to use
+ ** PutStrKey on a pseudo-table
+ */
+ assert( pOp->opcode==OP_PutIntKey );
+ sqliteFree(pC->pData);
+ pC->iKey = iKey;
+ pC->nData = pTos->n;
+ if( pTos->flags & MEM_Dyn ){
+ pC->pData = pTos->z;
+ pTos->flags = MEM_Null;
+ }else{
+ pC->pData = sqliteMallocRaw( pC->nData );
+ if( pC->pData ){
+ memcpy(pC->pData, pTos->z, pC->nData);
+ }
+ }
+ pC->nullRow = 0;
+ }else{
+ rc = sqliteBtreeInsert(pC->pCursor, zKey, nKey, pTos->z, pTos->n);
+ }
+ pC->recnoIsValid = 0;
+ pC->deferredMoveto = 0;
+ }
+ popStack(&pTos, 2);
+ break;
+}
+
+/* Opcode: Delete P1 P2 *
+**
+** Delete the record at which the P1 cursor is currently pointing.
+**
+** The cursor will be left pointing at either the next or the previous
+** record in the table. If it is left pointing at the next record, then
+** the next Next instruction will be a no-op. Hence it is OK to delete
+** a record from within an Next loop.
+**
+** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is
+** incremented (otherwise not). If OPFLAG_CSCHANGE flag is set,
+** then the current statement change count is incremented (otherwise not).
+**
+** If P1 is a pseudo-table, then this instruction is a no-op.
+*/
+case OP_Delete: {
+ int i = pOp->p1;
+ Cursor *pC;
+ assert( i>=0 && i<p->nCursor );
+ pC = &p->aCsr[i];
+ if( pC->pCursor!=0 ){
+ sqliteVdbeCursorMoveto(pC);
+ rc = sqliteBtreeDelete(pC->pCursor);
+ pC->nextRowidValid = 0;
+ }
+ if( pOp->p2 & OPFLAG_NCHANGE ) db->nChange++;
+ if( pOp->p2 & OPFLAG_CSCHANGE ) db->csChange++;
+ break;
+}
+
+/* Opcode: SetCounts * * *
+**
+** Called at end of statement. Updates lsChange (last statement change count)
+** and resets csChange (current statement change count) to 0.
+*/
+case OP_SetCounts: {
+ db->lsChange=db->csChange;
+ db->csChange=0;
+ break;
+}
+
+/* Opcode: KeyAsData P1 P2 *
+**
+** Turn the key-as-data mode for cursor P1 either on (if P2==1) or
+** off (if P2==0). In key-as-data mode, the OP_Column opcode pulls
+** data off of the key rather than the data. This is used for
+** processing compound selects.
+*/
+case OP_KeyAsData: {
+ int i = pOp->p1;
+ assert( i>=0 && i<p->nCursor );
+ p->aCsr[i].keyAsData = pOp->p2;
+ break;
+}
+
+/* Opcode: RowData P1 * *
+**
+** Push onto the stack the complete row data for cursor P1.
+** There is no interpretation of the data. It is just copied
+** onto the stack exactly as it is found in the database file.
+**
+** If the cursor is not pointing to a valid row, a NULL is pushed
+** onto the stack.
+*/
+/* Opcode: RowKey P1 * *
+**
+** Push onto the stack the complete row key for cursor P1.
+** There is no interpretation of the key. It is just copied
+** onto the stack exactly as it is found in the database file.
+**
+** If the cursor is not pointing to a valid row, a NULL is pushed
+** onto the stack.
+*/
+case OP_RowKey:
+case OP_RowData: {
+ int i = pOp->p1;
+ Cursor *pC;
+ int n;
+
+ pTos++;
+ assert( i>=0 && i<p->nCursor );
+ pC = &p->aCsr[i];
+ if( pC->nullRow ){
+ pTos->flags = MEM_Null;
+ }else if( pC->pCursor!=0 ){
+ BtCursor *pCrsr = pC->pCursor;
+ sqliteVdbeCursorMoveto(pC);
+ if( pC->nullRow ){
+ pTos->flags = MEM_Null;
+ break;
+ }else if( pC->keyAsData || pOp->opcode==OP_RowKey ){
+ sqliteBtreeKeySize(pCrsr, &n);
+ }else{
+ sqliteBtreeDataSize(pCrsr, &n);
+ }
+ pTos->n = n;
+ if( n<=NBFS ){
+ pTos->flags = MEM_Str | MEM_Short;
+ pTos->z = pTos->zShort;
+ }else{
+ char *z = sqliteMallocRaw( n );
+ if( z==0 ) goto no_mem;
+ pTos->flags = MEM_Str | MEM_Dyn;
+ pTos->z = z;
+ }
+ if( pC->keyAsData || pOp->opcode==OP_RowKey ){
+ sqliteBtreeKey(pCrsr, 0, n, pTos->z);
+ }else{
+ sqliteBtreeData(pCrsr, 0, n, pTos->z);
+ }
+ }else if( pC->pseudoTable ){
+ pTos->n = pC->nData;
+ pTos->z = pC->pData;
+ pTos->flags = MEM_Str|MEM_Ephem;
+ }else{
+ pTos->flags = MEM_Null;
+ }
+ break;
+}
+
+/* Opcode: Column P1 P2 *
+**
+** Interpret the data that cursor P1 points to as
+** a structure built using the MakeRecord instruction.
+** (See the MakeRecord opcode for additional information about
+** the format of the data.)
+** Push onto the stack the value of the P2-th column contained
+** in the data.
+**
+** If the KeyAsData opcode has previously executed on this cursor,
+** then the field might be extracted from the key rather than the
+** data.
+**
+** If P1 is negative, then the record is stored on the stack rather
+** than in a table. For P1==-1, the top of the stack is used.
+** For P1==-2, the next on the stack is used. And so forth. The
+** value pushed is always just a pointer into the record which is
+** stored further down on the stack. The column value is not copied.
+*/
+case OP_Column: {
+ int amt, offset, end, payloadSize;
+ int i = pOp->p1;
+ int p2 = pOp->p2;
+ Cursor *pC;
+ char *zRec;
+ BtCursor *pCrsr;
+ int idxWidth;
+ unsigned char aHdr[10];
+
+ assert( i<p->nCursor );
+ pTos++;
+ if( i<0 ){
+ assert( &pTos[i]>=p->aStack );
+ assert( pTos[i].flags & MEM_Str );
+ zRec = pTos[i].z;
+ payloadSize = pTos[i].n;
+ }else if( (pC = &p->aCsr[i])->pCursor!=0 ){
+ sqliteVdbeCursorMoveto(pC);
+ zRec = 0;
+ pCrsr = pC->pCursor;
+ if( pC->nullRow ){
+ payloadSize = 0;
+ }else if( pC->keyAsData ){
+ sqliteBtreeKeySize(pCrsr, &payloadSize);
+ }else{
+ sqliteBtreeDataSize(pCrsr, &payloadSize);
+ }
+ }else if( pC->pseudoTable ){
+ payloadSize = pC->nData;
+ zRec = pC->pData;
+ assert( payloadSize==0 || zRec!=0 );
+ }else{
+ payloadSize = 0;
+ }
+
+ /* Figure out how many bytes in the column data and where the column
+ ** data begins.
+ */
+ if( payloadSize==0 ){
+ pTos->flags = MEM_Null;
+ break;
+ }else if( payloadSize<256 ){
+ idxWidth = 1;
+ }else if( payloadSize<65536 ){
+ idxWidth = 2;
+ }else{
+ idxWidth = 3;
+ }
+
+ /* Figure out where the requested column is stored and how big it is.
+ */
+ if( payloadSize < idxWidth*(p2+1) ){
+ rc = SQLITE_CORRUPT;
+ goto abort_due_to_error;
+ }
+ if( zRec ){
+ memcpy(aHdr, &zRec[idxWidth*p2], idxWidth*2);
+ }else if( pC->keyAsData ){
+ sqliteBtreeKey(pCrsr, idxWidth*p2, idxWidth*2, (char*)aHdr);
+ }else{
+ sqliteBtreeData(pCrsr, idxWidth*p2, idxWidth*2, (char*)aHdr);
+ }
+ offset = aHdr[0];
+ end = aHdr[idxWidth];
+ if( idxWidth>1 ){
+ offset |= aHdr[1]<<8;
+ end |= aHdr[idxWidth+1]<<8;
+ if( idxWidth>2 ){
+ offset |= aHdr[2]<<16;
+ end |= aHdr[idxWidth+2]<<16;
+ }
+ }
+ amt = end - offset;
+ if( amt<0 || offset<0 || end>payloadSize ){
+ rc = SQLITE_CORRUPT;
+ goto abort_due_to_error;
+ }
+
+ /* amt and offset now hold the offset to the start of data and the
+ ** amount of data. Go get the data and put it on the stack.
+ */
+ pTos->n = amt;
+ if( amt==0 ){
+ pTos->flags = MEM_Null;
+ }else if( zRec ){
+ pTos->flags = MEM_Str | MEM_Ephem;
+ pTos->z = &zRec[offset];
+ }else{
+ if( amt<=NBFS ){
+ pTos->flags = MEM_Str | MEM_Short;
+ pTos->z = pTos->zShort;
+ }else{
+ char *z = sqliteMallocRaw( amt );
+ if( z==0 ) goto no_mem;
+ pTos->flags = MEM_Str | MEM_Dyn;
+ pTos->z = z;
+ }
+ if( pC->keyAsData ){
+ sqliteBtreeKey(pCrsr, offset, amt, pTos->z);
+ }else{
+ sqliteBtreeData(pCrsr, offset, amt, pTos->z);
+ }
+ }
+ break;
+}
+
+/* Opcode: Recno P1 * *
+**
+** Push onto the stack an integer which is the first 4 bytes of the
+** the key to the current entry in a sequential scan of the database
+** file P1. The sequential scan should have been started using the
+** Next opcode.
+*/
+case OP_Recno: {
+ int i = pOp->p1;
+ Cursor *pC;
+ int v;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = &p->aCsr[i];
+ sqliteVdbeCursorMoveto(pC);
+ pTos++;
+ if( pC->recnoIsValid ){
+ v = pC->lastRecno;
+ }else if( pC->pseudoTable ){
+ v = keyToInt(pC->iKey);
+ }else if( pC->nullRow || pC->pCursor==0 ){
+ pTos->flags = MEM_Null;
+ break;
+ }else{
+ assert( pC->pCursor!=0 );
+ sqliteBtreeKey(pC->pCursor, 0, sizeof(u32), (char*)&v);
+ v = keyToInt(v);
+ }
+ pTos->i = v;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: FullKey P1 * *
+**
+** Extract the complete key from the record that cursor P1 is currently
+** pointing to and push the key onto the stack as a string.
+**
+** Compare this opcode to Recno. The Recno opcode extracts the first
+** 4 bytes of the key and pushes those bytes onto the stack as an
+** integer. This instruction pushes the entire key as a string.
+**
+** This opcode may not be used on a pseudo-table.
+*/
+case OP_FullKey: {
+ int i = pOp->p1;
+ BtCursor *pCrsr;
+
+ assert( p->aCsr[i].keyAsData );
+ assert( !p->aCsr[i].pseudoTable );
+ assert( i>=0 && i<p->nCursor );
+ pTos++;
+ if( (pCrsr = p->aCsr[i].pCursor)!=0 ){
+ int amt;
+ char *z;
+
+ sqliteVdbeCursorMoveto(&p->aCsr[i]);
+ sqliteBtreeKeySize(pCrsr, &amt);
+ if( amt<=0 ){
+ rc = SQLITE_CORRUPT;
+ goto abort_due_to_error;
+ }
+ if( amt>NBFS ){
+ z = sqliteMallocRaw( amt );
+ if( z==0 ) goto no_mem;
+ pTos->flags = MEM_Str | MEM_Dyn;
+ }else{
+ z = pTos->zShort;
+ pTos->flags = MEM_Str | MEM_Short;
+ }
+ sqliteBtreeKey(pCrsr, 0, amt, z);
+ pTos->z = z;
+ pTos->n = amt;
+ }
+ break;
+}
+
+/* Opcode: NullRow P1 * *
+**
+** Move the cursor P1 to a null row. Any OP_Column operations
+** that occur while the cursor is on the null row will always push
+** a NULL onto the stack.
+*/
+case OP_NullRow: {
+ int i = pOp->p1;
+
+ assert( i>=0 && i<p->nCursor );
+ p->aCsr[i].nullRow = 1;
+ p->aCsr[i].recnoIsValid = 0;
+ break;
+}
+
+/* Opcode: Last P1 P2 *
+**
+** The next use of the Recno or Column or Next instruction for P1
+** will refer to the last entry in the database table or index.
+** If the table or index is empty and P2>0, then jump immediately to P2.
+** If P2 is 0 or if the table or index is not empty, fall through
+** to the following instruction.
+*/
+case OP_Last: {
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = &p->aCsr[i];
+ if( (pCrsr = pC->pCursor)!=0 ){
+ int res;
+ rc = sqliteBtreeLast(pCrsr, &res);
+ pC->nullRow = res;
+ pC->deferredMoveto = 0;
+ if( res && pOp->p2>0 ){
+ pc = pOp->p2 - 1;
+ }
+ }else{
+ pC->nullRow = 0;
+ }
+ break;
+}
+
+/* Opcode: Rewind P1 P2 *
+**
+** The next use of the Recno or Column or Next instruction for P1
+** will refer to the first entry in the database table or index.
+** If the table or index is empty and P2>0, then jump immediately to P2.
+** If P2 is 0 or if the table or index is not empty, fall through
+** to the following instruction.
+*/
+case OP_Rewind: {
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = &p->aCsr[i];
+ if( (pCrsr = pC->pCursor)!=0 ){
+ int res;
+ rc = sqliteBtreeFirst(pCrsr, &res);
+ pC->atFirst = res==0;
+ pC->nullRow = res;
+ pC->deferredMoveto = 0;
+ if( res && pOp->p2>0 ){
+ pc = pOp->p2 - 1;
+ }
+ }else{
+ pC->nullRow = 0;
+ }
+ break;
+}
+
+/* Opcode: Next P1 P2 *
+**
+** Advance cursor P1 so that it points to the next key/data pair in its
+** table or index. If there are no more key/value pairs then fall through
+** to the following instruction. But if the cursor advance was successful,
+** jump immediately to P2.
+**
+** See also: Prev
+*/
+/* Opcode: Prev P1 P2 *
+**
+** Back up cursor P1 so that it points to the previous key/data pair in its
+** table or index. If there is no previous key/value pairs then fall through
+** to the following instruction. But if the cursor backup was successful,
+** jump immediately to P2.
+*/
+case OP_Prev:
+case OP_Next: {
+ Cursor *pC;
+ BtCursor *pCrsr;
+
+ CHECK_FOR_INTERRUPT;
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = &p->aCsr[pOp->p1];
+ if( (pCrsr = pC->pCursor)!=0 ){
+ int res;
+ if( pC->nullRow ){
+ res = 1;
+ }else{
+ assert( pC->deferredMoveto==0 );
+ rc = pOp->opcode==OP_Next ? sqliteBtreeNext(pCrsr, &res) :
+ sqliteBtreePrevious(pCrsr, &res);
+ pC->nullRow = res;
+ }
+ if( res==0 ){
+ pc = pOp->p2 - 1;
+ sqlite_search_count++;
+ }
+ }else{
+ pC->nullRow = 1;
+ }
+ pC->recnoIsValid = 0;
+ break;
+}
+
+/* Opcode: IdxPut P1 P2 P3
+**
+** The top of the stack holds a SQL index key made using the
+** MakeIdxKey instruction. This opcode writes that key into the
+** index P1. Data for the entry is nil.
+**
+** If P2==1, then the key must be unique. If the key is not unique,
+** the program aborts with a SQLITE_CONSTRAINT error and the database
+** is rolled back. If P3 is not null, then it becomes part of the
+** error message returned with the SQLITE_CONSTRAINT.
+*/
+case OP_IdxPut: {
+ int i = pOp->p1;
+ BtCursor *pCrsr;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ assert( pTos->flags & MEM_Str );
+ if( (pCrsr = p->aCsr[i].pCursor)!=0 ){
+ int nKey = pTos->n;
+ const char *zKey = pTos->z;
+ if( pOp->p2 ){
+ int res, n;
+ assert( nKey >= 4 );
+ rc = sqliteBtreeMoveto(pCrsr, zKey, nKey-4, &res);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ while( res!=0 ){
+ int c;
+ sqliteBtreeKeySize(pCrsr, &n);
+ if( n==nKey
+ && sqliteBtreeKeyCompare(pCrsr, zKey, nKey-4, 4, &c)==SQLITE_OK
+ && c==0
+ ){
+ rc = SQLITE_CONSTRAINT;
+ if( pOp->p3 && pOp->p3[0] ){
+ sqliteSetString(&p->zErrMsg, pOp->p3, (char*)0);
+ }
+ goto abort_due_to_error;
+ }
+ if( res<0 ){
+ sqliteBtreeNext(pCrsr, &res);
+ res = +1;
+ }else{
+ break;
+ }
+ }
+ }
+ rc = sqliteBtreeInsert(pCrsr, zKey, nKey, "", 0);
+ assert( p->aCsr[i].deferredMoveto==0 );
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: IdxDelete P1 * *
+**
+** The top of the stack is an index key built using the MakeIdxKey opcode.
+** This opcode removes that entry from the index.
+*/
+case OP_IdxDelete: {
+ int i = pOp->p1;
+ BtCursor *pCrsr;
+ assert( pTos>=p->aStack );
+ assert( pTos->flags & MEM_Str );
+ assert( i>=0 && i<p->nCursor );
+ if( (pCrsr = p->aCsr[i].pCursor)!=0 ){
+ int rx, res;
+ rx = sqliteBtreeMoveto(pCrsr, pTos->z, pTos->n, &res);
+ if( rx==SQLITE_OK && res==0 ){
+ rc = sqliteBtreeDelete(pCrsr);
+ }
+ assert( p->aCsr[i].deferredMoveto==0 );
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: IdxRecno P1 * *
+**
+** Push onto the stack an integer which is the last 4 bytes of the
+** the key to the current entry in index P1. These 4 bytes should
+** be the record number of the table entry to which this index entry
+** points.
+**
+** See also: Recno, MakeIdxKey.
+*/
+case OP_IdxRecno: {
+ int i = pOp->p1;
+ BtCursor *pCrsr;
+
+ assert( i>=0 && i<p->nCursor );
+ pTos++;
+ if( (pCrsr = p->aCsr[i].pCursor)!=0 ){
+ int v;
+ int sz;
+ assert( p->aCsr[i].deferredMoveto==0 );
+ sqliteBtreeKeySize(pCrsr, &sz);
+ if( sz<sizeof(u32) ){
+ pTos->flags = MEM_Null;
+ }else{
+ sqliteBtreeKey(pCrsr, sz - sizeof(u32), sizeof(u32), (char*)&v);
+ v = keyToInt(v);
+ pTos->i = v;
+ pTos->flags = MEM_Int;
+ }
+ }else{
+ pTos->flags = MEM_Null;
+ }
+ break;
+}
+
+/* Opcode: IdxGT P1 P2 *
+**
+** Compare the top of the stack against the key on the index entry that
+** cursor P1 is currently pointing to. Ignore the last 4 bytes of the
+** index entry. If the index entry is greater than the top of the stack
+** then jump to P2. Otherwise fall through to the next instruction.
+** In either case, the stack is popped once.
+*/
+/* Opcode: IdxGE P1 P2 *
+**
+** Compare the top of the stack against the key on the index entry that
+** cursor P1 is currently pointing to. Ignore the last 4 bytes of the
+** index entry. If the index entry is greater than or equal to
+** the top of the stack
+** then jump to P2. Otherwise fall through to the next instruction.
+** In either case, the stack is popped once.
+*/
+/* Opcode: IdxLT P1 P2 *
+**
+** Compare the top of the stack against the key on the index entry that
+** cursor P1 is currently pointing to. Ignore the last 4 bytes of the
+** index entry. If the index entry is less than the top of the stack
+** then jump to P2. Otherwise fall through to the next instruction.
+** In either case, the stack is popped once.
+*/
+case OP_IdxLT:
+case OP_IdxGT:
+case OP_IdxGE: {
+ int i= pOp->p1;
+ BtCursor *pCrsr;
+
+ assert( i>=0 && i<p->nCursor );
+ assert( pTos>=p->aStack );
+ if( (pCrsr = p->aCsr[i].pCursor)!=0 ){
+ int res, rc;
+
+ Stringify(pTos);
+ assert( p->aCsr[i].deferredMoveto==0 );
+ rc = sqliteBtreeKeyCompare(pCrsr, pTos->z, pTos->n, 4, &res);
+ if( rc!=SQLITE_OK ){
+ break;
+ }
+ if( pOp->opcode==OP_IdxLT ){
+ res = -res;
+ }else if( pOp->opcode==OP_IdxGE ){
+ res++;
+ }
+ if( res>0 ){
+ pc = pOp->p2 - 1 ;
+ }
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: IdxIsNull P1 P2 *
+**
+** The top of the stack contains an index entry such as might be generated
+** by the MakeIdxKey opcode. This routine looks at the first P1 fields of
+** that key. If any of the first P1 fields are NULL, then a jump is made
+** to address P2. Otherwise we fall straight through.
+**
+** The index entry is always popped from the stack.
+*/
+case OP_IdxIsNull: {
+ int i = pOp->p1;
+ int k, n;
+ const char *z;
+
+ assert( pTos>=p->aStack );
+ assert( pTos->flags & MEM_Str );
+ z = pTos->z;
+ n = pTos->n;
+ for(k=0; k<n && i>0; i--){
+ if( z[k]=='a' ){
+ pc = pOp->p2-1;
+ break;
+ }
+ while( k<n && z[k] ){ k++; }
+ k++;
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: Destroy P1 P2 *
+**
+** Delete an entire database table or index whose root page in the database
+** file is given by P1.
+**
+** The table being destroyed is in the main database file if P2==0. If
+** P2==1 then the table to be clear is in the auxiliary database file
+** that is used to store tables create using CREATE TEMPORARY TABLE.
+**
+** See also: Clear
+*/
+case OP_Destroy: {
+ rc = sqliteBtreeDropTable(db->aDb[pOp->p2].pBt, pOp->p1);
+ break;
+}
+
+/* Opcode: Clear P1 P2 *
+**
+** Delete all contents of the database table or index whose root page
+** in the database file is given by P1. But, unlike Destroy, do not
+** remove the table or index from the database file.
+**
+** The table being clear is in the main database file if P2==0. If
+** P2==1 then the table to be clear is in the auxiliary database file
+** that is used to store tables create using CREATE TEMPORARY TABLE.
+**
+** See also: Destroy
+*/
+case OP_Clear: {
+ rc = sqliteBtreeClearTable(db->aDb[pOp->p2].pBt, pOp->p1);
+ break;
+}
+
+/* Opcode: CreateTable * P2 P3
+**
+** Allocate a new table in the main database file if P2==0 or in the
+** auxiliary database file if P2==1. Push the page number
+** for the root page of the new table onto the stack.
+**
+** The root page number is also written to a memory location that P3
+** points to. This is the mechanism is used to write the root page
+** number into the parser's internal data structures that describe the
+** new table.
+**
+** The difference between a table and an index is this: A table must
+** have a 4-byte integer key and can have arbitrary data. An index
+** has an arbitrary key but no data.
+**
+** See also: CreateIndex
+*/
+/* Opcode: CreateIndex * P2 P3
+**
+** Allocate a new index in the main database file if P2==0 or in the
+** auxiliary database file if P2==1. Push the page number of the
+** root page of the new index onto the stack.
+**
+** See documentation on OP_CreateTable for additional information.
+*/
+case OP_CreateIndex:
+case OP_CreateTable: {
+ int pgno;
+ assert( pOp->p3!=0 && pOp->p3type==P3_POINTER );
+ assert( pOp->p2>=0 && pOp->p2<db->nDb );
+ assert( db->aDb[pOp->p2].pBt!=0 );
+ if( pOp->opcode==OP_CreateTable ){
+ rc = sqliteBtreeCreateTable(db->aDb[pOp->p2].pBt, &pgno);
+ }else{
+ rc = sqliteBtreeCreateIndex(db->aDb[pOp->p2].pBt, &pgno);
+ }
+ pTos++;
+ if( rc==SQLITE_OK ){
+ pTos->i = pgno;
+ pTos->flags = MEM_Int;
+ *(u32*)pOp->p3 = pgno;
+ pOp->p3 = 0;
+ }else{
+ pTos->flags = MEM_Null;
+ }
+ break;
+}
+
+/* Opcode: IntegrityCk P1 P2 *
+**
+** Do an analysis of the currently open database. Push onto the
+** stack the text of an error message describing any problems.
+** If there are no errors, push a "ok" onto the stack.
+**
+** P1 is the index of a set that contains the root page numbers
+** for all tables and indices in the main database file. The set
+** is cleared by this opcode. In other words, after this opcode
+** has executed, the set will be empty.
+**
+** If P2 is not zero, the check is done on the auxiliary database
+** file, not the main database file.
+**
+** This opcode is used for testing purposes only.
+*/
+case OP_IntegrityCk: {
+ int nRoot;
+ int *aRoot;
+ int iSet = pOp->p1;
+ Set *pSet;
+ int j;
+ HashElem *i;
+ char *z;
+
+ assert( iSet>=0 && iSet<p->nSet );
+ pTos++;
+ pSet = &p->aSet[iSet];
+ nRoot = sqliteHashCount(&pSet->hash);
+ aRoot = sqliteMallocRaw( sizeof(int)*(nRoot+1) );
+ if( aRoot==0 ) goto no_mem;
+ for(j=0, i=sqliteHashFirst(&pSet->hash); i; i=sqliteHashNext(i), j++){
+ toInt((char*)sqliteHashKey(i), &aRoot[j]);
+ }
+ aRoot[j] = 0;
+ sqliteHashClear(&pSet->hash);
+ pSet->prev = 0;
+ z = sqliteBtreeIntegrityCheck(db->aDb[pOp->p2].pBt, aRoot, nRoot);
+ if( z==0 || z[0]==0 ){
+ if( z ) sqliteFree(z);
+ pTos->z = "ok";
+ pTos->n = 3;
+ pTos->flags = MEM_Str | MEM_Static;
+ }else{
+ pTos->z = z;
+ pTos->n = strlen(z) + 1;
+ pTos->flags = MEM_Str | MEM_Dyn;
+ }
+ sqliteFree(aRoot);
+ break;
+}
+
+/* Opcode: ListWrite * * *
+**
+** Write the integer on the top of the stack
+** into the temporary storage list.
+*/
+case OP_ListWrite: {
+ Keylist *pKeylist;
+ assert( pTos>=p->aStack );
+ pKeylist = p->pList;
+ if( pKeylist==0 || pKeylist->nUsed>=pKeylist->nKey ){
+ pKeylist = sqliteMallocRaw( sizeof(Keylist)+999*sizeof(pKeylist->aKey[0]) );
+ if( pKeylist==0 ) goto no_mem;
+ pKeylist->nKey = 1000;
+ pKeylist->nRead = 0;
+ pKeylist->nUsed = 0;
+ pKeylist->pNext = p->pList;
+ p->pList = pKeylist;
+ }
+ Integerify(pTos);
+ pKeylist->aKey[pKeylist->nUsed++] = pTos->i;
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: ListRewind * * *
+**
+** Rewind the temporary buffer back to the beginning.
+*/
+case OP_ListRewind: {
+ /* What this opcode codes, really, is reverse the order of the
+ ** linked list of Keylist structures so that they are read out
+ ** in the same order that they were read in. */
+ Keylist *pRev, *pTop;
+ pRev = 0;
+ while( p->pList ){
+ pTop = p->pList;
+ p->pList = pTop->pNext;
+ pTop->pNext = pRev;
+ pRev = pTop;
+ }
+ p->pList = pRev;
+ break;
+}
+
+/* Opcode: ListRead * P2 *
+**
+** Attempt to read an integer from the temporary storage buffer
+** and push it onto the stack. If the storage buffer is empty,
+** push nothing but instead jump to P2.
+*/
+case OP_ListRead: {
+ Keylist *pKeylist;
+ CHECK_FOR_INTERRUPT;
+ pKeylist = p->pList;
+ if( pKeylist!=0 ){
+ assert( pKeylist->nRead>=0 );
+ assert( pKeylist->nRead<pKeylist->nUsed );
+ assert( pKeylist->nRead<pKeylist->nKey );
+ pTos++;
+ pTos->i = pKeylist->aKey[pKeylist->nRead++];
+ pTos->flags = MEM_Int;
+ if( pKeylist->nRead>=pKeylist->nUsed ){
+ p->pList = pKeylist->pNext;
+ sqliteFree(pKeylist);
+ }
+ }else{
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: ListReset * * *
+**
+** Reset the temporary storage buffer so that it holds nothing.
+*/
+case OP_ListReset: {
+ if( p->pList ){
+ sqliteVdbeKeylistFree(p->pList);
+ p->pList = 0;
+ }
+ break;
+}
+
+/* Opcode: ListPush * * *
+**
+** Save the current Vdbe list such that it can be restored by a ListPop
+** opcode. The list is empty after this is executed.
+*/
+case OP_ListPush: {
+ p->keylistStackDepth++;
+ assert(p->keylistStackDepth > 0);
+ p->keylistStack = sqliteRealloc(p->keylistStack,
+ sizeof(Keylist *) * p->keylistStackDepth);
+ if( p->keylistStack==0 ) goto no_mem;
+ p->keylistStack[p->keylistStackDepth - 1] = p->pList;
+ p->pList = 0;
+ break;
+}
+
+/* Opcode: ListPop * * *
+**
+** Restore the Vdbe list to the state it was in when ListPush was last
+** executed.
+*/
+case OP_ListPop: {
+ assert(p->keylistStackDepth > 0);
+ p->keylistStackDepth--;
+ sqliteVdbeKeylistFree(p->pList);
+ p->pList = p->keylistStack[p->keylistStackDepth];
+ p->keylistStack[p->keylistStackDepth] = 0;
+ if( p->keylistStackDepth == 0 ){
+ sqliteFree(p->keylistStack);
+ p->keylistStack = 0;
+ }
+ break;
+}
+
+/* Opcode: ContextPush * * *
+**
+** Save the current Vdbe context such that it can be restored by a ContextPop
+** opcode. The context stores the last insert row id, the last statement change
+** count, and the current statement change count.
+*/
+case OP_ContextPush: {
+ p->contextStackDepth++;
+ assert(p->contextStackDepth > 0);
+ p->contextStack = sqliteRealloc(p->contextStack,
+ sizeof(Context) * p->contextStackDepth);
+ if( p->contextStack==0 ) goto no_mem;
+ p->contextStack[p->contextStackDepth - 1].lastRowid = p->db->lastRowid;
+ p->contextStack[p->contextStackDepth - 1].lsChange = p->db->lsChange;
+ p->contextStack[p->contextStackDepth - 1].csChange = p->db->csChange;
+ break;
+}
+
+/* Opcode: ContextPop * * *
+**
+** Restore the Vdbe context to the state it was in when contextPush was last
+** executed. The context stores the last insert row id, the last statement
+** change count, and the current statement change count.
+*/
+case OP_ContextPop: {
+ assert(p->contextStackDepth > 0);
+ p->contextStackDepth--;
+ p->db->lastRowid = p->contextStack[p->contextStackDepth].lastRowid;
+ p->db->lsChange = p->contextStack[p->contextStackDepth].lsChange;
+ p->db->csChange = p->contextStack[p->contextStackDepth].csChange;
+ if( p->contextStackDepth == 0 ){
+ sqliteFree(p->contextStack);
+ p->contextStack = 0;
+ }
+ break;
+}
+
+/* Opcode: SortPut * * *
+**
+** The TOS is the key and the NOS is the data. Pop both from the stack
+** and put them on the sorter. The key and data should have been
+** made using SortMakeKey and SortMakeRec, respectively.
+*/
+case OP_SortPut: {
+ Mem *pNos = &pTos[-1];
+ Sorter *pSorter;
+ assert( pNos>=p->aStack );
+ if( Dynamicify(pTos) || Dynamicify(pNos) ) goto no_mem;
+ pSorter = sqliteMallocRaw( sizeof(Sorter) );
+ if( pSorter==0 ) goto no_mem;
+ pSorter->pNext = p->pSort;
+ p->pSort = pSorter;
+ assert( pTos->flags & MEM_Dyn );
+ pSorter->nKey = pTos->n;
+ pSorter->zKey = pTos->z;
+ assert( pNos->flags & MEM_Dyn );
+ pSorter->nData = pNos->n;
+ pSorter->pData = pNos->z;
+ pTos -= 2;
+ break;
+}
+
+/* Opcode: SortMakeRec P1 * *
+**
+** The top P1 elements are the arguments to a callback. Form these
+** elements into a single data entry that can be stored on a sorter
+** using SortPut and later fed to a callback using SortCallback.
+*/
+case OP_SortMakeRec: {
+ char *z;
+ char **azArg;
+ int nByte;
+ int nField;
+ int i;
+ Mem *pRec;
+
+ nField = pOp->p1;
+ pRec = &pTos[1-nField];
+ assert( pRec>=p->aStack );
+ nByte = 0;
+ for(i=0; i<nField; i++, pRec++){
+ if( (pRec->flags & MEM_Null)==0 ){
+ Stringify(pRec);
+ nByte += pRec->n;
+ }
+ }
+ nByte += sizeof(char*)*(nField+1);
+ azArg = sqliteMallocRaw( nByte );
+ if( azArg==0 ) goto no_mem;
+ z = (char*)&azArg[nField+1];
+ for(pRec=&pTos[1-nField], i=0; i<nField; i++, pRec++){
+ if( pRec->flags & MEM_Null ){
+ azArg[i] = 0;
+ }else{
+ azArg[i] = z;
+ memcpy(z, pRec->z, pRec->n);
+ z += pRec->n;
+ }
+ }
+ popStack(&pTos, nField);
+ pTos++;
+ pTos->n = nByte;
+ pTos->z = (char*)azArg;
+ pTos->flags = MEM_Str | MEM_Dyn;
+ break;
+}
+
+/* Opcode: SortMakeKey * * P3
+**
+** Convert the top few entries of the stack into a sort key. The
+** number of stack entries consumed is the number of characters in
+** the string P3. One character from P3 is prepended to each entry.
+** The first character of P3 is prepended to the element lowest in
+** the stack and the last character of P3 is prepended to the top of
+** the stack. All stack entries are separated by a \000 character
+** in the result. The whole key is terminated by two \000 characters
+** in a row.
+**
+** "N" is substituted in place of the P3 character for NULL values.
+**
+** See also the MakeKey and MakeIdxKey opcodes.
+*/
+case OP_SortMakeKey: {
+ char *zNewKey;
+ int nByte;
+ int nField;
+ int i, j, k;
+ Mem *pRec;
+
+ nField = strlen(pOp->p3);
+ pRec = &pTos[1-nField];
+ nByte = 1;
+ for(i=0; i<nField; i++, pRec++){
+ if( pRec->flags & MEM_Null ){
+ nByte += 2;
+ }else{
+ Stringify(pRec);
+ nByte += pRec->n+2;
+ }
+ }
+ zNewKey = sqliteMallocRaw( nByte );
+ if( zNewKey==0 ) goto no_mem;
+ j = 0;
+ k = 0;
+ for(pRec=&pTos[1-nField], i=0; i<nField; i++, pRec++){
+ if( pRec->flags & MEM_Null ){
+ zNewKey[j++] = 'N';
+ zNewKey[j++] = 0;
+ k++;
+ }else{
+ zNewKey[j++] = pOp->p3[k++];
+ memcpy(&zNewKey[j], pRec->z, pRec->n-1);
+ j += pRec->n-1;
+ zNewKey[j++] = 0;
+ }
+ }
+ zNewKey[j] = 0;
+ assert( j<nByte );
+ popStack(&pTos, nField);
+ pTos++;
+ pTos->n = nByte;
+ pTos->flags = MEM_Str|MEM_Dyn;
+ pTos->z = zNewKey;
+ break;
+}
+
+/* Opcode: Sort * * *
+**
+** Sort all elements on the sorter. The algorithm is a
+** mergesort.
+*/
+case OP_Sort: {
+ int i;
+ Sorter *pElem;
+ Sorter *apSorter[NSORT];
+ for(i=0; i<NSORT; i++){
+ apSorter[i] = 0;
+ }
+ while( p->pSort ){
+ pElem = p->pSort;
+ p->pSort = pElem->pNext;
+ pElem->pNext = 0;
+ for(i=0; i<NSORT-1; i++){
+ if( apSorter[i]==0 ){
+ apSorter[i] = pElem;
+ break;
+ }else{
+ pElem = Merge(apSorter[i], pElem);
+ apSorter[i] = 0;
+ }
+ }
+ if( i>=NSORT-1 ){
+ apSorter[NSORT-1] = Merge(apSorter[NSORT-1],pElem);
+ }
+ }
+ pElem = 0;
+ for(i=0; i<NSORT; i++){
+ pElem = Merge(apSorter[i], pElem);
+ }
+ p->pSort = pElem;
+ break;
+}
+
+/* Opcode: SortNext * P2 *
+**
+** Push the data for the topmost element in the sorter onto the
+** stack, then remove the element from the sorter. If the sorter
+** is empty, push nothing on the stack and instead jump immediately
+** to instruction P2.
+*/
+case OP_SortNext: {
+ Sorter *pSorter = p->pSort;
+ CHECK_FOR_INTERRUPT;
+ if( pSorter!=0 ){
+ p->pSort = pSorter->pNext;
+ pTos++;
+ pTos->z = pSorter->pData;
+ pTos->n = pSorter->nData;
+ pTos->flags = MEM_Str|MEM_Dyn;
+ sqliteFree(pSorter->zKey);
+ sqliteFree(pSorter);
+ }else{
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: SortCallback P1 * *
+**
+** The top of the stack contains a callback record built using
+** the SortMakeRec operation with the same P1 value as this
+** instruction. Pop this record from the stack and invoke the
+** callback on it.
+*/
+case OP_SortCallback: {
+ assert( pTos>=p->aStack );
+ assert( pTos->flags & MEM_Str );
+ p->nCallback++;
+ p->pc = pc+1;
+ p->azResColumn = (char**)pTos->z;
+ assert( p->nResColumn==pOp->p1 );
+ p->popStack = 1;
+ p->pTos = pTos;
+ return SQLITE_ROW;
+}
+
+/* Opcode: SortReset * * *
+**
+** Remove any elements that remain on the sorter.
+*/
+case OP_SortReset: {
+ sqliteVdbeSorterReset(p);
+ break;
+}
+
+/* Opcode: FileOpen * * P3
+**
+** Open the file named by P3 for reading using the FileRead opcode.
+** If P3 is "stdin" then open standard input for reading.
+*/
+case OP_FileOpen: {
+ assert( pOp->p3!=0 );
+ if( p->pFile ){
+ if( p->pFile!=stdin ) fclose(p->pFile);
+ p->pFile = 0;
+ }
+ if( sqliteStrICmp(pOp->p3,"stdin")==0 ){
+ p->pFile = stdin;
+ }else{
+ p->pFile = fopen(pOp->p3, "r");
+ }
+ if( p->pFile==0 ){
+ sqliteSetString(&p->zErrMsg,"unable to open file: ", pOp->p3, (char*)0);
+ rc = SQLITE_ERROR;
+ }
+ break;
+}
+
+/* Opcode: FileRead P1 P2 P3
+**
+** Read a single line of input from the open file (the file opened using
+** FileOpen). If we reach end-of-file, jump immediately to P2. If
+** we are able to get another line, split the line apart using P3 as
+** a delimiter. There should be P1 fields. If the input line contains
+** more than P1 fields, ignore the excess. If the input line contains
+** fewer than P1 fields, assume the remaining fields contain NULLs.
+**
+** Input ends if a line consists of just "\.". A field containing only
+** "\N" is a null field. The backslash \ character can be used be used
+** to escape newlines or the delimiter.
+*/
+case OP_FileRead: {
+ int n, eol, nField, i, c, nDelim;
+ char *zDelim, *z;
+ CHECK_FOR_INTERRUPT;
+ if( p->pFile==0 ) goto fileread_jump;
+ nField = pOp->p1;
+ if( nField<=0 ) goto fileread_jump;
+ if( nField!=p->nField || p->azField==0 ){
+ char **azField = sqliteRealloc(p->azField, sizeof(char*)*nField+1);
+ if( azField==0 ){ goto no_mem; }
+ p->azField = azField;
+ p->nField = nField;
+ }
+ n = 0;
+ eol = 0;
+ while( eol==0 ){
+ if( p->zLine==0 || n+200>p->nLineAlloc ){
+ char *zLine;
+ p->nLineAlloc = p->nLineAlloc*2 + 300;
+ zLine = sqliteRealloc(p->zLine, p->nLineAlloc);
+ if( zLine==0 ){
+ p->nLineAlloc = 0;
+ sqliteFree(p->zLine);
+ p->zLine = 0;
+ goto no_mem;
+ }
+ p->zLine = zLine;
+ }
+ if( vdbe_fgets(&p->zLine[n], p->nLineAlloc-n, p->pFile)==0 ){
+ eol = 1;
+ p->zLine[n] = 0;
+ }else{
+ int c;
+ while( (c = p->zLine[n])!=0 ){
+ if( c=='\\' ){
+ if( p->zLine[n+1]==0 ) break;
+ n += 2;
+ }else if( c=='\n' ){
+ p->zLine[n] = 0;
+ eol = 1;
+ break;
+ }else{
+ n++;
+ }
+ }
+ }
+ }
+ if( n==0 ) goto fileread_jump;
+ z = p->zLine;
+ if( z[0]=='\\' && z[1]=='.' && z[2]==0 ){
+ goto fileread_jump;
+ }
+ zDelim = pOp->p3;
+ if( zDelim==0 ) zDelim = "\t";
+ c = zDelim[0];
+ nDelim = strlen(zDelim);
+ p->azField[0] = z;
+ for(i=1; *z!=0 && i<=nField; i++){
+ int from, to;
+ from = to = 0;
+ if( z[0]=='\\' && z[1]=='N'
+ && (z[2]==0 || strncmp(&z[2],zDelim,nDelim)==0) ){
+ if( i<=nField ) p->azField[i-1] = 0;
+ z += 2 + nDelim;
+ if( i<nField ) p->azField[i] = z;
+ continue;
+ }
+ while( z[from] ){
+ if( z[from]=='\\' && z[from+1]!=0 ){
+ int tx = z[from+1];
+ switch( tx ){
+ case 'b': tx = '\b'; break;
+ case 'f': tx = '\f'; break;
+ case 'n': tx = '\n'; break;
+ case 'r': tx = '\r'; break;
+ case 't': tx = '\t'; break;
+ case 'v': tx = '\v'; break;
+ default: break;
+ }
+ z[to++] = tx;
+ from += 2;
+ continue;
+ }
+ if( z[from]==c && strncmp(&z[from],zDelim,nDelim)==0 ) break;
+ z[to++] = z[from++];
+ }
+ if( z[from] ){
+ z[to] = 0;
+ z += from + nDelim;
+ if( i<nField ) p->azField[i] = z;
+ }else{
+ z[to] = 0;
+ z = "";
+ }
+ }
+ while( i<nField ){
+ p->azField[i++] = 0;
+ }
+ break;
+
+ /* If we reach end-of-file, or if anything goes wrong, jump here.
+ ** This code will cause a jump to P2 */
+fileread_jump:
+ pc = pOp->p2 - 1;
+ break;
+}
+
+/* Opcode: FileColumn P1 * *
+**
+** Push onto the stack the P1-th column of the most recently read line
+** from the input file.
+*/
+case OP_FileColumn: {
+ int i = pOp->p1;
+ char *z;
+ assert( i>=0 && i<p->nField );
+ if( p->azField ){
+ z = p->azField[i];
+ }else{
+ z = 0;
+ }
+ pTos++;
+ if( z ){
+ pTos->n = strlen(z) + 1;
+ pTos->z = z;
+ pTos->flags = MEM_Str | MEM_Ephem;
+ }else{
+ pTos->flags = MEM_Null;
+ }
+ break;
+}
+
+/* Opcode: MemStore P1 P2 *
+**
+** Write the top of the stack into memory location P1.
+** P1 should be a small integer since space is allocated
+** for all memory locations between 0 and P1 inclusive.
+**
+** After the data is stored in the memory location, the
+** stack is popped once if P2 is 1. If P2 is zero, then
+** the original data remains on the stack.
+*/
+case OP_MemStore: {
+ int i = pOp->p1;
+ Mem *pMem;
+ assert( pTos>=p->aStack );
+ if( i>=p->nMem ){
+ int nOld = p->nMem;
+ Mem *aMem;
+ p->nMem = i + 5;
+ aMem = sqliteRealloc(p->aMem, p->nMem*sizeof(p->aMem[0]));
+ if( aMem==0 ) goto no_mem;
+ if( aMem!=p->aMem ){
+ int j;
+ for(j=0; j<nOld; j++){
+ if( aMem[j].flags & MEM_Short ){
+ aMem[j].z = aMem[j].zShort;
+ }
+ }
+ }
+ p->aMem = aMem;
+ if( nOld<p->nMem ){
+ memset(&p->aMem[nOld], 0, sizeof(p->aMem[0])*(p->nMem-nOld));
+ }
+ }
+ Deephemeralize(pTos);
+ pMem = &p->aMem[i];
+ Release(pMem);
+ *pMem = *pTos;
+ if( pMem->flags & MEM_Dyn ){
+ if( pOp->p2 ){
+ pTos->flags = MEM_Null;
+ }else{
+ pMem->z = sqliteMallocRaw( pMem->n );
+ if( pMem->z==0 ) goto no_mem;
+ memcpy(pMem->z, pTos->z, pMem->n);
+ }
+ }else if( pMem->flags & MEM_Short ){
+ pMem->z = pMem->zShort;
+ }
+ if( pOp->p2 ){
+ Release(pTos);
+ pTos--;
+ }
+ break;
+}
+
+/* Opcode: MemLoad P1 * *
+**
+** Push a copy of the value in memory location P1 onto the stack.
+**
+** If the value is a string, then the value pushed is a pointer to
+** the string that is stored in the memory location. If the memory
+** location is subsequently changed (using OP_MemStore) then the
+** value pushed onto the stack will change too.
+*/
+case OP_MemLoad: {
+ int i = pOp->p1;
+ assert( i>=0 && i<p->nMem );
+ pTos++;
+ memcpy(pTos, &p->aMem[i], sizeof(pTos[0])-NBFS);;
+ if( pTos->flags & MEM_Str ){
+ pTos->flags |= MEM_Ephem;
+ pTos->flags &= ~(MEM_Dyn|MEM_Static|MEM_Short);
+ }
+ break;
+}
+
+/* Opcode: MemIncr P1 P2 *
+**
+** Increment the integer valued memory cell P1 by 1. If P2 is not zero
+** and the result after the increment is greater than zero, then jump
+** to P2.
+**
+** This instruction throws an error if the memory cell is not initially
+** an integer.
+*/
+case OP_MemIncr: {
+ int i = pOp->p1;
+ Mem *pMem;
+ assert( i>=0 && i<p->nMem );
+ pMem = &p->aMem[i];
+ assert( pMem->flags==MEM_Int );
+ pMem->i++;
+ if( pOp->p2>0 && pMem->i>0 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: AggReset * P2 *
+**
+** Reset the aggregator so that it no longer contains any data.
+** Future aggregator elements will contain P2 values each.
+*/
+case OP_AggReset: {
+ sqliteVdbeAggReset(&p->agg);
+ p->agg.nMem = pOp->p2;
+ p->agg.apFunc = sqliteMalloc( p->agg.nMem*sizeof(p->agg.apFunc[0]) );
+ if( p->agg.apFunc==0 ) goto no_mem;
+ break;
+}
+
+/* Opcode: AggInit * P2 P3
+**
+** Initialize the function parameters for an aggregate function.
+** The aggregate will operate out of aggregate column P2.
+** P3 is a pointer to the FuncDef structure for the function.
+*/
+case OP_AggInit: {
+ int i = pOp->p2;
+ assert( i>=0 && i<p->agg.nMem );
+ p->agg.apFunc[i] = (FuncDef*)pOp->p3;
+ break;
+}
+
+/* Opcode: AggFunc * P2 P3
+**
+** Execute the step function for an aggregate. The
+** function has P2 arguments. P3 is a pointer to the FuncDef
+** structure that specifies the function.
+**
+** The top of the stack must be an integer which is the index of
+** the aggregate column that corresponds to this aggregate function.
+** Ideally, this index would be another parameter, but there are
+** no free parameters left. The integer is popped from the stack.
+*/
+case OP_AggFunc: {
+ int n = pOp->p2;
+ int i;
+ Mem *pMem, *pRec;
+ char **azArgv = p->zArgv;
+ sqlite_func ctx;
+
+ assert( n>=0 );
+ assert( pTos->flags==MEM_Int );
+ pRec = &pTos[-n];
+ assert( pRec>=p->aStack );
+ for(i=0; i<n; i++, pRec++){
+ if( pRec->flags & MEM_Null ){
+ azArgv[i] = 0;
+ }else{
+ Stringify(pRec);
+ azArgv[i] = pRec->z;
+ }
+ }
+ i = pTos->i;
+ assert( i>=0 && i<p->agg.nMem );
+ ctx.pFunc = (FuncDef*)pOp->p3;
+ pMem = &p->agg.pCurrent->aMem[i];
+ ctx.s.z = pMem->zShort; /* Space used for small aggregate contexts */
+ ctx.pAgg = pMem->z;
+ ctx.cnt = ++pMem->i;
+ ctx.isError = 0;
+ ctx.isStep = 1;
+ (ctx.pFunc->xStep)(&ctx, n, (const char**)azArgv);
+ pMem->z = ctx.pAgg;
+ pMem->flags = MEM_AggCtx;
+ popStack(&pTos, n+1);
+ if( ctx.isError ){
+ rc = SQLITE_ERROR;
+ }
+ break;
+}
+
+/* Opcode: AggFocus * P2 *
+**
+** Pop the top of the stack and use that as an aggregator key. If
+** an aggregator with that same key already exists, then make the
+** aggregator the current aggregator and jump to P2. If no aggregator
+** with the given key exists, create one and make it current but
+** do not jump.
+**
+** The order of aggregator opcodes is important. The order is:
+** AggReset AggFocus AggNext. In other words, you must execute
+** AggReset first, then zero or more AggFocus operations, then
+** zero or more AggNext operations. You must not execute an AggFocus
+** in between an AggNext and an AggReset.
+*/
+case OP_AggFocus: {
+ AggElem *pElem;
+ char *zKey;
+ int nKey;
+
+ assert( pTos>=p->aStack );
+ Stringify(pTos);
+ zKey = pTos->z;
+ nKey = pTos->n;
+ pElem = sqliteHashFind(&p->agg.hash, zKey, nKey);
+ if( pElem ){
+ p->agg.pCurrent = pElem;
+ pc = pOp->p2 - 1;
+ }else{
+ AggInsert(&p->agg, zKey, nKey);
+ if( sqlite_malloc_failed ) goto no_mem;
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: AggSet * P2 *
+**
+** Move the top of the stack into the P2-th field of the current
+** aggregate. String values are duplicated into new memory.
+*/
+case OP_AggSet: {
+ AggElem *pFocus = AggInFocus(p->agg);
+ Mem *pMem;
+ int i = pOp->p2;
+ assert( pTos>=p->aStack );
+ if( pFocus==0 ) goto no_mem;
+ assert( i>=0 && i<p->agg.nMem );
+ Deephemeralize(pTos);
+ pMem = &pFocus->aMem[i];
+ Release(pMem);
+ *pMem = *pTos;
+ if( pMem->flags & MEM_Dyn ){
+ pTos->flags = MEM_Null;
+ }else if( pMem->flags & MEM_Short ){
+ pMem->z = pMem->zShort;
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: AggGet * P2 *
+**
+** Push a new entry onto the stack which is a copy of the P2-th field
+** of the current aggregate. Strings are not duplicated so
+** string values will be ephemeral.
+*/
+case OP_AggGet: {
+ AggElem *pFocus = AggInFocus(p->agg);
+ Mem *pMem;
+ int i = pOp->p2;
+ if( pFocus==0 ) goto no_mem;
+ assert( i>=0 && i<p->agg.nMem );
+ pTos++;
+ pMem = &pFocus->aMem[i];
+ *pTos = *pMem;
+ if( pTos->flags & MEM_Str ){
+ pTos->flags &= ~(MEM_Dyn|MEM_Static|MEM_Short);
+ pTos->flags |= MEM_Ephem;
+ }
+ if( pTos->flags & MEM_AggCtx ){
+ Release(pTos);
+ pTos->flags = MEM_Null;
+ }
+ break;
+}
+
+/* Opcode: AggNext * P2 *
+**
+** Make the next aggregate value the current aggregate. The prior
+** aggregate is deleted. If all aggregate values have been consumed,
+** jump to P2.
+**
+** The order of aggregator opcodes is important. The order is:
+** AggReset AggFocus AggNext. In other words, you must execute
+** AggReset first, then zero or more AggFocus operations, then
+** zero or more AggNext operations. You must not execute an AggFocus
+** in between an AggNext and an AggReset.
+*/
+case OP_AggNext: {
+ CHECK_FOR_INTERRUPT;
+ if( p->agg.pSearch==0 ){
+ p->agg.pSearch = sqliteHashFirst(&p->agg.hash);
+ }else{
+ p->agg.pSearch = sqliteHashNext(p->agg.pSearch);
+ }
+ if( p->agg.pSearch==0 ){
+ pc = pOp->p2 - 1;
+ } else {
+ int i;
+ sqlite_func ctx;
+ Mem *aMem;
+ p->agg.pCurrent = sqliteHashData(p->agg.pSearch);
+ aMem = p->agg.pCurrent->aMem;
+ for(i=0; i<p->agg.nMem; i++){
+ int freeCtx;
+ if( p->agg.apFunc[i]==0 ) continue;
+ if( p->agg.apFunc[i]->xFinalize==0 ) continue;
+ ctx.s.flags = MEM_Null;
+ ctx.s.z = aMem[i].zShort;
+ ctx.pAgg = (void*)aMem[i].z;
+ freeCtx = aMem[i].z && aMem[i].z!=aMem[i].zShort;
+ ctx.cnt = aMem[i].i;
+ ctx.isStep = 0;
+ ctx.pFunc = p->agg.apFunc[i];
+ (*p->agg.apFunc[i]->xFinalize)(&ctx);
+ if( freeCtx ){
+ sqliteFree( aMem[i].z );
+ }
+ aMem[i] = ctx.s;
+ if( aMem[i].flags & MEM_Short ){
+ aMem[i].z = aMem[i].zShort;
+ }
+ }
+ }
+ break;
+}
+
+/* Opcode: SetInsert P1 * P3
+**
+** If Set P1 does not exist then create it. Then insert value
+** P3 into that set. If P3 is NULL, then insert the top of the
+** stack into the set.
+*/
+case OP_SetInsert: {
+ int i = pOp->p1;
+ if( p->nSet<=i ){
+ int k;
+ Set *aSet = sqliteRealloc(p->aSet, (i+1)*sizeof(p->aSet[0]) );
+ if( aSet==0 ) goto no_mem;
+ p->aSet = aSet;
+ for(k=p->nSet; k<=i; k++){
+ sqliteHashInit(&p->aSet[k].hash, SQLITE_HASH_BINARY, 1);
+ }
+ p->nSet = i+1;
+ }
+ if( pOp->p3 ){
+ sqliteHashInsert(&p->aSet[i].hash, pOp->p3, strlen(pOp->p3)+1, p);
+ }else{
+ assert( pTos>=p->aStack );
+ Stringify(pTos);
+ sqliteHashInsert(&p->aSet[i].hash, pTos->z, pTos->n, p);
+ Release(pTos);
+ pTos--;
+ }
+ if( sqlite_malloc_failed ) goto no_mem;
+ break;
+}
+
+/* Opcode: SetFound P1 P2 *
+**
+** Pop the stack once and compare the value popped off with the
+** contents of set P1. If the element popped exists in set P1,
+** then jump to P2. Otherwise fall through.
+*/
+case OP_SetFound: {
+ int i = pOp->p1;
+ assert( pTos>=p->aStack );
+ Stringify(pTos);
+ if( i>=0 && i<p->nSet && sqliteHashFind(&p->aSet[i].hash, pTos->z, pTos->n)){
+ pc = pOp->p2 - 1;
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: SetNotFound P1 P2 *
+**
+** Pop the stack once and compare the value popped off with the
+** contents of set P1. If the element popped does not exists in
+** set P1, then jump to P2. Otherwise fall through.
+*/
+case OP_SetNotFound: {
+ int i = pOp->p1;
+ assert( pTos>=p->aStack );
+ Stringify(pTos);
+ if( i<0 || i>=p->nSet ||
+ sqliteHashFind(&p->aSet[i].hash, pTos->z, pTos->n)==0 ){
+ pc = pOp->p2 - 1;
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: SetFirst P1 P2 *
+**
+** Read the first element from set P1 and push it onto the stack. If the
+** set is empty, push nothing and jump immediately to P2. This opcode is
+** used in combination with OP_SetNext to loop over all elements of a set.
+*/
+/* Opcode: SetNext P1 P2 *
+**
+** Read the next element from set P1 and push it onto the stack. If there
+** are no more elements in the set, do not do the push and fall through.
+** Otherwise, jump to P2 after pushing the next set element.
+*/
+case OP_SetFirst:
+case OP_SetNext: {
+ Set *pSet;
+ CHECK_FOR_INTERRUPT;
+ if( pOp->p1<0 || pOp->p1>=p->nSet ){
+ if( pOp->opcode==OP_SetFirst ) pc = pOp->p2 - 1;
+ break;
+ }
+ pSet = &p->aSet[pOp->p1];
+ if( pOp->opcode==OP_SetFirst ){
+ pSet->prev = sqliteHashFirst(&pSet->hash);
+ if( pSet->prev==0 ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+ }else{
+ if( pSet->prev ){
+ pSet->prev = sqliteHashNext(pSet->prev);
+ }
+ if( pSet->prev==0 ){
+ break;
+ }else{
+ pc = pOp->p2 - 1;
+ }
+ }
+ pTos++;
+ pTos->z = sqliteHashKey(pSet->prev);
+ pTos->n = sqliteHashKeysize(pSet->prev);
+ pTos->flags = MEM_Str | MEM_Ephem;
+ break;
+}
+
+/* Opcode: Vacuum * * *
+**
+** Vacuum the entire database. This opcode will cause other virtual
+** machines to be created and run. It may not be called from within
+** a transaction.
+*/
+case OP_Vacuum: {
+ if( sqliteSafetyOff(db) ) goto abort_due_to_misuse;
+ rc = sqliteRunVacuum(&p->zErrMsg, db);
+ if( sqliteSafetyOn(db) ) goto abort_due_to_misuse;
+ break;
+}
+
+/* Opcode: StackDepth * * *
+**
+** Push an integer onto the stack which is the depth of the stack prior
+** to that integer being pushed.
+*/
+case OP_StackDepth: {
+ int depth = (&pTos[1]) - p->aStack;
+ pTos++;
+ pTos->i = depth;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: StackReset * * *
+**
+** Pop a single integer off of the stack. Then pop the stack
+** as many times as necessary to get the depth of the stack down
+** to the value of the integer that was popped.
+*/
+case OP_StackReset: {
+ int depth, goal;
+ assert( pTos>=p->aStack );
+ Integerify(pTos);
+ goal = pTos->i;
+ depth = (&pTos[1]) - p->aStack;
+ assert( goal<depth );
+ popStack(&pTos, depth-goal);
+ break;
+}
+
+/* An other opcode is illegal...
+*/
+default: {
+ sqlite_snprintf(sizeof(zBuf),zBuf,"%d",pOp->opcode);
+ sqliteSetString(&p->zErrMsg, "unknown opcode ", zBuf, (char*)0);
+ rc = SQLITE_INTERNAL;
+ break;
+}
+
+/*****************************************************************************
+** The cases of the switch statement above this line should all be indented
+** by 6 spaces. But the left-most 6 spaces have been removed to improve the
+** readability. From this point on down, the normal indentation rules are
+** restored.
+*****************************************************************************/
+ }
+
+#ifdef VDBE_PROFILE
+ {
+ long long elapse = hwtime() - start;
+ pOp->cycles += elapse;
+ pOp->cnt++;
+#if 0
+ fprintf(stdout, "%10lld ", elapse);
+ sqliteVdbePrintOp(stdout, origPc, &p->aOp[origPc]);
+#endif
+ }
+#endif
+
+ /* The following code adds nothing to the actual functionality
+ ** of the program. It is only here for testing and debugging.
+ ** On the other hand, it does burn CPU cycles every time through
+ ** the evaluator loop. So we can leave it out when NDEBUG is defined.
+ */
+#ifndef NDEBUG
+ /* Sanity checking on the top element of the stack */
+ if( pTos>=p->aStack ){
+ assert( pTos->flags!=0 ); /* Must define some type */
+ if( pTos->flags & MEM_Str ){
+ int x = pTos->flags & (MEM_Static|MEM_Dyn|MEM_Ephem|MEM_Short);
+ assert( x!=0 ); /* Strings must define a string subtype */
+ assert( (x & (x-1))==0 ); /* Only one string subtype can be defined */
+ assert( pTos->z!=0 ); /* Strings must have a value */
+ /* Mem.z points to Mem.zShort iff the subtype is MEM_Short */
+ assert( (pTos->flags & MEM_Short)==0 || pTos->z==pTos->zShort );
+ assert( (pTos->flags & MEM_Short)!=0 || pTos->z!=pTos->zShort );
+ }else{
+ /* Cannot define a string subtype for non-string objects */
+ assert( (pTos->flags & (MEM_Static|MEM_Dyn|MEM_Ephem|MEM_Short))==0 );
+ }
+ /* MEM_Null excludes all other types */
+ assert( pTos->flags==MEM_Null || (pTos->flags&MEM_Null)==0 );
+ }
+ if( pc<-1 || pc>=p->nOp ){
+ sqliteSetString(&p->zErrMsg, "jump destination out of range", (char*)0);
+ rc = SQLITE_INTERNAL;
+ }
+ if( p->trace && pTos>=p->aStack ){
+ int i;
+ fprintf(p->trace, "Stack:");
+ for(i=0; i>-5 && &pTos[i]>=p->aStack; i--){
+ if( pTos[i].flags & MEM_Null ){
+ fprintf(p->trace, " NULL");
+ }else if( (pTos[i].flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){
+ fprintf(p->trace, " si:%d", pTos[i].i);
+ }else if( pTos[i].flags & MEM_Int ){
+ fprintf(p->trace, " i:%d", pTos[i].i);
+ }else if( pTos[i].flags & MEM_Real ){
+ fprintf(p->trace, " r:%g", pTos[i].r);
+ }else if( pTos[i].flags & MEM_Str ){
+ int j, k;
+ char zBuf[100];
+ zBuf[0] = ' ';
+ if( pTos[i].flags & MEM_Dyn ){
+ zBuf[1] = 'z';
+ assert( (pTos[i].flags & (MEM_Static|MEM_Ephem))==0 );
+ }else if( pTos[i].flags & MEM_Static ){
+ zBuf[1] = 't';
+ assert( (pTos[i].flags & (MEM_Dyn|MEM_Ephem))==0 );
+ }else if( pTos[i].flags & MEM_Ephem ){
+ zBuf[1] = 'e';
+ assert( (pTos[i].flags & (MEM_Static|MEM_Dyn))==0 );
+ }else{
+ zBuf[1] = 's';
+ }
+ zBuf[2] = '[';
+ k = 3;
+ for(j=0; j<20 && j<pTos[i].n; j++){
+ int c = pTos[i].z[j];
+ if( c==0 && j==pTos[i].n-1 ) break;
+ if( isprint(c) && !isspace(c) ){
+ zBuf[k++] = c;
+ }else{
+ zBuf[k++] = '.';
+ }
+ }
+ zBuf[k++] = ']';
+ zBuf[k++] = 0;
+ fprintf(p->trace, "%s", zBuf);
+ }else{
+ fprintf(p->trace, " ???");
+ }
+ }
+ if( rc!=0 ) fprintf(p->trace," rc=%d",rc);
+ fprintf(p->trace,"\n");
+ }
+#endif
+ } /* The end of the for(;;) loop the loops through opcodes */
+
+ /* If we reach this point, it means that execution is finished.
+ */
+vdbe_halt:
+ CHECK_FOR_INTERRUPT
+ if( rc ){
+ p->rc = rc;
+ rc = SQLITE_ERROR;
+ }else{
+ rc = SQLITE_DONE;
+ }
+ p->magic = VDBE_MAGIC_HALT;
+ p->pTos = pTos;
+ return rc;
+
+ /* Jump to here if a malloc() fails. It's hard to get a malloc()
+ ** to fail on a modern VM computer, so this code is untested.
+ */
+no_mem:
+ sqliteSetString(&p->zErrMsg, "out of memory", (char*)0);
+ rc = SQLITE_NOMEM;
+ goto vdbe_halt;
+
+ /* Jump to here for an SQLITE_MISUSE error.
+ */
+abort_due_to_misuse:
+ rc = SQLITE_MISUSE;
+ /* Fall thru into abort_due_to_error */
+
+ /* Jump to here for any other kind of fatal error. The "rc" variable
+ ** should hold the error number.
+ */
+abort_due_to_error:
+ if( p->zErrMsg==0 ){
+ if( sqlite_malloc_failed ) rc = SQLITE_NOMEM;
+ sqliteSetString(&p->zErrMsg, sqlite_error_string(rc), (char*)0);
+ }
+ goto vdbe_halt;
+
+ /* Jump to here if the sqlite_interrupt() API sets the interrupt
+ ** flag.
+ */
+abort_due_to_interrupt:
+ assert( db->flags & SQLITE_Interrupt );
+ db->flags &= ~SQLITE_Interrupt;
+ if( db->magic!=SQLITE_MAGIC_BUSY ){
+ rc = SQLITE_MISUSE;
+ }else{
+ rc = SQLITE_INTERRUPT;
+ }
+ sqliteSetString(&p->zErrMsg, sqlite_error_string(rc), (char*)0);
+ goto vdbe_halt;
+}
diff --git a/src/libs/sqlite2/vdbe.h b/src/libs/sqlite2/vdbe.h
new file mode 100644
index 00000000..8dc1451c
--- /dev/null
+++ b/src/libs/sqlite2/vdbe.h
@@ -0,0 +1,112 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Header file for the Virtual DataBase Engine (VDBE)
+**
+** This header defines the interface to the virtual database engine
+** or VDBE. The VDBE implements an abstract machine that runs a
+** simple program to access and modify the underlying database.
+**
+** $Id: vdbe.h 326789 2004-07-07 21:25:56Z pahlibar $
+*/
+#ifndef _SQLITE_VDBE_H_
+#define _SQLITE_VDBE_H_
+#include <stdio.h>
+
+/*
+** A single VDBE is an opaque structure named "Vdbe". Only routines
+** in the source file sqliteVdbe.c are allowed to see the insides
+** of this structure.
+*/
+typedef struct Vdbe Vdbe;
+
+/*
+** A single instruction of the virtual machine has an opcode
+** and as many as three operands. The instruction is recorded
+** as an instance of the following structure:
+*/
+struct VdbeOp {
+ u8 opcode; /* What operation to perform */
+ int p1; /* First operand */
+ int p2; /* Second parameter (often the jump destination) */
+ char *p3; /* Third parameter */
+ int p3type; /* P3_STATIC, P3_DYNAMIC or P3_POINTER */
+#ifdef VDBE_PROFILE
+ int cnt; /* Number of times this instruction was executed */
+ long long cycles; /* Total time spend executing this instruction */
+#endif
+};
+typedef struct VdbeOp VdbeOp;
+
+/*
+** A smaller version of VdbeOp used for the VdbeAddOpList() function because
+** it takes up less space.
+*/
+struct VdbeOpList {
+ u8 opcode; /* What operation to perform */
+ signed char p1; /* First operand */
+ short int p2; /* Second parameter (often the jump destination) */
+ char *p3; /* Third parameter */
+};
+typedef struct VdbeOpList VdbeOpList;
+
+/*
+** Allowed values of VdbeOp.p3type
+*/
+#define P3_NOTUSED 0 /* The P3 parameter is not used */
+#define P3_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */
+#define P3_STATIC (-2) /* Pointer to a static string */
+#define P3_POINTER (-3) /* P3 is a pointer to some structure or object */
+
+/*
+** The following macro converts a relative address in the p2 field
+** of a VdbeOp structure into a negative number so that
+** sqliteVdbeAddOpList() knows that the address is relative. Calling
+** the macro again restores the address.
+*/
+#define ADDR(X) (-1-(X))
+
+/*
+** The makefile scans the vdbe.c source file and creates the "opcodes.h"
+** header file that defines a number for each opcode used by the VDBE.
+*/
+#include "opcodes.h"
+
+/*
+** Prototypes for the VDBE interface. See comments on the implementation
+** for a description of what each of these routines does.
+*/
+Vdbe *sqliteVdbeCreate(sqlite*);
+void sqliteVdbeCreateCallback(Vdbe*, int*);
+int sqliteVdbeAddOp(Vdbe*,int,int,int);
+int sqliteVdbeOp3(Vdbe*,int,int,int,const char *zP3,int);
+int sqliteVdbeCode(Vdbe*,...);
+int sqliteVdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp);
+void sqliteVdbeChangeP1(Vdbe*, int addr, int P1);
+void sqliteVdbeChangeP2(Vdbe*, int addr, int P2);
+void sqliteVdbeChangeP3(Vdbe*, int addr, const char *zP1, int N);
+void sqliteVdbeDequoteP3(Vdbe*, int addr);
+int sqliteVdbeFindOp(Vdbe*, int, int);
+VdbeOp *sqliteVdbeGetOp(Vdbe*, int);
+int sqliteVdbeMakeLabel(Vdbe*);
+void sqliteVdbeDelete(Vdbe*);
+void sqliteVdbeMakeReady(Vdbe*,int,int);
+int sqliteVdbeExec(Vdbe*);
+int sqliteVdbeList(Vdbe*);
+int sqliteVdbeFinalize(Vdbe*,char**);
+void sqliteVdbeResolveLabel(Vdbe*, int);
+int sqliteVdbeCurrentAddr(Vdbe*);
+void sqliteVdbeTrace(Vdbe*,FILE*);
+void sqliteVdbeCompressSpace(Vdbe*,int);
+int sqliteVdbeReset(Vdbe*,char **);
+int sqliteVdbeSetVariables(Vdbe*,int,const char**);
+
+#endif
diff --git a/src/libs/sqlite2/vdbeInt.h b/src/libs/sqlite2/vdbeInt.h
new file mode 100644
index 00000000..79b6b51a
--- /dev/null
+++ b/src/libs/sqlite2/vdbeInt.h
@@ -0,0 +1,303 @@
+/*
+** 2003 September 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the header file for information that is private to the
+** VDBE. This information used to all be at the top of the single
+** source code file "vdbe.c". When that file became too big (over
+** 6000 lines long) it was split up into several smaller files and
+** this header information was factored out.
+*/
+
+/*
+** When converting from the native format to the key format and back
+** again, in addition to changing the byte order we invert the high-order
+** bit of the most significant byte. This causes negative numbers to
+** sort before positive numbers in the memcmp() function.
+*/
+#define keyToInt(X) (sqliteVdbeByteSwap(X) ^ 0x80000000)
+#define intToKey(X) (sqliteVdbeByteSwap((X) ^ 0x80000000))
+
+/*
+** The makefile scans this source file and creates the following
+** array of string constants which are the names of all VDBE opcodes.
+** This array is defined in a separate source code file named opcode.c
+** which is automatically generated by the makefile.
+*/
+extern char *sqliteOpcodeNames[];
+
+/*
+** SQL is translated into a sequence of instructions to be
+** executed by a virtual machine. Each instruction is an instance
+** of the following structure.
+*/
+typedef struct VdbeOp Op;
+
+/*
+** Boolean values
+*/
+typedef unsigned char Bool;
+
+/*
+** A cursor is a pointer into a single BTree within a database file.
+** The cursor can seek to a BTree entry with a particular key, or
+** loop over all entries of the Btree. You can also insert new BTree
+** entries or retrieve the key or data from the entry that the cursor
+** is currently pointing to.
+**
+** Every cursor that the virtual machine has open is represented by an
+** instance of the following structure.
+**
+** If the Cursor.isTriggerRow flag is set it means that this cursor is
+** really a single row that represents the NEW or OLD pseudo-table of
+** a row trigger. The data for the row is stored in Cursor.pData and
+** the rowid is in Cursor.iKey.
+*/
+struct Cursor {
+ BtCursor *pCursor; /* The cursor structure of the backend */
+ int lastRecno; /* Last recno from a Next or NextIdx operation */
+ int nextRowid; /* Next rowid returned by OP_NewRowid */
+ Bool recnoIsValid; /* True if lastRecno is valid */
+ Bool keyAsData; /* The OP_Column command works on key instead of data */
+ Bool atFirst; /* True if pointing to first entry */
+ Bool useRandomRowid; /* Generate new record numbers semi-randomly */
+ Bool nullRow; /* True if pointing to a row with no data */
+ Bool nextRowidValid; /* True if the nextRowid field is valid */
+ Bool pseudoTable; /* This is a NEW or OLD pseudo-tables of a trigger */
+ Bool deferredMoveto; /* A call to sqliteBtreeMoveto() is needed */
+ int movetoTarget; /* Argument to the deferred sqliteBtreeMoveto() */
+ Btree *pBt; /* Separate file holding temporary table */
+ int nData; /* Number of bytes in pData */
+ char *pData; /* Data for a NEW or OLD pseudo-table */
+ int iKey; /* Key for the NEW or OLD pseudo-table row */
+};
+typedef struct Cursor Cursor;
+
+/*
+** A sorter builds a list of elements to be sorted. Each element of
+** the list is an instance of the following structure.
+*/
+typedef struct Sorter Sorter;
+struct Sorter {
+ int nKey; /* Number of bytes in the key */
+ char *zKey; /* The key by which we will sort */
+ int nData; /* Number of bytes in the data */
+ char *pData; /* The data associated with this key */
+ Sorter *pNext; /* Next in the list */
+};
+
+/*
+** Number of buckets used for merge-sort.
+*/
+#define NSORT 30
+
+/*
+** Number of bytes of string storage space available to each stack
+** layer without having to malloc. NBFS is short for Number of Bytes
+** For Strings.
+*/
+#define NBFS 32
+
+/*
+** A single level of the stack or a single memory cell
+** is an instance of the following structure.
+*/
+struct Mem {
+ int i; /* Integer value */
+ int n; /* Number of characters in string value, including '\0' */
+ int flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
+ double r; /* Real value */
+ char *z; /* String value */
+ char zShort[NBFS]; /* Space for short strings */
+};
+typedef struct Mem Mem;
+
+/*
+** Allowed values for Mem.flags
+*/
+#define MEM_Null 0x0001 /* Value is NULL */
+#define MEM_Str 0x0002 /* Value is a string */
+#define MEM_Int 0x0004 /* Value is an integer */
+#define MEM_Real 0x0008 /* Value is a real number */
+#define MEM_Dyn 0x0010 /* Need to call sqliteFree() on Mem.z */
+#define MEM_Static 0x0020 /* Mem.z points to a static string */
+#define MEM_Ephem 0x0040 /* Mem.z points to an ephemeral string */
+#define MEM_Short 0x0080 /* Mem.z points to Mem.zShort */
+
+/* The following MEM_ value appears only in AggElem.aMem.s.flag fields.
+** It indicates that the corresponding AggElem.aMem.z points to a
+** aggregate function context that needs to be finalized.
+*/
+#define MEM_AggCtx 0x0100 /* Mem.z points to an agg function context */
+
+/*
+** The "context" argument for a installable function. A pointer to an
+** instance of this structure is the first argument to the routines used
+** implement the SQL functions.
+**
+** There is a typedef for this structure in sqlite.h. So all routines,
+** even the public interface to SQLite, can use a pointer to this structure.
+** But this file is the only place where the internal details of this
+** structure are known.
+**
+** This structure is defined inside of vdbe.c because it uses substructures
+** (Mem) which are only defined there.
+*/
+struct sqlite_func {
+ FuncDef *pFunc; /* Pointer to function information. MUST BE FIRST */
+ Mem s; /* The return value is stored here */
+ void *pAgg; /* Aggregate context */
+ u8 isError; /* Set to true for an error */
+ u8 isStep; /* Current in the step function */
+ int cnt; /* Number of times that the step function has been called */
+};
+
+/*
+** An Agg structure describes an Aggregator. Each Agg consists of
+** zero or more Aggregator elements (AggElem). Each AggElem contains
+** a key and one or more values. The values are used in processing
+** aggregate functions in a SELECT. The key is used to implement
+** the GROUP BY clause of a select.
+*/
+typedef struct Agg Agg;
+typedef struct AggElem AggElem;
+struct Agg {
+ int nMem; /* Number of values stored in each AggElem */
+ AggElem *pCurrent; /* The AggElem currently in focus */
+ HashElem *pSearch; /* The hash element for pCurrent */
+ Hash hash; /* Hash table of all aggregate elements */
+ FuncDef **apFunc; /* Information about aggregate functions */
+};
+struct AggElem {
+ char *zKey; /* The key to this AggElem */
+ int nKey; /* Number of bytes in the key, including '\0' at end */
+ Mem aMem[1]; /* The values for this AggElem */
+};
+
+/*
+** A Set structure is used for quick testing to see if a value
+** is part of a small set. Sets are used to implement code like
+** this:
+** x.y IN ('hi','hoo','hum')
+*/
+typedef struct Set Set;
+struct Set {
+ Hash hash; /* A set is just a hash table */
+ HashElem *prev; /* Previously accessed hash elemen */
+};
+
+/*
+** A Keylist is a bunch of keys into a table. The keylist can
+** grow without bound. The keylist stores the ROWIDs of database
+** records that need to be deleted or updated.
+*/
+typedef struct Keylist Keylist;
+struct Keylist {
+ int nKey; /* Number of slots in aKey[] */
+ int nUsed; /* Next unwritten slot in aKey[] */
+ int nRead; /* Next unread slot in aKey[] */
+ Keylist *pNext; /* Next block of keys */
+ int aKey[1]; /* One or more keys. Extra space allocated as needed */
+};
+
+/*
+** A Context stores the last insert rowid, the last statement change count,
+** and the current statement change count (i.e. changes since last statement).
+** Elements of Context structure type make up the ContextStack, which is
+** updated by the ContextPush and ContextPop opcodes (used by triggers)
+*/
+typedef struct Context Context;
+struct Context {
+ int lastRowid; /* Last insert rowid (from db->lastRowid) */
+ int lsChange; /* Last statement change count (from db->lsChange) */
+ int csChange; /* Current statement change count (from db->csChange) */
+};
+
+/*
+** An instance of the virtual machine. This structure contains the complete
+** state of the virtual machine.
+**
+** The "sqlite_vm" structure pointer that is returned by sqlite_compile()
+** is really a pointer to an instance of this structure.
+*/
+struct Vdbe {
+ sqlite *db; /* The whole database */
+ Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */
+ FILE *trace; /* Write an execution trace here, if not NULL */
+ int nOp; /* Number of instructions in the program */
+ int nOpAlloc; /* Number of slots allocated for aOp[] */
+ Op *aOp; /* Space to hold the virtual machine's program */
+ int nLabel; /* Number of labels used */
+ int nLabelAlloc; /* Number of slots allocated in aLabel[] */
+ int *aLabel; /* Space to hold the labels */
+ Mem *aStack; /* The operand stack, except string values */
+ Mem *pTos; /* Top entry in the operand stack */
+ char **zArgv; /* Text values used by the callback */
+ char **azColName; /* Becomes the 4th parameter to callbacks */
+ int nCursor; /* Number of slots in aCsr[] */
+ Cursor *aCsr; /* One element of this array for each open cursor */
+ Sorter *pSort; /* A linked list of objects to be sorted */
+ FILE *pFile; /* At most one open file handler */
+ int nField; /* Number of file fields */
+ char **azField; /* Data for each file field */
+ int nVar; /* Number of entries in azVariable[] */
+ char **azVar; /* Values for the OP_Variable opcode */
+ int *anVar; /* Length of each value in azVariable[] */
+ u8 *abVar; /* TRUE if azVariable[i] needs to be sqliteFree()ed */
+ char *zLine; /* A single line from the input file */
+ int nLineAlloc; /* Number of spaces allocated for zLine */
+ int magic; /* Magic number for sanity checking */
+ int nMem; /* Number of memory locations currently allocated */
+ Mem *aMem; /* The memory locations */
+ Agg agg; /* Aggregate information */
+ int nSet; /* Number of sets allocated */
+ Set *aSet; /* An array of sets */
+ int nCallback; /* Number of callbacks invoked so far */
+ Keylist *pList; /* A list of ROWIDs */
+ int keylistStackDepth; /* The size of the "keylist" stack */
+ Keylist **keylistStack; /* The stack used by opcodes ListPush & ListPop */
+ int contextStackDepth; /* The size of the "context" stack */
+ Context *contextStack; /* Stack used by opcodes ContextPush & ContextPop*/
+ int pc; /* The program counter */
+ int rc; /* Value to return */
+ unsigned uniqueCnt; /* Used by OP_MakeRecord when P2!=0 */
+ int errorAction; /* Recovery action to do in case of an error */
+ int undoTransOnError; /* If error, either ROLLBACK or COMMIT */
+ int inTempTrans; /* True if temp database is transactioned */
+ int returnStack[100]; /* Return address stack for OP_Gosub & OP_Return */
+ int returnDepth; /* Next unused element in returnStack[] */
+ int nResColumn; /* Number of columns in one row of the result set */
+ char **azResColumn; /* Values for one row of result */
+ int popStack; /* Pop the stack this much on entry to VdbeExec() */
+ char *zErrMsg; /* Error message written here */
+ u8 explain; /* True if EXPLAIN present on SQL command */
+};
+
+/*
+** The following are allowed values for Vdbe.magic
+*/
+#define VDBE_MAGIC_INIT 0x26bceaa5 /* Building a VDBE program */
+#define VDBE_MAGIC_RUN 0xbdf20da3 /* VDBE is ready to execute */
+#define VDBE_MAGIC_HALT 0x519c2973 /* VDBE has completed execution */
+#define VDBE_MAGIC_DEAD 0xb606c3c8 /* The VDBE has been deallocated */
+
+/*
+** Function prototypes
+*/
+void sqliteVdbeCleanupCursor(Cursor*);
+void sqliteVdbeSorterReset(Vdbe*);
+void sqliteVdbeAggReset(Agg*);
+void sqliteVdbeKeylistFree(Keylist*);
+void sqliteVdbePopStack(Vdbe*,int);
+int sqliteVdbeCursorMoveto(Cursor*);
+int sqliteVdbeByteSwap(int);
+#if !defined(NDEBUG) || defined(VDBE_PROFILE)
+void sqliteVdbePrintOp(FILE*, int, Op*);
+#endif
diff --git a/src/libs/sqlite2/vdbeaux.c b/src/libs/sqlite2/vdbeaux.c
new file mode 100644
index 00000000..c206bad4
--- /dev/null
+++ b/src/libs/sqlite2/vdbeaux.c
@@ -0,0 +1,1061 @@
+/*
+** 2003 September 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used for creating, destroying, and populating
+** a VDBE (or an "sqlite_vm" as it is known to the outside world.) Prior
+** to version 2.8.7, all this code was combined into the vdbe.c source file.
+** But that file was getting too big so this subroutines were split out.
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+#include "vdbeInt.h"
+
+
+/*
+** When debugging the code generator in a symbolic debugger, one can
+** set the sqlite_vdbe_addop_trace to 1 and all opcodes will be printed
+** as they are added to the instruction stream.
+*/
+#ifndef NDEBUG
+int sqlite_vdbe_addop_trace = 0;
+#endif
+
+
+/*
+** Create a new virtual database engine.
+*/
+Vdbe *sqliteVdbeCreate(sqlite *db){
+ Vdbe *p;
+ p = sqliteMalloc( sizeof(Vdbe) );
+ if( p==0 ) return 0;
+ p->db = db;
+ if( db->pVdbe ){
+ db->pVdbe->pPrev = p;
+ }
+ p->pNext = db->pVdbe;
+ p->pPrev = 0;
+ db->pVdbe = p;
+ p->magic = VDBE_MAGIC_INIT;
+ return p;
+}
+
+/*
+** Turn tracing on or off
+*/
+void sqliteVdbeTrace(Vdbe *p, FILE *trace){
+ p->trace = trace;
+}
+
+/*
+** Add a new instruction to the list of instructions current in the
+** VDBE. Return the address of the new instruction.
+**
+** Parameters:
+**
+** p Pointer to the VDBE
+**
+** op The opcode for this instruction
+**
+** p1, p2 First two of the three possible operands.
+**
+** Use the sqliteVdbeResolveLabel() function to fix an address and
+** the sqliteVdbeChangeP3() function to change the value of the P3
+** operand.
+*/
+int sqliteVdbeAddOp(Vdbe *p, int op, int p1, int p2){
+ int i;
+ VdbeOp *pOp;
+
+ i = p->nOp;
+ p->nOp++;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( i>=p->nOpAlloc ){
+ int oldSize = p->nOpAlloc;
+ Op *aNew;
+ p->nOpAlloc = p->nOpAlloc*2 + 100;
+ aNew = sqliteRealloc(p->aOp, p->nOpAlloc*sizeof(Op));
+ if( aNew==0 ){
+ p->nOpAlloc = oldSize;
+ return 0;
+ }
+ p->aOp = aNew;
+ memset(&p->aOp[oldSize], 0, (p->nOpAlloc-oldSize)*sizeof(Op));
+ }
+ pOp = &p->aOp[i];
+ pOp->opcode = op;
+ pOp->p1 = p1;
+ if( p2<0 && (-1-p2)<p->nLabel && p->aLabel[-1-p2]>=0 ){
+ p2 = p->aLabel[-1-p2];
+ }
+ pOp->p2 = p2;
+ pOp->p3 = 0;
+ pOp->p3type = P3_NOTUSED;
+#ifndef NDEBUG
+ if( sqlite_vdbe_addop_trace ) sqliteVdbePrintOp(0, i, &p->aOp[i]);
+#endif
+ return i;
+}
+
+/*
+** Add an opcode that includes the p3 value.
+*/
+int sqliteVdbeOp3(Vdbe *p, int op, int p1, int p2, const char *zP3, int p3type){
+ int addr = sqliteVdbeAddOp(p, op, p1, p2);
+ sqliteVdbeChangeP3(p, addr, zP3, p3type);
+ return addr;
+}
+
+/*
+** Add multiple opcodes. The list is terminated by an opcode of 0.
+*/
+int sqliteVdbeCode(Vdbe *p, ...){
+ int addr;
+ va_list ap;
+ int opcode, p1, p2;
+ va_start(ap, p);
+ addr = p->nOp;
+ while( (opcode = va_arg(ap,int))!=0 ){
+ p1 = va_arg(ap,int);
+ p2 = va_arg(ap,int);
+ sqliteVdbeAddOp(p, opcode, p1, p2);
+ }
+ va_end(ap);
+ return addr;
+}
+
+
+
+/*
+** Create a new symbolic label for an instruction that has yet to be
+** coded. The symbolic label is really just a negative number. The
+** label can be used as the P2 value of an operation. Later, when
+** the label is resolved to a specific address, the VDBE will scan
+** through its operation list and change all values of P2 which match
+** the label into the resolved address.
+**
+** The VDBE knows that a P2 value is a label because labels are
+** always negative and P2 values are suppose to be non-negative.
+** Hence, a negative P2 value is a label that has yet to be resolved.
+*/
+int sqliteVdbeMakeLabel(Vdbe *p){
+ int i;
+ i = p->nLabel++;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( i>=p->nLabelAlloc ){
+ int *aNew;
+ p->nLabelAlloc = p->nLabelAlloc*2 + 10;
+ aNew = sqliteRealloc( p->aLabel, p->nLabelAlloc*sizeof(p->aLabel[0]));
+ if( aNew==0 ){
+ sqliteFree(p->aLabel);
+ }
+ p->aLabel = aNew;
+ }
+ if( p->aLabel==0 ){
+ p->nLabel = 0;
+ p->nLabelAlloc = 0;
+ return 0;
+ }
+ p->aLabel[i] = -1;
+ return -1-i;
+}
+
+/*
+** Resolve label "x" to be the address of the next instruction to
+** be inserted. The parameter "x" must have been obtained from
+** a prior call to sqliteVdbeMakeLabel().
+*/
+void sqliteVdbeResolveLabel(Vdbe *p, int x){
+ int j;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( x<0 && (-x)<=p->nLabel && p->aOp ){
+ if( p->aLabel[-1-x]==p->nOp ) return;
+ assert( p->aLabel[-1-x]<0 );
+ p->aLabel[-1-x] = p->nOp;
+ for(j=0; j<p->nOp; j++){
+ if( p->aOp[j].p2==x ) p->aOp[j].p2 = p->nOp;
+ }
+ }
+}
+
+/*
+** Return the address of the next instruction to be inserted.
+*/
+int sqliteVdbeCurrentAddr(Vdbe *p){
+ assert( p->magic==VDBE_MAGIC_INIT );
+ return p->nOp;
+}
+
+/*
+** Add a whole list of operations to the operation stack. Return the
+** address of the first operation added.
+*/
+int sqliteVdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp){
+ int addr;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p->nOp + nOp >= p->nOpAlloc ){
+ int oldSize = p->nOpAlloc;
+ Op *aNew;
+ p->nOpAlloc = p->nOpAlloc*2 + nOp + 10;
+ aNew = sqliteRealloc(p->aOp, p->nOpAlloc*sizeof(Op));
+ if( aNew==0 ){
+ p->nOpAlloc = oldSize;
+ return 0;
+ }
+ p->aOp = aNew;
+ memset(&p->aOp[oldSize], 0, (p->nOpAlloc-oldSize)*sizeof(Op));
+ }
+ addr = p->nOp;
+ if( nOp>0 ){
+ int i;
+ VdbeOpList const *pIn = aOp;
+ for(i=0; i<nOp; i++, pIn++){
+ int p2 = pIn->p2;
+ VdbeOp *pOut = &p->aOp[i+addr];
+ pOut->opcode = pIn->opcode;
+ pOut->p1 = pIn->p1;
+ pOut->p2 = p2<0 ? addr + ADDR(p2) : p2;
+ pOut->p3 = pIn->p3;
+ pOut->p3type = pIn->p3 ? P3_STATIC : P3_NOTUSED;
+#ifndef NDEBUG
+ if( sqlite_vdbe_addop_trace ){
+ sqliteVdbePrintOp(0, i+addr, &p->aOp[i+addr]);
+ }
+#endif
+ }
+ p->nOp += nOp;
+ }
+ return addr;
+}
+
+/*
+** Change the value of the P1 operand for a specific instruction.
+** This routine is useful when a large program is loaded from a
+** static array using sqliteVdbeAddOpList but we want to make a
+** few minor changes to the program.
+*/
+void sqliteVdbeChangeP1(Vdbe *p, int addr, int val){
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p && addr>=0 && p->nOp>addr && p->aOp ){
+ p->aOp[addr].p1 = val;
+ }
+}
+
+/*
+** Change the value of the P2 operand for a specific instruction.
+** This routine is useful for setting a jump destination.
+*/
+void sqliteVdbeChangeP2(Vdbe *p, int addr, int val){
+ assert( val>=0 );
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p && addr>=0 && p->nOp>addr && p->aOp ){
+ p->aOp[addr].p2 = val;
+ }
+}
+
+/*
+** Change the value of the P3 operand for a specific instruction.
+** This routine is useful when a large program is loaded from a
+** static array using sqliteVdbeAddOpList but we want to make a
+** few minor changes to the program.
+**
+** If n>=0 then the P3 operand is dynamic, meaning that a copy of
+** the string is made into memory obtained from sqliteMalloc().
+** A value of n==0 means copy bytes of zP3 up to and including the
+** first null byte. If n>0 then copy n+1 bytes of zP3.
+**
+** If n==P3_STATIC it means that zP3 is a pointer to a constant static
+** string and we can just copy the pointer. n==P3_POINTER means zP3 is
+** a pointer to some object other than a string.
+**
+** If addr<0 then change P3 on the most recently inserted instruction.
+*/
+void sqliteVdbeChangeP3(Vdbe *p, int addr, const char *zP3, int n){
+ Op *pOp;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p==0 || p->aOp==0 ) return;
+ if( addr<0 || addr>=p->nOp ){
+ addr = p->nOp - 1;
+ if( addr<0 ) return;
+ }
+ pOp = &p->aOp[addr];
+ if( pOp->p3 && pOp->p3type==P3_DYNAMIC ){
+ sqliteFree(pOp->p3);
+ pOp->p3 = 0;
+ }
+ if( zP3==0 ){
+ pOp->p3 = 0;
+ pOp->p3type = P3_NOTUSED;
+ }else if( n<0 ){
+ pOp->p3 = (char*)zP3;
+ pOp->p3type = n;
+ }else{
+ sqliteSetNString(&pOp->p3, zP3, n, 0);
+ pOp->p3type = P3_DYNAMIC;
+ }
+}
+
+/*
+** If the P3 operand to the specified instruction appears
+** to be a quoted string token, then this procedure removes
+** the quotes.
+**
+** The quoting operator can be either a grave ascent (ASCII 0x27)
+** or a double quote character (ASCII 0x22). Two quotes in a row
+** resolve to be a single actual quote character within the string.
+*/
+void sqliteVdbeDequoteP3(Vdbe *p, int addr){
+ Op *pOp;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p->aOp==0 ) return;
+ if( addr<0 || addr>=p->nOp ){
+ addr = p->nOp - 1;
+ if( addr<0 ) return;
+ }
+ pOp = &p->aOp[addr];
+ if( pOp->p3==0 || pOp->p3[0]==0 ) return;
+ if( pOp->p3type==P3_POINTER ) return;
+ if( pOp->p3type!=P3_DYNAMIC ){
+ pOp->p3 = sqliteStrDup(pOp->p3);
+ pOp->p3type = P3_DYNAMIC;
+ }
+ sqliteDequote(pOp->p3);
+}
+
+/*
+** On the P3 argument of the given instruction, change all
+** strings of whitespace characters into a single space and
+** delete leading and trailing whitespace.
+*/
+void sqliteVdbeCompressSpace(Vdbe *p, int addr){
+ unsigned char *z;
+ int i, j;
+ Op *pOp;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p->aOp==0 || addr<0 || addr>=p->nOp ) return;
+ pOp = &p->aOp[addr];
+ if( pOp->p3type==P3_POINTER ){
+ return;
+ }
+ if( pOp->p3type!=P3_DYNAMIC ){
+ pOp->p3 = sqliteStrDup(pOp->p3);
+ pOp->p3type = P3_DYNAMIC;
+ }
+ z = (unsigned char*)pOp->p3;
+ if( z==0 ) return;
+ i = j = 0;
+ while( isspace(z[i]) ){ i++; }
+ while( z[i] ){
+ if( isspace(z[i]) ){
+ z[j++] = ' ';
+ while( isspace(z[++i]) ){}
+ }else{
+ z[j++] = z[i++];
+ }
+ }
+ while( j>0 && isspace(z[j-1]) ){ j--; }
+ z[j] = 0;
+}
+
+/*
+** Search for the current program for the given opcode and P2
+** value. Return the address plus 1 if found and 0 if not found.
+*/
+int sqliteVdbeFindOp(Vdbe *p, int op, int p2){
+ int i;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ for(i=0; i<p->nOp; i++){
+ if( p->aOp[i].opcode==op && p->aOp[i].p2==p2 ) return i+1;
+ }
+ return 0;
+}
+
+/*
+** Return the opcode for a given address.
+*/
+VdbeOp *sqliteVdbeGetOp(Vdbe *p, int addr){
+ assert( p->magic==VDBE_MAGIC_INIT );
+ assert( addr>=0 && addr<p->nOp );
+ return &p->aOp[addr];
+}
+
+/*
+** The following group or routines are employed by installable functions
+** to return their results.
+**
+** The sqlite_set_result_string() routine can be used to return a string
+** value or to return a NULL. To return a NULL, pass in NULL for zResult.
+** A copy is made of the string before this routine returns so it is safe
+** to pass in an ephemeral string.
+**
+** sqlite_set_result_error() works like sqlite_set_result_string() except
+** that it signals a fatal error. The string argument, if any, is the
+** error message. If the argument is NULL a generic substitute error message
+** is used.
+**
+** The sqlite_set_result_int() and sqlite_set_result_double() set the return
+** value of the user function to an integer or a double.
+**
+** These routines are defined here in vdbe.c because they depend on knowing
+** the internals of the sqlite_func structure which is only defined in
+** this source file.
+*/
+char *sqlite_set_result_string(sqlite_func *p, const char *zResult, int n){
+ assert( !p->isStep );
+ if( p->s.flags & MEM_Dyn ){
+ sqliteFree(p->s.z);
+ }
+ if( zResult==0 ){
+ p->s.flags = MEM_Null;
+ n = 0;
+ p->s.z = 0;
+ p->s.n = 0;
+ }else{
+ if( n<0 ) n = strlen(zResult);
+ if( n<NBFS-1 ){
+ memcpy(p->s.zShort, zResult, n);
+ p->s.zShort[n] = 0;
+ p->s.flags = MEM_Str | MEM_Short;
+ p->s.z = p->s.zShort;
+ }else{
+ p->s.z = sqliteMallocRaw( n+1 );
+ if( p->s.z ){
+ memcpy(p->s.z, zResult, n);
+ p->s.z[n] = 0;
+ }
+ p->s.flags = MEM_Str | MEM_Dyn;
+ }
+ p->s.n = n+1;
+ }
+ return p->s.z;
+}
+void sqlite_set_result_int(sqlite_func *p, int iResult){
+ assert( !p->isStep );
+ if( p->s.flags & MEM_Dyn ){
+ sqliteFree(p->s.z);
+ }
+ p->s.i = iResult;
+ p->s.flags = MEM_Int;
+}
+void sqlite_set_result_double(sqlite_func *p, double rResult){
+ assert( !p->isStep );
+ if( p->s.flags & MEM_Dyn ){
+ sqliteFree(p->s.z);
+ }
+ p->s.r = rResult;
+ p->s.flags = MEM_Real;
+}
+void sqlite_set_result_error(sqlite_func *p, const char *zMsg, int n){
+ assert( !p->isStep );
+ sqlite_set_result_string(p, zMsg, n);
+ p->isError = 1;
+}
+
+/*
+** Extract the user data from a sqlite_func structure and return a
+** pointer to it.
+*/
+void *sqlite_user_data(sqlite_func *p){
+ assert( p && p->pFunc );
+ return p->pFunc->pUserData;
+}
+
+/*
+** Allocate or return the aggregate context for a user function. A new
+** context is allocated on the first call. Subsequent calls return the
+** same context that was returned on prior calls.
+**
+** This routine is defined here in vdbe.c because it depends on knowing
+** the internals of the sqlite_func structure which is only defined in
+** this source file.
+*/
+void *sqlite_aggregate_context(sqlite_func *p, int nByte){
+ assert( p && p->pFunc && p->pFunc->xStep );
+ if( p->pAgg==0 ){
+ if( nByte<=NBFS ){
+ p->pAgg = (void*)p->s.z;
+ memset(p->pAgg, 0, nByte);
+ }else{
+ p->pAgg = sqliteMalloc( nByte );
+ }
+ }
+ return p->pAgg;
+}
+
+/*
+** Return the number of times the Step function of a aggregate has been
+** called.
+**
+** This routine is defined here in vdbe.c because it depends on knowing
+** the internals of the sqlite_func structure which is only defined in
+** this source file.
+*/
+int sqlite_aggregate_count(sqlite_func *p){
+ assert( p && p->pFunc && p->pFunc->xStep );
+ return p->cnt;
+}
+
+#if !defined(NDEBUG) || defined(VDBE_PROFILE)
+/*
+** Print a single opcode. This routine is used for debugging only.
+*/
+void sqliteVdbePrintOp(FILE *pOut, int pc, Op *pOp){
+ char *zP3;
+ char zPtr[40];
+ if( pOp->p3type==P3_POINTER ){
+ sprintf(zPtr, "ptr(%#lx)", (long)pOp->p3);
+ zP3 = zPtr;
+ }else{
+ zP3 = pOp->p3;
+ }
+ if( pOut==0 ) pOut = stdout;
+ fprintf(pOut,"%4d %-12s %4d %4d %s\n",
+ pc, sqliteOpcodeNames[pOp->opcode], pOp->p1, pOp->p2, zP3 ? zP3 : "");
+ fflush(pOut);
+}
+#endif
+
+/*
+** Give a listing of the program in the virtual machine.
+**
+** The interface is the same as sqliteVdbeExec(). But instead of
+** running the code, it invokes the callback once for each instruction.
+** This feature is used to implement "EXPLAIN".
+*/
+int sqliteVdbeList(
+ Vdbe *p /* The VDBE */
+){
+ sqlite *db = p->db;
+ int i;
+ int rc = SQLITE_OK;
+ static char *azColumnNames[] = {
+ "addr", "opcode", "p1", "p2", "p3",
+ "int", "text", "int", "int", "text",
+ 0
+ };
+
+ assert( p->popStack==0 );
+ assert( p->explain );
+ p->azColName = azColumnNames;
+ p->azResColumn = p->zArgv;
+ for(i=0; i<5; i++) p->zArgv[i] = p->aStack[i].zShort;
+ i = p->pc;
+ if( i>=p->nOp ){
+ p->rc = SQLITE_OK;
+ rc = SQLITE_DONE;
+ }else if( db->flags & SQLITE_Interrupt ){
+ db->flags &= ~SQLITE_Interrupt;
+ if( db->magic!=SQLITE_MAGIC_BUSY ){
+ p->rc = SQLITE_MISUSE;
+ }else{
+ p->rc = SQLITE_INTERRUPT;
+ }
+ rc = SQLITE_ERROR;
+ sqliteSetString(&p->zErrMsg, sqlite_error_string(p->rc), (char*)0);
+ }else{
+ sprintf(p->zArgv[0],"%d",i);
+ sprintf(p->zArgv[2],"%d", p->aOp[i].p1);
+ sprintf(p->zArgv[3],"%d", p->aOp[i].p2);
+ if( p->aOp[i].p3type==P3_POINTER ){
+ sprintf(p->aStack[4].zShort, "ptr(%#lx)", (long)p->aOp[i].p3);
+ p->zArgv[4] = p->aStack[4].zShort;
+ }else{
+ p->zArgv[4] = p->aOp[i].p3;
+ }
+ p->zArgv[1] = sqliteOpcodeNames[p->aOp[i].opcode];
+ p->pc = i+1;
+ p->azResColumn = p->zArgv;
+ p->nResColumn = 5;
+ p->rc = SQLITE_OK;
+ rc = SQLITE_ROW;
+ }
+ return rc;
+}
+
+/*
+** Prepare a virtual machine for execution. This involves things such
+** as allocating stack space and initializing the program counter.
+** After the VDBE has be prepped, it can be executed by one or more
+** calls to sqliteVdbeExec().
+*/
+void sqliteVdbeMakeReady(
+ Vdbe *p, /* The VDBE */
+ int nVar, /* Number of '?' see in the SQL statement */
+ int isExplain /* True if the EXPLAIN keywords is present */
+){
+ int n;
+
+ assert( p!=0 );
+ assert( p->magic==VDBE_MAGIC_INIT );
+
+ /* Add a HALT instruction to the very end of the program.
+ */
+ if( p->nOp==0 || (p->aOp && p->aOp[p->nOp-1].opcode!=OP_Halt) ){
+ sqliteVdbeAddOp(p, OP_Halt, 0, 0);
+ }
+
+ /* No instruction ever pushes more than a single element onto the
+ ** stack. And the stack never grows on successive executions of the
+ ** same loop. So the total number of instructions is an upper bound
+ ** on the maximum stack depth required.
+ **
+ ** Allocation all the stack space we will ever need.
+ */
+ if( p->aStack==0 ){
+ p->nVar = nVar;
+ assert( nVar>=0 );
+ n = isExplain ? 10 : p->nOp;
+ p->aStack = sqliteMalloc(
+ n*(sizeof(p->aStack[0]) + 2*sizeof(char*)) /* aStack and zArgv */
+ + p->nVar*(sizeof(char*)+sizeof(int)+1) /* azVar, anVar, abVar */
+ );
+ p->zArgv = (char**)&p->aStack[n];
+ p->azColName = (char**)&p->zArgv[n];
+ p->azVar = (char**)&p->azColName[n];
+ p->anVar = (int*)&p->azVar[p->nVar];
+ p->abVar = (u8*)&p->anVar[p->nVar];
+ }
+
+ sqliteHashInit(&p->agg.hash, SQLITE_HASH_BINARY, 0);
+ p->agg.pSearch = 0;
+#ifdef MEMORY_DEBUG
+ if( sqliteOsFileExists("vdbe_trace") ){
+ p->trace = stdout;
+ }
+#endif
+ p->pTos = &p->aStack[-1];
+ p->pc = 0;
+ p->rc = SQLITE_OK;
+ p->uniqueCnt = 0;
+ p->returnDepth = 0;
+ p->errorAction = OE_Abort;
+ p->undoTransOnError = 0;
+ p->popStack = 0;
+ p->explain |= isExplain;
+ p->magic = VDBE_MAGIC_RUN;
+#ifdef VDBE_PROFILE
+ {
+ int i;
+ for(i=0; i<p->nOp; i++){
+ p->aOp[i].cnt = 0;
+ p->aOp[i].cycles = 0;
+ }
+ }
+#endif
+}
+
+
+/*
+** Remove any elements that remain on the sorter for the VDBE given.
+*/
+void sqliteVdbeSorterReset(Vdbe *p){
+ while( p->pSort ){
+ Sorter *pSorter = p->pSort;
+ p->pSort = pSorter->pNext;
+ sqliteFree(pSorter->zKey);
+ sqliteFree(pSorter->pData);
+ sqliteFree(pSorter);
+ }
+}
+
+/*
+** Reset an Agg structure. Delete all its contents.
+**
+** For installable aggregate functions, if the step function has been
+** called, make sure the finalizer function has also been called. The
+** finalizer might need to free memory that was allocated as part of its
+** private context. If the finalizer has not been called yet, call it
+** now.
+*/
+void sqliteVdbeAggReset(Agg *pAgg){
+ int i;
+ HashElem *p;
+ for(p = sqliteHashFirst(&pAgg->hash); p; p = sqliteHashNext(p)){
+ AggElem *pElem = sqliteHashData(p);
+ assert( pAgg->apFunc!=0 );
+ for(i=0; i<pAgg->nMem; i++){
+ Mem *pMem = &pElem->aMem[i];
+ if( pAgg->apFunc[i] && (pMem->flags & MEM_AggCtx)!=0 ){
+ sqlite_func ctx;
+ ctx.pFunc = pAgg->apFunc[i];
+ ctx.s.flags = MEM_Null;
+ ctx.pAgg = pMem->z;
+ ctx.cnt = pMem->i;
+ ctx.isStep = 0;
+ ctx.isError = 0;
+ (*pAgg->apFunc[i]->xFinalize)(&ctx);
+ if( pMem->z!=0 && pMem->z!=pMem->zShort ){
+ sqliteFree(pMem->z);
+ }
+ if( ctx.s.flags & MEM_Dyn ){
+ sqliteFree(ctx.s.z);
+ }
+ }else if( pMem->flags & MEM_Dyn ){
+ sqliteFree(pMem->z);
+ }
+ }
+ sqliteFree(pElem);
+ }
+ sqliteHashClear(&pAgg->hash);
+ sqliteFree(pAgg->apFunc);
+ pAgg->apFunc = 0;
+ pAgg->pCurrent = 0;
+ pAgg->pSearch = 0;
+ pAgg->nMem = 0;
+}
+
+/*
+** Delete a keylist
+*/
+void sqliteVdbeKeylistFree(Keylist *p){
+ while( p ){
+ Keylist *pNext = p->pNext;
+ sqliteFree(p);
+ p = pNext;
+ }
+}
+
+/*
+** Close a cursor and release all the resources that cursor happens
+** to hold.
+*/
+void sqliteVdbeCleanupCursor(Cursor *pCx){
+ if( pCx->pCursor ){
+ sqliteBtreeCloseCursor(pCx->pCursor);
+ }
+ if( pCx->pBt ){
+ sqliteBtreeClose(pCx->pBt);
+ }
+ sqliteFree(pCx->pData);
+ memset(pCx, 0, sizeof(Cursor));
+}
+
+/*
+** Close all cursors
+*/
+static void closeAllCursors(Vdbe *p){
+ int i;
+ for(i=0; i<p->nCursor; i++){
+ sqliteVdbeCleanupCursor(&p->aCsr[i]);
+ }
+ sqliteFree(p->aCsr);
+ p->aCsr = 0;
+ p->nCursor = 0;
+}
+
+/*
+** Clean up the VM after execution.
+**
+** This routine will automatically close any cursors, lists, and/or
+** sorters that were left open. It also deletes the values of
+** variables in the azVariable[] array.
+*/
+static void Cleanup(Vdbe *p){
+ int i;
+ if( p->aStack ){
+ Mem *pTos = p->pTos;
+ while( pTos>=p->aStack ){
+ if( pTos->flags & MEM_Dyn ){
+ sqliteFree(pTos->z);
+ }
+ pTos--;
+ }
+ p->pTos = pTos;
+ }
+ closeAllCursors(p);
+ if( p->aMem ){
+ for(i=0; i<p->nMem; i++){
+ if( p->aMem[i].flags & MEM_Dyn ){
+ sqliteFree(p->aMem[i].z);
+ }
+ }
+ }
+ sqliteFree(p->aMem);
+ p->aMem = 0;
+ p->nMem = 0;
+ if( p->pList ){
+ sqliteVdbeKeylistFree(p->pList);
+ p->pList = 0;
+ }
+ sqliteVdbeSorterReset(p);
+ if( p->pFile ){
+ if( p->pFile!=stdin ) fclose(p->pFile);
+ p->pFile = 0;
+ }
+ if( p->azField ){
+ sqliteFree(p->azField);
+ p->azField = 0;
+ }
+ p->nField = 0;
+ if( p->zLine ){
+ sqliteFree(p->zLine);
+ p->zLine = 0;
+ }
+ p->nLineAlloc = 0;
+ sqliteVdbeAggReset(&p->agg);
+ if( p->aSet ){
+ for(i=0; i<p->nSet; i++){
+ sqliteHashClear(&p->aSet[i].hash);
+ }
+ }
+ sqliteFree(p->aSet);
+ p->aSet = 0;
+ p->nSet = 0;
+ if( p->keylistStack ){
+ int ii;
+ for(ii = 0; ii < p->keylistStackDepth; ii++){
+ sqliteVdbeKeylistFree(p->keylistStack[ii]);
+ }
+ sqliteFree(p->keylistStack);
+ p->keylistStackDepth = 0;
+ p->keylistStack = 0;
+ }
+ sqliteFree(p->contextStack);
+ p->contextStack = 0;
+ sqliteFree(p->zErrMsg);
+ p->zErrMsg = 0;
+}
+
+/*
+** Clean up a VDBE after execution but do not delete the VDBE just yet.
+** Write any error messages into *pzErrMsg. Return the result code.
+**
+** After this routine is run, the VDBE should be ready to be executed
+** again.
+*/
+int sqliteVdbeReset(Vdbe *p, char **pzErrMsg){
+ sqlite *db = p->db;
+ int i;
+
+ if( p->magic!=VDBE_MAGIC_RUN && p->magic!=VDBE_MAGIC_HALT ){
+ sqliteSetString(pzErrMsg, sqlite_error_string(SQLITE_MISUSE), (char*)0);
+ return SQLITE_MISUSE;
+ }
+ if( p->zErrMsg ){
+ if( pzErrMsg && *pzErrMsg==0 ){
+ *pzErrMsg = p->zErrMsg;
+ }else{
+ sqliteFree(p->zErrMsg);
+ }
+ p->zErrMsg = 0;
+ }else if( p->rc ){
+ sqliteSetString(pzErrMsg, sqlite_error_string(p->rc), (char*)0);
+ }
+ Cleanup(p);
+ if( p->rc!=SQLITE_OK ){
+ switch( p->errorAction ){
+ case OE_Abort: {
+ if( !p->undoTransOnError ){
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt ){
+ sqliteBtreeRollbackCkpt(db->aDb[i].pBt);
+ }
+ }
+ break;
+ }
+ /* Fall through to ROLLBACK */
+ }
+ case OE_Rollback: {
+ sqliteRollbackAll(db);
+ db->flags &= ~SQLITE_InTrans;
+ db->onError = OE_Default;
+ break;
+ }
+ default: {
+ if( p->undoTransOnError ){
+ sqliteRollbackAll(db);
+ db->flags &= ~SQLITE_InTrans;
+ db->onError = OE_Default;
+ }
+ break;
+ }
+ }
+ sqliteRollbackInternalChanges(db);
+ }
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt && db->aDb[i].inTrans==2 ){
+ sqliteBtreeCommitCkpt(db->aDb[i].pBt);
+ db->aDb[i].inTrans = 1;
+ }
+ }
+ assert( p->pTos<&p->aStack[p->pc] || sqlite_malloc_failed==1 );
+#ifdef VDBE_PROFILE
+ {
+ FILE *out = fopen("vdbe_profile.out", "a");
+ if( out ){
+ int i;
+ fprintf(out, "---- ");
+ for(i=0; i<p->nOp; i++){
+ fprintf(out, "%02x", p->aOp[i].opcode);
+ }
+ fprintf(out, "\n");
+ for(i=0; i<p->nOp; i++){
+ fprintf(out, "%6d %10lld %8lld ",
+ p->aOp[i].cnt,
+ p->aOp[i].cycles,
+ p->aOp[i].cnt>0 ? p->aOp[i].cycles/p->aOp[i].cnt : 0
+ );
+ sqliteVdbePrintOp(out, i, &p->aOp[i]);
+ }
+ fclose(out);
+ }
+ }
+#endif
+ p->magic = VDBE_MAGIC_INIT;
+ return p->rc;
+}
+
+/*
+** Clean up and delete a VDBE after execution. Return an integer which is
+** the result code. Write any error message text into *pzErrMsg.
+*/
+int sqliteVdbeFinalize(Vdbe *p, char **pzErrMsg){
+ int rc;
+ sqlite *db;
+
+ if( p->magic!=VDBE_MAGIC_RUN && p->magic!=VDBE_MAGIC_HALT ){
+ sqliteSetString(pzErrMsg, sqlite_error_string(SQLITE_MISUSE), (char*)0);
+ return SQLITE_MISUSE;
+ }
+ db = p->db;
+ rc = sqliteVdbeReset(p, pzErrMsg);
+ sqliteVdbeDelete(p);
+ if( db->want_to_close && db->pVdbe==0 ){
+ sqlite_close(db);
+ }
+ if( rc==SQLITE_SCHEMA ){
+ sqliteResetInternalSchema(db, 0);
+ }
+ return rc;
+}
+
+/*
+** Set the values of all variables. Variable $1 in the original SQL will
+** be the string azValue[0]. $2 will have the value azValue[1]. And
+** so forth. If a value is out of range (for example $3 when nValue==2)
+** then its value will be NULL.
+**
+** This routine overrides any prior call.
+*/
+int sqlite_bind(sqlite_vm *pVm, int i, const char *zVal, int len, int copy){
+ Vdbe *p = (Vdbe*)pVm;
+ if( p->magic!=VDBE_MAGIC_RUN || p->pc!=0 ){
+ return SQLITE_MISUSE;
+ }
+ if( i<1 || i>p->nVar ){
+ return SQLITE_RANGE;
+ }
+ i--;
+ if( p->abVar[i] ){
+ sqliteFree(p->azVar[i]);
+ }
+ if( zVal==0 ){
+ copy = 0;
+ len = 0;
+ }
+ if( len<0 ){
+ len = strlen(zVal)+1;
+ }
+ if( copy ){
+ p->azVar[i] = sqliteMalloc( len );
+ if( p->azVar[i] ) memcpy(p->azVar[i], zVal, len);
+ }else{
+ p->azVar[i] = (char*)zVal;
+ }
+ p->abVar[i] = copy;
+ p->anVar[i] = len;
+ return SQLITE_OK;
+}
+
+
+/*
+** Delete an entire VDBE.
+*/
+void sqliteVdbeDelete(Vdbe *p){
+ int i;
+ if( p==0 ) return;
+ Cleanup(p);
+ if( p->pPrev ){
+ p->pPrev->pNext = p->pNext;
+ }else{
+ assert( p->db->pVdbe==p );
+ p->db->pVdbe = p->pNext;
+ }
+ if( p->pNext ){
+ p->pNext->pPrev = p->pPrev;
+ }
+ p->pPrev = p->pNext = 0;
+ if( p->nOpAlloc==0 ){
+ p->aOp = 0;
+ p->nOp = 0;
+ }
+ for(i=0; i<p->nOp; i++){
+ if( p->aOp[i].p3type==P3_DYNAMIC ){
+ sqliteFree(p->aOp[i].p3);
+ }
+ }
+ for(i=0; i<p->nVar; i++){
+ if( p->abVar[i] ) sqliteFree(p->azVar[i]);
+ }
+ sqliteFree(p->aOp);
+ sqliteFree(p->aLabel);
+ sqliteFree(p->aStack);
+ p->magic = VDBE_MAGIC_DEAD;
+ sqliteFree(p);
+}
+
+/*
+** Convert an integer in between the native integer format and
+** the bigEndian format used as the record number for tables.
+**
+** The bigEndian format (most significant byte first) is used for
+** record numbers so that records will sort into the correct order
+** even though memcmp() is used to compare the keys. On machines
+** whose native integer format is little endian (ex: i486) the
+** order of bytes is reversed. On native big-endian machines
+** (ex: Alpha, Sparc, Motorola) the byte order is the same.
+**
+** This function is its own inverse. In other words
+**
+** X == byteSwap(byteSwap(X))
+*/
+int sqliteVdbeByteSwap(int x){
+ union {
+ char zBuf[sizeof(int)];
+ int i;
+ } ux;
+ ux.zBuf[3] = x&0xff;
+ ux.zBuf[2] = (x>>8)&0xff;
+ ux.zBuf[1] = (x>>16)&0xff;
+ ux.zBuf[0] = (x>>24)&0xff;
+ return ux.i;
+}
+
+/*
+** If a MoveTo operation is pending on the given cursor, then do that
+** MoveTo now. Return an error code. If no MoveTo is pending, this
+** routine does nothing and returns SQLITE_OK.
+*/
+int sqliteVdbeCursorMoveto(Cursor *p){
+ if( p->deferredMoveto ){
+ int res;
+ extern int sqlite_search_count;
+ sqliteBtreeMoveto(p->pCursor, (char*)&p->movetoTarget, sizeof(int), &res);
+ p->lastRecno = keyToInt(p->movetoTarget);
+ p->recnoIsValid = res==0;
+ if( res<0 ){
+ sqliteBtreeNext(p->pCursor, &res);
+ }
+ sqlite_search_count++;
+ p->deferredMoveto = 0;
+ }
+ return SQLITE_OK;
+}
diff --git a/src/libs/sqlite2/where.c b/src/libs/sqlite2/where.c
new file mode 100644
index 00000000..ea427719
--- /dev/null
+++ b/src/libs/sqlite2/where.c
@@ -0,0 +1,1235 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This module contains C code that generates VDBE code used to process
+** the WHERE clause of SQL statements.
+**
+** $Id: where.c 875429 2008-10-24 12:20:41Z cgilles $
+*/
+#include "sqliteInt.h"
+
+/*
+** The query generator uses an array of instances of this structure to
+** help it analyze the subexpressions of the WHERE clause. Each WHERE
+** clause subexpression is separated from the others by an AND operator.
+*/
+typedef struct ExprInfo ExprInfo;
+struct ExprInfo {
+ Expr *p; /* Pointer to the subexpression */
+ u8 indexable; /* True if this subexprssion is usable by an index */
+ short int idxLeft; /* p->pLeft is a column in this table number. -1 if
+ ** p->pLeft is not the column of any table */
+ short int idxRight; /* p->pRight is a column in this table number. -1 if
+ ** p->pRight is not the column of any table */
+ unsigned prereqLeft; /* Bitmask of tables referenced by p->pLeft */
+ unsigned prereqRight; /* Bitmask of tables referenced by p->pRight */
+ unsigned prereqAll; /* Bitmask of tables referenced by p */
+};
+
+/*
+** An instance of the following structure keeps track of a mapping
+** between VDBE cursor numbers and bitmasks. The VDBE cursor numbers
+** are small integers contained in SrcList_item.iCursor and Expr.iTable
+** fields. For any given WHERE clause, we want to track which cursors
+** are being used, so we assign a single bit in a 32-bit word to track
+** that cursor. Then a 32-bit integer is able to show the set of all
+** cursors being used.
+*/
+typedef struct ExprMaskSet ExprMaskSet;
+struct ExprMaskSet {
+ int n; /* Number of assigned cursor values */
+ int ix[31]; /* Cursor assigned to each bit */
+};
+
+/*
+** Determine the number of elements in an array.
+*/
+#define ARRAYSIZE(X) (sizeof(X)/sizeof(X[0]))
+
+/*
+** This routine is used to divide the WHERE expression into subexpressions
+** separated by the AND operator.
+**
+** aSlot[] is an array of subexpressions structures.
+** There are nSlot spaces left in this array. This routine attempts to
+** split pExpr into subexpressions and fills aSlot[] with those subexpressions.
+** The return value is the number of slots filled.
+*/
+static int exprSplit(int nSlot, ExprInfo *aSlot, Expr *pExpr){
+ int cnt = 0;
+ if( pExpr==0 || nSlot<1 ) return 0;
+ if( nSlot==1 || pExpr->op!=TK_AND ){
+ aSlot[0].p = pExpr;
+ return 1;
+ }
+ if( pExpr->pLeft->op!=TK_AND ){
+ aSlot[0].p = pExpr->pLeft;
+ cnt = 1 + exprSplit(nSlot-1, &aSlot[1], pExpr->pRight);
+ }else{
+ cnt = exprSplit(nSlot, aSlot, pExpr->pLeft);
+ cnt += exprSplit(nSlot-cnt, &aSlot[cnt], pExpr->pRight);
+ }
+ return cnt;
+}
+
+/*
+** Initialize an expression mask set
+*/
+#define initMaskSet(P) memset(P, 0, sizeof(*P))
+
+/*
+** Return the bitmask for the given cursor. Assign a new bitmask
+** if this is the first time the cursor has been seen.
+*/
+static int getMask(ExprMaskSet *pMaskSet, int iCursor){
+ int i;
+ for(i=0; i<pMaskSet->n; i++){
+ if( pMaskSet->ix[i]==iCursor ) return 1<<i;
+ }
+ if( i==pMaskSet->n && i<ARRAYSIZE(pMaskSet->ix) ){
+ pMaskSet->n++;
+ pMaskSet->ix[i] = iCursor;
+ return 1<<i;
+ }
+ return 0;
+}
+
+/*
+** Destroy an expression mask set
+*/
+#define freeMaskSet(P) /* NO-OP */
+
+/*
+** This routine walks (recursively) an expression tree and generates
+** a bitmask indicating which tables are used in that expression
+** tree.
+**
+** In order for this routine to work, the calling function must have
+** previously invoked sqliteExprResolveIds() on the expression. See
+** the header comment on that routine for additional information.
+** The sqliteExprResolveIds() routines looks for column names and
+** sets their opcodes to TK_COLUMN and their Expr.iTable fields to
+** the VDBE cursor number of the table.
+*/
+static int exprTableUsage(ExprMaskSet *pMaskSet, Expr *p){
+ unsigned int mask = 0;
+ if( p==0 ) return 0;
+ if( p->op==TK_COLUMN ){
+ mask = getMask(pMaskSet, p->iTable);
+ if( mask==0 ) mask = -1;
+ return mask;
+ }
+ if( p->pRight ){
+ mask = exprTableUsage(pMaskSet, p->pRight);
+ }
+ if( p->pLeft ){
+ mask |= exprTableUsage(pMaskSet, p->pLeft);
+ }
+ if( p->pList ){
+ int i;
+ for(i=0; i<p->pList->nExpr; i++){
+ mask |= exprTableUsage(pMaskSet, p->pList->a[i].pExpr);
+ }
+ }
+ return mask;
+}
+
+/*
+** Return TRUE if the given operator is one of the operators that is
+** allowed for an indexable WHERE clause. The allowed operators are
+** "=", "<", ">", "<=", ">=", and "IN".
+*/
+static int allowedOp(int op){
+ switch( op ){
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_EQ:
+ case TK_IN:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*
+** The input to this routine is an ExprInfo structure with only the
+** "p" field filled in. The job of this routine is to analyze the
+** subexpression and populate all the other fields of the ExprInfo
+** structure.
+*/
+static void exprAnalyze(ExprMaskSet *pMaskSet, ExprInfo *pInfo){
+ Expr *pExpr = pInfo->p;
+ pInfo->prereqLeft = exprTableUsage(pMaskSet, pExpr->pLeft);
+ pInfo->prereqRight = exprTableUsage(pMaskSet, pExpr->pRight);
+ pInfo->prereqAll = exprTableUsage(pMaskSet, pExpr);
+ pInfo->indexable = 0;
+ pInfo->idxLeft = -1;
+ pInfo->idxRight = -1;
+ if( allowedOp(pExpr->op) && (pInfo->prereqRight & pInfo->prereqLeft)==0 ){
+ if( pExpr->pRight && pExpr->pRight->op==TK_COLUMN ){
+ pInfo->idxRight = pExpr->pRight->iTable;
+ pInfo->indexable = 1;
+ }
+ if( pExpr->pLeft->op==TK_COLUMN ){
+ pInfo->idxLeft = pExpr->pLeft->iTable;
+ pInfo->indexable = 1;
+ }
+ }
+}
+
+/*
+** pOrderBy is an ORDER BY clause from a SELECT statement. pTab is the
+** left-most table in the FROM clause of that same SELECT statement and
+** the table has a cursor number of "base".
+**
+** This routine attempts to find an index for pTab that generates the
+** correct record sequence for the given ORDER BY clause. The return value
+** is a pointer to an index that does the job. NULL is returned if the
+** table has no index that will generate the correct sort order.
+**
+** If there are two or more indices that generate the correct sort order
+** and pPreferredIdx is one of those indices, then return pPreferredIdx.
+**
+** nEqCol is the number of columns of pPreferredIdx that are used as
+** equality constraints. Any index returned must have exactly this same
+** set of columns. The ORDER BY clause only matches index columns beyond the
+** the first nEqCol columns.
+**
+** All terms of the ORDER BY clause must be either ASC or DESC. The
+** *pbRev value is set to 1 if the ORDER BY clause is all DESC and it is
+** set to 0 if the ORDER BY clause is all ASC.
+*/
+static Index *findSortingIndex(
+ Table *pTab, /* The table to be sorted */
+ int base, /* Cursor number for pTab */
+ ExprList *pOrderBy, /* The ORDER BY clause */
+ Index *pPreferredIdx, /* Use this index, if possible and not NULL */
+ int nEqCol, /* Number of index columns used with == constraints */
+ int *pbRev /* Set to 1 if ORDER BY is DESC */
+){
+ int i, j;
+ Index *pMatch;
+ Index *pIdx;
+ int sortOrder;
+
+ assert( pOrderBy!=0 );
+ assert( pOrderBy->nExpr>0 );
+ sortOrder = pOrderBy->a[0].sortOrder & SQLITE_SO_DIRMASK;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ Expr *p;
+ if( (pOrderBy->a[i].sortOrder & SQLITE_SO_DIRMASK)!=sortOrder ){
+ /* Indices can only be used if all ORDER BY terms are either
+ ** DESC or ASC. Indices cannot be used on a mixture. */
+ return 0;
+ }
+ if( (pOrderBy->a[i].sortOrder & SQLITE_SO_TYPEMASK)!=SQLITE_SO_UNK ){
+ /* Do not sort by index if there is a COLLATE clause */
+ return 0;
+ }
+ p = pOrderBy->a[i].pExpr;
+ if( p->op!=TK_COLUMN || p->iTable!=base ){
+ /* Can not use an index sort on anything that is not a column in the
+ ** left-most table of the FROM clause */
+ return 0;
+ }
+ }
+
+ /* If we get this far, it means the ORDER BY clause consists only of
+ ** ascending columns in the left-most table of the FROM clause. Now
+ ** check for a matching index.
+ */
+ pMatch = 0;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int nExpr = pOrderBy->nExpr;
+ if( pIdx->nColumn < nEqCol || pIdx->nColumn < nExpr ) continue;
+ for(i=j=0; i<nEqCol; i++){
+ if( pPreferredIdx->aiColumn[i]!=pIdx->aiColumn[i] ) break;
+ if( j<nExpr && pOrderBy->a[j].pExpr->iColumn==pIdx->aiColumn[i] ){ j++; }
+ }
+ if( i<nEqCol ) continue;
+ for(i=0; i+j<nExpr; i++){
+ if( pOrderBy->a[i+j].pExpr->iColumn!=pIdx->aiColumn[i+nEqCol] ) break;
+ }
+ if( i+j>=nExpr ){
+ pMatch = pIdx;
+ if( pIdx==pPreferredIdx ) break;
+ }
+ }
+ if( pMatch && pbRev ){
+ *pbRev = sortOrder==SQLITE_SO_DESC;
+ }
+ return pMatch;
+}
+
+/*
+** Disable a term in the WHERE clause. Except, do not disable the term
+** if it controls a LEFT OUTER JOIN and it did not originate in the ON
+** or USING clause of that join.
+**
+** Consider the term t2.z='ok' in the following queries:
+**
+** (1) SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.x WHERE t2.z='ok'
+** (2) SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.x AND t2.z='ok'
+** (3) SELECT * FROM t1, t2 WHERE t1.a=t2.x AND t2.z='ok'
+**
+** The t2.z='ok' is disabled in the in (2) because it did not originate
+** in the ON clause. The term is disabled in (3) because it is not part
+** of a LEFT OUTER JOIN. In (1), the term is not disabled.
+**
+** Disabling a term causes that term to not be tested in the inner loop
+** of the join. Disabling is an optimization. We would get the correct
+** results if nothing were ever disabled, but joins might run a little
+** slower. The trick is to disable as much as we can without disabling
+** too much. If we disabled in (1), we'd get the wrong answer.
+** See ticket #813.
+*/
+static void disableTerm(WhereLevel *pLevel, Expr **ppExpr){
+ Expr *pExpr = *ppExpr;
+ if( pLevel->iLeftJoin==0 || ExprHasProperty(pExpr, EP_FromJoin) ){
+ *ppExpr = 0;
+ }
+}
+
+/*
+** Generate the beginning of the loop used for WHERE clause processing.
+** The return value is a pointer to an (opaque) structure that contains
+** information needed to terminate the loop. Later, the calling routine
+** should invoke sqliteWhereEnd() with the return value of this function
+** in order to complete the WHERE clause processing.
+**
+** If an error occurs, this routine returns NULL.
+**
+** The basic idea is to do a nested loop, one loop for each table in
+** the FROM clause of a select. (INSERT and UPDATE statements are the
+** same as a SELECT with only a single table in the FROM clause.) For
+** example, if the SQL is this:
+**
+** SELECT * FROM t1, t2, t3 WHERE ...;
+**
+** Then the code generated is conceptually like the following:
+**
+** foreach row1 in t1 do \ Code generated
+** foreach row2 in t2 do |-- by sqliteWhereBegin()
+** foreach row3 in t3 do /
+** ...
+** end \ Code generated
+** end |-- by sqliteWhereEnd()
+** end /
+**
+** There are Btree cursors associated with each table. t1 uses cursor
+** number pTabList->a[0].iCursor. t2 uses the cursor pTabList->a[1].iCursor.
+** And so forth. This routine generates code to open those VDBE cursors
+** and sqliteWhereEnd() generates the code to close them.
+**
+** If the WHERE clause is empty, the foreach loops must each scan their
+** entire tables. Thus a three-way join is an O(N^3) operation. But if
+** the tables have indices and there are terms in the WHERE clause that
+** refer to those indices, a complete table scan can be avoided and the
+** code will run much faster. Most of the work of this routine is checking
+** to see if there are indices that can be used to speed up the loop.
+**
+** Terms of the WHERE clause are also used to limit which rows actually
+** make it to the "..." in the middle of the loop. After each "foreach",
+** terms of the WHERE clause that use only terms in that loop and outer
+** loops are evaluated and if false a jump is made around all subsequent
+** inner loops (or around the "..." if the test occurs within the inner-
+** most loop)
+**
+** OUTER JOINS
+**
+** An outer join of tables t1 and t2 is conceptally coded as follows:
+**
+** foreach row1 in t1 do
+** flag = 0
+** foreach row2 in t2 do
+** start:
+** ...
+** flag = 1
+** end
+** if flag==0 then
+** move the row2 cursor to a null row
+** goto start
+** fi
+** end
+**
+** ORDER BY CLAUSE PROCESSING
+**
+** *ppOrderBy is a pointer to the ORDER BY clause of a SELECT statement,
+** if there is one. If there is no ORDER BY clause or if this routine
+** is called from an UPDATE or DELETE statement, then ppOrderBy is NULL.
+**
+** If an index can be used so that the natural output order of the table
+** scan is correct for the ORDER BY clause, then that index is used and
+** *ppOrderBy is set to NULL. This is an optimization that prevents an
+** unnecessary sort of the result set if an index appropriate for the
+** ORDER BY clause already exists.
+**
+** If the where clause loops cannot be arranged to provide the correct
+** output order, then the *ppOrderBy is unchanged.
+*/
+WhereInfo *sqliteWhereBegin(
+ Parse *pParse, /* The parser context */
+ SrcList *pTabList, /* A list of all tables to be scanned */
+ Expr *pWhere, /* The WHERE clause */
+ int pushKey, /* If TRUE, leave the table key on the stack */
+ ExprList **ppOrderBy /* An ORDER BY clause, or NULL */
+){
+ int i; /* Loop counter */
+ WhereInfo *pWInfo; /* Will become the return value of this function */
+ Vdbe *v = pParse->pVdbe; /* The virtual database engine */
+ int brk, cont = 0; /* Addresses used during code generation */
+ int nExpr; /* Number of subexpressions in the WHERE clause */
+ int loopMask; /* One bit set for each outer loop */
+ int haveKey; /* True if KEY is on the stack */
+ ExprMaskSet maskSet; /* The expression mask set */
+ int iDirectEq[32]; /* Term of the form ROWID==X for the N-th table */
+ int iDirectLt[32]; /* Term of the form ROWID<X or ROWID<=X */
+ int iDirectGt[32]; /* Term of the form ROWID>X or ROWID>=X */
+ ExprInfo aExpr[101]; /* The WHERE clause is divided into these expressions */
+
+ /* pushKey is only allowed if there is a single table (as in an INSERT or
+ ** UPDATE statement)
+ */
+ assert( pushKey==0 || pTabList->nSrc==1 );
+
+ /* Split the WHERE clause into separate subexpressions where each
+ ** subexpression is separated by an AND operator. If the aExpr[]
+ ** array fills up, the last entry might point to an expression which
+ ** contains additional unfactored AND operators.
+ */
+ initMaskSet(&maskSet);
+ memset(aExpr, 0, sizeof(aExpr));
+ nExpr = exprSplit(ARRAYSIZE(aExpr), aExpr, pWhere);
+ if( nExpr==ARRAYSIZE(aExpr) ){
+ sqliteErrorMsg(pParse, "WHERE clause too complex - no more "
+ "than %d terms allowed", (int)ARRAYSIZE(aExpr)-1);
+ return 0;
+ }
+
+ /* Allocate and initialize the WhereInfo structure that will become the
+ ** return value.
+ */
+ pWInfo = sqliteMalloc( sizeof(WhereInfo) + pTabList->nSrc*sizeof(WhereLevel));
+ if( sqlite_malloc_failed ){
+ sqliteFree(pWInfo);
+ return 0;
+ }
+ pWInfo->pParse = pParse;
+ pWInfo->pTabList = pTabList;
+ pWInfo->peakNTab = pWInfo->savedNTab = pParse->nTab;
+ pWInfo->iBreak = sqliteVdbeMakeLabel(v);
+
+ /* Special case: a WHERE clause that is constant. Evaluate the
+ ** expression and either jump over all of the code or fall thru.
+ */
+ if( pWhere && (pTabList->nSrc==0 || sqliteExprIsConstant(pWhere)) ){
+ sqliteExprIfFalse(pParse, pWhere, pWInfo->iBreak, 1);
+ pWhere = 0;
+ }
+
+ /* Analyze all of the subexpressions.
+ */
+ for(i=0; i<nExpr; i++){
+ exprAnalyze(&maskSet, &aExpr[i]);
+
+ /* If we are executing a trigger body, remove all references to
+ ** new.* and old.* tables from the prerequisite masks.
+ */
+ if( pParse->trigStack ){
+ int x;
+ if( (x = pParse->trigStack->newIdx) >= 0 ){
+ int mask = ~getMask(&maskSet, x);
+ aExpr[i].prereqRight &= mask;
+ aExpr[i].prereqLeft &= mask;
+ aExpr[i].prereqAll &= mask;
+ }
+ if( (x = pParse->trigStack->oldIdx) >= 0 ){
+ int mask = ~getMask(&maskSet, x);
+ aExpr[i].prereqRight &= mask;
+ aExpr[i].prereqLeft &= mask;
+ aExpr[i].prereqAll &= mask;
+ }
+ }
+ }
+
+ /* Figure out what index to use (if any) for each nested loop.
+ ** Make pWInfo->a[i].pIdx point to the index to use for the i-th nested
+ ** loop where i==0 is the outer loop and i==pTabList->nSrc-1 is the inner
+ ** loop.
+ **
+ ** If terms exist that use the ROWID of any table, then set the
+ ** iDirectEq[], iDirectLt[], or iDirectGt[] elements for that table
+ ** to the index of the term containing the ROWID. We always prefer
+ ** to use a ROWID which can directly access a table rather than an
+ ** index which requires reading an index first to get the rowid then
+ ** doing a second read of the actual database table.
+ **
+ ** Actually, if there are more than 32 tables in the join, only the
+ ** first 32 tables are candidates for indices. This is (again) due
+ ** to the limit of 32 bits in an integer bitmask.
+ */
+ loopMask = 0;
+ for(i=0; i<pTabList->nSrc && i<ARRAYSIZE(iDirectEq); i++){
+ int j;
+ int iCur = pTabList->a[i].iCursor; /* The cursor for this table */
+ int mask = getMask(&maskSet, iCur); /* Cursor mask for this table */
+ Table *pTab = pTabList->a[i].pTab;
+ Index *pIdx;
+ Index *pBestIdx = 0;
+ int bestScore = 0;
+
+ /* Check to see if there is an expression that uses only the
+ ** ROWID field of this table. For terms of the form ROWID==expr
+ ** set iDirectEq[i] to the index of the term. For terms of the
+ ** form ROWID<expr or ROWID<=expr set iDirectLt[i] to the term index.
+ ** For terms like ROWID>expr or ROWID>=expr set iDirectGt[i].
+ **
+ ** (Added:) Treat ROWID IN expr like ROWID=expr.
+ */
+ pWInfo->a[i].iCur = -1;
+ iDirectEq[i] = -1;
+ iDirectLt[i] = -1;
+ iDirectGt[i] = -1;
+ for(j=0; j<nExpr; j++){
+ if( aExpr[j].idxLeft==iCur && aExpr[j].p->pLeft->iColumn<0
+ && (aExpr[j].prereqRight & loopMask)==aExpr[j].prereqRight ){
+ switch( aExpr[j].p->op ){
+ case TK_IN:
+ case TK_EQ: iDirectEq[i] = j; break;
+ case TK_LE:
+ case TK_LT: iDirectLt[i] = j; break;
+ case TK_GE:
+ case TK_GT: iDirectGt[i] = j; break;
+ }
+ }
+ if( aExpr[j].idxRight==iCur && aExpr[j].p->pRight->iColumn<0
+ && (aExpr[j].prereqLeft & loopMask)==aExpr[j].prereqLeft ){
+ switch( aExpr[j].p->op ){
+ case TK_EQ: iDirectEq[i] = j; break;
+ case TK_LE:
+ case TK_LT: iDirectGt[i] = j; break;
+ case TK_GE:
+ case TK_GT: iDirectLt[i] = j; break;
+ }
+ }
+ }
+ if( iDirectEq[i]>=0 ){
+ loopMask |= mask;
+ pWInfo->a[i].pIdx = 0;
+ continue;
+ }
+
+ /* Do a search for usable indices. Leave pBestIdx pointing to
+ ** the "best" index. pBestIdx is left set to NULL if no indices
+ ** are usable.
+ **
+ ** The best index is determined as follows. For each of the
+ ** left-most terms that is fixed by an equality operator, add
+ ** 8 to the score. The right-most term of the index may be
+ ** constrained by an inequality. Add 1 if for an "x<..." constraint
+ ** and add 2 for an "x>..." constraint. Chose the index that
+ ** gives the best score.
+ **
+ ** This scoring system is designed so that the score can later be
+ ** used to determine how the index is used. If the score&7 is 0
+ ** then all constraints are equalities. If score&1 is not 0 then
+ ** there is an inequality used as a termination key. (ex: "x<...")
+ ** If score&2 is not 0 then there is an inequality used as the
+ ** start key. (ex: "x>..."). A score or 4 is the special case
+ ** of an IN operator constraint. (ex: "x IN ...").
+ **
+ ** The IN operator (as in "<expr> IN (...)") is treated the same as
+ ** an equality comparison except that it can only be used on the
+ ** left-most column of an index and other terms of the WHERE clause
+ ** cannot be used in conjunction with the IN operator to help satisfy
+ ** other columns of the index.
+ */
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int eqMask = 0; /* Index columns covered by an x=... term */
+ int ltMask = 0; /* Index columns covered by an x<... term */
+ int gtMask = 0; /* Index columns covered by an x>... term */
+ int inMask = 0; /* Index columns covered by an x IN .. term */
+ int nEq, m, score;
+
+ if( pIdx->nColumn>32 ) continue; /* Ignore indices too many columns */
+ for(j=0; j<nExpr; j++){
+ if( aExpr[j].idxLeft==iCur
+ && (aExpr[j].prereqRight & loopMask)==aExpr[j].prereqRight ){
+ int iColumn = aExpr[j].p->pLeft->iColumn;
+ int k;
+ for(k=0; k<pIdx->nColumn; k++){
+ if( pIdx->aiColumn[k]==iColumn ){
+ switch( aExpr[j].p->op ){
+ case TK_IN: {
+ if( k==0 ) inMask |= 1;
+ break;
+ }
+ case TK_EQ: {
+ eqMask |= 1<<k;
+ break;
+ }
+ case TK_LE:
+ case TK_LT: {
+ ltMask |= 1<<k;
+ break;
+ }
+ case TK_GE:
+ case TK_GT: {
+ gtMask |= 1<<k;
+ break;
+ }
+ default: {
+ /* CANT_HAPPEN */
+ assert( 0 );
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ if( aExpr[j].idxRight==iCur
+ && (aExpr[j].prereqLeft & loopMask)==aExpr[j].prereqLeft ){
+ int iColumn = aExpr[j].p->pRight->iColumn;
+ int k;
+ for(k=0; k<pIdx->nColumn; k++){
+ if( pIdx->aiColumn[k]==iColumn ){
+ switch( aExpr[j].p->op ){
+ case TK_EQ: {
+ eqMask |= 1<<k;
+ break;
+ }
+ case TK_LE:
+ case TK_LT: {
+ gtMask |= 1<<k;
+ break;
+ }
+ case TK_GE:
+ case TK_GT: {
+ ltMask |= 1<<k;
+ break;
+ }
+ default: {
+ /* CANT_HAPPEN */
+ assert( 0 );
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /* The following loop ends with nEq set to the number of columns
+ ** on the left of the index with == constraints.
+ */
+ for(nEq=0; nEq<pIdx->nColumn; nEq++){
+ m = (1<<(nEq+1))-1;
+ if( (m & eqMask)!=m ) break;
+ }
+ score = nEq*8; /* Base score is 8 times number of == constraints */
+ m = 1<<nEq;
+ if( m & ltMask ) score++; /* Increase score for a < constraint */
+ if( m & gtMask ) score+=2; /* Increase score for a > constraint */
+ if( score==0 && inMask ) score = 4; /* Default score for IN constraint */
+ if( score>bestScore ){
+ pBestIdx = pIdx;
+ bestScore = score;
+ }
+ }
+ pWInfo->a[i].pIdx = pBestIdx;
+ pWInfo->a[i].score = bestScore;
+ pWInfo->a[i].bRev = 0;
+ loopMask |= mask;
+ if( pBestIdx ){
+ pWInfo->a[i].iCur = pParse->nTab++;
+ pWInfo->peakNTab = pParse->nTab;
+ }
+ }
+
+ /* Check to see if the ORDER BY clause is or can be satisfied by the
+ ** use of an index on the first table.
+ */
+ if( ppOrderBy && *ppOrderBy && pTabList->nSrc>0 ){
+ Index *pSortIdx;
+ Index *pIdx;
+ Table *pTab;
+ int bRev = 0;
+
+ pTab = pTabList->a[0].pTab;
+ pIdx = pWInfo->a[0].pIdx;
+ if( pIdx && pWInfo->a[0].score==4 ){
+ /* If there is already an IN index on the left-most table,
+ ** it will not give the correct sort order.
+ ** So, pretend that no suitable index is found.
+ */
+ pSortIdx = 0;
+ }else if( iDirectEq[0]>=0 || iDirectLt[0]>=0 || iDirectGt[0]>=0 ){
+ /* If the left-most column is accessed using its ROWID, then do
+ ** not try to sort by index.
+ */
+ pSortIdx = 0;
+ }else{
+ int nEqCol = (pWInfo->a[0].score+4)/8;
+ pSortIdx = findSortingIndex(pTab, pTabList->a[0].iCursor,
+ *ppOrderBy, pIdx, nEqCol, &bRev);
+ }
+ if( pSortIdx && (pIdx==0 || pIdx==pSortIdx) ){
+ if( pIdx==0 ){
+ pWInfo->a[0].pIdx = pSortIdx;
+ pWInfo->a[0].iCur = pParse->nTab++;
+ pWInfo->peakNTab = pParse->nTab;
+ }
+ pWInfo->a[0].bRev = bRev;
+ *ppOrderBy = 0;
+ }
+ }
+
+ /* Open all tables in the pTabList and all indices used by those tables.
+ */
+ for(i=0; i<pTabList->nSrc; i++){
+ Table *pTab;
+ Index *pIx;
+
+ pTab = pTabList->a[i].pTab;
+ if( pTab->isTransient || pTab->pSelect ) continue;
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenRead, pTabList->a[i].iCursor, pTab->tnum,
+ pTab->zName, P3_STATIC);
+ sqliteCodeVerifySchema(pParse, pTab->iDb);
+ if( (pIx = pWInfo->a[i].pIdx)!=0 ){
+ sqliteVdbeAddOp(v, OP_Integer, pIx->iDb, 0);
+ sqliteVdbeOp3(v, OP_OpenRead, pWInfo->a[i].iCur, pIx->tnum, pIx->zName,0);
+ }
+ }
+
+ /* Generate the code to do the search
+ */
+ loopMask = 0;
+ for(i=0; i<pTabList->nSrc; i++){
+ int j, k;
+ int iCur = pTabList->a[i].iCursor;
+ Index *pIdx;
+ WhereLevel *pLevel = &pWInfo->a[i];
+
+ /* If this is the right table of a LEFT OUTER JOIN, allocate and
+ ** initialize a memory cell that records if this table matches any
+ ** row of the left table of the join.
+ */
+ if( i>0 && (pTabList->a[i-1].jointype & JT_LEFT)!=0 ){
+ if( !pParse->nMem ) pParse->nMem++;
+ pLevel->iLeftJoin = pParse->nMem++;
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, pLevel->iLeftJoin, 1);
+ }
+
+ pIdx = pLevel->pIdx;
+ pLevel->inOp = OP_Noop;
+ if( i<ARRAYSIZE(iDirectEq) && iDirectEq[i]>=0 ){
+ /* Case 1: We can directly reference a single row using an
+ ** equality comparison against the ROWID field. Or
+ ** we reference multiple rows using a "rowid IN (...)"
+ ** construct.
+ */
+ k = iDirectEq[i];
+ assert( k<nExpr );
+ assert( aExpr[k].p!=0 );
+ assert( aExpr[k].idxLeft==iCur || aExpr[k].idxRight==iCur );
+ brk = pLevel->brk = sqliteVdbeMakeLabel(v);
+ if( aExpr[k].idxLeft==iCur ){
+ Expr *pX = aExpr[k].p;
+ if( pX->op!=TK_IN ){
+ sqliteExprCode(pParse, aExpr[k].p->pRight);
+ }else if( pX->pList ){
+ sqliteVdbeAddOp(v, OP_SetFirst, pX->iTable, brk);
+ pLevel->inOp = OP_SetNext;
+ pLevel->inP1 = pX->iTable;
+ pLevel->inP2 = sqliteVdbeCurrentAddr(v);
+ }else{
+ assert( pX->pSelect );
+ sqliteVdbeAddOp(v, OP_Rewind, pX->iTable, brk);
+ sqliteVdbeAddOp(v, OP_KeyAsData, pX->iTable, 1);
+ pLevel->inP2 = sqliteVdbeAddOp(v, OP_FullKey, pX->iTable, 0);
+ pLevel->inOp = OP_Next;
+ pLevel->inP1 = pX->iTable;
+ }
+ }else{
+ sqliteExprCode(pParse, aExpr[k].p->pLeft);
+ }
+ disableTerm(pLevel, &aExpr[k].p);
+ cont = pLevel->cont = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_MustBeInt, 1, brk);
+ haveKey = 0;
+ sqliteVdbeAddOp(v, OP_NotExists, iCur, brk);
+ pLevel->op = OP_Noop;
+ }else if( pIdx!=0 && pLevel->score>0 && pLevel->score%4==0 ){
+ /* Case 2: There is an index and all terms of the WHERE clause that
+ ** refer to the index use the "==" or "IN" operators.
+ */
+ int start;
+ int testOp;
+ int nColumn = (pLevel->score+4)/8;
+ brk = pLevel->brk = sqliteVdbeMakeLabel(v);
+ for(j=0; j<nColumn; j++){
+ for(k=0; k<nExpr; k++){
+ Expr *pX = aExpr[k].p;
+ if( pX==0 ) continue;
+ if( aExpr[k].idxLeft==iCur
+ && (aExpr[k].prereqRight & loopMask)==aExpr[k].prereqRight
+ && pX->pLeft->iColumn==pIdx->aiColumn[j]
+ ){
+ if( pX->op==TK_EQ ){
+ sqliteExprCode(pParse, pX->pRight);
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ if( pX->op==TK_IN && nColumn==1 ){
+ if( pX->pList ){
+ sqliteVdbeAddOp(v, OP_SetFirst, pX->iTable, brk);
+ pLevel->inOp = OP_SetNext;
+ pLevel->inP1 = pX->iTable;
+ pLevel->inP2 = sqliteVdbeCurrentAddr(v);
+ }else{
+ assert( pX->pSelect );
+ sqliteVdbeAddOp(v, OP_Rewind, pX->iTable, brk);
+ sqliteVdbeAddOp(v, OP_KeyAsData, pX->iTable, 1);
+ pLevel->inP2 = sqliteVdbeAddOp(v, OP_FullKey, pX->iTable, 0);
+ pLevel->inOp = OP_Next;
+ pLevel->inP1 = pX->iTable;
+ }
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ }
+ if( aExpr[k].idxRight==iCur
+ && aExpr[k].p->op==TK_EQ
+ && (aExpr[k].prereqLeft & loopMask)==aExpr[k].prereqLeft
+ && aExpr[k].p->pRight->iColumn==pIdx->aiColumn[j]
+ ){
+ sqliteExprCode(pParse, aExpr[k].p->pLeft);
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ }
+ }
+ pLevel->iMem = pParse->nMem++;
+ cont = pLevel->cont = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_NotNull, -nColumn, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_Pop, nColumn, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, brk);
+ sqliteVdbeAddOp(v, OP_MakeKey, nColumn, 0);
+ sqliteAddIdxKeyType(v, pIdx);
+ if( nColumn==pIdx->nColumn || pLevel->bRev ){
+ sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 0);
+ testOp = OP_IdxGT;
+ }else{
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
+ testOp = OP_IdxGE;
+ }
+ if( pLevel->bRev ){
+ /* Scan in reverse order */
+ sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
+ sqliteVdbeAddOp(v, OP_MoveLt, pLevel->iCur, brk);
+ start = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqliteVdbeAddOp(v, OP_IdxLT, pLevel->iCur, brk);
+ pLevel->op = OP_Prev;
+ }else{
+ /* Scan in the forward order */
+ sqliteVdbeAddOp(v, OP_MoveTo, pLevel->iCur, brk);
+ start = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqliteVdbeAddOp(v, testOp, pLevel->iCur, brk);
+ pLevel->op = OP_Next;
+ }
+ sqliteVdbeAddOp(v, OP_RowKey, pLevel->iCur, 0);
+ sqliteVdbeAddOp(v, OP_IdxIsNull, nColumn, cont);
+ sqliteVdbeAddOp(v, OP_IdxRecno, pLevel->iCur, 0);
+ if( i==pTabList->nSrc-1 && pushKey ){
+ haveKey = 1;
+ }else{
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0);
+ haveKey = 0;
+ }
+ pLevel->p1 = pLevel->iCur;
+ pLevel->p2 = start;
+ }else if( i<ARRAYSIZE(iDirectLt) && (iDirectLt[i]>=0 || iDirectGt[i]>=0) ){
+ /* Case 3: We have an inequality comparison against the ROWID field.
+ */
+ int testOp = OP_Noop;
+ int start;
+
+ brk = pLevel->brk = sqliteVdbeMakeLabel(v);
+ cont = pLevel->cont = sqliteVdbeMakeLabel(v);
+ if( iDirectGt[i]>=0 ){
+ k = iDirectGt[i];
+ assert( k<nExpr );
+ assert( aExpr[k].p!=0 );
+ assert( aExpr[k].idxLeft==iCur || aExpr[k].idxRight==iCur );
+ if( aExpr[k].idxLeft==iCur ){
+ sqliteExprCode(pParse, aExpr[k].p->pRight);
+ }else{
+ sqliteExprCode(pParse, aExpr[k].p->pLeft);
+ }
+ sqliteVdbeAddOp(v, OP_ForceInt,
+ aExpr[k].p->op==TK_LT || aExpr[k].p->op==TK_GT, brk);
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, brk);
+ disableTerm(pLevel, &aExpr[k].p);
+ }else{
+ sqliteVdbeAddOp(v, OP_Rewind, iCur, brk);
+ }
+ if( iDirectLt[i]>=0 ){
+ k = iDirectLt[i];
+ assert( k<nExpr );
+ assert( aExpr[k].p!=0 );
+ assert( aExpr[k].idxLeft==iCur || aExpr[k].idxRight==iCur );
+ if( aExpr[k].idxLeft==iCur ){
+ sqliteExprCode(pParse, aExpr[k].p->pRight);
+ }else{
+ sqliteExprCode(pParse, aExpr[k].p->pLeft);
+ }
+ /* sqliteVdbeAddOp(v, OP_MustBeInt, 0, sqliteVdbeCurrentAddr(v)+1); */
+ pLevel->iMem = pParse->nMem++;
+ sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
+ if( aExpr[k].p->op==TK_LT || aExpr[k].p->op==TK_GT ){
+ testOp = OP_Ge;
+ }else{
+ testOp = OP_Gt;
+ }
+ disableTerm(pLevel, &aExpr[k].p);
+ }
+ start = sqliteVdbeCurrentAddr(v);
+ pLevel->op = OP_Next;
+ pLevel->p1 = iCur;
+ pLevel->p2 = start;
+ if( testOp!=OP_Noop ){
+ sqliteVdbeAddOp(v, OP_Recno, iCur, 0);
+ sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqliteVdbeAddOp(v, testOp, 0, brk);
+ }
+ haveKey = 0;
+ }else if( pIdx==0 ){
+ /* Case 4: There is no usable index. We must do a complete
+ ** scan of the entire database table.
+ */
+ int start;
+
+ brk = pLevel->brk = sqliteVdbeMakeLabel(v);
+ cont = pLevel->cont = sqliteVdbeMakeLabel(v);
+ sqliteVdbeAddOp(v, OP_Rewind, iCur, brk);
+ start = sqliteVdbeCurrentAddr(v);
+ pLevel->op = OP_Next;
+ pLevel->p1 = iCur;
+ pLevel->p2 = start;
+ haveKey = 0;
+ }else{
+ /* Case 5: The WHERE clause term that refers to the right-most
+ ** column of the index is an inequality. For example, if
+ ** the index is on (x,y,z) and the WHERE clause is of the
+ ** form "x=5 AND y<10" then this case is used. Only the
+ ** right-most column can be an inequality - the rest must
+ ** use the "==" operator.
+ **
+ ** This case is also used when there are no WHERE clause
+ ** constraints but an index is selected anyway, in order
+ ** to force the output order to conform to an ORDER BY.
+ */
+ int score = pLevel->score;
+ int nEqColumn = score/8;
+ int start;
+ int leFlag, geFlag;
+ int testOp;
+
+ /* Evaluate the equality constraints
+ */
+ for(j=0; j<nEqColumn; j++){
+ for(k=0; k<nExpr; k++){
+ if( aExpr[k].p==0 ) continue;
+ if( aExpr[k].idxLeft==iCur
+ && aExpr[k].p->op==TK_EQ
+ && (aExpr[k].prereqRight & loopMask)==aExpr[k].prereqRight
+ && aExpr[k].p->pLeft->iColumn==pIdx->aiColumn[j]
+ ){
+ sqliteExprCode(pParse, aExpr[k].p->pRight);
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ if( aExpr[k].idxRight==iCur
+ && aExpr[k].p->op==TK_EQ
+ && (aExpr[k].prereqLeft & loopMask)==aExpr[k].prereqLeft
+ && aExpr[k].p->pRight->iColumn==pIdx->aiColumn[j]
+ ){
+ sqliteExprCode(pParse, aExpr[k].p->pLeft);
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ }
+ }
+
+ /* Duplicate the equality term values because they will all be
+ ** used twice: once to make the termination key and once to make the
+ ** start key.
+ */
+ for(j=0; j<nEqColumn; j++){
+ sqliteVdbeAddOp(v, OP_Dup, nEqColumn-1, 0);
+ }
+
+ /* Labels for the beginning and end of the loop
+ */
+ cont = pLevel->cont = sqliteVdbeMakeLabel(v);
+ brk = pLevel->brk = sqliteVdbeMakeLabel(v);
+
+ /* Generate the termination key. This is the key value that
+ ** will end the search. There is no termination key if there
+ ** are no equality terms and no "X<..." term.
+ **
+ ** 2002-Dec-04: On a reverse-order scan, the so-called "termination"
+ ** key computed here really ends up being the start key.
+ */
+ if( (score & 1)!=0 ){
+ for(k=0; k<nExpr; k++){
+ Expr *pExpr = aExpr[k].p;
+ if( pExpr==0 ) continue;
+ if( aExpr[k].idxLeft==iCur
+ && (pExpr->op==TK_LT || pExpr->op==TK_LE)
+ && (aExpr[k].prereqRight & loopMask)==aExpr[k].prereqRight
+ && pExpr->pLeft->iColumn==pIdx->aiColumn[j]
+ ){
+ sqliteExprCode(pParse, pExpr->pRight);
+ leFlag = pExpr->op==TK_LE;
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ if( aExpr[k].idxRight==iCur
+ && (pExpr->op==TK_GT || pExpr->op==TK_GE)
+ && (aExpr[k].prereqLeft & loopMask)==aExpr[k].prereqLeft
+ && pExpr->pRight->iColumn==pIdx->aiColumn[j]
+ ){
+ sqliteExprCode(pParse, pExpr->pLeft);
+ leFlag = pExpr->op==TK_GE;
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ }
+ testOp = OP_IdxGE;
+ }else{
+ testOp = nEqColumn>0 ? OP_IdxGE : OP_Noop;
+ leFlag = 1;
+ }
+ if( testOp!=OP_Noop ){
+ int nCol = nEqColumn + (score & 1);
+ pLevel->iMem = pParse->nMem++;
+ sqliteVdbeAddOp(v, OP_NotNull, -nCol, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_Pop, nCol, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, brk);
+ sqliteVdbeAddOp(v, OP_MakeKey, nCol, 0);
+ sqliteAddIdxKeyType(v, pIdx);
+ if( leFlag ){
+ sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
+ }
+ if( pLevel->bRev ){
+ sqliteVdbeAddOp(v, OP_MoveLt, pLevel->iCur, brk);
+ }else{
+ sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
+ }
+ }else if( pLevel->bRev ){
+ sqliteVdbeAddOp(v, OP_Last, pLevel->iCur, brk);
+ }
+
+ /* Generate the start key. This is the key that defines the lower
+ ** bound on the search. There is no start key if there are no
+ ** equality terms and if there is no "X>..." term. In
+ ** that case, generate a "Rewind" instruction in place of the
+ ** start key search.
+ **
+ ** 2002-Dec-04: In the case of a reverse-order search, the so-called
+ ** "start" key really ends up being used as the termination key.
+ */
+ if( (score & 2)!=0 ){
+ for(k=0; k<nExpr; k++){
+ Expr *pExpr = aExpr[k].p;
+ if( pExpr==0 ) continue;
+ if( aExpr[k].idxLeft==iCur
+ && (pExpr->op==TK_GT || pExpr->op==TK_GE)
+ && (aExpr[k].prereqRight & loopMask)==aExpr[k].prereqRight
+ && pExpr->pLeft->iColumn==pIdx->aiColumn[j]
+ ){
+ sqliteExprCode(pParse, pExpr->pRight);
+ geFlag = pExpr->op==TK_GE;
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ if( aExpr[k].idxRight==iCur
+ && (pExpr->op==TK_LT || pExpr->op==TK_LE)
+ && (aExpr[k].prereqLeft & loopMask)==aExpr[k].prereqLeft
+ && pExpr->pRight->iColumn==pIdx->aiColumn[j]
+ ){
+ sqliteExprCode(pParse, pExpr->pLeft);
+ geFlag = pExpr->op==TK_LE;
+ disableTerm(pLevel, &aExpr[k].p);
+ break;
+ }
+ }
+ }else{
+ geFlag = 1;
+ }
+ if( nEqColumn>0 || (score&2)!=0 ){
+ int nCol = nEqColumn + ((score&2)!=0);
+ sqliteVdbeAddOp(v, OP_NotNull, -nCol, sqliteVdbeCurrentAddr(v)+3);
+ sqliteVdbeAddOp(v, OP_Pop, nCol, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, brk);
+ sqliteVdbeAddOp(v, OP_MakeKey, nCol, 0);
+ sqliteAddIdxKeyType(v, pIdx);
+ if( !geFlag ){
+ sqliteVdbeAddOp(v, OP_IncrKey, 0, 0);
+ }
+ if( pLevel->bRev ){
+ pLevel->iMem = pParse->nMem++;
+ sqliteVdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
+ testOp = OP_IdxLT;
+ }else{
+ sqliteVdbeAddOp(v, OP_MoveTo, pLevel->iCur, brk);
+ }
+ }else if( pLevel->bRev ){
+ testOp = OP_Noop;
+ }else{
+ sqliteVdbeAddOp(v, OP_Rewind, pLevel->iCur, brk);
+ }
+
+ /* Generate the the top of the loop. If there is a termination
+ ** key we have to test for that key and abort at the top of the
+ ** loop.
+ */
+ start = sqliteVdbeCurrentAddr(v);
+ if( testOp!=OP_Noop ){
+ sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqliteVdbeAddOp(v, testOp, pLevel->iCur, brk);
+ }
+ sqliteVdbeAddOp(v, OP_RowKey, pLevel->iCur, 0);
+ sqliteVdbeAddOp(v, OP_IdxIsNull, nEqColumn + (score & 1), cont);
+ sqliteVdbeAddOp(v, OP_IdxRecno, pLevel->iCur, 0);
+ if( i==pTabList->nSrc-1 && pushKey ){
+ haveKey = 1;
+ }else{
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0);
+ haveKey = 0;
+ }
+
+ /* Record the instruction used to terminate the loop.
+ */
+ pLevel->op = pLevel->bRev ? OP_Prev : OP_Next;
+ pLevel->p1 = pLevel->iCur;
+ pLevel->p2 = start;
+ }
+ loopMask |= getMask(&maskSet, iCur);
+
+ /* Insert code to test every subexpression that can be completely
+ ** computed using the current set of tables.
+ */
+ for(j=0; j<nExpr; j++){
+ if( aExpr[j].p==0 ) continue;
+ if( (aExpr[j].prereqAll & loopMask)!=aExpr[j].prereqAll ) continue;
+ if( pLevel->iLeftJoin && !ExprHasProperty(aExpr[j].p,EP_FromJoin) ){
+ continue;
+ }
+ if( haveKey ){
+ haveKey = 0;
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0);
+ }
+ sqliteExprIfFalse(pParse, aExpr[j].p, cont, 1);
+ aExpr[j].p = 0;
+ }
+ brk = cont;
+
+ /* For a LEFT OUTER JOIN, generate code that will record the fact that
+ ** at least one row of the right table has matched the left table.
+ */
+ if( pLevel->iLeftJoin ){
+ pLevel->top = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_Integer, 1, 0);
+ sqliteVdbeAddOp(v, OP_MemStore, pLevel->iLeftJoin, 1);
+ for(j=0; j<nExpr; j++){
+ if( aExpr[j].p==0 ) continue;
+ if( (aExpr[j].prereqAll & loopMask)!=aExpr[j].prereqAll ) continue;
+ if( haveKey ){
+ /* Cannot happen. "haveKey" can only be true if pushKey is true
+ ** an pushKey can only be true for DELETE and UPDATE and there are
+ ** no outer joins with DELETE and UPDATE.
+ */
+ haveKey = 0;
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0);
+ }
+ sqliteExprIfFalse(pParse, aExpr[j].p, cont, 1);
+ aExpr[j].p = 0;
+ }
+ }
+ }
+ pWInfo->iContinue = cont;
+ if( pushKey && !haveKey ){
+ sqliteVdbeAddOp(v, OP_Recno, pTabList->a[0].iCursor, 0);
+ }
+ freeMaskSet(&maskSet);
+ return pWInfo;
+}
+
+/*
+** Generate the end of the WHERE loop. See comments on
+** sqliteWhereBegin() for additional information.
+*/
+void sqliteWhereEnd(WhereInfo *pWInfo){
+ Vdbe *v = pWInfo->pParse->pVdbe;
+ int i;
+ WhereLevel *pLevel;
+ SrcList *pTabList = pWInfo->pTabList;
+
+ for(i=pTabList->nSrc-1; i>=0; i--){
+ pLevel = &pWInfo->a[i];
+ sqliteVdbeResolveLabel(v, pLevel->cont);
+ if( pLevel->op!=OP_Noop ){
+ sqliteVdbeAddOp(v, pLevel->op, pLevel->p1, pLevel->p2);
+ }
+ sqliteVdbeResolveLabel(v, pLevel->brk);
+ if( pLevel->inOp!=OP_Noop ){
+ sqliteVdbeAddOp(v, pLevel->inOp, pLevel->inP1, pLevel->inP2);
+ }
+ if( pLevel->iLeftJoin ){
+ int addr;
+ addr = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iLeftJoin, 0);
+ sqliteVdbeAddOp(v, OP_NotNull, 1, addr+4 + (pLevel->iCur>=0));
+ sqliteVdbeAddOp(v, OP_NullRow, pTabList->a[i].iCursor, 0);
+ if( pLevel->iCur>=0 ){
+ sqliteVdbeAddOp(v, OP_NullRow, pLevel->iCur, 0);
+ }
+ sqliteVdbeAddOp(v, OP_Goto, 0, pLevel->top);
+ }
+ }
+ sqliteVdbeResolveLabel(v, pWInfo->iBreak);
+ for(i=0; i<pTabList->nSrc; i++){
+ Table *pTab = pTabList->a[i].pTab;
+ assert( pTab!=0 );
+ if( pTab->isTransient || pTab->pSelect ) continue;
+ pLevel = &pWInfo->a[i];
+ sqliteVdbeAddOp(v, OP_Close, pTabList->a[i].iCursor, 0);
+ if( pLevel->pIdx!=0 ){
+ sqliteVdbeAddOp(v, OP_Close, pLevel->iCur, 0);
+ }
+ }
+#if 0 /* Never reuse a cursor */
+ if( pWInfo->pParse->nTab==pWInfo->peakNTab ){
+ pWInfo->pParse->nTab = pWInfo->savedNTab;
+ }
+#endif
+ sqliteFree(pWInfo);
+ return;
+}
diff --git a/src/libs/sqlite3/Makefile.am b/src/libs/sqlite3/Makefile.am
new file mode 100644
index 00000000..b672e2c1
--- /dev/null
+++ b/src/libs/sqlite3/Makefile.am
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libsqlite3.la
+
+INCLUDES = $(all_includes)
+
+libsqlite3_la_CFLAGS = -w
+
+libsqlite3_la_LDFLAGS = $(LIBPTHREAD) -no-undefined
+
+libsqlite3_la_SOURCES = sqlite3.c \ No newline at end of file
diff --git a/src/libs/sqlite3/sqlite3.c b/src/libs/sqlite3/sqlite3.c
new file mode 100644
index 00000000..45077b4b
--- /dev/null
+++ b/src/libs/sqlite3/sqlite3.c
@@ -0,0 +1,86994 @@
+/******************************************************************************
+** This file is an amalgamation of many separate C source files from SQLite
+** version 3.5.9. By combining all the individual C code files into this
+** single large file, the entire code can be compiled as a one translation
+** unit. This allows many compilers to do optimizations that would not be
+** possible if the files were compiled separately. Performance improvements
+** of 5% are more are commonly seen when SQLite is compiled as a single
+** translation unit.
+**
+** This file is all you need to compile SQLite. To use SQLite in other
+** programs, you need this file and the "sqlite3.h" header file that defines
+** the programming interface to the SQLite library. (If you do not have
+** the "sqlite3.h" header file at hand, you will find a copy in the first
+** 5638 lines past this header comment.) Additional code files may be
+** needed if you want a wrapper to interface SQLite with your choice of
+** programming language. The code for the "sqlite3" command-line shell
+** is also in a separate file. This file contains only code for the core
+** SQLite library.
+**
+** This amalgamation was generated on 2008-05-14 16:30:52 UTC.
+*/
+#define SQLITE_CORE 1
+#define SQLITE_AMALGAMATION 1
+#ifndef SQLITE_PRIVATE
+# define SQLITE_PRIVATE static
+#endif
+#ifndef SQLITE_API
+# define SQLITE_API
+#endif
+/************** Begin file sqliteInt.h ***************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Internal interface definitions for SQLite.
+**
+** @(#) $Id: sqliteInt.h,v 1.704 2008/05/13 13:27:34 drh Exp $
+*/
+#ifndef _SQLITEINT_H_
+#define _SQLITEINT_H_
+
+/*
+** Include the configuration header output by 'configure' if we're using the
+** autoconf-based build
+*/
+#ifdef _HAVE_SQLITE_CONFIG_H
+#include "config.h"
+#endif
+
+/************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/
+/************** Begin file sqliteLimit.h *************************************/
+/*
+** 2007 May 7
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file defines various limits of what SQLite can process.
+**
+** @(#) $Id: sqliteLimit.h,v 1.8 2008/03/26 15:56:22 drh Exp $
+*/
+
+/*
+** The maximum length of a TEXT or BLOB in bytes. This also
+** limits the size of a row in a table or index.
+**
+** The hard limit is the ability of a 32-bit signed integer
+** to count the size: 2^31-1 or 2147483647.
+*/
+#ifndef SQLITE_MAX_LENGTH
+# define SQLITE_MAX_LENGTH 1000000000
+#endif
+
+/*
+** This is the maximum number of
+**
+** * Columns in a table
+** * Columns in an index
+** * Columns in a view
+** * Terms in the SET clause of an UPDATE statement
+** * Terms in the result set of a SELECT statement
+** * Terms in the GROUP BY or ORDER BY clauses of a SELECT statement.
+** * Terms in the VALUES clause of an INSERT statement
+**
+** The hard upper limit here is 32676. Most database people will
+** tell you that in a well-normalized database, you usually should
+** not have more than a dozen or so columns in any table. And if
+** that is the case, there is no point in having more than a few
+** dozen values in any of the other situations described above.
+*/
+#ifndef SQLITE_MAX_COLUMN
+# define SQLITE_MAX_COLUMN 2000
+#endif
+
+/*
+** The maximum length of a single SQL statement in bytes.
+**
+** It used to be the case that setting this value to zero would
+** turn the limit off. That is no longer true. It is not possible
+** to turn this limit off.
+*/
+#ifndef SQLITE_MAX_SQL_LENGTH
+# define SQLITE_MAX_SQL_LENGTH 1000000000
+#endif
+
+/*
+** The maximum depth of an expression tree. This is limited to
+** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might
+** want to place more severe limits on the complexity of an
+** expression.
+**
+** A value of 0 used to mean that the limit was not enforced.
+** But that is no longer true. The limit is now strictly enforced
+** at all times.
+*/
+#ifndef SQLITE_MAX_EXPR_DEPTH
+# define SQLITE_MAX_EXPR_DEPTH 1000
+#endif
+
+/*
+** The maximum number of terms in a compound SELECT statement.
+** The code generator for compound SELECT statements does one
+** level of recursion for each term. A stack overflow can result
+** if the number of terms is too large. In practice, most SQL
+** never has more than 3 or 4 terms. Use a value of 0 to disable
+** any limit on the number of terms in a compount SELECT.
+*/
+#ifndef SQLITE_MAX_COMPOUND_SELECT
+# define SQLITE_MAX_COMPOUND_SELECT 500
+#endif
+
+/*
+** The maximum number of opcodes in a VDBE program.
+** Not currently enforced.
+*/
+#ifndef SQLITE_MAX_VDBE_OP
+# define SQLITE_MAX_VDBE_OP 25000
+#endif
+
+/*
+** The maximum number of arguments to an SQL function.
+*/
+#ifndef SQLITE_MAX_FUNCTION_ARG
+# define SQLITE_MAX_FUNCTION_ARG 100
+#endif
+
+/*
+** The maximum number of in-memory pages to use for the main database
+** table and for temporary tables. The SQLITE_DEFAULT_CACHE_SIZE
+*/
+#ifndef SQLITE_DEFAULT_CACHE_SIZE
+# define SQLITE_DEFAULT_CACHE_SIZE 2000
+#endif
+#ifndef SQLITE_DEFAULT_TEMP_CACHE_SIZE
+# define SQLITE_DEFAULT_TEMP_CACHE_SIZE 500
+#endif
+
+/*
+** The maximum number of attached databases. This must be between 0
+** and 30. The upper bound on 30 is because a 32-bit integer bitmap
+** is used internally to track attached databases.
+*/
+#ifndef SQLITE_MAX_ATTACHED
+# define SQLITE_MAX_ATTACHED 10
+#endif
+
+
+/*
+** The maximum value of a ?nnn wildcard that the parser will accept.
+*/
+#ifndef SQLITE_MAX_VARIABLE_NUMBER
+# define SQLITE_MAX_VARIABLE_NUMBER 999
+#endif
+
+/* Maximum page size. The upper bound on this value is 32768. This a limit
+** imposed by the necessity of storing the value in a 2-byte unsigned integer
+** and the fact that the page size must be a power of 2.
+*/
+#ifndef SQLITE_MAX_PAGE_SIZE
+# define SQLITE_MAX_PAGE_SIZE 32768
+#endif
+
+
+/*
+** The default size of a database page.
+*/
+#ifndef SQLITE_DEFAULT_PAGE_SIZE
+# define SQLITE_DEFAULT_PAGE_SIZE 1024
+#endif
+#if SQLITE_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE
+# undef SQLITE_DEFAULT_PAGE_SIZE
+# define SQLITE_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE
+#endif
+
+/*
+** Ordinarily, if no value is explicitly provided, SQLite creates databases
+** with page size SQLITE_DEFAULT_PAGE_SIZE. However, based on certain
+** device characteristics (sector-size and atomic write() support),
+** SQLite may choose a larger value. This constant is the maximum value
+** SQLite will choose on its own.
+*/
+#ifndef SQLITE_MAX_DEFAULT_PAGE_SIZE
+# define SQLITE_MAX_DEFAULT_PAGE_SIZE 8192
+#endif
+#if SQLITE_MAX_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE
+# undef SQLITE_MAX_DEFAULT_PAGE_SIZE
+# define SQLITE_MAX_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE
+#endif
+
+
+/*
+** Maximum number of pages in one database file.
+**
+** This is really just the default value for the max_page_count pragma.
+** This value can be lowered (or raised) at run-time using that the
+** max_page_count macro.
+*/
+#ifndef SQLITE_MAX_PAGE_COUNT
+# define SQLITE_MAX_PAGE_COUNT 1073741823
+#endif
+
+/*
+** Maximum length (in bytes) of the pattern in a LIKE or GLOB
+** operator.
+*/
+#ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH
+# define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000
+#endif
+
+/************** End of sqliteLimit.h *****************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+
+/* Needed for various definitions... */
+#define _GNU_SOURCE
+
+/*
+** Include standard header files as necessary
+*/
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+/*
+** A macro used to aid in coverage testing. When doing coverage
+** testing, the condition inside the argument must be evaluated
+** both true and false in order to get full branch coverage.
+** This macro can be inserted to ensure adequate test coverage
+** in places where simple condition/decision coverage is inadequate.
+*/
+#ifdef SQLITE_COVERAGE_TEST
+SQLITE_PRIVATE void sqlite3Coverage(int);
+# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); }
+#else
+# define testcase(X)
+#endif
+
+
+/*
+** The macro unlikely() is a hint that surrounds a boolean
+** expression that is usually false. Macro likely() surrounds
+** a boolean expression that is usually true. GCC is able to
+** use these hints to generate better code, sometimes.
+*/
+#if defined(__GNUC__) && 0
+# define likely(X) __builtin_expect((X),1)
+# define unlikely(X) __builtin_expect((X),0)
+#else
+# define likely(X) !!(X)
+# define unlikely(X) !!(X)
+#endif
+
+
+/*
+** These #defines should enable >2GB file support on Posix if the
+** underlying operating system supports it. If the OS lacks
+** large file support, or if the OS is windows, these should be no-ops.
+**
+** Ticket #2739: The _LARGEFILE_SOURCE macro must appear before any
+** system #includes. Hence, this block of code must be the very first
+** code in all source files.
+**
+** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch
+** on the compiler command line. This is necessary if you are compiling
+** on a recent machine (ex: RedHat 7.2) but you want your code to work
+** on an older machine (ex: RedHat 6.0). If you compile on RedHat 7.2
+** without this option, LFS is enable. But LFS does not exist in the kernel
+** in RedHat 6.0, so the code won't work. Hence, for maximum binary
+** portability you should omit LFS.
+**
+** Similar is true for MacOS. LFS is only supported on MacOS 9 and later.
+*/
+#ifndef SQLITE_DISABLE_LFS
+# define _LARGE_FILE 1
+# ifndef _FILE_OFFSET_BITS
+# define _FILE_OFFSET_BITS 64
+# endif
+# define _LARGEFILE_SOURCE 1
+#endif
+
+
+/*
+** The SQLITE_THREADSAFE macro must be defined as either 0 or 1.
+** Older versions of SQLite used an optional THREADSAFE macro.
+** We support that for legacy
+*/
+#if !defined(SQLITE_THREADSAFE)
+#if defined(THREADSAFE)
+# define SQLITE_THREADSAFE THREADSAFE
+#else
+# define SQLITE_THREADSAFE 1
+#endif
+#endif
+
+/*
+** Exactly one of the following macros must be defined in order to
+** specify which memory allocation subsystem to use.
+**
+** SQLITE_SYSTEM_MALLOC // Use normal system malloc()
+** SQLITE_MEMDEBUG // Debugging version of system malloc()
+** SQLITE_MEMORY_SIZE // internal allocator #1
+** SQLITE_MMAP_HEAP_SIZE // internal mmap() allocator
+** SQLITE_POW2_MEMORY_SIZE // internal power-of-two allocator
+**
+** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as
+** the default.
+*/
+#if defined(SQLITE_SYSTEM_MALLOC)+defined(SQLITE_MEMDEBUG)+\
+ defined(SQLITE_MEMORY_SIZE)+defined(SQLITE_MMAP_HEAP_SIZE)+\
+ defined(SQLITE_POW2_MEMORY_SIZE)>1
+# error "At most one of the following compile-time configuration options\
+ is allows: SQLITE_SYSTEM_MALLOC, SQLITE_MEMDEBUG, SQLITE_MEMORY_SIZE,\
+ SQLITE_MMAP_HEAP_SIZE, SQLITE_POW2_MEMORY_SIZE"
+#endif
+#if defined(SQLITE_SYSTEM_MALLOC)+defined(SQLITE_MEMDEBUG)+\
+ defined(SQLITE_MEMORY_SIZE)+defined(SQLITE_MMAP_HEAP_SIZE)+\
+ defined(SQLITE_POW2_MEMORY_SIZE)==0
+# define SQLITE_SYSTEM_MALLOC 1
+#endif
+
+/*
+** If SQLITE_MALLOC_SOFT_LIMIT is defined, then try to keep the
+** sizes of memory allocations below this value where possible.
+*/
+#if defined(SQLITE_POW2_MEMORY_SIZE) && !defined(SQLITE_MALLOC_SOFT_LIMIT)
+# define SQLITE_MALLOC_SOFT_LIMIT 1024
+#endif
+
+/*
+** We need to define _XOPEN_SOURCE as follows in order to enable
+** recursive mutexes on most unix systems. But Mac OS X is different.
+** The _XOPEN_SOURCE define causes problems for Mac OS X we are told,
+** so it is omitted there. See ticket #2673.
+**
+** Later we learn that _XOPEN_SOURCE is poorly or incorrectly
+** implemented on some systems. So we avoid defining it at all
+** if it is already defined or if it is unneeded because we are
+** not doing a threadsafe build. Ticket #2681.
+**
+** See also ticket #2741.
+*/
+#if !defined(_XOPEN_SOURCE) && !defined(__DARWIN__) && !defined(__APPLE__) && SQLITE_THREADSAFE
+# define _XOPEN_SOURCE 500 /* Needed to enable pthread recursive mutexes */
+#endif
+
+#if defined(SQLITE_TCL) || defined(TCLSH)
+# include <tcl.h>
+#endif
+
+/*
+** Many people are failing to set -DNDEBUG=1 when compiling SQLite.
+** Setting NDEBUG makes the code smaller and run faster. So the following
+** lines are added to automatically set NDEBUG unless the -DSQLITE_DEBUG=1
+** option is set. Thus NDEBUG becomes an opt-in rather than an opt-out
+** feature.
+*/
+#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
+# define NDEBUG 1
+#endif
+
+/************** Include sqlite3.h in the middle of sqliteInt.h ***************/
+/************** Begin file sqlite3.h *****************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the SQLite library
+** presents to client programs. If a C-function, structure, datatype,
+** or constant definition does not appear in this file, then it is
+** not a published API of SQLite, is subject to change without
+** notice, and should not be referenced by programs that use SQLite.
+**
+** Some of the definitions that are in this file are marked as
+** "experimental". Experimental interfaces are normally new
+** features recently added to SQLite. We do not anticipate changes
+** to experimental interfaces but reserve to make minor changes if
+** experience from use "in the wild" suggest such changes are prudent.
+**
+** The official C-language API documentation for SQLite is derived
+** from comments in this file. This file is the authoritative source
+** on how SQLite interfaces are suppose to operate.
+**
+** The name of this file under configuration management is "sqlite.h.in".
+** The makefile makes some minor changes to this file (such as inserting
+** the version number) and changes its name to "sqlite3.h" as
+** part of the build process.
+**
+** @(#) $Id: sqlite.h.in,v 1.312 2008/05/12 12:39:56 drh Exp $
+*/
+#ifndef _SQLITE3_H_
+#define _SQLITE3_H_
+#include <stdarg.h> /* Needed for the definition of va_list */
+
+/*
+** Make sure we can call this stuff from C++.
+*/
+#if 0
+extern "C" {
+#endif
+
+
+/*
+** Add the ability to override 'extern'
+*/
+#ifndef SQLITE_EXTERN
+# define SQLITE_EXTERN extern
+#endif
+
+/*
+** Make sure these symbols where not defined by some previous header
+** file.
+*/
+#ifdef SQLITE_VERSION
+# undef SQLITE_VERSION
+#endif
+#ifdef SQLITE_VERSION_NUMBER
+# undef SQLITE_VERSION_NUMBER
+#endif
+
+/*
+** CAPI3REF: Compile-Time Library Version Numbers {F10010}
+**
+** The SQLITE_VERSION and SQLITE_VERSION_NUMBER #defines in
+** the sqlite3.h file specify the version of SQLite with which
+** that header file is associated.
+**
+** The "version" of SQLite is a string of the form "X.Y.Z".
+** The phrase "alpha" or "beta" might be appended after the Z.
+** The X value is major version number always 3 in SQLite3.
+** The X value only changes when backwards compatibility is
+** broken and we intend to never break
+** backwards compatibility. The Y value is the minor version
+** number and only changes when
+** there are major feature enhancements that are forwards compatible
+** but not backwards compatible. The Z value is release number
+** and is incremented with
+** each release but resets back to 0 when Y is incremented.
+**
+** See also: [sqlite3_libversion()] and [sqlite3_libversion_number()].
+**
+** INVARIANTS:
+**
+** {F10011} The SQLITE_VERSION #define in the sqlite3.h header file
+** evaluates to a string literal that is the SQLite version
+** with which the header file is associated.
+**
+** {F10014} The SQLITE_VERSION_NUMBER #define resolves to an integer
+** with the value (X*1000000 + Y*1000 + Z) where X, Y, and
+** Z are the major version, minor version, and release number.
+*/
+#define SQLITE_VERSION "3.5.9"
+#define SQLITE_VERSION_NUMBER 3005009
+
+/*
+** CAPI3REF: Run-Time Library Version Numbers {F10020}
+** KEYWORDS: sqlite3_version
+**
+** These features provide the same information as the [SQLITE_VERSION]
+** and [SQLITE_VERSION_NUMBER] #defines in the header, but are associated
+** with the library instead of the header file. Cautious programmers might
+** include a check in their application to verify that
+** sqlite3_libversion_number() always returns the value
+** [SQLITE_VERSION_NUMBER].
+**
+** The sqlite3_libversion() function returns the same information as is
+** in the sqlite3_version[] string constant. The function is provided
+** for use in DLLs since DLL users usually do not have direct access to string
+** constants within the DLL.
+**
+** INVARIANTS:
+**
+** {F10021} The [sqlite3_libversion_number()] interface returns an integer
+** equal to [SQLITE_VERSION_NUMBER].
+**
+** {F10022} The [sqlite3_version] string constant contains the text of the
+** [SQLITE_VERSION] string.
+**
+** {F10023} The [sqlite3_libversion()] function returns
+** a pointer to the [sqlite3_version] string constant.
+*/
+SQLITE_API const char sqlite3_version[];
+SQLITE_API const char *sqlite3_libversion(void);
+SQLITE_API int sqlite3_libversion_number(void);
+
+/*
+** CAPI3REF: Test To See If The Library Is Threadsafe {F10100}
+**
+** SQLite can be compiled with or without mutexes. When
+** the SQLITE_THREADSAFE C preprocessor macro is true, mutexes
+** are enabled and SQLite is threadsafe. When that macro is false,
+** the mutexes are omitted. Without the mutexes, it is not safe
+** to use SQLite from more than one thread.
+**
+** There is a measurable performance penalty for enabling mutexes.
+** So if speed is of utmost importance, it makes sense to disable
+** the mutexes. But for maximum safety, mutexes should be enabled.
+** The default behavior is for mutexes to be enabled.
+**
+** This interface can be used by a program to make sure that the
+** version of SQLite that it is linking against was compiled with
+** the desired setting of the SQLITE_THREADSAFE macro.
+**
+** INVARIANTS:
+**
+** {F10101} The [sqlite3_threadsafe()] function returns nonzero if
+** SQLite was compiled with its mutexes enabled or zero
+** if SQLite was compiled with mutexes disabled.
+*/
+SQLITE_API int sqlite3_threadsafe(void);
+
+/*
+** CAPI3REF: Database Connection Handle {F12000}
+** KEYWORDS: {database connection} {database connections}
+**
+** Each open SQLite database is represented by pointer to an instance of the
+** opaque structure named "sqlite3". It is useful to think of an sqlite3
+** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and
+** [sqlite3_open_v2()] interfaces are its constructors
+** and [sqlite3_close()] is its destructor. There are many other interfaces
+** (such as [sqlite3_prepare_v2()], [sqlite3_create_function()], and
+** [sqlite3_busy_timeout()] to name but three) that are methods on this
+** object.
+*/
+typedef struct sqlite3 sqlite3;
+
+
+/*
+** CAPI3REF: 64-Bit Integer Types {F10200}
+** KEYWORDS: sqlite_int64 sqlite_uint64
+**
+** Because there is no cross-platform way to specify 64-bit integer types
+** SQLite includes typedefs for 64-bit signed and unsigned integers.
+**
+** The sqlite3_int64 and sqlite3_uint64 are the preferred type
+** definitions. The sqlite_int64 and sqlite_uint64 types are
+** supported for backwards compatibility only.
+**
+** INVARIANTS:
+**
+** {F10201} The [sqlite_int64] and [sqlite3_int64] types specify a
+** 64-bit signed integer.
+**
+** {F10202} The [sqlite_uint64] and [sqlite3_uint64] types specify
+** a 64-bit unsigned integer.
+*/
+#ifdef SQLITE_INT64_TYPE
+ typedef SQLITE_INT64_TYPE sqlite_int64;
+ typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
+#elif defined(_MSC_VER)
+ typedef __int64 sqlite_int64;
+ typedef unsigned __int64 sqlite_uint64;
+#else
+ typedef long long int sqlite_int64;
+ typedef unsigned long long int sqlite_uint64;
+#endif
+typedef sqlite_int64 sqlite3_int64;
+typedef sqlite_uint64 sqlite3_uint64;
+
+/*
+** If compiling for a processor that lacks floating point support,
+** substitute integer for floating-point
+*/
+#ifdef SQLITE_OMIT_FLOATING_POINT
+# define double sqlite3_int64
+#endif
+
+/*
+** CAPI3REF: Closing A Database Connection {F12010}
+**
+** This routine is the destructor for the [sqlite3] object.
+**
+** Applications should [sqlite3_finalize | finalize] all
+** [prepared statements] and
+** [sqlite3_blob_close | close] all [sqlite3_blob | BLOBs]
+** associated with the [sqlite3] object prior
+** to attempting to close the [sqlite3] object.
+**
+** <todo>What happens to pending transactions? Are they
+** rolled back, or abandoned?</todo>
+**
+** INVARIANTS:
+**
+** {F12011} The [sqlite3_close()] interface destroys an [sqlite3] object
+** allocated by a prior call to [sqlite3_open()],
+** [sqlite3_open16()], or [sqlite3_open_v2()].
+**
+** {F12012} The [sqlite3_close()] function releases all memory used by the
+** connection and closes all open files.
+**
+** {F12013} If the database connection contains
+** [prepared statements] that have not been
+** finalized by [sqlite3_finalize()], then [sqlite3_close()]
+** returns [SQLITE_BUSY] and leaves the connection open.
+**
+** {F12014} Giving sqlite3_close() a NULL pointer is a harmless no-op.
+**
+** LIMITATIONS:
+**
+** {U12015} The parameter to [sqlite3_close()] must be an [sqlite3] object
+** pointer previously obtained from [sqlite3_open()] or the
+** equivalent, or NULL.
+**
+** {U12016} The parameter to [sqlite3_close()] must not have been previously
+** closed.
+*/
+SQLITE_API int sqlite3_close(sqlite3 *);
+
+/*
+** The type for a callback function.
+** This is legacy and deprecated. It is included for historical
+** compatibility and is not documented.
+*/
+typedef int (*sqlite3_callback)(void*,int,char**, char**);
+
+/*
+** CAPI3REF: One-Step Query Execution Interface {F12100}
+**
+** The sqlite3_exec() interface is a convenient way of running
+** one or more SQL statements without a lot of C code. The
+** SQL statements are passed in as the second parameter to
+** sqlite3_exec(). The statements are evaluated one by one
+** until either an error or an interrupt is encountered or
+** until they are all done. The 3rd parameter is an optional
+** callback that is invoked once for each row of any query results
+** produced by the SQL statements. The 5th parameter tells where
+** to write any error messages.
+**
+** The sqlite3_exec() interface is implemented in terms of
+** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()].
+** The sqlite3_exec() routine does nothing that cannot be done
+** by [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()].
+** The sqlite3_exec() is just a convenient wrapper.
+**
+** INVARIANTS:
+**
+** {F12101} The [sqlite3_exec()] interface evaluates zero or more UTF-8
+** encoded, semicolon-separated, SQL statements in the
+** zero-terminated string of its 2nd parameter within the
+** context of the [sqlite3] object given in the 1st parameter.
+**
+** {F12104} The return value of [sqlite3_exec()] is SQLITE_OK if all
+** SQL statements run successfully.
+**
+** {F12105} The return value of [sqlite3_exec()] is an appropriate
+** non-zero error code if any SQL statement fails.
+**
+** {F12107} If one or more of the SQL statements handed to [sqlite3_exec()]
+** return results and the 3rd parameter is not NULL, then
+** the callback function specified by the 3rd parameter is
+** invoked once for each row of result.
+**
+** {F12110} If the callback returns a non-zero value then [sqlite3_exec()]
+** will aborted the SQL statement it is currently evaluating,
+** skip all subsequent SQL statements, and return [SQLITE_ABORT].
+** <todo>What happens to *errmsg here? Does the result code for
+** sqlite3_errcode() get set?</todo>
+**
+** {F12113} The [sqlite3_exec()] routine will pass its 4th parameter through
+** as the 1st parameter of the callback.
+**
+** {F12116} The [sqlite3_exec()] routine sets the 2nd parameter of its
+** callback to be the number of columns in the current row of
+** result.
+**
+** {F12119} The [sqlite3_exec()] routine sets the 3rd parameter of its
+** callback to be an array of pointers to strings holding the
+** values for each column in the current result set row as
+** obtained from [sqlite3_column_text()].
+**
+** {F12122} The [sqlite3_exec()] routine sets the 4th parameter of its
+** callback to be an array of pointers to strings holding the
+** names of result columns as obtained from [sqlite3_column_name()].
+**
+** {F12125} If the 3rd parameter to [sqlite3_exec()] is NULL then
+** [sqlite3_exec()] never invokes a callback. All query
+** results are silently discarded.
+**
+** {F12128} If an error occurs while parsing or evaluating any of the SQL
+** statements handed to [sqlite3_exec()] then [sqlite3_exec()] will
+** return an [error code] other than [SQLITE_OK].
+**
+** {F12131} If an error occurs while parsing or evaluating any of the SQL
+** handed to [sqlite3_exec()] and if the 5th parameter (errmsg)
+** to [sqlite3_exec()] is not NULL, then an error message is
+** allocated using the equivalent of [sqlite3_mprintf()] and
+** *errmsg is made to point to that message.
+**
+** {F12134} The [sqlite3_exec()] routine does not change the value of
+** *errmsg if errmsg is NULL or if there are no errors.
+**
+** {F12137} The [sqlite3_exec()] function sets the error code and message
+** accessible via [sqlite3_errcode()], [sqlite3_errmsg()], and
+** [sqlite3_errmsg16()].
+**
+** LIMITATIONS:
+**
+** {U12141} The first parameter to [sqlite3_exec()] must be an valid and open
+** [database connection].
+**
+** {U12142} The database connection must not be closed while
+** [sqlite3_exec()] is running.
+**
+** {U12143} The calling function is should use [sqlite3_free()] to free
+** the memory that *errmsg is left pointing at once the error
+** message is no longer needed.
+**
+** {U12145} The SQL statement text in the 2nd parameter to [sqlite3_exec()]
+** must remain unchanged while [sqlite3_exec()] is running.
+*/
+SQLITE_API int sqlite3_exec(
+ sqlite3*, /* An open database */
+ const char *sql, /* SQL to be evaluted */
+ int (*callback)(void*,int,char**,char**), /* Callback function */
+ void *, /* 1st argument to callback */
+ char **errmsg /* Error msg written here */
+);
+
+/*
+** CAPI3REF: Result Codes {F10210}
+** KEYWORDS: SQLITE_OK {error code} {error codes}
+**
+** Many SQLite functions return an integer result code from the set shown
+** here in order to indicates success or failure.
+**
+** See also: [SQLITE_IOERR_READ | extended result codes]
+*/
+#define SQLITE_OK 0 /* Successful result */
+/* beginning-of-error-codes */
+#define SQLITE_ERROR 1 /* SQL error or missing database */
+#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */
+#define SQLITE_PERM 3 /* Access permission denied */
+#define SQLITE_ABORT 4 /* Callback routine requested an abort */
+#define SQLITE_BUSY 5 /* The database file is locked */
+#define SQLITE_LOCKED 6 /* A table in the database is locked */
+#define SQLITE_NOMEM 7 /* A malloc() failed */
+#define SQLITE_READONLY 8 /* Attempt to write a readonly database */
+#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/
+#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */
+#define SQLITE_CORRUPT 11 /* The database disk image is malformed */
+#define SQLITE_NOTFOUND 12 /* NOT USED. Table or record not found */
+#define SQLITE_FULL 13 /* Insertion failed because database is full */
+#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
+#define SQLITE_PROTOCOL 15 /* NOT USED. Database lock protocol error */
+#define SQLITE_EMPTY 16 /* Database is empty */
+#define SQLITE_SCHEMA 17 /* The database schema changed */
+#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
+#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
+#define SQLITE_MISMATCH 20 /* Data type mismatch */
+#define SQLITE_MISUSE 21 /* Library used incorrectly */
+#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */
+#define SQLITE_AUTH 23 /* Authorization denied */
+#define SQLITE_FORMAT 24 /* Auxiliary database format error */
+#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */
+#define SQLITE_NOTADB 26 /* File opened that is not a database file */
+#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
+#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
+/* end-of-error-codes */
+
+/*
+** CAPI3REF: Extended Result Codes {F10220}
+** KEYWORDS: {extended error code} {extended error codes}
+** KEYWORDS: {extended result codes}
+**
+** In its default configuration, SQLite API routines return one of 26 integer
+** [SQLITE_OK | result codes]. However, experience has shown that
+** many of these result codes are too course-grained. They do not provide as
+** much information about problems as programmers might like. In an effort to
+** address this, newer versions of SQLite (version 3.3.8 and later) include
+** support for additional result codes that provide more detailed information
+** about errors. The extended result codes are enabled or disabled
+** for each database connection using the [sqlite3_extended_result_codes()]
+** API.
+**
+** Some of the available extended result codes are listed here.
+** One may expect the number of extended result codes will be expand
+** over time. Software that uses extended result codes should expect
+** to see new result codes in future releases of SQLite.
+**
+** The SQLITE_OK result code will never be extended. It will always
+** be exactly zero.
+**
+** INVARIANTS:
+**
+** {F10223} The symbolic name for an extended result code always contains
+** a related primary result code as a prefix.
+**
+** {F10224} Primary result code names contain a single "_" character.
+**
+** {F10225} Extended result code names contain two or more "_" characters.
+**
+** {F10226} The numeric value of an extended result code contains the
+** numeric value of its corresponding primary result code in
+** its least significant 8 bits.
+*/
+#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
+#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
+#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
+#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8))
+#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8))
+#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8))
+#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8))
+#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8))
+#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8))
+#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8))
+#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8))
+#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8))
+
+/*
+** CAPI3REF: Flags For File Open Operations {F10230}
+**
+** These bit values are intended for use in the
+** 3rd parameter to the [sqlite3_open_v2()] interface and
+** in the 4th parameter to the xOpen method of the
+** [sqlite3_vfs] object.
+*/
+#define SQLITE_OPEN_READONLY 0x00000001
+#define SQLITE_OPEN_READWRITE 0x00000002
+#define SQLITE_OPEN_CREATE 0x00000004
+#define SQLITE_OPEN_DELETEONCLOSE 0x00000008
+#define SQLITE_OPEN_EXCLUSIVE 0x00000010
+#define SQLITE_OPEN_MAIN_DB 0x00000100
+#define SQLITE_OPEN_TEMP_DB 0x00000200
+#define SQLITE_OPEN_TRANSIENT_DB 0x00000400
+#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800
+#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000
+#define SQLITE_OPEN_SUBJOURNAL 0x00002000
+#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000
+
+/*
+** CAPI3REF: Device Characteristics {F10240}
+**
+** The xDeviceCapabilities method of the [sqlite3_io_methods]
+** object returns an integer which is a vector of the these
+** bit values expressing I/O characteristics of the mass storage
+** device that holds the file that the [sqlite3_io_methods]
+** refers to.
+**
+** The SQLITE_IOCAP_ATOMIC property means that all writes of
+** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
+** mean that writes of blocks that are nnn bytes in size and
+** are aligned to an address which is an integer multiple of
+** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means
+** that when data is appended to a file, the data is appended
+** first then the size of the file is extended, never the other
+** way around. The SQLITE_IOCAP_SEQUENTIAL property means that
+** information is written to disk in the same order as calls
+** to xWrite().
+*/
+#define SQLITE_IOCAP_ATOMIC 0x00000001
+#define SQLITE_IOCAP_ATOMIC512 0x00000002
+#define SQLITE_IOCAP_ATOMIC1K 0x00000004
+#define SQLITE_IOCAP_ATOMIC2K 0x00000008
+#define SQLITE_IOCAP_ATOMIC4K 0x00000010
+#define SQLITE_IOCAP_ATOMIC8K 0x00000020
+#define SQLITE_IOCAP_ATOMIC16K 0x00000040
+#define SQLITE_IOCAP_ATOMIC32K 0x00000080
+#define SQLITE_IOCAP_ATOMIC64K 0x00000100
+#define SQLITE_IOCAP_SAFE_APPEND 0x00000200
+#define SQLITE_IOCAP_SEQUENTIAL 0x00000400
+
+/*
+** CAPI3REF: File Locking Levels {F10250}
+**
+** SQLite uses one of these integer values as the second
+** argument to calls it makes to the xLock() and xUnlock() methods
+** of an [sqlite3_io_methods] object.
+*/
+#define SQLITE_LOCK_NONE 0
+#define SQLITE_LOCK_SHARED 1
+#define SQLITE_LOCK_RESERVED 2
+#define SQLITE_LOCK_PENDING 3
+#define SQLITE_LOCK_EXCLUSIVE 4
+
+/*
+** CAPI3REF: Synchronization Type Flags {F10260}
+**
+** When SQLite invokes the xSync() method of an
+** [sqlite3_io_methods] object it uses a combination of
+** these integer values as the second argument.
+**
+** When the SQLITE_SYNC_DATAONLY flag is used, it means that the
+** sync operation only needs to flush data to mass storage. Inode
+** information need not be flushed. The SQLITE_SYNC_NORMAL flag means
+** to use normal fsync() semantics. The SQLITE_SYNC_FULL flag means
+** to use Mac OS-X style fullsync instead of fsync().
+*/
+#define SQLITE_SYNC_NORMAL 0x00002
+#define SQLITE_SYNC_FULL 0x00003
+#define SQLITE_SYNC_DATAONLY 0x00010
+
+
+/*
+** CAPI3REF: OS Interface Open File Handle {F11110}
+**
+** An [sqlite3_file] object represents an open file in the OS
+** interface layer. Individual OS interface implementations will
+** want to subclass this object by appending additional fields
+** for their own use. The pMethods entry is a pointer to an
+** [sqlite3_io_methods] object that defines methods for performing
+** I/O operations on the open file.
+*/
+typedef struct sqlite3_file sqlite3_file;
+struct sqlite3_file {
+ const struct sqlite3_io_methods *pMethods; /* Methods for an open file */
+};
+
+/*
+** CAPI3REF: OS Interface File Virtual Methods Object {F11120}
+**
+** Every file opened by the [sqlite3_vfs] xOpen method contains a pointer to
+** an instance of this object. This object defines the
+** methods used to perform various operations against the open file.
+**
+** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or
+** [SQLITE_SYNC_FULL]. The first choice is the normal fsync().
+* The second choice is an
+** OS-X style fullsync. The SQLITE_SYNC_DATA flag may be ORed in to
+** indicate that only the data of the file and not its inode needs to be
+** synced.
+**
+** The integer values to xLock() and xUnlock() are one of
+** <ul>
+** <li> [SQLITE_LOCK_NONE],
+** <li> [SQLITE_LOCK_SHARED],
+** <li> [SQLITE_LOCK_RESERVED],
+** <li> [SQLITE_LOCK_PENDING], or
+** <li> [SQLITE_LOCK_EXCLUSIVE].
+** </ul>
+** xLock() increases the lock. xUnlock() decreases the lock.
+** The xCheckReservedLock() method looks
+** to see if any database connection, either in this
+** process or in some other process, is holding an RESERVED,
+** PENDING, or EXCLUSIVE lock on the file. It returns true
+** if such a lock exists and false if not.
+**
+** The xFileControl() method is a generic interface that allows custom
+** VFS implementations to directly control an open file using the
+** [sqlite3_file_control()] interface. The second "op" argument
+** is an integer opcode. The third
+** argument is a generic pointer which is intended to be a pointer
+** to a structure that may contain arguments or space in which to
+** write return values. Potential uses for xFileControl() might be
+** functions to enable blocking locks with timeouts, to change the
+** locking strategy (for example to use dot-file locks), to inquire
+** about the status of a lock, or to break stale locks. The SQLite
+** core reserves opcodes less than 100 for its own use.
+** A [SQLITE_FCNTL_LOCKSTATE | list of opcodes] less than 100 is available.
+** Applications that define a custom xFileControl method should use opcodes
+** greater than 100 to avoid conflicts.
+**
+** The xSectorSize() method returns the sector size of the
+** device that underlies the file. The sector size is the
+** minimum write that can be performed without disturbing
+** other bytes in the file. The xDeviceCharacteristics()
+** method returns a bit vector describing behaviors of the
+** underlying device:
+**
+** <ul>
+** <li> [SQLITE_IOCAP_ATOMIC]
+** <li> [SQLITE_IOCAP_ATOMIC512]
+** <li> [SQLITE_IOCAP_ATOMIC1K]
+** <li> [SQLITE_IOCAP_ATOMIC2K]
+** <li> [SQLITE_IOCAP_ATOMIC4K]
+** <li> [SQLITE_IOCAP_ATOMIC8K]
+** <li> [SQLITE_IOCAP_ATOMIC16K]
+** <li> [SQLITE_IOCAP_ATOMIC32K]
+** <li> [SQLITE_IOCAP_ATOMIC64K]
+** <li> [SQLITE_IOCAP_SAFE_APPEND]
+** <li> [SQLITE_IOCAP_SEQUENTIAL]
+** </ul>
+**
+** The SQLITE_IOCAP_ATOMIC property means that all writes of
+** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
+** mean that writes of blocks that are nnn bytes in size and
+** are aligned to an address which is an integer multiple of
+** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means
+** that when data is appended to a file, the data is appended
+** first then the size of the file is extended, never the other
+** way around. The SQLITE_IOCAP_SEQUENTIAL property means that
+** information is written to disk in the same order as calls
+** to xWrite().
+*/
+typedef struct sqlite3_io_methods sqlite3_io_methods;
+struct sqlite3_io_methods {
+ int iVersion;
+ int (*xClose)(sqlite3_file*);
+ int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+ int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
+ int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
+ int (*xSync)(sqlite3_file*, int flags);
+ int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
+ int (*xLock)(sqlite3_file*, int);
+ int (*xUnlock)(sqlite3_file*, int);
+ int (*xCheckReservedLock)(sqlite3_file*);
+ int (*xFileControl)(sqlite3_file*, int op, void *pArg);
+ int (*xSectorSize)(sqlite3_file*);
+ int (*xDeviceCharacteristics)(sqlite3_file*);
+ /* Additional methods may be added in future releases */
+};
+
+/*
+** CAPI3REF: Standard File Control Opcodes {F11310}
+**
+** These integer constants are opcodes for the xFileControl method
+** of the [sqlite3_io_methods] object and to the [sqlite3_file_control()]
+** interface.
+**
+** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This
+** opcode causes the xFileControl method to write the current state of
+** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED],
+** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE])
+** into an integer that the pArg argument points to. This capability
+** is used during testing and only needs to be supported when SQLITE_TEST
+** is defined.
+*/
+#define SQLITE_FCNTL_LOCKSTATE 1
+
+/*
+** CAPI3REF: Mutex Handle {F17110}
+**
+** The mutex module within SQLite defines [sqlite3_mutex] to be an
+** abstract type for a mutex object. The SQLite core never looks
+** at the internal representation of an [sqlite3_mutex]. It only
+** deals with pointers to the [sqlite3_mutex] object.
+**
+** Mutexes are created using [sqlite3_mutex_alloc()].
+*/
+typedef struct sqlite3_mutex sqlite3_mutex;
+
+/*
+** CAPI3REF: OS Interface Object {F11140}
+**
+** An instance of this object defines the interface between the
+** SQLite core and the underlying operating system. The "vfs"
+** in the name of the object stands for "virtual file system".
+**
+** The iVersion field is initially 1 but may be larger for future
+** versions of SQLite. Additional fields may be appended to this
+** object when the iVersion value is increased.
+**
+** The szOsFile field is the size of the subclassed [sqlite3_file]
+** structure used by this VFS. mxPathname is the maximum length of
+** a pathname in this VFS.
+**
+** Registered sqlite3_vfs objects are kept on a linked list formed by
+** the pNext pointer. The [sqlite3_vfs_register()]
+** and [sqlite3_vfs_unregister()] interfaces manage this list
+** in a thread-safe way. The [sqlite3_vfs_find()] interface
+** searches the list.
+**
+** The pNext field is the only field in the sqlite3_vfs
+** structure that SQLite will ever modify. SQLite will only access
+** or modify this field while holding a particular static mutex.
+** The application should never modify anything within the sqlite3_vfs
+** object once the object has been registered.
+**
+** The zName field holds the name of the VFS module. The name must
+** be unique across all VFS modules.
+**
+** {F11141} SQLite will guarantee that the zFilename string passed to
+** xOpen() is a full pathname as generated by xFullPathname() and
+** that the string will be valid and unchanged until xClose() is
+** called. {END} So the [sqlite3_file] can store a pointer to the
+** filename if it needs to remember the filename for some reason.
+**
+** {F11142} The flags argument to xOpen() includes all bits set in
+** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()]
+** or [sqlite3_open16()] is used, then flags includes at least
+** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. {END}
+** If xOpen() opens a file read-only then it sets *pOutFlags to
+** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be
+** set.
+**
+** {F11143} SQLite will also add one of the following flags to the xOpen()
+** call, depending on the object being opened:
+**
+** <ul>
+** <li> [SQLITE_OPEN_MAIN_DB]
+** <li> [SQLITE_OPEN_MAIN_JOURNAL]
+** <li> [SQLITE_OPEN_TEMP_DB]
+** <li> [SQLITE_OPEN_TEMP_JOURNAL]
+** <li> [SQLITE_OPEN_TRANSIENT_DB]
+** <li> [SQLITE_OPEN_SUBJOURNAL]
+** <li> [SQLITE_OPEN_MASTER_JOURNAL]
+** </ul> {END}
+**
+** The file I/O implementation can use the object type flags to
+** changes the way it deals with files. For example, an application
+** that does not care about crash recovery or rollback might make
+** the open of a journal file a no-op. Writes to this journal would
+** also be no-ops, and any attempt to read the journal would return
+** SQLITE_IOERR. Or the implementation might recognize that a database
+** file will be doing page-aligned sector reads and writes in a random
+** order and set up its I/O subsystem accordingly.
+**
+** SQLite might also add one of the following flags to the xOpen
+** method:
+**
+** <ul>
+** <li> [SQLITE_OPEN_DELETEONCLOSE]
+** <li> [SQLITE_OPEN_EXCLUSIVE]
+** </ul>
+**
+** {F11145} The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be
+** deleted when it is closed. {F11146} The [SQLITE_OPEN_DELETEONCLOSE]
+** will be set for TEMP databases, journals and for subjournals.
+** {F11147} The [SQLITE_OPEN_EXCLUSIVE] flag means the file should be opened
+** for exclusive access. This flag is set for all files except
+** for the main database file. {END}
+**
+** {F11148} At least szOsFile bytes of memory are allocated by SQLite
+** to hold the [sqlite3_file] structure passed as the third
+** argument to xOpen. {END} The xOpen method does not have to
+** allocate the structure; it should just fill it in.
+**
+** {F11149} The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS]
+** to test for the existance of a file,
+** or [SQLITE_ACCESS_READWRITE] to test to see
+** if a file is readable and writable, or [SQLITE_ACCESS_READ]
+** to test to see if a file is at least readable. {END} The file can be a
+** directory.
+**
+** {F11150} SQLite will always allocate at least mxPathname+1 bytes for
+** the output buffers for xGetTempname and xFullPathname. {F11151} The exact
+** size of the output buffer is also passed as a parameter to both
+** methods. {END} If the output buffer is not large enough, SQLITE_CANTOPEN
+** should be returned. As this is handled as a fatal error by SQLite,
+** vfs implementations should endeavor to prevent this by setting
+** mxPathname to a sufficiently large value.
+**
+** The xRandomness(), xSleep(), and xCurrentTime() interfaces
+** are not strictly a part of the filesystem, but they are
+** included in the VFS structure for completeness.
+** The xRandomness() function attempts to return nBytes bytes
+** of good-quality randomness into zOut. The return value is
+** the actual number of bytes of randomness obtained. The
+** xSleep() method causes the calling thread to sleep for at
+** least the number of microseconds given. The xCurrentTime()
+** method returns a Julian Day Number for the current date and
+** time.
+*/
+typedef struct sqlite3_vfs sqlite3_vfs;
+struct sqlite3_vfs {
+ int iVersion; /* Structure version number */
+ int szOsFile; /* Size of subclassed sqlite3_file */
+ int mxPathname; /* Maximum file pathname length */
+ sqlite3_vfs *pNext; /* Next registered VFS */
+ const char *zName; /* Name of this virtual file system */
+ void *pAppData; /* Pointer to application-specific data */
+ int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
+ int flags, int *pOutFlags);
+ int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
+ int (*xAccess)(sqlite3_vfs*, const char *zName, int flags);
+ int (*xGetTempname)(sqlite3_vfs*, int nOut, char *zOut);
+ int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
+ void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
+ void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
+ void *(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol);
+ void (*xDlClose)(sqlite3_vfs*, void*);
+ int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
+ int (*xSleep)(sqlite3_vfs*, int microseconds);
+ int (*xCurrentTime)(sqlite3_vfs*, double*);
+ /* New fields may be appended in figure versions. The iVersion
+ ** value will increment whenever this happens. */
+};
+
+/*
+** CAPI3REF: Flags for the xAccess VFS method {F11190}
+**
+** {F11191} These integer constants can be used as the third parameter to
+** the xAccess method of an [sqlite3_vfs] object. {END} They determine
+** what kind of permissions the xAccess method is
+** looking for. {F11192} With SQLITE_ACCESS_EXISTS, the xAccess method
+** simply checks to see if the file exists. {F11193} With
+** SQLITE_ACCESS_READWRITE, the xAccess method checks to see
+** if the file is both readable and writable. {F11194} With
+** SQLITE_ACCESS_READ the xAccess method
+** checks to see if the file is readable.
+*/
+#define SQLITE_ACCESS_EXISTS 0
+#define SQLITE_ACCESS_READWRITE 1
+#define SQLITE_ACCESS_READ 2
+
+/*
+** CAPI3REF: Enable Or Disable Extended Result Codes {F12200}
+**
+** The sqlite3_extended_result_codes() routine enables or disables the
+** [SQLITE_IOERR_READ | extended result codes] feature of SQLite.
+** The extended result codes are disabled by default for historical
+** compatibility.
+**
+** INVARIANTS:
+**
+** {F12201} Each new [database connection] has the
+** [extended result codes] feature
+** disabled by default.
+**
+** {F12202} The [sqlite3_extended_result_codes(D,F)] interface will enable
+** [extended result codes] for the
+** [database connection] D if the F parameter
+** is true, or disable them if F is false.
+*/
+SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff);
+
+/*
+** CAPI3REF: Last Insert Rowid {F12220}
+**
+** Each entry in an SQLite table has a unique 64-bit signed
+** integer key called the "rowid". The rowid is always available
+** as an undeclared column named ROWID, OID, or _ROWID_ as long as those
+** names are not also used by explicitly declared columns. If
+** the table has a column of type INTEGER PRIMARY KEY then that column
+** is another alias for the rowid.
+**
+** This routine returns the rowid of the most recent
+** successful INSERT into the database from the database connection
+** shown in the first argument. If no successful inserts
+** have ever occurred on this database connection, zero is returned.
+**
+** If an INSERT occurs within a trigger, then the rowid of the
+** inserted row is returned by this routine as long as the trigger
+** is running. But once the trigger terminates, the value returned
+** by this routine reverts to the last value inserted before the
+** trigger fired.
+**
+** An INSERT that fails due to a constraint violation is not a
+** successful insert and does not change the value returned by this
+** routine. Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK,
+** and INSERT OR ABORT make no changes to the return value of this
+** routine when their insertion fails. When INSERT OR REPLACE
+** encounters a constraint violation, it does not fail. The
+** INSERT continues to completion after deleting rows that caused
+** the constraint problem so INSERT OR REPLACE will always change
+** the return value of this interface.
+**
+** For the purposes of this routine, an insert is considered to
+** be successful even if it is subsequently rolled back.
+**
+** INVARIANTS:
+**
+** {F12221} The [sqlite3_last_insert_rowid()] function returns the
+** rowid of the most recent successful insert done
+** on the same database connection and within the same
+** trigger context, or zero if there have
+** been no qualifying inserts on that connection.
+**
+** {F12223} The [sqlite3_last_insert_rowid()] function returns
+** same value when called from the same trigger context
+** immediately before and after a ROLLBACK.
+**
+** LIMITATIONS:
+**
+** {U12232} If a separate thread does a new insert on the same
+** database connection while the [sqlite3_last_insert_rowid()]
+** function is running and thus changes the last insert rowid,
+** then the value returned by [sqlite3_last_insert_rowid()] is
+** unpredictable and might not equal either the old or the new
+** last insert rowid.
+*/
+SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
+
+/*
+** CAPI3REF: Count The Number Of Rows Modified {F12240}
+**
+** This function returns the number of database rows that were changed
+** or inserted or deleted by the most recently completed SQL statement
+** on the connection specified by the first parameter. Only
+** changes that are directly specified by the INSERT, UPDATE, or
+** DELETE statement are counted. Auxiliary changes caused by
+** triggers are not counted. Use the [sqlite3_total_changes()] function
+** to find the total number of changes including changes caused by triggers.
+**
+** A "row change" is a change to a single row of a single table
+** caused by an INSERT, DELETE, or UPDATE statement. Rows that
+** are changed as side effects of REPLACE constraint resolution,
+** rollback, ABORT processing, DROP TABLE, or by any other
+** mechanisms do not count as direct row changes.
+**
+** A "trigger context" is a scope of execution that begins and
+** ends with the script of a trigger. Most SQL statements are
+** evaluated outside of any trigger. This is the "top level"
+** trigger context. If a trigger fires from the top level, a
+** new trigger context is entered for the duration of that one
+** trigger. Subtriggers create subcontexts for their duration.
+**
+** Calling [sqlite3_exec()] or [sqlite3_step()] recursively does
+** not create a new trigger context.
+**
+** This function returns the number of direct row changes in the
+** most recent INSERT, UPDATE, or DELETE statement within the same
+** trigger context.
+**
+** So when called from the top level, this function returns the
+** number of changes in the most recent INSERT, UPDATE, or DELETE
+** that also occurred at the top level.
+** Within the body of a trigger, the sqlite3_changes() interface
+** can be called to find the number of
+** changes in the most recently completed INSERT, UPDATE, or DELETE
+** statement within the body of the same trigger.
+** However, the number returned does not include in changes
+** caused by subtriggers since they have their own context.
+**
+** SQLite implements the command "DELETE FROM table" without
+** a WHERE clause by dropping and recreating the table. (This is much
+** faster than going through and deleting individual elements from the
+** table.) Because of this optimization, the deletions in
+** "DELETE FROM table" are not row changes and will not be counted
+** by the sqlite3_changes() or [sqlite3_total_changes()] functions.
+** To get an accurate count of the number of rows deleted, use
+** "DELETE FROM table WHERE 1" instead.
+**
+** INVARIANTS:
+**
+** {F12241} The [sqlite3_changes()] function returns the number of
+** row changes caused by the most recent INSERT, UPDATE,
+** or DELETE statement on the same database connection and
+** within the same trigger context, or zero if there have
+** not been any qualifying row changes.
+**
+** LIMITATIONS:
+**
+** {U12252} If a separate thread makes changes on the same database connection
+** while [sqlite3_changes()] is running then the value returned
+** is unpredictable and unmeaningful.
+*/
+SQLITE_API int sqlite3_changes(sqlite3*);
+
+/*
+** CAPI3REF: Total Number Of Rows Modified {F12260}
+***
+** This function returns the number of row changes caused
+** by INSERT, UPDATE or DELETE statements since the database handle
+** was opened. The count includes all changes from all trigger
+** contexts. But the count does not include changes used to
+** implement REPLACE constraints, do rollbacks or ABORT processing,
+** or DROP table processing.
+** The changes
+** are counted as soon as the statement that makes them is completed
+** (when the statement handle is passed to [sqlite3_reset()] or
+** [sqlite3_finalize()]).
+**
+** SQLite implements the command "DELETE FROM table" without
+** a WHERE clause by dropping and recreating the table. (This is much
+** faster than going
+** through and deleting individual elements from the table.) Because of
+** this optimization, the change count for "DELETE FROM table" will be
+** zero regardless of the number of elements that were originally in the
+** table. To get an accurate count of the number of rows deleted, use
+** "DELETE FROM table WHERE 1" instead.
+**
+** See also the [sqlite3_changes()] interface.
+**
+** INVARIANTS:
+**
+** {F12261} The [sqlite3_total_changes()] returns the total number
+** of row changes caused by INSERT, UPDATE, and/or DELETE
+** statements on the same [database connection], in any
+** trigger context, since the database connection was
+** created.
+**
+** LIMITATIONS:
+**
+** {U12264} If a separate thread makes changes on the same database connection
+** while [sqlite3_total_changes()] is running then the value
+** returned is unpredictable and unmeaningful.
+*/
+SQLITE_API int sqlite3_total_changes(sqlite3*);
+
+/*
+** CAPI3REF: Interrupt A Long-Running Query {F12270}
+**
+** This function causes any pending database operation to abort and
+** return at its earliest opportunity. This routine is typically
+** called in response to a user action such as pressing "Cancel"
+** or Ctrl-C where the user wants a long query operation to halt
+** immediately.
+**
+** It is safe to call this routine from a thread different from the
+** thread that is currently running the database operation. But it
+** is not safe to call this routine with a database connection that
+** is closed or might close before sqlite3_interrupt() returns.
+**
+** If an SQL is very nearly finished at the time when sqlite3_interrupt()
+** is called, then it might not have an opportunity to be interrupted.
+** It might continue to completion.
+** An SQL operation that is interrupted will return
+** [SQLITE_INTERRUPT]. If the interrupted SQL operation is an
+** INSERT, UPDATE, or DELETE that is inside an explicit transaction,
+** then the entire transaction will be rolled back automatically.
+** A call to sqlite3_interrupt() has no effect on SQL statements
+** that are started after sqlite3_interrupt() returns.
+**
+** INVARIANTS:
+**
+** {F12271} The [sqlite3_interrupt()] interface will force all running
+** SQL statements associated with the same database connection
+** to halt after processing at most one additional row of
+** data.
+**
+** {F12272} Any SQL statement that is interrupted by [sqlite3_interrupt()]
+** will return [SQLITE_INTERRUPT].
+**
+** LIMITATIONS:
+**
+** {U12279} If the database connection closes while [sqlite3_interrupt()]
+** is running then bad things will likely happen.
+*/
+SQLITE_API void sqlite3_interrupt(sqlite3*);
+
+/*
+** CAPI3REF: Determine If An SQL Statement Is Complete {F10510}
+**
+** These routines are useful for command-line input to determine if the
+** currently entered text seems to form complete a SQL statement or
+** if additional input is needed before sending the text into
+** SQLite for parsing. These routines return true if the input string
+** appears to be a complete SQL statement. A statement is judged to be
+** complete if it ends with a semicolon token and is not a fragment of a
+** CREATE TRIGGER statement. Semicolons that are embedded within
+** string literals or quoted identifier names or comments are not
+** independent tokens (they are part of the token in which they are
+** embedded) and thus do not count as a statement terminator.
+**
+** These routines do not parse the SQL and
+** so will not detect syntactically incorrect SQL.
+**
+** INVARIANTS:
+**
+** {F10511} The sqlite3_complete() and sqlite3_complete16() functions
+** return true (non-zero) if and only if the last
+** non-whitespace token in their input is a semicolon that
+** is not in between the BEGIN and END of a CREATE TRIGGER
+** statement.
+**
+** LIMITATIONS:
+**
+** {U10512} The input to sqlite3_complete() must be a zero-terminated
+** UTF-8 string.
+**
+** {U10513} The input to sqlite3_complete16() must be a zero-terminated
+** UTF-16 string in native byte order.
+*/
+SQLITE_API int sqlite3_complete(const char *sql);
+SQLITE_API int sqlite3_complete16(const void *sql);
+
+/*
+** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors {F12310}
+**
+** This routine identifies a callback function that might be
+** invoked whenever an attempt is made to open a database table
+** that another thread or process has locked.
+** If the busy callback is NULL, then [SQLITE_BUSY]
+** or [SQLITE_IOERR_BLOCKED]
+** is returned immediately upon encountering the lock.
+** If the busy callback is not NULL, then the
+** callback will be invoked with two arguments. The
+** first argument to the handler is a copy of the void* pointer which
+** is the third argument to this routine. The second argument to
+** the handler is the number of times that the busy handler has
+** been invoked for this locking event. If the
+** busy callback returns 0, then no additional attempts are made to
+** access the database and [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] is returned.
+** If the callback returns non-zero, then another attempt
+** is made to open the database for reading and the cycle repeats.
+**
+** The presence of a busy handler does not guarantee that
+** it will be invoked when there is lock contention.
+** If SQLite determines that invoking the busy handler could result in
+** a deadlock, it will go ahead and return [SQLITE_BUSY] or
+** [SQLITE_IOERR_BLOCKED] instead of invoking the
+** busy handler.
+** Consider a scenario where one process is holding a read lock that
+** it is trying to promote to a reserved lock and
+** a second process is holding a reserved lock that it is trying
+** to promote to an exclusive lock. The first process cannot proceed
+** because it is blocked by the second and the second process cannot
+** proceed because it is blocked by the first. If both processes
+** invoke the busy handlers, neither will make any progress. Therefore,
+** SQLite returns [SQLITE_BUSY] for the first process, hoping that this
+** will induce the first process to release its read lock and allow
+** the second process to proceed.
+**
+** The default busy callback is NULL.
+**
+** The [SQLITE_BUSY] error is converted to [SQLITE_IOERR_BLOCKED]
+** when SQLite is in the middle of a large transaction where all the
+** changes will not fit into the in-memory cache. SQLite will
+** already hold a RESERVED lock on the database file, but it needs
+** to promote this lock to EXCLUSIVE so that it can spill cache
+** pages into the database file without harm to concurrent
+** readers. If it is unable to promote the lock, then the in-memory
+** cache will be left in an inconsistent state and so the error
+** code is promoted from the relatively benign [SQLITE_BUSY] to
+** the more severe [SQLITE_IOERR_BLOCKED]. This error code promotion
+** forces an automatic rollback of the changes. See the
+** <a href="http://www.sqlite.org/cvstrac/wiki?p=CorruptionFollowingBusyError">
+** CorruptionFollowingBusyError</a> wiki page for a discussion of why
+** this is important.
+**
+** There can only be a single busy handler defined for each database
+** connection. Setting a new busy handler clears any previous one.
+** Note that calling [sqlite3_busy_timeout()] will also set or clear
+** the busy handler.
+**
+** INVARIANTS:
+**
+** {F12311} The [sqlite3_busy_handler()] function replaces the busy handler
+** callback in the database connection identified by the 1st
+** parameter with a new busy handler identified by the 2nd and 3rd
+** parameters.
+**
+** {F12312} The default busy handler for new database connections is NULL.
+**
+** {F12314} When two or more database connection share a common cache,
+** the busy handler for the database connection currently using
+** the cache is invoked when the cache encounters a lock.
+**
+** {F12316} If a busy handler callback returns zero, then the SQLite
+** interface that provoked the locking event will return
+** [SQLITE_BUSY].
+**
+** {F12318} SQLite will invokes the busy handler with two argument which
+** are a copy of the pointer supplied by the 3rd parameter to
+** [sqlite3_busy_handler()] and a count of the number of prior
+** invocations of the busy handler for the same locking event.
+**
+** LIMITATIONS:
+**
+** {U12319} A busy handler should not call close the database connection
+** or prepared statement that invoked the busy handler.
+*/
+SQLITE_API int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
+
+/*
+** CAPI3REF: Set A Busy Timeout {F12340}
+**
+** This routine sets a [sqlite3_busy_handler | busy handler]
+** that sleeps for a while when a
+** table is locked. The handler will sleep multiple times until
+** at least "ms" milliseconds of sleeping have been done. {F12343} After
+** "ms" milliseconds of sleeping, the handler returns 0 which
+** causes [sqlite3_step()] to return [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED].
+**
+** Calling this routine with an argument less than or equal to zero
+** turns off all busy handlers.
+**
+** There can only be a single busy handler for a particular database
+** connection. If another busy handler was defined
+** (using [sqlite3_busy_handler()]) prior to calling
+** this routine, that other busy handler is cleared.
+**
+** INVARIANTS:
+**
+** {F12341} The [sqlite3_busy_timeout()] function overrides any prior
+** [sqlite3_busy_timeout()] or [sqlite3_busy_handler()] setting
+** on the same database connection.
+**
+** {F12343} If the 2nd parameter to [sqlite3_busy_timeout()] is less than
+** or equal to zero, then the busy handler is cleared so that
+** all subsequent locking events immediately return [SQLITE_BUSY].
+**
+** {F12344} If the 2nd parameter to [sqlite3_busy_timeout()] is a positive
+** number N, then a busy handler is set that repeatedly calls
+** the xSleep() method in the VFS interface until either the
+** lock clears or until the cumulative sleep time reported back
+** by xSleep() exceeds N milliseconds.
+*/
+SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
+
+/*
+** CAPI3REF: Convenience Routines For Running Queries {F12370}
+**
+** Definition: A <b>result table</b> is memory data structure created by the
+** [sqlite3_get_table()] interface. A result table records the
+** complete query results from one or more queries.
+**
+** The table conceptually has a number of rows and columns. But
+** these numbers are not part of the result table itself. These
+** numbers are obtained separately. Let N be the number of rows
+** and M be the number of columns.
+**
+** A result table is an array of pointers to zero-terminated
+** UTF-8 strings. There are (N+1)*M elements in the array.
+** The first M pointers point to zero-terminated strings that
+** contain the names of the columns.
+** The remaining entries all point to query results. NULL
+** values are give a NULL pointer. All other values are in
+** their UTF-8 zero-terminated string representation as returned by
+** [sqlite3_column_text()].
+**
+** A result table might consists of one or more memory allocations.
+** It is not safe to pass a result table directly to [sqlite3_free()].
+** A result table should be deallocated using [sqlite3_free_table()].
+**
+** As an example of the result table format, suppose a query result
+** is as follows:
+**
+** <blockquote><pre>
+** Name | Age
+** -----------------------
+** Alice | 43
+** Bob | 28
+** Cindy | 21
+** </pre></blockquote>
+**
+** There are two column (M==2) and three rows (N==3). Thus the
+** result table has 8 entries. Suppose the result table is stored
+** in an array names azResult. Then azResult holds this content:
+**
+** <blockquote><pre>
+** azResult&#91;0] = "Name";
+** azResult&#91;1] = "Age";
+** azResult&#91;2] = "Alice";
+** azResult&#91;3] = "43";
+** azResult&#91;4] = "Bob";
+** azResult&#91;5] = "28";
+** azResult&#91;6] = "Cindy";
+** azResult&#91;7] = "21";
+** </pre></blockquote>
+**
+** The sqlite3_get_table() function evaluates one or more
+** semicolon-separated SQL statements in the zero-terminated UTF-8
+** string of its 2nd parameter. It returns a result table to the
+** pointer given in its 3rd parameter.
+**
+** After the calling function has finished using the result, it should
+** pass the pointer to the result table to sqlite3_free_table() in order to
+** release the memory that was malloc-ed. Because of the way the
+** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling
+** function must not try to call [sqlite3_free()] directly. Only
+** [sqlite3_free_table()] is able to release the memory properly and safely.
+**
+** The sqlite3_get_table() interface is implemented as a wrapper around
+** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access
+** to any internal data structures of SQLite. It uses only the public
+** interface defined here. As a consequence, errors that occur in the
+** wrapper layer outside of the internal [sqlite3_exec()] call are not
+** reflected in subsequent calls to [sqlite3_errcode()] or
+** [sqlite3_errmsg()].
+**
+** INVARIANTS:
+**
+** {F12371} If a [sqlite3_get_table()] fails a memory allocation, then
+** it frees the result table under construction, aborts the
+** query in process, skips any subsequent queries, sets the
+** *resultp output pointer to NULL and returns [SQLITE_NOMEM].
+**
+** {F12373} If the ncolumn parameter to [sqlite3_get_table()] is not NULL
+** then [sqlite3_get_table()] write the number of columns in the
+** result set of the query into *ncolumn if the query is
+** successful (if the function returns SQLITE_OK).
+**
+** {F12374} If the nrow parameter to [sqlite3_get_table()] is not NULL
+** then [sqlite3_get_table()] write the number of rows in the
+** result set of the query into *nrow if the query is
+** successful (if the function returns SQLITE_OK).
+**
+** {F12376} The [sqlite3_get_table()] function sets its *ncolumn value
+** to the number of columns in the result set of the query in the
+** sql parameter, or to zero if the query in sql has an empty
+** result set.
+*/
+SQLITE_API int sqlite3_get_table(
+ sqlite3*, /* An open database */
+ const char *sql, /* SQL to be evaluated */
+ char ***pResult, /* Results of the query */
+ int *nrow, /* Number of result rows written here */
+ int *ncolumn, /* Number of result columns written here */
+ char **errmsg /* Error msg written here */
+);
+SQLITE_API void sqlite3_free_table(char **result);
+
+/*
+** CAPI3REF: Formatted String Printing Functions {F17400}
+**
+** These routines are workalikes of the "printf()" family of functions
+** from the standard C library.
+**
+** The sqlite3_mprintf() and sqlite3_vmprintf() routines write their
+** results into memory obtained from [sqlite3_malloc()].
+** The strings returned by these two routines should be
+** released by [sqlite3_free()]. Both routines return a
+** NULL pointer if [sqlite3_malloc()] is unable to allocate enough
+** memory to hold the resulting string.
+**
+** In sqlite3_snprintf() routine is similar to "snprintf()" from
+** the standard C library. The result is written into the
+** buffer supplied as the second parameter whose size is given by
+** the first parameter. Note that the order of the
+** first two parameters is reversed from snprintf(). This is an
+** historical accident that cannot be fixed without breaking
+** backwards compatibility. Note also that sqlite3_snprintf()
+** returns a pointer to its buffer instead of the number of
+** characters actually written into the buffer. We admit that
+** the number of characters written would be a more useful return
+** value but we cannot change the implementation of sqlite3_snprintf()
+** now without breaking compatibility.
+**
+** As long as the buffer size is greater than zero, sqlite3_snprintf()
+** guarantees that the buffer is always zero-terminated. The first
+** parameter "n" is the total size of the buffer, including space for
+** the zero terminator. So the longest string that can be completely
+** written will be n-1 characters.
+**
+** These routines all implement some additional formatting
+** options that are useful for constructing SQL statements.
+** All of the usual printf formatting options apply. In addition, there
+** is are "%q", "%Q", and "%z" options.
+**
+** The %q option works like %s in that it substitutes a null-terminated
+** string from the argument list. But %q also doubles every '\'' character.
+** %q is designed for use inside a string literal. By doubling each '\''
+** character it escapes that character and allows it to be inserted into
+** the string.
+**
+** For example, so some string variable contains text as follows:
+**
+** <blockquote><pre>
+** char *zText = "It's a happy day!";
+** </pre></blockquote>
+**
+** One can use this text in an SQL statement as follows:
+**
+** <blockquote><pre>
+** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
+** sqlite3_exec(db, zSQL, 0, 0, 0);
+** sqlite3_free(zSQL);
+** </pre></blockquote>
+**
+** Because the %q format string is used, the '\'' character in zText
+** is escaped and the SQL generated is as follows:
+**
+** <blockquote><pre>
+** INSERT INTO table1 VALUES('It''s a happy day!')
+** </pre></blockquote>
+**
+** This is correct. Had we used %s instead of %q, the generated SQL
+** would have looked like this:
+**
+** <blockquote><pre>
+** INSERT INTO table1 VALUES('It's a happy day!');
+** </pre></blockquote>
+**
+** This second example is an SQL syntax error. As a general rule you
+** should always use %q instead of %s when inserting text into a string
+** literal.
+**
+** The %Q option works like %q except it also adds single quotes around
+** the outside of the total string. Or if the parameter in the argument
+** list is a NULL pointer, %Q substitutes the text "NULL" (without single
+** quotes) in place of the %Q option. {END} So, for example, one could say:
+**
+** <blockquote><pre>
+** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
+** sqlite3_exec(db, zSQL, 0, 0, 0);
+** sqlite3_free(zSQL);
+** </pre></blockquote>
+**
+** The code above will render a correct SQL statement in the zSQL
+** variable even if the zText variable is a NULL pointer.
+**
+** The "%z" formatting option works exactly like "%s" with the
+** addition that after the string has been read and copied into
+** the result, [sqlite3_free()] is called on the input string. {END}
+**
+** INVARIANTS:
+**
+** {F17403} The [sqlite3_mprintf()] and [sqlite3_vmprintf()] interfaces
+** return either pointers to zero-terminated UTF-8 strings held in
+** memory obtained from [sqlite3_malloc()] or NULL pointers if
+** a call to [sqlite3_malloc()] fails.
+**
+** {F17406} The [sqlite3_snprintf()] interface writes a zero-terminated
+** UTF-8 string into the buffer pointed to by the second parameter
+** provided that the first parameter is greater than zero.
+**
+** {F17407} The [sqlite3_snprintf()] interface does not writes slots of
+** its output buffer (the second parameter) outside the range
+** of 0 through N-1 (where N is the first parameter)
+** regardless of the length of the string
+** requested by the format specification.
+**
+*/
+SQLITE_API char *sqlite3_mprintf(const char*,...);
+SQLITE_API char *sqlite3_vmprintf(const char*, va_list);
+SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...);
+
+/*
+** CAPI3REF: Memory Allocation Subsystem {F17300}
+**
+** The SQLite core uses these three routines for all of its own
+** internal memory allocation needs. "Core" in the previous sentence
+** does not include operating-system specific VFS implementation. The
+** windows VFS uses native malloc and free for some operations.
+**
+** The sqlite3_malloc() routine returns a pointer to a block
+** of memory at least N bytes in length, where N is the parameter.
+** If sqlite3_malloc() is unable to obtain sufficient free
+** memory, it returns a NULL pointer. If the parameter N to
+** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns
+** a NULL pointer.
+**
+** Calling sqlite3_free() with a pointer previously returned
+** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
+** that it might be reused. The sqlite3_free() routine is
+** a no-op if is called with a NULL pointer. Passing a NULL pointer
+** to sqlite3_free() is harmless. After being freed, memory
+** should neither be read nor written. Even reading previously freed
+** memory might result in a segmentation fault or other severe error.
+** Memory corruption, a segmentation fault, or other severe error
+** might result if sqlite3_free() is called with a non-NULL pointer that
+** was not obtained from sqlite3_malloc() or sqlite3_free().
+**
+** The sqlite3_realloc() interface attempts to resize a
+** prior memory allocation to be at least N bytes, where N is the
+** second parameter. The memory allocation to be resized is the first
+** parameter. If the first parameter to sqlite3_realloc()
+** is a NULL pointer then its behavior is identical to calling
+** sqlite3_malloc(N) where N is the second parameter to sqlite3_realloc().
+** If the second parameter to sqlite3_realloc() is zero or
+** negative then the behavior is exactly the same as calling
+** sqlite3_free(P) where P is the first parameter to sqlite3_realloc().
+** Sqlite3_realloc() returns a pointer to a memory allocation
+** of at least N bytes in size or NULL if sufficient memory is unavailable.
+** If M is the size of the prior allocation, then min(N,M) bytes
+** of the prior allocation are copied into the beginning of buffer returned
+** by sqlite3_realloc() and the prior allocation is freed.
+** If sqlite3_realloc() returns NULL, then the prior allocation
+** is not freed.
+**
+** The memory returned by sqlite3_malloc() and sqlite3_realloc()
+** is always aligned to at least an 8 byte boundary. {END}
+**
+** The default implementation
+** of the memory allocation subsystem uses the malloc(), realloc()
+** and free() provided by the standard C library. {F17382} However, if
+** SQLite is compiled with the following C preprocessor macro
+**
+** <blockquote> SQLITE_MEMORY_SIZE=<i>NNN</i> </blockquote>
+**
+** where <i>NNN</i> is an integer, then SQLite create a static
+** array of at least <i>NNN</i> bytes in size and use that array
+** for all of its dynamic memory allocation needs. {END} Additional
+** memory allocator options may be added in future releases.
+**
+** In SQLite version 3.5.0 and 3.5.1, it was possible to define
+** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in
+** implementation of these routines to be omitted. That capability
+** is no longer provided. Only built-in memory allocators can be
+** used.
+**
+** The windows OS interface layer calls
+** the system malloc() and free() directly when converting
+** filenames between the UTF-8 encoding used by SQLite
+** and whatever filename encoding is used by the particular windows
+** installation. Memory allocation errors are detected, but
+** they are reported back as [SQLITE_CANTOPEN] or
+** [SQLITE_IOERR] rather than [SQLITE_NOMEM].
+**
+** INVARIANTS:
+**
+** {F17303} The [sqlite3_malloc(N)] interface returns either a pointer to
+** newly checked-out block of at least N bytes of memory
+** that is 8-byte aligned,
+** or it returns NULL if it is unable to fulfill the request.
+**
+** {F17304} The [sqlite3_malloc(N)] interface returns a NULL pointer if
+** N is less than or equal to zero.
+**
+** {F17305} The [sqlite3_free(P)] interface releases memory previously
+** returned from [sqlite3_malloc()] or [sqlite3_realloc()],
+** making it available for reuse.
+**
+** {F17306} A call to [sqlite3_free(NULL)] is a harmless no-op.
+**
+** {F17310} A call to [sqlite3_realloc(0,N)] is equivalent to a call
+** to [sqlite3_malloc(N)].
+**
+** {F17312} A call to [sqlite3_realloc(P,0)] is equivalent to a call
+** to [sqlite3_free(P)].
+**
+** {F17315} The SQLite core uses [sqlite3_malloc()], [sqlite3_realloc()],
+** and [sqlite3_free()] for all of its memory allocation and
+** deallocation needs.
+**
+** {F17318} The [sqlite3_realloc(P,N)] interface returns either a pointer
+** to a block of checked-out memory of at least N bytes in size
+** that is 8-byte aligned, or a NULL pointer.
+**
+** {F17321} When [sqlite3_realloc(P,N)] returns a non-NULL pointer, it first
+** copies the first K bytes of content from P into the newly allocated
+** where K is the lessor of N and the size of the buffer P.
+**
+** {F17322} When [sqlite3_realloc(P,N)] returns a non-NULL pointer, it first
+** releases the buffer P.
+**
+** {F17323} When [sqlite3_realloc(P,N)] returns NULL, the buffer P is
+** not modified or released.
+**
+** LIMITATIONS:
+**
+** {U17350} The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()]
+** must be either NULL or else a pointer obtained from a prior
+** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that has
+** not been released.
+**
+** {U17351} The application must not read or write any part of
+** a block of memory after it has been released using
+** [sqlite3_free()] or [sqlite3_realloc()].
+**
+*/
+SQLITE_API void *sqlite3_malloc(int);
+SQLITE_API void *sqlite3_realloc(void*, int);
+SQLITE_API void sqlite3_free(void*);
+
+/*
+** CAPI3REF: Memory Allocator Statistics {F17370}
+**
+** SQLite provides these two interfaces for reporting on the status
+** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()]
+** the memory allocation subsystem included within the SQLite.
+**
+** INVARIANTS:
+**
+** {F17371} The [sqlite3_memory_used()] routine returns the
+** number of bytes of memory currently outstanding
+** (malloced but not freed).
+**
+** {F17373} The [sqlite3_memory_highwater()] routine returns the maximum
+** value of [sqlite3_memory_used()]
+** since the highwater mark was last reset.
+**
+** {F17374} The values returned by [sqlite3_memory_used()] and
+** [sqlite3_memory_highwater()] include any overhead
+** added by SQLite in its implementation of [sqlite3_malloc()],
+** but not overhead added by the any underlying system library
+** routines that [sqlite3_malloc()] may call.
+**
+** {F17375} The memory highwater mark is reset to the current value of
+** [sqlite3_memory_used()] if and only if the parameter to
+** [sqlite3_memory_highwater()] is true. The value returned
+** by [sqlite3_memory_highwater(1)] is the highwater mark
+** prior to the reset.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_used(void);
+SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
+
+/*
+** CAPI3REF: Pseudo-Random Number Generator {F17390}
+**
+** SQLite contains a high-quality pseudo-random number generator (PRNG) used to
+** select random ROWIDs when inserting new records into a table that
+** already uses the largest possible ROWID. The PRNG is also used for
+** the build-in random() and randomblob() SQL functions. This interface allows
+** appliations to access the same PRNG for other purposes.
+**
+** A call to this routine stores N bytes of randomness into buffer P.
+**
+** The first time this routine is invoked (either internally or by
+** the application) the PRNG is seeded using randomness obtained
+** from the xRandomness method of the default [sqlite3_vfs] object.
+** On all subsequent invocations, the pseudo-randomness is generated
+** internally and without recourse to the [sqlite3_vfs] xRandomness
+** method.
+**
+** INVARIANTS:
+**
+** {F17392} The [sqlite3_randomness(N,P)] interface writes N bytes of
+** high-quality pseudo-randomness into buffer P.
+*/
+SQLITE_API void sqlite3_randomness(int N, void *P);
+
+/*
+** CAPI3REF: Compile-Time Authorization Callbacks {F12500}
+**
+** This routine registers a authorizer callback with a particular
+** [database connection], supplied in the first argument.
+** The authorizer callback is invoked as SQL statements are being compiled
+** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()],
+** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. At various
+** points during the compilation process, as logic is being created
+** to perform various actions, the authorizer callback is invoked to
+** see if those actions are allowed. The authorizer callback should
+** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the
+** specific action but allow the SQL statement to continue to be
+** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be
+** rejected with an error. If the authorizer callback returns
+** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY]
+** then [sqlite3_prepare_v2()] or equivalent call that triggered
+** the authorizer will fail with an error message.
+**
+** When the callback returns [SQLITE_OK], that means the operation
+** requested is ok. When the callback returns [SQLITE_DENY], the
+** [sqlite3_prepare_v2()] or equivalent call that triggered the
+** authorizer will fail with an error message explaining that
+** access is denied. If the authorizer code is [SQLITE_READ]
+** and the callback returns [SQLITE_IGNORE] then the
+** [prepared statement] statement is constructed to substitute
+** a NULL value in place of the table column that would have
+** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE]
+** return can be used to deny an untrusted user access to individual
+** columns of a table.
+**
+** The first parameter to the authorizer callback is a copy of
+** the third parameter to the sqlite3_set_authorizer() interface.
+** The second parameter to the callback is an integer
+** [SQLITE_COPY | action code] that specifies the particular action
+** to be authorized. The third through sixth
+** parameters to the callback are zero-terminated strings that contain
+** additional details about the action to be authorized.
+**
+** An authorizer is used when [sqlite3_prepare | preparing]
+** SQL statements from an untrusted
+** source, to ensure that the SQL statements do not try to access data
+** that they are not allowed to see, or that they do not try to
+** execute malicious statements that damage the database. For
+** example, an application may allow a user to enter arbitrary
+** SQL queries for evaluation by a database. But the application does
+** not want the user to be able to make arbitrary changes to the
+** database. An authorizer could then be put in place while the
+** user-entered SQL is being [sqlite3_prepare | prepared] that
+** disallows everything except [SELECT] statements.
+**
+** Applications that need to process SQL from untrusted sources
+** might also consider lowering resource limits using [sqlite3_limit()]
+** and limiting database size using the [max_page_count] [PRAGMA]
+** in addition to using an authorizer.
+**
+** Only a single authorizer can be in place on a database connection
+** at a time. Each call to sqlite3_set_authorizer overrides the
+** previous call. Disable the authorizer by installing a NULL callback.
+** The authorizer is disabled by default.
+**
+** Note that the authorizer callback is invoked only during
+** [sqlite3_prepare()] or its variants. Authorization is not
+** performed during statement evaluation in [sqlite3_step()].
+**
+** INVARIANTS:
+**
+** {F12501} The [sqlite3_set_authorizer(D,...)] interface registers a
+** authorizer callback with database connection D.
+**
+** {F12502} The authorizer callback is invoked as SQL statements are
+** being compiled
+**
+** {F12503} If the authorizer callback returns any value other than
+** [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] then
+** the [sqlite3_prepare_v2()] or equivalent call that caused
+** the authorizer callback to run shall fail with an
+** [SQLITE_ERROR] error code and an appropriate error message.
+**
+** {F12504} When the authorizer callback returns [SQLITE_OK], the operation
+** described is coded normally.
+**
+** {F12505} When the authorizer callback returns [SQLITE_DENY], the
+** [sqlite3_prepare_v2()] or equivalent call that caused the
+** authorizer callback to run shall fail
+** with an [SQLITE_ERROR] error code and an error message
+** explaining that access is denied.
+**
+** {F12506} If the authorizer code (the 2nd parameter to the authorizer
+** callback) is [SQLITE_READ] and the authorizer callback returns
+** [SQLITE_IGNORE] then the prepared statement is constructed to
+** insert a NULL value in place of the table column that would have
+** been read if [SQLITE_OK] had been returned.
+**
+** {F12507} If the authorizer code (the 2nd parameter to the authorizer
+** callback) is anything other than [SQLITE_READ], then
+** a return of [SQLITE_IGNORE] has the same effect as [SQLITE_DENY].
+**
+** {F12510} The first parameter to the authorizer callback is a copy of
+** the third parameter to the [sqlite3_set_authorizer()] interface.
+**
+** {F12511} The second parameter to the callback is an integer
+** [SQLITE_COPY | action code] that specifies the particular action
+** to be authorized.
+**
+** {F12512} The third through sixth parameters to the callback are
+** zero-terminated strings that contain
+** additional details about the action to be authorized.
+**
+** {F12520} Each call to [sqlite3_set_authorizer()] overrides the
+** any previously installed authorizer.
+**
+** {F12521} A NULL authorizer means that no authorization
+** callback is invoked.
+**
+** {F12522} The default authorizer is NULL.
+*/
+SQLITE_API int sqlite3_set_authorizer(
+ sqlite3*,
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pUserData
+);
+
+/*
+** CAPI3REF: Authorizer Return Codes {F12590}
+**
+** The [sqlite3_set_authorizer | authorizer callback function] must
+** return either [SQLITE_OK] or one of these two constants in order
+** to signal SQLite whether or not the action is permitted. See the
+** [sqlite3_set_authorizer | authorizer documentation] for additional
+** information.
+*/
+#define SQLITE_DENY 1 /* Abort the SQL statement with an error */
+#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
+
+/*
+** CAPI3REF: Authorizer Action Codes {F12550}
+**
+** The [sqlite3_set_authorizer()] interface registers a callback function
+** that is invoked to authorizer certain SQL statement actions. The
+** second parameter to the callback is an integer code that specifies
+** what action is being authorized. These are the integer action codes that
+** the authorizer callback may be passed.
+**
+** These action code values signify what kind of operation is to be
+** authorized. The 3rd and 4th parameters to the authorization
+** callback function will be parameters or NULL depending on which of these
+** codes is used as the second parameter. The 5th parameter to the
+** authorizer callback is the name of the database ("main", "temp",
+** etc.) if applicable. The 6th parameter to the authorizer callback
+** is the name of the inner-most trigger or view that is responsible for
+** the access attempt or NULL if this access attempt is directly from
+** top-level SQL code.
+**
+** INVARIANTS:
+**
+** {F12551} The second parameter to an
+** [sqlite3_set_authorizer | authorizer callback is always an integer
+** [SQLITE_COPY | authorizer code] that specifies what action
+** is being authorized.
+**
+** {F12552} The 3rd and 4th parameters to the
+** [sqlite3_set_authorizer | authorization callback function]
+** will be parameters or NULL depending on which
+** [SQLITE_COPY | authorizer code] is used as the second parameter.
+**
+** {F12553} The 5th parameter to the
+** [sqlite3_set_authorizer | authorizer callback] is the name
+** of the database (example: "main", "temp", etc.) if applicable.
+**
+** {F12554} The 6th parameter to the
+** [sqlite3_set_authorizer | authorizer callback] is the name
+** of the inner-most trigger or view that is responsible for
+** the access attempt or NULL if this access attempt is directly from
+** top-level SQL code.
+*/
+/******************************************* 3rd ************ 4th ***********/
+#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */
+#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */
+#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */
+#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */
+#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */
+#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */
+#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */
+#define SQLITE_CREATE_VIEW 8 /* View Name NULL */
+#define SQLITE_DELETE 9 /* Table Name NULL */
+#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */
+#define SQLITE_DROP_TABLE 11 /* Table Name NULL */
+#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */
+#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */
+#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */
+#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */
+#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */
+#define SQLITE_DROP_VIEW 17 /* View Name NULL */
+#define SQLITE_INSERT 18 /* Table Name NULL */
+#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */
+#define SQLITE_READ 20 /* Table Name Column Name */
+#define SQLITE_SELECT 21 /* NULL NULL */
+#define SQLITE_TRANSACTION 22 /* NULL NULL */
+#define SQLITE_UPDATE 23 /* Table Name Column Name */
+#define SQLITE_ATTACH 24 /* Filename NULL */
+#define SQLITE_DETACH 25 /* Database Name NULL */
+#define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */
+#define SQLITE_REINDEX 27 /* Index Name NULL */
+#define SQLITE_ANALYZE 28 /* Table Name NULL */
+#define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */
+#define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */
+#define SQLITE_FUNCTION 31 /* Function Name NULL */
+#define SQLITE_COPY 0 /* No longer used */
+
+/*
+** CAPI3REF: Tracing And Profiling Functions {F12280}
+**
+** These routines register callback functions that can be used for
+** tracing and profiling the execution of SQL statements.
+**
+** The callback function registered by sqlite3_trace() is invoked at
+** various times when an SQL statement is being run by [sqlite3_step()].
+** The callback returns a UTF-8 rendering of the SQL statement text
+** as the statement first begins executing. Additional callbacks occur
+** as each triggersubprogram is entered. The callbacks for triggers
+** contain a UTF-8 SQL comment that identifies the trigger.
+**
+** The callback function registered by sqlite3_profile() is invoked
+** as each SQL statement finishes. The profile callback contains
+** the original statement text and an estimate of wall-clock time
+** of how long that statement took to run.
+**
+** The sqlite3_profile() API is currently considered experimental and
+** is subject to change or removal in a future release.
+**
+** The trigger reporting feature of the trace callback is considered
+** experimental and is subject to change or removal in future releases.
+** Future versions of SQLite might also add new trace callback
+** invocations.
+**
+** INVARIANTS:
+**
+** {F12281} The callback function registered by [sqlite3_trace()] is
+** whenever an SQL statement first begins to execute and
+** whenever a trigger subprogram first begins to run.
+**
+** {F12282} Each call to [sqlite3_trace()] overrides the previously
+** registered trace callback.
+**
+** {F12283} A NULL trace callback disables tracing.
+**
+** {F12284} The first argument to the trace callback is a copy of
+** the pointer which was the 3rd argument to [sqlite3_trace()].
+**
+** {F12285} The second argument to the trace callback is a
+** zero-terminated UTF8 string containing the original text
+** of the SQL statement as it was passed into [sqlite3_prepare_v2()]
+** or the equivalent, or an SQL comment indicating the beginning
+** of a trigger subprogram.
+**
+** {F12287} The callback function registered by [sqlite3_profile()] is invoked
+** as each SQL statement finishes.
+**
+** {F12288} The first parameter to the profile callback is a copy of
+** the 3rd parameter to [sqlite3_profile()].
+**
+** {F12289} The second parameter to the profile callback is a
+** zero-terminated UTF-8 string that contains the complete text of
+** the SQL statement as it was processed by [sqlite3_prepare_v2()]
+** or the equivalent.
+**
+** {F12290} The third parameter to the profile callback is an estimate
+** of the number of nanoseconds of wall-clock time required to
+** run the SQL statement from start to finish.
+*/
+SQLITE_API void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*);
+SQLITE_API void *sqlite3_profile(sqlite3*,
+ void(*xProfile)(void*,const char*,sqlite3_uint64), void*);
+
+/*
+** CAPI3REF: Query Progress Callbacks {F12910}
+**
+** This routine configures a callback function - the
+** progress callback - that is invoked periodically during long
+** running calls to [sqlite3_exec()], [sqlite3_step()] and
+** [sqlite3_get_table()]. An example use for this
+** interface is to keep a GUI updated during a large query.
+**
+** If the progress callback returns non-zero, the opertion is
+** interrupted. This feature can be used to implement a
+** "Cancel" button on a GUI dialog box.
+**
+** INVARIANTS:
+**
+** {F12911} The callback function registered by [sqlite3_progress_handler()]
+** is invoked periodically during long running calls to
+** [sqlite3_step()].
+**
+** {F12912} The progress callback is invoked once for every N virtual
+** machine opcodes, where N is the second argument to
+** the [sqlite3_progress_handler()] call that registered
+** the callback. <todo>What if N is less than 1?</todo>
+**
+** {F12913} The progress callback itself is identified by the third
+** argument to [sqlite3_progress_handler()].
+**
+** {F12914} The fourth argument [sqlite3_progress_handler()] is a
+*** void pointer passed to the progress callback
+** function each time it is invoked.
+**
+** {F12915} If a call to [sqlite3_step()] results in fewer than
+** N opcodes being executed,
+** then the progress callback is never invoked. {END}
+**
+** {F12916} Every call to [sqlite3_progress_handler()]
+** overwrites any previously registere progress handler.
+**
+** {F12917} If the progress handler callback is NULL then no progress
+** handler is invoked.
+**
+** {F12918} If the progress callback returns a result other than 0, then
+** the behavior is a if [sqlite3_interrupt()] had been called.
+*/
+SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
+
+/*
+** CAPI3REF: Opening A New Database Connection {F12700}
+**
+** These routines open an SQLite database file whose name
+** is given by the filename argument.
+** The filename argument is interpreted as UTF-8
+** for [sqlite3_open()] and [sqlite3_open_v2()] and as UTF-16
+** in the native byte order for [sqlite3_open16()].
+** An [sqlite3*] handle is usually returned in *ppDb, even
+** if an error occurs. The only exception is if SQLite is unable
+** to allocate memory to hold the [sqlite3] object, a NULL will
+** be written into *ppDb instead of a pointer to the [sqlite3] object.
+** If the database is opened (and/or created)
+** successfully, then [SQLITE_OK] is returned. Otherwise an
+** error code is returned. The
+** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain
+** an English language description of the error.
+**
+** The default encoding for the database will be UTF-8 if
+** [sqlite3_open()] or [sqlite3_open_v2()] is called and
+** UTF-16 in the native byte order if [sqlite3_open16()] is used.
+**
+** Whether or not an error occurs when it is opened, resources
+** associated with the [sqlite3*] handle should be released by passing it
+** to [sqlite3_close()] when it is no longer required.
+**
+** The [sqlite3_open_v2()] interface works like [sqlite3_open()]
+** except that it acccepts two additional parameters for additional control
+** over the new database connection. The flags parameter can be
+** one of:
+**
+** <ol>
+** <li> [SQLITE_OPEN_READONLY]
+** <li> [SQLITE_OPEN_READWRITE]
+** <li> [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
+** </ol>
+**
+** The first value opens the database read-only.
+** If the database does not previously exist, an error is returned.
+** The second option opens
+** the database for reading and writing if possible, or reading only if
+** if the file is write protected. In either case the database
+** must already exist or an error is returned. The third option
+** opens the database for reading and writing and creates it if it does
+** not already exist.
+** The third options is behavior that is always used for [sqlite3_open()]
+** and [sqlite3_open16()].
+**
+** If the 3rd parameter to [sqlite3_open_v2()] is not one of the
+** combinations shown above then the behavior is undefined.
+**
+** If the filename is ":memory:", then an private
+** in-memory database is created for the connection. This in-memory
+** database will vanish when the database connection is closed. Future
+** version of SQLite might make use of additional special filenames
+** that begin with the ":" character. It is recommended that
+** when a database filename really does begin with
+** ":" that you prefix the filename with a pathname like "./" to
+** avoid ambiguity.
+**
+** If the filename is an empty string, then a private temporary
+** on-disk database will be created. This private database will be
+** automatically deleted as soon as the database connection is closed.
+**
+** The fourth parameter to sqlite3_open_v2() is the name of the
+** [sqlite3_vfs] object that defines the operating system
+** interface that the new database connection should use. If the
+** fourth parameter is a NULL pointer then the default [sqlite3_vfs]
+** object is used.
+**
+** <b>Note to windows users:</b> The encoding used for the filename argument
+** of [sqlite3_open()] and [sqlite3_open_v2()] must be UTF-8, not whatever
+** codepage is currently defined. Filenames containing international
+** characters must be converted to UTF-8 prior to passing them into
+** [sqlite3_open()] or [sqlite3_open_v2()].
+**
+** INVARIANTS:
+**
+** {F12701} The [sqlite3_open()], [sqlite3_open16()], and
+** [sqlite3_open_v2()] interfaces create a new
+** [database connection] associated with
+** the database file given in their first parameter.
+**
+** {F12702} The filename argument is interpreted as UTF-8
+** for [sqlite3_open()] and [sqlite3_open_v2()] and as UTF-16
+** in the native byte order for [sqlite3_open16()].
+**
+** {F12703} A successful invocation of [sqlite3_open()], [sqlite3_open16()],
+** or [sqlite3_open_v2()] writes a pointer to a new
+** [database connection] into *ppDb.
+**
+** {F12704} The [sqlite3_open()], [sqlite3_open16()], and
+** [sqlite3_open_v2()] interfaces return [SQLITE_OK] upon success,
+** or an appropriate [error code] on failure.
+**
+** {F12706} The default text encoding for a new database created using
+** [sqlite3_open()] or [sqlite3_open_v2()] will be UTF-8.
+**
+** {F12707} The default text encoding for a new database created using
+** [sqlite3_open16()] will be UTF-16.
+**
+** {F12709} The [sqlite3_open(F,D)] interface is equivalent to
+** [sqlite3_open_v2(F,D,G,0)] where the G parameter is
+** [SQLITE_OPEN_READWRITE]|[SQLITE_OPEN_CREATE].
+**
+** {F12711} If the G parameter to [sqlite3_open_v2(F,D,G,V)] contains the
+** bit value [SQLITE_OPEN_READONLY] then the database is opened
+** for reading only.
+**
+** {F12712} If the G parameter to [sqlite3_open_v2(F,D,G,V)] contains the
+** bit value [SQLITE_OPEN_READWRITE] then the database is opened
+** reading and writing if possible, or for reading only if the
+** file is write protected by the operating system.
+**
+** {F12713} If the G parameter to [sqlite3_open(v2(F,D,G,V)] omits the
+** bit value [SQLITE_OPEN_CREATE] and the database does not
+** previously exist, an error is returned.
+**
+** {F12714} If the G parameter to [sqlite3_open(v2(F,D,G,V)] contains the
+** bit value [SQLITE_OPEN_CREATE] and the database does not
+** previously exist, then an attempt is made to create and
+** initialize the database.
+**
+** {F12717} If the filename argument to [sqlite3_open()], [sqlite3_open16()],
+** or [sqlite3_open_v2()] is ":memory:", then an private,
+** ephemeral, in-memory database is created for the connection.
+** <todo>Is SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE required
+** in sqlite3_open_v2()?</todo>
+**
+** {F12719} If the filename is NULL or an empty string, then a private,
+** ephermeral on-disk database will be created.
+** <todo>Is SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE required
+** in sqlite3_open_v2()?</todo>
+**
+** {F12721} The [database connection] created by
+** [sqlite3_open_v2(F,D,G,V)] will use the
+** [sqlite3_vfs] object identified by the V parameter, or
+** the default [sqlite3_vfs] object is V is a NULL pointer.
+*/
+SQLITE_API int sqlite3_open(
+ const char *filename, /* Database filename (UTF-8) */
+ sqlite3 **ppDb /* OUT: SQLite db handle */
+);
+SQLITE_API int sqlite3_open16(
+ const void *filename, /* Database filename (UTF-16) */
+ sqlite3 **ppDb /* OUT: SQLite db handle */
+);
+SQLITE_API int sqlite3_open_v2(
+ const char *filename, /* Database filename (UTF-8) */
+ sqlite3 **ppDb, /* OUT: SQLite db handle */
+ int flags, /* Flags */
+ const char *zVfs /* Name of VFS module to use */
+);
+
+/*
+** CAPI3REF: Error Codes And Messages {F12800}
+**
+** The sqlite3_errcode() interface returns the numeric
+** [SQLITE_OK | result code] or [SQLITE_IOERR_READ | extended result code]
+** for the most recent failed sqlite3_* API call associated
+** with [sqlite3] handle 'db'. If a prior API call failed but the
+** most recent API call succeeded, the return value from sqlite3_errcode()
+** is undefined.
+**
+** The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
+** text that describes the error, as either UTF8 or UTF16 respectively.
+** Memory to hold the error message string is managed internally.
+** The application does not need to worry with freeing the result.
+** However, the error string might be overwritten or deallocated by
+** subsequent calls to other SQLite interface functions.
+**
+** INVARIANTS:
+**
+** {F12801} The [sqlite3_errcode(D)] interface returns the numeric
+** [SQLITE_OK | result code] or
+** [SQLITE_IOERR_READ | extended result code]
+** for the most recently failed interface call associated
+** with [database connection] D.
+**
+** {F12803} The [sqlite3_errmsg(D)] and [sqlite3_errmsg16(D)]
+** interfaces return English-language text that describes
+** the error in the mostly recently failed interface call,
+** encoded as either UTF8 or UTF16 respectively.
+**
+** {F12807} The strings returned by [sqlite3_errmsg()] and [sqlite3_errmsg16()]
+** are valid until the next SQLite interface call.
+**
+** {F12808} Calls to API routines that do not return an error code
+** (example: [sqlite3_data_count()]) do not
+** change the error code or message returned by
+** [sqlite3_errcode()], [sqlite3_errmsg()], or [sqlite3_errmsg16()].
+**
+** {F12809} Interfaces that are not associated with a specific
+** [database connection] (examples:
+** [sqlite3_mprintf()] or [sqlite3_enable_shared_cache()]
+** do not change the values returned by
+** [sqlite3_errcode()], [sqlite3_errmsg()], or [sqlite3_errmsg16()].
+*/
+SQLITE_API int sqlite3_errcode(sqlite3 *db);
+SQLITE_API const char *sqlite3_errmsg(sqlite3*);
+SQLITE_API const void *sqlite3_errmsg16(sqlite3*);
+
+/*
+** CAPI3REF: SQL Statement Object {F13000}
+** KEYWORDS: {prepared statement} {prepared statements}
+**
+** An instance of this object represent single SQL statements. This
+** object is variously known as a "prepared statement" or a
+** "compiled SQL statement" or simply as a "statement".
+**
+** The life of a statement object goes something like this:
+**
+** <ol>
+** <li> Create the object using [sqlite3_prepare_v2()] or a related
+** function.
+** <li> Bind values to host parameters using
+** [sqlite3_bind_blob | sqlite3_bind_* interfaces].
+** <li> Run the SQL by calling [sqlite3_step()] one or more times.
+** <li> Reset the statement using [sqlite3_reset()] then go back
+** to step 2. Do this zero or more times.
+** <li> Destroy the object using [sqlite3_finalize()].
+** </ol>
+**
+** Refer to documentation on individual methods above for additional
+** information.
+*/
+typedef struct sqlite3_stmt sqlite3_stmt;
+
+/*
+** CAPI3REF: Run-time Limits {F12760}
+**
+** This interface allows the size of various constructs to be limited
+** on a connection by connection basis. The first parameter is the
+** [database connection] whose limit is to be set or queried. The
+** second parameter is one of the [limit categories] that define a
+** class of constructs to be size limited. The third parameter is the
+** new limit for that construct. The function returns the old limit.
+**
+** If the new limit is a negative number, the limit is unchanged.
+** For the limit category of SQLITE_LIMIT_XYZ there is a hard upper
+** bound set by a compile-time C-preprocess macro named SQLITE_MAX_XYZ.
+** (The "_LIMIT_" in the name is changed to "_MAX_".)
+** Attempts to increase a limit above its hard upper bound are
+** silently truncated to the hard upper limit.
+**
+** Run time limits are intended for use in applications that manage
+** both their own internal database and also databases that are controlled
+** by untrusted external sources. An example application might be a
+** webbrowser that has its own databases for storing history and
+** separate databases controlled by javascript applications downloaded
+** off the internet. The internal databases can be given the
+** large, default limits. Databases managed by external sources can
+** be given much smaller limits designed to prevent a denial of service
+** attach. Developers might also want to use the [sqlite3_set_authorizer()]
+** interface to further control untrusted SQL. The size of the database
+** created by an untrusted script can be contained using the
+** [max_page_count] [PRAGMA].
+**
+** This interface is currently considered experimental and is subject
+** to change or removal without prior notice.
+**
+** INVARIANTS:
+**
+** {F12762} A successful call to [sqlite3_limit(D,C,V)] where V is
+** positive changes the
+** limit on the size of construct C in [database connection] D
+** to the lessor of V and the hard upper bound on the size
+** of C that is set at compile-time.
+**
+** {F12766} A successful call to [sqlite3_limit(D,C,V)] where V is negative
+** leaves the state of [database connection] D unchanged.
+**
+** {F12769} A successful call to [sqlite3_limit(D,C,V)] returns the
+** value of the limit on the size of construct C in
+** in [database connection] D as it was prior to the call.
+*/
+SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
+
+/*
+** CAPI3REF: Run-Time Limit Categories {F12790}
+** KEYWORDS: {limit category} {limit categories}
+**
+** These constants define various aspects of a [database connection]
+** that can be limited in size by calls to [sqlite3_limit()].
+** The meanings of the various limits are as follows:
+**
+** <dl>
+** <dt>SQLITE_LIMIT_LENGTH</dt>
+** <dd>The maximum size of any
+** string or blob or table row.<dd>
+**
+** <dt>SQLITE_LIMIT_SQL_LENGTH</dt>
+** <dd>The maximum length of an SQL statement.</dd>
+**
+** <dt>SQLITE_LIMIT_COLUMN</dt>
+** <dd>The maximum number of columns in a table definition or in the
+** result set of a SELECT or the maximum number of columns in an index
+** or in an ORDER BY or GROUP BY clause.</dd>
+**
+** <dt>SQLITE_LIMIT_EXPR_DEPTH</dt>
+** <dd>The maximum depth of the parse tree on any expression.</dd>
+**
+** <dt>SQLITE_LIMIT_COMPOUND_SELECT</dt>
+** <dd>The maximum number of terms in a compound SELECT statement.</dd>
+**
+** <dt>SQLITE_LIMIT_VDBE_OP</dt>
+** <dd>The maximum number of instructions in a virtual machine program
+** used to implement an SQL statement.</dd>
+**
+** <dt>SQLITE_LIMIT_FUNCTION_ARG</dt>
+** <dd>The maximum number of arguments on a function.</dd>
+**
+** <dt>SQLITE_LIMIT_ATTACHED</dt>
+** <dd>The maximum number of attached databases.</dd>
+**
+** <dt>SQLITE_LIMIT_LIKE_PATTERN_LENGTH</dt>
+** <dd>The maximum length of the pattern argument to the LIKE or
+** GLOB operators.</dd>
+**
+** <dt>SQLITE_LIMIT_VARIABLE_NUMBER</dt>
+** <dd>The maximum number of variables in an SQL statement that can
+** be bound.</dd>
+** </dl>
+*/
+#define SQLITE_LIMIT_LENGTH 0
+#define SQLITE_LIMIT_SQL_LENGTH 1
+#define SQLITE_LIMIT_COLUMN 2
+#define SQLITE_LIMIT_EXPR_DEPTH 3
+#define SQLITE_LIMIT_COMPOUND_SELECT 4
+#define SQLITE_LIMIT_VDBE_OP 5
+#define SQLITE_LIMIT_FUNCTION_ARG 6
+#define SQLITE_LIMIT_ATTACHED 7
+#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8
+#define SQLITE_LIMIT_VARIABLE_NUMBER 9
+
+/*
+** CAPI3REF: Compiling An SQL Statement {F13010}
+**
+** To execute an SQL query, it must first be compiled into a byte-code
+** program using one of these routines.
+**
+** The first argument "db" is an [database connection]
+** obtained from a prior call to [sqlite3_open()], [sqlite3_open_v2()]
+** or [sqlite3_open16()].
+** The second argument "zSql" is the statement to be compiled, encoded
+** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2()
+** interfaces uses UTF-8 and sqlite3_prepare16() and sqlite3_prepare16_v2()
+** use UTF-16. {END}
+**
+** If the nByte argument is less
+** than zero, then zSql is read up to the first zero terminator.
+** If nByte is non-negative, then it is the maximum number of
+** bytes read from zSql. When nByte is non-negative, the
+** zSql string ends at either the first '\000' or '\u0000' character or
+** the nByte-th byte, whichever comes first. If the caller knows
+** that the supplied string is nul-terminated, then there is a small
+** performance advantage to be had by passing an nByte parameter that
+** is equal to the number of bytes in the input string <i>including</i>
+** the nul-terminator bytes.{END}
+**
+** *pzTail is made to point to the first byte past the end of the
+** first SQL statement in zSql. These routines only compiles the first
+** statement in zSql, so *pzTail is left pointing to what remains
+** uncompiled.
+**
+** *ppStmt is left pointing to a compiled [prepared statement] that can be
+** executed using [sqlite3_step()]. Or if there is an error, *ppStmt is
+** set to NULL. If the input text contains no SQL (if the input
+** is and empty string or a comment) then *ppStmt is set to NULL.
+** {U13018} The calling procedure is responsible for deleting the
+** compiled SQL statement
+** using [sqlite3_finalize()] after it has finished with it.
+**
+** On success, [SQLITE_OK] is returned. Otherwise an
+** [error code] is returned.
+**
+** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
+** recommended for all new programs. The two older interfaces are retained
+** for backwards compatibility, but their use is discouraged.
+** In the "v2" interfaces, the prepared statement
+** that is returned (the [sqlite3_stmt] object) contains a copy of the
+** original SQL text. {END} This causes the [sqlite3_step()] interface to
+** behave a differently in two ways:
+**
+** <ol>
+** <li>
+** If the database schema changes, instead of returning [SQLITE_SCHEMA] as it
+** always used to do, [sqlite3_step()] will automatically recompile the SQL
+** statement and try to run it again. If the schema has changed in
+** a way that makes the statement no longer valid, [sqlite3_step()] will still
+** return [SQLITE_SCHEMA]. But unlike the legacy behavior,
+** [SQLITE_SCHEMA] is now a fatal error. Calling
+** [sqlite3_prepare_v2()] again will not make the
+** error go away. Note: use [sqlite3_errmsg()] to find the text
+** of the parsing error that results in an [SQLITE_SCHEMA] return. {END}
+** </li>
+**
+** <li>
+** When an error occurs,
+** [sqlite3_step()] will return one of the detailed
+** [error codes] or [extended error codes].
+** The legacy behavior was that [sqlite3_step()] would only return a generic
+** [SQLITE_ERROR] result code and you would have to make a second call to
+** [sqlite3_reset()] in order to find the underlying cause of the problem.
+** With the "v2" prepare interfaces, the underlying reason for the error is
+** returned immediately.
+** </li>
+** </ol>
+**
+** INVARIANTS:
+**
+** {F13011} The [sqlite3_prepare(db,zSql,...)] and
+** [sqlite3_prepare_v2(db,zSql,...)] interfaces interpret the
+** text in their zSql parameter as UTF-8.
+**
+** {F13012} The [sqlite3_prepare16(db,zSql,...)] and
+** [sqlite3_prepare16_v2(db,zSql,...)] interfaces interpret the
+** text in their zSql parameter as UTF-16 in the native byte order.
+**
+** {F13013} If the nByte argument to [sqlite3_prepare_v2(db,zSql,nByte,...)]
+** and its variants is less than zero, then SQL text is
+** read from zSql is read up to the first zero terminator.
+**
+** {F13014} If the nByte argument to [sqlite3_prepare_v2(db,zSql,nByte,...)]
+** and its variants is non-negative, then at most nBytes bytes
+** SQL text is read from zSql.
+**
+** {F13015} In [sqlite3_prepare_v2(db,zSql,N,P,pzTail)] and its variants
+** if the zSql input text contains more than one SQL statement
+** and pzTail is not NULL, then *pzTail is made to point to the
+** first byte past the end of the first SQL statement in zSql.
+** <todo>What does *pzTail point to if there is one statement?</todo>
+**
+** {F13016} A successful call to [sqlite3_prepare_v2(db,zSql,N,ppStmt,...)]
+** or one of its variants writes into *ppStmt a pointer to a new
+** [prepared statement] or a pointer to NULL
+** if zSql contains nothing other than whitespace or comments.
+**
+** {F13019} The [sqlite3_prepare_v2()] interface and its variants return
+** [SQLITE_OK] or an appropriate [error code] upon failure.
+**
+** {F13021} Before [sqlite3_prepare(db,zSql,nByte,ppStmt,pzTail)] or its
+** variants returns an error (any value other than [SQLITE_OK])
+** it first sets *ppStmt to NULL.
+*/
+SQLITE_API int sqlite3_prepare(
+ sqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL statement, UTF-8 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const char **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+SQLITE_API int sqlite3_prepare_v2(
+ sqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL statement, UTF-8 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const char **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+SQLITE_API int sqlite3_prepare16(
+ sqlite3 *db, /* Database handle */
+ const void *zSql, /* SQL statement, UTF-16 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const void **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+SQLITE_API int sqlite3_prepare16_v2(
+ sqlite3 *db, /* Database handle */
+ const void *zSql, /* SQL statement, UTF-16 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const void **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+
+/*
+** CAPIREF: Retrieving Statement SQL {F13100}
+**
+** This intereface can be used to retrieve a saved copy of the original
+** SQL text used to create a [prepared statement].
+**
+** INVARIANTS:
+**
+** {F13101} If the [prepared statement] passed as
+** the an argument to [sqlite3_sql()] was compiled
+** compiled using either [sqlite3_prepare_v2()] or
+** [sqlite3_prepare16_v2()],
+** then [sqlite3_sql()] function returns a pointer to a
+** zero-terminated string containing a UTF-8 rendering
+** of the original SQL statement.
+**
+** {F13102} If the [prepared statement] passed as
+** the an argument to [sqlite3_sql()] was compiled
+** compiled using either [sqlite3_prepare()] or
+** [sqlite3_prepare16()],
+** then [sqlite3_sql()] function returns a NULL pointer.
+**
+** {F13103} The string returned by [sqlite3_sql(S)] is valid until the
+** [prepared statement] S is deleted using [sqlite3_finalize(S)].
+*/
+SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Dynamically Typed Value Object {F15000}
+** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value}
+**
+** SQLite uses the sqlite3_value object to represent all values
+** that can be stored in a database table.
+** SQLite uses dynamic typing for the values it stores.
+** Values stored in sqlite3_value objects can be
+** be integers, floating point values, strings, BLOBs, or NULL.
+**
+** An sqlite3_value object may be either "protected" or "unprotected".
+** Some interfaces require a protected sqlite3_value. Other interfaces
+** will accept either a protected or an unprotected sqlite3_value.
+** Every interface that accepts sqlite3_value arguments specifies
+** whether or not it requires a protected sqlite3_value.
+**
+** The terms "protected" and "unprotected" refer to whether or not
+** a mutex is held. A internal mutex is held for a protected
+** sqlite3_value object but no mutex is held for an unprotected
+** sqlite3_value object. If SQLite is compiled to be single-threaded
+** (with SQLITE_THREADSAFE=0 and with [sqlite3_threadsafe()] returning 0)
+** then there is no distinction between
+** protected and unprotected sqlite3_value objects and they can be
+** used interchangable. However, for maximum code portability it
+** is recommended that applications make the distinction between
+** between protected and unprotected sqlite3_value objects even if
+** they are single threaded.
+**
+** The sqlite3_value objects that are passed as parameters into the
+** implementation of application-defined SQL functions are protected.
+** The sqlite3_value object returned by
+** [sqlite3_column_value()] is unprotected.
+** Unprotected sqlite3_value objects may only be used with
+** [sqlite3_result_value()] and [sqlite3_bind_value()]. All other
+** interfaces that use sqlite3_value require protected sqlite3_value objects.
+*/
+typedef struct Mem sqlite3_value;
+
+/*
+** CAPI3REF: SQL Function Context Object {F16001}
+**
+** The context in which an SQL function executes is stored in an
+** sqlite3_context object. A pointer to an sqlite3_context
+** object is always first parameter to application-defined SQL functions.
+*/
+typedef struct sqlite3_context sqlite3_context;
+
+/*
+** CAPI3REF: Binding Values To Prepared Statements {F13500}
+**
+** In the SQL strings input to [sqlite3_prepare_v2()] and its
+** variants, literals may be replace by a parameter in one
+** of these forms:
+**
+** <ul>
+** <li> ?
+** <li> ?NNN
+** <li> :VVV
+** <li> @VVV
+** <li> $VVV
+** </ul>
+**
+** In the parameter forms shown above NNN is an integer literal,
+** VVV alpha-numeric parameter name.
+** The values of these parameters (also called "host parameter names"
+** or "SQL parameters")
+** can be set using the sqlite3_bind_*() routines defined here.
+**
+** The first argument to the sqlite3_bind_*() routines always
+** is a pointer to the [sqlite3_stmt] object returned from
+** [sqlite3_prepare_v2()] or its variants. The second
+** argument is the index of the parameter to be set. The
+** first parameter has an index of 1. When the same named
+** parameter is used more than once, second and subsequent
+** occurrences have the same index as the first occurrence.
+** The index for named parameters can be looked up using the
+** [sqlite3_bind_parameter_name()] API if desired. The index
+** for "?NNN" parameters is the value of NNN.
+** The NNN value must be between 1 and the compile-time
+** parameter SQLITE_MAX_VARIABLE_NUMBER (default value: 999).
+**
+** The third argument is the value to bind to the parameter.
+**
+** In those
+** routines that have a fourth argument, its value is the number of bytes
+** in the parameter. To be clear: the value is the number of <u>bytes</u>
+** in the value, not the number of characters.
+** If the fourth parameter is negative, the length of the string is
+** number of bytes up to the first zero terminator.
+**
+** The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and
+** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or
+** string after SQLite has finished with it. If the fifth argument is
+** the special value [SQLITE_STATIC], then SQLite assumes that the
+** information is in static, unmanaged space and does not need to be freed.
+** If the fifth argument has the value [SQLITE_TRANSIENT], then
+** SQLite makes its own private copy of the data immediately, before
+** the sqlite3_bind_*() routine returns.
+**
+** The sqlite3_bind_zeroblob() routine binds a BLOB of length N that
+** is filled with zeros. A zeroblob uses a fixed amount of memory
+** (just an integer to hold it size) while it is being processed.
+** Zeroblobs are intended to serve as place-holders for BLOBs whose
+** content is later written using
+** [sqlite3_blob_open | increment BLOB I/O] routines. A negative
+** value for the zeroblob results in a zero-length BLOB.
+**
+** The sqlite3_bind_*() routines must be called after
+** [sqlite3_prepare_v2()] (and its variants) or [sqlite3_reset()] and
+** before [sqlite3_step()].
+** Bindings are not cleared by the [sqlite3_reset()] routine.
+** Unbound parameters are interpreted as NULL.
+**
+** These routines return [SQLITE_OK] on success or an error code if
+** anything goes wrong. [SQLITE_RANGE] is returned if the parameter
+** index is out of range. [SQLITE_NOMEM] is returned if malloc fails.
+** [SQLITE_MISUSE] might be returned if these routines are called on a
+** virtual machine that is the wrong state or which has already been finalized.
+** Detection of misuse is unreliable. Applications should not depend
+** on SQLITE_MISUSE returns. SQLITE_MISUSE is intended to indicate a
+** a logic error in the application. Future versions of SQLite might
+** panic rather than return SQLITE_MISUSE.
+**
+** See also: [sqlite3_bind_parameter_count()],
+** [sqlite3_bind_parameter_name()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13506} The [sqlite3_prepare | SQL statement compiler] recognizes
+** tokens of the forms "?", "?NNN", "$VVV", ":VVV", and "@VVV"
+** as SQL parameters, where NNN is any sequence of one or more
+** digits and where VVV is any sequence of one or more
+** alphanumeric characters or "::" optionally followed by
+** a string containing no spaces and contained within parentheses.
+**
+** {F13509} The initial value of an SQL parameter is NULL.
+**
+** {F13512} The index of an "?" SQL parameter is one larger than the
+** largest index of SQL parameter to the left, or 1 if
+** the "?" is the leftmost SQL parameter.
+**
+** {F13515} The index of an "?NNN" SQL parameter is the integer NNN.
+**
+** {F13518} The index of an ":VVV", "$VVV", or "@VVV" SQL parameter is
+** the same as the index of leftmost occurances of the same
+** parameter, or one more than the largest index over all
+** parameters to the left if this is the first occurrance
+** of this parameter, or 1 if this is the leftmost parameter.
+**
+** {F13521} The [sqlite3_prepare | SQL statement compiler] fail with
+** an [SQLITE_RANGE] error if the index of an SQL parameter
+** is less than 1 or greater than SQLITE_MAX_VARIABLE_NUMBER.
+**
+** {F13524} Calls to [sqlite3_bind_text | sqlite3_bind(S,N,V,...)]
+** associate the value V with all SQL parameters having an
+** index of N in the [prepared statement] S.
+**
+** {F13527} Calls to [sqlite3_bind_text | sqlite3_bind(S,N,...)]
+** override prior calls with the same values of S and N.
+**
+** {F13530} Bindings established by [sqlite3_bind_text | sqlite3_bind(S,...)]
+** persist across calls to [sqlite3_reset(S)].
+**
+** {F13533} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] SQLite binds the first L
+** bytes of the blob or string pointed to by V, when L
+** is non-negative.
+**
+** {F13536} In calls to [sqlite3_bind_text(S,N,V,L,D)] or
+** [sqlite3_bind_text16(S,N,V,L,D)] SQLite binds characters
+** from V through the first zero character when L is negative.
+**
+** {F13539} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] when D is the special
+** constant [SQLITE_STATIC], SQLite assumes that the value V
+** is held in static unmanaged space that will not change
+** during the lifetime of the binding.
+**
+** {F13542} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] when D is the special
+** constant [SQLITE_TRANSIENT], the routine makes a
+** private copy of V value before it returns.
+**
+** {F13545} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] when D is a pointer to
+** a function, SQLite invokes that function to destroy the
+** V value after it has finished using the V value.
+**
+** {F13548} In calls to [sqlite3_bind_zeroblob(S,N,V,L)] the value bound
+** is a blob of L bytes, or a zero-length blob if L is negative.
+**
+** {F13551} In calls to [sqlite3_bind_value(S,N,V)] the V argument may
+** be either a [protected sqlite3_value] object or an
+** [unprotected sqlite3_value] object.
+*/
+SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
+SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double);
+SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int);
+SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
+SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int);
+SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
+SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
+SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
+SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
+
+/*
+** CAPI3REF: Number Of SQL Parameters {F13600}
+**
+** This routine can be used to find the number of SQL parameters
+** in a prepared statement. SQL parameters are tokens of the
+** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as
+** place-holders for values that are [sqlite3_bind_blob | bound]
+** to the parameters at a later time.
+**
+** This routine actually returns the index of the largest parameter.
+** For all forms except ?NNN, this will correspond to the number of
+** unique parameters. If parameters of the ?NNN are used, there may
+** be gaps in the list.
+**
+** See also: [sqlite3_bind_blob|sqlite3_bind()],
+** [sqlite3_bind_parameter_name()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13601} The [sqlite3_bind_parameter_count(S)] interface returns
+** the largest index of all SQL parameters in the
+** [prepared statement] S, or 0 if S
+** contains no SQL parameters.
+*/
+SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*);
+
+/*
+** CAPI3REF: Name Of A Host Parameter {F13620}
+**
+** This routine returns a pointer to the name of the n-th
+** SQL parameter in a [prepared statement].
+** SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA"
+** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA"
+** respectively.
+** In other words, the initial ":" or "$" or "@" or "?"
+** is included as part of the name.
+** Parameters of the form "?" without a following integer have no name.
+**
+** The first host parameter has an index of 1, not 0.
+**
+** If the value n is out of range or if the n-th parameter is
+** nameless, then NULL is returned. The returned string is
+** always in the UTF-8 encoding even if the named parameter was
+** originally specified as UTF-16 in [sqlite3_prepare16()] or
+** [sqlite3_prepare16_v2()].
+**
+** See also: [sqlite3_bind_blob|sqlite3_bind()],
+** [sqlite3_bind_parameter_count()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13621} The [sqlite3_bind_parameter_name(S,N)] interface returns
+** a UTF-8 rendering of the name of the SQL parameter in
+** [prepared statement] S having index N, or
+** NULL if there is no SQL parameter with index N or if the
+** parameter with index N is an anonymous parameter "?".
+*/
+SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int);
+
+/*
+** CAPI3REF: Index Of A Parameter With A Given Name {F13640}
+**
+** Return the index of an SQL parameter given its name. The
+** index value returned is suitable for use as the second
+** parameter to [sqlite3_bind_blob|sqlite3_bind()]. A zero
+** is returned if no matching parameter is found. The parameter
+** name must be given in UTF-8 even if the original statement
+** was prepared from UTF-16 text using [sqlite3_prepare16_v2()].
+**
+** See also: [sqlite3_bind_blob|sqlite3_bind()],
+** [sqlite3_bind_parameter_count()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13641} The [sqlite3_bind_parameter_index(S,N)] interface returns
+** the index of SQL parameter in [prepared statement]
+** S whose name matches the UTF-8 string N, or 0 if there is
+** no match.
+*/
+SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+
+/*
+** CAPI3REF: Reset All Bindings On A Prepared Statement {F13660}
+**
+** Contrary to the intuition of many, [sqlite3_reset()] does not
+** reset the [sqlite3_bind_blob | bindings] on a
+** [prepared statement]. Use this routine to
+** reset all host parameters to NULL.
+**
+** INVARIANTS:
+**
+** {F13661} The [sqlite3_clear_bindings(S)] interface resets all
+** SQL parameter bindings in [prepared statement] S
+** back to NULL.
+*/
+SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*);
+
+/*
+** CAPI3REF: Number Of Columns In A Result Set {F13710}
+**
+** Return the number of columns in the result set returned by the
+** [prepared statement]. This routine returns 0
+** if pStmt is an SQL statement that does not return data (for
+** example an UPDATE).
+**
+** INVARIANTS:
+**
+** {F13711} The [sqlite3_column_count(S)] interface returns the number of
+** columns in the result set generated by the
+** [prepared statement] S, or 0 if S does not generate
+** a result set.
+*/
+SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Column Names In A Result Set {F13720}
+**
+** These routines return the name assigned to a particular column
+** in the result set of a SELECT statement. The sqlite3_column_name()
+** interface returns a pointer to a zero-terminated UTF8 string
+** and sqlite3_column_name16() returns a pointer to a zero-terminated
+** UTF16 string. The first parameter is the
+** [prepared statement] that implements the SELECT statement.
+** The second parameter is the column number. The left-most column is
+** number 0.
+**
+** The returned string pointer is valid until either the
+** [prepared statement] is destroyed by [sqlite3_finalize()]
+** or until the next call sqlite3_column_name() or sqlite3_column_name16()
+** on the same column.
+**
+** If sqlite3_malloc() fails during the processing of either routine
+** (for example during a conversion from UTF-8 to UTF-16) then a
+** NULL pointer is returned.
+**
+** The name of a result column is the value of the "AS" clause for
+** that column, if there is an AS clause. If there is no AS clause
+** then the name of the column is unspecified and may change from
+** one release of SQLite to the next.
+**
+** INVARIANTS:
+**
+** {F13721} A successful invocation of the [sqlite3_column_name(S,N)]
+** interface returns the name
+** of the Nth column (where 0 is the left-most column) for the
+** result set of [prepared statement] S as a
+** zero-terminated UTF-8 string.
+**
+** {F13723} A successful invocation of the [sqlite3_column_name16(S,N)]
+** interface returns the name
+** of the Nth column (where 0 is the left-most column) for the
+** result set of [prepared statement] S as a
+** zero-terminated UTF-16 string in the native byte order.
+**
+** {F13724} The [sqlite3_column_name()] and [sqlite3_column_name16()]
+** interfaces return a NULL pointer if they are unable to
+** allocate memory memory to hold there normal return strings.
+**
+** {F13725} If the N parameter to [sqlite3_column_name(S,N)] or
+** [sqlite3_column_name16(S,N)] is out of range, then the
+** interfaces returns a NULL pointer.
+**
+** {F13726} The strings returned by [sqlite3_column_name(S,N)] and
+** [sqlite3_column_name16(S,N)] are valid until the next
+** call to either routine with the same S and N parameters
+** or until [sqlite3_finalize(S)] is called.
+**
+** {F13727} When a result column of a [SELECT] statement contains
+** an AS clause, the name of that column is the indentifier
+** to the right of the AS keyword.
+*/
+SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N);
+SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
+
+/*
+** CAPI3REF: Source Of Data In A Query Result {F13740}
+**
+** These routines provide a means to determine what column of what
+** table in which database a result of a SELECT statement comes from.
+** The name of the database or table or column can be returned as
+** either a UTF8 or UTF16 string. The _database_ routines return
+** the database name, the _table_ routines return the table name, and
+** the origin_ routines return the column name.
+** The returned string is valid until
+** the [prepared statement] is destroyed using
+** [sqlite3_finalize()] or until the same information is requested
+** again in a different encoding.
+**
+** The names returned are the original un-aliased names of the
+** database, table, and column.
+**
+** The first argument to the following calls is a [prepared statement].
+** These functions return information about the Nth column returned by
+** the statement, where N is the second function argument.
+**
+** If the Nth column returned by the statement is an expression
+** or subquery and is not a column value, then all of these functions
+** return NULL. These routine might also return NULL if a memory
+** allocation error occurs. Otherwise, they return the
+** name of the attached database, table and column that query result
+** column was extracted from.
+**
+** As with all other SQLite APIs, those postfixed with "16" return
+** UTF-16 encoded strings, the other functions return UTF-8. {END}
+**
+** These APIs are only available if the library was compiled with the
+** SQLITE_ENABLE_COLUMN_METADATA preprocessor symbol defined.
+**
+** {U13751}
+** If two or more threads call one or more of these routines against the same
+** prepared statement and column at the same time then the results are
+** undefined.
+**
+** INVARIANTS:
+**
+** {F13741} The [sqlite3_column_database_name(S,N)] interface returns either
+** the UTF-8 zero-terminated name of the database from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13742} The [sqlite3_column_database_name16(S,N)] interface returns either
+** the UTF-16 native byte order
+** zero-terminated name of the database from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13743} The [sqlite3_column_table_name(S,N)] interface returns either
+** the UTF-8 zero-terminated name of the table from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13744} The [sqlite3_column_table_name16(S,N)] interface returns either
+** the UTF-16 native byte order
+** zero-terminated name of the table from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13745} The [sqlite3_column_origin_name(S,N)] interface returns either
+** the UTF-8 zero-terminated name of the table column from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13746} The [sqlite3_column_origin_name16(S,N)] interface returns either
+** the UTF-16 native byte order
+** zero-terminated name of the table column from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13748} The return values from
+** [sqlite3_column_database_name|column metadata interfaces]
+** are valid
+** for the lifetime of the [prepared statement]
+** or until the encoding is changed by another metadata
+** interface call for the same prepared statement and column.
+**
+** LIMITATIONS:
+**
+** {U13751} If two or more threads call one or more
+** [sqlite3_column_database_name|column metadata interfaces]
+** the same [prepared statement] and result column
+** at the same time then the results are undefined.
+*/
+SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int);
+SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int);
+SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int);
+SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int);
+SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int);
+SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int);
+
+/*
+** CAPI3REF: Declared Datatype Of A Query Result {F13760}
+**
+** The first parameter is a [prepared statement].
+** If this statement is a SELECT statement and the Nth column of the
+** returned result set of that SELECT is a table column (not an
+** expression or subquery) then the declared type of the table
+** column is returned. If the Nth column of the result set is an
+** expression or subquery, then a NULL pointer is returned.
+** The returned string is always UTF-8 encoded. {END}
+** For example, in the database schema:
+**
+** CREATE TABLE t1(c1 VARIANT);
+**
+** And the following statement compiled:
+**
+** SELECT c1 + 1, c1 FROM t1;
+**
+** Then this routine would return the string "VARIANT" for the second
+** result column (i==1), and a NULL pointer for the first result column
+** (i==0).
+**
+** SQLite uses dynamic run-time typing. So just because a column
+** is declared to contain a particular type does not mean that the
+** data stored in that column is of the declared type. SQLite is
+** strongly typed, but the typing is dynamic not static. Type
+** is associated with individual values, not with the containers
+** used to hold those values.
+**
+** INVARIANTS:
+**
+** {F13761} A successful call to [sqlite3_column_decltype(S,N)]
+** returns a zero-terminated UTF-8 string containing the
+** the declared datatype of the table column that appears
+** as the Nth column (numbered from 0) of the result set to the
+** [prepared statement] S.
+**
+** {F13762} A successful call to [sqlite3_column_decltype16(S,N)]
+** returns a zero-terminated UTF-16 native byte order string
+** containing the declared datatype of the table column that appears
+** as the Nth column (numbered from 0) of the result set to the
+** [prepared statement] S.
+**
+** {F13763} If N is less than 0 or N is greater than or equal to
+** the number of columns in [prepared statement] S
+** or if the Nth column of S is an expression or subquery rather
+** than a table column or if a memory allocation failure
+** occurs during encoding conversions, then
+** calls to [sqlite3_column_decltype(S,N)] or
+** [sqlite3_column_decltype16(S,N)] return NULL.
+*/
+SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int);
+SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
+
+/*
+** CAPI3REF: Evaluate An SQL Statement {F13200}
+**
+** After an [prepared statement] has been prepared with a call
+** to either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or to one of
+** the legacy interfaces [sqlite3_prepare()] or [sqlite3_prepare16()],
+** then this function must be called one or more times to evaluate the
+** statement.
+**
+** The details of the behavior of this sqlite3_step() interface depend
+** on whether the statement was prepared using the newer "v2" interface
+** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy
+** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the
+** new "v2" interface is recommended for new applications but the legacy
+** interface will continue to be supported.
+**
+** In the legacy interface, the return value will be either [SQLITE_BUSY],
+** [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE].
+** With the "v2" interface, any of the other [SQLITE_OK | result code]
+** or [SQLITE_IOERR_READ | extended result code] might be returned as
+** well.
+**
+** [SQLITE_BUSY] means that the database engine was unable to acquire the
+** database locks it needs to do its job. If the statement is a COMMIT
+** or occurs outside of an explicit transaction, then you can retry the
+** statement. If the statement is not a COMMIT and occurs within a
+** explicit transaction then you should rollback the transaction before
+** continuing.
+**
+** [SQLITE_DONE] means that the statement has finished executing
+** successfully. sqlite3_step() should not be called again on this virtual
+** machine without first calling [sqlite3_reset()] to reset the virtual
+** machine back to its initial state.
+**
+** If the SQL statement being executed returns any data, then
+** [SQLITE_ROW] is returned each time a new row of data is ready
+** for processing by the caller. The values may be accessed using
+** the [sqlite3_column_int | column access functions].
+** sqlite3_step() is called again to retrieve the next row of data.
+**
+** [SQLITE_ERROR] means that a run-time error (such as a constraint
+** violation) has occurred. sqlite3_step() should not be called again on
+** the VM. More information may be found by calling [sqlite3_errmsg()].
+** With the legacy interface, a more specific error code (example:
+** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth)
+** can be obtained by calling [sqlite3_reset()] on the
+** [prepared statement]. In the "v2" interface,
+** the more specific error code is returned directly by sqlite3_step().
+**
+** [SQLITE_MISUSE] means that the this routine was called inappropriately.
+** Perhaps it was called on a [prepared statement] that has
+** already been [sqlite3_finalize | finalized] or on one that had
+** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could
+** be the case that the same database connection is being used by two or
+** more threads at the same moment in time.
+**
+** <b>Goofy Interface Alert:</b>
+** In the legacy interface,
+** the sqlite3_step() API always returns a generic error code,
+** [SQLITE_ERROR], following any error other than [SQLITE_BUSY]
+** and [SQLITE_MISUSE]. You must call [sqlite3_reset()] or
+** [sqlite3_finalize()] in order to find one of the specific
+** [error codes] that better describes the error.
+** We admit that this is a goofy design. The problem has been fixed
+** with the "v2" interface. If you prepare all of your SQL statements
+** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead
+** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()], then the
+** more specific [error codes] are returned directly
+** by sqlite3_step(). The use of the "v2" interface is recommended.
+**
+** INVARIANTS:
+**
+** {F13202} If [prepared statement] S is ready to be
+** run, then [sqlite3_step(S)] advances that prepared statement
+** until to completion or until it is ready to return another
+** row of the result set or an interrupt or run-time error occurs.
+**
+** {F15304} When a call to [sqlite3_step(S)] causes the
+** [prepared statement] S to run to completion,
+** the function returns [SQLITE_DONE].
+**
+** {F15306} When a call to [sqlite3_step(S)] stops because it is ready
+** to return another row of the result set, it returns
+** [SQLITE_ROW].
+**
+** {F15308} If a call to [sqlite3_step(S)] encounters an
+** [sqlite3_interrupt|interrupt] or a run-time error,
+** it returns an appropraite error code that is not one of
+** [SQLITE_OK], [SQLITE_ROW], or [SQLITE_DONE].
+**
+** {F15310} If an [sqlite3_interrupt|interrupt] or run-time error
+** occurs during a call to [sqlite3_step(S)]
+** for a [prepared statement] S created using
+** legacy interfaces [sqlite3_prepare()] or
+** [sqlite3_prepare16()] then the function returns either
+** [SQLITE_ERROR], [SQLITE_BUSY], or [SQLITE_MISUSE].
+*/
+SQLITE_API int sqlite3_step(sqlite3_stmt*);
+
+/*
+** CAPI3REF: Number of columns in a result set {F13770}
+**
+** Return the number of values in the current row of the result set.
+**
+** INVARIANTS:
+**
+** {F13771} After a call to [sqlite3_step(S)] that returns
+** [SQLITE_ROW], the [sqlite3_data_count(S)] routine
+** will return the same value as the
+** [sqlite3_column_count(S)] function.
+**
+** {F13772} After [sqlite3_step(S)] has returned any value other than
+** [SQLITE_ROW] or before [sqlite3_step(S)] has been
+** called on the [prepared statement] for
+** the first time since it was [sqlite3_prepare|prepared]
+** or [sqlite3_reset|reset], the [sqlite3_data_count(S)]
+** routine returns zero.
+*/
+SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Fundamental Datatypes {F10265}
+** KEYWORDS: SQLITE_TEXT
+**
+** {F10266}Every value in SQLite has one of five fundamental datatypes:
+**
+** <ul>
+** <li> 64-bit signed integer
+** <li> 64-bit IEEE floating point number
+** <li> string
+** <li> BLOB
+** <li> NULL
+** </ul> {END}
+**
+** These constants are codes for each of those types.
+**
+** Note that the SQLITE_TEXT constant was also used in SQLite version 2
+** for a completely different meaning. Software that links against both
+** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT not
+** SQLITE_TEXT.
+*/
+#define SQLITE_INTEGER 1
+#define SQLITE_FLOAT 2
+#define SQLITE_BLOB 4
+#define SQLITE_NULL 5
+#ifdef SQLITE_TEXT
+# undef SQLITE_TEXT
+#else
+# define SQLITE_TEXT 3
+#endif
+#define SQLITE3_TEXT 3
+
+/*
+** CAPI3REF: Results Values From A Query {F13800}
+**
+** These routines form the "result set query" interface.
+**
+** These routines return information about
+** a single column of the current result row of a query. In every
+** case the first argument is a pointer to the
+** [prepared statement] that is being
+** evaluated (the [sqlite3_stmt*] that was returned from
+** [sqlite3_prepare_v2()] or one of its variants) and
+** the second argument is the index of the column for which information
+** should be returned. The left-most column of the result set
+** has an index of 0.
+**
+** If the SQL statement is not currently point to a valid row, or if the
+** the column index is out of range, the result is undefined.
+** These routines may only be called when the most recent call to
+** [sqlite3_step()] has returned [SQLITE_ROW] and neither
+** [sqlite3_reset()] nor [sqlite3_finalize()] has been call subsequently.
+** If any of these routines are called after [sqlite3_reset()] or
+** [sqlite3_finalize()] or after [sqlite3_step()] has returned
+** something other than [SQLITE_ROW], the results are undefined.
+** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()]
+** are called from a different thread while any of these routines
+** are pending, then the results are undefined.
+**
+** The sqlite3_column_type() routine returns
+** [SQLITE_INTEGER | datatype code] for the initial data type
+** of the result column. The returned value is one of [SQLITE_INTEGER],
+** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value
+** returned by sqlite3_column_type() is only meaningful if no type
+** conversions have occurred as described below. After a type conversion,
+** the value returned by sqlite3_column_type() is undefined. Future
+** versions of SQLite may change the behavior of sqlite3_column_type()
+** following a type conversion.
+**
+** If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes()
+** routine returns the number of bytes in that BLOB or string.
+** If the result is a UTF-16 string, then sqlite3_column_bytes() converts
+** the string to UTF-8 and then returns the number of bytes.
+** If the result is a numeric value then sqlite3_column_bytes() uses
+** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns
+** the number of bytes in that string.
+** The value returned does not include the zero terminator at the end
+** of the string. For clarity: the value returned is the number of
+** bytes in the string, not the number of characters.
+**
+** Strings returned by sqlite3_column_text() and sqlite3_column_text16(),
+** even empty strings, are always zero terminated. The return
+** value from sqlite3_column_blob() for a zero-length blob is an arbitrary
+** pointer, possibly even a NULL pointer.
+**
+** The sqlite3_column_bytes16() routine is similar to sqlite3_column_bytes()
+** but leaves the result in UTF-16 in native byte order instead of UTF-8.
+** The zero terminator is not included in this count.
+**
+** The object returned by [sqlite3_column_value()] is an
+** [unprotected sqlite3_value] object. An unprotected sqlite3_value object
+** may only be used with [sqlite3_bind_value()] and [sqlite3_result_value()].
+** If the [unprotected sqlite3_value] object returned by
+** [sqlite3_column_value()] is used in any other way, including calls
+** to routines like
+** [sqlite3_value_int()], [sqlite3_value_text()], or [sqlite3_value_bytes()],
+** then the behavior is undefined.
+**
+** These routines attempt to convert the value where appropriate. For
+** example, if the internal representation is FLOAT and a text result
+** is requested, [sqlite3_snprintf()] is used internally to do the conversion
+** automatically. The following table details the conversions that
+** are applied:
+**
+** <blockquote>
+** <table border="1">
+** <tr><th> Internal<br>Type <th> Requested<br>Type <th> Conversion
+**
+** <tr><td> NULL <td> INTEGER <td> Result is 0
+** <tr><td> NULL <td> FLOAT <td> Result is 0.0
+** <tr><td> NULL <td> TEXT <td> Result is NULL pointer
+** <tr><td> NULL <td> BLOB <td> Result is NULL pointer
+** <tr><td> INTEGER <td> FLOAT <td> Convert from integer to float
+** <tr><td> INTEGER <td> TEXT <td> ASCII rendering of the integer
+** <tr><td> INTEGER <td> BLOB <td> Same as for INTEGER->TEXT
+** <tr><td> FLOAT <td> INTEGER <td> Convert from float to integer
+** <tr><td> FLOAT <td> TEXT <td> ASCII rendering of the float
+** <tr><td> FLOAT <td> BLOB <td> Same as FLOAT->TEXT
+** <tr><td> TEXT <td> INTEGER <td> Use atoi()
+** <tr><td> TEXT <td> FLOAT <td> Use atof()
+** <tr><td> TEXT <td> BLOB <td> No change
+** <tr><td> BLOB <td> INTEGER <td> Convert to TEXT then use atoi()
+** <tr><td> BLOB <td> FLOAT <td> Convert to TEXT then use atof()
+** <tr><td> BLOB <td> TEXT <td> Add a zero terminator if needed
+** </table>
+** </blockquote>
+**
+** The table above makes reference to standard C library functions atoi()
+** and atof(). SQLite does not really use these functions. It has its
+** on equavalent internal routines. The atoi() and atof() names are
+** used in the table for brevity and because they are familiar to most
+** C programmers.
+**
+** Note that when type conversions occur, pointers returned by prior
+** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or
+** sqlite3_column_text16() may be invalidated.
+** Type conversions and pointer invalidations might occur
+** in the following cases:
+**
+** <ul>
+** <li><p> The initial content is a BLOB and sqlite3_column_text()
+** or sqlite3_column_text16() is called. A zero-terminator might
+** need to be added to the string.</p></li>
+**
+** <li><p> The initial content is UTF-8 text and sqlite3_column_bytes16() or
+** sqlite3_column_text16() is called. The content must be converted
+** to UTF-16.</p></li>
+**
+** <li><p> The initial content is UTF-16 text and sqlite3_column_bytes() or
+** sqlite3_column_text() is called. The content must be converted
+** to UTF-8.</p></li>
+** </ul>
+**
+** Conversions between UTF-16be and UTF-16le are always done in place and do
+** not invalidate a prior pointer, though of course the content of the buffer
+** that the prior pointer points to will have been modified. Other kinds
+** of conversion are done in place when it is possible, but sometime it is
+** not possible and in those cases prior pointers are invalidated.
+**
+** The safest and easiest to remember policy is to invoke these routines
+** in one of the following ways:
+**
+** <ul>
+** <li>sqlite3_column_text() followed by sqlite3_column_bytes()</li>
+** <li>sqlite3_column_blob() followed by sqlite3_column_bytes()</li>
+** <li>sqlite3_column_text16() followed by sqlite3_column_bytes16()</li>
+** </ul>
+**
+** In other words, you should call sqlite3_column_text(), sqlite3_column_blob(),
+** or sqlite3_column_text16() first to force the result into the desired
+** format, then invoke sqlite3_column_bytes() or sqlite3_column_bytes16() to
+** find the size of the result. Do not mix call to sqlite3_column_text() or
+** sqlite3_column_blob() with calls to sqlite3_column_bytes16(). And do not
+** mix calls to sqlite3_column_text16() with calls to sqlite3_column_bytes().
+**
+** The pointers returned are valid until a type conversion occurs as
+** described above, or until [sqlite3_step()] or [sqlite3_reset()] or
+** [sqlite3_finalize()] is called. The memory space used to hold strings
+** and blobs is freed automatically. Do <b>not</b> pass the pointers returned
+** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into
+** [sqlite3_free()].
+**
+** If a memory allocation error occurs during the evaluation of any
+** of these routines, a default value is returned. The default value
+** is either the integer 0, the floating point number 0.0, or a NULL
+** pointer. Subsequent calls to [sqlite3_errcode()] will return
+** [SQLITE_NOMEM].
+**
+** INVARIANTS:
+**
+** {F13803} The [sqlite3_column_blob(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a blob and then returns a
+** pointer to the converted value.
+**
+** {F13806} The [sqlite3_column_bytes(S,N)] interface returns the
+** number of bytes in the blob or string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_column_blob(S,N)] or
+** [sqlite3_column_text(S,N)].
+**
+** {F13809} The [sqlite3_column_bytes16(S,N)] interface returns the
+** number of bytes in the string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_column_text16(S,N)].
+**
+** {F13812} The [sqlite3_column_double(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a floating point value and
+** returns a copy of that value.
+**
+** {F13815} The [sqlite3_column_int(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a 64-bit signed integer and
+** returns the lower 32 bits of that integer.
+**
+** {F13818} The [sqlite3_column_int64(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a 64-bit signed integer and
+** returns a copy of that integer.
+**
+** {F13821} The [sqlite3_column_text(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a zero-terminated UTF-8
+** string and returns a pointer to that string.
+**
+** {F13824} The [sqlite3_column_text16(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a zero-terminated 2-byte
+** aligned UTF-16 native byte order
+** string and returns a pointer to that string.
+**
+** {F13827} The [sqlite3_column_type(S,N)] interface returns
+** one of [SQLITE_NULL], [SQLITE_INTEGER], [SQLITE_FLOAT],
+** [SQLITE_TEXT], or [SQLITE_BLOB] as appropriate for
+** the Nth column in the current row of the result set for
+** [prepared statement] S.
+**
+** {F13830} The [sqlite3_column_value(S,N)] interface returns a
+** pointer to an [unprotected sqlite3_value] object for the
+** Nth column in the current row of the result set for
+** [prepared statement] S.
+*/
+SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
+SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
+SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
+SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol);
+SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol);
+SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
+SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
+SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
+SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol);
+SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
+
+/*
+** CAPI3REF: Destroy A Prepared Statement Object {F13300}
+**
+** The sqlite3_finalize() function is called to delete a
+** [prepared statement]. If the statement was
+** executed successfully, or not executed at all, then SQLITE_OK is returned.
+** If execution of the statement failed then an
+** [error code] or [extended error code]
+** is returned.
+**
+** This routine can be called at any point during the execution of the
+** [prepared statement]. If the virtual machine has not
+** completed execution when this routine is called, that is like
+** encountering an error or an interrupt. (See [sqlite3_interrupt()].)
+** Incomplete updates may be rolled back and transactions cancelled,
+** depending on the circumstances, and the
+** [error code] returned will be [SQLITE_ABORT].
+**
+** INVARIANTS:
+**
+** {F11302} The [sqlite3_finalize(S)] interface destroys the
+** [prepared statement] S and releases all
+** memory and file resources held by that object.
+**
+** {F11304} If the most recent call to [sqlite3_step(S)] for the
+** [prepared statement] S returned an error,
+** then [sqlite3_finalize(S)] returns that same error.
+*/
+SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Reset A Prepared Statement Object {F13330}
+**
+** The sqlite3_reset() function is called to reset a
+** [prepared statement] object.
+** back to its initial state, ready to be re-executed.
+** Any SQL statement variables that had values bound to them using
+** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values.
+** Use [sqlite3_clear_bindings()] to reset the bindings.
+**
+** {F11332} The [sqlite3_reset(S)] interface resets the [prepared statement] S
+** back to the beginning of its program.
+**
+** {F11334} If the most recent call to [sqlite3_step(S)] for
+** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE],
+** or if [sqlite3_step(S)] has never before been called on S,
+** then [sqlite3_reset(S)] returns [SQLITE_OK].
+**
+** {F11336} If the most recent call to [sqlite3_step(S)] for
+** [prepared statement] S indicated an error, then
+** [sqlite3_reset(S)] returns an appropriate [error code].
+**
+** {F11338} The [sqlite3_reset(S)] interface does not change the values
+** of any [sqlite3_bind_blob|bindings] on [prepared statement] S.
+*/
+SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Create Or Redefine SQL Functions {F16100}
+** KEYWORDS: {function creation routines}
+**
+** These two functions (collectively known as
+** "function creation routines") are used to add SQL functions or aggregates
+** or to redefine the behavior of existing SQL functions or aggregates. The
+** difference only between the two is that the second parameter, the
+** name of the (scalar) function or aggregate, is encoded in UTF-8 for
+** sqlite3_create_function() and UTF-16 for sqlite3_create_function16().
+**
+** The first parameter is the [database connection] to which the SQL
+** function is to be added. If a single
+** program uses more than one [database connection] internally, then SQL
+** functions must be added individually to each [database connection].
+**
+** The second parameter is the name of the SQL function to be created
+** or redefined.
+** The length of the name is limited to 255 bytes, exclusive of the
+** zero-terminator. Note that the name length limit is in bytes, not
+** characters. Any attempt to create a function with a longer name
+** will result in an SQLITE_ERROR error.
+**
+** The third parameter is the number of arguments that the SQL function or
+** aggregate takes. If this parameter is negative, then the SQL function or
+** aggregate may take any number of arguments.
+**
+** The fourth parameter, eTextRep, specifies what
+** [SQLITE_UTF8 | text encoding] this SQL function prefers for
+** its parameters. Any SQL function implementation should be able to work
+** work with UTF-8, UTF-16le, or UTF-16be. But some implementations may be
+** more efficient with one encoding than another. It is allowed to
+** invoke sqlite3_create_function() or sqlite3_create_function16() multiple
+** times with the same function but with different values of eTextRep.
+** When multiple implementations of the same function are available, SQLite
+** will pick the one that involves the least amount of data conversion.
+** If there is only a single implementation which does not care what
+** text encoding is used, then the fourth argument should be
+** [SQLITE_ANY].
+**
+** The fifth parameter is an arbitrary pointer. The implementation
+** of the function can gain access to this pointer using
+** [sqlite3_user_data()].
+**
+** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are
+** pointers to C-language functions that implement the SQL
+** function or aggregate. A scalar SQL function requires an implementation of
+** the xFunc callback only, NULL pointers should be passed as the xStep
+** and xFinal parameters. An aggregate SQL function requires an implementation
+** of xStep and xFinal and NULL should be passed for xFunc. To delete an
+** existing SQL function or aggregate, pass NULL for all three function
+** callback.
+**
+** It is permitted to register multiple implementations of the same
+** functions with the same name but with either differing numbers of
+** arguments or differing perferred text encodings. SQLite will use
+** the implementation most closely matches the way in which the
+** SQL function is used.
+**
+** INVARIANTS:
+**
+** {F16103} The [sqlite3_create_function16()] interface behaves exactly
+** like [sqlite3_create_function()] in every way except that it
+** interprets the zFunctionName argument as
+** zero-terminated UTF-16 native byte order instead of as a
+** zero-terminated UTF-8.
+**
+** {F16106} A successful invocation of
+** the [sqlite3_create_function(D,X,N,E,...)] interface registers
+** or replaces callback functions in [database connection] D
+** used to implement the SQL function named X with N parameters
+** and having a perferred text encoding of E.
+**
+** {F16109} A successful call to [sqlite3_create_function(D,X,N,E,P,F,S,L)]
+** replaces the P, F, S, and L values from any prior calls with
+** the same D, X, N, and E values.
+**
+** {F16112} The [sqlite3_create_function(D,X,...)] interface fails with
+** a return code of [SQLITE_ERROR] if the SQL function name X is
+** longer than 255 bytes exclusive of the zero terminator.
+**
+** {F16118} Either F must be NULL and S and L are non-NULL or else F
+** is non-NULL and S and L are NULL, otherwise
+** [sqlite3_create_function(D,X,N,E,P,F,S,L)] returns [SQLITE_ERROR].
+**
+** {F16121} The [sqlite3_create_function(D,...)] interface fails with an
+** error code of [SQLITE_BUSY] if there exist [prepared statements]
+** associated with the [database connection] D.
+**
+** {F16124} The [sqlite3_create_function(D,X,N,...)] interface fails with an
+** error code of [SQLITE_ERROR] if parameter N (specifying the number
+** of arguments to the SQL function being registered) is less
+** than -1 or greater than 127.
+**
+** {F16127} When N is non-negative, the [sqlite3_create_function(D,X,N,...)]
+** interface causes callbacks to be invoked for the SQL function
+** named X when the number of arguments to the SQL function is
+** exactly N.
+**
+** {F16130} When N is -1, the [sqlite3_create_function(D,X,N,...)]
+** interface causes callbacks to be invoked for the SQL function
+** named X with any number of arguments.
+**
+** {F16133} When calls to [sqlite3_create_function(D,X,N,...)]
+** specify multiple implementations of the same function X
+** and when one implementation has N>=0 and the other has N=(-1)
+** the implementation with a non-zero N is preferred.
+**
+** {F16136} When calls to [sqlite3_create_function(D,X,N,E,...)]
+** specify multiple implementations of the same function X with
+** the same number of arguments N but with different
+** encodings E, then the implementation where E matches the
+** database encoding is preferred.
+**
+** {F16139} For an aggregate SQL function created using
+** [sqlite3_create_function(D,X,N,E,P,0,S,L)] the finializer
+** function L will always be invoked exactly once if the
+** step function S is called one or more times.
+**
+** {F16142} When SQLite invokes either the xFunc or xStep function of
+** an application-defined SQL function or aggregate created
+** by [sqlite3_create_function()] or [sqlite3_create_function16()],
+** then the array of [sqlite3_value] objects passed as the
+** third parameter are always [protected sqlite3_value] objects.
+*/
+SQLITE_API int sqlite3_create_function(
+ sqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+);
+SQLITE_API int sqlite3_create_function16(
+ sqlite3 *db,
+ const void *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+);
+
+/*
+** CAPI3REF: Text Encodings {F10267}
+**
+** These constant define integer codes that represent the various
+** text encodings supported by SQLite.
+*/
+#define SQLITE_UTF8 1
+#define SQLITE_UTF16LE 2
+#define SQLITE_UTF16BE 3
+#define SQLITE_UTF16 4 /* Use native byte order */
+#define SQLITE_ANY 5 /* sqlite3_create_function only */
+#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */
+
+/*
+** CAPI3REF: Obsolete Functions
+**
+** These functions are all now obsolete. In order to maintain
+** backwards compatibility with older code, we continue to support
+** these functions. However, new development projects should avoid
+** the use of these functions. To help encourage people to avoid
+** using these functions, we are not going to tell you want they do.
+*/
+SQLITE_API int sqlite3_aggregate_count(sqlite3_context*);
+SQLITE_API int sqlite3_expired(sqlite3_stmt*);
+SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*);
+SQLITE_API int sqlite3_global_recover(void);
+SQLITE_API void sqlite3_thread_cleanup(void);
+SQLITE_API int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),void*,sqlite3_int64);
+
+/*
+** CAPI3REF: Obtaining SQL Function Parameter Values {F15100}
+**
+** The C-language implementation of SQL functions and aggregates uses
+** this set of interface routines to access the parameter values on
+** the function or aggregate.
+**
+** The xFunc (for scalar functions) or xStep (for aggregates) parameters
+** to [sqlite3_create_function()] and [sqlite3_create_function16()]
+** define callbacks that implement the SQL functions and aggregates.
+** The 4th parameter to these callbacks is an array of pointers to
+** [protected sqlite3_value] objects. There is one [sqlite3_value] object for
+** each parameter to the SQL function. These routines are used to
+** extract values from the [sqlite3_value] objects.
+**
+** These routines work only with [protected sqlite3_value] objects.
+** Any attempt to use these routines on an [unprotected sqlite3_value]
+** object results in undefined behavior.
+**
+** These routines work just like the corresponding
+** [sqlite3_column_blob | sqlite3_column_* routines] except that
+** these routines take a single [protected sqlite3_value] object pointer
+** instead of an [sqlite3_stmt*] pointer and an integer column number.
+**
+** The sqlite3_value_text16() interface extracts a UTF16 string
+** in the native byte-order of the host machine. The
+** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces
+** extract UTF16 strings as big-endian and little-endian respectively.
+**
+** The sqlite3_value_numeric_type() interface attempts to apply
+** numeric affinity to the value. This means that an attempt is
+** made to convert the value to an integer or floating point. If
+** such a conversion is possible without loss of information (in other
+** words if the value is a string that looks like a number)
+** then the conversion is done. Otherwise no conversion occurs. The
+** [SQLITE_INTEGER | datatype] after conversion is returned.
+**
+** Please pay particular attention to the fact that the pointer that
+** is returned from [sqlite3_value_blob()], [sqlite3_value_text()], or
+** [sqlite3_value_text16()] can be invalidated by a subsequent call to
+** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()],
+** or [sqlite3_value_text16()].
+**
+** These routines must be called from the same thread as
+** the SQL function that supplied the [sqlite3_value*] parameters.
+**
+**
+** INVARIANTS:
+**
+** {F15103} The [sqlite3_value_blob(V)] interface converts the
+** [protected sqlite3_value] object V into a blob and then returns a
+** pointer to the converted value.
+**
+** {F15106} The [sqlite3_value_bytes(V)] interface returns the
+** number of bytes in the blob or string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_value_blob(V)] or
+** [sqlite3_value_text(V)].
+**
+** {F15109} The [sqlite3_value_bytes16(V)] interface returns the
+** number of bytes in the string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_value_text16(V)],
+** [sqlite3_value_text16be(V)], or [sqlite3_value_text16le(V)].
+**
+** {F15112} The [sqlite3_value_double(V)] interface converts the
+** [protected sqlite3_value] object V into a floating point value and
+** returns a copy of that value.
+**
+** {F15115} The [sqlite3_value_int(V)] interface converts the
+** [protected sqlite3_value] object V into a 64-bit signed integer and
+** returns the lower 32 bits of that integer.
+**
+** {F15118} The [sqlite3_value_int64(V)] interface converts the
+** [protected sqlite3_value] object V into a 64-bit signed integer and
+** returns a copy of that integer.
+**
+** {F15121} The [sqlite3_value_text(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated UTF-8
+** string and returns a pointer to that string.
+**
+** {F15124} The [sqlite3_value_text16(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated 2-byte
+** aligned UTF-16 native byte order
+** string and returns a pointer to that string.
+**
+** {F15127} The [sqlite3_value_text16be(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated 2-byte
+** aligned UTF-16 big-endian
+** string and returns a pointer to that string.
+**
+** {F15130} The [sqlite3_value_text16le(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated 2-byte
+** aligned UTF-16 little-endian
+** string and returns a pointer to that string.
+**
+** {F15133} The [sqlite3_value_type(V)] interface returns
+** one of [SQLITE_NULL], [SQLITE_INTEGER], [SQLITE_FLOAT],
+** [SQLITE_TEXT], or [SQLITE_BLOB] as appropriate for
+** the [sqlite3_value] object V.
+**
+** {F15136} The [sqlite3_value_numeric_type(V)] interface converts
+** the [protected sqlite3_value] object V into either an integer or
+** a floating point value if it can do so without loss of
+** information, and returns one of [SQLITE_NULL],
+** [SQLITE_INTEGER], [SQLITE_FLOAT], [SQLITE_TEXT], or
+** [SQLITE_BLOB] as appropriate for
+** the [protected sqlite3_value] object V after the conversion attempt.
+*/
+SQLITE_API const void *sqlite3_value_blob(sqlite3_value*);
+SQLITE_API int sqlite3_value_bytes(sqlite3_value*);
+SQLITE_API int sqlite3_value_bytes16(sqlite3_value*);
+SQLITE_API double sqlite3_value_double(sqlite3_value*);
+SQLITE_API int sqlite3_value_int(sqlite3_value*);
+SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*);
+SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*);
+SQLITE_API const void *sqlite3_value_text16(sqlite3_value*);
+SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*);
+SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*);
+SQLITE_API int sqlite3_value_type(sqlite3_value*);
+SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
+
+/*
+** CAPI3REF: Obtain Aggregate Function Context {F16210}
+**
+** The implementation of aggregate SQL functions use this routine to allocate
+** a structure for storing their state.
+** The first time the sqlite3_aggregate_context() routine is
+** is called for a particular aggregate, SQLite allocates nBytes of memory
+** zeros that memory, and returns a pointer to it.
+** On second and subsequent calls to sqlite3_aggregate_context()
+** for the same aggregate function index, the same buffer is returned.
+** The implementation
+** of the aggregate can use the returned buffer to accumulate data.
+**
+** SQLite automatically frees the allocated buffer when the aggregate
+** query concludes.
+**
+** The first parameter should be a copy of the
+** [sqlite3_context | SQL function context] that is the first
+** parameter to the callback routine that implements the aggregate
+** function.
+**
+** This routine must be called from the same thread in which
+** the aggregate SQL function is running.
+**
+** INVARIANTS:
+**
+** {F16211} The first invocation of [sqlite3_aggregate_context(C,N)] for
+** a particular instance of an aggregate function (for a particular
+** context C) causes SQLite to allocation N bytes of memory,
+** zero that memory, and return a pointer to the allocationed
+** memory.
+**
+** {F16213} If a memory allocation error occurs during
+** [sqlite3_aggregate_context(C,N)] then the function returns 0.
+**
+** {F16215} Second and subsequent invocations of
+** [sqlite3_aggregate_context(C,N)] for the same context pointer C
+** ignore the N parameter and return a pointer to the same
+** block of memory returned by the first invocation.
+**
+** {F16217} The memory allocated by [sqlite3_aggregate_context(C,N)] is
+** automatically freed on the next call to [sqlite3_reset()]
+** or [sqlite3_finalize()] for the [prepared statement] containing
+** the aggregate function associated with context C.
+*/
+SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes);
+
+/*
+** CAPI3REF: User Data For Functions {F16240}
+**
+** The sqlite3_user_data() interface returns a copy of
+** the pointer that was the pUserData parameter (the 5th parameter)
+** of the the [sqlite3_create_function()]
+** and [sqlite3_create_function16()] routines that originally
+** registered the application defined function. {END}
+**
+** This routine must be called from the same thread in which
+** the application-defined function is running.
+**
+** INVARIANTS:
+**
+** {F16243} The [sqlite3_user_data(C)] interface returns a copy of the
+** P pointer from the [sqlite3_create_function(D,X,N,E,P,F,S,L)]
+** or [sqlite3_create_function16(D,X,N,E,P,F,S,L)] call that
+** registered the SQL function associated with
+** [sqlite3_context] C.
+*/
+SQLITE_API void *sqlite3_user_data(sqlite3_context*);
+
+/*
+** CAPI3REF: Database Connection For Functions {F16250}
+**
+** The sqlite3_context_db_handle() interface returns a copy of
+** the pointer to the [database connection] (the 1st parameter)
+** of the the [sqlite3_create_function()]
+** and [sqlite3_create_function16()] routines that originally
+** registered the application defined function.
+**
+** INVARIANTS:
+**
+** {F16253} The [sqlite3_context_db_handle(C)] interface returns a copy of the
+** D pointer from the [sqlite3_create_function(D,X,N,E,P,F,S,L)]
+** or [sqlite3_create_function16(D,X,N,E,P,F,S,L)] call that
+** registered the SQL function associated with
+** [sqlite3_context] C.
+*/
+SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
+
+/*
+** CAPI3REF: Function Auxiliary Data {F16270}
+**
+** The following two functions may be used by scalar SQL functions to
+** associate meta-data with argument values. If the same value is passed to
+** multiple invocations of the same SQL function during query execution, under
+** some circumstances the associated meta-data may be preserved. This may
+** be used, for example, to add a regular-expression matching scalar
+** function. The compiled version of the regular expression is stored as
+** meta-data associated with the SQL value passed as the regular expression
+** pattern. The compiled regular expression can be reused on multiple
+** invocations of the same function so that the original pattern string
+** does not need to be recompiled on each invocation.
+**
+** The sqlite3_get_auxdata() interface returns a pointer to the meta-data
+** associated by the sqlite3_set_auxdata() function with the Nth argument
+** value to the application-defined function.
+** If no meta-data has been ever been set for the Nth
+** argument of the function, or if the cooresponding function parameter
+** has changed since the meta-data was set, then sqlite3_get_auxdata()
+** returns a NULL pointer.
+**
+** The sqlite3_set_auxdata() interface saves the meta-data
+** pointed to by its 3rd parameter as the meta-data for the N-th
+** argument of the application-defined function. Subsequent
+** calls to sqlite3_get_auxdata() might return this data, if it has
+** not been destroyed.
+** If it is not NULL, SQLite will invoke the destructor
+** function given by the 4th parameter to sqlite3_set_auxdata() on
+** the meta-data when the corresponding function parameter changes
+** or when the SQL statement completes, whichever comes first.
+**
+** SQLite is free to call the destructor and drop meta-data on
+** any parameter of any function at any time. The only guarantee
+** is that the destructor will be called before the metadata is
+** dropped.
+**
+** In practice, meta-data is preserved between function calls for
+** expressions that are constant at compile time. This includes literal
+** values and SQL variables.
+**
+** These routines must be called from the same thread in which
+** the SQL function is running.
+**
+** INVARIANTS:
+**
+** {F16272} The [sqlite3_get_auxdata(C,N)] interface returns a pointer
+** to metadata associated with the Nth parameter of the SQL function
+** whose context is C, or NULL if there is no metadata associated
+** with that parameter.
+**
+** {F16274} The [sqlite3_set_auxdata(C,N,P,D)] interface assigns a metadata
+** pointer P to the Nth parameter of the SQL function with context
+** C.
+**
+** {F16276} SQLite will invoke the destructor D with a single argument
+** which is the metadata pointer P following a call to
+** [sqlite3_set_auxdata(C,N,P,D)] when SQLite ceases to hold
+** the metadata.
+**
+** {F16277} SQLite ceases to hold metadata for an SQL function parameter
+** when the value of that parameter changes.
+**
+** {F16278} When [sqlite3_set_auxdata(C,N,P,D)] is invoked, the destructor
+** is called for any prior metadata associated with the same function
+** context C and parameter N.
+**
+** {F16279} SQLite will call destructors for any metadata it is holding
+** in a particular [prepared statement] S when either
+** [sqlite3_reset(S)] or [sqlite3_finalize(S)] is called.
+*/
+SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N);
+SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*));
+
+
+/*
+** CAPI3REF: Constants Defining Special Destructor Behavior {F10280}
+**
+** These are special value for the destructor that is passed in as the
+** final argument to routines like [sqlite3_result_blob()]. If the destructor
+** argument is SQLITE_STATIC, it means that the content pointer is constant
+** and will never change. It does not need to be destroyed. The
+** SQLITE_TRANSIENT value means that the content will likely change in
+** the near future and that SQLite should make its own private copy of
+** the content before returning.
+**
+** The typedef is necessary to work around problems in certain
+** C++ compilers. See ticket #2191.
+*/
+typedef void (*sqlite3_destructor_type)(void*);
+#define SQLITE_STATIC ((sqlite3_destructor_type)0)
+#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
+
+/*
+** CAPI3REF: Setting The Result Of An SQL Function {F16400}
+**
+** These routines are used by the xFunc or xFinal callbacks that
+** implement SQL functions and aggregates. See
+** [sqlite3_create_function()] and [sqlite3_create_function16()]
+** for additional information.
+**
+** These functions work very much like the
+** [sqlite3_bind_blob | sqlite3_bind_*] family of functions used
+** to bind values to host parameters in prepared statements.
+** Refer to the
+** [sqlite3_bind_blob | sqlite3_bind_* documentation] for
+** additional information.
+**
+** The sqlite3_result_blob() interface sets the result from
+** an application defined function to be the BLOB whose content is pointed
+** to by the second parameter and which is N bytes long where N is the
+** third parameter.
+** The sqlite3_result_zeroblob() inerfaces set the result of
+** the application defined function to be a BLOB containing all zero
+** bytes and N bytes in size, where N is the value of the 2nd parameter.
+**
+** The sqlite3_result_double() interface sets the result from
+** an application defined function to be a floating point value specified
+** by its 2nd argument.
+**
+** The sqlite3_result_error() and sqlite3_result_error16() functions
+** cause the implemented SQL function to throw an exception.
+** SQLite uses the string pointed to by the
+** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16()
+** as the text of an error message. SQLite interprets the error
+** message string from sqlite3_result_error() as UTF8. SQLite
+** interprets the string from sqlite3_result_error16() as UTF16 in native
+** byte order. If the third parameter to sqlite3_result_error()
+** or sqlite3_result_error16() is negative then SQLite takes as the error
+** message all text up through the first zero character.
+** If the third parameter to sqlite3_result_error() or
+** sqlite3_result_error16() is non-negative then SQLite takes that many
+** bytes (not characters) from the 2nd parameter as the error message.
+** The sqlite3_result_error() and sqlite3_result_error16()
+** routines make a copy private copy of the error message text before
+** they return. Hence, the calling function can deallocate or
+** modify the text after they return without harm.
+** The sqlite3_result_error_code() function changes the error code
+** returned by SQLite as a result of an error in a function. By default,
+** the error code is SQLITE_ERROR. A subsequent call to sqlite3_result_error()
+** or sqlite3_result_error16() resets the error code to SQLITE_ERROR.
+**
+** The sqlite3_result_toobig() interface causes SQLite
+** to throw an error indicating that a string or BLOB is to long
+** to represent. The sqlite3_result_nomem() interface
+** causes SQLite to throw an exception indicating that the a
+** memory allocation failed.
+**
+** The sqlite3_result_int() interface sets the return value
+** of the application-defined function to be the 32-bit signed integer
+** value given in the 2nd argument.
+** The sqlite3_result_int64() interface sets the return value
+** of the application-defined function to be the 64-bit signed integer
+** value given in the 2nd argument.
+**
+** The sqlite3_result_null() interface sets the return value
+** of the application-defined function to be NULL.
+**
+** The sqlite3_result_text(), sqlite3_result_text16(),
+** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces
+** set the return value of the application-defined function to be
+** a text string which is represented as UTF-8, UTF-16 native byte order,
+** UTF-16 little endian, or UTF-16 big endian, respectively.
+** SQLite takes the text result from the application from
+** the 2nd parameter of the sqlite3_result_text* interfaces.
+** If the 3rd parameter to the sqlite3_result_text* interfaces
+** is negative, then SQLite takes result text from the 2nd parameter
+** through the first zero character.
+** If the 3rd parameter to the sqlite3_result_text* interfaces
+** is non-negative, then as many bytes (not characters) of the text
+** pointed to by the 2nd parameter are taken as the application-defined
+** function result.
+** If the 4th parameter to the sqlite3_result_text* interfaces
+** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that
+** function as the destructor on the text or blob result when it has
+** finished using that result.
+** If the 4th parameter to the sqlite3_result_text* interfaces
+** or sqlite3_result_blob is the special constant SQLITE_STATIC, then
+** SQLite assumes that the text or blob result is constant space and
+** does not copy the space or call a destructor when it has
+** finished using that result.
+** If the 4th parameter to the sqlite3_result_text* interfaces
+** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT
+** then SQLite makes a copy of the result into space obtained from
+** from [sqlite3_malloc()] before it returns.
+**
+** The sqlite3_result_value() interface sets the result of
+** the application-defined function to be a copy the
+** [unprotected sqlite3_value] object specified by the 2nd parameter. The
+** sqlite3_result_value() interface makes a copy of the [sqlite3_value]
+** so that [sqlite3_value] specified in the parameter may change or
+** be deallocated after sqlite3_result_value() returns without harm.
+** A [protected sqlite3_value] object may always be used where an
+** [unprotected sqlite3_value] object is required, so either
+** kind of [sqlite3_value] object can be used with this interface.
+**
+** If these routines are called from within the different thread
+** than the one containing the application-defined function that recieved
+** the [sqlite3_context] pointer, the results are undefined.
+**
+** INVARIANTS:
+**
+** {F16403} The default return value from any SQL function is NULL.
+**
+** {F16406} The [sqlite3_result_blob(C,V,N,D)] interface changes the
+** return value of function C to be a blob that is N bytes
+** in length and with content pointed to by V.
+**
+** {F16409} The [sqlite3_result_double(C,V)] interface changes the
+** return value of function C to be the floating point value V.
+**
+** {F16412} The [sqlite3_result_error(C,V,N)] interface changes the return
+** value of function C to be an exception with error code
+** [SQLITE_ERROR] and a UTF8 error message copied from V up to the
+** first zero byte or until N bytes are read if N is positive.
+**
+** {F16415} The [sqlite3_result_error16(C,V,N)] interface changes the return
+** value of function C to be an exception with error code
+** [SQLITE_ERROR] and a UTF16 native byte order error message
+** copied from V up to the first zero terminator or until N bytes
+** are read if N is positive.
+**
+** {F16418} The [sqlite3_result_error_toobig(C)] interface changes the return
+** value of the function C to be an exception with error code
+** [SQLITE_TOOBIG] and an appropriate error message.
+**
+** {F16421} The [sqlite3_result_error_nomem(C)] interface changes the return
+** value of the function C to be an exception with error code
+** [SQLITE_NOMEM] and an appropriate error message.
+**
+** {F16424} The [sqlite3_result_error_code(C,E)] interface changes the return
+** value of the function C to be an exception with error code E.
+** The error message text is unchanged.
+**
+** {F16427} The [sqlite3_result_int(C,V)] interface changes the
+** return value of function C to be the 32-bit integer value V.
+**
+** {F16430} The [sqlite3_result_int64(C,V)] interface changes the
+** return value of function C to be the 64-bit integer value V.
+**
+** {F16433} The [sqlite3_result_null(C)] interface changes the
+** return value of function C to be NULL.
+**
+** {F16436} The [sqlite3_result_text(C,V,N,D)] interface changes the
+** return value of function C to be the UTF8 string
+** V up to the first zero if N is negative
+** or the first N bytes of V if N is non-negative.
+**
+** {F16439} The [sqlite3_result_text16(C,V,N,D)] interface changes the
+** return value of function C to be the UTF16 native byte order
+** string V up to the first zero if N is
+** negative or the first N bytes of V if N is non-negative.
+**
+** {F16442} The [sqlite3_result_text16be(C,V,N,D)] interface changes the
+** return value of function C to be the UTF16 big-endian
+** string V up to the first zero if N is
+** is negative or the first N bytes or V if N is non-negative.
+**
+** {F16445} The [sqlite3_result_text16le(C,V,N,D)] interface changes the
+** return value of function C to be the UTF16 little-endian
+** string V up to the first zero if N is
+** negative or the first N bytes of V if N is non-negative.
+**
+** {F16448} The [sqlite3_result_value(C,V)] interface changes the
+** return value of function C to be [unprotected sqlite3_value]
+** object V.
+**
+** {F16451} The [sqlite3_result_zeroblob(C,N)] interface changes the
+** return value of function C to be an N-byte blob of all zeros.
+**
+** {F16454} The [sqlite3_result_error()] and [sqlite3_result_error16()]
+** interfaces make a copy of their error message strings before
+** returning.
+**
+** {F16457} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)],
+** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)],
+** [sqlite3_result_text16be(C,V,N,D)], or
+** [sqlite3_result_text16le(C,V,N,D)] is the constant [SQLITE_STATIC]
+** then no destructor is ever called on the pointer V and SQLite
+** assumes that V is immutable.
+**
+** {F16460} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)],
+** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)],
+** [sqlite3_result_text16be(C,V,N,D)], or
+** [sqlite3_result_text16le(C,V,N,D)] is the constant
+** [SQLITE_TRANSIENT] then the interfaces makes a copy of the
+** content of V and retains the copy.
+**
+** {F16463} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)],
+** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)],
+** [sqlite3_result_text16be(C,V,N,D)], or
+** [sqlite3_result_text16le(C,V,N,D)] is some value other than
+** the constants [SQLITE_STATIC] and [SQLITE_TRANSIENT] then
+** SQLite will invoke the destructor D with V as its only argument
+** when it has finished with the V value.
+*/
+SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*));
+SQLITE_API void sqlite3_result_double(sqlite3_context*, double);
+SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int);
+SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int);
+SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*);
+SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*);
+SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int);
+SQLITE_API void sqlite3_result_int(sqlite3_context*, int);
+SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64);
+SQLITE_API void sqlite3_result_null(sqlite3_context*);
+SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*));
+SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*));
+SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*));
+SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*));
+SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
+SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n);
+
+/*
+** CAPI3REF: Define New Collating Sequences {F16600}
+**
+** These functions are used to add new collation sequences to the
+** [sqlite3*] handle specified as the first argument.
+**
+** The name of the new collation sequence is specified as a UTF-8 string
+** for sqlite3_create_collation() and sqlite3_create_collation_v2()
+** and a UTF-16 string for sqlite3_create_collation16(). In all cases
+** the name is passed as the second function argument.
+**
+** The third argument may be one of the constants [SQLITE_UTF8],
+** [SQLITE_UTF16LE] or [SQLITE_UTF16BE], indicating that the user-supplied
+** routine expects to be passed pointers to strings encoded using UTF-8,
+** UTF-16 little-endian or UTF-16 big-endian respectively. The
+** third argument might also be [SQLITE_UTF16_ALIGNED] to indicate that
+** the routine expects pointers to 16-bit word aligned strings
+** of UTF16 in the native byte order of the host computer.
+**
+** A pointer to the user supplied routine must be passed as the fifth
+** argument. If it is NULL, this is the same as deleting the collation
+** sequence (so that SQLite cannot call it anymore).
+** Each time the application
+** supplied function is invoked, it is passed a copy of the void* passed as
+** the fourth argument to sqlite3_create_collation() or
+** sqlite3_create_collation16() as its first parameter.
+**
+** The remaining arguments to the application-supplied routine are two strings,
+** each represented by a (length, data) pair and encoded in the encoding
+** that was passed as the third argument when the collation sequence was
+** registered. {END} The application defined collation routine should
+** return negative, zero or positive if
+** the first string is less than, equal to, or greater than the second
+** string. i.e. (STRING1 - STRING2).
+**
+** The sqlite3_create_collation_v2() works like sqlite3_create_collation()
+** excapt that it takes an extra argument which is a destructor for
+** the collation. The destructor is called when the collation is
+** destroyed and is passed a copy of the fourth parameter void* pointer
+** of the sqlite3_create_collation_v2().
+** Collations are destroyed when
+** they are overridden by later calls to the collation creation functions
+** or when the [sqlite3*] database handle is closed using [sqlite3_close()].
+**
+** INVARIANTS:
+**
+** {F16603} A successful call to the
+** [sqlite3_create_collation_v2(B,X,E,P,F,D)] interface
+** registers function F as the comparison function used to
+** implement collation X on [database connection] B for
+** databases having encoding E.
+**
+** {F16604} SQLite understands the X parameter to
+** [sqlite3_create_collation_v2(B,X,E,P,F,D)] as a zero-terminated
+** UTF-8 string in which case is ignored for ASCII characters and
+** is significant for non-ASCII characters.
+**
+** {F16606} Successive calls to [sqlite3_create_collation_v2(B,X,E,P,F,D)]
+** with the same values for B, X, and E, override prior values
+** of P, F, and D.
+**
+** {F16609} The destructor D in [sqlite3_create_collation_v2(B,X,E,P,F,D)]
+** is not NULL then it is called with argument P when the
+** collating function is dropped by SQLite.
+**
+** {F16612} A collating function is dropped when it is overloaded.
+**
+** {F16615} A collating function is dropped when the database connection
+** is closed using [sqlite3_close()].
+**
+** {F16618} The pointer P in [sqlite3_create_collation_v2(B,X,E,P,F,D)]
+** is passed through as the first parameter to the comparison
+** function F for all subsequent invocations of F.
+**
+** {F16621} A call to [sqlite3_create_collation(B,X,E,P,F)] is exactly
+** the same as a call to [sqlite3_create_collation_v2()] with
+** the same parameters and a NULL destructor.
+**
+** {F16624} Following a [sqlite3_create_collation_v2(B,X,E,P,F,D)],
+** SQLite uses the comparison function F for all text comparison
+** operations on [database connection] B on text values that
+** use the collating sequence name X.
+**
+** {F16627} The [sqlite3_create_collation16(B,X,E,P,F)] works the same
+** as [sqlite3_create_collation(B,X,E,P,F)] except that the
+** collation name X is understood as UTF-16 in native byte order
+** instead of UTF-8.
+**
+** {F16630} When multiple comparison functions are available for the same
+** collating sequence, SQLite chooses the one whose text encoding
+** requires the least amount of conversion from the default
+** text encoding of the database.
+*/
+SQLITE_API int sqlite3_create_collation(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+);
+SQLITE_API int sqlite3_create_collation_v2(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*),
+ void(*xDestroy)(void*)
+);
+SQLITE_API int sqlite3_create_collation16(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+);
+
+/*
+** CAPI3REF: Collation Needed Callbacks {F16700}
+**
+** To avoid having to register all collation sequences before a database
+** can be used, a single callback function may be registered with the
+** database handle to be called whenever an undefined collation sequence is
+** required.
+**
+** If the function is registered using the sqlite3_collation_needed() API,
+** then it is passed the names of undefined collation sequences as strings
+** encoded in UTF-8. {F16703} If sqlite3_collation_needed16() is used, the names
+** are passed as UTF-16 in machine native byte order. A call to either
+** function replaces any existing callback.
+**
+** When the callback is invoked, the first argument passed is a copy
+** of the second argument to sqlite3_collation_needed() or
+** sqlite3_collation_needed16(). The second argument is the database
+** handle. The third argument is one of [SQLITE_UTF8],
+** [SQLITE_UTF16BE], or [SQLITE_UTF16LE], indicating the most
+** desirable form of the collation sequence function required.
+** The fourth parameter is the name of the
+** required collation sequence.
+**
+** The callback function should register the desired collation using
+** [sqlite3_create_collation()], [sqlite3_create_collation16()], or
+** [sqlite3_create_collation_v2()].
+**
+** INVARIANTS:
+**
+** {F16702} A successful call to [sqlite3_collation_needed(D,P,F)]
+** or [sqlite3_collation_needed16(D,P,F)] causes
+** the [database connection] D to invoke callback F with first
+** parameter P whenever it needs a comparison function for a
+** collating sequence that it does not know about.
+**
+** {F16704} Each successful call to [sqlite3_collation_needed()] or
+** [sqlite3_collation_needed16()] overrides the callback registered
+** on the same [database connection] by prior calls to either
+** interface.
+**
+** {F16706} The name of the requested collating function passed in the
+** 4th parameter to the callback is in UTF-8 if the callback
+** was registered using [sqlite3_collation_needed()] and
+** is in UTF-16 native byte order if the callback was
+** registered using [sqlite3_collation_needed16()].
+**
+**
+*/
+SQLITE_API int sqlite3_collation_needed(
+ sqlite3*,
+ void*,
+ void(*)(void*,sqlite3*,int eTextRep,const char*)
+);
+SQLITE_API int sqlite3_collation_needed16(
+ sqlite3*,
+ void*,
+ void(*)(void*,sqlite3*,int eTextRep,const void*)
+);
+
+/*
+** Specify the key for an encrypted database. This routine should be
+** called right after sqlite3_open().
+**
+** The code to implement this API is not available in the public release
+** of SQLite.
+*/
+SQLITE_API int sqlite3_key(
+ sqlite3 *db, /* Database to be rekeyed */
+ const void *pKey, int nKey /* The key */
+);
+
+/*
+** Change the key on an open database. If the current database is not
+** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the
+** database is decrypted.
+**
+** The code to implement this API is not available in the public release
+** of SQLite.
+*/
+SQLITE_API int sqlite3_rekey(
+ sqlite3 *db, /* Database to be rekeyed */
+ const void *pKey, int nKey /* The new key */
+);
+
+/*
+** CAPI3REF: Suspend Execution For A Short Time {F10530}
+**
+** The sqlite3_sleep() function
+** causes the current thread to suspend execution
+** for at least a number of milliseconds specified in its parameter.
+**
+** If the operating system does not support sleep requests with
+** millisecond time resolution, then the time will be rounded up to
+** the nearest second. The number of milliseconds of sleep actually
+** requested from the operating system is returned.
+**
+** SQLite implements this interface by calling the xSleep()
+** method of the default [sqlite3_vfs] object.
+**
+** INVARIANTS:
+**
+** {F10533} The [sqlite3_sleep(M)] interface invokes the xSleep
+** method of the default [sqlite3_vfs|VFS] in order to
+** suspend execution of the current thread for at least
+** M milliseconds.
+**
+** {F10536} The [sqlite3_sleep(M)] interface returns the number of
+** milliseconds of sleep actually requested of the operating
+** system, which might be larger than the parameter M.
+*/
+SQLITE_API int sqlite3_sleep(int);
+
+/*
+** CAPI3REF: Name Of The Folder Holding Temporary Files {F10310}
+**
+** If this global variable is made to point to a string which is
+** the name of a folder (a.ka. directory), then all temporary files
+** created by SQLite will be placed in that directory. If this variable
+** is NULL pointer, then SQLite does a search for an appropriate temporary
+** file directory.
+**
+** It is not safe to modify this variable once a database connection
+** has been opened. It is intended that this variable be set once
+** as part of process initialization and before any SQLite interface
+** routines have been call and remain unchanged thereafter.
+*/
+SQLITE_API char *sqlite3_temp_directory;
+
+/*
+** CAPI3REF: Test To See If The Database Is In Auto-Commit Mode {F12930}
+**
+** The sqlite3_get_autocommit() interfaces returns non-zero or
+** zero if the given database connection is or is not in autocommit mode,
+** respectively. Autocommit mode is on
+** by default. Autocommit mode is disabled by a [BEGIN] statement.
+** Autocommit mode is reenabled by a [COMMIT] or [ROLLBACK].
+**
+** If certain kinds of errors occur on a statement within a multi-statement
+** transactions (errors including [SQLITE_FULL], [SQLITE_IOERR],
+** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the
+** transaction might be rolled back automatically. The only way to
+** find out if SQLite automatically rolled back the transaction after
+** an error is to use this function.
+**
+** INVARIANTS:
+**
+** {F12931} The [sqlite3_get_autocommit(D)] interface returns non-zero or
+** zero if the [database connection] D is or is not in autocommit
+** mode, respectively.
+**
+** {F12932} Autocommit mode is on by default.
+**
+** {F12933} Autocommit mode is disabled by a successful [BEGIN] statement.
+**
+** {F12934} Autocommit mode is enabled by a successful [COMMIT] or [ROLLBACK]
+** statement.
+**
+**
+** LIMITATIONS:
+***
+** {U12936} If another thread changes the autocommit status of the database
+** connection while this routine is running, then the return value
+** is undefined.
+*/
+SQLITE_API int sqlite3_get_autocommit(sqlite3*);
+
+/*
+** CAPI3REF: Find The Database Handle Of A Prepared Statement {F13120}
+**
+** The sqlite3_db_handle interface
+** returns the [sqlite3*] database handle to which a
+** [prepared statement] belongs.
+** The database handle returned by sqlite3_db_handle
+** is the same database handle that was
+** the first argument to the [sqlite3_prepare_v2()] or its variants
+** that was used to create the statement in the first place.
+**
+** INVARIANTS:
+**
+** {F13123} The [sqlite3_db_handle(S)] interface returns a pointer
+** to the [database connection] associated with
+** [prepared statement] S.
+*/
+SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
+
+
+/*
+** CAPI3REF: Commit And Rollback Notification Callbacks {F12950}
+**
+** The sqlite3_commit_hook() interface registers a callback
+** function to be invoked whenever a transaction is committed.
+** Any callback set by a previous call to sqlite3_commit_hook()
+** for the same database connection is overridden.
+** The sqlite3_rollback_hook() interface registers a callback
+** function to be invoked whenever a transaction is committed.
+** Any callback set by a previous call to sqlite3_commit_hook()
+** for the same database connection is overridden.
+** The pArg argument is passed through
+** to the callback. If the callback on a commit hook function
+** returns non-zero, then the commit is converted into a rollback.
+**
+** If another function was previously registered, its
+** pArg value is returned. Otherwise NULL is returned.
+**
+** Registering a NULL function disables the callback.
+**
+** For the purposes of this API, a transaction is said to have been
+** rolled back if an explicit "ROLLBACK" statement is executed, or
+** an error or constraint causes an implicit rollback to occur.
+** The rollback callback is not invoked if a transaction is
+** automatically rolled back because the database connection is closed.
+** The rollback callback is not invoked if a transaction is
+** rolled back because a commit callback returned non-zero.
+** <todo> Check on this </todo>
+**
+** These are experimental interfaces and are subject to change.
+**
+** INVARIANTS:
+**
+** {F12951} The [sqlite3_commit_hook(D,F,P)] interface registers the
+** callback function F to be invoked with argument P whenever
+** a transaction commits on [database connection] D.
+**
+** {F12952} The [sqlite3_commit_hook(D,F,P)] interface returns the P
+** argument from the previous call with the same
+** [database connection ] D , or NULL on the first call
+** for a particular [database connection] D.
+**
+** {F12953} Each call to [sqlite3_commit_hook()] overwrites the callback
+** registered by prior calls.
+**
+** {F12954} If the F argument to [sqlite3_commit_hook(D,F,P)] is NULL
+** then the commit hook callback is cancelled and no callback
+** is invoked when a transaction commits.
+**
+** {F12955} If the commit callback returns non-zero then the commit is
+** converted into a rollback.
+**
+** {F12961} The [sqlite3_rollback_hook(D,F,P)] interface registers the
+** callback function F to be invoked with argument P whenever
+** a transaction rolls back on [database connection] D.
+**
+** {F12962} The [sqlite3_rollback_hook(D,F,P)] interface returns the P
+** argument from the previous call with the same
+** [database connection ] D , or NULL on the first call
+** for a particular [database connection] D.
+**
+** {F12963} Each call to [sqlite3_rollback_hook()] overwrites the callback
+** registered by prior calls.
+**
+** {F12964} If the F argument to [sqlite3_rollback_hook(D,F,P)] is NULL
+** then the rollback hook callback is cancelled and no callback
+** is invoked when a transaction rolls back.
+*/
+SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*);
+SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
+
+/*
+** CAPI3REF: Data Change Notification Callbacks {F12970}
+**
+** The sqlite3_update_hook() interface
+** registers a callback function with the database connection identified by the
+** first argument to be invoked whenever a row is updated, inserted or deleted.
+** Any callback set by a previous call to this function for the same
+** database connection is overridden.
+**
+** The second argument is a pointer to the function to invoke when a
+** row is updated, inserted or deleted.
+** The first argument to the callback is
+** a copy of the third argument to sqlite3_update_hook().
+** The second callback
+** argument is one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE],
+** depending on the operation that caused the callback to be invoked.
+** The third and
+** fourth arguments to the callback contain pointers to the database and
+** table name containing the affected row.
+** The final callback parameter is
+** the rowid of the row.
+** In the case of an update, this is the rowid after
+** the update takes place.
+**
+** The update hook is not invoked when internal system tables are
+** modified (i.e. sqlite_master and sqlite_sequence).
+**
+** If another function was previously registered, its pArg value
+** is returned. Otherwise NULL is returned.
+**
+** INVARIANTS:
+**
+** {F12971} The [sqlite3_update_hook(D,F,P)] interface causes callback
+** function F to be invoked with first parameter P whenever
+** a table row is modified, inserted, or deleted on
+** [database connection] D.
+**
+** {F12973} The [sqlite3_update_hook(D,F,P)] interface returns the value
+** of P for the previous call on the same [database connection] D,
+** or NULL for the first call.
+**
+** {F12975} If the update hook callback F in [sqlite3_update_hook(D,F,P)]
+** is NULL then the no update callbacks are made.
+**
+** {F12977} Each call to [sqlite3_update_hook(D,F,P)] overrides prior calls
+** to the same interface on the same [database connection] D.
+**
+** {F12979} The update hook callback is not invoked when internal system
+** tables such as sqlite_master and sqlite_sequence are modified.
+**
+** {F12981} The second parameter to the update callback
+** is one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE],
+** depending on the operation that caused the callback to be invoked.
+**
+** {F12983} The third and fourth arguments to the callback contain pointers
+** to zero-terminated UTF-8 strings which are the names of the
+** database and table that is being updated.
+
+** {F12985} The final callback parameter is the rowid of the row after
+** the change occurs.
+*/
+SQLITE_API void *sqlite3_update_hook(
+ sqlite3*,
+ void(*)(void *,int ,char const *,char const *,sqlite3_int64),
+ void*
+);
+
+/*
+** CAPI3REF: Enable Or Disable Shared Pager Cache {F10330}
+**
+** This routine enables or disables the sharing of the database cache
+** and schema data structures between connections to the same database.
+** Sharing is enabled if the argument is true and disabled if the argument
+** is false.
+**
+** Cache sharing is enabled and disabled
+** for an entire process. {END} This is a change as of SQLite version 3.5.0.
+** In prior versions of SQLite, sharing was
+** enabled or disabled for each thread separately.
+**
+** The cache sharing mode set by this interface effects all subsequent
+** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()].
+** Existing database connections continue use the sharing mode
+** that was in effect at the time they were opened.
+**
+** Virtual tables cannot be used with a shared cache. When shared
+** cache is enabled, the [sqlite3_create_module()] API used to register
+** virtual tables will always return an error.
+**
+** This routine returns [SQLITE_OK] if shared cache was
+** enabled or disabled successfully. An [error code]
+** is returned otherwise.
+**
+** Shared cache is disabled by default. But this might change in
+** future releases of SQLite. Applications that care about shared
+** cache setting should set it explicitly.
+**
+** INVARIANTS:
+**
+** {F10331} A successful invocation of [sqlite3_enable_shared_cache(B)]
+** will enable or disable shared cache mode for any subsequently
+** created [database connection] in the same process.
+**
+** {F10336} When shared cache is enabled, the [sqlite3_create_module()]
+** interface will always return an error.
+**
+** {F10337} The [sqlite3_enable_shared_cache(B)] interface returns
+** [SQLITE_OK] if shared cache was enabled or disabled successfully.
+**
+** {F10339} Shared cache is disabled by default.
+*/
+SQLITE_API int sqlite3_enable_shared_cache(int);
+
+/*
+** CAPI3REF: Attempt To Free Heap Memory {F17340}
+**
+** The sqlite3_release_memory() interface attempts to
+** free N bytes of heap memory by deallocating non-essential memory
+** allocations held by the database labrary. {END} Memory used
+** to cache database pages to improve performance is an example of
+** non-essential memory. Sqlite3_release_memory() returns
+** the number of bytes actually freed, which might be more or less
+** than the amount requested.
+**
+** INVARIANTS:
+**
+** {F17341} The [sqlite3_release_memory(N)] interface attempts to
+** free N bytes of heap memory by deallocating non-essential
+** memory allocations held by the database labrary.
+**
+** {F16342} The [sqlite3_release_memory(N)] returns the number
+** of bytes actually freed, which might be more or less
+** than the amount requested.
+*/
+SQLITE_API int sqlite3_release_memory(int);
+
+/*
+** CAPI3REF: Impose A Limit On Heap Size {F17350}
+**
+** The sqlite3_soft_heap_limit() interface
+** places a "soft" limit on the amount of heap memory that may be allocated
+** by SQLite. If an internal allocation is requested
+** that would exceed the soft heap limit, [sqlite3_release_memory()] is
+** invoked one or more times to free up some space before the allocation
+** is made.
+**
+** The limit is called "soft", because if
+** [sqlite3_release_memory()] cannot
+** free sufficient memory to prevent the limit from being exceeded,
+** the memory is allocated anyway and the current operation proceeds.
+**
+** A negative or zero value for N means that there is no soft heap limit and
+** [sqlite3_release_memory()] will only be called when memory is exhausted.
+** The default value for the soft heap limit is zero.
+**
+** SQLite makes a best effort to honor the soft heap limit.
+** But if the soft heap limit cannot honored, execution will
+** continue without error or notification. This is why the limit is
+** called a "soft" limit. It is advisory only.
+**
+** Prior to SQLite version 3.5.0, this routine only constrained the memory
+** allocated by a single thread - the same thread in which this routine
+** runs. Beginning with SQLite version 3.5.0, the soft heap limit is
+** applied to all threads. The value specified for the soft heap limit
+** is an upper bound on the total memory allocation for all threads. In
+** version 3.5.0 there is no mechanism for limiting the heap usage for
+** individual threads.
+**
+** INVARIANTS:
+**
+** {F16351} The [sqlite3_soft_heap_limit(N)] interface places a soft limit
+** of N bytes on the amount of heap memory that may be allocated
+** using [sqlite3_malloc()] or [sqlite3_realloc()] at any point
+** in time.
+**
+** {F16352} If a call to [sqlite3_malloc()] or [sqlite3_realloc()] would
+** cause the total amount of allocated memory to exceed the
+** soft heap limit, then [sqlite3_release_memory()] is invoked
+** in an attempt to reduce the memory usage prior to proceeding
+** with the memory allocation attempt.
+**
+** {F16353} Calls to [sqlite3_malloc()] or [sqlite3_realloc()] that trigger
+** attempts to reduce memory usage through the soft heap limit
+** mechanism continue even if the attempt to reduce memory
+** usage is unsuccessful.
+**
+** {F16354} A negative or zero value for N in a call to
+** [sqlite3_soft_heap_limit(N)] means that there is no soft
+** heap limit and [sqlite3_release_memory()] will only be
+** called when memory is completely exhausted.
+**
+** {F16355} The default value for the soft heap limit is zero.
+**
+** {F16358} Each call to [sqlite3_soft_heap_limit(N)] overrides the
+** values set by all prior calls.
+*/
+SQLITE_API void sqlite3_soft_heap_limit(int);
+
+/*
+** CAPI3REF: Extract Metadata About A Column Of A Table {F12850}
+**
+** This routine
+** returns meta-data about a specific column of a specific database
+** table accessible using the connection handle passed as the first function
+** argument.
+**
+** The column is identified by the second, third and fourth parameters to
+** this function. The second parameter is either the name of the database
+** (i.e. "main", "temp" or an attached database) containing the specified
+** table or NULL. If it is NULL, then all attached databases are searched
+** for the table using the same algorithm as the database engine uses to
+** resolve unqualified table references.
+**
+** The third and fourth parameters to this function are the table and column
+** name of the desired column, respectively. Neither of these parameters
+** may be NULL.
+**
+** Meta information is returned by writing to the memory locations passed as
+** the 5th and subsequent parameters to this function. Any of these
+** arguments may be NULL, in which case the corresponding element of meta
+** information is ommitted.
+**
+** <pre>
+** Parameter Output Type Description
+** -----------------------------------
+**
+** 5th const char* Data type
+** 6th const char* Name of the default collation sequence
+** 7th int True if the column has a NOT NULL constraint
+** 8th int True if the column is part of the PRIMARY KEY
+** 9th int True if the column is AUTOINCREMENT
+** </pre>
+**
+**
+** The memory pointed to by the character pointers returned for the
+** declaration type and collation sequence is valid only until the next
+** call to any sqlite API function.
+**
+** If the specified table is actually a view, then an error is returned.
+**
+** If the specified column is "rowid", "oid" or "_rowid_" and an
+** INTEGER PRIMARY KEY column has been explicitly declared, then the output
+** parameters are set for the explicitly declared column. If there is no
+** explicitly declared IPK column, then the output parameters are set as
+** follows:
+**
+** <pre>
+** data type: "INTEGER"
+** collation sequence: "BINARY"
+** not null: 0
+** primary key: 1
+** auto increment: 0
+** </pre>
+**
+** This function may load one or more schemas from database files. If an
+** error occurs during this process, or if the requested table or column
+** cannot be found, an SQLITE error code is returned and an error message
+** left in the database handle (to be retrieved using sqlite3_errmsg()).
+**
+** This API is only available if the library was compiled with the
+** SQLITE_ENABLE_COLUMN_METADATA preprocessor symbol defined.
+*/
+SQLITE_API int sqlite3_table_column_metadata(
+ sqlite3 *db, /* Connection handle */
+ const char *zDbName, /* Database name or NULL */
+ const char *zTableName, /* Table name */
+ const char *zColumnName, /* Column name */
+ char const **pzDataType, /* OUTPUT: Declared data type */
+ char const **pzCollSeq, /* OUTPUT: Collation sequence name */
+ int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */
+ int *pPrimaryKey, /* OUTPUT: True if column part of PK */
+ int *pAutoinc /* OUTPUT: True if column is auto-increment */
+);
+
+/*
+** CAPI3REF: Load An Extension {F12600}
+**
+** {F12601} The sqlite3_load_extension() interface
+** attempts to load an SQLite extension library contained in the file
+** zFile. {F12602} The entry point is zProc. {F12603} zProc may be 0
+** in which case the name of the entry point defaults
+** to "sqlite3_extension_init".
+**
+** {F12604} The sqlite3_load_extension() interface shall
+** return [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong.
+**
+** {F12605}
+** If an error occurs and pzErrMsg is not 0, then the
+** sqlite3_load_extension() interface shall attempt to fill *pzErrMsg with
+** error message text stored in memory obtained from [sqlite3_malloc()].
+** {END} The calling function should free this memory
+** by calling [sqlite3_free()].
+**
+** {F12606}
+** Extension loading must be enabled using [sqlite3_enable_load_extension()]
+** prior to calling this API or an error will be returned.
+*/
+SQLITE_API int sqlite3_load_extension(
+ sqlite3 *db, /* Load the extension into this database connection */
+ const char *zFile, /* Name of the shared library containing extension */
+ const char *zProc, /* Entry point. Derived from zFile if 0 */
+ char **pzErrMsg /* Put error message here if not 0 */
+);
+
+/*
+** CAPI3REF: Enable Or Disable Extension Loading {F12620}
+**
+** So as not to open security holes in older applications that are
+** unprepared to deal with extension loading, and as a means of disabling
+** extension loading while evaluating user-entered SQL, the following
+** API is provided to turn the [sqlite3_load_extension()] mechanism on and
+** off. {F12622} It is off by default. {END} See ticket #1863.
+**
+** {F12621} Call the sqlite3_enable_load_extension() routine
+** with onoff==1 to turn extension loading on
+** and call it with onoff==0 to turn it back off again. {END}
+*/
+SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
+
+/*
+** CAPI3REF: Make Arrangements To Automatically Load An Extension {F12640}
+**
+** {F12641} This function
+** registers an extension entry point that is automatically invoked
+** whenever a new database connection is opened using
+** [sqlite3_open()], [sqlite3_open16()], or [sqlite3_open_v2()]. {END}
+**
+** This API can be invoked at program startup in order to register
+** one or more statically linked extensions that will be available
+** to all new database connections.
+**
+** {F12642} Duplicate extensions are detected so calling this routine multiple
+** times with the same extension is harmless.
+**
+** {F12643} This routine stores a pointer to the extension in an array
+** that is obtained from sqlite_malloc(). {END} If you run a memory leak
+** checker on your program and it reports a leak because of this
+** array, then invoke [sqlite3_reset_auto_extension()] prior
+** to shutdown to free the memory.
+**
+** {F12644} Automatic extensions apply across all threads. {END}
+**
+** This interface is experimental and is subject to change or
+** removal in future releases of SQLite.
+*/
+SQLITE_API int sqlite3_auto_extension(void *xEntryPoint);
+
+
+/*
+** CAPI3REF: Reset Automatic Extension Loading {F12660}
+**
+** {F12661} This function disables all previously registered
+** automatic extensions. {END} This
+** routine undoes the effect of all prior [sqlite3_auto_extension()]
+** calls.
+**
+** {F12662} This call disabled automatic extensions in all threads. {END}
+**
+** This interface is experimental and is subject to change or
+** removal in future releases of SQLite.
+*/
+SQLITE_API void sqlite3_reset_auto_extension(void);
+
+
+/*
+****** EXPERIMENTAL - subject to change without notice **************
+**
+** The interface to the virtual-table mechanism is currently considered
+** to be experimental. The interface might change in incompatible ways.
+** If this is a problem for you, do not use the interface at this time.
+**
+** When the virtual-table mechanism stablizes, we will declare the
+** interface fixed, support it indefinitely, and remove this comment.
+*/
+
+/*
+** Structures used by the virtual table interface
+*/
+typedef struct sqlite3_vtab sqlite3_vtab;
+typedef struct sqlite3_index_info sqlite3_index_info;
+typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor;
+typedef struct sqlite3_module sqlite3_module;
+
+/*
+** CAPI3REF: Virtual Table Object {F18000}
+** KEYWORDS: sqlite3_module
+**
+** A module is a class of virtual tables. Each module is defined
+** by an instance of the following structure. This structure consists
+** mostly of methods for the module.
+*/
+struct sqlite3_module {
+ int iVersion;
+ int (*xCreate)(sqlite3*, void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVTab, char**);
+ int (*xConnect)(sqlite3*, void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVTab, char**);
+ int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
+ int (*xDisconnect)(sqlite3_vtab *pVTab);
+ int (*xDestroy)(sqlite3_vtab *pVTab);
+ int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
+ int (*xClose)(sqlite3_vtab_cursor*);
+ int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv);
+ int (*xNext)(sqlite3_vtab_cursor*);
+ int (*xEof)(sqlite3_vtab_cursor*);
+ int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid);
+ int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *);
+ int (*xBegin)(sqlite3_vtab *pVTab);
+ int (*xSync)(sqlite3_vtab *pVTab);
+ int (*xCommit)(sqlite3_vtab *pVTab);
+ int (*xRollback)(sqlite3_vtab *pVTab);
+ int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
+ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ void **ppArg);
+
+ int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
+};
+
+/*
+** CAPI3REF: Virtual Table Indexing Information {F18100}
+** KEYWORDS: sqlite3_index_info
+**
+** The sqlite3_index_info structure and its substructures is used to
+** pass information into and receive the reply from the xBestIndex
+** method of an sqlite3_module. The fields under **Inputs** are the
+** inputs to xBestIndex and are read-only. xBestIndex inserts its
+** results into the **Outputs** fields.
+**
+** The aConstraint[] array records WHERE clause constraints of the
+** form:
+**
+** column OP expr
+**
+** Where OP is =, &lt;, &lt;=, &gt;, or &gt;=.
+** The particular operator is stored
+** in aConstraint[].op. The index of the column is stored in
+** aConstraint[].iColumn. aConstraint[].usable is TRUE if the
+** expr on the right-hand side can be evaluated (and thus the constraint
+** is usable) and false if it cannot.
+**
+** The optimizer automatically inverts terms of the form "expr OP column"
+** and makes other simplifications to the WHERE clause in an attempt to
+** get as many WHERE clause terms into the form shown above as possible.
+** The aConstraint[] array only reports WHERE clause terms in the correct
+** form that refer to the particular virtual table being queried.
+**
+** Information about the ORDER BY clause is stored in aOrderBy[].
+** Each term of aOrderBy records a column of the ORDER BY clause.
+**
+** The xBestIndex method must fill aConstraintUsage[] with information
+** about what parameters to pass to xFilter. If argvIndex>0 then
+** the right-hand side of the corresponding aConstraint[] is evaluated
+** and becomes the argvIndex-th entry in argv. If aConstraintUsage[].omit
+** is true, then the constraint is assumed to be fully handled by the
+** virtual table and is not checked again by SQLite.
+**
+** The idxNum and idxPtr values are recorded and passed into xFilter.
+** sqlite3_free() is used to free idxPtr if needToFreeIdxPtr is true.
+**
+** The orderByConsumed means that output from xFilter will occur in
+** the correct order to satisfy the ORDER BY clause so that no separate
+** sorting step is required.
+**
+** The estimatedCost value is an estimate of the cost of doing the
+** particular lookup. A full scan of a table with N entries should have
+** a cost of N. A binary search of a table of N entries should have a
+** cost of approximately log(N).
+*/
+struct sqlite3_index_info {
+ /* Inputs */
+ int nConstraint; /* Number of entries in aConstraint */
+ struct sqlite3_index_constraint {
+ int iColumn; /* Column on left-hand side of constraint */
+ unsigned char op; /* Constraint operator */
+ unsigned char usable; /* True if this constraint is usable */
+ int iTermOffset; /* Used internally - xBestIndex should ignore */
+ } *aConstraint; /* Table of WHERE clause constraints */
+ int nOrderBy; /* Number of terms in the ORDER BY clause */
+ struct sqlite3_index_orderby {
+ int iColumn; /* Column number */
+ unsigned char desc; /* True for DESC. False for ASC. */
+ } *aOrderBy; /* The ORDER BY clause */
+
+ /* Outputs */
+ struct sqlite3_index_constraint_usage {
+ int argvIndex; /* if >0, constraint is part of argv to xFilter */
+ unsigned char omit; /* Do not code a test for this constraint */
+ } *aConstraintUsage;
+ int idxNum; /* Number used to identify the index */
+ char *idxStr; /* String, possibly obtained from sqlite3_malloc */
+ int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */
+ int orderByConsumed; /* True if output is already ordered */
+ double estimatedCost; /* Estimated cost of using this index */
+};
+#define SQLITE_INDEX_CONSTRAINT_EQ 2
+#define SQLITE_INDEX_CONSTRAINT_GT 4
+#define SQLITE_INDEX_CONSTRAINT_LE 8
+#define SQLITE_INDEX_CONSTRAINT_LT 16
+#define SQLITE_INDEX_CONSTRAINT_GE 32
+#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+
+/*
+** CAPI3REF: Register A Virtual Table Implementation {F18200}
+**
+** This routine is used to register a new module name with an SQLite
+** connection. Module names must be registered before creating new
+** virtual tables on the module, or before using preexisting virtual
+** tables of the module.
+*/
+SQLITE_API int sqlite3_create_module(
+ sqlite3 *db, /* SQLite connection to register module with */
+ const char *zName, /* Name of the module */
+ const sqlite3_module *, /* Methods for the module */
+ void * /* Client data for xCreate/xConnect */
+);
+
+/*
+** CAPI3REF: Register A Virtual Table Implementation {F18210}
+**
+** This routine is identical to the sqlite3_create_module() method above,
+** except that it allows a destructor function to be specified. It is
+** even more experimental than the rest of the virtual tables API.
+*/
+SQLITE_API int sqlite3_create_module_v2(
+ sqlite3 *db, /* SQLite connection to register module with */
+ const char *zName, /* Name of the module */
+ const sqlite3_module *, /* Methods for the module */
+ void *, /* Client data for xCreate/xConnect */
+ void(*xDestroy)(void*) /* Module destructor function */
+);
+
+/*
+** CAPI3REF: Virtual Table Instance Object {F18010}
+** KEYWORDS: sqlite3_vtab
+**
+** Every module implementation uses a subclass of the following structure
+** to describe a particular instance of the module. Each subclass will
+** be tailored to the specific needs of the module implementation. The
+** purpose of this superclass is to define certain fields that are common
+** to all module implementations.
+**
+** Virtual tables methods can set an error message by assigning a
+** string obtained from sqlite3_mprintf() to zErrMsg. The method should
+** take care that any prior string is freed by a call to sqlite3_free()
+** prior to assigning a new string to zErrMsg. After the error message
+** is delivered up to the client application, the string will be automatically
+** freed by sqlite3_free() and the zErrMsg field will be zeroed. Note
+** that sqlite3_mprintf() and sqlite3_free() are used on the zErrMsg field
+** since virtual tables are commonly implemented in loadable extensions which
+** do not have access to sqlite3MPrintf() or sqlite3Free().
+*/
+struct sqlite3_vtab {
+ const sqlite3_module *pModule; /* The module for this virtual table */
+ int nRef; /* Used internally */
+ char *zErrMsg; /* Error message from sqlite3_mprintf() */
+ /* Virtual table implementations will typically add additional fields */
+};
+
+/*
+** CAPI3REF: Virtual Table Cursor Object {F18020}
+** KEYWORDS: sqlite3_vtab_cursor
+**
+** Every module implementation uses a subclass of the following structure
+** to describe cursors that point into the virtual table and are used
+** to loop through the virtual table. Cursors are created using the
+** xOpen method of the module. Each module implementation will define
+** the content of a cursor structure to suit its own needs.
+**
+** This superclass exists in order to define fields of the cursor that
+** are common to all implementations.
+*/
+struct sqlite3_vtab_cursor {
+ sqlite3_vtab *pVtab; /* Virtual table of this cursor */
+ /* Virtual table implementations will typically add additional fields */
+};
+
+/*
+** CAPI3REF: Declare The Schema Of A Virtual Table {F18280}
+**
+** The xCreate and xConnect methods of a module use the following API
+** to declare the format (the names and datatypes of the columns) of
+** the virtual tables they implement.
+*/
+SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zCreateTable);
+
+/*
+** CAPI3REF: Overload A Function For A Virtual Table {F18300}
+**
+** Virtual tables can provide alternative implementations of functions
+** using the xFindFunction method. But global versions of those functions
+** must exist in order to be overloaded.
+**
+** This API makes sure a global version of a function with a particular
+** name and number of parameters exists. If no such function exists
+** before this API is called, a new function is created. The implementation
+** of the new function always causes an exception to be thrown. So
+** the new function is not good for anything by itself. Its only
+** purpose is to be a place-holder function that can be overloaded
+** by virtual tables.
+**
+** This API should be considered part of the virtual table interface,
+** which is experimental and subject to change.
+*/
+SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg);
+
+/*
+** The interface to the virtual-table mechanism defined above (back up
+** to a comment remarkably similar to this one) is currently considered
+** to be experimental. The interface might change in incompatible ways.
+** If this is a problem for you, do not use the interface at this time.
+**
+** When the virtual-table mechanism stabilizes, we will declare the
+** interface fixed, support it indefinitely, and remove this comment.
+**
+****** EXPERIMENTAL - subject to change without notice **************
+*/
+
+/*
+** CAPI3REF: A Handle To An Open BLOB {F17800}
+**
+** An instance of this object represents an open BLOB on which
+** incremental I/O can be preformed.
+** Objects of this type are created by
+** [sqlite3_blob_open()] and destroyed by [sqlite3_blob_close()].
+** The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces
+** can be used to read or write small subsections of the blob.
+** The [sqlite3_blob_bytes()] interface returns the size of the
+** blob in bytes.
+*/
+typedef struct sqlite3_blob sqlite3_blob;
+
+/*
+** CAPI3REF: Open A BLOB For Incremental I/O {F17810}
+**
+** This interfaces opens a handle to the blob located
+** in row iRow, column zColumn, table zTable in database zDb;
+** in other words, the same blob that would be selected by:
+**
+** <pre>
+** SELECT zColumn FROM zDb.zTable WHERE rowid = iRow;
+** </pre> {END}
+**
+** If the flags parameter is non-zero, the blob is opened for
+** read and write access. If it is zero, the blob is opened for read
+** access.
+**
+** Note that the database name is not the filename that contains
+** the database but rather the symbolic name of the database that
+** is assigned when the database is connected using [ATTACH].
+** For the main database file, the database name is "main". For
+** TEMP tables, the database name is "temp".
+**
+** On success, [SQLITE_OK] is returned and the new
+** [sqlite3_blob | blob handle] is written to *ppBlob.
+** Otherwise an error code is returned and
+** any value written to *ppBlob should not be used by the caller.
+** This function sets the database-handle error code and message
+** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()].
+**
+** INVARIANTS:
+**
+** {F17813} A successful invocation of the [sqlite3_blob_open(D,B,T,C,R,F,P)]
+** interface opens an [sqlite3_blob] object P on the blob
+** in column C of table T in database B on [database connection] D.
+**
+** {F17814} A successful invocation of [sqlite3_blob_open(D,...)] starts
+** a new transaction on [database connection] D if that connection
+** is not already in a transaction.
+**
+** {F17816} The [sqlite3_blob_open(D,B,T,C,R,F,P)] interface opens the blob
+** for read and write access if and only if the F parameter
+** is non-zero.
+**
+** {F17819} The [sqlite3_blob_open()] interface returns [SQLITE_OK] on
+** success and an appropriate [error code] on failure.
+**
+** {F17821} If an error occurs during evaluation of [sqlite3_blob_open(D,...)]
+** then subsequent calls to [sqlite3_errcode(D)],
+** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return
+** information approprate for that error.
+*/
+SQLITE_API int sqlite3_blob_open(
+ sqlite3*,
+ const char *zDb,
+ const char *zTable,
+ const char *zColumn,
+ sqlite3_int64 iRow,
+ int flags,
+ sqlite3_blob **ppBlob
+);
+
+/*
+** CAPI3REF: Close A BLOB Handle {F17830}
+**
+** Close an open [sqlite3_blob | blob handle].
+**
+** Closing a BLOB shall cause the current transaction to commit
+** if there are no other BLOBs, no pending prepared statements, and the
+** database connection is in autocommit mode.
+** If any writes were made to the BLOB, they might be held in cache
+** until the close operation if they will fit. {END}
+** Closing the BLOB often forces the changes
+** out to disk and so if any I/O errors occur, they will likely occur
+** at the time when the BLOB is closed. {F17833} Any errors that occur during
+** closing are reported as a non-zero return value.
+**
+** The BLOB is closed unconditionally. Even if this routine returns
+** an error code, the BLOB is still closed.
+**
+** INVARIANTS:
+**
+** {F17833} The [sqlite3_blob_close(P)] interface closes an
+** [sqlite3_blob] object P previously opened using
+** [sqlite3_blob_open()].
+**
+** {F17836} Closing an [sqlite3_blob] object using
+** [sqlite3_blob_close()] shall cause the current transaction to
+** commit if there are no other open [sqlite3_blob] objects
+** or [prepared statements] on the same [database connection] and
+** the [database connection] is in
+** [sqlite3_get_autocommit | autocommit mode].
+**
+** {F17839} The [sqlite3_blob_close(P)] interfaces closes the
+** [sqlite3_blob] object P unconditionally, even if
+** [sqlite3_blob_close(P)] returns something other than [SQLITE_OK].
+**
+*/
+SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
+
+/*
+** CAPI3REF: Return The Size Of An Open BLOB {F17840}
+**
+** Return the size in bytes of the blob accessible via the open
+** [sqlite3_blob] object in its only argument.
+**
+** INVARIANTS:
+**
+** {F17843} The [sqlite3_blob_bytes(P)] interface returns the size
+** in bytes of the BLOB that the [sqlite3_blob] object P
+** refers to.
+*/
+SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *);
+
+/*
+** CAPI3REF: Read Data From A BLOB Incrementally {F17850}
+**
+** This function is used to read data from an open
+** [sqlite3_blob | blob-handle] into a caller supplied buffer.
+** N bytes of data are copied into buffer
+** Z from the open blob, starting at offset iOffset.
+**
+** If offset iOffset is less than N bytes from the end of the blob,
+** [SQLITE_ERROR] is returned and no data is read. If N or iOffset is
+** less than zero [SQLITE_ERROR] is returned and no data is read.
+**
+** On success, SQLITE_OK is returned. Otherwise, an
+** [error code] or an [extended error code] is returned.
+**
+** INVARIANTS:
+**
+** {F17853} The [sqlite3_blob_read(P,Z,N,X)] interface reads N bytes
+** beginning at offset X from
+** the blob that [sqlite3_blob] object P refers to
+** and writes those N bytes into buffer Z.
+**
+** {F17856} In [sqlite3_blob_read(P,Z,N,X)] if the size of the blob
+** is less than N+X bytes, then the function returns [SQLITE_ERROR]
+** and nothing is read from the blob.
+**
+** {F17859} In [sqlite3_blob_read(P,Z,N,X)] if X or N is less than zero
+** then the function returns [SQLITE_ERROR]
+** and nothing is read from the blob.
+**
+** {F17862} The [sqlite3_blob_read(P,Z,N,X)] interface returns [SQLITE_OK]
+** if N bytes where successfully read into buffer Z.
+**
+** {F17865} If the requested read could not be completed,
+** the [sqlite3_blob_read(P,Z,N,X)] interface returns an
+** appropriate [error code] or [extended error code].
+**
+** {F17868} If an error occurs during evaluation of [sqlite3_blob_read(P,...)]
+** then subsequent calls to [sqlite3_errcode(D)],
+** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return
+** information approprate for that error, where D is the
+** database handle that was used to open blob handle P.
+*/
+SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
+
+/*
+** CAPI3REF: Write Data Into A BLOB Incrementally {F17870}
+**
+** This function is used to write data into an open
+** [sqlite3_blob | blob-handle] from a user supplied buffer.
+** n bytes of data are copied from the buffer
+** pointed to by z into the open blob, starting at offset iOffset.
+**
+** If the [sqlite3_blob | blob-handle] passed as the first argument
+** was not opened for writing (the flags parameter to [sqlite3_blob_open()]
+*** was zero), this function returns [SQLITE_READONLY].
+**
+** This function may only modify the contents of the blob; it is
+** not possible to increase the size of a blob using this API.
+** If offset iOffset is less than n bytes from the end of the blob,
+** [SQLITE_ERROR] is returned and no data is written. If n is
+** less than zero [SQLITE_ERROR] is returned and no data is written.
+**
+** On success, SQLITE_OK is returned. Otherwise, an
+** [error code] or an [extended error code] is returned.
+**
+** INVARIANTS:
+**
+** {F17873} The [sqlite3_blob_write(P,Z,N,X)] interface writes N bytes
+** from buffer Z into
+** the blob that [sqlite3_blob] object P refers to
+** beginning at an offset of X into the blob.
+**
+** {F17875} The [sqlite3_blob_write(P,Z,N,X)] interface returns
+** [SQLITE_READONLY] if the [sqlite3_blob] object P was
+** [sqlite3_blob_open | opened] for reading only.
+**
+** {F17876} In [sqlite3_blob_write(P,Z,N,X)] if the size of the blob
+** is less than N+X bytes, then the function returns [SQLITE_ERROR]
+** and nothing is written into the blob.
+**
+** {F17879} In [sqlite3_blob_write(P,Z,N,X)] if X or N is less than zero
+** then the function returns [SQLITE_ERROR]
+** and nothing is written into the blob.
+**
+** {F17882} The [sqlite3_blob_write(P,Z,N,X)] interface returns [SQLITE_OK]
+** if N bytes where successfully written into blob.
+**
+** {F17885} If the requested write could not be completed,
+** the [sqlite3_blob_write(P,Z,N,X)] interface returns an
+** appropriate [error code] or [extended error code].
+**
+** {F17888} If an error occurs during evaluation of [sqlite3_blob_write(D,...)]
+** then subsequent calls to [sqlite3_errcode(D)],
+** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return
+** information approprate for that error.
+*/
+SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
+
+/*
+** CAPI3REF: Virtual File System Objects {F11200}
+**
+** A virtual filesystem (VFS) is an [sqlite3_vfs] object
+** that SQLite uses to interact
+** with the underlying operating system. Most SQLite builds come with a
+** single default VFS that is appropriate for the host computer.
+** New VFSes can be registered and existing VFSes can be unregistered.
+** The following interfaces are provided.
+**
+** The sqlite3_vfs_find() interface returns a pointer to
+** a VFS given its name. Names are case sensitive.
+** Names are zero-terminated UTF-8 strings.
+** If there is no match, a NULL
+** pointer is returned. If zVfsName is NULL then the default
+** VFS is returned.
+**
+** New VFSes are registered with sqlite3_vfs_register().
+** Each new VFS becomes the default VFS if the makeDflt flag is set.
+** The same VFS can be registered multiple times without injury.
+** To make an existing VFS into the default VFS, register it again
+** with the makeDflt flag set. If two different VFSes with the
+** same name are registered, the behavior is undefined. If a
+** VFS is registered with a name that is NULL or an empty string,
+** then the behavior is undefined.
+**
+** Unregister a VFS with the sqlite3_vfs_unregister() interface.
+** If the default VFS is unregistered, another VFS is chosen as
+** the default. The choice for the new VFS is arbitrary.
+**
+** INVARIANTS:
+**
+** {F11203} The [sqlite3_vfs_find(N)] interface returns a pointer to the
+** registered [sqlite3_vfs] object whose name exactly matches
+** the zero-terminated UTF-8 string N, or it returns NULL if
+** there is no match.
+**
+** {F11206} If the N parameter to [sqlite3_vfs_find(N)] is NULL then
+** the function returns a pointer to the default [sqlite3_vfs]
+** object if there is one, or NULL if there is no default
+** [sqlite3_vfs] object.
+**
+** {F11209} The [sqlite3_vfs_register(P,F)] interface registers the
+** well-formed [sqlite3_vfs] object P using the name given
+** by the zName field of the object.
+**
+** {F11212} Using the [sqlite3_vfs_register(P,F)] interface to register
+** the same [sqlite3_vfs] object multiple times is a harmless no-op.
+**
+** {F11215} The [sqlite3_vfs_register(P,F)] interface makes the
+** the [sqlite3_vfs] object P the default [sqlite3_vfs] object
+** if F is non-zero.
+**
+** {F11218} The [sqlite3_vfs_unregister(P)] interface unregisters the
+** [sqlite3_vfs] object P so that it is no longer returned by
+** subsequent calls to [sqlite3_vfs_find()].
+*/
+SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);
+SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);
+SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
+
+/*
+** CAPI3REF: Mutexes {F17000}
+**
+** The SQLite core uses these routines for thread
+** synchronization. Though they are intended for internal
+** use by SQLite, code that links against SQLite is
+** permitted to use any of these routines.
+**
+** The SQLite source code contains multiple implementations
+** of these mutex routines. An appropriate implementation
+** is selected automatically at compile-time. The following
+** implementations are available in the SQLite core:
+**
+** <ul>
+** <li> SQLITE_MUTEX_OS2
+** <li> SQLITE_MUTEX_PTHREAD
+** <li> SQLITE_MUTEX_W32
+** <li> SQLITE_MUTEX_NOOP
+** </ul>
+**
+** The SQLITE_MUTEX_NOOP implementation is a set of routines
+** that does no real locking and is appropriate for use in
+** a single-threaded application. The SQLITE_MUTEX_OS2,
+** SQLITE_MUTEX_PTHREAD, and SQLITE_MUTEX_W32 implementations
+** are appropriate for use on os/2, unix, and windows.
+**
+** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor
+** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex
+** implementation is included with the library. The
+** mutex interface routines defined here become external
+** references in the SQLite library for which implementations
+** must be provided by the application. This facility allows an
+** application that links against SQLite to provide its own mutex
+** implementation without having to modify the SQLite core.
+**
+** {F17011} The sqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. {F17012} If it returns NULL
+** that means that a mutex could not be allocated. {F17013} SQLite
+** will unwind its stack and return an error. {F17014} The argument
+** to sqlite3_mutex_alloc() is one of these integer constants:
+**
+** <ul>
+** <li> SQLITE_MUTEX_FAST
+** <li> SQLITE_MUTEX_RECURSIVE
+** <li> SQLITE_MUTEX_STATIC_MASTER
+** <li> SQLITE_MUTEX_STATIC_MEM
+** <li> SQLITE_MUTEX_STATIC_MEM2
+** <li> SQLITE_MUTEX_STATIC_PRNG
+** <li> SQLITE_MUTEX_STATIC_LRU
+** <li> SQLITE_MUTEX_STATIC_LRU2
+** </ul> {END}
+**
+** {F17015} The first two constants cause sqlite3_mutex_alloc() to create
+** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
+** is used but not necessarily so when SQLITE_MUTEX_FAST is used. {END}
+** The mutex implementation does not need to make a distinction
+** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does
+** not want to. {F17016} But SQLite will only request a recursive mutex in
+** cases where it really needs one. {END} If a faster non-recursive mutex
+** implementation is available on the host platform, the mutex subsystem
+** might return such a mutex in response to SQLITE_MUTEX_FAST.
+**
+** {F17017} The other allowed parameters to sqlite3_mutex_alloc() each return
+** a pointer to a static preexisting mutex. {END} Four static mutexes are
+** used by the current version of SQLite. Future versions of SQLite
+** may add additional static mutexes. Static mutexes are for internal
+** use by SQLite only. Applications that use SQLite mutexes should
+** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or
+** SQLITE_MUTEX_RECURSIVE.
+**
+** {F17018} Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
+** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** returns a different mutex on every call. {F17034} But for the static
+** mutex types, the same mutex is returned on every call that has
+** the same type number. {END}
+**
+** {F17019} The sqlite3_mutex_free() routine deallocates a previously
+** allocated dynamic mutex. {F17020} SQLite is careful to deallocate every
+** dynamic mutex that it allocates. {U17021} The dynamic mutexes must not be in
+** use when they are deallocated. {U17022} Attempting to deallocate a static
+** mutex results in undefined behavior. {F17023} SQLite never deallocates
+** a static mutex. {END}
+**
+** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** to enter a mutex. {F17024} If another thread is already within the mutex,
+** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
+** SQLITE_BUSY. {F17025} The sqlite3_mutex_try() interface returns SQLITE_OK
+** upon successful entry. {F17026} Mutexes created using
+** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread.
+** {F17027} In such cases the,
+** mutex must be exited an equal number of times before another thread
+** can enter. {U17028} If the same thread tries to enter any other
+** kind of mutex more than once, the behavior is undefined.
+** {F17029} SQLite will never exhibit
+** such behavior in its own use of mutexes. {END}
+**
+** Some systems (ex: windows95) do not the operation implemented by
+** sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() will
+** always return SQLITE_BUSY. {F17030} The SQLite core only ever uses
+** sqlite3_mutex_try() as an optimization so this is acceptable behavior. {END}
+**
+** {F17031} The sqlite3_mutex_leave() routine exits a mutex that was
+** previously entered by the same thread. {U17032} The behavior
+** is undefined if the mutex is not currently entered by the
+** calling thread or is not currently allocated. {F17033} SQLite will
+** never do either. {END}
+**
+** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()].
+*/
+SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int);
+SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*);
+SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*);
+SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*);
+SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
+
+/*
+** CAPI3REF: Mutex Verifcation Routines {F17080}
+**
+** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines
+** are intended for use inside assert() statements. {F17081} The SQLite core
+** never uses these routines except inside an assert() and applications
+** are advised to follow the lead of the core. {F17082} The core only
+** provides implementations for these routines when it is compiled
+** with the SQLITE_DEBUG flag. {U17087} External mutex implementations
+** are only required to provide these routines if SQLITE_DEBUG is
+** defined and if NDEBUG is not defined.
+**
+** {F17083} These routines should return true if the mutex in their argument
+** is held or not held, respectively, by the calling thread. {END}
+**
+** {X17084} The implementation is not required to provided versions of these
+** routines that actually work.
+** If the implementation does not provide working
+** versions of these routines, it should at least provide stubs
+** that always return true so that one does not get spurious
+** assertion failures. {END}
+**
+** {F17085} If the argument to sqlite3_mutex_held() is a NULL pointer then
+** the routine should return 1. {END} This seems counter-intuitive since
+** clearly the mutex cannot be held if it does not exist. But the
+** the reason the mutex does not exist is because the build is not
+** using mutexes. And we do not want the assert() containing the
+** call to sqlite3_mutex_held() to fail, so a non-zero return is
+** the appropriate thing to do. {F17086} The sqlite3_mutex_notheld()
+** interface should also return 1 when given a NULL pointer.
+*/
+SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*);
+SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
+
+/*
+** CAPI3REF: Mutex Types {F17001}
+**
+** {F17002} The [sqlite3_mutex_alloc()] interface takes a single argument
+** which is one of these integer constants. {END}
+*/
+#define SQLITE_MUTEX_FAST 0
+#define SQLITE_MUTEX_RECURSIVE 1
+#define SQLITE_MUTEX_STATIC_MASTER 2
+#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */
+#define SQLITE_MUTEX_STATIC_MEM2 4 /* sqlite3_release_memory() */
+#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */
+#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */
+#define SQLITE_MUTEX_STATIC_LRU2 7 /* lru page list */
+
+/*
+** CAPI3REF: Low-Level Control Of Database Files {F11300}
+**
+** {F11301} The [sqlite3_file_control()] interface makes a direct call to the
+** xFileControl method for the [sqlite3_io_methods] object associated
+** with a particular database identified by the second argument. {F11302} The
+** name of the database is the name assigned to the database by the
+** <a href="lang_attach.html">ATTACH</a> SQL command that opened the
+** database. {F11303} To control the main database file, use the name "main"
+** or a NULL pointer. {F11304} The third and fourth parameters to this routine
+** are passed directly through to the second and third parameters of
+** the xFileControl method. {F11305} The return value of the xFileControl
+** method becomes the return value of this routine.
+**
+** {F11306} If the second parameter (zDbName) does not match the name of any
+** open database file, then SQLITE_ERROR is returned. {F11307} This error
+** code is not remembered and will not be recalled by [sqlite3_errcode()]
+** or [sqlite3_errmsg()]. {U11308} The underlying xFileControl method might
+** also return SQLITE_ERROR. {U11309} There is no way to distinguish between
+** an incorrect zDbName and an SQLITE_ERROR return from the underlying
+** xFileControl method. {END}
+**
+** See also: [SQLITE_FCNTL_LOCKSTATE]
+*/
+SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*);
+
+/*
+** CAPI3REF: Testing Interface {F11400}
+**
+** The sqlite3_test_control() interface is used to read out internal
+** state of SQLite and to inject faults into SQLite for testing
+** purposes. The first parameter a operation code that determines
+** the number, meaning, and operation of all subsequent parameters.
+**
+** This interface is not for use by applications. It exists solely
+** for verifying the correct operation of the SQLite library. Depending
+** on how the SQLite library is compiled, this interface might not exist.
+**
+** The details of the operation codes, their meanings, the parameters
+** they take, and what they do are all subject to change without notice.
+** Unlike most of the SQLite API, this function is not guaranteed to
+** operate consistently from one release to the next.
+*/
+SQLITE_API int sqlite3_test_control(int op, ...);
+
+/*
+** CAPI3REF: Testing Interface Operation Codes {F11410}
+**
+** These constants are the valid operation code parameters used
+** as the first argument to [sqlite3_test_control()].
+**
+** These parameters and their meansing are subject to change
+** without notice. These values are for testing purposes only.
+** Applications should not use any of these parameters or the
+** [sqlite3_test_control()] interface.
+*/
+#define SQLITE_TESTCTRL_FAULT_CONFIG 1
+#define SQLITE_TESTCTRL_FAULT_FAILURES 2
+#define SQLITE_TESTCTRL_FAULT_BENIGN_FAILURES 3
+#define SQLITE_TESTCTRL_FAULT_PENDING 4
+#define SQLITE_TESTCTRL_PRNG_SAVE 5
+#define SQLITE_TESTCTRL_PRNG_RESTORE 6
+#define SQLITE_TESTCTRL_PRNG_RESET 7
+#define SQLITE_TESTCTRL_BITVEC_TEST 8
+
+
+/*
+** Undo the hack that converts floating point types to integer for
+** builds on processors without floating point support.
+*/
+#ifdef SQLITE_OMIT_FLOATING_POINT
+# undef double
+#endif
+
+#if 0
+} /* End of the 'extern "C"' block */
+#endif
+#endif
+
+/************** End of sqlite3.h *********************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+/************** Include hash.h in the middle of sqliteInt.h ******************/
+/************** Begin file hash.h ********************************************/
+/*
+** 2001 September 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the header file for the generic hash-table implemenation
+** used in SQLite.
+**
+** $Id: hash.h,v 1.11 2007/09/04 14:31:47 danielk1977 Exp $
+*/
+#ifndef _SQLITE_HASH_H_
+#define _SQLITE_HASH_H_
+
+/* Forward declarations of structures. */
+typedef struct Hash Hash;
+typedef struct HashElem HashElem;
+
+/* A complete hash table is an instance of the following structure.
+** The internals of this structure are intended to be opaque -- client
+** code should not attempt to access or modify the fields of this structure
+** directly. Change this structure only by using the routines below.
+** However, many of the "procedures" and "functions" for modifying and
+** accessing this structure are really macros, so we can't really make
+** this structure opaque.
+*/
+struct Hash {
+ char keyClass; /* SQLITE_HASH_INT, _POINTER, _STRING, _BINARY */
+ char copyKey; /* True if copy of key made on insert */
+ int count; /* Number of entries in this table */
+ int htsize; /* Number of buckets in the hash table */
+ HashElem *first; /* The first element of the array */
+ struct _ht { /* the hash table */
+ int count; /* Number of entries with this hash */
+ HashElem *chain; /* Pointer to first entry with this hash */
+ } *ht;
+};
+
+/* Each element in the hash table is an instance of the following
+** structure. All elements are stored on a single doubly-linked list.
+**
+** Again, this structure is intended to be opaque, but it can't really
+** be opaque because it is used by macros.
+*/
+struct HashElem {
+ HashElem *next, *prev; /* Next and previous elements in the table */
+ void *data; /* Data associated with this element */
+ void *pKey; int nKey; /* Key associated with this element */
+};
+
+/*
+** There are 4 different modes of operation for a hash table:
+**
+** SQLITE_HASH_INT nKey is used as the key and pKey is ignored.
+**
+** SQLITE_HASH_POINTER pKey is used as the key and nKey is ignored.
+**
+** SQLITE_HASH_STRING pKey points to a string that is nKey bytes long
+** (including the null-terminator, if any). Case
+** is ignored in comparisons.
+**
+** SQLITE_HASH_BINARY pKey points to binary data nKey bytes long.
+** memcmp() is used to compare keys.
+**
+** A copy of the key is made for SQLITE_HASH_STRING and SQLITE_HASH_BINARY
+** if the copyKey parameter to HashInit is 1.
+*/
+/* #define SQLITE_HASH_INT 1 // NOT USED */
+/* #define SQLITE_HASH_POINTER 2 // NOT USED */
+#define SQLITE_HASH_STRING 3
+#define SQLITE_HASH_BINARY 4
+
+/*
+** Access routines. To delete, insert a NULL pointer.
+*/
+SQLITE_PRIVATE void sqlite3HashInit(Hash*, int keytype, int copyKey);
+SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const void *pKey, int nKey, void *pData);
+SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const void *pKey, int nKey);
+SQLITE_PRIVATE HashElem *sqlite3HashFindElem(const Hash*, const void *pKey, int nKey);
+SQLITE_PRIVATE void sqlite3HashClear(Hash*);
+
+/*
+** Macros for looping over all elements of a hash table. The idiom is
+** like this:
+**
+** Hash h;
+** HashElem *p;
+** ...
+** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){
+** SomeStructure *pData = sqliteHashData(p);
+** // do something with pData
+** }
+*/
+#define sqliteHashFirst(H) ((H)->first)
+#define sqliteHashNext(E) ((E)->next)
+#define sqliteHashData(E) ((E)->data)
+#define sqliteHashKey(E) ((E)->pKey)
+#define sqliteHashKeysize(E) ((E)->nKey)
+
+/*
+** Number of entries in a hash table
+*/
+#define sqliteHashCount(H) ((H)->count)
+
+#endif /* _SQLITE_HASH_H_ */
+
+/************** End of hash.h ************************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+/************** Include parse.h in the middle of sqliteInt.h *****************/
+/************** Begin file parse.h *******************************************/
+#define TK_SEMI 1
+#define TK_EXPLAIN 2
+#define TK_QUERY 3
+#define TK_PLAN 4
+#define TK_BEGIN 5
+#define TK_TRANSACTION 6
+#define TK_DEFERRED 7
+#define TK_IMMEDIATE 8
+#define TK_EXCLUSIVE 9
+#define TK_COMMIT 10
+#define TK_END 11
+#define TK_ROLLBACK 12
+#define TK_CREATE 13
+#define TK_TABLE 14
+#define TK_IF 15
+#define TK_NOT 16
+#define TK_EXISTS 17
+#define TK_TEMP 18
+#define TK_LP 19
+#define TK_RP 20
+#define TK_AS 21
+#define TK_COMMA 22
+#define TK_ID 23
+#define TK_ABORT 24
+#define TK_AFTER 25
+#define TK_ANALYZE 26
+#define TK_ASC 27
+#define TK_ATTACH 28
+#define TK_BEFORE 29
+#define TK_CASCADE 30
+#define TK_CAST 31
+#define TK_CONFLICT 32
+#define TK_DATABASE 33
+#define TK_DESC 34
+#define TK_DETACH 35
+#define TK_EACH 36
+#define TK_FAIL 37
+#define TK_FOR 38
+#define TK_IGNORE 39
+#define TK_INITIALLY 40
+#define TK_INSTEAD 41
+#define TK_LIKE_KW 42
+#define TK_MATCH 43
+#define TK_KEY 44
+#define TK_OF 45
+#define TK_OFFSET 46
+#define TK_PRAGMA 47
+#define TK_RAISE 48
+#define TK_REPLACE 49
+#define TK_RESTRICT 50
+#define TK_ROW 51
+#define TK_TRIGGER 52
+#define TK_VACUUM 53
+#define TK_VIEW 54
+#define TK_VIRTUAL 55
+#define TK_REINDEX 56
+#define TK_RENAME 57
+#define TK_CTIME_KW 58
+#define TK_ANY 59
+#define TK_OR 60
+#define TK_AND 61
+#define TK_IS 62
+#define TK_BETWEEN 63
+#define TK_IN 64
+#define TK_ISNULL 65
+#define TK_NOTNULL 66
+#define TK_NE 67
+#define TK_EQ 68
+#define TK_GT 69
+#define TK_LE 70
+#define TK_LT 71
+#define TK_GE 72
+#define TK_ESCAPE 73
+#define TK_BITAND 74
+#define TK_BITOR 75
+#define TK_LSHIFT 76
+#define TK_RSHIFT 77
+#define TK_PLUS 78
+#define TK_MINUS 79
+#define TK_STAR 80
+#define TK_SLASH 81
+#define TK_REM 82
+#define TK_CONCAT 83
+#define TK_COLLATE 84
+#define TK_UMINUS 85
+#define TK_UPLUS 86
+#define TK_BITNOT 87
+#define TK_STRING 88
+#define TK_JOIN_KW 89
+#define TK_CONSTRAINT 90
+#define TK_DEFAULT 91
+#define TK_NULL 92
+#define TK_PRIMARY 93
+#define TK_UNIQUE 94
+#define TK_CHECK 95
+#define TK_REFERENCES 96
+#define TK_AUTOINCR 97
+#define TK_ON 98
+#define TK_DELETE 99
+#define TK_UPDATE 100
+#define TK_INSERT 101
+#define TK_SET 102
+#define TK_DEFERRABLE 103
+#define TK_FOREIGN 104
+#define TK_DROP 105
+#define TK_UNION 106
+#define TK_ALL 107
+#define TK_EXCEPT 108
+#define TK_INTERSECT 109
+#define TK_SELECT 110
+#define TK_DISTINCT 111
+#define TK_DOT 112
+#define TK_FROM 113
+#define TK_JOIN 114
+#define TK_USING 115
+#define TK_ORDER 116
+#define TK_BY 117
+#define TK_GROUP 118
+#define TK_HAVING 119
+#define TK_LIMIT 120
+#define TK_WHERE 121
+#define TK_INTO 122
+#define TK_VALUES 123
+#define TK_INTEGER 124
+#define TK_FLOAT 125
+#define TK_BLOB 126
+#define TK_REGISTER 127
+#define TK_VARIABLE 128
+#define TK_CASE 129
+#define TK_WHEN 130
+#define TK_THEN 131
+#define TK_ELSE 132
+#define TK_INDEX 133
+#define TK_ALTER 134
+#define TK_TO 135
+#define TK_ADD 136
+#define TK_COLUMNKW 137
+#define TK_TO_TEXT 138
+#define TK_TO_BLOB 139
+#define TK_TO_NUMERIC 140
+#define TK_TO_INT 141
+#define TK_TO_REAL 142
+#define TK_END_OF_FILE 143
+#define TK_ILLEGAL 144
+#define TK_SPACE 145
+#define TK_UNCLOSED_STRING 146
+#define TK_COMMENT 147
+#define TK_FUNCTION 148
+#define TK_COLUMN 149
+#define TK_AGG_FUNCTION 150
+#define TK_AGG_COLUMN 151
+#define TK_CONST_FUNC 152
+
+/************** End of parse.h ***********************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stddef.h>
+
+/*
+** If compiling for a processor that lacks floating point support,
+** substitute integer for floating-point
+*/
+#ifdef SQLITE_OMIT_FLOATING_POINT
+# define double sqlite_int64
+# define LONGDOUBLE_TYPE sqlite_int64
+# ifndef SQLITE_BIG_DBL
+# define SQLITE_BIG_DBL (0x7fffffffffffffff)
+# endif
+# define SQLITE_OMIT_DATETIME_FUNCS 1
+# define SQLITE_OMIT_TRACE 1
+# undef SQLITE_MIXED_ENDIAN_64BIT_FLOAT
+#endif
+#ifndef SQLITE_BIG_DBL
+# define SQLITE_BIG_DBL (1e99)
+#endif
+
+/*
+** OMIT_TEMPDB is set to 1 if SQLITE_OMIT_TEMPDB is defined, or 0
+** afterward. Having this macro allows us to cause the C compiler
+** to omit code used by TEMP tables without messy #ifndef statements.
+*/
+#ifdef SQLITE_OMIT_TEMPDB
+#define OMIT_TEMPDB 1
+#else
+#define OMIT_TEMPDB 0
+#endif
+
+/*
+** If the following macro is set to 1, then NULL values are considered
+** distinct when determining whether or not two entries are the same
+** in a UNIQUE index. This is the way PostgreSQL, Oracle, DB2, MySQL,
+** OCELOT, and Firebird all work. The SQL92 spec explicitly says this
+** is the way things are suppose to work.
+**
+** If the following macro is set to 0, the NULLs are indistinct for
+** a UNIQUE index. In this mode, you can only have a single NULL entry
+** for a column declared UNIQUE. This is the way Informix and SQL Server
+** work.
+*/
+#define NULL_DISTINCT_FOR_UNIQUE 1
+
+/*
+** The "file format" number is an integer that is incremented whenever
+** the VDBE-level file format changes. The following macros define the
+** the default file format for new databases and the maximum file format
+** that the library can read.
+*/
+#define SQLITE_MAX_FILE_FORMAT 4
+#ifndef SQLITE_DEFAULT_FILE_FORMAT
+# define SQLITE_DEFAULT_FILE_FORMAT 1
+#endif
+
+/*
+** Provide a default value for TEMP_STORE in case it is not specified
+** on the command-line
+*/
+#ifndef TEMP_STORE
+# define TEMP_STORE 1
+#endif
+
+/*
+** GCC does not define the offsetof() macro so we'll have to do it
+** ourselves.
+*/
+#ifndef offsetof
+#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD))
+#endif
+
+/*
+** Check to see if this machine uses EBCDIC. (Yes, believe it or
+** not, there are still machines out there that use EBCDIC.)
+*/
+#if 'A' == '\301'
+# define SQLITE_EBCDIC 1
+#else
+# define SQLITE_ASCII 1
+#endif
+
+/*
+** Integers of known sizes. These typedefs might change for architectures
+** where the sizes very. Preprocessor macros are available so that the
+** types can be conveniently redefined at compile-type. Like this:
+**
+** cc '-DUINTPTR_TYPE=long long int' ...
+*/
+#ifndef UINT32_TYPE
+# ifdef HAVE_UINT32_T
+# define UINT32_TYPE uint32_t
+# else
+# define UINT32_TYPE unsigned int
+# endif
+#endif
+#ifndef UINT16_TYPE
+# ifdef HAVE_UINT16_T
+# define UINT16_TYPE uint16_t
+# else
+# define UINT16_TYPE unsigned short int
+# endif
+#endif
+#ifndef INT16_TYPE
+# ifdef HAVE_INT16_T
+# define INT16_TYPE int16_t
+# else
+# define INT16_TYPE short int
+# endif
+#endif
+#ifndef UINT8_TYPE
+# ifdef HAVE_UINT8_T
+# define UINT8_TYPE uint8_t
+# else
+# define UINT8_TYPE unsigned char
+# endif
+#endif
+#ifndef INT8_TYPE
+# ifdef HAVE_INT8_T
+# define INT8_TYPE int8_t
+# else
+# define INT8_TYPE signed char
+# endif
+#endif
+#ifndef LONGDOUBLE_TYPE
+# define LONGDOUBLE_TYPE long double
+#endif
+typedef sqlite_int64 i64; /* 8-byte signed integer */
+typedef sqlite_uint64 u64; /* 8-byte unsigned integer */
+typedef UINT32_TYPE u32; /* 4-byte unsigned integer */
+typedef UINT16_TYPE u16; /* 2-byte unsigned integer */
+typedef INT16_TYPE i16; /* 2-byte signed integer */
+typedef UINT8_TYPE u8; /* 1-byte unsigned integer */
+typedef UINT8_TYPE i8; /* 1-byte signed integer */
+
+/*
+** Macros to determine whether the machine is big or little endian,
+** evaluated at runtime.
+*/
+#ifdef SQLITE_AMALGAMATION
+SQLITE_PRIVATE const int sqlite3one;
+#else
+SQLITE_PRIVATE const int sqlite3one;
+#endif
+#if defined(i386) || defined(__i386__) || defined(_M_IX86)
+# define SQLITE_BIGENDIAN 0
+# define SQLITE_LITTLEENDIAN 1
+# define SQLITE_UTF16NATIVE SQLITE_UTF16LE
+#else
+# define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0)
+# define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1)
+# define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE)
+#endif
+
+/*
+** Constants for the largest and smallest possible 64-bit signed integers.
+** These macros are designed to work correctly on both 32-bit and 64-bit
+** compilers.
+*/
+#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32))
+#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64)
+
+/*
+** An instance of the following structure is used to store the busy-handler
+** callback for a given sqlite handle.
+**
+** The sqlite.busyHandler member of the sqlite struct contains the busy
+** callback for the database handle. Each pager opened via the sqlite
+** handle is passed a pointer to sqlite.busyHandler. The busy-handler
+** callback is currently invoked only from within pager.c.
+*/
+typedef struct BusyHandler BusyHandler;
+struct BusyHandler {
+ int (*xFunc)(void *,int); /* The busy callback */
+ void *pArg; /* First arg to busy callback */
+ int nBusy; /* Incremented with each busy call */
+};
+
+/*
+** Name of the master database table. The master database table
+** is a special table that holds the names and attributes of all
+** user tables and indices.
+*/
+#define MASTER_NAME "sqlite_master"
+#define TEMP_MASTER_NAME "sqlite_temp_master"
+
+/*
+** The root-page of the master database table.
+*/
+#define MASTER_ROOT 1
+
+/*
+** The name of the schema table.
+*/
+#define SCHEMA_TABLE(x) ((!OMIT_TEMPDB)&&(x==1)?TEMP_MASTER_NAME:MASTER_NAME)
+
+/*
+** A convenience macro that returns the number of elements in
+** an array.
+*/
+#define ArraySize(X) (sizeof(X)/sizeof(X[0]))
+
+/*
+** Forward references to structures
+*/
+typedef struct AggInfo AggInfo;
+typedef struct AuthContext AuthContext;
+typedef struct Bitvec Bitvec;
+typedef struct CollSeq CollSeq;
+typedef struct Column Column;
+typedef struct Db Db;
+typedef struct Schema Schema;
+typedef struct Expr Expr;
+typedef struct ExprList ExprList;
+typedef struct FKey FKey;
+typedef struct FuncDef FuncDef;
+typedef struct IdList IdList;
+typedef struct Index Index;
+typedef struct KeyClass KeyClass;
+typedef struct KeyInfo KeyInfo;
+typedef struct Module Module;
+typedef struct NameContext NameContext;
+typedef struct Parse Parse;
+typedef struct Select Select;
+typedef struct SrcList SrcList;
+typedef struct StrAccum StrAccum;
+typedef struct Table Table;
+typedef struct TableLock TableLock;
+typedef struct Token Token;
+typedef struct TriggerStack TriggerStack;
+typedef struct TriggerStep TriggerStep;
+typedef struct Trigger Trigger;
+typedef struct WhereInfo WhereInfo;
+typedef struct WhereLevel WhereLevel;
+
+/*
+** Defer sourcing vdbe.h and btree.h until after the "u8" and
+** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque
+** pointer types (i.e. FuncDef) defined above.
+*/
+/************** Include btree.h in the middle of sqliteInt.h *****************/
+/************** Begin file btree.h *******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the sqlite B-Tree file
+** subsystem. See comments in the source code for a detailed description
+** of what each interface routine does.
+**
+** @(#) $Id: btree.h,v 1.98 2008/04/26 13:39:47 drh Exp $
+*/
+#ifndef _BTREE_H_
+#define _BTREE_H_
+
+/* TODO: This definition is just included so other modules compile. It
+** needs to be revisited.
+*/
+#define SQLITE_N_BTREE_META 10
+
+/*
+** If defined as non-zero, auto-vacuum is enabled by default. Otherwise
+** it must be turned on for each database using "PRAGMA auto_vacuum = 1".
+*/
+#ifndef SQLITE_DEFAULT_AUTOVACUUM
+ #define SQLITE_DEFAULT_AUTOVACUUM 0
+#endif
+
+#define BTREE_AUTOVACUUM_NONE 0 /* Do not do auto-vacuum */
+#define BTREE_AUTOVACUUM_FULL 1 /* Do full auto-vacuum */
+#define BTREE_AUTOVACUUM_INCR 2 /* Incremental vacuum */
+
+/*
+** Forward declarations of structure
+*/
+typedef struct Btree Btree;
+typedef struct BtCursor BtCursor;
+typedef struct BtShared BtShared;
+typedef struct BtreeMutexArray BtreeMutexArray;
+
+/*
+** This structure records all of the Btrees that need to hold
+** a mutex before we enter sqlite3VdbeExec(). The Btrees are
+** are placed in aBtree[] in order of aBtree[]->pBt. That way,
+** we can always lock and unlock them all quickly.
+*/
+struct BtreeMutexArray {
+ int nMutex;
+ Btree *aBtree[SQLITE_MAX_ATTACHED+1];
+};
+
+
+SQLITE_PRIVATE int sqlite3BtreeOpen(
+ const char *zFilename, /* Name of database file to open */
+ sqlite3 *db, /* Associated database connection */
+ Btree **, /* Return open Btree* here */
+ int flags, /* Flags */
+ int vfsFlags /* Flags passed through to VFS open */
+);
+
+/* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the
+** following values.
+**
+** NOTE: These values must match the corresponding PAGER_ values in
+** pager.h.
+*/
+#define BTREE_OMIT_JOURNAL 1 /* Do not use journal. No argument */
+#define BTREE_NO_READLOCK 2 /* Omit readlocks on readonly files */
+#define BTREE_MEMORY 4 /* In-memory DB. No argument */
+#define BTREE_READONLY 8 /* Open the database in read-only mode */
+#define BTREE_READWRITE 16 /* Open for both reading and writing */
+#define BTREE_CREATE 32 /* Create the database if it does not exist */
+
+/* Additional values for the 4th argument of sqlite3BtreeOpen that
+** are not associated with PAGER_ values.
+*/
+#define BTREE_PRIVATE 64 /* Never share with other connections */
+
+SQLITE_PRIVATE int sqlite3BtreeClose(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int);
+SQLITE_PRIVATE int sqlite3BtreeSetSafetyLevel(Btree*,int,int);
+SQLITE_PRIVATE int sqlite3BtreeSyncDisabled(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree*,int,int);
+SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int);
+SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int);
+SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *);
+SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int);
+SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster);
+SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeCommitStmt(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeRollbackStmt(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, int*, int flags);
+SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeIsInStmt(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*);
+SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *));
+SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *);
+SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *, int, u8);
+
+SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *);
+SQLITE_PRIVATE const char *sqlite3BtreeGetDirname(Btree *);
+SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *);
+SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *);
+
+SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *);
+
+/* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR
+** of the following flags:
+*/
+#define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */
+#define BTREE_ZERODATA 2 /* Table has keys only - no data */
+#define BTREE_LEAFDATA 4 /* Data stored in leaves only. Implies INTKEY */
+
+SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*);
+SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int);
+SQLITE_PRIVATE int sqlite3BtreeGetMeta(Btree*, int idx, u32 *pValue);
+SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value);
+SQLITE_PRIVATE void sqlite3BtreeTripAllCursors(Btree*, int);
+
+struct UnpackedRecord; /* Forward declaration. Definition in vdbeaux.c. */
+
+SQLITE_PRIVATE int sqlite3BtreeCursor(
+ Btree*, /* BTree containing table to open */
+ int iTable, /* Index of root page */
+ int wrFlag, /* 1 for writing. 0 for read-only */
+ struct KeyInfo*, /* First argument to compare function */
+ BtCursor *pCursor /* Space to write cursor structure */
+);
+SQLITE_PRIVATE int sqlite3BtreeCursorSize(void);
+
+SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*);
+SQLITE_PRIVATE int sqlite3BtreeMoveto(
+ BtCursor*,
+ const void *pKey,
+ struct UnpackedRecord *pUnKey,
+ i64 nKey,
+ int bias,
+ int *pRes
+);
+SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*);
+SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey,
+ const void *pData, int nData,
+ int nZero, int bias);
+SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes);
+SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes);
+SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes);
+SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*);
+SQLITE_PRIVATE int sqlite3BtreeFlags(BtCursor*);
+SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes);
+SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor*, i64 *pSize);
+SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*);
+SQLITE_PRIVATE sqlite3 *sqlite3BtreeCursorDb(const BtCursor*);
+SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor*, int *pAmt);
+SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor*, int *pAmt);
+SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor*, u32 *pSize);
+SQLITE_PRIVATE int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*);
+
+SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*);
+SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*);
+
+SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*);
+SQLITE_PRIVATE void sqlite3BtreeCacheOverflow(BtCursor *);
+
+#ifdef SQLITE_TEST
+SQLITE_PRIVATE int sqlite3BtreeCursorInfo(BtCursor*, int*, int);
+SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*);
+SQLITE_PRIVATE int sqlite3BtreePageDump(Btree*, int, int recursive);
+#endif
+
+/*
+** If we are not using shared cache, then there is no need to
+** use mutexes to access the BtShared structures. So make the
+** Enter and Leave procedures no-ops.
+*/
+#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE
+SQLITE_PRIVATE void sqlite3BtreeEnter(Btree*);
+SQLITE_PRIVATE void sqlite3BtreeLeave(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree*);
+SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*);
+SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor*);
+SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3*);
+SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3*);
+SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3*);
+SQLITE_PRIVATE void sqlite3BtreeMutexArrayEnter(BtreeMutexArray*);
+SQLITE_PRIVATE void sqlite3BtreeMutexArrayLeave(BtreeMutexArray*);
+SQLITE_PRIVATE void sqlite3BtreeMutexArrayInsert(BtreeMutexArray*, Btree*);
+#else
+# define sqlite3BtreeEnter(X)
+# define sqlite3BtreeLeave(X)
+# define sqlite3BtreeHoldsMutex(X) 1
+# define sqlite3BtreeEnterCursor(X)
+# define sqlite3BtreeLeaveCursor(X)
+# define sqlite3BtreeEnterAll(X)
+# define sqlite3BtreeLeaveAll(X)
+# define sqlite3BtreeHoldsAllMutexes(X) 1
+# define sqlite3BtreeMutexArrayEnter(X)
+# define sqlite3BtreeMutexArrayLeave(X)
+# define sqlite3BtreeMutexArrayInsert(X,Y)
+#endif
+
+
+#endif /* _BTREE_H_ */
+
+/************** End of btree.h ***********************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+/************** Include vdbe.h in the middle of sqliteInt.h ******************/
+/************** Begin file vdbe.h ********************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Header file for the Virtual DataBase Engine (VDBE)
+**
+** This header defines the interface to the virtual database engine
+** or VDBE. The VDBE implements an abstract machine that runs a
+** simple program to access and modify the underlying database.
+**
+** $Id: vdbe.h,v 1.131 2008/05/01 17:03:49 drh Exp $
+*/
+#ifndef _SQLITE_VDBE_H_
+#define _SQLITE_VDBE_H_
+
+/*
+** A single VDBE is an opaque structure named "Vdbe". Only routines
+** in the source file sqliteVdbe.c are allowed to see the insides
+** of this structure.
+*/
+typedef struct Vdbe Vdbe;
+
+/*
+** The names of the following types declared in vdbeInt.h are required
+** for the VdbeOp definition.
+*/
+typedef struct VdbeFunc VdbeFunc;
+typedef struct Mem Mem;
+typedef struct UnpackedRecord UnpackedRecord;
+
+/*
+** A single instruction of the virtual machine has an opcode
+** and as many as three operands. The instruction is recorded
+** as an instance of the following structure:
+*/
+struct VdbeOp {
+ u8 opcode; /* What operation to perform */
+ signed char p4type; /* One of the P4_xxx constants for p4 */
+ u8 opflags; /* Not currently used */
+ u8 p5; /* Fifth parameter is an unsigned character */
+ int p1; /* First operand */
+ int p2; /* Second parameter (often the jump destination) */
+ int p3; /* The third parameter */
+ union { /* forth parameter */
+ int i; /* Integer value if p4type==P4_INT32 */
+ void *p; /* Generic pointer */
+ char *z; /* Pointer to data for string (char array) types */
+ i64 *pI64; /* Used when p4type is P4_INT64 */
+ double *pReal; /* Used when p4type is P4_REAL */
+ FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */
+ VdbeFunc *pVdbeFunc; /* Used when p4type is P4_VDBEFUNC */
+ CollSeq *pColl; /* Used when p4type is P4_COLLSEQ */
+ Mem *pMem; /* Used when p4type is P4_MEM */
+ sqlite3_vtab *pVtab; /* Used when p4type is P4_VTAB */
+ KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */
+ } p4;
+#ifdef SQLITE_DEBUG
+ char *zComment; /* Comment to improve readability */
+#endif
+#ifdef VDBE_PROFILE
+ int cnt; /* Number of times this instruction was executed */
+ long long cycles; /* Total time spend executing this instruction */
+#endif
+};
+typedef struct VdbeOp VdbeOp;
+
+/*
+** A smaller version of VdbeOp used for the VdbeAddOpList() function because
+** it takes up less space.
+*/
+struct VdbeOpList {
+ u8 opcode; /* What operation to perform */
+ signed char p1; /* First operand */
+ signed char p2; /* Second parameter (often the jump destination) */
+ signed char p3; /* Third parameter */
+};
+typedef struct VdbeOpList VdbeOpList;
+
+/*
+** Allowed values of VdbeOp.p3type
+*/
+#define P4_NOTUSED 0 /* The P4 parameter is not used */
+#define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */
+#define P4_STATIC (-2) /* Pointer to a static string */
+#define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */
+#define P4_FUNCDEF (-5) /* P4 is a pointer to a FuncDef structure */
+#define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */
+#define P4_VDBEFUNC (-7) /* P4 is a pointer to a VdbeFunc structure */
+#define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */
+#define P4_TRANSIENT (-9) /* P4 is a pointer to a transient string */
+#define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */
+#define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */
+#define P4_REAL (-12) /* P4 is a 64-bit floating point value */
+#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */
+#define P4_INT32 (-14) /* P4 is a 32-bit signed integer */
+
+/* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure
+** is made. That copy is freed when the Vdbe is finalized. But if the
+** argument is P4_KEYINFO_HANDOFF, the passed in pointer is used. It still
+** gets freed when the Vdbe is finalized so it still should be obtained
+** from a single sqliteMalloc(). But no copy is made and the calling
+** function should *not* try to free the KeyInfo.
+*/
+#define P4_KEYINFO_HANDOFF (-9)
+
+/*
+** The Vdbe.aColName array contains 5n Mem structures, where n is the
+** number of columns of data returned by the statement.
+*/
+#define COLNAME_NAME 0
+#define COLNAME_DECLTYPE 1
+#define COLNAME_DATABASE 2
+#define COLNAME_TABLE 3
+#define COLNAME_COLUMN 4
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+# define COLNAME_N 5 /* Number of COLNAME_xxx symbols */
+#else
+# ifdef SQLITE_OMIT_DECLTYPE
+# define COLNAME_N 1 /* Store only the name */
+# else
+# define COLNAME_N 2 /* Store the name and decltype */
+# endif
+#endif
+
+/*
+** The following macro converts a relative address in the p2 field
+** of a VdbeOp structure into a negative number so that
+** sqlite3VdbeAddOpList() knows that the address is relative. Calling
+** the macro again restores the address.
+*/
+#define ADDR(X) (-1-(X))
+
+/*
+** The makefile scans the vdbe.c source file and creates the "opcodes.h"
+** header file that defines a number for each opcode used by the VDBE.
+*/
+/************** Include opcodes.h in the middle of vdbe.h ********************/
+/************** Begin file opcodes.h *****************************************/
+/* Automatically generated. Do not edit */
+/* See the mkopcodeh.awk script for details */
+#define OP_VNext 1
+#define OP_Affinity 2
+#define OP_Column 3
+#define OP_SetCookie 4
+#define OP_Real 125 /* same as TK_FLOAT */
+#define OP_Sequence 5
+#define OP_MoveGt 6
+#define OP_Ge 72 /* same as TK_GE */
+#define OP_RowKey 7
+#define OP_SCopy 8
+#define OP_Eq 68 /* same as TK_EQ */
+#define OP_OpenWrite 9
+#define OP_NotNull 66 /* same as TK_NOTNULL */
+#define OP_If 10
+#define OP_ToInt 141 /* same as TK_TO_INT */
+#define OP_String8 88 /* same as TK_STRING */
+#define OP_VRowid 11
+#define OP_CollSeq 12
+#define OP_OpenRead 13
+#define OP_Expire 14
+#define OP_AutoCommit 15
+#define OP_Gt 69 /* same as TK_GT */
+#define OP_IntegrityCk 17
+#define OP_Sort 18
+#define OP_Copy 19
+#define OP_Trace 20
+#define OP_Function 21
+#define OP_IfNeg 22
+#define OP_And 61 /* same as TK_AND */
+#define OP_Subtract 79 /* same as TK_MINUS */
+#define OP_Noop 23
+#define OP_Return 24
+#define OP_Remainder 82 /* same as TK_REM */
+#define OP_NewRowid 25
+#define OP_Multiply 80 /* same as TK_STAR */
+#define OP_Variable 26
+#define OP_String 27
+#define OP_RealAffinity 28
+#define OP_VRename 29
+#define OP_ParseSchema 30
+#define OP_VOpen 31
+#define OP_Close 32
+#define OP_CreateIndex 33
+#define OP_IsUnique 34
+#define OP_NotFound 35
+#define OP_Int64 36
+#define OP_MustBeInt 37
+#define OP_Halt 38
+#define OP_Rowid 39
+#define OP_IdxLT 40
+#define OP_AddImm 41
+#define OP_Statement 42
+#define OP_RowData 43
+#define OP_MemMax 44
+#define OP_Or 60 /* same as TK_OR */
+#define OP_NotExists 45
+#define OP_Gosub 46
+#define OP_Divide 81 /* same as TK_SLASH */
+#define OP_Integer 47
+#define OP_ToNumeric 140 /* same as TK_TO_NUMERIC*/
+#define OP_Prev 48
+#define OP_Concat 83 /* same as TK_CONCAT */
+#define OP_BitAnd 74 /* same as TK_BITAND */
+#define OP_VColumn 49
+#define OP_CreateTable 50
+#define OP_Last 51
+#define OP_IsNull 65 /* same as TK_ISNULL */
+#define OP_IncrVacuum 52
+#define OP_IdxRowid 53
+#define OP_ShiftRight 77 /* same as TK_RSHIFT */
+#define OP_ResetCount 54
+#define OP_FifoWrite 55
+#define OP_ContextPush 56
+#define OP_DropTrigger 57
+#define OP_DropIndex 58
+#define OP_IdxGE 59
+#define OP_IdxDelete 62
+#define OP_Vacuum 63
+#define OP_MoveLe 64
+#define OP_IfNot 73
+#define OP_DropTable 84
+#define OP_MakeRecord 85
+#define OP_ToBlob 139 /* same as TK_TO_BLOB */
+#define OP_ResultRow 86
+#define OP_Delete 89
+#define OP_AggFinal 90
+#define OP_ShiftLeft 76 /* same as TK_LSHIFT */
+#define OP_Goto 91
+#define OP_TableLock 92
+#define OP_FifoRead 93
+#define OP_Clear 94
+#define OP_MoveLt 95
+#define OP_Le 70 /* same as TK_LE */
+#define OP_VerifyCookie 96
+#define OP_AggStep 97
+#define OP_ToText 138 /* same as TK_TO_TEXT */
+#define OP_Not 16 /* same as TK_NOT */
+#define OP_ToReal 142 /* same as TK_TO_REAL */
+#define OP_SetNumColumns 98
+#define OP_Transaction 99
+#define OP_VFilter 100
+#define OP_Ne 67 /* same as TK_NE */
+#define OP_VDestroy 101
+#define OP_ContextPop 102
+#define OP_BitOr 75 /* same as TK_BITOR */
+#define OP_Next 103
+#define OP_IdxInsert 104
+#define OP_Lt 71 /* same as TK_LT */
+#define OP_Insert 105
+#define OP_Destroy 106
+#define OP_ReadCookie 107
+#define OP_ForceInt 108
+#define OP_LoadAnalysis 109
+#define OP_Explain 110
+#define OP_OpenPseudo 111
+#define OP_OpenEphemeral 112
+#define OP_Null 113
+#define OP_Move 114
+#define OP_Blob 115
+#define OP_Add 78 /* same as TK_PLUS */
+#define OP_Rewind 116
+#define OP_MoveGe 117
+#define OP_VBegin 118
+#define OP_VUpdate 119
+#define OP_IfZero 120
+#define OP_BitNot 87 /* same as TK_BITNOT */
+#define OP_VCreate 121
+#define OP_Found 122
+#define OP_IfPos 123
+#define OP_NullRow 124
+
+/* The following opcode values are never used */
+#define OP_NotUsed_126 126
+#define OP_NotUsed_127 127
+#define OP_NotUsed_128 128
+#define OP_NotUsed_129 129
+#define OP_NotUsed_130 130
+#define OP_NotUsed_131 131
+#define OP_NotUsed_132 132
+#define OP_NotUsed_133 133
+#define OP_NotUsed_134 134
+#define OP_NotUsed_135 135
+#define OP_NotUsed_136 136
+#define OP_NotUsed_137 137
+
+
+/* Properties such as "out2" or "jump" that are specified in
+** comments following the "case" for each opcode in the vdbe.c
+** are encoded into bitvectors as follows:
+*/
+#define OPFLG_JUMP 0x0001 /* jump: P2 holds jmp target */
+#define OPFLG_OUT2_PRERELEASE 0x0002 /* out2-prerelease: */
+#define OPFLG_IN1 0x0004 /* in1: P1 is an input */
+#define OPFLG_IN2 0x0008 /* in2: P2 is an input */
+#define OPFLG_IN3 0x0010 /* in3: P3 is an input */
+#define OPFLG_OUT3 0x0020 /* out3: P3 is an output */
+#define OPFLG_INITIALIZER {\
+/* 0 */ 0x00, 0x01, 0x00, 0x00, 0x10, 0x02, 0x11, 0x00,\
+/* 8 */ 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00,\
+/* 16 */ 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00,\
+/* 24 */ 0x00, 0x02, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00,\
+/* 32 */ 0x00, 0x02, 0x11, 0x11, 0x02, 0x05, 0x00, 0x02,\
+/* 40 */ 0x11, 0x04, 0x00, 0x00, 0x0c, 0x11, 0x01, 0x02,\
+/* 48 */ 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x04,\
+/* 56 */ 0x00, 0x00, 0x00, 0x11, 0x2c, 0x2c, 0x00, 0x00,\
+/* 64 */ 0x11, 0x05, 0x05, 0x15, 0x15, 0x15, 0x15, 0x15,\
+/* 72 */ 0x15, 0x05, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,\
+/* 80 */ 0x2c, 0x2c, 0x2c, 0x2c, 0x00, 0x00, 0x00, 0x04,\
+/* 88 */ 0x02, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x11,\
+/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,\
+/* 104 */ 0x08, 0x00, 0x02, 0x02, 0x05, 0x00, 0x00, 0x00,\
+/* 112 */ 0x00, 0x02, 0x00, 0x02, 0x01, 0x11, 0x00, 0x00,\
+/* 120 */ 0x05, 0x00, 0x11, 0x05, 0x00, 0x02, 0x00, 0x00,\
+/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 136 */ 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04,}
+
+/************** End of opcodes.h *********************************************/
+/************** Continuing where we left off in vdbe.h ***********************/
+
+/*
+** Prototypes for the VDBE interface. See comments on the implementation
+** for a description of what each of these routines does.
+*/
+SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(sqlite3*);
+SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int);
+SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int);
+SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int);
+SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int);
+SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int);
+SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp);
+SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1);
+SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2);
+SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3);
+SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u8 P5);
+SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr);
+SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe*, int addr, int N);
+SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N);
+SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int);
+SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int);
+SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe*);
+SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*);
+SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int);
+SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*);
+SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int);
+SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe*);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE void sqlite3VdbeTrace(Vdbe*,FILE*);
+#endif
+SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe*);
+SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe*, int);
+SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int);
+SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, int);
+SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe*);
+SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe*);
+SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n);
+SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*);
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+SQLITE_PRIVATE int sqlite3VdbeReleaseMemory(int);
+#endif
+SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,void*,int);
+SQLITE_PRIVATE void sqlite3VdbeDeleteUnpackedRecord(UnpackedRecord*);
+SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*);
+
+
+#ifndef NDEBUG
+SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe*, const char*, ...);
+# define VdbeComment(X) sqlite3VdbeComment X
+#else
+# define VdbeComment(X)
+#endif
+
+#endif
+
+/************** End of vdbe.h ************************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+/************** Include pager.h in the middle of sqliteInt.h *****************/
+/************** Begin file pager.h *******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the sqlite page cache
+** subsystem. The page cache subsystem reads and writes a file a page
+** at a time and provides a journal for rollback.
+**
+** @(#) $Id: pager.h,v 1.72 2008/05/01 17:03:49 drh Exp $
+*/
+
+#ifndef _PAGER_H_
+#define _PAGER_H_
+
+/*
+** The type used to represent a page number. The first page in a file
+** is called page 1. 0 is used to represent "not a page".
+*/
+typedef unsigned int Pgno;
+
+/*
+** Each open file is managed by a separate instance of the "Pager" structure.
+*/
+typedef struct Pager Pager;
+
+/*
+** Handle type for pages.
+*/
+typedef struct PgHdr DbPage;
+
+/*
+** Allowed values for the flags parameter to sqlite3PagerOpen().
+**
+** NOTE: This values must match the corresponding BTREE_ values in btree.h.
+*/
+#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */
+#define PAGER_NO_READLOCK 0x0002 /* Omit readlocks on readonly files */
+
+/*
+** Valid values for the second argument to sqlite3PagerLockingMode().
+*/
+#define PAGER_LOCKINGMODE_QUERY -1
+#define PAGER_LOCKINGMODE_NORMAL 0
+#define PAGER_LOCKINGMODE_EXCLUSIVE 1
+
+/*
+** Valid values for the second argument to sqlite3PagerJournalMode().
+*/
+#define PAGER_JOURNALMODE_QUERY -1
+#define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */
+#define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */
+#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */
+
+/*
+** See source code comments for a detailed description of the following
+** routines:
+*/
+SQLITE_PRIVATE int sqlite3PagerOpen(sqlite3_vfs *, Pager **ppPager, const char*, int,int,int);
+SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(Pager*, BusyHandler *pBusyHandler);
+SQLITE_PRIVATE void sqlite3PagerSetDestructor(Pager*, void(*)(DbPage*,int));
+SQLITE_PRIVATE void sqlite3PagerSetReiniter(Pager*, void(*)(DbPage*,int));
+SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u16*);
+SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager*, int);
+SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*);
+SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int);
+SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager);
+SQLITE_PRIVATE int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag);
+#define sqlite3PagerGet(A,B,C) sqlite3PagerAcquire(A,B,C,0)
+SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno);
+SQLITE_PRIVATE int sqlite3PagerRef(DbPage*);
+SQLITE_PRIVATE int sqlite3PagerUnref(DbPage*);
+SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*);
+SQLITE_PRIVATE int sqlite3PagerPagecount(Pager*);
+SQLITE_PRIVATE int sqlite3PagerTruncate(Pager*,Pgno);
+SQLITE_PRIVATE int sqlite3PagerBegin(DbPage*, int exFlag);
+SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, Pgno, int);
+SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*);
+SQLITE_PRIVATE int sqlite3PagerRollback(Pager*);
+SQLITE_PRIVATE int sqlite3PagerIsreadonly(Pager*);
+SQLITE_PRIVATE int sqlite3PagerStmtBegin(Pager*);
+SQLITE_PRIVATE int sqlite3PagerStmtCommit(Pager*);
+SQLITE_PRIVATE int sqlite3PagerStmtRollback(Pager*);
+SQLITE_PRIVATE void sqlite3PagerDontRollback(DbPage*);
+SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*);
+SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*);
+SQLITE_PRIVATE void sqlite3PagerSetSafetyLevel(Pager*,int,int);
+SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*);
+SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager*);
+SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*);
+SQLITE_PRIVATE const char *sqlite3PagerDirname(Pager*);
+SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*);
+SQLITE_PRIVATE int sqlite3PagerNosync(Pager*);
+SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno);
+SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *);
+SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *);
+SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int);
+SQLITE_PRIVATE int sqlite3PagerJournalMode(Pager *, int);
+SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*);
+SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager);
+
+#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) && !defined(SQLITE_OMIT_DISKIO)
+SQLITE_PRIVATE int sqlite3PagerReleaseMemory(int);
+#endif
+
+#ifdef SQLITE_HAS_CODEC
+SQLITE_PRIVATE void sqlite3PagerSetCodec(Pager*,void*(*)(void*,void*,Pgno,int),void*);
+#endif
+
+#if !defined(NDEBUG) || defined(SQLITE_TEST)
+SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*);
+SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*);
+#endif
+
+#ifdef SQLITE_TEST
+SQLITE_PRIVATE int *sqlite3PagerStats(Pager*);
+SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*);
+#endif
+
+#ifdef SQLITE_TEST
+void disable_simulated_io_errors(void);
+void enable_simulated_io_errors(void);
+#else
+# define disable_simulated_io_errors()
+# define enable_simulated_io_errors()
+#endif
+
+#endif /* _PAGER_H_ */
+
+/************** End of pager.h ***********************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+
+/************** Include os.h in the middle of sqliteInt.h ********************/
+/************** Begin file os.h **********************************************/
+/*
+** 2001 September 16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This header file (together with is companion C source-code file
+** "os.c") attempt to abstract the underlying operating system so that
+** the SQLite library will work on both POSIX and windows systems.
+**
+** This header file is #include-ed by sqliteInt.h and thus ends up
+** being included by every source file.
+*/
+#ifndef _SQLITE_OS_H_
+#define _SQLITE_OS_H_
+
+/*
+** Figure out if we are dealing with Unix, Windows, or some other
+** operating system. After the following block of preprocess macros,
+** all of OS_UNIX, OS_WIN, OS_OS2, and OS_OTHER will defined to either
+** 1 or 0. One of the four will be 1. The other three will be 0.
+*/
+#if defined(OS_OTHER)
+# if OS_OTHER==1
+# undef OS_UNIX
+# define OS_UNIX 0
+# undef OS_WIN
+# define OS_WIN 0
+# undef OS_OS2
+# define OS_OS2 0
+# else
+# undef OS_OTHER
+# endif
+#endif
+#if !defined(OS_UNIX) && !defined(OS_OTHER)
+# define OS_OTHER 0
+# ifndef OS_WIN
+# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
+# define OS_WIN 1
+# define OS_UNIX 0
+# define OS_OS2 0
+# elif defined(__EMX__) || defined(_OS2) || defined(OS2) || defined(_OS2_) || defined(__OS2__)
+# define OS_WIN 0
+# define OS_UNIX 0
+# define OS_OS2 1
+# else
+# define OS_WIN 0
+# define OS_UNIX 1
+# define OS_OS2 0
+# endif
+# else
+# define OS_UNIX 0
+# define OS_OS2 0
+# endif
+#else
+# ifndef OS_WIN
+# define OS_WIN 0
+# endif
+#endif
+
+
+
+/*
+** Define the maximum size of a temporary filename
+*/
+#if OS_WIN
+# include <windows.h>
+# define SQLITE_TEMPNAME_SIZE (MAX_PATH+50)
+#elif OS_OS2
+# if (__GNUC__ > 3 || __GNUC__ == 3 && __GNUC_MINOR__ >= 3) && defined(OS2_HIGH_MEMORY)
+# include <os2safe.h> /* has to be included before os2.h for linking to work */
+# endif
+# define INCL_DOSDATETIME
+# define INCL_DOSFILEMGR
+# define INCL_DOSERRORS
+# define INCL_DOSMISC
+# define INCL_DOSPROCESS
+# define INCL_DOSMODULEMGR
+# define INCL_DOSSEMAPHORES
+# include <os2.h>
+# include <uconv.h>
+# define SQLITE_TEMPNAME_SIZE (CCHMAXPATHCOMP)
+#else
+# define SQLITE_TEMPNAME_SIZE 200
+#endif
+
+/* If the SET_FULLSYNC macro is not defined above, then make it
+** a no-op
+*/
+#ifndef SET_FULLSYNC
+# define SET_FULLSYNC(x,y)
+#endif
+
+/*
+** The default size of a disk sector
+*/
+#ifndef SQLITE_DEFAULT_SECTOR_SIZE
+# define SQLITE_DEFAULT_SECTOR_SIZE 512
+#endif
+
+/*
+** Temporary files are named starting with this prefix followed by 16 random
+** alphanumeric characters, and no file extension. They are stored in the
+** OS's standard temporary file directory, and are deleted prior to exit.
+** If sqlite is being embedded in another program, you may wish to change the
+** prefix to reflect your program's name, so that if your program exits
+** prematurely, old temporary files can be easily identified. This can be done
+** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line.
+**
+** 2006-10-31: The default prefix used to be "sqlite_". But then
+** Mcafee started using SQLite in their anti-virus product and it
+** started putting files with the "sqlite" name in the c:/temp folder.
+** This annoyed many windows users. Those users would then do a
+** Google search for "sqlite", find the telephone numbers of the
+** developers and call to wake them up at night and complain.
+** For this reason, the default name prefix is changed to be "sqlite"
+** spelled backwards. So the temp files are still identified, but
+** anybody smart enough to figure out the code is also likely smart
+** enough to know that calling the developer will not help get rid
+** of the file.
+*/
+#ifndef SQLITE_TEMP_FILE_PREFIX
+# define SQLITE_TEMP_FILE_PREFIX "etilqs_"
+#endif
+
+/*
+** The following values may be passed as the second argument to
+** sqlite3OsLock(). The various locks exhibit the following semantics:
+**
+** SHARED: Any number of processes may hold a SHARED lock simultaneously.
+** RESERVED: A single process may hold a RESERVED lock on a file at
+** any time. Other processes may hold and obtain new SHARED locks.
+** PENDING: A single process may hold a PENDING lock on a file at
+** any one time. Existing SHARED locks may persist, but no new
+** SHARED locks may be obtained by other processes.
+** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks.
+**
+** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a
+** process that requests an EXCLUSIVE lock may actually obtain a PENDING
+** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to
+** sqlite3OsLock().
+*/
+#define NO_LOCK 0
+#define SHARED_LOCK 1
+#define RESERVED_LOCK 2
+#define PENDING_LOCK 3
+#define EXCLUSIVE_LOCK 4
+
+/*
+** File Locking Notes: (Mostly about windows but also some info for Unix)
+**
+** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because
+** those functions are not available. So we use only LockFile() and
+** UnlockFile().
+**
+** LockFile() prevents not just writing but also reading by other processes.
+** A SHARED_LOCK is obtained by locking a single randomly-chosen
+** byte out of a specific range of bytes. The lock byte is obtained at
+** random so two separate readers can probably access the file at the
+** same time, unless they are unlucky and choose the same lock byte.
+** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range.
+** There can only be one writer. A RESERVED_LOCK is obtained by locking
+** a single byte of the file that is designated as the reserved lock byte.
+** A PENDING_LOCK is obtained by locking a designated byte different from
+** the RESERVED_LOCK byte.
+**
+** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available,
+** which means we can use reader/writer locks. When reader/writer locks
+** are used, the lock is placed on the same range of bytes that is used
+** for probabilistic locking in Win95/98/ME. Hence, the locking scheme
+** will support two or more Win95 readers or two or more WinNT readers.
+** But a single Win95 reader will lock out all WinNT readers and a single
+** WinNT reader will lock out all other Win95 readers.
+**
+** The following #defines specify the range of bytes used for locking.
+** SHARED_SIZE is the number of bytes available in the pool from which
+** a random byte is selected for a shared lock. The pool of bytes for
+** shared locks begins at SHARED_FIRST.
+**
+** These #defines are available in sqlite_aux.h so that adaptors for
+** connecting SQLite to other operating systems can use the same byte
+** ranges for locking. In particular, the same locking strategy and
+** byte ranges are used for Unix. This leaves open the possiblity of having
+** clients on win95, winNT, and unix all talking to the same shared file
+** and all locking correctly. To do so would require that samba (or whatever
+** tool is being used for file sharing) implements locks correctly between
+** windows and unix. I'm guessing that isn't likely to happen, but by
+** using the same locking range we are at least open to the possibility.
+**
+** Locking in windows is manditory. For this reason, we cannot store
+** actual data in the bytes used for locking. The pager never allocates
+** the pages involved in locking therefore. SHARED_SIZE is selected so
+** that all locks will fit on a single page even at the minimum page size.
+** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE
+** is set high so that we don't have to allocate an unused page except
+** for very large databases. But one should test the page skipping logic
+** by setting PENDING_BYTE low and running the entire regression suite.
+**
+** Changing the value of PENDING_BYTE results in a subtly incompatible
+** file format. Depending on how it is changed, you might not notice
+** the incompatibility right away, even running a full regression test.
+** The default location of PENDING_BYTE is the first byte past the
+** 1GB boundary.
+**
+*/
+#ifndef SQLITE_TEST
+#define PENDING_BYTE 0x40000000 /* First byte past the 1GB boundary */
+#else
+SQLITE_API extern unsigned int sqlite3_pending_byte;
+#define PENDING_BYTE sqlite3_pending_byte
+#endif
+
+#define RESERVED_BYTE (PENDING_BYTE+1)
+#define SHARED_FIRST (PENDING_BYTE+2)
+#define SHARED_SIZE 510
+
+/*
+** Functions for accessing sqlite3_file methods
+*/
+SQLITE_PRIVATE int sqlite3OsClose(sqlite3_file*);
+SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset);
+SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset);
+SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size);
+SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int);
+SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize);
+SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int);
+SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int);
+SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id);
+SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*);
+SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id);
+SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id);
+
+/*
+** Functions for accessing sqlite3_vfs methods
+*/
+SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *);
+SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int);
+SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int);
+SQLITE_PRIVATE int sqlite3OsGetTempname(sqlite3_vfs *, int, char *);
+SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *);
+SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *);
+SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *);
+SQLITE_PRIVATE void *sqlite3OsDlSym(sqlite3_vfs *, void *, const char *);
+SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *);
+SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *);
+SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int);
+SQLITE_PRIVATE int sqlite3OsCurrentTime(sqlite3_vfs *, double*);
+
+/*
+** Convenience functions for opening and closing files using
+** sqlite3_malloc() to obtain space for the file-handle structure.
+*/
+SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*);
+SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *);
+
+/*
+** Each OS-specific backend defines an instance of the following
+** structure for returning a pointer to its sqlite3_vfs. If OS_OTHER
+** is defined (meaning that the application-defined OS interface layer
+** is used) then there is no default VFS. The application must
+** register one or more VFS structures using sqlite3_vfs_register()
+** before attempting to use SQLite.
+*/
+SQLITE_PRIVATE sqlite3_vfs *sqlite3OsDefaultVfs(void);
+
+#endif /* _SQLITE_OS_H_ */
+
+/************** End of os.h **************************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+/************** Include mutex.h in the middle of sqliteInt.h *****************/
+/************** Begin file mutex.h *******************************************/
+/*
+** 2007 August 28
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file contains the common header for all mutex implementations.
+** The sqliteInt.h header #includes this file so that it is available
+** to all source files. We break it out in an effort to keep the code
+** better organized.
+**
+** NOTE: source files should *not* #include this header file directly.
+** Source files should #include the sqliteInt.h file and let that file
+** include this one indirectly.
+**
+** $Id: mutex.h,v 1.2 2007/08/30 14:10:30 drh Exp $
+*/
+
+
+#ifdef SQLITE_MUTEX_APPDEF
+/*
+** If SQLITE_MUTEX_APPDEF is defined, then this whole module is
+** omitted and equivalent functionality must be provided by the
+** application that links against the SQLite library.
+*/
+#else
+/*
+** Figure out what version of the code to use. The choices are
+**
+** SQLITE_MUTEX_NOOP For single-threaded applications that
+** do not desire error checking.
+**
+** SQLITE_MUTEX_NOOP_DEBUG For single-threaded applications with
+** error checking to help verify that mutexes
+** are being used correctly even though they
+** are not needed. Used when SQLITE_DEBUG is
+** defined on single-threaded builds.
+**
+** SQLITE_MUTEX_PTHREADS For multi-threaded applications on Unix.
+**
+** SQLITE_MUTEX_W32 For multi-threaded applications on Win32.
+**
+** SQLITE_MUTEX_OS2 For multi-threaded applications on OS/2.
+*/
+#define SQLITE_MUTEX_NOOP 1 /* The default */
+#if defined(SQLITE_DEBUG) && !SQLITE_THREADSAFE
+# undef SQLITE_MUTEX_NOOP
+# define SQLITE_MUTEX_NOOP_DEBUG
+#endif
+#if defined(SQLITE_MUTEX_NOOP) && SQLITE_THREADSAFE && OS_UNIX
+# undef SQLITE_MUTEX_NOOP
+# define SQLITE_MUTEX_PTHREADS
+#endif
+#if defined(SQLITE_MUTEX_NOOP) && SQLITE_THREADSAFE && OS_WIN
+# undef SQLITE_MUTEX_NOOP
+# define SQLITE_MUTEX_W32
+#endif
+#if defined(SQLITE_MUTEX_NOOP) && SQLITE_THREADSAFE && OS_OS2
+# undef SQLITE_MUTEX_NOOP
+# define SQLITE_MUTEX_OS2
+#endif
+
+#ifdef SQLITE_MUTEX_NOOP
+/*
+** If this is a no-op implementation, implement everything as macros.
+*/
+#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8)
+#define sqlite3_mutex_free(X)
+#define sqlite3_mutex_enter(X)
+#define sqlite3_mutex_try(X) SQLITE_OK
+#define sqlite3_mutex_leave(X)
+#define sqlite3_mutex_held(X) 1
+#define sqlite3_mutex_notheld(X) 1
+#endif
+
+#endif /* SQLITE_MUTEX_APPDEF */
+
+/************** End of mutex.h ***********************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
+
+
+/*
+** Each database file to be accessed by the system is an instance
+** of the following structure. There are normally two of these structures
+** in the sqlite.aDb[] array. aDb[0] is the main database file and
+** aDb[1] is the database file used to hold temporary tables. Additional
+** databases may be attached.
+*/
+struct Db {
+ char *zName; /* Name of this database */
+ Btree *pBt; /* The B*Tree structure for this database file */
+ u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */
+ u8 safety_level; /* How aggressive at synching data to disk */
+ void *pAux; /* Auxiliary data. Usually NULL */
+ void (*xFreeAux)(void*); /* Routine to free pAux */
+ Schema *pSchema; /* Pointer to database schema (possibly shared) */
+};
+
+/*
+** An instance of the following structure stores a database schema.
+**
+** If there are no virtual tables configured in this schema, the
+** Schema.db variable is set to NULL. After the first virtual table
+** has been added, it is set to point to the database connection
+** used to create the connection. Once a virtual table has been
+** added to the Schema structure and the Schema.db variable populated,
+** only that database connection may use the Schema to prepare
+** statements.
+*/
+struct Schema {
+ int schema_cookie; /* Database schema version number for this file */
+ Hash tblHash; /* All tables indexed by name */
+ Hash idxHash; /* All (named) indices indexed by name */
+ Hash trigHash; /* All triggers indexed by name */
+ Hash aFKey; /* Foreign keys indexed by to-table */
+ Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */
+ u8 file_format; /* Schema format version for this file */
+ u8 enc; /* Text encoding used by this database */
+ u16 flags; /* Flags associated with this schema */
+ int cache_size; /* Number of pages to use in the cache */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ sqlite3 *db; /* "Owner" connection. See comment above */
+#endif
+};
+
+/*
+** These macros can be used to test, set, or clear bits in the
+** Db.flags field.
+*/
+#define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))==(P))
+#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))!=0)
+#define DbSetProperty(D,I,P) (D)->aDb[I].pSchema->flags|=(P)
+#define DbClearProperty(D,I,P) (D)->aDb[I].pSchema->flags&=~(P)
+
+/*
+** Allowed values for the DB.flags field.
+**
+** The DB_SchemaLoaded flag is set after the database schema has been
+** read into internal hash tables.
+**
+** DB_UnresetViews means that one or more views have column names that
+** have been filled out. If the schema changes, these column names might
+** changes and so the view will need to be reset.
+*/
+#define DB_SchemaLoaded 0x0001 /* The schema has been loaded */
+#define DB_UnresetViews 0x0002 /* Some views have defined column names */
+#define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */
+
+/*
+** The number of different kinds of things that can be limited
+** using the sqlite3_limit() interface.
+*/
+#define SQLITE_N_LIMIT (SQLITE_LIMIT_VARIABLE_NUMBER+1)
+
+/*
+** Each database is an instance of the following structure.
+**
+** The sqlite.lastRowid records the last insert rowid generated by an
+** insert statement. Inserts on views do not affect its value. Each
+** trigger has its own context, so that lastRowid can be updated inside
+** triggers as usual. The previous value will be restored once the trigger
+** exits. Upon entering a before or instead of trigger, lastRowid is no
+** longer (since after version 2.8.12) reset to -1.
+**
+** The sqlite.nChange does not count changes within triggers and keeps no
+** context. It is reset at start of sqlite3_exec.
+** The sqlite.lsChange represents the number of changes made by the last
+** insert, update, or delete statement. It remains constant throughout the
+** length of a statement and is then updated by OP_SetCounts. It keeps a
+** context stack just like lastRowid so that the count of changes
+** within a trigger is not seen outside the trigger. Changes to views do not
+** affect the value of lsChange.
+** The sqlite.csChange keeps track of the number of current changes (since
+** the last statement) and is used to update sqlite_lsChange.
+**
+** The member variables sqlite.errCode, sqlite.zErrMsg and sqlite.zErrMsg16
+** store the most recent error code and, if applicable, string. The
+** internal function sqlite3Error() is used to set these variables
+** consistently.
+*/
+struct sqlite3 {
+ sqlite3_vfs *pVfs; /* OS Interface */
+ int nDb; /* Number of backends currently in use */
+ Db *aDb; /* All backends */
+ int flags; /* Miscellanous flags. See below */
+ int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */
+ int errCode; /* Most recent error code (SQLITE_*) */
+ int errMask; /* & result codes with this before returning */
+ u8 autoCommit; /* The auto-commit flag. */
+ u8 temp_store; /* 1: file 2: memory 0: default */
+ u8 mallocFailed; /* True if we have seen a malloc failure */
+ u8 dfltLockMode; /* Default locking-mode for attached dbs */
+ u8 dfltJournalMode; /* Default journal mode for attached dbs */
+ signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */
+ int nextPagesize; /* Pagesize after VACUUM if >0 */
+ int nTable; /* Number of tables in the database */
+ CollSeq *pDfltColl; /* The default collating sequence (BINARY) */
+ i64 lastRowid; /* ROWID of most recent insert (see above) */
+ i64 priorNewRowid; /* Last randomly generated ROWID */
+ int magic; /* Magic number for detect library misuse */
+ int nChange; /* Value returned by sqlite3_changes() */
+ int nTotalChange; /* Value returned by sqlite3_total_changes() */
+ sqlite3_mutex *mutex; /* Connection mutex */
+ int aLimit[SQLITE_N_LIMIT]; /* Limits */
+ struct sqlite3InitInfo { /* Information used during initialization */
+ int iDb; /* When back is being initialized */
+ int newTnum; /* Rootpage of table being initialized */
+ u8 busy; /* TRUE if currently initializing */
+ } init;
+ int nExtension; /* Number of loaded extensions */
+ void **aExtension; /* Array of shared libraray handles */
+ struct Vdbe *pVdbe; /* List of active virtual machines */
+ int activeVdbeCnt; /* Number of vdbes currently executing */
+ void (*xTrace)(void*,const char*); /* Trace function */
+ void *pTraceArg; /* Argument to the trace function */
+ void (*xProfile)(void*,const char*,u64); /* Profiling function */
+ void *pProfileArg; /* Argument to profile function */
+ void *pCommitArg; /* Argument to xCommitCallback() */
+ int (*xCommitCallback)(void*); /* Invoked at every commit. */
+ void *pRollbackArg; /* Argument to xRollbackCallback() */
+ void (*xRollbackCallback)(void*); /* Invoked at every commit. */
+ void *pUpdateArg;
+ void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
+ void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
+ void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
+ void *pCollNeededArg;
+ sqlite3_value *pErr; /* Most recent error message */
+ char *zErrMsg; /* Most recent error message (UTF-8 encoded) */
+ char *zErrMsg16; /* Most recent error message (UTF-16 encoded) */
+ union {
+ int isInterrupted; /* True if sqlite3_interrupt has been called */
+ double notUsed1; /* Spacer */
+ } u1;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
+ /* Access authorization function */
+ void *pAuthArg; /* 1st argument to the access auth function */
+#endif
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ int (*xProgress)(void *); /* The progress callback */
+ void *pProgressArg; /* Argument to the progress callback */
+ int nProgressOps; /* Number of opcodes for progress callback */
+#endif
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ Hash aModule; /* populated by sqlite3_create_module() */
+ Table *pVTab; /* vtab with active Connect/Create method */
+ sqlite3_vtab **aVTrans; /* Virtual tables with open transactions */
+ int nVTrans; /* Allocated size of aVTrans */
+#endif
+ Hash aFunc; /* All functions that can be in SQL exprs */
+ Hash aCollSeq; /* All collating sequences */
+ BusyHandler busyHandler; /* Busy callback */
+ int busyTimeout; /* Busy handler timeout, in msec */
+ Db aDbStatic[2]; /* Static space for the 2 default backends */
+#ifdef SQLITE_SSE
+ sqlite3_stmt *pFetch; /* Used by SSE to fetch stored statements */
+#endif
+};
+
+/*
+** A macro to discover the encoding of a database.
+*/
+#define ENC(db) ((db)->aDb[0].pSchema->enc)
+
+/*
+** Possible values for the sqlite.flags and or Db.flags fields.
+**
+** On sqlite.flags, the SQLITE_InTrans value means that we have
+** executed a BEGIN. On Db.flags, SQLITE_InTrans means a statement
+** transaction is active on that particular database file.
+*/
+#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */
+#define SQLITE_InTrans 0x00000008 /* True if in a transaction */
+#define SQLITE_InternChanges 0x00000010 /* Uncommitted Hash table changes */
+#define SQLITE_FullColNames 0x00000020 /* Show full column names on SELECT */
+#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */
+#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */
+ /* DELETE, or UPDATE and return */
+ /* the count using a callback. */
+#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */
+ /* result set is empty */
+#define SQLITE_SqlTrace 0x00000200 /* Debug print SQL as it executes */
+#define SQLITE_VdbeListing 0x00000400 /* Debug listings of VDBE programs */
+#define SQLITE_WriteSchema 0x00000800 /* OK to update SQLITE_MASTER */
+#define SQLITE_NoReadlock 0x00001000 /* Readlocks are omitted when
+ ** accessing read-only databases */
+#define SQLITE_IgnoreChecks 0x00002000 /* Do not enforce check constraints */
+#define SQLITE_ReadUncommitted 0x00004000 /* For shared-cache mode */
+#define SQLITE_LegacyFileFmt 0x00008000 /* Create new databases in format 1 */
+#define SQLITE_FullFSync 0x00010000 /* Use full fsync on the backend */
+#define SQLITE_LoadExtension 0x00020000 /* Enable load_extension */
+
+#define SQLITE_RecoveryMode 0x00040000 /* Ignore schema errors */
+#define SQLITE_SharedCache 0x00080000 /* Cache sharing is enabled */
+#define SQLITE_Vtab 0x00100000 /* There exists a virtual table */
+
+/*
+** Possible values for the sqlite.magic field.
+** The numbers are obtained at random and have no special meaning, other
+** than being distinct from one another.
+*/
+#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */
+#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */
+#define SQLITE_MAGIC_SICK 0x4b771290 /* Error and awaiting close */
+#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */
+#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */
+
+/*
+** Each SQL function is defined by an instance of the following
+** structure. A pointer to this structure is stored in the sqlite.aFunc
+** hash table. When multiple functions have the same name, the hash table
+** points to a linked list of these structures.
+*/
+struct FuncDef {
+ i16 nArg; /* Number of arguments. -1 means unlimited */
+ u8 iPrefEnc; /* Preferred text encoding (SQLITE_UTF8, 16LE, 16BE) */
+ u8 needCollSeq; /* True if sqlite3GetFuncCollSeq() might be called */
+ u8 flags; /* Some combination of SQLITE_FUNC_* */
+ void *pUserData; /* User data parameter */
+ FuncDef *pNext; /* Next function with same name */
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**); /* Aggregate step */
+ void (*xFinalize)(sqlite3_context*); /* Aggregate finializer */
+ char zName[1]; /* SQL name of the function. MUST BE LAST */
+};
+
+/*
+** Each SQLite module (virtual table definition) is defined by an
+** instance of the following structure, stored in the sqlite3.aModule
+** hash table.
+*/
+struct Module {
+ const sqlite3_module *pModule; /* Callback pointers */
+ const char *zName; /* Name passed to create_module() */
+ void *pAux; /* pAux passed to create_module() */
+ void (*xDestroy)(void *); /* Module destructor function */
+};
+
+/*
+** Possible values for FuncDef.flags
+*/
+#define SQLITE_FUNC_LIKE 0x01 /* Candidate for the LIKE optimization */
+#define SQLITE_FUNC_CASE 0x02 /* Case-sensitive LIKE-type function */
+#define SQLITE_FUNC_EPHEM 0x04 /* Ephermeral. Delete with VDBE */
+
+/*
+** information about each column of an SQL table is held in an instance
+** of this structure.
+*/
+struct Column {
+ char *zName; /* Name of this column */
+ Expr *pDflt; /* Default value of this column */
+ char *zType; /* Data type for this column */
+ char *zColl; /* Collating sequence. If NULL, use the default */
+ u8 notNull; /* True if there is a NOT NULL constraint */
+ u8 isPrimKey; /* True if this column is part of the PRIMARY KEY */
+ char affinity; /* One of the SQLITE_AFF_... values */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ u8 isHidden; /* True if this column is 'hidden' */
+#endif
+};
+
+/*
+** A "Collating Sequence" is defined by an instance of the following
+** structure. Conceptually, a collating sequence consists of a name and
+** a comparison routine that defines the order of that sequence.
+**
+** There may two seperate implementations of the collation function, one
+** that processes text in UTF-8 encoding (CollSeq.xCmp) and another that
+** processes text encoded in UTF-16 (CollSeq.xCmp16), using the machine
+** native byte order. When a collation sequence is invoked, SQLite selects
+** the version that will require the least expensive encoding
+** translations, if any.
+**
+** The CollSeq.pUser member variable is an extra parameter that passed in
+** as the first argument to the UTF-8 comparison function, xCmp.
+** CollSeq.pUser16 is the equivalent for the UTF-16 comparison function,
+** xCmp16.
+**
+** If both CollSeq.xCmp and CollSeq.xCmp16 are NULL, it means that the
+** collating sequence is undefined. Indices built on an undefined
+** collating sequence may not be read or written.
+*/
+struct CollSeq {
+ char *zName; /* Name of the collating sequence, UTF-8 encoded */
+ u8 enc; /* Text encoding handled by xCmp() */
+ u8 type; /* One of the SQLITE_COLL_... values below */
+ void *pUser; /* First argument to xCmp() */
+ int (*xCmp)(void*,int, const void*, int, const void*);
+ void (*xDel)(void*); /* Destructor for pUser */
+};
+
+/*
+** Allowed values of CollSeq flags:
+*/
+#define SQLITE_COLL_BINARY 1 /* The default memcmp() collating sequence */
+#define SQLITE_COLL_NOCASE 2 /* The built-in NOCASE collating sequence */
+#define SQLITE_COLL_REVERSE 3 /* The built-in REVERSE collating sequence */
+#define SQLITE_COLL_USER 0 /* Any other user-defined collating sequence */
+
+/*
+** A sort order can be either ASC or DESC.
+*/
+#define SQLITE_SO_ASC 0 /* Sort in ascending order */
+#define SQLITE_SO_DESC 1 /* Sort in ascending order */
+
+/*
+** Column affinity types.
+**
+** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and
+** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve
+** the speed a little by number the values consecutively.
+**
+** But rather than start with 0 or 1, we begin with 'a'. That way,
+** when multiple affinity types are concatenated into a string and
+** used as the P4 operand, they will be more readable.
+**
+** Note also that the numeric types are grouped together so that testing
+** for a numeric type is a single comparison.
+*/
+#define SQLITE_AFF_TEXT 'a'
+#define SQLITE_AFF_NONE 'b'
+#define SQLITE_AFF_NUMERIC 'c'
+#define SQLITE_AFF_INTEGER 'd'
+#define SQLITE_AFF_REAL 'e'
+
+#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC)
+
+/*
+** The SQLITE_AFF_MASK values masks off the significant bits of an
+** affinity value.
+*/
+#define SQLITE_AFF_MASK 0x67
+
+/*
+** Additional bit values that can be ORed with an affinity without
+** changing the affinity.
+*/
+#define SQLITE_JUMPIFNULL 0x08 /* jumps if either operand is NULL */
+#define SQLITE_NULLEQUAL 0x10 /* compare NULLs equal */
+#define SQLITE_STOREP2 0x80 /* Store result in reg[P2] rather than jump */
+
+/*
+** Each SQL table is represented in memory by an instance of the
+** following structure.
+**
+** Table.zName is the name of the table. The case of the original
+** CREATE TABLE statement is stored, but case is not significant for
+** comparisons.
+**
+** Table.nCol is the number of columns in this table. Table.aCol is a
+** pointer to an array of Column structures, one for each column.
+**
+** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of
+** the column that is that key. Otherwise Table.iPKey is negative. Note
+** that the datatype of the PRIMARY KEY must be INTEGER for this field to
+** be set. An INTEGER PRIMARY KEY is used as the rowid for each row of
+** the table. If a table has no INTEGER PRIMARY KEY, then a random rowid
+** is generated for each row of the table. Table.hasPrimKey is true if
+** the table has any PRIMARY KEY, INTEGER or otherwise.
+**
+** Table.tnum is the page number for the root BTree page of the table in the
+** database file. If Table.iDb is the index of the database table backend
+** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that
+** holds temporary tables and indices. If Table.isEphem
+** is true, then the table is stored in a file that is automatically deleted
+** when the VDBE cursor to the table is closed. In this case Table.tnum
+** refers VDBE cursor number that holds the table open, not to the root
+** page number. Transient tables are used to hold the results of a
+** sub-query that appears instead of a real table name in the FROM clause
+** of a SELECT statement.
+*/
+struct Table {
+ char *zName; /* Name of the table */
+ int nCol; /* Number of columns in this table */
+ Column *aCol; /* Information about each column */
+ int iPKey; /* If not less then 0, use aCol[iPKey] as the primary key */
+ Index *pIndex; /* List of SQL indexes on this table. */
+ int tnum; /* Root BTree node for this table (see note above) */
+ Select *pSelect; /* NULL for tables. Points to definition if a view. */
+ int nRef; /* Number of pointers to this Table */
+ Trigger *pTrigger; /* List of SQL triggers on this table */
+ FKey *pFKey; /* Linked list of all foreign keys in this table */
+ char *zColAff; /* String defining the affinity of each column */
+#ifndef SQLITE_OMIT_CHECK
+ Expr *pCheck; /* The AND of all CHECK constraints */
+#endif
+#ifndef SQLITE_OMIT_ALTERTABLE
+ int addColOffset; /* Offset in CREATE TABLE statement to add a new column */
+#endif
+ u8 readOnly; /* True if this table should not be written by the user */
+ u8 isEphem; /* True if created using OP_OpenEphermeral */
+ u8 hasPrimKey; /* True if there exists a primary key */
+ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */
+ u8 autoInc; /* True if the integer primary key is autoincrement */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ u8 isVirtual; /* True if this is a virtual table */
+ u8 isCommit; /* True once the CREATE TABLE has been committed */
+ Module *pMod; /* Pointer to the implementation of the module */
+ sqlite3_vtab *pVtab; /* Pointer to the module instance */
+ int nModuleArg; /* Number of arguments to the module */
+ char **azModuleArg; /* Text of all module args. [0] is module name */
+#endif
+ Schema *pSchema; /* Schema that contains this table */
+};
+
+/*
+** Test to see whether or not a table is a virtual table. This is
+** done as a macro so that it will be optimized out when virtual
+** table support is omitted from the build.
+*/
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+# define IsVirtual(X) ((X)->isVirtual)
+# define IsHiddenColumn(X) ((X)->isHidden)
+#else
+# define IsVirtual(X) 0
+# define IsHiddenColumn(X) 0
+#endif
+
+/*
+** Each foreign key constraint is an instance of the following structure.
+**
+** A foreign key is associated with two tables. The "from" table is
+** the table that contains the REFERENCES clause that creates the foreign
+** key. The "to" table is the table that is named in the REFERENCES clause.
+** Consider this example:
+**
+** CREATE TABLE ex1(
+** a INTEGER PRIMARY KEY,
+** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x)
+** );
+**
+** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2".
+**
+** Each REFERENCES clause generates an instance of the following structure
+** which is attached to the from-table. The to-table need not exist when
+** the from-table is created. The existance of the to-table is not checked
+** until an attempt is made to insert data into the from-table.
+**
+** The sqlite.aFKey hash table stores pointers to this structure
+** given the name of a to-table. For each to-table, all foreign keys
+** associated with that table are on a linked list using the FKey.pNextTo
+** field.
+*/
+struct FKey {
+ Table *pFrom; /* The table that constains the REFERENCES clause */
+ FKey *pNextFrom; /* Next foreign key in pFrom */
+ char *zTo; /* Name of table that the key points to */
+ FKey *pNextTo; /* Next foreign key that points to zTo */
+ int nCol; /* Number of columns in this key */
+ struct sColMap { /* Mapping of columns in pFrom to columns in zTo */
+ int iFrom; /* Index of column in pFrom */
+ char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */
+ } *aCol; /* One entry for each of nCol column s */
+ u8 isDeferred; /* True if constraint checking is deferred till COMMIT */
+ u8 updateConf; /* How to resolve conflicts that occur on UPDATE */
+ u8 deleteConf; /* How to resolve conflicts that occur on DELETE */
+ u8 insertConf; /* How to resolve conflicts that occur on INSERT */
+};
+
+/*
+** SQLite supports many different ways to resolve a constraint
+** error. ROLLBACK processing means that a constraint violation
+** causes the operation in process to fail and for the current transaction
+** to be rolled back. ABORT processing means the operation in process
+** fails and any prior changes from that one operation are backed out,
+** but the transaction is not rolled back. FAIL processing means that
+** the operation in progress stops and returns an error code. But prior
+** changes due to the same operation are not backed out and no rollback
+** occurs. IGNORE means that the particular row that caused the constraint
+** error is not inserted or updated. Processing continues and no error
+** is returned. REPLACE means that preexisting database rows that caused
+** a UNIQUE constraint violation are removed so that the new insert or
+** update can proceed. Processing continues and no error is reported.
+**
+** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys.
+** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the
+** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign
+** key is set to NULL. CASCADE means that a DELETE or UPDATE of the
+** referenced table row is propagated into the row that holds the
+** foreign key.
+**
+** The following symbolic values are used to record which type
+** of action to take.
+*/
+#define OE_None 0 /* There is no constraint to check */
+#define OE_Rollback 1 /* Fail the operation and rollback the transaction */
+#define OE_Abort 2 /* Back out changes but do no rollback transaction */
+#define OE_Fail 3 /* Stop the operation but leave all prior changes */
+#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */
+#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */
+
+#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */
+#define OE_SetNull 7 /* Set the foreign key value to NULL */
+#define OE_SetDflt 8 /* Set the foreign key value to its default */
+#define OE_Cascade 9 /* Cascade the changes */
+
+#define OE_Default 99 /* Do whatever the default action is */
+
+
+/*
+** An instance of the following structure is passed as the first
+** argument to sqlite3VdbeKeyCompare and is used to control the
+** comparison of the two index keys.
+**
+** If the KeyInfo.incrKey value is true and the comparison would
+** otherwise be equal, then return a result as if the second key
+** were larger.
+*/
+struct KeyInfo {
+ sqlite3 *db; /* The database connection */
+ u8 enc; /* Text encoding - one of the TEXT_Utf* values */
+ u8 incrKey; /* Increase 2nd key by epsilon before comparison */
+ u8 prefixIsEqual; /* Treat a prefix as equal */
+ int nField; /* Number of entries in aColl[] */
+ u8 *aSortOrder; /* If defined an aSortOrder[i] is true, sort DESC */
+ CollSeq *aColl[1]; /* Collating sequence for each term of the key */
+};
+
+/*
+** Each SQL index is represented in memory by an
+** instance of the following structure.
+**
+** The columns of the table that are to be indexed are described
+** by the aiColumn[] field of this structure. For example, suppose
+** we have the following table and index:
+**
+** CREATE TABLE Ex1(c1 int, c2 int, c3 text);
+** CREATE INDEX Ex2 ON Ex1(c3,c1);
+**
+** In the Table structure describing Ex1, nCol==3 because there are
+** three columns in the table. In the Index structure describing
+** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed.
+** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the
+** first column to be indexed (c3) has an index of 2 in Ex1.aCol[].
+** The second column to be indexed (c1) has an index of 0 in
+** Ex1.aCol[], hence Ex2.aiColumn[1]==0.
+**
+** The Index.onError field determines whether or not the indexed columns
+** must be unique and what to do if they are not. When Index.onError=OE_None,
+** it means this is not a unique index. Otherwise it is a unique index
+** and the value of Index.onError indicate the which conflict resolution
+** algorithm to employ whenever an attempt is made to insert a non-unique
+** element.
+*/
+struct Index {
+ char *zName; /* Name of this index */
+ int nColumn; /* Number of columns in the table used by this index */
+ int *aiColumn; /* Which columns are used by this index. 1st is 0 */
+ unsigned *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */
+ Table *pTable; /* The SQL table being indexed */
+ int tnum; /* Page containing root of this index in database file */
+ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
+ u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */
+ char *zColAff; /* String defining the affinity of each column */
+ Index *pNext; /* The next index associated with the same table */
+ Schema *pSchema; /* Schema containing this index */
+ u8 *aSortOrder; /* Array of size Index.nColumn. True==DESC, False==ASC */
+ char **azColl; /* Array of collation sequence names for index */
+};
+
+/*
+** Each token coming out of the lexer is an instance of
+** this structure. Tokens are also used as part of an expression.
+**
+** Note if Token.z==0 then Token.dyn and Token.n are undefined and
+** may contain random values. Do not make any assuptions about Token.dyn
+** and Token.n when Token.z==0.
+*/
+struct Token {
+ const unsigned char *z; /* Text of the token. Not NULL-terminated! */
+ unsigned dyn : 1; /* True for malloced memory, false for static */
+ unsigned n : 31; /* Number of characters in this token */
+};
+
+/*
+** An instance of this structure contains information needed to generate
+** code for a SELECT that contains aggregate functions.
+**
+** If Expr.op==TK_AGG_COLUMN or TK_AGG_FUNCTION then Expr.pAggInfo is a
+** pointer to this structure. The Expr.iColumn field is the index in
+** AggInfo.aCol[] or AggInfo.aFunc[] of information needed to generate
+** code for that node.
+**
+** AggInfo.pGroupBy and AggInfo.aFunc.pExpr point to fields within the
+** original Select structure that describes the SELECT statement. These
+** fields do not need to be freed when deallocating the AggInfo structure.
+*/
+struct AggInfo {
+ u8 directMode; /* Direct rendering mode means take data directly
+ ** from source tables rather than from accumulators */
+ u8 useSortingIdx; /* In direct mode, reference the sorting index rather
+ ** than the source table */
+ int sortingIdx; /* Cursor number of the sorting index */
+ ExprList *pGroupBy; /* The group by clause */
+ int nSortingColumn; /* Number of columns in the sorting index */
+ struct AggInfo_col { /* For each column used in source tables */
+ Table *pTab; /* Source table */
+ int iTable; /* Cursor number of the source table */
+ int iColumn; /* Column number within the source table */
+ int iSorterColumn; /* Column number in the sorting index */
+ int iMem; /* Memory location that acts as accumulator */
+ Expr *pExpr; /* The original expression */
+ } *aCol;
+ int nColumn; /* Number of used entries in aCol[] */
+ int nColumnAlloc; /* Number of slots allocated for aCol[] */
+ int nAccumulator; /* Number of columns that show through to the output.
+ ** Additional columns are used only as parameters to
+ ** aggregate functions */
+ struct AggInfo_func { /* For each aggregate function */
+ Expr *pExpr; /* Expression encoding the function */
+ FuncDef *pFunc; /* The aggregate function implementation */
+ int iMem; /* Memory location that acts as accumulator */
+ int iDistinct; /* Ephermeral table used to enforce DISTINCT */
+ } *aFunc;
+ int nFunc; /* Number of entries in aFunc[] */
+ int nFuncAlloc; /* Number of slots allocated for aFunc[] */
+};
+
+/*
+** Each node of an expression in the parse tree is an instance
+** of this structure.
+**
+** Expr.op is the opcode. The integer parser token codes are reused
+** as opcodes here. For example, the parser defines TK_GE to be an integer
+** code representing the ">=" operator. This same integer code is reused
+** to represent the greater-than-or-equal-to operator in the expression
+** tree.
+**
+** Expr.pRight and Expr.pLeft are subexpressions. Expr.pList is a list
+** of argument if the expression is a function.
+**
+** Expr.token is the operator token for this node. For some expressions
+** that have subexpressions, Expr.token can be the complete text that gave
+** rise to the Expr. In the latter case, the token is marked as being
+** a compound token.
+**
+** An expression of the form ID or ID.ID refers to a column in a table.
+** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is
+** the integer cursor number of a VDBE cursor pointing to that table and
+** Expr.iColumn is the column number for the specific column. If the
+** expression is used as a result in an aggregate SELECT, then the
+** value is also stored in the Expr.iAgg column in the aggregate so that
+** it can be accessed after all aggregates are computed.
+**
+** If the expression is a function, the Expr.iTable is an integer code
+** representing which function. If the expression is an unbound variable
+** marker (a question mark character '?' in the original SQL) then the
+** Expr.iTable holds the index number for that variable.
+**
+** If the expression is a subquery then Expr.iColumn holds an integer
+** register number containing the result of the subquery. If the
+** subquery gives a constant result, then iTable is -1. If the subquery
+** gives a different answer at different times during statement processing
+** then iTable is the address of a subroutine that computes the subquery.
+**
+** The Expr.pSelect field points to a SELECT statement. The SELECT might
+** be the right operand of an IN operator. Or, if a scalar SELECT appears
+** in an expression the opcode is TK_SELECT and Expr.pSelect is the only
+** operand.
+**
+** If the Expr is of type OP_Column, and the table it is selecting from
+** is a disk table or the "old.*" pseudo-table, then pTab points to the
+** corresponding table definition.
+*/
+struct Expr {
+ u8 op; /* Operation performed by this node */
+ char affinity; /* The affinity of the column or 0 if not a column */
+ u16 flags; /* Various flags. See below */
+ CollSeq *pColl; /* The collation type of the column or 0 */
+ Expr *pLeft, *pRight; /* Left and right subnodes */
+ ExprList *pList; /* A list of expressions used as function arguments
+ ** or in "<expr> IN (<expr-list)" */
+ Token token; /* An operand token */
+ Token span; /* Complete text of the expression */
+ int iTable, iColumn; /* When op==TK_COLUMN, then this expr node means the
+ ** iColumn-th field of the iTable-th table. */
+ AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */
+ int iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */
+ int iRightJoinTable; /* If EP_FromJoin, the right table of the join */
+ Select *pSelect; /* When the expression is a sub-select. Also the
+ ** right side of "<expr> IN (<select>)" */
+ Table *pTab; /* Table for OP_Column expressions. */
+/* Schema *pSchema; */
+#if defined(SQLITE_TEST) || SQLITE_MAX_EXPR_DEPTH>0
+ int nHeight; /* Height of the tree headed by this node */
+#endif
+};
+
+/*
+** The following are the meanings of bits in the Expr.flags field.
+*/
+#define EP_FromJoin 0x0001 /* Originated in ON or USING clause of a join */
+#define EP_Agg 0x0002 /* Contains one or more aggregate functions */
+#define EP_Resolved 0x0004 /* IDs have been resolved to COLUMNs */
+#define EP_Error 0x0008 /* Expression contains one or more errors */
+#define EP_Distinct 0x0010 /* Aggregate function with DISTINCT keyword */
+#define EP_VarSelect 0x0020 /* pSelect is correlated, not constant */
+#define EP_Dequoted 0x0040 /* True if the string has been dequoted */
+#define EP_InfixFunc 0x0080 /* True for an infix function: LIKE, GLOB, etc */
+#define EP_ExpCollate 0x0100 /* Collating sequence specified explicitly */
+#define EP_AnyAff 0x0200 /* Can take a cached column of any affinity */
+#define EP_FixedDest 0x0400 /* Result needed in a specific register */
+
+/*
+** These macros can be used to test, set, or clear bits in the
+** Expr.flags field.
+*/
+#define ExprHasProperty(E,P) (((E)->flags&(P))==(P))
+#define ExprHasAnyProperty(E,P) (((E)->flags&(P))!=0)
+#define ExprSetProperty(E,P) (E)->flags|=(P)
+#define ExprClearProperty(E,P) (E)->flags&=~(P)
+
+/*
+** A list of expressions. Each expression may optionally have a
+** name. An expr/name combination can be used in several ways, such
+** as the list of "expr AS ID" fields following a "SELECT" or in the
+** list of "ID = expr" items in an UPDATE. A list of expressions can
+** also be used as the argument to a function, in which case the a.zName
+** field is not used.
+*/
+struct ExprList {
+ int nExpr; /* Number of expressions on the list */
+ int nAlloc; /* Number of entries allocated below */
+ int iECursor; /* VDBE Cursor associated with this ExprList */
+ struct ExprList_item {
+ Expr *pExpr; /* The list of expressions */
+ char *zName; /* Token associated with this expression */
+ u8 sortOrder; /* 1 for DESC or 0 for ASC */
+ u8 isAgg; /* True if this is an aggregate like count(*) */
+ u8 done; /* A flag to indicate when processing is finished */
+ } *a; /* One entry for each expression */
+};
+
+/*
+** An instance of this structure can hold a simple list of identifiers,
+** such as the list "a,b,c" in the following statements:
+**
+** INSERT INTO t(a,b,c) VALUES ...;
+** CREATE INDEX idx ON t(a,b,c);
+** CREATE TRIGGER trig BEFORE UPDATE ON t(a,b,c) ...;
+**
+** The IdList.a.idx field is used when the IdList represents the list of
+** column names after a table name in an INSERT statement. In the statement
+**
+** INSERT INTO t(a,b,c) ...
+**
+** If "a" is the k-th column of table "t", then IdList.a[0].idx==k.
+*/
+struct IdList {
+ struct IdList_item {
+ char *zName; /* Name of the identifier */
+ int idx; /* Index in some Table.aCol[] of a column named zName */
+ } *a;
+ int nId; /* Number of identifiers on the list */
+ int nAlloc; /* Number of entries allocated for a[] below */
+};
+
+/*
+** The bitmask datatype defined below is used for various optimizations.
+**
+** Changing this from a 64-bit to a 32-bit type limits the number of
+** tables in a join to 32 instead of 64. But it also reduces the size
+** of the library by 738 bytes on ix86.
+*/
+typedef u64 Bitmask;
+
+/*
+** The following structure describes the FROM clause of a SELECT statement.
+** Each table or subquery in the FROM clause is a separate element of
+** the SrcList.a[] array.
+**
+** With the addition of multiple database support, the following structure
+** can also be used to describe a particular table such as the table that
+** is modified by an INSERT, DELETE, or UPDATE statement. In standard SQL,
+** such a table must be a simple name: ID. But in SQLite, the table can
+** now be identified by a database name, a dot, then the table name: ID.ID.
+**
+** The jointype starts out showing the join type between the current table
+** and the next table on the list. The parser builds the list this way.
+** But sqlite3SrcListShiftJoinType() later shifts the jointypes so that each
+** jointype expresses the join between the table and the previous table.
+*/
+struct SrcList {
+ i16 nSrc; /* Number of tables or subqueries in the FROM clause */
+ i16 nAlloc; /* Number of entries allocated in a[] below */
+ struct SrcList_item {
+ char *zDatabase; /* Name of database holding this table */
+ char *zName; /* Name of the table */
+ char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */
+ Table *pTab; /* An SQL table corresponding to zName */
+ Select *pSelect; /* A SELECT statement used in place of a table name */
+ u8 isPopulated; /* Temporary table associated with SELECT is populated */
+ u8 jointype; /* Type of join between this able and the previous */
+ int iCursor; /* The VDBE cursor number used to access this table */
+ Expr *pOn; /* The ON clause of a join */
+ IdList *pUsing; /* The USING clause of a join */
+ Bitmask colUsed; /* Bit N (1<<N) set if column N or pTab is used */
+ } a[1]; /* One entry for each identifier on the list */
+};
+
+/*
+** Permitted values of the SrcList.a.jointype field
+*/
+#define JT_INNER 0x0001 /* Any kind of inner or cross join */
+#define JT_CROSS 0x0002 /* Explicit use of the CROSS keyword */
+#define JT_NATURAL 0x0004 /* True for a "natural" join */
+#define JT_LEFT 0x0008 /* Left outer join */
+#define JT_RIGHT 0x0010 /* Right outer join */
+#define JT_OUTER 0x0020 /* The "OUTER" keyword is present */
+#define JT_ERROR 0x0040 /* unknown or unsupported join type */
+
+/*
+** For each nested loop in a WHERE clause implementation, the WhereInfo
+** structure contains a single instance of this structure. This structure
+** is intended to be private the the where.c module and should not be
+** access or modified by other modules.
+**
+** The pIdxInfo and pBestIdx fields are used to help pick the best
+** index on a virtual table. The pIdxInfo pointer contains indexing
+** information for the i-th table in the FROM clause before reordering.
+** All the pIdxInfo pointers are freed by whereInfoFree() in where.c.
+** The pBestIdx pointer is a copy of pIdxInfo for the i-th table after
+** FROM clause ordering. This is a little confusing so I will repeat
+** it in different words. WhereInfo.a[i].pIdxInfo is index information
+** for WhereInfo.pTabList.a[i]. WhereInfo.a[i].pBestInfo is the
+** index information for the i-th loop of the join. pBestInfo is always
+** either NULL or a copy of some pIdxInfo. So for cleanup it is
+** sufficient to free all of the pIdxInfo pointers.
+**
+*/
+struct WhereLevel {
+ int iFrom; /* Which entry in the FROM clause */
+ int flags; /* Flags associated with this level */
+ int iMem; /* First memory cell used by this level */
+ int iLeftJoin; /* Memory cell used to implement LEFT OUTER JOIN */
+ Index *pIdx; /* Index used. NULL if no index */
+ int iTabCur; /* The VDBE cursor used to access the table */
+ int iIdxCur; /* The VDBE cursor used to acesss pIdx */
+ int brk; /* Jump here to break out of the loop */
+ int nxt; /* Jump here to start the next IN combination */
+ int cont; /* Jump here to continue with the next loop cycle */
+ int top; /* First instruction of interior of the loop */
+ int op, p1, p2; /* Opcode used to terminate the loop */
+ int nEq; /* Number of == or IN constraints on this loop */
+ int nIn; /* Number of IN operators constraining this loop */
+ struct InLoop {
+ int iCur; /* The VDBE cursor used by this IN operator */
+ int topAddr; /* Top of the IN loop */
+ } *aInLoop; /* Information about each nested IN operator */
+ sqlite3_index_info *pBestIdx; /* Index information for this level */
+
+ /* The following field is really not part of the current level. But
+ ** we need a place to cache index information for each table in the
+ ** FROM clause and the WhereLevel structure is a convenient place.
+ */
+ sqlite3_index_info *pIdxInfo; /* Index info for n-th source table */
+};
+
+/*
+** Flags appropriate for the wflags parameter of sqlite3WhereBegin().
+*/
+#define WHERE_ORDERBY_NORMAL 0 /* No-op */
+#define WHERE_ORDERBY_MIN 1 /* ORDER BY processing for min() func */
+#define WHERE_ORDERBY_MAX 2 /* ORDER BY processing for max() func */
+#define WHERE_ONEPASS_DESIRED 4 /* Want to do one-pass UPDATE/DELETE */
+
+/*
+** The WHERE clause processing routine has two halves. The
+** first part does the start of the WHERE loop and the second
+** half does the tail of the WHERE loop. An instance of
+** this structure is returned by the first half and passed
+** into the second half to give some continuity.
+*/
+struct WhereInfo {
+ Parse *pParse; /* Parsing and code generating context */
+ u8 okOnePass; /* Ok to use one-pass algorithm for UPDATE or DELETE */
+ SrcList *pTabList; /* List of tables in the join */
+ int iTop; /* The very beginning of the WHERE loop */
+ int iContinue; /* Jump here to continue with next record */
+ int iBreak; /* Jump here to break out of the loop */
+ int nLevel; /* Number of nested loop */
+ sqlite3_index_info **apInfo; /* Array of pointers to index info structures */
+ WhereLevel a[1]; /* Information about each nest loop in the WHERE */
+};
+
+/*
+** A NameContext defines a context in which to resolve table and column
+** names. The context consists of a list of tables (the pSrcList) field and
+** a list of named expression (pEList). The named expression list may
+** be NULL. The pSrc corresponds to the FROM clause of a SELECT or
+** to the table being operated on by INSERT, UPDATE, or DELETE. The
+** pEList corresponds to the result set of a SELECT and is NULL for
+** other statements.
+**
+** NameContexts can be nested. When resolving names, the inner-most
+** context is searched first. If no match is found, the next outer
+** context is checked. If there is still no match, the next context
+** is checked. This process continues until either a match is found
+** or all contexts are check. When a match is found, the nRef member of
+** the context containing the match is incremented.
+**
+** Each subquery gets a new NameContext. The pNext field points to the
+** NameContext in the parent query. Thus the process of scanning the
+** NameContext list corresponds to searching through successively outer
+** subqueries looking for a match.
+*/
+struct NameContext {
+ Parse *pParse; /* The parser */
+ SrcList *pSrcList; /* One or more tables used to resolve names */
+ ExprList *pEList; /* Optional list of named expressions */
+ int nRef; /* Number of names resolved by this context */
+ int nErr; /* Number of errors encountered while resolving names */
+ u8 allowAgg; /* Aggregate functions allowed here */
+ u8 hasAgg; /* True if aggregates are seen */
+ u8 isCheck; /* True if resolving names in a CHECK constraint */
+ int nDepth; /* Depth of subquery recursion. 1 for no recursion */
+ AggInfo *pAggInfo; /* Information about aggregates at this level */
+ NameContext *pNext; /* Next outer name context. NULL for outermost */
+};
+
+/*
+** An instance of the following structure contains all information
+** needed to generate code for a single SELECT statement.
+**
+** nLimit is set to -1 if there is no LIMIT clause. nOffset is set to 0.
+** If there is a LIMIT clause, the parser sets nLimit to the value of the
+** limit and nOffset to the value of the offset (or 0 if there is not
+** offset). But later on, nLimit and nOffset become the memory locations
+** in the VDBE that record the limit and offset counters.
+**
+** addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes.
+** These addresses must be stored so that we can go back and fill in
+** the P4_KEYINFO and P2 parameters later. Neither the KeyInfo nor
+** the number of columns in P2 can be computed at the same time
+** as the OP_OpenEphm instruction is coded because not
+** enough information about the compound query is known at that point.
+** The KeyInfo for addrOpenTran[0] and [1] contains collating sequences
+** for the result set. The KeyInfo for addrOpenTran[2] contains collating
+** sequences for the ORDER BY clause.
+*/
+struct Select {
+ ExprList *pEList; /* The fields of the result */
+ u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */
+ u8 isDistinct; /* True if the DISTINCT keyword is present */
+ u8 isResolved; /* True once sqlite3SelectResolve() has run. */
+ u8 isAgg; /* True if this is an aggregate query */
+ u8 usesEphm; /* True if uses an OpenEphemeral opcode */
+ u8 disallowOrderBy; /* Do not allow an ORDER BY to be attached if TRUE */
+ char affinity; /* MakeRecord with this affinity for SRT_Set */
+ SrcList *pSrc; /* The FROM clause */
+ Expr *pWhere; /* The WHERE clause */
+ ExprList *pGroupBy; /* The GROUP BY clause */
+ Expr *pHaving; /* The HAVING clause */
+ ExprList *pOrderBy; /* The ORDER BY clause */
+ Select *pPrior; /* Prior select in a compound select statement */
+ Select *pNext; /* Next select to the left in a compound */
+ Select *pRightmost; /* Right-most select in a compound select statement */
+ Expr *pLimit; /* LIMIT expression. NULL means not used. */
+ Expr *pOffset; /* OFFSET expression. NULL means not used. */
+ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */
+ int addrOpenEphm[3]; /* OP_OpenEphem opcodes related to this select */
+};
+
+/*
+** The results of a select can be distributed in several ways.
+*/
+#define SRT_Union 1 /* Store result as keys in an index */
+#define SRT_Except 2 /* Remove result from a UNION index */
+#define SRT_Exists 3 /* Store 1 if the result is not empty */
+#define SRT_Discard 4 /* Do not save the results anywhere */
+
+/* The ORDER BY clause is ignored for all of the above */
+#define IgnorableOrderby(X) ((X->eDest)<=SRT_Discard)
+
+#define SRT_Callback 5 /* Invoke a callback with each row of result */
+#define SRT_Mem 6 /* Store result in a memory cell */
+#define SRT_Set 7 /* Store non-null results as keys in an index */
+#define SRT_Table 8 /* Store result as data with an automatic rowid */
+#define SRT_EphemTab 9 /* Create transient tab and store like SRT_Table */
+#define SRT_Subroutine 10 /* Call a subroutine to handle results */
+
+/*
+** A structure used to customize the behaviour of sqlite3Select(). See
+** comments above sqlite3Select() for details.
+*/
+typedef struct SelectDest SelectDest;
+struct SelectDest {
+ u8 eDest; /* How to dispose of the results */
+ u8 affinity; /* Affinity used when eDest==SRT_Set */
+ int iParm; /* A parameter used by the eDest disposal method */
+ int iMem; /* Base register where results are written */
+ int nMem; /* Number of registers allocated */
+};
+
+/*
+** An SQL parser context. A copy of this structure is passed through
+** the parser and down into all the parser action routine in order to
+** carry around information that is global to the entire parse.
+**
+** The structure is divided into two parts. When the parser and code
+** generate call themselves recursively, the first part of the structure
+** is constant but the second part is reset at the beginning and end of
+** each recursion.
+**
+** The nTableLock and aTableLock variables are only used if the shared-cache
+** feature is enabled (if sqlite3Tsd()->useSharedData is true). They are
+** used to store the set of table-locks required by the statement being
+** compiled. Function sqlite3TableLock() is used to add entries to the
+** list.
+*/
+struct Parse {
+ sqlite3 *db; /* The main database structure */
+ int rc; /* Return code from execution */
+ char *zErrMsg; /* An error message */
+ Vdbe *pVdbe; /* An engine for executing database bytecode */
+ u8 colNamesSet; /* TRUE after OP_ColumnName has been issued to pVdbe */
+ u8 nameClash; /* A permanent table name clashes with temp table name */
+ u8 checkSchema; /* Causes schema cookie check after an error */
+ u8 nested; /* Number of nested calls to the parser/code generator */
+ u8 parseError; /* True after a parsing error. Ticket #1794 */
+ u8 nTempReg; /* Number of temporary registers in aTempReg[] */
+ u8 nTempInUse; /* Number of aTempReg[] currently checked out */
+ int aTempReg[8]; /* Holding area for temporary registers */
+ int nRangeReg; /* Size of the temporary register block */
+ int iRangeReg; /* First register in temporary register block */
+ int nErr; /* Number of errors seen */
+ int nTab; /* Number of previously allocated VDBE cursors */
+ int nMem; /* Number of memory cells used so far */
+ int nSet; /* Number of sets used so far */
+ int ckBase; /* Base register of data during check constraints */
+ int disableColCache; /* True to disable adding to column cache */
+ int nColCache; /* Number of entries in the column cache */
+ int iColCache; /* Next entry of the cache to replace */
+ struct yColCache {
+ int iTable; /* Table cursor number */
+ int iColumn; /* Table column number */
+ char affChange; /* True if this register has had an affinity change */
+ int iReg; /* Register holding value of this column */
+ } aColCache[10]; /* One for each valid column cache entry */
+ u32 writeMask; /* Start a write transaction on these databases */
+ u32 cookieMask; /* Bitmask of schema verified databases */
+ int cookieGoto; /* Address of OP_Goto to cookie verifier subroutine */
+ int cookieValue[SQLITE_MAX_ATTACHED+2]; /* Values of cookies to verify */
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ int nTableLock; /* Number of locks in aTableLock */
+ TableLock *aTableLock; /* Required table locks for shared-cache mode */
+#endif
+ int regRowid; /* Register holding rowid of CREATE TABLE entry */
+ int regRoot; /* Register holding root page number for new objects */
+
+ /* Above is constant between recursions. Below is reset before and after
+ ** each recursion */
+
+ int nVar; /* Number of '?' variables seen in the SQL so far */
+ int nVarExpr; /* Number of used slots in apVarExpr[] */
+ int nVarExprAlloc; /* Number of allocated slots in apVarExpr[] */
+ Expr **apVarExpr; /* Pointers to :aaa and $aaaa wildcard expressions */
+ u8 explain; /* True if the EXPLAIN flag is found on the query */
+ Token sErrToken; /* The token at which the error occurred */
+ Token sNameToken; /* Token with unqualified schema object name */
+ Token sLastToken; /* The last token parsed */
+ const char *zSql; /* All SQL text */
+ const char *zTail; /* All SQL text past the last semicolon parsed */
+ Table *pNewTable; /* A table being constructed by CREATE TABLE */
+ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */
+ TriggerStack *trigStack; /* Trigger actions being coded */
+ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ Token sArg; /* Complete text of a module argument */
+ u8 declareVtab; /* True if inside sqlite3_declare_vtab() */
+ int nVtabLock; /* Number of virtual tables to lock */
+ Table **apVtabLock; /* Pointer to virtual tables needing locking */
+#endif
+#if defined(SQLITE_TEST) || SQLITE_MAX_EXPR_DEPTH>0
+ int nHeight; /* Expression tree height of current sub-select */
+#endif
+};
+
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+ #define IN_DECLARE_VTAB 0
+#else
+ #define IN_DECLARE_VTAB (pParse->declareVtab)
+#endif
+
+/*
+** An instance of the following structure can be declared on a stack and used
+** to save the Parse.zAuthContext value so that it can be restored later.
+*/
+struct AuthContext {
+ const char *zAuthContext; /* Put saved Parse.zAuthContext here */
+ Parse *pParse; /* The Parse structure */
+};
+
+/*
+** Bitfield flags for P2 value in OP_Insert and OP_Delete
+*/
+#define OPFLAG_NCHANGE 1 /* Set to update db->nChange */
+#define OPFLAG_LASTROWID 2 /* Set to update db->lastRowid */
+#define OPFLAG_ISUPDATE 4 /* This OP_Insert is an sql UPDATE */
+#define OPFLAG_APPEND 8 /* This is likely to be an append */
+
+/*
+ * Each trigger present in the database schema is stored as an instance of
+ * struct Trigger.
+ *
+ * Pointers to instances of struct Trigger are stored in two ways.
+ * 1. In the "trigHash" hash table (part of the sqlite3* that represents the
+ * database). This allows Trigger structures to be retrieved by name.
+ * 2. All triggers associated with a single table form a linked list, using the
+ * pNext member of struct Trigger. A pointer to the first element of the
+ * linked list is stored as the "pTrigger" member of the associated
+ * struct Table.
+ *
+ * The "step_list" member points to the first element of a linked list
+ * containing the SQL statements specified as the trigger program.
+ */
+struct Trigger {
+ char *name; /* The name of the trigger */
+ char *table; /* The table or view to which the trigger applies */
+ u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */
+ u8 tr_tm; /* One of TRIGGER_BEFORE, TRIGGER_AFTER */
+ Expr *pWhen; /* The WHEN clause of the expresion (may be NULL) */
+ IdList *pColumns; /* If this is an UPDATE OF <column-list> trigger,
+ the <column-list> is stored here */
+ Token nameToken; /* Token containing zName. Use during parsing only */
+ Schema *pSchema; /* Schema containing the trigger */
+ Schema *pTabSchema; /* Schema containing the table */
+ TriggerStep *step_list; /* Link list of trigger program steps */
+ Trigger *pNext; /* Next trigger associated with the table */
+};
+
+/*
+** A trigger is either a BEFORE or an AFTER trigger. The following constants
+** determine which.
+**
+** If there are multiple triggers, you might of some BEFORE and some AFTER.
+** In that cases, the constants below can be ORed together.
+*/
+#define TRIGGER_BEFORE 1
+#define TRIGGER_AFTER 2
+
+/*
+ * An instance of struct TriggerStep is used to store a single SQL statement
+ * that is a part of a trigger-program.
+ *
+ * Instances of struct TriggerStep are stored in a singly linked list (linked
+ * using the "pNext" member) referenced by the "step_list" member of the
+ * associated struct Trigger instance. The first element of the linked list is
+ * the first step of the trigger-program.
+ *
+ * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or
+ * "SELECT" statement. The meanings of the other members is determined by the
+ * value of "op" as follows:
+ *
+ * (op == TK_INSERT)
+ * orconf -> stores the ON CONFLICT algorithm
+ * pSelect -> If this is an INSERT INTO ... SELECT ... statement, then
+ * this stores a pointer to the SELECT statement. Otherwise NULL.
+ * target -> A token holding the name of the table to insert into.
+ * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then
+ * this stores values to be inserted. Otherwise NULL.
+ * pIdList -> If this is an INSERT INTO ... (<column-names>) VALUES ...
+ * statement, then this stores the column-names to be
+ * inserted into.
+ *
+ * (op == TK_DELETE)
+ * target -> A token holding the name of the table to delete from.
+ * pWhere -> The WHERE clause of the DELETE statement if one is specified.
+ * Otherwise NULL.
+ *
+ * (op == TK_UPDATE)
+ * target -> A token holding the name of the table to update rows of.
+ * pWhere -> The WHERE clause of the UPDATE statement if one is specified.
+ * Otherwise NULL.
+ * pExprList -> A list of the columns to update and the expressions to update
+ * them to. See sqlite3Update() documentation of "pChanges"
+ * argument.
+ *
+ */
+struct TriggerStep {
+ int op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
+ int orconf; /* OE_Rollback etc. */
+ Trigger *pTrig; /* The trigger that this step is a part of */
+
+ Select *pSelect; /* Valid for SELECT and sometimes
+ INSERT steps (when pExprList == 0) */
+ Token target; /* Valid for DELETE, UPDATE, INSERT steps */
+ Expr *pWhere; /* Valid for DELETE, UPDATE steps */
+ ExprList *pExprList; /* Valid for UPDATE statements and sometimes
+ INSERT steps (when pSelect == 0) */
+ IdList *pIdList; /* Valid for INSERT statements only */
+ TriggerStep *pNext; /* Next in the link-list */
+ TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */
+};
+
+/*
+ * An instance of struct TriggerStack stores information required during code
+ * generation of a single trigger program. While the trigger program is being
+ * coded, its associated TriggerStack instance is pointed to by the
+ * "pTriggerStack" member of the Parse structure.
+ *
+ * The pTab member points to the table that triggers are being coded on. The
+ * newIdx member contains the index of the vdbe cursor that points at the temp
+ * table that stores the new.* references. If new.* references are not valid
+ * for the trigger being coded (for example an ON DELETE trigger), then newIdx
+ * is set to -1. The oldIdx member is analogous to newIdx, for old.* references.
+ *
+ * The ON CONFLICT policy to be used for the trigger program steps is stored
+ * as the orconf member. If this is OE_Default, then the ON CONFLICT clause
+ * specified for individual triggers steps is used.
+ *
+ * struct TriggerStack has a "pNext" member, to allow linked lists to be
+ * constructed. When coding nested triggers (triggers fired by other triggers)
+ * each nested trigger stores its parent trigger's TriggerStack as the "pNext"
+ * pointer. Once the nested trigger has been coded, the pNext value is restored
+ * to the pTriggerStack member of the Parse stucture and coding of the parent
+ * trigger continues.
+ *
+ * Before a nested trigger is coded, the linked list pointed to by the
+ * pTriggerStack is scanned to ensure that the trigger is not about to be coded
+ * recursively. If this condition is detected, the nested trigger is not coded.
+ */
+struct TriggerStack {
+ Table *pTab; /* Table that triggers are currently being coded on */
+ int newIdx; /* Index of vdbe cursor to "new" temp table */
+ int oldIdx; /* Index of vdbe cursor to "old" temp table */
+ u32 newColMask;
+ u32 oldColMask;
+ int orconf; /* Current orconf policy */
+ int ignoreJump; /* where to jump to for a RAISE(IGNORE) */
+ Trigger *pTrigger; /* The trigger currently being coded */
+ TriggerStack *pNext; /* Next trigger down on the trigger stack */
+};
+
+/*
+** The following structure contains information used by the sqliteFix...
+** routines as they walk the parse tree to make database references
+** explicit.
+*/
+typedef struct DbFixer DbFixer;
+struct DbFixer {
+ Parse *pParse; /* The parsing context. Error messages written here */
+ const char *zDb; /* Make sure all objects are contained in this database */
+ const char *zType; /* Type of the container - used for error messages */
+ const Token *pName; /* Name of the container - used for error messages */
+};
+
+/*
+** An objected used to accumulate the text of a string where we
+** do not necessarily know how big the string will be in the end.
+*/
+struct StrAccum {
+ char *zBase; /* A base allocation. Not from malloc. */
+ char *zText; /* The string collected so far */
+ int nChar; /* Length of the string so far */
+ int nAlloc; /* Amount of space allocated in zText */
+ int mxAlloc; /* Maximum allowed string length */
+ u8 mallocFailed; /* Becomes true if any memory allocation fails */
+ u8 useMalloc; /* True if zText is enlargable using realloc */
+ u8 tooBig; /* Becomes true if string size exceeds limits */
+};
+
+/*
+** A pointer to this structure is used to communicate information
+** from sqlite3Init and OP_ParseSchema into the sqlite3InitCallback.
+*/
+typedef struct {
+ sqlite3 *db; /* The database being initialized */
+ int iDb; /* 0 for main database. 1 for TEMP, 2.. for ATTACHed */
+ char **pzErrMsg; /* Error message stored here */
+ int rc; /* Result code stored here */
+} InitData;
+
+/*
+** Assuming zIn points to the first byte of a UTF-8 character,
+** advance zIn to point to the first byte of the next UTF-8 character.
+*/
+#define SQLITE_SKIP_UTF8(zIn) { \
+ if( (*(zIn++))>=0xc0 ){ \
+ while( (*zIn & 0xc0)==0x80 ){ zIn++; } \
+ } \
+}
+
+/*
+** The SQLITE_CORRUPT_BKPT macro can be either a constant (for production
+** builds) or a function call (for debugging). If it is a function call,
+** it allows the operator to set a breakpoint at the spot where database
+** corruption is first detected.
+*/
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3Corrupt(void);
+# define SQLITE_CORRUPT_BKPT sqlite3Corrupt()
+# define DEBUGONLY(X) X
+#else
+# define SQLITE_CORRUPT_BKPT SQLITE_CORRUPT
+# define DEBUGONLY(X)
+#endif
+
+/*
+** Internal function prototypes
+*/
+SQLITE_PRIVATE int sqlite3StrICmp(const char *, const char *);
+SQLITE_PRIVATE int sqlite3StrNICmp(const char *, const char *, int);
+SQLITE_PRIVATE int sqlite3IsNumber(const char*, int*, u8);
+
+SQLITE_PRIVATE void *sqlite3MallocZero(unsigned);
+SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3*, unsigned);
+SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3*, unsigned);
+SQLITE_PRIVATE char *sqlite3StrDup(const char*);
+SQLITE_PRIVATE char *sqlite3StrNDup(const char*, int);
+SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3*,const char*);
+SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3*,const char*, int);
+SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, int);
+SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, int);
+SQLITE_PRIVATE int sqlite3MallocSize(void *);
+
+SQLITE_PRIVATE int sqlite3IsNaN(double);
+
+SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3*,const char*, ...);
+SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3*,const char*, va_list);
+#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
+SQLITE_PRIVATE void sqlite3DebugPrintf(const char*, ...);
+#endif
+#if defined(SQLITE_TEST)
+SQLITE_PRIVATE void *sqlite3TextToPtr(const char*);
+#endif
+SQLITE_PRIVATE void sqlite3SetString(char **, ...);
+SQLITE_PRIVATE void sqlite3ErrorMsg(Parse*, const char*, ...);
+SQLITE_PRIVATE void sqlite3ErrorClear(Parse*);
+SQLITE_PRIVATE void sqlite3Dequote(char*);
+SQLITE_PRIVATE void sqlite3DequoteExpr(sqlite3*, Expr*);
+SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char*, int);
+SQLITE_PRIVATE int sqlite3RunParser(Parse*, const char*, char **);
+SQLITE_PRIVATE void sqlite3FinishCoding(Parse*);
+SQLITE_PRIVATE int sqlite3GetTempReg(Parse*);
+SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse*,int);
+SQLITE_PRIVATE int sqlite3GetTempRange(Parse*,int);
+SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse*,int,int);
+SQLITE_PRIVATE Expr *sqlite3Expr(sqlite3*, int, Expr*, Expr*, const Token*);
+SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*, const Token*);
+SQLITE_PRIVATE Expr *sqlite3RegisterExpr(Parse*,Token*);
+SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3*,Expr*, Expr*);
+SQLITE_PRIVATE void sqlite3ExprSpan(Expr*,Token*,Token*);
+SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*);
+SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*);
+SQLITE_PRIVATE void sqlite3ExprDelete(Expr*);
+SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*,Token*);
+SQLITE_PRIVATE void sqlite3ExprListDelete(ExprList*);
+SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**);
+SQLITE_PRIVATE int sqlite3InitCallback(void*, int, char**, char**);
+SQLITE_PRIVATE void sqlite3Pragma(Parse*,Token*,Token*,Token*,int);
+SQLITE_PRIVATE void sqlite3ResetInternalSchema(sqlite3*, int);
+SQLITE_PRIVATE void sqlite3BeginParse(Parse*,int);
+SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*);
+SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,char*,Select*);
+SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *, int);
+SQLITE_PRIVATE void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int);
+SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token*);
+SQLITE_PRIVATE void sqlite3AddNotNull(Parse*, int);
+SQLITE_PRIVATE void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int);
+SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*);
+SQLITE_PRIVATE void sqlite3AddColumnType(Parse*,Token*);
+SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,Expr*);
+SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*);
+SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,Select*);
+
+SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32);
+SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec*, u32);
+SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec*, u32);
+SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec*, u32);
+SQLITE_PRIVATE void sqlite3BitvecDestroy(Bitvec*);
+SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int,int*);
+
+SQLITE_PRIVATE void sqlite3CreateView(Parse*,Token*,Token*,Token*,Select*,int,int);
+
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
+SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse*,Table*);
+#else
+# define sqlite3ViewGetColumnNames(A,B) 0
+#endif
+
+SQLITE_PRIVATE void sqlite3DropTable(Parse*, SrcList*, int, int);
+SQLITE_PRIVATE void sqlite3DeleteTable(Table*);
+SQLITE_PRIVATE void sqlite3Insert(Parse*, SrcList*, ExprList*, Select*, IdList*, int);
+SQLITE_PRIVATE void *sqlite3ArrayAllocate(sqlite3*,void*,int,int,int*,int*,int*);
+SQLITE_PRIVATE IdList *sqlite3IdListAppend(sqlite3*, IdList*, Token*);
+SQLITE_PRIVATE int sqlite3IdListIndex(IdList*,const char*);
+SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(sqlite3*, SrcList*, Token*, Token*);
+SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, Token*,
+ Select*, Expr*, IdList*);
+SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList*);
+SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*);
+SQLITE_PRIVATE void sqlite3IdListDelete(IdList*);
+SQLITE_PRIVATE void sqlite3SrcListDelete(SrcList*);
+SQLITE_PRIVATE void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
+ Token*, int, int);
+SQLITE_PRIVATE void sqlite3DropIndex(Parse*, SrcList*, int);
+SQLITE_PRIVATE int sqlite3Select(Parse*, Select*, SelectDest*, Select*, int, int*, char *aff);
+SQLITE_PRIVATE Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*,
+ Expr*,ExprList*,int,Expr*,Expr*);
+SQLITE_PRIVATE void sqlite3SelectDelete(Select*);
+SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse*, SrcList*);
+SQLITE_PRIVATE int sqlite3IsReadOnly(Parse*, Table*, int);
+SQLITE_PRIVATE void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
+SQLITE_PRIVATE void sqlite3DeleteFrom(Parse*, SrcList*, Expr*);
+SQLITE_PRIVATE void sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
+SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*, SrcList*, Expr*, ExprList**, u8);
+SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo*);
+SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, int);
+SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse*, int, int);
+SQLITE_PRIVATE void sqlite3ExprClearColumnCache(Parse*, int);
+SQLITE_PRIVATE void sqlite3ExprCacheAffinityChange(Parse*, int, int);
+SQLITE_PRIVATE int sqlite3ExprWritableRegister(Parse*,int,int);
+SQLITE_PRIVATE void sqlite3ExprHardCopy(Parse*,int,int);
+SQLITE_PRIVATE int sqlite3ExprCode(Parse*, Expr*, int);
+SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
+SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
+SQLITE_PRIVATE int sqlite3ExprCodeAndCache(Parse*, Expr*, int);
+SQLITE_PRIVATE void sqlite3ExprCodeConstants(Parse*, Expr*);
+SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int);
+SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse*, Expr*, int, int);
+SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse*, Expr*, int, int);
+SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3*,const char*, const char*);
+SQLITE_PRIVATE Table *sqlite3LocateTable(Parse*,int isView,const char*, const char*);
+SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*);
+SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*);
+SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*);
+SQLITE_PRIVATE void sqlite3Vacuum(Parse*);
+SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*);
+SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*);
+SQLITE_PRIVATE int sqlite3ExprCompare(Expr*, Expr*);
+SQLITE_PRIVATE int sqlite3ExprResolveNames(NameContext *, Expr *);
+SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
+SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
+SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse*);
+SQLITE_PRIVATE Expr *sqlite3CreateIdExpr(Parse *, const char*);
+SQLITE_PRIVATE void sqlite3PrngSaveState(void);
+SQLITE_PRIVATE void sqlite3PrngRestoreState(void);
+SQLITE_PRIVATE void sqlite3PrngResetState(void);
+SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3*);
+SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse*, int);
+SQLITE_PRIVATE void sqlite3BeginTransaction(Parse*, int);
+SQLITE_PRIVATE void sqlite3CommitTransaction(Parse*);
+SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse*);
+SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*);
+SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*);
+SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*);
+SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*);
+SQLITE_PRIVATE int sqlite3IsRowid(const char*);
+SQLITE_PRIVATE void sqlite3GenerateRowDelete(Parse*, Table*, int, int, int);
+SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int*);
+SQLITE_PRIVATE int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int);
+SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(Parse*,Table*,int,int,
+ int*,int,int,int,int);
+SQLITE_PRIVATE void sqlite3CompleteInsertion(Parse*, Table*, int, int, int*,int,int,int,int);
+SQLITE_PRIVATE int sqlite3OpenTableAndIndices(Parse*, Table*, int, int);
+SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse*, int, int);
+SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,Expr*);
+SQLITE_PRIVATE void sqlite3TokenCopy(sqlite3*,Token*, Token*);
+SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,ExprList*);
+SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,SrcList*);
+SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,IdList*);
+SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,Select*);
+SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,int,u8,int);
+SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(sqlite3*);
+SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(sqlite3*);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3SafetyOn(sqlite3*);
+SQLITE_PRIVATE int sqlite3SafetyOff(sqlite3*);
+#else
+# define sqlite3SafetyOn(A) 0
+# define sqlite3SafetyOff(A) 0
+#endif
+SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*);
+SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*);
+SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int);
+SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Select*, Expr*, int);
+
+#ifndef SQLITE_OMIT_TRIGGER
+SQLITE_PRIVATE void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
+ Expr*,int, int);
+SQLITE_PRIVATE void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
+SQLITE_PRIVATE void sqlite3DropTrigger(Parse*, SrcList*, int);
+SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse*, Trigger*);
+SQLITE_PRIVATE int sqlite3TriggersExist(Parse*, Table*, int, ExprList*);
+SQLITE_PRIVATE int sqlite3CodeRowTrigger(Parse*, int, ExprList*, int, Table *, int, int,
+ int, int, u32*, u32*);
+ void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*);
+SQLITE_PRIVATE void sqlite3DeleteTriggerStep(TriggerStep*);
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*);
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(sqlite3*,Token*, IdList*,
+ ExprList*,Select*,int);
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, int);
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*);
+SQLITE_PRIVATE void sqlite3DeleteTrigger(Trigger*);
+SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*);
+#else
+# define sqlite3TriggersExist(A,B,C,D,E,F) 0
+# define sqlite3DeleteTrigger(A)
+# define sqlite3DropTriggerPtr(A,B)
+# define sqlite3UnlinkAndDeleteTrigger(A,B,C)
+# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I,J,K) 0
+#endif
+
+SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*);
+SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int);
+SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+SQLITE_PRIVATE void sqlite3AuthRead(Parse*,Expr*,Schema*,SrcList*);
+SQLITE_PRIVATE int sqlite3AuthCheck(Parse*,int, const char*, const char*, const char*);
+SQLITE_PRIVATE void sqlite3AuthContextPush(Parse*, AuthContext*, const char*);
+SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext*);
+#else
+# define sqlite3AuthRead(a,b,c,d)
+# define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK
+# define sqlite3AuthContextPush(a,b,c)
+# define sqlite3AuthContextPop(a) ((void)(a))
+#endif
+SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*);
+SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*);
+SQLITE_PRIVATE int sqlite3BtreeFactory(const sqlite3 *db, const char *zFilename,
+ int omitJournal, int nCache, int flags, Btree **ppBtree);
+SQLITE_PRIVATE int sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
+SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*);
+SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*);
+SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*);
+SQLITE_PRIVATE int sqlite3FixExprList(DbFixer*, ExprList*);
+SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*);
+SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*);
+SQLITE_API char *sqlite3_snprintf(int,char*,const char*,...);
+SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*);
+SQLITE_PRIVATE int sqlite3FitsIn64Bits(const char *, int);
+SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *pData, int nChar);
+SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *pData, int nByte);
+SQLITE_PRIVATE int sqlite3Utf8Read(const u8*, const u8*, const u8**);
+
+/*
+** Routines to read and write variable-length integers. These used to
+** be defined locally, but now we use the varint routines in the util.c
+** file. Code should use the MACRO forms below, as the Varint32 versions
+** are coded to assume the single byte case is already handled (which
+** the MACRO form does).
+*/
+SQLITE_PRIVATE int sqlite3PutVarint(unsigned char*, u64);
+SQLITE_PRIVATE int sqlite3PutVarint32(unsigned char*, u32);
+SQLITE_PRIVATE int sqlite3GetVarint(const unsigned char *, u64 *);
+SQLITE_PRIVATE int sqlite3GetVarint32(const unsigned char *, u32 *);
+SQLITE_PRIVATE int sqlite3VarintLen(u64 v);
+
+/*
+** The header of a record consists of a sequence variable-length integers.
+** These integers are almost always small and are encoded as a single byte.
+** The following macros take advantage this fact to provide a fast encode
+** and decode of the integers in a record header. It is faster for the common
+** case where the integer is a single byte. It is a little slower when the
+** integer is two or more bytes. But overall it is faster.
+**
+** The following expressions are equivalent:
+**
+** x = sqlite3GetVarint32( A, &B );
+** x = sqlite3PutVarint32( A, B );
+**
+** x = getVarint32( A, B );
+** x = putVarint32( A, B );
+**
+*/
+#define getVarint32(A,B) ((*(A)<(unsigned char)0x80) ? ((B) = (u32)*(A)),1 : sqlite3GetVarint32((A), &(B)))
+#define putVarint32(A,B) (((B)<(u32)0x80) ? (*(A) = (unsigned char)(B)),1 : sqlite3PutVarint32((A), (B)))
+#define getVarint sqlite3GetVarint
+#define putVarint sqlite3PutVarint
+
+
+SQLITE_PRIVATE void sqlite3IndexAffinityStr(Vdbe *, Index *);
+SQLITE_PRIVATE void sqlite3TableAffinityStr(Vdbe *, Table *);
+SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2);
+SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
+SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr);
+SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*);
+SQLITE_PRIVATE void sqlite3Error(sqlite3*, int, const char*,...);
+SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3*, const char *z, int n);
+SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);
+SQLITE_PRIVATE const char *sqlite3ErrStr(int);
+SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse);
+SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char *,int,int);
+SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName, int nName);
+SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
+SQLITE_PRIVATE Expr *sqlite3ExprSetColl(Parse *pParse, Expr *, Token *);
+SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
+SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *, const char *);
+SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, int);
+
+SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value*, u8);
+SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value*, u8);
+SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8,
+ void(*)(void*));
+SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*);
+SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *);
+SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int);
+SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **);
+SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
+#ifndef SQLITE_AMALGAMATION
+SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[];
+#endif
+SQLITE_PRIVATE void sqlite3RootPageMoved(Db*, int, int);
+SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*);
+SQLITE_PRIVATE void sqlite3AlterFunctions(sqlite3*);
+SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
+SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *);
+SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...);
+SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*);
+SQLITE_PRIVATE void sqlite3CodeSubselect(Parse *, Expr *);
+SQLITE_PRIVATE int sqlite3SelectResolve(Parse *, Select *, NameContext *);
+SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int);
+SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *);
+SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
+SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(sqlite3*, CollSeq *, const char *, int);
+SQLITE_PRIVATE char sqlite3AffinityType(const Token*);
+SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*);
+SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler*);
+SQLITE_PRIVATE int sqlite3FindDb(sqlite3*, Token*);
+SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3*,int iDB);
+SQLITE_PRIVATE void sqlite3DefaultRowEst(Index*);
+SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3*, int);
+SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*);
+SQLITE_PRIVATE void sqlite3AttachFunctions(sqlite3 *);
+SQLITE_PRIVATE void sqlite3MinimumFileFormat(Parse*, int, int);
+SQLITE_PRIVATE void sqlite3SchemaFree(void *);
+SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *, Btree *);
+SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *);
+SQLITE_PRIVATE KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *);
+SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *,
+ void (*)(sqlite3_context*,int,sqlite3_value **),
+ void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*));
+SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int);
+SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *);
+
+SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum*,const char*,int);
+SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*);
+SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum*);
+SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int);
+
+/*
+** The interface to the LEMON-generated parser
+*/
+SQLITE_PRIVATE void *sqlite3ParserAlloc(void*(*)(size_t));
+SQLITE_PRIVATE void sqlite3ParserFree(void*, void(*)(void*));
+SQLITE_PRIVATE void sqlite3Parser(void*, int, Token, Parse*);
+
+SQLITE_PRIVATE int sqlite3AutoLoadExtensions(sqlite3*);
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3*);
+#else
+# define sqlite3CloseExtensions(X)
+#endif
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+SQLITE_PRIVATE void sqlite3TableLock(Parse *, int, int, u8, const char *);
+#else
+ #define sqlite3TableLock(v,w,x,y,z)
+#endif
+
+#ifdef SQLITE_TEST
+SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*);
+#endif
+
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+# define sqlite3VtabClear(X)
+# define sqlite3VtabSync(X,Y) (Y)
+# define sqlite3VtabRollback(X)
+# define sqlite3VtabCommit(X)
+#else
+SQLITE_PRIVATE void sqlite3VtabClear(Table*);
+SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, int rc);
+SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db);
+SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db);
+#endif
+SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse*,Table*);
+SQLITE_PRIVATE void sqlite3VtabLock(sqlite3_vtab*);
+SQLITE_PRIVATE void sqlite3VtabUnlock(sqlite3*, sqlite3_vtab*);
+SQLITE_PRIVATE void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*);
+SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse*, Token*);
+SQLITE_PRIVATE void sqlite3VtabArgInit(Parse*);
+SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse*, Token*);
+SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **);
+SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse*, Table*);
+SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3*, int, const char *);
+SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, sqlite3_vtab *);
+SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*);
+SQLITE_PRIVATE void sqlite3InvalidFunction(sqlite3_context*,int,sqlite3_value**);
+SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*);
+SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*);
+SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *);
+
+
+/*
+** Available fault injectors. Should be numbered beginning with 0.
+*/
+#define SQLITE_FAULTINJECTOR_MALLOC 0
+#define SQLITE_FAULTINJECTOR_COUNT 1
+
+/*
+** The interface to the fault injector subsystem. If the fault injector
+** mechanism is disabled at compile-time then set up macros so that no
+** unnecessary code is generated.
+*/
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+SQLITE_PRIVATE void sqlite3FaultConfig(int,int,int);
+SQLITE_PRIVATE int sqlite3FaultFailures(int);
+SQLITE_PRIVATE int sqlite3FaultBenignFailures(int);
+SQLITE_PRIVATE int sqlite3FaultPending(int);
+SQLITE_PRIVATE void sqlite3FaultBeginBenign(int);
+SQLITE_PRIVATE void sqlite3FaultEndBenign(int);
+SQLITE_PRIVATE int sqlite3FaultStep(int);
+#else
+# define sqlite3FaultConfig(A,B,C)
+# define sqlite3FaultFailures(A) 0
+# define sqlite3FaultBenignFailures(A) 0
+# define sqlite3FaultPending(A) (-1)
+# define sqlite3FaultBeginBenign(A)
+# define sqlite3FaultEndBenign(A)
+# define sqlite3FaultStep(A) 0
+#endif
+
+
+
+#define IN_INDEX_ROWID 1
+#define IN_INDEX_EPH 2
+#define IN_INDEX_INDEX 3
+SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, int);
+
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+SQLITE_PRIVATE int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int);
+SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *);
+SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *);
+#else
+ #define sqlite3JournalSize(pVfs) ((pVfs)->szOsFile)
+#endif
+
+#if defined(SQLITE_TEST) || SQLITE_MAX_EXPR_DEPTH>0
+SQLITE_PRIVATE void sqlite3ExprSetHeight(Expr *);
+SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *);
+#else
+ #define sqlite3ExprSetHeight(x)
+#endif
+
+SQLITE_PRIVATE u32 sqlite3Get4byte(const u8*);
+SQLITE_PRIVATE void sqlite3Put4byte(u8*, u32);
+
+#ifdef SQLITE_SSE
+#include "sseInt.h"
+#endif
+
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE void sqlite3ParserTrace(FILE*, char *);
+#endif
+
+/*
+** If the SQLITE_ENABLE IOTRACE exists then the global variable
+** sqlite3IoTrace is a pointer to a printf-like routine used to
+** print I/O tracing messages.
+*/
+#ifdef SQLITE_ENABLE_IOTRACE
+# define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; }
+SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe*);
+SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*,...);
+#else
+# define IOTRACE(A)
+# define sqlite3VdbeIOTraceSql(X)
+#endif
+
+#endif
+
+/************** End of sqliteInt.h *******************************************/
+/************** Begin file date.c ********************************************/
+/*
+** 2003 October 31
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement date and time
+** functions for SQLite.
+**
+** There is only one exported symbol in this file - the function
+** sqlite3RegisterDateTimeFunctions() found at the bottom of the file.
+** All other code has file scope.
+**
+** $Id: date.c,v 1.79 2008/03/20 14:03:29 drh Exp $
+**
+** SQLite processes all times and dates as Julian Day numbers. The
+** dates and times are stored as the number of days since noon
+** in Greenwich on November 24, 4714 B.C. according to the Gregorian
+** calendar system.
+**
+** 1970-01-01 00:00:00 is JD 2440587.5
+** 2000-01-01 00:00:00 is JD 2451544.5
+**
+** This implemention requires years to be expressed as a 4-digit number
+** which means that only dates between 0000-01-01 and 9999-12-31 can
+** be represented, even though julian day numbers allow a much wider
+** range of dates.
+**
+** The Gregorian calendar system is used for all dates and times,
+** even those that predate the Gregorian calendar. Historians usually
+** use the Julian calendar for dates prior to 1582-10-15 and for some
+** dates afterwards, depending on locale. Beware of this difference.
+**
+** The conversion algorithms are implemented based on descriptions
+** in the following text:
+**
+** Jean Meeus
+** Astronomical Algorithms, 2nd Edition, 1998
+** ISBM 0-943396-61-1
+** Willmann-Bell, Inc
+** Richmond, Virginia (USA)
+*/
+#include <ctype.h>
+#include <time.h>
+
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
+
+/*
+** A structure for holding a single date and time.
+*/
+typedef struct DateTime DateTime;
+struct DateTime {
+ double rJD; /* The julian day number */
+ int Y, M, D; /* Year, month, and day */
+ int h, m; /* Hour and minutes */
+ int tz; /* Timezone offset in minutes */
+ double s; /* Seconds */
+ char validYMD; /* True if Y,M,D are valid */
+ char validHMS; /* True if h,m,s are valid */
+ char validJD; /* True if rJD is valid */
+ char validTZ; /* True if tz is valid */
+};
+
+
+/*
+** Convert zDate into one or more integers. Additional arguments
+** come in groups of 5 as follows:
+**
+** N number of digits in the integer
+** min minimum allowed value of the integer
+** max maximum allowed value of the integer
+** nextC first character after the integer
+** pVal where to write the integers value.
+**
+** Conversions continue until one with nextC==0 is encountered.
+** The function returns the number of successful conversions.
+*/
+static int getDigits(const char *zDate, ...){
+ va_list ap;
+ int val;
+ int N;
+ int min;
+ int max;
+ int nextC;
+ int *pVal;
+ int cnt = 0;
+ va_start(ap, zDate);
+ do{
+ N = va_arg(ap, int);
+ min = va_arg(ap, int);
+ max = va_arg(ap, int);
+ nextC = va_arg(ap, int);
+ pVal = va_arg(ap, int*);
+ val = 0;
+ while( N-- ){
+ if( !isdigit(*(u8*)zDate) ){
+ goto end_getDigits;
+ }
+ val = val*10 + *zDate - '0';
+ zDate++;
+ }
+ if( val<min || val>max || (nextC!=0 && nextC!=*zDate) ){
+ goto end_getDigits;
+ }
+ *pVal = val;
+ zDate++;
+ cnt++;
+ }while( nextC );
+end_getDigits:
+ va_end(ap);
+ return cnt;
+}
+
+/*
+** Read text from z[] and convert into a floating point number. Return
+** the number of digits converted.
+*/
+#define getValue sqlite3AtoF
+
+/*
+** Parse a timezone extension on the end of a date-time.
+** The extension is of the form:
+**
+** (+/-)HH:MM
+**
+** Or the "zulu" notation:
+**
+** Z
+**
+** If the parse is successful, write the number of minutes
+** of change in p->tz and return 0. If a parser error occurs,
+** return non-zero.
+**
+** A missing specifier is not considered an error.
+*/
+static int parseTimezone(const char *zDate, DateTime *p){
+ int sgn = 0;
+ int nHr, nMn;
+ int c;
+ while( isspace(*(u8*)zDate) ){ zDate++; }
+ p->tz = 0;
+ c = *zDate;
+ if( c=='-' ){
+ sgn = -1;
+ }else if( c=='+' ){
+ sgn = +1;
+ }else if( c=='Z' || c=='z' ){
+ zDate++;
+ goto zulu_time;
+ }else{
+ return c!=0;
+ }
+ zDate++;
+ if( getDigits(zDate, 2, 0, 14, ':', &nHr, 2, 0, 59, 0, &nMn)!=2 ){
+ return 1;
+ }
+ zDate += 5;
+ p->tz = sgn*(nMn + nHr*60);
+zulu_time:
+ while( isspace(*(u8*)zDate) ){ zDate++; }
+ return *zDate!=0;
+}
+
+/*
+** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF.
+** The HH, MM, and SS must each be exactly 2 digits. The
+** fractional seconds FFFF can be one or more digits.
+**
+** Return 1 if there is a parsing error and 0 on success.
+*/
+static int parseHhMmSs(const char *zDate, DateTime *p){
+ int h, m, s;
+ double ms = 0.0;
+ if( getDigits(zDate, 2, 0, 24, ':', &h, 2, 0, 59, 0, &m)!=2 ){
+ return 1;
+ }
+ zDate += 5;
+ if( *zDate==':' ){
+ zDate++;
+ if( getDigits(zDate, 2, 0, 59, 0, &s)!=1 ){
+ return 1;
+ }
+ zDate += 2;
+ if( *zDate=='.' && isdigit((u8)zDate[1]) ){
+ double rScale = 1.0;
+ zDate++;
+ while( isdigit(*(u8*)zDate) ){
+ ms = ms*10.0 + *zDate - '0';
+ rScale *= 10.0;
+ zDate++;
+ }
+ ms /= rScale;
+ }
+ }else{
+ s = 0;
+ }
+ p->validJD = 0;
+ p->validHMS = 1;
+ p->h = h;
+ p->m = m;
+ p->s = s + ms;
+ if( parseTimezone(zDate, p) ) return 1;
+ p->validTZ = p->tz!=0;
+ return 0;
+}
+
+/*
+** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume
+** that the YYYY-MM-DD is according to the Gregorian calendar.
+**
+** Reference: Meeus page 61
+*/
+static void computeJD(DateTime *p){
+ int Y, M, D, A, B, X1, X2;
+
+ if( p->validJD ) return;
+ if( p->validYMD ){
+ Y = p->Y;
+ M = p->M;
+ D = p->D;
+ }else{
+ Y = 2000; /* If no YMD specified, assume 2000-Jan-01 */
+ M = 1;
+ D = 1;
+ }
+ if( M<=2 ){
+ Y--;
+ M += 12;
+ }
+ A = Y/100;
+ B = 2 - A + (A/4);
+ X1 = 365.25*(Y+4716);
+ X2 = 30.6001*(M+1);
+ p->rJD = X1 + X2 + D + B - 1524.5;
+ p->validJD = 1;
+ if( p->validHMS ){
+ p->rJD += (p->h*3600.0 + p->m*60.0 + p->s)/86400.0;
+ if( p->validTZ ){
+ p->rJD -= p->tz*60/86400.0;
+ p->validYMD = 0;
+ p->validHMS = 0;
+ p->validTZ = 0;
+ }
+ }
+}
+
+/*
+** Parse dates of the form
+**
+** YYYY-MM-DD HH:MM:SS.FFF
+** YYYY-MM-DD HH:MM:SS
+** YYYY-MM-DD HH:MM
+** YYYY-MM-DD
+**
+** Write the result into the DateTime structure and return 0
+** on success and 1 if the input string is not a well-formed
+** date.
+*/
+static int parseYyyyMmDd(const char *zDate, DateTime *p){
+ int Y, M, D, neg;
+
+ if( zDate[0]=='-' ){
+ zDate++;
+ neg = 1;
+ }else{
+ neg = 0;
+ }
+ if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 ){
+ return 1;
+ }
+ zDate += 10;
+ while( isspace(*(u8*)zDate) || 'T'==*(u8*)zDate ){ zDate++; }
+ if( parseHhMmSs(zDate, p)==0 ){
+ /* We got the time */
+ }else if( *zDate==0 ){
+ p->validHMS = 0;
+ }else{
+ return 1;
+ }
+ p->validJD = 0;
+ p->validYMD = 1;
+ p->Y = neg ? -Y : Y;
+ p->M = M;
+ p->D = D;
+ if( p->validTZ ){
+ computeJD(p);
+ }
+ return 0;
+}
+
+/*
+** Attempt to parse the given string into a Julian Day Number. Return
+** the number of errors.
+**
+** The following are acceptable forms for the input string:
+**
+** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM
+** DDDD.DD
+** now
+**
+** In the first form, the +/-HH:MM is always optional. The fractional
+** seconds extension (the ".FFF") is optional. The seconds portion
+** (":SS.FFF") is option. The year and date can be omitted as long
+** as there is a time string. The time string can be omitted as long
+** as there is a year and date.
+*/
+static int parseDateOrTime(
+ sqlite3_context *context,
+ const char *zDate,
+ DateTime *p
+){
+ memset(p, 0, sizeof(*p));
+ if( parseYyyyMmDd(zDate,p)==0 ){
+ return 0;
+ }else if( parseHhMmSs(zDate, p)==0 ){
+ return 0;
+ }else if( sqlite3StrICmp(zDate,"now")==0){
+ double r;
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ sqlite3OsCurrentTime(db->pVfs, &r);
+ p->rJD = r;
+ p->validJD = 1;
+ return 0;
+ }else if( sqlite3IsNumber(zDate, 0, SQLITE_UTF8) ){
+ getValue(zDate, &p->rJD);
+ p->validJD = 1;
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** Compute the Year, Month, and Day from the julian day number.
+*/
+static void computeYMD(DateTime *p){
+ int Z, A, B, C, D, E, X1;
+ if( p->validYMD ) return;
+ if( !p->validJD ){
+ p->Y = 2000;
+ p->M = 1;
+ p->D = 1;
+ }else{
+ Z = p->rJD + 0.5;
+ A = (Z - 1867216.25)/36524.25;
+ A = Z + 1 + A - (A/4);
+ B = A + 1524;
+ C = (B - 122.1)/365.25;
+ D = 365.25*C;
+ E = (B-D)/30.6001;
+ X1 = 30.6001*E;
+ p->D = B - D - X1;
+ p->M = E<14 ? E-1 : E-13;
+ p->Y = p->M>2 ? C - 4716 : C - 4715;
+ }
+ p->validYMD = 1;
+}
+
+/*
+** Compute the Hour, Minute, and Seconds from the julian day number.
+*/
+static void computeHMS(DateTime *p){
+ int Z, s;
+ if( p->validHMS ) return;
+ computeJD(p);
+ Z = p->rJD + 0.5;
+ s = (p->rJD + 0.5 - Z)*86400000.0 + 0.5;
+ p->s = 0.001*s;
+ s = p->s;
+ p->s -= s;
+ p->h = s/3600;
+ s -= p->h*3600;
+ p->m = s/60;
+ p->s += s - p->m*60;
+ p->validHMS = 1;
+}
+
+/*
+** Compute both YMD and HMS
+*/
+static void computeYMD_HMS(DateTime *p){
+ computeYMD(p);
+ computeHMS(p);
+}
+
+/*
+** Clear the YMD and HMS and the TZ
+*/
+static void clearYMD_HMS_TZ(DateTime *p){
+ p->validYMD = 0;
+ p->validHMS = 0;
+ p->validTZ = 0;
+}
+
+/*
+** Compute the difference (in days) between localtime and UTC (a.k.a. GMT)
+** for the time value p where p is in UTC.
+*/
+static double localtimeOffset(DateTime *p){
+ DateTime x, y;
+ time_t t;
+ x = *p;
+ computeYMD_HMS(&x);
+ if( x.Y<1971 || x.Y>=2038 ){
+ x.Y = 2000;
+ x.M = 1;
+ x.D = 1;
+ x.h = 0;
+ x.m = 0;
+ x.s = 0.0;
+ } else {
+ int s = x.s + 0.5;
+ x.s = s;
+ }
+ x.tz = 0;
+ x.validJD = 0;
+ computeJD(&x);
+ t = (x.rJD-2440587.5)*86400.0 + 0.5;
+#ifdef HAVE_LOCALTIME_R
+ {
+ struct tm sLocal;
+ localtime_r(&t, &sLocal);
+ y.Y = sLocal.tm_year + 1900;
+ y.M = sLocal.tm_mon + 1;
+ y.D = sLocal.tm_mday;
+ y.h = sLocal.tm_hour;
+ y.m = sLocal.tm_min;
+ y.s = sLocal.tm_sec;
+ }
+#else
+ {
+ struct tm *pTm;
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ pTm = localtime(&t);
+ y.Y = pTm->tm_year + 1900;
+ y.M = pTm->tm_mon + 1;
+ y.D = pTm->tm_mday;
+ y.h = pTm->tm_hour;
+ y.m = pTm->tm_min;
+ y.s = pTm->tm_sec;
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ }
+#endif
+ y.validYMD = 1;
+ y.validHMS = 1;
+ y.validJD = 0;
+ y.validTZ = 0;
+ computeJD(&y);
+ return y.rJD - x.rJD;
+}
+
+/*
+** Process a modifier to a date-time stamp. The modifiers are
+** as follows:
+**
+** NNN days
+** NNN hours
+** NNN minutes
+** NNN.NNNN seconds
+** NNN months
+** NNN years
+** start of month
+** start of year
+** start of week
+** start of day
+** weekday N
+** unixepoch
+** localtime
+** utc
+**
+** Return 0 on success and 1 if there is any kind of error.
+*/
+static int parseModifier(const char *zMod, DateTime *p){
+ int rc = 1;
+ int n;
+ double r;
+ char *z, zBuf[30];
+ z = zBuf;
+ for(n=0; n<sizeof(zBuf)-1 && zMod[n]; n++){
+ z[n] = tolower(zMod[n]);
+ }
+ z[n] = 0;
+ switch( z[0] ){
+ case 'l': {
+ /* localtime
+ **
+ ** Assuming the current time value is UTC (a.k.a. GMT), shift it to
+ ** show local time.
+ */
+ if( strcmp(z, "localtime")==0 ){
+ computeJD(p);
+ p->rJD += localtimeOffset(p);
+ clearYMD_HMS_TZ(p);
+ rc = 0;
+ }
+ break;
+ }
+ case 'u': {
+ /*
+ ** unixepoch
+ **
+ ** Treat the current value of p->rJD as the number of
+ ** seconds since 1970. Convert to a real julian day number.
+ */
+ if( strcmp(z, "unixepoch")==0 && p->validJD ){
+ p->rJD = p->rJD/86400.0 + 2440587.5;
+ clearYMD_HMS_TZ(p);
+ rc = 0;
+ }else if( strcmp(z, "utc")==0 ){
+ double c1;
+ computeJD(p);
+ c1 = localtimeOffset(p);
+ p->rJD -= c1;
+ clearYMD_HMS_TZ(p);
+ p->rJD += c1 - localtimeOffset(p);
+ rc = 0;
+ }
+ break;
+ }
+ case 'w': {
+ /*
+ ** weekday N
+ **
+ ** Move the date to the same time on the next occurrence of
+ ** weekday N where 0==Sunday, 1==Monday, and so forth. If the
+ ** date is already on the appropriate weekday, this is a no-op.
+ */
+ if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0
+ && (n=r)==r && n>=0 && r<7 ){
+ int Z;
+ computeYMD_HMS(p);
+ p->validTZ = 0;
+ p->validJD = 0;
+ computeJD(p);
+ Z = p->rJD + 1.5;
+ Z %= 7;
+ if( Z>n ) Z -= 7;
+ p->rJD += n - Z;
+ clearYMD_HMS_TZ(p);
+ rc = 0;
+ }
+ break;
+ }
+ case 's': {
+ /*
+ ** start of TTTTT
+ **
+ ** Move the date backwards to the beginning of the current day,
+ ** or month or year.
+ */
+ if( strncmp(z, "start of ", 9)!=0 ) break;
+ z += 9;
+ computeYMD(p);
+ p->validHMS = 1;
+ p->h = p->m = 0;
+ p->s = 0.0;
+ p->validTZ = 0;
+ p->validJD = 0;
+ if( strcmp(z,"month")==0 ){
+ p->D = 1;
+ rc = 0;
+ }else if( strcmp(z,"year")==0 ){
+ computeYMD(p);
+ p->M = 1;
+ p->D = 1;
+ rc = 0;
+ }else if( strcmp(z,"day")==0 ){
+ rc = 0;
+ }
+ break;
+ }
+ case '+':
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ n = getValue(z, &r);
+ assert( n>=1 );
+ if( z[n]==':' ){
+ /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the
+ ** specified number of hours, minutes, seconds, and fractional seconds
+ ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be
+ ** omitted.
+ */
+ const char *z2 = z;
+ DateTime tx;
+ int day;
+ if( !isdigit(*(u8*)z2) ) z2++;
+ memset(&tx, 0, sizeof(tx));
+ if( parseHhMmSs(z2, &tx) ) break;
+ computeJD(&tx);
+ tx.rJD -= 0.5;
+ day = (int)tx.rJD;
+ tx.rJD -= day;
+ if( z[0]=='-' ) tx.rJD = -tx.rJD;
+ computeJD(p);
+ clearYMD_HMS_TZ(p);
+ p->rJD += tx.rJD;
+ rc = 0;
+ break;
+ }
+ z += n;
+ while( isspace(*(u8*)z) ) z++;
+ n = strlen(z);
+ if( n>10 || n<3 ) break;
+ if( z[n-1]=='s' ){ z[n-1] = 0; n--; }
+ computeJD(p);
+ rc = 0;
+ if( n==3 && strcmp(z,"day")==0 ){
+ p->rJD += r;
+ }else if( n==4 && strcmp(z,"hour")==0 ){
+ p->rJD += r/24.0;
+ }else if( n==6 && strcmp(z,"minute")==0 ){
+ p->rJD += r/(24.0*60.0);
+ }else if( n==6 && strcmp(z,"second")==0 ){
+ p->rJD += r/(24.0*60.0*60.0);
+ }else if( n==5 && strcmp(z,"month")==0 ){
+ int x, y;
+ computeYMD_HMS(p);
+ p->M += r;
+ x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12;
+ p->Y += x;
+ p->M -= x*12;
+ p->validJD = 0;
+ computeJD(p);
+ y = r;
+ if( y!=r ){
+ p->rJD += (r - y)*30.0;
+ }
+ }else if( n==4 && strcmp(z,"year")==0 ){
+ computeYMD_HMS(p);
+ p->Y += r;
+ p->validJD = 0;
+ computeJD(p);
+ }else{
+ rc = 1;
+ }
+ clearYMD_HMS_TZ(p);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ return rc;
+}
+
+/*
+** Process time function arguments. argv[0] is a date-time stamp.
+** argv[1] and following are modifiers. Parse them all and write
+** the resulting time into the DateTime structure p. Return 0
+** on success and 1 if there are any errors.
+**
+** If there are zero parameters (if even argv[0] is undefined)
+** then assume a default value of "now" for argv[0].
+*/
+static int isDate(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv,
+ DateTime *p
+){
+ int i;
+ const unsigned char *z;
+ static const unsigned char zDflt[] = "now";
+ if( argc==0 ){
+ z = zDflt;
+ }else{
+ z = sqlite3_value_text(argv[0]);
+ }
+ if( !z || parseDateOrTime(context, (char*)z, p) ){
+ return 1;
+ }
+ for(i=1; i<argc; i++){
+ if( (z = sqlite3_value_text(argv[i]))==0 || parseModifier((char*)z, p) ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/*
+** The following routines implement the various date and time functions
+** of SQLite.
+*/
+
+/*
+** julianday( TIMESTRING, MOD, MOD, ...)
+**
+** Return the julian day number of the date specified in the arguments
+*/
+static void juliandayFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ DateTime x;
+ if( isDate(context, argc, argv, &x)==0 ){
+ computeJD(&x);
+ sqlite3_result_double(context, x.rJD);
+ }
+}
+
+/*
+** datetime( TIMESTRING, MOD, MOD, ...)
+**
+** Return YYYY-MM-DD HH:MM:SS
+*/
+static void datetimeFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ DateTime x;
+ if( isDate(context, argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeYMD_HMS(&x);
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d %02d:%02d:%02d",
+ x.Y, x.M, x.D, x.h, x.m, (int)(x.s));
+ sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ }
+}
+
+/*
+** time( TIMESTRING, MOD, MOD, ...)
+**
+** Return HH:MM:SS
+*/
+static void timeFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ DateTime x;
+ if( isDate(context, argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeHMS(&x);
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%02d:%02d:%02d", x.h, x.m, (int)x.s);
+ sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ }
+}
+
+/*
+** date( TIMESTRING, MOD, MOD, ...)
+**
+** Return YYYY-MM-DD
+*/
+static void dateFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ DateTime x;
+ if( isDate(context, argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeYMD(&x);
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D);
+ sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ }
+}
+
+/*
+** strftime( FORMAT, TIMESTRING, MOD, MOD, ...)
+**
+** Return a string described by FORMAT. Conversions as follows:
+**
+** %d day of month
+** %f ** fractional seconds SS.SSS
+** %H hour 00-24
+** %j day of year 000-366
+** %J ** Julian day number
+** %m month 01-12
+** %M minute 00-59
+** %s seconds since 1970-01-01
+** %S seconds 00-59
+** %w day of week 0-6 sunday==0
+** %W week of year 00-53
+** %Y year 0000-9999
+** %% %
+*/
+static void strftimeFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ DateTime x;
+ u64 n;
+ int i, j;
+ char *z;
+ const char *zFmt = (const char*)sqlite3_value_text(argv[0]);
+ char zBuf[100];
+ if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return;
+ for(i=0, n=1; zFmt[i]; i++, n++){
+ if( zFmt[i]=='%' ){
+ switch( zFmt[i+1] ){
+ case 'd':
+ case 'H':
+ case 'm':
+ case 'M':
+ case 'S':
+ case 'W':
+ n++;
+ /* fall thru */
+ case 'w':
+ case '%':
+ break;
+ case 'f':
+ n += 8;
+ break;
+ case 'j':
+ n += 3;
+ break;
+ case 'Y':
+ n += 8;
+ break;
+ case 's':
+ case 'J':
+ n += 50;
+ break;
+ default:
+ return; /* ERROR. return a NULL */
+ }
+ i++;
+ }
+ }
+ if( n<sizeof(zBuf) ){
+ z = zBuf;
+ }else if( n>sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH] ){
+ sqlite3_result_error_toobig(context);
+ return;
+ }else{
+ z = sqlite3_malloc( n );
+ if( z==0 ){
+ sqlite3_result_error_nomem(context);
+ return;
+ }
+ }
+ computeJD(&x);
+ computeYMD_HMS(&x);
+ for(i=j=0; zFmt[i]; i++){
+ if( zFmt[i]!='%' ){
+ z[j++] = zFmt[i];
+ }else{
+ i++;
+ switch( zFmt[i] ){
+ case 'd': sqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break;
+ case 'f': {
+ double s = x.s;
+ if( s>59.999 ) s = 59.999;
+ sqlite3_snprintf(7, &z[j],"%06.3f", s);
+ j += strlen(&z[j]);
+ break;
+ }
+ case 'H': sqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break;
+ case 'W': /* Fall thru */
+ case 'j': {
+ int nDay; /* Number of days since 1st day of year */
+ DateTime y = x;
+ y.validJD = 0;
+ y.M = 1;
+ y.D = 1;
+ computeJD(&y);
+ nDay = x.rJD - y.rJD + 0.5;
+ if( zFmt[i]=='W' ){
+ int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
+ wd = ((int)(x.rJD+0.5)) % 7;
+ sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7);
+ j += 2;
+ }else{
+ sqlite3_snprintf(4, &z[j],"%03d",nDay+1);
+ j += 3;
+ }
+ break;
+ }
+ case 'J': {
+ sqlite3_snprintf(20, &z[j],"%.16g",x.rJD);
+ j+=strlen(&z[j]);
+ break;
+ }
+ case 'm': sqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break;
+ case 'M': sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break;
+ case 's': {
+ sqlite3_snprintf(30,&z[j],"%d",
+ (int)((x.rJD-2440587.5)*86400.0 + 0.5));
+ j += strlen(&z[j]);
+ break;
+ }
+ case 'S': sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break;
+ case 'w': z[j++] = (((int)(x.rJD+1.5)) % 7) + '0'; break;
+ case 'Y': sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=strlen(&z[j]);break;
+ default: z[j++] = '%'; break;
+ }
+ }
+ }
+ z[j] = 0;
+ sqlite3_result_text(context, z, -1,
+ z==zBuf ? SQLITE_TRANSIENT : sqlite3_free);
+}
+
+/*
+** current_time()
+**
+** This function returns the same value as time('now').
+*/
+static void ctimeFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ timeFunc(context, 0, 0);
+}
+
+/*
+** current_date()
+**
+** This function returns the same value as date('now').
+*/
+static void cdateFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ dateFunc(context, 0, 0);
+}
+
+/*
+** current_timestamp()
+**
+** This function returns the same value as datetime('now').
+*/
+static void ctimestampFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ datetimeFunc(context, 0, 0);
+}
+#endif /* !defined(SQLITE_OMIT_DATETIME_FUNCS) */
+
+#ifdef SQLITE_OMIT_DATETIME_FUNCS
+/*
+** If the library is compiled to omit the full-scale date and time
+** handling (to get a smaller binary), the following minimal version
+** of the functions current_time(), current_date() and current_timestamp()
+** are included instead. This is to support column declarations that
+** include "DEFAULT CURRENT_TIME" etc.
+**
+** This function uses the C-library functions time(), gmtime()
+** and strftime(). The format string to pass to strftime() is supplied
+** as the user-data for the function.
+*/
+static void currentTimeFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ time_t t;
+ char *zFormat = (char *)sqlite3_user_data(context);
+ sqlite3 *db;
+ double rT;
+ char zBuf[20];
+
+ db = sqlite3_context_db_handle(context);
+ sqlite3OsCurrentTime(db->pVfs, &rT);
+ t = 86400.0*(rT - 2440587.5) + 0.5;
+#ifdef HAVE_GMTIME_R
+ {
+ struct tm sNow;
+ gmtime_r(&t, &sNow);
+ strftime(zBuf, 20, zFormat, &sNow);
+ }
+#else
+ {
+ struct tm *pTm;
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ pTm = gmtime(&t);
+ strftime(zBuf, 20, zFormat, pTm);
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ }
+#endif
+
+ sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+}
+#endif
+
+/*
+** This function registered all of the above C functions as SQL
+** functions. This should be the only routine in this file with
+** external linkage.
+*/
+SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(sqlite3 *db){
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
+ static const struct {
+ char *zName;
+ int nArg;
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
+ } aFuncs[] = {
+ { "julianday", -1, juliandayFunc },
+ { "date", -1, dateFunc },
+ { "time", -1, timeFunc },
+ { "datetime", -1, datetimeFunc },
+ { "strftime", -1, strftimeFunc },
+ { "current_time", 0, ctimeFunc },
+ { "current_timestamp", 0, ctimestampFunc },
+ { "current_date", 0, cdateFunc },
+ };
+ int i;
+
+ for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+ sqlite3CreateFunc(db, aFuncs[i].zName, aFuncs[i].nArg,
+ SQLITE_UTF8, 0, aFuncs[i].xFunc, 0, 0);
+ }
+#else
+ static const struct {
+ char *zName;
+ char *zFormat;
+ } aFuncs[] = {
+ { "current_time", "%H:%M:%S" },
+ { "current_date", "%Y-%m-%d" },
+ { "current_timestamp", "%Y-%m-%d %H:%M:%S" }
+ };
+ int i;
+
+ for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+ sqlite3CreateFunc(db, aFuncs[i].zName, 0, SQLITE_UTF8,
+ aFuncs[i].zFormat, currentTimeFunc, 0, 0);
+ }
+#endif
+}
+
+/************** End of date.c ************************************************/
+/************** Begin file os.c **********************************************/
+/*
+** 2005 November 29
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains OS interface code that is common to all
+** architectures.
+*/
+#define _SQLITE_OS_C_ 1
+#undef _SQLITE_OS_C_
+
+/*
+** The default SQLite sqlite3_vfs implementations do not allocate
+** memory (actually, os_unix.c allocates a small amount of memory
+** from within OsOpen()), but some third-party implementations may.
+** So we test the effects of a malloc() failing and the sqlite3OsXXX()
+** function returning SQLITE_IOERR_NOMEM using the DO_OS_MALLOC_TEST macro.
+**
+** The following functions are instrumented for malloc() failure
+** testing:
+**
+** sqlite3OsOpen()
+** sqlite3OsRead()
+** sqlite3OsWrite()
+** sqlite3OsSync()
+** sqlite3OsLock()
+**
+*/
+#if defined(SQLITE_TEST) && (OS_WIN==0)
+ #define DO_OS_MALLOC_TEST if (1) { \
+ void *pTstAlloc = sqlite3_malloc(10); \
+ if (!pTstAlloc) return SQLITE_IOERR_NOMEM; \
+ sqlite3_free(pTstAlloc); \
+ }
+#else
+ #define DO_OS_MALLOC_TEST
+#endif
+
+/*
+** The following routines are convenience wrappers around methods
+** of the sqlite3_file object. This is mostly just syntactic sugar. All
+** of this would be completely automatic if SQLite were coded using
+** C++ instead of plain old C.
+*/
+SQLITE_PRIVATE int sqlite3OsClose(sqlite3_file *pId){
+ int rc = SQLITE_OK;
+ if( pId->pMethods ){
+ rc = pId->pMethods->xClose(pId);
+ pId->pMethods = 0;
+ }
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file *id, void *pBuf, int amt, i64 offset){
+ DO_OS_MALLOC_TEST;
+ return id->pMethods->xRead(id, pBuf, amt, offset);
+}
+SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file *id, const void *pBuf, int amt, i64 offset){
+ DO_OS_MALLOC_TEST;
+ return id->pMethods->xWrite(id, pBuf, amt, offset);
+}
+SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file *id, i64 size){
+ return id->pMethods->xTruncate(id, size);
+}
+SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file *id, int flags){
+ DO_OS_MALLOC_TEST;
+ return id->pMethods->xSync(id, flags);
+}
+SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file *id, i64 *pSize){
+ return id->pMethods->xFileSize(id, pSize);
+}
+SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file *id, int lockType){
+ DO_OS_MALLOC_TEST;
+ return id->pMethods->xLock(id, lockType);
+}
+SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file *id, int lockType){
+ return id->pMethods->xUnlock(id, lockType);
+}
+SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id){
+ return id->pMethods->xCheckReservedLock(id);
+}
+SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){
+ return id->pMethods->xFileControl(id,op,pArg);
+}
+SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id){
+ int (*xSectorSize)(sqlite3_file*) = id->pMethods->xSectorSize;
+ return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE);
+}
+SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id){
+ return id->pMethods->xDeviceCharacteristics(id);
+}
+
+/*
+** The next group of routines are convenience wrappers around the
+** VFS methods.
+*/
+SQLITE_PRIVATE int sqlite3OsOpen(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ sqlite3_file *pFile,
+ int flags,
+ int *pFlagsOut
+){
+ DO_OS_MALLOC_TEST;
+ return pVfs->xOpen(pVfs, zPath, pFile, flags, pFlagsOut);
+}
+SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ return pVfs->xDelete(pVfs, zPath, dirSync);
+}
+SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *pVfs, const char *zPath, int flags){
+ int rc;
+#ifdef SQLITE_TEST
+ void *pTstAlloc = sqlite3_malloc(10);
+ if (!pTstAlloc) return -1;
+ sqlite3_free(pTstAlloc);
+#endif
+ rc = pVfs->xAccess(pVfs, zPath, flags);
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3OsGetTempname(sqlite3_vfs *pVfs, int nBufOut, char *zBufOut){
+ return pVfs->xGetTempname(pVfs, nBufOut, zBufOut);
+}
+SQLITE_PRIVATE int sqlite3OsFullPathname(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int nPathOut,
+ char *zPathOut
+){
+ return pVfs->xFullPathname(pVfs, zPath, nPathOut, zPathOut);
+}
+SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+ return pVfs->xDlOpen(pVfs, zPath);
+}
+SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ pVfs->xDlError(pVfs, nByte, zBufOut);
+}
+SQLITE_PRIVATE void *sqlite3OsDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol){
+ return pVfs->xDlSym(pVfs, pHandle, zSymbol);
+}
+SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *pVfs, void *pHandle){
+ pVfs->xDlClose(pVfs, pHandle);
+}
+SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ return pVfs->xRandomness(pVfs, nByte, zBufOut);
+}
+SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){
+ return pVfs->xSleep(pVfs, nMicro);
+}
+SQLITE_PRIVATE int sqlite3OsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+ return pVfs->xCurrentTime(pVfs, pTimeOut);
+}
+
+SQLITE_PRIVATE int sqlite3OsOpenMalloc(
+ sqlite3_vfs *pVfs,
+ const char *zFile,
+ sqlite3_file **ppFile,
+ int flags,
+ int *pOutFlags
+){
+ int rc = SQLITE_NOMEM;
+ sqlite3_file *pFile;
+ pFile = (sqlite3_file *)sqlite3_malloc(pVfs->szOsFile);
+ if( pFile ){
+ rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags);
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(pFile);
+ }else{
+ *ppFile = pFile;
+ }
+ }
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *pFile){
+ int rc = SQLITE_OK;
+ assert( pFile );
+ rc = sqlite3OsClose(pFile);
+ sqlite3_free(pFile);
+ return rc;
+}
+
+/*
+** The list of all registered VFS implementations. This list is
+** initialized to the single VFS returned by sqlite3OsDefaultVfs()
+** upon the first call to sqlite3_vfs_find().
+*/
+static sqlite3_vfs *vfsList = 0;
+
+/*
+** Locate a VFS by name. If no name is given, simply return the
+** first VFS on the list.
+*/
+SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfs){
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+#endif
+ sqlite3_vfs *pVfs = 0;
+ static int isInit = 0;
+ sqlite3_mutex_enter(mutex);
+ if( !isInit ){
+ vfsList = sqlite3OsDefaultVfs();
+ isInit = 1;
+ }
+ for(pVfs = vfsList; pVfs; pVfs=pVfs->pNext){
+ if( zVfs==0 ) break;
+ if( strcmp(zVfs, pVfs->zName)==0 ) break;
+ }
+ sqlite3_mutex_leave(mutex);
+ return pVfs;
+}
+
+/*
+** Unlink a VFS from the linked list
+*/
+static void vfsUnlink(sqlite3_vfs *pVfs){
+ assert( sqlite3_mutex_held(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)) );
+ if( pVfs==0 ){
+ /* No-op */
+ }else if( vfsList==pVfs ){
+ vfsList = pVfs->pNext;
+ }else if( vfsList ){
+ sqlite3_vfs *p = vfsList;
+ while( p->pNext && p->pNext!=pVfs ){
+ p = p->pNext;
+ }
+ if( p->pNext==pVfs ){
+ p->pNext = pVfs->pNext;
+ }
+ }
+}
+
+/*
+** Register a VFS with the system. It is harmless to register the same
+** VFS multiple times. The new VFS becomes the default if makeDflt is
+** true.
+*/
+SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+#endif
+ sqlite3_vfs_find(0); /* Make sure we are initialized */
+ sqlite3_mutex_enter(mutex);
+ vfsUnlink(pVfs);
+ if( makeDflt || vfsList==0 ){
+ pVfs->pNext = vfsList;
+ vfsList = pVfs;
+ }else{
+ pVfs->pNext = vfsList->pNext;
+ vfsList->pNext = pVfs;
+ }
+ assert(vfsList);
+ sqlite3_mutex_leave(mutex);
+ return SQLITE_OK;
+}
+
+/*
+** Unregister a VFS so that it is no longer accessible.
+*/
+SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+#endif
+ sqlite3_mutex_enter(mutex);
+ vfsUnlink(pVfs);
+ sqlite3_mutex_leave(mutex);
+ return SQLITE_OK;
+}
+
+/*
+** Provide a default sqlite3OsDefaultVfs() implementation in the
+** cases where none of the standard backends are used.
+*/
+#if !OS_UNIX && !OS_WIN && !OS_OS2
+SQLITE_PRIVATE sqlite3_vfs *sqlite3OsDefaultVfs(void){ return 0; }
+#endif
+
+/************** End of os.c **************************************************/
+/************** Begin file fault.c *******************************************/
+/*
+** 2008 Jan 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code to implement a fault-injector used for
+** testing and verification of SQLite.
+**
+** Subsystems within SQLite can call sqlite3FaultStep() to see if
+** they should simulate a fault. sqlite3FaultStep() normally returns
+** zero but will return non-zero if a fault should be simulated.
+** Fault injectors can be used, for example, to simulate memory
+** allocation failures or I/O errors.
+**
+** The fault injector is omitted from the code if SQLite is
+** compiled with -DSQLITE_OMIT_BUILTIN_TEST=1. There is a very
+** small performance hit for leaving the fault injector in the code.
+** Commerical products will probably want to omit the fault injector
+** from production builds. But safety-critical systems who work
+** under the motto "fly what you test and test what you fly" may
+** choose to leave the fault injector enabled even in production.
+*/
+
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+
+/*
+** There can be various kinds of faults. For example, there can be
+** a memory allocation failure. Or an I/O failure. For each different
+** fault type, there is a separate FaultInjector structure to keep track
+** of the status of that fault.
+*/
+static struct FaultInjector {
+ int iCountdown; /* Number of pending successes before we hit a failure */
+ int nRepeat; /* Number of times to repeat the failure */
+ int nBenign; /* Number of benign failures seen since last config */
+ int nFail; /* Number of failures seen since last config */
+ u8 enable; /* True if enabled */
+ i16 benign; /* Positive if next failure will be benign */
+} aFault[SQLITE_FAULTINJECTOR_COUNT];
+
+/*
+** This routine configures and enables a fault injector. After
+** calling this routine, aFaultStep() will return false (zero)
+** nDelay times, then it will return true nRepeat times,
+** then it will again begin returning false.
+*/
+SQLITE_PRIVATE void sqlite3FaultConfig(int id, int nDelay, int nRepeat){
+ assert( id>=0 && id<SQLITE_FAULTINJECTOR_COUNT );
+ aFault[id].iCountdown = nDelay;
+ aFault[id].nRepeat = nRepeat;
+ aFault[id].nBenign = 0;
+ aFault[id].nFail = 0;
+ aFault[id].enable = nDelay>=0;
+ aFault[id].benign = 0;
+}
+
+/*
+** Return the number of faults (both hard and benign faults) that have
+** occurred since the injector was last configured.
+*/
+SQLITE_PRIVATE int sqlite3FaultFailures(int id){
+ assert( id>=0 && id<SQLITE_FAULTINJECTOR_COUNT );
+ return aFault[id].nFail;
+}
+
+/*
+** Return the number of benign faults that have occurred since the
+** injector was last configured.
+*/
+SQLITE_PRIVATE int sqlite3FaultBenignFailures(int id){
+ assert( id>=0 && id<SQLITE_FAULTINJECTOR_COUNT );
+ return aFault[id].nBenign;
+}
+
+/*
+** Return the number of successes that will occur before the next failure.
+** If no failures are scheduled, return -1.
+*/
+SQLITE_PRIVATE int sqlite3FaultPending(int id){
+ assert( id>=0 && id<SQLITE_FAULTINJECTOR_COUNT );
+ if( aFault[id].enable ){
+ return aFault[id].iCountdown;
+ }else{
+ return -1;
+ }
+}
+
+/*
+** After this routine causes subsequent faults to be either benign
+** or hard (not benign), according to the "enable" parameter.
+**
+** Most faults are hard. In other words, most faults cause
+** an error to be propagated back up to the application interface.
+** However, sometimes a fault is easily recoverable. For example,
+** if a malloc fails while resizing a hash table, this is completely
+** recoverable simply by not carrying out the resize. The hash table
+** will continue to function normally. So a malloc failure during
+** a hash table resize is a benign fault.
+*/
+SQLITE_PRIVATE void sqlite3FaultBeginBenign(int id){
+ if( id<0 ){
+ for(id=0; id<SQLITE_FAULTINJECTOR_COUNT; id++){
+ aFault[id].benign++;
+ }
+ }else{
+ assert( id>=0 && id<SQLITE_FAULTINJECTOR_COUNT );
+ aFault[id].benign++;
+ }
+}
+SQLITE_PRIVATE void sqlite3FaultEndBenign(int id){
+ if( id<0 ){
+ for(id=0; id<SQLITE_FAULTINJECTOR_COUNT; id++){
+ assert( aFault[id].benign>0 );
+ aFault[id].benign--;
+ }
+ }else{
+ assert( id>=0 && id<SQLITE_FAULTINJECTOR_COUNT );
+ assert( aFault[id].benign>0 );
+ aFault[id].benign--;
+ }
+}
+
+/*
+** This routine exists as a place to set a breakpoint that will
+** fire on any simulated fault.
+*/
+static void sqlite3Fault(void){
+ static int cnt = 0;
+ cnt++;
+}
+
+
+/*
+** Check to see if a fault should be simulated. Return true to simulate
+** the fault. Return false if the fault should not be simulated.
+*/
+SQLITE_PRIVATE int sqlite3FaultStep(int id){
+ assert( id>=0 && id<SQLITE_FAULTINJECTOR_COUNT );
+ if( likely(!aFault[id].enable) ){
+ return 0;
+ }
+ if( aFault[id].iCountdown>0 ){
+ aFault[id].iCountdown--;
+ return 0;
+ }
+ sqlite3Fault();
+ aFault[id].nFail++;
+ if( aFault[id].benign>0 ){
+ aFault[id].nBenign++;
+ }
+ aFault[id].nRepeat--;
+ if( aFault[id].nRepeat<=0 ){
+ aFault[id].enable = 0;
+ }
+ return 1;
+}
+
+#endif /* SQLITE_OMIT_BUILTIN_TEST */
+
+/************** End of fault.c ***********************************************/
+/************** Begin file mem1.c ********************************************/
+/*
+** 2007 August 14
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement a memory
+** allocation subsystem for use by SQLite.
+**
+** $Id: mem1.c,v 1.17 2008/03/18 00:07:11 drh Exp $
+*/
+
+/*
+** This version of the memory allocator is the default. It is
+** used when no other memory allocator is specified using compile-time
+** macros.
+*/
+#ifdef SQLITE_SYSTEM_MALLOC
+
+/*
+** All of the static variables used by this module are collected
+** into a single structure named "mem". This is to keep the
+** static variables organized and to reduce namespace pollution
+** when this module is combined with other in the amalgamation.
+*/
+static struct {
+ /*
+ ** The alarm callback and its arguments. The mem.mutex lock will
+ ** be held while the callback is running. Recursive calls into
+ ** the memory subsystem are allowed, but no new callbacks will be
+ ** issued. The alarmBusy variable is set to prevent recursive
+ ** callbacks.
+ */
+ sqlite3_int64 alarmThreshold;
+ void (*alarmCallback)(void*, sqlite3_int64,int);
+ void *alarmArg;
+ int alarmBusy;
+
+ /*
+ ** Mutex to control access to the memory allocation subsystem.
+ */
+ sqlite3_mutex *mutex;
+
+ /*
+ ** Current allocation and high-water mark.
+ */
+ sqlite3_int64 nowUsed;
+ sqlite3_int64 mxUsed;
+
+
+} mem;
+
+/*
+** Enter the mutex mem.mutex. Allocate it if it is not already allocated.
+*/
+static void enterMem(void){
+ if( mem.mutex==0 ){
+ mem.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM);
+ }
+ sqlite3_mutex_enter(mem.mutex);
+}
+
+/*
+** Return the amount of memory currently checked out.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_used(void){
+ sqlite3_int64 n;
+ enterMem();
+ n = mem.nowUsed;
+ sqlite3_mutex_leave(mem.mutex);
+ return n;
+}
+
+/*
+** Return the maximum amount of memory that has ever been
+** checked out since either the beginning of this process
+** or since the most recent reset.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
+ sqlite3_int64 n;
+ enterMem();
+ n = mem.mxUsed;
+ if( resetFlag ){
+ mem.mxUsed = mem.nowUsed;
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ return n;
+}
+
+/*
+** Change the alarm callback
+*/
+SQLITE_API int sqlite3_memory_alarm(
+ void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
+ void *pArg,
+ sqlite3_int64 iThreshold
+){
+ enterMem();
+ mem.alarmCallback = xCallback;
+ mem.alarmArg = pArg;
+ mem.alarmThreshold = iThreshold;
+ sqlite3_mutex_leave(mem.mutex);
+ return SQLITE_OK;
+}
+
+/*
+** Trigger the alarm
+*/
+static void sqlite3MemsysAlarm(int nByte){
+ void (*xCallback)(void*,sqlite3_int64,int);
+ sqlite3_int64 nowUsed;
+ void *pArg;
+ if( mem.alarmCallback==0 || mem.alarmBusy ) return;
+ mem.alarmBusy = 1;
+ xCallback = mem.alarmCallback;
+ nowUsed = mem.nowUsed;
+ pArg = mem.alarmArg;
+ sqlite3_mutex_leave(mem.mutex);
+ xCallback(pArg, nowUsed, nByte);
+ sqlite3_mutex_enter(mem.mutex);
+ mem.alarmBusy = 0;
+}
+
+/*
+** Allocate nBytes of memory
+*/
+SQLITE_API void *sqlite3_malloc(int nBytes){
+ sqlite3_int64 *p = 0;
+ if( nBytes>0 ){
+ enterMem();
+ if( mem.alarmCallback!=0 && mem.nowUsed+nBytes>=mem.alarmThreshold ){
+ sqlite3MemsysAlarm(nBytes);
+ }
+ if( sqlite3FaultStep(SQLITE_FAULTINJECTOR_MALLOC) ){
+ p = 0;
+ }else{
+ p = malloc(nBytes+8);
+ if( p==0 ){
+ sqlite3MemsysAlarm(nBytes);
+ p = malloc(nBytes+8);
+ }
+ }
+ if( p ){
+ p[0] = nBytes;
+ p++;
+ mem.nowUsed += nBytes;
+ if( mem.nowUsed>mem.mxUsed ){
+ mem.mxUsed = mem.nowUsed;
+ }
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ }
+ return (void*)p;
+}
+
+/*
+** Free memory.
+*/
+SQLITE_API void sqlite3_free(void *pPrior){
+ sqlite3_int64 *p;
+ int nByte;
+ if( pPrior==0 ){
+ return;
+ }
+ assert( mem.mutex!=0 );
+ p = pPrior;
+ p--;
+ nByte = (int)*p;
+ sqlite3_mutex_enter(mem.mutex);
+ mem.nowUsed -= nByte;
+ free(p);
+ sqlite3_mutex_leave(mem.mutex);
+}
+
+/*
+** Return the number of bytes allocated at p.
+*/
+SQLITE_PRIVATE int sqlite3MallocSize(void *p){
+ sqlite3_int64 *pInt;
+ if( !p ) return 0;
+ pInt = p;
+ return pInt[-1];
+}
+
+/*
+** Change the size of an existing memory allocation
+*/
+SQLITE_API void *sqlite3_realloc(void *pPrior, int nBytes){
+ int nOld;
+ sqlite3_int64 *p;
+ if( pPrior==0 ){
+ return sqlite3_malloc(nBytes);
+ }
+ if( nBytes<=0 ){
+ sqlite3_free(pPrior);
+ return 0;
+ }
+ p = pPrior;
+ p--;
+ nOld = (int)p[0];
+ assert( mem.mutex!=0 );
+ sqlite3_mutex_enter(mem.mutex);
+ if( mem.nowUsed+nBytes-nOld>=mem.alarmThreshold ){
+ sqlite3MemsysAlarm(nBytes-nOld);
+ }
+ if( sqlite3FaultStep(SQLITE_FAULTINJECTOR_MALLOC) ){
+ p = 0;
+ }else{
+ p = realloc(p, nBytes+8);
+ if( p==0 ){
+ sqlite3MemsysAlarm(nBytes);
+ p = pPrior;
+ p--;
+ p = realloc(p, nBytes+8);
+ }
+ }
+ if( p ){
+ p[0] = nBytes;
+ p++;
+ mem.nowUsed += nBytes-nOld;
+ if( mem.nowUsed>mem.mxUsed ){
+ mem.mxUsed = mem.nowUsed;
+ }
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ return (void*)p;
+}
+
+#endif /* SQLITE_SYSTEM_MALLOC */
+
+/************** End of mem1.c ************************************************/
+/************** Begin file mem2.c ********************************************/
+/*
+** 2007 August 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement a memory
+** allocation subsystem for use by SQLite.
+**
+** $Id: mem2.c,v 1.26 2008/04/10 14:57:25 drh Exp $
+*/
+
+/*
+** This version of the memory allocator is used only if the
+** SQLITE_MEMDEBUG macro is defined
+*/
+#ifdef SQLITE_MEMDEBUG
+
+/*
+** The backtrace functionality is only available with GLIBC
+*/
+#ifdef __GLIBC__
+ extern int backtrace(void**,int);
+ extern void backtrace_symbols_fd(void*const*,int,int);
+#else
+# define backtrace(A,B) 0
+# define backtrace_symbols_fd(A,B,C)
+#endif
+
+/*
+** Each memory allocation looks like this:
+**
+** ------------------------------------------------------------------------
+** | Title | backtrace pointers | MemBlockHdr | allocation | EndGuard |
+** ------------------------------------------------------------------------
+**
+** The application code sees only a pointer to the allocation. We have
+** to back up from the allocation pointer to find the MemBlockHdr. The
+** MemBlockHdr tells us the size of the allocation and the number of
+** backtrace pointers. There is also a guard word at the end of the
+** MemBlockHdr.
+*/
+struct MemBlockHdr {
+ i64 iSize; /* Size of this allocation */
+ struct MemBlockHdr *pNext, *pPrev; /* Linked list of all unfreed memory */
+ char nBacktrace; /* Number of backtraces on this alloc */
+ char nBacktraceSlots; /* Available backtrace slots */
+ short nTitle; /* Bytes of title; includes '\0' */
+ int iForeGuard; /* Guard word for sanity */
+};
+
+/*
+** Guard words
+*/
+#define FOREGUARD 0x80F5E153
+#define REARGUARD 0xE4676B53
+
+/*
+** Number of malloc size increments to track.
+*/
+#define NCSIZE 1000
+
+/*
+** All of the static variables used by this module are collected
+** into a single structure named "mem". This is to keep the
+** static variables organized and to reduce namespace pollution
+** when this module is combined with other in the amalgamation.
+*/
+static struct {
+ /*
+ ** The alarm callback and its arguments. The mem.mutex lock will
+ ** be held while the callback is running. Recursive calls into
+ ** the memory subsystem are allowed, but no new callbacks will be
+ ** issued. The alarmBusy variable is set to prevent recursive
+ ** callbacks.
+ */
+ sqlite3_int64 alarmThreshold;
+ void (*alarmCallback)(void*, sqlite3_int64, int);
+ void *alarmArg;
+ int alarmBusy;
+
+ /*
+ ** Mutex to control access to the memory allocation subsystem.
+ */
+ sqlite3_mutex *mutex;
+
+ /*
+ ** Current allocation and high-water mark.
+ */
+ sqlite3_int64 nowUsed;
+ sqlite3_int64 mxUsed;
+
+ /*
+ ** Head and tail of a linked list of all outstanding allocations
+ */
+ struct MemBlockHdr *pFirst;
+ struct MemBlockHdr *pLast;
+
+ /*
+ ** The number of levels of backtrace to save in new allocations.
+ */
+ int nBacktrace;
+ void (*xBacktrace)(int, int, void **);
+
+ /*
+ ** Title text to insert in front of each block
+ */
+ int nTitle; /* Bytes of zTitle to save. Includes '\0' and padding */
+ char zTitle[100]; /* The title text */
+
+ /*
+ ** sqlite3MallocDisallow() increments the following counter.
+ ** sqlite3MallocAllow() decrements it.
+ */
+ int disallow; /* Do not allow memory allocation */
+
+ /*
+ ** Gather statistics on the sizes of memory allocations.
+ ** sizeCnt[i] is the number of allocation attempts of i*8
+ ** bytes. i==NCSIZE is the number of allocation attempts for
+ ** sizes more than NCSIZE*8 bytes.
+ */
+ int sizeCnt[NCSIZE];
+
+} mem;
+
+
+/*
+** Enter the mutex mem.mutex. Allocate it if it is not already allocated.
+*/
+static void enterMem(void){
+ if( mem.mutex==0 ){
+ mem.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM);
+ }
+ sqlite3_mutex_enter(mem.mutex);
+}
+
+/*
+** Return the amount of memory currently checked out.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_used(void){
+ sqlite3_int64 n;
+ enterMem();
+ n = mem.nowUsed;
+ sqlite3_mutex_leave(mem.mutex);
+ return n;
+}
+
+/*
+** Return the maximum amount of memory that has ever been
+** checked out since either the beginning of this process
+** or since the most recent reset.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
+ sqlite3_int64 n;
+ enterMem();
+ n = mem.mxUsed;
+ if( resetFlag ){
+ mem.mxUsed = mem.nowUsed;
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ return n;
+}
+
+/*
+** Change the alarm callback
+*/
+SQLITE_API int sqlite3_memory_alarm(
+ void(*xCallback)(void *pArg, sqlite3_int64 used, int N),
+ void *pArg,
+ sqlite3_int64 iThreshold
+){
+ enterMem();
+ mem.alarmCallback = xCallback;
+ mem.alarmArg = pArg;
+ mem.alarmThreshold = iThreshold;
+ sqlite3_mutex_leave(mem.mutex);
+ return SQLITE_OK;
+}
+
+/*
+** Trigger the alarm
+*/
+static void sqlite3MemsysAlarm(int nByte){
+ void (*xCallback)(void*,sqlite3_int64,int);
+ sqlite3_int64 nowUsed;
+ void *pArg;
+ if( mem.alarmCallback==0 || mem.alarmBusy ) return;
+ mem.alarmBusy = 1;
+ xCallback = mem.alarmCallback;
+ nowUsed = mem.nowUsed;
+ pArg = mem.alarmArg;
+ sqlite3_mutex_leave(mem.mutex);
+ xCallback(pArg, nowUsed, nByte);
+ sqlite3_mutex_enter(mem.mutex);
+ mem.alarmBusy = 0;
+}
+
+/*
+** Given an allocation, find the MemBlockHdr for that allocation.
+**
+** This routine checks the guards at either end of the allocation and
+** if they are incorrect it asserts.
+*/
+static struct MemBlockHdr *sqlite3MemsysGetHeader(void *pAllocation){
+ struct MemBlockHdr *p;
+ int *pInt;
+ u8 *pU8;
+ int nReserve;
+
+ p = (struct MemBlockHdr*)pAllocation;
+ p--;
+ assert( p->iForeGuard==FOREGUARD );
+ nReserve = (p->iSize+7)&~7;
+ pInt = (int*)pAllocation;
+ pU8 = (u8*)pAllocation;
+ assert( pInt[nReserve/sizeof(int)]==REARGUARD );
+ assert( (nReserve-0)<=p->iSize || pU8[nReserve-1]==0x65 );
+ assert( (nReserve-1)<=p->iSize || pU8[nReserve-2]==0x65 );
+ assert( (nReserve-2)<=p->iSize || pU8[nReserve-3]==0x65 );
+ return p;
+}
+
+/*
+** Return the number of bytes currently allocated at address p.
+*/
+SQLITE_PRIVATE int sqlite3MallocSize(void *p){
+ struct MemBlockHdr *pHdr;
+ if( !p ){
+ return 0;
+ }
+ pHdr = sqlite3MemsysGetHeader(p);
+ return pHdr->iSize;
+}
+
+/*
+** Allocate nByte bytes of memory.
+*/
+SQLITE_API void *sqlite3_malloc(int nByte){
+ struct MemBlockHdr *pHdr;
+ void **pBt;
+ char *z;
+ int *pInt;
+ void *p = 0;
+ int totalSize;
+
+ if( nByte>0 ){
+ int nReserve;
+ enterMem();
+ assert( mem.disallow==0 );
+ if( mem.alarmCallback!=0 && mem.nowUsed+nByte>=mem.alarmThreshold ){
+ sqlite3MemsysAlarm(nByte);
+ }
+ nReserve = (nByte+7)&~7;
+ if( nReserve/8>NCSIZE-1 ){
+ mem.sizeCnt[NCSIZE-1]++;
+ }else{
+ mem.sizeCnt[nReserve/8]++;
+ }
+ totalSize = nReserve + sizeof(*pHdr) + sizeof(int) +
+ mem.nBacktrace*sizeof(void*) + mem.nTitle;
+ if( sqlite3FaultStep(SQLITE_FAULTINJECTOR_MALLOC) ){
+ p = 0;
+ }else{
+ p = malloc(totalSize);
+ if( p==0 ){
+ sqlite3MemsysAlarm(nByte);
+ p = malloc(totalSize);
+ }
+ }
+ if( p ){
+ z = p;
+ pBt = (void**)&z[mem.nTitle];
+ pHdr = (struct MemBlockHdr*)&pBt[mem.nBacktrace];
+ pHdr->pNext = 0;
+ pHdr->pPrev = mem.pLast;
+ if( mem.pLast ){
+ mem.pLast->pNext = pHdr;
+ }else{
+ mem.pFirst = pHdr;
+ }
+ mem.pLast = pHdr;
+ pHdr->iForeGuard = FOREGUARD;
+ pHdr->nBacktraceSlots = mem.nBacktrace;
+ pHdr->nTitle = mem.nTitle;
+ if( mem.nBacktrace ){
+ void *aAddr[40];
+ pHdr->nBacktrace = backtrace(aAddr, mem.nBacktrace+1)-1;
+ memcpy(pBt, &aAddr[1], pHdr->nBacktrace*sizeof(void*));
+ if( mem.xBacktrace ){
+ mem.xBacktrace(nByte, pHdr->nBacktrace-1, &aAddr[1]);
+ }
+ }else{
+ pHdr->nBacktrace = 0;
+ }
+ if( mem.nTitle ){
+ memcpy(z, mem.zTitle, mem.nTitle);
+ }
+ pHdr->iSize = nByte;
+ pInt = (int*)&pHdr[1];
+ pInt[nReserve/sizeof(int)] = REARGUARD;
+ memset(pInt, 0x65, nReserve);
+ mem.nowUsed += nByte;
+ if( mem.nowUsed>mem.mxUsed ){
+ mem.mxUsed = mem.nowUsed;
+ }
+ p = (void*)pInt;
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ }
+ return p;
+}
+
+/*
+** Free memory.
+*/
+SQLITE_API void sqlite3_free(void *pPrior){
+ struct MemBlockHdr *pHdr;
+ void **pBt;
+ char *z;
+ if( pPrior==0 ){
+ return;
+ }
+ assert( mem.mutex!=0 );
+ pHdr = sqlite3MemsysGetHeader(pPrior);
+ pBt = (void**)pHdr;
+ pBt -= pHdr->nBacktraceSlots;
+ sqlite3_mutex_enter(mem.mutex);
+ mem.nowUsed -= pHdr->iSize;
+ if( pHdr->pPrev ){
+ assert( pHdr->pPrev->pNext==pHdr );
+ pHdr->pPrev->pNext = pHdr->pNext;
+ }else{
+ assert( mem.pFirst==pHdr );
+ mem.pFirst = pHdr->pNext;
+ }
+ if( pHdr->pNext ){
+ assert( pHdr->pNext->pPrev==pHdr );
+ pHdr->pNext->pPrev = pHdr->pPrev;
+ }else{
+ assert( mem.pLast==pHdr );
+ mem.pLast = pHdr->pPrev;
+ }
+ z = (char*)pBt;
+ z -= pHdr->nTitle;
+ memset(z, 0x2b, sizeof(void*)*pHdr->nBacktraceSlots + sizeof(*pHdr) +
+ pHdr->iSize + sizeof(int) + pHdr->nTitle);
+ free(z);
+ sqlite3_mutex_leave(mem.mutex);
+}
+
+/*
+** Change the size of an existing memory allocation.
+**
+** For this debugging implementation, we *always* make a copy of the
+** allocation into a new place in memory. In this way, if the
+** higher level code is using pointer to the old allocation, it is
+** much more likely to break and we are much more liking to find
+** the error.
+*/
+SQLITE_API void *sqlite3_realloc(void *pPrior, int nByte){
+ struct MemBlockHdr *pOldHdr;
+ void *pNew;
+ if( pPrior==0 ){
+ return sqlite3_malloc(nByte);
+ }
+ if( nByte<=0 ){
+ sqlite3_free(pPrior);
+ return 0;
+ }
+ assert( mem.disallow==0 );
+ pOldHdr = sqlite3MemsysGetHeader(pPrior);
+ pNew = sqlite3_malloc(nByte);
+ if( pNew ){
+ memcpy(pNew, pPrior, nByte<pOldHdr->iSize ? nByte : pOldHdr->iSize);
+ if( nByte>pOldHdr->iSize ){
+ memset(&((char*)pNew)[pOldHdr->iSize], 0x2b, nByte - pOldHdr->iSize);
+ }
+ sqlite3_free(pPrior);
+ }
+ return pNew;
+}
+
+/*
+** Set the number of backtrace levels kept for each allocation.
+** A value of zero turns of backtracing. The number is always rounded
+** up to a multiple of 2.
+*/
+SQLITE_PRIVATE void sqlite3MemdebugBacktrace(int depth){
+ if( depth<0 ){ depth = 0; }
+ if( depth>20 ){ depth = 20; }
+ depth = (depth+1)&0xfe;
+ mem.nBacktrace = depth;
+}
+
+SQLITE_PRIVATE void sqlite3MemdebugBacktraceCallback(void (*xBacktrace)(int, int, void **)){
+ mem.xBacktrace = xBacktrace;
+}
+
+/*
+** Set the title string for subsequent allocations.
+*/
+SQLITE_PRIVATE void sqlite3MemdebugSettitle(const char *zTitle){
+ int n = strlen(zTitle) + 1;
+ enterMem();
+ if( n>=sizeof(mem.zTitle) ) n = sizeof(mem.zTitle)-1;
+ memcpy(mem.zTitle, zTitle, n);
+ mem.zTitle[n] = 0;
+ mem.nTitle = (n+7)&~7;
+ sqlite3_mutex_leave(mem.mutex);
+}
+
+SQLITE_PRIVATE void sqlite3MemdebugSync(){
+ struct MemBlockHdr *pHdr;
+ for(pHdr=mem.pFirst; pHdr; pHdr=pHdr->pNext){
+ void **pBt = (void**)pHdr;
+ pBt -= pHdr->nBacktraceSlots;
+ mem.xBacktrace(pHdr->iSize, pHdr->nBacktrace-1, &pBt[1]);
+ }
+}
+
+/*
+** Open the file indicated and write a log of all unfreed memory
+** allocations into that log.
+*/
+SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){
+ FILE *out;
+ struct MemBlockHdr *pHdr;
+ void **pBt;
+ int i;
+ out = fopen(zFilename, "w");
+ if( out==0 ){
+ fprintf(stderr, "** Unable to output memory debug output log: %s **\n",
+ zFilename);
+ return;
+ }
+ for(pHdr=mem.pFirst; pHdr; pHdr=pHdr->pNext){
+ char *z = (char*)pHdr;
+ z -= pHdr->nBacktraceSlots*sizeof(void*) + pHdr->nTitle;
+ fprintf(out, "**** %lld bytes at %p from %s ****\n",
+ pHdr->iSize, &pHdr[1], pHdr->nTitle ? z : "???");
+ if( pHdr->nBacktrace ){
+ fflush(out);
+ pBt = (void**)pHdr;
+ pBt -= pHdr->nBacktraceSlots;
+ backtrace_symbols_fd(pBt, pHdr->nBacktrace, fileno(out));
+ fprintf(out, "\n");
+ }
+ }
+ fprintf(out, "COUNTS:\n");
+ for(i=0; i<NCSIZE-1; i++){
+ if( mem.sizeCnt[i] ){
+ fprintf(out, " %3d: %d\n", i*8+8, mem.sizeCnt[i]);
+ }
+ }
+ if( mem.sizeCnt[NCSIZE-1] ){
+ fprintf(out, " >%3d: %d\n", NCSIZE*8, mem.sizeCnt[NCSIZE-1]);
+ }
+ fclose(out);
+}
+
+/*
+** Return the number of times sqlite3_malloc() has been called.
+*/
+SQLITE_PRIVATE int sqlite3MemdebugMallocCount(){
+ int i;
+ int nTotal = 0;
+ for(i=0; i<NCSIZE; i++){
+ nTotal += mem.sizeCnt[i];
+ }
+ return nTotal;
+}
+
+
+#endif /* SQLITE_MEMDEBUG */
+
+/************** End of mem2.c ************************************************/
+/************** Begin file mem3.c ********************************************/
+/*
+** 2007 October 14
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement a memory
+** allocation subsystem for use by SQLite.
+**
+** This version of the memory allocation subsystem omits all
+** use of malloc(). All dynamically allocatable memory is
+** contained in a static array, mem.aPool[]. The size of this
+** fixed memory pool is SQLITE_MEMORY_SIZE bytes.
+**
+** This version of the memory allocation subsystem is used if
+** and only if SQLITE_MEMORY_SIZE is defined.
+**
+** $Id: mem3.c,v 1.12 2008/02/19 15:15:16 drh Exp $
+*/
+
+/*
+** This version of the memory allocator is used only when
+** SQLITE_MEMORY_SIZE is defined.
+*/
+#ifdef SQLITE_MEMORY_SIZE
+
+/*
+** Maximum size (in Mem3Blocks) of a "small" chunk.
+*/
+#define MX_SMALL 10
+
+
+/*
+** Number of freelist hash slots
+*/
+#define N_HASH 61
+
+/*
+** A memory allocation (also called a "chunk") consists of two or
+** more blocks where each block is 8 bytes. The first 8 bytes are
+** a header that is not returned to the user.
+**
+** A chunk is two or more blocks that is either checked out or
+** free. The first block has format u.hdr. u.hdr.size4x is 4 times the
+** size of the allocation in blocks if the allocation is free.
+** The u.hdr.size4x&1 bit is true if the chunk is checked out and
+** false if the chunk is on the freelist. The u.hdr.size4x&2 bit
+** is true if the previous chunk is checked out and false if the
+** previous chunk is free. The u.hdr.prevSize field is the size of
+** the previous chunk in blocks if the previous chunk is on the
+** freelist. If the previous chunk is checked out, then
+** u.hdr.prevSize can be part of the data for that chunk and should
+** not be read or written.
+**
+** We often identify a chunk by its index in mem.aPool[]. When
+** this is done, the chunk index refers to the second block of
+** the chunk. In this way, the first chunk has an index of 1.
+** A chunk index of 0 means "no such chunk" and is the equivalent
+** of a NULL pointer.
+**
+** The second block of free chunks is of the form u.list. The
+** two fields form a double-linked list of chunks of related sizes.
+** Pointers to the head of the list are stored in mem.aiSmall[]
+** for smaller chunks and mem.aiHash[] for larger chunks.
+**
+** The second block of a chunk is user data if the chunk is checked
+** out. If a chunk is checked out, the user data may extend into
+** the u.hdr.prevSize value of the following chunk.
+*/
+typedef struct Mem3Block Mem3Block;
+struct Mem3Block {
+ union {
+ struct {
+ u32 prevSize; /* Size of previous chunk in Mem3Block elements */
+ u32 size4x; /* 4x the size of current chunk in Mem3Block elements */
+ } hdr;
+ struct {
+ u32 next; /* Index in mem.aPool[] of next free chunk */
+ u32 prev; /* Index in mem.aPool[] of previous free chunk */
+ } list;
+ } u;
+};
+
+/*
+** All of the static variables used by this module are collected
+** into a single structure named "mem". This is to keep the
+** static variables organized and to reduce namespace pollution
+** when this module is combined with other in the amalgamation.
+*/
+static struct {
+ /*
+ ** True if we are evaluating an out-of-memory callback.
+ */
+ int alarmBusy;
+
+ /*
+ ** Mutex to control access to the memory allocation subsystem.
+ */
+ sqlite3_mutex *mutex;
+
+ /*
+ ** The minimum amount of free space that we have seen.
+ */
+ u32 mnMaster;
+
+ /*
+ ** iMaster is the index of the master chunk. Most new allocations
+ ** occur off of this chunk. szMaster is the size (in Mem3Blocks)
+ ** of the current master. iMaster is 0 if there is not master chunk.
+ ** The master chunk is not in either the aiHash[] or aiSmall[].
+ */
+ u32 iMaster;
+ u32 szMaster;
+
+ /*
+ ** Array of lists of free blocks according to the block size
+ ** for smaller chunks, or a hash on the block size for larger
+ ** chunks.
+ */
+ u32 aiSmall[MX_SMALL-1]; /* For sizes 2 through MX_SMALL, inclusive */
+ u32 aiHash[N_HASH]; /* For sizes MX_SMALL+1 and larger */
+
+ /*
+ ** Memory available for allocation
+ */
+ Mem3Block aPool[SQLITE_MEMORY_SIZE/sizeof(Mem3Block)+2];
+} mem;
+
+/*
+** Unlink the chunk at mem.aPool[i] from list it is currently
+** on. *pRoot is the list that i is a member of.
+*/
+static void memsys3UnlinkFromList(u32 i, u32 *pRoot){
+ u32 next = mem.aPool[i].u.list.next;
+ u32 prev = mem.aPool[i].u.list.prev;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ if( prev==0 ){
+ *pRoot = next;
+ }else{
+ mem.aPool[prev].u.list.next = next;
+ }
+ if( next ){
+ mem.aPool[next].u.list.prev = prev;
+ }
+ mem.aPool[i].u.list.next = 0;
+ mem.aPool[i].u.list.prev = 0;
+}
+
+/*
+** Unlink the chunk at index i from
+** whatever list is currently a member of.
+*/
+static void memsys3Unlink(u32 i){
+ u32 size, hash;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( (mem.aPool[i-1].u.hdr.size4x & 1)==0 );
+ assert( i>=1 );
+ size = mem.aPool[i-1].u.hdr.size4x/4;
+ assert( size==mem.aPool[i+size-1].u.hdr.prevSize );
+ assert( size>=2 );
+ if( size <= MX_SMALL ){
+ memsys3UnlinkFromList(i, &mem.aiSmall[size-2]);
+ }else{
+ hash = size % N_HASH;
+ memsys3UnlinkFromList(i, &mem.aiHash[hash]);
+ }
+}
+
+/*
+** Link the chunk at mem.aPool[i] so that is on the list rooted
+** at *pRoot.
+*/
+static void memsys3LinkIntoList(u32 i, u32 *pRoot){
+ assert( sqlite3_mutex_held(mem.mutex) );
+ mem.aPool[i].u.list.next = *pRoot;
+ mem.aPool[i].u.list.prev = 0;
+ if( *pRoot ){
+ mem.aPool[*pRoot].u.list.prev = i;
+ }
+ *pRoot = i;
+}
+
+/*
+** Link the chunk at index i into either the appropriate
+** small chunk list, or into the large chunk hash table.
+*/
+static void memsys3Link(u32 i){
+ u32 size, hash;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( i>=1 );
+ assert( (mem.aPool[i-1].u.hdr.size4x & 1)==0 );
+ size = mem.aPool[i-1].u.hdr.size4x/4;
+ assert( size==mem.aPool[i+size-1].u.hdr.prevSize );
+ assert( size>=2 );
+ if( size <= MX_SMALL ){
+ memsys3LinkIntoList(i, &mem.aiSmall[size-2]);
+ }else{
+ hash = size % N_HASH;
+ memsys3LinkIntoList(i, &mem.aiHash[hash]);
+ }
+}
+
+/*
+** Enter the mutex mem.mutex. Allocate it if it is not already allocated.
+**
+** Also: Initialize the memory allocation subsystem the first time
+** this routine is called.
+*/
+static void memsys3Enter(void){
+ if( mem.mutex==0 ){
+ mem.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM);
+ mem.aPool[0].u.hdr.size4x = SQLITE_MEMORY_SIZE/2 + 2;
+ mem.aPool[SQLITE_MEMORY_SIZE/8].u.hdr.prevSize = SQLITE_MEMORY_SIZE/8;
+ mem.aPool[SQLITE_MEMORY_SIZE/8].u.hdr.size4x = 1;
+ mem.iMaster = 1;
+ mem.szMaster = SQLITE_MEMORY_SIZE/8;
+ mem.mnMaster = mem.szMaster;
+ }
+ sqlite3_mutex_enter(mem.mutex);
+}
+
+/*
+** Return the amount of memory currently checked out.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_used(void){
+ sqlite3_int64 n;
+ memsys3Enter();
+ n = SQLITE_MEMORY_SIZE - mem.szMaster*8;
+ sqlite3_mutex_leave(mem.mutex);
+ return n;
+}
+
+/*
+** Return the maximum amount of memory that has ever been
+** checked out since either the beginning of this process
+** or since the most recent reset.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
+ sqlite3_int64 n;
+ memsys3Enter();
+ n = SQLITE_MEMORY_SIZE - mem.mnMaster*8;
+ if( resetFlag ){
+ mem.mnMaster = mem.szMaster;
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ return n;
+}
+
+/*
+** Change the alarm callback.
+**
+** This is a no-op for the static memory allocator. The purpose
+** of the memory alarm is to support sqlite3_soft_heap_limit().
+** But with this memory allocator, the soft_heap_limit is really
+** a hard limit that is fixed at SQLITE_MEMORY_SIZE.
+*/
+SQLITE_API int sqlite3_memory_alarm(
+ void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
+ void *pArg,
+ sqlite3_int64 iThreshold
+){
+ return SQLITE_OK;
+}
+
+/*
+** Called when we are unable to satisfy an allocation of nBytes.
+*/
+static void memsys3OutOfMemory(int nByte){
+ if( !mem.alarmBusy ){
+ mem.alarmBusy = 1;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ sqlite3_mutex_leave(mem.mutex);
+ sqlite3_release_memory(nByte);
+ sqlite3_mutex_enter(mem.mutex);
+ mem.alarmBusy = 0;
+ }
+}
+
+/*
+** Return the size of an outstanding allocation, in bytes. The
+** size returned omits the 8-byte header overhead. This only
+** works for chunks that are currently checked out.
+*/
+SQLITE_PRIVATE int sqlite3MallocSize(void *p){
+ int iSize = 0;
+ if( p ){
+ Mem3Block *pBlock = (Mem3Block*)p;
+ assert( (pBlock[-1].u.hdr.size4x&1)!=0 );
+ iSize = (pBlock[-1].u.hdr.size4x&~3)*2 - 4;
+ }
+ return iSize;
+}
+
+/*
+** Chunk i is a free chunk that has been unlinked. Adjust its
+** size parameters for check-out and return a pointer to the
+** user portion of the chunk.
+*/
+static void *memsys3Checkout(u32 i, int nBlock){
+ u32 x;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( i>=1 );
+ assert( mem.aPool[i-1].u.hdr.size4x/4==nBlock );
+ assert( mem.aPool[i+nBlock-1].u.hdr.prevSize==nBlock );
+ x = mem.aPool[i-1].u.hdr.size4x;
+ mem.aPool[i-1].u.hdr.size4x = nBlock*4 | 1 | (x&2);
+ mem.aPool[i+nBlock-1].u.hdr.prevSize = nBlock;
+ mem.aPool[i+nBlock-1].u.hdr.size4x |= 2;
+ return &mem.aPool[i];
+}
+
+/*
+** Carve a piece off of the end of the mem.iMaster free chunk.
+** Return a pointer to the new allocation. Or, if the master chunk
+** is not large enough, return 0.
+*/
+static void *memsys3FromMaster(int nBlock){
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( mem.szMaster>=nBlock );
+ if( nBlock>=mem.szMaster-1 ){
+ /* Use the entire master */
+ void *p = memsys3Checkout(mem.iMaster, mem.szMaster);
+ mem.iMaster = 0;
+ mem.szMaster = 0;
+ mem.mnMaster = 0;
+ return p;
+ }else{
+ /* Split the master block. Return the tail. */
+ u32 newi, x;
+ newi = mem.iMaster + mem.szMaster - nBlock;
+ assert( newi > mem.iMaster+1 );
+ mem.aPool[mem.iMaster+mem.szMaster-1].u.hdr.prevSize = nBlock;
+ mem.aPool[mem.iMaster+mem.szMaster-1].u.hdr.size4x |= 2;
+ mem.aPool[newi-1].u.hdr.size4x = nBlock*4 + 1;
+ mem.szMaster -= nBlock;
+ mem.aPool[newi-1].u.hdr.prevSize = mem.szMaster;
+ x = mem.aPool[mem.iMaster-1].u.hdr.size4x & 2;
+ mem.aPool[mem.iMaster-1].u.hdr.size4x = mem.szMaster*4 | x;
+ if( mem.szMaster < mem.mnMaster ){
+ mem.mnMaster = mem.szMaster;
+ }
+ return (void*)&mem.aPool[newi];
+ }
+}
+
+/*
+** *pRoot is the head of a list of free chunks of the same size
+** or same size hash. In other words, *pRoot is an entry in either
+** mem.aiSmall[] or mem.aiHash[].
+**
+** This routine examines all entries on the given list and tries
+** to coalesce each entries with adjacent free chunks.
+**
+** If it sees a chunk that is larger than mem.iMaster, it replaces
+** the current mem.iMaster with the new larger chunk. In order for
+** this mem.iMaster replacement to work, the master chunk must be
+** linked into the hash tables. That is not the normal state of
+** affairs, of course. The calling routine must link the master
+** chunk before invoking this routine, then must unlink the (possibly
+** changed) master chunk once this routine has finished.
+*/
+static void memsys3Merge(u32 *pRoot){
+ u32 iNext, prev, size, i, x;
+
+ assert( sqlite3_mutex_held(mem.mutex) );
+ for(i=*pRoot; i>0; i=iNext){
+ iNext = mem.aPool[i].u.list.next;
+ size = mem.aPool[i-1].u.hdr.size4x;
+ assert( (size&1)==0 );
+ if( (size&2)==0 ){
+ memsys3UnlinkFromList(i, pRoot);
+ assert( i > mem.aPool[i-1].u.hdr.prevSize );
+ prev = i - mem.aPool[i-1].u.hdr.prevSize;
+ if( prev==iNext ){
+ iNext = mem.aPool[prev].u.list.next;
+ }
+ memsys3Unlink(prev);
+ size = i + size/4 - prev;
+ x = mem.aPool[prev-1].u.hdr.size4x & 2;
+ mem.aPool[prev-1].u.hdr.size4x = size*4 | x;
+ mem.aPool[prev+size-1].u.hdr.prevSize = size;
+ memsys3Link(prev);
+ i = prev;
+ }else{
+ size /= 4;
+ }
+ if( size>mem.szMaster ){
+ mem.iMaster = i;
+ mem.szMaster = size;
+ }
+ }
+}
+
+/*
+** Return a block of memory of at least nBytes in size.
+** Return NULL if unable.
+*/
+static void *memsys3Malloc(int nByte){
+ u32 i;
+ int nBlock;
+ int toFree;
+
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( sizeof(Mem3Block)==8 );
+ if( nByte<=12 ){
+ nBlock = 2;
+ }else{
+ nBlock = (nByte + 11)/8;
+ }
+ assert( nBlock >= 2 );
+
+ /* STEP 1:
+ ** Look for an entry of the correct size in either the small
+ ** chunk table or in the large chunk hash table. This is
+ ** successful most of the time (about 9 times out of 10).
+ */
+ if( nBlock <= MX_SMALL ){
+ i = mem.aiSmall[nBlock-2];
+ if( i>0 ){
+ memsys3UnlinkFromList(i, &mem.aiSmall[nBlock-2]);
+ return memsys3Checkout(i, nBlock);
+ }
+ }else{
+ int hash = nBlock % N_HASH;
+ for(i=mem.aiHash[hash]; i>0; i=mem.aPool[i].u.list.next){
+ if( mem.aPool[i-1].u.hdr.size4x/4==nBlock ){
+ memsys3UnlinkFromList(i, &mem.aiHash[hash]);
+ return memsys3Checkout(i, nBlock);
+ }
+ }
+ }
+
+ /* STEP 2:
+ ** Try to satisfy the allocation by carving a piece off of the end
+ ** of the master chunk. This step usually works if step 1 fails.
+ */
+ if( mem.szMaster>=nBlock ){
+ return memsys3FromMaster(nBlock);
+ }
+
+
+ /* STEP 3:
+ ** Loop through the entire memory pool. Coalesce adjacent free
+ ** chunks. Recompute the master chunk as the largest free chunk.
+ ** Then try again to satisfy the allocation by carving a piece off
+ ** of the end of the master chunk. This step happens very
+ ** rarely (we hope!)
+ */
+ for(toFree=nBlock*16; toFree<SQLITE_MEMORY_SIZE*2; toFree *= 2){
+ memsys3OutOfMemory(toFree);
+ if( mem.iMaster ){
+ memsys3Link(mem.iMaster);
+ mem.iMaster = 0;
+ mem.szMaster = 0;
+ }
+ for(i=0; i<N_HASH; i++){
+ memsys3Merge(&mem.aiHash[i]);
+ }
+ for(i=0; i<MX_SMALL-1; i++){
+ memsys3Merge(&mem.aiSmall[i]);
+ }
+ if( mem.szMaster ){
+ memsys3Unlink(mem.iMaster);
+ if( mem.szMaster>=nBlock ){
+ return memsys3FromMaster(nBlock);
+ }
+ }
+ }
+
+ /* If none of the above worked, then we fail. */
+ return 0;
+}
+
+/*
+** Free an outstanding memory allocation.
+*/
+void memsys3Free(void *pOld){
+ Mem3Block *p = (Mem3Block*)pOld;
+ int i;
+ u32 size, x;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( p>mem.aPool && p<&mem.aPool[SQLITE_MEMORY_SIZE/8] );
+ i = p - mem.aPool;
+ assert( (mem.aPool[i-1].u.hdr.size4x&1)==1 );
+ size = mem.aPool[i-1].u.hdr.size4x/4;
+ assert( i+size<=SQLITE_MEMORY_SIZE/8+1 );
+ mem.aPool[i-1].u.hdr.size4x &= ~1;
+ mem.aPool[i+size-1].u.hdr.prevSize = size;
+ mem.aPool[i+size-1].u.hdr.size4x &= ~2;
+ memsys3Link(i);
+
+ /* Try to expand the master using the newly freed chunk */
+ if( mem.iMaster ){
+ while( (mem.aPool[mem.iMaster-1].u.hdr.size4x&2)==0 ){
+ size = mem.aPool[mem.iMaster-1].u.hdr.prevSize;
+ mem.iMaster -= size;
+ mem.szMaster += size;
+ memsys3Unlink(mem.iMaster);
+ x = mem.aPool[mem.iMaster-1].u.hdr.size4x & 2;
+ mem.aPool[mem.iMaster-1].u.hdr.size4x = mem.szMaster*4 | x;
+ mem.aPool[mem.iMaster+mem.szMaster-1].u.hdr.prevSize = mem.szMaster;
+ }
+ x = mem.aPool[mem.iMaster-1].u.hdr.size4x & 2;
+ while( (mem.aPool[mem.iMaster+mem.szMaster-1].u.hdr.size4x&1)==0 ){
+ memsys3Unlink(mem.iMaster+mem.szMaster);
+ mem.szMaster += mem.aPool[mem.iMaster+mem.szMaster-1].u.hdr.size4x/4;
+ mem.aPool[mem.iMaster-1].u.hdr.size4x = mem.szMaster*4 | x;
+ mem.aPool[mem.iMaster+mem.szMaster-1].u.hdr.prevSize = mem.szMaster;
+ }
+ }
+}
+
+/*
+** Allocate nBytes of memory
+*/
+SQLITE_API void *sqlite3_malloc(int nBytes){
+ sqlite3_int64 *p = 0;
+ if( nBytes>0 ){
+ memsys3Enter();
+ p = memsys3Malloc(nBytes);
+ sqlite3_mutex_leave(mem.mutex);
+ }
+ return (void*)p;
+}
+
+/*
+** Free memory.
+*/
+SQLITE_API void sqlite3_free(void *pPrior){
+ if( pPrior==0 ){
+ return;
+ }
+ assert( mem.mutex!=0 );
+ sqlite3_mutex_enter(mem.mutex);
+ memsys3Free(pPrior);
+ sqlite3_mutex_leave(mem.mutex);
+}
+
+/*
+** Change the size of an existing memory allocation
+*/
+SQLITE_API void *sqlite3_realloc(void *pPrior, int nBytes){
+ int nOld;
+ void *p;
+ if( pPrior==0 ){
+ return sqlite3_malloc(nBytes);
+ }
+ if( nBytes<=0 ){
+ sqlite3_free(pPrior);
+ return 0;
+ }
+ assert( mem.mutex!=0 );
+ nOld = sqlite3MallocSize(pPrior);
+ if( nBytes<=nOld && nBytes>=nOld-128 ){
+ return pPrior;
+ }
+ sqlite3_mutex_enter(mem.mutex);
+ p = memsys3Malloc(nBytes);
+ if( p ){
+ if( nOld<nBytes ){
+ memcpy(p, pPrior, nOld);
+ }else{
+ memcpy(p, pPrior, nBytes);
+ }
+ memsys3Free(pPrior);
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ return p;
+}
+
+/*
+** Open the file indicated and write a log of all unfreed memory
+** allocations into that log.
+*/
+SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){
+#ifdef SQLITE_DEBUG
+ FILE *out;
+ int i, j;
+ u32 size;
+ if( zFilename==0 || zFilename[0]==0 ){
+ out = stdout;
+ }else{
+ out = fopen(zFilename, "w");
+ if( out==0 ){
+ fprintf(stderr, "** Unable to output memory debug output log: %s **\n",
+ zFilename);
+ return;
+ }
+ }
+ memsys3Enter();
+ fprintf(out, "CHUNKS:\n");
+ for(i=1; i<=SQLITE_MEMORY_SIZE/8; i+=size/4){
+ size = mem.aPool[i-1].u.hdr.size4x;
+ if( size/4<=1 ){
+ fprintf(out, "%p size error\n", &mem.aPool[i]);
+ assert( 0 );
+ break;
+ }
+ if( (size&1)==0 && mem.aPool[i+size/4-1].u.hdr.prevSize!=size/4 ){
+ fprintf(out, "%p tail size does not match\n", &mem.aPool[i]);
+ assert( 0 );
+ break;
+ }
+ if( ((mem.aPool[i+size/4-1].u.hdr.size4x&2)>>1)!=(size&1) ){
+ fprintf(out, "%p tail checkout bit is incorrect\n", &mem.aPool[i]);
+ assert( 0 );
+ break;
+ }
+ if( size&1 ){
+ fprintf(out, "%p %6d bytes checked out\n", &mem.aPool[i], (size/4)*8-8);
+ }else{
+ fprintf(out, "%p %6d bytes free%s\n", &mem.aPool[i], (size/4)*8-8,
+ i==mem.iMaster ? " **master**" : "");
+ }
+ }
+ for(i=0; i<MX_SMALL-1; i++){
+ if( mem.aiSmall[i]==0 ) continue;
+ fprintf(out, "small(%2d):", i);
+ for(j = mem.aiSmall[i]; j>0; j=mem.aPool[j].u.list.next){
+ fprintf(out, " %p(%d)", &mem.aPool[j],
+ (mem.aPool[j-1].u.hdr.size4x/4)*8-8);
+ }
+ fprintf(out, "\n");
+ }
+ for(i=0; i<N_HASH; i++){
+ if( mem.aiHash[i]==0 ) continue;
+ fprintf(out, "hash(%2d):", i);
+ for(j = mem.aiHash[i]; j>0; j=mem.aPool[j].u.list.next){
+ fprintf(out, " %p(%d)", &mem.aPool[j],
+ (mem.aPool[j-1].u.hdr.size4x/4)*8-8);
+ }
+ fprintf(out, "\n");
+ }
+ fprintf(out, "master=%d\n", mem.iMaster);
+ fprintf(out, "nowUsed=%d\n", SQLITE_MEMORY_SIZE - mem.szMaster*8);
+ fprintf(out, "mxUsed=%d\n", SQLITE_MEMORY_SIZE - mem.mnMaster*8);
+ sqlite3_mutex_leave(mem.mutex);
+ if( out==stdout ){
+ fflush(stdout);
+ }else{
+ fclose(out);
+ }
+#endif
+}
+
+
+#endif /* !SQLITE_MEMORY_SIZE */
+
+/************** End of mem3.c ************************************************/
+/************** Begin file mem5.c ********************************************/
+/*
+** 2007 October 14
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement a memory
+** allocation subsystem for use by SQLite.
+**
+** This version of the memory allocation subsystem omits all
+** use of malloc(). All dynamically allocatable memory is
+** contained in a static array, mem.aPool[]. The size of this
+** fixed memory pool is SQLITE_POW2_MEMORY_SIZE bytes.
+**
+** This version of the memory allocation subsystem is used if
+** and only if SQLITE_POW2_MEMORY_SIZE is defined.
+**
+** $Id: mem5.c,v 1.4 2008/02/19 15:15:16 drh Exp $
+*/
+
+/*
+** This version of the memory allocator is used only when
+** SQLITE_POW2_MEMORY_SIZE is defined.
+*/
+#ifdef SQLITE_POW2_MEMORY_SIZE
+
+/*
+** Log2 of the minimum size of an allocation. For example, if
+** 4 then all allocations will be rounded up to at least 16 bytes.
+** If 5 then all allocations will be rounded up to at least 32 bytes.
+*/
+#ifndef SQLITE_POW2_LOGMIN
+# define SQLITE_POW2_LOGMIN 6
+#endif
+#define POW2_MIN (1<<SQLITE_POW2_LOGMIN)
+
+/*
+** Log2 of the maximum size of an allocation.
+*/
+#ifndef SQLITE_POW2_LOGMAX
+# define SQLITE_POW2_LOGMAX 18
+#endif
+#define POW2_MAX (((unsigned int)1)<<SQLITE_POW2_LOGMAX)
+
+/*
+** Number of distinct allocation sizes.
+*/
+#define NSIZE (SQLITE_POW2_LOGMAX - SQLITE_POW2_LOGMIN + 1)
+
+/*
+** A minimum allocation is an instance of the following structure.
+** Larger allocations are an array of these structures where the
+** size of the array is a power of 2.
+*/
+typedef struct Mem5Block Mem5Block;
+struct Mem5Block {
+ union {
+ char aData[POW2_MIN];
+ struct {
+ int next; /* Index in mem.aPool[] of next free chunk */
+ int prev; /* Index in mem.aPool[] of previous free chunk */
+ } list;
+ } u;
+};
+
+/*
+** Number of blocks of memory available for allocation.
+*/
+#define NBLOCK (SQLITE_POW2_MEMORY_SIZE/POW2_MIN)
+
+/*
+** The size in blocks of an POW2_MAX allocation
+*/
+#define SZ_MAX (1<<(NSIZE-1))
+
+/*
+** Masks used for mem.aCtrl[] elements.
+*/
+#define CTRL_LOGSIZE 0x1f /* Log2 Size of this block relative to POW2_MIN */
+#define CTRL_FREE 0x20 /* True if not checked out */
+
+/*
+** All of the static variables used by this module are collected
+** into a single structure named "mem". This is to keep the
+** static variables organized and to reduce namespace pollution
+** when this module is combined with other in the amalgamation.
+*/
+static struct {
+ /*
+ ** The alarm callback and its arguments. The mem.mutex lock will
+ ** be held while the callback is running. Recursive calls into
+ ** the memory subsystem are allowed, but no new callbacks will be
+ ** issued. The alarmBusy variable is set to prevent recursive
+ ** callbacks.
+ */
+ sqlite3_int64 alarmThreshold;
+ void (*alarmCallback)(void*, sqlite3_int64,int);
+ void *alarmArg;
+ int alarmBusy;
+
+ /*
+ ** Mutex to control access to the memory allocation subsystem.
+ */
+ sqlite3_mutex *mutex;
+
+ /*
+ ** Performance statistics
+ */
+ u64 nAlloc; /* Total number of calls to malloc */
+ u64 totalAlloc; /* Total of all malloc calls - includes internal frag */
+ u64 totalExcess; /* Total internal fragmentation */
+ u32 currentOut; /* Current checkout, including internal fragmentation */
+ u32 currentCount; /* Current number of distinct checkouts */
+ u32 maxOut; /* Maximum instantaneous currentOut */
+ u32 maxCount; /* Maximum instantaneous currentCount */
+ u32 maxRequest; /* Largest allocation (exclusive of internal frag) */
+
+ /*
+ ** Lists of free blocks of various sizes.
+ */
+ int aiFreelist[NSIZE];
+
+ /*
+ ** Space for tracking which blocks are checked out and the size
+ ** of each block. One byte per block.
+ */
+ u8 aCtrl[NBLOCK];
+
+ /*
+ ** Memory available for allocation
+ */
+ Mem5Block aPool[NBLOCK];
+} mem;
+
+/*
+** Unlink the chunk at mem.aPool[i] from list it is currently
+** on. It should be found on mem.aiFreelist[iLogsize].
+*/
+static void memsys5Unlink(int i, int iLogsize){
+ int next, prev;
+ assert( i>=0 && i<NBLOCK );
+ assert( iLogsize>=0 && iLogsize<NSIZE );
+ assert( (mem.aCtrl[i] & CTRL_LOGSIZE)==iLogsize );
+ assert( sqlite3_mutex_held(mem.mutex) );
+
+ next = mem.aPool[i].u.list.next;
+ prev = mem.aPool[i].u.list.prev;
+ if( prev<0 ){
+ mem.aiFreelist[iLogsize] = next;
+ }else{
+ mem.aPool[prev].u.list.next = next;
+ }
+ if( next>=0 ){
+ mem.aPool[next].u.list.prev = prev;
+ }
+}
+
+/*
+** Link the chunk at mem.aPool[i] so that is on the iLogsize
+** free list.
+*/
+static void memsys5Link(int i, int iLogsize){
+ int x;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( i>=0 && i<NBLOCK );
+ assert( iLogsize>=0 && iLogsize<NSIZE );
+ assert( (mem.aCtrl[i] & CTRL_LOGSIZE)==iLogsize );
+
+ mem.aPool[i].u.list.next = x = mem.aiFreelist[iLogsize];
+ mem.aPool[i].u.list.prev = -1;
+ if( x>=0 ){
+ assert( x<NBLOCK );
+ mem.aPool[x].u.list.prev = i;
+ }
+ mem.aiFreelist[iLogsize] = i;
+}
+
+/*
+** Enter the mutex mem.mutex. Allocate it if it is not already allocated.
+**
+** Also: Initialize the memory allocation subsystem the first time
+** this routine is called.
+*/
+static void memsys5Enter(void){
+ if( mem.mutex==0 ){
+ int i;
+ assert( sizeof(Mem5Block)==POW2_MIN );
+ assert( (SQLITE_POW2_MEMORY_SIZE % POW2_MAX)==0 );
+ assert( SQLITE_POW2_MEMORY_SIZE>=POW2_MAX );
+ mem.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM);
+ sqlite3_mutex_enter(mem.mutex);
+ for(i=0; i<NSIZE; i++) mem.aiFreelist[i] = -1;
+ for(i=0; i<=NBLOCK-SZ_MAX; i += SZ_MAX){
+ mem.aCtrl[i] = (NSIZE-1) | CTRL_FREE;
+ memsys5Link(i, NSIZE-1);
+ }
+ }else{
+ sqlite3_mutex_enter(mem.mutex);
+ }
+}
+
+/*
+** Return the amount of memory currently checked out.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_used(void){
+ return mem.currentOut;
+}
+
+/*
+** Return the maximum amount of memory that has ever been
+** checked out since either the beginning of this process
+** or since the most recent reset.
+*/
+SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
+ sqlite3_int64 n;
+ memsys5Enter();
+ n = mem.maxOut;
+ if( resetFlag ){
+ mem.maxOut = mem.currentOut;
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ return n;
+}
+
+
+/*
+** Trigger the alarm
+*/
+static void memsys5Alarm(int nByte){
+ void (*xCallback)(void*,sqlite3_int64,int);
+ sqlite3_int64 nowUsed;
+ void *pArg;
+ if( mem.alarmCallback==0 || mem.alarmBusy ) return;
+ mem.alarmBusy = 1;
+ xCallback = mem.alarmCallback;
+ nowUsed = mem.currentOut;
+ pArg = mem.alarmArg;
+ sqlite3_mutex_leave(mem.mutex);
+ xCallback(pArg, nowUsed, nByte);
+ sqlite3_mutex_enter(mem.mutex);
+ mem.alarmBusy = 0;
+}
+
+/*
+** Change the alarm callback.
+**
+** This is a no-op for the static memory allocator. The purpose
+** of the memory alarm is to support sqlite3_soft_heap_limit().
+** But with this memory allocator, the soft_heap_limit is really
+** a hard limit that is fixed at SQLITE_POW2_MEMORY_SIZE.
+*/
+SQLITE_API int sqlite3_memory_alarm(
+ void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
+ void *pArg,
+ sqlite3_int64 iThreshold
+){
+ memsys5Enter();
+ mem.alarmCallback = xCallback;
+ mem.alarmArg = pArg;
+ mem.alarmThreshold = iThreshold;
+ sqlite3_mutex_leave(mem.mutex);
+ return SQLITE_OK;
+}
+
+/*
+** Return the size of an outstanding allocation, in bytes. The
+** size returned omits the 8-byte header overhead. This only
+** works for chunks that are currently checked out.
+*/
+SQLITE_PRIVATE int sqlite3MallocSize(void *p){
+ int iSize = 0;
+ if( p ){
+ int i = ((Mem5Block*)p) - mem.aPool;
+ assert( i>=0 && i<NBLOCK );
+ iSize = 1 << ((mem.aCtrl[i]&CTRL_LOGSIZE) + SQLITE_POW2_LOGMIN);
+ }
+ return iSize;
+}
+
+/*
+** Find the first entry on the freelist iLogsize. Unlink that
+** entry and return its index.
+*/
+static int memsys5UnlinkFirst(int iLogsize){
+ int i;
+ int iFirst;
+
+ assert( iLogsize>=0 && iLogsize<NSIZE );
+ i = iFirst = mem.aiFreelist[iLogsize];
+ assert( iFirst>=0 );
+ while( i>0 ){
+ if( i<iFirst ) iFirst = i;
+ i = mem.aPool[i].u.list.next;
+ }
+ memsys5Unlink(iFirst, iLogsize);
+ return iFirst;
+}
+
+/*
+** Return a block of memory of at least nBytes in size.
+** Return NULL if unable.
+*/
+static void *memsys5Malloc(int nByte){
+ int i; /* Index of a mem.aPool[] slot */
+ int iBin; /* Index into mem.aiFreelist[] */
+ int iFullSz; /* Size of allocation rounded up to power of 2 */
+ int iLogsize; /* Log2 of iFullSz/POW2_MIN */
+
+ assert( sqlite3_mutex_held(mem.mutex) );
+
+ /* Keep track of the maximum allocation request. Even unfulfilled
+ ** requests are counted */
+ if( nByte>mem.maxRequest ){
+ mem.maxRequest = nByte;
+ }
+
+ /* Simulate a memory allocation fault */
+ if( sqlite3FaultStep(SQLITE_FAULTINJECTOR_MALLOC) ) return 0;
+
+ /* Round nByte up to the next valid power of two */
+ if( nByte>POW2_MAX ) return 0;
+ for(iFullSz=POW2_MIN, iLogsize=0; iFullSz<nByte; iFullSz *= 2, iLogsize++){}
+
+ /* If we will be over the memory alarm threshold after this allocation,
+ ** then trigger the memory overflow alarm */
+ if( mem.alarmCallback!=0 && mem.currentOut+iFullSz>=mem.alarmThreshold ){
+ memsys5Alarm(iFullSz);
+ }
+
+ /* Make sure mem.aiFreelist[iLogsize] contains at least one free
+ ** block. If not, then split a block of the next larger power of
+ ** two in order to create a new free block of size iLogsize.
+ */
+ for(iBin=iLogsize; mem.aiFreelist[iBin]<0 && iBin<NSIZE; iBin++){}
+ if( iBin>=NSIZE ) return 0;
+ i = memsys5UnlinkFirst(iBin);
+ while( iBin>iLogsize ){
+ int newSize;
+
+ iBin--;
+ newSize = 1 << iBin;
+ mem.aCtrl[i+newSize] = CTRL_FREE | iBin;
+ memsys5Link(i+newSize, iBin);
+ }
+ mem.aCtrl[i] = iLogsize;
+
+ /* Update allocator performance statistics. */
+ mem.nAlloc++;
+ mem.totalAlloc += iFullSz;
+ mem.totalExcess += iFullSz - nByte;
+ mem.currentCount++;
+ mem.currentOut += iFullSz;
+ if( mem.maxCount<mem.currentCount ) mem.maxCount = mem.currentCount;
+ if( mem.maxOut<mem.currentOut ) mem.maxOut = mem.currentOut;
+
+ /* Return a pointer to the allocated memory. */
+ return (void*)&mem.aPool[i];
+}
+
+/*
+** Free an outstanding memory allocation.
+*/
+void memsys5Free(void *pOld){
+ u32 size, iLogsize;
+ int i;
+
+ i = ((Mem5Block*)pOld) - mem.aPool;
+ assert( sqlite3_mutex_held(mem.mutex) );
+ assert( i>=0 && i<NBLOCK );
+ assert( (mem.aCtrl[i] & CTRL_FREE)==0 );
+ iLogsize = mem.aCtrl[i] & CTRL_LOGSIZE;
+ size = 1<<iLogsize;
+ assert( i+size-1<NBLOCK );
+ mem.aCtrl[i] |= CTRL_FREE;
+ mem.aCtrl[i+size-1] |= CTRL_FREE;
+ assert( mem.currentCount>0 );
+ assert( mem.currentOut>=0 );
+ mem.currentCount--;
+ mem.currentOut -= size*POW2_MIN;
+ assert( mem.currentOut>0 || mem.currentCount==0 );
+ assert( mem.currentCount>0 || mem.currentOut==0 );
+
+ mem.aCtrl[i] = CTRL_FREE | iLogsize;
+ while( iLogsize<NSIZE-1 ){
+ int iBuddy;
+
+ if( (i>>iLogsize) & 1 ){
+ iBuddy = i - size;
+ }else{
+ iBuddy = i + size;
+ }
+ assert( iBuddy>=0 && iBuddy<NBLOCK );
+ if( mem.aCtrl[iBuddy]!=(CTRL_FREE | iLogsize) ) break;
+ memsys5Unlink(iBuddy, iLogsize);
+ iLogsize++;
+ if( iBuddy<i ){
+ mem.aCtrl[iBuddy] = CTRL_FREE | iLogsize;
+ mem.aCtrl[i] = 0;
+ i = iBuddy;
+ }else{
+ mem.aCtrl[i] = CTRL_FREE | iLogsize;
+ mem.aCtrl[iBuddy] = 0;
+ }
+ size *= 2;
+ }
+ memsys5Link(i, iLogsize);
+}
+
+/*
+** Allocate nBytes of memory
+*/
+SQLITE_API void *sqlite3_malloc(int nBytes){
+ sqlite3_int64 *p = 0;
+ if( nBytes>0 ){
+ memsys5Enter();
+ p = memsys5Malloc(nBytes);
+ sqlite3_mutex_leave(mem.mutex);
+ }
+ return (void*)p;
+}
+
+/*
+** Free memory.
+*/
+SQLITE_API void sqlite3_free(void *pPrior){
+ if( pPrior==0 ){
+ return;
+ }
+ assert( mem.mutex!=0 );
+ sqlite3_mutex_enter(mem.mutex);
+ memsys5Free(pPrior);
+ sqlite3_mutex_leave(mem.mutex);
+}
+
+/*
+** Change the size of an existing memory allocation
+*/
+SQLITE_API void *sqlite3_realloc(void *pPrior, int nBytes){
+ int nOld;
+ void *p;
+ if( pPrior==0 ){
+ return sqlite3_malloc(nBytes);
+ }
+ if( nBytes<=0 ){
+ sqlite3_free(pPrior);
+ return 0;
+ }
+ assert( mem.mutex!=0 );
+ nOld = sqlite3MallocSize(pPrior);
+ if( nBytes<=nOld ){
+ return pPrior;
+ }
+ sqlite3_mutex_enter(mem.mutex);
+ p = memsys5Malloc(nBytes);
+ if( p ){
+ memcpy(p, pPrior, nOld);
+ memsys5Free(pPrior);
+ }
+ sqlite3_mutex_leave(mem.mutex);
+ return p;
+}
+
+/*
+** Open the file indicated and write a log of all unfreed memory
+** allocations into that log.
+*/
+SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){
+#ifdef SQLITE_DEBUG
+ FILE *out;
+ int i, j, n;
+
+ if( zFilename==0 || zFilename[0]==0 ){
+ out = stdout;
+ }else{
+ out = fopen(zFilename, "w");
+ if( out==0 ){
+ fprintf(stderr, "** Unable to output memory debug output log: %s **\n",
+ zFilename);
+ return;
+ }
+ }
+ memsys5Enter();
+ for(i=0; i<NSIZE; i++){
+ for(n=0, j=mem.aiFreelist[i]; j>=0; j = mem.aPool[j].u.list.next, n++){}
+ fprintf(out, "freelist items of size %d: %d\n", POW2_MIN << i, n);
+ }
+ fprintf(out, "mem.nAlloc = %llu\n", mem.nAlloc);
+ fprintf(out, "mem.totalAlloc = %llu\n", mem.totalAlloc);
+ fprintf(out, "mem.totalExcess = %llu\n", mem.totalExcess);
+ fprintf(out, "mem.currentOut = %u\n", mem.currentOut);
+ fprintf(out, "mem.currentCount = %u\n", mem.currentCount);
+ fprintf(out, "mem.maxOut = %u\n", mem.maxOut);
+ fprintf(out, "mem.maxCount = %u\n", mem.maxCount);
+ fprintf(out, "mem.maxRequest = %u\n", mem.maxRequest);
+ sqlite3_mutex_leave(mem.mutex);
+ if( out==stdout ){
+ fflush(stdout);
+ }else{
+ fclose(out);
+ }
+#endif
+}
+
+
+#endif /* !SQLITE_POW2_MEMORY_SIZE */
+
+/************** End of mem5.c ************************************************/
+/************** Begin file mutex.c *******************************************/
+/*
+** 2007 August 14
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement mutexes.
+**
+** The implementation in this file does not provide any mutual
+** exclusion and is thus suitable for use only in applications
+** that use SQLite in a single thread. But this implementation
+** does do a lot of error checking on mutexes to make sure they
+** are called correctly and at appropriate times. Hence, this
+** implementation is suitable for testing.
+** debugging purposes
+**
+** $Id: mutex.c,v 1.17 2008/03/26 18:34:43 danielk1977 Exp $
+*/
+
+#ifdef SQLITE_MUTEX_NOOP_DEBUG
+/*
+** In this implementation, mutexes do not provide any mutual exclusion.
+** But the error checking is provided. This implementation is useful
+** for test purposes.
+*/
+
+/*
+** The mutex object
+*/
+struct sqlite3_mutex {
+ int id; /* The mutex type */
+ int cnt; /* Number of entries without a matching leave */
+};
+
+/*
+** The sqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. If it returns NULL
+** that means that a mutex could not be allocated.
+*/
+SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int id){
+ static sqlite3_mutex aStatic[6];
+ sqlite3_mutex *pNew = 0;
+ switch( id ){
+ case SQLITE_MUTEX_FAST:
+ case SQLITE_MUTEX_RECURSIVE: {
+ pNew = sqlite3_malloc(sizeof(*pNew));
+ if( pNew ){
+ pNew->id = id;
+ pNew->cnt = 0;
+ }
+ break;
+ }
+ default: {
+ assert( id-2 >= 0 );
+ assert( id-2 < sizeof(aStatic)/sizeof(aStatic[0]) );
+ pNew = &aStatic[id-2];
+ pNew->id = id;
+ break;
+ }
+ }
+ return pNew;
+}
+
+/*
+** This routine deallocates a previously allocated mutex.
+*/
+SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){
+ assert( p );
+ assert( p->cnt==0 );
+ assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE );
+ sqlite3_free(p);
+}
+
+/*
+** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** to enter a mutex. If another thread is already within the mutex,
+** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
+** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK
+** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can
+** be entered multiple times by the same thread. In such cases the,
+** mutex must be exited an equal number of times before another thread
+** can enter. If the same thread tries to enter any other kind of mutex
+** more than once, the behavior is undefined.
+*/
+SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+ p->cnt++;
+}
+SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+ p->cnt++;
+ return SQLITE_OK;
+}
+
+/*
+** The sqlite3_mutex_leave() routine exits a mutex that was
+** previously entered by the same thread. The behavior
+** is undefined if the mutex is not currently entered or
+** is not currently allocated. SQLite will never do either.
+*/
+SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){
+ assert( p );
+ assert( sqlite3_mutex_held(p) );
+ p->cnt--;
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+}
+
+/*
+** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** intended for use inside assert() statements.
+*/
+SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){
+ return p==0 || p->cnt>0;
+}
+SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){
+ return p==0 || p->cnt==0;
+}
+#endif /* SQLITE_MUTEX_NOOP_DEBUG */
+
+/************** End of mutex.c ***********************************************/
+/************** Begin file mutex_os2.c ***************************************/
+/*
+** 2007 August 28
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement mutexes for OS/2
+**
+** $Id: mutex_os2.c,v 1.6 2008/03/26 18:34:43 danielk1977 Exp $
+*/
+
+/*
+** The code in this file is only used if SQLITE_MUTEX_OS2 is defined.
+** See the mutex.h file for details.
+*/
+#ifdef SQLITE_MUTEX_OS2
+
+/********************** OS/2 Mutex Implementation **********************
+**
+** This implementation of mutexes is built using the OS/2 API.
+*/
+
+/*
+** The mutex object
+** Each recursive mutex is an instance of the following structure.
+*/
+struct sqlite3_mutex {
+ HMTX mutex; /* Mutex controlling the lock */
+ int id; /* Mutex type */
+ int nRef; /* Number of references */
+ TID owner; /* Thread holding this mutex */
+};
+
+#define OS2_MUTEX_INITIALIZER 0,0,0,0
+
+/*
+** The sqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. If it returns NULL
+** that means that a mutex could not be allocated.
+** SQLite will unwind its stack and return an error. The argument
+** to sqlite3_mutex_alloc() is one of these integer constants:
+**
+** <ul>
+** <li> SQLITE_MUTEX_FAST 0
+** <li> SQLITE_MUTEX_RECURSIVE 1
+** <li> SQLITE_MUTEX_STATIC_MASTER 2
+** <li> SQLITE_MUTEX_STATIC_MEM 3
+** <li> SQLITE_MUTEX_STATIC_PRNG 4
+** </ul>
+**
+** The first two constants cause sqlite3_mutex_alloc() to create
+** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
+** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
+** The mutex implementation does not need to make a distinction
+** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does
+** not want to. But SQLite will only request a recursive mutex in
+** cases where it really needs one. If a faster non-recursive mutex
+** implementation is available on the host platform, the mutex subsystem
+** might return such a mutex in response to SQLITE_MUTEX_FAST.
+**
+** The other allowed parameters to sqlite3_mutex_alloc() each return
+** a pointer to a static preexisting mutex. Three static mutexes are
+** used by the current version of SQLite. Future versions of SQLite
+** may add additional static mutexes. Static mutexes are for internal
+** use by SQLite only. Applications that use SQLite mutexes should
+** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or
+** SQLITE_MUTEX_RECURSIVE.
+**
+** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
+** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** returns a different mutex on every call. But for the static
+** mutex types, the same mutex is returned on every call that has
+** the same type number.
+*/
+SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int iType){
+ sqlite3_mutex *p = NULL;
+ switch( iType ){
+ case SQLITE_MUTEX_FAST:
+ case SQLITE_MUTEX_RECURSIVE: {
+ p = sqlite3MallocZero( sizeof(*p) );
+ if( p ){
+ p->id = iType;
+ if( DosCreateMutexSem( 0, &p->mutex, 0, FALSE ) != NO_ERROR ){
+ sqlite3_free( p );
+ p = NULL;
+ }
+ }
+ break;
+ }
+ default: {
+ static volatile int isInit = 0;
+ static sqlite3_mutex staticMutexes[] = {
+ { OS2_MUTEX_INITIALIZER, },
+ { OS2_MUTEX_INITIALIZER, },
+ { OS2_MUTEX_INITIALIZER, },
+ { OS2_MUTEX_INITIALIZER, },
+ { OS2_MUTEX_INITIALIZER, },
+ { OS2_MUTEX_INITIALIZER, },
+ };
+ if ( !isInit ){
+ APIRET rc;
+ PTIB ptib;
+ PPIB ppib;
+ HMTX mutex;
+ char name[32];
+ DosGetInfoBlocks( &ptib, &ppib );
+ sqlite3_snprintf( sizeof(name), name, "\\SEM32\\SQLITE%04x",
+ ppib->pib_ulpid );
+ while( !isInit ){
+ mutex = 0;
+ rc = DosCreateMutexSem( name, &mutex, 0, FALSE);
+ if( rc == NO_ERROR ){
+ int i;
+ if( !isInit ){
+ for( i = 0; i < sizeof(staticMutexes)/sizeof(staticMutexes[0]); i++ ){
+ DosCreateMutexSem( 0, &staticMutexes[i].mutex, 0, FALSE );
+ }
+ isInit = 1;
+ }
+ DosCloseMutexSem( mutex );
+ }else if( rc == ERROR_DUPLICATE_NAME ){
+ DosSleep( 1 );
+ }else{
+ return p;
+ }
+ }
+ }
+ assert( iType-2 >= 0 );
+ assert( iType-2 < sizeof(staticMutexes)/sizeof(staticMutexes[0]) );
+ p = &staticMutexes[iType-2];
+ p->id = iType;
+ break;
+ }
+ }
+ return p;
+}
+
+
+/*
+** This routine deallocates a previously allocated mutex.
+** SQLite is careful to deallocate every mutex that it allocates.
+*/
+SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){
+ assert( p );
+ assert( p->nRef==0 );
+ assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE );
+ DosCloseMutexSem( p->mutex );
+ sqlite3_free( p );
+}
+
+/*
+** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** to enter a mutex. If another thread is already within the mutex,
+** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
+** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK
+** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can
+** be entered multiple times by the same thread. In such cases the,
+** mutex must be exited an equal number of times before another thread
+** can enter. If the same thread tries to enter any other kind of mutex
+** more than once, the behavior is undefined.
+*/
+SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){
+ TID tid;
+ PID holder1;
+ ULONG holder2;
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+ DosRequestMutexSem(p->mutex, SEM_INDEFINITE_WAIT);
+ DosQueryMutexSem(p->mutex, &holder1, &tid, &holder2);
+ p->owner = tid;
+ p->nRef++;
+}
+SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){
+ int rc;
+ TID tid;
+ PID holder1;
+ ULONG holder2;
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+ if( DosRequestMutexSem(p->mutex, SEM_IMMEDIATE_RETURN) == NO_ERROR) {
+ DosQueryMutexSem(p->mutex, &holder1, &tid, &holder2);
+ p->owner = tid;
+ p->nRef++;
+ rc = SQLITE_OK;
+ } else {
+ rc = SQLITE_BUSY;
+ }
+
+ return rc;
+}
+
+/*
+** The sqlite3_mutex_leave() routine exits a mutex that was
+** previously entered by the same thread. The behavior
+** is undefined if the mutex is not currently entered or
+** is not currently allocated. SQLite will never do either.
+*/
+SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){
+ TID tid;
+ PID holder1;
+ ULONG holder2;
+ assert( p->nRef>0 );
+ DosQueryMutexSem(p->mutex, &holder1, &tid, &holder2);
+ assert( p->owner==tid );
+ p->nRef--;
+ assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE );
+ DosReleaseMutexSem(p->mutex);
+}
+
+/*
+** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** intended for use inside assert() statements.
+*/
+SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){
+ TID tid;
+ PID pid;
+ ULONG ulCount;
+ PTIB ptib;
+ if( p!=0 ) {
+ DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
+ } else {
+ DosGetInfoBlocks(&ptib, NULL);
+ tid = ptib->tib_ptib2->tib2_ultid;
+ }
+ return p==0 || (p->nRef!=0 && p->owner==tid);
+}
+SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){
+ TID tid;
+ PID pid;
+ ULONG ulCount;
+ PTIB ptib;
+ if( p!= 0 ) {
+ DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
+ } else {
+ DosGetInfoBlocks(&ptib, NULL);
+ tid = ptib->tib_ptib2->tib2_ultid;
+ }
+ return p==0 || p->nRef==0 || p->owner!=tid;
+}
+#endif /* SQLITE_MUTEX_OS2 */
+
+/************** End of mutex_os2.c *******************************************/
+/************** Begin file mutex_unix.c **************************************/
+/*
+** 2007 August 28
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement mutexes for pthreads
+**
+** $Id: mutex_unix.c,v 1.7 2008/03/29 12:47:27 rse Exp $
+*/
+
+/*
+** The code in this file is only used if we are compiling threadsafe
+** under unix with pthreads.
+**
+** Note that this implementation requires a version of pthreads that
+** supports recursive mutexes.
+*/
+#ifdef SQLITE_MUTEX_PTHREADS
+
+#include <pthread.h>
+
+
+/*
+** Each recursive mutex is an instance of the following structure.
+*/
+struct sqlite3_mutex {
+ pthread_mutex_t mutex; /* Mutex controlling the lock */
+ int id; /* Mutex type */
+ int nRef; /* Number of entrances */
+ pthread_t owner; /* Thread that is within this mutex */
+#ifdef SQLITE_DEBUG
+ int trace; /* True to trace changes */
+#endif
+};
+#ifdef SQLITE_DEBUG
+#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, 0, 0, (pthread_t)0, 0 }
+#else
+#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, 0, 0, (pthread_t)0 }
+#endif
+
+/*
+** The sqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. If it returns NULL
+** that means that a mutex could not be allocated. SQLite
+** will unwind its stack and return an error. The argument
+** to sqlite3_mutex_alloc() is one of these integer constants:
+**
+** <ul>
+** <li> SQLITE_MUTEX_FAST
+** <li> SQLITE_MUTEX_RECURSIVE
+** <li> SQLITE_MUTEX_STATIC_MASTER
+** <li> SQLITE_MUTEX_STATIC_MEM
+** <li> SQLITE_MUTEX_STATIC_MEM2
+** <li> SQLITE_MUTEX_STATIC_PRNG
+** <li> SQLITE_MUTEX_STATIC_LRU
+** </ul>
+**
+** The first two constants cause sqlite3_mutex_alloc() to create
+** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
+** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
+** The mutex implementation does not need to make a distinction
+** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does
+** not want to. But SQLite will only request a recursive mutex in
+** cases where it really needs one. If a faster non-recursive mutex
+** implementation is available on the host platform, the mutex subsystem
+** might return such a mutex in response to SQLITE_MUTEX_FAST.
+**
+** The other allowed parameters to sqlite3_mutex_alloc() each return
+** a pointer to a static preexisting mutex. Three static mutexes are
+** used by the current version of SQLite. Future versions of SQLite
+** may add additional static mutexes. Static mutexes are for internal
+** use by SQLite only. Applications that use SQLite mutexes should
+** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or
+** SQLITE_MUTEX_RECURSIVE.
+**
+** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
+** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** returns a different mutex on every call. But for the static
+** mutex types, the same mutex is returned on every call that has
+** the same type number.
+*/
+SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int iType){
+ static sqlite3_mutex staticMutexes[] = {
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER,
+ SQLITE3_MUTEX_INITIALIZER
+ };
+ sqlite3_mutex *p;
+ switch( iType ){
+ case SQLITE_MUTEX_RECURSIVE: {
+ p = sqlite3MallocZero( sizeof(*p) );
+ if( p ){
+#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
+ /* If recursive mutexes are not available, we will have to
+ ** build our own. See below. */
+ pthread_mutex_init(&p->mutex, 0);
+#else
+ /* Use a recursive mutex if it is available */
+ pthread_mutexattr_t recursiveAttr;
+ pthread_mutexattr_init(&recursiveAttr);
+ pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&p->mutex, &recursiveAttr);
+ pthread_mutexattr_destroy(&recursiveAttr);
+#endif
+ p->id = iType;
+ }
+ break;
+ }
+ case SQLITE_MUTEX_FAST: {
+ p = sqlite3MallocZero( sizeof(*p) );
+ if( p ){
+ p->id = iType;
+ pthread_mutex_init(&p->mutex, 0);
+ }
+ break;
+ }
+ default: {
+ assert( iType-2 >= 0 );
+ assert( iType-2 < sizeof(staticMutexes)/sizeof(staticMutexes[0]) );
+ p = &staticMutexes[iType-2];
+ p->id = iType;
+ break;
+ }
+ }
+ return p;
+}
+
+
+/*
+** This routine deallocates a previously
+** allocated mutex. SQLite is careful to deallocate every
+** mutex that it allocates.
+*/
+SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){
+ assert( p );
+ assert( p->nRef==0 );
+ assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE );
+ pthread_mutex_destroy(&p->mutex);
+ sqlite3_free(p);
+}
+
+/*
+** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** to enter a mutex. If another thread is already within the mutex,
+** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
+** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK
+** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can
+** be entered multiple times by the same thread. In such cases the,
+** mutex must be exited an equal number of times before another thread
+** can enter. If the same thread tries to enter any other kind of mutex
+** more than once, the behavior is undefined.
+*/
+SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+
+#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
+ /* If recursive mutexes are not available, then we have to grow
+ ** our own. This implementation assumes that pthread_equal()
+ ** is atomic - that it cannot be deceived into thinking self
+ ** and p->owner are equal if p->owner changes between two values
+ ** that are not equal to self while the comparison is taking place.
+ ** This implementation also assumes a coherent cache - that
+ ** separate processes cannot read different values from the same
+ ** address at the same time. If either of these two conditions
+ ** are not met, then the mutexes will fail and problems will result.
+ */
+ {
+ pthread_t self = pthread_self();
+ if( p->nRef>0 && pthread_equal(p->owner, self) ){
+ p->nRef++;
+ }else{
+ pthread_mutex_lock(&p->mutex);
+ assert( p->nRef==0 );
+ p->owner = self;
+ p->nRef = 1;
+ }
+ }
+#else
+ /* Use the built-in recursive mutexes if they are available.
+ */
+ pthread_mutex_lock(&p->mutex);
+ p->owner = pthread_self();
+ p->nRef++;
+#endif
+
+#ifdef SQLITE_DEBUG
+ if( p->trace ){
+ printf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef);
+ }
+#endif
+}
+SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){
+ int rc;
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+
+#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
+ /* If recursive mutexes are not available, then we have to grow
+ ** our own. This implementation assumes that pthread_equal()
+ ** is atomic - that it cannot be deceived into thinking self
+ ** and p->owner are equal if p->owner changes between two values
+ ** that are not equal to self while the comparison is taking place.
+ ** This implementation also assumes a coherent cache - that
+ ** separate processes cannot read different values from the same
+ ** address at the same time. If either of these two conditions
+ ** are not met, then the mutexes will fail and problems will result.
+ */
+ {
+ pthread_t self = pthread_self();
+ if( p->nRef>0 && pthread_equal(p->owner, self) ){
+ p->nRef++;
+ rc = SQLITE_OK;
+ }else if( pthread_mutex_lock(&p->mutex)==0 ){
+ assert( p->nRef==0 );
+ p->owner = self;
+ p->nRef = 1;
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_BUSY;
+ }
+ }
+#else
+ /* Use the built-in recursive mutexes if they are available.
+ */
+ if( pthread_mutex_trylock(&p->mutex)==0 ){
+ p->owner = pthread_self();
+ p->nRef++;
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_BUSY;
+ }
+#endif
+
+#ifdef SQLITE_DEBUG
+ if( rc==SQLITE_OK && p->trace ){
+ printf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef);
+ }
+#endif
+ return rc;
+}
+
+/*
+** The sqlite3_mutex_leave() routine exits a mutex that was
+** previously entered by the same thread. The behavior
+** is undefined if the mutex is not currently entered or
+** is not currently allocated. SQLite will never do either.
+*/
+SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){
+ assert( p );
+ assert( sqlite3_mutex_held(p) );
+ p->nRef--;
+ assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE );
+
+#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
+ if( p->nRef==0 ){
+ pthread_mutex_unlock(&p->mutex);
+ }
+#else
+ pthread_mutex_unlock(&p->mutex);
+#endif
+
+#ifdef SQLITE_DEBUG
+ if( p->trace ){
+ printf("leave mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef);
+ }
+#endif
+}
+
+/*
+** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** intended for use only inside assert() statements. On some platforms,
+** there might be race conditions that can cause these routines to
+** deliver incorrect results. In particular, if pthread_equal() is
+** not an atomic operation, then these routines might delivery
+** incorrect results. On most platforms, pthread_equal() is a
+** comparison of two integers and is therefore atomic. But we are
+** told that HPUX is not such a platform. If so, then these routines
+** will not always work correctly on HPUX.
+**
+** On those platforms where pthread_equal() is not atomic, SQLite
+** should be compiled without -DSQLITE_DEBUG and with -DNDEBUG to
+** make sure no assert() statements are evaluated and hence these
+** routines are never called.
+*/
+#ifndef NDEBUG
+SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){
+ return p==0 || (p->nRef!=0 && pthread_equal(p->owner, pthread_self()));
+}
+SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){
+ return p==0 || p->nRef==0 || pthread_equal(p->owner, pthread_self())==0;
+}
+#endif
+#endif /* SQLITE_MUTEX_PTHREAD */
+
+/************** End of mutex_unix.c ******************************************/
+/************** Begin file mutex_w32.c ***************************************/
+/*
+** 2007 August 14
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement mutexes for win32
+**
+** $Id: mutex_w32.c,v 1.6 2008/03/26 18:34:43 danielk1977 Exp $
+*/
+
+/*
+** The code in this file is only used if we are compiling multithreaded
+** on a win32 system.
+*/
+#ifdef SQLITE_MUTEX_W32
+
+/*
+** Each recursive mutex is an instance of the following structure.
+*/
+struct sqlite3_mutex {
+ CRITICAL_SECTION mutex; /* Mutex controlling the lock */
+ int id; /* Mutex type */
+ int nRef; /* Number of enterances */
+ DWORD owner; /* Thread holding this mutex */
+};
+
+/*
+** Return true (non-zero) if we are running under WinNT, Win2K, WinXP,
+** or WinCE. Return false (zero) for Win95, Win98, or WinME.
+**
+** Here is an interesting observation: Win95, Win98, and WinME lack
+** the LockFileEx() API. But we can still statically link against that
+** API as long as we don't call it win running Win95/98/ME. A call to
+** this routine is used to determine if the host is Win95/98/ME or
+** WinNT/2K/XP so that we will know whether or not we can safely call
+** the LockFileEx() API.
+*/
+#if OS_WINCE
+# define mutexIsNT() (1)
+#else
+ static int mutexIsNT(void){
+ static int osType = 0;
+ if( osType==0 ){
+ OSVERSIONINFO sInfo;
+ sInfo.dwOSVersionInfoSize = sizeof(sInfo);
+ GetVersionEx(&sInfo);
+ osType = sInfo.dwPlatformId==VER_PLATFORM_WIN32_NT ? 2 : 1;
+ }
+ return osType==2;
+ }
+#endif /* OS_WINCE */
+
+
+/*
+** The sqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. If it returns NULL
+** that means that a mutex could not be allocated. SQLite
+** will unwind its stack and return an error. The argument
+** to sqlite3_mutex_alloc() is one of these integer constants:
+**
+** <ul>
+** <li> SQLITE_MUTEX_FAST 0
+** <li> SQLITE_MUTEX_RECURSIVE 1
+** <li> SQLITE_MUTEX_STATIC_MASTER 2
+** <li> SQLITE_MUTEX_STATIC_MEM 3
+** <li> SQLITE_MUTEX_STATIC_PRNG 4
+** </ul>
+**
+** The first two constants cause sqlite3_mutex_alloc() to create
+** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
+** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
+** The mutex implementation does not need to make a distinction
+** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does
+** not want to. But SQLite will only request a recursive mutex in
+** cases where it really needs one. If a faster non-recursive mutex
+** implementation is available on the host platform, the mutex subsystem
+** might return such a mutex in response to SQLITE_MUTEX_FAST.
+**
+** The other allowed parameters to sqlite3_mutex_alloc() each return
+** a pointer to a static preexisting mutex. Three static mutexes are
+** used by the current version of SQLite. Future versions of SQLite
+** may add additional static mutexes. Static mutexes are for internal
+** use by SQLite only. Applications that use SQLite mutexes should
+** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or
+** SQLITE_MUTEX_RECURSIVE.
+**
+** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
+** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** returns a different mutex on every call. But for the static
+** mutex types, the same mutex is returned on every call that has
+** the same type number.
+*/
+SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int iType){
+ sqlite3_mutex *p;
+
+ switch( iType ){
+ case SQLITE_MUTEX_FAST:
+ case SQLITE_MUTEX_RECURSIVE: {
+ p = sqlite3MallocZero( sizeof(*p) );
+ if( p ){
+ p->id = iType;
+ InitializeCriticalSection(&p->mutex);
+ }
+ break;
+ }
+ default: {
+ static sqlite3_mutex staticMutexes[6];
+ static int isInit = 0;
+ while( !isInit ){
+ static long lock = 0;
+ if( InterlockedIncrement(&lock)==1 ){
+ int i;
+ for(i=0; i<sizeof(staticMutexes)/sizeof(staticMutexes[0]); i++){
+ InitializeCriticalSection(&staticMutexes[i].mutex);
+ }
+ isInit = 1;
+ }else{
+ Sleep(1);
+ }
+ }
+ assert( iType-2 >= 0 );
+ assert( iType-2 < sizeof(staticMutexes)/sizeof(staticMutexes[0]) );
+ p = &staticMutexes[iType-2];
+ p->id = iType;
+ break;
+ }
+ }
+ return p;
+}
+
+
+/*
+** This routine deallocates a previously
+** allocated mutex. SQLite is careful to deallocate every
+** mutex that it allocates.
+*/
+SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){
+ assert( p );
+ assert( p->nRef==0 );
+ assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE );
+ DeleteCriticalSection(&p->mutex);
+ sqlite3_free(p);
+}
+
+/*
+** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** to enter a mutex. If another thread is already within the mutex,
+** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
+** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK
+** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can
+** be entered multiple times by the same thread. In such cases the,
+** mutex must be exited an equal number of times before another thread
+** can enter. If the same thread tries to enter any other kind of mutex
+** more than once, the behavior is undefined.
+*/
+SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+ EnterCriticalSection(&p->mutex);
+ p->owner = GetCurrentThreadId();
+ p->nRef++;
+}
+SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){
+ int rc = SQLITE_BUSY;
+ assert( p );
+ assert( p->id==SQLITE_MUTEX_RECURSIVE || sqlite3_mutex_notheld(p) );
+ /*
+ ** The sqlite3_mutex_try() routine is very rarely used, and when it
+ ** is used it is merely an optimization. So it is OK for it to always
+ ** fail.
+ **
+ ** The TryEnterCriticalSection() interface is only available on WinNT.
+ ** And some windows compilers complain if you try to use it without
+ ** first doing some #defines that prevent SQLite from building on Win98.
+ ** For that reason, we will omit this optimization for now. See
+ ** ticket #2685.
+ */
+#if 0
+ if( mutexIsNT() && TryEnterCriticalSection(&p->mutex) ){
+ p->owner = GetCurrentThreadId();
+ p->nRef++;
+ rc = SQLITE_OK;
+ }
+#endif
+ return rc;
+}
+
+/*
+** The sqlite3_mutex_leave() routine exits a mutex that was
+** previously entered by the same thread. The behavior
+** is undefined if the mutex is not currently entered or
+** is not currently allocated. SQLite will never do either.
+*/
+SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){
+ assert( p->nRef>0 );
+ assert( p->owner==GetCurrentThreadId() );
+ p->nRef--;
+ assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE );
+ LeaveCriticalSection(&p->mutex);
+}
+
+/*
+** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** intended for use only inside assert() statements.
+*/
+SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){
+ return p==0 || (p->nRef!=0 && p->owner==GetCurrentThreadId());
+}
+SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){
+ return p==0 || p->nRef==0 || p->owner!=GetCurrentThreadId();
+}
+#endif /* SQLITE_MUTEX_W32 */
+
+/************** End of mutex_w32.c *******************************************/
+/************** Begin file malloc.c ******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Memory allocation functions used throughout sqlite.
+**
+**
+** $Id: malloc.c,v 1.15 2008/03/26 18:34:43 danielk1977 Exp $
+*/
+
+/*
+** This routine runs when the memory allocator sees that the
+** total memory allocation is about to exceed the soft heap
+** limit.
+*/
+static void softHeapLimitEnforcer(
+ void *NotUsed,
+ sqlite3_int64 inUse,
+ int allocSize
+){
+ sqlite3_release_memory(allocSize);
+}
+
+/*
+** Set the soft heap-size limit for the current thread. Passing a
+** zero or negative value indicates no limit.
+*/
+SQLITE_API void sqlite3_soft_heap_limit(int n){
+ sqlite3_uint64 iLimit;
+ int overage;
+ if( n<0 ){
+ iLimit = 0;
+ }else{
+ iLimit = n;
+ }
+ if( iLimit>0 ){
+ sqlite3_memory_alarm(softHeapLimitEnforcer, 0, iLimit);
+ }else{
+ sqlite3_memory_alarm(0, 0, 0);
+ }
+ overage = sqlite3_memory_used() - n;
+ if( overage>0 ){
+ sqlite3_release_memory(overage);
+ }
+}
+
+/*
+** Release memory held by SQLite instances created by the current thread.
+*/
+SQLITE_API int sqlite3_release_memory(int n){
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ int nRet = sqlite3VdbeReleaseMemory(n);
+ nRet += sqlite3PagerReleaseMemory(n-nRet);
+ return nRet;
+#else
+ return SQLITE_OK;
+#endif
+}
+
+
+/*
+** Allocate and zero memory.
+*/
+SQLITE_PRIVATE void *sqlite3MallocZero(unsigned n){
+ void *p = sqlite3_malloc(n);
+ if( p ){
+ memset(p, 0, n);
+ }
+ return p;
+}
+
+/*
+** Allocate and zero memory. If the allocation fails, make
+** the mallocFailed flag in the connection pointer.
+*/
+SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3 *db, unsigned n){
+ void *p = sqlite3DbMallocRaw(db, n);
+ if( p ){
+ memset(p, 0, n);
+ }
+ return p;
+}
+
+/*
+** Allocate and zero memory. If the allocation fails, make
+** the mallocFailed flag in the connection pointer.
+*/
+SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3 *db, unsigned n){
+ void *p = 0;
+ if( !db || db->mallocFailed==0 ){
+ p = sqlite3_malloc(n);
+ if( !p && db ){
+ db->mallocFailed = 1;
+ }
+ }
+ return p;
+}
+
+/*
+** Resize the block of memory pointed to by p to n bytes. If the
+** resize fails, set the mallocFailed flag inthe connection object.
+*/
+SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *db, void *p, int n){
+ void *pNew = 0;
+ if( db->mallocFailed==0 ){
+ pNew = sqlite3_realloc(p, n);
+ if( !pNew ){
+ db->mallocFailed = 1;
+ }
+ }
+ return pNew;
+}
+
+/*
+** Attempt to reallocate p. If the reallocation fails, then free p
+** and set the mallocFailed flag in the database connection.
+*/
+SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, int n){
+ void *pNew;
+ pNew = sqlite3DbRealloc(db, p, n);
+ if( !pNew ){
+ sqlite3_free(p);
+ }
+ return pNew;
+}
+
+/*
+** Make a copy of a string in memory obtained from sqliteMalloc(). These
+** functions call sqlite3MallocRaw() directly instead of sqliteMalloc(). This
+** is because when memory debugging is turned on, these two functions are
+** called via macros that record the current file and line number in the
+** ThreadData structure.
+*/
+SQLITE_PRIVATE char *sqlite3StrDup(const char *z){
+ char *zNew;
+ int n;
+ if( z==0 ) return 0;
+ n = strlen(z)+1;
+ zNew = sqlite3_malloc(n);
+ if( zNew ) memcpy(zNew, z, n);
+ return zNew;
+}
+SQLITE_PRIVATE char *sqlite3StrNDup(const char *z, int n){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqlite3_malloc(n+1);
+ if( zNew ){
+ memcpy(zNew, z, n);
+ zNew[n] = 0;
+ }
+ return zNew;
+}
+
+SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3 *db, const char *z){
+ char *zNew = sqlite3StrDup(z);
+ if( z && !zNew ){
+ db->mallocFailed = 1;
+ }
+ return zNew;
+}
+SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, int n){
+ char *zNew = sqlite3StrNDup(z, n);
+ if( z && !zNew ){
+ db->mallocFailed = 1;
+ }
+ return zNew;
+}
+
+/*
+** Create a string from the 2nd and subsequent arguments (up to the
+** first NULL argument), store the string in memory obtained from
+** sqliteMalloc() and make the pointer indicated by the 1st argument
+** point to that string. The 1st argument must either be NULL or
+** point to memory obtained from sqliteMalloc().
+*/
+SQLITE_PRIVATE void sqlite3SetString(char **pz, ...){
+ va_list ap;
+ int nByte;
+ const char *z;
+ char *zResult;
+
+ assert( pz!=0 );
+ nByte = 1;
+ va_start(ap, pz);
+ while( (z = va_arg(ap, const char*))!=0 ){
+ nByte += strlen(z);
+ }
+ va_end(ap);
+ sqlite3_free(*pz);
+ *pz = zResult = sqlite3_malloc(nByte);
+ if( zResult==0 ){
+ return;
+ }
+ *zResult = 0;
+ va_start(ap, pz);
+ while( (z = va_arg(ap, const char*))!=0 ){
+ int n = strlen(z);
+ memcpy(zResult, z, n);
+ zResult += n;
+ }
+ zResult[0] = 0;
+ va_end(ap);
+}
+
+
+/*
+** This function must be called before exiting any API function (i.e.
+** returning control to the user) that has called sqlite3_malloc or
+** sqlite3_realloc.
+**
+** The returned value is normally a copy of the second argument to this
+** function. However, if a malloc() failure has occured since the previous
+** invocation SQLITE_NOMEM is returned instead.
+**
+** If the first argument, db, is not NULL and a malloc() error has occured,
+** then the connection error-code (the value returned by sqlite3_errcode())
+** is set to SQLITE_NOMEM.
+*/
+SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){
+ /* If the db handle is not NULL, then we must hold the connection handle
+ ** mutex here. Otherwise the read (and possible write) of db->mallocFailed
+ ** is unsafe, as is the call to sqlite3Error().
+ */
+ assert( !db || sqlite3_mutex_held(db->mutex) );
+ if( db && db->mallocFailed ){
+ sqlite3Error(db, SQLITE_NOMEM, 0);
+ db->mallocFailed = 0;
+ rc = SQLITE_NOMEM;
+ }
+ return rc & (db ? db->errMask : 0xff);
+}
+
+/************** End of malloc.c **********************************************/
+/************** Begin file printf.c ******************************************/
+/*
+** The "printf" code that follows dates from the 1980's. It is in
+** the public domain. The original comments are included here for
+** completeness. They are very out-of-date but might be useful as
+** an historical reference. Most of the "enhancements" have been backed
+** out so that the functionality is now the same as standard printf().
+**
+**************************************************************************
+**
+** The following modules is an enhanced replacement for the "printf" subroutines
+** found in the standard C library. The following enhancements are
+** supported:
+**
+** + Additional functions. The standard set of "printf" functions
+** includes printf, fprintf, sprintf, vprintf, vfprintf, and
+** vsprintf. This module adds the following:
+**
+** * snprintf -- Works like sprintf, but has an extra argument
+** which is the size of the buffer written to.
+**
+** * mprintf -- Similar to sprintf. Writes output to memory
+** obtained from malloc.
+**
+** * xprintf -- Calls a function to dispose of output.
+**
+** * nprintf -- No output, but returns the number of characters
+** that would have been output by printf.
+**
+** * A v- version (ex: vsnprintf) of every function is also
+** supplied.
+**
+** + A few extensions to the formatting notation are supported:
+**
+** * The "=" flag (similar to "-") causes the output to be
+** be centered in the appropriately sized field.
+**
+** * The %b field outputs an integer in binary notation.
+**
+** * The %c field now accepts a precision. The character output
+** is repeated by the number of times the precision specifies.
+**
+** * The %' field works like %c, but takes as its character the
+** next character of the format string, instead of the next
+** argument. For example, printf("%.78'-") prints 78 minus
+** signs, the same as printf("%.78c",'-').
+**
+** + When compiled using GCC on a SPARC, this version of printf is
+** faster than the library printf for SUN OS 4.1.
+**
+** + All functions are fully reentrant.
+**
+*/
+
+/*
+** Conversion types fall into various categories as defined by the
+** following enumeration.
+*/
+#define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */
+#define etFLOAT 2 /* Floating point. %f */
+#define etEXP 3 /* Exponentional notation. %e and %E */
+#define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */
+#define etSIZE 5 /* Return number of characters processed so far. %n */
+#define etSTRING 6 /* Strings. %s */
+#define etDYNSTRING 7 /* Dynamically allocated strings. %z */
+#define etPERCENT 8 /* Percent symbol. %% */
+#define etCHARX 9 /* Characters. %c */
+/* The rest are extensions, not normally found in printf() */
+#define etCHARLIT 10 /* Literal characters. %' */
+#define etSQLESCAPE 11 /* Strings with '\'' doubled. %q */
+#define etSQLESCAPE2 12 /* Strings with '\'' doubled and enclosed in '',
+ NULL pointers replaced by SQL NULL. %Q */
+#define etTOKEN 13 /* a pointer to a Token structure */
+#define etSRCLIST 14 /* a pointer to a SrcList */
+#define etPOINTER 15 /* The %p conversion */
+#define etSQLESCAPE3 16 /* %w -> Strings with '\"' doubled */
+#define etORDINAL 17 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */
+
+
+/*
+** An "etByte" is an 8-bit unsigned value.
+*/
+typedef unsigned char etByte;
+
+/*
+** Each builtin conversion character (ex: the 'd' in "%d") is described
+** by an instance of the following structure
+*/
+typedef struct et_info { /* Information about each format field */
+ char fmttype; /* The format field code letter */
+ etByte base; /* The base for radix conversion */
+ etByte flags; /* One or more of FLAG_ constants below */
+ etByte type; /* Conversion paradigm */
+ etByte charset; /* Offset into aDigits[] of the digits string */
+ etByte prefix; /* Offset into aPrefix[] of the prefix string */
+} et_info;
+
+/*
+** Allowed values for et_info.flags
+*/
+#define FLAG_SIGNED 1 /* True if the value to convert is signed */
+#define FLAG_INTERN 2 /* True if for internal use only */
+#define FLAG_STRING 4 /* Allow infinity precision */
+
+
+/*
+** The following table is searched linearly, so it is good to put the
+** most frequently used conversion types first.
+*/
+static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
+static const char aPrefix[] = "-x0\000X0";
+static const et_info fmtinfo[] = {
+ { 'd', 10, 1, etRADIX, 0, 0 },
+ { 's', 0, 4, etSTRING, 0, 0 },
+ { 'g', 0, 1, etGENERIC, 30, 0 },
+ { 'z', 0, 4, etDYNSTRING, 0, 0 },
+ { 'q', 0, 4, etSQLESCAPE, 0, 0 },
+ { 'Q', 0, 4, etSQLESCAPE2, 0, 0 },
+ { 'w', 0, 4, etSQLESCAPE3, 0, 0 },
+ { 'c', 0, 0, etCHARX, 0, 0 },
+ { 'o', 8, 0, etRADIX, 0, 2 },
+ { 'u', 10, 0, etRADIX, 0, 0 },
+ { 'x', 16, 0, etRADIX, 16, 1 },
+ { 'X', 16, 0, etRADIX, 0, 4 },
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ { 'f', 0, 1, etFLOAT, 0, 0 },
+ { 'e', 0, 1, etEXP, 30, 0 },
+ { 'E', 0, 1, etEXP, 14, 0 },
+ { 'G', 0, 1, etGENERIC, 14, 0 },
+#endif
+ { 'i', 10, 1, etRADIX, 0, 0 },
+ { 'n', 0, 0, etSIZE, 0, 0 },
+ { '%', 0, 0, etPERCENT, 0, 0 },
+ { 'p', 16, 0, etPOINTER, 0, 1 },
+ { 'T', 0, 2, etTOKEN, 0, 0 },
+ { 'S', 0, 2, etSRCLIST, 0, 0 },
+ { 'r', 10, 3, etORDINAL, 0, 0 },
+};
+#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0]))
+
+/*
+** If SQLITE_OMIT_FLOATING_POINT is defined, then none of the floating point
+** conversions will work.
+*/
+#ifndef SQLITE_OMIT_FLOATING_POINT
+/*
+** "*val" is a double such that 0.1 <= *val < 10.0
+** Return the ascii code for the leading digit of *val, then
+** multiply "*val" by 10.0 to renormalize.
+**
+** Example:
+** input: *val = 3.14159
+** output: *val = 1.4159 function return = '3'
+**
+** The counter *cnt is incremented each time. After counter exceeds
+** 16 (the number of significant digits in a 64-bit float) '0' is
+** always returned.
+*/
+static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){
+ int digit;
+ LONGDOUBLE_TYPE d;
+ if( (*cnt)++ >= 16 ) return '0';
+ digit = (int)*val;
+ d = digit;
+ digit += '0';
+ *val = (*val - d)*10.0;
+ return digit;
+}
+#endif /* SQLITE_OMIT_FLOATING_POINT */
+
+/*
+** Append N space characters to the given string buffer.
+*/
+static void appendSpace(StrAccum *pAccum, int N){
+ static const char zSpaces[] = " ";
+ while( N>=sizeof(zSpaces)-1 ){
+ sqlite3StrAccumAppend(pAccum, zSpaces, sizeof(zSpaces)-1);
+ N -= sizeof(zSpaces)-1;
+ }
+ if( N>0 ){
+ sqlite3StrAccumAppend(pAccum, zSpaces, N);
+ }
+}
+
+/*
+** On machines with a small stack size, you can redefine the
+** SQLITE_PRINT_BUF_SIZE to be less than 350. But beware - for
+** smaller values some %f conversions may go into an infinite loop.
+*/
+#ifndef SQLITE_PRINT_BUF_SIZE
+# define SQLITE_PRINT_BUF_SIZE 350
+#endif
+#define etBUFSIZE SQLITE_PRINT_BUF_SIZE /* Size of the output buffer */
+
+/*
+** The root program. All variations call this core.
+**
+** INPUTS:
+** func This is a pointer to a function taking three arguments
+** 1. A pointer to anything. Same as the "arg" parameter.
+** 2. A pointer to the list of characters to be output
+** (Note, this list is NOT null terminated.)
+** 3. An integer number of characters to be output.
+** (Note: This number might be zero.)
+**
+** arg This is the pointer to anything which will be passed as the
+** first argument to "func". Use it for whatever you like.
+**
+** fmt This is the format string, as in the usual print.
+**
+** ap This is a pointer to a list of arguments. Same as in
+** vfprint.
+**
+** OUTPUTS:
+** The return value is the total number of characters sent to
+** the function "func". Returns -1 on a error.
+**
+** Note that the order in which automatic variables are declared below
+** seems to make a big difference in determining how fast this beast
+** will run.
+*/
+static void vxprintf(
+ StrAccum *pAccum, /* Accumulate results here */
+ int useExtended, /* Allow extended %-conversions */
+ const char *fmt, /* Format string */
+ va_list ap /* arguments */
+){
+ int c; /* Next character in the format string */
+ char *bufpt; /* Pointer to the conversion buffer */
+ int precision; /* Precision of the current field */
+ int length; /* Length of the field */
+ int idx; /* A general purpose loop counter */
+ int width; /* Width of the current field */
+ etByte flag_leftjustify; /* True if "-" flag is present */
+ etByte flag_plussign; /* True if "+" flag is present */
+ etByte flag_blanksign; /* True if " " flag is present */
+ etByte flag_alternateform; /* True if "#" flag is present */
+ etByte flag_altform2; /* True if "!" flag is present */
+ etByte flag_zeropad; /* True if field width constant starts with zero */
+ etByte flag_long; /* True if "l" flag is present */
+ etByte flag_longlong; /* True if the "ll" flag is present */
+ etByte done; /* Loop termination flag */
+ sqlite_uint64 longvalue; /* Value for integer types */
+ LONGDOUBLE_TYPE realvalue; /* Value for real types */
+ const et_info *infop; /* Pointer to the appropriate info structure */
+ char buf[etBUFSIZE]; /* Conversion buffer */
+ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
+ etByte errorflag = 0; /* True if an error is encountered */
+ etByte xtype; /* Conversion paradigm */
+ char *zExtra; /* Extra memory used for etTCLESCAPE conversions */
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ int exp, e2; /* exponent of real numbers */
+ double rounder; /* Used for rounding floating point values */
+ etByte flag_dp; /* True if decimal point should be shown */
+ etByte flag_rtz; /* True if trailing zeros should be removed */
+ etByte flag_exp; /* True to force display of the exponent */
+ int nsd; /* Number of significant digits returned */
+#endif
+
+ length = 0;
+ bufpt = 0;
+ for(; (c=(*fmt))!=0; ++fmt){
+ if( c!='%' ){
+ int amt;
+ bufpt = (char *)fmt;
+ amt = 1;
+ while( (c=(*++fmt))!='%' && c!=0 ) amt++;
+ sqlite3StrAccumAppend(pAccum, bufpt, amt);
+ if( c==0 ) break;
+ }
+ if( (c=(*++fmt))==0 ){
+ errorflag = 1;
+ sqlite3StrAccumAppend(pAccum, "%", 1);
+ break;
+ }
+ /* Find out what flags are present */
+ flag_leftjustify = flag_plussign = flag_blanksign =
+ flag_alternateform = flag_altform2 = flag_zeropad = 0;
+ done = 0;
+ do{
+ switch( c ){
+ case '-': flag_leftjustify = 1; break;
+ case '+': flag_plussign = 1; break;
+ case ' ': flag_blanksign = 1; break;
+ case '#': flag_alternateform = 1; break;
+ case '!': flag_altform2 = 1; break;
+ case '0': flag_zeropad = 1; break;
+ default: done = 1; break;
+ }
+ }while( !done && (c=(*++fmt))!=0 );
+ /* Get the field width */
+ width = 0;
+ if( c=='*' ){
+ width = va_arg(ap,int);
+ if( width<0 ){
+ flag_leftjustify = 1;
+ width = -width;
+ }
+ c = *++fmt;
+ }else{
+ while( c>='0' && c<='9' ){
+ width = width*10 + c - '0';
+ c = *++fmt;
+ }
+ }
+ if( width > etBUFSIZE-10 ){
+ width = etBUFSIZE-10;
+ }
+ /* Get the precision */
+ if( c=='.' ){
+ precision = 0;
+ c = *++fmt;
+ if( c=='*' ){
+ precision = va_arg(ap,int);
+ if( precision<0 ) precision = -precision;
+ c = *++fmt;
+ }else{
+ while( c>='0' && c<='9' ){
+ precision = precision*10 + c - '0';
+ c = *++fmt;
+ }
+ }
+ }else{
+ precision = -1;
+ }
+ /* Get the conversion type modifier */
+ if( c=='l' ){
+ flag_long = 1;
+ c = *++fmt;
+ if( c=='l' ){
+ flag_longlong = 1;
+ c = *++fmt;
+ }else{
+ flag_longlong = 0;
+ }
+ }else{
+ flag_long = flag_longlong = 0;
+ }
+ /* Fetch the info entry for the field */
+ infop = 0;
+ for(idx=0; idx<etNINFO; idx++){
+ if( c==fmtinfo[idx].fmttype ){
+ infop = &fmtinfo[idx];
+ if( useExtended || (infop->flags & FLAG_INTERN)==0 ){
+ xtype = infop->type;
+ }else{
+ return;
+ }
+ break;
+ }
+ }
+ zExtra = 0;
+ if( infop==0 ){
+ return;
+ }
+
+
+ /* Limit the precision to prevent overflowing buf[] during conversion */
+ if( precision>etBUFSIZE-40 && (infop->flags & FLAG_STRING)==0 ){
+ precision = etBUFSIZE-40;
+ }
+
+ /*
+ ** At this point, variables are initialized as follows:
+ **
+ ** flag_alternateform TRUE if a '#' is present.
+ ** flag_altform2 TRUE if a '!' is present.
+ ** flag_plussign TRUE if a '+' is present.
+ ** flag_leftjustify TRUE if a '-' is present or if the
+ ** field width was negative.
+ ** flag_zeropad TRUE if the width began with 0.
+ ** flag_long TRUE if the letter 'l' (ell) prefixed
+ ** the conversion character.
+ ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed
+ ** the conversion character.
+ ** flag_blanksign TRUE if a ' ' is present.
+ ** width The specified field width. This is
+ ** always non-negative. Zero is the default.
+ ** precision The specified precision. The default
+ ** is -1.
+ ** xtype The class of the conversion.
+ ** infop Pointer to the appropriate info struct.
+ */
+ switch( xtype ){
+ case etPOINTER:
+ flag_longlong = sizeof(char*)==sizeof(i64);
+ flag_long = sizeof(char*)==sizeof(long int);
+ /* Fall through into the next case */
+ case etORDINAL:
+ case etRADIX:
+ if( infop->flags & FLAG_SIGNED ){
+ i64 v;
+ if( flag_longlong ) v = va_arg(ap,i64);
+ else if( flag_long ) v = va_arg(ap,long int);
+ else v = va_arg(ap,int);
+ if( v<0 ){
+ longvalue = -v;
+ prefix = '-';
+ }else{
+ longvalue = v;
+ if( flag_plussign ) prefix = '+';
+ else if( flag_blanksign ) prefix = ' ';
+ else prefix = 0;
+ }
+ }else{
+ if( flag_longlong ) longvalue = va_arg(ap,u64);
+ else if( flag_long ) longvalue = va_arg(ap,unsigned long int);
+ else longvalue = va_arg(ap,unsigned int);
+ prefix = 0;
+ }
+ if( longvalue==0 ) flag_alternateform = 0;
+ if( flag_zeropad && precision<width-(prefix!=0) ){
+ precision = width-(prefix!=0);
+ }
+ bufpt = &buf[etBUFSIZE-1];
+ if( xtype==etORDINAL ){
+ static const char zOrd[] = "thstndrd";
+ int x = longvalue % 10;
+ if( x>=4 || (longvalue/10)%10==1 ){
+ x = 0;
+ }
+ buf[etBUFSIZE-3] = zOrd[x*2];
+ buf[etBUFSIZE-2] = zOrd[x*2+1];
+ bufpt -= 2;
+ }
+ {
+ const char *cset; /* Use registers for speed */
+ int base;
+ cset = &aDigits[infop->charset];
+ base = infop->base;
+ do{ /* Convert to ascii */
+ *(--bufpt) = cset[longvalue%base];
+ longvalue = longvalue/base;
+ }while( longvalue>0 );
+ }
+ length = &buf[etBUFSIZE-1]-bufpt;
+ for(idx=precision-length; idx>0; idx--){
+ *(--bufpt) = '0'; /* Zero pad */
+ }
+ if( prefix ) *(--bufpt) = prefix; /* Add sign */
+ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */
+ const char *pre;
+ char x;
+ pre = &aPrefix[infop->prefix];
+ if( *bufpt!=pre[0] ){
+ for(; (x=(*pre))!=0; pre++) *(--bufpt) = x;
+ }
+ }
+ length = &buf[etBUFSIZE-1]-bufpt;
+ break;
+ case etFLOAT:
+ case etEXP:
+ case etGENERIC:
+ realvalue = va_arg(ap,double);
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ if( precision<0 ) precision = 6; /* Set default precision */
+ if( precision>etBUFSIZE/2-10 ) precision = etBUFSIZE/2-10;
+ if( realvalue<0.0 ){
+ realvalue = -realvalue;
+ prefix = '-';
+ }else{
+ if( flag_plussign ) prefix = '+';
+ else if( flag_blanksign ) prefix = ' ';
+ else prefix = 0;
+ }
+ if( xtype==etGENERIC && precision>0 ) precision--;
+#if 0
+ /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */
+ for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1);
+#else
+ /* It makes more sense to use 0.5 */
+ for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1){}
+#endif
+ if( xtype==etFLOAT ) realvalue += rounder;
+ /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
+ exp = 0;
+ if( sqlite3IsNaN(realvalue) ){
+ bufpt = "NaN";
+ length = 3;
+ break;
+ }
+ if( realvalue>0.0 ){
+ while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; }
+ while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
+ while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
+ while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; }
+ while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; }
+ if( exp>350 || exp<-350 ){
+ if( prefix=='-' ){
+ bufpt = "-Inf";
+ }else if( prefix=='+' ){
+ bufpt = "+Inf";
+ }else{
+ bufpt = "Inf";
+ }
+ length = strlen(bufpt);
+ break;
+ }
+ }
+ bufpt = buf;
+ /*
+ ** If the field type is etGENERIC, then convert to either etEXP
+ ** or etFLOAT, as appropriate.
+ */
+ flag_exp = xtype==etEXP;
+ if( xtype!=etFLOAT ){
+ realvalue += rounder;
+ if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
+ }
+ if( xtype==etGENERIC ){
+ flag_rtz = !flag_alternateform;
+ if( exp<-4 || exp>precision ){
+ xtype = etEXP;
+ }else{
+ precision = precision - exp;
+ xtype = etFLOAT;
+ }
+ }else{
+ flag_rtz = 0;
+ }
+ if( xtype==etEXP ){
+ e2 = 0;
+ }else{
+ e2 = exp;
+ }
+ nsd = 0;
+ flag_dp = (precision>0) | flag_alternateform | flag_altform2;
+ /* The sign in front of the number */
+ if( prefix ){
+ *(bufpt++) = prefix;
+ }
+ /* Digits prior to the decimal point */
+ if( e2<0 ){
+ *(bufpt++) = '0';
+ }else{
+ for(; e2>=0; e2--){
+ *(bufpt++) = et_getdigit(&realvalue,&nsd);
+ }
+ }
+ /* The decimal point */
+ if( flag_dp ){
+ *(bufpt++) = '.';
+ }
+ /* "0" digits after the decimal point but before the first
+ ** significant digit of the number */
+ for(e2++; e2<0 && precision>0; precision--, e2++){
+ *(bufpt++) = '0';
+ }
+ /* Significant digits after the decimal point */
+ while( (precision--)>0 ){
+ *(bufpt++) = et_getdigit(&realvalue,&nsd);
+ }
+ /* Remove trailing zeros and the "." if no digits follow the "." */
+ if( flag_rtz && flag_dp ){
+ while( bufpt[-1]=='0' ) *(--bufpt) = 0;
+ assert( bufpt>buf );
+ if( bufpt[-1]=='.' ){
+ if( flag_altform2 ){
+ *(bufpt++) = '0';
+ }else{
+ *(--bufpt) = 0;
+ }
+ }
+ }
+ /* Add the "eNNN" suffix */
+ if( flag_exp || (xtype==etEXP && exp) ){
+ *(bufpt++) = aDigits[infop->charset];
+ if( exp<0 ){
+ *(bufpt++) = '-'; exp = -exp;
+ }else{
+ *(bufpt++) = '+';
+ }
+ if( exp>=100 ){
+ *(bufpt++) = (exp/100)+'0'; /* 100's digit */
+ exp %= 100;
+ }
+ *(bufpt++) = exp/10+'0'; /* 10's digit */
+ *(bufpt++) = exp%10+'0'; /* 1's digit */
+ }
+ *bufpt = 0;
+
+ /* The converted number is in buf[] and zero terminated. Output it.
+ ** Note that the number is in the usual order, not reversed as with
+ ** integer conversions. */
+ length = bufpt-buf;
+ bufpt = buf;
+
+ /* Special case: Add leading zeros if the flag_zeropad flag is
+ ** set and we are not left justified */
+ if( flag_zeropad && !flag_leftjustify && length < width){
+ int i;
+ int nPad = width - length;
+ for(i=width; i>=nPad; i--){
+ bufpt[i] = bufpt[i-nPad];
+ }
+ i = prefix!=0;
+ while( nPad-- ) bufpt[i++] = '0';
+ length = width;
+ }
+#endif
+ break;
+ case etSIZE:
+ *(va_arg(ap,int*)) = pAccum->nChar;
+ length = width = 0;
+ break;
+ case etPERCENT:
+ buf[0] = '%';
+ bufpt = buf;
+ length = 1;
+ break;
+ case etCHARLIT:
+ case etCHARX:
+ c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
+ if( precision>=0 ){
+ for(idx=1; idx<precision; idx++) buf[idx] = c;
+ length = precision;
+ }else{
+ length =1;
+ }
+ bufpt = buf;
+ break;
+ case etSTRING:
+ case etDYNSTRING:
+ bufpt = va_arg(ap,char*);
+ if( bufpt==0 ){
+ bufpt = "";
+ }else if( xtype==etDYNSTRING ){
+ zExtra = bufpt;
+ }
+ if( precision>=0 ){
+ for(length=0; length<precision && bufpt[length]; length++){}
+ }else{
+ length = strlen(bufpt);
+ }
+ break;
+ case etSQLESCAPE:
+ case etSQLESCAPE2:
+ case etSQLESCAPE3: {
+ int i, j, n, ch, isnull;
+ int needQuote;
+ char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */
+ char *escarg = va_arg(ap,char*);
+ isnull = escarg==0;
+ if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
+ for(i=n=0; (ch=escarg[i])!=0; i++){
+ if( ch==q ) n++;
+ }
+ needQuote = !isnull && xtype==etSQLESCAPE2;
+ n += i + 1 + needQuote*2;
+ if( n>etBUFSIZE ){
+ bufpt = zExtra = sqlite3_malloc( n );
+ if( bufpt==0 ) return;
+ }else{
+ bufpt = buf;
+ }
+ j = 0;
+ if( needQuote ) bufpt[j++] = q;
+ for(i=0; (ch=escarg[i])!=0; i++){
+ bufpt[j++] = ch;
+ if( ch==q ) bufpt[j++] = ch;
+ }
+ if( needQuote ) bufpt[j++] = q;
+ bufpt[j] = 0;
+ length = j;
+ /* The precision is ignored on %q and %Q */
+ /* if( precision>=0 && precision<length ) length = precision; */
+ break;
+ }
+ case etTOKEN: {
+ Token *pToken = va_arg(ap, Token*);
+ if( pToken && pToken->z ){
+ sqlite3StrAccumAppend(pAccum, (const char*)pToken->z, pToken->n);
+ }
+ length = width = 0;
+ break;
+ }
+ case etSRCLIST: {
+ SrcList *pSrc = va_arg(ap, SrcList*);
+ int k = va_arg(ap, int);
+ struct SrcList_item *pItem = &pSrc->a[k];
+ assert( k>=0 && k<pSrc->nSrc );
+ if( pItem->zDatabase && pItem->zDatabase[0] ){
+ sqlite3StrAccumAppend(pAccum, pItem->zDatabase, -1);
+ sqlite3StrAccumAppend(pAccum, ".", 1);
+ }
+ sqlite3StrAccumAppend(pAccum, pItem->zName, -1);
+ length = width = 0;
+ break;
+ }
+ }/* End switch over the format type */
+ /*
+ ** The text of the conversion is pointed to by "bufpt" and is
+ ** "length" characters long. The field width is "width". Do
+ ** the output.
+ */
+ if( !flag_leftjustify ){
+ int nspace;
+ nspace = width-length;
+ if( nspace>0 ){
+ appendSpace(pAccum, nspace);
+ }
+ }
+ if( length>0 ){
+ sqlite3StrAccumAppend(pAccum, bufpt, length);
+ }
+ if( flag_leftjustify ){
+ int nspace;
+ nspace = width-length;
+ if( nspace>0 ){
+ appendSpace(pAccum, nspace);
+ }
+ }
+ if( zExtra ){
+ sqlite3_free(zExtra);
+ }
+ }/* End for loop over the format string */
+} /* End of function */
+
+/*
+** Append N bytes of text from z to the StrAccum object.
+*/
+SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){
+ if( p->tooBig | p->mallocFailed ){
+ return;
+ }
+ if( N<0 ){
+ N = strlen(z);
+ }
+ if( N==0 ){
+ return;
+ }
+ if( p->nChar+N >= p->nAlloc ){
+ char *zNew;
+ if( !p->useMalloc ){
+ p->tooBig = 1;
+ N = p->nAlloc - p->nChar - 1;
+ if( N<=0 ){
+ return;
+ }
+ }else{
+ i64 szNew = p->nAlloc;
+ szNew += N + 1;
+ if( szNew > p->mxAlloc ){
+ p->nAlloc = p->mxAlloc;
+ if( ((i64)p->nChar)+((i64)N) >= p->nAlloc ){
+ sqlite3StrAccumReset(p);
+ p->tooBig = 1;
+ return;
+ }
+ }else{
+ p->nAlloc = szNew;
+ }
+ zNew = sqlite3_malloc( p->nAlloc );
+ if( zNew ){
+ memcpy(zNew, p->zText, p->nChar);
+ sqlite3StrAccumReset(p);
+ p->zText = zNew;
+ }else{
+ p->mallocFailed = 1;
+ sqlite3StrAccumReset(p);
+ return;
+ }
+ }
+ }
+ memcpy(&p->zText[p->nChar], z, N);
+ p->nChar += N;
+}
+
+/*
+** Finish off a string by making sure it is zero-terminated.
+** Return a pointer to the resulting string. Return a NULL
+** pointer if any kind of error was encountered.
+*/
+SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){
+ if( p->zText ){
+ p->zText[p->nChar] = 0;
+ if( p->useMalloc && p->zText==p->zBase ){
+ p->zText = sqlite3_malloc( p->nChar+1 );
+ if( p->zText ){
+ memcpy(p->zText, p->zBase, p->nChar+1);
+ }else{
+ p->mallocFailed = 1;
+ }
+ }
+ }
+ return p->zText;
+}
+
+/*
+** Reset an StrAccum string. Reclaim all malloced memory.
+*/
+SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum *p){
+ if( p->zText!=p->zBase ){
+ sqlite3_free(p->zText);
+ p->zText = 0;
+ }
+}
+
+/*
+** Initialize a string accumulator
+*/
+static void sqlite3StrAccumInit(StrAccum *p, char *zBase, int n, int mx){
+ p->zText = p->zBase = zBase;
+ p->nChar = 0;
+ p->nAlloc = n;
+ p->mxAlloc = mx;
+ p->useMalloc = 1;
+ p->tooBig = 0;
+ p->mallocFailed = 0;
+}
+
+/*
+** Print into memory obtained from sqliteMalloc(). Use the internal
+** %-conversion extensions.
+*/
+SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list ap){
+ char *z;
+ char zBase[SQLITE_PRINT_BUF_SIZE];
+ StrAccum acc;
+ sqlite3StrAccumInit(&acc, zBase, sizeof(zBase),
+ db ? db->aLimit[SQLITE_LIMIT_LENGTH] : SQLITE_MAX_LENGTH);
+ vxprintf(&acc, 1, zFormat, ap);
+ z = sqlite3StrAccumFinish(&acc);
+ if( acc.mallocFailed && db ){
+ db->mallocFailed = 1;
+ }
+ return z;
+}
+
+/*
+** Print into memory obtained from sqliteMalloc(). Use the internal
+** %-conversion extensions.
+*/
+SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3 *db, const char *zFormat, ...){
+ va_list ap;
+ char *z;
+ va_start(ap, zFormat);
+ z = sqlite3VMPrintf(db, zFormat, ap);
+ va_end(ap);
+ return z;
+}
+
+/*
+** Print into memory obtained from sqlite3_malloc(). Omit the internal
+** %-conversion extensions.
+*/
+SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){
+ char *z;
+ char zBase[SQLITE_PRINT_BUF_SIZE];
+ StrAccum acc;
+ sqlite3StrAccumInit(&acc, zBase, sizeof(zBase), SQLITE_MAX_LENGTH);
+ vxprintf(&acc, 0, zFormat, ap);
+ z = sqlite3StrAccumFinish(&acc);
+ return z;
+}
+
+/*
+** Print into memory obtained from sqlite3_malloc()(). Omit the internal
+** %-conversion extensions.
+*/
+SQLITE_API char *sqlite3_mprintf(const char *zFormat, ...){
+ va_list ap;
+ char *z;
+ va_start(ap, zFormat);
+ z = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+ return z;
+}
+
+/*
+** sqlite3_snprintf() works like snprintf() except that it ignores the
+** current locale settings. This is important for SQLite because we
+** are not able to use a "," as the decimal point in place of "." as
+** specified by some locales.
+*/
+SQLITE_API char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){
+ char *z;
+ va_list ap;
+ StrAccum acc;
+
+ if( n<=0 ){
+ return zBuf;
+ }
+ sqlite3StrAccumInit(&acc, zBuf, n, 0);
+ acc.useMalloc = 0;
+ va_start(ap,zFormat);
+ vxprintf(&acc, 0, zFormat, ap);
+ va_end(ap);
+ z = sqlite3StrAccumFinish(&acc);
+ return z;
+}
+
+#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) || defined(SQLITE_MEMDEBUG)
+/*
+** A version of printf() that understands %lld. Used for debugging.
+** The printf() built into some versions of windows does not understand %lld
+** and segfaults if you give it a long long int.
+*/
+SQLITE_PRIVATE void sqlite3DebugPrintf(const char *zFormat, ...){
+ va_list ap;
+ StrAccum acc;
+ char zBuf[500];
+ sqlite3StrAccumInit(&acc, zBuf, sizeof(zBuf), 0);
+ acc.useMalloc = 0;
+ va_start(ap,zFormat);
+ vxprintf(&acc, 0, zFormat, ap);
+ va_end(ap);
+ sqlite3StrAccumFinish(&acc);
+ fprintf(stdout,"%s", zBuf);
+ fflush(stdout);
+}
+#endif
+
+/************** End of printf.c **********************************************/
+/************** Begin file random.c ******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code to implement a pseudo-random number
+** generator (PRNG) for SQLite.
+**
+** Random numbers are used by some of the database backends in order
+** to generate random integer keys for tables or random filenames.
+**
+** $Id: random.c,v 1.23 2008/03/21 16:45:47 drh Exp $
+*/
+
+
+/* All threads share a single random number generator.
+** This structure is the current state of the generator.
+*/
+static struct sqlite3PrngType {
+ unsigned char isInit; /* True if initialized */
+ unsigned char i, j; /* State variables */
+ unsigned char s[256]; /* State variables */
+} sqlite3Prng;
+
+/*
+** Get a single 8-bit random value from the RC4 PRNG. The Mutex
+** must be held while executing this routine.
+**
+** Why not just use a library random generator like lrand48() for this?
+** Because the OP_NewRowid opcode in the VDBE depends on having a very
+** good source of random numbers. The lrand48() library function may
+** well be good enough. But maybe not. Or maybe lrand48() has some
+** subtle problems on some systems that could cause problems. It is hard
+** to know. To minimize the risk of problems due to bad lrand48()
+** implementations, SQLite uses this random number generator based
+** on RC4, which we know works very well.
+**
+** (Later): Actually, OP_NewRowid does not depend on a good source of
+** randomness any more. But we will leave this code in all the same.
+*/
+static int randomByte(void){
+ unsigned char t;
+
+
+ /* Initialize the state of the random number generator once,
+ ** the first time this routine is called. The seed value does
+ ** not need to contain a lot of randomness since we are not
+ ** trying to do secure encryption or anything like that...
+ **
+ ** Nothing in this file or anywhere else in SQLite does any kind of
+ ** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
+ ** number generator) not as an encryption device.
+ */
+ if( !sqlite3Prng.isInit ){
+ int i;
+ char k[256];
+ sqlite3Prng.j = 0;
+ sqlite3Prng.i = 0;
+ sqlite3OsRandomness(sqlite3_vfs_find(0), 256, k);
+ for(i=0; i<256; i++){
+ sqlite3Prng.s[i] = i;
+ }
+ for(i=0; i<256; i++){
+ sqlite3Prng.j += sqlite3Prng.s[i] + k[i];
+ t = sqlite3Prng.s[sqlite3Prng.j];
+ sqlite3Prng.s[sqlite3Prng.j] = sqlite3Prng.s[i];
+ sqlite3Prng.s[i] = t;
+ }
+ sqlite3Prng.isInit = 1;
+ }
+
+ /* Generate and return single random byte
+ */
+ sqlite3Prng.i++;
+ t = sqlite3Prng.s[sqlite3Prng.i];
+ sqlite3Prng.j += t;
+ sqlite3Prng.s[sqlite3Prng.i] = sqlite3Prng.s[sqlite3Prng.j];
+ sqlite3Prng.s[sqlite3Prng.j] = t;
+ t += sqlite3Prng.s[sqlite3Prng.i];
+ return sqlite3Prng.s[t];
+}
+
+/*
+** Return N random bytes.
+*/
+SQLITE_API void sqlite3_randomness(int N, void *pBuf){
+ unsigned char *zBuf = pBuf;
+ static sqlite3_mutex *mutex = 0;
+ if( mutex==0 ){
+ mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_PRNG);
+ }
+ sqlite3_mutex_enter(mutex);
+ while( N-- ){
+ *(zBuf++) = randomByte();
+ }
+ sqlite3_mutex_leave(mutex);
+}
+
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+/*
+** For testing purposes, we sometimes want to preserve the state of
+** PRNG and restore the PRNG to its saved state at a later time.
+** The sqlite3_test_control() interface calls these routines to
+** control the PRNG.
+*/
+static struct sqlite3PrngType sqlite3SavedPrng;
+SQLITE_PRIVATE void sqlite3PrngSaveState(void){
+ memcpy(&sqlite3SavedPrng, &sqlite3Prng, sizeof(sqlite3Prng));
+}
+SQLITE_PRIVATE void sqlite3PrngRestoreState(void){
+ memcpy(&sqlite3Prng, &sqlite3SavedPrng, sizeof(sqlite3Prng));
+}
+SQLITE_PRIVATE void sqlite3PrngResetState(void){
+ sqlite3Prng.isInit = 0;
+}
+#endif /* SQLITE_OMIT_BUILTIN_TEST */
+
+/************** End of random.c **********************************************/
+/************** Begin file utf.c *********************************************/
+/*
+** 2004 April 13
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains routines used to translate between UTF-8,
+** UTF-16, UTF-16BE, and UTF-16LE.
+**
+** $Id: utf.c,v 1.61 2008/03/28 15:44:10 danielk1977 Exp $
+**
+** Notes on UTF-8:
+**
+** Byte-0 Byte-1 Byte-2 Byte-3 Value
+** 0xxxxxxx 00000000 00000000 0xxxxxxx
+** 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx
+** 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx
+** 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx
+**
+**
+** Notes on UTF-16: (with wwww+1==uuuuu)
+**
+** Word-0 Word-1 Value
+** 110110ww wwzzzzyy 110111yy yyxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
+** zzzzyyyy yyxxxxxx 00000000 zzzzyyyy yyxxxxxx
+**
+**
+** BOM or Byte Order Mark:
+** 0xff 0xfe little-endian utf-16 follows
+** 0xfe 0xff big-endian utf-16 follows
+**
+*/
+/************** Include vdbeInt.h in the middle of utf.c *********************/
+/************** Begin file vdbeInt.h *****************************************/
+/*
+** 2003 September 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the header file for information that is private to the
+** VDBE. This information used to all be at the top of the single
+** source code file "vdbe.c". When that file became too big (over
+** 6000 lines long) it was split up into several smaller files and
+** this header information was factored out.
+*/
+#ifndef _VDBEINT_H_
+#define _VDBEINT_H_
+
+/*
+** intToKey() and keyToInt() used to transform the rowid. But with
+** the latest versions of the design they are no-ops.
+*/
+#define keyToInt(X) (X)
+#define intToKey(X) (X)
+
+
+/*
+** SQL is translated into a sequence of instructions to be
+** executed by a virtual machine. Each instruction is an instance
+** of the following structure.
+*/
+typedef struct VdbeOp Op;
+
+/*
+** Boolean values
+*/
+typedef unsigned char Bool;
+
+/*
+** A cursor is a pointer into a single BTree within a database file.
+** The cursor can seek to a BTree entry with a particular key, or
+** loop over all entries of the Btree. You can also insert new BTree
+** entries or retrieve the key or data from the entry that the cursor
+** is currently pointing to.
+**
+** Every cursor that the virtual machine has open is represented by an
+** instance of the following structure.
+**
+** If the Cursor.isTriggerRow flag is set it means that this cursor is
+** really a single row that represents the NEW or OLD pseudo-table of
+** a row trigger. The data for the row is stored in Cursor.pData and
+** the rowid is in Cursor.iKey.
+*/
+struct Cursor {
+ BtCursor *pCursor; /* The cursor structure of the backend */
+ int iDb; /* Index of cursor database in db->aDb[] (or -1) */
+ i64 lastRowid; /* Last rowid from a Next or NextIdx operation */
+ i64 nextRowid; /* Next rowid returned by OP_NewRowid */
+ Bool zeroed; /* True if zeroed out and ready for reuse */
+ Bool rowidIsValid; /* True if lastRowid is valid */
+ Bool atFirst; /* True if pointing to first entry */
+ Bool useRandomRowid; /* Generate new record numbers semi-randomly */
+ Bool nullRow; /* True if pointing to a row with no data */
+ Bool nextRowidValid; /* True if the nextRowid field is valid */
+ Bool pseudoTable; /* This is a NEW or OLD pseudo-tables of a trigger */
+ Bool ephemPseudoTable;
+ Bool deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */
+ Bool isTable; /* True if a table requiring integer keys */
+ Bool isIndex; /* True if an index containing keys only - no data */
+ u8 bogusIncrKey; /* Something for pIncrKey to point to if pKeyInfo==0 */
+ i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */
+ Btree *pBt; /* Separate file holding temporary table */
+ int nData; /* Number of bytes in pData */
+ char *pData; /* Data for a NEW or OLD pseudo-table */
+ i64 iKey; /* Key for the NEW or OLD pseudo-table row */
+ u8 *pIncrKey; /* Pointer to pKeyInfo->incrKey */
+ KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */
+ int nField; /* Number of fields in the header */
+ i64 seqCount; /* Sequence counter */
+ sqlite3_vtab_cursor *pVtabCursor; /* The cursor for a virtual table */
+ const sqlite3_module *pModule; /* Module for cursor pVtabCursor */
+
+ /* Cached information about the header for the data record that the
+ ** cursor is currently pointing to. Only valid if cacheValid is true.
+ ** aRow might point to (ephemeral) data for the current row, or it might
+ ** be NULL.
+ */
+ int cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */
+ int payloadSize; /* Total number of bytes in the record */
+ u32 *aType; /* Type values for all entries in the record */
+ u32 *aOffset; /* Cached offsets to the start of each columns data */
+ u8 *aRow; /* Data for the current row, if all on one page */
+};
+typedef struct Cursor Cursor;
+
+/*
+** A value for Cursor.cacheValid that means the cache is always invalid.
+*/
+#define CACHE_STALE 0
+
+/*
+** Internally, the vdbe manipulates nearly all SQL values as Mem
+** structures. Each Mem struct may cache multiple representations (string,
+** integer etc.) of the same value. A value (and therefore Mem structure)
+** has the following properties:
+**
+** Each value has a manifest type. The manifest type of the value stored
+** in a Mem struct is returned by the MemType(Mem*) macro. The type is
+** one of SQLITE_NULL, SQLITE_INTEGER, SQLITE_REAL, SQLITE_TEXT or
+** SQLITE_BLOB.
+*/
+struct Mem {
+ union {
+ i64 i; /* Integer value. Or FuncDef* when flags==MEM_Agg */
+ FuncDef *pDef; /* Used only when flags==MEM_Agg */
+ } u;
+ double r; /* Real value */
+ sqlite3 *db; /* The associated database connection */
+ char *z; /* String or BLOB value */
+ int n; /* Number of characters in string value, excluding '\0' */
+ u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
+ u8 type; /* One of SQLITE_NULL, SQLITE_TEXT, SQLITE_INTEGER, etc */
+ u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */
+ void (*xDel)(void *); /* If not null, call this function to delete Mem.z */
+ char *zMalloc; /* Dynamic buffer allocated by sqlite3_malloc() */
+};
+
+/* One or more of the following flags are set to indicate the validOK
+** representations of the value stored in the Mem struct.
+**
+** If the MEM_Null flag is set, then the value is an SQL NULL value.
+** No other flags may be set in this case.
+**
+** If the MEM_Str flag is set then Mem.z points at a string representation.
+** Usually this is encoded in the same unicode encoding as the main
+** database (see below for exceptions). If the MEM_Term flag is also
+** set, then the string is nul terminated. The MEM_Int and MEM_Real
+** flags may coexist with the MEM_Str flag.
+**
+** Multiple of these values can appear in Mem.flags. But only one
+** at a time can appear in Mem.type.
+*/
+#define MEM_Null 0x0001 /* Value is NULL */
+#define MEM_Str 0x0002 /* Value is a string */
+#define MEM_Int 0x0004 /* Value is an integer */
+#define MEM_Real 0x0008 /* Value is a real number */
+#define MEM_Blob 0x0010 /* Value is a BLOB */
+
+#define MemSetTypeFlag(p, f) \
+ ((p)->flags = ((p)->flags&~(MEM_Int|MEM_Real|MEM_Null|MEM_Blob|MEM_Str))|f)
+
+/* Whenever Mem contains a valid string or blob representation, one of
+** the following flags must be set to determine the memory management
+** policy for Mem.z. The MEM_Term flag tells us whether or not the
+** string is \000 or \u0000 terminated
+*/
+#define MEM_Term 0x0020 /* String rep is nul terminated */
+#define MEM_Dyn 0x0040 /* Need to call sqliteFree() on Mem.z */
+#define MEM_Static 0x0080 /* Mem.z points to a static string */
+#define MEM_Ephem 0x0100 /* Mem.z points to an ephemeral string */
+#define MEM_Agg 0x0400 /* Mem.z points to an agg function context */
+#define MEM_Zero 0x0800 /* Mem.i contains count of 0s appended to blob */
+
+#ifdef SQLITE_OMIT_INCRBLOB
+ #undef MEM_Zero
+ #define MEM_Zero 0x0000
+#endif
+
+
+/* A VdbeFunc is just a FuncDef (defined in sqliteInt.h) that contains
+** additional information about auxiliary information bound to arguments
+** of the function. This is used to implement the sqlite3_get_auxdata()
+** and sqlite3_set_auxdata() APIs. The "auxdata" is some auxiliary data
+** that can be associated with a constant argument to a function. This
+** allows functions such as "regexp" to compile their constant regular
+** expression argument once and reused the compiled code for multiple
+** invocations.
+*/
+struct VdbeFunc {
+ FuncDef *pFunc; /* The definition of the function */
+ int nAux; /* Number of entries allocated for apAux[] */
+ struct AuxData {
+ void *pAux; /* Aux data for the i-th argument */
+ void (*xDelete)(void *); /* Destructor for the aux data */
+ } apAux[1]; /* One slot for each function argument */
+};
+
+/*
+** The "context" argument for a installable function. A pointer to an
+** instance of this structure is the first argument to the routines used
+** implement the SQL functions.
+**
+** There is a typedef for this structure in sqlite.h. So all routines,
+** even the public interface to SQLite, can use a pointer to this structure.
+** But this file is the only place where the internal details of this
+** structure are known.
+**
+** This structure is defined inside of vdbeInt.h because it uses substructures
+** (Mem) which are only defined there.
+*/
+struct sqlite3_context {
+ FuncDef *pFunc; /* Pointer to function information. MUST BE FIRST */
+ VdbeFunc *pVdbeFunc; /* Auxilary data, if created. */
+ Mem s; /* The return value is stored here */
+ Mem *pMem; /* Memory cell used to store aggregate context */
+ int isError; /* Error code returned by the function. */
+ CollSeq *pColl; /* Collating sequence */
+};
+
+/*
+** A Set structure is used for quick testing to see if a value
+** is part of a small set. Sets are used to implement code like
+** this:
+** x.y IN ('hi','hoo','hum')
+*/
+typedef struct Set Set;
+struct Set {
+ Hash hash; /* A set is just a hash table */
+ HashElem *prev; /* Previously accessed hash elemen */
+};
+
+/*
+** A FifoPage structure holds a single page of valves. Pages are arranged
+** in a list.
+*/
+typedef struct FifoPage FifoPage;
+struct FifoPage {
+ int nSlot; /* Number of entries aSlot[] */
+ int iWrite; /* Push the next value into this entry in aSlot[] */
+ int iRead; /* Read the next value from this entry in aSlot[] */
+ FifoPage *pNext; /* Next page in the fifo */
+ i64 aSlot[1]; /* One or more slots for rowid values */
+};
+
+/*
+** The Fifo structure is typedef-ed in vdbeInt.h. But the implementation
+** of that structure is private to this file.
+**
+** The Fifo structure describes the entire fifo.
+*/
+typedef struct Fifo Fifo;
+struct Fifo {
+ int nEntry; /* Total number of entries */
+ FifoPage *pFirst; /* First page on the list */
+ FifoPage *pLast; /* Last page on the list */
+};
+
+/*
+** A Context stores the last insert rowid, the last statement change count,
+** and the current statement change count (i.e. changes since last statement).
+** The current keylist is also stored in the context.
+** Elements of Context structure type make up the ContextStack, which is
+** updated by the ContextPush and ContextPop opcodes (used by triggers).
+** The context is pushed before executing a trigger a popped when the
+** trigger finishes.
+*/
+typedef struct Context Context;
+struct Context {
+ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */
+ int nChange; /* Statement changes (Vdbe.nChanges) */
+ Fifo sFifo; /* Records that will participate in a DELETE or UPDATE */
+};
+
+/*
+** An instance of the virtual machine. This structure contains the complete
+** state of the virtual machine.
+**
+** The "sqlite3_stmt" structure pointer that is returned by sqlite3_compile()
+** is really a pointer to an instance of this structure.
+**
+** The Vdbe.inVtabMethod variable is set to non-zero for the duration of
+** any virtual table method invocations made by the vdbe program. It is
+** set to 2 for xDestroy method calls and 1 for all other methods. This
+** variable is used for two purposes: to allow xDestroy methods to execute
+** "DROP TABLE" statements and to prevent some nasty side effects of
+** malloc failure when SQLite is invoked recursively by a virtual table
+** method function.
+*/
+struct Vdbe {
+ sqlite3 *db; /* The whole database */
+ Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */
+ int nOp; /* Number of instructions in the program */
+ int nOpAlloc; /* Number of slots allocated for aOp[] */
+ Op *aOp; /* Space to hold the virtual machine's program */
+ int nLabel; /* Number of labels used */
+ int nLabelAlloc; /* Number of slots allocated in aLabel[] */
+ int *aLabel; /* Space to hold the labels */
+ Mem **apArg; /* Arguments to currently executing user function */
+ Mem *aColName; /* Column names to return */
+ int nCursor; /* Number of slots in apCsr[] */
+ Cursor **apCsr; /* One element of this array for each open cursor */
+ int nVar; /* Number of entries in aVar[] */
+ Mem *aVar; /* Values for the OP_Variable opcode. */
+ char **azVar; /* Name of variables */
+ int okVar; /* True if azVar[] has been initialized */
+ int magic; /* Magic number for sanity checking */
+ int nMem; /* Number of memory locations currently allocated */
+ Mem *aMem; /* The memory locations */
+ int nCallback; /* Number of callbacks invoked so far */
+ int cacheCtr; /* Cursor row cache generation counter */
+ Fifo sFifo; /* A list of ROWIDs */
+ int contextStackTop; /* Index of top element in the context stack */
+ int contextStackDepth; /* The size of the "context" stack */
+ Context *contextStack; /* Stack used by opcodes ContextPush & ContextPop*/
+ int pc; /* The program counter */
+ int rc; /* Value to return */
+ unsigned uniqueCnt; /* Used by OP_MakeRecord when P2!=0 */
+ int errorAction; /* Recovery action to do in case of an error */
+ int inTempTrans; /* True if temp database is transactioned */
+ int returnStack[25]; /* Return address stack for OP_Gosub & OP_Return */
+ int returnDepth; /* Next unused element in returnStack[] */
+ int nResColumn; /* Number of columns in one row of the result set */
+ char **azResColumn; /* Values for one row of result */
+ char *zErrMsg; /* Error message written here */
+ Mem *pResultSet; /* Pointer to an array of results */
+ u8 explain; /* True if EXPLAIN present on SQL command */
+ u8 changeCntOn; /* True to update the change-counter */
+ u8 aborted; /* True if ROLLBACK in another VM causes an abort */
+ u8 expired; /* True if the VM needs to be recompiled */
+ u8 minWriteFileFormat; /* Minimum file format for writable database files */
+ u8 inVtabMethod; /* See comments above */
+ int nChange; /* Number of db changes made since last reset */
+ i64 startTime; /* Time when query started - used for profiling */
+ int btreeMask; /* Bitmask of db->aDb[] entries referenced */
+ BtreeMutexArray aMutex; /* An array of Btree used here and needing locks */
+ int nSql; /* Number of bytes in zSql */
+ char *zSql; /* Text of the SQL statement that generated this */
+#ifdef SQLITE_DEBUG
+ FILE *trace; /* Write an execution trace here, if not NULL */
+#endif
+ int openedStatement; /* True if this VM has opened a statement journal */
+#ifdef SQLITE_SSE
+ int fetchId; /* Statement number used by sqlite3_fetch_statement */
+ int lru; /* Counter used for LRU cache replacement */
+#endif
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ Vdbe *pLruPrev;
+ Vdbe *pLruNext;
+#endif
+};
+
+/*
+** An instance of the following structure holds information about a
+** single index record that has already been parsed out into individual
+** values.
+**
+** A record is an object that contains one or more fields of data.
+** Records are used to store the content of a table row and to store
+** the key of an index. A blob encoding of a record is created by
+** the OP_MakeRecord opcode of the VDBE and is disassemblied by the
+** OP_Column opcode.
+**
+** This structure holds a record that has already been disassembled
+** into its constitutent fields.
+*/
+struct UnpackedRecord {
+ KeyInfo *pKeyInfo; /* Collation and sort-order information */
+ u16 nField; /* Number of entries in apMem[] */
+ u8 needFree; /* True if memory obtained from sqlite3_malloc() */
+ u8 needDestroy; /* True if apMem[]s should be destroyed on close */
+ Mem *aMem; /* Values */
+};
+
+/*
+** The following are allowed values for Vdbe.magic
+*/
+#define VDBE_MAGIC_INIT 0x26bceaa5 /* Building a VDBE program */
+#define VDBE_MAGIC_RUN 0xbdf20da3 /* VDBE is ready to execute */
+#define VDBE_MAGIC_HALT 0x519c2973 /* VDBE has completed execution */
+#define VDBE_MAGIC_DEAD 0xb606c3c8 /* The VDBE has been deallocated */
+
+/*
+** Function prototypes
+*/
+SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, Cursor*);
+void sqliteVdbePopStack(Vdbe*,int);
+SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(Cursor*);
+#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE)
+SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE*, int, Op*);
+#endif
+SQLITE_PRIVATE int sqlite3VdbeSerialTypeLen(u32);
+SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem*, int);
+SQLITE_PRIVATE int sqlite3VdbeSerialPut(unsigned char*, int, Mem*, int);
+SQLITE_PRIVATE int sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*);
+SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(VdbeFunc*, int);
+
+int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *);
+SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(Cursor*,UnpackedRecord *,int,const unsigned char*,int*);
+SQLITE_PRIVATE int sqlite3VdbeIdxRowid(BtCursor *, i64 *);
+SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
+SQLITE_PRIVATE int sqlite3VdbeIdxRowidLen(const u8*);
+SQLITE_PRIVATE int sqlite3VdbeExec(Vdbe*);
+SQLITE_PRIVATE int sqlite3VdbeList(Vdbe*);
+SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe*);
+SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *, int);
+SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem*, const Mem*);
+SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int);
+SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem*, Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*));
+SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64);
+SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem*, double);
+SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem*);
+SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem*,int);
+SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemDynamicify(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, int);
+SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem*);
+SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem*);
+SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem*);
+SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,int,int,int,Mem*);
+SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p);
+SQLITE_PRIVATE void sqlite3VdbeMemReleaseExternal(Mem *p);
+SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*);
+SQLITE_PRIVATE const char *sqlite3OpcodeName(int);
+SQLITE_PRIVATE int sqlite3VdbeOpcodeHasProperty(int, int);
+SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve);
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+SQLITE_PRIVATE int sqlite3VdbeReleaseBuffers(Vdbe *p);
+#endif
+
+#ifndef NDEBUG
+SQLITE_PRIVATE void sqlite3VdbeMemSanity(Mem*);
+#endif
+SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem*, u8);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*);
+SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf);
+#endif
+SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem);
+SQLITE_PRIVATE void sqlite3VdbeFifoInit(Fifo*);
+SQLITE_PRIVATE int sqlite3VdbeFifoPush(Fifo*, i64);
+SQLITE_PRIVATE int sqlite3VdbeFifoPop(Fifo*, i64*);
+SQLITE_PRIVATE void sqlite3VdbeFifoClear(Fifo*);
+
+#ifndef SQLITE_OMIT_INCRBLOB
+SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *);
+#else
+ #define sqlite3VdbeMemExpandBlob(x) SQLITE_OK
+#endif
+
+#endif /* !defined(_VDBEINT_H_) */
+
+/************** End of vdbeInt.h *********************************************/
+/************** Continuing where we left off in utf.c ************************/
+
+/*
+** The following constant value is used by the SQLITE_BIGENDIAN and
+** SQLITE_LITTLEENDIAN macros.
+*/
+SQLITE_PRIVATE const int sqlite3one = 1;
+
+/*
+** This lookup table is used to help decode the first byte of
+** a multi-byte UTF8 character.
+*/
+static const unsigned char sqlite3UtfTrans1[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
+};
+
+
+#define WRITE_UTF8(zOut, c) { \
+ if( c<0x00080 ){ \
+ *zOut++ = (c&0xFF); \
+ } \
+ else if( c<0x00800 ){ \
+ *zOut++ = 0xC0 + ((c>>6)&0x1F); \
+ *zOut++ = 0x80 + (c & 0x3F); \
+ } \
+ else if( c<0x10000 ){ \
+ *zOut++ = 0xE0 + ((c>>12)&0x0F); \
+ *zOut++ = 0x80 + ((c>>6) & 0x3F); \
+ *zOut++ = 0x80 + (c & 0x3F); \
+ }else{ \
+ *zOut++ = 0xF0 + ((c>>18) & 0x07); \
+ *zOut++ = 0x80 + ((c>>12) & 0x3F); \
+ *zOut++ = 0x80 + ((c>>6) & 0x3F); \
+ *zOut++ = 0x80 + (c & 0x3F); \
+ } \
+}
+
+#define WRITE_UTF16LE(zOut, c) { \
+ if( c<=0xFFFF ){ \
+ *zOut++ = (c&0x00FF); \
+ *zOut++ = ((c>>8)&0x00FF); \
+ }else{ \
+ *zOut++ = (((c>>10)&0x003F) + (((c-0x10000)>>10)&0x00C0)); \
+ *zOut++ = (0x00D8 + (((c-0x10000)>>18)&0x03)); \
+ *zOut++ = (c&0x00FF); \
+ *zOut++ = (0x00DC + ((c>>8)&0x03)); \
+ } \
+}
+
+#define WRITE_UTF16BE(zOut, c) { \
+ if( c<=0xFFFF ){ \
+ *zOut++ = ((c>>8)&0x00FF); \
+ *zOut++ = (c&0x00FF); \
+ }else{ \
+ *zOut++ = (0x00D8 + (((c-0x10000)>>18)&0x03)); \
+ *zOut++ = (((c>>10)&0x003F) + (((c-0x10000)>>10)&0x00C0)); \
+ *zOut++ = (0x00DC + ((c>>8)&0x03)); \
+ *zOut++ = (c&0x00FF); \
+ } \
+}
+
+#define READ_UTF16LE(zIn, c){ \
+ c = (*zIn++); \
+ c += ((*zIn++)<<8); \
+ if( c>=0xD800 && c<0xE000 ){ \
+ int c2 = (*zIn++); \
+ c2 += ((*zIn++)<<8); \
+ c = (c2&0x03FF) + ((c&0x003F)<<10) + (((c&0x03C0)+0x0040)<<10); \
+ if( (c & 0xFFFF0000)==0 ) c = 0xFFFD; \
+ } \
+}
+
+#define READ_UTF16BE(zIn, c){ \
+ c = ((*zIn++)<<8); \
+ c += (*zIn++); \
+ if( c>=0xD800 && c<0xE000 ){ \
+ int c2 = ((*zIn++)<<8); \
+ c2 += (*zIn++); \
+ c = (c2&0x03FF) + ((c&0x003F)<<10) + (((c&0x03C0)+0x0040)<<10); \
+ if( (c & 0xFFFF0000)==0 ) c = 0xFFFD; \
+ } \
+}
+
+/*
+** Translate a single UTF-8 character. Return the unicode value.
+**
+** During translation, assume that the byte that zTerm points
+** is a 0x00.
+**
+** Write a pointer to the next unread byte back into *pzNext.
+**
+** Notes On Invalid UTF-8:
+**
+** * This routine never allows a 7-bit character (0x00 through 0x7f) to
+** be encoded as a multi-byte character. Any multi-byte character that
+** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd.
+**
+** * This routine never allows a UTF16 surrogate value to be encoded.
+** If a multi-byte character attempts to encode a value between
+** 0xd800 and 0xe000 then it is rendered as 0xfffd.
+**
+** * Bytes in the range of 0x80 through 0xbf which occur as the first
+** byte of a character are interpreted as single-byte characters
+** and rendered as themselves even though they are technically
+** invalid characters.
+**
+** * This routine accepts an infinite number of different UTF8 encodings
+** for unicode values 0x80 and greater. It do not change over-length
+** encodings to 0xfffd as some systems recommend.
+*/
+SQLITE_PRIVATE int sqlite3Utf8Read(
+ const unsigned char *z, /* First byte of UTF-8 character */
+ const unsigned char *zTerm, /* Pretend this byte is 0x00 */
+ const unsigned char **pzNext /* Write first byte past UTF-8 char here */
+){
+ int c = *(z++);
+ if( c>=0xc0 ){
+ c = sqlite3UtfTrans1[c-0xc0];
+ while( z!=zTerm && (*z & 0xc0)==0x80 ){
+ c = (c<<6) + (0x3f & *(z++));
+ }
+ if( c<0x80
+ || (c&0xFFFFF800)==0xD800
+ || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; }
+ }
+ *pzNext = z;
+ return c;
+}
+
+
+
+/*
+** If the TRANSLATE_TRACE macro is defined, the value of each Mem is
+** printed on stderr on the way into and out of sqlite3VdbeMemTranslate().
+*/
+/* #define TRANSLATE_TRACE 1 */
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** This routine transforms the internal text encoding used by pMem to
+** desiredEnc. It is an error if the string is already of the desired
+** encoding, or if *pMem does not contain a string value.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desiredEnc){
+ int len; /* Maximum length of output string in bytes */
+ unsigned char *zOut; /* Output buffer */
+ unsigned char *zIn; /* Input iterator */
+ unsigned char *zTerm; /* End of input */
+ unsigned char *z; /* Output iterator */
+ unsigned int c;
+
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( pMem->flags&MEM_Str );
+ assert( pMem->enc!=desiredEnc );
+ assert( pMem->enc!=0 );
+ assert( pMem->n>=0 );
+
+#if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG)
+ {
+ char zBuf[100];
+ sqlite3VdbeMemPrettyPrint(pMem, zBuf);
+ fprintf(stderr, "INPUT: %s\n", zBuf);
+ }
+#endif
+
+ /* If the translation is between UTF-16 little and big endian, then
+ ** all that is required is to swap the byte order. This case is handled
+ ** differently from the others.
+ */
+ if( pMem->enc!=SQLITE_UTF8 && desiredEnc!=SQLITE_UTF8 ){
+ u8 temp;
+ int rc;
+ rc = sqlite3VdbeMemMakeWriteable(pMem);
+ if( rc!=SQLITE_OK ){
+ assert( rc==SQLITE_NOMEM );
+ return SQLITE_NOMEM;
+ }
+ zIn = (u8*)pMem->z;
+ zTerm = &zIn[pMem->n];
+ while( zIn<zTerm ){
+ temp = *zIn;
+ *zIn = *(zIn+1);
+ zIn++;
+ *zIn++ = temp;
+ }
+ pMem->enc = desiredEnc;
+ goto translate_out;
+ }
+
+ /* Set len to the maximum number of bytes required in the output buffer. */
+ if( desiredEnc==SQLITE_UTF8 ){
+ /* When converting from UTF-16, the maximum growth results from
+ ** translating a 2-byte character to a 4-byte UTF-8 character.
+ ** A single byte is required for the output string
+ ** nul-terminator.
+ */
+ len = pMem->n * 2 + 1;
+ }else{
+ /* When converting from UTF-8 to UTF-16 the maximum growth is caused
+ ** when a 1-byte UTF-8 character is translated into a 2-byte UTF-16
+ ** character. Two bytes are required in the output buffer for the
+ ** nul-terminator.
+ */
+ len = pMem->n * 2 + 2;
+ }
+
+ /* Set zIn to point at the start of the input buffer and zTerm to point 1
+ ** byte past the end.
+ **
+ ** Variable zOut is set to point at the output buffer, space obtained
+ ** from sqlite3_malloc().
+ */
+ zIn = (u8*)pMem->z;
+ zTerm = &zIn[pMem->n];
+ zOut = sqlite3DbMallocRaw(pMem->db, len);
+ if( !zOut ){
+ return SQLITE_NOMEM;
+ }
+ z = zOut;
+
+ if( pMem->enc==SQLITE_UTF8 ){
+ if( desiredEnc==SQLITE_UTF16LE ){
+ /* UTF-8 -> UTF-16 Little-endian */
+ while( zIn<zTerm ){
+ c = sqlite3Utf8Read(zIn, zTerm, (const u8**)&zIn);
+ WRITE_UTF16LE(z, c);
+ }
+ }else{
+ assert( desiredEnc==SQLITE_UTF16BE );
+ /* UTF-8 -> UTF-16 Big-endian */
+ while( zIn<zTerm ){
+ c = sqlite3Utf8Read(zIn, zTerm, (const u8**)&zIn);
+ WRITE_UTF16BE(z, c);
+ }
+ }
+ pMem->n = z - zOut;
+ *z++ = 0;
+ }else{
+ assert( desiredEnc==SQLITE_UTF8 );
+ if( pMem->enc==SQLITE_UTF16LE ){
+ /* UTF-16 Little-endian -> UTF-8 */
+ while( zIn<zTerm ){
+ READ_UTF16LE(zIn, c);
+ WRITE_UTF8(z, c);
+ }
+ }else{
+ /* UTF-16 Little-endian -> UTF-8 */
+ while( zIn<zTerm ){
+ READ_UTF16BE(zIn, c);
+ WRITE_UTF8(z, c);
+ }
+ }
+ pMem->n = z - zOut;
+ }
+ *z = 0;
+ assert( (pMem->n+(desiredEnc==SQLITE_UTF8?1:2))<=len );
+
+ sqlite3VdbeMemRelease(pMem);
+ pMem->flags &= ~(MEM_Static|MEM_Dyn|MEM_Ephem);
+ pMem->enc = desiredEnc;
+ pMem->flags |= (MEM_Term|MEM_Dyn);
+ pMem->z = (char*)zOut;
+ pMem->zMalloc = pMem->z;
+
+translate_out:
+#if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG)
+ {
+ char zBuf[100];
+ sqlite3VdbeMemPrettyPrint(pMem, zBuf);
+ fprintf(stderr, "OUTPUT: %s\n", zBuf);
+ }
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** This routine checks for a byte-order mark at the beginning of the
+** UTF-16 string stored in *pMem. If one is present, it is removed and
+** the encoding of the Mem adjusted. This routine does not do any
+** byte-swapping, it just sets Mem.enc appropriately.
+**
+** The allocation (static, dynamic etc.) and encoding of the Mem may be
+** changed by this function.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem){
+ int rc = SQLITE_OK;
+ u8 bom = 0;
+
+ if( pMem->n<0 || pMem->n>1 ){
+ u8 b1 = *(u8 *)pMem->z;
+ u8 b2 = *(((u8 *)pMem->z) + 1);
+ if( b1==0xFE && b2==0xFF ){
+ bom = SQLITE_UTF16BE;
+ }
+ if( b1==0xFF && b2==0xFE ){
+ bom = SQLITE_UTF16LE;
+ }
+ }
+
+ if( bom ){
+ rc = sqlite3VdbeMemMakeWriteable(pMem);
+ if( rc==SQLITE_OK ){
+ pMem->n -= 2;
+ memmove(pMem->z, &pMem->z[2], pMem->n);
+ pMem->z[pMem->n] = '\0';
+ pMem->z[pMem->n+1] = '\0';
+ pMem->flags |= MEM_Term;
+ pMem->enc = bom;
+ }
+ }
+ return rc;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** pZ is a UTF-8 encoded unicode string. If nByte is less than zero,
+** return the number of unicode characters in pZ up to (but not including)
+** the first 0x00 byte. If nByte is not less than zero, return the
+** number of unicode characters in the first nByte of pZ (or up to
+** the first 0x00, whichever comes first).
+*/
+SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *zIn, int nByte){
+ int r = 0;
+ const u8 *z = (const u8*)zIn;
+ const u8 *zTerm;
+ if( nByte>=0 ){
+ zTerm = &z[nByte];
+ }else{
+ zTerm = (const u8*)(-1);
+ }
+ assert( z<=zTerm );
+ while( *z!=0 && z<zTerm ){
+ SQLITE_SKIP_UTF8(z);
+ r++;
+ }
+ return r;
+}
+
+/* This test function is not currently used by the automated test-suite.
+** Hence it is only available in debug builds.
+*/
+#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG)
+/*
+** Translate UTF-8 to UTF-8.
+**
+** This has the effect of making sure that the string is well-formed
+** UTF-8. Miscoded characters are removed.
+**
+** The translation is done in-place (since it is impossible for the
+** correct UTF-8 encoding to be longer than a malformed encoding).
+*/
+SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char *zIn){
+ unsigned char *zOut = zIn;
+ unsigned char *zStart = zIn;
+ unsigned char *zTerm;
+ u32 c;
+
+ while( zIn[0] ){
+ c = sqlite3Utf8Read(zIn, zTerm, (const u8**)&zIn);
+ if( c!=0xfffd ){
+ WRITE_UTF8(zOut, c);
+ }
+ }
+ *zOut = 0;
+ return zOut - zStart;
+}
+#endif
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Convert a UTF-16 string in the native encoding into a UTF-8 string.
+** Memory to hold the UTF-8 string is obtained from sqlite3_malloc and must
+** be freed by the calling function.
+**
+** NULL is returned if there is an allocation error.
+*/
+SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *db, const void *z, int nByte){
+ Mem m;
+ memset(&m, 0, sizeof(m));
+ m.db = db;
+ sqlite3VdbeMemSetStr(&m, z, nByte, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ sqlite3VdbeChangeEncoding(&m, SQLITE_UTF8);
+ if( db->mallocFailed ){
+ sqlite3VdbeMemRelease(&m);
+ m.z = 0;
+ }
+ assert( (m.flags & MEM_Term)!=0 || db->mallocFailed );
+ assert( (m.flags & MEM_Str)!=0 || db->mallocFailed );
+ return (m.flags & MEM_Dyn)!=0 ? m.z : sqlite3DbStrDup(db, m.z);
+}
+
+/*
+** pZ is a UTF-16 encoded unicode string. If nChar is less than zero,
+** return the number of bytes up to (but not including), the first pair
+** of consecutive 0x00 bytes in pZ. If nChar is not less than zero,
+** then return the number of bytes in the first nChar unicode characters
+** in pZ (or up until the first pair of 0x00 bytes, whichever comes first).
+*/
+SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nChar){
+ unsigned int c = 1;
+ char const *z = zIn;
+ int n = 0;
+ if( SQLITE_UTF16NATIVE==SQLITE_UTF16BE ){
+ /* Using an "if (SQLITE_UTF16NATIVE==SQLITE_UTF16BE)" construct here
+ ** and in other parts of this file means that at one branch will
+ ** not be covered by coverage testing on any single host. But coverage
+ ** will be complete if the tests are run on both a little-endian and
+ ** big-endian host. Because both the UTF16NATIVE and SQLITE_UTF16BE
+ ** macros are constant at compile time the compiler can determine
+ ** which branch will be followed. It is therefore assumed that no runtime
+ ** penalty is paid for this "if" statement.
+ */
+ while( c && ((nChar<0) || n<nChar) ){
+ READ_UTF16BE(z, c);
+ n++;
+ }
+ }else{
+ while( c && ((nChar<0) || n<nChar) ){
+ READ_UTF16LE(z, c);
+ n++;
+ }
+ }
+ return (z-(char const *)zIn)-((c==0)?2:0);
+}
+
+#if defined(SQLITE_TEST)
+/*
+** This routine is called from the TCL test function "translate_selftest".
+** It checks that the primitives for serializing and deserializing
+** characters in each encoding are inverses of each other.
+*/
+SQLITE_PRIVATE void sqlite3UtfSelfTest(){
+ unsigned int i, t;
+ unsigned char zBuf[20];
+ unsigned char *z;
+ unsigned char *zTerm;
+ int n;
+ unsigned int c;
+
+ for(i=0; i<0x00110000; i++){
+ z = zBuf;
+ WRITE_UTF8(z, i);
+ n = z-zBuf;
+ z[0] = 0;
+ zTerm = z;
+ z = zBuf;
+ c = sqlite3Utf8Read(z, zTerm, (const u8**)&z);
+ t = i;
+ if( i>=0xD800 && i<=0xDFFF ) t = 0xFFFD;
+ if( (i&0xFFFFFFFE)==0xFFFE ) t = 0xFFFD;
+ assert( c==t );
+ assert( (z-zBuf)==n );
+ }
+ for(i=0; i<0x00110000; i++){
+ if( i>=0xD800 && i<0xE000 ) continue;
+ z = zBuf;
+ WRITE_UTF16LE(z, i);
+ n = z-zBuf;
+ z[0] = 0;
+ z = zBuf;
+ READ_UTF16LE(z, c);
+ assert( c==i );
+ assert( (z-zBuf)==n );
+ }
+ for(i=0; i<0x00110000; i++){
+ if( i>=0xD800 && i<0xE000 ) continue;
+ z = zBuf;
+ WRITE_UTF16BE(z, i);
+ n = z-zBuf;
+ z[0] = 0;
+ z = zBuf;
+ READ_UTF16BE(z, c);
+ assert( c==i );
+ assert( (z-zBuf)==n );
+ }
+}
+#endif /* SQLITE_TEST */
+#endif /* SQLITE_OMIT_UTF16 */
+
+/************** End of utf.c *************************************************/
+/************** Begin file util.c ********************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Utility functions used throughout sqlite.
+**
+** This file contains functions for allocating memory, comparing
+** strings, and stuff like that.
+**
+** $Id: util.c,v 1.229 2008/05/13 16:41:50 drh Exp $
+*/
+
+
+/*
+** Return true if the floating point value is Not a Number.
+*/
+SQLITE_PRIVATE int sqlite3IsNaN(double x){
+ /* This NaN test sometimes fails if compiled on GCC with -ffast-math.
+ ** On the other hand, the use of -ffast-math comes with the following
+ ** warning:
+ **
+ ** This option [-ffast-math] should never be turned on by any
+ ** -O option since it can result in incorrect output for programs
+ ** which depend on an exact implementation of IEEE or ISO
+ ** rules/specifications for math functions.
+ */
+ volatile double y = x;
+ return x!=y;
+}
+
+/*
+** Set the most recent error code and error string for the sqlite
+** handle "db". The error code is set to "err_code".
+**
+** If it is not NULL, string zFormat specifies the format of the
+** error string in the style of the printf functions: The following
+** format characters are allowed:
+**
+** %s Insert a string
+** %z A string that should be freed after use
+** %d Insert an integer
+** %T Insert a token
+** %S Insert the first element of a SrcList
+**
+** zFormat and any string tokens that follow it are assumed to be
+** encoded in UTF-8.
+**
+** To clear the most recent error for sqlite handle "db", sqlite3Error
+** should be called with err_code set to SQLITE_OK and zFormat set
+** to NULL.
+*/
+SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code, const char *zFormat, ...){
+ if( db && (db->pErr || (db->pErr = sqlite3ValueNew(db))!=0) ){
+ db->errCode = err_code;
+ if( zFormat ){
+ char *z;
+ va_list ap;
+ va_start(ap, zFormat);
+ z = sqlite3VMPrintf(db, zFormat, ap);
+ va_end(ap);
+ sqlite3ValueSetStr(db->pErr, -1, z, SQLITE_UTF8, sqlite3_free);
+ }else{
+ sqlite3ValueSetStr(db->pErr, 0, 0, SQLITE_UTF8, SQLITE_STATIC);
+ }
+ }
+}
+
+/*
+** Add an error message to pParse->zErrMsg and increment pParse->nErr.
+** The following formatting characters are allowed:
+**
+** %s Insert a string
+** %z A string that should be freed after use
+** %d Insert an integer
+** %T Insert a token
+** %S Insert the first element of a SrcList
+**
+** This function should be used to report any error that occurs whilst
+** compiling an SQL statement (i.e. within sqlite3_prepare()). The
+** last thing the sqlite3_prepare() function does is copy the error
+** stored by this function into the database handle using sqlite3Error().
+** Function sqlite3Error() should be used during statement execution
+** (sqlite3_step() etc.).
+*/
+SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
+ va_list ap;
+ pParse->nErr++;
+ sqlite3_free(pParse->zErrMsg);
+ va_start(ap, zFormat);
+ pParse->zErrMsg = sqlite3VMPrintf(pParse->db, zFormat, ap);
+ va_end(ap);
+ if( pParse->rc==SQLITE_OK ){
+ pParse->rc = SQLITE_ERROR;
+ }
+}
+
+/*
+** Clear the error message in pParse, if any
+*/
+SQLITE_PRIVATE void sqlite3ErrorClear(Parse *pParse){
+ sqlite3_free(pParse->zErrMsg);
+ pParse->zErrMsg = 0;
+ pParse->nErr = 0;
+}
+
+/*
+** Convert an SQL-style quoted string into a normal string by removing
+** the quote characters. The conversion is done in-place. If the
+** input does not begin with a quote character, then this routine
+** is a no-op.
+**
+** 2002-Feb-14: This routine is extended to remove MS-Access style
+** brackets from around identifers. For example: "[a-b-c]" becomes
+** "a-b-c".
+*/
+SQLITE_PRIVATE void sqlite3Dequote(char *z){
+ int quote;
+ int i, j;
+ if( z==0 ) return;
+ quote = z[0];
+ switch( quote ){
+ case '\'': break;
+ case '"': break;
+ case '`': break; /* For MySQL compatibility */
+ case '[': quote = ']'; break; /* For MS SqlServer compatibility */
+ default: return;
+ }
+ for(i=1, j=0; z[i]; i++){
+ if( z[i]==quote ){
+ if( z[i+1]==quote ){
+ z[j++] = quote;
+ i++;
+ }else{
+ z[j++] = 0;
+ break;
+ }
+ }else{
+ z[j++] = z[i];
+ }
+ }
+}
+
+/* An array to map all upper-case characters into their corresponding
+** lower-case character.
+*/
+SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = {
+#ifdef SQLITE_ASCII
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103,
+ 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,
+ 122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,
+ 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,
+ 126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,
+ 162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,
+ 180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,
+ 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,
+ 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,
+ 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,
+ 252,253,254,255
+#endif
+#ifdef SQLITE_EBCDIC
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 0x */
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 1x */
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* 2x */
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* 3x */
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* 4x */
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, /* 5x */
+ 96, 97, 66, 67, 68, 69, 70, 71, 72, 73,106,107,108,109,110,111, /* 6x */
+ 112, 81, 82, 83, 84, 85, 86, 87, 88, 89,122,123,124,125,126,127, /* 7x */
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, /* 8x */
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,156,159, /* 9x */
+ 160,161,162,163,164,165,166,167,168,169,170,171,140,141,142,175, /* Ax */
+ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, /* Bx */
+ 192,129,130,131,132,133,134,135,136,137,202,203,204,205,206,207, /* Cx */
+ 208,145,146,147,148,149,150,151,152,153,218,219,220,221,222,223, /* Dx */
+ 224,225,162,163,164,165,166,167,168,169,232,203,204,205,206,207, /* Ex */
+ 239,240,241,242,243,244,245,246,247,248,249,219,220,221,222,255, /* Fx */
+#endif
+};
+#define UpperToLower sqlite3UpperToLower
+
+/*
+** Some systems have stricmp(). Others have strcasecmp(). Because
+** there is no consistency, we will define our own.
+*/
+SQLITE_PRIVATE int sqlite3StrICmp(const char *zLeft, const char *zRight){
+ unsigned char *a, *b;
+ a = (unsigned char *)zLeft;
+ b = (unsigned char *)zRight;
+ while( *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; }
+ return UpperToLower[*a] - UpperToLower[*b];
+}
+SQLITE_PRIVATE int sqlite3StrNICmp(const char *zLeft, const char *zRight, int N){
+ unsigned char *a, *b;
+ a = (unsigned char *)zLeft;
+ b = (unsigned char *)zRight;
+ while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; }
+ return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b];
+}
+
+/*
+** Return TRUE if z is a pure numeric string. Return FALSE if the
+** string contains any character which is not part of a number. If
+** the string is numeric and contains the '.' character, set *realnum
+** to TRUE (otherwise FALSE).
+**
+** An empty string is considered non-numeric.
+*/
+SQLITE_PRIVATE int sqlite3IsNumber(const char *z, int *realnum, u8 enc){
+ int incr = (enc==SQLITE_UTF8?1:2);
+ if( enc==SQLITE_UTF16BE ) z++;
+ if( *z=='-' || *z=='+' ) z += incr;
+ if( !isdigit(*(u8*)z) ){
+ return 0;
+ }
+ z += incr;
+ if( realnum ) *realnum = 0;
+ while( isdigit(*(u8*)z) ){ z += incr; }
+ if( *z=='.' ){
+ z += incr;
+ if( !isdigit(*(u8*)z) ) return 0;
+ while( isdigit(*(u8*)z) ){ z += incr; }
+ if( realnum ) *realnum = 1;
+ }
+ if( *z=='e' || *z=='E' ){
+ z += incr;
+ if( *z=='+' || *z=='-' ) z += incr;
+ if( !isdigit(*(u8*)z) ) return 0;
+ while( isdigit(*(u8*)z) ){ z += incr; }
+ if( realnum ) *realnum = 1;
+ }
+ return *z==0;
+}
+
+/*
+** The string z[] is an ascii representation of a real number.
+** Convert this string to a double.
+**
+** This routine assumes that z[] really is a valid number. If it
+** is not, the result is undefined.
+**
+** This routine is used instead of the library atof() function because
+** the library atof() might want to use "," as the decimal point instead
+** of "." depending on how locale is set. But that would cause problems
+** for SQL. So this routine always uses "." regardless of locale.
+*/
+SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult){
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ int sign = 1;
+ const char *zBegin = z;
+ LONGDOUBLE_TYPE v1 = 0.0;
+ int nSignificant = 0;
+ while( isspace(*(u8*)z) ) z++;
+ if( *z=='-' ){
+ sign = -1;
+ z++;
+ }else if( *z=='+' ){
+ z++;
+ }
+ while( z[0]=='0' ){
+ z++;
+ }
+ while( isdigit(*(u8*)z) ){
+ v1 = v1*10.0 + (*z - '0');
+ z++;
+ nSignificant++;
+ }
+ if( *z=='.' ){
+ LONGDOUBLE_TYPE divisor = 1.0;
+ z++;
+ if( nSignificant==0 ){
+ while( z[0]=='0' ){
+ divisor *= 10.0;
+ z++;
+ }
+ }
+ while( isdigit(*(u8*)z) ){
+ if( nSignificant<18 ){
+ v1 = v1*10.0 + (*z - '0');
+ divisor *= 10.0;
+ nSignificant++;
+ }
+ z++;
+ }
+ v1 /= divisor;
+ }
+ if( *z=='e' || *z=='E' ){
+ int esign = 1;
+ int eval = 0;
+ LONGDOUBLE_TYPE scale = 1.0;
+ z++;
+ if( *z=='-' ){
+ esign = -1;
+ z++;
+ }else if( *z=='+' ){
+ z++;
+ }
+ while( isdigit(*(u8*)z) ){
+ eval = eval*10 + *z - '0';
+ z++;
+ }
+ while( eval>=64 ){ scale *= 1.0e+64; eval -= 64; }
+ while( eval>=16 ){ scale *= 1.0e+16; eval -= 16; }
+ while( eval>=4 ){ scale *= 1.0e+4; eval -= 4; }
+ while( eval>=1 ){ scale *= 1.0e+1; eval -= 1; }
+ if( esign<0 ){
+ v1 /= scale;
+ }else{
+ v1 *= scale;
+ }
+ }
+ *pResult = sign<0 ? -v1 : v1;
+ return z - zBegin;
+#else
+ return sqlite3Atoi64(z, pResult);
+#endif /* SQLITE_OMIT_FLOATING_POINT */
+}
+
+/*
+** Compare the 19-character string zNum against the text representation
+** value 2^63: 9223372036854775808. Return negative, zero, or positive
+** if zNum is less than, equal to, or greater than the string.
+**
+** Unlike memcmp() this routine is guaranteed to return the difference
+** in the values of the last digit if the only difference is in the
+** last digit. So, for example,
+**
+** compare2pow63("9223372036854775800")
+**
+** will return -8.
+*/
+static int compare2pow63(const char *zNum){
+ int c;
+ c = memcmp(zNum,"922337203685477580",18);
+ if( c==0 ){
+ c = zNum[18] - '8';
+ }
+ return c;
+}
+
+
+/*
+** Return TRUE if zNum is a 64-bit signed integer and write
+** the value of the integer into *pNum. If zNum is not an integer
+** or is an integer that is too large to be expressed with 64 bits,
+** then return false.
+**
+** When this routine was originally written it dealt with only
+** 32-bit numbers. At that time, it was much faster than the
+** atoi() library routine in RedHat 7.2.
+*/
+SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum){
+ i64 v = 0;
+ int neg;
+ int i, c;
+ while( isspace(*(u8*)zNum) ) zNum++;
+ if( *zNum=='-' ){
+ neg = 1;
+ zNum++;
+ }else if( *zNum=='+' ){
+ neg = 0;
+ zNum++;
+ }else{
+ neg = 0;
+ }
+ while( zNum[0]=='0' ){ zNum++; } /* Skip over leading zeros. Ticket #2454 */
+ for(i=0; (c=zNum[i])>='0' && c<='9'; i++){
+ v = v*10 + c - '0';
+ }
+ *pNum = neg ? -v : v;
+ if( c!=0 || i==0 || i>19 ){
+ /* zNum is empty or contains non-numeric text or is longer
+ ** than 19 digits (thus guaranting that it is too large) */
+ return 0;
+ }else if( i<19 ){
+ /* Less than 19 digits, so we know that it fits in 64 bits */
+ return 1;
+ }else{
+ /* 19-digit numbers must be no larger than 9223372036854775807 if positive
+ ** or 9223372036854775808 if negative. Note that 9223372036854665808
+ ** is 2^63. */
+ return compare2pow63(zNum)<neg;
+ }
+}
+
+/*
+** The string zNum represents an integer. There might be some other
+** information following the integer too, but that part is ignored.
+** If the integer that the prefix of zNum represents will fit in a
+** 64-bit signed integer, return TRUE. Otherwise return FALSE.
+**
+** This routine returns FALSE for the string -9223372036854775808 even that
+** that number will, in theory fit in a 64-bit integer. Positive
+** 9223373036854775808 will not fit in 64 bits. So it seems safer to return
+** false.
+*/
+SQLITE_PRIVATE int sqlite3FitsIn64Bits(const char *zNum, int negFlag){
+ int i, c;
+ int neg = 0;
+ if( *zNum=='-' ){
+ neg = 1;
+ zNum++;
+ }else if( *zNum=='+' ){
+ zNum++;
+ }
+ if( negFlag ) neg = 1-neg;
+ while( *zNum=='0' ){
+ zNum++; /* Skip leading zeros. Ticket #2454 */
+ }
+ for(i=0; (c=zNum[i])>='0' && c<='9'; i++){}
+ if( i<19 ){
+ /* Guaranteed to fit if less than 19 digits */
+ return 1;
+ }else if( i>19 ){
+ /* Guaranteed to be too big if greater than 19 digits */
+ return 0;
+ }else{
+ /* Compare against 2^63. */
+ return compare2pow63(zNum)<neg;
+ }
+}
+
+/*
+** If zNum represents an integer that will fit in 32-bits, then set
+** *pValue to that integer and return true. Otherwise return false.
+**
+** Any non-numeric characters that following zNum are ignored.
+** This is different from sqlite3Atoi64() which requires the
+** input number to be zero-terminated.
+*/
+SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){
+ sqlite_int64 v = 0;
+ int i, c;
+ int neg = 0;
+ if( zNum[0]=='-' ){
+ neg = 1;
+ zNum++;
+ }else if( zNum[0]=='+' ){
+ zNum++;
+ }
+ while( zNum[0]=='0' ) zNum++;
+ for(i=0; i<11 && (c = zNum[i] - '0')>=0 && c<=9; i++){
+ v = v*10 + c;
+ }
+
+ /* The longest decimal representation of a 32 bit integer is 10 digits:
+ **
+ ** 1234567890
+ ** 2^31 -> 2147483648
+ */
+ if( i>10 ){
+ return 0;
+ }
+ if( v-neg>2147483647 ){
+ return 0;
+ }
+ if( neg ){
+ v = -v;
+ }
+ *pValue = (int)v;
+ return 1;
+}
+
+/*
+** The variable-length integer encoding is as follows:
+**
+** KEY:
+** A = 0xxxxxxx 7 bits of data and one flag bit
+** B = 1xxxxxxx 7 bits of data and one flag bit
+** C = xxxxxxxx 8 bits of data
+**
+** 7 bits - A
+** 14 bits - BA
+** 21 bits - BBA
+** 28 bits - BBBA
+** 35 bits - BBBBA
+** 42 bits - BBBBBA
+** 49 bits - BBBBBBA
+** 56 bits - BBBBBBBA
+** 64 bits - BBBBBBBBC
+*/
+
+/*
+** Write a 64-bit variable-length integer to memory starting at p[0].
+** The length of data write will be between 1 and 9 bytes. The number
+** of bytes written is returned.
+**
+** A variable-length integer consists of the lower 7 bits of each byte
+** for all bytes that have the 8th bit set and one byte with the 8th
+** bit clear. Except, if we get to the 9th byte, it stores the full
+** 8 bits and is the last byte.
+*/
+SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){
+ int i, j, n;
+ u8 buf[10];
+ if( v & (((u64)0xff000000)<<32) ){
+ p[8] = v;
+ v >>= 8;
+ for(i=7; i>=0; i--){
+ p[i] = (v & 0x7f) | 0x80;
+ v >>= 7;
+ }
+ return 9;
+ }
+ n = 0;
+ do{
+ buf[n++] = (v & 0x7f) | 0x80;
+ v >>= 7;
+ }while( v!=0 );
+ buf[0] &= 0x7f;
+ assert( n<=9 );
+ for(i=0, j=n-1; j>=0; j--, i++){
+ p[i] = buf[j];
+ }
+ return n;
+}
+
+/*
+** This routine is a faster version of sqlite3PutVarint() that only
+** works for 32-bit positive integers and which is optimized for
+** the common case of small integers. A MACRO version, putVarint32,
+** is provided which inlines the single-byte case. All code should use
+** the MACRO version as this function assumes the single-byte case has
+** already been handled.
+*/
+SQLITE_PRIVATE int sqlite3PutVarint32(unsigned char *p, u32 v){
+#ifndef putVarint32
+ if( (v & ~0x7f)==0 ){
+ p[0] = v;
+ return 1;
+ }
+#endif
+ if( (v & ~0x3fff)==0 ){
+ p[0] = (v>>7) | 0x80;
+ p[1] = v & 0x7f;
+ return 2;
+ }
+ return sqlite3PutVarint(p, v);
+}
+
+/*
+** Read a 64-bit variable-length integer from memory starting at p[0].
+** Return the number of bytes read. The value is stored in *v.
+*/
+SQLITE_PRIVATE int sqlite3GetVarint(const unsigned char *p, u64 *v){
+ u32 a,b,s;
+
+ a = *p;
+ /* a: p0 (unmasked)*/
+ if (!(a&0x80))
+ {
+ *v = a;
+ return 1;
+ }
+
+ p++;
+ b = *p;
+ /* b: p1 (unmasked)*/
+ if (!(b&0x80))
+ {
+ a &= 0x7f;
+ a = a<<7;
+ a |= b;
+ *v = a;
+ return 2;
+ }
+
+ p++;
+ a = a<<14;
+ a |= *p;
+ /* a: p0<<14 | p2 (unmasked)*/
+ if (!(a&0x80))
+ {
+ a &= (0x7f<<14)|(0x7f);
+ b &= 0x7f;
+ b = b<<7;
+ a |= b;
+ *v = a;
+ return 3;
+ }
+
+ /* CSE1 from below*/
+ a &= (0x7f<<14)|(0x7f);
+ p++;
+ b = b<<14;
+ b |= *p;
+ /* b: p1<<14 | p3 (unmasked)*/
+ if (!(b&0x80))
+ {
+ b &= (0x7f<<14)|(0x7f);
+ /* moved CSE1 up
+ a &= (0x7f<<14)|(0x7f);*/
+ a = a<<7;
+ a |= b;
+ *v = a;
+ return 4;
+ }
+
+ /* a: p0<<14 | p2 (masked)
+ b: p1<<14 | p3 (unmasked)
+ 1:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked)
+ moved CSE1 up
+ a &= (0x7f<<14)|(0x7f);*/
+ b &= (0x7f<<14)|(0x7f);
+ s = a;
+ /* s: p0<<14 | p2 (masked)*/
+
+ p++;
+ a = a<<14;
+ a |= *p;
+ /* a: p0<<28 | p2<<14 | p4 (unmasked)*/
+ if (!(a&0x80))
+ {
+ /* we can skip these cause they were (effectively) done above in calc'ing s
+ a &= (0x7f<<28)|(0x7f<<14)|(0x7f);
+ b &= (0x7f<<14)|(0x7f);*/
+ b = b<<7;
+ a |= b;
+ s = s>>18;
+ *v = ((u64)s)<<32 | a;
+ return 5;
+ }
+
+ /* 2:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked)*/
+ s = s<<7;
+ s |= b;
+ /* s: p0<<21 | p1<<14 | p2<<7 | p3 (masked)*/
+
+ p++;
+ b = b<<14;
+ b |= *p;
+ /* b: p1<<28 | p3<<14 | p5 (unmasked)*/
+ if (!(b&0x80))
+ {
+ /* we can skip this cause it was (effectively) done above in calc'ing s
+ b &= (0x7f<<28)|(0x7f<<14)|(0x7f);*/
+ a &= (0x7f<<14)|(0x7f);
+ a = a<<7;
+ a |= b;
+ s = s>>18;
+ *v = ((u64)s)<<32 | a;
+ return 6;
+ }
+
+ p++;
+ a = a<<14;
+ a |= *p;
+ /* a: p2<<28 | p4<<14 | p6 (unmasked)*/
+ if (!(a&0x80))
+ {
+ a &= (0x7f<<28)|(0x7f<<14)|(0x7f);
+ b &= (0x7f<<14)|(0x7f);
+ b = b<<7;
+ a |= b;
+ s = s>>11;
+ *v = ((u64)s)<<32 | a;
+ return 7;
+ }
+
+ /* CSE2 from below*/
+ a &= (0x7f<<14)|(0x7f);
+ p++;
+ b = b<<14;
+ b |= *p;
+ /* b: p3<<28 | p5<<14 | p7 (unmasked)*/
+ if (!(b&0x80))
+ {
+ b &= (0x7f<<28)|(0x7f<<14)|(0x7f);
+ /* moved CSE2 up
+ a &= (0x7f<<14)|(0x7f);*/
+ a = a<<7;
+ a |= b;
+ s = s>>4;
+ *v = ((u64)s)<<32 | a;
+ return 8;
+ }
+
+ p++;
+ a = a<<15;
+ a |= *p;
+ /* a: p4<<29 | p6<<15 | p8 (unmasked)*/
+
+ /* moved CSE2 up
+ a &= (0x7f<<29)|(0x7f<<15)|(0xff);*/
+ b &= (0x7f<<14)|(0x7f);
+ b = b<<8;
+ a |= b;
+
+ s = s<<4;
+ b = p[-4];
+ b &= 0x7f;
+ b = b>>3;
+ s |= b;
+
+ *v = ((u64)s)<<32 | a;
+
+ return 9;
+}
+
+/*
+** Read a 32-bit variable-length integer from memory starting at p[0].
+** Return the number of bytes read. The value is stored in *v.
+** A MACRO version, getVarint32, is provided which inlines the
+** single-byte case. All code should use the MACRO version as
+** this function assumes the single-byte case has already been handled.
+*/
+SQLITE_PRIVATE int sqlite3GetVarint32(const unsigned char *p, u32 *v){
+ u32 a,b;
+
+ a = *p;
+ /* a: p0 (unmasked)*/
+#ifndef getVarint32
+ if (!(a&0x80))
+ {
+ *v = a;
+ return 1;
+ }
+#endif
+
+ p++;
+ b = *p;
+ /* b: p1 (unmasked)*/
+ if (!(b&0x80))
+ {
+ a &= 0x7f;
+ a = a<<7;
+ *v = a | b;
+ return 2;
+ }
+
+ p++;
+ a = a<<14;
+ a |= *p;
+ /* a: p0<<14 | p2 (unmasked)*/
+ if (!(a&0x80))
+ {
+ a &= (0x7f<<14)|(0x7f);
+ b &= 0x7f;
+ b = b<<7;
+ *v = a | b;
+ return 3;
+ }
+
+ p++;
+ b = b<<14;
+ b |= *p;
+ /* b: p1<<14 | p3 (unmasked)*/
+ if (!(b&0x80))
+ {
+ b &= (0x7f<<14)|(0x7f);
+ a &= (0x7f<<14)|(0x7f);
+ a = a<<7;
+ *v = a | b;
+ return 4;
+ }
+
+ p++;
+ a = a<<14;
+ a |= *p;
+ /* a: p0<<28 | p2<<14 | p4 (unmasked)*/
+ if (!(a&0x80))
+ {
+ a &= (0x7f<<28)|(0x7f<<14)|(0x7f);
+ b &= (0x7f<<28)|(0x7f<<14)|(0x7f);
+ b = b<<7;
+ *v = a | b;
+ return 5;
+ }
+
+ /* We can only reach this point when reading a corrupt database
+ ** file. In that case we are not in any hurry. Use the (relatively
+ ** slow) general-purpose sqlite3GetVarint() routine to extract the
+ ** value. */
+ {
+ u64 v64;
+ int n;
+
+ p -= 4;
+ n = sqlite3GetVarint(p, &v64);
+ assert( n>5 && n<=9 );
+ *v = (u32)v64;
+ return n;
+ }
+}
+
+/*
+** Return the number of bytes that will be needed to store the given
+** 64-bit integer.
+*/
+SQLITE_PRIVATE int sqlite3VarintLen(u64 v){
+ int i = 0;
+ do{
+ i++;
+ v >>= 7;
+ }while( v!=0 && i<9 );
+ return i;
+}
+
+
+/*
+** Read or write a four-byte big-endian integer value.
+*/
+SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){
+ return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
+}
+SQLITE_PRIVATE void sqlite3Put4byte(unsigned char *p, u32 v){
+ p[0] = v>>24;
+ p[1] = v>>16;
+ p[2] = v>>8;
+ p[3] = v;
+}
+
+
+
+#if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC)
+/*
+** Translate a single byte of Hex into an integer.
+** This routinen only works if h really is a valid hexadecimal
+** character: 0..9a..fA..F
+*/
+static int hexToInt(int h){
+ assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') );
+#ifdef SQLITE_ASCII
+ h += 9*(1&(h>>6));
+#endif
+#ifdef SQLITE_EBCDIC
+ h += 9*(1&~(h>>4));
+#endif
+ return h & 0xf;
+}
+#endif /* !SQLITE_OMIT_BLOB_LITERAL || SQLITE_HAS_CODEC */
+
+#if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC)
+/*
+** Convert a BLOB literal of the form "x'hhhhhh'" into its binary
+** value. Return a pointer to its binary value. Space to hold the
+** binary value has been obtained from malloc and must be freed by
+** the calling routine.
+*/
+SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){
+ char *zBlob;
+ int i;
+
+ zBlob = (char *)sqlite3DbMallocRaw(db, n/2 + 1);
+ n--;
+ if( zBlob ){
+ for(i=0; i<n; i+=2){
+ zBlob[i/2] = (hexToInt(z[i])<<4) | hexToInt(z[i+1]);
+ }
+ zBlob[i/2] = 0;
+ }
+ return zBlob;
+}
+#endif /* !SQLITE_OMIT_BLOB_LITERAL || SQLITE_HAS_CODEC */
+
+
+/*
+** Change the sqlite.magic from SQLITE_MAGIC_OPEN to SQLITE_MAGIC_BUSY.
+** Return an error (non-zero) if the magic was not SQLITE_MAGIC_OPEN
+** when this routine is called.
+**
+** This routine is called when entering an SQLite API. The SQLITE_MAGIC_OPEN
+** value indicates that the database connection passed into the API is
+** open and is not being used by another thread. By changing the value
+** to SQLITE_MAGIC_BUSY we indicate that the connection is in use.
+** sqlite3SafetyOff() below will change the value back to SQLITE_MAGIC_OPEN
+** when the API exits.
+**
+** This routine is a attempt to detect if two threads use the
+** same sqlite* pointer at the same time. There is a race
+** condition so it is possible that the error is not detected.
+** But usually the problem will be seen. The result will be an
+** error which can be used to debug the application that is
+** using SQLite incorrectly.
+**
+** Ticket #202: If db->magic is not a valid open value, take care not
+** to modify the db structure at all. It could be that db is a stale
+** pointer. In other words, it could be that there has been a prior
+** call to sqlite3_close(db) and db has been deallocated. And we do
+** not want to write into deallocated memory.
+*/
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3SafetyOn(sqlite3 *db){
+ if( db->magic==SQLITE_MAGIC_OPEN ){
+ db->magic = SQLITE_MAGIC_BUSY;
+ assert( sqlite3_mutex_held(db->mutex) );
+ return 0;
+ }else if( db->magic==SQLITE_MAGIC_BUSY ){
+ db->magic = SQLITE_MAGIC_ERROR;
+ db->u1.isInterrupted = 1;
+ }
+ return 1;
+}
+#endif
+
+/*
+** Change the magic from SQLITE_MAGIC_BUSY to SQLITE_MAGIC_OPEN.
+** Return an error (non-zero) if the magic was not SQLITE_MAGIC_BUSY
+** when this routine is called.
+*/
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3SafetyOff(sqlite3 *db){
+ if( db->magic==SQLITE_MAGIC_BUSY ){
+ db->magic = SQLITE_MAGIC_OPEN;
+ assert( sqlite3_mutex_held(db->mutex) );
+ return 0;
+ }else{
+ db->magic = SQLITE_MAGIC_ERROR;
+ db->u1.isInterrupted = 1;
+ return 1;
+ }
+}
+#endif
+
+/*
+** Check to make sure we have a valid db pointer. This test is not
+** foolproof but it does provide some measure of protection against
+** misuse of the interface such as passing in db pointers that are
+** NULL or which have been previously closed. If this routine returns
+** 1 it means that the db pointer is valid and 0 if it should not be
+** dereferenced for any reason. The calling function should invoke
+** SQLITE_MISUSE immediately.
+**
+** sqlite3SafetyCheckOk() requires that the db pointer be valid for
+** use. sqlite3SafetyCheckSickOrOk() allows a db pointer that failed to
+** open properly and is not fit for general use but which can be
+** used as an argument to sqlite3_errmsg() or sqlite3_close().
+*/
+SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){
+ int magic;
+ if( db==0 ) return 0;
+ magic = db->magic;
+ if( magic!=SQLITE_MAGIC_OPEN &&
+ magic!=SQLITE_MAGIC_BUSY ) return 0;
+ return 1;
+}
+SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){
+ int magic;
+ if( db==0 ) return 0;
+ magic = db->magic;
+ if( magic!=SQLITE_MAGIC_SICK &&
+ magic!=SQLITE_MAGIC_OPEN &&
+ magic!=SQLITE_MAGIC_BUSY ) return 0;
+ return 1;
+}
+
+/************** End of util.c ************************************************/
+/************** Begin file hash.c ********************************************/
+/*
+** 2001 September 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the implementation of generic hash-tables
+** used in SQLite.
+**
+** $Id: hash.c,v 1.28 2008/05/13 13:27:34 drh Exp $
+*/
+
+/* Turn bulk memory into a hash table object by initializing the
+** fields of the Hash structure.
+**
+** "pNew" is a pointer to the hash table that is to be initialized.
+** keyClass is one of the constants SQLITE_HASH_INT, SQLITE_HASH_POINTER,
+** SQLITE_HASH_BINARY, or SQLITE_HASH_STRING. The value of keyClass
+** determines what kind of key the hash table will use. "copyKey" is
+** true if the hash table should make its own private copy of keys and
+** false if it should just use the supplied pointer. CopyKey only makes
+** sense for SQLITE_HASH_STRING and SQLITE_HASH_BINARY and is ignored
+** for other key classes.
+*/
+SQLITE_PRIVATE void sqlite3HashInit(Hash *pNew, int keyClass, int copyKey){
+ assert( pNew!=0 );
+ assert( keyClass>=SQLITE_HASH_STRING && keyClass<=SQLITE_HASH_BINARY );
+ pNew->keyClass = keyClass;
+#if 0
+ if( keyClass==SQLITE_HASH_POINTER || keyClass==SQLITE_HASH_INT ) copyKey = 0;
+#endif
+ pNew->copyKey = copyKey;
+ pNew->first = 0;
+ pNew->count = 0;
+ pNew->htsize = 0;
+ pNew->ht = 0;
+}
+
+/* Remove all entries from a hash table. Reclaim all memory.
+** Call this routine to delete a hash table or to reset a hash table
+** to the empty state.
+*/
+SQLITE_PRIVATE void sqlite3HashClear(Hash *pH){
+ HashElem *elem; /* For looping over all elements of the table */
+
+ assert( pH!=0 );
+ elem = pH->first;
+ pH->first = 0;
+ sqlite3_free(pH->ht);
+ pH->ht = 0;
+ pH->htsize = 0;
+ while( elem ){
+ HashElem *next_elem = elem->next;
+ if( pH->copyKey && elem->pKey ){
+ sqlite3_free(elem->pKey);
+ }
+ sqlite3_free(elem);
+ elem = next_elem;
+ }
+ pH->count = 0;
+}
+
+#if 0 /* NOT USED */
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_INT
+*/
+static int intHash(const void *pKey, int nKey){
+ return nKey ^ (nKey<<8) ^ (nKey>>8);
+}
+static int intCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ return n2 - n1;
+}
+#endif
+
+#if 0 /* NOT USED */
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_POINTER
+*/
+static int ptrHash(const void *pKey, int nKey){
+ uptr x = Addr(pKey);
+ return x ^ (x<<8) ^ (x>>8);
+}
+static int ptrCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( pKey1==pKey2 ) return 0;
+ if( pKey1<pKey2 ) return -1;
+ return 1;
+}
+#endif
+
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_STRING
+*/
+static int strHash(const void *pKey, int nKey){
+ const char *z = (const char *)pKey;
+ int h = 0;
+ if( nKey<=0 ) nKey = strlen(z);
+ while( nKey > 0 ){
+ h = (h<<3) ^ h ^ sqlite3UpperToLower[(unsigned char)*z++];
+ nKey--;
+ }
+ return h & 0x7fffffff;
+}
+static int strCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( n1!=n2 ) return 1;
+ return sqlite3StrNICmp((const char*)pKey1,(const char*)pKey2,n1);
+}
+
+/*
+** Hash and comparison functions when the mode is SQLITE_HASH_BINARY
+*/
+static int binHash(const void *pKey, int nKey){
+ int h = 0;
+ const char *z = (const char *)pKey;
+ while( nKey-- > 0 ){
+ h = (h<<3) ^ h ^ *(z++);
+ }
+ return h & 0x7fffffff;
+}
+static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( n1!=n2 ) return 1;
+ return memcmp(pKey1,pKey2,n1);
+}
+
+/*
+** Return a pointer to the appropriate hash function given the key class.
+**
+** The C syntax in this function definition may be unfamilar to some
+** programmers, so we provide the following additional explanation:
+**
+** The name of the function is "hashFunction". The function takes a
+** single parameter "keyClass". The return value of hashFunction()
+** is a pointer to another function. Specifically, the return value
+** of hashFunction() is a pointer to a function that takes two parameters
+** with types "const void*" and "int" and returns an "int".
+*/
+static int (*hashFunction(int keyClass))(const void*,int){
+#if 0 /* HASH_INT and HASH_POINTER are never used */
+ switch( keyClass ){
+ case SQLITE_HASH_INT: return &intHash;
+ case SQLITE_HASH_POINTER: return &ptrHash;
+ case SQLITE_HASH_STRING: return &strHash;
+ case SQLITE_HASH_BINARY: return &binHash;;
+ default: break;
+ }
+ return 0;
+#else
+ if( keyClass==SQLITE_HASH_STRING ){
+ return &strHash;
+ }else{
+ assert( keyClass==SQLITE_HASH_BINARY );
+ return &binHash;
+ }
+#endif
+}
+
+/*
+** Return a pointer to the appropriate hash function given the key class.
+**
+** For help in interpreted the obscure C code in the function definition,
+** see the header comment on the previous function.
+*/
+static int (*compareFunction(int keyClass))(const void*,int,const void*,int){
+#if 0 /* HASH_INT and HASH_POINTER are never used */
+ switch( keyClass ){
+ case SQLITE_HASH_INT: return &intCompare;
+ case SQLITE_HASH_POINTER: return &ptrCompare;
+ case SQLITE_HASH_STRING: return &strCompare;
+ case SQLITE_HASH_BINARY: return &binCompare;
+ default: break;
+ }
+ return 0;
+#else
+ if( keyClass==SQLITE_HASH_STRING ){
+ return &strCompare;
+ }else{
+ assert( keyClass==SQLITE_HASH_BINARY );
+ return &binCompare;
+ }
+#endif
+}
+
+/* Link an element into the hash table
+*/
+static void insertElement(
+ Hash *pH, /* The complete hash table */
+ struct _ht *pEntry, /* The entry into which pNew is inserted */
+ HashElem *pNew /* The element to be inserted */
+){
+ HashElem *pHead; /* First element already in pEntry */
+ pHead = pEntry->chain;
+ if( pHead ){
+ pNew->next = pHead;
+ pNew->prev = pHead->prev;
+ if( pHead->prev ){ pHead->prev->next = pNew; }
+ else { pH->first = pNew; }
+ pHead->prev = pNew;
+ }else{
+ pNew->next = pH->first;
+ if( pH->first ){ pH->first->prev = pNew; }
+ pNew->prev = 0;
+ pH->first = pNew;
+ }
+ pEntry->count++;
+ pEntry->chain = pNew;
+}
+
+
+/* Resize the hash table so that it cantains "new_size" buckets.
+** "new_size" must be a power of 2. The hash table might fail
+** to resize if sqlite3_malloc() fails.
+*/
+static void rehash(Hash *pH, int new_size){
+ struct _ht *new_ht; /* The new hash table */
+ HashElem *elem, *next_elem; /* For looping over existing elements */
+ int (*xHash)(const void*,int); /* The hash function */
+
+#ifdef SQLITE_MALLOC_SOFT_LIMIT
+ if( new_size*sizeof(struct _ht)>SQLITE_MALLOC_SOFT_LIMIT ){
+ new_size = SQLITE_MALLOC_SOFT_LIMIT/sizeof(struct _ht);
+ }
+ if( new_size==pH->htsize ) return;
+#endif
+
+ /* There is a call to sqlite3_malloc() inside rehash(). If there is
+ ** already an allocation at pH->ht, then if this malloc() fails it
+ ** is benign (since failing to resize a hash table is a performance
+ ** hit only, not a fatal error).
+ */
+ if( pH->htsize>0 ) sqlite3FaultBeginBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ new_ht = (struct _ht *)sqlite3MallocZero( new_size*sizeof(struct _ht) );
+ if( pH->htsize>0 ) sqlite3FaultEndBenign(SQLITE_FAULTINJECTOR_MALLOC);
+
+ if( new_ht==0 ) return;
+ sqlite3_free(pH->ht);
+ pH->ht = new_ht;
+ pH->htsize = new_size;
+ xHash = hashFunction(pH->keyClass);
+ for(elem=pH->first, pH->first=0; elem; elem = next_elem){
+ int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1);
+ next_elem = elem->next;
+ insertElement(pH, &new_ht[h], elem);
+ }
+}
+
+/* This function (for internal use only) locates an element in an
+** hash table that matches the given key. The hash for this key has
+** already been computed and is passed as the 4th parameter.
+*/
+static HashElem *findElementGivenHash(
+ const Hash *pH, /* The pH to be searched */
+ const void *pKey, /* The key we are searching for */
+ int nKey,
+ int h /* The hash for this key. */
+){
+ HashElem *elem; /* Used to loop thru the element list */
+ int count; /* Number of elements left to test */
+ int (*xCompare)(const void*,int,const void*,int); /* comparison function */
+
+ if( pH->ht ){
+ struct _ht *pEntry = &pH->ht[h];
+ elem = pEntry->chain;
+ count = pEntry->count;
+ xCompare = compareFunction(pH->keyClass);
+ while( count-- && elem ){
+ if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){
+ return elem;
+ }
+ elem = elem->next;
+ }
+ }
+ return 0;
+}
+
+/* Remove a single entry from the hash table given a pointer to that
+** element and a hash on the element's key.
+*/
+static void removeElementGivenHash(
+ Hash *pH, /* The pH containing "elem" */
+ HashElem* elem, /* The element to be removed from the pH */
+ int h /* Hash value for the element */
+){
+ struct _ht *pEntry;
+ if( elem->prev ){
+ elem->prev->next = elem->next;
+ }else{
+ pH->first = elem->next;
+ }
+ if( elem->next ){
+ elem->next->prev = elem->prev;
+ }
+ pEntry = &pH->ht[h];
+ if( pEntry->chain==elem ){
+ pEntry->chain = elem->next;
+ }
+ pEntry->count--;
+ if( pEntry->count<=0 ){
+ pEntry->chain = 0;
+ }
+ if( pH->copyKey ){
+ sqlite3_free(elem->pKey);
+ }
+ sqlite3_free( elem );
+ pH->count--;
+ if( pH->count<=0 ){
+ assert( pH->first==0 );
+ assert( pH->count==0 );
+ sqlite3HashClear(pH);
+ }
+}
+
+/* Attempt to locate an element of the hash table pH with a key
+** that matches pKey,nKey. Return a pointer to the corresponding
+** HashElem structure for this element if it is found, or NULL
+** otherwise.
+*/
+SQLITE_PRIVATE HashElem *sqlite3HashFindElem(const Hash *pH, const void *pKey, int nKey){
+ int h; /* A hash on key */
+ HashElem *elem; /* The element that matches key */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ if( pH==0 || pH->ht==0 ) return 0;
+ xHash = hashFunction(pH->keyClass);
+ assert( xHash!=0 );
+ h = (*xHash)(pKey,nKey);
+ elem = findElementGivenHash(pH,pKey,nKey, h % pH->htsize);
+ return elem;
+}
+
+/* Attempt to locate an element of the hash table pH with a key
+** that matches pKey,nKey. Return the data for this element if it is
+** found, or NULL if there is no match.
+*/
+SQLITE_PRIVATE void *sqlite3HashFind(const Hash *pH, const void *pKey, int nKey){
+ HashElem *elem; /* The element that matches key */
+ elem = sqlite3HashFindElem(pH, pKey, nKey);
+ return elem ? elem->data : 0;
+}
+
+/* Insert an element into the hash table pH. The key is pKey,nKey
+** and the data is "data".
+**
+** If no element exists with a matching key, then a new
+** element is created. A copy of the key is made if the copyKey
+** flag is set. NULL is returned.
+**
+** If another element already exists with the same key, then the
+** new data replaces the old data and the old data is returned.
+** The key is not copied in this instance. If a malloc fails, then
+** the new data is returned and the hash table is unchanged.
+**
+** If the "data" parameter to this function is NULL, then the
+** element corresponding to "key" is removed from the hash table.
+*/
+SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const void *pKey, int nKey, void *data){
+ int hraw; /* Raw hash value of the key */
+ int h; /* the hash of the key modulo hash table size */
+ HashElem *elem; /* Used to loop thru the element list */
+ HashElem *new_elem; /* New element added to the pH */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ assert( pH!=0 );
+ xHash = hashFunction(pH->keyClass);
+ assert( xHash!=0 );
+ hraw = (*xHash)(pKey, nKey);
+ if( pH->htsize ){
+ h = hraw % pH->htsize;
+ elem = findElementGivenHash(pH,pKey,nKey,h);
+ if( elem ){
+ void *old_data = elem->data;
+ if( data==0 ){
+ removeElementGivenHash(pH,elem,h);
+ }else{
+ elem->data = data;
+ if( !pH->copyKey ){
+ elem->pKey = (void *)pKey;
+ }
+ assert(nKey==elem->nKey);
+ }
+ return old_data;
+ }
+ }
+ if( data==0 ) return 0;
+ new_elem = (HashElem*)sqlite3_malloc( sizeof(HashElem) );
+ if( new_elem==0 ) return data;
+ if( pH->copyKey && pKey!=0 ){
+ new_elem->pKey = sqlite3_malloc( nKey );
+ if( new_elem->pKey==0 ){
+ sqlite3_free(new_elem);
+ return data;
+ }
+ memcpy((void*)new_elem->pKey, pKey, nKey);
+ }else{
+ new_elem->pKey = (void*)pKey;
+ }
+ new_elem->nKey = nKey;
+ pH->count++;
+ if( pH->htsize==0 ){
+ rehash(pH, 128/sizeof(pH->ht[0]));
+ if( pH->htsize==0 ){
+ pH->count = 0;
+ if( pH->copyKey ){
+ sqlite3_free(new_elem->pKey);
+ }
+ sqlite3_free(new_elem);
+ return data;
+ }
+ }
+ if( pH->count > pH->htsize ){
+ rehash(pH,pH->htsize*2);
+ }
+ assert( pH->htsize>0 );
+ h = hraw % pH->htsize;
+ insertElement(pH, &pH->ht[h], new_elem);
+ new_elem->data = data;
+ return 0;
+}
+
+/************** End of hash.c ************************************************/
+/************** Begin file opcodes.c *****************************************/
+/* Automatically generated. Do not edit */
+/* See the mkopcodec.awk script for details. */
+#if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG)
+SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
+ static const char *const azName[] = { "?",
+ /* 1 */ "VNext",
+ /* 2 */ "Affinity",
+ /* 3 */ "Column",
+ /* 4 */ "SetCookie",
+ /* 5 */ "Sequence",
+ /* 6 */ "MoveGt",
+ /* 7 */ "RowKey",
+ /* 8 */ "SCopy",
+ /* 9 */ "OpenWrite",
+ /* 10 */ "If",
+ /* 11 */ "VRowid",
+ /* 12 */ "CollSeq",
+ /* 13 */ "OpenRead",
+ /* 14 */ "Expire",
+ /* 15 */ "AutoCommit",
+ /* 16 */ "Not",
+ /* 17 */ "IntegrityCk",
+ /* 18 */ "Sort",
+ /* 19 */ "Copy",
+ /* 20 */ "Trace",
+ /* 21 */ "Function",
+ /* 22 */ "IfNeg",
+ /* 23 */ "Noop",
+ /* 24 */ "Return",
+ /* 25 */ "NewRowid",
+ /* 26 */ "Variable",
+ /* 27 */ "String",
+ /* 28 */ "RealAffinity",
+ /* 29 */ "VRename",
+ /* 30 */ "ParseSchema",
+ /* 31 */ "VOpen",
+ /* 32 */ "Close",
+ /* 33 */ "CreateIndex",
+ /* 34 */ "IsUnique",
+ /* 35 */ "NotFound",
+ /* 36 */ "Int64",
+ /* 37 */ "MustBeInt",
+ /* 38 */ "Halt",
+ /* 39 */ "Rowid",
+ /* 40 */ "IdxLT",
+ /* 41 */ "AddImm",
+ /* 42 */ "Statement",
+ /* 43 */ "RowData",
+ /* 44 */ "MemMax",
+ /* 45 */ "NotExists",
+ /* 46 */ "Gosub",
+ /* 47 */ "Integer",
+ /* 48 */ "Prev",
+ /* 49 */ "VColumn",
+ /* 50 */ "CreateTable",
+ /* 51 */ "Last",
+ /* 52 */ "IncrVacuum",
+ /* 53 */ "IdxRowid",
+ /* 54 */ "ResetCount",
+ /* 55 */ "FifoWrite",
+ /* 56 */ "ContextPush",
+ /* 57 */ "DropTrigger",
+ /* 58 */ "DropIndex",
+ /* 59 */ "IdxGE",
+ /* 60 */ "Or",
+ /* 61 */ "And",
+ /* 62 */ "IdxDelete",
+ /* 63 */ "Vacuum",
+ /* 64 */ "MoveLe",
+ /* 65 */ "IsNull",
+ /* 66 */ "NotNull",
+ /* 67 */ "Ne",
+ /* 68 */ "Eq",
+ /* 69 */ "Gt",
+ /* 70 */ "Le",
+ /* 71 */ "Lt",
+ /* 72 */ "Ge",
+ /* 73 */ "IfNot",
+ /* 74 */ "BitAnd",
+ /* 75 */ "BitOr",
+ /* 76 */ "ShiftLeft",
+ /* 77 */ "ShiftRight",
+ /* 78 */ "Add",
+ /* 79 */ "Subtract",
+ /* 80 */ "Multiply",
+ /* 81 */ "Divide",
+ /* 82 */ "Remainder",
+ /* 83 */ "Concat",
+ /* 84 */ "DropTable",
+ /* 85 */ "MakeRecord",
+ /* 86 */ "ResultRow",
+ /* 87 */ "BitNot",
+ /* 88 */ "String8",
+ /* 89 */ "Delete",
+ /* 90 */ "AggFinal",
+ /* 91 */ "Goto",
+ /* 92 */ "TableLock",
+ /* 93 */ "FifoRead",
+ /* 94 */ "Clear",
+ /* 95 */ "MoveLt",
+ /* 96 */ "VerifyCookie",
+ /* 97 */ "AggStep",
+ /* 98 */ "SetNumColumns",
+ /* 99 */ "Transaction",
+ /* 100 */ "VFilter",
+ /* 101 */ "VDestroy",
+ /* 102 */ "ContextPop",
+ /* 103 */ "Next",
+ /* 104 */ "IdxInsert",
+ /* 105 */ "Insert",
+ /* 106 */ "Destroy",
+ /* 107 */ "ReadCookie",
+ /* 108 */ "ForceInt",
+ /* 109 */ "LoadAnalysis",
+ /* 110 */ "Explain",
+ /* 111 */ "OpenPseudo",
+ /* 112 */ "OpenEphemeral",
+ /* 113 */ "Null",
+ /* 114 */ "Move",
+ /* 115 */ "Blob",
+ /* 116 */ "Rewind",
+ /* 117 */ "MoveGe",
+ /* 118 */ "VBegin",
+ /* 119 */ "VUpdate",
+ /* 120 */ "IfZero",
+ /* 121 */ "VCreate",
+ /* 122 */ "Found",
+ /* 123 */ "IfPos",
+ /* 124 */ "NullRow",
+ /* 125 */ "Real",
+ /* 126 */ "NotUsed_126",
+ /* 127 */ "NotUsed_127",
+ /* 128 */ "NotUsed_128",
+ /* 129 */ "NotUsed_129",
+ /* 130 */ "NotUsed_130",
+ /* 131 */ "NotUsed_131",
+ /* 132 */ "NotUsed_132",
+ /* 133 */ "NotUsed_133",
+ /* 134 */ "NotUsed_134",
+ /* 135 */ "NotUsed_135",
+ /* 136 */ "NotUsed_136",
+ /* 137 */ "NotUsed_137",
+ /* 138 */ "ToText",
+ /* 139 */ "ToBlob",
+ /* 140 */ "ToNumeric",
+ /* 141 */ "ToInt",
+ /* 142 */ "ToReal",
+ };
+ return azName[i];
+}
+#endif
+
+/************** End of opcodes.c *********************************************/
+/************** Begin file os_os2.c ******************************************/
+/*
+** 2006 Feb 14
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains code that is specific to OS/2.
+*/
+
+
+#if OS_OS2
+
+/*
+** A Note About Memory Allocation:
+**
+** This driver uses malloc()/free() directly rather than going through
+** the SQLite-wrappers sqlite3_malloc()/sqlite3_free(). Those wrappers
+** are designed for use on embedded systems where memory is scarce and
+** malloc failures happen frequently. OS/2 does not typically run on
+** embedded systems, and when it does the developers normally have bigger
+** problems to worry about than running out of memory. So there is not
+** a compelling need to use the wrappers.
+**
+** But there is a good reason to not use the wrappers. If we use the
+** wrappers then we will get simulated malloc() failures within this
+** driver. And that causes all kinds of problems for our tests. We
+** could enhance SQLite to deal with simulated malloc failures within
+** the OS driver, but the code to deal with those failure would not
+** be exercised on Linux (which does not need to malloc() in the driver)
+** and so we would have difficulty writing coverage tests for that
+** code. Better to leave the code out, we think.
+**
+** The point of this discussion is as follows: When creating a new
+** OS layer for an embedded system, if you use this file as an example,
+** avoid the use of malloc()/free(). Those routines work ok on OS/2
+** desktops but not so well in embedded systems.
+*/
+
+/*
+** Macros used to determine whether or not to use threads.
+*/
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE
+# define SQLITE_OS2_THREADS 1
+#endif
+
+/*
+** Include code that is common to all os_*.c files
+*/
+/************** Include os_common.h in the middle of os_os2.c ****************/
+/************** Begin file os_common.h ***************************************/
+/*
+** 2004 May 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains macros and a little bit of code that is common to
+** all of the platform-specific files (os_*.c) and is #included into those
+** files.
+**
+** This file should be #included by the os_*.c files only. It is not a
+** general purpose header file.
+*/
+
+/*
+** At least two bugs have slipped in because we changed the MEMORY_DEBUG
+** macro to SQLITE_DEBUG and some older makefiles have not yet made the
+** switch. The following code should catch this problem at compile-time.
+*/
+#ifdef MEMORY_DEBUG
+# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead."
+#endif
+
+
+/*
+ * When testing, this global variable stores the location of the
+ * pending-byte in the database file.
+ */
+#ifdef SQLITE_TEST
+SQLITE_API unsigned int sqlite3_pending_byte = 0x40000000;
+#endif
+
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3OSTrace = 0;
+#define OSTRACE1(X) if( sqlite3OSTrace ) sqlite3DebugPrintf(X)
+#define OSTRACE2(X,Y) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y)
+#define OSTRACE3(X,Y,Z) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z)
+#define OSTRACE4(X,Y,Z,A) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z,A)
+#define OSTRACE5(X,Y,Z,A,B) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z,A,B)
+#define OSTRACE6(X,Y,Z,A,B,C) \
+ if(sqlite3OSTrace) sqlite3DebugPrintf(X,Y,Z,A,B,C)
+#define OSTRACE7(X,Y,Z,A,B,C,D) \
+ if(sqlite3OSTrace) sqlite3DebugPrintf(X,Y,Z,A,B,C,D)
+#else
+#define OSTRACE1(X)
+#define OSTRACE2(X,Y)
+#define OSTRACE3(X,Y,Z)
+#define OSTRACE4(X,Y,Z,A)
+#define OSTRACE5(X,Y,Z,A,B)
+#define OSTRACE6(X,Y,Z,A,B,C)
+#define OSTRACE7(X,Y,Z,A,B,C,D)
+#endif
+
+/*
+** Macros for performance tracing. Normally turned off. Only works
+** on i486 hardware.
+*/
+#ifdef SQLITE_PERFORMANCE_TRACE
+__inline__ unsigned long long int hwtime(void){
+ unsigned long long int x;
+ __asm__("rdtsc\n\t"
+ "mov %%edx, %%ecx\n\t"
+ :"=A" (x));
+ return x;
+}
+static unsigned long long int g_start;
+static unsigned int elapse;
+#define TIMER_START g_start=hwtime()
+#define TIMER_END elapse=hwtime()-g_start
+#define TIMER_ELAPSED elapse
+#else
+#define TIMER_START
+#define TIMER_END
+#define TIMER_ELAPSED 0
+#endif
+
+/*
+** If we compile with the SQLITE_TEST macro set, then the following block
+** of code will give us the ability to simulate a disk I/O error. This
+** is used for testing the I/O recovery logic.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_io_error_hit = 0; /* Total number of I/O Errors */
+SQLITE_API int sqlite3_io_error_hardhit = 0; /* Number of non-benign errors */
+SQLITE_API int sqlite3_io_error_pending = 0; /* Count down to first I/O error */
+SQLITE_API int sqlite3_io_error_persist = 0; /* True if I/O errors persist */
+SQLITE_API int sqlite3_io_error_benign = 0; /* True if errors are benign */
+SQLITE_API int sqlite3_diskfull_pending = 0;
+SQLITE_API int sqlite3_diskfull = 0;
+#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X)
+#define SimulateIOError(CODE) \
+ if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \
+ || sqlite3_io_error_pending-- == 1 ) \
+ { local_ioerr(); CODE; }
+static void local_ioerr(){
+ IOTRACE(("IOERR\n"));
+ sqlite3_io_error_hit++;
+ if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++;
+}
+#define SimulateDiskfullError(CODE) \
+ if( sqlite3_diskfull_pending ){ \
+ if( sqlite3_diskfull_pending == 1 ){ \
+ local_ioerr(); \
+ sqlite3_diskfull = 1; \
+ sqlite3_io_error_hit = 1; \
+ CODE; \
+ }else{ \
+ sqlite3_diskfull_pending--; \
+ } \
+ }
+#else
+#define SimulateIOErrorBenign(X)
+#define SimulateIOError(A)
+#define SimulateDiskfullError(A)
+#endif
+
+/*
+** When testing, keep a count of the number of open files.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_open_file_count = 0;
+#define OpenCounter(X) sqlite3_open_file_count+=(X)
+#else
+#define OpenCounter(X)
+#endif
+
+/************** End of os_common.h *******************************************/
+/************** Continuing where we left off in os_os2.c *********************/
+
+/*
+** The os2File structure is subclass of sqlite3_file specific for the OS/2
+** protability layer.
+*/
+typedef struct os2File os2File;
+struct os2File {
+ const sqlite3_io_methods *pMethod; /* Always the first entry */
+ HFILE h; /* Handle for accessing the file */
+ char* pathToDel; /* Name of file to delete on close, NULL if not */
+ unsigned char locktype; /* Type of lock currently held on this file */
+};
+
+#define LOCK_TIMEOUT 10L /* the default locking timeout */
+
+/*****************************************************************************
+** The next group of routines implement the I/O methods specified
+** by the sqlite3_io_methods object.
+******************************************************************************/
+
+/*
+** Close a file.
+*/
+int os2Close( sqlite3_file *id ){
+ APIRET rc = NO_ERROR;
+ os2File *pFile;
+ if( id && (pFile = (os2File*)id) != 0 ){
+ OSTRACE2( "CLOSE %d\n", pFile->h );
+ rc = DosClose( pFile->h );
+ pFile->locktype = NO_LOCK;
+ if( pFile->pathToDel != NULL ){
+ rc = DosForceDelete( (PSZ)pFile->pathToDel );
+ free( pFile->pathToDel );
+ pFile->pathToDel = NULL;
+ }
+ id = 0;
+ OpenCounter( -1 );
+ }
+
+ return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR;
+}
+
+/*
+** Read data from a file into a buffer. Return SQLITE_OK if all
+** bytes were read successfully and SQLITE_IOERR if anything goes
+** wrong.
+*/
+int os2Read(
+ sqlite3_file *id, /* File to read from */
+ void *pBuf, /* Write content into this buffer */
+ int amt, /* Number of bytes to read */
+ sqlite3_int64 offset /* Begin reading at this offset */
+){
+ ULONG fileLocation = 0L;
+ ULONG got;
+ os2File *pFile = (os2File*)id;
+ assert( id!=0 );
+ SimulateIOError( return SQLITE_IOERR_READ );
+ OSTRACE3( "READ %d lock=%d\n", pFile->h, pFile->locktype );
+ if( DosSetFilePtr(pFile->h, offset, FILE_BEGIN, &fileLocation) != NO_ERROR ){
+ return SQLITE_IOERR;
+ }
+ if( DosRead( pFile->h, pBuf, amt, &got ) != NO_ERROR ){
+ return SQLITE_IOERR_READ;
+ }
+ if( got == (ULONG)amt )
+ return SQLITE_OK;
+ else {
+ memset(&((char*)pBuf)[got], 0, amt-got);
+ return SQLITE_IOERR_SHORT_READ;
+ }
+}
+
+/*
+** Write data from a buffer into a file. Return SQLITE_OK on success
+** or some other error code on failure.
+*/
+int os2Write(
+ sqlite3_file *id, /* File to write into */
+ const void *pBuf, /* The bytes to be written */
+ int amt, /* Number of bytes to write */
+ sqlite3_int64 offset /* Offset into the file to begin writing at */
+){
+ ULONG fileLocation = 0L;
+ APIRET rc = NO_ERROR;
+ ULONG wrote;
+ os2File *pFile = (os2File*)id;
+ assert( id!=0 );
+ SimulateIOError( return SQLITE_IOERR_WRITE );
+ SimulateDiskfullError( return SQLITE_FULL );
+ OSTRACE3( "WRITE %d lock=%d\n", pFile->h, pFile->locktype );
+ if( DosSetFilePtr(pFile->h, offset, FILE_BEGIN, &fileLocation) != NO_ERROR ){
+ return SQLITE_IOERR;
+ }
+ assert( amt>0 );
+ while( amt > 0 &&
+ ( rc = DosWrite( pFile->h, (PVOID)pBuf, amt, &wrote ) ) == NO_ERROR &&
+ wrote > 0
+ ){
+ amt -= wrote;
+ pBuf = &((char*)pBuf)[wrote];
+ }
+
+ return ( rc != NO_ERROR || amt > (int)wrote ) ? SQLITE_FULL : SQLITE_OK;
+}
+
+/*
+** Truncate an open file to a specified size
+*/
+int os2Truncate( sqlite3_file *id, i64 nByte ){
+ APIRET rc = NO_ERROR;
+ os2File *pFile = (os2File*)id;
+ OSTRACE3( "TRUNCATE %d %lld\n", pFile->h, nByte );
+ SimulateIOError( return SQLITE_IOERR_TRUNCATE );
+ rc = DosSetFileSize( pFile->h, nByte );
+ return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR;
+}
+
+#ifdef SQLITE_TEST
+/*
+** Count the number of fullsyncs and normal syncs. This is used to test
+** that syncs and fullsyncs are occuring at the right times.
+*/
+SQLITE_API int sqlite3_sync_count = 0;
+SQLITE_API int sqlite3_fullsync_count = 0;
+#endif
+
+/*
+** Make sure all writes to a particular file are committed to disk.
+*/
+int os2Sync( sqlite3_file *id, int flags ){
+ os2File *pFile = (os2File*)id;
+ OSTRACE3( "SYNC %d lock=%d\n", pFile->h, pFile->locktype );
+#ifdef SQLITE_TEST
+ if( flags & SQLITE_SYNC_FULL){
+ sqlite3_fullsync_count++;
+ }
+ sqlite3_sync_count++;
+#endif
+ return DosResetBuffer( pFile->h ) == NO_ERROR ? SQLITE_OK : SQLITE_IOERR;
+}
+
+/*
+** Determine the current size of a file in bytes
+*/
+int os2FileSize( sqlite3_file *id, sqlite3_int64 *pSize ){
+ APIRET rc = NO_ERROR;
+ FILESTATUS3 fsts3FileInfo;
+ memset(&fsts3FileInfo, 0, sizeof(fsts3FileInfo));
+ assert( id!=0 );
+ SimulateIOError( return SQLITE_IOERR );
+ rc = DosQueryFileInfo( ((os2File*)id)->h, FIL_STANDARD, &fsts3FileInfo, sizeof(FILESTATUS3) );
+ if( rc == NO_ERROR ){
+ *pSize = fsts3FileInfo.cbFile;
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+}
+
+/*
+** Acquire a reader lock.
+*/
+static int getReadLock( os2File *pFile ){
+ FILELOCK LockArea,
+ UnlockArea;
+ APIRET res;
+ memset(&LockArea, 0, sizeof(LockArea));
+ memset(&UnlockArea, 0, sizeof(UnlockArea));
+ LockArea.lOffset = SHARED_FIRST;
+ LockArea.lRange = SHARED_SIZE;
+ UnlockArea.lOffset = 0L;
+ UnlockArea.lRange = 0L;
+ res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 1L );
+ OSTRACE3( "GETREADLOCK %d res=%d\n", pFile->h, res );
+ return res;
+}
+
+/*
+** Undo a readlock
+*/
+static int unlockReadLock( os2File *id ){
+ FILELOCK LockArea,
+ UnlockArea;
+ APIRET res;
+ memset(&LockArea, 0, sizeof(LockArea));
+ memset(&UnlockArea, 0, sizeof(UnlockArea));
+ LockArea.lOffset = 0L;
+ LockArea.lRange = 0L;
+ UnlockArea.lOffset = SHARED_FIRST;
+ UnlockArea.lRange = SHARED_SIZE;
+ res = DosSetFileLocks( id->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 1L );
+ OSTRACE3( "UNLOCK-READLOCK file handle=%d res=%d?\n", id->h, res );
+ return res;
+}
+
+/*
+** Lock the file with the lock specified by parameter locktype - one
+** of the following:
+**
+** (1) SHARED_LOCK
+** (2) RESERVED_LOCK
+** (3) PENDING_LOCK
+** (4) EXCLUSIVE_LOCK
+**
+** Sometimes when requesting one lock state, additional lock states
+** are inserted in between. The locking might fail on one of the later
+** transitions leaving the lock state different from what it started but
+** still short of its goal. The following chart shows the allowed
+** transitions and the inserted intermediate states:
+**
+** UNLOCKED -> SHARED
+** SHARED -> RESERVED
+** SHARED -> (PENDING) -> EXCLUSIVE
+** RESERVED -> (PENDING) -> EXCLUSIVE
+** PENDING -> EXCLUSIVE
+**
+** This routine will only increase a lock. The os2Unlock() routine
+** erases all locks at once and returns us immediately to locking level 0.
+** It is not possible to lower the locking level one step at a time. You
+** must go straight to locking level 0.
+*/
+int os2Lock( sqlite3_file *id, int locktype ){
+ int rc = SQLITE_OK; /* Return code from subroutines */
+ APIRET res = NO_ERROR; /* Result of an OS/2 lock call */
+ int newLocktype; /* Set pFile->locktype to this value before exiting */
+ int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */
+ FILELOCK LockArea,
+ UnlockArea;
+ os2File *pFile = (os2File*)id;
+ memset(&LockArea, 0, sizeof(LockArea));
+ memset(&UnlockArea, 0, sizeof(UnlockArea));
+ assert( pFile!=0 );
+ OSTRACE4( "LOCK %d %d was %d\n", pFile->h, locktype, pFile->locktype );
+
+ /* If there is already a lock of this type or more restrictive on the
+ ** os2File, do nothing. Don't use the end_lock: exit path, as
+ ** sqlite3_mutex_enter() hasn't been called yet.
+ */
+ if( pFile->locktype>=locktype ){
+ OSTRACE3( "LOCK %d %d ok (already held)\n", pFile->h, locktype );
+ return SQLITE_OK;
+ }
+
+ /* Make sure the locking sequence is correct
+ */
+ assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK );
+ assert( locktype!=PENDING_LOCK );
+ assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK );
+
+ /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or
+ ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of
+ ** the PENDING_LOCK byte is temporary.
+ */
+ newLocktype = pFile->locktype;
+ if( pFile->locktype==NO_LOCK
+ || (locktype==EXCLUSIVE_LOCK && pFile->locktype==RESERVED_LOCK)
+ ){
+ LockArea.lOffset = PENDING_BYTE;
+ LockArea.lRange = 1L;
+ UnlockArea.lOffset = 0L;
+ UnlockArea.lRange = 0L;
+
+ /* wait longer than LOCK_TIMEOUT here not to have to try multiple times */
+ res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, 100L, 0L );
+ if( res == NO_ERROR ){
+ gotPendingLock = 1;
+ OSTRACE3( "LOCK %d pending lock boolean set. res=%d\n", pFile->h, res );
+ }
+ }
+
+ /* Acquire a shared lock
+ */
+ if( locktype==SHARED_LOCK && res == NO_ERROR ){
+ assert( pFile->locktype==NO_LOCK );
+ res = getReadLock(pFile);
+ if( res == NO_ERROR ){
+ newLocktype = SHARED_LOCK;
+ }
+ OSTRACE3( "LOCK %d acquire shared lock. res=%d\n", pFile->h, res );
+ }
+
+ /* Acquire a RESERVED lock
+ */
+ if( locktype==RESERVED_LOCK && res == NO_ERROR ){
+ assert( pFile->locktype==SHARED_LOCK );
+ LockArea.lOffset = RESERVED_BYTE;
+ LockArea.lRange = 1L;
+ UnlockArea.lOffset = 0L;
+ UnlockArea.lRange = 0L;
+ res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ if( res == NO_ERROR ){
+ newLocktype = RESERVED_LOCK;
+ }
+ OSTRACE3( "LOCK %d acquire reserved lock. res=%d\n", pFile->h, res );
+ }
+
+ /* Acquire a PENDING lock
+ */
+ if( locktype==EXCLUSIVE_LOCK && res == NO_ERROR ){
+ newLocktype = PENDING_LOCK;
+ gotPendingLock = 0;
+ OSTRACE2( "LOCK %d acquire pending lock. pending lock boolean unset.\n", pFile->h );
+ }
+
+ /* Acquire an EXCLUSIVE lock
+ */
+ if( locktype==EXCLUSIVE_LOCK && res == NO_ERROR ){
+ assert( pFile->locktype>=SHARED_LOCK );
+ res = unlockReadLock(pFile);
+ OSTRACE2( "unreadlock = %d\n", res );
+ LockArea.lOffset = SHARED_FIRST;
+ LockArea.lRange = SHARED_SIZE;
+ UnlockArea.lOffset = 0L;
+ UnlockArea.lRange = 0L;
+ res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ if( res == NO_ERROR ){
+ newLocktype = EXCLUSIVE_LOCK;
+ }else{
+ OSTRACE2( "OS/2 error-code = %d\n", res );
+ getReadLock(pFile);
+ }
+ OSTRACE3( "LOCK %d acquire exclusive lock. res=%d\n", pFile->h, res );
+ }
+
+ /* If we are holding a PENDING lock that ought to be released, then
+ ** release it now.
+ */
+ if( gotPendingLock && locktype==SHARED_LOCK ){
+ int r;
+ LockArea.lOffset = 0L;
+ LockArea.lRange = 0L;
+ UnlockArea.lOffset = PENDING_BYTE;
+ UnlockArea.lRange = 1L;
+ r = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ OSTRACE3( "LOCK %d unlocking pending/is shared. r=%d\n", pFile->h, r );
+ }
+
+ /* Update the state of the lock has held in the file descriptor then
+ ** return the appropriate result code.
+ */
+ if( res == NO_ERROR ){
+ rc = SQLITE_OK;
+ }else{
+ OSTRACE4( "LOCK FAILED %d trying for %d but got %d\n", pFile->h,
+ locktype, newLocktype );
+ rc = SQLITE_BUSY;
+ }
+ pFile->locktype = newLocktype;
+ OSTRACE3( "LOCK %d now %d\n", pFile->h, pFile->locktype );
+ return rc;
+}
+
+/*
+** This routine checks if there is a RESERVED lock held on the specified
+** file by this or any other process. If such a lock is held, return
+** non-zero, otherwise zero.
+*/
+int os2CheckReservedLock( sqlite3_file *id ){
+ int r = 0;
+ os2File *pFile = (os2File*)id;
+ assert( pFile!=0 );
+ if( pFile->locktype>=RESERVED_LOCK ){
+ r = 1;
+ OSTRACE3( "TEST WR-LOCK %d %d (local)\n", pFile->h, r );
+ }else{
+ FILELOCK LockArea,
+ UnlockArea;
+ APIRET rc = NO_ERROR;
+ memset(&LockArea, 0, sizeof(LockArea));
+ memset(&UnlockArea, 0, sizeof(UnlockArea));
+ LockArea.lOffset = RESERVED_BYTE;
+ LockArea.lRange = 1L;
+ UnlockArea.lOffset = 0L;
+ UnlockArea.lRange = 0L;
+ rc = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ OSTRACE3( "TEST WR-LOCK %d lock reserved byte rc=%d\n", pFile->h, rc );
+ if( rc == NO_ERROR ){
+ APIRET rcu = NO_ERROR; /* return code for unlocking */
+ LockArea.lOffset = 0L;
+ LockArea.lRange = 0L;
+ UnlockArea.lOffset = RESERVED_BYTE;
+ UnlockArea.lRange = 1L;
+ rcu = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ OSTRACE3( "TEST WR-LOCK %d unlock reserved byte r=%d\n", pFile->h, rcu );
+ }
+ r = !(rc == NO_ERROR);
+ OSTRACE3( "TEST WR-LOCK %d %d (remote)\n", pFile->h, r );
+ }
+ return r;
+}
+
+/*
+** Lower the locking level on file descriptor id to locktype. locktype
+** must be either NO_LOCK or SHARED_LOCK.
+**
+** If the locking level of the file descriptor is already at or below
+** the requested locking level, this routine is a no-op.
+**
+** It is not possible for this routine to fail if the second argument
+** is NO_LOCK. If the second argument is SHARED_LOCK then this routine
+** might return SQLITE_IOERR;
+*/
+int os2Unlock( sqlite3_file *id, int locktype ){
+ int type;
+ os2File *pFile = (os2File*)id;
+ APIRET rc = SQLITE_OK;
+ APIRET res = NO_ERROR;
+ FILELOCK LockArea,
+ UnlockArea;
+ memset(&LockArea, 0, sizeof(LockArea));
+ memset(&UnlockArea, 0, sizeof(UnlockArea));
+ assert( pFile!=0 );
+ assert( locktype<=SHARED_LOCK );
+ OSTRACE4( "UNLOCK %d to %d was %d\n", pFile->h, locktype, pFile->locktype );
+ type = pFile->locktype;
+ if( type>=EXCLUSIVE_LOCK ){
+ LockArea.lOffset = 0L;
+ LockArea.lRange = 0L;
+ UnlockArea.lOffset = SHARED_FIRST;
+ UnlockArea.lRange = SHARED_SIZE;
+ res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ OSTRACE3( "UNLOCK %d exclusive lock res=%d\n", pFile->h, res );
+ if( locktype==SHARED_LOCK && getReadLock(pFile) != NO_ERROR ){
+ /* This should never happen. We should always be able to
+ ** reacquire the read lock */
+ OSTRACE3( "UNLOCK %d to %d getReadLock() failed\n", pFile->h, locktype );
+ rc = SQLITE_IOERR_UNLOCK;
+ }
+ }
+ if( type>=RESERVED_LOCK ){
+ LockArea.lOffset = 0L;
+ LockArea.lRange = 0L;
+ UnlockArea.lOffset = RESERVED_BYTE;
+ UnlockArea.lRange = 1L;
+ res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ OSTRACE3( "UNLOCK %d reserved res=%d\n", pFile->h, res );
+ }
+ if( locktype==NO_LOCK && type>=SHARED_LOCK ){
+ res = unlockReadLock(pFile);
+ OSTRACE5( "UNLOCK %d is %d want %d res=%d\n", pFile->h, type, locktype, res );
+ }
+ if( type>=PENDING_LOCK ){
+ LockArea.lOffset = 0L;
+ LockArea.lRange = 0L;
+ UnlockArea.lOffset = PENDING_BYTE;
+ UnlockArea.lRange = 1L;
+ res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L );
+ OSTRACE3( "UNLOCK %d pending res=%d\n", pFile->h, res );
+ }
+ pFile->locktype = locktype;
+ OSTRACE3( "UNLOCK %d now %d\n", pFile->h, pFile->locktype );
+ return rc;
+}
+
+/*
+** Control and query of the open file handle.
+*/
+static int os2FileControl(sqlite3_file *id, int op, void *pArg){
+ switch( op ){
+ case SQLITE_FCNTL_LOCKSTATE: {
+ *(int*)pArg = ((os2File*)id)->locktype;
+ OSTRACE3( "FCNTL_LOCKSTATE %d lock=%d\n", ((os2File*)id)->h, ((os2File*)id)->locktype );
+ return SQLITE_OK;
+ }
+ }
+ return SQLITE_ERROR;
+}
+
+/*
+** Return the sector size in bytes of the underlying block device for
+** the specified file. This is almost always 512 bytes, but may be
+** larger for some devices.
+**
+** SQLite code assumes this function cannot fail. It also assumes that
+** if two files are created in the same file-system directory (i.e.
+** a database and its journal file) that the sector size will be the
+** same for both.
+*/
+static int os2SectorSize(sqlite3_file *id){
+ return SQLITE_DEFAULT_SECTOR_SIZE;
+}
+
+/*
+** Return a vector of device characteristics.
+*/
+static int os2DeviceCharacteristics(sqlite3_file *id){
+ return 0;
+}
+
+/*
+** Helper function to convert UTF-8 filenames to local OS/2 codepage.
+** The two-step process: first convert the incoming UTF-8 string
+** into UCS-2 and then from UCS-2 to the current codepage.
+** The returned char pointer has to be freed.
+*/
+char *convertUtf8PathToCp(const char *in)
+{
+ UconvObject uconv;
+ UniChar ucsUtf8Cp[12],
+ tempPath[CCHMAXPATH];
+ char *out;
+ int rc = 0;
+
+ out = (char *)calloc(CCHMAXPATH, 1);
+
+ /* determine string for the conversion of UTF-8 which is CP1208 */
+ rc = UniMapCpToUcsCp(1208, ucsUtf8Cp, 12);
+ rc = UniCreateUconvObject(ucsUtf8Cp, &uconv);
+ rc = UniStrToUcs(uconv, tempPath, (char *)in, CCHMAXPATH);
+ rc = UniFreeUconvObject(uconv);
+
+ /* conversion for current codepage which can be used for paths */
+ rc = UniCreateUconvObject((UniChar *)L"@path=yes", &uconv);
+ rc = UniStrFromUcs(uconv, out, tempPath, CCHMAXPATH);
+ rc = UniFreeUconvObject(uconv);
+
+ return out;
+}
+
+/*
+** Helper function to convert filenames from local codepage to UTF-8.
+** The two-step process: first convert the incoming codepage-specific
+** string into UCS-2 and then from UCS-2 to the codepage of UTF-8.
+** The returned char pointer has to be freed.
+*/
+char *convertCpPathToUtf8(const char *in)
+{
+ UconvObject uconv;
+ UniChar ucsUtf8Cp[12],
+ tempPath[CCHMAXPATH];
+ char *out;
+ int rc = 0;
+
+ out = (char *)calloc(CCHMAXPATH, 1);
+
+ /* conversion for current codepage which can be used for paths */
+ rc = UniCreateUconvObject((UniChar *)L"@path=yes", &uconv);
+ rc = UniStrToUcs(uconv, tempPath, (char *)in, CCHMAXPATH);
+ rc = UniFreeUconvObject(uconv);
+
+ /* determine string for the conversion of UTF-8 which is CP1208 */
+ rc = UniMapCpToUcsCp(1208, ucsUtf8Cp, 12);
+ rc = UniCreateUconvObject(ucsUtf8Cp, &uconv);
+ rc = UniStrFromUcs(uconv, out, tempPath, CCHMAXPATH);
+ rc = UniFreeUconvObject(uconv);
+
+ return out;
+}
+
+/*
+** This vector defines all the methods that can operate on an
+** sqlite3_file for os2.
+*/
+static const sqlite3_io_methods os2IoMethod = {
+ 1, /* iVersion */
+ os2Close,
+ os2Read,
+ os2Write,
+ os2Truncate,
+ os2Sync,
+ os2FileSize,
+ os2Lock,
+ os2Unlock,
+ os2CheckReservedLock,
+ os2FileControl,
+ os2SectorSize,
+ os2DeviceCharacteristics
+};
+
+/***************************************************************************
+** Here ends the I/O methods that form the sqlite3_io_methods object.
+**
+** The next block of code implements the VFS methods.
+****************************************************************************/
+
+/*
+** Open a file.
+*/
+static int os2Open(
+ sqlite3_vfs *pVfs, /* Not used */
+ const char *zName, /* Name of the file */
+ sqlite3_file *id, /* Write the SQLite file handle here */
+ int flags, /* Open mode flags */
+ int *pOutFlags /* Status return flags */
+){
+ HFILE h;
+ ULONG ulFileAttribute = 0;
+ ULONG ulOpenFlags = 0;
+ ULONG ulOpenMode = 0;
+ os2File *pFile = (os2File*)id;
+ APIRET rc = NO_ERROR;
+ ULONG ulAction;
+
+ memset( pFile, 0, sizeof(*pFile) );
+
+ OSTRACE2( "OPEN want %d\n", flags );
+
+ //ulOpenMode = flags & SQLITE_OPEN_READWRITE ? OPEN_ACCESS_READWRITE : OPEN_ACCESS_READONLY;
+ if( flags & SQLITE_OPEN_READWRITE ){
+ ulOpenMode |= OPEN_ACCESS_READWRITE;
+ OSTRACE1( "OPEN read/write\n" );
+ }else{
+ ulOpenMode |= OPEN_ACCESS_READONLY;
+ OSTRACE1( "OPEN read only\n" );
+ }
+
+ //ulOpenFlags = flags & SQLITE_OPEN_CREATE ? OPEN_ACTION_CREATE_IF_NEW : OPEN_ACTION_FAIL_IF_NEW;
+ if( flags & SQLITE_OPEN_CREATE ){
+ ulOpenFlags |= OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW;
+ OSTRACE1( "OPEN open new/create\n" );
+ }else{
+ ulOpenFlags |= OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW;
+ OSTRACE1( "OPEN open existing\n" );
+ }
+
+ //ulOpenMode |= flags & SQLITE_OPEN_MAIN_DB ? OPEN_SHARE_DENYNONE : OPEN_SHARE_DENYWRITE;
+ if( flags & SQLITE_OPEN_MAIN_DB ){
+ ulOpenMode |= OPEN_SHARE_DENYNONE;
+ OSTRACE1( "OPEN share read/write\n" );
+ }else{
+ ulOpenMode |= OPEN_SHARE_DENYWRITE;
+ OSTRACE1( "OPEN share read only\n" );
+ }
+
+ if( flags & (SQLITE_OPEN_TEMP_DB | SQLITE_OPEN_TEMP_JOURNAL
+ | SQLITE_OPEN_SUBJOURNAL) ){
+ char pathUtf8[CCHMAXPATH];
+ //ulFileAttribute = FILE_HIDDEN; //for debugging, we want to make sure it is deleted
+ ulFileAttribute = FILE_NORMAL;
+ sqlite3OsFullPathname( pVfs, zName, CCHMAXPATH, pathUtf8 );
+ pFile->pathToDel = convertUtf8PathToCp( pathUtf8 );
+ OSTRACE1( "OPEN hidden/delete on close file attributes\n" );
+ }else{
+ ulFileAttribute = FILE_ARCHIVED | FILE_NORMAL;
+ pFile->pathToDel = NULL;
+ OSTRACE1( "OPEN normal file attribute\n" );
+ }
+
+ /* always open in random access mode for possibly better speed */
+ ulOpenMode |= OPEN_FLAGS_RANDOM;
+ ulOpenMode |= OPEN_FLAGS_FAIL_ON_ERROR;
+ ulOpenMode |= OPEN_FLAGS_NOINHERIT;
+
+ char *zNameCp = convertUtf8PathToCp( zName );
+ rc = DosOpen( (PSZ)zNameCp,
+ &h,
+ &ulAction,
+ 0L,
+ ulFileAttribute,
+ ulOpenFlags,
+ ulOpenMode,
+ (PEAOP2)NULL );
+ free( zNameCp );
+ if( rc != NO_ERROR ){
+ OSTRACE7( "OPEN Invalid handle rc=%d: zName=%s, ulAction=%#lx, ulAttr=%#lx, ulFlags=%#lx, ulMode=%#lx\n",
+ rc, zName, ulAction, ulFileAttribute, ulOpenFlags, ulOpenMode );
+ free( pFile->pathToDel );
+ pFile->pathToDel = NULL;
+ if( flags & SQLITE_OPEN_READWRITE ){
+ OSTRACE2( "OPEN %d Invalid handle\n", ((flags | SQLITE_OPEN_READONLY) & ~SQLITE_OPEN_READWRITE) );
+ return os2Open( 0, zName, id,
+ ((flags | SQLITE_OPEN_READONLY) & ~SQLITE_OPEN_READWRITE),
+ pOutFlags );
+ }else{
+ return SQLITE_CANTOPEN;
+ }
+ }
+
+ if( pOutFlags ){
+ *pOutFlags = flags & SQLITE_OPEN_READWRITE ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY;
+ }
+
+ pFile->pMethod = &os2IoMethod;
+ pFile->h = h;
+ OpenCounter(+1);
+ OSTRACE3( "OPEN %d pOutFlags=%d\n", pFile->h, pOutFlags );
+ return SQLITE_OK;
+}
+
+/*
+** Delete the named file.
+*/
+int os2Delete(
+ sqlite3_vfs *pVfs, /* Not used on os2 */
+ const char *zFilename, /* Name of file to delete */
+ int syncDir /* Not used on os2 */
+){
+ APIRET rc = NO_ERROR;
+ SimulateIOError(return SQLITE_IOERR_DELETE);
+ char *zFilenameCp = convertUtf8PathToCp( zFilename );
+ rc = DosDelete( (PSZ)zFilenameCp );
+ free( zFilenameCp );
+ OSTRACE2( "DELETE \"%s\"\n", zFilename );
+ return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR;
+}
+
+/*
+** Check the existance and status of a file.
+*/
+static int os2Access(
+ sqlite3_vfs *pVfs, /* Not used on os2 */
+ const char *zFilename, /* Name of file to check */
+ int flags /* Type of test to make on this file */
+){
+ FILESTATUS3 fsts3ConfigInfo;
+ APIRET rc = NO_ERROR;
+
+ memset( &fsts3ConfigInfo, 0, sizeof(fsts3ConfigInfo) );
+ char *zFilenameCp = convertUtf8PathToCp( zFilename );
+ rc = DosQueryPathInfo( (PSZ)zFilenameCp, FIL_STANDARD,
+ &fsts3ConfigInfo, sizeof(FILESTATUS3) );
+ free( zFilenameCp );
+ OSTRACE4( "ACCESS fsts3ConfigInfo.attrFile=%d flags=%d rc=%d\n",
+ fsts3ConfigInfo.attrFile, flags, rc );
+ switch( flags ){
+ case SQLITE_ACCESS_READ:
+ case SQLITE_ACCESS_EXISTS:
+ rc = (rc == NO_ERROR);
+ OSTRACE3( "ACCESS %s access of read and exists rc=%d\n", zFilename, rc );
+ break;
+ case SQLITE_ACCESS_READWRITE:
+ rc = (fsts3ConfigInfo.attrFile & FILE_READONLY) == 0;
+ OSTRACE3( "ACCESS %s access of read/write rc=%d\n", zFilename, rc );
+ break;
+ default:
+ assert( !"Invalid flags argument" );
+ }
+ return rc;
+}
+
+
+/*
+** Create a temporary file name in zBuf. zBuf must be big enough to
+** hold at pVfs->mxPathname characters.
+*/
+static int os2GetTempname( sqlite3_vfs *pVfs, int nBuf, char *zBuf ){
+ static const unsigned char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ char zTempPathBuf[3];
+ PSZ zTempPath = (PSZ)&zTempPathBuf;
+ char *zTempPathUTF;
+ if( DosScanEnv( (PSZ)"TEMP", &zTempPath ) ){
+ if( DosScanEnv( (PSZ)"TMP", &zTempPath ) ){
+ if( DosScanEnv( (PSZ)"TMPDIR", &zTempPath ) ){
+ ULONG ulDriveNum = 0, ulDriveMap = 0;
+ DosQueryCurrentDisk( &ulDriveNum, &ulDriveMap );
+ sprintf( (char*)zTempPath, "%c:", (char)( 'A' + ulDriveNum - 1 ) );
+ }
+ }
+ }
+ /* strip off a trailing slashes or backslashes, otherwise we would get *
+ * multiple (back)slashes which causes DosOpen() to fail */
+ j = strlen(zTempPath);
+ while( j > 0 && ( zTempPath[j-1] == '\\' || zTempPath[j-1] == '/' ) ){
+ j--;
+ }
+ zTempPath[j] = '\0';
+ zTempPathUTF = convertCpPathToUtf8( zTempPath );
+ sqlite3_snprintf( nBuf-30, zBuf,
+ "%s\\"SQLITE_TEMP_FILE_PREFIX, zTempPathUTF );
+ free( zTempPathUTF );
+ j = strlen( zBuf );
+ sqlite3_randomness( 20, &zBuf[j] );
+ for( i = 0; i < 20; i++, j++ ){
+ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
+ }
+ zBuf[j] = 0;
+ OSTRACE2( "TEMP FILENAME: %s\n", zBuf );
+ return SQLITE_OK;
+}
+
+
+/*
+** Turn a relative pathname into a full pathname. Write the full
+** pathname into zFull[]. zFull[] will be at least pVfs->mxPathname
+** bytes in size.
+*/
+static int os2FullPathname(
+ sqlite3_vfs *pVfs, /* Pointer to vfs object */
+ const char *zRelative, /* Possibly relative input path */
+ int nFull, /* Size of output buffer in bytes */
+ char *zFull /* Output buffer */
+){
+ char *zRelativeCp = convertUtf8PathToCp( zRelative );
+ char zFullCp[CCHMAXPATH];
+ char *zFullUTF;
+ APIRET rc = DosQueryPathInfo( zRelativeCp, FIL_QUERYFULLNAME, zFullCp,
+ CCHMAXPATH );
+ free( zRelativeCp );
+ zFullUTF = convertCpPathToUtf8( zFullCp );
+ sqlite3_snprintf( nFull, zFull, zFullUTF );
+ free( zFullUTF );
+ return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR;
+}
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+/*
+** Interfaces for opening a shared library, finding entry points
+** within the shared library, and closing the shared library.
+*/
+/*
+** Interfaces for opening a shared library, finding entry points
+** within the shared library, and closing the shared library.
+*/
+static void *os2DlOpen(sqlite3_vfs *pVfs, const char *zFilename){
+ UCHAR loadErr[256];
+ HMODULE hmod;
+ APIRET rc;
+ char *zFilenameCp = convertUtf8PathToCp(zFilename);
+ rc = DosLoadModule((PSZ)loadErr, sizeof(loadErr), zFilenameCp, &hmod);
+ free(zFilenameCp);
+ return rc != NO_ERROR ? 0 : (void*)hmod;
+}
+/*
+** A no-op since the error code is returned on the DosLoadModule call.
+** os2Dlopen returns zero if DosLoadModule is not successful.
+*/
+static void os2DlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){
+/* no-op */
+}
+void *os2DlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol){
+ PFN pfn;
+ APIRET rc;
+ rc = DosQueryProcAddr((HMODULE)pHandle, 0L, zSymbol, &pfn);
+ if( rc != NO_ERROR ){
+ /* if the symbol itself was not found, search again for the same
+ * symbol with an extra underscore, that might be needed depending
+ * on the calling convention */
+ char _zSymbol[256] = "_";
+ strncat(_zSymbol, zSymbol, 255);
+ rc = DosQueryProcAddr((HMODULE)pHandle, 0L, _zSymbol, &pfn);
+ }
+ return rc != NO_ERROR ? 0 : (void*)pfn;
+}
+void os2DlClose(sqlite3_vfs *pVfs, void *pHandle){
+ DosFreeModule((HMODULE)pHandle);
+}
+#else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */
+ #define os2DlOpen 0
+ #define os2DlError 0
+ #define os2DlSym 0
+ #define os2DlClose 0
+#endif
+
+
+/*
+** Write up to nBuf bytes of randomness into zBuf.
+*/
+static int os2Randomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf ){
+ ULONG sizeofULong = sizeof(ULONG);
+ int n = 0;
+ if( sizeof(DATETIME) <= nBuf - n ){
+ DATETIME x;
+ DosGetDateTime(&x);
+ memcpy(&zBuf[n], &x, sizeof(x));
+ n += sizeof(x);
+ }
+
+ if( sizeofULong <= nBuf - n ){
+ PPIB ppib;
+ DosGetInfoBlocks(NULL, &ppib);
+ memcpy(&zBuf[n], &ppib->pib_ulpid, sizeofULong);
+ n += sizeofULong;
+ }
+
+ if( sizeofULong <= nBuf - n ){
+ PTIB ptib;
+ DosGetInfoBlocks(&ptib, NULL);
+ memcpy(&zBuf[n], &ptib->tib_ptib2->tib2_ultid, sizeofULong);
+ n += sizeofULong;
+ }
+
+ /* if we still haven't filled the buffer yet the following will */
+ /* grab everything once instead of making several calls for a single item */
+ if( sizeofULong <= nBuf - n ){
+ ULONG ulSysInfo[QSV_MAX];
+ DosQuerySysInfo(1L, QSV_MAX, ulSysInfo, sizeofULong * QSV_MAX);
+
+ memcpy(&zBuf[n], &ulSysInfo[QSV_MS_COUNT - 1], sizeofULong);
+ n += sizeofULong;
+
+ if( sizeofULong <= nBuf - n ){
+ memcpy(&zBuf[n], &ulSysInfo[QSV_TIMER_INTERVAL - 1], sizeofULong);
+ n += sizeofULong;
+ }
+ if( sizeofULong <= nBuf - n ){
+ memcpy(&zBuf[n], &ulSysInfo[QSV_TIME_LOW - 1], sizeofULong);
+ n += sizeofULong;
+ }
+ if( sizeofULong <= nBuf - n ){
+ memcpy(&zBuf[n], &ulSysInfo[QSV_TIME_HIGH - 1], sizeofULong);
+ n += sizeofULong;
+ }
+ if( sizeofULong <= nBuf - n ){
+ memcpy(&zBuf[n], &ulSysInfo[QSV_TOTAVAILMEM - 1], sizeofULong);
+ n += sizeofULong;
+ }
+ }
+
+ return n;
+}
+
+/*
+** Sleep for a little while. Return the amount of time slept.
+** The argument is the number of microseconds we want to sleep.
+** The return value is the number of microseconds of sleep actually
+** requested from the underlying operating system, a number which
+** might be greater than or equal to the argument, but not less
+** than the argument.
+*/
+static int os2Sleep( sqlite3_vfs *pVfs, int microsec ){
+ DosSleep( (microsec/1000) );
+ return microsec;
+}
+
+/*
+** The following variable, if set to a non-zero value, becomes the result
+** returned from sqlite3OsCurrentTime(). This is used for testing.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_current_time = 0;
+#endif
+
+/*
+** Find the current time (in Universal Coordinated Time). Write the
+** current time and date as a Julian Day number into *prNow and
+** return 0. Return 1 if the time and date cannot be found.
+*/
+int os2CurrentTime( sqlite3_vfs *pVfs, double *prNow ){
+ double now;
+ SHORT minute; /* needs to be able to cope with negative timezone offset */
+ USHORT second, hour,
+ day, month, year;
+ DATETIME dt;
+ DosGetDateTime( &dt );
+ second = (USHORT)dt.seconds;
+ minute = (SHORT)dt.minutes + dt.timezone;
+ hour = (USHORT)dt.hours;
+ day = (USHORT)dt.day;
+ month = (USHORT)dt.month;
+ year = (USHORT)dt.year;
+
+ /* Calculations from http://www.astro.keele.ac.uk/~rno/Astronomy/hjd.html
+ http://www.astro.keele.ac.uk/~rno/Astronomy/hjd-0.1.c */
+ /* Calculate the Julian days */
+ now = day - 32076 +
+ 1461*(year + 4800 + (month - 14)/12)/4 +
+ 367*(month - 2 - (month - 14)/12*12)/12 -
+ 3*((year + 4900 + (month - 14)/12)/100)/4;
+
+ /* Add the fractional hours, mins and seconds */
+ now += (hour + 12.0)/24.0;
+ now += minute/1440.0;
+ now += second/86400.0;
+ *prNow = now;
+#ifdef SQLITE_TEST
+ if( sqlite3_current_time ){
+ *prNow = sqlite3_current_time/86400.0 + 2440587.5;
+ }
+#endif
+ return 0;
+}
+
+/*
+** Return a pointer to the sqlite3DefaultVfs structure. We use
+** a function rather than give the structure global scope because
+** some compilers (MSVC) do not allow forward declarations of
+** initialized structures.
+*/
+SQLITE_PRIVATE sqlite3_vfs *sqlite3OsDefaultVfs(void){
+ static sqlite3_vfs os2Vfs = {
+ 1, /* iVersion */
+ sizeof(os2File), /* szOsFile */
+ CCHMAXPATH, /* mxPathname */
+ 0, /* pNext */
+ "os2", /* zName */
+ 0, /* pAppData */
+
+ os2Open, /* xOpen */
+ os2Delete, /* xDelete */
+ os2Access, /* xAccess */
+ os2GetTempname, /* xGetTempname */
+ os2FullPathname, /* xFullPathname */
+ os2DlOpen, /* xDlOpen */
+ os2DlError, /* xDlError */
+ os2DlSym, /* xDlSym */
+ os2DlClose, /* xDlClose */
+ os2Randomness, /* xRandomness */
+ os2Sleep, /* xSleep */
+ os2CurrentTime /* xCurrentTime */
+ };
+
+ return &os2Vfs;
+}
+
+#endif /* OS_OS2 */
+
+/************** End of os_os2.c **********************************************/
+/************** Begin file os_unix.c *****************************************/
+/*
+** 2004 May 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains code that is specific to Unix systems.
+*/
+#if OS_UNIX /* This file is used on unix only */
+
+/* #define SQLITE_ENABLE_LOCKING_STYLE 0 */
+
+/*
+** These #defines should enable >2GB file support on Posix if the
+** underlying operating system supports it. If the OS lacks
+** large file support, these should be no-ops.
+**
+** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch
+** on the compiler command line. This is necessary if you are compiling
+** on a recent machine (ex: RedHat 7.2) but you want your code to work
+** on an older machine (ex: RedHat 6.0). If you compile on RedHat 7.2
+** without this option, LFS is enable. But LFS does not exist in the kernel
+** in RedHat 6.0, so the code won't work. Hence, for maximum binary
+** portability you should omit LFS.
+*/
+#ifndef SQLITE_DISABLE_LFS
+# define _LARGE_FILE 1
+# ifndef _FILE_OFFSET_BITS
+# define _FILE_OFFSET_BITS 64
+# endif
+# define _LARGEFILE_SOURCE 1
+#endif
+
+/*
+** standard include files.
+*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <errno.h>
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#endif /* SQLITE_ENABLE_LOCKING_STYLE */
+
+/*
+** If we are to be thread-safe, include the pthreads header and define
+** the SQLITE_UNIX_THREADS macro.
+*/
+#if SQLITE_THREADSAFE
+# define SQLITE_UNIX_THREADS 1
+#endif
+
+/*
+** Default permissions when creating a new file
+*/
+#ifndef SQLITE_DEFAULT_FILE_PERMISSIONS
+# define SQLITE_DEFAULT_FILE_PERMISSIONS 0644
+#endif
+
+/*
+** Maximum supported path-length.
+*/
+#define MAX_PATHNAME 512
+
+
+/*
+** The unixFile structure is subclass of sqlite3_file specific for the unix
+** protability layer.
+*/
+typedef struct unixFile unixFile;
+struct unixFile {
+ sqlite3_io_methods const *pMethod; /* Always the first entry */
+#ifdef SQLITE_TEST
+ /* In test mode, increase the size of this structure a bit so that
+ ** it is larger than the struct CrashFile defined in test6.c.
+ */
+ char aPadding[32];
+#endif
+ struct openCnt *pOpen; /* Info about all open fd's on this inode */
+ struct lockInfo *pLock; /* Info about locks on this inode */
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+ void *lockingContext; /* Locking style specific state */
+#endif /* SQLITE_ENABLE_LOCKING_STYLE */
+ int h; /* The file descriptor */
+ unsigned char locktype; /* The type of lock held on this fd */
+ int dirfd; /* File descriptor for the directory */
+#if SQLITE_THREADSAFE
+ pthread_t tid; /* The thread that "owns" this unixFile */
+#endif
+};
+
+/*
+** Include code that is common to all os_*.c files
+*/
+/************** Include os_common.h in the middle of os_unix.c ***************/
+/************** Begin file os_common.h ***************************************/
+/*
+** 2004 May 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains macros and a little bit of code that is common to
+** all of the platform-specific files (os_*.c) and is #included into those
+** files.
+**
+** This file should be #included by the os_*.c files only. It is not a
+** general purpose header file.
+*/
+
+/*
+** At least two bugs have slipped in because we changed the MEMORY_DEBUG
+** macro to SQLITE_DEBUG and some older makefiles have not yet made the
+** switch. The following code should catch this problem at compile-time.
+*/
+#ifdef MEMORY_DEBUG
+# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead."
+#endif
+
+
+/*
+ * When testing, this global variable stores the location of the
+ * pending-byte in the database file.
+ */
+#ifdef SQLITE_TEST
+SQLITE_API unsigned int sqlite3_pending_byte = 0x40000000;
+#endif
+
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3OSTrace = 0;
+#define OSTRACE1(X) if( sqlite3OSTrace ) sqlite3DebugPrintf(X)
+#define OSTRACE2(X,Y) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y)
+#define OSTRACE3(X,Y,Z) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z)
+#define OSTRACE4(X,Y,Z,A) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z,A)
+#define OSTRACE5(X,Y,Z,A,B) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z,A,B)
+#define OSTRACE6(X,Y,Z,A,B,C) \
+ if(sqlite3OSTrace) sqlite3DebugPrintf(X,Y,Z,A,B,C)
+#define OSTRACE7(X,Y,Z,A,B,C,D) \
+ if(sqlite3OSTrace) sqlite3DebugPrintf(X,Y,Z,A,B,C,D)
+#else
+#define OSTRACE1(X)
+#define OSTRACE2(X,Y)
+#define OSTRACE3(X,Y,Z)
+#define OSTRACE4(X,Y,Z,A)
+#define OSTRACE5(X,Y,Z,A,B)
+#define OSTRACE6(X,Y,Z,A,B,C)
+#define OSTRACE7(X,Y,Z,A,B,C,D)
+#endif
+
+/*
+** Macros for performance tracing. Normally turned off. Only works
+** on i486 hardware.
+*/
+#ifdef SQLITE_PERFORMANCE_TRACE
+__inline__ unsigned long long int hwtime(void){
+ unsigned long long int x;
+ __asm__("rdtsc\n\t"
+ "mov %%edx, %%ecx\n\t"
+ :"=A" (x));
+ return x;
+}
+static unsigned long long int g_start;
+static unsigned int elapse;
+#define TIMER_START g_start=hwtime()
+#define TIMER_END elapse=hwtime()-g_start
+#define TIMER_ELAPSED elapse
+#else
+#define TIMER_START
+#define TIMER_END
+#define TIMER_ELAPSED 0
+#endif
+
+/*
+** If we compile with the SQLITE_TEST macro set, then the following block
+** of code will give us the ability to simulate a disk I/O error. This
+** is used for testing the I/O recovery logic.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_io_error_hit = 0; /* Total number of I/O Errors */
+SQLITE_API int sqlite3_io_error_hardhit = 0; /* Number of non-benign errors */
+SQLITE_API int sqlite3_io_error_pending = 0; /* Count down to first I/O error */
+SQLITE_API int sqlite3_io_error_persist = 0; /* True if I/O errors persist */
+SQLITE_API int sqlite3_io_error_benign = 0; /* True if errors are benign */
+SQLITE_API int sqlite3_diskfull_pending = 0;
+SQLITE_API int sqlite3_diskfull = 0;
+#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X)
+#define SimulateIOError(CODE) \
+ if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \
+ || sqlite3_io_error_pending-- == 1 ) \
+ { local_ioerr(); CODE; }
+static void local_ioerr(){
+ IOTRACE(("IOERR\n"));
+ sqlite3_io_error_hit++;
+ if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++;
+}
+#define SimulateDiskfullError(CODE) \
+ if( sqlite3_diskfull_pending ){ \
+ if( sqlite3_diskfull_pending == 1 ){ \
+ local_ioerr(); \
+ sqlite3_diskfull = 1; \
+ sqlite3_io_error_hit = 1; \
+ CODE; \
+ }else{ \
+ sqlite3_diskfull_pending--; \
+ } \
+ }
+#else
+#define SimulateIOErrorBenign(X)
+#define SimulateIOError(A)
+#define SimulateDiskfullError(A)
+#endif
+
+/*
+** When testing, keep a count of the number of open files.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_open_file_count = 0;
+#define OpenCounter(X) sqlite3_open_file_count+=(X)
+#else
+#define OpenCounter(X)
+#endif
+
+/************** End of os_common.h *******************************************/
+/************** Continuing where we left off in os_unix.c ********************/
+
+/*
+** Define various macros that are missing from some systems.
+*/
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+#ifdef SQLITE_DISABLE_LFS
+# undef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+/*
+** The DJGPP compiler environment looks mostly like Unix, but it
+** lacks the fcntl() system call. So redefine fcntl() to be something
+** that always succeeds. This means that locking does not occur under
+** DJGPP. But it is DOS - what did you expect?
+*/
+#ifdef __DJGPP__
+# define fcntl(A,B,C) 0
+#endif
+
+/*
+** The threadid macro resolves to the thread-id or to 0. Used for
+** testing and debugging only.
+*/
+#if SQLITE_THREADSAFE
+#define threadid pthread_self()
+#else
+#define threadid 0
+#endif
+
+/*
+** Set or check the unixFile.tid field. This field is set when an unixFile
+** is first opened. All subsequent uses of the unixFile verify that the
+** same thread is operating on the unixFile. Some operating systems do
+** not allow locks to be overridden by other threads and that restriction
+** means that sqlite3* database handles cannot be moved from one thread
+** to another. This logic makes sure a user does not try to do that
+** by mistake.
+**
+** Version 3.3.1 (2006-01-15): unixFile can be moved from one thread to
+** another as long as we are running on a system that supports threads
+** overriding each others locks (which now the most common behavior)
+** or if no locks are held. But the unixFile.pLock field needs to be
+** recomputed because its key includes the thread-id. See the
+** transferOwnership() function below for additional information
+*/
+#if SQLITE_THREADSAFE
+# define SET_THREADID(X) (X)->tid = pthread_self()
+# define CHECK_THREADID(X) (threadsOverrideEachOthersLocks==0 && \
+ !pthread_equal((X)->tid, pthread_self()))
+#else
+# define SET_THREADID(X)
+# define CHECK_THREADID(X) 0
+#endif
+
+/*
+** Here is the dirt on POSIX advisory locks: ANSI STD 1003.1 (1996)
+** section 6.5.2.2 lines 483 through 490 specify that when a process
+** sets or clears a lock, that operation overrides any prior locks set
+** by the same process. It does not explicitly say so, but this implies
+** that it overrides locks set by the same process using a different
+** file descriptor. Consider this test case:
+**
+** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644);
+** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644);
+**
+** Suppose ./file1 and ./file2 are really the same file (because
+** one is a hard or symbolic link to the other) then if you set
+** an exclusive lock on fd1, then try to get an exclusive lock
+** on fd2, it works. I would have expected the second lock to
+** fail since there was already a lock on the file due to fd1.
+** But not so. Since both locks came from the same process, the
+** second overrides the first, even though they were on different
+** file descriptors opened on different file names.
+**
+** Bummer. If you ask me, this is broken. Badly broken. It means
+** that we cannot use POSIX locks to synchronize file access among
+** competing threads of the same process. POSIX locks will work fine
+** to synchronize access for threads in separate processes, but not
+** threads within the same process.
+**
+** To work around the problem, SQLite has to manage file locks internally
+** on its own. Whenever a new database is opened, we have to find the
+** specific inode of the database file (the inode is determined by the
+** st_dev and st_ino fields of the stat structure that fstat() fills in)
+** and check for locks already existing on that inode. When locks are
+** created or removed, we have to look at our own internal record of the
+** locks to see if another thread has previously set a lock on that same
+** inode.
+**
+** The sqlite3_file structure for POSIX is no longer just an integer file
+** descriptor. It is now a structure that holds the integer file
+** descriptor and a pointer to a structure that describes the internal
+** locks on the corresponding inode. There is one locking structure
+** per inode, so if the same inode is opened twice, both unixFile structures
+** point to the same locking structure. The locking structure keeps
+** a reference count (so we will know when to delete it) and a "cnt"
+** field that tells us its internal lock status. cnt==0 means the
+** file is unlocked. cnt==-1 means the file has an exclusive lock.
+** cnt>0 means there are cnt shared locks on the file.
+**
+** Any attempt to lock or unlock a file first checks the locking
+** structure. The fcntl() system call is only invoked to set a
+** POSIX lock if the internal lock structure transitions between
+** a locked and an unlocked state.
+**
+** 2004-Jan-11:
+** More recent discoveries about POSIX advisory locks. (The more
+** I discover, the more I realize the a POSIX advisory locks are
+** an abomination.)
+**
+** If you close a file descriptor that points to a file that has locks,
+** all locks on that file that are owned by the current process are
+** released. To work around this problem, each unixFile structure contains
+** a pointer to an openCnt structure. There is one openCnt structure
+** per open inode, which means that multiple unixFile can point to a single
+** openCnt. When an attempt is made to close an unixFile, if there are
+** other unixFile open on the same inode that are holding locks, the call
+** to close() the file descriptor is deferred until all of the locks clear.
+** The openCnt structure keeps a list of file descriptors that need to
+** be closed and that list is walked (and cleared) when the last lock
+** clears.
+**
+** First, under Linux threads, because each thread has a separate
+** process ID, lock operations in one thread do not override locks
+** to the same file in other threads. Linux threads behave like
+** separate processes in this respect. But, if you close a file
+** descriptor in linux threads, all locks are cleared, even locks
+** on other threads and even though the other threads have different
+** process IDs. Linux threads is inconsistent in this respect.
+** (I'm beginning to think that linux threads is an abomination too.)
+** The consequence of this all is that the hash table for the lockInfo
+** structure has to include the process id as part of its key because
+** locks in different threads are treated as distinct. But the
+** openCnt structure should not include the process id in its
+** key because close() clears lock on all threads, not just the current
+** thread. Were it not for this goofiness in linux threads, we could
+** combine the lockInfo and openCnt structures into a single structure.
+**
+** 2004-Jun-28:
+** On some versions of linux, threads can override each others locks.
+** On others not. Sometimes you can change the behavior on the same
+** system by setting the LD_ASSUME_KERNEL environment variable. The
+** POSIX standard is silent as to which behavior is correct, as far
+** as I can tell, so other versions of unix might show the same
+** inconsistency. There is no little doubt in my mind that posix
+** advisory locks and linux threads are profoundly broken.
+**
+** To work around the inconsistencies, we have to test at runtime
+** whether or not threads can override each others locks. This test
+** is run once, the first time any lock is attempted. A static
+** variable is set to record the results of this test for future
+** use.
+*/
+
+/*
+** An instance of the following structure serves as the key used
+** to locate a particular lockInfo structure given its inode.
+**
+** If threads cannot override each others locks, then we set the
+** lockKey.tid field to the thread ID. If threads can override
+** each others locks then tid is always set to zero. tid is omitted
+** if we compile without threading support.
+*/
+struct lockKey {
+ dev_t dev; /* Device number */
+ ino_t ino; /* Inode number */
+#if SQLITE_THREADSAFE
+ pthread_t tid; /* Thread ID or zero if threads can override each other */
+#endif
+};
+
+/*
+** An instance of the following structure is allocated for each open
+** inode on each thread with a different process ID. (Threads have
+** different process IDs on linux, but not on most other unixes.)
+**
+** A single inode can have multiple file descriptors, so each unixFile
+** structure contains a pointer to an instance of this object and this
+** object keeps a count of the number of unixFile pointing to it.
+*/
+struct lockInfo {
+ struct lockKey key; /* The lookup key */
+ int cnt; /* Number of SHARED locks held */
+ int locktype; /* One of SHARED_LOCK, RESERVED_LOCK etc. */
+ int nRef; /* Number of pointers to this structure */
+};
+
+/*
+** An instance of the following structure serves as the key used
+** to locate a particular openCnt structure given its inode. This
+** is the same as the lockKey except that the thread ID is omitted.
+*/
+struct openKey {
+ dev_t dev; /* Device number */
+ ino_t ino; /* Inode number */
+};
+
+/*
+** An instance of the following structure is allocated for each open
+** inode. This structure keeps track of the number of locks on that
+** inode. If a close is attempted against an inode that is holding
+** locks, the close is deferred until all locks clear by adding the
+** file descriptor to be closed to the pending list.
+*/
+struct openCnt {
+ struct openKey key; /* The lookup key */
+ int nRef; /* Number of pointers to this structure */
+ int nLock; /* Number of outstanding locks */
+ int nPending; /* Number of pending close() operations */
+ int *aPending; /* Malloced space holding fd's awaiting a close() */
+};
+
+/*
+** These hash tables map inodes and file descriptors (really, lockKey and
+** openKey structures) into lockInfo and openCnt structures. Access to
+** these hash tables must be protected by a mutex.
+*/
+static Hash lockHash = {SQLITE_HASH_BINARY, 0, 0, 0, 0, 0};
+static Hash openHash = {SQLITE_HASH_BINARY, 0, 0, 0, 0, 0};
+
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+/*
+** The locking styles are associated with the different file locking
+** capabilities supported by different file systems.
+**
+** POSIX locking style fully supports shared and exclusive byte-range locks
+** ADP locking only supports exclusive byte-range locks
+** FLOCK only supports a single file-global exclusive lock
+** DOTLOCK isn't a true locking style, it refers to the use of a special
+** file named the same as the database file with a '.lock' extension, this
+** can be used on file systems that do not offer any reliable file locking
+** NO locking means that no locking will be attempted, this is only used for
+** read-only file systems currently
+** UNSUPPORTED means that no locking will be attempted, this is only used for
+** file systems that are known to be unsupported
+*/
+typedef enum {
+ posixLockingStyle = 0, /* standard posix-advisory locks */
+ afpLockingStyle, /* use afp locks */
+ flockLockingStyle, /* use flock() */
+ dotlockLockingStyle, /* use <file>.lock files */
+ noLockingStyle, /* useful for read-only file system */
+ unsupportedLockingStyle /* indicates unsupported file system */
+} sqlite3LockingStyle;
+#endif /* SQLITE_ENABLE_LOCKING_STYLE */
+
+/*
+** Helper functions to obtain and relinquish the global mutex.
+*/
+static void enterMutex(){
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+}
+static void leaveMutex(){
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+}
+
+#if SQLITE_THREADSAFE
+/*
+** This variable records whether or not threads can override each others
+** locks.
+**
+** 0: No. Threads cannot override each others locks.
+** 1: Yes. Threads can override each others locks.
+** -1: We don't know yet.
+**
+** On some systems, we know at compile-time if threads can override each
+** others locks. On those systems, the SQLITE_THREAD_OVERRIDE_LOCK macro
+** will be set appropriately. On other systems, we have to check at
+** runtime. On these latter systems, SQLTIE_THREAD_OVERRIDE_LOCK is
+** undefined.
+**
+** This variable normally has file scope only. But during testing, we make
+** it a global so that the test code can change its value in order to verify
+** that the right stuff happens in either case.
+*/
+#ifndef SQLITE_THREAD_OVERRIDE_LOCK
+# define SQLITE_THREAD_OVERRIDE_LOCK -1
+#endif
+#ifdef SQLITE_TEST
+int threadsOverrideEachOthersLocks = SQLITE_THREAD_OVERRIDE_LOCK;
+#else
+static int threadsOverrideEachOthersLocks = SQLITE_THREAD_OVERRIDE_LOCK;
+#endif
+
+/*
+** This structure holds information passed into individual test
+** threads by the testThreadLockingBehavior() routine.
+*/
+struct threadTestData {
+ int fd; /* File to be locked */
+ struct flock lock; /* The locking operation */
+ int result; /* Result of the locking operation */
+};
+
+#ifdef SQLITE_LOCK_TRACE
+/*
+** Print out information about all locking operations.
+**
+** This routine is used for troubleshooting locks on multithreaded
+** platforms. Enable by compiling with the -DSQLITE_LOCK_TRACE
+** command-line option on the compiler. This code is normally
+** turned off.
+*/
+static int lockTrace(int fd, int op, struct flock *p){
+ char *zOpName, *zType;
+ int s;
+ int savedErrno;
+ if( op==F_GETLK ){
+ zOpName = "GETLK";
+ }else if( op==F_SETLK ){
+ zOpName = "SETLK";
+ }else{
+ s = fcntl(fd, op, p);
+ sqlite3DebugPrintf("fcntl unknown %d %d %d\n", fd, op, s);
+ return s;
+ }
+ if( p->l_type==F_RDLCK ){
+ zType = "RDLCK";
+ }else if( p->l_type==F_WRLCK ){
+ zType = "WRLCK";
+ }else if( p->l_type==F_UNLCK ){
+ zType = "UNLCK";
+ }else{
+ assert( 0 );
+ }
+ assert( p->l_whence==SEEK_SET );
+ s = fcntl(fd, op, p);
+ savedErrno = errno;
+ sqlite3DebugPrintf("fcntl %d %d %s %s %d %d %d %d\n",
+ threadid, fd, zOpName, zType, (int)p->l_start, (int)p->l_len,
+ (int)p->l_pid, s);
+ if( s==(-1) && op==F_SETLK && (p->l_type==F_RDLCK || p->l_type==F_WRLCK) ){
+ struct flock l2;
+ l2 = *p;
+ fcntl(fd, F_GETLK, &l2);
+ if( l2.l_type==F_RDLCK ){
+ zType = "RDLCK";
+ }else if( l2.l_type==F_WRLCK ){
+ zType = "WRLCK";
+ }else if( l2.l_type==F_UNLCK ){
+ zType = "UNLCK";
+ }else{
+ assert( 0 );
+ }
+ sqlite3DebugPrintf("fcntl-failure-reason: %s %d %d %d\n",
+ zType, (int)l2.l_start, (int)l2.l_len, (int)l2.l_pid);
+ }
+ errno = savedErrno;
+ return s;
+}
+#define fcntl lockTrace
+#endif /* SQLITE_LOCK_TRACE */
+
+/*
+** The testThreadLockingBehavior() routine launches two separate
+** threads on this routine. This routine attempts to lock a file
+** descriptor then returns. The success or failure of that attempt
+** allows the testThreadLockingBehavior() procedure to determine
+** whether or not threads can override each others locks.
+*/
+static void *threadLockingTest(void *pArg){
+ struct threadTestData *pData = (struct threadTestData*)pArg;
+ pData->result = fcntl(pData->fd, F_SETLK, &pData->lock);
+ return pArg;
+}
+
+/*
+** This procedure attempts to determine whether or not threads
+** can override each others locks then sets the
+** threadsOverrideEachOthersLocks variable appropriately.
+*/
+static void testThreadLockingBehavior(int fd_orig){
+ int fd;
+ struct threadTestData d[2];
+ pthread_t t[2];
+
+ fd = dup(fd_orig);
+ if( fd<0 ) return;
+ memset(d, 0, sizeof(d));
+ d[0].fd = fd;
+ d[0].lock.l_type = F_RDLCK;
+ d[0].lock.l_len = 1;
+ d[0].lock.l_start = 0;
+ d[0].lock.l_whence = SEEK_SET;
+ d[1] = d[0];
+ d[1].lock.l_type = F_WRLCK;
+ pthread_create(&t[0], 0, threadLockingTest, &d[0]);
+ pthread_create(&t[1], 0, threadLockingTest, &d[1]);
+ pthread_join(t[0], 0);
+ pthread_join(t[1], 0);
+ close(fd);
+ threadsOverrideEachOthersLocks = d[0].result==0 && d[1].result==0;
+}
+#endif /* SQLITE_THREADSAFE */
+
+/*
+** Release a lockInfo structure previously allocated by findLockInfo().
+*/
+static void releaseLockInfo(struct lockInfo *pLock){
+ if (pLock == NULL)
+ return;
+ pLock->nRef--;
+ if( pLock->nRef==0 ){
+ sqlite3HashInsert(&lockHash, &pLock->key, sizeof(pLock->key), 0);
+ sqlite3_free(pLock);
+ }
+}
+
+/*
+** Release a openCnt structure previously allocated by findLockInfo().
+*/
+static void releaseOpenCnt(struct openCnt *pOpen){
+ if (pOpen == NULL)
+ return;
+ pOpen->nRef--;
+ if( pOpen->nRef==0 ){
+ sqlite3HashInsert(&openHash, &pOpen->key, sizeof(pOpen->key), 0);
+ free(pOpen->aPending);
+ sqlite3_free(pOpen);
+ }
+}
+
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+/*
+** Tests a byte-range locking query to see if byte range locks are
+** supported, if not we fall back to dotlockLockingStyle.
+*/
+static sqlite3LockingStyle sqlite3TestLockingStyle(
+ const char *filePath,
+ int fd
+){
+ /* test byte-range lock using fcntl */
+ struct flock lockInfo;
+
+ lockInfo.l_len = 1;
+ lockInfo.l_start = 0;
+ lockInfo.l_whence = SEEK_SET;
+ lockInfo.l_type = F_RDLCK;
+
+ if( fcntl(fd, F_GETLK, &lockInfo)!=-1 ) {
+ return posixLockingStyle;
+ }
+
+ /* testing for flock can give false positives. So if if the above test
+ ** fails, then we fall back to using dot-lock style locking.
+ */
+ return dotlockLockingStyle;
+}
+
+/*
+** Examines the f_fstypename entry in the statfs structure as returned by
+** stat() for the file system hosting the database file, assigns the
+** appropriate locking style based on its value. These values and
+** assignments are based on Darwin/OSX behavior and have not been tested on
+** other systems.
+*/
+static sqlite3LockingStyle sqlite3DetectLockingStyle(
+ const char *filePath,
+ int fd
+){
+
+#ifdef SQLITE_FIXED_LOCKING_STYLE
+ return (sqlite3LockingStyle)SQLITE_FIXED_LOCKING_STYLE;
+#else
+ struct statfs fsInfo;
+
+ if( statfs(filePath, &fsInfo) == -1 ){
+ return sqlite3TestLockingStyle(filePath, fd);
+ }
+ if( fsInfo.f_flags & MNT_RDONLY ){
+ return noLockingStyle;
+ }
+ if( strcmp(fsInfo.f_fstypename, "hfs")==0 ||
+ strcmp(fsInfo.f_fstypename, "ufs")==0 ){
+ return posixLockingStyle;
+ }
+ if( strcmp(fsInfo.f_fstypename, "afpfs")==0 ){
+ return afpLockingStyle;
+ }
+ if( strcmp(fsInfo.f_fstypename, "nfs")==0 ){
+ return sqlite3TestLockingStyle(filePath, fd);
+ }
+ if( strcmp(fsInfo.f_fstypename, "smbfs")==0 ){
+ return flockLockingStyle;
+ }
+ if( strcmp(fsInfo.f_fstypename, "msdos")==0 ){
+ return dotlockLockingStyle;
+ }
+ if( strcmp(fsInfo.f_fstypename, "webdav")==0 ){
+ return unsupportedLockingStyle;
+ }
+ return sqlite3TestLockingStyle(filePath, fd);
+#endif /* SQLITE_FIXED_LOCKING_STYLE */
+}
+
+#endif /* SQLITE_ENABLE_LOCKING_STYLE */
+
+/*
+** Given a file descriptor, locate lockInfo and openCnt structures that
+** describes that file descriptor. Create new ones if necessary. The
+** return values might be uninitialized if an error occurs.
+**
+** Return an appropriate error code.
+*/
+static int findLockInfo(
+ int fd, /* The file descriptor used in the key */
+ struct lockInfo **ppLock, /* Return the lockInfo structure here */
+ struct openCnt **ppOpen /* Return the openCnt structure here */
+){
+ int rc;
+ struct lockKey key1;
+ struct openKey key2;
+ struct stat statbuf;
+ struct lockInfo *pLock;
+ struct openCnt *pOpen;
+ rc = fstat(fd, &statbuf);
+ if( rc!=0 ){
+#ifdef EOVERFLOW
+ if( errno==EOVERFLOW ) return SQLITE_NOLFS;
+#endif
+ return SQLITE_IOERR;
+ }
+
+ memset(&key1, 0, sizeof(key1));
+ key1.dev = statbuf.st_dev;
+ key1.ino = statbuf.st_ino;
+#if SQLITE_THREADSAFE
+ if( threadsOverrideEachOthersLocks<0 ){
+ testThreadLockingBehavior(fd);
+ }
+ key1.tid = threadsOverrideEachOthersLocks ? 0 : pthread_self();
+#endif
+ memset(&key2, 0, sizeof(key2));
+ key2.dev = statbuf.st_dev;
+ key2.ino = statbuf.st_ino;
+ pLock = (struct lockInfo*)sqlite3HashFind(&lockHash, &key1, sizeof(key1));
+ if( pLock==0 ){
+ struct lockInfo *pOld;
+ pLock = sqlite3_malloc( sizeof(*pLock) );
+ if( pLock==0 ){
+ rc = SQLITE_NOMEM;
+ goto exit_findlockinfo;
+ }
+ pLock->key = key1;
+ pLock->nRef = 1;
+ pLock->cnt = 0;
+ pLock->locktype = 0;
+ pOld = sqlite3HashInsert(&lockHash, &pLock->key, sizeof(key1), pLock);
+ if( pOld!=0 ){
+ assert( pOld==pLock );
+ sqlite3_free(pLock);
+ rc = SQLITE_NOMEM;
+ goto exit_findlockinfo;
+ }
+ }else{
+ pLock->nRef++;
+ }
+ *ppLock = pLock;
+ if( ppOpen!=0 ){
+ pOpen = (struct openCnt*)sqlite3HashFind(&openHash, &key2, sizeof(key2));
+ if( pOpen==0 ){
+ struct openCnt *pOld;
+ pOpen = sqlite3_malloc( sizeof(*pOpen) );
+ if( pOpen==0 ){
+ releaseLockInfo(pLock);
+ rc = SQLITE_NOMEM;
+ goto exit_findlockinfo;
+ }
+ pOpen->key = key2;
+ pOpen->nRef = 1;
+ pOpen->nLock = 0;
+ pOpen->nPending = 0;
+ pOpen->aPending = 0;
+ pOld = sqlite3HashInsert(&openHash, &pOpen->key, sizeof(key2), pOpen);
+ if( pOld!=0 ){
+ assert( pOld==pOpen );
+ sqlite3_free(pOpen);
+ releaseLockInfo(pLock);
+ rc = SQLITE_NOMEM;
+ goto exit_findlockinfo;
+ }
+ }else{
+ pOpen->nRef++;
+ }
+ *ppOpen = pOpen;
+ }
+
+exit_findlockinfo:
+ return rc;
+}
+
+#ifdef SQLITE_DEBUG
+/*
+** Helper function for printing out trace information from debugging
+** binaries. This returns the string represetation of the supplied
+** integer lock-type.
+*/
+static const char *locktypeName(int locktype){
+ switch( locktype ){
+ case NO_LOCK: return "NONE";
+ case SHARED_LOCK: return "SHARED";
+ case RESERVED_LOCK: return "RESERVED";
+ case PENDING_LOCK: return "PENDING";
+ case EXCLUSIVE_LOCK: return "EXCLUSIVE";
+ }
+ return "ERROR";
+}
+#endif
+
+/*
+** If we are currently in a different thread than the thread that the
+** unixFile argument belongs to, then transfer ownership of the unixFile
+** over to the current thread.
+**
+** A unixFile is only owned by a thread on systems where one thread is
+** unable to override locks created by a different thread. RedHat9 is
+** an example of such a system.
+**
+** Ownership transfer is only allowed if the unixFile is currently unlocked.
+** If the unixFile is locked and an ownership is wrong, then return
+** SQLITE_MISUSE. SQLITE_OK is returned if everything works.
+*/
+#if SQLITE_THREADSAFE
+static int transferOwnership(unixFile *pFile){
+ int rc;
+ pthread_t hSelf;
+ if( threadsOverrideEachOthersLocks ){
+ /* Ownership transfers not needed on this system */
+ return SQLITE_OK;
+ }
+ hSelf = pthread_self();
+ if( pthread_equal(pFile->tid, hSelf) ){
+ /* We are still in the same thread */
+ OSTRACE1("No-transfer, same thread\n");
+ return SQLITE_OK;
+ }
+ if( pFile->locktype!=NO_LOCK ){
+ /* We cannot change ownership while we are holding a lock! */
+ return SQLITE_MISUSE;
+ }
+ OSTRACE4("Transfer ownership of %d from %d to %d\n",
+ pFile->h, pFile->tid, hSelf);
+ pFile->tid = hSelf;
+ if (pFile->pLock != NULL) {
+ releaseLockInfo(pFile->pLock);
+ rc = findLockInfo(pFile->h, &pFile->pLock, 0);
+ OSTRACE5("LOCK %d is now %s(%s,%d)\n", pFile->h,
+ locktypeName(pFile->locktype),
+ locktypeName(pFile->pLock->locktype), pFile->pLock->cnt);
+ return rc;
+ } else {
+ return SQLITE_OK;
+ }
+}
+#else
+ /* On single-threaded builds, ownership transfer is a no-op */
+# define transferOwnership(X) SQLITE_OK
+#endif
+
+/*
+** Seek to the offset passed as the second argument, then read cnt
+** bytes into pBuf. Return the number of bytes actually read.
+**
+** NB: If you define USE_PREAD or USE_PREAD64, then it might also
+** be necessary to define _XOPEN_SOURCE to be 500. This varies from
+** one system to another. Since SQLite does not define USE_PREAD
+** any any form by default, we will not attempt to define _XOPEN_SOURCE.
+** See tickets #2741 and #2681.
+*/
+static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){
+ int got;
+ i64 newOffset;
+ TIMER_START;
+#if defined(USE_PREAD)
+ got = pread(id->h, pBuf, cnt, offset);
+ SimulateIOError( got = -1 );
+#elif defined(USE_PREAD64)
+ got = pread64(id->h, pBuf, cnt, offset);
+ SimulateIOError( got = -1 );
+#else
+ newOffset = lseek(id->h, offset, SEEK_SET);
+ SimulateIOError( newOffset-- );
+ if( newOffset!=offset ){
+ return -1;
+ }
+ got = read(id->h, pBuf, cnt);
+#endif
+ TIMER_END;
+ OSTRACE5("READ %-3d %5d %7lld %d\n", id->h, got, offset, TIMER_ELAPSED);
+ return got;
+}
+
+/*
+** Read data from a file into a buffer. Return SQLITE_OK if all
+** bytes were read successfully and SQLITE_IOERR if anything goes
+** wrong.
+*/
+static int unixRead(
+ sqlite3_file *id,
+ void *pBuf,
+ int amt,
+ sqlite3_int64 offset
+){
+ int got;
+ assert( id );
+ got = seekAndRead((unixFile*)id, offset, pBuf, amt);
+ if( got==amt ){
+ return SQLITE_OK;
+ }else if( got<0 ){
+ return SQLITE_IOERR_READ;
+ }else{
+ memset(&((char*)pBuf)[got], 0, amt-got);
+ return SQLITE_IOERR_SHORT_READ;
+ }
+}
+
+/*
+** Seek to the offset in id->offset then read cnt bytes into pBuf.
+** Return the number of bytes actually read. Update the offset.
+*/
+static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){
+ int got;
+ i64 newOffset;
+ TIMER_START;
+#if defined(USE_PREAD)
+ got = pwrite(id->h, pBuf, cnt, offset);
+#elif defined(USE_PREAD64)
+ got = pwrite64(id->h, pBuf, cnt, offset);
+#else
+ newOffset = lseek(id->h, offset, SEEK_SET);
+ if( newOffset!=offset ){
+ return -1;
+ }
+ got = write(id->h, pBuf, cnt);
+#endif
+ TIMER_END;
+ OSTRACE5("WRITE %-3d %5d %7lld %d\n", id->h, got, offset, TIMER_ELAPSED);
+ return got;
+}
+
+
+/*
+** Write data from a buffer into a file. Return SQLITE_OK on success
+** or some other error code on failure.
+*/
+static int unixWrite(
+ sqlite3_file *id,
+ const void *pBuf,
+ int amt,
+ sqlite3_int64 offset
+){
+ int wrote = 0;
+ assert( id );
+ assert( amt>0 );
+ while( amt>0 && (wrote = seekAndWrite((unixFile*)id, offset, pBuf, amt))>0 ){
+ amt -= wrote;
+ offset += wrote;
+ pBuf = &((char*)pBuf)[wrote];
+ }
+ SimulateIOError(( wrote=(-1), amt=1 ));
+ SimulateDiskfullError(( wrote=0, amt=1 ));
+ if( amt>0 ){
+ if( wrote<0 ){
+ return SQLITE_IOERR_WRITE;
+ }else{
+ return SQLITE_FULL;
+ }
+ }
+ return SQLITE_OK;
+}
+
+#ifdef SQLITE_TEST
+/*
+** Count the number of fullsyncs and normal syncs. This is used to test
+** that syncs and fullsyncs are occuring at the right times.
+*/
+SQLITE_API int sqlite3_sync_count = 0;
+SQLITE_API int sqlite3_fullsync_count = 0;
+#endif
+
+/*
+** Use the fdatasync() API only if the HAVE_FDATASYNC macro is defined.
+** Otherwise use fsync() in its place.
+*/
+#ifndef HAVE_FDATASYNC
+# define fdatasync fsync
+#endif
+
+/*
+** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not
+** the F_FULLFSYNC macro is defined. F_FULLFSYNC is currently
+** only available on Mac OS X. But that could change.
+*/
+#ifdef F_FULLFSYNC
+# define HAVE_FULLFSYNC 1
+#else
+# define HAVE_FULLFSYNC 0
+#endif
+
+
+/*
+** The fsync() system call does not work as advertised on many
+** unix systems. The following procedure is an attempt to make
+** it work better.
+**
+** The SQLITE_NO_SYNC macro disables all fsync()s. This is useful
+** for testing when we want to run through the test suite quickly.
+** You are strongly advised *not* to deploy with SQLITE_NO_SYNC
+** enabled, however, since with SQLITE_NO_SYNC enabled, an OS crash
+** or power failure will likely corrupt the database file.
+*/
+static int full_fsync(int fd, int fullSync, int dataOnly){
+ int rc;
+
+ /* Record the number of times that we do a normal fsync() and
+ ** FULLSYNC. This is used during testing to verify that this procedure
+ ** gets called with the correct arguments.
+ */
+#ifdef SQLITE_TEST
+ if( fullSync ) sqlite3_fullsync_count++;
+ sqlite3_sync_count++;
+#endif
+
+ /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a
+ ** no-op
+ */
+#ifdef SQLITE_NO_SYNC
+ rc = SQLITE_OK;
+#else
+
+#if HAVE_FULLFSYNC
+ if( fullSync ){
+ rc = fcntl(fd, F_FULLFSYNC, 0);
+ }else{
+ rc = 1;
+ }
+ /* If the FULLFSYNC failed, fall back to attempting an fsync().
+ * It shouldn't be possible for fullfsync to fail on the local
+ * file system (on OSX), so failure indicates that FULLFSYNC
+ * isn't supported for this file system. So, attempt an fsync
+ * and (for now) ignore the overhead of a superfluous fcntl call.
+ * It'd be better to detect fullfsync support once and avoid
+ * the fcntl call every time sync is called.
+ */
+ if( rc ) rc = fsync(fd);
+
+#else
+ if( dataOnly ){
+ rc = fdatasync(fd);
+ }else{
+ rc = fsync(fd);
+ }
+#endif /* HAVE_FULLFSYNC */
+#endif /* defined(SQLITE_NO_SYNC) */
+
+ return rc;
+}
+
+/*
+** Make sure all writes to a particular file are committed to disk.
+**
+** If dataOnly==0 then both the file itself and its metadata (file
+** size, access time, etc) are synced. If dataOnly!=0 then only the
+** file data is synced.
+**
+** Under Unix, also make sure that the directory entry for the file
+** has been created by fsync-ing the directory that contains the file.
+** If we do not do this and we encounter a power failure, the directory
+** entry for the journal might not exist after we reboot. The next
+** SQLite to access the file will not know that the journal exists (because
+** the directory entry for the journal was never created) and the transaction
+** will not roll back - possibly leading to database corruption.
+*/
+static int unixSync(sqlite3_file *id, int flags){
+ int rc;
+ unixFile *pFile = (unixFile*)id;
+
+ int isDataOnly = (flags&SQLITE_SYNC_DATAONLY);
+ int isFullsync = (flags&0x0F)==SQLITE_SYNC_FULL;
+
+ /* Check that one of SQLITE_SYNC_NORMAL or FULL was passed */
+ assert((flags&0x0F)==SQLITE_SYNC_NORMAL
+ || (flags&0x0F)==SQLITE_SYNC_FULL
+ );
+
+ assert( pFile );
+ OSTRACE2("SYNC %-3d\n", pFile->h);
+ rc = full_fsync(pFile->h, isFullsync, isDataOnly);
+ SimulateIOError( rc=1 );
+ if( rc ){
+ return SQLITE_IOERR_FSYNC;
+ }
+ if( pFile->dirfd>=0 ){
+ OSTRACE4("DIRSYNC %-3d (have_fullfsync=%d fullsync=%d)\n", pFile->dirfd,
+ HAVE_FULLFSYNC, isFullsync);
+#ifndef SQLITE_DISABLE_DIRSYNC
+ /* The directory sync is only attempted if full_fsync is
+ ** turned off or unavailable. If a full_fsync occurred above,
+ ** then the directory sync is superfluous.
+ */
+ if( (!HAVE_FULLFSYNC || !isFullsync) && full_fsync(pFile->dirfd,0,0) ){
+ /*
+ ** We have received multiple reports of fsync() returning
+ ** errors when applied to directories on certain file systems.
+ ** A failed directory sync is not a big deal. So it seems
+ ** better to ignore the error. Ticket #1657
+ */
+ /* return SQLITE_IOERR; */
+ }
+#endif
+ close(pFile->dirfd); /* Only need to sync once, so close the directory */
+ pFile->dirfd = -1; /* when we are done. */
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Truncate an open file to a specified size
+*/
+static int unixTruncate(sqlite3_file *id, i64 nByte){
+ int rc;
+ assert( id );
+ SimulateIOError( return SQLITE_IOERR_TRUNCATE );
+ rc = ftruncate(((unixFile*)id)->h, (off_t)nByte);
+ if( rc ){
+ return SQLITE_IOERR_TRUNCATE;
+ }else{
+ return SQLITE_OK;
+ }
+}
+
+/*
+** Determine the current size of a file in bytes
+*/
+static int unixFileSize(sqlite3_file *id, i64 *pSize){
+ int rc;
+ struct stat buf;
+ assert( id );
+ rc = fstat(((unixFile*)id)->h, &buf);
+ SimulateIOError( rc=1 );
+ if( rc!=0 ){
+ return SQLITE_IOERR_FSTAT;
+ }
+ *pSize = buf.st_size;
+ return SQLITE_OK;
+}
+
+/*
+** This routine checks if there is a RESERVED lock held on the specified
+** file by this or any other process. If such a lock is held, return
+** non-zero. If the file is unlocked or holds only SHARED locks, then
+** return zero.
+*/
+static int unixCheckReservedLock(sqlite3_file *id){
+ int r = 0;
+ unixFile *pFile = (unixFile*)id;
+
+ assert( pFile );
+ enterMutex(); /* Because pFile->pLock is shared across threads */
+
+ /* Check if a thread in this process holds such a lock */
+ if( pFile->pLock->locktype>SHARED_LOCK ){
+ r = 1;
+ }
+
+ /* Otherwise see if some other process holds it.
+ */
+ if( !r ){
+ struct flock lock;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = RESERVED_BYTE;
+ lock.l_len = 1;
+ lock.l_type = F_WRLCK;
+ fcntl(pFile->h, F_GETLK, &lock);
+ if( lock.l_type!=F_UNLCK ){
+ r = 1;
+ }
+ }
+
+ leaveMutex();
+ OSTRACE3("TEST WR-LOCK %d %d\n", pFile->h, r);
+
+ return r;
+}
+
+/*
+** Lock the file with the lock specified by parameter locktype - one
+** of the following:
+**
+** (1) SHARED_LOCK
+** (2) RESERVED_LOCK
+** (3) PENDING_LOCK
+** (4) EXCLUSIVE_LOCK
+**
+** Sometimes when requesting one lock state, additional lock states
+** are inserted in between. The locking might fail on one of the later
+** transitions leaving the lock state different from what it started but
+** still short of its goal. The following chart shows the allowed
+** transitions and the inserted intermediate states:
+**
+** UNLOCKED -> SHARED
+** SHARED -> RESERVED
+** SHARED -> (PENDING) -> EXCLUSIVE
+** RESERVED -> (PENDING) -> EXCLUSIVE
+** PENDING -> EXCLUSIVE
+**
+** This routine will only increase a lock. Use the sqlite3OsUnlock()
+** routine to lower a locking level.
+*/
+static int unixLock(sqlite3_file *id, int locktype){
+ /* The following describes the implementation of the various locks and
+ ** lock transitions in terms of the POSIX advisory shared and exclusive
+ ** lock primitives (called read-locks and write-locks below, to avoid
+ ** confusion with SQLite lock names). The algorithms are complicated
+ ** slightly in order to be compatible with windows systems simultaneously
+ ** accessing the same database file, in case that is ever required.
+ **
+ ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved
+ ** byte', each single bytes at well known offsets, and the 'shared byte
+ ** range', a range of 510 bytes at a well known offset.
+ **
+ ** To obtain a SHARED lock, a read-lock is obtained on the 'pending
+ ** byte'. If this is successful, a random byte from the 'shared byte
+ ** range' is read-locked and the lock on the 'pending byte' released.
+ **
+ ** A process may only obtain a RESERVED lock after it has a SHARED lock.
+ ** A RESERVED lock is implemented by grabbing a write-lock on the
+ ** 'reserved byte'.
+ **
+ ** A process may only obtain a PENDING lock after it has obtained a
+ ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock
+ ** on the 'pending byte'. This ensures that no new SHARED locks can be
+ ** obtained, but existing SHARED locks are allowed to persist. A process
+ ** does not have to obtain a RESERVED lock on the way to a PENDING lock.
+ ** This property is used by the algorithm for rolling back a journal file
+ ** after a crash.
+ **
+ ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is
+ ** implemented by obtaining a write-lock on the entire 'shared byte
+ ** range'. Since all other locks require a read-lock on one of the bytes
+ ** within this range, this ensures that no other locks are held on the
+ ** database.
+ **
+ ** The reason a single byte cannot be used instead of the 'shared byte
+ ** range' is that some versions of windows do not support read-locks. By
+ ** locking a random byte from a range, concurrent SHARED locks may exist
+ ** even if the locking primitive used is always a write-lock.
+ */
+ int rc = SQLITE_OK;
+ unixFile *pFile = (unixFile*)id;
+ struct lockInfo *pLock = pFile->pLock;
+ struct flock lock;
+ int s;
+
+ assert( pFile );
+ OSTRACE7("LOCK %d %s was %s(%s,%d) pid=%d\n", pFile->h,
+ locktypeName(locktype), locktypeName(pFile->locktype),
+ locktypeName(pLock->locktype), pLock->cnt , getpid());
+
+ /* If there is already a lock of this type or more restrictive on the
+ ** unixFile, do nothing. Don't use the end_lock: exit path, as
+ ** enterMutex() hasn't been called yet.
+ */
+ if( pFile->locktype>=locktype ){
+ OSTRACE3("LOCK %d %s ok (already held)\n", pFile->h,
+ locktypeName(locktype));
+ return SQLITE_OK;
+ }
+
+ /* Make sure the locking sequence is correct
+ */
+ assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK );
+ assert( locktype!=PENDING_LOCK );
+ assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK );
+
+ /* This mutex is needed because pFile->pLock is shared across threads
+ */
+ enterMutex();
+
+ /* Make sure the current thread owns the pFile.
+ */
+ rc = transferOwnership(pFile);
+ if( rc!=SQLITE_OK ){
+ leaveMutex();
+ return rc;
+ }
+ pLock = pFile->pLock;
+
+ /* If some thread using this PID has a lock via a different unixFile*
+ ** handle that precludes the requested lock, return BUSY.
+ */
+ if( (pFile->locktype!=pLock->locktype &&
+ (pLock->locktype>=PENDING_LOCK || locktype>SHARED_LOCK))
+ ){
+ rc = SQLITE_BUSY;
+ goto end_lock;
+ }
+
+ /* If a SHARED lock is requested, and some thread using this PID already
+ ** has a SHARED or RESERVED lock, then increment reference counts and
+ ** return SQLITE_OK.
+ */
+ if( locktype==SHARED_LOCK &&
+ (pLock->locktype==SHARED_LOCK || pLock->locktype==RESERVED_LOCK) ){
+ assert( locktype==SHARED_LOCK );
+ assert( pFile->locktype==0 );
+ assert( pLock->cnt>0 );
+ pFile->locktype = SHARED_LOCK;
+ pLock->cnt++;
+ pFile->pOpen->nLock++;
+ goto end_lock;
+ }
+
+ lock.l_len = 1L;
+
+ lock.l_whence = SEEK_SET;
+
+ /* A PENDING lock is needed before acquiring a SHARED lock and before
+ ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will
+ ** be released.
+ */
+ if( locktype==SHARED_LOCK
+ || (locktype==EXCLUSIVE_LOCK && pFile->locktype<PENDING_LOCK)
+ ){
+ lock.l_type = (locktype==SHARED_LOCK?F_RDLCK:F_WRLCK);
+ lock.l_start = PENDING_BYTE;
+ s = fcntl(pFile->h, F_SETLK, &lock);
+ if( s==(-1) ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ goto end_lock;
+ }
+ }
+
+
+ /* If control gets to this point, then actually go ahead and make
+ ** operating system calls for the specified lock.
+ */
+ if( locktype==SHARED_LOCK ){
+ assert( pLock->cnt==0 );
+ assert( pLock->locktype==0 );
+
+ /* Now get the read-lock */
+ lock.l_start = SHARED_FIRST;
+ lock.l_len = SHARED_SIZE;
+ s = fcntl(pFile->h, F_SETLK, &lock);
+
+ /* Drop the temporary PENDING lock */
+ lock.l_start = PENDING_BYTE;
+ lock.l_len = 1L;
+ lock.l_type = F_UNLCK;
+ if( fcntl(pFile->h, F_SETLK, &lock)!=0 ){
+ rc = SQLITE_IOERR_UNLOCK; /* This should never happen */
+ goto end_lock;
+ }
+ if( s==(-1) ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ }else{
+ pFile->locktype = SHARED_LOCK;
+ pFile->pOpen->nLock++;
+ pLock->cnt = 1;
+ }
+ }else if( locktype==EXCLUSIVE_LOCK && pLock->cnt>1 ){
+ /* We are trying for an exclusive lock but another thread in this
+ ** same process is still holding a shared lock. */
+ rc = SQLITE_BUSY;
+ }else{
+ /* The request was for a RESERVED or EXCLUSIVE lock. It is
+ ** assumed that there is a SHARED or greater lock on the file
+ ** already.
+ */
+ assert( 0!=pFile->locktype );
+ lock.l_type = F_WRLCK;
+ switch( locktype ){
+ case RESERVED_LOCK:
+ lock.l_start = RESERVED_BYTE;
+ break;
+ case EXCLUSIVE_LOCK:
+ lock.l_start = SHARED_FIRST;
+ lock.l_len = SHARED_SIZE;
+ break;
+ default:
+ assert(0);
+ }
+ s = fcntl(pFile->h, F_SETLK, &lock);
+ if( s==(-1) ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ pFile->locktype = locktype;
+ pLock->locktype = locktype;
+ }else if( locktype==EXCLUSIVE_LOCK ){
+ pFile->locktype = PENDING_LOCK;
+ pLock->locktype = PENDING_LOCK;
+ }
+
+end_lock:
+ leaveMutex();
+ OSTRACE4("LOCK %d %s %s\n", pFile->h, locktypeName(locktype),
+ rc==SQLITE_OK ? "ok" : "failed");
+ return rc;
+}
+
+/*
+** Lower the locking level on file descriptor pFile to locktype. locktype
+** must be either NO_LOCK or SHARED_LOCK.
+**
+** If the locking level of the file descriptor is already at or below
+** the requested locking level, this routine is a no-op.
+*/
+static int unixUnlock(sqlite3_file *id, int locktype){
+ struct lockInfo *pLock;
+ struct flock lock;
+ int rc = SQLITE_OK;
+ unixFile *pFile = (unixFile*)id;
+ int h;
+
+ assert( pFile );
+ OSTRACE7("UNLOCK %d %d was %d(%d,%d) pid=%d\n", pFile->h, locktype,
+ pFile->locktype, pFile->pLock->locktype, pFile->pLock->cnt, getpid());
+
+ assert( locktype<=SHARED_LOCK );
+ if( pFile->locktype<=locktype ){
+ return SQLITE_OK;
+ }
+ if( CHECK_THREADID(pFile) ){
+ return SQLITE_MISUSE;
+ }
+ enterMutex();
+ h = pFile->h;
+ pLock = pFile->pLock;
+ assert( pLock->cnt!=0 );
+ if( pFile->locktype>SHARED_LOCK ){
+ assert( pLock->locktype==pFile->locktype );
+ SimulateIOErrorBenign(1);
+ SimulateIOError( h=(-1) )
+ SimulateIOErrorBenign(0);
+ if( locktype==SHARED_LOCK ){
+ lock.l_type = F_RDLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = SHARED_FIRST;
+ lock.l_len = SHARED_SIZE;
+ if( fcntl(h, F_SETLK, &lock)==(-1) ){
+ rc = SQLITE_IOERR_RDLOCK;
+ }
+ }
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = PENDING_BYTE;
+ lock.l_len = 2L; assert( PENDING_BYTE+1==RESERVED_BYTE );
+ if( fcntl(h, F_SETLK, &lock)!=(-1) ){
+ pLock->locktype = SHARED_LOCK;
+ }else{
+ rc = SQLITE_IOERR_UNLOCK;
+ }
+ }
+ if( locktype==NO_LOCK ){
+ struct openCnt *pOpen;
+
+ /* Decrement the shared lock counter. Release the lock using an
+ ** OS call only when all threads in this same process have released
+ ** the lock.
+ */
+ pLock->cnt--;
+ if( pLock->cnt==0 ){
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = lock.l_len = 0L;
+ SimulateIOErrorBenign(1);
+ SimulateIOError( h=(-1) )
+ SimulateIOErrorBenign(0);
+ if( fcntl(h, F_SETLK, &lock)!=(-1) ){
+ pLock->locktype = NO_LOCK;
+ }else{
+ rc = SQLITE_IOERR_UNLOCK;
+ pLock->cnt = 1;
+ }
+ }
+
+ /* Decrement the count of locks against this same file. When the
+ ** count reaches zero, close any other file descriptors whose close
+ ** was deferred because of outstanding locks.
+ */
+ if( rc==SQLITE_OK ){
+ pOpen = pFile->pOpen;
+ pOpen->nLock--;
+ assert( pOpen->nLock>=0 );
+ if( pOpen->nLock==0 && pOpen->nPending>0 ){
+ int i;
+ for(i=0; i<pOpen->nPending; i++){
+ close(pOpen->aPending[i]);
+ }
+ free(pOpen->aPending);
+ pOpen->nPending = 0;
+ pOpen->aPending = 0;
+ }
+ }
+ }
+ leaveMutex();
+ if( rc==SQLITE_OK ) pFile->locktype = locktype;
+ return rc;
+}
+
+/*
+** Close a file.
+*/
+static int unixClose(sqlite3_file *id){
+ unixFile *pFile = (unixFile *)id;
+ if( !pFile ) return SQLITE_OK;
+ unixUnlock(id, NO_LOCK);
+ if( pFile->dirfd>=0 ) close(pFile->dirfd);
+ pFile->dirfd = -1;
+ enterMutex();
+
+ if( pFile->pOpen->nLock ){
+ /* If there are outstanding locks, do not actually close the file just
+ ** yet because that would clear those locks. Instead, add the file
+ ** descriptor to pOpen->aPending. It will be automatically closed when
+ ** the last lock is cleared.
+ */
+ int *aNew;
+ struct openCnt *pOpen = pFile->pOpen;
+ aNew = realloc( pOpen->aPending, (pOpen->nPending+1)*sizeof(int) );
+ if( aNew==0 ){
+ /* If a malloc fails, just leak the file descriptor */
+ }else{
+ pOpen->aPending = aNew;
+ pOpen->aPending[pOpen->nPending] = pFile->h;
+ pOpen->nPending++;
+ }
+ }else{
+ /* There are no outstanding locks so we can close the file immediately */
+ close(pFile->h);
+ }
+ releaseLockInfo(pFile->pLock);
+ releaseOpenCnt(pFile->pOpen);
+
+ leaveMutex();
+ OSTRACE2("CLOSE %-3d\n", pFile->h);
+ OpenCounter(-1);
+ memset(pFile, 0, sizeof(unixFile));
+ return SQLITE_OK;
+}
+
+
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+#pragma mark AFP Support
+
+/*
+ ** The afpLockingContext structure contains all afp lock specific state
+ */
+typedef struct afpLockingContext afpLockingContext;
+struct afpLockingContext {
+ unsigned long long sharedLockByte;
+ const char *filePath;
+};
+
+struct ByteRangeLockPB2
+{
+ unsigned long long offset; /* offset to first byte to lock */
+ unsigned long long length; /* nbr of bytes to lock */
+ unsigned long long retRangeStart; /* nbr of 1st byte locked if successful */
+ unsigned char unLockFlag; /* 1 = unlock, 0 = lock */
+ unsigned char startEndFlag; /* 1=rel to end of fork, 0=rel to start */
+ int fd; /* file desc to assoc this lock with */
+};
+
+#define afpfsByteRangeLock2FSCTL _IOWR('z', 23, struct ByteRangeLockPB2)
+
+/*
+** Return 0 on success, 1 on failure. To match the behavior of the
+** normal posix file locking (used in unixLock for example), we should
+** provide 'richer' return codes - specifically to differentiate between
+** 'file busy' and 'file system error' results.
+*/
+static int _AFPFSSetLock(
+ const char *path,
+ int fd,
+ unsigned long long offset,
+ unsigned long long length,
+ int setLockFlag
+){
+ struct ByteRangeLockPB2 pb;
+ int err;
+
+ pb.unLockFlag = setLockFlag ? 0 : 1;
+ pb.startEndFlag = 0;
+ pb.offset = offset;
+ pb.length = length;
+ pb.fd = fd;
+ OSTRACE5("AFPLOCK setting lock %s for %d in range %llx:%llx\n",
+ (setLockFlag?"ON":"OFF"), fd, offset, length);
+ err = fsctl(path, afpfsByteRangeLock2FSCTL, &pb, 0);
+ if ( err==-1 ) {
+ OSTRACE4("AFPLOCK failed to fsctl() '%s' %d %s\n", path, errno,
+ strerror(errno));
+ return 1; /* error */
+ } else {
+ return 0;
+ }
+}
+
+/*
+ ** This routine checks if there is a RESERVED lock held on the specified
+ ** file by this or any other process. If such a lock is held, return
+ ** non-zero. If the file is unlocked or holds only SHARED locks, then
+ ** return zero.
+ */
+static int afpUnixCheckReservedLock(sqlite3_file *id){
+ int r = 0;
+ unixFile *pFile = (unixFile*)id;
+
+ assert( pFile );
+ afpLockingContext *context = (afpLockingContext *) pFile->lockingContext;
+
+ /* Check if a thread in this process holds such a lock */
+ if( pFile->locktype>SHARED_LOCK ){
+ r = 1;
+ }
+
+ /* Otherwise see if some other process holds it.
+ */
+ if ( !r ) {
+ /* lock the byte */
+ int failed = _AFPFSSetLock(context->filePath, pFile->h, RESERVED_BYTE, 1,1);
+ if (failed) {
+ /* if we failed to get the lock then someone else must have it */
+ r = 1;
+ } else {
+ /* if we succeeded in taking the reserved lock, unlock it to restore
+ ** the original state */
+ _AFPFSSetLock(context->filePath, pFile->h, RESERVED_BYTE, 1, 0);
+ }
+ }
+ OSTRACE3("TEST WR-LOCK %d %d\n", pFile->h, r);
+
+ return r;
+}
+
+/* AFP-style locking following the behavior of unixLock, see the unixLock
+** function comments for details of lock management. */
+static int afpUnixLock(sqlite3_file *id, int locktype){
+ int rc = SQLITE_OK;
+ unixFile *pFile = (unixFile*)id;
+ afpLockingContext *context = (afpLockingContext *) pFile->lockingContext;
+ int gotPendingLock = 0;
+
+ assert( pFile );
+ OSTRACE5("LOCK %d %s was %s pid=%d\n", pFile->h,
+ locktypeName(locktype), locktypeName(pFile->locktype), getpid());
+
+ /* If there is already a lock of this type or more restrictive on the
+ ** unixFile, do nothing. Don't use the afp_end_lock: exit path, as
+ ** enterMutex() hasn't been called yet.
+ */
+ if( pFile->locktype>=locktype ){
+ OSTRACE3("LOCK %d %s ok (already held)\n", pFile->h,
+ locktypeName(locktype));
+ return SQLITE_OK;
+ }
+
+ /* Make sure the locking sequence is correct
+ */
+ assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK );
+ assert( locktype!=PENDING_LOCK );
+ assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK );
+
+ /* This mutex is needed because pFile->pLock is shared across threads
+ */
+ enterMutex();
+
+ /* Make sure the current thread owns the pFile.
+ */
+ rc = transferOwnership(pFile);
+ if( rc!=SQLITE_OK ){
+ leaveMutex();
+ return rc;
+ }
+
+ /* A PENDING lock is needed before acquiring a SHARED lock and before
+ ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will
+ ** be released.
+ */
+ if( locktype==SHARED_LOCK
+ || (locktype==EXCLUSIVE_LOCK && pFile->locktype<PENDING_LOCK)
+ ){
+ int failed;
+ failed = _AFPFSSetLock(context->filePath, pFile->h, PENDING_BYTE, 1, 1);
+ if (failed) {
+ rc = SQLITE_BUSY;
+ goto afp_end_lock;
+ }
+ }
+
+ /* If control gets to this point, then actually go ahead and make
+ ** operating system calls for the specified lock.
+ */
+ if( locktype==SHARED_LOCK ){
+ int lk, failed;
+ int tries = 0;
+
+ /* Now get the read-lock */
+ /* note that the quality of the randomness doesn't matter that much */
+ lk = random();
+ context->sharedLockByte = (lk & 0x7fffffff)%(SHARED_SIZE - 1);
+ failed = _AFPFSSetLock(context->filePath, pFile->h,
+ SHARED_FIRST+context->sharedLockByte, 1, 1);
+
+ /* Drop the temporary PENDING lock */
+ if (_AFPFSSetLock(context->filePath, pFile->h, PENDING_BYTE, 1, 0)) {
+ rc = SQLITE_IOERR_UNLOCK; /* This should never happen */
+ goto afp_end_lock;
+ }
+
+ if( failed ){
+ rc = SQLITE_BUSY;
+ } else {
+ pFile->locktype = SHARED_LOCK;
+ }
+ }else{
+ /* The request was for a RESERVED or EXCLUSIVE lock. It is
+ ** assumed that there is a SHARED or greater lock on the file
+ ** already.
+ */
+ int failed = 0;
+ assert( 0!=pFile->locktype );
+ if (locktype >= RESERVED_LOCK && pFile->locktype < RESERVED_LOCK) {
+ /* Acquire a RESERVED lock */
+ failed = _AFPFSSetLock(context->filePath, pFile->h, RESERVED_BYTE, 1,1);
+ }
+ if (!failed && locktype == EXCLUSIVE_LOCK) {
+ /* Acquire an EXCLUSIVE lock */
+
+ /* Remove the shared lock before trying the range. we'll need to
+ ** reestablish the shared lock if we can't get the afpUnixUnlock
+ */
+ if (!_AFPFSSetLock(context->filePath, pFile->h, SHARED_FIRST +
+ context->sharedLockByte, 1, 0)) {
+ /* now attemmpt to get the exclusive lock range */
+ failed = _AFPFSSetLock(context->filePath, pFile->h, SHARED_FIRST,
+ SHARED_SIZE, 1);
+ if (failed && _AFPFSSetLock(context->filePath, pFile->h, SHARED_FIRST +
+ context->sharedLockByte, 1, 1)) {
+ rc = SQLITE_IOERR_RDLOCK; /* this should never happen */
+ }
+ } else {
+ /* */
+ rc = SQLITE_IOERR_UNLOCK; /* this should never happen */
+ }
+ }
+ if( failed && rc == SQLITE_OK){
+ rc = SQLITE_BUSY;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ pFile->locktype = locktype;
+ }else if( locktype==EXCLUSIVE_LOCK ){
+ pFile->locktype = PENDING_LOCK;
+ }
+
+afp_end_lock:
+ leaveMutex();
+ OSTRACE4("LOCK %d %s %s\n", pFile->h, locktypeName(locktype),
+ rc==SQLITE_OK ? "ok" : "failed");
+ return rc;
+}
+
+/*
+** Lower the locking level on file descriptor pFile to locktype. locktype
+** must be either NO_LOCK or SHARED_LOCK.
+**
+** If the locking level of the file descriptor is already at or below
+** the requested locking level, this routine is a no-op.
+*/
+static int afpUnixUnlock(sqlite3_file *id, int locktype) {
+ struct flock lock;
+ int rc = SQLITE_OK;
+ unixFile *pFile = (unixFile*)id;
+ afpLockingContext *context = (afpLockingContext *) pFile->lockingContext;
+
+ assert( pFile );
+ OSTRACE5("UNLOCK %d %d was %d pid=%d\n", pFile->h, locktype,
+ pFile->locktype, getpid());
+
+ assert( locktype<=SHARED_LOCK );
+ if( pFile->locktype<=locktype ){
+ return SQLITE_OK;
+ }
+ if( CHECK_THREADID(pFile) ){
+ return SQLITE_MISUSE;
+ }
+ enterMutex();
+ if( pFile->locktype>SHARED_LOCK ){
+ if( locktype==SHARED_LOCK ){
+ int failed = 0;
+
+ /* unlock the exclusive range - then re-establish the shared lock */
+ if (pFile->locktype==EXCLUSIVE_LOCK) {
+ failed = _AFPFSSetLock(context->filePath, pFile->h, SHARED_FIRST,
+ SHARED_SIZE, 0);
+ if (!failed) {
+ /* successfully removed the exclusive lock */
+ if (_AFPFSSetLock(context->filePath, pFile->h, SHARED_FIRST+
+ context->sharedLockByte, 1, 1)) {
+ /* failed to re-establish our shared lock */
+ rc = SQLITE_IOERR_RDLOCK; /* This should never happen */
+ }
+ } else {
+ /* This should never happen - failed to unlock the exclusive range */
+ rc = SQLITE_IOERR_UNLOCK;
+ }
+ }
+ }
+ if (rc == SQLITE_OK && pFile->locktype>=PENDING_LOCK) {
+ if (_AFPFSSetLock(context->filePath, pFile->h, PENDING_BYTE, 1, 0)){
+ /* failed to release the pending lock */
+ rc = SQLITE_IOERR_UNLOCK; /* This should never happen */
+ }
+ }
+ if (rc == SQLITE_OK && pFile->locktype>=RESERVED_LOCK) {
+ if (_AFPFSSetLock(context->filePath, pFile->h, RESERVED_BYTE, 1, 0)) {
+ /* failed to release the reserved lock */
+ rc = SQLITE_IOERR_UNLOCK; /* This should never happen */
+ }
+ }
+ }
+ if( locktype==NO_LOCK ){
+ int failed = _AFPFSSetLock(context->filePath, pFile->h,
+ SHARED_FIRST + context->sharedLockByte, 1, 0);
+ if (failed) {
+ rc = SQLITE_IOERR_UNLOCK; /* This should never happen */
+ }
+ }
+ if (rc == SQLITE_OK)
+ pFile->locktype = locktype;
+ leaveMutex();
+ return rc;
+}
+
+/*
+** Close a file & cleanup AFP specific locking context
+*/
+static int afpUnixClose(sqlite3_file *id) {
+ unixFile *pFile = (unixFile*)id;
+
+ if( !pFile ) return SQLITE_OK;
+ afpUnixUnlock(id, NO_LOCK);
+ sqlite3_free(pFile->lockingContext);
+ if( pFile->dirfd>=0 ) close(pFile->dirfd);
+ pFile->dirfd = -1;
+ enterMutex();
+ close(pFile->h);
+ leaveMutex();
+ OSTRACE2("CLOSE %-3d\n", pFile->h);
+ OpenCounter(-1);
+ memset(pFile, 0, sizeof(unixFile));
+ return SQLITE_OK;
+}
+
+
+#pragma mark flock() style locking
+
+/*
+** The flockLockingContext is not used
+*/
+typedef void flockLockingContext;
+
+static int flockUnixCheckReservedLock(sqlite3_file *id){
+ unixFile *pFile = (unixFile*)id;
+
+ if (pFile->locktype == RESERVED_LOCK) {
+ return 1; /* already have a reserved lock */
+ } else {
+ /* attempt to get the lock */
+ int rc = flock(pFile->h, LOCK_EX | LOCK_NB);
+ if (!rc) {
+ /* got the lock, unlock it */
+ flock(pFile->h, LOCK_UN);
+ return 0; /* no one has it reserved */
+ }
+ return 1; /* someone else might have it reserved */
+ }
+}
+
+static int flockUnixLock(sqlite3_file *id, int locktype) {
+ unixFile *pFile = (unixFile*)id;
+
+ /* if we already have a lock, it is exclusive.
+ ** Just adjust level and punt on outta here. */
+ if (pFile->locktype > NO_LOCK) {
+ pFile->locktype = locktype;
+ return SQLITE_OK;
+ }
+
+ /* grab an exclusive lock */
+ int rc = flock(pFile->h, LOCK_EX | LOCK_NB);
+ if (rc) {
+ /* didn't get, must be busy */
+ return SQLITE_BUSY;
+ } else {
+ /* got it, set the type and return ok */
+ pFile->locktype = locktype;
+ return SQLITE_OK;
+ }
+}
+
+static int flockUnixUnlock(sqlite3_file *id, int locktype) {
+ unixFile *pFile = (unixFile*)id;
+
+ assert( locktype<=SHARED_LOCK );
+
+ /* no-op if possible */
+ if( pFile->locktype==locktype ){
+ return SQLITE_OK;
+ }
+
+ /* shared can just be set because we always have an exclusive */
+ if (locktype==SHARED_LOCK) {
+ pFile->locktype = locktype;
+ return SQLITE_OK;
+ }
+
+ /* no, really, unlock. */
+ int rc = flock(pFile->h, LOCK_UN);
+ if (rc)
+ return SQLITE_IOERR_UNLOCK;
+ else {
+ pFile->locktype = NO_LOCK;
+ return SQLITE_OK;
+ }
+}
+
+/*
+** Close a file.
+*/
+static int flockUnixClose(sqlite3_file *id) {
+ unixFile *pFile = (unixFile*)id;
+
+ if( !pFile ) return SQLITE_OK;
+ flockUnixUnlock(id, NO_LOCK);
+
+ if( pFile->dirfd>=0 ) close(pFile->dirfd);
+ pFile->dirfd = -1;
+
+ enterMutex();
+ close(pFile->h);
+ leaveMutex();
+ OSTRACE2("CLOSE %-3d\n", pFile->h);
+ OpenCounter(-1);
+ memset(pFile, 0, sizeof(unixFile));
+ return SQLITE_OK;
+}
+
+#pragma mark Old-School .lock file based locking
+
+/*
+** The dotlockLockingContext structure contains all dotlock (.lock) lock
+** specific state
+*/
+typedef struct dotlockLockingContext dotlockLockingContext;
+struct dotlockLockingContext {
+ char *lockPath;
+};
+
+
+static int dotlockUnixCheckReservedLock(sqlite3_file *id) {
+ unixFile *pFile = (unixFile*)id;
+ dotlockLockingContext *context;
+
+ context = (dotlockLockingContext*)pFile->lockingContext;
+ if (pFile->locktype == RESERVED_LOCK) {
+ return 1; /* already have a reserved lock */
+ } else {
+ struct stat statBuf;
+ if (lstat(context->lockPath,&statBuf) == 0){
+ /* file exists, someone else has the lock */
+ return 1;
+ }else{
+ /* file does not exist, we could have it if we want it */
+ return 0;
+ }
+ }
+}
+
+static int dotlockUnixLock(sqlite3_file *id, int locktype) {
+ unixFile *pFile = (unixFile*)id;
+ dotlockLockingContext *context;
+ int fd;
+
+ context = (dotlockLockingContext*)pFile->lockingContext;
+
+ /* if we already have a lock, it is exclusive.
+ ** Just adjust level and punt on outta here. */
+ if (pFile->locktype > NO_LOCK) {
+ pFile->locktype = locktype;
+
+ /* Always update the timestamp on the old file */
+ utimes(context->lockPath,NULL);
+ return SQLITE_OK;
+ }
+
+ /* check to see if lock file already exists */
+ struct stat statBuf;
+ if (lstat(context->lockPath,&statBuf) == 0){
+ return SQLITE_BUSY; /* it does, busy */
+ }
+
+ /* grab an exclusive lock */
+ fd = open(context->lockPath,O_RDONLY|O_CREAT|O_EXCL,0600);
+ if( fd<0 ){
+ /* failed to open/create the file, someone else may have stolen the lock */
+ return SQLITE_BUSY;
+ }
+ close(fd);
+
+ /* got it, set the type and return ok */
+ pFile->locktype = locktype;
+ return SQLITE_OK;
+}
+
+static int dotlockUnixUnlock(sqlite3_file *id, int locktype) {
+ unixFile *pFile = (unixFile*)id;
+ dotlockLockingContext *context;
+
+ context = (dotlockLockingContext*)pFile->lockingContext;
+
+ assert( locktype<=SHARED_LOCK );
+
+ /* no-op if possible */
+ if( pFile->locktype==locktype ){
+ return SQLITE_OK;
+ }
+
+ /* shared can just be set because we always have an exclusive */
+ if (locktype==SHARED_LOCK) {
+ pFile->locktype = locktype;
+ return SQLITE_OK;
+ }
+
+ /* no, really, unlock. */
+ unlink(context->lockPath);
+ pFile->locktype = NO_LOCK;
+ return SQLITE_OK;
+}
+
+/*
+ ** Close a file.
+ */
+static int dotlockUnixClose(sqlite3_file *id) {
+ unixFile *pFile = (unixFile*)id;
+
+ if( !pFile ) return SQLITE_OK;
+ dotlockUnixUnlock(id, NO_LOCK);
+ sqlite3_free(pFile->lockingContext);
+ if( pFile->dirfd>=0 ) close(pFile->dirfd);
+ pFile->dirfd = -1;
+ enterMutex();
+ close(pFile->h);
+ leaveMutex();
+ OSTRACE2("CLOSE %-3d\n", pFile->h);
+ OpenCounter(-1);
+ memset(pFile, 0, sizeof(unixFile));
+ return SQLITE_OK;
+}
+
+
+#pragma mark No locking
+
+/*
+** The nolockLockingContext is void
+*/
+typedef void nolockLockingContext;
+
+static int nolockUnixCheckReservedLock(sqlite3_file *id) {
+ return 0;
+}
+
+static int nolockUnixLock(sqlite3_file *id, int locktype) {
+ return SQLITE_OK;
+}
+
+static int nolockUnixUnlock(sqlite3_file *id, int locktype) {
+ return SQLITE_OK;
+}
+
+/*
+** Close a file.
+*/
+static int nolockUnixClose(sqlite3_file *id) {
+ unixFile *pFile = (unixFile*)id;
+
+ if( !pFile ) return SQLITE_OK;
+ if( pFile->dirfd>=0 ) close(pFile->dirfd);
+ pFile->dirfd = -1;
+ enterMutex();
+ close(pFile->h);
+ leaveMutex();
+ OSTRACE2("CLOSE %-3d\n", pFile->h);
+ OpenCounter(-1);
+ memset(pFile, 0, sizeof(unixFile));
+ return SQLITE_OK;
+}
+
+#endif /* SQLITE_ENABLE_LOCKING_STYLE */
+
+
+/*
+** Information and control of an open file handle.
+*/
+static int unixFileControl(sqlite3_file *id, int op, void *pArg){
+ switch( op ){
+ case SQLITE_FCNTL_LOCKSTATE: {
+ *(int*)pArg = ((unixFile*)id)->locktype;
+ return SQLITE_OK;
+ }
+ }
+ return SQLITE_ERROR;
+}
+
+/*
+** Return the sector size in bytes of the underlying block device for
+** the specified file. This is almost always 512 bytes, but may be
+** larger for some devices.
+**
+** SQLite code assumes this function cannot fail. It also assumes that
+** if two files are created in the same file-system directory (i.e.
+** a database and its journal file) that the sector size will be the
+** same for both.
+*/
+static int unixSectorSize(sqlite3_file *id){
+ return SQLITE_DEFAULT_SECTOR_SIZE;
+}
+
+/*
+** Return the device characteristics for the file. This is always 0.
+*/
+static int unixDeviceCharacteristics(sqlite3_file *id){
+ return 0;
+}
+
+/*
+** This vector defines all the methods that can operate on an sqlite3_file
+** for unix.
+*/
+static const sqlite3_io_methods sqlite3UnixIoMethod = {
+ 1, /* iVersion */
+ unixClose,
+ unixRead,
+ unixWrite,
+ unixTruncate,
+ unixSync,
+ unixFileSize,
+ unixLock,
+ unixUnlock,
+ unixCheckReservedLock,
+ unixFileControl,
+ unixSectorSize,
+ unixDeviceCharacteristics
+};
+
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+/*
+** This vector defines all the methods that can operate on an sqlite3_file
+** for unix with AFP style file locking.
+*/
+static const sqlite3_io_methods sqlite3AFPLockingUnixIoMethod = {
+ 1, /* iVersion */
+ afpUnixClose,
+ unixRead,
+ unixWrite,
+ unixTruncate,
+ unixSync,
+ unixFileSize,
+ afpUnixLock,
+ afpUnixUnlock,
+ afpUnixCheckReservedLock,
+ unixFileControl,
+ unixSectorSize,
+ unixDeviceCharacteristics
+};
+
+/*
+** This vector defines all the methods that can operate on an sqlite3_file
+** for unix with flock() style file locking.
+*/
+static const sqlite3_io_methods sqlite3FlockLockingUnixIoMethod = {
+ 1, /* iVersion */
+ flockUnixClose,
+ unixRead,
+ unixWrite,
+ unixTruncate,
+ unixSync,
+ unixFileSize,
+ flockUnixLock,
+ flockUnixUnlock,
+ flockUnixCheckReservedLock,
+ unixFileControl,
+ unixSectorSize,
+ unixDeviceCharacteristics
+};
+
+/*
+** This vector defines all the methods that can operate on an sqlite3_file
+** for unix with dotlock style file locking.
+*/
+static const sqlite3_io_methods sqlite3DotlockLockingUnixIoMethod = {
+ 1, /* iVersion */
+ dotlockUnixClose,
+ unixRead,
+ unixWrite,
+ unixTruncate,
+ unixSync,
+ unixFileSize,
+ dotlockUnixLock,
+ dotlockUnixUnlock,
+ dotlockUnixCheckReservedLock,
+ unixFileControl,
+ unixSectorSize,
+ unixDeviceCharacteristics
+};
+
+/*
+** This vector defines all the methods that can operate on an sqlite3_file
+** for unix with nolock style file locking.
+*/
+static const sqlite3_io_methods sqlite3NolockLockingUnixIoMethod = {
+ 1, /* iVersion */
+ nolockUnixClose,
+ unixRead,
+ unixWrite,
+ unixTruncate,
+ unixSync,
+ unixFileSize,
+ nolockUnixLock,
+ nolockUnixUnlock,
+ nolockUnixCheckReservedLock,
+ unixFileControl,
+ unixSectorSize,
+ unixDeviceCharacteristics
+};
+
+#endif /* SQLITE_ENABLE_LOCKING_STYLE */
+
+/*
+** Allocate memory for a new unixFile and initialize that unixFile.
+** Write a pointer to the new unixFile into *pId.
+** If we run out of memory, close the file and return an error.
+*/
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+/*
+** When locking extensions are enabled, the filepath and locking style
+** are needed to determine the unixFile pMethod to use for locking operations.
+** The locking-style specific lockingContext data structure is created
+** and assigned here also.
+*/
+static int fillInUnixFile(
+ int h, /* Open file descriptor of file being opened */
+ int dirfd, /* Directory file descriptor */
+ sqlite3_file *pId, /* Write to the unixFile structure here */
+ const char *zFilename /* Name of the file being opened */
+){
+ sqlite3LockingStyle lockingStyle;
+ unixFile *pNew = (unixFile *)pId;
+ int rc;
+
+#ifdef FD_CLOEXEC
+ fcntl(h, F_SETFD, fcntl(h, F_GETFD, 0) | FD_CLOEXEC);
+#endif
+
+ lockingStyle = sqlite3DetectLockingStyle(zFilename, h);
+ if ( lockingStyle==posixLockingStyle ){
+ enterMutex();
+ rc = findLockInfo(h, &pNew->pLock, &pNew->pOpen);
+ leaveMutex();
+ if( rc ){
+ if( dirfd>=0 ) close(dirfd);
+ close(h);
+ return rc;
+ }
+ } else {
+ /* pLock and pOpen are only used for posix advisory locking */
+ pNew->pLock = NULL;
+ pNew->pOpen = NULL;
+ }
+
+ OSTRACE3("OPEN %-3d %s\n", h, zFilename);
+ pNew->dirfd = -1;
+ pNew->h = h;
+ pNew->dirfd = dirfd;
+ SET_THREADID(pNew);
+
+ switch(lockingStyle) {
+ case afpLockingStyle: {
+ /* afp locking uses the file path so it needs to be included in
+ ** the afpLockingContext */
+ afpLockingContext *context;
+ pNew->pMethod = &sqlite3AFPLockingUnixIoMethod;
+ pNew->lockingContext = context = sqlite3_malloc( sizeof(*context) );
+ if( context==0 ){
+ close(h);
+ if( dirfd>=0 ) close(dirfd);
+ return SQLITE_NOMEM;
+ }
+
+ /* NB: zFilename exists and remains valid until the file is closed
+ ** according to requirement F11141. So we do not need to make a
+ ** copy of the filename. */
+ context->filePath = zFilename;
+ srandomdev();
+ break;
+ }
+ case flockLockingStyle:
+ /* flock locking doesn't need additional lockingContext information */
+ pNew->pMethod = &sqlite3FlockLockingUnixIoMethod;
+ break;
+ case dotlockLockingStyle: {
+ /* dotlock locking uses the file path so it needs to be included in
+ ** the dotlockLockingContext */
+ dotlockLockingContext *context;
+ int nFilename;
+ nFilename = strlen(zFilename);
+ pNew->pMethod = &sqlite3DotlockLockingUnixIoMethod;
+ pNew->lockingContext = context =
+ sqlite3_malloc( sizeof(*context) + nFilename + 6 );
+ if( context==0 ){
+ close(h);
+ if( dirfd>=0 ) close(dirfd);
+ return SQLITE_NOMEM;
+ }
+ context->lockPath = (char*)&context[1];
+ sqlite3_snprintf(nFilename, context->lockPath,
+ "%s.lock", zFilename);
+ break;
+ }
+ case posixLockingStyle:
+ /* posix locking doesn't need additional lockingContext information */
+ pNew->pMethod = &sqlite3UnixIoMethod;
+ break;
+ case noLockingStyle:
+ case unsupportedLockingStyle:
+ default:
+ pNew->pMethod = &sqlite3NolockLockingUnixIoMethod;
+ }
+ OpenCounter(+1);
+ return SQLITE_OK;
+}
+#else /* SQLITE_ENABLE_LOCKING_STYLE */
+static int fillInUnixFile(
+ int h, /* Open file descriptor on file being opened */
+ int dirfd,
+ sqlite3_file *pId, /* Write to the unixFile structure here */
+ const char *zFilename /* Name of the file being opened */
+){
+ unixFile *pNew = (unixFile *)pId;
+ int rc;
+
+#ifdef FD_CLOEXEC
+ fcntl(h, F_SETFD, fcntl(h, F_GETFD, 0) | FD_CLOEXEC);
+#endif
+
+ enterMutex();
+ rc = findLockInfo(h, &pNew->pLock, &pNew->pOpen);
+ leaveMutex();
+ if( rc ){
+ if( dirfd>=0 ) close(dirfd);
+ close(h);
+ return rc;
+ }
+
+ OSTRACE3("OPEN %-3d %s\n", h, zFilename);
+ pNew->dirfd = -1;
+ pNew->h = h;
+ pNew->dirfd = dirfd;
+ SET_THREADID(pNew);
+
+ pNew->pMethod = &sqlite3UnixIoMethod;
+ OpenCounter(+1);
+ return SQLITE_OK;
+}
+#endif /* SQLITE_ENABLE_LOCKING_STYLE */
+
+/*
+** Open a file descriptor to the directory containing file zFilename.
+** If successful, *pFd is set to the opened file descriptor and
+** SQLITE_OK is returned. If an error occurs, either SQLITE_NOMEM
+** or SQLITE_CANTOPEN is returned and *pFd is set to an undefined
+** value.
+**
+** If SQLITE_OK is returned, the caller is responsible for closing
+** the file descriptor *pFd using close().
+*/
+static int openDirectory(const char *zFilename, int *pFd){
+ int ii;
+ int fd = -1;
+ char zDirname[MAX_PATHNAME+1];
+
+ sqlite3_snprintf(MAX_PATHNAME, zDirname, "%s", zFilename);
+ for(ii=strlen(zDirname); ii>=0 && zDirname[ii]!='/'; ii--);
+ if( ii>0 ){
+ zDirname[ii] = '\0';
+ fd = open(zDirname, O_RDONLY|O_BINARY, 0);
+ if( fd>=0 ){
+#ifdef FD_CLOEXEC
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
+#endif
+ OSTRACE3("OPENDIR %-3d %s\n", fd, zDirname);
+ }
+ }
+ *pFd = fd;
+ return (fd>=0?SQLITE_OK:SQLITE_CANTOPEN);
+}
+
+/*
+** Open the file zPath.
+**
+** Previously, the SQLite OS layer used three functions in place of this
+** one:
+**
+** sqlite3OsOpenReadWrite();
+** sqlite3OsOpenReadOnly();
+** sqlite3OsOpenExclusive();
+**
+** These calls correspond to the following combinations of flags:
+**
+** ReadWrite() -> (READWRITE | CREATE)
+** ReadOnly() -> (READONLY)
+** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE)
+**
+** The old OpenExclusive() accepted a boolean argument - "delFlag". If
+** true, the file was configured to be automatically deleted when the
+** file handle closed. To achieve the same effect using this new
+** interface, add the DELETEONCLOSE flag to those specified above for
+** OpenExclusive().
+*/
+static int unixOpen(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ sqlite3_file *pFile,
+ int flags,
+ int *pOutFlags
+){
+ int fd = 0; /* File descriptor returned by open() */
+ int dirfd = -1; /* Directory file descriptor */
+ int oflags = 0; /* Flags to pass to open() */
+ int eType = flags&0xFFFFFF00; /* Type of file to open */
+
+ int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE);
+ int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE);
+ int isCreate = (flags & SQLITE_OPEN_CREATE);
+ int isReadonly = (flags & SQLITE_OPEN_READONLY);
+ int isReadWrite = (flags & SQLITE_OPEN_READWRITE);
+
+ /* If creating a master or main-file journal, this function will open
+ ** a file-descriptor on the directory too. The first time unixSync()
+ ** is called the directory file descriptor will be fsync()ed and close()d.
+ */
+ int isOpenDirectory = (isCreate &&
+ (eType==SQLITE_OPEN_MASTER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL)
+ );
+
+ /* Check the following statements are true:
+ **
+ ** (a) Exactly one of the READWRITE and READONLY flags must be set, and
+ ** (b) if CREATE is set, then READWRITE must also be set, and
+ ** (c) if EXCLUSIVE is set, then CREATE must also be set.
+ ** (d) if DELETEONCLOSE is set, then CREATE must also be set.
+ */
+ assert((isReadonly==0 || isReadWrite==0) && (isReadWrite || isReadonly));
+ assert(isCreate==0 || isReadWrite);
+ assert(isExclusive==0 || isCreate);
+ assert(isDelete==0 || isCreate);
+
+
+ /* The main DB, main journal, and master journal are never automatically
+ ** deleted
+ */
+ assert( eType!=SQLITE_OPEN_MAIN_DB || !isDelete );
+ assert( eType!=SQLITE_OPEN_MAIN_JOURNAL || !isDelete );
+ assert( eType!=SQLITE_OPEN_MASTER_JOURNAL || !isDelete );
+
+ /* Assert that the upper layer has set one of the "file-type" flags. */
+ assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB
+ || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL
+ || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL
+ || eType==SQLITE_OPEN_TRANSIENT_DB
+ );
+
+ if( isReadonly ) oflags |= O_RDONLY;
+ if( isReadWrite ) oflags |= O_RDWR;
+ if( isCreate ) oflags |= O_CREAT;
+ if( isExclusive ) oflags |= (O_EXCL|O_NOFOLLOW);
+ oflags |= (O_LARGEFILE|O_BINARY);
+
+ memset(pFile, 0, sizeof(unixFile));
+ fd = open(zPath, oflags, isDelete?0600:SQLITE_DEFAULT_FILE_PERMISSIONS);
+ if( fd<0 && errno!=EISDIR && isReadWrite && !isExclusive ){
+ /* Failed to open the file for read/write access. Try read-only. */
+ flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
+ flags |= SQLITE_OPEN_READONLY;
+ return unixOpen(pVfs, zPath, pFile, flags, pOutFlags);
+ }
+ if( fd<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ if( isDelete ){
+ unlink(zPath);
+ }
+ if( pOutFlags ){
+ *pOutFlags = flags;
+ }
+
+ assert(fd!=0);
+ if( isOpenDirectory ){
+ int rc = openDirectory(zPath, &dirfd);
+ if( rc!=SQLITE_OK ){
+ close(fd);
+ return rc;
+ }
+ }
+ return fillInUnixFile(fd, dirfd, pFile, zPath);
+}
+
+/*
+** Delete the file at zPath. If the dirSync argument is true, fsync()
+** the directory after deleting the file.
+*/
+static int unixDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ int rc = SQLITE_OK;
+ SimulateIOError(return SQLITE_IOERR_DELETE);
+ unlink(zPath);
+ if( dirSync ){
+ int fd;
+ rc = openDirectory(zPath, &fd);
+ if( rc==SQLITE_OK ){
+ if( fsync(fd) ){
+ rc = SQLITE_IOERR_DIR_FSYNC;
+ }
+ close(fd);
+ }
+ }
+ return rc;
+}
+
+/*
+** Test the existance of or access permissions of file zPath. The
+** test performed depends on the value of flags:
+**
+** SQLITE_ACCESS_EXISTS: Return 1 if the file exists
+** SQLITE_ACCESS_READWRITE: Return 1 if the file is read and writable.
+** SQLITE_ACCESS_READONLY: Return 1 if the file is readable.
+**
+** Otherwise return 0.
+*/
+static int unixAccess(sqlite3_vfs *pVfs, const char *zPath, int flags){
+ int amode = 0;
+ switch( flags ){
+ case SQLITE_ACCESS_EXISTS:
+ amode = F_OK;
+ break;
+ case SQLITE_ACCESS_READWRITE:
+ amode = W_OK|R_OK;
+ break;
+ case SQLITE_ACCESS_READ:
+ amode = R_OK;
+ break;
+
+ default:
+ assert(!"Invalid flags argument");
+ }
+ return (access(zPath, amode)==0);
+}
+
+/*
+** Create a temporary file name in zBuf. zBuf must be allocated
+** by the calling process and must be big enough to hold at least
+** pVfs->mxPathname bytes.
+*/
+static int unixGetTempname(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
+ static const char *azDirs[] = {
+ 0,
+ "/var/tmp",
+ "/usr/tmp",
+ "/tmp",
+ ".",
+ };
+ static const unsigned char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ struct stat buf;
+ const char *zDir = ".";
+
+ /* It's odd to simulate an io-error here, but really this is just
+ ** using the io-error infrastructure to test that SQLite handles this
+ ** function failing.
+ */
+ SimulateIOError( return SQLITE_ERROR );
+
+ azDirs[0] = sqlite3_temp_directory;
+ for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); i++){
+ if( azDirs[i]==0 ) continue;
+ if( stat(azDirs[i], &buf) ) continue;
+ if( !S_ISDIR(buf.st_mode) ) continue;
+ if( access(azDirs[i], 07) ) continue;
+ zDir = azDirs[i];
+ break;
+ }
+
+ /* Check that the output buffer is large enough for the temporary file
+ ** name. If it is not, return SQLITE_ERROR.
+ */
+ if( (strlen(zDir) + strlen(SQLITE_TEMP_FILE_PREFIX) + 17) >= nBuf ){
+ return SQLITE_ERROR;
+ }
+
+ do{
+ assert( pVfs->mxPathname==MAX_PATHNAME );
+ sqlite3_snprintf(nBuf-17, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX, zDir);
+ j = strlen(zBuf);
+ sqlite3_randomness(15, &zBuf[j]);
+ for(i=0; i<15; i++, j++){
+ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
+ }
+ zBuf[j] = 0;
+ }while( access(zBuf,0)==0 );
+ return SQLITE_OK;
+}
+
+
+/*
+** Turn a relative pathname into a full pathname. The relative path
+** is stored as a nul-terminated string in the buffer pointed to by
+** zPath.
+**
+** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes
+** (in this case, MAX_PATHNAME bytes). The full-path is written to
+** this buffer before returning.
+*/
+static int unixFullPathname(
+ sqlite3_vfs *pVfs, /* Pointer to vfs object */
+ const char *zPath, /* Possibly relative input path */
+ int nOut, /* Size of output buffer in bytes */
+ char *zOut /* Output buffer */
+){
+
+ /* It's odd to simulate an io-error here, but really this is just
+ ** using the io-error infrastructure to test that SQLite handles this
+ ** function failing. This function could fail if, for example, the
+ ** current working directly has been unlinked.
+ */
+ SimulateIOError( return SQLITE_ERROR );
+
+ assert( pVfs->mxPathname==MAX_PATHNAME );
+ zOut[nOut-1] = '\0';
+ if( zPath[0]=='/' ){
+ sqlite3_snprintf(nOut, zOut, "%s", zPath);
+ }else{
+ int nCwd;
+ if( getcwd(zOut, nOut-1)==0 ){
+ return SQLITE_CANTOPEN;
+ }
+ nCwd = strlen(zOut);
+ sqlite3_snprintf(nOut-nCwd, &zOut[nCwd], "/%s", zPath);
+ }
+ return SQLITE_OK;
+
+#if 0
+ /*
+ ** Remove "/./" path elements and convert "/A/./" path elements
+ ** to just "/".
+ */
+ if( zFull ){
+ int i, j;
+ for(i=j=0; zFull[i]; i++){
+ if( zFull[i]=='/' ){
+ if( zFull[i+1]=='/' ) continue;
+ if( zFull[i+1]=='.' && zFull[i+2]=='/' ){
+ i += 1;
+ continue;
+ }
+ if( zFull[i+1]=='.' && zFull[i+2]=='.' && zFull[i+3]=='/' ){
+ while( j>0 && zFull[j-1]!='/' ){ j--; }
+ i += 3;
+ continue;
+ }
+ }
+ zFull[j++] = zFull[i];
+ }
+ zFull[j] = 0;
+ }
+#endif
+}
+
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+/*
+** Interfaces for opening a shared library, finding entry points
+** within the shared library, and closing the shared library.
+*/
+#include <dlfcn.h>
+static void *unixDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
+ return dlopen(zFilename, RTLD_NOW | RTLD_GLOBAL);
+}
+
+/*
+** SQLite calls this function immediately after a call to unixDlSym() or
+** unixDlOpen() fails (returns a null pointer). If a more detailed error
+** message is available, it is written to zBufOut. If no error message
+** is available, zBufOut is left unmodified and SQLite uses a default
+** error message.
+*/
+static void unixDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){
+ char *zErr;
+ enterMutex();
+ zErr = dlerror();
+ if( zErr ){
+ sqlite3_snprintf(nBuf, zBufOut, "%s", zErr);
+ }
+ leaveMutex();
+}
+static void *unixDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol){
+ return dlsym(pHandle, zSymbol);
+}
+static void unixDlClose(sqlite3_vfs *pVfs, void *pHandle){
+ dlclose(pHandle);
+}
+#else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */
+ #define unixDlOpen 0
+ #define unixDlError 0
+ #define unixDlSym 0
+ #define unixDlClose 0
+#endif
+
+/*
+** Write nBuf bytes of random data to the supplied buffer zBuf.
+*/
+static int unixRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
+
+ assert(nBuf>=(sizeof(time_t)+sizeof(int)));
+
+ /* We have to initialize zBuf to prevent valgrind from reporting
+ ** errors. The reports issued by valgrind are incorrect - we would
+ ** prefer that the randomness be increased by making use of the
+ ** uninitialized space in zBuf - but valgrind errors tend to worry
+ ** some users. Rather than argue, it seems easier just to initialize
+ ** the whole array and silence valgrind, even if that means less randomness
+ ** in the random seed.
+ **
+ ** When testing, initializing zBuf[] to zero is all we do. That means
+ ** that we always use the same random number sequence. This makes the
+ ** tests repeatable.
+ */
+ memset(zBuf, 0, nBuf);
+#if !defined(SQLITE_TEST)
+ {
+ int pid, fd;
+ fd = open("/dev/urandom", O_RDONLY);
+ if( fd<0 ){
+ time_t t;
+ time(&t);
+ memcpy(zBuf, &t, sizeof(t));
+ pid = getpid();
+ memcpy(&zBuf[sizeof(t)], &pid, sizeof(pid));
+ }else{
+ read(fd, zBuf, nBuf);
+ close(fd);
+ }
+ }
+#endif
+ return SQLITE_OK;
+}
+
+
+/*
+** Sleep for a little while. Return the amount of time slept.
+** The argument is the number of microseconds we want to sleep.
+** The return value is the number of microseconds of sleep actually
+** requested from the underlying operating system, a number which
+** might be greater than or equal to the argument, but not less
+** than the argument.
+*/
+static int unixSleep(sqlite3_vfs *pVfs, int microseconds){
+#if defined(HAVE_USLEEP) && HAVE_USLEEP
+ usleep(microseconds);
+ return microseconds;
+#else
+ int seconds = (microseconds+999999)/1000000;
+ sleep(seconds);
+ return seconds*1000000;
+#endif
+}
+
+/*
+** The following variable, if set to a non-zero value, becomes the result
+** returned from sqlite3OsCurrentTime(). This is used for testing.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_current_time = 0;
+#endif
+
+/*
+** Find the current time (in Universal Coordinated Time). Write the
+** current time and date as a Julian Day number into *prNow and
+** return 0. Return 1 if the time and date cannot be found.
+*/
+static int unixCurrentTime(sqlite3_vfs *pVfs, double *prNow){
+#ifdef NO_GETTOD
+ time_t t;
+ time(&t);
+ *prNow = t/86400.0 + 2440587.5;
+#else
+ struct timeval sNow;
+ gettimeofday(&sNow, 0);
+ *prNow = 2440587.5 + sNow.tv_sec/86400.0 + sNow.tv_usec/86400000000.0;
+#endif
+#ifdef SQLITE_TEST
+ if( sqlite3_current_time ){
+ *prNow = sqlite3_current_time/86400.0 + 2440587.5;
+ }
+#endif
+ return 0;
+}
+
+/*
+** Return a pointer to the sqlite3DefaultVfs structure. We use
+** a function rather than give the structure global scope because
+** some compilers (MSVC) do not allow forward declarations of
+** initialized structures.
+*/
+SQLITE_PRIVATE sqlite3_vfs *sqlite3OsDefaultVfs(void){
+ static sqlite3_vfs unixVfs = {
+ 1, /* iVersion */
+ sizeof(unixFile), /* szOsFile */
+ MAX_PATHNAME, /* mxPathname */
+ 0, /* pNext */
+ "unix", /* zName */
+ 0, /* pAppData */
+
+ unixOpen, /* xOpen */
+ unixDelete, /* xDelete */
+ unixAccess, /* xAccess */
+ unixGetTempname, /* xGetTempName */
+ unixFullPathname, /* xFullPathname */
+ unixDlOpen, /* xDlOpen */
+ unixDlError, /* xDlError */
+ unixDlSym, /* xDlSym */
+ unixDlClose, /* xDlClose */
+ unixRandomness, /* xRandomness */
+ unixSleep, /* xSleep */
+ unixCurrentTime /* xCurrentTime */
+ };
+
+ return &unixVfs;
+}
+
+#endif /* OS_UNIX */
+
+/************** End of os_unix.c *********************************************/
+/************** Begin file os_win.c ******************************************/
+/*
+** 2004 May 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains code that is specific to windows.
+*/
+#if OS_WIN /* This file is used for windows only */
+
+
+/*
+** A Note About Memory Allocation:
+**
+** This driver uses malloc()/free() directly rather than going through
+** the SQLite-wrappers sqlite3_malloc()/sqlite3_free(). Those wrappers
+** are designed for use on embedded systems where memory is scarce and
+** malloc failures happen frequently. Win32 does not typically run on
+** embedded systems, and when it does the developers normally have bigger
+** problems to worry about than running out of memory. So there is not
+** a compelling need to use the wrappers.
+**
+** But there is a good reason to not use the wrappers. If we use the
+** wrappers then we will get simulated malloc() failures within this
+** driver. And that causes all kinds of problems for our tests. We
+** could enhance SQLite to deal with simulated malloc failures within
+** the OS driver, but the code to deal with those failure would not
+** be exercised on Linux (which does not need to malloc() in the driver)
+** and so we would have difficulty writing coverage tests for that
+** code. Better to leave the code out, we think.
+**
+** The point of this discussion is as follows: When creating a new
+** OS layer for an embedded system, if you use this file as an example,
+** avoid the use of malloc()/free(). Those routines work ok on windows
+** desktops but not so well in embedded systems.
+*/
+
+#include <winbase.h>
+
+#ifdef __CYGWIN__
+# include <sys/cygwin.h>
+#endif
+
+/*
+** Macros used to determine whether or not to use threads.
+*/
+#if defined(THREADSAFE) && THREADSAFE
+# define SQLITE_W32_THREADS 1
+#endif
+
+/*
+** Include code that is common to all os_*.c files
+*/
+/************** Include os_common.h in the middle of os_win.c ****************/
+/************** Begin file os_common.h ***************************************/
+/*
+** 2004 May 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains macros and a little bit of code that is common to
+** all of the platform-specific files (os_*.c) and is #included into those
+** files.
+**
+** This file should be #included by the os_*.c files only. It is not a
+** general purpose header file.
+*/
+
+/*
+** At least two bugs have slipped in because we changed the MEMORY_DEBUG
+** macro to SQLITE_DEBUG and some older makefiles have not yet made the
+** switch. The following code should catch this problem at compile-time.
+*/
+#ifdef MEMORY_DEBUG
+# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead."
+#endif
+
+
+/*
+ * When testing, this global variable stores the location of the
+ * pending-byte in the database file.
+ */
+#ifdef SQLITE_TEST
+SQLITE_API unsigned int sqlite3_pending_byte = 0x40000000;
+#endif
+
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3OSTrace = 0;
+#define OSTRACE1(X) if( sqlite3OSTrace ) sqlite3DebugPrintf(X)
+#define OSTRACE2(X,Y) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y)
+#define OSTRACE3(X,Y,Z) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z)
+#define OSTRACE4(X,Y,Z,A) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z,A)
+#define OSTRACE5(X,Y,Z,A,B) if( sqlite3OSTrace ) sqlite3DebugPrintf(X,Y,Z,A,B)
+#define OSTRACE6(X,Y,Z,A,B,C) \
+ if(sqlite3OSTrace) sqlite3DebugPrintf(X,Y,Z,A,B,C)
+#define OSTRACE7(X,Y,Z,A,B,C,D) \
+ if(sqlite3OSTrace) sqlite3DebugPrintf(X,Y,Z,A,B,C,D)
+#else
+#define OSTRACE1(X)
+#define OSTRACE2(X,Y)
+#define OSTRACE3(X,Y,Z)
+#define OSTRACE4(X,Y,Z,A)
+#define OSTRACE5(X,Y,Z,A,B)
+#define OSTRACE6(X,Y,Z,A,B,C)
+#define OSTRACE7(X,Y,Z,A,B,C,D)
+#endif
+
+/*
+** Macros for performance tracing. Normally turned off. Only works
+** on i486 hardware.
+*/
+#ifdef SQLITE_PERFORMANCE_TRACE
+__inline__ unsigned long long int hwtime(void){
+ unsigned long long int x;
+ __asm__("rdtsc\n\t"
+ "mov %%edx, %%ecx\n\t"
+ :"=A" (x));
+ return x;
+}
+static unsigned long long int g_start;
+static unsigned int elapse;
+#define TIMER_START g_start=hwtime()
+#define TIMER_END elapse=hwtime()-g_start
+#define TIMER_ELAPSED elapse
+#else
+#define TIMER_START
+#define TIMER_END
+#define TIMER_ELAPSED 0
+#endif
+
+/*
+** If we compile with the SQLITE_TEST macro set, then the following block
+** of code will give us the ability to simulate a disk I/O error. This
+** is used for testing the I/O recovery logic.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_io_error_hit = 0; /* Total number of I/O Errors */
+SQLITE_API int sqlite3_io_error_hardhit = 0; /* Number of non-benign errors */
+SQLITE_API int sqlite3_io_error_pending = 0; /* Count down to first I/O error */
+SQLITE_API int sqlite3_io_error_persist = 0; /* True if I/O errors persist */
+SQLITE_API int sqlite3_io_error_benign = 0; /* True if errors are benign */
+SQLITE_API int sqlite3_diskfull_pending = 0;
+SQLITE_API int sqlite3_diskfull = 0;
+#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X)
+#define SimulateIOError(CODE) \
+ if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \
+ || sqlite3_io_error_pending-- == 1 ) \
+ { local_ioerr(); CODE; }
+static void local_ioerr(){
+ IOTRACE(("IOERR\n"));
+ sqlite3_io_error_hit++;
+ if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++;
+}
+#define SimulateDiskfullError(CODE) \
+ if( sqlite3_diskfull_pending ){ \
+ if( sqlite3_diskfull_pending == 1 ){ \
+ local_ioerr(); \
+ sqlite3_diskfull = 1; \
+ sqlite3_io_error_hit = 1; \
+ CODE; \
+ }else{ \
+ sqlite3_diskfull_pending--; \
+ } \
+ }
+#else
+#define SimulateIOErrorBenign(X)
+#define SimulateIOError(A)
+#define SimulateDiskfullError(A)
+#endif
+
+/*
+** When testing, keep a count of the number of open files.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_open_file_count = 0;
+#define OpenCounter(X) sqlite3_open_file_count+=(X)
+#else
+#define OpenCounter(X)
+#endif
+
+/************** End of os_common.h *******************************************/
+/************** Continuing where we left off in os_win.c *********************/
+
+/*
+** Determine if we are dealing with WindowsCE - which has a much
+** reduced API.
+*/
+#if defined(_WIN32_WCE)
+# define OS_WINCE 1
+# define AreFileApisANSI() 1
+#else
+# define OS_WINCE 0
+#endif
+
+/*
+** WinCE lacks native support for file locking so we have to fake it
+** with some code of our own.
+*/
+#if OS_WINCE
+typedef struct winceLock {
+ int nReaders; /* Number of reader locks obtained */
+ BOOL bPending; /* Indicates a pending lock has been obtained */
+ BOOL bReserved; /* Indicates a reserved lock has been obtained */
+ BOOL bExclusive; /* Indicates an exclusive lock has been obtained */
+} winceLock;
+#endif
+
+/*
+** The winFile structure is a subclass of sqlite3_file* specific to the win32
+** portability layer.
+*/
+typedef struct winFile winFile;
+struct winFile {
+ const sqlite3_io_methods *pMethod;/* Must be first */
+ HANDLE h; /* Handle for accessing the file */
+ unsigned char locktype; /* Type of lock currently held on this file */
+ short sharedLockByte; /* Randomly chosen byte used as a shared lock */
+#if OS_WINCE
+ WCHAR *zDeleteOnClose; /* Name of file to delete when closing */
+ HANDLE hMutex; /* Mutex used to control access to shared lock */
+ HANDLE hShared; /* Shared memory segment used for locking */
+ winceLock local; /* Locks obtained by this instance of winFile */
+ winceLock *shared; /* Global shared lock memory for the file */
+#endif
+};
+
+
+/*
+** The following variable is (normally) set once and never changes
+** thereafter. It records whether the operating system is Win95
+** or WinNT.
+**
+** 0: Operating system unknown.
+** 1: Operating system is Win95.
+** 2: Operating system is WinNT.
+**
+** In order to facilitate testing on a WinNT system, the test fixture
+** can manually set this value to 1 to emulate Win98 behavior.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_os_type = 0;
+#else
+static int sqlite3_os_type = 0;
+#endif
+
+/*
+** Return true (non-zero) if we are running under WinNT, Win2K, WinXP,
+** or WinCE. Return false (zero) for Win95, Win98, or WinME.
+**
+** Here is an interesting observation: Win95, Win98, and WinME lack
+** the LockFileEx() API. But we can still statically link against that
+** API as long as we don't call it win running Win95/98/ME. A call to
+** this routine is used to determine if the host is Win95/98/ME or
+** WinNT/2K/XP so that we will know whether or not we can safely call
+** the LockFileEx() API.
+*/
+#if OS_WINCE
+# define isNT() (1)
+#else
+ static int isNT(void){
+ if( sqlite3_os_type==0 ){
+ OSVERSIONINFO sInfo;
+ sInfo.dwOSVersionInfoSize = sizeof(sInfo);
+ GetVersionEx(&sInfo);
+ sqlite3_os_type = sInfo.dwPlatformId==VER_PLATFORM_WIN32_NT ? 2 : 1;
+ }
+ return sqlite3_os_type==2;
+ }
+#endif /* OS_WINCE */
+
+/*
+** Convert a UTF-8 string to microsoft unicode (UTF-16?).
+**
+** Space to hold the returned string is obtained from malloc.
+*/
+static WCHAR *utf8ToUnicode(const char *zFilename){
+ int nChar;
+ WCHAR *zWideFilename;
+
+ nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0);
+ zWideFilename = malloc( nChar*sizeof(zWideFilename[0]) );
+ if( zWideFilename==0 ){
+ return 0;
+ }
+ nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar);
+ if( nChar==0 ){
+ free(zWideFilename);
+ zWideFilename = 0;
+ }
+ return zWideFilename;
+}
+
+/*
+** Convert microsoft unicode to UTF-8. Space to hold the returned string is
+** obtained from malloc().
+*/
+static char *unicodeToUtf8(const WCHAR *zWideFilename){
+ int nByte;
+ char *zFilename;
+
+ nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
+ zFilename = malloc( nByte );
+ if( zFilename==0 ){
+ return 0;
+ }
+ nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
+ 0, 0);
+ if( nByte == 0 ){
+ free(zFilename);
+ zFilename = 0;
+ }
+ return zFilename;
+}
+
+/*
+** Convert an ansi string to microsoft unicode, based on the
+** current codepage settings for file apis.
+**
+** Space to hold the returned string is obtained
+** from malloc.
+*/
+static WCHAR *mbcsToUnicode(const char *zFilename){
+ int nByte;
+ WCHAR *zMbcsFilename;
+ int codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
+
+ nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, NULL,0)*sizeof(WCHAR);
+ zMbcsFilename = malloc( nByte*sizeof(zMbcsFilename[0]) );
+ if( zMbcsFilename==0 ){
+ return 0;
+ }
+ nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte);
+ if( nByte==0 ){
+ free(zMbcsFilename);
+ zMbcsFilename = 0;
+ }
+ return zMbcsFilename;
+}
+
+/*
+** Convert microsoft unicode to multibyte character string, based on the
+** user's Ansi codepage.
+**
+** Space to hold the returned string is obtained from
+** malloc().
+*/
+static char *unicodeToMbcs(const WCHAR *zWideFilename){
+ int nByte;
+ char *zFilename;
+ int codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
+
+ nByte = WideCharToMultiByte(codepage, 0, zWideFilename, -1, 0, 0, 0, 0);
+ zFilename = malloc( nByte );
+ if( zFilename==0 ){
+ return 0;
+ }
+ nByte = WideCharToMultiByte(codepage, 0, zWideFilename, -1, zFilename, nByte,
+ 0, 0);
+ if( nByte == 0 ){
+ free(zFilename);
+ zFilename = 0;
+ }
+ return zFilename;
+}
+
+/*
+** Convert multibyte character string to UTF-8. Space to hold the
+** returned string is obtained from malloc().
+*/
+static char *mbcsToUtf8(const char *zFilename){
+ char *zFilenameUtf8;
+ WCHAR *zTmpWide;
+
+ zTmpWide = mbcsToUnicode(zFilename);
+ if( zTmpWide==0 ){
+ return 0;
+ }
+ zFilenameUtf8 = unicodeToUtf8(zTmpWide);
+ free(zTmpWide);
+ return zFilenameUtf8;
+}
+
+/*
+** Convert UTF-8 to multibyte character string. Space to hold the
+** returned string is obtained from malloc().
+*/
+static char *utf8ToMbcs(const char *zFilename){
+ char *zFilenameMbcs;
+ WCHAR *zTmpWide;
+
+ zTmpWide = utf8ToUnicode(zFilename);
+ if( zTmpWide==0 ){
+ return 0;
+ }
+ zFilenameMbcs = unicodeToMbcs(zTmpWide);
+ free(zTmpWide);
+ return zFilenameMbcs;
+}
+
+#if OS_WINCE
+/*************************************************************************
+** This section contains code for WinCE only.
+*/
+/*
+** WindowsCE does not have a localtime() function. So create a
+** substitute.
+*/
+struct tm *__cdecl localtime(const time_t *t)
+{
+ static struct tm y;
+ FILETIME uTm, lTm;
+ SYSTEMTIME pTm;
+ sqlite3_int64 t64;
+ t64 = *t;
+ t64 = (t64 + 11644473600)*10000000;
+ uTm.dwLowDateTime = t64 & 0xFFFFFFFF;
+ uTm.dwHighDateTime= t64 >> 32;
+ FileTimeToLocalFileTime(&uTm,&lTm);
+ FileTimeToSystemTime(&lTm,&pTm);
+ y.tm_year = pTm.wYear - 1900;
+ y.tm_mon = pTm.wMonth - 1;
+ y.tm_wday = pTm.wDayOfWeek;
+ y.tm_mday = pTm.wDay;
+ y.tm_hour = pTm.wHour;
+ y.tm_min = pTm.wMinute;
+ y.tm_sec = pTm.wSecond;
+ return &y;
+}
+
+/* This will never be called, but defined to make the code compile */
+#define GetTempPathA(a,b)
+
+#define LockFile(a,b,c,d,e) winceLockFile(&a, b, c, d, e)
+#define UnlockFile(a,b,c,d,e) winceUnlockFile(&a, b, c, d, e)
+#define LockFileEx(a,b,c,d,e,f) winceLockFileEx(&a, b, c, d, e, f)
+
+#define HANDLE_TO_WINFILE(a) (winFile*)&((char*)a)[-offsetof(winFile,h)]
+
+/*
+** Acquire a lock on the handle h
+*/
+static void winceMutexAcquire(HANDLE h){
+ DWORD dwErr;
+ do {
+ dwErr = WaitForSingleObject(h, INFINITE);
+ } while (dwErr != WAIT_OBJECT_0 && dwErr != WAIT_ABANDONED);
+}
+/*
+** Release a lock acquired by winceMutexAcquire()
+*/
+#define winceMutexRelease(h) ReleaseMutex(h)
+
+/*
+** Create the mutex and shared memory used for locking in the file
+** descriptor pFile
+*/
+static BOOL winceCreateLock(const char *zFilename, winFile *pFile){
+ WCHAR *zTok;
+ WCHAR *zName = utf8ToUnicode(zFilename);
+ BOOL bInit = TRUE;
+
+ /* Initialize the local lockdata */
+ ZeroMemory(&pFile->local, sizeof(pFile->local));
+
+ /* Replace the backslashes from the filename and lowercase it
+ ** to derive a mutex name. */
+ zTok = CharLowerW(zName);
+ for (;*zTok;zTok++){
+ if (*zTok == '\\') *zTok = '_';
+ }
+
+ /* Create/open the named mutex */
+ pFile->hMutex = CreateMutexW(NULL, FALSE, zName);
+ if (!pFile->hMutex){
+ free(zName);
+ return FALSE;
+ }
+
+ /* Acquire the mutex before continuing */
+ winceMutexAcquire(pFile->hMutex);
+
+ /* Since the names of named mutexes, semaphores, file mappings etc are
+ ** case-sensitive, take advantage of that by uppercasing the mutex name
+ ** and using that as the shared filemapping name.
+ */
+ CharUpperW(zName);
+ pFile->hShared = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
+ PAGE_READWRITE, 0, sizeof(winceLock),
+ zName);
+
+ /* Set a flag that indicates we're the first to create the memory so it
+ ** must be zero-initialized */
+ if (GetLastError() == ERROR_ALREADY_EXISTS){
+ bInit = FALSE;
+ }
+
+ free(zName);
+
+ /* If we succeeded in making the shared memory handle, map it. */
+ if (pFile->hShared){
+ pFile->shared = (winceLock*)MapViewOfFile(pFile->hShared,
+ FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, sizeof(winceLock));
+ /* If mapping failed, close the shared memory handle and erase it */
+ if (!pFile->shared){
+ CloseHandle(pFile->hShared);
+ pFile->hShared = NULL;
+ }
+ }
+
+ /* If shared memory could not be created, then close the mutex and fail */
+ if (pFile->hShared == NULL){
+ winceMutexRelease(pFile->hMutex);
+ CloseHandle(pFile->hMutex);
+ pFile->hMutex = NULL;
+ return FALSE;
+ }
+
+ /* Initialize the shared memory if we're supposed to */
+ if (bInit) {
+ ZeroMemory(pFile->shared, sizeof(winceLock));
+ }
+
+ winceMutexRelease(pFile->hMutex);
+ return TRUE;
+}
+
+/*
+** Destroy the part of winFile that deals with wince locks
+*/
+static void winceDestroyLock(winFile *pFile){
+ if (pFile->hMutex){
+ /* Acquire the mutex */
+ winceMutexAcquire(pFile->hMutex);
+
+ /* The following blocks should probably assert in debug mode, but they
+ are to cleanup in case any locks remained open */
+ if (pFile->local.nReaders){
+ pFile->shared->nReaders --;
+ }
+ if (pFile->local.bReserved){
+ pFile->shared->bReserved = FALSE;
+ }
+ if (pFile->local.bPending){
+ pFile->shared->bPending = FALSE;
+ }
+ if (pFile->local.bExclusive){
+ pFile->shared->bExclusive = FALSE;
+ }
+
+ /* De-reference and close our copy of the shared memory handle */
+ UnmapViewOfFile(pFile->shared);
+ CloseHandle(pFile->hShared);
+
+ /* Done with the mutex */
+ winceMutexRelease(pFile->hMutex);
+ CloseHandle(pFile->hMutex);
+ pFile->hMutex = NULL;
+ }
+}
+
+/*
+** An implementation of the LockFile() API of windows for wince
+*/
+static BOOL winceLockFile(
+ HANDLE *phFile,
+ DWORD dwFileOffsetLow,
+ DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToLockLow,
+ DWORD nNumberOfBytesToLockHigh
+){
+ winFile *pFile = HANDLE_TO_WINFILE(phFile);
+ BOOL bReturn = FALSE;
+
+ if (!pFile->hMutex) return TRUE;
+ winceMutexAcquire(pFile->hMutex);
+
+ /* Wanting an exclusive lock? */
+ if (dwFileOffsetLow == SHARED_FIRST
+ && nNumberOfBytesToLockLow == SHARED_SIZE){
+ if (pFile->shared->nReaders == 0 && pFile->shared->bExclusive == 0){
+ pFile->shared->bExclusive = TRUE;
+ pFile->local.bExclusive = TRUE;
+ bReturn = TRUE;
+ }
+ }
+
+ /* Want a read-only lock? */
+ else if ((dwFileOffsetLow >= SHARED_FIRST &&
+ dwFileOffsetLow < SHARED_FIRST + SHARED_SIZE) &&
+ nNumberOfBytesToLockLow == 1){
+ if (pFile->shared->bExclusive == 0){
+ pFile->local.nReaders ++;
+ if (pFile->local.nReaders == 1){
+ pFile->shared->nReaders ++;
+ }
+ bReturn = TRUE;
+ }
+ }
+
+ /* Want a pending lock? */
+ else if (dwFileOffsetLow == PENDING_BYTE && nNumberOfBytesToLockLow == 1){
+ /* If no pending lock has been acquired, then acquire it */
+ if (pFile->shared->bPending == 0) {
+ pFile->shared->bPending = TRUE;
+ pFile->local.bPending = TRUE;
+ bReturn = TRUE;
+ }
+ }
+ /* Want a reserved lock? */
+ else if (dwFileOffsetLow == RESERVED_BYTE && nNumberOfBytesToLockLow == 1){
+ if (pFile->shared->bReserved == 0) {
+ pFile->shared->bReserved = TRUE;
+ pFile->local.bReserved = TRUE;
+ bReturn = TRUE;
+ }
+ }
+
+ winceMutexRelease(pFile->hMutex);
+ return bReturn;
+}
+
+/*
+** An implementation of the UnlockFile API of windows for wince
+*/
+static BOOL winceUnlockFile(
+ HANDLE *phFile,
+ DWORD dwFileOffsetLow,
+ DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToUnlockLow,
+ DWORD nNumberOfBytesToUnlockHigh
+){
+ winFile *pFile = HANDLE_TO_WINFILE(phFile);
+ BOOL bReturn = FALSE;
+
+ if (!pFile->hMutex) return TRUE;
+ winceMutexAcquire(pFile->hMutex);
+
+ /* Releasing a reader lock or an exclusive lock */
+ if (dwFileOffsetLow >= SHARED_FIRST &&
+ dwFileOffsetLow < SHARED_FIRST + SHARED_SIZE){
+ /* Did we have an exclusive lock? */
+ if (pFile->local.bExclusive){
+ pFile->local.bExclusive = FALSE;
+ pFile->shared->bExclusive = FALSE;
+ bReturn = TRUE;
+ }
+
+ /* Did we just have a reader lock? */
+ else if (pFile->local.nReaders){
+ pFile->local.nReaders --;
+ if (pFile->local.nReaders == 0)
+ {
+ pFile->shared->nReaders --;
+ }
+ bReturn = TRUE;
+ }
+ }
+
+ /* Releasing a pending lock */
+ else if (dwFileOffsetLow == PENDING_BYTE && nNumberOfBytesToUnlockLow == 1){
+ if (pFile->local.bPending){
+ pFile->local.bPending = FALSE;
+ pFile->shared->bPending = FALSE;
+ bReturn = TRUE;
+ }
+ }
+ /* Releasing a reserved lock */
+ else if (dwFileOffsetLow == RESERVED_BYTE && nNumberOfBytesToUnlockLow == 1){
+ if (pFile->local.bReserved) {
+ pFile->local.bReserved = FALSE;
+ pFile->shared->bReserved = FALSE;
+ bReturn = TRUE;
+ }
+ }
+
+ winceMutexRelease(pFile->hMutex);
+ return bReturn;
+}
+
+/*
+** An implementation of the LockFileEx() API of windows for wince
+*/
+static BOOL winceLockFileEx(
+ HANDLE *phFile,
+ DWORD dwFlags,
+ DWORD dwReserved,
+ DWORD nNumberOfBytesToLockLow,
+ DWORD nNumberOfBytesToLockHigh,
+ LPOVERLAPPED lpOverlapped
+){
+ /* If the caller wants a shared read lock, forward this call
+ ** to winceLockFile */
+ if (lpOverlapped->Offset == SHARED_FIRST &&
+ dwFlags == 1 &&
+ nNumberOfBytesToLockLow == SHARED_SIZE){
+ return winceLockFile(phFile, SHARED_FIRST, 0, 1, 0);
+ }
+ return FALSE;
+}
+/*
+** End of the special code for wince
+*****************************************************************************/
+#endif /* OS_WINCE */
+
+/*****************************************************************************
+** The next group of routines implement the I/O methods specified
+** by the sqlite3_io_methods object.
+******************************************************************************/
+
+/*
+** Close a file.
+**
+** It is reported that an attempt to close a handle might sometimes
+** fail. This is a very unreasonable result, but windows is notorious
+** for being unreasonable so I do not doubt that it might happen. If
+** the close fails, we pause for 100 milliseconds and try again. As
+** many as MX_CLOSE_ATTEMPT attempts to close the handle are made before
+** giving up and returning an error.
+*/
+#define MX_CLOSE_ATTEMPT 3
+static int winClose(sqlite3_file *id){
+ int rc, cnt = 0;
+ winFile *pFile = (winFile*)id;
+ OSTRACE2("CLOSE %d\n", pFile->h);
+ do{
+ rc = CloseHandle(pFile->h);
+ }while( rc==0 && cnt++ < MX_CLOSE_ATTEMPT && (Sleep(100), 1) );
+#if OS_WINCE
+#define WINCE_DELETION_ATTEMPTS 3
+ winceDestroyLock(pFile);
+ if( pFile->zDeleteOnClose ){
+ int cnt = 0;
+ while(
+ DeleteFileW(pFile->zDeleteOnClose)==0
+ && GetFileAttributesW(pFile->zDeleteOnClose)!=0xffffffff
+ && cnt++ < WINCE_DELETION_ATTEMPTS
+ ){
+ Sleep(100); /* Wait a little before trying again */
+ }
+ free(pFile->zDeleteOnClose);
+ }
+#endif
+ OpenCounter(-1);
+ return rc ? SQLITE_OK : SQLITE_IOERR;
+}
+
+/*
+** Some microsoft compilers lack this definition.
+*/
+#ifndef INVALID_SET_FILE_POINTER
+# define INVALID_SET_FILE_POINTER ((DWORD)-1)
+#endif
+
+/*
+** Read data from a file into a buffer. Return SQLITE_OK if all
+** bytes were read successfully and SQLITE_IOERR if anything goes
+** wrong.
+*/
+static int winRead(
+ sqlite3_file *id, /* File to read from */
+ void *pBuf, /* Write content into this buffer */
+ int amt, /* Number of bytes to read */
+ sqlite3_int64 offset /* Begin reading at this offset */
+){
+ LONG upperBits = (offset>>32) & 0x7fffffff;
+ LONG lowerBits = offset & 0xffffffff;
+ DWORD rc;
+ DWORD got;
+ winFile *pFile = (winFile*)id;
+ assert( id!=0 );
+ SimulateIOError(return SQLITE_IOERR_READ);
+ OSTRACE3("READ %d lock=%d\n", pFile->h, pFile->locktype);
+ rc = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
+ if( rc==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR ){
+ return SQLITE_FULL;
+ }
+ if( !ReadFile(pFile->h, pBuf, amt, &got, 0) ){
+ return SQLITE_IOERR_READ;
+ }
+ if( got==(DWORD)amt ){
+ return SQLITE_OK;
+ }else{
+ memset(&((char*)pBuf)[got], 0, amt-got);
+ return SQLITE_IOERR_SHORT_READ;
+ }
+}
+
+/*
+** Write data from a buffer into a file. Return SQLITE_OK on success
+** or some other error code on failure.
+*/
+static int winWrite(
+ sqlite3_file *id, /* File to write into */
+ const void *pBuf, /* The bytes to be written */
+ int amt, /* Number of bytes to write */
+ sqlite3_int64 offset /* Offset into the file to begin writing at */
+){
+ LONG upperBits = (offset>>32) & 0x7fffffff;
+ LONG lowerBits = offset & 0xffffffff;
+ DWORD rc;
+ DWORD wrote;
+ winFile *pFile = (winFile*)id;
+ assert( id!=0 );
+ SimulateIOError(return SQLITE_IOERR_WRITE);
+ SimulateDiskfullError(return SQLITE_FULL);
+ OSTRACE3("WRITE %d lock=%d\n", pFile->h, pFile->locktype);
+ rc = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
+ if( rc==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR ){
+ return SQLITE_FULL;
+ }
+ assert( amt>0 );
+ while(
+ amt>0
+ && (rc = WriteFile(pFile->h, pBuf, amt, &wrote, 0))!=0
+ && wrote>0
+ ){
+ amt -= wrote;
+ pBuf = &((char*)pBuf)[wrote];
+ }
+ if( !rc || amt>(int)wrote ){
+ return SQLITE_FULL;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Truncate an open file to a specified size
+*/
+static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
+ LONG upperBits = (nByte>>32) & 0x7fffffff;
+ LONG lowerBits = nByte & 0xffffffff;
+ winFile *pFile = (winFile*)id;
+ OSTRACE3("TRUNCATE %d %lld\n", pFile->h, nByte);
+ SimulateIOError(return SQLITE_IOERR_TRUNCATE);
+ SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
+ SetEndOfFile(pFile->h);
+ return SQLITE_OK;
+}
+
+#ifdef SQLITE_TEST
+/*
+** Count the number of fullsyncs and normal syncs. This is used to test
+** that syncs and fullsyncs are occuring at the right times.
+*/
+SQLITE_API int sqlite3_sync_count = 0;
+SQLITE_API int sqlite3_fullsync_count = 0;
+#endif
+
+/*
+** Make sure all writes to a particular file are committed to disk.
+*/
+static int winSync(sqlite3_file *id, int flags){
+ winFile *pFile = (winFile*)id;
+ OSTRACE3("SYNC %d lock=%d\n", pFile->h, pFile->locktype);
+#ifdef SQLITE_TEST
+ if( flags & SQLITE_SYNC_FULL ){
+ sqlite3_fullsync_count++;
+ }
+ sqlite3_sync_count++;
+#endif
+ if( FlushFileBuffers(pFile->h) ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+}
+
+/*
+** Determine the current size of a file in bytes
+*/
+static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){
+ winFile *pFile = (winFile*)id;
+ DWORD upperBits, lowerBits;
+ SimulateIOError(return SQLITE_IOERR_FSTAT);
+ lowerBits = GetFileSize(pFile->h, &upperBits);
+ *pSize = (((sqlite3_int64)upperBits)<<32) + lowerBits;
+ return SQLITE_OK;
+}
+
+/*
+** LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems.
+*/
+#ifndef LOCKFILE_FAIL_IMMEDIATELY
+# define LOCKFILE_FAIL_IMMEDIATELY 1
+#endif
+
+/*
+** Acquire a reader lock.
+** Different API routines are called depending on whether or not this
+** is Win95 or WinNT.
+*/
+static int getReadLock(winFile *pFile){
+ int res;
+ if( isNT() ){
+ OVERLAPPED ovlp;
+ ovlp.Offset = SHARED_FIRST;
+ ovlp.OffsetHigh = 0;
+ ovlp.hEvent = 0;
+ res = LockFileEx(pFile->h, LOCKFILE_FAIL_IMMEDIATELY,
+ 0, SHARED_SIZE, 0, &ovlp);
+ }else{
+ int lk;
+ sqlite3_randomness(sizeof(lk), &lk);
+ pFile->sharedLockByte = (lk & 0x7fffffff)%(SHARED_SIZE - 1);
+ res = LockFile(pFile->h, SHARED_FIRST+pFile->sharedLockByte, 0, 1, 0);
+ }
+ return res;
+}
+
+/*
+** Undo a readlock
+*/
+static int unlockReadLock(winFile *pFile){
+ int res;
+ if( isNT() ){
+ res = UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
+ }else{
+ res = UnlockFile(pFile->h, SHARED_FIRST + pFile->sharedLockByte, 0, 1, 0);
+ }
+ return res;
+}
+
+/*
+** Lock the file with the lock specified by parameter locktype - one
+** of the following:
+**
+** (1) SHARED_LOCK
+** (2) RESERVED_LOCK
+** (3) PENDING_LOCK
+** (4) EXCLUSIVE_LOCK
+**
+** Sometimes when requesting one lock state, additional lock states
+** are inserted in between. The locking might fail on one of the later
+** transitions leaving the lock state different from what it started but
+** still short of its goal. The following chart shows the allowed
+** transitions and the inserted intermediate states:
+**
+** UNLOCKED -> SHARED
+** SHARED -> RESERVED
+** SHARED -> (PENDING) -> EXCLUSIVE
+** RESERVED -> (PENDING) -> EXCLUSIVE
+** PENDING -> EXCLUSIVE
+**
+** This routine will only increase a lock. The winUnlock() routine
+** erases all locks at once and returns us immediately to locking level 0.
+** It is not possible to lower the locking level one step at a time. You
+** must go straight to locking level 0.
+*/
+static int winLock(sqlite3_file *id, int locktype){
+ int rc = SQLITE_OK; /* Return code from subroutines */
+ int res = 1; /* Result of a windows lock call */
+ int newLocktype; /* Set pFile->locktype to this value before exiting */
+ int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */
+ winFile *pFile = (winFile*)id;
+
+ assert( pFile!=0 );
+ OSTRACE5("LOCK %d %d was %d(%d)\n",
+ pFile->h, locktype, pFile->locktype, pFile->sharedLockByte);
+
+ /* If there is already a lock of this type or more restrictive on the
+ ** OsFile, do nothing. Don't use the end_lock: exit path, as
+ ** sqlite3OsEnterMutex() hasn't been called yet.
+ */
+ if( pFile->locktype>=locktype ){
+ return SQLITE_OK;
+ }
+
+ /* Make sure the locking sequence is correct
+ */
+ assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK );
+ assert( locktype!=PENDING_LOCK );
+ assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK );
+
+ /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or
+ ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of
+ ** the PENDING_LOCK byte is temporary.
+ */
+ newLocktype = pFile->locktype;
+ if( pFile->locktype==NO_LOCK
+ || (locktype==EXCLUSIVE_LOCK && pFile->locktype==RESERVED_LOCK)
+ ){
+ int cnt = 3;
+ while( cnt-->0 && (res = LockFile(pFile->h, PENDING_BYTE, 0, 1, 0))==0 ){
+ /* Try 3 times to get the pending lock. The pending lock might be
+ ** held by another reader process who will release it momentarily.
+ */
+ OSTRACE2("could not get a PENDING lock. cnt=%d\n", cnt);
+ Sleep(1);
+ }
+ gotPendingLock = res;
+ }
+
+ /* Acquire a shared lock
+ */
+ if( locktype==SHARED_LOCK && res ){
+ assert( pFile->locktype==NO_LOCK );
+ res = getReadLock(pFile);
+ if( res ){
+ newLocktype = SHARED_LOCK;
+ }
+ }
+
+ /* Acquire a RESERVED lock
+ */
+ if( locktype==RESERVED_LOCK && res ){
+ assert( pFile->locktype==SHARED_LOCK );
+ res = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
+ if( res ){
+ newLocktype = RESERVED_LOCK;
+ }
+ }
+
+ /* Acquire a PENDING lock
+ */
+ if( locktype==EXCLUSIVE_LOCK && res ){
+ newLocktype = PENDING_LOCK;
+ gotPendingLock = 0;
+ }
+
+ /* Acquire an EXCLUSIVE lock
+ */
+ if( locktype==EXCLUSIVE_LOCK && res ){
+ assert( pFile->locktype>=SHARED_LOCK );
+ res = unlockReadLock(pFile);
+ OSTRACE2("unreadlock = %d\n", res);
+ res = LockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
+ if( res ){
+ newLocktype = EXCLUSIVE_LOCK;
+ }else{
+ OSTRACE2("error-code = %d\n", GetLastError());
+ getReadLock(pFile);
+ }
+ }
+
+ /* If we are holding a PENDING lock that ought to be released, then
+ ** release it now.
+ */
+ if( gotPendingLock && locktype==SHARED_LOCK ){
+ UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0);
+ }
+
+ /* Update the state of the lock has held in the file descriptor then
+ ** return the appropriate result code.
+ */
+ if( res ){
+ rc = SQLITE_OK;
+ }else{
+ OSTRACE4("LOCK FAILED %d trying for %d but got %d\n", pFile->h,
+ locktype, newLocktype);
+ rc = SQLITE_BUSY;
+ }
+ pFile->locktype = newLocktype;
+ return rc;
+}
+
+/*
+** This routine checks if there is a RESERVED lock held on the specified
+** file by this or any other process. If such a lock is held, return
+** non-zero, otherwise zero.
+*/
+static int winCheckReservedLock(sqlite3_file *id){
+ int rc;
+ winFile *pFile = (winFile*)id;
+ assert( pFile!=0 );
+ if( pFile->locktype>=RESERVED_LOCK ){
+ rc = 1;
+ OSTRACE3("TEST WR-LOCK %d %d (local)\n", pFile->h, rc);
+ }else{
+ rc = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
+ if( rc ){
+ UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
+ }
+ rc = !rc;
+ OSTRACE3("TEST WR-LOCK %d %d (remote)\n", pFile->h, rc);
+ }
+ return rc;
+}
+
+/*
+** Lower the locking level on file descriptor id to locktype. locktype
+** must be either NO_LOCK or SHARED_LOCK.
+**
+** If the locking level of the file descriptor is already at or below
+** the requested locking level, this routine is a no-op.
+**
+** It is not possible for this routine to fail if the second argument
+** is NO_LOCK. If the second argument is SHARED_LOCK then this routine
+** might return SQLITE_IOERR;
+*/
+static int winUnlock(sqlite3_file *id, int locktype){
+ int type;
+ winFile *pFile = (winFile*)id;
+ int rc = SQLITE_OK;
+ assert( pFile!=0 );
+ assert( locktype<=SHARED_LOCK );
+ OSTRACE5("UNLOCK %d to %d was %d(%d)\n", pFile->h, locktype,
+ pFile->locktype, pFile->sharedLockByte);
+ type = pFile->locktype;
+ if( type>=EXCLUSIVE_LOCK ){
+ UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
+ if( locktype==SHARED_LOCK && !getReadLock(pFile) ){
+ /* This should never happen. We should always be able to
+ ** reacquire the read lock */
+ rc = SQLITE_IOERR_UNLOCK;
+ }
+ }
+ if( type>=RESERVED_LOCK ){
+ UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
+ }
+ if( locktype==NO_LOCK && type>=SHARED_LOCK ){
+ unlockReadLock(pFile);
+ }
+ if( type>=PENDING_LOCK ){
+ UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0);
+ }
+ pFile->locktype = locktype;
+ return rc;
+}
+
+/*
+** Control and query of the open file handle.
+*/
+static int winFileControl(sqlite3_file *id, int op, void *pArg){
+ switch( op ){
+ case SQLITE_FCNTL_LOCKSTATE: {
+ *(int*)pArg = ((winFile*)id)->locktype;
+ return SQLITE_OK;
+ }
+ }
+ return SQLITE_ERROR;
+}
+
+/*
+** Return the sector size in bytes of the underlying block device for
+** the specified file. This is almost always 512 bytes, but may be
+** larger for some devices.
+**
+** SQLite code assumes this function cannot fail. It also assumes that
+** if two files are created in the same file-system directory (i.e.
+** a database and its journal file) that the sector size will be the
+** same for both.
+*/
+static int winSectorSize(sqlite3_file *id){
+ return SQLITE_DEFAULT_SECTOR_SIZE;
+}
+
+/*
+** Return a vector of device characteristics.
+*/
+static int winDeviceCharacteristics(sqlite3_file *id){
+ return 0;
+}
+
+/*
+** This vector defines all the methods that can operate on an
+** sqlite3_file for win32.
+*/
+static const sqlite3_io_methods winIoMethod = {
+ 1, /* iVersion */
+ winClose,
+ winRead,
+ winWrite,
+ winTruncate,
+ winSync,
+ winFileSize,
+ winLock,
+ winUnlock,
+ winCheckReservedLock,
+ winFileControl,
+ winSectorSize,
+ winDeviceCharacteristics
+};
+
+/***************************************************************************
+** Here ends the I/O methods that form the sqlite3_io_methods object.
+**
+** The next block of code implements the VFS methods.
+****************************************************************************/
+
+/*
+** Convert a UTF-8 filename into whatever form the underlying
+** operating system wants filenames in. Space to hold the result
+** is obtained from malloc and must be freed by the calling
+** function.
+*/
+static void *convertUtf8Filename(const char *zFilename){
+ void *zConverted = 0;
+ if( isNT() ){
+ zConverted = utf8ToUnicode(zFilename);
+ }else{
+ zConverted = utf8ToMbcs(zFilename);
+ }
+ /* caller will handle out of memory */
+ return zConverted;
+}
+
+/*
+** Open a file.
+*/
+static int winOpen(
+ sqlite3_vfs *pVfs, /* Not used */
+ const char *zName, /* Name of the file (UTF-8) */
+ sqlite3_file *id, /* Write the SQLite file handle here */
+ int flags, /* Open mode flags */
+ int *pOutFlags /* Status return flags */
+){
+ HANDLE h;
+ DWORD dwDesiredAccess;
+ DWORD dwShareMode;
+ DWORD dwCreationDisposition;
+ DWORD dwFlagsAndAttributes = 0;
+ int isTemp;
+ winFile *pFile = (winFile*)id;
+ void *zConverted = convertUtf8Filename(zName);
+ if( zConverted==0 ){
+ return SQLITE_NOMEM;
+ }
+
+ if( flags & SQLITE_OPEN_READWRITE ){
+ dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
+ }else{
+ dwDesiredAccess = GENERIC_READ;
+ }
+ if( flags & SQLITE_OPEN_CREATE ){
+ dwCreationDisposition = OPEN_ALWAYS;
+ }else{
+ dwCreationDisposition = OPEN_EXISTING;
+ }
+ if( flags & SQLITE_OPEN_MAIN_DB ){
+ dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ }else{
+ dwShareMode = 0;
+ }
+ if( flags & SQLITE_OPEN_DELETEONCLOSE ){
+#if OS_WINCE
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_HIDDEN;
+#else
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY
+ | FILE_ATTRIBUTE_HIDDEN
+ | FILE_FLAG_DELETE_ON_CLOSE;
+#endif
+ isTemp = 1;
+ }else{
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
+ isTemp = 0;
+ }
+ /* Reports from the internet are that performance is always
+ ** better if FILE_FLAG_RANDOM_ACCESS is used. Ticket #2699. */
+ dwFlagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS;
+ if( isNT() ){
+ h = CreateFileW((WCHAR*)zConverted,
+ dwDesiredAccess,
+ dwShareMode,
+ NULL,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ NULL
+ );
+ }else{
+#if OS_WINCE
+ return SQLITE_NOMEM;
+#else
+ h = CreateFileA((char*)zConverted,
+ dwDesiredAccess,
+ dwShareMode,
+ NULL,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ NULL
+ );
+#endif
+ }
+ if( h==INVALID_HANDLE_VALUE ){
+ free(zConverted);
+ if( flags & SQLITE_OPEN_READWRITE ){
+ return winOpen(0, zName, id,
+ ((flags|SQLITE_OPEN_READONLY)&~SQLITE_OPEN_READWRITE), pOutFlags);
+ }else{
+ return SQLITE_CANTOPEN;
+ }
+ }
+ if( pOutFlags ){
+ if( flags & SQLITE_OPEN_READWRITE ){
+ *pOutFlags = SQLITE_OPEN_READWRITE;
+ }else{
+ *pOutFlags = SQLITE_OPEN_READONLY;
+ }
+ }
+ memset(pFile, 0, sizeof(*pFile));
+ pFile->pMethod = &winIoMethod;
+ pFile->h = h;
+#if OS_WINCE
+ if( (flags & (SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB)) ==
+ (SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB)
+ && !winceCreateLock(zName, pFile)
+ ){
+ CloseHandle(h);
+ free(zConverted);
+ return SQLITE_CANTOPEN;
+ }
+ if( isTemp ){
+ pFile->zDeleteOnClose = zConverted;
+ }else
+#endif
+ {
+ free(zConverted);
+ }
+ OpenCounter(+1);
+ return SQLITE_OK;
+}
+
+/*
+** Delete the named file.
+**
+** Note that windows does not allow a file to be deleted if some other
+** process has it open. Sometimes a virus scanner or indexing program
+** will open a journal file shortly after it is created in order to do
+** whatever does. While this other process is holding the
+** file open, we will be unable to delete it. To work around this
+** problem, we delay 100 milliseconds and try to delete again. Up
+** to MX_DELETION_ATTEMPTs deletion attempts are run before giving
+** up and returning an error.
+*/
+#define MX_DELETION_ATTEMPTS 5
+static int winDelete(
+ sqlite3_vfs *pVfs, /* Not used on win32 */
+ const char *zFilename, /* Name of file to delete */
+ int syncDir /* Not used on win32 */
+){
+ int cnt = 0;
+ int rc;
+ void *zConverted = convertUtf8Filename(zFilename);
+ if( zConverted==0 ){
+ return SQLITE_NOMEM;
+ }
+ SimulateIOError(return SQLITE_IOERR_DELETE);
+ if( isNT() ){
+ do{
+ DeleteFileW(zConverted);
+ }while( (rc = GetFileAttributesW(zConverted))!=0xffffffff
+ && cnt++ < MX_DELETION_ATTEMPTS && (Sleep(100), 1) );
+ }else{
+#if OS_WINCE
+ return SQLITE_NOMEM;
+#else
+ do{
+ DeleteFileA(zConverted);
+ }while( (rc = GetFileAttributesA(zConverted))!=0xffffffff
+ && cnt++ < MX_DELETION_ATTEMPTS && (Sleep(100), 1) );
+#endif
+ }
+ free(zConverted);
+ OSTRACE2("DELETE \"%s\"\n", zFilename);
+ return rc==0xffffffff ? SQLITE_OK : SQLITE_IOERR_DELETE;
+}
+
+/*
+** Check the existance and status of a file.
+*/
+static int winAccess(
+ sqlite3_vfs *pVfs, /* Not used on win32 */
+ const char *zFilename, /* Name of file to check */
+ int flags /* Type of test to make on this file */
+){
+ DWORD attr;
+ int rc;
+ void *zConverted = convertUtf8Filename(zFilename);
+ if( zConverted==0 ){
+ return SQLITE_NOMEM;
+ }
+ if( isNT() ){
+ attr = GetFileAttributesW((WCHAR*)zConverted);
+ }else{
+#if OS_WINCE
+ return SQLITE_NOMEM;
+#else
+ attr = GetFileAttributesA((char*)zConverted);
+#endif
+ }
+ free(zConverted);
+ switch( flags ){
+ case SQLITE_ACCESS_READ:
+ case SQLITE_ACCESS_EXISTS:
+ rc = attr!=0xffffffff;
+ break;
+ case SQLITE_ACCESS_READWRITE:
+ rc = (attr & FILE_ATTRIBUTE_READONLY)==0;
+ break;
+ default:
+ assert(!"Invalid flags argument");
+ }
+ return rc;
+}
+
+
+/*
+** Create a temporary file name in zBuf. zBuf must be big enough to
+** hold at pVfs->mxPathname characters.
+*/
+static int winGetTempname(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
+ static char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ char zTempPath[MAX_PATH+1];
+ if( sqlite3_temp_directory ){
+ sqlite3_snprintf(MAX_PATH-30, zTempPath, "%s", sqlite3_temp_directory);
+ }else if( isNT() ){
+ char *zMulti;
+ WCHAR zWidePath[MAX_PATH];
+ GetTempPathW(MAX_PATH-30, zWidePath);
+ zMulti = unicodeToUtf8(zWidePath);
+ if( zMulti ){
+ sqlite3_snprintf(MAX_PATH-30, zTempPath, "%s", zMulti);
+ free(zMulti);
+ }else{
+ return SQLITE_NOMEM;
+ }
+ }else{
+ char *zUtf8;
+ char zMbcsPath[MAX_PATH];
+ GetTempPathA(MAX_PATH-30, zMbcsPath);
+ zUtf8 = mbcsToUtf8(zMbcsPath);
+ if( zUtf8 ){
+ sqlite3_snprintf(MAX_PATH-30, zTempPath, "%s", zUtf8);
+ free(zUtf8);
+ }else{
+ return SQLITE_NOMEM;
+ }
+ }
+ for(i=strlen(zTempPath); i>0 && zTempPath[i-1]=='\\'; i--){}
+ zTempPath[i] = 0;
+ sqlite3_snprintf(nBuf-30, zBuf,
+ "%s\\"SQLITE_TEMP_FILE_PREFIX, zTempPath);
+ j = strlen(zBuf);
+ sqlite3_randomness(20, &zBuf[j]);
+ for(i=0; i<20; i++, j++){
+ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
+ }
+ zBuf[j] = 0;
+ OSTRACE2("TEMP FILENAME: %s\n", zBuf);
+ return SQLITE_OK;
+}
+
+/*
+** Turn a relative pathname into a full pathname. Write the full
+** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
+** bytes in size.
+*/
+static int winFullPathname(
+ sqlite3_vfs *pVfs, /* Pointer to vfs object */
+ const char *zRelative, /* Possibly relative input path */
+ int nFull, /* Size of output buffer in bytes */
+ char *zFull /* Output buffer */
+){
+
+#if defined(__CYGWIN__)
+ cygwin_conv_to_full_win32_path(zRelative, zFull);
+ return SQLITE_OK;
+#endif
+
+#if OS_WINCE
+ /* WinCE has no concept of a relative pathname, or so I am told. */
+ sqlite3_snprintf(pVfs->mxPathname, zFull, "%s", zRelative);
+ return SQLITE_OK;
+#endif
+
+#if !OS_WINCE && !defined(__CYGWIN__)
+ int nByte;
+ void *zConverted;
+ char *zOut;
+ zConverted = convertUtf8Filename(zRelative);
+ if( isNT() ){
+ WCHAR *zTemp;
+ nByte = GetFullPathNameW((WCHAR*)zConverted, 0, 0, 0) + 3;
+ zTemp = malloc( nByte*sizeof(zTemp[0]) );
+ if( zTemp==0 ){
+ free(zConverted);
+ return SQLITE_NOMEM;
+ }
+ GetFullPathNameW((WCHAR*)zConverted, nByte, zTemp, 0);
+ free(zConverted);
+ zOut = unicodeToUtf8(zTemp);
+ free(zTemp);
+ }else{
+ char *zTemp;
+ nByte = GetFullPathNameA((char*)zConverted, 0, 0, 0) + 3;
+ zTemp = malloc( nByte*sizeof(zTemp[0]) );
+ if( zTemp==0 ){
+ free(zConverted);
+ return SQLITE_NOMEM;
+ }
+ GetFullPathNameA((char*)zConverted, nByte, zTemp, 0);
+ free(zConverted);
+ zOut = mbcsToUtf8(zTemp);
+ free(zTemp);
+ }
+ if( zOut ){
+ sqlite3_snprintf(pVfs->mxPathname, zFull, "%s", zOut);
+ free(zOut);
+ return SQLITE_OK;
+ }else{
+ return SQLITE_NOMEM;
+ }
+#endif
+}
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+/*
+** Interfaces for opening a shared library, finding entry points
+** within the shared library, and closing the shared library.
+*/
+/*
+** Interfaces for opening a shared library, finding entry points
+** within the shared library, and closing the shared library.
+*/
+static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
+ HANDLE h;
+ void *zConverted = convertUtf8Filename(zFilename);
+ if( zConverted==0 ){
+ return 0;
+ }
+ if( isNT() ){
+ h = LoadLibraryW((WCHAR*)zConverted);
+ }else{
+#if OS_WINCE
+ return 0;
+#else
+ h = LoadLibraryA((char*)zConverted);
+#endif
+ }
+ free(zConverted);
+ return (void*)h;
+}
+static void winDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){
+#if OS_WINCE
+ int error = GetLastError();
+ if( error>0x7FFFFFF ){
+ sqlite3_snprintf(nBuf, zBufOut, "OsError 0x%x", error);
+ }else{
+ sqlite3_snprintf(nBuf, zBufOut, "OsError %d", error);
+ }
+#else
+ FormatMessageA(
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ 0,
+ zBufOut,
+ nBuf-1,
+ 0
+ );
+#endif
+}
+void *winDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol){
+#if OS_WINCE
+ /* The GetProcAddressA() routine is only available on wince. */
+ return GetProcAddressA((HANDLE)pHandle, zSymbol);
+#else
+ /* All other windows platforms expect GetProcAddress() to take
+ ** an Ansi string regardless of the _UNICODE setting */
+ return GetProcAddress((HANDLE)pHandle, zSymbol);
+#endif
+}
+void winDlClose(sqlite3_vfs *pVfs, void *pHandle){
+ FreeLibrary((HANDLE)pHandle);
+}
+#else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */
+ #define winDlOpen 0
+ #define winDlError 0
+ #define winDlSym 0
+ #define winDlClose 0
+#endif
+
+
+/*
+** Write up to nBuf bytes of randomness into zBuf.
+*/
+static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
+ int n = 0;
+ if( sizeof(SYSTEMTIME)<=nBuf-n ){
+ SYSTEMTIME x;
+ GetSystemTime(&x);
+ memcpy(&zBuf[n], &x, sizeof(x));
+ n += sizeof(x);
+ }
+ if( sizeof(DWORD)<=nBuf-n ){
+ DWORD pid = GetCurrentProcessId();
+ memcpy(&zBuf[n], &pid, sizeof(pid));
+ n += sizeof(pid);
+ }
+ if( sizeof(DWORD)<=nBuf-n ){
+ DWORD cnt = GetTickCount();
+ memcpy(&zBuf[n], &cnt, sizeof(cnt));
+ n += sizeof(cnt);
+ }
+ if( sizeof(LARGE_INTEGER)<=nBuf-n ){
+ LARGE_INTEGER i;
+ QueryPerformanceCounter(&i);
+ memcpy(&zBuf[n], &i, sizeof(i));
+ n += sizeof(i);
+ }
+ return n;
+}
+
+
+/*
+** Sleep for a little while. Return the amount of time slept.
+*/
+static int winSleep(sqlite3_vfs *pVfs, int microsec){
+ Sleep((microsec+999)/1000);
+ return ((microsec+999)/1000)*1000;
+}
+
+/*
+** The following variable, if set to a non-zero value, becomes the result
+** returned from sqlite3OsCurrentTime(). This is used for testing.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_current_time = 0;
+#endif
+
+/*
+** Find the current time (in Universal Coordinated Time). Write the
+** current time and date as a Julian Day number into *prNow and
+** return 0. Return 1 if the time and date cannot be found.
+*/
+int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){
+ FILETIME ft;
+ /* FILETIME structure is a 64-bit value representing the number of
+ 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5).
+ */
+ double now;
+#if OS_WINCE
+ SYSTEMTIME time;
+ GetSystemTime(&time);
+ SystemTimeToFileTime(&time,&ft);
+#else
+ GetSystemTimeAsFileTime( &ft );
+#endif
+ now = ((double)ft.dwHighDateTime) * 4294967296.0;
+ *prNow = (now + ft.dwLowDateTime)/864000000000.0 + 2305813.5;
+#ifdef SQLITE_TEST
+ if( sqlite3_current_time ){
+ *prNow = sqlite3_current_time/86400.0 + 2440587.5;
+ }
+#endif
+ return 0;
+}
+
+
+/*
+** Return a pointer to the sqlite3DefaultVfs structure. We use
+** a function rather than give the structure global scope because
+** some compilers (MSVC) do not allow forward declarations of
+** initialized structures.
+*/
+SQLITE_PRIVATE sqlite3_vfs *sqlite3OsDefaultVfs(void){
+ static sqlite3_vfs winVfs = {
+ 1, /* iVersion */
+ sizeof(winFile), /* szOsFile */
+ MAX_PATH, /* mxPathname */
+ 0, /* pNext */
+ "win32", /* zName */
+ 0, /* pAppData */
+
+ winOpen, /* xOpen */
+ winDelete, /* xDelete */
+ winAccess, /* xAccess */
+ winGetTempname, /* xGetTempName */
+ winFullPathname, /* xFullPathname */
+ winDlOpen, /* xDlOpen */
+ winDlError, /* xDlError */
+ winDlSym, /* xDlSym */
+ winDlClose, /* xDlClose */
+ winRandomness, /* xRandomness */
+ winSleep, /* xSleep */
+ winCurrentTime /* xCurrentTime */
+ };
+
+ return &winVfs;
+}
+
+#endif /* OS_WIN */
+
+/************** End of os_win.c **********************************************/
+/************** Begin file bitvec.c ******************************************/
+/*
+** 2008 February 16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file implements an object that represents a fixed-length
+** bitmap. Bits are numbered starting with 1.
+**
+** A bitmap is used to record what pages a database file have been
+** journalled during a transaction. Usually only a few pages are
+** journalled. So the bitmap is usually sparse and has low cardinality.
+** But sometimes (for example when during a DROP of a large table) most
+** or all of the pages get journalled. In those cases, the bitmap becomes
+** dense. The algorithm needs to handle both cases well.
+**
+** The size of the bitmap is fixed when the object is created.
+**
+** All bits are clear when the bitmap is created. Individual bits
+** may be set or cleared one at a time.
+**
+** Test operations are about 100 times more common that set operations.
+** Clear operations are exceedingly rare. There are usually between
+** 5 and 500 set operations per Bitvec object, though the number of sets can
+** sometimes grow into tens of thousands or larger. The size of the
+** Bitvec object is the number of pages in the database file at the
+** start of a transaction, and is thus usually less than a few thousand,
+** but can be as large as 2 billion for a really big database.
+**
+** @(#) $Id: bitvec.c,v 1.5 2008/05/13 13:27:34 drh Exp $
+*/
+
+#define BITVEC_SZ 512
+/* Round the union size down to the nearest pointer boundary, since that's how
+** it will be aligned within the Bitvec struct. */
+#define BITVEC_USIZE (((BITVEC_SZ-12)/sizeof(Bitvec*))*sizeof(Bitvec*))
+#define BITVEC_NCHAR BITVEC_USIZE
+#define BITVEC_NBIT (BITVEC_NCHAR*8)
+#define BITVEC_NINT (BITVEC_USIZE/4)
+#define BITVEC_MXHASH (BITVEC_NINT/2)
+#define BITVEC_NPTR (BITVEC_USIZE/sizeof(Bitvec *))
+
+#define BITVEC_HASH(X) (((X)*37)%BITVEC_NINT)
+
+/*
+** A bitmap is an instance of the following structure.
+**
+** This bitmap records the existance of zero or more bits
+** with values between 1 and iSize, inclusive.
+**
+** There are three possible representations of the bitmap.
+** If iSize<=BITVEC_NBIT, then Bitvec.u.aBitmap[] is a straight
+** bitmap. The least significant bit is bit 1.
+**
+** If iSize>BITVEC_NBIT and iDivisor==0 then Bitvec.u.aHash[] is
+** a hash table that will hold up to BITVEC_MXHASH distinct values.
+**
+** Otherwise, the value i is redirected into one of BITVEC_NPTR
+** sub-bitmaps pointed to by Bitvec.u.apSub[]. Each subbitmap
+** handles up to iDivisor separate values of i. apSub[0] holds
+** values between 1 and iDivisor. apSub[1] holds values between
+** iDivisor+1 and 2*iDivisor. apSub[N] holds values between
+** N*iDivisor+1 and (N+1)*iDivisor. Each subbitmap is normalized
+** to hold deal with values between 1 and iDivisor.
+*/
+struct Bitvec {
+ u32 iSize; /* Maximum bit index */
+ u32 nSet; /* Number of bits that are set */
+ u32 iDivisor; /* Number of bits handled by each apSub[] entry */
+ union {
+ u8 aBitmap[BITVEC_NCHAR]; /* Bitmap representation */
+ u32 aHash[BITVEC_NINT]; /* Hash table representation */
+ Bitvec *apSub[BITVEC_NPTR]; /* Recursive representation */
+ } u;
+};
+
+/*
+** Create a new bitmap object able to handle bits between 0 and iSize,
+** inclusive. Return a pointer to the new object. Return NULL if
+** malloc fails.
+*/
+SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32 iSize){
+ Bitvec *p;
+ assert( sizeof(*p)==BITVEC_SZ );
+ p = sqlite3MallocZero( sizeof(*p) );
+ if( p ){
+ p->iSize = iSize;
+ }
+ return p;
+}
+
+/*
+** Check to see if the i-th bit is set. Return true or false.
+** If p is NULL (if the bitmap has not been created) or if
+** i is out of range, then return false.
+*/
+SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec *p, u32 i){
+ if( p==0 ) return 0;
+ if( i>p->iSize || i==0 ) return 0;
+ if( p->iSize<=BITVEC_NBIT ){
+ i--;
+ return (p->u.aBitmap[i/8] & (1<<(i&7)))!=0;
+ }
+ if( p->iDivisor>0 ){
+ u32 bin = (i-1)/p->iDivisor;
+ i = (i-1)%p->iDivisor + 1;
+ return sqlite3BitvecTest(p->u.apSub[bin], i);
+ }else{
+ u32 h = BITVEC_HASH(i);
+ while( p->u.aHash[h] ){
+ if( p->u.aHash[h]==i ) return 1;
+ h++;
+ if( h>=BITVEC_NINT ) h = 0;
+ }
+ return 0;
+ }
+}
+
+/*
+** Set the i-th bit. Return 0 on success and an error code if
+** anything goes wrong.
+*/
+SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec *p, u32 i){
+ u32 h;
+ assert( p!=0 );
+ assert( i>0 );
+ assert( i<=p->iSize );
+ if( p->iSize<=BITVEC_NBIT ){
+ i--;
+ p->u.aBitmap[i/8] |= 1 << (i&7);
+ return SQLITE_OK;
+ }
+ if( p->iDivisor ){
+ u32 bin = (i-1)/p->iDivisor;
+ i = (i-1)%p->iDivisor + 1;
+ if( p->u.apSub[bin]==0 ){
+ sqlite3FaultBeginBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ p->u.apSub[bin] = sqlite3BitvecCreate( p->iDivisor );
+ sqlite3FaultEndBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ if( p->u.apSub[bin]==0 ) return SQLITE_NOMEM;
+ }
+ return sqlite3BitvecSet(p->u.apSub[bin], i);
+ }
+ h = BITVEC_HASH(i);
+ while( p->u.aHash[h] ){
+ if( p->u.aHash[h]==i ) return SQLITE_OK;
+ h++;
+ if( h==BITVEC_NINT ) h = 0;
+ }
+ p->nSet++;
+ if( p->nSet>=BITVEC_MXHASH ){
+ int j, rc;
+ u32 aiValues[BITVEC_NINT];
+ memcpy(aiValues, p->u.aHash, sizeof(aiValues));
+ memset(p->u.apSub, 0, sizeof(p->u.apSub[0])*BITVEC_NPTR);
+ p->iDivisor = (p->iSize + BITVEC_NPTR - 1)/BITVEC_NPTR;
+ rc = sqlite3BitvecSet(p, i);
+ for(j=0; j<BITVEC_NINT; j++){
+ if( aiValues[j] ) rc |= sqlite3BitvecSet(p, aiValues[j]);
+ }
+ return rc;
+ }
+ p->u.aHash[h] = i;
+ return SQLITE_OK;
+}
+
+/*
+** Clear the i-th bit. Return 0 on success and an error code if
+** anything goes wrong.
+*/
+SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec *p, u32 i){
+ assert( p!=0 );
+ assert( i>0 );
+ if( p->iSize<=BITVEC_NBIT ){
+ i--;
+ p->u.aBitmap[i/8] &= ~(1 << (i&7));
+ }else if( p->iDivisor ){
+ u32 bin = (i-1)/p->iDivisor;
+ i = (i-1)%p->iDivisor + 1;
+ if( p->u.apSub[bin] ){
+ sqlite3BitvecClear(p->u.apSub[bin], i);
+ }
+ }else{
+ int j;
+ u32 aiValues[BITVEC_NINT];
+ memcpy(aiValues, p->u.aHash, sizeof(aiValues));
+ memset(p->u.aHash, 0, sizeof(p->u.aHash[0])*BITVEC_NINT);
+ p->nSet = 0;
+ for(j=0; j<BITVEC_NINT; j++){
+ if( aiValues[j] && aiValues[j]!=i ){
+ sqlite3BitvecSet(p, aiValues[j]);
+ }
+ }
+ }
+}
+
+/*
+** Destroy a bitmap object. Reclaim all memory used.
+*/
+SQLITE_PRIVATE void sqlite3BitvecDestroy(Bitvec *p){
+ if( p==0 ) return;
+ if( p->iDivisor ){
+ int i;
+ for(i=0; i<BITVEC_NPTR; i++){
+ sqlite3BitvecDestroy(p->u.apSub[i]);
+ }
+ }
+ sqlite3_free(p);
+}
+
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+/*
+** Let V[] be an array of unsigned characters sufficient to hold
+** up to N bits. Let I be an integer between 0 and N. 0<=I<N.
+** Then the following macros can be used to set, clear, or test
+** individual bits within V.
+*/
+#define SETBIT(V,I) V[I>>3] |= (1<<(I&7))
+#define CLEARBIT(V,I) V[I>>3] &= ~(1<<(I&7))
+#define TESTBIT(V,I) (V[I>>3]&(1<<(I&7)))!=0
+
+/*
+** This routine runs an extensive test of the Bitvec code.
+**
+** The input is an array of integers that acts as a program
+** to test the Bitvec. The integers are opcodes followed
+** by 0, 1, or 3 operands, depending on the opcode. Another
+** opcode follows immediately after the last operand.
+**
+** There are 6 opcodes numbered from 0 through 5. 0 is the
+** "halt" opcode and causes the test to end.
+**
+** 0 Halt and return the number of errors
+** 1 N S X Set N bits beginning with S and incrementing by X
+** 2 N S X Clear N bits beginning with S and incrementing by X
+** 3 N Set N randomly chosen bits
+** 4 N Clear N randomly chosen bits
+** 5 N S X Set N bits from S increment X in array only, not in bitvec
+**
+** The opcodes 1 through 4 perform set and clear operations are performed
+** on both a Bitvec object and on a linear array of bits obtained from malloc.
+** Opcode 5 works on the linear array only, not on the Bitvec.
+** Opcode 5 is used to deliberately induce a fault in order to
+** confirm that error detection works.
+**
+** At the conclusion of the test the linear array is compared
+** against the Bitvec object. If there are any differences,
+** an error is returned. If they are the same, zero is returned.
+**
+** If a memory allocation error occurs, return -1.
+*/
+SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
+ Bitvec *pBitvec = 0;
+ unsigned char *pV = 0;
+ int rc = -1;
+ int i, nx, pc, op;
+
+ /* Allocate the Bitvec to be tested and a linear array of
+ ** bits to act as the reference */
+ pBitvec = sqlite3BitvecCreate( sz );
+ pV = sqlite3_malloc( (sz+7)/8 + 1 );
+ if( pBitvec==0 || pV==0 ) goto bitvec_end;
+ memset(pV, 0, (sz+7)/8 + 1);
+
+ /* Run the program */
+ pc = 0;
+ while( (op = aOp[pc])!=0 ){
+ switch( op ){
+ case 1:
+ case 2:
+ case 5: {
+ nx = 4;
+ i = aOp[pc+2] - 1;
+ aOp[pc+2] += aOp[pc+3];
+ break;
+ }
+ case 3:
+ case 4:
+ default: {
+ nx = 2;
+ sqlite3_randomness(sizeof(i), &i);
+ break;
+ }
+ }
+ if( (--aOp[pc+1]) > 0 ) nx = 0;
+ pc += nx;
+ i = (i & 0x7fffffff)%sz;
+ if( (op & 1)!=0 ){
+ SETBIT(pV, (i+1));
+ if( op!=5 ){
+ if( sqlite3BitvecSet(pBitvec, i+1) ) goto bitvec_end;
+ }
+ }else{
+ CLEARBIT(pV, (i+1));
+ sqlite3BitvecClear(pBitvec, i+1);
+ }
+ }
+
+ /* Test to make sure the linear array exactly matches the
+ ** Bitvec object. Start with the assumption that they do
+ ** match (rc==0). Change rc to non-zero if a discrepancy
+ ** is found.
+ */
+ rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1)
+ + sqlite3BitvecTest(pBitvec, 0);
+ for(i=1; i<=sz; i++){
+ if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){
+ rc = i;
+ break;
+ }
+ }
+
+ /* Free allocated structure */
+bitvec_end:
+ sqlite3_free(pV);
+ sqlite3BitvecDestroy(pBitvec);
+ return rc;
+}
+#endif /* SQLITE_OMIT_BUILTIN_TEST */
+
+/************** End of bitvec.c **********************************************/
+/************** Begin file pager.c *******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the implementation of the page cache subsystem or "pager".
+**
+** The pager is used to access a database disk file. It implements
+** atomic commit and rollback through the use of a journal file that
+** is separate from the database file. The pager also implements file
+** locking to prevent two processes from writing the same database
+** file simultaneously, or one process from reading the database while
+** another is writing.
+**
+** @(#) $Id: pager.c,v 1.446 2008/05/13 13:27:34 drh Exp $
+*/
+#ifndef SQLITE_OMIT_DISKIO
+
+/*
+** Macros for troubleshooting. Normally turned off
+*/
+#if 0
+#define sqlite3DebugPrintf printf
+#define PAGERTRACE1(X) sqlite3DebugPrintf(X)
+#define PAGERTRACE2(X,Y) sqlite3DebugPrintf(X,Y)
+#define PAGERTRACE3(X,Y,Z) sqlite3DebugPrintf(X,Y,Z)
+#define PAGERTRACE4(X,Y,Z,W) sqlite3DebugPrintf(X,Y,Z,W)
+#define PAGERTRACE5(X,Y,Z,W,V) sqlite3DebugPrintf(X,Y,Z,W,V)
+#else
+#define PAGERTRACE1(X)
+#define PAGERTRACE2(X,Y)
+#define PAGERTRACE3(X,Y,Z)
+#define PAGERTRACE4(X,Y,Z,W)
+#define PAGERTRACE5(X,Y,Z,W,V)
+#endif
+
+/*
+** The following two macros are used within the PAGERTRACEX() macros above
+** to print out file-descriptors.
+**
+** PAGERID() takes a pointer to a Pager struct as its argument. The
+** associated file-descriptor is returned. FILEHANDLEID() takes an sqlite3_file
+** struct as its argument.
+*/
+#define PAGERID(p) ((int)(p->fd))
+#define FILEHANDLEID(fd) ((int)fd)
+
+/*
+** The page cache as a whole is always in one of the following
+** states:
+**
+** PAGER_UNLOCK The page cache is not currently reading or
+** writing the database file. There is no
+** data held in memory. This is the initial
+** state.
+**
+** PAGER_SHARED The page cache is reading the database.
+** Writing is not permitted. There can be
+** multiple readers accessing the same database
+** file at the same time.
+**
+** PAGER_RESERVED This process has reserved the database for writing
+** but has not yet made any changes. Only one process
+** at a time can reserve the database. The original
+** database file has not been modified so other
+** processes may still be reading the on-disk
+** database file.
+**
+** PAGER_EXCLUSIVE The page cache is writing the database.
+** Access is exclusive. No other processes or
+** threads can be reading or writing while one
+** process is writing.
+**
+** PAGER_SYNCED The pager moves to this state from PAGER_EXCLUSIVE
+** after all dirty pages have been written to the
+** database file and the file has been synced to
+** disk. All that remains to do is to remove or
+** truncate the journal file and the transaction
+** will be committed.
+**
+** The page cache comes up in PAGER_UNLOCK. The first time a
+** sqlite3PagerGet() occurs, the state transitions to PAGER_SHARED.
+** After all pages have been released using sqlite_page_unref(),
+** the state transitions back to PAGER_UNLOCK. The first time
+** that sqlite3PagerWrite() is called, the state transitions to
+** PAGER_RESERVED. (Note that sqlite3PagerWrite() can only be
+** called on an outstanding page which means that the pager must
+** be in PAGER_SHARED before it transitions to PAGER_RESERVED.)
+** PAGER_RESERVED means that there is an open rollback journal.
+** The transition to PAGER_EXCLUSIVE occurs before any changes
+** are made to the database file, though writes to the rollback
+** journal occurs with just PAGER_RESERVED. After an sqlite3PagerRollback()
+** or sqlite3PagerCommitPhaseTwo(), the state can go back to PAGER_SHARED,
+** or it can stay at PAGER_EXCLUSIVE if we are in exclusive access mode.
+*/
+#define PAGER_UNLOCK 0
+#define PAGER_SHARED 1 /* same as SHARED_LOCK */
+#define PAGER_RESERVED 2 /* same as RESERVED_LOCK */
+#define PAGER_EXCLUSIVE 4 /* same as EXCLUSIVE_LOCK */
+#define PAGER_SYNCED 5
+
+/*
+** If the SQLITE_BUSY_RESERVED_LOCK macro is set to true at compile-time,
+** then failed attempts to get a reserved lock will invoke the busy callback.
+** This is off by default. To see why, consider the following scenario:
+**
+** Suppose thread A already has a shared lock and wants a reserved lock.
+** Thread B already has a reserved lock and wants an exclusive lock. If
+** both threads are using their busy callbacks, it might be a long time
+** be for one of the threads give up and allows the other to proceed.
+** But if the thread trying to get the reserved lock gives up quickly
+** (if it never invokes its busy callback) then the contention will be
+** resolved quickly.
+*/
+#ifndef SQLITE_BUSY_RESERVED_LOCK
+# define SQLITE_BUSY_RESERVED_LOCK 0
+#endif
+
+/*
+** This macro rounds values up so that if the value is an address it
+** is guaranteed to be an address that is aligned to an 8-byte boundary.
+*/
+#define FORCE_ALIGNMENT(X) (((X)+7)&~7)
+
+typedef struct PgHdr PgHdr;
+
+/*
+** Each pager stores all currently unreferenced pages in a list sorted
+** in least-recently-used (LRU) order (i.e. the first item on the list has
+** not been referenced in a long time, the last item has been recently
+** used). An instance of this structure is included as part of each
+** pager structure for this purpose (variable Pager.lru).
+**
+** Additionally, if memory-management is enabled, all unreferenced pages
+** are stored in a global LRU list (global variable sqlite3LruPageList).
+**
+** In both cases, the PagerLruList.pFirstSynced variable points to
+** the first page in the corresponding list that does not require an
+** fsync() operation before its memory can be reclaimed. If no such
+** page exists, PagerLruList.pFirstSynced is set to NULL.
+*/
+typedef struct PagerLruList PagerLruList;
+struct PagerLruList {
+ PgHdr *pFirst; /* First page in LRU list */
+ PgHdr *pLast; /* Last page in LRU list (the most recently used) */
+ PgHdr *pFirstSynced; /* First page in list with PgHdr.needSync==0 */
+};
+
+/*
+** The following structure contains the next and previous pointers used
+** to link a PgHdr structure into a PagerLruList linked list.
+*/
+typedef struct PagerLruLink PagerLruLink;
+struct PagerLruLink {
+ PgHdr *pNext;
+ PgHdr *pPrev;
+};
+
+/*
+** Each in-memory image of a page begins with the following header.
+** This header is only visible to this pager module. The client
+** code that calls pager sees only the data that follows the header.
+**
+** Client code should call sqlite3PagerWrite() on a page prior to making
+** any modifications to that page. The first time sqlite3PagerWrite()
+** is called, the original page contents are written into the rollback
+** journal and PgHdr.inJournal and PgHdr.needSync are set. Later, once
+** the journal page has made it onto the disk surface, PgHdr.needSync
+** is cleared. The modified page cannot be written back into the original
+** database file until the journal pages has been synced to disk and the
+** PgHdr.needSync has been cleared.
+**
+** The PgHdr.dirty flag is set when sqlite3PagerWrite() is called and
+** is cleared again when the page content is written back to the original
+** database file.
+**
+** Details of important structure elements:
+**
+** needSync
+**
+** If this is true, this means that it is not safe to write the page
+** content to the database because the original content needed
+** for rollback has not by synced to the main rollback journal.
+** The original content may have been written to the rollback journal
+** but it has not yet been synced. So we cannot write to the database
+** file because power failure might cause the page in the journal file
+** to never reach the disk. It is as if the write to the journal file
+** does not occur until the journal file is synced.
+**
+** This flag is false if the page content exactly matches what
+** currently exists in the database file. The needSync flag is also
+** false if the original content has been written to the main rollback
+** journal and synced. If the page represents a new page that has
+** been added onto the end of the database during the current
+** transaction, the needSync flag is true until the original database
+** size in the journal header has been synced to disk.
+**
+** inJournal
+**
+** This is true if the original page has been written into the main
+** rollback journal. This is always false for new pages added to
+** the end of the database file during the current transaction.
+** And this flag says nothing about whether or not the journal
+** has been synced to disk. For pages that are in the original
+** database file, the following expression should always be true:
+**
+** inJournal = sqlite3BitvecTest(pPager->pInJournal, pgno)
+**
+** The pPager->pInJournal object is only valid for the original
+** pages of the database, not new pages that are added to the end
+** of the database, so obviously the above expression cannot be
+** valid for new pages. For new pages inJournal is always 0.
+**
+** dirty
+**
+** When true, this means that the content of the page has been
+** modified and needs to be written back to the database file.
+** If false, it means that either the content of the page is
+** unchanged or else the content is unimportant and we do not
+** care whether or not it is preserved.
+**
+** alwaysRollback
+**
+** This means that the sqlite3PagerDontRollback() API should be
+** ignored for this page. The DontRollback() API attempts to say
+** that the content of the page on disk is unimportant (it is an
+** unused page on the freelist) so that it is unnecessary to
+** rollback changes to this page because the content of the page
+** can change without changing the meaning of the database. This
+** flag overrides any DontRollback() attempt. This flag is set
+** when a page that originally contained valid data is added to
+** the freelist. Later in the same transaction, this page might
+** be pulled from the freelist and reused for something different
+** and at that point the DontRollback() API will be called because
+** pages taken from the freelist do not need to be protected by
+** the rollback journal. But this flag says that the page was
+** not originally part of the freelist so that it still needs to
+** be rolled back in spite of any subsequent DontRollback() calls.
+**
+** needRead
+**
+** This flag means (when true) that the content of the page has
+** not yet been loaded from disk. The in-memory content is just
+** garbage. (Actually, we zero the content, but you should not
+** make any assumptions about the content nevertheless.) If the
+** content is needed in the future, it should be read from the
+** original database file.
+*/
+struct PgHdr {
+ Pager *pPager; /* The pager to which this page belongs */
+ Pgno pgno; /* The page number for this page */
+ PgHdr *pNextHash, *pPrevHash; /* Hash collision chain for PgHdr.pgno */
+ PagerLruLink free; /* Next and previous free pages */
+ PgHdr *pNextAll; /* A list of all pages */
+ u8 inJournal; /* TRUE if has been written to journal */
+ u8 dirty; /* TRUE if we need to write back changes */
+ u8 needSync; /* Sync journal before writing this page */
+ u8 alwaysRollback; /* Disable DontRollback() for this page */
+ u8 needRead; /* Read content if PagerWrite() is called */
+ short int nRef; /* Number of users of this page */
+ PgHdr *pDirty, *pPrevDirty; /* Dirty pages */
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ PagerLruLink gfree; /* Global list of nRef==0 pages */
+#endif
+#ifdef SQLITE_CHECK_PAGES
+ u32 pageHash;
+#endif
+ void *pData; /* Page data */
+ /* Pager.nExtra bytes of local data appended to this header */
+};
+
+/*
+** For an in-memory only database, some extra information is recorded about
+** each page so that changes can be rolled back. (Journal files are not
+** used for in-memory databases.) The following information is added to
+** the end of every EXTRA block for in-memory databases.
+**
+** This information could have been added directly to the PgHdr structure.
+** But then it would take up an extra 8 bytes of storage on every PgHdr
+** even for disk-based databases. Splitting it out saves 8 bytes. This
+** is only a savings of 0.8% but those percentages add up.
+*/
+typedef struct PgHistory PgHistory;
+struct PgHistory {
+ u8 *pOrig; /* Original page text. Restore to this on a full rollback */
+ u8 *pStmt; /* Text as it was at the beginning of the current statement */
+ PgHdr *pNextStmt, *pPrevStmt; /* List of pages in the statement journal */
+ u8 inStmt; /* TRUE if in the statement subjournal */
+};
+
+/*
+** A macro used for invoking the codec if there is one
+*/
+#ifdef SQLITE_HAS_CODEC
+# define CODEC1(P,D,N,X) if( P->xCodec!=0 ){ P->xCodec(P->pCodecArg,D,N,X); }
+# define CODEC2(P,D,N,X) ((char*)(P->xCodec!=0?P->xCodec(P->pCodecArg,D,N,X):D))
+#else
+# define CODEC1(P,D,N,X) /* NO-OP */
+# define CODEC2(P,D,N,X) ((char*)D)
+#endif
+
+/*
+** Convert a pointer to a PgHdr into a pointer to its data
+** and back again.
+*/
+#define PGHDR_TO_DATA(P) ((P)->pData)
+#define PGHDR_TO_EXTRA(G,P) ((void*)&((G)[1]))
+#define PGHDR_TO_HIST(P,PGR) \
+ ((PgHistory*)&((char*)(&(P)[1]))[(PGR)->nExtra])
+
+/*
+** A open page cache is an instance of the following structure.
+**
+** Pager.errCode may be set to SQLITE_IOERR, SQLITE_CORRUPT, or
+** or SQLITE_FULL. Once one of the first three errors occurs, it persists
+** and is returned as the result of every major pager API call. The
+** SQLITE_FULL return code is slightly different. It persists only until the
+** next successful rollback is performed on the pager cache. Also,
+** SQLITE_FULL does not affect the sqlite3PagerGet() and sqlite3PagerLookup()
+** APIs, they may still be used successfully.
+*/
+struct Pager {
+ sqlite3_vfs *pVfs; /* OS functions to use for IO */
+ u8 journalOpen; /* True if journal file descriptors is valid */
+ u8 journalStarted; /* True if header of journal is synced */
+ u8 useJournal; /* Use a rollback journal on this file */
+ u8 noReadlock; /* Do not bother to obtain readlocks */
+ u8 stmtOpen; /* True if the statement subjournal is open */
+ u8 stmtInUse; /* True we are in a statement subtransaction */
+ u8 stmtAutoopen; /* Open stmt journal when main journal is opened*/
+ u8 noSync; /* Do not sync the journal if true */
+ u8 fullSync; /* Do extra syncs of the journal for robustness */
+ u8 sync_flags; /* One of SYNC_NORMAL or SYNC_FULL */
+ u8 state; /* PAGER_UNLOCK, _SHARED, _RESERVED, etc. */
+ u8 tempFile; /* zFilename is a temporary file */
+ u8 readOnly; /* True for a read-only database */
+ u8 needSync; /* True if an fsync() is needed on the journal */
+ u8 dirtyCache; /* True if cached pages have changed */
+ u8 alwaysRollback; /* Disable DontRollback() for all pages */
+ u8 memDb; /* True to inhibit all file I/O */
+ u8 setMaster; /* True if a m-j name has been written to jrnl */
+ u8 doNotSync; /* Boolean. While true, do not spill the cache */
+ u8 exclusiveMode; /* Boolean. True if locking_mode==EXCLUSIVE */
+ u8 journalMode; /* On of the PAGER_JOURNALMODE_* values */
+ u8 dbModified; /* True if there are any changes to the Db */
+ u8 changeCountDone; /* Set after incrementing the change-counter */
+ u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */
+ int errCode; /* One of several kinds of errors */
+ int dbSize; /* Number of pages in the file */
+ int origDbSize; /* dbSize before the current change */
+ int stmtSize; /* Size of database (in pages) at stmt_begin() */
+ int nRec; /* Number of pages written to the journal */
+ u32 cksumInit; /* Quasi-random value added to every checksum */
+ int stmtNRec; /* Number of records in stmt subjournal */
+ int nExtra; /* Add this many bytes to each in-memory page */
+ int pageSize; /* Number of bytes in a page */
+ int nPage; /* Total number of in-memory pages */
+ int nRef; /* Number of in-memory pages with PgHdr.nRef>0 */
+ int mxPage; /* Maximum number of pages to hold in cache */
+ Pgno mxPgno; /* Maximum allowed size of the database */
+ Bitvec *pInJournal; /* One bit for each page in the database file */
+ Bitvec *pInStmt; /* One bit for each page in the database */
+ char *zFilename; /* Name of the database file */
+ char *zJournal; /* Name of the journal file */
+ char *zDirectory; /* Directory hold database and journal files */
+ char *zStmtJrnl; /* Name of the statement journal file */
+ sqlite3_file *fd, *jfd; /* File descriptors for database and journal */
+ sqlite3_file *stfd; /* File descriptor for the statement subjournal*/
+ BusyHandler *pBusyHandler; /* Pointer to sqlite.busyHandler */
+ PagerLruList lru; /* LRU list of free pages */
+ PgHdr *pAll; /* List of all pages */
+ PgHdr *pStmt; /* List of pages in the statement subjournal */
+ PgHdr *pDirty; /* List of all dirty pages */
+ i64 journalOff; /* Current byte offset in the journal file */
+ i64 journalHdr; /* Byte offset to previous journal header */
+ i64 stmtHdrOff; /* First journal header written this statement */
+ i64 stmtCksum; /* cksumInit when statement was started */
+ i64 stmtJSize; /* Size of journal at stmt_begin() */
+ int sectorSize; /* Assumed sector size during rollback */
+#ifdef SQLITE_TEST
+ int nHit, nMiss; /* Cache hits and missing */
+ int nRead, nWrite; /* Database pages read/written */
+#endif
+ void (*xDestructor)(DbPage*,int); /* Call this routine when freeing pages */
+ void (*xReiniter)(DbPage*,int); /* Call this routine when reloading pages */
+#ifdef SQLITE_HAS_CODEC
+ void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */
+ void *pCodecArg; /* First argument to xCodec() */
+#endif
+ int nHash; /* Size of the pager hash table */
+ PgHdr **aHash; /* Hash table to map page number to PgHdr */
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ Pager *pNext; /* Doubly linked list of pagers on which */
+ Pager *pPrev; /* sqlite3_release_memory() will work */
+ int iInUseMM; /* Non-zero if unavailable to MM */
+ int iInUseDB; /* Non-zero if in sqlite3_release_memory() */
+#endif
+ char *pTmpSpace; /* Pager.pageSize bytes of space for tmp use */
+ char dbFileVers[16]; /* Changes whenever database file changes */
+};
+
+/*
+** The following global variables hold counters used for
+** testing purposes only. These variables do not exist in
+** a non-testing build. These variables are not thread-safe.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_pager_readdb_count = 0; /* Number of full pages read from DB */
+SQLITE_API int sqlite3_pager_writedb_count = 0; /* Number of full pages written to DB */
+SQLITE_API int sqlite3_pager_writej_count = 0; /* Number of pages written to journal */
+SQLITE_API int sqlite3_pager_pgfree_count = 0; /* Number of cache pages freed */
+# define PAGER_INCR(v) v++
+#else
+# define PAGER_INCR(v)
+#endif
+
+/*
+** The following variable points to the head of a double-linked list
+** of all pagers that are eligible for page stealing by the
+** sqlite3_release_memory() interface. Access to this list is
+** protected by the SQLITE_MUTEX_STATIC_MEM2 mutex.
+*/
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+static Pager *sqlite3PagerList = 0;
+static PagerLruList sqlite3LruPageList = {0, 0, 0};
+#endif
+
+
+/*
+** Journal files begin with the following magic string. The data
+** was obtained from /dev/random. It is used only as a sanity check.
+**
+** Since version 2.8.0, the journal format contains additional sanity
+** checking information. If the power fails while the journal is begin
+** written, semi-random garbage data might appear in the journal
+** file after power is restored. If an attempt is then made
+** to roll the journal back, the database could be corrupted. The additional
+** sanity checking data is an attempt to discover the garbage in the
+** journal and ignore it.
+**
+** The sanity checking information for the new journal format consists
+** of a 32-bit checksum on each page of data. The checksum covers both
+** the page number and the pPager->pageSize bytes of data for the page.
+** This cksum is initialized to a 32-bit random value that appears in the
+** journal file right after the header. The random initializer is important,
+** because garbage data that appears at the end of a journal is likely
+** data that was once in other files that have now been deleted. If the
+** garbage data came from an obsolete journal file, the checksums might
+** be correct. But by initializing the checksum to random value which
+** is different for every journal, we minimize that risk.
+*/
+static const unsigned char aJournalMagic[] = {
+ 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd7,
+};
+
+/*
+** The size of the header and of each page in the journal is determined
+** by the following macros.
+*/
+#define JOURNAL_PG_SZ(pPager) ((pPager->pageSize) + 8)
+
+/*
+** The journal header size for this pager. In the future, this could be
+** set to some value read from the disk controller. The important
+** characteristic is that it is the same size as a disk sector.
+*/
+#define JOURNAL_HDR_SZ(pPager) (pPager->sectorSize)
+
+/*
+** The macro MEMDB is true if we are dealing with an in-memory database.
+** We do this as a macro so that if the SQLITE_OMIT_MEMORYDB macro is set,
+** the value of MEMDB will be a constant and the compiler will optimize
+** out code that would never execute.
+*/
+#ifdef SQLITE_OMIT_MEMORYDB
+# define MEMDB 0
+#else
+# define MEMDB pPager->memDb
+#endif
+
+/*
+** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is
+** reserved for working around a windows/posix incompatibility). It is
+** used in the journal to signify that the remainder of the journal file
+** is devoted to storing a master journal name - there are no more pages to
+** roll back. See comments for function writeMasterJournal() for details.
+*/
+/* #define PAGER_MJ_PGNO(x) (PENDING_BYTE/((x)->pageSize)) */
+#define PAGER_MJ_PGNO(x) ((PENDING_BYTE/((x)->pageSize))+1)
+
+/*
+** The maximum legal page number is (2^31 - 1).
+*/
+#define PAGER_MAX_PGNO 2147483647
+
+/*
+** The pagerEnter() and pagerLeave() routines acquire and release
+** a mutex on each pager. The mutex is recursive.
+**
+** This is a special-purpose mutex. It only provides mutual exclusion
+** between the Btree and the Memory Management sqlite3_release_memory()
+** function. It does not prevent, for example, two Btrees from accessing
+** the same pager at the same time. Other general-purpose mutexes in
+** the btree layer handle that chore.
+*/
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ static void pagerEnter(Pager *p){
+ p->iInUseDB++;
+ if( p->iInUseMM && p->iInUseDB==1 ){
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex;
+ mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM2);
+#endif
+ p->iInUseDB = 0;
+ sqlite3_mutex_enter(mutex);
+ p->iInUseDB = 1;
+ sqlite3_mutex_leave(mutex);
+ }
+ assert( p->iInUseMM==0 );
+ }
+ static void pagerLeave(Pager *p){
+ p->iInUseDB--;
+ assert( p->iInUseDB>=0 );
+ }
+#else
+# define pagerEnter(X)
+# define pagerLeave(X)
+#endif
+
+/*
+** Add page pPg to the end of the linked list managed by structure
+** pList (pPg becomes the last entry in the list - the most recently
+** used). Argument pLink should point to either pPg->free or pPg->gfree,
+** depending on whether pPg is being added to the pager-specific or
+** global LRU list.
+*/
+static void listAdd(PagerLruList *pList, PagerLruLink *pLink, PgHdr *pPg){
+ pLink->pNext = 0;
+ pLink->pPrev = pList->pLast;
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ assert(pLink==&pPg->free || pLink==&pPg->gfree);
+ assert(pLink==&pPg->gfree || pList!=&sqlite3LruPageList);
+#endif
+
+ if( pList->pLast ){
+ int iOff = (char *)pLink - (char *)pPg;
+ PagerLruLink *pLastLink = (PagerLruLink *)(&((u8 *)pList->pLast)[iOff]);
+ pLastLink->pNext = pPg;
+ }else{
+ assert(!pList->pFirst);
+ pList->pFirst = pPg;
+ }
+
+ pList->pLast = pPg;
+ if( !pList->pFirstSynced && pPg->needSync==0 ){
+ pList->pFirstSynced = pPg;
+ }
+}
+
+/*
+** Remove pPg from the list managed by the structure pointed to by pList.
+**
+** Argument pLink should point to either pPg->free or pPg->gfree, depending
+** on whether pPg is being added to the pager-specific or global LRU list.
+*/
+static void listRemove(PagerLruList *pList, PagerLruLink *pLink, PgHdr *pPg){
+ int iOff = (char *)pLink - (char *)pPg;
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ assert(pLink==&pPg->free || pLink==&pPg->gfree);
+ assert(pLink==&pPg->gfree || pList!=&sqlite3LruPageList);
+#endif
+
+ if( pPg==pList->pFirst ){
+ pList->pFirst = pLink->pNext;
+ }
+ if( pPg==pList->pLast ){
+ pList->pLast = pLink->pPrev;
+ }
+ if( pLink->pPrev ){
+ PagerLruLink *pPrevLink = (PagerLruLink *)(&((u8 *)pLink->pPrev)[iOff]);
+ pPrevLink->pNext = pLink->pNext;
+ }
+ if( pLink->pNext ){
+ PagerLruLink *pNextLink = (PagerLruLink *)(&((u8 *)pLink->pNext)[iOff]);
+ pNextLink->pPrev = pLink->pPrev;
+ }
+ if( pPg==pList->pFirstSynced ){
+ PgHdr *p = pLink->pNext;
+ while( p && p->needSync ){
+ PagerLruLink *pL = (PagerLruLink *)(&((u8 *)p)[iOff]);
+ p = pL->pNext;
+ }
+ pList->pFirstSynced = p;
+ }
+
+ pLink->pNext = pLink->pPrev = 0;
+}
+
+/*
+** Add page pPg to the list of free pages for the pager. If
+** memory-management is enabled, also add the page to the global
+** list of free pages.
+*/
+static void lruListAdd(PgHdr *pPg){
+ listAdd(&pPg->pPager->lru, &pPg->free, pPg);
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ if( !pPg->pPager->memDb ){
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+ listAdd(&sqlite3LruPageList, &pPg->gfree, pPg);
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+ }
+#endif
+}
+
+/*
+** Remove page pPg from the list of free pages for the associated pager.
+** If memory-management is enabled, also remove pPg from the global list
+** of free pages.
+*/
+static void lruListRemove(PgHdr *pPg){
+ listRemove(&pPg->pPager->lru, &pPg->free, pPg);
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ if( !pPg->pPager->memDb ){
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+ listRemove(&sqlite3LruPageList, &pPg->gfree, pPg);
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+ }
+#endif
+}
+
+/*
+** This function is called just after the needSync flag has been cleared
+** from all pages managed by pPager (usually because the journal file
+** has just been synced). It updates the pPager->lru.pFirstSynced variable
+** and, if memory-management is enabled, the sqlite3LruPageList.pFirstSynced
+** variable also.
+*/
+static void lruListSetFirstSynced(Pager *pPager){
+ pPager->lru.pFirstSynced = pPager->lru.pFirst;
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ if( !pPager->memDb ){
+ PgHdr *p;
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+ for(p=sqlite3LruPageList.pFirst; p && p->needSync; p=p->gfree.pNext);
+ assert(p==pPager->lru.pFirstSynced || p==sqlite3LruPageList.pFirstSynced);
+ sqlite3LruPageList.pFirstSynced = p;
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+ }
+#endif
+}
+
+/*
+** Return true if page *pPg has already been written to the statement
+** journal (or statement snapshot has been created, if *pPg is part
+** of an in-memory database).
+*/
+static int pageInStatement(PgHdr *pPg){
+ Pager *pPager = pPg->pPager;
+ if( MEMDB ){
+ return PGHDR_TO_HIST(pPg, pPager)->inStmt;
+ }else{
+ return sqlite3BitvecTest(pPager->pInStmt, pPg->pgno);
+ }
+}
+
+/*
+** Change the size of the pager hash table to N. N must be a power
+** of two.
+*/
+static void pager_resize_hash_table(Pager *pPager, int N){
+ PgHdr **aHash, *pPg;
+ assert( N>0 && (N&(N-1))==0 );
+#ifdef SQLITE_MALLOC_SOFT_LIMIT
+ if( N*sizeof(aHash[0])>SQLITE_MALLOC_SOFT_LIMIT ){
+ N = SQLITE_MALLOC_SOFT_LIMIT/sizeof(aHash[0]);
+ }
+ if( N==pPager->nHash ) return;
+#endif
+ pagerLeave(pPager);
+ if( pPager->aHash!=0 ) sqlite3FaultBeginBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ aHash = sqlite3MallocZero( sizeof(aHash[0])*N );
+ if( pPager->aHash!=0 ) sqlite3FaultEndBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ pagerEnter(pPager);
+ if( aHash==0 ){
+ /* Failure to rehash is not an error. It is only a performance hit. */
+ return;
+ }
+ sqlite3_free(pPager->aHash);
+ pPager->nHash = N;
+ pPager->aHash = aHash;
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ int h;
+ if( pPg->pgno==0 ){
+ assert( pPg->pNextHash==0 && pPg->pPrevHash==0 );
+ continue;
+ }
+ h = pPg->pgno & (N-1);
+ pPg->pNextHash = aHash[h];
+ if( aHash[h] ){
+ aHash[h]->pPrevHash = pPg;
+ }
+ aHash[h] = pPg;
+ pPg->pPrevHash = 0;
+ }
+}
+
+/*
+** Read a 32-bit integer from the given file descriptor. Store the integer
+** that is read in *pRes. Return SQLITE_OK if everything worked, or an
+** error code is something goes wrong.
+**
+** All values are stored on disk as big-endian.
+*/
+static int read32bits(sqlite3_file *fd, i64 offset, u32 *pRes){
+ unsigned char ac[4];
+ int rc = sqlite3OsRead(fd, ac, sizeof(ac), offset);
+ if( rc==SQLITE_OK ){
+ *pRes = sqlite3Get4byte(ac);
+ }
+ return rc;
+}
+
+/*
+** Write a 32-bit integer into a string buffer in big-endian byte order.
+*/
+#define put32bits(A,B) sqlite3Put4byte((u8*)A,B)
+
+/*
+** Write a 32-bit integer into the given file descriptor. Return SQLITE_OK
+** on success or an error code is something goes wrong.
+*/
+static int write32bits(sqlite3_file *fd, i64 offset, u32 val){
+ char ac[4];
+ put32bits(ac, val);
+ return sqlite3OsWrite(fd, ac, 4, offset);
+}
+
+/*
+** If file pFd is open, call sqlite3OsUnlock() on it.
+*/
+static int osUnlock(sqlite3_file *pFd, int eLock){
+ if( !pFd->pMethods ){
+ return SQLITE_OK;
+ }
+ return sqlite3OsUnlock(pFd, eLock);
+}
+
+/*
+** This function determines whether or not the atomic-write optimization
+** can be used with this pager. The optimization can be used if:
+**
+** (a) the value returned by OsDeviceCharacteristics() indicates that
+** a database page may be written atomically, and
+** (b) the value returned by OsSectorSize() is less than or equal
+** to the page size.
+**
+** If the optimization cannot be used, 0 is returned. If it can be used,
+** then the value returned is the size of the journal file when it
+** contains rollback data for exactly one page.
+*/
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+static int jrnlBufferSize(Pager *pPager){
+ int dc; /* Device characteristics */
+ int nSector; /* Sector size */
+ int nPage; /* Page size */
+ sqlite3_file *fd = pPager->fd;
+
+ if( fd->pMethods ){
+ dc = sqlite3OsDeviceCharacteristics(fd);
+ nSector = sqlite3OsSectorSize(fd);
+ nPage = pPager->pageSize;
+ }
+
+ assert(SQLITE_IOCAP_ATOMIC512==(512>>8));
+ assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8));
+
+ if( !fd->pMethods || (dc&(SQLITE_IOCAP_ATOMIC|(nPage>>8))&&nSector<=nPage) ){
+ return JOURNAL_HDR_SZ(pPager) + JOURNAL_PG_SZ(pPager);
+ }
+ return 0;
+}
+#endif
+
+/*
+** This function should be called when an error occurs within the pager
+** code. The first argument is a pointer to the pager structure, the
+** second the error-code about to be returned by a pager API function.
+** The value returned is a copy of the second argument to this function.
+**
+** If the second argument is SQLITE_IOERR, SQLITE_CORRUPT, or SQLITE_FULL
+** the error becomes persistent. Until the persisten error is cleared,
+** subsequent API calls on this Pager will immediately return the same
+** error code.
+**
+** A persistent error indicates that the contents of the pager-cache
+** cannot be trusted. This state can be cleared by completely discarding
+** the contents of the pager-cache. If a transaction was active when
+** the persistent error occured, then the rollback journal may need
+** to be replayed.
+*/
+static void pager_unlock(Pager *pPager);
+static int pager_error(Pager *pPager, int rc){
+ int rc2 = rc & 0xff;
+ assert(
+ pPager->errCode==SQLITE_FULL ||
+ pPager->errCode==SQLITE_OK ||
+ (pPager->errCode & 0xff)==SQLITE_IOERR
+ );
+ if(
+ rc2==SQLITE_FULL ||
+ rc2==SQLITE_IOERR ||
+ rc2==SQLITE_CORRUPT
+ ){
+ pPager->errCode = rc;
+ if( pPager->state==PAGER_UNLOCK && pPager->nRef==0 ){
+ /* If the pager is already unlocked, call pager_unlock() now to
+ ** clear the error state and ensure that the pager-cache is
+ ** completely empty.
+ */
+ pager_unlock(pPager);
+ }
+ }
+ return rc;
+}
+
+/*
+** If SQLITE_CHECK_PAGES is defined then we do some sanity checking
+** on the cache using a hash function. This is used for testing
+** and debugging only.
+*/
+#ifdef SQLITE_CHECK_PAGES
+/*
+** Return a 32-bit hash of the page data for pPage.
+*/
+static u32 pager_datahash(int nByte, unsigned char *pData){
+ u32 hash = 0;
+ int i;
+ for(i=0; i<nByte; i++){
+ hash = (hash*1039) + pData[i];
+ }
+ return hash;
+}
+static u32 pager_pagehash(PgHdr *pPage){
+ return pager_datahash(pPage->pPager->pageSize,
+ (unsigned char *)PGHDR_TO_DATA(pPage));
+}
+
+/*
+** The CHECK_PAGE macro takes a PgHdr* as an argument. If SQLITE_CHECK_PAGES
+** is defined, and NDEBUG is not defined, an assert() statement checks
+** that the page is either dirty or still matches the calculated page-hash.
+*/
+#define CHECK_PAGE(x) checkPage(x)
+static void checkPage(PgHdr *pPg){
+ Pager *pPager = pPg->pPager;
+ assert( !pPg->pageHash || pPager->errCode || MEMDB || pPg->dirty ||
+ pPg->pageHash==pager_pagehash(pPg) );
+}
+
+#else
+#define pager_datahash(X,Y) 0
+#define pager_pagehash(X) 0
+#define CHECK_PAGE(x)
+#endif
+
+/*
+** When this is called the journal file for pager pPager must be open.
+** The master journal file name is read from the end of the file and
+** written into memory supplied by the caller.
+**
+** zMaster must point to a buffer of at least nMaster bytes allocated by
+** the caller. This should be sqlite3_vfs.mxPathname+1 (to ensure there is
+** enough space to write the master journal name). If the master journal
+** name in the journal is longer than nMaster bytes (including a
+** nul-terminator), then this is handled as if no master journal name
+** were present in the journal.
+**
+** If no master journal file name is present zMaster[0] is set to 0 and
+** SQLITE_OK returned.
+*/
+static int readMasterJournal(sqlite3_file *pJrnl, char *zMaster, int nMaster){
+ int rc;
+ u32 len;
+ i64 szJ;
+ u32 cksum;
+ int i;
+ unsigned char aMagic[8]; /* A buffer to hold the magic header */
+
+ zMaster[0] = '\0';
+
+ rc = sqlite3OsFileSize(pJrnl, &szJ);
+ if( rc!=SQLITE_OK || szJ<16 ) return rc;
+
+ rc = read32bits(pJrnl, szJ-16, &len);
+ if( rc!=SQLITE_OK ) return rc;
+
+ if( len>=nMaster ){
+ return SQLITE_OK;
+ }
+
+ rc = read32bits(pJrnl, szJ-12, &cksum);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8);
+ if( rc!=SQLITE_OK || memcmp(aMagic, aJournalMagic, 8) ) return rc;
+
+ rc = sqlite3OsRead(pJrnl, zMaster, len, szJ-16-len);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ zMaster[len] = '\0';
+
+ /* See if the checksum matches the master journal name */
+ for(i=0; i<len; i++){
+ cksum -= zMaster[i];
+ }
+ if( cksum ){
+ /* If the checksum doesn't add up, then one or more of the disk sectors
+ ** containing the master journal filename is corrupted. This means
+ ** definitely roll back, so just return SQLITE_OK and report a (nul)
+ ** master-journal filename.
+ */
+ zMaster[0] = '\0';
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** Seek the journal file descriptor to the next sector boundary where a
+** journal header may be read or written. Pager.journalOff is updated with
+** the new seek offset.
+**
+** i.e for a sector size of 512:
+**
+** Input Offset Output Offset
+** ---------------------------------------
+** 0 0
+** 512 512
+** 100 512
+** 2000 2048
+**
+*/
+static void seekJournalHdr(Pager *pPager){
+ i64 offset = 0;
+ i64 c = pPager->journalOff;
+ if( c ){
+ offset = ((c-1)/JOURNAL_HDR_SZ(pPager) + 1) * JOURNAL_HDR_SZ(pPager);
+ }
+ assert( offset%JOURNAL_HDR_SZ(pPager)==0 );
+ assert( offset>=c );
+ assert( (offset-c)<JOURNAL_HDR_SZ(pPager) );
+ pPager->journalOff = offset;
+}
+
+/*
+** Write zeros over the header of the journal file. This has the
+** effect of invalidating the journal file and committing the
+** transaction.
+*/
+static int zeroJournalHdr(Pager *pPager, int doTruncate){
+ int rc = SQLITE_OK;
+ static const char zeroHdr[28];
+
+ if( pPager->journalOff ){
+ IOTRACE(("JZEROHDR %p\n", pPager))
+ if( doTruncate ){
+ rc = sqlite3OsTruncate(pPager->jfd, 0);
+ }else{
+ rc = sqlite3OsWrite(pPager->jfd, zeroHdr, sizeof(zeroHdr), 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->sync_flags);
+ }
+ }
+ return rc;
+}
+
+/*
+** The journal file must be open when this routine is called. A journal
+** header (JOURNAL_HDR_SZ bytes) is written into the journal file at the
+** current location.
+**
+** The format for the journal header is as follows:
+** - 8 bytes: Magic identifying journal format.
+** - 4 bytes: Number of records in journal, or -1 no-sync mode is on.
+** - 4 bytes: Random number used for page hash.
+** - 4 bytes: Initial database page count.
+** - 4 bytes: Sector size used by the process that wrote this journal.
+** - 4 bytes: Database page size.
+**
+** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space.
+*/
+static int writeJournalHdr(Pager *pPager){
+ int rc = SQLITE_OK;
+ char *zHeader = pPager->pTmpSpace;
+ int nHeader = pPager->pageSize;
+ int nWrite;
+
+ if( nHeader>JOURNAL_HDR_SZ(pPager) ){
+ nHeader = JOURNAL_HDR_SZ(pPager);
+ }
+
+ if( pPager->stmtHdrOff==0 ){
+ pPager->stmtHdrOff = pPager->journalOff;
+ }
+
+ seekJournalHdr(pPager);
+ pPager->journalHdr = pPager->journalOff;
+
+ memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic));
+
+ /*
+ ** Write the nRec Field - the number of page records that follow this
+ ** journal header. Normally, zero is written to this value at this time.
+ ** After the records are added to the journal (and the journal synced,
+ ** if in full-sync mode), the zero is overwritten with the true number
+ ** of records (see syncJournal()).
+ **
+ ** A faster alternative is to write 0xFFFFFFFF to the nRec field. When
+ ** reading the journal this value tells SQLite to assume that the
+ ** rest of the journal file contains valid page records. This assumption
+ ** is dangerous, as if a failure occured whilst writing to the journal
+ ** file it may contain some garbage data. There are two scenarios
+ ** where this risk can be ignored:
+ **
+ ** * When the pager is in no-sync mode. Corruption can follow a
+ ** power failure in this case anyway.
+ **
+ ** * When the SQLITE_IOCAP_SAFE_APPEND flag is set. This guarantees
+ ** that garbage data is never appended to the journal file.
+ */
+ assert(pPager->fd->pMethods||pPager->noSync);
+ if( (pPager->noSync)
+ || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND)
+ ){
+ put32bits(&zHeader[sizeof(aJournalMagic)], 0xffffffff);
+ }else{
+ put32bits(&zHeader[sizeof(aJournalMagic)], 0);
+ }
+
+ /* The random check-hash initialiser */
+ sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
+ put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit);
+ /* The initial database size */
+ put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbSize);
+ /* The assumed sector size for this process */
+ put32bits(&zHeader[sizeof(aJournalMagic)+12], pPager->sectorSize);
+ if( pPager->journalHdr==0 ){
+ /* The page size */
+ put32bits(&zHeader[sizeof(aJournalMagic)+16], pPager->pageSize);
+ }
+
+ for(nWrite=0; rc==SQLITE_OK&&nWrite<JOURNAL_HDR_SZ(pPager); nWrite+=nHeader){
+ IOTRACE(("JHDR %p %lld %d\n", pPager, pPager->journalHdr, nHeader))
+ rc = sqlite3OsWrite(pPager->jfd, zHeader, nHeader, pPager->journalOff);
+ pPager->journalOff += nHeader;
+ }
+
+ return rc;
+}
+
+/*
+** The journal file must be open when this is called. A journal header file
+** (JOURNAL_HDR_SZ bytes) is read from the current location in the journal
+** file. See comments above function writeJournalHdr() for a description of
+** the journal header format.
+**
+** If the header is read successfully, *nRec is set to the number of
+** page records following this header and *dbSize is set to the size of the
+** database before the transaction began, in pages. Also, pPager->cksumInit
+** is set to the value read from the journal header. SQLITE_OK is returned
+** in this case.
+**
+** If the journal header file appears to be corrupted, SQLITE_DONE is
+** returned and *nRec and *dbSize are not set. If JOURNAL_HDR_SZ bytes
+** cannot be read from the journal file an error code is returned.
+*/
+static int readJournalHdr(
+ Pager *pPager,
+ i64 journalSize,
+ u32 *pNRec,
+ u32 *pDbSize
+){
+ int rc;
+ unsigned char aMagic[8]; /* A buffer to hold the magic header */
+ i64 jrnlOff;
+ int iPageSize;
+
+ seekJournalHdr(pPager);
+ if( pPager->journalOff+JOURNAL_HDR_SZ(pPager) > journalSize ){
+ return SQLITE_DONE;
+ }
+ jrnlOff = pPager->journalOff;
+
+ rc = sqlite3OsRead(pPager->jfd, aMagic, sizeof(aMagic), jrnlOff);
+ if( rc ) return rc;
+ jrnlOff += sizeof(aMagic);
+
+ if( memcmp(aMagic, aJournalMagic, sizeof(aMagic))!=0 ){
+ return SQLITE_DONE;
+ }
+
+ rc = read32bits(pPager->jfd, jrnlOff, pNRec);
+ if( rc ) return rc;
+
+ rc = read32bits(pPager->jfd, jrnlOff+4, &pPager->cksumInit);
+ if( rc ) return rc;
+
+ rc = read32bits(pPager->jfd, jrnlOff+8, pDbSize);
+ if( rc ) return rc;
+
+ rc = read32bits(pPager->jfd, jrnlOff+16, (u32 *)&iPageSize);
+ if( rc==SQLITE_OK
+ && iPageSize>=512
+ && iPageSize<=SQLITE_MAX_PAGE_SIZE
+ && ((iPageSize-1)&iPageSize)==0
+ ){
+ u16 pagesize = iPageSize;
+ rc = sqlite3PagerSetPagesize(pPager, &pagesize);
+ }
+ if( rc ) return rc;
+
+ /* Update the assumed sector-size to match the value used by
+ ** the process that created this journal. If this journal was
+ ** created by a process other than this one, then this routine
+ ** is being called from within pager_playback(). The local value
+ ** of Pager.sectorSize is restored at the end of that routine.
+ */
+ rc = read32bits(pPager->jfd, jrnlOff+12, (u32 *)&pPager->sectorSize);
+ if( rc ) return rc;
+
+ pPager->journalOff += JOURNAL_HDR_SZ(pPager);
+ return SQLITE_OK;
+}
+
+
+/*
+** Write the supplied master journal name into the journal file for pager
+** pPager at the current location. The master journal name must be the last
+** thing written to a journal file. If the pager is in full-sync mode, the
+** journal file descriptor is advanced to the next sector boundary before
+** anything is written. The format is:
+**
+** + 4 bytes: PAGER_MJ_PGNO.
+** + N bytes: length of master journal name.
+** + 4 bytes: N
+** + 4 bytes: Master journal name checksum.
+** + 8 bytes: aJournalMagic[].
+**
+** The master journal page checksum is the sum of the bytes in the master
+** journal name.
+**
+** If zMaster is a NULL pointer (occurs for a single database transaction),
+** this call is a no-op.
+*/
+static int writeMasterJournal(Pager *pPager, const char *zMaster){
+ int rc;
+ int len;
+ int i;
+ i64 jrnlOff;
+ i64 jrnlSize;
+ u32 cksum = 0;
+ char zBuf[sizeof(aJournalMagic)+2*4];
+
+ if( !zMaster || pPager->setMaster) return SQLITE_OK;
+ pPager->setMaster = 1;
+
+ len = strlen(zMaster);
+ for(i=0; i<len; i++){
+ cksum += zMaster[i];
+ }
+
+ /* If in full-sync mode, advance to the next disk sector before writing
+ ** the master journal name. This is in case the previous page written to
+ ** the journal has already been synced.
+ */
+ if( pPager->fullSync ){
+ seekJournalHdr(pPager);
+ }
+ jrnlOff = pPager->journalOff;
+ pPager->journalOff += (len+20);
+
+ rc = write32bits(pPager->jfd, jrnlOff, PAGER_MJ_PGNO(pPager));
+ if( rc!=SQLITE_OK ) return rc;
+ jrnlOff += 4;
+
+ rc = sqlite3OsWrite(pPager->jfd, zMaster, len, jrnlOff);
+ if( rc!=SQLITE_OK ) return rc;
+ jrnlOff += len;
+
+ put32bits(zBuf, len);
+ put32bits(&zBuf[4], cksum);
+ memcpy(&zBuf[8], aJournalMagic, sizeof(aJournalMagic));
+ rc = sqlite3OsWrite(pPager->jfd, zBuf, 8+sizeof(aJournalMagic), jrnlOff);
+ jrnlOff += 8+sizeof(aJournalMagic);
+ pPager->needSync = !pPager->noSync;
+
+ /* If the pager is in peristent-journal mode, then the physical
+ ** journal-file may extend past the end of the master-journal name
+ ** and 8 bytes of magic data just written to the file. This is
+ ** dangerous because the code to rollback a hot-journal file
+ ** will not be able to find the master-journal name to determine
+ ** whether or not the journal is hot.
+ **
+ ** Easiest thing to do in this scenario is to truncate the journal
+ ** file to the required size.
+ */
+ if( (rc==SQLITE_OK)
+ && (rc = sqlite3OsFileSize(pPager->jfd, &jrnlSize))==SQLITE_OK
+ && jrnlSize>jrnlOff
+ ){
+ rc = sqlite3OsTruncate(pPager->jfd, jrnlOff);
+ }
+ return rc;
+}
+
+/*
+** Add or remove a page from the list of all pages that are in the
+** statement journal.
+**
+** The Pager keeps a separate list of pages that are currently in
+** the statement journal. This helps the sqlite3PagerStmtCommit()
+** routine run MUCH faster for the common case where there are many
+** pages in memory but only a few are in the statement journal.
+*/
+static void page_add_to_stmt_list(PgHdr *pPg){
+ Pager *pPager = pPg->pPager;
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ assert( MEMDB );
+ if( !pHist->inStmt ){
+ assert( pHist->pPrevStmt==0 && pHist->pNextStmt==0 );
+ if( pPager->pStmt ){
+ PGHDR_TO_HIST(pPager->pStmt, pPager)->pPrevStmt = pPg;
+ }
+ pHist->pNextStmt = pPager->pStmt;
+ pPager->pStmt = pPg;
+ pHist->inStmt = 1;
+ }
+}
+
+/*
+** Find a page in the hash table given its page number. Return
+** a pointer to the page or NULL if not found.
+*/
+static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){
+ PgHdr *p;
+ if( pPager->aHash==0 ) return 0;
+ p = pPager->aHash[pgno & (pPager->nHash-1)];
+ while( p && p->pgno!=pgno ){
+ p = p->pNextHash;
+ }
+ return p;
+}
+
+/*
+** Clear the in-memory cache. This routine
+** sets the state of the pager back to what it was when it was first
+** opened. Any outstanding pages are invalidated and subsequent attempts
+** to access those pages will likely result in a coredump.
+*/
+static void pager_reset(Pager *pPager){
+ PgHdr *pPg, *pNext;
+ if( pPager->errCode ) return;
+ for(pPg=pPager->pAll; pPg; pPg=pNext){
+ IOTRACE(("PGFREE %p %d\n", pPager, pPg->pgno));
+ PAGER_INCR(sqlite3_pager_pgfree_count);
+ pNext = pPg->pNextAll;
+ lruListRemove(pPg);
+ sqlite3_free(pPg->pData);
+ sqlite3_free(pPg);
+ }
+ assert(pPager->lru.pFirst==0);
+ assert(pPager->lru.pFirstSynced==0);
+ assert(pPager->lru.pLast==0);
+ pPager->pStmt = 0;
+ pPager->pAll = 0;
+ pPager->pDirty = 0;
+ pPager->nHash = 0;
+ sqlite3_free(pPager->aHash);
+ pPager->nPage = 0;
+ pPager->aHash = 0;
+ pPager->nRef = 0;
+}
+
+/*
+** Unlock the database file.
+**
+** If the pager is currently in error state, discard the contents of
+** the cache and reset the Pager structure internal state. If there is
+** an open journal-file, then the next time a shared-lock is obtained
+** on the pager file (by this or any other process), it will be
+** treated as a hot-journal and rolled back.
+*/
+static void pager_unlock(Pager *pPager){
+ if( !pPager->exclusiveMode ){
+ if( !MEMDB ){
+ int rc = osUnlock(pPager->fd, NO_LOCK);
+ if( rc ) pPager->errCode = rc;
+ pPager->dbSize = -1;
+ IOTRACE(("UNLOCK %p\n", pPager))
+
+ /* Always close the journal file when dropping the database lock.
+ ** Otherwise, another connection with journal_mode=delete might
+ ** delete the file out from under us.
+ */
+ if( pPager->journalOpen ){
+ sqlite3OsClose(pPager->jfd);
+ pPager->journalOpen = 0;
+ sqlite3BitvecDestroy(pPager->pInJournal);
+ pPager->pInJournal = 0;
+ }
+
+ /* If Pager.errCode is set, the contents of the pager cache cannot be
+ ** trusted. Now that the pager file is unlocked, the contents of the
+ ** cache can be discarded and the error code safely cleared.
+ */
+ if( pPager->errCode ){
+ if( rc==SQLITE_OK ) pPager->errCode = SQLITE_OK;
+ pager_reset(pPager);
+ if( pPager->stmtOpen ){
+ sqlite3OsClose(pPager->stfd);
+ sqlite3BitvecDestroy(pPager->pInStmt);
+ pPager->pInStmt = 0;
+ }
+ pPager->stmtOpen = 0;
+ pPager->stmtInUse = 0;
+ pPager->journalOff = 0;
+ pPager->journalStarted = 0;
+ pPager->stmtAutoopen = 0;
+ pPager->origDbSize = 0;
+ }
+ }
+
+ if( !MEMDB || pPager->errCode==SQLITE_OK ){
+ pPager->state = PAGER_UNLOCK;
+ pPager->changeCountDone = 0;
+ }
+ }
+}
+
+/*
+** Execute a rollback if a transaction is active and unlock the
+** database file. If the pager has already entered the error state,
+** do not attempt the rollback.
+*/
+static void pagerUnlockAndRollback(Pager *p){
+ /* assert( p->state>=PAGER_RESERVED || p->journalOpen==0 ); */
+ if( p->errCode==SQLITE_OK && p->state>=PAGER_RESERVED ){
+ sqlite3FaultBeginBenign(-1);
+ sqlite3PagerRollback(p);
+ sqlite3FaultEndBenign(-1);
+ }
+ pager_unlock(p);
+#if 0
+ assert( p->errCode || !p->journalOpen || (p->exclusiveMode&&!p->journalOff) );
+ assert( p->errCode || !p->stmtOpen || p->exclusiveMode );
+#endif
+}
+
+/*
+** This routine ends a transaction. A transaction is ended by either
+** a COMMIT or a ROLLBACK.
+**
+** When this routine is called, the pager has the journal file open and
+** a RESERVED or EXCLUSIVE lock on the database. This routine will release
+** the database lock and acquires a SHARED lock in its place if that is
+** the appropriate thing to do. Release locks usually is appropriate,
+** unless we are in exclusive access mode or unless this is a
+** COMMIT AND BEGIN or ROLLBACK AND BEGIN operation.
+**
+** The journal file is either deleted or truncated.
+**
+** TODO: Consider keeping the journal file open for temporary databases.
+** This might give a performance improvement on windows where opening
+** a file is an expensive operation.
+*/
+static int pager_end_transaction(Pager *pPager, int hasMaster){
+ PgHdr *pPg;
+ int rc = SQLITE_OK;
+ int rc2 = SQLITE_OK;
+ assert( !MEMDB );
+ if( pPager->state<PAGER_RESERVED ){
+ return SQLITE_OK;
+ }
+ sqlite3PagerStmtCommit(pPager);
+ if( pPager->stmtOpen && !pPager->exclusiveMode ){
+ sqlite3OsClose(pPager->stfd);
+ pPager->stmtOpen = 0;
+ }
+ if( pPager->journalOpen ){
+ if( pPager->exclusiveMode
+ || pPager->journalMode==PAGER_JOURNALMODE_PERSIST
+ ){
+ rc = zeroJournalHdr(pPager, hasMaster);
+ pager_error(pPager, rc);
+ pPager->journalOff = 0;
+ pPager->journalStarted = 0;
+ }else{
+ sqlite3OsClose(pPager->jfd);
+ pPager->journalOpen = 0;
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0);
+ }
+ }
+ sqlite3BitvecDestroy(pPager->pInJournal);
+ pPager->pInJournal = 0;
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ pPg->inJournal = 0;
+ pPg->dirty = 0;
+ pPg->needSync = 0;
+ pPg->alwaysRollback = 0;
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ }
+ pPager->pDirty = 0;
+ pPager->dirtyCache = 0;
+ pPager->nRec = 0;
+ }else{
+ assert( pPager->pInJournal==0 );
+ }
+
+ if( !pPager->exclusiveMode ){
+ rc2 = osUnlock(pPager->fd, SHARED_LOCK);
+ pPager->state = PAGER_SHARED;
+ }else if( pPager->state==PAGER_SYNCED ){
+ pPager->state = PAGER_EXCLUSIVE;
+ }
+ pPager->origDbSize = 0;
+ pPager->setMaster = 0;
+ pPager->needSync = 0;
+ lruListSetFirstSynced(pPager);
+ pPager->dbSize = -1;
+ pPager->dbModified = 0;
+
+ return (rc==SQLITE_OK?rc2:rc);
+}
+
+/*
+** Compute and return a checksum for the page of data.
+**
+** This is not a real checksum. It is really just the sum of the
+** random initial value and the page number. We experimented with
+** a checksum of the entire data, but that was found to be too slow.
+**
+** Note that the page number is stored at the beginning of data and
+** the checksum is stored at the end. This is important. If journal
+** corruption occurs due to a power failure, the most likely scenario
+** is that one end or the other of the record will be changed. It is
+** much less likely that the two ends of the journal record will be
+** correct and the middle be corrupt. Thus, this "checksum" scheme,
+** though fast and simple, catches the mostly likely kind of corruption.
+**
+** FIX ME: Consider adding every 200th (or so) byte of the data to the
+** checksum. That way if a single page spans 3 or more disk sectors and
+** only the middle sector is corrupt, we will still have a reasonable
+** chance of failing the checksum and thus detecting the problem.
+*/
+static u32 pager_cksum(Pager *pPager, const u8 *aData){
+ u32 cksum = pPager->cksumInit;
+ int i = pPager->pageSize-200;
+ while( i>0 ){
+ cksum += aData[i];
+ i -= 200;
+ }
+ return cksum;
+}
+
+/* Forward declaration */
+static void makeClean(PgHdr*);
+
+/*
+** Read a single page from the journal file opened on file descriptor
+** jfd. Playback this one page.
+**
+** If useCksum==0 it means this journal does not use checksums. Checksums
+** are not used in statement journals because statement journals do not
+** need to survive power failures.
+*/
+static int pager_playback_one_page(
+ Pager *pPager,
+ sqlite3_file *jfd,
+ i64 offset,
+ int useCksum
+){
+ int rc;
+ PgHdr *pPg; /* An existing page in the cache */
+ Pgno pgno; /* The page number of a page in journal */
+ u32 cksum; /* Checksum used for sanity checking */
+ u8 *aData = (u8 *)pPager->pTmpSpace; /* Temp storage for a page */
+
+ /* useCksum should be true for the main journal and false for
+ ** statement journals. Verify that this is always the case
+ */
+ assert( jfd == (useCksum ? pPager->jfd : pPager->stfd) );
+ assert( aData );
+
+ rc = read32bits(jfd, offset, &pgno);
+ if( rc!=SQLITE_OK ) return rc;
+ rc = sqlite3OsRead(jfd, aData, pPager->pageSize, offset+4);
+ if( rc!=SQLITE_OK ) return rc;
+ pPager->journalOff += pPager->pageSize + 4;
+
+ /* Sanity checking on the page. This is more important that I originally
+ ** thought. If a power failure occurs while the journal is being written,
+ ** it could cause invalid data to be written into the journal. We need to
+ ** detect this invalid data (with high probability) and ignore it.
+ */
+ if( pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){
+ return SQLITE_DONE;
+ }
+ if( pgno>(unsigned)pPager->dbSize ){
+ return SQLITE_OK;
+ }
+ if( useCksum ){
+ rc = read32bits(jfd, offset+pPager->pageSize+4, &cksum);
+ if( rc ) return rc;
+ pPager->journalOff += 4;
+ if( pager_cksum(pPager, aData)!=cksum ){
+ return SQLITE_DONE;
+ }
+ }
+
+ assert( pPager->state==PAGER_RESERVED || pPager->state>=PAGER_EXCLUSIVE );
+
+ /* If the pager is in RESERVED state, then there must be a copy of this
+ ** page in the pager cache. In this case just update the pager cache,
+ ** not the database file. The page is left marked dirty in this case.
+ **
+ ** An exception to the above rule: If the database is in no-sync mode
+ ** and a page is moved during an incremental vacuum then the page may
+ ** not be in the pager cache. Later: if a malloc() or IO error occurs
+ ** during a Movepage() call, then the page may not be in the cache
+ ** either. So the condition described in the above paragraph is not
+ ** assert()able.
+ **
+ ** If in EXCLUSIVE state, then we update the pager cache if it exists
+ ** and the main file. The page is then marked not dirty.
+ **
+ ** Ticket #1171: The statement journal might contain page content that is
+ ** different from the page content at the start of the transaction.
+ ** This occurs when a page is changed prior to the start of a statement
+ ** then changed again within the statement. When rolling back such a
+ ** statement we must not write to the original database unless we know
+ ** for certain that original page contents are synced into the main rollback
+ ** journal. Otherwise, a power loss might leave modified data in the
+ ** database file without an entry in the rollback journal that can
+ ** restore the database to its original form. Two conditions must be
+ ** met before writing to the database files. (1) the database must be
+ ** locked. (2) we know that the original page content is fully synced
+ ** in the main journal either because the page is not in cache or else
+ ** the page is marked as needSync==0.
+ **
+ ** 2008-04-14: When attempting to vacuum a corrupt database file, it
+ ** is possible to fail a statement on a database that does not yet exist.
+ ** Do not attempt to write if database file has never been opened.
+ */
+ pPg = pager_lookup(pPager, pgno);
+ PAGERTRACE4("PLAYBACK %d page %d hash(%08x)\n",
+ PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, aData));
+ if( pPager->state>=PAGER_EXCLUSIVE && (pPg==0 || pPg->needSync==0)
+ && pPager->fd->pMethods ){
+ i64 offset = (pgno-1)*(i64)pPager->pageSize;
+ rc = sqlite3OsWrite(pPager->fd, aData, pPager->pageSize, offset);
+ if( pPg ){
+ makeClean(pPg);
+ }
+ }
+ if( pPg ){
+ /* No page should ever be explicitly rolled back that is in use, except
+ ** for page 1 which is held in use in order to keep the lock on the
+ ** database active. However such a page may be rolled back as a result
+ ** of an internal error resulting in an automatic call to
+ ** sqlite3PagerRollback().
+ */
+ void *pData;
+ /* assert( pPg->nRef==0 || pPg->pgno==1 ); */
+ pData = PGHDR_TO_DATA(pPg);
+ memcpy(pData, aData, pPager->pageSize);
+ if( pPager->xReiniter ){
+ pPager->xReiniter(pPg, pPager->pageSize);
+ }
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ /* If this was page 1, then restore the value of Pager.dbFileVers.
+ ** Do this before any decoding. */
+ if( pgno==1 ){
+ memcpy(&pPager->dbFileVers, &((u8*)pData)[24],sizeof(pPager->dbFileVers));
+ }
+
+ /* Decode the page just read from disk */
+ CODEC1(pPager, pData, pPg->pgno, 3);
+ }
+ return rc;
+}
+
+/*
+** Parameter zMaster is the name of a master journal file. A single journal
+** file that referred to the master journal file has just been rolled back.
+** This routine checks if it is possible to delete the master journal file,
+** and does so if it is.
+**
+** Argument zMaster may point to Pager.pTmpSpace. So that buffer is not
+** available for use within this function.
+**
+**
+** The master journal file contains the names of all child journals.
+** To tell if a master journal can be deleted, check to each of the
+** children. If all children are either missing or do not refer to
+** a different master journal, then this master journal can be deleted.
+*/
+static int pager_delmaster(Pager *pPager, const char *zMaster){
+ sqlite3_vfs *pVfs = pPager->pVfs;
+ int rc;
+ int master_open = 0;
+ sqlite3_file *pMaster;
+ sqlite3_file *pJournal;
+ char *zMasterJournal = 0; /* Contents of master journal file */
+ i64 nMasterJournal; /* Size of master journal file */
+
+ /* Open the master journal file exclusively in case some other process
+ ** is running this routine also. Not that it makes too much difference.
+ */
+ pMaster = (sqlite3_file *)sqlite3_malloc(pVfs->szOsFile * 2);
+ pJournal = (sqlite3_file *)(((u8 *)pMaster) + pVfs->szOsFile);
+ if( !pMaster ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_MASTER_JOURNAL);
+ rc = sqlite3OsOpen(pVfs, zMaster, pMaster, flags, 0);
+ }
+ if( rc!=SQLITE_OK ) goto delmaster_out;
+ master_open = 1;
+
+ rc = sqlite3OsFileSize(pMaster, &nMasterJournal);
+ if( rc!=SQLITE_OK ) goto delmaster_out;
+
+ if( nMasterJournal>0 ){
+ char *zJournal;
+ char *zMasterPtr = 0;
+ int nMasterPtr = pPager->pVfs->mxPathname+1;
+
+ /* Load the entire master journal file into space obtained from
+ ** sqlite3_malloc() and pointed to by zMasterJournal.
+ */
+ zMasterJournal = (char *)sqlite3_malloc(nMasterJournal + nMasterPtr);
+ if( !zMasterJournal ){
+ rc = SQLITE_NOMEM;
+ goto delmaster_out;
+ }
+ zMasterPtr = &zMasterJournal[nMasterJournal];
+ rc = sqlite3OsRead(pMaster, zMasterJournal, nMasterJournal, 0);
+ if( rc!=SQLITE_OK ) goto delmaster_out;
+
+ zJournal = zMasterJournal;
+ while( (zJournal-zMasterJournal)<nMasterJournal ){
+ rc = sqlite3OsAccess(pVfs, zJournal, SQLITE_ACCESS_EXISTS);
+ if( rc!=0 && rc!=1 ){
+ rc = SQLITE_IOERR_NOMEM;
+ goto delmaster_out;
+ }
+ if( rc==1 ){
+ /* One of the journals pointed to by the master journal exists.
+ ** Open it and check if it points at the master journal. If
+ ** so, return without deleting the master journal file.
+ */
+ int c;
+ int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_MAIN_JOURNAL);
+ rc = sqlite3OsOpen(pVfs, zJournal, pJournal, flags, 0);
+ if( rc!=SQLITE_OK ){
+ goto delmaster_out;
+ }
+
+ rc = readMasterJournal(pJournal, zMasterPtr, nMasterPtr);
+ sqlite3OsClose(pJournal);
+ if( rc!=SQLITE_OK ){
+ goto delmaster_out;
+ }
+
+ c = zMasterPtr[0]!=0 && strcmp(zMasterPtr, zMaster)==0;
+ if( c ){
+ /* We have a match. Do not delete the master journal file. */
+ goto delmaster_out;
+ }
+ }
+ zJournal += (strlen(zJournal)+1);
+ }
+ }
+
+ rc = sqlite3OsDelete(pVfs, zMaster, 0);
+
+delmaster_out:
+ if( zMasterJournal ){
+ sqlite3_free(zMasterJournal);
+ }
+ if( master_open ){
+ sqlite3OsClose(pMaster);
+ }
+ sqlite3_free(pMaster);
+ return rc;
+}
+
+
+static void pager_truncate_cache(Pager *pPager);
+
+/*
+** Truncate the main file of the given pager to the number of pages
+** indicated. Also truncate the cached representation of the file.
+**
+** Might might be the case that the file on disk is smaller than nPage.
+** This can happen, for example, if we are in the middle of a transaction
+** which has extended the file size and the new pages are still all held
+** in cache, then an INSERT or UPDATE does a statement rollback. Some
+** operating system implementations can get confused if you try to
+** truncate a file to some size that is larger than it currently is,
+** so detect this case and write a single zero byte to the end of the new
+** file instead.
+*/
+static int pager_truncate(Pager *pPager, int nPage){
+ int rc = SQLITE_OK;
+ if( pPager->state>=PAGER_EXCLUSIVE && pPager->fd->pMethods ){
+ i64 currentSize, newSize;
+ rc = sqlite3OsFileSize(pPager->fd, &currentSize);
+ newSize = pPager->pageSize*(i64)nPage;
+ if( rc==SQLITE_OK && currentSize!=newSize ){
+ if( currentSize>newSize ){
+ rc = sqlite3OsTruncate(pPager->fd, newSize);
+ }else{
+ rc = sqlite3OsWrite(pPager->fd, "", 1, newSize-1);
+ }
+ }
+ }
+ if( rc==SQLITE_OK ){
+ pPager->dbSize = nPage;
+ pager_truncate_cache(pPager);
+ }
+ return rc;
+}
+
+/*
+** Set the sectorSize for the given pager.
+**
+** The sector size is at least as big as the sector size reported
+** by sqlite3OsSectorSize(). The minimum sector size is 512.
+*/
+static void setSectorSize(Pager *pPager){
+ assert(pPager->fd->pMethods||pPager->tempFile);
+ if( !pPager->tempFile ){
+ /* Sector size doesn't matter for temporary files. Also, the file
+ ** may not have been opened yet, in whcih case the OsSectorSize()
+ ** call will segfault.
+ */
+ pPager->sectorSize = sqlite3OsSectorSize(pPager->fd);
+ }
+ if( pPager->sectorSize<512 ){
+ pPager->sectorSize = 512;
+ }
+}
+
+/*
+** Playback the journal and thus restore the database file to
+** the state it was in before we started making changes.
+**
+** The journal file format is as follows:
+**
+** (1) 8 byte prefix. A copy of aJournalMagic[].
+** (2) 4 byte big-endian integer which is the number of valid page records
+** in the journal. If this value is 0xffffffff, then compute the
+** number of page records from the journal size.
+** (3) 4 byte big-endian integer which is the initial value for the
+** sanity checksum.
+** (4) 4 byte integer which is the number of pages to truncate the
+** database to during a rollback.
+** (5) 4 byte big-endian integer which is the sector size. The header
+** is this many bytes in size.
+** (6) 4 byte big-endian integer which is the page case.
+** (7) 4 byte integer which is the number of bytes in the master journal
+** name. The value may be zero (indicate that there is no master
+** journal.)
+** (8) N bytes of the master journal name. The name will be nul-terminated
+** and might be shorter than the value read from (5). If the first byte
+** of the name is \000 then there is no master journal. The master
+** journal name is stored in UTF-8.
+** (9) Zero or more pages instances, each as follows:
+** + 4 byte page number.
+** + pPager->pageSize bytes of data.
+** + 4 byte checksum
+**
+** When we speak of the journal header, we mean the first 8 items above.
+** Each entry in the journal is an instance of the 9th item.
+**
+** Call the value from the second bullet "nRec". nRec is the number of
+** valid page entries in the journal. In most cases, you can compute the
+** value of nRec from the size of the journal file. But if a power
+** failure occurred while the journal was being written, it could be the
+** case that the size of the journal file had already been increased but
+** the extra entries had not yet made it safely to disk. In such a case,
+** the value of nRec computed from the file size would be too large. For
+** that reason, we always use the nRec value in the header.
+**
+** If the nRec value is 0xffffffff it means that nRec should be computed
+** from the file size. This value is used when the user selects the
+** no-sync option for the journal. A power failure could lead to corruption
+** in this case. But for things like temporary table (which will be
+** deleted when the power is restored) we don't care.
+**
+** If the file opened as the journal file is not a well-formed
+** journal file then all pages up to the first corrupted page are rolled
+** back (or no pages if the journal header is corrupted). The journal file
+** is then deleted and SQLITE_OK returned, just as if no corruption had
+** been encountered.
+**
+** If an I/O or malloc() error occurs, the journal-file is not deleted
+** and an error code is returned.
+*/
+static int pager_playback(Pager *pPager, int isHot){
+ sqlite3_vfs *pVfs = pPager->pVfs;
+ i64 szJ; /* Size of the journal file in bytes */
+ u32 nRec; /* Number of Records in the journal */
+ int i; /* Loop counter */
+ Pgno mxPg = 0; /* Size of the original file in pages */
+ int rc; /* Result code of a subroutine */
+ int res = 0; /* Value returned by sqlite3OsAccess() */
+ char *zMaster = 0; /* Name of master journal file if any */
+
+ /* Figure out how many records are in the journal. Abort early if
+ ** the journal is empty.
+ */
+ assert( pPager->journalOpen );
+ rc = sqlite3OsFileSize(pPager->jfd, &szJ);
+ if( rc!=SQLITE_OK || szJ==0 ){
+ goto end_playback;
+ }
+
+ /* Read the master journal name from the journal, if it is present.
+ ** If a master journal file name is specified, but the file is not
+ ** present on disk, then the journal is not hot and does not need to be
+ ** played back.
+ */
+ zMaster = pPager->pTmpSpace;
+ rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1);
+ if( rc!=SQLITE_OK || (zMaster[0]
+ && (res=sqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS))==0 )
+ ){
+ zMaster = 0;
+ goto end_playback;
+ }
+ zMaster = 0;
+ if( res<0 ){
+ rc = SQLITE_IOERR_NOMEM;
+ goto end_playback;
+ }
+ pPager->journalOff = 0;
+
+ /* This loop terminates either when the readJournalHdr() call returns
+ ** SQLITE_DONE or an IO error occurs. */
+ while( 1 ){
+
+ /* Read the next journal header from the journal file. If there are
+ ** not enough bytes left in the journal file for a complete header, or
+ ** it is corrupted, then a process must of failed while writing it.
+ ** This indicates nothing more needs to be rolled back.
+ */
+ rc = readJournalHdr(pPager, szJ, &nRec, &mxPg);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ }
+ goto end_playback;
+ }
+
+ /* If nRec is 0xffffffff, then this journal was created by a process
+ ** working in no-sync mode. This means that the rest of the journal
+ ** file consists of pages, there are no more journal headers. Compute
+ ** the value of nRec based on this assumption.
+ */
+ if( nRec==0xffffffff ){
+ assert( pPager->journalOff==JOURNAL_HDR_SZ(pPager) );
+ nRec = (szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager);
+ }
+
+ /* If nRec is 0 and this rollback is of a transaction created by this
+ ** process and if this is the final header in the journal, then it means
+ ** that this part of the journal was being filled but has not yet been
+ ** synced to disk. Compute the number of pages based on the remaining
+ ** size of the file.
+ **
+ ** The third term of the test was added to fix ticket #2565.
+ */
+ if( nRec==0 && !isHot &&
+ pPager->journalHdr+JOURNAL_HDR_SZ(pPager)==pPager->journalOff ){
+ nRec = (szJ - pPager->journalOff) / JOURNAL_PG_SZ(pPager);
+ }
+
+ /* If this is the first header read from the journal, truncate the
+ ** database file back to its original size.
+ */
+ if( pPager->journalOff==JOURNAL_HDR_SZ(pPager) ){
+ rc = pager_truncate(pPager, mxPg);
+ if( rc!=SQLITE_OK ){
+ goto end_playback;
+ }
+ }
+
+ /* Copy original pages out of the journal and back into the database file.
+ */
+ for(i=0; i<nRec; i++){
+ rc = pager_playback_one_page(pPager, pPager->jfd, pPager->journalOff, 1);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ pPager->journalOff = szJ;
+ break;
+ }else{
+ goto end_playback;
+ }
+ }
+ }
+ }
+ /*NOTREACHED*/
+ assert( 0 );
+
+end_playback:
+ if( rc==SQLITE_OK ){
+ zMaster = pPager->pTmpSpace;
+ rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1);
+ }
+ if( rc==SQLITE_OK ){
+ rc = pager_end_transaction(pPager, zMaster[0]!='\0');
+ }
+ if( rc==SQLITE_OK && zMaster[0] ){
+ /* If there was a master journal and this routine will return success,
+ ** see if it is possible to delete the master journal.
+ */
+ rc = pager_delmaster(pPager, zMaster);
+ }
+
+ /* The Pager.sectorSize variable may have been updated while rolling
+ ** back a journal created by a process with a different sector size
+ ** value. Reset it to the correct value for this process.
+ */
+ setSectorSize(pPager);
+ return rc;
+}
+
+/*
+** Playback the statement journal.
+**
+** This is similar to playing back the transaction journal but with
+** a few extra twists.
+**
+** (1) The number of pages in the database file at the start of
+** the statement is stored in pPager->stmtSize, not in the
+** journal file itself.
+**
+** (2) In addition to playing back the statement journal, also
+** playback all pages of the transaction journal beginning
+** at offset pPager->stmtJSize.
+*/
+static int pager_stmt_playback(Pager *pPager){
+ i64 szJ; /* Size of the full journal */
+ i64 hdrOff;
+ int nRec; /* Number of Records */
+ int i; /* Loop counter */
+ int rc;
+
+ szJ = pPager->journalOff;
+
+ /* Set hdrOff to be the offset just after the end of the last journal
+ ** page written before the first journal-header for this statement
+ ** transaction was written, or the end of the file if no journal
+ ** header was written.
+ */
+ hdrOff = pPager->stmtHdrOff;
+ assert( pPager->fullSync || !hdrOff );
+ if( !hdrOff ){
+ hdrOff = szJ;
+ }
+
+ /* Truncate the database back to its original size.
+ */
+ rc = pager_truncate(pPager, pPager->stmtSize);
+ assert( pPager->state>=PAGER_SHARED );
+
+ /* Figure out how many records are in the statement journal.
+ */
+ assert( pPager->stmtInUse && pPager->journalOpen );
+ nRec = pPager->stmtNRec;
+
+ /* Copy original pages out of the statement journal and back into the
+ ** database file. Note that the statement journal omits checksums from
+ ** each record since power-failure recovery is not important to statement
+ ** journals.
+ */
+ for(i=0; i<nRec; i++){
+ i64 offset = i*(4+pPager->pageSize);
+ rc = pager_playback_one_page(pPager, pPager->stfd, offset, 0);
+ assert( rc!=SQLITE_DONE );
+ if( rc!=SQLITE_OK ) goto end_stmt_playback;
+ }
+
+ /* Now roll some pages back from the transaction journal. Pager.stmtJSize
+ ** was the size of the journal file when this statement was started, so
+ ** everything after that needs to be rolled back, either into the
+ ** database, the memory cache, or both.
+ **
+ ** If it is not zero, then Pager.stmtHdrOff is the offset to the start
+ ** of the first journal header written during this statement transaction.
+ */
+ pPager->journalOff = pPager->stmtJSize;
+ pPager->cksumInit = pPager->stmtCksum;
+ while( pPager->journalOff < hdrOff ){
+ rc = pager_playback_one_page(pPager, pPager->jfd, pPager->journalOff, 1);
+ assert( rc!=SQLITE_DONE );
+ if( rc!=SQLITE_OK ) goto end_stmt_playback;
+ }
+
+ while( pPager->journalOff < szJ ){
+ u32 nJRec; /* Number of Journal Records */
+ u32 dummy;
+ rc = readJournalHdr(pPager, szJ, &nJRec, &dummy);
+ if( rc!=SQLITE_OK ){
+ assert( rc!=SQLITE_DONE );
+ goto end_stmt_playback;
+ }
+ if( nJRec==0 ){
+ nJRec = (szJ - pPager->journalOff) / (pPager->pageSize+8);
+ }
+ for(i=nJRec-1; i>=0 && pPager->journalOff < szJ; i--){
+ rc = pager_playback_one_page(pPager, pPager->jfd, pPager->journalOff, 1);
+ assert( rc!=SQLITE_DONE );
+ if( rc!=SQLITE_OK ) goto end_stmt_playback;
+ }
+ }
+
+ pPager->journalOff = szJ;
+
+end_stmt_playback:
+ if( rc==SQLITE_OK) {
+ pPager->journalOff = szJ;
+ /* pager_reload_cache(pPager); */
+ }
+ return rc;
+}
+
+/*
+** Change the maximum number of in-memory pages that are allowed.
+*/
+SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager *pPager, int mxPage){
+ if( mxPage>10 ){
+ pPager->mxPage = mxPage;
+ }else{
+ pPager->mxPage = 10;
+ }
+}
+
+/*
+** Adjust the robustness of the database to damage due to OS crashes
+** or power failures by changing the number of syncs()s when writing
+** the rollback journal. There are three levels:
+**
+** OFF sqlite3OsSync() is never called. This is the default
+** for temporary and transient files.
+**
+** NORMAL The journal is synced once before writes begin on the
+** database. This is normally adequate protection, but
+** it is theoretically possible, though very unlikely,
+** that an inopertune power failure could leave the journal
+** in a state which would cause damage to the database
+** when it is rolled back.
+**
+** FULL The journal is synced twice before writes begin on the
+** database (with some additional information - the nRec field
+** of the journal header - being written in between the two
+** syncs). If we assume that writing a
+** single disk sector is atomic, then this mode provides
+** assurance that the journal will not be corrupted to the
+** point of causing damage to the database during rollback.
+**
+** Numeric values associated with these states are OFF==1, NORMAL=2,
+** and FULL=3.
+*/
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+SQLITE_PRIVATE void sqlite3PagerSetSafetyLevel(Pager *pPager, int level, int full_fsync){
+ pPager->noSync = level==1 || pPager->tempFile;
+ pPager->fullSync = level==3 && !pPager->tempFile;
+ pPager->sync_flags = (full_fsync?SQLITE_SYNC_FULL:SQLITE_SYNC_NORMAL);
+ if( pPager->noSync ) pPager->needSync = 0;
+}
+#endif
+
+/*
+** The following global variable is incremented whenever the library
+** attempts to open a temporary file. This information is used for
+** testing and analysis only.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_opentemp_count = 0;
+#endif
+
+/*
+** Open a temporary file.
+**
+** Write the file descriptor into *fd. Return SQLITE_OK on success or some
+** other error code if we fail. The OS will automatically delete the temporary
+** file when it is closed.
+*/
+static int sqlite3PagerOpentemp(
+ sqlite3_vfs *pVfs, /* The virtual file system layer */
+ sqlite3_file *pFile, /* Write the file descriptor here */
+ char *zFilename, /* Name of the file. Might be NULL */
+ int vfsFlags /* Flags passed through to the VFS */
+){
+ int rc;
+ assert( zFilename!=0 );
+
+#ifdef SQLITE_TEST
+ sqlite3_opentemp_count++; /* Used for testing and analysis only */
+#endif
+
+ vfsFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
+ SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE;
+ rc = sqlite3OsOpen(pVfs, zFilename, pFile, vfsFlags, 0);
+ assert( rc!=SQLITE_OK || pFile->pMethods );
+ return rc;
+}
+
+/*
+** Create a new page cache and put a pointer to the page cache in *ppPager.
+** The file to be cached need not exist. The file is not locked until
+** the first call to sqlite3PagerGet() and is only held open until the
+** last page is released using sqlite3PagerUnref().
+**
+** If zFilename is NULL then a randomly-named temporary file is created
+** and used as the file to be cached. The file will be deleted
+** automatically when it is closed.
+**
+** If zFilename is ":memory:" then all information is held in cache.
+** It is never written to disk. This can be used to implement an
+** in-memory database.
+*/
+SQLITE_PRIVATE int sqlite3PagerOpen(
+ sqlite3_vfs *pVfs, /* The virtual file system to use */
+ Pager **ppPager, /* Return the Pager structure here */
+ const char *zFilename, /* Name of the database file to open */
+ int nExtra, /* Extra bytes append to each in-memory page */
+ int flags, /* flags controlling this file */
+ int vfsFlags /* flags passed through to sqlite3_vfs.xOpen() */
+){
+ u8 *pPtr;
+ Pager *pPager = 0;
+ int rc = SQLITE_OK;
+ int i;
+ int tempFile = 0;
+ int memDb = 0;
+ int readOnly = 0;
+ int useJournal = (flags & PAGER_OMIT_JOURNAL)==0;
+ int noReadlock = (flags & PAGER_NO_READLOCK)!=0;
+ int journalFileSize = sqlite3JournalSize(pVfs);
+ int nDefaultPage = SQLITE_DEFAULT_PAGE_SIZE;
+ char *zPathname;
+ int nPathname;
+ char *zStmtJrnl;
+ int nStmtJrnl;
+
+ /* The default return is a NULL pointer */
+ *ppPager = 0;
+
+ /* Compute the full pathname */
+ nPathname = pVfs->mxPathname+1;
+ zPathname = sqlite3_malloc(nPathname*2);
+ if( zPathname==0 ){
+ return SQLITE_NOMEM;
+ }
+ if( zFilename && zFilename[0] ){
+#ifndef SQLITE_OMIT_MEMORYDB
+ if( strcmp(zFilename,":memory:")==0 ){
+ memDb = 1;
+ zPathname[0] = 0;
+ }else
+#endif
+ {
+ rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname);
+ }
+ }else{
+ rc = sqlite3OsGetTempname(pVfs, nPathname, zPathname);
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(zPathname);
+ return rc;
+ }
+ nPathname = strlen(zPathname);
+
+ /* Put the statement journal in temporary disk space since this is
+ ** sometimes RAM disk or other optimized storage. Unlikely the main
+ ** main journal file, the statement journal does not need to be
+ ** colocated with the database nor does it need to be persistent.
+ */
+ zStmtJrnl = &zPathname[nPathname+1];
+ rc = sqlite3OsGetTempname(pVfs, pVfs->mxPathname+1, zStmtJrnl);
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(zPathname);
+ return rc;
+ }
+ nStmtJrnl = strlen(zStmtJrnl);
+
+ /* Allocate memory for the pager structure */
+ pPager = sqlite3MallocZero(
+ sizeof(*pPager) + /* Pager structure */
+ journalFileSize + /* The journal file structure */
+ pVfs->szOsFile * 3 + /* The main db and two journal files */
+ 3*nPathname + 40 + /* zFilename, zDirectory, zJournal */
+ nStmtJrnl /* zStmtJrnl */
+ );
+ if( !pPager ){
+ sqlite3_free(zPathname);
+ return SQLITE_NOMEM;
+ }
+ pPtr = (u8 *)&pPager[1];
+ pPager->vfsFlags = vfsFlags;
+ pPager->fd = (sqlite3_file*)&pPtr[pVfs->szOsFile*0];
+ pPager->stfd = (sqlite3_file*)&pPtr[pVfs->szOsFile*1];
+ pPager->jfd = (sqlite3_file*)&pPtr[pVfs->szOsFile*2];
+ pPager->zFilename = (char*)&pPtr[pVfs->szOsFile*2+journalFileSize];
+ pPager->zDirectory = &pPager->zFilename[nPathname+1];
+ pPager->zJournal = &pPager->zDirectory[nPathname+1];
+ pPager->zStmtJrnl = &pPager->zJournal[nPathname+10];
+ pPager->pVfs = pVfs;
+ memcpy(pPager->zFilename, zPathname, nPathname+1);
+ memcpy(pPager->zStmtJrnl, zStmtJrnl, nStmtJrnl+1);
+ sqlite3_free(zPathname);
+
+ /* Open the pager file.
+ */
+ if( zFilename && zFilename[0] && !memDb ){
+ if( nPathname>(pVfs->mxPathname - sizeof("-journal")) ){
+ rc = SQLITE_CANTOPEN;
+ }else{
+ int fout = 0;
+ rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd,
+ pPager->vfsFlags, &fout);
+ readOnly = (fout&SQLITE_OPEN_READONLY);
+
+ /* If the file was successfully opened for read/write access,
+ ** choose a default page size in case we have to create the
+ ** database file. The default page size is the maximum of:
+ **
+ ** + SQLITE_DEFAULT_PAGE_SIZE,
+ ** + The value returned by sqlite3OsSectorSize()
+ ** + The largest page size that can be written atomically.
+ */
+ if( rc==SQLITE_OK && !readOnly ){
+ int iSectorSize = sqlite3OsSectorSize(pPager->fd);
+ if( nDefaultPage<iSectorSize ){
+ nDefaultPage = iSectorSize;
+ }
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+ {
+ int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
+ int ii;
+ assert(SQLITE_IOCAP_ATOMIC512==(512>>8));
+ assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8));
+ assert(SQLITE_MAX_DEFAULT_PAGE_SIZE<=65536);
+ for(ii=nDefaultPage; ii<=SQLITE_MAX_DEFAULT_PAGE_SIZE; ii=ii*2){
+ if( iDc&(SQLITE_IOCAP_ATOMIC|(ii>>8)) ) nDefaultPage = ii;
+ }
+ }
+#endif
+ if( nDefaultPage>SQLITE_MAX_DEFAULT_PAGE_SIZE ){
+ nDefaultPage = SQLITE_MAX_DEFAULT_PAGE_SIZE;
+ }
+ }
+ }
+ }else if( !memDb ){
+ /* If a temporary file is requested, it is not opened immediately.
+ ** In this case we accept the default page size and delay actually
+ ** opening the file until the first call to OsWrite().
+ */
+ tempFile = 1;
+ pPager->state = PAGER_EXCLUSIVE;
+ }
+
+ if( pPager && rc==SQLITE_OK ){
+ pPager->pTmpSpace = sqlite3MallocZero(nDefaultPage);
+ }
+
+ /* If an error occured in either of the blocks above.
+ ** Free the Pager structure and close the file.
+ ** Since the pager is not allocated there is no need to set
+ ** any Pager.errMask variables.
+ */
+ if( !pPager || !pPager->pTmpSpace ){
+ sqlite3OsClose(pPager->fd);
+ sqlite3_free(pPager);
+ return ((rc==SQLITE_OK)?SQLITE_NOMEM:rc);
+ }
+
+ PAGERTRACE3("OPEN %d %s\n", FILEHANDLEID(pPager->fd), pPager->zFilename);
+ IOTRACE(("OPEN %p %s\n", pPager, pPager->zFilename))
+
+ /* Fill in Pager.zDirectory[] */
+ memcpy(pPager->zDirectory, pPager->zFilename, nPathname+1);
+ for(i=strlen(pPager->zDirectory); i>0 && pPager->zDirectory[i-1]!='/'; i--){}
+ if( i>0 ) pPager->zDirectory[i-1] = 0;
+
+ /* Fill in Pager.zJournal[] */
+ memcpy(pPager->zJournal, pPager->zFilename, nPathname);
+ memcpy(&pPager->zJournal[nPathname], "-journal", 9);
+
+ /* pPager->journalOpen = 0; */
+ pPager->useJournal = useJournal && !memDb;
+ pPager->noReadlock = noReadlock && readOnly;
+ /* pPager->stmtOpen = 0; */
+ /* pPager->stmtInUse = 0; */
+ /* pPager->nRef = 0; */
+ pPager->dbSize = memDb-1;
+ pPager->pageSize = nDefaultPage;
+ /* pPager->stmtSize = 0; */
+ /* pPager->stmtJSize = 0; */
+ /* pPager->nPage = 0; */
+ pPager->mxPage = 100;
+ pPager->mxPgno = SQLITE_MAX_PAGE_COUNT;
+ /* pPager->state = PAGER_UNLOCK; */
+ assert( pPager->state == (tempFile ? PAGER_EXCLUSIVE : PAGER_UNLOCK) );
+ /* pPager->errMask = 0; */
+ pPager->tempFile = tempFile;
+ assert( tempFile==PAGER_LOCKINGMODE_NORMAL
+ || tempFile==PAGER_LOCKINGMODE_EXCLUSIVE );
+ assert( PAGER_LOCKINGMODE_EXCLUSIVE==1 );
+ pPager->exclusiveMode = tempFile;
+ pPager->memDb = memDb;
+ pPager->readOnly = readOnly;
+ /* pPager->needSync = 0; */
+ pPager->noSync = pPager->tempFile || !useJournal;
+ pPager->fullSync = (pPager->noSync?0:1);
+ pPager->sync_flags = SQLITE_SYNC_NORMAL;
+ /* pPager->pFirst = 0; */
+ /* pPager->pFirstSynced = 0; */
+ /* pPager->pLast = 0; */
+ pPager->nExtra = FORCE_ALIGNMENT(nExtra);
+ assert(pPager->fd->pMethods||memDb||tempFile);
+ if( !memDb ){
+ setSectorSize(pPager);
+ }
+ /* pPager->pBusyHandler = 0; */
+ /* memset(pPager->aHash, 0, sizeof(pPager->aHash)); */
+ *ppPager = pPager;
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ pPager->iInUseMM = 0;
+ pPager->iInUseDB = 0;
+ if( !memDb ){
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM2);
+#endif
+ sqlite3_mutex_enter(mutex);
+ pPager->pNext = sqlite3PagerList;
+ if( sqlite3PagerList ){
+ assert( sqlite3PagerList->pPrev==0 );
+ sqlite3PagerList->pPrev = pPager;
+ }
+ pPager->pPrev = 0;
+ sqlite3PagerList = pPager;
+ sqlite3_mutex_leave(mutex);
+ }
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** Set the busy handler function.
+*/
+SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(Pager *pPager, BusyHandler *pBusyHandler){
+ pPager->pBusyHandler = pBusyHandler;
+}
+
+/*
+** Set the destructor for this pager. If not NULL, the destructor is called
+** when the reference count on each page reaches zero. The destructor can
+** be used to clean up information in the extra segment appended to each page.
+**
+** The destructor is not called as a result sqlite3PagerClose().
+** Destructors are only called by sqlite3PagerUnref().
+*/
+SQLITE_PRIVATE void sqlite3PagerSetDestructor(Pager *pPager, void (*xDesc)(DbPage*,int)){
+ pPager->xDestructor = xDesc;
+}
+
+/*
+** Set the reinitializer for this pager. If not NULL, the reinitializer
+** is called when the content of a page in cache is restored to its original
+** value as a result of a rollback. The callback gives higher-level code
+** an opportunity to restore the EXTRA section to agree with the restored
+** page data.
+*/
+SQLITE_PRIVATE void sqlite3PagerSetReiniter(Pager *pPager, void (*xReinit)(DbPage*,int)){
+ pPager->xReiniter = xReinit;
+}
+
+/*
+** Set the page size to *pPageSize. If the suggest new page size is
+** inappropriate, then an alternative page size is set to that
+** value before returning.
+*/
+SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u16 *pPageSize){
+ int rc = SQLITE_OK;
+ u16 pageSize = *pPageSize;
+ assert( pageSize==0 || (pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE) );
+ if( pageSize && pageSize!=pPager->pageSize
+ && !pPager->memDb && pPager->nRef==0
+ ){
+ char *pNew = (char *)sqlite3_malloc(pageSize);
+ if( !pNew ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pagerEnter(pPager);
+ pager_reset(pPager);
+ pPager->pageSize = pageSize;
+ setSectorSize(pPager);
+ sqlite3_free(pPager->pTmpSpace);
+ pPager->pTmpSpace = pNew;
+ pagerLeave(pPager);
+ }
+ }
+ *pPageSize = pPager->pageSize;
+ return rc;
+}
+
+/*
+** Return a pointer to the "temporary page" buffer held internally
+** by the pager. This is a buffer that is big enough to hold the
+** entire content of a database page. This buffer is used internally
+** during rollback and will be overwritten whenever a rollback
+** occurs. But other modules are free to use it too, as long as
+** no rollbacks are happening.
+*/
+SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager *pPager){
+ return pPager->pTmpSpace;
+}
+
+/*
+** Attempt to set the maximum database page count if mxPage is positive.
+** Make no changes if mxPage is zero or negative. And never reduce the
+** maximum page count below the current size of the database.
+**
+** Regardless of mxPage, return the current maximum page count.
+*/
+SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager *pPager, int mxPage){
+ if( mxPage>0 ){
+ pPager->mxPgno = mxPage;
+ }
+ sqlite3PagerPagecount(pPager);
+ return pPager->mxPgno;
+}
+
+/*
+** The following set of routines are used to disable the simulated
+** I/O error mechanism. These routines are used to avoid simulated
+** errors in places where we do not care about errors.
+**
+** Unless -DSQLITE_TEST=1 is used, these routines are all no-ops
+** and generate no code.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API extern int sqlite3_io_error_pending;
+SQLITE_API extern int sqlite3_io_error_hit;
+static int saved_cnt;
+void disable_simulated_io_errors(void){
+ saved_cnt = sqlite3_io_error_pending;
+ sqlite3_io_error_pending = -1;
+}
+void enable_simulated_io_errors(void){
+ sqlite3_io_error_pending = saved_cnt;
+}
+#else
+# define disable_simulated_io_errors()
+# define enable_simulated_io_errors()
+#endif
+
+/*
+** Read the first N bytes from the beginning of the file into memory
+** that pDest points to.
+**
+** No error checking is done. The rational for this is that this function
+** may be called even if the file does not exist or contain a header. In
+** these cases sqlite3OsRead() will return an error, to which the correct
+** response is to zero the memory at pDest and continue. A real IO error
+** will presumably recur and be picked up later (Todo: Think about this).
+*/
+SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned char *pDest){
+ int rc = SQLITE_OK;
+ memset(pDest, 0, N);
+ assert(MEMDB||pPager->fd->pMethods||pPager->tempFile);
+ if( pPager->fd->pMethods ){
+ IOTRACE(("DBHDR %p 0 %d\n", pPager, N))
+ rc = sqlite3OsRead(pPager->fd, pDest, N, 0);
+ if( rc==SQLITE_IOERR_SHORT_READ ){
+ rc = SQLITE_OK;
+ }
+ }
+ return rc;
+}
+
+/*
+** Return the total number of pages in the disk file associated with
+** pPager.
+**
+** If the PENDING_BYTE lies on the page directly after the end of the
+** file, then consider this page part of the file too. For example, if
+** PENDING_BYTE is byte 4096 (the first byte of page 5) and the size of the
+** file is 4096 bytes, 5 is returned instead of 4.
+*/
+SQLITE_PRIVATE int sqlite3PagerPagecount(Pager *pPager){
+ i64 n = 0;
+ int rc;
+ assert( pPager!=0 );
+ if( pPager->errCode ){
+ return -1;
+ }
+ if( pPager->dbSize>=0 ){
+ n = pPager->dbSize;
+ } else {
+ assert(pPager->fd->pMethods||pPager->tempFile);
+ if( (pPager->fd->pMethods)
+ && (rc = sqlite3OsFileSize(pPager->fd, &n))!=SQLITE_OK ){
+ pPager->nRef++;
+ pager_error(pPager, rc);
+ pPager->nRef--;
+ return -1;
+ }
+ if( n>0 && n<pPager->pageSize ){
+ n = 1;
+ }else{
+ n /= pPager->pageSize;
+ }
+ if( pPager->state!=PAGER_UNLOCK ){
+ pPager->dbSize = n;
+ }
+ }
+ if( n==(PENDING_BYTE/pPager->pageSize) ){
+ n++;
+ }
+ if( n>pPager->mxPgno ){
+ pPager->mxPgno = n;
+ }
+ return n;
+}
+
+
+#ifndef SQLITE_OMIT_MEMORYDB
+/*
+** Clear a PgHistory block
+*/
+static void clearHistory(PgHistory *pHist){
+ sqlite3_free(pHist->pOrig);
+ sqlite3_free(pHist->pStmt);
+ pHist->pOrig = 0;
+ pHist->pStmt = 0;
+}
+#else
+#define clearHistory(x)
+#endif
+
+/*
+** Forward declaration
+*/
+static int syncJournal(Pager*);
+
+/*
+** Unlink pPg from its hash chain. Also set the page number to 0 to indicate
+** that the page is not part of any hash chain. This is required because the
+** sqlite3PagerMovepage() routine can leave a page in the
+** pNextFree/pPrevFree list that is not a part of any hash-chain.
+*/
+static void unlinkHashChain(Pager *pPager, PgHdr *pPg){
+ if( pPg->pgno==0 ){
+ assert( pPg->pNextHash==0 && pPg->pPrevHash==0 );
+ return;
+ }
+ if( pPg->pNextHash ){
+ pPg->pNextHash->pPrevHash = pPg->pPrevHash;
+ }
+ if( pPg->pPrevHash ){
+ assert( pPager->aHash[pPg->pgno & (pPager->nHash-1)]!=pPg );
+ pPg->pPrevHash->pNextHash = pPg->pNextHash;
+ }else{
+ int h = pPg->pgno & (pPager->nHash-1);
+ pPager->aHash[h] = pPg->pNextHash;
+ }
+ if( MEMDB ){
+ clearHistory(PGHDR_TO_HIST(pPg, pPager));
+ }
+ pPg->pgno = 0;
+ pPg->pNextHash = pPg->pPrevHash = 0;
+}
+
+/*
+** Unlink a page from the free list (the list of all pages where nRef==0)
+** and from its hash collision chain.
+*/
+static void unlinkPage(PgHdr *pPg){
+ Pager *pPager = pPg->pPager;
+
+ /* Unlink from free page list */
+ lruListRemove(pPg);
+
+ /* Unlink from the pgno hash table */
+ unlinkHashChain(pPager, pPg);
+}
+
+/*
+** This routine is used to truncate the cache when a database
+** is truncated. Drop from the cache all pages whose pgno is
+** larger than pPager->dbSize and is unreferenced.
+**
+** Referenced pages larger than pPager->dbSize are zeroed.
+**
+** Actually, at the point this routine is called, it would be
+** an error to have a referenced page. But rather than delete
+** that page and guarantee a subsequent segfault, it seems better
+** to zero it and hope that we error out sanely.
+*/
+static void pager_truncate_cache(Pager *pPager){
+ PgHdr *pPg;
+ PgHdr **ppPg;
+ int dbSize = pPager->dbSize;
+
+ ppPg = &pPager->pAll;
+ while( (pPg = *ppPg)!=0 ){
+ if( pPg->pgno<=dbSize ){
+ ppPg = &pPg->pNextAll;
+ }else if( pPg->nRef>0 ){
+ memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize);
+ ppPg = &pPg->pNextAll;
+ }else{
+ *ppPg = pPg->pNextAll;
+ IOTRACE(("PGFREE %p %d\n", pPager, pPg->pgno));
+ PAGER_INCR(sqlite3_pager_pgfree_count);
+ unlinkPage(pPg);
+ makeClean(pPg);
+ sqlite3_free(pPg->pData);
+ sqlite3_free(pPg);
+ pPager->nPage--;
+ }
+ }
+}
+
+/*
+** Try to obtain a lock on a file. Invoke the busy callback if the lock
+** is currently not available. Repeat until the busy callback returns
+** false or until the lock succeeds.
+**
+** Return SQLITE_OK on success and an error code if we cannot obtain
+** the lock.
+*/
+static int pager_wait_on_lock(Pager *pPager, int locktype){
+ int rc;
+
+ /* The OS lock values must be the same as the Pager lock values */
+ assert( PAGER_SHARED==SHARED_LOCK );
+ assert( PAGER_RESERVED==RESERVED_LOCK );
+ assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK );
+
+ /* If the file is currently unlocked then the size must be unknown */
+ assert( pPager->state>=PAGER_SHARED || pPager->dbSize<0 || MEMDB );
+
+ if( pPager->state>=locktype ){
+ rc = SQLITE_OK;
+ }else{
+ if( pPager->pBusyHandler ) pPager->pBusyHandler->nBusy = 0;
+ do {
+ rc = sqlite3OsLock(pPager->fd, locktype);
+ }while( rc==SQLITE_BUSY && sqlite3InvokeBusyHandler(pPager->pBusyHandler) );
+ if( rc==SQLITE_OK ){
+ pPager->state = locktype;
+ IOTRACE(("LOCK %p %d\n", pPager, locktype))
+ }
+ }
+ return rc;
+}
+
+/*
+** Truncate the file to the number of pages specified.
+*/
+SQLITE_PRIVATE int sqlite3PagerTruncate(Pager *pPager, Pgno nPage){
+ int rc;
+ assert( pPager->state>=PAGER_SHARED || MEMDB );
+ sqlite3PagerPagecount(pPager);
+ if( pPager->errCode ){
+ rc = pPager->errCode;
+ return rc;
+ }
+ if( nPage>=(unsigned)pPager->dbSize ){
+ return SQLITE_OK;
+ }
+ if( MEMDB ){
+ pPager->dbSize = nPage;
+ pager_truncate_cache(pPager);
+ return SQLITE_OK;
+ }
+ pagerEnter(pPager);
+ rc = syncJournal(pPager);
+ pagerLeave(pPager);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ /* Get an exclusive lock on the database before truncating. */
+ pagerEnter(pPager);
+ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
+ pagerLeave(pPager);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ rc = pager_truncate(pPager, nPage);
+ return rc;
+}
+
+/*
+** Shutdown the page cache. Free all memory and close all files.
+**
+** If a transaction was in progress when this routine is called, that
+** transaction is rolled back. All outstanding pages are invalidated
+** and their memory is freed. Any attempt to use a page associated
+** with this page cache after this function returns will likely
+** result in a coredump.
+**
+** This function always succeeds. If a transaction is active an attempt
+** is made to roll it back. If an error occurs during the rollback
+** a hot journal may be left in the filesystem but no error is returned
+** to the caller.
+*/
+SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager){
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+ if( !MEMDB ){
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM2);
+#endif
+ sqlite3_mutex_enter(mutex);
+ if( pPager->pPrev ){
+ pPager->pPrev->pNext = pPager->pNext;
+ }else{
+ sqlite3PagerList = pPager->pNext;
+ }
+ if( pPager->pNext ){
+ pPager->pNext->pPrev = pPager->pPrev;
+ }
+ sqlite3_mutex_leave(mutex);
+ }
+#endif
+
+ disable_simulated_io_errors();
+ sqlite3FaultBeginBenign(-1);
+ pPager->errCode = 0;
+ pPager->exclusiveMode = 0;
+ pager_reset(pPager);
+ pagerUnlockAndRollback(pPager);
+ enable_simulated_io_errors();
+ sqlite3FaultEndBenign(-1);
+ PAGERTRACE2("CLOSE %d\n", PAGERID(pPager));
+ IOTRACE(("CLOSE %p\n", pPager))
+ if( pPager->journalOpen ){
+ sqlite3OsClose(pPager->jfd);
+ }
+ sqlite3BitvecDestroy(pPager->pInJournal);
+ if( pPager->stmtOpen ){
+ sqlite3OsClose(pPager->stfd);
+ }
+ sqlite3OsClose(pPager->fd);
+ /* Temp files are automatically deleted by the OS
+ ** if( pPager->tempFile ){
+ ** sqlite3OsDelete(pPager->zFilename);
+ ** }
+ */
+
+ sqlite3_free(pPager->aHash);
+ sqlite3_free(pPager->pTmpSpace);
+ sqlite3_free(pPager);
+ return SQLITE_OK;
+}
+
+#if !defined(NDEBUG) || defined(SQLITE_TEST)
+/*
+** Return the page number for the given page data.
+*/
+SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage *p){
+ return p->pgno;
+}
+#endif
+
+/*
+** The page_ref() function increments the reference count for a page.
+** If the page is currently on the freelist (the reference count is zero) then
+** remove it from the freelist.
+**
+** For non-test systems, page_ref() is a macro that calls _page_ref()
+** online of the reference count is zero. For test systems, page_ref()
+** is a real function so that we can set breakpoints and trace it.
+*/
+static void _page_ref(PgHdr *pPg){
+ if( pPg->nRef==0 ){
+ /* The page is currently on the freelist. Remove it. */
+ lruListRemove(pPg);
+ pPg->pPager->nRef++;
+ }
+ pPg->nRef++;
+}
+#ifdef SQLITE_DEBUG
+ static void page_ref(PgHdr *pPg){
+ if( pPg->nRef==0 ){
+ _page_ref(pPg);
+ }else{
+ pPg->nRef++;
+ }
+ }
+#else
+# define page_ref(P) ((P)->nRef==0?_page_ref(P):(void)(P)->nRef++)
+#endif
+
+/*
+** Increment the reference count for a page. The input pointer is
+** a reference to the page data.
+*/
+SQLITE_PRIVATE int sqlite3PagerRef(DbPage *pPg){
+ pagerEnter(pPg->pPager);
+ page_ref(pPg);
+ pagerLeave(pPg->pPager);
+ return SQLITE_OK;
+}
+
+/*
+** Sync the journal. In other words, make sure all the pages that have
+** been written to the journal have actually reached the surface of the
+** disk. It is not safe to modify the original database file until after
+** the journal has been synced. If the original database is modified before
+** the journal is synced and a power failure occurs, the unsynced journal
+** data would be lost and we would be unable to completely rollback the
+** database changes. Database corruption would occur.
+**
+** This routine also updates the nRec field in the header of the journal.
+** (See comments on the pager_playback() routine for additional information.)
+** If the sync mode is FULL, two syncs will occur. First the whole journal
+** is synced, then the nRec field is updated, then a second sync occurs.
+**
+** For temporary databases, we do not care if we are able to rollback
+** after a power failure, so no sync occurs.
+**
+** If the IOCAP_SEQUENTIAL flag is set for the persistent media on which
+** the database is stored, then OsSync() is never called on the journal
+** file. In this case all that is required is to update the nRec field in
+** the journal header.
+**
+** This routine clears the needSync field of every page current held in
+** memory.
+*/
+static int syncJournal(Pager *pPager){
+ PgHdr *pPg;
+ int rc = SQLITE_OK;
+
+
+ /* Sync the journal before modifying the main database
+ ** (assuming there is a journal and it needs to be synced.)
+ */
+ if( pPager->needSync ){
+ if( !pPager->tempFile ){
+ int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
+ assert( pPager->journalOpen );
+
+ if( 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){
+ /* Write the nRec value into the journal file header. If in
+ ** full-synchronous mode, sync the journal first. This ensures that
+ ** all data has really hit the disk before nRec is updated to mark
+ ** it as a candidate for rollback.
+ **
+ ** This is not required if the persistent media supports the
+ ** SAFE_APPEND property. Because in this case it is not possible
+ ** for garbage data to be appended to the file, the nRec field
+ ** is populated with 0xFFFFFFFF when the journal header is written
+ ** and never needs to be updated.
+ */
+ i64 jrnlOff;
+ if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){
+ PAGERTRACE2("SYNC journal of %d\n", PAGERID(pPager));
+ IOTRACE(("JSYNC %p\n", pPager))
+ rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags);
+ if( rc!=0 ) return rc;
+ }
+
+ jrnlOff = pPager->journalHdr + sizeof(aJournalMagic);
+ IOTRACE(("JHDR %p %lld %d\n", pPager, jrnlOff, 4));
+ rc = write32bits(pPager->jfd, jrnlOff, pPager->nRec);
+ if( rc ) return rc;
+ }
+ if( 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){
+ PAGERTRACE2("SYNC journal of %d\n", PAGERID(pPager));
+ IOTRACE(("JSYNC %p\n", pPager))
+ rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags|
+ (pPager->sync_flags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0)
+ );
+ if( rc!=0 ) return rc;
+ }
+ pPager->journalStarted = 1;
+ }
+ pPager->needSync = 0;
+
+ /* Erase the needSync flag from every page.
+ */
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ pPg->needSync = 0;
+ }
+ lruListSetFirstSynced(pPager);
+ }
+
+#ifndef NDEBUG
+ /* If the Pager.needSync flag is clear then the PgHdr.needSync
+ ** flag must also be clear for all pages. Verify that this
+ ** invariant is true.
+ */
+ else{
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ assert( pPg->needSync==0 );
+ }
+ assert( pPager->lru.pFirstSynced==pPager->lru.pFirst );
+ }
+#endif
+
+ return rc;
+}
+
+/*
+** Merge two lists of pages connected by pDirty and in pgno order.
+** Do not both fixing the pPrevDirty pointers.
+*/
+static PgHdr *merge_pagelist(PgHdr *pA, PgHdr *pB){
+ PgHdr result, *pTail;
+ pTail = &result;
+ while( pA && pB ){
+ if( pA->pgno<pB->pgno ){
+ pTail->pDirty = pA;
+ pTail = pA;
+ pA = pA->pDirty;
+ }else{
+ pTail->pDirty = pB;
+ pTail = pB;
+ pB = pB->pDirty;
+ }
+ }
+ if( pA ){
+ pTail->pDirty = pA;
+ }else if( pB ){
+ pTail->pDirty = pB;
+ }else{
+ pTail->pDirty = 0;
+ }
+ return result.pDirty;
+}
+
+/*
+** Sort the list of pages in accending order by pgno. Pages are
+** connected by pDirty pointers. The pPrevDirty pointers are
+** corrupted by this sort.
+*/
+#define N_SORT_BUCKET_ALLOC 25
+#define N_SORT_BUCKET 25
+#ifdef SQLITE_TEST
+ int sqlite3_pager_n_sort_bucket = 0;
+ #undef N_SORT_BUCKET
+ #define N_SORT_BUCKET \
+ (sqlite3_pager_n_sort_bucket?sqlite3_pager_n_sort_bucket:N_SORT_BUCKET_ALLOC)
+#endif
+static PgHdr *sort_pagelist(PgHdr *pIn){
+ PgHdr *a[N_SORT_BUCKET_ALLOC], *p;
+ int i;
+ memset(a, 0, sizeof(a));
+ while( pIn ){
+ p = pIn;
+ pIn = p->pDirty;
+ p->pDirty = 0;
+ for(i=0; i<N_SORT_BUCKET-1; i++){
+ if( a[i]==0 ){
+ a[i] = p;
+ break;
+ }else{
+ p = merge_pagelist(a[i], p);
+ a[i] = 0;
+ }
+ }
+ if( i==N_SORT_BUCKET-1 ){
+ /* Coverage: To get here, there need to be 2^(N_SORT_BUCKET)
+ ** elements in the input list. This is possible, but impractical.
+ ** Testing this line is the point of global variable
+ ** sqlite3_pager_n_sort_bucket.
+ */
+ a[i] = merge_pagelist(a[i], p);
+ }
+ }
+ p = a[0];
+ for(i=1; i<N_SORT_BUCKET; i++){
+ p = merge_pagelist(p, a[i]);
+ }
+ return p;
+}
+
+/*
+** Given a list of pages (connected by the PgHdr.pDirty pointer) write
+** every one of those pages out to the database file and mark them all
+** as clean.
+*/
+static int pager_write_pagelist(PgHdr *pList){
+ Pager *pPager;
+ PgHdr *p;
+ int rc;
+
+ if( pList==0 ) return SQLITE_OK;
+ pPager = pList->pPager;
+
+ /* At this point there may be either a RESERVED or EXCLUSIVE lock on the
+ ** database file. If there is already an EXCLUSIVE lock, the following
+ ** calls to sqlite3OsLock() are no-ops.
+ **
+ ** Moving the lock from RESERVED to EXCLUSIVE actually involves going
+ ** through an intermediate state PENDING. A PENDING lock prevents new
+ ** readers from attaching to the database but is unsufficient for us to
+ ** write. The idea of a PENDING lock is to prevent new readers from
+ ** coming in while we wait for existing readers to clear.
+ **
+ ** While the pager is in the RESERVED state, the original database file
+ ** is unchanged and we can rollback without having to playback the
+ ** journal into the original database file. Once we transition to
+ ** EXCLUSIVE, it means the database file has been changed and any rollback
+ ** will require a journal playback.
+ */
+ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ pList = sort_pagelist(pList);
+ for(p=pList; p; p=p->pDirty){
+ assert( p->dirty );
+ p->dirty = 0;
+ }
+ while( pList ){
+
+ /* If the file has not yet been opened, open it now. */
+ if( !pPager->fd->pMethods ){
+ assert(pPager->tempFile);
+ rc = sqlite3PagerOpentemp(pPager->pVfs, pPager->fd, pPager->zFilename,
+ pPager->vfsFlags);
+ if( rc ) return rc;
+ }
+
+ /* If there are dirty pages in the page cache with page numbers greater
+ ** than Pager.dbSize, this means sqlite3PagerTruncate() was called to
+ ** make the file smaller (presumably by auto-vacuum code). Do not write
+ ** any such pages to the file.
+ */
+ if( pList->pgno<=pPager->dbSize ){
+ i64 offset = (pList->pgno-1)*(i64)pPager->pageSize;
+ char *pData = CODEC2(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6);
+ PAGERTRACE4("STORE %d page %d hash(%08x)\n",
+ PAGERID(pPager), pList->pgno, pager_pagehash(pList));
+ IOTRACE(("PGOUT %p %d\n", pPager, pList->pgno));
+ rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset);
+ PAGER_INCR(sqlite3_pager_writedb_count);
+ PAGER_INCR(pPager->nWrite);
+ if( pList->pgno==1 ){
+ memcpy(&pPager->dbFileVers, &pData[24], sizeof(pPager->dbFileVers));
+ }
+ }
+#ifndef NDEBUG
+ else{
+ PAGERTRACE3("NOSTORE %d page %d\n", PAGERID(pPager), pList->pgno);
+ }
+#endif
+ if( rc ) return rc;
+#ifdef SQLITE_CHECK_PAGES
+ pList->pageHash = pager_pagehash(pList);
+#endif
+ pList = pList->pDirty;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Collect every dirty page into a dirty list and
+** return a pointer to the head of that list. All pages are
+** collected even if they are still in use.
+*/
+static PgHdr *pager_get_all_dirty_pages(Pager *pPager){
+
+#ifndef NDEBUG
+ /* Verify the sanity of the dirty list when we are running
+ ** in debugging mode. This is expensive, so do not
+ ** do this on a normal build. */
+ int n1 = 0;
+ int n2 = 0;
+ PgHdr *p;
+ for(p=pPager->pAll; p; p=p->pNextAll){ if( p->dirty ) n1++; }
+ for(p=pPager->pDirty; p; p=p->pDirty){ n2++; }
+ assert( n1==n2 );
+#endif
+
+ return pPager->pDirty;
+}
+
+/*
+** Return 1 if there is a hot journal on the given pager.
+** A hot journal is one that needs to be played back.
+**
+** If the current size of the database file is 0 but a journal file
+** exists, that is probably an old journal left over from a prior
+** database with the same name. Just delete the journal.
+**
+** Return negative if unable to determine the status of the journal.
+**
+** This routine does not open the journal file to examine its
+** content. Hence, the journal might contain the name of a master
+** journal file that has been deleted, and hence not be hot. Or
+** the header of the journal might be zeroed out. This routine
+** does not discover these cases of a non-hot journal - if the
+** journal file exists and is not empty this routine assumes it
+** is hot. The pager_playback() routine will discover that the
+** journal file is not really hot and will no-op.
+*/
+static int hasHotJournal(Pager *pPager){
+ sqlite3_vfs *pVfs = pPager->pVfs;
+ int rc;
+ if( !pPager->useJournal ) return 0;
+ if( !pPager->fd->pMethods ) return 0;
+ rc = sqlite3OsAccess(pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS);
+ if( rc<=0 ){
+ return rc;
+ }
+ if( sqlite3OsCheckReservedLock(pPager->fd) ){
+ return 0;
+ }
+ if( sqlite3PagerPagecount(pPager)==0 ){
+ sqlite3OsDelete(pVfs, pPager->zJournal, 0);
+ return 0;
+ }else{
+ return 1;
+ }
+}
+
+/*
+** Try to find a page in the cache that can be recycled.
+**
+** This routine may return SQLITE_IOERR, SQLITE_FULL or SQLITE_OK. It
+** does not set the pPager->errCode variable.
+*/
+static int pager_recycle(Pager *pPager, PgHdr **ppPg){
+ PgHdr *pPg;
+ *ppPg = 0;
+
+ /* It is illegal to call this function unless the pager object
+ ** pointed to by pPager has at least one free page (page with nRef==0).
+ */
+ assert(!MEMDB);
+ assert(pPager->lru.pFirst);
+
+ /* Find a page to recycle. Try to locate a page that does not
+ ** require us to do an fsync() on the journal.
+ */
+ pPg = pPager->lru.pFirstSynced;
+
+ /* If we could not find a page that does not require an fsync()
+ ** on the journal file then fsync the journal file. This is a
+ ** very slow operation, so we work hard to avoid it. But sometimes
+ ** it can't be helped.
+ */
+ if( pPg==0 && pPager->lru.pFirst){
+ int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
+ int rc = syncJournal(pPager);
+ if( rc!=0 ){
+ return rc;
+ }
+ if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){
+ /* If in full-sync mode, write a new journal header into the
+ ** journal file. This is done to avoid ever modifying a journal
+ ** header that is involved in the rollback of pages that have
+ ** already been written to the database (in case the header is
+ ** trashed when the nRec field is updated).
+ */
+ pPager->nRec = 0;
+ assert( pPager->journalOff > 0 );
+ assert( pPager->doNotSync==0 );
+ rc = writeJournalHdr(pPager);
+ if( rc!=0 ){
+ return rc;
+ }
+ }
+ pPg = pPager->lru.pFirst;
+ }
+
+ assert( pPg->nRef==0 );
+
+ /* Write the page to the database file if it is dirty.
+ */
+ if( pPg->dirty ){
+ int rc;
+ assert( pPg->needSync==0 );
+ makeClean(pPg);
+ pPg->dirty = 1;
+ pPg->pDirty = 0;
+ rc = pager_write_pagelist( pPg );
+ pPg->dirty = 0;
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ assert( pPg->dirty==0 );
+
+ /* If the page we are recycling is marked as alwaysRollback, then
+ ** set the global alwaysRollback flag, thus disabling the
+ ** sqlite3PagerDontRollback() optimization for the rest of this transaction.
+ ** It is necessary to do this because the page marked alwaysRollback
+ ** might be reloaded at a later time but at that point we won't remember
+ ** that is was marked alwaysRollback. This means that all pages must
+ ** be marked as alwaysRollback from here on out.
+ */
+ if( pPg->alwaysRollback ){
+ IOTRACE(("ALWAYS_ROLLBACK %p\n", pPager))
+ pPager->alwaysRollback = 1;
+ }
+
+ /* Unlink the old page from the free list and the hash table
+ */
+ unlinkPage(pPg);
+ assert( pPg->pgno==0 );
+
+ *ppPg = pPg;
+ return SQLITE_OK;
+}
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+/*
+** This function is called to free superfluous dynamically allocated memory
+** held by the pager system. Memory in use by any SQLite pager allocated
+** by the current thread may be sqlite3_free()ed.
+**
+** nReq is the number of bytes of memory required. Once this much has
+** been released, the function returns. The return value is the total number
+** of bytes of memory released.
+*/
+SQLITE_PRIVATE int sqlite3PagerReleaseMemory(int nReq){
+ int nReleased = 0; /* Bytes of memory released so far */
+ Pager *pPager; /* For looping over pagers */
+ BusyHandler *savedBusy; /* Saved copy of the busy handler */
+ int rc = SQLITE_OK;
+
+ /* Acquire the memory-management mutex
+ */
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex; /* The MEM2 mutex */
+ mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM2);
+#endif
+ sqlite3_mutex_enter(mutex);
+
+ /* Signal all database connections that memory management wants
+ ** to have access to the pagers.
+ */
+ for(pPager=sqlite3PagerList; pPager; pPager=pPager->pNext){
+ pPager->iInUseMM = 1;
+ }
+
+ while( rc==SQLITE_OK && (nReq<0 || nReleased<nReq) ){
+ PgHdr *pPg;
+ PgHdr *pRecycled;
+
+ /* Try to find a page to recycle that does not require a sync(). If
+ ** this is not possible, find one that does require a sync().
+ */
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+ pPg = sqlite3LruPageList.pFirstSynced;
+ while( pPg && (pPg->needSync || pPg->pPager->iInUseDB) ){
+ pPg = pPg->gfree.pNext;
+ }
+ if( !pPg ){
+ pPg = sqlite3LruPageList.pFirst;
+ while( pPg && pPg->pPager->iInUseDB ){
+ pPg = pPg->gfree.pNext;
+ }
+ }
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU));
+
+ /* If pPg==0, then the block above has failed to find a page to
+ ** recycle. In this case return early - no further memory will
+ ** be released.
+ */
+ if( !pPg ) break;
+
+ pPager = pPg->pPager;
+ assert(!pPg->needSync || pPg==pPager->lru.pFirst);
+ assert(pPg->needSync || pPg==pPager->lru.pFirstSynced);
+
+ savedBusy = pPager->pBusyHandler;
+ pPager->pBusyHandler = 0;
+ rc = pager_recycle(pPager, &pRecycled);
+ pPager->pBusyHandler = savedBusy;
+ assert(pRecycled==pPg || rc!=SQLITE_OK);
+ if( rc==SQLITE_OK ){
+ /* We've found a page to free. At this point the page has been
+ ** removed from the page hash-table, free-list and synced-list
+ ** (pFirstSynced). It is still in the all pages (pAll) list.
+ ** Remove it from this list before freeing.
+ **
+ ** Todo: Check the Pager.pStmt list to make sure this is Ok. It
+ ** probably is though.
+ */
+ PgHdr *pTmp;
+ assert( pPg );
+ if( pPg==pPager->pAll ){
+ pPager->pAll = pPg->pNextAll;
+ }else{
+ for( pTmp=pPager->pAll; pTmp->pNextAll!=pPg; pTmp=pTmp->pNextAll ){}
+ pTmp->pNextAll = pPg->pNextAll;
+ }
+ nReleased += (
+ sizeof(*pPg) + pPager->pageSize
+ + sizeof(u32) + pPager->nExtra
+ + MEMDB*sizeof(PgHistory)
+ );
+ IOTRACE(("PGFREE %p %d *\n", pPager, pPg->pgno));
+ PAGER_INCR(sqlite3_pager_pgfree_count);
+ sqlite3_free(pPg->pData);
+ sqlite3_free(pPg);
+ pPager->nPage--;
+ }else{
+ /* An error occured whilst writing to the database file or
+ ** journal in pager_recycle(). The error is not returned to the
+ ** caller of this function. Instead, set the Pager.errCode variable.
+ ** The error will be returned to the user (or users, in the case
+ ** of a shared pager cache) of the pager for which the error occured.
+ */
+ assert(
+ (rc&0xff)==SQLITE_IOERR ||
+ rc==SQLITE_FULL ||
+ rc==SQLITE_BUSY
+ );
+ assert( pPager->state>=PAGER_RESERVED );
+ pager_error(pPager, rc);
+ }
+ }
+
+ /* Clear the memory management flags and release the mutex
+ */
+ for(pPager=sqlite3PagerList; pPager; pPager=pPager->pNext){
+ pPager->iInUseMM = 0;
+ }
+ sqlite3_mutex_leave(mutex);
+
+ /* Return the number of bytes released
+ */
+ return nReleased;
+}
+#endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */
+
+/*
+** Read the content of page pPg out of the database file.
+*/
+static int readDbPage(Pager *pPager, PgHdr *pPg, Pgno pgno){
+ int rc;
+ i64 offset;
+ assert( MEMDB==0 );
+ assert(pPager->fd->pMethods||pPager->tempFile);
+ if( !pPager->fd->pMethods ){
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ offset = (pgno-1)*(i64)pPager->pageSize;
+ rc = sqlite3OsRead(pPager->fd, PGHDR_TO_DATA(pPg), pPager->pageSize, offset);
+ PAGER_INCR(sqlite3_pager_readdb_count);
+ PAGER_INCR(pPager->nRead);
+ IOTRACE(("PGIN %p %d\n", pPager, pgno));
+ if( pgno==1 ){
+ memcpy(&pPager->dbFileVers, &((u8*)PGHDR_TO_DATA(pPg))[24],
+ sizeof(pPager->dbFileVers));
+ }
+ CODEC1(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3);
+ PAGERTRACE4("FETCH %d page %d hash(%08x)\n",
+ PAGERID(pPager), pPg->pgno, pager_pagehash(pPg));
+ return rc;
+}
+
+
+/*
+** This function is called to obtain the shared lock required before
+** data may be read from the pager cache. If the shared lock has already
+** been obtained, this function is a no-op.
+**
+** Immediately after obtaining the shared lock (if required), this function
+** checks for a hot-journal file. If one is found, an emergency rollback
+** is performed immediately.
+*/
+static int pagerSharedLock(Pager *pPager){
+ int rc = SQLITE_OK;
+ int isHot = 0;
+
+ /* If this database is opened for exclusive access, has no outstanding
+ ** page references and is in an error-state, now is the chance to clear
+ ** the error. Discard the contents of the pager-cache and treat any
+ ** open journal file as a hot-journal.
+ */
+ if( !MEMDB && pPager->exclusiveMode && pPager->nRef==0 && pPager->errCode ){
+ if( pPager->journalOpen ){
+ isHot = 1;
+ }
+ pPager->errCode = SQLITE_OK;
+ pager_reset(pPager);
+ }
+
+ /* If the pager is still in an error state, do not proceed. The error
+ ** state will be cleared at some point in the future when all page
+ ** references are dropped and the cache can be discarded.
+ */
+ if( pPager->errCode && pPager->errCode!=SQLITE_FULL ){
+ return pPager->errCode;
+ }
+
+ if( pPager->state==PAGER_UNLOCK || isHot ){
+ sqlite3_vfs *pVfs = pPager->pVfs;
+ if( !MEMDB ){
+ assert( pPager->nRef==0 );
+ if( !pPager->noReadlock ){
+ rc = pager_wait_on_lock(pPager, SHARED_LOCK);
+ if( rc!=SQLITE_OK ){
+ assert( pPager->state==PAGER_UNLOCK );
+ return pager_error(pPager, rc);
+ }
+ assert( pPager->state>=SHARED_LOCK );
+ }
+
+ /* If a journal file exists, and there is no RESERVED lock on the
+ ** database file, then it either needs to be played back or deleted.
+ */
+ rc = hasHotJournal(pPager);
+ if( rc<0 ){
+ rc = SQLITE_IOERR_NOMEM;
+ goto failed;
+ }
+ if( rc==1 || isHot ){
+ /* Get an EXCLUSIVE lock on the database file. At this point it is
+ ** important that a RESERVED lock is not obtained on the way to the
+ ** EXCLUSIVE lock. If it were, another process might open the
+ ** database file, detect the RESERVED lock, and conclude that the
+ ** database is safe to read while this process is still rolling it
+ ** back.
+ **
+ ** Because the intermediate RESERVED lock is not requested, the
+ ** second process will get to this point in the code and fail to
+ ** obtain its own EXCLUSIVE lock on the database file.
+ */
+ if( pPager->state<EXCLUSIVE_LOCK ){
+ rc = sqlite3OsLock(pPager->fd, EXCLUSIVE_LOCK);
+ if( rc!=SQLITE_OK ){
+ rc = pager_error(pPager, rc);
+ goto failed;
+ }
+ pPager->state = PAGER_EXCLUSIVE;
+ }
+
+ /* Open the journal for read/write access. This is because in
+ ** exclusive-access mode the file descriptor will be kept open and
+ ** possibly used for a transaction later on. On some systems, the
+ ** OsTruncate() call used in exclusive-access mode also requires
+ ** a read/write file handle.
+ */
+ if( !isHot && pPager->journalOpen==0 ){
+ int res = sqlite3OsAccess(pVfs,pPager->zJournal,SQLITE_ACCESS_EXISTS);
+ if( res==1 ){
+ int fout = 0;
+ int f = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_JOURNAL;
+ assert( !pPager->tempFile );
+ rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &fout);
+ assert( rc!=SQLITE_OK || pPager->jfd->pMethods );
+ if( fout&SQLITE_OPEN_READONLY ){
+ rc = SQLITE_BUSY;
+ sqlite3OsClose(pPager->jfd);
+ }
+ }else if( res==0 ){
+ /* If the journal does not exist, that means some other process
+ ** has already rolled it back */
+ rc = SQLITE_BUSY;
+ }else{
+ /* If sqlite3OsAccess() returns a negative value, that means it
+ ** failed a memory allocation */
+ rc = SQLITE_IOERR_NOMEM;
+ }
+ }
+ if( rc!=SQLITE_OK ){
+ if( rc!=SQLITE_NOMEM && rc!=SQLITE_IOERR_UNLOCK
+ && rc!=SQLITE_IOERR_NOMEM
+ ){
+ rc = SQLITE_BUSY;
+ }
+ goto failed;
+ }
+ pPager->journalOpen = 1;
+ pPager->journalStarted = 0;
+ pPager->journalOff = 0;
+ pPager->setMaster = 0;
+ pPager->journalHdr = 0;
+
+ /* Playback and delete the journal. Drop the database write
+ ** lock and reacquire the read lock.
+ */
+ rc = pager_playback(pPager, 1);
+ if( rc!=SQLITE_OK ){
+ rc = pager_error(pPager, rc);
+ goto failed;
+ }
+ assert(pPager->state==PAGER_SHARED ||
+ (pPager->exclusiveMode && pPager->state>PAGER_SHARED)
+ );
+ }
+
+ if( pPager->pAll ){
+ /* The shared-lock has just been acquired on the database file
+ ** and there are already pages in the cache (from a previous
+ ** read or write transaction). Check to see if the database
+ ** has been modified. If the database has changed, flush the
+ ** cache.
+ **
+ ** Database changes is detected by looking at 15 bytes beginning
+ ** at offset 24 into the file. The first 4 of these 16 bytes are
+ ** a 32-bit counter that is incremented with each change. The
+ ** other bytes change randomly with each file change when
+ ** a codec is in use.
+ **
+ ** There is a vanishingly small chance that a change will not be
+ ** detected. The chance of an undetected change is so small that
+ ** it can be neglected.
+ */
+ char dbFileVers[sizeof(pPager->dbFileVers)];
+ sqlite3PagerPagecount(pPager);
+
+ if( pPager->errCode ){
+ rc = pPager->errCode;
+ goto failed;
+ }
+
+ if( pPager->dbSize>0 ){
+ IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers)));
+ rc = sqlite3OsRead(pPager->fd, &dbFileVers, sizeof(dbFileVers), 24);
+ if( rc!=SQLITE_OK ){
+ goto failed;
+ }
+ }else{
+ memset(dbFileVers, 0, sizeof(dbFileVers));
+ }
+
+ if( memcmp(pPager->dbFileVers, dbFileVers, sizeof(dbFileVers))!=0 ){
+ pager_reset(pPager);
+ }
+ }
+ }
+ assert( pPager->exclusiveMode || pPager->state<=PAGER_SHARED );
+ if( pPager->state==PAGER_UNLOCK ){
+ pPager->state = PAGER_SHARED;
+ }
+ }
+
+ failed:
+ if( rc!=SQLITE_OK ){
+ /* pager_unlock() is a no-op for exclusive mode and in-memory databases. */
+ pager_unlock(pPager);
+ }
+ return rc;
+}
+
+/*
+** Allocate a PgHdr object. Either create a new one or reuse
+** an existing one that is not otherwise in use.
+**
+** A new PgHdr structure is created if any of the following are
+** true:
+**
+** (1) We have not exceeded our maximum allocated cache size
+** as set by the "PRAGMA cache_size" command.
+**
+** (2) There are no unused PgHdr objects available at this time.
+**
+** (3) This is an in-memory database.
+**
+** (4) There are no PgHdr objects that do not require a journal
+** file sync and a sync of the journal file is currently
+** prohibited.
+**
+** Otherwise, reuse an existing PgHdr. In other words, reuse an
+** existing PgHdr if all of the following are true:
+**
+** (1) We have reached or exceeded the maximum cache size
+** allowed by "PRAGMA cache_size".
+**
+** (2) There is a PgHdr available with PgHdr->nRef==0
+**
+** (3) We are not in an in-memory database
+**
+** (4) Either there is an available PgHdr that does not need
+** to be synced to disk or else disk syncing is currently
+** allowed.
+*/
+static int pagerAllocatePage(Pager *pPager, PgHdr **ppPg){
+ int rc = SQLITE_OK;
+ PgHdr *pPg;
+ int nByteHdr;
+
+ /* Create a new PgHdr if any of the four conditions defined
+ ** above are met: */
+ if( pPager->nPage<pPager->mxPage
+ || pPager->lru.pFirst==0
+ || MEMDB
+ || (pPager->lru.pFirstSynced==0 && pPager->doNotSync)
+ ){
+ void *pData;
+ if( pPager->nPage>=pPager->nHash ){
+ pager_resize_hash_table(pPager,
+ pPager->nHash<256 ? 256 : pPager->nHash*2);
+ if( pPager->nHash==0 ){
+ rc = SQLITE_NOMEM;
+ goto pager_allocate_out;
+ }
+ }
+ pagerLeave(pPager);
+ nByteHdr = sizeof(*pPg) + sizeof(u32) + pPager->nExtra
+ + MEMDB*sizeof(PgHistory);
+ pPg = sqlite3_malloc( nByteHdr );
+ if( pPg ){
+ pData = sqlite3_malloc( pPager->pageSize );
+ if( pData==0 ){
+ sqlite3_free(pPg);
+ pPg = 0;
+ }
+ }
+ pagerEnter(pPager);
+ if( pPg==0 ){
+ rc = SQLITE_NOMEM;
+ goto pager_allocate_out;
+ }
+ memset(pPg, 0, nByteHdr);
+ pPg->pData = pData;
+ pPg->pPager = pPager;
+ pPg->pNextAll = pPager->pAll;
+ pPager->pAll = pPg;
+ pPager->nPage++;
+ }else{
+ /* Recycle an existing page with a zero ref-count. */
+ rc = pager_recycle(pPager, &pPg);
+ if( rc==SQLITE_BUSY ){
+ rc = SQLITE_IOERR_BLOCKED;
+ }
+ if( rc!=SQLITE_OK ){
+ goto pager_allocate_out;
+ }
+ assert( pPager->state>=SHARED_LOCK );
+ assert(pPg);
+ }
+ *ppPg = pPg;
+
+pager_allocate_out:
+ return rc;
+}
+
+/*
+** Make sure we have the content for a page. If the page was
+** previously acquired with noContent==1, then the content was
+** just initialized to zeros instead of being read from disk.
+** But now we need the real data off of disk. So make sure we
+** have it. Read it in if we do not have it already.
+*/
+static int pager_get_content(PgHdr *pPg){
+ if( pPg->needRead ){
+ int rc = readDbPage(pPg->pPager, pPg, pPg->pgno);
+ if( rc==SQLITE_OK ){
+ pPg->needRead = 0;
+ }else{
+ return rc;
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Acquire a page.
+**
+** A read lock on the disk file is obtained when the first page is acquired.
+** This read lock is dropped when the last page is released.
+**
+** This routine works for any page number greater than 0. If the database
+** file is smaller than the requested page, then no actual disk
+** read occurs and the memory image of the page is initialized to
+** all zeros. The extra data appended to a page is always initialized
+** to zeros the first time a page is loaded into memory.
+**
+** The acquisition might fail for several reasons. In all cases,
+** an appropriate error code is returned and *ppPage is set to NULL.
+**
+** See also sqlite3PagerLookup(). Both this routine and Lookup() attempt
+** to find a page in the in-memory cache first. If the page is not already
+** in memory, this routine goes to disk to read it in whereas Lookup()
+** just returns 0. This routine acquires a read-lock the first time it
+** has to go to disk, and could also playback an old journal if necessary.
+** Since Lookup() never goes to disk, it never has to deal with locks
+** or journal files.
+**
+** If noContent is false, the page contents are actually read from disk.
+** If noContent is true, it means that we do not care about the contents
+** of the page at this time, so do not do a disk read. Just fill in the
+** page content with zeros. But mark the fact that we have not read the
+** content by setting the PgHdr.needRead flag. Later on, if
+** sqlite3PagerWrite() is called on this page or if this routine is
+** called again with noContent==0, that means that the content is needed
+** and the disk read should occur at that point.
+*/
+static int pagerAcquire(
+ Pager *pPager, /* The pager open on the database file */
+ Pgno pgno, /* Page number to fetch */
+ DbPage **ppPage, /* Write a pointer to the page here */
+ int noContent /* Do not bother reading content from disk if true */
+){
+ PgHdr *pPg;
+ int rc;
+
+ assert( pPager->state==PAGER_UNLOCK || pPager->nRef>0 || pgno==1 );
+
+ /* The maximum page number is 2^31. Return SQLITE_CORRUPT if a page
+ ** number greater than this, or zero, is requested.
+ */
+ if( pgno>PAGER_MAX_PGNO || pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ /* Make sure we have not hit any critical errors.
+ */
+ assert( pPager!=0 );
+ *ppPage = 0;
+
+ /* If this is the first page accessed, then get a SHARED lock
+ ** on the database file. pagerSharedLock() is a no-op if
+ ** a database lock is already held.
+ */
+ rc = pagerSharedLock(pPager);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( pPager->state!=PAGER_UNLOCK );
+
+ pPg = pager_lookup(pPager, pgno);
+ if( pPg==0 ){
+ /* The requested page is not in the page cache. */
+ int nMax;
+ int h;
+ PAGER_INCR(pPager->nMiss);
+ rc = pagerAllocatePage(pPager, &pPg);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ pPg->pgno = pgno;
+ assert( !MEMDB || pgno>pPager->stmtSize );
+ pPg->inJournal = sqlite3BitvecTest(pPager->pInJournal, pgno);
+ pPg->needSync = 0;
+
+ makeClean(pPg);
+ pPg->nRef = 1;
+
+ pPager->nRef++;
+ if( pPager->nExtra>0 ){
+ memset(PGHDR_TO_EXTRA(pPg, pPager), 0, pPager->nExtra);
+ }
+ nMax = sqlite3PagerPagecount(pPager);
+ if( pPager->errCode ){
+ rc = pPager->errCode;
+ sqlite3PagerUnref(pPg);
+ return rc;
+ }
+
+ /* Populate the page with data, either by reading from the database
+ ** file, or by setting the entire page to zero.
+ */
+ if( nMax<(int)pgno || MEMDB || (noContent && !pPager->alwaysRollback) ){
+ if( pgno>pPager->mxPgno ){
+ sqlite3PagerUnref(pPg);
+ return SQLITE_FULL;
+ }
+ memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize);
+ pPg->needRead = noContent && !pPager->alwaysRollback;
+ IOTRACE(("ZERO %p %d\n", pPager, pgno));
+ }else{
+ rc = readDbPage(pPager, pPg, pgno);
+ if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){
+ pPg->pgno = 0;
+ sqlite3PagerUnref(pPg);
+ return rc;
+ }
+ pPg->needRead = 0;
+ }
+
+ /* Link the page into the page hash table */
+ h = pgno & (pPager->nHash-1);
+ assert( pgno!=0 );
+ pPg->pNextHash = pPager->aHash[h];
+ pPager->aHash[h] = pPg;
+ if( pPg->pNextHash ){
+ assert( pPg->pNextHash->pPrevHash==0 );
+ pPg->pNextHash->pPrevHash = pPg;
+ }
+
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ }else{
+ /* The requested page is in the page cache. */
+ assert(pPager->nRef>0 || pgno==1);
+ PAGER_INCR(pPager->nHit);
+ if( !noContent ){
+ rc = pager_get_content(pPg);
+ if( rc ){
+ return rc;
+ }
+ }
+ page_ref(pPg);
+ }
+ *ppPage = pPg;
+ return SQLITE_OK;
+}
+SQLITE_PRIVATE int sqlite3PagerAcquire(
+ Pager *pPager, /* The pager open on the database file */
+ Pgno pgno, /* Page number to fetch */
+ DbPage **ppPage, /* Write a pointer to the page here */
+ int noContent /* Do not bother reading content from disk if true */
+){
+ int rc;
+ pagerEnter(pPager);
+ rc = pagerAcquire(pPager, pgno, ppPage, noContent);
+ pagerLeave(pPager);
+ return rc;
+}
+
+
+/*
+** Acquire a page if it is already in the in-memory cache. Do
+** not read the page from disk. Return a pointer to the page,
+** or 0 if the page is not in cache.
+**
+** See also sqlite3PagerGet(). The difference between this routine
+** and sqlite3PagerGet() is that _get() will go to the disk and read
+** in the page if the page is not already in cache. This routine
+** returns NULL if the page is not in cache or if a disk I/O error
+** has ever happened.
+*/
+SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){
+ PgHdr *pPg = 0;
+
+ assert( pPager!=0 );
+ assert( pgno!=0 );
+
+ pagerEnter(pPager);
+ if( pPager->state==PAGER_UNLOCK ){
+ assert( !pPager->pAll || pPager->exclusiveMode );
+ }else if( pPager->errCode && pPager->errCode!=SQLITE_FULL ){
+ /* Do nothing */
+ }else if( (pPg = pager_lookup(pPager, pgno))!=0 ){
+ page_ref(pPg);
+ }
+ pagerLeave(pPager);
+ return pPg;
+}
+
+/*
+** Release a page.
+**
+** If the number of references to the page drop to zero, then the
+** page is added to the LRU list. When all references to all pages
+** are released, a rollback occurs and the lock on the database is
+** removed.
+*/
+SQLITE_PRIVATE int sqlite3PagerUnref(DbPage *pPg){
+ Pager *pPager;
+
+ if( pPg==0 ) return SQLITE_OK;
+ pPager = pPg->pPager;
+
+ /* Decrement the reference count for this page
+ */
+ assert( pPg->nRef>0 );
+ pagerEnter(pPg->pPager);
+ pPg->nRef--;
+
+ CHECK_PAGE(pPg);
+
+ /* When the number of references to a page reach 0, call the
+ ** destructor and add the page to the freelist.
+ */
+ if( pPg->nRef==0 ){
+
+ lruListAdd(pPg);
+ if( pPager->xDestructor ){
+ pPager->xDestructor(pPg, pPager->pageSize);
+ }
+
+ /* When all pages reach the freelist, drop the read lock from
+ ** the database file.
+ */
+ pPager->nRef--;
+ assert( pPager->nRef>=0 );
+ if( pPager->nRef==0 && (!pPager->exclusiveMode || pPager->journalOff>0) ){
+ pagerUnlockAndRollback(pPager);
+ }
+ }
+ pagerLeave(pPager);
+ return SQLITE_OK;
+}
+
+/*
+** Create a journal file for pPager. There should already be a RESERVED
+** or EXCLUSIVE lock on the database file when this routine is called.
+**
+** Return SQLITE_OK if everything. Return an error code and release the
+** write lock if anything goes wrong.
+*/
+static int pager_open_journal(Pager *pPager){
+ sqlite3_vfs *pVfs = pPager->pVfs;
+ int flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_EXCLUSIVE|SQLITE_OPEN_CREATE);
+
+ int rc;
+ assert( !MEMDB );
+ assert( pPager->state>=PAGER_RESERVED );
+ assert( pPager->useJournal );
+ assert( pPager->pInJournal==0 );
+ sqlite3PagerPagecount(pPager);
+ pagerLeave(pPager);
+ pPager->pInJournal = sqlite3BitvecCreate(pPager->dbSize);
+ pagerEnter(pPager);
+ if( pPager->pInJournal==0 ){
+ rc = SQLITE_NOMEM;
+ goto failed_to_open_journal;
+ }
+
+ if( pPager->journalOpen==0 ){
+ if( pPager->tempFile ){
+ flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL);
+ }else{
+ flags |= (SQLITE_OPEN_MAIN_JOURNAL);
+ }
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+ rc = sqlite3JournalOpen(
+ pVfs, pPager->zJournal, pPager->jfd, flags, jrnlBufferSize(pPager)
+ );
+#else
+ rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0);
+#endif
+ assert( rc!=SQLITE_OK || pPager->jfd->pMethods );
+ pPager->journalOff = 0;
+ pPager->setMaster = 0;
+ pPager->journalHdr = 0;
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_NOMEM ){
+ sqlite3OsDelete(pVfs, pPager->zJournal, 0);
+ }
+ goto failed_to_open_journal;
+ }
+ }
+ pPager->journalOpen = 1;
+ pPager->journalStarted = 0;
+ pPager->needSync = 0;
+ pPager->alwaysRollback = 0;
+ pPager->nRec = 0;
+ if( pPager->errCode ){
+ rc = pPager->errCode;
+ goto failed_to_open_journal;
+ }
+ pPager->origDbSize = pPager->dbSize;
+
+ rc = writeJournalHdr(pPager);
+
+ if( pPager->stmtAutoopen && rc==SQLITE_OK ){
+ rc = sqlite3PagerStmtBegin(pPager);
+ }
+ if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM && rc!=SQLITE_IOERR_NOMEM ){
+ rc = pager_end_transaction(pPager, 0);
+ if( rc==SQLITE_OK ){
+ rc = SQLITE_FULL;
+ }
+ }
+ return rc;
+
+failed_to_open_journal:
+ sqlite3BitvecDestroy(pPager->pInJournal);
+ pPager->pInJournal = 0;
+ return rc;
+}
+
+/*
+** Acquire a write-lock on the database. The lock is removed when
+** the any of the following happen:
+**
+** * sqlite3PagerCommitPhaseTwo() is called.
+** * sqlite3PagerRollback() is called.
+** * sqlite3PagerClose() is called.
+** * sqlite3PagerUnref() is called to on every outstanding page.
+**
+** The first parameter to this routine is a pointer to any open page of the
+** database file. Nothing changes about the page - it is used merely to
+** acquire a pointer to the Pager structure and as proof that there is
+** already a read-lock on the database.
+**
+** The second parameter indicates how much space in bytes to reserve for a
+** master journal file-name at the start of the journal when it is created.
+**
+** A journal file is opened if this is not a temporary file. For temporary
+** files, the opening of the journal file is deferred until there is an
+** actual need to write to the journal.
+**
+** If the database is already reserved for writing, this routine is a no-op.
+**
+** If exFlag is true, go ahead and get an EXCLUSIVE lock on the file
+** immediately instead of waiting until we try to flush the cache. The
+** exFlag is ignored if a transaction is already active.
+*/
+SQLITE_PRIVATE int sqlite3PagerBegin(DbPage *pPg, int exFlag){
+ Pager *pPager = pPg->pPager;
+ int rc = SQLITE_OK;
+ pagerEnter(pPager);
+ assert( pPg->nRef>0 );
+ assert( pPager->state!=PAGER_UNLOCK );
+ if( pPager->state==PAGER_SHARED ){
+ assert( pPager->pInJournal==0 );
+ if( MEMDB ){
+ pPager->state = PAGER_EXCLUSIVE;
+ pPager->origDbSize = pPager->dbSize;
+ }else{
+ rc = sqlite3OsLock(pPager->fd, RESERVED_LOCK);
+ if( rc==SQLITE_OK ){
+ pPager->state = PAGER_RESERVED;
+ if( exFlag ){
+ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
+ }
+ }
+ if( rc!=SQLITE_OK ){
+ pagerLeave(pPager);
+ return rc;
+ }
+ pPager->dirtyCache = 0;
+ PAGERTRACE2("TRANSACTION %d\n", PAGERID(pPager));
+ if( pPager->useJournal && !pPager->tempFile
+ && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){
+ rc = pager_open_journal(pPager);
+ }
+ }
+ }else if( pPager->journalOpen && pPager->journalOff==0 ){
+ /* This happens when the pager was in exclusive-access mode the last
+ ** time a (read or write) transaction was successfully concluded
+ ** by this connection. Instead of deleting the journal file it was
+ ** kept open and either was truncated to 0 bytes or its header was
+ ** overwritten with zeros.
+ */
+ assert( pPager->nRec==0 );
+ assert( pPager->origDbSize==0 );
+ assert( pPager->pInJournal==0 );
+ sqlite3PagerPagecount(pPager);
+ pagerLeave(pPager);
+ pPager->pInJournal = sqlite3BitvecCreate( pPager->dbSize );
+ pagerEnter(pPager);
+ if( !pPager->pInJournal ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pPager->origDbSize = pPager->dbSize;
+ rc = writeJournalHdr(pPager);
+ }
+ }
+ assert( !pPager->journalOpen || pPager->journalOff>0 || rc!=SQLITE_OK );
+ pagerLeave(pPager);
+ return rc;
+}
+
+/*
+** Make a page dirty. Set its dirty flag and add it to the dirty
+** page list.
+*/
+static void makeDirty(PgHdr *pPg){
+ if( pPg->dirty==0 ){
+ Pager *pPager = pPg->pPager;
+ pPg->dirty = 1;
+ pPg->pDirty = pPager->pDirty;
+ if( pPager->pDirty ){
+ pPager->pDirty->pPrevDirty = pPg;
+ }
+ pPg->pPrevDirty = 0;
+ pPager->pDirty = pPg;
+ }
+}
+
+/*
+** Make a page clean. Clear its dirty bit and remove it from the
+** dirty page list.
+*/
+static void makeClean(PgHdr *pPg){
+ if( pPg->dirty ){
+ pPg->dirty = 0;
+ if( pPg->pDirty ){
+ assert( pPg->pDirty->pPrevDirty==pPg );
+ pPg->pDirty->pPrevDirty = pPg->pPrevDirty;
+ }
+ if( pPg->pPrevDirty ){
+ assert( pPg->pPrevDirty->pDirty==pPg );
+ pPg->pPrevDirty->pDirty = pPg->pDirty;
+ }else{
+ assert( pPg->pPager->pDirty==pPg );
+ pPg->pPager->pDirty = pPg->pDirty;
+ }
+ }
+}
+
+
+/*
+** Mark a data page as writeable. The page is written into the journal
+** if it is not there already. This routine must be called before making
+** changes to a page.
+**
+** The first time this routine is called, the pager creates a new
+** journal and acquires a RESERVED lock on the database. If the RESERVED
+** lock could not be acquired, this routine returns SQLITE_BUSY. The
+** calling routine must check for that return value and be careful not to
+** change any page data until this routine returns SQLITE_OK.
+**
+** If the journal file could not be written because the disk is full,
+** then this routine returns SQLITE_FULL and does an immediate rollback.
+** All subsequent write attempts also return SQLITE_FULL until there
+** is a call to sqlite3PagerCommit() or sqlite3PagerRollback() to
+** reset.
+*/
+static int pager_write(PgHdr *pPg){
+ void *pData = PGHDR_TO_DATA(pPg);
+ Pager *pPager = pPg->pPager;
+ int rc = SQLITE_OK;
+
+ /* Check for errors
+ */
+ if( pPager->errCode ){
+ return pPager->errCode;
+ }
+ if( pPager->readOnly ){
+ return SQLITE_PERM;
+ }
+
+ assert( !pPager->setMaster );
+
+ CHECK_PAGE(pPg);
+
+ /* If this page was previously acquired with noContent==1, that means
+ ** we didn't really read in the content of the page. This can happen
+ ** (for example) when the page is being moved to the freelist. But
+ ** now we are (perhaps) moving the page off of the freelist for
+ ** reuse and we need to know its original content so that content
+ ** can be stored in the rollback journal. So do the read at this
+ ** time.
+ */
+ rc = pager_get_content(pPg);
+ if( rc ){
+ return rc;
+ }
+
+ /* Mark the page as dirty. If the page has already been written
+ ** to the journal then we can return right away.
+ */
+ makeDirty(pPg);
+ if( pPg->inJournal && (pageInStatement(pPg) || pPager->stmtInUse==0) ){
+ pPager->dirtyCache = 1;
+ pPager->dbModified = 1;
+ }else{
+
+ /* If we get this far, it means that the page needs to be
+ ** written to the transaction journal or the ckeckpoint journal
+ ** or both.
+ **
+ ** First check to see that the transaction journal exists and
+ ** create it if it does not.
+ */
+ assert( pPager->state!=PAGER_UNLOCK );
+ rc = sqlite3PagerBegin(pPg, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( pPager->state>=PAGER_RESERVED );
+ if( !pPager->journalOpen && pPager->useJournal
+ && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){
+ rc = pager_open_journal(pPager);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ pPager->dirtyCache = 1;
+ pPager->dbModified = 1;
+
+ /* The transaction journal now exists and we have a RESERVED or an
+ ** EXCLUSIVE lock on the main database file. Write the current page to
+ ** the transaction journal if it is not there already.
+ */
+ if( !pPg->inJournal && (pPager->journalOpen || MEMDB) ){
+ if( (int)pPg->pgno <= pPager->origDbSize ){
+ if( MEMDB ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ PAGERTRACE3("JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno);
+ assert( pHist->pOrig==0 );
+ pHist->pOrig = sqlite3_malloc( pPager->pageSize );
+ if( !pHist->pOrig ){
+ return SQLITE_NOMEM;
+ }
+ memcpy(pHist->pOrig, PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }else{
+ u32 cksum;
+ char *pData2;
+
+ /* We should never write to the journal file the page that
+ ** contains the database locks. The following assert verifies
+ ** that we do not. */
+ assert( pPg->pgno!=PAGER_MJ_PGNO(pPager) );
+ pData2 = CODEC2(pPager, pData, pPg->pgno, 7);
+ cksum = pager_cksum(pPager, (u8*)pData2);
+ rc = write32bits(pPager->jfd, pPager->journalOff, pPg->pgno);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsWrite(pPager->jfd, pData2, pPager->pageSize,
+ pPager->journalOff + 4);
+ pPager->journalOff += pPager->pageSize+4;
+ }
+ if( rc==SQLITE_OK ){
+ rc = write32bits(pPager->jfd, pPager->journalOff, cksum);
+ pPager->journalOff += 4;
+ }
+ IOTRACE(("JOUT %p %d %lld %d\n", pPager, pPg->pgno,
+ pPager->journalOff, pPager->pageSize));
+ PAGER_INCR(sqlite3_pager_writej_count);
+ PAGERTRACE5("JOURNAL %d page %d needSync=%d hash(%08x)\n",
+ PAGERID(pPager), pPg->pgno, pPg->needSync, pager_pagehash(pPg));
+
+ /* An error has occured writing to the journal file. The
+ ** transaction will be rolled back by the layer above.
+ */
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ pPager->nRec++;
+ assert( pPager->pInJournal!=0 );
+ sqlite3BitvecSet(pPager->pInJournal, pPg->pgno);
+ pPg->needSync = !pPager->noSync;
+ if( pPager->stmtInUse ){
+ sqlite3BitvecSet(pPager->pInStmt, pPg->pgno);
+ }
+ }
+ }else{
+ pPg->needSync = !pPager->journalStarted && !pPager->noSync;
+ PAGERTRACE4("APPEND %d page %d needSync=%d\n",
+ PAGERID(pPager), pPg->pgno, pPg->needSync);
+ }
+ if( pPg->needSync ){
+ pPager->needSync = 1;
+ }
+ pPg->inJournal = 1;
+ }
+
+ /* If the statement journal is open and the page is not in it,
+ ** then write the current page to the statement journal. Note that
+ ** the statement journal format differs from the standard journal format
+ ** in that it omits the checksums and the header.
+ */
+ if( pPager->stmtInUse
+ && !pageInStatement(pPg)
+ && (int)pPg->pgno<=pPager->stmtSize
+ ){
+ assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize );
+ if( MEMDB ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ assert( pHist->pStmt==0 );
+ pHist->pStmt = sqlite3_malloc( pPager->pageSize );
+ if( pHist->pStmt ){
+ memcpy(pHist->pStmt, PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }
+ PAGERTRACE3("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno);
+ page_add_to_stmt_list(pPg);
+ }else{
+ i64 offset = pPager->stmtNRec*(4+pPager->pageSize);
+ char *pData2 = CODEC2(pPager, pData, pPg->pgno, 7);
+ rc = write32bits(pPager->stfd, offset, pPg->pgno);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsWrite(pPager->stfd, pData2, pPager->pageSize, offset+4);
+ }
+ PAGERTRACE3("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pPager->stmtNRec++;
+ assert( pPager->pInStmt!=0 );
+ sqlite3BitvecSet(pPager->pInStmt, pPg->pgno);
+ }
+ }
+ }
+
+ /* Update the database size and return.
+ */
+ assert( pPager->state>=PAGER_SHARED );
+ if( pPager->dbSize<(int)pPg->pgno ){
+ pPager->dbSize = pPg->pgno;
+ if( !MEMDB && pPager->dbSize==PENDING_BYTE/pPager->pageSize ){
+ pPager->dbSize++;
+ }
+ }
+ return rc;
+}
+
+/*
+** This function is used to mark a data-page as writable. It uses
+** pager_write() to open a journal file (if it is not already open)
+** and write the page *pData to the journal.
+**
+** The difference between this function and pager_write() is that this
+** function also deals with the special case where 2 or more pages
+** fit on a single disk sector. In this case all co-resident pages
+** must have been written to the journal file before returning.
+*/
+SQLITE_PRIVATE int sqlite3PagerWrite(DbPage *pDbPage){
+ int rc = SQLITE_OK;
+
+ PgHdr *pPg = pDbPage;
+ Pager *pPager = pPg->pPager;
+ Pgno nPagePerSector = (pPager->sectorSize/pPager->pageSize);
+
+ pagerEnter(pPager);
+ if( !MEMDB && nPagePerSector>1 ){
+ Pgno nPageCount; /* Total number of pages in database file */
+ Pgno pg1; /* First page of the sector pPg is located on. */
+ int nPage; /* Number of pages starting at pg1 to journal */
+ int ii;
+ int needSync = 0;
+
+ /* Set the doNotSync flag to 1. This is because we cannot allow a journal
+ ** header to be written between the pages journaled by this function.
+ */
+ assert( pPager->doNotSync==0 );
+ pPager->doNotSync = 1;
+
+ /* This trick assumes that both the page-size and sector-size are
+ ** an integer power of 2. It sets variable pg1 to the identifier
+ ** of the first page of the sector pPg is located on.
+ */
+ pg1 = ((pPg->pgno-1) & ~(nPagePerSector-1)) + 1;
+
+ nPageCount = sqlite3PagerPagecount(pPager);
+ if( pPg->pgno>nPageCount ){
+ nPage = (pPg->pgno - pg1)+1;
+ }else if( (pg1+nPagePerSector-1)>nPageCount ){
+ nPage = nPageCount+1-pg1;
+ }else{
+ nPage = nPagePerSector;
+ }
+ assert(nPage>0);
+ assert(pg1<=pPg->pgno);
+ assert((pg1+nPage)>pPg->pgno);
+
+ for(ii=0; ii<nPage && rc==SQLITE_OK; ii++){
+ Pgno pg = pg1+ii;
+ PgHdr *pPage;
+ if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){
+ if( pg!=PAGER_MJ_PGNO(pPager) ){
+ rc = sqlite3PagerGet(pPager, pg, &pPage);
+ if( rc==SQLITE_OK ){
+ rc = pager_write(pPage);
+ if( pPage->needSync ){
+ needSync = 1;
+ }
+ sqlite3PagerUnref(pPage);
+ }
+ }
+ }else if( (pPage = pager_lookup(pPager, pg))!=0 ){
+ if( pPage->needSync ){
+ needSync = 1;
+ }
+ }
+ }
+
+ /* If the PgHdr.needSync flag is set for any of the nPage pages
+ ** starting at pg1, then it needs to be set for all of them. Because
+ ** writing to any of these nPage pages may damage the others, the
+ ** journal file must contain sync()ed copies of all of them
+ ** before any of them can be written out to the database file.
+ */
+ if( needSync ){
+ for(ii=0; ii<nPage && needSync; ii++){
+ PgHdr *pPage = pager_lookup(pPager, pg1+ii);
+ if( pPage ) pPage->needSync = 1;
+ }
+ assert(pPager->needSync);
+ }
+
+ assert( pPager->doNotSync==1 );
+ pPager->doNotSync = 0;
+ }else{
+ rc = pager_write(pDbPage);
+ }
+ pagerLeave(pPager);
+ return rc;
+}
+
+/*
+** Return TRUE if the page given in the argument was previously passed
+** to sqlite3PagerWrite(). In other words, return TRUE if it is ok
+** to change the content of the page.
+*/
+#ifndef NDEBUG
+SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage *pPg){
+ return pPg->dirty;
+}
+#endif
+
+/*
+** A call to this routine tells the pager that it is not necessary to
+** write the information on page pPg back to the disk, even though
+** that page might be marked as dirty.
+**
+** The overlying software layer calls this routine when all of the data
+** on the given page is unused. The pager marks the page as clean so
+** that it does not get written to disk.
+**
+** Tests show that this optimization, together with the
+** sqlite3PagerDontRollback() below, more than double the speed
+** of large INSERT operations and quadruple the speed of large DELETEs.
+**
+** When this routine is called, set the alwaysRollback flag to true.
+** Subsequent calls to sqlite3PagerDontRollback() for the same page
+** will thereafter be ignored. This is necessary to avoid a problem
+** where a page with data is added to the freelist during one part of
+** a transaction then removed from the freelist during a later part
+** of the same transaction and reused for some other purpose. When it
+** is first added to the freelist, this routine is called. When reused,
+** the sqlite3PagerDontRollback() routine is called. But because the
+** page contains critical data, we still need to be sure it gets
+** rolled back in spite of the sqlite3PagerDontRollback() call.
+*/
+SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage *pDbPage){
+ PgHdr *pPg = pDbPage;
+ Pager *pPager = pPg->pPager;
+
+ if( MEMDB ) return;
+ pagerEnter(pPager);
+ pPg->alwaysRollback = 1;
+ if( pPg->dirty && !pPager->stmtInUse ){
+ assert( pPager->state>=PAGER_SHARED );
+ if( pPager->dbSize==(int)pPg->pgno && pPager->origDbSize<pPager->dbSize ){
+ /* If this pages is the last page in the file and the file has grown
+ ** during the current transaction, then do NOT mark the page as clean.
+ ** When the database file grows, we must make sure that the last page
+ ** gets written at least once so that the disk file will be the correct
+ ** size. If you do not write this page and the size of the file
+ ** on the disk ends up being too small, that can lead to database
+ ** corruption during the next transaction.
+ */
+ }else{
+ PAGERTRACE3("DONT_WRITE page %d of %d\n", pPg->pgno, PAGERID(pPager));
+ IOTRACE(("CLEAN %p %d\n", pPager, pPg->pgno))
+ makeClean(pPg);
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ }
+ }
+ pagerLeave(pPager);
+}
+
+/*
+** A call to this routine tells the pager that if a rollback occurs,
+** it is not necessary to restore the data on the given page. This
+** means that the pager does not have to record the given page in the
+** rollback journal.
+**
+** If we have not yet actually read the content of this page (if
+** the PgHdr.needRead flag is set) then this routine acts as a promise
+** that we will never need to read the page content in the future.
+** so the needRead flag can be cleared at this point.
+*/
+SQLITE_PRIVATE void sqlite3PagerDontRollback(DbPage *pPg){
+ Pager *pPager = pPg->pPager;
+
+ pagerEnter(pPager);
+ assert( pPager->state>=PAGER_RESERVED );
+
+ /* If the journal file is not open, or DontWrite() has been called on
+ ** this page (DontWrite() sets the alwaysRollback flag), then this
+ ** function is a no-op.
+ */
+ if( pPager->journalOpen==0 || pPg->alwaysRollback || pPager->alwaysRollback ){
+ pagerLeave(pPager);
+ return;
+ }
+ assert( !MEMDB ); /* For a memdb, pPager->journalOpen is always 0 */
+
+#ifdef SQLITE_SECURE_DELETE
+ if( pPg->inJournal || (int)pPg->pgno > pPager->origDbSize ){
+ return;
+ }
+#endif
+
+ /* If SECURE_DELETE is disabled, then there is no way that this
+ ** routine can be called on a page for which sqlite3PagerDontWrite()
+ ** has not been previously called during the same transaction.
+ ** And if DontWrite() has previously been called, the following
+ ** conditions must be met.
+ */
+ assert( !pPg->inJournal && (int)pPg->pgno <= pPager->origDbSize );
+
+ assert( pPager->pInJournal!=0 );
+ sqlite3BitvecSet(pPager->pInJournal, pPg->pgno);
+ pPg->inJournal = 1;
+ pPg->needRead = 0;
+ if( pPager->stmtInUse ){
+ assert( pPager->stmtSize >= pPager->origDbSize );
+ sqlite3BitvecSet(pPager->pInStmt, pPg->pgno);
+ }
+ PAGERTRACE3("DONT_ROLLBACK page %d of %d\n", pPg->pgno, PAGERID(pPager));
+ IOTRACE(("GARBAGE %p %d\n", pPager, pPg->pgno))
+ pagerLeave(pPager);
+}
+
+
+/*
+** This routine is called to increment the database file change-counter,
+** stored at byte 24 of the pager file.
+*/
+static int pager_incr_changecounter(Pager *pPager, int isDirect){
+ PgHdr *pPgHdr;
+ u32 change_counter;
+ int rc = SQLITE_OK;
+
+ if( !pPager->changeCountDone ){
+ /* Open page 1 of the file for writing. */
+ rc = sqlite3PagerGet(pPager, 1, &pPgHdr);
+ if( rc!=SQLITE_OK ) return rc;
+
+ if( !isDirect ){
+ rc = sqlite3PagerWrite(pPgHdr);
+ if( rc!=SQLITE_OK ){
+ sqlite3PagerUnref(pPgHdr);
+ return rc;
+ }
+ }
+
+ /* Increment the value just read and write it back to byte 24. */
+ change_counter = sqlite3Get4byte((u8*)pPager->dbFileVers);
+ change_counter++;
+ put32bits(((char*)PGHDR_TO_DATA(pPgHdr))+24, change_counter);
+
+ if( isDirect && pPager->fd->pMethods ){
+ const void *zBuf = PGHDR_TO_DATA(pPgHdr);
+ rc = sqlite3OsWrite(pPager->fd, zBuf, pPager->pageSize, 0);
+ }
+
+ /* Release the page reference. */
+ sqlite3PagerUnref(pPgHdr);
+ pPager->changeCountDone = 1;
+ }
+ return rc;
+}
+
+/*
+** Sync the pager file to disk.
+*/
+SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager){
+ int rc;
+ pagerEnter(pPager);
+ rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
+ pagerLeave(pPager);
+ return rc;
+}
+
+/*
+** Sync the database file for the pager pPager. zMaster points to the name
+** of a master journal file that should be written into the individual
+** journal file. zMaster may be NULL, which is interpreted as no master
+** journal (a single database transaction).
+**
+** This routine ensures that the journal is synced, all dirty pages written
+** to the database file and the database file synced. The only thing that
+** remains to commit the transaction is to delete the journal file (or
+** master journal file if specified).
+**
+** Note that if zMaster==NULL, this does not overwrite a previous value
+** passed to an sqlite3PagerCommitPhaseOne() call.
+**
+** If parameter nTrunc is non-zero, then the pager file is truncated to
+** nTrunc pages (this is used by auto-vacuum databases).
+**
+** If the final parameter - noSync - is true, then the database file itself
+** is not synced. The caller must call sqlite3PagerSync() directly to
+** sync the database file before calling CommitPhaseTwo() to delete the
+** journal file in this case.
+*/
+SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
+ Pager *pPager,
+ const char *zMaster,
+ Pgno nTrunc,
+ int noSync
+){
+ int rc = SQLITE_OK;
+
+ /* If no changes have been made, we can leave the transaction early.
+ */
+ if( pPager->dbModified==0 &&
+ (pPager->journalMode!=PAGER_JOURNALMODE_DELETE ||
+ pPager->exclusiveMode!=0) ){
+ assert( pPager->dirtyCache==0 || pPager->journalOpen==0 );
+ return SQLITE_OK;
+ }
+
+ PAGERTRACE4("DATABASE SYNC: File=%s zMaster=%s nTrunc=%d\n",
+ pPager->zFilename, zMaster, nTrunc);
+ pagerEnter(pPager);
+
+ /* If this is an in-memory db, or no pages have been written to, or this
+ ** function has already been called, it is a no-op.
+ */
+ if( pPager->state!=PAGER_SYNCED && !MEMDB && pPager->dirtyCache ){
+ PgHdr *pPg;
+
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+ /* The atomic-write optimization can be used if all of the
+ ** following are true:
+ **
+ ** + The file-system supports the atomic-write property for
+ ** blocks of size page-size, and
+ ** + This commit is not part of a multi-file transaction, and
+ ** + Exactly one page has been modified and store in the journal file.
+ **
+ ** If the optimization can be used, then the journal file will never
+ ** be created for this transaction.
+ */
+ int useAtomicWrite = (
+ !zMaster &&
+ pPager->journalOpen &&
+ pPager->journalOff==jrnlBufferSize(pPager) &&
+ nTrunc==0 &&
+ (0==pPager->pDirty || 0==pPager->pDirty->pDirty)
+ );
+ assert( pPager->journalOpen || pPager->journalMode==PAGER_JOURNALMODE_OFF );
+ if( useAtomicWrite ){
+ /* Update the nRec field in the journal file. */
+ int offset = pPager->journalHdr + sizeof(aJournalMagic);
+ assert(pPager->nRec==1);
+ rc = write32bits(pPager->jfd, offset, pPager->nRec);
+
+ /* Update the db file change counter. The following call will modify
+ ** the in-memory representation of page 1 to include the updated
+ ** change counter and then write page 1 directly to the database
+ ** file. Because of the atomic-write property of the host file-system,
+ ** this is safe.
+ */
+ if( rc==SQLITE_OK ){
+ rc = pager_incr_changecounter(pPager, 1);
+ }
+ }else{
+ rc = sqlite3JournalCreate(pPager->jfd);
+ }
+
+ if( !useAtomicWrite && rc==SQLITE_OK )
+#endif
+
+ /* If a master journal file name has already been written to the
+ ** journal file, then no sync is required. This happens when it is
+ ** written, then the process fails to upgrade from a RESERVED to an
+ ** EXCLUSIVE lock. The next time the process tries to commit the
+ ** transaction the m-j name will have already been written.
+ */
+ if( !pPager->setMaster ){
+ rc = pager_incr_changecounter(pPager, 0);
+ if( rc!=SQLITE_OK ) goto sync_exit;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( nTrunc!=0 ){
+ /* If this transaction has made the database smaller, then all pages
+ ** being discarded by the truncation must be written to the journal
+ ** file.
+ */
+ Pgno i;
+ int iSkip = PAGER_MJ_PGNO(pPager);
+ for( i=nTrunc+1; i<=pPager->origDbSize; i++ ){
+ if( !sqlite3BitvecTest(pPager->pInJournal, i) && i!=iSkip ){
+ rc = sqlite3PagerGet(pPager, i, &pPg);
+ if( rc!=SQLITE_OK ) goto sync_exit;
+ rc = sqlite3PagerWrite(pPg);
+ sqlite3PagerUnref(pPg);
+ if( rc!=SQLITE_OK ) goto sync_exit;
+ }
+ }
+ }
+#endif
+ rc = writeMasterJournal(pPager, zMaster);
+ if( rc!=SQLITE_OK ) goto sync_exit;
+ rc = syncJournal(pPager);
+ }
+ if( rc!=SQLITE_OK ) goto sync_exit;
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( nTrunc!=0 ){
+ rc = sqlite3PagerTruncate(pPager, nTrunc);
+ if( rc!=SQLITE_OK ) goto sync_exit;
+ }
+#endif
+
+ /* Write all dirty pages to the database file */
+ pPg = pager_get_all_dirty_pages(pPager);
+ rc = pager_write_pagelist(pPg);
+ if( rc!=SQLITE_OK ){
+ assert( rc!=SQLITE_IOERR_BLOCKED );
+ /* The error might have left the dirty list all fouled up here,
+ ** but that does not matter because if the if the dirty list did
+ ** get corrupted, then the transaction will roll back and
+ ** discard the dirty list. There is an assert in
+ ** pager_get_all_dirty_pages() that verifies that no attempt
+ ** is made to use an invalid dirty list.
+ */
+ goto sync_exit;
+ }
+ pPager->pDirty = 0;
+
+ /* Sync the database file. */
+ if( !pPager->noSync && !noSync ){
+ rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
+ }
+ IOTRACE(("DBSYNC %p\n", pPager))
+
+ pPager->state = PAGER_SYNCED;
+ }else if( MEMDB && nTrunc!=0 ){
+ rc = sqlite3PagerTruncate(pPager, nTrunc);
+ }
+
+sync_exit:
+ if( rc==SQLITE_IOERR_BLOCKED ){
+ /* pager_incr_changecounter() may attempt to obtain an exclusive
+ * lock to spill the cache and return IOERR_BLOCKED. But since
+ * there is no chance the cache is inconsistent, it is
+ * better to return SQLITE_BUSY.
+ */
+ rc = SQLITE_BUSY;
+ }
+ pagerLeave(pPager);
+ return rc;
+}
+
+
+/*
+** Commit all changes to the database and release the write lock.
+**
+** If the commit fails for any reason, a rollback attempt is made
+** and an error code is returned. If the commit worked, SQLITE_OK
+** is returned.
+*/
+SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){
+ int rc;
+ PgHdr *pPg;
+
+ if( pPager->errCode ){
+ return pPager->errCode;
+ }
+ if( pPager->state<PAGER_RESERVED ){
+ return SQLITE_ERROR;
+ }
+ if( pPager->dbModified==0 &&
+ (pPager->journalMode!=PAGER_JOURNALMODE_DELETE ||
+ pPager->exclusiveMode!=0) ){
+ assert( pPager->dirtyCache==0 || pPager->journalOpen==0 );
+ return SQLITE_OK;
+ }
+ pagerEnter(pPager);
+ PAGERTRACE2("COMMIT %d\n", PAGERID(pPager));
+ if( MEMDB ){
+ pPg = pager_get_all_dirty_pages(pPager);
+ while( pPg ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ clearHistory(pHist);
+ pPg->dirty = 0;
+ pPg->inJournal = 0;
+ pHist->inStmt = 0;
+ pPg->needSync = 0;
+ pHist->pPrevStmt = pHist->pNextStmt = 0;
+ pPg = pPg->pDirty;
+ }
+ pPager->pDirty = 0;
+#ifndef NDEBUG
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ assert( !pPg->alwaysRollback );
+ assert( !pHist->pOrig );
+ assert( !pHist->pStmt );
+ }
+#endif
+ pPager->pStmt = 0;
+ pPager->state = PAGER_SHARED;
+ pagerLeave(pPager);
+ return SQLITE_OK;
+ }
+ assert( pPager->state==PAGER_SYNCED || !pPager->dirtyCache );
+ rc = pager_end_transaction(pPager, pPager->setMaster);
+ rc = pager_error(pPager, rc);
+ pagerLeave(pPager);
+ return rc;
+}
+
+/*
+** Rollback all changes. The database falls back to PAGER_SHARED mode.
+** All in-memory cache pages revert to their original data contents.
+** The journal is deleted.
+**
+** This routine cannot fail unless some other process is not following
+** the correct locking protocol or unless some other
+** process is writing trash into the journal file (SQLITE_CORRUPT) or
+** unless a prior malloc() failed (SQLITE_NOMEM). Appropriate error
+** codes are returned for all these occasions. Otherwise,
+** SQLITE_OK is returned.
+*/
+SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){
+ int rc;
+ PAGERTRACE2("ROLLBACK %d\n", PAGERID(pPager));
+ if( MEMDB ){
+ PgHdr *p;
+ for(p=pPager->pAll; p; p=p->pNextAll){
+ PgHistory *pHist;
+ assert( !p->alwaysRollback );
+ if( !p->dirty ){
+ assert( !((PgHistory *)PGHDR_TO_HIST(p, pPager))->pOrig );
+ assert( !((PgHistory *)PGHDR_TO_HIST(p, pPager))->pStmt );
+ continue;
+ }
+
+ pHist = PGHDR_TO_HIST(p, pPager);
+ if( pHist->pOrig ){
+ memcpy(PGHDR_TO_DATA(p), pHist->pOrig, pPager->pageSize);
+ PAGERTRACE3("ROLLBACK-PAGE %d of %d\n", p->pgno, PAGERID(pPager));
+ }else{
+ PAGERTRACE3("PAGE %d is clean on %d\n", p->pgno, PAGERID(pPager));
+ }
+ clearHistory(pHist);
+ p->dirty = 0;
+ p->inJournal = 0;
+ pHist->inStmt = 0;
+ pHist->pPrevStmt = pHist->pNextStmt = 0;
+ if( pPager->xReiniter ){
+ pPager->xReiniter(p, pPager->pageSize);
+ }
+ }
+ pPager->pDirty = 0;
+ pPager->pStmt = 0;
+ pPager->dbSize = pPager->origDbSize;
+ pager_truncate_cache(pPager);
+ pPager->stmtInUse = 0;
+ pPager->state = PAGER_SHARED;
+ return SQLITE_OK;
+ }
+
+ pagerEnter(pPager);
+ if( !pPager->dirtyCache || !pPager->journalOpen ){
+ rc = pager_end_transaction(pPager, pPager->setMaster);
+ pagerLeave(pPager);
+ return rc;
+ }
+
+ if( pPager->errCode && pPager->errCode!=SQLITE_FULL ){
+ if( pPager->state>=PAGER_EXCLUSIVE ){
+ pager_playback(pPager, 0);
+ }
+ pagerLeave(pPager);
+ return pPager->errCode;
+ }
+ if( pPager->state==PAGER_RESERVED ){
+ int rc2;
+ rc = pager_playback(pPager, 0);
+ rc2 = pager_end_transaction(pPager, pPager->setMaster);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }else{
+ rc = pager_playback(pPager, 0);
+ }
+ /* pager_reset(pPager); */
+ pPager->dbSize = -1;
+
+ /* If an error occurs during a ROLLBACK, we can no longer trust the pager
+ ** cache. So call pager_error() on the way out to make any error
+ ** persistent.
+ */
+ rc = pager_error(pPager, rc);
+ pagerLeave(pPager);
+ return rc;
+}
+
+/*
+** Return TRUE if the database file is opened read-only. Return FALSE
+** if the database is (in theory) writable.
+*/
+SQLITE_PRIVATE int sqlite3PagerIsreadonly(Pager *pPager){
+ return pPager->readOnly;
+}
+
+/*
+** Return the number of references to the pager.
+*/
+SQLITE_PRIVATE int sqlite3PagerRefcount(Pager *pPager){
+ return pPager->nRef;
+}
+
+#ifdef SQLITE_TEST
+/*
+** This routine is used for testing and analysis only.
+*/
+SQLITE_PRIVATE int *sqlite3PagerStats(Pager *pPager){
+ static int a[11];
+ a[0] = pPager->nRef;
+ a[1] = pPager->nPage;
+ a[2] = pPager->mxPage;
+ a[3] = pPager->dbSize;
+ a[4] = pPager->state;
+ a[5] = pPager->errCode;
+ a[6] = pPager->nHit;
+ a[7] = pPager->nMiss;
+ a[8] = 0; /* Used to be pPager->nOvfl */
+ a[9] = pPager->nRead;
+ a[10] = pPager->nWrite;
+ return a;
+}
+#endif
+
+/*
+** Set the statement rollback point.
+**
+** This routine should be called with the transaction journal already
+** open. A new statement journal is created that can be used to rollback
+** changes of a single SQL command within a larger transaction.
+*/
+static int pagerStmtBegin(Pager *pPager){
+ int rc;
+ assert( !pPager->stmtInUse );
+ assert( pPager->state>=PAGER_SHARED );
+ assert( pPager->dbSize>=0 );
+ PAGERTRACE2("STMT-BEGIN %d\n", PAGERID(pPager));
+ if( MEMDB ){
+ pPager->stmtInUse = 1;
+ pPager->stmtSize = pPager->dbSize;
+ return SQLITE_OK;
+ }
+ if( !pPager->journalOpen ){
+ pPager->stmtAutoopen = 1;
+ return SQLITE_OK;
+ }
+ assert( pPager->journalOpen );
+ pagerLeave(pPager);
+ assert( pPager->pInStmt==0 );
+ pPager->pInStmt = sqlite3BitvecCreate(pPager->dbSize);
+ pagerEnter(pPager);
+ if( pPager->pInStmt==0 ){
+ /* sqlite3OsLock(pPager->fd, SHARED_LOCK); */
+ return SQLITE_NOMEM;
+ }
+ pPager->stmtJSize = pPager->journalOff;
+ pPager->stmtSize = pPager->dbSize;
+ pPager->stmtHdrOff = 0;
+ pPager->stmtCksum = pPager->cksumInit;
+ if( !pPager->stmtOpen ){
+ rc = sqlite3PagerOpentemp(pPager->pVfs, pPager->stfd, pPager->zStmtJrnl,
+ SQLITE_OPEN_SUBJOURNAL);
+ if( rc ){
+ goto stmt_begin_failed;
+ }
+ pPager->stmtOpen = 1;
+ pPager->stmtNRec = 0;
+ }
+ pPager->stmtInUse = 1;
+ return SQLITE_OK;
+
+stmt_begin_failed:
+ if( pPager->pInStmt ){
+ sqlite3BitvecDestroy(pPager->pInStmt);
+ pPager->pInStmt = 0;
+ }
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3PagerStmtBegin(Pager *pPager){
+ int rc;
+ pagerEnter(pPager);
+ rc = pagerStmtBegin(pPager);
+ pagerLeave(pPager);
+ return rc;
+}
+
+/*
+** Commit a statement.
+*/
+SQLITE_PRIVATE int sqlite3PagerStmtCommit(Pager *pPager){
+ pagerEnter(pPager);
+ if( pPager->stmtInUse ){
+ PgHdr *pPg, *pNext;
+ PAGERTRACE2("STMT-COMMIT %d\n", PAGERID(pPager));
+ if( !MEMDB ){
+ /* sqlite3OsTruncate(pPager->stfd, 0); */
+ sqlite3BitvecDestroy(pPager->pInStmt);
+ pPager->pInStmt = 0;
+ }else{
+ for(pPg=pPager->pStmt; pPg; pPg=pNext){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ pNext = pHist->pNextStmt;
+ assert( pHist->inStmt );
+ pHist->inStmt = 0;
+ pHist->pPrevStmt = pHist->pNextStmt = 0;
+ sqlite3_free(pHist->pStmt);
+ pHist->pStmt = 0;
+ }
+ }
+ pPager->stmtNRec = 0;
+ pPager->stmtInUse = 0;
+ pPager->pStmt = 0;
+ }
+ pPager->stmtAutoopen = 0;
+ pagerLeave(pPager);
+ return SQLITE_OK;
+}
+
+/*
+** Rollback a statement.
+*/
+SQLITE_PRIVATE int sqlite3PagerStmtRollback(Pager *pPager){
+ int rc;
+ pagerEnter(pPager);
+ if( pPager->stmtInUse ){
+ PAGERTRACE2("STMT-ROLLBACK %d\n", PAGERID(pPager));
+ if( MEMDB ){
+ PgHdr *pPg;
+ PgHistory *pHist;
+ for(pPg=pPager->pStmt; pPg; pPg=pHist->pNextStmt){
+ pHist = PGHDR_TO_HIST(pPg, pPager);
+ if( pHist->pStmt ){
+ memcpy(PGHDR_TO_DATA(pPg), pHist->pStmt, pPager->pageSize);
+ sqlite3_free(pHist->pStmt);
+ pHist->pStmt = 0;
+ }
+ }
+ pPager->dbSize = pPager->stmtSize;
+ pager_truncate_cache(pPager);
+ rc = SQLITE_OK;
+ }else{
+ rc = pager_stmt_playback(pPager);
+ }
+ sqlite3PagerStmtCommit(pPager);
+ }else{
+ rc = SQLITE_OK;
+ }
+ pPager->stmtAutoopen = 0;
+ pagerLeave(pPager);
+ return rc;
+}
+
+/*
+** Return the full pathname of the database file.
+*/
+SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager *pPager){
+ return pPager->zFilename;
+}
+
+/*
+** Return the VFS structure for the pager.
+*/
+SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager *pPager){
+ return pPager->pVfs;
+}
+
+/*
+** Return the file handle for the database file associated
+** with the pager. This might return NULL if the file has
+** not yet been opened.
+*/
+SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){
+ return pPager->fd;
+}
+
+/*
+** Return the directory of the database file.
+*/
+SQLITE_PRIVATE const char *sqlite3PagerDirname(Pager *pPager){
+ return pPager->zDirectory;
+}
+
+/*
+** Return the full pathname of the journal file.
+*/
+SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager *pPager){
+ return pPager->zJournal;
+}
+
+/*
+** Return true if fsync() calls are disabled for this pager. Return FALSE
+** if fsync()s are executed normally.
+*/
+SQLITE_PRIVATE int sqlite3PagerNosync(Pager *pPager){
+ return pPager->noSync;
+}
+
+#ifdef SQLITE_HAS_CODEC
+/*
+** Set the codec for this pager
+*/
+SQLITE_PRIVATE void sqlite3PagerSetCodec(
+ Pager *pPager,
+ void *(*xCodec)(void*,void*,Pgno,int),
+ void *pCodecArg
+){
+ pPager->xCodec = xCodec;
+ pPager->pCodecArg = pCodecArg;
+}
+#endif
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+/*
+** Move the page pPg to location pgno in the file.
+**
+** There must be no references to the page previously located at
+** pgno (which we call pPgOld) though that page is allowed to be
+** in cache. If the page previous located at pgno is not already
+** in the rollback journal, it is not put there by by this routine.
+**
+** References to the page pPg remain valid. Updating any
+** meta-data associated with pPg (i.e. data stored in the nExtra bytes
+** allocated along with the page) is the responsibility of the caller.
+**
+** A transaction must be active when this routine is called. It used to be
+** required that a statement transaction was not active, but this restriction
+** has been removed (CREATE INDEX needs to move a page when a statement
+** transaction is active).
+*/
+SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno){
+ PgHdr *pPgOld; /* The page being overwritten. */
+ int h;
+ Pgno needSyncPgno = 0;
+
+ pagerEnter(pPager);
+ assert( pPg->nRef>0 );
+
+ PAGERTRACE5("MOVE %d page %d (needSync=%d) moves to %d\n",
+ PAGERID(pPager), pPg->pgno, pPg->needSync, pgno);
+ IOTRACE(("MOVE %p %d %d\n", pPager, pPg->pgno, pgno))
+
+ pager_get_content(pPg);
+ if( pPg->needSync ){
+ needSyncPgno = pPg->pgno;
+ assert( pPg->inJournal || (int)pgno>pPager->origDbSize );
+ assert( pPg->dirty );
+ assert( pPager->needSync );
+ }
+
+ /* Unlink pPg from its hash-chain */
+ unlinkHashChain(pPager, pPg);
+
+ /* If the cache contains a page with page-number pgno, remove it
+ ** from its hash chain. Also, if the PgHdr.needSync was set for
+ ** page pgno before the 'move' operation, it needs to be retained
+ ** for the page moved there.
+ */
+ pPg->needSync = 0;
+ pPgOld = pager_lookup(pPager, pgno);
+ if( pPgOld ){
+ assert( pPgOld->nRef==0 );
+ unlinkHashChain(pPager, pPgOld);
+ makeClean(pPgOld);
+ pPg->needSync = pPgOld->needSync;
+ }else{
+ pPg->needSync = 0;
+ }
+ pPg->inJournal = sqlite3BitvecTest(pPager->pInJournal, pgno);
+
+ /* Change the page number for pPg and insert it into the new hash-chain. */
+ assert( pgno!=0 );
+ pPg->pgno = pgno;
+ h = pgno & (pPager->nHash-1);
+ if( pPager->aHash[h] ){
+ assert( pPager->aHash[h]->pPrevHash==0 );
+ pPager->aHash[h]->pPrevHash = pPg;
+ }
+ pPg->pNextHash = pPager->aHash[h];
+ pPager->aHash[h] = pPg;
+ pPg->pPrevHash = 0;
+
+ makeDirty(pPg);
+ pPager->dirtyCache = 1;
+ pPager->dbModified = 1;
+
+ if( needSyncPgno ){
+ /* If needSyncPgno is non-zero, then the journal file needs to be
+ ** sync()ed before any data is written to database file page needSyncPgno.
+ ** Currently, no such page exists in the page-cache and the
+ ** Pager.pInJournal bit has been set. This needs to be remedied by loading
+ ** the page into the pager-cache and setting the PgHdr.needSync flag.
+ **
+ ** If the attempt to load the page into the page-cache fails, (due
+ ** to a malloc() or IO failure), clear the bit in the pInJournal[]
+ ** array. Otherwise, if the page is loaded and written again in
+ ** this transaction, it may be written to the database file before
+ ** it is synced into the journal file. This way, it may end up in
+ ** the journal file twice, but that is not a problem.
+ **
+ ** The sqlite3PagerGet() call may cause the journal to sync. So make
+ ** sure the Pager.needSync flag is set too.
+ */
+ int rc;
+ PgHdr *pPgHdr;
+ assert( pPager->needSync );
+ rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr);
+ if( rc!=SQLITE_OK ){
+ if( pPager->pInJournal && (int)needSyncPgno<=pPager->origDbSize ){
+ sqlite3BitvecClear(pPager->pInJournal, needSyncPgno);
+ }
+ pagerLeave(pPager);
+ return rc;
+ }
+ pPager->needSync = 1;
+ pPgHdr->needSync = 1;
+ pPgHdr->inJournal = 1;
+ makeDirty(pPgHdr);
+ sqlite3PagerUnref(pPgHdr);
+ }
+
+ pagerLeave(pPager);
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** Return a pointer to the data for the specified page.
+*/
+SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *pPg){
+ return PGHDR_TO_DATA(pPg);
+}
+
+/*
+** Return a pointer to the Pager.nExtra bytes of "extra" space
+** allocated along with the specified page.
+*/
+SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *pPg){
+ Pager *pPager = pPg->pPager;
+ return (pPager?PGHDR_TO_EXTRA(pPg, pPager):0);
+}
+
+/*
+** Get/set the locking-mode for this pager. Parameter eMode must be one
+** of PAGER_LOCKINGMODE_QUERY, PAGER_LOCKINGMODE_NORMAL or
+** PAGER_LOCKINGMODE_EXCLUSIVE. If the parameter is not _QUERY, then
+** the locking-mode is set to the value specified.
+**
+** The returned value is either PAGER_LOCKINGMODE_NORMAL or
+** PAGER_LOCKINGMODE_EXCLUSIVE, indicating the current (possibly updated)
+** locking-mode.
+*/
+SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *pPager, int eMode){
+ assert( eMode==PAGER_LOCKINGMODE_QUERY
+ || eMode==PAGER_LOCKINGMODE_NORMAL
+ || eMode==PAGER_LOCKINGMODE_EXCLUSIVE );
+ assert( PAGER_LOCKINGMODE_QUERY<0 );
+ assert( PAGER_LOCKINGMODE_NORMAL>=0 && PAGER_LOCKINGMODE_EXCLUSIVE>=0 );
+ if( eMode>=0 && !pPager->tempFile ){
+ pPager->exclusiveMode = eMode;
+ }
+ return (int)pPager->exclusiveMode;
+}
+
+/*
+** Get/set the journal-mode for this pager. Parameter eMode must be one
+** of PAGER_JOURNALMODE_QUERY, PAGER_JOURNALMODE_DELETE or
+** PAGER_JOURNALMODE_PERSIST. If the parameter is not _QUERY, then
+** the journal-mode is set to the value specified.
+**
+** The returned value is either PAGER_JOURNALMODE_DELETE or
+** PAGER_JOURNALMODE_PERSIST, indicating the current (possibly updated)
+** journal-mode.
+*/
+SQLITE_PRIVATE int sqlite3PagerJournalMode(Pager *pPager, int eMode){
+ assert( eMode==PAGER_JOURNALMODE_QUERY
+ || eMode==PAGER_JOURNALMODE_DELETE
+ || eMode==PAGER_JOURNALMODE_PERSIST
+ || eMode==PAGER_JOURNALMODE_OFF );
+ assert( PAGER_JOURNALMODE_QUERY<0 );
+ assert( PAGER_JOURNALMODE_DELETE>=0 && PAGER_JOURNALMODE_PERSIST>=0 );
+ if( eMode>=0 ){
+ pPager->journalMode = eMode;
+ }
+ return (int)pPager->journalMode;
+}
+
+#ifdef SQLITE_TEST
+/*
+** Print a listing of all referenced pages and their ref count.
+*/
+SQLITE_PRIVATE void sqlite3PagerRefdump(Pager *pPager){
+ PgHdr *pPg;
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ if( pPg->nRef<=0 ) continue;
+ sqlite3DebugPrintf("PAGE %3d addr=%p nRef=%d\n",
+ pPg->pgno, PGHDR_TO_DATA(pPg), pPg->nRef);
+ }
+}
+#endif
+
+#endif /* SQLITE_OMIT_DISKIO */
+
+/************** End of pager.c ***********************************************/
+/************** Begin file btmutex.c *****************************************/
+/*
+** 2007 August 27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** $Id: btmutex.c,v 1.9 2008/01/23 12:52:41 drh Exp $
+**
+** This file contains code used to implement mutexes on Btree objects.
+** This code really belongs in btree.c. But btree.c is getting too
+** big and we want to break it down some. This packaged seemed like
+** a good breakout.
+*/
+/************** Include btreeInt.h in the middle of btmutex.c ****************/
+/************** Begin file btreeInt.h ****************************************/
+/*
+** 2004 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** $Id: btreeInt.h,v 1.21 2008/04/24 19:15:10 shane Exp $
+**
+** This file implements a external (disk-based) database using BTrees.
+** For a detailed discussion of BTrees, refer to
+**
+** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
+** "Sorting And Searching", pages 473-480. Addison-Wesley
+** Publishing Company, Reading, Massachusetts.
+**
+** The basic idea is that each page of the file contains N database
+** entries and N+1 pointers to subpages.
+**
+** ----------------------------------------------------------------
+** | Ptr(0) | Key(0) | Ptr(1) | Key(1) | ... | Key(N-1) | Ptr(N) |
+** ----------------------------------------------------------------
+**
+** All of the keys on the page that Ptr(0) points to have values less
+** than Key(0). All of the keys on page Ptr(1) and its subpages have
+** values greater than Key(0) and less than Key(1). All of the keys
+** on Ptr(N) and its subpages have values greater than Key(N-1). And
+** so forth.
+**
+** Finding a particular key requires reading O(log(M)) pages from the
+** disk where M is the number of entries in the tree.
+**
+** In this implementation, a single file can hold one or more separate
+** BTrees. Each BTree is identified by the index of its root page. The
+** key and data for any entry are combined to form the "payload". A
+** fixed amount of payload can be carried directly on the database
+** page. If the payload is larger than the preset amount then surplus
+** bytes are stored on overflow pages. The payload for an entry
+** and the preceding pointer are combined to form a "Cell". Each
+** page has a small header which contains the Ptr(N) pointer and other
+** information such as the size of key and data.
+**
+** FORMAT DETAILS
+**
+** The file is divided into pages. The first page is called page 1,
+** the second is page 2, and so forth. A page number of zero indicates
+** "no such page". The page size can be anything between 512 and 65536.
+** Each page can be either a btree page, a freelist page or an overflow
+** page.
+**
+** The first page is always a btree page. The first 100 bytes of the first
+** page contain a special header (the "file header") that describes the file.
+** The format of the file header is as follows:
+**
+** OFFSET SIZE DESCRIPTION
+** 0 16 Header string: "SQLite format 3\000"
+** 16 2 Page size in bytes.
+** 18 1 File format write version
+** 19 1 File format read version
+** 20 1 Bytes of unused space at the end of each page
+** 21 1 Max embedded payload fraction
+** 22 1 Min embedded payload fraction
+** 23 1 Min leaf payload fraction
+** 24 4 File change counter
+** 28 4 Reserved for future use
+** 32 4 First freelist page
+** 36 4 Number of freelist pages in the file
+** 40 60 15 4-byte meta values passed to higher layers
+**
+** All of the integer values are big-endian (most significant byte first).
+**
+** The file change counter is incremented when the database is changed
+** This counter allows other processes to know when the file has changed
+** and thus when they need to flush their cache.
+**
+** The max embedded payload fraction is the amount of the total usable
+** space in a page that can be consumed by a single cell for standard
+** B-tree (non-LEAFDATA) tables. A value of 255 means 100%. The default
+** is to limit the maximum cell size so that at least 4 cells will fit
+** on one page. Thus the default max embedded payload fraction is 64.
+**
+** If the payload for a cell is larger than the max payload, then extra
+** payload is spilled to overflow pages. Once an overflow page is allocated,
+** as many bytes as possible are moved into the overflow pages without letting
+** the cell size drop below the min embedded payload fraction.
+**
+** The min leaf payload fraction is like the min embedded payload fraction
+** except that it applies to leaf nodes in a LEAFDATA tree. The maximum
+** payload fraction for a LEAFDATA tree is always 100% (or 255) and it
+** not specified in the header.
+**
+** Each btree pages is divided into three sections: The header, the
+** cell pointer array, and the cell content area. Page 1 also has a 100-byte
+** file header that occurs before the page header.
+**
+** |----------------|
+** | file header | 100 bytes. Page 1 only.
+** |----------------|
+** | page header | 8 bytes for leaves. 12 bytes for interior nodes
+** |----------------|
+** | cell pointer | | 2 bytes per cell. Sorted order.
+** | array | | Grows downward
+** | | v
+** |----------------|
+** | unallocated |
+** | space |
+** |----------------| ^ Grows upwards
+** | cell content | | Arbitrary order interspersed with freeblocks.
+** | area | | and free space fragments.
+** |----------------|
+**
+** The page headers looks like this:
+**
+** OFFSET SIZE DESCRIPTION
+** 0 1 Flags. 1: intkey, 2: zerodata, 4: leafdata, 8: leaf
+** 1 2 byte offset to the first freeblock
+** 3 2 number of cells on this page
+** 5 2 first byte of the cell content area
+** 7 1 number of fragmented free bytes
+** 8 4 Right child (the Ptr(N) value). Omitted on leaves.
+**
+** The flags define the format of this btree page. The leaf flag means that
+** this page has no children. The zerodata flag means that this page carries
+** only keys and no data. The intkey flag means that the key is a integer
+** which is stored in the key size entry of the cell header rather than in
+** the payload area.
+**
+** The cell pointer array begins on the first byte after the page header.
+** The cell pointer array contains zero or more 2-byte numbers which are
+** offsets from the beginning of the page to the cell content in the cell
+** content area. The cell pointers occur in sorted order. The system strives
+** to keep free space after the last cell pointer so that new cells can
+** be easily added without having to defragment the page.
+**
+** Cell content is stored at the very end of the page and grows toward the
+** beginning of the page.
+**
+** Unused space within the cell content area is collected into a linked list of
+** freeblocks. Each freeblock is at least 4 bytes in size. The byte offset
+** to the first freeblock is given in the header. Freeblocks occur in
+** increasing order. Because a freeblock must be at least 4 bytes in size,
+** any group of 3 or fewer unused bytes in the cell content area cannot
+** exist on the freeblock chain. A group of 3 or fewer free bytes is called
+** a fragment. The total number of bytes in all fragments is recorded.
+** in the page header at offset 7.
+**
+** SIZE DESCRIPTION
+** 2 Byte offset of the next freeblock
+** 2 Bytes in this freeblock
+**
+** Cells are of variable length. Cells are stored in the cell content area at
+** the end of the page. Pointers to the cells are in the cell pointer array
+** that immediately follows the page header. Cells is not necessarily
+** contiguous or in order, but cell pointers are contiguous and in order.
+**
+** Cell content makes use of variable length integers. A variable
+** length integer is 1 to 9 bytes where the lower 7 bits of each
+** byte are used. The integer consists of all bytes that have bit 8 set and
+** the first byte with bit 8 clear. The most significant byte of the integer
+** appears first. A variable-length integer may not be more than 9 bytes long.
+** As a special case, all 8 bytes of the 9th byte are used as data. This
+** allows a 64-bit integer to be encoded in 9 bytes.
+**
+** 0x00 becomes 0x00000000
+** 0x7f becomes 0x0000007f
+** 0x81 0x00 becomes 0x00000080
+** 0x82 0x00 becomes 0x00000100
+** 0x80 0x7f becomes 0x0000007f
+** 0x8a 0x91 0xd1 0xac 0x78 becomes 0x12345678
+** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081
+**
+** Variable length integers are used for rowids and to hold the number of
+** bytes of key and data in a btree cell.
+**
+** The content of a cell looks like this:
+**
+** SIZE DESCRIPTION
+** 4 Page number of the left child. Omitted if leaf flag is set.
+** var Number of bytes of data. Omitted if the zerodata flag is set.
+** var Number of bytes of key. Or the key itself if intkey flag is set.
+** * Payload
+** 4 First page of the overflow chain. Omitted if no overflow
+**
+** Overflow pages form a linked list. Each page except the last is completely
+** filled with data (pagesize - 4 bytes). The last page can have as little
+** as 1 byte of data.
+**
+** SIZE DESCRIPTION
+** 4 Page number of next overflow page
+** * Data
+**
+** Freelist pages come in two subtypes: trunk pages and leaf pages. The
+** file header points to the first in a linked list of trunk page. Each trunk
+** page points to multiple leaf pages. The content of a leaf page is
+** unspecified. A trunk page looks like this:
+**
+** SIZE DESCRIPTION
+** 4 Page number of next trunk page
+** 4 Number of leaf pointers on this page
+** * zero or more pages numbers of leaves
+*/
+
+/* Round up a number to the next larger multiple of 8. This is used
+** to force 8-byte alignment on 64-bit architectures.
+*/
+#define ROUND8(x) ((x+7)&~7)
+
+
+/* The following value is the maximum cell size assuming a maximum page
+** size give above.
+*/
+#define MX_CELL_SIZE(pBt) (pBt->pageSize-8)
+
+/* The maximum number of cells on a single page of the database. This
+** assumes a minimum cell size of 6 bytes (4 bytes for the cell itself
+** plus 2 bytes for the index to the cell in the page header). Such
+** small cells will be rare, but they are possible.
+*/
+#define MX_CELL(pBt) ((pBt->pageSize-8)/6)
+
+/* Forward declarations */
+typedef struct MemPage MemPage;
+typedef struct BtLock BtLock;
+
+/*
+** This is a magic string that appears at the beginning of every
+** SQLite database in order to identify the file as a real database.
+**
+** You can change this value at compile-time by specifying a
+** -DSQLITE_FILE_HEADER="..." on the compiler command-line. The
+** header must be exactly 16 bytes including the zero-terminator so
+** the string itself should be 15 characters long. If you change
+** the header, then your custom library will not be able to read
+** databases generated by the standard tools and the standard tools
+** will not be able to read databases created by your custom library.
+*/
+#ifndef SQLITE_FILE_HEADER /* 123456789 123456 */
+# define SQLITE_FILE_HEADER "SQLite format 3"
+#endif
+
+/*
+** Page type flags. An ORed combination of these flags appear as the
+** first byte of on-disk image of every BTree page.
+*/
+#define PTF_INTKEY 0x01
+#define PTF_ZERODATA 0x02
+#define PTF_LEAFDATA 0x04
+#define PTF_LEAF 0x08
+
+/*
+** As each page of the file is loaded into memory, an instance of the following
+** structure is appended and initialized to zero. This structure stores
+** information about the page that is decoded from the raw file page.
+**
+** The pParent field points back to the parent page. This allows us to
+** walk up the BTree from any leaf to the root. Care must be taken to
+** unref() the parent page pointer when this page is no longer referenced.
+** The pageDestructor() routine handles that chore.
+**
+** Access to all fields of this structure is controlled by the mutex
+** stored in MemPage.pBt->mutex.
+*/
+struct MemPage {
+ u8 isInit; /* True if previously initialized. MUST BE FIRST! */
+ u8 idxShift; /* True if Cell indices have changed */
+ u8 nOverflow; /* Number of overflow cell bodies in aCell[] */
+ u8 intKey; /* True if intkey flag is set */
+ u8 leaf; /* True if leaf flag is set */
+ u8 zeroData; /* True if table stores keys only */
+ u8 leafData; /* True if tables stores data on leaves only */
+ u8 hasData; /* True if this page stores data */
+ u8 hdrOffset; /* 100 for page 1. 0 otherwise */
+ u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */
+ u16 maxLocal; /* Copy of BtShared.maxLocal or BtShared.maxLeaf */
+ u16 minLocal; /* Copy of BtShared.minLocal or BtShared.minLeaf */
+ u16 cellOffset; /* Index in aData of first cell pointer */
+ u16 idxParent; /* Index in parent of this node */
+ u16 nFree; /* Number of free bytes on the page */
+ u16 nCell; /* Number of cells on this page, local and ovfl */
+ struct _OvflCell { /* Cells that will not fit on aData[] */
+ u8 *pCell; /* Pointers to the body of the overflow cell */
+ u16 idx; /* Insert this cell before idx-th non-overflow cell */
+ } aOvfl[5];
+ BtShared *pBt; /* Pointer to BtShared that this page is part of */
+ u8 *aData; /* Pointer to disk image of the page data */
+ DbPage *pDbPage; /* Pager page handle */
+ Pgno pgno; /* Page number for this page */
+ MemPage *pParent; /* The parent of this page. NULL for root */
+};
+
+/*
+** The in-memory image of a disk page has the auxiliary information appended
+** to the end. EXTRA_SIZE is the number of bytes of space needed to hold
+** that extra information.
+*/
+#define EXTRA_SIZE sizeof(MemPage)
+
+/* A Btree handle
+**
+** A database connection contains a pointer to an instance of
+** this object for every database file that it has open. This structure
+** is opaque to the database connection. The database connection cannot
+** see the internals of this structure and only deals with pointers to
+** this structure.
+**
+** For some database files, the same underlying database cache might be
+** shared between multiple connections. In that case, each contection
+** has it own pointer to this object. But each instance of this object
+** points to the same BtShared object. The database cache and the
+** schema associated with the database file are all contained within
+** the BtShared object.
+**
+** All fields in this structure are accessed under sqlite3.mutex.
+** The pBt pointer itself may not be changed while there exists cursors
+** in the referenced BtShared that point back to this Btree since those
+** cursors have to do go through this Btree to find their BtShared and
+** they often do so without holding sqlite3.mutex.
+*/
+struct Btree {
+ sqlite3 *db; /* The database connection holding this btree */
+ BtShared *pBt; /* Sharable content of this btree */
+ u8 inTrans; /* TRANS_NONE, TRANS_READ or TRANS_WRITE */
+ u8 sharable; /* True if we can share pBt with another db */
+ u8 locked; /* True if db currently has pBt locked */
+ int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */
+ Btree *pNext; /* List of other sharable Btrees from the same db */
+ Btree *pPrev; /* Back pointer of the same list */
+};
+
+/*
+** Btree.inTrans may take one of the following values.
+**
+** If the shared-data extension is enabled, there may be multiple users
+** of the Btree structure. At most one of these may open a write transaction,
+** but any number may have active read transactions.
+*/
+#define TRANS_NONE 0
+#define TRANS_READ 1
+#define TRANS_WRITE 2
+
+/*
+** An instance of this object represents a single database file.
+**
+** A single database file can be in use as the same time by two
+** or more database connections. When two or more connections are
+** sharing the same database file, each connection has it own
+** private Btree object for the file and each of those Btrees points
+** to this one BtShared object. BtShared.nRef is the number of
+** connections currently sharing this database file.
+**
+** Fields in this structure are accessed under the BtShared.mutex
+** mutex, except for nRef and pNext which are accessed under the
+** global SQLITE_MUTEX_STATIC_MASTER mutex. The pPager field
+** may not be modified once it is initially set as long as nRef>0.
+** The pSchema field may be set once under BtShared.mutex and
+** thereafter is unchanged as long as nRef>0.
+*/
+struct BtShared {
+ Pager *pPager; /* The page cache */
+ sqlite3 *db; /* Database connection currently using this Btree */
+ BtCursor *pCursor; /* A list of all open cursors */
+ MemPage *pPage1; /* First page of the database */
+ u8 inStmt; /* True if we are in a statement subtransaction */
+ u8 readOnly; /* True if the underlying file is readonly */
+ u8 maxEmbedFrac; /* Maximum payload as % of total page size */
+ u8 minEmbedFrac; /* Minimum payload as % of total page size */
+ u8 minLeafFrac; /* Minimum leaf payload as % of total page size */
+ u8 pageSizeFixed; /* True if the page size can no longer be changed */
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ u8 autoVacuum; /* True if auto-vacuum is enabled */
+ u8 incrVacuum; /* True if incr-vacuum is enabled */
+ Pgno nTrunc; /* Non-zero if the db will be truncated (incr vacuum) */
+#endif
+ u16 pageSize; /* Total number of bytes on a page */
+ u16 usableSize; /* Number of usable bytes on each page */
+ int maxLocal; /* Maximum local payload in non-LEAFDATA tables */
+ int minLocal; /* Minimum local payload in non-LEAFDATA tables */
+ int maxLeaf; /* Maximum local payload in a LEAFDATA table */
+ int minLeaf; /* Minimum local payload in a LEAFDATA table */
+ u8 inTransaction; /* Transaction state */
+ int nTransaction; /* Number of open transactions (read + write) */
+ void *pSchema; /* Pointer to space allocated by sqlite3BtreeSchema() */
+ void (*xFreeSchema)(void*); /* Destructor for BtShared.pSchema */
+ sqlite3_mutex *mutex; /* Non-recursive mutex required to access this struct */
+ BusyHandler busyHdr; /* The busy handler for this btree */
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ int nRef; /* Number of references to this structure */
+ BtShared *pNext; /* Next on a list of sharable BtShared structs */
+ BtLock *pLock; /* List of locks held on this shared-btree struct */
+ Btree *pExclusive; /* Btree with an EXCLUSIVE lock on the whole db */
+#endif
+ u8 *pTmpSpace; /* BtShared.pageSize bytes of space for tmp use */
+};
+
+/*
+** An instance of the following structure is used to hold information
+** about a cell. The parseCellPtr() function fills in this structure
+** based on information extract from the raw disk page.
+*/
+typedef struct CellInfo CellInfo;
+struct CellInfo {
+ u8 *pCell; /* Pointer to the start of cell content */
+ i64 nKey; /* The key for INTKEY tables, or number of bytes in key */
+ u32 nData; /* Number of bytes of data */
+ u32 nPayload; /* Total amount of payload */
+ u16 nHeader; /* Size of the cell content header in bytes */
+ u16 nLocal; /* Amount of payload held locally */
+ u16 iOverflow; /* Offset to overflow page number. Zero if no overflow */
+ u16 nSize; /* Size of the cell content on the main b-tree page */
+};
+
+/*
+** A cursor is a pointer to a particular entry within a particular
+** b-tree within a database file.
+**
+** The entry is identified by its MemPage and the index in
+** MemPage.aCell[] of the entry.
+**
+** When a single database file can shared by two more database connections,
+** but cursors cannot be shared. Each cursor is associated with a
+** particular database connection identified BtCursor.pBtree.db.
+**
+** Fields in this structure are accessed under the BtShared.mutex
+** found at self->pBt->mutex.
+*/
+struct BtCursor {
+ Btree *pBtree; /* The Btree to which this cursor belongs */
+ BtShared *pBt; /* The BtShared this cursor points to */
+ BtCursor *pNext, *pPrev; /* Forms a linked list of all cursors */
+ struct KeyInfo *pKeyInfo; /* Argument passed to comparison function */
+ Pgno pgnoRoot; /* The root page of this tree */
+ MemPage *pPage; /* Page that contains the entry */
+ int idx; /* Index of the entry in pPage->aCell[] */
+ CellInfo info; /* A parse of the cell we are pointing at */
+ u8 wrFlag; /* True if writable */
+ u8 atLast; /* Cursor pointing to the last entry */
+ u8 validNKey; /* True if info.nKey is valid */
+ u8 eState; /* One of the CURSOR_XXX constants (see below) */
+ void *pKey; /* Saved key that was cursor's last known position */
+ i64 nKey; /* Size of pKey, or last integer key */
+ int skip; /* (skip<0) -> Prev() is a no-op. (skip>0) -> Next() is */
+#ifndef SQLITE_OMIT_INCRBLOB
+ u8 isIncrblobHandle; /* True if this cursor is an incr. io handle */
+ Pgno *aOverflow; /* Cache of overflow page locations */
+#endif
+};
+
+/*
+** Potential values for BtCursor.eState.
+**
+** CURSOR_VALID:
+** Cursor points to a valid entry. getPayload() etc. may be called.
+**
+** CURSOR_INVALID:
+** Cursor does not point to a valid entry. This can happen (for example)
+** because the table is empty or because BtreeCursorFirst() has not been
+** called.
+**
+** CURSOR_REQUIRESEEK:
+** The table that this cursor was opened on still exists, but has been
+** modified since the cursor was last used. The cursor position is saved
+** in variables BtCursor.pKey and BtCursor.nKey. When a cursor is in
+** this state, restoreOrClearCursorPosition() can be called to attempt to
+** seek the cursor to the saved position.
+**
+** CURSOR_FAULT:
+** A unrecoverable error (an I/O error or a malloc failure) has occurred
+** on a different connection that shares the BtShared cache with this
+** cursor. The error has left the cache in an inconsistent state.
+** Do nothing else with this cursor. Any attempt to use the cursor
+** should return the error code stored in BtCursor.skip
+*/
+#define CURSOR_INVALID 0
+#define CURSOR_VALID 1
+#define CURSOR_REQUIRESEEK 2
+#define CURSOR_FAULT 3
+
+/*
+** The TRACE macro will print high-level status information about the
+** btree operation when the global variable sqlite3BtreeTrace is
+** enabled.
+*/
+#if SQLITE_TEST
+# define TRACE(X) if( sqlite3BtreeTrace ){ printf X; fflush(stdout); }
+#else
+# define TRACE(X)
+#endif
+
+/* The database page the PENDING_BYTE occupies. This page is never used.
+** TODO: This macro is very similary to PAGER_MJ_PGNO() in pager.c. They
+** should possibly be consolidated (presumably in pager.h).
+**
+** If disk I/O is omitted (meaning that the database is stored purely
+** in memory) then there is no pending byte.
+*/
+#ifdef SQLITE_OMIT_DISKIO
+# define PENDING_BYTE_PAGE(pBt) 0x7fffffff
+#else
+# define PENDING_BYTE_PAGE(pBt) ((PENDING_BYTE/(pBt)->pageSize)+1)
+#endif
+
+/*
+** A linked list of the following structures is stored at BtShared.pLock.
+** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor
+** is opened on the table with root page BtShared.iTable. Locks are removed
+** from this list when a transaction is committed or rolled back, or when
+** a btree handle is closed.
+*/
+struct BtLock {
+ Btree *pBtree; /* Btree handle holding this lock */
+ Pgno iTable; /* Root page of table */
+ u8 eLock; /* READ_LOCK or WRITE_LOCK */
+ BtLock *pNext; /* Next in BtShared.pLock list */
+};
+
+/* Candidate values for BtLock.eLock */
+#define READ_LOCK 1
+#define WRITE_LOCK 2
+
+/*
+** These macros define the location of the pointer-map entry for a
+** database page. The first argument to each is the number of usable
+** bytes on each page of the database (often 1024). The second is the
+** page number to look up in the pointer map.
+**
+** PTRMAP_PAGENO returns the database page number of the pointer-map
+** page that stores the required pointer. PTRMAP_PTROFFSET returns
+** the offset of the requested map entry.
+**
+** If the pgno argument passed to PTRMAP_PAGENO is a pointer-map page,
+** then pgno is returned. So (pgno==PTRMAP_PAGENO(pgsz, pgno)) can be
+** used to test if pgno is a pointer-map page. PTRMAP_ISPAGE implements
+** this test.
+*/
+#define PTRMAP_PAGENO(pBt, pgno) ptrmapPageno(pBt, pgno)
+#define PTRMAP_PTROFFSET(pBt, pgno) (5*(pgno-ptrmapPageno(pBt, pgno)-1))
+#define PTRMAP_ISPAGE(pBt, pgno) (PTRMAP_PAGENO((pBt),(pgno))==(pgno))
+
+/*
+** The pointer map is a lookup table that identifies the parent page for
+** each child page in the database file. The parent page is the page that
+** contains a pointer to the child. Every page in the database contains
+** 0 or 1 parent pages. (In this context 'database page' refers
+** to any page that is not part of the pointer map itself.) Each pointer map
+** entry consists of a single byte 'type' and a 4 byte parent page number.
+** The PTRMAP_XXX identifiers below are the valid types.
+**
+** The purpose of the pointer map is to facility moving pages from one
+** position in the file to another as part of autovacuum. When a page
+** is moved, the pointer in its parent must be updated to point to the
+** new location. The pointer map is used to locate the parent page quickly.
+**
+** PTRMAP_ROOTPAGE: The database page is a root-page. The page-number is not
+** used in this case.
+**
+** PTRMAP_FREEPAGE: The database page is an unused (free) page. The page-number
+** is not used in this case.
+**
+** PTRMAP_OVERFLOW1: The database page is the first page in a list of
+** overflow pages. The page number identifies the page that
+** contains the cell with a pointer to this overflow page.
+**
+** PTRMAP_OVERFLOW2: The database page is the second or later page in a list of
+** overflow pages. The page-number identifies the previous
+** page in the overflow page list.
+**
+** PTRMAP_BTREE: The database page is a non-root btree page. The page number
+** identifies the parent page in the btree.
+*/
+#define PTRMAP_ROOTPAGE 1
+#define PTRMAP_FREEPAGE 2
+#define PTRMAP_OVERFLOW1 3
+#define PTRMAP_OVERFLOW2 4
+#define PTRMAP_BTREE 5
+
+/* A bunch of assert() statements to check the transaction state variables
+** of handle p (type Btree*) are internally consistent.
+*/
+#define btreeIntegrity(p) \
+ assert( p->pBt->inTransaction!=TRANS_NONE || p->pBt->nTransaction==0 ); \
+ assert( p->pBt->inTransaction>=p->inTrans );
+
+
+/*
+** The ISAUTOVACUUM macro is used within balance_nonroot() to determine
+** if the database supports auto-vacuum or not. Because it is used
+** within an expression that is an argument to another macro
+** (sqliteMallocRaw), it is not possible to use conditional compilation.
+** So, this macro is defined instead.
+*/
+#ifndef SQLITE_OMIT_AUTOVACUUM
+#define ISAUTOVACUUM (pBt->autoVacuum)
+#else
+#define ISAUTOVACUUM 0
+#endif
+
+
+/*
+** This structure is passed around through all the sanity checking routines
+** in order to keep track of some global state information.
+*/
+typedef struct IntegrityCk IntegrityCk;
+struct IntegrityCk {
+ BtShared *pBt; /* The tree being checked out */
+ Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */
+ int nPage; /* Number of pages in the database */
+ int *anRef; /* Number of times each page is referenced */
+ int mxErr; /* Stop accumulating errors when this reaches zero */
+ char *zErrMsg; /* An error message. NULL if no errors seen. */
+ int nErr; /* Number of messages written to zErrMsg so far */
+};
+
+/*
+** Read or write a two- and four-byte big-endian integer values.
+*/
+#define get2byte(x) ((x)[0]<<8 | (x)[1])
+#define put2byte(p,v) ((p)[0] = (v)>>8, (p)[1] = (v))
+#define get4byte sqlite3Get4byte
+#define put4byte sqlite3Put4byte
+
+/*
+** Internal routines that should be accessed by the btree layer only.
+*/
+SQLITE_PRIVATE int sqlite3BtreeGetPage(BtShared*, Pgno, MemPage**, int);
+SQLITE_PRIVATE int sqlite3BtreeInitPage(MemPage *pPage, MemPage *pParent);
+SQLITE_PRIVATE void sqlite3BtreeParseCellPtr(MemPage*, u8*, CellInfo*);
+SQLITE_PRIVATE void sqlite3BtreeParseCell(MemPage*, int, CellInfo*);
+#ifdef SQLITE_TEST
+SQLITE_PRIVATE u8 *sqlite3BtreeFindCell(MemPage *pPage, int iCell);
+#endif
+SQLITE_PRIVATE int sqlite3BtreeRestoreOrClearCursorPosition(BtCursor *pCur);
+SQLITE_PRIVATE void sqlite3BtreeGetTempCursor(BtCursor *pCur, BtCursor *pTempCur);
+SQLITE_PRIVATE void sqlite3BtreeReleaseTempCursor(BtCursor *pCur);
+SQLITE_PRIVATE int sqlite3BtreeIsRootPage(MemPage *pPage);
+SQLITE_PRIVATE void sqlite3BtreeMoveToParent(BtCursor *pCur);
+
+/************** End of btreeInt.h ********************************************/
+/************** Continuing where we left off in btmutex.c ********************/
+#if SQLITE_THREADSAFE && !defined(SQLITE_OMIT_SHARED_CACHE)
+
+
+/*
+** Enter a mutex on the given BTree object.
+**
+** If the object is not sharable, then no mutex is ever required
+** and this routine is a no-op. The underlying mutex is non-recursive.
+** But we keep a reference count in Btree.wantToLock so the behavior
+** of this interface is recursive.
+**
+** To avoid deadlocks, multiple Btrees are locked in the same order
+** by all database connections. The p->pNext is a list of other
+** Btrees belonging to the same database connection as the p Btree
+** which need to be locked after p. If we cannot get a lock on
+** p, then first unlock all of the others on p->pNext, then wait
+** for the lock to become available on p, then relock all of the
+** subsequent Btrees that desire a lock.
+*/
+SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){
+ Btree *pLater;
+
+ /* Some basic sanity checking on the Btree. The list of Btrees
+ ** connected by pNext and pPrev should be in sorted order by
+ ** Btree.pBt value. All elements of the list should belong to
+ ** the same connection. Only shared Btrees are on the list. */
+ assert( p->pNext==0 || p->pNext->pBt>p->pBt );
+ assert( p->pPrev==0 || p->pPrev->pBt<p->pBt );
+ assert( p->pNext==0 || p->pNext->db==p->db );
+ assert( p->pPrev==0 || p->pPrev->db==p->db );
+ assert( p->sharable || (p->pNext==0 && p->pPrev==0) );
+
+ /* Check for locking consistency */
+ assert( !p->locked || p->wantToLock>0 );
+ assert( p->sharable || p->wantToLock==0 );
+
+ /* We should already hold a lock on the database connection */
+ assert( sqlite3_mutex_held(p->db->mutex) );
+
+ if( !p->sharable ) return;
+ p->wantToLock++;
+ if( p->locked ) return;
+
+#ifndef SQLITE_MUTEX_NOOP
+ /* In most cases, we should be able to acquire the lock we
+ ** want without having to go throught the ascending lock
+ ** procedure that follows. Just be sure not to block.
+ */
+ if( sqlite3_mutex_try(p->pBt->mutex)==SQLITE_OK ){
+ p->locked = 1;
+ return;
+ }
+
+ /* To avoid deadlock, first release all locks with a larger
+ ** BtShared address. Then acquire our lock. Then reacquire
+ ** the other BtShared locks that we used to hold in ascending
+ ** order.
+ */
+ for(pLater=p->pNext; pLater; pLater=pLater->pNext){
+ assert( pLater->sharable );
+ assert( pLater->pNext==0 || pLater->pNext->pBt>pLater->pBt );
+ assert( !pLater->locked || pLater->wantToLock>0 );
+ if( pLater->locked ){
+ sqlite3_mutex_leave(pLater->pBt->mutex);
+ pLater->locked = 0;
+ }
+ }
+ sqlite3_mutex_enter(p->pBt->mutex);
+ p->locked = 1;
+ for(pLater=p->pNext; pLater; pLater=pLater->pNext){
+ if( pLater->wantToLock ){
+ sqlite3_mutex_enter(pLater->pBt->mutex);
+ pLater->locked = 1;
+ }
+ }
+#endif /* SQLITE_MUTEX_NOOP */
+}
+
+/*
+** Exit the recursive mutex on a Btree.
+*/
+SQLITE_PRIVATE void sqlite3BtreeLeave(Btree *p){
+ if( p->sharable ){
+ assert( p->wantToLock>0 );
+ p->wantToLock--;
+ if( p->wantToLock==0 ){
+ assert( p->locked );
+ sqlite3_mutex_leave(p->pBt->mutex);
+ p->locked = 0;
+ }
+ }
+}
+
+#ifndef NDEBUG
+/*
+** Return true if the BtShared mutex is held on the btree.
+**
+** This routine makes no determination one why or another if the
+** database connection mutex is held.
+**
+** This routine is used only from within assert() statements.
+*/
+SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){
+ return (p->sharable==0 ||
+ (p->locked && p->wantToLock && sqlite3_mutex_held(p->pBt->mutex)));
+}
+#endif
+
+
+#ifndef SQLITE_OMIT_INCRBLOB
+/*
+** Enter and leave a mutex on a Btree given a cursor owned by that
+** Btree. These entry points are used by incremental I/O and can be
+** omitted if that module is not used.
+*/
+SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor *pCur){
+ sqlite3BtreeEnter(pCur->pBtree);
+}
+SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor *pCur){
+ sqlite3BtreeLeave(pCur->pBtree);
+}
+#endif /* SQLITE_OMIT_INCRBLOB */
+
+
+/*
+** Enter the mutex on every Btree associated with a database
+** connection. This is needed (for example) prior to parsing
+** a statement since we will be comparing table and column names
+** against all schemas and we do not want those schemas being
+** reset out from under us.
+**
+** There is a corresponding leave-all procedures.
+**
+** Enter the mutexes in accending order by BtShared pointer address
+** to avoid the possibility of deadlock when two threads with
+** two or more btrees in common both try to lock all their btrees
+** at the same instant.
+*/
+SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){
+ int i;
+ Btree *p, *pLater;
+ assert( sqlite3_mutex_held(db->mutex) );
+ for(i=0; i<db->nDb; i++){
+ p = db->aDb[i].pBt;
+ if( p && p->sharable ){
+ p->wantToLock++;
+ if( !p->locked ){
+ assert( p->wantToLock==1 );
+ while( p->pPrev ) p = p->pPrev;
+ while( p->locked && p->pNext ) p = p->pNext;
+ for(pLater = p->pNext; pLater; pLater=pLater->pNext){
+ if( pLater->locked ){
+ sqlite3_mutex_leave(pLater->pBt->mutex);
+ pLater->locked = 0;
+ }
+ }
+ while( p ){
+ sqlite3_mutex_enter(p->pBt->mutex);
+ p->locked++;
+ p = p->pNext;
+ }
+ }
+ }
+ }
+}
+SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){
+ int i;
+ Btree *p;
+ assert( sqlite3_mutex_held(db->mutex) );
+ for(i=0; i<db->nDb; i++){
+ p = db->aDb[i].pBt;
+ if( p && p->sharable ){
+ assert( p->wantToLock>0 );
+ p->wantToLock--;
+ if( p->wantToLock==0 ){
+ assert( p->locked );
+ sqlite3_mutex_leave(p->pBt->mutex);
+ p->locked = 0;
+ }
+ }
+ }
+}
+
+#ifndef NDEBUG
+/*
+** Return true if the current thread holds the database connection
+** mutex and all required BtShared mutexes.
+**
+** This routine is used inside assert() statements only.
+*/
+SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){
+ int i;
+ if( !sqlite3_mutex_held(db->mutex) ){
+ return 0;
+ }
+ for(i=0; i<db->nDb; i++){
+ Btree *p;
+ p = db->aDb[i].pBt;
+ if( p && p->sharable &&
+ (p->wantToLock==0 || !sqlite3_mutex_held(p->pBt->mutex)) ){
+ return 0;
+ }
+ }
+ return 1;
+}
+#endif /* NDEBUG */
+
+/*
+** Potentially dd a new Btree pointer to a BtreeMutexArray.
+** Really only add the Btree if it can possibly be shared with
+** another database connection.
+**
+** The Btrees are kept in sorted order by pBtree->pBt. That
+** way when we go to enter all the mutexes, we can enter them
+** in order without every having to backup and retry and without
+** worrying about deadlock.
+**
+** The number of shared btrees will always be small (usually 0 or 1)
+** so an insertion sort is an adequate algorithm here.
+*/
+SQLITE_PRIVATE void sqlite3BtreeMutexArrayInsert(BtreeMutexArray *pArray, Btree *pBtree){
+ int i, j;
+ BtShared *pBt;
+ if( pBtree==0 || pBtree->sharable==0 ) return;
+#ifndef NDEBUG
+ {
+ for(i=0; i<pArray->nMutex; i++){
+ assert( pArray->aBtree[i]!=pBtree );
+ }
+ }
+#endif
+ assert( pArray->nMutex>=0 );
+ assert( pArray->nMutex<sizeof(pArray->aBtree)/sizeof(pArray->aBtree[0])-1 );
+ pBt = pBtree->pBt;
+ for(i=0; i<pArray->nMutex; i++){
+ assert( pArray->aBtree[i]!=pBtree );
+ if( pArray->aBtree[i]->pBt>pBt ){
+ for(j=pArray->nMutex; j>i; j--){
+ pArray->aBtree[j] = pArray->aBtree[j-1];
+ }
+ pArray->aBtree[i] = pBtree;
+ pArray->nMutex++;
+ return;
+ }
+ }
+ pArray->aBtree[pArray->nMutex++] = pBtree;
+}
+
+/*
+** Enter the mutex of every btree in the array. This routine is
+** called at the beginning of sqlite3VdbeExec(). The mutexes are
+** exited at the end of the same function.
+*/
+SQLITE_PRIVATE void sqlite3BtreeMutexArrayEnter(BtreeMutexArray *pArray){
+ int i;
+ for(i=0; i<pArray->nMutex; i++){
+ Btree *p = pArray->aBtree[i];
+ /* Some basic sanity checking */
+ assert( i==0 || pArray->aBtree[i-1]->pBt<p->pBt );
+ assert( !p->locked || p->wantToLock>0 );
+
+ /* We should already hold a lock on the database connection */
+ assert( sqlite3_mutex_held(p->db->mutex) );
+
+ p->wantToLock++;
+ if( !p->locked && p->sharable ){
+ sqlite3_mutex_enter(p->pBt->mutex);
+ p->locked = 1;
+ }
+ }
+}
+
+/*
+** Leave the mutex of every btree in the group.
+*/
+SQLITE_PRIVATE void sqlite3BtreeMutexArrayLeave(BtreeMutexArray *pArray){
+ int i;
+ for(i=0; i<pArray->nMutex; i++){
+ Btree *p = pArray->aBtree[i];
+ /* Some basic sanity checking */
+ assert( i==0 || pArray->aBtree[i-1]->pBt<p->pBt );
+ assert( p->locked || !p->sharable );
+ assert( p->wantToLock>0 );
+
+ /* We should already hold a lock on the database connection */
+ assert( sqlite3_mutex_held(p->db->mutex) );
+
+ p->wantToLock--;
+ if( p->wantToLock==0 && p->locked ){
+ sqlite3_mutex_leave(p->pBt->mutex);
+ p->locked = 0;
+ }
+ }
+}
+
+
+#endif /* SQLITE_THREADSAFE && !SQLITE_OMIT_SHARED_CACHE */
+
+/************** End of btmutex.c *********************************************/
+/************** Begin file btree.c *******************************************/
+/*
+** 2004 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** $Id: btree.c,v 1.458 2008/05/09 16:57:51 danielk1977 Exp $
+**
+** This file implements a external (disk-based) database using BTrees.
+** See the header comment on "btreeInt.h" for additional information.
+** Including a description of file format and an overview of operation.
+*/
+
+/*
+** The header string that appears at the beginning of every
+** SQLite database.
+*/
+static const char zMagicHeader[] = SQLITE_FILE_HEADER;
+
+/*
+** Set this global variable to 1 to enable tracing using the TRACE
+** macro.
+*/
+#if SQLITE_TEST
+int sqlite3BtreeTrace=0; /* True to enable tracing */
+#endif
+
+
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** A flag to indicate whether or not shared cache is enabled. Also,
+** a list of BtShared objects that are eligible for participation
+** in shared cache. The variables have file scope during normal builds,
+** but the test harness needs to access these variables so we make them
+** global for test builds.
+*/
+#ifdef SQLITE_TEST
+SQLITE_PRIVATE BtShared *sqlite3SharedCacheList = 0;
+SQLITE_PRIVATE int sqlite3SharedCacheEnabled = 0;
+#else
+static BtShared *sqlite3SharedCacheList = 0;
+static int sqlite3SharedCacheEnabled = 0;
+#endif
+#endif /* SQLITE_OMIT_SHARED_CACHE */
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** Enable or disable the shared pager and schema features.
+**
+** This routine has no effect on existing database connections.
+** The shared cache setting effects only future calls to
+** sqlite3_open(), sqlite3_open16(), or sqlite3_open_v2().
+*/
+SQLITE_API int sqlite3_enable_shared_cache(int enable){
+ sqlite3SharedCacheEnabled = enable;
+ return SQLITE_OK;
+}
+#endif
+
+
+/*
+** Forward declaration
+*/
+static int checkReadLocks(Btree*,Pgno,BtCursor*);
+
+
+#ifdef SQLITE_OMIT_SHARED_CACHE
+ /*
+ ** The functions queryTableLock(), lockTable() and unlockAllTables()
+ ** manipulate entries in the BtShared.pLock linked list used to store
+ ** shared-cache table level locks. If the library is compiled with the
+ ** shared-cache feature disabled, then there is only ever one user
+ ** of each BtShared structure and so this locking is not necessary.
+ ** So define the lock related functions as no-ops.
+ */
+ #define queryTableLock(a,b,c) SQLITE_OK
+ #define lockTable(a,b,c) SQLITE_OK
+ #define unlockAllTables(a)
+#endif
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** Query to see if btree handle p may obtain a lock of type eLock
+** (READ_LOCK or WRITE_LOCK) on the table with root-page iTab. Return
+** SQLITE_OK if the lock may be obtained (by calling lockTable()), or
+** SQLITE_LOCKED if not.
+*/
+static int queryTableLock(Btree *p, Pgno iTab, u8 eLock){
+ BtShared *pBt = p->pBt;
+ BtLock *pIter;
+
+ assert( sqlite3BtreeHoldsMutex(p) );
+
+ /* This is a no-op if the shared-cache is not enabled */
+ if( !p->sharable ){
+ return SQLITE_OK;
+ }
+
+ /* If some other connection is holding an exclusive lock, the
+ ** requested lock may not be obtained.
+ */
+ if( pBt->pExclusive && pBt->pExclusive!=p ){
+ return SQLITE_LOCKED;
+ }
+
+ /* This (along with lockTable()) is where the ReadUncommitted flag is
+ ** dealt with. If the caller is querying for a read-lock and the flag is
+ ** set, it is unconditionally granted - even if there are write-locks
+ ** on the table. If a write-lock is requested, the ReadUncommitted flag
+ ** is not considered.
+ **
+ ** In function lockTable(), if a read-lock is demanded and the
+ ** ReadUncommitted flag is set, no entry is added to the locks list
+ ** (BtShared.pLock).
+ **
+ ** To summarize: If the ReadUncommitted flag is set, then read cursors do
+ ** not create or respect table locks. The locking procedure for a
+ ** write-cursor does not change.
+ */
+ if(
+ !p->db ||
+ 0==(p->db->flags&SQLITE_ReadUncommitted) ||
+ eLock==WRITE_LOCK ||
+ iTab==MASTER_ROOT
+ ){
+ for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
+ if( pIter->pBtree!=p && pIter->iTable==iTab &&
+ (pIter->eLock!=eLock || eLock!=READ_LOCK) ){
+ return SQLITE_LOCKED;
+ }
+ }
+ }
+ return SQLITE_OK;
+}
+#endif /* !SQLITE_OMIT_SHARED_CACHE */
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** Add a lock on the table with root-page iTable to the shared-btree used
+** by Btree handle p. Parameter eLock must be either READ_LOCK or
+** WRITE_LOCK.
+**
+** SQLITE_OK is returned if the lock is added successfully. SQLITE_BUSY and
+** SQLITE_NOMEM may also be returned.
+*/
+static int lockTable(Btree *p, Pgno iTable, u8 eLock){
+ BtShared *pBt = p->pBt;
+ BtLock *pLock = 0;
+ BtLock *pIter;
+
+ assert( sqlite3BtreeHoldsMutex(p) );
+
+ /* This is a no-op if the shared-cache is not enabled */
+ if( !p->sharable ){
+ return SQLITE_OK;
+ }
+
+ assert( SQLITE_OK==queryTableLock(p, iTable, eLock) );
+
+ /* If the read-uncommitted flag is set and a read-lock is requested,
+ ** return early without adding an entry to the BtShared.pLock list. See
+ ** comment in function queryTableLock() for more info on handling
+ ** the ReadUncommitted flag.
+ */
+ if(
+ (p->db) &&
+ (p->db->flags&SQLITE_ReadUncommitted) &&
+ (eLock==READ_LOCK) &&
+ iTable!=MASTER_ROOT
+ ){
+ return SQLITE_OK;
+ }
+
+ /* First search the list for an existing lock on this table. */
+ for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
+ if( pIter->iTable==iTable && pIter->pBtree==p ){
+ pLock = pIter;
+ break;
+ }
+ }
+
+ /* If the above search did not find a BtLock struct associating Btree p
+ ** with table iTable, allocate one and link it into the list.
+ */
+ if( !pLock ){
+ pLock = (BtLock *)sqlite3MallocZero(sizeof(BtLock));
+ if( !pLock ){
+ return SQLITE_NOMEM;
+ }
+ pLock->iTable = iTable;
+ pLock->pBtree = p;
+ pLock->pNext = pBt->pLock;
+ pBt->pLock = pLock;
+ }
+
+ /* Set the BtLock.eLock variable to the maximum of the current lock
+ ** and the requested lock. This means if a write-lock was already held
+ ** and a read-lock requested, we don't incorrectly downgrade the lock.
+ */
+ assert( WRITE_LOCK>READ_LOCK );
+ if( eLock>pLock->eLock ){
+ pLock->eLock = eLock;
+ }
+
+ return SQLITE_OK;
+}
+#endif /* !SQLITE_OMIT_SHARED_CACHE */
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** Release all the table locks (locks obtained via calls to the lockTable()
+** procedure) held by Btree handle p.
+*/
+static void unlockAllTables(Btree *p){
+ BtShared *pBt = p->pBt;
+ BtLock **ppIter = &pBt->pLock;
+
+ assert( sqlite3BtreeHoldsMutex(p) );
+ assert( p->sharable || 0==*ppIter );
+
+ while( *ppIter ){
+ BtLock *pLock = *ppIter;
+ assert( pBt->pExclusive==0 || pBt->pExclusive==pLock->pBtree );
+ if( pLock->pBtree==p ){
+ *ppIter = pLock->pNext;
+ sqlite3_free(pLock);
+ }else{
+ ppIter = &pLock->pNext;
+ }
+ }
+
+ if( pBt->pExclusive==p ){
+ pBt->pExclusive = 0;
+ }
+}
+#endif /* SQLITE_OMIT_SHARED_CACHE */
+
+static void releasePage(MemPage *pPage); /* Forward reference */
+
+/*
+** Verify that the cursor holds a mutex on the BtShared
+*/
+#ifndef NDEBUG
+static int cursorHoldsMutex(BtCursor *p){
+ return sqlite3_mutex_held(p->pBt->mutex);
+}
+#endif
+
+
+#ifndef SQLITE_OMIT_INCRBLOB
+/*
+** Invalidate the overflow page-list cache for cursor pCur, if any.
+*/
+static void invalidateOverflowCache(BtCursor *pCur){
+ assert( cursorHoldsMutex(pCur) );
+ sqlite3_free(pCur->aOverflow);
+ pCur->aOverflow = 0;
+}
+
+/*
+** Invalidate the overflow page-list cache for all cursors opened
+** on the shared btree structure pBt.
+*/
+static void invalidateAllOverflowCache(BtShared *pBt){
+ BtCursor *p;
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ for(p=pBt->pCursor; p; p=p->pNext){
+ invalidateOverflowCache(p);
+ }
+}
+#else
+ #define invalidateOverflowCache(x)
+ #define invalidateAllOverflowCache(x)
+#endif
+
+/*
+** Save the current cursor position in the variables BtCursor.nKey
+** and BtCursor.pKey. The cursor's state is set to CURSOR_REQUIRESEEK.
+*/
+static int saveCursorPosition(BtCursor *pCur){
+ int rc;
+
+ assert( CURSOR_VALID==pCur->eState );
+ assert( 0==pCur->pKey );
+ assert( cursorHoldsMutex(pCur) );
+
+ rc = sqlite3BtreeKeySize(pCur, &pCur->nKey);
+
+ /* If this is an intKey table, then the above call to BtreeKeySize()
+ ** stores the integer key in pCur->nKey. In this case this value is
+ ** all that is required. Otherwise, if pCur is not open on an intKey
+ ** table, then malloc space for and store the pCur->nKey bytes of key
+ ** data.
+ */
+ if( rc==SQLITE_OK && 0==pCur->pPage->intKey){
+ void *pKey = sqlite3_malloc(pCur->nKey);
+ if( pKey ){
+ rc = sqlite3BtreeKey(pCur, 0, pCur->nKey, pKey);
+ if( rc==SQLITE_OK ){
+ pCur->pKey = pKey;
+ }else{
+ sqlite3_free(pKey);
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ assert( !pCur->pPage->intKey || !pCur->pKey );
+
+ if( rc==SQLITE_OK ){
+ releasePage(pCur->pPage);
+ pCur->pPage = 0;
+ pCur->eState = CURSOR_REQUIRESEEK;
+ }
+
+ invalidateOverflowCache(pCur);
+ return rc;
+}
+
+/*
+** Save the positions of all cursors except pExcept open on the table
+** with root-page iRoot. Usually, this is called just before cursor
+** pExcept is used to modify the table (BtreeDelete() or BtreeInsert()).
+*/
+static int saveAllCursors(BtShared *pBt, Pgno iRoot, BtCursor *pExcept){
+ BtCursor *p;
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( pExcept==0 || pExcept->pBt==pBt );
+ for(p=pBt->pCursor; p; p=p->pNext){
+ if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) &&
+ p->eState==CURSOR_VALID ){
+ int rc = saveCursorPosition(p);
+ if( SQLITE_OK!=rc ){
+ return rc;
+ }
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Clear the current cursor position.
+*/
+static void clearCursorPosition(BtCursor *pCur){
+ assert( cursorHoldsMutex(pCur) );
+ sqlite3_free(pCur->pKey);
+ pCur->pKey = 0;
+ pCur->eState = CURSOR_INVALID;
+}
+
+/*
+** Restore the cursor to the position it was in (or as close to as possible)
+** when saveCursorPosition() was called. Note that this call deletes the
+** saved position info stored by saveCursorPosition(), so there can be
+** at most one effective restoreOrClearCursorPosition() call after each
+** saveCursorPosition().
+**
+** If the second argument argument - doSeek - is false, then instead of
+** returning the cursor to its saved position, any saved position is deleted
+** and the cursor state set to CURSOR_INVALID.
+*/
+SQLITE_PRIVATE int sqlite3BtreeRestoreOrClearCursorPosition(BtCursor *pCur){
+ int rc;
+ assert( cursorHoldsMutex(pCur) );
+ assert( pCur->eState>=CURSOR_REQUIRESEEK );
+ if( pCur->eState==CURSOR_FAULT ){
+ return pCur->skip;
+ }
+#ifndef SQLITE_OMIT_INCRBLOB
+ if( pCur->isIncrblobHandle ){
+ return SQLITE_ABORT;
+ }
+#endif
+ pCur->eState = CURSOR_INVALID;
+ rc = sqlite3BtreeMoveto(pCur, pCur->pKey, 0, pCur->nKey, 0, &pCur->skip);
+ if( rc==SQLITE_OK ){
+ sqlite3_free(pCur->pKey);
+ pCur->pKey = 0;
+ assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_INVALID );
+ }
+ return rc;
+}
+
+#define restoreOrClearCursorPosition(p) \
+ (p->eState>=CURSOR_REQUIRESEEK ? \
+ sqlite3BtreeRestoreOrClearCursorPosition(p) : \
+ SQLITE_OK)
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+/*
+** Given a page number of a regular database page, return the page
+** number for the pointer-map page that contains the entry for the
+** input page number.
+*/
+static Pgno ptrmapPageno(BtShared *pBt, Pgno pgno){
+ int nPagesPerMapPage, iPtrMap, ret;
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ nPagesPerMapPage = (pBt->usableSize/5)+1;
+ iPtrMap = (pgno-2)/nPagesPerMapPage;
+ ret = (iPtrMap*nPagesPerMapPage) + 2;
+ if( ret==PENDING_BYTE_PAGE(pBt) ){
+ ret++;
+ }
+ return ret;
+}
+
+/*
+** Write an entry into the pointer map.
+**
+** This routine updates the pointer map entry for page number 'key'
+** so that it maps to type 'eType' and parent page number 'pgno'.
+** An error code is returned if something goes wrong, otherwise SQLITE_OK.
+*/
+static int ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent){
+ DbPage *pDbPage; /* The pointer map page */
+ u8 *pPtrmap; /* The pointer map data */
+ Pgno iPtrmap; /* The pointer map page number */
+ int offset; /* Offset in pointer map page */
+ int rc;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ /* The master-journal page number must never be used as a pointer map page */
+ assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );
+
+ assert( pBt->autoVacuum );
+ if( key==0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ iPtrmap = PTRMAP_PAGENO(pBt, key);
+ rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ offset = PTRMAP_PTROFFSET(pBt, key);
+ pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
+
+ if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
+ TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent));
+ rc = sqlite3PagerWrite(pDbPage);
+ if( rc==SQLITE_OK ){
+ pPtrmap[offset] = eType;
+ put4byte(&pPtrmap[offset+1], parent);
+ }
+ }
+
+ sqlite3PagerUnref(pDbPage);
+ return rc;
+}
+
+/*
+** Read an entry from the pointer map.
+**
+** This routine retrieves the pointer map entry for page 'key', writing
+** the type and parent page number to *pEType and *pPgno respectively.
+** An error code is returned if something goes wrong, otherwise SQLITE_OK.
+*/
+static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
+ DbPage *pDbPage; /* The pointer map page */
+ int iPtrmap; /* Pointer map page index */
+ u8 *pPtrmap; /* Pointer map page data */
+ int offset; /* Offset of entry in pointer map */
+ int rc;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+
+ iPtrmap = PTRMAP_PAGENO(pBt, key);
+ rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage);
+ if( rc!=0 ){
+ return rc;
+ }
+ pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
+
+ offset = PTRMAP_PTROFFSET(pBt, key);
+ assert( pEType!=0 );
+ *pEType = pPtrmap[offset];
+ if( pPgno ) *pPgno = get4byte(&pPtrmap[offset+1]);
+
+ sqlite3PagerUnref(pDbPage);
+ if( *pEType<1 || *pEType>5 ) return SQLITE_CORRUPT_BKPT;
+ return SQLITE_OK;
+}
+
+#endif /* SQLITE_OMIT_AUTOVACUUM */
+
+/*
+** Given a btree page and a cell index (0 means the first cell on
+** the page, 1 means the second cell, and so forth) return a pointer
+** to the cell content.
+**
+** This routine works only for pages that do not contain overflow cells.
+*/
+#define findCell(pPage, iCell) \
+ ((pPage)->aData + get2byte(&(pPage)->aData[(pPage)->cellOffset+2*(iCell)]))
+#ifdef SQLITE_TEST
+SQLITE_PRIVATE u8 *sqlite3BtreeFindCell(MemPage *pPage, int iCell){
+ assert( iCell>=0 );
+ assert( iCell<get2byte(&pPage->aData[pPage->hdrOffset+3]) );
+ return findCell(pPage, iCell);
+}
+#endif
+
+/*
+** This a more complex version of sqlite3BtreeFindCell() that works for
+** pages that do contain overflow cells. See insert
+*/
+static u8 *findOverflowCell(MemPage *pPage, int iCell){
+ int i;
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ for(i=pPage->nOverflow-1; i>=0; i--){
+ int k;
+ struct _OvflCell *pOvfl;
+ pOvfl = &pPage->aOvfl[i];
+ k = pOvfl->idx;
+ if( k<=iCell ){
+ if( k==iCell ){
+ return pOvfl->pCell;
+ }
+ iCell--;
+ }
+ }
+ return findCell(pPage, iCell);
+}
+
+/*
+** Parse a cell content block and fill in the CellInfo structure. There
+** are two versions of this function. sqlite3BtreeParseCell() takes a
+** cell index as the second argument and sqlite3BtreeParseCellPtr()
+** takes a pointer to the body of the cell as its second argument.
+**
+** Within this file, the parseCell() macro can be called instead of
+** sqlite3BtreeParseCellPtr(). Using some compilers, this will be faster.
+*/
+SQLITE_PRIVATE void sqlite3BtreeParseCellPtr(
+ MemPage *pPage, /* Page containing the cell */
+ u8 *pCell, /* Pointer to the cell text. */
+ CellInfo *pInfo /* Fill in this structure */
+){
+ int n; /* Number bytes in cell content header */
+ u32 nPayload; /* Number of bytes of cell payload */
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+
+ pInfo->pCell = pCell;
+ assert( pPage->leaf==0 || pPage->leaf==1 );
+ n = pPage->childPtrSize;
+ assert( n==4-4*pPage->leaf );
+ if( pPage->hasData ){
+ n += getVarint32(&pCell[n], nPayload);
+ }else{
+ nPayload = 0;
+ }
+ pInfo->nData = nPayload;
+ if( pPage->intKey ){
+ n += getVarint(&pCell[n], (u64 *)&pInfo->nKey);
+ }else{
+ u32 x;
+ n += getVarint32(&pCell[n], x);
+ pInfo->nKey = x;
+ nPayload += x;
+ }
+ pInfo->nPayload = nPayload;
+ pInfo->nHeader = n;
+ if( nPayload<=pPage->maxLocal ){
+ /* This is the (easy) common case where the entire payload fits
+ ** on the local page. No overflow is required.
+ */
+ int nSize; /* Total size of cell content in bytes */
+ pInfo->nLocal = nPayload;
+ pInfo->iOverflow = 0;
+ nSize = nPayload + n;
+ if( nSize<4 ){
+ nSize = 4; /* Minimum cell size is 4 */
+ }
+ pInfo->nSize = nSize;
+ }else{
+ /* If the payload will not fit completely on the local page, we have
+ ** to decide how much to store locally and how much to spill onto
+ ** overflow pages. The strategy is to minimize the amount of unused
+ ** space on overflow pages while keeping the amount of local storage
+ ** in between minLocal and maxLocal.
+ **
+ ** Warning: changing the way overflow payload is distributed in any
+ ** way will result in an incompatible file format.
+ */
+ int minLocal; /* Minimum amount of payload held locally */
+ int maxLocal; /* Maximum amount of payload held locally */
+ int surplus; /* Overflow payload available for local storage */
+
+ minLocal = pPage->minLocal;
+ maxLocal = pPage->maxLocal;
+ surplus = minLocal + (nPayload - minLocal)%(pPage->pBt->usableSize - 4);
+ if( surplus <= maxLocal ){
+ pInfo->nLocal = surplus;
+ }else{
+ pInfo->nLocal = minLocal;
+ }
+ pInfo->iOverflow = pInfo->nLocal + n;
+ pInfo->nSize = pInfo->iOverflow + 4;
+ }
+}
+#define parseCell(pPage, iCell, pInfo) \
+ sqlite3BtreeParseCellPtr((pPage), findCell((pPage), (iCell)), (pInfo))
+SQLITE_PRIVATE void sqlite3BtreeParseCell(
+ MemPage *pPage, /* Page containing the cell */
+ int iCell, /* The cell index. First cell is 0 */
+ CellInfo *pInfo /* Fill in this structure */
+){
+ parseCell(pPage, iCell, pInfo);
+}
+
+/*
+** Compute the total number of bytes that a Cell needs in the cell
+** data area of the btree-page. The return number includes the cell
+** data header and the local payload, but not any overflow page or
+** the space used by the cell pointer.
+*/
+#ifndef NDEBUG
+static u16 cellSize(MemPage *pPage, int iCell){
+ CellInfo info;
+ sqlite3BtreeParseCell(pPage, iCell, &info);
+ return info.nSize;
+}
+#endif
+static u16 cellSizePtr(MemPage *pPage, u8 *pCell){
+ CellInfo info;
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ return info.nSize;
+}
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+/*
+** If the cell pCell, part of page pPage contains a pointer
+** to an overflow page, insert an entry into the pointer-map
+** for the overflow page.
+*/
+static int ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell){
+ if( pCell ){
+ CellInfo info;
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ assert( (info.nData+(pPage->intKey?0:info.nKey))==info.nPayload );
+ if( (info.nData+(pPage->intKey?0:info.nKey))>info.nLocal ){
+ Pgno ovfl = get4byte(&pCell[info.iOverflow]);
+ return ptrmapPut(pPage->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno);
+ }
+ }
+ return SQLITE_OK;
+}
+/*
+** If the cell with index iCell on page pPage contains a pointer
+** to an overflow page, insert an entry into the pointer-map
+** for the overflow page.
+*/
+static int ptrmapPutOvfl(MemPage *pPage, int iCell){
+ u8 *pCell;
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ pCell = findOverflowCell(pPage, iCell);
+ return ptrmapPutOvflPtr(pPage, pCell);
+}
+#endif
+
+
+/*
+** Defragment the page given. All Cells are moved to the
+** end of the page and all free space is collected into one
+** big FreeBlk that occurs in between the header and cell
+** pointer array and the cell content area.
+*/
+static int defragmentPage(MemPage *pPage){
+ int i; /* Loop counter */
+ int pc; /* Address of a i-th cell */
+ int addr; /* Offset of first byte after cell pointer array */
+ int hdr; /* Offset to the page header */
+ int size; /* Size of a cell */
+ int usableSize; /* Number of usable bytes on a page */
+ int cellOffset; /* Offset to the cell pointer array */
+ int brk; /* Offset to the cell content area */
+ int nCell; /* Number of cells on the page */
+ unsigned char *data; /* The page data */
+ unsigned char *temp; /* Temp area for cell content */
+
+ assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( pPage->pBt!=0 );
+ assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE );
+ assert( pPage->nOverflow==0 );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ temp = sqlite3PagerTempSpace(pPage->pBt->pPager);
+ data = pPage->aData;
+ hdr = pPage->hdrOffset;
+ cellOffset = pPage->cellOffset;
+ nCell = pPage->nCell;
+ assert( nCell==get2byte(&data[hdr+3]) );
+ usableSize = pPage->pBt->usableSize;
+ brk = get2byte(&data[hdr+5]);
+ memcpy(&temp[brk], &data[brk], usableSize - brk);
+ brk = usableSize;
+ for(i=0; i<nCell; i++){
+ u8 *pAddr; /* The i-th cell pointer */
+ pAddr = &data[cellOffset + i*2];
+ pc = get2byte(pAddr);
+ assert( pc<pPage->pBt->usableSize );
+ size = cellSizePtr(pPage, &temp[pc]);
+ brk -= size;
+ memcpy(&data[brk], &temp[pc], size);
+ put2byte(pAddr, brk);
+ }
+ assert( brk>=cellOffset+2*nCell );
+ put2byte(&data[hdr+5], brk);
+ data[hdr+1] = 0;
+ data[hdr+2] = 0;
+ data[hdr+7] = 0;
+ addr = cellOffset+2*nCell;
+ memset(&data[addr], 0, brk-addr);
+ return SQLITE_OK;
+}
+
+/*
+** Allocate nByte bytes of space on a page.
+**
+** Return the index into pPage->aData[] of the first byte of
+** the new allocation. Or return 0 if there is not enough free
+** space on the page to satisfy the allocation request.
+**
+** If the page contains nBytes of free space but does not contain
+** nBytes of contiguous free space, then this routine automatically
+** calls defragementPage() to consolidate all free space before
+** allocating the new chunk.
+*/
+static int allocateSpace(MemPage *pPage, int nByte){
+ int addr, pc, hdr;
+ int size;
+ int nFrag;
+ int top;
+ int nCell;
+ int cellOffset;
+ unsigned char *data;
+
+ data = pPage->aData;
+ assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( pPage->pBt );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ if( nByte<4 ) nByte = 4;
+ if( pPage->nFree<nByte || pPage->nOverflow>0 ) return 0;
+ pPage->nFree -= nByte;
+ hdr = pPage->hdrOffset;
+
+ nFrag = data[hdr+7];
+ if( nFrag<60 ){
+ /* Search the freelist looking for a slot big enough to satisfy the
+ ** space request. */
+ addr = hdr+1;
+ while( (pc = get2byte(&data[addr]))>0 ){
+ size = get2byte(&data[pc+2]);
+ if( size>=nByte ){
+ if( size<nByte+4 ){
+ memcpy(&data[addr], &data[pc], 2);
+ data[hdr+7] = nFrag + size - nByte;
+ return pc;
+ }else{
+ put2byte(&data[pc+2], size-nByte);
+ return pc + size - nByte;
+ }
+ }
+ addr = pc;
+ }
+ }
+
+ /* Allocate memory from the gap in between the cell pointer array
+ ** and the cell content area.
+ */
+ top = get2byte(&data[hdr+5]);
+ nCell = get2byte(&data[hdr+3]);
+ cellOffset = pPage->cellOffset;
+ if( nFrag>=60 || cellOffset + 2*nCell > top - nByte ){
+ if( defragmentPage(pPage) ) return 0;
+ top = get2byte(&data[hdr+5]);
+ }
+ top -= nByte;
+ assert( cellOffset + 2*nCell <= top );
+ put2byte(&data[hdr+5], top);
+ return top;
+}
+
+/*
+** Return a section of the pPage->aData to the freelist.
+** The first byte of the new free block is pPage->aDisk[start]
+** and the size of the block is "size" bytes.
+**
+** Most of the effort here is involved in coalesing adjacent
+** free blocks into a single big free block.
+*/
+static void freeSpace(MemPage *pPage, int start, int size){
+ int addr, pbegin, hdr;
+ unsigned char *data = pPage->aData;
+
+ assert( pPage->pBt!=0 );
+ assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( start>=pPage->hdrOffset+6+(pPage->leaf?0:4) );
+ assert( (start + size)<=pPage->pBt->usableSize );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ if( size<4 ) size = 4;
+
+#ifdef SQLITE_SECURE_DELETE
+ /* Overwrite deleted information with zeros when the SECURE_DELETE
+ ** option is enabled at compile-time */
+ memset(&data[start], 0, size);
+#endif
+
+ /* Add the space back into the linked list of freeblocks */
+ hdr = pPage->hdrOffset;
+ addr = hdr + 1;
+ while( (pbegin = get2byte(&data[addr]))<start && pbegin>0 ){
+ assert( pbegin<=pPage->pBt->usableSize-4 );
+ assert( pbegin>addr );
+ addr = pbegin;
+ }
+ assert( pbegin<=pPage->pBt->usableSize-4 );
+ assert( pbegin>addr || pbegin==0 );
+ put2byte(&data[addr], start);
+ put2byte(&data[start], pbegin);
+ put2byte(&data[start+2], size);
+ pPage->nFree += size;
+
+ /* Coalesce adjacent free blocks */
+ addr = pPage->hdrOffset + 1;
+ while( (pbegin = get2byte(&data[addr]))>0 ){
+ int pnext, psize;
+ assert( pbegin>addr );
+ assert( pbegin<=pPage->pBt->usableSize-4 );
+ pnext = get2byte(&data[pbegin]);
+ psize = get2byte(&data[pbegin+2]);
+ if( pbegin + psize + 3 >= pnext && pnext>0 ){
+ int frag = pnext - (pbegin+psize);
+ assert( frag<=data[pPage->hdrOffset+7] );
+ data[pPage->hdrOffset+7] -= frag;
+ put2byte(&data[pbegin], get2byte(&data[pnext]));
+ put2byte(&data[pbegin+2], pnext+get2byte(&data[pnext+2])-pbegin);
+ }else{
+ addr = pbegin;
+ }
+ }
+
+ /* If the cell content area begins with a freeblock, remove it. */
+ if( data[hdr+1]==data[hdr+5] && data[hdr+2]==data[hdr+6] ){
+ int top;
+ pbegin = get2byte(&data[hdr+1]);
+ memcpy(&data[hdr+1], &data[pbegin], 2);
+ top = get2byte(&data[hdr+5]);
+ put2byte(&data[hdr+5], top + get2byte(&data[pbegin+2]));
+ }
+}
+
+/*
+** Decode the flags byte (the first byte of the header) for a page
+** and initialize fields of the MemPage structure accordingly.
+*/
+static void decodeFlags(MemPage *pPage, int flagByte){
+ BtShared *pBt; /* A copy of pPage->pBt */
+
+ assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ pPage->intKey = (flagByte & (PTF_INTKEY|PTF_LEAFDATA))!=0;
+ pPage->zeroData = (flagByte & PTF_ZERODATA)!=0;
+ pPage->leaf = (flagByte & PTF_LEAF)!=0;
+ pPage->childPtrSize = 4*(pPage->leaf==0);
+ pBt = pPage->pBt;
+ if( flagByte & PTF_LEAFDATA ){
+ pPage->leafData = 1;
+ pPage->maxLocal = pBt->maxLeaf;
+ pPage->minLocal = pBt->minLeaf;
+ }else{
+ pPage->leafData = 0;
+ pPage->maxLocal = pBt->maxLocal;
+ pPage->minLocal = pBt->minLocal;
+ }
+ pPage->hasData = !(pPage->zeroData || (!pPage->leaf && pPage->leafData));
+}
+
+/*
+** Initialize the auxiliary information for a disk block.
+**
+** The pParent parameter must be a pointer to the MemPage which
+** is the parent of the page being initialized. The root of a
+** BTree has no parent and so for that page, pParent==NULL.
+**
+** Return SQLITE_OK on success. If we see that the page does
+** not contain a well-formed database page, then return
+** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not
+** guarantee that the page is well-formed. It only shows that
+** we failed to detect any corruption.
+*/
+SQLITE_PRIVATE int sqlite3BtreeInitPage(
+ MemPage *pPage, /* The page to be initialized */
+ MemPage *pParent /* The parent. Might be NULL */
+){
+ int pc; /* Address of a freeblock within pPage->aData[] */
+ int hdr; /* Offset to beginning of page header */
+ u8 *data; /* Equal to pPage->aData */
+ BtShared *pBt; /* The main btree structure */
+ int usableSize; /* Amount of usable space on each page */
+ int cellOffset; /* Offset from start of page to first cell pointer */
+ int nFree; /* Number of unused bytes on the page */
+ int top; /* First byte of the cell content area */
+
+ pBt = pPage->pBt;
+ assert( pBt!=0 );
+ assert( pParent==0 || pParent->pBt==pBt );
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( pPage->pgno==sqlite3PagerPagenumber(pPage->pDbPage) );
+ assert( pPage == sqlite3PagerGetExtra(pPage->pDbPage) );
+ assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) );
+ if( pPage->pParent!=pParent && (pPage->pParent!=0 || pPage->isInit) ){
+ /* The parent page should never change unless the file is corrupt */
+ return SQLITE_CORRUPT_BKPT;
+ }
+ if( pPage->isInit ) return SQLITE_OK;
+ if( pPage->pParent==0 && pParent!=0 ){
+ pPage->pParent = pParent;
+ sqlite3PagerRef(pParent->pDbPage);
+ }
+ hdr = pPage->hdrOffset;
+ data = pPage->aData;
+ decodeFlags(pPage, data[hdr]);
+ pPage->nOverflow = 0;
+ pPage->idxShift = 0;
+ usableSize = pBt->usableSize;
+ pPage->cellOffset = cellOffset = hdr + 12 - 4*pPage->leaf;
+ top = get2byte(&data[hdr+5]);
+ pPage->nCell = get2byte(&data[hdr+3]);
+ if( pPage->nCell>MX_CELL(pBt) ){
+ /* To many cells for a single page. The page must be corrupt */
+ return SQLITE_CORRUPT_BKPT;
+ }
+ if( pPage->nCell==0 && pParent!=0 && pParent->pgno!=1 ){
+ /* All pages must have at least one cell, except for root pages */
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ /* Compute the total free space on the page */
+ pc = get2byte(&data[hdr+1]);
+ nFree = data[hdr+7] + top - (cellOffset + 2*pPage->nCell);
+ while( pc>0 ){
+ int next, size;
+ if( pc>usableSize-4 ){
+ /* Free block is off the page */
+ return SQLITE_CORRUPT_BKPT;
+ }
+ next = get2byte(&data[pc]);
+ size = get2byte(&data[pc+2]);
+ if( next>0 && next<=pc+size+3 ){
+ /* Free blocks must be in accending order */
+ return SQLITE_CORRUPT_BKPT;
+ }
+ nFree += size;
+ pc = next;
+ }
+ pPage->nFree = nFree;
+ if( nFree>=usableSize ){
+ /* Free space cannot exceed total page size */
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ pPage->isInit = 1;
+ return SQLITE_OK;
+}
+
+/*
+** Set up a raw page so that it looks like a database page holding
+** no entries.
+*/
+static void zeroPage(MemPage *pPage, int flags){
+ unsigned char *data = pPage->aData;
+ BtShared *pBt = pPage->pBt;
+ int hdr = pPage->hdrOffset;
+ int first;
+
+ assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno );
+ assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
+ assert( sqlite3PagerGetData(pPage->pDbPage) == data );
+ assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ memset(&data[hdr], 0, pBt->usableSize - hdr);
+ data[hdr] = flags;
+ first = hdr + 8 + 4*((flags&PTF_LEAF)==0);
+ memset(&data[hdr+1], 0, 4);
+ data[hdr+7] = 0;
+ put2byte(&data[hdr+5], pBt->usableSize);
+ pPage->nFree = pBt->usableSize - first;
+ decodeFlags(pPage, flags);
+ pPage->hdrOffset = hdr;
+ pPage->cellOffset = first;
+ pPage->nOverflow = 0;
+ pPage->idxShift = 0;
+ pPage->nCell = 0;
+ pPage->isInit = 1;
+}
+
+/*
+** Get a page from the pager. Initialize the MemPage.pBt and
+** MemPage.aData elements if needed.
+**
+** If the noContent flag is set, it means that we do not care about
+** the content of the page at this time. So do not go to the disk
+** to fetch the content. Just fill in the content with zeros for now.
+** If in the future we call sqlite3PagerWrite() on this page, that
+** means we have started to be concerned about content and the disk
+** read should occur at that point.
+*/
+SQLITE_PRIVATE int sqlite3BtreeGetPage(
+ BtShared *pBt, /* The btree */
+ Pgno pgno, /* Number of the page to fetch */
+ MemPage **ppPage, /* Return the page in this parameter */
+ int noContent /* Do not load page content if true */
+){
+ int rc;
+ MemPage *pPage;
+ DbPage *pDbPage;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ rc = sqlite3PagerAcquire(pBt->pPager, pgno, (DbPage**)&pDbPage, noContent);
+ if( rc ) return rc;
+ pPage = (MemPage *)sqlite3PagerGetExtra(pDbPage);
+ pPage->aData = sqlite3PagerGetData(pDbPage);
+ pPage->pDbPage = pDbPage;
+ pPage->pBt = pBt;
+ pPage->pgno = pgno;
+ pPage->hdrOffset = pPage->pgno==1 ? 100 : 0;
+ *ppPage = pPage;
+ return SQLITE_OK;
+}
+
+/*
+** Get a page from the pager and initialize it. This routine
+** is just a convenience wrapper around separate calls to
+** sqlite3BtreeGetPage() and sqlite3BtreeInitPage().
+*/
+static int getAndInitPage(
+ BtShared *pBt, /* The database file */
+ Pgno pgno, /* Number of the page to get */
+ MemPage **ppPage, /* Write the page pointer here */
+ MemPage *pParent /* Parent of the page */
+){
+ int rc;
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ if( pgno==0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ rc = sqlite3BtreeGetPage(pBt, pgno, ppPage, 0);
+ if( rc==SQLITE_OK && (*ppPage)->isInit==0 ){
+ rc = sqlite3BtreeInitPage(*ppPage, pParent);
+ if( rc!=SQLITE_OK ){
+ releasePage(*ppPage);
+ *ppPage = 0;
+ }
+ }
+ return rc;
+}
+
+/*
+** Release a MemPage. This should be called once for each prior
+** call to sqlite3BtreeGetPage.
+*/
+static void releasePage(MemPage *pPage){
+ if( pPage ){
+ assert( pPage->aData );
+ assert( pPage->pBt );
+ assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
+ assert( sqlite3PagerGetData(pPage->pDbPage)==pPage->aData );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ sqlite3PagerUnref(pPage->pDbPage);
+ }
+}
+
+/*
+** This routine is called when the reference count for a page
+** reaches zero. We need to unref the pParent pointer when that
+** happens.
+*/
+static void pageDestructor(DbPage *pData, int pageSize){
+ MemPage *pPage;
+ assert( (pageSize & 7)==0 );
+ pPage = (MemPage *)sqlite3PagerGetExtra(pData);
+ assert( pPage->isInit==0 || sqlite3_mutex_held(pPage->pBt->mutex) );
+ if( pPage->pParent ){
+ MemPage *pParent = pPage->pParent;
+ assert( pParent->pBt==pPage->pBt );
+ pPage->pParent = 0;
+ releasePage(pParent);
+ }
+ pPage->isInit = 0;
+}
+
+/*
+** During a rollback, when the pager reloads information into the cache
+** so that the cache is restored to its original state at the start of
+** the transaction, for each page restored this routine is called.
+**
+** This routine needs to reset the extra data section at the end of the
+** page to agree with the restored data.
+*/
+static void pageReinit(DbPage *pData, int pageSize){
+ MemPage *pPage;
+ assert( (pageSize & 7)==0 );
+ pPage = (MemPage *)sqlite3PagerGetExtra(pData);
+ if( pPage->isInit ){
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ pPage->isInit = 0;
+ sqlite3BtreeInitPage(pPage, pPage->pParent);
+ }
+}
+
+/*
+** Invoke the busy handler for a btree.
+*/
+static int sqlite3BtreeInvokeBusyHandler(void *pArg, int n){
+ BtShared *pBt = (BtShared*)pArg;
+ assert( pBt->db );
+ assert( sqlite3_mutex_held(pBt->db->mutex) );
+ return sqlite3InvokeBusyHandler(&pBt->db->busyHandler);
+}
+
+/*
+** Open a database file.
+**
+** zFilename is the name of the database file. If zFilename is NULL
+** a new database with a random name is created. This randomly named
+** database file will be deleted when sqlite3BtreeClose() is called.
+** If zFilename is ":memory:" then an in-memory database is created
+** that is automatically destroyed when it is closed.
+*/
+SQLITE_PRIVATE int sqlite3BtreeOpen(
+ const char *zFilename, /* Name of the file containing the BTree database */
+ sqlite3 *db, /* Associated database handle */
+ Btree **ppBtree, /* Pointer to new Btree object written here */
+ int flags, /* Options */
+ int vfsFlags /* Flags passed through to sqlite3_vfs.xOpen() */
+){
+ sqlite3_vfs *pVfs; /* The VFS to use for this btree */
+ BtShared *pBt = 0; /* Shared part of btree structure */
+ Btree *p; /* Handle to return */
+ int rc = SQLITE_OK;
+ int nReserve;
+ unsigned char zDbHeader[100];
+
+ /* Set the variable isMemdb to true for an in-memory database, or
+ ** false for a file-based database. This symbol is only required if
+ ** either of the shared-data or autovacuum features are compiled
+ ** into the library.
+ */
+#if !defined(SQLITE_OMIT_SHARED_CACHE) || !defined(SQLITE_OMIT_AUTOVACUUM)
+ #ifdef SQLITE_OMIT_MEMORYDB
+ const int isMemdb = 0;
+ #else
+ const int isMemdb = zFilename && !strcmp(zFilename, ":memory:");
+ #endif
+#endif
+
+ assert( db!=0 );
+ assert( sqlite3_mutex_held(db->mutex) );
+
+ pVfs = db->pVfs;
+ p = sqlite3MallocZero(sizeof(Btree));
+ if( !p ){
+ return SQLITE_NOMEM;
+ }
+ p->inTrans = TRANS_NONE;
+ p->db = db;
+
+#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO)
+ /*
+ ** If this Btree is a candidate for shared cache, try to find an
+ ** existing BtShared object that we can share with
+ */
+ if( (flags & BTREE_PRIVATE)==0
+ && isMemdb==0
+ && (db->flags & SQLITE_Vtab)==0
+ && zFilename && zFilename[0]
+ ){
+ if( sqlite3SharedCacheEnabled ){
+ int nFullPathname = pVfs->mxPathname+1;
+ char *zFullPathname = (char *)sqlite3_malloc(nFullPathname);
+ sqlite3_mutex *mutexShared;
+ p->sharable = 1;
+ if( db ){
+ db->flags |= SQLITE_SharedCache;
+ }
+ if( !zFullPathname ){
+ sqlite3_free(p);
+ return SQLITE_NOMEM;
+ }
+ sqlite3OsFullPathname(pVfs, zFilename, nFullPathname, zFullPathname);
+ mutexShared = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+ sqlite3_mutex_enter(mutexShared);
+ for(pBt=sqlite3SharedCacheList; pBt; pBt=pBt->pNext){
+ assert( pBt->nRef>0 );
+ if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager))
+ && sqlite3PagerVfs(pBt->pPager)==pVfs ){
+ p->pBt = pBt;
+ pBt->nRef++;
+ break;
+ }
+ }
+ sqlite3_mutex_leave(mutexShared);
+ sqlite3_free(zFullPathname);
+ }
+#ifdef SQLITE_DEBUG
+ else{
+ /* In debug mode, we mark all persistent databases as sharable
+ ** even when they are not. This exercises the locking code and
+ ** gives more opportunity for asserts(sqlite3_mutex_held())
+ ** statements to find locking problems.
+ */
+ p->sharable = 1;
+ }
+#endif
+ }
+#endif
+ if( pBt==0 ){
+ /*
+ ** The following asserts make sure that structures used by the btree are
+ ** the right size. This is to guard against size changes that result
+ ** when compiling on a different architecture.
+ */
+ assert( sizeof(i64)==8 || sizeof(i64)==4 );
+ assert( sizeof(u64)==8 || sizeof(u64)==4 );
+ assert( sizeof(u32)==4 );
+ assert( sizeof(u16)==2 );
+ assert( sizeof(Pgno)==4 );
+
+ pBt = sqlite3MallocZero( sizeof(*pBt) );
+ if( pBt==0 ){
+ rc = SQLITE_NOMEM;
+ goto btree_open_out;
+ }
+ pBt->busyHdr.xFunc = sqlite3BtreeInvokeBusyHandler;
+ pBt->busyHdr.pArg = pBt;
+ rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename,
+ EXTRA_SIZE, flags, vfsFlags);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader);
+ }
+ if( rc!=SQLITE_OK ){
+ goto btree_open_out;
+ }
+ sqlite3PagerSetBusyhandler(pBt->pPager, &pBt->busyHdr);
+ p->pBt = pBt;
+
+ sqlite3PagerSetDestructor(pBt->pPager, pageDestructor);
+ sqlite3PagerSetReiniter(pBt->pPager, pageReinit);
+ pBt->pCursor = 0;
+ pBt->pPage1 = 0;
+ pBt->readOnly = sqlite3PagerIsreadonly(pBt->pPager);
+ pBt->pageSize = get2byte(&zDbHeader[16]);
+ if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE
+ || ((pBt->pageSize-1)&pBt->pageSize)!=0 ){
+ pBt->pageSize = 0;
+ sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize);
+ pBt->maxEmbedFrac = 64; /* 25% */
+ pBt->minEmbedFrac = 32; /* 12.5% */
+ pBt->minLeafFrac = 32; /* 12.5% */
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* If the magic name ":memory:" will create an in-memory database, then
+ ** leave the autoVacuum mode at 0 (do not auto-vacuum), even if
+ ** SQLITE_DEFAULT_AUTOVACUUM is true. On the other hand, if
+ ** SQLITE_OMIT_MEMORYDB has been defined, then ":memory:" is just a
+ ** regular file-name. In this case the auto-vacuum applies as per normal.
+ */
+ if( zFilename && !isMemdb ){
+ pBt->autoVacuum = (SQLITE_DEFAULT_AUTOVACUUM ? 1 : 0);
+ pBt->incrVacuum = (SQLITE_DEFAULT_AUTOVACUUM==2 ? 1 : 0);
+ }
+#endif
+ nReserve = 0;
+ }else{
+ nReserve = zDbHeader[20];
+ pBt->maxEmbedFrac = zDbHeader[21];
+ pBt->minEmbedFrac = zDbHeader[22];
+ pBt->minLeafFrac = zDbHeader[23];
+ pBt->pageSizeFixed = 1;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ pBt->autoVacuum = (get4byte(&zDbHeader[36 + 4*4])?1:0);
+ pBt->incrVacuum = (get4byte(&zDbHeader[36 + 7*4])?1:0);
+#endif
+ }
+ pBt->usableSize = pBt->pageSize - nReserve;
+ assert( (pBt->pageSize & 7)==0 ); /* 8-byte alignment of pageSize */
+ sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize);
+
+#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO)
+ /* Add the new BtShared object to the linked list sharable BtShareds.
+ */
+ if( p->sharable ){
+ sqlite3_mutex *mutexShared;
+ pBt->nRef = 1;
+ mutexShared = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+ if( SQLITE_THREADSAFE ){
+ pBt->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ if( pBt->mutex==0 ){
+ rc = SQLITE_NOMEM;
+ db->mallocFailed = 0;
+ goto btree_open_out;
+ }
+ }
+ sqlite3_mutex_enter(mutexShared);
+ pBt->pNext = sqlite3SharedCacheList;
+ sqlite3SharedCacheList = pBt;
+ sqlite3_mutex_leave(mutexShared);
+ }
+#endif
+ }
+
+#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO)
+ /* If the new Btree uses a sharable pBtShared, then link the new
+ ** Btree into the list of all sharable Btrees for the same connection.
+ ** The list is kept in ascending order by pBt address.
+ */
+ if( p->sharable ){
+ int i;
+ Btree *pSib;
+ for(i=0; i<db->nDb; i++){
+ if( (pSib = db->aDb[i].pBt)!=0 && pSib->sharable ){
+ while( pSib->pPrev ){ pSib = pSib->pPrev; }
+ if( p->pBt<pSib->pBt ){
+ p->pNext = pSib;
+ p->pPrev = 0;
+ pSib->pPrev = p;
+ }else{
+ while( pSib->pNext && pSib->pNext->pBt<p->pBt ){
+ pSib = pSib->pNext;
+ }
+ p->pNext = pSib->pNext;
+ p->pPrev = pSib;
+ if( p->pNext ){
+ p->pNext->pPrev = p;
+ }
+ pSib->pNext = p;
+ }
+ break;
+ }
+ }
+ }
+#endif
+ *ppBtree = p;
+
+btree_open_out:
+ if( rc!=SQLITE_OK ){
+ if( pBt && pBt->pPager ){
+ sqlite3PagerClose(pBt->pPager);
+ }
+ sqlite3_free(pBt);
+ sqlite3_free(p);
+ *ppBtree = 0;
+ }
+ return rc;
+}
+
+/*
+** Decrement the BtShared.nRef counter. When it reaches zero,
+** remove the BtShared structure from the sharing list. Return
+** true if the BtShared.nRef counter reaches zero and return
+** false if it is still positive.
+*/
+static int removeFromSharingList(BtShared *pBt){
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ sqlite3_mutex *pMaster;
+ BtShared *pList;
+ int removed = 0;
+
+ assert( sqlite3_mutex_notheld(pBt->mutex) );
+ pMaster = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+ sqlite3_mutex_enter(pMaster);
+ pBt->nRef--;
+ if( pBt->nRef<=0 ){
+ if( sqlite3SharedCacheList==pBt ){
+ sqlite3SharedCacheList = pBt->pNext;
+ }else{
+ pList = sqlite3SharedCacheList;
+ while( pList && pList->pNext!=pBt ){
+ pList=pList->pNext;
+ }
+ if( pList ){
+ pList->pNext = pBt->pNext;
+ }
+ }
+ if( SQLITE_THREADSAFE ){
+ sqlite3_mutex_free(pBt->mutex);
+ }
+ removed = 1;
+ }
+ sqlite3_mutex_leave(pMaster);
+ return removed;
+#else
+ return 1;
+#endif
+}
+
+/*
+** Close an open database and invalidate all cursors.
+*/
+SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){
+ BtShared *pBt = p->pBt;
+ BtCursor *pCur;
+
+ /* Close all cursors opened via this handle. */
+ assert( sqlite3_mutex_held(p->db->mutex) );
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ pCur = pBt->pCursor;
+ while( pCur ){
+ BtCursor *pTmp = pCur;
+ pCur = pCur->pNext;
+ if( pTmp->pBtree==p ){
+ sqlite3BtreeCloseCursor(pTmp);
+ }
+ }
+
+ /* Rollback any active transaction and free the handle structure.
+ ** The call to sqlite3BtreeRollback() drops any table-locks held by
+ ** this handle.
+ */
+ sqlite3BtreeRollback(p);
+ sqlite3BtreeLeave(p);
+
+ /* If there are still other outstanding references to the shared-btree
+ ** structure, return now. The remainder of this procedure cleans
+ ** up the shared-btree.
+ */
+ assert( p->wantToLock==0 && p->locked==0 );
+ if( !p->sharable || removeFromSharingList(pBt) ){
+ /* The pBt is no longer on the sharing list, so we can access
+ ** it without having to hold the mutex.
+ **
+ ** Clean out and delete the BtShared object.
+ */
+ assert( !pBt->pCursor );
+ sqlite3PagerClose(pBt->pPager);
+ if( pBt->xFreeSchema && pBt->pSchema ){
+ pBt->xFreeSchema(pBt->pSchema);
+ }
+ sqlite3_free(pBt->pSchema);
+ sqlite3_free(pBt->pTmpSpace);
+ sqlite3_free(pBt);
+ }
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ assert( p->wantToLock==0 );
+ assert( p->locked==0 );
+ if( p->pPrev ) p->pPrev->pNext = p->pNext;
+ if( p->pNext ) p->pNext->pPrev = p->pPrev;
+#endif
+
+ sqlite3_free(p);
+ return SQLITE_OK;
+}
+
+/*
+** Change the limit on the number of pages allowed in the cache.
+**
+** The maximum number of cache pages is set to the absolute
+** value of mxPage. If mxPage is negative, the pager will
+** operate asynchronously - it will not stop to do fsync()s
+** to insure data is written to the disk surface before
+** continuing. Transactions still work if synchronous is off,
+** and the database cannot be corrupted if this program
+** crashes. But if the operating system crashes or there is
+** an abrupt power failure when synchronous is off, the database
+** could be left in an inconsistent and unrecoverable state.
+** Synchronous is on by default so database corruption is not
+** normally a worry.
+*/
+SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){
+ BtShared *pBt = p->pBt;
+ assert( sqlite3_mutex_held(p->db->mutex) );
+ sqlite3BtreeEnter(p);
+ sqlite3PagerSetCachesize(pBt->pPager, mxPage);
+ sqlite3BtreeLeave(p);
+ return SQLITE_OK;
+}
+
+/*
+** Change the way data is synced to disk in order to increase or decrease
+** how well the database resists damage due to OS crashes and power
+** failures. Level 1 is the same as asynchronous (no syncs() occur and
+** there is a high probability of damage) Level 2 is the default. There
+** is a very low but non-zero probability of damage. Level 3 reduces the
+** probability of damage to near zero but with a write performance reduction.
+*/
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+SQLITE_PRIVATE int sqlite3BtreeSetSafetyLevel(Btree *p, int level, int fullSync){
+ BtShared *pBt = p->pBt;
+ assert( sqlite3_mutex_held(p->db->mutex) );
+ sqlite3BtreeEnter(p);
+ sqlite3PagerSetSafetyLevel(pBt->pPager, level, fullSync);
+ sqlite3BtreeLeave(p);
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** Return TRUE if the given btree is set to safety level 1. In other
+** words, return TRUE if no sync() occurs on the disk files.
+*/
+SQLITE_PRIVATE int sqlite3BtreeSyncDisabled(Btree *p){
+ BtShared *pBt = p->pBt;
+ int rc;
+ assert( sqlite3_mutex_held(p->db->mutex) );
+ sqlite3BtreeEnter(p);
+ assert( pBt && pBt->pPager );
+ rc = sqlite3PagerNosync(pBt->pPager);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM)
+/*
+** Change the default pages size and the number of reserved bytes per page.
+**
+** The page size must be a power of 2 between 512 and 65536. If the page
+** size supplied does not meet this constraint then the page size is not
+** changed.
+**
+** Page sizes are constrained to be a power of two so that the region
+** of the database file used for locking (beginning at PENDING_BYTE,
+** the first byte past the 1GB boundary, 0x40000000) needs to occur
+** at the beginning of a page.
+**
+** If parameter nReserve is less than zero, then the number of reserved
+** bytes per page is left unchanged.
+*/
+SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve){
+ int rc = SQLITE_OK;
+ BtShared *pBt = p->pBt;
+ sqlite3BtreeEnter(p);
+ if( pBt->pageSizeFixed ){
+ sqlite3BtreeLeave(p);
+ return SQLITE_READONLY;
+ }
+ if( nReserve<0 ){
+ nReserve = pBt->pageSize - pBt->usableSize;
+ }
+ if( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE &&
+ ((pageSize-1)&pageSize)==0 ){
+ assert( (pageSize & 7)==0 );
+ assert( !pBt->pPage1 && !pBt->pCursor );
+ pBt->pageSize = pageSize;
+ sqlite3_free(pBt->pTmpSpace);
+ pBt->pTmpSpace = 0;
+ rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize);
+ }
+ pBt->usableSize = pBt->pageSize - nReserve;
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Return the currently defined page size
+*/
+SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){
+ return p->pBt->pageSize;
+}
+SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree *p){
+ int n;
+ sqlite3BtreeEnter(p);
+ n = p->pBt->pageSize - p->pBt->usableSize;
+ sqlite3BtreeLeave(p);
+ return n;
+}
+
+/*
+** Set the maximum page count for a database if mxPage is positive.
+** No changes are made if mxPage is 0 or negative.
+** Regardless of the value of mxPage, return the maximum page count.
+*/
+SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree *p, int mxPage){
+ int n;
+ sqlite3BtreeEnter(p);
+ n = sqlite3PagerMaxPageCount(p->pBt->pPager, mxPage);
+ sqlite3BtreeLeave(p);
+ return n;
+}
+#endif /* !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM) */
+
+/*
+** Change the 'auto-vacuum' property of the database. If the 'autoVacuum'
+** parameter is non-zero, then auto-vacuum mode is enabled. If zero, it
+** is disabled. The default value for the auto-vacuum property is
+** determined by the SQLITE_DEFAULT_AUTOVACUUM macro.
+*/
+SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ return SQLITE_READONLY;
+#else
+ BtShared *pBt = p->pBt;
+ int rc = SQLITE_OK;
+ int av = (autoVacuum?1:0);
+
+ sqlite3BtreeEnter(p);
+ if( pBt->pageSizeFixed && av!=pBt->autoVacuum ){
+ rc = SQLITE_READONLY;
+ }else{
+ pBt->autoVacuum = av;
+ }
+ sqlite3BtreeLeave(p);
+ return rc;
+#endif
+}
+
+/*
+** Return the value of the 'auto-vacuum' property. If auto-vacuum is
+** enabled 1 is returned. Otherwise 0.
+*/
+SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *p){
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ return BTREE_AUTOVACUUM_NONE;
+#else
+ int rc;
+ sqlite3BtreeEnter(p);
+ rc = (
+ (!p->pBt->autoVacuum)?BTREE_AUTOVACUUM_NONE:
+ (!p->pBt->incrVacuum)?BTREE_AUTOVACUUM_FULL:
+ BTREE_AUTOVACUUM_INCR
+ );
+ sqlite3BtreeLeave(p);
+ return rc;
+#endif
+}
+
+
+/*
+** Get a reference to pPage1 of the database file. This will
+** also acquire a readlock on that file.
+**
+** SQLITE_OK is returned on success. If the file is not a
+** well-formed database file, then SQLITE_CORRUPT is returned.
+** SQLITE_BUSY is returned if the database is locked. SQLITE_NOMEM
+** is returned if we run out of memory.
+*/
+static int lockBtree(BtShared *pBt){
+ int rc;
+ MemPage *pPage1;
+ int nPage;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ if( pBt->pPage1 ) return SQLITE_OK;
+ rc = sqlite3BtreeGetPage(pBt, 1, &pPage1, 0);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Do some checking to help insure the file we opened really is
+ ** a valid database file.
+ */
+ rc = SQLITE_NOTADB;
+ nPage = sqlite3PagerPagecount(pBt->pPager);
+ if( nPage<0 ){
+ rc = SQLITE_IOERR;
+ goto page1_init_failed;
+ }else if( nPage>0 ){
+ int pageSize;
+ int usableSize;
+ u8 *page1 = pPage1->aData;
+ if( memcmp(page1, zMagicHeader, 16)!=0 ){
+ goto page1_init_failed;
+ }
+ if( page1[18]>1 ){
+ pBt->readOnly = 1;
+ }
+ if( page1[19]>1 ){
+ goto page1_init_failed;
+ }
+ pageSize = get2byte(&page1[16]);
+ if( ((pageSize-1)&pageSize)!=0 || pageSize<512 ||
+ (SQLITE_MAX_PAGE_SIZE<32768 && pageSize>SQLITE_MAX_PAGE_SIZE)
+ ){
+ goto page1_init_failed;
+ }
+ assert( (pageSize & 7)==0 );
+ usableSize = pageSize - page1[20];
+ if( pageSize!=pBt->pageSize ){
+ /* After reading the first page of the database assuming a page size
+ ** of BtShared.pageSize, we have discovered that the page-size is
+ ** actually pageSize. Unlock the database, leave pBt->pPage1 at
+ ** zero and return SQLITE_OK. The caller will call this function
+ ** again with the correct page-size.
+ */
+ releasePage(pPage1);
+ pBt->usableSize = usableSize;
+ pBt->pageSize = pageSize;
+ sqlite3_free(pBt->pTmpSpace);
+ pBt->pTmpSpace = 0;
+ sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize);
+ return SQLITE_OK;
+ }
+ if( usableSize<500 ){
+ goto page1_init_failed;
+ }
+ pBt->pageSize = pageSize;
+ pBt->usableSize = usableSize;
+ pBt->maxEmbedFrac = page1[21];
+ pBt->minEmbedFrac = page1[22];
+ pBt->minLeafFrac = page1[23];
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ pBt->autoVacuum = (get4byte(&page1[36 + 4*4])?1:0);
+ pBt->incrVacuum = (get4byte(&page1[36 + 7*4])?1:0);
+#endif
+ }
+
+ /* maxLocal is the maximum amount of payload to store locally for
+ ** a cell. Make sure it is small enough so that at least minFanout
+ ** cells can will fit on one page. We assume a 10-byte page header.
+ ** Besides the payload, the cell must store:
+ ** 2-byte pointer to the cell
+ ** 4-byte child pointer
+ ** 9-byte nKey value
+ ** 4-byte nData value
+ ** 4-byte overflow page pointer
+ ** So a cell consists of a 2-byte poiner, a header which is as much as
+ ** 17 bytes long, 0 to N bytes of payload, and an optional 4 byte overflow
+ ** page pointer.
+ */
+ pBt->maxLocal = (pBt->usableSize-12)*pBt->maxEmbedFrac/255 - 23;
+ pBt->minLocal = (pBt->usableSize-12)*pBt->minEmbedFrac/255 - 23;
+ pBt->maxLeaf = pBt->usableSize - 35;
+ pBt->minLeaf = (pBt->usableSize-12)*pBt->minLeafFrac/255 - 23;
+ if( pBt->minLocal>pBt->maxLocal || pBt->maxLocal<0 ){
+ goto page1_init_failed;
+ }
+ assert( pBt->maxLeaf + 23 <= MX_CELL_SIZE(pBt) );
+ pBt->pPage1 = pPage1;
+ return SQLITE_OK;
+
+page1_init_failed:
+ releasePage(pPage1);
+ pBt->pPage1 = 0;
+ return rc;
+}
+
+/*
+** This routine works like lockBtree() except that it also invokes the
+** busy callback if there is lock contention.
+*/
+static int lockBtreeWithRetry(Btree *pRef){
+ int rc = SQLITE_OK;
+
+ assert( sqlite3BtreeHoldsMutex(pRef) );
+ if( pRef->inTrans==TRANS_NONE ){
+ u8 inTransaction = pRef->pBt->inTransaction;
+ btreeIntegrity(pRef);
+ rc = sqlite3BtreeBeginTrans(pRef, 0);
+ pRef->pBt->inTransaction = inTransaction;
+ pRef->inTrans = TRANS_NONE;
+ if( rc==SQLITE_OK ){
+ pRef->pBt->nTransaction--;
+ }
+ btreeIntegrity(pRef);
+ }
+ return rc;
+}
+
+
+/*
+** If there are no outstanding cursors and we are not in the middle
+** of a transaction but there is a read lock on the database, then
+** this routine unrefs the first page of the database file which
+** has the effect of releasing the read lock.
+**
+** If there are any outstanding cursors, this routine is a no-op.
+**
+** If there is a transaction in progress, this routine is a no-op.
+*/
+static void unlockBtreeIfUnused(BtShared *pBt){
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ if( pBt->inTransaction==TRANS_NONE && pBt->pCursor==0 && pBt->pPage1!=0 ){
+ if( sqlite3PagerRefcount(pBt->pPager)>=1 ){
+ assert( pBt->pPage1->aData );
+#if 0
+ if( pBt->pPage1->aData==0 ){
+ MemPage *pPage = pBt->pPage1;
+ pPage->aData = sqlite3PagerGetData(pPage->pDbPage);
+ pPage->pBt = pBt;
+ pPage->pgno = 1;
+ }
+#endif
+ releasePage(pBt->pPage1);
+ }
+ pBt->pPage1 = 0;
+ pBt->inStmt = 0;
+ }
+}
+
+/*
+** Create a new database by initializing the first page of the
+** file.
+*/
+static int newDatabase(BtShared *pBt){
+ MemPage *pP1;
+ unsigned char *data;
+ int rc;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ if( sqlite3PagerPagecount(pBt->pPager)>0 ) return SQLITE_OK;
+ pP1 = pBt->pPage1;
+ assert( pP1!=0 );
+ data = pP1->aData;
+ rc = sqlite3PagerWrite(pP1->pDbPage);
+ if( rc ) return rc;
+ memcpy(data, zMagicHeader, sizeof(zMagicHeader));
+ assert( sizeof(zMagicHeader)==16 );
+ put2byte(&data[16], pBt->pageSize);
+ data[18] = 1;
+ data[19] = 1;
+ data[20] = pBt->pageSize - pBt->usableSize;
+ data[21] = pBt->maxEmbedFrac;
+ data[22] = pBt->minEmbedFrac;
+ data[23] = pBt->minLeafFrac;
+ memset(&data[24], 0, 100-24);
+ zeroPage(pP1, PTF_INTKEY|PTF_LEAF|PTF_LEAFDATA );
+ pBt->pageSizeFixed = 1;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ assert( pBt->autoVacuum==1 || pBt->autoVacuum==0 );
+ assert( pBt->incrVacuum==1 || pBt->incrVacuum==0 );
+ put4byte(&data[36 + 4*4], pBt->autoVacuum);
+ put4byte(&data[36 + 7*4], pBt->incrVacuum);
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** Attempt to start a new transaction. A write-transaction
+** is started if the second argument is nonzero, otherwise a read-
+** transaction. If the second argument is 2 or more and exclusive
+** transaction is started, meaning that no other process is allowed
+** to access the database. A preexisting transaction may not be
+** upgraded to exclusive by calling this routine a second time - the
+** exclusivity flag only works for a new transaction.
+**
+** A write-transaction must be started before attempting any
+** changes to the database. None of the following routines
+** will work unless a transaction is started first:
+**
+** sqlite3BtreeCreateTable()
+** sqlite3BtreeCreateIndex()
+** sqlite3BtreeClearTable()
+** sqlite3BtreeDropTable()
+** sqlite3BtreeInsert()
+** sqlite3BtreeDelete()
+** sqlite3BtreeUpdateMeta()
+**
+** If an initial attempt to acquire the lock fails because of lock contention
+** and the database was previously unlocked, then invoke the busy handler
+** if there is one. But if there was previously a read-lock, do not
+** invoke the busy handler - just return SQLITE_BUSY. SQLITE_BUSY is
+** returned when there is already a read-lock in order to avoid a deadlock.
+**
+** Suppose there are two processes A and B. A has a read lock and B has
+** a reserved lock. B tries to promote to exclusive but is blocked because
+** of A's read lock. A tries to promote to reserved but is blocked by B.
+** One or the other of the two processes must give way or there can be
+** no progress. By returning SQLITE_BUSY and not invoking the busy callback
+** when A already has a read lock, we encourage A to give up and let B
+** proceed.
+*/
+SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
+ BtShared *pBt = p->pBt;
+ int rc = SQLITE_OK;
+
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ btreeIntegrity(p);
+
+ /* If the btree is already in a write-transaction, or it
+ ** is already in a read-transaction and a read-transaction
+ ** is requested, this is a no-op.
+ */
+ if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){
+ goto trans_begun;
+ }
+
+ /* Write transactions are not possible on a read-only database */
+ if( pBt->readOnly && wrflag ){
+ rc = SQLITE_READONLY;
+ goto trans_begun;
+ }
+
+ /* If another database handle has already opened a write transaction
+ ** on this shared-btree structure and a second write transaction is
+ ** requested, return SQLITE_BUSY.
+ */
+ if( pBt->inTransaction==TRANS_WRITE && wrflag ){
+ rc = SQLITE_BUSY;
+ goto trans_begun;
+ }
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ if( wrflag>1 ){
+ BtLock *pIter;
+ for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
+ if( pIter->pBtree!=p ){
+ rc = SQLITE_BUSY;
+ goto trans_begun;
+ }
+ }
+ }
+#endif
+
+ do {
+ if( pBt->pPage1==0 ){
+ do{
+ rc = lockBtree(pBt);
+ }while( pBt->pPage1==0 && rc==SQLITE_OK );
+ }
+
+ if( rc==SQLITE_OK && wrflag ){
+ if( pBt->readOnly ){
+ rc = SQLITE_READONLY;
+ }else{
+ rc = sqlite3PagerBegin(pBt->pPage1->pDbPage, wrflag>1);
+ if( rc==SQLITE_OK ){
+ rc = newDatabase(pBt);
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ if( wrflag ) pBt->inStmt = 0;
+ }else{
+ unlockBtreeIfUnused(pBt);
+ }
+ }while( rc==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
+ sqlite3BtreeInvokeBusyHandler(pBt, 0) );
+
+ if( rc==SQLITE_OK ){
+ if( p->inTrans==TRANS_NONE ){
+ pBt->nTransaction++;
+ }
+ p->inTrans = (wrflag?TRANS_WRITE:TRANS_READ);
+ if( p->inTrans>pBt->inTransaction ){
+ pBt->inTransaction = p->inTrans;
+ }
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ if( wrflag>1 ){
+ assert( !pBt->pExclusive );
+ pBt->pExclusive = p;
+ }
+#endif
+ }
+
+
+trans_begun:
+ btreeIntegrity(p);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+
+/*
+** Set the pointer-map entries for all children of page pPage. Also, if
+** pPage contains cells that point to overflow pages, set the pointer
+** map entries for the overflow pages as well.
+*/
+static int setChildPtrmaps(MemPage *pPage){
+ int i; /* Counter variable */
+ int nCell; /* Number of cells in page pPage */
+ int rc; /* Return code */
+ BtShared *pBt = pPage->pBt;
+ int isInitOrig = pPage->isInit;
+ Pgno pgno = pPage->pgno;
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ rc = sqlite3BtreeInitPage(pPage, pPage->pParent);
+ if( rc!=SQLITE_OK ){
+ goto set_child_ptrmaps_out;
+ }
+ nCell = pPage->nCell;
+
+ for(i=0; i<nCell; i++){
+ u8 *pCell = findCell(pPage, i);
+
+ rc = ptrmapPutOvflPtr(pPage, pCell);
+ if( rc!=SQLITE_OK ){
+ goto set_child_ptrmaps_out;
+ }
+
+ if( !pPage->leaf ){
+ Pgno childPgno = get4byte(pCell);
+ rc = ptrmapPut(pBt, childPgno, PTRMAP_BTREE, pgno);
+ if( rc!=SQLITE_OK ) goto set_child_ptrmaps_out;
+ }
+ }
+
+ if( !pPage->leaf ){
+ Pgno childPgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
+ rc = ptrmapPut(pBt, childPgno, PTRMAP_BTREE, pgno);
+ }
+
+set_child_ptrmaps_out:
+ pPage->isInit = isInitOrig;
+ return rc;
+}
+
+/*
+** Somewhere on pPage, which is guarenteed to be a btree page, not an overflow
+** page, is a pointer to page iFrom. Modify this pointer so that it points to
+** iTo. Parameter eType describes the type of pointer to be modified, as
+** follows:
+**
+** PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child
+** page of pPage.
+**
+** PTRMAP_OVERFLOW1: pPage is a btree-page. The pointer points at an overflow
+** page pointed to by one of the cells on pPage.
+**
+** PTRMAP_OVERFLOW2: pPage is an overflow-page. The pointer points at the next
+** overflow page in the list.
+*/
+static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ if( eType==PTRMAP_OVERFLOW2 ){
+ /* The pointer is always the first 4 bytes of the page in this case. */
+ if( get4byte(pPage->aData)!=iFrom ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ put4byte(pPage->aData, iTo);
+ }else{
+ int isInitOrig = pPage->isInit;
+ int i;
+ int nCell;
+
+ sqlite3BtreeInitPage(pPage, 0);
+ nCell = pPage->nCell;
+
+ for(i=0; i<nCell; i++){
+ u8 *pCell = findCell(pPage, i);
+ if( eType==PTRMAP_OVERFLOW1 ){
+ CellInfo info;
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ if( info.iOverflow ){
+ if( iFrom==get4byte(&pCell[info.iOverflow]) ){
+ put4byte(&pCell[info.iOverflow], iTo);
+ break;
+ }
+ }
+ }else{
+ if( get4byte(pCell)==iFrom ){
+ put4byte(pCell, iTo);
+ break;
+ }
+ }
+ }
+
+ if( i==nCell ){
+ if( eType!=PTRMAP_BTREE ||
+ get4byte(&pPage->aData[pPage->hdrOffset+8])!=iFrom ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ put4byte(&pPage->aData[pPage->hdrOffset+8], iTo);
+ }
+
+ pPage->isInit = isInitOrig;
+ }
+ return SQLITE_OK;
+}
+
+
+/*
+** Move the open database page pDbPage to location iFreePage in the
+** database. The pDbPage reference remains valid.
+*/
+static int relocatePage(
+ BtShared *pBt, /* Btree */
+ MemPage *pDbPage, /* Open page to move */
+ u8 eType, /* Pointer map 'type' entry for pDbPage */
+ Pgno iPtrPage, /* Pointer map 'page-no' entry for pDbPage */
+ Pgno iFreePage /* The location to move pDbPage to */
+){
+ MemPage *pPtrPage; /* The page that contains a pointer to pDbPage */
+ Pgno iDbPage = pDbPage->pgno;
+ Pager *pPager = pBt->pPager;
+ int rc;
+
+ assert( eType==PTRMAP_OVERFLOW2 || eType==PTRMAP_OVERFLOW1 ||
+ eType==PTRMAP_BTREE || eType==PTRMAP_ROOTPAGE );
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( pDbPage->pBt==pBt );
+
+ /* Move page iDbPage from its current location to page number iFreePage */
+ TRACE(("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n",
+ iDbPage, iFreePage, iPtrPage, eType));
+ rc = sqlite3PagerMovepage(pPager, pDbPage->pDbPage, iFreePage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pDbPage->pgno = iFreePage;
+
+ /* If pDbPage was a btree-page, then it may have child pages and/or cells
+ ** that point to overflow pages. The pointer map entries for all these
+ ** pages need to be changed.
+ **
+ ** If pDbPage is an overflow page, then the first 4 bytes may store a
+ ** pointer to a subsequent overflow page. If this is the case, then
+ ** the pointer map needs to be updated for the subsequent overflow page.
+ */
+ if( eType==PTRMAP_BTREE || eType==PTRMAP_ROOTPAGE ){
+ rc = setChildPtrmaps(pDbPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }else{
+ Pgno nextOvfl = get4byte(pDbPage->aData);
+ if( nextOvfl!=0 ){
+ rc = ptrmapPut(pBt, nextOvfl, PTRMAP_OVERFLOW2, iFreePage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ }
+
+ /* Fix the database pointer on page iPtrPage that pointed at iDbPage so
+ ** that it points at iFreePage. Also fix the pointer map entry for
+ ** iPtrPage.
+ */
+ if( eType!=PTRMAP_ROOTPAGE ){
+ rc = sqlite3BtreeGetPage(pBt, iPtrPage, &pPtrPage, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = sqlite3PagerWrite(pPtrPage->pDbPage);
+ if( rc!=SQLITE_OK ){
+ releasePage(pPtrPage);
+ return rc;
+ }
+ rc = modifyPagePointer(pPtrPage, iDbPage, iFreePage, eType);
+ releasePage(pPtrPage);
+ if( rc==SQLITE_OK ){
+ rc = ptrmapPut(pBt, iFreePage, eType, iPtrPage);
+ }
+ }
+ return rc;
+}
+
+/* Forward declaration required by incrVacuumStep(). */
+static int allocateBtreePage(BtShared *, MemPage **, Pgno *, Pgno, u8);
+
+/*
+** Perform a single step of an incremental-vacuum. If successful,
+** return SQLITE_OK. If there is no work to do (and therefore no
+** point in calling this function again), return SQLITE_DONE.
+**
+** More specificly, this function attempts to re-organize the
+** database so that the last page of the file currently in use
+** is no longer in use.
+**
+** If the nFin parameter is non-zero, the implementation assumes
+** that the caller will keep calling incrVacuumStep() until
+** it returns SQLITE_DONE or an error, and that nFin is the
+** number of pages the database file will contain after this
+** process is complete.
+*/
+static int incrVacuumStep(BtShared *pBt, Pgno nFin){
+ Pgno iLastPg; /* Last page in the database */
+ Pgno nFreeList; /* Number of pages still on the free-list */
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ iLastPg = pBt->nTrunc;
+ if( iLastPg==0 ){
+ iLastPg = sqlite3PagerPagecount(pBt->pPager);
+ }
+
+ if( !PTRMAP_ISPAGE(pBt, iLastPg) && iLastPg!=PENDING_BYTE_PAGE(pBt) ){
+ int rc;
+ u8 eType;
+ Pgno iPtrPage;
+
+ nFreeList = get4byte(&pBt->pPage1->aData[36]);
+ if( nFreeList==0 || nFin==iLastPg ){
+ return SQLITE_DONE;
+ }
+
+ rc = ptrmapGet(pBt, iLastPg, &eType, &iPtrPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ if( eType==PTRMAP_ROOTPAGE ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ if( eType==PTRMAP_FREEPAGE ){
+ if( nFin==0 ){
+ /* Remove the page from the files free-list. This is not required
+ ** if nFin is non-zero. In that case, the free-list will be
+ ** truncated to zero after this function returns, so it doesn't
+ ** matter if it still contains some garbage entries.
+ */
+ Pgno iFreePg;
+ MemPage *pFreePg;
+ rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iLastPg, 1);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( iFreePg==iLastPg );
+ releasePage(pFreePg);
+ }
+ } else {
+ Pgno iFreePg; /* Index of free page to move pLastPg to */
+ MemPage *pLastPg;
+
+ rc = sqlite3BtreeGetPage(pBt, iLastPg, &pLastPg, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ /* If nFin is zero, this loop runs exactly once and page pLastPg
+ ** is swapped with the first free page pulled off the free list.
+ **
+ ** On the other hand, if nFin is greater than zero, then keep
+ ** looping until a free-page located within the first nFin pages
+ ** of the file is found.
+ */
+ do {
+ MemPage *pFreePg;
+ rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, 0, 0);
+ if( rc!=SQLITE_OK ){
+ releasePage(pLastPg);
+ return rc;
+ }
+ releasePage(pFreePg);
+ }while( nFin!=0 && iFreePg>nFin );
+ assert( iFreePg<iLastPg );
+
+ rc = sqlite3PagerWrite(pLastPg->pDbPage);
+ if( rc==SQLITE_OK ){
+ rc = relocatePage(pBt, pLastPg, eType, iPtrPage, iFreePg);
+ }
+ releasePage(pLastPg);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ }
+
+ pBt->nTrunc = iLastPg - 1;
+ while( pBt->nTrunc==PENDING_BYTE_PAGE(pBt)||PTRMAP_ISPAGE(pBt, pBt->nTrunc) ){
+ pBt->nTrunc--;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** A write-transaction must be opened before calling this function.
+** It performs a single unit of work towards an incremental vacuum.
+**
+** If the incremental vacuum is finished after this function has run,
+** SQLITE_DONE is returned. If it is not finished, but no error occured,
+** SQLITE_OK is returned. Otherwise an SQLite error code.
+*/
+SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *p){
+ int rc;
+ BtShared *pBt = p->pBt;
+
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ assert( pBt->inTransaction==TRANS_WRITE && p->inTrans==TRANS_WRITE );
+ if( !pBt->autoVacuum ){
+ rc = SQLITE_DONE;
+ }else{
+ invalidateAllOverflowCache(pBt);
+ rc = incrVacuumStep(pBt, 0);
+ }
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** This routine is called prior to sqlite3PagerCommit when a transaction
+** is commited for an auto-vacuum database.
+**
+** If SQLITE_OK is returned, then *pnTrunc is set to the number of pages
+** the database file should be truncated to during the commit process.
+** i.e. the database has been reorganized so that only the first *pnTrunc
+** pages are in use.
+*/
+static int autoVacuumCommit(BtShared *pBt, Pgno *pnTrunc){
+ int rc = SQLITE_OK;
+ Pager *pPager = pBt->pPager;
+#ifndef NDEBUG
+ int nRef = sqlite3PagerRefcount(pPager);
+#endif
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ invalidateAllOverflowCache(pBt);
+ assert(pBt->autoVacuum);
+ if( !pBt->incrVacuum ){
+ Pgno nFin = 0;
+
+ if( pBt->nTrunc==0 ){
+ Pgno nFree;
+ Pgno nPtrmap;
+ const int pgsz = pBt->pageSize;
+ Pgno nOrig = sqlite3PagerPagecount(pBt->pPager);
+
+ if( PTRMAP_ISPAGE(pBt, nOrig) ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ if( nOrig==PENDING_BYTE_PAGE(pBt) ){
+ nOrig--;
+ }
+ nFree = get4byte(&pBt->pPage1->aData[36]);
+ nPtrmap = (nFree-nOrig+PTRMAP_PAGENO(pBt, nOrig)+pgsz/5)/(pgsz/5);
+ nFin = nOrig - nFree - nPtrmap;
+ if( nOrig>PENDING_BYTE_PAGE(pBt) && nFin<=PENDING_BYTE_PAGE(pBt) ){
+ nFin--;
+ }
+ while( PTRMAP_ISPAGE(pBt, nFin) || nFin==PENDING_BYTE_PAGE(pBt) ){
+ nFin--;
+ }
+ }
+
+ while( rc==SQLITE_OK ){
+ rc = incrVacuumStep(pBt, nFin);
+ }
+ if( rc==SQLITE_DONE ){
+ assert(nFin==0 || pBt->nTrunc==0 || nFin<=pBt->nTrunc);
+ rc = SQLITE_OK;
+ if( pBt->nTrunc && nFin ){
+ rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
+ put4byte(&pBt->pPage1->aData[32], 0);
+ put4byte(&pBt->pPage1->aData[36], 0);
+ pBt->nTrunc = nFin;
+ }
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3PagerRollback(pPager);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ *pnTrunc = pBt->nTrunc;
+ pBt->nTrunc = 0;
+ }
+ assert( nRef==sqlite3PagerRefcount(pPager) );
+ return rc;
+}
+
+#endif
+
+/*
+** This routine does the first phase of a two-phase commit. This routine
+** causes a rollback journal to be created (if it does not already exist)
+** and populated with enough information so that if a power loss occurs
+** the database can be restored to its original state by playing back
+** the journal. Then the contents of the journal are flushed out to
+** the disk. After the journal is safely on oxide, the changes to the
+** database are written into the database file and flushed to oxide.
+** At the end of this call, the rollback journal still exists on the
+** disk and we are still holding all locks, so the transaction has not
+** committed. See sqlite3BtreeCommit() for the second phase of the
+** commit process.
+**
+** This call is a no-op if no write-transaction is currently active on pBt.
+**
+** Otherwise, sync the database file for the btree pBt. zMaster points to
+** the name of a master journal file that should be written into the
+** individual journal file, or is NULL, indicating no master journal file
+** (single database transaction).
+**
+** When this is called, the master journal should already have been
+** created, populated with this journal pointer and synced to disk.
+**
+** Once this is routine has returned, the only thing required to commit
+** the write-transaction for this database file is to delete the journal.
+*/
+SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
+ int rc = SQLITE_OK;
+ if( p->inTrans==TRANS_WRITE ){
+ BtShared *pBt = p->pBt;
+ Pgno nTrunc = 0;
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ rc = autoVacuumCommit(pBt, &nTrunc);
+ if( rc!=SQLITE_OK ){
+ sqlite3BtreeLeave(p);
+ return rc;
+ }
+ }
+#endif
+ rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, nTrunc, 0);
+ sqlite3BtreeLeave(p);
+ }
+ return rc;
+}
+
+/*
+** Commit the transaction currently in progress.
+**
+** This routine implements the second phase of a 2-phase commit. The
+** sqlite3BtreeSync() routine does the first phase and should be invoked
+** prior to calling this routine. The sqlite3BtreeSync() routine did
+** all the work of writing information out to disk and flushing the
+** contents so that they are written onto the disk platter. All this
+** routine has to do is delete or truncate the rollback journal
+** (which causes the transaction to commit) and drop locks.
+**
+** This will release the write lock on the database file. If there
+** are no active cursors, it also releases the read lock.
+*/
+SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p){
+ BtShared *pBt = p->pBt;
+
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ btreeIntegrity(p);
+
+ /* If the handle has a write-transaction open, commit the shared-btrees
+ ** transaction and set the shared state to TRANS_READ.
+ */
+ if( p->inTrans==TRANS_WRITE ){
+ int rc;
+ assert( pBt->inTransaction==TRANS_WRITE );
+ assert( pBt->nTransaction>0 );
+ rc = sqlite3PagerCommitPhaseTwo(pBt->pPager);
+ if( rc!=SQLITE_OK ){
+ sqlite3BtreeLeave(p);
+ return rc;
+ }
+ pBt->inTransaction = TRANS_READ;
+ pBt->inStmt = 0;
+ }
+ unlockAllTables(p);
+
+ /* If the handle has any kind of transaction open, decrement the transaction
+ ** count of the shared btree. If the transaction count reaches 0, set
+ ** the shared state to TRANS_NONE. The unlockBtreeIfUnused() call below
+ ** will unlock the pager.
+ */
+ if( p->inTrans!=TRANS_NONE ){
+ pBt->nTransaction--;
+ if( 0==pBt->nTransaction ){
+ pBt->inTransaction = TRANS_NONE;
+ }
+ }
+
+ /* Set the handles current transaction state to TRANS_NONE and unlock
+ ** the pager if this call closed the only read or write transaction.
+ */
+ p->inTrans = TRANS_NONE;
+ unlockBtreeIfUnused(pBt);
+
+ btreeIntegrity(p);
+ sqlite3BtreeLeave(p);
+ return SQLITE_OK;
+}
+
+/*
+** Do both phases of a commit.
+*/
+SQLITE_PRIVATE int sqlite3BtreeCommit(Btree *p){
+ int rc;
+ sqlite3BtreeEnter(p);
+ rc = sqlite3BtreeCommitPhaseOne(p, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3BtreeCommitPhaseTwo(p);
+ }
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+#ifndef NDEBUG
+/*
+** Return the number of write-cursors open on this handle. This is for use
+** in assert() expressions, so it is only compiled if NDEBUG is not
+** defined.
+**
+** For the purposes of this routine, a write-cursor is any cursor that
+** is capable of writing to the databse. That means the cursor was
+** originally opened for writing and the cursor has not be disabled
+** by having its state changed to CURSOR_FAULT.
+*/
+static int countWriteCursors(BtShared *pBt){
+ BtCursor *pCur;
+ int r = 0;
+ for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+ if( pCur->wrFlag && pCur->eState!=CURSOR_FAULT ) r++;
+ }
+ return r;
+}
+#endif
+
+/*
+** This routine sets the state to CURSOR_FAULT and the error
+** code to errCode for every cursor on BtShared that pBtree
+** references.
+**
+** Every cursor is tripped, including cursors that belong
+** to other database connections that happen to be sharing
+** the cache with pBtree.
+**
+** This routine gets called when a rollback occurs.
+** All cursors using the same cache must be tripped
+** to prevent them from trying to use the btree after
+** the rollback. The rollback may have deleted tables
+** or moved root pages, so it is not sufficient to
+** save the state of the cursor. The cursor must be
+** invalidated.
+*/
+SQLITE_PRIVATE void sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode){
+ BtCursor *p;
+ sqlite3BtreeEnter(pBtree);
+ for(p=pBtree->pBt->pCursor; p; p=p->pNext){
+ clearCursorPosition(p);
+ p->eState = CURSOR_FAULT;
+ p->skip = errCode;
+ }
+ sqlite3BtreeLeave(pBtree);
+}
+
+/*
+** Rollback the transaction in progress. All cursors will be
+** invalided by this operation. Any attempt to use a cursor
+** that was open at the beginning of this operation will result
+** in an error.
+**
+** This will release the write lock on the database file. If there
+** are no active cursors, it also releases the read lock.
+*/
+SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p){
+ int rc;
+ BtShared *pBt = p->pBt;
+ MemPage *pPage1;
+
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ rc = saveAllCursors(pBt, 0, 0);
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ if( rc!=SQLITE_OK ){
+ /* This is a horrible situation. An IO or malloc() error occured whilst
+ ** trying to save cursor positions. If this is an automatic rollback (as
+ ** the result of a constraint, malloc() failure or IO error) then
+ ** the cache may be internally inconsistent (not contain valid trees) so
+ ** we cannot simply return the error to the caller. Instead, abort
+ ** all queries that may be using any of the cursors that failed to save.
+ */
+ sqlite3BtreeTripAllCursors(p, rc);
+ }
+#endif
+ btreeIntegrity(p);
+ unlockAllTables(p);
+
+ if( p->inTrans==TRANS_WRITE ){
+ int rc2;
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ pBt->nTrunc = 0;
+#endif
+
+ assert( TRANS_WRITE==pBt->inTransaction );
+ rc2 = sqlite3PagerRollback(pBt->pPager);
+ if( rc2!=SQLITE_OK ){
+ rc = rc2;
+ }
+
+ /* The rollback may have destroyed the pPage1->aData value. So
+ ** call sqlite3BtreeGetPage() on page 1 again to make
+ ** sure pPage1->aData is set correctly. */
+ if( sqlite3BtreeGetPage(pBt, 1, &pPage1, 0)==SQLITE_OK ){
+ releasePage(pPage1);
+ }
+ assert( countWriteCursors(pBt)==0 );
+ pBt->inTransaction = TRANS_READ;
+ }
+
+ if( p->inTrans!=TRANS_NONE ){
+ assert( pBt->nTransaction>0 );
+ pBt->nTransaction--;
+ if( 0==pBt->nTransaction ){
+ pBt->inTransaction = TRANS_NONE;
+ }
+ }
+
+ p->inTrans = TRANS_NONE;
+ pBt->inStmt = 0;
+ unlockBtreeIfUnused(pBt);
+
+ btreeIntegrity(p);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Start a statement subtransaction. The subtransaction can
+** can be rolled back independently of the main transaction.
+** You must start a transaction before starting a subtransaction.
+** The subtransaction is ended automatically if the main transaction
+** commits or rolls back.
+**
+** Only one subtransaction may be active at a time. It is an error to try
+** to start a new subtransaction if another subtransaction is already active.
+**
+** Statement subtransactions are used around individual SQL statements
+** that are contained within a BEGIN...COMMIT block. If a constraint
+** error occurs within the statement, the effect of that one statement
+** can be rolled back without having to rollback the entire transaction.
+*/
+SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree *p){
+ int rc;
+ BtShared *pBt = p->pBt;
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ if( (p->inTrans!=TRANS_WRITE) || pBt->inStmt ){
+ rc = pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }else{
+ assert( pBt->inTransaction==TRANS_WRITE );
+ rc = pBt->readOnly ? SQLITE_OK : sqlite3PagerStmtBegin(pBt->pPager);
+ pBt->inStmt = 1;
+ }
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+
+/*
+** Commit the statment subtransaction currently in progress. If no
+** subtransaction is active, this is a no-op.
+*/
+SQLITE_PRIVATE int sqlite3BtreeCommitStmt(Btree *p){
+ int rc;
+ BtShared *pBt = p->pBt;
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ if( pBt->inStmt && !pBt->readOnly ){
+ rc = sqlite3PagerStmtCommit(pBt->pPager);
+ }else{
+ rc = SQLITE_OK;
+ }
+ pBt->inStmt = 0;
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Rollback the active statement subtransaction. If no subtransaction
+** is active this routine is a no-op.
+**
+** All cursors will be invalidated by this operation. Any attempt
+** to use a cursor that was open at the beginning of this operation
+** will result in an error.
+*/
+SQLITE_PRIVATE int sqlite3BtreeRollbackStmt(Btree *p){
+ int rc = SQLITE_OK;
+ BtShared *pBt = p->pBt;
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ if( pBt->inStmt && !pBt->readOnly ){
+ rc = sqlite3PagerStmtRollback(pBt->pPager);
+ assert( countWriteCursors(pBt)==0 );
+ pBt->inStmt = 0;
+ }
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Create a new cursor for the BTree whose root is on the page
+** iTable. The act of acquiring a cursor gets a read lock on
+** the database file.
+**
+** If wrFlag==0, then the cursor can only be used for reading.
+** If wrFlag==1, then the cursor can be used for reading or for
+** writing if other conditions for writing are also met. These
+** are the conditions that must be met in order for writing to
+** be allowed:
+**
+** 1: The cursor must have been opened with wrFlag==1
+**
+** 2: Other database connections that share the same pager cache
+** but which are not in the READ_UNCOMMITTED state may not have
+** cursors open with wrFlag==0 on the same table. Otherwise
+** the changes made by this write cursor would be visible to
+** the read cursors in the other database connection.
+**
+** 3: The database must be writable (not on read-only media)
+**
+** 4: There must be an active transaction.
+**
+** No checking is done to make sure that page iTable really is the
+** root page of a b-tree. If it is not, then the cursor acquired
+** will not work correctly.
+*/
+static int btreeCursor(
+ Btree *p, /* The btree */
+ int iTable, /* Root page of table to open */
+ int wrFlag, /* 1 to write. 0 read-only */
+ struct KeyInfo *pKeyInfo, /* First arg to comparison function */
+ BtCursor *pCur /* Space for new cursor */
+){
+ int rc;
+ BtShared *pBt = p->pBt;
+
+ assert( sqlite3BtreeHoldsMutex(p) );
+ if( wrFlag ){
+ if( pBt->readOnly ){
+ return SQLITE_READONLY;
+ }
+ if( checkReadLocks(p, iTable, 0) ){
+ return SQLITE_LOCKED;
+ }
+ }
+
+ if( pBt->pPage1==0 ){
+ rc = lockBtreeWithRetry(p);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ if( pBt->readOnly && wrFlag ){
+ return SQLITE_READONLY;
+ }
+ }
+ pCur->pgnoRoot = (Pgno)iTable;
+ if( iTable==1 && sqlite3PagerPagecount(pBt->pPager)==0 ){
+ rc = SQLITE_EMPTY;
+ goto create_cursor_exception;
+ }
+ rc = getAndInitPage(pBt, pCur->pgnoRoot, &pCur->pPage, 0);
+ if( rc!=SQLITE_OK ){
+ goto create_cursor_exception;
+ }
+
+ /* Now that no other errors can occur, finish filling in the BtCursor
+ ** variables, link the cursor into the BtShared list and set *ppCur (the
+ ** output argument to this function).
+ */
+ pCur->pKeyInfo = pKeyInfo;
+ pCur->pBtree = p;
+ pCur->pBt = pBt;
+ pCur->wrFlag = wrFlag;
+ pCur->pNext = pBt->pCursor;
+ if( pCur->pNext ){
+ pCur->pNext->pPrev = pCur;
+ }
+ pBt->pCursor = pCur;
+ pCur->eState = CURSOR_INVALID;
+
+ return SQLITE_OK;
+
+create_cursor_exception:
+ if( pCur ){
+ releasePage(pCur->pPage);
+ }
+ unlockBtreeIfUnused(pBt);
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3BtreeCursor(
+ Btree *p, /* The btree */
+ int iTable, /* Root page of table to open */
+ int wrFlag, /* 1 to write. 0 read-only */
+ struct KeyInfo *pKeyInfo, /* First arg to xCompare() */
+ BtCursor *pCur /* Write new cursor here */
+){
+ int rc;
+ sqlite3BtreeEnter(p);
+ p->pBt->db = p->db;
+ rc = btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3BtreeCursorSize(){
+ return sizeof(BtCursor);
+}
+
+
+
+/*
+** Close a cursor. The read lock on the database file is released
+** when the last cursor is closed.
+*/
+SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){
+ Btree *pBtree = pCur->pBtree;
+ if( pBtree ){
+ BtShared *pBt = pCur->pBt;
+ sqlite3BtreeEnter(pBtree);
+ pBt->db = pBtree->db;
+ clearCursorPosition(pCur);
+ if( pCur->pPrev ){
+ pCur->pPrev->pNext = pCur->pNext;
+ }else{
+ pBt->pCursor = pCur->pNext;
+ }
+ if( pCur->pNext ){
+ pCur->pNext->pPrev = pCur->pPrev;
+ }
+ releasePage(pCur->pPage);
+ unlockBtreeIfUnused(pBt);
+ invalidateOverflowCache(pCur);
+ /* sqlite3_free(pCur); */
+ sqlite3BtreeLeave(pBtree);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Make a temporary cursor by filling in the fields of pTempCur.
+** The temporary cursor is not on the cursor list for the Btree.
+*/
+SQLITE_PRIVATE void sqlite3BtreeGetTempCursor(BtCursor *pCur, BtCursor *pTempCur){
+ assert( cursorHoldsMutex(pCur) );
+ memcpy(pTempCur, pCur, sizeof(*pCur));
+ pTempCur->pNext = 0;
+ pTempCur->pPrev = 0;
+ if( pTempCur->pPage ){
+ sqlite3PagerRef(pTempCur->pPage->pDbPage);
+ }
+}
+
+/*
+** Delete a temporary cursor such as was made by the CreateTemporaryCursor()
+** function above.
+*/
+SQLITE_PRIVATE void sqlite3BtreeReleaseTempCursor(BtCursor *pCur){
+ assert( cursorHoldsMutex(pCur) );
+ if( pCur->pPage ){
+ sqlite3PagerUnref(pCur->pPage->pDbPage);
+ }
+}
+
+/*
+** Make sure the BtCursor* given in the argument has a valid
+** BtCursor.info structure. If it is not already valid, call
+** sqlite3BtreeParseCell() to fill it in.
+**
+** BtCursor.info is a cache of the information in the current cell.
+** Using this cache reduces the number of calls to sqlite3BtreeParseCell().
+**
+** 2007-06-25: There is a bug in some versions of MSVC that cause the
+** compiler to crash when getCellInfo() is implemented as a macro.
+** But there is a measureable speed advantage to using the macro on gcc
+** (when less compiler optimizations like -Os or -O0 are used and the
+** compiler is not doing agressive inlining.) So we use a real function
+** for MSVC and a macro for everything else. Ticket #2457.
+*/
+#ifndef NDEBUG
+ static void assertCellInfo(BtCursor *pCur){
+ CellInfo info;
+ memset(&info, 0, sizeof(info));
+ sqlite3BtreeParseCell(pCur->pPage, pCur->idx, &info);
+ assert( memcmp(&info, &pCur->info, sizeof(info))==0 );
+ }
+#else
+ #define assertCellInfo(x)
+#endif
+#ifdef _MSC_VER
+ /* Use a real function in MSVC to work around bugs in that compiler. */
+ static void getCellInfo(BtCursor *pCur){
+ if( pCur->info.nSize==0 ){
+ sqlite3BtreeParseCell(pCur->pPage, pCur->idx, &pCur->info);
+ pCur->validNKey = 1;
+ }else{
+ assertCellInfo(pCur);
+ }
+ }
+#else /* if not _MSC_VER */
+ /* Use a macro in all other compilers so that the function is inlined */
+#define getCellInfo(pCur) \
+ if( pCur->info.nSize==0 ){ \
+ sqlite3BtreeParseCell(pCur->pPage, pCur->idx, &pCur->info); \
+ pCur->validNKey = 1; \
+ }else{ \
+ assertCellInfo(pCur); \
+ }
+#endif /* _MSC_VER */
+
+/*
+** Set *pSize to the size of the buffer needed to hold the value of
+** the key for the current entry. If the cursor is not pointing
+** to a valid entry, *pSize is set to 0.
+**
+** For a table with the INTKEY flag set, this routine returns the key
+** itself, not the number of bytes in the key.
+*/
+SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){
+ int rc;
+
+ assert( cursorHoldsMutex(pCur) );
+ rc = restoreOrClearCursorPosition(pCur);
+ if( rc==SQLITE_OK ){
+ assert( pCur->eState==CURSOR_INVALID || pCur->eState==CURSOR_VALID );
+ if( pCur->eState==CURSOR_INVALID ){
+ *pSize = 0;
+ }else{
+ getCellInfo(pCur);
+ *pSize = pCur->info.nKey;
+ }
+ }
+ return rc;
+}
+
+/*
+** Set *pSize to the number of bytes of data in the entry the
+** cursor currently points to. Always return SQLITE_OK.
+** Failure is not possible. If the cursor is not currently
+** pointing to an entry (which can happen, for example, if
+** the database is empty) then *pSize is set to 0.
+*/
+SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor *pCur, u32 *pSize){
+ int rc;
+
+ assert( cursorHoldsMutex(pCur) );
+ rc = restoreOrClearCursorPosition(pCur);
+ if( rc==SQLITE_OK ){
+ assert( pCur->eState==CURSOR_INVALID || pCur->eState==CURSOR_VALID );
+ if( pCur->eState==CURSOR_INVALID ){
+ /* Not pointing at a valid entry - set *pSize to 0. */
+ *pSize = 0;
+ }else{
+ getCellInfo(pCur);
+ *pSize = pCur->info.nData;
+ }
+ }
+ return rc;
+}
+
+/*
+** Given the page number of an overflow page in the database (parameter
+** ovfl), this function finds the page number of the next page in the
+** linked list of overflow pages. If possible, it uses the auto-vacuum
+** pointer-map data instead of reading the content of page ovfl to do so.
+**
+** If an error occurs an SQLite error code is returned. Otherwise:
+**
+** Unless pPgnoNext is NULL, the page number of the next overflow
+** page in the linked list is written to *pPgnoNext. If page ovfl
+** is the last page in its linked list, *pPgnoNext is set to zero.
+**
+** If ppPage is not NULL, *ppPage is set to the MemPage* handle
+** for page ovfl. The underlying pager page may have been requested
+** with the noContent flag set, so the page data accessable via
+** this handle may not be trusted.
+*/
+static int getOverflowPage(
+ BtShared *pBt,
+ Pgno ovfl, /* Overflow page */
+ MemPage **ppPage, /* OUT: MemPage handle */
+ Pgno *pPgnoNext /* OUT: Next overflow page number */
+){
+ Pgno next = 0;
+ int rc;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ /* One of these must not be NULL. Otherwise, why call this function? */
+ assert(ppPage || pPgnoNext);
+
+ /* If pPgnoNext is NULL, then this function is being called to obtain
+ ** a MemPage* reference only. No page-data is required in this case.
+ */
+ if( !pPgnoNext ){
+ return sqlite3BtreeGetPage(pBt, ovfl, ppPage, 1);
+ }
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* Try to find the next page in the overflow list using the
+ ** autovacuum pointer-map pages. Guess that the next page in
+ ** the overflow list is page number (ovfl+1). If that guess turns
+ ** out to be wrong, fall back to loading the data of page
+ ** number ovfl to determine the next page number.
+ */
+ if( pBt->autoVacuum ){
+ Pgno pgno;
+ Pgno iGuess = ovfl+1;
+ u8 eType;
+
+ while( PTRMAP_ISPAGE(pBt, iGuess) || iGuess==PENDING_BYTE_PAGE(pBt) ){
+ iGuess++;
+ }
+
+ if( iGuess<=sqlite3PagerPagecount(pBt->pPager) ){
+ rc = ptrmapGet(pBt, iGuess, &eType, &pgno);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ if( eType==PTRMAP_OVERFLOW2 && pgno==ovfl ){
+ next = iGuess;
+ }
+ }
+ }
+#endif
+
+ if( next==0 || ppPage ){
+ MemPage *pPage = 0;
+
+ rc = sqlite3BtreeGetPage(pBt, ovfl, &pPage, next!=0);
+ assert(rc==SQLITE_OK || pPage==0);
+ if( next==0 && rc==SQLITE_OK ){
+ next = get4byte(pPage->aData);
+ }
+
+ if( ppPage ){
+ *ppPage = pPage;
+ }else{
+ releasePage(pPage);
+ }
+ }
+ *pPgnoNext = next;
+
+ return rc;
+}
+
+/*
+** Copy data from a buffer to a page, or from a page to a buffer.
+**
+** pPayload is a pointer to data stored on database page pDbPage.
+** If argument eOp is false, then nByte bytes of data are copied
+** from pPayload to the buffer pointed at by pBuf. If eOp is true,
+** then sqlite3PagerWrite() is called on pDbPage and nByte bytes
+** of data are copied from the buffer pBuf to pPayload.
+**
+** SQLITE_OK is returned on success, otherwise an error code.
+*/
+static int copyPayload(
+ void *pPayload, /* Pointer to page data */
+ void *pBuf, /* Pointer to buffer */
+ int nByte, /* Number of bytes to copy */
+ int eOp, /* 0 -> copy from page, 1 -> copy to page */
+ DbPage *pDbPage /* Page containing pPayload */
+){
+ if( eOp ){
+ /* Copy data from buffer to page (a write operation) */
+ int rc = sqlite3PagerWrite(pDbPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ memcpy(pPayload, pBuf, nByte);
+ }else{
+ /* Copy data from page to buffer (a read operation) */
+ memcpy(pBuf, pPayload, nByte);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** This function is used to read or overwrite payload information
+** for the entry that the pCur cursor is pointing to. If the eOp
+** parameter is 0, this is a read operation (data copied into
+** buffer pBuf). If it is non-zero, a write (data copied from
+** buffer pBuf).
+**
+** A total of "amt" bytes are read or written beginning at "offset".
+** Data is read to or from the buffer pBuf.
+**
+** This routine does not make a distinction between key and data.
+** It just reads or writes bytes from the payload area. Data might
+** appear on the main page or be scattered out on multiple overflow
+** pages.
+**
+** If the BtCursor.isIncrblobHandle flag is set, and the current
+** cursor entry uses one or more overflow pages, this function
+** allocates space for and lazily popluates the overflow page-list
+** cache array (BtCursor.aOverflow). Subsequent calls use this
+** cache to make seeking to the supplied offset more efficient.
+**
+** Once an overflow page-list cache has been allocated, it may be
+** invalidated if some other cursor writes to the same table, or if
+** the cursor is moved to a different row. Additionally, in auto-vacuum
+** mode, the following events may invalidate an overflow page-list cache.
+**
+** * An incremental vacuum,
+** * A commit in auto_vacuum="full" mode,
+** * Creating a table (may require moving an overflow page).
+*/
+static int accessPayload(
+ BtCursor *pCur, /* Cursor pointing to entry to read from */
+ int offset, /* Begin reading this far into payload */
+ int amt, /* Read this many bytes */
+ unsigned char *pBuf, /* Write the bytes into this buffer */
+ int skipKey, /* offset begins at data if this is true */
+ int eOp /* zero to read. non-zero to write. */
+){
+ unsigned char *aPayload;
+ int rc = SQLITE_OK;
+ u32 nKey;
+ int iIdx = 0;
+ MemPage *pPage = pCur->pPage; /* Btree page of current cursor entry */
+ BtShared *pBt; /* Btree this cursor belongs to */
+
+ assert( pPage );
+ assert( pCur->eState==CURSOR_VALID );
+ assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
+ assert( offset>=0 );
+ assert( cursorHoldsMutex(pCur) );
+
+ getCellInfo(pCur);
+ aPayload = pCur->info.pCell + pCur->info.nHeader;
+ nKey = (pPage->intKey ? 0 : pCur->info.nKey);
+
+ if( skipKey ){
+ offset += nKey;
+ }
+ if( offset+amt > nKey+pCur->info.nData ){
+ /* Trying to read or write past the end of the data is an error */
+ return SQLITE_ERROR;
+ }
+
+ /* Check if data must be read/written to/from the btree page itself. */
+ if( offset<pCur->info.nLocal ){
+ int a = amt;
+ if( a+offset>pCur->info.nLocal ){
+ a = pCur->info.nLocal - offset;
+ }
+ rc = copyPayload(&aPayload[offset], pBuf, a, eOp, pPage->pDbPage);
+ offset = 0;
+ pBuf += a;
+ amt -= a;
+ }else{
+ offset -= pCur->info.nLocal;
+ }
+
+ pBt = pCur->pBt;
+ if( rc==SQLITE_OK && amt>0 ){
+ const int ovflSize = pBt->usableSize - 4; /* Bytes content per ovfl page */
+ Pgno nextPage;
+
+ nextPage = get4byte(&aPayload[pCur->info.nLocal]);
+
+#ifndef SQLITE_OMIT_INCRBLOB
+ /* If the isIncrblobHandle flag is set and the BtCursor.aOverflow[]
+ ** has not been allocated, allocate it now. The array is sized at
+ ** one entry for each overflow page in the overflow chain. The
+ ** page number of the first overflow page is stored in aOverflow[0],
+ ** etc. A value of 0 in the aOverflow[] array means "not yet known"
+ ** (the cache is lazily populated).
+ */
+ if( pCur->isIncrblobHandle && !pCur->aOverflow ){
+ int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize;
+ pCur->aOverflow = (Pgno *)sqlite3MallocZero(sizeof(Pgno)*nOvfl);
+ if( nOvfl && !pCur->aOverflow ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ /* If the overflow page-list cache has been allocated and the
+ ** entry for the first required overflow page is valid, skip
+ ** directly to it.
+ */
+ if( pCur->aOverflow && pCur->aOverflow[offset/ovflSize] ){
+ iIdx = (offset/ovflSize);
+ nextPage = pCur->aOverflow[iIdx];
+ offset = (offset%ovflSize);
+ }
+#endif
+
+ for( ; rc==SQLITE_OK && amt>0 && nextPage; iIdx++){
+
+#ifndef SQLITE_OMIT_INCRBLOB
+ /* If required, populate the overflow page-list cache. */
+ if( pCur->aOverflow ){
+ assert(!pCur->aOverflow[iIdx] || pCur->aOverflow[iIdx]==nextPage);
+ pCur->aOverflow[iIdx] = nextPage;
+ }
+#endif
+
+ if( offset>=ovflSize ){
+ /* The only reason to read this page is to obtain the page
+ ** number for the next page in the overflow chain. The page
+ ** data is not required. So first try to lookup the overflow
+ ** page-list cache, if any, then fall back to the getOverflowPage()
+ ** function.
+ */
+#ifndef SQLITE_OMIT_INCRBLOB
+ if( pCur->aOverflow && pCur->aOverflow[iIdx+1] ){
+ nextPage = pCur->aOverflow[iIdx+1];
+ } else
+#endif
+ rc = getOverflowPage(pBt, nextPage, 0, &nextPage);
+ offset -= ovflSize;
+ }else{
+ /* Need to read this page properly. It contains some of the
+ ** range of data that is being read (eOp==0) or written (eOp!=0).
+ */
+ DbPage *pDbPage;
+ int a = amt;
+ rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage);
+ if( rc==SQLITE_OK ){
+ aPayload = sqlite3PagerGetData(pDbPage);
+ nextPage = get4byte(aPayload);
+ if( a + offset > ovflSize ){
+ a = ovflSize - offset;
+ }
+ rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage);
+ sqlite3PagerUnref(pDbPage);
+ offset = 0;
+ amt -= a;
+ pBuf += a;
+ }
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK && amt>0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ return rc;
+}
+
+/*
+** Read part of the key associated with cursor pCur. Exactly
+** "amt" bytes will be transfered into pBuf[]. The transfer
+** begins at "offset".
+**
+** Return SQLITE_OK on success or an error code if anything goes
+** wrong. An error is returned if "offset+amt" is larger than
+** the available payload.
+*/
+SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
+ int rc;
+
+ assert( cursorHoldsMutex(pCur) );
+ rc = restoreOrClearCursorPosition(pCur);
+ if( rc==SQLITE_OK ){
+ assert( pCur->eState==CURSOR_VALID );
+ assert( pCur->pPage!=0 );
+ if( pCur->pPage->intKey ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ assert( pCur->pPage->intKey==0 );
+ assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
+ rc = accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0, 0);
+ }
+ return rc;
+}
+
+/*
+** Read part of the data associated with cursor pCur. Exactly
+** "amt" bytes will be transfered into pBuf[]. The transfer
+** begins at "offset".
+**
+** Return SQLITE_OK on success or an error code if anything goes
+** wrong. An error is returned if "offset+amt" is larger than
+** the available payload.
+*/
+SQLITE_PRIVATE int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
+ int rc;
+
+ assert( cursorHoldsMutex(pCur) );
+ rc = restoreOrClearCursorPosition(pCur);
+ if( rc==SQLITE_OK ){
+ assert( pCur->eState==CURSOR_VALID );
+ assert( pCur->pPage!=0 );
+ assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
+ rc = accessPayload(pCur, offset, amt, pBuf, 1, 0);
+ }
+ return rc;
+}
+
+/*
+** Return a pointer to payload information from the entry that the
+** pCur cursor is pointing to. The pointer is to the beginning of
+** the key if skipKey==0 and it points to the beginning of data if
+** skipKey==1. The number of bytes of available key/data is written
+** into *pAmt. If *pAmt==0, then the value returned will not be
+** a valid pointer.
+**
+** This routine is an optimization. It is common for the entire key
+** and data to fit on the local page and for there to be no overflow
+** pages. When that is so, this routine can be used to access the
+** key and data without making a copy. If the key and/or data spills
+** onto overflow pages, then accessPayload() must be used to reassembly
+** the key/data and copy it into a preallocated buffer.
+**
+** The pointer returned by this routine looks directly into the cached
+** page of the database. The data might change or move the next time
+** any btree routine is called.
+*/
+static const unsigned char *fetchPayload(
+ BtCursor *pCur, /* Cursor pointing to entry to read from */
+ int *pAmt, /* Write the number of available bytes here */
+ int skipKey /* read beginning at data if this is true */
+){
+ unsigned char *aPayload;
+ MemPage *pPage;
+ u32 nKey;
+ int nLocal;
+
+ assert( pCur!=0 && pCur->pPage!=0 );
+ assert( pCur->eState==CURSOR_VALID );
+ assert( cursorHoldsMutex(pCur) );
+ pPage = pCur->pPage;
+ assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
+ getCellInfo(pCur);
+ aPayload = pCur->info.pCell;
+ aPayload += pCur->info.nHeader;
+ if( pPage->intKey ){
+ nKey = 0;
+ }else{
+ nKey = pCur->info.nKey;
+ }
+ if( skipKey ){
+ aPayload += nKey;
+ nLocal = pCur->info.nLocal - nKey;
+ }else{
+ nLocal = pCur->info.nLocal;
+ if( nLocal>nKey ){
+ nLocal = nKey;
+ }
+ }
+ *pAmt = nLocal;
+ return aPayload;
+}
+
+
+/*
+** For the entry that cursor pCur is point to, return as
+** many bytes of the key or data as are available on the local
+** b-tree page. Write the number of available bytes into *pAmt.
+**
+** The pointer returned is ephemeral. The key/data may move
+** or be destroyed on the next call to any Btree routine,
+** including calls from other threads against the same cache.
+** Hence, a mutex on the BtShared should be held prior to calling
+** this routine.
+**
+** These routines is used to get quick access to key and data
+** in the common case where no overflow pages are used.
+*/
+SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor *pCur, int *pAmt){
+ assert( cursorHoldsMutex(pCur) );
+ if( pCur->eState==CURSOR_VALID ){
+ return (const void*)fetchPayload(pCur, pAmt, 0);
+ }
+ return 0;
+}
+SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor *pCur, int *pAmt){
+ assert( cursorHoldsMutex(pCur) );
+ if( pCur->eState==CURSOR_VALID ){
+ return (const void*)fetchPayload(pCur, pAmt, 1);
+ }
+ return 0;
+}
+
+
+/*
+** Move the cursor down to a new child page. The newPgno argument is the
+** page number of the child page to move to.
+*/
+static int moveToChild(BtCursor *pCur, u32 newPgno){
+ int rc;
+ MemPage *pNewPage;
+ MemPage *pOldPage;
+ BtShared *pBt = pCur->pBt;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( pCur->eState==CURSOR_VALID );
+ rc = getAndInitPage(pBt, newPgno, &pNewPage, pCur->pPage);
+ if( rc ) return rc;
+ pNewPage->idxParent = pCur->idx;
+ pOldPage = pCur->pPage;
+ pOldPage->idxShift = 0;
+ releasePage(pOldPage);
+ pCur->pPage = pNewPage;
+ pCur->idx = 0;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 0;
+ if( pNewPage->nCell<1 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return true if the page is the virtual root of its table.
+**
+** The virtual root page is the root page for most tables. But
+** for the table rooted on page 1, sometime the real root page
+** is empty except for the right-pointer. In such cases the
+** virtual root page is the page that the right-pointer of page
+** 1 is pointing to.
+*/
+SQLITE_PRIVATE int sqlite3BtreeIsRootPage(MemPage *pPage){
+ MemPage *pParent;
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ pParent = pPage->pParent;
+ if( pParent==0 ) return 1;
+ if( pParent->pgno>1 ) return 0;
+ if( get2byte(&pParent->aData[pParent->hdrOffset+3])==0 ) return 1;
+ return 0;
+}
+
+/*
+** Move the cursor up to the parent page.
+**
+** pCur->idx is set to the cell index that contains the pointer
+** to the page we are coming from. If we are coming from the
+** right-most child page then pCur->idx is set to one more than
+** the largest cell index.
+*/
+SQLITE_PRIVATE void sqlite3BtreeMoveToParent(BtCursor *pCur){
+ MemPage *pParent;
+ MemPage *pPage;
+ int idxParent;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( pCur->eState==CURSOR_VALID );
+ pPage = pCur->pPage;
+ assert( pPage!=0 );
+ assert( !sqlite3BtreeIsRootPage(pPage) );
+ pParent = pPage->pParent;
+ assert( pParent!=0 );
+ idxParent = pPage->idxParent;
+ sqlite3PagerRef(pParent->pDbPage);
+ releasePage(pPage);
+ pCur->pPage = pParent;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 0;
+ assert( pParent->idxShift==0 );
+ pCur->idx = idxParent;
+}
+
+/*
+** Move the cursor to the root page
+*/
+static int moveToRoot(BtCursor *pCur){
+ MemPage *pRoot;
+ int rc = SQLITE_OK;
+ Btree *p = pCur->pBtree;
+ BtShared *pBt = p->pBt;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( CURSOR_INVALID < CURSOR_REQUIRESEEK );
+ assert( CURSOR_VALID < CURSOR_REQUIRESEEK );
+ assert( CURSOR_FAULT > CURSOR_REQUIRESEEK );
+ if( pCur->eState>=CURSOR_REQUIRESEEK ){
+ if( pCur->eState==CURSOR_FAULT ){
+ return pCur->skip;
+ }
+ clearCursorPosition(pCur);
+ }
+ pRoot = pCur->pPage;
+ if( pRoot && pRoot->pgno==pCur->pgnoRoot ){
+ assert( pRoot->isInit );
+ }else{
+ if(
+ SQLITE_OK!=(rc = getAndInitPage(pBt, pCur->pgnoRoot, &pRoot, 0))
+ ){
+ pCur->eState = CURSOR_INVALID;
+ return rc;
+ }
+ releasePage(pCur->pPage);
+ pCur->pPage = pRoot;
+ }
+ pCur->idx = 0;
+ pCur->info.nSize = 0;
+ pCur->atLast = 0;
+ pCur->validNKey = 0;
+ if( pRoot->nCell==0 && !pRoot->leaf ){
+ Pgno subpage;
+ assert( pRoot->pgno==1 );
+ subpage = get4byte(&pRoot->aData[pRoot->hdrOffset+8]);
+ assert( subpage>0 );
+ pCur->eState = CURSOR_VALID;
+ rc = moveToChild(pCur, subpage);
+ }
+ pCur->eState = ((pCur->pPage->nCell>0)?CURSOR_VALID:CURSOR_INVALID);
+ return rc;
+}
+
+/*
+** Move the cursor down to the left-most leaf entry beneath the
+** entry to which it is currently pointing.
+**
+** The left-most leaf is the one with the smallest key - the first
+** in ascending order.
+*/
+static int moveToLeftmost(BtCursor *pCur){
+ Pgno pgno;
+ int rc = SQLITE_OK;
+ MemPage *pPage;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( pCur->eState==CURSOR_VALID );
+ while( rc==SQLITE_OK && !(pPage = pCur->pPage)->leaf ){
+ assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
+ pgno = get4byte(findCell(pPage, pCur->idx));
+ rc = moveToChild(pCur, pgno);
+ }
+ return rc;
+}
+
+/*
+** Move the cursor down to the right-most leaf entry beneath the
+** page to which it is currently pointing. Notice the difference
+** between moveToLeftmost() and moveToRightmost(). moveToLeftmost()
+** finds the left-most entry beneath the *entry* whereas moveToRightmost()
+** finds the right-most entry beneath the *page*.
+**
+** The right-most entry is the one with the largest key - the last
+** key in ascending order.
+*/
+static int moveToRightmost(BtCursor *pCur){
+ Pgno pgno;
+ int rc = SQLITE_OK;
+ MemPage *pPage;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( pCur->eState==CURSOR_VALID );
+ while( rc==SQLITE_OK && !(pPage = pCur->pPage)->leaf ){
+ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
+ pCur->idx = pPage->nCell;
+ rc = moveToChild(pCur, pgno);
+ }
+ if( rc==SQLITE_OK ){
+ pCur->idx = pPage->nCell - 1;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 0;
+ }
+ return SQLITE_OK;
+}
+
+/* Move the cursor to the first entry in the table. Return SQLITE_OK
+** on success. Set *pRes to 0 if the cursor actually points to something
+** or set *pRes to 1 if the table is empty.
+*/
+SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
+ int rc;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ rc = moveToRoot(pCur);
+ if( rc==SQLITE_OK ){
+ if( pCur->eState==CURSOR_INVALID ){
+ assert( pCur->pPage->nCell==0 );
+ *pRes = 1;
+ rc = SQLITE_OK;
+ }else{
+ assert( pCur->pPage->nCell>0 );
+ *pRes = 0;
+ rc = moveToLeftmost(pCur);
+ }
+ }
+ return rc;
+}
+
+/* Move the cursor to the last entry in the table. Return SQLITE_OK
+** on success. Set *pRes to 0 if the cursor actually points to something
+** or set *pRes to 1 if the table is empty.
+*/
+SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
+ int rc;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ rc = moveToRoot(pCur);
+ if( rc==SQLITE_OK ){
+ if( CURSOR_INVALID==pCur->eState ){
+ assert( pCur->pPage->nCell==0 );
+ *pRes = 1;
+ }else{
+ assert( pCur->eState==CURSOR_VALID );
+ *pRes = 0;
+ rc = moveToRightmost(pCur);
+ getCellInfo(pCur);
+ pCur->atLast = rc==SQLITE_OK;
+ }
+ }
+ return rc;
+}
+
+/* Move the cursor so that it points to an entry near the key
+** specified by pKey/nKey/pUnKey. Return a success code.
+**
+** For INTKEY tables, only the nKey parameter is used. pKey
+** and pUnKey must be NULL. For index tables, either pUnKey
+** must point to a key that has already been unpacked, or else
+** pKey/nKey describes a blob containing the key.
+**
+** If an exact match is not found, then the cursor is always
+** left pointing at a leaf page which would hold the entry if it
+** were present. The cursor might point to an entry that comes
+** before or after the key.
+**
+** The result of comparing the key with the entry to which the
+** cursor is written to *pRes if pRes!=NULL. The meaning of
+** this value is as follows:
+**
+** *pRes<0 The cursor is left pointing at an entry that
+** is smaller than pKey or if the table is empty
+** and the cursor is therefore left point to nothing.
+**
+** *pRes==0 The cursor is left pointing at an entry that
+** exactly matches pKey.
+**
+** *pRes>0 The cursor is left pointing at an entry that
+** is larger than pKey.
+**
+*/
+SQLITE_PRIVATE int sqlite3BtreeMoveto(
+ BtCursor *pCur, /* The cursor to be moved */
+ const void *pKey, /* The key content for indices. Not used by tables */
+ UnpackedRecord *pUnKey,/* Unpacked version of pKey */
+ i64 nKey, /* Size of pKey. Or the key for tables */
+ int biasRight, /* If true, bias the search to the high end */
+ int *pRes /* Search result flag */
+){
+ int rc;
+ char aSpace[200];
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+
+ /* If the cursor is already positioned at the point we are trying
+ ** to move to, then just return without doing any work */
+ if( pCur->eState==CURSOR_VALID && pCur->validNKey && pCur->pPage->intKey ){
+ if( pCur->info.nKey==nKey ){
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ if( pCur->atLast && pCur->info.nKey<nKey ){
+ *pRes = -1;
+ return SQLITE_OK;
+ }
+ }
+
+
+ rc = moveToRoot(pCur);
+ if( rc ){
+ return rc;
+ }
+ assert( pCur->pPage );
+ assert( pCur->pPage->isInit );
+ if( pCur->eState==CURSOR_INVALID ){
+ *pRes = -1;
+ assert( pCur->pPage->nCell==0 );
+ return SQLITE_OK;
+ }
+ if( pCur->pPage->intKey ){
+ /* We are given an SQL table to search. The key is the integer
+ ** rowid contained in nKey. pKey and pUnKey should both be NULL */
+ assert( pUnKey==0 );
+ assert( pKey==0 );
+ }else if( pUnKey==0 ){
+ /* We are to search an SQL index using a key encoded as a blob.
+ ** The blob is found at pKey and is nKey bytes in length. Unpack
+ ** this key so that we can use it. */
+ assert( pKey!=0 );
+ pUnKey = sqlite3VdbeRecordUnpack(pCur->pKeyInfo, nKey, pKey,
+ aSpace, sizeof(aSpace));
+ if( pUnKey==0 ) return SQLITE_NOMEM;
+ }else{
+ /* We are to search an SQL index using a key that is already unpacked
+ ** and handed to us in pUnKey. */
+ assert( pKey==0 );
+ }
+ for(;;){
+ int lwr, upr;
+ Pgno chldPg;
+ MemPage *pPage = pCur->pPage;
+ int c = -1; /* pRes return if table is empty must be -1 */
+ lwr = 0;
+ upr = pPage->nCell-1;
+ if( !pPage->intKey && pUnKey==0 ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto moveto_finish;
+ }
+ if( biasRight ){
+ pCur->idx = upr;
+ }else{
+ pCur->idx = (upr+lwr)/2;
+ }
+ if( lwr<=upr ) for(;;){
+ void *pCellKey;
+ i64 nCellKey;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 1;
+ if( pPage->intKey ){
+ u8 *pCell;
+ pCell = findCell(pPage, pCur->idx) + pPage->childPtrSize;
+ if( pPage->hasData ){
+ u32 dummy;
+ pCell += getVarint32(pCell, dummy);
+ }
+ getVarint(pCell, (u64*)&nCellKey);
+ if( nCellKey==nKey ){
+ c = 0;
+ }else if( nCellKey<nKey ){
+ c = -1;
+ }else{
+ assert( nCellKey>nKey );
+ c = +1;
+ }
+ }else{
+ int available;
+ pCellKey = (void *)fetchPayload(pCur, &available, 0);
+ nCellKey = pCur->info.nKey;
+ if( available>=nCellKey ){
+ c = sqlite3VdbeRecordCompare(nCellKey, pCellKey, pUnKey);
+ }else{
+ pCellKey = sqlite3_malloc( nCellKey );
+ if( pCellKey==0 ){
+ rc = SQLITE_NOMEM;
+ goto moveto_finish;
+ }
+ rc = sqlite3BtreeKey(pCur, 0, nCellKey, (void *)pCellKey);
+ c = sqlite3VdbeRecordCompare(nCellKey, pCellKey, pUnKey);
+ sqlite3_free(pCellKey);
+ if( rc ) goto moveto_finish;
+ }
+ }
+ if( c==0 ){
+ pCur->info.nKey = nCellKey;
+ if( pPage->leafData && !pPage->leaf ){
+ lwr = pCur->idx;
+ upr = lwr - 1;
+ break;
+ }else{
+ if( pRes ) *pRes = 0;
+ rc = SQLITE_OK;
+ goto moveto_finish;
+ }
+ }
+ if( c<0 ){
+ lwr = pCur->idx+1;
+ }else{
+ upr = pCur->idx-1;
+ }
+ if( lwr>upr ){
+ pCur->info.nKey = nCellKey;
+ break;
+ }
+ pCur->idx = (lwr+upr)/2;
+ }
+ assert( lwr==upr+1 );
+ assert( pPage->isInit );
+ if( pPage->leaf ){
+ chldPg = 0;
+ }else if( lwr>=pPage->nCell ){
+ chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]);
+ }else{
+ chldPg = get4byte(findCell(pPage, lwr));
+ }
+ if( chldPg==0 ){
+ assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
+ if( pRes ) *pRes = c;
+ rc = SQLITE_OK;
+ goto moveto_finish;
+ }
+ pCur->idx = lwr;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 0;
+ rc = moveToChild(pCur, chldPg);
+ if( rc ) goto moveto_finish;
+ }
+moveto_finish:
+ if( pKey ){
+ /* If we created our own unpacked key at the top of this
+ ** procedure, then destroy that key before returning. */
+ sqlite3VdbeDeleteUnpackedRecord(pUnKey);
+ }
+ return rc;
+}
+
+
+/*
+** Return TRUE if the cursor is not pointing at an entry of the table.
+**
+** TRUE will be returned after a call to sqlite3BtreeNext() moves
+** past the last entry in the table or sqlite3BtreePrev() moves past
+** the first entry. TRUE is also returned if the table is empty.
+*/
+SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor *pCur){
+ /* TODO: What if the cursor is in CURSOR_REQUIRESEEK but all table entries
+ ** have been deleted? This API will need to change to return an error code
+ ** as well as the boolean result value.
+ */
+ return (CURSOR_VALID!=pCur->eState);
+}
+
+/*
+** Return the database connection handle for a cursor.
+*/
+SQLITE_PRIVATE sqlite3 *sqlite3BtreeCursorDb(const BtCursor *pCur){
+ assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ return pCur->pBtree->db;
+}
+
+/*
+** Advance the cursor to the next entry in the database. If
+** successful then set *pRes=0. If the cursor
+** was already pointing to the last entry in the database before
+** this routine was called, then set *pRes=1.
+*/
+SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){
+ int rc;
+ MemPage *pPage;
+
+ assert( cursorHoldsMutex(pCur) );
+ rc = restoreOrClearCursorPosition(pCur);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( pRes!=0 );
+ pPage = pCur->pPage;
+ if( CURSOR_INVALID==pCur->eState ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ if( pCur->skip>0 ){
+ pCur->skip = 0;
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ pCur->skip = 0;
+
+ assert( pPage->isInit );
+ assert( pCur->idx<pPage->nCell );
+
+ pCur->idx++;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 0;
+ if( pCur->idx>=pPage->nCell ){
+ if( !pPage->leaf ){
+ rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8]));
+ if( rc ) return rc;
+ rc = moveToLeftmost(pCur);
+ *pRes = 0;
+ return rc;
+ }
+ do{
+ if( sqlite3BtreeIsRootPage(pPage) ){
+ *pRes = 1;
+ pCur->eState = CURSOR_INVALID;
+ return SQLITE_OK;
+ }
+ sqlite3BtreeMoveToParent(pCur);
+ pPage = pCur->pPage;
+ }while( pCur->idx>=pPage->nCell );
+ *pRes = 0;
+ if( pPage->leafData ){
+ rc = sqlite3BtreeNext(pCur, pRes);
+ }else{
+ rc = SQLITE_OK;
+ }
+ return rc;
+ }
+ *pRes = 0;
+ if( pPage->leaf ){
+ return SQLITE_OK;
+ }
+ rc = moveToLeftmost(pCur);
+ return rc;
+}
+
+
+/*
+** Step the cursor to the back to the previous entry in the database. If
+** successful then set *pRes=0. If the cursor
+** was already pointing to the first entry in the database before
+** this routine was called, then set *pRes=1.
+*/
+SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){
+ int rc;
+ Pgno pgno;
+ MemPage *pPage;
+
+ assert( cursorHoldsMutex(pCur) );
+ rc = restoreOrClearCursorPosition(pCur);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pCur->atLast = 0;
+ if( CURSOR_INVALID==pCur->eState ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ if( pCur->skip<0 ){
+ pCur->skip = 0;
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ pCur->skip = 0;
+
+ pPage = pCur->pPage;
+ assert( pPage->isInit );
+ assert( pCur->idx>=0 );
+ if( !pPage->leaf ){
+ pgno = get4byte( findCell(pPage, pCur->idx) );
+ rc = moveToChild(pCur, pgno);
+ if( rc ){
+ return rc;
+ }
+ rc = moveToRightmost(pCur);
+ }else{
+ while( pCur->idx==0 ){
+ if( sqlite3BtreeIsRootPage(pPage) ){
+ pCur->eState = CURSOR_INVALID;
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ sqlite3BtreeMoveToParent(pCur);
+ pPage = pCur->pPage;
+ }
+ pCur->idx--;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 0;
+ if( pPage->leafData && !pPage->leaf ){
+ rc = sqlite3BtreePrevious(pCur, pRes);
+ }else{
+ rc = SQLITE_OK;
+ }
+ }
+ *pRes = 0;
+ return rc;
+}
+
+/*
+** Allocate a new page from the database file.
+**
+** The new page is marked as dirty. (In other words, sqlite3PagerWrite()
+** has already been called on the new page.) The new page has also
+** been referenced and the calling routine is responsible for calling
+** sqlite3PagerUnref() on the new page when it is done.
+**
+** SQLITE_OK is returned on success. Any other return value indicates
+** an error. *ppPage and *pPgno are undefined in the event of an error.
+** Do not invoke sqlite3PagerUnref() on *ppPage if an error is returned.
+**
+** If the "nearby" parameter is not 0, then a (feeble) effort is made to
+** locate a page close to the page number "nearby". This can be used in an
+** attempt to keep related pages close to each other in the database file,
+** which in turn can make database access faster.
+**
+** If the "exact" parameter is not 0, and the page-number nearby exists
+** anywhere on the free-list, then it is guarenteed to be returned. This
+** is only used by auto-vacuum databases when allocating a new table.
+*/
+static int allocateBtreePage(
+ BtShared *pBt,
+ MemPage **ppPage,
+ Pgno *pPgno,
+ Pgno nearby,
+ u8 exact
+){
+ MemPage *pPage1;
+ int rc;
+ int n; /* Number of pages on the freelist */
+ int k; /* Number of leaves on the trunk of the freelist */
+ MemPage *pTrunk = 0;
+ MemPage *pPrevTrunk = 0;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ pPage1 = pBt->pPage1;
+ n = get4byte(&pPage1->aData[36]);
+ if( n>0 ){
+ /* There are pages on the freelist. Reuse one of those pages. */
+ Pgno iTrunk;
+ u8 searchList = 0; /* If the free-list must be searched for 'nearby' */
+
+ /* If the 'exact' parameter was true and a query of the pointer-map
+ ** shows that the page 'nearby' is somewhere on the free-list, then
+ ** the entire-list will be searched for that page.
+ */
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( exact && nearby<=sqlite3PagerPagecount(pBt->pPager) ){
+ u8 eType;
+ assert( nearby>0 );
+ assert( pBt->autoVacuum );
+ rc = ptrmapGet(pBt, nearby, &eType, 0);
+ if( rc ) return rc;
+ if( eType==PTRMAP_FREEPAGE ){
+ searchList = 1;
+ }
+ *pPgno = nearby;
+ }
+#endif
+
+ /* Decrement the free-list count by 1. Set iTrunk to the index of the
+ ** first free-list trunk page. iPrevTrunk is initially 1.
+ */
+ rc = sqlite3PagerWrite(pPage1->pDbPage);
+ if( rc ) return rc;
+ put4byte(&pPage1->aData[36], n-1);
+
+ /* The code within this loop is run only once if the 'searchList' variable
+ ** is not true. Otherwise, it runs once for each trunk-page on the
+ ** free-list until the page 'nearby' is located.
+ */
+ do {
+ pPrevTrunk = pTrunk;
+ if( pPrevTrunk ){
+ iTrunk = get4byte(&pPrevTrunk->aData[0]);
+ }else{
+ iTrunk = get4byte(&pPage1->aData[32]);
+ }
+ rc = sqlite3BtreeGetPage(pBt, iTrunk, &pTrunk, 0);
+ if( rc ){
+ pTrunk = 0;
+ goto end_allocate_page;
+ }
+
+ k = get4byte(&pTrunk->aData[4]);
+ if( k==0 && !searchList ){
+ /* The trunk has no leaves and the list is not being searched.
+ ** So extract the trunk page itself and use it as the newly
+ ** allocated page */
+ assert( pPrevTrunk==0 );
+ rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ if( rc ){
+ goto end_allocate_page;
+ }
+ *pPgno = iTrunk;
+ memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4);
+ *ppPage = pTrunk;
+ pTrunk = 0;
+ TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1));
+ }else if( k>pBt->usableSize/4 - 8 ){
+ /* Value of k is out of range. Database corruption */
+ rc = SQLITE_CORRUPT_BKPT;
+ goto end_allocate_page;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ }else if( searchList && nearby==iTrunk ){
+ /* The list is being searched and this trunk page is the page
+ ** to allocate, regardless of whether it has leaves.
+ */
+ assert( *pPgno==iTrunk );
+ *ppPage = pTrunk;
+ searchList = 0;
+ rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ if( rc ){
+ goto end_allocate_page;
+ }
+ if( k==0 ){
+ if( !pPrevTrunk ){
+ memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4);
+ }else{
+ memcpy(&pPrevTrunk->aData[0], &pTrunk->aData[0], 4);
+ }
+ }else{
+ /* The trunk page is required by the caller but it contains
+ ** pointers to free-list leaves. The first leaf becomes a trunk
+ ** page in this case.
+ */
+ MemPage *pNewTrunk;
+ Pgno iNewTrunk = get4byte(&pTrunk->aData[8]);
+ rc = sqlite3BtreeGetPage(pBt, iNewTrunk, &pNewTrunk, 0);
+ if( rc!=SQLITE_OK ){
+ goto end_allocate_page;
+ }
+ rc = sqlite3PagerWrite(pNewTrunk->pDbPage);
+ if( rc!=SQLITE_OK ){
+ releasePage(pNewTrunk);
+ goto end_allocate_page;
+ }
+ memcpy(&pNewTrunk->aData[0], &pTrunk->aData[0], 4);
+ put4byte(&pNewTrunk->aData[4], k-1);
+ memcpy(&pNewTrunk->aData[8], &pTrunk->aData[12], (k-1)*4);
+ releasePage(pNewTrunk);
+ if( !pPrevTrunk ){
+ put4byte(&pPage1->aData[32], iNewTrunk);
+ }else{
+ rc = sqlite3PagerWrite(pPrevTrunk->pDbPage);
+ if( rc ){
+ goto end_allocate_page;
+ }
+ put4byte(&pPrevTrunk->aData[0], iNewTrunk);
+ }
+ }
+ pTrunk = 0;
+ TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1));
+#endif
+ }else{
+ /* Extract a leaf from the trunk */
+ int closest;
+ Pgno iPage;
+ unsigned char *aData = pTrunk->aData;
+ rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ if( rc ){
+ goto end_allocate_page;
+ }
+ if( nearby>0 ){
+ int i, dist;
+ closest = 0;
+ dist = get4byte(&aData[8]) - nearby;
+ if( dist<0 ) dist = -dist;
+ for(i=1; i<k; i++){
+ int d2 = get4byte(&aData[8+i*4]) - nearby;
+ if( d2<0 ) d2 = -d2;
+ if( d2<dist ){
+ closest = i;
+ dist = d2;
+ }
+ }
+ }else{
+ closest = 0;
+ }
+
+ iPage = get4byte(&aData[8+closest*4]);
+ if( !searchList || iPage==nearby ){
+ *pPgno = iPage;
+ if( *pPgno>sqlite3PagerPagecount(pBt->pPager) ){
+ /* Free page off the end of the file */
+ rc = SQLITE_CORRUPT_BKPT;
+ goto end_allocate_page;
+ }
+ TRACE(("ALLOCATE: %d was leaf %d of %d on trunk %d"
+ ": %d more free pages\n",
+ *pPgno, closest+1, k, pTrunk->pgno, n-1));
+ if( closest<k-1 ){
+ memcpy(&aData[8+closest*4], &aData[4+k*4], 4);
+ }
+ put4byte(&aData[4], k-1);
+ rc = sqlite3BtreeGetPage(pBt, *pPgno, ppPage, 1);
+ if( rc==SQLITE_OK ){
+ sqlite3PagerDontRollback((*ppPage)->pDbPage);
+ rc = sqlite3PagerWrite((*ppPage)->pDbPage);
+ if( rc!=SQLITE_OK ){
+ releasePage(*ppPage);
+ }
+ }
+ searchList = 0;
+ }
+ }
+ releasePage(pPrevTrunk);
+ pPrevTrunk = 0;
+ }while( searchList );
+ }else{
+ /* There are no pages on the freelist, so create a new page at the
+ ** end of the file */
+ *pPgno = sqlite3PagerPagecount(pBt->pPager) + 1;
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->nTrunc ){
+ /* An incr-vacuum has already run within this transaction. So the
+ ** page to allocate is not from the physical end of the file, but
+ ** at pBt->nTrunc.
+ */
+ *pPgno = pBt->nTrunc+1;
+ if( *pPgno==PENDING_BYTE_PAGE(pBt) ){
+ (*pPgno)++;
+ }
+ }
+ if( pBt->autoVacuum && PTRMAP_ISPAGE(pBt, *pPgno) ){
+ /* If *pPgno refers to a pointer-map page, allocate two new pages
+ ** at the end of the file instead of one. The first allocated page
+ ** becomes a new pointer-map page, the second is used by the caller.
+ */
+ TRACE(("ALLOCATE: %d from end of file (pointer-map page)\n", *pPgno));
+ assert( *pPgno!=PENDING_BYTE_PAGE(pBt) );
+ (*pPgno)++;
+ if( *pPgno==PENDING_BYTE_PAGE(pBt) ){ (*pPgno)++; }
+ }
+ if( pBt->nTrunc ){
+ pBt->nTrunc = *pPgno;
+ }
+#endif
+
+ assert( *pPgno!=PENDING_BYTE_PAGE(pBt) );
+ rc = sqlite3BtreeGetPage(pBt, *pPgno, ppPage, 0);
+ if( rc ) return rc;
+ rc = sqlite3PagerWrite((*ppPage)->pDbPage);
+ if( rc!=SQLITE_OK ){
+ releasePage(*ppPage);
+ }
+ TRACE(("ALLOCATE: %d from end of file\n", *pPgno));
+ }
+
+ assert( *pPgno!=PENDING_BYTE_PAGE(pBt) );
+
+end_allocate_page:
+ releasePage(pTrunk);
+ releasePage(pPrevTrunk);
+ return rc;
+}
+
+/*
+** Add a page of the database file to the freelist.
+**
+** sqlite3PagerUnref() is NOT called for pPage.
+*/
+static int freePage(MemPage *pPage){
+ BtShared *pBt = pPage->pBt;
+ MemPage *pPage1 = pBt->pPage1;
+ int rc, n, k;
+
+ /* Prepare the page for freeing */
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( pPage->pgno>1 );
+ pPage->isInit = 0;
+ releasePage(pPage->pParent);
+ pPage->pParent = 0;
+
+ /* Increment the free page count on pPage1 */
+ rc = sqlite3PagerWrite(pPage1->pDbPage);
+ if( rc ) return rc;
+ n = get4byte(&pPage1->aData[36]);
+ put4byte(&pPage1->aData[36], n+1);
+
+#ifdef SQLITE_SECURE_DELETE
+ /* If the SQLITE_SECURE_DELETE compile-time option is enabled, then
+ ** always fully overwrite deleted information with zeros.
+ */
+ rc = sqlite3PagerWrite(pPage->pDbPage);
+ if( rc ) return rc;
+ memset(pPage->aData, 0, pPage->pBt->pageSize);
+#endif
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* If the database supports auto-vacuum, write an entry in the pointer-map
+ ** to indicate that the page is free.
+ */
+ if( pBt->autoVacuum ){
+ rc = ptrmapPut(pBt, pPage->pgno, PTRMAP_FREEPAGE, 0);
+ if( rc ) return rc;
+ }
+#endif
+
+ if( n==0 ){
+ /* This is the first free page */
+ rc = sqlite3PagerWrite(pPage->pDbPage);
+ if( rc ) return rc;
+ memset(pPage->aData, 0, 8);
+ put4byte(&pPage1->aData[32], pPage->pgno);
+ TRACE(("FREE-PAGE: %d first\n", pPage->pgno));
+ }else{
+ /* Other free pages already exist. Retrive the first trunk page
+ ** of the freelist and find out how many leaves it has. */
+ MemPage *pTrunk;
+ rc = sqlite3BtreeGetPage(pBt, get4byte(&pPage1->aData[32]), &pTrunk, 0);
+ if( rc ) return rc;
+ k = get4byte(&pTrunk->aData[4]);
+ if( k>=pBt->usableSize/4 - 8 ){
+ /* The trunk is full. Turn the page being freed into a new
+ ** trunk page with no leaves. */
+ rc = sqlite3PagerWrite(pPage->pDbPage);
+ if( rc==SQLITE_OK ){
+ put4byte(pPage->aData, pTrunk->pgno);
+ put4byte(&pPage->aData[4], 0);
+ put4byte(&pPage1->aData[32], pPage->pgno);
+ TRACE(("FREE-PAGE: %d new trunk page replacing %d\n",
+ pPage->pgno, pTrunk->pgno));
+ }
+ }else if( k<0 ){
+ rc = SQLITE_CORRUPT;
+ }else{
+ /* Add the newly freed page as a leaf on the current trunk */
+ rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ if( rc==SQLITE_OK ){
+ put4byte(&pTrunk->aData[4], k+1);
+ put4byte(&pTrunk->aData[8+k*4], pPage->pgno);
+#ifndef SQLITE_SECURE_DELETE
+ sqlite3PagerDontWrite(pPage->pDbPage);
+#endif
+ }
+ TRACE(("FREE-PAGE: %d leaf on trunk page %d\n",pPage->pgno,pTrunk->pgno));
+ }
+ releasePage(pTrunk);
+ }
+ return rc;
+}
+
+/*
+** Free any overflow pages associated with the given Cell.
+*/
+static int clearCell(MemPage *pPage, unsigned char *pCell){
+ BtShared *pBt = pPage->pBt;
+ CellInfo info;
+ Pgno ovflPgno;
+ int rc;
+ int nOvfl;
+ int ovflPageSize;
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ if( info.iOverflow==0 ){
+ return SQLITE_OK; /* No overflow pages. Return without doing anything */
+ }
+ ovflPgno = get4byte(&pCell[info.iOverflow]);
+ ovflPageSize = pBt->usableSize - 4;
+ nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize;
+ assert( ovflPgno==0 || nOvfl>0 );
+ while( nOvfl-- ){
+ MemPage *pOvfl;
+ if( ovflPgno==0 || ovflPgno>sqlite3PagerPagecount(pBt->pPager) ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ rc = getOverflowPage(pBt, ovflPgno, &pOvfl, (nOvfl==0)?0:&ovflPgno);
+ if( rc ) return rc;
+ rc = freePage(pOvfl);
+ sqlite3PagerUnref(pOvfl->pDbPage);
+ if( rc ) return rc;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Create the byte sequence used to represent a cell on page pPage
+** and write that byte sequence into pCell[]. Overflow pages are
+** allocated and filled in as necessary. The calling procedure
+** is responsible for making sure sufficient space has been allocated
+** for pCell[].
+**
+** Note that pCell does not necessary need to point to the pPage->aData
+** area. pCell might point to some temporary storage. The cell will
+** be constructed in this temporary area then copied into pPage->aData
+** later.
+*/
+static int fillInCell(
+ MemPage *pPage, /* The page that contains the cell */
+ unsigned char *pCell, /* Complete text of the cell */
+ const void *pKey, i64 nKey, /* The key */
+ const void *pData,int nData, /* The data */
+ int nZero, /* Extra zero bytes to append to pData */
+ int *pnSize /* Write cell size here */
+){
+ int nPayload;
+ const u8 *pSrc;
+ int nSrc, n, rc;
+ int spaceLeft;
+ MemPage *pOvfl = 0;
+ MemPage *pToRelease = 0;
+ unsigned char *pPrior;
+ unsigned char *pPayload;
+ BtShared *pBt = pPage->pBt;
+ Pgno pgnoOvfl = 0;
+ int nHeader;
+ CellInfo info;
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+
+ /* Fill in the header. */
+ nHeader = 0;
+ if( !pPage->leaf ){
+ nHeader += 4;
+ }
+ if( pPage->hasData ){
+ nHeader += putVarint(&pCell[nHeader], nData+nZero);
+ }else{
+ nData = nZero = 0;
+ }
+ nHeader += putVarint(&pCell[nHeader], *(u64*)&nKey);
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ assert( info.nHeader==nHeader );
+ assert( info.nKey==nKey );
+ assert( info.nData==nData+nZero );
+
+ /* Fill in the payload */
+ nPayload = nData + nZero;
+ if( pPage->intKey ){
+ pSrc = pData;
+ nSrc = nData;
+ nData = 0;
+ }else{
+ nPayload += nKey;
+ pSrc = pKey;
+ nSrc = nKey;
+ }
+ *pnSize = info.nSize;
+ spaceLeft = info.nLocal;
+ pPayload = &pCell[nHeader];
+ pPrior = &pCell[info.iOverflow];
+
+ while( nPayload>0 ){
+ if( spaceLeft==0 ){
+ int isExact = 0;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ Pgno pgnoPtrmap = pgnoOvfl; /* Overflow page pointer-map entry page */
+ if( pBt->autoVacuum ){
+ do{
+ pgnoOvfl++;
+ } while(
+ PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt)
+ );
+ if( pgnoOvfl>1 ){
+ /* isExact = 1; */
+ }
+ }
+#endif
+ rc = allocateBtreePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, isExact);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* If the database supports auto-vacuum, and the second or subsequent
+ ** overflow page is being allocated, add an entry to the pointer-map
+ ** for that page now.
+ **
+ ** If this is the first overflow page, then write a partial entry
+ ** to the pointer-map. If we write nothing to this pointer-map slot,
+ ** then the optimistic overflow chain processing in clearCell()
+ ** may misinterpret the uninitialised values and delete the
+ ** wrong pages from the database.
+ */
+ if( pBt->autoVacuum && rc==SQLITE_OK ){
+ u8 eType = (pgnoPtrmap?PTRMAP_OVERFLOW2:PTRMAP_OVERFLOW1);
+ rc = ptrmapPut(pBt, pgnoOvfl, eType, pgnoPtrmap);
+ if( rc ){
+ releasePage(pOvfl);
+ }
+ }
+#endif
+ if( rc ){
+ releasePage(pToRelease);
+ return rc;
+ }
+ put4byte(pPrior, pgnoOvfl);
+ releasePage(pToRelease);
+ pToRelease = pOvfl;
+ pPrior = pOvfl->aData;
+ put4byte(pPrior, 0);
+ pPayload = &pOvfl->aData[4];
+ spaceLeft = pBt->usableSize - 4;
+ }
+ n = nPayload;
+ if( n>spaceLeft ) n = spaceLeft;
+ if( nSrc>0 ){
+ if( n>nSrc ) n = nSrc;
+ assert( pSrc );
+ memcpy(pPayload, pSrc, n);
+ }else{
+ memset(pPayload, 0, n);
+ }
+ nPayload -= n;
+ pPayload += n;
+ pSrc += n;
+ nSrc -= n;
+ spaceLeft -= n;
+ if( nSrc==0 ){
+ nSrc = nData;
+ pSrc = pData;
+ }
+ }
+ releasePage(pToRelease);
+ return SQLITE_OK;
+}
+
+/*
+** Change the MemPage.pParent pointer on the page whose number is
+** given in the second argument so that MemPage.pParent holds the
+** pointer in the third argument.
+*/
+static int reparentPage(BtShared *pBt, Pgno pgno, MemPage *pNewParent, int idx){
+ MemPage *pThis;
+ DbPage *pDbPage;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( pNewParent!=0 );
+ if( pgno==0 ) return SQLITE_OK;
+ assert( pBt->pPager!=0 );
+ pDbPage = sqlite3PagerLookup(pBt->pPager, pgno);
+ if( pDbPage ){
+ pThis = (MemPage *)sqlite3PagerGetExtra(pDbPage);
+ if( pThis->isInit ){
+ assert( pThis->aData==sqlite3PagerGetData(pDbPage) );
+ if( pThis->pParent!=pNewParent ){
+ if( pThis->pParent ) sqlite3PagerUnref(pThis->pParent->pDbPage);
+ pThis->pParent = pNewParent;
+ sqlite3PagerRef(pNewParent->pDbPage);
+ }
+ pThis->idxParent = idx;
+ }
+ sqlite3PagerUnref(pDbPage);
+ }
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ return ptrmapPut(pBt, pgno, PTRMAP_BTREE, pNewParent->pgno);
+ }
+#endif
+ return SQLITE_OK;
+}
+
+
+
+/*
+** Change the pParent pointer of all children of pPage to point back
+** to pPage.
+**
+** In other words, for every child of pPage, invoke reparentPage()
+** to make sure that each child knows that pPage is its parent.
+**
+** This routine gets called after you memcpy() one page into
+** another.
+*/
+static int reparentChildPages(MemPage *pPage){
+ int i;
+ BtShared *pBt = pPage->pBt;
+ int rc = SQLITE_OK;
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ if( pPage->leaf ) return SQLITE_OK;
+
+ for(i=0; i<pPage->nCell; i++){
+ u8 *pCell = findCell(pPage, i);
+ rc = reparentPage(pBt, get4byte(pCell), pPage, i);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ rc = reparentPage(pBt, get4byte(&pPage->aData[pPage->hdrOffset+8]),
+ pPage, i);
+ pPage->idxShift = 0;
+ return rc;
+}
+
+/*
+** Remove the i-th cell from pPage. This routine effects pPage only.
+** The cell content is not freed or deallocated. It is assumed that
+** the cell content has been copied someplace else. This routine just
+** removes the reference to the cell from pPage.
+**
+** "sz" must be the number of bytes in the cell.
+*/
+static void dropCell(MemPage *pPage, int idx, int sz){
+ int i; /* Loop counter */
+ int pc; /* Offset to cell content of cell being deleted */
+ u8 *data; /* pPage->aData */
+ u8 *ptr; /* Used to move bytes around within data[] */
+
+ assert( idx>=0 && idx<pPage->nCell );
+ assert( sz==cellSize(pPage, idx) );
+ assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ data = pPage->aData;
+ ptr = &data[pPage->cellOffset + 2*idx];
+ pc = get2byte(ptr);
+ assert( pc>10 && pc+sz<=pPage->pBt->usableSize );
+ freeSpace(pPage, pc, sz);
+ for(i=idx+1; i<pPage->nCell; i++, ptr+=2){
+ ptr[0] = ptr[2];
+ ptr[1] = ptr[3];
+ }
+ pPage->nCell--;
+ put2byte(&data[pPage->hdrOffset+3], pPage->nCell);
+ pPage->nFree += 2;
+ pPage->idxShift = 1;
+}
+
+/*
+** Insert a new cell on pPage at cell index "i". pCell points to the
+** content of the cell.
+**
+** If the cell content will fit on the page, then put it there. If it
+** will not fit, then make a copy of the cell content into pTemp if
+** pTemp is not null. Regardless of pTemp, allocate a new entry
+** in pPage->aOvfl[] and make it point to the cell content (either
+** in pTemp or the original pCell) and also record its index.
+** Allocating a new entry in pPage->aCell[] implies that
+** pPage->nOverflow is incremented.
+**
+** If nSkip is non-zero, then do not copy the first nSkip bytes of the
+** cell. The caller will overwrite them after this function returns. If
+** nSkip is non-zero, then pCell may not point to an invalid memory location
+** (but pCell+nSkip is always valid).
+*/
+static int insertCell(
+ MemPage *pPage, /* Page into which we are copying */
+ int i, /* New cell becomes the i-th cell of the page */
+ u8 *pCell, /* Content of the new cell */
+ int sz, /* Bytes of content in pCell */
+ u8 *pTemp, /* Temp storage space for pCell, if needed */
+ u8 nSkip /* Do not write the first nSkip bytes of the cell */
+){
+ int idx; /* Where to write new cell content in data[] */
+ int j; /* Loop counter */
+ int top; /* First byte of content for any cell in data[] */
+ int end; /* First byte past the last cell pointer in data[] */
+ int ins; /* Index in data[] where new cell pointer is inserted */
+ int hdr; /* Offset into data[] of the page header */
+ int cellOffset; /* Address of first cell pointer in data[] */
+ u8 *data; /* The content of the whole page */
+ u8 *ptr; /* Used for moving information around in data[] */
+
+ assert( i>=0 && i<=pPage->nCell+pPage->nOverflow );
+ assert( sz==cellSizePtr(pPage, pCell) );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ if( pPage->nOverflow || sz+2>pPage->nFree ){
+ if( pTemp ){
+ memcpy(pTemp+nSkip, pCell+nSkip, sz-nSkip);
+ pCell = pTemp;
+ }
+ j = pPage->nOverflow++;
+ assert( j<sizeof(pPage->aOvfl)/sizeof(pPage->aOvfl[0]) );
+ pPage->aOvfl[j].pCell = pCell;
+ pPage->aOvfl[j].idx = i;
+ pPage->nFree = 0;
+ }else{
+ int rc = sqlite3PagerWrite(pPage->pDbPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ data = pPage->aData;
+ hdr = pPage->hdrOffset;
+ top = get2byte(&data[hdr+5]);
+ cellOffset = pPage->cellOffset;
+ end = cellOffset + 2*pPage->nCell + 2;
+ ins = cellOffset + 2*i;
+ if( end > top - sz ){
+ rc = defragmentPage(pPage);
+ if( rc!=SQLITE_OK ) return rc;
+ top = get2byte(&data[hdr+5]);
+ assert( end + sz <= top );
+ }
+ idx = allocateSpace(pPage, sz);
+ assert( idx>0 );
+ assert( end <= get2byte(&data[hdr+5]) );
+ pPage->nCell++;
+ pPage->nFree -= 2;
+ memcpy(&data[idx+nSkip], pCell+nSkip, sz-nSkip);
+ for(j=end-2, ptr=&data[j]; j>ins; j-=2, ptr-=2){
+ ptr[0] = ptr[-2];
+ ptr[1] = ptr[-1];
+ }
+ put2byte(&data[ins], idx);
+ put2byte(&data[hdr+3], pPage->nCell);
+ pPage->idxShift = 1;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pPage->pBt->autoVacuum ){
+ /* The cell may contain a pointer to an overflow page. If so, write
+ ** the entry for the overflow page into the pointer map.
+ */
+ CellInfo info;
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ assert( (info.nData+(pPage->intKey?0:info.nKey))==info.nPayload );
+ if( (info.nData+(pPage->intKey?0:info.nKey))>info.nLocal ){
+ Pgno pgnoOvfl = get4byte(&pCell[info.iOverflow]);
+ rc = ptrmapPut(pPage->pBt, pgnoOvfl, PTRMAP_OVERFLOW1, pPage->pgno);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ }
+#endif
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** Add a list of cells to a page. The page should be initially empty.
+** The cells are guaranteed to fit on the page.
+*/
+static void assemblePage(
+ MemPage *pPage, /* The page to be assemblied */
+ int nCell, /* The number of cells to add to this page */
+ u8 **apCell, /* Pointers to cell bodies */
+ u16 *aSize /* Sizes of the cells */
+){
+ int i; /* Loop counter */
+ int totalSize; /* Total size of all cells */
+ int hdr; /* Index of page header */
+ int cellptr; /* Address of next cell pointer */
+ int cellbody; /* Address of next cell body */
+ u8 *data; /* Data for the page */
+
+ assert( pPage->nOverflow==0 );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ totalSize = 0;
+ for(i=0; i<nCell; i++){
+ totalSize += aSize[i];
+ }
+ assert( totalSize+2*nCell<=pPage->nFree );
+ assert( pPage->nCell==0 );
+ cellptr = pPage->cellOffset;
+ data = pPage->aData;
+ hdr = pPage->hdrOffset;
+ put2byte(&data[hdr+3], nCell);
+ if( nCell ){
+ cellbody = allocateSpace(pPage, totalSize);
+ assert( cellbody>0 );
+ assert( pPage->nFree >= 2*nCell );
+ pPage->nFree -= 2*nCell;
+ for(i=0; i<nCell; i++){
+ put2byte(&data[cellptr], cellbody);
+ memcpy(&data[cellbody], apCell[i], aSize[i]);
+ cellptr += 2;
+ cellbody += aSize[i];
+ }
+ assert( cellbody==pPage->pBt->usableSize );
+ }
+ pPage->nCell = nCell;
+}
+
+/*
+** The following parameters determine how many adjacent pages get involved
+** in a balancing operation. NN is the number of neighbors on either side
+** of the page that participate in the balancing operation. NB is the
+** total number of pages that participate, including the target page and
+** NN neighbors on either side.
+**
+** The minimum value of NN is 1 (of course). Increasing NN above 1
+** (to 2 or 3) gives a modest improvement in SELECT and DELETE performance
+** in exchange for a larger degradation in INSERT and UPDATE performance.
+** The value of NN appears to give the best results overall.
+*/
+#define NN 1 /* Number of neighbors on either side of pPage */
+#define NB (NN*2+1) /* Total pages involved in the balance */
+
+/* Forward reference */
+static int balance(MemPage*, int);
+
+#ifndef SQLITE_OMIT_QUICKBALANCE
+/*
+** This version of balance() handles the common special case where
+** a new entry is being inserted on the extreme right-end of the
+** tree, in other words, when the new entry will become the largest
+** entry in the tree.
+**
+** Instead of trying balance the 3 right-most leaf pages, just add
+** a new page to the right-hand side and put the one new entry in
+** that page. This leaves the right side of the tree somewhat
+** unbalanced. But odds are that we will be inserting new entries
+** at the end soon afterwards so the nearly empty page will quickly
+** fill up. On average.
+**
+** pPage is the leaf page which is the right-most page in the tree.
+** pParent is its parent. pPage must have a single overflow entry
+** which is also the right-most entry on the page.
+*/
+static int balance_quick(MemPage *pPage, MemPage *pParent){
+ int rc;
+ MemPage *pNew;
+ Pgno pgnoNew;
+ u8 *pCell;
+ u16 szCell;
+ CellInfo info;
+ BtShared *pBt = pPage->pBt;
+ int parentIdx = pParent->nCell; /* pParent new divider cell index */
+ int parentSize; /* Size of new divider cell */
+ u8 parentCell[64]; /* Space for the new divider cell */
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+
+ /* Allocate a new page. Insert the overflow cell from pPage
+ ** into it. Then remove the overflow cell from pPage.
+ */
+ rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pCell = pPage->aOvfl[0].pCell;
+ szCell = cellSizePtr(pPage, pCell);
+ zeroPage(pNew, pPage->aData[0]);
+ assemblePage(pNew, 1, &pCell, &szCell);
+ pPage->nOverflow = 0;
+
+ /* Set the parent of the newly allocated page to pParent. */
+ pNew->pParent = pParent;
+ sqlite3PagerRef(pParent->pDbPage);
+
+ /* pPage is currently the right-child of pParent. Change this
+ ** so that the right-child is the new page allocated above and
+ ** pPage is the next-to-right child.
+ */
+ assert( pPage->nCell>0 );
+ pCell = findCell(pPage, pPage->nCell-1);
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ rc = fillInCell(pParent, parentCell, 0, info.nKey, 0, 0, 0, &parentSize);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( parentSize<64 );
+ rc = insertCell(pParent, parentIdx, parentCell, parentSize, 0, 4);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ put4byte(findOverflowCell(pParent,parentIdx), pPage->pgno);
+ put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew);
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* If this is an auto-vacuum database, update the pointer map
+ ** with entries for the new page, and any pointer from the
+ ** cell on the page to an overflow page.
+ */
+ if( pBt->autoVacuum ){
+ rc = ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno);
+ if( rc==SQLITE_OK ){
+ rc = ptrmapPutOvfl(pNew, 0);
+ }
+ if( rc!=SQLITE_OK ){
+ releasePage(pNew);
+ return rc;
+ }
+ }
+#endif
+
+ /* Release the reference to the new page and balance the parent page,
+ ** in case the divider cell inserted caused it to become overfull.
+ */
+ releasePage(pNew);
+ return balance(pParent, 0);
+}
+#endif /* SQLITE_OMIT_QUICKBALANCE */
+
+/*
+** This routine redistributes Cells on pPage and up to NN*2 siblings
+** of pPage so that all pages have about the same amount of free space.
+** Usually NN siblings on either side of pPage is used in the balancing,
+** though more siblings might come from one side if pPage is the first
+** or last child of its parent. If pPage has fewer than 2*NN siblings
+** (something which can only happen if pPage is the root page or a
+** child of root) then all available siblings participate in the balancing.
+**
+** The number of siblings of pPage might be increased or decreased by one or
+** two in an effort to keep pages nearly full but not over full. The root page
+** is special and is allowed to be nearly empty. If pPage is
+** the root page, then the depth of the tree might be increased
+** or decreased by one, as necessary, to keep the root page from being
+** overfull or completely empty.
+**
+** Note that when this routine is called, some of the Cells on pPage
+** might not actually be stored in pPage->aData[]. This can happen
+** if the page is overfull. Part of the job of this routine is to
+** make sure all Cells for pPage once again fit in pPage->aData[].
+**
+** In the course of balancing the siblings of pPage, the parent of pPage
+** might become overfull or underfull. If that happens, then this routine
+** is called recursively on the parent.
+**
+** If this routine fails for any reason, it might leave the database
+** in a corrupted state. So if this routine fails, the database should
+** be rolled back.
+*/
+static int balance_nonroot(MemPage *pPage){
+ MemPage *pParent; /* The parent of pPage */
+ BtShared *pBt; /* The whole database */
+ int nCell = 0; /* Number of cells in apCell[] */
+ int nMaxCells = 0; /* Allocated size of apCell, szCell, aFrom. */
+ int nOld; /* Number of pages in apOld[] */
+ int nNew; /* Number of pages in apNew[] */
+ int nDiv; /* Number of cells in apDiv[] */
+ int i, j, k; /* Loop counters */
+ int idx; /* Index of pPage in pParent->aCell[] */
+ int nxDiv; /* Next divider slot in pParent->aCell[] */
+ int rc; /* The return code */
+ int leafCorrection; /* 4 if pPage is a leaf. 0 if not */
+ int leafData; /* True if pPage is a leaf of a LEAFDATA tree */
+ int usableSpace; /* Bytes in pPage beyond the header */
+ int pageFlags; /* Value of pPage->aData[0] */
+ int subtotal; /* Subtotal of bytes in cells on one page */
+ int iSpace = 0; /* First unused byte of aSpace[] */
+ MemPage *apOld[NB]; /* pPage and up to two siblings */
+ Pgno pgnoOld[NB]; /* Page numbers for each page in apOld[] */
+ MemPage *apCopy[NB]; /* Private copies of apOld[] pages */
+ MemPage *apNew[NB+2]; /* pPage and up to NB siblings after balancing */
+ Pgno pgnoNew[NB+2]; /* Page numbers for each page in apNew[] */
+ u8 *apDiv[NB]; /* Divider cells in pParent */
+ int cntNew[NB+2]; /* Index in aCell[] of cell after i-th page */
+ int szNew[NB+2]; /* Combined size of cells place on i-th page */
+ u8 **apCell = 0; /* All cells begin balanced */
+ u16 *szCell; /* Local size of all cells in apCell[] */
+ u8 *aCopy[NB]; /* Space for holding data of apCopy[] */
+ u8 *aSpace; /* Space to hold copies of dividers cells */
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ u8 *aFrom = 0;
+#endif
+
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+
+ /*
+ ** Find the parent page.
+ */
+ assert( pPage->isInit );
+ assert( sqlite3PagerIswriteable(pPage->pDbPage) || pPage->nOverflow==1 );
+ pBt = pPage->pBt;
+ pParent = pPage->pParent;
+ assert( pParent );
+ if( SQLITE_OK!=(rc = sqlite3PagerWrite(pParent->pDbPage)) ){
+ return rc;
+ }
+ TRACE(("BALANCE: begin page %d child of %d\n", pPage->pgno, pParent->pgno));
+
+#ifndef SQLITE_OMIT_QUICKBALANCE
+ /*
+ ** A special case: If a new entry has just been inserted into a
+ ** table (that is, a btree with integer keys and all data at the leaves)
+ ** and the new entry is the right-most entry in the tree (it has the
+ ** largest key) then use the special balance_quick() routine for
+ ** balancing. balance_quick() is much faster and results in a tighter
+ ** packing of data in the common case.
+ */
+ if( pPage->leaf &&
+ pPage->intKey &&
+ pPage->leafData &&
+ pPage->nOverflow==1 &&
+ pPage->aOvfl[0].idx==pPage->nCell &&
+ pPage->pParent->pgno!=1 &&
+ get4byte(&pParent->aData[pParent->hdrOffset+8])==pPage->pgno
+ ){
+ /*
+ ** TODO: Check the siblings to the left of pPage. It may be that
+ ** they are not full and no new page is required.
+ */
+ return balance_quick(pPage, pParent);
+ }
+#endif
+
+ if( SQLITE_OK!=(rc = sqlite3PagerWrite(pPage->pDbPage)) ){
+ return rc;
+ }
+
+ /*
+ ** Find the cell in the parent page whose left child points back
+ ** to pPage. The "idx" variable is the index of that cell. If pPage
+ ** is the rightmost child of pParent then set idx to pParent->nCell
+ */
+ if( pParent->idxShift ){
+ Pgno pgno;
+ pgno = pPage->pgno;
+ assert( pgno==sqlite3PagerPagenumber(pPage->pDbPage) );
+ for(idx=0; idx<pParent->nCell; idx++){
+ if( get4byte(findCell(pParent, idx))==pgno ){
+ break;
+ }
+ }
+ assert( idx<pParent->nCell
+ || get4byte(&pParent->aData[pParent->hdrOffset+8])==pgno );
+ }else{
+ idx = pPage->idxParent;
+ }
+
+ /*
+ ** Initialize variables so that it will be safe to jump
+ ** directly to balance_cleanup at any moment.
+ */
+ nOld = nNew = 0;
+ sqlite3PagerRef(pParent->pDbPage);
+
+ /*
+ ** Find sibling pages to pPage and the cells in pParent that divide
+ ** the siblings. An attempt is made to find NN siblings on either
+ ** side of pPage. More siblings are taken from one side, however, if
+ ** pPage there are fewer than NN siblings on the other side. If pParent
+ ** has NB or fewer children then all children of pParent are taken.
+ */
+ nxDiv = idx - NN;
+ if( nxDiv + NB > pParent->nCell ){
+ nxDiv = pParent->nCell - NB + 1;
+ }
+ if( nxDiv<0 ){
+ nxDiv = 0;
+ }
+ nDiv = 0;
+ for(i=0, k=nxDiv; i<NB; i++, k++){
+ if( k<pParent->nCell ){
+ apDiv[i] = findCell(pParent, k);
+ nDiv++;
+ assert( !pParent->leaf );
+ pgnoOld[i] = get4byte(apDiv[i]);
+ }else if( k==pParent->nCell ){
+ pgnoOld[i] = get4byte(&pParent->aData[pParent->hdrOffset+8]);
+ }else{
+ break;
+ }
+ rc = getAndInitPage(pBt, pgnoOld[i], &apOld[i], pParent);
+ if( rc ) goto balance_cleanup;
+ apOld[i]->idxParent = k;
+ apCopy[i] = 0;
+ assert( i==nOld );
+ nOld++;
+ nMaxCells += 1+apOld[i]->nCell+apOld[i]->nOverflow;
+ }
+
+ /* Make nMaxCells a multiple of 4 in order to preserve 8-byte
+ ** alignment */
+ nMaxCells = (nMaxCells + 3)&~3;
+
+ /*
+ ** Allocate space for memory structures
+ */
+ apCell = sqlite3_malloc(
+ nMaxCells*sizeof(u8*) /* apCell */
+ + nMaxCells*sizeof(u16) /* szCell */
+ + (ROUND8(sizeof(MemPage))+pBt->pageSize)*NB /* aCopy */
+ + pBt->pageSize*5 /* aSpace */
+ + (ISAUTOVACUUM ? nMaxCells : 0) /* aFrom */
+ );
+ if( apCell==0 ){
+ rc = SQLITE_NOMEM;
+ goto balance_cleanup;
+ }
+ szCell = (u16*)&apCell[nMaxCells];
+ aCopy[0] = (u8*)&szCell[nMaxCells];
+ assert( ((aCopy[0] - (u8*)apCell) & 7)==0 ); /* 8-byte alignment required */
+ for(i=1; i<NB; i++){
+ aCopy[i] = &aCopy[i-1][pBt->pageSize+ROUND8(sizeof(MemPage))];
+ assert( ((aCopy[i] - (u8*)apCell) & 7)==0 ); /* 8-byte alignment required */
+ }
+ aSpace = &aCopy[NB-1][pBt->pageSize+ROUND8(sizeof(MemPage))];
+ assert( ((aSpace - (u8*)apCell) & 7)==0 ); /* 8-byte alignment required */
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ aFrom = &aSpace[5*pBt->pageSize];
+ }
+#endif
+
+ /*
+ ** Make copies of the content of pPage and its siblings into aOld[].
+ ** The rest of this function will use data from the copies rather
+ ** that the original pages since the original pages will be in the
+ ** process of being overwritten.
+ */
+ for(i=0; i<nOld; i++){
+ MemPage *p = apCopy[i] = (MemPage*)aCopy[i];
+ memcpy(p, apOld[i], sizeof(MemPage));
+ p->aData = (void*)&p[1];
+ memcpy(p->aData, apOld[i]->aData, pBt->pageSize);
+ }
+
+ /*
+ ** Load pointers to all cells on sibling pages and the divider cells
+ ** into the local apCell[] array. Make copies of the divider cells
+ ** into space obtained form aSpace[] and remove the the divider Cells
+ ** from pParent.
+ **
+ ** If the siblings are on leaf pages, then the child pointers of the
+ ** divider cells are stripped from the cells before they are copied
+ ** into aSpace[]. In this way, all cells in apCell[] are without
+ ** child pointers. If siblings are not leaves, then all cell in
+ ** apCell[] include child pointers. Either way, all cells in apCell[]
+ ** are alike.
+ **
+ ** leafCorrection: 4 if pPage is a leaf. 0 if pPage is not a leaf.
+ ** leafData: 1 if pPage holds key+data and pParent holds only keys.
+ */
+ nCell = 0;
+ leafCorrection = pPage->leaf*4;
+ leafData = pPage->leafData && pPage->leaf;
+ for(i=0; i<nOld; i++){
+ MemPage *pOld = apCopy[i];
+ int limit = pOld->nCell+pOld->nOverflow;
+ for(j=0; j<limit; j++){
+ assert( nCell<nMaxCells );
+ apCell[nCell] = findOverflowCell(pOld, j);
+ szCell[nCell] = cellSizePtr(pOld, apCell[nCell]);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ int a;
+ aFrom[nCell] = i;
+ for(a=0; a<pOld->nOverflow; a++){
+ if( pOld->aOvfl[a].pCell==apCell[nCell] ){
+ aFrom[nCell] = 0xFF;
+ break;
+ }
+ }
+ }
+#endif
+ nCell++;
+ }
+ if( i<nOld-1 ){
+ u16 sz = cellSizePtr(pParent, apDiv[i]);
+ if( leafData ){
+ /* With the LEAFDATA flag, pParent cells hold only INTKEYs that
+ ** are duplicates of keys on the child pages. We need to remove
+ ** the divider cells from pParent, but the dividers cells are not
+ ** added to apCell[] because they are duplicates of child cells.
+ */
+ dropCell(pParent, nxDiv, sz);
+ }else{
+ u8 *pTemp;
+ assert( nCell<nMaxCells );
+ szCell[nCell] = sz;
+ pTemp = &aSpace[iSpace];
+ iSpace += sz;
+ assert( iSpace<=pBt->pageSize*5 );
+ memcpy(pTemp, apDiv[i], sz);
+ apCell[nCell] = pTemp+leafCorrection;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ aFrom[nCell] = 0xFF;
+ }
+#endif
+ dropCell(pParent, nxDiv, sz);
+ szCell[nCell] -= leafCorrection;
+ assert( get4byte(pTemp)==pgnoOld[i] );
+ if( !pOld->leaf ){
+ assert( leafCorrection==0 );
+ /* The right pointer of the child page pOld becomes the left
+ ** pointer of the divider cell */
+ memcpy(apCell[nCell], &pOld->aData[pOld->hdrOffset+8], 4);
+ }else{
+ assert( leafCorrection==4 );
+ if( szCell[nCell]<4 ){
+ /* Do not allow any cells smaller than 4 bytes. */
+ szCell[nCell] = 4;
+ }
+ }
+ nCell++;
+ }
+ }
+ }
+
+ /*
+ ** Figure out the number of pages needed to hold all nCell cells.
+ ** Store this number in "k". Also compute szNew[] which is the total
+ ** size of all cells on the i-th page and cntNew[] which is the index
+ ** in apCell[] of the cell that divides page i from page i+1.
+ ** cntNew[k] should equal nCell.
+ **
+ ** Values computed by this block:
+ **
+ ** k: The total number of sibling pages
+ ** szNew[i]: Spaced used on the i-th sibling page.
+ ** cntNew[i]: Index in apCell[] and szCell[] for the first cell to
+ ** the right of the i-th sibling page.
+ ** usableSpace: Number of bytes of space available on each sibling.
+ **
+ */
+ usableSpace = pBt->usableSize - 12 + leafCorrection;
+ for(subtotal=k=i=0; i<nCell; i++){
+ assert( i<nMaxCells );
+ subtotal += szCell[i] + 2;
+ if( subtotal > usableSpace ){
+ szNew[k] = subtotal - szCell[i];
+ cntNew[k] = i;
+ if( leafData ){ i--; }
+ subtotal = 0;
+ k++;
+ }
+ }
+ szNew[k] = subtotal;
+ cntNew[k] = nCell;
+ k++;
+
+ /*
+ ** The packing computed by the previous block is biased toward the siblings
+ ** on the left side. The left siblings are always nearly full, while the
+ ** right-most sibling might be nearly empty. This block of code attempts
+ ** to adjust the packing of siblings to get a better balance.
+ **
+ ** This adjustment is more than an optimization. The packing above might
+ ** be so out of balance as to be illegal. For example, the right-most
+ ** sibling might be completely empty. This adjustment is not optional.
+ */
+ for(i=k-1; i>0; i--){
+ int szRight = szNew[i]; /* Size of sibling on the right */
+ int szLeft = szNew[i-1]; /* Size of sibling on the left */
+ int r; /* Index of right-most cell in left sibling */
+ int d; /* Index of first cell to the left of right sibling */
+
+ r = cntNew[i-1] - 1;
+ d = r + 1 - leafData;
+ assert( d<nMaxCells );
+ assert( r<nMaxCells );
+ while( szRight==0 || szRight+szCell[d]+2<=szLeft-(szCell[r]+2) ){
+ szRight += szCell[d] + 2;
+ szLeft -= szCell[r] + 2;
+ cntNew[i-1]--;
+ r = cntNew[i-1] - 1;
+ d = r + 1 - leafData;
+ }
+ szNew[i] = szRight;
+ szNew[i-1] = szLeft;
+ }
+
+ /* Either we found one or more cells (cntnew[0])>0) or we are the
+ ** a virtual root page. A virtual root page is when the real root
+ ** page is page 1 and we are the only child of that page.
+ */
+ assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) );
+
+ /*
+ ** Allocate k new pages. Reuse old pages where possible.
+ */
+ assert( pPage->pgno>1 );
+ pageFlags = pPage->aData[0];
+ for(i=0; i<k; i++){
+ MemPage *pNew;
+ if( i<nOld ){
+ pNew = apNew[i] = apOld[i];
+ pgnoNew[i] = pgnoOld[i];
+ apOld[i] = 0;
+ rc = sqlite3PagerWrite(pNew->pDbPage);
+ nNew++;
+ if( rc ) goto balance_cleanup;
+ }else{
+ assert( i>0 );
+ rc = allocateBtreePage(pBt, &pNew, &pgnoNew[i], pgnoNew[i-1], 0);
+ if( rc ) goto balance_cleanup;
+ apNew[i] = pNew;
+ nNew++;
+ }
+ zeroPage(pNew, pageFlags);
+ }
+
+ /* Free any old pages that were not reused as new pages.
+ */
+ while( i<nOld ){
+ rc = freePage(apOld[i]);
+ if( rc ) goto balance_cleanup;
+ releasePage(apOld[i]);
+ apOld[i] = 0;
+ i++;
+ }
+
+ /*
+ ** Put the new pages in accending order. This helps to
+ ** keep entries in the disk file in order so that a scan
+ ** of the table is a linear scan through the file. That
+ ** in turn helps the operating system to deliver pages
+ ** from the disk more rapidly.
+ **
+ ** An O(n^2) insertion sort algorithm is used, but since
+ ** n is never more than NB (a small constant), that should
+ ** not be a problem.
+ **
+ ** When NB==3, this one optimization makes the database
+ ** about 25% faster for large insertions and deletions.
+ */
+ for(i=0; i<k-1; i++){
+ int minV = pgnoNew[i];
+ int minI = i;
+ for(j=i+1; j<k; j++){
+ if( pgnoNew[j]<(unsigned)minV ){
+ minI = j;
+ minV = pgnoNew[j];
+ }
+ }
+ if( minI>i ){
+ int t;
+ MemPage *pT;
+ t = pgnoNew[i];
+ pT = apNew[i];
+ pgnoNew[i] = pgnoNew[minI];
+ apNew[i] = apNew[minI];
+ pgnoNew[minI] = t;
+ apNew[minI] = pT;
+ }
+ }
+ TRACE(("BALANCE: old: %d %d %d new: %d(%d) %d(%d) %d(%d) %d(%d) %d(%d)\n",
+ pgnoOld[0],
+ nOld>=2 ? pgnoOld[1] : 0,
+ nOld>=3 ? pgnoOld[2] : 0,
+ pgnoNew[0], szNew[0],
+ nNew>=2 ? pgnoNew[1] : 0, nNew>=2 ? szNew[1] : 0,
+ nNew>=3 ? pgnoNew[2] : 0, nNew>=3 ? szNew[2] : 0,
+ nNew>=4 ? pgnoNew[3] : 0, nNew>=4 ? szNew[3] : 0,
+ nNew>=5 ? pgnoNew[4] : 0, nNew>=5 ? szNew[4] : 0));
+
+ /*
+ ** Evenly distribute the data in apCell[] across the new pages.
+ ** Insert divider cells into pParent as necessary.
+ */
+ j = 0;
+ for(i=0; i<nNew; i++){
+ /* Assemble the new sibling page. */
+ MemPage *pNew = apNew[i];
+ assert( j<nMaxCells );
+ assert( pNew->pgno==pgnoNew[i] );
+ assemblePage(pNew, cntNew[i]-j, &apCell[j], &szCell[j]);
+ assert( pNew->nCell>0 || (nNew==1 && cntNew[0]==0) );
+ assert( pNew->nOverflow==0 );
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* If this is an auto-vacuum database, update the pointer map entries
+ ** that point to the siblings that were rearranged. These can be: left
+ ** children of cells, the right-child of the page, or overflow pages
+ ** pointed to by cells.
+ */
+ if( pBt->autoVacuum ){
+ for(k=j; k<cntNew[i]; k++){
+ assert( k<nMaxCells );
+ if( aFrom[k]==0xFF || apCopy[aFrom[k]]->pgno!=pNew->pgno ){
+ rc = ptrmapPutOvfl(pNew, k-j);
+ if( rc!=SQLITE_OK ){
+ goto balance_cleanup;
+ }
+ }
+ }
+ }
+#endif
+
+ j = cntNew[i];
+
+ /* If the sibling page assembled above was not the right-most sibling,
+ ** insert a divider cell into the parent page.
+ */
+ if( i<nNew-1 && j<nCell ){
+ u8 *pCell;
+ u8 *pTemp;
+ int sz;
+
+ assert( j<nMaxCells );
+ pCell = apCell[j];
+ sz = szCell[j] + leafCorrection;
+ if( !pNew->leaf ){
+ memcpy(&pNew->aData[8], pCell, 4);
+ pTemp = 0;
+ }else if( leafData ){
+ /* If the tree is a leaf-data tree, and the siblings are leaves,
+ ** then there is no divider cell in apCell[]. Instead, the divider
+ ** cell consists of the integer key for the right-most cell of
+ ** the sibling-page assembled above only.
+ */
+ CellInfo info;
+ j--;
+ sqlite3BtreeParseCellPtr(pNew, apCell[j], &info);
+ pCell = &aSpace[iSpace];
+ fillInCell(pParent, pCell, 0, info.nKey, 0, 0, 0, &sz);
+ iSpace += sz;
+ assert( iSpace<=pBt->pageSize*5 );
+ pTemp = 0;
+ }else{
+ pCell -= 4;
+ pTemp = &aSpace[iSpace];
+ iSpace += sz;
+ assert( iSpace<=pBt->pageSize*5 );
+ /* Obscure case for non-leaf-data trees: If the cell at pCell was
+ ** previously stored on a leaf node, and its reported size was 4
+ ** bytes, then it may actually be smaller than this
+ ** (see sqlite3BtreeParseCellPtr(), 4 bytes is the minimum size of
+ ** any cell). But it is important to pass the correct size to
+ ** insertCell(), so reparse the cell now.
+ **
+ ** Note that this can never happen in an SQLite data file, as all
+ ** cells are at least 4 bytes. It only happens in b-trees used
+ ** to evaluate "IN (SELECT ...)" and similar clauses.
+ */
+ if( szCell[j]==4 ){
+ assert(leafCorrection==4);
+ sz = cellSizePtr(pParent, pCell);
+ }
+ }
+ rc = insertCell(pParent, nxDiv, pCell, sz, pTemp, 4);
+ if( rc!=SQLITE_OK ) goto balance_cleanup;
+ put4byte(findOverflowCell(pParent,nxDiv), pNew->pgno);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* If this is an auto-vacuum database, and not a leaf-data tree,
+ ** then update the pointer map with an entry for the overflow page
+ ** that the cell just inserted points to (if any).
+ */
+ if( pBt->autoVacuum && !leafData ){
+ rc = ptrmapPutOvfl(pParent, nxDiv);
+ if( rc!=SQLITE_OK ){
+ goto balance_cleanup;
+ }
+ }
+#endif
+ j++;
+ nxDiv++;
+ }
+ }
+ assert( j==nCell );
+ assert( nOld>0 );
+ assert( nNew>0 );
+ if( (pageFlags & PTF_LEAF)==0 ){
+ memcpy(&apNew[nNew-1]->aData[8], &apCopy[nOld-1]->aData[8], 4);
+ }
+ if( nxDiv==pParent->nCell+pParent->nOverflow ){
+ /* Right-most sibling is the right-most child of pParent */
+ put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew[nNew-1]);
+ }else{
+ /* Right-most sibling is the left child of the first entry in pParent
+ ** past the right-most divider entry */
+ put4byte(findOverflowCell(pParent, nxDiv), pgnoNew[nNew-1]);
+ }
+
+ /*
+ ** Reparent children of all cells.
+ */
+ for(i=0; i<nNew; i++){
+ rc = reparentChildPages(apNew[i]);
+ if( rc!=SQLITE_OK ) goto balance_cleanup;
+ }
+ rc = reparentChildPages(pParent);
+ if( rc!=SQLITE_OK ) goto balance_cleanup;
+
+ /*
+ ** Balance the parent page. Note that the current page (pPage) might
+ ** have been added to the freelist so it might no longer be initialized.
+ ** But the parent page will always be initialized.
+ */
+ assert( pParent->isInit );
+ rc = balance(pParent, 0);
+
+ /*
+ ** Cleanup before returning.
+ */
+balance_cleanup:
+ sqlite3_free(apCell);
+ for(i=0; i<nOld; i++){
+ releasePage(apOld[i]);
+ }
+ for(i=0; i<nNew; i++){
+ releasePage(apNew[i]);
+ }
+ releasePage(pParent);
+ TRACE(("BALANCE: finished with %d: old=%d new=%d cells=%d\n",
+ pPage->pgno, nOld, nNew, nCell));
+ return rc;
+}
+
+/*
+** This routine is called for the root page of a btree when the root
+** page contains no cells. This is an opportunity to make the tree
+** shallower by one level.
+*/
+static int balance_shallower(MemPage *pPage){
+ MemPage *pChild; /* The only child page of pPage */
+ Pgno pgnoChild; /* Page number for pChild */
+ int rc = SQLITE_OK; /* Return code from subprocedures */
+ BtShared *pBt; /* The main BTree structure */
+ int mxCellPerPage; /* Maximum number of cells per page */
+ u8 **apCell; /* All cells from pages being balanced */
+ u16 *szCell; /* Local size of all cells */
+
+ assert( pPage->pParent==0 );
+ assert( pPage->nCell==0 );
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ pBt = pPage->pBt;
+ mxCellPerPage = MX_CELL(pBt);
+ apCell = sqlite3_malloc( mxCellPerPage*(sizeof(u8*)+sizeof(u16)) );
+ if( apCell==0 ) return SQLITE_NOMEM;
+ szCell = (u16*)&apCell[mxCellPerPage];
+ if( pPage->leaf ){
+ /* The table is completely empty */
+ TRACE(("BALANCE: empty table %d\n", pPage->pgno));
+ }else{
+ /* The root page is empty but has one child. Transfer the
+ ** information from that one child into the root page if it
+ ** will fit. This reduces the depth of the tree by one.
+ **
+ ** If the root page is page 1, it has less space available than
+ ** its child (due to the 100 byte header that occurs at the beginning
+ ** of the database fle), so it might not be able to hold all of the
+ ** information currently contained in the child. If this is the
+ ** case, then do not do the transfer. Leave page 1 empty except
+ ** for the right-pointer to the child page. The child page becomes
+ ** the virtual root of the tree.
+ */
+ pgnoChild = get4byte(&pPage->aData[pPage->hdrOffset+8]);
+ assert( pgnoChild>0 );
+ assert( pgnoChild<=sqlite3PagerPagecount(pPage->pBt->pPager) );
+ rc = sqlite3BtreeGetPage(pPage->pBt, pgnoChild, &pChild, 0);
+ if( rc ) goto end_shallow_balance;
+ if( pPage->pgno==1 ){
+ rc = sqlite3BtreeInitPage(pChild, pPage);
+ if( rc ) goto end_shallow_balance;
+ assert( pChild->nOverflow==0 );
+ if( pChild->nFree>=100 ){
+ /* The child information will fit on the root page, so do the
+ ** copy */
+ int i;
+ zeroPage(pPage, pChild->aData[0]);
+ for(i=0; i<pChild->nCell; i++){
+ apCell[i] = findCell(pChild,i);
+ szCell[i] = cellSizePtr(pChild, apCell[i]);
+ }
+ assemblePage(pPage, pChild->nCell, apCell, szCell);
+ /* Copy the right-pointer of the child to the parent. */
+ put4byte(&pPage->aData[pPage->hdrOffset+8],
+ get4byte(&pChild->aData[pChild->hdrOffset+8]));
+ freePage(pChild);
+ TRACE(("BALANCE: child %d transfer to page 1\n", pChild->pgno));
+ }else{
+ /* The child has more information that will fit on the root.
+ ** The tree is already balanced. Do nothing. */
+ TRACE(("BALANCE: child %d will not fit on page 1\n", pChild->pgno));
+ }
+ }else{
+ memcpy(pPage->aData, pChild->aData, pPage->pBt->usableSize);
+ pPage->isInit = 0;
+ pPage->pParent = 0;
+ rc = sqlite3BtreeInitPage(pPage, 0);
+ assert( rc==SQLITE_OK );
+ freePage(pChild);
+ TRACE(("BALANCE: transfer child %d into root %d\n",
+ pChild->pgno, pPage->pgno));
+ }
+ rc = reparentChildPages(pPage);
+ assert( pPage->nOverflow==0 );
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ int i;
+ for(i=0; i<pPage->nCell; i++){
+ rc = ptrmapPutOvfl(pPage, i);
+ if( rc!=SQLITE_OK ){
+ goto end_shallow_balance;
+ }
+ }
+ }
+#endif
+ releasePage(pChild);
+ }
+end_shallow_balance:
+ sqlite3_free(apCell);
+ return rc;
+}
+
+
+/*
+** The root page is overfull
+**
+** When this happens, Create a new child page and copy the
+** contents of the root into the child. Then make the root
+** page an empty page with rightChild pointing to the new
+** child. Finally, call balance_internal() on the new child
+** to cause it to split.
+*/
+static int balance_deeper(MemPage *pPage){
+ int rc; /* Return value from subprocedures */
+ MemPage *pChild; /* Pointer to a new child page */
+ Pgno pgnoChild; /* Page number of the new child page */
+ BtShared *pBt; /* The BTree */
+ int usableSize; /* Total usable size of a page */
+ u8 *data; /* Content of the parent page */
+ u8 *cdata; /* Content of the child page */
+ int hdr; /* Offset to page header in parent */
+ int brk; /* Offset to content of first cell in parent */
+
+ assert( pPage->pParent==0 );
+ assert( pPage->nOverflow>0 );
+ pBt = pPage->pBt;
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ rc = allocateBtreePage(pBt, &pChild, &pgnoChild, pPage->pgno, 0);
+ if( rc ) return rc;
+ assert( sqlite3PagerIswriteable(pChild->pDbPage) );
+ usableSize = pBt->usableSize;
+ data = pPage->aData;
+ hdr = pPage->hdrOffset;
+ brk = get2byte(&data[hdr+5]);
+ cdata = pChild->aData;
+ memcpy(cdata, &data[hdr], pPage->cellOffset+2*pPage->nCell-hdr);
+ memcpy(&cdata[brk], &data[brk], usableSize-brk);
+ assert( pChild->isInit==0 );
+ rc = sqlite3BtreeInitPage(pChild, pPage);
+ if( rc ) goto balancedeeper_out;
+ memcpy(pChild->aOvfl, pPage->aOvfl, pPage->nOverflow*sizeof(pPage->aOvfl[0]));
+ pChild->nOverflow = pPage->nOverflow;
+ if( pChild->nOverflow ){
+ pChild->nFree = 0;
+ }
+ assert( pChild->nCell==pPage->nCell );
+ zeroPage(pPage, pChild->aData[0] & ~PTF_LEAF);
+ put4byte(&pPage->aData[pPage->hdrOffset+8], pgnoChild);
+ TRACE(("BALANCE: copy root %d into %d\n", pPage->pgno, pChild->pgno));
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ int i;
+ rc = ptrmapPut(pBt, pChild->pgno, PTRMAP_BTREE, pPage->pgno);
+ if( rc ) goto balancedeeper_out;
+ for(i=0; i<pChild->nCell; i++){
+ rc = ptrmapPutOvfl(pChild, i);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ }
+#endif
+ rc = balance_nonroot(pChild);
+
+balancedeeper_out:
+ releasePage(pChild);
+ return rc;
+}
+
+/*
+** Decide if the page pPage needs to be balanced. If balancing is
+** required, call the appropriate balancing routine.
+*/
+static int balance(MemPage *pPage, int insert){
+ int rc = SQLITE_OK;
+ assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ if( pPage->pParent==0 ){
+ rc = sqlite3PagerWrite(pPage->pDbPage);
+ if( rc==SQLITE_OK && pPage->nOverflow>0 ){
+ rc = balance_deeper(pPage);
+ }
+ if( rc==SQLITE_OK && pPage->nCell==0 ){
+ rc = balance_shallower(pPage);
+ }
+ }else{
+ if( pPage->nOverflow>0 ||
+ (!insert && pPage->nFree>pPage->pBt->usableSize*2/3) ){
+ rc = balance_nonroot(pPage);
+ }
+ }
+ return rc;
+}
+
+/*
+** This routine checks all cursors that point to table pgnoRoot.
+** If any of those cursors were opened with wrFlag==0 in a different
+** database connection (a database connection that shares the pager
+** cache with the current connection) and that other connection
+** is not in the ReadUncommmitted state, then this routine returns
+** SQLITE_LOCKED.
+**
+** In addition to checking for read-locks (where a read-lock
+** means a cursor opened with wrFlag==0) this routine also moves
+** all write cursors so that they are pointing to the
+** first Cell on the root page. This is necessary because an insert
+** or delete might change the number of cells on a page or delete
+** a page entirely and we do not want to leave any cursors
+** pointing to non-existant pages or cells.
+*/
+static int checkReadLocks(Btree *pBtree, Pgno pgnoRoot, BtCursor *pExclude){
+ BtCursor *p;
+ BtShared *pBt = pBtree->pBt;
+ sqlite3 *db = pBtree->db;
+ assert( sqlite3BtreeHoldsMutex(pBtree) );
+ for(p=pBt->pCursor; p; p=p->pNext){
+ if( p==pExclude ) continue;
+ if( p->eState!=CURSOR_VALID ) continue;
+ if( p->pgnoRoot!=pgnoRoot ) continue;
+ if( p->wrFlag==0 ){
+ sqlite3 *dbOther = p->pBtree->db;
+ if( dbOther==0 ||
+ (dbOther!=db && (dbOther->flags & SQLITE_ReadUncommitted)==0) ){
+ return SQLITE_LOCKED;
+ }
+ }else if( p->pPage->pgno!=p->pgnoRoot ){
+ moveToRoot(p);
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Make sure pBt->pTmpSpace points to an allocation of
+** MX_CELL_SIZE(pBt) bytes.
+*/
+static void allocateTempSpace(BtShared *pBt){
+ if( !pBt->pTmpSpace ){
+ pBt->pTmpSpace = sqlite3_malloc(MX_CELL_SIZE(pBt));
+ }
+}
+
+/*
+** Insert a new record into the BTree. The key is given by (pKey,nKey)
+** and the data is given by (pData,nData). The cursor is used only to
+** define what table the record should be inserted into. The cursor
+** is left pointing at a random location.
+**
+** For an INTKEY table, only the nKey value of the key is used. pKey is
+** ignored. For a ZERODATA table, the pData and nData are both ignored.
+*/
+SQLITE_PRIVATE int sqlite3BtreeInsert(
+ BtCursor *pCur, /* Insert data into the table of this cursor */
+ const void *pKey, i64 nKey, /* The key of the new record */
+ const void *pData, int nData, /* The data of the new record */
+ int nZero, /* Number of extra 0 bytes to append to data */
+ int appendBias /* True if this is likely an append */
+){
+ int rc;
+ int loc;
+ int szNew;
+ MemPage *pPage;
+ Btree *p = pCur->pBtree;
+ BtShared *pBt = p->pBt;
+ unsigned char *oldCell;
+ unsigned char *newCell = 0;
+
+ assert( cursorHoldsMutex(pCur) );
+ if( pBt->inTransaction!=TRANS_WRITE ){
+ /* Must start a transaction before doing an insert */
+ rc = pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ return rc;
+ }
+ assert( !pBt->readOnly );
+ if( !pCur->wrFlag ){
+ return SQLITE_PERM; /* Cursor not open for writing */
+ }
+ if( checkReadLocks(pCur->pBtree, pCur->pgnoRoot, pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+ if( pCur->eState==CURSOR_FAULT ){
+ return pCur->skip;
+ }
+
+ /* Save the positions of any other cursors open on this table */
+ clearCursorPosition(pCur);
+ if(
+ SQLITE_OK!=(rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur)) ||
+ SQLITE_OK!=(rc = sqlite3BtreeMoveto(pCur, pKey, 0, nKey, appendBias, &loc))
+ ){
+ return rc;
+ }
+
+ pPage = pCur->pPage;
+ assert( pPage->intKey || nKey>=0 );
+ assert( pPage->leaf || !pPage->leafData );
+ TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n",
+ pCur->pgnoRoot, nKey, nData, pPage->pgno,
+ loc==0 ? "overwrite" : "new entry"));
+ assert( pPage->isInit );
+ allocateTempSpace(pBt);
+ newCell = pBt->pTmpSpace;
+ if( newCell==0 ) return SQLITE_NOMEM;
+ rc = fillInCell(pPage, newCell, pKey, nKey, pData, nData, nZero, &szNew);
+ if( rc ) goto end_insert;
+ assert( szNew==cellSizePtr(pPage, newCell) );
+ assert( szNew<=MX_CELL_SIZE(pBt) );
+ if( loc==0 && CURSOR_VALID==pCur->eState ){
+ u16 szOld;
+ assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
+ rc = sqlite3PagerWrite(pPage->pDbPage);
+ if( rc ){
+ goto end_insert;
+ }
+ oldCell = findCell(pPage, pCur->idx);
+ if( !pPage->leaf ){
+ memcpy(newCell, oldCell, 4);
+ }
+ szOld = cellSizePtr(pPage, oldCell);
+ rc = clearCell(pPage, oldCell);
+ if( rc ) goto end_insert;
+ dropCell(pPage, pCur->idx, szOld);
+ }else if( loc<0 && pPage->nCell>0 ){
+ assert( pPage->leaf );
+ pCur->idx++;
+ pCur->info.nSize = 0;
+ pCur->validNKey = 0;
+ }else{
+ assert( pPage->leaf );
+ }
+ rc = insertCell(pPage, pCur->idx, newCell, szNew, 0, 0);
+ if( rc!=SQLITE_OK ) goto end_insert;
+ rc = balance(pPage, 1);
+ /* sqlite3BtreePageDump(pCur->pBt, pCur->pgnoRoot, 1); */
+ /* fflush(stdout); */
+ if( rc==SQLITE_OK ){
+ moveToRoot(pCur);
+ }
+end_insert:
+ return rc;
+}
+
+/*
+** Delete the entry that the cursor is pointing to. The cursor
+** is left pointing at a random location.
+*/
+SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur){
+ MemPage *pPage = pCur->pPage;
+ unsigned char *pCell;
+ int rc;
+ Pgno pgnoChild = 0;
+ Btree *p = pCur->pBtree;
+ BtShared *pBt = p->pBt;
+
+ assert( cursorHoldsMutex(pCur) );
+ assert( pPage->isInit );
+ if( pBt->inTransaction!=TRANS_WRITE ){
+ /* Must start a transaction before doing a delete */
+ rc = pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ return rc;
+ }
+ assert( !pBt->readOnly );
+ if( pCur->eState==CURSOR_FAULT ){
+ return pCur->skip;
+ }
+ if( pCur->idx >= pPage->nCell ){
+ return SQLITE_ERROR; /* The cursor is not pointing to anything */
+ }
+ if( !pCur->wrFlag ){
+ return SQLITE_PERM; /* Did not open this cursor for writing */
+ }
+ if( checkReadLocks(pCur->pBtree, pCur->pgnoRoot, pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+
+ /* Restore the current cursor position (a no-op if the cursor is not in
+ ** CURSOR_REQUIRESEEK state) and save the positions of any other cursors
+ ** open on the same table. Then call sqlite3PagerWrite() on the page
+ ** that the entry will be deleted from.
+ */
+ if(
+ (rc = restoreOrClearCursorPosition(pCur))!=0 ||
+ (rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur))!=0 ||
+ (rc = sqlite3PagerWrite(pPage->pDbPage))!=0
+ ){
+ return rc;
+ }
+
+ /* Locate the cell within its page and leave pCell pointing to the
+ ** data. The clearCell() call frees any overflow pages associated with the
+ ** cell. The cell itself is still intact.
+ */
+ pCell = findCell(pPage, pCur->idx);
+ if( !pPage->leaf ){
+ pgnoChild = get4byte(pCell);
+ }
+ rc = clearCell(pPage, pCell);
+ if( rc ){
+ return rc;
+ }
+
+ if( !pPage->leaf ){
+ /*
+ ** The entry we are about to delete is not a leaf so if we do not
+ ** do something we will leave a hole on an internal page.
+ ** We have to fill the hole by moving in a cell from a leaf. The
+ ** next Cell after the one to be deleted is guaranteed to exist and
+ ** to be a leaf so we can use it.
+ */
+ BtCursor leafCur;
+ unsigned char *pNext;
+ int notUsed;
+ unsigned char *tempCell = 0;
+ assert( !pPage->leafData );
+ sqlite3BtreeGetTempCursor(pCur, &leafCur);
+ rc = sqlite3BtreeNext(&leafCur, &notUsed);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerWrite(leafCur.pPage->pDbPage);
+ }
+ if( rc==SQLITE_OK ){
+ u16 szNext;
+ TRACE(("DELETE: table=%d delete internal from %d replace from leaf %d\n",
+ pCur->pgnoRoot, pPage->pgno, leafCur.pPage->pgno));
+ dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell));
+ pNext = findCell(leafCur.pPage, leafCur.idx);
+ szNext = cellSizePtr(leafCur.pPage, pNext);
+ assert( MX_CELL_SIZE(pBt)>=szNext+4 );
+ allocateTempSpace(pBt);
+ tempCell = pBt->pTmpSpace;
+ if( tempCell==0 ){
+ rc = SQLITE_NOMEM;
+ }
+ if( rc==SQLITE_OK ){
+ rc = insertCell(pPage, pCur->idx, pNext-4, szNext+4, tempCell, 0);
+ }
+ if( rc==SQLITE_OK ){
+ put4byte(findOverflowCell(pPage, pCur->idx), pgnoChild);
+ rc = balance(pPage, 0);
+ }
+ if( rc==SQLITE_OK ){
+ dropCell(leafCur.pPage, leafCur.idx, szNext);
+ rc = balance(leafCur.pPage, 0);
+ }
+ }
+ sqlite3BtreeReleaseTempCursor(&leafCur);
+ }else{
+ TRACE(("DELETE: table=%d delete from leaf %d\n",
+ pCur->pgnoRoot, pPage->pgno));
+ dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell));
+ rc = balance(pPage, 0);
+ }
+ if( rc==SQLITE_OK ){
+ moveToRoot(pCur);
+ }
+ return rc;
+}
+
+/*
+** Create a new BTree table. Write into *piTable the page
+** number for the root page of the new table.
+**
+** The type of type is determined by the flags parameter. Only the
+** following values of flags are currently in use. Other values for
+** flags might not work:
+**
+** BTREE_INTKEY|BTREE_LEAFDATA Used for SQL tables with rowid keys
+** BTREE_ZERODATA Used for SQL indices
+*/
+static int btreeCreateTable(Btree *p, int *piTable, int flags){
+ BtShared *pBt = p->pBt;
+ MemPage *pRoot;
+ Pgno pgnoRoot;
+ int rc;
+
+ assert( sqlite3BtreeHoldsMutex(p) );
+ if( pBt->inTransaction!=TRANS_WRITE ){
+ /* Must start a transaction first */
+ rc = pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ return rc;
+ }
+ assert( !pBt->readOnly );
+
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ rc = allocateBtreePage(pBt, &pRoot, &pgnoRoot, 1, 0);
+ if( rc ){
+ return rc;
+ }
+#else
+ if( pBt->autoVacuum ){
+ Pgno pgnoMove; /* Move a page here to make room for the root-page */
+ MemPage *pPageMove; /* The page to move to. */
+
+ /* Creating a new table may probably require moving an existing database
+ ** to make room for the new tables root page. In case this page turns
+ ** out to be an overflow page, delete all overflow page-map caches
+ ** held by open cursors.
+ */
+ invalidateAllOverflowCache(pBt);
+
+ /* Read the value of meta[3] from the database to determine where the
+ ** root page of the new table should go. meta[3] is the largest root-page
+ ** created so far, so the new root-page is (meta[3]+1).
+ */
+ rc = sqlite3BtreeGetMeta(p, 4, &pgnoRoot);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ pgnoRoot++;
+
+ /* The new root-page may not be allocated on a pointer-map page, or the
+ ** PENDING_BYTE page.
+ */
+ while( pgnoRoot==PTRMAP_PAGENO(pBt, pgnoRoot) ||
+ pgnoRoot==PENDING_BYTE_PAGE(pBt) ){
+ pgnoRoot++;
+ }
+ assert( pgnoRoot>=3 );
+
+ /* Allocate a page. The page that currently resides at pgnoRoot will
+ ** be moved to the allocated page (unless the allocated page happens
+ ** to reside at pgnoRoot).
+ */
+ rc = allocateBtreePage(pBt, &pPageMove, &pgnoMove, pgnoRoot, 1);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ if( pgnoMove!=pgnoRoot ){
+ /* pgnoRoot is the page that will be used for the root-page of
+ ** the new table (assuming an error did not occur). But we were
+ ** allocated pgnoMove. If required (i.e. if it was not allocated
+ ** by extending the file), the current page at position pgnoMove
+ ** is already journaled.
+ */
+ u8 eType;
+ Pgno iPtrPage;
+
+ releasePage(pPageMove);
+
+ /* Move the page currently at pgnoRoot to pgnoMove. */
+ rc = sqlite3BtreeGetPage(pBt, pgnoRoot, &pRoot, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = ptrmapGet(pBt, pgnoRoot, &eType, &iPtrPage);
+ if( rc!=SQLITE_OK || eType==PTRMAP_ROOTPAGE || eType==PTRMAP_FREEPAGE ){
+ releasePage(pRoot);
+ return rc;
+ }
+ assert( eType!=PTRMAP_ROOTPAGE );
+ assert( eType!=PTRMAP_FREEPAGE );
+ rc = sqlite3PagerWrite(pRoot->pDbPage);
+ if( rc!=SQLITE_OK ){
+ releasePage(pRoot);
+ return rc;
+ }
+ rc = relocatePage(pBt, pRoot, eType, iPtrPage, pgnoMove);
+ releasePage(pRoot);
+
+ /* Obtain the page at pgnoRoot */
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = sqlite3BtreeGetPage(pBt, pgnoRoot, &pRoot, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = sqlite3PagerWrite(pRoot->pDbPage);
+ if( rc!=SQLITE_OK ){
+ releasePage(pRoot);
+ return rc;
+ }
+ }else{
+ pRoot = pPageMove;
+ }
+
+ /* Update the pointer-map and meta-data with the new root-page number. */
+ rc = ptrmapPut(pBt, pgnoRoot, PTRMAP_ROOTPAGE, 0);
+ if( rc ){
+ releasePage(pRoot);
+ return rc;
+ }
+ rc = sqlite3BtreeUpdateMeta(p, 4, pgnoRoot);
+ if( rc ){
+ releasePage(pRoot);
+ return rc;
+ }
+
+ }else{
+ rc = allocateBtreePage(pBt, &pRoot, &pgnoRoot, 1, 0);
+ if( rc ) return rc;
+ }
+#endif
+ assert( sqlite3PagerIswriteable(pRoot->pDbPage) );
+ zeroPage(pRoot, flags | PTF_LEAF);
+ sqlite3PagerUnref(pRoot->pDbPage);
+ *piTable = (int)pgnoRoot;
+ return SQLITE_OK;
+}
+SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree *p, int *piTable, int flags){
+ int rc;
+ sqlite3BtreeEnter(p);
+ p->pBt->db = p->db;
+ rc = btreeCreateTable(p, piTable, flags);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Erase the given database page and all its children. Return
+** the page to the freelist.
+*/
+static int clearDatabasePage(
+ BtShared *pBt, /* The BTree that contains the table */
+ Pgno pgno, /* Page number to clear */
+ MemPage *pParent, /* Parent page. NULL for the root */
+ int freePageFlag /* Deallocate page if true */
+){
+ MemPage *pPage = 0;
+ int rc;
+ unsigned char *pCell;
+ int i;
+
+ assert( sqlite3_mutex_held(pBt->mutex) );
+ if( pgno>sqlite3PagerPagecount(pBt->pPager) ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ rc = getAndInitPage(pBt, pgno, &pPage, pParent);
+ if( rc ) goto cleardatabasepage_out;
+ for(i=0; i<pPage->nCell; i++){
+ pCell = findCell(pPage, i);
+ if( !pPage->leaf ){
+ rc = clearDatabasePage(pBt, get4byte(pCell), pPage->pParent, 1);
+ if( rc ) goto cleardatabasepage_out;
+ }
+ rc = clearCell(pPage, pCell);
+ if( rc ) goto cleardatabasepage_out;
+ }
+ if( !pPage->leaf ){
+ rc = clearDatabasePage(pBt, get4byte(&pPage->aData[8]), pPage->pParent, 1);
+ if( rc ) goto cleardatabasepage_out;
+ }
+ if( freePageFlag ){
+ rc = freePage(pPage);
+ }else if( (rc = sqlite3PagerWrite(pPage->pDbPage))==0 ){
+ zeroPage(pPage, pPage->aData[0] | PTF_LEAF);
+ }
+
+cleardatabasepage_out:
+ releasePage(pPage);
+ return rc;
+}
+
+/*
+** Delete all information from a single table in the database. iTable is
+** the page number of the root of the table. After this routine returns,
+** the root page is empty, but still exists.
+**
+** This routine will fail with SQLITE_LOCKED if there are any open
+** read cursors on the table. Open write cursors are moved to the
+** root of the table.
+*/
+SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable){
+ int rc;
+ BtShared *pBt = p->pBt;
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ if( p->inTrans!=TRANS_WRITE ){
+ rc = pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }else if( (rc = checkReadLocks(p, iTable, 0))!=SQLITE_OK ){
+ /* nothing to do */
+ }else if( SQLITE_OK!=(rc = saveAllCursors(pBt, iTable, 0)) ){
+ /* nothing to do */
+ }else{
+ rc = clearDatabasePage(pBt, (Pgno)iTable, 0, 0);
+ }
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Erase all information in a table and add the root of the table to
+** the freelist. Except, the root of the principle table (the one on
+** page 1) is never added to the freelist.
+**
+** This routine will fail with SQLITE_LOCKED if there are any open
+** cursors on the table.
+**
+** If AUTOVACUUM is enabled and the page at iTable is not the last
+** root page in the database file, then the last root page
+** in the database file is moved into the slot formerly occupied by
+** iTable and that last slot formerly occupied by the last root page
+** is added to the freelist instead of iTable. In this say, all
+** root pages are kept at the beginning of the database file, which
+** is necessary for AUTOVACUUM to work right. *piMoved is set to the
+** page number that used to be the last root page in the file before
+** the move. If no page gets moved, *piMoved is set to 0.
+** The last root page is recorded in meta[3] and the value of
+** meta[3] is updated by this procedure.
+*/
+static int btreeDropTable(Btree *p, int iTable, int *piMoved){
+ int rc;
+ MemPage *pPage = 0;
+ BtShared *pBt = p->pBt;
+
+ assert( sqlite3BtreeHoldsMutex(p) );
+ if( p->inTrans!=TRANS_WRITE ){
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+
+ /* It is illegal to drop a table if any cursors are open on the
+ ** database. This is because in auto-vacuum mode the backend may
+ ** need to move another root-page to fill a gap left by the deleted
+ ** root page. If an open cursor was using this page a problem would
+ ** occur.
+ */
+ if( pBt->pCursor ){
+ return SQLITE_LOCKED;
+ }
+
+ rc = sqlite3BtreeGetPage(pBt, (Pgno)iTable, &pPage, 0);
+ if( rc ) return rc;
+ rc = sqlite3BtreeClearTable(p, iTable);
+ if( rc ){
+ releasePage(pPage);
+ return rc;
+ }
+
+ *piMoved = 0;
+
+ if( iTable>1 ){
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ rc = freePage(pPage);
+ releasePage(pPage);
+#else
+ if( pBt->autoVacuum ){
+ Pgno maxRootPgno;
+ rc = sqlite3BtreeGetMeta(p, 4, &maxRootPgno);
+ if( rc!=SQLITE_OK ){
+ releasePage(pPage);
+ return rc;
+ }
+
+ if( iTable==maxRootPgno ){
+ /* If the table being dropped is the table with the largest root-page
+ ** number in the database, put the root page on the free list.
+ */
+ rc = freePage(pPage);
+ releasePage(pPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }else{
+ /* The table being dropped does not have the largest root-page
+ ** number in the database. So move the page that does into the
+ ** gap left by the deleted root-page.
+ */
+ MemPage *pMove;
+ releasePage(pPage);
+ rc = sqlite3BtreeGetPage(pBt, maxRootPgno, &pMove, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = relocatePage(pBt, pMove, PTRMAP_ROOTPAGE, 0, iTable);
+ releasePage(pMove);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = sqlite3BtreeGetPage(pBt, maxRootPgno, &pMove, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = freePage(pMove);
+ releasePage(pMove);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ *piMoved = maxRootPgno;
+ }
+
+ /* Set the new 'max-root-page' value in the database header. This
+ ** is the old value less one, less one more if that happens to
+ ** be a root-page number, less one again if that is the
+ ** PENDING_BYTE_PAGE.
+ */
+ maxRootPgno--;
+ if( maxRootPgno==PENDING_BYTE_PAGE(pBt) ){
+ maxRootPgno--;
+ }
+ if( maxRootPgno==PTRMAP_PAGENO(pBt, maxRootPgno) ){
+ maxRootPgno--;
+ }
+ assert( maxRootPgno!=PENDING_BYTE_PAGE(pBt) );
+
+ rc = sqlite3BtreeUpdateMeta(p, 4, maxRootPgno);
+ }else{
+ rc = freePage(pPage);
+ releasePage(pPage);
+ }
+#endif
+ }else{
+ /* If sqlite3BtreeDropTable was called on page 1. */
+ zeroPage(pPage, PTF_INTKEY|PTF_LEAF );
+ releasePage(pPage);
+ }
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){
+ int rc;
+ sqlite3BtreeEnter(p);
+ p->pBt->db = p->db;
+ rc = btreeDropTable(p, iTable, piMoved);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+
+/*
+** Read the meta-information out of a database file. Meta[0]
+** is the number of free pages currently in the database. Meta[1]
+** through meta[15] are available for use by higher layers. Meta[0]
+** is read-only, the others are read/write.
+**
+** The schema layer numbers meta values differently. At the schema
+** layer (and the SetCookie and ReadCookie opcodes) the number of
+** free pages is not visible. So Cookie[0] is the same as Meta[1].
+*/
+SQLITE_PRIVATE int sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
+ DbPage *pDbPage;
+ int rc;
+ unsigned char *pP1;
+ BtShared *pBt = p->pBt;
+
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+
+ /* Reading a meta-data value requires a read-lock on page 1 (and hence
+ ** the sqlite_master table. We grab this lock regardless of whether or
+ ** not the SQLITE_ReadUncommitted flag is set (the table rooted at page
+ ** 1 is treated as a special case by queryTableLock() and lockTable()).
+ */
+ rc = queryTableLock(p, 1, READ_LOCK);
+ if( rc!=SQLITE_OK ){
+ sqlite3BtreeLeave(p);
+ return rc;
+ }
+
+ assert( idx>=0 && idx<=15 );
+ rc = sqlite3PagerGet(pBt->pPager, 1, &pDbPage);
+ if( rc ){
+ sqlite3BtreeLeave(p);
+ return rc;
+ }
+ pP1 = (unsigned char *)sqlite3PagerGetData(pDbPage);
+ *pMeta = get4byte(&pP1[36 + idx*4]);
+ sqlite3PagerUnref(pDbPage);
+
+ /* If autovacuumed is disabled in this build but we are trying to
+ ** access an autovacuumed database, then make the database readonly.
+ */
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ if( idx==4 && *pMeta>0 ) pBt->readOnly = 1;
+#endif
+
+ /* Grab the read-lock on page 1. */
+ rc = lockTable(p, 1, READ_LOCK);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Write meta-information back into the database. Meta[0] is
+** read-only and may not be written.
+*/
+SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
+ BtShared *pBt = p->pBt;
+ unsigned char *pP1;
+ int rc;
+ assert( idx>=1 && idx<=15 );
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ if( p->inTrans!=TRANS_WRITE ){
+ rc = pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }else{
+ assert( pBt->pPage1!=0 );
+ pP1 = pBt->pPage1->aData;
+ rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
+ if( rc==SQLITE_OK ){
+ put4byte(&pP1[36 + idx*4], iMeta);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( idx==7 ){
+ assert( pBt->autoVacuum || iMeta==0 );
+ assert( iMeta==0 || iMeta==1 );
+ pBt->incrVacuum = iMeta;
+ }
+#endif
+ }
+ }
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+/*
+** Return the flag byte at the beginning of the page that the cursor
+** is currently pointing to.
+*/
+SQLITE_PRIVATE int sqlite3BtreeFlags(BtCursor *pCur){
+ /* TODO: What about CURSOR_REQUIRESEEK state? Probably need to call
+ ** restoreOrClearCursorPosition() here.
+ */
+ MemPage *pPage;
+ restoreOrClearCursorPosition(pCur);
+ pPage = pCur->pPage;
+ assert( cursorHoldsMutex(pCur) );
+ assert( pPage->pBt==pCur->pBt );
+ return pPage ? pPage->aData[pPage->hdrOffset] : 0;
+}
+
+
+/*
+** Return the pager associated with a BTree. This routine is used for
+** testing and debugging only.
+*/
+SQLITE_PRIVATE Pager *sqlite3BtreePager(Btree *p){
+ return p->pBt->pPager;
+}
+
+#ifndef SQLITE_OMIT_INTEGRITY_CHECK
+/*
+** Append a message to the error message string.
+*/
+static void checkAppendMsg(
+ IntegrityCk *pCheck,
+ char *zMsg1,
+ const char *zFormat,
+ ...
+){
+ va_list ap;
+ char *zMsg2;
+ if( !pCheck->mxErr ) return;
+ pCheck->mxErr--;
+ pCheck->nErr++;
+ va_start(ap, zFormat);
+ zMsg2 = sqlite3VMPrintf(0, zFormat, ap);
+ va_end(ap);
+ if( zMsg1==0 ) zMsg1 = "";
+ if( pCheck->zErrMsg ){
+ char *zOld = pCheck->zErrMsg;
+ pCheck->zErrMsg = 0;
+ sqlite3SetString(&pCheck->zErrMsg, zOld, "\n", zMsg1, zMsg2, (char*)0);
+ sqlite3_free(zOld);
+ }else{
+ sqlite3SetString(&pCheck->zErrMsg, zMsg1, zMsg2, (char*)0);
+ }
+ sqlite3_free(zMsg2);
+}
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+#ifndef SQLITE_OMIT_INTEGRITY_CHECK
+/*
+** Add 1 to the reference count for page iPage. If this is the second
+** reference to the page, add an error message to pCheck->zErrMsg.
+** Return 1 if there are 2 ore more references to the page and 0 if
+** if this is the first reference to the page.
+**
+** Also check that the page number is in bounds.
+*/
+static int checkRef(IntegrityCk *pCheck, int iPage, char *zContext){
+ if( iPage==0 ) return 1;
+ if( iPage>pCheck->nPage || iPage<0 ){
+ checkAppendMsg(pCheck, zContext, "invalid page number %d", iPage);
+ return 1;
+ }
+ if( pCheck->anRef[iPage]==1 ){
+ checkAppendMsg(pCheck, zContext, "2nd reference to page %d", iPage);
+ return 1;
+ }
+ return (pCheck->anRef[iPage]++)>1;
+}
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+/*
+** Check that the entry in the pointer-map for page iChild maps to
+** page iParent, pointer type ptrType. If not, append an error message
+** to pCheck.
+*/
+static void checkPtrmap(
+ IntegrityCk *pCheck, /* Integrity check context */
+ Pgno iChild, /* Child page number */
+ u8 eType, /* Expected pointer map type */
+ Pgno iParent, /* Expected pointer map parent page number */
+ char *zContext /* Context description (used for error msg) */
+){
+ int rc;
+ u8 ePtrmapType;
+ Pgno iPtrmapParent;
+
+ rc = ptrmapGet(pCheck->pBt, iChild, &ePtrmapType, &iPtrmapParent);
+ if( rc!=SQLITE_OK ){
+ checkAppendMsg(pCheck, zContext, "Failed to read ptrmap key=%d", iChild);
+ return;
+ }
+
+ if( ePtrmapType!=eType || iPtrmapParent!=iParent ){
+ checkAppendMsg(pCheck, zContext,
+ "Bad ptr map entry key=%d expected=(%d,%d) got=(%d,%d)",
+ iChild, eType, iParent, ePtrmapType, iPtrmapParent);
+ }
+}
+#endif
+
+/*
+** Check the integrity of the freelist or of an overflow page list.
+** Verify that the number of pages on the list is N.
+*/
+static void checkList(
+ IntegrityCk *pCheck, /* Integrity checking context */
+ int isFreeList, /* True for a freelist. False for overflow page list */
+ int iPage, /* Page number for first page in the list */
+ int N, /* Expected number of pages in the list */
+ char *zContext /* Context for error messages */
+){
+ int i;
+ int expected = N;
+ int iFirst = iPage;
+ while( N-- > 0 && pCheck->mxErr ){
+ DbPage *pOvflPage;
+ unsigned char *pOvflData;
+ if( iPage<1 ){
+ checkAppendMsg(pCheck, zContext,
+ "%d of %d pages missing from overflow list starting at %d",
+ N+1, expected, iFirst);
+ break;
+ }
+ if( checkRef(pCheck, iPage, zContext) ) break;
+ if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage) ){
+ checkAppendMsg(pCheck, zContext, "failed to get page %d", iPage);
+ break;
+ }
+ pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage);
+ if( isFreeList ){
+ int n = get4byte(&pOvflData[4]);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pCheck->pBt->autoVacuum ){
+ checkPtrmap(pCheck, iPage, PTRMAP_FREEPAGE, 0, zContext);
+ }
+#endif
+ if( n>pCheck->pBt->usableSize/4-8 ){
+ checkAppendMsg(pCheck, zContext,
+ "freelist leaf count too big on page %d", iPage);
+ N--;
+ }else{
+ for(i=0; i<n; i++){
+ Pgno iFreePage = get4byte(&pOvflData[8+i*4]);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pCheck->pBt->autoVacuum ){
+ checkPtrmap(pCheck, iFreePage, PTRMAP_FREEPAGE, 0, zContext);
+ }
+#endif
+ checkRef(pCheck, iFreePage, zContext);
+ }
+ N -= n;
+ }
+ }
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ else{
+ /* If this database supports auto-vacuum and iPage is not the last
+ ** page in this overflow list, check that the pointer-map entry for
+ ** the following page matches iPage.
+ */
+ if( pCheck->pBt->autoVacuum && N>0 ){
+ i = get4byte(pOvflData);
+ checkPtrmap(pCheck, i, PTRMAP_OVERFLOW2, iPage, zContext);
+ }
+ }
+#endif
+ iPage = get4byte(pOvflData);
+ sqlite3PagerUnref(pOvflPage);
+ }
+}
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+#ifndef SQLITE_OMIT_INTEGRITY_CHECK
+/*
+** Do various sanity checks on a single page of a tree. Return
+** the tree depth. Root pages return 0. Parents of root pages
+** return 1, and so forth.
+**
+** These checks are done:
+**
+** 1. Make sure that cells and freeblocks do not overlap
+** but combine to completely cover the page.
+** NO 2. Make sure cell keys are in order.
+** NO 3. Make sure no key is less than or equal to zLowerBound.
+** NO 4. Make sure no key is greater than or equal to zUpperBound.
+** 5. Check the integrity of overflow pages.
+** 6. Recursively call checkTreePage on all children.
+** 7. Verify that the depth of all children is the same.
+** 8. Make sure this page is at least 33% full or else it is
+** the root of the tree.
+*/
+static int checkTreePage(
+ IntegrityCk *pCheck, /* Context for the sanity check */
+ int iPage, /* Page number of the page to check */
+ MemPage *pParent, /* Parent page */
+ char *zParentContext /* Parent context */
+){
+ MemPage *pPage;
+ int i, rc, depth, d2, pgno, cnt;
+ int hdr, cellStart;
+ int nCell;
+ u8 *data;
+ BtShared *pBt;
+ int usableSize;
+ char zContext[100];
+ char *hit;
+
+ sqlite3_snprintf(sizeof(zContext), zContext, "Page %d: ", iPage);
+
+ /* Check that the page exists
+ */
+ pBt = pCheck->pBt;
+ usableSize = pBt->usableSize;
+ if( iPage==0 ) return 0;
+ if( checkRef(pCheck, iPage, zParentContext) ) return 0;
+ if( (rc = sqlite3BtreeGetPage(pBt, (Pgno)iPage, &pPage, 0))!=0 ){
+ checkAppendMsg(pCheck, zContext,
+ "unable to get the page. error code=%d", rc);
+ return 0;
+ }
+ if( (rc = sqlite3BtreeInitPage(pPage, pParent))!=0 ){
+ checkAppendMsg(pCheck, zContext,
+ "sqlite3BtreeInitPage() returns error code %d", rc);
+ releasePage(pPage);
+ return 0;
+ }
+
+ /* Check out all the cells.
+ */
+ depth = 0;
+ for(i=0; i<pPage->nCell && pCheck->mxErr; i++){
+ u8 *pCell;
+ int sz;
+ CellInfo info;
+
+ /* Check payload overflow pages
+ */
+ sqlite3_snprintf(sizeof(zContext), zContext,
+ "On tree page %d cell %d: ", iPage, i);
+ pCell = findCell(pPage,i);
+ sqlite3BtreeParseCellPtr(pPage, pCell, &info);
+ sz = info.nData;
+ if( !pPage->intKey ) sz += info.nKey;
+ assert( sz==info.nPayload );
+ if( sz>info.nLocal ){
+ int nPage = (sz - info.nLocal + usableSize - 5)/(usableSize - 4);
+ Pgno pgnoOvfl = get4byte(&pCell[info.iOverflow]);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, iPage, zContext);
+ }
+#endif
+ checkList(pCheck, 0, pgnoOvfl, nPage, zContext);
+ }
+
+ /* Check sanity of left child page.
+ */
+ if( !pPage->leaf ){
+ pgno = get4byte(pCell);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage, zContext);
+ }
+#endif
+ d2 = checkTreePage(pCheck,pgno,pPage,zContext);
+ if( i>0 && d2!=depth ){
+ checkAppendMsg(pCheck, zContext, "Child page depth differs");
+ }
+ depth = d2;
+ }
+ }
+ if( !pPage->leaf ){
+ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
+ sqlite3_snprintf(sizeof(zContext), zContext,
+ "On page %d at right child: ", iPage);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage, 0);
+ }
+#endif
+ checkTreePage(pCheck, pgno, pPage, zContext);
+ }
+
+ /* Check for complete coverage of the page
+ */
+ data = pPage->aData;
+ hdr = pPage->hdrOffset;
+ hit = sqlite3MallocZero( usableSize );
+ if( hit ){
+ memset(hit, 1, get2byte(&data[hdr+5]));
+ nCell = get2byte(&data[hdr+3]);
+ cellStart = hdr + 12 - 4*pPage->leaf;
+ for(i=0; i<nCell; i++){
+ int pc = get2byte(&data[cellStart+i*2]);
+ u16 size = cellSizePtr(pPage, &data[pc]);
+ int j;
+ if( (pc+size-1)>=usableSize || pc<0 ){
+ checkAppendMsg(pCheck, 0,
+ "Corruption detected in cell %d on page %d",i,iPage,0);
+ }else{
+ for(j=pc+size-1; j>=pc; j--) hit[j]++;
+ }
+ }
+ for(cnt=0, i=get2byte(&data[hdr+1]); i>0 && i<usableSize && cnt<10000;
+ cnt++){
+ int size = get2byte(&data[i+2]);
+ int j;
+ if( (i+size-1)>=usableSize || i<0 ){
+ checkAppendMsg(pCheck, 0,
+ "Corruption detected in cell %d on page %d",i,iPage,0);
+ }else{
+ for(j=i+size-1; j>=i; j--) hit[j]++;
+ }
+ i = get2byte(&data[i]);
+ }
+ for(i=cnt=0; i<usableSize; i++){
+ if( hit[i]==0 ){
+ cnt++;
+ }else if( hit[i]>1 ){
+ checkAppendMsg(pCheck, 0,
+ "Multiple uses for byte %d of page %d", i, iPage);
+ break;
+ }
+ }
+ if( cnt!=data[hdr+7] ){
+ checkAppendMsg(pCheck, 0,
+ "Fragmented space is %d byte reported as %d on page %d",
+ cnt, data[hdr+7], iPage);
+ }
+ }
+ sqlite3_free(hit);
+
+ releasePage(pPage);
+ return depth+1;
+}
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+#ifndef SQLITE_OMIT_INTEGRITY_CHECK
+/*
+** This routine does a complete check of the given BTree file. aRoot[] is
+** an array of pages numbers were each page number is the root page of
+** a table. nRoot is the number of entries in aRoot.
+**
+** If everything checks out, this routine returns NULL. If something is
+** amiss, an error message is written into memory obtained from malloc()
+** and a pointer to that error message is returned. The calling function
+** is responsible for freeing the error message when it is done.
+*/
+SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(
+ Btree *p, /* The btree to be checked */
+ int *aRoot, /* An array of root pages numbers for individual trees */
+ int nRoot, /* Number of entries in aRoot[] */
+ int mxErr, /* Stop reporting errors after this many */
+ int *pnErr /* Write number of errors seen to this variable */
+){
+ int i;
+ int nRef;
+ IntegrityCk sCheck;
+ BtShared *pBt = p->pBt;
+
+ sqlite3BtreeEnter(p);
+ pBt->db = p->db;
+ nRef = sqlite3PagerRefcount(pBt->pPager);
+ if( lockBtreeWithRetry(p)!=SQLITE_OK ){
+ sqlite3BtreeLeave(p);
+ return sqlite3StrDup("Unable to acquire a read lock on the database");
+ }
+ sCheck.pBt = pBt;
+ sCheck.pPager = pBt->pPager;
+ sCheck.nPage = sqlite3PagerPagecount(sCheck.pPager);
+ sCheck.mxErr = mxErr;
+ sCheck.nErr = 0;
+ *pnErr = 0;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->nTrunc!=0 ){
+ sCheck.nPage = pBt->nTrunc;
+ }
+#endif
+ if( sCheck.nPage==0 ){
+ unlockBtreeIfUnused(pBt);
+ sqlite3BtreeLeave(p);
+ return 0;
+ }
+ sCheck.anRef = sqlite3_malloc( (sCheck.nPage+1)*sizeof(sCheck.anRef[0]) );
+ if( !sCheck.anRef ){
+ unlockBtreeIfUnused(pBt);
+ *pnErr = 1;
+ sqlite3BtreeLeave(p);
+ return sqlite3MPrintf(p->db, "Unable to malloc %d bytes",
+ (sCheck.nPage+1)*sizeof(sCheck.anRef[0]));
+ }
+ for(i=0; i<=sCheck.nPage; i++){ sCheck.anRef[i] = 0; }
+ i = PENDING_BYTE_PAGE(pBt);
+ if( i<=sCheck.nPage ){
+ sCheck.anRef[i] = 1;
+ }
+ sCheck.zErrMsg = 0;
+
+ /* Check the integrity of the freelist
+ */
+ checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
+ get4byte(&pBt->pPage1->aData[36]), "Main freelist: ");
+
+ /* Check all the tables.
+ */
+ for(i=0; i<nRoot && sCheck.mxErr; i++){
+ if( aRoot[i]==0 ) continue;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum && aRoot[i]>1 ){
+ checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0, 0);
+ }
+#endif
+ checkTreePage(&sCheck, aRoot[i], 0, "List of tree roots: ");
+ }
+
+ /* Make sure every page in the file is referenced
+ */
+ for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ if( sCheck.anRef[i]==0 ){
+ checkAppendMsg(&sCheck, 0, "Page %d is never used", i);
+ }
+#else
+ /* If the database supports auto-vacuum, make sure no tables contain
+ ** references to pointer-map pages.
+ */
+ if( sCheck.anRef[i]==0 &&
+ (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){
+ checkAppendMsg(&sCheck, 0, "Page %d is never used", i);
+ }
+ if( sCheck.anRef[i]!=0 &&
+ (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){
+ checkAppendMsg(&sCheck, 0, "Pointer map page %d is referenced", i);
+ }
+#endif
+ }
+
+ /* Make sure this analysis did not leave any unref() pages
+ */
+ unlockBtreeIfUnused(pBt);
+ if( nRef != sqlite3PagerRefcount(pBt->pPager) ){
+ checkAppendMsg(&sCheck, 0,
+ "Outstanding page count goes from %d to %d during this analysis",
+ nRef, sqlite3PagerRefcount(pBt->pPager)
+ );
+ }
+
+ /* Clean up and report errors.
+ */
+ sqlite3BtreeLeave(p);
+ sqlite3_free(sCheck.anRef);
+ *pnErr = sCheck.nErr;
+ return sCheck.zErrMsg;
+}
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+/*
+** Return the full pathname of the underlying database file.
+**
+** The pager filename is invariant as long as the pager is
+** open so it is safe to access without the BtShared mutex.
+*/
+SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *p){
+ assert( p->pBt->pPager!=0 );
+ return sqlite3PagerFilename(p->pBt->pPager);
+}
+
+/*
+** Return the pathname of the directory that contains the database file.
+**
+** The pager directory name is invariant as long as the pager is
+** open so it is safe to access without the BtShared mutex.
+*/
+SQLITE_PRIVATE const char *sqlite3BtreeGetDirname(Btree *p){
+ assert( p->pBt->pPager!=0 );
+ return sqlite3PagerDirname(p->pBt->pPager);
+}
+
+/*
+** Return the pathname of the journal file for this database. The return
+** value of this routine is the same regardless of whether the journal file
+** has been created or not.
+**
+** The pager journal filename is invariant as long as the pager is
+** open so it is safe to access without the BtShared mutex.
+*/
+SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *p){
+ assert( p->pBt->pPager!=0 );
+ return sqlite3PagerJournalname(p->pBt->pPager);
+}
+
+#ifndef SQLITE_OMIT_VACUUM
+/*
+** Copy the complete content of pBtFrom into pBtTo. A transaction
+** must be active for both files.
+**
+** The size of file pTo may be reduced by this operation.
+** If anything goes wrong, the transaction on pTo is rolled back.
+**
+** If successful, CommitPhaseOne() may be called on pTo before returning.
+** The caller should finish committing the transaction on pTo by calling
+** sqlite3BtreeCommit().
+*/
+static int btreeCopyFile(Btree *pTo, Btree *pFrom){
+ int rc = SQLITE_OK;
+ Pgno i;
+
+ Pgno nFromPage; /* Number of pages in pFrom */
+ Pgno nToPage; /* Number of pages in pTo */
+ Pgno nNewPage; /* Number of pages in pTo after the copy */
+
+ Pgno iSkip; /* Pending byte page in pTo */
+ int nToPageSize; /* Page size of pTo in bytes */
+ int nFromPageSize; /* Page size of pFrom in bytes */
+
+ BtShared *pBtTo = pTo->pBt;
+ BtShared *pBtFrom = pFrom->pBt;
+ pBtTo->db = pTo->db;
+ pBtFrom->db = pFrom->db;
+
+ nToPageSize = pBtTo->pageSize;
+ nFromPageSize = pBtFrom->pageSize;
+
+ if( pTo->inTrans!=TRANS_WRITE || pFrom->inTrans!=TRANS_WRITE ){
+ return SQLITE_ERROR;
+ }
+ if( pBtTo->pCursor ){
+ return SQLITE_BUSY;
+ }
+
+ nToPage = sqlite3PagerPagecount(pBtTo->pPager);
+ nFromPage = sqlite3PagerPagecount(pBtFrom->pPager);
+ iSkip = PENDING_BYTE_PAGE(pBtTo);
+
+ /* Variable nNewPage is the number of pages required to store the
+ ** contents of pFrom using the current page-size of pTo.
+ */
+ nNewPage = ((i64)nFromPage * (i64)nFromPageSize + (i64)nToPageSize - 1) /
+ (i64)nToPageSize;
+
+ for(i=1; rc==SQLITE_OK && (i<=nToPage || i<=nNewPage); i++){
+
+ /* Journal the original page.
+ **
+ ** iSkip is the page number of the locking page (PENDING_BYTE_PAGE)
+ ** in database *pTo (before the copy). This page is never written
+ ** into the journal file. Unless i==iSkip or the page was not
+ ** present in pTo before the copy operation, journal page i from pTo.
+ */
+ if( i!=iSkip && i<=nToPage ){
+ DbPage *pDbPage = 0;
+ rc = sqlite3PagerGet(pBtTo->pPager, i, &pDbPage);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerWrite(pDbPage);
+ if( rc==SQLITE_OK && i>nFromPage ){
+ /* Yeah. It seems wierd to call DontWrite() right after Write(). But
+ ** that is because the names of those procedures do not exactly
+ ** represent what they do. Write() really means "put this page in the
+ ** rollback journal and mark it as dirty so that it will be written
+ ** to the database file later." DontWrite() undoes the second part of
+ ** that and prevents the page from being written to the database. The
+ ** page is still on the rollback journal, though. And that is the
+ ** whole point of this block: to put pages on the rollback journal.
+ */
+ sqlite3PagerDontWrite(pDbPage);
+ }
+ sqlite3PagerUnref(pDbPage);
+ }
+ }
+
+ /* Overwrite the data in page i of the target database */
+ if( rc==SQLITE_OK && i!=iSkip && i<=nNewPage ){
+
+ DbPage *pToPage = 0;
+ sqlite3_int64 iOff;
+
+ rc = sqlite3PagerGet(pBtTo->pPager, i, &pToPage);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerWrite(pToPage);
+ }
+
+ for(
+ iOff=(i-1)*nToPageSize;
+ rc==SQLITE_OK && iOff<i*nToPageSize;
+ iOff += nFromPageSize
+ ){
+ DbPage *pFromPage = 0;
+ Pgno iFrom = (iOff/nFromPageSize)+1;
+
+ if( iFrom==PENDING_BYTE_PAGE(pBtFrom) ){
+ continue;
+ }
+
+ rc = sqlite3PagerGet(pBtFrom->pPager, iFrom, &pFromPage);
+ if( rc==SQLITE_OK ){
+ char *zTo = sqlite3PagerGetData(pToPage);
+ char *zFrom = sqlite3PagerGetData(pFromPage);
+ int nCopy;
+
+ if( nFromPageSize>=nToPageSize ){
+ zFrom += ((i-1)*nToPageSize - ((iFrom-1)*nFromPageSize));
+ nCopy = nToPageSize;
+ }else{
+ zTo += (((iFrom-1)*nFromPageSize) - (i-1)*nToPageSize);
+ nCopy = nFromPageSize;
+ }
+
+ memcpy(zTo, zFrom, nCopy);
+ sqlite3PagerUnref(pFromPage);
+ }
+ }
+
+ if( pToPage ) sqlite3PagerUnref(pToPage);
+ }
+ }
+
+ /* If things have worked so far, the database file may need to be
+ ** truncated. The complex part is that it may need to be truncated to
+ ** a size that is not an integer multiple of nToPageSize - the current
+ ** page size used by the pager associated with B-Tree pTo.
+ **
+ ** For example, say the page-size of pTo is 2048 bytes and the original
+ ** number of pages is 5 (10 KB file). If pFrom has a page size of 1024
+ ** bytes and 9 pages, then the file needs to be truncated to 9KB.
+ */
+ if( rc==SQLITE_OK ){
+ if( nFromPageSize!=nToPageSize ){
+ sqlite3_file *pFile = sqlite3PagerFile(pBtTo->pPager);
+ i64 iSize = (i64)nFromPageSize * (i64)nFromPage;
+ i64 iNow = (i64)((nToPage>nNewPage)?nToPage:nNewPage) * (i64)nToPageSize;
+ i64 iPending = ((i64)PENDING_BYTE_PAGE(pBtTo)-1) *(i64)nToPageSize;
+
+ assert( iSize<=iNow );
+
+ /* Commit phase one syncs the journal file associated with pTo
+ ** containing the original data. It does not sync the database file
+ ** itself. After doing this it is safe to use OsTruncate() and other
+ ** file APIs on the database file directly.
+ */
+ pBtTo->db = pTo->db;
+ rc = sqlite3PagerCommitPhaseOne(pBtTo->pPager, 0, 0, 1);
+ if( iSize<iNow && rc==SQLITE_OK ){
+ rc = sqlite3OsTruncate(pFile, iSize);
+ }
+
+ /* The loop that copied data from database pFrom to pTo did not
+ ** populate the locking page of database pTo. If the page-size of
+ ** pFrom is smaller than that of pTo, this means some data will
+ ** not have been copied.
+ **
+ ** This block copies the missing data from database pFrom to pTo
+ ** using file APIs. This is safe because at this point we know that
+ ** all of the original data from pTo has been synced into the
+ ** journal file. At this point it would be safe to do anything at
+ ** all to the database file except truncate it to zero bytes.
+ */
+ if( rc==SQLITE_OK && nFromPageSize<nToPageSize && iSize>iPending){
+ i64 iOff;
+ for(
+ iOff=iPending;
+ rc==SQLITE_OK && iOff<(iPending+nToPageSize);
+ iOff += nFromPageSize
+ ){
+ DbPage *pFromPage = 0;
+ Pgno iFrom = (iOff/nFromPageSize)+1;
+
+ if( iFrom==PENDING_BYTE_PAGE(pBtFrom) || iFrom>nFromPage ){
+ continue;
+ }
+
+ rc = sqlite3PagerGet(pBtFrom->pPager, iFrom, &pFromPage);
+ if( rc==SQLITE_OK ){
+ char *zFrom = sqlite3PagerGetData(pFromPage);
+ rc = sqlite3OsWrite(pFile, zFrom, nFromPageSize, iOff);
+ sqlite3PagerUnref(pFromPage);
+ }
+ }
+ }
+
+ /* Sync the database file */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerSync(pBtTo->pPager);
+ }
+ }else{
+ rc = sqlite3PagerTruncate(pBtTo->pPager, nNewPage);
+ }
+ if( rc==SQLITE_OK ){
+ pBtTo->pageSizeFixed = 0;
+ }
+ }
+
+ if( rc ){
+ sqlite3BtreeRollback(pTo);
+ }
+
+ return rc;
+}
+SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
+ int rc;
+ sqlite3BtreeEnter(pTo);
+ sqlite3BtreeEnter(pFrom);
+ rc = btreeCopyFile(pTo, pFrom);
+ sqlite3BtreeLeave(pFrom);
+ sqlite3BtreeLeave(pTo);
+ return rc;
+}
+
+#endif /* SQLITE_OMIT_VACUUM */
+
+/*
+** Return non-zero if a transaction is active.
+*/
+SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree *p){
+ assert( p==0 || sqlite3_mutex_held(p->db->mutex) );
+ return (p && (p->inTrans==TRANS_WRITE));
+}
+
+/*
+** Return non-zero if a statement transaction is active.
+*/
+SQLITE_PRIVATE int sqlite3BtreeIsInStmt(Btree *p){
+ assert( sqlite3BtreeHoldsMutex(p) );
+ return (p->pBt && p->pBt->inStmt);
+}
+
+/*
+** Return non-zero if a read (or write) transaction is active.
+*/
+SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree *p){
+ assert( sqlite3_mutex_held(p->db->mutex) );
+ return (p && (p->inTrans!=TRANS_NONE));
+}
+
+/*
+** This function returns a pointer to a blob of memory associated with
+** a single shared-btree. The memory is used by client code for its own
+** purposes (for example, to store a high-level schema associated with
+** the shared-btree). The btree layer manages reference counting issues.
+**
+** The first time this is called on a shared-btree, nBytes bytes of memory
+** are allocated, zeroed, and returned to the caller. For each subsequent
+** call the nBytes parameter is ignored and a pointer to the same blob
+** of memory returned.
+**
+** Just before the shared-btree is closed, the function passed as the
+** xFree argument when the memory allocation was made is invoked on the
+** blob of allocated memory. This function should not call sqlite3_free()
+** on the memory, the btree layer does that.
+*/
+SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){
+ BtShared *pBt = p->pBt;
+ sqlite3BtreeEnter(p);
+ if( !pBt->pSchema ){
+ pBt->pSchema = sqlite3MallocZero(nBytes);
+ pBt->xFreeSchema = xFree;
+ }
+ sqlite3BtreeLeave(p);
+ return pBt->pSchema;
+}
+
+/*
+** Return true if another user of the same shared btree as the argument
+** handle holds an exclusive lock on the sqlite_master table.
+*/
+SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *p){
+ int rc;
+ assert( sqlite3_mutex_held(p->db->mutex) );
+ sqlite3BtreeEnter(p);
+ rc = (queryTableLock(p, MASTER_ROOT, READ_LOCK)!=SQLITE_OK);
+ sqlite3BtreeLeave(p);
+ return rc;
+}
+
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** Obtain a lock on the table whose root page is iTab. The
+** lock is a write lock if isWritelock is true or a read lock
+** if it is false.
+*/
+SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
+ int rc = SQLITE_OK;
+ if( p->sharable ){
+ u8 lockType = READ_LOCK + isWriteLock;
+ assert( READ_LOCK+1==WRITE_LOCK );
+ assert( isWriteLock==0 || isWriteLock==1 );
+ sqlite3BtreeEnter(p);
+ rc = queryTableLock(p, iTab, lockType);
+ if( rc==SQLITE_OK ){
+ rc = lockTable(p, iTab, lockType);
+ }
+ sqlite3BtreeLeave(p);
+ }
+ return rc;
+}
+#endif
+
+#ifndef SQLITE_OMIT_INCRBLOB
+/*
+** Argument pCsr must be a cursor opened for writing on an
+** INTKEY table currently pointing at a valid table entry.
+** This function modifies the data stored as part of that entry.
+** Only the data content may only be modified, it is not possible
+** to change the length of the data stored.
+*/
+SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
+ assert( cursorHoldsMutex(pCsr) );
+ assert( sqlite3_mutex_held(pCsr->pBtree->db->mutex) );
+ assert(pCsr->isIncrblobHandle);
+ if( pCsr->eState>=CURSOR_REQUIRESEEK ){
+ if( pCsr->eState==CURSOR_FAULT ){
+ return pCsr->skip;
+ }else{
+ return SQLITE_ABORT;
+ }
+ }
+
+ /* Check some preconditions:
+ ** (a) the cursor is open for writing,
+ ** (b) there is no read-lock on the table being modified and
+ ** (c) the cursor points at a valid row of an intKey table.
+ */
+ if( !pCsr->wrFlag ){
+ return SQLITE_READONLY;
+ }
+ assert( !pCsr->pBt->readOnly
+ && pCsr->pBt->inTransaction==TRANS_WRITE );
+ if( checkReadLocks(pCsr->pBtree, pCsr->pgnoRoot, pCsr) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+ if( pCsr->eState==CURSOR_INVALID || !pCsr->pPage->intKey ){
+ return SQLITE_ERROR;
+ }
+
+ return accessPayload(pCsr, offset, amt, (unsigned char *)z, 0, 1);
+}
+
+/*
+** Set a flag on this cursor to cache the locations of pages from the
+** overflow list for the current row. This is used by cursors opened
+** for incremental blob IO only.
+**
+** This function sets a flag only. The actual page location cache
+** (stored in BtCursor.aOverflow[]) is allocated and used by function
+** accessPayload() (the worker function for sqlite3BtreeData() and
+** sqlite3BtreePutData()).
+*/
+SQLITE_PRIVATE void sqlite3BtreeCacheOverflow(BtCursor *pCur){
+ assert( cursorHoldsMutex(pCur) );
+ assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ assert(!pCur->isIncrblobHandle);
+ assert(!pCur->aOverflow);
+ pCur->isIncrblobHandle = 1;
+}
+#endif
+
+/************** End of btree.c ***********************************************/
+/************** Begin file vdbefifo.c ****************************************/
+/*
+** 2005 June 16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file implements a FIFO queue of rowids used for processing
+** UPDATE and DELETE statements.
+*/
+
+/*
+** Constants FIFOSIZE_FIRST and FIFOSIZE_MAX are the initial
+** number of entries in a fifo page and the maximum number of
+** entries in a fifo page.
+*/
+#define FIFOSIZE_FIRST (((128-sizeof(FifoPage))/8)+1)
+#ifdef SQLITE_MALLOC_SOFT_LIMIT
+# define FIFOSIZE_MAX (((SQLITE_MALLOC_SOFT_LIMIT-sizeof(FifoPage))/8)+1)
+#else
+# define FIFOSIZE_MAX (((262144-sizeof(FifoPage))/8)+1)
+#endif
+
+/*
+** Allocate a new FifoPage and return a pointer to it. Return NULL if
+** we run out of memory. Leave space on the page for nEntry entries.
+*/
+static FifoPage *allocateFifoPage(int nEntry){
+ FifoPage *pPage;
+ if( nEntry>FIFOSIZE_MAX ){
+ nEntry = FIFOSIZE_MAX;
+ }
+ pPage = sqlite3_malloc( sizeof(FifoPage) + sizeof(i64)*(nEntry-1) );
+ if( pPage ){
+ pPage->nSlot = nEntry;
+ pPage->iWrite = 0;
+ pPage->iRead = 0;
+ pPage->pNext = 0;
+ }
+ return pPage;
+}
+
+/*
+** Initialize a Fifo structure.
+*/
+SQLITE_PRIVATE void sqlite3VdbeFifoInit(Fifo *pFifo){
+ memset(pFifo, 0, sizeof(*pFifo));
+}
+
+/*
+** Push a single 64-bit integer value into the Fifo. Return SQLITE_OK
+** normally. SQLITE_NOMEM is returned if we are unable to allocate
+** memory.
+*/
+SQLITE_PRIVATE int sqlite3VdbeFifoPush(Fifo *pFifo, i64 val){
+ FifoPage *pPage;
+ pPage = pFifo->pLast;
+ if( pPage==0 ){
+ pPage = pFifo->pLast = pFifo->pFirst = allocateFifoPage(FIFOSIZE_FIRST);
+ if( pPage==0 ){
+ return SQLITE_NOMEM;
+ }
+ }else if( pPage->iWrite>=pPage->nSlot ){
+ pPage->pNext = allocateFifoPage(pFifo->nEntry);
+ if( pPage->pNext==0 ){
+ return SQLITE_NOMEM;
+ }
+ pPage = pFifo->pLast = pPage->pNext;
+ }
+ pPage->aSlot[pPage->iWrite++] = val;
+ pFifo->nEntry++;
+ return SQLITE_OK;
+}
+
+/*
+** Extract a single 64-bit integer value from the Fifo. The integer
+** extracted is the one least recently inserted. If the Fifo is empty
+** return SQLITE_DONE.
+*/
+SQLITE_PRIVATE int sqlite3VdbeFifoPop(Fifo *pFifo, i64 *pVal){
+ FifoPage *pPage;
+ if( pFifo->nEntry==0 ){
+ return SQLITE_DONE;
+ }
+ assert( pFifo->nEntry>0 );
+ pPage = pFifo->pFirst;
+ assert( pPage!=0 );
+ assert( pPage->iWrite>pPage->iRead );
+ assert( pPage->iWrite<=pPage->nSlot );
+ assert( pPage->iRead<pPage->nSlot );
+ assert( pPage->iRead>=0 );
+ *pVal = pPage->aSlot[pPage->iRead++];
+ pFifo->nEntry--;
+ if( pPage->iRead>=pPage->iWrite ){
+ pFifo->pFirst = pPage->pNext;
+ sqlite3_free(pPage);
+ if( pFifo->nEntry==0 ){
+ assert( pFifo->pLast==pPage );
+ pFifo->pLast = 0;
+ }else{
+ assert( pFifo->pFirst!=0 );
+ }
+ }else{
+ assert( pFifo->nEntry>0 );
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Delete all information from a Fifo object. Free all memory held
+** by the Fifo.
+*/
+SQLITE_PRIVATE void sqlite3VdbeFifoClear(Fifo *pFifo){
+ FifoPage *pPage, *pNextPage;
+ for(pPage=pFifo->pFirst; pPage; pPage=pNextPage){
+ pNextPage = pPage->pNext;
+ sqlite3_free(pPage);
+ }
+ sqlite3VdbeFifoInit(pFifo);
+}
+
+/************** End of vdbefifo.c ********************************************/
+/************** Begin file vdbemem.c *****************************************/
+/*
+** 2004 May 26
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file contains code use to manipulate "Mem" structure. A "Mem"
+** stores a single value in the VDBE. Mem is an opaque structure visible
+** only within the VDBE. Interface routines refer to a Mem using the
+** name sqlite_value
+*/
+
+/*
+** Call sqlite3VdbeMemExpandBlob() on the supplied value (type Mem*)
+** P if required.
+*/
+#define expandBlob(P) (((P)->flags&MEM_Zero)?sqlite3VdbeMemExpandBlob(P):0)
+
+/*
+** If pMem is an object with a valid string representation, this routine
+** ensures the internal encoding for the string representation is
+** 'desiredEnc', one of SQLITE_UTF8, SQLITE_UTF16LE or SQLITE_UTF16BE.
+**
+** If pMem is not a string object, or the encoding of the string
+** representation is already stored using the requested encoding, then this
+** routine is a no-op.
+**
+** SQLITE_OK is returned if the conversion is successful (or not required).
+** SQLITE_NOMEM may be returned if a malloc() fails during conversion
+** between formats.
+*/
+SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){
+ int rc;
+ if( !(pMem->flags&MEM_Str) || pMem->enc==desiredEnc ){
+ return SQLITE_OK;
+ }
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+#ifdef SQLITE_OMIT_UTF16
+ return SQLITE_ERROR;
+#else
+
+ /* MemTranslate() may return SQLITE_OK or SQLITE_NOMEM. If NOMEM is returned,
+ ** then the encoding of the value may not have changed.
+ */
+ rc = sqlite3VdbeMemTranslate(pMem, desiredEnc);
+ assert(rc==SQLITE_OK || rc==SQLITE_NOMEM);
+ assert(rc==SQLITE_OK || pMem->enc!=desiredEnc);
+ assert(rc==SQLITE_NOMEM || pMem->enc==desiredEnc);
+ return rc;
+#endif
+}
+
+/*
+** Make sure pMem->z points to a writable allocation of at least
+** n bytes.
+**
+** If the memory cell currently contains string or blob data
+** and the third argument passed to this function is true, the
+** current content of the cell is preserved. Otherwise, it may
+** be discarded.
+**
+** This function sets the MEM_Dyn flag and clears any xDel callback.
+** It also clears MEM_Ephem and MEM_Static. If the preserve flag is
+** not set, Mem.n is zeroed.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve){
+ assert( 1 >=
+ ((pMem->zMalloc && pMem->zMalloc==pMem->z) ? 1 : 0) +
+ (((pMem->flags&MEM_Dyn)&&pMem->xDel) ? 1 : 0) +
+ ((pMem->flags&MEM_Ephem) ? 1 : 0) +
+ ((pMem->flags&MEM_Static) ? 1 : 0)
+ );
+
+ if( !pMem->zMalloc || sqlite3MallocSize(pMem->zMalloc)<n ){
+ n = (n>32?n:32);
+ if( preserve && pMem->z==pMem->zMalloc ){
+ pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n);
+ if( !pMem->z ){
+ pMem->flags = MEM_Null;
+ }
+ preserve = 0;
+ }else{
+ sqlite3_free(pMem->zMalloc);
+ pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, n);
+ }
+ }
+
+ if( preserve && pMem->z && pMem->zMalloc && pMem->z!=pMem->zMalloc ){
+ memcpy(pMem->zMalloc, pMem->z, pMem->n);
+ }
+ if( pMem->flags&MEM_Dyn && pMem->xDel ){
+ pMem->xDel((void *)(pMem->z));
+ }
+
+ pMem->z = pMem->zMalloc;
+ pMem->flags &= ~(MEM_Ephem|MEM_Static);
+ pMem->xDel = 0;
+ return (pMem->z ? SQLITE_OK : SQLITE_NOMEM);
+}
+
+/*
+** Make the given Mem object MEM_Dyn.
+**
+** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemDynamicify(Mem *pMem){
+ int f;
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ expandBlob(pMem);
+ f = pMem->flags;
+ if( (f&(MEM_Str|MEM_Blob)) && pMem->z!=pMem->zMalloc ){
+ if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){
+ return SQLITE_NOMEM;
+ }
+ pMem->z[pMem->n] = 0;
+ pMem->z[pMem->n+1] = 0;
+ pMem->flags |= MEM_Term;
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** If the given Mem* has a zero-filled tail, turn it into an ordinary
+** blob stored in dynamically allocated space.
+*/
+#ifndef SQLITE_OMIT_INCRBLOB
+SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){
+ if( pMem->flags & MEM_Zero ){
+ int nByte;
+ assert( pMem->flags&MEM_Blob );
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+
+ /* Set nByte to the number of bytes required to store the expanded blob. */
+ nByte = pMem->n + pMem->u.i;
+ if( nByte<=0 ){
+ nByte = 1;
+ }
+ if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){
+ return SQLITE_NOMEM;
+ }
+
+ memset(&pMem->z[pMem->n], 0, pMem->u.i);
+ pMem->n += pMem->u.i;
+ pMem->flags &= ~(MEM_Zero|MEM_Term);
+ }
+ return SQLITE_OK;
+}
+#endif
+
+
+/*
+** Make the given Mem object either MEM_Short or MEM_Dyn so that bytes
+** of the Mem.z[] array can be modified.
+**
+** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){
+ return sqlite3VdbeMemDynamicify(pMem);
+}
+
+/*
+** Make sure the given Mem is \u0000 terminated.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ if( (pMem->flags & MEM_Term)!=0 || (pMem->flags & MEM_Str)==0 ){
+ return SQLITE_OK; /* Nothing to do */
+ }
+ if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){
+ return SQLITE_NOMEM;
+ }
+ pMem->z[pMem->n] = 0;
+ pMem->z[pMem->n+1] = 0;
+ pMem->flags |= MEM_Term;
+ return SQLITE_OK;
+}
+
+/*
+** Add MEM_Str to the set of representations for the given Mem. Numbers
+** are converted using sqlite3_snprintf(). Converting a BLOB to a string
+** is a no-op.
+**
+** Existing representations MEM_Int and MEM_Real are *not* invalidated.
+**
+** A MEM_Null value will never be passed to this function. This function is
+** used for converting values to text for returning to the user (i.e. via
+** sqlite3_value_text()), or for ensuring that values to be used as btree
+** keys are strings. In the former case a NULL pointer is returned the
+** user and the later is an internal programming error.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, int enc){
+ int rc = SQLITE_OK;
+ int fg = pMem->flags;
+ const int nByte = 32;
+
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( !(fg&MEM_Zero) );
+ assert( !(fg&(MEM_Str|MEM_Blob)) );
+ assert( fg&(MEM_Int|MEM_Real) );
+
+ if( sqlite3VdbeMemGrow(pMem, nByte, 0) ){
+ return SQLITE_NOMEM;
+ }
+
+ /* For a Real or Integer, use sqlite3_mprintf() to produce the UTF-8
+ ** string representation of the value. Then, if the required encoding
+ ** is UTF-16le or UTF-16be do a translation.
+ **
+ ** FIX ME: It would be better if sqlite3_snprintf() could do UTF-16.
+ */
+ if( fg & MEM_Int ){
+ sqlite3_snprintf(nByte, pMem->z, "%lld", pMem->u.i);
+ }else{
+ assert( fg & MEM_Real );
+ sqlite3_snprintf(nByte, pMem->z, "%!.15g", pMem->r);
+ }
+ pMem->n = strlen(pMem->z);
+ pMem->enc = SQLITE_UTF8;
+ pMem->flags |= MEM_Str|MEM_Term;
+ sqlite3VdbeChangeEncoding(pMem, enc);
+ return rc;
+}
+
+/*
+** Memory cell pMem contains the context of an aggregate function.
+** This routine calls the finalize method for that function. The
+** result of the aggregate is stored back into pMem.
+**
+** Return SQLITE_ERROR if the finalizer reports an error. SQLITE_OK
+** otherwise.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
+ int rc = SQLITE_OK;
+ if( pFunc && pFunc->xFinalize ){
+ sqlite3_context ctx;
+ assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef );
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ ctx.s.flags = MEM_Null;
+ ctx.s.db = pMem->db;
+ ctx.s.zMalloc = 0;
+ ctx.pMem = pMem;
+ ctx.pFunc = pFunc;
+ ctx.isError = 0;
+ pFunc->xFinalize(&ctx);
+ assert( 0==(pMem->flags&MEM_Dyn) && !pMem->xDel );
+ sqlite3_free(pMem->zMalloc);
+ *pMem = ctx.s;
+ rc = (ctx.isError?SQLITE_ERROR:SQLITE_OK);
+ }
+ return rc;
+}
+
+/*
+** If the memory cell contains a string value that must be freed by
+** invoking an external callback, free it now. Calling this function
+** does not free any Mem.zMalloc buffer.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemReleaseExternal(Mem *p){
+ assert( p->db==0 || sqlite3_mutex_held(p->db->mutex) );
+ if( p->flags&MEM_Agg ){
+ sqlite3VdbeMemFinalize(p, p->u.pDef);
+ assert( (p->flags & MEM_Agg)==0 );
+ sqlite3VdbeMemRelease(p);
+ }else if( p->flags&MEM_Dyn && p->xDel ){
+ p->xDel((void *)p->z);
+ p->xDel = 0;
+ }
+}
+
+/*
+** Release any memory held by the Mem. This may leave the Mem in an
+** inconsistent state, for example with (Mem.z==0) and
+** (Mem.type==SQLITE_TEXT).
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){
+ sqlite3VdbeMemReleaseExternal(p);
+ sqlite3_free(p->zMalloc);
+ p->z = 0;
+ p->zMalloc = 0;
+ p->xDel = 0;
+}
+
+/*
+** Convert a 64-bit IEEE double into a 64-bit signed integer.
+** If the double is too large, return 0x8000000000000000.
+**
+** Most systems appear to do this simply by assigning
+** variables and without the extra range tests. But
+** there are reports that windows throws an expection
+** if the floating point value is out of range. (See ticket #2880.)
+** Because we do not completely understand the problem, we will
+** take the conservative approach and always do range tests
+** before attempting the conversion.
+*/
+static i64 doubleToInt64(double r){
+ /*
+ ** Many compilers we encounter do not define constants for the
+ ** minimum and maximum 64-bit integers, or they define them
+ ** inconsistently. And many do not understand the "LL" notation.
+ ** So we define our own static constants here using nothing
+ ** larger than a 32-bit integer constant.
+ */
+ static const i64 maxInt = LARGEST_INT64;
+ static const i64 minInt = SMALLEST_INT64;
+
+ if( r<(double)minInt ){
+ return minInt;
+ }else if( r>(double)maxInt ){
+ return minInt;
+ }else{
+ return (i64)r;
+ }
+}
+
+/*
+** Return some kind of integer value which is the best we can do
+** at representing the value that *pMem describes as an integer.
+** If pMem is an integer, then the value is exact. If pMem is
+** a floating-point then the value returned is the integer part.
+** If pMem is a string or blob, then we make an attempt to convert
+** it into a integer and return that. If pMem is NULL, return 0.
+**
+** If pMem is a string, its encoding might be changed.
+*/
+SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){
+ int flags;
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ flags = pMem->flags;
+ if( flags & MEM_Int ){
+ return pMem->u.i;
+ }else if( flags & MEM_Real ){
+ return doubleToInt64(pMem->r);
+ }else if( flags & (MEM_Str|MEM_Blob) ){
+ i64 value;
+ pMem->flags |= MEM_Str;
+ if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8)
+ || sqlite3VdbeMemNulTerminate(pMem) ){
+ return 0;
+ }
+ assert( pMem->z );
+ sqlite3Atoi64(pMem->z, &value);
+ return value;
+ }else{
+ return 0;
+ }
+}
+
+/*
+** Return the best representation of pMem that we can get into a
+** double. If pMem is already a double or an integer, return its
+** value. If it is a string or blob, try to convert it to a double.
+** If it is a NULL, return 0.0.
+*/
+SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ if( pMem->flags & MEM_Real ){
+ return pMem->r;
+ }else if( pMem->flags & MEM_Int ){
+ return (double)pMem->u.i;
+ }else if( pMem->flags & (MEM_Str|MEM_Blob) ){
+ double val = 0.0;
+ pMem->flags |= MEM_Str;
+ if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8)
+ || sqlite3VdbeMemNulTerminate(pMem) ){
+ return 0.0;
+ }
+ assert( pMem->z );
+ sqlite3AtoF(pMem->z, &val);
+ return val;
+ }else{
+ return 0.0;
+ }
+}
+
+/*
+** The MEM structure is already a MEM_Real. Try to also make it a
+** MEM_Int if we can.
+*/
+SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){
+ assert( pMem->flags & MEM_Real );
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+
+ pMem->u.i = doubleToInt64(pMem->r);
+ if( pMem->r==(double)pMem->u.i ){
+ pMem->flags |= MEM_Int;
+ }
+}
+
+static void setTypeFlag(Mem *pMem, int f){
+ MemSetTypeFlag(pMem, f);
+}
+
+/*
+** Convert pMem to type integer. Invalidate any prior representations.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ pMem->u.i = sqlite3VdbeIntValue(pMem);
+ setTypeFlag(pMem, MEM_Int);
+ return SQLITE_OK;
+}
+
+/*
+** Convert pMem so that it is of type MEM_Real.
+** Invalidate any prior representations.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem *pMem){
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ pMem->r = sqlite3VdbeRealValue(pMem);
+ setTypeFlag(pMem, MEM_Real);
+ return SQLITE_OK;
+}
+
+/*
+** Convert pMem so that it has types MEM_Real or MEM_Int or both.
+** Invalidate any prior representations.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){
+ double r1, r2;
+ i64 i;
+ assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))==0 );
+ assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 );
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ r1 = sqlite3VdbeRealValue(pMem);
+ i = doubleToInt64(r1);
+ r2 = (double)i;
+ if( r1==r2 ){
+ sqlite3VdbeMemIntegerify(pMem);
+ }else{
+ pMem->r = r1;
+ setTypeFlag(pMem, MEM_Real);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Delete any previous value and set the value stored in *pMem to NULL.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem *pMem){
+ setTypeFlag(pMem, MEM_Null);
+ pMem->type = SQLITE_NULL;
+}
+
+/*
+** Delete any previous value and set the value to be a BLOB of length
+** n containing all zeros.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){
+ sqlite3VdbeMemRelease(pMem);
+ setTypeFlag(pMem, MEM_Blob);
+ pMem->flags = MEM_Blob|MEM_Zero;
+ pMem->type = SQLITE_BLOB;
+ pMem->n = 0;
+ if( n<0 ) n = 0;
+ pMem->u.i = n;
+ pMem->enc = SQLITE_UTF8;
+}
+
+/*
+** Delete any previous value and set the value stored in *pMem to val,
+** manifest type INTEGER.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){
+ sqlite3VdbeMemRelease(pMem);
+ pMem->u.i = val;
+ pMem->flags = MEM_Int;
+ pMem->type = SQLITE_INTEGER;
+}
+
+/*
+** Delete any previous value and set the value stored in *pMem to val,
+** manifest type REAL.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem *pMem, double val){
+ if( sqlite3IsNaN(val) ){
+ sqlite3VdbeMemSetNull(pMem);
+ }else{
+ sqlite3VdbeMemRelease(pMem);
+ pMem->r = val;
+ pMem->flags = MEM_Real;
+ pMem->type = SQLITE_FLOAT;
+ }
+}
+
+/*
+** Return true if the Mem object contains a TEXT or BLOB that is
+** too large - whose size exceeds SQLITE_MAX_LENGTH.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem *p){
+ assert( p->db!=0 );
+ if( p->flags & (MEM_Str|MEM_Blob) ){
+ int n = p->n;
+ if( p->flags & MEM_Zero ){
+ n += p->u.i;
+ }
+ return n>p->db->aLimit[SQLITE_LIMIT_LENGTH];
+ }
+ return 0;
+}
+
+/*
+** Size of struct Mem not including the Mem.zMalloc member.
+*/
+#define MEMCELLSIZE (size_t)(&(((Mem *)0)->zMalloc))
+
+/*
+** Make an shallow copy of pFrom into pTo. Prior contents of
+** pTo are freed. The pFrom->z field is not duplicated. If
+** pFrom->z is used, then pTo->z points to the same thing as pFrom->z
+** and flags gets srcType (either MEM_Ephem or MEM_Static).
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int srcType){
+ sqlite3VdbeMemReleaseExternal(pTo);
+ memcpy(pTo, pFrom, MEMCELLSIZE);
+ pTo->xDel = 0;
+ if( (pFrom->flags&MEM_Dyn)!=0 || pFrom->z==pFrom->zMalloc ){
+ pTo->flags &= ~(MEM_Dyn|MEM_Static|MEM_Ephem);
+ assert( srcType==MEM_Ephem || srcType==MEM_Static );
+ pTo->flags |= srcType;
+ }
+}
+
+/*
+** Make a full copy of pFrom into pTo. Prior contents of pTo are
+** freed before the copy is made.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){
+ int rc = SQLITE_OK;
+
+ sqlite3VdbeMemReleaseExternal(pTo);
+ memcpy(pTo, pFrom, MEMCELLSIZE);
+ pTo->flags &= ~MEM_Dyn;
+
+ if( pTo->flags&(MEM_Str|MEM_Blob) ){
+ if( 0==(pFrom->flags&MEM_Static) ){
+ pTo->flags |= MEM_Ephem;
+ rc = sqlite3VdbeMemMakeWriteable(pTo);
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Transfer the contents of pFrom to pTo. Any existing value in pTo is
+** freed. If pFrom contains ephemeral data, a copy is made.
+**
+** pFrom contains an SQL NULL when this routine returns.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){
+ assert( pFrom->db==0 || sqlite3_mutex_held(pFrom->db->mutex) );
+ assert( pTo->db==0 || sqlite3_mutex_held(pTo->db->mutex) );
+ assert( pFrom->db==0 || pTo->db==0 || pFrom->db==pTo->db );
+
+ sqlite3VdbeMemRelease(pTo);
+ memcpy(pTo, pFrom, sizeof(Mem));
+ pFrom->flags = MEM_Null;
+ pFrom->xDel = 0;
+ pFrom->zMalloc = 0;
+}
+
+/*
+** Change the value of a Mem to be a string or a BLOB.
+**
+** The memory management strategy depends on the value of the xDel
+** parameter. If the value passed is SQLITE_TRANSIENT, then the
+** string is copied into a (possibly existing) buffer managed by the
+** Mem structure. Otherwise, any existing buffer is freed and the
+** pointer copied.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
+ Mem *pMem, /* Memory cell to set to string value */
+ const char *z, /* String pointer */
+ int n, /* Bytes in string, or negative */
+ u8 enc, /* Encoding of z. 0 for BLOBs */
+ void (*xDel)(void*) /* Destructor function */
+){
+ int nByte = n; /* New value for pMem->n */
+ int flags = 0; /* New value for pMem->flags */
+
+ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+
+ /* If z is a NULL pointer, set pMem to contain an SQL NULL. */
+ if( !z ){
+ sqlite3VdbeMemSetNull(pMem);
+ return SQLITE_OK;
+ }
+
+ flags = (enc==0?MEM_Blob:MEM_Str);
+ if( nByte<0 ){
+ assert( enc!=0 );
+ if( enc==SQLITE_UTF8 ){
+ for(nByte=0; z[nByte]; nByte++){}
+ }else{
+ for(nByte=0; z[nByte] | z[nByte+1]; nByte+=2){}
+ }
+ flags |= MEM_Term;
+ }
+
+ /* The following block sets the new values of Mem.z and Mem.xDel. It
+ ** also sets a flag in local variable "flags" to indicate the memory
+ ** management (one of MEM_Dyn or MEM_Static).
+ */
+ if( xDel==SQLITE_TRANSIENT ){
+ int nAlloc = nByte;
+ if( flags&MEM_Term ){
+ nAlloc += (enc==SQLITE_UTF8?1:2);
+ }
+ if( sqlite3VdbeMemGrow(pMem, nAlloc, 0) ){
+ return SQLITE_NOMEM;
+ }
+ memcpy(pMem->z, z, nAlloc);
+ }else{
+ sqlite3VdbeMemRelease(pMem);
+ pMem->z = (char *)z;
+ pMem->xDel = xDel;
+ flags |= ((xDel==SQLITE_STATIC)?MEM_Static:MEM_Dyn);
+ }
+
+ pMem->n = nByte;
+ pMem->flags = flags;
+ pMem->enc = (enc==0 ? SQLITE_UTF8 : enc);
+ pMem->type = (enc==0 ? SQLITE_BLOB : SQLITE_TEXT);
+
+#ifndef SQLITE_OMIT_UTF16
+ if( pMem->enc!=SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){
+ return SQLITE_NOMEM;
+ }
+#endif
+
+ return SQLITE_OK;
+}
+
+/*
+** Compare the values contained by the two memory cells, returning
+** negative, zero or positive if pMem1 is less than, equal to, or greater
+** than pMem2. Sorting order is NULL's first, followed by numbers (integers
+** and reals) sorted numerically, followed by text ordered by the collating
+** sequence pColl and finally blob's ordered by memcmp().
+**
+** Two NULL values are considered equal by this function.
+*/
+SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){
+ int rc;
+ int f1, f2;
+ int combined_flags;
+
+ /* Interchange pMem1 and pMem2 if the collating sequence specifies
+ ** DESC order.
+ */
+ f1 = pMem1->flags;
+ f2 = pMem2->flags;
+ combined_flags = f1|f2;
+
+ /* If one value is NULL, it is less than the other. If both values
+ ** are NULL, return 0.
+ */
+ if( combined_flags&MEM_Null ){
+ return (f2&MEM_Null) - (f1&MEM_Null);
+ }
+
+ /* If one value is a number and the other is not, the number is less.
+ ** If both are numbers, compare as reals if one is a real, or as integers
+ ** if both values are integers.
+ */
+ if( combined_flags&(MEM_Int|MEM_Real) ){
+ if( !(f1&(MEM_Int|MEM_Real)) ){
+ return 1;
+ }
+ if( !(f2&(MEM_Int|MEM_Real)) ){
+ return -1;
+ }
+ if( (f1 & f2 & MEM_Int)==0 ){
+ double r1, r2;
+ if( (f1&MEM_Real)==0 ){
+ r1 = pMem1->u.i;
+ }else{
+ r1 = pMem1->r;
+ }
+ if( (f2&MEM_Real)==0 ){
+ r2 = pMem2->u.i;
+ }else{
+ r2 = pMem2->r;
+ }
+ if( r1<r2 ) return -1;
+ if( r1>r2 ) return 1;
+ return 0;
+ }else{
+ assert( f1&MEM_Int );
+ assert( f2&MEM_Int );
+ if( pMem1->u.i < pMem2->u.i ) return -1;
+ if( pMem1->u.i > pMem2->u.i ) return 1;
+ return 0;
+ }
+ }
+
+ /* If one value is a string and the other is a blob, the string is less.
+ ** If both are strings, compare using the collating functions.
+ */
+ if( combined_flags&MEM_Str ){
+ if( (f1 & MEM_Str)==0 ){
+ return 1;
+ }
+ if( (f2 & MEM_Str)==0 ){
+ return -1;
+ }
+
+ assert( pMem1->enc==pMem2->enc );
+ assert( pMem1->enc==SQLITE_UTF8 ||
+ pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE );
+
+ /* The collation sequence must be defined at this point, even if
+ ** the user deletes the collation sequence after the vdbe program is
+ ** compiled (this was not always the case).
+ */
+ assert( !pColl || pColl->xCmp );
+
+ if( pColl ){
+ if( pMem1->enc==pColl->enc ){
+ /* The strings are already in the correct encoding. Call the
+ ** comparison function directly */
+ return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z);
+ }else{
+ u8 origEnc = pMem1->enc;
+ const void *v1, *v2;
+ int n1, n2;
+ /* Convert the strings into the encoding that the comparison
+ ** function expects */
+ v1 = sqlite3ValueText((sqlite3_value*)pMem1, pColl->enc);
+ n1 = v1==0 ? 0 : pMem1->n;
+ assert( n1==sqlite3ValueBytes((sqlite3_value*)pMem1, pColl->enc) );
+ v2 = sqlite3ValueText((sqlite3_value*)pMem2, pColl->enc);
+ n2 = v2==0 ? 0 : pMem2->n;
+ assert( n2==sqlite3ValueBytes((sqlite3_value*)pMem2, pColl->enc) );
+ /* Do the comparison */
+ rc = pColl->xCmp(pColl->pUser, n1, v1, n2, v2);
+ /* Convert the strings back into the database encoding */
+ sqlite3ValueText((sqlite3_value*)pMem1, origEnc);
+ sqlite3ValueText((sqlite3_value*)pMem2, origEnc);
+ return rc;
+ }
+ }
+ /* If a NULL pointer was passed as the collate function, fall through
+ ** to the blob case and use memcmp(). */
+ }
+
+ /* Both values must be blobs. Compare using memcmp(). */
+ rc = memcmp(pMem1->z, pMem2->z, (pMem1->n>pMem2->n)?pMem2->n:pMem1->n);
+ if( rc==0 ){
+ rc = pMem1->n - pMem2->n;
+ }
+ return rc;
+}
+
+/*
+** Move data out of a btree key or data field and into a Mem structure.
+** The data or key is taken from the entry that pCur is currently pointing
+** to. offset and amt determine what portion of the data or key to retrieve.
+** key is true to get the key or false to get data. The result is written
+** into the pMem element.
+**
+** The pMem structure is assumed to be uninitialized. Any prior content
+** is overwritten without being freed.
+**
+** If this routine fails for any reason (malloc returns NULL or unable
+** to read from the disk) then the pMem is left in an inconsistent state.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(
+ BtCursor *pCur, /* Cursor pointing at record to retrieve. */
+ int offset, /* Offset from the start of data to return bytes from. */
+ int amt, /* Number of bytes to return. */
+ int key, /* If true, retrieve from the btree key, not data. */
+ Mem *pMem /* OUT: Return data in this Mem structure. */
+){
+ char *zData; /* Data from the btree layer */
+ int available = 0; /* Number of bytes available on the local btree page */
+ sqlite3 *db; /* Database connection */
+ int rc = SQLITE_OK;
+
+ db = sqlite3BtreeCursorDb(pCur);
+ assert( sqlite3_mutex_held(db->mutex) );
+ if( key ){
+ zData = (char *)sqlite3BtreeKeyFetch(pCur, &available);
+ }else{
+ zData = (char *)sqlite3BtreeDataFetch(pCur, &available);
+ }
+ assert( zData!=0 );
+
+ if( offset+amt<=available && ((pMem->flags&MEM_Dyn)==0 || pMem->xDel) ){
+ sqlite3VdbeMemRelease(pMem);
+ pMem->z = &zData[offset];
+ pMem->flags = MEM_Blob|MEM_Ephem;
+ }else if( SQLITE_OK==(rc = sqlite3VdbeMemGrow(pMem, amt+2, 0)) ){
+ pMem->flags = MEM_Blob|MEM_Dyn|MEM_Term;
+ pMem->enc = 0;
+ pMem->type = SQLITE_BLOB;
+ if( key ){
+ rc = sqlite3BtreeKey(pCur, offset, amt, pMem->z);
+ }else{
+ rc = sqlite3BtreeData(pCur, offset, amt, pMem->z);
+ }
+ pMem->z[amt] = 0;
+ pMem->z[amt+1] = 0;
+ if( rc!=SQLITE_OK ){
+ sqlite3VdbeMemRelease(pMem);
+ }
+ }
+ pMem->n = amt;
+
+ return rc;
+}
+
+#if 0
+/*
+** Perform various checks on the memory cell pMem. An assert() will
+** fail if pMem is internally inconsistent.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemSanity(Mem *pMem){
+ int flags = pMem->flags;
+ assert( flags!=0 ); /* Must define some type */
+ if( flags & (MEM_Str|MEM_Blob) ){
+ int x = flags & (MEM_Static|MEM_Dyn|MEM_Ephem|MEM_Short);
+ assert( x!=0 ); /* Strings must define a string subtype */
+ assert( (x & (x-1))==0 ); /* Only one string subtype can be defined */
+ assert( pMem->z!=0 ); /* Strings must have a value */
+ /* Mem.z points to Mem.zShort iff the subtype is MEM_Short */
+ assert( (x & MEM_Short)==0 || pMem->z==pMem->zShort );
+ assert( (x & MEM_Short)!=0 || pMem->z!=pMem->zShort );
+ /* No destructor unless there is MEM_Dyn */
+ assert( pMem->xDel==0 || (pMem->flags & MEM_Dyn)!=0 );
+
+ if( (flags & MEM_Str) ){
+ assert( pMem->enc==SQLITE_UTF8 ||
+ pMem->enc==SQLITE_UTF16BE ||
+ pMem->enc==SQLITE_UTF16LE
+ );
+ /* If the string is UTF-8 encoded and nul terminated, then pMem->n
+ ** must be the length of the string. (Later:) If the database file
+ ** has been corrupted, '\000' characters might have been inserted
+ ** into the middle of the string. In that case, the strlen() might
+ ** be less.
+ */
+ if( pMem->enc==SQLITE_UTF8 && (flags & MEM_Term) ){
+ assert( strlen(pMem->z)<=pMem->n );
+ assert( pMem->z[pMem->n]==0 );
+ }
+ }
+ }else{
+ /* Cannot define a string subtype for non-string objects */
+ assert( (pMem->flags & (MEM_Static|MEM_Dyn|MEM_Ephem|MEM_Short))==0 );
+ assert( pMem->xDel==0 );
+ }
+ /* MEM_Null excludes all other types */
+ assert( (pMem->flags&(MEM_Str|MEM_Int|MEM_Real|MEM_Blob))==0
+ || (pMem->flags&MEM_Null)==0 );
+ /* If the MEM is both real and integer, the values are equal */
+ assert( (pMem->flags & (MEM_Int|MEM_Real))!=(MEM_Int|MEM_Real)
+ || pMem->r==pMem->u.i );
+}
+#endif
+
+/* This function is only available internally, it is not part of the
+** external API. It works in a similar way to sqlite3_value_text(),
+** except the data returned is in the encoding specified by the second
+** parameter, which must be one of SQLITE_UTF16BE, SQLITE_UTF16LE or
+** SQLITE_UTF8.
+**
+** (2006-02-16:) The enc value can be or-ed with SQLITE_UTF16_ALIGNED.
+** If that is the case, then the result must be aligned on an even byte
+** boundary.
+*/
+SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
+ if( !pVal ) return 0;
+
+ assert( pVal->db==0 || sqlite3_mutex_held(pVal->db->mutex) );
+ assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) );
+
+ if( pVal->flags&MEM_Null ){
+ return 0;
+ }
+ assert( (MEM_Blob>>3) == MEM_Str );
+ pVal->flags |= (pVal->flags & MEM_Blob)>>3;
+ expandBlob(pVal);
+ if( pVal->flags&MEM_Str ){
+ sqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED);
+ if( (enc & SQLITE_UTF16_ALIGNED)!=0 && 1==(1&(int)pVal->z) ){
+ assert( (pVal->flags & (MEM_Ephem|MEM_Static))!=0 );
+ if( sqlite3VdbeMemMakeWriteable(pVal)!=SQLITE_OK ){
+ return 0;
+ }
+ }
+ sqlite3VdbeMemNulTerminate(pVal);
+ }else{
+ assert( (pVal->flags&MEM_Blob)==0 );
+ sqlite3VdbeMemStringify(pVal, enc);
+ assert( 0==(1&(int)pVal->z) );
+ }
+ assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0
+ || pVal->db->mallocFailed );
+ if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){
+ return pVal->z;
+ }else{
+ return 0;
+ }
+}
+
+/*
+** Create a new sqlite3_value object.
+*/
+SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *db){
+ Mem *p = sqlite3DbMallocZero(db, sizeof(*p));
+ if( p ){
+ p->flags = MEM_Null;
+ p->type = SQLITE_NULL;
+ p->db = db;
+ }
+ return p;
+}
+
+/*
+** Create a new sqlite3_value object, containing the value of pExpr.
+**
+** This only works for very simple expressions that consist of one constant
+** token (i.e. "5", "5.1", "'a string'"). If the expression can
+** be converted directly into a value, then the value is allocated and
+** a pointer written to *ppVal. The caller is responsible for deallocating
+** the value by passing it to sqlite3ValueFree() later on. If the expression
+** cannot be converted to a value, then *ppVal is set to NULL.
+*/
+SQLITE_PRIVATE int sqlite3ValueFromExpr(
+ sqlite3 *db, /* The database connection */
+ Expr *pExpr, /* The expression to evaluate */
+ u8 enc, /* Encoding to use */
+ u8 affinity, /* Affinity to use */
+ sqlite3_value **ppVal /* Write the new value here */
+){
+ int op;
+ char *zVal = 0;
+ sqlite3_value *pVal = 0;
+
+ if( !pExpr ){
+ *ppVal = 0;
+ return SQLITE_OK;
+ }
+ op = pExpr->op;
+
+ if( op==TK_STRING || op==TK_FLOAT || op==TK_INTEGER ){
+ zVal = sqlite3StrNDup((char*)pExpr->token.z, pExpr->token.n);
+ pVal = sqlite3ValueNew(db);
+ if( !zVal || !pVal ) goto no_mem;
+ sqlite3Dequote(zVal);
+ sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, sqlite3_free);
+ if( (op==TK_INTEGER || op==TK_FLOAT ) && affinity==SQLITE_AFF_NONE ){
+ sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, enc);
+ }else{
+ sqlite3ValueApplyAffinity(pVal, affinity, enc);
+ }
+ }else if( op==TK_UMINUS ) {
+ if( SQLITE_OK==sqlite3ValueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal) ){
+ pVal->u.i = -1 * pVal->u.i;
+ pVal->r = -1.0 * pVal->r;
+ }
+ }
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+ else if( op==TK_BLOB ){
+ int nVal;
+ assert( pExpr->token.n>=3 );
+ assert( pExpr->token.z[0]=='x' || pExpr->token.z[0]=='X' );
+ assert( pExpr->token.z[1]=='\'' );
+ assert( pExpr->token.z[pExpr->token.n-1]=='\'' );
+ pVal = sqlite3ValueNew(db);
+ nVal = pExpr->token.n - 3;
+ zVal = (char*)pExpr->token.z + 2;
+ sqlite3VdbeMemSetStr(pVal, sqlite3HexToBlob(db, zVal, nVal), nVal/2,
+ 0, sqlite3_free);
+ }
+#endif
+
+ *ppVal = pVal;
+ return SQLITE_OK;
+
+no_mem:
+ db->mallocFailed = 1;
+ sqlite3_free(zVal);
+ sqlite3ValueFree(pVal);
+ *ppVal = 0;
+ return SQLITE_NOMEM;
+}
+
+/*
+** Change the string value of an sqlite3_value object
+*/
+SQLITE_PRIVATE void sqlite3ValueSetStr(
+ sqlite3_value *v, /* Value to be set */
+ int n, /* Length of string z */
+ const void *z, /* Text of the new string */
+ u8 enc, /* Encoding to use */
+ void (*xDel)(void*) /* Destructor for the string */
+){
+ if( v ) sqlite3VdbeMemSetStr((Mem *)v, z, n, enc, xDel);
+}
+
+/*
+** Free an sqlite3_value object
+*/
+SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value *v){
+ if( !v ) return;
+ sqlite3VdbeMemRelease((Mem *)v);
+ sqlite3_free(v);
+}
+
+/*
+** Return the number of bytes in the sqlite3_value object assuming
+** that it uses the encoding "enc"
+*/
+SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){
+ Mem *p = (Mem*)pVal;
+ if( (p->flags & MEM_Blob)!=0 || sqlite3ValueText(pVal, enc) ){
+ if( p->flags & MEM_Zero ){
+ return p->n+p->u.i;
+ }else{
+ return p->n;
+ }
+ }
+ return 0;
+}
+
+/************** End of vdbemem.c *********************************************/
+/************** Begin file vdbeaux.c *****************************************/
+/*
+** 2003 September 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used for creating, destroying, and populating
+** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) Prior
+** to version 2.8.7, all this code was combined into the vdbe.c source file.
+** But that file was getting too big so this subroutines were split out.
+**
+** $Id: vdbeaux.c,v 1.383 2008/05/13 13:27:34 drh Exp $
+*/
+
+
+
+/*
+** When debugging the code generator in a symbolic debugger, one can
+** set the sqlite3VdbeAddopTrace to 1 and all opcodes will be printed
+** as they are added to the instruction stream.
+*/
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int sqlite3VdbeAddopTrace = 0;
+#endif
+
+
+/*
+** Create a new virtual database engine.
+*/
+SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(sqlite3 *db){
+ Vdbe *p;
+ p = sqlite3DbMallocZero(db, sizeof(Vdbe) );
+ if( p==0 ) return 0;
+ p->db = db;
+ if( db->pVdbe ){
+ db->pVdbe->pPrev = p;
+ }
+ p->pNext = db->pVdbe;
+ p->pPrev = 0;
+ db->pVdbe = p;
+ p->magic = VDBE_MAGIC_INIT;
+ return p;
+}
+
+/*
+** Remember the SQL string for a prepared statement.
+*/
+SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n){
+ if( p==0 ) return;
+ assert( p->zSql==0 );
+ p->zSql = sqlite3DbStrNDup(p->db, z, n);
+}
+
+/*
+** Return the SQL associated with a prepared statement
+*/
+SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt){
+ return ((Vdbe *)pStmt)->zSql;
+}
+
+/*
+** Swap all content between two VDBE structures.
+*/
+SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
+ Vdbe tmp, *pTmp;
+ char *zTmp;
+ int nTmp;
+ tmp = *pA;
+ *pA = *pB;
+ *pB = tmp;
+ pTmp = pA->pNext;
+ pA->pNext = pB->pNext;
+ pB->pNext = pTmp;
+ pTmp = pA->pPrev;
+ pA->pPrev = pB->pPrev;
+ pB->pPrev = pTmp;
+ zTmp = pA->zSql;
+ pA->zSql = pB->zSql;
+ pB->zSql = zTmp;
+ nTmp = pA->nSql;
+ pA->nSql = pB->nSql;
+ pB->nSql = nTmp;
+}
+
+#ifdef SQLITE_DEBUG
+/*
+** Turn tracing on or off
+*/
+SQLITE_PRIVATE void sqlite3VdbeTrace(Vdbe *p, FILE *trace){
+ p->trace = trace;
+}
+#endif
+
+/*
+** Resize the Vdbe.aOp array so that it contains at least N
+** elements.
+**
+** If an out-of-memory error occurs while resizing the array,
+** Vdbe.aOp and Vdbe.nOpAlloc remain unchanged (this is so that
+** any opcodes already allocated can be correctly deallocated
+** along with the rest of the Vdbe).
+*/
+static void resizeOpArray(Vdbe *p, int N){
+ VdbeOp *pNew;
+ pNew = sqlite3DbRealloc(p->db, p->aOp, N*sizeof(Op));
+ if( pNew ){
+ p->nOpAlloc = N;
+ p->aOp = pNew;
+ }
+}
+
+/*
+** Add a new instruction to the list of instructions current in the
+** VDBE. Return the address of the new instruction.
+**
+** Parameters:
+**
+** p Pointer to the VDBE
+**
+** op The opcode for this instruction
+**
+** p1, p2, p3 Operands
+**
+** Use the sqlite3VdbeResolveLabel() function to fix an address and
+** the sqlite3VdbeChangeP4() function to change the value of the P4
+** operand.
+*/
+SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
+ int i;
+ VdbeOp *pOp;
+
+ i = p->nOp;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p->nOpAlloc<=i ){
+ resizeOpArray(p, p->nOpAlloc ? p->nOpAlloc*2 : 1024/sizeof(Op));
+ if( p->db->mallocFailed ){
+ return 0;
+ }
+ }
+ p->nOp++;
+ pOp = &p->aOp[i];
+ pOp->opcode = op;
+ pOp->p5 = 0;
+ pOp->p1 = p1;
+ pOp->p2 = p2;
+ pOp->p3 = p3;
+ pOp->p4.p = 0;
+ pOp->p4type = P4_NOTUSED;
+ p->expired = 0;
+#ifdef SQLITE_DEBUG
+ pOp->zComment = 0;
+ if( sqlite3VdbeAddopTrace ) sqlite3VdbePrintOp(0, i, &p->aOp[i]);
+#endif
+#ifdef VDBE_PROFILE
+ pOp->cycles = 0;
+ pOp->cnt = 0;
+#endif
+ return i;
+}
+SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){
+ return sqlite3VdbeAddOp3(p, op, 0, 0, 0);
+}
+SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){
+ return sqlite3VdbeAddOp3(p, op, p1, 0, 0);
+}
+SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){
+ return sqlite3VdbeAddOp3(p, op, p1, p2, 0);
+}
+
+
+/*
+** Add an opcode that includes the p4 value as a pointer.
+*/
+SQLITE_PRIVATE int sqlite3VdbeAddOp4(
+ Vdbe *p, /* Add the opcode to this VM */
+ int op, /* The new opcode */
+ int p1, /* The P1 operand */
+ int p2, /* The P2 operand */
+ int p3, /* The P3 operand */
+ const char *zP4, /* The P4 operand */
+ int p4type /* P4 operand type */
+){
+ int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3);
+ sqlite3VdbeChangeP4(p, addr, zP4, p4type);
+ return addr;
+}
+
+/*
+** Create a new symbolic label for an instruction that has yet to be
+** coded. The symbolic label is really just a negative number. The
+** label can be used as the P2 value of an operation. Later, when
+** the label is resolved to a specific address, the VDBE will scan
+** through its operation list and change all values of P2 which match
+** the label into the resolved address.
+**
+** The VDBE knows that a P2 value is a label because labels are
+** always negative and P2 values are suppose to be non-negative.
+** Hence, a negative P2 value is a label that has yet to be resolved.
+**
+** Zero is returned if a malloc() fails.
+*/
+SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe *p){
+ int i;
+ i = p->nLabel++;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( i>=p->nLabelAlloc ){
+ p->nLabelAlloc = p->nLabelAlloc*2 + 10;
+ p->aLabel = sqlite3DbReallocOrFree(p->db, p->aLabel,
+ p->nLabelAlloc*sizeof(p->aLabel[0]));
+ }
+ if( p->aLabel ){
+ p->aLabel[i] = -1;
+ }
+ return -1-i;
+}
+
+/*
+** Resolve label "x" to be the address of the next instruction to
+** be inserted. The parameter "x" must have been obtained from
+** a prior call to sqlite3VdbeMakeLabel().
+*/
+SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *p, int x){
+ int j = -1-x;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ assert( j>=0 && j<p->nLabel );
+ if( p->aLabel ){
+ p->aLabel[j] = p->nOp;
+ }
+}
+
+/*
+** Loop through the program looking for P2 values that are negative
+** on jump instructions. Each such value is a label. Resolve the
+** label by setting the P2 value to its correct non-zero value.
+**
+** This routine is called once after all opcodes have been inserted.
+**
+** Variable *pMaxFuncArgs is set to the maximum value of any P2 argument
+** to an OP_Function, OP_AggStep or OP_VFilter opcode. This is used by
+** sqlite3VdbeMakeReady() to size the Vdbe.apArg[] array.
+**
+** This routine also does the following optimization: It scans for
+** instructions that might cause a statement rollback. Such instructions
+** are:
+**
+** * OP_Halt with P1=SQLITE_CONSTRAINT and P2=OE_Abort.
+** * OP_Destroy
+** * OP_VUpdate
+** * OP_VRename
+**
+** If no such instruction is found, then every Statement instruction
+** is changed to a Noop. In this way, we avoid creating the statement
+** journal file unnecessarily.
+*/
+static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
+ int i;
+ int nMaxArgs = 0;
+ Op *pOp;
+ int *aLabel = p->aLabel;
+ int doesStatementRollback = 0;
+ int hasStatementBegin = 0;
+ for(pOp=p->aOp, i=p->nOp-1; i>=0; i--, pOp++){
+ u8 opcode = pOp->opcode;
+
+ if( opcode==OP_Function ){
+ if( pOp->p5>nMaxArgs ) nMaxArgs = pOp->p5;
+ }else if( opcode==OP_AggStep
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ || opcode==OP_VUpdate
+#endif
+ ){
+ if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2;
+ }
+ if( opcode==OP_Halt ){
+ if( pOp->p1==SQLITE_CONSTRAINT && pOp->p2==OE_Abort ){
+ doesStatementRollback = 1;
+ }
+ }else if( opcode==OP_Statement ){
+ hasStatementBegin = 1;
+ }else if( opcode==OP_Destroy ){
+ doesStatementRollback = 1;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ }else if( opcode==OP_VUpdate || opcode==OP_VRename ){
+ doesStatementRollback = 1;
+ }else if( opcode==OP_VFilter ){
+ int n;
+ assert( p->nOp - i >= 3 );
+ assert( pOp[-1].opcode==OP_Integer );
+ n = pOp[-1].p1;
+ if( n>nMaxArgs ) nMaxArgs = n;
+#endif
+ }
+
+ if( sqlite3VdbeOpcodeHasProperty(opcode, OPFLG_JUMP) && pOp->p2<0 ){
+ assert( -1-pOp->p2<p->nLabel );
+ pOp->p2 = aLabel[-1-pOp->p2];
+ }
+ }
+ sqlite3_free(p->aLabel);
+ p->aLabel = 0;
+
+ *pMaxFuncArgs = nMaxArgs;
+
+ /* If we never rollback a statement transaction, then statement
+ ** transactions are not needed. So change every OP_Statement
+ ** opcode into an OP_Noop. This avoid a call to sqlite3OsOpenExclusive()
+ ** which can be expensive on some platforms.
+ */
+ if( hasStatementBegin && !doesStatementRollback ){
+ for(pOp=p->aOp, i=p->nOp-1; i>=0; i--, pOp++){
+ if( pOp->opcode==OP_Statement ){
+ pOp->opcode = OP_Noop;
+ }
+ }
+ }
+}
+
+/*
+** Return the address of the next instruction to be inserted.
+*/
+SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){
+ assert( p->magic==VDBE_MAGIC_INIT );
+ return p->nOp;
+}
+
+/*
+** Add a whole list of operations to the operation stack. Return the
+** address of the first operation added.
+*/
+SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp){
+ int addr;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p->nOp + nOp > p->nOpAlloc ){
+ resizeOpArray(p, p->nOpAlloc ? p->nOpAlloc*2 : 1024/sizeof(Op));
+ assert( p->nOp+nOp<=p->nOpAlloc || p->db->mallocFailed );
+ }
+ if( p->db->mallocFailed ){
+ return 0;
+ }
+ addr = p->nOp;
+ if( nOp>0 ){
+ int i;
+ VdbeOpList const *pIn = aOp;
+ for(i=0; i<nOp; i++, pIn++){
+ int p2 = pIn->p2;
+ VdbeOp *pOut = &p->aOp[i+addr];
+ pOut->opcode = pIn->opcode;
+ pOut->p1 = pIn->p1;
+ if( p2<0 && sqlite3VdbeOpcodeHasProperty(pOut->opcode, OPFLG_JUMP) ){
+ pOut->p2 = addr + ADDR(p2);
+ }else{
+ pOut->p2 = p2;
+ }
+ pOut->p3 = pIn->p3;
+ pOut->p4type = P4_NOTUSED;
+ pOut->p4.p = 0;
+ pOut->p5 = 0;
+#ifdef SQLITE_DEBUG
+ pOut->zComment = 0;
+ if( sqlite3VdbeAddopTrace ){
+ sqlite3VdbePrintOp(0, i+addr, &p->aOp[i+addr]);
+ }
+#endif
+ }
+ p->nOp += nOp;
+ }
+ return addr;
+}
+
+/*
+** Change the value of the P1 operand for a specific instruction.
+** This routine is useful when a large program is loaded from a
+** static array using sqlite3VdbeAddOpList but we want to make a
+** few minor changes to the program.
+*/
+SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe *p, int addr, int val){
+ assert( p==0 || p->magic==VDBE_MAGIC_INIT );
+ if( p && addr>=0 && p->nOp>addr && p->aOp ){
+ p->aOp[addr].p1 = val;
+ }
+}
+
+/*
+** Change the value of the P2 operand for a specific instruction.
+** This routine is useful for setting a jump destination.
+*/
+SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe *p, int addr, int val){
+ assert( p==0 || p->magic==VDBE_MAGIC_INIT );
+ if( p && addr>=0 && p->nOp>addr && p->aOp ){
+ p->aOp[addr].p2 = val;
+ }
+}
+
+/*
+** Change the value of the P3 operand for a specific instruction.
+*/
+SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, int addr, int val){
+ assert( p==0 || p->magic==VDBE_MAGIC_INIT );
+ if( p && addr>=0 && p->nOp>addr && p->aOp ){
+ p->aOp[addr].p3 = val;
+ }
+}
+
+/*
+** Change the value of the P5 operand for the most recently
+** added operation.
+*/
+SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u8 val){
+ assert( p==0 || p->magic==VDBE_MAGIC_INIT );
+ if( p && p->aOp ){
+ assert( p->nOp>0 );
+ p->aOp[p->nOp-1].p5 = val;
+ }
+}
+
+/*
+** Change the P2 operand of instruction addr so that it points to
+** the address of the next instruction to be coded.
+*/
+SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){
+ sqlite3VdbeChangeP2(p, addr, p->nOp);
+}
+
+
+/*
+** If the input FuncDef structure is ephemeral, then free it. If
+** the FuncDef is not ephermal, then do nothing.
+*/
+static void freeEphemeralFunction(FuncDef *pDef){
+ if( pDef && (pDef->flags & SQLITE_FUNC_EPHEM)!=0 ){
+ sqlite3_free(pDef);
+ }
+}
+
+/*
+** Delete a P4 value if necessary.
+*/
+static void freeP4(int p4type, void *p3){
+ if( p3 ){
+ switch( p4type ){
+ case P4_REAL:
+ case P4_INT64:
+ case P4_MPRINTF:
+ case P4_DYNAMIC:
+ case P4_KEYINFO:
+ case P4_KEYINFO_HANDOFF: {
+ sqlite3_free(p3);
+ break;
+ }
+ case P4_VDBEFUNC: {
+ VdbeFunc *pVdbeFunc = (VdbeFunc *)p3;
+ freeEphemeralFunction(pVdbeFunc->pFunc);
+ sqlite3VdbeDeleteAuxData(pVdbeFunc, 0);
+ sqlite3_free(pVdbeFunc);
+ break;
+ }
+ case P4_FUNCDEF: {
+ freeEphemeralFunction((FuncDef*)p3);
+ break;
+ }
+ case P4_MEM: {
+ sqlite3ValueFree((sqlite3_value*)p3);
+ break;
+ }
+ }
+ }
+}
+
+
+/*
+** Change N opcodes starting at addr to No-ops.
+*/
+SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe *p, int addr, int N){
+ if( p && p->aOp ){
+ VdbeOp *pOp = &p->aOp[addr];
+ while( N-- ){
+ freeP4(pOp->p4type, pOp->p4.p);
+ memset(pOp, 0, sizeof(pOp[0]));
+ pOp->opcode = OP_Noop;
+ pOp++;
+ }
+ }
+}
+
+/*
+** Change the value of the P4 operand for a specific instruction.
+** This routine is useful when a large program is loaded from a
+** static array using sqlite3VdbeAddOpList but we want to make a
+** few minor changes to the program.
+**
+** If n>=0 then the P4 operand is dynamic, meaning that a copy of
+** the string is made into memory obtained from sqlite3_malloc().
+** A value of n==0 means copy bytes of zP4 up to and including the
+** first null byte. If n>0 then copy n+1 bytes of zP4.
+**
+** If n==P4_KEYINFO it means that zP4 is a pointer to a KeyInfo structure.
+** A copy is made of the KeyInfo structure into memory obtained from
+** sqlite3_malloc, to be freed when the Vdbe is finalized.
+** n==P4_KEYINFO_HANDOFF indicates that zP4 points to a KeyInfo structure
+** stored in memory that the caller has obtained from sqlite3_malloc. The
+** caller should not free the allocation, it will be freed when the Vdbe is
+** finalized.
+**
+** Other values of n (P4_STATIC, P4_COLLSEQ etc.) indicate that zP4 points
+** to a string or structure that is guaranteed to exist for the lifetime of
+** the Vdbe. In these cases we can just copy the pointer.
+**
+** If addr<0 then change P4 on the most recently inserted instruction.
+*/
+SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){
+ Op *pOp;
+ assert( p!=0 );
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p->aOp==0 || p->db->mallocFailed ){
+ if (n != P4_KEYINFO) {
+ freeP4(n, (void*)*(char**)&zP4);
+ }
+ return;
+ }
+ assert( addr<p->nOp );
+ if( addr<0 ){
+ addr = p->nOp - 1;
+ if( addr<0 ) return;
+ }
+ pOp = &p->aOp[addr];
+ freeP4(pOp->p4type, pOp->p4.p);
+ pOp->p4.p = 0;
+ if( n==P4_INT32 ){
+ /* Note: this cast is safe, because the origin data point was an int
+ ** that was cast to a (const char *). */
+ pOp->p4.i = (int)zP4;
+ pOp->p4type = n;
+ }else if( zP4==0 ){
+ pOp->p4.p = 0;
+ pOp->p4type = P4_NOTUSED;
+ }else if( n==P4_KEYINFO ){
+ KeyInfo *pKeyInfo;
+ int nField, nByte;
+
+ nField = ((KeyInfo*)zP4)->nField;
+ nByte = sizeof(*pKeyInfo) + (nField-1)*sizeof(pKeyInfo->aColl[0]) + nField;
+ pKeyInfo = sqlite3_malloc( nByte );
+ pOp->p4.pKeyInfo = pKeyInfo;
+ if( pKeyInfo ){
+ memcpy(pKeyInfo, zP4, nByte);
+ /* In the current implementation, P4_KEYINFO is only ever used on
+ ** KeyInfo structures that have no aSortOrder component. Elements
+ ** with an aSortOrder always use P4_KEYINFO_HANDOFF. So we do not
+ ** need to bother with duplicating the aSortOrder. */
+ assert( pKeyInfo->aSortOrder==0 );
+#if 0
+ aSortOrder = pKeyInfo->aSortOrder;
+ if( aSortOrder ){
+ pKeyInfo->aSortOrder = (unsigned char*)&pKeyInfo->aColl[nField];
+ memcpy(pKeyInfo->aSortOrder, aSortOrder, nField);
+ }
+#endif
+ pOp->p4type = P4_KEYINFO;
+ }else{
+ p->db->mallocFailed = 1;
+ pOp->p4type = P4_NOTUSED;
+ }
+ }else if( n==P4_KEYINFO_HANDOFF ){
+ pOp->p4.p = (void*)zP4;
+ pOp->p4type = P4_KEYINFO;
+ }else if( n<0 ){
+ pOp->p4.p = (void*)zP4;
+ pOp->p4type = n;
+ }else{
+ if( n==0 ) n = strlen(zP4);
+ pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n);
+ pOp->p4type = P4_DYNAMIC;
+ }
+}
+
+#ifndef NDEBUG
+/*
+** Change the comment on the the most recently coded instruction.
+*/
+SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){
+ va_list ap;
+ assert( p->nOp>0 || p->aOp==0 );
+ assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed );
+ if( p->nOp ){
+ char **pz = &p->aOp[p->nOp-1].zComment;
+ va_start(ap, zFormat);
+ sqlite3_free(*pz);
+ *pz = sqlite3VMPrintf(p->db, zFormat, ap);
+ va_end(ap);
+ }
+}
+#endif
+
+/*
+** Return the opcode for a given address.
+*/
+SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){
+ assert( p->magic==VDBE_MAGIC_INIT );
+ assert( (addr>=0 && addr<p->nOp) || p->db->mallocFailed );
+ return ((addr>=0 && addr<p->nOp)?(&p->aOp[addr]):0);
+}
+
+#if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) \
+ || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG)
+/*
+** Compute a string that describes the P4 parameter for an opcode.
+** Use zTemp for any required temporary buffer space.
+*/
+static char *displayP4(Op *pOp, char *zTemp, int nTemp){
+ char *zP4 = zTemp;
+ assert( nTemp>=20 );
+ switch( pOp->p4type ){
+ case P4_KEYINFO: {
+ int i, j;
+ KeyInfo *pKeyInfo = pOp->p4.pKeyInfo;
+ sqlite3_snprintf(nTemp, zTemp, "keyinfo(%d", pKeyInfo->nField);
+ i = strlen(zTemp);
+ for(j=0; j<pKeyInfo->nField; j++){
+ CollSeq *pColl = pKeyInfo->aColl[j];
+ if( pColl ){
+ int n = strlen(pColl->zName);
+ if( i+n>nTemp-6 ){
+ memcpy(&zTemp[i],",...",4);
+ break;
+ }
+ zTemp[i++] = ',';
+ if( pKeyInfo->aSortOrder && pKeyInfo->aSortOrder[j] ){
+ zTemp[i++] = '-';
+ }
+ memcpy(&zTemp[i], pColl->zName,n+1);
+ i += n;
+ }else if( i+4<nTemp-6 ){
+ memcpy(&zTemp[i],",nil",4);
+ i += 4;
+ }
+ }
+ zTemp[i++] = ')';
+ zTemp[i] = 0;
+ assert( i<nTemp );
+ break;
+ }
+ case P4_COLLSEQ: {
+ CollSeq *pColl = pOp->p4.pColl;
+ sqlite3_snprintf(nTemp, zTemp, "collseq(%.20s)", pColl->zName);
+ break;
+ }
+ case P4_FUNCDEF: {
+ FuncDef *pDef = pOp->p4.pFunc;
+ sqlite3_snprintf(nTemp, zTemp, "%s(%d)", pDef->zName, pDef->nArg);
+ break;
+ }
+ case P4_INT64: {
+ sqlite3_snprintf(nTemp, zTemp, "%lld", *pOp->p4.pI64);
+ break;
+ }
+ case P4_INT32: {
+ sqlite3_snprintf(nTemp, zTemp, "%d", pOp->p4.i);
+ break;
+ }
+ case P4_REAL: {
+ sqlite3_snprintf(nTemp, zTemp, "%.16g", *pOp->p4.pReal);
+ break;
+ }
+ case P4_MEM: {
+ Mem *pMem = pOp->p4.pMem;
+ assert( (pMem->flags & MEM_Null)==0 );
+ if( pMem->flags & MEM_Str ){
+ zP4 = pMem->z;
+ }else if( pMem->flags & MEM_Int ){
+ sqlite3_snprintf(nTemp, zTemp, "%lld", pMem->u.i);
+ }else if( pMem->flags & MEM_Real ){
+ sqlite3_snprintf(nTemp, zTemp, "%.16g", pMem->r);
+ }
+ break;
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ case P4_VTAB: {
+ sqlite3_vtab *pVtab = pOp->p4.pVtab;
+ sqlite3_snprintf(nTemp, zTemp, "vtab:%p:%p", pVtab, pVtab->pModule);
+ break;
+ }
+#endif
+ default: {
+ zP4 = pOp->p4.z;
+ if( zP4==0 ){
+ zP4 = zTemp;
+ zTemp[0] = 0;
+ }
+ }
+ }
+ assert( zP4!=0 );
+ return zP4;
+}
+#endif
+
+/*
+** Declare to the Vdbe that the BTree object at db->aDb[i] is used.
+**
+*/
+SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){
+ int mask;
+ assert( i>=0 && i<p->db->nDb );
+ assert( i<sizeof(p->btreeMask)*8 );
+ mask = 1<<i;
+ if( (p->btreeMask & mask)==0 ){
+ p->btreeMask |= mask;
+ sqlite3BtreeMutexArrayInsert(&p->aMutex, p->db->aDb[i].pBt);
+ }
+}
+
+
+#if defined(VDBE_PROFILE) || defined(SQLITE_DEBUG)
+/*
+** Print a single opcode. This routine is used for debugging only.
+*/
+SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){
+ char *zP4;
+ char zPtr[50];
+ static const char *zFormat1 = "%4d %-13s %4d %4d %4d %-4s %.2X %s\n";
+ if( pOut==0 ) pOut = stdout;
+ zP4 = displayP4(pOp, zPtr, sizeof(zPtr));
+ fprintf(pOut, zFormat1, pc,
+ sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, zP4, pOp->p5,
+#ifdef SQLITE_DEBUG
+ pOp->zComment ? pOp->zComment : ""
+#else
+ ""
+#endif
+ );
+ fflush(pOut);
+}
+#endif
+
+/*
+** Release an array of N Mem elements
+*/
+static void releaseMemArray(Mem *p, int N, int freebuffers){
+ if( p && N ){
+ sqlite3 *db = p->db;
+ int malloc_failed = db->mallocFailed;
+ while( N-->0 ){
+ assert( N<2 || p[0].db==p[1].db );
+ if( freebuffers ){
+ sqlite3VdbeMemRelease(p);
+ }else{
+ sqlite3VdbeMemReleaseExternal(p);
+ }
+ p->flags = MEM_Null;
+ p++;
+ }
+ db->mallocFailed = malloc_failed;
+ }
+}
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+SQLITE_PRIVATE int sqlite3VdbeReleaseBuffers(Vdbe *p){
+ int ii;
+ int nFree = 0;
+ assert( sqlite3_mutex_held(p->db->mutex) );
+ for(ii=1; ii<=p->nMem; ii++){
+ Mem *pMem = &p->aMem[ii];
+ if( pMem->z && pMem->flags&MEM_Dyn ){
+ assert( !pMem->xDel );
+ nFree += sqlite3MallocSize(pMem->z);
+ sqlite3VdbeMemRelease(pMem);
+ }
+ }
+ return nFree;
+}
+#endif
+
+#ifndef SQLITE_OMIT_EXPLAIN
+/*
+** Give a listing of the program in the virtual machine.
+**
+** The interface is the same as sqlite3VdbeExec(). But instead of
+** running the code, it invokes the callback once for each instruction.
+** This feature is used to implement "EXPLAIN".
+**
+** When p->explain==1, each instruction is listed. When
+** p->explain==2, only OP_Explain instructions are listed and these
+** are shown in a different format. p->explain==2 is used to implement
+** EXPLAIN QUERY PLAN.
+*/
+SQLITE_PRIVATE int sqlite3VdbeList(
+ Vdbe *p /* The VDBE */
+){
+ sqlite3 *db = p->db;
+ int i;
+ int rc = SQLITE_OK;
+ Mem *pMem = p->pResultSet = &p->aMem[1];
+
+ assert( p->explain );
+ if( p->magic!=VDBE_MAGIC_RUN ) return SQLITE_MISUSE;
+ assert( db->magic==SQLITE_MAGIC_BUSY );
+ assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY );
+
+ /* Even though this opcode does not use dynamic strings for
+ ** the result, result columns may become dynamic if the user calls
+ ** sqlite3_column_text16(), causing a translation to UTF-16 encoding.
+ */
+ releaseMemArray(pMem, p->nMem, 1);
+
+ do{
+ i = p->pc++;
+ }while( i<p->nOp && p->explain==2 && p->aOp[i].opcode!=OP_Explain );
+ if( i>=p->nOp ){
+ p->rc = SQLITE_OK;
+ rc = SQLITE_DONE;
+ }else if( db->u1.isInterrupted ){
+ p->rc = SQLITE_INTERRUPT;
+ rc = SQLITE_ERROR;
+ sqlite3SetString(&p->zErrMsg, sqlite3ErrStr(p->rc), (char*)0);
+ }else{
+ char *z;
+ Op *pOp = &p->aOp[i];
+ if( p->explain==1 ){
+ pMem->flags = MEM_Int;
+ pMem->type = SQLITE_INTEGER;
+ pMem->u.i = i; /* Program counter */
+ pMem++;
+
+ pMem->flags = MEM_Static|MEM_Str|MEM_Term;
+ pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */
+ assert( pMem->z!=0 );
+ pMem->n = strlen(pMem->z);
+ pMem->type = SQLITE_TEXT;
+ pMem->enc = SQLITE_UTF8;
+ pMem++;
+ }
+
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p1; /* P1 */
+ pMem->type = SQLITE_INTEGER;
+ pMem++;
+
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p2; /* P2 */
+ pMem->type = SQLITE_INTEGER;
+ pMem++;
+
+ if( p->explain==1 ){
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p3; /* P3 */
+ pMem->type = SQLITE_INTEGER;
+ pMem++;
+ }
+
+ if( sqlite3VdbeMemGrow(pMem, 32, 0) ){ /* P4 */
+ p->db->mallocFailed = 1;
+ return SQLITE_NOMEM;
+ }
+ pMem->flags = MEM_Dyn|MEM_Str|MEM_Term;
+ z = displayP4(pOp, pMem->z, 32);
+ if( z!=pMem->z ){
+ sqlite3VdbeMemSetStr(pMem, z, -1, SQLITE_UTF8, 0);
+ }else{
+ assert( pMem->z!=0 );
+ pMem->n = strlen(pMem->z);
+ pMem->enc = SQLITE_UTF8;
+ }
+ pMem->type = SQLITE_TEXT;
+ pMem++;
+
+ if( p->explain==1 ){
+ if( sqlite3VdbeMemGrow(pMem, 4, 0) ){
+ p->db->mallocFailed = 1;
+ return SQLITE_NOMEM;
+ }
+ pMem->flags = MEM_Dyn|MEM_Str|MEM_Term;
+ pMem->n = 2;
+ sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */
+ pMem->type = SQLITE_TEXT;
+ pMem->enc = SQLITE_UTF8;
+ pMem++;
+
+#ifdef SQLITE_DEBUG
+ if( pOp->zComment ){
+ pMem->flags = MEM_Str|MEM_Term;
+ pMem->z = pOp->zComment;
+ pMem->n = strlen(pMem->z);
+ pMem->enc = SQLITE_UTF8;
+ }else
+#endif
+ {
+ pMem->flags = MEM_Null; /* Comment */
+ pMem->type = SQLITE_NULL;
+ }
+ }
+
+ p->nResColumn = 8 - 5*(p->explain-1);
+ p->rc = SQLITE_OK;
+ rc = SQLITE_ROW;
+ }
+ return rc;
+}
+#endif /* SQLITE_OMIT_EXPLAIN */
+
+#ifdef SQLITE_DEBUG
+/*
+** Print the SQL that was used to generate a VDBE program.
+*/
+SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe *p){
+ int nOp = p->nOp;
+ VdbeOp *pOp;
+ if( nOp<1 ) return;
+ pOp = &p->aOp[0];
+ if( pOp->opcode==OP_Trace && pOp->p4.z!=0 ){
+ const char *z = pOp->p4.z;
+ while( isspace(*(u8*)z) ) z++;
+ printf("SQL: [%s]\n", z);
+ }
+}
+#endif
+
+#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
+/*
+** Print an IOTRACE message showing SQL content.
+*/
+SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe *p){
+ int nOp = p->nOp;
+ VdbeOp *pOp;
+ if( sqlite3IoTrace==0 ) return;
+ if( nOp<1 ) return;
+ pOp = &p->aOp[0];
+ if( pOp->opcode==OP_Trace && pOp->p4.z!=0 ){
+ int i, j;
+ char z[1000];
+ sqlite3_snprintf(sizeof(z), z, "%s", pOp->p4.z);
+ for(i=0; isspace((unsigned char)z[i]); i++){}
+ for(j=0; z[i]; i++){
+ if( isspace((unsigned char)z[i]) ){
+ if( z[i-1]!=' ' ){
+ z[j++] = ' ';
+ }
+ }else{
+ z[j++] = z[i];
+ }
+ }
+ z[j] = 0;
+ sqlite3IoTrace("SQL %s\n", z);
+ }
+}
+#endif /* !SQLITE_OMIT_TRACE && SQLITE_ENABLE_IOTRACE */
+
+
+/*
+** Prepare a virtual machine for execution. This involves things such
+** as allocating stack space and initializing the program counter.
+** After the VDBE has be prepped, it can be executed by one or more
+** calls to sqlite3VdbeExec().
+**
+** This is the only way to move a VDBE from VDBE_MAGIC_INIT to
+** VDBE_MAGIC_RUN.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMakeReady(
+ Vdbe *p, /* The VDBE */
+ int nVar, /* Number of '?' see in the SQL statement */
+ int nMem, /* Number of memory cells to allocate */
+ int nCursor, /* Number of cursors to allocate */
+ int isExplain /* True if the EXPLAIN keywords is present */
+){
+ int n;
+ sqlite3 *db = p->db;
+
+ assert( p!=0 );
+ assert( p->magic==VDBE_MAGIC_INIT );
+
+ /* There should be at least one opcode.
+ */
+ assert( p->nOp>0 );
+
+ /* Set the magic to VDBE_MAGIC_RUN sooner rather than later. This
+ * is because the call to resizeOpArray() below may shrink the
+ * p->aOp[] array to save memory if called when in VDBE_MAGIC_RUN
+ * state.
+ */
+ p->magic = VDBE_MAGIC_RUN;
+
+ /* For each cursor required, also allocate a memory cell. Memory
+ ** cells (nMem+1-nCursor)..nMem, inclusive, will never be used by
+ ** the vdbe program. Instead they are used to allocate space for
+ ** Cursor/BtCursor structures. The blob of memory associated with
+ ** cursor 0 is stored in memory cell nMem. Memory cell (nMem-1)
+ ** stores the blob of memory associated with cursor 1, etc.
+ **
+ ** See also: allocateCursor().
+ */
+ nMem += nCursor;
+
+ /*
+ ** Allocation space for registers.
+ */
+ if( p->aMem==0 ){
+ int nArg; /* Maximum number of args passed to a user function. */
+ resolveP2Values(p, &nArg);
+ /*resizeOpArray(p, p->nOp);*/
+ assert( nVar>=0 );
+ if( isExplain && nMem<10 ){
+ p->nMem = nMem = 10;
+ }
+ p->aMem = sqlite3DbMallocZero(db,
+ nMem*sizeof(Mem) /* aMem */
+ + nVar*sizeof(Mem) /* aVar */
+ + nArg*sizeof(Mem*) /* apArg */
+ + nVar*sizeof(char*) /* azVar */
+ + nCursor*sizeof(Cursor*) + 1 /* apCsr */
+ );
+ if( !db->mallocFailed ){
+ p->aMem--; /* aMem[] goes from 1..nMem */
+ p->nMem = nMem; /* not from 0..nMem-1 */
+ p->aVar = &p->aMem[nMem+1];
+ p->nVar = nVar;
+ p->okVar = 0;
+ p->apArg = (Mem**)&p->aVar[nVar];
+ p->azVar = (char**)&p->apArg[nArg];
+ p->apCsr = (Cursor**)&p->azVar[nVar];
+ p->nCursor = nCursor;
+ for(n=0; n<nVar; n++){
+ p->aVar[n].flags = MEM_Null;
+ p->aVar[n].db = db;
+ }
+ for(n=1; n<=nMem; n++){
+ p->aMem[n].flags = MEM_Null;
+ p->aMem[n].db = db;
+ }
+ }
+ }
+#ifdef SQLITE_DEBUG
+ for(n=1; n<p->nMem; n++){
+ assert( p->aMem[n].db==db );
+ }
+#endif
+
+ p->pc = -1;
+ p->rc = SQLITE_OK;
+ p->uniqueCnt = 0;
+ p->returnDepth = 0;
+ p->errorAction = OE_Abort;
+ p->explain |= isExplain;
+ p->magic = VDBE_MAGIC_RUN;
+ p->nChange = 0;
+ p->cacheCtr = 1;
+ p->minWriteFileFormat = 255;
+ p->openedStatement = 0;
+#ifdef VDBE_PROFILE
+ {
+ int i;
+ for(i=0; i<p->nOp; i++){
+ p->aOp[i].cnt = 0;
+ p->aOp[i].cycles = 0;
+ }
+ }
+#endif
+}
+
+/*
+** Close a VDBE cursor and release all the resources that cursor
+** happens to hold.
+*/
+SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, Cursor *pCx){
+ if( pCx==0 ){
+ return;
+ }
+ if( pCx->pCursor ){
+ sqlite3BtreeCloseCursor(pCx->pCursor);
+ }
+ if( pCx->pBt ){
+ sqlite3BtreeClose(pCx->pBt);
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( pCx->pVtabCursor ){
+ sqlite3_vtab_cursor *pVtabCursor = pCx->pVtabCursor;
+ const sqlite3_module *pModule = pCx->pModule;
+ p->inVtabMethod = 1;
+ (void)sqlite3SafetyOff(p->db);
+ pModule->xClose(pVtabCursor);
+ (void)sqlite3SafetyOn(p->db);
+ p->inVtabMethod = 0;
+ }
+#endif
+ if( !pCx->ephemPseudoTable ){
+ sqlite3_free(pCx->pData);
+ }
+ /* memset(pCx, 0, sizeof(Cursor)); */
+ /* sqlite3_free(pCx->aType); */
+ /* sqlite3_free(pCx); */
+}
+
+/*
+** Close all cursors except for VTab cursors that are currently
+** in use.
+*/
+static void closeAllCursorsExceptActiveVtabs(Vdbe *p){
+ int i;
+ if( p->apCsr==0 ) return;
+ for(i=0; i<p->nCursor; i++){
+ Cursor *pC = p->apCsr[i];
+ if( pC && (!p->inVtabMethod || !pC->pVtabCursor) ){
+ sqlite3VdbeFreeCursor(p, pC);
+ p->apCsr[i] = 0;
+ }
+ }
+}
+
+/*
+** Clean up the VM after execution.
+**
+** This routine will automatically close any cursors, lists, and/or
+** sorters that were left open. It also deletes the values of
+** variables in the aVar[] array.
+*/
+static void Cleanup(Vdbe *p, int freebuffers){
+ int i;
+ closeAllCursorsExceptActiveVtabs(p);
+ for(i=1; i<=p->nMem; i++){
+ MemSetTypeFlag(&p->aMem[i], MEM_Null);
+ }
+ releaseMemArray(&p->aMem[1], p->nMem, freebuffers);
+ sqlite3VdbeFifoClear(&p->sFifo);
+ if( p->contextStack ){
+ for(i=0; i<p->contextStackTop; i++){
+ sqlite3VdbeFifoClear(&p->contextStack[i].sFifo);
+ }
+ sqlite3_free(p->contextStack);
+ }
+ p->contextStack = 0;
+ p->contextStackDepth = 0;
+ p->contextStackTop = 0;
+ sqlite3_free(p->zErrMsg);
+ p->zErrMsg = 0;
+ p->pResultSet = 0;
+}
+
+/*
+** Set the number of result columns that will be returned by this SQL
+** statement. This is now set at compile time, rather than during
+** execution of the vdbe program so that sqlite3_column_count() can
+** be called on an SQL statement before sqlite3_step().
+*/
+SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){
+ Mem *pColName;
+ int n;
+
+ releaseMemArray(p->aColName, p->nResColumn*COLNAME_N, 1);
+ sqlite3_free(p->aColName);
+ n = nResColumn*COLNAME_N;
+ p->nResColumn = nResColumn;
+ p->aColName = pColName = (Mem*)sqlite3DbMallocZero(p->db, sizeof(Mem)*n );
+ if( p->aColName==0 ) return;
+ while( n-- > 0 ){
+ pColName->flags = MEM_Null;
+ pColName->db = p->db;
+ pColName++;
+ }
+}
+
+/*
+** Set the name of the idx'th column to be returned by the SQL statement.
+** zName must be a pointer to a nul terminated string.
+**
+** This call must be made after a call to sqlite3VdbeSetNumCols().
+**
+** If N==P4_STATIC it means that zName is a pointer to a constant static
+** string and we can just copy the pointer. If it is P4_DYNAMIC, then
+** the string is freed using sqlite3_free() when the vdbe is finished with
+** it. Otherwise, N bytes of zName are copied.
+*/
+SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe *p, int idx, int var, const char *zName, int N){
+ int rc;
+ Mem *pColName;
+ assert( idx<p->nResColumn );
+ assert( var<COLNAME_N );
+ if( p->db->mallocFailed ) return SQLITE_NOMEM;
+ assert( p->aColName!=0 );
+ pColName = &(p->aColName[idx+var*p->nResColumn]);
+ if( N==P4_DYNAMIC || N==P4_STATIC ){
+ rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, SQLITE_STATIC);
+ }else{
+ rc = sqlite3VdbeMemSetStr(pColName, zName, N, SQLITE_UTF8,SQLITE_TRANSIENT);
+ }
+ if( rc==SQLITE_OK && N==P4_DYNAMIC ){
+ pColName->flags &= (~MEM_Static);
+ pColName->zMalloc = pColName->z;
+ }
+ return rc;
+}
+
+/*
+** A read or write transaction may or may not be active on database handle
+** db. If a transaction is active, commit it. If there is a
+** write-transaction spanning more than one database file, this routine
+** takes care of the master journal trickery.
+*/
+static int vdbeCommit(sqlite3 *db){
+ int i;
+ int nTrans = 0; /* Number of databases with an active write-transaction */
+ int rc = SQLITE_OK;
+ int needXcommit = 0;
+
+ /* Before doing anything else, call the xSync() callback for any
+ ** virtual module tables written in this transaction. This has to
+ ** be done before determining whether a master journal file is
+ ** required, as an xSync() callback may add an attached database
+ ** to the transaction.
+ */
+ rc = sqlite3VtabSync(db, rc);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ /* This loop determines (a) if the commit hook should be invoked and
+ ** (b) how many database files have open write transactions, not
+ ** including the temp database. (b) is important because if more than
+ ** one database file has an open write transaction, a master journal
+ ** file is required for an atomic commit.
+ */
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( sqlite3BtreeIsInTrans(pBt) ){
+ needXcommit = 1;
+ if( i!=1 ) nTrans++;
+ }
+ }
+
+ /* If there are any write-transactions at all, invoke the commit hook */
+ if( needXcommit && db->xCommitCallback ){
+ (void)sqlite3SafetyOff(db);
+ rc = db->xCommitCallback(db->pCommitArg);
+ (void)sqlite3SafetyOn(db);
+ if( rc ){
+ return SQLITE_CONSTRAINT;
+ }
+ }
+
+ /* The simple case - no more than one database file (not counting the
+ ** TEMP database) has a transaction active. There is no need for the
+ ** master-journal.
+ **
+ ** If the return value of sqlite3BtreeGetFilename() is a zero length
+ ** string, it means the main database is :memory:. In that case we do
+ ** not support atomic multi-file commits, so use the simple case then
+ ** too.
+ */
+ if( 0==strlen(sqlite3BtreeGetFilename(db->aDb[0].pBt)) || nTrans<=1 ){
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ rc = sqlite3BtreeCommitPhaseOne(pBt, 0);
+ }
+ }
+
+ /* Do the commit only if all databases successfully complete phase 1.
+ ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an
+ ** IO error while deleting or truncating a journal file. It is unlikely,
+ ** but could happen. In this case abandon processing and return the error.
+ */
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ rc = sqlite3BtreeCommitPhaseTwo(pBt);
+ }
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3VtabCommit(db);
+ }
+ }
+
+ /* The complex case - There is a multi-file write-transaction active.
+ ** This requires a master journal file to ensure the transaction is
+ ** committed atomicly.
+ */
+#ifndef SQLITE_OMIT_DISKIO
+ else{
+ sqlite3_vfs *pVfs = db->pVfs;
+ int needSync = 0;
+ char *zMaster = 0; /* File-name for the master journal */
+ char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt);
+ sqlite3_file *pMaster = 0;
+ i64 offset = 0;
+
+ /* Select a master journal file name */
+ do {
+ u32 random;
+ sqlite3_free(zMaster);
+ sqlite3_randomness(sizeof(random), &random);
+ zMaster = sqlite3MPrintf(db, "%s-mj%08X", zMainFile, random&0x7fffffff);
+ if( !zMaster ){
+ return SQLITE_NOMEM;
+ }
+ rc = sqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS);
+ }while( rc==1 );
+ if( rc!=0 ){
+ rc = SQLITE_IOERR_NOMEM;
+ }else{
+ /* Open the master journal. */
+ rc = sqlite3OsOpenMalloc(pVfs, zMaster, &pMaster,
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|
+ SQLITE_OPEN_EXCLUSIVE|SQLITE_OPEN_MASTER_JOURNAL, 0
+ );
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(zMaster);
+ return rc;
+ }
+
+ /* Write the name of each database file in the transaction into the new
+ ** master journal file. If an error occurs at this point close
+ ** and delete the master journal file. All the individual journal files
+ ** still have 'null' as the master journal pointer, so they will roll
+ ** back independently if a failure occurs.
+ */
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( i==1 ) continue; /* Ignore the TEMP database */
+ if( sqlite3BtreeIsInTrans(pBt) ){
+ char const *zFile = sqlite3BtreeGetJournalname(pBt);
+ if( zFile[0]==0 ) continue; /* Ignore :memory: databases */
+ if( !needSync && !sqlite3BtreeSyncDisabled(pBt) ){
+ needSync = 1;
+ }
+ rc = sqlite3OsWrite(pMaster, zFile, strlen(zFile)+1, offset);
+ offset += strlen(zFile)+1;
+ if( rc!=SQLITE_OK ){
+ sqlite3OsCloseFree(pMaster);
+ sqlite3OsDelete(pVfs, zMaster, 0);
+ sqlite3_free(zMaster);
+ return rc;
+ }
+ }
+ }
+
+ /* Sync the master journal file. If the IOCAP_SEQUENTIAL device
+ ** flag is set this is not required.
+ */
+ zMainFile = sqlite3BtreeGetDirname(db->aDb[0].pBt);
+ if( (needSync
+ && (0==(sqlite3OsDeviceCharacteristics(pMaster)&SQLITE_IOCAP_SEQUENTIAL))
+ && (rc=sqlite3OsSync(pMaster, SQLITE_SYNC_NORMAL))!=SQLITE_OK) ){
+ sqlite3OsCloseFree(pMaster);
+ sqlite3OsDelete(pVfs, zMaster, 0);
+ sqlite3_free(zMaster);
+ return rc;
+ }
+
+ /* Sync all the db files involved in the transaction. The same call
+ ** sets the master journal pointer in each individual journal. If
+ ** an error occurs here, do not delete the master journal file.
+ **
+ ** If the error occurs during the first call to
+ ** sqlite3BtreeCommitPhaseOne(), then there is a chance that the
+ ** master journal file will be orphaned. But we cannot delete it,
+ ** in case the master journal file name was written into the journal
+ ** file before the failure occured.
+ */
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ rc = sqlite3BtreeCommitPhaseOne(pBt, zMaster);
+ }
+ }
+ sqlite3OsCloseFree(pMaster);
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(zMaster);
+ return rc;
+ }
+
+ /* Delete the master journal file. This commits the transaction. After
+ ** doing this the directory is synced again before any individual
+ ** transaction files are deleted.
+ */
+ rc = sqlite3OsDelete(pVfs, zMaster, 1);
+ sqlite3_free(zMaster);
+ zMaster = 0;
+ if( rc ){
+ return rc;
+ }
+
+ /* All files and directories have already been synced, so the following
+ ** calls to sqlite3BtreeCommitPhaseTwo() are only closing files and
+ ** deleting or truncating journals. If something goes wrong while
+ ** this is happening we don't really care. The integrity of the
+ ** transaction is already guaranteed, but some stray 'cold' journals
+ ** may be lying around. Returning an error code won't help matters.
+ */
+ disable_simulated_io_errors();
+ sqlite3FaultBeginBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ sqlite3BtreeCommitPhaseTwo(pBt);
+ }
+ }
+ sqlite3FaultEndBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ enable_simulated_io_errors();
+
+ sqlite3VtabCommit(db);
+ }
+#endif
+
+ return rc;
+}
+
+/*
+** This routine checks that the sqlite3.activeVdbeCnt count variable
+** matches the number of vdbe's in the list sqlite3.pVdbe that are
+** currently active. An assertion fails if the two counts do not match.
+** This is an internal self-check only - it is not an essential processing
+** step.
+**
+** This is a no-op if NDEBUG is defined.
+*/
+#ifndef NDEBUG
+static void checkActiveVdbeCnt(sqlite3 *db){
+ Vdbe *p;
+ int cnt = 0;
+ p = db->pVdbe;
+ while( p ){
+ if( p->magic==VDBE_MAGIC_RUN && p->pc>=0 ){
+ cnt++;
+ }
+ p = p->pNext;
+ }
+ assert( cnt==db->activeVdbeCnt );
+}
+#else
+#define checkActiveVdbeCnt(x)
+#endif
+
+/*
+** For every Btree that in database connection db which
+** has been modified, "trip" or invalidate each cursor in
+** that Btree might have been modified so that the cursor
+** can never be used again. This happens when a rollback
+*** occurs. We have to trip all the other cursors, even
+** cursor from other VMs in different database connections,
+** so that none of them try to use the data at which they
+** were pointing and which now may have been changed due
+** to the rollback.
+**
+** Remember that a rollback can delete tables complete and
+** reorder rootpages. So it is not sufficient just to save
+** the state of the cursor. We have to invalidate the cursor
+** so that it is never used again.
+*/
+static void invalidateCursorsOnModifiedBtrees(sqlite3 *db){
+ int i;
+ for(i=0; i<db->nDb; i++){
+ Btree *p = db->aDb[i].pBt;
+ if( p && sqlite3BtreeIsInTrans(p) ){
+ sqlite3BtreeTripAllCursors(p, SQLITE_ABORT);
+ }
+ }
+}
+
+/*
+** This routine is called the when a VDBE tries to halt. If the VDBE
+** has made changes and is in autocommit mode, then commit those
+** changes. If a rollback is needed, then do the rollback.
+**
+** This routine is the only way to move the state of a VM from
+** SQLITE_MAGIC_RUN to SQLITE_MAGIC_HALT. It is harmless to
+** call this on a VM that is in the SQLITE_MAGIC_HALT state.
+**
+** Return an error code. If the commit could not complete because of
+** lock contention, return SQLITE_BUSY. If SQLITE_BUSY is returned, it
+** means the close did not happen and needs to be repeated.
+*/
+SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
+ sqlite3 *db = p->db;
+ int i;
+ int (*xFunc)(Btree *pBt) = 0; /* Function to call on each btree backend */
+ int isSpecialError; /* Set to true if SQLITE_NOMEM or IOERR */
+
+ /* This function contains the logic that determines if a statement or
+ ** transaction will be committed or rolled back as a result of the
+ ** execution of this virtual machine.
+ **
+ ** If any of the following errors occur:
+ **
+ ** SQLITE_NOMEM
+ ** SQLITE_IOERR
+ ** SQLITE_FULL
+ ** SQLITE_INTERRUPT
+ **
+ ** Then the internal cache might have been left in an inconsistent
+ ** state. We need to rollback the statement transaction, if there is
+ ** one, or the complete transaction if there is no statement transaction.
+ */
+
+ if( p->db->mallocFailed ){
+ p->rc = SQLITE_NOMEM;
+ }
+ closeAllCursorsExceptActiveVtabs(p);
+ if( p->magic!=VDBE_MAGIC_RUN ){
+ return SQLITE_OK;
+ }
+ checkActiveVdbeCnt(db);
+
+ /* No commit or rollback needed if the program never started */
+ if( p->pc>=0 ){
+ int mrc; /* Primary error code from p->rc */
+
+ /* Lock all btrees used by the statement */
+ sqlite3BtreeMutexArrayEnter(&p->aMutex);
+
+ /* Check for one of the special errors */
+ mrc = p->rc & 0xff;
+ isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR
+ || mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL;
+ if( isSpecialError ){
+ /* This loop does static analysis of the query to see which of the
+ ** following three categories it falls into:
+ **
+ ** Read-only
+ ** Query with statement journal
+ ** Query without statement journal
+ **
+ ** We could do something more elegant than this static analysis (i.e.
+ ** store the type of query as part of the compliation phase), but
+ ** handling malloc() or IO failure is a fairly obscure edge case so
+ ** this is probably easier. Todo: Might be an opportunity to reduce
+ ** code size a very small amount though...
+ */
+ int notReadOnly = 0;
+ int isStatement = 0;
+ assert(p->aOp || p->nOp==0);
+ for(i=0; i<p->nOp; i++){
+ switch( p->aOp[i].opcode ){
+ case OP_Transaction:
+ notReadOnly |= p->aOp[i].p2;
+ break;
+ case OP_Statement:
+ isStatement = 1;
+ break;
+ }
+ }
+
+
+ /* If the query was read-only, we need do no rollback at all. Otherwise,
+ ** proceed with the special handling.
+ */
+ if( notReadOnly || mrc!=SQLITE_INTERRUPT ){
+ if( p->rc==SQLITE_IOERR_BLOCKED && isStatement ){
+ xFunc = sqlite3BtreeRollbackStmt;
+ p->rc = SQLITE_BUSY;
+ } else if( (mrc==SQLITE_NOMEM || mrc==SQLITE_FULL) && isStatement ){
+ xFunc = sqlite3BtreeRollbackStmt;
+ }else{
+ /* We are forced to roll back the active transaction. Before doing
+ ** so, abort any other statements this handle currently has active.
+ */
+ invalidateCursorsOnModifiedBtrees(db);
+ sqlite3RollbackAll(db);
+ db->autoCommit = 1;
+ }
+ }
+ }
+
+ /* If the auto-commit flag is set and this is the only active vdbe, then
+ ** we do either a commit or rollback of the current transaction.
+ **
+ ** Note: This block also runs if one of the special errors handled
+ ** above has occured.
+ */
+ if( db->autoCommit && db->activeVdbeCnt==1 ){
+ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){
+ /* The auto-commit flag is true, and the vdbe program was
+ ** successful or hit an 'OR FAIL' constraint. This means a commit
+ ** is required.
+ */
+ int rc = vdbeCommit(db);
+ if( rc==SQLITE_BUSY ){
+ sqlite3BtreeMutexArrayLeave(&p->aMutex);
+ return SQLITE_BUSY;
+ }else if( rc!=SQLITE_OK ){
+ p->rc = rc;
+ sqlite3RollbackAll(db);
+ }else{
+ sqlite3CommitInternalChanges(db);
+ }
+ }else{
+ sqlite3RollbackAll(db);
+ }
+ }else if( !xFunc ){
+ if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){
+ if( p->openedStatement ){
+ xFunc = sqlite3BtreeCommitStmt;
+ }
+ }else if( p->errorAction==OE_Abort ){
+ xFunc = sqlite3BtreeRollbackStmt;
+ }else{
+ invalidateCursorsOnModifiedBtrees(db);
+ sqlite3RollbackAll(db);
+ db->autoCommit = 1;
+ }
+ }
+
+ /* If xFunc is not NULL, then it is one of sqlite3BtreeRollbackStmt or
+ ** sqlite3BtreeCommitStmt. Call it once on each backend. If an error occurs
+ ** and the return code is still SQLITE_OK, set the return code to the new
+ ** error value.
+ */
+ assert(!xFunc ||
+ xFunc==sqlite3BtreeCommitStmt ||
+ xFunc==sqlite3BtreeRollbackStmt
+ );
+ for(i=0; xFunc && i<db->nDb; i++){
+ int rc;
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ rc = xFunc(pBt);
+ if( rc && (p->rc==SQLITE_OK || p->rc==SQLITE_CONSTRAINT) ){
+ p->rc = rc;
+ sqlite3SetString(&p->zErrMsg, 0);
+ }
+ }
+ }
+
+ /* If this was an INSERT, UPDATE or DELETE and the statement was committed,
+ ** set the change counter.
+ */
+ if( p->changeCntOn && p->pc>=0 ){
+ if( !xFunc || xFunc==sqlite3BtreeCommitStmt ){
+ sqlite3VdbeSetChanges(db, p->nChange);
+ }else{
+ sqlite3VdbeSetChanges(db, 0);
+ }
+ p->nChange = 0;
+ }
+
+ /* Rollback or commit any schema changes that occurred. */
+ if( p->rc!=SQLITE_OK && db->flags&SQLITE_InternChanges ){
+ sqlite3ResetInternalSchema(db, 0);
+ db->flags = (db->flags | SQLITE_InternChanges);
+ }
+
+ /* Release the locks */
+ sqlite3BtreeMutexArrayLeave(&p->aMutex);
+ }
+
+ /* We have successfully halted and closed the VM. Record this fact. */
+ if( p->pc>=0 ){
+ db->activeVdbeCnt--;
+ }
+ p->magic = VDBE_MAGIC_HALT;
+ checkActiveVdbeCnt(db);
+ if( p->db->mallocFailed ){
+ p->rc = SQLITE_NOMEM;
+ }
+
+ return SQLITE_OK;
+}
+
+
+/*
+** Each VDBE holds the result of the most recent sqlite3_step() call
+** in p->rc. This routine sets that result back to SQLITE_OK.
+*/
+SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe *p){
+ p->rc = SQLITE_OK;
+}
+
+/*
+** Clean up a VDBE after execution but do not delete the VDBE just yet.
+** Write any error messages into *pzErrMsg. Return the result code.
+**
+** After this routine is run, the VDBE should be ready to be executed
+** again.
+**
+** To look at it another way, this routine resets the state of the
+** virtual machine from VDBE_MAGIC_RUN or VDBE_MAGIC_HALT back to
+** VDBE_MAGIC_INIT.
+*/
+SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p, int freebuffers){
+ sqlite3 *db;
+ db = p->db;
+
+ /* If the VM did not run to completion or if it encountered an
+ ** error, then it might not have been halted properly. So halt
+ ** it now.
+ */
+ (void)sqlite3SafetyOn(db);
+ sqlite3VdbeHalt(p);
+ (void)sqlite3SafetyOff(db);
+
+ /* If the VDBE has be run even partially, then transfer the error code
+ ** and error message from the VDBE into the main database structure. But
+ ** if the VDBE has just been set to run but has not actually executed any
+ ** instructions yet, leave the main database error information unchanged.
+ */
+ if( p->pc>=0 ){
+ if( p->zErrMsg ){
+ sqlite3ValueSetStr(db->pErr,-1,p->zErrMsg,SQLITE_UTF8,sqlite3_free);
+ db->errCode = p->rc;
+ p->zErrMsg = 0;
+ }else if( p->rc ){
+ sqlite3Error(db, p->rc, 0);
+ }else{
+ sqlite3Error(db, SQLITE_OK, 0);
+ }
+ }else if( p->rc && p->expired ){
+ /* The expired flag was set on the VDBE before the first call
+ ** to sqlite3_step(). For consistency (since sqlite3_step() was
+ ** called), set the database error in this case as well.
+ */
+ sqlite3Error(db, p->rc, 0);
+ sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, sqlite3_free);
+ p->zErrMsg = 0;
+ }
+
+ /* Reclaim all memory used by the VDBE
+ */
+ Cleanup(p, freebuffers);
+
+ /* Save profiling information from this VDBE run.
+ */
+#ifdef VDBE_PROFILE
+ {
+ FILE *out = fopen("vdbe_profile.out", "a");
+ if( out ){
+ int i;
+ fprintf(out, "---- ");
+ for(i=0; i<p->nOp; i++){
+ fprintf(out, "%02x", p->aOp[i].opcode);
+ }
+ fprintf(out, "\n");
+ for(i=0; i<p->nOp; i++){
+ fprintf(out, "%6d %10lld %8lld ",
+ p->aOp[i].cnt,
+ p->aOp[i].cycles,
+ p->aOp[i].cnt>0 ? p->aOp[i].cycles/p->aOp[i].cnt : 0
+ );
+ sqlite3VdbePrintOp(out, i, &p->aOp[i]);
+ }
+ fclose(out);
+ }
+ }
+#endif
+ p->magic = VDBE_MAGIC_INIT;
+ p->aborted = 0;
+ return p->rc & db->errMask;
+}
+
+/*
+** Clean up and delete a VDBE after execution. Return an integer which is
+** the result code. Write any error message text into *pzErrMsg.
+*/
+SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){
+ int rc = SQLITE_OK;
+ if( p->magic==VDBE_MAGIC_RUN || p->magic==VDBE_MAGIC_HALT ){
+ rc = sqlite3VdbeReset(p, 1);
+ assert( (rc & p->db->errMask)==rc );
+ }else if( p->magic!=VDBE_MAGIC_INIT ){
+ return SQLITE_MISUSE;
+ }
+ releaseMemArray(&p->aMem[1], p->nMem, 1);
+ sqlite3VdbeDelete(p);
+ return rc;
+}
+
+/*
+** Call the destructor for each auxdata entry in pVdbeFunc for which
+** the corresponding bit in mask is clear. Auxdata entries beyond 31
+** are always destroyed. To destroy all auxdata entries, call this
+** routine with mask==0.
+*/
+SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(VdbeFunc *pVdbeFunc, int mask){
+ int i;
+ for(i=0; i<pVdbeFunc->nAux; i++){
+ struct AuxData *pAux = &pVdbeFunc->apAux[i];
+ if( (i>31 || !(mask&(1<<i))) && pAux->pAux ){
+ if( pAux->xDelete ){
+ pAux->xDelete(pAux->pAux);
+ }
+ pAux->pAux = 0;
+ }
+ }
+}
+
+/*
+** Delete an entire VDBE.
+*/
+SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){
+ int i;
+ if( p==0 ) return;
+ Cleanup(p, 1);
+ if( p->pPrev ){
+ p->pPrev->pNext = p->pNext;
+ }else{
+ assert( p->db->pVdbe==p );
+ p->db->pVdbe = p->pNext;
+ }
+ if( p->pNext ){
+ p->pNext->pPrev = p->pPrev;
+ }
+ if( p->aOp ){
+ Op *pOp = p->aOp;
+ for(i=0; i<p->nOp; i++, pOp++){
+ freeP4(pOp->p4type, pOp->p4.p);
+#ifdef SQLITE_DEBUG
+ sqlite3_free(pOp->zComment);
+#endif
+ }
+ sqlite3_free(p->aOp);
+ }
+ releaseMemArray(p->aVar, p->nVar, 1);
+ sqlite3_free(p->aLabel);
+ if( p->aMem ){
+ sqlite3_free(&p->aMem[1]);
+ }
+ releaseMemArray(p->aColName, p->nResColumn*COLNAME_N, 1);
+ sqlite3_free(p->aColName);
+ sqlite3_free(p->zSql);
+ p->magic = VDBE_MAGIC_DEAD;
+ sqlite3_free(p);
+}
+
+/*
+** If a MoveTo operation is pending on the given cursor, then do that
+** MoveTo now. Return an error code. If no MoveTo is pending, this
+** routine does nothing and returns SQLITE_OK.
+*/
+SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(Cursor *p){
+ if( p->deferredMoveto ){
+ int res, rc;
+#ifdef SQLITE_TEST
+ extern int sqlite3_search_count;
+#endif
+ assert( p->isTable );
+ rc = sqlite3BtreeMoveto(p->pCursor, 0, 0, p->movetoTarget, 0, &res);
+ if( rc ) return rc;
+ *p->pIncrKey = 0;
+ p->lastRowid = keyToInt(p->movetoTarget);
+ p->rowidIsValid = res==0;
+ if( res<0 ){
+ rc = sqlite3BtreeNext(p->pCursor, &res);
+ if( rc ) return rc;
+ }
+#ifdef SQLITE_TEST
+ sqlite3_search_count++;
+#endif
+ p->deferredMoveto = 0;
+ p->cacheStatus = CACHE_STALE;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** The following functions:
+**
+** sqlite3VdbeSerialType()
+** sqlite3VdbeSerialTypeLen()
+** sqlite3VdbeSerialRead()
+** sqlite3VdbeSerialLen()
+** sqlite3VdbeSerialWrite()
+**
+** encapsulate the code that serializes values for storage in SQLite
+** data and index records. Each serialized value consists of a
+** 'serial-type' and a blob of data. The serial type is an 8-byte unsigned
+** integer, stored as a varint.
+**
+** In an SQLite index record, the serial type is stored directly before
+** the blob of data that it corresponds to. In a table record, all serial
+** types are stored at the start of the record, and the blobs of data at
+** the end. Hence these functions allow the caller to handle the
+** serial-type and data blob seperately.
+**
+** The following table describes the various storage classes for data:
+**
+** serial type bytes of data type
+** -------------- --------------- ---------------
+** 0 0 NULL
+** 1 1 signed integer
+** 2 2 signed integer
+** 3 3 signed integer
+** 4 4 signed integer
+** 5 6 signed integer
+** 6 8 signed integer
+** 7 8 IEEE float
+** 8 0 Integer constant 0
+** 9 0 Integer constant 1
+** 10,11 reserved for expansion
+** N>=12 and even (N-12)/2 BLOB
+** N>=13 and odd (N-13)/2 text
+**
+** The 8 and 9 types were added in 3.3.0, file format 4. Prior versions
+** of SQLite will not understand those serial types.
+*/
+
+/*
+** Return the serial-type for the value stored in pMem.
+*/
+SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){
+ int flags = pMem->flags;
+ int n;
+
+ if( flags&MEM_Null ){
+ return 0;
+ }
+ if( flags&MEM_Int ){
+ /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */
+# define MAX_6BYTE ((((i64)0x00008000)<<32)-1)
+ i64 i = pMem->u.i;
+ u64 u;
+ if( file_format>=4 && (i&1)==i ){
+ return 8+i;
+ }
+ u = i<0 ? -i : i;
+ if( u<=127 ) return 1;
+ if( u<=32767 ) return 2;
+ if( u<=8388607 ) return 3;
+ if( u<=2147483647 ) return 4;
+ if( u<=MAX_6BYTE ) return 5;
+ return 6;
+ }
+ if( flags&MEM_Real ){
+ return 7;
+ }
+ assert( flags&(MEM_Str|MEM_Blob) );
+ n = pMem->n;
+ if( flags & MEM_Zero ){
+ n += pMem->u.i;
+ }
+ assert( n>=0 );
+ return ((n*2) + 12 + ((flags&MEM_Str)!=0));
+}
+
+/*
+** Return the length of the data corresponding to the supplied serial-type.
+*/
+SQLITE_PRIVATE int sqlite3VdbeSerialTypeLen(u32 serial_type){
+ if( serial_type>=12 ){
+ return (serial_type-12)/2;
+ }else{
+ static const u8 aSize[] = { 0, 1, 2, 3, 4, 6, 8, 8, 0, 0, 0, 0 };
+ return aSize[serial_type];
+ }
+}
+
+/*
+** If we are on an architecture with mixed-endian floating
+** points (ex: ARM7) then swap the lower 4 bytes with the
+** upper 4 bytes. Return the result.
+**
+** For most architectures, this is a no-op.
+**
+** (later): It is reported to me that the mixed-endian problem
+** on ARM7 is an issue with GCC, not with the ARM7 chip. It seems
+** that early versions of GCC stored the two words of a 64-bit
+** float in the wrong order. And that error has been propagated
+** ever since. The blame is not necessarily with GCC, though.
+** GCC might have just copying the problem from a prior compiler.
+** I am also told that newer versions of GCC that follow a different
+** ABI get the byte order right.
+**
+** Developers using SQLite on an ARM7 should compile and run their
+** application using -DSQLITE_DEBUG=1 at least once. With DEBUG
+** enabled, some asserts below will ensure that the byte order of
+** floating point values is correct.
+**
+** (2007-08-30) Frank van Vugt has studied this problem closely
+** and has send his findings to the SQLite developers. Frank
+** writes that some Linux kernels offer floating point hardware
+** emulation that uses only 32-bit mantissas instead of a full
+** 48-bits as required by the IEEE standard. (This is the
+** CONFIG_FPE_FASTFPE option.) On such systems, floating point
+** byte swapping becomes very complicated. To avoid problems,
+** the necessary byte swapping is carried out using a 64-bit integer
+** rather than a 64-bit float. Frank assures us that the code here
+** works for him. We, the developers, have no way to independently
+** verify this, but Frank seems to know what he is talking about
+** so we trust him.
+*/
+#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT
+static u64 floatSwap(u64 in){
+ union {
+ u64 r;
+ u32 i[2];
+ } u;
+ u32 t;
+
+ u.r = in;
+ t = u.i[0];
+ u.i[0] = u.i[1];
+ u.i[1] = t;
+ return u.r;
+}
+# define swapMixedEndianFloat(X) X = floatSwap(X)
+#else
+# define swapMixedEndianFloat(X)
+#endif
+
+/*
+** Write the serialized data blob for the value stored in pMem into
+** buf. It is assumed that the caller has allocated sufficient space.
+** Return the number of bytes written.
+**
+** nBuf is the amount of space left in buf[]. nBuf must always be
+** large enough to hold the entire field. Except, if the field is
+** a blob with a zero-filled tail, then buf[] might be just the right
+** size to hold everything except for the zero-filled tail. If buf[]
+** is only big enough to hold the non-zero prefix, then only write that
+** prefix into buf[]. But if buf[] is large enough to hold both the
+** prefix and the tail then write the prefix and set the tail to all
+** zeros.
+**
+** Return the number of bytes actually written into buf[]. The number
+** of bytes in the zero-filled tail is included in the return value only
+** if those bytes were zeroed in buf[].
+*/
+SQLITE_PRIVATE int sqlite3VdbeSerialPut(u8 *buf, int nBuf, Mem *pMem, int file_format){
+ u32 serial_type = sqlite3VdbeSerialType(pMem, file_format);
+ int len;
+
+ /* Integer and Real */
+ if( serial_type<=7 && serial_type>0 ){
+ u64 v;
+ int i;
+ if( serial_type==7 ){
+ assert( sizeof(v)==sizeof(pMem->r) );
+ memcpy(&v, &pMem->r, sizeof(v));
+ swapMixedEndianFloat(v);
+ }else{
+ v = pMem->u.i;
+ }
+ len = i = sqlite3VdbeSerialTypeLen(serial_type);
+ assert( len<=nBuf );
+ while( i-- ){
+ buf[i] = (v&0xFF);
+ v >>= 8;
+ }
+ return len;
+ }
+
+ /* String or blob */
+ if( serial_type>=12 ){
+ assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.i:0)
+ == sqlite3VdbeSerialTypeLen(serial_type) );
+ assert( pMem->n<=nBuf );
+ len = pMem->n;
+ memcpy(buf, pMem->z, len);
+ if( pMem->flags & MEM_Zero ){
+ len += pMem->u.i;
+ if( len>nBuf ){
+ len = nBuf;
+ }
+ memset(&buf[pMem->n], 0, len-pMem->n);
+ }
+ return len;
+ }
+
+ /* NULL or constants 0 or 1 */
+ return 0;
+}
+
+/*
+** Deserialize the data blob pointed to by buf as serial type serial_type
+** and store the result in pMem. Return the number of bytes read.
+*/
+SQLITE_PRIVATE int sqlite3VdbeSerialGet(
+ const unsigned char *buf, /* Buffer to deserialize from */
+ u32 serial_type, /* Serial type to deserialize */
+ Mem *pMem /* Memory cell to write value into */
+){
+ switch( serial_type ){
+ case 10: /* Reserved for future use */
+ case 11: /* Reserved for future use */
+ case 0: { /* NULL */
+ pMem->flags = MEM_Null;
+ break;
+ }
+ case 1: { /* 1-byte signed integer */
+ pMem->u.i = (signed char)buf[0];
+ pMem->flags = MEM_Int;
+ return 1;
+ }
+ case 2: { /* 2-byte signed integer */
+ pMem->u.i = (((signed char)buf[0])<<8) | buf[1];
+ pMem->flags = MEM_Int;
+ return 2;
+ }
+ case 3: { /* 3-byte signed integer */
+ pMem->u.i = (((signed char)buf[0])<<16) | (buf[1]<<8) | buf[2];
+ pMem->flags = MEM_Int;
+ return 3;
+ }
+ case 4: { /* 4-byte signed integer */
+ pMem->u.i = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3];
+ pMem->flags = MEM_Int;
+ return 4;
+ }
+ case 5: { /* 6-byte signed integer */
+ u64 x = (((signed char)buf[0])<<8) | buf[1];
+ u32 y = (buf[2]<<24) | (buf[3]<<16) | (buf[4]<<8) | buf[5];
+ x = (x<<32) | y;
+ pMem->u.i = *(i64*)&x;
+ pMem->flags = MEM_Int;
+ return 6;
+ }
+ case 6: /* 8-byte signed integer */
+ case 7: { /* IEEE floating point */
+ u64 x;
+ u32 y;
+#if !defined(NDEBUG) && !defined(SQLITE_OMIT_FLOATING_POINT)
+ /* Verify that integers and floating point values use the same
+ ** byte order. Or, that if SQLITE_MIXED_ENDIAN_64BIT_FLOAT is
+ ** defined that 64-bit floating point values really are mixed
+ ** endian.
+ */
+ static const u64 t1 = ((u64)0x3ff00000)<<32;
+ static const double r1 = 1.0;
+ u64 t2 = t1;
+ swapMixedEndianFloat(t2);
+ assert( sizeof(r1)==sizeof(t2) && memcmp(&r1, &t2, sizeof(r1))==0 );
+#endif
+
+ x = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3];
+ y = (buf[4]<<24) | (buf[5]<<16) | (buf[6]<<8) | buf[7];
+ x = (x<<32) | y;
+ if( serial_type==6 ){
+ pMem->u.i = *(i64*)&x;
+ pMem->flags = MEM_Int;
+ }else{
+ assert( sizeof(x)==8 && sizeof(pMem->r)==8 );
+ swapMixedEndianFloat(x);
+ memcpy(&pMem->r, &x, sizeof(x));
+ pMem->flags = sqlite3IsNaN(pMem->r) ? MEM_Null : MEM_Real;
+ }
+ return 8;
+ }
+ case 8: /* Integer 0 */
+ case 9: { /* Integer 1 */
+ pMem->u.i = serial_type-8;
+ pMem->flags = MEM_Int;
+ return 0;
+ }
+ default: {
+ int len = (serial_type-12)/2;
+ pMem->z = (char *)buf;
+ pMem->n = len;
+ pMem->xDel = 0;
+ if( serial_type&0x01 ){
+ pMem->flags = MEM_Str | MEM_Ephem;
+ }else{
+ pMem->flags = MEM_Blob | MEM_Ephem;
+ }
+ return len;
+ }
+ }
+ return 0;
+}
+
+
+/*
+** Given the nKey-byte encoding of a record in pKey[], parse the
+** record into a UnpackedRecord structure. Return a pointer to
+** that structure.
+**
+** The calling function might provide szSpace bytes of memory
+** space at pSpace. This space can be used to hold the returned
+** VDbeParsedRecord structure if it is large enough. If it is
+** not big enough, space is obtained from sqlite3_malloc().
+**
+** The returned structure should be closed by a call to
+** sqlite3VdbeDeleteUnpackedRecord().
+*/
+SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeRecordUnpack(
+ KeyInfo *pKeyInfo, /* Information about the record format */
+ int nKey, /* Size of the binary record */
+ const void *pKey, /* The binary record */
+ void *pSpace, /* Space available to hold resulting object */
+ int szSpace /* Size of pSpace[] in bytes */
+){
+ const unsigned char *aKey = (const unsigned char *)pKey;
+ UnpackedRecord *p;
+ int nByte;
+ int i, idx, d;
+ u32 szHdr;
+ Mem *pMem;
+
+ assert( sizeof(Mem)>sizeof(*p) );
+ nByte = sizeof(Mem)*(pKeyInfo->nField+2);
+ if( nByte>szSpace ){
+ p = sqlite3DbMallocRaw(pKeyInfo->db, nByte);
+ if( p==0 ) return 0;
+ p->needFree = 1;
+ }else{
+ p = pSpace;
+ p->needFree = 0;
+ }
+ p->pKeyInfo = pKeyInfo;
+ p->nField = pKeyInfo->nField + 1;
+ p->needDestroy = 1;
+ p->aMem = pMem = &((Mem*)p)[1];
+ idx = getVarint32(aKey, szHdr);
+ d = szHdr;
+ i = 0;
+ while( idx<szHdr && i<p->nField ){
+ u32 serial_type;
+
+ idx += getVarint32( aKey+idx, serial_type);
+ if( d>=nKey && sqlite3VdbeSerialTypeLen(serial_type)>0 ) break;
+ pMem->enc = pKeyInfo->enc;
+ pMem->db = pKeyInfo->db;
+ pMem->flags = 0;
+ pMem->zMalloc = 0;
+ d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem);
+ pMem++;
+ i++;
+ }
+ p->nField = i;
+ return (void*)p;
+}
+
+/*
+** This routine destroys a UnpackedRecord object
+*/
+SQLITE_PRIVATE void sqlite3VdbeDeleteUnpackedRecord(UnpackedRecord *p){
+ if( p ){
+ if( p->needDestroy ){
+ int i;
+ Mem *pMem;
+ for(i=0, pMem=p->aMem; i<p->nField; i++, pMem++){
+ if( pMem->zMalloc ){
+ sqlite3VdbeMemRelease(pMem);
+ }
+ }
+ }
+ if( p->needFree ){
+ sqlite3_free(p);
+ }
+ }
+}
+
+/*
+** This function compares the two table rows or index records
+** specified by {nKey1, pKey1} and pPKey2. It returns a negative, zero
+** or positive integer if {nKey1, pKey1} is less than, equal to or
+** greater than pPKey2. The {nKey1, pKey1} key must be a blob
+** created by th OP_MakeRecord opcode of the VDBE. The pPKey2
+** key must be a parsed key such as obtained from
+** sqlite3VdbeParseRecord.
+**
+** Key1 and Key2 do not have to contain the same number of fields.
+** But if the lengths differ, Key2 must be the shorter of the two.
+**
+** Historical note: In earlier versions of this routine both Key1
+** and Key2 were blobs obtained from OP_MakeRecord. But we found
+** that in typical use the same Key2 would be submitted multiple times
+** in a row. So an optimization was added to parse the Key2 key
+** separately and submit the parsed version. In this way, we avoid
+** parsing the same Key2 multiple times in a row.
+*/
+SQLITE_PRIVATE int sqlite3VdbeRecordCompare(
+ int nKey1, const void *pKey1,
+ UnpackedRecord *pPKey2
+){
+ u32 d1; /* Offset into aKey[] of next data element */
+ u32 idx1; /* Offset into aKey[] of next header element */
+ u32 szHdr1; /* Number of bytes in header */
+ int i = 0;
+ int nField;
+ int rc = 0;
+ const unsigned char *aKey1 = (const unsigned char *)pKey1;
+ KeyInfo *pKeyInfo;
+ Mem mem1;
+
+ pKeyInfo = pPKey2->pKeyInfo;
+ mem1.enc = pKeyInfo->enc;
+ mem1.db = pKeyInfo->db;
+ mem1.flags = 0;
+ mem1.zMalloc = 0;
+
+ idx1 = getVarint32(aKey1, szHdr1);
+ d1 = szHdr1;
+ nField = pKeyInfo->nField;
+ while( idx1<szHdr1 && i<pPKey2->nField ){
+ u32 serial_type1;
+
+ /* Read the serial types for the next element in each key. */
+ idx1 += getVarint32( aKey1+idx1, serial_type1 );
+ if( d1>=nKey1 && sqlite3VdbeSerialTypeLen(serial_type1)>0 ) break;
+
+ /* Extract the values to be compared.
+ */
+ d1 += sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1);
+
+ /* Do the comparison
+ */
+ rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i],
+ i<nField ? pKeyInfo->aColl[i] : 0);
+ if( rc!=0 ){
+ break;
+ }
+ i++;
+ }
+ if( mem1.zMalloc ) sqlite3VdbeMemRelease(&mem1);
+
+ /* One of the keys ran out of fields, but all the fields up to that point
+ ** were equal. If the incrKey flag is true, then the second key is
+ ** treated as larger.
+ */
+ if( rc==0 ){
+ if( pKeyInfo->incrKey ){
+ rc = -1;
+ }else if( !pKeyInfo->prefixIsEqual ){
+ if( d1<nKey1 ){
+ rc = 1;
+ }
+ }
+ }else if( pKeyInfo->aSortOrder && i<pKeyInfo->nField
+ && pKeyInfo->aSortOrder[i] ){
+ rc = -rc;
+ }
+
+ return rc;
+}
+
+/*
+** The argument is an index entry composed using the OP_MakeRecord opcode.
+** The last entry in this record should be an integer (specifically
+** an integer rowid). This routine returns the number of bytes in
+** that integer.
+*/
+SQLITE_PRIVATE int sqlite3VdbeIdxRowidLen(const u8 *aKey){
+ u32 szHdr; /* Size of the header */
+ u32 typeRowid; /* Serial type of the rowid */
+
+ (void)getVarint32(aKey, szHdr);
+ (void)getVarint32(&aKey[szHdr-1], typeRowid);
+ return sqlite3VdbeSerialTypeLen(typeRowid);
+}
+
+
+/*
+** pCur points at an index entry created using the OP_MakeRecord opcode.
+** Read the rowid (the last field in the record) and store it in *rowid.
+** Return SQLITE_OK if everything works, or an error code otherwise.
+*/
+SQLITE_PRIVATE int sqlite3VdbeIdxRowid(BtCursor *pCur, i64 *rowid){
+ i64 nCellKey = 0;
+ int rc;
+ u32 szHdr; /* Size of the header */
+ u32 typeRowid; /* Serial type of the rowid */
+ u32 lenRowid; /* Size of the rowid */
+ Mem m, v;
+
+ sqlite3BtreeKeySize(pCur, &nCellKey);
+ if( nCellKey<=0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ m.flags = 0;
+ m.db = 0;
+ m.zMalloc = 0;
+ rc = sqlite3VdbeMemFromBtree(pCur, 0, nCellKey, 1, &m);
+ if( rc ){
+ return rc;
+ }
+ (void)getVarint32((u8*)m.z, szHdr);
+ (void)getVarint32((u8*)&m.z[szHdr-1], typeRowid);
+ lenRowid = sqlite3VdbeSerialTypeLen(typeRowid);
+ sqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v);
+ *rowid = v.u.i;
+ sqlite3VdbeMemRelease(&m);
+ return SQLITE_OK;
+}
+
+/*
+** Compare the key of the index entry that cursor pC is point to against
+** the key string in pKey (of length nKey). Write into *pRes a number
+** that is negative, zero, or positive if pC is less than, equal to,
+** or greater than pKey. Return SQLITE_OK on success.
+**
+** pKey is either created without a rowid or is truncated so that it
+** omits the rowid at the end. The rowid at the end of the index entry
+** is ignored as well.
+*/
+SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(
+ Cursor *pC, /* The cursor to compare against */
+ UnpackedRecord *pUnpacked,
+ int nKey, const u8 *pKey, /* The key to compare */
+ int *res /* Write the comparison result here */
+){
+ i64 nCellKey = 0;
+ int rc;
+ BtCursor *pCur = pC->pCursor;
+ int lenRowid;
+ Mem m;
+ UnpackedRecord *pRec;
+ char zSpace[200];
+
+ sqlite3BtreeKeySize(pCur, &nCellKey);
+ if( nCellKey<=0 ){
+ *res = 0;
+ return SQLITE_OK;
+ }
+ m.db = 0;
+ m.flags = 0;
+ m.zMalloc = 0;
+ rc = sqlite3VdbeMemFromBtree(pC->pCursor, 0, nCellKey, 1, &m);
+ if( rc ){
+ return rc;
+ }
+ lenRowid = sqlite3VdbeIdxRowidLen((u8*)m.z);
+ if( !pUnpacked ){
+ pRec = sqlite3VdbeRecordUnpack(pC->pKeyInfo, nKey, pKey,
+ zSpace, sizeof(zSpace));
+ }else{
+ pRec = pUnpacked;
+ }
+ if( pRec==0 ){
+ return SQLITE_NOMEM;
+ }
+ *res = sqlite3VdbeRecordCompare(m.n-lenRowid, m.z, pRec);
+ if( !pUnpacked ){
+ sqlite3VdbeDeleteUnpackedRecord(pRec);
+ }
+ sqlite3VdbeMemRelease(&m);
+ return SQLITE_OK;
+}
+
+/*
+** This routine sets the value to be returned by subsequent calls to
+** sqlite3_changes() on the database handle 'db'.
+*/
+SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){
+ assert( sqlite3_mutex_held(db->mutex) );
+ db->nChange = nChange;
+ db->nTotalChange += nChange;
+}
+
+/*
+** Set a flag in the vdbe to update the change counter when it is finalised
+** or reset.
+*/
+SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe *v){
+ v->changeCntOn = 1;
+}
+
+/*
+** Mark every prepared statement associated with a database connection
+** as expired.
+**
+** An expired statement means that recompilation of the statement is
+** recommend. Statements expire when things happen that make their
+** programs obsolete. Removing user-defined functions or collating
+** sequences, or changing an authorization function are the types of
+** things that make prepared statements obsolete.
+*/
+SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3 *db){
+ Vdbe *p;
+ for(p = db->pVdbe; p; p=p->pNext){
+ p->expired = 1;
+ }
+}
+
+/*
+** Return the database associated with the Vdbe.
+*/
+SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe *v){
+ return v->db;
+}
+
+/************** End of vdbeaux.c *********************************************/
+/************** Begin file vdbeapi.c *****************************************/
+/*
+** 2004 May 26
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file contains code use to implement APIs that are part of the
+** VDBE.
+*/
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+/*
+** The following structure contains pointers to the end points of a
+** doubly-linked list of all compiled SQL statements that may be holding
+** buffers eligible for release when the sqlite3_release_memory() interface is
+** invoked. Access to this list is protected by the SQLITE_MUTEX_STATIC_LRU2
+** mutex.
+**
+** Statements are added to the end of this list when sqlite3_reset() is
+** called. They are removed either when sqlite3_step() or sqlite3_finalize()
+** is called. When statements are added to this list, the associated
+** register array (p->aMem[1..p->nMem]) may contain dynamic buffers that
+** can be freed using sqlite3VdbeReleaseMemory().
+**
+** When statements are added or removed from this list, the mutex
+** associated with the Vdbe being added or removed (Vdbe.db->mutex) is
+** already held. The LRU2 mutex is then obtained, blocking if necessary,
+** the linked-list pointers manipulated and the LRU2 mutex relinquished.
+*/
+struct StatementLruList {
+ Vdbe *pFirst;
+ Vdbe *pLast;
+};
+static struct StatementLruList sqlite3LruStatements;
+
+/*
+** Check that the list looks to be internally consistent. This is used
+** as part of an assert() statement as follows:
+**
+** assert( stmtLruCheck() );
+*/
+#ifndef NDEBUG
+static int stmtLruCheck(){
+ Vdbe *p;
+ for(p=sqlite3LruStatements.pFirst; p; p=p->pLruNext){
+ assert(p->pLruNext || p==sqlite3LruStatements.pLast);
+ assert(!p->pLruNext || p->pLruNext->pLruPrev==p);
+ assert(p->pLruPrev || p==sqlite3LruStatements.pFirst);
+ assert(!p->pLruPrev || p->pLruPrev->pLruNext==p);
+ }
+ return 1;
+}
+#endif
+
+/*
+** Add vdbe p to the end of the statement lru list. It is assumed that
+** p is not already part of the list when this is called. The lru list
+** is protected by the SQLITE_MUTEX_STATIC_LRU mutex.
+*/
+static void stmtLruAdd(Vdbe *p){
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU2));
+
+ if( p->pLruPrev || p->pLruNext || sqlite3LruStatements.pFirst==p ){
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU2));
+ return;
+ }
+
+ assert( stmtLruCheck() );
+
+ if( !sqlite3LruStatements.pFirst ){
+ assert( !sqlite3LruStatements.pLast );
+ sqlite3LruStatements.pFirst = p;
+ sqlite3LruStatements.pLast = p;
+ }else{
+ assert( !sqlite3LruStatements.pLast->pLruNext );
+ p->pLruPrev = sqlite3LruStatements.pLast;
+ sqlite3LruStatements.pLast->pLruNext = p;
+ sqlite3LruStatements.pLast = p;
+ }
+
+ assert( stmtLruCheck() );
+
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU2));
+}
+
+/*
+** Assuming the SQLITE_MUTEX_STATIC_LRU2 mutext is already held, remove
+** statement p from the least-recently-used statement list. If the
+** statement is not currently part of the list, this call is a no-op.
+*/
+static void stmtLruRemoveNomutex(Vdbe *p){
+ if( p->pLruPrev || p->pLruNext || p==sqlite3LruStatements.pFirst ){
+ assert( stmtLruCheck() );
+ if( p->pLruNext ){
+ p->pLruNext->pLruPrev = p->pLruPrev;
+ }else{
+ sqlite3LruStatements.pLast = p->pLruPrev;
+ }
+ if( p->pLruPrev ){
+ p->pLruPrev->pLruNext = p->pLruNext;
+ }else{
+ sqlite3LruStatements.pFirst = p->pLruNext;
+ }
+ p->pLruNext = 0;
+ p->pLruPrev = 0;
+ assert( stmtLruCheck() );
+ }
+}
+
+/*
+** Assuming the SQLITE_MUTEX_STATIC_LRU2 mutext is not held, remove
+** statement p from the least-recently-used statement list. If the
+** statement is not currently part of the list, this call is a no-op.
+*/
+static void stmtLruRemove(Vdbe *p){
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU2));
+ stmtLruRemoveNomutex(p);
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU2));
+}
+
+/*
+** Try to release n bytes of memory by freeing buffers associated
+** with the memory registers of currently unused vdbes.
+*/
+SQLITE_PRIVATE int sqlite3VdbeReleaseMemory(int n){
+ Vdbe *p;
+ Vdbe *pNext;
+ int nFree = 0;
+
+ sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU2));
+ for(p=sqlite3LruStatements.pFirst; p && nFree<n; p=pNext){
+ pNext = p->pLruNext;
+
+ /* For each statement handle in the lru list, attempt to obtain the
+ ** associated database mutex. If it cannot be obtained, continue
+ ** to the next statement handle. It is not possible to block on
+ ** the database mutex - that could cause deadlock.
+ */
+ if( SQLITE_OK==sqlite3_mutex_try(p->db->mutex) ){
+ nFree += sqlite3VdbeReleaseBuffers(p);
+ stmtLruRemoveNomutex(p);
+ sqlite3_mutex_leave(p->db->mutex);
+ }
+ }
+ sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU2));
+
+ return nFree;
+}
+
+/*
+** Call sqlite3Reprepare() on the statement. Remove it from the
+** lru list before doing so, as Reprepare() will free all the
+** memory register buffers anyway.
+*/
+int vdbeReprepare(Vdbe *p){
+ stmtLruRemove(p);
+ return sqlite3Reprepare(p);
+}
+
+#else /* !SQLITE_ENABLE_MEMORY_MANAGEMENT */
+ #define stmtLruRemove(x)
+ #define stmtLruAdd(x)
+ #define vdbeReprepare(x) sqlite3Reprepare(x)
+#endif
+
+
+/*
+** Return TRUE (non-zero) of the statement supplied as an argument needs
+** to be recompiled. A statement needs to be recompiled whenever the
+** execution environment changes in a way that would alter the program
+** that sqlite3_prepare() generates. For example, if new functions or
+** collating sequences are registered or if an authorizer function is
+** added or changed.
+*/
+SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe*)pStmt;
+ return p==0 || p->expired;
+}
+
+/*
+** The following routine destroys a virtual machine that is created by
+** the sqlite3_compile() routine. The integer returned is an SQLITE_
+** success/failure code that describes the result of executing the virtual
+** machine.
+**
+** This routine sets the error code and string returned by
+** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16().
+*/
+SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){
+ int rc;
+ if( pStmt==0 ){
+ rc = SQLITE_OK;
+ }else{
+ Vdbe *v = (Vdbe*)pStmt;
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = v->db->mutex;
+#endif
+ sqlite3_mutex_enter(mutex);
+ stmtLruRemove(v);
+ rc = sqlite3VdbeFinalize(v);
+ sqlite3_mutex_leave(mutex);
+ }
+ return rc;
+}
+
+/*
+** Terminate the current execution of an SQL statement and reset it
+** back to its starting state so that it can be reused. A success code from
+** the prior execution is returned.
+**
+** This routine sets the error code and string returned by
+** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16().
+*/
+SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt){
+ int rc;
+ if( pStmt==0 ){
+ rc = SQLITE_OK;
+ }else{
+ Vdbe *v = (Vdbe*)pStmt;
+ sqlite3_mutex_enter(v->db->mutex);
+ rc = sqlite3VdbeReset(v, 1);
+ stmtLruAdd(v);
+ sqlite3VdbeMakeReady(v, -1, 0, 0, 0);
+ assert( (rc & (v->db->errMask))==rc );
+ sqlite3_mutex_leave(v->db->mutex);
+ }
+ return rc;
+}
+
+/*
+** Set all the parameters in the compiled SQL statement to NULL.
+*/
+SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){
+ int i;
+ int rc = SQLITE_OK;
+ Vdbe *p = (Vdbe*)pStmt;
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex;
+#endif
+ sqlite3_mutex_enter(mutex);
+ for(i=0; i<p->nVar; i++){
+ sqlite3VdbeMemRelease(&p->aVar[i]);
+ p->aVar[i].flags = MEM_Null;
+ }
+ sqlite3_mutex_leave(mutex);
+ return rc;
+}
+
+
+/**************************** sqlite3_value_ *******************************
+** The following routines extract information from a Mem or sqlite3_value
+** structure.
+*/
+SQLITE_API const void *sqlite3_value_blob(sqlite3_value *pVal){
+ Mem *p = (Mem*)pVal;
+ if( p->flags & (MEM_Blob|MEM_Str) ){
+ sqlite3VdbeMemExpandBlob(p);
+ p->flags &= ~MEM_Str;
+ p->flags |= MEM_Blob;
+ return p->z;
+ }else{
+ return sqlite3_value_text(pVal);
+ }
+}
+SQLITE_API int sqlite3_value_bytes(sqlite3_value *pVal){
+ return sqlite3ValueBytes(pVal, SQLITE_UTF8);
+}
+SQLITE_API int sqlite3_value_bytes16(sqlite3_value *pVal){
+ return sqlite3ValueBytes(pVal, SQLITE_UTF16NATIVE);
+}
+SQLITE_API double sqlite3_value_double(sqlite3_value *pVal){
+ return sqlite3VdbeRealValue((Mem*)pVal);
+}
+SQLITE_API int sqlite3_value_int(sqlite3_value *pVal){
+ return sqlite3VdbeIntValue((Mem*)pVal);
+}
+SQLITE_API sqlite_int64 sqlite3_value_int64(sqlite3_value *pVal){
+ return sqlite3VdbeIntValue((Mem*)pVal);
+}
+SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value *pVal){
+ return (const unsigned char *)sqlite3ValueText(pVal, SQLITE_UTF8);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API const void *sqlite3_value_text16(sqlite3_value* pVal){
+ return sqlite3ValueText(pVal, SQLITE_UTF16NATIVE);
+}
+SQLITE_API const void *sqlite3_value_text16be(sqlite3_value *pVal){
+ return sqlite3ValueText(pVal, SQLITE_UTF16BE);
+}
+SQLITE_API const void *sqlite3_value_text16le(sqlite3_value *pVal){
+ return sqlite3ValueText(pVal, SQLITE_UTF16LE);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+SQLITE_API int sqlite3_value_type(sqlite3_value* pVal){
+ return pVal->type;
+}
+
+/**************************** sqlite3_result_ *******************************
+** The following routines are used by user-defined functions to specify
+** the function result.
+*/
+SQLITE_API void sqlite3_result_blob(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ assert( n>=0 );
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, 0, xDel);
+}
+SQLITE_API void sqlite3_result_double(sqlite3_context *pCtx, double rVal){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetDouble(&pCtx->s, rVal);
+}
+SQLITE_API void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ pCtx->isError = SQLITE_ERROR;
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF8, SQLITE_TRANSIENT);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ pCtx->isError = SQLITE_ERROR;
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT);
+}
+#endif
+SQLITE_API void sqlite3_result_int(sqlite3_context *pCtx, int iVal){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetInt64(&pCtx->s, (i64)iVal);
+}
+SQLITE_API void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetInt64(&pCtx->s, iVal);
+}
+SQLITE_API void sqlite3_result_null(sqlite3_context *pCtx){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetNull(&pCtx->s);
+}
+SQLITE_API void sqlite3_result_text(
+ sqlite3_context *pCtx,
+ const char *z,
+ int n,
+ void (*xDel)(void *)
+){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF8, xDel);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API void sqlite3_result_text16(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16NATIVE, xDel);
+}
+SQLITE_API void sqlite3_result_text16be(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16BE, xDel);
+}
+SQLITE_API void sqlite3_result_text16le(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16LE, xDel);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+SQLITE_API void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemCopy(&pCtx->s, pValue);
+}
+SQLITE_API void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetZeroBlob(&pCtx->s, n);
+}
+SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
+ pCtx->isError = errCode;
+}
+
+/* Force an SQLITE_TOOBIG error. */
+SQLITE_API void sqlite3_result_error_toobig(sqlite3_context *pCtx){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ pCtx->isError = SQLITE_TOOBIG;
+ sqlite3VdbeMemSetStr(&pCtx->s, "string or blob too big", -1,
+ SQLITE_UTF8, SQLITE_STATIC);
+}
+
+/* An SQLITE_NOMEM error. */
+SQLITE_API void sqlite3_result_error_nomem(sqlite3_context *pCtx){
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ sqlite3VdbeMemSetNull(&pCtx->s);
+ pCtx->isError = SQLITE_NOMEM;
+ pCtx->s.db->mallocFailed = 1;
+}
+
+/*
+** Execute the statement pStmt, either until a row of data is ready, the
+** statement is completely executed or an error occurs.
+**
+** This routine implements the bulk of the logic behind the sqlite_step()
+** API. The only thing omitted is the automatic recompile if a
+** schema change has occurred. That detail is handled by the
+** outer sqlite3_step() wrapper procedure.
+*/
+static int sqlite3Step(Vdbe *p){
+ sqlite3 *db;
+ int rc;
+
+ assert(p);
+ if( p->magic!=VDBE_MAGIC_RUN ){
+ return SQLITE_MISUSE;
+ }
+
+ /* Assert that malloc() has not failed */
+ db = p->db;
+ assert( !db->mallocFailed );
+
+ if( p->aborted ){
+ return SQLITE_ABORT;
+ }
+ if( p->pc<=0 && p->expired ){
+ if( p->rc==SQLITE_OK ){
+ p->rc = SQLITE_SCHEMA;
+ }
+ rc = SQLITE_ERROR;
+ goto end_of_step;
+ }
+ if( sqlite3SafetyOn(db) ){
+ p->rc = SQLITE_MISUSE;
+ return SQLITE_MISUSE;
+ }
+ if( p->pc<0 ){
+ /* If there are no other statements currently running, then
+ ** reset the interrupt flag. This prevents a call to sqlite3_interrupt
+ ** from interrupting a statement that has not yet started.
+ */
+ if( db->activeVdbeCnt==0 ){
+ db->u1.isInterrupted = 0;
+ }
+
+#ifndef SQLITE_OMIT_TRACE
+ if( db->xProfile && !db->init.busy ){
+ double rNow;
+ sqlite3OsCurrentTime(db->pVfs, &rNow);
+ p->startTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0;
+ }
+#endif
+
+ db->activeVdbeCnt++;
+ p->pc = 0;
+ stmtLruRemove(p);
+ }
+#ifndef SQLITE_OMIT_EXPLAIN
+ if( p->explain ){
+ rc = sqlite3VdbeList(p);
+ }else
+#endif /* SQLITE_OMIT_EXPLAIN */
+ {
+ rc = sqlite3VdbeExec(p);
+ }
+
+ if( sqlite3SafetyOff(db) ){
+ rc = SQLITE_MISUSE;
+ }
+
+#ifndef SQLITE_OMIT_TRACE
+ /* Invoke the profile callback if there is one
+ */
+ if( rc!=SQLITE_ROW && db->xProfile && !db->init.busy && p->nOp>0
+ && p->aOp[0].opcode==OP_Trace && p->aOp[0].p4.z!=0 ){
+ double rNow;
+ u64 elapseTime;
+
+ sqlite3OsCurrentTime(db->pVfs, &rNow);
+ elapseTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0 - p->startTime;
+ db->xProfile(db->pProfileArg, p->aOp[0].p4.z, elapseTime);
+ }
+#endif
+
+ sqlite3Error(p->db, rc, 0);
+ p->rc = sqlite3ApiExit(p->db, p->rc);
+end_of_step:
+ assert( (rc&0xff)==rc );
+ if( p->zSql && (rc&0xff)<SQLITE_ROW ){
+ /* This behavior occurs if sqlite3_prepare_v2() was used to build
+ ** the prepared statement. Return error codes directly */
+ sqlite3Error(p->db, p->rc, 0);
+ return p->rc;
+ }else{
+ /* This is for legacy sqlite3_prepare() builds and when the code
+ ** is SQLITE_ROW or SQLITE_DONE */
+ return rc;
+ }
+}
+
+/*
+** This is the top-level implementation of sqlite3_step(). Call
+** sqlite3Step() to do most of the work. If a schema error occurs,
+** call sqlite3Reprepare() and try again.
+*/
+#ifdef SQLITE_OMIT_PARSER
+SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){
+ int rc = SQLITE_MISUSE;
+ if( pStmt ){
+ Vdbe *v;
+ v = (Vdbe*)pStmt;
+ sqlite3_mutex_enter(v->db->mutex);
+ rc = sqlite3Step(v);
+ sqlite3_mutex_leave(v->db->mutex);
+ }
+ return rc;
+}
+#else
+SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){
+ int rc = SQLITE_MISUSE;
+ if( pStmt ){
+ int cnt = 0;
+ Vdbe *v = (Vdbe*)pStmt;
+ sqlite3 *db = v->db;
+ sqlite3_mutex_enter(db->mutex);
+ while( (rc = sqlite3Step(v))==SQLITE_SCHEMA
+ && cnt++ < 5
+ && vdbeReprepare(v) ){
+ sqlite3_reset(pStmt);
+ v->expired = 0;
+ }
+ if( rc==SQLITE_SCHEMA && v->zSql && db->pErr ){
+ /* This case occurs after failing to recompile an sql statement.
+ ** The error message from the SQL compiler has already been loaded
+ ** into the database handle. This block copies the error message
+ ** from the database handle into the statement and sets the statement
+ ** program counter to 0 to ensure that when the statement is
+ ** finalized or reset the parser error message is available via
+ ** sqlite3_errmsg() and sqlite3_errcode().
+ */
+ const char *zErr = (const char *)sqlite3_value_text(db->pErr);
+ sqlite3_free(v->zErrMsg);
+ if( !db->mallocFailed ){
+ v->zErrMsg = sqlite3DbStrDup(db, zErr);
+ } else {
+ v->zErrMsg = 0;
+ v->rc = SQLITE_NOMEM;
+ }
+ }
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ }
+ return rc;
+}
+#endif
+
+/*
+** Extract the user data from a sqlite3_context structure and return a
+** pointer to it.
+*/
+SQLITE_API void *sqlite3_user_data(sqlite3_context *p){
+ assert( p && p->pFunc );
+ return p->pFunc->pUserData;
+}
+
+/*
+** Extract the user data from a sqlite3_context structure and return a
+** pointer to it.
+*/
+SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){
+ assert( p && p->pFunc );
+ return p->s.db;
+}
+
+/*
+** The following is the implementation of an SQL function that always
+** fails with an error message stating that the function is used in the
+** wrong context. The sqlite3_overload_function() API might construct
+** SQL function that use this routine so that the functions will exist
+** for name resolution but are actually overloaded by the xFindFunction
+** method of virtual tables.
+*/
+SQLITE_PRIVATE void sqlite3InvalidFunction(
+ sqlite3_context *context, /* The function calling context */
+ int argc, /* Number of arguments to the function */
+ sqlite3_value **argv /* Value of each argument */
+){
+ const char *zName = context->pFunc->zName;
+ char *zErr;
+ zErr = sqlite3MPrintf(0,
+ "unable to use function %s in the requested context", zName);
+ sqlite3_result_error(context, zErr, -1);
+ sqlite3_free(zErr);
+}
+
+/*
+** Allocate or return the aggregate context for a user function. A new
+** context is allocated on the first call. Subsequent calls return the
+** same context that was returned on prior calls.
+*/
+SQLITE_API void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){
+ Mem *pMem;
+ assert( p && p->pFunc && p->pFunc->xStep );
+ assert( sqlite3_mutex_held(p->s.db->mutex) );
+ pMem = p->pMem;
+ if( (pMem->flags & MEM_Agg)==0 ){
+ if( nByte==0 ){
+ sqlite3VdbeMemReleaseExternal(pMem);
+ pMem->flags = MEM_Null;
+ pMem->z = 0;
+ }else{
+ sqlite3VdbeMemGrow(pMem, nByte, 0);
+ pMem->flags = MEM_Agg;
+ pMem->u.pDef = p->pFunc;
+ if( pMem->z ){
+ memset(pMem->z, 0, nByte);
+ }
+ }
+ }
+ return (void*)pMem->z;
+}
+
+/*
+** Return the auxilary data pointer, if any, for the iArg'th argument to
+** the user-function defined by pCtx.
+*/
+SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){
+ VdbeFunc *pVdbeFunc;
+
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ pVdbeFunc = pCtx->pVdbeFunc;
+ if( !pVdbeFunc || iArg>=pVdbeFunc->nAux || iArg<0 ){
+ return 0;
+ }
+ return pVdbeFunc->apAux[iArg].pAux;
+}
+
+/*
+** Set the auxilary data pointer and delete function, for the iArg'th
+** argument to the user-function defined by pCtx. Any previous value is
+** deleted by calling the delete function specified when it was set.
+*/
+SQLITE_API void sqlite3_set_auxdata(
+ sqlite3_context *pCtx,
+ int iArg,
+ void *pAux,
+ void (*xDelete)(void*)
+){
+ struct AuxData *pAuxData;
+ VdbeFunc *pVdbeFunc;
+ if( iArg<0 ) goto failed;
+
+ assert( sqlite3_mutex_held(pCtx->s.db->mutex) );
+ pVdbeFunc = pCtx->pVdbeFunc;
+ if( !pVdbeFunc || pVdbeFunc->nAux<=iArg ){
+ int nAux = (pVdbeFunc ? pVdbeFunc->nAux : 0);
+ int nMalloc = sizeof(VdbeFunc) + sizeof(struct AuxData)*iArg;
+ pVdbeFunc = sqlite3DbRealloc(pCtx->s.db, pVdbeFunc, nMalloc);
+ if( !pVdbeFunc ){
+ goto failed;
+ }
+ pCtx->pVdbeFunc = pVdbeFunc;
+ memset(&pVdbeFunc->apAux[nAux], 0, sizeof(struct AuxData)*(iArg+1-nAux));
+ pVdbeFunc->nAux = iArg+1;
+ pVdbeFunc->pFunc = pCtx->pFunc;
+ }
+
+ pAuxData = &pVdbeFunc->apAux[iArg];
+ if( pAuxData->pAux && pAuxData->xDelete ){
+ pAuxData->xDelete(pAuxData->pAux);
+ }
+ pAuxData->pAux = pAux;
+ pAuxData->xDelete = xDelete;
+ return;
+
+failed:
+ if( xDelete ){
+ xDelete(pAux);
+ }
+}
+
+/*
+** Return the number of times the Step function of a aggregate has been
+** called.
+**
+** This function is deprecated. Do not use it for new code. It is
+** provide only to avoid breaking legacy code. New aggregate function
+** implementations should keep their own counts within their aggregate
+** context.
+*/
+SQLITE_API int sqlite3_aggregate_count(sqlite3_context *p){
+ assert( p && p->pFunc && p->pFunc->xStep );
+ return p->pMem->n;
+}
+
+/*
+** Return the number of columns in the result set for the statement pStmt.
+*/
+SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){
+ Vdbe *pVm = (Vdbe *)pStmt;
+ return pVm ? pVm->nResColumn : 0;
+}
+
+/*
+** Return the number of values available from the current row of the
+** currently executing statement pStmt.
+*/
+SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt){
+ Vdbe *pVm = (Vdbe *)pStmt;
+ if( pVm==0 || pVm->pResultSet==0 ) return 0;
+ return pVm->nResColumn;
+}
+
+
+/*
+** Check to see if column iCol of the given statement is valid. If
+** it is, return a pointer to the Mem for the value of that column.
+** If iCol is not valid, return a pointer to a Mem which has a value
+** of NULL.
+*/
+static Mem *columnMem(sqlite3_stmt *pStmt, int i){
+ Vdbe *pVm;
+ int vals;
+ Mem *pOut;
+
+ pVm = (Vdbe *)pStmt;
+ if( pVm && pVm->pResultSet!=0 && i<pVm->nResColumn && i>=0 ){
+ sqlite3_mutex_enter(pVm->db->mutex);
+ vals = sqlite3_data_count(pStmt);
+ pOut = &pVm->pResultSet[i];
+ }else{
+ static const Mem nullMem = {{0}, 0.0, 0, "", 0, MEM_Null, SQLITE_NULL, 0, 0, 0 };
+ if( pVm->db ){
+ sqlite3_mutex_enter(pVm->db->mutex);
+ sqlite3Error(pVm->db, SQLITE_RANGE, 0);
+ }
+ pOut = (Mem*)&nullMem;
+ }
+ return pOut;
+}
+
+/*
+** This function is called after invoking an sqlite3_value_XXX function on a
+** column value (i.e. a value returned by evaluating an SQL expression in the
+** select list of a SELECT statement) that may cause a malloc() failure. If
+** malloc() has failed, the threads mallocFailed flag is cleared and the result
+** code of statement pStmt set to SQLITE_NOMEM.
+**
+** Specifically, this is called from within:
+**
+** sqlite3_column_int()
+** sqlite3_column_int64()
+** sqlite3_column_text()
+** sqlite3_column_text16()
+** sqlite3_column_real()
+** sqlite3_column_bytes()
+** sqlite3_column_bytes16()
+**
+** But not for sqlite3_column_blob(), which never calls malloc().
+*/
+static void columnMallocFailure(sqlite3_stmt *pStmt)
+{
+ /* If malloc() failed during an encoding conversion within an
+ ** sqlite3_column_XXX API, then set the return code of the statement to
+ ** SQLITE_NOMEM. The next call to _step() (if any) will return SQLITE_ERROR
+ ** and _finalize() will return NOMEM.
+ */
+ Vdbe *p = (Vdbe *)pStmt;
+ if( p ){
+ p->rc = sqlite3ApiExit(p->db, p->rc);
+ sqlite3_mutex_leave(p->db->mutex);
+ }
+}
+
+/**************************** sqlite3_column_ *******************************
+** The following routines are used to access elements of the current row
+** in the result set.
+*/
+SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){
+ const void *val;
+ val = sqlite3_value_blob( columnMem(pStmt,i) );
+ /* Even though there is no encoding conversion, value_blob() might
+ ** need to call malloc() to expand the result of a zeroblob()
+ ** expression.
+ */
+ columnMallocFailure(pStmt);
+ return val;
+}
+SQLITE_API int sqlite3_column_bytes(sqlite3_stmt *pStmt, int i){
+ int val = sqlite3_value_bytes( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return val;
+}
+SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt *pStmt, int i){
+ int val = sqlite3_value_bytes16( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return val;
+}
+SQLITE_API double sqlite3_column_double(sqlite3_stmt *pStmt, int i){
+ double val = sqlite3_value_double( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return val;
+}
+SQLITE_API int sqlite3_column_int(sqlite3_stmt *pStmt, int i){
+ int val = sqlite3_value_int( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return val;
+}
+SQLITE_API sqlite_int64 sqlite3_column_int64(sqlite3_stmt *pStmt, int i){
+ sqlite_int64 val = sqlite3_value_int64( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return val;
+}
+SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int i){
+ const unsigned char *val = sqlite3_value_text( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return val;
+}
+SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt *pStmt, int i){
+ sqlite3_value *pOut = columnMem(pStmt, i);
+ columnMallocFailure(pStmt);
+ return pOut;
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt *pStmt, int i){
+ const void *val = sqlite3_value_text16( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return val;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+SQLITE_API int sqlite3_column_type(sqlite3_stmt *pStmt, int i){
+ int iType = sqlite3_value_type( columnMem(pStmt,i) );
+ columnMallocFailure(pStmt);
+ return iType;
+}
+
+/* The following function is experimental and subject to change or
+** removal */
+/*int sqlite3_column_numeric_type(sqlite3_stmt *pStmt, int i){
+** return sqlite3_value_numeric_type( columnMem(pStmt,i) );
+**}
+*/
+
+/*
+** Convert the N-th element of pStmt->pColName[] into a string using
+** xFunc() then return that string. If N is out of range, return 0.
+**
+** There are up to 5 names for each column. useType determines which
+** name is returned. Here are the names:
+**
+** 0 The column name as it should be displayed for output
+** 1 The datatype name for the column
+** 2 The name of the database that the column derives from
+** 3 The name of the table that the column derives from
+** 4 The name of the table column that the result column derives from
+**
+** If the result is not a simple column reference (if it is an expression
+** or a constant) then useTypes 2, 3, and 4 return NULL.
+*/
+static const void *columnName(
+ sqlite3_stmt *pStmt,
+ int N,
+ const void *(*xFunc)(Mem*),
+ int useType
+){
+ const void *ret = 0;
+ Vdbe *p = (Vdbe *)pStmt;
+ int n;
+
+
+ if( p!=0 ){
+ n = sqlite3_column_count(pStmt);
+ if( N<n && N>=0 ){
+ N += useType*n;
+ sqlite3_mutex_enter(p->db->mutex);
+ ret = xFunc(&p->aColName[N]);
+
+ /* A malloc may have failed inside of the xFunc() call. If this
+ ** is the case, clear the mallocFailed flag and return NULL.
+ */
+ if( p->db && p->db->mallocFailed ){
+ p->db->mallocFailed = 0;
+ ret = 0;
+ }
+ sqlite3_mutex_leave(p->db->mutex);
+ }
+ }
+ return ret;
+}
+
+/*
+** Return the name of the Nth column of the result set returned by SQL
+** statement pStmt.
+*/
+SQLITE_API const char *sqlite3_column_name(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_NAME);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_NAME);
+}
+#endif
+
+/*
+** Constraint: If you have ENABLE_COLUMN_METADATA then you must
+** not define OMIT_DECLTYPE.
+*/
+#if defined(SQLITE_OMIT_DECLTYPE) && defined(SQLITE_ENABLE_COLUMN_METADATA)
+# error "Must not define both SQLITE_OMIT_DECLTYPE \
+ and SQLITE_ENABLE_COLUMN_METADATA"
+#endif
+
+#ifndef SQLITE_OMIT_DECLTYPE
+/*
+** Return the column declaration type (if applicable) of the 'i'th column
+** of the result set of SQL statement pStmt.
+*/
+SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_DECLTYPE);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_DECLTYPE);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+#endif /* SQLITE_OMIT_DECLTYPE */
+
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+/*
+** Return the name of the database from which a result column derives.
+** NULL is returned if the result column is an expression or constant or
+** anything else which is not an unabiguous reference to a database column.
+*/
+SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_DATABASE);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_DATABASE);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** Return the name of the table from which a result column derives.
+** NULL is returned if the result column is an expression or constant or
+** anything else which is not an unabiguous reference to a database column.
+*/
+SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_TABLE);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_TABLE);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** Return the name of the table column from which a result column derives.
+** NULL is returned if the result column is an expression or constant or
+** anything else which is not an unabiguous reference to a database column.
+*/
+SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_COLUMN);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(
+ pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_COLUMN);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+#endif /* SQLITE_ENABLE_COLUMN_METADATA */
+
+
+/******************************* sqlite3_bind_ ***************************
+**
+** Routines used to attach values to wildcards in a compiled SQL statement.
+*/
+/*
+** Unbind the value bound to variable i in virtual machine p. This is the
+** the same as binding a NULL value to the column. If the "i" parameter is
+** out of range, then SQLITE_RANGE is returned. Othewise SQLITE_OK.
+**
+** The error code stored in database p->db is overwritten with the return
+** value in any case.
+*/
+static int vdbeUnbind(Vdbe *p, int i){
+ Mem *pVar;
+ if( p==0 || p->magic!=VDBE_MAGIC_RUN || p->pc>=0 ){
+ if( p ) sqlite3Error(p->db, SQLITE_MISUSE, 0);
+ return SQLITE_MISUSE;
+ }
+ if( i<1 || i>p->nVar ){
+ sqlite3Error(p->db, SQLITE_RANGE, 0);
+ return SQLITE_RANGE;
+ }
+ i--;
+ pVar = &p->aVar[i];
+ sqlite3VdbeMemRelease(pVar);
+ pVar->flags = MEM_Null;
+ sqlite3Error(p->db, SQLITE_OK, 0);
+ return SQLITE_OK;
+}
+
+/*
+** Bind a text or BLOB value.
+*/
+static int bindText(
+ sqlite3_stmt *pStmt, /* The statement to bind against */
+ int i, /* Index of the parameter to bind */
+ const void *zData, /* Pointer to the data to be bound */
+ int nData, /* Number of bytes of data to be bound */
+ void (*xDel)(void*), /* Destructor for the data */
+ int encoding /* Encoding for the data */
+){
+ Vdbe *p = (Vdbe *)pStmt;
+ Mem *pVar;
+ int rc;
+
+ if( p==0 ){
+ return SQLITE_MISUSE;
+ }
+ sqlite3_mutex_enter(p->db->mutex);
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK && zData!=0 ){
+ pVar = &p->aVar[i-1];
+ rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel);
+ if( rc==SQLITE_OK && encoding!=0 ){
+ rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db));
+ }
+ sqlite3Error(p->db, rc, 0);
+ rc = sqlite3ApiExit(p->db, rc);
+ }
+ sqlite3_mutex_leave(p->db->mutex);
+ return rc;
+}
+
+
+/*
+** Bind a blob value to an SQL statement variable.
+*/
+SQLITE_API int sqlite3_bind_blob(
+ sqlite3_stmt *pStmt,
+ int i,
+ const void *zData,
+ int nData,
+ void (*xDel)(void*)
+){
+ return bindText(pStmt, i, zData, nData, xDel, 0);
+}
+SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){
+ int rc;
+ Vdbe *p = (Vdbe *)pStmt;
+ sqlite3_mutex_enter(p->db->mutex);
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK ){
+ sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue);
+ }
+ sqlite3_mutex_leave(p->db->mutex);
+ return rc;
+}
+SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){
+ return sqlite3_bind_int64(p, i, (i64)iValue);
+}
+SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
+ int rc;
+ Vdbe *p = (Vdbe *)pStmt;
+ sqlite3_mutex_enter(p->db->mutex);
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK ){
+ sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue);
+ }
+ sqlite3_mutex_leave(p->db->mutex);
+ return rc;
+}
+SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){
+ int rc;
+ Vdbe *p = (Vdbe*)pStmt;
+ sqlite3_mutex_enter(p->db->mutex);
+ rc = vdbeUnbind(p, i);
+ sqlite3_mutex_leave(p->db->mutex);
+ return rc;
+}
+SQLITE_API int sqlite3_bind_text(
+ sqlite3_stmt *pStmt,
+ int i,
+ const char *zData,
+ int nData,
+ void (*xDel)(void*)
+){
+ return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF8);
+}
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API int sqlite3_bind_text16(
+ sqlite3_stmt *pStmt,
+ int i,
+ const void *zData,
+ int nData,
+ void (*xDel)(void*)
+){
+ return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF16NATIVE);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){
+ int rc;
+ Vdbe *p = (Vdbe *)pStmt;
+ sqlite3_mutex_enter(p->db->mutex);
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3VdbeMemCopy(&p->aVar[i-1], pValue);
+ }
+ rc = sqlite3ApiExit(p->db, rc);
+ sqlite3_mutex_leave(p->db->mutex);
+ return rc;
+}
+SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){
+ int rc;
+ Vdbe *p = (Vdbe *)pStmt;
+ sqlite3_mutex_enter(p->db->mutex);
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK ){
+ sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n);
+ }
+ sqlite3_mutex_leave(p->db->mutex);
+ return rc;
+}
+
+/*
+** Return the number of wildcards that can be potentially bound to.
+** This routine is added to support DBD::SQLite.
+*/
+SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe*)pStmt;
+ return p ? p->nVar : 0;
+}
+
+/*
+** Create a mapping from variable numbers to variable names
+** in the Vdbe.azVar[] array, if such a mapping does not already
+** exist.
+*/
+static void createVarMap(Vdbe *p){
+ if( !p->okVar ){
+ sqlite3_mutex_enter(p->db->mutex);
+ if( !p->okVar ){
+ int j;
+ Op *pOp;
+ for(j=0, pOp=p->aOp; j<p->nOp; j++, pOp++){
+ if( pOp->opcode==OP_Variable ){
+ assert( pOp->p1>0 && pOp->p1<=p->nVar );
+ p->azVar[pOp->p1-1] = pOp->p4.z;
+ }
+ }
+ p->okVar = 1;
+ }
+ sqlite3_mutex_leave(p->db->mutex);
+ }
+}
+
+/*
+** Return the name of a wildcard parameter. Return NULL if the index
+** is out of range or if the wildcard is unnamed.
+**
+** The result is always UTF-8.
+*/
+SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){
+ Vdbe *p = (Vdbe*)pStmt;
+ if( p==0 || i<1 || i>p->nVar ){
+ return 0;
+ }
+ createVarMap(p);
+ return p->azVar[i-1];
+}
+
+/*
+** Given a wildcard parameter name, return the index of the variable
+** with that name. If there is no variable with the given name,
+** return 0.
+*/
+SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){
+ Vdbe *p = (Vdbe*)pStmt;
+ int i;
+ if( p==0 ){
+ return 0;
+ }
+ createVarMap(p);
+ if( zName ){
+ for(i=0; i<p->nVar; i++){
+ const char *z = p->azVar[i];
+ if( z && strcmp(z,zName)==0 ){
+ return i+1;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Transfer all bindings from the first statement over to the second.
+** If the two statements contain a different number of bindings, then
+** an SQLITE_ERROR is returned.
+*/
+SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt *pFromStmt, sqlite3_stmt *pToStmt){
+ Vdbe *pFrom = (Vdbe*)pFromStmt;
+ Vdbe *pTo = (Vdbe*)pToStmt;
+ int i, rc = SQLITE_OK;
+ if( (pFrom->magic!=VDBE_MAGIC_RUN && pFrom->magic!=VDBE_MAGIC_HALT)
+ || (pTo->magic!=VDBE_MAGIC_RUN && pTo->magic!=VDBE_MAGIC_HALT)
+ || pTo->db!=pFrom->db ){
+ return SQLITE_MISUSE;
+ }
+ if( pFrom->nVar!=pTo->nVar ){
+ return SQLITE_ERROR;
+ }
+ sqlite3_mutex_enter(pTo->db->mutex);
+ for(i=0; rc==SQLITE_OK && i<pFrom->nVar; i++){
+ sqlite3VdbeMemMove(&pTo->aVar[i], &pFrom->aVar[i]);
+ }
+ sqlite3_mutex_leave(pTo->db->mutex);
+ assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
+ return rc;
+}
+
+/*
+** Return the sqlite3* database handle to which the prepared statement given
+** in the argument belongs. This is the same database handle that was
+** the first argument to the sqlite3_prepare() that was used to create
+** the statement in the first place.
+*/
+SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt *pStmt){
+ return pStmt ? ((Vdbe*)pStmt)->db : 0;
+}
+
+/************** End of vdbeapi.c *********************************************/
+/************** Begin file vdbe.c ********************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** The code in this file implements execution method of the
+** Virtual Database Engine (VDBE). A separate file ("vdbeaux.c")
+** handles housekeeping details such as creating and deleting
+** VDBE instances. This file is solely interested in executing
+** the VDBE program.
+**
+** In the external interface, an "sqlite3_stmt*" is an opaque pointer
+** to a VDBE.
+**
+** The SQL parser generates a program which is then executed by
+** the VDBE to do the work of the SQL statement. VDBE programs are
+** similar in form to assembly language. The program consists of
+** a linear sequence of operations. Each operation has an opcode
+** and 5 operands. Operands P1, P2, and P3 are integers. Operand P4
+** is a null-terminated string. Operand P5 is an unsigned character.
+** Few opcodes use all 5 operands.
+**
+** Computation results are stored on a set of registers numbered beginning
+** with 1 and going up to Vdbe.nMem. Each register can store
+** either an integer, a null-terminated string, a floating point
+** number, or the SQL "NULL" value. An inplicit conversion from one
+** type to the other occurs as necessary.
+**
+** Most of the code in this file is taken up by the sqlite3VdbeExec()
+** function which does the work of interpreting a VDBE program.
+** But other routines are also provided to help in building up
+** a program instruction by instruction.
+**
+** Various scripts scan this source file in order to generate HTML
+** documentation, headers files, or other derived files. The formatting
+** of the code in this file is, therefore, important. See other comments
+** in this file for details. If in doubt, do not deviate from existing
+** commenting and indentation practices when changing or adding code.
+**
+** $Id: vdbe.c,v 1.740 2008/05/13 13:27:34 drh Exp $
+*/
+
+/*
+** The following global variable is incremented every time a cursor
+** moves, either by the OP_MoveXX, OP_Next, or OP_Prev opcodes. The test
+** procedures use this information to make sure that indices are
+** working correctly. This variable has no function other than to
+** help verify the correct operation of the library.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_search_count = 0;
+#endif
+
+/*
+** When this global variable is positive, it gets decremented once before
+** each instruction in the VDBE. When reaches zero, the u1.isInterrupted
+** field of the sqlite3 structure is set in order to simulate and interrupt.
+**
+** This facility is used for testing purposes only. It does not function
+** in an ordinary build.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_interrupt_count = 0;
+#endif
+
+/*
+** The next global variable is incremented each type the OP_Sort opcode
+** is executed. The test procedures use this information to make sure that
+** sorting is occurring or not occuring at appropriate times. This variable
+** has no function other than to help verify the correct operation of the
+** library.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_sort_count = 0;
+#endif
+
+/*
+** The next global variable records the size of the largest MEM_Blob
+** or MEM_Str that has been used by a VDBE opcode. The test procedures
+** use this information to make sure that the zero-blob functionality
+** is working correctly. This variable has no function other than to
+** help verify the correct operation of the library.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_max_blobsize = 0;
+static void updateMaxBlobsize(Mem *p){
+ if( (p->flags & (MEM_Str|MEM_Blob))!=0 && p->n>sqlite3_max_blobsize ){
+ sqlite3_max_blobsize = p->n;
+ }
+}
+#endif
+
+/*
+** Test a register to see if it exceeds the current maximum blob size.
+** If it does, record the new maximum blob size.
+*/
+#if defined(SQLITE_TEST) && !defined(SQLITE_OMIT_BUILTIN_TEST)
+# define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P)
+#else
+# define UPDATE_MAX_BLOBSIZE(P)
+#endif
+
+/*
+** Release the memory associated with a register. This
+** leaves the Mem.flags field in an inconsistent state.
+*/
+#define Release(P) if((P)->flags&MEM_Dyn){ sqlite3VdbeMemRelease(P); }
+
+/*
+** Convert the given into a string if it isn't one
+** already. Return non-zero if a malloc() fails.
+*/
+#define Stringify(P, enc) \
+ if(((P)->flags&(MEM_Str|MEM_Blob))==0 && sqlite3VdbeMemStringify(P,enc)) \
+ { goto no_mem; }
+
+/*
+** An ephemeral string value (signified by the MEM_Ephem flag) contains
+** a pointer to a dynamically allocated string where some other entity
+** is responsible for deallocating that string. Because the register
+** does not control the string, it might be deleted without the register
+** knowing it.
+**
+** This routine converts an ephemeral string into a dynamically allocated
+** string that the register itself controls. In other words, it
+** converts an MEM_Ephem string into an MEM_Dyn string.
+*/
+#define Deephemeralize(P) \
+ if( ((P)->flags&MEM_Ephem)!=0 \
+ && sqlite3VdbeMemMakeWriteable(P) ){ goto no_mem;}
+
+/*
+** Call sqlite3VdbeMemExpandBlob() on the supplied value (type Mem*)
+** P if required.
+*/
+#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlite3VdbeMemExpandBlob(P):0)
+
+/*
+** Argument pMem points at a regiser that will be passed to a
+** user-defined function or returned to the user as the result of a query.
+** The second argument, 'db_enc' is the text encoding used by the vdbe for
+** register variables. This routine sets the pMem->enc and pMem->type
+** variables used by the sqlite3_value_*() routines.
+*/
+#define storeTypeInfo(A,B) _storeTypeInfo(A)
+static void _storeTypeInfo(Mem *pMem){
+ int flags = pMem->flags;
+ if( flags & MEM_Null ){
+ pMem->type = SQLITE_NULL;
+ }
+ else if( flags & MEM_Int ){
+ pMem->type = SQLITE_INTEGER;
+ }
+ else if( flags & MEM_Real ){
+ pMem->type = SQLITE_FLOAT;
+ }
+ else if( flags & MEM_Str ){
+ pMem->type = SQLITE_TEXT;
+ }else{
+ pMem->type = SQLITE_BLOB;
+ }
+}
+
+/*
+** Properties of opcodes. The OPFLG_INITIALIZER macro is
+** created by mkopcodeh.awk during compilation. Data is obtained
+** from the comments following the "case OP_xxxx:" statements in
+** this file.
+*/
+static unsigned char opcodeProperty[] = OPFLG_INITIALIZER;
+
+/*
+** Return true if an opcode has any of the OPFLG_xxx properties
+** specified by mask.
+*/
+SQLITE_PRIVATE int sqlite3VdbeOpcodeHasProperty(int opcode, int mask){
+ assert( opcode>0 && opcode<sizeof(opcodeProperty) );
+ return (opcodeProperty[opcode]&mask)!=0;
+}
+
+/*
+** Allocate cursor number iCur. Return a pointer to it. Return NULL
+** if we run out of memory.
+*/
+static Cursor *allocateCursor(
+ Vdbe *p,
+ int iCur,
+ Op *pOp,
+ int iDb,
+ int isBtreeCursor
+){
+ /* Find the memory cell that will be used to store the blob of memory
+ ** required for this Cursor structure. It is convenient to use a
+ ** vdbe memory cell to manage the memory allocation required for a
+ ** Cursor structure for the following reasons:
+ **
+ ** * Sometimes cursor numbers are used for a couple of different
+ ** purposes in a vdbe program. The different uses might require
+ ** different sized allocations. Memory cells provide growable
+ ** allocations.
+ **
+ ** * When using ENABLE_MEMORY_MANAGEMENT, memory cell buffers can
+ ** be freed lazily via the sqlite3_release_memory() API. This
+ ** minimizes the number of malloc calls made by the system.
+ **
+ ** Memory cells for cursors are allocated at the top of the address
+ ** space. Memory cell (p->nMem) corresponds to cursor 0. Space for
+ ** cursor 1 is managed by memory cell (p->nMem-1), etc.
+ */
+ Mem *pMem = &p->aMem[p->nMem-iCur];
+
+ int nByte;
+ Cursor *pCx = 0;
+ /* If the opcode of pOp is OP_SetNumColumns, then pOp->p2 contains
+ ** the number of fields in the records contained in the table or
+ ** index being opened. Use this to reserve space for the
+ ** Cursor.aType[] array.
+ */
+ int nField = 0;
+ if( pOp->opcode==OP_SetNumColumns || pOp->opcode==OP_OpenEphemeral ){
+ nField = pOp->p2;
+ }
+ nByte =
+ sizeof(Cursor) +
+ (isBtreeCursor?sqlite3BtreeCursorSize():0) +
+ 2*nField*sizeof(u32);
+
+ assert( iCur<p->nCursor );
+ if( p->apCsr[iCur] ){
+ sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
+ p->apCsr[iCur] = 0;
+ }
+ if( SQLITE_OK==sqlite3VdbeMemGrow(pMem, nByte, 0) ){
+ p->apCsr[iCur] = pCx = (Cursor *)pMem->z;
+ memset(pMem->z, 0, nByte);
+ pCx->iDb = iDb;
+ pCx->nField = nField;
+ if( nField ){
+ pCx->aType = (u32 *)&pMem->z[sizeof(Cursor)];
+ }
+ if( isBtreeCursor ){
+ pCx->pCursor = (BtCursor *)&pMem->z[sizeof(Cursor)+2*nField*sizeof(u32)];
+ }
+ }
+ return pCx;
+}
+
+/*
+** Try to convert a value into a numeric representation if we can
+** do so without loss of information. In other words, if the string
+** looks like a number, convert it into a number. If it does not
+** look like a number, leave it alone.
+*/
+static void applyNumericAffinity(Mem *pRec){
+ if( (pRec->flags & (MEM_Real|MEM_Int))==0 ){
+ int realnum;
+ sqlite3VdbeMemNulTerminate(pRec);
+ if( (pRec->flags&MEM_Str)
+ && sqlite3IsNumber(pRec->z, &realnum, pRec->enc) ){
+ i64 value;
+ sqlite3VdbeChangeEncoding(pRec, SQLITE_UTF8);
+ if( !realnum && sqlite3Atoi64(pRec->z, &value) ){
+ pRec->u.i = value;
+ MemSetTypeFlag(pRec, MEM_Int);
+ }else{
+ sqlite3VdbeMemRealify(pRec);
+ }
+ }
+ }
+}
+
+/*
+** Processing is determine by the affinity parameter:
+**
+** SQLITE_AFF_INTEGER:
+** SQLITE_AFF_REAL:
+** SQLITE_AFF_NUMERIC:
+** Try to convert pRec to an integer representation or a
+** floating-point representation if an integer representation
+** is not possible. Note that the integer representation is
+** always preferred, even if the affinity is REAL, because
+** an integer representation is more space efficient on disk.
+**
+** SQLITE_AFF_TEXT:
+** Convert pRec to a text representation.
+**
+** SQLITE_AFF_NONE:
+** No-op. pRec is unchanged.
+*/
+static void applyAffinity(
+ Mem *pRec, /* The value to apply affinity to */
+ char affinity, /* The affinity to be applied */
+ u8 enc /* Use this text encoding */
+){
+ if( affinity==SQLITE_AFF_TEXT ){
+ /* Only attempt the conversion to TEXT if there is an integer or real
+ ** representation (blob and NULL do not get converted) but no string
+ ** representation.
+ */
+ if( 0==(pRec->flags&MEM_Str) && (pRec->flags&(MEM_Real|MEM_Int)) ){
+ sqlite3VdbeMemStringify(pRec, enc);
+ }
+ pRec->flags &= ~(MEM_Real|MEM_Int);
+ }else if( affinity!=SQLITE_AFF_NONE ){
+ assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL
+ || affinity==SQLITE_AFF_NUMERIC );
+ applyNumericAffinity(pRec);
+ if( pRec->flags & MEM_Real ){
+ sqlite3VdbeIntegerAffinity(pRec);
+ }
+ }
+}
+
+/*
+** Try to convert the type of a function argument or a result column
+** into a numeric representation. Use either INTEGER or REAL whichever
+** is appropriate. But only do the conversion if it is possible without
+** loss of information and return the revised type of the argument.
+**
+** This is an EXPERIMENTAL api and is subject to change or removal.
+*/
+SQLITE_API int sqlite3_value_numeric_type(sqlite3_value *pVal){
+ Mem *pMem = (Mem*)pVal;
+ applyNumericAffinity(pMem);
+ storeTypeInfo(pMem, 0);
+ return pMem->type;
+}
+
+/*
+** Exported version of applyAffinity(). This one works on sqlite3_value*,
+** not the internal Mem* type.
+*/
+SQLITE_PRIVATE void sqlite3ValueApplyAffinity(
+ sqlite3_value *pVal,
+ u8 affinity,
+ u8 enc
+){
+ applyAffinity((Mem *)pVal, affinity, enc);
+}
+
+#ifdef SQLITE_DEBUG
+/*
+** Write a nice string representation of the contents of cell pMem
+** into buffer zBuf, length nBuf.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf){
+ char *zCsr = zBuf;
+ int f = pMem->flags;
+
+ static const char *const encnames[] = {"(X)", "(8)", "(16LE)", "(16BE)"};
+
+ if( f&MEM_Blob ){
+ int i;
+ char c;
+ if( f & MEM_Dyn ){
+ c = 'z';
+ assert( (f & (MEM_Static|MEM_Ephem))==0 );
+ }else if( f & MEM_Static ){
+ c = 't';
+ assert( (f & (MEM_Dyn|MEM_Ephem))==0 );
+ }else if( f & MEM_Ephem ){
+ c = 'e';
+ assert( (f & (MEM_Static|MEM_Dyn))==0 );
+ }else{
+ c = 's';
+ }
+
+ sqlite3_snprintf(100, zCsr, "%c", c);
+ zCsr += strlen(zCsr);
+ sqlite3_snprintf(100, zCsr, "%d[", pMem->n);
+ zCsr += strlen(zCsr);
+ for(i=0; i<16 && i<pMem->n; i++){
+ sqlite3_snprintf(100, zCsr, "%02X", ((int)pMem->z[i] & 0xFF));
+ zCsr += strlen(zCsr);
+ }
+ for(i=0; i<16 && i<pMem->n; i++){
+ char z = pMem->z[i];
+ if( z<32 || z>126 ) *zCsr++ = '.';
+ else *zCsr++ = z;
+ }
+
+ sqlite3_snprintf(100, zCsr, "]%s", encnames[pMem->enc]);
+ zCsr += strlen(zCsr);
+ if( f & MEM_Zero ){
+ sqlite3_snprintf(100, zCsr,"+%lldz",pMem->u.i);
+ zCsr += strlen(zCsr);
+ }
+ *zCsr = '\0';
+ }else if( f & MEM_Str ){
+ int j, k;
+ zBuf[0] = ' ';
+ if( f & MEM_Dyn ){
+ zBuf[1] = 'z';
+ assert( (f & (MEM_Static|MEM_Ephem))==0 );
+ }else if( f & MEM_Static ){
+ zBuf[1] = 't';
+ assert( (f & (MEM_Dyn|MEM_Ephem))==0 );
+ }else if( f & MEM_Ephem ){
+ zBuf[1] = 'e';
+ assert( (f & (MEM_Static|MEM_Dyn))==0 );
+ }else{
+ zBuf[1] = 's';
+ }
+ k = 2;
+ sqlite3_snprintf(100, &zBuf[k], "%d", pMem->n);
+ k += strlen(&zBuf[k]);
+ zBuf[k++] = '[';
+ for(j=0; j<15 && j<pMem->n; j++){
+ u8 c = pMem->z[j];
+ if( c>=0x20 && c<0x7f ){
+ zBuf[k++] = c;
+ }else{
+ zBuf[k++] = '.';
+ }
+ }
+ zBuf[k++] = ']';
+ sqlite3_snprintf(100,&zBuf[k], encnames[pMem->enc]);
+ k += strlen(&zBuf[k]);
+ zBuf[k++] = 0;
+ }
+}
+#endif
+
+#ifdef SQLITE_DEBUG
+/*
+** Print the value of a register for tracing purposes:
+*/
+static void memTracePrint(FILE *out, Mem *p){
+ if( p->flags & MEM_Null ){
+ fprintf(out, " NULL");
+ }else if( (p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){
+ fprintf(out, " si:%lld", p->u.i);
+ }else if( p->flags & MEM_Int ){
+ fprintf(out, " i:%lld", p->u.i);
+ }else if( p->flags & MEM_Real ){
+ fprintf(out, " r:%g", p->r);
+ }else{
+ char zBuf[200];
+ sqlite3VdbeMemPrettyPrint(p, zBuf);
+ fprintf(out, " ");
+ fprintf(out, "%s", zBuf);
+ }
+}
+static void registerTrace(FILE *out, int iReg, Mem *p){
+ fprintf(out, "REG[%d] = ", iReg);
+ memTracePrint(out, p);
+ fprintf(out, "\n");
+}
+#endif
+
+#ifdef SQLITE_DEBUG
+# define REGISTER_TRACE(R,M) if(p->trace&&R>0)registerTrace(p->trace,R,M)
+#else
+# define REGISTER_TRACE(R,M)
+#endif
+
+
+#ifdef VDBE_PROFILE
+/*
+** The following routine only works on pentium-class processors.
+** It uses the RDTSC opcode to read the cycle count value out of the
+** processor and returns that value. This can be used for high-res
+** profiling.
+*/
+__inline__ unsigned long long int hwtime(void){
+ unsigned int lo, hi;
+ /* We cannot use "=A", since this would use %rax on x86_64 */
+ __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
+ return (unsigned long long int)hi << 32 | lo;
+}
+#endif
+
+/*
+** The CHECK_FOR_INTERRUPT macro defined here looks to see if the
+** sqlite3_interrupt() routine has been called. If it has been, then
+** processing of the VDBE program is interrupted.
+**
+** This macro added to every instruction that does a jump in order to
+** implement a loop. This test used to be on every single instruction,
+** but that meant we more testing that we needed. By only testing the
+** flag on jump instructions, we get a (small) speed improvement.
+*/
+#define CHECK_FOR_INTERRUPT \
+ if( db->u1.isInterrupted ) goto abort_due_to_interrupt;
+
+
+/*
+** Execute as much of a VDBE program as we can then return.
+**
+** sqlite3VdbeMakeReady() must be called before this routine in order to
+** close the program with a final OP_Halt and to set up the callbacks
+** and the error message pointer.
+**
+** Whenever a row or result data is available, this routine will either
+** invoke the result callback (if there is one) or return with
+** SQLITE_ROW.
+**
+** If an attempt is made to open a locked database, then this routine
+** will either invoke the busy callback (if there is one) or it will
+** return SQLITE_BUSY.
+**
+** If an error occurs, an error message is written to memory obtained
+** from sqlite3_malloc() and p->zErrMsg is made to point to that memory.
+** The error code is stored in p->rc and this routine returns SQLITE_ERROR.
+**
+** If the callback ever returns non-zero, then the program exits
+** immediately. There will be no error message but the p->rc field is
+** set to SQLITE_ABORT and this routine will return SQLITE_ERROR.
+**
+** A memory allocation error causes p->rc to be set to SQLITE_NOMEM and this
+** routine to return SQLITE_ERROR.
+**
+** Other fatal errors return SQLITE_ERROR.
+**
+** After this routine has finished, sqlite3VdbeFinalize() should be
+** used to clean up the mess that was left behind.
+*/
+SQLITE_PRIVATE int sqlite3VdbeExec(
+ Vdbe *p /* The VDBE */
+){
+ int pc; /* The program counter */
+ Op *pOp; /* Current operation */
+ int rc = SQLITE_OK; /* Value to return */
+ sqlite3 *db = p->db; /* The database */
+ u8 encoding = ENC(db); /* The database encoding */
+ Mem *pIn1, *pIn2, *pIn3; /* Input operands */
+ Mem *pOut; /* Output operand */
+ u8 opProperty;
+#ifdef VDBE_PROFILE
+ unsigned long long start; /* CPU clock count at start of opcode */
+ int origPc; /* Program counter at start of opcode */
+#endif
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ int nProgressOps = 0; /* Opcodes executed since progress callback. */
+#endif
+
+ assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */
+ assert( db->magic==SQLITE_MAGIC_BUSY );
+ sqlite3BtreeMutexArrayEnter(&p->aMutex);
+ if( p->rc==SQLITE_NOMEM ){
+ /* This happens if a malloc() inside a call to sqlite3_column_text() or
+ ** sqlite3_column_text16() failed. */
+ goto no_mem;
+ }
+ assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY );
+ p->rc = SQLITE_OK;
+ assert( p->explain==0 );
+ p->pResultSet = 0;
+ db->busyHandler.nBusy = 0;
+ CHECK_FOR_INTERRUPT;
+ sqlite3VdbeIOTraceSql(p);
+#ifdef SQLITE_DEBUG
+ sqlite3FaultBeginBenign(-1);
+ if( p->pc==0 && ((p->db->flags & SQLITE_VdbeListing)!=0
+ || sqlite3OsAccess(db->pVfs, "vdbe_explain", SQLITE_ACCESS_EXISTS)==1 )
+ ){
+ int i;
+ printf("VDBE Program Listing:\n");
+ sqlite3VdbePrintSql(p);
+ for(i=0; i<p->nOp; i++){
+ sqlite3VdbePrintOp(stdout, i, &p->aOp[i]);
+ }
+ }
+ if( sqlite3OsAccess(db->pVfs, "vdbe_trace", SQLITE_ACCESS_EXISTS)==1 ){
+ p->trace = stdout;
+ }
+ sqlite3FaultEndBenign(-1);
+#endif
+ for(pc=p->pc; rc==SQLITE_OK; pc++){
+ assert( pc>=0 && pc<p->nOp );
+ if( db->mallocFailed ) goto no_mem;
+#ifdef VDBE_PROFILE
+ origPc = pc;
+ start = hwtime();
+#endif
+ pOp = &p->aOp[pc];
+
+ /* Only allow tracing if SQLITE_DEBUG is defined.
+ */
+#ifdef SQLITE_DEBUG
+ if( p->trace ){
+ if( pc==0 ){
+ printf("VDBE Execution Trace:\n");
+ sqlite3VdbePrintSql(p);
+ }
+ sqlite3VdbePrintOp(p->trace, pc, pOp);
+ }
+ if( p->trace==0 && pc==0 ){
+ sqlite3FaultBeginBenign(-1);
+ if( sqlite3OsAccess(db->pVfs, "vdbe_sqltrace", SQLITE_ACCESS_EXISTS)==1 ){
+ sqlite3VdbePrintSql(p);
+ }
+ sqlite3FaultEndBenign(-1);
+ }
+#endif
+
+
+ /* Check to see if we need to simulate an interrupt. This only happens
+ ** if we have a special test build.
+ */
+#ifdef SQLITE_TEST
+ if( sqlite3_interrupt_count>0 ){
+ sqlite3_interrupt_count--;
+ if( sqlite3_interrupt_count==0 ){
+ sqlite3_interrupt(db);
+ }
+ }
+#endif
+
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ /* Call the progress callback if it is configured and the required number
+ ** of VDBE ops have been executed (either since this invocation of
+ ** sqlite3VdbeExec() or since last time the progress callback was called).
+ ** If the progress callback returns non-zero, exit the virtual machine with
+ ** a return code SQLITE_ABORT.
+ */
+ if( db->xProgress ){
+ if( db->nProgressOps==nProgressOps ){
+ int prc;
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ prc =db->xProgress(db->pProgressArg);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+ if( prc!=0 ){
+ rc = SQLITE_INTERRUPT;
+ goto vdbe_error_halt;
+ }
+ nProgressOps = 0;
+ }
+ nProgressOps++;
+ }
+#endif
+
+ /* Do common setup processing for any opcode that is marked
+ ** with the "out2-prerelease" tag. Such opcodes have a single
+ ** output which is specified by the P2 parameter. The P2 register
+ ** is initialized to a NULL.
+ */
+ opProperty = opcodeProperty[pOp->opcode];
+ if( (opProperty & OPFLG_OUT2_PRERELEASE)!=0 ){
+ assert( pOp->p2>0 );
+ assert( pOp->p2<=p->nMem );
+ pOut = &p->aMem[pOp->p2];
+ sqlite3VdbeMemReleaseExternal(pOut);
+ pOut->flags = MEM_Null;
+ }else
+
+ /* Do common setup for opcodes marked with one of the following
+ ** combinations of properties.
+ **
+ ** in1
+ ** in1 in2
+ ** in1 in2 out3
+ ** in1 in3
+ **
+ ** Variables pIn1, pIn2, and pIn3 are made to point to appropriate
+ ** registers for inputs. Variable pOut points to the output register.
+ */
+ if( (opProperty & OPFLG_IN1)!=0 ){
+ assert( pOp->p1>0 );
+ assert( pOp->p1<=p->nMem );
+ pIn1 = &p->aMem[pOp->p1];
+ REGISTER_TRACE(pOp->p1, pIn1);
+ if( (opProperty & OPFLG_IN2)!=0 ){
+ assert( pOp->p2>0 );
+ assert( pOp->p2<=p->nMem );
+ pIn2 = &p->aMem[pOp->p2];
+ REGISTER_TRACE(pOp->p2, pIn2);
+ if( (opProperty & OPFLG_OUT3)!=0 ){
+ assert( pOp->p3>0 );
+ assert( pOp->p3<=p->nMem );
+ pOut = &p->aMem[pOp->p3];
+ }
+ }else if( (opProperty & OPFLG_IN3)!=0 ){
+ assert( pOp->p3>0 );
+ assert( pOp->p3<=p->nMem );
+ pIn3 = &p->aMem[pOp->p3];
+ REGISTER_TRACE(pOp->p3, pIn3);
+ }
+ }else if( (opProperty & OPFLG_IN2)!=0 ){
+ assert( pOp->p2>0 );
+ assert( pOp->p2<=p->nMem );
+ pIn2 = &p->aMem[pOp->p2];
+ REGISTER_TRACE(pOp->p2, pIn2);
+ }else if( (opProperty & OPFLG_IN3)!=0 ){
+ assert( pOp->p3>0 );
+ assert( pOp->p3<=p->nMem );
+ pIn3 = &p->aMem[pOp->p3];
+ REGISTER_TRACE(pOp->p3, pIn3);
+ }
+
+ switch( pOp->opcode ){
+
+/*****************************************************************************
+** What follows is a massive switch statement where each case implements a
+** separate instruction in the virtual machine. If we follow the usual
+** indentation conventions, each case should be indented by 6 spaces. But
+** that is a lot of wasted space on the left margin. So the code within
+** the switch statement will break with convention and be flush-left. Another
+** big comment (similar to this one) will mark the point in the code where
+** we transition back to normal indentation.
+**
+** The formatting of each case is important. The makefile for SQLite
+** generates two C files "opcodes.h" and "opcodes.c" by scanning this
+** file looking for lines that begin with "case OP_". The opcodes.h files
+** will be filled with #defines that give unique integer values to each
+** opcode and the opcodes.c file is filled with an array of strings where
+** each string is the symbolic name for the corresponding opcode. If the
+** case statement is followed by a comment of the form "/# same as ... #/"
+** that comment is used to determine the particular value of the opcode.
+**
+** Other keywords in the comment that follows each case are used to
+** construct the OPFLG_INITIALIZER value that initializes opcodeProperty[].
+** Keywords include: in1, in2, in3, out2_prerelease, out2, out3. See
+** the mkopcodeh.awk script for additional information.
+**
+** Documentation about VDBE opcodes is generated by scanning this file
+** for lines of that contain "Opcode:". That line and all subsequent
+** comment lines are used in the generation of the opcode.html documentation
+** file.
+**
+** SUMMARY:
+**
+** Formatting is important to scripts that scan this file.
+** Do not deviate from the formatting style currently in use.
+**
+*****************************************************************************/
+
+/* Opcode: Goto * P2 * * *
+**
+** An unconditional jump to address P2.
+** The next instruction executed will be
+** the one at index P2 from the beginning of
+** the program.
+*/
+case OP_Goto: { /* jump */
+ CHECK_FOR_INTERRUPT;
+ pc = pOp->p2 - 1;
+ break;
+}
+
+/* Opcode: Gosub * P2 * * *
+**
+** Push the current address plus 1 onto the return address stack
+** and then jump to address P2.
+**
+** The return address stack is of limited depth. If too many
+** OP_Gosub operations occur without intervening OP_Returns, then
+** the return address stack will fill up and processing will abort
+** with a fatal error.
+*/
+case OP_Gosub: { /* jump */
+ assert( p->returnDepth<sizeof(p->returnStack)/sizeof(p->returnStack[0]) );
+ p->returnStack[p->returnDepth++] = pc+1;
+ pc = pOp->p2 - 1;
+ break;
+}
+
+/* Opcode: Return * * * * *
+**
+** Jump immediately to the next instruction after the last unreturned
+** OP_Gosub. If an OP_Return has occurred for all OP_Gosubs, then
+** processing aborts with a fatal error.
+*/
+case OP_Return: {
+ assert( p->returnDepth>0 );
+ p->returnDepth--;
+ pc = p->returnStack[p->returnDepth] - 1;
+ break;
+}
+
+/* Opcode: Halt P1 P2 * P4 *
+**
+** Exit immediately. All open cursors, Fifos, etc are closed
+** automatically.
+**
+** P1 is the result code returned by sqlite3_exec(), sqlite3_reset(),
+** or sqlite3_finalize(). For a normal halt, this should be SQLITE_OK (0).
+** For errors, it can be some other value. If P1!=0 then P2 will determine
+** whether or not to rollback the current transaction. Do not rollback
+** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort,
+** then back out all changes that have occurred during this execution of the
+** VDBE, but do not rollback the transaction.
+**
+** If P4 is not null then it is an error message string.
+**
+** There is an implied "Halt 0 0 0" instruction inserted at the very end of
+** every program. So a jump past the last instruction of the program
+** is the same as executing Halt.
+*/
+case OP_Halt: {
+ p->rc = pOp->p1;
+ p->pc = pc;
+ p->errorAction = pOp->p2;
+ if( pOp->p4.z ){
+ sqlite3SetString(&p->zErrMsg, pOp->p4.z, (char*)0);
+ }
+ rc = sqlite3VdbeHalt(p);
+ assert( rc==SQLITE_BUSY || rc==SQLITE_OK );
+ if( rc==SQLITE_BUSY ){
+ p->rc = rc = SQLITE_BUSY;
+ }else{
+ rc = p->rc ? SQLITE_ERROR : SQLITE_DONE;
+ }
+ goto vdbe_return;
+}
+
+/* Opcode: Integer P1 P2 * * *
+**
+** The 32-bit integer value P1 is written into register P2.
+*/
+case OP_Integer: { /* out2-prerelease */
+ pOut->flags = MEM_Int;
+ pOut->u.i = pOp->p1;
+ break;
+}
+
+/* Opcode: Int64 * P2 * P4 *
+**
+** P4 is a pointer to a 64-bit integer value.
+** Write that value into register P2.
+*/
+case OP_Int64: { /* out2-prerelease */
+ assert( pOp->p4.pI64!=0 );
+ pOut->flags = MEM_Int;
+ pOut->u.i = *pOp->p4.pI64;
+ break;
+}
+
+/* Opcode: Real * P2 * P4 *
+**
+** P4 is a pointer to a 64-bit floating point value.
+** Write that value into register P2.
+*/
+case OP_Real: { /* same as TK_FLOAT, out2-prerelease */
+ pOut->flags = MEM_Real;
+ assert( !sqlite3IsNaN(*pOp->p4.pReal) );
+ pOut->r = *pOp->p4.pReal;
+ break;
+}
+
+/* Opcode: String8 * P2 * P4 *
+**
+** P4 points to a nul terminated UTF-8 string. This opcode is transformed
+** into an OP_String before it is executed for the first time.
+*/
+case OP_String8: { /* same as TK_STRING, out2-prerelease */
+ assert( pOp->p4.z!=0 );
+ pOp->opcode = OP_String;
+ pOp->p1 = strlen(pOp->p4.z);
+
+#ifndef SQLITE_OMIT_UTF16
+ if( encoding!=SQLITE_UTF8 ){
+ sqlite3VdbeMemSetStr(pOut, pOp->p4.z, -1, SQLITE_UTF8, SQLITE_STATIC);
+ if( SQLITE_OK!=sqlite3VdbeChangeEncoding(pOut, encoding) ) goto no_mem;
+ if( SQLITE_OK!=sqlite3VdbeMemDynamicify(pOut) ) goto no_mem;
+ pOut->zMalloc = 0;
+ pOut->flags |= MEM_Static;
+ pOut->flags &= ~MEM_Dyn;
+ if( pOp->p4type==P4_DYNAMIC ){
+ sqlite3_free(pOp->p4.z);
+ }
+ pOp->p4type = P4_DYNAMIC;
+ pOp->p4.z = pOut->z;
+ pOp->p1 = pOut->n;
+ if( pOp->p1>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+ }
+#endif
+ if( pOp->p1>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+ /* Fall through to the next case, OP_String */
+}
+
+/* Opcode: String P1 P2 * P4 *
+**
+** The string value P4 of length P1 (bytes) is stored in register P2.
+*/
+case OP_String: { /* out2-prerelease */
+ assert( pOp->p4.z!=0 );
+ pOut->flags = MEM_Str|MEM_Static|MEM_Term;
+ pOut->z = pOp->p4.z;
+ pOut->n = pOp->p1;
+ pOut->enc = encoding;
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+
+/* Opcode: Null * P2 * * *
+**
+** Write a NULL into register P2.
+*/
+case OP_Null: { /* out2-prerelease */
+ break;
+}
+
+
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+/* Opcode: Blob P1 P2 * P4
+**
+** P4 points to a blob of data P1 bytes long. Store this
+** blob in register P2. This instruction is not coded directly
+** by the compiler. Instead, the compiler layer specifies
+** an OP_HexBlob opcode, with the hex string representation of
+** the blob as P4. This opcode is transformed to an OP_Blob
+** the first time it is executed.
+*/
+case OP_Blob: { /* out2-prerelease */
+ assert( pOp->p1 <= SQLITE_MAX_LENGTH );
+ sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0);
+ pOut->enc = encoding;
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+#endif /* SQLITE_OMIT_BLOB_LITERAL */
+
+/* Opcode: Variable P1 P2 * * *
+**
+** The value of variable P1 is written into register P2. A variable is
+** an unknown in the original SQL string as handed to sqlite3_compile().
+** Any occurance of the '?' character in the original SQL is considered
+** a variable. Variables in the SQL string are number from left to
+** right beginning with 1. The values of variables are set using the
+** sqlite3_bind() API.
+*/
+case OP_Variable: { /* out2-prerelease */
+ int j = pOp->p1 - 1;
+ Mem *pVar;
+ assert( j>=0 && j<p->nVar );
+
+ pVar = &p->aVar[j];
+ if( sqlite3VdbeMemTooBig(pVar) ){
+ goto too_big;
+ }
+ sqlite3VdbeMemShallowCopy(pOut, &p->aVar[j], MEM_Static);
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+
+/* Opcode: Move P1 P2 * * *
+**
+** Move the value in register P1 over into register P2. Register P1
+** is left holding a NULL. It is an error for P1 and P2 to be the
+** same register.
+*/
+case OP_Move: {
+ char *zMalloc;
+ assert( pOp->p1>0 );
+ assert( pOp->p1<=p->nMem );
+ pIn1 = &p->aMem[pOp->p1];
+ REGISTER_TRACE(pOp->p1, pIn1);
+ assert( pOp->p2>0 );
+ assert( pOp->p2<=p->nMem );
+ pOut = &p->aMem[pOp->p2];
+ assert( pOut!=pIn1 );
+ zMalloc = pOut->zMalloc;
+ pOut->zMalloc = 0;
+ sqlite3VdbeMemMove(pOut, pIn1);
+ pIn1->zMalloc = zMalloc;
+ REGISTER_TRACE(pOp->p2, pOut);
+ break;
+}
+
+/* Opcode: Copy P1 P2 * * *
+**
+** Make a copy of register P1 into register P2.
+**
+** This instruction makes a deep copy of the value. A duplicate
+** is made of any string or blob constant. See also OP_SCopy.
+*/
+case OP_Copy: {
+ assert( pOp->p1>0 );
+ assert( pOp->p1<=p->nMem );
+ pIn1 = &p->aMem[pOp->p1];
+ REGISTER_TRACE(pOp->p1, pIn1);
+ assert( pOp->p2>0 );
+ assert( pOp->p2<=p->nMem );
+ pOut = &p->aMem[pOp->p2];
+ assert( pOut!=pIn1 );
+ sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
+ Deephemeralize(pOut);
+ REGISTER_TRACE(pOp->p2, pOut);
+ break;
+}
+
+/* Opcode: SCopy P1 P2 * * *
+**
+** Make a shallow copy of register P1 into register P2.
+**
+** This instruction makes a shallow copy of the value. If the value
+** is a string or blob, then the copy is only a pointer to the
+** original and hence if the original changes so will the copy.
+** Worse, if the original is deallocated, the copy becomes invalid.
+** Thus the program must guarantee that the original will not change
+** during the lifetime of the copy. Use OP_Copy to make a complete
+** copy.
+*/
+case OP_SCopy: {
+ assert( pOp->p1>0 );
+ assert( pOp->p1<=p->nMem );
+ pIn1 = &p->aMem[pOp->p1];
+ REGISTER_TRACE(pOp->p1, pIn1);
+ assert( pOp->p2>0 );
+ assert( pOp->p2<=p->nMem );
+ pOut = &p->aMem[pOp->p2];
+ assert( pOut!=pIn1 );
+ sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
+ REGISTER_TRACE(pOp->p2, pOut);
+ break;
+}
+
+/* Opcode: ResultRow P1 P2 * * *
+**
+** The registers P1 throught P1+P2-1 contain a single row of
+** results. This opcode causes the sqlite3_step() call to terminate
+** with an SQLITE_ROW return code and it sets up the sqlite3_stmt
+** structure to provide access to the top P1 values as the result
+** row.
+*/
+case OP_ResultRow: {
+ Mem *pMem;
+ int i;
+ assert( p->nResColumn==pOp->p2 );
+ assert( pOp->p1>0 );
+ assert( pOp->p1+pOp->p2<=p->nMem );
+
+ /* Invalidate all ephemeral cursor row caches */
+ p->cacheCtr = (p->cacheCtr + 2)|1;
+
+ /* Make sure the results of the current row are \000 terminated
+ ** and have an assigned type. The results are deephemeralized as
+ ** as side effect.
+ */
+ pMem = p->pResultSet = &p->aMem[pOp->p1];
+ for(i=0; i<pOp->p2; i++){
+ sqlite3VdbeMemNulTerminate(&pMem[i]);
+ storeTypeInfo(&pMem[i], encoding);
+ }
+ if( db->mallocFailed ) goto no_mem;
+
+ /* Return SQLITE_ROW
+ */
+ p->nCallback++;
+ p->pc = pc + 1;
+ rc = SQLITE_ROW;
+ goto vdbe_return;
+}
+
+/* Opcode: Concat P1 P2 P3 * *
+**
+** Add the text in register P1 onto the end of the text in
+** register P2 and store the result in register P3.
+** If either the P1 or P2 text are NULL then store NULL in P3.
+**
+** P3 = P2 || P1
+**
+** It is illegal for P1 and P3 to be the same register. Sometimes,
+** if P3 is the same register as P2, the implementation is able
+** to avoid a memcpy().
+*/
+case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
+ i64 nByte;
+
+ assert( pIn1!=pOut );
+ if( (pIn1->flags | pIn2->flags) & MEM_Null ){
+ sqlite3VdbeMemSetNull(pOut);
+ break;
+ }
+ ExpandBlob(pIn1);
+ Stringify(pIn1, encoding);
+ ExpandBlob(pIn2);
+ Stringify(pIn2, encoding);
+ nByte = pIn1->n + pIn2->n;
+ if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+ MemSetTypeFlag(pOut, MEM_Str);
+ if( sqlite3VdbeMemGrow(pOut, nByte+2, pOut==pIn2) ){
+ goto no_mem;
+ }
+ if( pOut!=pIn2 ){
+ memcpy(pOut->z, pIn2->z, pIn2->n);
+ }
+ memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n);
+ pOut->z[nByte] = 0;
+ pOut->z[nByte+1] = 0;
+ pOut->flags |= MEM_Term;
+ pOut->n = nByte;
+ pOut->enc = encoding;
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+
+/* Opcode: Add P1 P2 P3 * *
+**
+** Add the value in register P1 to the value in register P2
+** and store the result in regiser P3.
+** If either input is NULL, the result is NULL.
+*/
+/* Opcode: Multiply P1 P2 P3 * *
+**
+**
+** Multiply the value in regiser P1 by the value in regiser P2
+** and store the result in register P3.
+** If either input is NULL, the result is NULL.
+*/
+/* Opcode: Subtract P1 P2 P3 * *
+**
+** Subtract the value in register P1 from the value in register P2
+** and store the result in register P3.
+** If either input is NULL, the result is NULL.
+*/
+/* Opcode: Divide P1 P2 P3 * *
+**
+** Divide the value in register P1 by the value in register P2
+** and store the result in register P3. If the value in register P2
+** is zero, then the result is NULL.
+** If either input is NULL, the result is NULL.
+*/
+/* Opcode: Remainder P1 P2 P3 * *
+**
+** Compute the remainder after integer division of the value in
+** register P1 by the value in register P2 and store the result in P3.
+** If the value in register P2 is zero the result is NULL.
+** If either operand is NULL, the result is NULL.
+*/
+case OP_Add: /* same as TK_PLUS, in1, in2, out3 */
+case OP_Subtract: /* same as TK_MINUS, in1, in2, out3 */
+case OP_Multiply: /* same as TK_STAR, in1, in2, out3 */
+case OP_Divide: /* same as TK_SLASH, in1, in2, out3 */
+case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
+ int flags;
+ flags = pIn1->flags | pIn2->flags;
+ if( (flags & MEM_Null)!=0 ) goto arithmetic_result_is_null;
+ if( (pIn1->flags & pIn2->flags & MEM_Int)==MEM_Int ){
+ i64 a, b;
+ a = pIn1->u.i;
+ b = pIn2->u.i;
+ switch( pOp->opcode ){
+ case OP_Add: b += a; break;
+ case OP_Subtract: b -= a; break;
+ case OP_Multiply: b *= a; break;
+ case OP_Divide: {
+ if( a==0 ) goto arithmetic_result_is_null;
+ /* Dividing the largest possible negative 64-bit integer (1<<63) by
+ ** -1 returns an integer too large to store in a 64-bit data-type. On
+ ** some architectures, the value overflows to (1<<63). On others,
+ ** a SIGFPE is issued. The following statement normalizes this
+ ** behaviour so that all architectures behave as if integer
+ ** overflow occured.
+ */
+ if( a==-1 && b==SMALLEST_INT64 ) a = 1;
+ b /= a;
+ break;
+ }
+ default: {
+ if( a==0 ) goto arithmetic_result_is_null;
+ if( a==-1 ) a = 1;
+ b %= a;
+ break;
+ }
+ }
+ pOut->u.i = b;
+ MemSetTypeFlag(pOut, MEM_Int);
+ }else{
+ double a, b;
+ a = sqlite3VdbeRealValue(pIn1);
+ b = sqlite3VdbeRealValue(pIn2);
+ switch( pOp->opcode ){
+ case OP_Add: b += a; break;
+ case OP_Subtract: b -= a; break;
+ case OP_Multiply: b *= a; break;
+ case OP_Divide: {
+ if( a==0.0 ) goto arithmetic_result_is_null;
+ b /= a;
+ break;
+ }
+ default: {
+ i64 ia = (i64)a;
+ i64 ib = (i64)b;
+ if( ia==0 ) goto arithmetic_result_is_null;
+ if( ia==-1 ) ia = 1;
+ b = ib % ia;
+ break;
+ }
+ }
+ if( sqlite3IsNaN(b) ){
+ goto arithmetic_result_is_null;
+ }
+ pOut->r = b;
+ MemSetTypeFlag(pOut, MEM_Real);
+ if( (flags & MEM_Real)==0 ){
+ sqlite3VdbeIntegerAffinity(pOut);
+ }
+ }
+ break;
+
+arithmetic_result_is_null:
+ sqlite3VdbeMemSetNull(pOut);
+ break;
+}
+
+/* Opcode: CollSeq * * P4
+**
+** P4 is a pointer to a CollSeq struct. If the next call to a user function
+** or aggregate calls sqlite3GetFuncCollSeq(), this collation sequence will
+** be returned. This is used by the built-in min(), max() and nullif()
+** functions.
+**
+** The interface used by the implementation of the aforementioned functions
+** to retrieve the collation sequence set by this opcode is not available
+** publicly, only to user functions defined in func.c.
+*/
+case OP_CollSeq: {
+ assert( pOp->p4type==P4_COLLSEQ );
+ break;
+}
+
+/* Opcode: Function P1 P2 P3 P4 P5
+**
+** Invoke a user function (P4 is a pointer to a Function structure that
+** defines the function) with P5 arguments taken from register P2 and
+** successors. The result of the function is stored in register P3.
+** Register P3 must not be one of the function inputs.
+**
+** P1 is a 32-bit bitmask indicating whether or not each argument to the
+** function was determined to be constant at compile time. If the first
+** argument was constant then bit 0 of P1 is set. This is used to determine
+** whether meta data associated with a user function argument using the
+** sqlite3_set_auxdata() API may be safely retained until the next
+** invocation of this opcode.
+**
+** See also: AggStep and AggFinal
+*/
+case OP_Function: {
+ int i;
+ Mem *pArg;
+ sqlite3_context ctx;
+ sqlite3_value **apVal;
+ int n = pOp->p5;
+
+ apVal = p->apArg;
+ assert( apVal || n==0 );
+
+ assert( n==0 || (pOp->p2>0 && pOp->p2+n<=p->nMem) );
+ assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n );
+ pArg = &p->aMem[pOp->p2];
+ for(i=0; i<n; i++, pArg++){
+ apVal[i] = pArg;
+ storeTypeInfo(pArg, encoding);
+ REGISTER_TRACE(pOp->p2, pArg);
+ }
+
+ assert( pOp->p4type==P4_FUNCDEF || pOp->p4type==P4_VDBEFUNC );
+ if( pOp->p4type==P4_FUNCDEF ){
+ ctx.pFunc = pOp->p4.pFunc;
+ ctx.pVdbeFunc = 0;
+ }else{
+ ctx.pVdbeFunc = (VdbeFunc*)pOp->p4.pVdbeFunc;
+ ctx.pFunc = ctx.pVdbeFunc->pFunc;
+ }
+
+ assert( pOp->p3>0 && pOp->p3<=p->nMem );
+ pOut = &p->aMem[pOp->p3];
+ ctx.s.flags = MEM_Null;
+ ctx.s.db = db;
+ ctx.s.xDel = 0;
+ ctx.s.zMalloc = 0;
+
+ /* The output cell may already have a buffer allocated. Move
+ ** the pointer to ctx.s so in case the user-function can use
+ ** the already allocated buffer instead of allocating a new one.
+ */
+ sqlite3VdbeMemMove(&ctx.s, pOut);
+ MemSetTypeFlag(&ctx.s, MEM_Null);
+
+ ctx.isError = 0;
+ if( ctx.pFunc->needCollSeq ){
+ assert( pOp>p->aOp );
+ assert( pOp[-1].p4type==P4_COLLSEQ );
+ assert( pOp[-1].opcode==OP_CollSeq );
+ ctx.pColl = pOp[-1].p4.pColl;
+ }
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ (*ctx.pFunc->xFunc)(&ctx, n, apVal);
+ if( sqlite3SafetyOn(db) ){
+ sqlite3VdbeMemRelease(&ctx.s);
+ goto abort_due_to_misuse;
+ }
+ if( db->mallocFailed ){
+ /* Even though a malloc() has failed, the implementation of the
+ ** user function may have called an sqlite3_result_XXX() function
+ ** to return a value. The following call releases any resources
+ ** associated with such a value.
+ **
+ ** Note: Maybe MemRelease() should be called if sqlite3SafetyOn()
+ ** fails also (the if(...) statement above). But if people are
+ ** misusing sqlite, they have bigger problems than a leaked value.
+ */
+ sqlite3VdbeMemRelease(&ctx.s);
+ goto no_mem;
+ }
+
+ /* If any auxilary data functions have been called by this user function,
+ ** immediately call the destructor for any non-static values.
+ */
+ if( ctx.pVdbeFunc ){
+ sqlite3VdbeDeleteAuxData(ctx.pVdbeFunc, pOp->p1);
+ pOp->p4.pVdbeFunc = ctx.pVdbeFunc;
+ pOp->p4type = P4_VDBEFUNC;
+ }
+
+ /* If the function returned an error, throw an exception */
+ if( ctx.isError ){
+ sqlite3SetString(&p->zErrMsg, sqlite3_value_text(&ctx.s), (char*)0);
+ rc = ctx.isError;
+ }
+
+ /* Copy the result of the function into register P3 */
+ sqlite3VdbeChangeEncoding(&ctx.s, encoding);
+ sqlite3VdbeMemMove(pOut, &ctx.s);
+ if( sqlite3VdbeMemTooBig(pOut) ){
+ goto too_big;
+ }
+ REGISTER_TRACE(pOp->p3, pOut);
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+
+/* Opcode: BitAnd P1 P2 P3 * *
+**
+** Take the bit-wise AND of the values in register P1 and P2 and
+** store the result in register P3.
+** If either input is NULL, the result is NULL.
+*/
+/* Opcode: BitOr P1 P2 P3 * *
+**
+** Take the bit-wise OR of the values in register P1 and P2 and
+** store the result in register P3.
+** If either input is NULL, the result is NULL.
+*/
+/* Opcode: ShiftLeft P1 P2 P3 * *
+**
+** Shift the integer value in register P2 to the left by the
+** number of bits specified by the integer in regiser P1.
+** Store the result in register P3.
+** If either input is NULL, the result is NULL.
+*/
+/* Opcode: ShiftRight P1 P2 P3 * *
+**
+** Shift the integer value in register P2 to the right by the
+** number of bits specified by the integer in register P1.
+** Store the result in register P3.
+** If either input is NULL, the result is NULL.
+*/
+case OP_BitAnd: /* same as TK_BITAND, in1, in2, out3 */
+case OP_BitOr: /* same as TK_BITOR, in1, in2, out3 */
+case OP_ShiftLeft: /* same as TK_LSHIFT, in1, in2, out3 */
+case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */
+ i64 a, b;
+
+ if( (pIn1->flags | pIn2->flags) & MEM_Null ){
+ sqlite3VdbeMemSetNull(pOut);
+ break;
+ }
+ a = sqlite3VdbeIntValue(pIn2);
+ b = sqlite3VdbeIntValue(pIn1);
+ switch( pOp->opcode ){
+ case OP_BitAnd: a &= b; break;
+ case OP_BitOr: a |= b; break;
+ case OP_ShiftLeft: a <<= b; break;
+ default: assert( pOp->opcode==OP_ShiftRight );
+ a >>= b; break;
+ }
+ pOut->u.i = a;
+ MemSetTypeFlag(pOut, MEM_Int);
+ break;
+}
+
+/* Opcode: AddImm P1 P2 * * *
+**
+** Add the constant P2 the value in register P1.
+** The result is always an integer.
+**
+** To force any register to be an integer, just add 0.
+*/
+case OP_AddImm: { /* in1 */
+ sqlite3VdbeMemIntegerify(pIn1);
+ pIn1->u.i += pOp->p2;
+ break;
+}
+
+/* Opcode: ForceInt P1 P2 P3 * *
+**
+** Convert value in register P1 into an integer. If the value
+** in P1 is not numeric (meaning that is is a NULL or a string that
+** does not look like an integer or floating point number) then
+** jump to P2. If the value in P1 is numeric then
+** convert it into the least integer that is greater than or equal to its
+** current value if P3==0, or to the least integer that is strictly
+** greater than its current value if P3==1.
+*/
+case OP_ForceInt: { /* jump, in1 */
+ i64 v;
+ applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding);
+ if( (pIn1->flags & (MEM_Int|MEM_Real))==0 ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+ if( pIn1->flags & MEM_Int ){
+ v = pIn1->u.i + (pOp->p3!=0);
+ }else{
+ assert( pIn1->flags & MEM_Real );
+ v = (sqlite3_int64)pIn1->r;
+ if( pIn1->r>(double)v ) v++;
+ if( pOp->p3 && pIn1->r==(double)v ) v++;
+ }
+ pIn1->u.i = v;
+ MemSetTypeFlag(pIn1, MEM_Int);
+ break;
+}
+
+/* Opcode: MustBeInt P1 P2 * * *
+**
+** Force the value in register P1 to be an integer. If the value
+** in P1 is not an integer and cannot be converted into an integer
+** without data loss, then jump immediately to P2, or if P2==0
+** raise an SQLITE_MISMATCH exception.
+*/
+case OP_MustBeInt: { /* jump, in1 */
+ applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding);
+ if( (pIn1->flags & MEM_Int)==0 ){
+ if( pOp->p2==0 ){
+ rc = SQLITE_MISMATCH;
+ goto abort_due_to_error;
+ }else{
+ pc = pOp->p2 - 1;
+ }
+ }else{
+ MemSetTypeFlag(pIn1, MEM_Int);
+ }
+ break;
+}
+
+/* Opcode: RealAffinity P1 * * * *
+**
+** If register P1 holds an integer convert it to a real value.
+**
+** This opcode is used when extracting information from a column that
+** has REAL affinity. Such column values may still be stored as
+** integers, for space efficiency, but after extraction we want them
+** to have only a real value.
+*/
+case OP_RealAffinity: { /* in1 */
+ if( pIn1->flags & MEM_Int ){
+ sqlite3VdbeMemRealify(pIn1);
+ }
+ break;
+}
+
+#ifndef SQLITE_OMIT_CAST
+/* Opcode: ToText P1 * * * *
+**
+** Force the value in register P1 to be text.
+** If the value is numeric, convert it to a string using the
+** equivalent of printf(). Blob values are unchanged and
+** are afterwards simply interpreted as text.
+**
+** A NULL value is not changed by this routine. It remains NULL.
+*/
+case OP_ToText: { /* same as TK_TO_TEXT, in1 */
+ if( pIn1->flags & MEM_Null ) break;
+ assert( MEM_Str==(MEM_Blob>>3) );
+ pIn1->flags |= (pIn1->flags&MEM_Blob)>>3;
+ applyAffinity(pIn1, SQLITE_AFF_TEXT, encoding);
+ rc = ExpandBlob(pIn1);
+ assert( pIn1->flags & MEM_Str || db->mallocFailed );
+ pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_Blob);
+ UPDATE_MAX_BLOBSIZE(pIn1);
+ break;
+}
+
+/* Opcode: ToBlob P1 * * * *
+**
+** Force the value in register P1 to be a BLOB.
+** If the value is numeric, convert it to a string first.
+** Strings are simply reinterpreted as blobs with no change
+** to the underlying data.
+**
+** A NULL value is not changed by this routine. It remains NULL.
+*/
+case OP_ToBlob: { /* same as TK_TO_BLOB, in1 */
+ if( pIn1->flags & MEM_Null ) break;
+ if( (pIn1->flags & MEM_Blob)==0 ){
+ applyAffinity(pIn1, SQLITE_AFF_TEXT, encoding);
+ assert( pIn1->flags & MEM_Str || db->mallocFailed );
+ }
+ MemSetTypeFlag(pIn1, MEM_Blob);
+ UPDATE_MAX_BLOBSIZE(pIn1);
+ break;
+}
+
+/* Opcode: ToNumeric P1 * * * *
+**
+** Force the value in register P1 to be numeric (either an
+** integer or a floating-point number.)
+** If the value is text or blob, try to convert it to an using the
+** equivalent of atoi() or atof() and store 0 if no such conversion
+** is possible.
+**
+** A NULL value is not changed by this routine. It remains NULL.
+*/
+case OP_ToNumeric: { /* same as TK_TO_NUMERIC, in1 */
+ if( (pIn1->flags & (MEM_Null|MEM_Int|MEM_Real))==0 ){
+ sqlite3VdbeMemNumerify(pIn1);
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_CAST */
+
+/* Opcode: ToInt P1 * * * *
+**
+** Force the value in register P1 be an integer. If
+** The value is currently a real number, drop its fractional part.
+** If the value is text or blob, try to convert it to an integer using the
+** equivalent of atoi() and store 0 if no such conversion is possible.
+**
+** A NULL value is not changed by this routine. It remains NULL.
+*/
+case OP_ToInt: { /* same as TK_TO_INT, in1 */
+ if( (pIn1->flags & MEM_Null)==0 ){
+ sqlite3VdbeMemIntegerify(pIn1);
+ }
+ break;
+}
+
+#ifndef SQLITE_OMIT_CAST
+/* Opcode: ToReal P1 * * * *
+**
+** Force the value in register P1 to be a floating point number.
+** If The value is currently an integer, convert it.
+** If the value is text or blob, try to convert it to an integer using the
+** equivalent of atoi() and store 0.0 if no such conversion is possible.
+**
+** A NULL value is not changed by this routine. It remains NULL.
+*/
+case OP_ToReal: { /* same as TK_TO_REAL, in1 */
+ if( (pIn1->flags & MEM_Null)==0 ){
+ sqlite3VdbeMemRealify(pIn1);
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_CAST */
+
+/* Opcode: Lt P1 P2 P3 P4 P5
+**
+** Compare the values in register P1 and P3. If reg(P3)<reg(P1) then
+** jump to address P2.
+**
+** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or
+** reg(P3) is NULL then take the jump. If the SQLITE_JUMPIFNULL
+** bit is clear then fall thru if either operand is NULL.
+**
+** If the SQLITE_NULLEQUAL bit of P5 is set then treat NULL operands
+** as being equal to one another. Normally NULLs are not equal to
+** anything including other NULLs.
+**
+** The SQLITE_AFF_MASK portion of P5 must be an affinity character -
+** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made
+** to coerce both inputs according to this affinity before the
+** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric
+** affinity is used. Note that the affinity conversions are stored
+** back into the input registers P1 and P3. So this opcode can cause
+** persistent changes to registers P1 and P3.
+**
+** Once any conversions have taken place, and neither value is NULL,
+** the values are compared. If both values are blobs then memcmp() is
+** used to determine the results of the comparison. If both values
+** are text, then the appropriate collating function specified in
+** P4 is used to do the comparison. If P4 is not specified then
+** memcmp() is used to compare text string. If both values are
+** numeric, then a numeric comparison is used. If the two values
+** are of different types, then numbers are considered less than
+** strings and strings are considered less than blobs.
+**
+** If the SQLITE_STOREP2 bit of P5 is set, then do not jump. Instead,
+** store a boolean result (either 0, or 1, or NULL) in register P2.
+*/
+/* Opcode: Ne P1 P2 P3 P4 P5
+**
+** This works just like the Lt opcode except that the jump is taken if
+** the operands in registers P1 and P3 are not equal. See the Lt opcode for
+** additional information.
+*/
+/* Opcode: Eq P1 P2 P3 P4 P5
+**
+** This works just like the Lt opcode except that the jump is taken if
+** the operands in registers P1 and P3 are equal.
+** See the Lt opcode for additional information.
+*/
+/* Opcode: Le P1 P2 P3 P4 P5
+**
+** This works just like the Lt opcode except that the jump is taken if
+** the content of register P3 is less than or equal to the content of
+** register P1. See the Lt opcode for additional information.
+*/
+/* Opcode: Gt P1 P2 P3 P4 P5
+**
+** This works just like the Lt opcode except that the jump is taken if
+** the content of register P3 is greater than the content of
+** register P1. See the Lt opcode for additional information.
+*/
+/* Opcode: Ge P1 P2 P3 P4 P5
+**
+** This works just like the Lt opcode except that the jump is taken if
+** the content of register P3 is greater than or equal to the content of
+** register P1. See the Lt opcode for additional information.
+*/
+case OP_Eq: /* same as TK_EQ, jump, in1, in3 */
+case OP_Ne: /* same as TK_NE, jump, in1, in3 */
+case OP_Lt: /* same as TK_LT, jump, in1, in3 */
+case OP_Le: /* same as TK_LE, jump, in1, in3 */
+case OP_Gt: /* same as TK_GT, jump, in1, in3 */
+case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
+ int flags;
+ int res;
+ char affinity;
+ Mem x1, x3;
+
+ flags = pIn1->flags|pIn3->flags;
+
+ if( flags&MEM_Null ){
+ if( (pOp->p5 & SQLITE_NULLEQUAL)!=0 ){
+ /*
+ ** When SQLITE_NULLEQUAL set and either operand is NULL
+ ** then both operands are converted to integers prior to being
+ ** passed down into the normal comparison logic below.
+ ** NULL operands are converted to zero and non-NULL operands
+ ** are converted to 1. Thus, for example, with SQLITE_NULLEQUAL
+ ** set, NULL==NULL is true whereas it would normally NULL.
+ ** Similarly, NULL!=123 is true.
+ */
+ x1.flags = MEM_Int;
+ x1.u.i = (pIn1->flags & MEM_Null)==0;
+ pIn1 = &x1;
+ x3.flags = MEM_Int;
+ x3.u.i = (pIn3->flags & MEM_Null)==0;
+ pIn3 = &x3;
+ }else{
+ /* If the SQLITE_NULLEQUAL bit is clear and either operand is NULL then
+ ** the result is always NULL. The jump is taken if the
+ ** SQLITE_JUMPIFNULL bit is set.
+ */
+ if( pOp->p5 & SQLITE_STOREP2 ){
+ pOut = &p->aMem[pOp->p2];
+ MemSetTypeFlag(pOut, MEM_Null);
+ REGISTER_TRACE(pOp->p2, pOut);
+ }else if( pOp->p5 & SQLITE_JUMPIFNULL ){
+ pc = pOp->p2-1;
+ }
+ break;
+ }
+ }
+
+ affinity = pOp->p5 & SQLITE_AFF_MASK;
+ if( affinity ){
+ applyAffinity(pIn1, affinity, encoding);
+ applyAffinity(pIn3, affinity, encoding);
+ }
+
+ assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 );
+ ExpandBlob(pIn1);
+ ExpandBlob(pIn3);
+ res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
+ switch( pOp->opcode ){
+ case OP_Eq: res = res==0; break;
+ case OP_Ne: res = res!=0; break;
+ case OP_Lt: res = res<0; break;
+ case OP_Le: res = res<=0; break;
+ case OP_Gt: res = res>0; break;
+ default: res = res>=0; break;
+ }
+
+ if( pOp->p5 & SQLITE_STOREP2 ){
+ pOut = &p->aMem[pOp->p2];
+ MemSetTypeFlag(pOut, MEM_Int);
+ pOut->u.i = res;
+ REGISTER_TRACE(pOp->p2, pOut);
+ }else if( res ){
+ pc = pOp->p2-1;
+ }
+ break;
+}
+
+/* Opcode: And P1 P2 P3 * *
+**
+** Take the logical AND of the values in registers P1 and P2 and
+** write the result into register P3.
+**
+** If either P1 or P2 is 0 (false) then the result is 0 even if
+** the other input is NULL. A NULL and true or two NULLs give
+** a NULL output.
+*/
+/* Opcode: Or P1 P2 P3 * *
+**
+** Take the logical OR of the values in register P1 and P2 and
+** store the answer in register P3.
+**
+** If either P1 or P2 is nonzero (true) then the result is 1 (true)
+** even if the other input is NULL. A NULL and false or two NULLs
+** give a NULL output.
+*/
+case OP_And: /* same as TK_AND, in1, in2, out3 */
+case OP_Or: { /* same as TK_OR, in1, in2, out3 */
+ int v1, v2; /* 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */
+
+ if( pIn1->flags & MEM_Null ){
+ v1 = 2;
+ }else{
+ v1 = sqlite3VdbeIntValue(pIn1)!=0;
+ }
+ if( pIn2->flags & MEM_Null ){
+ v2 = 2;
+ }else{
+ v2 = sqlite3VdbeIntValue(pIn2)!=0;
+ }
+ if( pOp->opcode==OP_And ){
+ static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 };
+ v1 = and_logic[v1*3+v2];
+ }else{
+ static const unsigned char or_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 };
+ v1 = or_logic[v1*3+v2];
+ }
+ if( v1==2 ){
+ MemSetTypeFlag(pOut, MEM_Null);
+ }else{
+ pOut->u.i = v1;
+ MemSetTypeFlag(pOut, MEM_Int);
+ }
+ break;
+}
+
+/* Opcode: Not P1 * * * *
+**
+** Interpret the value in register P1 as a boolean value. Replace it
+** with its complement. If the value in register P1 is NULL its value
+** is unchanged.
+*/
+case OP_Not: { /* same as TK_NOT, in1 */
+ if( pIn1->flags & MEM_Null ) break; /* Do nothing to NULLs */
+ sqlite3VdbeMemIntegerify(pIn1);
+ pIn1->u.i = !pIn1->u.i;
+ assert( pIn1->flags&MEM_Int );
+ break;
+}
+
+/* Opcode: BitNot P1 * * * *
+**
+** Interpret the content of register P1 as an integer. Replace it
+** with its ones-complement. If the value is originally NULL, leave
+** it unchanged.
+*/
+case OP_BitNot: { /* same as TK_BITNOT, in1 */
+ if( pIn1->flags & MEM_Null ) break; /* Do nothing to NULLs */
+ sqlite3VdbeMemIntegerify(pIn1);
+ pIn1->u.i = ~pIn1->u.i;
+ assert( pIn1->flags&MEM_Int );
+ break;
+}
+
+/* Opcode: If P1 P2 P3 * *
+**
+** Jump to P2 if the value in register P1 is true. The value is
+** is considered true if it is numeric and non-zero. If the value
+** in P1 is NULL then take the jump if P3 is true.
+*/
+/* Opcode: IfNot P1 P2 P3 * *
+**
+** Jump to P2 if the value in register P1 is False. The value is
+** is considered true if it has a numeric value of zero. If the value
+** in P1 is NULL then take the jump if P3 is true.
+*/
+case OP_If: /* jump, in1 */
+case OP_IfNot: { /* jump, in1 */
+ int c;
+ if( pIn1->flags & MEM_Null ){
+ c = pOp->p3;
+ }else{
+#ifdef SQLITE_OMIT_FLOATING_POINT
+ c = sqlite3VdbeIntValue(pIn1);
+#else
+ c = sqlite3VdbeRealValue(pIn1)!=0.0;
+#endif
+ if( pOp->opcode==OP_IfNot ) c = !c;
+ }
+ if( c ){
+ pc = pOp->p2-1;
+ }
+ break;
+}
+
+/* Opcode: IsNull P1 P2 P3 * *
+**
+** Jump to P2 if the value in register P1 is NULL. If P3 is greater
+** than zero, then check all values reg(P1), reg(P1+1),
+** reg(P1+2), ..., reg(P1+P3-1).
+*/
+case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */
+ int n = pOp->p3;
+ assert( pOp->p3==0 || pOp->p1>0 );
+ do{
+ if( (pIn1->flags & MEM_Null)!=0 ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+ pIn1++;
+ }while( --n > 0 );
+ break;
+}
+
+/* Opcode: NotNull P1 P2 * * *
+**
+** Jump to P2 if the value in register P1 is not NULL.
+*/
+case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */
+ if( (pIn1->flags & MEM_Null)==0 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: SetNumColumns * P2 * * *
+**
+** This opcode sets the number of columns for the cursor opened by the
+** following instruction to P2.
+**
+** An OP_SetNumColumns is only useful if it occurs immediately before
+** one of the following opcodes:
+**
+** OpenRead
+** OpenWrite
+** OpenPseudo
+**
+** If the OP_Column opcode is to be executed on a cursor, then
+** this opcode must be present immediately before the opcode that
+** opens the cursor.
+*/
+case OP_SetNumColumns: {
+ break;
+}
+
+/* Opcode: Column P1 P2 P3 P4 *
+**
+** Interpret the data that cursor P1 points to as a structure built using
+** the MakeRecord instruction. (See the MakeRecord opcode for additional
+** information about the format of the data.) Extract the P2-th column
+** from this record. If there are less that (P2+1)
+** values in the record, extract a NULL.
+**
+** The value extracted is stored in register P3.
+**
+** If the KeyAsData opcode has previously executed on this cursor, then the
+** field might be extracted from the key rather than the data.
+**
+** If the column contains fewer than P2 fields, then extract a NULL. Or,
+** if the P4 argument is a P4_MEM use the value of the P4 argument as
+** the result.
+*/
+case OP_Column: {
+ u32 payloadSize; /* Number of bytes in the record */
+ int p1 = pOp->p1; /* P1 value of the opcode */
+ int p2 = pOp->p2; /* column number to retrieve */
+ Cursor *pC = 0; /* The VDBE cursor */
+ char *zRec; /* Pointer to complete record-data */
+ BtCursor *pCrsr; /* The BTree cursor */
+ u32 *aType; /* aType[i] holds the numeric type of the i-th column */
+ u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */
+ u32 nField; /* number of fields in the record */
+ int len; /* The length of the serialized data for the column */
+ int i; /* Loop counter */
+ char *zData; /* Part of the record being decoded */
+ Mem *pDest; /* Where to write the extracted value */
+ Mem sMem; /* For storing the record being decoded */
+
+ sMem.flags = 0;
+ sMem.db = 0;
+ sMem.zMalloc = 0;
+ assert( p1<p->nCursor );
+ assert( pOp->p3>0 && pOp->p3<=p->nMem );
+ pDest = &p->aMem[pOp->p3];
+ MemSetTypeFlag(pDest, MEM_Null);
+
+ /* This block sets the variable payloadSize to be the total number of
+ ** bytes in the record.
+ **
+ ** zRec is set to be the complete text of the record if it is available.
+ ** The complete record text is always available for pseudo-tables
+ ** If the record is stored in a cursor, the complete record text
+ ** might be available in the pC->aRow cache. Or it might not be.
+ ** If the data is unavailable, zRec is set to NULL.
+ **
+ ** We also compute the number of columns in the record. For cursors,
+ ** the number of columns is stored in the Cursor.nField element.
+ */
+ pC = p->apCsr[p1];
+ assert( pC!=0 );
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ assert( pC->pVtabCursor==0 );
+#endif
+ if( pC->pCursor!=0 ){
+ /* The record is stored in a B-Tree */
+ rc = sqlite3VdbeCursorMoveto(pC);
+ if( rc ) goto abort_due_to_error;
+ zRec = 0;
+ pCrsr = pC->pCursor;
+ if( pC->nullRow ){
+ payloadSize = 0;
+ }else if( pC->cacheStatus==p->cacheCtr ){
+ payloadSize = pC->payloadSize;
+ zRec = (char*)pC->aRow;
+ }else if( pC->isIndex ){
+ i64 payloadSize64;
+ sqlite3BtreeKeySize(pCrsr, &payloadSize64);
+ payloadSize = payloadSize64;
+ }else{
+ sqlite3BtreeDataSize(pCrsr, &payloadSize);
+ }
+ nField = pC->nField;
+ }else{
+ assert( pC->pseudoTable );
+ /* The record is the sole entry of a pseudo-table */
+ payloadSize = pC->nData;
+ zRec = pC->pData;
+ pC->cacheStatus = CACHE_STALE;
+ assert( payloadSize==0 || zRec!=0 );
+ nField = pC->nField;
+ pCrsr = 0;
+ }
+
+ /* If payloadSize is 0, then just store a NULL */
+ if( payloadSize==0 ){
+ assert( pDest->flags&MEM_Null );
+ goto op_column_out;
+ }
+ if( payloadSize>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+
+ assert( p2<nField );
+
+ /* Read and parse the table header. Store the results of the parse
+ ** into the record header cache fields of the cursor.
+ */
+ aType = pC->aType;
+ if( pC->cacheStatus==p->cacheCtr ){
+ aOffset = pC->aOffset;
+ }else{
+ u8 *zIdx; /* Index into header */
+ u8 *zEndHdr; /* Pointer to first byte after the header */
+ u32 offset; /* Offset into the data */
+ int szHdrSz; /* Size of the header size field at start of record */
+ int avail; /* Number of bytes of available data */
+
+ assert(aType);
+ pC->aOffset = aOffset = &aType[nField];
+ pC->payloadSize = payloadSize;
+ pC->cacheStatus = p->cacheCtr;
+
+ /* Figure out how many bytes are in the header */
+ if( zRec ){
+ zData = zRec;
+ }else{
+ if( pC->isIndex ){
+ zData = (char*)sqlite3BtreeKeyFetch(pCrsr, &avail);
+ }else{
+ zData = (char*)sqlite3BtreeDataFetch(pCrsr, &avail);
+ }
+ /* If KeyFetch()/DataFetch() managed to get the entire payload,
+ ** save the payload in the pC->aRow cache. That will save us from
+ ** having to make additional calls to fetch the content portion of
+ ** the record.
+ */
+ if( avail>=payloadSize ){
+ zRec = zData;
+ pC->aRow = (u8*)zData;
+ }else{
+ pC->aRow = 0;
+ }
+ }
+ /* The following assert is true in all cases accept when
+ ** the database file has been corrupted externally.
+ ** assert( zRec!=0 || avail>=payloadSize || avail>=9 ); */
+ szHdrSz = getVarint32((u8*)zData, offset);
+
+ /* The KeyFetch() or DataFetch() above are fast and will get the entire
+ ** record header in most cases. But they will fail to get the complete
+ ** record header if the record header does not fit on a single page
+ ** in the B-Tree. When that happens, use sqlite3VdbeMemFromBtree() to
+ ** acquire the complete header text.
+ */
+ if( !zRec && avail<offset ){
+ sMem.flags = 0;
+ sMem.db = 0;
+ rc = sqlite3VdbeMemFromBtree(pCrsr, 0, offset, pC->isIndex, &sMem);
+ if( rc!=SQLITE_OK ){
+ goto op_column_out;
+ }
+ zData = sMem.z;
+ }
+ zEndHdr = (u8 *)&zData[offset];
+ zIdx = (u8 *)&zData[szHdrSz];
+
+ /* Scan the header and use it to fill in the aType[] and aOffset[]
+ ** arrays. aType[i] will contain the type integer for the i-th
+ ** column and aOffset[i] will contain the offset from the beginning
+ ** of the record to the start of the data for the i-th column
+ */
+ for(i=0; i<nField; i++){
+ if( zIdx<zEndHdr ){
+ aOffset[i] = offset;
+ zIdx += getVarint32(zIdx, aType[i]);
+ offset += sqlite3VdbeSerialTypeLen(aType[i]);
+ }else{
+ /* If i is less that nField, then there are less fields in this
+ ** record than SetNumColumns indicated there are columns in the
+ ** table. Set the offset for any extra columns not present in
+ ** the record to 0. This tells code below to store a NULL
+ ** instead of deserializing a value from the record.
+ */
+ aOffset[i] = 0;
+ }
+ }
+ sqlite3VdbeMemRelease(&sMem);
+ sMem.flags = MEM_Null;
+
+ /* If we have read more header data than was contained in the header,
+ ** or if the end of the last field appears to be past the end of the
+ ** record, or if the end of the last field appears to be before the end
+ ** of the record (when all fields present), then we must be dealing
+ ** with a corrupt database.
+ */
+ if( zIdx>zEndHdr || offset>payloadSize || (zIdx==zEndHdr && offset!=payloadSize) ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto op_column_out;
+ }
+ }
+
+ /* Get the column information. If aOffset[p2] is non-zero, then
+ ** deserialize the value from the record. If aOffset[p2] is zero,
+ ** then there are not enough fields in the record to satisfy the
+ ** request. In this case, set the value NULL or to P4 if P4 is
+ ** a pointer to a Mem object.
+ */
+ if( aOffset[p2] ){
+ assert( rc==SQLITE_OK );
+ if( zRec ){
+ if( pDest->flags&MEM_Dyn ){
+ sqlite3VdbeSerialGet((u8 *)&zRec[aOffset[p2]], aType[p2], &sMem);
+ sMem.db = db;
+ rc = sqlite3VdbeMemCopy(pDest, &sMem);
+ assert( !(sMem.flags&MEM_Dyn) );
+ if( rc!=SQLITE_OK ){
+ goto op_column_out;
+ }
+ }else{
+ sqlite3VdbeSerialGet((u8 *)&zRec[aOffset[p2]], aType[p2], pDest);
+ }
+ }else{
+ len = sqlite3VdbeSerialTypeLen(aType[p2]);
+ sqlite3VdbeMemMove(&sMem, pDest);
+ rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, pC->isIndex, &sMem);
+ if( rc!=SQLITE_OK ){
+ goto op_column_out;
+ }
+ zData = sMem.z;
+ sqlite3VdbeSerialGet((u8*)zData, aType[p2], pDest);
+ }
+ pDest->enc = encoding;
+ }else{
+ if( pOp->p4type==P4_MEM ){
+ sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static);
+ }else{
+ assert( pDest->flags&MEM_Null );
+ }
+ }
+
+ /* If we dynamically allocated space to hold the data (in the
+ ** sqlite3VdbeMemFromBtree() call above) then transfer control of that
+ ** dynamically allocated space over to the pDest structure.
+ ** This prevents a memory copy.
+ */
+ if( sMem.zMalloc ){
+ assert( sMem.z==sMem.zMalloc );
+ assert( !(pDest->flags & MEM_Dyn) );
+ assert( !(pDest->flags & (MEM_Blob|MEM_Str)) || pDest->z==sMem.z );
+ pDest->flags &= ~(MEM_Ephem|MEM_Static);
+ pDest->flags |= MEM_Term;
+ pDest->z = sMem.z;
+ pDest->zMalloc = sMem.zMalloc;
+ }
+
+ rc = sqlite3VdbeMemMakeWriteable(pDest);
+
+op_column_out:
+ UPDATE_MAX_BLOBSIZE(pDest);
+ REGISTER_TRACE(pOp->p3, pDest);
+ break;
+}
+
+/* Opcode: Affinity P1 P2 * P4 *
+**
+** Apply affinities to a range of P2 registers starting with P1.
+**
+** P4 is a string that is P2 characters long. The nth character of the
+** string indicates the column affinity that should be used for the nth
+** memory cell in the range.
+*/
+case OP_Affinity: {
+ char *zAffinity = pOp->p4.z;
+ Mem *pData0 = &p->aMem[pOp->p1];
+ Mem *pLast = &pData0[pOp->p2-1];
+ Mem *pRec;
+
+ for(pRec=pData0; pRec<=pLast; pRec++){
+ ExpandBlob(pRec);
+ applyAffinity(pRec, zAffinity[pRec-pData0], encoding);
+ }
+ break;
+}
+
+/* Opcode: MakeRecord P1 P2 P3 P4 *
+**
+** Convert P2 registers beginning with P1 into a single entry
+** suitable for use as a data record in a database table or as a key
+** in an index. The details of the format are irrelavant as long as
+** the OP_Column opcode can decode the record later.
+** Refer to source code comments for the details of the record
+** format.
+**
+** P4 may be a string that is P2 characters long. The nth character of the
+** string indicates the column affinity that should be used for the nth
+** field of the index key.
+**
+** The mapping from character to affinity is given by the SQLITE_AFF_
+** macros defined in sqliteInt.h.
+**
+** If P4 is NULL then all index fields have the affinity NONE.
+*/
+case OP_MakeRecord: {
+ /* Assuming the record contains N fields, the record format looks
+ ** like this:
+ **
+ ** ------------------------------------------------------------------------
+ ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 |
+ ** ------------------------------------------------------------------------
+ **
+ ** Data(0) is taken from register P1. Data(1) comes from register P1+1
+ ** and so froth.
+ **
+ ** Each type field is a varint representing the serial type of the
+ ** corresponding data element (see sqlite3VdbeSerialType()). The
+ ** hdr-size field is also a varint which is the offset from the beginning
+ ** of the record to data0.
+ */
+ u8 *zNewRecord; /* A buffer to hold the data for the new record */
+ Mem *pRec; /* The new record */
+ u64 nData = 0; /* Number of bytes of data space */
+ int nHdr = 0; /* Number of bytes of header space */
+ u64 nByte = 0; /* Data space required for this record */
+ int nZero = 0; /* Number of zero bytes at the end of the record */
+ int nVarint; /* Number of bytes in a varint */
+ u32 serial_type; /* Type field */
+ Mem *pData0; /* First field to be combined into the record */
+ Mem *pLast; /* Last field of the record */
+ int nField; /* Number of fields in the record */
+ char *zAffinity; /* The affinity string for the record */
+ int file_format; /* File format to use for encoding */
+ int i; /* Space used in zNewRecord[] */
+
+ nField = pOp->p1;
+ zAffinity = pOp->p4.z;
+ assert( nField>0 && pOp->p2>0 && pOp->p2+nField<=p->nMem );
+ pData0 = &p->aMem[nField];
+ nField = pOp->p2;
+ pLast = &pData0[nField-1];
+ file_format = p->minWriteFileFormat;
+
+ /* Loop through the elements that will make up the record to figure
+ ** out how much space is required for the new record.
+ */
+ for(pRec=pData0; pRec<=pLast; pRec++){
+ int len;
+ if( zAffinity ){
+ applyAffinity(pRec, zAffinity[pRec-pData0], encoding);
+ }
+ if( pRec->flags&MEM_Zero && pRec->n>0 ){
+ sqlite3VdbeMemExpandBlob(pRec);
+ }
+ serial_type = sqlite3VdbeSerialType(pRec, file_format);
+ len = sqlite3VdbeSerialTypeLen(serial_type);
+ nData += len;
+ nHdr += sqlite3VarintLen(serial_type);
+ if( pRec->flags & MEM_Zero ){
+ /* Only pure zero-filled BLOBs can be input to this Opcode.
+ ** We do not allow blobs with a prefix and a zero-filled tail. */
+ nZero += pRec->u.i;
+ }else if( len ){
+ nZero = 0;
+ }
+ }
+
+ /* Add the initial header varint and total the size */
+ nHdr += nVarint = sqlite3VarintLen(nHdr);
+ if( nVarint<sqlite3VarintLen(nHdr) ){
+ nHdr++;
+ }
+ nByte = nHdr+nData-nZero;
+ if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+
+ /* Make sure the output register has a buffer large enough to store
+ ** the new record. The output register (pOp->p3) is not allowed to
+ ** be one of the input registers (because the following call to
+ ** sqlite3VdbeMemGrow() could clobber the value before it is used).
+ */
+ assert( pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2 );
+ pOut = &p->aMem[pOp->p3];
+ if( sqlite3VdbeMemGrow(pOut, nByte, 0) ){
+ goto no_mem;
+ }
+ zNewRecord = (u8 *)pOut->z;
+
+ /* Write the record */
+ i = putVarint32(zNewRecord, nHdr);
+ for(pRec=pData0; pRec<=pLast; pRec++){
+ serial_type = sqlite3VdbeSerialType(pRec, file_format);
+ i += putVarint32(&zNewRecord[i], serial_type); /* serial type */
+ }
+ for(pRec=pData0; pRec<=pLast; pRec++){ /* serial data */
+ i += sqlite3VdbeSerialPut(&zNewRecord[i], nByte-i, pRec, file_format);
+ }
+ assert( i==nByte );
+
+ assert( pOp->p3>0 && pOp->p3<=p->nMem );
+ pOut->n = nByte;
+ pOut->flags = MEM_Blob | MEM_Dyn;
+ pOut->xDel = 0;
+ if( nZero ){
+ pOut->u.i = nZero;
+ pOut->flags |= MEM_Zero;
+ }
+ pOut->enc = SQLITE_UTF8; /* In case the blob is ever converted to text */
+ REGISTER_TRACE(pOp->p3, pOut);
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+
+/* Opcode: Statement P1 * * * *
+**
+** Begin an individual statement transaction which is part of a larger
+** transaction. This is needed so that the statement
+** can be rolled back after an error without having to roll back the
+** entire transaction. The statement transaction will automatically
+** commit when the VDBE halts.
+**
+** If the database connection is currently in autocommit mode (that
+** is to say, if it is in between BEGIN and COMMIT)
+** and if there are no other active statements on the same database
+** connection, then this operation is a no-op. No statement transaction
+** is needed since any error can use the normal ROLLBACK process to
+** undo changes.
+**
+** If a statement transaction is started, then a statement journal file
+** will be allocated and initialized.
+**
+** The statement is begun on the database file with index P1. The main
+** database file has an index of 0 and the file used for temporary tables
+** has an index of 1.
+*/
+case OP_Statement: {
+ if( db->autoCommit==0 || db->activeVdbeCnt>1 ){
+ int i = pOp->p1;
+ Btree *pBt;
+ assert( i>=0 && i<db->nDb );
+ assert( db->aDb[i].pBt!=0 );
+ pBt = db->aDb[i].pBt;
+ assert( sqlite3BtreeIsInTrans(pBt) );
+ assert( (p->btreeMask & (1<<i))!=0 );
+ if( !sqlite3BtreeIsInStmt(pBt) ){
+ rc = sqlite3BtreeBeginStmt(pBt);
+ p->openedStatement = 1;
+ }
+ }
+ break;
+}
+
+/* Opcode: AutoCommit P1 P2 * * *
+**
+** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll
+** back any currently active btree transactions. If there are any active
+** VMs (apart from this one), then the COMMIT or ROLLBACK statement fails.
+**
+** This instruction causes the VM to halt.
+*/
+case OP_AutoCommit: {
+ u8 i = pOp->p1;
+ u8 rollback = pOp->p2;
+
+ assert( i==1 || i==0 );
+ assert( i==1 || rollback==0 );
+
+ assert( db->activeVdbeCnt>0 ); /* At least this one VM is active */
+
+ if( db->activeVdbeCnt>1 && i && !db->autoCommit ){
+ /* If this instruction implements a COMMIT or ROLLBACK, other VMs are
+ ** still running, and a transaction is active, return an error indicating
+ ** that the other VMs must complete first.
+ */
+ sqlite3SetString(&p->zErrMsg, "cannot ", rollback?"rollback":"commit",
+ " transaction - SQL statements in progress", (char*)0);
+ rc = SQLITE_ERROR;
+ }else if( i!=db->autoCommit ){
+ if( pOp->p2 ){
+ assert( i==1 );
+ sqlite3RollbackAll(db);
+ db->autoCommit = 1;
+ }else{
+ db->autoCommit = i;
+ if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
+ p->pc = pc;
+ db->autoCommit = 1-i;
+ p->rc = rc = SQLITE_BUSY;
+ goto vdbe_return;
+ }
+ }
+ if( p->rc==SQLITE_OK ){
+ rc = SQLITE_DONE;
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ goto vdbe_return;
+ }else{
+ sqlite3SetString(&p->zErrMsg,
+ (!i)?"cannot start a transaction within a transaction":(
+ (rollback)?"cannot rollback - no transaction is active":
+ "cannot commit - no transaction is active"), (char*)0);
+
+ rc = SQLITE_ERROR;
+ }
+ break;
+}
+
+/* Opcode: Transaction P1 P2 * * *
+**
+** Begin a transaction. The transaction ends when a Commit or Rollback
+** opcode is encountered. Depending on the ON CONFLICT setting, the
+** transaction might also be rolled back if an error is encountered.
+**
+** P1 is the index of the database file on which the transaction is
+** started. Index 0 is the main database file and index 1 is the
+** file used for temporary tables. Indices of 2 or more are used for
+** attached databases.
+**
+** If P2 is non-zero, then a write-transaction is started. A RESERVED lock is
+** obtained on the database file when a write-transaction is started. No
+** other process can start another write transaction while this transaction is
+** underway. Starting a write transaction also creates a rollback journal. A
+** write transaction must be started before any changes can be made to the
+** database. If P2 is 2 or greater then an EXCLUSIVE lock is also obtained
+** on the file.
+**
+** If P2 is zero, then a read-lock is obtained on the database file.
+*/
+case OP_Transaction: {
+ int i = pOp->p1;
+ Btree *pBt;
+
+ assert( i>=0 && i<db->nDb );
+ assert( (p->btreeMask & (1<<i))!=0 );
+ pBt = db->aDb[i].pBt;
+
+ if( pBt ){
+ rc = sqlite3BtreeBeginTrans(pBt, pOp->p2);
+ if( rc==SQLITE_BUSY ){
+ p->pc = pc;
+ p->rc = rc = SQLITE_BUSY;
+ goto vdbe_return;
+ }
+ if( rc!=SQLITE_OK && rc!=SQLITE_READONLY /* && rc!=SQLITE_BUSY */ ){
+ goto abort_due_to_error;
+ }
+ }
+ break;
+}
+
+/* Opcode: ReadCookie P1 P2 P3 * *
+**
+** Read cookie number P3 from database P1 and write it into register P2.
+** P3==0 is the schema version. P3==1 is the database format.
+** P3==2 is the recommended pager cache size, and so forth. P1==0 is
+** the main database file and P1==1 is the database file used to store
+** temporary tables.
+**
+** If P1 is negative, then this is a request to read the size of a
+** databases free-list. P3 must be set to 1 in this case. The actual
+** database accessed is ((P1+1)*-1). For example, a P1 parameter of -1
+** corresponds to database 0 ("main"), a P1 of -2 is database 1 ("temp").
+**
+** There must be a read-lock on the database (either a transaction
+** must be started or there must be an open cursor) before
+** executing this instruction.
+*/
+case OP_ReadCookie: { /* out2-prerelease */
+ int iMeta;
+ int iDb = pOp->p1;
+ int iCookie = pOp->p3;
+
+ assert( pOp->p3<SQLITE_N_BTREE_META );
+ if( iDb<0 ){
+ iDb = (-1*(iDb+1));
+ iCookie *= -1;
+ }
+ assert( iDb>=0 && iDb<db->nDb );
+ assert( db->aDb[iDb].pBt!=0 );
+ assert( (p->btreeMask & (1<<iDb))!=0 );
+ /* The indexing of meta values at the schema layer is off by one from
+ ** the indexing in the btree layer. The btree considers meta[0] to
+ ** be the number of free pages in the database (a read-only value)
+ ** and meta[1] to be the schema cookie. The schema layer considers
+ ** meta[1] to be the schema cookie. So we have to shift the index
+ ** by one in the following statement.
+ */
+ rc = sqlite3BtreeGetMeta(db->aDb[iDb].pBt, 1 + iCookie, (u32 *)&iMeta);
+ pOut->u.i = iMeta;
+ MemSetTypeFlag(pOut, MEM_Int);
+ break;
+}
+
+/* Opcode: SetCookie P1 P2 P3 * *
+**
+** Write the content of register P3 (interpreted as an integer)
+** into cookie number P2 of database P1.
+** P2==0 is the schema version. P2==1 is the database format.
+** P2==2 is the recommended pager cache size, and so forth. P1==0 is
+** the main database file and P1==1 is the database file used to store
+** temporary tables.
+**
+** A transaction must be started before executing this opcode.
+*/
+case OP_SetCookie: { /* in3 */
+ Db *pDb;
+ assert( pOp->p2<SQLITE_N_BTREE_META );
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( (p->btreeMask & (1<<pOp->p1))!=0 );
+ pDb = &db->aDb[pOp->p1];
+ assert( pDb->pBt!=0 );
+ sqlite3VdbeMemIntegerify(pIn3);
+ /* See note about index shifting on OP_ReadCookie */
+ rc = sqlite3BtreeUpdateMeta(pDb->pBt, 1+pOp->p2, (int)pIn3->u.i);
+ if( pOp->p2==0 ){
+ /* When the schema cookie changes, record the new cookie internally */
+ pDb->pSchema->schema_cookie = pIn3->u.i;
+ db->flags |= SQLITE_InternChanges;
+ }else if( pOp->p2==1 ){
+ /* Record changes in the file format */
+ pDb->pSchema->file_format = pIn3->u.i;
+ }
+ if( pOp->p1==1 ){
+ /* Invalidate all prepared statements whenever the TEMP database
+ ** schema is changed. Ticket #1644 */
+ sqlite3ExpirePreparedStatements(db);
+ }
+ break;
+}
+
+/* Opcode: VerifyCookie P1 P2 *
+**
+** Check the value of global database parameter number 0 (the
+** schema version) and make sure it is equal to P2.
+** P1 is the database number which is 0 for the main database file
+** and 1 for the file holding temporary tables and some higher number
+** for auxiliary databases.
+**
+** The cookie changes its value whenever the database schema changes.
+** This operation is used to detect when that the cookie has changed
+** and that the current process needs to reread the schema.
+**
+** Either a transaction needs to have been started or an OP_Open needs
+** to be executed (to establish a read lock) before this opcode is
+** invoked.
+*/
+case OP_VerifyCookie: {
+ int iMeta;
+ Btree *pBt;
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( (p->btreeMask & (1<<pOp->p1))!=0 );
+ pBt = db->aDb[pOp->p1].pBt;
+ if( pBt ){
+ rc = sqlite3BtreeGetMeta(pBt, 1, (u32 *)&iMeta);
+ }else{
+ rc = SQLITE_OK;
+ iMeta = 0;
+ }
+ if( rc==SQLITE_OK && iMeta!=pOp->p2 ){
+ sqlite3_free(p->zErrMsg);
+ p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed");
+ /* If the schema-cookie from the database file matches the cookie
+ ** stored with the in-memory representation of the schema, do
+ ** not reload the schema from the database file.
+ **
+ ** If virtual-tables are in use, this is not just an optimisation.
+ ** Often, v-tables store their data in other SQLite tables, which
+ ** are queried from within xNext() and other v-table methods using
+ ** prepared queries. If such a query is out-of-date, we do not want to
+ ** discard the database schema, as the user code implementing the
+ ** v-table would have to be ready for the sqlite3_vtab structure itself
+ ** to be invalidated whenever sqlite3_step() is called from within
+ ** a v-table method.
+ */
+ if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){
+ sqlite3ResetInternalSchema(db, pOp->p1);
+ }
+
+ sqlite3ExpirePreparedStatements(db);
+ rc = SQLITE_SCHEMA;
+ }
+ break;
+}
+
+/* Opcode: OpenRead P1 P2 P3 P4 P5
+**
+** Open a read-only cursor for the database table whose root page is
+** P2 in a database file. The database file is determined by P3.
+** P3==0 means the main database, P3==1 means the database used for
+** temporary tables, and P3>1 means used the corresponding attached
+** database. Give the new cursor an identifier of P1. The P1
+** values need not be contiguous but all P1 values should be small integers.
+** It is an error for P1 to be negative.
+**
+** If P5!=0 then use the content of register P2 as the root page, not
+** the value of P2 itself.
+**
+** There will be a read lock on the database whenever there is an
+** open cursor. If the database was unlocked prior to this instruction
+** then a read lock is acquired as part of this instruction. A read
+** lock allows other processes to read the database but prohibits
+** any other process from modifying the database. The read lock is
+** released when all cursors are closed. If this instruction attempts
+** to get a read lock but fails, the script terminates with an
+** SQLITE_BUSY error code.
+**
+** The P4 value is a pointer to a KeyInfo structure that defines the
+** content and collating sequence of indices. P4 is NULL for cursors
+** that are not pointing to indices.
+**
+** See also OpenWrite.
+*/
+/* Opcode: OpenWrite P1 P2 P3 P4 P5
+**
+** Open a read/write cursor named P1 on the table or index whose root
+** page is P2. Or if P5!=0 use the content of register P2 to find the
+** root page.
+**
+** The P4 value is a pointer to a KeyInfo structure that defines the
+** content and collating sequence of indices. P4 is NULL for cursors
+** that are not pointing to indices.
+**
+** This instruction works just like OpenRead except that it opens the cursor
+** in read/write mode. For a given table, there can be one or more read-only
+** cursors or a single read/write cursor but not both.
+**
+** See also OpenRead.
+*/
+case OP_OpenRead:
+case OP_OpenWrite: {
+ int i = pOp->p1;
+ int p2 = pOp->p2;
+ int iDb = pOp->p3;
+ int wrFlag;
+ Btree *pX;
+ Cursor *pCur;
+ Db *pDb;
+
+ assert( iDb>=0 && iDb<db->nDb );
+ assert( (p->btreeMask & (1<<iDb))!=0 );
+ pDb = &db->aDb[iDb];
+ pX = pDb->pBt;
+ assert( pX!=0 );
+ if( pOp->opcode==OP_OpenWrite ){
+ wrFlag = 1;
+ if( pDb->pSchema->file_format < p->minWriteFileFormat ){
+ p->minWriteFileFormat = pDb->pSchema->file_format;
+ }
+ }else{
+ wrFlag = 0;
+ }
+ if( pOp->p5 ){
+ assert( p2>0 );
+ assert( p2<=p->nMem );
+ pIn2 = &p->aMem[p2];
+ sqlite3VdbeMemIntegerify(pIn2);
+ p2 = pIn2->u.i;
+ assert( p2>=2 );
+ }
+ assert( i>=0 );
+ pCur = allocateCursor(p, i, &pOp[-1], iDb, 1);
+ if( pCur==0 ) goto no_mem;
+ pCur->nullRow = 1;
+ rc = sqlite3BtreeCursor(pX, p2, wrFlag, pOp->p4.p, pCur->pCursor);
+ if( pOp->p4type==P4_KEYINFO ){
+ pCur->pKeyInfo = pOp->p4.pKeyInfo;
+ pCur->pIncrKey = &pCur->pKeyInfo->incrKey;
+ pCur->pKeyInfo->enc = ENC(p->db);
+ }else{
+ pCur->pKeyInfo = 0;
+ pCur->pIncrKey = &pCur->bogusIncrKey;
+ }
+ switch( rc ){
+ case SQLITE_BUSY: {
+ p->pc = pc;
+ p->rc = rc = SQLITE_BUSY;
+ goto vdbe_return;
+ }
+ case SQLITE_OK: {
+ int flags = sqlite3BtreeFlags(pCur->pCursor);
+ /* Sanity checking. Only the lower four bits of the flags byte should
+ ** be used. Bit 3 (mask 0x08) is unpreditable. The lower 3 bits
+ ** (mask 0x07) should be either 5 (intkey+leafdata for tables) or
+ ** 2 (zerodata for indices). If these conditions are not met it can
+ ** only mean that we are dealing with a corrupt database file
+ */
+ if( (flags & 0xf0)!=0 || ((flags & 0x07)!=5 && (flags & 0x07)!=2) ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto abort_due_to_error;
+ }
+ pCur->isTable = (flags & BTREE_INTKEY)!=0;
+ pCur->isIndex = (flags & BTREE_ZERODATA)!=0;
+ /* If P4==0 it means we are expected to open a table. If P4!=0 then
+ ** we expect to be opening an index. If this is not what happened,
+ ** then the database is corrupt
+ */
+ if( (pCur->isTable && pOp->p4type==P4_KEYINFO)
+ || (pCur->isIndex && pOp->p4type!=P4_KEYINFO) ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto abort_due_to_error;
+ }
+ break;
+ }
+ case SQLITE_EMPTY: {
+ pCur->isTable = pOp->p4type!=P4_KEYINFO;
+ pCur->isIndex = !pCur->isTable;
+ pCur->pCursor = 0;
+ rc = SQLITE_OK;
+ break;
+ }
+ default: {
+ goto abort_due_to_error;
+ }
+ }
+ break;
+}
+
+/* Opcode: OpenEphemeral P1 P2 * P4 *
+**
+** Open a new cursor P1 to a transient table.
+** The cursor is always opened read/write even if
+** the main database is read-only. The transient or virtual
+** table is deleted automatically when the cursor is closed.
+**
+** P2 is the number of columns in the virtual table.
+** The cursor points to a BTree table if P4==0 and to a BTree index
+** if P4 is not 0. If P4 is not NULL, it points to a KeyInfo structure
+** that defines the format of keys in the index.
+**
+** This opcode was once called OpenTemp. But that created
+** confusion because the term "temp table", might refer either
+** to a TEMP table at the SQL level, or to a table opened by
+** this opcode. Then this opcode was call OpenVirtual. But
+** that created confusion with the whole virtual-table idea.
+*/
+case OP_OpenEphemeral: {
+ int i = pOp->p1;
+ Cursor *pCx;
+ static const int openFlags =
+ SQLITE_OPEN_READWRITE |
+ SQLITE_OPEN_CREATE |
+ SQLITE_OPEN_EXCLUSIVE |
+ SQLITE_OPEN_DELETEONCLOSE |
+ SQLITE_OPEN_TRANSIENT_DB;
+
+ assert( i>=0 );
+ pCx = allocateCursor(p, i, pOp, -1, 1);
+ if( pCx==0 ) goto no_mem;
+ pCx->nullRow = 1;
+ rc = sqlite3BtreeFactory(db, 0, 1, SQLITE_DEFAULT_TEMP_CACHE_SIZE, openFlags,
+ &pCx->pBt);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3BtreeBeginTrans(pCx->pBt, 1);
+ }
+ if( rc==SQLITE_OK ){
+ /* If a transient index is required, create it by calling
+ ** sqlite3BtreeCreateTable() with the BTREE_ZERODATA flag before
+ ** opening it. If a transient table is required, just use the
+ ** automatically created table with root-page 1 (an INTKEY table).
+ */
+ if( pOp->p4.pKeyInfo ){
+ int pgno;
+ assert( pOp->p4type==P4_KEYINFO );
+ rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_ZERODATA);
+ if( rc==SQLITE_OK ){
+ assert( pgno==MASTER_ROOT+1 );
+ rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1,
+ (KeyInfo*)pOp->p4.z, pCx->pCursor);
+ pCx->pKeyInfo = pOp->p4.pKeyInfo;
+ pCx->pKeyInfo->enc = ENC(p->db);
+ pCx->pIncrKey = &pCx->pKeyInfo->incrKey;
+ }
+ pCx->isTable = 0;
+ }else{
+ rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, 1, 0, pCx->pCursor);
+ pCx->isTable = 1;
+ pCx->pIncrKey = &pCx->bogusIncrKey;
+ }
+ }
+ pCx->isIndex = !pCx->isTable;
+ break;
+}
+
+/* Opcode: OpenPseudo P1 P2 * * *
+**
+** Open a new cursor that points to a fake table that contains a single
+** row of data. Any attempt to write a second row of data causes the
+** first row to be deleted. All data is deleted when the cursor is
+** closed.
+**
+** A pseudo-table created by this opcode is useful for holding the
+** NEW or OLD tables in a trigger. Also used to hold the a single
+** row output from the sorter so that the row can be decomposed into
+** individual columns using the OP_Column opcode.
+**
+** When OP_Insert is executed to insert a row in to the pseudo table,
+** the pseudo-table cursor may or may not make it's own copy of the
+** original row data. If P2 is 0, then the pseudo-table will copy the
+** original row data. Otherwise, a pointer to the original memory cell
+** is stored. In this case, the vdbe program must ensure that the
+** memory cell containing the row data is not overwritten until the
+** pseudo table is closed (or a new row is inserted into it).
+*/
+case OP_OpenPseudo: {
+ int i = pOp->p1;
+ Cursor *pCx;
+ assert( i>=0 );
+ pCx = allocateCursor(p, i, &pOp[-1], -1, 0);
+ if( pCx==0 ) goto no_mem;
+ pCx->nullRow = 1;
+ pCx->pseudoTable = 1;
+ pCx->ephemPseudoTable = pOp->p2;
+ pCx->pIncrKey = &pCx->bogusIncrKey;
+ pCx->isTable = 1;
+ pCx->isIndex = 0;
+ break;
+}
+
+/* Opcode: Close P1 * * * *
+**
+** Close a cursor previously opened as P1. If P1 is not
+** currently open, this instruction is a no-op.
+*/
+case OP_Close: {
+ int i = pOp->p1;
+ assert( i>=0 && i<p->nCursor );
+ sqlite3VdbeFreeCursor(p, p->apCsr[i]);
+ p->apCsr[i] = 0;
+ break;
+}
+
+/* Opcode: MoveGe P1 P2 P3 P4 *
+**
+** If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
+** use the integer value in register P3 as a key. If cursor P1 refers
+** to an SQL index, then P3 is the first in an array of P4 registers
+** that are used as an unpacked index key.
+**
+** Reposition cursor P1 so that it points to the smallest entry that
+** is greater than or equal to the key value. If there are no records
+** greater than or equal to the key and P2 is not zero, then jump to P2.
+**
+** A special feature of this opcode (and different from the
+** related OP_MoveGt, OP_MoveLt, and OP_MoveLe) is that if P2 is
+** zero and P1 is an SQL table (a b-tree with integer keys) then
+** the seek is deferred until it is actually needed. It might be
+** the case that the cursor is never accessed. By deferring the
+** seek, we avoid unnecessary seeks.
+**
+** See also: Found, NotFound, Distinct, MoveLt, MoveGt, MoveLe
+*/
+/* Opcode: MoveGt P1 P2 P3 P4 *
+**
+** If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
+** use the integer value in register P3 as a key. If cursor P1 refers
+** to an SQL index, then P3 is the first in an array of P4 registers
+** that are used as an unpacked index key.
+**
+** Reposition cursor P1 so that it points to the smallest entry that
+** is greater than the key value. If there are no records greater than
+** the key and P2 is not zero, then jump to P2.
+**
+** See also: Found, NotFound, Distinct, MoveLt, MoveGe, MoveLe
+*/
+/* Opcode: MoveLt P1 P2 P3 P4 *
+**
+** If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
+** use the integer value in register P3 as a key. If cursor P1 refers
+** to an SQL index, then P3 is the first in an array of P4 registers
+** that are used as an unpacked index key.
+**
+** Reposition cursor P1 so that it points to the largest entry that
+** is less than the key value. If there are no records less than
+** the key and P2 is not zero, then jump to P2.
+**
+** See also: Found, NotFound, Distinct, MoveGt, MoveGe, MoveLe
+*/
+/* Opcode: MoveLe P1 P2 P3 P4 *
+**
+** If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
+** use the integer value in register P3 as a key. If cursor P1 refers
+** to an SQL index, then P3 is the first in an array of P4 registers
+** that are used as an unpacked index key.
+**
+** Reposition cursor P1 so that it points to the largest entry that
+** is less than or equal to the key value. If there are no records
+** less than or equal to the key and P2 is not zero, then jump to P2.
+**
+** See also: Found, NotFound, Distinct, MoveGt, MoveGe, MoveLt
+*/
+case OP_MoveLt: /* jump, in3 */
+case OP_MoveLe: /* jump, in3 */
+case OP_MoveGe: /* jump, in3 */
+case OP_MoveGt: { /* jump, in3 */
+ int i = pOp->p1;
+ Cursor *pC;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ if( pC->pCursor!=0 ){
+ int res, oc;
+ oc = pOp->opcode;
+ pC->nullRow = 0;
+ *pC->pIncrKey = oc==OP_MoveGt || oc==OP_MoveLe;
+ if( pC->isTable ){
+ i64 iKey = sqlite3VdbeIntValue(pIn3);
+ if( pOp->p2==0 ){
+ assert( pOp->opcode==OP_MoveGe );
+ pC->movetoTarget = iKey;
+ pC->rowidIsValid = 0;
+ pC->deferredMoveto = 1;
+ break;
+ }
+ rc = sqlite3BtreeMoveto(pC->pCursor, 0, 0, (u64)iKey, 0, &res);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ pC->lastRowid = iKey;
+ pC->rowidIsValid = res==0;
+ }else{
+ UnpackedRecord r;
+ int nField = pOp->p4.i;
+ assert( pOp->p4type==P4_INT32 );
+ assert( nField>0 );
+ r.pKeyInfo = pC->pKeyInfo;
+ r.nField = nField;
+ r.needFree = 0;
+ r.needDestroy = 0;
+ r.aMem = &p->aMem[pOp->p3];
+ rc = sqlite3BtreeMoveto(pC->pCursor, 0, &r, 0, 0, &res);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ pC->rowidIsValid = 0;
+ }
+ pC->deferredMoveto = 0;
+ pC->cacheStatus = CACHE_STALE;
+ *pC->pIncrKey = 0;
+#ifdef SQLITE_TEST
+ sqlite3_search_count++;
+#endif
+ if( oc==OP_MoveGe || oc==OP_MoveGt ){
+ if( res<0 ){
+ rc = sqlite3BtreeNext(pC->pCursor, &res);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ pC->rowidIsValid = 0;
+ }else{
+ res = 0;
+ }
+ }else{
+ assert( oc==OP_MoveLt || oc==OP_MoveLe );
+ if( res>=0 ){
+ rc = sqlite3BtreePrevious(pC->pCursor, &res);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ pC->rowidIsValid = 0;
+ }else{
+ /* res might be negative because the table is empty. Check to
+ ** see if this is the case.
+ */
+ res = sqlite3BtreeEof(pC->pCursor);
+ }
+ }
+ assert( pOp->p2>0 );
+ if( res ){
+ pc = pOp->p2 - 1;
+ }
+ }
+ break;
+}
+
+/* Opcode: Found P1 P2 P3 * *
+**
+** Register P3 holds a blob constructed by MakeRecord. P1 is an index.
+** If an entry that matches the value in register p3 exists in P1 then
+** jump to P2. If the P3 value does not match any entry in P1
+** then fall thru. The P1 cursor is left pointing at the matching entry
+** if it exists.
+**
+** This instruction is used to implement the IN operator where the
+** left-hand side is a SELECT statement. P1 may be a true index, or it
+** may be a temporary index that holds the results of the SELECT
+** statement. This instruction is also used to implement the
+** DISTINCT keyword in SELECT statements.
+**
+** This instruction checks if index P1 contains a record for which
+** the first N serialised values exactly match the N serialised values
+** in the record in register P3, where N is the total number of values in
+** the P3 record (the P3 record is a prefix of the P1 record).
+**
+** See also: NotFound, MoveTo, IsUnique, NotExists
+*/
+/* Opcode: NotFound P1 P2 P3 * *
+**
+** Register P3 holds a blob constructed by MakeRecord. P1 is
+** an index. If no entry exists in P1 that matches the blob then jump
+** to P2. If an entry does existing, fall through. The cursor is left
+** pointing to the entry that matches.
+**
+** See also: Found, MoveTo, NotExists, IsUnique
+*/
+case OP_NotFound: /* jump, in3 */
+case OP_Found: { /* jump, in3 */
+ int i = pOp->p1;
+ int alreadyExists = 0;
+ Cursor *pC;
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pC = p->apCsr[i])->pCursor!=0 ){
+ int res;
+ assert( pC->isTable==0 );
+ assert( pIn3->flags & MEM_Blob );
+ if( pOp->opcode==OP_Found ){
+ pC->pKeyInfo->prefixIsEqual = 1;
+ }
+ rc = sqlite3BtreeMoveto(pC->pCursor, pIn3->z, 0, pIn3->n, 0, &res);
+ pC->pKeyInfo->prefixIsEqual = 0;
+ if( rc!=SQLITE_OK ){
+ break;
+ }
+ alreadyExists = (res==0);
+ pC->deferredMoveto = 0;
+ pC->cacheStatus = CACHE_STALE;
+ }
+ if( pOp->opcode==OP_Found ){
+ if( alreadyExists ) pc = pOp->p2 - 1;
+ }else{
+ if( !alreadyExists ) pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: IsUnique P1 P2 P3 P4 *
+**
+** The P3 register contains an integer record number. Call this
+** record number R. The P4 register contains an index key created
+** using MakeIdxRec. Call it K.
+**
+** P1 is an index. So it has no data and its key consists of a
+** record generated by OP_MakeRecord where the last field is the
+** rowid of the entry that the index refers to.
+**
+** This instruction asks if there is an entry in P1 where the
+** fields matches K but the rowid is different from R.
+** If there is no such entry, then there is an immediate
+** jump to P2. If any entry does exist where the index string
+** matches K but the record number is not R, then the record
+** number for that entry is written into P3 and control
+** falls through to the next instruction.
+**
+** See also: NotFound, NotExists, Found
+*/
+case OP_IsUnique: { /* jump, in3 */
+ int i = pOp->p1;
+ Cursor *pCx;
+ BtCursor *pCrsr;
+ Mem *pK;
+ i64 R;
+
+ /* Pop the value R off the top of the stack
+ */
+ assert( pOp->p4type==P4_INT32 );
+ assert( pOp->p4.i>0 && pOp->p4.i<=p->nMem );
+ pK = &p->aMem[pOp->p4.i];
+ sqlite3VdbeMemIntegerify(pIn3);
+ R = pIn3->u.i;
+ assert( i>=0 && i<p->nCursor );
+ pCx = p->apCsr[i];
+ assert( pCx!=0 );
+ pCrsr = pCx->pCursor;
+ if( pCrsr!=0 ){
+ int res;
+ i64 v; /* The record number on the P1 entry that matches K */
+ char *zKey; /* The value of K */
+ int nKey; /* Number of bytes in K */
+ int len; /* Number of bytes in K without the rowid at the end */
+ int szRowid; /* Size of the rowid column at the end of zKey */
+
+ /* Make sure K is a string and make zKey point to K
+ */
+ assert( pK->flags & MEM_Blob );
+ zKey = pK->z;
+ nKey = pK->n;
+
+ szRowid = sqlite3VdbeIdxRowidLen((u8*)zKey);
+ len = nKey-szRowid;
+
+ /* Search for an entry in P1 where all but the last four bytes match K.
+ ** If there is no such entry, jump immediately to P2.
+ */
+ assert( pCx->deferredMoveto==0 );
+ pCx->cacheStatus = CACHE_STALE;
+ rc = sqlite3BtreeMoveto(pCrsr, zKey, 0, len, 0, &res);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ if( res<0 ){
+ rc = sqlite3BtreeNext(pCrsr, &res);
+ if( res ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+ }
+ rc = sqlite3VdbeIdxKeyCompare(pCx, 0, len, (u8*)zKey, &res);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ if( res>0 ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+
+ /* At this point, pCrsr is pointing to an entry in P1 where all but
+ ** the final entry (the rowid) matches K. Check to see if the
+ ** final rowid column is different from R. If it equals R then jump
+ ** immediately to P2.
+ */
+ rc = sqlite3VdbeIdxRowid(pCrsr, &v);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ if( v==R ){
+ pc = pOp->p2 - 1;
+ break;
+ }
+
+ /* The final varint of the key is different from R. Store it back
+ ** into register R3. (The record number of an entry that violates
+ ** a UNIQUE constraint.)
+ */
+ pIn3->u.i = v;
+ assert( pIn3->flags&MEM_Int );
+ }
+ break;
+}
+
+/* Opcode: NotExists P1 P2 P3 * *
+**
+** Use the content of register P3 as a integer key. If a record
+** with that key does not exist in table of P1, then jump to P2.
+** If the record does exist, then fall thru. The cursor is left
+** pointing to the record if it exists.
+**
+** The difference between this operation and NotFound is that this
+** operation assumes the key is an integer and that P1 is a table whereas
+** NotFound assumes key is a blob constructed from MakeRecord and
+** P1 is an index.
+**
+** See also: Found, MoveTo, NotFound, IsUnique
+*/
+case OP_NotExists: { /* jump, in3 */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ int res;
+ u64 iKey;
+ assert( pIn3->flags & MEM_Int );
+ assert( p->apCsr[i]->isTable );
+ iKey = intToKey(pIn3->u.i);
+ rc = sqlite3BtreeMoveto(pCrsr, 0, 0, iKey, 0,&res);
+ pC->lastRowid = pIn3->u.i;
+ pC->rowidIsValid = res==0;
+ pC->nullRow = 0;
+ pC->cacheStatus = CACHE_STALE;
+ /* res might be uninitialized if rc!=SQLITE_OK. But if rc!=SQLITE_OK
+ ** processing is about to abort so we really do not care whether or not
+ ** the following jump is taken. (In other words, do not stress over
+ ** the error that valgrind sometimes shows on the next statement when
+ ** running ioerr.test and similar failure-recovery test scripts.) */
+ if( res!=0 ){
+ pc = pOp->p2 - 1;
+ assert( pC->rowidIsValid==0 );
+ }
+ }
+ break;
+}
+
+/* Opcode: Sequence P1 P2 * * *
+**
+** Find the next available sequence number for cursor P1.
+** Write the sequence number into register P2.
+** The sequence number on the cursor is incremented after this
+** instruction.
+*/
+case OP_Sequence: { /* out2-prerelease */
+ int i = pOp->p1;
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ pOut->u.i = p->apCsr[i]->seqCount++;
+ MemSetTypeFlag(pOut, MEM_Int);
+ break;
+}
+
+
+/* Opcode: NewRowid P1 P2 P3 * *
+**
+** Get a new integer record number (a.k.a "rowid") used as the key to a table.
+** The record number is not previously used as a key in the database
+** table that cursor P1 points to. The new record number is written
+** written to register P2.
+**
+** If P3>0 then P3 is a register that holds the largest previously
+** generated record number. No new record numbers are allowed to be less
+** than this value. When this value reaches its maximum, a SQLITE_FULL
+** error is generated. The P3 register is updated with the generated
+** record number. This P3 mechanism is used to help implement the
+** AUTOINCREMENT feature.
+*/
+case OP_NewRowid: { /* out2-prerelease */
+ int i = pOp->p1;
+ i64 v = 0;
+ Cursor *pC;
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pC = p->apCsr[i])->pCursor==0 ){
+ /* The zero initialization above is all that is needed */
+ }else{
+ /* The next rowid or record number (different terms for the same
+ ** thing) is obtained in a two-step algorithm.
+ **
+ ** First we attempt to find the largest existing rowid and add one
+ ** to that. But if the largest existing rowid is already the maximum
+ ** positive integer, we have to fall through to the second
+ ** probabilistic algorithm
+ **
+ ** The second algorithm is to select a rowid at random and see if
+ ** it already exists in the table. If it does not exist, we have
+ ** succeeded. If the random rowid does exist, we select a new one
+ ** and try again, up to 1000 times.
+ **
+ ** For a table with less than 2 billion entries, the probability
+ ** of not finding a unused rowid is about 1.0e-300. This is a
+ ** non-zero probability, but it is still vanishingly small and should
+ ** never cause a problem. You are much, much more likely to have a
+ ** hardware failure than for this algorithm to fail.
+ **
+ ** The analysis in the previous paragraph assumes that you have a good
+ ** source of random numbers. Is a library function like lrand48()
+ ** good enough? Maybe. Maybe not. It's hard to know whether there
+ ** might be subtle bugs is some implementations of lrand48() that
+ ** could cause problems. To avoid uncertainty, SQLite uses its own
+ ** random number generator based on the RC4 algorithm.
+ **
+ ** To promote locality of reference for repetitive inserts, the
+ ** first few attempts at chosing a random rowid pick values just a little
+ ** larger than the previous rowid. This has been shown experimentally
+ ** to double the speed of the COPY operation.
+ */
+ int res, rx=SQLITE_OK, cnt;
+ i64 x;
+ cnt = 0;
+ if( (sqlite3BtreeFlags(pC->pCursor)&(BTREE_INTKEY|BTREE_ZERODATA)) !=
+ BTREE_INTKEY ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto abort_due_to_error;
+ }
+ assert( (sqlite3BtreeFlags(pC->pCursor) & BTREE_INTKEY)!=0 );
+ assert( (sqlite3BtreeFlags(pC->pCursor) & BTREE_ZERODATA)==0 );
+
+#ifdef SQLITE_32BIT_ROWID
+# define MAX_ROWID 0x7fffffff
+#else
+ /* Some compilers complain about constants of the form 0x7fffffffffffffff.
+ ** Others complain about 0x7ffffffffffffffffLL. The following macro seems
+ ** to provide the constant while making all compilers happy.
+ */
+# define MAX_ROWID ( (((u64)0x7fffffff)<<32) | (u64)0xffffffff )
+#endif
+
+ if( !pC->useRandomRowid ){
+ if( pC->nextRowidValid ){
+ v = pC->nextRowid;
+ }else{
+ rc = sqlite3BtreeLast(pC->pCursor, &res);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ if( res ){
+ v = 1;
+ }else{
+ sqlite3BtreeKeySize(pC->pCursor, &v);
+ v = keyToInt(v);
+ if( v==MAX_ROWID ){
+ pC->useRandomRowid = 1;
+ }else{
+ v++;
+ }
+ }
+ }
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ if( pOp->p3 ){
+ Mem *pMem;
+ assert( pOp->p3>0 && pOp->p3<=p->nMem ); /* P3 is a valid memory cell */
+ pMem = &p->aMem[pOp->p3];
+ REGISTER_TRACE(pOp->p3, pMem);
+ sqlite3VdbeMemIntegerify(pMem);
+ assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */
+ if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){
+ rc = SQLITE_FULL;
+ goto abort_due_to_error;
+ }
+ if( v<pMem->u.i+1 ){
+ v = pMem->u.i + 1;
+ }
+ pMem->u.i = v;
+ }
+#endif
+
+ if( v<MAX_ROWID ){
+ pC->nextRowidValid = 1;
+ pC->nextRowid = v+1;
+ }else{
+ pC->nextRowidValid = 0;
+ }
+ }
+ if( pC->useRandomRowid ){
+ assert( pOp->p3==0 ); /* SQLITE_FULL must have occurred prior to this */
+ v = db->priorNewRowid;
+ cnt = 0;
+ do{
+ if( cnt==0 && (v&0xffffff)==v ){
+ v++;
+ }else{
+ sqlite3_randomness(sizeof(v), &v);
+ if( cnt<5 ) v &= 0xffffff;
+ }
+ if( v==0 ) continue;
+ x = intToKey(v);
+ rx = sqlite3BtreeMoveto(pC->pCursor, 0, 0, (u64)x, 0, &res);
+ cnt++;
+ }while( cnt<100 && rx==SQLITE_OK && res==0 );
+ db->priorNewRowid = v;
+ if( rx==SQLITE_OK && res==0 ){
+ rc = SQLITE_FULL;
+ goto abort_due_to_error;
+ }
+ }
+ pC->rowidIsValid = 0;
+ pC->deferredMoveto = 0;
+ pC->cacheStatus = CACHE_STALE;
+ }
+ MemSetTypeFlag(pOut, MEM_Int);
+ pOut->u.i = v;
+ break;
+}
+
+/* Opcode: Insert P1 P2 P3 P4 P5
+**
+** Write an entry into the table of cursor P1. A new entry is
+** created if it doesn't already exist or the data for an existing
+** entry is overwritten. The data is the value stored register
+** number P2. The key is stored in register P3. The key must
+** be an integer.
+**
+** If the OPFLAG_NCHANGE flag of P5 is set, then the row change count is
+** incremented (otherwise not). If the OPFLAG_LASTROWID flag of P5 is set,
+** then rowid is stored for subsequent return by the
+** sqlite3_last_insert_rowid() function (otherwise it is unmodified).
+**
+** Parameter P4 may point to a string containing the table-name, or
+** may be NULL. If it is not NULL, then the update-hook
+** (sqlite3.xUpdateCallback) is invoked following a successful insert.
+**
+** (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically
+** allocated, then ownership of P2 is transferred to the pseudo-cursor
+** and register P2 becomes ephemeral. If the cursor is changed, the
+** value of register P2 will then change. Make sure this does not
+** cause any problems.)
+**
+** This instruction only works on tables. The equivalent instruction
+** for indices is OP_IdxInsert.
+*/
+case OP_Insert: {
+ Mem *pData = &p->aMem[pOp->p2];
+ Mem *pKey = &p->aMem[pOp->p3];
+
+ i64 iKey; /* The integer ROWID or key for the record to be inserted */
+ int i = pOp->p1;
+ Cursor *pC;
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ assert( pC->pCursor!=0 || pC->pseudoTable );
+ assert( pKey->flags & MEM_Int );
+ assert( pC->isTable );
+ REGISTER_TRACE(pOp->p2, pData);
+ REGISTER_TRACE(pOp->p3, pKey);
+
+ iKey = intToKey(pKey->u.i);
+ if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
+ if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = pKey->u.i;
+ if( pC->nextRowidValid && pKey->u.i>=pC->nextRowid ){
+ pC->nextRowidValid = 0;
+ }
+ if( pData->flags & MEM_Null ){
+ pData->z = 0;
+ pData->n = 0;
+ }else{
+ assert( pData->flags & (MEM_Blob|MEM_Str) );
+ }
+ if( pC->pseudoTable ){
+ if( !pC->ephemPseudoTable ){
+ sqlite3_free(pC->pData);
+ }
+ pC->iKey = iKey;
+ pC->nData = pData->n;
+ if( pData->z==pData->zMalloc || pC->ephemPseudoTable ){
+ pC->pData = pData->z;
+ if( !pC->ephemPseudoTable ){
+ pData->flags &= ~MEM_Dyn;
+ pData->flags |= MEM_Ephem;
+ pData->zMalloc = 0;
+ }
+ }else{
+ pC->pData = sqlite3_malloc( pC->nData+2 );
+ if( !pC->pData ) goto no_mem;
+ memcpy(pC->pData, pData->z, pC->nData);
+ pC->pData[pC->nData] = 0;
+ pC->pData[pC->nData+1] = 0;
+ }
+ pC->nullRow = 0;
+ }else{
+ int nZero;
+ if( pData->flags & MEM_Zero ){
+ nZero = pData->u.i;
+ }else{
+ nZero = 0;
+ }
+ rc = sqlite3BtreeInsert(pC->pCursor, 0, iKey,
+ pData->z, pData->n, nZero,
+ pOp->p5 & OPFLAG_APPEND);
+ }
+
+ pC->rowidIsValid = 0;
+ pC->deferredMoveto = 0;
+ pC->cacheStatus = CACHE_STALE;
+
+ /* Invoke the update-hook if required. */
+ if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){
+ const char *zDb = db->aDb[pC->iDb].zName;
+ const char *zTbl = pOp->p4.z;
+ int op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
+ assert( pC->isTable );
+ db->xUpdateCallback(db->pUpdateArg, op, zDb, zTbl, iKey);
+ assert( pC->iDb>=0 );
+ }
+ break;
+}
+
+/* Opcode: Delete P1 P2 * P4 *
+**
+** Delete the record at which the P1 cursor is currently pointing.
+**
+** The cursor will be left pointing at either the next or the previous
+** record in the table. If it is left pointing at the next record, then
+** the next Next instruction will be a no-op. Hence it is OK to delete
+** a record from within an Next loop.
+**
+** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is
+** incremented (otherwise not).
+**
+** P1 must not be pseudo-table. It has to be a real table with
+** multiple rows.
+**
+** If P4 is not NULL, then it is the name of the table that P1 is
+** pointing to. The update hook will be invoked, if it exists.
+** If P4 is not NULL then the P1 cursor must have been positioned
+** using OP_NotFound prior to invoking this opcode.
+*/
+case OP_Delete: {
+ int i = pOp->p1;
+ i64 iKey;
+ Cursor *pC;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ assert( pC->pCursor!=0 ); /* Only valid for real tables, no pseudotables */
+
+ /* If the update-hook will be invoked, set iKey to the rowid of the
+ ** row being deleted.
+ */
+ if( db->xUpdateCallback && pOp->p4.z ){
+ assert( pC->isTable );
+ assert( pC->rowidIsValid ); /* lastRowid set by previous OP_NotFound */
+ iKey = pC->lastRowid;
+ }
+
+ rc = sqlite3VdbeCursorMoveto(pC);
+ if( rc ) goto abort_due_to_error;
+ rc = sqlite3BtreeDelete(pC->pCursor);
+ pC->nextRowidValid = 0;
+ pC->cacheStatus = CACHE_STALE;
+
+ /* Invoke the update-hook if required. */
+ if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){
+ const char *zDb = db->aDb[pC->iDb].zName;
+ const char *zTbl = pOp->p4.z;
+ db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, zTbl, iKey);
+ assert( pC->iDb>=0 );
+ }
+ if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++;
+ break;
+}
+
+/* Opcode: ResetCount P1 * *
+**
+** This opcode resets the VMs internal change counter to 0. If P1 is true,
+** then the value of the change counter is copied to the database handle
+** change counter (returned by subsequent calls to sqlite3_changes())
+** before it is reset. This is used by trigger programs.
+*/
+case OP_ResetCount: {
+ if( pOp->p1 ){
+ sqlite3VdbeSetChanges(db, p->nChange);
+ }
+ p->nChange = 0;
+ break;
+}
+
+/* Opcode: RowData P1 P2 * * *
+**
+** Write into register P2 the complete row data for cursor P1.
+** There is no interpretation of the data.
+** It is just copied onto the P2 register exactly as
+** it is found in the database file.
+**
+** If the P1 cursor must be pointing to a valid row (not a NULL row)
+** of a real table, not a pseudo-table.
+*/
+/* Opcode: RowKey P1 P2 * * *
+**
+** Write into register P2 the complete row key for cursor P1.
+** There is no interpretation of the data.
+** The key is copied onto the P3 register exactly as
+** it is found in the database file.
+**
+** If the P1 cursor must be pointing to a valid row (not a NULL row)
+** of a real table, not a pseudo-table.
+*/
+case OP_RowKey:
+case OP_RowData: {
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ u32 n;
+
+ pOut = &p->aMem[pOp->p2];
+
+ /* Note that RowKey and RowData are really exactly the same instruction */
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC->isTable || pOp->opcode==OP_RowKey );
+ assert( pC->isIndex || pOp->opcode==OP_RowData );
+ assert( pC!=0 );
+ assert( pC->nullRow==0 );
+ assert( pC->pseudoTable==0 );
+ assert( pC->pCursor!=0 );
+ pCrsr = pC->pCursor;
+ rc = sqlite3VdbeCursorMoveto(pC);
+ if( rc ) goto abort_due_to_error;
+ if( pC->isIndex ){
+ i64 n64;
+ assert( !pC->isTable );
+ sqlite3BtreeKeySize(pCrsr, &n64);
+ if( n64>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+ n = n64;
+ }else{
+ sqlite3BtreeDataSize(pCrsr, &n);
+ if( n>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+ }
+ if( sqlite3VdbeMemGrow(pOut, n, 0) ){
+ goto no_mem;
+ }
+ pOut->n = n;
+ MemSetTypeFlag(pOut, MEM_Blob);
+ if( pC->isIndex ){
+ rc = sqlite3BtreeKey(pCrsr, 0, n, pOut->z);
+ }else{
+ rc = sqlite3BtreeData(pCrsr, 0, n, pOut->z);
+ }
+ pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+
+/* Opcode: Rowid P1 P2 * * *
+**
+** Store in register P2 an integer which is the key of the table entry that
+** P1 is currently point to. If p2==0 then push the integer.
+*/
+case OP_Rowid: { /* out2-prerelease */
+ int i = pOp->p1;
+ Cursor *pC;
+ i64 v;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ rc = sqlite3VdbeCursorMoveto(pC);
+ if( rc ) goto abort_due_to_error;
+ if( pC->rowidIsValid ){
+ v = pC->lastRowid;
+ }else if( pC->pseudoTable ){
+ v = keyToInt(pC->iKey);
+ }else if( pC->nullRow ){
+ /* Leave the rowid set to a NULL */
+ break;
+ }else{
+ assert( pC->pCursor!=0 );
+ sqlite3BtreeKeySize(pC->pCursor, &v);
+ v = keyToInt(v);
+ }
+ pOut->u.i = v;
+ MemSetTypeFlag(pOut, MEM_Int);
+ break;
+}
+
+/* Opcode: NullRow P1 * * * *
+**
+** Move the cursor P1 to a null row. Any OP_Column operations
+** that occur while the cursor is on the null row will always
+** write a NULL.
+*/
+case OP_NullRow: {
+ int i = pOp->p1;
+ Cursor *pC;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ pC->nullRow = 1;
+ pC->rowidIsValid = 0;
+ break;
+}
+
+/* Opcode: Last P1 P2 * * *
+**
+** The next use of the Rowid or Column or Next instruction for P1
+** will refer to the last entry in the database table or index.
+** If the table or index is empty and P2>0, then jump immediately to P2.
+** If P2 is 0 or if the table or index is not empty, fall through
+** to the following instruction.
+*/
+case OP_Last: { /* jump */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ int res;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ pCrsr = pC->pCursor;
+ assert( pCrsr!=0 );
+ rc = sqlite3BtreeLast(pCrsr, &res);
+ pC->nullRow = res;
+ pC->deferredMoveto = 0;
+ pC->cacheStatus = CACHE_STALE;
+ if( res && pOp->p2>0 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+
+/* Opcode: Sort P1 P2 * * *
+**
+** This opcode does exactly the same thing as OP_Rewind except that
+** it increments an undocumented global variable used for testing.
+**
+** Sorting is accomplished by writing records into a sorting index,
+** then rewinding that index and playing it back from beginning to
+** end. We use the OP_Sort opcode instead of OP_Rewind to do the
+** rewinding so that the global variable will be incremented and
+** regression tests can determine whether or not the optimizer is
+** correctly optimizing out sorts.
+*/
+case OP_Sort: { /* jump */
+#ifdef SQLITE_TEST
+ sqlite3_sort_count++;
+ sqlite3_search_count--;
+#endif
+ /* Fall through into OP_Rewind */
+}
+/* Opcode: Rewind P1 P2 * * *
+**
+** The next use of the Rowid or Column or Next instruction for P1
+** will refer to the first entry in the database table or index.
+** If the table or index is empty and P2>0, then jump immediately to P2.
+** If P2 is 0 or if the table or index is not empty, fall through
+** to the following instruction.
+*/
+case OP_Rewind: { /* jump */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ int res;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ if( (pCrsr = pC->pCursor)!=0 ){
+ rc = sqlite3BtreeFirst(pCrsr, &res);
+ pC->atFirst = res==0;
+ pC->deferredMoveto = 0;
+ pC->cacheStatus = CACHE_STALE;
+ }else{
+ res = 1;
+ }
+ pC->nullRow = res;
+ assert( pOp->p2>0 && pOp->p2<p->nOp );
+ if( res ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: Next P1 P2 * * *
+**
+** Advance cursor P1 so that it points to the next key/data pair in its
+** table or index. If there are no more key/value pairs then fall through
+** to the following instruction. But if the cursor advance was successful,
+** jump immediately to P2.
+**
+** The P1 cursor must be for a real table, not a pseudo-table.
+**
+** See also: Prev
+*/
+/* Opcode: Prev P1 P2 * * *
+**
+** Back up cursor P1 so that it points to the previous key/data pair in its
+** table or index. If there is no previous key/value pairs then fall through
+** to the following instruction. But if the cursor backup was successful,
+** jump immediately to P2.
+**
+** The P1 cursor must be for a real table, not a pseudo-table.
+*/
+case OP_Prev: /* jump */
+case OP_Next: { /* jump */
+ Cursor *pC;
+ BtCursor *pCrsr;
+
+ CHECK_FOR_INTERRUPT;
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ if( pC==0 ){
+ break; /* See ticket #2273 */
+ }
+ pCrsr = pC->pCursor;
+ assert( pCrsr );
+ if( pC->nullRow==0 ){
+ int res = 1;
+ assert( pC->deferredMoveto==0 );
+ rc = pOp->opcode==OP_Next ? sqlite3BtreeNext(pCrsr, &res) :
+ sqlite3BtreePrevious(pCrsr, &res);
+ pC->nullRow = res;
+ pC->cacheStatus = CACHE_STALE;
+ if( res==0 ){
+ pc = pOp->p2 - 1;
+#ifdef SQLITE_TEST
+ sqlite3_search_count++;
+#endif
+ }
+ }
+ pC->rowidIsValid = 0;
+ break;
+}
+
+/* Opcode: IdxInsert P1 P2 P3 * *
+**
+** Register P2 holds a SQL index key made using the
+** MakeIdxRec instructions. This opcode writes that key
+** into the index P1. Data for the entry is nil.
+**
+** P3 is a flag that provides a hint to the b-tree layer that this
+** insert is likely to be an append.
+**
+** This instruction only works for indices. The equivalent instruction
+** for tables is OP_Insert.
+*/
+case OP_IdxInsert: { /* in2 */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ assert( pIn2->flags & MEM_Blob );
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ assert( pC->isTable==0 );
+ rc = ExpandBlob(pIn2);
+ if( rc==SQLITE_OK ){
+ int nKey = pIn2->n;
+ const char *zKey = pIn2->z;
+ rc = sqlite3BtreeInsert(pCrsr, zKey, nKey, "", 0, 0, pOp->p3);
+ assert( pC->deferredMoveto==0 );
+ pC->cacheStatus = CACHE_STALE;
+ }
+ }
+ break;
+}
+
+/* Opcode: IdxDeleteM P1 P2 P3 * *
+**
+** The content of P3 registers starting at register P2 form
+** an unpacked index key. This opcode removes that entry from the
+** index opened by cursor P1.
+*/
+case OP_IdxDelete: {
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ assert( pOp->p3>0 );
+ assert( pOp->p2>0 && pOp->p2+pOp->p3<=p->nMem );
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ int res;
+ UnpackedRecord r;
+ r.pKeyInfo = pC->pKeyInfo;
+ r.nField = pOp->p3;
+ r.needFree = 0;
+ r.needDestroy = 0;
+ r.aMem = &p->aMem[pOp->p2];
+ rc = sqlite3BtreeMoveto(pCrsr, 0, &r, 0, 0, &res);
+ if( rc==SQLITE_OK && res==0 ){
+ rc = sqlite3BtreeDelete(pCrsr);
+ }
+ assert( pC->deferredMoveto==0 );
+ pC->cacheStatus = CACHE_STALE;
+ }
+ break;
+}
+
+/* Opcode: IdxRowid P1 P2 * * *
+**
+** Write into register P2 an integer which is the last entry in the record at
+** the end of the index key pointed to by cursor P1. This integer should be
+** the rowid of the table entry to which this index entry points.
+**
+** See also: Rowid, MakeIdxRec.
+*/
+case OP_IdxRowid: { /* out2-prerelease */
+ int i = pOp->p1;
+ BtCursor *pCrsr;
+ Cursor *pC;
+
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ i64 rowid;
+
+ assert( pC->deferredMoveto==0 );
+ assert( pC->isTable==0 );
+ if( !pC->nullRow ){
+ rc = sqlite3VdbeIdxRowid(pCrsr, &rowid);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ MemSetTypeFlag(pOut, MEM_Int);
+ pOut->u.i = rowid;
+ }
+ }
+ break;
+}
+
+/* Opcode: IdxGE P1 P2 P3 P4 P5
+**
+** The P4 register values beginning with P3 form an unpacked index
+** key that omits the ROWID. Compare this key value against the index
+** that P1 is currently pointing to, ignoring the ROWID on the P1 index.
+**
+** If the P1 index entry is greater than or equal to the key value
+** then jump to P2. Otherwise fall through to the next instruction.
+**
+** If P5 is non-zero then the key value is increased by an epsilon
+** prior to the comparison. This make the opcode work like IdxGT except
+** that if the key from register P3 is a prefix of the key in the cursor,
+** the result is false whereas it would be true with IdxGT.
+*/
+/* Opcode: IdxLT P1 P2 P3 * P5
+**
+** The P4 register values beginning with P3 form an unpacked index
+** key that omits the ROWID. Compare this key value against the index
+** that P1 is currently pointing to, ignoring the ROWID on the P1 index.
+**
+** If the P1 index entry is less than the key value then jump to P2.
+** Otherwise fall through to the next instruction.
+**
+** If P5 is non-zero then the key value is increased by an epsilon prior
+** to the comparison. This makes the opcode work like IdxLE.
+*/
+case OP_IdxLT: /* jump, in3 */
+case OP_IdxGE: { /* jump, in3 */
+ int i= pOp->p1;
+ Cursor *pC;
+
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pC = p->apCsr[i])->pCursor!=0 ){
+ int res;
+ UnpackedRecord r;
+ assert( pC->deferredMoveto==0 );
+ assert( pOp->p5==0 || pOp->p5==1 );
+ assert( pOp->p4type==P4_INT32 );
+ r.pKeyInfo = pC->pKeyInfo;
+ r.nField = pOp->p4.i;
+ r.needFree = 0;
+ r.needDestroy = 0;
+ r.aMem = &p->aMem[pOp->p3];
+ *pC->pIncrKey = pOp->p5;
+ rc = sqlite3VdbeIdxKeyCompare(pC, &r, 0, 0, &res);
+ *pC->pIncrKey = 0;
+ if( pOp->opcode==OP_IdxLT ){
+ res = -res;
+ }else{
+ assert( pOp->opcode==OP_IdxGE );
+ res++;
+ }
+ if( res>0 ){
+ pc = pOp->p2 - 1 ;
+ }
+ }
+ break;
+}
+
+/* Opcode: Destroy P1 P2 P3 * *
+**
+** Delete an entire database table or index whose root page in the database
+** file is given by P1.
+**
+** The table being destroyed is in the main database file if P3==0. If
+** P3==1 then the table to be clear is in the auxiliary database file
+** that is used to store tables create using CREATE TEMPORARY TABLE.
+**
+** If AUTOVACUUM is enabled then it is possible that another root page
+** might be moved into the newly deleted root page in order to keep all
+** root pages contiguous at the beginning of the database. The former
+** value of the root page that moved - its value before the move occurred -
+** is stored in register P2. If no page
+** movement was required (because the table being dropped was already
+** the last one in the database) then a zero is stored in register P2.
+** If AUTOVACUUM is disabled then a zero is stored in register P2.
+**
+** See also: Clear
+*/
+case OP_Destroy: { /* out2-prerelease */
+ int iMoved;
+ int iCnt;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ Vdbe *pVdbe;
+ iCnt = 0;
+ for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){
+ if( pVdbe->magic==VDBE_MAGIC_RUN && pVdbe->inVtabMethod<2 && pVdbe->pc>=0 ){
+ iCnt++;
+ }
+ }
+#else
+ iCnt = db->activeVdbeCnt;
+#endif
+ if( iCnt>1 ){
+ rc = SQLITE_LOCKED;
+ p->errorAction = OE_Abort;
+ }else{
+ int iDb = pOp->p3;
+ assert( iCnt==1 );
+ assert( (p->btreeMask & (1<<iDb))!=0 );
+ rc = sqlite3BtreeDropTable(db->aDb[iDb].pBt, pOp->p1, &iMoved);
+ MemSetTypeFlag(pOut, MEM_Int);
+ pOut->u.i = iMoved;
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( rc==SQLITE_OK && iMoved!=0 ){
+ sqlite3RootPageMoved(&db->aDb[iDb], iMoved, pOp->p1);
+ }
+#endif
+ }
+ break;
+}
+
+/* Opcode: Clear P1 P2 *
+**
+** Delete all contents of the database table or index whose root page
+** in the database file is given by P1. But, unlike Destroy, do not
+** remove the table or index from the database file.
+**
+** The table being clear is in the main database file if P2==0. If
+** P2==1 then the table to be clear is in the auxiliary database file
+** that is used to store tables create using CREATE TEMPORARY TABLE.
+**
+** See also: Destroy
+*/
+case OP_Clear: {
+ assert( (p->btreeMask & (1<<pOp->p2))!=0 );
+ rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, pOp->p1);
+ break;
+}
+
+/* Opcode: CreateTable P1 P2 * * *
+**
+** Allocate a new table in the main database file if P1==0 or in the
+** auxiliary database file if P1==1 or in an attached database if
+** P1>1. Write the root page number of the new table into
+** register P2
+**
+** The difference between a table and an index is this: A table must
+** have a 4-byte integer key and can have arbitrary data. An index
+** has an arbitrary key but no data.
+**
+** See also: CreateIndex
+*/
+/* Opcode: CreateIndex P1 P2 * * *
+**
+** Allocate a new index in the main database file if P1==0 or in the
+** auxiliary database file if P1==1 or in an attached database if
+** P1>1. Write the root page number of the new table into
+** register P2.
+**
+** See documentation on OP_CreateTable for additional information.
+*/
+case OP_CreateIndex: /* out2-prerelease */
+case OP_CreateTable: { /* out2-prerelease */
+ int pgno;
+ int flags;
+ Db *pDb;
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( (p->btreeMask & (1<<pOp->p1))!=0 );
+ pDb = &db->aDb[pOp->p1];
+ assert( pDb->pBt!=0 );
+ if( pOp->opcode==OP_CreateTable ){
+ /* flags = BTREE_INTKEY; */
+ flags = BTREE_LEAFDATA|BTREE_INTKEY;
+ }else{
+ flags = BTREE_ZERODATA;
+ }
+ rc = sqlite3BtreeCreateTable(pDb->pBt, &pgno, flags);
+ if( rc==SQLITE_OK ){
+ pOut->u.i = pgno;
+ MemSetTypeFlag(pOut, MEM_Int);
+ }
+ break;
+}
+
+/* Opcode: ParseSchema P1 P2 * P4 *
+**
+** Read and parse all entries from the SQLITE_MASTER table of database P1
+** that match the WHERE clause P4. P2 is the "force" flag. Always do
+** the parsing if P2 is true. If P2 is false, then this routine is a
+** no-op if the schema is not currently loaded. In other words, if P2
+** is false, the SQLITE_MASTER table is only parsed if the rest of the
+** schema is already loaded into the symbol table.
+**
+** This opcode invokes the parser to create a new virtual machine,
+** then runs the new virtual machine. It is thus a reentrant opcode.
+*/
+case OP_ParseSchema: {
+ char *zSql;
+ int iDb = pOp->p1;
+ const char *zMaster;
+ InitData initData;
+
+ assert( iDb>=0 && iDb<db->nDb );
+ if( !pOp->p2 && !DbHasProperty(db, iDb, DB_SchemaLoaded) ){
+ break;
+ }
+ zMaster = SCHEMA_TABLE(iDb);
+ initData.db = db;
+ initData.iDb = pOp->p1;
+ initData.pzErrMsg = &p->zErrMsg;
+ zSql = sqlite3MPrintf(db,
+ "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s",
+ db->aDb[iDb].zName, zMaster, pOp->p4.z);
+ if( zSql==0 ) goto no_mem;
+ (void)sqlite3SafetyOff(db);
+ assert( db->init.busy==0 );
+ db->init.busy = 1;
+ assert( !db->mallocFailed );
+ rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
+ if( rc==SQLITE_ABORT ) rc = initData.rc;
+ sqlite3_free(zSql);
+ db->init.busy = 0;
+ (void)sqlite3SafetyOn(db);
+ if( rc==SQLITE_NOMEM ){
+ goto no_mem;
+ }
+ break;
+}
+
+#if !defined(SQLITE_OMIT_ANALYZE) && !defined(SQLITE_OMIT_PARSER)
+/* Opcode: LoadAnalysis P1 * * * *
+**
+** Read the sqlite_stat1 table for database P1 and load the content
+** of that table into the internal index hash table. This will cause
+** the analysis to be used when preparing all subsequent queries.
+*/
+case OP_LoadAnalysis: {
+ int iDb = pOp->p1;
+ assert( iDb>=0 && iDb<db->nDb );
+ rc = sqlite3AnalysisLoad(db, iDb);
+ break;
+}
+#endif /* !defined(SQLITE_OMIT_ANALYZE) && !defined(SQLITE_OMIT_PARSER) */
+
+/* Opcode: DropTable P1 * * P4 *
+**
+** Remove the internal (in-memory) data structures that describe
+** the table named P4 in database P1. This is called after a table
+** is dropped in order to keep the internal representation of the
+** schema consistent with what is on disk.
+*/
+case OP_DropTable: {
+ sqlite3UnlinkAndDeleteTable(db, pOp->p1, pOp->p4.z);
+ break;
+}
+
+/* Opcode: DropIndex P1 * * P4 *
+**
+** Remove the internal (in-memory) data structures that describe
+** the index named P4 in database P1. This is called after an index
+** is dropped in order to keep the internal representation of the
+** schema consistent with what is on disk.
+*/
+case OP_DropIndex: {
+ sqlite3UnlinkAndDeleteIndex(db, pOp->p1, pOp->p4.z);
+ break;
+}
+
+/* Opcode: DropTrigger P1 * * P4 *
+**
+** Remove the internal (in-memory) data structures that describe
+** the trigger named P4 in database P1. This is called after a trigger
+** is dropped in order to keep the internal representation of the
+** schema consistent with what is on disk.
+*/
+case OP_DropTrigger: {
+ sqlite3UnlinkAndDeleteTrigger(db, pOp->p1, pOp->p4.z);
+ break;
+}
+
+
+#ifndef SQLITE_OMIT_INTEGRITY_CHECK
+/* Opcode: IntegrityCk P1 P2 P3 * P5
+**
+** Do an analysis of the currently open database. Store in
+** register P1 the text of an error message describing any problems.
+** If no problems are found, store a NULL in register P1.
+**
+** The register P3 contains the maximum number of allowed errors.
+** At most reg(P3) errors will be reported.
+** In other words, the analysis stops as soon as reg(P1) errors are
+** seen. Reg(P1) is updated with the number of errors remaining.
+**
+** The root page numbers of all tables in the database are integer
+** stored in reg(P1), reg(P1+1), reg(P1+2), .... There are P2 tables
+** total.
+**
+** If P5 is not zero, the check is done on the auxiliary database
+** file, not the main database file.
+**
+** This opcode is used to implement the integrity_check pragma.
+*/
+case OP_IntegrityCk: {
+ int nRoot; /* Number of tables to check. (Number of root pages.) */
+ int *aRoot; /* Array of rootpage numbers for tables to be checked */
+ int j; /* Loop counter */
+ int nErr; /* Number of errors reported */
+ char *z; /* Text of the error report */
+ Mem *pnErr; /* Register keeping track of errors remaining */
+
+ nRoot = pOp->p2;
+ assert( nRoot>0 );
+ aRoot = sqlite3_malloc( sizeof(int)*(nRoot+1) );
+ if( aRoot==0 ) goto no_mem;
+ assert( pOp->p3>0 && pOp->p3<=p->nMem );
+ pnErr = &p->aMem[pOp->p3];
+ assert( (pnErr->flags & MEM_Int)!=0 );
+ assert( (pnErr->flags & (MEM_Str|MEM_Blob))==0 );
+ pIn1 = &p->aMem[pOp->p1];
+ for(j=0; j<nRoot; j++){
+ aRoot[j] = sqlite3VdbeIntValue(&pIn1[j]);
+ }
+ aRoot[j] = 0;
+ assert( pOp->p5<db->nDb );
+ assert( (p->btreeMask & (1<<pOp->p5))!=0 );
+ z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, aRoot, nRoot,
+ pnErr->u.i, &nErr);
+ pnErr->u.i -= nErr;
+ sqlite3VdbeMemSetNull(pIn1);
+ if( nErr==0 ){
+ assert( z==0 );
+ }else{
+ sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free);
+ }
+ UPDATE_MAX_BLOBSIZE(pIn1);
+ sqlite3VdbeChangeEncoding(pIn1, encoding);
+ sqlite3_free(aRoot);
+ break;
+}
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+/* Opcode: FifoWrite P1 * * * *
+**
+** Write the integer from register P1 into the Fifo.
+*/
+case OP_FifoWrite: { /* in1 */
+ if( sqlite3VdbeFifoPush(&p->sFifo, sqlite3VdbeIntValue(pIn1))==SQLITE_NOMEM ){
+ goto no_mem;
+ }
+ break;
+}
+
+/* Opcode: FifoRead P1 P2 * * *
+**
+** Attempt to read a single integer from the Fifo. Store that
+** integer in register P1.
+**
+** If the Fifo is empty jump to P2.
+*/
+case OP_FifoRead: { /* jump */
+ CHECK_FOR_INTERRUPT;
+ assert( pOp->p1>0 && pOp->p1<=p->nMem );
+ pOut = &p->aMem[pOp->p1];
+ MemSetTypeFlag(pOut, MEM_Int);
+ if( sqlite3VdbeFifoPop(&p->sFifo, &pOut->u.i)==SQLITE_DONE ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+#ifndef SQLITE_OMIT_TRIGGER
+/* Opcode: ContextPush * * *
+**
+** Save the current Vdbe context such that it can be restored by a ContextPop
+** opcode. The context stores the last insert row id, the last statement change
+** count, and the current statement change count.
+*/
+case OP_ContextPush: {
+ int i = p->contextStackTop++;
+ Context *pContext;
+
+ assert( i>=0 );
+ /* FIX ME: This should be allocated as part of the vdbe at compile-time */
+ if( i>=p->contextStackDepth ){
+ p->contextStackDepth = i+1;
+ p->contextStack = sqlite3DbReallocOrFree(db, p->contextStack,
+ sizeof(Context)*(i+1));
+ if( p->contextStack==0 ) goto no_mem;
+ }
+ pContext = &p->contextStack[i];
+ pContext->lastRowid = db->lastRowid;
+ pContext->nChange = p->nChange;
+ pContext->sFifo = p->sFifo;
+ sqlite3VdbeFifoInit(&p->sFifo);
+ break;
+}
+
+/* Opcode: ContextPop * * *
+**
+** Restore the Vdbe context to the state it was in when contextPush was last
+** executed. The context stores the last insert row id, the last statement
+** change count, and the current statement change count.
+*/
+case OP_ContextPop: {
+ Context *pContext = &p->contextStack[--p->contextStackTop];
+ assert( p->contextStackTop>=0 );
+ db->lastRowid = pContext->lastRowid;
+ p->nChange = pContext->nChange;
+ sqlite3VdbeFifoClear(&p->sFifo);
+ p->sFifo = pContext->sFifo;
+ break;
+}
+#endif /* #ifndef SQLITE_OMIT_TRIGGER */
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+/* Opcode: MemMax P1 P2 * * *
+**
+** Set the value of register P1 to the maximum of its current value
+** and the value in register P2.
+**
+** This instruction throws an error if the memory cell is not initially
+** an integer.
+*/
+case OP_MemMax: { /* in1, in2 */
+ sqlite3VdbeMemIntegerify(pIn1);
+ sqlite3VdbeMemIntegerify(pIn2);
+ if( pIn1->u.i<pIn2->u.i){
+ pIn1->u.i = pIn2->u.i;
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_AUTOINCREMENT */
+
+/* Opcode: IfPos P1 P2 * * *
+**
+** If the value of register P1 is 1 or greater, jump to P2.
+**
+** It is illegal to use this instruction on a register that does
+** not contain an integer. An assertion fault will result if you try.
+*/
+case OP_IfPos: { /* jump, in1 */
+ assert( pIn1->flags&MEM_Int );
+ if( pIn1->u.i>0 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: IfNeg P1 P2 * * *
+**
+** If the value of register P1 is less than zero, jump to P2.
+**
+** It is illegal to use this instruction on a register that does
+** not contain an integer. An assertion fault will result if you try.
+*/
+case OP_IfNeg: { /* jump, in1 */
+ assert( pIn1->flags&MEM_Int );
+ if( pIn1->u.i<0 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: IfZero P1 P2 * * *
+**
+** If the value of register P1 is exactly 0, jump to P2.
+**
+** It is illegal to use this instruction on a register that does
+** not contain an integer. An assertion fault will result if you try.
+*/
+case OP_IfZero: { /* jump, in1 */
+ assert( pIn1->flags&MEM_Int );
+ if( pIn1->u.i==0 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: AggStep * P2 P3 P4 P5
+**
+** Execute the step function for an aggregate. The
+** function has P5 arguments. P4 is a pointer to the FuncDef
+** structure that specifies the function. Use register
+** P3 as the accumulator.
+**
+** The P5 arguments are taken from register P2 and its
+** successors.
+*/
+case OP_AggStep: {
+ int n = pOp->p5;
+ int i;
+ Mem *pMem, *pRec;
+ sqlite3_context ctx;
+ sqlite3_value **apVal;
+
+ assert( n>=0 );
+ pRec = &p->aMem[pOp->p2];
+ apVal = p->apArg;
+ assert( apVal || n==0 );
+ for(i=0; i<n; i++, pRec++){
+ apVal[i] = pRec;
+ storeTypeInfo(pRec, encoding);
+ }
+ ctx.pFunc = pOp->p4.pFunc;
+ assert( pOp->p3>0 && pOp->p3<=p->nMem );
+ ctx.pMem = pMem = &p->aMem[pOp->p3];
+ pMem->n++;
+ ctx.s.flags = MEM_Null;
+ ctx.s.z = 0;
+ ctx.s.zMalloc = 0;
+ ctx.s.xDel = 0;
+ ctx.s.db = db;
+ ctx.isError = 0;
+ ctx.pColl = 0;
+ if( ctx.pFunc->needCollSeq ){
+ assert( pOp>p->aOp );
+ assert( pOp[-1].p4type==P4_COLLSEQ );
+ assert( pOp[-1].opcode==OP_CollSeq );
+ ctx.pColl = pOp[-1].p4.pColl;
+ }
+ (ctx.pFunc->xStep)(&ctx, n, apVal);
+ if( ctx.isError ){
+ sqlite3SetString(&p->zErrMsg, sqlite3_value_text(&ctx.s), (char*)0);
+ rc = ctx.isError;
+ }
+ sqlite3VdbeMemRelease(&ctx.s);
+ break;
+}
+
+/* Opcode: AggFinal P1 P2 * P4 *
+**
+** Execute the finalizer function for an aggregate. P1 is
+** the memory location that is the accumulator for the aggregate.
+**
+** P2 is the number of arguments that the step function takes and
+** P4 is a pointer to the FuncDef for this function. The P2
+** argument is not used by this opcode. It is only there to disambiguate
+** functions that can take varying numbers of arguments. The
+** P4 argument is only needed for the degenerate case where
+** the step function was not previously called.
+*/
+case OP_AggFinal: {
+ Mem *pMem;
+ assert( pOp->p1>0 && pOp->p1<=p->nMem );
+ pMem = &p->aMem[pOp->p1];
+ assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 );
+ rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc);
+ if( rc==SQLITE_ERROR ){
+ sqlite3SetString(&p->zErrMsg, sqlite3_value_text(pMem), (char*)0);
+ }
+ sqlite3VdbeChangeEncoding(pMem, encoding);
+ UPDATE_MAX_BLOBSIZE(pMem);
+ if( sqlite3VdbeMemTooBig(pMem) ){
+ goto too_big;
+ }
+ break;
+}
+
+
+#if !defined(SQLITE_OMIT_VACUUM) && !defined(SQLITE_OMIT_ATTACH)
+/* Opcode: Vacuum * * * * *
+**
+** Vacuum the entire database. This opcode will cause other virtual
+** machines to be created and run. It may not be called from within
+** a transaction.
+*/
+case OP_Vacuum: {
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ rc = sqlite3RunVacuum(&p->zErrMsg, db);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+ break;
+}
+#endif
+
+#if !defined(SQLITE_OMIT_AUTOVACUUM)
+/* Opcode: IncrVacuum P1 P2 * * *
+**
+** Perform a single step of the incremental vacuum procedure on
+** the P1 database. If the vacuum has finished, jump to instruction
+** P2. Otherwise, fall through to the next instruction.
+*/
+case OP_IncrVacuum: { /* jump */
+ Btree *pBt;
+
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( (p->btreeMask & (1<<pOp->p1))!=0 );
+ pBt = db->aDb[pOp->p1].pBt;
+ rc = sqlite3BtreeIncrVacuum(pBt);
+ if( rc==SQLITE_DONE ){
+ pc = pOp->p2 - 1;
+ rc = SQLITE_OK;
+ }
+ break;
+}
+#endif
+
+/* Opcode: Expire P1 * * * *
+**
+** Cause precompiled statements to become expired. An expired statement
+** fails with an error code of SQLITE_SCHEMA if it is ever executed
+** (via sqlite3_step()).
+**
+** If P1 is 0, then all SQL statements become expired. If P1 is non-zero,
+** then only the currently executing statement is affected.
+*/
+case OP_Expire: {
+ if( !pOp->p1 ){
+ sqlite3ExpirePreparedStatements(db);
+ }else{
+ p->expired = 1;
+ }
+ break;
+}
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/* Opcode: TableLock P1 P2 P3 P4 *
+**
+** Obtain a lock on a particular table. This instruction is only used when
+** the shared-cache feature is enabled.
+**
+** If P1 is the index of the database in sqlite3.aDb[] of the database
+** on which the lock is acquired. A readlock is obtained if P3==0 or
+** a write lock if P3==1.
+**
+** P2 contains the root-page of the table to lock.
+**
+** P4 contains a pointer to the name of the table being locked. This is only
+** used to generate an error message if the lock cannot be obtained.
+*/
+case OP_TableLock: {
+ int p1 = pOp->p1;
+ u8 isWriteLock = pOp->p3;
+ assert( p1>=0 && p1<db->nDb );
+ assert( (p->btreeMask & (1<<p1))!=0 );
+ assert( isWriteLock==0 || isWriteLock==1 );
+ rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
+ if( rc==SQLITE_LOCKED ){
+ const char *z = pOp->p4.z;
+ sqlite3SetString(&p->zErrMsg, "database table is locked: ", z, (char*)0);
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_SHARED_CACHE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VBegin * * * P4 *
+**
+** P4 a pointer to an sqlite3_vtab structure. Call the xBegin method
+** for that table.
+*/
+case OP_VBegin: {
+ rc = sqlite3VtabBegin(db, pOp->p4.pVtab);
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VCreate P1 * * P4 *
+**
+** P4 is the name of a virtual table in database P1. Call the xCreate method
+** for that table.
+*/
+case OP_VCreate: {
+ rc = sqlite3VtabCallCreate(db, pOp->p1, pOp->p4.z, &p->zErrMsg);
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VDestroy P1 * * P4 *
+**
+** P4 is the name of a virtual table in database P1. Call the xDestroy method
+** of that table.
+*/
+case OP_VDestroy: {
+ p->inVtabMethod = 2;
+ rc = sqlite3VtabCallDestroy(db, pOp->p1, pOp->p4.z);
+ p->inVtabMethod = 0;
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VOpen P1 * * P4 *
+**
+** P4 is a pointer to a virtual table object, an sqlite3_vtab structure.
+** P1 is a cursor number. This opcode opens a cursor to the virtual
+** table and stores that cursor in P1.
+*/
+case OP_VOpen: {
+ Cursor *pCur = 0;
+ sqlite3_vtab_cursor *pVtabCursor = 0;
+
+ sqlite3_vtab *pVtab = pOp->p4.pVtab;
+ sqlite3_module *pModule = (sqlite3_module *)pVtab->pModule;
+
+ assert(pVtab && pModule);
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ rc = pModule->xOpen(pVtab, &pVtabCursor);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+ if( SQLITE_OK==rc ){
+ /* Initialise sqlite3_vtab_cursor base class */
+ pVtabCursor->pVtab = pVtab;
+
+ /* Initialise vdbe cursor object */
+ pCur = allocateCursor(p, pOp->p1, &pOp[-1], -1, 0);
+ if( pCur ){
+ pCur->pVtabCursor = pVtabCursor;
+ pCur->pModule = pVtabCursor->pVtab->pModule;
+ }else{
+ db->mallocFailed = 1;
+ pModule->xClose(pVtabCursor);
+ }
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VFilter P1 P2 P3 P4 *
+**
+** P1 is a cursor opened using VOpen. P2 is an address to jump to if
+** the filtered result set is empty.
+**
+** P4 is either NULL or a string that was generated by the xBestIndex
+** method of the module. The interpretation of the P4 string is left
+** to the module implementation.
+**
+** This opcode invokes the xFilter method on the virtual table specified
+** by P1. The integer query plan parameter to xFilter is stored in register
+** P3. Register P3+1 stores the argc parameter to be passed to the
+** xFilter method. Registers P3+2..P3+1+argc are the argc additional
+** parametersneath additional parameters which are passed to
+** xFilter as argv. Register P3+2 becomes argv[0] when passed to xFilter.
+**
+** A jump is made to P2 if the result set after filtering would be empty.
+*/
+case OP_VFilter: { /* jump */
+ int nArg;
+ int iQuery;
+ const sqlite3_module *pModule;
+ Mem *pQuery = &p->aMem[pOp->p3];
+ Mem *pArgc = &pQuery[1];
+
+ Cursor *pCur = p->apCsr[pOp->p1];
+
+ REGISTER_TRACE(pOp->p3, pQuery);
+ assert( pCur->pVtabCursor );
+ pModule = pCur->pVtabCursor->pVtab->pModule;
+
+ /* Grab the index number and argc parameters */
+ assert( (pQuery->flags&MEM_Int)!=0 && pArgc->flags==MEM_Int );
+ nArg = pArgc->u.i;
+ iQuery = pQuery->u.i;
+
+ /* Invoke the xFilter method */
+ {
+ int res = 0;
+ int i;
+ Mem **apArg = p->apArg;
+ for(i = 0; i<nArg; i++){
+ apArg[i] = &pArgc[i+1];
+ storeTypeInfo(apArg[i], 0);
+ }
+
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ p->inVtabMethod = 1;
+ rc = pModule->xFilter(pCur->pVtabCursor, iQuery, pOp->p4.z, nArg, apArg);
+ p->inVtabMethod = 0;
+ if( rc==SQLITE_OK ){
+ res = pModule->xEof(pCur->pVtabCursor);
+ }
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+
+ if( res ){
+ pc = pOp->p2 - 1;
+ }
+ }
+ pCur->nullRow = 0;
+
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VRowid P1 P2 * * *
+**
+** Store into register P2 the rowid of
+** the virtual-table that the P1 cursor is pointing to.
+*/
+case OP_VRowid: { /* out2-prerelease */
+ const sqlite3_module *pModule;
+ sqlite_int64 iRow;
+ Cursor *pCur = p->apCsr[pOp->p1];
+
+ assert( pCur->pVtabCursor );
+ if( pCur->nullRow ){
+ break;
+ }
+ pModule = pCur->pVtabCursor->pVtab->pModule;
+ assert( pModule->xRowid );
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ rc = pModule->xRowid(pCur->pVtabCursor, &iRow);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+ MemSetTypeFlag(pOut, MEM_Int);
+ pOut->u.i = iRow;
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VColumn P1 P2 P3 * *
+**
+** Store the value of the P2-th column of
+** the row of the virtual-table that the
+** P1 cursor is pointing to into register P3.
+*/
+case OP_VColumn: {
+ const sqlite3_module *pModule;
+ Mem *pDest;
+ sqlite3_context sContext;
+
+ Cursor *pCur = p->apCsr[pOp->p1];
+ assert( pCur->pVtabCursor );
+ assert( pOp->p3>0 && pOp->p3<=p->nMem );
+ pDest = &p->aMem[pOp->p3];
+ if( pCur->nullRow ){
+ sqlite3VdbeMemSetNull(pDest);
+ break;
+ }
+ pModule = pCur->pVtabCursor->pVtab->pModule;
+ assert( pModule->xColumn );
+ memset(&sContext, 0, sizeof(sContext));
+
+ /* The output cell may already have a buffer allocated. Move
+ ** the current contents to sContext.s so in case the user-function
+ ** can use the already allocated buffer instead of allocating a
+ ** new one.
+ */
+ sqlite3VdbeMemMove(&sContext.s, pDest);
+ MemSetTypeFlag(&sContext.s, MEM_Null);
+
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ rc = pModule->xColumn(pCur->pVtabCursor, &sContext, pOp->p2);
+
+ /* Copy the result of the function to the P3 register. We
+ ** do this regardless of whether or not an error occured to ensure any
+ ** dynamic allocation in sContext.s (a Mem struct) is released.
+ */
+ sqlite3VdbeChangeEncoding(&sContext.s, encoding);
+ REGISTER_TRACE(pOp->p3, pDest);
+ sqlite3VdbeMemMove(pDest, &sContext.s);
+ UPDATE_MAX_BLOBSIZE(pDest);
+
+ if( sqlite3SafetyOn(db) ){
+ goto abort_due_to_misuse;
+ }
+ if( sqlite3VdbeMemTooBig(pDest) ){
+ goto too_big;
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VNext P1 P2 * * *
+**
+** Advance virtual table P1 to the next row in its result set and
+** jump to instruction P2. Or, if the virtual table has reached
+** the end of its result set, then fall through to the next instruction.
+*/
+case OP_VNext: { /* jump */
+ const sqlite3_module *pModule;
+ int res = 0;
+
+ Cursor *pCur = p->apCsr[pOp->p1];
+ assert( pCur->pVtabCursor );
+ if( pCur->nullRow ){
+ break;
+ }
+ pModule = pCur->pVtabCursor->pVtab->pModule;
+ assert( pModule->xNext );
+
+ /* Invoke the xNext() method of the module. There is no way for the
+ ** underlying implementation to return an error if one occurs during
+ ** xNext(). Instead, if an error occurs, true is returned (indicating that
+ ** data is available) and the error code returned when xColumn or
+ ** some other method is next invoked on the save virtual table cursor.
+ */
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ p->inVtabMethod = 1;
+ rc = pModule->xNext(pCur->pVtabCursor);
+ p->inVtabMethod = 0;
+ if( rc==SQLITE_OK ){
+ res = pModule->xEof(pCur->pVtabCursor);
+ }
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+
+ if( !res ){
+ /* If there is data, jump to P2 */
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VRename P1 * * P4 *
+**
+** P4 is a pointer to a virtual table object, an sqlite3_vtab structure.
+** This opcode invokes the corresponding xRename method. The value
+** in register P1 is passed as the zName argument to the xRename method.
+*/
+case OP_VRename: {
+ sqlite3_vtab *pVtab = pOp->p4.pVtab;
+ Mem *pName = &p->aMem[pOp->p1];
+ assert( pVtab->pModule->xRename );
+ REGISTER_TRACE(pOp->p1, pName);
+
+ Stringify(pName, encoding);
+
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ sqlite3VtabLock(pVtab);
+ rc = pVtab->pModule->xRename(pVtab, pName->z);
+ sqlite3VtabUnlock(db, pVtab);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+
+ break;
+}
+#endif
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Opcode: VUpdate P1 P2 P3 P4 *
+**
+** P4 is a pointer to a virtual table object, an sqlite3_vtab structure.
+** This opcode invokes the corresponding xUpdate method. P2 values
+** are contiguous memory cells starting at P3 to pass to the xUpdate
+** invocation. The value in register (P3+P2-1) corresponds to the
+** p2th element of the argv array passed to xUpdate.
+**
+** The xUpdate method will do a DELETE or an INSERT or both.
+** The argv[0] element (which corresponds to memory cell P3)
+** is the rowid of a row to delete. If argv[0] is NULL then no
+** deletion occurs. The argv[1] element is the rowid of the new
+** row. This can be NULL to have the virtual table select the new
+** rowid for itself. The subsequent elements in the array are
+** the values of columns in the new row.
+**
+** If P2==1 then no insert is performed. argv[0] is the rowid of
+** a row to delete.
+**
+** P1 is a boolean flag. If it is set to true and the xUpdate call
+** is successful, then the value returned by sqlite3_last_insert_rowid()
+** is set to the value of the rowid for the row just inserted.
+*/
+case OP_VUpdate: {
+ sqlite3_vtab *pVtab = pOp->p4.pVtab;
+ sqlite3_module *pModule = (sqlite3_module *)pVtab->pModule;
+ int nArg = pOp->p2;
+ assert( pOp->p4type==P4_VTAB );
+ if( pModule->xUpdate==0 ){
+ sqlite3SetString(&p->zErrMsg, "read-only table", 0);
+ rc = SQLITE_ERROR;
+ }else{
+ int i;
+ sqlite_int64 rowid;
+ Mem **apArg = p->apArg;
+ Mem *pX = &p->aMem[pOp->p3];
+ for(i=0; i<nArg; i++){
+ storeTypeInfo(pX, 0);
+ apArg[i] = pX;
+ pX++;
+ }
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ sqlite3VtabLock(pVtab);
+ rc = pModule->xUpdate(pVtab, nArg, apArg, &rowid);
+ sqlite3VtabUnlock(db, pVtab);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+ if( pOp->p1 && rc==SQLITE_OK ){
+ assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) );
+ db->lastRowid = rowid;
+ }
+ p->nChange++;
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifndef SQLITE_OMIT_TRACE
+/* Opcode: Trace * * * P4 *
+**
+** If tracing is enabled (by the sqlite3_trace()) interface, then
+** the UTF-8 string contained in P4 is emitted on the trace callback.
+*/
+case OP_Trace: {
+ if( pOp->p4.z ){
+ if( db->xTrace ){
+ db->xTrace(db->pTraceArg, pOp->p4.z);
+ }
+#ifdef SQLITE_DEBUG
+ if( (db->flags & SQLITE_SqlTrace)!=0 ){
+ sqlite3DebugPrintf("SQL-trace: %s\n", pOp->p4.z);
+ }
+#endif /* SQLITE_DEBUG */
+ }
+ break;
+}
+#endif
+
+
+/* Opcode: Noop * * * * *
+**
+** Do nothing. This instruction is often useful as a jump
+** destination.
+*/
+/*
+** The magic Explain opcode are only inserted when explain==2 (which
+** is to say when the EXPLAIN QUERY PLAN syntax is used.)
+** This opcode records information from the optimizer. It is the
+** the same as a no-op. This opcodesnever appears in a real VM program.
+*/
+default: { /* This is really OP_Noop and OP_Explain */
+ break;
+}
+
+/*****************************************************************************
+** The cases of the switch statement above this line should all be indented
+** by 6 spaces. But the left-most 6 spaces have been removed to improve the
+** readability. From this point on down, the normal indentation rules are
+** restored.
+*****************************************************************************/
+ }
+
+#ifdef VDBE_PROFILE
+ {
+ long long elapse = hwtime() - start;
+ pOp->cycles += elapse;
+ pOp->cnt++;
+#if 0
+ fprintf(stdout, "%10lld ", elapse);
+ sqlite3VdbePrintOp(stdout, origPc, &p->aOp[origPc]);
+#endif
+ }
+#endif
+
+ /* The following code adds nothing to the actual functionality
+ ** of the program. It is only here for testing and debugging.
+ ** On the other hand, it does burn CPU cycles every time through
+ ** the evaluator loop. So we can leave it out when NDEBUG is defined.
+ */
+#ifndef NDEBUG
+ assert( pc>=-1 && pc<p->nOp );
+
+#ifdef SQLITE_DEBUG
+ if( p->trace ){
+ if( rc!=0 ) fprintf(p->trace,"rc=%d\n",rc);
+ if( opProperty & OPFLG_OUT2_PRERELEASE ){
+ registerTrace(p->trace, pOp->p2, pOut);
+ }
+ if( opProperty & OPFLG_OUT3 ){
+ registerTrace(p->trace, pOp->p3, pOut);
+ }
+ }
+#endif /* SQLITE_DEBUG */
+#endif /* NDEBUG */
+ } /* The end of the for(;;) loop the loops through opcodes */
+
+ /* If we reach this point, it means that execution is finished with
+ ** an error of some kind.
+ */
+vdbe_error_halt:
+ assert( rc );
+ p->rc = rc;
+ rc = SQLITE_ERROR;
+ sqlite3VdbeHalt(p);
+
+ /* This is the only way out of this procedure. We have to
+ ** release the mutexes on btrees that were acquired at the
+ ** top. */
+vdbe_return:
+ sqlite3BtreeMutexArrayLeave(&p->aMutex);
+ return rc;
+
+ /* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH
+ ** is encountered.
+ */
+too_big:
+ sqlite3SetString(&p->zErrMsg, "string or blob too big", (char*)0);
+ rc = SQLITE_TOOBIG;
+ goto vdbe_error_halt;
+
+ /* Jump to here if a malloc() fails.
+ */
+no_mem:
+ db->mallocFailed = 1;
+ sqlite3SetString(&p->zErrMsg, "out of memory", (char*)0);
+ rc = SQLITE_NOMEM;
+ goto vdbe_error_halt;
+
+ /* Jump to here for an SQLITE_MISUSE error.
+ */
+abort_due_to_misuse:
+ rc = SQLITE_MISUSE;
+ /* Fall thru into abort_due_to_error */
+
+ /* Jump to here for any other kind of fatal error. The "rc" variable
+ ** should hold the error number.
+ */
+abort_due_to_error:
+ assert( p->zErrMsg==0 );
+ if( db->mallocFailed ) rc = SQLITE_NOMEM;
+ sqlite3SetString(&p->zErrMsg, sqlite3ErrStr(rc), (char*)0);
+ goto vdbe_error_halt;
+
+ /* Jump to here if the sqlite3_interrupt() API sets the interrupt
+ ** flag.
+ */
+abort_due_to_interrupt:
+ assert( db->u1.isInterrupted );
+ rc = SQLITE_INTERRUPT;
+ p->rc = rc;
+ sqlite3SetString(&p->zErrMsg, sqlite3ErrStr(rc), (char*)0);
+ goto vdbe_error_halt;
+}
+
+/************** End of vdbe.c ************************************************/
+/************** Begin file vdbeblob.c ****************************************/
+/*
+** 2007 May 1
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file contains code used to implement incremental BLOB I/O.
+**
+** $Id: vdbeblob.c,v 1.22 2008/04/24 09:49:55 danielk1977 Exp $
+*/
+
+
+#ifndef SQLITE_OMIT_INCRBLOB
+
+/*
+** Valid sqlite3_blob* handles point to Incrblob structures.
+*/
+typedef struct Incrblob Incrblob;
+struct Incrblob {
+ int flags; /* Copy of "flags" passed to sqlite3_blob_open() */
+ int nByte; /* Size of open blob, in bytes */
+ int iOffset; /* Byte offset of blob in cursor data */
+ BtCursor *pCsr; /* Cursor pointing at blob row */
+ sqlite3_stmt *pStmt; /* Statement holding cursor open */
+ sqlite3 *db; /* The associated database */
+};
+
+/*
+** Open a blob handle.
+*/
+SQLITE_API int sqlite3_blob_open(
+ sqlite3* db, /* The database connection */
+ const char *zDb, /* The attached database containing the blob */
+ const char *zTable, /* The table containing the blob */
+ const char *zColumn, /* The column containing the blob */
+ sqlite_int64 iRow, /* The row containing the glob */
+ int flags, /* True -> read/write access, false -> read-only */
+ sqlite3_blob **ppBlob /* Handle for accessing the blob returned here */
+){
+ int nAttempt = 0;
+ int iCol; /* Index of zColumn in row-record */
+
+ /* This VDBE program seeks a btree cursor to the identified
+ ** db/table/row entry. The reason for using a vdbe program instead
+ ** of writing code to use the b-tree layer directly is that the
+ ** vdbe program will take advantage of the various transaction,
+ ** locking and error handling infrastructure built into the vdbe.
+ **
+ ** After seeking the cursor, the vdbe executes an OP_ResultRow.
+ ** Code external to the Vdbe then "borrows" the b-tree cursor and
+ ** uses it to implement the blob_read(), blob_write() and
+ ** blob_bytes() functions.
+ **
+ ** The sqlite3_blob_close() function finalizes the vdbe program,
+ ** which closes the b-tree cursor and (possibly) commits the
+ ** transaction.
+ */
+ static const VdbeOpList openBlob[] = {
+ {OP_Transaction, 0, 0, 0}, /* 0: Start a transaction */
+ {OP_VerifyCookie, 0, 0, 0}, /* 1: Check the schema cookie */
+
+ /* One of the following two instructions is replaced by an
+ ** OP_Noop before exection.
+ */
+ {OP_SetNumColumns, 0, 0, 0}, /* 2: Num cols for cursor */
+ {OP_OpenRead, 0, 0, 0}, /* 3: Open cursor 0 for reading */
+ {OP_SetNumColumns, 0, 0, 0}, /* 4: Num cols for cursor */
+ {OP_OpenWrite, 0, 0, 0}, /* 5: Open cursor 0 for read/write */
+
+ {OP_Variable, 1, 1, 0}, /* 6: Push the rowid to the stack */
+ {OP_NotExists, 0, 10, 1}, /* 7: Seek the cursor */
+ {OP_Column, 0, 0, 1}, /* 8 */
+ {OP_ResultRow, 1, 0, 0}, /* 9 */
+ {OP_Close, 0, 0, 0}, /* 10 */
+ {OP_Halt, 0, 0, 0}, /* 11 */
+ };
+
+ Vdbe *v = 0;
+ int rc = SQLITE_OK;
+ char zErr[128];
+
+ zErr[0] = 0;
+ sqlite3_mutex_enter(db->mutex);
+ do {
+ Parse sParse;
+ Table *pTab;
+
+ memset(&sParse, 0, sizeof(Parse));
+ sParse.db = db;
+
+ rc = sqlite3SafetyOn(db);
+ if( rc!=SQLITE_OK ){
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+ }
+
+ sqlite3BtreeEnterAll(db);
+ pTab = sqlite3LocateTable(&sParse, 0, zTable, zDb);
+ if( pTab && IsVirtual(pTab) ){
+ pTab = 0;
+ sqlite3ErrorMsg(&sParse, "cannot open virtual table: %s", zTable);
+ }
+#ifndef SQLITE_OMIT_VIEW
+ if( pTab && pTab->pSelect ){
+ pTab = 0;
+ sqlite3ErrorMsg(&sParse, "cannot open view: %s", zTable);
+ }
+#endif
+ if( !pTab ){
+ if( sParse.zErrMsg ){
+ sqlite3_snprintf(sizeof(zErr), zErr, "%s", sParse.zErrMsg);
+ }
+ sqlite3_free(sParse.zErrMsg);
+ rc = SQLITE_ERROR;
+ (void)sqlite3SafetyOff(db);
+ sqlite3BtreeLeaveAll(db);
+ goto blob_open_out;
+ }
+
+ /* Now search pTab for the exact column. */
+ for(iCol=0; iCol < pTab->nCol; iCol++) {
+ if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){
+ break;
+ }
+ }
+ if( iCol==pTab->nCol ){
+ sqlite3_snprintf(sizeof(zErr), zErr, "no such column: \"%s\"", zColumn);
+ rc = SQLITE_ERROR;
+ (void)sqlite3SafetyOff(db);
+ sqlite3BtreeLeaveAll(db);
+ goto blob_open_out;
+ }
+
+ /* If the value is being opened for writing, check that the
+ ** column is not indexed. It is against the rules to open an
+ ** indexed column for writing.
+ */
+ if( flags ){
+ Index *pIdx;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int j;
+ for(j=0; j<pIdx->nColumn; j++){
+ if( pIdx->aiColumn[j]==iCol ){
+ sqlite3_snprintf(sizeof(zErr), zErr,
+ "cannot open indexed column for writing");
+ rc = SQLITE_ERROR;
+ (void)sqlite3SafetyOff(db);
+ sqlite3BtreeLeaveAll(db);
+ goto blob_open_out;
+ }
+ }
+ }
+ }
+
+ v = sqlite3VdbeCreate(db);
+ if( v ){
+ int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ sqlite3VdbeAddOpList(v, sizeof(openBlob)/sizeof(VdbeOpList), openBlob);
+
+ /* Configure the OP_Transaction */
+ sqlite3VdbeChangeP1(v, 0, iDb);
+ sqlite3VdbeChangeP2(v, 0, (flags ? 1 : 0));
+
+ /* Configure the OP_VerifyCookie */
+ sqlite3VdbeChangeP1(v, 1, iDb);
+ sqlite3VdbeChangeP2(v, 1, pTab->pSchema->schema_cookie);
+
+ /* Make sure a mutex is held on the table to be accessed */
+ sqlite3VdbeUsesBtree(v, iDb);
+
+ /* Remove either the OP_OpenWrite or OpenRead. Set the P2
+ ** parameter of the other to pTab->tnum.
+ */
+ sqlite3VdbeChangeToNoop(v, (flags ? 3 : 5), 1);
+ sqlite3VdbeChangeP2(v, (flags ? 5 : 3), pTab->tnum);
+ sqlite3VdbeChangeP3(v, (flags ? 5 : 3), iDb);
+
+ /* Configure the OP_SetNumColumns. Configure the cursor to
+ ** think that the table has one more column than it really
+ ** does. An OP_Column to retrieve this imaginary column will
+ ** always return an SQL NULL. This is useful because it means
+ ** we can invoke OP_Column to fill in the vdbe cursors type
+ ** and offset cache without causing any IO.
+ */
+ sqlite3VdbeChangeP2(v, flags ? 4 : 2, pTab->nCol+1);
+ if( !db->mallocFailed ){
+ sqlite3VdbeMakeReady(v, 1, 1, 1, 0);
+ }
+ }
+
+ sqlite3BtreeLeaveAll(db);
+ rc = sqlite3SafetyOff(db);
+ if( rc!=SQLITE_OK || db->mallocFailed ){
+ goto blob_open_out;
+ }
+
+ sqlite3_bind_int64((sqlite3_stmt *)v, 1, iRow);
+ rc = sqlite3_step((sqlite3_stmt *)v);
+ if( rc!=SQLITE_ROW ){
+ nAttempt++;
+ rc = sqlite3_finalize((sqlite3_stmt *)v);
+ sqlite3_snprintf(sizeof(zErr), zErr, sqlite3_errmsg(db));
+ v = 0;
+ }
+ } while( nAttempt<5 && rc==SQLITE_SCHEMA );
+
+ if( rc==SQLITE_ROW ){
+ /* The row-record has been opened successfully. Check that the
+ ** column in question contains text or a blob. If it contains
+ ** text, it is up to the caller to get the encoding right.
+ */
+ Incrblob *pBlob;
+ u32 type = v->apCsr[0]->aType[iCol];
+
+ if( type<12 ){
+ sqlite3_snprintf(sizeof(zErr), zErr, "cannot open value of type %s",
+ type==0?"null": type==7?"real": "integer"
+ );
+ rc = SQLITE_ERROR;
+ goto blob_open_out;
+ }
+ pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob));
+ if( db->mallocFailed ){
+ sqlite3_free(pBlob);
+ goto blob_open_out;
+ }
+ pBlob->flags = flags;
+ pBlob->pCsr = v->apCsr[0]->pCursor;
+ sqlite3BtreeEnterCursor(pBlob->pCsr);
+ sqlite3BtreeCacheOverflow(pBlob->pCsr);
+ sqlite3BtreeLeaveCursor(pBlob->pCsr);
+ pBlob->pStmt = (sqlite3_stmt *)v;
+ pBlob->iOffset = v->apCsr[0]->aOffset[iCol];
+ pBlob->nByte = sqlite3VdbeSerialTypeLen(type);
+ pBlob->db = db;
+ *ppBlob = (sqlite3_blob *)pBlob;
+ rc = SQLITE_OK;
+ }else if( rc==SQLITE_OK ){
+ sqlite3_snprintf(sizeof(zErr), zErr, "no such rowid: %lld", iRow);
+ rc = SQLITE_ERROR;
+ }
+
+blob_open_out:
+ zErr[sizeof(zErr)-1] = '\0';
+ if( rc!=SQLITE_OK || db->mallocFailed ){
+ sqlite3_finalize((sqlite3_stmt *)v);
+ }
+ sqlite3Error(db, rc, (rc==SQLITE_OK?0:zErr));
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** Close a blob handle that was previously created using
+** sqlite3_blob_open().
+*/
+SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){
+ Incrblob *p = (Incrblob *)pBlob;
+ int rc;
+
+ rc = sqlite3_finalize(p->pStmt);
+ sqlite3_free(p);
+ return rc;
+}
+
+/*
+** Perform a read or write operation on a blob
+*/
+static int blobReadWrite(
+ sqlite3_blob *pBlob,
+ void *z,
+ int n,
+ int iOffset,
+ int (*xCall)(BtCursor*, u32, u32, void*)
+){
+ int rc;
+ Incrblob *p = (Incrblob *)pBlob;
+ Vdbe *v;
+ sqlite3 *db = p->db;
+
+ /* Request is out of range. Return a transient error. */
+ if( (iOffset+n)>p->nByte ){
+ return SQLITE_ERROR;
+ }
+ sqlite3_mutex_enter(db->mutex);
+
+ /* If there is no statement handle, then the blob-handle has
+ ** already been invalidated. Return SQLITE_ABORT in this case.
+ */
+ v = (Vdbe*)p->pStmt;
+ if( v==0 ){
+ rc = SQLITE_ABORT;
+ }else{
+ /* Call either BtreeData() or BtreePutData(). If SQLITE_ABORT is
+ ** returned, clean-up the statement handle.
+ */
+ assert( db == v->db );
+ sqlite3BtreeEnterCursor(p->pCsr);
+ rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
+ sqlite3BtreeLeaveCursor(p->pCsr);
+ if( rc==SQLITE_ABORT ){
+ sqlite3VdbeFinalize(v);
+ p->pStmt = 0;
+ }else{
+ db->errCode = rc;
+ v->rc = rc;
+ }
+ }
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** Read data from a blob handle.
+*/
+SQLITE_API int sqlite3_blob_read(sqlite3_blob *pBlob, void *z, int n, int iOffset){
+ return blobReadWrite(pBlob, z, n, iOffset, sqlite3BtreeData);
+}
+
+/*
+** Write data to a blob handle.
+*/
+SQLITE_API int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){
+ return blobReadWrite(pBlob, (void *)z, n, iOffset, sqlite3BtreePutData);
+}
+
+/*
+** Query a blob handle for the size of the data.
+**
+** The Incrblob.nByte field is fixed for the lifetime of the Incrblob
+** so no mutex is required for access.
+*/
+SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *pBlob){
+ Incrblob *p = (Incrblob *)pBlob;
+ return p->nByte;
+}
+
+#endif /* #ifndef SQLITE_OMIT_INCRBLOB */
+
+/************** End of vdbeblob.c ********************************************/
+/************** Begin file journal.c *****************************************/
+/*
+** 2007 August 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** @(#) $Id: journal.c,v 1.8 2008/05/01 18:01:47 drh Exp $
+*/
+
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+
+/*
+** This file implements a special kind of sqlite3_file object used
+** by SQLite to create journal files if the atomic-write optimization
+** is enabled.
+**
+** The distinctive characteristic of this sqlite3_file is that the
+** actual on disk file is created lazily. When the file is created,
+** the caller specifies a buffer size for an in-memory buffer to
+** be used to service read() and write() requests. The actual file
+** on disk is not created or populated until either:
+**
+** 1) The in-memory representation grows too large for the allocated
+** buffer, or
+** 2) The xSync() method is called.
+*/
+
+
+
+/*
+** A JournalFile object is a subclass of sqlite3_file used by
+** as an open file handle for journal files.
+*/
+struct JournalFile {
+ sqlite3_io_methods *pMethod; /* I/O methods on journal files */
+ int nBuf; /* Size of zBuf[] in bytes */
+ char *zBuf; /* Space to buffer journal writes */
+ int iSize; /* Amount of zBuf[] currently used */
+ int flags; /* xOpen flags */
+ sqlite3_vfs *pVfs; /* The "real" underlying VFS */
+ sqlite3_file *pReal; /* The "real" underlying file descriptor */
+ const char *zJournal; /* Name of the journal file */
+};
+typedef struct JournalFile JournalFile;
+
+/*
+** If it does not already exists, create and populate the on-disk file
+** for JournalFile p.
+*/
+static int createFile(JournalFile *p){
+ int rc = SQLITE_OK;
+ if( !p->pReal ){
+ sqlite3_file *pReal = (sqlite3_file *)&p[1];
+ rc = sqlite3OsOpen(p->pVfs, p->zJournal, pReal, p->flags, 0);
+ if( rc==SQLITE_OK ){
+ p->pReal = pReal;
+ if( p->iSize>0 ){
+ assert(p->iSize<=p->nBuf);
+ rc = sqlite3OsWrite(p->pReal, p->zBuf, p->iSize, 0);
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+** Close the file.
+*/
+static int jrnlClose(sqlite3_file *pJfd){
+ JournalFile *p = (JournalFile *)pJfd;
+ if( p->pReal ){
+ sqlite3OsClose(p->pReal);
+ }
+ sqlite3_free(p->zBuf);
+ return SQLITE_OK;
+}
+
+/*
+** Read data from the file.
+*/
+static int jrnlRead(
+ sqlite3_file *pJfd, /* The journal file from which to read */
+ void *zBuf, /* Put the results here */
+ int iAmt, /* Number of bytes to read */
+ sqlite_int64 iOfst /* Begin reading at this offset */
+){
+ int rc = SQLITE_OK;
+ JournalFile *p = (JournalFile *)pJfd;
+ if( p->pReal ){
+ rc = sqlite3OsRead(p->pReal, zBuf, iAmt, iOfst);
+ }else{
+ assert( iAmt+iOfst<=p->iSize );
+ memcpy(zBuf, &p->zBuf[iOfst], iAmt);
+ }
+ return rc;
+}
+
+/*
+** Write data to the file.
+*/
+static int jrnlWrite(
+ sqlite3_file *pJfd, /* The journal file into which to write */
+ const void *zBuf, /* Take data to be written from here */
+ int iAmt, /* Number of bytes to write */
+ sqlite_int64 iOfst /* Begin writing at this offset into the file */
+){
+ int rc = SQLITE_OK;
+ JournalFile *p = (JournalFile *)pJfd;
+ if( !p->pReal && (iOfst+iAmt)>p->nBuf ){
+ rc = createFile(p);
+ }
+ if( rc==SQLITE_OK ){
+ if( p->pReal ){
+ rc = sqlite3OsWrite(p->pReal, zBuf, iAmt, iOfst);
+ }else{
+ memcpy(&p->zBuf[iOfst], zBuf, iAmt);
+ if( p->iSize<(iOfst+iAmt) ){
+ p->iSize = (iOfst+iAmt);
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+** Truncate the file.
+*/
+static int jrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){
+ int rc = SQLITE_OK;
+ JournalFile *p = (JournalFile *)pJfd;
+ if( p->pReal ){
+ rc = sqlite3OsTruncate(p->pReal, size);
+ }else if( size<p->iSize ){
+ p->iSize = size;
+ }
+ return rc;
+}
+
+/*
+** Sync the file.
+*/
+static int jrnlSync(sqlite3_file *pJfd, int flags){
+ int rc;
+ JournalFile *p = (JournalFile *)pJfd;
+ if( p->pReal ){
+ rc = sqlite3OsSync(p->pReal, flags);
+ }else{
+ rc = SQLITE_OK;
+ }
+ return rc;
+}
+
+/*
+** Query the size of the file in bytes.
+*/
+static int jrnlFileSize(sqlite3_file *pJfd, sqlite_int64 *pSize){
+ int rc = SQLITE_OK;
+ JournalFile *p = (JournalFile *)pJfd;
+ if( p->pReal ){
+ rc = sqlite3OsFileSize(p->pReal, pSize);
+ }else{
+ *pSize = (sqlite_int64) p->iSize;
+ }
+ return rc;
+}
+
+/*
+** Table of methods for JournalFile sqlite3_file object.
+*/
+static struct sqlite3_io_methods JournalFileMethods = {
+ 1, /* iVersion */
+ jrnlClose, /* xClose */
+ jrnlRead, /* xRead */
+ jrnlWrite, /* xWrite */
+ jrnlTruncate, /* xTruncate */
+ jrnlSync, /* xSync */
+ jrnlFileSize, /* xFileSize */
+ 0, /* xLock */
+ 0, /* xUnlock */
+ 0, /* xCheckReservedLock */
+ 0, /* xFileControl */
+ 0, /* xSectorSize */
+ 0 /* xDeviceCharacteristics */
+};
+
+/*
+** Open a journal file.
+*/
+SQLITE_PRIVATE int sqlite3JournalOpen(
+ sqlite3_vfs *pVfs, /* The VFS to use for actual file I/O */
+ const char *zName, /* Name of the journal file */
+ sqlite3_file *pJfd, /* Preallocated, blank file handle */
+ int flags, /* Opening flags */
+ int nBuf /* Bytes buffered before opening the file */
+){
+ JournalFile *p = (JournalFile *)pJfd;
+ memset(p, 0, sqlite3JournalSize(pVfs));
+ if( nBuf>0 ){
+ p->zBuf = sqlite3MallocZero(nBuf);
+ if( !p->zBuf ){
+ return SQLITE_NOMEM;
+ }
+ }else{
+ return sqlite3OsOpen(pVfs, zName, pJfd, flags, 0);
+ }
+ p->pMethod = &JournalFileMethods;
+ p->nBuf = nBuf;
+ p->flags = flags;
+ p->zJournal = zName;
+ p->pVfs = pVfs;
+ return SQLITE_OK;
+}
+
+/*
+** If the argument p points to a JournalFile structure, and the underlying
+** file has not yet been created, create it now.
+*/
+SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *p){
+ if( p->pMethods!=&JournalFileMethods ){
+ return SQLITE_OK;
+ }
+ return createFile((JournalFile *)p);
+}
+
+/*
+** Return the number of bytes required to store a JournalFile that uses vfs
+** pVfs to create the underlying on-disk files.
+*/
+SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){
+ return (pVfs->szOsFile+sizeof(JournalFile));
+}
+#endif
+
+/************** End of journal.c *********************************************/
+/************** Begin file expr.c ********************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains routines used for analyzing expressions and
+** for generating VDBE code that evaluates expressions in SQLite.
+**
+** $Id: expr.c,v 1.371 2008/05/01 17:16:53 drh Exp $
+*/
+
+/*
+** Return the 'affinity' of the expression pExpr if any.
+**
+** If pExpr is a column, a reference to a column via an 'AS' alias,
+** or a sub-select with a column as the return value, then the
+** affinity of that column is returned. Otherwise, 0x00 is returned,
+** indicating no affinity for the expression.
+**
+** i.e. the WHERE clause expresssions in the following statements all
+** have an affinity:
+**
+** CREATE TABLE t1(a);
+** SELECT * FROM t1 WHERE a;
+** SELECT a AS b FROM t1 WHERE b;
+** SELECT * FROM t1 WHERE (select a from t1);
+*/
+SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){
+ int op = pExpr->op;
+ if( op==TK_SELECT ){
+ return sqlite3ExprAffinity(pExpr->pSelect->pEList->a[0].pExpr);
+ }
+#ifndef SQLITE_OMIT_CAST
+ if( op==TK_CAST ){
+ return sqlite3AffinityType(&pExpr->token);
+ }
+#endif
+ return pExpr->affinity;
+}
+
+/*
+** Set the collating sequence for expression pExpr to be the collating
+** sequence named by pToken. Return a pointer to the revised expression.
+** The collating sequence is marked as "explicit" using the EP_ExpCollate
+** flag. An explicit collating sequence will override implicit
+** collating sequences.
+*/
+SQLITE_PRIVATE Expr *sqlite3ExprSetColl(Parse *pParse, Expr *pExpr, Token *pName){
+ char *zColl = 0; /* Dequoted name of collation sequence */
+ CollSeq *pColl;
+ zColl = sqlite3NameFromToken(pParse->db, pName);
+ if( pExpr && zColl ){
+ pColl = sqlite3LocateCollSeq(pParse, zColl, -1);
+ if( pColl ){
+ pExpr->pColl = pColl;
+ pExpr->flags |= EP_ExpCollate;
+ }
+ }
+ sqlite3_free(zColl);
+ return pExpr;
+}
+
+/*
+** Return the default collation sequence for the expression pExpr. If
+** there is no default collation type, return 0.
+*/
+SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
+ CollSeq *pColl = 0;
+ if( pExpr ){
+ int op;
+ pColl = pExpr->pColl;
+ op = pExpr->op;
+ if( (op==TK_CAST || op==TK_UPLUS) && !pColl ){
+ return sqlite3ExprCollSeq(pParse, pExpr->pLeft);
+ }
+ }
+ if( sqlite3CheckCollSeq(pParse, pColl) ){
+ pColl = 0;
+ }
+ return pColl;
+}
+
+/*
+** pExpr is an operand of a comparison operator. aff2 is the
+** type affinity of the other operand. This routine returns the
+** type affinity that should be used for the comparison operator.
+*/
+SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){
+ char aff1 = sqlite3ExprAffinity(pExpr);
+ if( aff1 && aff2 ){
+ /* Both sides of the comparison are columns. If one has numeric
+ ** affinity, use that. Otherwise use no affinity.
+ */
+ if( sqlite3IsNumericAffinity(aff1) || sqlite3IsNumericAffinity(aff2) ){
+ return SQLITE_AFF_NUMERIC;
+ }else{
+ return SQLITE_AFF_NONE;
+ }
+ }else if( !aff1 && !aff2 ){
+ /* Neither side of the comparison is a column. Compare the
+ ** results directly.
+ */
+ return SQLITE_AFF_NONE;
+ }else{
+ /* One side is a column, the other is not. Use the columns affinity. */
+ assert( aff1==0 || aff2==0 );
+ return (aff1 + aff2);
+ }
+}
+
+/*
+** pExpr is a comparison operator. Return the type affinity that should
+** be applied to both operands prior to doing the comparison.
+*/
+static char comparisonAffinity(Expr *pExpr){
+ char aff;
+ assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT ||
+ pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE ||
+ pExpr->op==TK_NE );
+ assert( pExpr->pLeft );
+ aff = sqlite3ExprAffinity(pExpr->pLeft);
+ if( pExpr->pRight ){
+ aff = sqlite3CompareAffinity(pExpr->pRight, aff);
+ }
+ else if( pExpr->pSelect ){
+ aff = sqlite3CompareAffinity(pExpr->pSelect->pEList->a[0].pExpr, aff);
+ }
+ else if( !aff ){
+ aff = SQLITE_AFF_NONE;
+ }
+ return aff;
+}
+
+/*
+** pExpr is a comparison expression, eg. '=', '<', IN(...) etc.
+** idx_affinity is the affinity of an indexed column. Return true
+** if the index with affinity idx_affinity may be used to implement
+** the comparison in pExpr.
+*/
+SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){
+ char aff = comparisonAffinity(pExpr);
+ switch( aff ){
+ case SQLITE_AFF_NONE:
+ return 1;
+ case SQLITE_AFF_TEXT:
+ return idx_affinity==SQLITE_AFF_TEXT;
+ default:
+ return sqlite3IsNumericAffinity(idx_affinity);
+ }
+}
+
+/*
+** Return the P5 value that should be used for a binary comparison
+** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2.
+*/
+static u8 binaryCompareP5(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){
+ u8 aff = (char)sqlite3ExprAffinity(pExpr2);
+ aff = sqlite3CompareAffinity(pExpr1, aff) | jumpIfNull;
+ return aff;
+}
+
+/*
+** Return a pointer to the collation sequence that should be used by
+** a binary comparison operator comparing pLeft and pRight.
+**
+** If the left hand expression has a collating sequence type, then it is
+** used. Otherwise the collation sequence for the right hand expression
+** is used, or the default (BINARY) if neither expression has a collating
+** type.
+**
+** Argument pRight (but not pLeft) may be a null pointer. In this case,
+** it is not considered.
+*/
+SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(
+ Parse *pParse,
+ Expr *pLeft,
+ Expr *pRight
+){
+ CollSeq *pColl;
+ assert( pLeft );
+ if( pLeft->flags & EP_ExpCollate ){
+ assert( pLeft->pColl );
+ pColl = pLeft->pColl;
+ }else if( pRight && pRight->flags & EP_ExpCollate ){
+ assert( pRight->pColl );
+ pColl = pRight->pColl;
+ }else{
+ pColl = sqlite3ExprCollSeq(pParse, pLeft);
+ if( !pColl ){
+ pColl = sqlite3ExprCollSeq(pParse, pRight);
+ }
+ }
+ return pColl;
+}
+
+/*
+** Generate the operands for a comparison operation. Before
+** generating the code for each operand, set the EP_AnyAff
+** flag on the expression so that it will be able to used a
+** cached column value that has previously undergone an
+** affinity change.
+*/
+static void codeCompareOperands(
+ Parse *pParse, /* Parsing and code generating context */
+ Expr *pLeft, /* The left operand */
+ int *pRegLeft, /* Register where left operand is stored */
+ int *pFreeLeft, /* Free this register when done */
+ Expr *pRight, /* The right operand */
+ int *pRegRight, /* Register where right operand is stored */
+ int *pFreeRight /* Write temp register for right operand there */
+){
+ while( pLeft->op==TK_UPLUS ) pLeft = pLeft->pLeft;
+ pLeft->flags |= EP_AnyAff;
+ *pRegLeft = sqlite3ExprCodeTemp(pParse, pLeft, pFreeLeft);
+ while( pRight->op==TK_UPLUS ) pRight = pRight->pLeft;
+ pRight->flags |= EP_AnyAff;
+ *pRegRight = sqlite3ExprCodeTemp(pParse, pRight, pFreeRight);
+}
+
+/*
+** Generate code for a comparison operator.
+*/
+static int codeCompare(
+ Parse *pParse, /* The parsing (and code generating) context */
+ Expr *pLeft, /* The left operand */
+ Expr *pRight, /* The right operand */
+ int opcode, /* The comparison opcode */
+ int in1, int in2, /* Register holding operands */
+ int dest, /* Jump here if true. */
+ int jumpIfNull /* If true, jump if either operand is NULL */
+){
+ int p5;
+ int addr;
+ CollSeq *p4;
+
+ p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
+ p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
+ addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
+ (void*)p4, P4_COLLSEQ);
+ sqlite3VdbeChangeP5(pParse->pVdbe, p5);
+ if( p5 & SQLITE_AFF_MASK ){
+ sqlite3ExprCacheAffinityChange(pParse, in1, 1);
+ sqlite3ExprCacheAffinityChange(pParse, in2, 1);
+ }
+ return addr;
+}
+
+/*
+** Construct a new expression node and return a pointer to it. Memory
+** for this node is obtained from sqlite3_malloc(). The calling function
+** is responsible for making sure the node eventually gets freed.
+*/
+SQLITE_PRIVATE Expr *sqlite3Expr(
+ sqlite3 *db, /* Handle for sqlite3DbMallocZero() (may be null) */
+ int op, /* Expression opcode */
+ Expr *pLeft, /* Left operand */
+ Expr *pRight, /* Right operand */
+ const Token *pToken /* Argument token */
+){
+ Expr *pNew;
+ pNew = sqlite3DbMallocZero(db, sizeof(Expr));
+ if( pNew==0 ){
+ /* When malloc fails, delete pLeft and pRight. Expressions passed to
+ ** this function must always be allocated with sqlite3Expr() for this
+ ** reason.
+ */
+ sqlite3ExprDelete(pLeft);
+ sqlite3ExprDelete(pRight);
+ return 0;
+ }
+ pNew->op = op;
+ pNew->pLeft = pLeft;
+ pNew->pRight = pRight;
+ pNew->iAgg = -1;
+ if( pToken ){
+ assert( pToken->dyn==0 );
+ pNew->span = pNew->token = *pToken;
+ }else if( pLeft ){
+ if( pRight ){
+ sqlite3ExprSpan(pNew, &pLeft->span, &pRight->span);
+ if( pRight->flags & EP_ExpCollate ){
+ pNew->flags |= EP_ExpCollate;
+ pNew->pColl = pRight->pColl;
+ }
+ }
+ if( pLeft->flags & EP_ExpCollate ){
+ pNew->flags |= EP_ExpCollate;
+ pNew->pColl = pLeft->pColl;
+ }
+ }
+
+ sqlite3ExprSetHeight(pNew);
+ return pNew;
+}
+
+/*
+** Works like sqlite3Expr() except that it takes an extra Parse*
+** argument and notifies the associated connection object if malloc fails.
+*/
+SQLITE_PRIVATE Expr *sqlite3PExpr(
+ Parse *pParse, /* Parsing context */
+ int op, /* Expression opcode */
+ Expr *pLeft, /* Left operand */
+ Expr *pRight, /* Right operand */
+ const Token *pToken /* Argument token */
+){
+ return sqlite3Expr(pParse->db, op, pLeft, pRight, pToken);
+}
+
+/*
+** When doing a nested parse, you can include terms in an expression
+** that look like this: #1 #2 ... These terms refer to registers
+** in the virtual machine. #N is the N-th register.
+**
+** This routine is called by the parser to deal with on of those terms.
+** It immediately generates code to store the value in a memory location.
+** The returns an expression that will code to extract the value from
+** that memory location as needed.
+*/
+SQLITE_PRIVATE Expr *sqlite3RegisterExpr(Parse *pParse, Token *pToken){
+ Vdbe *v = pParse->pVdbe;
+ Expr *p;
+ if( pParse->nested==0 ){
+ sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", pToken);
+ return sqlite3PExpr(pParse, TK_NULL, 0, 0, 0);
+ }
+ if( v==0 ) return 0;
+ p = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, pToken);
+ if( p==0 ){
+ return 0; /* Malloc failed */
+ }
+ p->iTable = atoi((char*)&pToken->z[1]);
+ return p;
+}
+
+/*
+** Join two expressions using an AND operator. If either expression is
+** NULL, then just return the other expression.
+*/
+SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3 *db, Expr *pLeft, Expr *pRight){
+ if( pLeft==0 ){
+ return pRight;
+ }else if( pRight==0 ){
+ return pLeft;
+ }else{
+ return sqlite3Expr(db, TK_AND, pLeft, pRight, 0);
+ }
+}
+
+/*
+** Set the Expr.span field of the given expression to span all
+** text between the two given tokens.
+*/
+SQLITE_PRIVATE void sqlite3ExprSpan(Expr *pExpr, Token *pLeft, Token *pRight){
+ assert( pRight!=0 );
+ assert( pLeft!=0 );
+ if( pExpr && pRight->z && pLeft->z ){
+ assert( pLeft->dyn==0 || pLeft->z[pLeft->n]==0 );
+ if( pLeft->dyn==0 && pRight->dyn==0 ){
+ pExpr->span.z = pLeft->z;
+ pExpr->span.n = pRight->n + (pRight->z - pLeft->z);
+ }else{
+ pExpr->span.z = 0;
+ }
+ }
+}
+
+/*
+** Construct a new expression node for a function with multiple
+** arguments.
+*/
+SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token *pToken){
+ Expr *pNew;
+ assert( pToken );
+ pNew = sqlite3DbMallocZero(pParse->db, sizeof(Expr) );
+ if( pNew==0 ){
+ sqlite3ExprListDelete(pList); /* Avoid leaking memory when malloc fails */
+ return 0;
+ }
+ pNew->op = TK_FUNCTION;
+ pNew->pList = pList;
+ assert( pToken->dyn==0 );
+ pNew->token = *pToken;
+ pNew->span = pNew->token;
+
+ sqlite3ExprSetHeight(pNew);
+ return pNew;
+}
+
+/*
+** Assign a variable number to an expression that encodes a wildcard
+** in the original SQL statement.
+**
+** Wildcards consisting of a single "?" are assigned the next sequential
+** variable number.
+**
+** Wildcards of the form "?nnn" are assigned the number "nnn". We make
+** sure "nnn" is not too be to avoid a denial of service attack when
+** the SQL statement comes from an external source.
+**
+** Wildcards of the form ":aaa" or "$aaa" are assigned the same number
+** as the previous instance of the same wildcard. Or if this is the first
+** instance of the wildcard, the next sequenial variable number is
+** assigned.
+*/
+SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){
+ Token *pToken;
+ sqlite3 *db = pParse->db;
+
+ if( pExpr==0 ) return;
+ pToken = &pExpr->token;
+ assert( pToken->n>=1 );
+ assert( pToken->z!=0 );
+ assert( pToken->z[0]!=0 );
+ if( pToken->n==1 ){
+ /* Wildcard of the form "?". Assign the next variable number */
+ pExpr->iTable = ++pParse->nVar;
+ }else if( pToken->z[0]=='?' ){
+ /* Wildcard of the form "?nnn". Convert "nnn" to an integer and
+ ** use it as the variable number */
+ int i;
+ pExpr->iTable = i = atoi((char*)&pToken->z[1]);
+ testcase( i==0 );
+ testcase( i==1 );
+ testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]-1 );
+ testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] );
+ if( i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){
+ sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d",
+ db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]);
+ }
+ if( i>pParse->nVar ){
+ pParse->nVar = i;
+ }
+ }else{
+ /* Wildcards of the form ":aaa" or "$aaa". Reuse the same variable
+ ** number as the prior appearance of the same name, or if the name
+ ** has never appeared before, reuse the same variable number
+ */
+ int i, n;
+ n = pToken->n;
+ for(i=0; i<pParse->nVarExpr; i++){
+ Expr *pE;
+ if( (pE = pParse->apVarExpr[i])!=0
+ && pE->token.n==n
+ && memcmp(pE->token.z, pToken->z, n)==0 ){
+ pExpr->iTable = pE->iTable;
+ break;
+ }
+ }
+ if( i>=pParse->nVarExpr ){
+ pExpr->iTable = ++pParse->nVar;
+ if( pParse->nVarExpr>=pParse->nVarExprAlloc-1 ){
+ pParse->nVarExprAlloc += pParse->nVarExprAlloc + 10;
+ pParse->apVarExpr =
+ sqlite3DbReallocOrFree(
+ db,
+ pParse->apVarExpr,
+ pParse->nVarExprAlloc*sizeof(pParse->apVarExpr[0])
+ );
+ }
+ if( !db->mallocFailed ){
+ assert( pParse->apVarExpr!=0 );
+ pParse->apVarExpr[pParse->nVarExpr++] = pExpr;
+ }
+ }
+ }
+ if( !pParse->nErr && pParse->nVar>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){
+ sqlite3ErrorMsg(pParse, "too many SQL variables");
+ }
+}
+
+/*
+** Recursively delete an expression tree.
+*/
+SQLITE_PRIVATE void sqlite3ExprDelete(Expr *p){
+ if( p==0 ) return;
+ if( p->span.dyn ) sqlite3_free((char*)p->span.z);
+ if( p->token.dyn ) sqlite3_free((char*)p->token.z);
+ sqlite3ExprDelete(p->pLeft);
+ sqlite3ExprDelete(p->pRight);
+ sqlite3ExprListDelete(p->pList);
+ sqlite3SelectDelete(p->pSelect);
+ sqlite3_free(p);
+}
+
+/*
+** The Expr.token field might be a string literal that is quoted.
+** If so, remove the quotation marks.
+*/
+SQLITE_PRIVATE void sqlite3DequoteExpr(sqlite3 *db, Expr *p){
+ if( ExprHasAnyProperty(p, EP_Dequoted) ){
+ return;
+ }
+ ExprSetProperty(p, EP_Dequoted);
+ if( p->token.dyn==0 ){
+ sqlite3TokenCopy(db, &p->token, &p->token);
+ }
+ sqlite3Dequote((char*)p->token.z);
+}
+
+
+/*
+** The following group of routines make deep copies of expressions,
+** expression lists, ID lists, and select statements. The copies can
+** be deleted (by being passed to their respective ...Delete() routines)
+** without effecting the originals.
+**
+** The expression list, ID, and source lists return by sqlite3ExprListDup(),
+** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded
+** by subsequent calls to sqlite*ListAppend() routines.
+**
+** Any tables that the SrcList might point to are not duplicated.
+*/
+SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, Expr *p){
+ Expr *pNew;
+ if( p==0 ) return 0;
+ pNew = sqlite3DbMallocRaw(db, sizeof(*p) );
+ if( pNew==0 ) return 0;
+ memcpy(pNew, p, sizeof(*pNew));
+ if( p->token.z!=0 ){
+ pNew->token.z = (u8*)sqlite3DbStrNDup(db, (char*)p->token.z, p->token.n);
+ pNew->token.dyn = 1;
+ }else{
+ assert( pNew->token.z==0 );
+ }
+ pNew->span.z = 0;
+ pNew->pLeft = sqlite3ExprDup(db, p->pLeft);
+ pNew->pRight = sqlite3ExprDup(db, p->pRight);
+ pNew->pList = sqlite3ExprListDup(db, p->pList);
+ pNew->pSelect = sqlite3SelectDup(db, p->pSelect);
+ return pNew;
+}
+SQLITE_PRIVATE void sqlite3TokenCopy(sqlite3 *db, Token *pTo, Token *pFrom){
+ if( pTo->dyn ) sqlite3_free((char*)pTo->z);
+ if( pFrom->z ){
+ pTo->n = pFrom->n;
+ pTo->z = (u8*)sqlite3DbStrNDup(db, (char*)pFrom->z, pFrom->n);
+ pTo->dyn = 1;
+ }else{
+ pTo->z = 0;
+ }
+}
+SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p){
+ ExprList *pNew;
+ struct ExprList_item *pItem, *pOldItem;
+ int i;
+ if( p==0 ) return 0;
+ pNew = sqlite3DbMallocRaw(db, sizeof(*pNew) );
+ if( pNew==0 ) return 0;
+ pNew->iECursor = 0;
+ pNew->nExpr = pNew->nAlloc = p->nExpr;
+ pNew->a = pItem = sqlite3DbMallocRaw(db, p->nExpr*sizeof(p->a[0]) );
+ if( pItem==0 ){
+ sqlite3_free(pNew);
+ return 0;
+ }
+ pOldItem = p->a;
+ for(i=0; i<p->nExpr; i++, pItem++, pOldItem++){
+ Expr *pNewExpr, *pOldExpr;
+ pItem->pExpr = pNewExpr = sqlite3ExprDup(db, pOldExpr = pOldItem->pExpr);
+ if( pOldExpr->span.z!=0 && pNewExpr ){
+ /* Always make a copy of the span for top-level expressions in the
+ ** expression list. The logic in SELECT processing that determines
+ ** the names of columns in the result set needs this information */
+ sqlite3TokenCopy(db, &pNewExpr->span, &pOldExpr->span);
+ }
+ assert( pNewExpr==0 || pNewExpr->span.z!=0
+ || pOldExpr->span.z==0
+ || db->mallocFailed );
+ pItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
+ pItem->sortOrder = pOldItem->sortOrder;
+ pItem->isAgg = pOldItem->isAgg;
+ pItem->done = 0;
+ }
+ return pNew;
+}
+
+/*
+** If cursors, triggers, views and subqueries are all omitted from
+** the build, then none of the following routines, except for
+** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes
+** called with a NULL argument.
+*/
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \
+ || !defined(SQLITE_OMIT_SUBQUERY)
+SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p){
+ SrcList *pNew;
+ int i;
+ int nByte;
+ if( p==0 ) return 0;
+ nByte = sizeof(*p) + (p->nSrc>0 ? sizeof(p->a[0]) * (p->nSrc-1) : 0);
+ pNew = sqlite3DbMallocRaw(db, nByte );
+ if( pNew==0 ) return 0;
+ pNew->nSrc = pNew->nAlloc = p->nSrc;
+ for(i=0; i<p->nSrc; i++){
+ struct SrcList_item *pNewItem = &pNew->a[i];
+ struct SrcList_item *pOldItem = &p->a[i];
+ Table *pTab;
+ pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase);
+ pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
+ pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
+ pNewItem->jointype = pOldItem->jointype;
+ pNewItem->iCursor = pOldItem->iCursor;
+ pNewItem->isPopulated = pOldItem->isPopulated;
+ pTab = pNewItem->pTab = pOldItem->pTab;
+ if( pTab ){
+ pTab->nRef++;
+ }
+ pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect);
+ pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn);
+ pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing);
+ pNewItem->colUsed = pOldItem->colUsed;
+ }
+ return pNew;
+}
+SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){
+ IdList *pNew;
+ int i;
+ if( p==0 ) return 0;
+ pNew = sqlite3DbMallocRaw(db, sizeof(*pNew) );
+ if( pNew==0 ) return 0;
+ pNew->nId = pNew->nAlloc = p->nId;
+ pNew->a = sqlite3DbMallocRaw(db, p->nId*sizeof(p->a[0]) );
+ if( pNew->a==0 ){
+ sqlite3_free(pNew);
+ return 0;
+ }
+ for(i=0; i<p->nId; i++){
+ struct IdList_item *pNewItem = &pNew->a[i];
+ struct IdList_item *pOldItem = &p->a[i];
+ pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
+ pNewItem->idx = pOldItem->idx;
+ }
+ return pNew;
+}
+SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p){
+ Select *pNew;
+ if( p==0 ) return 0;
+ pNew = sqlite3DbMallocRaw(db, sizeof(*p) );
+ if( pNew==0 ) return 0;
+ pNew->isDistinct = p->isDistinct;
+ pNew->pEList = sqlite3ExprListDup(db, p->pEList);
+ pNew->pSrc = sqlite3SrcListDup(db, p->pSrc);
+ pNew->pWhere = sqlite3ExprDup(db, p->pWhere);
+ pNew->pGroupBy = sqlite3ExprListDup(db, p->pGroupBy);
+ pNew->pHaving = sqlite3ExprDup(db, p->pHaving);
+ pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy);
+ pNew->op = p->op;
+ pNew->pPrior = sqlite3SelectDup(db, p->pPrior);
+ pNew->pLimit = sqlite3ExprDup(db, p->pLimit);
+ pNew->pOffset = sqlite3ExprDup(db, p->pOffset);
+ pNew->iLimit = -1;
+ pNew->iOffset = -1;
+ pNew->isResolved = p->isResolved;
+ pNew->isAgg = p->isAgg;
+ pNew->usesEphm = 0;
+ pNew->disallowOrderBy = 0;
+ pNew->pRightmost = 0;
+ pNew->addrOpenEphm[0] = -1;
+ pNew->addrOpenEphm[1] = -1;
+ pNew->addrOpenEphm[2] = -1;
+ return pNew;
+}
+#else
+SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p){
+ assert( p==0 );
+ return 0;
+}
+#endif
+
+
+/*
+** Add a new element to the end of an expression list. If pList is
+** initially NULL, then create a new expression list.
+*/
+SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* List to which to append. Might be NULL */
+ Expr *pExpr, /* Expression to be appended */
+ Token *pName /* AS keyword for the expression */
+){
+ sqlite3 *db = pParse->db;
+ if( pList==0 ){
+ pList = sqlite3DbMallocZero(db, sizeof(ExprList) );
+ if( pList==0 ){
+ goto no_mem;
+ }
+ assert( pList->nAlloc==0 );
+ }
+ if( pList->nAlloc<=pList->nExpr ){
+ struct ExprList_item *a;
+ int n = pList->nAlloc*2 + 4;
+ a = sqlite3DbRealloc(db, pList->a, n*sizeof(pList->a[0]));
+ if( a==0 ){
+ goto no_mem;
+ }
+ pList->a = a;
+ pList->nAlloc = n;
+ }
+ assert( pList->a!=0 );
+ if( pExpr || pName ){
+ struct ExprList_item *pItem = &pList->a[pList->nExpr++];
+ memset(pItem, 0, sizeof(*pItem));
+ pItem->zName = sqlite3NameFromToken(db, pName);
+ pItem->pExpr = pExpr;
+ }
+ return pList;
+
+no_mem:
+ /* Avoid leaking memory if malloc has failed. */
+ sqlite3ExprDelete(pExpr);
+ sqlite3ExprListDelete(pList);
+ return 0;
+}
+
+/*
+** If the expression list pEList contains more than iLimit elements,
+** leave an error message in pParse.
+*/
+SQLITE_PRIVATE void sqlite3ExprListCheckLength(
+ Parse *pParse,
+ ExprList *pEList,
+ const char *zObject
+){
+ int mx = pParse->db->aLimit[SQLITE_LIMIT_COLUMN];
+ testcase( pEList && pEList->nExpr==mx );
+ testcase( pEList && pEList->nExpr==mx+1 );
+ if( pEList && pEList->nExpr>mx ){
+ sqlite3ErrorMsg(pParse, "too many columns in %s", zObject);
+ }
+}
+
+
+/* The following three functions, heightOfExpr(), heightOfExprList()
+** and heightOfSelect(), are used to determine the maximum height
+** of any expression tree referenced by the structure passed as the
+** first argument.
+**
+** If this maximum height is greater than the current value pointed
+** to by pnHeight, the second parameter, then set *pnHeight to that
+** value.
+*/
+static void heightOfExpr(Expr *p, int *pnHeight){
+ if( p ){
+ if( p->nHeight>*pnHeight ){
+ *pnHeight = p->nHeight;
+ }
+ }
+}
+static void heightOfExprList(ExprList *p, int *pnHeight){
+ if( p ){
+ int i;
+ for(i=0; i<p->nExpr; i++){
+ heightOfExpr(p->a[i].pExpr, pnHeight);
+ }
+ }
+}
+static void heightOfSelect(Select *p, int *pnHeight){
+ if( p ){
+ heightOfExpr(p->pWhere, pnHeight);
+ heightOfExpr(p->pHaving, pnHeight);
+ heightOfExpr(p->pLimit, pnHeight);
+ heightOfExpr(p->pOffset, pnHeight);
+ heightOfExprList(p->pEList, pnHeight);
+ heightOfExprList(p->pGroupBy, pnHeight);
+ heightOfExprList(p->pOrderBy, pnHeight);
+ heightOfSelect(p->pPrior, pnHeight);
+ }
+}
+
+/*
+** Set the Expr.nHeight variable in the structure passed as an
+** argument. An expression with no children, Expr.pList or
+** Expr.pSelect member has a height of 1. Any other expression
+** has a height equal to the maximum height of any other
+** referenced Expr plus one.
+*/
+SQLITE_PRIVATE void sqlite3ExprSetHeight(Expr *p){
+ int nHeight = 0;
+ heightOfExpr(p->pLeft, &nHeight);
+ heightOfExpr(p->pRight, &nHeight);
+ heightOfExprList(p->pList, &nHeight);
+ heightOfSelect(p->pSelect, &nHeight);
+ p->nHeight = nHeight + 1;
+}
+
+/*
+** Return the maximum height of any expression tree referenced
+** by the select statement passed as an argument.
+*/
+SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){
+ int nHeight = 0;
+ heightOfSelect(p, &nHeight);
+ return nHeight;
+}
+
+/*
+** Delete an entire expression list.
+*/
+SQLITE_PRIVATE void sqlite3ExprListDelete(ExprList *pList){
+ int i;
+ struct ExprList_item *pItem;
+ if( pList==0 ) return;
+ assert( pList->a!=0 || (pList->nExpr==0 && pList->nAlloc==0) );
+ assert( pList->nExpr<=pList->nAlloc );
+ for(pItem=pList->a, i=0; i<pList->nExpr; i++, pItem++){
+ sqlite3ExprDelete(pItem->pExpr);
+ sqlite3_free(pItem->zName);
+ }
+ sqlite3_free(pList->a);
+ sqlite3_free(pList);
+}
+
+/*
+** Walk an expression tree. Call xFunc for each node visited. xFunc
+** is called on the node before xFunc is called on the nodes children.
+**
+** The return value from xFunc determines whether the tree walk continues.
+** 0 means continue walking the tree. 1 means do not walk children
+** of the current node but continue with siblings. 2 means abandon
+** the tree walk completely.
+**
+** The return value from this routine is 1 to abandon the tree walk
+** and 0 to continue.
+**
+** NOTICE: This routine does *not* descend into subqueries.
+*/
+static int walkExprList(ExprList *, int (*)(void *, Expr*), void *);
+static int walkExprTree(Expr *pExpr, int (*xFunc)(void*,Expr*), void *pArg){
+ int rc;
+ if( pExpr==0 ) return 0;
+ rc = (*xFunc)(pArg, pExpr);
+ if( rc==0 ){
+ if( walkExprTree(pExpr->pLeft, xFunc, pArg) ) return 1;
+ if( walkExprTree(pExpr->pRight, xFunc, pArg) ) return 1;
+ if( walkExprList(pExpr->pList, xFunc, pArg) ) return 1;
+ }
+ return rc>1;
+}
+
+/*
+** Call walkExprTree() for every expression in list p.
+*/
+static int walkExprList(ExprList *p, int (*xFunc)(void *, Expr*), void *pArg){
+ int i;
+ struct ExprList_item *pItem;
+ if( !p ) return 0;
+ for(i=p->nExpr, pItem=p->a; i>0; i--, pItem++){
+ if( walkExprTree(pItem->pExpr, xFunc, pArg) ) return 1;
+ }
+ return 0;
+}
+
+/*
+** Call walkExprTree() for every expression in Select p, not including
+** expressions that are part of sub-selects in any FROM clause or the LIMIT
+** or OFFSET expressions..
+*/
+static int walkSelectExpr(Select *p, int (*xFunc)(void *, Expr*), void *pArg){
+ walkExprList(p->pEList, xFunc, pArg);
+ walkExprTree(p->pWhere, xFunc, pArg);
+ walkExprList(p->pGroupBy, xFunc, pArg);
+ walkExprTree(p->pHaving, xFunc, pArg);
+ walkExprList(p->pOrderBy, xFunc, pArg);
+ if( p->pPrior ){
+ walkSelectExpr(p->pPrior, xFunc, pArg);
+ }
+ return 0;
+}
+
+
+/*
+** This routine is designed as an xFunc for walkExprTree().
+**
+** pArg is really a pointer to an integer. If we can tell by looking
+** at pExpr that the expression that contains pExpr is not a constant
+** expression, then set *pArg to 0 and return 2 to abandon the tree walk.
+** If pExpr does does not disqualify the expression from being a constant
+** then do nothing.
+**
+** After walking the whole tree, if no nodes are found that disqualify
+** the expression as constant, then we assume the whole expression
+** is constant. See sqlite3ExprIsConstant() for additional information.
+*/
+static int exprNodeIsConstant(void *pArg, Expr *pExpr){
+ int *pN = (int*)pArg;
+
+ /* If *pArg is 3 then any term of the expression that comes from
+ ** the ON or USING clauses of a join disqualifies the expression
+ ** from being considered constant. */
+ if( (*pN)==3 && ExprHasAnyProperty(pExpr, EP_FromJoin) ){
+ *pN = 0;
+ return 2;
+ }
+
+ switch( pExpr->op ){
+ /* Consider functions to be constant if all their arguments are constant
+ ** and *pArg==2 */
+ case TK_FUNCTION:
+ if( (*pN)==2 ) return 0;
+ /* Fall through */
+ case TK_ID:
+ case TK_COLUMN:
+ case TK_DOT:
+ case TK_AGG_FUNCTION:
+ case TK_AGG_COLUMN:
+#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_SELECT:
+ case TK_EXISTS:
+ testcase( pExpr->op==TK_SELECT );
+ testcase( pExpr->op==TK_EXISTS );
+#endif
+ testcase( pExpr->op==TK_ID );
+ testcase( pExpr->op==TK_COLUMN );
+ testcase( pExpr->op==TK_DOT );
+ testcase( pExpr->op==TK_AGG_FUNCTION );
+ testcase( pExpr->op==TK_AGG_COLUMN );
+ *pN = 0;
+ return 2;
+ case TK_IN:
+ if( pExpr->pSelect ){
+ *pN = 0;
+ return 2;
+ }
+ default:
+ return 0;
+ }
+}
+
+/*
+** Walk an expression tree. Return 1 if the expression is constant
+** and 0 if it involves variables or function calls.
+**
+** For the purposes of this function, a double-quoted string (ex: "abc")
+** is considered a variable but a single-quoted string (ex: 'abc') is
+** a constant.
+*/
+SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){
+ int isConst = 1;
+ walkExprTree(p, exprNodeIsConstant, &isConst);
+ return isConst;
+}
+
+/*
+** Walk an expression tree. Return 1 if the expression is constant
+** that does no originate from the ON or USING clauses of a join.
+** Return 0 if it involves variables or function calls or terms from
+** an ON or USING clause.
+*/
+SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
+ int isConst = 3;
+ walkExprTree(p, exprNodeIsConstant, &isConst);
+ return isConst!=0;
+}
+
+/*
+** Walk an expression tree. Return 1 if the expression is constant
+** or a function call with constant arguments. Return and 0 if there
+** are any variables.
+**
+** For the purposes of this function, a double-quoted string (ex: "abc")
+** is considered a variable but a single-quoted string (ex: 'abc') is
+** a constant.
+*/
+SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p){
+ int isConst = 2;
+ walkExprTree(p, exprNodeIsConstant, &isConst);
+ return isConst!=0;
+}
+
+/*
+** If the expression p codes a constant integer that is small enough
+** to fit in a 32-bit integer, return 1 and put the value of the integer
+** in *pValue. If the expression is not an integer or if it is too big
+** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
+*/
+SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){
+ switch( p->op ){
+ case TK_INTEGER: {
+ if( sqlite3GetInt32((char*)p->token.z, pValue) ){
+ return 1;
+ }
+ break;
+ }
+ case TK_UPLUS: {
+ return sqlite3ExprIsInteger(p->pLeft, pValue);
+ }
+ case TK_UMINUS: {
+ int v;
+ if( sqlite3ExprIsInteger(p->pLeft, &v) ){
+ *pValue = -v;
+ return 1;
+ }
+ break;
+ }
+ default: break;
+ }
+ return 0;
+}
+
+/*
+** Return TRUE if the given string is a row-id column name.
+*/
+SQLITE_PRIVATE int sqlite3IsRowid(const char *z){
+ if( sqlite3StrICmp(z, "_ROWID_")==0 ) return 1;
+ if( sqlite3StrICmp(z, "ROWID")==0 ) return 1;
+ if( sqlite3StrICmp(z, "OID")==0 ) return 1;
+ return 0;
+}
+
+/*
+** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
+** that name in the set of source tables in pSrcList and make the pExpr
+** expression node refer back to that source column. The following changes
+** are made to pExpr:
+**
+** pExpr->iDb Set the index in db->aDb[] of the database holding
+** the table.
+** pExpr->iTable Set to the cursor number for the table obtained
+** from pSrcList.
+** pExpr->iColumn Set to the column number within the table.
+** pExpr->op Set to TK_COLUMN.
+** pExpr->pLeft Any expression this points to is deleted
+** pExpr->pRight Any expression this points to is deleted.
+**
+** The pDbToken is the name of the database (the "X"). This value may be
+** NULL meaning that name is of the form Y.Z or Z. Any available database
+** can be used. The pTableToken is the name of the table (the "Y"). This
+** value can be NULL if pDbToken is also NULL. If pTableToken is NULL it
+** means that the form of the name is Z and that columns from any table
+** can be used.
+**
+** If the name cannot be resolved unambiguously, leave an error message
+** in pParse and return non-zero. Return zero on success.
+*/
+static int lookupName(
+ Parse *pParse, /* The parsing context */
+ Token *pDbToken, /* Name of the database containing table, or NULL */
+ Token *pTableToken, /* Name of table containing column, or NULL */
+ Token *pColumnToken, /* Name of the column. */
+ NameContext *pNC, /* The name context used to resolve the name */
+ Expr *pExpr /* Make this EXPR node point to the selected column */
+){
+ char *zDb = 0; /* Name of the database. The "X" in X.Y.Z */
+ char *zTab = 0; /* Name of the table. The "Y" in X.Y.Z or Y.Z */
+ char *zCol = 0; /* Name of the column. The "Z" */
+ int i, j; /* Loop counters */
+ int cnt = 0; /* Number of matching column names */
+ int cntTab = 0; /* Number of matching table names */
+ sqlite3 *db = pParse->db; /* The database */
+ struct SrcList_item *pItem; /* Use for looping over pSrcList items */
+ struct SrcList_item *pMatch = 0; /* The matching pSrcList item */
+ NameContext *pTopNC = pNC; /* First namecontext in the list */
+ Schema *pSchema = 0; /* Schema of the expression */
+
+ assert( pColumnToken && pColumnToken->z ); /* The Z in X.Y.Z cannot be NULL */
+ zDb = sqlite3NameFromToken(db, pDbToken);
+ zTab = sqlite3NameFromToken(db, pTableToken);
+ zCol = sqlite3NameFromToken(db, pColumnToken);
+ if( db->mallocFailed ){
+ goto lookupname_end;
+ }
+
+ pExpr->iTable = -1;
+ while( pNC && cnt==0 ){
+ ExprList *pEList;
+ SrcList *pSrcList = pNC->pSrcList;
+
+ if( pSrcList ){
+ for(i=0, pItem=pSrcList->a; i<pSrcList->nSrc; i++, pItem++){
+ Table *pTab;
+ int iDb;
+ Column *pCol;
+
+ pTab = pItem->pTab;
+ assert( pTab!=0 );
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( pTab->nCol>0 );
+ if( zTab ){
+ if( pItem->zAlias ){
+ char *zTabName = pItem->zAlias;
+ if( sqlite3StrICmp(zTabName, zTab)!=0 ) continue;
+ }else{
+ char *zTabName = pTab->zName;
+ if( zTabName==0 || sqlite3StrICmp(zTabName, zTab)!=0 ) continue;
+ if( zDb!=0 && sqlite3StrICmp(db->aDb[iDb].zName, zDb)!=0 ){
+ continue;
+ }
+ }
+ }
+ if( 0==(cntTab++) ){
+ pExpr->iTable = pItem->iCursor;
+ pSchema = pTab->pSchema;
+ pMatch = pItem;
+ }
+ for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){
+ if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
+ const char *zColl = pTab->aCol[j].zColl;
+ IdList *pUsing;
+ cnt++;
+ pExpr->iTable = pItem->iCursor;
+ pMatch = pItem;
+ pSchema = pTab->pSchema;
+ /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
+ pExpr->iColumn = j==pTab->iPKey ? -1 : j;
+ pExpr->affinity = pTab->aCol[j].affinity;
+ if( (pExpr->flags & EP_ExpCollate)==0 ){
+ pExpr->pColl = sqlite3FindCollSeq(db, ENC(db), zColl,-1, 0);
+ }
+ if( i<pSrcList->nSrc-1 ){
+ if( pItem[1].jointype & JT_NATURAL ){
+ /* If this match occurred in the left table of a natural join,
+ ** then skip the right table to avoid a duplicate match */
+ pItem++;
+ i++;
+ }else if( (pUsing = pItem[1].pUsing)!=0 ){
+ /* If this match occurs on a column that is in the USING clause
+ ** of a join, skip the search of the right table of the join
+ ** to avoid a duplicate match there. */
+ int k;
+ for(k=0; k<pUsing->nId; k++){
+ if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ){
+ pItem++;
+ i++;
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* If we have not already resolved the name, then maybe
+ ** it is a new.* or old.* trigger argument reference
+ */
+ if( zDb==0 && zTab!=0 && cnt==0 && pParse->trigStack!=0 ){
+ TriggerStack *pTriggerStack = pParse->trigStack;
+ Table *pTab = 0;
+ u32 *piColMask;
+ if( pTriggerStack->newIdx != -1 && sqlite3StrICmp("new", zTab) == 0 ){
+ pExpr->iTable = pTriggerStack->newIdx;
+ assert( pTriggerStack->pTab );
+ pTab = pTriggerStack->pTab;
+ piColMask = &(pTriggerStack->newColMask);
+ }else if( pTriggerStack->oldIdx != -1 && sqlite3StrICmp("old", zTab)==0 ){
+ pExpr->iTable = pTriggerStack->oldIdx;
+ assert( pTriggerStack->pTab );
+ pTab = pTriggerStack->pTab;
+ piColMask = &(pTriggerStack->oldColMask);
+ }
+
+ if( pTab ){
+ int iCol;
+ Column *pCol = pTab->aCol;
+
+ pSchema = pTab->pSchema;
+ cntTab++;
+ for(iCol=0; iCol < pTab->nCol; iCol++, pCol++) {
+ if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
+ const char *zColl = pTab->aCol[iCol].zColl;
+ cnt++;
+ pExpr->iColumn = iCol==pTab->iPKey ? -1 : iCol;
+ pExpr->affinity = pTab->aCol[iCol].affinity;
+ if( (pExpr->flags & EP_ExpCollate)==0 ){
+ pExpr->pColl = sqlite3FindCollSeq(db, ENC(db), zColl,-1, 0);
+ }
+ pExpr->pTab = pTab;
+ if( iCol>=0 ){
+ testcase( iCol==31 );
+ testcase( iCol==32 );
+ *piColMask |= ((u32)1<<iCol) | (iCol>=32?0xffffffff:0);
+ }
+ break;
+ }
+ }
+ }
+ }
+#endif /* !defined(SQLITE_OMIT_TRIGGER) */
+
+ /*
+ ** Perhaps the name is a reference to the ROWID
+ */
+ if( cnt==0 && cntTab==1 && sqlite3IsRowid(zCol) ){
+ cnt = 1;
+ pExpr->iColumn = -1;
+ pExpr->affinity = SQLITE_AFF_INTEGER;
+ }
+
+ /*
+ ** If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z
+ ** might refer to an result-set alias. This happens, for example, when
+ ** we are resolving names in the WHERE clause of the following command:
+ **
+ ** SELECT a+b AS x FROM table WHERE x<10;
+ **
+ ** In cases like this, replace pExpr with a copy of the expression that
+ ** forms the result set entry ("a+b" in the example) and return immediately.
+ ** Note that the expression in the result set should have already been
+ ** resolved by the time the WHERE clause is resolved.
+ */
+ if( cnt==0 && (pEList = pNC->pEList)!=0 && zTab==0 ){
+ for(j=0; j<pEList->nExpr; j++){
+ char *zAs = pEList->a[j].zName;
+ if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){
+ Expr *pDup, *pOrig;
+ assert( pExpr->pLeft==0 && pExpr->pRight==0 );
+ assert( pExpr->pList==0 );
+ assert( pExpr->pSelect==0 );
+ pOrig = pEList->a[j].pExpr;
+ if( !pNC->allowAgg && ExprHasProperty(pOrig, EP_Agg) ){
+ sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs);
+ sqlite3_free(zCol);
+ return 2;
+ }
+ pDup = sqlite3ExprDup(db, pOrig);
+ if( pExpr->flags & EP_ExpCollate ){
+ pDup->pColl = pExpr->pColl;
+ pDup->flags |= EP_ExpCollate;
+ }
+ if( pExpr->span.dyn ) sqlite3_free((char*)pExpr->span.z);
+ if( pExpr->token.dyn ) sqlite3_free((char*)pExpr->token.z);
+ memcpy(pExpr, pDup, sizeof(*pExpr));
+ sqlite3_free(pDup);
+ cnt = 1;
+ pMatch = 0;
+ assert( zTab==0 && zDb==0 );
+ goto lookupname_end_2;
+ }
+ }
+ }
+
+ /* Advance to the next name context. The loop will exit when either
+ ** we have a match (cnt>0) or when we run out of name contexts.
+ */
+ if( cnt==0 ){
+ pNC = pNC->pNext;
+ }
+ }
+
+ /*
+ ** If X and Y are NULL (in other words if only the column name Z is
+ ** supplied) and the value of Z is enclosed in double-quotes, then
+ ** Z is a string literal if it doesn't match any column names. In that
+ ** case, we need to return right away and not make any changes to
+ ** pExpr.
+ **
+ ** Because no reference was made to outer contexts, the pNC->nRef
+ ** fields are not changed in any context.
+ */
+ if( cnt==0 && zTab==0 && pColumnToken->z[0]=='"' ){
+ sqlite3_free(zCol);
+ return 0;
+ }
+
+ /*
+ ** cnt==0 means there was not match. cnt>1 means there were two or
+ ** more matches. Either way, we have an error.
+ */
+ if( cnt!=1 ){
+ const char *zErr;
+ zErr = cnt==0 ? "no such column" : "ambiguous column name";
+ if( zDb ){
+ sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol);
+ }else if( zTab ){
+ sqlite3ErrorMsg(pParse, "%s: %s.%s", zErr, zTab, zCol);
+ }else{
+ sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol);
+ }
+ pTopNC->nErr++;
+ }
+
+ /* If a column from a table in pSrcList is referenced, then record
+ ** this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes
+ ** bit 0 to be set. Column 1 sets bit 1. And so forth. If the
+ ** column number is greater than the number of bits in the bitmask
+ ** then set the high-order bit of the bitmask.
+ */
+ if( pExpr->iColumn>=0 && pMatch!=0 ){
+ int n = pExpr->iColumn;
+ testcase( n==sizeof(Bitmask)*8-1 );
+ if( n>=sizeof(Bitmask)*8 ){
+ n = sizeof(Bitmask)*8-1;
+ }
+ assert( pMatch->iCursor==pExpr->iTable );
+ pMatch->colUsed |= ((Bitmask)1)<<n;
+ }
+
+lookupname_end:
+ /* Clean up and return
+ */
+ sqlite3_free(zDb);
+ sqlite3_free(zTab);
+ sqlite3ExprDelete(pExpr->pLeft);
+ pExpr->pLeft = 0;
+ sqlite3ExprDelete(pExpr->pRight);
+ pExpr->pRight = 0;
+ pExpr->op = TK_COLUMN;
+lookupname_end_2:
+ sqlite3_free(zCol);
+ if( cnt==1 ){
+ assert( pNC!=0 );
+ sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList);
+ if( pMatch && !pMatch->pSelect ){
+ pExpr->pTab = pMatch->pTab;
+ }
+ /* Increment the nRef value on all name contexts from TopNC up to
+ ** the point where the name matched. */
+ for(;;){
+ assert( pTopNC!=0 );
+ pTopNC->nRef++;
+ if( pTopNC==pNC ) break;
+ pTopNC = pTopNC->pNext;
+ }
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/*
+** This routine is designed as an xFunc for walkExprTree().
+**
+** Resolve symbolic names into TK_COLUMN operators for the current
+** node in the expression tree. Return 0 to continue the search down
+** the tree or 2 to abort the tree walk.
+**
+** This routine also does error checking and name resolution for
+** function names. The operator for aggregate functions is changed
+** to TK_AGG_FUNCTION.
+*/
+static int nameResolverStep(void *pArg, Expr *pExpr){
+ NameContext *pNC = (NameContext*)pArg;
+ Parse *pParse;
+
+ if( pExpr==0 ) return 1;
+ assert( pNC!=0 );
+ pParse = pNC->pParse;
+
+ if( ExprHasAnyProperty(pExpr, EP_Resolved) ) return 1;
+ ExprSetProperty(pExpr, EP_Resolved);
+#ifndef NDEBUG
+ if( pNC->pSrcList && pNC->pSrcList->nAlloc>0 ){
+ SrcList *pSrcList = pNC->pSrcList;
+ int i;
+ for(i=0; i<pNC->pSrcList->nSrc; i++){
+ assert( pSrcList->a[i].iCursor>=0 && pSrcList->a[i].iCursor<pParse->nTab);
+ }
+ }
+#endif
+ switch( pExpr->op ){
+ /* Double-quoted strings (ex: "abc") are used as identifiers if
+ ** possible. Otherwise they remain as strings. Single-quoted
+ ** strings (ex: 'abc') are always string literals.
+ */
+ case TK_STRING: {
+ if( pExpr->token.z[0]=='\'' ) break;
+ /* Fall thru into the TK_ID case if this is a double-quoted string */
+ }
+ /* A lone identifier is the name of a column.
+ */
+ case TK_ID: {
+ lookupName(pParse, 0, 0, &pExpr->token, pNC, pExpr);
+ return 1;
+ }
+
+ /* A table name and column name: ID.ID
+ ** Or a database, table and column: ID.ID.ID
+ */
+ case TK_DOT: {
+ Token *pColumn;
+ Token *pTable;
+ Token *pDb;
+ Expr *pRight;
+
+ /* if( pSrcList==0 ) break; */
+ pRight = pExpr->pRight;
+ if( pRight->op==TK_ID ){
+ pDb = 0;
+ pTable = &pExpr->pLeft->token;
+ pColumn = &pRight->token;
+ }else{
+ assert( pRight->op==TK_DOT );
+ pDb = &pExpr->pLeft->token;
+ pTable = &pRight->pLeft->token;
+ pColumn = &pRight->pRight->token;
+ }
+ lookupName(pParse, pDb, pTable, pColumn, pNC, pExpr);
+ return 1;
+ }
+
+ /* Resolve function names
+ */
+ case TK_CONST_FUNC:
+ case TK_FUNCTION: {
+ ExprList *pList = pExpr->pList; /* The argument list */
+ int n = pList ? pList->nExpr : 0; /* Number of arguments */
+ int no_such_func = 0; /* True if no such function exists */
+ int wrong_num_args = 0; /* True if wrong number of arguments */
+ int is_agg = 0; /* True if is an aggregate function */
+ int i;
+ int auth; /* Authorization to use the function */
+ int nId; /* Number of characters in function name */
+ const char *zId; /* The function name. */
+ FuncDef *pDef; /* Information about the function */
+ int enc = ENC(pParse->db); /* The database encoding */
+
+ zId = (char*)pExpr->token.z;
+ nId = pExpr->token.n;
+ pDef = sqlite3FindFunction(pParse->db, zId, nId, n, enc, 0);
+ if( pDef==0 ){
+ pDef = sqlite3FindFunction(pParse->db, zId, nId, -1, enc, 0);
+ if( pDef==0 ){
+ no_such_func = 1;
+ }else{
+ wrong_num_args = 1;
+ }
+ }else{
+ is_agg = pDef->xFunc==0;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( pDef ){
+ auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0, pDef->zName, 0);
+ if( auth!=SQLITE_OK ){
+ if( auth==SQLITE_DENY ){
+ sqlite3ErrorMsg(pParse, "not authorized to use function: %s",
+ pDef->zName);
+ pNC->nErr++;
+ }
+ pExpr->op = TK_NULL;
+ return 1;
+ }
+ }
+#endif
+ if( is_agg && !pNC->allowAgg ){
+ sqlite3ErrorMsg(pParse, "misuse of aggregate function %.*s()", nId,zId);
+ pNC->nErr++;
+ is_agg = 0;
+ }else if( no_such_func ){
+ sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId);
+ pNC->nErr++;
+ }else if( wrong_num_args ){
+ sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()",
+ nId, zId);
+ pNC->nErr++;
+ }
+ if( is_agg ){
+ pExpr->op = TK_AGG_FUNCTION;
+ pNC->hasAgg = 1;
+ }
+ if( is_agg ) pNC->allowAgg = 0;
+ for(i=0; pNC->nErr==0 && i<n; i++){
+ walkExprTree(pList->a[i].pExpr, nameResolverStep, pNC);
+ }
+ if( is_agg ) pNC->allowAgg = 1;
+ /* FIX ME: Compute pExpr->affinity based on the expected return
+ ** type of the function
+ */
+ return is_agg;
+ }
+#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_SELECT:
+ case TK_EXISTS:
+#endif
+ case TK_IN: {
+ if( pExpr->pSelect ){
+ int nRef = pNC->nRef;
+#ifndef SQLITE_OMIT_CHECK
+ if( pNC->isCheck ){
+ sqlite3ErrorMsg(pParse,"subqueries prohibited in CHECK constraints");
+ }
+#endif
+ sqlite3SelectResolve(pParse, pExpr->pSelect, pNC);
+ assert( pNC->nRef>=nRef );
+ if( nRef!=pNC->nRef ){
+ ExprSetProperty(pExpr, EP_VarSelect);
+ }
+ }
+ break;
+ }
+#ifndef SQLITE_OMIT_CHECK
+ case TK_VARIABLE: {
+ if( pNC->isCheck ){
+ sqlite3ErrorMsg(pParse,"parameters prohibited in CHECK constraints");
+ }
+ break;
+ }
+#endif
+ }
+ return 0;
+}
+
+/*
+** This routine walks an expression tree and resolves references to
+** table columns. Nodes of the form ID.ID or ID resolve into an
+** index to the table in the table list and a column offset. The
+** Expr.opcode for such nodes is changed to TK_COLUMN. The Expr.iTable
+** value is changed to the index of the referenced table in pTabList
+** plus the "base" value. The base value will ultimately become the
+** VDBE cursor number for a cursor that is pointing into the referenced
+** table. The Expr.iColumn value is changed to the index of the column
+** of the referenced table. The Expr.iColumn value for the special
+** ROWID column is -1. Any INTEGER PRIMARY KEY column is tried as an
+** alias for ROWID.
+**
+** Also resolve function names and check the functions for proper
+** usage. Make sure all function names are recognized and all functions
+** have the correct number of arguments. Leave an error message
+** in pParse->zErrMsg if anything is amiss. Return the number of errors.
+**
+** If the expression contains aggregate functions then set the EP_Agg
+** property on the expression.
+*/
+SQLITE_PRIVATE int sqlite3ExprResolveNames(
+ NameContext *pNC, /* Namespace to resolve expressions in. */
+ Expr *pExpr /* The expression to be analyzed. */
+){
+ int savedHasAgg;
+
+ if( pExpr==0 ) return 0;
+#if SQLITE_MAX_EXPR_DEPTH>0
+ {
+ int mxDepth = pNC->pParse->db->aLimit[SQLITE_LIMIT_EXPR_DEPTH];
+ if( (pExpr->nHeight+pNC->pParse->nHeight)>mxDepth ){
+ sqlite3ErrorMsg(pNC->pParse,
+ "Expression tree is too large (maximum depth %d)", mxDepth
+ );
+ return 1;
+ }
+ pNC->pParse->nHeight += pExpr->nHeight;
+ }
+#endif
+ savedHasAgg = pNC->hasAgg;
+ pNC->hasAgg = 0;
+ walkExprTree(pExpr, nameResolverStep, pNC);
+#if SQLITE_MAX_EXPR_DEPTH>0
+ pNC->pParse->nHeight -= pExpr->nHeight;
+#endif
+ if( pNC->nErr>0 ){
+ ExprSetProperty(pExpr, EP_Error);
+ }
+ if( pNC->hasAgg ){
+ ExprSetProperty(pExpr, EP_Agg);
+ }else if( savedHasAgg ){
+ pNC->hasAgg = 1;
+ }
+ return ExprHasProperty(pExpr, EP_Error);
+}
+
+/*
+** A pointer instance of this structure is used to pass information
+** through walkExprTree into codeSubqueryStep().
+*/
+typedef struct QueryCoder QueryCoder;
+struct QueryCoder {
+ Parse *pParse; /* The parsing context */
+ NameContext *pNC; /* Namespace of first enclosing query */
+};
+
+#ifdef SQLITE_TEST
+ int sqlite3_enable_in_opt = 1;
+#else
+ #define sqlite3_enable_in_opt 1
+#endif
+
+/*
+** Return true if the IN operator optimization is enabled and
+** the SELECT statement p exists and is of the
+** simple form:
+**
+** SELECT <column> FROM <table>
+**
+** If this is the case, it may be possible to use an existing table
+** or index instead of generating an epheremal table.
+*/
+#ifndef SQLITE_OMIT_SUBQUERY
+static int isCandidateForInOpt(Select *p){
+ SrcList *pSrc;
+ ExprList *pEList;
+ Table *pTab;
+ if( !sqlite3_enable_in_opt ) return 0; /* IN optimization must be enabled */
+ if( p==0 ) return 0; /* right-hand side of IN is SELECT */
+ if( p->pPrior ) return 0; /* Not a compound SELECT */
+ if( p->isDistinct ) return 0; /* No DISTINCT keyword */
+ if( p->isAgg ) return 0; /* Contains no aggregate functions */
+ if( p->pGroupBy ) return 0; /* Has no GROUP BY clause */
+ if( p->pLimit ) return 0; /* Has no LIMIT clause */
+ if( p->pOffset ) return 0;
+ if( p->pWhere ) return 0; /* Has no WHERE clause */
+ pSrc = p->pSrc;
+ if( pSrc==0 ) return 0; /* A single table in the FROM clause */
+ if( pSrc->nSrc!=1 ) return 0;
+ if( pSrc->a[0].pSelect ) return 0; /* FROM clause is not a subquery */
+ pTab = pSrc->a[0].pTab;
+ if( pTab==0 ) return 0;
+ if( pTab->pSelect ) return 0; /* FROM clause is not a view */
+ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */
+ pEList = p->pEList;
+ if( pEList->nExpr!=1 ) return 0; /* One column in the result set */
+ if( pEList->a[0].pExpr->op!=TK_COLUMN ) return 0; /* Result is a column */
+ return 1;
+}
+#endif /* SQLITE_OMIT_SUBQUERY */
+
+/*
+** This function is used by the implementation of the IN (...) operator.
+** It's job is to find or create a b-tree structure that may be used
+** either to test for membership of the (...) set or to iterate through
+** its members, skipping duplicates.
+**
+** The cursor opened on the structure (database table, database index
+** or ephermal table) is stored in pX->iTable before this function returns.
+** The returned value indicates the structure type, as follows:
+**
+** IN_INDEX_ROWID - The cursor was opened on a database table.
+** IN_INDEX_INDEX - The cursor was opened on a database index.
+** IN_INDEX_EPH - The cursor was opened on a specially created and
+** populated epheremal table.
+**
+** An existing structure may only be used if the SELECT is of the simple
+** form:
+**
+** SELECT <column> FROM <table>
+**
+** If the mustBeUnique parameter is false, the structure will be used
+** for fast set membership tests. In this case an epheremal table must
+** be used unless <column> is an INTEGER PRIMARY KEY or an index can
+** be found with <column> as its left-most column.
+**
+** If mustBeUnique is true, then the structure will be used to iterate
+** through the set members, skipping any duplicates. In this case an
+** epheremal table must be used unless the selected <column> is guaranteed
+** to be unique - either because it is an INTEGER PRIMARY KEY or it
+** is unique by virtue of a constraint or implicit index.
+*/
+#ifndef SQLITE_OMIT_SUBQUERY
+SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, int mustBeUnique){
+ Select *p;
+ int eType = 0;
+ int iTab = pParse->nTab++;
+
+ /* The follwing if(...) expression is true if the SELECT is of the
+ ** simple form:
+ **
+ ** SELECT <column> FROM <table>
+ **
+ ** If this is the case, it may be possible to use an existing table
+ ** or index instead of generating an epheremal table.
+ */
+ p = pX->pSelect;
+ if( isCandidateForInOpt(p) ){
+ sqlite3 *db = pParse->db;
+ Index *pIdx;
+ Expr *pExpr = p->pEList->a[0].pExpr;
+ int iCol = pExpr->iColumn;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+
+ /* This function is only called from two places. In both cases the vdbe
+ ** has already been allocated. So assume sqlite3GetVdbe() is always
+ ** successful here.
+ */
+ assert(v);
+ if( iCol<0 ){
+ int iMem = ++pParse->nMem;
+ int iAddr;
+ Table *pTab = p->pSrc->a[0].pTab;
+ int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ sqlite3VdbeUsesBtree(v, iDb);
+
+ iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem);
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem);
+
+ sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
+ eType = IN_INDEX_ROWID;
+
+ sqlite3VdbeJumpHere(v, iAddr);
+ }else{
+ /* The collation sequence used by the comparison. If an index is to
+ ** be used in place of a temp-table, it must be ordered according
+ ** to this collation sequence.
+ */
+ CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pExpr);
+
+ /* Check that the affinity that will be used to perform the
+ ** comparison is the same as the affinity of the column. If
+ ** it is not, it is not possible to use any index.
+ */
+ Table *pTab = p->pSrc->a[0].pTab;
+ char aff = comparisonAffinity(pX);
+ int affinity_ok = (pTab->aCol[iCol].affinity==aff||aff==SQLITE_AFF_NONE);
+
+ for(pIdx=pTab->pIndex; pIdx && eType==0 && affinity_ok; pIdx=pIdx->pNext){
+ if( (pIdx->aiColumn[0]==iCol)
+ && (pReq==sqlite3FindCollSeq(db, ENC(db), pIdx->azColl[0], -1, 0))
+ && (!mustBeUnique || (pIdx->nColumn==1 && pIdx->onError!=OE_None))
+ ){
+ int iDb;
+ int iMem = ++pParse->nMem;
+ int iAddr;
+ char *pKey;
+
+ pKey = (char *)sqlite3IndexKeyinfo(pParse, pIdx);
+ iDb = sqlite3SchemaToIndex(db, pIdx->pSchema);
+ sqlite3VdbeUsesBtree(v, iDb);
+
+ iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem);
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem);
+
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, pIdx->nColumn);
+ sqlite3VdbeAddOp4(v, OP_OpenRead, iTab, pIdx->tnum, iDb,
+ pKey,P4_KEYINFO_HANDOFF);
+ VdbeComment((v, "%s", pIdx->zName));
+ eType = IN_INDEX_INDEX;
+
+ sqlite3VdbeJumpHere(v, iAddr);
+ }
+ }
+ }
+ }
+
+ if( eType==0 ){
+ sqlite3CodeSubselect(pParse, pX);
+ eType = IN_INDEX_EPH;
+ }else{
+ pX->iTable = iTab;
+ }
+ return eType;
+}
+#endif
+
+/*
+** Generate code for scalar subqueries used as an expression
+** and IN operators. Examples:
+**
+** (SELECT a FROM b) -- subquery
+** EXISTS (SELECT a FROM b) -- EXISTS subquery
+** x IN (4,5,11) -- IN operator with list on right-hand side
+** x IN (SELECT a FROM b) -- IN operator with subquery on the right
+**
+** The pExpr parameter describes the expression that contains the IN
+** operator or subquery.
+*/
+#ifndef SQLITE_OMIT_SUBQUERY
+SQLITE_PRIVATE void sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
+ int testAddr = 0; /* One-time test address */
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+
+
+ /* This code must be run in its entirety every time it is encountered
+ ** if any of the following is true:
+ **
+ ** * The right-hand side is a correlated subquery
+ ** * The right-hand side is an expression list containing variables
+ ** * We are inside a trigger
+ **
+ ** If all of the above are false, then we can run this code just once
+ ** save the results, and reuse the same result on subsequent invocations.
+ */
+ if( !ExprHasAnyProperty(pExpr, EP_VarSelect) && !pParse->trigStack ){
+ int mem = ++pParse->nMem;
+ sqlite3VdbeAddOp1(v, OP_If, mem);
+ testAddr = sqlite3VdbeAddOp2(v, OP_Integer, 1, mem);
+ assert( testAddr>0 || pParse->db->mallocFailed );
+ }
+
+ switch( pExpr->op ){
+ case TK_IN: {
+ char affinity;
+ KeyInfo keyInfo;
+ int addr; /* Address of OP_OpenEphemeral instruction */
+
+ affinity = sqlite3ExprAffinity(pExpr->pLeft);
+
+ /* Whether this is an 'x IN(SELECT...)' or an 'x IN(<exprlist>)'
+ ** expression it is handled the same way. A virtual table is
+ ** filled with single-field index keys representing the results
+ ** from the SELECT or the <exprlist>.
+ **
+ ** If the 'x' expression is a column value, or the SELECT...
+ ** statement returns a column value, then the affinity of that
+ ** column is used to build the index keys. If both 'x' and the
+ ** SELECT... statement are columns, then numeric affinity is used
+ ** if either column has NUMERIC or INTEGER affinity. If neither
+ ** 'x' nor the SELECT... statement are columns, then numeric affinity
+ ** is used.
+ */
+ pExpr->iTable = pParse->nTab++;
+ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, 1);
+ memset(&keyInfo, 0, sizeof(keyInfo));
+ keyInfo.nField = 1;
+
+ if( pExpr->pSelect ){
+ /* Case 1: expr IN (SELECT ...)
+ **
+ ** Generate code to write the results of the select into the temporary
+ ** table allocated and opened above.
+ */
+ SelectDest dest;
+ ExprList *pEList;
+
+ sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable);
+ dest.affinity = (int)affinity;
+ assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable );
+ if( sqlite3Select(pParse, pExpr->pSelect, &dest, 0, 0, 0, 0) ){
+ return;
+ }
+ pEList = pExpr->pSelect->pEList;
+ if( pEList && pEList->nExpr>0 ){
+ keyInfo.aColl[0] = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft,
+ pEList->a[0].pExpr);
+ }
+ }else if( pExpr->pList ){
+ /* Case 2: expr IN (exprlist)
+ **
+ ** For each expression, build an index key from the evaluation and
+ ** store it in the temporary table. If <expr> is a column, then use
+ ** that columns affinity when building index keys. If <expr> is not
+ ** a column, use numeric affinity.
+ */
+ int i;
+ ExprList *pList = pExpr->pList;
+ struct ExprList_item *pItem;
+ int r1, r2;
+
+ if( !affinity ){
+ affinity = SQLITE_AFF_NONE;
+ }
+ keyInfo.aColl[0] = pExpr->pLeft->pColl;
+
+ /* Loop through each expression in <exprlist>. */
+ r1 = sqlite3GetTempReg(pParse);
+ r2 = sqlite3GetTempReg(pParse);
+ for(i=pList->nExpr, pItem=pList->a; i>0; i--, pItem++){
+ Expr *pE2 = pItem->pExpr;
+
+ /* If the expression is not constant then we will need to
+ ** disable the test that was generated above that makes sure
+ ** this code only executes once. Because for a non-constant
+ ** expression we need to rerun this code each time.
+ */
+ if( testAddr && !sqlite3ExprIsConstant(pE2) ){
+ sqlite3VdbeChangeToNoop(v, testAddr-1, 2);
+ testAddr = 0;
+ }
+
+ /* Evaluate the expression and insert it into the temp table */
+ pParse->disableColCache++;
+ sqlite3ExprCode(pParse, pE2, r1);
+ assert( pParse->disableColCache>0 );
+ pParse->disableColCache--;
+ sqlite3VdbeAddOp4(v, OP_MakeRecord, r1, 1, r2, &affinity, 1);
+ sqlite3ExprCacheAffinityChange(pParse, r1, 1);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, pExpr->iTable, r2);
+ }
+ sqlite3ReleaseTempReg(pParse, r1);
+ sqlite3ReleaseTempReg(pParse, r2);
+ }
+ sqlite3VdbeChangeP4(v, addr, (void *)&keyInfo, P4_KEYINFO);
+ break;
+ }
+
+ case TK_EXISTS:
+ case TK_SELECT: {
+ /* This has to be a scalar SELECT. Generate code to put the
+ ** value of this select in a memory cell and record the number
+ ** of the memory cell in iColumn.
+ */
+ static const Token one = { (u8*)"1", 0, 1 };
+ Select *pSel;
+ SelectDest dest;
+
+ pSel = pExpr->pSelect;
+ sqlite3SelectDestInit(&dest, 0, ++pParse->nMem);
+ if( pExpr->op==TK_SELECT ){
+ dest.eDest = SRT_Mem;
+ sqlite3VdbeAddOp2(v, OP_Null, 0, dest.iParm);
+ VdbeComment((v, "Init subquery result"));
+ }else{
+ dest.eDest = SRT_Exists;
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, dest.iParm);
+ VdbeComment((v, "Init EXISTS result"));
+ }
+ sqlite3ExprDelete(pSel->pLimit);
+ pSel->pLimit = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &one);
+ if( sqlite3Select(pParse, pSel, &dest, 0, 0, 0, 0) ){
+ return;
+ }
+ pExpr->iColumn = dest.iParm;
+ break;
+ }
+ }
+
+ if( testAddr ){
+ sqlite3VdbeJumpHere(v, testAddr-1);
+ }
+
+ return;
+}
+#endif /* SQLITE_OMIT_SUBQUERY */
+
+/*
+** Duplicate an 8-byte value
+*/
+static char *dup8bytes(Vdbe *v, const char *in){
+ char *out = sqlite3DbMallocRaw(sqlite3VdbeDb(v), 8);
+ if( out ){
+ memcpy(out, in, 8);
+ }
+ return out;
+}
+
+/*
+** Generate an instruction that will put the floating point
+** value described by z[0..n-1] into register iMem.
+**
+** The z[] string will probably not be zero-terminated. But the
+** z[n] character is guaranteed to be something that does not look
+** like the continuation of the number.
+*/
+static void codeReal(Vdbe *v, const char *z, int n, int negateFlag, int iMem){
+ assert( z || v==0 || sqlite3VdbeDb(v)->mallocFailed );
+ if( z ){
+ double value;
+ char *zV;
+ assert( !isdigit(z[n]) );
+ sqlite3AtoF(z, &value);
+ if( sqlite3IsNaN(value) ){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, iMem);
+ }else{
+ if( negateFlag ) value = -value;
+ zV = dup8bytes(v, (char*)&value);
+ sqlite3VdbeAddOp4(v, OP_Real, 0, iMem, 0, zV, P4_REAL);
+ }
+ }
+}
+
+
+/*
+** Generate an instruction that will put the integer describe by
+** text z[0..n-1] into register iMem.
+**
+** The z[] string will probably not be zero-terminated. But the
+** z[n] character is guaranteed to be something that does not look
+** like the continuation of the number.
+*/
+static void codeInteger(Vdbe *v, const char *z, int n, int negFlag, int iMem){
+ assert( z || v==0 || sqlite3VdbeDb(v)->mallocFailed );
+ if( z ){
+ int i;
+ assert( !isdigit(z[n]) );
+ if( sqlite3GetInt32(z, &i) ){
+ if( negFlag ) i = -i;
+ sqlite3VdbeAddOp2(v, OP_Integer, i, iMem);
+ }else if( sqlite3FitsIn64Bits(z, negFlag) ){
+ i64 value;
+ char *zV;
+ sqlite3Atoi64(z, &value);
+ if( negFlag ) value = -value;
+ zV = dup8bytes(v, (char*)&value);
+ sqlite3VdbeAddOp4(v, OP_Int64, 0, iMem, 0, zV, P4_INT64);
+ }else{
+ codeReal(v, z, n, negFlag, iMem);
+ }
+ }
+}
+
+
+/*
+** Generate code that will extract the iColumn-th column from
+** table pTab and store the column value in a register. An effort
+** is made to store the column value in register iReg, but this is
+** not guaranteed. The location of the column value is returned.
+**
+** There must be an open cursor to pTab in iTable when this routine
+** is called. If iColumn<0 then code is generated that extracts the rowid.
+**
+** This routine might attempt to reuse the value of the column that
+** has already been loaded into a register. The value will always
+** be used if it has not undergone any affinity changes. But if
+** an affinity change has occurred, then the cached value will only be
+** used if allowAffChng is true.
+*/
+SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(
+ Parse *pParse, /* Parsing and code generating context */
+ Table *pTab, /* Description of the table we are reading from */
+ int iColumn, /* Index of the table column */
+ int iTable, /* The cursor pointing to the table */
+ int iReg, /* Store results here */
+ int allowAffChng /* True if prior affinity changes are OK */
+){
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ struct yColCache *p;
+
+ for(i=0, p=pParse->aColCache; i<pParse->nColCache; i++, p++){
+ if( p->iTable==iTable && p->iColumn==iColumn
+ && (!p->affChange || allowAffChng) ){
+#if 0
+ sqlite3VdbeAddOp0(v, OP_Noop);
+ VdbeComment((v, "OPT: tab%d.col%d -> r%d", iTable, iColumn, p->iReg));
+#endif
+ return p->iReg;
+ }
+ }
+ assert( v!=0 );
+ if( iColumn<0 ){
+ int op = (pTab && IsVirtual(pTab)) ? OP_VRowid : OP_Rowid;
+ sqlite3VdbeAddOp2(v, op, iTable, iReg);
+ }else if( pTab==0 ){
+ sqlite3VdbeAddOp3(v, OP_Column, iTable, iColumn, iReg);
+ }else{
+ int op = IsVirtual(pTab) ? OP_VColumn : OP_Column;
+ sqlite3VdbeAddOp3(v, op, iTable, iColumn, iReg);
+ sqlite3ColumnDefault(v, pTab, iColumn);
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ if( pTab->aCol[iColumn].affinity==SQLITE_AFF_REAL ){
+ sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
+ }
+#endif
+ }
+ if( pParse->disableColCache==0 ){
+ i = pParse->iColCache;
+ p = &pParse->aColCache[i];
+ p->iTable = iTable;
+ p->iColumn = iColumn;
+ p->iReg = iReg;
+ p->affChange = 0;
+ i++;
+ if( i>=ArraySize(pParse->aColCache) ) i = 0;
+ if( i>pParse->nColCache ) pParse->nColCache = i;
+ pParse->iColCache = i;
+ }
+ return iReg;
+}
+
+/*
+** Clear all column cache entries associated with the vdbe
+** cursor with cursor number iTable.
+*/
+SQLITE_PRIVATE void sqlite3ExprClearColumnCache(Parse *pParse, int iTable){
+ if( iTable<0 ){
+ pParse->nColCache = 0;
+ pParse->iColCache = 0;
+ }else{
+ int i;
+ for(i=0; i<pParse->nColCache; i++){
+ if( pParse->aColCache[i].iTable==iTable ){
+ testcase( i==pParse->nColCache-1 );
+ pParse->aColCache[i] = pParse->aColCache[--pParse->nColCache];
+ pParse->iColCache = pParse->nColCache;
+ }
+ }
+ }
+}
+
+/*
+** Record the fact that an affinity change has occurred on iCount
+** registers starting with iStart.
+*/
+SQLITE_PRIVATE void sqlite3ExprCacheAffinityChange(Parse *pParse, int iStart, int iCount){
+ int iEnd = iStart + iCount - 1;
+ int i;
+ for(i=0; i<pParse->nColCache; i++){
+ int r = pParse->aColCache[i].iReg;
+ if( r>=iStart && r<=iEnd ){
+ pParse->aColCache[i].affChange = 1;
+ }
+ }
+}
+
+/*
+** Generate code to moves content from one register to another.
+** Keep the column cache up-to-date.
+*/
+SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo){
+ int i;
+ if( iFrom==iTo ) return;
+ sqlite3VdbeAddOp2(pParse->pVdbe, OP_Move, iFrom, iTo);
+ for(i=0; i<pParse->nColCache; i++){
+ if( pParse->aColCache[i].iReg==iFrom ){
+ pParse->aColCache[i].iReg = iTo;
+ }
+ }
+}
+
+/*
+** Return true if any register in the range iFrom..iTo (inclusive)
+** is used as part of the column cache.
+*/
+static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){
+ int i;
+ for(i=0; i<pParse->nColCache; i++){
+ int r = pParse->aColCache[i].iReg;
+ if( r>=iFrom && r<=iTo ) return 1;
+ }
+ return 0;
+}
+
+/*
+** Theres is a value in register iCurrent. We ultimately want
+** the value to be in register iTarget. It might be that
+** iCurrent and iTarget are the same register.
+**
+** We are going to modify the value, so we need to make sure it
+** is not a cached register. If iCurrent is a cached register,
+** then try to move the value over to iTarget. If iTarget is a
+** cached register, then clear the corresponding cache line.
+**
+** Return the register that the value ends up in.
+*/
+SQLITE_PRIVATE int sqlite3ExprWritableRegister(Parse *pParse, int iCurrent, int iTarget){
+ int i;
+ assert( pParse->pVdbe!=0 );
+ if( !usedAsColumnCache(pParse, iCurrent, iCurrent) ){
+ return iCurrent;
+ }
+ if( iCurrent!=iTarget ){
+ sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, iCurrent, iTarget);
+ }
+ for(i=0; i<pParse->nColCache; i++){
+ if( pParse->aColCache[i].iReg==iTarget ){
+ pParse->aColCache[i] = pParse->aColCache[--pParse->nColCache];
+ pParse->iColCache = pParse->nColCache;
+ }
+ }
+ return iTarget;
+}
+
+/*
+** If the last instruction coded is an ephemeral copy of any of
+** the registers in the nReg registers beginning with iReg, then
+** convert the last instruction from OP_SCopy to OP_Copy.
+*/
+SQLITE_PRIVATE void sqlite3ExprHardCopy(Parse *pParse, int iReg, int nReg){
+ int addr;
+ VdbeOp *pOp;
+ Vdbe *v;
+
+ v = pParse->pVdbe;
+ addr = sqlite3VdbeCurrentAddr(v);
+ pOp = sqlite3VdbeGetOp(v, addr-1);
+ assert( pOp || pParse->db->mallocFailed );
+ if( pOp && pOp->opcode==OP_SCopy && pOp->p1>=iReg && pOp->p1<iReg+nReg ){
+ pOp->opcode = OP_Copy;
+ }
+}
+
+/*
+** Generate code into the current Vdbe to evaluate the given
+** expression. Attempt to store the results in register "target".
+** Return the register where results are stored.
+**
+** With this routine, there is no guaranteed that results will
+** be stored in target. The result might be stored in some other
+** register if it is convenient to do so. The calling function
+** must check the return code and move the results to the desired
+** register.
+*/
+SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
+ Vdbe *v = pParse->pVdbe; /* The VM under construction */
+ int op; /* The opcode being coded */
+ int inReg = target; /* Results stored in register inReg */
+ int regFree1 = 0; /* If non-zero free this temporary register */
+ int regFree2 = 0; /* If non-zero free this temporary register */
+ int r1, r2, r3, r4; /* Various register numbers */
+
+ assert( v!=0 || pParse->db->mallocFailed );
+ assert( target>0 && target<=pParse->nMem );
+ if( v==0 ) return 0;
+
+ if( pExpr==0 ){
+ op = TK_NULL;
+ }else{
+ op = pExpr->op;
+ }
+ switch( op ){
+ case TK_AGG_COLUMN: {
+ AggInfo *pAggInfo = pExpr->pAggInfo;
+ struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg];
+ if( !pAggInfo->directMode ){
+ assert( pCol->iMem>0 );
+ inReg = pCol->iMem;
+ break;
+ }else if( pAggInfo->useSortingIdx ){
+ sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdx,
+ pCol->iSorterColumn, target);
+ break;
+ }
+ /* Otherwise, fall thru into the TK_COLUMN case */
+ }
+ case TK_COLUMN: {
+ if( pExpr->iTable<0 ){
+ /* This only happens when coding check constraints */
+ assert( pParse->ckBase>0 );
+ inReg = pExpr->iColumn + pParse->ckBase;
+ }else{
+ testcase( (pExpr->flags & EP_AnyAff)!=0 );
+ inReg = sqlite3ExprCodeGetColumn(pParse, pExpr->pTab,
+ pExpr->iColumn, pExpr->iTable, target,
+ pExpr->flags & EP_AnyAff);
+ }
+ break;
+ }
+ case TK_INTEGER: {
+ codeInteger(v, (char*)pExpr->token.z, pExpr->token.n, 0, target);
+ break;
+ }
+ case TK_FLOAT: {
+ codeReal(v, (char*)pExpr->token.z, pExpr->token.n, 0, target);
+ break;
+ }
+ case TK_STRING: {
+ sqlite3DequoteExpr(pParse->db, pExpr);
+ sqlite3VdbeAddOp4(v,OP_String8, 0, target, 0,
+ (char*)pExpr->token.z, pExpr->token.n);
+ break;
+ }
+ case TK_NULL: {
+ sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ break;
+ }
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+ case TK_BLOB: {
+ int n;
+ const char *z;
+ char *zBlob;
+ assert( pExpr->token.n>=3 );
+ assert( pExpr->token.z[0]=='x' || pExpr->token.z[0]=='X' );
+ assert( pExpr->token.z[1]=='\'' );
+ assert( pExpr->token.z[pExpr->token.n-1]=='\'' );
+ n = pExpr->token.n - 3;
+ z = (char*)pExpr->token.z + 2;
+ zBlob = sqlite3HexToBlob(sqlite3VdbeDb(v), z, n);
+ sqlite3VdbeAddOp4(v, OP_Blob, n/2, target, 0, zBlob, P4_DYNAMIC);
+ break;
+ }
+#endif
+ case TK_VARIABLE: {
+ sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iTable, target);
+ if( pExpr->token.n>1 ){
+ sqlite3VdbeChangeP4(v, -1, (char*)pExpr->token.z, pExpr->token.n);
+ }
+ break;
+ }
+ case TK_REGISTER: {
+ inReg = pExpr->iTable;
+ break;
+ }
+#ifndef SQLITE_OMIT_CAST
+ case TK_CAST: {
+ /* Expressions of the form: CAST(pLeft AS token) */
+ int aff, to_op;
+ inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ aff = sqlite3AffinityType(&pExpr->token);
+ to_op = aff - SQLITE_AFF_TEXT + OP_ToText;
+ assert( to_op==OP_ToText || aff!=SQLITE_AFF_TEXT );
+ assert( to_op==OP_ToBlob || aff!=SQLITE_AFF_NONE );
+ assert( to_op==OP_ToNumeric || aff!=SQLITE_AFF_NUMERIC );
+ assert( to_op==OP_ToInt || aff!=SQLITE_AFF_INTEGER );
+ assert( to_op==OP_ToReal || aff!=SQLITE_AFF_REAL );
+ testcase( to_op==OP_ToText );
+ testcase( to_op==OP_ToBlob );
+ testcase( to_op==OP_ToNumeric );
+ testcase( to_op==OP_ToInt );
+ testcase( to_op==OP_ToReal );
+ sqlite3VdbeAddOp1(v, to_op, inReg);
+ testcase( usedAsColumnCache(pParse, inReg, inReg) );
+ sqlite3ExprCacheAffinityChange(pParse, inReg, 1);
+ break;
+ }
+#endif /* SQLITE_OMIT_CAST */
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ: {
+ assert( TK_LT==OP_Lt );
+ assert( TK_LE==OP_Le );
+ assert( TK_GT==OP_Gt );
+ assert( TK_GE==OP_Ge );
+ assert( TK_EQ==OP_Eq );
+ assert( TK_NE==OP_Ne );
+ testcase( op==TK_LT );
+ testcase( op==TK_LE );
+ testcase( op==TK_GT );
+ testcase( op==TK_GE );
+ testcase( op==TK_EQ );
+ testcase( op==TK_NE );
+ codeCompareOperands(pParse, pExpr->pLeft, &r1, &regFree1,
+ pExpr->pRight, &r2, &regFree2);
+ codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
+ r1, r2, inReg, SQLITE_STOREP2);
+ testcase( regFree1==0 );
+ testcase( regFree2==0 );
+ break;
+ }
+ case TK_AND:
+ case TK_OR:
+ case TK_PLUS:
+ case TK_STAR:
+ case TK_MINUS:
+ case TK_REM:
+ case TK_BITAND:
+ case TK_BITOR:
+ case TK_SLASH:
+ case TK_LSHIFT:
+ case TK_RSHIFT:
+ case TK_CONCAT: {
+ assert( TK_AND==OP_And );
+ assert( TK_OR==OP_Or );
+ assert( TK_PLUS==OP_Add );
+ assert( TK_MINUS==OP_Subtract );
+ assert( TK_REM==OP_Remainder );
+ assert( TK_BITAND==OP_BitAnd );
+ assert( TK_BITOR==OP_BitOr );
+ assert( TK_SLASH==OP_Divide );
+ assert( TK_LSHIFT==OP_ShiftLeft );
+ assert( TK_RSHIFT==OP_ShiftRight );
+ assert( TK_CONCAT==OP_Concat );
+ testcase( op==TK_AND );
+ testcase( op==TK_OR );
+ testcase( op==TK_PLUS );
+ testcase( op==TK_MINUS );
+ testcase( op==TK_REM );
+ testcase( op==TK_BITAND );
+ testcase( op==TK_BITOR );
+ testcase( op==TK_SLASH );
+ testcase( op==TK_LSHIFT );
+ testcase( op==TK_RSHIFT );
+ testcase( op==TK_CONCAT );
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ sqlite3VdbeAddOp3(v, op, r2, r1, target);
+ testcase( regFree1==0 );
+ testcase( regFree2==0 );
+ break;
+ }
+ case TK_UMINUS: {
+ Expr *pLeft = pExpr->pLeft;
+ assert( pLeft );
+ if( pLeft->op==TK_FLOAT || pLeft->op==TK_INTEGER ){
+ Token *p = &pLeft->token;
+ if( pLeft->op==TK_FLOAT ){
+ codeReal(v, (char*)p->z, p->n, 1, target);
+ }else{
+ codeInteger(v, (char*)p->z, p->n, 1, target);
+ }
+ }else{
+ regFree1 = r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, r1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree2);
+ sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target);
+ testcase( regFree2==0 );
+ }
+ inReg = target;
+ break;
+ }
+ case TK_BITNOT:
+ case TK_NOT: {
+ assert( TK_BITNOT==OP_BitNot );
+ assert( TK_NOT==OP_Not );
+ testcase( op==TK_BITNOT );
+ testcase( op==TK_NOT );
+ inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ testcase( inReg==target );
+ testcase( usedAsColumnCache(pParse, inReg, inReg) );
+ inReg = sqlite3ExprWritableRegister(pParse, inReg, target);
+ sqlite3VdbeAddOp1(v, op, inReg);
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ int addr;
+ assert( TK_ISNULL==OP_IsNull );
+ assert( TK_NOTNULL==OP_NotNull );
+ testcase( op==TK_ISNULL );
+ testcase( op==TK_NOTNULL );
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, target);
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ testcase( regFree1==0 );
+ addr = sqlite3VdbeAddOp1(v, op, r1);
+ sqlite3VdbeAddOp2(v, OP_AddImm, target, -1);
+ sqlite3VdbeJumpHere(v, addr);
+ break;
+ }
+ case TK_AGG_FUNCTION: {
+ AggInfo *pInfo = pExpr->pAggInfo;
+ if( pInfo==0 ){
+ sqlite3ErrorMsg(pParse, "misuse of aggregate: %T",
+ &pExpr->span);
+ }else{
+ inReg = pInfo->aFunc[pExpr->iAgg].iMem;
+ }
+ break;
+ }
+ case TK_CONST_FUNC:
+ case TK_FUNCTION: {
+ ExprList *pList = pExpr->pList;
+ int nExpr = pList ? pList->nExpr : 0;
+ FuncDef *pDef;
+ int nId;
+ const char *zId;
+ int constMask = 0;
+ int i;
+ sqlite3 *db = pParse->db;
+ u8 enc = ENC(db);
+ CollSeq *pColl = 0;
+
+ testcase( op==TK_CONST_FUNC );
+ testcase( op==TK_FUNCTION );
+ zId = (char*)pExpr->token.z;
+ nId = pExpr->token.n;
+ pDef = sqlite3FindFunction(pParse->db, zId, nId, nExpr, enc, 0);
+ assert( pDef!=0 );
+ if( pList ){
+ nExpr = pList->nExpr;
+ r1 = sqlite3GetTempRange(pParse, nExpr);
+ sqlite3ExprCodeExprList(pParse, pList, r1, 1);
+ }else{
+ nExpr = r1 = 0;
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ /* Possibly overload the function if the first argument is
+ ** a virtual table column.
+ **
+ ** For infix functions (LIKE, GLOB, REGEXP, and MATCH) use the
+ ** second argument, not the first, as the argument to test to
+ ** see if it is a column in a virtual table. This is done because
+ ** the left operand of infix functions (the operand we want to
+ ** control overloading) ends up as the second argument to the
+ ** function. The expression "A glob B" is equivalent to
+ ** "glob(B,A). We want to use the A in "A glob B" to test
+ ** for function overloading. But we use the B term in "glob(B,A)".
+ */
+ if( nExpr>=2 && (pExpr->flags & EP_InfixFunc) ){
+ pDef = sqlite3VtabOverloadFunction(db, pDef, nExpr, pList->a[1].pExpr);
+ }else if( nExpr>0 ){
+ pDef = sqlite3VtabOverloadFunction(db, pDef, nExpr, pList->a[0].pExpr);
+ }
+#endif
+ for(i=0; i<nExpr && i<32; i++){
+ if( sqlite3ExprIsConstant(pList->a[i].pExpr) ){
+ constMask |= (1<<i);
+ }
+ if( pDef->needCollSeq && !pColl ){
+ pColl = sqlite3ExprCollSeq(pParse, pList->a[i].pExpr);
+ }
+ }
+ if( pDef->needCollSeq ){
+ if( !pColl ) pColl = pParse->db->pDfltColl;
+ sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ);
+ }
+ sqlite3VdbeAddOp4(v, OP_Function, constMask, r1, target,
+ (char*)pDef, P4_FUNCDEF);
+ sqlite3VdbeChangeP5(v, nExpr);
+ if( nExpr ){
+ sqlite3ReleaseTempRange(pParse, r1, nExpr);
+ }
+ sqlite3ExprCacheAffinityChange(pParse, r1, nExpr);
+ break;
+ }
+#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_EXISTS:
+ case TK_SELECT: {
+ testcase( op==TK_EXISTS );
+ testcase( op==TK_SELECT );
+ if( pExpr->iColumn==0 ){
+ sqlite3CodeSubselect(pParse, pExpr);
+ }
+ inReg = pExpr->iColumn;
+ break;
+ }
+ case TK_IN: {
+ int j1, j2, j3, j4, j5;
+ char affinity;
+ int eType;
+
+ eType = sqlite3FindInIndex(pParse, pExpr, 0);
+
+ /* Figure out the affinity to use to create a key from the results
+ ** of the expression. affinityStr stores a static string suitable for
+ ** P4 of OP_MakeRecord.
+ */
+ affinity = comparisonAffinity(pExpr);
+
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, target);
+
+ /* Code the <expr> from "<expr> IN (...)". The temporary table
+ ** pExpr->iTable contains the values that make up the (...) set.
+ */
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ testcase( regFree1==0 );
+ j1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1);
+ sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ j2 = sqlite3VdbeAddOp0(v, OP_Goto);
+ sqlite3VdbeJumpHere(v, j1);
+ if( eType==IN_INDEX_ROWID ){
+ j3 = sqlite3VdbeAddOp1(v, OP_MustBeInt, r1);
+ j4 = sqlite3VdbeAddOp3(v, OP_NotExists, pExpr->iTable, 0, r1);
+ j5 = sqlite3VdbeAddOp0(v, OP_Goto);
+ sqlite3VdbeJumpHere(v, j3);
+ sqlite3VdbeJumpHere(v, j4);
+ }else{
+ r2 = regFree2 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp4(v, OP_MakeRecord, r1, 1, r2, &affinity, 1);
+ sqlite3ExprCacheAffinityChange(pParse, r1, 1);
+ j5 = sqlite3VdbeAddOp3(v, OP_Found, pExpr->iTable, 0, r2);
+ }
+ sqlite3VdbeAddOp2(v, OP_AddImm, target, -1);
+ sqlite3VdbeJumpHere(v, j2);
+ sqlite3VdbeJumpHere(v, j5);
+ break;
+ }
+#endif
+ /*
+ ** x BETWEEN y AND z
+ **
+ ** This is equivalent to
+ **
+ ** x>=y AND x<=z
+ **
+ ** X is stored in pExpr->pLeft.
+ ** Y is stored in pExpr->pList->a[0].pExpr.
+ ** Z is stored in pExpr->pList->a[1].pExpr.
+ */
+ case TK_BETWEEN: {
+ Expr *pLeft = pExpr->pLeft;
+ struct ExprList_item *pLItem = pExpr->pList->a;
+ Expr *pRight = pLItem->pExpr;
+
+ codeCompareOperands(pParse, pLeft, &r1, &regFree1,
+ pRight, &r2, &regFree2);
+ testcase( regFree1==0 );
+ testcase( regFree2==0 );
+ r3 = sqlite3GetTempReg(pParse);
+ r4 = sqlite3GetTempReg(pParse);
+ codeCompare(pParse, pLeft, pRight, OP_Ge,
+ r1, r2, r3, SQLITE_STOREP2);
+ pLItem++;
+ pRight = pLItem->pExpr;
+ sqlite3ReleaseTempReg(pParse, regFree2);
+ r2 = sqlite3ExprCodeTemp(pParse, pRight, &regFree2);
+ testcase( regFree2==0 );
+ codeCompare(pParse, pLeft, pRight, OP_Le, r1, r2, r4, SQLITE_STOREP2);
+ sqlite3VdbeAddOp3(v, OP_And, r3, r4, target);
+ sqlite3ReleaseTempReg(pParse, r3);
+ sqlite3ReleaseTempReg(pParse, r4);
+ break;
+ }
+ case TK_UPLUS: {
+ inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ break;
+ }
+
+ /*
+ ** Form A:
+ ** CASE x WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END
+ **
+ ** Form B:
+ ** CASE WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END
+ **
+ ** Form A is can be transformed into the equivalent form B as follows:
+ ** CASE WHEN x=e1 THEN r1 WHEN x=e2 THEN r2 ...
+ ** WHEN x=eN THEN rN ELSE y END
+ **
+ ** X (if it exists) is in pExpr->pLeft.
+ ** Y is in pExpr->pRight. The Y is also optional. If there is no
+ ** ELSE clause and no other term matches, then the result of the
+ ** exprssion is NULL.
+ ** Ei is in pExpr->pList->a[i*2] and Ri is pExpr->pList->a[i*2+1].
+ **
+ ** The result of the expression is the Ri for the first matching Ei,
+ ** or if there is no matching Ei, the ELSE term Y, or if there is
+ ** no ELSE term, NULL.
+ */
+ case TK_CASE: {
+ int endLabel; /* GOTO label for end of CASE stmt */
+ int nextCase; /* GOTO label for next WHEN clause */
+ int nExpr; /* 2x number of WHEN terms */
+ int i; /* Loop counter */
+ ExprList *pEList; /* List of WHEN terms */
+ struct ExprList_item *aListelem; /* Array of WHEN terms */
+ Expr opCompare; /* The X==Ei expression */
+ Expr cacheX; /* Cached expression X */
+ Expr *pX; /* The X expression */
+ Expr *pTest; /* X==Ei (form A) or just Ei (form B) */
+
+ assert(pExpr->pList);
+ assert((pExpr->pList->nExpr % 2) == 0);
+ assert(pExpr->pList->nExpr > 0);
+ pEList = pExpr->pList;
+ aListelem = pEList->a;
+ nExpr = pEList->nExpr;
+ endLabel = sqlite3VdbeMakeLabel(v);
+ if( (pX = pExpr->pLeft)!=0 ){
+ cacheX = *pX;
+ testcase( pX->op==TK_COLUMN || pX->op==TK_REGISTER );
+ cacheX.iTable = sqlite3ExprCodeTemp(pParse, pX, &regFree1);
+ testcase( regFree1==0 );
+ cacheX.op = TK_REGISTER;
+ cacheX.iColumn = 0;
+ opCompare.op = TK_EQ;
+ opCompare.pLeft = &cacheX;
+ pTest = &opCompare;
+ }
+ pParse->disableColCache++;
+ for(i=0; i<nExpr; i=i+2){
+ if( pX ){
+ opCompare.pRight = aListelem[i].pExpr;
+ }else{
+ pTest = aListelem[i].pExpr;
+ }
+ nextCase = sqlite3VdbeMakeLabel(v);
+ testcase( pTest->op==TK_COLUMN || pTest->op==TK_REGISTER );
+ sqlite3ExprIfFalse(pParse, pTest, nextCase, SQLITE_JUMPIFNULL);
+ testcase( aListelem[i+1].pExpr->op==TK_COLUMN );
+ testcase( aListelem[i+1].pExpr->op==TK_REGISTER );
+ sqlite3ExprCode(pParse, aListelem[i+1].pExpr, target);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, endLabel);
+ sqlite3VdbeResolveLabel(v, nextCase);
+ }
+ if( pExpr->pRight ){
+ sqlite3ExprCode(pParse, pExpr->pRight, target);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ }
+ sqlite3VdbeResolveLabel(v, endLabel);
+ assert( pParse->disableColCache>0 );
+ pParse->disableColCache--;
+ break;
+ }
+#ifndef SQLITE_OMIT_TRIGGER
+ case TK_RAISE: {
+ if( !pParse->trigStack ){
+ sqlite3ErrorMsg(pParse,
+ "RAISE() may only be used within a trigger-program");
+ return 0;
+ }
+ if( pExpr->iColumn!=OE_Ignore ){
+ assert( pExpr->iColumn==OE_Rollback ||
+ pExpr->iColumn == OE_Abort ||
+ pExpr->iColumn == OE_Fail );
+ sqlite3DequoteExpr(pParse->db, pExpr);
+ sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CONSTRAINT, pExpr->iColumn, 0,
+ (char*)pExpr->token.z, pExpr->token.n);
+ } else {
+ assert( pExpr->iColumn == OE_Ignore );
+ sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, pParse->trigStack->ignoreJump);
+ VdbeComment((v, "raise(IGNORE)"));
+ }
+ break;
+ }
+#endif
+ }
+ sqlite3ReleaseTempReg(pParse, regFree1);
+ sqlite3ReleaseTempReg(pParse, regFree2);
+ return inReg;
+}
+
+/*
+** Generate code to evaluate an expression and store the results
+** into a register. Return the register number where the results
+** are stored.
+**
+** If the register is a temporary register that can be deallocated,
+** then write its number into *pReg. If the result register is not
+** a temporary, then set *pReg to zero.
+*/
+SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){
+ int r1 = sqlite3GetTempReg(pParse);
+ int r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1);
+ if( r2==r1 ){
+ *pReg = r1;
+ }else{
+ sqlite3ReleaseTempReg(pParse, r1);
+ *pReg = 0;
+ }
+ return r2;
+}
+
+/*
+** Generate code that will evaluate expression pExpr and store the
+** results in register target. The results are guaranteed to appear
+** in register target.
+*/
+SQLITE_PRIVATE int sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){
+ int inReg;
+
+ assert( target>0 && target<=pParse->nMem );
+ inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
+ assert( pParse->pVdbe || pParse->db->mallocFailed );
+ if( inReg!=target && pParse->pVdbe ){
+ sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target);
+ }
+ return target;
+}
+
+/*
+** Generate code that evalutes the given expression and puts the result
+** in register target.
+**
+** Also make a copy of the expression results into another "cache" register
+** and modify the expression so that the next time it is evaluated,
+** the result is a copy of the cache register.
+**
+** This routine is used for expressions that are used multiple
+** times. They are evaluated once and the results of the expression
+** are reused.
+*/
+SQLITE_PRIVATE int sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int target){
+ Vdbe *v = pParse->pVdbe;
+ int inReg;
+ inReg = sqlite3ExprCode(pParse, pExpr, target);
+ assert( target>0 );
+ if( pExpr->op!=TK_REGISTER ){
+ int iMem;
+ iMem = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Copy, inReg, iMem);
+ pExpr->iTable = iMem;
+ pExpr->iColumn = pExpr->op;
+ pExpr->op = TK_REGISTER;
+ }
+ return inReg;
+}
+
+/*
+** Return TRUE if pExpr is an constant expression that is appropriate
+** for factoring out of a loop. Appropriate expressions are:
+**
+** * Any expression that evaluates to two or more opcodes.
+**
+** * Any OP_Integer, OP_Real, OP_String, OP_Blob, OP_Null,
+** or OP_Variable that does not need to be placed in a
+** specific register.
+**
+** There is no point in factoring out single-instruction constant
+** expressions that need to be placed in a particular register.
+** We could factor them out, but then we would end up adding an
+** OP_SCopy instruction to move the value into the correct register
+** later. We might as well just use the original instruction and
+** avoid the OP_SCopy.
+*/
+static int isAppropriateForFactoring(Expr *p){
+ if( !sqlite3ExprIsConstantNotJoin(p) ){
+ return 0; /* Only constant expressions are appropriate for factoring */
+ }
+ if( (p->flags & EP_FixedDest)==0 ){
+ return 1; /* Any constant without a fixed destination is appropriate */
+ }
+ while( p->op==TK_UPLUS ) p = p->pLeft;
+ switch( p->op ){
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+ case TK_BLOB:
+#endif
+ case TK_VARIABLE:
+ case TK_INTEGER:
+ case TK_FLOAT:
+ case TK_NULL:
+ case TK_STRING: {
+ testcase( p->op==TK_BLOB );
+ testcase( p->op==TK_VARIABLE );
+ testcase( p->op==TK_INTEGER );
+ testcase( p->op==TK_FLOAT );
+ testcase( p->op==TK_NULL );
+ testcase( p->op==TK_STRING );
+ /* Single-instruction constants with a fixed destination are
+ ** better done in-line. If we factor them, they will just end
+ ** up generating an OP_SCopy to move the value to the destination
+ ** register. */
+ return 0;
+ }
+ case TK_UMINUS: {
+ if( p->pLeft->op==TK_FLOAT || p->pLeft->op==TK_INTEGER ){
+ return 0;
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ return 1;
+}
+
+/*
+** If pExpr is a constant expression that is appropriate for
+** factoring out of a loop, then evaluate the expression
+** into a register and convert the expression into a TK_REGISTER
+** expression.
+*/
+static int evalConstExpr(void *pArg, Expr *pExpr){
+ Parse *pParse = (Parse*)pArg;
+ switch( pExpr->op ){
+ case TK_REGISTER: {
+ return 1;
+ }
+ case TK_FUNCTION:
+ case TK_AGG_FUNCTION:
+ case TK_CONST_FUNC: {
+ /* The arguments to a function have a fixed destination.
+ ** Mark them this way to avoid generated unneeded OP_SCopy
+ ** instructions.
+ */
+ ExprList *pList = pExpr->pList;
+ if( pList ){
+ int i = pList->nExpr;
+ struct ExprList_item *pItem = pList->a;
+ for(; i>0; i--, pItem++){
+ if( pItem->pExpr ) pItem->pExpr->flags |= EP_FixedDest;
+ }
+ }
+ break;
+ }
+ }
+ if( isAppropriateForFactoring(pExpr) ){
+ int r1 = ++pParse->nMem;
+ int r2;
+ r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1);
+ if( r1!=r2 ) sqlite3ReleaseTempReg(pParse, r1);
+ pExpr->iColumn = pExpr->op;
+ pExpr->op = TK_REGISTER;
+ pExpr->iTable = r2;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** Preevaluate constant subexpressions within pExpr and store the
+** results in registers. Modify pExpr so that the constant subexpresions
+** are TK_REGISTER opcodes that refer to the precomputed values.
+*/
+SQLITE_PRIVATE void sqlite3ExprCodeConstants(Parse *pParse, Expr *pExpr){
+ walkExprTree(pExpr, evalConstExpr, pParse);
+}
+
+
+/*
+** Generate code that pushes the value of every element of the given
+** expression list into a sequence of registers beginning at target.
+**
+** Return the number of elements evaluated.
+*/
+SQLITE_PRIVATE int sqlite3ExprCodeExprList(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* The expression list to be coded */
+ int target, /* Where to write results */
+ int doHardCopy /* Call sqlite3ExprHardCopy on each element if true */
+){
+ struct ExprList_item *pItem;
+ int i, n;
+ assert( pList!=0 || pParse->db->mallocFailed );
+ if( pList==0 ){
+ return 0;
+ }
+ assert( target>0 );
+ n = pList->nExpr;
+ for(pItem=pList->a, i=0; i<n; i++, pItem++){
+ sqlite3ExprCode(pParse, pItem->pExpr, target+i);
+ if( doHardCopy ) sqlite3ExprHardCopy(pParse, target, n);
+ }
+ return n;
+}
+
+/*
+** Generate code for a boolean expression such that a jump is made
+** to the label "dest" if the expression is true but execution
+** continues straight thru if the expression is false.
+**
+** If the expression evaluates to NULL (neither true nor false), then
+** take the jump if the jumpIfNull flag is SQLITE_JUMPIFNULL.
+**
+** This code depends on the fact that certain token values (ex: TK_EQ)
+** are the same as opcode values (ex: OP_Eq) that implement the corresponding
+** operation. Special comments in vdbe.c and the mkopcodeh.awk script in
+** the make process cause these values to align. Assert()s in the code
+** below verify that the numbers are aligned correctly.
+*/
+SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+ Vdbe *v = pParse->pVdbe;
+ int op = 0;
+ int regFree1 = 0;
+ int regFree2 = 0;
+ int r1, r2;
+
+ assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
+ if( v==0 || pExpr==0 ) return;
+ op = pExpr->op;
+ switch( op ){
+ case TK_AND: {
+ int d2 = sqlite3VdbeMakeLabel(v);
+ testcase( jumpIfNull==0 );
+ testcase( pParse->disableColCache==0 );
+ sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2,jumpIfNull^SQLITE_JUMPIFNULL);
+ pParse->disableColCache++;
+ sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ assert( pParse->disableColCache>0 );
+ pParse->disableColCache--;
+ sqlite3VdbeResolveLabel(v, d2);
+ break;
+ }
+ case TK_OR: {
+ testcase( jumpIfNull==0 );
+ testcase( pParse->disableColCache==0 );
+ sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ pParse->disableColCache++;
+ sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ assert( pParse->disableColCache>0 );
+ pParse->disableColCache--;
+ break;
+ }
+ case TK_NOT: {
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ break;
+ }
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ: {
+ assert( TK_LT==OP_Lt );
+ assert( TK_LE==OP_Le );
+ assert( TK_GT==OP_Gt );
+ assert( TK_GE==OP_Ge );
+ assert( TK_EQ==OP_Eq );
+ assert( TK_NE==OP_Ne );
+ testcase( op==TK_LT );
+ testcase( op==TK_LE );
+ testcase( op==TK_GT );
+ testcase( op==TK_GE );
+ testcase( op==TK_EQ );
+ testcase( op==TK_NE );
+ testcase( jumpIfNull==0 );
+ codeCompareOperands(pParse, pExpr->pLeft, &r1, &regFree1,
+ pExpr->pRight, &r2, &regFree2);
+ codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
+ r1, r2, dest, jumpIfNull);
+ testcase( regFree1==0 );
+ testcase( regFree2==0 );
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ assert( TK_ISNULL==OP_IsNull );
+ assert( TK_NOTNULL==OP_NotNull );
+ testcase( op==TK_ISNULL );
+ testcase( op==TK_NOTNULL );
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ sqlite3VdbeAddOp2(v, op, r1, dest);
+ testcase( regFree1==0 );
+ break;
+ }
+ case TK_BETWEEN: {
+ /* x BETWEEN y AND z
+ **
+ ** Is equivalent to
+ **
+ ** x>=y AND x<=z
+ **
+ ** Code it as such, taking care to do the common subexpression
+ ** elementation of x.
+ */
+ Expr exprAnd;
+ Expr compLeft;
+ Expr compRight;
+ Expr exprX;
+
+ exprX = *pExpr->pLeft;
+ exprAnd.op = TK_AND;
+ exprAnd.pLeft = &compLeft;
+ exprAnd.pRight = &compRight;
+ compLeft.op = TK_GE;
+ compLeft.pLeft = &exprX;
+ compLeft.pRight = pExpr->pList->a[0].pExpr;
+ compRight.op = TK_LE;
+ compRight.pLeft = &exprX;
+ compRight.pRight = pExpr->pList->a[1].pExpr;
+ exprX.iTable = sqlite3ExprCodeTemp(pParse, &exprX, &regFree1);
+ testcase( regFree1==0 );
+ exprX.op = TK_REGISTER;
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfTrue(pParse, &exprAnd, dest, jumpIfNull);
+ break;
+ }
+ default: {
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
+ sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0);
+ testcase( regFree1==0 );
+ testcase( jumpIfNull==0 );
+ break;
+ }
+ }
+ sqlite3ReleaseTempReg(pParse, regFree1);
+ sqlite3ReleaseTempReg(pParse, regFree2);
+}
+
+/*
+** Generate code for a boolean expression such that a jump is made
+** to the label "dest" if the expression is false but execution
+** continues straight thru if the expression is true.
+**
+** If the expression evaluates to NULL (neither true nor false) then
+** jump if jumpIfNull is SQLITE_JUMPIFNULL or fall through if jumpIfNull
+** is 0.
+*/
+SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+ Vdbe *v = pParse->pVdbe;
+ int op = 0;
+ int regFree1 = 0;
+ int regFree2 = 0;
+ int r1, r2;
+
+ assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
+ if( v==0 || pExpr==0 ) return;
+
+ /* The value of pExpr->op and op are related as follows:
+ **
+ ** pExpr->op op
+ ** --------- ----------
+ ** TK_ISNULL OP_NotNull
+ ** TK_NOTNULL OP_IsNull
+ ** TK_NE OP_Eq
+ ** TK_EQ OP_Ne
+ ** TK_GT OP_Le
+ ** TK_LE OP_Gt
+ ** TK_GE OP_Lt
+ ** TK_LT OP_Ge
+ **
+ ** For other values of pExpr->op, op is undefined and unused.
+ ** The value of TK_ and OP_ constants are arranged such that we
+ ** can compute the mapping above using the following expression.
+ ** Assert()s verify that the computation is correct.
+ */
+ op = ((pExpr->op+(TK_ISNULL&1))^1)-(TK_ISNULL&1);
+
+ /* Verify correct alignment of TK_ and OP_ constants
+ */
+ assert( pExpr->op!=TK_ISNULL || op==OP_NotNull );
+ assert( pExpr->op!=TK_NOTNULL || op==OP_IsNull );
+ assert( pExpr->op!=TK_NE || op==OP_Eq );
+ assert( pExpr->op!=TK_EQ || op==OP_Ne );
+ assert( pExpr->op!=TK_LT || op==OP_Ge );
+ assert( pExpr->op!=TK_LE || op==OP_Gt );
+ assert( pExpr->op!=TK_GT || op==OP_Le );
+ assert( pExpr->op!=TK_GE || op==OP_Lt );
+
+ switch( pExpr->op ){
+ case TK_AND: {
+ testcase( jumpIfNull==0 );
+ testcase( pParse->disableColCache==0 );
+ sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ pParse->disableColCache++;
+ sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ assert( pParse->disableColCache>0 );
+ pParse->disableColCache--;
+ break;
+ }
+ case TK_OR: {
+ int d2 = sqlite3VdbeMakeLabel(v);
+ testcase( jumpIfNull==0 );
+ testcase( pParse->disableColCache==0 );
+ sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, jumpIfNull^SQLITE_JUMPIFNULL);
+ pParse->disableColCache++;
+ sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ assert( pParse->disableColCache>0 );
+ pParse->disableColCache--;
+ sqlite3VdbeResolveLabel(v, d2);
+ break;
+ }
+ case TK_NOT: {
+ sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ break;
+ }
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ: {
+ testcase( op==TK_LT );
+ testcase( op==TK_LE );
+ testcase( op==TK_GT );
+ testcase( op==TK_GE );
+ testcase( op==TK_EQ );
+ testcase( op==TK_NE );
+ testcase( jumpIfNull==0 );
+ codeCompareOperands(pParse, pExpr->pLeft, &r1, &regFree1,
+ pExpr->pRight, &r2, &regFree2);
+ codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
+ r1, r2, dest, jumpIfNull);
+ testcase( regFree1==0 );
+ testcase( regFree2==0 );
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ testcase( op==TK_ISNULL );
+ testcase( op==TK_NOTNULL );
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ sqlite3VdbeAddOp2(v, op, r1, dest);
+ testcase( regFree1==0 );
+ break;
+ }
+ case TK_BETWEEN: {
+ /* x BETWEEN y AND z
+ **
+ ** Is equivalent to
+ **
+ ** x>=y AND x<=z
+ **
+ ** Code it as such, taking care to do the common subexpression
+ ** elementation of x.
+ */
+ Expr exprAnd;
+ Expr compLeft;
+ Expr compRight;
+ Expr exprX;
+
+ exprX = *pExpr->pLeft;
+ exprAnd.op = TK_AND;
+ exprAnd.pLeft = &compLeft;
+ exprAnd.pRight = &compRight;
+ compLeft.op = TK_GE;
+ compLeft.pLeft = &exprX;
+ compLeft.pRight = pExpr->pList->a[0].pExpr;
+ compRight.op = TK_LE;
+ compRight.pLeft = &exprX;
+ compRight.pRight = pExpr->pList->a[1].pExpr;
+ exprX.iTable = sqlite3ExprCodeTemp(pParse, &exprX, &regFree1);
+ testcase( regFree1==0 );
+ exprX.op = TK_REGISTER;
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfFalse(pParse, &exprAnd, dest, jumpIfNull);
+ break;
+ }
+ default: {
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
+ sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0);
+ testcase( regFree1==0 );
+ testcase( jumpIfNull==0 );
+ break;
+ }
+ }
+ sqlite3ReleaseTempReg(pParse, regFree1);
+ sqlite3ReleaseTempReg(pParse, regFree2);
+}
+
+/*
+** Do a deep comparison of two expression trees. Return TRUE (non-zero)
+** if they are identical and return FALSE if they differ in any way.
+**
+** Sometimes this routine will return FALSE even if the two expressions
+** really are equivalent. If we cannot prove that the expressions are
+** identical, we return FALSE just to be safe. So if this routine
+** returns false, then you do not really know for certain if the two
+** expressions are the same. But if you get a TRUE return, then you
+** can be sure the expressions are the same. In the places where
+** this routine is used, it does not hurt to get an extra FALSE - that
+** just might result in some slightly slower code. But returning
+** an incorrect TRUE could lead to a malfunction.
+*/
+SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB){
+ int i;
+ if( pA==0||pB==0 ){
+ return pB==pA;
+ }
+ if( pA->op!=pB->op ) return 0;
+ if( (pA->flags & EP_Distinct)!=(pB->flags & EP_Distinct) ) return 0;
+ if( !sqlite3ExprCompare(pA->pLeft, pB->pLeft) ) return 0;
+ if( !sqlite3ExprCompare(pA->pRight, pB->pRight) ) return 0;
+ if( pA->pList ){
+ if( pB->pList==0 ) return 0;
+ if( pA->pList->nExpr!=pB->pList->nExpr ) return 0;
+ for(i=0; i<pA->pList->nExpr; i++){
+ if( !sqlite3ExprCompare(pA->pList->a[i].pExpr, pB->pList->a[i].pExpr) ){
+ return 0;
+ }
+ }
+ }else if( pB->pList ){
+ return 0;
+ }
+ if( pA->pSelect || pB->pSelect ) return 0;
+ if( pA->iTable!=pB->iTable || pA->iColumn!=pB->iColumn ) return 0;
+ if( pA->op!=TK_COLUMN && pA->token.z ){
+ if( pB->token.z==0 ) return 0;
+ if( pB->token.n!=pA->token.n ) return 0;
+ if( sqlite3StrNICmp((char*)pA->token.z,(char*)pB->token.z,pB->token.n)!=0 ){
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/*
+** Add a new element to the pAggInfo->aCol[] array. Return the index of
+** the new element. Return a negative number if malloc fails.
+*/
+static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){
+ int i;
+ pInfo->aCol = sqlite3ArrayAllocate(
+ db,
+ pInfo->aCol,
+ sizeof(pInfo->aCol[0]),
+ 3,
+ &pInfo->nColumn,
+ &pInfo->nColumnAlloc,
+ &i
+ );
+ return i;
+}
+
+/*
+** Add a new element to the pAggInfo->aFunc[] array. Return the index of
+** the new element. Return a negative number if malloc fails.
+*/
+static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){
+ int i;
+ pInfo->aFunc = sqlite3ArrayAllocate(
+ db,
+ pInfo->aFunc,
+ sizeof(pInfo->aFunc[0]),
+ 3,
+ &pInfo->nFunc,
+ &pInfo->nFuncAlloc,
+ &i
+ );
+ return i;
+}
+
+/*
+** This is an xFunc for walkExprTree() used to implement
+** sqlite3ExprAnalyzeAggregates(). See sqlite3ExprAnalyzeAggregates
+** for additional information.
+**
+** This routine analyzes the aggregate function at pExpr.
+*/
+static int analyzeAggregate(void *pArg, Expr *pExpr){
+ int i;
+ NameContext *pNC = (NameContext *)pArg;
+ Parse *pParse = pNC->pParse;
+ SrcList *pSrcList = pNC->pSrcList;
+ AggInfo *pAggInfo = pNC->pAggInfo;
+
+ switch( pExpr->op ){
+ case TK_AGG_COLUMN:
+ case TK_COLUMN: {
+ /* Check to see if the column is in one of the tables in the FROM
+ ** clause of the aggregate query */
+ if( pSrcList ){
+ struct SrcList_item *pItem = pSrcList->a;
+ for(i=0; i<pSrcList->nSrc; i++, pItem++){
+ struct AggInfo_col *pCol;
+ if( pExpr->iTable==pItem->iCursor ){
+ /* If we reach this point, it means that pExpr refers to a table
+ ** that is in the FROM clause of the aggregate query.
+ **
+ ** Make an entry for the column in pAggInfo->aCol[] if there
+ ** is not an entry there already.
+ */
+ int k;
+ pCol = pAggInfo->aCol;
+ for(k=0; k<pAggInfo->nColumn; k++, pCol++){
+ if( pCol->iTable==pExpr->iTable &&
+ pCol->iColumn==pExpr->iColumn ){
+ break;
+ }
+ }
+ if( (k>=pAggInfo->nColumn)
+ && (k = addAggInfoColumn(pParse->db, pAggInfo))>=0
+ ){
+ pCol = &pAggInfo->aCol[k];
+ pCol->pTab = pExpr->pTab;
+ pCol->iTable = pExpr->iTable;
+ pCol->iColumn = pExpr->iColumn;
+ pCol->iMem = ++pParse->nMem;
+ pCol->iSorterColumn = -1;
+ pCol->pExpr = pExpr;
+ if( pAggInfo->pGroupBy ){
+ int j, n;
+ ExprList *pGB = pAggInfo->pGroupBy;
+ struct ExprList_item *pTerm = pGB->a;
+ n = pGB->nExpr;
+ for(j=0; j<n; j++, pTerm++){
+ Expr *pE = pTerm->pExpr;
+ if( pE->op==TK_COLUMN && pE->iTable==pExpr->iTable &&
+ pE->iColumn==pExpr->iColumn ){
+ pCol->iSorterColumn = j;
+ break;
+ }
+ }
+ }
+ if( pCol->iSorterColumn<0 ){
+ pCol->iSorterColumn = pAggInfo->nSortingColumn++;
+ }
+ }
+ /* There is now an entry for pExpr in pAggInfo->aCol[] (either
+ ** because it was there before or because we just created it).
+ ** Convert the pExpr to be a TK_AGG_COLUMN referring to that
+ ** pAggInfo->aCol[] entry.
+ */
+ pExpr->pAggInfo = pAggInfo;
+ pExpr->op = TK_AGG_COLUMN;
+ pExpr->iAgg = k;
+ break;
+ } /* endif pExpr->iTable==pItem->iCursor */
+ } /* end loop over pSrcList */
+ }
+ return 1;
+ }
+ case TK_AGG_FUNCTION: {
+ /* The pNC->nDepth==0 test causes aggregate functions in subqueries
+ ** to be ignored */
+ if( pNC->nDepth==0 ){
+ /* Check to see if pExpr is a duplicate of another aggregate
+ ** function that is already in the pAggInfo structure
+ */
+ struct AggInfo_func *pItem = pAggInfo->aFunc;
+ for(i=0; i<pAggInfo->nFunc; i++, pItem++){
+ if( sqlite3ExprCompare(pItem->pExpr, pExpr) ){
+ break;
+ }
+ }
+ if( i>=pAggInfo->nFunc ){
+ /* pExpr is original. Make a new entry in pAggInfo->aFunc[]
+ */
+ u8 enc = ENC(pParse->db);
+ i = addAggInfoFunc(pParse->db, pAggInfo);
+ if( i>=0 ){
+ pItem = &pAggInfo->aFunc[i];
+ pItem->pExpr = pExpr;
+ pItem->iMem = ++pParse->nMem;
+ pItem->pFunc = sqlite3FindFunction(pParse->db,
+ (char*)pExpr->token.z, pExpr->token.n,
+ pExpr->pList ? pExpr->pList->nExpr : 0, enc, 0);
+ if( pExpr->flags & EP_Distinct ){
+ pItem->iDistinct = pParse->nTab++;
+ }else{
+ pItem->iDistinct = -1;
+ }
+ }
+ }
+ /* Make pExpr point to the appropriate pAggInfo->aFunc[] entry
+ */
+ pExpr->iAgg = i;
+ pExpr->pAggInfo = pAggInfo;
+ return 1;
+ }
+ }
+ }
+
+ /* Recursively walk subqueries looking for TK_COLUMN nodes that need
+ ** to be changed to TK_AGG_COLUMN. But increment nDepth so that
+ ** TK_AGG_FUNCTION nodes in subqueries will be unchanged.
+ */
+ if( pExpr->pSelect ){
+ pNC->nDepth++;
+ walkSelectExpr(pExpr->pSelect, analyzeAggregate, pNC);
+ pNC->nDepth--;
+ }
+ return 0;
+}
+
+/*
+** Analyze the given expression looking for aggregate functions and
+** for variables that need to be added to the pParse->aAgg[] array.
+** Make additional entries to the pParse->aAgg[] array as necessary.
+**
+** This routine should only be called after the expression has been
+** analyzed by sqlite3ExprResolveNames().
+*/
+SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){
+ walkExprTree(pExpr, analyzeAggregate, pNC);
+}
+
+/*
+** Call sqlite3ExprAnalyzeAggregates() for every expression in an
+** expression list. Return the number of errors.
+**
+** If an error is found, the analysis is cut short.
+*/
+SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext *pNC, ExprList *pList){
+ struct ExprList_item *pItem;
+ int i;
+ if( pList ){
+ for(pItem=pList->a, i=0; i<pList->nExpr; i++, pItem++){
+ sqlite3ExprAnalyzeAggregates(pNC, pItem->pExpr);
+ }
+ }
+}
+
+/*
+** Allocate or deallocate temporary use registers during code generation.
+*/
+SQLITE_PRIVATE int sqlite3GetTempReg(Parse *pParse){
+ int i, r;
+ if( pParse->nTempReg==0 ){
+ return ++pParse->nMem;
+ }
+ for(i=0; i<pParse->nTempReg; i++){
+ r = pParse->aTempReg[i];
+ if( usedAsColumnCache(pParse, r, r) ) continue;
+ }
+ if( i>=pParse->nTempReg ){
+ return ++pParse->nMem;
+ }
+ while( i<pParse->nTempReg-1 ){
+ pParse->aTempReg[i] = pParse->aTempReg[i+1];
+ }
+ pParse->nTempReg--;
+ return r;
+}
+SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse *pParse, int iReg){
+ if( iReg && pParse->nTempReg<ArraySize(pParse->aTempReg) ){
+ pParse->aTempReg[pParse->nTempReg++] = iReg;
+ }
+}
+
+/*
+** Allocate or deallocate a block of nReg consecutive registers
+*/
+SQLITE_PRIVATE int sqlite3GetTempRange(Parse *pParse, int nReg){
+ int i, n;
+ i = pParse->iRangeReg;
+ n = pParse->nRangeReg;
+ if( nReg<=n && !usedAsColumnCache(pParse, i, i+n-1) ){
+ pParse->iRangeReg += nReg;
+ pParse->nRangeReg -= nReg;
+ }else{
+ i = pParse->nMem+1;
+ pParse->nMem += nReg;
+ }
+ return i;
+}
+SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){
+ if( nReg>pParse->nRangeReg ){
+ pParse->nRangeReg = nReg;
+ pParse->iRangeReg = iReg;
+ }
+}
+
+/************** End of expr.c ************************************************/
+/************** Begin file alter.c *******************************************/
+/*
+** 2005 February 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that used to generate VDBE code
+** that implements the ALTER TABLE command.
+**
+** $Id: alter.c,v 1.44 2008/05/09 14:17:52 drh Exp $
+*/
+
+/*
+** The code in this file only exists if we are not omitting the
+** ALTER TABLE logic from the build.
+*/
+#ifndef SQLITE_OMIT_ALTERTABLE
+
+
+/*
+** This function is used by SQL generated to implement the
+** ALTER TABLE command. The first argument is the text of a CREATE TABLE or
+** CREATE INDEX command. The second is a table name. The table name in
+** the CREATE TABLE or CREATE INDEX statement is replaced with the third
+** argument and the result returned. Examples:
+**
+** sqlite_rename_table('CREATE TABLE abc(a, b, c)', 'def')
+** -> 'CREATE TABLE def(a, b, c)'
+**
+** sqlite_rename_table('CREATE INDEX i ON abc(a)', 'def')
+** -> 'CREATE INDEX i ON def(a, b, c)'
+*/
+static void renameTableFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ unsigned char const *zSql = sqlite3_value_text(argv[0]);
+ unsigned char const *zTableName = sqlite3_value_text(argv[1]);
+
+ int token;
+ Token tname;
+ unsigned char const *zCsr = zSql;
+ int len = 0;
+ char *zRet;
+
+ sqlite3 *db = sqlite3_context_db_handle(context);
+
+ /* The principle used to locate the table name in the CREATE TABLE
+ ** statement is that the table name is the first non-space token that
+ ** is immediately followed by a left parenthesis - TK_LP - or "USING" TK_USING.
+ */
+ if( zSql ){
+ do {
+ if( !*zCsr ){
+ /* Ran out of input before finding an opening bracket. Return NULL. */
+ return;
+ }
+
+ /* Store the token that zCsr points to in tname. */
+ tname.z = zCsr;
+ tname.n = len;
+
+ /* Advance zCsr to the next token. Store that token type in 'token',
+ ** and its length in 'len' (to be used next iteration of this loop).
+ */
+ do {
+ zCsr += len;
+ len = sqlite3GetToken(zCsr, &token);
+ } while( token==TK_SPACE || token==TK_COMMENT );
+ assert( len>0 );
+ } while( token!=TK_LP && token!=TK_USING );
+
+ zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", tname.z - zSql, zSql,
+ zTableName, tname.z+tname.n);
+ sqlite3_result_text(context, zRet, -1, sqlite3_free);
+ }
+}
+
+#ifndef SQLITE_OMIT_TRIGGER
+/* This function is used by SQL generated to implement the
+** ALTER TABLE command. The first argument is the text of a CREATE TRIGGER
+** statement. The second is a table name. The table name in the CREATE
+** TRIGGER statement is replaced with the third argument and the result
+** returned. This is analagous to renameTableFunc() above, except for CREATE
+** TRIGGER, not CREATE INDEX and CREATE TABLE.
+*/
+static void renameTriggerFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ unsigned char const *zSql = sqlite3_value_text(argv[0]);
+ unsigned char const *zTableName = sqlite3_value_text(argv[1]);
+
+ int token;
+ Token tname;
+ int dist = 3;
+ unsigned char const *zCsr = zSql;
+ int len = 0;
+ char *zRet;
+
+ sqlite3 *db = sqlite3_context_db_handle(context);
+
+ /* The principle used to locate the table name in the CREATE TRIGGER
+ ** statement is that the table name is the first token that is immediatedly
+ ** preceded by either TK_ON or TK_DOT and immediatedly followed by one
+ ** of TK_WHEN, TK_BEGIN or TK_FOR.
+ */
+ if( zSql ){
+ do {
+
+ if( !*zCsr ){
+ /* Ran out of input before finding the table name. Return NULL. */
+ return;
+ }
+
+ /* Store the token that zCsr points to in tname. */
+ tname.z = zCsr;
+ tname.n = len;
+
+ /* Advance zCsr to the next token. Store that token type in 'token',
+ ** and its length in 'len' (to be used next iteration of this loop).
+ */
+ do {
+ zCsr += len;
+ len = sqlite3GetToken(zCsr, &token);
+ }while( token==TK_SPACE );
+ assert( len>0 );
+
+ /* Variable 'dist' stores the number of tokens read since the most
+ ** recent TK_DOT or TK_ON. This means that when a WHEN, FOR or BEGIN
+ ** token is read and 'dist' equals 2, the condition stated above
+ ** to be met.
+ **
+ ** Note that ON cannot be a database, table or column name, so
+ ** there is no need to worry about syntax like
+ ** "CREATE TRIGGER ... ON ON.ON BEGIN ..." etc.
+ */
+ dist++;
+ if( token==TK_DOT || token==TK_ON ){
+ dist = 0;
+ }
+ } while( dist!=2 || (token!=TK_WHEN && token!=TK_FOR && token!=TK_BEGIN) );
+
+ /* Variable tname now contains the token that is the old table-name
+ ** in the CREATE TRIGGER statement.
+ */
+ zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", tname.z - zSql, zSql,
+ zTableName, tname.z+tname.n);
+ sqlite3_result_text(context, zRet, -1, sqlite3_free);
+ }
+}
+#endif /* !SQLITE_OMIT_TRIGGER */
+
+/*
+** Register built-in functions used to help implement ALTER TABLE
+*/
+SQLITE_PRIVATE void sqlite3AlterFunctions(sqlite3 *db){
+ static const struct {
+ char *zName;
+ signed char nArg;
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value **);
+ } aFuncs[] = {
+ { "sqlite_rename_table", 2, renameTableFunc},
+#ifndef SQLITE_OMIT_TRIGGER
+ { "sqlite_rename_trigger", 2, renameTriggerFunc},
+#endif
+ };
+ int i;
+
+ for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+ sqlite3CreateFunc(db, aFuncs[i].zName, aFuncs[i].nArg,
+ SQLITE_UTF8, 0, aFuncs[i].xFunc, 0, 0);
+ }
+}
+
+/*
+** Generate the text of a WHERE expression which can be used to select all
+** temporary triggers on table pTab from the sqlite_temp_master table. If
+** table pTab has no temporary triggers, or is itself stored in the
+** temporary database, NULL is returned.
+*/
+static char *whereTempTriggers(Parse *pParse, Table *pTab){
+ Trigger *pTrig;
+ char *zWhere = 0;
+ char *tmp = 0;
+ const Schema *pTempSchema = pParse->db->aDb[1].pSchema; /* Temp db schema */
+
+ /* If the table is not located in the temp-db (in which case NULL is
+ ** returned, loop through the tables list of triggers. For each trigger
+ ** that is not part of the temp-db schema, add a clause to the WHERE
+ ** expression being built up in zWhere.
+ */
+ if( pTab->pSchema!=pTempSchema ){
+ sqlite3 *db = pParse->db;
+ for( pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext ){
+ if( pTrig->pSchema==pTempSchema ){
+ if( !zWhere ){
+ zWhere = sqlite3MPrintf(db, "name=%Q", pTrig->name);
+ }else{
+ tmp = zWhere;
+ zWhere = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, pTrig->name);
+ sqlite3_free(tmp);
+ }
+ }
+ }
+ }
+ return zWhere;
+}
+
+/*
+** Generate code to drop and reload the internal representation of table
+** pTab from the database, including triggers and temporary triggers.
+** Argument zName is the name of the table in the database schema at
+** the time the generated code is executed. This can be different from
+** pTab->zName if this function is being called to code part of an
+** "ALTER TABLE RENAME TO" statement.
+*/
+static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){
+ Vdbe *v;
+ char *zWhere;
+ int iDb; /* Index of database containing pTab */
+#ifndef SQLITE_OMIT_TRIGGER
+ Trigger *pTrig;
+#endif
+
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) return;
+ assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
+ iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ assert( iDb>=0 );
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* Drop any table triggers from the internal schema. */
+ for(pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext){
+ int iTrigDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
+ assert( iTrigDb==iDb || iTrigDb==1 );
+ sqlite3VdbeAddOp4(v, OP_DropTrigger, iTrigDb, 0, 0, pTrig->name, 0);
+ }
+#endif
+
+ /* Drop the table and index from the internal schema */
+ sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
+
+ /* Reload the table, index and permanent trigger schemas. */
+ zWhere = sqlite3MPrintf(pParse->db, "tbl_name=%Q", zName);
+ if( !zWhere ) return;
+ sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC);
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* Now, if the table is not stored in the temp database, reload any temp
+ ** triggers. Don't use IN(...) in case SQLITE_OMIT_SUBQUERY is defined.
+ */
+ if( (zWhere=whereTempTriggers(pParse, pTab))!=0 ){
+ sqlite3VdbeAddOp4(v, OP_ParseSchema, 1, 0, 0, zWhere, P4_DYNAMIC);
+ }
+#endif
+}
+
+/*
+** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
+** command.
+*/
+SQLITE_PRIVATE void sqlite3AlterRenameTable(
+ Parse *pParse, /* Parser context. */
+ SrcList *pSrc, /* The table to rename. */
+ Token *pName /* The new table name. */
+){
+ int iDb; /* Database that contains the table */
+ char *zDb; /* Name of database iDb */
+ Table *pTab; /* Table being renamed */
+ char *zName = 0; /* NULL-terminated version of pName */
+ sqlite3 *db = pParse->db; /* Database connection */
+ int nTabName; /* Number of UTF-8 characters in zTabName */
+ const char *zTabName; /* Original name of the table */
+ Vdbe *v;
+#ifndef SQLITE_OMIT_TRIGGER
+ char *zWhere = 0; /* Where clause to locate temp triggers */
+#endif
+ int isVirtualRename = 0; /* True if this is a v-table with an xRename() */
+
+ if( db->mallocFailed ) goto exit_rename_table;
+ assert( pSrc->nSrc==1 );
+ assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
+
+ pTab = sqlite3LocateTable(pParse, 0, pSrc->a[0].zName, pSrc->a[0].zDatabase);
+ if( !pTab ) goto exit_rename_table;
+ iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ zDb = db->aDb[iDb].zName;
+
+ /* Get a NULL terminated version of the new table name. */
+ zName = sqlite3NameFromToken(db, pName);
+ if( !zName ) goto exit_rename_table;
+
+ /* Check that a table or index named 'zName' does not already exist
+ ** in database iDb. If so, this is an error.
+ */
+ if( sqlite3FindTable(db, zName, zDb) || sqlite3FindIndex(db, zName, zDb) ){
+ sqlite3ErrorMsg(pParse,
+ "there is already another table or index with this name: %s", zName);
+ goto exit_rename_table;
+ }
+
+ /* Make sure it is not a system table being altered, or a reserved name
+ ** that the table is being renamed to.
+ */
+ if( strlen(pTab->zName)>6 && 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) ){
+ sqlite3ErrorMsg(pParse, "table %s may not be altered", pTab->zName);
+ goto exit_rename_table;
+ }
+ if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ goto exit_rename_table;
+ }
+
+#ifndef SQLITE_OMIT_VIEW
+ if( pTab->pSelect ){
+ sqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName);
+ goto exit_rename_table;
+ }
+#endif
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ /* Invoke the authorization callback. */
+ if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
+ goto exit_rename_table;
+ }
+#endif
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ goto exit_rename_table;
+ }
+ if( IsVirtual(pTab) && pTab->pMod->pModule->xRename ){
+ isVirtualRename = 1;
+ }
+#endif
+
+ /* Begin a transaction and code the VerifyCookie for database iDb.
+ ** Then modify the schema cookie (since the ALTER TABLE modifies the
+ ** schema). Open a statement transaction if the table is a virtual
+ ** table.
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ){
+ goto exit_rename_table;
+ }
+ sqlite3BeginWriteOperation(pParse, isVirtualRename, iDb);
+ sqlite3ChangeCookie(pParse, iDb);
+
+ /* If this is a virtual table, invoke the xRename() function if
+ ** one is defined. The xRename() callback will modify the names
+ ** of any resources used by the v-table implementation (including other
+ ** SQLite tables) that are identified by the name of the virtual table.
+ */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( isVirtualRename ){
+ int i = ++pParse->nMem;
+ sqlite3VdbeAddOp4(v, OP_String8, 0, i, 0, zName, 0);
+ sqlite3VdbeAddOp4(v, OP_VRename, i, 0, 0,(const char*)pTab->pVtab, P4_VTAB);
+ }
+#endif
+
+ /* figure out how many UTF-8 characters are in zName */
+ zTabName = pTab->zName;
+ nTabName = sqlite3Utf8CharLen(zTabName, -1);
+
+ /* Modify the sqlite_master table to use the new table name. */
+ sqlite3NestedParse(pParse,
+ "UPDATE %Q.%s SET "
+#ifdef SQLITE_OMIT_TRIGGER
+ "sql = sqlite_rename_table(sql, %Q), "
+#else
+ "sql = CASE "
+ "WHEN type = 'trigger' THEN sqlite_rename_trigger(sql, %Q)"
+ "ELSE sqlite_rename_table(sql, %Q) END, "
+#endif
+ "tbl_name = %Q, "
+ "name = CASE "
+ "WHEN type='table' THEN %Q "
+ "WHEN name LIKE 'sqlite_autoindex%%' AND type='index' THEN "
+ "'sqlite_autoindex_' || %Q || substr(name,%d+18) "
+ "ELSE name END "
+ "WHERE tbl_name=%Q AND "
+ "(type='table' OR type='index' OR type='trigger');",
+ zDb, SCHEMA_TABLE(iDb), zName, zName, zName,
+#ifndef SQLITE_OMIT_TRIGGER
+ zName,
+#endif
+ zName, nTabName, zTabName
+ );
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ /* If the sqlite_sequence table exists in this database, then update
+ ** it with the new table name.
+ */
+ if( sqlite3FindTable(db, "sqlite_sequence", zDb) ){
+ sqlite3NestedParse(pParse,
+ "UPDATE \"%w\".sqlite_sequence set name = %Q WHERE name = %Q",
+ zDb, zName, pTab->zName);
+ }
+#endif
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* If there are TEMP triggers on this table, modify the sqlite_temp_master
+ ** table. Don't do this if the table being ALTERed is itself located in
+ ** the temp database.
+ */
+ if( (zWhere=whereTempTriggers(pParse, pTab))!=0 ){
+ sqlite3NestedParse(pParse,
+ "UPDATE sqlite_temp_master SET "
+ "sql = sqlite_rename_trigger(sql, %Q), "
+ "tbl_name = %Q "
+ "WHERE %s;", zName, zName, zWhere);
+ sqlite3_free(zWhere);
+ }
+#endif
+
+ /* Drop and reload the internal table schema. */
+ reloadTableSchema(pParse, pTab, zName);
+
+exit_rename_table:
+ sqlite3SrcListDelete(pSrc);
+ sqlite3_free(zName);
+}
+
+
+/*
+** This function is called after an "ALTER TABLE ... ADD" statement
+** has been parsed. Argument pColDef contains the text of the new
+** column definition.
+**
+** The Table structure pParse->pNewTable was extended to include
+** the new column during parsing.
+*/
+SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
+ Table *pNew; /* Copy of pParse->pNewTable */
+ Table *pTab; /* Table being altered */
+ int iDb; /* Database number */
+ const char *zDb; /* Database name */
+ const char *zTab; /* Table name */
+ char *zCol; /* Null-terminated column definition */
+ Column *pCol; /* The new column */
+ Expr *pDflt; /* Default value for the new column */
+ sqlite3 *db; /* The database connection; */
+
+ if( pParse->nErr ) return;
+ pNew = pParse->pNewTable;
+ assert( pNew );
+
+ db = pParse->db;
+ assert( sqlite3BtreeHoldsAllMutexes(db) );
+ iDb = sqlite3SchemaToIndex(db, pNew->pSchema);
+ zDb = db->aDb[iDb].zName;
+ zTab = pNew->zName;
+ pCol = &pNew->aCol[pNew->nCol-1];
+ pDflt = pCol->pDflt;
+ pTab = sqlite3FindTable(db, zTab, zDb);
+ assert( pTab );
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ /* Invoke the authorization callback. */
+ if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
+ return;
+ }
+#endif
+
+ /* If the default value for the new column was specified with a
+ ** literal NULL, then set pDflt to 0. This simplifies checking
+ ** for an SQL NULL default below.
+ */
+ if( pDflt && pDflt->op==TK_NULL ){
+ pDflt = 0;
+ }
+
+ /* Check that the new column is not specified as PRIMARY KEY or UNIQUE.
+ ** If there is a NOT NULL constraint, then the default value for the
+ ** column must not be NULL.
+ */
+ if( pCol->isPrimKey ){
+ sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column");
+ return;
+ }
+ if( pNew->pIndex ){
+ sqlite3ErrorMsg(pParse, "Cannot add a UNIQUE column");
+ return;
+ }
+ if( pCol->notNull && !pDflt ){
+ sqlite3ErrorMsg(pParse,
+ "Cannot add a NOT NULL column with default value NULL");
+ return;
+ }
+
+ /* Ensure the default expression is something that sqlite3ValueFromExpr()
+ ** can handle (i.e. not CURRENT_TIME etc.)
+ */
+ if( pDflt ){
+ sqlite3_value *pVal;
+ if( sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_NONE, &pVal) ){
+ db->mallocFailed = 1;
+ return;
+ }
+ if( !pVal ){
+ sqlite3ErrorMsg(pParse, "Cannot add a column with non-constant default");
+ return;
+ }
+ sqlite3ValueFree(pVal);
+ }
+
+ /* Modify the CREATE TABLE statement. */
+ zCol = sqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n);
+ if( zCol ){
+ char *zEnd = &zCol[pColDef->n-1];
+ while( (zEnd>zCol && *zEnd==';') || isspace(*(unsigned char *)zEnd) ){
+ *zEnd-- = '\0';
+ }
+ sqlite3NestedParse(pParse,
+ "UPDATE \"%w\".%s SET "
+ "sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d) "
+ "WHERE type = 'table' AND name = %Q",
+ zDb, SCHEMA_TABLE(iDb), pNew->addColOffset, zCol, pNew->addColOffset+1,
+ zTab
+ );
+ sqlite3_free(zCol);
+ }
+
+ /* If the default value of the new column is NULL, then set the file
+ ** format to 2. If the default value of the new column is not NULL,
+ ** the file format becomes 3.
+ */
+ sqlite3MinimumFileFormat(pParse, iDb, pDflt ? 3 : 2);
+
+ /* Reload the schema of the modified table. */
+ reloadTableSchema(pParse, pTab, pTab->zName);
+}
+
+/*
+** This function is called by the parser after the table-name in
+** an "ALTER TABLE <table-name> ADD" statement is parsed. Argument
+** pSrc is the full-name of the table being altered.
+**
+** This routine makes a (partial) copy of the Table structure
+** for the table being altered and sets Parse.pNewTable to point
+** to it. Routines called by the parser as the column definition
+** is parsed (i.e. sqlite3AddColumn()) add the new Column data to
+** the copy. The copy of the Table structure is deleted by tokenize.c
+** after parsing is finished.
+**
+** Routine sqlite3AlterFinishAddColumn() will be called to complete
+** coding the "ALTER TABLE ... ADD" statement.
+*/
+SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){
+ Table *pNew;
+ Table *pTab;
+ Vdbe *v;
+ int iDb;
+ int i;
+ int nAlloc;
+ sqlite3 *db = pParse->db;
+
+ /* Look up the table being altered. */
+ assert( pParse->pNewTable==0 );
+ assert( sqlite3BtreeHoldsAllMutexes(db) );
+ if( db->mallocFailed ) goto exit_begin_add_column;
+ pTab = sqlite3LocateTable(pParse, 0, pSrc->a[0].zName, pSrc->a[0].zDatabase);
+ if( !pTab ) goto exit_begin_add_column;
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTab) ){
+ sqlite3ErrorMsg(pParse, "virtual tables may not be altered");
+ goto exit_begin_add_column;
+ }
+#endif
+
+ /* Make sure this is not an attempt to ALTER a view. */
+ if( pTab->pSelect ){
+ sqlite3ErrorMsg(pParse, "Cannot add a column to a view");
+ goto exit_begin_add_column;
+ }
+
+ assert( pTab->addColOffset>0 );
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+
+ /* Put a copy of the Table struct in Parse.pNewTable for the
+ ** sqlite3AddColumn() function and friends to modify.
+ */
+ pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table));
+ if( !pNew ) goto exit_begin_add_column;
+ pParse->pNewTable = pNew;
+ pNew->nRef = 1;
+ pNew->nCol = pTab->nCol;
+ assert( pNew->nCol>0 );
+ nAlloc = (((pNew->nCol-1)/8)*8)+8;
+ assert( nAlloc>=pNew->nCol && nAlloc%8==0 && nAlloc-pNew->nCol<8 );
+ pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nAlloc);
+ pNew->zName = sqlite3DbStrDup(db, pTab->zName);
+ if( !pNew->aCol || !pNew->zName ){
+ db->mallocFailed = 1;
+ goto exit_begin_add_column;
+ }
+ memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol);
+ for(i=0; i<pNew->nCol; i++){
+ Column *pCol = &pNew->aCol[i];
+ pCol->zName = sqlite3DbStrDup(db, pCol->zName);
+ pCol->zColl = 0;
+ pCol->zType = 0;
+ pCol->pDflt = 0;
+ }
+ pNew->pSchema = db->aDb[iDb].pSchema;
+ pNew->addColOffset = pTab->addColOffset;
+ pNew->nRef = 1;
+
+ /* Begin a transaction and increment the schema cookie. */
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) goto exit_begin_add_column;
+ sqlite3ChangeCookie(pParse, iDb);
+
+exit_begin_add_column:
+ sqlite3SrcListDelete(pSrc);
+ return;
+}
+#endif /* SQLITE_ALTER_TABLE */
+
+/************** End of alter.c ***********************************************/
+/************** Begin file analyze.c *****************************************/
+/*
+** 2005 July 8
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code associated with the ANALYZE command.
+**
+** @(#) $Id: analyze.c,v 1.42 2008/03/25 09:47:35 danielk1977 Exp $
+*/
+#ifndef SQLITE_OMIT_ANALYZE
+
+/*
+** This routine generates code that opens the sqlite_stat1 table on cursor
+** iStatCur.
+**
+** If the sqlite_stat1 tables does not previously exist, it is created.
+** If it does previously exist, all entires associated with table zWhere
+** are removed. If zWhere==0 then all entries are removed.
+*/
+static void openStatTable(
+ Parse *pParse, /* Parsing context */
+ int iDb, /* The database we are looking in */
+ int iStatCur, /* Open the sqlite_stat1 table on this cursor */
+ const char *zWhere /* Delete entries associated with this table */
+){
+ sqlite3 *db = pParse->db;
+ Db *pDb;
+ int iRootPage;
+ int createStat1 = 0;
+ Table *pStat;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+
+ if( v==0 ) return;
+ assert( sqlite3BtreeHoldsAllMutexes(db) );
+ assert( sqlite3VdbeDb(v)==db );
+ pDb = &db->aDb[iDb];
+ if( (pStat = sqlite3FindTable(db, "sqlite_stat1", pDb->zName))==0 ){
+ /* The sqlite_stat1 tables does not exist. Create it.
+ ** Note that a side-effect of the CREATE TABLE statement is to leave
+ ** the rootpage of the new table in register pParse->regRoot. This is
+ ** important because the OpenWrite opcode below will be needing it. */
+ sqlite3NestedParse(pParse,
+ "CREATE TABLE %Q.sqlite_stat1(tbl,idx,stat)",
+ pDb->zName
+ );
+ iRootPage = pParse->regRoot;
+ createStat1 = 1; /* Cause rootpage to be taken from top of stack */
+ }else if( zWhere ){
+ /* The sqlite_stat1 table exists. Delete all entries associated with
+ ** the table zWhere. */
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %Q.sqlite_stat1 WHERE tbl=%Q",
+ pDb->zName, zWhere
+ );
+ iRootPage = pStat->tnum;
+ }else{
+ /* The sqlite_stat1 table already exists. Delete all rows. */
+ iRootPage = pStat->tnum;
+ sqlite3VdbeAddOp2(v, OP_Clear, pStat->tnum, iDb);
+ }
+
+ /* Open the sqlite_stat1 table for writing. Unless it was created
+ ** by this vdbe program, lock it for writing at the shared-cache level.
+ ** If this vdbe did create the sqlite_stat1 table, then it must have
+ ** already obtained a schema-lock, making the write-lock redundant.
+ */
+ if( !createStat1 ){
+ sqlite3TableLock(pParse, iDb, iRootPage, 1, "sqlite_stat1");
+ }
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, 3);
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, iStatCur, iRootPage, iDb);
+ sqlite3VdbeChangeP5(v, createStat1);
+}
+
+/*
+** Generate code to do an analysis of all indices associated with
+** a single table.
+*/
+static void analyzeOneTable(
+ Parse *pParse, /* Parser context */
+ Table *pTab, /* Table whose indices are to be analyzed */
+ int iStatCur, /* Cursor that writes to the sqlite_stat1 table */
+ int iMem /* Available memory locations begin here */
+){
+ Index *pIdx; /* An index to being analyzed */
+ int iIdxCur; /* Cursor number for index being analyzed */
+ int nCol; /* Number of columns in the index */
+ Vdbe *v; /* The virtual machine being built up */
+ int i; /* Loop counter */
+ int topOfLoop; /* The top of the loop */
+ int endOfLoop; /* The end of the loop */
+ int addr; /* The address of an instruction */
+ int iDb; /* Index of database containing pTab */
+
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 || pTab==0 || pTab->pIndex==0 ){
+ /* Do no analysis for tables that have no indices */
+ return;
+ }
+ assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
+ iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ assert( iDb>=0 );
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0,
+ pParse->db->aDb[iDb].zName ) ){
+ return;
+ }
+#endif
+
+ /* Establish a read-lock on the table at the shared-cache level. */
+ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+
+ iIdxCur = pParse->nTab;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx);
+ int regFields; /* Register block for building records */
+ int regRec; /* Register holding completed record */
+ int regTemp; /* Temporary use register */
+ int regCol; /* Content of a column from the table being analyzed */
+ int regRowid; /* Rowid for the inserted record */
+ int regF2;
+
+ /* Open a cursor to the index to be analyzed
+ */
+ assert( iDb==sqlite3SchemaToIndex(pParse->db, pIdx->pSchema) );
+ nCol = pIdx->nColumn;
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, nCol+1);
+ sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb,
+ (char *)pKey, P4_KEYINFO_HANDOFF);
+ VdbeComment((v, "%s", pIdx->zName));
+ regFields = iMem+nCol*2;
+ regTemp = regRowid = regCol = regFields+3;
+ regRec = regCol+1;
+ if( regRec>pParse->nMem ){
+ pParse->nMem = regRec;
+ }
+
+ /* Memory cells are used as follows:
+ **
+ ** mem[iMem]: The total number of rows in the table.
+ ** mem[iMem+1]: Number of distinct values in column 1
+ ** ...
+ ** mem[iMem+nCol]: Number of distinct values in column N
+ ** mem[iMem+nCol+1] Last observed value of column 1
+ ** ...
+ ** mem[iMem+nCol+nCol]: Last observed value of column N
+ **
+ ** Cells iMem through iMem+nCol are initialized to 0. The others
+ ** are initialized to NULL.
+ */
+ for(i=0; i<=nCol; i++){
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, iMem+i);
+ }
+ for(i=0; i<nCol; i++){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, iMem+nCol+i+1);
+ }
+
+ /* Do the analysis.
+ */
+ endOfLoop = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp2(v, OP_Rewind, iIdxCur, endOfLoop);
+ topOfLoop = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp2(v, OP_AddImm, iMem, 1);
+ for(i=0; i<nCol; i++){
+ sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regCol);
+ sqlite3VdbeAddOp3(v, OP_Ne, regCol, 0, iMem+nCol+i+1);
+ /**** TODO: add collating sequence *****/
+ sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL);
+ }
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, endOfLoop);
+ for(i=0; i<nCol; i++){
+ sqlite3VdbeJumpHere(v, topOfLoop + 2*(i + 1));
+ sqlite3VdbeAddOp2(v, OP_AddImm, iMem+i+1, 1);
+ sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, iMem+nCol+i+1);
+ }
+ sqlite3VdbeResolveLabel(v, endOfLoop);
+ sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, topOfLoop);
+ sqlite3VdbeAddOp1(v, OP_Close, iIdxCur);
+
+ /* Store the results.
+ **
+ ** The result is a single row of the sqlite_stat1 table. The first
+ ** two columns are the names of the table and index. The third column
+ ** is a string composed of a list of integer statistics about the
+ ** index. The first integer in the list is the total number of entires
+ ** in the index. There is one additional integer in the list for each
+ ** column of the table. This additional integer is a guess of how many
+ ** rows of the table the index will select. If D is the count of distinct
+ ** values and K is the total number of rows, then the integer is computed
+ ** as:
+ **
+ ** I = (K+D-1)/D
+ **
+ ** If K==0 then no entry is made into the sqlite_stat1 table.
+ ** If K>0 then it is always the case the D>0 so division by zero
+ ** is never possible.
+ */
+ addr = sqlite3VdbeAddOp1(v, OP_IfNot, iMem);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, regFields, 0, pTab->zName, 0);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, regFields+1, 0, pIdx->zName, 0);
+ regF2 = regFields+2;
+ sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regF2);
+ for(i=0; i<nCol; i++){
+ sqlite3VdbeAddOp4(v, OP_String8, 0, regTemp, 0, " ", 0);
+ sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regF2, regF2);
+ sqlite3VdbeAddOp3(v, OP_Add, iMem, iMem+i+1, regTemp);
+ sqlite3VdbeAddOp2(v, OP_AddImm, regTemp, -1);
+ sqlite3VdbeAddOp3(v, OP_Divide, iMem+i+1, regTemp, regTemp);
+ sqlite3VdbeAddOp1(v, OP_ToInt, regTemp);
+ sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regF2, regF2);
+ }
+ sqlite3VdbeAddOp4(v, OP_MakeRecord, regFields, 3, regRec, "aaa", 0);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regRowid);
+ sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid);
+ sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ sqlite3VdbeJumpHere(v, addr);
+ }
+}
+
+/*
+** Generate code that will cause the most recent index analysis to
+** be laoded into internal hash tables where is can be used.
+*/
+static void loadAnalysis(Parse *pParse, int iDb){
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp1(v, OP_LoadAnalysis, iDb);
+ }
+}
+
+/*
+** Generate code that will do an analysis of an entire database
+*/
+static void analyzeDatabase(Parse *pParse, int iDb){
+ sqlite3 *db = pParse->db;
+ Schema *pSchema = db->aDb[iDb].pSchema; /* Schema of database iDb */
+ HashElem *k;
+ int iStatCur;
+ int iMem;
+
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ iStatCur = pParse->nTab++;
+ openStatTable(pParse, iDb, iStatCur, 0);
+ iMem = pParse->nMem+1;
+ for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){
+ Table *pTab = (Table*)sqliteHashData(k);
+ analyzeOneTable(pParse, pTab, iStatCur, iMem);
+ }
+ loadAnalysis(pParse, iDb);
+}
+
+/*
+** Generate code that will do an analysis of a single table in
+** a database.
+*/
+static void analyzeTable(Parse *pParse, Table *pTab){
+ int iDb;
+ int iStatCur;
+
+ assert( pTab!=0 );
+ assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
+ iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ iStatCur = pParse->nTab++;
+ openStatTable(pParse, iDb, iStatCur, pTab->zName);
+ analyzeOneTable(pParse, pTab, iStatCur, pParse->nMem+1);
+ loadAnalysis(pParse, iDb);
+}
+
+/*
+** Generate code for the ANALYZE command. The parser calls this routine
+** when it recognizes an ANALYZE command.
+**
+** ANALYZE -- 1
+** ANALYZE <database> -- 2
+** ANALYZE ?<database>.?<tablename> -- 3
+**
+** Form 1 causes all indices in all attached databases to be analyzed.
+** Form 2 analyzes all indices the single database named.
+** Form 3 analyzes all indices associated with the named table.
+*/
+SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
+ sqlite3 *db = pParse->db;
+ int iDb;
+ int i;
+ char *z, *zDb;
+ Table *pTab;
+ Token *pTableName;
+
+ /* Read the database schema. If an error occurs, leave an error message
+ ** and code in pParse and return NULL. */
+ assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ return;
+ }
+
+ if( pName1==0 ){
+ /* Form 1: Analyze everything */
+ for(i=0; i<db->nDb; i++){
+ if( i==1 ) continue; /* Do not analyze the TEMP database */
+ analyzeDatabase(pParse, i);
+ }
+ }else if( pName2==0 || pName2->n==0 ){
+ /* Form 2: Analyze the database or table named */
+ iDb = sqlite3FindDb(db, pName1);
+ if( iDb>=0 ){
+ analyzeDatabase(pParse, iDb);
+ }else{
+ z = sqlite3NameFromToken(db, pName1);
+ if( z ){
+ pTab = sqlite3LocateTable(pParse, 0, z, 0);
+ sqlite3_free(z);
+ if( pTab ){
+ analyzeTable(pParse, pTab);
+ }
+ }
+ }
+ }else{
+ /* Form 3: Analyze the fully qualified table name */
+ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pTableName);
+ if( iDb>=0 ){
+ zDb = db->aDb[iDb].zName;
+ z = sqlite3NameFromToken(db, pTableName);
+ if( z ){
+ pTab = sqlite3LocateTable(pParse, 0, z, zDb);
+ sqlite3_free(z);
+ if( pTab ){
+ analyzeTable(pParse, pTab);
+ }
+ }
+ }
+ }
+}
+
+/*
+** Used to pass information from the analyzer reader through to the
+** callback routine.
+*/
+typedef struct analysisInfo analysisInfo;
+struct analysisInfo {
+ sqlite3 *db;
+ const char *zDatabase;
+};
+
+/*
+** This callback is invoked once for each index when reading the
+** sqlite_stat1 table.
+**
+** argv[0] = name of the index
+** argv[1] = results of analysis - on integer for each column
+*/
+static int analysisLoader(void *pData, int argc, char **argv, char **azNotUsed){
+ analysisInfo *pInfo = (analysisInfo*)pData;
+ Index *pIndex;
+ int i, c;
+ unsigned int v;
+ const char *z;
+
+ assert( argc==2 );
+ if( argv==0 || argv[0]==0 || argv[1]==0 ){
+ return 0;
+ }
+ pIndex = sqlite3FindIndex(pInfo->db, argv[0], pInfo->zDatabase);
+ if( pIndex==0 ){
+ return 0;
+ }
+ z = argv[1];
+ for(i=0; *z && i<=pIndex->nColumn; i++){
+ v = 0;
+ while( (c=z[0])>='0' && c<='9' ){
+ v = v*10 + c - '0';
+ z++;
+ }
+ pIndex->aiRowEst[i] = v;
+ if( *z==' ' ) z++;
+ }
+ return 0;
+}
+
+/*
+** Load the content of the sqlite_stat1 table into the index hash tables.
+*/
+SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
+ analysisInfo sInfo;
+ HashElem *i;
+ char *zSql;
+ int rc;
+
+ assert( iDb>=0 && iDb<db->nDb );
+ assert( db->aDb[iDb].pBt!=0 );
+ assert( sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
+
+ /* Clear any prior statistics */
+ for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
+ Index *pIdx = sqliteHashData(i);
+ sqlite3DefaultRowEst(pIdx);
+ }
+
+ /* Check to make sure the sqlite_stat1 table existss */
+ sInfo.db = db;
+ sInfo.zDatabase = db->aDb[iDb].zName;
+ if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)==0 ){
+ return SQLITE_ERROR;
+ }
+
+
+ /* Load new statistics out of the sqlite_stat1 table */
+ zSql = sqlite3MPrintf(db, "SELECT idx, stat FROM %Q.sqlite_stat1",
+ sInfo.zDatabase);
+ (void)sqlite3SafetyOff(db);
+ rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
+ (void)sqlite3SafetyOn(db);
+ sqlite3_free(zSql);
+ return rc;
+}
+
+
+#endif /* SQLITE_OMIT_ANALYZE */
+
+/************** End of analyze.c *********************************************/
+/************** Begin file attach.c ******************************************/
+/*
+** 2003 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the ATTACH and DETACH commands.
+**
+** $Id: attach.c,v 1.75 2008/04/17 17:02:01 drh Exp $
+*/
+
+#ifndef SQLITE_OMIT_ATTACH
+/*
+** Resolve an expression that was part of an ATTACH or DETACH statement. This
+** is slightly different from resolving a normal SQL expression, because simple
+** identifiers are treated as strings, not possible column names or aliases.
+**
+** i.e. if the parser sees:
+**
+** ATTACH DATABASE abc AS def
+**
+** it treats the two expressions as literal strings 'abc' and 'def' instead of
+** looking for columns of the same name.
+**
+** This only applies to the root node of pExpr, so the statement:
+**
+** ATTACH DATABASE abc||def AS 'db2'
+**
+** will fail because neither abc or def can be resolved.
+*/
+static int resolveAttachExpr(NameContext *pName, Expr *pExpr)
+{
+ int rc = SQLITE_OK;
+ if( pExpr ){
+ if( pExpr->op!=TK_ID ){
+ rc = sqlite3ExprResolveNames(pName, pExpr);
+ if( rc==SQLITE_OK && !sqlite3ExprIsConstant(pExpr) ){
+ sqlite3ErrorMsg(pName->pParse, "invalid name: \"%T\"", &pExpr->span);
+ return SQLITE_ERROR;
+ }
+ }else{
+ pExpr->op = TK_STRING;
+ }
+ }
+ return rc;
+}
+
+/*
+** An SQL user-function registered to do the work of an ATTACH statement. The
+** three arguments to the function come directly from an attach statement:
+**
+** ATTACH DATABASE x AS y KEY z
+**
+** SELECT sqlite_attach(x, y, z)
+**
+** If the optional "KEY z" syntax is omitted, an SQL NULL is passed as the
+** third argument.
+*/
+static void attachFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int i;
+ int rc = 0;
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ const char *zName;
+ const char *zFile;
+ Db *aNew;
+ char *zErrDyn = 0;
+ char zErr[128];
+
+ zFile = (const char *)sqlite3_value_text(argv[0]);
+ zName = (const char *)sqlite3_value_text(argv[1]);
+ if( zFile==0 ) zFile = "";
+ if( zName==0 ) zName = "";
+
+ /* Check for the following errors:
+ **
+ ** * Too many attached databases,
+ ** * Transaction currently open
+ ** * Specified database name already being used.
+ */
+ if( db->nDb>=db->aLimit[SQLITE_LIMIT_ATTACHED]+2 ){
+ sqlite3_snprintf(
+ sizeof(zErr), zErr, "too many attached databases - max %d",
+ db->aLimit[SQLITE_LIMIT_ATTACHED]
+ );
+ goto attach_error;
+ }
+ if( !db->autoCommit ){
+ sqlite3_snprintf(sizeof(zErr), zErr,
+ "cannot ATTACH database within transaction");
+ goto attach_error;
+ }
+ for(i=0; i<db->nDb; i++){
+ char *z = db->aDb[i].zName;
+ if( z && zName && sqlite3StrICmp(z, zName)==0 ){
+ sqlite3_snprintf(sizeof(zErr), zErr,
+ "database %s is already in use", zName);
+ goto attach_error;
+ }
+ }
+
+ /* Allocate the new entry in the db->aDb[] array and initialise the schema
+ ** hash tables.
+ */
+ if( db->aDb==db->aDbStatic ){
+ aNew = sqlite3_malloc( sizeof(db->aDb[0])*3 );
+ if( aNew==0 ){
+ db->mallocFailed = 1;
+ return;
+ }
+ memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
+ }else{
+ aNew = sqlite3_realloc(db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
+ if( aNew==0 ){
+ db->mallocFailed = 1;
+ return;
+ }
+ }
+ db->aDb = aNew;
+ aNew = &db->aDb[db->nDb++];
+ memset(aNew, 0, sizeof(*aNew));
+
+ /* Open the database file. If the btree is successfully opened, use
+ ** it to obtain the database schema. At this point the schema may
+ ** or may not be initialised.
+ */
+ rc = sqlite3BtreeFactory(db, zFile, 0, SQLITE_DEFAULT_CACHE_SIZE,
+ db->openFlags | SQLITE_OPEN_MAIN_DB,
+ &aNew->pBt);
+ if( rc==SQLITE_OK ){
+ Pager *pPager;
+ aNew->pSchema = sqlite3SchemaGet(db, aNew->pBt);
+ if( !aNew->pSchema ){
+ rc = SQLITE_NOMEM;
+ }else if( aNew->pSchema->file_format && aNew->pSchema->enc!=ENC(db) ){
+ sqlite3_snprintf(sizeof(zErr), zErr,
+ "attached databases must use the same text encoding as main database");
+ goto attach_error;
+ }
+ pPager = sqlite3BtreePager(aNew->pBt);
+ sqlite3PagerLockingMode(pPager, db->dfltLockMode);
+ sqlite3PagerJournalMode(pPager, db->dfltJournalMode);
+ }
+ aNew->zName = sqlite3DbStrDup(db, zName);
+ aNew->safety_level = 3;
+
+#if SQLITE_HAS_CODEC
+ {
+ extern int sqlite3CodecAttach(sqlite3*, int, const void*, int);
+ extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*);
+ int nKey;
+ char *zKey;
+ int t = sqlite3_value_type(argv[2]);
+ switch( t ){
+ case SQLITE_INTEGER:
+ case SQLITE_FLOAT:
+ zErrDyn = sqlite3DbStrDup(db, "Invalid key value");
+ rc = SQLITE_ERROR;
+ break;
+
+ case SQLITE_TEXT:
+ case SQLITE_BLOB:
+ nKey = sqlite3_value_bytes(argv[2]);
+ zKey = (char *)sqlite3_value_blob(argv[2]);
+ sqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
+ break;
+
+ case SQLITE_NULL:
+ /* No key specified. Use the key from the main database */
+ sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey);
+ sqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
+ break;
+ }
+ }
+#endif
+
+ /* If the file was opened successfully, read the schema for the new database.
+ ** If this fails, or if opening the file failed, then close the file and
+ ** remove the entry from the db->aDb[] array. i.e. put everything back the way
+ ** we found it.
+ */
+ if( rc==SQLITE_OK ){
+ (void)sqlite3SafetyOn(db);
+ sqlite3BtreeEnterAll(db);
+ rc = sqlite3Init(db, &zErrDyn);
+ sqlite3BtreeLeaveAll(db);
+ (void)sqlite3SafetyOff(db);
+ }
+ if( rc ){
+ int iDb = db->nDb - 1;
+ assert( iDb>=2 );
+ if( db->aDb[iDb].pBt ){
+ sqlite3BtreeClose(db->aDb[iDb].pBt);
+ db->aDb[iDb].pBt = 0;
+ db->aDb[iDb].pSchema = 0;
+ }
+ sqlite3ResetInternalSchema(db, 0);
+ db->nDb = iDb;
+ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
+ db->mallocFailed = 1;
+ sqlite3_snprintf(sizeof(zErr),zErr, "out of memory");
+ }else{
+ sqlite3_snprintf(sizeof(zErr),zErr, "unable to open database: %s", zFile);
+ }
+ goto attach_error;
+ }
+
+ return;
+
+attach_error:
+ /* Return an error if we get here */
+ if( zErrDyn ){
+ sqlite3_result_error(context, zErrDyn, -1);
+ sqlite3_free(zErrDyn);
+ }else{
+ zErr[sizeof(zErr)-1] = 0;
+ sqlite3_result_error(context, zErr, -1);
+ }
+ if( rc ) sqlite3_result_error_code(context, rc);
+}
+
+/*
+** An SQL user-function registered to do the work of an DETACH statement. The
+** three arguments to the function come directly from a detach statement:
+**
+** DETACH DATABASE x
+**
+** SELECT sqlite_detach(x)
+*/
+static void detachFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zName = (const char *)sqlite3_value_text(argv[0]);
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ int i;
+ Db *pDb = 0;
+ char zErr[128];
+
+ if( zName==0 ) zName = "";
+ for(i=0; i<db->nDb; i++){
+ pDb = &db->aDb[i];
+ if( pDb->pBt==0 ) continue;
+ if( sqlite3StrICmp(pDb->zName, zName)==0 ) break;
+ }
+
+ if( i>=db->nDb ){
+ sqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName);
+ goto detach_error;
+ }
+ if( i<2 ){
+ sqlite3_snprintf(sizeof(zErr),zErr, "cannot detach database %s", zName);
+ goto detach_error;
+ }
+ if( !db->autoCommit ){
+ sqlite3_snprintf(sizeof(zErr), zErr,
+ "cannot DETACH database within transaction");
+ goto detach_error;
+ }
+ if( sqlite3BtreeIsInReadTrans(pDb->pBt) ){
+ sqlite3_snprintf(sizeof(zErr),zErr, "database %s is locked", zName);
+ goto detach_error;
+ }
+
+ sqlite3BtreeClose(pDb->pBt);
+ pDb->pBt = 0;
+ pDb->pSchema = 0;
+ sqlite3ResetInternalSchema(db, 0);
+ return;
+
+detach_error:
+ sqlite3_result_error(context, zErr, -1);
+}
+
+/*
+** This procedure generates VDBE code for a single invocation of either the
+** sqlite_detach() or sqlite_attach() SQL user functions.
+*/
+static void codeAttach(
+ Parse *pParse, /* The parser context */
+ int type, /* Either SQLITE_ATTACH or SQLITE_DETACH */
+ const char *zFunc, /* Either "sqlite_attach" or "sqlite_detach */
+ int nFunc, /* Number of args to pass to zFunc */
+ Expr *pAuthArg, /* Expression to pass to authorization callback */
+ Expr *pFilename, /* Name of database file */
+ Expr *pDbname, /* Name of the database to use internally */
+ Expr *pKey /* Database key for encryption extension */
+){
+ int rc;
+ NameContext sName;
+ Vdbe *v;
+ FuncDef *pFunc;
+ sqlite3* db = pParse->db;
+ int regArgs;
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ assert( db->mallocFailed || pAuthArg );
+ if( pAuthArg ){
+ char *zAuthArg = sqlite3NameFromToken(db, &pAuthArg->span);
+ if( !zAuthArg ){
+ goto attach_end;
+ }
+ rc = sqlite3AuthCheck(pParse, type, zAuthArg, 0, 0);
+ sqlite3_free(zAuthArg);
+ if(rc!=SQLITE_OK ){
+ goto attach_end;
+ }
+ }
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+
+ memset(&sName, 0, sizeof(NameContext));
+ sName.pParse = pParse;
+
+ if(
+ SQLITE_OK!=(rc = resolveAttachExpr(&sName, pFilename)) ||
+ SQLITE_OK!=(rc = resolveAttachExpr(&sName, pDbname)) ||
+ SQLITE_OK!=(rc = resolveAttachExpr(&sName, pKey))
+ ){
+ pParse->nErr++;
+ goto attach_end;
+ }
+
+ v = sqlite3GetVdbe(pParse);
+ regArgs = sqlite3GetTempRange(pParse, 4);
+ sqlite3ExprCode(pParse, pFilename, regArgs);
+ sqlite3ExprCode(pParse, pDbname, regArgs+1);
+ sqlite3ExprCode(pParse, pKey, regArgs+2);
+
+ assert( v || db->mallocFailed );
+ if( v ){
+ sqlite3VdbeAddOp3(v, OP_Function, 0, regArgs+3-nFunc, regArgs+3);
+ sqlite3VdbeChangeP5(v, nFunc);
+ pFunc = sqlite3FindFunction(db, zFunc, strlen(zFunc), nFunc, SQLITE_UTF8,0);
+ sqlite3VdbeChangeP4(v, -1, (char *)pFunc, P4_FUNCDEF);
+
+ /* Code an OP_Expire. For an ATTACH statement, set P1 to true (expire this
+ ** statement only). For DETACH, set it to false (expire all existing
+ ** statements).
+ */
+ sqlite3VdbeAddOp1(v, OP_Expire, (type==SQLITE_ATTACH));
+ }
+
+attach_end:
+ sqlite3ExprDelete(pFilename);
+ sqlite3ExprDelete(pDbname);
+ sqlite3ExprDelete(pKey);
+}
+
+/*
+** Called by the parser to compile a DETACH statement.
+**
+** DETACH pDbname
+*/
+SQLITE_PRIVATE void sqlite3Detach(Parse *pParse, Expr *pDbname){
+ codeAttach(pParse, SQLITE_DETACH, "sqlite_detach", 1, pDbname, 0, 0, pDbname);
+}
+
+/*
+** Called by the parser to compile an ATTACH statement.
+**
+** ATTACH p AS pDbname KEY pKey
+*/
+SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){
+ codeAttach(pParse, SQLITE_ATTACH, "sqlite_attach", 3, p, p, pDbname, pKey);
+}
+#endif /* SQLITE_OMIT_ATTACH */
+
+/*
+** Register the functions sqlite_attach and sqlite_detach.
+*/
+SQLITE_PRIVATE void sqlite3AttachFunctions(sqlite3 *db){
+#ifndef SQLITE_OMIT_ATTACH
+ static const int enc = SQLITE_UTF8;
+ sqlite3CreateFunc(db, "sqlite_attach", 3, enc, 0, attachFunc, 0, 0);
+ sqlite3CreateFunc(db, "sqlite_detach", 1, enc, 0, detachFunc, 0, 0);
+#endif
+}
+
+/*
+** Initialize a DbFixer structure. This routine must be called prior
+** to passing the structure to one of the sqliteFixAAAA() routines below.
+**
+** The return value indicates whether or not fixation is required. TRUE
+** means we do need to fix the database references, FALSE means we do not.
+*/
+SQLITE_PRIVATE int sqlite3FixInit(
+ DbFixer *pFix, /* The fixer to be initialized */
+ Parse *pParse, /* Error messages will be written here */
+ int iDb, /* This is the database that must be used */
+ const char *zType, /* "view", "trigger", or "index" */
+ const Token *pName /* Name of the view, trigger, or index */
+){
+ sqlite3 *db;
+
+ if( iDb<0 || iDb==1 ) return 0;
+ db = pParse->db;
+ assert( db->nDb>iDb );
+ pFix->pParse = pParse;
+ pFix->zDb = db->aDb[iDb].zName;
+ pFix->zType = zType;
+ pFix->pName = pName;
+ return 1;
+}
+
+/*
+** The following set of routines walk through the parse tree and assign
+** a specific database to all table references where the database name
+** was left unspecified in the original SQL statement. The pFix structure
+** must have been initialized by a prior call to sqlite3FixInit().
+**
+** These routines are used to make sure that an index, trigger, or
+** view in one database does not refer to objects in a different database.
+** (Exception: indices, triggers, and views in the TEMP database are
+** allowed to refer to anything.) If a reference is explicitly made
+** to an object in a different database, an error message is added to
+** pParse->zErrMsg and these routines return non-zero. If everything
+** checks out, these routines return 0.
+*/
+SQLITE_PRIVATE int sqlite3FixSrcList(
+ DbFixer *pFix, /* Context of the fixation */
+ SrcList *pList /* The Source list to check and modify */
+){
+ int i;
+ const char *zDb;
+ struct SrcList_item *pItem;
+
+ if( pList==0 ) return 0;
+ zDb = pFix->zDb;
+ for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
+ if( pItem->zDatabase==0 ){
+ pItem->zDatabase = sqlite3DbStrDup(pFix->pParse->db, zDb);
+ }else if( sqlite3StrICmp(pItem->zDatabase,zDb)!=0 ){
+ sqlite3ErrorMsg(pFix->pParse,
+ "%s %T cannot reference objects in database %s",
+ pFix->zType, pFix->pName, pItem->zDatabase);
+ return 1;
+ }
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
+ if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1;
+ if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1;
+#endif
+ }
+ return 0;
+}
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
+SQLITE_PRIVATE int sqlite3FixSelect(
+ DbFixer *pFix, /* Context of the fixation */
+ Select *pSelect /* The SELECT statement to be fixed to one database */
+){
+ while( pSelect ){
+ if( sqlite3FixExprList(pFix, pSelect->pEList) ){
+ return 1;
+ }
+ if( sqlite3FixSrcList(pFix, pSelect->pSrc) ){
+ return 1;
+ }
+ if( sqlite3FixExpr(pFix, pSelect->pWhere) ){
+ return 1;
+ }
+ if( sqlite3FixExpr(pFix, pSelect->pHaving) ){
+ return 1;
+ }
+ pSelect = pSelect->pPrior;
+ }
+ return 0;
+}
+SQLITE_PRIVATE int sqlite3FixExpr(
+ DbFixer *pFix, /* Context of the fixation */
+ Expr *pExpr /* The expression to be fixed to one database */
+){
+ while( pExpr ){
+ if( sqlite3FixSelect(pFix, pExpr->pSelect) ){
+ return 1;
+ }
+ if( sqlite3FixExprList(pFix, pExpr->pList) ){
+ return 1;
+ }
+ if( sqlite3FixExpr(pFix, pExpr->pRight) ){
+ return 1;
+ }
+ pExpr = pExpr->pLeft;
+ }
+ return 0;
+}
+SQLITE_PRIVATE int sqlite3FixExprList(
+ DbFixer *pFix, /* Context of the fixation */
+ ExprList *pList /* The expression to be fixed to one database */
+){
+ int i;
+ struct ExprList_item *pItem;
+ if( pList==0 ) return 0;
+ for(i=0, pItem=pList->a; i<pList->nExpr; i++, pItem++){
+ if( sqlite3FixExpr(pFix, pItem->pExpr) ){
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif
+
+#ifndef SQLITE_OMIT_TRIGGER
+SQLITE_PRIVATE int sqlite3FixTriggerStep(
+ DbFixer *pFix, /* Context of the fixation */
+ TriggerStep *pStep /* The trigger step be fixed to one database */
+){
+ while( pStep ){
+ if( sqlite3FixSelect(pFix, pStep->pSelect) ){
+ return 1;
+ }
+ if( sqlite3FixExpr(pFix, pStep->pWhere) ){
+ return 1;
+ }
+ if( sqlite3FixExprList(pFix, pStep->pExprList) ){
+ return 1;
+ }
+ pStep = pStep->pNext;
+ }
+ return 0;
+}
+#endif
+
+/************** End of attach.c **********************************************/
+/************** Begin file auth.c ********************************************/
+/*
+** 2003 January 11
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the sqlite3_set_authorizer()
+** API. This facility is an optional feature of the library. Embedded
+** systems that do not need this facility may omit it by recompiling
+** the library with -DSQLITE_OMIT_AUTHORIZATION=1
+**
+** $Id: auth.c,v 1.29 2007/09/18 15:55:07 drh Exp $
+*/
+
+/*
+** All of the code in this file may be omitted by defining a single
+** macro.
+*/
+#ifndef SQLITE_OMIT_AUTHORIZATION
+
+/*
+** Set or clear the access authorization function.
+**
+** The access authorization function is be called during the compilation
+** phase to verify that the user has read and/or write access permission on
+** various fields of the database. The first argument to the auth function
+** is a copy of the 3rd argument to this routine. The second argument
+** to the auth function is one of these constants:
+**
+** SQLITE_CREATE_INDEX
+** SQLITE_CREATE_TABLE
+** SQLITE_CREATE_TEMP_INDEX
+** SQLITE_CREATE_TEMP_TABLE
+** SQLITE_CREATE_TEMP_TRIGGER
+** SQLITE_CREATE_TEMP_VIEW
+** SQLITE_CREATE_TRIGGER
+** SQLITE_CREATE_VIEW
+** SQLITE_DELETE
+** SQLITE_DROP_INDEX
+** SQLITE_DROP_TABLE
+** SQLITE_DROP_TEMP_INDEX
+** SQLITE_DROP_TEMP_TABLE
+** SQLITE_DROP_TEMP_TRIGGER
+** SQLITE_DROP_TEMP_VIEW
+** SQLITE_DROP_TRIGGER
+** SQLITE_DROP_VIEW
+** SQLITE_INSERT
+** SQLITE_PRAGMA
+** SQLITE_READ
+** SQLITE_SELECT
+** SQLITE_TRANSACTION
+** SQLITE_UPDATE
+**
+** The third and fourth arguments to the auth function are the name of
+** the table and the column that are being accessed. The auth function
+** should return either SQLITE_OK, SQLITE_DENY, or SQLITE_IGNORE. If
+** SQLITE_OK is returned, it means that access is allowed. SQLITE_DENY
+** means that the SQL statement will never-run - the sqlite3_exec() call
+** will return with an error. SQLITE_IGNORE means that the SQL statement
+** should run but attempts to read the specified column will return NULL
+** and attempts to write the column will be ignored.
+**
+** Setting the auth function to NULL disables this hook. The default
+** setting of the auth function is NULL.
+*/
+SQLITE_API int sqlite3_set_authorizer(
+ sqlite3 *db,
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pArg
+){
+ sqlite3_mutex_enter(db->mutex);
+ db->xAuth = xAuth;
+ db->pAuthArg = pArg;
+ sqlite3ExpirePreparedStatements(db);
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_OK;
+}
+
+/*
+** Write an error message into pParse->zErrMsg that explains that the
+** user-supplied authorization function returned an illegal value.
+*/
+static void sqliteAuthBadReturnCode(Parse *pParse, int rc){
+ sqlite3ErrorMsg(pParse, "illegal return value (%d) from the "
+ "authorization function - should be SQLITE_OK, SQLITE_IGNORE, "
+ "or SQLITE_DENY", rc);
+ pParse->rc = SQLITE_ERROR;
+}
+
+/*
+** The pExpr should be a TK_COLUMN expression. The table referred to
+** is in pTabList or else it is the NEW or OLD table of a trigger.
+** Check to see if it is OK to read this particular column.
+**
+** If the auth function returns SQLITE_IGNORE, change the TK_COLUMN
+** instruction into a TK_NULL. If the auth function returns SQLITE_DENY,
+** then generate an error.
+*/
+SQLITE_PRIVATE void sqlite3AuthRead(
+ Parse *pParse, /* The parser context */
+ Expr *pExpr, /* The expression to check authorization on */
+ Schema *pSchema, /* The schema of the expression */
+ SrcList *pTabList /* All table that pExpr might refer to */
+){
+ sqlite3 *db = pParse->db;
+ int rc;
+ Table *pTab = 0; /* The table being read */
+ const char *zCol; /* Name of the column of the table */
+ int iSrc; /* Index in pTabList->a[] of table being read */
+ const char *zDBase; /* Name of database being accessed */
+ TriggerStack *pStack; /* The stack of current triggers */
+ int iDb; /* The index of the database the expression refers to */
+
+ if( db->xAuth==0 ) return;
+ if( pExpr->op!=TK_COLUMN ) return;
+ iDb = sqlite3SchemaToIndex(pParse->db, pSchema);
+ if( iDb<0 ){
+ /* An attempt to read a column out of a subquery or other
+ ** temporary table. */
+ return;
+ }
+ for(iSrc=0; pTabList && iSrc<pTabList->nSrc; iSrc++){
+ if( pExpr->iTable==pTabList->a[iSrc].iCursor ) break;
+ }
+ if( iSrc>=0 && pTabList && iSrc<pTabList->nSrc ){
+ pTab = pTabList->a[iSrc].pTab;
+ }else if( (pStack = pParse->trigStack)!=0 ){
+ /* This must be an attempt to read the NEW or OLD pseudo-tables
+ ** of a trigger.
+ */
+ assert( pExpr->iTable==pStack->newIdx || pExpr->iTable==pStack->oldIdx );
+ pTab = pStack->pTab;
+ }
+ if( pTab==0 ) return;
+ if( pExpr->iColumn>=0 ){
+ assert( pExpr->iColumn<pTab->nCol );
+ zCol = pTab->aCol[pExpr->iColumn].zName;
+ }else if( pTab->iPKey>=0 ){
+ assert( pTab->iPKey<pTab->nCol );
+ zCol = pTab->aCol[pTab->iPKey].zName;
+ }else{
+ zCol = "ROWID";
+ }
+ assert( iDb>=0 && iDb<db->nDb );
+ zDBase = db->aDb[iDb].zName;
+ rc = db->xAuth(db->pAuthArg, SQLITE_READ, pTab->zName, zCol, zDBase,
+ pParse->zAuthContext);
+ if( rc==SQLITE_IGNORE ){
+ pExpr->op = TK_NULL;
+ }else if( rc==SQLITE_DENY ){
+ if( db->nDb>2 || iDb!=0 ){
+ sqlite3ErrorMsg(pParse, "access to %s.%s.%s is prohibited",
+ zDBase, pTab->zName, zCol);
+ }else{
+ sqlite3ErrorMsg(pParse, "access to %s.%s is prohibited",pTab->zName,zCol);
+ }
+ pParse->rc = SQLITE_AUTH;
+ }else if( rc!=SQLITE_OK ){
+ sqliteAuthBadReturnCode(pParse, rc);
+ }
+}
+
+/*
+** Do an authorization check using the code and arguments given. Return
+** either SQLITE_OK (zero) or SQLITE_IGNORE or SQLITE_DENY. If SQLITE_DENY
+** is returned, then the error count and error message in pParse are
+** modified appropriately.
+*/
+SQLITE_PRIVATE int sqlite3AuthCheck(
+ Parse *pParse,
+ int code,
+ const char *zArg1,
+ const char *zArg2,
+ const char *zArg3
+){
+ sqlite3 *db = pParse->db;
+ int rc;
+
+ /* Don't do any authorization checks if the database is initialising
+ ** or if the parser is being invoked from within sqlite3_declare_vtab.
+ */
+ if( db->init.busy || IN_DECLARE_VTAB ){
+ return SQLITE_OK;
+ }
+
+ if( db->xAuth==0 ){
+ return SQLITE_OK;
+ }
+ rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext);
+ if( rc==SQLITE_DENY ){
+ sqlite3ErrorMsg(pParse, "not authorized");
+ pParse->rc = SQLITE_AUTH;
+ }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){
+ rc = SQLITE_DENY;
+ sqliteAuthBadReturnCode(pParse, rc);
+ }
+ return rc;
+}
+
+/*
+** Push an authorization context. After this routine is called, the
+** zArg3 argument to authorization callbacks will be zContext until
+** popped. Or if pParse==0, this routine is a no-op.
+*/
+SQLITE_PRIVATE void sqlite3AuthContextPush(
+ Parse *pParse,
+ AuthContext *pContext,
+ const char *zContext
+){
+ pContext->pParse = pParse;
+ if( pParse ){
+ pContext->zAuthContext = pParse->zAuthContext;
+ pParse->zAuthContext = zContext;
+ }
+}
+
+/*
+** Pop an authorization context that was previously pushed
+** by sqlite3AuthContextPush
+*/
+SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext *pContext){
+ if( pContext->pParse ){
+ pContext->pParse->zAuthContext = pContext->zAuthContext;
+ pContext->pParse = 0;
+ }
+}
+
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+
+/************** End of auth.c ************************************************/
+/************** Begin file build.c *******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the SQLite parser
+** when syntax rules are reduced. The routines in this file handle the
+** following kinds of SQL syntax:
+**
+** CREATE TABLE
+** DROP TABLE
+** CREATE INDEX
+** DROP INDEX
+** creating ID lists
+** BEGIN TRANSACTION
+** COMMIT
+** ROLLBACK
+**
+** $Id: build.c,v 1.484 2008/05/01 17:16:53 drh Exp $
+*/
+
+/*
+** This routine is called when a new SQL statement is beginning to
+** be parsed. Initialize the pParse structure as needed.
+*/
+SQLITE_PRIVATE void sqlite3BeginParse(Parse *pParse, int explainFlag){
+ pParse->explain = explainFlag;
+ pParse->nVar = 0;
+}
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** The TableLock structure is only used by the sqlite3TableLock() and
+** codeTableLocks() functions.
+*/
+struct TableLock {
+ int iDb; /* The database containing the table to be locked */
+ int iTab; /* The root page of the table to be locked */
+ u8 isWriteLock; /* True for write lock. False for a read lock */
+ const char *zName; /* Name of the table */
+};
+
+/*
+** Record the fact that we want to lock a table at run-time.
+**
+** The table to be locked has root page iTab and is found in database iDb.
+** A read or a write lock can be taken depending on isWritelock.
+**
+** This routine just records the fact that the lock is desired. The
+** code to make the lock occur is generated by a later call to
+** codeTableLocks() which occurs during sqlite3FinishCoding().
+*/
+SQLITE_PRIVATE void sqlite3TableLock(
+ Parse *pParse, /* Parsing context */
+ int iDb, /* Index of the database containing the table to lock */
+ int iTab, /* Root page number of the table to be locked */
+ u8 isWriteLock, /* True for a write lock */
+ const char *zName /* Name of the table to be locked */
+){
+ int i;
+ int nBytes;
+ TableLock *p;
+
+ if( iDb<0 ){
+ return;
+ }
+
+ for(i=0; i<pParse->nTableLock; i++){
+ p = &pParse->aTableLock[i];
+ if( p->iDb==iDb && p->iTab==iTab ){
+ p->isWriteLock = (p->isWriteLock || isWriteLock);
+ return;
+ }
+ }
+
+ nBytes = sizeof(TableLock) * (pParse->nTableLock+1);
+ pParse->aTableLock =
+ sqlite3DbReallocOrFree(pParse->db, pParse->aTableLock, nBytes);
+ if( pParse->aTableLock ){
+ p = &pParse->aTableLock[pParse->nTableLock++];
+ p->iDb = iDb;
+ p->iTab = iTab;
+ p->isWriteLock = isWriteLock;
+ p->zName = zName;
+ }else{
+ pParse->nTableLock = 0;
+ pParse->db->mallocFailed = 1;
+ }
+}
+
+/*
+** Code an OP_TableLock instruction for each table locked by the
+** statement (configured by calls to sqlite3TableLock()).
+*/
+static void codeTableLocks(Parse *pParse){
+ int i;
+ Vdbe *pVdbe;
+
+ if( 0==(pVdbe = sqlite3GetVdbe(pParse)) ){
+ return;
+ }
+
+ for(i=0; i<pParse->nTableLock; i++){
+ TableLock *p = &pParse->aTableLock[i];
+ int p1 = p->iDb;
+ sqlite3VdbeAddOp4(pVdbe, OP_TableLock, p1, p->iTab, p->isWriteLock,
+ p->zName, P4_STATIC);
+ }
+}
+#else
+ #define codeTableLocks(x)
+#endif
+
+/*
+** This routine is called after a single SQL statement has been
+** parsed and a VDBE program to execute that statement has been
+** prepared. This routine puts the finishing touches on the
+** VDBE program and resets the pParse structure for the next
+** parse.
+**
+** Note that if an error occurred, it might be the case that
+** no VDBE code was generated.
+*/
+SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
+ sqlite3 *db;
+ Vdbe *v;
+
+ db = pParse->db;
+ if( db->mallocFailed ) return;
+ if( pParse->nested ) return;
+ if( pParse->nErr ) return;
+ if( !pParse->pVdbe ){
+ if( pParse->rc==SQLITE_OK && pParse->nErr ){
+ pParse->rc = SQLITE_ERROR;
+ return;
+ }
+ }
+
+ /* Begin by generating some termination code at the end of the
+ ** vdbe program
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp0(v, OP_Halt);
+
+ /* The cookie mask contains one bit for each database file open.
+ ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are
+ ** set for each database that is used. Generate code to start a
+ ** transaction on each used database and to verify the schema cookie
+ ** on each used database.
+ */
+ if( pParse->cookieGoto>0 ){
+ u32 mask;
+ int iDb;
+ sqlite3VdbeJumpHere(v, pParse->cookieGoto-1);
+ for(iDb=0, mask=1; iDb<db->nDb; mask<<=1, iDb++){
+ if( (mask & pParse->cookieMask)==0 ) continue;
+ sqlite3VdbeUsesBtree(v, iDb);
+ sqlite3VdbeAddOp2(v,OP_Transaction, iDb, (mask & pParse->writeMask)!=0);
+ sqlite3VdbeAddOp2(v,OP_VerifyCookie, iDb, pParse->cookieValue[iDb]);
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ {
+ int i;
+ for(i=0; i<pParse->nVtabLock; i++){
+ char *vtab = (char *)pParse->apVtabLock[i]->pVtab;
+ sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB);
+ }
+ pParse->nVtabLock = 0;
+ }
+#endif
+
+ /* Once all the cookies have been verified and transactions opened,
+ ** obtain the required table-locks. This is a no-op unless the
+ ** shared-cache feature is enabled.
+ */
+ codeTableLocks(pParse);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, pParse->cookieGoto);
+ }
+
+#ifndef SQLITE_OMIT_TRACE
+ if( !db->init.busy ){
+ /* Change the P4 argument of the first opcode (which will always be
+ ** an OP_Trace) to be the complete text of the current SQL statement.
+ */
+ VdbeOp *pOp = sqlite3VdbeGetOp(v, 0);
+ if( pOp && pOp->opcode==OP_Trace ){
+ sqlite3VdbeChangeP4(v, 0, pParse->zSql, pParse->zTail-pParse->zSql);
+ }
+ }
+#endif /* SQLITE_OMIT_TRACE */
+ }
+
+
+ /* Get the VDBE program ready for execution
+ */
+ if( v && pParse->nErr==0 && !db->mallocFailed ){
+#ifdef SQLITE_DEBUG
+ FILE *trace = (db->flags & SQLITE_VdbeTrace)!=0 ? stdout : 0;
+ sqlite3VdbeTrace(v, trace);
+#endif
+ assert( pParse->disableColCache==0 ); /* Disables and re-enables match */
+ sqlite3VdbeMakeReady(v, pParse->nVar, pParse->nMem+3,
+ pParse->nTab+3, pParse->explain);
+ pParse->rc = SQLITE_DONE;
+ pParse->colNamesSet = 0;
+ }else if( pParse->rc==SQLITE_OK ){
+ pParse->rc = SQLITE_ERROR;
+ }
+ pParse->nTab = 0;
+ pParse->nMem = 0;
+ pParse->nSet = 0;
+ pParse->nVar = 0;
+ pParse->cookieMask = 0;
+ pParse->cookieGoto = 0;
+}
+
+/*
+** Run the parser and code generator recursively in order to generate
+** code for the SQL statement given onto the end of the pParse context
+** currently under construction. When the parser is run recursively
+** this way, the final OP_Halt is not appended and other initialization
+** and finalization steps are omitted because those are handling by the
+** outermost parser.
+**
+** Not everything is nestable. This facility is designed to permit
+** INSERT, UPDATE, and DELETE operations against SQLITE_MASTER. Use
+** care if you decide to try to use this routine for some other purposes.
+*/
+SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){
+ va_list ap;
+ char *zSql;
+# define SAVE_SZ (sizeof(Parse) - offsetof(Parse,nVar))
+ char saveBuf[SAVE_SZ];
+
+ if( pParse->nErr ) return;
+ assert( pParse->nested<10 ); /* Nesting should only be of limited depth */
+ va_start(ap, zFormat);
+ zSql = sqlite3VMPrintf(pParse->db, zFormat, ap);
+ va_end(ap);
+ if( zSql==0 ){
+ pParse->db->mallocFailed = 1;
+ return; /* A malloc must have failed */
+ }
+ pParse->nested++;
+ memcpy(saveBuf, &pParse->nVar, SAVE_SZ);
+ memset(&pParse->nVar, 0, SAVE_SZ);
+ sqlite3RunParser(pParse, zSql, 0);
+ sqlite3_free(zSql);
+ memcpy(&pParse->nVar, saveBuf, SAVE_SZ);
+ pParse->nested--;
+}
+
+/*
+** Locate the in-memory structure that describes a particular database
+** table given the name of that table and (optionally) the name of the
+** database containing the table. Return NULL if not found.
+**
+** If zDatabase is 0, all databases are searched for the table and the
+** first matching table is returned. (No checking for duplicate table
+** names is done.) The search order is TEMP first, then MAIN, then any
+** auxiliary databases added using the ATTACH command.
+**
+** See also sqlite3LocateTable().
+*/
+SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){
+ Table *p = 0;
+ int i;
+ assert( zName!=0 );
+ for(i=OMIT_TEMPDB; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue;
+ p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName, strlen(zName)+1);
+ if( p ) break;
+ }
+ return p;
+}
+
+/*
+** Locate the in-memory structure that describes a particular database
+** table given the name of that table and (optionally) the name of the
+** database containing the table. Return NULL if not found. Also leave an
+** error message in pParse->zErrMsg.
+**
+** The difference between this routine and sqlite3FindTable() is that this
+** routine leaves an error message in pParse->zErrMsg where
+** sqlite3FindTable() does not.
+*/
+SQLITE_PRIVATE Table *sqlite3LocateTable(
+ Parse *pParse, /* context in which to report errors */
+ int isView, /* True if looking for a VIEW rather than a TABLE */
+ const char *zName, /* Name of the table we are looking for */
+ const char *zDbase /* Name of the database. Might be NULL */
+){
+ Table *p;
+
+ /* Read the database schema. If an error occurs, leave an error message
+ ** and code in pParse and return NULL. */
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ return 0;
+ }
+
+ p = sqlite3FindTable(pParse->db, zName, zDbase);
+ if( p==0 ){
+ const char *zMsg = isView ? "no such view" : "no such table";
+ if( zDbase ){
+ sqlite3ErrorMsg(pParse, "%s: %s.%s", zMsg, zDbase, zName);
+ }else{
+ sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName);
+ }
+ pParse->checkSchema = 1;
+ }
+ return p;
+}
+
+/*
+** Locate the in-memory structure that describes
+** a particular index given the name of that index
+** and the name of the database that contains the index.
+** Return NULL if not found.
+**
+** If zDatabase is 0, all databases are searched for the
+** table and the first matching index is returned. (No checking
+** for duplicate index names is done.) The search order is
+** TEMP first, then MAIN, then any auxiliary databases added
+** using the ATTACH command.
+*/
+SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){
+ Index *p = 0;
+ int i;
+ for(i=OMIT_TEMPDB; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ Schema *pSchema = db->aDb[j].pSchema;
+ if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zName) ) continue;
+ assert( pSchema || (j==1 && !db->aDb[1].pBt) );
+ if( pSchema ){
+ p = sqlite3HashFind(&pSchema->idxHash, zName, strlen(zName)+1);
+ }
+ if( p ) break;
+ }
+ return p;
+}
+
+/*
+** Reclaim the memory used by an index
+*/
+static void freeIndex(Index *p){
+ sqlite3_free(p->zColAff);
+ sqlite3_free(p);
+}
+
+/*
+** Remove the given index from the index hash table, and free
+** its memory structures.
+**
+** The index is removed from the database hash tables but
+** it is not unlinked from the Table that it indexes.
+** Unlinking from the Table must be done by the calling function.
+*/
+static void sqliteDeleteIndex(Index *p){
+ Index *pOld;
+ const char *zName = p->zName;
+
+ pOld = sqlite3HashInsert(&p->pSchema->idxHash, zName, strlen( zName)+1, 0);
+ assert( pOld==0 || pOld==p );
+ freeIndex(p);
+}
+
+/*
+** For the index called zIdxName which is found in the database iDb,
+** unlike that index from its Table then remove the index from
+** the index hash table and free all memory structures associated
+** with the index.
+*/
+SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){
+ Index *pIndex;
+ int len;
+ Hash *pHash = &db->aDb[iDb].pSchema->idxHash;
+
+ len = strlen(zIdxName);
+ pIndex = sqlite3HashInsert(pHash, zIdxName, len+1, 0);
+ if( pIndex ){
+ if( pIndex->pTable->pIndex==pIndex ){
+ pIndex->pTable->pIndex = pIndex->pNext;
+ }else{
+ Index *p;
+ for(p=pIndex->pTable->pIndex; p && p->pNext!=pIndex; p=p->pNext){}
+ if( p && p->pNext==pIndex ){
+ p->pNext = pIndex->pNext;
+ }
+ }
+ freeIndex(pIndex);
+ }
+ db->flags |= SQLITE_InternChanges;
+}
+
+/*
+** Erase all schema information from the in-memory hash tables of
+** a single database. This routine is called to reclaim memory
+** before the database closes. It is also called during a rollback
+** if there were schema changes during the transaction or if a
+** schema-cookie mismatch occurs.
+**
+** If iDb<=0 then reset the internal schema tables for all database
+** files. If iDb>=2 then reset the internal schema for only the
+** single file indicated.
+*/
+SQLITE_PRIVATE void sqlite3ResetInternalSchema(sqlite3 *db, int iDb){
+ int i, j;
+ assert( iDb>=0 && iDb<db->nDb );
+
+ if( iDb==0 ){
+ sqlite3BtreeEnterAll(db);
+ }
+ for(i=iDb; i<db->nDb; i++){
+ Db *pDb = &db->aDb[i];
+ if( pDb->pSchema ){
+ assert(i==1 || (pDb->pBt && sqlite3BtreeHoldsMutex(pDb->pBt)));
+ sqlite3SchemaFree(pDb->pSchema);
+ }
+ if( iDb>0 ) return;
+ }
+ assert( iDb==0 );
+ db->flags &= ~SQLITE_InternChanges;
+ sqlite3BtreeLeaveAll(db);
+
+ /* If one or more of the auxiliary database files has been closed,
+ ** then remove them from the auxiliary database list. We take the
+ ** opportunity to do this here since we have just deleted all of the
+ ** schema hash tables and therefore do not have to make any changes
+ ** to any of those tables.
+ */
+ for(i=0; i<db->nDb; i++){
+ struct Db *pDb = &db->aDb[i];
+ if( pDb->pBt==0 ){
+ if( pDb->pAux && pDb->xFreeAux ) pDb->xFreeAux(pDb->pAux);
+ pDb->pAux = 0;
+ }
+ }
+ for(i=j=2; i<db->nDb; i++){
+ struct Db *pDb = &db->aDb[i];
+ if( pDb->pBt==0 ){
+ sqlite3_free(pDb->zName);
+ pDb->zName = 0;
+ continue;
+ }
+ if( j<i ){
+ db->aDb[j] = db->aDb[i];
+ }
+ j++;
+ }
+ memset(&db->aDb[j], 0, (db->nDb-j)*sizeof(db->aDb[j]));
+ db->nDb = j;
+ if( db->nDb<=2 && db->aDb!=db->aDbStatic ){
+ memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0]));
+ sqlite3_free(db->aDb);
+ db->aDb = db->aDbStatic;
+ }
+}
+
+/*
+** This routine is called when a commit occurs.
+*/
+SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3 *db){
+ db->flags &= ~SQLITE_InternChanges;
+}
+
+/*
+** Clear the column names from a table or view.
+*/
+static void sqliteResetColumnNames(Table *pTable){
+ int i;
+ Column *pCol;
+ assert( pTable!=0 );
+ if( (pCol = pTable->aCol)!=0 ){
+ for(i=0; i<pTable->nCol; i++, pCol++){
+ sqlite3_free(pCol->zName);
+ sqlite3ExprDelete(pCol->pDflt);
+ sqlite3_free(pCol->zType);
+ sqlite3_free(pCol->zColl);
+ }
+ sqlite3_free(pTable->aCol);
+ }
+ pTable->aCol = 0;
+ pTable->nCol = 0;
+}
+
+/*
+** Remove the memory data structures associated with the given
+** Table. No changes are made to disk by this routine.
+**
+** This routine just deletes the data structure. It does not unlink
+** the table data structure from the hash table. Nor does it remove
+** foreign keys from the sqlite.aFKey hash table. But it does destroy
+** memory structures of the indices and foreign keys associated with
+** the table.
+*/
+SQLITE_PRIVATE void sqlite3DeleteTable(Table *pTable){
+ Index *pIndex, *pNext;
+ FKey *pFKey, *pNextFKey;
+
+ if( pTable==0 ) return;
+
+ /* Do not delete the table until the reference count reaches zero. */
+ pTable->nRef--;
+ if( pTable->nRef>0 ){
+ return;
+ }
+ assert( pTable->nRef==0 );
+
+ /* Delete all indices associated with this table
+ */
+ for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){
+ pNext = pIndex->pNext;
+ assert( pIndex->pSchema==pTable->pSchema );
+ sqliteDeleteIndex(pIndex);
+ }
+
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ /* Delete all foreign keys associated with this table. The keys
+ ** should have already been unlinked from the pSchema->aFKey hash table
+ */
+ for(pFKey=pTable->pFKey; pFKey; pFKey=pNextFKey){
+ pNextFKey = pFKey->pNextFrom;
+ assert( sqlite3HashFind(&pTable->pSchema->aFKey,
+ pFKey->zTo, strlen(pFKey->zTo)+1)!=pFKey );
+ sqlite3_free(pFKey);
+ }
+#endif
+
+ /* Delete the Table structure itself.
+ */
+ sqliteResetColumnNames(pTable);
+ sqlite3_free(pTable->zName);
+ sqlite3_free(pTable->zColAff);
+ sqlite3SelectDelete(pTable->pSelect);
+#ifndef SQLITE_OMIT_CHECK
+ sqlite3ExprDelete(pTable->pCheck);
+#endif
+ sqlite3VtabClear(pTable);
+ sqlite3_free(pTable);
+}
+
+/*
+** Unlink the given table from the hash tables and the delete the
+** table structure with all its indices and foreign keys.
+*/
+SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char *zTabName){
+ Table *p;
+ FKey *pF1, *pF2;
+ Db *pDb;
+
+ assert( db!=0 );
+ assert( iDb>=0 && iDb<db->nDb );
+ assert( zTabName && zTabName[0] );
+ pDb = &db->aDb[iDb];
+ p = sqlite3HashInsert(&pDb->pSchema->tblHash, zTabName, strlen(zTabName)+1,0);
+ if( p ){
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ for(pF1=p->pFKey; pF1; pF1=pF1->pNextFrom){
+ int nTo = strlen(pF1->zTo) + 1;
+ pF2 = sqlite3HashFind(&pDb->pSchema->aFKey, pF1->zTo, nTo);
+ if( pF2==pF1 ){
+ sqlite3HashInsert(&pDb->pSchema->aFKey, pF1->zTo, nTo, pF1->pNextTo);
+ }else{
+ while( pF2 && pF2->pNextTo!=pF1 ){ pF2=pF2->pNextTo; }
+ if( pF2 ){
+ pF2->pNextTo = pF1->pNextTo;
+ }
+ }
+ }
+#endif
+ sqlite3DeleteTable(p);
+ }
+ db->flags |= SQLITE_InternChanges;
+}
+
+/*
+** Given a token, return a string that consists of the text of that
+** token with any quotations removed. Space to hold the returned string
+** is obtained from sqliteMalloc() and must be freed by the calling
+** function.
+**
+** Tokens are often just pointers into the original SQL text and so
+** are not \000 terminated and are not persistent. The returned string
+** is \000 terminated and is persistent.
+*/
+SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){
+ char *zName;
+ if( pName ){
+ zName = sqlite3DbStrNDup(db, (char*)pName->z, pName->n);
+ sqlite3Dequote(zName);
+ }else{
+ zName = 0;
+ }
+ return zName;
+}
+
+/*
+** Open the sqlite_master table stored in database number iDb for
+** writing. The table is opened using cursor 0.
+*/
+SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *p, int iDb){
+ Vdbe *v = sqlite3GetVdbe(p);
+ sqlite3TableLock(p, iDb, MASTER_ROOT, 1, SCHEMA_TABLE(iDb));
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, 5);/* sqlite_master has 5 columns */
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, 0, MASTER_ROOT, iDb);
+}
+
+/*
+** The token *pName contains the name of a database (either "main" or
+** "temp" or the name of an attached db). This routine returns the
+** index of the named database in db->aDb[], or -1 if the named db
+** does not exist.
+*/
+SQLITE_PRIVATE int sqlite3FindDb(sqlite3 *db, Token *pName){
+ int i = -1; /* Database number */
+ int n; /* Number of characters in the name */
+ Db *pDb; /* A database whose name space is being searched */
+ char *zName; /* Name we are searching for */
+
+ zName = sqlite3NameFromToken(db, pName);
+ if( zName ){
+ n = strlen(zName);
+ for(i=(db->nDb-1), pDb=&db->aDb[i]; i>=0; i--, pDb--){
+ if( (!OMIT_TEMPDB || i!=1 ) && n==strlen(pDb->zName) &&
+ 0==sqlite3StrICmp(pDb->zName, zName) ){
+ break;
+ }
+ }
+ sqlite3_free(zName);
+ }
+ return i;
+}
+
+/* The table or view or trigger name is passed to this routine via tokens
+** pName1 and pName2. If the table name was fully qualified, for example:
+**
+** CREATE TABLE xxx.yyy (...);
+**
+** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if
+** the table name is not fully qualified, i.e.:
+**
+** CREATE TABLE yyy(...);
+**
+** Then pName1 is set to "yyy" and pName2 is "".
+**
+** This routine sets the *ppUnqual pointer to point at the token (pName1 or
+** pName2) that stores the unqualified table name. The index of the
+** database "xxx" is returned.
+*/
+SQLITE_PRIVATE int sqlite3TwoPartName(
+ Parse *pParse, /* Parsing and code generating context */
+ Token *pName1, /* The "xxx" in the name "xxx.yyy" or "xxx" */
+ Token *pName2, /* The "yyy" in the name "xxx.yyy" */
+ Token **pUnqual /* Write the unqualified object name here */
+){
+ int iDb; /* Database holding the object */
+ sqlite3 *db = pParse->db;
+
+ if( pName2 && pName2->n>0 ){
+ assert( !db->init.busy );
+ *pUnqual = pName2;
+ iDb = sqlite3FindDb(db, pName1);
+ if( iDb<0 ){
+ sqlite3ErrorMsg(pParse, "unknown database %T", pName1);
+ pParse->nErr++;
+ return -1;
+ }
+ }else{
+ assert( db->init.iDb==0 || db->init.busy );
+ iDb = db->init.iDb;
+ *pUnqual = pName1;
+ }
+ return iDb;
+}
+
+/*
+** This routine is used to check if the UTF-8 string zName is a legal
+** unqualified name for a new schema object (table, index, view or
+** trigger). All names are legal except those that begin with the string
+** "sqlite_" (in upper, lower or mixed case). This portion of the namespace
+** is reserved for internal use.
+*/
+SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *pParse, const char *zName){
+ if( !pParse->db->init.busy && pParse->nested==0
+ && (pParse->db->flags & SQLITE_WriteSchema)==0
+ && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){
+ sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", zName);
+ return SQLITE_ERROR;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Begin constructing a new table representation in memory. This is
+** the first of several action routines that get called in response
+** to a CREATE TABLE statement. In particular, this routine is called
+** after seeing tokens "CREATE" and "TABLE" and the table name. The isTemp
+** flag is true if the table should be stored in the auxiliary database
+** file instead of in the main database file. This is normally the case
+** when the "TEMP" or "TEMPORARY" keyword occurs in between
+** CREATE and TABLE.
+**
+** The new table record is initialized and put in pParse->pNewTable.
+** As more of the CREATE TABLE statement is parsed, additional action
+** routines will be called to add more information to this record.
+** At the end of the CREATE TABLE statement, the sqlite3EndTable() routine
+** is called to complete the construction of the new table record.
+*/
+SQLITE_PRIVATE void sqlite3StartTable(
+ Parse *pParse, /* Parser context */
+ Token *pName1, /* First part of the name of the table or view */
+ Token *pName2, /* Second part of the name of the table or view */
+ int isTemp, /* True if this is a TEMP table */
+ int isView, /* True if this is a VIEW */
+ int isVirtual, /* True if this is a VIRTUAL table */
+ int noErr /* Do nothing if table already exists */
+){
+ Table *pTable;
+ char *zName = 0; /* The name of the new table */
+ sqlite3 *db = pParse->db;
+ Vdbe *v;
+ int iDb; /* Database number to create the table in */
+ Token *pName; /* Unqualified name of the table to create */
+
+ /* The table or view name to create is passed to this routine via tokens
+ ** pName1 and pName2. If the table name was fully qualified, for example:
+ **
+ ** CREATE TABLE xxx.yyy (...);
+ **
+ ** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if
+ ** the table name is not fully qualified, i.e.:
+ **
+ ** CREATE TABLE yyy(...);
+ **
+ ** Then pName1 is set to "yyy" and pName2 is "".
+ **
+ ** The call below sets the pName pointer to point at the token (pName1 or
+ ** pName2) that stores the unqualified table name. The variable iDb is
+ ** set to the index of the database that the table or view is to be
+ ** created in.
+ */
+ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ if( iDb<0 ) return;
+ if( !OMIT_TEMPDB && isTemp && iDb>1 ){
+ /* If creating a temp table, the name may not be qualified */
+ sqlite3ErrorMsg(pParse, "temporary table name must be unqualified");
+ return;
+ }
+ if( !OMIT_TEMPDB && isTemp ) iDb = 1;
+
+ pParse->sNameToken = *pName;
+ zName = sqlite3NameFromToken(db, pName);
+ if( zName==0 ) return;
+ if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ goto begin_table_error;
+ }
+ if( db->init.iDb==1 ) isTemp = 1;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ assert( (isTemp & 1)==isTemp );
+ {
+ int code;
+ char *zDb = db->aDb[iDb].zName;
+ if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){
+ goto begin_table_error;
+ }
+ if( isView ){
+ if( !OMIT_TEMPDB && isTemp ){
+ code = SQLITE_CREATE_TEMP_VIEW;
+ }else{
+ code = SQLITE_CREATE_VIEW;
+ }
+ }else{
+ if( !OMIT_TEMPDB && isTemp ){
+ code = SQLITE_CREATE_TEMP_TABLE;
+ }else{
+ code = SQLITE_CREATE_TABLE;
+ }
+ }
+ if( !isVirtual && sqlite3AuthCheck(pParse, code, zName, 0, zDb) ){
+ goto begin_table_error;
+ }
+ }
+#endif
+
+ /* Make sure the new table name does not collide with an existing
+ ** index or table name in the same database. Issue an error message if
+ ** it does. The exception is if the statement being parsed was passed
+ ** to an sqlite3_declare_vtab() call. In that case only the column names
+ ** and types will be used, so there is no need to test for namespace
+ ** collisions.
+ */
+ if( !IN_DECLARE_VTAB ){
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ goto begin_table_error;
+ }
+ pTable = sqlite3FindTable(db, zName, db->aDb[iDb].zName);
+ if( pTable ){
+ if( !noErr ){
+ sqlite3ErrorMsg(pParse, "table %T already exists", pName);
+ }
+ goto begin_table_error;
+ }
+ if( sqlite3FindIndex(db, zName, 0)!=0 && (iDb==0 || !db->init.busy) ){
+ sqlite3ErrorMsg(pParse, "there is already an index named %s", zName);
+ goto begin_table_error;
+ }
+ }
+
+ pTable = sqlite3DbMallocZero(db, sizeof(Table));
+ if( pTable==0 ){
+ db->mallocFailed = 1;
+ pParse->rc = SQLITE_NOMEM;
+ pParse->nErr++;
+ goto begin_table_error;
+ }
+ pTable->zName = zName;
+ pTable->iPKey = -1;
+ pTable->pSchema = db->aDb[iDb].pSchema;
+ pTable->nRef = 1;
+ if( pParse->pNewTable ) sqlite3DeleteTable(pParse->pNewTable);
+ pParse->pNewTable = pTable;
+
+ /* If this is the magic sqlite_sequence table used by autoincrement,
+ ** then record a pointer to this table in the main database structure
+ ** so that INSERT can find the table easily.
+ */
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){
+ pTable->pSchema->pSeqTab = pTable;
+ }
+#endif
+
+ /* Begin generating the code that will insert the table record into
+ ** the SQLITE_MASTER table. Note in particular that we must go ahead
+ ** and allocate the record number for the table entry now. Before any
+ ** PRIMARY KEY or UNIQUE keywords are parsed. Those keywords will cause
+ ** indices to be created and the table record must come before the
+ ** indices. Hence, the record number for the table must be allocated
+ ** now.
+ */
+ if( !db->init.busy && (v = sqlite3GetVdbe(pParse))!=0 ){
+ int j1;
+ int fileFormat;
+ int reg1, reg2, reg3;
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( isVirtual ){
+ sqlite3VdbeAddOp0(v, OP_VBegin);
+ }
+#endif
+
+ /* If the file format and encoding in the database have not been set,
+ ** set them now.
+ */
+ reg1 = pParse->regRowid = ++pParse->nMem;
+ reg2 = pParse->regRoot = ++pParse->nMem;
+ reg3 = ++pParse->nMem;
+ sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, reg3, 1); /* file_format */
+ sqlite3VdbeUsesBtree(v, iDb);
+ j1 = sqlite3VdbeAddOp1(v, OP_If, reg3);
+ fileFormat = (db->flags & SQLITE_LegacyFileFmt)!=0 ?
+ 1 : SQLITE_MAX_FILE_FORMAT;
+ sqlite3VdbeAddOp2(v, OP_Integer, fileFormat, reg3);
+ sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, 1, reg3);
+ sqlite3VdbeAddOp2(v, OP_Integer, ENC(db), reg3);
+ sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, 4, reg3);
+ sqlite3VdbeJumpHere(v, j1);
+
+ /* This just creates a place-holder record in the sqlite_master table.
+ ** The record created does not contain anything yet. It will be replaced
+ ** by the real entry in code generated at sqlite3EndTable().
+ **
+ ** The rowid for the new entry is left on the top of the stack.
+ ** The rowid value is needed by the code that sqlite3EndTable will
+ ** generate.
+ */
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
+ if( isView || isVirtual ){
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, reg2);
+ }else
+#endif
+ {
+ sqlite3VdbeAddOp2(v, OP_CreateTable, iDb, reg2);
+ }
+ sqlite3OpenMasterTable(pParse, iDb);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1);
+ sqlite3VdbeAddOp2(v, OP_Null, 0, reg3);
+ sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1);
+ sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ sqlite3VdbeAddOp0(v, OP_Close);
+ }
+
+ /* Normal (non-error) return. */
+ return;
+
+ /* If an error occurs, we jump here */
+begin_table_error:
+ sqlite3_free(zName);
+ return;
+}
+
+/*
+** This macro is used to compare two strings in a case-insensitive manner.
+** It is slightly faster than calling sqlite3StrICmp() directly, but
+** produces larger code.
+**
+** WARNING: This macro is not compatible with the strcmp() family. It
+** returns true if the two strings are equal, otherwise false.
+*/
+#define STRICMP(x, y) (\
+sqlite3UpperToLower[*(unsigned char *)(x)]== \
+sqlite3UpperToLower[*(unsigned char *)(y)] \
+&& sqlite3StrICmp((x)+1,(y)+1)==0 )
+
+/*
+** Add a new column to the table currently being constructed.
+**
+** The parser calls this routine once for each column declaration
+** in a CREATE TABLE statement. sqlite3StartTable() gets called
+** first to get things going. Then this routine is called for each
+** column.
+*/
+SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName){
+ Table *p;
+ int i;
+ char *z;
+ Column *pCol;
+ sqlite3 *db = pParse->db;
+ if( (p = pParse->pNewTable)==0 ) return;
+#if SQLITE_MAX_COLUMN
+ if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){
+ sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName);
+ return;
+ }
+#endif
+ z = sqlite3NameFromToken(pParse->db, pName);
+ if( z==0 ) return;
+ for(i=0; i<p->nCol; i++){
+ if( STRICMP(z, p->aCol[i].zName) ){
+ sqlite3ErrorMsg(pParse, "duplicate column name: %s", z);
+ sqlite3_free(z);
+ return;
+ }
+ }
+ if( (p->nCol & 0x7)==0 ){
+ Column *aNew;
+ aNew = sqlite3DbRealloc(pParse->db,p->aCol,(p->nCol+8)*sizeof(p->aCol[0]));
+ if( aNew==0 ){
+ sqlite3_free(z);
+ return;
+ }
+ p->aCol = aNew;
+ }
+ pCol = &p->aCol[p->nCol];
+ memset(pCol, 0, sizeof(p->aCol[0]));
+ pCol->zName = z;
+
+ /* If there is no type specified, columns have the default affinity
+ ** 'NONE'. If there is a type specified, then sqlite3AddColumnType() will
+ ** be called next to set pCol->affinity correctly.
+ */
+ pCol->affinity = SQLITE_AFF_NONE;
+ p->nCol++;
+}
+
+/*
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement. A "NOT NULL" constraint has
+** been seen on a column. This routine sets the notNull flag on
+** the column currently under construction.
+*/
+SQLITE_PRIVATE void sqlite3AddNotNull(Parse *pParse, int onError){
+ Table *p;
+ int i;
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+ if( i>=0 ) p->aCol[i].notNull = onError;
+}
+
+/*
+** Scan the column type name zType (length nType) and return the
+** associated affinity type.
+**
+** This routine does a case-independent search of zType for the
+** substrings in the following table. If one of the substrings is
+** found, the corresponding affinity is returned. If zType contains
+** more than one of the substrings, entries toward the top of
+** the table take priority. For example, if zType is 'BLOBINT',
+** SQLITE_AFF_INTEGER is returned.
+**
+** Substring | Affinity
+** --------------------------------
+** 'INT' | SQLITE_AFF_INTEGER
+** 'CHAR' | SQLITE_AFF_TEXT
+** 'CLOB' | SQLITE_AFF_TEXT
+** 'TEXT' | SQLITE_AFF_TEXT
+** 'BLOB' | SQLITE_AFF_NONE
+** 'REAL' | SQLITE_AFF_REAL
+** 'FLOA' | SQLITE_AFF_REAL
+** 'DOUB' | SQLITE_AFF_REAL
+**
+** If none of the substrings in the above table are found,
+** SQLITE_AFF_NUMERIC is returned.
+*/
+SQLITE_PRIVATE char sqlite3AffinityType(const Token *pType){
+ u32 h = 0;
+ char aff = SQLITE_AFF_NUMERIC;
+ const unsigned char *zIn = pType->z;
+ const unsigned char *zEnd = &pType->z[pType->n];
+
+ while( zIn!=zEnd ){
+ h = (h<<8) + sqlite3UpperToLower[*zIn];
+ zIn++;
+ if( h==(('c'<<24)+('h'<<16)+('a'<<8)+'r') ){ /* CHAR */
+ aff = SQLITE_AFF_TEXT;
+ }else if( h==(('c'<<24)+('l'<<16)+('o'<<8)+'b') ){ /* CLOB */
+ aff = SQLITE_AFF_TEXT;
+ }else if( h==(('t'<<24)+('e'<<16)+('x'<<8)+'t') ){ /* TEXT */
+ aff = SQLITE_AFF_TEXT;
+ }else if( h==(('b'<<24)+('l'<<16)+('o'<<8)+'b') /* BLOB */
+ && (aff==SQLITE_AFF_NUMERIC || aff==SQLITE_AFF_REAL) ){
+ aff = SQLITE_AFF_NONE;
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ }else if( h==(('r'<<24)+('e'<<16)+('a'<<8)+'l') /* REAL */
+ && aff==SQLITE_AFF_NUMERIC ){
+ aff = SQLITE_AFF_REAL;
+ }else if( h==(('f'<<24)+('l'<<16)+('o'<<8)+'a') /* FLOA */
+ && aff==SQLITE_AFF_NUMERIC ){
+ aff = SQLITE_AFF_REAL;
+ }else if( h==(('d'<<24)+('o'<<16)+('u'<<8)+'b') /* DOUB */
+ && aff==SQLITE_AFF_NUMERIC ){
+ aff = SQLITE_AFF_REAL;
+#endif
+ }else if( (h&0x00FFFFFF)==(('i'<<16)+('n'<<8)+'t') ){ /* INT */
+ aff = SQLITE_AFF_INTEGER;
+ break;
+ }
+ }
+
+ return aff;
+}
+
+/*
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement. The pFirst token is the first
+** token in the sequence of tokens that describe the type of the
+** column currently under construction. pLast is the last token
+** in the sequence. Use this information to construct a string
+** that contains the typename of the column and store that string
+** in zType.
+*/
+SQLITE_PRIVATE void sqlite3AddColumnType(Parse *pParse, Token *pType){
+ Table *p;
+ int i;
+ Column *pCol;
+
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+ if( i<0 ) return;
+ pCol = &p->aCol[i];
+ sqlite3_free(pCol->zType);
+ pCol->zType = sqlite3NameFromToken(pParse->db, pType);
+ pCol->affinity = sqlite3AffinityType(pType);
+}
+
+/*
+** The expression is the default value for the most recently added column
+** of the table currently under construction.
+**
+** Default value expressions must be constant. Raise an exception if this
+** is not the case.
+**
+** This routine is called by the parser while in the middle of
+** parsing a CREATE TABLE statement.
+*/
+SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse *pParse, Expr *pExpr){
+ Table *p;
+ Column *pCol;
+ if( (p = pParse->pNewTable)!=0 ){
+ pCol = &(p->aCol[p->nCol-1]);
+ if( !sqlite3ExprIsConstantOrFunction(pExpr) ){
+ sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant",
+ pCol->zName);
+ }else{
+ Expr *pCopy;
+ sqlite3 *db = pParse->db;
+ sqlite3ExprDelete(pCol->pDflt);
+ pCol->pDflt = pCopy = sqlite3ExprDup(db, pExpr);
+ if( pCopy ){
+ sqlite3TokenCopy(db, &pCopy->span, &pExpr->span);
+ }
+ }
+ }
+ sqlite3ExprDelete(pExpr);
+}
+
+/*
+** Designate the PRIMARY KEY for the table. pList is a list of names
+** of columns that form the primary key. If pList is NULL, then the
+** most recently added column of the table is the primary key.
+**
+** A table can have at most one primary key. If the table already has
+** a primary key (and this is the second primary key) then create an
+** error.
+**
+** If the PRIMARY KEY is on a single column whose datatype is INTEGER,
+** then we will try to use that column as the rowid. Set the Table.iPKey
+** field of the table under construction to be the index of the
+** INTEGER PRIMARY KEY column. Table.iPKey is set to -1 if there is
+** no INTEGER PRIMARY KEY.
+**
+** If the key is not an INTEGER PRIMARY KEY, then create a unique
+** index for the key. No index is created for INTEGER PRIMARY KEYs.
+*/
+SQLITE_PRIVATE void sqlite3AddPrimaryKey(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* List of field names to be indexed */
+ int onError, /* What to do with a uniqueness conflict */
+ int autoInc, /* True if the AUTOINCREMENT keyword is present */
+ int sortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */
+){
+ Table *pTab = pParse->pNewTable;
+ char *zType = 0;
+ int iCol = -1, i;
+ if( pTab==0 || IN_DECLARE_VTAB ) goto primary_key_exit;
+ if( pTab->hasPrimKey ){
+ sqlite3ErrorMsg(pParse,
+ "table \"%s\" has more than one primary key", pTab->zName);
+ goto primary_key_exit;
+ }
+ pTab->hasPrimKey = 1;
+ if( pList==0 ){
+ iCol = pTab->nCol - 1;
+ pTab->aCol[iCol].isPrimKey = 1;
+ }else{
+ for(i=0; i<pList->nExpr; i++){
+ for(iCol=0; iCol<pTab->nCol; iCol++){
+ if( sqlite3StrICmp(pList->a[i].zName, pTab->aCol[iCol].zName)==0 ){
+ break;
+ }
+ }
+ if( iCol<pTab->nCol ){
+ pTab->aCol[iCol].isPrimKey = 1;
+ }
+ }
+ if( pList->nExpr>1 ) iCol = -1;
+ }
+ if( iCol>=0 && iCol<pTab->nCol ){
+ zType = pTab->aCol[iCol].zType;
+ }
+ if( zType && sqlite3StrICmp(zType, "INTEGER")==0
+ && sortOrder==SQLITE_SO_ASC ){
+ pTab->iPKey = iCol;
+ pTab->keyConf = onError;
+ pTab->autoInc = autoInc;
+ }else if( autoInc ){
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
+ "INTEGER PRIMARY KEY");
+#endif
+ }else{
+ sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0, 0, sortOrder, 0);
+ pList = 0;
+ }
+
+primary_key_exit:
+ sqlite3ExprListDelete(pList);
+ return;
+}
+
+/*
+** Add a new CHECK constraint to the table currently under construction.
+*/
+SQLITE_PRIVATE void sqlite3AddCheckConstraint(
+ Parse *pParse, /* Parsing context */
+ Expr *pCheckExpr /* The check expression */
+){
+#ifndef SQLITE_OMIT_CHECK
+ Table *pTab = pParse->pNewTable;
+ sqlite3 *db = pParse->db;
+ if( pTab && !IN_DECLARE_VTAB ){
+ /* The CHECK expression must be duplicated so that tokens refer
+ ** to malloced space and not the (ephemeral) text of the CREATE TABLE
+ ** statement */
+ pTab->pCheck = sqlite3ExprAnd(db, pTab->pCheck,
+ sqlite3ExprDup(db, pCheckExpr));
+ }
+#endif
+ sqlite3ExprDelete(pCheckExpr);
+}
+
+/*
+** Set the collation function of the most recently parsed table column
+** to the CollSeq given.
+*/
+SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){
+ Table *p;
+ int i;
+ char *zColl; /* Dequoted name of collation sequence */
+
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+
+ zColl = sqlite3NameFromToken(pParse->db, pToken);
+ if( !zColl ) return;
+
+ if( sqlite3LocateCollSeq(pParse, zColl, -1) ){
+ Index *pIdx;
+ p->aCol[i].zColl = zColl;
+
+ /* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
+ ** then an index may have been created on this column before the
+ ** collation type was added. Correct this if it is the case.
+ */
+ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){
+ assert( pIdx->nColumn==1 );
+ if( pIdx->aiColumn[0]==i ){
+ pIdx->azColl[0] = p->aCol[i].zColl;
+ }
+ }
+ }else{
+ sqlite3_free(zColl);
+ }
+}
+
+/*
+** This function returns the collation sequence for database native text
+** encoding identified by the string zName, length nName.
+**
+** If the requested collation sequence is not available, or not available
+** in the database native encoding, the collation factory is invoked to
+** request it. If the collation factory does not supply such a sequence,
+** and the sequence is available in another text encoding, then that is
+** returned instead.
+**
+** If no versions of the requested collations sequence are available, or
+** another error occurs, NULL is returned and an error message written into
+** pParse.
+**
+** This routine is a wrapper around sqlite3FindCollSeq(). This routine
+** invokes the collation factory if the named collation cannot be found
+** and generates an error message.
+*/
+SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName, int nName){
+ sqlite3 *db = pParse->db;
+ u8 enc = ENC(db);
+ u8 initbusy = db->init.busy;
+ CollSeq *pColl;
+
+ pColl = sqlite3FindCollSeq(db, enc, zName, nName, initbusy);
+ if( !initbusy && (!pColl || !pColl->xCmp) ){
+ pColl = sqlite3GetCollSeq(db, pColl, zName, nName);
+ if( !pColl ){
+ if( nName<0 ){
+ nName = strlen(zName);
+ }
+ sqlite3ErrorMsg(pParse, "no such collation sequence: %.*s", nName, zName);
+ pColl = 0;
+ }
+ }
+
+ return pColl;
+}
+
+
+/*
+** Generate code that will increment the schema cookie.
+**
+** The schema cookie is used to determine when the schema for the
+** database changes. After each schema change, the cookie value
+** changes. When a process first reads the schema it records the
+** cookie. Thereafter, whenever it goes to access the database,
+** it checks the cookie to make sure the schema has not changed
+** since it was last read.
+**
+** This plan is not completely bullet-proof. It is possible for
+** the schema to change multiple times and for the cookie to be
+** set back to prior value. But schema changes are infrequent
+** and the probability of hitting the same cookie value is only
+** 1 chance in 2^32. So we're safe enough.
+*/
+SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){
+ int r1 = sqlite3GetTempReg(pParse);
+ sqlite3 *db = pParse->db;
+ Vdbe *v = pParse->pVdbe;
+ sqlite3VdbeAddOp2(v, OP_Integer, db->aDb[iDb].pSchema->schema_cookie+1, r1);
+ sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, 0, r1);
+ sqlite3ReleaseTempReg(pParse, r1);
+}
+
+/*
+** Measure the number of characters needed to output the given
+** identifier. The number returned includes any quotes used
+** but does not include the null terminator.
+**
+** The estimate is conservative. It might be larger that what is
+** really needed.
+*/
+static int identLength(const char *z){
+ int n;
+ for(n=0; *z; n++, z++){
+ if( *z=='"' ){ n++; }
+ }
+ return n + 2;
+}
+
+/*
+** Write an identifier onto the end of the given string. Add
+** quote characters as needed.
+*/
+static void identPut(char *z, int *pIdx, char *zSignedIdent){
+ unsigned char *zIdent = (unsigned char*)zSignedIdent;
+ int i, j, needQuote;
+ i = *pIdx;
+ for(j=0; zIdent[j]; j++){
+ if( !isalnum(zIdent[j]) && zIdent[j]!='_' ) break;
+ }
+ needQuote = zIdent[j]!=0 || isdigit(zIdent[0])
+ || sqlite3KeywordCode(zIdent, j)!=TK_ID;
+ if( needQuote ) z[i++] = '"';
+ for(j=0; zIdent[j]; j++){
+ z[i++] = zIdent[j];
+ if( zIdent[j]=='"' ) z[i++] = '"';
+ }
+ if( needQuote ) z[i++] = '"';
+ z[i] = 0;
+ *pIdx = i;
+}
+
+/*
+** Generate a CREATE TABLE statement appropriate for the given
+** table. Memory to hold the text of the statement is obtained
+** from sqliteMalloc() and must be freed by the calling function.
+*/
+static char *createTableStmt(sqlite3 *db, Table *p, int isTemp){
+ int i, k, n;
+ char *zStmt;
+ char *zSep, *zSep2, *zEnd, *z;
+ Column *pCol;
+ n = 0;
+ for(pCol = p->aCol, i=0; i<p->nCol; i++, pCol++){
+ n += identLength(pCol->zName);
+ z = pCol->zType;
+ if( z ){
+ n += (strlen(z) + 1);
+ }
+ }
+ n += identLength(p->zName);
+ if( n<50 ){
+ zSep = "";
+ zSep2 = ",";
+ zEnd = ")";
+ }else{
+ zSep = "\n ";
+ zSep2 = ",\n ";
+ zEnd = "\n)";
+ }
+ n += 35 + 6*p->nCol;
+ zStmt = sqlite3_malloc( n );
+ if( zStmt==0 ){
+ db->mallocFailed = 1;
+ return 0;
+ }
+ sqlite3_snprintf(n, zStmt,
+ !OMIT_TEMPDB&&isTemp ? "CREATE TEMP TABLE ":"CREATE TABLE ");
+ k = strlen(zStmt);
+ identPut(zStmt, &k, p->zName);
+ zStmt[k++] = '(';
+ for(pCol=p->aCol, i=0; i<p->nCol; i++, pCol++){
+ sqlite3_snprintf(n-k, &zStmt[k], zSep);
+ k += strlen(&zStmt[k]);
+ zSep = zSep2;
+ identPut(zStmt, &k, pCol->zName);
+ if( (z = pCol->zType)!=0 ){
+ zStmt[k++] = ' ';
+ assert( strlen(z)+k+1<=n );
+ sqlite3_snprintf(n-k, &zStmt[k], "%s", z);
+ k += strlen(z);
+ }
+ }
+ sqlite3_snprintf(n-k, &zStmt[k], "%s", zEnd);
+ return zStmt;
+}
+
+/*
+** This routine is called to report the final ")" that terminates
+** a CREATE TABLE statement.
+**
+** The table structure that other action routines have been building
+** is added to the internal hash tables, assuming no errors have
+** occurred.
+**
+** An entry for the table is made in the master table on disk, unless
+** this is a temporary table or db->init.busy==1. When db->init.busy==1
+** it means we are reading the sqlite_master table because we just
+** connected to the database or because the sqlite_master table has
+** recently changed, so the entry for this table already exists in
+** the sqlite_master table. We do not want to create it again.
+**
+** If the pSelect argument is not NULL, it means that this routine
+** was called to create a table generated from a
+** "CREATE TABLE ... AS SELECT ..." statement. The column names of
+** the new table will match the result set of the SELECT.
+*/
+SQLITE_PRIVATE void sqlite3EndTable(
+ Parse *pParse, /* Parse context */
+ Token *pCons, /* The ',' token after the last column defn. */
+ Token *pEnd, /* The final ')' token in the CREATE TABLE */
+ Select *pSelect /* Select from a "CREATE ... AS SELECT" */
+){
+ Table *p;
+ sqlite3 *db = pParse->db;
+ int iDb;
+
+ if( (pEnd==0 && pSelect==0) || pParse->nErr || db->mallocFailed ) {
+ return;
+ }
+ p = pParse->pNewTable;
+ if( p==0 ) return;
+
+ assert( !db->init.busy || !pSelect );
+
+ iDb = sqlite3SchemaToIndex(db, p->pSchema);
+
+#ifndef SQLITE_OMIT_CHECK
+ /* Resolve names in all CHECK constraint expressions.
+ */
+ if( p->pCheck ){
+ SrcList sSrc; /* Fake SrcList for pParse->pNewTable */
+ NameContext sNC; /* Name context for pParse->pNewTable */
+
+ memset(&sNC, 0, sizeof(sNC));
+ memset(&sSrc, 0, sizeof(sSrc));
+ sSrc.nSrc = 1;
+ sSrc.a[0].zName = p->zName;
+ sSrc.a[0].pTab = p;
+ sSrc.a[0].iCursor = -1;
+ sNC.pParse = pParse;
+ sNC.pSrcList = &sSrc;
+ sNC.isCheck = 1;
+ if( sqlite3ExprResolveNames(&sNC, p->pCheck) ){
+ return;
+ }
+ }
+#endif /* !defined(SQLITE_OMIT_CHECK) */
+
+ /* If the db->init.busy is 1 it means we are reading the SQL off the
+ ** "sqlite_master" or "sqlite_temp_master" table on the disk.
+ ** So do not write to the disk again. Extract the root page number
+ ** for the table from the db->init.newTnum field. (The page number
+ ** should have been put there by the sqliteOpenCb routine.)
+ */
+ if( db->init.busy ){
+ p->tnum = db->init.newTnum;
+ }
+
+ /* If not initializing, then create a record for the new table
+ ** in the SQLITE_MASTER table of the database. The record number
+ ** for the new table entry should already be on the stack.
+ **
+ ** If this is a TEMPORARY table, write the entry into the auxiliary
+ ** file instead of into the main database file.
+ */
+ if( !db->init.busy ){
+ int n;
+ Vdbe *v;
+ char *zType; /* "view" or "table" */
+ char *zType2; /* "VIEW" or "TABLE" */
+ char *zStmt; /* Text of the CREATE TABLE or CREATE VIEW statement */
+
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+
+ sqlite3VdbeAddOp1(v, OP_Close, 0);
+
+ /* Create the rootpage for the new table and push it onto the stack.
+ ** A view has no rootpage, so just push a zero onto the stack for
+ ** views. Initialize zType at the same time.
+ */
+ if( p->pSelect==0 ){
+ /* A regular table */
+ zType = "table";
+ zType2 = "TABLE";
+#ifndef SQLITE_OMIT_VIEW
+ }else{
+ /* A view */
+ zType = "view";
+ zType2 = "VIEW";
+#endif
+ }
+
+ /* If this is a CREATE TABLE xx AS SELECT ..., execute the SELECT
+ ** statement to populate the new table. The root-page number for the
+ ** new table is on the top of the vdbe stack.
+ **
+ ** Once the SELECT has been coded by sqlite3Select(), it is in a
+ ** suitable state to query for the column names and types to be used
+ ** by the new table.
+ **
+ ** A shared-cache write-lock is not required to write to the new table,
+ ** as a schema-lock must have already been obtained to create it. Since
+ ** a schema-lock excludes all other database users, the write-lock would
+ ** be redundant.
+ */
+ if( pSelect ){
+ SelectDest dest;
+ Table *pSelTab;
+
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb);
+ sqlite3VdbeChangeP5(v, 1);
+ pParse->nTab = 2;
+ sqlite3SelectDestInit(&dest, SRT_Table, 1);
+ sqlite3Select(pParse, pSelect, &dest, 0, 0, 0, 0);
+ sqlite3VdbeAddOp1(v, OP_Close, 1);
+ if( pParse->nErr==0 ){
+ pSelTab = sqlite3ResultSetOfSelect(pParse, 0, pSelect);
+ if( pSelTab==0 ) return;
+ assert( p->aCol==0 );
+ p->nCol = pSelTab->nCol;
+ p->aCol = pSelTab->aCol;
+ pSelTab->nCol = 0;
+ pSelTab->aCol = 0;
+ sqlite3DeleteTable(pSelTab);
+ }
+ }
+
+ /* Compute the complete text of the CREATE statement */
+ if( pSelect ){
+ zStmt = createTableStmt(db, p, p->pSchema==db->aDb[1].pSchema);
+ }else{
+ n = pEnd->z - pParse->sNameToken.z + 1;
+ zStmt = sqlite3MPrintf(db,
+ "CREATE %s %.*s", zType2, n, pParse->sNameToken.z
+ );
+ }
+
+ /* A slot for the record has already been allocated in the
+ ** SQLITE_MASTER table. We just need to update that slot with all
+ ** the information we've collected. The rowid for the preallocated
+ ** slot is the 2nd item on the stack. The top of the stack is the
+ ** root page for the new table (or a 0 if this is a view).
+ */
+ sqlite3NestedParse(pParse,
+ "UPDATE %Q.%s "
+ "SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q "
+ "WHERE rowid=#%d",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
+ zType,
+ p->zName,
+ p->zName,
+ pParse->regRoot,
+ zStmt,
+ pParse->regRowid
+ );
+ sqlite3_free(zStmt);
+ sqlite3ChangeCookie(pParse, iDb);
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ /* Check to see if we need to create an sqlite_sequence table for
+ ** keeping track of autoincrement keys.
+ */
+ if( p->autoInc ){
+ Db *pDb = &db->aDb[iDb];
+ if( pDb->pSchema->pSeqTab==0 ){
+ sqlite3NestedParse(pParse,
+ "CREATE TABLE %Q.sqlite_sequence(name,seq)",
+ pDb->zName
+ );
+ }
+ }
+#endif
+
+ /* Reparse everything to update our internal data structures */
+ sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0,
+ sqlite3MPrintf(db, "tbl_name='%q'",p->zName), P4_DYNAMIC);
+ }
+
+
+ /* Add the table to the in-memory representation of the database.
+ */
+ if( db->init.busy && pParse->nErr==0 ){
+ Table *pOld;
+ FKey *pFKey;
+ Schema *pSchema = p->pSchema;
+ pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, strlen(p->zName)+1,p);
+ if( pOld ){
+ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */
+ db->mallocFailed = 1;
+ return;
+ }
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ for(pFKey=p->pFKey; pFKey; pFKey=pFKey->pNextFrom){
+ void *data;
+ int nTo = strlen(pFKey->zTo) + 1;
+ pFKey->pNextTo = sqlite3HashFind(&pSchema->aFKey, pFKey->zTo, nTo);
+ data = sqlite3HashInsert(&pSchema->aFKey, pFKey->zTo, nTo, pFKey);
+ if( data==(void *)pFKey ){
+ db->mallocFailed = 1;
+ }
+ }
+#endif
+ pParse->pNewTable = 0;
+ db->nTable++;
+ db->flags |= SQLITE_InternChanges;
+
+#ifndef SQLITE_OMIT_ALTERTABLE
+ if( !p->pSelect ){
+ const char *zName = (const char *)pParse->sNameToken.z;
+ int nName;
+ assert( !pSelect && pCons && pEnd );
+ if( pCons->z==0 ){
+ pCons = pEnd;
+ }
+ nName = (const char *)pCons->z - zName;
+ p->addColOffset = 13 + sqlite3Utf8CharLen(zName, nName);
+ }
+#endif
+ }
+}
+
+#ifndef SQLITE_OMIT_VIEW
+/*
+** The parser calls this routine in order to create a new VIEW
+*/
+SQLITE_PRIVATE void sqlite3CreateView(
+ Parse *pParse, /* The parsing context */
+ Token *pBegin, /* The CREATE token that begins the statement */
+ Token *pName1, /* The token that holds the name of the view */
+ Token *pName2, /* The token that holds the name of the view */
+ Select *pSelect, /* A SELECT statement that will become the new view */
+ int isTemp, /* TRUE for a TEMPORARY view */
+ int noErr /* Suppress error messages if VIEW already exists */
+){
+ Table *p;
+ int n;
+ const unsigned char *z;
+ Token sEnd;
+ DbFixer sFix;
+ Token *pName;
+ int iDb;
+ sqlite3 *db = pParse->db;
+
+ if( pParse->nVar>0 ){
+ sqlite3ErrorMsg(pParse, "parameters are not allowed in views");
+ sqlite3SelectDelete(pSelect);
+ return;
+ }
+ sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr);
+ p = pParse->pNewTable;
+ if( p==0 || pParse->nErr ){
+ sqlite3SelectDelete(pSelect);
+ return;
+ }
+ sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ iDb = sqlite3SchemaToIndex(db, p->pSchema);
+ if( sqlite3FixInit(&sFix, pParse, iDb, "view", pName)
+ && sqlite3FixSelect(&sFix, pSelect)
+ ){
+ sqlite3SelectDelete(pSelect);
+ return;
+ }
+
+ /* Make a copy of the entire SELECT statement that defines the view.
+ ** This will force all the Expr.token.z values to be dynamically
+ ** allocated rather than point to the input string - which means that
+ ** they will persist after the current sqlite3_exec() call returns.
+ */
+ p->pSelect = sqlite3SelectDup(db, pSelect);
+ sqlite3SelectDelete(pSelect);
+ if( db->mallocFailed ){
+ return;
+ }
+ if( !db->init.busy ){
+ sqlite3ViewGetColumnNames(pParse, p);
+ }
+
+ /* Locate the end of the CREATE VIEW statement. Make sEnd point to
+ ** the end.
+ */
+ sEnd = pParse->sLastToken;
+ if( sEnd.z[0]!=0 && sEnd.z[0]!=';' ){
+ sEnd.z += sEnd.n;
+ }
+ sEnd.n = 0;
+ n = sEnd.z - pBegin->z;
+ z = (const unsigned char*)pBegin->z;
+ while( n>0 && (z[n-1]==';' || isspace(z[n-1])) ){ n--; }
+ sEnd.z = &z[n-1];
+ sEnd.n = 1;
+
+ /* Use sqlite3EndTable() to add the view to the SQLITE_MASTER table */
+ sqlite3EndTable(pParse, 0, &sEnd, 0);
+ return;
+}
+#endif /* SQLITE_OMIT_VIEW */
+
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
+/*
+** The Table structure pTable is really a VIEW. Fill in the names of
+** the columns of the view in the pTable structure. Return the number
+** of errors. If an error is seen leave an error message in pParse->zErrMsg.
+*/
+SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
+ Table *pSelTab; /* A fake table from which we get the result set */
+ Select *pSel; /* Copy of the SELECT that implements the view */
+ int nErr = 0; /* Number of errors encountered */
+ int n; /* Temporarily holds the number of cursors assigned */
+ sqlite3 *db = pParse->db; /* Database connection for malloc errors */
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
+
+ assert( pTable );
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( sqlite3VtabCallConnect(pParse, pTable) ){
+ return SQLITE_ERROR;
+ }
+ if( IsVirtual(pTable) ) return 0;
+#endif
+
+#ifndef SQLITE_OMIT_VIEW
+ /* A positive nCol means the columns names for this view are
+ ** already known.
+ */
+ if( pTable->nCol>0 ) return 0;
+
+ /* A negative nCol is a special marker meaning that we are currently
+ ** trying to compute the column names. If we enter this routine with
+ ** a negative nCol, it means two or more views form a loop, like this:
+ **
+ ** CREATE VIEW one AS SELECT * FROM two;
+ ** CREATE VIEW two AS SELECT * FROM one;
+ **
+ ** Actually, this error is caught previously and so the following test
+ ** should always fail. But we will leave it in place just to be safe.
+ */
+ if( pTable->nCol<0 ){
+ sqlite3ErrorMsg(pParse, "view %s is circularly defined", pTable->zName);
+ return 1;
+ }
+ assert( pTable->nCol>=0 );
+
+ /* If we get this far, it means we need to compute the table names.
+ ** Note that the call to sqlite3ResultSetOfSelect() will expand any
+ ** "*" elements in the results set of the view and will assign cursors
+ ** to the elements of the FROM clause. But we do not want these changes
+ ** to be permanent. So the computation is done on a copy of the SELECT
+ ** statement that defines the view.
+ */
+ assert( pTable->pSelect );
+ pSel = sqlite3SelectDup(db, pTable->pSelect);
+ if( pSel ){
+ n = pParse->nTab;
+ sqlite3SrcListAssignCursors(pParse, pSel->pSrc);
+ pTable->nCol = -1;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ xAuth = db->xAuth;
+ db->xAuth = 0;
+ pSelTab = sqlite3ResultSetOfSelect(pParse, 0, pSel);
+ db->xAuth = xAuth;
+#else
+ pSelTab = sqlite3ResultSetOfSelect(pParse, 0, pSel);
+#endif
+ pParse->nTab = n;
+ if( pSelTab ){
+ assert( pTable->aCol==0 );
+ pTable->nCol = pSelTab->nCol;
+ pTable->aCol = pSelTab->aCol;
+ pSelTab->nCol = 0;
+ pSelTab->aCol = 0;
+ sqlite3DeleteTable(pSelTab);
+ pTable->pSchema->flags |= DB_UnresetViews;
+ }else{
+ pTable->nCol = 0;
+ nErr++;
+ }
+ sqlite3SelectDelete(pSel);
+ } else {
+ nErr++;
+ }
+#endif /* SQLITE_OMIT_VIEW */
+ return nErr;
+}
+#endif /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */
+
+#ifndef SQLITE_OMIT_VIEW
+/*
+** Clear the column names from every VIEW in database idx.
+*/
+static void sqliteViewResetAll(sqlite3 *db, int idx){
+ HashElem *i;
+ if( !DbHasProperty(db, idx, DB_UnresetViews) ) return;
+ for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){
+ Table *pTab = sqliteHashData(i);
+ if( pTab->pSelect ){
+ sqliteResetColumnNames(pTab);
+ }
+ }
+ DbClearProperty(db, idx, DB_UnresetViews);
+}
+#else
+# define sqliteViewResetAll(A,B)
+#endif /* SQLITE_OMIT_VIEW */
+
+/*
+** This function is called by the VDBE to adjust the internal schema
+** used by SQLite when the btree layer moves a table root page. The
+** root-page of a table or index in database iDb has changed from iFrom
+** to iTo.
+**
+** Ticket #1728: The symbol table might still contain information
+** on tables and/or indices that are the process of being deleted.
+** If you are unlucky, one of those deleted indices or tables might
+** have the same rootpage number as the real table or index that is
+** being moved. So we cannot stop searching after the first match
+** because the first match might be for one of the deleted indices
+** or tables and not the table/index that is actually being moved.
+** We must continue looping until all tables and indices with
+** rootpage==iFrom have been converted to have a rootpage of iTo
+** in order to be certain that we got the right one.
+*/
+#ifndef SQLITE_OMIT_AUTOVACUUM
+SQLITE_PRIVATE void sqlite3RootPageMoved(Db *pDb, int iFrom, int iTo){
+ HashElem *pElem;
+ Hash *pHash;
+
+ pHash = &pDb->pSchema->tblHash;
+ for(pElem=sqliteHashFirst(pHash); pElem; pElem=sqliteHashNext(pElem)){
+ Table *pTab = sqliteHashData(pElem);
+ if( pTab->tnum==iFrom ){
+ pTab->tnum = iTo;
+ }
+ }
+ pHash = &pDb->pSchema->idxHash;
+ for(pElem=sqliteHashFirst(pHash); pElem; pElem=sqliteHashNext(pElem)){
+ Index *pIdx = sqliteHashData(pElem);
+ if( pIdx->tnum==iFrom ){
+ pIdx->tnum = iTo;
+ }
+ }
+}
+#endif
+
+/*
+** Write code to erase the table with root-page iTable from database iDb.
+** Also write code to modify the sqlite_master table and internal schema
+** if a root-page of another table is moved by the btree-layer whilst
+** erasing iTable (this can happen with an auto-vacuum database).
+*/
+static void destroyRootPage(Parse *pParse, int iTable, int iDb){
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ int r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_Destroy, iTable, r1, iDb);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* OP_Destroy stores an in integer r1. If this integer
+ ** is non-zero, then it is the root page number of a table moved to
+ ** location iTable. The following code modifies the sqlite_master table to
+ ** reflect this.
+ **
+ ** The "#%d" in the SQL is a special constant that means whatever value
+ ** is on the top of the stack. See sqlite3RegisterExpr().
+ */
+ sqlite3NestedParse(pParse,
+ "UPDATE %Q.%s SET rootpage=%d WHERE #%d AND rootpage=#%d",
+ pParse->db->aDb[iDb].zName, SCHEMA_TABLE(iDb), iTable, r1, r1);
+#endif
+ sqlite3ReleaseTempReg(pParse, r1);
+}
+
+/*
+** Write VDBE code to erase table pTab and all associated indices on disk.
+** Code to update the sqlite_master tables and internal schema definitions
+** in case a root-page belonging to another table is moved by the btree layer
+** is also added (this can happen with an auto-vacuum database).
+*/
+static void destroyTable(Parse *pParse, Table *pTab){
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ Index *pIdx;
+ int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ destroyRootPage(pParse, pTab->tnum, iDb);
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ destroyRootPage(pParse, pIdx->tnum, iDb);
+ }
+#else
+ /* If the database may be auto-vacuum capable (if SQLITE_OMIT_AUTOVACUUM
+ ** is not defined), then it is important to call OP_Destroy on the
+ ** table and index root-pages in order, starting with the numerically
+ ** largest root-page number. This guarantees that none of the root-pages
+ ** to be destroyed is relocated by an earlier OP_Destroy. i.e. if the
+ ** following were coded:
+ **
+ ** OP_Destroy 4 0
+ ** ...
+ ** OP_Destroy 5 0
+ **
+ ** and root page 5 happened to be the largest root-page number in the
+ ** database, then root page 5 would be moved to page 4 by the
+ ** "OP_Destroy 4 0" opcode. The subsequent "OP_Destroy 5 0" would hit
+ ** a free-list page.
+ */
+ int iTab = pTab->tnum;
+ int iDestroyed = 0;
+
+ while( 1 ){
+ Index *pIdx;
+ int iLargest = 0;
+
+ if( iDestroyed==0 || iTab<iDestroyed ){
+ iLargest = iTab;
+ }
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int iIdx = pIdx->tnum;
+ assert( pIdx->pSchema==pTab->pSchema );
+ if( (iDestroyed==0 || (iIdx<iDestroyed)) && iIdx>iLargest ){
+ iLargest = iIdx;
+ }
+ }
+ if( iLargest==0 ){
+ return;
+ }else{
+ int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ destroyRootPage(pParse, iLargest, iDb);
+ iDestroyed = iLargest;
+ }
+ }
+#endif
+}
+
+/*
+** This routine is called to do the work of a DROP TABLE statement.
+** pName is the name of the table to be dropped.
+*/
+SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
+ Table *pTab;
+ Vdbe *v;
+ sqlite3 *db = pParse->db;
+ int iDb;
+
+ if( pParse->nErr || db->mallocFailed ){
+ goto exit_drop_table;
+ }
+ assert( pName->nSrc==1 );
+ pTab = sqlite3LocateTable(pParse, isView,
+ pName->a[0].zName, pName->a[0].zDatabase);
+
+ if( pTab==0 ){
+ if( noErr ){
+ sqlite3ErrorClear(pParse);
+ }
+ goto exit_drop_table;
+ }
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( iDb>=0 && iDb<db->nDb );
+
+ /* If pTab is a virtual table, call ViewGetColumnNames() to ensure
+ ** it is initialized.
+ */
+ if( IsVirtual(pTab) && sqlite3ViewGetColumnNames(pParse, pTab) ){
+ goto exit_drop_table;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code;
+ const char *zTab = SCHEMA_TABLE(iDb);
+ const char *zDb = db->aDb[iDb].zName;
+ const char *zArg2 = 0;
+ if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){
+ goto exit_drop_table;
+ }
+ if( isView ){
+ if( !OMIT_TEMPDB && iDb==1 ){
+ code = SQLITE_DROP_TEMP_VIEW;
+ }else{
+ code = SQLITE_DROP_VIEW;
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ }else if( IsVirtual(pTab) ){
+ code = SQLITE_DROP_VTABLE;
+ zArg2 = pTab->pMod->zName;
+#endif
+ }else{
+ if( !OMIT_TEMPDB && iDb==1 ){
+ code = SQLITE_DROP_TEMP_TABLE;
+ }else{
+ code = SQLITE_DROP_TABLE;
+ }
+ }
+ if( sqlite3AuthCheck(pParse, code, pTab->zName, zArg2, zDb) ){
+ goto exit_drop_table;
+ }
+ if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){
+ goto exit_drop_table;
+ }
+ }
+#endif
+ if( pTab->readOnly || pTab==db->aDb[iDb].pSchema->pSeqTab ){
+ sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName);
+ goto exit_drop_table;
+ }
+
+#ifndef SQLITE_OMIT_VIEW
+ /* Ensure DROP TABLE is not used on a view, and DROP VIEW is not used
+ ** on a table.
+ */
+ if( isView && pTab->pSelect==0 ){
+ sqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName);
+ goto exit_drop_table;
+ }
+ if( !isView && pTab->pSelect ){
+ sqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName);
+ goto exit_drop_table;
+ }
+#endif
+
+ /* Generate code to remove the table from the master table
+ ** on disk.
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ Trigger *pTrigger;
+ Db *pDb = &db->aDb[iDb];
+ sqlite3BeginWriteOperation(pParse, 1, iDb);
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTab) ){
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp0(v, OP_VBegin);
+ }
+ }
+#endif
+
+ /* Drop all triggers associated with the table being dropped. Code
+ ** is generated to remove entries from sqlite_master and/or
+ ** sqlite_temp_master if required.
+ */
+ pTrigger = pTab->pTrigger;
+ while( pTrigger ){
+ assert( pTrigger->pSchema==pTab->pSchema ||
+ pTrigger->pSchema==db->aDb[1].pSchema );
+ sqlite3DropTriggerPtr(pParse, pTrigger);
+ pTrigger = pTrigger->pNext;
+ }
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ /* Remove any entries of the sqlite_sequence table associated with
+ ** the table being dropped. This is done before the table is dropped
+ ** at the btree level, in case the sqlite_sequence table needs to
+ ** move as a result of the drop (can happen in auto-vacuum mode).
+ */
+ if( pTab->autoInc ){
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %s.sqlite_sequence WHERE name=%Q",
+ pDb->zName, pTab->zName
+ );
+ }
+#endif
+
+ /* Drop all SQLITE_MASTER table and index entries that refer to the
+ ** table. The program name loops through the master table and deletes
+ ** every row that refers to a table of the same name as the one being
+ ** dropped. Triggers are handled seperately because a trigger can be
+ ** created in the temp database that refers to a table in another
+ ** database.
+ */
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'",
+ pDb->zName, SCHEMA_TABLE(iDb), pTab->zName);
+
+ /* Drop any statistics from the sqlite_stat1 table, if it exists */
+ if( sqlite3FindTable(db, "sqlite_stat1", db->aDb[iDb].zName) ){
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %Q.sqlite_stat1 WHERE tbl=%Q", pDb->zName, pTab->zName
+ );
+ }
+
+ if( !isView && !IsVirtual(pTab) ){
+ destroyTable(pParse, pTab);
+ }
+
+ /* Remove the table entry from SQLite's internal schema and modify
+ ** the schema cookie.
+ */
+ if( IsVirtual(pTab) ){
+ sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0);
+ }
+ sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
+ sqlite3ChangeCookie(pParse, iDb);
+ }
+ sqliteViewResetAll(db, iDb);
+
+exit_drop_table:
+ sqlite3SrcListDelete(pName);
+}
+
+/*
+** This routine is called to create a new foreign key on the table
+** currently under construction. pFromCol determines which columns
+** in the current table point to the foreign key. If pFromCol==0 then
+** connect the key to the last column inserted. pTo is the name of
+** the table referred to. pToCol is a list of tables in the other
+** pTo table that the foreign key points to. flags contains all
+** information about the conflict resolution algorithms specified
+** in the ON DELETE, ON UPDATE and ON INSERT clauses.
+**
+** An FKey structure is created and added to the table currently
+** under construction in the pParse->pNewTable field. The new FKey
+** is not linked into db->aFKey at this point - that does not happen
+** until sqlite3EndTable().
+**
+** The foreign key is set for IMMEDIATE processing. A subsequent call
+** to sqlite3DeferForeignKey() might change this to DEFERRED.
+*/
+SQLITE_PRIVATE void sqlite3CreateForeignKey(
+ Parse *pParse, /* Parsing context */
+ ExprList *pFromCol, /* Columns in this table that point to other table */
+ Token *pTo, /* Name of the other table */
+ ExprList *pToCol, /* Columns in the other table */
+ int flags /* Conflict resolution algorithms. */
+){
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ FKey *pFKey = 0;
+ Table *p = pParse->pNewTable;
+ int nByte;
+ int i;
+ int nCol;
+ char *z;
+
+ assert( pTo!=0 );
+ if( p==0 || pParse->nErr || IN_DECLARE_VTAB ) goto fk_end;
+ if( pFromCol==0 ){
+ int iCol = p->nCol-1;
+ if( iCol<0 ) goto fk_end;
+ if( pToCol && pToCol->nExpr!=1 ){
+ sqlite3ErrorMsg(pParse, "foreign key on %s"
+ " should reference only one column of table %T",
+ p->aCol[iCol].zName, pTo);
+ goto fk_end;
+ }
+ nCol = 1;
+ }else if( pToCol && pToCol->nExpr!=pFromCol->nExpr ){
+ sqlite3ErrorMsg(pParse,
+ "number of columns in foreign key does not match the number of "
+ "columns in the referenced table");
+ goto fk_end;
+ }else{
+ nCol = pFromCol->nExpr;
+ }
+ nByte = sizeof(*pFKey) + nCol*sizeof(pFKey->aCol[0]) + pTo->n + 1;
+ if( pToCol ){
+ for(i=0; i<pToCol->nExpr; i++){
+ nByte += strlen(pToCol->a[i].zName) + 1;
+ }
+ }
+ pFKey = sqlite3DbMallocZero(pParse->db, nByte );
+ if( pFKey==0 ){
+ goto fk_end;
+ }
+ pFKey->pFrom = p;
+ pFKey->pNextFrom = p->pFKey;
+ z = (char*)&pFKey[1];
+ pFKey->aCol = (struct sColMap*)z;
+ z += sizeof(struct sColMap)*nCol;
+ pFKey->zTo = z;
+ memcpy(z, pTo->z, pTo->n);
+ z[pTo->n] = 0;
+ z += pTo->n+1;
+ pFKey->pNextTo = 0;
+ pFKey->nCol = nCol;
+ if( pFromCol==0 ){
+ pFKey->aCol[0].iFrom = p->nCol-1;
+ }else{
+ for(i=0; i<nCol; i++){
+ int j;
+ for(j=0; j<p->nCol; j++){
+ if( sqlite3StrICmp(p->aCol[j].zName, pFromCol->a[i].zName)==0 ){
+ pFKey->aCol[i].iFrom = j;
+ break;
+ }
+ }
+ if( j>=p->nCol ){
+ sqlite3ErrorMsg(pParse,
+ "unknown column \"%s\" in foreign key definition",
+ pFromCol->a[i].zName);
+ goto fk_end;
+ }
+ }
+ }
+ if( pToCol ){
+ for(i=0; i<nCol; i++){
+ int n = strlen(pToCol->a[i].zName);
+ pFKey->aCol[i].zCol = z;
+ memcpy(z, pToCol->a[i].zName, n);
+ z[n] = 0;
+ z += n+1;
+ }
+ }
+ pFKey->isDeferred = 0;
+ pFKey->deleteConf = flags & 0xff;
+ pFKey->updateConf = (flags >> 8 ) & 0xff;
+ pFKey->insertConf = (flags >> 16 ) & 0xff;
+
+ /* Link the foreign key to the table as the last step.
+ */
+ p->pFKey = pFKey;
+ pFKey = 0;
+
+fk_end:
+ sqlite3_free(pFKey);
+#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
+ sqlite3ExprListDelete(pFromCol);
+ sqlite3ExprListDelete(pToCol);
+}
+
+/*
+** This routine is called when an INITIALLY IMMEDIATE or INITIALLY DEFERRED
+** clause is seen as part of a foreign key definition. The isDeferred
+** parameter is 1 for INITIALLY DEFERRED and 0 for INITIALLY IMMEDIATE.
+** The behavior of the most recently created foreign key is adjusted
+** accordingly.
+*/
+SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ Table *pTab;
+ FKey *pFKey;
+ if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return;
+ pFKey->isDeferred = isDeferred;
+#endif
+}
+
+/*
+** Generate code that will erase and refill index *pIdx. This is
+** used to initialize a newly created index or to recompute the
+** content of an index in response to a REINDEX command.
+**
+** if memRootPage is not negative, it means that the index is newly
+** created. The register specified by memRootPage contains the
+** root page number of the index. If memRootPage is negative, then
+** the index already exists and must be cleared before being refilled and
+** the root page number of the index is taken from pIndex->tnum.
+*/
+static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
+ Table *pTab = pIndex->pTable; /* The table that is indexed */
+ int iTab = pParse->nTab; /* Btree cursor used for pTab */
+ int iIdx = pParse->nTab+1; /* Btree cursor used for pIndex */
+ int addr1; /* Address of top of loop */
+ int tnum; /* Root page of index */
+ Vdbe *v; /* Generate code into this virtual machine */
+ KeyInfo *pKey; /* KeyInfo for index */
+ int regIdxKey; /* Registers containing the index key */
+ int regRecord; /* Register holding assemblied index record */
+ sqlite3 *db = pParse->db; /* The database connection */
+ int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema);
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0,
+ db->aDb[iDb].zName ) ){
+ return;
+ }
+#endif
+
+ /* Require a write-lock on the table to perform this operation */
+ sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
+
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+ if( memRootPage>=0 ){
+ tnum = memRootPage;
+ }else{
+ tnum = pIndex->tnum;
+ sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb);
+ }
+ pKey = sqlite3IndexKeyinfo(pParse, pIndex);
+ sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb,
+ (char *)pKey, P4_KEYINFO_HANDOFF);
+ if( memRootPage>=0 ){
+ sqlite3VdbeChangeP5(v, 1);
+ }
+ sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0);
+ regRecord = sqlite3GetTempReg(pParse);
+ regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1);
+ if( pIndex->onError!=OE_None ){
+ int j1, j2;
+ int regRowid;
+
+ regRowid = regIdxKey + pIndex->nColumn;
+ j1 = sqlite3VdbeAddOp3(v, OP_IsNull, regIdxKey, 0, pIndex->nColumn);
+ j2 = sqlite3VdbeAddOp4(v, OP_IsUnique, iIdx,
+ 0, regRowid, (char*)regRecord, P4_INT32);
+ sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CONSTRAINT, OE_Abort, 0,
+ "indexed columns are not unique", P4_STATIC);
+ sqlite3VdbeJumpHere(v, j1);
+ sqlite3VdbeJumpHere(v, j2);
+ }
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord);
+ sqlite3ReleaseTempReg(pParse, regRecord);
+ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp1(v, OP_Close, iTab);
+ sqlite3VdbeAddOp1(v, OP_Close, iIdx);
+}
+
+/*
+** Create a new index for an SQL table. pName1.pName2 is the name of the index
+** and pTblList is the name of the table that is to be indexed. Both will
+** be NULL for a primary key or an index that is created to satisfy a
+** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable
+** as the table to be indexed. pParse->pNewTable is a table that is
+** currently being constructed by a CREATE TABLE statement.
+**
+** pList is a list of columns to be indexed. pList will be NULL if this
+** is a primary key or unique-constraint on the most recent column added
+** to the table currently under construction.
+*/
+SQLITE_PRIVATE void sqlite3CreateIndex(
+ Parse *pParse, /* All information about this parse */
+ Token *pName1, /* First part of index name. May be NULL */
+ Token *pName2, /* Second part of index name. May be NULL */
+ SrcList *pTblName, /* Table to index. Use pParse->pNewTable if 0 */
+ ExprList *pList, /* A list of columns to be indexed */
+ int onError, /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
+ Token *pStart, /* The CREATE token that begins this statement */
+ Token *pEnd, /* The ")" that closes the CREATE INDEX statement */
+ int sortOrder, /* Sort order of primary key when pList==NULL */
+ int ifNotExist /* Omit error if index already exists */
+){
+ Table *pTab = 0; /* Table to be indexed */
+ Index *pIndex = 0; /* The index to be created */
+ char *zName = 0; /* Name of the index */
+ int nName; /* Number of characters in zName */
+ int i, j;
+ Token nullId; /* Fake token for an empty ID list */
+ DbFixer sFix; /* For assigning database names to pTable */
+ int sortOrderMask; /* 1 to honor DESC in index. 0 to ignore. */
+ sqlite3 *db = pParse->db;
+ Db *pDb; /* The specific table containing the indexed database */
+ int iDb; /* Index of the database that is being written */
+ Token *pName = 0; /* Unqualified name of the index to create */
+ struct ExprList_item *pListItem; /* For looping over pList */
+ int nCol;
+ int nExtra = 0;
+ char *zExtra;
+
+ if( pParse->nErr || db->mallocFailed || IN_DECLARE_VTAB ){
+ goto exit_create_index;
+ }
+
+ /*
+ ** Find the table that is to be indexed. Return early if not found.
+ */
+ if( pTblName!=0 ){
+
+ /* Use the two-part index name to determine the database
+ ** to search for the table. 'Fix' the table name to this db
+ ** before looking up the table.
+ */
+ assert( pName1 && pName2 );
+ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ if( iDb<0 ) goto exit_create_index;
+
+#ifndef SQLITE_OMIT_TEMPDB
+ /* If the index name was unqualified, check if the the table
+ ** is a temp table. If so, set the database to 1. Do not do this
+ ** if initialising a database schema.
+ */
+ if( !db->init.busy ){
+ pTab = sqlite3SrcListLookup(pParse, pTblName);
+ if( pName2 && pName2->n==0 && pTab && pTab->pSchema==db->aDb[1].pSchema ){
+ iDb = 1;
+ }
+ }
+#endif
+
+ if( sqlite3FixInit(&sFix, pParse, iDb, "index", pName) &&
+ sqlite3FixSrcList(&sFix, pTblName)
+ ){
+ /* Because the parser constructs pTblName from a single identifier,
+ ** sqlite3FixSrcList can never fail. */
+ assert(0);
+ }
+ pTab = sqlite3LocateTable(pParse, 0, pTblName->a[0].zName,
+ pTblName->a[0].zDatabase);
+ if( !pTab ) goto exit_create_index;
+ assert( db->aDb[iDb].pSchema==pTab->pSchema );
+ }else{
+ assert( pName==0 );
+ pTab = pParse->pNewTable;
+ if( !pTab ) goto exit_create_index;
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ }
+ pDb = &db->aDb[iDb];
+
+ if( pTab==0 || pParse->nErr ) goto exit_create_index;
+ if( pTab->readOnly ){
+ sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
+ goto exit_create_index;
+ }
+#ifndef SQLITE_OMIT_VIEW
+ if( pTab->pSelect ){
+ sqlite3ErrorMsg(pParse, "views may not be indexed");
+ goto exit_create_index;
+ }
+#endif
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTab) ){
+ sqlite3ErrorMsg(pParse, "virtual tables may not be indexed");
+ goto exit_create_index;
+ }
+#endif
+
+ /*
+ ** Find the name of the index. Make sure there is not already another
+ ** index or table with the same name.
+ **
+ ** Exception: If we are reading the names of permanent indices from the
+ ** sqlite_master table (because some other process changed the schema) and
+ ** one of the index names collides with the name of a temporary table or
+ ** index, then we will continue to process this index.
+ **
+ ** If pName==0 it means that we are
+ ** dealing with a primary key or UNIQUE constraint. We have to invent our
+ ** own name.
+ */
+ if( pName ){
+ zName = sqlite3NameFromToken(db, pName);
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto exit_create_index;
+ if( zName==0 ) goto exit_create_index;
+ if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ goto exit_create_index;
+ }
+ if( !db->init.busy ){
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto exit_create_index;
+ if( sqlite3FindTable(db, zName, 0)!=0 ){
+ sqlite3ErrorMsg(pParse, "there is already a table named %s", zName);
+ goto exit_create_index;
+ }
+ }
+ if( sqlite3FindIndex(db, zName, pDb->zName)!=0 ){
+ if( !ifNotExist ){
+ sqlite3ErrorMsg(pParse, "index %s already exists", zName);
+ }
+ goto exit_create_index;
+ }
+ }else{
+ char zBuf[30];
+ int n;
+ Index *pLoop;
+ for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){}
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"_%d",n);
+ zName = 0;
+ sqlite3SetString(&zName, "sqlite_autoindex_", pTab->zName, zBuf, (char*)0);
+ if( zName==0 ){
+ db->mallocFailed = 1;
+ goto exit_create_index;
+ }
+ }
+
+ /* Check for authorization to create an index.
+ */
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ const char *zDb = pDb->zName;
+ if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iDb), 0, zDb) ){
+ goto exit_create_index;
+ }
+ i = SQLITE_CREATE_INDEX;
+ if( !OMIT_TEMPDB && iDb==1 ) i = SQLITE_CREATE_TEMP_INDEX;
+ if( sqlite3AuthCheck(pParse, i, zName, pTab->zName, zDb) ){
+ goto exit_create_index;
+ }
+ }
+#endif
+
+ /* If pList==0, it means this routine was called to make a primary
+ ** key out of the last column added to the table under construction.
+ ** So create a fake list to simulate this.
+ */
+ if( pList==0 ){
+ nullId.z = (u8*)pTab->aCol[pTab->nCol-1].zName;
+ nullId.n = strlen((char*)nullId.z);
+ pList = sqlite3ExprListAppend(pParse, 0, 0, &nullId);
+ if( pList==0 ) goto exit_create_index;
+ pList->a[0].sortOrder = sortOrder;
+ }
+
+ /* Figure out how many bytes of space are required to store explicitly
+ ** specified collation sequence names.
+ */
+ for(i=0; i<pList->nExpr; i++){
+ Expr *pExpr = pList->a[i].pExpr;
+ if( pExpr ){
+ nExtra += (1 + strlen(pExpr->pColl->zName));
+ }
+ }
+
+ /*
+ ** Allocate the index structure.
+ */
+ nName = strlen(zName);
+ nCol = pList->nExpr;
+ pIndex = sqlite3DbMallocZero(db,
+ sizeof(Index) + /* Index structure */
+ sizeof(int)*nCol + /* Index.aiColumn */
+ sizeof(int)*(nCol+1) + /* Index.aiRowEst */
+ sizeof(char *)*nCol + /* Index.azColl */
+ sizeof(u8)*nCol + /* Index.aSortOrder */
+ nName + 1 + /* Index.zName */
+ nExtra /* Collation sequence names */
+ );
+ if( db->mallocFailed ){
+ goto exit_create_index;
+ }
+ pIndex->azColl = (char**)(&pIndex[1]);
+ pIndex->aiColumn = (int *)(&pIndex->azColl[nCol]);
+ pIndex->aiRowEst = (unsigned *)(&pIndex->aiColumn[nCol]);
+ pIndex->aSortOrder = (u8 *)(&pIndex->aiRowEst[nCol+1]);
+ pIndex->zName = (char *)(&pIndex->aSortOrder[nCol]);
+ zExtra = (char *)(&pIndex->zName[nName+1]);
+ memcpy(pIndex->zName, zName, nName+1);
+ pIndex->pTable = pTab;
+ pIndex->nColumn = pList->nExpr;
+ pIndex->onError = onError;
+ pIndex->autoIndex = pName==0;
+ pIndex->pSchema = db->aDb[iDb].pSchema;
+
+ /* Check to see if we should honor DESC requests on index columns
+ */
+ if( pDb->pSchema->file_format>=4 ){
+ sortOrderMask = -1; /* Honor DESC */
+ }else{
+ sortOrderMask = 0; /* Ignore DESC */
+ }
+
+ /* Scan the names of the columns of the table to be indexed and
+ ** load the column indices into the Index structure. Report an error
+ ** if any column is not found.
+ */
+ for(i=0, pListItem=pList->a; i<pList->nExpr; i++, pListItem++){
+ const char *zColName = pListItem->zName;
+ Column *pTabCol;
+ int requestedSortOrder;
+ char *zColl; /* Collation sequence name */
+
+ for(j=0, pTabCol=pTab->aCol; j<pTab->nCol; j++, pTabCol++){
+ if( sqlite3StrICmp(zColName, pTabCol->zName)==0 ) break;
+ }
+ if( j>=pTab->nCol ){
+ sqlite3ErrorMsg(pParse, "table %s has no column named %s",
+ pTab->zName, zColName);
+ goto exit_create_index;
+ }
+ /* TODO: Add a test to make sure that the same column is not named
+ ** more than once within the same index. Only the first instance of
+ ** the column will ever be used by the optimizer. Note that using the
+ ** same column more than once cannot be an error because that would
+ ** break backwards compatibility - it needs to be a warning.
+ */
+ pIndex->aiColumn[i] = j;
+ if( pListItem->pExpr ){
+ assert( pListItem->pExpr->pColl );
+ zColl = zExtra;
+ sqlite3_snprintf(nExtra, zExtra, "%s", pListItem->pExpr->pColl->zName);
+ zExtra += (strlen(zColl) + 1);
+ }else{
+ zColl = pTab->aCol[j].zColl;
+ if( !zColl ){
+ zColl = db->pDfltColl->zName;
+ }
+ }
+ if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl, -1) ){
+ goto exit_create_index;
+ }
+ pIndex->azColl[i] = zColl;
+ requestedSortOrder = pListItem->sortOrder & sortOrderMask;
+ pIndex->aSortOrder[i] = requestedSortOrder;
+ }
+ sqlite3DefaultRowEst(pIndex);
+
+ if( pTab==pParse->pNewTable ){
+ /* This routine has been called to create an automatic index as a
+ ** result of a PRIMARY KEY or UNIQUE clause on a column definition, or
+ ** a PRIMARY KEY or UNIQUE clause following the column definitions.
+ ** i.e. one of:
+ **
+ ** CREATE TABLE t(x PRIMARY KEY, y);
+ ** CREATE TABLE t(x, y, UNIQUE(x, y));
+ **
+ ** Either way, check to see if the table already has such an index. If
+ ** so, don't bother creating this one. This only applies to
+ ** automatically created indices. Users can do as they wish with
+ ** explicit indices.
+ */
+ Index *pIdx;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int k;
+ assert( pIdx->onError!=OE_None );
+ assert( pIdx->autoIndex );
+ assert( pIndex->onError!=OE_None );
+
+ if( pIdx->nColumn!=pIndex->nColumn ) continue;
+ for(k=0; k<pIdx->nColumn; k++){
+ const char *z1 = pIdx->azColl[k];
+ const char *z2 = pIndex->azColl[k];
+ if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break;
+ if( pIdx->aSortOrder[k]!=pIndex->aSortOrder[k] ) break;
+ if( z1!=z2 && sqlite3StrICmp(z1, z2) ) break;
+ }
+ if( k==pIdx->nColumn ){
+ if( pIdx->onError!=pIndex->onError ){
+ /* This constraint creates the same index as a previous
+ ** constraint specified somewhere in the CREATE TABLE statement.
+ ** However the ON CONFLICT clauses are different. If both this
+ ** constraint and the previous equivalent constraint have explicit
+ ** ON CONFLICT clauses this is an error. Otherwise, use the
+ ** explicitly specified behaviour for the index.
+ */
+ if( !(pIdx->onError==OE_Default || pIndex->onError==OE_Default) ){
+ sqlite3ErrorMsg(pParse,
+ "conflicting ON CONFLICT clauses specified", 0);
+ }
+ if( pIdx->onError==OE_Default ){
+ pIdx->onError = pIndex->onError;
+ }
+ }
+ goto exit_create_index;
+ }
+ }
+ }
+
+ /* Link the new Index structure to its table and to the other
+ ** in-memory database structures.
+ */
+ if( db->init.busy ){
+ Index *p;
+ p = sqlite3HashInsert(&pIndex->pSchema->idxHash,
+ pIndex->zName, strlen(pIndex->zName)+1, pIndex);
+ if( p ){
+ assert( p==pIndex ); /* Malloc must have failed */
+ db->mallocFailed = 1;
+ goto exit_create_index;
+ }
+ db->flags |= SQLITE_InternChanges;
+ if( pTblName!=0 ){
+ pIndex->tnum = db->init.newTnum;
+ }
+ }
+
+ /* If the db->init.busy is 0 then create the index on disk. This
+ ** involves writing the index into the master table and filling in the
+ ** index with the current table contents.
+ **
+ ** The db->init.busy is 0 when the user first enters a CREATE INDEX
+ ** command. db->init.busy is 1 when a database is opened and
+ ** CREATE INDEX statements are read out of the master table. In
+ ** the latter case the index already exists on disk, which is why
+ ** we don't want to recreate it.
+ **
+ ** If pTblName==0 it means this index is generated as a primary key
+ ** or UNIQUE constraint of a CREATE TABLE statement. Since the table
+ ** has just been created, it contains no data and the index initialization
+ ** step can be skipped.
+ */
+ else if( db->init.busy==0 ){
+ Vdbe *v;
+ char *zStmt;
+ int iMem = ++pParse->nMem;
+
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto exit_create_index;
+
+
+ /* Create the rootpage for the index
+ */
+ sqlite3BeginWriteOperation(pParse, 1, iDb);
+ sqlite3VdbeAddOp2(v, OP_CreateIndex, iDb, iMem);
+
+ /* Gather the complete text of the CREATE INDEX statement into
+ ** the zStmt variable
+ */
+ if( pStart && pEnd ){
+ /* A named index with an explicit CREATE INDEX statement */
+ zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
+ onError==OE_None ? "" : " UNIQUE",
+ pEnd->z - pName->z + 1,
+ pName->z);
+ }else{
+ /* An automatic index created by a PRIMARY KEY or UNIQUE constraint */
+ /* zStmt = sqlite3MPrintf(""); */
+ zStmt = 0;
+ }
+
+ /* Add an entry in sqlite_master for this index
+ */
+ sqlite3NestedParse(pParse,
+ "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
+ pIndex->zName,
+ pTab->zName,
+ iMem,
+ zStmt
+ );
+ sqlite3_free(zStmt);
+
+ /* Fill the index with data and reparse the schema. Code an OP_Expire
+ ** to invalidate all pre-compiled statements.
+ */
+ if( pTblName ){
+ sqlite3RefillIndex(pParse, pIndex, iMem);
+ sqlite3ChangeCookie(pParse, iDb);
+ sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0,
+ sqlite3MPrintf(db, "name='%q'", pIndex->zName), P4_DYNAMIC);
+ sqlite3VdbeAddOp1(v, OP_Expire, 0);
+ }
+ }
+
+ /* When adding an index to the list of indices for a table, make
+ ** sure all indices labeled OE_Replace come after all those labeled
+ ** OE_Ignore. This is necessary for the correct operation of UPDATE
+ ** and INSERT.
+ */
+ if( db->init.busy || pTblName==0 ){
+ if( onError!=OE_Replace || pTab->pIndex==0
+ || pTab->pIndex->onError==OE_Replace){
+ pIndex->pNext = pTab->pIndex;
+ pTab->pIndex = pIndex;
+ }else{
+ Index *pOther = pTab->pIndex;
+ while( pOther->pNext && pOther->pNext->onError!=OE_Replace ){
+ pOther = pOther->pNext;
+ }
+ pIndex->pNext = pOther->pNext;
+ pOther->pNext = pIndex;
+ }
+ pIndex = 0;
+ }
+
+ /* Clean up before exiting */
+exit_create_index:
+ if( pIndex ){
+ freeIndex(pIndex);
+ }
+ sqlite3ExprListDelete(pList);
+ sqlite3SrcListDelete(pTblName);
+ sqlite3_free(zName);
+ return;
+}
+
+/*
+** Generate code to make sure the file format number is at least minFormat.
+** The generated code will increase the file format number if necessary.
+*/
+SQLITE_PRIVATE void sqlite3MinimumFileFormat(Parse *pParse, int iDb, int minFormat){
+ Vdbe *v;
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ int r1 = sqlite3GetTempReg(pParse);
+ int r2 = sqlite3GetTempReg(pParse);
+ int j1;
+ sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, 1);
+ sqlite3VdbeUsesBtree(v, iDb);
+ sqlite3VdbeAddOp2(v, OP_Integer, minFormat, r2);
+ j1 = sqlite3VdbeAddOp3(v, OP_Ge, r2, 0, r1);
+ sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, 1, r2);
+ sqlite3VdbeJumpHere(v, j1);
+ sqlite3ReleaseTempReg(pParse, r1);
+ sqlite3ReleaseTempReg(pParse, r2);
+ }
+}
+
+/*
+** Fill the Index.aiRowEst[] array with default information - information
+** to be used when we have not run the ANALYZE command.
+**
+** aiRowEst[0] is suppose to contain the number of elements in the index.
+** Since we do not know, guess 1 million. aiRowEst[1] is an estimate of the
+** number of rows in the table that match any particular value of the
+** first column of the index. aiRowEst[2] is an estimate of the number
+** of rows that match any particular combiniation of the first 2 columns
+** of the index. And so forth. It must always be the case that
+*
+** aiRowEst[N]<=aiRowEst[N-1]
+** aiRowEst[N]>=1
+**
+** Apart from that, we have little to go on besides intuition as to
+** how aiRowEst[] should be initialized. The numbers generated here
+** are based on typical values found in actual indices.
+*/
+SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){
+ unsigned *a = pIdx->aiRowEst;
+ int i;
+ assert( a!=0 );
+ a[0] = 1000000;
+ for(i=pIdx->nColumn; i>=5; i--){
+ a[i] = 5;
+ }
+ while( i>=1 ){
+ a[i] = 11 - i;
+ i--;
+ }
+ if( pIdx->onError!=OE_None ){
+ a[pIdx->nColumn] = 1;
+ }
+}
+
+/*
+** This routine will drop an existing named index. This routine
+** implements the DROP INDEX statement.
+*/
+SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
+ Index *pIndex;
+ Vdbe *v;
+ sqlite3 *db = pParse->db;
+ int iDb;
+
+ if( pParse->nErr || db->mallocFailed ){
+ goto exit_drop_index;
+ }
+ assert( pName->nSrc==1 );
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ goto exit_drop_index;
+ }
+ pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
+ if( pIndex==0 ){
+ if( !ifExists ){
+ sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0);
+ }
+ pParse->checkSchema = 1;
+ goto exit_drop_index;
+ }
+ if( pIndex->autoIndex ){
+ sqlite3ErrorMsg(pParse, "index associated with UNIQUE "
+ "or PRIMARY KEY constraint cannot be dropped", 0);
+ goto exit_drop_index;
+ }
+ iDb = sqlite3SchemaToIndex(db, pIndex->pSchema);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code = SQLITE_DROP_INDEX;
+ Table *pTab = pIndex->pTable;
+ const char *zDb = db->aDb[iDb].zName;
+ const char *zTab = SCHEMA_TABLE(iDb);
+ if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
+ goto exit_drop_index;
+ }
+ if( !OMIT_TEMPDB && iDb ) code = SQLITE_DROP_TEMP_INDEX;
+ if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){
+ goto exit_drop_index;
+ }
+ }
+#endif
+
+ /* Generate code to remove the index and from the master table */
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3BeginWriteOperation(pParse, 1, iDb);
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %Q.%s WHERE name=%Q",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
+ pIndex->zName
+ );
+ if( sqlite3FindTable(db, "sqlite_stat1", db->aDb[iDb].zName) ){
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %Q.sqlite_stat1 WHERE idx=%Q",
+ db->aDb[iDb].zName, pIndex->zName
+ );
+ }
+ sqlite3ChangeCookie(pParse, iDb);
+ destroyRootPage(pParse, pIndex->tnum, iDb);
+ sqlite3VdbeAddOp4(v, OP_DropIndex, iDb, 0, 0, pIndex->zName, 0);
+ }
+
+exit_drop_index:
+ sqlite3SrcListDelete(pName);
+}
+
+/*
+** pArray is a pointer to an array of objects. Each object in the
+** array is szEntry bytes in size. This routine allocates a new
+** object on the end of the array.
+**
+** *pnEntry is the number of entries already in use. *pnAlloc is
+** the previously allocated size of the array. initSize is the
+** suggested initial array size allocation.
+**
+** The index of the new entry is returned in *pIdx.
+**
+** This routine returns a pointer to the array of objects. This
+** might be the same as the pArray parameter or it might be a different
+** pointer if the array was resized.
+*/
+SQLITE_PRIVATE void *sqlite3ArrayAllocate(
+ sqlite3 *db, /* Connection to notify of malloc failures */
+ void *pArray, /* Array of objects. Might be reallocated */
+ int szEntry, /* Size of each object in the array */
+ int initSize, /* Suggested initial allocation, in elements */
+ int *pnEntry, /* Number of objects currently in use */
+ int *pnAlloc, /* Current size of the allocation, in elements */
+ int *pIdx /* Write the index of a new slot here */
+){
+ char *z;
+ if( *pnEntry >= *pnAlloc ){
+ void *pNew;
+ int newSize;
+ newSize = (*pnAlloc)*2 + initSize;
+ pNew = sqlite3DbRealloc(db, pArray, newSize*szEntry);
+ if( pNew==0 ){
+ *pIdx = -1;
+ return pArray;
+ }
+ *pnAlloc = newSize;
+ pArray = pNew;
+ }
+ z = (char*)pArray;
+ memset(&z[*pnEntry * szEntry], 0, szEntry);
+ *pIdx = *pnEntry;
+ ++*pnEntry;
+ return pArray;
+}
+
+/*
+** Append a new element to the given IdList. Create a new IdList if
+** need be.
+**
+** A new IdList is returned, or NULL if malloc() fails.
+*/
+SQLITE_PRIVATE IdList *sqlite3IdListAppend(sqlite3 *db, IdList *pList, Token *pToken){
+ int i;
+ if( pList==0 ){
+ pList = sqlite3DbMallocZero(db, sizeof(IdList) );
+ if( pList==0 ) return 0;
+ pList->nAlloc = 0;
+ }
+ pList->a = sqlite3ArrayAllocate(
+ db,
+ pList->a,
+ sizeof(pList->a[0]),
+ 5,
+ &pList->nId,
+ &pList->nAlloc,
+ &i
+ );
+ if( i<0 ){
+ sqlite3IdListDelete(pList);
+ return 0;
+ }
+ pList->a[i].zName = sqlite3NameFromToken(db, pToken);
+ return pList;
+}
+
+/*
+** Delete an IdList.
+*/
+SQLITE_PRIVATE void sqlite3IdListDelete(IdList *pList){
+ int i;
+ if( pList==0 ) return;
+ for(i=0; i<pList->nId; i++){
+ sqlite3_free(pList->a[i].zName);
+ }
+ sqlite3_free(pList->a);
+ sqlite3_free(pList);
+}
+
+/*
+** Return the index in pList of the identifier named zId. Return -1
+** if not found.
+*/
+SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){
+ int i;
+ if( pList==0 ) return -1;
+ for(i=0; i<pList->nId; i++){
+ if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i;
+ }
+ return -1;
+}
+
+/*
+** Append a new table name to the given SrcList. Create a new SrcList if
+** need be. A new entry is created in the SrcList even if pToken is NULL.
+**
+** A new SrcList is returned, or NULL if malloc() fails.
+**
+** If pDatabase is not null, it means that the table has an optional
+** database name prefix. Like this: "database.table". The pDatabase
+** points to the table name and the pTable points to the database name.
+** The SrcList.a[].zName field is filled with the table name which might
+** come from pTable (if pDatabase is NULL) or from pDatabase.
+** SrcList.a[].zDatabase is filled with the database name from pTable,
+** or with NULL if no database is specified.
+**
+** In other words, if call like this:
+**
+** sqlite3SrcListAppend(D,A,B,0);
+**
+** Then B is a table name and the database name is unspecified. If called
+** like this:
+**
+** sqlite3SrcListAppend(D,A,B,C);
+**
+** Then C is the table name and B is the database name.
+*/
+SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(
+ sqlite3 *db, /* Connection to notify of malloc failures */
+ SrcList *pList, /* Append to this SrcList. NULL creates a new SrcList */
+ Token *pTable, /* Table to append */
+ Token *pDatabase /* Database of the table */
+){
+ struct SrcList_item *pItem;
+ if( pList==0 ){
+ pList = sqlite3DbMallocZero(db, sizeof(SrcList) );
+ if( pList==0 ) return 0;
+ pList->nAlloc = 1;
+ }
+ if( pList->nSrc>=pList->nAlloc ){
+ SrcList *pNew;
+ pList->nAlloc *= 2;
+ pNew = sqlite3DbRealloc(db, pList,
+ sizeof(*pList) + (pList->nAlloc-1)*sizeof(pList->a[0]) );
+ if( pNew==0 ){
+ sqlite3SrcListDelete(pList);
+ return 0;
+ }
+ pList = pNew;
+ }
+ pItem = &pList->a[pList->nSrc];
+ memset(pItem, 0, sizeof(pList->a[0]));
+ if( pDatabase && pDatabase->z==0 ){
+ pDatabase = 0;
+ }
+ if( pDatabase && pTable ){
+ Token *pTemp = pDatabase;
+ pDatabase = pTable;
+ pTable = pTemp;
+ }
+ pItem->zName = sqlite3NameFromToken(db, pTable);
+ pItem->zDatabase = sqlite3NameFromToken(db, pDatabase);
+ pItem->iCursor = -1;
+ pItem->isPopulated = 0;
+ pList->nSrc++;
+ return pList;
+}
+
+/*
+** Assign cursors to all tables in a SrcList
+*/
+SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
+ int i;
+ struct SrcList_item *pItem;
+ assert(pList || pParse->db->mallocFailed );
+ if( pList ){
+ for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
+ if( pItem->iCursor>=0 ) break;
+ pItem->iCursor = pParse->nTab++;
+ if( pItem->pSelect ){
+ sqlite3SrcListAssignCursors(pParse, pItem->pSelect->pSrc);
+ }
+ }
+ }
+}
+
+/*
+** Delete an entire SrcList including all its substructure.
+*/
+SQLITE_PRIVATE void sqlite3SrcListDelete(SrcList *pList){
+ int i;
+ struct SrcList_item *pItem;
+ if( pList==0 ) return;
+ for(pItem=pList->a, i=0; i<pList->nSrc; i++, pItem++){
+ sqlite3_free(pItem->zDatabase);
+ sqlite3_free(pItem->zName);
+ sqlite3_free(pItem->zAlias);
+ sqlite3DeleteTable(pItem->pTab);
+ sqlite3SelectDelete(pItem->pSelect);
+ sqlite3ExprDelete(pItem->pOn);
+ sqlite3IdListDelete(pItem->pUsing);
+ }
+ sqlite3_free(pList);
+}
+
+/*
+** This routine is called by the parser to add a new term to the
+** end of a growing FROM clause. The "p" parameter is the part of
+** the FROM clause that has already been constructed. "p" is NULL
+** if this is the first term of the FROM clause. pTable and pDatabase
+** are the name of the table and database named in the FROM clause term.
+** pDatabase is NULL if the database name qualifier is missing - the
+** usual case. If the term has a alias, then pAlias points to the
+** alias token. If the term is a subquery, then pSubquery is the
+** SELECT statement that the subquery encodes. The pTable and
+** pDatabase parameters are NULL for subqueries. The pOn and pUsing
+** parameters are the content of the ON and USING clauses.
+**
+** Return a new SrcList which encodes is the FROM with the new
+** term added.
+*/
+SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
+ Parse *pParse, /* Parsing context */
+ SrcList *p, /* The left part of the FROM clause already seen */
+ Token *pTable, /* Name of the table to add to the FROM clause */
+ Token *pDatabase, /* Name of the database containing pTable */
+ Token *pAlias, /* The right-hand side of the AS subexpression */
+ Select *pSubquery, /* A subquery used in place of a table name */
+ Expr *pOn, /* The ON clause of a join */
+ IdList *pUsing /* The USING clause of a join */
+){
+ struct SrcList_item *pItem;
+ sqlite3 *db = pParse->db;
+ p = sqlite3SrcListAppend(db, p, pTable, pDatabase);
+ if( p==0 || p->nSrc==0 ){
+ sqlite3ExprDelete(pOn);
+ sqlite3IdListDelete(pUsing);
+ sqlite3SelectDelete(pSubquery);
+ return p;
+ }
+ pItem = &p->a[p->nSrc-1];
+ if( pAlias && pAlias->n ){
+ pItem->zAlias = sqlite3NameFromToken(db, pAlias);
+ }
+ pItem->pSelect = pSubquery;
+ pItem->pOn = pOn;
+ pItem->pUsing = pUsing;
+ return p;
+}
+
+/*
+** When building up a FROM clause in the parser, the join operator
+** is initially attached to the left operand. But the code generator
+** expects the join operator to be on the right operand. This routine
+** Shifts all join operators from left to right for an entire FROM
+** clause.
+**
+** Example: Suppose the join is like this:
+**
+** A natural cross join B
+**
+** The operator is "natural cross join". The A and B operands are stored
+** in p->a[0] and p->a[1], respectively. The parser initially stores the
+** operator with A. This routine shifts that operator over to B.
+*/
+SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList *p){
+ if( p && p->a ){
+ int i;
+ for(i=p->nSrc-1; i>0; i--){
+ p->a[i].jointype = p->a[i-1].jointype;
+ }
+ p->a[0].jointype = 0;
+ }
+}
+
+/*
+** Begin a transaction
+*/
+SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){
+ sqlite3 *db;
+ Vdbe *v;
+ int i;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || db->mallocFailed ) return;
+ if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ) return;
+
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) return;
+ if( type!=TK_DEFERRED ){
+ for(i=0; i<db->nDb; i++){
+ sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
+ sqlite3VdbeUsesBtree(v, i);
+ }
+ }
+ sqlite3VdbeAddOp2(v, OP_AutoCommit, 0, 0);
+}
+
+/*
+** Commit a transaction
+*/
+SQLITE_PRIVATE void sqlite3CommitTransaction(Parse *pParse){
+ sqlite3 *db;
+ Vdbe *v;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || db->mallocFailed ) return;
+ if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ) return;
+
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, 0);
+ }
+}
+
+/*
+** Rollback a transaction
+*/
+SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse *pParse){
+ sqlite3 *db;
+ Vdbe *v;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || db->mallocFailed ) return;
+ if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ) return;
+
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, 1);
+ }
+}
+
+/*
+** Make sure the TEMP database is open and available for use. Return
+** the number of errors. Leave any error messages in the pParse structure.
+*/
+SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){
+ sqlite3 *db = pParse->db;
+ if( db->aDb[1].pBt==0 && !pParse->explain ){
+ int rc;
+ static const int flags =
+ SQLITE_OPEN_READWRITE |
+ SQLITE_OPEN_CREATE |
+ SQLITE_OPEN_EXCLUSIVE |
+ SQLITE_OPEN_DELETEONCLOSE |
+ SQLITE_OPEN_TEMP_DB;
+
+ rc = sqlite3BtreeFactory(db, 0, 0, SQLITE_DEFAULT_CACHE_SIZE, flags,
+ &db->aDb[1].pBt);
+ if( rc!=SQLITE_OK ){
+ sqlite3ErrorMsg(pParse, "unable to open a temporary database "
+ "file for storing temporary tables");
+ pParse->rc = rc;
+ return 1;
+ }
+ assert( (db->flags & SQLITE_InTrans)==0 || db->autoCommit );
+ assert( db->aDb[1].pSchema );
+ sqlite3PagerJournalMode(sqlite3BtreePager(db->aDb[1].pBt),
+ db->dfltJournalMode);
+ }
+ return 0;
+}
+
+/*
+** Generate VDBE code that will verify the schema cookie and start
+** a read-transaction for all named database files.
+**
+** It is important that all schema cookies be verified and all
+** read transactions be started before anything else happens in
+** the VDBE program. But this routine can be called after much other
+** code has been generated. So here is what we do:
+**
+** The first time this routine is called, we code an OP_Goto that
+** will jump to a subroutine at the end of the program. Then we
+** record every database that needs its schema verified in the
+** pParse->cookieMask field. Later, after all other code has been
+** generated, the subroutine that does the cookie verifications and
+** starts the transactions will be coded and the OP_Goto P2 value
+** will be made to point to that subroutine. The generation of the
+** cookie verification subroutine code happens in sqlite3FinishCoding().
+**
+** If iDb<0 then code the OP_Goto only - don't set flag to verify the
+** schema on any databases. This can be used to position the OP_Goto
+** early in the code, before we know if any database tables will be used.
+*/
+SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
+ sqlite3 *db;
+ Vdbe *v;
+ int mask;
+
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return; /* This only happens if there was a prior error */
+ db = pParse->db;
+ if( pParse->cookieGoto==0 ){
+ pParse->cookieGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0)+1;
+ }
+ if( iDb>=0 ){
+ assert( iDb<db->nDb );
+ assert( db->aDb[iDb].pBt!=0 || iDb==1 );
+ assert( iDb<SQLITE_MAX_ATTACHED+2 );
+ mask = 1<<iDb;
+ if( (pParse->cookieMask & mask)==0 ){
+ pParse->cookieMask |= mask;
+ pParse->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie;
+ if( !OMIT_TEMPDB && iDb==1 ){
+ sqlite3OpenTempDatabase(pParse);
+ }
+ }
+ }
+}
+
+/*
+** Generate VDBE code that prepares for doing an operation that
+** might change the database.
+**
+** This routine starts a new transaction if we are not already within
+** a transaction. If we are already within a transaction, then a checkpoint
+** is set if the setStatement parameter is true. A checkpoint should
+** be set for operations that might fail (due to a constraint) part of
+** the way through and which will need to undo some writes without having to
+** rollback the whole transaction. For operations where all constraints
+** can be checked before any changes are made to the database, it is never
+** necessary to undo a write and the checkpoint should not be set.
+**
+** Only database iDb and the temp database are made writable by this call.
+** If iDb==0, then the main and temp databases are made writable. If
+** iDb==1 then only the temp database is made writable. If iDb>1 then the
+** specified auxiliary database and the temp database are made writable.
+*/
+SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+ sqlite3CodeVerifySchema(pParse, iDb);
+ pParse->writeMask |= 1<<iDb;
+ if( setStatement && pParse->nested==0 ){
+ sqlite3VdbeAddOp1(v, OP_Statement, iDb);
+ }
+ if( (OMIT_TEMPDB || iDb!=1) && pParse->db->aDb[1].pBt!=0 ){
+ sqlite3BeginWriteOperation(pParse, setStatement, 1);
+ }
+}
+
+/*
+** Check to see if pIndex uses the collating sequence pColl. Return
+** true if it does and false if it does not.
+*/
+#ifndef SQLITE_OMIT_REINDEX
+static int collationMatch(const char *zColl, Index *pIndex){
+ int i;
+ for(i=0; i<pIndex->nColumn; i++){
+ const char *z = pIndex->azColl[i];
+ if( z==zColl || (z && zColl && 0==sqlite3StrICmp(z, zColl)) ){
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif
+
+/*
+** Recompute all indices of pTab that use the collating sequence pColl.
+** If pColl==0 then recompute all indices of pTab.
+*/
+#ifndef SQLITE_OMIT_REINDEX
+static void reindexTable(Parse *pParse, Table *pTab, char const *zColl){
+ Index *pIndex; /* An index associated with pTab */
+
+ for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){
+ if( zColl==0 || collationMatch(zColl, pIndex) ){
+ int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3RefillIndex(pParse, pIndex, -1);
+ }
+ }
+}
+#endif
+
+/*
+** Recompute all indices of all tables in all databases where the
+** indices use the collating sequence pColl. If pColl==0 then recompute
+** all indices everywhere.
+*/
+#ifndef SQLITE_OMIT_REINDEX
+static void reindexDatabases(Parse *pParse, char const *zColl){
+ Db *pDb; /* A single database */
+ int iDb; /* The database index number */
+ sqlite3 *db = pParse->db; /* The database connection */
+ HashElem *k; /* For looping over tables in pDb */
+ Table *pTab; /* A table in the database */
+
+ for(iDb=0, pDb=db->aDb; iDb<db->nDb; iDb++, pDb++){
+ assert( pDb!=0 );
+ for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){
+ pTab = (Table*)sqliteHashData(k);
+ reindexTable(pParse, pTab, zColl);
+ }
+ }
+}
+#endif
+
+/*
+** Generate code for the REINDEX command.
+**
+** REINDEX -- 1
+** REINDEX <collation> -- 2
+** REINDEX ?<database>.?<tablename> -- 3
+** REINDEX ?<database>.?<indexname> -- 4
+**
+** Form 1 causes all indices in all attached databases to be rebuilt.
+** Form 2 rebuilds all indices in all databases that use the named
+** collating function. Forms 3 and 4 rebuild the named index or all
+** indices associated with the named table.
+*/
+#ifndef SQLITE_OMIT_REINDEX
+SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){
+ CollSeq *pColl; /* Collating sequence to be reindexed, or NULL */
+ char *z; /* Name of a table or index */
+ const char *zDb; /* Name of the database */
+ Table *pTab; /* A table in the database */
+ Index *pIndex; /* An index associated with pTab */
+ int iDb; /* The database index number */
+ sqlite3 *db = pParse->db; /* The database connection */
+ Token *pObjName; /* Name of the table or index to be reindexed */
+
+ /* Read the database schema. If an error occurs, leave an error message
+ ** and code in pParse and return NULL. */
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ return;
+ }
+
+ if( pName1==0 || pName1->z==0 ){
+ reindexDatabases(pParse, 0);
+ return;
+ }else if( pName2==0 || pName2->z==0 ){
+ char *zColl;
+ assert( pName1->z );
+ zColl = sqlite3NameFromToken(pParse->db, pName1);
+ if( !zColl ) return;
+ pColl = sqlite3FindCollSeq(db, ENC(db), zColl, -1, 0);
+ if( pColl ){
+ if( zColl ){
+ reindexDatabases(pParse, zColl);
+ sqlite3_free(zColl);
+ }
+ return;
+ }
+ sqlite3_free(zColl);
+ }
+ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pObjName);
+ if( iDb<0 ) return;
+ z = sqlite3NameFromToken(db, pObjName);
+ if( z==0 ) return;
+ zDb = db->aDb[iDb].zName;
+ pTab = sqlite3FindTable(db, z, zDb);
+ if( pTab ){
+ reindexTable(pParse, pTab, 0);
+ sqlite3_free(z);
+ return;
+ }
+ pIndex = sqlite3FindIndex(db, z, zDb);
+ sqlite3_free(z);
+ if( pIndex ){
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3RefillIndex(pParse, pIndex, -1);
+ return;
+ }
+ sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed");
+}
+#endif
+
+/*
+** Return a dynamicly allocated KeyInfo structure that can be used
+** with OP_OpenRead or OP_OpenWrite to access database index pIdx.
+**
+** If successful, a pointer to the new structure is returned. In this case
+** the caller is responsible for calling sqlite3_free() on the returned
+** pointer. If an error occurs (out of memory or missing collation
+** sequence), NULL is returned and the state of pParse updated to reflect
+** the error.
+*/
+SQLITE_PRIVATE KeyInfo *sqlite3IndexKeyinfo(Parse *pParse, Index *pIdx){
+ int i;
+ int nCol = pIdx->nColumn;
+ int nBytes = sizeof(KeyInfo) + (nCol-1)*sizeof(CollSeq*) + nCol;
+ KeyInfo *pKey = (KeyInfo *)sqlite3DbMallocZero(pParse->db, nBytes);
+
+ if( pKey ){
+ pKey->db = pParse->db;
+ pKey->aSortOrder = (u8 *)&(pKey->aColl[nCol]);
+ assert( &pKey->aSortOrder[nCol]==&(((u8 *)pKey)[nBytes]) );
+ for(i=0; i<nCol; i++){
+ char *zColl = pIdx->azColl[i];
+ assert( zColl );
+ pKey->aColl[i] = sqlite3LocateCollSeq(pParse, zColl, -1);
+ pKey->aSortOrder[i] = pIdx->aSortOrder[i];
+ }
+ pKey->nField = nCol;
+ }
+
+ if( pParse->nErr ){
+ sqlite3_free(pKey);
+ pKey = 0;
+ }
+ return pKey;
+}
+
+/************** End of build.c ***********************************************/
+/************** Begin file callback.c ****************************************/
+/*
+** 2005 May 23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file contains functions used to access the internal hash tables
+** of user defined functions and collation sequences.
+**
+** $Id: callback.c,v 1.23 2007/08/29 12:31:26 danielk1977 Exp $
+*/
+
+
+/*
+** Invoke the 'collation needed' callback to request a collation sequence
+** in the database text encoding of name zName, length nName.
+** If the collation sequence
+*/
+static void callCollNeeded(sqlite3 *db, const char *zName, int nName){
+ assert( !db->xCollNeeded || !db->xCollNeeded16 );
+ if( nName<0 ) nName = strlen(zName);
+ if( db->xCollNeeded ){
+ char *zExternal = sqlite3DbStrNDup(db, zName, nName);
+ if( !zExternal ) return;
+ db->xCollNeeded(db->pCollNeededArg, db, (int)ENC(db), zExternal);
+ sqlite3_free(zExternal);
+ }
+#ifndef SQLITE_OMIT_UTF16
+ if( db->xCollNeeded16 ){
+ char const *zExternal;
+ sqlite3_value *pTmp = sqlite3ValueNew(db);
+ sqlite3ValueSetStr(pTmp, nName, zName, SQLITE_UTF8, SQLITE_STATIC);
+ zExternal = sqlite3ValueText(pTmp, SQLITE_UTF16NATIVE);
+ if( zExternal ){
+ db->xCollNeeded16(db->pCollNeededArg, db, (int)ENC(db), zExternal);
+ }
+ sqlite3ValueFree(pTmp);
+ }
+#endif
+}
+
+/*
+** This routine is called if the collation factory fails to deliver a
+** collation function in the best encoding but there may be other versions
+** of this collation function (for other text encodings) available. Use one
+** of these instead if they exist. Avoid a UTF-8 <-> UTF-16 conversion if
+** possible.
+*/
+static int synthCollSeq(sqlite3 *db, CollSeq *pColl){
+ CollSeq *pColl2;
+ char *z = pColl->zName;
+ int n = strlen(z);
+ int i;
+ static const u8 aEnc[] = { SQLITE_UTF16BE, SQLITE_UTF16LE, SQLITE_UTF8 };
+ for(i=0; i<3; i++){
+ pColl2 = sqlite3FindCollSeq(db, aEnc[i], z, n, 0);
+ if( pColl2->xCmp!=0 ){
+ memcpy(pColl, pColl2, sizeof(CollSeq));
+ pColl->xDel = 0; /* Do not copy the destructor */
+ return SQLITE_OK;
+ }
+ }
+ return SQLITE_ERROR;
+}
+
+/*
+** This function is responsible for invoking the collation factory callback
+** or substituting a collation sequence of a different encoding when the
+** requested collation sequence is not available in the database native
+** encoding.
+**
+** If it is not NULL, then pColl must point to the database native encoding
+** collation sequence with name zName, length nName.
+**
+** The return value is either the collation sequence to be used in database
+** db for collation type name zName, length nName, or NULL, if no collation
+** sequence can be found.
+*/
+SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(
+ sqlite3* db,
+ CollSeq *pColl,
+ const char *zName,
+ int nName
+){
+ CollSeq *p;
+
+ p = pColl;
+ if( !p ){
+ p = sqlite3FindCollSeq(db, ENC(db), zName, nName, 0);
+ }
+ if( !p || !p->xCmp ){
+ /* No collation sequence of this type for this encoding is registered.
+ ** Call the collation factory to see if it can supply us with one.
+ */
+ callCollNeeded(db, zName, nName);
+ p = sqlite3FindCollSeq(db, ENC(db), zName, nName, 0);
+ }
+ if( p && !p->xCmp && synthCollSeq(db, p) ){
+ p = 0;
+ }
+ assert( !p || p->xCmp );
+ return p;
+}
+
+/*
+** This routine is called on a collation sequence before it is used to
+** check that it is defined. An undefined collation sequence exists when
+** a database is loaded that contains references to collation sequences
+** that have not been defined by sqlite3_create_collation() etc.
+**
+** If required, this routine calls the 'collation needed' callback to
+** request a definition of the collating sequence. If this doesn't work,
+** an equivalent collating sequence that uses a text encoding different
+** from the main database is substituted, if one is available.
+*/
+SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *pParse, CollSeq *pColl){
+ if( pColl ){
+ const char *zName = pColl->zName;
+ CollSeq *p = sqlite3GetCollSeq(pParse->db, pColl, zName, -1);
+ if( !p ){
+ if( pParse->nErr==0 ){
+ sqlite3ErrorMsg(pParse, "no such collation sequence: %s", zName);
+ }
+ pParse->nErr++;
+ return SQLITE_ERROR;
+ }
+ assert( p==pColl );
+ }
+ return SQLITE_OK;
+}
+
+
+
+/*
+** Locate and return an entry from the db.aCollSeq hash table. If the entry
+** specified by zName and nName is not found and parameter 'create' is
+** true, then create a new entry. Otherwise return NULL.
+**
+** Each pointer stored in the sqlite3.aCollSeq hash table contains an
+** array of three CollSeq structures. The first is the collation sequence
+** prefferred for UTF-8, the second UTF-16le, and the third UTF-16be.
+**
+** Stored immediately after the three collation sequences is a copy of
+** the collation sequence name. A pointer to this string is stored in
+** each collation sequence structure.
+*/
+static CollSeq *findCollSeqEntry(
+ sqlite3 *db,
+ const char *zName,
+ int nName,
+ int create
+){
+ CollSeq *pColl;
+ if( nName<0 ) nName = strlen(zName);
+ pColl = sqlite3HashFind(&db->aCollSeq, zName, nName);
+
+ if( 0==pColl && create ){
+ pColl = sqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName + 1 );
+ if( pColl ){
+ CollSeq *pDel = 0;
+ pColl[0].zName = (char*)&pColl[3];
+ pColl[0].enc = SQLITE_UTF8;
+ pColl[1].zName = (char*)&pColl[3];
+ pColl[1].enc = SQLITE_UTF16LE;
+ pColl[2].zName = (char*)&pColl[3];
+ pColl[2].enc = SQLITE_UTF16BE;
+ memcpy(pColl[0].zName, zName, nName);
+ pColl[0].zName[nName] = 0;
+ pDel = sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, nName, pColl);
+
+ /* If a malloc() failure occured in sqlite3HashInsert(), it will
+ ** return the pColl pointer to be deleted (because it wasn't added
+ ** to the hash table).
+ */
+ assert( pDel==0 || pDel==pColl );
+ if( pDel!=0 ){
+ db->mallocFailed = 1;
+ sqlite3_free(pDel);
+ pColl = 0;
+ }
+ }
+ }
+ return pColl;
+}
+
+/*
+** Parameter zName points to a UTF-8 encoded string nName bytes long.
+** Return the CollSeq* pointer for the collation sequence named zName
+** for the encoding 'enc' from the database 'db'.
+**
+** If the entry specified is not found and 'create' is true, then create a
+** new entry. Otherwise return NULL.
+**
+** A separate function sqlite3LocateCollSeq() is a wrapper around
+** this routine. sqlite3LocateCollSeq() invokes the collation factory
+** if necessary and generates an error message if the collating sequence
+** cannot be found.
+*/
+SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(
+ sqlite3 *db,
+ u8 enc,
+ const char *zName,
+ int nName,
+ int create
+){
+ CollSeq *pColl;
+ if( zName ){
+ pColl = findCollSeqEntry(db, zName, nName, create);
+ }else{
+ pColl = db->pDfltColl;
+ }
+ assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 );
+ assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE );
+ if( pColl ) pColl += enc-1;
+ return pColl;
+}
+
+/*
+** Locate a user function given a name, a number of arguments and a flag
+** indicating whether the function prefers UTF-16 over UTF-8. Return a
+** pointer to the FuncDef structure that defines that function, or return
+** NULL if the function does not exist.
+**
+** If the createFlag argument is true, then a new (blank) FuncDef
+** structure is created and liked into the "db" structure if a
+** no matching function previously existed. When createFlag is true
+** and the nArg parameter is -1, then only a function that accepts
+** any number of arguments will be returned.
+**
+** If createFlag is false and nArg is -1, then the first valid
+** function found is returned. A function is valid if either xFunc
+** or xStep is non-zero.
+**
+** If createFlag is false, then a function with the required name and
+** number of arguments may be returned even if the eTextRep flag does not
+** match that requested.
+*/
+SQLITE_PRIVATE FuncDef *sqlite3FindFunction(
+ sqlite3 *db, /* An open database */
+ const char *zName, /* Name of the function. Not null-terminated */
+ int nName, /* Number of characters in the name */
+ int nArg, /* Number of arguments. -1 means any number */
+ u8 enc, /* Preferred text encoding */
+ int createFlag /* Create new entry if true and does not otherwise exist */
+){
+ FuncDef *p; /* Iterator variable */
+ FuncDef *pFirst; /* First function with this name */
+ FuncDef *pBest = 0; /* Best match found so far */
+ int bestmatch = 0;
+
+
+ assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
+ if( nArg<-1 ) nArg = -1;
+
+ pFirst = (FuncDef*)sqlite3HashFind(&db->aFunc, zName, nName);
+ for(p=pFirst; p; p=p->pNext){
+ /* During the search for the best function definition, bestmatch is set
+ ** as follows to indicate the quality of the match with the definition
+ ** pointed to by pBest:
+ **
+ ** 0: pBest is NULL. No match has been found.
+ ** 1: A variable arguments function that prefers UTF-8 when a UTF-16
+ ** encoding is requested, or vice versa.
+ ** 2: A variable arguments function that uses UTF-16BE when UTF-16LE is
+ ** requested, or vice versa.
+ ** 3: A variable arguments function using the same text encoding.
+ ** 4: A function with the exact number of arguments requested that
+ ** prefers UTF-8 when a UTF-16 encoding is requested, or vice versa.
+ ** 5: A function with the exact number of arguments requested that
+ ** prefers UTF-16LE when UTF-16BE is requested, or vice versa.
+ ** 6: An exact match.
+ **
+ ** A larger value of 'matchqual' indicates a more desirable match.
+ */
+ if( p->nArg==-1 || p->nArg==nArg || nArg==-1 ){
+ int match = 1; /* Quality of this match */
+ if( p->nArg==nArg || nArg==-1 ){
+ match = 4;
+ }
+ if( enc==p->iPrefEnc ){
+ match += 2;
+ }
+ else if( (enc==SQLITE_UTF16LE && p->iPrefEnc==SQLITE_UTF16BE) ||
+ (enc==SQLITE_UTF16BE && p->iPrefEnc==SQLITE_UTF16LE) ){
+ match += 1;
+ }
+
+ if( match>bestmatch ){
+ pBest = p;
+ bestmatch = match;
+ }
+ }
+ }
+
+ /* If the createFlag parameter is true, and the seach did not reveal an
+ ** exact match for the name, number of arguments and encoding, then add a
+ ** new entry to the hash table and return it.
+ */
+ if( createFlag && bestmatch<6 &&
+ (pBest = sqlite3DbMallocZero(db, sizeof(*pBest)+nName))!=0 ){
+ pBest->nArg = nArg;
+ pBest->pNext = pFirst;
+ pBest->iPrefEnc = enc;
+ memcpy(pBest->zName, zName, nName);
+ pBest->zName[nName] = 0;
+ if( pBest==sqlite3HashInsert(&db->aFunc,pBest->zName,nName,(void*)pBest) ){
+ db->mallocFailed = 1;
+ sqlite3_free(pBest);
+ return 0;
+ }
+ }
+
+ if( pBest && (pBest->xStep || pBest->xFunc || createFlag) ){
+ return pBest;
+ }
+ return 0;
+}
+
+/*
+** Free all resources held by the schema structure. The void* argument points
+** at a Schema struct. This function does not call sqlite3_free() on the
+** pointer itself, it just cleans up subsiduary resources (i.e. the contents
+** of the schema hash tables).
+*/
+SQLITE_PRIVATE void sqlite3SchemaFree(void *p){
+ Hash temp1;
+ Hash temp2;
+ HashElem *pElem;
+ Schema *pSchema = (Schema *)p;
+
+ temp1 = pSchema->tblHash;
+ temp2 = pSchema->trigHash;
+ sqlite3HashInit(&pSchema->trigHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashClear(&pSchema->aFKey);
+ sqlite3HashClear(&pSchema->idxHash);
+ for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
+ sqlite3DeleteTrigger((Trigger*)sqliteHashData(pElem));
+ }
+ sqlite3HashClear(&temp2);
+ sqlite3HashInit(&pSchema->tblHash, SQLITE_HASH_STRING, 0);
+ for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
+ Table *pTab = sqliteHashData(pElem);
+ sqlite3DeleteTable(pTab);
+ }
+ sqlite3HashClear(&temp1);
+ pSchema->pSeqTab = 0;
+ pSchema->flags &= ~DB_SchemaLoaded;
+}
+
+/*
+** Find and return the schema associated with a BTree. Create
+** a new one if necessary.
+*/
+SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){
+ Schema * p;
+ if( pBt ){
+ p = (Schema *)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaFree);
+ }else{
+ p = (Schema *)sqlite3MallocZero(sizeof(Schema));
+ }
+ if( !p ){
+ db->mallocFailed = 1;
+ }else if ( 0==p->file_format ){
+ sqlite3HashInit(&p->tblHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&p->idxHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&p->trigHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&p->aFKey, SQLITE_HASH_STRING, 1);
+ p->enc = SQLITE_UTF8;
+ }
+ return p;
+}
+
+/************** End of callback.c ********************************************/
+/************** Begin file delete.c ******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** in order to generate code for DELETE FROM statements.
+**
+** $Id: delete.c,v 1.169 2008/04/28 18:46:43 drh Exp $
+*/
+
+/*
+** Look up every table that is named in pSrc. If any table is not found,
+** add an error message to pParse->zErrMsg and return NULL. If all tables
+** are found, return a pointer to the last table.
+*/
+SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
+ Table *pTab = 0;
+ int i;
+ struct SrcList_item *pItem;
+ for(i=0, pItem=pSrc->a; i<pSrc->nSrc; i++, pItem++){
+ pTab = sqlite3LocateTable(pParse, 0, pItem->zName, pItem->zDatabase);
+ sqlite3DeleteTable(pItem->pTab);
+ pItem->pTab = pTab;
+ if( pTab ){
+ pTab->nRef++;
+ }
+ }
+ return pTab;
+}
+
+/*
+** Check to make sure the given table is writable. If it is not
+** writable, generate an error message and return 1. If it is
+** writable return 0;
+*/
+SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
+ if( (pTab->readOnly && (pParse->db->flags & SQLITE_WriteSchema)==0
+ && pParse->nested==0)
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ || (pTab->pMod && pTab->pMod->pModule->xUpdate==0)
+#endif
+ ){
+ sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName);
+ return 1;
+ }
+#ifndef SQLITE_OMIT_VIEW
+ if( !viewOk && pTab->pSelect ){
+ sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/*
+** Generate code that will open a table for reading.
+*/
+SQLITE_PRIVATE void sqlite3OpenTable(
+ Parse *p, /* Generate code into this VDBE */
+ int iCur, /* The cursor number of the table */
+ int iDb, /* The database index in sqlite3.aDb[] */
+ Table *pTab, /* The table to be opened */
+ int opcode /* OP_OpenRead or OP_OpenWrite */
+){
+ Vdbe *v;
+ if( IsVirtual(pTab) ) return;
+ v = sqlite3GetVdbe(p);
+ assert( opcode==OP_OpenWrite || opcode==OP_OpenRead );
+ sqlite3TableLock(p, iDb, pTab->tnum, (opcode==OP_OpenWrite), pTab->zName);
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, pTab->nCol);
+ sqlite3VdbeAddOp3(v, opcode, iCur, pTab->tnum, iDb);
+ VdbeComment((v, "%s", pTab->zName));
+}
+
+
+#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
+/*
+** Evaluate a view and store its result in an ephemeral table. The
+** pWhere argument is an optional WHERE clause that restricts the
+** set of rows in the view that are to be added to the ephemeral table.
+*/
+SQLITE_PRIVATE void sqlite3MaterializeView(
+ Parse *pParse, /* Parsing context */
+ Select *pView, /* View definition */
+ Expr *pWhere, /* Optional WHERE clause to be added */
+ int iCur /* Cursor number for ephemerial table */
+){
+ SelectDest dest;
+ Select *pDup;
+ sqlite3 *db = pParse->db;
+
+ pDup = sqlite3SelectDup(db, pView);
+ if( pWhere ){
+ SrcList *pFrom;
+
+ pWhere = sqlite3ExprDup(db, pWhere);
+ pFrom = sqlite3SrcListAppendFromTerm(pParse, 0, 0, 0, 0, pDup, 0, 0);
+ pDup = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, 0, 0, 0);
+ }
+ sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
+ sqlite3Select(pParse, pDup, &dest, 0, 0, 0, 0);
+ sqlite3SelectDelete(pDup);
+}
+#endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
+
+
+/*
+** Generate code for a DELETE FROM statement.
+**
+** DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
+** \________/ \________________/
+** pTabList pWhere
+*/
+SQLITE_PRIVATE void sqlite3DeleteFrom(
+ Parse *pParse, /* The parser context */
+ SrcList *pTabList, /* The table from which we should delete things */
+ Expr *pWhere /* The WHERE clause. May be null */
+){
+ Vdbe *v; /* The virtual database engine */
+ Table *pTab; /* The table from which records will be deleted */
+ const char *zDb; /* Name of database holding pTab */
+ int end, addr = 0; /* A couple addresses of generated code */
+ int i; /* Loop counter */
+ WhereInfo *pWInfo; /* Information about the WHERE clause */
+ Index *pIdx; /* For looping over indices of the table */
+ int iCur; /* VDBE Cursor number for pTab */
+ sqlite3 *db; /* Main database structure */
+ AuthContext sContext; /* Authorization context */
+ int oldIdx = -1; /* Cursor for the OLD table of AFTER triggers */
+ NameContext sNC; /* Name context to resolve expressions in */
+ int iDb; /* Database number */
+ int memCnt = 0; /* Memory cell used for change counting */
+
+#ifndef SQLITE_OMIT_TRIGGER
+ int isView; /* True if attempting to delete from a view */
+ int triggers_exist = 0; /* True if any triggers exist */
+#endif
+ int iBeginAfterTrigger; /* Address of after trigger program */
+ int iEndAfterTrigger; /* Exit of after trigger program */
+ int iBeginBeforeTrigger; /* Address of before trigger program */
+ int iEndBeforeTrigger; /* Exit of before trigger program */
+ u32 old_col_mask = 0; /* Mask of OLD.* columns in use */
+
+ sContext.pParse = 0;
+ db = pParse->db;
+ if( pParse->nErr || db->mallocFailed ){
+ goto delete_from_cleanup;
+ }
+ assert( pTabList->nSrc==1 );
+
+ /* Locate the table which we want to delete. This table has to be
+ ** put in an SrcList structure because some of the subroutines we
+ ** will be calling are designed to work with multiple tables and expect
+ ** an SrcList* parameter instead of just a Table* parameter.
+ */
+ pTab = sqlite3SrcListLookup(pParse, pTabList);
+ if( pTab==0 ) goto delete_from_cleanup;
+
+ /* Figure out if we have any triggers and if the table being
+ ** deleted from is a view
+ */
+#ifndef SQLITE_OMIT_TRIGGER
+ triggers_exist = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0);
+ isView = pTab->pSelect!=0;
+#else
+# define triggers_exist 0
+# define isView 0
+#endif
+#ifdef SQLITE_OMIT_VIEW
+# undef isView
+# define isView 0
+#endif
+
+ if( sqlite3IsReadOnly(pParse, pTab, triggers_exist) ){
+ goto delete_from_cleanup;
+ }
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( iDb<db->nDb );
+ zDb = db->aDb[iDb].zName;
+ if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){
+ goto delete_from_cleanup;
+ }
+
+ /* If pTab is really a view, make sure it has been initialized.
+ */
+ if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ goto delete_from_cleanup;
+ }
+
+ /* Allocate a cursor used to store the old.* data for a trigger.
+ */
+ if( triggers_exist ){
+ oldIdx = pParse->nTab++;
+ }
+
+ /* Assign cursor number to the table and all its indices.
+ */
+ assert( pTabList->nSrc==1 );
+ iCur = pTabList->a[0].iCursor = pParse->nTab++;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ pParse->nTab++;
+ }
+
+ /* Start the view context
+ */
+ if( isView ){
+ sqlite3AuthContextPush(pParse, &sContext, pTab->zName);
+ }
+
+ /* Begin generating code.
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ){
+ goto delete_from_cleanup;
+ }
+ if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
+ sqlite3BeginWriteOperation(pParse, triggers_exist, iDb);
+
+ if( triggers_exist ){
+ int orconf = ((pParse->trigStack)?pParse->trigStack->orconf:OE_Default);
+ int iGoto = sqlite3VdbeAddOp0(v, OP_Goto);
+ addr = sqlite3VdbeMakeLabel(v);
+
+ iBeginBeforeTrigger = sqlite3VdbeCurrentAddr(v);
+ (void)sqlite3CodeRowTrigger(pParse, TK_DELETE, 0, TRIGGER_BEFORE, pTab,
+ -1, oldIdx, orconf, addr, &old_col_mask, 0);
+ iEndBeforeTrigger = sqlite3VdbeAddOp0(v, OP_Goto);
+
+ iBeginAfterTrigger = sqlite3VdbeCurrentAddr(v);
+ (void)sqlite3CodeRowTrigger(pParse, TK_DELETE, 0, TRIGGER_AFTER, pTab, -1,
+ oldIdx, orconf, addr, &old_col_mask, 0);
+ iEndAfterTrigger = sqlite3VdbeAddOp0(v, OP_Goto);
+
+ sqlite3VdbeJumpHere(v, iGoto);
+ }
+
+ /* If we are trying to delete from a view, realize that view into
+ ** a ephemeral table.
+ */
+ if( isView ){
+ sqlite3MaterializeView(pParse, pTab->pSelect, pWhere, iCur);
+ }
+
+ /* Resolve the column names in the WHERE clause.
+ */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ sNC.pSrcList = pTabList;
+ if( sqlite3ExprResolveNames(&sNC, pWhere) ){
+ goto delete_from_cleanup;
+ }
+
+ /* Initialize the counter of the number of rows deleted, if
+ ** we are counting rows.
+ */
+ if( db->flags & SQLITE_CountRows ){
+ memCnt = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
+ }
+
+ /* Special case: A DELETE without a WHERE clause deletes everything.
+ ** It is easier just to erase the whole table. Note, however, that
+ ** this means that the row change count will be incorrect.
+ */
+ if( pWhere==0 && !triggers_exist && !IsVirtual(pTab) ){
+ if( db->flags & SQLITE_CountRows ){
+ /* If counting rows deleted, just count the total number of
+ ** entries in the table. */
+ int addr2;
+ if( !isView ){
+ sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead);
+ }
+ sqlite3VdbeAddOp2(v, OP_Rewind, iCur, sqlite3VdbeCurrentAddr(v)+2);
+ addr2 = sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
+ sqlite3VdbeAddOp2(v, OP_Next, iCur, addr2);
+ sqlite3VdbeAddOp1(v, OP_Close, iCur);
+ }
+ if( !isView ){
+ sqlite3VdbeAddOp2(v, OP_Clear, pTab->tnum, iDb);
+ if( !pParse->nested ){
+ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
+ }
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ assert( pIdx->pSchema==pTab->pSchema );
+ sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
+ }
+ }
+ }
+ /* The usual case: There is a WHERE clause so we have to scan through
+ ** the table and pick which records to delete.
+ */
+ else{
+ int iRowid = ++pParse->nMem; /* Used for storing rowid values. */
+
+ /* Begin the database scan
+ */
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0);
+ if( pWInfo==0 ) goto delete_from_cleanup;
+
+ /* Remember the rowid of every item to be deleted.
+ */
+ sqlite3VdbeAddOp2(v, IsVirtual(pTab) ? OP_VRowid : OP_Rowid, iCur, iRowid);
+ sqlite3VdbeAddOp1(v, OP_FifoWrite, iRowid);
+ if( db->flags & SQLITE_CountRows ){
+ sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
+ }
+
+ /* End the database scan loop.
+ */
+ sqlite3WhereEnd(pWInfo);
+
+ /* Open the pseudo-table used to store OLD if there are triggers.
+ */
+ if( triggers_exist ){
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, pTab->nCol);
+ sqlite3VdbeAddOp1(v, OP_OpenPseudo, oldIdx);
+ }
+
+ /* Delete every item whose key was written to the list during the
+ ** database scan. We have to delete items after the scan is complete
+ ** because deleting an item can change the scan order.
+ */
+ end = sqlite3VdbeMakeLabel(v);
+
+ if( !isView ){
+ /* Open cursors for the table we are deleting from and
+ ** all its indices.
+ */
+ sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite);
+ }
+
+ /* This is the beginning of the delete loop. If a trigger encounters
+ ** an IGNORE constraint, it jumps back to here.
+ */
+ if( triggers_exist ){
+ sqlite3VdbeResolveLabel(v, addr);
+ }
+ addr = sqlite3VdbeAddOp2(v, OP_FifoRead, iRowid, end);
+
+ if( triggers_exist ){
+ int iData = ++pParse->nMem; /* For storing row data of OLD table */
+
+ /* If the record is no longer present in the table, jump to the
+ ** next iteration of the loop through the contents of the fifo.
+ */
+ sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, iRowid);
+
+ /* Populate the OLD.* pseudo-table */
+ if( old_col_mask ){
+ sqlite3VdbeAddOp2(v, OP_RowData, iCur, iData);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Null, 0, iData);
+ }
+ sqlite3VdbeAddOp3(v, OP_Insert, oldIdx, iData, iRowid);
+
+ /* Jump back and run the BEFORE triggers */
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginBeforeTrigger);
+ sqlite3VdbeJumpHere(v, iEndBeforeTrigger);
+ }
+
+ if( !isView ){
+ /* Delete the row */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTab) ){
+ const char *pVtab = (const char *)pTab->pVtab;
+ sqlite3VtabMakeWritable(pParse, pTab);
+ sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iRowid, pVtab, P4_VTAB);
+ }else
+#endif
+ {
+ sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, pParse->nested==0);
+ }
+ }
+
+ /* If there are row triggers, close all cursors then invoke
+ ** the AFTER triggers
+ */
+ if( triggers_exist ){
+ /* Jump back and run the AFTER triggers */
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginAfterTrigger);
+ sqlite3VdbeJumpHere(v, iEndAfterTrigger);
+ }
+
+ /* End of the delete loop */
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
+ sqlite3VdbeResolveLabel(v, end);
+
+ /* Close the cursors after the loop if there are no row triggers */
+ if( !isView && !IsVirtual(pTab) ){
+ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
+ sqlite3VdbeAddOp2(v, OP_Close, iCur + i, pIdx->tnum);
+ }
+ sqlite3VdbeAddOp1(v, OP_Close, iCur);
+ }
+ }
+
+ /*
+ ** Return the number of rows that were deleted. If this routine is
+ ** generating code because of a call to sqlite3NestedParse(), do not
+ ** invoke the callback function.
+ */
+ if( db->flags & SQLITE_CountRows && pParse->nested==0 && !pParse->trigStack ){
+ sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", P4_STATIC);
+ }
+
+delete_from_cleanup:
+ sqlite3AuthContextPop(&sContext);
+ sqlite3SrcListDelete(pTabList);
+ sqlite3ExprDelete(pWhere);
+ return;
+}
+
+/*
+** This routine generates VDBE code that causes a single row of a
+** single table to be deleted.
+**
+** The VDBE must be in a particular state when this routine is called.
+** These are the requirements:
+**
+** 1. A read/write cursor pointing to pTab, the table containing the row
+** to be deleted, must be opened as cursor number "base".
+**
+** 2. Read/write cursors for all indices of pTab must be open as
+** cursor number base+i for the i-th index.
+**
+** 3. The record number of the row to be deleted must be stored in
+** memory cell iRowid.
+**
+** This routine pops the top of the stack to remove the record number
+** and then generates code to remove both the table record and all index
+** entries that point to that record.
+*/
+SQLITE_PRIVATE void sqlite3GenerateRowDelete(
+ Parse *pParse, /* Parsing context */
+ Table *pTab, /* Table containing the row to be deleted */
+ int iCur, /* Cursor number for the table */
+ int iRowid, /* Memory cell that contains the rowid to delete */
+ int count /* Increment the row change counter */
+){
+ int addr;
+ Vdbe *v;
+
+ v = pParse->pVdbe;
+ addr = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowid);
+ sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0);
+ sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0));
+ if( count ){
+ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
+ }
+ sqlite3VdbeJumpHere(v, addr);
+}
+
+/*
+** This routine generates VDBE code that causes the deletion of all
+** index entries associated with a single row of a single table.
+**
+** The VDBE must be in a particular state when this routine is called.
+** These are the requirements:
+**
+** 1. A read/write cursor pointing to pTab, the table containing the row
+** to be deleted, must be opened as cursor number "iCur".
+**
+** 2. Read/write cursors for all indices of pTab must be open as
+** cursor number iCur+i for the i-th index.
+**
+** 3. The "iCur" cursor must be pointing to the row that is to be
+** deleted.
+*/
+SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(
+ Parse *pParse, /* Parsing and code generating context */
+ Table *pTab, /* Table containing the row to be deleted */
+ int iCur, /* Cursor number for the table */
+ int *aRegIdx /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
+){
+ int i;
+ Index *pIdx;
+ int r1;
+
+ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
+ if( aRegIdx!=0 && aRegIdx[i-1]==0 ) continue;
+ r1 = sqlite3GenerateIndexKey(pParse, pIdx, iCur, 0, 0);
+ sqlite3VdbeAddOp3(pParse->pVdbe, OP_IdxDelete, iCur+i, r1,pIdx->nColumn+1);
+ }
+}
+
+/*
+** Generate code that will assemble an index key and put it in register
+** regOut. The key with be for index pIdx which is an index on pTab.
+** iCur is the index of a cursor open on the pTab table and pointing to
+** the entry that needs indexing.
+**
+** Return a register number which is the first in a block of
+** registers that holds the elements of the index key. The
+** block of registers has already been deallocated by the time
+** this routine returns.
+*/
+SQLITE_PRIVATE int sqlite3GenerateIndexKey(
+ Parse *pParse, /* Parsing context */
+ Index *pIdx, /* The index for which to generate a key */
+ int iCur, /* Cursor number for the pIdx->pTable table */
+ int regOut, /* Write the new index key to this register */
+ int doMakeRec /* Run the OP_MakeRecord instruction if true */
+){
+ Vdbe *v = pParse->pVdbe;
+ int j;
+ Table *pTab = pIdx->pTable;
+ int regBase;
+ int nCol;
+
+ nCol = pIdx->nColumn;
+ regBase = sqlite3GetTempRange(pParse, nCol+1);
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regBase+nCol);
+ for(j=0; j<nCol; j++){
+ int idx = pIdx->aiColumn[j];
+ if( idx==pTab->iPKey ){
+ sqlite3VdbeAddOp2(v, OP_SCopy, regBase+nCol, regBase+j);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, idx, regBase+j);
+ sqlite3ColumnDefault(v, pTab, idx);
+ }
+ }
+ if( doMakeRec ){
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol+1, regOut);
+ sqlite3IndexAffinityStr(v, pIdx);
+ sqlite3ExprCacheAffinityChange(pParse, regBase, nCol+1);
+ }
+ sqlite3ReleaseTempRange(pParse, regBase, nCol+1);
+ return regBase;
+}
+
+/* Make sure "isView" gets undefined in case this file becomes part of
+** the amalgamation - so that subsequent files do not see isView as a
+** macro. */
+#undef isView
+
+/************** End of delete.c **********************************************/
+/************** Begin file func.c ********************************************/
+/*
+** 2002 February 23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the C functions that implement various SQL
+** functions of SQLite.
+**
+** There is only one exported symbol in this file - the function
+** sqliteRegisterBuildinFunctions() found at the bottom of the file.
+** All other code has file scope.
+**
+** $Id: func.c,v 1.192 2008/04/27 18:40:12 drh Exp $
+*/
+
+
+/*
+** Return the collating function associated with a function.
+*/
+static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){
+ return context->pColl;
+}
+
+/*
+** Implementation of the non-aggregate min() and max() functions
+*/
+static void minmaxFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int i;
+ int mask; /* 0 for min() or 0xffffffff for max() */
+ int iBest;
+ CollSeq *pColl;
+
+ if( argc==0 ) return;
+ mask = sqlite3_user_data(context)==0 ? 0 : -1;
+ pColl = sqlite3GetFuncCollSeq(context);
+ assert( pColl );
+ assert( mask==-1 || mask==0 );
+ iBest = 0;
+ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ for(i=1; i<argc; i++){
+ if( sqlite3_value_type(argv[i])==SQLITE_NULL ) return;
+ if( (sqlite3MemCompare(argv[iBest], argv[i], pColl)^mask)>=0 ){
+ iBest = i;
+ }
+ }
+ sqlite3_result_value(context, argv[iBest]);
+}
+
+/*
+** Return the type of the argument.
+*/
+static void typeofFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *z = 0;
+ switch( sqlite3_value_type(argv[0]) ){
+ case SQLITE_NULL: z = "null"; break;
+ case SQLITE_INTEGER: z = "integer"; break;
+ case SQLITE_TEXT: z = "text"; break;
+ case SQLITE_FLOAT: z = "real"; break;
+ case SQLITE_BLOB: z = "blob"; break;
+ }
+ sqlite3_result_text(context, z, -1, SQLITE_STATIC);
+}
+
+
+/*
+** Implementation of the length() function
+*/
+static void lengthFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int len;
+
+ assert( argc==1 );
+ switch( sqlite3_value_type(argv[0]) ){
+ case SQLITE_BLOB:
+ case SQLITE_INTEGER:
+ case SQLITE_FLOAT: {
+ sqlite3_result_int(context, sqlite3_value_bytes(argv[0]));
+ break;
+ }
+ case SQLITE_TEXT: {
+ const unsigned char *z = sqlite3_value_text(argv[0]);
+ if( z==0 ) return;
+ len = 0;
+ while( *z ){
+ len++;
+ SQLITE_SKIP_UTF8(z);
+ }
+ sqlite3_result_int(context, len);
+ break;
+ }
+ default: {
+ sqlite3_result_null(context);
+ break;
+ }
+ }
+}
+
+/*
+** Implementation of the abs() function
+*/
+static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+ assert( argc==1 );
+ switch( sqlite3_value_type(argv[0]) ){
+ case SQLITE_INTEGER: {
+ i64 iVal = sqlite3_value_int64(argv[0]);
+ if( iVal<0 ){
+ if( (iVal<<1)==0 ){
+ sqlite3_result_error(context, "integer overflow", -1);
+ return;
+ }
+ iVal = -iVal;
+ }
+ sqlite3_result_int64(context, iVal);
+ break;
+ }
+ case SQLITE_NULL: {
+ sqlite3_result_null(context);
+ break;
+ }
+ default: {
+ double rVal = sqlite3_value_double(argv[0]);
+ if( rVal<0 ) rVal = -rVal;
+ sqlite3_result_double(context, rVal);
+ break;
+ }
+ }
+}
+
+/*
+** Implementation of the substr() function.
+**
+** substr(x,p1,p2) returns p2 characters of x[] beginning with p1.
+** p1 is 1-indexed. So substr(x,1,1) returns the first character
+** of x. If x is text, then we actually count UTF-8 characters.
+** If x is a blob, then we count bytes.
+**
+** If p1 is negative, then we begin abs(p1) from the end of x[].
+*/
+static void substrFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const unsigned char *z;
+ const unsigned char *z2;
+ int len;
+ int p0type;
+ i64 p1, p2;
+
+ assert( argc==3 || argc==2 );
+ p0type = sqlite3_value_type(argv[0]);
+ if( p0type==SQLITE_BLOB ){
+ len = sqlite3_value_bytes(argv[0]);
+ z = sqlite3_value_blob(argv[0]);
+ if( z==0 ) return;
+ assert( len==sqlite3_value_bytes(argv[0]) );
+ }else{
+ z = sqlite3_value_text(argv[0]);
+ if( z==0 ) return;
+ len = 0;
+ for(z2=z; *z2; len++){
+ SQLITE_SKIP_UTF8(z2);
+ }
+ }
+ p1 = sqlite3_value_int(argv[1]);
+ if( argc==3 ){
+ p2 = sqlite3_value_int(argv[2]);
+ }else{
+ p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH];
+ }
+ if( p1<0 ){
+ p1 += len;
+ if( p1<0 ){
+ p2 += p1;
+ p1 = 0;
+ }
+ }else if( p1>0 ){
+ p1--;
+ }
+ if( p1+p2>len ){
+ p2 = len-p1;
+ }
+ if( p0type!=SQLITE_BLOB ){
+ while( *z && p1 ){
+ SQLITE_SKIP_UTF8(z);
+ p1--;
+ }
+ for(z2=z; *z2 && p2; p2--){
+ SQLITE_SKIP_UTF8(z2);
+ }
+ sqlite3_result_text(context, (char*)z, z2-z, SQLITE_TRANSIENT);
+ }else{
+ if( p2<0 ) p2 = 0;
+ sqlite3_result_blob(context, (char*)&z[p1], p2, SQLITE_TRANSIENT);
+ }
+}
+
+/*
+** Implementation of the round() function
+*/
+static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+ int n = 0;
+ double r;
+ char zBuf[500]; /* larger than the %f representation of the largest double */
+ assert( argc==1 || argc==2 );
+ if( argc==2 ){
+ if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
+ n = sqlite3_value_int(argv[1]);
+ if( n>30 ) n = 30;
+ if( n<0 ) n = 0;
+ }
+ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ r = sqlite3_value_double(argv[0]);
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%.*f",n,r);
+ sqlite3AtoF(zBuf, &r);
+ sqlite3_result_double(context, r);
+}
+
+/*
+** Allocate nByte bytes of space using sqlite3_malloc(). If the
+** allocation fails, call sqlite3_result_error_nomem() to notify
+** the database handle that malloc() has failed.
+*/
+static void *contextMalloc(sqlite3_context *context, i64 nByte){
+ char *z;
+ if( nByte>sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH] ){
+ sqlite3_result_error_toobig(context);
+ z = 0;
+ }else{
+ z = sqlite3_malloc(nByte);
+ if( !z && nByte>0 ){
+ sqlite3_result_error_nomem(context);
+ }
+ }
+ return z;
+}
+
+/*
+** Implementation of the upper() and lower() SQL functions.
+*/
+static void upperFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+ char *z1;
+ const char *z2;
+ int i, n;
+ if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return;
+ z2 = (char*)sqlite3_value_text(argv[0]);
+ n = sqlite3_value_bytes(argv[0]);
+ /* Verify that the call to _bytes() does not invalidate the _text() pointer */
+ assert( z2==(char*)sqlite3_value_text(argv[0]) );
+ if( z2 ){
+ z1 = contextMalloc(context, ((i64)n)+1);
+ if( z1 ){
+ memcpy(z1, z2, n+1);
+ for(i=0; z1[i]; i++){
+ z1[i] = toupper(z1[i]);
+ }
+ sqlite3_result_text(context, z1, -1, sqlite3_free);
+ }
+ }
+}
+static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+ char *z1;
+ const char *z2;
+ int i, n;
+ if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return;
+ z2 = (char*)sqlite3_value_text(argv[0]);
+ n = sqlite3_value_bytes(argv[0]);
+ /* Verify that the call to _bytes() does not invalidate the _text() pointer */
+ assert( z2==(char*)sqlite3_value_text(argv[0]) );
+ if( z2 ){
+ z1 = contextMalloc(context, ((i64)n)+1);
+ if( z1 ){
+ memcpy(z1, z2, n+1);
+ for(i=0; z1[i]; i++){
+ z1[i] = tolower(z1[i]);
+ }
+ sqlite3_result_text(context, z1, -1, sqlite3_free);
+ }
+ }
+}
+
+/*
+** Implementation of the IFNULL(), NVL(), and COALESCE() functions.
+** All three do the same thing. They return the first non-NULL
+** argument.
+*/
+static void ifnullFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int i;
+ for(i=0; i<argc; i++){
+ if( SQLITE_NULL!=sqlite3_value_type(argv[i]) ){
+ sqlite3_result_value(context, argv[i]);
+ break;
+ }
+ }
+}
+
+/*
+** Implementation of random(). Return a random integer.
+*/
+static void randomFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite_int64 r;
+ sqlite3_randomness(sizeof(r), &r);
+ if( (r<<1)==0 ) r = 0; /* Prevent 0x8000.... as the result so that we */
+ /* can always do abs() of the result */
+ sqlite3_result_int64(context, r);
+}
+
+/*
+** Implementation of randomblob(N). Return a random blob
+** that is N bytes long.
+*/
+static void randomBlob(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int n;
+ unsigned char *p;
+ assert( argc==1 );
+ n = sqlite3_value_int(argv[0]);
+ if( n<1 ){
+ n = 1;
+ }
+ p = contextMalloc(context, n);
+ if( p ){
+ sqlite3_randomness(n, p);
+ sqlite3_result_blob(context, (char*)p, n, sqlite3_free);
+ }
+}
+
+/*
+** Implementation of the last_insert_rowid() SQL function. The return
+** value is the same as the sqlite3_last_insert_rowid() API function.
+*/
+static void last_insert_rowid(
+ sqlite3_context *context,
+ int arg,
+ sqlite3_value **argv
+){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ sqlite3_result_int64(context, sqlite3_last_insert_rowid(db));
+}
+
+/*
+** Implementation of the changes() SQL function. The return value is the
+** same as the sqlite3_changes() API function.
+*/
+static void changes(
+ sqlite3_context *context,
+ int arg,
+ sqlite3_value **argv
+){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ sqlite3_result_int(context, sqlite3_changes(db));
+}
+
+/*
+** Implementation of the total_changes() SQL function. The return value is
+** the same as the sqlite3_total_changes() API function.
+*/
+static void total_changes(
+ sqlite3_context *context,
+ int arg,
+ sqlite3_value **argv
+){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ sqlite3_result_int(context, sqlite3_total_changes(db));
+}
+
+/*
+** A structure defining how to do GLOB-style comparisons.
+*/
+struct compareInfo {
+ u8 matchAll;
+ u8 matchOne;
+ u8 matchSet;
+ u8 noCase;
+};
+
+/*
+** For LIKE and GLOB matching on EBCDIC machines, assume that every
+** character is exactly one byte in size. Also, all characters are
+** able to participate in upper-case-to-lower-case mappings in EBCDIC
+** whereas only characters less than 0x80 do in ASCII.
+*/
+#if defined(SQLITE_EBCDIC)
+# define sqlite3Utf8Read(A,B,C) (*(A++))
+# define GlogUpperToLower(A) A = sqlite3UpperToLower[A]
+#else
+# define GlogUpperToLower(A) if( A<0x80 ){ A = sqlite3UpperToLower[A]; }
+#endif
+
+static const struct compareInfo globInfo = { '*', '?', '[', 0 };
+/* The correct SQL-92 behavior is for the LIKE operator to ignore
+** case. Thus 'a' LIKE 'A' would be true. */
+static const struct compareInfo likeInfoNorm = { '%', '_', 0, 1 };
+/* If SQLITE_CASE_SENSITIVE_LIKE is defined, then the LIKE operator
+** is case sensitive causing 'a' LIKE 'A' to be false */
+static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 };
+
+/*
+** Compare two UTF-8 strings for equality where the first string can
+** potentially be a "glob" expression. Return true (1) if they
+** are the same and false (0) if they are different.
+**
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** With the [...] and [^...] matching, a ']' character can be included
+** in the list by making it the first character after '[' or '^'. A
+** range of characters can be specified using '-'. Example:
+** "[a-z]" matches any single lower-case letter. To match a '-', make
+** it the last character in the list.
+**
+** This routine is usually quick, but can be N**2 in the worst case.
+**
+** Hints: to match '*' or '?', put them in "[]". Like this:
+**
+** abc[*]xyz Matches "abc*xyz" only
+*/
+static int patternCompare(
+ const u8 *zPattern, /* The glob pattern */
+ const u8 *zString, /* The string to compare against the glob */
+ const struct compareInfo *pInfo, /* Information about how to do the compare */
+ const int esc /* The escape character */
+){
+ int c, c2;
+ int invert;
+ int seen;
+ u8 matchOne = pInfo->matchOne;
+ u8 matchAll = pInfo->matchAll;
+ u8 matchSet = pInfo->matchSet;
+ u8 noCase = pInfo->noCase;
+ int prevEscape = 0; /* True if the previous character was 'escape' */
+
+ while( (c = sqlite3Utf8Read(zPattern,0,&zPattern))!=0 ){
+ if( !prevEscape && c==matchAll ){
+ while( (c=sqlite3Utf8Read(zPattern,0,&zPattern)) == matchAll
+ || c == matchOne ){
+ if( c==matchOne && sqlite3Utf8Read(zString, 0, &zString)==0 ){
+ return 0;
+ }
+ }
+ if( c==0 ){
+ return 1;
+ }else if( c==esc ){
+ c = sqlite3Utf8Read(zPattern, 0, &zPattern);
+ if( c==0 ){
+ return 0;
+ }
+ }else if( c==matchSet ){
+ assert( esc==0 ); /* This is GLOB, not LIKE */
+ assert( matchSet<0x80 ); /* '[' is a single-byte character */
+ while( *zString && patternCompare(&zPattern[-1],zString,pInfo,esc)==0 ){
+ SQLITE_SKIP_UTF8(zString);
+ }
+ return *zString!=0;
+ }
+ while( (c2 = sqlite3Utf8Read(zString,0,&zString))!=0 ){
+ if( noCase ){
+ GlogUpperToLower(c2);
+ GlogUpperToLower(c);
+ while( c2 != 0 && c2 != c ){
+ c2 = sqlite3Utf8Read(zString, 0, &zString);
+ GlogUpperToLower(c2);
+ }
+ }else{
+ while( c2 != 0 && c2 != c ){
+ c2 = sqlite3Utf8Read(zString, 0, &zString);
+ }
+ }
+ if( c2==0 ) return 0;
+ if( patternCompare(zPattern,zString,pInfo,esc) ) return 1;
+ }
+ return 0;
+ }else if( !prevEscape && c==matchOne ){
+ if( sqlite3Utf8Read(zString, 0, &zString)==0 ){
+ return 0;
+ }
+ }else if( c==matchSet ){
+ int prior_c = 0;
+ assert( esc==0 ); /* This only occurs for GLOB, not LIKE */
+ seen = 0;
+ invert = 0;
+ c = sqlite3Utf8Read(zString, 0, &zString);
+ if( c==0 ) return 0;
+ c2 = sqlite3Utf8Read(zPattern, 0, &zPattern);
+ if( c2=='^' ){
+ invert = 1;
+ c2 = sqlite3Utf8Read(zPattern, 0, &zPattern);
+ }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = sqlite3Utf8Read(zPattern, 0, &zPattern);
+ }
+ while( c2 && c2!=']' ){
+ if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){
+ c2 = sqlite3Utf8Read(zPattern, 0, &zPattern);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else{
+ if( c==c2 ){
+ seen = 1;
+ }
+ prior_c = c2;
+ }
+ c2 = sqlite3Utf8Read(zPattern, 0, &zPattern);
+ }
+ if( c2==0 || (seen ^ invert)==0 ){
+ return 0;
+ }
+ }else if( esc==c && !prevEscape ){
+ prevEscape = 1;
+ }else{
+ c2 = sqlite3Utf8Read(zString, 0, &zString);
+ if( noCase ){
+ GlogUpperToLower(c);
+ GlogUpperToLower(c2);
+ }
+ if( c!=c2 ){
+ return 0;
+ }
+ prevEscape = 0;
+ }
+ }
+ return *zString==0;
+}
+
+/*
+** Count the number of times that the LIKE operator (or GLOB which is
+** just a variation of LIKE) gets called. This is used for testing
+** only.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_like_count = 0;
+#endif
+
+
+/*
+** Implementation of the like() SQL function. This function implements
+** the build-in LIKE operator. The first argument to the function is the
+** pattern and the second argument is the string. So, the SQL statements:
+**
+** A LIKE B
+**
+** is implemented as like(B,A).
+**
+** This same function (with a different compareInfo structure) computes
+** the GLOB operator.
+*/
+static void likeFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const unsigned char *zA, *zB;
+ int escape = 0;
+ sqlite3 *db = sqlite3_context_db_handle(context);
+
+ zB = sqlite3_value_text(argv[0]);
+ zA = sqlite3_value_text(argv[1]);
+
+ /* Limit the length of the LIKE or GLOB pattern to avoid problems
+ ** of deep recursion and N*N behavior in patternCompare().
+ */
+ if( sqlite3_value_bytes(argv[0]) >
+ db->aLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH] ){
+ sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1);
+ return;
+ }
+ assert( zB==sqlite3_value_text(argv[0]) ); /* Encoding did not change */
+
+ if( argc==3 ){
+ /* The escape character string must consist of a single UTF-8 character.
+ ** Otherwise, return an error.
+ */
+ const unsigned char *zEsc = sqlite3_value_text(argv[2]);
+ if( zEsc==0 ) return;
+ if( sqlite3Utf8CharLen((char*)zEsc, -1)!=1 ){
+ sqlite3_result_error(context,
+ "ESCAPE expression must be a single character", -1);
+ return;
+ }
+ escape = sqlite3Utf8Read(zEsc, 0, &zEsc);
+ }
+ if( zA && zB ){
+ struct compareInfo *pInfo = sqlite3_user_data(context);
+#ifdef SQLITE_TEST
+ sqlite3_like_count++;
+#endif
+
+ sqlite3_result_int(context, patternCompare(zB, zA, pInfo, escape));
+ }
+}
+
+/*
+** Implementation of the NULLIF(x,y) function. The result is the first
+** argument if the arguments are different. The result is NULL if the
+** arguments are equal to each other.
+*/
+static void nullifFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ CollSeq *pColl = sqlite3GetFuncCollSeq(context);
+ if( sqlite3MemCompare(argv[0], argv[1], pColl)!=0 ){
+ sqlite3_result_value(context, argv[0]);
+ }
+}
+
+/*
+** Implementation of the VERSION(*) function. The result is the version
+** of the SQLite library that is running.
+*/
+static void versionFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite3_result_text(context, sqlite3_version, -1, SQLITE_STATIC);
+}
+
+/* Array for converting from half-bytes (nybbles) into ASCII hex
+** digits. */
+static const char hexdigits[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+/*
+** EXPERIMENTAL - This is not an official function. The interface may
+** change. This function may disappear. Do not write code that depends
+** on this function.
+**
+** Implementation of the QUOTE() function. This function takes a single
+** argument. If the argument is numeric, the return value is the same as
+** the argument. If the argument is NULL, the return value is the string
+** "NULL". Otherwise, the argument is enclosed in single quotes with
+** single-quote escapes.
+*/
+static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+ if( argc<1 ) return;
+ switch( sqlite3_value_type(argv[0]) ){
+ case SQLITE_NULL: {
+ sqlite3_result_text(context, "NULL", 4, SQLITE_STATIC);
+ break;
+ }
+ case SQLITE_INTEGER:
+ case SQLITE_FLOAT: {
+ sqlite3_result_value(context, argv[0]);
+ break;
+ }
+ case SQLITE_BLOB: {
+ char *zText = 0;
+ char const *zBlob = sqlite3_value_blob(argv[0]);
+ int nBlob = sqlite3_value_bytes(argv[0]);
+ assert( zBlob==sqlite3_value_blob(argv[0]) ); /* No encoding change */
+ zText = (char *)contextMalloc(context, (2*(i64)nBlob)+4);
+ if( zText ){
+ int i;
+ for(i=0; i<nBlob; i++){
+ zText[(i*2)+2] = hexdigits[(zBlob[i]>>4)&0x0F];
+ zText[(i*2)+3] = hexdigits[(zBlob[i])&0x0F];
+ }
+ zText[(nBlob*2)+2] = '\'';
+ zText[(nBlob*2)+3] = '\0';
+ zText[0] = 'X';
+ zText[1] = '\'';
+ sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT);
+ sqlite3_free(zText);
+ }
+ break;
+ }
+ case SQLITE_TEXT: {
+ int i,j;
+ u64 n;
+ const unsigned char *zArg = sqlite3_value_text(argv[0]);
+ char *z;
+
+ if( zArg==0 ) return;
+ for(i=0, n=0; zArg[i]; i++){ if( zArg[i]=='\'' ) n++; }
+ z = contextMalloc(context, ((i64)i)+((i64)n)+3);
+ if( z ){
+ z[0] = '\'';
+ for(i=0, j=1; zArg[i]; i++){
+ z[j++] = zArg[i];
+ if( zArg[i]=='\'' ){
+ z[j++] = '\'';
+ }
+ }
+ z[j++] = '\'';
+ z[j] = 0;
+ sqlite3_result_text(context, z, j, sqlite3_free);
+ }
+ }
+ }
+}
+
+/*
+** The hex() function. Interpret the argument as a blob. Return
+** a hexadecimal rendering as text.
+*/
+static void hexFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int i, n;
+ const unsigned char *pBlob;
+ char *zHex, *z;
+ assert( argc==1 );
+ pBlob = sqlite3_value_blob(argv[0]);
+ n = sqlite3_value_bytes(argv[0]);
+ assert( pBlob==sqlite3_value_blob(argv[0]) ); /* No encoding change */
+ z = zHex = contextMalloc(context, ((i64)n)*2 + 1);
+ if( zHex ){
+ for(i=0; i<n; i++, pBlob++){
+ unsigned char c = *pBlob;
+ *(z++) = hexdigits[(c>>4)&0xf];
+ *(z++) = hexdigits[c&0xf];
+ }
+ *z = 0;
+ sqlite3_result_text(context, zHex, n*2, sqlite3_free);
+ }
+}
+
+/*
+** The zeroblob(N) function returns a zero-filled blob of size N bytes.
+*/
+static void zeroblobFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ i64 n;
+ assert( argc==1 );
+ n = sqlite3_value_int64(argv[0]);
+ if( n>SQLITE_MAX_LENGTH ){
+ sqlite3_result_error_toobig(context);
+ }else{
+ sqlite3_result_zeroblob(context, n);
+ }
+}
+
+/*
+** The replace() function. Three arguments are all strings: call
+** them A, B, and C. The result is also a string which is derived
+** from A by replacing every occurance of B with C. The match
+** must be exact. Collating sequences are not used.
+*/
+static void replaceFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const unsigned char *zStr; /* The input string A */
+ const unsigned char *zPattern; /* The pattern string B */
+ const unsigned char *zRep; /* The replacement string C */
+ unsigned char *zOut; /* The output */
+ int nStr; /* Size of zStr */
+ int nPattern; /* Size of zPattern */
+ int nRep; /* Size of zRep */
+ i64 nOut; /* Maximum size of zOut */
+ int loopLimit; /* Last zStr[] that might match zPattern[] */
+ int i, j; /* Loop counters */
+
+ assert( argc==3 );
+ zStr = sqlite3_value_text(argv[0]);
+ if( zStr==0 ) return;
+ nStr = sqlite3_value_bytes(argv[0]);
+ assert( zStr==sqlite3_value_text(argv[0]) ); /* No encoding change */
+ zPattern = sqlite3_value_text(argv[1]);
+ if( zPattern==0 || zPattern[0]==0 ) return;
+ nPattern = sqlite3_value_bytes(argv[1]);
+ assert( zPattern==sqlite3_value_text(argv[1]) ); /* No encoding change */
+ zRep = sqlite3_value_text(argv[2]);
+ if( zRep==0 ) return;
+ nRep = sqlite3_value_bytes(argv[2]);
+ assert( zRep==sqlite3_value_text(argv[2]) );
+ nOut = nStr + 1;
+ assert( nOut<SQLITE_MAX_LENGTH );
+ zOut = contextMalloc(context, (i64)nOut);
+ if( zOut==0 ){
+ return;
+ }
+ loopLimit = nStr - nPattern;
+ for(i=j=0; i<=loopLimit; i++){
+ if( zStr[i]!=zPattern[0] || memcmp(&zStr[i], zPattern, nPattern) ){
+ zOut[j++] = zStr[i];
+ }else{
+ u8 *zOld;
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ nOut += nRep - nPattern;
+ if( nOut>=db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ sqlite3_result_error_toobig(context);
+ sqlite3_free(zOut);
+ return;
+ }
+ zOld = zOut;
+ zOut = sqlite3_realloc(zOut, (int)nOut);
+ if( zOut==0 ){
+ sqlite3_result_error_nomem(context);
+ sqlite3_free(zOld);
+ return;
+ }
+ memcpy(&zOut[j], zRep, nRep);
+ j += nRep;
+ i += nPattern-1;
+ }
+ }
+ assert( j+nStr-i+1==nOut );
+ memcpy(&zOut[j], &zStr[i], nStr-i);
+ j += nStr - i;
+ assert( j<=nOut );
+ zOut[j] = 0;
+ sqlite3_result_text(context, (char*)zOut, j, sqlite3_free);
+}
+
+/*
+** Implementation of the TRIM(), LTRIM(), and RTRIM() functions.
+** The userdata is 0x1 for left trim, 0x2 for right trim, 0x3 for both.
+*/
+static void trimFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const unsigned char *zIn; /* Input string */
+ const unsigned char *zCharSet; /* Set of characters to trim */
+ int nIn; /* Number of bytes in input */
+ int flags; /* 1: trimleft 2: trimright 3: trim */
+ int i; /* Loop counter */
+ unsigned char *aLen; /* Length of each character in zCharSet */
+ unsigned char **azChar; /* Individual characters in zCharSet */
+ int nChar; /* Number of characters in zCharSet */
+
+ if( sqlite3_value_type(argv[0])==SQLITE_NULL ){
+ return;
+ }
+ zIn = sqlite3_value_text(argv[0]);
+ if( zIn==0 ) return;
+ nIn = sqlite3_value_bytes(argv[0]);
+ assert( zIn==sqlite3_value_text(argv[0]) );
+ if( argc==1 ){
+ static const unsigned char lenOne[] = { 1 };
+ static const unsigned char *azOne[] = { (u8*)" " };
+ nChar = 1;
+ aLen = (u8*)lenOne;
+ azChar = (unsigned char **)azOne;
+ zCharSet = 0;
+ }else if( (zCharSet = sqlite3_value_text(argv[1]))==0 ){
+ return;
+ }else{
+ const unsigned char *z;
+ for(z=zCharSet, nChar=0; *z; nChar++){
+ SQLITE_SKIP_UTF8(z);
+ }
+ if( nChar>0 ){
+ azChar = contextMalloc(context, ((i64)nChar)*(sizeof(char*)+1));
+ if( azChar==0 ){
+ return;
+ }
+ aLen = (unsigned char*)&azChar[nChar];
+ for(z=zCharSet, nChar=0; *z; nChar++){
+ azChar[nChar] = (unsigned char *)z;
+ SQLITE_SKIP_UTF8(z);
+ aLen[nChar] = z - azChar[nChar];
+ }
+ }
+ }
+ if( nChar>0 ){
+ flags = (int)sqlite3_user_data(context);
+ if( flags & 1 ){
+ while( nIn>0 ){
+ int len;
+ for(i=0; i<nChar; i++){
+ len = aLen[i];
+ if( memcmp(zIn, azChar[i], len)==0 ) break;
+ }
+ if( i>=nChar ) break;
+ zIn += len;
+ nIn -= len;
+ }
+ }
+ if( flags & 2 ){
+ while( nIn>0 ){
+ int len;
+ for(i=0; i<nChar; i++){
+ len = aLen[i];
+ if( len<=nIn && memcmp(&zIn[nIn-len],azChar[i],len)==0 ) break;
+ }
+ if( i>=nChar ) break;
+ nIn -= len;
+ }
+ }
+ if( zCharSet ){
+ sqlite3_free(azChar);
+ }
+ }
+ sqlite3_result_text(context, (char*)zIn, nIn, SQLITE_TRANSIENT);
+}
+
+#ifdef SQLITE_SOUNDEX
+/*
+** Compute the soundex encoding of a word.
+*/
+static void soundexFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ char zResult[8];
+ const u8 *zIn;
+ int i, j;
+ static const unsigned char iCode[] = {
+ 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,
+ 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,
+ 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
+ 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
+ 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
+ };
+ assert( argc==1 );
+ zIn = (u8*)sqlite3_value_text(argv[0]);
+ if( zIn==0 ) zIn = (u8*)"";
+ for(i=0; zIn[i] && !isalpha(zIn[i]); i++){}
+ if( zIn[i] ){
+ u8 prevcode = iCode[zIn[i]&0x7f];
+ zResult[0] = toupper(zIn[i]);
+ for(j=1; j<4 && zIn[i]; i++){
+ int code = iCode[zIn[i]&0x7f];
+ if( code>0 ){
+ if( code!=prevcode ){
+ prevcode = code;
+ zResult[j++] = code + '0';
+ }
+ }else{
+ prevcode = 0;
+ }
+ }
+ while( j<4 ){
+ zResult[j++] = '0';
+ }
+ zResult[j] = 0;
+ sqlite3_result_text(context, zResult, 4, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_text(context, "?000", 4, SQLITE_STATIC);
+ }
+}
+#endif
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+/*
+** A function that loads a shared-library extension then returns NULL.
+*/
+static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){
+ const char *zFile = (const char *)sqlite3_value_text(argv[0]);
+ const char *zProc;
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ char *zErrMsg = 0;
+
+ if( argc==2 ){
+ zProc = (const char *)sqlite3_value_text(argv[1]);
+ }else{
+ zProc = 0;
+ }
+ if( zFile && sqlite3_load_extension(db, zFile, zProc, &zErrMsg) ){
+ sqlite3_result_error(context, zErrMsg, -1);
+ sqlite3_free(zErrMsg);
+ }
+}
+#endif
+
+
+/*
+** An instance of the following structure holds the context of a
+** sum() or avg() aggregate computation.
+*/
+typedef struct SumCtx SumCtx;
+struct SumCtx {
+ double rSum; /* Floating point sum */
+ i64 iSum; /* Integer sum */
+ i64 cnt; /* Number of elements summed */
+ u8 overflow; /* True if integer overflow seen */
+ u8 approx; /* True if non-integer value was input to the sum */
+};
+
+/*
+** Routines used to compute the sum, average, and total.
+**
+** The SUM() function follows the (broken) SQL standard which means
+** that it returns NULL if it sums over no inputs. TOTAL returns
+** 0.0 in that case. In addition, TOTAL always returns a float where
+** SUM might return an integer if it never encounters a floating point
+** value. TOTAL never fails, but SUM might through an exception if
+** it overflows an integer.
+*/
+static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){
+ SumCtx *p;
+ int type;
+ assert( argc==1 );
+ p = sqlite3_aggregate_context(context, sizeof(*p));
+ type = sqlite3_value_numeric_type(argv[0]);
+ if( p && type!=SQLITE_NULL ){
+ p->cnt++;
+ if( type==SQLITE_INTEGER ){
+ i64 v = sqlite3_value_int64(argv[0]);
+ p->rSum += v;
+ if( (p->approx|p->overflow)==0 ){
+ i64 iNewSum = p->iSum + v;
+ int s1 = p->iSum >> (sizeof(i64)*8-1);
+ int s2 = v >> (sizeof(i64)*8-1);
+ int s3 = iNewSum >> (sizeof(i64)*8-1);
+ p->overflow = (s1&s2&~s3) | (~s1&~s2&s3);
+ p->iSum = iNewSum;
+ }
+ }else{
+ p->rSum += sqlite3_value_double(argv[0]);
+ p->approx = 1;
+ }
+ }
+}
+static void sumFinalize(sqlite3_context *context){
+ SumCtx *p;
+ p = sqlite3_aggregate_context(context, 0);
+ if( p && p->cnt>0 ){
+ if( p->overflow ){
+ sqlite3_result_error(context,"integer overflow",-1);
+ }else if( p->approx ){
+ sqlite3_result_double(context, p->rSum);
+ }else{
+ sqlite3_result_int64(context, p->iSum);
+ }
+ }
+}
+static void avgFinalize(sqlite3_context *context){
+ SumCtx *p;
+ p = sqlite3_aggregate_context(context, 0);
+ if( p && p->cnt>0 ){
+ sqlite3_result_double(context, p->rSum/(double)p->cnt);
+ }
+}
+static void totalFinalize(sqlite3_context *context){
+ SumCtx *p;
+ p = sqlite3_aggregate_context(context, 0);
+ sqlite3_result_double(context, p ? p->rSum : 0.0);
+}
+
+/*
+** The following structure keeps track of state information for the
+** count() aggregate function.
+*/
+typedef struct CountCtx CountCtx;
+struct CountCtx {
+ i64 n;
+};
+
+/*
+** Routines to implement the count() aggregate function.
+*/
+static void countStep(sqlite3_context *context, int argc, sqlite3_value **argv){
+ CountCtx *p;
+ p = sqlite3_aggregate_context(context, sizeof(*p));
+ if( (argc==0 || SQLITE_NULL!=sqlite3_value_type(argv[0])) && p ){
+ p->n++;
+ }
+}
+static void countFinalize(sqlite3_context *context){
+ CountCtx *p;
+ p = sqlite3_aggregate_context(context, 0);
+ sqlite3_result_int64(context, p ? p->n : 0);
+}
+
+/*
+** Routines to implement min() and max() aggregate functions.
+*/
+static void minmaxStep(sqlite3_context *context, int argc, sqlite3_value **argv){
+ Mem *pArg = (Mem *)argv[0];
+ Mem *pBest;
+
+ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ pBest = (Mem *)sqlite3_aggregate_context(context, sizeof(*pBest));
+ if( !pBest ) return;
+
+ if( pBest->flags ){
+ int max;
+ int cmp;
+ CollSeq *pColl = sqlite3GetFuncCollSeq(context);
+ /* This step function is used for both the min() and max() aggregates,
+ ** the only difference between the two being that the sense of the
+ ** comparison is inverted. For the max() aggregate, the
+ ** sqlite3_user_data() function returns (void *)-1. For min() it
+ ** returns (void *)db, where db is the sqlite3* database pointer.
+ ** Therefore the next statement sets variable 'max' to 1 for the max()
+ ** aggregate, or 0 for min().
+ */
+ max = sqlite3_user_data(context)!=0;
+ cmp = sqlite3MemCompare(pBest, pArg, pColl);
+ if( (max && cmp<0) || (!max && cmp>0) ){
+ sqlite3VdbeMemCopy(pBest, pArg);
+ }
+ }else{
+ sqlite3VdbeMemCopy(pBest, pArg);
+ }
+}
+static void minMaxFinalize(sqlite3_context *context){
+ sqlite3_value *pRes;
+ pRes = (sqlite3_value *)sqlite3_aggregate_context(context, 0);
+ if( pRes ){
+ if( pRes->flags ){
+ sqlite3_result_value(context, pRes);
+ }
+ sqlite3VdbeMemRelease(pRes);
+ }
+}
+
+/*
+** group_concat(EXPR, ?SEPARATOR?)
+*/
+static void groupConcatStep(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zVal;
+ StrAccum *pAccum;
+ const char *zSep;
+ int nVal, nSep;
+ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
+
+ if( pAccum ){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ pAccum->useMalloc = 1;
+ pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
+ if( pAccum->nChar ){
+ if( argc==2 ){
+ zSep = (char*)sqlite3_value_text(argv[1]);
+ nSep = sqlite3_value_bytes(argv[1]);
+ }else{
+ zSep = ",";
+ nSep = 1;
+ }
+ sqlite3StrAccumAppend(pAccum, zSep, nSep);
+ }
+ zVal = (char*)sqlite3_value_text(argv[0]);
+ nVal = sqlite3_value_bytes(argv[0]);
+ sqlite3StrAccumAppend(pAccum, zVal, nVal);
+ }
+}
+static void groupConcatFinalize(sqlite3_context *context){
+ StrAccum *pAccum;
+ pAccum = sqlite3_aggregate_context(context, 0);
+ if( pAccum ){
+ if( pAccum->tooBig ){
+ sqlite3_result_error_toobig(context);
+ }else if( pAccum->mallocFailed ){
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1,
+ sqlite3_free);
+ }
+ }
+}
+
+/*
+** This function registered all of the above C functions as SQL
+** functions. This should be the only routine in this file with
+** external linkage.
+*/
+SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(sqlite3 *db){
+ static const struct {
+ char *zName;
+ signed char nArg;
+ u8 argType; /* 1: 0, 2: 1, 3: 2,... N: N-1. */
+ u8 eTextRep; /* 1: UTF-16. 0: UTF-8 */
+ u8 needCollSeq;
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value **);
+ } aFuncs[] = {
+ { "min", -1, 0, SQLITE_UTF8, 1, minmaxFunc },
+ { "min", 0, 0, SQLITE_UTF8, 1, 0 },
+ { "max", -1, 1, SQLITE_UTF8, 1, minmaxFunc },
+ { "max", 0, 1, SQLITE_UTF8, 1, 0 },
+ { "typeof", 1, 0, SQLITE_UTF8, 0, typeofFunc },
+ { "length", 1, 0, SQLITE_UTF8, 0, lengthFunc },
+ { "substr", 2, 0, SQLITE_UTF8, 0, substrFunc },
+ { "substr", 3, 0, SQLITE_UTF8, 0, substrFunc },
+ { "abs", 1, 0, SQLITE_UTF8, 0, absFunc },
+ { "round", 1, 0, SQLITE_UTF8, 0, roundFunc },
+ { "round", 2, 0, SQLITE_UTF8, 0, roundFunc },
+ { "upper", 1, 0, SQLITE_UTF8, 0, upperFunc },
+ { "lower", 1, 0, SQLITE_UTF8, 0, lowerFunc },
+ { "coalesce", -1, 0, SQLITE_UTF8, 0, ifnullFunc },
+ { "coalesce", 0, 0, SQLITE_UTF8, 0, 0 },
+ { "coalesce", 1, 0, SQLITE_UTF8, 0, 0 },
+ { "hex", 1, 0, SQLITE_UTF8, 0, hexFunc },
+ { "ifnull", 2, 0, SQLITE_UTF8, 1, ifnullFunc },
+ { "random", -1, 0, SQLITE_UTF8, 0, randomFunc },
+ { "randomblob", 1, 0, SQLITE_UTF8, 0, randomBlob },
+ { "nullif", 2, 0, SQLITE_UTF8, 1, nullifFunc },
+ { "sqlite_version", 0, 0, SQLITE_UTF8, 0, versionFunc},
+ { "quote", 1, 0, SQLITE_UTF8, 0, quoteFunc },
+ { "last_insert_rowid", 0, 0, SQLITE_UTF8, 0, last_insert_rowid },
+ { "changes", 0, 0, SQLITE_UTF8, 0, changes },
+ { "total_changes", 0, 0, SQLITE_UTF8, 0, total_changes },
+ { "replace", 3, 0, SQLITE_UTF8, 0, replaceFunc },
+ { "ltrim", 1, 1, SQLITE_UTF8, 0, trimFunc },
+ { "ltrim", 2, 1, SQLITE_UTF8, 0, trimFunc },
+ { "rtrim", 1, 2, SQLITE_UTF8, 0, trimFunc },
+ { "rtrim", 2, 2, SQLITE_UTF8, 0, trimFunc },
+ { "trim", 1, 3, SQLITE_UTF8, 0, trimFunc },
+ { "trim", 2, 3, SQLITE_UTF8, 0, trimFunc },
+ { "zeroblob", 1, 0, SQLITE_UTF8, 0, zeroblobFunc },
+#ifdef SQLITE_SOUNDEX
+ { "soundex", 1, 0, SQLITE_UTF8, 0, soundexFunc},
+#endif
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+ { "load_extension", 1, 0, SQLITE_UTF8, 0, loadExt },
+ { "load_extension", 2, 0, SQLITE_UTF8, 0, loadExt },
+#endif
+ };
+ static const struct {
+ char *zName;
+ signed char nArg;
+ u8 argType;
+ u8 needCollSeq;
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**);
+ void (*xFinalize)(sqlite3_context*);
+ } aAggs[] = {
+ { "min", 1, 0, 1, minmaxStep, minMaxFinalize },
+ { "max", 1, 1, 1, minmaxStep, minMaxFinalize },
+ { "sum", 1, 0, 0, sumStep, sumFinalize },
+ { "total", 1, 0, 0, sumStep, totalFinalize },
+ { "avg", 1, 0, 0, sumStep, avgFinalize },
+ { "count", 0, 0, 0, countStep, countFinalize },
+ { "count", 1, 0, 0, countStep, countFinalize },
+ { "group_concat", 1, 0, 0, groupConcatStep, groupConcatFinalize },
+ { "group_concat", 2, 0, 0, groupConcatStep, groupConcatFinalize },
+ };
+ int i;
+
+ for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+ void *pArg;
+ u8 argType = aFuncs[i].argType;
+ pArg = (void*)(int)argType;
+ sqlite3CreateFunc(db, aFuncs[i].zName, aFuncs[i].nArg,
+ aFuncs[i].eTextRep, pArg, aFuncs[i].xFunc, 0, 0);
+ if( aFuncs[i].needCollSeq ){
+ FuncDef *pFunc = sqlite3FindFunction(db, aFuncs[i].zName,
+ strlen(aFuncs[i].zName), aFuncs[i].nArg, aFuncs[i].eTextRep, 0);
+ if( pFunc && aFuncs[i].needCollSeq ){
+ pFunc->needCollSeq = 1;
+ }
+ }
+ }
+#ifndef SQLITE_OMIT_ALTERTABLE
+ sqlite3AlterFunctions(db);
+#endif
+#ifndef SQLITE_OMIT_PARSER
+ sqlite3AttachFunctions(db);
+#endif
+ for(i=0; i<sizeof(aAggs)/sizeof(aAggs[0]); i++){
+ void *pArg = (void*)(int)aAggs[i].argType;
+ sqlite3CreateFunc(db, aAggs[i].zName, aAggs[i].nArg, SQLITE_UTF8,
+ pArg, 0, aAggs[i].xStep, aAggs[i].xFinalize);
+ if( aAggs[i].needCollSeq ){
+ FuncDef *pFunc = sqlite3FindFunction( db, aAggs[i].zName,
+ strlen(aAggs[i].zName), aAggs[i].nArg, SQLITE_UTF8, 0);
+ if( pFunc && aAggs[i].needCollSeq ){
+ pFunc->needCollSeq = 1;
+ }
+ }
+ }
+ sqlite3RegisterDateTimeFunctions(db);
+ if( !db->mallocFailed ){
+ int rc = sqlite3_overload_function(db, "MATCH", 2);
+ assert( rc==SQLITE_NOMEM || rc==SQLITE_OK );
+ if( rc==SQLITE_NOMEM ){
+ db->mallocFailed = 1;
+ }
+ }
+#ifdef SQLITE_SSE
+ (void)sqlite3SseFunctions(db);
+#endif
+#ifdef SQLITE_CASE_SENSITIVE_LIKE
+ sqlite3RegisterLikeFunctions(db, 1);
+#else
+ sqlite3RegisterLikeFunctions(db, 0);
+#endif
+}
+
+/*
+** Set the LIKEOPT flag on the 2-argument function with the given name.
+*/
+static void setLikeOptFlag(sqlite3 *db, const char *zName, int flagVal){
+ FuncDef *pDef;
+ pDef = sqlite3FindFunction(db, zName, strlen(zName), 2, SQLITE_UTF8, 0);
+ if( pDef ){
+ pDef->flags = flagVal;
+ }
+}
+
+/*
+** Register the built-in LIKE and GLOB functions. The caseSensitive
+** parameter determines whether or not the LIKE operator is case
+** sensitive. GLOB is always case sensitive.
+*/
+SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){
+ struct compareInfo *pInfo;
+ if( caseSensitive ){
+ pInfo = (struct compareInfo*)&likeInfoAlt;
+ }else{
+ pInfo = (struct compareInfo*)&likeInfoNorm;
+ }
+ sqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0);
+ sqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0);
+ sqlite3CreateFunc(db, "glob", 2, SQLITE_UTF8,
+ (struct compareInfo*)&globInfo, likeFunc, 0,0);
+ setLikeOptFlag(db, "glob", SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE);
+ setLikeOptFlag(db, "like",
+ caseSensitive ? (SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE) : SQLITE_FUNC_LIKE);
+}
+
+/*
+** pExpr points to an expression which implements a function. If
+** it is appropriate to apply the LIKE optimization to that function
+** then set aWc[0] through aWc[2] to the wildcard characters and
+** return TRUE. If the function is not a LIKE-style function then
+** return FALSE.
+*/
+SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){
+ FuncDef *pDef;
+ if( pExpr->op!=TK_FUNCTION || !pExpr->pList ){
+ return 0;
+ }
+ if( pExpr->pList->nExpr!=2 ){
+ return 0;
+ }
+ pDef = sqlite3FindFunction(db, (char*)pExpr->token.z, pExpr->token.n, 2,
+ SQLITE_UTF8, 0);
+ if( pDef==0 || (pDef->flags & SQLITE_FUNC_LIKE)==0 ){
+ return 0;
+ }
+
+ /* The memcpy() statement assumes that the wildcard characters are
+ ** the first three statements in the compareInfo structure. The
+ ** asserts() that follow verify that assumption
+ */
+ memcpy(aWc, pDef->pUserData, 3);
+ assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll );
+ assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne );
+ assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet );
+ *pIsNocase = (pDef->flags & SQLITE_FUNC_CASE)==0;
+ return 1;
+}
+
+/************** End of func.c ************************************************/
+/************** Begin file insert.c ******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** to handle INSERT statements in SQLite.
+**
+** $Id: insert.c,v 1.238 2008/04/28 18:46:43 drh Exp $
+*/
+
+/*
+** Set P4 of the most recently inserted opcode to a column affinity
+** string for index pIdx. A column affinity string has one character
+** for each column in the table, according to the affinity of the column:
+**
+** Character Column affinity
+** ------------------------------
+** 'a' TEXT
+** 'b' NONE
+** 'c' NUMERIC
+** 'd' INTEGER
+** 'e' REAL
+**
+** An extra 'b' is appended to the end of the string to cover the
+** rowid that appears as the last column in every index.
+*/
+SQLITE_PRIVATE void sqlite3IndexAffinityStr(Vdbe *v, Index *pIdx){
+ if( !pIdx->zColAff ){
+ /* The first time a column affinity string for a particular index is
+ ** required, it is allocated and populated here. It is then stored as
+ ** a member of the Index structure for subsequent use.
+ **
+ ** The column affinity string will eventually be deleted by
+ ** sqliteDeleteIndex() when the Index structure itself is cleaned
+ ** up.
+ */
+ int n;
+ Table *pTab = pIdx->pTable;
+ sqlite3 *db = sqlite3VdbeDb(v);
+ pIdx->zColAff = (char *)sqlite3DbMallocRaw(db, pIdx->nColumn+2);
+ if( !pIdx->zColAff ){
+ return;
+ }
+ for(n=0; n<pIdx->nColumn; n++){
+ pIdx->zColAff[n] = pTab->aCol[pIdx->aiColumn[n]].affinity;
+ }
+ pIdx->zColAff[n++] = SQLITE_AFF_NONE;
+ pIdx->zColAff[n] = 0;
+ }
+
+ sqlite3VdbeChangeP4(v, -1, pIdx->zColAff, 0);
+}
+
+/*
+** Set P4 of the most recently inserted opcode to a column affinity
+** string for table pTab. A column affinity string has one character
+** for each column indexed by the index, according to the affinity of the
+** column:
+**
+** Character Column affinity
+** ------------------------------
+** 'a' TEXT
+** 'b' NONE
+** 'c' NUMERIC
+** 'd' INTEGER
+** 'e' REAL
+*/
+SQLITE_PRIVATE void sqlite3TableAffinityStr(Vdbe *v, Table *pTab){
+ /* The first time a column affinity string for a particular table
+ ** is required, it is allocated and populated here. It is then
+ ** stored as a member of the Table structure for subsequent use.
+ **
+ ** The column affinity string will eventually be deleted by
+ ** sqlite3DeleteTable() when the Table structure itself is cleaned up.
+ */
+ if( !pTab->zColAff ){
+ char *zColAff;
+ int i;
+ sqlite3 *db = sqlite3VdbeDb(v);
+
+ zColAff = (char *)sqlite3DbMallocRaw(db, pTab->nCol+1);
+ if( !zColAff ){
+ return;
+ }
+
+ for(i=0; i<pTab->nCol; i++){
+ zColAff[i] = pTab->aCol[i].affinity;
+ }
+ zColAff[pTab->nCol] = '\0';
+
+ pTab->zColAff = zColAff;
+ }
+
+ sqlite3VdbeChangeP4(v, -1, pTab->zColAff, 0);
+}
+
+/*
+** Return non-zero if the table pTab in database iDb or any of its indices
+** have been opened at any point in the VDBE program beginning at location
+** iStartAddr throught the end of the program. This is used to see if
+** a statement of the form "INSERT INTO <iDb, pTab> SELECT ..." can
+** run without using temporary table for the results of the SELECT.
+*/
+static int readsTable(Vdbe *v, int iStartAddr, int iDb, Table *pTab){
+ int i;
+ int iEnd = sqlite3VdbeCurrentAddr(v);
+ for(i=iStartAddr; i<iEnd; i++){
+ VdbeOp *pOp = sqlite3VdbeGetOp(v, i);
+ assert( pOp!=0 );
+ if( pOp->opcode==OP_OpenRead && pOp->p3==iDb ){
+ Index *pIndex;
+ int tnum = pOp->p2;
+ if( tnum==pTab->tnum ){
+ return 1;
+ }
+ for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){
+ if( tnum==pIndex->tnum ){
+ return 1;
+ }
+ }
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( pOp->opcode==OP_VOpen && pOp->p4.pVtab==pTab->pVtab ){
+ assert( pOp->p4.pVtab!=0 );
+ assert( pOp->p4type==P4_VTAB );
+ return 1;
+ }
+#endif
+ }
+ return 0;
+}
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+/*
+** Write out code to initialize the autoincrement logic. This code
+** looks up the current autoincrement value in the sqlite_sequence
+** table and stores that value in a register. Code generated by
+** autoIncStep() will keep that register holding the largest
+** rowid value. Code generated by autoIncEnd() will write the new
+** largest value of the counter back into the sqlite_sequence table.
+**
+** This routine returns the index of the mem[] cell that contains
+** the maximum rowid counter.
+**
+** Three consecutive registers are allocated by this routine. The
+** first two hold the name of the target table and the maximum rowid
+** inserted into the target table, respectively.
+** The third holds the rowid in sqlite_sequence where we will
+** write back the revised maximum rowid. This routine returns the
+** index of the second of these three registers.
+*/
+static int autoIncBegin(
+ Parse *pParse, /* Parsing context */
+ int iDb, /* Index of the database holding pTab */
+ Table *pTab /* The table we are writing to */
+){
+ int memId = 0; /* Register holding maximum rowid */
+ if( pTab->autoInc ){
+ Vdbe *v = pParse->pVdbe;
+ Db *pDb = &pParse->db->aDb[iDb];
+ int iCur = pParse->nTab;
+ int addr; /* Address of the top of the loop */
+ assert( v );
+ pParse->nMem++; /* Holds name of table */
+ memId = ++pParse->nMem;
+ pParse->nMem++;
+ sqlite3OpenTable(pParse, iCur, iDb, pDb->pSchema->pSeqTab, OP_OpenRead);
+ addr = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, memId-1, 0, pTab->zName, 0);
+ sqlite3VdbeAddOp2(v, OP_Rewind, iCur, addr+8);
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, 0, memId);
+ sqlite3VdbeAddOp3(v, OP_Ne, memId-1, addr+7, memId);
+ sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL);
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCur, memId+1);
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, 1, memId);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr+8);
+ sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+2);
+ sqlite3VdbeAddOp2(v, OP_Close, iCur, 0);
+ }
+ return memId;
+}
+
+/*
+** Update the maximum rowid for an autoincrement calculation.
+**
+** This routine should be called when the top of the stack holds a
+** new rowid that is about to be inserted. If that new rowid is
+** larger than the maximum rowid in the memId memory cell, then the
+** memory cell is updated. The stack is unchanged.
+*/
+static void autoIncStep(Parse *pParse, int memId, int regRowid){
+ if( memId>0 ){
+ sqlite3VdbeAddOp2(pParse->pVdbe, OP_MemMax, memId, regRowid);
+ }
+}
+
+/*
+** After doing one or more inserts, the maximum rowid is stored
+** in reg[memId]. Generate code to write this value back into the
+** the sqlite_sequence table.
+*/
+static void autoIncEnd(
+ Parse *pParse, /* The parsing context */
+ int iDb, /* Index of the database holding pTab */
+ Table *pTab, /* Table we are inserting into */
+ int memId /* Memory cell holding the maximum rowid */
+){
+ if( pTab->autoInc ){
+ int iCur = pParse->nTab;
+ Vdbe *v = pParse->pVdbe;
+ Db *pDb = &pParse->db->aDb[iDb];
+ int j1;
+ int iRec = ++pParse->nMem; /* Memory cell used for record */
+
+ assert( v );
+ sqlite3OpenTable(pParse, iCur, iDb, pDb->pSchema->pSeqTab, OP_OpenWrite);
+ j1 = sqlite3VdbeAddOp1(v, OP_NotNull, memId+1);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, iCur, memId+1);
+ sqlite3VdbeJumpHere(v, j1);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, memId-1, 2, iRec);
+ sqlite3VdbeAddOp3(v, OP_Insert, iCur, iRec, memId+1);
+ sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ sqlite3VdbeAddOp1(v, OP_Close, iCur);
+ }
+}
+#else
+/*
+** If SQLITE_OMIT_AUTOINCREMENT is defined, then the three routines
+** above are all no-ops
+*/
+# define autoIncBegin(A,B,C) (0)
+# define autoIncStep(A,B,C)
+# define autoIncEnd(A,B,C,D)
+#endif /* SQLITE_OMIT_AUTOINCREMENT */
+
+
+/* Forward declaration */
+static int xferOptimization(
+ Parse *pParse, /* Parser context */
+ Table *pDest, /* The table we are inserting into */
+ Select *pSelect, /* A SELECT statement to use as the data source */
+ int onError, /* How to handle constraint errors */
+ int iDbDest /* The database of pDest */
+);
+
+/*
+** This routine is call to handle SQL of the following forms:
+**
+** insert into TABLE (IDLIST) values(EXPRLIST)
+** insert into TABLE (IDLIST) select
+**
+** The IDLIST following the table name is always optional. If omitted,
+** then a list of all columns for the table is substituted. The IDLIST
+** appears in the pColumn parameter. pColumn is NULL if IDLIST is omitted.
+**
+** The pList parameter holds EXPRLIST in the first form of the INSERT
+** statement above, and pSelect is NULL. For the second form, pList is
+** NULL and pSelect is a pointer to the select statement used to generate
+** data for the insert.
+**
+** The code generated follows one of four templates. For a simple
+** select with data coming from a VALUES clause, the code executes
+** once straight down through. The template looks like this:
+**
+** open write cursor to <table> and its indices
+** puts VALUES clause expressions onto the stack
+** write the resulting record into <table>
+** cleanup
+**
+** The three remaining templates assume the statement is of the form
+**
+** INSERT INTO <table> SELECT ...
+**
+** If the SELECT clause is of the restricted form "SELECT * FROM <table2>" -
+** in other words if the SELECT pulls all columns from a single table
+** and there is no WHERE or LIMIT or GROUP BY or ORDER BY clauses, and
+** if <table2> and <table1> are distinct tables but have identical
+** schemas, including all the same indices, then a special optimization
+** is invoked that copies raw records from <table2> over to <table1>.
+** See the xferOptimization() function for the implementation of this
+** template. This is the second template.
+**
+** open a write cursor to <table>
+** open read cursor on <table2>
+** transfer all records in <table2> over to <table>
+** close cursors
+** foreach index on <table>
+** open a write cursor on the <table> index
+** open a read cursor on the corresponding <table2> index
+** transfer all records from the read to the write cursors
+** close cursors
+** end foreach
+**
+** The third template is for when the second template does not apply
+** and the SELECT clause does not read from <table> at any time.
+** The generated code follows this template:
+**
+** goto B
+** A: setup for the SELECT
+** loop over the rows in the SELECT
+** gosub C
+** end loop
+** cleanup after the SELECT
+** goto D
+** B: open write cursor to <table> and its indices
+** goto A
+** C: insert the select result into <table>
+** return
+** D: cleanup
+**
+** The fourth template is used if the insert statement takes its
+** values from a SELECT but the data is being inserted into a table
+** that is also read as part of the SELECT. In the third form,
+** we have to use a intermediate table to store the results of
+** the select. The template is like this:
+**
+** goto B
+** A: setup for the SELECT
+** loop over the tables in the SELECT
+** gosub C
+** end loop
+** cleanup after the SELECT
+** goto D
+** C: insert the select result into the intermediate table
+** return
+** B: open a cursor to an intermediate table
+** goto A
+** D: open write cursor to <table> and its indices
+** loop over the intermediate table
+** transfer values form intermediate table into <table>
+** end the loop
+** cleanup
+*/
+SQLITE_PRIVATE void sqlite3Insert(
+ Parse *pParse, /* Parser context */
+ SrcList *pTabList, /* Name of table into which we are inserting */
+ ExprList *pList, /* List of values to be inserted */
+ Select *pSelect, /* A SELECT statement to use as the data source */
+ IdList *pColumn, /* Column names corresponding to IDLIST. */
+ int onError /* How to handle constraint errors */
+){
+ sqlite3 *db; /* The main database structure */
+ Table *pTab; /* The table to insert into. aka TABLE */
+ char *zTab; /* Name of the table into which we are inserting */
+ const char *zDb; /* Name of the database holding this table */
+ int i, j, idx; /* Loop counters */
+ Vdbe *v; /* Generate code into this virtual machine */
+ Index *pIdx; /* For looping over indices of the table */
+ int nColumn; /* Number of columns in the data */
+ int nHidden = 0; /* Number of hidden columns if TABLE is virtual */
+ int baseCur = 0; /* VDBE Cursor number for pTab */
+ int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */
+ int endOfLoop; /* Label for the end of the insertion loop */
+ int useTempTable = 0; /* Store SELECT results in intermediate table */
+ int srcTab = 0; /* Data comes from this temporary cursor if >=0 */
+ int iCont=0,iBreak=0; /* Beginning and end of the loop over srcTab */
+ int iSelectLoop = 0; /* Address of code that implements the SELECT */
+ int iCleanup = 0; /* Address of the cleanup code */
+ int iInsertBlock = 0; /* Address of the subroutine used to insert data */
+ int newIdx = -1; /* Cursor for the NEW pseudo-table */
+ int iDb; /* Index of database holding TABLE */
+ Db *pDb; /* The database containing table being inserted into */
+ int appendFlag = 0; /* True if the insert is likely to be an append */
+
+ /* Register allocations */
+ int regFromSelect; /* Base register for data coming from SELECT */
+ int regAutoinc = 0; /* Register holding the AUTOINCREMENT counter */
+ int regRowCount = 0; /* Memory cell used for the row counter */
+ int regIns; /* Block of regs holding rowid+data being inserted */
+ int regRowid; /* registers holding insert rowid */
+ int regData; /* register holding first column to insert */
+ int regRecord; /* Holds the assemblied row record */
+ int *aRegIdx = 0; /* One register allocated to each index */
+
+
+#ifndef SQLITE_OMIT_TRIGGER
+ int isView; /* True if attempting to insert into a view */
+ int triggers_exist = 0; /* True if there are FOR EACH ROW triggers */
+#endif
+
+ db = pParse->db;
+ if( pParse->nErr || db->mallocFailed ){
+ goto insert_cleanup;
+ }
+
+ /* Locate the table into which we will be inserting new information.
+ */
+ assert( pTabList->nSrc==1 );
+ zTab = pTabList->a[0].zName;
+ if( zTab==0 ) goto insert_cleanup;
+ pTab = sqlite3SrcListLookup(pParse, pTabList);
+ if( pTab==0 ){
+ goto insert_cleanup;
+ }
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( iDb<db->nDb );
+ pDb = &db->aDb[iDb];
+ zDb = pDb->zName;
+ if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){
+ goto insert_cleanup;
+ }
+
+ /* Figure out if we have any triggers and if the table being
+ ** inserted into is a view
+ */
+#ifndef SQLITE_OMIT_TRIGGER
+ triggers_exist = sqlite3TriggersExist(pParse, pTab, TK_INSERT, 0);
+ isView = pTab->pSelect!=0;
+#else
+# define triggers_exist 0
+# define isView 0
+#endif
+#ifdef SQLITE_OMIT_VIEW
+# undef isView
+# define isView 0
+#endif
+
+ /* Ensure that:
+ * (a) the table is not read-only,
+ * (b) that if it is a view then ON INSERT triggers exist
+ */
+ if( sqlite3IsReadOnly(pParse, pTab, triggers_exist) ){
+ goto insert_cleanup;
+ }
+ assert( pTab!=0 );
+
+ /* If pTab is really a view, make sure it has been initialized.
+ ** ViewGetColumnNames() is a no-op if pTab is not a view (or virtual
+ ** module table).
+ */
+ if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ goto insert_cleanup;
+ }
+
+ /* Allocate a VDBE
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto insert_cleanup;
+ if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
+ sqlite3BeginWriteOperation(pParse, pSelect || triggers_exist, iDb);
+
+ /* if there are row triggers, allocate a temp table for new.* references. */
+ if( triggers_exist ){
+ newIdx = pParse->nTab++;
+ }
+
+#ifndef SQLITE_OMIT_XFER_OPT
+ /* If the statement is of the form
+ **
+ ** INSERT INTO <table1> SELECT * FROM <table2>;
+ **
+ ** Then special optimizations can be applied that make the transfer
+ ** very fast and which reduce fragmentation of indices.
+ */
+ if( pColumn==0 && xferOptimization(pParse, pTab, pSelect, onError, iDb) ){
+ assert( !triggers_exist );
+ assert( pList==0 );
+ goto insert_cleanup;
+ }
+#endif /* SQLITE_OMIT_XFER_OPT */
+
+ /* If this is an AUTOINCREMENT table, look up the sequence number in the
+ ** sqlite_sequence table and store it in memory cell regAutoinc.
+ */
+ regAutoinc = autoIncBegin(pParse, iDb, pTab);
+
+ /* Figure out how many columns of data are supplied. If the data
+ ** is coming from a SELECT statement, then this step also generates
+ ** all the code to implement the SELECT statement and invoke a subroutine
+ ** to process each row of the result. (Template 2.) If the SELECT
+ ** statement uses the the table that is being inserted into, then the
+ ** subroutine is also coded here. That subroutine stores the SELECT
+ ** results in a temporary table. (Template 3.)
+ */
+ if( pSelect ){
+ /* Data is coming from a SELECT. Generate code to implement that SELECT
+ */
+ SelectDest dest;
+ int rc, iInitCode;
+
+ iInitCode = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
+ iSelectLoop = sqlite3VdbeCurrentAddr(v);
+ iInsertBlock = sqlite3VdbeMakeLabel(v);
+ sqlite3SelectDestInit(&dest, SRT_Subroutine, iInsertBlock);
+
+ /* Resolve the expressions in the SELECT statement and execute it. */
+ rc = sqlite3Select(pParse, pSelect, &dest, 0, 0, 0, 0);
+ if( rc || pParse->nErr || db->mallocFailed ){
+ goto insert_cleanup;
+ }
+
+ regFromSelect = dest.iMem;
+ iCleanup = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iCleanup);
+ assert( pSelect->pEList );
+ nColumn = pSelect->pEList->nExpr;
+
+ /* Set useTempTable to TRUE if the result of the SELECT statement
+ ** should be written into a temporary table. Set to FALSE if each
+ ** row of the SELECT can be written directly into the result table.
+ **
+ ** A temp table must be used if the table being updated is also one
+ ** of the tables being read by the SELECT statement. Also use a
+ ** temp table in the case of row triggers.
+ */
+ if( triggers_exist || readsTable(v, iSelectLoop, iDb, pTab) ){
+ useTempTable = 1;
+ }
+
+ if( useTempTable ){
+ /* Generate the subroutine that SELECT calls to process each row of
+ ** the result. Store the result in a temporary table
+ */
+ int regRec, regRowid;
+
+ srcTab = pParse->nTab++;
+ regRec = sqlite3GetTempReg(pParse);
+ regRowid = sqlite3GetTempReg(pParse);
+ sqlite3VdbeResolveLabel(v, iInsertBlock);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regFromSelect, nColumn, regRec);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, srcTab, regRowid);
+ sqlite3VdbeAddOp3(v, OP_Insert, srcTab, regRec, regRowid);
+ sqlite3VdbeAddOp2(v, OP_Return, 0, 0);
+ sqlite3ReleaseTempReg(pParse, regRec);
+ sqlite3ReleaseTempReg(pParse, regRowid);
+
+ /* The following code runs first because the GOTO at the very top
+ ** of the program jumps to it. Create the temporary table, then jump
+ ** back up and execute the SELECT code above.
+ */
+ sqlite3VdbeJumpHere(v, iInitCode);
+ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, srcTab, nColumn);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iSelectLoop);
+ sqlite3VdbeResolveLabel(v, iCleanup);
+ }else{
+ sqlite3VdbeJumpHere(v, iInitCode);
+ }
+ }else{
+ /* This is the case if the data for the INSERT is coming from a VALUES
+ ** clause
+ */
+ NameContext sNC;
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ srcTab = -1;
+ assert( useTempTable==0 );
+ nColumn = pList ? pList->nExpr : 0;
+ for(i=0; i<nColumn; i++){
+ if( sqlite3ExprResolveNames(&sNC, pList->a[i].pExpr) ){
+ goto insert_cleanup;
+ }
+ }
+ }
+
+ /* Make sure the number of columns in the source data matches the number
+ ** of columns to be inserted into the table.
+ */
+ if( IsVirtual(pTab) ){
+ for(i=0; i<pTab->nCol; i++){
+ nHidden += (IsHiddenColumn(&pTab->aCol[i]) ? 1 : 0);
+ }
+ }
+ if( pColumn==0 && nColumn && nColumn!=(pTab->nCol-nHidden) ){
+ sqlite3ErrorMsg(pParse,
+ "table %S has %d columns but %d values were supplied",
+ pTabList, 0, pTab->nCol, nColumn);
+ goto insert_cleanup;
+ }
+ if( pColumn!=0 && nColumn!=pColumn->nId ){
+ sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId);
+ goto insert_cleanup;
+ }
+
+ /* If the INSERT statement included an IDLIST term, then make sure
+ ** all elements of the IDLIST really are columns of the table and
+ ** remember the column indices.
+ **
+ ** If the table has an INTEGER PRIMARY KEY column and that column
+ ** is named in the IDLIST, then record in the keyColumn variable
+ ** the index into IDLIST of the primary key column. keyColumn is
+ ** the index of the primary key as it appears in IDLIST, not as
+ ** is appears in the original table. (The index of the primary
+ ** key in the original table is pTab->iPKey.)
+ */
+ if( pColumn ){
+ for(i=0; i<pColumn->nId; i++){
+ pColumn->a[i].idx = -1;
+ }
+ for(i=0; i<pColumn->nId; i++){
+ for(j=0; j<pTab->nCol; j++){
+ if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){
+ pColumn->a[i].idx = j;
+ if( j==pTab->iPKey ){
+ keyColumn = i;
+ }
+ break;
+ }
+ }
+ if( j>=pTab->nCol ){
+ if( sqlite3IsRowid(pColumn->a[i].zName) ){
+ keyColumn = i;
+ }else{
+ sqlite3ErrorMsg(pParse, "table %S has no column named %s",
+ pTabList, 0, pColumn->a[i].zName);
+ pParse->nErr++;
+ goto insert_cleanup;
+ }
+ }
+ }
+ }
+
+ /* If there is no IDLIST term but the table has an integer primary
+ ** key, the set the keyColumn variable to the primary key column index
+ ** in the original table definition.
+ */
+ if( pColumn==0 && nColumn>0 ){
+ keyColumn = pTab->iPKey;
+ }
+
+ /* Open the temp table for FOR EACH ROW triggers
+ */
+ if( triggers_exist ){
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, pTab->nCol);
+ sqlite3VdbeAddOp2(v, OP_OpenPseudo, newIdx, 0);
+ }
+
+ /* Initialize the count of rows to be inserted
+ */
+ if( db->flags & SQLITE_CountRows ){
+ regRowCount = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
+ }
+
+ /* If this is not a view, open the table and and all indices */
+ if( !isView ){
+ int nIdx;
+ int i;
+
+ baseCur = pParse->nTab;
+ nIdx = sqlite3OpenTableAndIndices(pParse, pTab, baseCur, OP_OpenWrite);
+ aRegIdx = sqlite3DbMallocRaw(db, sizeof(int)*(nIdx+1));
+ if( aRegIdx==0 ){
+ goto insert_cleanup;
+ }
+ for(i=0; i<nIdx; i++){
+ aRegIdx[i] = ++pParse->nMem;
+ }
+ }
+
+ /* If the data source is a temporary table, then we have to create
+ ** a loop because there might be multiple rows of data. If the data
+ ** source is a subroutine call from the SELECT statement, then we need
+ ** to launch the SELECT statement processing.
+ */
+ if( useTempTable ){
+ iBreak = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp2(v, OP_Rewind, srcTab, iBreak);
+ iCont = sqlite3VdbeCurrentAddr(v);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iSelectLoop);
+ sqlite3VdbeResolveLabel(v, iInsertBlock);
+ }
+
+ /* Allocate registers for holding the rowid of the new row,
+ ** the content of the new row, and the assemblied row record.
+ */
+ regRecord = ++pParse->nMem;
+ regRowid = regIns = pParse->nMem+1;
+ pParse->nMem += pTab->nCol + 1;
+ if( IsVirtual(pTab) ){
+ regRowid++;
+ pParse->nMem++;
+ }
+ regData = regRowid+1;
+
+ /* Run the BEFORE and INSTEAD OF triggers, if there are any
+ */
+ endOfLoop = sqlite3VdbeMakeLabel(v);
+ if( triggers_exist & TRIGGER_BEFORE ){
+ int regRowid;
+ int regCols;
+ int regRec;
+
+ /* build the NEW.* reference row. Note that if there is an INTEGER
+ ** PRIMARY KEY into which a NULL is being inserted, that NULL will be
+ ** translated into a unique ID for the row. But on a BEFORE trigger,
+ ** we do not know what the unique ID will be (because the insert has
+ ** not happened yet) so we substitute a rowid of -1
+ */
+ regRowid = sqlite3GetTempReg(pParse);
+ if( keyColumn<0 ){
+ sqlite3VdbeAddOp2(v, OP_Integer, -1, regRowid);
+ }else if( useTempTable ){
+ sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regRowid);
+ }else{
+ int j1;
+ assert( pSelect==0 ); /* Otherwise useTempTable is true */
+ sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regRowid);
+ j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regRowid);
+ sqlite3VdbeAddOp2(v, OP_Integer, -1, regRowid);
+ sqlite3VdbeJumpHere(v, j1);
+ sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid);
+ }
+
+ /* Cannot have triggers on a virtual table. If it were possible,
+ ** this block would have to account for hidden column.
+ */
+ assert(!IsVirtual(pTab));
+
+ /* Create the new column data
+ */
+ regCols = sqlite3GetTempRange(pParse, pTab->nCol);
+ for(i=0; i<pTab->nCol; i++){
+ if( pColumn==0 ){
+ j = i;
+ }else{
+ for(j=0; j<pColumn->nId; j++){
+ if( pColumn->a[j].idx==i ) break;
+ }
+ }
+ if( pColumn && j>=pColumn->nId ){
+ sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regCols+i);
+ }else if( useTempTable ){
+ sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regCols+i);
+ }else{
+ assert( pSelect==0 ); /* Otherwise useTempTable is true */
+ sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i);
+ }
+ }
+ regRec = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regCols, pTab->nCol, regRec);
+
+ /* If this is an INSERT on a view with an INSTEAD OF INSERT trigger,
+ ** do not attempt any conversions before assembling the record.
+ ** If this is a real table, attempt conversions as required by the
+ ** table column affinities.
+ */
+ if( !isView ){
+ sqlite3TableAffinityStr(v, pTab);
+ }
+ sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRec, regRowid);
+ sqlite3ReleaseTempReg(pParse, regRec);
+ sqlite3ReleaseTempReg(pParse, regRowid);
+ sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol);
+
+ /* Fire BEFORE or INSTEAD OF triggers */
+ if( sqlite3CodeRowTrigger(pParse, TK_INSERT, 0, TRIGGER_BEFORE, pTab,
+ newIdx, -1, onError, endOfLoop, 0, 0) ){
+ goto insert_cleanup;
+ }
+ }
+
+ /* Push the record number for the new entry onto the stack. The
+ ** record number is a randomly generate integer created by NewRowid
+ ** except when the table has an INTEGER PRIMARY KEY column, in which
+ ** case the record number is the same as that column.
+ */
+ if( !isView ){
+ if( IsVirtual(pTab) ){
+ /* The row that the VUpdate opcode will delete: none */
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regIns);
+ }
+ if( keyColumn>=0 ){
+ if( useTempTable ){
+ sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regRowid);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+keyColumn, regRowid);
+ }else{
+ VdbeOp *pOp;
+ sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regRowid);
+ pOp = sqlite3VdbeGetOp(v, sqlite3VdbeCurrentAddr(v) - 1);
+ if( pOp && pOp->opcode==OP_Null ){
+ appendFlag = 1;
+ pOp->opcode = OP_NewRowid;
+ pOp->p1 = baseCur;
+ pOp->p2 = regRowid;
+ pOp->p3 = regAutoinc;
+ }
+ }
+ /* If the PRIMARY KEY expression is NULL, then use OP_NewRowid
+ ** to generate a unique primary key value.
+ */
+ if( !appendFlag ){
+ int j1;
+ j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regRowid);
+ sqlite3VdbeAddOp3(v, OP_NewRowid, baseCur, regRowid, regAutoinc);
+ sqlite3VdbeJumpHere(v, j1);
+ sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid);
+ }
+ }else if( IsVirtual(pTab) ){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regRowid);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_NewRowid, baseCur, regRowid, regAutoinc);
+ appendFlag = 1;
+ }
+ autoIncStep(pParse, regAutoinc, regRowid);
+
+ /* Push onto the stack, data for all columns of the new entry, beginning
+ ** with the first column.
+ */
+ nHidden = 0;
+ for(i=0; i<pTab->nCol; i++){
+ int iRegStore = regRowid+1+i;
+ if( i==pTab->iPKey ){
+ /* The value of the INTEGER PRIMARY KEY column is always a NULL.
+ ** Whenever this column is read, the record number will be substituted
+ ** in its place. So will fill this column with a NULL to avoid
+ ** taking up data space with information that will never be used. */
+ sqlite3VdbeAddOp2(v, OP_Null, 0, iRegStore);
+ continue;
+ }
+ if( pColumn==0 ){
+ if( IsHiddenColumn(&pTab->aCol[i]) ){
+ assert( IsVirtual(pTab) );
+ j = -1;
+ nHidden++;
+ }else{
+ j = i - nHidden;
+ }
+ }else{
+ for(j=0; j<pColumn->nId; j++){
+ if( pColumn->a[j].idx==i ) break;
+ }
+ }
+ if( j<0 || nColumn==0 || (pColumn && j>=pColumn->nId) ){
+ sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, iRegStore);
+ }else if( useTempTable ){
+ sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, iRegStore);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+j, iRegStore);
+ }else{
+ sqlite3ExprCode(pParse, pList->a[j].pExpr, iRegStore);
+ }
+ }
+
+ /* Generate code to check constraints and generate index keys and
+ ** do the insertion.
+ */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTab) ){
+ sqlite3VtabMakeWritable(pParse, pTab);
+ sqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns,
+ (const char*)pTab->pVtab, P4_VTAB);
+ }else
+#endif
+ {
+ sqlite3GenerateConstraintChecks(
+ pParse,
+ pTab,
+ baseCur,
+ regIns,
+ aRegIdx,
+ keyColumn>=0,
+ 0,
+ onError,
+ endOfLoop
+ );
+ sqlite3CompleteInsertion(
+ pParse,
+ pTab,
+ baseCur,
+ regIns,
+ aRegIdx,
+ 0,
+ 0,
+ (triggers_exist & TRIGGER_AFTER)!=0 ? newIdx : -1,
+ appendFlag
+ );
+ }
+ }
+
+ /* Update the count of rows that are inserted
+ */
+ if( (db->flags & SQLITE_CountRows)!=0 ){
+ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
+ }
+
+ if( triggers_exist ){
+ /* Code AFTER triggers */
+ if( sqlite3CodeRowTrigger(pParse, TK_INSERT, 0, TRIGGER_AFTER, pTab,
+ newIdx, -1, onError, endOfLoop, 0, 0) ){
+ goto insert_cleanup;
+ }
+ }
+
+ /* The bottom of the loop, if the data source is a SELECT statement
+ */
+ sqlite3VdbeResolveLabel(v, endOfLoop);
+ if( useTempTable ){
+ sqlite3VdbeAddOp2(v, OP_Next, srcTab, iCont);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Close, srcTab, 0);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp2(v, OP_Return, 0, 0);
+ sqlite3VdbeResolveLabel(v, iCleanup);
+ }
+
+ if( !IsVirtual(pTab) && !isView ){
+ /* Close all tables opened */
+ sqlite3VdbeAddOp2(v, OP_Close, baseCur, 0);
+ for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
+ sqlite3VdbeAddOp2(v, OP_Close, idx+baseCur, 0);
+ }
+ }
+
+ /* Update the sqlite_sequence table by storing the content of the
+ ** counter value in memory regAutoinc back into the sqlite_sequence
+ ** table.
+ */
+ autoIncEnd(pParse, iDb, pTab, regAutoinc);
+
+ /*
+ ** Return the number of rows inserted. If this routine is
+ ** generating code because of a call to sqlite3NestedParse(), do not
+ ** invoke the callback function.
+ */
+ if( db->flags & SQLITE_CountRows && pParse->nested==0 && !pParse->trigStack ){
+ sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", P4_STATIC);
+ }
+
+insert_cleanup:
+ sqlite3SrcListDelete(pTabList);
+ sqlite3ExprListDelete(pList);
+ sqlite3SelectDelete(pSelect);
+ sqlite3IdListDelete(pColumn);
+ sqlite3_free(aRegIdx);
+}
+
+/*
+** Generate code to do constraint checks prior to an INSERT or an UPDATE.
+**
+** The input is a range of consecutive registers as follows:
+**
+** 1. The rowid of the row to be updated before the update. This
+** value is omitted unless we are doing an UPDATE that involves a
+** change to the record number or writing to a virtual table.
+**
+** 2. The rowid of the row after the update.
+**
+** 3. The data in the first column of the entry after the update.
+**
+** i. Data from middle columns...
+**
+** N. The data in the last column of the entry after the update.
+**
+** The regRowid parameter is the index of the register containing (2).
+**
+** The old rowid shown as entry (1) above is omitted unless both isUpdate
+** and rowidChng are 1. isUpdate is true for UPDATEs and false for
+** INSERTs. RowidChng means that the new rowid is explicitly specified by
+** the update or insert statement. If rowidChng is false, it means that
+** the rowid is computed automatically in an insert or that the rowid value
+** is not modified by the update.
+**
+** The code generated by this routine store new index entries into
+** registers identified by aRegIdx[]. No index entry is created for
+** indices where aRegIdx[i]==0. The order of indices in aRegIdx[] is
+** the same as the order of indices on the linked list of indices
+** attached to the table.
+**
+** This routine also generates code to check constraints. NOT NULL,
+** CHECK, and UNIQUE constraints are all checked. If a constraint fails,
+** then the appropriate action is performed. There are five possible
+** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE.
+**
+** Constraint type Action What Happens
+** --------------- ---------- ----------------------------------------
+** any ROLLBACK The current transaction is rolled back and
+** sqlite3_exec() returns immediately with a
+** return code of SQLITE_CONSTRAINT.
+**
+** any ABORT Back out changes from the current command
+** only (do not do a complete rollback) then
+** cause sqlite3_exec() to return immediately
+** with SQLITE_CONSTRAINT.
+**
+** any FAIL Sqlite_exec() returns immediately with a
+** return code of SQLITE_CONSTRAINT. The
+** transaction is not rolled back and any
+** prior changes are retained.
+**
+** any IGNORE The record number and data is popped from
+** the stack and there is an immediate jump
+** to label ignoreDest.
+**
+** NOT NULL REPLACE The NULL value is replace by the default
+** value for that column. If the default value
+** is NULL, the action is the same as ABORT.
+**
+** UNIQUE REPLACE The other row that conflicts with the row
+** being inserted is removed.
+**
+** CHECK REPLACE Illegal. The results in an exception.
+**
+** Which action to take is determined by the overrideError parameter.
+** Or if overrideError==OE_Default, then the pParse->onError parameter
+** is used. Or if pParse->onError==OE_Default then the onError value
+** for the constraint is used.
+**
+** The calling routine must open a read/write cursor for pTab with
+** cursor number "baseCur". All indices of pTab must also have open
+** read/write cursors with cursor number baseCur+i for the i-th cursor.
+** Except, if there is no possibility of a REPLACE action then
+** cursors do not need to be open for indices where aRegIdx[i]==0.
+*/
+SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
+ Parse *pParse, /* The parser context */
+ Table *pTab, /* the table into which we are inserting */
+ int baseCur, /* Index of a read/write cursor pointing at pTab */
+ int regRowid, /* Index of the range of input registers */
+ int *aRegIdx, /* Register used by each index. 0 for unused indices */
+ int rowidChng, /* True if the rowid might collide with existing entry */
+ int isUpdate, /* True for UPDATE, False for INSERT */
+ int overrideError, /* Override onError to this if not OE_Default */
+ int ignoreDest /* Jump to this label on an OE_Ignore resolution */
+){
+ int i;
+ Vdbe *v;
+ int nCol;
+ int onError;
+ int j1, j2, j3; /* Addresses of jump instructions */
+ int regData; /* Register containing first data column */
+ int iCur;
+ Index *pIdx;
+ int seenReplace = 0;
+ int hasTwoRowids = (isUpdate && rowidChng);
+
+ v = sqlite3GetVdbe(pParse);
+ assert( v!=0 );
+ assert( pTab->pSelect==0 ); /* This table is not a VIEW */
+ nCol = pTab->nCol;
+ regData = regRowid + 1;
+
+
+ /* Test all NOT NULL constraints.
+ */
+ for(i=0; i<nCol; i++){
+ if( i==pTab->iPKey ){
+ continue;
+ }
+ onError = pTab->aCol[i].notNull;
+ if( onError==OE_None ) continue;
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+ if( onError==OE_Replace && pTab->aCol[i].pDflt==0 ){
+ onError = OE_Abort;
+ }
+ j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regData+i);
+ assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail
+ || onError==OE_Ignore || onError==OE_Replace );
+ switch( onError ){
+ case OE_Rollback:
+ case OE_Abort:
+ case OE_Fail: {
+ char *zMsg = 0;
+ sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_CONSTRAINT, onError);
+ sqlite3SetString(&zMsg, pTab->zName, ".", pTab->aCol[i].zName,
+ " may not be NULL", (char*)0);
+ sqlite3VdbeChangeP4(v, -1, zMsg, P4_DYNAMIC);
+ break;
+ }
+ case OE_Ignore: {
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ case OE_Replace: {
+ sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regData+i);
+ break;
+ }
+ }
+ sqlite3VdbeJumpHere(v, j1);
+ }
+
+ /* Test all CHECK constraints
+ */
+#ifndef SQLITE_OMIT_CHECK
+ if( pTab->pCheck && (pParse->db->flags & SQLITE_IgnoreChecks)==0 ){
+ int allOk = sqlite3VdbeMakeLabel(v);
+ pParse->ckBase = regData;
+ sqlite3ExprIfTrue(pParse, pTab->pCheck, allOk, SQLITE_JUMPIFNULL);
+ onError = overrideError!=OE_Default ? overrideError : OE_Abort;
+ if( onError==OE_Ignore ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_CONSTRAINT, onError);
+ }
+ sqlite3VdbeResolveLabel(v, allOk);
+ }
+#endif /* !defined(SQLITE_OMIT_CHECK) */
+
+ /* If we have an INTEGER PRIMARY KEY, make sure the primary key
+ ** of the new record does not previously exist. Except, if this
+ ** is an UPDATE and the primary key is not changing, that is OK.
+ */
+ if( rowidChng ){
+ onError = pTab->keyConf;
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+
+ if( onError!=OE_Replace || pTab->pIndex ){
+ if( isUpdate ){
+ j2 = sqlite3VdbeAddOp3(v, OP_Eq, regRowid, 0, regRowid-1);
+ }
+ j3 = sqlite3VdbeAddOp3(v, OP_NotExists, baseCur, 0, regRowid);
+ switch( onError ){
+ default: {
+ onError = OE_Abort;
+ /* Fall thru into the next case */
+ }
+ case OE_Rollback:
+ case OE_Abort:
+ case OE_Fail: {
+ sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CONSTRAINT, onError, 0,
+ "PRIMARY KEY must be unique", P4_STATIC);
+ break;
+ }
+ case OE_Replace: {
+ sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0);
+ seenReplace = 1;
+ break;
+ }
+ case OE_Ignore: {
+ assert( seenReplace==0 );
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ }
+ sqlite3VdbeJumpHere(v, j3);
+ if( isUpdate ){
+ sqlite3VdbeJumpHere(v, j2);
+ }
+ }
+ }
+
+ /* Test all UNIQUE constraints by creating entries for each UNIQUE
+ ** index and making sure that duplicate entries do not already exist.
+ ** Add the new records to the indices as we go.
+ */
+ for(iCur=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, iCur++){
+ int regIdx;
+ int regR;
+
+ if( aRegIdx[iCur]==0 ) continue; /* Skip unused indices */
+
+ /* Create a key for accessing the index entry */
+ regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn+1);
+ for(i=0; i<pIdx->nColumn; i++){
+ int idx = pIdx->aiColumn[i];
+ if( idx==pTab->iPKey ){
+ sqlite3VdbeAddOp2(v, OP_SCopy, regRowid, regIdx+i);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_SCopy, regData+idx, regIdx+i);
+ }
+ }
+ sqlite3VdbeAddOp2(v, OP_SCopy, regRowid, regIdx+i);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn+1, aRegIdx[iCur]);
+ sqlite3IndexAffinityStr(v, pIdx);
+ sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn+1);
+ sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn+1);
+
+ /* Find out what action to take in case there is an indexing conflict */
+ onError = pIdx->onError;
+ if( onError==OE_None ) continue; /* pIdx is not a UNIQUE index */
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+ if( seenReplace ){
+ if( onError==OE_Ignore ) onError = OE_Replace;
+ else if( onError==OE_Fail ) onError = OE_Abort;
+ }
+
+
+ /* Check to see if the new index entry will be unique */
+ j2 = sqlite3VdbeAddOp3(v, OP_IsNull, regIdx, 0, pIdx->nColumn);
+ regR = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp2(v, OP_SCopy, regRowid-hasTwoRowids, regR);
+ j3 = sqlite3VdbeAddOp4(v, OP_IsUnique, baseCur+iCur+1, 0,
+ regR, (char*)aRegIdx[iCur],
+ P4_INT32);
+
+ /* Generate code that executes if the new index entry is not unique */
+ assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail
+ || onError==OE_Ignore || onError==OE_Replace );
+ switch( onError ){
+ case OE_Rollback:
+ case OE_Abort:
+ case OE_Fail: {
+ int j, n1, n2;
+ char zErrMsg[200];
+ sqlite3_snprintf(sizeof(zErrMsg), zErrMsg,
+ pIdx->nColumn>1 ? "columns " : "column ");
+ n1 = strlen(zErrMsg);
+ for(j=0; j<pIdx->nColumn && n1<sizeof(zErrMsg)-30; j++){
+ char *zCol = pTab->aCol[pIdx->aiColumn[j]].zName;
+ n2 = strlen(zCol);
+ if( j>0 ){
+ sqlite3_snprintf(sizeof(zErrMsg)-n1, &zErrMsg[n1], ", ");
+ n1 += 2;
+ }
+ if( n1+n2>sizeof(zErrMsg)-30 ){
+ sqlite3_snprintf(sizeof(zErrMsg)-n1, &zErrMsg[n1], "...");
+ n1 += 3;
+ break;
+ }else{
+ sqlite3_snprintf(sizeof(zErrMsg)-n1, &zErrMsg[n1], "%s", zCol);
+ n1 += n2;
+ }
+ }
+ sqlite3_snprintf(sizeof(zErrMsg)-n1, &zErrMsg[n1],
+ pIdx->nColumn>1 ? " are not unique" : " is not unique");
+ sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CONSTRAINT, onError, 0, zErrMsg,0);
+ break;
+ }
+ case OE_Ignore: {
+ assert( seenReplace==0 );
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ case OE_Replace: {
+ sqlite3GenerateRowDelete(pParse, pTab, baseCur, regR, 0);
+ seenReplace = 1;
+ break;
+ }
+ }
+ sqlite3VdbeJumpHere(v, j2);
+ sqlite3VdbeJumpHere(v, j3);
+ sqlite3ReleaseTempReg(pParse, regR);
+ }
+}
+
+/*
+** This routine generates code to finish the INSERT or UPDATE operation
+** that was started by a prior call to sqlite3GenerateConstraintChecks.
+** A consecutive range of registers starting at regRowid contains the
+** rowid and the content to be inserted.
+**
+** The arguments to this routine should be the same as the first six
+** arguments to sqlite3GenerateConstraintChecks.
+*/
+SQLITE_PRIVATE void sqlite3CompleteInsertion(
+ Parse *pParse, /* The parser context */
+ Table *pTab, /* the table into which we are inserting */
+ int baseCur, /* Index of a read/write cursor pointing at pTab */
+ int regRowid, /* Range of content */
+ int *aRegIdx, /* Register used by each index. 0 for unused indices */
+ int rowidChng, /* True if the record number will change */
+ int isUpdate, /* True for UPDATE, False for INSERT */
+ int newIdx, /* Index of NEW table for triggers. -1 if none */
+ int appendBias /* True if this is likely to be an append */
+){
+ int i;
+ Vdbe *v;
+ int nIdx;
+ Index *pIdx;
+ int pik_flags;
+ int regData;
+ int regRec;
+
+ v = sqlite3GetVdbe(pParse);
+ assert( v!=0 );
+ assert( pTab->pSelect==0 ); /* This table is not a VIEW */
+ for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){}
+ for(i=nIdx-1; i>=0; i--){
+ if( aRegIdx[i]==0 ) continue;
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, baseCur+i+1, aRegIdx[i]);
+ }
+ regData = regRowid + 1;
+ regRec = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec);
+ sqlite3TableAffinityStr(v, pTab);
+ sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol);
+#ifndef SQLITE_OMIT_TRIGGER
+ if( newIdx>=0 ){
+ sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRec, regRowid);
+ }
+#endif
+ if( pParse->nested ){
+ pik_flags = 0;
+ }else{
+ pik_flags = OPFLAG_NCHANGE;
+ pik_flags |= (isUpdate?OPFLAG_ISUPDATE:OPFLAG_LASTROWID);
+ }
+ if( appendBias ){
+ pik_flags |= OPFLAG_APPEND;
+ }
+ sqlite3VdbeAddOp3(v, OP_Insert, baseCur, regRec, regRowid);
+ if( !pParse->nested ){
+ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
+ }
+ sqlite3VdbeChangeP5(v, pik_flags);
+}
+
+/*
+** Generate code that will open cursors for a table and for all
+** indices of that table. The "baseCur" parameter is the cursor number used
+** for the table. Indices are opened on subsequent cursors.
+**
+** Return the number of indices on the table.
+*/
+SQLITE_PRIVATE int sqlite3OpenTableAndIndices(
+ Parse *pParse, /* Parsing context */
+ Table *pTab, /* Table to be opened */
+ int baseCur, /* Cursor number assigned to the table */
+ int op /* OP_OpenRead or OP_OpenWrite */
+){
+ int i;
+ int iDb;
+ Index *pIdx;
+ Vdbe *v;
+
+ if( IsVirtual(pTab) ) return 0;
+ iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ v = sqlite3GetVdbe(pParse);
+ assert( v!=0 );
+ sqlite3OpenTable(pParse, baseCur, iDb, pTab, op);
+ for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx);
+ assert( pIdx->pSchema==pTab->pSchema );
+ sqlite3VdbeAddOp4(v, op, i+baseCur, pIdx->tnum, iDb,
+ (char*)pKey, P4_KEYINFO_HANDOFF);
+ VdbeComment((v, "%s", pIdx->zName));
+ }
+ if( pParse->nTab<=baseCur+i ){
+ pParse->nTab = baseCur+i;
+ }
+ return i-1;
+}
+
+
+#ifdef SQLITE_TEST
+/*
+** The following global variable is incremented whenever the
+** transfer optimization is used. This is used for testing
+** purposes only - to make sure the transfer optimization really
+** is happening when it is suppose to.
+*/
+SQLITE_API int sqlite3_xferopt_count;
+#endif /* SQLITE_TEST */
+
+
+#ifndef SQLITE_OMIT_XFER_OPT
+/*
+** Check to collation names to see if they are compatible.
+*/
+static int xferCompatibleCollation(const char *z1, const char *z2){
+ if( z1==0 ){
+ return z2==0;
+ }
+ if( z2==0 ){
+ return 0;
+ }
+ return sqlite3StrICmp(z1, z2)==0;
+}
+
+
+/*
+** Check to see if index pSrc is compatible as a source of data
+** for index pDest in an insert transfer optimization. The rules
+** for a compatible index:
+**
+** * The index is over the same set of columns
+** * The same DESC and ASC markings occurs on all columns
+** * The same onError processing (OE_Abort, OE_Ignore, etc)
+** * The same collating sequence on each column
+*/
+static int xferCompatibleIndex(Index *pDest, Index *pSrc){
+ int i;
+ assert( pDest && pSrc );
+ assert( pDest->pTable!=pSrc->pTable );
+ if( pDest->nColumn!=pSrc->nColumn ){
+ return 0; /* Different number of columns */
+ }
+ if( pDest->onError!=pSrc->onError ){
+ return 0; /* Different conflict resolution strategies */
+ }
+ for(i=0; i<pSrc->nColumn; i++){
+ if( pSrc->aiColumn[i]!=pDest->aiColumn[i] ){
+ return 0; /* Different columns indexed */
+ }
+ if( pSrc->aSortOrder[i]!=pDest->aSortOrder[i] ){
+ return 0; /* Different sort orders */
+ }
+ if( pSrc->azColl[i]!=pDest->azColl[i] ){
+ return 0; /* Different collating sequences */
+ }
+ }
+
+ /* If no test above fails then the indices must be compatible */
+ return 1;
+}
+
+/*
+** Attempt the transfer optimization on INSERTs of the form
+**
+** INSERT INTO tab1 SELECT * FROM tab2;
+**
+** This optimization is only attempted if
+**
+** (1) tab1 and tab2 have identical schemas including all the
+** same indices and constraints
+**
+** (2) tab1 and tab2 are different tables
+**
+** (3) There must be no triggers on tab1
+**
+** (4) The result set of the SELECT statement is "*"
+**
+** (5) The SELECT statement has no WHERE, HAVING, ORDER BY, GROUP BY,
+** or LIMIT clause.
+**
+** (6) The SELECT statement is a simple (not a compound) select that
+** contains only tab2 in its FROM clause
+**
+** This method for implementing the INSERT transfers raw records from
+** tab2 over to tab1. The columns are not decoded. Raw records from
+** the indices of tab2 are transfered to tab1 as well. In so doing,
+** the resulting tab1 has much less fragmentation.
+**
+** This routine returns TRUE if the optimization is attempted. If any
+** of the conditions above fail so that the optimization should not
+** be attempted, then this routine returns FALSE.
+*/
+static int xferOptimization(
+ Parse *pParse, /* Parser context */
+ Table *pDest, /* The table we are inserting into */
+ Select *pSelect, /* A SELECT statement to use as the data source */
+ int onError, /* How to handle constraint errors */
+ int iDbDest /* The database of pDest */
+){
+ ExprList *pEList; /* The result set of the SELECT */
+ Table *pSrc; /* The table in the FROM clause of SELECT */
+ Index *pSrcIdx, *pDestIdx; /* Source and destination indices */
+ struct SrcList_item *pItem; /* An element of pSelect->pSrc */
+ int i; /* Loop counter */
+ int iDbSrc; /* The database of pSrc */
+ int iSrc, iDest; /* Cursors from source and destination */
+ int addr1, addr2; /* Loop addresses */
+ int emptyDestTest; /* Address of test for empty pDest */
+ int emptySrcTest; /* Address of test for empty pSrc */
+ Vdbe *v; /* The VDBE we are building */
+ KeyInfo *pKey; /* Key information for an index */
+ int regAutoinc; /* Memory register used by AUTOINC */
+ int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */
+ int regData, regRowid; /* Registers holding data and rowid */
+
+ if( pSelect==0 ){
+ return 0; /* Must be of the form INSERT INTO ... SELECT ... */
+ }
+ if( pDest->pTrigger ){
+ return 0; /* tab1 must not have triggers */
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( pDest->isVirtual ){
+ return 0; /* tab1 must not be a virtual table */
+ }
+#endif
+ if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+ if( onError!=OE_Abort && onError!=OE_Rollback ){
+ return 0; /* Cannot do OR REPLACE or OR IGNORE or OR FAIL */
+ }
+ assert(pSelect->pSrc); /* allocated even if there is no FROM clause */
+ if( pSelect->pSrc->nSrc!=1 ){
+ return 0; /* FROM clause must have exactly one term */
+ }
+ if( pSelect->pSrc->a[0].pSelect ){
+ return 0; /* FROM clause cannot contain a subquery */
+ }
+ if( pSelect->pWhere ){
+ return 0; /* SELECT may not have a WHERE clause */
+ }
+ if( pSelect->pOrderBy ){
+ return 0; /* SELECT may not have an ORDER BY clause */
+ }
+ /* Do not need to test for a HAVING clause. If HAVING is present but
+ ** there is no ORDER BY, we will get an error. */
+ if( pSelect->pGroupBy ){
+ return 0; /* SELECT may not have a GROUP BY clause */
+ }
+ if( pSelect->pLimit ){
+ return 0; /* SELECT may not have a LIMIT clause */
+ }
+ assert( pSelect->pOffset==0 ); /* Must be so if pLimit==0 */
+ if( pSelect->pPrior ){
+ return 0; /* SELECT may not be a compound query */
+ }
+ if( pSelect->isDistinct ){
+ return 0; /* SELECT may not be DISTINCT */
+ }
+ pEList = pSelect->pEList;
+ assert( pEList!=0 );
+ if( pEList->nExpr!=1 ){
+ return 0; /* The result set must have exactly one column */
+ }
+ assert( pEList->a[0].pExpr );
+ if( pEList->a[0].pExpr->op!=TK_ALL ){
+ return 0; /* The result set must be the special operator "*" */
+ }
+
+ /* At this point we have established that the statement is of the
+ ** correct syntactic form to participate in this optimization. Now
+ ** we have to check the semantics.
+ */
+ pItem = pSelect->pSrc->a;
+ pSrc = sqlite3LocateTable(pParse, 0, pItem->zName, pItem->zDatabase);
+ if( pSrc==0 ){
+ return 0; /* FROM clause does not contain a real table */
+ }
+ if( pSrc==pDest ){
+ return 0; /* tab1 and tab2 may not be the same table */
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( pSrc->isVirtual ){
+ return 0; /* tab2 must not be a virtual table */
+ }
+#endif
+ if( pSrc->pSelect ){
+ return 0; /* tab2 may not be a view */
+ }
+ if( pDest->nCol!=pSrc->nCol ){
+ return 0; /* Number of columns must be the same in tab1 and tab2 */
+ }
+ if( pDest->iPKey!=pSrc->iPKey ){
+ return 0; /* Both tables must have the same INTEGER PRIMARY KEY */
+ }
+ for(i=0; i<pDest->nCol; i++){
+ if( pDest->aCol[i].affinity!=pSrc->aCol[i].affinity ){
+ return 0; /* Affinity must be the same on all columns */
+ }
+ if( !xferCompatibleCollation(pDest->aCol[i].zColl, pSrc->aCol[i].zColl) ){
+ return 0; /* Collating sequence must be the same on all columns */
+ }
+ if( pDest->aCol[i].notNull && !pSrc->aCol[i].notNull ){
+ return 0; /* tab2 must be NOT NULL if tab1 is */
+ }
+ }
+ for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){
+ if( pDestIdx->onError!=OE_None ){
+ destHasUniqueIdx = 1;
+ }
+ for(pSrcIdx=pSrc->pIndex; pSrcIdx; pSrcIdx=pSrcIdx->pNext){
+ if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break;
+ }
+ if( pSrcIdx==0 ){
+ return 0; /* pDestIdx has no corresponding index in pSrc */
+ }
+ }
+#ifndef SQLITE_OMIT_CHECK
+ if( pDest->pCheck && !sqlite3ExprCompare(pSrc->pCheck, pDest->pCheck) ){
+ return 0; /* Tables have different CHECK constraints. Ticket #2252 */
+ }
+#endif
+
+ /* If we get this far, it means either:
+ **
+ ** * We can always do the transfer if the table contains an
+ ** an integer primary key
+ **
+ ** * We can conditionally do the transfer if the destination
+ ** table is empty.
+ */
+#ifdef SQLITE_TEST
+ sqlite3_xferopt_count++;
+#endif
+ iDbSrc = sqlite3SchemaToIndex(pParse->db, pSrc->pSchema);
+ v = sqlite3GetVdbe(pParse);
+ sqlite3CodeVerifySchema(pParse, iDbSrc);
+ iSrc = pParse->nTab++;
+ iDest = pParse->nTab++;
+ regAutoinc = autoIncBegin(pParse, iDbDest, pDest);
+ sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite);
+ if( (pDest->iPKey<0 && pDest->pIndex!=0) || destHasUniqueIdx ){
+ /* If tables do not have an INTEGER PRIMARY KEY and there
+ ** are indices to be copied and the destination is not empty,
+ ** we have to disallow the transfer optimization because the
+ ** the rowids might change which will mess up indexing.
+ **
+ ** Or if the destination has a UNIQUE index and is not empty,
+ ** we also disallow the transfer optimization because we cannot
+ ** insure that all entries in the union of DEST and SRC will be
+ ** unique.
+ */
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0);
+ emptyDestTest = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
+ sqlite3VdbeJumpHere(v, addr1);
+ }else{
+ emptyDestTest = 0;
+ }
+ sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead);
+ emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+ regData = sqlite3GetTempReg(pParse);
+ regRowid = sqlite3GetTempReg(pParse);
+ if( pDest->iPKey>=0 ){
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
+ addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid);
+ sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CONSTRAINT, onError, 0,
+ "PRIMARY KEY must be unique", P4_STATIC);
+ sqlite3VdbeJumpHere(v, addr2);
+ autoIncStep(pParse, regAutoinc, regRowid);
+ }else if( pDest->pIndex==0 ){
+ addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid);
+ }else{
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
+ assert( pDest->autoInc==0 );
+ }
+ sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
+ sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid);
+ sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND);
+ sqlite3VdbeChangeP4(v, -1, pDest->zName, 0);
+ sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1);
+ autoIncEnd(pParse, iDbDest, pDest, regAutoinc);
+ for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){
+ for(pSrcIdx=pSrc->pIndex; pSrcIdx; pSrcIdx=pSrcIdx->pNext){
+ if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break;
+ }
+ assert( pSrcIdx );
+ sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ pKey = sqlite3IndexKeyinfo(pParse, pSrcIdx);
+ sqlite3VdbeAddOp4(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc,
+ (char*)pKey, P4_KEYINFO_HANDOFF);
+ VdbeComment((v, "%s", pSrcIdx->zName));
+ pKey = sqlite3IndexKeyinfo(pParse, pDestIdx);
+ sqlite3VdbeAddOp4(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest,
+ (char*)pKey, P4_KEYINFO_HANDOFF);
+ VdbeComment((v, "%s", pDestIdx->zName));
+ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_RowKey, iSrc, regData);
+ sqlite3VdbeAddOp3(v, OP_IdxInsert, iDest, regData, 1);
+ sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1);
+ sqlite3VdbeJumpHere(v, addr1);
+ }
+ sqlite3VdbeJumpHere(v, emptySrcTest);
+ sqlite3ReleaseTempReg(pParse, regRowid);
+ sqlite3ReleaseTempReg(pParse, regData);
+ sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ if( emptyDestTest ){
+ sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0);
+ sqlite3VdbeJumpHere(v, emptyDestTest);
+ sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ return 0;
+ }else{
+ return 1;
+ }
+}
+#endif /* SQLITE_OMIT_XFER_OPT */
+
+/* Make sure "isView" gets undefined in case this file becomes part of
+** the amalgamation - so that subsequent files do not see isView as a
+** macro. */
+#undef isView
+
+/************** End of insert.c **********************************************/
+/************** Begin file legacy.c ******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Main file for the SQLite library. The routines in this file
+** implement the programmer interface to the library. Routines in
+** other files are for internal use by SQLite and should not be
+** accessed by users of the library.
+**
+** $Id: legacy.c,v 1.24 2008/03/21 18:01:14 drh Exp $
+*/
+
+
+/*
+** Execute SQL code. Return one of the SQLITE_ success/failure
+** codes. Also write an error message into memory obtained from
+** malloc() and make *pzErrMsg point to that message.
+**
+** If the SQL is a query, then for each row in the query result
+** the xCallback() function is called. pArg becomes the first
+** argument to xCallback(). If xCallback=NULL then no callback
+** is invoked, even for queries.
+*/
+SQLITE_API int sqlite3_exec(
+ sqlite3 *db, /* The database on which the SQL executes */
+ const char *zSql, /* The SQL to be executed */
+ sqlite3_callback xCallback, /* Invoke this callback routine */
+ void *pArg, /* First argument to xCallback() */
+ char **pzErrMsg /* Write error messages here */
+){
+ int rc = SQLITE_OK;
+ const char *zLeftover;
+ sqlite3_stmt *pStmt = 0;
+ char **azCols = 0;
+
+ int nRetry = 0;
+ int nCallback;
+
+ if( zSql==0 ) return SQLITE_OK;
+
+ sqlite3_mutex_enter(db->mutex);
+ while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] ){
+ int nCol;
+ char **azVals = 0;
+
+ pStmt = 0;
+ rc = sqlite3_prepare(db, zSql, -1, &pStmt, &zLeftover);
+ assert( rc==SQLITE_OK || pStmt==0 );
+ if( rc!=SQLITE_OK ){
+ continue;
+ }
+ if( !pStmt ){
+ /* this happens for a comment or white-space */
+ zSql = zLeftover;
+ continue;
+ }
+
+ nCallback = 0;
+ nCol = sqlite3_column_count(pStmt);
+
+ while( 1 ){
+ int i;
+ rc = sqlite3_step(pStmt);
+
+ /* Invoke the callback function if required */
+ if( xCallback && (SQLITE_ROW==rc ||
+ (SQLITE_DONE==rc && !nCallback && db->flags&SQLITE_NullCallback)) ){
+ if( 0==nCallback ){
+ if( azCols==0 ){
+ azCols = sqlite3DbMallocZero(db, 2*nCol*sizeof(const char*) + 1);
+ if( azCols==0 ){
+ goto exec_out;
+ }
+ }
+ for(i=0; i<nCol; i++){
+ azCols[i] = (char *)sqlite3_column_name(pStmt, i);
+ if( !azCols[i] ){
+ db->mallocFailed = 1;
+ goto exec_out;
+ }
+ }
+ nCallback++;
+ }
+ if( rc==SQLITE_ROW ){
+ azVals = &azCols[nCol];
+ for(i=0; i<nCol; i++){
+ azVals[i] = (char *)sqlite3_column_text(pStmt, i);
+ if( !azVals[i] && sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
+ db->mallocFailed = 1;
+ goto exec_out;
+ }
+ }
+ }
+ if( xCallback(pArg, nCol, azVals, azCols) ){
+ rc = SQLITE_ABORT;
+ goto exec_out;
+ }
+ }
+
+ if( rc!=SQLITE_ROW ){
+ rc = sqlite3_finalize(pStmt);
+ pStmt = 0;
+ if( rc!=SQLITE_SCHEMA ){
+ nRetry = 0;
+ zSql = zLeftover;
+ while( isspace((unsigned char)zSql[0]) ) zSql++;
+ }
+ break;
+ }
+ }
+
+ sqlite3_free(azCols);
+ azCols = 0;
+ }
+
+exec_out:
+ if( pStmt ) sqlite3_finalize(pStmt);
+ if( azCols ) sqlite3_free(azCols);
+
+ rc = sqlite3ApiExit(db, rc);
+ if( rc!=SQLITE_OK && rc==sqlite3_errcode(db) && pzErrMsg ){
+ int nErrMsg = 1 + strlen(sqlite3_errmsg(db));
+ *pzErrMsg = sqlite3_malloc(nErrMsg);
+ if( *pzErrMsg ){
+ memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg);
+ }
+ }else if( pzErrMsg ){
+ *pzErrMsg = 0;
+ }
+
+ assert( (rc&db->errMask)==rc );
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/************** End of legacy.c **********************************************/
+/************** Begin file loadext.c *****************************************/
+/*
+** 2006 June 7
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to dynamically load extensions into
+** the SQLite library.
+*/
+
+#ifndef SQLITE_CORE
+ #define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */
+#endif
+/************** Include sqlite3ext.h in the middle of loadext.c **************/
+/************** Begin file sqlite3ext.h **************************************/
+/*
+** 2006 June 7
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the SQLite interface for use by
+** shared libraries that want to be imported as extensions into
+** an SQLite instance. Shared libraries that intend to be loaded
+** as extensions by SQLite should #include this file instead of
+** sqlite3.h.
+**
+** @(#) $Id: sqlite3ext.h,v 1.21 2008/03/19 21:45:51 drh Exp $
+*/
+#ifndef _SQLITE3EXT_H_
+#define _SQLITE3EXT_H_
+
+typedef struct sqlite3_api_routines sqlite3_api_routines;
+
+/*
+** The following structure holds pointers to all of the SQLite API
+** routines.
+**
+** WARNING: In order to maintain backwards compatibility, add new
+** interfaces to the end of this structure only. If you insert new
+** interfaces in the middle of this structure, then older different
+** versions of SQLite will not be able to load each others' shared
+** libraries!
+*/
+struct sqlite3_api_routines {
+ void * (*aggregate_context)(sqlite3_context*,int nBytes);
+ int (*aggregate_count)(sqlite3_context*);
+ int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
+ int (*bind_double)(sqlite3_stmt*,int,double);
+ int (*bind_int)(sqlite3_stmt*,int,int);
+ int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
+ int (*bind_null)(sqlite3_stmt*,int);
+ int (*bind_parameter_count)(sqlite3_stmt*);
+ int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
+ const char * (*bind_parameter_name)(sqlite3_stmt*,int);
+ int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
+ int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
+ int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
+ int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
+ int (*busy_timeout)(sqlite3*,int ms);
+ int (*changes)(sqlite3*);
+ int (*close)(sqlite3*);
+ int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const char*));
+ int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const void*));
+ const void * (*column_blob)(sqlite3_stmt*,int iCol);
+ int (*column_bytes)(sqlite3_stmt*,int iCol);
+ int (*column_bytes16)(sqlite3_stmt*,int iCol);
+ int (*column_count)(sqlite3_stmt*pStmt);
+ const char * (*column_database_name)(sqlite3_stmt*,int);
+ const void * (*column_database_name16)(sqlite3_stmt*,int);
+ const char * (*column_decltype)(sqlite3_stmt*,int i);
+ const void * (*column_decltype16)(sqlite3_stmt*,int);
+ double (*column_double)(sqlite3_stmt*,int iCol);
+ int (*column_int)(sqlite3_stmt*,int iCol);
+ sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
+ const char * (*column_name)(sqlite3_stmt*,int);
+ const void * (*column_name16)(sqlite3_stmt*,int);
+ const char * (*column_origin_name)(sqlite3_stmt*,int);
+ const void * (*column_origin_name16)(sqlite3_stmt*,int);
+ const char * (*column_table_name)(sqlite3_stmt*,int);
+ const void * (*column_table_name16)(sqlite3_stmt*,int);
+ const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
+ const void * (*column_text16)(sqlite3_stmt*,int iCol);
+ int (*column_type)(sqlite3_stmt*,int iCol);
+ sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
+ void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
+ int (*complete)(const char*sql);
+ int (*complete16)(const void*sql);
+ int (*create_collation)(sqlite3*,const char*,int,void*,int(*)(void*,int,const void*,int,const void*));
+ int (*create_collation16)(sqlite3*,const char*,int,void*,int(*)(void*,int,const void*,int,const void*));
+ int (*create_function)(sqlite3*,const char*,int,int,void*,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*));
+ int (*create_function16)(sqlite3*,const void*,int,int,void*,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*));
+ int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
+ int (*data_count)(sqlite3_stmt*pStmt);
+ sqlite3 * (*db_handle)(sqlite3_stmt*);
+ int (*declare_vtab)(sqlite3*,const char*);
+ int (*enable_shared_cache)(int);
+ int (*errcode)(sqlite3*db);
+ const char * (*errmsg)(sqlite3*);
+ const void * (*errmsg16)(sqlite3*);
+ int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
+ int (*expired)(sqlite3_stmt*);
+ int (*finalize)(sqlite3_stmt*pStmt);
+ void (*free)(void*);
+ void (*free_table)(char**result);
+ int (*get_autocommit)(sqlite3*);
+ void * (*get_auxdata)(sqlite3_context*,int);
+ int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
+ int (*global_recover)(void);
+ void (*interruptx)(sqlite3*);
+ sqlite_int64 (*last_insert_rowid)(sqlite3*);
+ const char * (*libversion)(void);
+ int (*libversion_number)(void);
+ void *(*malloc)(int);
+ char * (*mprintf)(const char*,...);
+ int (*open)(const char*,sqlite3**);
+ int (*open16)(const void*,sqlite3**);
+ int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
+ int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
+ void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
+ void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
+ void *(*realloc)(void*,int);
+ int (*reset)(sqlite3_stmt*pStmt);
+ void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_double)(sqlite3_context*,double);
+ void (*result_error)(sqlite3_context*,const char*,int);
+ void (*result_error16)(sqlite3_context*,const void*,int);
+ void (*result_int)(sqlite3_context*,int);
+ void (*result_int64)(sqlite3_context*,sqlite_int64);
+ void (*result_null)(sqlite3_context*);
+ void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
+ void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_value)(sqlite3_context*,sqlite3_value*);
+ void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
+ int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,const char*,const char*),void*);
+ void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
+ char * (*snprintf)(int,char*,const char*,...);
+ int (*step)(sqlite3_stmt*);
+ int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,char const**,char const**,int*,int*,int*);
+ void (*thread_cleanup)(void);
+ int (*total_changes)(sqlite3*);
+ void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
+ int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
+ void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,sqlite_int64),void*);
+ void * (*user_data)(sqlite3_context*);
+ const void * (*value_blob)(sqlite3_value*);
+ int (*value_bytes)(sqlite3_value*);
+ int (*value_bytes16)(sqlite3_value*);
+ double (*value_double)(sqlite3_value*);
+ int (*value_int)(sqlite3_value*);
+ sqlite_int64 (*value_int64)(sqlite3_value*);
+ int (*value_numeric_type)(sqlite3_value*);
+ const unsigned char * (*value_text)(sqlite3_value*);
+ const void * (*value_text16)(sqlite3_value*);
+ const void * (*value_text16be)(sqlite3_value*);
+ const void * (*value_text16le)(sqlite3_value*);
+ int (*value_type)(sqlite3_value*);
+ char *(*vmprintf)(const char*,va_list);
+ /* Added ??? */
+ int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
+ /* Added by 3.3.13 */
+ int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
+ int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
+ int (*clear_bindings)(sqlite3_stmt*);
+ /* Added by 3.4.1 */
+ int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,void (*xDestroy)(void *));
+ /* Added by 3.5.0 */
+ int (*bind_zeroblob)(sqlite3_stmt*,int,int);
+ int (*blob_bytes)(sqlite3_blob*);
+ int (*blob_close)(sqlite3_blob*);
+ int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,int,sqlite3_blob**);
+ int (*blob_read)(sqlite3_blob*,void*,int,int);
+ int (*blob_write)(sqlite3_blob*,const void*,int,int);
+ int (*create_collation_v2)(sqlite3*,const char*,int,void*,int(*)(void*,int,const void*,int,const void*),void(*)(void*));
+ int (*file_control)(sqlite3*,const char*,int,void*);
+ sqlite3_int64 (*memory_highwater)(int);
+ sqlite3_int64 (*memory_used)(void);
+ sqlite3_mutex *(*mutex_alloc)(int);
+ void (*mutex_enter)(sqlite3_mutex*);
+ void (*mutex_free)(sqlite3_mutex*);
+ void (*mutex_leave)(sqlite3_mutex*);
+ int (*mutex_try)(sqlite3_mutex*);
+ int (*open_v2)(const char*,sqlite3**,int,const char*);
+ int (*release_memory)(int);
+ void (*result_error_nomem)(sqlite3_context*);
+ void (*result_error_toobig)(sqlite3_context*);
+ int (*sleep)(int);
+ void (*soft_heap_limit)(int);
+ sqlite3_vfs *(*vfs_find)(const char*);
+ int (*vfs_register)(sqlite3_vfs*,int);
+ int (*vfs_unregister)(sqlite3_vfs*);
+ int (*xthreadsafe)(void);
+ void (*result_zeroblob)(sqlite3_context*,int);
+ void (*result_error_code)(sqlite3_context*,int);
+ int (*test_control)(int, ...);
+ void (*randomness)(int,void*);
+ sqlite3 *(*context_db_handle)(sqlite3_context*);
+};
+
+/*
+** The following macros redefine the API routines so that they are
+** redirected throught the global sqlite3_api structure.
+**
+** This header file is also used by the loadext.c source file
+** (part of the main SQLite library - not an extension) so that
+** it can get access to the sqlite3_api_routines structure
+** definition. But the main library does not want to redefine
+** the API. So the redefinition macros are only valid if the
+** SQLITE_CORE macros is undefined.
+*/
+#ifndef SQLITE_CORE
+#define sqlite3_aggregate_context sqlite3_api->aggregate_context
+#define sqlite3_aggregate_count sqlite3_api->aggregate_count
+#define sqlite3_bind_blob sqlite3_api->bind_blob
+#define sqlite3_bind_double sqlite3_api->bind_double
+#define sqlite3_bind_int sqlite3_api->bind_int
+#define sqlite3_bind_int64 sqlite3_api->bind_int64
+#define sqlite3_bind_null sqlite3_api->bind_null
+#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
+#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
+#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
+#define sqlite3_bind_text sqlite3_api->bind_text
+#define sqlite3_bind_text16 sqlite3_api->bind_text16
+#define sqlite3_bind_value sqlite3_api->bind_value
+#define sqlite3_busy_handler sqlite3_api->busy_handler
+#define sqlite3_busy_timeout sqlite3_api->busy_timeout
+#define sqlite3_changes sqlite3_api->changes
+#define sqlite3_close sqlite3_api->close
+#define sqlite3_collation_needed sqlite3_api->collation_needed
+#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
+#define sqlite3_column_blob sqlite3_api->column_blob
+#define sqlite3_column_bytes sqlite3_api->column_bytes
+#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
+#define sqlite3_column_count sqlite3_api->column_count
+#define sqlite3_column_database_name sqlite3_api->column_database_name
+#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
+#define sqlite3_column_decltype sqlite3_api->column_decltype
+#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
+#define sqlite3_column_double sqlite3_api->column_double
+#define sqlite3_column_int sqlite3_api->column_int
+#define sqlite3_column_int64 sqlite3_api->column_int64
+#define sqlite3_column_name sqlite3_api->column_name
+#define sqlite3_column_name16 sqlite3_api->column_name16
+#define sqlite3_column_origin_name sqlite3_api->column_origin_name
+#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
+#define sqlite3_column_table_name sqlite3_api->column_table_name
+#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
+#define sqlite3_column_text sqlite3_api->column_text
+#define sqlite3_column_text16 sqlite3_api->column_text16
+#define sqlite3_column_type sqlite3_api->column_type
+#define sqlite3_column_value sqlite3_api->column_value
+#define sqlite3_commit_hook sqlite3_api->commit_hook
+#define sqlite3_complete sqlite3_api->complete
+#define sqlite3_complete16 sqlite3_api->complete16
+#define sqlite3_create_collation sqlite3_api->create_collation
+#define sqlite3_create_collation16 sqlite3_api->create_collation16
+#define sqlite3_create_function sqlite3_api->create_function
+#define sqlite3_create_function16 sqlite3_api->create_function16
+#define sqlite3_create_module sqlite3_api->create_module
+#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
+#define sqlite3_data_count sqlite3_api->data_count
+#define sqlite3_db_handle sqlite3_api->db_handle
+#define sqlite3_declare_vtab sqlite3_api->declare_vtab
+#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
+#define sqlite3_errcode sqlite3_api->errcode
+#define sqlite3_errmsg sqlite3_api->errmsg
+#define sqlite3_errmsg16 sqlite3_api->errmsg16
+#define sqlite3_exec sqlite3_api->exec
+#define sqlite3_expired sqlite3_api->expired
+#define sqlite3_finalize sqlite3_api->finalize
+#define sqlite3_free sqlite3_api->free
+#define sqlite3_free_table sqlite3_api->free_table
+#define sqlite3_get_autocommit sqlite3_api->get_autocommit
+#define sqlite3_get_auxdata sqlite3_api->get_auxdata
+#define sqlite3_get_table sqlite3_api->get_table
+#define sqlite3_global_recover sqlite3_api->global_recover
+#define sqlite3_interrupt sqlite3_api->interruptx
+#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
+#define sqlite3_libversion sqlite3_api->libversion
+#define sqlite3_libversion_number sqlite3_api->libversion_number
+#define sqlite3_malloc sqlite3_api->malloc
+#define sqlite3_mprintf sqlite3_api->mprintf
+#define sqlite3_open sqlite3_api->open
+#define sqlite3_open16 sqlite3_api->open16
+#define sqlite3_prepare sqlite3_api->prepare
+#define sqlite3_prepare16 sqlite3_api->prepare16
+#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
+#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
+#define sqlite3_profile sqlite3_api->profile
+#define sqlite3_progress_handler sqlite3_api->progress_handler
+#define sqlite3_realloc sqlite3_api->realloc
+#define sqlite3_reset sqlite3_api->reset
+#define sqlite3_result_blob sqlite3_api->result_blob
+#define sqlite3_result_double sqlite3_api->result_double
+#define sqlite3_result_error sqlite3_api->result_error
+#define sqlite3_result_error16 sqlite3_api->result_error16
+#define sqlite3_result_int sqlite3_api->result_int
+#define sqlite3_result_int64 sqlite3_api->result_int64
+#define sqlite3_result_null sqlite3_api->result_null
+#define sqlite3_result_text sqlite3_api->result_text
+#define sqlite3_result_text16 sqlite3_api->result_text16
+#define sqlite3_result_text16be sqlite3_api->result_text16be
+#define sqlite3_result_text16le sqlite3_api->result_text16le
+#define sqlite3_result_value sqlite3_api->result_value
+#define sqlite3_rollback_hook sqlite3_api->rollback_hook
+#define sqlite3_set_authorizer sqlite3_api->set_authorizer
+#define sqlite3_set_auxdata sqlite3_api->set_auxdata
+#define sqlite3_snprintf sqlite3_api->snprintf
+#define sqlite3_step sqlite3_api->step
+#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
+#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
+#define sqlite3_total_changes sqlite3_api->total_changes
+#define sqlite3_trace sqlite3_api->trace
+#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
+#define sqlite3_update_hook sqlite3_api->update_hook
+#define sqlite3_user_data sqlite3_api->user_data
+#define sqlite3_value_blob sqlite3_api->value_blob
+#define sqlite3_value_bytes sqlite3_api->value_bytes
+#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
+#define sqlite3_value_double sqlite3_api->value_double
+#define sqlite3_value_int sqlite3_api->value_int
+#define sqlite3_value_int64 sqlite3_api->value_int64
+#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
+#define sqlite3_value_text sqlite3_api->value_text
+#define sqlite3_value_text16 sqlite3_api->value_text16
+#define sqlite3_value_text16be sqlite3_api->value_text16be
+#define sqlite3_value_text16le sqlite3_api->value_text16le
+#define sqlite3_value_type sqlite3_api->value_type
+#define sqlite3_vmprintf sqlite3_api->vmprintf
+#define sqlite3_overload_function sqlite3_api->overload_function
+#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
+#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
+#define sqlite3_clear_bindings sqlite3_api->clear_bindings
+#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
+#define sqlite3_blob_bytes sqlite3_api->blob_bytes
+#define sqlite3_blob_close sqlite3_api->blob_close
+#define sqlite3_blob_open sqlite3_api->blob_open
+#define sqlite3_blob_read sqlite3_api->blob_read
+#define sqlite3_blob_write sqlite3_api->blob_write
+#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
+#define sqlite3_file_control sqlite3_api->file_control
+#define sqlite3_memory_highwater sqlite3_api->memory_highwater
+#define sqlite3_memory_used sqlite3_api->memory_used
+#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
+#define sqlite3_mutex_enter sqlite3_api->mutex_enter
+#define sqlite3_mutex_free sqlite3_api->mutex_free
+#define sqlite3_mutex_leave sqlite3_api->mutex_leave
+#define sqlite3_mutex_try sqlite3_api->mutex_try
+#define sqlite3_open_v2 sqlite3_api->open_v2
+#define sqlite3_release_memory sqlite3_api->release_memory
+#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
+#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
+#define sqlite3_sleep sqlite3_api->sleep
+#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
+#define sqlite3_vfs_find sqlite3_api->vfs_find
+#define sqlite3_vfs_register sqlite3_api->vfs_register
+#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
+#define sqlite3_threadsafe sqlite3_api->xthreadsafe
+#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
+#define sqlite3_result_error_code sqlite3_api->result_error_code
+#define sqlite3_test_control sqlite3_api->test_control
+#define sqlite3_randomness sqlite3_api->randomness
+#define sqlite3_context_db_handle sqlite3_api->context_db_handle
+#endif /* SQLITE_CORE */
+
+#define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api;
+#define SQLITE_EXTENSION_INIT2(v) sqlite3_api = v;
+
+#endif /* _SQLITE3EXT_H_ */
+
+/************** End of sqlite3ext.h ******************************************/
+/************** Continuing where we left off in loadext.c ********************/
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+
+/*
+** Some API routines are omitted when various features are
+** excluded from a build of SQLite. Substitute a NULL pointer
+** for any missing APIs.
+*/
+#ifndef SQLITE_ENABLE_COLUMN_METADATA
+# define sqlite3_column_database_name 0
+# define sqlite3_column_database_name16 0
+# define sqlite3_column_table_name 0
+# define sqlite3_column_table_name16 0
+# define sqlite3_column_origin_name 0
+# define sqlite3_column_origin_name16 0
+# define sqlite3_table_column_metadata 0
+#endif
+
+#ifdef SQLITE_OMIT_AUTHORIZATION
+# define sqlite3_set_authorizer 0
+#endif
+
+#ifdef SQLITE_OMIT_UTF16
+# define sqlite3_bind_text16 0
+# define sqlite3_collation_needed16 0
+# define sqlite3_column_decltype16 0
+# define sqlite3_column_name16 0
+# define sqlite3_column_text16 0
+# define sqlite3_complete16 0
+# define sqlite3_create_collation16 0
+# define sqlite3_create_function16 0
+# define sqlite3_errmsg16 0
+# define sqlite3_open16 0
+# define sqlite3_prepare16 0
+# define sqlite3_prepare16_v2 0
+# define sqlite3_result_error16 0
+# define sqlite3_result_text16 0
+# define sqlite3_result_text16be 0
+# define sqlite3_result_text16le 0
+# define sqlite3_value_text16 0
+# define sqlite3_value_text16be 0
+# define sqlite3_value_text16le 0
+# define sqlite3_column_database_name16 0
+# define sqlite3_column_table_name16 0
+# define sqlite3_column_origin_name16 0
+#endif
+
+#ifdef SQLITE_OMIT_COMPLETE
+# define sqlite3_complete 0
+# define sqlite3_complete16 0
+#endif
+
+#ifdef SQLITE_OMIT_PROGRESS_CALLBACK
+# define sqlite3_progress_handler 0
+#endif
+
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+# define sqlite3_create_module 0
+# define sqlite3_create_module_v2 0
+# define sqlite3_declare_vtab 0
+#endif
+
+#ifdef SQLITE_OMIT_SHARED_CACHE
+# define sqlite3_enable_shared_cache 0
+#endif
+
+#ifdef SQLITE_OMIT_TRACE
+# define sqlite3_profile 0
+# define sqlite3_trace 0
+#endif
+
+#ifdef SQLITE_OMIT_GET_TABLE
+# define sqlite3_free_table 0
+# define sqlite3_get_table 0
+#endif
+
+#ifdef SQLITE_OMIT_INCRBLOB
+#define sqlite3_bind_zeroblob 0
+#define sqlite3_blob_bytes 0
+#define sqlite3_blob_close 0
+#define sqlite3_blob_open 0
+#define sqlite3_blob_read 0
+#define sqlite3_blob_write 0
+#endif
+
+/*
+** The following structure contains pointers to all SQLite API routines.
+** A pointer to this structure is passed into extensions when they are
+** loaded so that the extension can make calls back into the SQLite
+** library.
+**
+** When adding new APIs, add them to the bottom of this structure
+** in order to preserve backwards compatibility.
+**
+** Extensions that use newer APIs should first call the
+** sqlite3_libversion_number() to make sure that the API they
+** intend to use is supported by the library. Extensions should
+** also check to make sure that the pointer to the function is
+** not NULL before calling it.
+*/
+static const sqlite3_api_routines sqlite3Apis = {
+ sqlite3_aggregate_context,
+ sqlite3_aggregate_count,
+ sqlite3_bind_blob,
+ sqlite3_bind_double,
+ sqlite3_bind_int,
+ sqlite3_bind_int64,
+ sqlite3_bind_null,
+ sqlite3_bind_parameter_count,
+ sqlite3_bind_parameter_index,
+ sqlite3_bind_parameter_name,
+ sqlite3_bind_text,
+ sqlite3_bind_text16,
+ sqlite3_bind_value,
+ sqlite3_busy_handler,
+ sqlite3_busy_timeout,
+ sqlite3_changes,
+ sqlite3_close,
+ sqlite3_collation_needed,
+ sqlite3_collation_needed16,
+ sqlite3_column_blob,
+ sqlite3_column_bytes,
+ sqlite3_column_bytes16,
+ sqlite3_column_count,
+ sqlite3_column_database_name,
+ sqlite3_column_database_name16,
+ sqlite3_column_decltype,
+ sqlite3_column_decltype16,
+ sqlite3_column_double,
+ sqlite3_column_int,
+ sqlite3_column_int64,
+ sqlite3_column_name,
+ sqlite3_column_name16,
+ sqlite3_column_origin_name,
+ sqlite3_column_origin_name16,
+ sqlite3_column_table_name,
+ sqlite3_column_table_name16,
+ sqlite3_column_text,
+ sqlite3_column_text16,
+ sqlite3_column_type,
+ sqlite3_column_value,
+ sqlite3_commit_hook,
+ sqlite3_complete,
+ sqlite3_complete16,
+ sqlite3_create_collation,
+ sqlite3_create_collation16,
+ sqlite3_create_function,
+ sqlite3_create_function16,
+ sqlite3_create_module,
+ sqlite3_data_count,
+ sqlite3_db_handle,
+ sqlite3_declare_vtab,
+ sqlite3_enable_shared_cache,
+ sqlite3_errcode,
+ sqlite3_errmsg,
+ sqlite3_errmsg16,
+ sqlite3_exec,
+ sqlite3_expired,
+ sqlite3_finalize,
+ sqlite3_free,
+ sqlite3_free_table,
+ sqlite3_get_autocommit,
+ sqlite3_get_auxdata,
+ sqlite3_get_table,
+ 0, /* Was sqlite3_global_recover(), but that function is deprecated */
+ sqlite3_interrupt,
+ sqlite3_last_insert_rowid,
+ sqlite3_libversion,
+ sqlite3_libversion_number,
+ sqlite3_malloc,
+ sqlite3_mprintf,
+ sqlite3_open,
+ sqlite3_open16,
+ sqlite3_prepare,
+ sqlite3_prepare16,
+ sqlite3_profile,
+ sqlite3_progress_handler,
+ sqlite3_realloc,
+ sqlite3_reset,
+ sqlite3_result_blob,
+ sqlite3_result_double,
+ sqlite3_result_error,
+ sqlite3_result_error16,
+ sqlite3_result_int,
+ sqlite3_result_int64,
+ sqlite3_result_null,
+ sqlite3_result_text,
+ sqlite3_result_text16,
+ sqlite3_result_text16be,
+ sqlite3_result_text16le,
+ sqlite3_result_value,
+ sqlite3_rollback_hook,
+ sqlite3_set_authorizer,
+ sqlite3_set_auxdata,
+ sqlite3_snprintf,
+ sqlite3_step,
+ sqlite3_table_column_metadata,
+ sqlite3_thread_cleanup,
+ sqlite3_total_changes,
+ sqlite3_trace,
+ sqlite3_transfer_bindings,
+ sqlite3_update_hook,
+ sqlite3_user_data,
+ sqlite3_value_blob,
+ sqlite3_value_bytes,
+ sqlite3_value_bytes16,
+ sqlite3_value_double,
+ sqlite3_value_int,
+ sqlite3_value_int64,
+ sqlite3_value_numeric_type,
+ sqlite3_value_text,
+ sqlite3_value_text16,
+ sqlite3_value_text16be,
+ sqlite3_value_text16le,
+ sqlite3_value_type,
+ sqlite3_vmprintf,
+ /*
+ ** The original API set ends here. All extensions can call any
+ ** of the APIs above provided that the pointer is not NULL. But
+ ** before calling APIs that follow, extension should check the
+ ** sqlite3_libversion_number() to make sure they are dealing with
+ ** a library that is new enough to support that API.
+ *************************************************************************
+ */
+ sqlite3_overload_function,
+
+ /*
+ ** Added after 3.3.13
+ */
+ sqlite3_prepare_v2,
+ sqlite3_prepare16_v2,
+ sqlite3_clear_bindings,
+
+ /*
+ ** Added for 3.4.1
+ */
+ sqlite3_create_module_v2,
+
+ /*
+ ** Added for 3.5.0
+ */
+ sqlite3_bind_zeroblob,
+ sqlite3_blob_bytes,
+ sqlite3_blob_close,
+ sqlite3_blob_open,
+ sqlite3_blob_read,
+ sqlite3_blob_write,
+ sqlite3_create_collation_v2,
+ sqlite3_file_control,
+ sqlite3_memory_highwater,
+ sqlite3_memory_used,
+#ifdef SQLITE_MUTEX_NOOP
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+#else
+ sqlite3_mutex_alloc,
+ sqlite3_mutex_enter,
+ sqlite3_mutex_free,
+ sqlite3_mutex_leave,
+ sqlite3_mutex_try,
+#endif
+ sqlite3_open_v2,
+ sqlite3_release_memory,
+ sqlite3_result_error_nomem,
+ sqlite3_result_error_toobig,
+ sqlite3_sleep,
+ sqlite3_soft_heap_limit,
+ sqlite3_vfs_find,
+ sqlite3_vfs_register,
+ sqlite3_vfs_unregister,
+
+ /*
+ ** Added for 3.5.8
+ */
+ sqlite3_threadsafe,
+ sqlite3_result_zeroblob,
+ sqlite3_result_error_code,
+ sqlite3_test_control,
+ sqlite3_randomness,
+ sqlite3_context_db_handle,
+};
+
+/*
+** Attempt to load an SQLite extension library contained in the file
+** zFile. The entry point is zProc. zProc may be 0 in which case a
+** default entry point name (sqlite3_extension_init) is used. Use
+** of the default name is recommended.
+**
+** Return SQLITE_OK on success and SQLITE_ERROR if something goes wrong.
+**
+** If an error occurs and pzErrMsg is not 0, then fill *pzErrMsg with
+** error message text. The calling function should free this memory
+** by calling sqlite3_free().
+*/
+static int sqlite3LoadExtension(
+ sqlite3 *db, /* Load the extension into this database connection */
+ const char *zFile, /* Name of the shared library containing extension */
+ const char *zProc, /* Entry point. Use "sqlite3_extension_init" if 0 */
+ char **pzErrMsg /* Put error message here if not 0 */
+){
+ sqlite3_vfs *pVfs = db->pVfs;
+ void *handle;
+ int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*);
+ char *zErrmsg = 0;
+ void **aHandle;
+
+ /* Ticket #1863. To avoid a creating security problems for older
+ ** applications that relink against newer versions of SQLite, the
+ ** ability to run load_extension is turned off by default. One
+ ** must call sqlite3_enable_load_extension() to turn on extension
+ ** loading. Otherwise you get the following error.
+ */
+ if( (db->flags & SQLITE_LoadExtension)==0 ){
+ if( pzErrMsg ){
+ *pzErrMsg = sqlite3_mprintf("not authorized");
+ }
+ return SQLITE_ERROR;
+ }
+
+ if( zProc==0 ){
+ zProc = "sqlite3_extension_init";
+ }
+
+ handle = sqlite3OsDlOpen(pVfs, zFile);
+ if( handle==0 ){
+ if( pzErrMsg ){
+ char zErr[256];
+ zErr[sizeof(zErr)-1] = '\0';
+ sqlite3_snprintf(sizeof(zErr)-1, zErr,
+ "unable to open shared library [%s]", zFile);
+ sqlite3OsDlError(pVfs, sizeof(zErr)-1, zErr);
+ *pzErrMsg = sqlite3DbStrDup(db, zErr);
+ }
+ return SQLITE_ERROR;
+ }
+ xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*))
+ sqlite3OsDlSym(pVfs, handle, zProc);
+ if( xInit==0 ){
+ if( pzErrMsg ){
+ char zErr[256];
+ zErr[sizeof(zErr)-1] = '\0';
+ sqlite3_snprintf(sizeof(zErr)-1, zErr,
+ "no entry point [%s] in shared library [%s]", zProc,zFile);
+ sqlite3OsDlError(pVfs, sizeof(zErr)-1, zErr);
+ *pzErrMsg = sqlite3DbStrDup(db, zErr);
+ sqlite3OsDlClose(pVfs, handle);
+ }
+ return SQLITE_ERROR;
+ }else if( xInit(db, &zErrmsg, &sqlite3Apis) ){
+ if( pzErrMsg ){
+ *pzErrMsg = sqlite3_mprintf("error during initialization: %s", zErrmsg);
+ }
+ sqlite3_free(zErrmsg);
+ sqlite3OsDlClose(pVfs, handle);
+ return SQLITE_ERROR;
+ }
+
+ /* Append the new shared library handle to the db->aExtension array. */
+ db->nExtension++;
+ aHandle = sqlite3DbMallocZero(db, sizeof(handle)*db->nExtension);
+ if( aHandle==0 ){
+ return SQLITE_NOMEM;
+ }
+ if( db->nExtension>0 ){
+ memcpy(aHandle, db->aExtension, sizeof(handle)*(db->nExtension-1));
+ }
+ sqlite3_free(db->aExtension);
+ db->aExtension = aHandle;
+
+ db->aExtension[db->nExtension-1] = handle;
+ return SQLITE_OK;
+}
+SQLITE_API int sqlite3_load_extension(
+ sqlite3 *db, /* Load the extension into this database connection */
+ const char *zFile, /* Name of the shared library containing extension */
+ const char *zProc, /* Entry point. Use "sqlite3_extension_init" if 0 */
+ char **pzErrMsg /* Put error message here if not 0 */
+){
+ int rc;
+ sqlite3_mutex_enter(db->mutex);
+ rc = sqlite3LoadExtension(db, zFile, zProc, pzErrMsg);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** Call this routine when the database connection is closing in order
+** to clean up loaded extensions
+*/
+SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3 *db){
+ int i;
+ assert( sqlite3_mutex_held(db->mutex) );
+ for(i=0; i<db->nExtension; i++){
+ sqlite3OsDlClose(db->pVfs, db->aExtension[i]);
+ }
+ sqlite3_free(db->aExtension);
+}
+
+/*
+** Enable or disable extension loading. Extension loading is disabled by
+** default so as not to open security holes in older applications.
+*/
+SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){
+ sqlite3_mutex_enter(db->mutex);
+ if( onoff ){
+ db->flags |= SQLITE_LoadExtension;
+ }else{
+ db->flags &= ~SQLITE_LoadExtension;
+ }
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_OK;
+}
+
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+
+/*
+** The auto-extension code added regardless of whether or not extension
+** loading is supported. We need a dummy sqlite3Apis pointer for that
+** code if regular extension loading is not available. This is that
+** dummy pointer.
+*/
+#ifdef SQLITE_OMIT_LOAD_EXTENSION
+static const sqlite3_api_routines sqlite3Apis = { 0 };
+#endif
+
+
+/*
+** The following object holds the list of automatically loaded
+** extensions.
+**
+** This list is shared across threads. The SQLITE_MUTEX_STATIC_MASTER
+** mutex must be held while accessing this list.
+*/
+static struct {
+ int nExt; /* Number of entries in aExt[] */
+ void **aExt; /* Pointers to the extension init functions */
+} autoext = { 0, 0 };
+
+
+/*
+** Register a statically linked extension that is automatically
+** loaded by every new database connection.
+*/
+SQLITE_API int sqlite3_auto_extension(void *xInit){
+ int i;
+ int rc = SQLITE_OK;
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+#endif
+ sqlite3_mutex_enter(mutex);
+ for(i=0; i<autoext.nExt; i++){
+ if( autoext.aExt[i]==xInit ) break;
+ }
+ if( i==autoext.nExt ){
+ int nByte = (autoext.nExt+1)*sizeof(autoext.aExt[0]);
+ void **aNew;
+ aNew = sqlite3_realloc(autoext.aExt, nByte);
+ if( aNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ autoext.aExt = aNew;
+ autoext.aExt[autoext.nExt] = xInit;
+ autoext.nExt++;
+ }
+ }
+ sqlite3_mutex_leave(mutex);
+ assert( (rc&0xff)==rc );
+ return rc;
+}
+
+/*
+** Reset the automatic extension loading mechanism.
+*/
+SQLITE_API void sqlite3_reset_auto_extension(void){
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+#endif
+ sqlite3_mutex_enter(mutex);
+ sqlite3_free(autoext.aExt);
+ autoext.aExt = 0;
+ autoext.nExt = 0;
+ sqlite3_mutex_leave(mutex);
+}
+
+/*
+** Load all automatic extensions.
+*/
+SQLITE_PRIVATE int sqlite3AutoLoadExtensions(sqlite3 *db){
+ int i;
+ int go = 1;
+ int rc = SQLITE_OK;
+ int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*);
+
+ if( autoext.nExt==0 ){
+ /* Common case: early out without every having to acquire a mutex */
+ return SQLITE_OK;
+ }
+ for(i=0; go; i++){
+ char *zErrmsg = 0;
+#ifndef SQLITE_MUTEX_NOOP
+ sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+#endif
+ sqlite3_mutex_enter(mutex);
+ if( i>=autoext.nExt ){
+ xInit = 0;
+ go = 0;
+ }else{
+ xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*))
+ autoext.aExt[i];
+ }
+ sqlite3_mutex_leave(mutex);
+ if( xInit && xInit(db, &zErrmsg, &sqlite3Apis) ){
+ sqlite3Error(db, SQLITE_ERROR,
+ "automatic extension loading failed: %s", zErrmsg);
+ go = 0;
+ rc = SQLITE_ERROR;
+ sqlite3_free(zErrmsg);
+ }
+ }
+ return rc;
+}
+
+/************** End of loadext.c *********************************************/
+/************** Begin file pragma.c ******************************************/
+/*
+** 2003 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the PRAGMA command.
+**
+** $Id: pragma.c,v 1.176 2008/04/17 20:59:38 drh Exp $
+*/
+
+/* Ignore this whole file if pragmas are disabled
+*/
+#if !defined(SQLITE_OMIT_PRAGMA) && !defined(SQLITE_OMIT_PARSER)
+
+/*
+** Interpret the given string as a safety level. Return 0 for OFF,
+** 1 for ON or NORMAL and 2 for FULL. Return 1 for an empty or
+** unrecognized string argument.
+**
+** Note that the values returned are one less that the values that
+** should be passed into sqlite3BtreeSetSafetyLevel(). The is done
+** to support legacy SQL code. The safety level used to be boolean
+** and older scripts may have used numbers 0 for OFF and 1 for ON.
+*/
+static int getSafetyLevel(const char *z){
+ /* 123456789 123456789 */
+ static const char zText[] = "onoffalseyestruefull";
+ static const u8 iOffset[] = {0, 1, 2, 4, 9, 12, 16};
+ static const u8 iLength[] = {2, 2, 3, 5, 3, 4, 4};
+ static const u8 iValue[] = {1, 0, 0, 0, 1, 1, 2};
+ int i, n;
+ if( isdigit(*z) ){
+ return atoi(z);
+ }
+ n = strlen(z);
+ for(i=0; i<sizeof(iLength); i++){
+ if( iLength[i]==n && sqlite3StrNICmp(&zText[iOffset[i]],z,n)==0 ){
+ return iValue[i];
+ }
+ }
+ return 1;
+}
+
+/*
+** Interpret the given string as a boolean value.
+*/
+static int getBoolean(const char *z){
+ return getSafetyLevel(z)&1;
+}
+
+/*
+** Interpret the given string as a locking mode value.
+*/
+static int getLockingMode(const char *z){
+ if( z ){
+ if( 0==sqlite3StrICmp(z, "exclusive") ) return PAGER_LOCKINGMODE_EXCLUSIVE;
+ if( 0==sqlite3StrICmp(z, "normal") ) return PAGER_LOCKINGMODE_NORMAL;
+ }
+ return PAGER_LOCKINGMODE_QUERY;
+}
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+/*
+** Interpret the given string as an auto-vacuum mode value.
+**
+** The following strings, "none", "full" and "incremental" are
+** acceptable, as are their numeric equivalents: 0, 1 and 2 respectively.
+*/
+static int getAutoVacuum(const char *z){
+ int i;
+ if( 0==sqlite3StrICmp(z, "none") ) return BTREE_AUTOVACUUM_NONE;
+ if( 0==sqlite3StrICmp(z, "full") ) return BTREE_AUTOVACUUM_FULL;
+ if( 0==sqlite3StrICmp(z, "incremental") ) return BTREE_AUTOVACUUM_INCR;
+ i = atoi(z);
+ return ((i>=0&&i<=2)?i:0);
+}
+#endif /* ifndef SQLITE_OMIT_AUTOVACUUM */
+
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+/*
+** Interpret the given string as a temp db location. Return 1 for file
+** backed temporary databases, 2 for the Red-Black tree in memory database
+** and 0 to use the compile-time default.
+*/
+static int getTempStore(const char *z){
+ if( z[0]>='0' && z[0]<='2' ){
+ return z[0] - '0';
+ }else if( sqlite3StrICmp(z, "file")==0 ){
+ return 1;
+ }else if( sqlite3StrICmp(z, "memory")==0 ){
+ return 2;
+ }else{
+ return 0;
+ }
+}
+#endif /* SQLITE_PAGER_PRAGMAS */
+
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+/*
+** Invalidate temp storage, either when the temp storage is changed
+** from default, or when 'file' and the temp_store_directory has changed
+*/
+static int invalidateTempStorage(Parse *pParse){
+ sqlite3 *db = pParse->db;
+ if( db->aDb[1].pBt!=0 ){
+ if( !db->autoCommit ){
+ sqlite3ErrorMsg(pParse, "temporary storage cannot be changed "
+ "from within a transaction");
+ return SQLITE_ERROR;
+ }
+ sqlite3BtreeClose(db->aDb[1].pBt);
+ db->aDb[1].pBt = 0;
+ sqlite3ResetInternalSchema(db, 0);
+ }
+ return SQLITE_OK;
+}
+#endif /* SQLITE_PAGER_PRAGMAS */
+
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+/*
+** If the TEMP database is open, close it and mark the database schema
+** as needing reloading. This must be done when using the TEMP_STORE
+** or DEFAULT_TEMP_STORE pragmas.
+*/
+static int changeTempStorage(Parse *pParse, const char *zStorageType){
+ int ts = getTempStore(zStorageType);
+ sqlite3 *db = pParse->db;
+ if( db->temp_store==ts ) return SQLITE_OK;
+ if( invalidateTempStorage( pParse ) != SQLITE_OK ){
+ return SQLITE_ERROR;
+ }
+ db->temp_store = ts;
+ return SQLITE_OK;
+}
+#endif /* SQLITE_PAGER_PRAGMAS */
+
+/*
+** Generate code to return a single integer value.
+*/
+static void returnSingleInt(Parse *pParse, const char *zLabel, int value){
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ int mem = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Integer, value, mem);
+ if( pParse->explain==0 ){
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLabel, P4_STATIC);
+ }
+ sqlite3VdbeAddOp2(v, OP_ResultRow, mem, 1);
+}
+
+#ifndef SQLITE_OMIT_FLAG_PRAGMAS
+/*
+** Check to see if zRight and zLeft refer to a pragma that queries
+** or changes one of the flags in db->flags. Return 1 if so and 0 if not.
+** Also, implement the pragma.
+*/
+static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){
+ static const struct sPragmaType {
+ const char *zName; /* Name of the pragma */
+ int mask; /* Mask for the db->flags value */
+ } aPragma[] = {
+ { "full_column_names", SQLITE_FullColNames },
+ { "short_column_names", SQLITE_ShortColNames },
+ { "count_changes", SQLITE_CountRows },
+ { "empty_result_callbacks", SQLITE_NullCallback },
+ { "legacy_file_format", SQLITE_LegacyFileFmt },
+ { "fullfsync", SQLITE_FullFSync },
+#ifdef SQLITE_DEBUG
+ { "sql_trace", SQLITE_SqlTrace },
+ { "vdbe_listing", SQLITE_VdbeListing },
+ { "vdbe_trace", SQLITE_VdbeTrace },
+#endif
+#ifndef SQLITE_OMIT_CHECK
+ { "ignore_check_constraints", SQLITE_IgnoreChecks },
+#endif
+ /* The following is VERY experimental */
+ { "writable_schema", SQLITE_WriteSchema|SQLITE_RecoveryMode },
+ { "omit_readlock", SQLITE_NoReadlock },
+
+ /* TODO: Maybe it shouldn't be possible to change the ReadUncommitted
+ ** flag if there are any active statements. */
+ { "read_uncommitted", SQLITE_ReadUncommitted },
+ };
+ int i;
+ const struct sPragmaType *p;
+ for(i=0, p=aPragma; i<sizeof(aPragma)/sizeof(aPragma[0]); i++, p++){
+ if( sqlite3StrICmp(zLeft, p->zName)==0 ){
+ sqlite3 *db = pParse->db;
+ Vdbe *v;
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ if( zRight==0 ){
+ returnSingleInt(pParse, p->zName, (db->flags & p->mask)!=0 );
+ }else{
+ if( getBoolean(zRight) ){
+ db->flags |= p->mask;
+ }else{
+ db->flags &= ~p->mask;
+ }
+
+ /* Many of the flag-pragmas modify the code generated by the SQL
+ ** compiler (eg. count_changes). So add an opcode to expire all
+ ** compiled SQL statements after modifying a pragma value.
+ */
+ sqlite3VdbeAddOp2(v, OP_Expire, 0, 0);
+ }
+ }
+
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif /* SQLITE_OMIT_FLAG_PRAGMAS */
+
+/*
+** Process a pragma statement.
+**
+** Pragmas are of this form:
+**
+** PRAGMA [database.]id [= value]
+**
+** The identifier might also be a string. The value is a string, and
+** identifier, or a number. If minusFlag is true, then the value is
+** a number that was preceded by a minus sign.
+**
+** If the left side is "database.id" then pId1 is the database name
+** and pId2 is the id. If the left side is just "id" then pId1 is the
+** id and pId2 is any empty string.
+*/
+SQLITE_PRIVATE void sqlite3Pragma(
+ Parse *pParse,
+ Token *pId1, /* First part of [database.]id field */
+ Token *pId2, /* Second part of [database.]id field, or NULL */
+ Token *pValue, /* Token for <value>, or NULL */
+ int minusFlag /* True if a '-' sign preceded <value> */
+){
+ char *zLeft = 0; /* Nul-terminated UTF-8 string <id> */
+ char *zRight = 0; /* Nul-terminated UTF-8 string <value>, or NULL */
+ const char *zDb = 0; /* The database name */
+ Token *pId; /* Pointer to <id> token */
+ int iDb; /* Database index for <database> */
+ sqlite3 *db = pParse->db;
+ Db *pDb;
+ Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(db);
+ if( v==0 ) return;
+ pParse->nMem = 2;
+
+ /* Interpret the [database.] part of the pragma statement. iDb is the
+ ** index of the database this pragma is being applied to in db.aDb[]. */
+ iDb = sqlite3TwoPartName(pParse, pId1, pId2, &pId);
+ if( iDb<0 ) return;
+ pDb = &db->aDb[iDb];
+
+ /* If the temp database has been explicitly named as part of the
+ ** pragma, make sure it is open.
+ */
+ if( iDb==1 && sqlite3OpenTempDatabase(pParse) ){
+ return;
+ }
+
+ zLeft = sqlite3NameFromToken(db, pId);
+ if( !zLeft ) return;
+ if( minusFlag ){
+ zRight = sqlite3MPrintf(db, "-%T", pValue);
+ }else{
+ zRight = sqlite3NameFromToken(db, pValue);
+ }
+
+ zDb = ((iDb>0)?pDb->zName:0);
+ if( sqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){
+ goto pragma_out;
+ }
+
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+ /*
+ ** PRAGMA [database.]default_cache_size
+ ** PRAGMA [database.]default_cache_size=N
+ **
+ ** The first form reports the current persistent setting for the
+ ** page cache size. The value returned is the maximum number of
+ ** pages in the page cache. The second form sets both the current
+ ** page cache size value and the persistent page cache size value
+ ** stored in the database file.
+ **
+ ** The default cache size is stored in meta-value 2 of page 1 of the
+ ** database file. The cache size is actually the absolute value of
+ ** this memory location. The sign of meta-value 2 determines the
+ ** synchronous setting. A negative value means synchronous is off
+ ** and a positive value means synchronous is on.
+ */
+ if( sqlite3StrICmp(zLeft,"default_cache_size")==0 ){
+ static const VdbeOpList getCacheSize[] = {
+ { OP_ReadCookie, 0, 1, 2}, /* 0 */
+ { OP_IfPos, 1, 6, 0},
+ { OP_Integer, 0, 2, 0},
+ { OP_Subtract, 1, 2, 1},
+ { OP_IfPos, 1, 6, 0},
+ { OP_Integer, 0, 1, 0}, /* 5 */
+ { OP_ResultRow, 1, 1, 0},
+ };
+ int addr;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ sqlite3VdbeUsesBtree(v, iDb);
+ if( !zRight ){
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "cache_size", P4_STATIC);
+ pParse->nMem += 2;
+ addr = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize);
+ sqlite3VdbeChangeP1(v, addr, iDb);
+ sqlite3VdbeChangeP1(v, addr+5, SQLITE_DEFAULT_CACHE_SIZE);
+ }else{
+ int size = atoi(zRight);
+ if( size<0 ) size = -size;
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3VdbeAddOp2(v, OP_Integer, size, 1);
+ sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, 2, 2);
+ addr = sqlite3VdbeAddOp2(v, OP_IfPos, 2, 0);
+ sqlite3VdbeAddOp2(v, OP_Integer, -size, 1);
+ sqlite3VdbeJumpHere(v, addr);
+ sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, 2, 1);
+ pDb->pSchema->cache_size = size;
+ sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
+ }
+ }else
+
+ /*
+ ** PRAGMA [database.]page_size
+ ** PRAGMA [database.]page_size=N
+ **
+ ** The first form reports the current setting for the
+ ** database page size in bytes. The second form sets the
+ ** database page size value. The value can only be set if
+ ** the database has not yet been created.
+ */
+ if( sqlite3StrICmp(zLeft,"page_size")==0 ){
+ Btree *pBt = pDb->pBt;
+ if( !zRight ){
+ int size = pBt ? sqlite3BtreeGetPageSize(pBt) : 0;
+ returnSingleInt(pParse, "page_size", size);
+ }else{
+ /* Malloc may fail when setting the page-size, as there is an internal
+ ** buffer that the pager module resizes using sqlite3_realloc().
+ */
+ db->nextPagesize = atoi(zRight);
+ if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, -1) ){
+ db->mallocFailed = 1;
+ }
+ }
+ }else
+
+ /*
+ ** PRAGMA [database.]max_page_count
+ ** PRAGMA [database.]max_page_count=N
+ **
+ ** The first form reports the current setting for the
+ ** maximum number of pages in the database file. The
+ ** second form attempts to change this setting. Both
+ ** forms return the current setting.
+ */
+ if( sqlite3StrICmp(zLeft,"max_page_count")==0 ){
+ Btree *pBt = pDb->pBt;
+ int newMax = 0;
+ if( zRight ){
+ newMax = atoi(zRight);
+ }
+ if( pBt ){
+ newMax = sqlite3BtreeMaxPageCount(pBt, newMax);
+ }
+ returnSingleInt(pParse, "max_page_count", newMax);
+ }else
+
+ /*
+ ** PRAGMA [database.]locking_mode
+ ** PRAGMA [database.]locking_mode = (normal|exclusive)
+ */
+ if( sqlite3StrICmp(zLeft,"locking_mode")==0 ){
+ const char *zRet = "normal";
+ int eMode = getLockingMode(zRight);
+
+ if( pId2->n==0 && eMode==PAGER_LOCKINGMODE_QUERY ){
+ /* Simple "PRAGMA locking_mode;" statement. This is a query for
+ ** the current default locking mode (which may be different to
+ ** the locking-mode of the main database).
+ */
+ eMode = db->dfltLockMode;
+ }else{
+ Pager *pPager;
+ if( pId2->n==0 ){
+ /* This indicates that no database name was specified as part
+ ** of the PRAGMA command. In this case the locking-mode must be
+ ** set on all attached databases, as well as the main db file.
+ **
+ ** Also, the sqlite3.dfltLockMode variable is set so that
+ ** any subsequently attached databases also use the specified
+ ** locking mode.
+ */
+ int ii;
+ assert(pDb==&db->aDb[0]);
+ for(ii=2; ii<db->nDb; ii++){
+ pPager = sqlite3BtreePager(db->aDb[ii].pBt);
+ sqlite3PagerLockingMode(pPager, eMode);
+ }
+ db->dfltLockMode = eMode;
+ }
+ pPager = sqlite3BtreePager(pDb->pBt);
+ eMode = sqlite3PagerLockingMode(pPager, eMode);
+ }
+
+ assert(eMode==PAGER_LOCKINGMODE_NORMAL||eMode==PAGER_LOCKINGMODE_EXCLUSIVE);
+ if( eMode==PAGER_LOCKINGMODE_EXCLUSIVE ){
+ zRet = "exclusive";
+ }
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "locking_mode", P4_STATIC);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, zRet, 0);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ }else
+
+ /*
+ ** PRAGMA [database.]journal_mode
+ ** PRAGMA [database.]journal_mode = (delete|persist|off)
+ */
+ if( sqlite3StrICmp(zLeft,"journal_mode")==0 ){
+ int eMode;
+ static const char *azModeName[] = {"delete", "persist", "off"};
+
+ if( zRight==0 ){
+ eMode = PAGER_JOURNALMODE_QUERY;
+ }else{
+ int n = strlen(zRight);
+ eMode = 2;
+ while( eMode>=0 && sqlite3StrNICmp(zRight, azModeName[eMode], n)!=0 ){
+ eMode--;
+ }
+ }
+ if( pId2->n==0 && eMode==PAGER_JOURNALMODE_QUERY ){
+ /* Simple "PRAGMA persistent_journal;" statement. This is a query for
+ ** the current default journal mode (which may be different to
+ ** the journal-mode of the main database).
+ */
+ eMode = db->dfltJournalMode;
+ }else{
+ Pager *pPager;
+ if( pId2->n==0 ){
+ /* This indicates that no database name was specified as part
+ ** of the PRAGMA command. In this case the journal-mode must be
+ ** set on all attached databases, as well as the main db file.
+ **
+ ** Also, the sqlite3.dfltJournalMode variable is set so that
+ ** any subsequently attached databases also use the specified
+ ** journal mode.
+ */
+ int ii;
+ assert(pDb==&db->aDb[0]);
+ for(ii=1; ii<db->nDb; ii++){
+ if( db->aDb[ii].pBt ){
+ pPager = sqlite3BtreePager(db->aDb[ii].pBt);
+ sqlite3PagerJournalMode(pPager, eMode);
+ }
+ }
+ db->dfltJournalMode = eMode;
+ }
+ pPager = sqlite3BtreePager(pDb->pBt);
+ eMode = sqlite3PagerJournalMode(pPager, eMode);
+ }
+ assert( eMode==PAGER_JOURNALMODE_DELETE
+ || eMode==PAGER_JOURNALMODE_PERSIST
+ || eMode==PAGER_JOURNALMODE_OFF );
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "journal_mode", P4_STATIC);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0,
+ azModeName[eMode], P4_STATIC);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ }else
+#endif /* SQLITE_OMIT_PAGER_PRAGMAS */
+
+ /*
+ ** PRAGMA [database.]auto_vacuum
+ ** PRAGMA [database.]auto_vacuum=N
+ **
+ ** Get or set the (boolean) value of the database 'auto-vacuum' parameter.
+ */
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( sqlite3StrICmp(zLeft,"auto_vacuum")==0 ){
+ Btree *pBt = pDb->pBt;
+ if( sqlite3ReadSchema(pParse) ){
+ goto pragma_out;
+ }
+ if( !zRight ){
+ int auto_vacuum =
+ pBt ? sqlite3BtreeGetAutoVacuum(pBt) : SQLITE_DEFAULT_AUTOVACUUM;
+ returnSingleInt(pParse, "auto_vacuum", auto_vacuum);
+ }else{
+ int eAuto = getAutoVacuum(zRight);
+ db->nextAutovac = eAuto;
+ if( eAuto>=0 ){
+ /* Call SetAutoVacuum() to set initialize the internal auto and
+ ** incr-vacuum flags. This is required in case this connection
+ ** creates the database file. It is important that it is created
+ ** as an auto-vacuum capable db.
+ */
+ int rc = sqlite3BtreeSetAutoVacuum(pBt, eAuto);
+ if( rc==SQLITE_OK && (eAuto==1 || eAuto==2) ){
+ /* When setting the auto_vacuum mode to either "full" or
+ ** "incremental", write the value of meta[6] in the database
+ ** file. Before writing to meta[6], check that meta[3] indicates
+ ** that this really is an auto-vacuum capable database.
+ */
+ static const VdbeOpList setMeta6[] = {
+ { OP_Transaction, 0, 1, 0}, /* 0 */
+ { OP_ReadCookie, 0, 1, 3}, /* 1 */
+ { OP_If, 1, 0, 0}, /* 2 */
+ { OP_Halt, SQLITE_OK, OE_Abort, 0}, /* 3 */
+ { OP_Integer, 0, 1, 0}, /* 4 */
+ { OP_SetCookie, 0, 6, 1}, /* 5 */
+ };
+ int iAddr;
+ iAddr = sqlite3VdbeAddOpList(v, ArraySize(setMeta6), setMeta6);
+ sqlite3VdbeChangeP1(v, iAddr, iDb);
+ sqlite3VdbeChangeP1(v, iAddr+1, iDb);
+ sqlite3VdbeChangeP2(v, iAddr+2, iAddr+4);
+ sqlite3VdbeChangeP1(v, iAddr+4, eAuto-1);
+ sqlite3VdbeChangeP1(v, iAddr+5, iDb);
+ sqlite3VdbeUsesBtree(v, iDb);
+ }
+ }
+ }
+ }else
+#endif
+
+ /*
+ ** PRAGMA [database.]incremental_vacuum(N)
+ **
+ ** Do N steps of incremental vacuuming on a database.
+ */
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( sqlite3StrICmp(zLeft,"incremental_vacuum")==0 ){
+ int iLimit, addr;
+ if( sqlite3ReadSchema(pParse) ){
+ goto pragma_out;
+ }
+ if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){
+ iLimit = 0x7fffffff;
+ }
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3VdbeAddOp2(v, OP_Integer, iLimit, 1);
+ addr = sqlite3VdbeAddOp1(v, OP_IncrVacuum, iDb);
+ sqlite3VdbeAddOp1(v, OP_ResultRow, 1);
+ sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1);
+ sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr);
+ sqlite3VdbeJumpHere(v, addr);
+ }else
+#endif
+
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+ /*
+ ** PRAGMA [database.]cache_size
+ ** PRAGMA [database.]cache_size=N
+ **
+ ** The first form reports the current local setting for the
+ ** page cache size. The local setting can be different from
+ ** the persistent cache size value that is stored in the database
+ ** file itself. The value returned is the maximum number of
+ ** pages in the page cache. The second form sets the local
+ ** page cache size value. It does not change the persistent
+ ** cache size stored on the disk so the cache size will revert
+ ** to its default value when the database is closed and reopened.
+ ** N should be a positive integer.
+ */
+ if( sqlite3StrICmp(zLeft,"cache_size")==0 ){
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ if( !zRight ){
+ returnSingleInt(pParse, "cache_size", pDb->pSchema->cache_size);
+ }else{
+ int size = atoi(zRight);
+ if( size<0 ) size = -size;
+ pDb->pSchema->cache_size = size;
+ sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
+ }
+ }else
+
+ /*
+ ** PRAGMA temp_store
+ ** PRAGMA temp_store = "default"|"memory"|"file"
+ **
+ ** Return or set the local value of the temp_store flag. Changing
+ ** the local value does not make changes to the disk file and the default
+ ** value will be restored the next time the database is opened.
+ **
+ ** Note that it is possible for the library compile-time options to
+ ** override this setting
+ */
+ if( sqlite3StrICmp(zLeft, "temp_store")==0 ){
+ if( !zRight ){
+ returnSingleInt(pParse, "temp_store", db->temp_store);
+ }else{
+ changeTempStorage(pParse, zRight);
+ }
+ }else
+
+ /*
+ ** PRAGMA temp_store_directory
+ ** PRAGMA temp_store_directory = ""|"directory_name"
+ **
+ ** Return or set the local value of the temp_store_directory flag. Changing
+ ** the value sets a specific directory to be used for temporary files.
+ ** Setting to a null string reverts to the default temporary directory search.
+ ** If temporary directory is changed, then invalidateTempStorage.
+ **
+ */
+ if( sqlite3StrICmp(zLeft, "temp_store_directory")==0 ){
+ if( !zRight ){
+ if( sqlite3_temp_directory ){
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME,
+ "temp_store_directory", P4_STATIC);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, sqlite3_temp_directory, 0);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ }
+ }else{
+ if( zRight[0]
+ && sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE)==0
+ ){
+ sqlite3ErrorMsg(pParse, "not a writable directory");
+ goto pragma_out;
+ }
+ if( TEMP_STORE==0
+ || (TEMP_STORE==1 && db->temp_store<=1)
+ || (TEMP_STORE==2 && db->temp_store==1)
+ ){
+ invalidateTempStorage(pParse);
+ }
+ sqlite3_free(sqlite3_temp_directory);
+ if( zRight[0] ){
+ sqlite3_temp_directory = zRight;
+ zRight = 0;
+ }else{
+ sqlite3_temp_directory = 0;
+ }
+ }
+ }else
+
+ /*
+ ** PRAGMA [database.]synchronous
+ ** PRAGMA [database.]synchronous=OFF|ON|NORMAL|FULL
+ **
+ ** Return or set the local value of the synchronous flag. Changing
+ ** the local value does not make changes to the disk file and the
+ ** default value will be restored the next time the database is
+ ** opened.
+ */
+ if( sqlite3StrICmp(zLeft,"synchronous")==0 ){
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ if( !zRight ){
+ returnSingleInt(pParse, "synchronous", pDb->safety_level-1);
+ }else{
+ if( !db->autoCommit ){
+ sqlite3ErrorMsg(pParse,
+ "Safety level may not be changed inside a transaction");
+ }else{
+ pDb->safety_level = getSafetyLevel(zRight)+1;
+ }
+ }
+ }else
+#endif /* SQLITE_OMIT_PAGER_PRAGMAS */
+
+#ifndef SQLITE_OMIT_FLAG_PRAGMAS
+ if( flagPragma(pParse, zLeft, zRight) ){
+ /* The flagPragma() subroutine also generates any necessary code
+ ** there is nothing more to do here */
+ }else
+#endif /* SQLITE_OMIT_FLAG_PRAGMAS */
+
+#ifndef SQLITE_OMIT_SCHEMA_PRAGMAS
+ /*
+ ** PRAGMA table_info(<table>)
+ **
+ ** Return a single row for each column of the named table. The columns of
+ ** the returned data set are:
+ **
+ ** cid: Column id (numbered from left to right, starting at 0)
+ ** name: Column name
+ ** type: Column declaration type.
+ ** notnull: True if 'NOT NULL' is part of column declaration
+ ** dflt_value: The default value for the column, if any.
+ */
+ if( sqlite3StrICmp(zLeft, "table_info")==0 && zRight ){
+ Table *pTab;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ pTab = sqlite3FindTable(db, zRight, zDb);
+ if( pTab ){
+ int i;
+ int nHidden = 0;
+ Column *pCol;
+ sqlite3VdbeSetNumCols(v, 6);
+ pParse->nMem = 6;
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "cid", P4_STATIC);
+ sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", P4_STATIC);
+ sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "type", P4_STATIC);
+ sqlite3VdbeSetColName(v, 3, COLNAME_NAME, "notnull", P4_STATIC);
+ sqlite3VdbeSetColName(v, 4, COLNAME_NAME, "dflt_value", P4_STATIC);
+ sqlite3VdbeSetColName(v, 5, COLNAME_NAME, "pk", P4_STATIC);
+ sqlite3ViewGetColumnNames(pParse, pTab);
+ for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){
+ const Token *pDflt;
+ if( IsHiddenColumn(pCol) ){
+ nHidden++;
+ continue;
+ }
+ sqlite3VdbeAddOp2(v, OP_Integer, i-nHidden, 1);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, pCol->zName, 0);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
+ pCol->zType ? pCol->zType : "", 0);
+ sqlite3VdbeAddOp2(v, OP_Integer, pCol->notNull, 4);
+ if( pCol->pDflt && (pDflt = &pCol->pDflt->span)->z ){
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 5, 0, (char*)pDflt->z, pDflt->n);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Null, 0, 5);
+ }
+ sqlite3VdbeAddOp2(v, OP_Integer, pCol->isPrimKey, 6);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
+ }
+ }
+ }else
+
+ if( sqlite3StrICmp(zLeft, "index_info")==0 && zRight ){
+ Index *pIdx;
+ Table *pTab;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ pIdx = sqlite3FindIndex(db, zRight, zDb);
+ if( pIdx ){
+ int i;
+ pTab = pIdx->pTable;
+ sqlite3VdbeSetNumCols(v, 3);
+ pParse->nMem = 3;
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seqno", P4_STATIC);
+ sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "cid", P4_STATIC);
+ sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "name", P4_STATIC);
+ for(i=0; i<pIdx->nColumn; i++){
+ int cnum = pIdx->aiColumn[i];
+ sqlite3VdbeAddOp2(v, OP_Integer, i, 1);
+ sqlite3VdbeAddOp2(v, OP_Integer, cnum, 2);
+ assert( pTab->nCol>cnum );
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, pTab->aCol[cnum].zName, 0);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3);
+ }
+ }
+ }else
+
+ if( sqlite3StrICmp(zLeft, "index_list")==0 && zRight ){
+ Index *pIdx;
+ Table *pTab;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ pTab = sqlite3FindTable(db, zRight, zDb);
+ if( pTab ){
+ v = sqlite3GetVdbe(pParse);
+ pIdx = pTab->pIndex;
+ if( pIdx ){
+ int i = 0;
+ sqlite3VdbeSetNumCols(v, 3);
+ pParse->nMem = 3;
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seq", P4_STATIC);
+ sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", P4_STATIC);
+ sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "unique", P4_STATIC);
+ while(pIdx){
+ sqlite3VdbeAddOp2(v, OP_Integer, i, 1);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, pIdx->zName, 0);
+ sqlite3VdbeAddOp2(v, OP_Integer, pIdx->onError!=OE_None, 3);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3);
+ ++i;
+ pIdx = pIdx->pNext;
+ }
+ }
+ }
+ }else
+
+ if( sqlite3StrICmp(zLeft, "database_list")==0 ){
+ int i;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ sqlite3VdbeSetNumCols(v, 3);
+ pParse->nMem = 3;
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seq", P4_STATIC);
+ sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", P4_STATIC);
+ sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "file", P4_STATIC);
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt==0 ) continue;
+ assert( db->aDb[i].zName!=0 );
+ sqlite3VdbeAddOp2(v, OP_Integer, i, 1);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, db->aDb[i].zName, 0);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
+ sqlite3BtreeGetFilename(db->aDb[i].pBt), 0);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3);
+ }
+ }else
+
+ if( sqlite3StrICmp(zLeft, "collation_list")==0 ){
+ int i = 0;
+ HashElem *p;
+ sqlite3VdbeSetNumCols(v, 2);
+ pParse->nMem = 2;
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seq", P4_STATIC);
+ sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", P4_STATIC);
+ for(p=sqliteHashFirst(&db->aCollSeq); p; p=sqliteHashNext(p)){
+ CollSeq *pColl = (CollSeq *)sqliteHashData(p);
+ sqlite3VdbeAddOp2(v, OP_Integer, i++, 1);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, pColl->zName, 0);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2);
+ }
+ }else
+#endif /* SQLITE_OMIT_SCHEMA_PRAGMAS */
+
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ if( sqlite3StrICmp(zLeft, "foreign_key_list")==0 && zRight ){
+ FKey *pFK;
+ Table *pTab;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ pTab = sqlite3FindTable(db, zRight, zDb);
+ if( pTab ){
+ v = sqlite3GetVdbe(pParse);
+ pFK = pTab->pFKey;
+ if( pFK ){
+ int i = 0;
+ sqlite3VdbeSetNumCols(v, 5);
+ pParse->nMem = 5;
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "id", P4_STATIC);
+ sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "seq", P4_STATIC);
+ sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "table", P4_STATIC);
+ sqlite3VdbeSetColName(v, 3, COLNAME_NAME, "from", P4_STATIC);
+ sqlite3VdbeSetColName(v, 4, COLNAME_NAME, "to", P4_STATIC);
+ while(pFK){
+ int j;
+ for(j=0; j<pFK->nCol; j++){
+ char *zCol = pFK->aCol[j].zCol;
+ sqlite3VdbeAddOp2(v, OP_Integer, i, 1);
+ sqlite3VdbeAddOp2(v, OP_Integer, j, 2);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, pFK->zTo, 0);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0,
+ pTab->aCol[pFK->aCol[j].iFrom].zName, 0);
+ sqlite3VdbeAddOp4(v, zCol ? OP_String8 : OP_Null, 0, 5, 0, zCol, 0);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5);
+ }
+ ++i;
+ pFK = pFK->pNextFrom;
+ }
+ }
+ }
+ }else
+#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
+
+#ifndef NDEBUG
+ if( sqlite3StrICmp(zLeft, "parser_trace")==0 ){
+ if( zRight ){
+ if( getBoolean(zRight) ){
+ sqlite3ParserTrace(stderr, "parser: ");
+ }else{
+ sqlite3ParserTrace(0, 0);
+ }
+ }
+ }else
+#endif
+
+ /* Reinstall the LIKE and GLOB functions. The variant of LIKE
+ ** used will be case sensitive or not depending on the RHS.
+ */
+ if( sqlite3StrICmp(zLeft, "case_sensitive_like")==0 ){
+ if( zRight ){
+ sqlite3RegisterLikeFunctions(db, getBoolean(zRight));
+ }
+ }else
+
+#ifndef SQLITE_INTEGRITY_CHECK_ERROR_MAX
+# define SQLITE_INTEGRITY_CHECK_ERROR_MAX 100
+#endif
+
+#ifndef SQLITE_OMIT_INTEGRITY_CHECK
+ /* Pragma "quick_check" is an experimental reduced version of
+ ** integrity_check designed to detect most database corruption
+ ** without most of the overhead of a full integrity-check.
+ */
+ if( sqlite3StrICmp(zLeft, "integrity_check")==0
+ || sqlite3StrICmp(zLeft, "quick_check")==0
+ ){
+ int i, j, addr, mxErr;
+
+ /* Code that appears at the end of the integrity check. If no error
+ ** messages have been generated, output OK. Otherwise output the
+ ** error message
+ */
+ static const VdbeOpList endCode[] = {
+ { OP_AddImm, 1, 0, 0}, /* 0 */
+ { OP_IfNeg, 1, 0, 0}, /* 1 */
+ { OP_String8, 0, 3, 0}, /* 2 */
+ { OP_ResultRow, 3, 1, 0},
+ };
+
+ int isQuick = (zLeft[0]=='q');
+
+ /* Initialize the VDBE program */
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ pParse->nMem = 6;
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "integrity_check", P4_STATIC);
+
+ /* Set the maximum error count */
+ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX;
+ if( zRight ){
+ mxErr = atoi(zRight);
+ if( mxErr<=0 ){
+ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX;
+ }
+ }
+ sqlite3VdbeAddOp2(v, OP_Integer, mxErr, 1); /* reg[1] holds errors left */
+
+ /* Do an integrity check on each database file */
+ for(i=0; i<db->nDb; i++){
+ HashElem *x;
+ Hash *pTbls;
+ int cnt = 0;
+
+ if( OMIT_TEMPDB && i==1 ) continue;
+
+ sqlite3CodeVerifySchema(pParse, i);
+ addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Halt if out of errors */
+ sqlite3VdbeAddOp2(v, OP_Halt, 0, 0);
+ sqlite3VdbeJumpHere(v, addr);
+
+ /* Do an integrity check of the B-Tree
+ **
+ ** Begin by filling registers 2, 3, ... with the root pages numbers
+ ** for all tables and indices in the database.
+ */
+ pTbls = &db->aDb[i].pSchema->tblHash;
+ for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
+ Table *pTab = sqliteHashData(x);
+ Index *pIdx;
+ sqlite3VdbeAddOp2(v, OP_Integer, pTab->tnum, 2+cnt);
+ cnt++;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ sqlite3VdbeAddOp2(v, OP_Integer, pIdx->tnum, 2+cnt);
+ cnt++;
+ }
+ }
+ if( cnt==0 ) continue;
+
+ /* Make sure sufficient number of registers have been allocated */
+ if( pParse->nMem < cnt+4 ){
+ pParse->nMem = cnt+4;
+ }
+
+ /* Do the b-tree integrity checks */
+ sqlite3VdbeAddOp3(v, OP_IntegrityCk, 2, cnt, 1);
+ sqlite3VdbeChangeP5(v, i);
+ addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
+ sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zName),
+ P4_DYNAMIC);
+ sqlite3VdbeAddOp2(v, OP_Move, 2, 4);
+ sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 2);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 2, 1);
+ sqlite3VdbeJumpHere(v, addr);
+
+ /* Make sure all the indices are constructed correctly.
+ */
+ for(x=sqliteHashFirst(pTbls); x && !isQuick; x=sqliteHashNext(x)){
+ Table *pTab = sqliteHashData(x);
+ Index *pIdx;
+ int loopTop;
+
+ if( pTab->pIndex==0 ) continue;
+ addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Stop if out of errors */
+ sqlite3VdbeAddOp2(v, OP_Halt, 0, 0);
+ sqlite3VdbeJumpHere(v, addr);
+ sqlite3OpenTableAndIndices(pParse, pTab, 1, OP_OpenRead);
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, 2); /* reg(2) will count entries */
+ loopTop = sqlite3VdbeAddOp2(v, OP_Rewind, 1, 0);
+ sqlite3VdbeAddOp2(v, OP_AddImm, 2, 1); /* increment entry count */
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ int jmp2;
+ static const VdbeOpList idxErr[] = {
+ { OP_AddImm, 1, -1, 0},
+ { OP_String8, 0, 3, 0}, /* 1 */
+ { OP_Rowid, 1, 4, 0},
+ { OP_String8, 0, 5, 0}, /* 3 */
+ { OP_String8, 0, 6, 0}, /* 4 */
+ { OP_Concat, 4, 3, 3},
+ { OP_Concat, 5, 3, 3},
+ { OP_Concat, 6, 3, 3},
+ { OP_ResultRow, 3, 1, 0},
+ { OP_IfPos, 1, 0, 0}, /* 9 */
+ { OP_Halt, 0, 0, 0},
+ };
+ sqlite3GenerateIndexKey(pParse, pIdx, 1, 3, 1);
+ jmp2 = sqlite3VdbeAddOp3(v, OP_Found, j+2, 0, 3);
+ addr = sqlite3VdbeAddOpList(v, ArraySize(idxErr), idxErr);
+ sqlite3VdbeChangeP4(v, addr+1, "rowid ", P4_STATIC);
+ sqlite3VdbeChangeP4(v, addr+3, " missing from index ", P4_STATIC);
+ sqlite3VdbeChangeP4(v, addr+4, pIdx->zName, P4_STATIC);
+ sqlite3VdbeJumpHere(v, addr+9);
+ sqlite3VdbeJumpHere(v, jmp2);
+ }
+ sqlite3VdbeAddOp2(v, OP_Next, 1, loopTop+1);
+ sqlite3VdbeJumpHere(v, loopTop);
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ static const VdbeOpList cntIdx[] = {
+ { OP_Integer, 0, 3, 0},
+ { OP_Rewind, 0, 0, 0}, /* 1 */
+ { OP_AddImm, 3, 1, 0},
+ { OP_Next, 0, 0, 0}, /* 3 */
+ { OP_Eq, 2, 0, 3}, /* 4 */
+ { OP_AddImm, 1, -1, 0},
+ { OP_String8, 0, 2, 0}, /* 6 */
+ { OP_String8, 0, 3, 0}, /* 7 */
+ { OP_Concat, 3, 2, 2},
+ { OP_ResultRow, 2, 1, 0},
+ };
+ if( pIdx->tnum==0 ) continue;
+ addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1);
+ sqlite3VdbeAddOp2(v, OP_Halt, 0, 0);
+ sqlite3VdbeJumpHere(v, addr);
+ addr = sqlite3VdbeAddOpList(v, ArraySize(cntIdx), cntIdx);
+ sqlite3VdbeChangeP1(v, addr+1, j+2);
+ sqlite3VdbeChangeP2(v, addr+1, addr+4);
+ sqlite3VdbeChangeP1(v, addr+3, j+2);
+ sqlite3VdbeChangeP2(v, addr+3, addr+2);
+ sqlite3VdbeJumpHere(v, addr+4);
+ sqlite3VdbeChangeP4(v, addr+6,
+ "wrong # of entries in index ", P4_STATIC);
+ sqlite3VdbeChangeP4(v, addr+7, pIdx->zName, P4_STATIC);
+ }
+ }
+ }
+ addr = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode);
+ sqlite3VdbeChangeP2(v, addr, -mxErr);
+ sqlite3VdbeJumpHere(v, addr+1);
+ sqlite3VdbeChangeP4(v, addr+2, "ok", P4_STATIC);
+ }else
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+#ifndef SQLITE_OMIT_UTF16
+ /*
+ ** PRAGMA encoding
+ ** PRAGMA encoding = "utf-8"|"utf-16"|"utf-16le"|"utf-16be"
+ **
+ ** In its first form, this pragma returns the encoding of the main
+ ** database. If the database is not initialized, it is initialized now.
+ **
+ ** The second form of this pragma is a no-op if the main database file
+ ** has not already been initialized. In this case it sets the default
+ ** encoding that will be used for the main database file if a new file
+ ** is created. If an existing main database file is opened, then the
+ ** default text encoding for the existing database is used.
+ **
+ ** In all cases new databases created using the ATTACH command are
+ ** created to use the same default text encoding as the main database. If
+ ** the main database has not been initialized and/or created when ATTACH
+ ** is executed, this is done before the ATTACH operation.
+ **
+ ** In the second form this pragma sets the text encoding to be used in
+ ** new database files created using this database handle. It is only
+ ** useful if invoked immediately after the main database i
+ */
+ if( sqlite3StrICmp(zLeft, "encoding")==0 ){
+ static const struct EncName {
+ char *zName;
+ u8 enc;
+ } encnames[] = {
+ { "UTF-8", SQLITE_UTF8 },
+ { "UTF8", SQLITE_UTF8 },
+ { "UTF-16le", SQLITE_UTF16LE },
+ { "UTF16le", SQLITE_UTF16LE },
+ { "UTF-16be", SQLITE_UTF16BE },
+ { "UTF16be", SQLITE_UTF16BE },
+ { "UTF-16", 0 }, /* SQLITE_UTF16NATIVE */
+ { "UTF16", 0 }, /* SQLITE_UTF16NATIVE */
+ { 0, 0 }
+ };
+ const struct EncName *pEnc;
+ if( !zRight ){ /* "PRAGMA encoding" */
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "encoding", P4_STATIC);
+ sqlite3VdbeAddOp2(v, OP_String8, 0, 1);
+ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
+ if( pEnc->enc==ENC(pParse->db) ){
+ sqlite3VdbeChangeP4(v, -1, pEnc->zName, P4_STATIC);
+ break;
+ }
+ }
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ }else{ /* "PRAGMA encoding = XXX" */
+ /* Only change the value of sqlite.enc if the database handle is not
+ ** initialized. If the main database exists, the new sqlite.enc value
+ ** will be overwritten when the schema is next loaded. If it does not
+ ** already exists, it will be created to use the new encoding value.
+ */
+ if(
+ !(DbHasProperty(db, 0, DB_SchemaLoaded)) ||
+ DbHasProperty(db, 0, DB_Empty)
+ ){
+ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
+ if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){
+ ENC(pParse->db) = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
+ break;
+ }
+ }
+ if( !pEnc->zName ){
+ sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight);
+ }
+ }
+ }
+ }else
+#endif /* SQLITE_OMIT_UTF16 */
+
+#ifndef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
+ /*
+ ** PRAGMA [database.]schema_version
+ ** PRAGMA [database.]schema_version = <integer>
+ **
+ ** PRAGMA [database.]user_version
+ ** PRAGMA [database.]user_version = <integer>
+ **
+ ** The pragma's schema_version and user_version are used to set or get
+ ** the value of the schema-version and user-version, respectively. Both
+ ** the schema-version and the user-version are 32-bit signed integers
+ ** stored in the database header.
+ **
+ ** The schema-cookie is usually only manipulated internally by SQLite. It
+ ** is incremented by SQLite whenever the database schema is modified (by
+ ** creating or dropping a table or index). The schema version is used by
+ ** SQLite each time a query is executed to ensure that the internal cache
+ ** of the schema used when compiling the SQL query matches the schema of
+ ** the database against which the compiled query is actually executed.
+ ** Subverting this mechanism by using "PRAGMA schema_version" to modify
+ ** the schema-version is potentially dangerous and may lead to program
+ ** crashes or database corruption. Use with caution!
+ **
+ ** The user-version is not used internally by SQLite. It may be used by
+ ** applications for any purpose.
+ */
+ if( sqlite3StrICmp(zLeft, "schema_version")==0
+ || sqlite3StrICmp(zLeft, "user_version")==0
+ || sqlite3StrICmp(zLeft, "freelist_count")==0
+ ){
+
+ int iCookie; /* Cookie index. 0 for schema-cookie, 6 for user-cookie. */
+ sqlite3VdbeUsesBtree(v, iDb);
+ switch( zLeft[0] ){
+ case 's': case 'S':
+ iCookie = 0;
+ break;
+ case 'f': case 'F':
+ iCookie = 1;
+ iDb = (-1*(iDb+1));
+ assert(iDb<=0);
+ break;
+ default:
+ iCookie = 5;
+ break;
+ }
+
+ if( zRight && iDb>=0 ){
+ /* Write the specified cookie value */
+ static const VdbeOpList setCookie[] = {
+ { OP_Transaction, 0, 1, 0}, /* 0 */
+ { OP_Integer, 0, 1, 0}, /* 1 */
+ { OP_SetCookie, 0, 0, 1}, /* 2 */
+ };
+ int addr = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie);
+ sqlite3VdbeChangeP1(v, addr, iDb);
+ sqlite3VdbeChangeP1(v, addr+1, atoi(zRight));
+ sqlite3VdbeChangeP1(v, addr+2, iDb);
+ sqlite3VdbeChangeP2(v, addr+2, iCookie);
+ }else{
+ /* Read the specified cookie value */
+ static const VdbeOpList readCookie[] = {
+ { OP_ReadCookie, 0, 1, 0}, /* 0 */
+ { OP_ResultRow, 1, 1, 0}
+ };
+ int addr = sqlite3VdbeAddOpList(v, ArraySize(readCookie), readCookie);
+ sqlite3VdbeChangeP1(v, addr, iDb);
+ sqlite3VdbeChangeP3(v, addr, iCookie);
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, P4_TRANSIENT);
+ }
+ }else
+#endif /* SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS */
+
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+ /*
+ ** Report the current state of file logs for all databases
+ */
+ if( sqlite3StrICmp(zLeft, "lock_status")==0 ){
+ static const char *const azLockName[] = {
+ "unlocked", "shared", "reserved", "pending", "exclusive"
+ };
+ int i;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ sqlite3VdbeSetNumCols(v, 2);
+ pParse->nMem = 2;
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "database", P4_STATIC);
+ sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "status", P4_STATIC);
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt;
+ Pager *pPager;
+ const char *zState = "unknown";
+ int j;
+ if( db->aDb[i].zName==0 ) continue;
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, db->aDb[i].zName, P4_STATIC);
+ pBt = db->aDb[i].pBt;
+ if( pBt==0 || (pPager = sqlite3BtreePager(pBt))==0 ){
+ zState = "closed";
+ }else if( sqlite3_file_control(db, i ? db->aDb[i].zName : 0,
+ SQLITE_FCNTL_LOCKSTATE, &j)==SQLITE_OK ){
+ zState = azLockName[j];
+ }
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, zState, P4_STATIC);
+ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2);
+ }
+ }else
+#endif
+
+#ifdef SQLITE_SSE
+ /*
+ ** Check to see if the sqlite_statements table exists. Create it
+ ** if it does not.
+ */
+ if( sqlite3StrICmp(zLeft, "create_sqlite_statement_table")==0 ){
+ extern int sqlite3CreateStatementsTable(Parse*);
+ sqlite3CreateStatementsTable(pParse);
+ }else
+#endif
+
+#if SQLITE_HAS_CODEC
+ if( sqlite3StrICmp(zLeft, "key")==0 ){
+ sqlite3_key(db, zRight, strlen(zRight));
+ }else
+#endif
+#if SQLITE_HAS_CODEC || defined(SQLITE_ENABLE_CEROD)
+ if( sqlite3StrICmp(zLeft, "activate_extensions")==0 ){
+#if SQLITE_HAS_CODEC
+ if( sqlite3StrNICmp(zRight, "see-", 4)==0 ){
+ extern void sqlite3_activate_see(const char*);
+ sqlite3_activate_see(&zRight[4]);
+ }
+#endif
+#ifdef SQLITE_ENABLE_CEROD
+ if( sqlite3StrNICmp(zRight, "cerod-", 6)==0 ){
+ extern void sqlite3_activate_cerod(const char*);
+ sqlite3_activate_cerod(&zRight[6]);
+ }
+#endif
+ }
+#endif
+
+ {}
+
+ if( v ){
+ /* Code an OP_Expire at the end of each PRAGMA program to cause
+ ** the VDBE implementing the pragma to expire. Most (all?) pragmas
+ ** are only valid for a single execution.
+ */
+ sqlite3VdbeAddOp2(v, OP_Expire, 1, 0);
+
+ /*
+ ** Reset the safety level, in case the fullfsync flag or synchronous
+ ** setting changed.
+ */
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+ if( db->autoCommit ){
+ sqlite3BtreeSetSafetyLevel(pDb->pBt, pDb->safety_level,
+ (db->flags&SQLITE_FullFSync)!=0);
+ }
+#endif
+ }
+pragma_out:
+ sqlite3_free(zLeft);
+ sqlite3_free(zRight);
+}
+
+#endif /* SQLITE_OMIT_PRAGMA || SQLITE_OMIT_PARSER */
+
+/************** End of pragma.c **********************************************/
+/************** Begin file prepare.c *****************************************/
+/*
+** 2005 May 25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the implementation of the sqlite3_prepare()
+** interface, and routines that contribute to loading the database schema
+** from disk.
+**
+** $Id: prepare.c,v 1.83 2008/04/03 14:36:26 danielk1977 Exp $
+*/
+
+/*
+** Fill the InitData structure with an error message that indicates
+** that the database is corrupt.
+*/
+static void corruptSchema(
+ InitData *pData, /* Initialization context */
+ const char *zObj, /* Object being parsed at the point of error */
+ const char *zExtra /* Error information */
+){
+ if( !pData->db->mallocFailed ){
+ if( zObj==0 ) zObj = "?";
+ sqlite3SetString(pData->pzErrMsg, "malformed database schema (", zObj, ")",
+ zExtra!=0 && zExtra[0]!=0 ? " - " : (char*)0, zExtra, (char*)0);
+ }
+ pData->rc = SQLITE_CORRUPT;
+}
+
+/*
+** This is the callback routine for the code that initializes the
+** database. See sqlite3Init() below for additional information.
+** This routine is also called from the OP_ParseSchema opcode of the VDBE.
+**
+** Each callback contains the following information:
+**
+** argv[0] = name of thing being created
+** argv[1] = root page number for table or index. 0 for trigger or view.
+** argv[2] = SQL text for the CREATE statement.
+**
+*/
+SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char **azColName){
+ InitData *pData = (InitData*)pInit;
+ sqlite3 *db = pData->db;
+ int iDb = pData->iDb;
+
+ assert( sqlite3_mutex_held(db->mutex) );
+ pData->rc = SQLITE_OK;
+ DbClearProperty(db, iDb, DB_Empty);
+ if( db->mallocFailed ){
+ corruptSchema(pData, argv[0], 0);
+ return SQLITE_NOMEM;
+ }
+
+ assert( argc==3 );
+ if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */
+ if( argv[1]==0 ){
+ corruptSchema(pData, argv[0], 0);
+ return 1;
+ }
+ assert( iDb>=0 && iDb<db->nDb );
+ if( argv[2] && argv[2][0] ){
+ /* Call the parser to process a CREATE TABLE, INDEX or VIEW.
+ ** But because db->init.busy is set to 1, no VDBE code is generated
+ ** or executed. All the parser does is build the internal data
+ ** structures that describe the table, index, or view.
+ */
+ char *zErr;
+ int rc;
+ assert( db->init.busy );
+ db->init.iDb = iDb;
+ db->init.newTnum = atoi(argv[1]);
+ rc = sqlite3_exec(db, argv[2], 0, 0, &zErr);
+ db->init.iDb = 0;
+ assert( rc!=SQLITE_OK || zErr==0 );
+ if( SQLITE_OK!=rc ){
+ pData->rc = rc;
+ if( rc==SQLITE_NOMEM ){
+ db->mallocFailed = 1;
+ }else if( rc!=SQLITE_INTERRUPT ){
+ corruptSchema(pData, argv[0], zErr);
+ }
+ sqlite3_free(zErr);
+ return 1;
+ }
+ }else if( argv[0]==0 ){
+ corruptSchema(pData, 0, 0);
+ }else{
+ /* If the SQL column is blank it means this is an index that
+ ** was created to be the PRIMARY KEY or to fulfill a UNIQUE
+ ** constraint for a CREATE TABLE. The index should have already
+ ** been created when we processed the CREATE TABLE. All we have
+ ** to do here is record the root page number for that index.
+ */
+ Index *pIndex;
+ pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zName);
+ if( pIndex==0 || pIndex->tnum!=0 ){
+ /* This can occur if there exists an index on a TEMP table which
+ ** has the same name as another index on a permanent index. Since
+ ** the permanent table is hidden by the TEMP table, we can also
+ ** safely ignore the index on the permanent table.
+ */
+ /* Do Nothing */;
+ }else{
+ pIndex->tnum = atoi(argv[1]);
+ }
+ }
+ return 0;
+}
+
+/*
+** Attempt to read the database schema and initialize internal
+** data structures for a single database file. The index of the
+** database file is given by iDb. iDb==0 is used for the main
+** database. iDb==1 should never be used. iDb>=2 is used for
+** auxiliary databases. Return one of the SQLITE_ error codes to
+** indicate success or failure.
+*/
+static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
+ int rc;
+ BtCursor *curMain;
+ int size;
+ Table *pTab;
+ Db *pDb;
+ char const *azArg[4];
+ int meta[10];
+ InitData initData;
+ char const *zMasterSchema;
+ char const *zMasterName = SCHEMA_TABLE(iDb);
+
+ /*
+ ** The master database table has a structure like this
+ */
+ static const char master_schema[] =
+ "CREATE TABLE sqlite_master(\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")"
+ ;
+#ifndef SQLITE_OMIT_TEMPDB
+ static const char temp_master_schema[] =
+ "CREATE TEMP TABLE sqlite_temp_master(\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")"
+ ;
+#else
+ #define temp_master_schema 0
+#endif
+
+ assert( iDb>=0 && iDb<db->nDb );
+ assert( db->aDb[iDb].pSchema );
+ assert( sqlite3_mutex_held(db->mutex) );
+ assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
+
+ /* zMasterSchema and zInitScript are set to point at the master schema
+ ** and initialisation script appropriate for the database being
+ ** initialised. zMasterName is the name of the master table.
+ */
+ if( !OMIT_TEMPDB && iDb==1 ){
+ zMasterSchema = temp_master_schema;
+ }else{
+ zMasterSchema = master_schema;
+ }
+ zMasterName = SCHEMA_TABLE(iDb);
+
+ /* Construct the schema tables. */
+ azArg[0] = zMasterName;
+ azArg[1] = "1";
+ azArg[2] = zMasterSchema;
+ azArg[3] = 0;
+ initData.db = db;
+ initData.iDb = iDb;
+ initData.pzErrMsg = pzErrMsg;
+ (void)sqlite3SafetyOff(db);
+ rc = sqlite3InitCallback(&initData, 3, (char **)azArg, 0);
+ (void)sqlite3SafetyOn(db);
+ if( rc ){
+ rc = initData.rc;
+ goto error_out;
+ }
+ pTab = sqlite3FindTable(db, zMasterName, db->aDb[iDb].zName);
+ if( pTab ){
+ pTab->readOnly = 1;
+ }
+
+ /* Create a cursor to hold the database open
+ */
+ pDb = &db->aDb[iDb];
+ if( pDb->pBt==0 ){
+ if( !OMIT_TEMPDB && iDb==1 ){
+ DbSetProperty(db, 1, DB_SchemaLoaded);
+ }
+ return SQLITE_OK;
+ }
+ curMain = sqlite3MallocZero(sqlite3BtreeCursorSize());
+ if( !curMain ){
+ rc = SQLITE_NOMEM;
+ goto error_out;
+ }
+ sqlite3BtreeEnter(pDb->pBt);
+ rc = sqlite3BtreeCursor(pDb->pBt, MASTER_ROOT, 0, 0, curMain);
+ if( rc!=SQLITE_OK && rc!=SQLITE_EMPTY ){
+ sqlite3SetString(pzErrMsg, sqlite3ErrStr(rc), (char*)0);
+ goto leave_error_out;
+ }
+
+ /* Get the database meta information.
+ **
+ ** Meta values are as follows:
+ ** meta[0] Schema cookie. Changes with each schema change.
+ ** meta[1] File format of schema layer.
+ ** meta[2] Size of the page cache.
+ ** meta[3] Use freelist if 0. Autovacuum if greater than zero.
+ ** meta[4] Db text encoding. 1:UTF-8 2:UTF-16LE 3:UTF-16BE
+ ** meta[5] The user cookie. Used by the application.
+ ** meta[6] Incremental-vacuum flag.
+ ** meta[7]
+ ** meta[8]
+ ** meta[9]
+ **
+ ** Note: The #defined SQLITE_UTF* symbols in sqliteInt.h correspond to
+ ** the possible values of meta[4].
+ */
+ if( rc==SQLITE_OK ){
+ int i;
+ for(i=0; rc==SQLITE_OK && i<sizeof(meta)/sizeof(meta[0]); i++){
+ rc = sqlite3BtreeGetMeta(pDb->pBt, i+1, (u32 *)&meta[i]);
+ }
+ if( rc ){
+ sqlite3SetString(pzErrMsg, sqlite3ErrStr(rc), (char*)0);
+ goto leave_error_out;
+ }
+ }else{
+ memset(meta, 0, sizeof(meta));
+ }
+ pDb->pSchema->schema_cookie = meta[0];
+
+ /* If opening a non-empty database, check the text encoding. For the
+ ** main database, set sqlite3.enc to the encoding of the main database.
+ ** For an attached db, it is an error if the encoding is not the same
+ ** as sqlite3.enc.
+ */
+ if( meta[4] ){ /* text encoding */
+ if( iDb==0 ){
+ /* If opening the main database, set ENC(db). */
+ ENC(db) = (u8)meta[4];
+ db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 6, 0);
+ }else{
+ /* If opening an attached database, the encoding much match ENC(db) */
+ if( meta[4]!=ENC(db) ){
+ sqlite3SetString(pzErrMsg, "attached databases must use the same"
+ " text encoding as main database", (char*)0);
+ rc = SQLITE_ERROR;
+ goto leave_error_out;
+ }
+ }
+ }else{
+ DbSetProperty(db, iDb, DB_Empty);
+ }
+ pDb->pSchema->enc = ENC(db);
+
+ size = meta[2];
+ if( size==0 ){ size = SQLITE_DEFAULT_CACHE_SIZE; }
+ if( size<0 ) size = -size;
+ pDb->pSchema->cache_size = size;
+ sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
+
+ /*
+ ** file_format==1 Version 3.0.0.
+ ** file_format==2 Version 3.1.3. // ALTER TABLE ADD COLUMN
+ ** file_format==3 Version 3.1.4. // ditto but with non-NULL defaults
+ ** file_format==4 Version 3.3.0. // DESC indices. Boolean constants
+ */
+ pDb->pSchema->file_format = meta[1];
+ if( pDb->pSchema->file_format==0 ){
+ pDb->pSchema->file_format = 1;
+ }
+ if( pDb->pSchema->file_format>SQLITE_MAX_FILE_FORMAT ){
+ sqlite3SetString(pzErrMsg, "unsupported file format", (char*)0);
+ rc = SQLITE_ERROR;
+ goto leave_error_out;
+ }
+
+ /* Ticket #2804: When we open a database in the newer file format,
+ ** clear the legacy_file_format pragma flag so that a VACUUM will
+ ** not downgrade the database and thus invalidate any descending
+ ** indices that the user might have created.
+ */
+ if( iDb==0 && meta[1]>=4 ){
+ db->flags &= ~SQLITE_LegacyFileFmt;
+ }
+
+ /* Read the schema information out of the schema tables
+ */
+ assert( db->init.busy );
+ if( rc==SQLITE_EMPTY ){
+ /* For an empty database, there is nothing to read */
+ rc = SQLITE_OK;
+ }else{
+ char *zSql;
+ zSql = sqlite3MPrintf(db,
+ "SELECT name, rootpage, sql FROM '%q'.%s",
+ db->aDb[iDb].zName, zMasterName);
+ (void)sqlite3SafetyOff(db);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
+ xAuth = db->xAuth;
+ db->xAuth = 0;
+#endif
+ rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ db->xAuth = xAuth;
+ }
+#endif
+ if( rc==SQLITE_ABORT ) rc = initData.rc;
+ (void)sqlite3SafetyOn(db);
+ sqlite3_free(zSql);
+#ifndef SQLITE_OMIT_ANALYZE
+ if( rc==SQLITE_OK ){
+ sqlite3AnalysisLoad(db, iDb);
+ }
+#endif
+ }
+ if( db->mallocFailed ){
+ /* sqlite3SetString(pzErrMsg, "out of memory", (char*)0); */
+ rc = SQLITE_NOMEM;
+ sqlite3ResetInternalSchema(db, 0);
+ }
+ if( rc==SQLITE_OK || (db->flags&SQLITE_RecoveryMode)){
+ /* Black magic: If the SQLITE_RecoveryMode flag is set, then consider
+ ** the schema loaded, even if errors occured. In this situation the
+ ** current sqlite3_prepare() operation will fail, but the following one
+ ** will attempt to compile the supplied statement against whatever subset
+ ** of the schema was loaded before the error occured. The primary
+ ** purpose of this is to allow access to the sqlite_master table
+ ** even when its contents have been corrupted.
+ */
+ DbSetProperty(db, iDb, DB_SchemaLoaded);
+ rc = SQLITE_OK;
+ }
+
+ /* Jump here for an error that occurs after successfully allocating
+ ** curMain and calling sqlite3BtreeEnter(). For an error that occurs
+ ** before that point, jump to error_out.
+ */
+leave_error_out:
+ sqlite3BtreeCloseCursor(curMain);
+ sqlite3_free(curMain);
+ sqlite3BtreeLeave(pDb->pBt);
+
+error_out:
+ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
+ db->mallocFailed = 1;
+ }
+ return rc;
+}
+
+/*
+** Initialize all database files - the main database file, the file
+** used to store temporary tables, and any additional database files
+** created using ATTACH statements. Return a success code. If an
+** error occurs, write an error message into *pzErrMsg.
+**
+** After a database is initialized, the DB_SchemaLoaded bit is set
+** bit is set in the flags field of the Db structure. If the database
+** file was of zero-length, then the DB_Empty flag is also set.
+*/
+SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){
+ int i, rc;
+ int commit_internal = !(db->flags&SQLITE_InternChanges);
+
+ assert( sqlite3_mutex_held(db->mutex) );
+ if( db->init.busy ) return SQLITE_OK;
+ rc = SQLITE_OK;
+ db->init.busy = 1;
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+ if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue;
+ rc = sqlite3InitOne(db, i, pzErrMsg);
+ if( rc ){
+ sqlite3ResetInternalSchema(db, i);
+ }
+ }
+
+ /* Once all the other databases have been initialised, load the schema
+ ** for the TEMP database. This is loaded last, as the TEMP database
+ ** schema may contain references to objects in other databases.
+ */
+#ifndef SQLITE_OMIT_TEMPDB
+ if( rc==SQLITE_OK && db->nDb>1 && !DbHasProperty(db, 1, DB_SchemaLoaded) ){
+ rc = sqlite3InitOne(db, 1, pzErrMsg);
+ if( rc ){
+ sqlite3ResetInternalSchema(db, 1);
+ }
+ }
+#endif
+
+ db->init.busy = 0;
+ if( rc==SQLITE_OK && commit_internal ){
+ sqlite3CommitInternalChanges(db);
+ }
+
+ return rc;
+}
+
+/*
+** This routine is a no-op if the database schema is already initialised.
+** Otherwise, the schema is loaded. An error code is returned.
+*/
+SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse){
+ int rc = SQLITE_OK;
+ sqlite3 *db = pParse->db;
+ assert( sqlite3_mutex_held(db->mutex) );
+ if( !db->init.busy ){
+ rc = sqlite3Init(db, &pParse->zErrMsg);
+ }
+ if( rc!=SQLITE_OK ){
+ pParse->rc = rc;
+ pParse->nErr++;
+ }
+ return rc;
+}
+
+
+/*
+** Check schema cookies in all databases. If any cookie is out
+** of date, return 0. If all schema cookies are current, return 1.
+*/
+static int schemaIsValid(sqlite3 *db){
+ int iDb;
+ int rc;
+ BtCursor *curTemp;
+ int cookie;
+ int allOk = 1;
+
+ curTemp = (BtCursor *)sqlite3_malloc(sqlite3BtreeCursorSize());
+ if( curTemp ){
+ assert( sqlite3_mutex_held(db->mutex) );
+ for(iDb=0; allOk && iDb<db->nDb; iDb++){
+ Btree *pBt;
+ pBt = db->aDb[iDb].pBt;
+ if( pBt==0 ) continue;
+ memset(curTemp, 0, sqlite3BtreeCursorSize());
+ rc = sqlite3BtreeCursor(pBt, MASTER_ROOT, 0, 0, curTemp);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3BtreeGetMeta(pBt, 1, (u32 *)&cookie);
+ if( rc==SQLITE_OK && cookie!=db->aDb[iDb].pSchema->schema_cookie ){
+ allOk = 0;
+ }
+ sqlite3BtreeCloseCursor(curTemp);
+ }
+ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
+ db->mallocFailed = 1;
+ }
+ }
+ sqlite3_free(curTemp);
+ }else{
+ allOk = 0;
+ db->mallocFailed = 1;
+ }
+
+ return allOk;
+}
+
+/*
+** Convert a schema pointer into the iDb index that indicates
+** which database file in db->aDb[] the schema refers to.
+**
+** If the same database is attached more than once, the first
+** attached database is returned.
+*/
+SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){
+ int i = -1000000;
+
+ /* If pSchema is NULL, then return -1000000. This happens when code in
+ ** expr.c is trying to resolve a reference to a transient table (i.e. one
+ ** created by a sub-select). In this case the return value of this
+ ** function should never be used.
+ **
+ ** We return -1000000 instead of the more usual -1 simply because using
+ ** -1000000 as incorrectly using -1000000 index into db->aDb[] is much
+ ** more likely to cause a segfault than -1 (of course there are assert()
+ ** statements too, but it never hurts to play the odds).
+ */
+ assert( sqlite3_mutex_held(db->mutex) );
+ if( pSchema ){
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pSchema==pSchema ){
+ break;
+ }
+ }
+ assert( i>=0 &&i>=0 && i<db->nDb );
+ }
+ return i;
+}
+
+/*
+** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
+*/
+static int sqlite3Prepare(
+ sqlite3 *db, /* Database handle. */
+ const char *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ int saveSqlFlag, /* True to copy SQL text into the sqlite3_stmt */
+ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const char **pzTail /* OUT: End of parsed string */
+){
+ Parse sParse;
+ char *zErrMsg = 0;
+ int rc = SQLITE_OK;
+ int i;
+
+ assert( ppStmt );
+ *ppStmt = 0;
+ if( sqlite3SafetyOn(db) ){
+ return SQLITE_MISUSE;
+ }
+ assert( !db->mallocFailed );
+ assert( sqlite3_mutex_held(db->mutex) );
+
+ /* If any attached database schemas are locked, do not proceed with
+ ** compilation. Instead return SQLITE_LOCKED immediately.
+ */
+ for(i=0; i<db->nDb; i++) {
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ int rc;
+ rc = sqlite3BtreeSchemaLocked(pBt);
+ if( rc ){
+ const char *zDb = db->aDb[i].zName;
+ sqlite3Error(db, SQLITE_LOCKED, "database schema is locked: %s", zDb);
+ (void)sqlite3SafetyOff(db);
+ return SQLITE_LOCKED;
+ }
+ }
+ }
+
+ memset(&sParse, 0, sizeof(sParse));
+ sParse.db = db;
+ if( nBytes>=0 && zSql[nBytes-1]!=0 ){
+ char *zSqlCopy;
+ int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH];
+ if( nBytes>mxLen ){
+ sqlite3Error(db, SQLITE_TOOBIG, "statement too long");
+ (void)sqlite3SafetyOff(db);
+ return SQLITE_TOOBIG;
+ }
+ zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes);
+ if( zSqlCopy ){
+ sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg);
+ sqlite3_free(zSqlCopy);
+ sParse.zTail = &zSql[sParse.zTail-zSqlCopy];
+ }else{
+ sParse.zTail = &zSql[nBytes];
+ }
+ }else{
+ sqlite3RunParser(&sParse, zSql, &zErrMsg);
+ }
+
+ if( db->mallocFailed ){
+ sParse.rc = SQLITE_NOMEM;
+ }
+ if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;
+ if( sParse.checkSchema && !schemaIsValid(db) ){
+ sParse.rc = SQLITE_SCHEMA;
+ }
+ if( sParse.rc==SQLITE_SCHEMA ){
+ sqlite3ResetInternalSchema(db, 0);
+ }
+ if( db->mallocFailed ){
+ sParse.rc = SQLITE_NOMEM;
+ }
+ if( pzTail ){
+ *pzTail = sParse.zTail;
+ }
+ rc = sParse.rc;
+
+#ifndef SQLITE_OMIT_EXPLAIN
+ if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){
+ if( sParse.explain==2 ){
+ sqlite3VdbeSetNumCols(sParse.pVdbe, 3);
+ sqlite3VdbeSetColName(sParse.pVdbe, 0, COLNAME_NAME, "order", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 1, COLNAME_NAME, "from", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 2, COLNAME_NAME, "detail", P4_STATIC);
+ }else{
+ sqlite3VdbeSetNumCols(sParse.pVdbe, 8);
+ sqlite3VdbeSetColName(sParse.pVdbe, 0, COLNAME_NAME, "addr", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 1, COLNAME_NAME, "opcode", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 2, COLNAME_NAME, "p1", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 3, COLNAME_NAME, "p2", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 4, COLNAME_NAME, "p3", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 5, COLNAME_NAME, "p4", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 6, COLNAME_NAME, "p5", P4_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 7, COLNAME_NAME, "comment",P4_STATIC);
+ }
+ }
+#endif
+
+ if( sqlite3SafetyOff(db) ){
+ rc = SQLITE_MISUSE;
+ }
+
+ if( saveSqlFlag ){
+ sqlite3VdbeSetSql(sParse.pVdbe, zSql, sParse.zTail - zSql);
+ }
+ if( rc!=SQLITE_OK || db->mallocFailed ){
+ sqlite3_finalize((sqlite3_stmt*)sParse.pVdbe);
+ assert(!(*ppStmt));
+ }else{
+ *ppStmt = (sqlite3_stmt*)sParse.pVdbe;
+ }
+
+ if( zErrMsg ){
+ sqlite3Error(db, rc, "%s", zErrMsg);
+ sqlite3_free(zErrMsg);
+ }else{
+ sqlite3Error(db, rc, 0);
+ }
+
+ rc = sqlite3ApiExit(db, rc);
+ assert( (rc&db->errMask)==rc );
+ return rc;
+}
+static int sqlite3LockAndPrepare(
+ sqlite3 *db, /* Database handle. */
+ const char *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ int saveSqlFlag, /* True to copy SQL text into the sqlite3_stmt */
+ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const char **pzTail /* OUT: End of parsed string */
+){
+ int rc;
+ if( !sqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE;
+ }
+ sqlite3_mutex_enter(db->mutex);
+ sqlite3BtreeEnterAll(db);
+ rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, ppStmt, pzTail);
+ sqlite3BtreeLeaveAll(db);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** Rerun the compilation of a statement after a schema change.
+** Return true if the statement was recompiled successfully.
+** Return false if there is an error of some kind.
+*/
+SQLITE_PRIVATE int sqlite3Reprepare(Vdbe *p){
+ int rc;
+ sqlite3_stmt *pNew;
+ const char *zSql;
+ sqlite3 *db;
+
+ assert( sqlite3_mutex_held(sqlite3VdbeDb(p)->mutex) );
+ zSql = sqlite3_sql((sqlite3_stmt *)p);
+ assert( zSql!=0 ); /* Reprepare only called for prepare_v2() statements */
+ db = sqlite3VdbeDb(p);
+ assert( sqlite3_mutex_held(db->mutex) );
+ rc = sqlite3LockAndPrepare(db, zSql, -1, 0, &pNew, 0);
+ if( rc ){
+ if( rc==SQLITE_NOMEM ){
+ db->mallocFailed = 1;
+ }
+ assert( pNew==0 );
+ return 0;
+ }else{
+ assert( pNew!=0 );
+ }
+ sqlite3VdbeSwap((Vdbe*)pNew, p);
+ sqlite3_transfer_bindings(pNew, (sqlite3_stmt*)p);
+ sqlite3VdbeResetStepResult((Vdbe*)pNew);
+ sqlite3VdbeFinalize((Vdbe*)pNew);
+ return 1;
+}
+
+
+/*
+** Two versions of the official API. Legacy and new use. In the legacy
+** version, the original SQL text is not saved in the prepared statement
+** and so if a schema change occurs, SQLITE_SCHEMA is returned by
+** sqlite3_step(). In the new version, the original SQL text is retained
+** and the statement is automatically recompiled if an schema change
+** occurs.
+*/
+SQLITE_API int sqlite3_prepare(
+ sqlite3 *db, /* Database handle. */
+ const char *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const char **pzTail /* OUT: End of parsed string */
+){
+ int rc;
+ rc = sqlite3LockAndPrepare(db,zSql,nBytes,0,ppStmt,pzTail);
+ assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
+ return rc;
+}
+SQLITE_API int sqlite3_prepare_v2(
+ sqlite3 *db, /* Database handle. */
+ const char *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const char **pzTail /* OUT: End of parsed string */
+){
+ int rc;
+ rc = sqlite3LockAndPrepare(db,zSql,nBytes,1,ppStmt,pzTail);
+ assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
+ return rc;
+}
+
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Compile the UTF-16 encoded SQL statement zSql into a statement handle.
+*/
+static int sqlite3Prepare16(
+ sqlite3 *db, /* Database handle. */
+ const void *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ int saveSqlFlag, /* True to save SQL text into the sqlite3_stmt */
+ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const void **pzTail /* OUT: End of parsed string */
+){
+ /* This function currently works by first transforming the UTF-16
+ ** encoded string to UTF-8, then invoking sqlite3_prepare(). The
+ ** tricky bit is figuring out the pointer to return in *pzTail.
+ */
+ char *zSql8;
+ const char *zTail8 = 0;
+ int rc = SQLITE_OK;
+
+ if( !sqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE;
+ }
+ sqlite3_mutex_enter(db->mutex);
+ zSql8 = sqlite3Utf16to8(db, zSql, nBytes);
+ if( zSql8 ){
+ rc = sqlite3LockAndPrepare(db, zSql8, -1, saveSqlFlag, ppStmt, &zTail8);
+ }
+
+ if( zTail8 && pzTail ){
+ /* If sqlite3_prepare returns a tail pointer, we calculate the
+ ** equivalent pointer into the UTF-16 string by counting the unicode
+ ** characters between zSql8 and zTail8, and then returning a pointer
+ ** the same number of characters into the UTF-16 string.
+ */
+ int chars_parsed = sqlite3Utf8CharLen(zSql8, zTail8-zSql8);
+ *pzTail = (u8 *)zSql + sqlite3Utf16ByteLen(zSql, chars_parsed);
+ }
+ sqlite3_free(zSql8);
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** Two versions of the official API. Legacy and new use. In the legacy
+** version, the original SQL text is not saved in the prepared statement
+** and so if a schema change occurs, SQLITE_SCHEMA is returned by
+** sqlite3_step(). In the new version, the original SQL text is retained
+** and the statement is automatically recompiled if an schema change
+** occurs.
+*/
+SQLITE_API int sqlite3_prepare16(
+ sqlite3 *db, /* Database handle. */
+ const void *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const void **pzTail /* OUT: End of parsed string */
+){
+ int rc;
+ rc = sqlite3Prepare16(db,zSql,nBytes,0,ppStmt,pzTail);
+ assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
+ return rc;
+}
+SQLITE_API int sqlite3_prepare16_v2(
+ sqlite3 *db, /* Database handle. */
+ const void *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const void **pzTail /* OUT: End of parsed string */
+){
+ int rc;
+ rc = sqlite3Prepare16(db,zSql,nBytes,1,ppStmt,pzTail);
+ assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
+ return rc;
+}
+
+#endif /* SQLITE_OMIT_UTF16 */
+
+/************** End of prepare.c *********************************************/
+/************** Begin file select.c ******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** to handle SELECT statements in SQLite.
+**
+** $Id: select.c,v 1.429 2008/05/01 17:03:49 drh Exp $
+*/
+
+
+/*
+** Delete all the content of a Select structure but do not deallocate
+** the select structure itself.
+*/
+static void clearSelect(Select *p){
+ sqlite3ExprListDelete(p->pEList);
+ sqlite3SrcListDelete(p->pSrc);
+ sqlite3ExprDelete(p->pWhere);
+ sqlite3ExprListDelete(p->pGroupBy);
+ sqlite3ExprDelete(p->pHaving);
+ sqlite3ExprListDelete(p->pOrderBy);
+ sqlite3SelectDelete(p->pPrior);
+ sqlite3ExprDelete(p->pLimit);
+ sqlite3ExprDelete(p->pOffset);
+}
+
+/*
+** Initialize a SelectDest structure.
+*/
+SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){
+ pDest->eDest = eDest;
+ pDest->iParm = iParm;
+ pDest->affinity = 0;
+ pDest->iMem = 0;
+ pDest->nMem = 0;
+}
+
+
+/*
+** Allocate a new Select structure and return a pointer to that
+** structure.
+*/
+SQLITE_PRIVATE Select *sqlite3SelectNew(
+ Parse *pParse, /* Parsing context */
+ ExprList *pEList, /* which columns to include in the result */
+ SrcList *pSrc, /* the FROM clause -- which tables to scan */
+ Expr *pWhere, /* the WHERE clause */
+ ExprList *pGroupBy, /* the GROUP BY clause */
+ Expr *pHaving, /* the HAVING clause */
+ ExprList *pOrderBy, /* the ORDER BY clause */
+ int isDistinct, /* true if the DISTINCT keyword is present */
+ Expr *pLimit, /* LIMIT value. NULL means not used */
+ Expr *pOffset /* OFFSET value. NULL means no offset */
+){
+ Select *pNew;
+ Select standin;
+ sqlite3 *db = pParse->db;
+ pNew = sqlite3DbMallocZero(db, sizeof(*pNew) );
+ assert( !pOffset || pLimit ); /* Can't have OFFSET without LIMIT. */
+ if( pNew==0 ){
+ pNew = &standin;
+ memset(pNew, 0, sizeof(*pNew));
+ }
+ if( pEList==0 ){
+ pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ALL,0,0,0), 0);
+ }
+ pNew->pEList = pEList;
+ pNew->pSrc = pSrc;
+ pNew->pWhere = pWhere;
+ pNew->pGroupBy = pGroupBy;
+ pNew->pHaving = pHaving;
+ pNew->pOrderBy = pOrderBy;
+ pNew->isDistinct = isDistinct;
+ pNew->op = TK_SELECT;
+ assert( pOffset==0 || pLimit!=0 );
+ pNew->pLimit = pLimit;
+ pNew->pOffset = pOffset;
+ pNew->iLimit = -1;
+ pNew->iOffset = -1;
+ pNew->addrOpenEphm[0] = -1;
+ pNew->addrOpenEphm[1] = -1;
+ pNew->addrOpenEphm[2] = -1;
+ if( pNew==&standin) {
+ clearSelect(pNew);
+ pNew = 0;
+ }
+ return pNew;
+}
+
+/*
+** Delete the given Select structure and all of its substructures.
+*/
+SQLITE_PRIVATE void sqlite3SelectDelete(Select *p){
+ if( p ){
+ clearSelect(p);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Given 1 to 3 identifiers preceeding the JOIN keyword, determine the
+** type of join. Return an integer constant that expresses that type
+** in terms of the following bit values:
+**
+** JT_INNER
+** JT_CROSS
+** JT_OUTER
+** JT_NATURAL
+** JT_LEFT
+** JT_RIGHT
+**
+** A full outer join is the combination of JT_LEFT and JT_RIGHT.
+**
+** If an illegal or unsupported join type is seen, then still return
+** a join type, but put an error in the pParse structure.
+*/
+SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){
+ int jointype = 0;
+ Token *apAll[3];
+ Token *p;
+ static const struct {
+ const char zKeyword[8];
+ u8 nChar;
+ u8 code;
+ } keywords[] = {
+ { "natural", 7, JT_NATURAL },
+ { "left", 4, JT_LEFT|JT_OUTER },
+ { "right", 5, JT_RIGHT|JT_OUTER },
+ { "full", 4, JT_LEFT|JT_RIGHT|JT_OUTER },
+ { "outer", 5, JT_OUTER },
+ { "inner", 5, JT_INNER },
+ { "cross", 5, JT_INNER|JT_CROSS },
+ };
+ int i, j;
+ apAll[0] = pA;
+ apAll[1] = pB;
+ apAll[2] = pC;
+ for(i=0; i<3 && apAll[i]; i++){
+ p = apAll[i];
+ for(j=0; j<sizeof(keywords)/sizeof(keywords[0]); j++){
+ if( p->n==keywords[j].nChar
+ && sqlite3StrNICmp((char*)p->z, keywords[j].zKeyword, p->n)==0 ){
+ jointype |= keywords[j].code;
+ break;
+ }
+ }
+ if( j>=sizeof(keywords)/sizeof(keywords[0]) ){
+ jointype |= JT_ERROR;
+ break;
+ }
+ }
+ if(
+ (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) ||
+ (jointype & JT_ERROR)!=0
+ ){
+ const char *zSp1 = " ";
+ const char *zSp2 = " ";
+ if( pB==0 ){ zSp1++; }
+ if( pC==0 ){ zSp2++; }
+ sqlite3ErrorMsg(pParse, "unknown or unsupported join type: "
+ "%T%s%T%s%T", pA, zSp1, pB, zSp2, pC);
+ jointype = JT_INNER;
+ }else if( jointype & JT_RIGHT ){
+ sqlite3ErrorMsg(pParse,
+ "RIGHT and FULL OUTER JOINs are not currently supported");
+ jointype = JT_INNER;
+ }
+ return jointype;
+}
+
+/*
+** Return the index of a column in a table. Return -1 if the column
+** is not contained in the table.
+*/
+static int columnIndex(Table *pTab, const char *zCol){
+ int i;
+ for(i=0; i<pTab->nCol; i++){
+ if( sqlite3StrICmp(pTab->aCol[i].zName, zCol)==0 ) return i;
+ }
+ return -1;
+}
+
+/*
+** Set the value of a token to a '\000'-terminated string.
+*/
+static void setToken(Token *p, const char *z){
+ p->z = (u8*)z;
+ p->n = z ? strlen(z) : 0;
+ p->dyn = 0;
+}
+
+/*
+** Set the token to the double-quoted and escaped version of the string pointed
+** to by z. For example;
+**
+** {a"bc} -> {"a""bc"}
+*/
+static void setQuotedToken(Parse *pParse, Token *p, const char *z){
+
+ /* Check if the string contains any " characters. If it does, then
+ ** this function will malloc space to create a quoted version of
+ ** the string in. Otherwise, save a call to sqlite3MPrintf() by
+ ** just copying the pointer to the string.
+ */
+ const char *z2 = z;
+ while( *z2 ){
+ if( *z2=='"' ) break;
+ z2++;
+ }
+
+ if( *z2 ){
+ /* String contains " characters - copy and quote the string. */
+ p->z = (u8 *)sqlite3MPrintf(pParse->db, "\"%w\"", z);
+ if( p->z ){
+ p->n = strlen((char *)p->z);
+ p->dyn = 1;
+ }
+ }else{
+ /* String contains no " characters - copy the pointer. */
+ p->z = (u8*)z;
+ p->n = (z2 - z);
+ p->dyn = 0;
+ }
+}
+
+/*
+** Create an expression node for an identifier with the name of zName
+*/
+SQLITE_PRIVATE Expr *sqlite3CreateIdExpr(Parse *pParse, const char *zName){
+ Token dummy;
+ setToken(&dummy, zName);
+ return sqlite3PExpr(pParse, TK_ID, 0, 0, &dummy);
+}
+
+/*
+** Add a term to the WHERE expression in *ppExpr that requires the
+** zCol column to be equal in the two tables pTab1 and pTab2.
+*/
+static void addWhereTerm(
+ Parse *pParse, /* Parsing context */
+ const char *zCol, /* Name of the column */
+ const Table *pTab1, /* First table */
+ const char *zAlias1, /* Alias for first table. May be NULL */
+ const Table *pTab2, /* Second table */
+ const char *zAlias2, /* Alias for second table. May be NULL */
+ int iRightJoinTable, /* VDBE cursor for the right table */
+ Expr **ppExpr, /* Add the equality term to this expression */
+ int isOuterJoin /* True if dealing with an OUTER join */
+){
+ Expr *pE1a, *pE1b, *pE1c;
+ Expr *pE2a, *pE2b, *pE2c;
+ Expr *pE;
+
+ pE1a = sqlite3CreateIdExpr(pParse, zCol);
+ pE2a = sqlite3CreateIdExpr(pParse, zCol);
+ if( zAlias1==0 ){
+ zAlias1 = pTab1->zName;
+ }
+ pE1b = sqlite3CreateIdExpr(pParse, zAlias1);
+ if( zAlias2==0 ){
+ zAlias2 = pTab2->zName;
+ }
+ pE2b = sqlite3CreateIdExpr(pParse, zAlias2);
+ pE1c = sqlite3PExpr(pParse, TK_DOT, pE1b, pE1a, 0);
+ pE2c = sqlite3PExpr(pParse, TK_DOT, pE2b, pE2a, 0);
+ pE = sqlite3PExpr(pParse, TK_EQ, pE1c, pE2c, 0);
+ if( pE && isOuterJoin ){
+ ExprSetProperty(pE, EP_FromJoin);
+ pE->iRightJoinTable = iRightJoinTable;
+ }
+ *ppExpr = sqlite3ExprAnd(pParse->db,*ppExpr, pE);
+}
+
+/*
+** Set the EP_FromJoin property on all terms of the given expression.
+** And set the Expr.iRightJoinTable to iTable for every term in the
+** expression.
+**
+** The EP_FromJoin property is used on terms of an expression to tell
+** the LEFT OUTER JOIN processing logic that this term is part of the
+** join restriction specified in the ON or USING clause and not a part
+** of the more general WHERE clause. These terms are moved over to the
+** WHERE clause during join processing but we need to remember that they
+** originated in the ON or USING clause.
+**
+** The Expr.iRightJoinTable tells the WHERE clause processing that the
+** expression depends on table iRightJoinTable even if that table is not
+** explicitly mentioned in the expression. That information is needed
+** for cases like this:
+**
+** SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.b AND t1.x=5
+**
+** The where clause needs to defer the handling of the t1.x=5
+** term until after the t2 loop of the join. In that way, a
+** NULL t2 row will be inserted whenever t1.x!=5. If we do not
+** defer the handling of t1.x=5, it will be processed immediately
+** after the t1 loop and rows with t1.x!=5 will never appear in
+** the output, which is incorrect.
+*/
+static void setJoinExpr(Expr *p, int iTable){
+ while( p ){
+ ExprSetProperty(p, EP_FromJoin);
+ p->iRightJoinTable = iTable;
+ setJoinExpr(p->pLeft, iTable);
+ p = p->pRight;
+ }
+}
+
+/*
+** This routine processes the join information for a SELECT statement.
+** ON and USING clauses are converted into extra terms of the WHERE clause.
+** NATURAL joins also create extra WHERE clause terms.
+**
+** The terms of a FROM clause are contained in the Select.pSrc structure.
+** The left most table is the first entry in Select.pSrc. The right-most
+** table is the last entry. The join operator is held in the entry to
+** the left. Thus entry 0 contains the join operator for the join between
+** entries 0 and 1. Any ON or USING clauses associated with the join are
+** also attached to the left entry.
+**
+** This routine returns the number of errors encountered.
+*/
+static int sqliteProcessJoin(Parse *pParse, Select *p){
+ SrcList *pSrc; /* All tables in the FROM clause */
+ int i, j; /* Loop counters */
+ struct SrcList_item *pLeft; /* Left table being joined */
+ struct SrcList_item *pRight; /* Right table being joined */
+
+ pSrc = p->pSrc;
+ pLeft = &pSrc->a[0];
+ pRight = &pLeft[1];
+ for(i=0; i<pSrc->nSrc-1; i++, pRight++, pLeft++){
+ Table *pLeftTab = pLeft->pTab;
+ Table *pRightTab = pRight->pTab;
+ int isOuter;
+
+ if( pLeftTab==0 || pRightTab==0 ) continue;
+ isOuter = (pRight->jointype & JT_OUTER)!=0;
+
+ /* When the NATURAL keyword is present, add WHERE clause terms for
+ ** every column that the two tables have in common.
+ */
+ if( pRight->jointype & JT_NATURAL ){
+ if( pRight->pOn || pRight->pUsing ){
+ sqlite3ErrorMsg(pParse, "a NATURAL join may not have "
+ "an ON or USING clause", 0);
+ return 1;
+ }
+ for(j=0; j<pLeftTab->nCol; j++){
+ char *zName = pLeftTab->aCol[j].zName;
+ if( columnIndex(pRightTab, zName)>=0 ){
+ addWhereTerm(pParse, zName, pLeftTab, pLeft->zAlias,
+ pRightTab, pRight->zAlias,
+ pRight->iCursor, &p->pWhere, isOuter);
+
+ }
+ }
+ }
+
+ /* Disallow both ON and USING clauses in the same join
+ */
+ if( pRight->pOn && pRight->pUsing ){
+ sqlite3ErrorMsg(pParse, "cannot have both ON and USING "
+ "clauses in the same join");
+ return 1;
+ }
+
+ /* Add the ON clause to the end of the WHERE clause, connected by
+ ** an AND operator.
+ */
+ if( pRight->pOn ){
+ if( isOuter ) setJoinExpr(pRight->pOn, pRight->iCursor);
+ p->pWhere = sqlite3ExprAnd(pParse->db, p->pWhere, pRight->pOn);
+ pRight->pOn = 0;
+ }
+
+ /* Create extra terms on the WHERE clause for each column named
+ ** in the USING clause. Example: If the two tables to be joined are
+ ** A and B and the USING clause names X, Y, and Z, then add this
+ ** to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z
+ ** Report an error if any column mentioned in the USING clause is
+ ** not contained in both tables to be joined.
+ */
+ if( pRight->pUsing ){
+ IdList *pList = pRight->pUsing;
+ for(j=0; j<pList->nId; j++){
+ char *zName = pList->a[j].zName;
+ if( columnIndex(pLeftTab, zName)<0 || columnIndex(pRightTab, zName)<0 ){
+ sqlite3ErrorMsg(pParse, "cannot join using column %s - column "
+ "not present in both tables", zName);
+ return 1;
+ }
+ addWhereTerm(pParse, zName, pLeftTab, pLeft->zAlias,
+ pRightTab, pRight->zAlias,
+ pRight->iCursor, &p->pWhere, isOuter);
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Insert code into "v" that will push the record on the top of the
+** stack into the sorter.
+*/
+static void pushOntoSorter(
+ Parse *pParse, /* Parser context */
+ ExprList *pOrderBy, /* The ORDER BY clause */
+ Select *pSelect, /* The whole SELECT statement */
+ int regData /* Register holding data to be sorted */
+){
+ Vdbe *v = pParse->pVdbe;
+ int nExpr = pOrderBy->nExpr;
+ int regBase = sqlite3GetTempRange(pParse, nExpr+2);
+ int regRecord = sqlite3GetTempReg(pParse);
+ sqlite3ExprCodeExprList(pParse, pOrderBy, regBase, 0);
+ sqlite3VdbeAddOp2(v, OP_Sequence, pOrderBy->iECursor, regBase+nExpr);
+ sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+1);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nExpr + 2, regRecord);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, pOrderBy->iECursor, regRecord);
+ sqlite3ReleaseTempReg(pParse, regRecord);
+ sqlite3ReleaseTempRange(pParse, regBase, nExpr+2);
+ if( pSelect->iLimit>=0 ){
+ int addr1, addr2;
+ int iLimit;
+ if( pSelect->pOffset ){
+ iLimit = pSelect->iOffset+1;
+ }else{
+ iLimit = pSelect->iLimit;
+ }
+ addr1 = sqlite3VdbeAddOp1(v, OP_IfZero, iLimit);
+ sqlite3VdbeAddOp2(v, OP_AddImm, iLimit, -1);
+ addr2 = sqlite3VdbeAddOp0(v, OP_Goto);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp1(v, OP_Last, pOrderBy->iECursor);
+ sqlite3VdbeAddOp1(v, OP_Delete, pOrderBy->iECursor);
+ sqlite3VdbeJumpHere(v, addr2);
+ pSelect->iLimit = -1;
+ }
+}
+
+/*
+** Add code to implement the OFFSET
+*/
+static void codeOffset(
+ Vdbe *v, /* Generate code into this VM */
+ Select *p, /* The SELECT statement being coded */
+ int iContinue /* Jump here to skip the current record */
+){
+ if( p->iOffset>=0 && iContinue!=0 ){
+ int addr;
+ sqlite3VdbeAddOp2(v, OP_AddImm, p->iOffset, -1);
+ addr = sqlite3VdbeAddOp1(v, OP_IfNeg, p->iOffset);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iContinue);
+ VdbeComment((v, "skip OFFSET records"));
+ sqlite3VdbeJumpHere(v, addr);
+ }
+}
+
+/*
+** Add code that will check to make sure the N registers starting at iMem
+** form a distinct entry. iTab is a sorting index that holds previously
+** seen combinations of the N values. A new entry is made in iTab
+** if the current N values are new.
+**
+** A jump to addrRepeat is made and the N+1 values are popped from the
+** stack if the top N elements are not distinct.
+*/
+static void codeDistinct(
+ Parse *pParse, /* Parsing and code generating context */
+ int iTab, /* A sorting index used to test for distinctness */
+ int addrRepeat, /* Jump to here if not distinct */
+ int N, /* Number of elements */
+ int iMem /* First element */
+){
+ Vdbe *v;
+ int r1;
+
+ v = pParse->pVdbe;
+ r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1);
+ sqlite3VdbeAddOp3(v, OP_Found, iTab, addrRepeat, r1);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iTab, r1);
+ sqlite3ReleaseTempReg(pParse, r1);
+}
+
+/*
+** Generate an error message when a SELECT is used within a subexpression
+** (example: "a IN (SELECT * FROM table)") but it has more than 1 result
+** column. We do this in a subroutine because the error occurs in multiple
+** places.
+*/
+static int checkForMultiColumnSelectError(
+ Parse *pParse, /* Parse context. */
+ SelectDest *pDest, /* Destination of SELECT results */
+ int nExpr /* Number of result columns returned by SELECT */
+){
+ int eDest = pDest->eDest;
+ if( nExpr>1 && (eDest==SRT_Mem || eDest==SRT_Set) ){
+ sqlite3ErrorMsg(pParse, "only a single result allowed for "
+ "a SELECT that is part of an expression");
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
+/*
+** This routine generates the code for the inside of the inner loop
+** of a SELECT.
+**
+** If srcTab and nColumn are both zero, then the pEList expressions
+** are evaluated in order to get the data for this row. If nColumn>0
+** then data is pulled from srcTab and pEList is used only to get the
+** datatypes for each column.
+*/
+static void selectInnerLoop(
+ Parse *pParse, /* The parser context */
+ Select *p, /* The complete select statement being coded */
+ ExprList *pEList, /* List of values being extracted */
+ int srcTab, /* Pull data from this table */
+ int nColumn, /* Number of columns in the source table */
+ ExprList *pOrderBy, /* If not NULL, sort results using this key */
+ int distinct, /* If >=0, make sure results are distinct */
+ SelectDest *pDest, /* How to dispose of the results */
+ int iContinue, /* Jump here to continue with next row */
+ int iBreak, /* Jump here to break out of the inner loop */
+ char *aff /* affinity string if eDest is SRT_Union */
+){
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ int hasDistinct; /* True if the DISTINCT keyword is present */
+ int regResult; /* Start of memory holding result set */
+ int eDest = pDest->eDest; /* How to dispose of results */
+ int iParm = pDest->iParm; /* First argument to disposal method */
+ int nResultCol; /* Number of result columns */
+
+ if( v==0 ) return;
+ assert( pEList!=0 );
+
+ /* If there was a LIMIT clause on the SELECT statement, then do the check
+ ** to see if this row should be output.
+ */
+ hasDistinct = distinct>=0 && pEList->nExpr>0;
+ if( pOrderBy==0 && !hasDistinct ){
+ codeOffset(v, p, iContinue);
+ }
+
+ /* Pull the requested columns.
+ */
+ if( nColumn>0 ){
+ nResultCol = nColumn;
+ }else{
+ nResultCol = pEList->nExpr;
+ }
+ if( pDest->iMem==0 ){
+ pDest->iMem = sqlite3GetTempRange(pParse, nResultCol);
+ pDest->nMem = nResultCol;
+ }else if( pDest->nMem!=nResultCol ){
+ /* This happens when two SELECTs of a compound SELECT have differing
+ ** numbers of result columns. The error message will be generated by
+ ** a higher-level routine. */
+ return;
+ }
+ regResult = pDest->iMem;
+ if( nColumn>0 ){
+ for(i=0; i<nColumn; i++){
+ sqlite3VdbeAddOp3(v, OP_Column, srcTab, i, regResult+i);
+ }
+ }else if( eDest!=SRT_Exists ){
+ /* If the destination is an EXISTS(...) expression, the actual
+ ** values returned by the SELECT are not required.
+ */
+ sqlite3ExprCodeExprList(pParse, pEList, regResult, eDest==SRT_Callback);
+ }
+ nColumn = nResultCol;
+
+ /* If the DISTINCT keyword was present on the SELECT statement
+ ** and this row has been seen before, then do not make this row
+ ** part of the result.
+ */
+ if( hasDistinct ){
+ assert( pEList!=0 );
+ assert( pEList->nExpr==nColumn );
+ codeDistinct(pParse, distinct, iContinue, nColumn, regResult);
+ if( pOrderBy==0 ){
+ codeOffset(v, p, iContinue);
+ }
+ }
+
+ if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){
+ return;
+ }
+
+ switch( eDest ){
+ /* In this mode, write each query result to the key of the temporary
+ ** table iParm.
+ */
+#ifndef SQLITE_OMIT_COMPOUND_SELECT
+ case SRT_Union: {
+ int r1;
+ r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nColumn, r1);
+ if( aff ){
+ sqlite3VdbeChangeP4(v, -1, aff, P4_STATIC);
+ }
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
+ sqlite3ReleaseTempReg(pParse, r1);
+ break;
+ }
+
+ /* Construct a record from the query result, but instead of
+ ** saving that record, use it as a key to delete elements from
+ ** the temporary table iParm.
+ */
+ case SRT_Except: {
+ sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nColumn);
+ break;
+ }
+#endif
+
+ /* Store the result as data using a unique key.
+ */
+ case SRT_Table:
+ case SRT_EphemTab: {
+ int r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nColumn, r1);
+ if( pOrderBy ){
+ pushOntoSorter(pParse, pOrderBy, p, r1);
+ }else{
+ int r2 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2);
+ sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2);
+ sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ sqlite3ReleaseTempReg(pParse, r2);
+ }
+ sqlite3ReleaseTempReg(pParse, r1);
+ break;
+ }
+
+#ifndef SQLITE_OMIT_SUBQUERY
+ /* If we are creating a set for an "expr IN (SELECT ...)" construct,
+ ** then there should be a single item on the stack. Write this
+ ** item into the set table with bogus data.
+ */
+ case SRT_Set: {
+ int addr2;
+
+ assert( nColumn==1 );
+ addr2 = sqlite3VdbeAddOp1(v, OP_IsNull, regResult);
+ p->affinity = sqlite3CompareAffinity(pEList->a[0].pExpr, pDest->affinity);
+ if( pOrderBy ){
+ /* At first glance you would think we could optimize out the
+ ** ORDER BY in this case since the order of entries in the set
+ ** does not matter. But there might be a LIMIT clause, in which
+ ** case the order does matter */
+ pushOntoSorter(pParse, pOrderBy, p, regResult);
+ }else{
+ int r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, 1, r1, &p->affinity, 1);
+ sqlite3ExprCacheAffinityChange(pParse, regResult, 1);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
+ sqlite3ReleaseTempReg(pParse, r1);
+ }
+ sqlite3VdbeJumpHere(v, addr2);
+ break;
+ }
+
+ /* If any row exist in the result set, record that fact and abort.
+ */
+ case SRT_Exists: {
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, iParm);
+ /* The LIMIT clause will terminate the loop for us */
+ break;
+ }
+
+ /* If this is a scalar select that is part of an expression, then
+ ** store the results in the appropriate memory cell and break out
+ ** of the scan loop.
+ */
+ case SRT_Mem: {
+ assert( nColumn==1 );
+ if( pOrderBy ){
+ pushOntoSorter(pParse, pOrderBy, p, regResult);
+ }else{
+ sqlite3ExprCodeMove(pParse, regResult, iParm);
+ /* The LIMIT clause will jump out of the loop for us */
+ }
+ break;
+ }
+#endif /* #ifndef SQLITE_OMIT_SUBQUERY */
+
+ /* Send the data to the callback function or to a subroutine. In the
+ ** case of a subroutine, the subroutine itself is responsible for
+ ** popping the data from the stack.
+ */
+ case SRT_Subroutine:
+ case SRT_Callback: {
+ if( pOrderBy ){
+ int r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nColumn, r1);
+ pushOntoSorter(pParse, pOrderBy, p, r1);
+ sqlite3ReleaseTempReg(pParse, r1);
+ }else if( eDest==SRT_Subroutine ){
+ sqlite3VdbeAddOp2(v, OP_Gosub, 0, iParm);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, nColumn);
+ sqlite3ExprCacheAffinityChange(pParse, regResult, nColumn);
+ }
+ break;
+ }
+
+#if !defined(SQLITE_OMIT_TRIGGER)
+ /* Discard the results. This is used for SELECT statements inside
+ ** the body of a TRIGGER. The purpose of such selects is to call
+ ** user-defined functions that have side effects. We do not care
+ ** about the actual results of the select.
+ */
+ default: {
+ assert( eDest==SRT_Discard );
+ break;
+ }
+#endif
+ }
+
+ /* Jump to the end of the loop if the LIMIT is reached.
+ */
+ if( p->iLimit>=0 && pOrderBy==0 ){
+ sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1);
+ sqlite3VdbeAddOp2(v, OP_IfZero, p->iLimit, iBreak);
+ }
+}
+
+/*
+** Given an expression list, generate a KeyInfo structure that records
+** the collating sequence for each expression in that expression list.
+**
+** If the ExprList is an ORDER BY or GROUP BY clause then the resulting
+** KeyInfo structure is appropriate for initializing a virtual index to
+** implement that clause. If the ExprList is the result set of a SELECT
+** then the KeyInfo structure is appropriate for initializing a virtual
+** index to implement a DISTINCT test.
+**
+** Space to hold the KeyInfo structure is obtain from malloc. The calling
+** function is responsible for seeing that this structure is eventually
+** freed. Add the KeyInfo structure to the P4 field of an opcode using
+** P4_KEYINFO_HANDOFF is the usual way of dealing with this.
+*/
+static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){
+ sqlite3 *db = pParse->db;
+ int nExpr;
+ KeyInfo *pInfo;
+ struct ExprList_item *pItem;
+ int i;
+
+ nExpr = pList->nExpr;
+ pInfo = sqlite3DbMallocZero(db, sizeof(*pInfo) + nExpr*(sizeof(CollSeq*)+1) );
+ if( pInfo ){
+ pInfo->aSortOrder = (u8*)&pInfo->aColl[nExpr];
+ pInfo->nField = nExpr;
+ pInfo->enc = ENC(db);
+ for(i=0, pItem=pList->a; i<nExpr; i++, pItem++){
+ CollSeq *pColl;
+ pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
+ if( !pColl ){
+ pColl = db->pDfltColl;
+ }
+ pInfo->aColl[i] = pColl;
+ pInfo->aSortOrder[i] = pItem->sortOrder;
+ }
+ }
+ return pInfo;
+}
+
+
+/*
+** If the inner loop was generated using a non-null pOrderBy argument,
+** then the results were placed in a sorter. After the loop is terminated
+** we need to run the sorter and output the results. The following
+** routine generates the code needed to do that.
+*/
+static void generateSortTail(
+ Parse *pParse, /* Parsing context */
+ Select *p, /* The SELECT statement */
+ Vdbe *v, /* Generate code into this VDBE */
+ int nColumn, /* Number of columns of data */
+ SelectDest *pDest /* Write the sorted results here */
+){
+ int brk = sqlite3VdbeMakeLabel(v);
+ int cont = sqlite3VdbeMakeLabel(v);
+ int addr;
+ int iTab;
+ int pseudoTab = 0;
+ ExprList *pOrderBy = p->pOrderBy;
+
+ int eDest = pDest->eDest;
+ int iParm = pDest->iParm;
+
+ int regRow;
+ int regRowid;
+
+ iTab = pOrderBy->iECursor;
+ if( eDest==SRT_Callback || eDest==SRT_Subroutine ){
+ pseudoTab = pParse->nTab++;
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, nColumn);
+ sqlite3VdbeAddOp2(v, OP_OpenPseudo, pseudoTab, eDest==SRT_Callback);
+ }
+ addr = 1 + sqlite3VdbeAddOp2(v, OP_Sort, iTab, brk);
+ codeOffset(v, p, cont);
+ regRow = sqlite3GetTempReg(pParse);
+ regRowid = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_Column, iTab, pOrderBy->nExpr + 1, regRow);
+ switch( eDest ){
+ case SRT_Table:
+ case SRT_EphemTab: {
+ sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, regRowid);
+ sqlite3VdbeAddOp3(v, OP_Insert, iParm, regRow, regRowid);
+ sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ break;
+ }
+#ifndef SQLITE_OMIT_SUBQUERY
+ case SRT_Set: {
+ int j1;
+ assert( nColumn==1 );
+ j1 = sqlite3VdbeAddOp1(v, OP_IsNull, regRow);
+ sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, 1, regRowid, &p->affinity, 1);
+ sqlite3ExprCacheAffinityChange(pParse, regRow, 1);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRowid);
+ sqlite3VdbeJumpHere(v, j1);
+ break;
+ }
+ case SRT_Mem: {
+ assert( nColumn==1 );
+ sqlite3ExprCodeMove(pParse, regRow, iParm);
+ /* The LIMIT clause will terminate the loop for us */
+ break;
+ }
+#endif
+ case SRT_Callback:
+ case SRT_Subroutine: {
+ int i;
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, regRowid);
+ sqlite3VdbeAddOp3(v, OP_Insert, pseudoTab, regRow, regRowid);
+ for(i=0; i<nColumn; i++){
+ assert( regRow!=pDest->iMem+i );
+ sqlite3VdbeAddOp3(v, OP_Column, pseudoTab, i, pDest->iMem+i);
+ }
+ if( eDest==SRT_Callback ){
+ sqlite3VdbeAddOp2(v, OP_ResultRow, pDest->iMem, nColumn);
+ sqlite3ExprCacheAffinityChange(pParse, pDest->iMem, nColumn);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Gosub, 0, iParm);
+ }
+ break;
+ }
+ default: {
+ /* Do nothing */
+ break;
+ }
+ }
+ sqlite3ReleaseTempReg(pParse, regRow);
+ sqlite3ReleaseTempReg(pParse, regRowid);
+
+ /* Jump to the end of the loop when the LIMIT is reached
+ */
+ if( p->iLimit>=0 ){
+ sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1);
+ sqlite3VdbeAddOp2(v, OP_IfZero, p->iLimit, brk);
+ }
+
+ /* The bottom of the loop
+ */
+ sqlite3VdbeResolveLabel(v, cont);
+ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr);
+ sqlite3VdbeResolveLabel(v, brk);
+ if( eDest==SRT_Callback || eDest==SRT_Subroutine ){
+ sqlite3VdbeAddOp2(v, OP_Close, pseudoTab, 0);
+ }
+
+}
+
+/*
+** Return a pointer to a string containing the 'declaration type' of the
+** expression pExpr. The string may be treated as static by the caller.
+**
+** The declaration type is the exact datatype definition extracted from the
+** original CREATE TABLE statement if the expression is a column. The
+** declaration type for a ROWID field is INTEGER. Exactly when an expression
+** is considered a column can be complex in the presence of subqueries. The
+** result-set expression in all of the following SELECT statements is
+** considered a column by this function.
+**
+** SELECT col FROM tbl;
+** SELECT (SELECT col FROM tbl;
+** SELECT (SELECT col FROM tbl);
+** SELECT abc FROM (SELECT col AS abc FROM tbl);
+**
+** The declaration type for any expression other than a column is NULL.
+*/
+static const char *columnType(
+ NameContext *pNC,
+ Expr *pExpr,
+ const char **pzOriginDb,
+ const char **pzOriginTab,
+ const char **pzOriginCol
+){
+ char const *zType = 0;
+ char const *zOriginDb = 0;
+ char const *zOriginTab = 0;
+ char const *zOriginCol = 0;
+ int j;
+ if( pExpr==0 || pNC->pSrcList==0 ) return 0;
+
+ switch( pExpr->op ){
+ case TK_AGG_COLUMN:
+ case TK_COLUMN: {
+ /* The expression is a column. Locate the table the column is being
+ ** extracted from in NameContext.pSrcList. This table may be real
+ ** database table or a subquery.
+ */
+ Table *pTab = 0; /* Table structure column is extracted from */
+ Select *pS = 0; /* Select the column is extracted from */
+ int iCol = pExpr->iColumn; /* Index of column in pTab */
+ while( pNC && !pTab ){
+ SrcList *pTabList = pNC->pSrcList;
+ for(j=0;j<pTabList->nSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++);
+ if( j<pTabList->nSrc ){
+ pTab = pTabList->a[j].pTab;
+ pS = pTabList->a[j].pSelect;
+ }else{
+ pNC = pNC->pNext;
+ }
+ }
+
+ if( pTab==0 ){
+ /* FIX ME:
+ ** This can occurs if you have something like "SELECT new.x;" inside
+ ** a trigger. In other words, if you reference the special "new"
+ ** table in the result set of a select. We do not have a good way
+ ** to find the actual table type, so call it "TEXT". This is really
+ ** something of a bug, but I do not know how to fix it.
+ **
+ ** This code does not produce the correct answer - it just prevents
+ ** a segfault. See ticket #1229.
+ */
+ zType = "TEXT";
+ break;
+ }
+
+ assert( pTab );
+ if( pS ){
+ /* The "table" is actually a sub-select or a view in the FROM clause
+ ** of the SELECT statement. Return the declaration type and origin
+ ** data for the result-set column of the sub-select.
+ */
+ if( iCol>=0 && iCol<pS->pEList->nExpr ){
+ /* If iCol is less than zero, then the expression requests the
+ ** rowid of the sub-select or view. This expression is legal (see
+ ** test case misc2.2.2) - it always evaluates to NULL.
+ */
+ NameContext sNC;
+ Expr *p = pS->pEList->a[iCol].pExpr;
+ sNC.pSrcList = pS->pSrc;
+ sNC.pNext = 0;
+ sNC.pParse = pNC->pParse;
+ zType = columnType(&sNC, p, &zOriginDb, &zOriginTab, &zOriginCol);
+ }
+ }else if( pTab->pSchema ){
+ /* A real table */
+ assert( !pS );
+ if( iCol<0 ) iCol = pTab->iPKey;
+ assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) );
+ if( iCol<0 ){
+ zType = "INTEGER";
+ zOriginCol = "rowid";
+ }else{
+ zType = pTab->aCol[iCol].zType;
+ zOriginCol = pTab->aCol[iCol].zName;
+ }
+ zOriginTab = pTab->zName;
+ if( pNC->pParse ){
+ int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema);
+ zOriginDb = pNC->pParse->db->aDb[iDb].zName;
+ }
+ }
+ break;
+ }
+#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_SELECT: {
+ /* The expression is a sub-select. Return the declaration type and
+ ** origin info for the single column in the result set of the SELECT
+ ** statement.
+ */
+ NameContext sNC;
+ Select *pS = pExpr->pSelect;
+ Expr *p = pS->pEList->a[0].pExpr;
+ sNC.pSrcList = pS->pSrc;
+ sNC.pNext = pNC;
+ sNC.pParse = pNC->pParse;
+ zType = columnType(&sNC, p, &zOriginDb, &zOriginTab, &zOriginCol);
+ break;
+ }
+#endif
+ }
+
+ if( pzOriginDb ){
+ assert( pzOriginTab && pzOriginCol );
+ *pzOriginDb = zOriginDb;
+ *pzOriginTab = zOriginTab;
+ *pzOriginCol = zOriginCol;
+ }
+ return zType;
+}
+
+/*
+** Generate code that will tell the VDBE the declaration types of columns
+** in the result set.
+*/
+static void generateColumnTypes(
+ Parse *pParse, /* Parser context */
+ SrcList *pTabList, /* List of tables */
+ ExprList *pEList /* Expressions defining the result set */
+){
+#ifndef SQLITE_OMIT_DECLTYPE
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ NameContext sNC;
+ sNC.pSrcList = pTabList;
+ sNC.pParse = pParse;
+ for(i=0; i<pEList->nExpr; i++){
+ Expr *p = pEList->a[i].pExpr;
+ const char *zType;
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+ const char *zOrigDb = 0;
+ const char *zOrigTab = 0;
+ const char *zOrigCol = 0;
+ zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol);
+
+ /* The vdbe must make its own copy of the column-type and other
+ ** column specific strings, in case the schema is reset before this
+ ** virtual machine is deleted.
+ */
+ sqlite3VdbeSetColName(v, i, COLNAME_DATABASE, zOrigDb, P4_TRANSIENT);
+ sqlite3VdbeSetColName(v, i, COLNAME_TABLE, zOrigTab, P4_TRANSIENT);
+ sqlite3VdbeSetColName(v, i, COLNAME_COLUMN, zOrigCol, P4_TRANSIENT);
+#else
+ zType = columnType(&sNC, p, 0, 0, 0);
+#endif
+ sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, P4_TRANSIENT);
+ }
+#endif /* SQLITE_OMIT_DECLTYPE */
+}
+
+/*
+** Generate code that will tell the VDBE the names of columns
+** in the result set. This information is used to provide the
+** azCol[] values in the callback.
+*/
+static void generateColumnNames(
+ Parse *pParse, /* Parser context */
+ SrcList *pTabList, /* List of tables */
+ ExprList *pEList /* Expressions defining the result set */
+){
+ Vdbe *v = pParse->pVdbe;
+ int i, j;
+ sqlite3 *db = pParse->db;
+ int fullNames, shortNames;
+
+#ifndef SQLITE_OMIT_EXPLAIN
+ /* If this is an EXPLAIN, skip this step */
+ if( pParse->explain ){
+ return;
+ }
+#endif
+
+ assert( v!=0 );
+ if( pParse->colNamesSet || v==0 || db->mallocFailed ) return;
+ pParse->colNamesSet = 1;
+ fullNames = (db->flags & SQLITE_FullColNames)!=0;
+ shortNames = (db->flags & SQLITE_ShortColNames)!=0;
+ sqlite3VdbeSetNumCols(v, pEList->nExpr);
+ for(i=0; i<pEList->nExpr; i++){
+ Expr *p;
+ p = pEList->a[i].pExpr;
+ if( p==0 ) continue;
+ if( pEList->a[i].zName ){
+ char *zName = pEList->a[i].zName;
+ sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, strlen(zName));
+ continue;
+ }
+ if( p->op==TK_COLUMN && pTabList ){
+ Table *pTab;
+ char *zCol;
+ int iCol = p->iColumn;
+ for(j=0; j<pTabList->nSrc && pTabList->a[j].iCursor!=p->iTable; j++){}
+ assert( j<pTabList->nSrc );
+ pTab = pTabList->a[j].pTab;
+ if( iCol<0 ) iCol = pTab->iPKey;
+ assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) );
+ if( iCol<0 ){
+ zCol = "rowid";
+ }else{
+ zCol = pTab->aCol[iCol].zName;
+ }
+ if( !shortNames && !fullNames && p->span.z && p->span.z[0] ){
+ sqlite3VdbeSetColName(v, i, COLNAME_NAME, (char*)p->span.z, p->span.n);
+ }else if( fullNames || (!shortNames && pTabList->nSrc>1) ){
+ char *zName = 0;
+ char *zTab;
+
+ zTab = pTabList->a[j].zAlias;
+ if( fullNames || zTab==0 ) zTab = pTab->zName;
+ sqlite3SetString(&zName, zTab, ".", zCol, (char*)0);
+ sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, P4_DYNAMIC);
+ }else{
+ sqlite3VdbeSetColName(v, i, COLNAME_NAME, zCol, strlen(zCol));
+ }
+ }else if( p->span.z && p->span.z[0] ){
+ sqlite3VdbeSetColName(v, i, COLNAME_NAME, (char*)p->span.z, p->span.n);
+ /* sqlite3VdbeCompressSpace(v, addr); */
+ }else{
+ char zName[30];
+ assert( p->op!=TK_COLUMN || pTabList==0 );
+ sqlite3_snprintf(sizeof(zName), zName, "column%d", i+1);
+ sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, 0);
+ }
+ }
+ generateColumnTypes(pParse, pTabList, pEList);
+}
+
+#ifndef SQLITE_OMIT_COMPOUND_SELECT
+/*
+** Name of the connection operator, used for error messages.
+*/
+static const char *selectOpName(int id){
+ char *z;
+ switch( id ){
+ case TK_ALL: z = "UNION ALL"; break;
+ case TK_INTERSECT: z = "INTERSECT"; break;
+ case TK_EXCEPT: z = "EXCEPT"; break;
+ default: z = "UNION"; break;
+ }
+ return z;
+}
+#endif /* SQLITE_OMIT_COMPOUND_SELECT */
+
+/*
+** Forward declaration
+*/
+static int prepSelectStmt(Parse*, Select*);
+
+/*
+** Given a SELECT statement, generate a Table structure that describes
+** the result set of that SELECT.
+*/
+SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){
+ Table *pTab;
+ int i, j;
+ ExprList *pEList;
+ Column *aCol, *pCol;
+ sqlite3 *db = pParse->db;
+
+ while( pSelect->pPrior ) pSelect = pSelect->pPrior;
+ if( prepSelectStmt(pParse, pSelect) ){
+ return 0;
+ }
+ if( sqlite3SelectResolve(pParse, pSelect, 0) ){
+ return 0;
+ }
+ pTab = sqlite3DbMallocZero(db, sizeof(Table) );
+ if( pTab==0 ){
+ return 0;
+ }
+ pTab->nRef = 1;
+ pTab->zName = zTabName ? sqlite3DbStrDup(db, zTabName) : 0;
+ pEList = pSelect->pEList;
+ pTab->nCol = pEList->nExpr;
+ assert( pTab->nCol>0 );
+ pTab->aCol = aCol = sqlite3DbMallocZero(db, sizeof(pTab->aCol[0])*pTab->nCol);
+ for(i=0, pCol=aCol; i<pTab->nCol; i++, pCol++){
+ Expr *p, *pR;
+ char *zType;
+ char *zName;
+ int nName;
+ CollSeq *pColl;
+ int cnt;
+ NameContext sNC;
+
+ /* Get an appropriate name for the column
+ */
+ p = pEList->a[i].pExpr;
+ assert( p->pRight==0 || p->pRight->token.z==0 || p->pRight->token.z[0]!=0 );
+ if( (zName = pEList->a[i].zName)!=0 ){
+ /* If the column contains an "AS <name>" phrase, use <name> as the name */
+ zName = sqlite3DbStrDup(db, zName);
+ }else if( p->op==TK_DOT
+ && (pR=p->pRight)!=0 && pR->token.z && pR->token.z[0] ){
+ /* For columns of the from A.B use B as the name */
+ zName = sqlite3MPrintf(db, "%T", &pR->token);
+ }else if( p->span.z && p->span.z[0] ){
+ /* Use the original text of the column expression as its name */
+ zName = sqlite3MPrintf(db, "%T", &p->span);
+ }else{
+ /* If all else fails, make up a name */
+ zName = sqlite3MPrintf(db, "column%d", i+1);
+ }
+ if( !zName || db->mallocFailed ){
+ db->mallocFailed = 1;
+ sqlite3_free(zName);
+ sqlite3DeleteTable(pTab);
+ return 0;
+ }
+ sqlite3Dequote(zName);
+
+ /* Make sure the column name is unique. If the name is not unique,
+ ** append a integer to the name so that it becomes unique.
+ */
+ nName = strlen(zName);
+ for(j=cnt=0; j<i; j++){
+ if( sqlite3StrICmp(aCol[j].zName, zName)==0 ){
+ zName[nName] = 0;
+ zName = sqlite3MPrintf(db, "%z:%d", zName, ++cnt);
+ j = -1;
+ if( zName==0 ) break;
+ }
+ }
+ pCol->zName = zName;
+
+ /* Get the typename, type affinity, and collating sequence for the
+ ** column.
+ */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pSrcList = pSelect->pSrc;
+ zType = sqlite3DbStrDup(db, columnType(&sNC, p, 0, 0, 0));
+ pCol->zType = zType;
+ pCol->affinity = sqlite3ExprAffinity(p);
+ pColl = sqlite3ExprCollSeq(pParse, p);
+ if( pColl ){
+ pCol->zColl = sqlite3DbStrDup(db, pColl->zName);
+ }
+ }
+ pTab->iPKey = -1;
+ return pTab;
+}
+
+/*
+** Prepare a SELECT statement for processing by doing the following
+** things:
+**
+** (1) Make sure VDBE cursor numbers have been assigned to every
+** element of the FROM clause.
+**
+** (2) Fill in the pTabList->a[].pTab fields in the SrcList that
+** defines FROM clause. When views appear in the FROM clause,
+** fill pTabList->a[].pSelect with a copy of the SELECT statement
+** that implements the view. A copy is made of the view's SELECT
+** statement so that we can freely modify or delete that statement
+** without worrying about messing up the presistent representation
+** of the view.
+**
+** (3) Add terms to the WHERE clause to accomodate the NATURAL keyword
+** on joins and the ON and USING clause of joins.
+**
+** (4) Scan the list of columns in the result set (pEList) looking
+** for instances of the "*" operator or the TABLE.* operator.
+** If found, expand each "*" to be every column in every table
+** and TABLE.* to be every column in TABLE.
+**
+** Return 0 on success. If there are problems, leave an error message
+** in pParse and return non-zero.
+*/
+static int prepSelectStmt(Parse *pParse, Select *p){
+ int i, j, k, rc;
+ SrcList *pTabList;
+ ExprList *pEList;
+ struct SrcList_item *pFrom;
+ sqlite3 *db = pParse->db;
+
+ if( p==0 || p->pSrc==0 || db->mallocFailed ){
+ return 1;
+ }
+ pTabList = p->pSrc;
+ pEList = p->pEList;
+
+ /* Make sure cursor numbers have been assigned to all entries in
+ ** the FROM clause of the SELECT statement.
+ */
+ sqlite3SrcListAssignCursors(pParse, p->pSrc);
+
+ /* Look up every table named in the FROM clause of the select. If
+ ** an entry of the FROM clause is a subquery instead of a table or view,
+ ** then create a transient table structure to describe the subquery.
+ */
+ for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){
+ Table *pTab;
+ if( pFrom->pTab!=0 ){
+ /* This statement has already been prepared. There is no need
+ ** to go further. */
+ assert( i==0 );
+ return 0;
+ }
+ if( pFrom->zName==0 ){
+#ifndef SQLITE_OMIT_SUBQUERY
+ /* A sub-query in the FROM clause of a SELECT */
+ assert( pFrom->pSelect!=0 );
+ if( pFrom->zAlias==0 ){
+ pFrom->zAlias =
+ sqlite3MPrintf(db, "sqlite_subquery_%p_", (void*)pFrom->pSelect);
+ }
+ assert( pFrom->pTab==0 );
+ pFrom->pTab = pTab =
+ sqlite3ResultSetOfSelect(pParse, pFrom->zAlias, pFrom->pSelect);
+ if( pTab==0 ){
+ return 1;
+ }
+ /* The isEphem flag indicates that the Table structure has been
+ ** dynamically allocated and may be freed at any time. In other words,
+ ** pTab is not pointing to a persistent table structure that defines
+ ** part of the schema. */
+ pTab->isEphem = 1;
+#endif
+ }else{
+ /* An ordinary table or view name in the FROM clause */
+ assert( pFrom->pTab==0 );
+ pFrom->pTab = pTab =
+ sqlite3LocateTable(pParse,0,pFrom->zName,pFrom->zDatabase);
+ if( pTab==0 ){
+ return 1;
+ }
+ pTab->nRef++;
+#if !defined(SQLITE_OMIT_VIEW) || !defined (SQLITE_OMIT_VIRTUALTABLE)
+ if( pTab->pSelect || IsVirtual(pTab) ){
+ /* We reach here if the named table is a really a view */
+ if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ return 1;
+ }
+ /* If pFrom->pSelect!=0 it means we are dealing with a
+ ** view within a view. The SELECT structure has already been
+ ** copied by the outer view so we can skip the copy step here
+ ** in the inner view.
+ */
+ if( pFrom->pSelect==0 ){
+ pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect);
+ }
+ }
+#endif
+ }
+ }
+
+ /* Process NATURAL keywords, and ON and USING clauses of joins.
+ */
+ if( sqliteProcessJoin(pParse, p) ) return 1;
+
+ /* For every "*" that occurs in the column list, insert the names of
+ ** all columns in all tables. And for every TABLE.* insert the names
+ ** of all columns in TABLE. The parser inserted a special expression
+ ** with the TK_ALL operator for each "*" that it found in the column list.
+ ** The following code just has to locate the TK_ALL expressions and expand
+ ** each one to the list of all columns in all tables.
+ **
+ ** The first loop just checks to see if there are any "*" operators
+ ** that need expanding.
+ */
+ for(k=0; k<pEList->nExpr; k++){
+ Expr *pE = pEList->a[k].pExpr;
+ if( pE->op==TK_ALL ) break;
+ if( pE->op==TK_DOT && pE->pRight && pE->pRight->op==TK_ALL
+ && pE->pLeft && pE->pLeft->op==TK_ID ) break;
+ }
+ rc = 0;
+ if( k<pEList->nExpr ){
+ /*
+ ** If we get here it means the result set contains one or more "*"
+ ** operators that need to be expanded. Loop through each expression
+ ** in the result set and expand them one by one.
+ */
+ struct ExprList_item *a = pEList->a;
+ ExprList *pNew = 0;
+ int flags = pParse->db->flags;
+ int longNames = (flags & SQLITE_FullColNames)!=0 &&
+ (flags & SQLITE_ShortColNames)==0;
+
+ for(k=0; k<pEList->nExpr; k++){
+ Expr *pE = a[k].pExpr;
+ if( pE->op!=TK_ALL &&
+ (pE->op!=TK_DOT || pE->pRight==0 || pE->pRight->op!=TK_ALL) ){
+ /* This particular expression does not need to be expanded.
+ */
+ pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr, 0);
+ if( pNew ){
+ pNew->a[pNew->nExpr-1].zName = a[k].zName;
+ }else{
+ rc = 1;
+ }
+ a[k].pExpr = 0;
+ a[k].zName = 0;
+ }else{
+ /* This expression is a "*" or a "TABLE.*" and needs to be
+ ** expanded. */
+ int tableSeen = 0; /* Set to 1 when TABLE matches */
+ char *zTName; /* text of name of TABLE */
+ if( pE->op==TK_DOT && pE->pLeft ){
+ zTName = sqlite3NameFromToken(db, &pE->pLeft->token);
+ }else{
+ zTName = 0;
+ }
+ for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){
+ Table *pTab = pFrom->pTab;
+ char *zTabName = pFrom->zAlias;
+ if( zTabName==0 || zTabName[0]==0 ){
+ zTabName = pTab->zName;
+ }
+ if( zTName && (zTabName==0 || zTabName[0]==0 ||
+ sqlite3StrICmp(zTName, zTabName)!=0) ){
+ continue;
+ }
+ tableSeen = 1;
+ for(j=0; j<pTab->nCol; j++){
+ Expr *pExpr, *pRight;
+ char *zName = pTab->aCol[j].zName;
+
+ /* If a column is marked as 'hidden' (currently only possible
+ ** for virtual tables), do not include it in the expanded
+ ** result-set list.
+ */
+ if( IsHiddenColumn(&pTab->aCol[j]) ){
+ assert(IsVirtual(pTab));
+ continue;
+ }
+
+ if( i>0 ){
+ struct SrcList_item *pLeft = &pTabList->a[i-1];
+ if( (pLeft[1].jointype & JT_NATURAL)!=0 &&
+ columnIndex(pLeft->pTab, zName)>=0 ){
+ /* In a NATURAL join, omit the join columns from the
+ ** table on the right */
+ continue;
+ }
+ if( sqlite3IdListIndex(pLeft[1].pUsing, zName)>=0 ){
+ /* In a join with a USING clause, omit columns in the
+ ** using clause from the table on the right. */
+ continue;
+ }
+ }
+ pRight = sqlite3PExpr(pParse, TK_ID, 0, 0, 0);
+ if( pRight==0 ) break;
+ setQuotedToken(pParse, &pRight->token, zName);
+ if( zTabName && (longNames || pTabList->nSrc>1) ){
+ Expr *pLeft = sqlite3PExpr(pParse, TK_ID, 0, 0, 0);
+ pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0);
+ if( pExpr==0 ) break;
+ setQuotedToken(pParse, &pLeft->token, zTabName);
+ setToken(&pExpr->span,
+ sqlite3MPrintf(db, "%s.%s", zTabName, zName));
+ pExpr->span.dyn = 1;
+ pExpr->token.z = 0;
+ pExpr->token.n = 0;
+ pExpr->token.dyn = 0;
+ }else{
+ pExpr = pRight;
+ pExpr->span = pExpr->token;
+ pExpr->span.dyn = 0;
+ }
+ if( longNames ){
+ pNew = sqlite3ExprListAppend(pParse, pNew, pExpr, &pExpr->span);
+ }else{
+ pNew = sqlite3ExprListAppend(pParse, pNew, pExpr, &pRight->token);
+ }
+ }
+ }
+ if( !tableSeen ){
+ if( zTName ){
+ sqlite3ErrorMsg(pParse, "no such table: %s", zTName);
+ }else{
+ sqlite3ErrorMsg(pParse, "no tables specified");
+ }
+ rc = 1;
+ }
+ sqlite3_free(zTName);
+ }
+ }
+ sqlite3ExprListDelete(pEList);
+ p->pEList = pNew;
+ }
+#if SQLITE_MAX_COLUMN
+ if( p->pEList && p->pEList->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
+ sqlite3ErrorMsg(pParse, "too many columns in result set");
+ rc = SQLITE_ERROR;
+ }
+#endif
+ if( db->mallocFailed ){
+ rc = SQLITE_NOMEM;
+ }
+ return rc;
+}
+
+/*
+** pE is a pointer to an expression which is a single term in
+** ORDER BY or GROUP BY clause.
+**
+** If pE evaluates to an integer constant i, then return i.
+** This is an indication to the caller that it should sort
+** by the i-th column of the result set.
+**
+** If pE is a well-formed expression and the SELECT statement
+** is not compound, then return 0. This indicates to the
+** caller that it should sort by the value of the ORDER BY
+** expression.
+**
+** If the SELECT is compound, then attempt to match pE against
+** result set columns in the left-most SELECT statement. Return
+** the index i of the matching column, as an indication to the
+** caller that it should sort by the i-th column. If there is
+** no match, return -1 and leave an error message in pParse.
+*/
+static int matchOrderByTermToExprList(
+ Parse *pParse, /* Parsing context for error messages */
+ Select *pSelect, /* The SELECT statement with the ORDER BY clause */
+ Expr *pE, /* The specific ORDER BY term */
+ int idx, /* When ORDER BY term is this */
+ int isCompound, /* True if this is a compound SELECT */
+ u8 *pHasAgg /* True if expression contains aggregate functions */
+){
+ int i; /* Loop counter */
+ ExprList *pEList; /* The columns of the result set */
+ NameContext nc; /* Name context for resolving pE */
+
+
+ /* If the term is an integer constant, return the value of that
+ ** constant */
+ pEList = pSelect->pEList;
+ if( sqlite3ExprIsInteger(pE, &i) ){
+ if( i<=0 ){
+ /* If i is too small, make it too big. That way the calling
+ ** function still sees a value that is out of range, but does
+ ** not confuse the column number with 0 or -1 result code.
+ */
+ i = pEList->nExpr+1;
+ }
+ return i;
+ }
+
+ /* If the term is a simple identifier that try to match that identifier
+ ** against a column name in the result set.
+ */
+ if( pE->op==TK_ID || (pE->op==TK_STRING && pE->token.z[0]!='\'') ){
+ sqlite3 *db = pParse->db;
+ char *zCol = sqlite3NameFromToken(db, &pE->token);
+ if( zCol==0 ){
+ return -1;
+ }
+ for(i=0; i<pEList->nExpr; i++){
+ char *zAs = pEList->a[i].zName;
+ if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){
+ sqlite3_free(zCol);
+ return i+1;
+ }
+ }
+ sqlite3_free(zCol);
+ }
+
+ /* Resolve all names in the ORDER BY term expression
+ */
+ memset(&nc, 0, sizeof(nc));
+ nc.pParse = pParse;
+ nc.pSrcList = pSelect->pSrc;
+ nc.pEList = pEList;
+ nc.allowAgg = 1;
+ nc.nErr = 0;
+ if( sqlite3ExprResolveNames(&nc, pE) ){
+ if( isCompound ){
+ sqlite3ErrorClear(pParse);
+ return 0;
+ }else{
+ return -1;
+ }
+ }
+ if( nc.hasAgg && pHasAgg ){
+ *pHasAgg = 1;
+ }
+
+ /* For a compound SELECT, we need to try to match the ORDER BY
+ ** expression against an expression in the result set
+ */
+ if( isCompound ){
+ for(i=0; i<pEList->nExpr; i++){
+ if( sqlite3ExprCompare(pEList->a[i].pExpr, pE) ){
+ return i+1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*
+** Analyze and ORDER BY or GROUP BY clause in a simple SELECT statement.
+** Return the number of errors seen.
+**
+** Every term of the ORDER BY or GROUP BY clause needs to be an
+** expression. If any expression is an integer constant, then
+** that expression is replaced by the corresponding
+** expression from the result set.
+*/
+static int processOrderGroupBy(
+ Parse *pParse, /* Parsing context. Leave error messages here */
+ Select *pSelect, /* The SELECT statement containing the clause */
+ ExprList *pOrderBy, /* The ORDER BY or GROUP BY clause to be processed */
+ int isOrder, /* 1 for ORDER BY. 0 for GROUP BY */
+ u8 *pHasAgg /* Set to TRUE if any term contains an aggregate */
+){
+ int i;
+ sqlite3 *db = pParse->db;
+ ExprList *pEList;
+
+ if( pOrderBy==0 || pParse->db->mallocFailed ) return 0;
+#if SQLITE_MAX_COLUMN
+ if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
+ const char *zType = isOrder ? "ORDER" : "GROUP";
+ sqlite3ErrorMsg(pParse, "too many terms in %s BY clause", zType);
+ return 1;
+ }
+#endif
+ pEList = pSelect->pEList;
+ if( pEList==0 ){
+ return 0;
+ }
+ for(i=0; i<pOrderBy->nExpr; i++){
+ int iCol;
+ Expr *pE = pOrderBy->a[i].pExpr;
+ iCol = matchOrderByTermToExprList(pParse, pSelect, pE, i+1, 0, pHasAgg);
+ if( iCol<0 ){
+ return 1;
+ }
+ if( iCol>pEList->nExpr ){
+ const char *zType = isOrder ? "ORDER" : "GROUP";
+ sqlite3ErrorMsg(pParse,
+ "%r %s BY term out of range - should be "
+ "between 1 and %d", i+1, zType, pEList->nExpr);
+ return 1;
+ }
+ if( iCol>0 ){
+ CollSeq *pColl = pE->pColl;
+ int flags = pE->flags & EP_ExpCollate;
+ sqlite3ExprDelete(pE);
+ pE = sqlite3ExprDup(db, pEList->a[iCol-1].pExpr);
+ pOrderBy->a[i].pExpr = pE;
+ if( pE && pColl && flags ){
+ pE->pColl = pColl;
+ pE->flags |= flags;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Analyze and ORDER BY or GROUP BY clause in a SELECT statement. Return
+** the number of errors seen.
+**
+** The processing depends on whether the SELECT is simple or compound.
+** For a simple SELECT statement, evry term of the ORDER BY or GROUP BY
+** clause needs to be an expression. If any expression is an integer
+** constant, then that expression is replaced by the corresponding
+** expression from the result set.
+**
+** For compound SELECT statements, every expression needs to be of
+** type TK_COLUMN with a iTable value as given in the 4th parameter.
+** If any expression is an integer, that becomes the column number.
+** Otherwise, match the expression against result set columns from
+** the left-most SELECT.
+*/
+static int processCompoundOrderBy(
+ Parse *pParse, /* Parsing context. Leave error messages here */
+ Select *pSelect, /* The SELECT statement containing the ORDER BY */
+ int iTable /* Output table for compound SELECT statements */
+){
+ int i;
+ ExprList *pOrderBy;
+ ExprList *pEList;
+ sqlite3 *db;
+ int moreToDo = 1;
+
+ pOrderBy = pSelect->pOrderBy;
+ if( pOrderBy==0 ) return 0;
+ db = pParse->db;
+#if SQLITE_MAX_COLUMN
+ if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
+ sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause");
+ return 1;
+ }
+#endif
+ for(i=0; i<pOrderBy->nExpr; i++){
+ pOrderBy->a[i].done = 0;
+ }
+ while( pSelect->pPrior ){
+ pSelect = pSelect->pPrior;
+ }
+ while( pSelect && moreToDo ){
+ moreToDo = 0;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ int iCol = -1;
+ Expr *pE, *pDup;
+ if( pOrderBy->a[i].done ) continue;
+ pE = pOrderBy->a[i].pExpr;
+ pDup = sqlite3ExprDup(db, pE);
+ if( !db->mallocFailed ){
+ assert(pDup);
+ iCol = matchOrderByTermToExprList(pParse, pSelect, pDup, i+1, 1, 0);
+ }
+ sqlite3ExprDelete(pDup);
+ if( iCol<0 ){
+ return 1;
+ }
+ pEList = pSelect->pEList;
+ if( pEList==0 ){
+ return 1;
+ }
+ if( iCol>pEList->nExpr ){
+ sqlite3ErrorMsg(pParse,
+ "%r ORDER BY term out of range - should be "
+ "between 1 and %d", i+1, pEList->nExpr);
+ return 1;
+ }
+ if( iCol>0 ){
+ pE->op = TK_COLUMN;
+ pE->iTable = iTable;
+ pE->iAgg = -1;
+ pE->iColumn = iCol-1;
+ pE->pTab = 0;
+ pOrderBy->a[i].done = 1;
+ }else{
+ moreToDo = 1;
+ }
+ }
+ pSelect = pSelect->pNext;
+ }
+ for(i=0; i<pOrderBy->nExpr; i++){
+ if( pOrderBy->a[i].done==0 ){
+ sqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any "
+ "column in the result set", i+1);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** Get a VDBE for the given parser context. Create a new one if necessary.
+** If an error occurs, return NULL and leave a message in pParse.
+*/
+SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){
+ Vdbe *v = pParse->pVdbe;
+ if( v==0 ){
+ v = pParse->pVdbe = sqlite3VdbeCreate(pParse->db);
+#ifndef SQLITE_OMIT_TRACE
+ if( v ){
+ sqlite3VdbeAddOp0(v, OP_Trace);
+ }
+#endif
+ }
+ return v;
+}
+
+
+/*
+** Compute the iLimit and iOffset fields of the SELECT based on the
+** pLimit and pOffset expressions. pLimit and pOffset hold the expressions
+** that appear in the original SQL statement after the LIMIT and OFFSET
+** keywords. Or NULL if those keywords are omitted. iLimit and iOffset
+** are the integer memory register numbers for counters used to compute
+** the limit and offset. If there is no limit and/or offset, then
+** iLimit and iOffset are negative.
+**
+** This routine changes the values of iLimit and iOffset only if
+** a limit or offset is defined by pLimit and pOffset. iLimit and
+** iOffset should have been preset to appropriate default values
+** (usually but not always -1) prior to calling this routine.
+** Only if pLimit!=0 or pOffset!=0 do the limit registers get
+** redefined. The UNION ALL operator uses this property to force
+** the reuse of the same limit and offset registers across multiple
+** SELECT statements.
+*/
+static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){
+ Vdbe *v = 0;
+ int iLimit = 0;
+ int iOffset;
+ int addr1;
+
+ /*
+ ** "LIMIT -1" always shows all rows. There is some
+ ** contraversy about what the correct behavior should be.
+ ** The current implementation interprets "LIMIT 0" to mean
+ ** no rows.
+ */
+ if( p->pLimit ){
+ p->iLimit = iLimit = ++pParse->nMem;
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+ sqlite3ExprCode(pParse, p->pLimit, iLimit);
+ sqlite3VdbeAddOp1(v, OP_MustBeInt, iLimit);
+ VdbeComment((v, "LIMIT counter"));
+ sqlite3VdbeAddOp2(v, OP_IfZero, iLimit, iBreak);
+ }
+ if( p->pOffset ){
+ p->iOffset = iOffset = ++pParse->nMem;
+ if( p->pLimit ){
+ pParse->nMem++; /* Allocate an extra register for limit+offset */
+ }
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+ sqlite3ExprCode(pParse, p->pOffset, iOffset);
+ sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset);
+ VdbeComment((v, "OFFSET counter"));
+ addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iOffset);
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, iOffset);
+ sqlite3VdbeJumpHere(v, addr1);
+ if( p->pLimit ){
+ sqlite3VdbeAddOp3(v, OP_Add, iLimit, iOffset, iOffset+1);
+ VdbeComment((v, "LIMIT+OFFSET"));
+ addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iLimit);
+ sqlite3VdbeAddOp2(v, OP_Integer, -1, iOffset+1);
+ sqlite3VdbeJumpHere(v, addr1);
+ }
+ }
+}
+
+/*
+** Allocate a virtual index to use for sorting.
+*/
+static void createSortingIndex(Parse *pParse, Select *p, ExprList *pOrderBy){
+ if( pOrderBy ){
+ int addr;
+ assert( pOrderBy->iECursor==0 );
+ pOrderBy->iECursor = pParse->nTab++;
+ addr = sqlite3VdbeAddOp2(pParse->pVdbe, OP_OpenEphemeral,
+ pOrderBy->iECursor, pOrderBy->nExpr+1);
+ assert( p->addrOpenEphm[2] == -1 );
+ p->addrOpenEphm[2] = addr;
+ }
+}
+
+#ifndef SQLITE_OMIT_COMPOUND_SELECT
+/*
+** Return the appropriate collating sequence for the iCol-th column of
+** the result set for the compound-select statement "p". Return NULL if
+** the column has no default collating sequence.
+**
+** The collating sequence for the compound select is taken from the
+** left-most term of the select that has a collating sequence.
+*/
+static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){
+ CollSeq *pRet;
+ if( p->pPrior ){
+ pRet = multiSelectCollSeq(pParse, p->pPrior, iCol);
+ }else{
+ pRet = 0;
+ }
+ if( pRet==0 ){
+ pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr);
+ }
+ return pRet;
+}
+#endif /* SQLITE_OMIT_COMPOUND_SELECT */
+
+#ifndef SQLITE_OMIT_COMPOUND_SELECT
+/*
+** This routine is called to process a query that is really the union
+** or intersection of two or more separate queries.
+**
+** "p" points to the right-most of the two queries. the query on the
+** left is p->pPrior. The left query could also be a compound query
+** in which case this routine will be called recursively.
+**
+** The results of the total query are to be written into a destination
+** of type eDest with parameter iParm.
+**
+** Example 1: Consider a three-way compound SQL statement.
+**
+** SELECT a FROM t1 UNION SELECT b FROM t2 UNION SELECT c FROM t3
+**
+** This statement is parsed up as follows:
+**
+** SELECT c FROM t3
+** |
+** `-----> SELECT b FROM t2
+** |
+** `------> SELECT a FROM t1
+**
+** The arrows in the diagram above represent the Select.pPrior pointer.
+** So if this routine is called with p equal to the t3 query, then
+** pPrior will be the t2 query. p->op will be TK_UNION in this case.
+**
+** Notice that because of the way SQLite parses compound SELECTs, the
+** individual selects always group from left to right.
+*/
+static int multiSelect(
+ Parse *pParse, /* Parsing context */
+ Select *p, /* The right-most of SELECTs to be coded */
+ SelectDest *pDest, /* What to do with query results */
+ char *aff /* If eDest is SRT_Union, the affinity string */
+){
+ int rc = SQLITE_OK; /* Success code from a subroutine */
+ Select *pPrior; /* Another SELECT immediately to our left */
+ Vdbe *v; /* Generate code to this VDBE */
+ int nCol; /* Number of columns in the result set */
+ ExprList *pOrderBy; /* The ORDER BY clause on p */
+ int aSetP2[2]; /* Set P2 value of these op to number of columns */
+ int nSetP2 = 0; /* Number of slots in aSetP2[] used */
+ SelectDest dest; /* Alternative data destination */
+
+ dest = *pDest;
+
+ /* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only
+ ** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT.
+ */
+ if( p==0 || p->pPrior==0 ){
+ rc = 1;
+ goto multi_select_end;
+ }
+ pPrior = p->pPrior;
+ assert( pPrior->pRightmost!=pPrior );
+ assert( pPrior->pRightmost==p->pRightmost );
+ if( pPrior->pOrderBy ){
+ sqlite3ErrorMsg(pParse,"ORDER BY clause should come after %s not before",
+ selectOpName(p->op));
+ rc = 1;
+ goto multi_select_end;
+ }
+ if( pPrior->pLimit ){
+ sqlite3ErrorMsg(pParse,"LIMIT clause should come after %s not before",
+ selectOpName(p->op));
+ rc = 1;
+ goto multi_select_end;
+ }
+
+ /* Make sure we have a valid query engine. If not, create a new one.
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ){
+ rc = 1;
+ goto multi_select_end;
+ }
+
+ /* Create the destination temporary table if necessary
+ */
+ if( dest.eDest==SRT_EphemTab ){
+ assert( p->pEList );
+ assert( nSetP2<sizeof(aSetP2)/sizeof(aSetP2[0]) );
+ aSetP2[nSetP2++] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iParm, 0);
+ dest.eDest = SRT_Table;
+ }
+
+ /* Generate code for the left and right SELECT statements.
+ */
+ pOrderBy = p->pOrderBy;
+ switch( p->op ){
+ case TK_ALL: {
+ if( pOrderBy==0 ){
+ int addr = 0;
+ assert( !pPrior->pLimit );
+ pPrior->pLimit = p->pLimit;
+ pPrior->pOffset = p->pOffset;
+ rc = sqlite3Select(pParse, pPrior, &dest, 0, 0, 0, aff);
+ p->pLimit = 0;
+ p->pOffset = 0;
+ if( rc ){
+ goto multi_select_end;
+ }
+ p->pPrior = 0;
+ p->iLimit = pPrior->iLimit;
+ p->iOffset = pPrior->iOffset;
+ if( p->iLimit>=0 ){
+ addr = sqlite3VdbeAddOp1(v, OP_IfZero, p->iLimit);
+ VdbeComment((v, "Jump ahead if LIMIT reached"));
+ }
+ rc = sqlite3Select(pParse, p, &dest, 0, 0, 0, aff);
+ p->pPrior = pPrior;
+ if( rc ){
+ goto multi_select_end;
+ }
+ if( addr ){
+ sqlite3VdbeJumpHere(v, addr);
+ }
+ break;
+ }
+ /* For UNION ALL ... ORDER BY fall through to the next case */
+ }
+ case TK_EXCEPT:
+ case TK_UNION: {
+ int unionTab; /* Cursor number of the temporary table holding result */
+ int op = 0; /* One of the SRT_ operations to apply to self */
+ int priorOp; /* The SRT_ operation to apply to prior selects */
+ Expr *pLimit, *pOffset; /* Saved values of p->nLimit and p->nOffset */
+ int addr;
+ SelectDest uniondest;
+
+ priorOp = p->op==TK_ALL ? SRT_Table : SRT_Union;
+ if( dest.eDest==priorOp && pOrderBy==0 && !p->pLimit && !p->pOffset ){
+ /* We can reuse a temporary table generated by a SELECT to our
+ ** right.
+ */
+ unionTab = dest.iParm;
+ }else{
+ /* We will need to create our own temporary table to hold the
+ ** intermediate results.
+ */
+ unionTab = pParse->nTab++;
+ if( processCompoundOrderBy(pParse, p, unionTab) ){
+ rc = 1;
+ goto multi_select_end;
+ }
+ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0);
+ if( priorOp==SRT_Table ){
+ assert( nSetP2<sizeof(aSetP2)/sizeof(aSetP2[0]) );
+ aSetP2[nSetP2++] = addr;
+ }else{
+ assert( p->addrOpenEphm[0] == -1 );
+ p->addrOpenEphm[0] = addr;
+ p->pRightmost->usesEphm = 1;
+ }
+ createSortingIndex(pParse, p, pOrderBy);
+ assert( p->pEList );
+ }
+
+ /* Code the SELECT statements to our left
+ */
+ assert( !pPrior->pOrderBy );
+ sqlite3SelectDestInit(&uniondest, priorOp, unionTab);
+ rc = sqlite3Select(pParse, pPrior, &uniondest, 0, 0, 0, aff);
+ if( rc ){
+ goto multi_select_end;
+ }
+
+ /* Code the current SELECT statement
+ */
+ switch( p->op ){
+ case TK_EXCEPT: op = SRT_Except; break;
+ case TK_UNION: op = SRT_Union; break;
+ case TK_ALL: op = SRT_Table; break;
+ }
+ p->pPrior = 0;
+ p->pOrderBy = 0;
+ p->disallowOrderBy = pOrderBy!=0;
+ pLimit = p->pLimit;
+ p->pLimit = 0;
+ pOffset = p->pOffset;
+ p->pOffset = 0;
+ uniondest.eDest = op;
+ rc = sqlite3Select(pParse, p, &uniondest, 0, 0, 0, aff);
+ /* Query flattening in sqlite3Select() might refill p->pOrderBy.
+ ** Be sure to delete p->pOrderBy, therefore, to avoid a memory leak. */
+ sqlite3ExprListDelete(p->pOrderBy);
+ p->pPrior = pPrior;
+ p->pOrderBy = pOrderBy;
+ sqlite3ExprDelete(p->pLimit);
+ p->pLimit = pLimit;
+ p->pOffset = pOffset;
+ p->iLimit = -1;
+ p->iOffset = -1;
+ if( rc ){
+ goto multi_select_end;
+ }
+
+
+ /* Convert the data in the temporary table into whatever form
+ ** it is that we currently need.
+ */
+ if( dest.eDest!=priorOp || unionTab!=dest.iParm ){
+ int iCont, iBreak, iStart;
+ assert( p->pEList );
+ if( dest.eDest==SRT_Callback ){
+ Select *pFirst = p;
+ while( pFirst->pPrior ) pFirst = pFirst->pPrior;
+ generateColumnNames(pParse, 0, pFirst->pEList);
+ }
+ iBreak = sqlite3VdbeMakeLabel(v);
+ iCont = sqlite3VdbeMakeLabel(v);
+ computeLimitRegisters(pParse, p, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak);
+ iStart = sqlite3VdbeCurrentAddr(v);
+ selectInnerLoop(pParse, p, p->pEList, unionTab, p->pEList->nExpr,
+ pOrderBy, -1, &dest, iCont, iBreak, 0);
+ sqlite3VdbeResolveLabel(v, iCont);
+ sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0);
+ }
+ break;
+ }
+ case TK_INTERSECT: {
+ int tab1, tab2;
+ int iCont, iBreak, iStart;
+ Expr *pLimit, *pOffset;
+ int addr;
+ SelectDest intersectdest;
+ int r1;
+
+ /* INTERSECT is different from the others since it requires
+ ** two temporary tables. Hence it has its own case. Begin
+ ** by allocating the tables we will need.
+ */
+ tab1 = pParse->nTab++;
+ tab2 = pParse->nTab++;
+ if( processCompoundOrderBy(pParse, p, tab1) ){
+ rc = 1;
+ goto multi_select_end;
+ }
+ createSortingIndex(pParse, p, pOrderBy);
+
+ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0);
+ assert( p->addrOpenEphm[0] == -1 );
+ p->addrOpenEphm[0] = addr;
+ p->pRightmost->usesEphm = 1;
+ assert( p->pEList );
+
+ /* Code the SELECTs to our left into temporary table "tab1".
+ */
+ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
+ rc = sqlite3Select(pParse, pPrior, &intersectdest, 0, 0, 0, aff);
+ if( rc ){
+ goto multi_select_end;
+ }
+
+ /* Code the current SELECT into temporary table "tab2"
+ */
+ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0);
+ assert( p->addrOpenEphm[1] == -1 );
+ p->addrOpenEphm[1] = addr;
+ p->pPrior = 0;
+ pLimit = p->pLimit;
+ p->pLimit = 0;
+ pOffset = p->pOffset;
+ p->pOffset = 0;
+ intersectdest.iParm = tab2;
+ rc = sqlite3Select(pParse, p, &intersectdest, 0, 0, 0, aff);
+ p->pPrior = pPrior;
+ sqlite3ExprDelete(p->pLimit);
+ p->pLimit = pLimit;
+ p->pOffset = pOffset;
+ if( rc ){
+ goto multi_select_end;
+ }
+
+ /* Generate code to take the intersection of the two temporary
+ ** tables.
+ */
+ assert( p->pEList );
+ if( dest.eDest==SRT_Callback ){
+ Select *pFirst = p;
+ while( pFirst->pPrior ) pFirst = pFirst->pPrior;
+ generateColumnNames(pParse, 0, pFirst->pEList);
+ }
+ iBreak = sqlite3VdbeMakeLabel(v);
+ iCont = sqlite3VdbeMakeLabel(v);
+ computeLimitRegisters(pParse, p, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak);
+ r1 = sqlite3GetTempReg(pParse);
+ iStart = sqlite3VdbeAddOp2(v, OP_RowKey, tab1, r1);
+ sqlite3VdbeAddOp3(v, OP_NotFound, tab2, iCont, r1);
+ sqlite3ReleaseTempReg(pParse, r1);
+ selectInnerLoop(pParse, p, p->pEList, tab1, p->pEList->nExpr,
+ pOrderBy, -1, &dest, iCont, iBreak, 0);
+ sqlite3VdbeResolveLabel(v, iCont);
+ sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp2(v, OP_Close, tab2, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, tab1, 0);
+ break;
+ }
+ }
+
+ /* Make sure all SELECTs in the statement have the same number of elements
+ ** in their result sets.
+ */
+ assert( p->pEList && pPrior->pEList );
+ if( p->pEList->nExpr!=pPrior->pEList->nExpr ){
+ sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
+ " do not have the same number of result columns", selectOpName(p->op));
+ rc = 1;
+ goto multi_select_end;
+ }
+
+ /* Set the number of columns in temporary tables
+ */
+ nCol = p->pEList->nExpr;
+ while( nSetP2 ){
+ sqlite3VdbeChangeP2(v, aSetP2[--nSetP2], nCol);
+ }
+
+ /* Compute collating sequences used by either the ORDER BY clause or
+ ** by any temporary tables needed to implement the compound select.
+ ** Attach the KeyInfo structure to all temporary tables. Invoke the
+ ** ORDER BY processing if there is an ORDER BY clause.
+ **
+ ** This section is run by the right-most SELECT statement only.
+ ** SELECT statements to the left always skip this part. The right-most
+ ** SELECT might also skip this part if it has no ORDER BY clause and
+ ** no temp tables are required.
+ */
+ if( pOrderBy || p->usesEphm ){
+ int i; /* Loop counter */
+ KeyInfo *pKeyInfo; /* Collating sequence for the result set */
+ Select *pLoop; /* For looping through SELECT statements */
+ int nKeyCol; /* Number of entries in pKeyInfo->aCol[] */
+ CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */
+ CollSeq **aCopy; /* A copy of pKeyInfo->aColl[] */
+
+ assert( p->pRightmost==p );
+ nKeyCol = nCol + (pOrderBy ? pOrderBy->nExpr : 0);
+ pKeyInfo = sqlite3DbMallocZero(pParse->db,
+ sizeof(*pKeyInfo)+nKeyCol*(sizeof(CollSeq*) + 1));
+ if( !pKeyInfo ){
+ rc = SQLITE_NOMEM;
+ goto multi_select_end;
+ }
+
+ pKeyInfo->enc = ENC(pParse->db);
+ pKeyInfo->nField = nCol;
+
+ for(i=0, apColl=pKeyInfo->aColl; i<nCol; i++, apColl++){
+ *apColl = multiSelectCollSeq(pParse, p, i);
+ if( 0==*apColl ){
+ *apColl = pParse->db->pDfltColl;
+ }
+ }
+
+ for(pLoop=p; pLoop; pLoop=pLoop->pPrior){
+ for(i=0; i<2; i++){
+ int addr = pLoop->addrOpenEphm[i];
+ if( addr<0 ){
+ /* If [0] is unused then [1] is also unused. So we can
+ ** always safely abort as soon as the first unused slot is found */
+ assert( pLoop->addrOpenEphm[1]<0 );
+ break;
+ }
+ sqlite3VdbeChangeP2(v, addr, nCol);
+ sqlite3VdbeChangeP4(v, addr, (char*)pKeyInfo, P4_KEYINFO);
+ pLoop->addrOpenEphm[i] = -1;
+ }
+ }
+
+ if( pOrderBy ){
+ struct ExprList_item *pOTerm = pOrderBy->a;
+ int nOrderByExpr = pOrderBy->nExpr;
+ int addr;
+ u8 *pSortOrder;
+
+ /* Reuse the same pKeyInfo for the ORDER BY as was used above for
+ ** the compound select statements. Except we have to change out the
+ ** pKeyInfo->aColl[] values. Some of the aColl[] values will be
+ ** reused when constructing the pKeyInfo for the ORDER BY, so make
+ ** a copy. Sufficient space to hold both the nCol entries for
+ ** the compound select and the nOrderbyExpr entries for the ORDER BY
+ ** was allocated above. But we need to move the compound select
+ ** entries out of the way before constructing the ORDER BY entries.
+ ** Move the compound select entries into aCopy[] where they can be
+ ** accessed and reused when constructing the ORDER BY entries.
+ ** Because nCol might be greater than or less than nOrderByExpr
+ ** we have to use memmove() when doing the copy.
+ */
+ aCopy = &pKeyInfo->aColl[nOrderByExpr];
+ pSortOrder = pKeyInfo->aSortOrder = (u8*)&aCopy[nCol];
+ memmove(aCopy, pKeyInfo->aColl, nCol*sizeof(CollSeq*));
+
+ apColl = pKeyInfo->aColl;
+ for(i=0; i<nOrderByExpr; i++, pOTerm++, apColl++, pSortOrder++){
+ Expr *pExpr = pOTerm->pExpr;
+ if( (pExpr->flags & EP_ExpCollate) ){
+ assert( pExpr->pColl!=0 );
+ *apColl = pExpr->pColl;
+ }else{
+ *apColl = aCopy[pExpr->iColumn];
+ }
+ *pSortOrder = pOTerm->sortOrder;
+ }
+ assert( p->pRightmost==p );
+ assert( p->addrOpenEphm[2]>=0 );
+ addr = p->addrOpenEphm[2];
+ sqlite3VdbeChangeP2(v, addr, p->pOrderBy->nExpr+2);
+ pKeyInfo->nField = nOrderByExpr;
+ sqlite3VdbeChangeP4(v, addr, (char*)pKeyInfo, P4_KEYINFO_HANDOFF);
+ pKeyInfo = 0;
+ generateSortTail(pParse, p, v, p->pEList->nExpr, &dest);
+ }
+
+ sqlite3_free(pKeyInfo);
+ }
+
+multi_select_end:
+ pDest->iMem = dest.iMem;
+ pDest->nMem = dest.nMem;
+ return rc;
+}
+#endif /* SQLITE_OMIT_COMPOUND_SELECT */
+
+#ifndef SQLITE_OMIT_VIEW
+/* Forward Declarations */
+static void substExprList(sqlite3*, ExprList*, int, ExprList*);
+static void substSelect(sqlite3*, Select *, int, ExprList *);
+
+/*
+** Scan through the expression pExpr. Replace every reference to
+** a column in table number iTable with a copy of the iColumn-th
+** entry in pEList. (But leave references to the ROWID column
+** unchanged.)
+**
+** This routine is part of the flattening procedure. A subquery
+** whose result set is defined by pEList appears as entry in the
+** FROM clause of a SELECT such that the VDBE cursor assigned to that
+** FORM clause entry is iTable. This routine make the necessary
+** changes to pExpr so that it refers directly to the source table
+** of the subquery rather the result set of the subquery.
+*/
+static void substExpr(
+ sqlite3 *db, /* Report malloc errors to this connection */
+ Expr *pExpr, /* Expr in which substitution occurs */
+ int iTable, /* Table to be substituted */
+ ExprList *pEList /* Substitute expressions */
+){
+ if( pExpr==0 ) return;
+ if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){
+ if( pExpr->iColumn<0 ){
+ pExpr->op = TK_NULL;
+ }else{
+ Expr *pNew;
+ assert( pEList!=0 && pExpr->iColumn<pEList->nExpr );
+ assert( pExpr->pLeft==0 && pExpr->pRight==0 && pExpr->pList==0 );
+ pNew = pEList->a[pExpr->iColumn].pExpr;
+ assert( pNew!=0 );
+ pExpr->op = pNew->op;
+ assert( pExpr->pLeft==0 );
+ pExpr->pLeft = sqlite3ExprDup(db, pNew->pLeft);
+ assert( pExpr->pRight==0 );
+ pExpr->pRight = sqlite3ExprDup(db, pNew->pRight);
+ assert( pExpr->pList==0 );
+ pExpr->pList = sqlite3ExprListDup(db, pNew->pList);
+ pExpr->iTable = pNew->iTable;
+ pExpr->pTab = pNew->pTab;
+ pExpr->iColumn = pNew->iColumn;
+ pExpr->iAgg = pNew->iAgg;
+ sqlite3TokenCopy(db, &pExpr->token, &pNew->token);
+ sqlite3TokenCopy(db, &pExpr->span, &pNew->span);
+ pExpr->pSelect = sqlite3SelectDup(db, pNew->pSelect);
+ pExpr->flags = pNew->flags;
+ }
+ }else{
+ substExpr(db, pExpr->pLeft, iTable, pEList);
+ substExpr(db, pExpr->pRight, iTable, pEList);
+ substSelect(db, pExpr->pSelect, iTable, pEList);
+ substExprList(db, pExpr->pList, iTable, pEList);
+ }
+}
+static void substExprList(
+ sqlite3 *db, /* Report malloc errors here */
+ ExprList *pList, /* List to scan and in which to make substitutes */
+ int iTable, /* Table to be substituted */
+ ExprList *pEList /* Substitute values */
+){
+ int i;
+ if( pList==0 ) return;
+ for(i=0; i<pList->nExpr; i++){
+ substExpr(db, pList->a[i].pExpr, iTable, pEList);
+ }
+}
+static void substSelect(
+ sqlite3 *db, /* Report malloc errors here */
+ Select *p, /* SELECT statement in which to make substitutions */
+ int iTable, /* Table to be replaced */
+ ExprList *pEList /* Substitute values */
+){
+ if( !p ) return;
+ substExprList(db, p->pEList, iTable, pEList);
+ substExprList(db, p->pGroupBy, iTable, pEList);
+ substExprList(db, p->pOrderBy, iTable, pEList);
+ substExpr(db, p->pHaving, iTable, pEList);
+ substExpr(db, p->pWhere, iTable, pEList);
+ substSelect(db, p->pPrior, iTable, pEList);
+}
+#endif /* !defined(SQLITE_OMIT_VIEW) */
+
+#ifndef SQLITE_OMIT_VIEW
+/*
+** This routine attempts to flatten subqueries in order to speed
+** execution. It returns 1 if it makes changes and 0 if no flattening
+** occurs.
+**
+** To understand the concept of flattening, consider the following
+** query:
+**
+** SELECT a FROM (SELECT x+y AS a FROM t1 WHERE z<100) WHERE a>5
+**
+** The default way of implementing this query is to execute the
+** subquery first and store the results in a temporary table, then
+** run the outer query on that temporary table. This requires two
+** passes over the data. Furthermore, because the temporary table
+** has no indices, the WHERE clause on the outer query cannot be
+** optimized.
+**
+** This routine attempts to rewrite queries such as the above into
+** a single flat select, like this:
+**
+** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5
+**
+** The code generated for this simpification gives the same result
+** but only has to scan the data once. And because indices might
+** exist on the table t1, a complete scan of the data might be
+** avoided.
+**
+** Flattening is only attempted if all of the following are true:
+**
+** (1) The subquery and the outer query do not both use aggregates.
+**
+** (2) The subquery is not an aggregate or the outer query is not a join.
+**
+** (3) The subquery is not the right operand of a left outer join, or
+** the subquery is not itself a join. (Ticket #306)
+**
+** (4) The subquery is not DISTINCT or the outer query is not a join.
+**
+** (5) The subquery is not DISTINCT or the outer query does not use
+** aggregates.
+**
+** (6) The subquery does not use aggregates or the outer query is not
+** DISTINCT.
+**
+** (7) The subquery has a FROM clause.
+**
+** (8) The subquery does not use LIMIT or the outer query is not a join.
+**
+** (9) The subquery does not use LIMIT or the outer query does not use
+** aggregates.
+**
+** (10) The subquery does not use aggregates or the outer query does not
+** use LIMIT.
+**
+** (11) The subquery and the outer query do not both have ORDER BY clauses.
+**
+** (12) The subquery is not the right term of a LEFT OUTER JOIN or the
+** subquery has no WHERE clause. (added by ticket #350)
+**
+** (13) The subquery and outer query do not both use LIMIT
+**
+** (14) The subquery does not use OFFSET
+**
+** (15) The outer query is not part of a compound select or the
+** subquery does not have both an ORDER BY and a LIMIT clause.
+** (See ticket #2339)
+**
+** (16) The outer query is not an aggregate or the subquery does
+** not contain ORDER BY. (Ticket #2942) This used to not matter
+** until we introduced the group_concat() function.
+**
+** In this routine, the "p" parameter is a pointer to the outer query.
+** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query
+** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates.
+**
+** If flattening is not attempted, this routine is a no-op and returns 0.
+** If flattening is attempted this routine returns 1.
+**
+** All of the expression analysis must occur on both the outer query and
+** the subquery before this routine runs.
+*/
+static int flattenSubquery(
+ sqlite3 *db, /* Database connection */
+ Select *p, /* The parent or outer SELECT statement */
+ int iFrom, /* Index in p->pSrc->a[] of the inner subquery */
+ int isAgg, /* True if outer SELECT uses aggregate functions */
+ int subqueryIsAgg /* True if the subquery uses aggregate functions */
+){
+ Select *pSub; /* The inner query or "subquery" */
+ SrcList *pSrc; /* The FROM clause of the outer query */
+ SrcList *pSubSrc; /* The FROM clause of the subquery */
+ ExprList *pList; /* The result set of the outer query */
+ int iParent; /* VDBE cursor number of the pSub result set temp table */
+ int i; /* Loop counter */
+ Expr *pWhere; /* The WHERE clause */
+ struct SrcList_item *pSubitem; /* The subquery */
+
+ /* Check to see if flattening is permitted. Return 0 if not.
+ */
+ if( p==0 ) return 0;
+ pSrc = p->pSrc;
+ assert( pSrc && iFrom>=0 && iFrom<pSrc->nSrc );
+ pSubitem = &pSrc->a[iFrom];
+ pSub = pSubitem->pSelect;
+ assert( pSub!=0 );
+ if( isAgg && subqueryIsAgg ) return 0; /* Restriction (1) */
+ if( subqueryIsAgg && pSrc->nSrc>1 ) return 0; /* Restriction (2) */
+ pSubSrc = pSub->pSrc;
+ assert( pSubSrc );
+ /* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants,
+ ** not arbitrary expresssions, we allowed some combining of LIMIT and OFFSET
+ ** because they could be computed at compile-time. But when LIMIT and OFFSET
+ ** became arbitrary expressions, we were forced to add restrictions (13)
+ ** and (14). */
+ if( pSub->pLimit && p->pLimit ) return 0; /* Restriction (13) */
+ if( pSub->pOffset ) return 0; /* Restriction (14) */
+ if( p->pRightmost && pSub->pLimit && pSub->pOrderBy ){
+ return 0; /* Restriction (15) */
+ }
+ if( pSubSrc->nSrc==0 ) return 0; /* Restriction (7) */
+ if( (pSub->isDistinct || pSub->pLimit)
+ && (pSrc->nSrc>1 || isAgg) ){ /* Restrictions (4)(5)(8)(9) */
+ return 0;
+ }
+ if( p->isDistinct && subqueryIsAgg ) return 0; /* Restriction (6) */
+ if( (p->disallowOrderBy || p->pOrderBy) && pSub->pOrderBy ){
+ return 0; /* Restriction (11) */
+ }
+ if( isAgg && pSub->pOrderBy ) return 0; /* Restriction (16) */
+
+ /* Restriction 3: If the subquery is a join, make sure the subquery is
+ ** not used as the right operand of an outer join. Examples of why this
+ ** is not allowed:
+ **
+ ** t1 LEFT OUTER JOIN (t2 JOIN t3)
+ **
+ ** If we flatten the above, we would get
+ **
+ ** (t1 LEFT OUTER JOIN t2) JOIN t3
+ **
+ ** which is not at all the same thing.
+ */
+ if( pSubSrc->nSrc>1 && (pSubitem->jointype & JT_OUTER)!=0 ){
+ return 0;
+ }
+
+ /* Restriction 12: If the subquery is the right operand of a left outer
+ ** join, make sure the subquery has no WHERE clause.
+ ** An examples of why this is not allowed:
+ **
+ ** t1 LEFT OUTER JOIN (SELECT * FROM t2 WHERE t2.x>0)
+ **
+ ** If we flatten the above, we would get
+ **
+ ** (t1 LEFT OUTER JOIN t2) WHERE t2.x>0
+ **
+ ** But the t2.x>0 test will always fail on a NULL row of t2, which
+ ** effectively converts the OUTER JOIN into an INNER JOIN.
+ */
+ if( (pSubitem->jointype & JT_OUTER)!=0 && pSub->pWhere!=0 ){
+ return 0;
+ }
+
+ /* If we reach this point, it means flattening is permitted for the
+ ** iFrom-th entry of the FROM clause in the outer query.
+ */
+
+ /* Move all of the FROM elements of the subquery into the
+ ** the FROM clause of the outer query. Before doing this, remember
+ ** the cursor number for the original outer query FROM element in
+ ** iParent. The iParent cursor will never be used. Subsequent code
+ ** will scan expressions looking for iParent references and replace
+ ** those references with expressions that resolve to the subquery FROM
+ ** elements we are now copying in.
+ */
+ iParent = pSubitem->iCursor;
+ {
+ int nSubSrc = pSubSrc->nSrc;
+ int jointype = pSubitem->jointype;
+
+ sqlite3DeleteTable(pSubitem->pTab);
+ sqlite3_free(pSubitem->zDatabase);
+ sqlite3_free(pSubitem->zName);
+ sqlite3_free(pSubitem->zAlias);
+ pSubitem->pTab = 0;
+ pSubitem->zDatabase = 0;
+ pSubitem->zName = 0;
+ pSubitem->zAlias = 0;
+ if( nSubSrc>1 ){
+ int extra = nSubSrc - 1;
+ for(i=1; i<nSubSrc; i++){
+ pSrc = sqlite3SrcListAppend(db, pSrc, 0, 0);
+ if( pSrc==0 ){
+ p->pSrc = 0;
+ return 1;
+ }
+ }
+ p->pSrc = pSrc;
+ for(i=pSrc->nSrc-1; i-extra>=iFrom; i--){
+ pSrc->a[i] = pSrc->a[i-extra];
+ }
+ }
+ for(i=0; i<nSubSrc; i++){
+ pSrc->a[i+iFrom] = pSubSrc->a[i];
+ memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
+ }
+ pSrc->a[iFrom].jointype = jointype;
+ }
+
+ /* Now begin substituting subquery result set expressions for
+ ** references to the iParent in the outer query.
+ **
+ ** Example:
+ **
+ ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b;
+ ** \ \_____________ subquery __________/ /
+ ** \_____________________ outer query ______________________________/
+ **
+ ** We look at every expression in the outer query and every place we see
+ ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10".
+ */
+ pList = p->pEList;
+ for(i=0; i<pList->nExpr; i++){
+ Expr *pExpr;
+ if( pList->a[i].zName==0 && (pExpr = pList->a[i].pExpr)->span.z!=0 ){
+ pList->a[i].zName =
+ sqlite3DbStrNDup(db, (char*)pExpr->span.z, pExpr->span.n);
+ }
+ }
+ substExprList(db, p->pEList, iParent, pSub->pEList);
+ if( isAgg ){
+ substExprList(db, p->pGroupBy, iParent, pSub->pEList);
+ substExpr(db, p->pHaving, iParent, pSub->pEList);
+ }
+ if( pSub->pOrderBy ){
+ assert( p->pOrderBy==0 );
+ p->pOrderBy = pSub->pOrderBy;
+ pSub->pOrderBy = 0;
+ }else if( p->pOrderBy ){
+ substExprList(db, p->pOrderBy, iParent, pSub->pEList);
+ }
+ if( pSub->pWhere ){
+ pWhere = sqlite3ExprDup(db, pSub->pWhere);
+ }else{
+ pWhere = 0;
+ }
+ if( subqueryIsAgg ){
+ assert( p->pHaving==0 );
+ p->pHaving = p->pWhere;
+ p->pWhere = pWhere;
+ substExpr(db, p->pHaving, iParent, pSub->pEList);
+ p->pHaving = sqlite3ExprAnd(db, p->pHaving,
+ sqlite3ExprDup(db, pSub->pHaving));
+ assert( p->pGroupBy==0 );
+ p->pGroupBy = sqlite3ExprListDup(db, pSub->pGroupBy);
+ }else{
+ substExpr(db, p->pWhere, iParent, pSub->pEList);
+ p->pWhere = sqlite3ExprAnd(db, p->pWhere, pWhere);
+ }
+
+ /* The flattened query is distinct if either the inner or the
+ ** outer query is distinct.
+ */
+ p->isDistinct = p->isDistinct || pSub->isDistinct;
+
+ /*
+ ** SELECT ... FROM (SELECT ... LIMIT a OFFSET b) LIMIT x OFFSET y;
+ **
+ ** One is tempted to try to add a and b to combine the limits. But this
+ ** does not work if either limit is negative.
+ */
+ if( pSub->pLimit ){
+ p->pLimit = pSub->pLimit;
+ pSub->pLimit = 0;
+ }
+
+ /* Finially, delete what is left of the subquery and return
+ ** success.
+ */
+ sqlite3SelectDelete(pSub);
+ return 1;
+}
+#endif /* SQLITE_OMIT_VIEW */
+
+/*
+** Analyze the SELECT statement passed as an argument to see if it
+** is a min() or max() query. Return WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX if
+** it is, or 0 otherwise. At present, a query is considered to be
+** a min()/max() query if:
+**
+** 1. There is a single object in the FROM clause.
+**
+** 2. There is a single expression in the result set, and it is
+** either min(x) or max(x), where x is a column reference.
+*/
+static int minMaxQuery(Parse *pParse, Select *p){
+ Expr *pExpr;
+ ExprList *pEList = p->pEList;
+
+ if( pEList->nExpr!=1 ) return WHERE_ORDERBY_NORMAL;
+ pExpr = pEList->a[0].pExpr;
+ pEList = pExpr->pList;
+ if( pExpr->op!=TK_AGG_FUNCTION || pEList==0 || pEList->nExpr!=1 ) return 0;
+ if( pEList->a[0].pExpr->op!=TK_AGG_COLUMN ) return WHERE_ORDERBY_NORMAL;
+ if( pExpr->token.n!=3 ) return WHERE_ORDERBY_NORMAL;
+ if( sqlite3StrNICmp((char*)pExpr->token.z,"min",3)==0 ){
+ return WHERE_ORDERBY_MIN;
+ }else if( sqlite3StrNICmp((char*)pExpr->token.z,"max",3)==0 ){
+ return WHERE_ORDERBY_MAX;
+ }
+ return WHERE_ORDERBY_NORMAL;
+}
+
+/*
+** This routine resolves any names used in the result set of the
+** supplied SELECT statement. If the SELECT statement being resolved
+** is a sub-select, then pOuterNC is a pointer to the NameContext
+** of the parent SELECT.
+*/
+SQLITE_PRIVATE int sqlite3SelectResolve(
+ Parse *pParse, /* The parser context */
+ Select *p, /* The SELECT statement being coded. */
+ NameContext *pOuterNC /* The outer name context. May be NULL. */
+){
+ ExprList *pEList; /* Result set. */
+ int i; /* For-loop variable used in multiple places */
+ NameContext sNC; /* Local name-context */
+ ExprList *pGroupBy; /* The group by clause */
+
+ /* If this routine has run before, return immediately. */
+ if( p->isResolved ){
+ assert( !pOuterNC );
+ return SQLITE_OK;
+ }
+ p->isResolved = 1;
+
+ /* If there have already been errors, do nothing. */
+ if( pParse->nErr>0 ){
+ return SQLITE_ERROR;
+ }
+
+ /* Prepare the select statement. This call will allocate all cursors
+ ** required to handle the tables and subqueries in the FROM clause.
+ */
+ if( prepSelectStmt(pParse, p) ){
+ return SQLITE_ERROR;
+ }
+
+ /* Resolve the expressions in the LIMIT and OFFSET clauses. These
+ ** are not allowed to refer to any names, so pass an empty NameContext.
+ */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ if( sqlite3ExprResolveNames(&sNC, p->pLimit) ||
+ sqlite3ExprResolveNames(&sNC, p->pOffset) ){
+ return SQLITE_ERROR;
+ }
+
+ /* Set up the local name-context to pass to ExprResolveNames() to
+ ** resolve the expression-list.
+ */
+ sNC.allowAgg = 1;
+ sNC.pSrcList = p->pSrc;
+ sNC.pNext = pOuterNC;
+
+ /* Resolve names in the result set. */
+ pEList = p->pEList;
+ if( !pEList ) return SQLITE_ERROR;
+ for(i=0; i<pEList->nExpr; i++){
+ Expr *pX = pEList->a[i].pExpr;
+ if( sqlite3ExprResolveNames(&sNC, pX) ){
+ return SQLITE_ERROR;
+ }
+ }
+
+ /* If there are no aggregate functions in the result-set, and no GROUP BY
+ ** expression, do not allow aggregates in any of the other expressions.
+ */
+ assert( !p->isAgg );
+ pGroupBy = p->pGroupBy;
+ if( pGroupBy || sNC.hasAgg ){
+ p->isAgg = 1;
+ }else{
+ sNC.allowAgg = 0;
+ }
+
+ /* If a HAVING clause is present, then there must be a GROUP BY clause.
+ */
+ if( p->pHaving && !pGroupBy ){
+ sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING");
+ return SQLITE_ERROR;
+ }
+
+ /* Add the expression list to the name-context before parsing the
+ ** other expressions in the SELECT statement. This is so that
+ ** expressions in the WHERE clause (etc.) can refer to expressions by
+ ** aliases in the result set.
+ **
+ ** Minor point: If this is the case, then the expression will be
+ ** re-evaluated for each reference to it.
+ */
+ sNC.pEList = p->pEList;
+ if( sqlite3ExprResolveNames(&sNC, p->pWhere) ||
+ sqlite3ExprResolveNames(&sNC, p->pHaving) ){
+ return SQLITE_ERROR;
+ }
+ if( p->pPrior==0 ){
+ if( processOrderGroupBy(pParse, p, p->pOrderBy, 1, &sNC.hasAgg) ){
+ return SQLITE_ERROR;
+ }
+ }
+ if( processOrderGroupBy(pParse, p, pGroupBy, 0, &sNC.hasAgg) ){
+ return SQLITE_ERROR;
+ }
+
+ if( pParse->db->mallocFailed ){
+ return SQLITE_NOMEM;
+ }
+
+ /* Make sure the GROUP BY clause does not contain aggregate functions.
+ */
+ if( pGroupBy ){
+ struct ExprList_item *pItem;
+
+ for(i=0, pItem=pGroupBy->a; i<pGroupBy->nExpr; i++, pItem++){
+ if( ExprHasProperty(pItem->pExpr, EP_Agg) ){
+ sqlite3ErrorMsg(pParse, "aggregate functions are not allowed in "
+ "the GROUP BY clause");
+ return SQLITE_ERROR;
+ }
+ }
+ }
+
+ /* If this is one SELECT of a compound, be sure to resolve names
+ ** in the other SELECTs.
+ */
+ if( p->pPrior ){
+ return sqlite3SelectResolve(pParse, p->pPrior, pOuterNC);
+ }else{
+ return SQLITE_OK;
+ }
+}
+
+/*
+** Reset the aggregate accumulator.
+**
+** The aggregate accumulator is a set of memory cells that hold
+** intermediate results while calculating an aggregate. This
+** routine simply stores NULLs in all of those memory cells.
+*/
+static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ struct AggInfo_func *pFunc;
+ if( pAggInfo->nFunc+pAggInfo->nColumn==0 ){
+ return;
+ }
+ for(i=0; i<pAggInfo->nColumn; i++){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, pAggInfo->aCol[i].iMem);
+ }
+ for(pFunc=pAggInfo->aFunc, i=0; i<pAggInfo->nFunc; i++, pFunc++){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, pFunc->iMem);
+ if( pFunc->iDistinct>=0 ){
+ Expr *pE = pFunc->pExpr;
+ if( pE->pList==0 || pE->pList->nExpr!=1 ){
+ sqlite3ErrorMsg(pParse, "DISTINCT in aggregate must be followed "
+ "by an expression");
+ pFunc->iDistinct = -1;
+ }else{
+ KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pE->pList);
+ sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0,
+ (char*)pKeyInfo, P4_KEYINFO_HANDOFF);
+ }
+ }
+ }
+}
+
+/*
+** Invoke the OP_AggFinalize opcode for every aggregate function
+** in the AggInfo structure.
+*/
+static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ struct AggInfo_func *pF;
+ for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){
+ ExprList *pList = pF->pExpr->pList;
+ sqlite3VdbeAddOp4(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0, 0,
+ (void*)pF->pFunc, P4_FUNCDEF);
+ }
+}
+
+/*
+** Update the accumulator memory cells for an aggregate based on
+** the current cursor position.
+*/
+static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ struct AggInfo_func *pF;
+ struct AggInfo_col *pC;
+
+ pAggInfo->directMode = 1;
+ for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){
+ int nArg;
+ int addrNext = 0;
+ int regAgg;
+ ExprList *pList = pF->pExpr->pList;
+ if( pList ){
+ nArg = pList->nExpr;
+ regAgg = sqlite3GetTempRange(pParse, nArg);
+ sqlite3ExprCodeExprList(pParse, pList, regAgg, 0);
+ }else{
+ nArg = 0;
+ regAgg = 0;
+ }
+ if( pF->iDistinct>=0 ){
+ addrNext = sqlite3VdbeMakeLabel(v);
+ assert( nArg==1 );
+ codeDistinct(pParse, pF->iDistinct, addrNext, 1, regAgg);
+ }
+ if( pF->pFunc->needCollSeq ){
+ CollSeq *pColl = 0;
+ struct ExprList_item *pItem;
+ int j;
+ assert( pList!=0 ); /* pList!=0 if pF->pFunc->needCollSeq is true */
+ for(j=0, pItem=pList->a; !pColl && j<nArg; j++, pItem++){
+ pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
+ }
+ if( !pColl ){
+ pColl = pParse->db->pDfltColl;
+ }
+ sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ);
+ }
+ sqlite3VdbeAddOp4(v, OP_AggStep, 0, regAgg, pF->iMem,
+ (void*)pF->pFunc, P4_FUNCDEF);
+ sqlite3VdbeChangeP5(v, nArg);
+ sqlite3ReleaseTempRange(pParse, regAgg, nArg);
+ sqlite3ExprCacheAffinityChange(pParse, regAgg, nArg);
+ if( addrNext ){
+ sqlite3VdbeResolveLabel(v, addrNext);
+ }
+ }
+ for(i=0, pC=pAggInfo->aCol; i<pAggInfo->nAccumulator; i++, pC++){
+ sqlite3ExprCode(pParse, pC->pExpr, pC->iMem);
+ }
+ pAggInfo->directMode = 0;
+}
+
+#if 0
+/*
+** This function is used when a SELECT statement is used to create a
+** temporary table for iterating through when running an INSTEAD OF
+** UPDATE or INSTEAD OF DELETE trigger.
+**
+** If possible, the SELECT statement is modified so that NULL values
+** are stored in the temporary table for all columns for which the
+** corresponding bit in argument mask is not set. If mask takes the
+** special value 0xffffffff, then all columns are populated.
+*/
+SQLITE_PRIVATE void sqlite3SelectMask(Parse *pParse, Select *p, u32 mask){
+ if( p && !p->pPrior && !p->isDistinct && mask!=0xffffffff ){
+ ExprList *pEList;
+ int i;
+ sqlite3SelectResolve(pParse, p, 0);
+ pEList = p->pEList;
+ for(i=0; pEList && i<pEList->nExpr && i<32; i++){
+ if( !(mask&((u32)1<<i)) ){
+ sqlite3ExprDelete(pEList->a[i].pExpr);
+ pEList->a[i].pExpr = sqlite3Expr(pParse->db, TK_NULL, 0, 0, 0);
+ }
+ }
+ }
+}
+#endif
+
+/*
+** Generate code for the given SELECT statement.
+**
+** The results are distributed in various ways depending on the
+** contents of the SelectDest structure pointed to by argument pDest
+** as follows:
+**
+** pDest->eDest Result
+** ------------ -------------------------------------------
+** SRT_Callback Invoke the callback for each row of the result.
+**
+** SRT_Mem Store first result in memory cell pDest->iParm
+**
+** SRT_Set Store non-null results as keys of table pDest->iParm.
+** Apply the affinity pDest->affinity before storing them.
+**
+** SRT_Union Store results as a key in a temporary table pDest->iParm.
+**
+** SRT_Except Remove results from the temporary table pDest->iParm.
+**
+** SRT_Table Store results in temporary table pDest->iParm
+**
+** SRT_EphemTab Create an temporary table pDest->iParm and store
+** the result there. The cursor is left open after
+** returning.
+**
+** SRT_Subroutine For each row returned, push the results onto the
+** vdbe stack and call the subroutine (via OP_Gosub)
+** at address pDest->iParm.
+**
+** SRT_Exists Store a 1 in memory cell pDest->iParm if the result
+** set is not empty.
+**
+** SRT_Discard Throw the results away.
+**
+** See the selectInnerLoop() function for a canonical listing of the
+** allowed values of eDest and their meanings.
+**
+** This routine returns the number of errors. If any errors are
+** encountered, then an appropriate error message is left in
+** pParse->zErrMsg.
+**
+** This routine does NOT free the Select structure passed in. The
+** calling function needs to do that.
+**
+** The pParent, parentTab, and *pParentAgg fields are filled in if this
+** SELECT is a subquery. This routine may try to combine this SELECT
+** with its parent to form a single flat query. In so doing, it might
+** change the parent query from a non-aggregate to an aggregate query.
+** For that reason, the pParentAgg flag is passed as a pointer, so it
+** can be changed.
+**
+** Example 1: The meaning of the pParent parameter.
+**
+** SELECT * FROM t1 JOIN (SELECT x, count(*) FROM t2) JOIN t3;
+** \ \_______ subquery _______/ /
+** \ /
+** \____________________ outer query ___________________/
+**
+** This routine is called for the outer query first. For that call,
+** pParent will be NULL. During the processing of the outer query, this
+** routine is called recursively to handle the subquery. For the recursive
+** call, pParent will point to the outer query. Because the subquery is
+** the second element in a three-way join, the parentTab parameter will
+** be 1 (the 2nd value of a 0-indexed array.)
+*/
+SQLITE_PRIVATE int sqlite3Select(
+ Parse *pParse, /* The parser context */
+ Select *p, /* The SELECT statement being coded. */
+ SelectDest *pDest, /* What to do with the query results */
+ Select *pParent, /* Another SELECT for which this is a sub-query */
+ int parentTab, /* Index in pParent->pSrc of this query */
+ int *pParentAgg, /* True if pParent uses aggregate functions */
+ char *aff /* If eDest is SRT_Union, the affinity string */
+){
+ int i, j; /* Loop counters */
+ WhereInfo *pWInfo; /* Return from sqlite3WhereBegin() */
+ Vdbe *v; /* The virtual machine under construction */
+ int isAgg; /* True for select lists like "count(*)" */
+ ExprList *pEList; /* List of columns to extract. */
+ SrcList *pTabList; /* List of tables to select from */
+ Expr *pWhere; /* The WHERE clause. May be NULL */
+ ExprList *pOrderBy; /* The ORDER BY clause. May be NULL */
+ ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */
+ Expr *pHaving; /* The HAVING clause. May be NULL */
+ int isDistinct; /* True if the DISTINCT keyword is present */
+ int distinct; /* Table to use for the distinct set */
+ int rc = 1; /* Value to return from this function */
+ int addrSortIndex; /* Address of an OP_OpenEphemeral instruction */
+ AggInfo sAggInfo; /* Information used by aggregate queries */
+ int iEnd; /* Address of the end of the query */
+ sqlite3 *db; /* The database connection */
+
+ db = pParse->db;
+ if( p==0 || db->mallocFailed || pParse->nErr ){
+ return 1;
+ }
+ if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
+ memset(&sAggInfo, 0, sizeof(sAggInfo));
+
+ pOrderBy = p->pOrderBy;
+ if( IgnorableOrderby(pDest) ){
+ p->pOrderBy = 0;
+
+ /* In these cases the DISTINCT operator makes no difference to the
+ ** results, so remove it if it were specified.
+ */
+ assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union ||
+ pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard);
+ p->isDistinct = 0;
+ }
+ if( sqlite3SelectResolve(pParse, p, 0) ){
+ goto select_end;
+ }
+ p->pOrderBy = pOrderBy;
+
+#ifndef SQLITE_OMIT_COMPOUND_SELECT
+ /* If there is are a sequence of queries, do the earlier ones first.
+ */
+ if( p->pPrior ){
+ if( p->pRightmost==0 ){
+ Select *pLoop, *pRight = 0;
+ int cnt = 0;
+ int mxSelect;
+ for(pLoop=p; pLoop; pLoop=pLoop->pPrior, cnt++){
+ pLoop->pRightmost = p;
+ pLoop->pNext = pRight;
+ pRight = pLoop;
+ }
+ mxSelect = db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT];
+ if( mxSelect && cnt>mxSelect ){
+ sqlite3ErrorMsg(pParse, "too many terms in compound SELECT");
+ return 1;
+ }
+ }
+ return multiSelect(pParse, p, pDest, aff);
+ }
+#endif
+
+ /* Make local copies of the parameters for this query.
+ */
+ pTabList = p->pSrc;
+ pWhere = p->pWhere;
+ pGroupBy = p->pGroupBy;
+ pHaving = p->pHaving;
+ isAgg = p->isAgg;
+ isDistinct = p->isDistinct;
+ pEList = p->pEList;
+ if( pEList==0 ) goto select_end;
+
+ /*
+ ** Do not even attempt to generate any code if we have already seen
+ ** errors before this routine starts.
+ */
+ if( pParse->nErr>0 ) goto select_end;
+
+ /* If writing to memory or generating a set
+ ** only a single column may be output.
+ */
+#ifndef SQLITE_OMIT_SUBQUERY
+ if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){
+ goto select_end;
+ }
+#endif
+
+ /* ORDER BY is ignored for some destinations.
+ */
+ if( IgnorableOrderby(pDest) ){
+ pOrderBy = 0;
+ }
+
+ /* Begin generating code.
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto select_end;
+
+ /* Generate code for all sub-queries in the FROM clause
+ */
+#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
+ for(i=0; i<pTabList->nSrc; i++){
+ const char *zSavedAuthContext = 0;
+ int needRestoreContext;
+ struct SrcList_item *pItem = &pTabList->a[i];
+ SelectDest dest;
+
+ if( pItem->pSelect==0 || pItem->isPopulated ) continue;
+ if( pItem->zName!=0 ){
+ zSavedAuthContext = pParse->zAuthContext;
+ pParse->zAuthContext = pItem->zName;
+ needRestoreContext = 1;
+ }else{
+ needRestoreContext = 0;
+ }
+ /* Increment Parse.nHeight by the height of the largest expression
+ ** tree refered to by this, the parent select. The child select
+ ** may contain expression trees of at most
+ ** (SQLITE_MAX_EXPR_DEPTH-Parse.nHeight) height. This is a bit
+ ** more conservative than necessary, but much easier than enforcing
+ ** an exact limit.
+ */
+ pParse->nHeight += sqlite3SelectExprHeight(p);
+ sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
+ sqlite3Select(pParse, pItem->pSelect, &dest, p, i, &isAgg, 0);
+ if( db->mallocFailed ){
+ goto select_end;
+ }
+ pParse->nHeight -= sqlite3SelectExprHeight(p);
+ if( needRestoreContext ){
+ pParse->zAuthContext = zSavedAuthContext;
+ }
+ pTabList = p->pSrc;
+ pWhere = p->pWhere;
+ if( !IgnorableOrderby(pDest) ){
+ pOrderBy = p->pOrderBy;
+ }
+ pGroupBy = p->pGroupBy;
+ pHaving = p->pHaving;
+ isDistinct = p->isDistinct;
+ }
+#endif
+
+ /* Check to see if this is a subquery that can be "flattened" into its parent.
+ ** If flattening is a possiblity, do so and return immediately.
+ */
+#ifndef SQLITE_OMIT_VIEW
+ if( pParent && pParentAgg &&
+ flattenSubquery(db, pParent, parentTab, *pParentAgg, isAgg) ){
+ if( isAgg ) *pParentAgg = 1;
+ goto select_end;
+ }
+#endif
+
+ /* If possible, rewrite the query to use GROUP BY instead of DISTINCT.
+ ** GROUP BY may use an index, DISTINCT never does.
+ */
+ if( p->isDistinct && !p->isAgg && !p->pGroupBy ){
+ p->pGroupBy = sqlite3ExprListDup(db, p->pEList);
+ pGroupBy = p->pGroupBy;
+ p->isDistinct = 0;
+ isDistinct = 0;
+ }
+
+ /* If there is an ORDER BY clause, then this sorting
+ ** index might end up being unused if the data can be
+ ** extracted in pre-sorted order. If that is the case, then the
+ ** OP_OpenEphemeral instruction will be changed to an OP_Noop once
+ ** we figure out that the sorting index is not needed. The addrSortIndex
+ ** variable is used to facilitate that change.
+ */
+ if( pOrderBy ){
+ KeyInfo *pKeyInfo;
+ pKeyInfo = keyInfoFromExprList(pParse, pOrderBy);
+ pOrderBy->iECursor = pParse->nTab++;
+ p->addrOpenEphm[2] = addrSortIndex =
+ sqlite3VdbeAddOp4(v, OP_OpenEphemeral,
+ pOrderBy->iECursor, pOrderBy->nExpr+2, 0,
+ (char*)pKeyInfo, P4_KEYINFO_HANDOFF);
+ }else{
+ addrSortIndex = -1;
+ }
+
+ /* If the output is destined for a temporary table, open that table.
+ */
+ if( pDest->eDest==SRT_EphemTab ){
+ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iParm, pEList->nExpr);
+ }
+
+ /* Set the limiter.
+ */
+ iEnd = sqlite3VdbeMakeLabel(v);
+ computeLimitRegisters(pParse, p, iEnd);
+
+ /* Open a virtual index to use for the distinct set.
+ */
+ if( isDistinct ){
+ KeyInfo *pKeyInfo;
+ assert( isAgg || pGroupBy );
+ distinct = pParse->nTab++;
+ pKeyInfo = keyInfoFromExprList(pParse, p->pEList);
+ sqlite3VdbeAddOp4(v, OP_OpenEphemeral, distinct, 0, 0,
+ (char*)pKeyInfo, P4_KEYINFO_HANDOFF);
+ }else{
+ distinct = -1;
+ }
+
+ /* Aggregate and non-aggregate queries are handled differently */
+ if( !isAgg && pGroupBy==0 ){
+ /* This case is for non-aggregate queries
+ ** Begin the database scan
+ */
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pOrderBy, 0);
+ if( pWInfo==0 ) goto select_end;
+
+ /* If sorting index that was created by a prior OP_OpenEphemeral
+ ** instruction ended up not being needed, then change the OP_OpenEphemeral
+ ** into an OP_Noop.
+ */
+ if( addrSortIndex>=0 && pOrderBy==0 ){
+ sqlite3VdbeChangeToNoop(v, addrSortIndex, 1);
+ p->addrOpenEphm[2] = -1;
+ }
+
+ /* Use the standard inner loop
+ */
+ assert(!isDistinct);
+ selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, -1, pDest,
+ pWInfo->iContinue, pWInfo->iBreak, aff);
+
+ /* End the database scan loop.
+ */
+ sqlite3WhereEnd(pWInfo);
+ }else{
+ /* This is the processing for aggregate queries */
+ NameContext sNC; /* Name context for processing aggregate information */
+ int iAMem; /* First Mem address for storing current GROUP BY */
+ int iBMem; /* First Mem address for previous GROUP BY */
+ int iUseFlag; /* Mem address holding flag indicating that at least
+ ** one row of the input to the aggregator has been
+ ** processed */
+ int iAbortFlag; /* Mem address which causes query abort if positive */
+ int groupBySort; /* Rows come from source in GROUP BY order */
+
+
+ /* The following variables hold addresses or labels for parts of the
+ ** virtual machine program we are putting together */
+ int addrOutputRow; /* Start of subroutine that outputs a result row */
+ int addrSetAbort; /* Set the abort flag and return */
+ int addrInitializeLoop; /* Start of code that initializes the input loop */
+ int addrTopOfLoop; /* Top of the input loop */
+ int addrGroupByChange; /* Code that runs when any GROUP BY term changes */
+ int addrProcessRow; /* Code to process a single input row */
+ int addrEnd; /* End of all processing */
+ int addrSortingIdx; /* The OP_OpenEphemeral for the sorting index */
+ int addrReset; /* Subroutine for resetting the accumulator */
+
+ addrEnd = sqlite3VdbeMakeLabel(v);
+
+ /* Convert TK_COLUMN nodes into TK_AGG_COLUMN and make entries in
+ ** sAggInfo for all TK_AGG_FUNCTION nodes in expressions of the
+ ** SELECT statement.
+ */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ sNC.pSrcList = pTabList;
+ sNC.pAggInfo = &sAggInfo;
+ sAggInfo.nSortingColumn = pGroupBy ? pGroupBy->nExpr+1 : 0;
+ sAggInfo.pGroupBy = pGroupBy;
+ sqlite3ExprAnalyzeAggList(&sNC, pEList);
+ sqlite3ExprAnalyzeAggList(&sNC, pOrderBy);
+ if( pHaving ){
+ sqlite3ExprAnalyzeAggregates(&sNC, pHaving);
+ }
+ sAggInfo.nAccumulator = sAggInfo.nColumn;
+ for(i=0; i<sAggInfo.nFunc; i++){
+ sqlite3ExprAnalyzeAggList(&sNC, sAggInfo.aFunc[i].pExpr->pList);
+ }
+ if( db->mallocFailed ) goto select_end;
+
+ /* Processing for aggregates with GROUP BY is very different and
+ ** much more complex than aggregates without a GROUP BY.
+ */
+ if( pGroupBy ){
+ KeyInfo *pKeyInfo; /* Keying information for the group by clause */
+
+ /* Create labels that we will be needing
+ */
+
+ addrInitializeLoop = sqlite3VdbeMakeLabel(v);
+ addrGroupByChange = sqlite3VdbeMakeLabel(v);
+ addrProcessRow = sqlite3VdbeMakeLabel(v);
+
+ /* If there is a GROUP BY clause we might need a sorting index to
+ ** implement it. Allocate that sorting index now. If it turns out
+ ** that we do not need it after all, the OpenEphemeral instruction
+ ** will be converted into a Noop.
+ */
+ sAggInfo.sortingIdx = pParse->nTab++;
+ pKeyInfo = keyInfoFromExprList(pParse, pGroupBy);
+ addrSortingIdx = sqlite3VdbeAddOp4(v, OP_OpenEphemeral,
+ sAggInfo.sortingIdx, sAggInfo.nSortingColumn,
+ 0, (char*)pKeyInfo, P4_KEYINFO_HANDOFF);
+
+ /* Initialize memory locations used by GROUP BY aggregate processing
+ */
+ iUseFlag = ++pParse->nMem;
+ iAbortFlag = ++pParse->nMem;
+ iAMem = pParse->nMem + 1;
+ pParse->nMem += pGroupBy->nExpr;
+ iBMem = pParse->nMem + 1;
+ pParse->nMem += pGroupBy->nExpr;
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag);
+ VdbeComment((v, "clear abort flag"));
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag);
+ VdbeComment((v, "indicate accumulator empty"));
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, addrInitializeLoop);
+
+ /* Generate a subroutine that outputs a single row of the result
+ ** set. This subroutine first looks at the iUseFlag. If iUseFlag
+ ** is less than or equal to zero, the subroutine is a no-op. If
+ ** the processing calls for the query to abort, this subroutine
+ ** increments the iAbortFlag memory location before returning in
+ ** order to signal the caller to abort.
+ */
+ addrSetAbort = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, iAbortFlag);
+ VdbeComment((v, "set abort flag"));
+ sqlite3VdbeAddOp2(v, OP_Return, 0, 0);
+ addrOutputRow = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2);
+ VdbeComment((v, "Groupby result generator entry point"));
+ sqlite3VdbeAddOp2(v, OP_Return, 0, 0);
+ finalizeAggFunctions(pParse, &sAggInfo);
+ if( pHaving ){
+ sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL);
+ }
+ selectInnerLoop(pParse, p, p->pEList, 0, 0, pOrderBy,
+ distinct, pDest,
+ addrOutputRow+1, addrSetAbort, aff);
+ sqlite3VdbeAddOp2(v, OP_Return, 0, 0);
+ VdbeComment((v, "end groupby result generator"));
+
+ /* Generate a subroutine that will reset the group-by accumulator
+ */
+ addrReset = sqlite3VdbeCurrentAddr(v);
+ resetAccumulator(pParse, &sAggInfo);
+ sqlite3VdbeAddOp2(v, OP_Return, 0, 0);
+
+ /* Begin a loop that will extract all source rows in GROUP BY order.
+ ** This might involve two separate loops with an OP_Sort in between, or
+ ** it might be a single loop that uses an index to extract information
+ ** in the right order to begin with.
+ */
+ sqlite3VdbeResolveLabel(v, addrInitializeLoop);
+ sqlite3VdbeAddOp2(v, OP_Gosub, 0, addrReset);
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pGroupBy, 0);
+ if( pWInfo==0 ) goto select_end;
+ if( pGroupBy==0 ){
+ /* The optimizer is able to deliver rows in group by order so
+ ** we do not have to sort. The OP_OpenEphemeral table will be
+ ** cancelled later because we still need to use the pKeyInfo
+ */
+ pGroupBy = p->pGroupBy;
+ groupBySort = 0;
+ }else{
+ /* Rows are coming out in undetermined order. We have to push
+ ** each row into a sorting index, terminate the first loop,
+ ** then loop over the sorting index in order to get the output
+ ** in sorted order
+ */
+ int regBase;
+ int regRecord;
+ int nCol;
+ int nGroupBy;
+
+ groupBySort = 1;
+ nGroupBy = pGroupBy->nExpr;
+ nCol = nGroupBy + 1;
+ j = nGroupBy+1;
+ for(i=0; i<sAggInfo.nColumn; i++){
+ if( sAggInfo.aCol[i].iSorterColumn>=j ){
+ nCol++;
+ j++;
+ }
+ }
+ regBase = sqlite3GetTempRange(pParse, nCol);
+ sqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0);
+ sqlite3VdbeAddOp2(v, OP_Sequence, sAggInfo.sortingIdx,regBase+nGroupBy);
+ j = nGroupBy+1;
+ for(i=0; i<sAggInfo.nColumn; i++){
+ struct AggInfo_col *pCol = &sAggInfo.aCol[i];
+ if( pCol->iSorterColumn>=j ){
+ int r1 = j + regBase;
+ int r2 = sqlite3ExprCodeGetColumn(pParse,
+ pCol->pTab, pCol->iColumn, pCol->iTable, r1, 0);
+ if( r1!=r2 ){
+ sqlite3VdbeAddOp2(v, OP_SCopy, r2, r1);
+ }
+ j++;
+ }
+ }
+ regRecord = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord);
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, sAggInfo.sortingIdx, regRecord);
+ sqlite3ReleaseTempReg(pParse, regRecord);
+ sqlite3ReleaseTempRange(pParse, regBase, nCol);
+ sqlite3WhereEnd(pWInfo);
+ sqlite3VdbeAddOp2(v, OP_Sort, sAggInfo.sortingIdx, addrEnd);
+ VdbeComment((v, "GROUP BY sort"));
+ sAggInfo.useSortingIdx = 1;
+ }
+
+ /* Evaluate the current GROUP BY terms and store in b0, b1, b2...
+ ** (b0 is memory location iBMem+0, b1 is iBMem+1, and so forth)
+ ** Then compare the current GROUP BY terms against the GROUP BY terms
+ ** from the previous row currently stored in a0, a1, a2...
+ */
+ addrTopOfLoop = sqlite3VdbeCurrentAddr(v);
+ for(j=0; j<pGroupBy->nExpr; j++){
+ if( groupBySort ){
+ sqlite3VdbeAddOp3(v, OP_Column, sAggInfo.sortingIdx, j, iBMem+j);
+ }else{
+ sAggInfo.directMode = 1;
+ sqlite3ExprCode(pParse, pGroupBy->a[j].pExpr, iBMem+j);
+ }
+ }
+ for(j=pGroupBy->nExpr-1; j>=0; j--){
+ if( j==0 ){
+ sqlite3VdbeAddOp3(v, OP_Eq, iAMem+j, addrProcessRow, iBMem+j);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_Ne, iAMem+j, addrGroupByChange, iBMem+j);
+ }
+ sqlite3VdbeChangeP4(v, -1, (void*)pKeyInfo->aColl[j], P4_COLLSEQ);
+ sqlite3VdbeChangeP5(v, SQLITE_NULLEQUAL);
+ }
+
+ /* Generate code that runs whenever the GROUP BY changes.
+ ** Change in the GROUP BY are detected by the previous code
+ ** block. If there were no changes, this block is skipped.
+ **
+ ** This code copies current group by terms in b0,b1,b2,...
+ ** over to a0,a1,a2. It then calls the output subroutine
+ ** and resets the aggregate accumulator registers in preparation
+ ** for the next GROUP BY batch.
+ */
+ sqlite3VdbeResolveLabel(v, addrGroupByChange);
+ for(j=0; j<pGroupBy->nExpr; j++){
+ sqlite3ExprCodeMove(pParse, iBMem+j, iAMem+j);
+ }
+ sqlite3VdbeAddOp2(v, OP_Gosub, 0, addrOutputRow);
+ VdbeComment((v, "output one row"));
+ sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd);
+ VdbeComment((v, "check abort flag"));
+ sqlite3VdbeAddOp2(v, OP_Gosub, 0, addrReset);
+ VdbeComment((v, "reset accumulator"));
+
+ /* Update the aggregate accumulators based on the content of
+ ** the current row
+ */
+ sqlite3VdbeResolveLabel(v, addrProcessRow);
+ updateAccumulator(pParse, &sAggInfo);
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag);
+ VdbeComment((v, "indicate data in accumulator"));
+
+ /* End of the loop
+ */
+ if( groupBySort ){
+ sqlite3VdbeAddOp2(v, OP_Next, sAggInfo.sortingIdx, addrTopOfLoop);
+ }else{
+ sqlite3WhereEnd(pWInfo);
+ sqlite3VdbeChangeToNoop(v, addrSortingIdx, 1);
+ }
+
+ /* Output the final row of result
+ */
+ sqlite3VdbeAddOp2(v, OP_Gosub, 0, addrOutputRow);
+ VdbeComment((v, "output final row"));
+
+ } /* endif pGroupBy */
+ else {
+ ExprList *pMinMax = 0;
+ ExprList *pDel = 0;
+ u8 flag;
+
+ /* Check if the query is of one of the following forms:
+ **
+ ** SELECT min(x) FROM ...
+ ** SELECT max(x) FROM ...
+ **
+ ** If it is, then ask the code in where.c to attempt to sort results
+ ** as if there was an "ORDER ON x" or "ORDER ON x DESC" clause.
+ ** If where.c is able to produce results sorted in this order, then
+ ** add vdbe code to break out of the processing loop after the
+ ** first iteration (since the first iteration of the loop is
+ ** guaranteed to operate on the row with the minimum or maximum
+ ** value of x, the only row required).
+ **
+ ** A special flag must be passed to sqlite3WhereBegin() to slightly
+ ** modify behaviour as follows:
+ **
+ ** + If the query is a "SELECT min(x)", then the loop coded by
+ ** where.c should not iterate over any values with a NULL value
+ ** for x.
+ **
+ ** + The optimizer code in where.c (the thing that decides which
+ ** index or indices to use) should place a different priority on
+ ** satisfying the 'ORDER BY' clause than it does in other cases.
+ ** Refer to code and comments in where.c for details.
+ */
+ flag = minMaxQuery(pParse, p);
+ if( flag ){
+ pDel = pMinMax = sqlite3ExprListDup(db, p->pEList->a[0].pExpr->pList);
+ if( pMinMax && !db->mallocFailed ){
+ pMinMax->a[0].sortOrder = ((flag==WHERE_ORDERBY_MIN)?0:1);
+ pMinMax->a[0].pExpr->op = TK_COLUMN;
+ }
+ }
+
+ /* This case runs if the aggregate has no GROUP BY clause. The
+ ** processing is much simpler since there is only a single row
+ ** of output.
+ */
+ resetAccumulator(pParse, &sAggInfo);
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pMinMax, flag);
+ if( pWInfo==0 ){
+ sqlite3ExprListDelete(pDel);
+ goto select_end;
+ }
+ updateAccumulator(pParse, &sAggInfo);
+ if( !pMinMax && flag ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, pWInfo->iBreak);
+ VdbeComment((v, "%s() by index", (flag==WHERE_ORDERBY_MIN?"min":"max")));
+ }
+ sqlite3WhereEnd(pWInfo);
+ finalizeAggFunctions(pParse, &sAggInfo);
+ pOrderBy = 0;
+ if( pHaving ){
+ sqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL);
+ }
+ selectInnerLoop(pParse, p, p->pEList, 0, 0, 0, -1,
+ pDest, addrEnd, addrEnd, aff);
+
+ sqlite3ExprListDelete(pDel);
+ }
+ sqlite3VdbeResolveLabel(v, addrEnd);
+
+ } /* endif aggregate query */
+
+ /* If there is an ORDER BY clause, then we need to sort the results
+ ** and send them to the callback one by one.
+ */
+ if( pOrderBy ){
+ generateSortTail(pParse, p, v, pEList->nExpr, pDest);
+ }
+
+#ifndef SQLITE_OMIT_SUBQUERY
+ /* If this was a subquery, we have now converted the subquery into a
+ ** temporary table. So set the SrcList_item.isPopulated flag to prevent
+ ** this subquery from being evaluated again and to force the use of
+ ** the temporary table.
+ */
+ if( pParent ){
+ assert( pParent->pSrc->nSrc>parentTab );
+ assert( pParent->pSrc->a[parentTab].pSelect==p );
+ pParent->pSrc->a[parentTab].isPopulated = 1;
+ }
+#endif
+
+ /* Jump here to skip this query
+ */
+ sqlite3VdbeResolveLabel(v, iEnd);
+
+ /* The SELECT was successfully coded. Set the return code to 0
+ ** to indicate no errors.
+ */
+ rc = 0;
+
+ /* Control jumps to here if an error is encountered above, or upon
+ ** successful coding of the SELECT.
+ */
+select_end:
+
+ /* Identify column names if we will be using them in a callback. This
+ ** step is skipped if the output is going to some other destination.
+ */
+ if( rc==SQLITE_OK && pDest->eDest==SRT_Callback ){
+ generateColumnNames(pParse, pTabList, pEList);
+ }
+
+ sqlite3_free(sAggInfo.aCol);
+ sqlite3_free(sAggInfo.aFunc);
+ return rc;
+}
+
+#if defined(SQLITE_DEBUG)
+/*
+*******************************************************************************
+** The following code is used for testing and debugging only. The code
+** that follows does not appear in normal builds.
+**
+** These routines are used to print out the content of all or part of a
+** parse structures such as Select or Expr. Such printouts are useful
+** for helping to understand what is happening inside the code generator
+** during the execution of complex SELECT statements.
+**
+** These routine are not called anywhere from within the normal
+** code base. Then are intended to be called from within the debugger
+** or from temporary "printf" statements inserted for debugging.
+*/
+SQLITE_PRIVATE void sqlite3PrintExpr(Expr *p){
+ if( p->token.z && p->token.n>0 ){
+ sqlite3DebugPrintf("(%.*s", p->token.n, p->token.z);
+ }else{
+ sqlite3DebugPrintf("(%d", p->op);
+ }
+ if( p->pLeft ){
+ sqlite3DebugPrintf(" ");
+ sqlite3PrintExpr(p->pLeft);
+ }
+ if( p->pRight ){
+ sqlite3DebugPrintf(" ");
+ sqlite3PrintExpr(p->pRight);
+ }
+ sqlite3DebugPrintf(")");
+}
+SQLITE_PRIVATE void sqlite3PrintExprList(ExprList *pList){
+ int i;
+ for(i=0; i<pList->nExpr; i++){
+ sqlite3PrintExpr(pList->a[i].pExpr);
+ if( i<pList->nExpr-1 ){
+ sqlite3DebugPrintf(", ");
+ }
+ }
+}
+SQLITE_PRIVATE void sqlite3PrintSelect(Select *p, int indent){
+ sqlite3DebugPrintf("%*sSELECT(%p) ", indent, "", p);
+ sqlite3PrintExprList(p->pEList);
+ sqlite3DebugPrintf("\n");
+ if( p->pSrc ){
+ char *zPrefix;
+ int i;
+ zPrefix = "FROM";
+ for(i=0; i<p->pSrc->nSrc; i++){
+ struct SrcList_item *pItem = &p->pSrc->a[i];
+ sqlite3DebugPrintf("%*s ", indent+6, zPrefix);
+ zPrefix = "";
+ if( pItem->pSelect ){
+ sqlite3DebugPrintf("(\n");
+ sqlite3PrintSelect(pItem->pSelect, indent+10);
+ sqlite3DebugPrintf("%*s)", indent+8, "");
+ }else if( pItem->zName ){
+ sqlite3DebugPrintf("%s", pItem->zName);
+ }
+ if( pItem->pTab ){
+ sqlite3DebugPrintf("(table: %s)", pItem->pTab->zName);
+ }
+ if( pItem->zAlias ){
+ sqlite3DebugPrintf(" AS %s", pItem->zAlias);
+ }
+ if( i<p->pSrc->nSrc-1 ){
+ sqlite3DebugPrintf(",");
+ }
+ sqlite3DebugPrintf("\n");
+ }
+ }
+ if( p->pWhere ){
+ sqlite3DebugPrintf("%*s WHERE ", indent, "");
+ sqlite3PrintExpr(p->pWhere);
+ sqlite3DebugPrintf("\n");
+ }
+ if( p->pGroupBy ){
+ sqlite3DebugPrintf("%*s GROUP BY ", indent, "");
+ sqlite3PrintExprList(p->pGroupBy);
+ sqlite3DebugPrintf("\n");
+ }
+ if( p->pHaving ){
+ sqlite3DebugPrintf("%*s HAVING ", indent, "");
+ sqlite3PrintExpr(p->pHaving);
+ sqlite3DebugPrintf("\n");
+ }
+ if( p->pOrderBy ){
+ sqlite3DebugPrintf("%*s ORDER BY ", indent, "");
+ sqlite3PrintExprList(p->pOrderBy);
+ sqlite3DebugPrintf("\n");
+ }
+}
+/* End of the structure debug printing code
+*****************************************************************************/
+#endif /* defined(SQLITE_TEST) || defined(SQLITE_DEBUG) */
+
+/************** End of select.c **********************************************/
+/************** Begin file table.c *******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the sqlite3_get_table() and sqlite3_free_table()
+** interface routines. These are just wrappers around the main
+** interface routine of sqlite3_exec().
+**
+** These routines are in a separate files so that they will not be linked
+** if they are not used.
+*/
+
+#ifndef SQLITE_OMIT_GET_TABLE
+
+/*
+** This structure is used to pass data from sqlite3_get_table() through
+** to the callback function is uses to build the result.
+*/
+typedef struct TabResult {
+ char **azResult;
+ char *zErrMsg;
+ int nResult;
+ int nAlloc;
+ int nRow;
+ int nColumn;
+ int nData;
+ int rc;
+} TabResult;
+
+/*
+** This routine is called once for each row in the result table. Its job
+** is to fill in the TabResult structure appropriately, allocating new
+** memory as necessary.
+*/
+static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
+ TabResult *p = (TabResult*)pArg;
+ int need;
+ int i;
+ char *z;
+
+ /* Make sure there is enough space in p->azResult to hold everything
+ ** we need to remember from this invocation of the callback.
+ */
+ if( p->nRow==0 && argv!=0 ){
+ need = nCol*2;
+ }else{
+ need = nCol;
+ }
+ if( p->nData + need >= p->nAlloc ){
+ char **azNew;
+ p->nAlloc = p->nAlloc*2 + need + 1;
+ azNew = sqlite3_realloc( p->azResult, sizeof(char*)*p->nAlloc );
+ if( azNew==0 ) goto malloc_failed;
+ p->azResult = azNew;
+ }
+
+ /* If this is the first row, then generate an extra row containing
+ ** the names of all columns.
+ */
+ if( p->nRow==0 ){
+ p->nColumn = nCol;
+ for(i=0; i<nCol; i++){
+ z = sqlite3_mprintf("%s", colv[i]);
+ if( z==0 ) goto malloc_failed;
+ p->azResult[p->nData++] = z;
+ }
+ }else if( p->nColumn!=nCol ){
+ sqlite3_free(p->zErrMsg);
+ p->zErrMsg = sqlite3_mprintf(
+ "sqlite3_get_table() called with two or more incompatible queries"
+ );
+ p->rc = SQLITE_ERROR;
+ return 1;
+ }
+
+ /* Copy over the row data
+ */
+ if( argv!=0 ){
+ for(i=0; i<nCol; i++){
+ if( argv[i]==0 ){
+ z = 0;
+ }else{
+ int n = strlen(argv[i])+1;
+ z = sqlite3_malloc( n );
+ if( z==0 ) goto malloc_failed;
+ memcpy(z, argv[i], n);
+ }
+ p->azResult[p->nData++] = z;
+ }
+ p->nRow++;
+ }
+ return 0;
+
+malloc_failed:
+ p->rc = SQLITE_NOMEM;
+ return 1;
+}
+
+/*
+** Query the database. But instead of invoking a callback for each row,
+** malloc() for space to hold the result and return the entire results
+** at the conclusion of the call.
+**
+** The result that is written to ***pazResult is held in memory obtained
+** from malloc(). But the caller cannot free this memory directly.
+** Instead, the entire table should be passed to sqlite3_free_table() when
+** the calling procedure is finished using it.
+*/
+SQLITE_API int sqlite3_get_table(
+ sqlite3 *db, /* The database on which the SQL executes */
+ const char *zSql, /* The SQL to be executed */
+ char ***pazResult, /* Write the result table here */
+ int *pnRow, /* Write the number of rows in the result here */
+ int *pnColumn, /* Write the number of columns of result here */
+ char **pzErrMsg /* Write error messages here */
+){
+ int rc;
+ TabResult res;
+
+ *pazResult = 0;
+ if( pnColumn ) *pnColumn = 0;
+ if( pnRow ) *pnRow = 0;
+ res.zErrMsg = 0;
+ res.nResult = 0;
+ res.nRow = 0;
+ res.nColumn = 0;
+ res.nData = 1;
+ res.nAlloc = 20;
+ res.rc = SQLITE_OK;
+ res.azResult = sqlite3_malloc(sizeof(char*)*res.nAlloc );
+ if( res.azResult==0 ){
+ db->errCode = SQLITE_NOMEM;
+ return SQLITE_NOMEM;
+ }
+ res.azResult[0] = 0;
+ rc = sqlite3_exec(db, zSql, sqlite3_get_table_cb, &res, pzErrMsg);
+ assert( sizeof(res.azResult[0])>= sizeof(res.nData) );
+ res.azResult[0] = (char*)res.nData;
+ if( (rc&0xff)==SQLITE_ABORT ){
+ sqlite3_free_table(&res.azResult[1]);
+ if( res.zErrMsg ){
+ if( pzErrMsg ){
+ sqlite3_free(*pzErrMsg);
+ *pzErrMsg = sqlite3_mprintf("%s",res.zErrMsg);
+ }
+ sqlite3_free(res.zErrMsg);
+ }
+ db->errCode = res.rc; /* Assume 32-bit assignment is atomic */
+ return res.rc;
+ }
+ sqlite3_free(res.zErrMsg);
+ if( rc!=SQLITE_OK ){
+ sqlite3_free_table(&res.azResult[1]);
+ return rc;
+ }
+ if( res.nAlloc>res.nData ){
+ char **azNew;
+ azNew = sqlite3_realloc( res.azResult, sizeof(char*)*(res.nData+1) );
+ if( azNew==0 ){
+ sqlite3_free_table(&res.azResult[1]);
+ db->errCode = SQLITE_NOMEM;
+ return SQLITE_NOMEM;
+ }
+ res.nAlloc = res.nData+1;
+ res.azResult = azNew;
+ }
+ *pazResult = &res.azResult[1];
+ if( pnColumn ) *pnColumn = res.nColumn;
+ if( pnRow ) *pnRow = res.nRow;
+ return rc;
+}
+
+/*
+** This routine frees the space the sqlite3_get_table() malloced.
+*/
+SQLITE_API void sqlite3_free_table(
+ char **azResult /* Result returned from from sqlite3_get_table() */
+){
+ if( azResult ){
+ int i, n;
+ azResult--;
+ assert( azResult!=0 );
+ n = (int)azResult[0];
+ for(i=1; i<n; i++){ if( azResult[i] ) sqlite3_free(azResult[i]); }
+ sqlite3_free(azResult);
+ }
+}
+
+#endif /* SQLITE_OMIT_GET_TABLE */
+
+/************** End of table.c ***********************************************/
+/************** Begin file trigger.c *****************************************/
+/*
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+*
+*/
+
+#ifndef SQLITE_OMIT_TRIGGER
+/*
+** Delete a linked list of TriggerStep structures.
+*/
+SQLITE_PRIVATE void sqlite3DeleteTriggerStep(TriggerStep *pTriggerStep){
+ while( pTriggerStep ){
+ TriggerStep * pTmp = pTriggerStep;
+ pTriggerStep = pTriggerStep->pNext;
+
+ if( pTmp->target.dyn ) sqlite3_free((char*)pTmp->target.z);
+ sqlite3ExprDelete(pTmp->pWhere);
+ sqlite3ExprListDelete(pTmp->pExprList);
+ sqlite3SelectDelete(pTmp->pSelect);
+ sqlite3IdListDelete(pTmp->pIdList);
+
+ sqlite3_free(pTmp);
+ }
+}
+
+/*
+** This is called by the parser when it sees a CREATE TRIGGER statement
+** up to the point of the BEGIN before the trigger actions. A Trigger
+** structure is generated based on the information available and stored
+** in pParse->pNewTrigger. After the trigger actions have been parsed, the
+** sqlite3FinishTrigger() function is called to complete the trigger
+** construction process.
+*/
+SQLITE_PRIVATE void sqlite3BeginTrigger(
+ Parse *pParse, /* The parse context of the CREATE TRIGGER statement */
+ Token *pName1, /* The name of the trigger */
+ Token *pName2, /* The name of the trigger */
+ int tr_tm, /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */
+ int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
+ IdList *pColumns, /* column list if this is an UPDATE OF trigger */
+ SrcList *pTableName,/* The name of the table/view the trigger applies to */
+ Expr *pWhen, /* WHEN clause */
+ int isTemp, /* True if the TEMPORARY keyword is present */
+ int noErr /* Suppress errors if the trigger already exists */
+){
+ Trigger *pTrigger = 0;
+ Table *pTab;
+ char *zName = 0; /* Name of the trigger */
+ sqlite3 *db = pParse->db;
+ int iDb; /* The database to store the trigger in */
+ Token *pName; /* The unqualified db name */
+ DbFixer sFix;
+ int iTabDb;
+
+ assert( pName1!=0 ); /* pName1->z might be NULL, but not pName1 itself */
+ assert( pName2!=0 );
+ if( isTemp ){
+ /* If TEMP was specified, then the trigger name may not be qualified. */
+ if( pName2->n>0 ){
+ sqlite3ErrorMsg(pParse, "temporary trigger may not have qualified name");
+ goto trigger_cleanup;
+ }
+ iDb = 1;
+ pName = pName1;
+ }else{
+ /* Figure out the db that the the trigger will be created in */
+ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ if( iDb<0 ){
+ goto trigger_cleanup;
+ }
+ }
+
+ /* If the trigger name was unqualified, and the table is a temp table,
+ ** then set iDb to 1 to create the trigger in the temporary database.
+ ** If sqlite3SrcListLookup() returns 0, indicating the table does not
+ ** exist, the error is caught by the block below.
+ */
+ if( !pTableName || db->mallocFailed ){
+ goto trigger_cleanup;
+ }
+ pTab = sqlite3SrcListLookup(pParse, pTableName);
+ if( pName2->n==0 && pTab && pTab->pSchema==db->aDb[1].pSchema ){
+ iDb = 1;
+ }
+
+ /* Ensure the table name matches database name and that the table exists */
+ if( db->mallocFailed ) goto trigger_cleanup;
+ assert( pTableName->nSrc==1 );
+ if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", pName) &&
+ sqlite3FixSrcList(&sFix, pTableName) ){
+ goto trigger_cleanup;
+ }
+ pTab = sqlite3SrcListLookup(pParse, pTableName);
+ if( !pTab ){
+ /* The table does not exist. */
+ goto trigger_cleanup;
+ }
+ if( IsVirtual(pTab) ){
+ sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");
+ goto trigger_cleanup;
+ }
+
+ /* Check that the trigger name is not reserved and that no trigger of the
+ ** specified name exists */
+ zName = sqlite3NameFromToken(db, pName);
+ if( !zName || SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ goto trigger_cleanup;
+ }
+ if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash), zName,strlen(zName)) ){
+ if( !noErr ){
+ sqlite3ErrorMsg(pParse, "trigger %T already exists", pName);
+ }
+ goto trigger_cleanup;
+ }
+
+ /* Do not create a trigger on a system table */
+ if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
+ sqlite3ErrorMsg(pParse, "cannot create trigger on system table");
+ pParse->nErr++;
+ goto trigger_cleanup;
+ }
+
+ /* INSTEAD of triggers are only for views and views only support INSTEAD
+ ** of triggers.
+ */
+ if( pTab->pSelect && tr_tm!=TK_INSTEAD ){
+ sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",
+ (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0);
+ goto trigger_cleanup;
+ }
+ if( !pTab->pSelect && tr_tm==TK_INSTEAD ){
+ sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF"
+ " trigger on table: %S", pTableName, 0);
+ goto trigger_cleanup;
+ }
+ iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code = SQLITE_CREATE_TRIGGER;
+ const char *zDb = db->aDb[iTabDb].zName;
+ const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb;
+ if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
+ if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){
+ goto trigger_cleanup;
+ }
+ if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iTabDb),0,zDb)){
+ goto trigger_cleanup;
+ }
+ }
+#endif
+
+ /* INSTEAD OF triggers can only appear on views and BEFORE triggers
+ ** cannot appear on views. So we might as well translate every
+ ** INSTEAD OF trigger into a BEFORE trigger. It simplifies code
+ ** elsewhere.
+ */
+ if (tr_tm == TK_INSTEAD){
+ tr_tm = TK_BEFORE;
+ }
+
+ /* Build the Trigger object */
+ pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));
+ if( pTrigger==0 ) goto trigger_cleanup;
+ pTrigger->name = zName;
+ zName = 0;
+ pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName);
+ pTrigger->pSchema = db->aDb[iDb].pSchema;
+ pTrigger->pTabSchema = pTab->pSchema;
+ pTrigger->op = op;
+ pTrigger->tr_tm = tr_tm==TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER;
+ pTrigger->pWhen = sqlite3ExprDup(db, pWhen);
+ pTrigger->pColumns = sqlite3IdListDup(db, pColumns);
+ sqlite3TokenCopy(db, &pTrigger->nameToken,pName);
+ assert( pParse->pNewTrigger==0 );
+ pParse->pNewTrigger = pTrigger;
+
+trigger_cleanup:
+ sqlite3_free(zName);
+ sqlite3SrcListDelete(pTableName);
+ sqlite3IdListDelete(pColumns);
+ sqlite3ExprDelete(pWhen);
+ if( !pParse->pNewTrigger ){
+ sqlite3DeleteTrigger(pTrigger);
+ }else{
+ assert( pParse->pNewTrigger==pTrigger );
+ }
+}
+
+/*
+** This routine is called after all of the trigger actions have been parsed
+** in order to complete the process of building the trigger.
+*/
+SQLITE_PRIVATE void sqlite3FinishTrigger(
+ Parse *pParse, /* Parser context */
+ TriggerStep *pStepList, /* The triggered program */
+ Token *pAll /* Token that describes the complete CREATE TRIGGER */
+){
+ Trigger *pTrig = 0; /* The trigger whose construction is finishing up */
+ sqlite3 *db = pParse->db; /* The database */
+ DbFixer sFix;
+ int iDb; /* Database containing the trigger */
+
+ pTrig = pParse->pNewTrigger;
+ pParse->pNewTrigger = 0;
+ if( pParse->nErr || !pTrig ) goto triggerfinish_cleanup;
+ iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
+ pTrig->step_list = pStepList;
+ while( pStepList ){
+ pStepList->pTrig = pTrig;
+ pStepList = pStepList->pNext;
+ }
+ if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", &pTrig->nameToken)
+ && sqlite3FixTriggerStep(&sFix, pTrig->step_list) ){
+ goto triggerfinish_cleanup;
+ }
+
+ /* if we are not initializing, and this trigger is not on a TEMP table,
+ ** build the sqlite_master entry
+ */
+ if( !db->init.busy ){
+ Vdbe *v;
+ char *z;
+
+ /* Make an entry in the sqlite_master table */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto triggerfinish_cleanup;
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n);
+ sqlite3NestedParse(pParse,
+ "INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pTrig->name,
+ pTrig->table, z);
+ sqlite3_free(z);
+ sqlite3ChangeCookie(pParse, iDb);
+ sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0, sqlite3MPrintf(
+ db, "type='trigger' AND name='%q'", pTrig->name), P4_DYNAMIC
+ );
+ }
+
+ if( db->init.busy ){
+ int n;
+ Table *pTab;
+ Trigger *pDel;
+ pDel = sqlite3HashInsert(&db->aDb[iDb].pSchema->trigHash,
+ pTrig->name, strlen(pTrig->name), pTrig);
+ if( pDel ){
+ assert( pDel==pTrig );
+ db->mallocFailed = 1;
+ goto triggerfinish_cleanup;
+ }
+ n = strlen(pTrig->table) + 1;
+ pTab = sqlite3HashFind(&pTrig->pTabSchema->tblHash, pTrig->table, n);
+ assert( pTab!=0 );
+ pTrig->pNext = pTab->pTrigger;
+ pTab->pTrigger = pTrig;
+ pTrig = 0;
+ }
+
+triggerfinish_cleanup:
+ sqlite3DeleteTrigger(pTrig);
+ assert( !pParse->pNewTrigger );
+ sqlite3DeleteTriggerStep(pStepList);
+}
+
+/*
+** Make a copy of all components of the given trigger step. This has
+** the effect of copying all Expr.token.z values into memory obtained
+** from sqlite3_malloc(). As initially created, the Expr.token.z values
+** all point to the input string that was fed to the parser. But that
+** string is ephemeral - it will go away as soon as the sqlite3_exec()
+** call that started the parser exits. This routine makes a persistent
+** copy of all the Expr.token.z strings so that the TriggerStep structure
+** will be valid even after the sqlite3_exec() call returns.
+*/
+static void sqlitePersistTriggerStep(sqlite3 *db, TriggerStep *p){
+ if( p->target.z ){
+ p->target.z = (u8*)sqlite3DbStrNDup(db, (char*)p->target.z, p->target.n);
+ p->target.dyn = 1;
+ }
+ if( p->pSelect ){
+ Select *pNew = sqlite3SelectDup(db, p->pSelect);
+ sqlite3SelectDelete(p->pSelect);
+ p->pSelect = pNew;
+ }
+ if( p->pWhere ){
+ Expr *pNew = sqlite3ExprDup(db, p->pWhere);
+ sqlite3ExprDelete(p->pWhere);
+ p->pWhere = pNew;
+ }
+ if( p->pExprList ){
+ ExprList *pNew = sqlite3ExprListDup(db, p->pExprList);
+ sqlite3ExprListDelete(p->pExprList);
+ p->pExprList = pNew;
+ }
+ if( p->pIdList ){
+ IdList *pNew = sqlite3IdListDup(db, p->pIdList);
+ sqlite3IdListDelete(p->pIdList);
+ p->pIdList = pNew;
+ }
+}
+
+/*
+** Turn a SELECT statement (that the pSelect parameter points to) into
+** a trigger step. Return a pointer to a TriggerStep structure.
+**
+** The parser calls this routine when it finds a SELECT statement in
+** body of a TRIGGER.
+*/
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){
+ TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep));
+ if( pTriggerStep==0 ) {
+ sqlite3SelectDelete(pSelect);
+ return 0;
+ }
+
+ pTriggerStep->op = TK_SELECT;
+ pTriggerStep->pSelect = pSelect;
+ pTriggerStep->orconf = OE_Default;
+ sqlitePersistTriggerStep(db, pTriggerStep);
+
+ return pTriggerStep;
+}
+
+/*
+** Build a trigger step out of an INSERT statement. Return a pointer
+** to the new trigger step.
+**
+** The parser calls this routine when it sees an INSERT inside the
+** body of a trigger.
+*/
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(
+ sqlite3 *db, /* The database connection */
+ Token *pTableName, /* Name of the table into which we insert */
+ IdList *pColumn, /* List of columns in pTableName to insert into */
+ ExprList *pEList, /* The VALUE clause: a list of values to be inserted */
+ Select *pSelect, /* A SELECT statement that supplies values */
+ int orconf /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
+){
+ TriggerStep *pTriggerStep;
+
+ assert(pEList == 0 || pSelect == 0);
+ assert(pEList != 0 || pSelect != 0 || db->mallocFailed);
+
+ pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep));
+ if( pTriggerStep ){
+ pTriggerStep->op = TK_INSERT;
+ pTriggerStep->pSelect = pSelect;
+ pTriggerStep->target = *pTableName;
+ pTriggerStep->pIdList = pColumn;
+ pTriggerStep->pExprList = pEList;
+ pTriggerStep->orconf = orconf;
+ sqlitePersistTriggerStep(db, pTriggerStep);
+ }else{
+ sqlite3IdListDelete(pColumn);
+ sqlite3ExprListDelete(pEList);
+ sqlite3SelectDelete(pSelect);
+ }
+
+ return pTriggerStep;
+}
+
+/*
+** Construct a trigger step that implements an UPDATE statement and return
+** a pointer to that trigger step. The parser calls this routine when it
+** sees an UPDATE statement inside the body of a CREATE TRIGGER.
+*/
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(
+ sqlite3 *db, /* The database connection */
+ Token *pTableName, /* Name of the table to be updated */
+ ExprList *pEList, /* The SET clause: list of column and new values */
+ Expr *pWhere, /* The WHERE clause */
+ int orconf /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
+){
+ TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep));
+ if( pTriggerStep==0 ){
+ sqlite3ExprListDelete(pEList);
+ sqlite3ExprDelete(pWhere);
+ return 0;
+ }
+
+ pTriggerStep->op = TK_UPDATE;
+ pTriggerStep->target = *pTableName;
+ pTriggerStep->pExprList = pEList;
+ pTriggerStep->pWhere = pWhere;
+ pTriggerStep->orconf = orconf;
+ sqlitePersistTriggerStep(db, pTriggerStep);
+
+ return pTriggerStep;
+}
+
+/*
+** Construct a trigger step that implements a DELETE statement and return
+** a pointer to that trigger step. The parser calls this routine when it
+** sees a DELETE statement inside the body of a CREATE TRIGGER.
+*/
+SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(
+ sqlite3 *db, /* Database connection */
+ Token *pTableName, /* The table from which rows are deleted */
+ Expr *pWhere /* The WHERE clause */
+){
+ TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep));
+ if( pTriggerStep==0 ){
+ sqlite3ExprDelete(pWhere);
+ return 0;
+ }
+
+ pTriggerStep->op = TK_DELETE;
+ pTriggerStep->target = *pTableName;
+ pTriggerStep->pWhere = pWhere;
+ pTriggerStep->orconf = OE_Default;
+ sqlitePersistTriggerStep(db, pTriggerStep);
+
+ return pTriggerStep;
+}
+
+/*
+** Recursively delete a Trigger structure
+*/
+SQLITE_PRIVATE void sqlite3DeleteTrigger(Trigger *pTrigger){
+ if( pTrigger==0 ) return;
+ sqlite3DeleteTriggerStep(pTrigger->step_list);
+ sqlite3_free(pTrigger->name);
+ sqlite3_free(pTrigger->table);
+ sqlite3ExprDelete(pTrigger->pWhen);
+ sqlite3IdListDelete(pTrigger->pColumns);
+ if( pTrigger->nameToken.dyn ) sqlite3_free((char*)pTrigger->nameToken.z);
+ sqlite3_free(pTrigger);
+}
+
+/*
+** This function is called to drop a trigger from the database schema.
+**
+** This may be called directly from the parser and therefore identifies
+** the trigger by name. The sqlite3DropTriggerPtr() routine does the
+** same job as this routine except it takes a pointer to the trigger
+** instead of the trigger name.
+**/
+SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){
+ Trigger *pTrigger = 0;
+ int i;
+ const char *zDb;
+ const char *zName;
+ int nName;
+ sqlite3 *db = pParse->db;
+
+ if( db->mallocFailed ) goto drop_trigger_cleanup;
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ goto drop_trigger_cleanup;
+ }
+
+ assert( pName->nSrc==1 );
+ zDb = pName->a[0].zDatabase;
+ zName = pName->a[0].zName;
+ nName = strlen(zName);
+ for(i=OMIT_TEMPDB; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ if( zDb && sqlite3StrICmp(db->aDb[j].zName, zDb) ) continue;
+ pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName, nName);
+ if( pTrigger ) break;
+ }
+ if( !pTrigger ){
+ if( !noErr ){
+ sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
+ }
+ goto drop_trigger_cleanup;
+ }
+ sqlite3DropTriggerPtr(pParse, pTrigger);
+
+drop_trigger_cleanup:
+ sqlite3SrcListDelete(pName);
+}
+
+/*
+** Return a pointer to the Table structure for the table that a trigger
+** is set on.
+*/
+static Table *tableOfTrigger(Trigger *pTrigger){
+ int n = strlen(pTrigger->table) + 1;
+ return sqlite3HashFind(&pTrigger->pTabSchema->tblHash, pTrigger->table, n);
+}
+
+
+/*
+** Drop a trigger given a pointer to that trigger.
+*/
+SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
+ Table *pTable;
+ Vdbe *v;
+ sqlite3 *db = pParse->db;
+ int iDb;
+
+ iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema);
+ assert( iDb>=0 && iDb<db->nDb );
+ pTable = tableOfTrigger(pTrigger);
+ assert( pTable );
+ assert( pTable->pSchema==pTrigger->pSchema || iDb==1 );
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code = SQLITE_DROP_TRIGGER;
+ const char *zDb = db->aDb[iDb].zName;
+ const char *zTab = SCHEMA_TABLE(iDb);
+ if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER;
+ if( sqlite3AuthCheck(pParse, code, pTrigger->name, pTable->zName, zDb) ||
+ sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
+ return;
+ }
+ }
+#endif
+
+ /* Generate code to destroy the database record of the trigger.
+ */
+ assert( pTable!=0 );
+ if( (v = sqlite3GetVdbe(pParse))!=0 ){
+ int base;
+ static const VdbeOpList dropTrigger[] = {
+ { OP_Rewind, 0, ADDR(9), 0},
+ { OP_String8, 0, 1, 0}, /* 1 */
+ { OP_Column, 0, 1, 2},
+ { OP_Ne, 2, ADDR(8), 1},
+ { OP_String8, 0, 1, 0}, /* 4: "trigger" */
+ { OP_Column, 0, 0, 2},
+ { OP_Ne, 2, ADDR(8), 1},
+ { OP_Delete, 0, 0, 0},
+ { OP_Next, 0, ADDR(1), 0}, /* 8 */
+ };
+
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3OpenMasterTable(pParse, iDb);
+ base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger);
+ sqlite3VdbeChangeP4(v, base+1, pTrigger->name, 0);
+ sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC);
+ sqlite3ChangeCookie(pParse, iDb);
+ sqlite3VdbeAddOp2(v, OP_Close, 0, 0);
+ sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->name, 0);
+ }
+}
+
+/*
+** Remove a trigger from the hash tables of the sqlite* pointer.
+*/
+SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const char *zName){
+ Trigger *pTrigger;
+ int nName = strlen(zName);
+ pTrigger = sqlite3HashInsert(&(db->aDb[iDb].pSchema->trigHash),
+ zName, nName, 0);
+ if( pTrigger ){
+ Table *pTable = tableOfTrigger(pTrigger);
+ assert( pTable!=0 );
+ if( pTable->pTrigger == pTrigger ){
+ pTable->pTrigger = pTrigger->pNext;
+ }else{
+ Trigger *cc = pTable->pTrigger;
+ while( cc ){
+ if( cc->pNext == pTrigger ){
+ cc->pNext = cc->pNext->pNext;
+ break;
+ }
+ cc = cc->pNext;
+ }
+ assert(cc);
+ }
+ sqlite3DeleteTrigger(pTrigger);
+ db->flags |= SQLITE_InternChanges;
+ }
+}
+
+/*
+** pEList is the SET clause of an UPDATE statement. Each entry
+** in pEList is of the format <id>=<expr>. If any of the entries
+** in pEList have an <id> which matches an identifier in pIdList,
+** then return TRUE. If pIdList==NULL, then it is considered a
+** wildcard that matches anything. Likewise if pEList==NULL then
+** it matches anything so always return true. Return false only
+** if there is no match.
+*/
+static int checkColumnOverLap(IdList *pIdList, ExprList *pEList){
+ int e;
+ if( !pIdList || !pEList ) return 1;
+ for(e=0; e<pEList->nExpr; e++){
+ if( sqlite3IdListIndex(pIdList, pEList->a[e].zName)>=0 ) return 1;
+ }
+ return 0;
+}
+
+/*
+** Return a bit vector to indicate what kind of triggers exist for operation
+** "op" on table pTab. If pChanges is not NULL then it is a list of columns
+** that are being updated. Triggers only match if the ON clause of the
+** trigger definition overlaps the set of columns being updated.
+**
+** The returned bit vector is some combination of TRIGGER_BEFORE and
+** TRIGGER_AFTER.
+*/
+SQLITE_PRIVATE int sqlite3TriggersExist(
+ Parse *pParse, /* Used to check for recursive triggers */
+ Table *pTab, /* The table the contains the triggers */
+ int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
+ ExprList *pChanges /* Columns that change in an UPDATE statement */
+){
+ Trigger *pTrigger;
+ int mask = 0;
+
+ pTrigger = IsVirtual(pTab) ? 0 : pTab->pTrigger;
+ while( pTrigger ){
+ if( pTrigger->op==op && checkColumnOverLap(pTrigger->pColumns, pChanges) ){
+ mask |= pTrigger->tr_tm;
+ }
+ pTrigger = pTrigger->pNext;
+ }
+ return mask;
+}
+
+/*
+** Convert the pStep->target token into a SrcList and return a pointer
+** to that SrcList.
+**
+** This routine adds a specific database name, if needed, to the target when
+** forming the SrcList. This prevents a trigger in one database from
+** referring to a target in another database. An exception is when the
+** trigger is in TEMP in which case it can refer to any other database it
+** wants.
+*/
+static SrcList *targetSrcList(
+ Parse *pParse, /* The parsing context */
+ TriggerStep *pStep /* The trigger containing the target token */
+){
+ Token sDb; /* Dummy database name token */
+ int iDb; /* Index of the database to use */
+ SrcList *pSrc; /* SrcList to be returned */
+
+ iDb = sqlite3SchemaToIndex(pParse->db, pStep->pTrig->pSchema);
+ if( iDb==0 || iDb>=2 ){
+ assert( iDb<pParse->db->nDb );
+ sDb.z = (u8*)pParse->db->aDb[iDb].zName;
+ sDb.n = strlen((char*)sDb.z);
+ pSrc = sqlite3SrcListAppend(pParse->db, 0, &sDb, &pStep->target);
+ } else {
+ pSrc = sqlite3SrcListAppend(pParse->db, 0, &pStep->target, 0);
+ }
+ return pSrc;
+}
+
+/*
+** Generate VDBE code for zero or more statements inside the body of a
+** trigger.
+*/
+static int codeTriggerProgram(
+ Parse *pParse, /* The parser context */
+ TriggerStep *pStepList, /* List of statements inside the trigger body */
+ int orconfin /* Conflict algorithm. (OE_Abort, etc) */
+){
+ TriggerStep * pTriggerStep = pStepList;
+ int orconf;
+ Vdbe *v = pParse->pVdbe;
+ sqlite3 *db = pParse->db;
+
+ assert( pTriggerStep!=0 );
+ assert( v!=0 );
+ sqlite3VdbeAddOp2(v, OP_ContextPush, 0, 0);
+ VdbeComment((v, "begin trigger %s", pStepList->pTrig->name));
+ while( pTriggerStep ){
+ orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
+ pParse->trigStack->orconf = orconf;
+ switch( pTriggerStep->op ){
+ case TK_SELECT: {
+ Select *ss = sqlite3SelectDup(db, pTriggerStep->pSelect);
+ if( ss ){
+ SelectDest dest;
+
+ sqlite3SelectDestInit(&dest, SRT_Discard, 0);
+ sqlite3SelectResolve(pParse, ss, 0);
+ sqlite3Select(pParse, ss, &dest, 0, 0, 0, 0);
+ sqlite3SelectDelete(ss);
+ }
+ break;
+ }
+ case TK_UPDATE: {
+ SrcList *pSrc;
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
+ sqlite3Update(pParse, pSrc,
+ sqlite3ExprListDup(db, pTriggerStep->pExprList),
+ sqlite3ExprDup(db, pTriggerStep->pWhere), orconf);
+ sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
+ break;
+ }
+ case TK_INSERT: {
+ SrcList *pSrc;
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
+ sqlite3Insert(pParse, pSrc,
+ sqlite3ExprListDup(db, pTriggerStep->pExprList),
+ sqlite3SelectDup(db, pTriggerStep->pSelect),
+ sqlite3IdListDup(db, pTriggerStep->pIdList), orconf);
+ sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
+ break;
+ }
+ case TK_DELETE: {
+ SrcList *pSrc;
+ sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqlite3DeleteFrom(pParse, pSrc,
+ sqlite3ExprDup(db, pTriggerStep->pWhere));
+ sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
+ break;
+ }
+ default:
+ assert(0);
+ }
+ pTriggerStep = pTriggerStep->pNext;
+ }
+ sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0);
+ VdbeComment((v, "end trigger %s", pStepList->pTrig->name));
+
+ return 0;
+}
+
+/*
+** This is called to code FOR EACH ROW triggers.
+**
+** When the code that this function generates is executed, the following
+** must be true:
+**
+** 1. No cursors may be open in the main database. (But newIdx and oldIdx
+** can be indices of cursors in temporary tables. See below.)
+**
+** 2. If the triggers being coded are ON INSERT or ON UPDATE triggers, then
+** a temporary vdbe cursor (index newIdx) must be open and pointing at
+** a row containing values to be substituted for new.* expressions in the
+** trigger program(s).
+**
+** 3. If the triggers being coded are ON DELETE or ON UPDATE triggers, then
+** a temporary vdbe cursor (index oldIdx) must be open and pointing at
+** a row containing values to be substituted for old.* expressions in the
+** trigger program(s).
+**
+** If they are not NULL, the piOldColMask and piNewColMask output variables
+** are set to values that describe the columns used by the trigger program
+** in the OLD.* and NEW.* tables respectively. If column N of the
+** pseudo-table is read at least once, the corresponding bit of the output
+** mask is set. If a column with an index greater than 32 is read, the
+** output mask is set to the special value 0xffffffff.
+**
+*/
+SQLITE_PRIVATE int sqlite3CodeRowTrigger(
+ Parse *pParse, /* Parse context */
+ int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
+ ExprList *pChanges, /* Changes list for any UPDATE OF triggers */
+ int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */
+ Table *pTab, /* The table to code triggers from */
+ int newIdx, /* The indice of the "new" row to access */
+ int oldIdx, /* The indice of the "old" row to access */
+ int orconf, /* ON CONFLICT policy */
+ int ignoreJump, /* Instruction to jump to for RAISE(IGNORE) */
+ u32 *piOldColMask, /* OUT: Mask of columns used from the OLD.* table */
+ u32 *piNewColMask /* OUT: Mask of columns used from the NEW.* table */
+){
+ Trigger *p;
+ sqlite3 *db = pParse->db;
+ TriggerStack trigStackEntry;
+
+ trigStackEntry.oldColMask = 0;
+ trigStackEntry.newColMask = 0;
+
+ assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE);
+ assert(tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER );
+
+ assert(newIdx != -1 || oldIdx != -1);
+
+ for(p=pTab->pTrigger; p; p=p->pNext){
+ int fire_this = 0;
+
+ /* Determine whether we should code this trigger */
+ if(
+ p->op==op &&
+ p->tr_tm==tr_tm &&
+ (p->pSchema==p->pTabSchema || p->pSchema==db->aDb[1].pSchema) &&
+ (op!=TK_UPDATE||!p->pColumns||checkColumnOverLap(p->pColumns,pChanges))
+ ){
+ TriggerStack *pS; /* Pointer to trigger-stack entry */
+ for(pS=pParse->trigStack; pS && p!=pS->pTrigger; pS=pS->pNext){}
+ if( !pS ){
+ fire_this = 1;
+ }
+#if 0 /* Give no warning for recursive triggers. Just do not do them */
+ else{
+ sqlite3ErrorMsg(pParse, "recursive triggers not supported (%s)",
+ p->name);
+ return SQLITE_ERROR;
+ }
+#endif
+ }
+
+ if( fire_this ){
+ int endTrigger;
+ Expr * whenExpr;
+ AuthContext sContext;
+ NameContext sNC;
+
+#ifndef SQLITE_OMIT_TRACE
+ sqlite3VdbeAddOp4(pParse->pVdbe, OP_Trace, 0, 0, 0,
+ sqlite3MPrintf(db, "-- TRIGGER %s", p->name),
+ P4_DYNAMIC);
+#endif
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+
+ /* Push an entry on to the trigger stack */
+ trigStackEntry.pTrigger = p;
+ trigStackEntry.newIdx = newIdx;
+ trigStackEntry.oldIdx = oldIdx;
+ trigStackEntry.pTab = pTab;
+ trigStackEntry.pNext = pParse->trigStack;
+ trigStackEntry.ignoreJump = ignoreJump;
+ pParse->trigStack = &trigStackEntry;
+ sqlite3AuthContextPush(pParse, &sContext, p->name);
+
+ /* code the WHEN clause */
+ endTrigger = sqlite3VdbeMakeLabel(pParse->pVdbe);
+ whenExpr = sqlite3ExprDup(db, p->pWhen);
+ if( db->mallocFailed || sqlite3ExprResolveNames(&sNC, whenExpr) ){
+ pParse->trigStack = trigStackEntry.pNext;
+ sqlite3ExprDelete(whenExpr);
+ return 1;
+ }
+ sqlite3ExprIfFalse(pParse, whenExpr, endTrigger, SQLITE_JUMPIFNULL);
+ sqlite3ExprDelete(whenExpr);
+
+ codeTriggerProgram(pParse, p->step_list, orconf);
+
+ /* Pop the entry off the trigger stack */
+ pParse->trigStack = trigStackEntry.pNext;
+ sqlite3AuthContextPop(&sContext);
+
+ sqlite3VdbeResolveLabel(pParse->pVdbe, endTrigger);
+ }
+ }
+ if( piOldColMask ) *piOldColMask |= trigStackEntry.oldColMask;
+ if( piNewColMask ) *piNewColMask |= trigStackEntry.newColMask;
+ return 0;
+}
+#endif /* !defined(SQLITE_OMIT_TRIGGER) */
+
+/************** End of trigger.c *********************************************/
+/************** Begin file update.c ******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C code routines that are called by the parser
+** to handle UPDATE statements.
+**
+** $Id: update.c,v 1.178 2008/04/28 18:46:43 drh Exp $
+*/
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/* Forward declaration */
+static void updateVirtualTable(
+ Parse *pParse, /* The parsing context */
+ SrcList *pSrc, /* The virtual table to be modified */
+ Table *pTab, /* The virtual table */
+ ExprList *pChanges, /* The columns to change in the UPDATE statement */
+ Expr *pRowidExpr, /* Expression used to recompute the rowid */
+ int *aXRef, /* Mapping from columns of pTab to entries in pChanges */
+ Expr *pWhere /* WHERE clause of the UPDATE statement */
+);
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+/*
+** The most recently coded instruction was an OP_Column to retrieve the
+** i-th column of table pTab. This routine sets the P4 parameter of the
+** OP_Column to the default value, if any.
+**
+** The default value of a column is specified by a DEFAULT clause in the
+** column definition. This was either supplied by the user when the table
+** was created, or added later to the table definition by an ALTER TABLE
+** command. If the latter, then the row-records in the table btree on disk
+** may not contain a value for the column and the default value, taken
+** from the P4 parameter of the OP_Column instruction, is returned instead.
+** If the former, then all row-records are guaranteed to include a value
+** for the column and the P4 value is not required.
+**
+** Column definitions created by an ALTER TABLE command may only have
+** literal default values specified: a number, null or a string. (If a more
+** complicated default expression value was provided, it is evaluated
+** when the ALTER TABLE is executed and one of the literal values written
+** into the sqlite_master table.)
+**
+** Therefore, the P4 parameter is only required if the default value for
+** the column is a literal number, string or null. The sqlite3ValueFromExpr()
+** function is capable of transforming these types of expressions into
+** sqlite3_value objects.
+*/
+SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i){
+ if( pTab && !pTab->pSelect ){
+ sqlite3_value *pValue;
+ u8 enc = ENC(sqlite3VdbeDb(v));
+ Column *pCol = &pTab->aCol[i];
+ VdbeComment((v, "%s.%s", pTab->zName, pCol->zName));
+ assert( i<pTab->nCol );
+ sqlite3ValueFromExpr(sqlite3VdbeDb(v), pCol->pDflt, enc,
+ pCol->affinity, &pValue);
+ if( pValue ){
+ sqlite3VdbeChangeP4(v, -1, (const char *)pValue, P4_MEM);
+ }
+ }
+}
+
+/*
+** Process an UPDATE statement.
+**
+** UPDATE OR IGNORE table_wxyz SET a=b, c=d WHERE e<5 AND f NOT NULL;
+** \_______/ \________/ \______/ \________________/
+* onError pTabList pChanges pWhere
+*/
+SQLITE_PRIVATE void sqlite3Update(
+ Parse *pParse, /* The parser context */
+ SrcList *pTabList, /* The table in which we should change things */
+ ExprList *pChanges, /* Things to be changed */
+ Expr *pWhere, /* The WHERE clause. May be null */
+ int onError /* How to handle constraint errors */
+){
+ int i, j; /* Loop counters */
+ Table *pTab; /* The table to be updated */
+ int addr = 0; /* VDBE instruction address of the start of the loop */
+ WhereInfo *pWInfo; /* Information about the WHERE clause */
+ Vdbe *v; /* The virtual database engine */
+ Index *pIdx; /* For looping over indices */
+ int nIdx; /* Number of indices that need updating */
+ int iCur; /* VDBE Cursor number of pTab */
+ sqlite3 *db; /* The database structure */
+ int *aRegIdx = 0; /* One register assigned to each index to be updated */
+ int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the
+ ** an expression for the i-th column of the table.
+ ** aXRef[i]==-1 if the i-th column is not changed. */
+ int chngRowid; /* True if the record number is being changed */
+ Expr *pRowidExpr = 0; /* Expression defining the new record number */
+ int openAll = 0; /* True if all indices need to be opened */
+ AuthContext sContext; /* The authorization context */
+ NameContext sNC; /* The name-context to resolve expressions in */
+ int iDb; /* Database containing the table being updated */
+ int j1; /* Addresses of jump instructions */
+ int okOnePass; /* True for one-pass algorithm without the FIFO */
+
+#ifndef SQLITE_OMIT_TRIGGER
+ int isView; /* Trying to update a view */
+ int triggers_exist = 0; /* True if any row triggers exist */
+#endif
+ int iBeginAfterTrigger; /* Address of after trigger program */
+ int iEndAfterTrigger; /* Exit of after trigger program */
+ int iBeginBeforeTrigger; /* Address of before trigger program */
+ int iEndBeforeTrigger; /* Exit of before trigger program */
+ u32 old_col_mask = 0; /* Mask of OLD.* columns in use */
+ u32 new_col_mask = 0; /* Mask of NEW.* columns in use */
+
+ int newIdx = -1; /* index of trigger "new" temp table */
+ int oldIdx = -1; /* index of trigger "old" temp table */
+
+ /* Register Allocations */
+ int regRowCount = 0; /* A count of rows changed */
+ int regOldRowid; /* The old rowid */
+ int regNewRowid; /* The new rowid */
+ int regData; /* New data for the row */
+
+ sContext.pParse = 0;
+ db = pParse->db;
+ if( pParse->nErr || db->mallocFailed ){
+ goto update_cleanup;
+ }
+ assert( pTabList->nSrc==1 );
+
+ /* Locate the table which we want to update.
+ */
+ pTab = sqlite3SrcListLookup(pParse, pTabList);
+ if( pTab==0 ) goto update_cleanup;
+ iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+
+ /* Figure out if we have any triggers and if the table being
+ ** updated is a view
+ */
+#ifndef SQLITE_OMIT_TRIGGER
+ triggers_exist = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges);
+ isView = pTab->pSelect!=0;
+#else
+# define triggers_exist 0
+# define isView 0
+#endif
+#ifdef SQLITE_OMIT_VIEW
+# undef isView
+# define isView 0
+#endif
+
+ if( sqlite3IsReadOnly(pParse, pTab, triggers_exist) ){
+ goto update_cleanup;
+ }
+ if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ goto update_cleanup;
+ }
+ aXRef = sqlite3DbMallocRaw(db, sizeof(int) * pTab->nCol );
+ if( aXRef==0 ) goto update_cleanup;
+ for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;
+
+ /* If there are FOR EACH ROW triggers, allocate cursors for the
+ ** special OLD and NEW tables
+ */
+ if( triggers_exist ){
+ newIdx = pParse->nTab++;
+ oldIdx = pParse->nTab++;
+ }
+
+ /* Allocate a cursors for the main database table and for all indices.
+ ** The index cursors might not be used, but if they are used they
+ ** need to occur right after the database cursor. So go ahead and
+ ** allocate enough space, just in case.
+ */
+ pTabList->a[0].iCursor = iCur = pParse->nTab++;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ pParse->nTab++;
+ }
+
+ /* Initialize the name-context */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ sNC.pSrcList = pTabList;
+
+ /* Resolve the column names in all the expressions of the
+ ** of the UPDATE statement. Also find the column index
+ ** for each column to be updated in the pChanges array. For each
+ ** column to be updated, make sure we have authorization to change
+ ** that column.
+ */
+ chngRowid = 0;
+ for(i=0; i<pChanges->nExpr; i++){
+ if( sqlite3ExprResolveNames(&sNC, pChanges->a[i].pExpr) ){
+ goto update_cleanup;
+ }
+ for(j=0; j<pTab->nCol; j++){
+ if( sqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zName)==0 ){
+ if( j==pTab->iPKey ){
+ chngRowid = 1;
+ pRowidExpr = pChanges->a[i].pExpr;
+ }
+ aXRef[j] = i;
+ break;
+ }
+ }
+ if( j>=pTab->nCol ){
+ if( sqlite3IsRowid(pChanges->a[i].zName) ){
+ chngRowid = 1;
+ pRowidExpr = pChanges->a[i].pExpr;
+ }else{
+ sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zName);
+ goto update_cleanup;
+ }
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int rc;
+ rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName,
+ pTab->aCol[j].zName, db->aDb[iDb].zName);
+ if( rc==SQLITE_DENY ){
+ goto update_cleanup;
+ }else if( rc==SQLITE_IGNORE ){
+ aXRef[j] = -1;
+ }
+ }
+#endif
+ }
+
+ /* Allocate memory for the array aRegIdx[]. There is one entry in the
+ ** array for each index associated with table being updated. Fill in
+ ** the value with a register number for indices that are to be used
+ ** and with zero for unused indices.
+ */
+ for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){}
+ if( nIdx>0 ){
+ aRegIdx = sqlite3DbMallocRaw(db, sizeof(Index*) * nIdx );
+ if( aRegIdx==0 ) goto update_cleanup;
+ }
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ int reg;
+ if( chngRowid ){
+ reg = ++pParse->nMem;
+ }else{
+ reg = 0;
+ for(i=0; i<pIdx->nColumn; i++){
+ if( aXRef[pIdx->aiColumn[i]]>=0 ){
+ reg = ++pParse->nMem;
+ break;
+ }
+ }
+ }
+ aRegIdx[j] = reg;
+ }
+
+ /* Allocate a block of register used to store the change record
+ ** sent to sqlite3GenerateConstraintChecks(). There are either
+ ** one or two registers for holding the rowid. One rowid register
+ ** is used if chngRowid is false and two are used if chngRowid is
+ ** true. Following these are pTab->nCol register holding column
+ ** data.
+ */
+ regOldRowid = regNewRowid = pParse->nMem + 1;
+ pParse->nMem += pTab->nCol + 1;
+ if( chngRowid ){
+ regNewRowid++;
+ pParse->nMem++;
+ }
+ regData = regNewRowid+1;
+
+
+ /* Begin generating code.
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto update_cleanup;
+ if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
+ sqlite3BeginWriteOperation(pParse, 1, iDb);
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ /* Virtual tables must be handled separately */
+ if( IsVirtual(pTab) ){
+ updateVirtualTable(pParse, pTabList, pTab, pChanges, pRowidExpr, aXRef,
+ pWhere);
+ pWhere = 0;
+ pTabList = 0;
+ goto update_cleanup;
+ }
+#endif
+
+ /* Start the view context
+ */
+ if( isView ){
+ sqlite3AuthContextPush(pParse, &sContext, pTab->zName);
+ }
+
+ /* Generate the code for triggers.
+ */
+ if( triggers_exist ){
+ int iGoto;
+
+ /* Create pseudo-tables for NEW and OLD
+ */
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, pTab->nCol);
+ sqlite3VdbeAddOp2(v, OP_OpenPseudo, oldIdx, 0);
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, pTab->nCol);
+ sqlite3VdbeAddOp2(v, OP_OpenPseudo, newIdx, 0);
+
+ iGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
+ addr = sqlite3VdbeMakeLabel(v);
+ iBeginBeforeTrigger = sqlite3VdbeCurrentAddr(v);
+ if( sqlite3CodeRowTrigger(pParse, TK_UPDATE, pChanges, TRIGGER_BEFORE, pTab,
+ newIdx, oldIdx, onError, addr, &old_col_mask, &new_col_mask) ){
+ goto update_cleanup;
+ }
+ iEndBeforeTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
+ iBeginAfterTrigger = sqlite3VdbeCurrentAddr(v);
+ if( sqlite3CodeRowTrigger(pParse, TK_UPDATE, pChanges, TRIGGER_AFTER, pTab,
+ newIdx, oldIdx, onError, addr, &old_col_mask, &new_col_mask) ){
+ goto update_cleanup;
+ }
+ iEndAfterTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
+ sqlite3VdbeJumpHere(v, iGoto);
+ }
+
+ /* If we are trying to update a view, realize that view into
+ ** a ephemeral table.
+ */
+ if( isView ){
+ sqlite3MaterializeView(pParse, pTab->pSelect, pWhere, iCur);
+ }
+
+ /* Resolve the column names in all the expressions in the
+ ** WHERE clause.
+ */
+ if( sqlite3ExprResolveNames(&sNC, pWhere) ){
+ goto update_cleanup;
+ }
+
+ /* Begin the database scan
+ */
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regOldRowid);
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0,
+ WHERE_ONEPASS_DESIRED);
+ if( pWInfo==0 ) goto update_cleanup;
+ okOnePass = pWInfo->okOnePass;
+
+ /* Remember the rowid of every item to be updated.
+ */
+ sqlite3VdbeAddOp2(v, IsVirtual(pTab)?OP_VRowid:OP_Rowid, iCur, regOldRowid);
+ if( !okOnePass ) sqlite3VdbeAddOp2(v, OP_FifoWrite, regOldRowid, 0);
+
+ /* End the database scan loop.
+ */
+ sqlite3WhereEnd(pWInfo);
+
+ /* Initialize the count of updated rows
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
+ regRowCount = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
+ }
+
+ if( !isView && !IsVirtual(pTab) ){
+ /*
+ ** Open every index that needs updating. Note that if any
+ ** index could potentially invoke a REPLACE conflict resolution
+ ** action, then we need to open all indices because we might need
+ ** to be deleting some records.
+ */
+ if( !okOnePass ) sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite);
+ if( onError==OE_Replace ){
+ openAll = 1;
+ }else{
+ openAll = 0;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( pIdx->onError==OE_Replace ){
+ openAll = 1;
+ break;
+ }
+ }
+ }
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aRegIdx[i]>0 ){
+ KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx);
+ sqlite3VdbeAddOp4(v, OP_OpenWrite, iCur+i+1, pIdx->tnum, iDb,
+ (char*)pKey, P4_KEYINFO_HANDOFF);
+ assert( pParse->nTab>iCur+i+1 );
+ }
+ }
+ }
+
+ /* Jump back to this point if a trigger encounters an IGNORE constraint. */
+ if( triggers_exist ){
+ sqlite3VdbeResolveLabel(v, addr);
+ }
+
+ /* Top of the update loop */
+ if( okOnePass ){
+ int a1 = sqlite3VdbeAddOp1(v, OP_NotNull, regOldRowid);
+ addr = sqlite3VdbeAddOp0(v, OP_Goto);
+ sqlite3VdbeJumpHere(v, a1);
+ }else{
+ addr = sqlite3VdbeAddOp2(v, OP_FifoRead, regOldRowid, 0);
+ }
+
+ if( triggers_exist ){
+ int regRowid;
+ int regRow;
+ int regCols;
+
+ /* Make cursor iCur point to the record that is being updated.
+ */
+ sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
+
+ /* Generate the OLD table
+ */
+ regRowid = sqlite3GetTempReg(pParse);
+ regRow = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid);
+ if( !old_col_mask ){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regRow);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_RowData, iCur, regRow);
+ }
+ sqlite3VdbeAddOp3(v, OP_Insert, oldIdx, regRow, regRowid);
+
+ /* Generate the NEW table
+ */
+ if( chngRowid ){
+ sqlite3ExprCodeAndCache(pParse, pRowidExpr, regRowid);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid);
+ }
+ regCols = sqlite3GetTempRange(pParse, pTab->nCol);
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i);
+ continue;
+ }
+ j = aXRef[i];
+ if( new_col_mask&((u32)1<<i) || new_col_mask==0xffffffff ){
+ if( j<0 ){
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regCols+i);
+ sqlite3ColumnDefault(v, pTab, i);
+ }else{
+ sqlite3ExprCodeAndCache(pParse, pChanges->a[j].pExpr, regCols+i);
+ }
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i);
+ }
+ }
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regCols, pTab->nCol, regRow);
+ if( !isView ){
+ sqlite3TableAffinityStr(v, pTab);
+ sqlite3ExprCacheAffinityChange(pParse, regCols, pTab->nCol);
+ }
+ sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol);
+ if( pParse->nErr ) goto update_cleanup;
+ sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRow, regRowid);
+ sqlite3ReleaseTempReg(pParse, regRowid);
+ sqlite3ReleaseTempReg(pParse, regRow);
+
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginBeforeTrigger);
+ sqlite3VdbeJumpHere(v, iEndBeforeTrigger);
+ }
+
+ if( !isView && !IsVirtual(pTab) ){
+ /* Loop over every record that needs updating. We have to load
+ ** the old data for each record to be updated because some columns
+ ** might not change and we will need to copy the old value.
+ ** Also, the old data is needed to delete the old index entries.
+ ** So make the cursor point at the old record.
+ */
+ sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
+
+ /* If the record number will change, push the record number as it
+ ** will be after the update. (The old record number is currently
+ ** on top of the stack.)
+ */
+ if( chngRowid ){
+ sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
+ sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid);
+ }
+
+ /* Compute new data for this record.
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regData+i);
+ continue;
+ }
+ j = aXRef[i];
+ if( j<0 ){
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regData+i);
+ sqlite3ColumnDefault(v, pTab, i);
+ }else{
+ sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regData+i);
+ }
+ }
+
+ /* Do constraint checks
+ */
+ sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid,
+ aRegIdx, chngRowid, 1,
+ onError, addr);
+
+ /* Delete the old indices for the current record.
+ */
+ j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid);
+ sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx);
+
+ /* If changing the record number, delete the old record.
+ */
+ if( chngRowid ){
+ sqlite3VdbeAddOp2(v, OP_Delete, iCur, 0);
+ }
+ sqlite3VdbeJumpHere(v, j1);
+
+ /* Create the new index entries and the new record.
+ */
+ sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid,
+ aRegIdx, chngRowid, 1, -1, 0);
+ }
+
+ /* Increment the row counter
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack){
+ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
+ }
+
+ /* If there are triggers, close all the cursors after each iteration
+ ** through the loop. The fire the after triggers.
+ */
+ if( triggers_exist ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginAfterTrigger);
+ sqlite3VdbeJumpHere(v, iEndAfterTrigger);
+ }
+
+ /* Repeat the above with the next record to be updated, until
+ ** all record selected by the WHERE clause have been updated.
+ */
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
+ sqlite3VdbeJumpHere(v, addr);
+
+ /* Close all tables */
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aRegIdx[i]>0 ){
+ sqlite3VdbeAddOp2(v, OP_Close, iCur+i+1, 0);
+ }
+ }
+ sqlite3VdbeAddOp2(v, OP_Close, iCur, 0);
+ if( triggers_exist ){
+ sqlite3VdbeAddOp2(v, OP_Close, newIdx, 0);
+ sqlite3VdbeAddOp2(v, OP_Close, oldIdx, 0);
+ }
+
+ /*
+ ** Return the number of rows that were changed. If this routine is
+ ** generating code because of a call to sqlite3NestedParse(), do not
+ ** invoke the callback function.
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack && pParse->nested==0 ){
+ sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", P4_STATIC);
+ }
+
+update_cleanup:
+ sqlite3AuthContextPop(&sContext);
+ sqlite3_free(aRegIdx);
+ sqlite3_free(aXRef);
+ sqlite3SrcListDelete(pTabList);
+ sqlite3ExprListDelete(pChanges);
+ sqlite3ExprDelete(pWhere);
+ return;
+}
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Generate code for an UPDATE of a virtual table.
+**
+** The strategy is that we create an ephemerial table that contains
+** for each row to be changed:
+**
+** (A) The original rowid of that row.
+** (B) The revised rowid for the row. (note1)
+** (C) The content of every column in the row.
+**
+** Then we loop over this ephemeral table and for each row in
+** the ephermeral table call VUpdate.
+**
+** When finished, drop the ephemeral table.
+**
+** (note1) Actually, if we know in advance that (A) is always the same
+** as (B) we only store (A), then duplicate (A) when pulling
+** it out of the ephemeral table before calling VUpdate.
+*/
+static void updateVirtualTable(
+ Parse *pParse, /* The parsing context */
+ SrcList *pSrc, /* The virtual table to be modified */
+ Table *pTab, /* The virtual table */
+ ExprList *pChanges, /* The columns to change in the UPDATE statement */
+ Expr *pRowid, /* Expression used to recompute the rowid */
+ int *aXRef, /* Mapping from columns of pTab to entries in pChanges */
+ Expr *pWhere /* WHERE clause of the UPDATE statement */
+){
+ Vdbe *v = pParse->pVdbe; /* Virtual machine under construction */
+ ExprList *pEList = 0; /* The result set of the SELECT statement */
+ Select *pSelect = 0; /* The SELECT statement */
+ Expr *pExpr; /* Temporary expression */
+ int ephemTab; /* Table holding the result of the SELECT */
+ int i; /* Loop counter */
+ int addr; /* Address of top of loop */
+ int iReg; /* First register in set passed to OP_VUpdate */
+ sqlite3 *db = pParse->db; /* Database connection */
+ const char *pVtab = (const char*)pTab->pVtab;
+ SelectDest dest;
+
+ /* Construct the SELECT statement that will find the new values for
+ ** all updated rows.
+ */
+ pEList = sqlite3ExprListAppend(pParse, 0,
+ sqlite3CreateIdExpr(pParse, "_rowid_"), 0);
+ if( pRowid ){
+ pEList = sqlite3ExprListAppend(pParse, pEList,
+ sqlite3ExprDup(db, pRowid), 0);
+ }
+ assert( pTab->iPKey<0 );
+ for(i=0; i<pTab->nCol; i++){
+ if( aXRef[i]>=0 ){
+ pExpr = sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr);
+ }else{
+ pExpr = sqlite3CreateIdExpr(pParse, pTab->aCol[i].zName);
+ }
+ pEList = sqlite3ExprListAppend(pParse, pEList, pExpr, 0);
+ }
+ pSelect = sqlite3SelectNew(pParse, pEList, pSrc, pWhere, 0, 0, 0, 0, 0, 0);
+
+ /* Create the ephemeral table into which the update results will
+ ** be stored.
+ */
+ assert( v );
+ ephemTab = pParse->nTab++;
+ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, pTab->nCol+1+(pRowid!=0));
+
+ /* fill the ephemeral table
+ */
+ sqlite3SelectDestInit(&dest, SRT_Table, ephemTab);
+ sqlite3Select(pParse, pSelect, &dest, 0, 0, 0, 0);
+
+ /* Generate code to scan the ephemeral table and call VUpdate. */
+ iReg = ++pParse->nMem;
+ pParse->nMem += pTab->nCol+1;
+ sqlite3VdbeAddOp2(v, OP_Rewind, ephemTab, 0);
+ addr = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp3(v, OP_Column, ephemTab, 0, iReg);
+ sqlite3VdbeAddOp3(v, OP_Column, ephemTab, (pRowid?1:0), iReg+1);
+ for(i=0; i<pTab->nCol; i++){
+ sqlite3VdbeAddOp3(v, OP_Column, ephemTab, i+1+(pRowid!=0), iReg+2+i);
+ }
+ sqlite3VtabMakeWritable(pParse, pTab);
+ sqlite3VdbeAddOp4(v, OP_VUpdate, 0, pTab->nCol+2, iReg, pVtab, P4_VTAB);
+ sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr);
+ sqlite3VdbeJumpHere(v, addr-1);
+ sqlite3VdbeAddOp2(v, OP_Close, ephemTab, 0);
+
+ /* Cleanup */
+ sqlite3SelectDelete(pSelect);
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+/* Make sure "isView" gets undefined in case this file becomes part of
+** the amalgamation - so that subsequent files do not see isView as a
+** macro. */
+#undef isView
+
+/************** End of update.c **********************************************/
+/************** Begin file vacuum.c ******************************************/
+/*
+** 2003 April 6
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to implement the VACUUM command.
+**
+** Most of the code in this file may be omitted by defining the
+** SQLITE_OMIT_VACUUM macro.
+**
+** $Id: vacuum.c,v 1.78 2008/04/30 16:38:23 drh Exp $
+*/
+
+#if !defined(SQLITE_OMIT_VACUUM) && !defined(SQLITE_OMIT_ATTACH)
+/*
+** Execute zSql on database db. Return an error code.
+*/
+static int execSql(sqlite3 *db, const char *zSql){
+ sqlite3_stmt *pStmt;
+ if( !zSql ){
+ return SQLITE_NOMEM;
+ }
+ if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){
+ return sqlite3_errcode(db);
+ }
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){}
+ return sqlite3_finalize(pStmt);
+}
+
+/*
+** Execute zSql on database db. The statement returns exactly
+** one column. Execute this as SQL on the same database.
+*/
+static int execExecSql(sqlite3 *db, const char *zSql){
+ sqlite3_stmt *pStmt;
+ int rc;
+
+ rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ rc = execSql(db, (char*)sqlite3_column_text(pStmt, 0));
+ if( rc!=SQLITE_OK ){
+ sqlite3_finalize(pStmt);
+ return rc;
+ }
+ }
+
+ return sqlite3_finalize(pStmt);
+}
+
+/*
+** The non-standard VACUUM command is used to clean up the database,
+** collapse free space, etc. It is modelled after the VACUUM command
+** in PostgreSQL.
+**
+** In version 1.0.x of SQLite, the VACUUM command would call
+** gdbm_reorganize() on all the database tables. But beginning
+** with 2.0.0, SQLite no longer uses GDBM so this command has
+** become a no-op.
+*/
+SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse){
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp2(v, OP_Vacuum, 0, 0);
+ }
+ return;
+}
+
+/*
+** This routine implements the OP_Vacuum opcode of the VDBE.
+*/
+SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){
+ int rc = SQLITE_OK; /* Return code from service routines */
+ Btree *pMain; /* The database being vacuumed */
+ Btree *pTemp; /* The temporary database we vacuum into */
+ char *zSql = 0; /* SQL statements */
+ int saved_flags; /* Saved value of the db->flags */
+ Db *pDb = 0; /* Database to detach at end of vacuum */
+ int nRes;
+
+ /* Save the current value of the write-schema flag before setting it. */
+ saved_flags = db->flags;
+ db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks;
+
+ if( !db->autoCommit ){
+ sqlite3SetString(pzErrMsg, "cannot VACUUM from within a transaction",
+ (char*)0);
+ rc = SQLITE_ERROR;
+ goto end_of_vacuum;
+ }
+ pMain = db->aDb[0].pBt;
+
+ /* Attach the temporary database as 'vacuum_db'. The synchronous pragma
+ ** can be set to 'off' for this file, as it is not recovered if a crash
+ ** occurs anyway. The integrity of the database is maintained by a
+ ** (possibly synchronous) transaction opened on the main database before
+ ** sqlite3BtreeCopyFile() is called.
+ **
+ ** An optimisation would be to use a non-journaled pager.
+ ** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but
+ ** that actually made the VACUUM run slower. Very little journalling
+ ** actually occurs when doing a vacuum since the vacuum_db is initially
+ ** empty. Only the journal header is written. Apparently it takes more
+ ** time to parse and run the PRAGMA to turn journalling off than it does
+ ** to write the journal header file.
+ */
+ zSql = "ATTACH '' AS vacuum_db;";
+ rc = execSql(db, zSql);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ pDb = &db->aDb[db->nDb-1];
+ assert( strcmp(db->aDb[db->nDb-1].zName,"vacuum_db")==0 );
+ pTemp = db->aDb[db->nDb-1].pBt;
+
+ nRes = sqlite3BtreeGetReserve(pMain);
+ if( sqlite3BtreeSetPageSize(pTemp, sqlite3BtreeGetPageSize(pMain), nRes)
+ || sqlite3BtreeSetPageSize(pTemp, db->nextPagesize, nRes)
+ || db->mallocFailed
+ ){
+ rc = SQLITE_NOMEM;
+ goto end_of_vacuum;
+ }
+ rc = execSql(db, "PRAGMA vacuum_db.synchronous=OFF");
+ if( rc!=SQLITE_OK ){
+ goto end_of_vacuum;
+ }
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ sqlite3BtreeSetAutoVacuum(pTemp, db->nextAutovac>=0 ? db->nextAutovac :
+ sqlite3BtreeGetAutoVacuum(pMain));
+#endif
+
+ /* Begin a transaction */
+ rc = execSql(db, "BEGIN EXCLUSIVE;");
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+ /* Query the schema of the main database. Create a mirror schema
+ ** in the temporary database.
+ */
+ rc = execExecSql(db,
+ "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) "
+ " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'"
+ " AND rootpage>0"
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)"
+ " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' ");
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21) "
+ " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'");
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+ /* Loop through the tables in the main database. For each, do
+ ** an "INSERT INTO vacuum_db.xxx SELECT * FROM xxx;" to copy
+ ** the contents to the temporary database.
+ */
+ rc = execExecSql(db,
+ "SELECT 'INSERT INTO vacuum_db.' || quote(name) "
+ "|| ' SELECT * FROM ' || quote(name) || ';'"
+ "FROM sqlite_master "
+ "WHERE type = 'table' AND name!='sqlite_sequence' "
+ " AND rootpage>0"
+
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+ /* Copy over the sequence table
+ */
+ rc = execExecSql(db,
+ "SELECT 'DELETE FROM vacuum_db.' || quote(name) || ';' "
+ "FROM vacuum_db.sqlite_master WHERE name='sqlite_sequence' "
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'INSERT INTO vacuum_db.' || quote(name) "
+ "|| ' SELECT * FROM ' || quote(name) || ';' "
+ "FROM vacuum_db.sqlite_master WHERE name=='sqlite_sequence';"
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+
+ /* Copy the triggers, views, and virtual tables from the main database
+ ** over to the temporary database. None of these objects has any
+ ** associated storage, so all we have to do is copy their entries
+ ** from the SQLITE_MASTER table.
+ */
+ rc = execSql(db,
+ "INSERT INTO vacuum_db.sqlite_master "
+ " SELECT type, name, tbl_name, rootpage, sql"
+ " FROM sqlite_master"
+ " WHERE type='view' OR type='trigger'"
+ " OR (type='table' AND rootpage=0)"
+ );
+ if( rc ) goto end_of_vacuum;
+
+ /* At this point, unless the main db was completely empty, there is now a
+ ** transaction open on the vacuum database, but not on the main database.
+ ** Open a btree level transaction on the main database. This allows a
+ ** call to sqlite3BtreeCopyFile(). The main database btree level
+ ** transaction is then committed, so the SQL level never knows it was
+ ** opened for writing. This way, the SQL transaction used to create the
+ ** temporary database never needs to be committed.
+ */
+ if( rc==SQLITE_OK ){
+ u32 meta;
+ int i;
+
+ /* This array determines which meta meta values are preserved in the
+ ** vacuum. Even entries are the meta value number and odd entries
+ ** are an increment to apply to the meta value after the vacuum.
+ ** The increment is used to increase the schema cookie so that other
+ ** connections to the same database will know to reread the schema.
+ */
+ static const unsigned char aCopy[] = {
+ 1, 1, /* Add one to the old schema cookie */
+ 3, 0, /* Preserve the default page cache size */
+ 5, 0, /* Preserve the default text encoding */
+ 6, 0, /* Preserve the user version */
+ };
+
+ assert( 1==sqlite3BtreeIsInTrans(pTemp) );
+ assert( 1==sqlite3BtreeIsInTrans(pMain) );
+
+ /* Copy Btree meta values */
+ for(i=0; i<sizeof(aCopy)/sizeof(aCopy[0]); i+=2){
+ rc = sqlite3BtreeGetMeta(pMain, aCopy[i], &meta);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = sqlite3BtreeUpdateMeta(pTemp, aCopy[i], meta+aCopy[i+1]);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ }
+
+ rc = sqlite3BtreeCopyFile(pMain, pTemp);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = sqlite3BtreeCommit(pTemp);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = sqlite3BtreeCommit(pMain);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3BtreeSetPageSize(pMain, sqlite3BtreeGetPageSize(pTemp), nRes);
+ }
+
+end_of_vacuum:
+ /* Restore the original value of db->flags */
+ db->flags = saved_flags;
+
+ /* Currently there is an SQL level transaction open on the vacuum
+ ** database. No locks are held on any other files (since the main file
+ ** was committed at the btree level). So it safe to end the transaction
+ ** by manually setting the autoCommit flag to true and detaching the
+ ** vacuum database. The vacuum_db journal file is deleted when the pager
+ ** is closed by the DETACH.
+ */
+ db->autoCommit = 1;
+
+ if( pDb ){
+ sqlite3BtreeClose(pDb->pBt);
+ pDb->pBt = 0;
+ pDb->pSchema = 0;
+ }
+
+ sqlite3ResetInternalSchema(db, 0);
+
+ return rc;
+}
+#endif /* SQLITE_OMIT_VACUUM && SQLITE_OMIT_ATTACH */
+
+/************** End of vacuum.c **********************************************/
+/************** Begin file vtab.c ********************************************/
+/*
+** 2006 June 10
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code used to help implement virtual tables.
+**
+** $Id: vtab.c,v 1.69 2008/05/05 13:23:04 drh Exp $
+*/
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+static int createModule(
+ sqlite3 *db, /* Database in which module is registered */
+ const char *zName, /* Name assigned to this module */
+ const sqlite3_module *pModule, /* The definition of the module */
+ void *pAux, /* Context pointer for xCreate/xConnect */
+ void (*xDestroy)(void *) /* Module destructor function */
+) {
+ int rc, nName;
+ Module *pMod;
+
+ sqlite3_mutex_enter(db->mutex);
+ nName = strlen(zName);
+ pMod = (Module *)sqlite3DbMallocRaw(db, sizeof(Module) + nName + 1);
+ if( pMod ){
+ char *zCopy = (char *)(&pMod[1]);
+ memcpy(zCopy, zName, nName+1);
+ pMod->zName = zCopy;
+ pMod->pModule = pModule;
+ pMod->pAux = pAux;
+ pMod->xDestroy = xDestroy;
+ pMod = (Module *)sqlite3HashInsert(&db->aModule, zCopy, nName, (void*)pMod);
+ if( pMod && pMod->xDestroy ){
+ pMod->xDestroy(pMod->pAux);
+ }
+ sqlite3_free(pMod);
+ sqlite3ResetInternalSchema(db, 0);
+ }
+ rc = sqlite3ApiExit(db, SQLITE_OK);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+
+/*
+** External API function used to create a new virtual-table module.
+*/
+SQLITE_API int sqlite3_create_module(
+ sqlite3 *db, /* Database in which module is registered */
+ const char *zName, /* Name assigned to this module */
+ const sqlite3_module *pModule, /* The definition of the module */
+ void *pAux /* Context pointer for xCreate/xConnect */
+){
+ return createModule(db, zName, pModule, pAux, 0);
+}
+
+/*
+** External API function used to create a new virtual-table module.
+*/
+SQLITE_API int sqlite3_create_module_v2(
+ sqlite3 *db, /* Database in which module is registered */
+ const char *zName, /* Name assigned to this module */
+ const sqlite3_module *pModule, /* The definition of the module */
+ void *pAux, /* Context pointer for xCreate/xConnect */
+ void (*xDestroy)(void *) /* Module destructor function */
+){
+ return createModule(db, zName, pModule, pAux, xDestroy);
+}
+
+/*
+** Lock the virtual table so that it cannot be disconnected.
+** Locks nest. Every lock should have a corresponding unlock.
+** If an unlock is omitted, resources leaks will occur.
+**
+** If a disconnect is attempted while a virtual table is locked,
+** the disconnect is deferred until all locks have been removed.
+*/
+SQLITE_PRIVATE void sqlite3VtabLock(sqlite3_vtab *pVtab){
+ pVtab->nRef++;
+}
+
+/*
+** Unlock a virtual table. When the last lock is removed,
+** disconnect the virtual table.
+*/
+SQLITE_PRIVATE void sqlite3VtabUnlock(sqlite3 *db, sqlite3_vtab *pVtab){
+ pVtab->nRef--;
+ assert(db);
+ assert( sqlite3SafetyCheckOk(db) );
+ if( pVtab->nRef==0 ){
+ if( db->magic==SQLITE_MAGIC_BUSY ){
+ (void)sqlite3SafetyOff(db);
+ pVtab->pModule->xDisconnect(pVtab);
+ (void)sqlite3SafetyOn(db);
+ } else {
+ pVtab->pModule->xDisconnect(pVtab);
+ }
+ }
+}
+
+/*
+** Clear any and all virtual-table information from the Table record.
+** This routine is called, for example, just before deleting the Table
+** record.
+*/
+SQLITE_PRIVATE void sqlite3VtabClear(Table *p){
+ sqlite3_vtab *pVtab = p->pVtab;
+ if( pVtab ){
+ assert( p->pMod && p->pMod->pModule );
+ sqlite3VtabUnlock(p->pSchema->db, pVtab);
+ p->pVtab = 0;
+ }
+ if( p->azModuleArg ){
+ int i;
+ for(i=0; i<p->nModuleArg; i++){
+ sqlite3_free(p->azModuleArg[i]);
+ }
+ sqlite3_free(p->azModuleArg);
+ }
+}
+
+/*
+** Add a new module argument to pTable->azModuleArg[].
+** The string is not copied - the pointer is stored. The
+** string will be freed automatically when the table is
+** deleted.
+*/
+static void addModuleArgument(sqlite3 *db, Table *pTable, char *zArg){
+ int i = pTable->nModuleArg++;
+ int nBytes = sizeof(char *)*(1+pTable->nModuleArg);
+ char **azModuleArg;
+ azModuleArg = sqlite3DbRealloc(db, pTable->azModuleArg, nBytes);
+ if( azModuleArg==0 ){
+ int j;
+ for(j=0; j<i; j++){
+ sqlite3_free(pTable->azModuleArg[j]);
+ }
+ sqlite3_free(zArg);
+ sqlite3_free(pTable->azModuleArg);
+ pTable->nModuleArg = 0;
+ }else{
+ azModuleArg[i] = zArg;
+ azModuleArg[i+1] = 0;
+ }
+ pTable->azModuleArg = azModuleArg;
+}
+
+/*
+** The parser calls this routine when it first sees a CREATE VIRTUAL TABLE
+** statement. The module name has been parsed, but the optional list
+** of parameters that follow the module name are still pending.
+*/
+SQLITE_PRIVATE void sqlite3VtabBeginParse(
+ Parse *pParse, /* Parsing context */
+ Token *pName1, /* Name of new table, or database name */
+ Token *pName2, /* Name of new table or NULL */
+ Token *pModuleName /* Name of the module for the virtual table */
+){
+ int iDb; /* The database the table is being created in */
+ Table *pTable; /* The new virtual table */
+ sqlite3 *db; /* Database connection */
+
+ if( pParse->db->flags & SQLITE_SharedCache ){
+ sqlite3ErrorMsg(pParse, "Cannot use virtual tables in shared-cache mode");
+ return;
+ }
+
+ sqlite3StartTable(pParse, pName1, pName2, 0, 0, 1, 0);
+ pTable = pParse->pNewTable;
+ if( pTable==0 || pParse->nErr ) return;
+ assert( 0==pTable->pIndex );
+
+ db = pParse->db;
+ iDb = sqlite3SchemaToIndex(db, pTable->pSchema);
+ assert( iDb>=0 );
+
+ pTable->isVirtual = 1;
+ pTable->nModuleArg = 0;
+ addModuleArgument(db, pTable, sqlite3NameFromToken(db, pModuleName));
+ addModuleArgument(db, pTable, sqlite3DbStrDup(db, db->aDb[iDb].zName));
+ addModuleArgument(db, pTable, sqlite3DbStrDup(db, pTable->zName));
+ pParse->sNameToken.n = pModuleName->z + pModuleName->n - pName1->z;
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ /* Creating a virtual table invokes the authorization callback twice.
+ ** The first invocation, to obtain permission to INSERT a row into the
+ ** sqlite_master table, has already been made by sqlite3StartTable().
+ ** The second call, to obtain permission to create the table, is made now.
+ */
+ if( pTable->azModuleArg ){
+ sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName,
+ pTable->azModuleArg[0], pParse->db->aDb[iDb].zName);
+ }
+#endif
+}
+
+/*
+** This routine takes the module argument that has been accumulating
+** in pParse->zArg[] and appends it to the list of arguments on the
+** virtual table currently under construction in pParse->pTable.
+*/
+static void addArgumentToVtab(Parse *pParse){
+ if( pParse->sArg.z && pParse->pNewTable ){
+ const char *z = (const char*)pParse->sArg.z;
+ int n = pParse->sArg.n;
+ sqlite3 *db = pParse->db;
+ addModuleArgument(db, pParse->pNewTable, sqlite3DbStrNDup(db, z, n));
+ }
+}
+
+/*
+** The parser calls this routine after the CREATE VIRTUAL TABLE statement
+** has been completely parsed.
+*/
+SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
+ Table *pTab; /* The table being constructed */
+ sqlite3 *db; /* The database connection */
+ char *zModule; /* The module name of the table: USING modulename */
+ Module *pMod = 0;
+
+ addArgumentToVtab(pParse);
+ pParse->sArg.z = 0;
+
+ /* Lookup the module name. */
+ pTab = pParse->pNewTable;
+ if( pTab==0 ) return;
+ db = pParse->db;
+ if( pTab->nModuleArg<1 ) return;
+ zModule = pTab->azModuleArg[0];
+ pMod = (Module *)sqlite3HashFind(&db->aModule, zModule, strlen(zModule));
+ pTab->pMod = pMod;
+
+ /* If the CREATE VIRTUAL TABLE statement is being entered for the
+ ** first time (in other words if the virtual table is actually being
+ ** created now instead of just being read out of sqlite_master) then
+ ** do additional initialization work and store the statement text
+ ** in the sqlite_master table.
+ */
+ if( !db->init.busy ){
+ char *zStmt;
+ char *zWhere;
+ int iDb;
+ Vdbe *v;
+
+ /* Compute the complete text of the CREATE VIRTUAL TABLE statement */
+ if( pEnd ){
+ pParse->sNameToken.n = pEnd->z - pParse->sNameToken.z + pEnd->n;
+ }
+ zStmt = sqlite3MPrintf(db, "CREATE VIRTUAL TABLE %T", &pParse->sNameToken);
+
+ /* A slot for the record has already been allocated in the
+ ** SQLITE_MASTER table. We just need to update that slot with all
+ ** the information we've collected.
+ **
+ ** The VM register number pParse->regRowid holds the rowid of an
+ ** entry in the sqlite_master table tht was created for this vtab
+ ** by sqlite3StartTable().
+ */
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ sqlite3NestedParse(pParse,
+ "UPDATE %Q.%s "
+ "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q "
+ "WHERE rowid=#%d",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
+ pTab->zName,
+ pTab->zName,
+ zStmt,
+ pParse->regRowid
+ );
+ sqlite3_free(zStmt);
+ v = sqlite3GetVdbe(pParse);
+ sqlite3ChangeCookie(pParse, iDb);
+
+ sqlite3VdbeAddOp2(v, OP_Expire, 0, 0);
+ zWhere = sqlite3MPrintf(db, "name='%q'", pTab->zName);
+ sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 1, 0, zWhere, P4_DYNAMIC);
+ sqlite3VdbeAddOp4(v, OP_VCreate, iDb, 0, 0,
+ pTab->zName, strlen(pTab->zName) + 1);
+ }
+
+ /* If we are rereading the sqlite_master table create the in-memory
+ ** record of the table. If the module has already been registered,
+ ** also call the xConnect method here.
+ */
+ else {
+ Table *pOld;
+ Schema *pSchema = pTab->pSchema;
+ const char *zName = pTab->zName;
+ int nName = strlen(zName) + 1;
+ pOld = sqlite3HashInsert(&pSchema->tblHash, zName, nName, pTab);
+ if( pOld ){
+ db->mallocFailed = 1;
+ assert( pTab==pOld ); /* Malloc must have failed inside HashInsert() */
+ return;
+ }
+ pSchema->db = pParse->db;
+ pParse->pNewTable = 0;
+ }
+}
+
+/*
+** The parser calls this routine when it sees the first token
+** of an argument to the module name in a CREATE VIRTUAL TABLE statement.
+*/
+SQLITE_PRIVATE void sqlite3VtabArgInit(Parse *pParse){
+ addArgumentToVtab(pParse);
+ pParse->sArg.z = 0;
+ pParse->sArg.n = 0;
+}
+
+/*
+** The parser calls this routine for each token after the first token
+** in an argument to the module name in a CREATE VIRTUAL TABLE statement.
+*/
+SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse *pParse, Token *p){
+ Token *pArg = &pParse->sArg;
+ if( pArg->z==0 ){
+ pArg->z = p->z;
+ pArg->n = p->n;
+ }else{
+ assert(pArg->z < p->z);
+ pArg->n = (p->z + p->n - pArg->z);
+ }
+}
+
+/*
+** Invoke a virtual table constructor (either xCreate or xConnect). The
+** pointer to the function to invoke is passed as the fourth parameter
+** to this procedure.
+*/
+static int vtabCallConstructor(
+ sqlite3 *db,
+ Table *pTab,
+ Module *pMod,
+ int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**),
+ char **pzErr
+){
+ int rc;
+ int rc2;
+ sqlite3_vtab *pVtab = 0;
+ const char *const*azArg = (const char *const*)pTab->azModuleArg;
+ int nArg = pTab->nModuleArg;
+ char *zErr = 0;
+ char *zModuleName = sqlite3MPrintf(db, "%s", pTab->zName);
+
+ if( !zModuleName ){
+ return SQLITE_NOMEM;
+ }
+
+ assert( !db->pVTab );
+ assert( xConstruct );
+
+ db->pVTab = pTab;
+ rc = sqlite3SafetyOff(db);
+ assert( rc==SQLITE_OK );
+ rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVtab, &zErr);
+ rc2 = sqlite3SafetyOn(db);
+ if( rc==SQLITE_OK && pVtab ){
+ pVtab->pModule = pMod->pModule;
+ pVtab->nRef = 1;
+ pTab->pVtab = pVtab;
+ }
+
+ if( SQLITE_OK!=rc ){
+ if( zErr==0 ){
+ *pzErr = sqlite3MPrintf(db, "vtable constructor failed: %s", zModuleName);
+ }else {
+ *pzErr = sqlite3MPrintf(db, "%s", zErr);
+ sqlite3_free(zErr);
+ }
+ }else if( db->pVTab ){
+ const char *zFormat = "vtable constructor did not declare schema: %s";
+ *pzErr = sqlite3MPrintf(db, zFormat, pTab->zName);
+ rc = SQLITE_ERROR;
+ }
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ db->pVTab = 0;
+ sqlite3_free(zModuleName);
+
+ /* If everything went according to plan, loop through the columns
+ ** of the table to see if any of them contain the token "hidden".
+ ** If so, set the Column.isHidden flag and remove the token from
+ ** the type string.
+ */
+ if( rc==SQLITE_OK ){
+ int iCol;
+ for(iCol=0; iCol<pTab->nCol; iCol++){
+ char *zType = pTab->aCol[iCol].zType;
+ int nType;
+ int i = 0;
+ if( !zType ) continue;
+ nType = strlen(zType);
+ if( sqlite3StrNICmp("hidden", zType, 6) || (zType[6] && zType[6]!=' ') ){
+ for(i=0; i<nType; i++){
+ if( (0==sqlite3StrNICmp(" hidden", &zType[i], 7))
+ && (zType[i+7]=='\0' || zType[i+7]==' ')
+ ){
+ i++;
+ break;
+ }
+ }
+ }
+ if( i<nType ){
+ int j;
+ int nDel = 6 + (zType[i+6] ? 1 : 0);
+ for(j=i; (j+nDel)<=nType; j++){
+ zType[j] = zType[j+nDel];
+ }
+ if( zType[i]=='\0' && i>0 ){
+ assert(zType[i-1]==' ');
+ zType[i-1] = '\0';
+ }
+ pTab->aCol[iCol].isHidden = 1;
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+** This function is invoked by the parser to call the xConnect() method
+** of the virtual table pTab. If an error occurs, an error code is returned
+** and an error left in pParse.
+**
+** This call is a no-op if table pTab is not a virtual table.
+*/
+SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){
+ Module *pMod;
+ int rc = SQLITE_OK;
+
+ if( !pTab || !pTab->isVirtual || pTab->pVtab ){
+ return SQLITE_OK;
+ }
+
+ pMod = pTab->pMod;
+ if( !pMod ){
+ const char *zModule = pTab->azModuleArg[0];
+ sqlite3ErrorMsg(pParse, "no such module: %s", zModule);
+ rc = SQLITE_ERROR;
+ } else {
+ char *zErr = 0;
+ sqlite3 *db = pParse->db;
+ rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xConnect, &zErr);
+ if( rc!=SQLITE_OK ){
+ sqlite3ErrorMsg(pParse, "%s", zErr);
+ }
+ sqlite3_free(zErr);
+ }
+
+ return rc;
+}
+
+/*
+** Add the virtual table pVtab to the array sqlite3.aVTrans[].
+*/
+static int addToVTrans(sqlite3 *db, sqlite3_vtab *pVtab){
+ const int ARRAY_INCR = 5;
+
+ /* Grow the sqlite3.aVTrans array if required */
+ if( (db->nVTrans%ARRAY_INCR)==0 ){
+ sqlite3_vtab **aVTrans;
+ int nBytes = sizeof(sqlite3_vtab *) * (db->nVTrans + ARRAY_INCR);
+ aVTrans = sqlite3DbRealloc(db, (void *)db->aVTrans, nBytes);
+ if( !aVTrans ){
+ return SQLITE_NOMEM;
+ }
+ memset(&aVTrans[db->nVTrans], 0, sizeof(sqlite3_vtab *)*ARRAY_INCR);
+ db->aVTrans = aVTrans;
+ }
+
+ /* Add pVtab to the end of sqlite3.aVTrans */
+ db->aVTrans[db->nVTrans++] = pVtab;
+ sqlite3VtabLock(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** This function is invoked by the vdbe to call the xCreate method
+** of the virtual table named zTab in database iDb.
+**
+** If an error occurs, *pzErr is set to point an an English language
+** description of the error and an SQLITE_XXX error code is returned.
+** In this case the caller must call sqlite3_free() on *pzErr.
+*/
+SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){
+ int rc = SQLITE_OK;
+ Table *pTab;
+ Module *pMod;
+ const char *zModule;
+
+ pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName);
+ assert(pTab && pTab->isVirtual && !pTab->pVtab);
+ pMod = pTab->pMod;
+ zModule = pTab->azModuleArg[0];
+
+ /* If the module has been registered and includes a Create method,
+ ** invoke it now. If the module has not been registered, return an
+ ** error. Otherwise, do nothing.
+ */
+ if( !pMod ){
+ *pzErr = sqlite3MPrintf(db, "no such module: %s", zModule);
+ rc = SQLITE_ERROR;
+ }else{
+ rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xCreate, pzErr);
+ }
+
+ if( rc==SQLITE_OK && pTab->pVtab ){
+ rc = addToVTrans(db, pTab->pVtab);
+ }
+
+ return rc;
+}
+
+/*
+** This function is used to set the schema of a virtual table. It is only
+** valid to call this function from within the xCreate() or xConnect() of a
+** virtual table module.
+*/
+SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
+ Parse sParse;
+
+ int rc = SQLITE_OK;
+ Table *pTab;
+ char *zErr = 0;
+
+ sqlite3_mutex_enter(db->mutex);
+ pTab = db->pVTab;
+ if( !pTab ){
+ sqlite3Error(db, SQLITE_MISUSE, 0);
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_MISUSE;
+ }
+ assert(pTab->isVirtual && pTab->nCol==0 && pTab->aCol==0);
+
+ memset(&sParse, 0, sizeof(Parse));
+ sParse.declareVtab = 1;
+ sParse.db = db;
+
+ if(
+ SQLITE_OK == sqlite3RunParser(&sParse, zCreateTable, &zErr) &&
+ sParse.pNewTable &&
+ !sParse.pNewTable->pSelect &&
+ !sParse.pNewTable->isVirtual
+ ){
+ pTab->aCol = sParse.pNewTable->aCol;
+ pTab->nCol = sParse.pNewTable->nCol;
+ sParse.pNewTable->nCol = 0;
+ sParse.pNewTable->aCol = 0;
+ db->pVTab = 0;
+ } else {
+ sqlite3Error(db, SQLITE_ERROR, zErr);
+ sqlite3_free(zErr);
+ rc = SQLITE_ERROR;
+ }
+ sParse.declareVtab = 0;
+
+ sqlite3_finalize((sqlite3_stmt*)sParse.pVdbe);
+ sqlite3DeleteTable(sParse.pNewTable);
+ sParse.pNewTable = 0;
+
+ assert( (rc&0xff)==rc );
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** This function is invoked by the vdbe to call the xDestroy method
+** of the virtual table named zTab in database iDb. This occurs
+** when a DROP TABLE is mentioned.
+**
+** This call is a no-op if zTab is not a virtual table.
+*/
+SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab)
+{
+ int rc = SQLITE_OK;
+ Table *pTab;
+
+ pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName);
+ assert(pTab);
+ if( pTab->pVtab ){
+ int (*xDestroy)(sqlite3_vtab *pVTab) = pTab->pMod->pModule->xDestroy;
+ rc = sqlite3SafetyOff(db);
+ assert( rc==SQLITE_OK );
+ if( xDestroy ){
+ rc = xDestroy(pTab->pVtab);
+ }
+ (void)sqlite3SafetyOn(db);
+ if( rc==SQLITE_OK ){
+ int i;
+ for(i=0; i<db->nVTrans; i++){
+ if( db->aVTrans[i]==pTab->pVtab ){
+ db->aVTrans[i] = db->aVTrans[--db->nVTrans];
+ break;
+ }
+ }
+ pTab->pVtab = 0;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** This function invokes either the xRollback or xCommit method
+** of each of the virtual tables in the sqlite3.aVTrans array. The method
+** called is identified by the second argument, "offset", which is
+** the offset of the method to call in the sqlite3_module structure.
+**
+** The array is cleared after invoking the callbacks.
+*/
+static void callFinaliser(sqlite3 *db, int offset){
+ int i;
+ if( db->aVTrans ){
+ for(i=0; i<db->nVTrans && db->aVTrans[i]; i++){
+ sqlite3_vtab *pVtab = db->aVTrans[i];
+ int (*x)(sqlite3_vtab *);
+ x = *(int (**)(sqlite3_vtab *))((char *)pVtab->pModule + offset);
+ if( x ) x(pVtab);
+ sqlite3VtabUnlock(db, pVtab);
+ }
+ sqlite3_free(db->aVTrans);
+ db->nVTrans = 0;
+ db->aVTrans = 0;
+ }
+}
+
+/*
+** If argument rc2 is not SQLITE_OK, then return it and do nothing.
+** Otherwise, invoke the xSync method of all virtual tables in the
+** sqlite3.aVTrans array. Return the error code for the first error
+** that occurs, or SQLITE_OK if all xSync operations are successful.
+*/
+SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, int rc2){
+ int i;
+ int rc = SQLITE_OK;
+ int rcsafety;
+ sqlite3_vtab **aVTrans = db->aVTrans;
+ if( rc2!=SQLITE_OK ) return rc2;
+
+ rc = sqlite3SafetyOff(db);
+ db->aVTrans = 0;
+ for(i=0; rc==SQLITE_OK && i<db->nVTrans && aVTrans[i]; i++){
+ sqlite3_vtab *pVtab = aVTrans[i];
+ int (*x)(sqlite3_vtab *);
+ x = pVtab->pModule->xSync;
+ if( x ){
+ rc = x(pVtab);
+ }
+ }
+ db->aVTrans = aVTrans;
+ rcsafety = sqlite3SafetyOn(db);
+
+ if( rc==SQLITE_OK ){
+ rc = rcsafety;
+ }
+ return rc;
+}
+
+/*
+** Invoke the xRollback method of all virtual tables in the
+** sqlite3.aVTrans array. Then clear the array itself.
+*/
+SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db){
+ callFinaliser(db, offsetof(sqlite3_module,xRollback));
+ return SQLITE_OK;
+}
+
+/*
+** Invoke the xCommit method of all virtual tables in the
+** sqlite3.aVTrans array. Then clear the array itself.
+*/
+SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db){
+ callFinaliser(db, offsetof(sqlite3_module,xCommit));
+ return SQLITE_OK;
+}
+
+/*
+** If the virtual table pVtab supports the transaction interface
+** (xBegin/xRollback/xCommit and optionally xSync) and a transaction is
+** not currently open, invoke the xBegin method now.
+**
+** If the xBegin call is successful, place the sqlite3_vtab pointer
+** in the sqlite3.aVTrans array.
+*/
+SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, sqlite3_vtab *pVtab){
+ int rc = SQLITE_OK;
+ const sqlite3_module *pModule;
+
+ /* Special case: If db->aVTrans is NULL and db->nVTrans is greater
+ ** than zero, then this function is being called from within a
+ ** virtual module xSync() callback. It is illegal to write to
+ ** virtual module tables in this case, so return SQLITE_LOCKED.
+ */
+ if( 0==db->aVTrans && db->nVTrans>0 ){
+ return SQLITE_LOCKED;
+ }
+ if( !pVtab ){
+ return SQLITE_OK;
+ }
+ pModule = pVtab->pModule;
+
+ if( pModule->xBegin ){
+ int i;
+
+
+ /* If pVtab is already in the aVTrans array, return early */
+ for(i=0; (i<db->nVTrans) && 0!=db->aVTrans[i]; i++){
+ if( db->aVTrans[i]==pVtab ){
+ return SQLITE_OK;
+ }
+ }
+
+ /* Invoke the xBegin method */
+ rc = pModule->xBegin(pVtab);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ rc = addToVTrans(db, pVtab);
+ }
+ return rc;
+}
+
+/*
+** The first parameter (pDef) is a function implementation. The
+** second parameter (pExpr) is the first argument to this function.
+** If pExpr is a column in a virtual table, then let the virtual
+** table implementation have an opportunity to overload the function.
+**
+** This routine is used to allow virtual table implementations to
+** overload MATCH, LIKE, GLOB, and REGEXP operators.
+**
+** Return either the pDef argument (indicating no change) or a
+** new FuncDef structure that is marked as ephemeral using the
+** SQLITE_FUNC_EPHEM flag.
+*/
+SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(
+ sqlite3 *db, /* Database connection for reporting malloc problems */
+ FuncDef *pDef, /* Function to possibly overload */
+ int nArg, /* Number of arguments to the function */
+ Expr *pExpr /* First argument to the function */
+){
+ Table *pTab;
+ sqlite3_vtab *pVtab;
+ sqlite3_module *pMod;
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
+ void *pArg;
+ FuncDef *pNew;
+ int rc = 0;
+ char *zLowerName;
+ unsigned char *z;
+
+
+ /* Check to see the left operand is a column in a virtual table */
+ if( pExpr==0 ) return pDef;
+ if( pExpr->op!=TK_COLUMN ) return pDef;
+ pTab = pExpr->pTab;
+ if( pTab==0 ) return pDef;
+ if( !pTab->isVirtual ) return pDef;
+ pVtab = pTab->pVtab;
+ assert( pVtab!=0 );
+ assert( pVtab->pModule!=0 );
+ pMod = (sqlite3_module *)pVtab->pModule;
+ if( pMod->xFindFunction==0 ) return pDef;
+
+ /* Call the xFindFunction method on the virtual table implementation
+ ** to see if the implementation wants to overload this function
+ */
+ zLowerName = sqlite3DbStrDup(db, pDef->zName);
+ if( zLowerName ){
+ for(z=(unsigned char*)zLowerName; *z; z++){
+ *z = sqlite3UpperToLower[*z];
+ }
+ rc = pMod->xFindFunction(pVtab, nArg, zLowerName, &xFunc, &pArg);
+ sqlite3_free(zLowerName);
+ }
+ if( rc==0 ){
+ return pDef;
+ }
+
+ /* Create a new ephemeral function definition for the overloaded
+ ** function */
+ pNew = sqlite3DbMallocZero(db, sizeof(*pNew) + strlen(pDef->zName) );
+ if( pNew==0 ){
+ return pDef;
+ }
+ *pNew = *pDef;
+ memcpy(pNew->zName, pDef->zName, strlen(pDef->zName)+1);
+ pNew->xFunc = xFunc;
+ pNew->pUserData = pArg;
+ pNew->flags |= SQLITE_FUNC_EPHEM;
+ return pNew;
+}
+
+/*
+** Make sure virtual table pTab is contained in the pParse->apVirtualLock[]
+** array so that an OP_VBegin will get generated for it. Add pTab to the
+** array if it is missing. If pTab is already in the array, this routine
+** is a no-op.
+*/
+SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
+ int i, n;
+ assert( IsVirtual(pTab) );
+ for(i=0; i<pParse->nVtabLock; i++){
+ if( pTab==pParse->apVtabLock[i] ) return;
+ }
+ n = (pParse->nVtabLock+1)*sizeof(pParse->apVtabLock[0]);
+ pParse->apVtabLock = sqlite3_realloc(pParse->apVtabLock, n);
+ if( pParse->apVtabLock ){
+ pParse->apVtabLock[pParse->nVtabLock++] = pTab;
+ }else{
+ pParse->db->mallocFailed = 1;
+ }
+}
+
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+/************** End of vtab.c ************************************************/
+/************** Begin file where.c *******************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This module contains C code that generates VDBE code used to process
+** the WHERE clause of SQL statements. This module is reponsible for
+** generating the code that loops through a table looking for applicable
+** rows. Indices are selected and used to speed the search when doing
+** so is applicable. Because this module is responsible for selecting
+** indices, you might also think of this module as the "query optimizer".
+**
+** $Id: where.c,v 1.302 2008/04/19 14:40:44 drh Exp $
+*/
+
+/*
+** The number of bits in a Bitmask. "BMS" means "BitMask Size".
+*/
+#define BMS (sizeof(Bitmask)*8)
+
+/*
+** Trace output macros
+*/
+#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
+SQLITE_PRIVATE int sqlite3WhereTrace = 0;
+# define WHERETRACE(X) if(sqlite3WhereTrace) sqlite3DebugPrintf X
+#else
+# define WHERETRACE(X)
+#endif
+
+/* Forward reference
+*/
+typedef struct WhereClause WhereClause;
+typedef struct ExprMaskSet ExprMaskSet;
+
+/*
+** The query generator uses an array of instances of this structure to
+** help it analyze the subexpressions of the WHERE clause. Each WHERE
+** clause subexpression is separated from the others by an AND operator.
+**
+** All WhereTerms are collected into a single WhereClause structure.
+** The following identity holds:
+**
+** WhereTerm.pWC->a[WhereTerm.idx] == WhereTerm
+**
+** When a term is of the form:
+**
+** X <op> <expr>
+**
+** where X is a column name and <op> is one of certain operators,
+** then WhereTerm.leftCursor and WhereTerm.leftColumn record the
+** cursor number and column number for X. WhereTerm.operator records
+** the <op> using a bitmask encoding defined by WO_xxx below. The
+** use of a bitmask encoding for the operator allows us to search
+** quickly for terms that match any of several different operators.
+**
+** prereqRight and prereqAll record sets of cursor numbers,
+** but they do so indirectly. A single ExprMaskSet structure translates
+** cursor number into bits and the translated bit is stored in the prereq
+** fields. The translation is used in order to maximize the number of
+** bits that will fit in a Bitmask. The VDBE cursor numbers might be
+** spread out over the non-negative integers. For example, the cursor
+** numbers might be 3, 8, 9, 10, 20, 23, 41, and 45. The ExprMaskSet
+** translates these sparse cursor numbers into consecutive integers
+** beginning with 0 in order to make the best possible use of the available
+** bits in the Bitmask. So, in the example above, the cursor numbers
+** would be mapped into integers 0 through 7.
+*/
+typedef struct WhereTerm WhereTerm;
+struct WhereTerm {
+ Expr *pExpr; /* Pointer to the subexpression */
+ i16 iParent; /* Disable pWC->a[iParent] when this term disabled */
+ i16 leftCursor; /* Cursor number of X in "X <op> <expr>" */
+ i16 leftColumn; /* Column number of X in "X <op> <expr>" */
+ u16 eOperator; /* A WO_xx value describing <op> */
+ u8 flags; /* Bit flags. See below */
+ u8 nChild; /* Number of children that must disable us */
+ WhereClause *pWC; /* The clause this term is part of */
+ Bitmask prereqRight; /* Bitmask of tables used by pRight */
+ Bitmask prereqAll; /* Bitmask of tables referenced by p */
+};
+
+/*
+** Allowed values of WhereTerm.flags
+*/
+#define TERM_DYNAMIC 0x01 /* Need to call sqlite3ExprDelete(pExpr) */
+#define TERM_VIRTUAL 0x02 /* Added by the optimizer. Do not code */
+#define TERM_CODED 0x04 /* This term is already coded */
+#define TERM_COPIED 0x08 /* Has a child */
+#define TERM_OR_OK 0x10 /* Used during OR-clause processing */
+
+/*
+** An instance of the following structure holds all information about a
+** WHERE clause. Mostly this is a container for one or more WhereTerms.
+*/
+struct WhereClause {
+ Parse *pParse; /* The parser context */
+ ExprMaskSet *pMaskSet; /* Mapping of table indices to bitmasks */
+ int nTerm; /* Number of terms */
+ int nSlot; /* Number of entries in a[] */
+ WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */
+ WhereTerm aStatic[10]; /* Initial static space for a[] */
+};
+
+/*
+** An instance of the following structure keeps track of a mapping
+** between VDBE cursor numbers and bits of the bitmasks in WhereTerm.
+**
+** The VDBE cursor numbers are small integers contained in
+** SrcList_item.iCursor and Expr.iTable fields. For any given WHERE
+** clause, the cursor numbers might not begin with 0 and they might
+** contain gaps in the numbering sequence. But we want to make maximum
+** use of the bits in our bitmasks. This structure provides a mapping
+** from the sparse cursor numbers into consecutive integers beginning
+** with 0.
+**
+** If ExprMaskSet.ix[A]==B it means that The A-th bit of a Bitmask
+** corresponds VDBE cursor number B. The A-th bit of a bitmask is 1<<A.
+**
+** For example, if the WHERE clause expression used these VDBE
+** cursors: 4, 5, 8, 29, 57, 73. Then the ExprMaskSet structure
+** would map those cursor numbers into bits 0 through 5.
+**
+** Note that the mapping is not necessarily ordered. In the example
+** above, the mapping might go like this: 4->3, 5->1, 8->2, 29->0,
+** 57->5, 73->4. Or one of 719 other combinations might be used. It
+** does not really matter. What is important is that sparse cursor
+** numbers all get mapped into bit numbers that begin with 0 and contain
+** no gaps.
+*/
+struct ExprMaskSet {
+ int n; /* Number of assigned cursor values */
+ int ix[sizeof(Bitmask)*8]; /* Cursor assigned to each bit */
+};
+
+
+/*
+** Bitmasks for the operators that indices are able to exploit. An
+** OR-ed combination of these values can be used when searching for
+** terms in the where clause.
+*/
+#define WO_IN 1
+#define WO_EQ 2
+#define WO_LT (WO_EQ<<(TK_LT-TK_EQ))
+#define WO_LE (WO_EQ<<(TK_LE-TK_EQ))
+#define WO_GT (WO_EQ<<(TK_GT-TK_EQ))
+#define WO_GE (WO_EQ<<(TK_GE-TK_EQ))
+#define WO_MATCH 64
+#define WO_ISNULL 128
+
+/*
+** Value for flags returned by bestIndex().
+**
+** The least significant byte is reserved as a mask for WO_ values above.
+** The WhereLevel.flags field is usually set to WO_IN|WO_EQ|WO_ISNULL.
+** But if the table is the right table of a left join, WhereLevel.flags
+** is set to WO_IN|WO_EQ. The WhereLevel.flags field can then be used as
+** the "op" parameter to findTerm when we are resolving equality constraints.
+** ISNULL constraints will then not be used on the right table of a left
+** join. Tickets #2177 and #2189.
+*/
+#define WHERE_ROWID_EQ 0x000100 /* rowid=EXPR or rowid IN (...) */
+#define WHERE_ROWID_RANGE 0x000200 /* rowid<EXPR and/or rowid>EXPR */
+#define WHERE_COLUMN_EQ 0x001000 /* x=EXPR or x IN (...) */
+#define WHERE_COLUMN_RANGE 0x002000 /* x<EXPR and/or x>EXPR */
+#define WHERE_COLUMN_IN 0x004000 /* x IN (...) */
+#define WHERE_TOP_LIMIT 0x010000 /* x<EXPR or x<=EXPR constraint */
+#define WHERE_BTM_LIMIT 0x020000 /* x>EXPR or x>=EXPR constraint */
+#define WHERE_IDX_ONLY 0x080000 /* Use index only - omit table */
+#define WHERE_ORDERBY 0x100000 /* Output will appear in correct order */
+#define WHERE_REVERSE 0x200000 /* Scan in reverse order */
+#define WHERE_UNIQUE 0x400000 /* Selects no more than one row */
+#define WHERE_VIRTUALTABLE 0x800000 /* Use virtual-table processing */
+
+/*
+** Initialize a preallocated WhereClause structure.
+*/
+static void whereClauseInit(
+ WhereClause *pWC, /* The WhereClause to be initialized */
+ Parse *pParse, /* The parsing context */
+ ExprMaskSet *pMaskSet /* Mapping from table indices to bitmasks */
+){
+ pWC->pParse = pParse;
+ pWC->pMaskSet = pMaskSet;
+ pWC->nTerm = 0;
+ pWC->nSlot = ArraySize(pWC->aStatic);
+ pWC->a = pWC->aStatic;
+}
+
+/*
+** Deallocate a WhereClause structure. The WhereClause structure
+** itself is not freed. This routine is the inverse of whereClauseInit().
+*/
+static void whereClauseClear(WhereClause *pWC){
+ int i;
+ WhereTerm *a;
+ for(i=pWC->nTerm-1, a=pWC->a; i>=0; i--, a++){
+ if( a->flags & TERM_DYNAMIC ){
+ sqlite3ExprDelete(a->pExpr);
+ }
+ }
+ if( pWC->a!=pWC->aStatic ){
+ sqlite3_free(pWC->a);
+ }
+}
+
+/*
+** Add a new entries to the WhereClause structure. Increase the allocated
+** space as necessary.
+**
+** If the flags argument includes TERM_DYNAMIC, then responsibility
+** for freeing the expression p is assumed by the WhereClause object.
+**
+** WARNING: This routine might reallocate the space used to store
+** WhereTerms. All pointers to WhereTerms should be invalided after
+** calling this routine. Such pointers may be reinitialized by referencing
+** the pWC->a[] array.
+*/
+static int whereClauseInsert(WhereClause *pWC, Expr *p, int flags){
+ WhereTerm *pTerm;
+ int idx;
+ if( pWC->nTerm>=pWC->nSlot ){
+ WhereTerm *pOld = pWC->a;
+ pWC->a = sqlite3_malloc( sizeof(pWC->a[0])*pWC->nSlot*2 );
+ if( pWC->a==0 ){
+ pWC->pParse->db->mallocFailed = 1;
+ if( flags & TERM_DYNAMIC ){
+ sqlite3ExprDelete(p);
+ }
+ pWC->a = pOld;
+ return 0;
+ }
+ memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm);
+ if( pOld!=pWC->aStatic ){
+ sqlite3_free(pOld);
+ }
+ pWC->nSlot *= 2;
+ }
+ pTerm = &pWC->a[idx = pWC->nTerm];
+ pWC->nTerm++;
+ pTerm->pExpr = p;
+ pTerm->flags = flags;
+ pTerm->pWC = pWC;
+ pTerm->iParent = -1;
+ return idx;
+}
+
+/*
+** This routine identifies subexpressions in the WHERE clause where
+** each subexpression is separated by the AND operator or some other
+** operator specified in the op parameter. The WhereClause structure
+** is filled with pointers to subexpressions. For example:
+**
+** WHERE a=='hello' AND coalesce(b,11)<10 AND (c+12!=d OR c==22)
+** \________/ \_______________/ \________________/
+** slot[0] slot[1] slot[2]
+**
+** The original WHERE clause in pExpr is unaltered. All this routine
+** does is make slot[] entries point to substructure within pExpr.
+**
+** In the previous sentence and in the diagram, "slot[]" refers to
+** the WhereClause.a[] array. This array grows as needed to contain
+** all terms of the WHERE clause.
+*/
+static void whereSplit(WhereClause *pWC, Expr *pExpr, int op){
+ if( pExpr==0 ) return;
+ if( pExpr->op!=op ){
+ whereClauseInsert(pWC, pExpr, 0);
+ }else{
+ whereSplit(pWC, pExpr->pLeft, op);
+ whereSplit(pWC, pExpr->pRight, op);
+ }
+}
+
+/*
+** Initialize an expression mask set
+*/
+#define initMaskSet(P) memset(P, 0, sizeof(*P))
+
+/*
+** Return the bitmask for the given cursor number. Return 0 if
+** iCursor is not in the set.
+*/
+static Bitmask getMask(ExprMaskSet *pMaskSet, int iCursor){
+ int i;
+ for(i=0; i<pMaskSet->n; i++){
+ if( pMaskSet->ix[i]==iCursor ){
+ return ((Bitmask)1)<<i;
+ }
+ }
+ return 0;
+}
+
+/*
+** Create a new mask for cursor iCursor.
+**
+** There is one cursor per table in the FROM clause. The number of
+** tables in the FROM clause is limited by a test early in the
+** sqlite3WhereBegin() routine. So we know that the pMaskSet->ix[]
+** array will never overflow.
+*/
+static void createMask(ExprMaskSet *pMaskSet, int iCursor){
+ assert( pMaskSet->n < ArraySize(pMaskSet->ix) );
+ pMaskSet->ix[pMaskSet->n++] = iCursor;
+}
+
+/*
+** This routine walks (recursively) an expression tree and generates
+** a bitmask indicating which tables are used in that expression
+** tree.
+**
+** In order for this routine to work, the calling function must have
+** previously invoked sqlite3ExprResolveNames() on the expression. See
+** the header comment on that routine for additional information.
+** The sqlite3ExprResolveNames() routines looks for column names and
+** sets their opcodes to TK_COLUMN and their Expr.iTable fields to
+** the VDBE cursor number of the table. This routine just has to
+** translate the cursor numbers into bitmask values and OR all
+** the bitmasks together.
+*/
+static Bitmask exprListTableUsage(ExprMaskSet*, ExprList*);
+static Bitmask exprSelectTableUsage(ExprMaskSet*, Select*);
+static Bitmask exprTableUsage(ExprMaskSet *pMaskSet, Expr *p){
+ Bitmask mask = 0;
+ if( p==0 ) return 0;
+ if( p->op==TK_COLUMN ){
+ mask = getMask(pMaskSet, p->iTable);
+ return mask;
+ }
+ mask = exprTableUsage(pMaskSet, p->pRight);
+ mask |= exprTableUsage(pMaskSet, p->pLeft);
+ mask |= exprListTableUsage(pMaskSet, p->pList);
+ mask |= exprSelectTableUsage(pMaskSet, p->pSelect);
+ return mask;
+}
+static Bitmask exprListTableUsage(ExprMaskSet *pMaskSet, ExprList *pList){
+ int i;
+ Bitmask mask = 0;
+ if( pList ){
+ for(i=0; i<pList->nExpr; i++){
+ mask |= exprTableUsage(pMaskSet, pList->a[i].pExpr);
+ }
+ }
+ return mask;
+}
+static Bitmask exprSelectTableUsage(ExprMaskSet *pMaskSet, Select *pS){
+ Bitmask mask = 0;
+ while( pS ){
+ mask |= exprListTableUsage(pMaskSet, pS->pEList);
+ mask |= exprListTableUsage(pMaskSet, pS->pGroupBy);
+ mask |= exprListTableUsage(pMaskSet, pS->pOrderBy);
+ mask |= exprTableUsage(pMaskSet, pS->pWhere);
+ mask |= exprTableUsage(pMaskSet, pS->pHaving);
+ pS = pS->pPrior;
+ }
+ return mask;
+}
+
+/*
+** Return TRUE if the given operator is one of the operators that is
+** allowed for an indexable WHERE clause term. The allowed operators are
+** "=", "<", ">", "<=", ">=", and "IN".
+*/
+static int allowedOp(int op){
+ assert( TK_GT>TK_EQ && TK_GT<TK_GE );
+ assert( TK_LT>TK_EQ && TK_LT<TK_GE );
+ assert( TK_LE>TK_EQ && TK_LE<TK_GE );
+ assert( TK_GE==TK_EQ+4 );
+ return op==TK_IN || (op>=TK_EQ && op<=TK_GE) || op==TK_ISNULL;
+}
+
+/*
+** Swap two objects of type T.
+*/
+#define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;}
+
+/*
+** Commute a comparision operator. Expressions of the form "X op Y"
+** are converted into "Y op X".
+**
+** If a collation sequence is associated with either the left or right
+** side of the comparison, it remains associated with the same side after
+** the commutation. So "Y collate NOCASE op X" becomes
+** "X collate NOCASE op Y". This is because any collation sequence on
+** the left hand side of a comparison overrides any collation sequence
+** attached to the right. For the same reason the EP_ExpCollate flag
+** is not commuted.
+*/
+static void exprCommute(Expr *pExpr){
+ u16 expRight = (pExpr->pRight->flags & EP_ExpCollate);
+ u16 expLeft = (pExpr->pLeft->flags & EP_ExpCollate);
+ assert( allowedOp(pExpr->op) && pExpr->op!=TK_IN );
+ SWAP(CollSeq*,pExpr->pRight->pColl,pExpr->pLeft->pColl);
+ pExpr->pRight->flags = (pExpr->pRight->flags & ~EP_ExpCollate) | expLeft;
+ pExpr->pLeft->flags = (pExpr->pLeft->flags & ~EP_ExpCollate) | expRight;
+ SWAP(Expr*,pExpr->pRight,pExpr->pLeft);
+ if( pExpr->op>=TK_GT ){
+ assert( TK_LT==TK_GT+2 );
+ assert( TK_GE==TK_LE+2 );
+ assert( TK_GT>TK_EQ );
+ assert( TK_GT<TK_LE );
+ assert( pExpr->op>=TK_GT && pExpr->op<=TK_GE );
+ pExpr->op = ((pExpr->op-TK_GT)^2)+TK_GT;
+ }
+}
+
+/*
+** Translate from TK_xx operator to WO_xx bitmask.
+*/
+static int operatorMask(int op){
+ int c;
+ assert( allowedOp(op) );
+ if( op==TK_IN ){
+ c = WO_IN;
+ }else if( op==TK_ISNULL ){
+ c = WO_ISNULL;
+ }else{
+ c = WO_EQ<<(op-TK_EQ);
+ }
+ assert( op!=TK_ISNULL || c==WO_ISNULL );
+ assert( op!=TK_IN || c==WO_IN );
+ assert( op!=TK_EQ || c==WO_EQ );
+ assert( op!=TK_LT || c==WO_LT );
+ assert( op!=TK_LE || c==WO_LE );
+ assert( op!=TK_GT || c==WO_GT );
+ assert( op!=TK_GE || c==WO_GE );
+ return c;
+}
+
+/*
+** Search for a term in the WHERE clause that is of the form "X <op> <expr>"
+** where X is a reference to the iColumn of table iCur and <op> is one of
+** the WO_xx operator codes specified by the op parameter.
+** Return a pointer to the term. Return 0 if not found.
+*/
+static WhereTerm *findTerm(
+ WhereClause *pWC, /* The WHERE clause to be searched */
+ int iCur, /* Cursor number of LHS */
+ int iColumn, /* Column number of LHS */
+ Bitmask notReady, /* RHS must not overlap with this mask */
+ u16 op, /* Mask of WO_xx values describing operator */
+ Index *pIdx /* Must be compatible with this index, if not NULL */
+){
+ WhereTerm *pTerm;
+ int k;
+ for(pTerm=pWC->a, k=pWC->nTerm; k; k--, pTerm++){
+ if( pTerm->leftCursor==iCur
+ && (pTerm->prereqRight & notReady)==0
+ && pTerm->leftColumn==iColumn
+ && (pTerm->eOperator & op)!=0
+ ){
+ if( iCur>=0 && pIdx && pTerm->eOperator!=WO_ISNULL ){
+ Expr *pX = pTerm->pExpr;
+ CollSeq *pColl;
+ char idxaff;
+ int j;
+ Parse *pParse = pWC->pParse;
+
+ idxaff = pIdx->pTable->aCol[iColumn].affinity;
+ if( !sqlite3IndexAffinityOk(pX, idxaff) ) continue;
+
+ /* Figure out the collation sequence required from an index for
+ ** it to be useful for optimising expression pX. Store this
+ ** value in variable pColl.
+ */
+ assert(pX->pLeft);
+ pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight);
+ if( !pColl ){
+ pColl = pParse->db->pDfltColl;
+ }
+
+ for(j=0; j<pIdx->nColumn && pIdx->aiColumn[j]!=iColumn; j++){}
+ assert( j<pIdx->nColumn );
+ if( sqlite3StrICmp(pColl->zName, pIdx->azColl[j]) ) continue;
+ }
+ return pTerm;
+ }
+ }
+ return 0;
+}
+
+/* Forward reference */
+static void exprAnalyze(SrcList*, WhereClause*, int);
+
+/*
+** Call exprAnalyze on all terms in a WHERE clause.
+**
+**
+*/
+static void exprAnalyzeAll(
+ SrcList *pTabList, /* the FROM clause */
+ WhereClause *pWC /* the WHERE clause to be analyzed */
+){
+ int i;
+ for(i=pWC->nTerm-1; i>=0; i--){
+ exprAnalyze(pTabList, pWC, i);
+ }
+}
+
+#ifndef SQLITE_OMIT_LIKE_OPTIMIZATION
+/*
+** Check to see if the given expression is a LIKE or GLOB operator that
+** can be optimized using inequality constraints. Return TRUE if it is
+** so and false if not.
+**
+** In order for the operator to be optimizible, the RHS must be a string
+** literal that does not begin with a wildcard.
+*/
+static int isLikeOrGlob(
+ sqlite3 *db, /* The database */
+ Expr *pExpr, /* Test this expression */
+ int *pnPattern, /* Number of non-wildcard prefix characters */
+ int *pisComplete, /* True if the only wildcard is % in the last character */
+ int *pnoCase /* True if uppercase is equivalent to lowercase */
+){
+ const char *z;
+ Expr *pRight, *pLeft;
+ ExprList *pList;
+ int c, cnt;
+ char wc[3];
+ CollSeq *pColl;
+
+ if( !sqlite3IsLikeFunction(db, pExpr, pnoCase, wc) ){
+ return 0;
+ }
+#ifdef SQLITE_EBCDIC
+ if( *pnoCase ) return 0;
+#endif
+ pList = pExpr->pList;
+ pRight = pList->a[0].pExpr;
+ if( pRight->op!=TK_STRING
+ && (pRight->op!=TK_REGISTER || pRight->iColumn!=TK_STRING) ){
+ return 0;
+ }
+ pLeft = pList->a[1].pExpr;
+ if( pLeft->op!=TK_COLUMN ){
+ return 0;
+ }
+ pColl = pLeft->pColl;
+ assert( pColl!=0 || pLeft->iColumn==-1 );
+ if( pColl==0 ){
+ /* No collation is defined for the ROWID. Use the default. */
+ pColl = db->pDfltColl;
+ }
+ if( (pColl->type!=SQLITE_COLL_BINARY || *pnoCase) &&
+ (pColl->type!=SQLITE_COLL_NOCASE || !*pnoCase) ){
+ return 0;
+ }
+ sqlite3DequoteExpr(db, pRight);
+ z = (char *)pRight->token.z;
+ cnt = 0;
+ if( z ){
+ while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){ cnt++; }
+ }
+ if( cnt==0 || 255==(u8)z[cnt] ){
+ return 0;
+ }
+ *pisComplete = z[cnt]==wc[0] && z[cnt+1]==0;
+ *pnPattern = cnt;
+ return 1;
+}
+#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */
+
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Check to see if the given expression is of the form
+**
+** column MATCH expr
+**
+** If it is then return TRUE. If not, return FALSE.
+*/
+static int isMatchOfColumn(
+ Expr *pExpr /* Test this expression */
+){
+ ExprList *pList;
+
+ if( pExpr->op!=TK_FUNCTION ){
+ return 0;
+ }
+ if( pExpr->token.n!=5 ||
+ sqlite3StrNICmp((const char*)pExpr->token.z,"match",5)!=0 ){
+ return 0;
+ }
+ pList = pExpr->pList;
+ if( pList->nExpr!=2 ){
+ return 0;
+ }
+ if( pList->a[1].pExpr->op != TK_COLUMN ){
+ return 0;
+ }
+ return 1;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+/*
+** If the pBase expression originated in the ON or USING clause of
+** a join, then transfer the appropriate markings over to derived.
+*/
+static void transferJoinMarkings(Expr *pDerived, Expr *pBase){
+ pDerived->flags |= pBase->flags & EP_FromJoin;
+ pDerived->iRightJoinTable = pBase->iRightJoinTable;
+}
+
+#if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY)
+/*
+** Return TRUE if the given term of an OR clause can be converted
+** into an IN clause. The iCursor and iColumn define the left-hand
+** side of the IN clause.
+**
+** The context is that we have multiple OR-connected equality terms
+** like this:
+**
+** a=<expr1> OR a=<expr2> OR b=<expr3> OR ...
+**
+** The pOrTerm input to this routine corresponds to a single term of
+** this OR clause. In order for the term to be a condidate for
+** conversion to an IN operator, the following must be true:
+**
+** * The left-hand side of the term must be the column which
+** is identified by iCursor and iColumn.
+**
+** * If the right-hand side is also a column, then the affinities
+** of both right and left sides must be such that no type
+** conversions are required on the right. (Ticket #2249)
+**
+** If both of these conditions are true, then return true. Otherwise
+** return false.
+*/
+static int orTermIsOptCandidate(WhereTerm *pOrTerm, int iCursor, int iColumn){
+ int affLeft, affRight;
+ assert( pOrTerm->eOperator==WO_EQ );
+ if( pOrTerm->leftCursor!=iCursor ){
+ return 0;
+ }
+ if( pOrTerm->leftColumn!=iColumn ){
+ return 0;
+ }
+ affRight = sqlite3ExprAffinity(pOrTerm->pExpr->pRight);
+ if( affRight==0 ){
+ return 1;
+ }
+ affLeft = sqlite3ExprAffinity(pOrTerm->pExpr->pLeft);
+ if( affRight!=affLeft ){
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** Return true if the given term of an OR clause can be ignored during
+** a check to make sure all OR terms are candidates for optimization.
+** In other words, return true if a call to the orTermIsOptCandidate()
+** above returned false but it is not necessary to disqualify the
+** optimization.
+**
+** Suppose the original OR phrase was this:
+**
+** a=4 OR a=11 OR a=b
+**
+** During analysis, the third term gets flipped around and duplicate
+** so that we are left with this:
+**
+** a=4 OR a=11 OR a=b OR b=a
+**
+** Since the last two terms are duplicates, only one of them
+** has to qualify in order for the whole phrase to qualify. When
+** this routine is called, we know that pOrTerm did not qualify.
+** This routine merely checks to see if pOrTerm has a duplicate that
+** might qualify. If there is a duplicate that has not yet been
+** disqualified, then return true. If there are no duplicates, or
+** the duplicate has also been disqualifed, return false.
+*/
+static int orTermHasOkDuplicate(WhereClause *pOr, WhereTerm *pOrTerm){
+ if( pOrTerm->flags & TERM_COPIED ){
+ /* This is the original term. The duplicate is to the left had
+ ** has not yet been analyzed and thus has not yet been disqualified. */
+ return 1;
+ }
+ if( (pOrTerm->flags & TERM_VIRTUAL)!=0
+ && (pOr->a[pOrTerm->iParent].flags & TERM_OR_OK)!=0 ){
+ /* This is a duplicate term. The original qualified so this one
+ ** does not have to. */
+ return 1;
+ }
+ /* This is either a singleton term or else it is a duplicate for
+ ** which the original did not qualify. Either way we are done for. */
+ return 0;
+}
+#endif /* !SQLITE_OMIT_OR_OPTIMIZATION && !SQLITE_OMIT_SUBQUERY */
+
+/*
+** The input to this routine is an WhereTerm structure with only the
+** "pExpr" field filled in. The job of this routine is to analyze the
+** subexpression and populate all the other fields of the WhereTerm
+** structure.
+**
+** If the expression is of the form "<expr> <op> X" it gets commuted
+** to the standard form of "X <op> <expr>". If the expression is of
+** the form "X <op> Y" where both X and Y are columns, then the original
+** expression is unchanged and a new virtual expression of the form
+** "Y <op> X" is added to the WHERE clause and analyzed separately.
+*/
+static void exprAnalyze(
+ SrcList *pSrc, /* the FROM clause */
+ WhereClause *pWC, /* the WHERE clause */
+ int idxTerm /* Index of the term to be analyzed */
+){
+ WhereTerm *pTerm;
+ ExprMaskSet *pMaskSet;
+ Expr *pExpr;
+ Bitmask prereqLeft;
+ Bitmask prereqAll;
+ Bitmask extraRight = 0;
+ int nPattern;
+ int isComplete;
+ int noCase;
+ int op;
+ Parse *pParse = pWC->pParse;
+ sqlite3 *db = pParse->db;
+
+ if( db->mallocFailed ){
+ return;
+ }
+ pTerm = &pWC->a[idxTerm];
+ pMaskSet = pWC->pMaskSet;
+ pExpr = pTerm->pExpr;
+ prereqLeft = exprTableUsage(pMaskSet, pExpr->pLeft);
+ op = pExpr->op;
+ if( op==TK_IN ){
+ assert( pExpr->pRight==0 );
+ pTerm->prereqRight = exprListTableUsage(pMaskSet, pExpr->pList)
+ | exprSelectTableUsage(pMaskSet, pExpr->pSelect);
+ }else if( op==TK_ISNULL ){
+ pTerm->prereqRight = 0;
+ }else{
+ pTerm->prereqRight = exprTableUsage(pMaskSet, pExpr->pRight);
+ }
+ prereqAll = exprTableUsage(pMaskSet, pExpr);
+ if( ExprHasProperty(pExpr, EP_FromJoin) ){
+ Bitmask x = getMask(pMaskSet, pExpr->iRightJoinTable);
+ prereqAll |= x;
+ extraRight = x-1; /* ON clause terms may not be used with an index
+ ** on left table of a LEFT JOIN. Ticket #3015 */
+ }
+ pTerm->prereqAll = prereqAll;
+ pTerm->leftCursor = -1;
+ pTerm->iParent = -1;
+ pTerm->eOperator = 0;
+ if( allowedOp(op) && (pTerm->prereqRight & prereqLeft)==0 ){
+ Expr *pLeft = pExpr->pLeft;
+ Expr *pRight = pExpr->pRight;
+ if( pLeft->op==TK_COLUMN ){
+ pTerm->leftCursor = pLeft->iTable;
+ pTerm->leftColumn = pLeft->iColumn;
+ pTerm->eOperator = operatorMask(op);
+ }
+ if( pRight && pRight->op==TK_COLUMN ){
+ WhereTerm *pNew;
+ Expr *pDup;
+ if( pTerm->leftCursor>=0 ){
+ int idxNew;
+ pDup = sqlite3ExprDup(db, pExpr);
+ if( db->mallocFailed ){
+ sqlite3ExprDelete(pDup);
+ return;
+ }
+ idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC);
+ if( idxNew==0 ) return;
+ pNew = &pWC->a[idxNew];
+ pNew->iParent = idxTerm;
+ pTerm = &pWC->a[idxTerm];
+ pTerm->nChild = 1;
+ pTerm->flags |= TERM_COPIED;
+ }else{
+ pDup = pExpr;
+ pNew = pTerm;
+ }
+ exprCommute(pDup);
+ pLeft = pDup->pLeft;
+ pNew->leftCursor = pLeft->iTable;
+ pNew->leftColumn = pLeft->iColumn;
+ pNew->prereqRight = prereqLeft;
+ pNew->prereqAll = prereqAll;
+ pNew->eOperator = operatorMask(pDup->op);
+ }
+ }
+
+#ifndef SQLITE_OMIT_BETWEEN_OPTIMIZATION
+ /* If a term is the BETWEEN operator, create two new virtual terms
+ ** that define the range that the BETWEEN implements.
+ */
+ else if( pExpr->op==TK_BETWEEN ){
+ ExprList *pList = pExpr->pList;
+ int i;
+ static const u8 ops[] = {TK_GE, TK_LE};
+ assert( pList!=0 );
+ assert( pList->nExpr==2 );
+ for(i=0; i<2; i++){
+ Expr *pNewExpr;
+ int idxNew;
+ pNewExpr = sqlite3Expr(db, ops[i], sqlite3ExprDup(db, pExpr->pLeft),
+ sqlite3ExprDup(db, pList->a[i].pExpr), 0);
+ idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
+ exprAnalyze(pSrc, pWC, idxNew);
+ pTerm = &pWC->a[idxTerm];
+ pWC->a[idxNew].iParent = idxTerm;
+ }
+ pTerm->nChild = 2;
+ }
+#endif /* SQLITE_OMIT_BETWEEN_OPTIMIZATION */
+
+#if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY)
+ /* Attempt to convert OR-connected terms into an IN operator so that
+ ** they can make use of indices. Example:
+ **
+ ** x = expr1 OR expr2 = x OR x = expr3
+ **
+ ** is converted into
+ **
+ ** x IN (expr1,expr2,expr3)
+ **
+ ** This optimization must be omitted if OMIT_SUBQUERY is defined because
+ ** the compiler for the the IN operator is part of sub-queries.
+ */
+ else if( pExpr->op==TK_OR ){
+ int ok;
+ int i, j;
+ int iColumn, iCursor;
+ WhereClause sOr;
+ WhereTerm *pOrTerm;
+
+ assert( (pTerm->flags & TERM_DYNAMIC)==0 );
+ whereClauseInit(&sOr, pWC->pParse, pMaskSet);
+ whereSplit(&sOr, pExpr, TK_OR);
+ exprAnalyzeAll(pSrc, &sOr);
+ assert( sOr.nTerm>=2 );
+ j = 0;
+ if( db->mallocFailed ) goto or_not_possible;
+ do{
+ assert( j<sOr.nTerm );
+ iColumn = sOr.a[j].leftColumn;
+ iCursor = sOr.a[j].leftCursor;
+ ok = iCursor>=0;
+ for(i=sOr.nTerm-1, pOrTerm=sOr.a; i>=0 && ok; i--, pOrTerm++){
+ if( pOrTerm->eOperator!=WO_EQ ){
+ goto or_not_possible;
+ }
+ if( orTermIsOptCandidate(pOrTerm, iCursor, iColumn) ){
+ pOrTerm->flags |= TERM_OR_OK;
+ }else if( orTermHasOkDuplicate(&sOr, pOrTerm) ){
+ pOrTerm->flags &= ~TERM_OR_OK;
+ }else{
+ ok = 0;
+ }
+ }
+ }while( !ok && (sOr.a[j++].flags & TERM_COPIED)!=0 && j<2 );
+ if( ok ){
+ ExprList *pList = 0;
+ Expr *pNew, *pDup;
+ Expr *pLeft = 0;
+ for(i=sOr.nTerm-1, pOrTerm=sOr.a; i>=0 && ok; i--, pOrTerm++){
+ if( (pOrTerm->flags & TERM_OR_OK)==0 ) continue;
+ pDup = sqlite3ExprDup(db, pOrTerm->pExpr->pRight);
+ pList = sqlite3ExprListAppend(pWC->pParse, pList, pDup, 0);
+ pLeft = pOrTerm->pExpr->pLeft;
+ }
+ assert( pLeft!=0 );
+ pDup = sqlite3ExprDup(db, pLeft);
+ pNew = sqlite3Expr(db, TK_IN, pDup, 0, 0);
+ if( pNew ){
+ int idxNew;
+ transferJoinMarkings(pNew, pExpr);
+ pNew->pList = pList;
+ idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC);
+ exprAnalyze(pSrc, pWC, idxNew);
+ pTerm = &pWC->a[idxTerm];
+ pWC->a[idxNew].iParent = idxTerm;
+ pTerm->nChild = 1;
+ }else{
+ sqlite3ExprListDelete(pList);
+ }
+ }
+or_not_possible:
+ whereClauseClear(&sOr);
+ }
+#endif /* SQLITE_OMIT_OR_OPTIMIZATION */
+
+#ifndef SQLITE_OMIT_LIKE_OPTIMIZATION
+ /* Add constraints to reduce the search space on a LIKE or GLOB
+ ** operator.
+ **
+ ** A like pattern of the form "x LIKE 'abc%'" is changed into constraints
+ **
+ ** x>='abc' AND x<'abd' AND x LIKE 'abc%'
+ **
+ ** The last character of the prefix "abc" is incremented to form the
+ ** termination condidtion "abd". This trick of incrementing the last
+ ** is not 255 and if the character set is not EBCDIC.
+ */
+ if( isLikeOrGlob(db, pExpr, &nPattern, &isComplete, &noCase) ){
+ Expr *pLeft, *pRight;
+ Expr *pStr1, *pStr2;
+ Expr *pNewExpr1, *pNewExpr2;
+ int idxNew1, idxNew2;
+
+ pLeft = pExpr->pList->a[1].pExpr;
+ pRight = pExpr->pList->a[0].pExpr;
+ pStr1 = sqlite3PExpr(pParse, TK_STRING, 0, 0, 0);
+ if( pStr1 ){
+ sqlite3TokenCopy(db, &pStr1->token, &pRight->token);
+ pStr1->token.n = nPattern;
+ pStr1->flags = EP_Dequoted;
+ }
+ pStr2 = sqlite3ExprDup(db, pStr1);
+ if( !db->mallocFailed ){
+ u8 c, *pC;
+ assert( pStr2->token.dyn );
+ pC = (u8*)&pStr2->token.z[nPattern-1];
+ c = *pC;
+ if( noCase ) c = sqlite3UpperToLower[c];
+ *pC = c + 1;
+ }
+ pNewExpr1 = sqlite3PExpr(pParse, TK_GE, sqlite3ExprDup(db,pLeft), pStr1, 0);
+ idxNew1 = whereClauseInsert(pWC, pNewExpr1, TERM_VIRTUAL|TERM_DYNAMIC);
+ exprAnalyze(pSrc, pWC, idxNew1);
+ pNewExpr2 = sqlite3PExpr(pParse, TK_LT, sqlite3ExprDup(db,pLeft), pStr2, 0);
+ idxNew2 = whereClauseInsert(pWC, pNewExpr2, TERM_VIRTUAL|TERM_DYNAMIC);
+ exprAnalyze(pSrc, pWC, idxNew2);
+ pTerm = &pWC->a[idxTerm];
+ if( isComplete ){
+ pWC->a[idxNew1].iParent = idxTerm;
+ pWC->a[idxNew2].iParent = idxTerm;
+ pTerm->nChild = 2;
+ }
+ }
+#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ /* Add a WO_MATCH auxiliary term to the constraint set if the
+ ** current expression is of the form: column MATCH expr.
+ ** This information is used by the xBestIndex methods of
+ ** virtual tables. The native query optimizer does not attempt
+ ** to do anything with MATCH functions.
+ */
+ if( isMatchOfColumn(pExpr) ){
+ int idxNew;
+ Expr *pRight, *pLeft;
+ WhereTerm *pNewTerm;
+ Bitmask prereqColumn, prereqExpr;
+
+ pRight = pExpr->pList->a[0].pExpr;
+ pLeft = pExpr->pList->a[1].pExpr;
+ prereqExpr = exprTableUsage(pMaskSet, pRight);
+ prereqColumn = exprTableUsage(pMaskSet, pLeft);
+ if( (prereqExpr & prereqColumn)==0 ){
+ Expr *pNewExpr;
+ pNewExpr = sqlite3Expr(db, TK_MATCH, 0, sqlite3ExprDup(db, pRight), 0);
+ idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
+ pNewTerm = &pWC->a[idxNew];
+ pNewTerm->prereqRight = prereqExpr;
+ pNewTerm->leftCursor = pLeft->iTable;
+ pNewTerm->leftColumn = pLeft->iColumn;
+ pNewTerm->eOperator = WO_MATCH;
+ pNewTerm->iParent = idxTerm;
+ pTerm = &pWC->a[idxTerm];
+ pTerm->nChild = 1;
+ pTerm->flags |= TERM_COPIED;
+ pNewTerm->prereqAll = pTerm->prereqAll;
+ }
+ }
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+ /* Prevent ON clause terms of a LEFT JOIN from being used to drive
+ ** an index for tables to the left of the join.
+ */
+ pTerm->prereqRight |= extraRight;
+}
+
+/*
+** Return TRUE if any of the expressions in pList->a[iFirst...] contain
+** a reference to any table other than the iBase table.
+*/
+static int referencesOtherTables(
+ ExprList *pList, /* Search expressions in ths list */
+ ExprMaskSet *pMaskSet, /* Mapping from tables to bitmaps */
+ int iFirst, /* Be searching with the iFirst-th expression */
+ int iBase /* Ignore references to this table */
+){
+ Bitmask allowed = ~getMask(pMaskSet, iBase);
+ while( iFirst<pList->nExpr ){
+ if( (exprTableUsage(pMaskSet, pList->a[iFirst++].pExpr)&allowed)!=0 ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/*
+** This routine decides if pIdx can be used to satisfy the ORDER BY
+** clause. If it can, it returns 1. If pIdx cannot satisfy the
+** ORDER BY clause, this routine returns 0.
+**
+** pOrderBy is an ORDER BY clause from a SELECT statement. pTab is the
+** left-most table in the FROM clause of that same SELECT statement and
+** the table has a cursor number of "base". pIdx is an index on pTab.
+**
+** nEqCol is the number of columns of pIdx that are used as equality
+** constraints. Any of these columns may be missing from the ORDER BY
+** clause and the match can still be a success.
+**
+** All terms of the ORDER BY that match against the index must be either
+** ASC or DESC. (Terms of the ORDER BY clause past the end of a UNIQUE
+** index do not need to satisfy this constraint.) The *pbRev value is
+** set to 1 if the ORDER BY clause is all DESC and it is set to 0 if
+** the ORDER BY clause is all ASC.
+*/
+static int isSortingIndex(
+ Parse *pParse, /* Parsing context */
+ ExprMaskSet *pMaskSet, /* Mapping from table indices to bitmaps */
+ Index *pIdx, /* The index we are testing */
+ int base, /* Cursor number for the table to be sorted */
+ ExprList *pOrderBy, /* The ORDER BY clause */
+ int nEqCol, /* Number of index columns with == constraints */
+ int *pbRev /* Set to 1 if ORDER BY is DESC */
+){
+ int i, j; /* Loop counters */
+ int sortOrder = 0; /* XOR of index and ORDER BY sort direction */
+ int nTerm; /* Number of ORDER BY terms */
+ struct ExprList_item *pTerm; /* A term of the ORDER BY clause */
+ sqlite3 *db = pParse->db;
+
+ assert( pOrderBy!=0 );
+ nTerm = pOrderBy->nExpr;
+ assert( nTerm>0 );
+
+ /* Match terms of the ORDER BY clause against columns of
+ ** the index.
+ **
+ ** Note that indices have pIdx->nColumn regular columns plus
+ ** one additional column containing the rowid. The rowid column
+ ** of the index is also allowed to match against the ORDER BY
+ ** clause.
+ */
+ for(i=j=0, pTerm=pOrderBy->a; j<nTerm && i<=pIdx->nColumn; i++){
+ Expr *pExpr; /* The expression of the ORDER BY pTerm */
+ CollSeq *pColl; /* The collating sequence of pExpr */
+ int termSortOrder; /* Sort order for this term */
+ int iColumn; /* The i-th column of the index. -1 for rowid */
+ int iSortOrder; /* 1 for DESC, 0 for ASC on the i-th index term */
+ const char *zColl; /* Name of the collating sequence for i-th index term */
+
+ pExpr = pTerm->pExpr;
+ if( pExpr->op!=TK_COLUMN || pExpr->iTable!=base ){
+ /* Can not use an index sort on anything that is not a column in the
+ ** left-most table of the FROM clause */
+ break;
+ }
+ pColl = sqlite3ExprCollSeq(pParse, pExpr);
+ if( !pColl ){
+ pColl = db->pDfltColl;
+ }
+ if( i<pIdx->nColumn ){
+ iColumn = pIdx->aiColumn[i];
+ if( iColumn==pIdx->pTable->iPKey ){
+ iColumn = -1;
+ }
+ iSortOrder = pIdx->aSortOrder[i];
+ zColl = pIdx->azColl[i];
+ }else{
+ iColumn = -1;
+ iSortOrder = 0;
+ zColl = pColl->zName;
+ }
+ if( pExpr->iColumn!=iColumn || sqlite3StrICmp(pColl->zName, zColl) ){
+ /* Term j of the ORDER BY clause does not match column i of the index */
+ if( i<nEqCol ){
+ /* If an index column that is constrained by == fails to match an
+ ** ORDER BY term, that is OK. Just ignore that column of the index
+ */
+ continue;
+ }else{
+ /* If an index column fails to match and is not constrained by ==
+ ** then the index cannot satisfy the ORDER BY constraint.
+ */
+ return 0;
+ }
+ }
+ assert( pIdx->aSortOrder!=0 );
+ assert( pTerm->sortOrder==0 || pTerm->sortOrder==1 );
+ assert( iSortOrder==0 || iSortOrder==1 );
+ termSortOrder = iSortOrder ^ pTerm->sortOrder;
+ if( i>nEqCol ){
+ if( termSortOrder!=sortOrder ){
+ /* Indices can only be used if all ORDER BY terms past the
+ ** equality constraints are all either DESC or ASC. */
+ return 0;
+ }
+ }else{
+ sortOrder = termSortOrder;
+ }
+ j++;
+ pTerm++;
+ if( iColumn<0 && !referencesOtherTables(pOrderBy, pMaskSet, j, base) ){
+ /* If the indexed column is the primary key and everything matches
+ ** so far and none of the ORDER BY terms to the right reference other
+ ** tables in the join, then we are assured that the index can be used
+ ** to sort because the primary key is unique and so none of the other
+ ** columns will make any difference
+ */
+ j = nTerm;
+ }
+ }
+
+ *pbRev = sortOrder!=0;
+ if( j>=nTerm ){
+ /* All terms of the ORDER BY clause are covered by this index so
+ ** this index can be used for sorting. */
+ return 1;
+ }
+ if( pIdx->onError!=OE_None && i==pIdx->nColumn
+ && !referencesOtherTables(pOrderBy, pMaskSet, j, base) ){
+ /* All terms of this index match some prefix of the ORDER BY clause
+ ** and the index is UNIQUE and no terms on the tail of the ORDER BY
+ ** clause reference other tables in a join. If this is all true then
+ ** the order by clause is superfluous. */
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** Check table to see if the ORDER BY clause in pOrderBy can be satisfied
+** by sorting in order of ROWID. Return true if so and set *pbRev to be
+** true for reverse ROWID and false for forward ROWID order.
+*/
+static int sortableByRowid(
+ int base, /* Cursor number for table to be sorted */
+ ExprList *pOrderBy, /* The ORDER BY clause */
+ ExprMaskSet *pMaskSet, /* Mapping from tables to bitmaps */
+ int *pbRev /* Set to 1 if ORDER BY is DESC */
+){
+ Expr *p;
+
+ assert( pOrderBy!=0 );
+ assert( pOrderBy->nExpr>0 );
+ p = pOrderBy->a[0].pExpr;
+ if( p->op==TK_COLUMN && p->iTable==base && p->iColumn==-1
+ && !referencesOtherTables(pOrderBy, pMaskSet, 1, base) ){
+ *pbRev = pOrderBy->a[0].sortOrder;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** Prepare a crude estimate of the logarithm of the input value.
+** The results need not be exact. This is only used for estimating
+** the total cost of performing operatings with O(logN) or O(NlogN)
+** complexity. Because N is just a guess, it is no great tragedy if
+** logN is a little off.
+*/
+static double estLog(double N){
+ double logN = 1;
+ double x = 10;
+ while( N>x ){
+ logN += 1;
+ x *= 10;
+ }
+ return logN;
+}
+
+/*
+** Two routines for printing the content of an sqlite3_index_info
+** structure. Used for testing and debugging only. If neither
+** SQLITE_TEST or SQLITE_DEBUG are defined, then these routines
+** are no-ops.
+*/
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_DEBUG)
+static void TRACE_IDX_INPUTS(sqlite3_index_info *p){
+ int i;
+ if( !sqlite3WhereTrace ) return;
+ for(i=0; i<p->nConstraint; i++){
+ sqlite3DebugPrintf(" constraint[%d]: col=%d termid=%d op=%d usabled=%d\n",
+ i,
+ p->aConstraint[i].iColumn,
+ p->aConstraint[i].iTermOffset,
+ p->aConstraint[i].op,
+ p->aConstraint[i].usable);
+ }
+ for(i=0; i<p->nOrderBy; i++){
+ sqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n",
+ i,
+ p->aOrderBy[i].iColumn,
+ p->aOrderBy[i].desc);
+ }
+}
+static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){
+ int i;
+ if( !sqlite3WhereTrace ) return;
+ for(i=0; i<p->nConstraint; i++){
+ sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n",
+ i,
+ p->aConstraintUsage[i].argvIndex,
+ p->aConstraintUsage[i].omit);
+ }
+ sqlite3DebugPrintf(" idxNum=%d\n", p->idxNum);
+ sqlite3DebugPrintf(" idxStr=%s\n", p->idxStr);
+ sqlite3DebugPrintf(" orderByConsumed=%d\n", p->orderByConsumed);
+ sqlite3DebugPrintf(" estimatedCost=%g\n", p->estimatedCost);
+}
+#else
+#define TRACE_IDX_INPUTS(A)
+#define TRACE_IDX_OUTPUTS(A)
+#endif
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Compute the best index for a virtual table.
+**
+** The best index is computed by the xBestIndex method of the virtual
+** table module. This routine is really just a wrapper that sets up
+** the sqlite3_index_info structure that is used to communicate with
+** xBestIndex.
+**
+** In a join, this routine might be called multiple times for the
+** same virtual table. The sqlite3_index_info structure is created
+** and initialized on the first invocation and reused on all subsequent
+** invocations. The sqlite3_index_info structure is also used when
+** code is generated to access the virtual table. The whereInfoDelete()
+** routine takes care of freeing the sqlite3_index_info structure after
+** everybody has finished with it.
+*/
+static double bestVirtualIndex(
+ Parse *pParse, /* The parsing context */
+ WhereClause *pWC, /* The WHERE clause */
+ struct SrcList_item *pSrc, /* The FROM clause term to search */
+ Bitmask notReady, /* Mask of cursors that are not available */
+ ExprList *pOrderBy, /* The order by clause */
+ int orderByUsable, /* True if we can potential sort */
+ sqlite3_index_info **ppIdxInfo /* Index information passed to xBestIndex */
+){
+ Table *pTab = pSrc->pTab;
+ sqlite3_index_info *pIdxInfo;
+ struct sqlite3_index_constraint *pIdxCons;
+ struct sqlite3_index_orderby *pIdxOrderBy;
+ struct sqlite3_index_constraint_usage *pUsage;
+ WhereTerm *pTerm;
+ int i, j;
+ int nOrderBy;
+ int rc;
+
+ /* If the sqlite3_index_info structure has not been previously
+ ** allocated and initialized for this virtual table, then allocate
+ ** and initialize it now
+ */
+ pIdxInfo = *ppIdxInfo;
+ if( pIdxInfo==0 ){
+ WhereTerm *pTerm;
+ int nTerm;
+ WHERETRACE(("Recomputing index info for %s...\n", pTab->zName));
+
+ /* Count the number of possible WHERE clause constraints referring
+ ** to this virtual table */
+ for(i=nTerm=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
+ if( pTerm->leftCursor != pSrc->iCursor ) continue;
+ if( (pTerm->eOperator&(pTerm->eOperator-1))==0 );
+ testcase( pTerm->eOperator==WO_IN );
+ testcase( pTerm->eOperator==WO_ISNULL );
+ if( pTerm->eOperator & (WO_IN|WO_ISNULL) ) continue;
+ nTerm++;
+ }
+
+ /* If the ORDER BY clause contains only columns in the current
+ ** virtual table then allocate space for the aOrderBy part of
+ ** the sqlite3_index_info structure.
+ */
+ nOrderBy = 0;
+ if( pOrderBy ){
+ for(i=0; i<pOrderBy->nExpr; i++){
+ Expr *pExpr = pOrderBy->a[i].pExpr;
+ if( pExpr->op!=TK_COLUMN || pExpr->iTable!=pSrc->iCursor ) break;
+ }
+ if( i==pOrderBy->nExpr ){
+ nOrderBy = pOrderBy->nExpr;
+ }
+ }
+
+ /* Allocate the sqlite3_index_info structure
+ */
+ pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo)
+ + (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm
+ + sizeof(*pIdxOrderBy)*nOrderBy );
+ if( pIdxInfo==0 ){
+ sqlite3ErrorMsg(pParse, "out of memory");
+ return 0.0;
+ }
+ *ppIdxInfo = pIdxInfo;
+
+ /* Initialize the structure. The sqlite3_index_info structure contains
+ ** many fields that are declared "const" to prevent xBestIndex from
+ ** changing them. We have to do some funky casting in order to
+ ** initialize those fields.
+ */
+ pIdxCons = (struct sqlite3_index_constraint*)&pIdxInfo[1];
+ pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm];
+ pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy];
+ *(int*)&pIdxInfo->nConstraint = nTerm;
+ *(int*)&pIdxInfo->nOrderBy = nOrderBy;
+ *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint = pIdxCons;
+ *(struct sqlite3_index_orderby**)&pIdxInfo->aOrderBy = pIdxOrderBy;
+ *(struct sqlite3_index_constraint_usage**)&pIdxInfo->aConstraintUsage =
+ pUsage;
+
+ for(i=j=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
+ if( pTerm->leftCursor != pSrc->iCursor ) continue;
+ if( (pTerm->eOperator&(pTerm->eOperator-1))==0 );
+ testcase( pTerm->eOperator==WO_IN );
+ testcase( pTerm->eOperator==WO_ISNULL );
+ if( pTerm->eOperator & (WO_IN|WO_ISNULL) ) continue;
+ pIdxCons[j].iColumn = pTerm->leftColumn;
+ pIdxCons[j].iTermOffset = i;
+ pIdxCons[j].op = pTerm->eOperator;
+ /* The direct assignment in the previous line is possible only because
+ ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The
+ ** following asserts verify this fact. */
+ assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ );
+ assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT );
+ assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE );
+ assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT );
+ assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
+ assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH );
+ assert( pTerm->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) );
+ j++;
+ }
+ for(i=0; i<nOrderBy; i++){
+ Expr *pExpr = pOrderBy->a[i].pExpr;
+ pIdxOrderBy[i].iColumn = pExpr->iColumn;
+ pIdxOrderBy[i].desc = pOrderBy->a[i].sortOrder;
+ }
+ }
+
+ /* At this point, the sqlite3_index_info structure that pIdxInfo points
+ ** to will have been initialized, either during the current invocation or
+ ** during some prior invocation. Now we just have to customize the
+ ** details of pIdxInfo for the current invocation and pass it to
+ ** xBestIndex.
+ */
+
+ /* The module name must be defined. Also, by this point there must
+ ** be a pointer to an sqlite3_vtab structure. Otherwise
+ ** sqlite3ViewGetColumnNames() would have picked up the error.
+ */
+ assert( pTab->azModuleArg && pTab->azModuleArg[0] );
+ assert( pTab->pVtab );
+#if 0
+ if( pTab->pVtab==0 ){
+ sqlite3ErrorMsg(pParse, "undefined module %s for table %s",
+ pTab->azModuleArg[0], pTab->zName);
+ return 0.0;
+ }
+#endif
+
+ /* Set the aConstraint[].usable fields and initialize all
+ ** output variables to zero.
+ **
+ ** aConstraint[].usable is true for constraints where the right-hand
+ ** side contains only references to tables to the left of the current
+ ** table. In other words, if the constraint is of the form:
+ **
+ ** column = expr
+ **
+ ** and we are evaluating a join, then the constraint on column is
+ ** only valid if all tables referenced in expr occur to the left
+ ** of the table containing column.
+ **
+ ** The aConstraints[] array contains entries for all constraints
+ ** on the current table. That way we only have to compute it once
+ ** even though we might try to pick the best index multiple times.
+ ** For each attempt at picking an index, the order of tables in the
+ ** join might be different so we have to recompute the usable flag
+ ** each time.
+ */
+ pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
+ pUsage = pIdxInfo->aConstraintUsage;
+ for(i=0; i<pIdxInfo->nConstraint; i++, pIdxCons++){
+ j = pIdxCons->iTermOffset;
+ pTerm = &pWC->a[j];
+ pIdxCons->usable = (pTerm->prereqRight & notReady)==0;
+ }
+ memset(pUsage, 0, sizeof(pUsage[0])*pIdxInfo->nConstraint);
+ if( pIdxInfo->needToFreeIdxStr ){
+ sqlite3_free(pIdxInfo->idxStr);
+ }
+ pIdxInfo->idxStr = 0;
+ pIdxInfo->idxNum = 0;
+ pIdxInfo->needToFreeIdxStr = 0;
+ pIdxInfo->orderByConsumed = 0;
+ pIdxInfo->estimatedCost = SQLITE_BIG_DBL / 2.0;
+ nOrderBy = pIdxInfo->nOrderBy;
+ if( pIdxInfo->nOrderBy && !orderByUsable ){
+ *(int*)&pIdxInfo->nOrderBy = 0;
+ }
+
+ (void)sqlite3SafetyOff(pParse->db);
+ WHERETRACE(("xBestIndex for %s\n", pTab->zName));
+ TRACE_IDX_INPUTS(pIdxInfo);
+ rc = pTab->pVtab->pModule->xBestIndex(pTab->pVtab, pIdxInfo);
+ TRACE_IDX_OUTPUTS(pIdxInfo);
+ (void)sqlite3SafetyOn(pParse->db);
+
+ for(i=0; i<pIdxInfo->nConstraint; i++){
+ if( !pIdxInfo->aConstraint[i].usable && pUsage[i].argvIndex>0 ){
+ sqlite3ErrorMsg(pParse,
+ "table %s: xBestIndex returned an invalid plan", pTab->zName);
+ return 0.0;
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_NOMEM ){
+ pParse->db->mallocFailed = 1;
+ }else {
+ sqlite3ErrorMsg(pParse, "%s", sqlite3ErrStr(rc));
+ }
+ }
+ *(int*)&pIdxInfo->nOrderBy = nOrderBy;
+
+ return pIdxInfo->estimatedCost;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+/*
+** Find the best index for accessing a particular table. Return a pointer
+** to the index, flags that describe how the index should be used, the
+** number of equality constraints, and the "cost" for this index.
+**
+** The lowest cost index wins. The cost is an estimate of the amount of
+** CPU and disk I/O need to process the request using the selected index.
+** Factors that influence cost include:
+**
+** * The estimated number of rows that will be retrieved. (The
+** fewer the better.)
+**
+** * Whether or not sorting must occur.
+**
+** * Whether or not there must be separate lookups in the
+** index and in the main table.
+**
+*/
+static double bestIndex(
+ Parse *pParse, /* The parsing context */
+ WhereClause *pWC, /* The WHERE clause */
+ struct SrcList_item *pSrc, /* The FROM clause term to search */
+ Bitmask notReady, /* Mask of cursors that are not available */
+ ExprList *pOrderBy, /* The order by clause */
+ Index **ppIndex, /* Make *ppIndex point to the best index */
+ int *pFlags, /* Put flags describing this choice in *pFlags */
+ int *pnEq /* Put the number of == or IN constraints here */
+){
+ WhereTerm *pTerm;
+ Index *bestIdx = 0; /* Index that gives the lowest cost */
+ double lowestCost; /* The cost of using bestIdx */
+ int bestFlags = 0; /* Flags associated with bestIdx */
+ int bestNEq = 0; /* Best value for nEq */
+ int iCur = pSrc->iCursor; /* The cursor of the table to be accessed */
+ Index *pProbe; /* An index we are evaluating */
+ int rev; /* True to scan in reverse order */
+ int flags; /* Flags associated with pProbe */
+ int nEq; /* Number of == or IN constraints */
+ int eqTermMask; /* Mask of valid equality operators */
+ double cost; /* Cost of using pProbe */
+
+ WHERETRACE(("bestIndex: tbl=%s notReady=%x\n", pSrc->pTab->zName, notReady));
+ lowestCost = SQLITE_BIG_DBL;
+ pProbe = pSrc->pTab->pIndex;
+
+ /* If the table has no indices and there are no terms in the where
+ ** clause that refer to the ROWID, then we will never be able to do
+ ** anything other than a full table scan on this table. We might as
+ ** well put it first in the join order. That way, perhaps it can be
+ ** referenced by other tables in the join.
+ */
+ if( pProbe==0 &&
+ findTerm(pWC, iCur, -1, 0, WO_EQ|WO_IN|WO_LT|WO_LE|WO_GT|WO_GE,0)==0 &&
+ (pOrderBy==0 || !sortableByRowid(iCur, pOrderBy, pWC->pMaskSet, &rev)) ){
+ *pFlags = 0;
+ *ppIndex = 0;
+ *pnEq = 0;
+ return 0.0;
+ }
+
+ /* Check for a rowid=EXPR or rowid IN (...) constraints
+ */
+ pTerm = findTerm(pWC, iCur, -1, notReady, WO_EQ|WO_IN, 0);
+ if( pTerm ){
+ Expr *pExpr;
+ *ppIndex = 0;
+ bestFlags = WHERE_ROWID_EQ;
+ if( pTerm->eOperator & WO_EQ ){
+ /* Rowid== is always the best pick. Look no further. Because only
+ ** a single row is generated, output is always in sorted order */
+ *pFlags = WHERE_ROWID_EQ | WHERE_UNIQUE;
+ *pnEq = 1;
+ WHERETRACE(("... best is rowid\n"));
+ return 0.0;
+ }else if( (pExpr = pTerm->pExpr)->pList!=0 ){
+ /* Rowid IN (LIST): cost is NlogN where N is the number of list
+ ** elements. */
+ lowestCost = pExpr->pList->nExpr;
+ lowestCost *= estLog(lowestCost);
+ }else{
+ /* Rowid IN (SELECT): cost is NlogN where N is the number of rows
+ ** in the result of the inner select. We have no way to estimate
+ ** that value so make a wild guess. */
+ lowestCost = 200;
+ }
+ WHERETRACE(("... rowid IN cost: %.9g\n", lowestCost));
+ }
+
+ /* Estimate the cost of a table scan. If we do not know how many
+ ** entries are in the table, use 1 million as a guess.
+ */
+ cost = pProbe ? pProbe->aiRowEst[0] : 1000000;
+ WHERETRACE(("... table scan base cost: %.9g\n", cost));
+ flags = WHERE_ROWID_RANGE;
+
+ /* Check for constraints on a range of rowids in a table scan.
+ */
+ pTerm = findTerm(pWC, iCur, -1, notReady, WO_LT|WO_LE|WO_GT|WO_GE, 0);
+ if( pTerm ){
+ if( findTerm(pWC, iCur, -1, notReady, WO_LT|WO_LE, 0) ){
+ flags |= WHERE_TOP_LIMIT;
+ cost /= 3; /* Guess that rowid<EXPR eliminates two-thirds or rows */
+ }
+ if( findTerm(pWC, iCur, -1, notReady, WO_GT|WO_GE, 0) ){
+ flags |= WHERE_BTM_LIMIT;
+ cost /= 3; /* Guess that rowid>EXPR eliminates two-thirds of rows */
+ }
+ WHERETRACE(("... rowid range reduces cost to %.9g\n", cost));
+ }else{
+ flags = 0;
+ }
+
+ /* If the table scan does not satisfy the ORDER BY clause, increase
+ ** the cost by NlogN to cover the expense of sorting. */
+ if( pOrderBy ){
+ if( sortableByRowid(iCur, pOrderBy, pWC->pMaskSet, &rev) ){
+ flags |= WHERE_ORDERBY|WHERE_ROWID_RANGE;
+ if( rev ){
+ flags |= WHERE_REVERSE;
+ }
+ }else{
+ cost += cost*estLog(cost);
+ WHERETRACE(("... sorting increases cost to %.9g\n", cost));
+ }
+ }
+ if( cost<lowestCost ){
+ lowestCost = cost;
+ bestFlags = flags;
+ }
+
+ /* If the pSrc table is the right table of a LEFT JOIN then we may not
+ ** use an index to satisfy IS NULL constraints on that table. This is
+ ** because columns might end up being NULL if the table does not match -
+ ** a circumstance which the index cannot help us discover. Ticket #2177.
+ */
+ if( (pSrc->jointype & JT_LEFT)!=0 ){
+ eqTermMask = WO_EQ|WO_IN;
+ }else{
+ eqTermMask = WO_EQ|WO_IN|WO_ISNULL;
+ }
+
+ /* Look at each index.
+ */
+ for(; pProbe; pProbe=pProbe->pNext){
+ int i; /* Loop counter */
+ double inMultiplier = 1;
+
+ WHERETRACE(("... index %s:\n", pProbe->zName));
+
+ /* Count the number of columns in the index that are satisfied
+ ** by x=EXPR constraints or x IN (...) constraints.
+ */
+ flags = 0;
+ for(i=0; i<pProbe->nColumn; i++){
+ int j = pProbe->aiColumn[i];
+ pTerm = findTerm(pWC, iCur, j, notReady, eqTermMask, pProbe);
+ if( pTerm==0 ) break;
+ flags |= WHERE_COLUMN_EQ;
+ if( pTerm->eOperator & WO_IN ){
+ Expr *pExpr = pTerm->pExpr;
+ flags |= WHERE_COLUMN_IN;
+ if( pExpr->pSelect!=0 ){
+ inMultiplier *= 25;
+ }else if( pExpr->pList!=0 ){
+ inMultiplier *= pExpr->pList->nExpr + 1;
+ }
+ }
+ }
+ cost = pProbe->aiRowEst[i] * inMultiplier * estLog(inMultiplier);
+ nEq = i;
+ if( pProbe->onError!=OE_None && (flags & WHERE_COLUMN_IN)==0
+ && nEq==pProbe->nColumn ){
+ flags |= WHERE_UNIQUE;
+ }
+ WHERETRACE(("...... nEq=%d inMult=%.9g cost=%.9g\n",nEq,inMultiplier,cost));
+
+ /* Look for range constraints
+ */
+ if( nEq<pProbe->nColumn ){
+ int j = pProbe->aiColumn[nEq];
+ pTerm = findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE|WO_GT|WO_GE, pProbe);
+ if( pTerm ){
+ flags |= WHERE_COLUMN_RANGE;
+ if( findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE, pProbe) ){
+ flags |= WHERE_TOP_LIMIT;
+ cost /= 3;
+ }
+ if( findTerm(pWC, iCur, j, notReady, WO_GT|WO_GE, pProbe) ){
+ flags |= WHERE_BTM_LIMIT;
+ cost /= 3;
+ }
+ WHERETRACE(("...... range reduces cost to %.9g\n", cost));
+ }
+ }
+
+ /* Add the additional cost of sorting if that is a factor.
+ */
+ if( pOrderBy ){
+ if( (flags & WHERE_COLUMN_IN)==0 &&
+ isSortingIndex(pParse,pWC->pMaskSet,pProbe,iCur,pOrderBy,nEq,&rev) ){
+ if( flags==0 ){
+ flags = WHERE_COLUMN_RANGE;
+ }
+ flags |= WHERE_ORDERBY;
+ if( rev ){
+ flags |= WHERE_REVERSE;
+ }
+ }else{
+ cost += cost*estLog(cost);
+ WHERETRACE(("...... orderby increases cost to %.9g\n", cost));
+ }
+ }
+
+ /* Check to see if we can get away with using just the index without
+ ** ever reading the table. If that is the case, then halve the
+ ** cost of this index.
+ */
+ if( flags && pSrc->colUsed < (((Bitmask)1)<<(BMS-1)) ){
+ Bitmask m = pSrc->colUsed;
+ int j;
+ for(j=0; j<pProbe->nColumn; j++){
+ int x = pProbe->aiColumn[j];
+ if( x<BMS-1 ){
+ m &= ~(((Bitmask)1)<<x);
+ }
+ }
+ if( m==0 ){
+ flags |= WHERE_IDX_ONLY;
+ cost /= 2;
+ WHERETRACE(("...... idx-only reduces cost to %.9g\n", cost));
+ }
+ }
+
+ /* If this index has achieved the lowest cost so far, then use it.
+ */
+ if( flags && cost < lowestCost ){
+ bestIdx = pProbe;
+ lowestCost = cost;
+ bestFlags = flags;
+ bestNEq = nEq;
+ }
+ }
+
+ /* Report the best result
+ */
+ *ppIndex = bestIdx;
+ WHERETRACE(("best index is %s, cost=%.9g, flags=%x, nEq=%d\n",
+ bestIdx ? bestIdx->zName : "(none)", lowestCost, bestFlags, bestNEq));
+ *pFlags = bestFlags | eqTermMask;
+ *pnEq = bestNEq;
+ return lowestCost;
+}
+
+
+/*
+** Disable a term in the WHERE clause. Except, do not disable the term
+** if it controls a LEFT OUTER JOIN and it did not originate in the ON
+** or USING clause of that join.
+**
+** Consider the term t2.z='ok' in the following queries:
+**
+** (1) SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.x WHERE t2.z='ok'
+** (2) SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.x AND t2.z='ok'
+** (3) SELECT * FROM t1, t2 WHERE t1.a=t2.x AND t2.z='ok'
+**
+** The t2.z='ok' is disabled in the in (2) because it originates
+** in the ON clause. The term is disabled in (3) because it is not part
+** of a LEFT OUTER JOIN. In (1), the term is not disabled.
+**
+** Disabling a term causes that term to not be tested in the inner loop
+** of the join. Disabling is an optimization. When terms are satisfied
+** by indices, we disable them to prevent redundant tests in the inner
+** loop. We would get the correct results if nothing were ever disabled,
+** but joins might run a little slower. The trick is to disable as much
+** as we can without disabling too much. If we disabled in (1), we'd get
+** the wrong answer. See ticket #813.
+*/
+static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){
+ if( pTerm
+ && (pTerm->flags & TERM_CODED)==0
+ && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin))
+ ){
+ pTerm->flags |= TERM_CODED;
+ if( pTerm->iParent>=0 ){
+ WhereTerm *pOther = &pTerm->pWC->a[pTerm->iParent];
+ if( (--pOther->nChild)==0 ){
+ disableTerm(pLevel, pOther);
+ }
+ }
+ }
+}
+
+/*
+** Apply the affinities associated with the first n columns of index
+** pIdx to the values in the n registers starting at base.
+*/
+static void codeApplyAffinity(Parse *pParse, int base, int n, Index *pIdx){
+ if( n>0 ){
+ Vdbe *v = pParse->pVdbe;
+ assert( v!=0 );
+ sqlite3VdbeAddOp2(v, OP_Affinity, base, n);
+ sqlite3IndexAffinityStr(v, pIdx);
+ sqlite3ExprCacheAffinityChange(pParse, base, n);
+ }
+}
+
+
+/*
+** Generate code for a single equality term of the WHERE clause. An equality
+** term can be either X=expr or X IN (...). pTerm is the term to be
+** coded.
+**
+** The current value for the constraint is left in register iReg.
+**
+** For a constraint of the form X=expr, the expression is evaluated and its
+** result is left on the stack. For constraints of the form X IN (...)
+** this routine sets up a loop that will iterate over all values of X.
+*/
+static int codeEqualityTerm(
+ Parse *pParse, /* The parsing context */
+ WhereTerm *pTerm, /* The term of the WHERE clause to be coded */
+ WhereLevel *pLevel, /* When level of the FROM clause we are working on */
+ int iTarget /* Attempt to leave results in this register */
+){
+ Expr *pX = pTerm->pExpr;
+ Vdbe *v = pParse->pVdbe;
+ int iReg; /* Register holding results */
+
+ if( iTarget<=0 ){
+ iReg = iTarget = sqlite3GetTempReg(pParse);
+ }
+ if( pX->op==TK_EQ ){
+ iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget);
+ }else if( pX->op==TK_ISNULL ){
+ iReg = iTarget;
+ sqlite3VdbeAddOp2(v, OP_Null, 0, iReg);
+#ifndef SQLITE_OMIT_SUBQUERY
+ }else{
+ int eType;
+ int iTab;
+ struct InLoop *pIn;
+
+ assert( pX->op==TK_IN );
+ iReg = iTarget;
+ eType = sqlite3FindInIndex(pParse, pX, 1);
+ iTab = pX->iTable;
+ sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0);
+ VdbeComment((v, "%.*s", pX->span.n, pX->span.z));
+ if( pLevel->nIn==0 ){
+ pLevel->nxt = sqlite3VdbeMakeLabel(v);
+ }
+ pLevel->nIn++;
+ pLevel->aInLoop = sqlite3DbReallocOrFree(pParse->db, pLevel->aInLoop,
+ sizeof(pLevel->aInLoop[0])*pLevel->nIn);
+ pIn = pLevel->aInLoop;
+ if( pIn ){
+ pIn += pLevel->nIn - 1;
+ pIn->iCur = iTab;
+ if( eType==IN_INDEX_ROWID ){
+ pIn->topAddr = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iReg);
+ }else{
+ pIn->topAddr = sqlite3VdbeAddOp3(v, OP_Column, iTab, 0, iReg);
+ }
+ sqlite3VdbeAddOp1(v, OP_IsNull, iReg);
+ }else{
+ pLevel->nIn = 0;
+ }
+#endif
+ }
+ disableTerm(pLevel, pTerm);
+ return iReg;
+}
+
+/*
+** Generate code that will evaluate all == and IN constraints for an
+** index. The values for all constraints are left on the stack.
+**
+** For example, consider table t1(a,b,c,d,e,f) with index i1(a,b,c).
+** Suppose the WHERE clause is this: a==5 AND b IN (1,2,3) AND c>5 AND c<10
+** The index has as many as three equality constraints, but in this
+** example, the third "c" value is an inequality. So only two
+** constraints are coded. This routine will generate code to evaluate
+** a==5 and b IN (1,2,3). The current values for a and b will be left
+** on the stack - a is the deepest and b the shallowest.
+**
+** In the example above nEq==2. But this subroutine works for any value
+** of nEq including 0. If nEq==0, this routine is nearly a no-op.
+** The only thing it does is allocate the pLevel->iMem memory cell.
+**
+** This routine always allocates at least one memory cell and puts
+** the address of that memory cell in pLevel->iMem. The code that
+** calls this routine will use pLevel->iMem to store the termination
+** key value of the loop. If one or more IN operators appear, then
+** this routine allocates an additional nEq memory cells for internal
+** use.
+*/
+static int codeAllEqualityTerms(
+ Parse *pParse, /* Parsing context */
+ WhereLevel *pLevel, /* Which nested loop of the FROM we are coding */
+ WhereClause *pWC, /* The WHERE clause */
+ Bitmask notReady, /* Which parts of FROM have not yet been coded */
+ int nExtraReg /* Number of extra registers to allocate */
+){
+ int nEq = pLevel->nEq; /* The number of == or IN constraints to code */
+ Vdbe *v = pParse->pVdbe; /* The virtual machine under construction */
+ Index *pIdx = pLevel->pIdx; /* The index being used for this loop */
+ int iCur = pLevel->iTabCur; /* The cursor of the table */
+ WhereTerm *pTerm; /* A single constraint term */
+ int j; /* Loop counter */
+ int regBase; /* Base register */
+
+ /* Figure out how many memory cells we will need then allocate them.
+ ** We always need at least one used to store the loop terminator
+ ** value. If there are IN operators we'll need one for each == or
+ ** IN constraint.
+ */
+ pLevel->iMem = pParse->nMem + 1;
+ regBase = pParse->nMem + 2;
+ pParse->nMem += pLevel->nEq + 2 + nExtraReg;
+
+ /* Evaluate the equality constraints
+ */
+ assert( pIdx->nColumn>=nEq );
+ for(j=0; j<nEq; j++){
+ int r1;
+ int k = pIdx->aiColumn[j];
+ pTerm = findTerm(pWC, iCur, k, notReady, pLevel->flags, pIdx);
+ if( pTerm==0 ) break;
+ assert( (pTerm->flags & TERM_CODED)==0 );
+ r1 = codeEqualityTerm(pParse, pTerm, pLevel, regBase+j);
+ if( r1!=regBase+j ){
+ sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j);
+ }
+ testcase( pTerm->eOperator & WO_ISNULL );
+ testcase( pTerm->eOperator & WO_IN );
+ if( (pTerm->eOperator & (WO_ISNULL|WO_IN))==0 ){
+ sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->brk);
+ }
+ }
+ return regBase;
+}
+
+#if defined(SQLITE_TEST)
+/*
+** The following variable holds a text description of query plan generated
+** by the most recent call to sqlite3WhereBegin(). Each call to WhereBegin
+** overwrites the previous. This information is used for testing and
+** analysis only.
+*/
+SQLITE_API char sqlite3_query_plan[BMS*2*40]; /* Text of the join */
+static int nQPlan = 0; /* Next free slow in _query_plan[] */
+
+#endif /* SQLITE_TEST */
+
+
+/*
+** Free a WhereInfo structure
+*/
+static void whereInfoFree(WhereInfo *pWInfo){
+ if( pWInfo ){
+ int i;
+ for(i=0; i<pWInfo->nLevel; i++){
+ sqlite3_index_info *pInfo = pWInfo->a[i].pIdxInfo;
+ if( pInfo ){
+ assert( pInfo->needToFreeIdxStr==0 );
+ sqlite3_free(pInfo);
+ }
+ }
+ sqlite3_free(pWInfo);
+ }
+}
+
+
+/*
+** Generate the beginning of the loop used for WHERE clause processing.
+** The return value is a pointer to an opaque structure that contains
+** information needed to terminate the loop. Later, the calling routine
+** should invoke sqlite3WhereEnd() with the return value of this function
+** in order to complete the WHERE clause processing.
+**
+** If an error occurs, this routine returns NULL.
+**
+** The basic idea is to do a nested loop, one loop for each table in
+** the FROM clause of a select. (INSERT and UPDATE statements are the
+** same as a SELECT with only a single table in the FROM clause.) For
+** example, if the SQL is this:
+**
+** SELECT * FROM t1, t2, t3 WHERE ...;
+**
+** Then the code generated is conceptually like the following:
+**
+** foreach row1 in t1 do \ Code generated
+** foreach row2 in t2 do |-- by sqlite3WhereBegin()
+** foreach row3 in t3 do /
+** ...
+** end \ Code generated
+** end |-- by sqlite3WhereEnd()
+** end /
+**
+** Note that the loops might not be nested in the order in which they
+** appear in the FROM clause if a different order is better able to make
+** use of indices. Note also that when the IN operator appears in
+** the WHERE clause, it might result in additional nested loops for
+** scanning through all values on the right-hand side of the IN.
+**
+** There are Btree cursors associated with each table. t1 uses cursor
+** number pTabList->a[0].iCursor. t2 uses the cursor pTabList->a[1].iCursor.
+** And so forth. This routine generates code to open those VDBE cursors
+** and sqlite3WhereEnd() generates the code to close them.
+**
+** The code that sqlite3WhereBegin() generates leaves the cursors named
+** in pTabList pointing at their appropriate entries. The [...] code
+** can use OP_Column and OP_Rowid opcodes on these cursors to extract
+** data from the various tables of the loop.
+**
+** If the WHERE clause is empty, the foreach loops must each scan their
+** entire tables. Thus a three-way join is an O(N^3) operation. But if
+** the tables have indices and there are terms in the WHERE clause that
+** refer to those indices, a complete table scan can be avoided and the
+** code will run much faster. Most of the work of this routine is checking
+** to see if there are indices that can be used to speed up the loop.
+**
+** Terms of the WHERE clause are also used to limit which rows actually
+** make it to the "..." in the middle of the loop. After each "foreach",
+** terms of the WHERE clause that use only terms in that loop and outer
+** loops are evaluated and if false a jump is made around all subsequent
+** inner loops (or around the "..." if the test occurs within the inner-
+** most loop)
+**
+** OUTER JOINS
+**
+** An outer join of tables t1 and t2 is conceptally coded as follows:
+**
+** foreach row1 in t1 do
+** flag = 0
+** foreach row2 in t2 do
+** start:
+** ...
+** flag = 1
+** end
+** if flag==0 then
+** move the row2 cursor to a null row
+** goto start
+** fi
+** end
+**
+** ORDER BY CLAUSE PROCESSING
+**
+** *ppOrderBy is a pointer to the ORDER BY clause of a SELECT statement,
+** if there is one. If there is no ORDER BY clause or if this routine
+** is called from an UPDATE or DELETE statement, then ppOrderBy is NULL.
+**
+** If an index can be used so that the natural output order of the table
+** scan is correct for the ORDER BY clause, then that index is used and
+** *ppOrderBy is set to NULL. This is an optimization that prevents an
+** unnecessary sort of the result set if an index appropriate for the
+** ORDER BY clause already exists.
+**
+** If the where clause loops cannot be arranged to provide the correct
+** output order, then the *ppOrderBy is unchanged.
+*/
+SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
+ Parse *pParse, /* The parser context */
+ SrcList *pTabList, /* A list of all tables to be scanned */
+ Expr *pWhere, /* The WHERE clause */
+ ExprList **ppOrderBy, /* An ORDER BY clause, or NULL */
+ u8 wflags /* One of the WHERE_* flags defined in sqliteInt.h */
+){
+ int i; /* Loop counter */
+ WhereInfo *pWInfo; /* Will become the return value of this function */
+ Vdbe *v = pParse->pVdbe; /* The virtual database engine */
+ int brk, cont = 0; /* Addresses used during code generation */
+ Bitmask notReady; /* Cursors that are not yet positioned */
+ WhereTerm *pTerm; /* A single term in the WHERE clause */
+ ExprMaskSet maskSet; /* The expression mask set */
+ WhereClause wc; /* The WHERE clause is divided into these terms */
+ struct SrcList_item *pTabItem; /* A single entry from pTabList */
+ WhereLevel *pLevel; /* A single level in the pWInfo list */
+ int iFrom; /* First unused FROM clause element */
+ int andFlags; /* AND-ed combination of all wc.a[].flags */
+ sqlite3 *db; /* Database connection */
+ ExprList *pOrderBy = 0;
+
+ /* The number of tables in the FROM clause is limited by the number of
+ ** bits in a Bitmask
+ */
+ if( pTabList->nSrc>BMS ){
+ sqlite3ErrorMsg(pParse, "at most %d tables in a join", BMS);
+ return 0;
+ }
+
+ if( ppOrderBy ){
+ pOrderBy = *ppOrderBy;
+ }
+
+ /* Split the WHERE clause into separate subexpressions where each
+ ** subexpression is separated by an AND operator.
+ */
+ initMaskSet(&maskSet);
+ whereClauseInit(&wc, pParse, &maskSet);
+ sqlite3ExprCodeConstants(pParse, pWhere);
+ whereSplit(&wc, pWhere, TK_AND);
+
+ /* Allocate and initialize the WhereInfo structure that will become the
+ ** return value.
+ */
+ db = pParse->db;
+ pWInfo = sqlite3DbMallocZero(db,
+ sizeof(WhereInfo) + pTabList->nSrc*sizeof(WhereLevel));
+ if( db->mallocFailed ){
+ goto whereBeginNoMem;
+ }
+ pWInfo->nLevel = pTabList->nSrc;
+ pWInfo->pParse = pParse;
+ pWInfo->pTabList = pTabList;
+ pWInfo->iBreak = sqlite3VdbeMakeLabel(v);
+
+ /* Special case: a WHERE clause that is constant. Evaluate the
+ ** expression and either jump over all of the code or fall thru.
+ */
+ if( pWhere && (pTabList->nSrc==0 || sqlite3ExprIsConstantNotJoin(pWhere)) ){
+ sqlite3ExprIfFalse(pParse, pWhere, pWInfo->iBreak, SQLITE_JUMPIFNULL);
+ pWhere = 0;
+ }
+
+ /* Assign a bit from the bitmask to every term in the FROM clause.
+ **
+ ** When assigning bitmask values to FROM clause cursors, it must be
+ ** the case that if X is the bitmask for the N-th FROM clause term then
+ ** the bitmask for all FROM clause terms to the left of the N-th term
+ ** is (X-1). An expression from the ON clause of a LEFT JOIN can use
+ ** its Expr.iRightJoinTable value to find the bitmask of the right table
+ ** of the join. Subtracting one from the right table bitmask gives a
+ ** bitmask for all tables to the left of the join. Knowing the bitmask
+ ** for all tables to the left of a left join is important. Ticket #3015.
+ */
+ for(i=0; i<pTabList->nSrc; i++){
+ createMask(&maskSet, pTabList->a[i].iCursor);
+ }
+#ifndef NDEBUG
+ {
+ Bitmask toTheLeft = 0;
+ for(i=0; i<pTabList->nSrc; i++){
+ Bitmask m = getMask(&maskSet, pTabList->a[i].iCursor);
+ assert( (m-1)==toTheLeft );
+ toTheLeft |= m;
+ }
+ }
+#endif
+
+ /* Analyze all of the subexpressions. Note that exprAnalyze() might
+ ** add new virtual terms onto the end of the WHERE clause. We do not
+ ** want to analyze these virtual terms, so start analyzing at the end
+ ** and work forward so that the added virtual terms are never processed.
+ */
+ exprAnalyzeAll(pTabList, &wc);
+ if( db->mallocFailed ){
+ goto whereBeginNoMem;
+ }
+
+ /* Chose the best index to use for each table in the FROM clause.
+ **
+ ** This loop fills in the following fields:
+ **
+ ** pWInfo->a[].pIdx The index to use for this level of the loop.
+ ** pWInfo->a[].flags WHERE_xxx flags associated with pIdx
+ ** pWInfo->a[].nEq The number of == and IN constraints
+ ** pWInfo->a[].iFrom When term of the FROM clause is being coded
+ ** pWInfo->a[].iTabCur The VDBE cursor for the database table
+ ** pWInfo->a[].iIdxCur The VDBE cursor for the index
+ **
+ ** This loop also figures out the nesting order of tables in the FROM
+ ** clause.
+ */
+ notReady = ~(Bitmask)0;
+ pTabItem = pTabList->a;
+ pLevel = pWInfo->a;
+ andFlags = ~0;
+ WHERETRACE(("*** Optimizer Start ***\n"));
+ for(i=iFrom=0, pLevel=pWInfo->a; i<pTabList->nSrc; i++, pLevel++){
+ Index *pIdx; /* Index for FROM table at pTabItem */
+ int flags; /* Flags asssociated with pIdx */
+ int nEq; /* Number of == or IN constraints */
+ double cost; /* The cost for pIdx */
+ int j; /* For looping over FROM tables */
+ Index *pBest = 0; /* The best index seen so far */
+ int bestFlags = 0; /* Flags associated with pBest */
+ int bestNEq = 0; /* nEq associated with pBest */
+ double lowestCost; /* Cost of the pBest */
+ int bestJ = 0; /* The value of j */
+ Bitmask m; /* Bitmask value for j or bestJ */
+ int once = 0; /* True when first table is seen */
+ sqlite3_index_info *pIndex; /* Current virtual index */
+
+ lowestCost = SQLITE_BIG_DBL;
+ for(j=iFrom, pTabItem=&pTabList->a[j]; j<pTabList->nSrc; j++, pTabItem++){
+ int doNotReorder; /* True if this table should not be reordered */
+
+ doNotReorder = (pTabItem->jointype & (JT_LEFT|JT_CROSS))!=0;
+ if( once && doNotReorder ) break;
+ m = getMask(&maskSet, pTabItem->iCursor);
+ if( (m & notReady)==0 ){
+ if( j==iFrom ) iFrom++;
+ continue;
+ }
+ assert( pTabItem->pTab );
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTabItem->pTab) ){
+ sqlite3_index_info **ppIdxInfo = &pWInfo->a[j].pIdxInfo;
+ cost = bestVirtualIndex(pParse, &wc, pTabItem, notReady,
+ ppOrderBy ? *ppOrderBy : 0, i==0,
+ ppIdxInfo);
+ flags = WHERE_VIRTUALTABLE;
+ pIndex = *ppIdxInfo;
+ if( pIndex && pIndex->orderByConsumed ){
+ flags = WHERE_VIRTUALTABLE | WHERE_ORDERBY;
+ }
+ pIdx = 0;
+ nEq = 0;
+ if( (SQLITE_BIG_DBL/2.0)<cost ){
+ /* The cost is not allowed to be larger than SQLITE_BIG_DBL (the
+ ** inital value of lowestCost in this loop. If it is, then
+ ** the (cost<lowestCost) test below will never be true and
+ ** pLevel->pBestIdx never set.
+ */
+ cost = (SQLITE_BIG_DBL/2.0);
+ }
+ }else
+#endif
+ {
+ cost = bestIndex(pParse, &wc, pTabItem, notReady,
+ (i==0 && ppOrderBy) ? *ppOrderBy : 0,
+ &pIdx, &flags, &nEq);
+ pIndex = 0;
+ }
+ if( cost<lowestCost ){
+ once = 1;
+ lowestCost = cost;
+ pBest = pIdx;
+ bestFlags = flags;
+ bestNEq = nEq;
+ bestJ = j;
+ pLevel->pBestIdx = pIndex;
+ }
+ if( doNotReorder ) break;
+ }
+ WHERETRACE(("*** Optimizer choose table %d for loop %d\n", bestJ,
+ pLevel-pWInfo->a));
+ if( (bestFlags & WHERE_ORDERBY)!=0 ){
+ *ppOrderBy = 0;
+ }
+ andFlags &= bestFlags;
+ pLevel->flags = bestFlags;
+ pLevel->pIdx = pBest;
+ pLevel->nEq = bestNEq;
+ pLevel->aInLoop = 0;
+ pLevel->nIn = 0;
+ if( pBest ){
+ pLevel->iIdxCur = pParse->nTab++;
+ }else{
+ pLevel->iIdxCur = -1;
+ }
+ notReady &= ~getMask(&maskSet, pTabList->a[bestJ].iCursor);
+ pLevel->iFrom = bestJ;
+ }
+ WHERETRACE(("*** Optimizer Finished ***\n"));
+
+ /* If the total query only selects a single row, then the ORDER BY
+ ** clause is irrelevant.
+ */
+ if( (andFlags & WHERE_UNIQUE)!=0 && ppOrderBy ){
+ *ppOrderBy = 0;
+ }
+
+ /* If the caller is an UPDATE or DELETE statement that is requesting
+ ** to use a one-pass algorithm, determine if this is appropriate.
+ ** The one-pass algorithm only works if the WHERE clause constraints
+ ** the statement to update a single row.
+ */
+ assert( (wflags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 );
+ if( (wflags & WHERE_ONEPASS_DESIRED)!=0 && (andFlags & WHERE_UNIQUE)!=0 ){
+ pWInfo->okOnePass = 1;
+ pWInfo->a[0].flags &= ~WHERE_IDX_ONLY;
+ }
+
+ /* Open all tables in the pTabList and any indices selected for
+ ** searching those tables.
+ */
+ sqlite3CodeVerifySchema(pParse, -1); /* Insert the cookie verifier Goto */
+ for(i=0, pLevel=pWInfo->a; i<pTabList->nSrc; i++, pLevel++){
+ Table *pTab; /* Table to open */
+ Index *pIx; /* Index used to access pTab (if any) */
+ int iDb; /* Index of database containing table/index */
+ int iIdxCur = pLevel->iIdxCur;
+
+#ifndef SQLITE_OMIT_EXPLAIN
+ if( pParse->explain==2 ){
+ char *zMsg;
+ struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
+ zMsg = sqlite3MPrintf(db, "TABLE %s", pItem->zName);
+ if( pItem->zAlias ){
+ zMsg = sqlite3MPrintf(db, "%z AS %s", zMsg, pItem->zAlias);
+ }
+ if( (pIx = pLevel->pIdx)!=0 ){
+ zMsg = sqlite3MPrintf(db, "%z WITH INDEX %s", zMsg, pIx->zName);
+ }else if( pLevel->flags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){
+ zMsg = sqlite3MPrintf(db, "%z USING PRIMARY KEY", zMsg);
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ else if( pLevel->pBestIdx ){
+ sqlite3_index_info *pBestIdx = pLevel->pBestIdx;
+ zMsg = sqlite3MPrintf(db, "%z VIRTUAL TABLE INDEX %d:%s", zMsg,
+ pBestIdx->idxNum, pBestIdx->idxStr);
+ }
+#endif
+ if( pLevel->flags & WHERE_ORDERBY ){
+ zMsg = sqlite3MPrintf(db, "%z ORDER BY", zMsg);
+ }
+ sqlite3VdbeAddOp4(v, OP_Explain, i, pLevel->iFrom, 0, zMsg, P4_DYNAMIC);
+ }
+#endif /* SQLITE_OMIT_EXPLAIN */
+ pTabItem = &pTabList->a[pLevel->iFrom];
+ pTab = pTabItem->pTab;
+ iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ if( pTab->isEphem || pTab->pSelect ) continue;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( pLevel->pBestIdx ){
+ int iCur = pTabItem->iCursor;
+ sqlite3VdbeAddOp4(v, OP_VOpen, iCur, 0, 0,
+ (const char*)pTab->pVtab, P4_VTAB);
+ }else
+#endif
+ if( (pLevel->flags & WHERE_IDX_ONLY)==0 ){
+ int op = pWInfo->okOnePass ? OP_OpenWrite : OP_OpenRead;
+ sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op);
+ if( !pWInfo->okOnePass && pTab->nCol<(sizeof(Bitmask)*8) ){
+ Bitmask b = pTabItem->colUsed;
+ int n = 0;
+ for(; b; b=b>>1, n++){}
+ sqlite3VdbeChangeP2(v, sqlite3VdbeCurrentAddr(v)-2, n);
+ assert( n<=pTab->nCol );
+ }
+ }else{
+ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+ }
+ pLevel->iTabCur = pTabItem->iCursor;
+ if( (pIx = pLevel->pIdx)!=0 ){
+ KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIx);
+ assert( pIx->pSchema==pTab->pSchema );
+ sqlite3VdbeAddOp2(v, OP_SetNumColumns, 0, pIx->nColumn+1);
+ sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur, pIx->tnum, iDb,
+ (char*)pKey, P4_KEYINFO_HANDOFF);
+ VdbeComment((v, "%s", pIx->zName));
+ }
+ sqlite3CodeVerifySchema(pParse, iDb);
+ }
+ pWInfo->iTop = sqlite3VdbeCurrentAddr(v);
+
+ /* Generate the code to do the search. Each iteration of the for
+ ** loop below generates code for a single nested loop of the VM
+ ** program.
+ */
+ notReady = ~(Bitmask)0;
+ for(i=0, pLevel=pWInfo->a; i<pTabList->nSrc; i++, pLevel++){
+ int j;
+ int iCur = pTabItem->iCursor; /* The VDBE cursor for the table */
+ Index *pIdx; /* The index we will be using */
+ int nxt; /* Where to jump to continue with the next IN case */
+ int iIdxCur; /* The VDBE cursor for the index */
+ int omitTable; /* True if we use the index only */
+ int bRev; /* True if we need to scan in reverse order */
+
+ pTabItem = &pTabList->a[pLevel->iFrom];
+ iCur = pTabItem->iCursor;
+ pIdx = pLevel->pIdx;
+ iIdxCur = pLevel->iIdxCur;
+ bRev = (pLevel->flags & WHERE_REVERSE)!=0;
+ omitTable = (pLevel->flags & WHERE_IDX_ONLY)!=0;
+
+ /* Create labels for the "break" and "continue" instructions
+ ** for the current loop. Jump to brk to break out of a loop.
+ ** Jump to cont to go immediately to the next iteration of the
+ ** loop.
+ **
+ ** When there is an IN operator, we also have a "nxt" label that
+ ** means to continue with the next IN value combination. When
+ ** there are no IN operators in the constraints, the "nxt" label
+ ** is the same as "brk".
+ */
+ brk = pLevel->brk = pLevel->nxt = sqlite3VdbeMakeLabel(v);
+ cont = pLevel->cont = sqlite3VdbeMakeLabel(v);
+
+ /* If this is the right table of a LEFT OUTER JOIN, allocate and
+ ** initialize a memory cell that records if this table matches any
+ ** row of the left table of the join.
+ */
+ if( pLevel->iFrom>0 && (pTabItem[0].jointype & JT_LEFT)!=0 ){
+ pLevel->iLeftJoin = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin);
+ VdbeComment((v, "init LEFT JOIN no-match flag"));
+ }
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( pLevel->pBestIdx ){
+ /* Case 0: The table is a virtual-table. Use the VFilter and VNext
+ ** to access the data.
+ */
+ int j;
+ int iReg; /* P3 Value for OP_VFilter */
+ sqlite3_index_info *pBestIdx = pLevel->pBestIdx;
+ int nConstraint = pBestIdx->nConstraint;
+ struct sqlite3_index_constraint_usage *aUsage =
+ pBestIdx->aConstraintUsage;
+ const struct sqlite3_index_constraint *aConstraint =
+ pBestIdx->aConstraint;
+
+ iReg = sqlite3GetTempRange(pParse, nConstraint+2);
+ for(j=1; j<=nConstraint; j++){
+ int k;
+ for(k=0; k<nConstraint; k++){
+ if( aUsage[k].argvIndex==j ){
+ int iTerm = aConstraint[k].iTermOffset;
+ sqlite3ExprCode(pParse, wc.a[iTerm].pExpr->pRight, iReg+j+1);
+ break;
+ }
+ }
+ if( k==nConstraint ) break;
+ }
+ sqlite3VdbeAddOp2(v, OP_Integer, pBestIdx->idxNum, iReg);
+ sqlite3VdbeAddOp2(v, OP_Integer, j-1, iReg+1);
+ sqlite3VdbeAddOp4(v, OP_VFilter, iCur, brk, iReg, pBestIdx->idxStr,
+ pBestIdx->needToFreeIdxStr ? P4_MPRINTF : P4_STATIC);
+ sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2);
+ pBestIdx->needToFreeIdxStr = 0;
+ for(j=0; j<pBestIdx->nConstraint; j++){
+ if( aUsage[j].omit ){
+ int iTerm = aConstraint[j].iTermOffset;
+ disableTerm(pLevel, &wc.a[iTerm]);
+ }
+ }
+ pLevel->op = OP_VNext;
+ pLevel->p1 = iCur;
+ pLevel->p2 = sqlite3VdbeCurrentAddr(v);
+ }else
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+ if( pLevel->flags & WHERE_ROWID_EQ ){
+ /* Case 1: We can directly reference a single row using an
+ ** equality comparison against the ROWID field. Or
+ ** we reference multiple rows using a "rowid IN (...)"
+ ** construct.
+ */
+ int r1;
+ pTerm = findTerm(&wc, iCur, -1, notReady, WO_EQ|WO_IN, 0);
+ assert( pTerm!=0 );
+ assert( pTerm->pExpr!=0 );
+ assert( pTerm->leftCursor==iCur );
+ assert( omitTable==0 );
+ r1 = codeEqualityTerm(pParse, pTerm, pLevel, 0);
+ nxt = pLevel->nxt;
+ sqlite3VdbeAddOp2(v, OP_MustBeInt, r1, nxt);
+ sqlite3VdbeAddOp3(v, OP_NotExists, iCur, nxt, r1);
+ VdbeComment((v, "pk"));
+ pLevel->op = OP_Noop;
+ }else if( pLevel->flags & WHERE_ROWID_RANGE ){
+ /* Case 2: We have an inequality comparison against the ROWID field.
+ */
+ int testOp = OP_Noop;
+ int start;
+ WhereTerm *pStart, *pEnd;
+
+ assert( omitTable==0 );
+ pStart = findTerm(&wc, iCur, -1, notReady, WO_GT|WO_GE, 0);
+ pEnd = findTerm(&wc, iCur, -1, notReady, WO_LT|WO_LE, 0);
+ if( bRev ){
+ pTerm = pStart;
+ pStart = pEnd;
+ pEnd = pTerm;
+ }
+ if( pStart ){
+ Expr *pX;
+ int r1, regFree1;
+ pX = pStart->pExpr;
+ assert( pX!=0 );
+ assert( pStart->leftCursor==iCur );
+ r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &regFree1);
+ sqlite3VdbeAddOp3(v, OP_ForceInt, r1, brk,
+ pX->op==TK_LE || pX->op==TK_GT);
+ sqlite3VdbeAddOp3(v, bRev ? OP_MoveLt : OP_MoveGe, iCur, brk, r1);
+ VdbeComment((v, "pk"));
+ sqlite3ReleaseTempReg(pParse, regFree1);
+ disableTerm(pLevel, pStart);
+ }else{
+ sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, brk);
+ }
+ if( pEnd ){
+ Expr *pX;
+ pX = pEnd->pExpr;
+ assert( pX!=0 );
+ assert( pEnd->leftCursor==iCur );
+ pLevel->iMem = ++pParse->nMem;
+ sqlite3ExprCode(pParse, pX->pRight, pLevel->iMem);
+ if( pX->op==TK_LT || pX->op==TK_GT ){
+ testOp = bRev ? OP_Le : OP_Ge;
+ }else{
+ testOp = bRev ? OP_Lt : OP_Gt;
+ }
+ disableTerm(pLevel, pEnd);
+ }
+ start = sqlite3VdbeCurrentAddr(v);
+ pLevel->op = bRev ? OP_Prev : OP_Next;
+ pLevel->p1 = iCur;
+ pLevel->p2 = start;
+ if( testOp!=OP_Noop ){
+ int r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCur, r1);
+ /* sqlite3VdbeAddOp2(v, OP_SCopy, pLevel->iMem, 0); */
+ sqlite3VdbeAddOp3(v, testOp, pLevel->iMem, brk, r1);
+ sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL);
+ sqlite3ReleaseTempReg(pParse, r1);
+ }
+ }else if( pLevel->flags & (WHERE_COLUMN_RANGE|WHERE_COLUMN_EQ) ){
+ /* Case 3: A scan using an index.
+ **
+ ** The WHERE clause may contain zero or more equality
+ ** terms ("==" or "IN" operators) that refer to the N
+ ** left-most columns of the index. It may also contain
+ ** inequality constraints (>, <, >= or <=) on the indexed
+ ** column that immediately follows the N equalities. Only
+ ** the right-most column can be an inequality - the rest must
+ ** use the "==" and "IN" operators. For example, if the
+ ** index is on (x,y,z), then the following clauses are all
+ ** optimized:
+ **
+ ** x=5
+ ** x=5 AND y=10
+ ** x=5 AND y<10
+ ** x=5 AND y>5 AND y<10
+ ** x=5 AND y=5 AND z<=10
+ **
+ ** The z<10 term of the following cannot be used, only
+ ** the x=5 term:
+ **
+ ** x=5 AND z<10
+ **
+ ** N may be zero if there are inequality constraints.
+ ** If there are no inequality constraints, then N is at
+ ** least one.
+ **
+ ** This case is also used when there are no WHERE clause
+ ** constraints but an index is selected anyway, in order
+ ** to force the output order to conform to an ORDER BY.
+ */
+ int aStartOp[] = {
+ 0,
+ 0,
+ OP_Rewind, /* 2: (!start_constraints && startEq && !bRev) */
+ OP_Last, /* 3: (!start_constraints && startEq && bRev) */
+ OP_MoveGt, /* 4: (start_constraints && !startEq && !bRev) */
+ OP_MoveLt, /* 5: (start_constraints && !startEq && bRev) */
+ OP_MoveGe, /* 6: (start_constraints && startEq && !bRev) */
+ OP_MoveLe /* 7: (start_constraints && startEq && bRev) */
+ };
+ int aEndOp[] = {
+ OP_Noop, /* 0: (!end_constraints) */
+ OP_IdxGE, /* 1: (end_constraints && !bRev) */
+ OP_IdxLT /* 2: (end_constraints && bRev) */
+ };
+ int nEq = pLevel->nEq;
+ int isMinQuery = 0; /* If this is an optimized SELECT min(x).. */
+ int regBase; /* Base register holding constraint values */
+ int r1; /* Temp register */
+ WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */
+ WhereTerm *pRangeEnd = 0; /* Inequality constraint at range end */
+ int startEq; /* True if range start uses ==, >= or <= */
+ int endEq; /* True if range end uses ==, >= or <= */
+ int start_constraints; /* Start of range is constrained */
+ int k = pIdx->aiColumn[nEq]; /* Column for inequality constraints */
+ int nConstraint; /* Number of constraint terms */
+ int op;
+
+ /* Generate code to evaluate all constraint terms using == or IN
+ ** and store the values of those terms in an array of registers
+ ** starting at regBase.
+ */
+ regBase = codeAllEqualityTerms(pParse, pLevel, &wc, notReady, 2);
+ nxt = pLevel->nxt;
+
+ /* If this loop satisfies a sort order (pOrderBy) request that
+ ** was passed to this function to implement a "SELECT min(x) ..."
+ ** query, then the caller will only allow the loop to run for
+ ** a single iteration. This means that the first row returned
+ ** should not have a NULL value stored in 'x'. If column 'x' is
+ ** the first one after the nEq equality constraints in the index,
+ ** this requires some special handling.
+ */
+ if( (wflags&WHERE_ORDERBY_MIN)!=0
+ && (pLevel->flags&WHERE_ORDERBY)
+ && (pIdx->nColumn>nEq)
+ && (pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq])
+ ){
+ isMinQuery = 1;
+ }
+
+ /* Find any inequality constraint terms for the start and end
+ ** of the range.
+ */
+ if( pLevel->flags & WHERE_TOP_LIMIT ){
+ pRangeEnd = findTerm(&wc, iCur, k, notReady, (WO_LT|WO_LE), pIdx);
+ }
+ if( pLevel->flags & WHERE_BTM_LIMIT ){
+ pRangeStart = findTerm(&wc, iCur, k, notReady, (WO_GT|WO_GE), pIdx);
+ }
+
+ /* If we are doing a reverse order scan on an ascending index, or
+ ** a forward order scan on a descending index, interchange the
+ ** start and end terms (pRangeStart and pRangeEnd).
+ */
+ if( bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC) ){
+ SWAP(WhereTerm *, pRangeEnd, pRangeStart);
+ }
+
+ testcase( pRangeStart && pRangeStart->eOperator & WO_LE );
+ testcase( pRangeStart && pRangeStart->eOperator & WO_GE );
+ testcase( pRangeEnd && pRangeEnd->eOperator & WO_LE );
+ testcase( pRangeEnd && pRangeEnd->eOperator & WO_GE );
+ startEq = !pRangeStart || pRangeStart->eOperator & (WO_LE|WO_GE);
+ endEq = !pRangeEnd || pRangeEnd->eOperator & (WO_LE|WO_GE);
+ start_constraints = pRangeStart || nEq>0;
+
+ /* Seek the index cursor to the start of the range. */
+ nConstraint = nEq;
+ if( pRangeStart ){
+ int dcc = pParse->disableColCache;
+ if( pRangeEnd ){
+ pParse->disableColCache = 1;
+ }
+ sqlite3ExprCode(pParse, pRangeStart->pExpr->pRight, regBase+nEq);
+ pParse->disableColCache = dcc;
+ sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, nxt);
+ nConstraint++;
+ }else if( isMinQuery ){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
+ nConstraint++;
+ startEq = 0;
+ start_constraints = 1;
+ }
+ codeApplyAffinity(pParse, regBase, nConstraint, pIdx);
+ op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev];
+ assert( op!=0 );
+ testcase( op==OP_Rewind );
+ testcase( op==OP_Last );
+ testcase( op==OP_MoveGt );
+ testcase( op==OP_MoveGe );
+ testcase( op==OP_MoveLe );
+ testcase( op==OP_MoveLt );
+ sqlite3VdbeAddOp4(v, op, iIdxCur, nxt, regBase,
+ (char*)nConstraint, P4_INT32);
+
+ /* Load the value for the inequality constraint at the end of the
+ ** range (if any).
+ */
+ nConstraint = nEq;
+ if( pRangeEnd ){
+ sqlite3ExprCode(pParse, pRangeEnd->pExpr->pRight, regBase+nEq);
+ sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, nxt);
+ codeApplyAffinity(pParse, regBase, nEq+1, pIdx);
+ nConstraint++;
+ }
+
+ /* Top of the loop body */
+ pLevel->p2 = sqlite3VdbeCurrentAddr(v);
+
+ /* Check if the index cursor is past the end of the range. */
+ op = aEndOp[(pRangeEnd || nEq) * (1 + bRev)];
+ testcase( op==OP_Noop );
+ testcase( op==OP_IdxGE );
+ testcase( op==OP_IdxLT );
+ sqlite3VdbeAddOp4(v, op, iIdxCur, nxt, regBase,
+ (char*)nConstraint, P4_INT32);
+ sqlite3VdbeChangeP5(v, endEq!=bRev);
+
+ /* If there are inequality constraints, check that the value
+ ** of the table column that the inequality contrains is not NULL.
+ ** If it is, jump to the next iteration of the loop.
+ */
+ r1 = sqlite3GetTempReg(pParse);
+ testcase( pLevel->flags & WHERE_BTM_LIMIT );
+ testcase( pLevel->flags & WHERE_TOP_LIMIT );
+ if( pLevel->flags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT) ){
+ sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, nEq, r1);
+ sqlite3VdbeAddOp2(v, OP_IsNull, r1, cont);
+ }
+
+ /* Seek the table cursor, if required */
+ if( !omitTable ){
+ sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, r1);
+ sqlite3VdbeAddOp3(v, OP_MoveGe, iCur, 0, r1); /* Deferred seek */
+ }
+ sqlite3ReleaseTempReg(pParse, r1);
+
+ /* Record the instruction used to terminate the loop. Disable
+ ** WHERE clause terms made redundant by the index range scan.
+ */
+ pLevel->op = bRev ? OP_Prev : OP_Next;
+ pLevel->p1 = iIdxCur;
+ disableTerm(pLevel, pRangeStart);
+ disableTerm(pLevel, pRangeEnd);
+ }else{
+ /* Case 4: There is no usable index. We must do a complete
+ ** scan of the entire table.
+ */
+ assert( omitTable==0 );
+ assert( bRev==0 );
+ pLevel->op = OP_Next;
+ pLevel->p1 = iCur;
+ pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, OP_Rewind, iCur, brk);
+ }
+ notReady &= ~getMask(&maskSet, iCur);
+
+ /* Insert code to test every subexpression that can be completely
+ ** computed using the current set of tables.
+ */
+ for(pTerm=wc.a, j=wc.nTerm; j>0; j--, pTerm++){
+ Expr *pE;
+ testcase( pTerm->flags & TERM_VIRTUAL );
+ testcase( pTerm->flags & TERM_CODED );
+ if( pTerm->flags & (TERM_VIRTUAL|TERM_CODED) ) continue;
+ if( (pTerm->prereqAll & notReady)!=0 ) continue;
+ pE = pTerm->pExpr;
+ assert( pE!=0 );
+ if( pLevel->iLeftJoin && !ExprHasProperty(pE, EP_FromJoin) ){
+ continue;
+ }
+ sqlite3ExprIfFalse(pParse, pE, cont, SQLITE_JUMPIFNULL);
+ pTerm->flags |= TERM_CODED;
+ }
+
+ /* For a LEFT OUTER JOIN, generate code that will record the fact that
+ ** at least one row of the right table has matched the left table.
+ */
+ if( pLevel->iLeftJoin ){
+ pLevel->top = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin);
+ VdbeComment((v, "record LEFT JOIN hit"));
+ sqlite3ExprClearColumnCache(pParse, pLevel->iTabCur);
+ sqlite3ExprClearColumnCache(pParse, pLevel->iIdxCur);
+ for(pTerm=wc.a, j=0; j<wc.nTerm; j++, pTerm++){
+ testcase( pTerm->flags & TERM_VIRTUAL );
+ testcase( pTerm->flags & TERM_CODED );
+ if( pTerm->flags & (TERM_VIRTUAL|TERM_CODED) ) continue;
+ if( (pTerm->prereqAll & notReady)!=0 ) continue;
+ assert( pTerm->pExpr );
+ sqlite3ExprIfFalse(pParse, pTerm->pExpr, cont, SQLITE_JUMPIFNULL);
+ pTerm->flags |= TERM_CODED;
+ }
+ }
+ }
+
+#ifdef SQLITE_TEST /* For testing and debugging use only */
+ /* Record in the query plan information about the current table
+ ** and the index used to access it (if any). If the table itself
+ ** is not used, its name is just '{}'. If no index is used
+ ** the index is listed as "{}". If the primary key is used the
+ ** index name is '*'.
+ */
+ for(i=0; i<pTabList->nSrc; i++){
+ char *z;
+ int n;
+ pLevel = &pWInfo->a[i];
+ pTabItem = &pTabList->a[pLevel->iFrom];
+ z = pTabItem->zAlias;
+ if( z==0 ) z = pTabItem->pTab->zName;
+ n = strlen(z);
+ if( n+nQPlan < sizeof(sqlite3_query_plan)-10 ){
+ if( pLevel->flags & WHERE_IDX_ONLY ){
+ memcpy(&sqlite3_query_plan[nQPlan], "{}", 2);
+ nQPlan += 2;
+ }else{
+ memcpy(&sqlite3_query_plan[nQPlan], z, n);
+ nQPlan += n;
+ }
+ sqlite3_query_plan[nQPlan++] = ' ';
+ }
+ testcase( pLevel->flags & WHERE_ROWID_EQ );
+ testcase( pLevel->flags & WHERE_ROWID_RANGE );
+ if( pLevel->flags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){
+ memcpy(&sqlite3_query_plan[nQPlan], "* ", 2);
+ nQPlan += 2;
+ }else if( pLevel->pIdx==0 ){
+ memcpy(&sqlite3_query_plan[nQPlan], "{} ", 3);
+ nQPlan += 3;
+ }else{
+ n = strlen(pLevel->pIdx->zName);
+ if( n+nQPlan < sizeof(sqlite3_query_plan)-2 ){
+ memcpy(&sqlite3_query_plan[nQPlan], pLevel->pIdx->zName, n);
+ nQPlan += n;
+ sqlite3_query_plan[nQPlan++] = ' ';
+ }
+ }
+ }
+ while( nQPlan>0 && sqlite3_query_plan[nQPlan-1]==' ' ){
+ sqlite3_query_plan[--nQPlan] = 0;
+ }
+ sqlite3_query_plan[nQPlan] = 0;
+ nQPlan = 0;
+#endif /* SQLITE_TEST // Testing and debugging use only */
+
+ /* Record the continuation address in the WhereInfo structure. Then
+ ** clean up and return.
+ */
+ pWInfo->iContinue = cont;
+ whereClauseClear(&wc);
+ return pWInfo;
+
+ /* Jump here if malloc fails */
+whereBeginNoMem:
+ whereClauseClear(&wc);
+ whereInfoFree(pWInfo);
+ return 0;
+}
+
+/*
+** Generate the end of the WHERE loop. See comments on
+** sqlite3WhereBegin() for additional information.
+*/
+SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
+ Vdbe *v = pWInfo->pParse->pVdbe;
+ int i;
+ WhereLevel *pLevel;
+ SrcList *pTabList = pWInfo->pTabList;
+
+ /* Generate loop termination code.
+ */
+ sqlite3ExprClearColumnCache(pWInfo->pParse, -1);
+ for(i=pTabList->nSrc-1; i>=0; i--){
+ pLevel = &pWInfo->a[i];
+ sqlite3VdbeResolveLabel(v, pLevel->cont);
+ if( pLevel->op!=OP_Noop ){
+ sqlite3VdbeAddOp2(v, pLevel->op, pLevel->p1, pLevel->p2);
+ }
+ if( pLevel->nIn ){
+ struct InLoop *pIn;
+ int j;
+ sqlite3VdbeResolveLabel(v, pLevel->nxt);
+ for(j=pLevel->nIn, pIn=&pLevel->aInLoop[j-1]; j>0; j--, pIn--){
+ sqlite3VdbeJumpHere(v, pIn->topAddr+1);
+ sqlite3VdbeAddOp2(v, OP_Next, pIn->iCur, pIn->topAddr);
+ sqlite3VdbeJumpHere(v, pIn->topAddr-1);
+ }
+ sqlite3_free(pLevel->aInLoop);
+ }
+ sqlite3VdbeResolveLabel(v, pLevel->brk);
+ if( pLevel->iLeftJoin ){
+ int addr;
+ addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin);
+ sqlite3VdbeAddOp1(v, OP_NullRow, pTabList->a[i].iCursor);
+ if( pLevel->iIdxCur>=0 ){
+ sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur);
+ }
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->top);
+ sqlite3VdbeJumpHere(v, addr);
+ }
+ }
+
+ /* The "break" point is here, just past the end of the outer loop.
+ ** Set it.
+ */
+ sqlite3VdbeResolveLabel(v, pWInfo->iBreak);
+
+ /* Close all of the cursors that were opened by sqlite3WhereBegin.
+ */
+ for(i=0, pLevel=pWInfo->a; i<pTabList->nSrc; i++, pLevel++){
+ struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom];
+ Table *pTab = pTabItem->pTab;
+ assert( pTab!=0 );
+ if( pTab->isEphem || pTab->pSelect ) continue;
+ if( !pWInfo->okOnePass && (pLevel->flags & WHERE_IDX_ONLY)==0 ){
+ sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor);
+ }
+ if( pLevel->pIdx!=0 ){
+ sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur);
+ }
+
+ /* If this scan uses an index, make code substitutions to read data
+ ** from the index in preference to the table. Sometimes, this means
+ ** the table need never be read from. This is a performance boost,
+ ** as the vdbe level waits until the table is read before actually
+ ** seeking the table cursor to the record corresponding to the current
+ ** position in the index.
+ **
+ ** Calls to the code generator in between sqlite3WhereBegin and
+ ** sqlite3WhereEnd will have created code that references the table
+ ** directly. This loop scans all that code looking for opcodes
+ ** that reference the table and converts them into opcodes that
+ ** reference the index.
+ */
+ if( pLevel->pIdx ){
+ int k, j, last;
+ VdbeOp *pOp;
+ Index *pIdx = pLevel->pIdx;
+ int useIndexOnly = pLevel->flags & WHERE_IDX_ONLY;
+
+ assert( pIdx!=0 );
+ pOp = sqlite3VdbeGetOp(v, pWInfo->iTop);
+ last = sqlite3VdbeCurrentAddr(v);
+ for(k=pWInfo->iTop; k<last; k++, pOp++){
+ if( pOp->p1!=pLevel->iTabCur ) continue;
+ if( pOp->opcode==OP_Column ){
+ for(j=0; j<pIdx->nColumn; j++){
+ if( pOp->p2==pIdx->aiColumn[j] ){
+ pOp->p2 = j;
+ pOp->p1 = pLevel->iIdxCur;
+ break;
+ }
+ }
+ assert(!useIndexOnly || j<pIdx->nColumn);
+ }else if( pOp->opcode==OP_Rowid ){
+ pOp->p1 = pLevel->iIdxCur;
+ pOp->opcode = OP_IdxRowid;
+ }else if( pOp->opcode==OP_NullRow && useIndexOnly ){
+ pOp->opcode = OP_Noop;
+ }
+ }
+ }
+ }
+
+ /* Final cleanup
+ */
+ whereInfoFree(pWInfo);
+ return;
+}
+
+/************** End of where.c ***********************************************/
+/************** Begin file parse.c *******************************************/
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is include which follows the "include" declaration
+** in the input file. */
+
+
+/*
+** An instance of this structure holds information about the
+** LIMIT clause of a SELECT statement.
+*/
+struct LimitVal {
+ Expr *pLimit; /* The LIMIT expression. NULL if there is no limit */
+ Expr *pOffset; /* The OFFSET expression. NULL if there is none */
+};
+
+/*
+** An instance of this structure is used to store the LIKE,
+** GLOB, NOT LIKE, and NOT GLOB operators.
+*/
+struct LikeOp {
+ Token eOperator; /* "like" or "glob" or "regexp" */
+ int not; /* True if the NOT keyword is present */
+};
+
+/*
+** An instance of the following structure describes the event of a
+** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT,
+** TK_DELETE, or TK_INSTEAD. If the event is of the form
+**
+** UPDATE ON (a,b,c)
+**
+** Then the "b" IdList records the list "a,b,c".
+*/
+struct TrigEvent { int a; IdList * b; };
+
+/*
+** An instance of this structure holds the ATTACH key and the key type.
+*/
+struct AttachKey { int type; Token key; };
+
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/*
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands.
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+** YYCODETYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 terminals
+** and nonterminals. "int" is used otherwise.
+** YYNOCODE is a number of type YYCODETYPE which corresponds
+** to no legal terminal or nonterminal number. This
+** number is used to fill in empty slots of the hash
+** table.
+** YYFALLBACK If defined, this indicates that one or more tokens
+** have fall-back values which should be used if the
+** original value of the token will not parse.
+** YYACTIONTYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 rules and
+** states combined. "int" is used otherwise.
+** sqlite3ParserTOKENTYPE is the data type used for minor tokens given
+** directly to the parser from the tokenizer.
+** YYMINORTYPE is the data type used for all minor tokens.
+** This is typically a union of many types, one of
+** which is sqlite3ParserTOKENTYPE. The entry in the union
+** for base tokens is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack. If
+** zero the stack is dynamically sized using realloc()
+** sqlite3ParserARG_SDECL A static variable declaration for the %extra_argument
+** sqlite3ParserARG_PDECL A parameter declaration for the %extra_argument
+** sqlite3ParserARG_STORE Code to store %extra_argument into yypParser
+** sqlite3ParserARG_FETCH Code to extract %extra_argument from yypParser
+** YYNSTATE the combined number of states.
+** YYNRULE the number of rules in the grammar
+** YYERRORSYMBOL is the code number of the error symbol. If not
+** defined, then do no error processing.
+*/
+#define YYCODETYPE unsigned char
+#define YYNOCODE 248
+#define YYACTIONTYPE unsigned short int
+#define YYWILDCARD 59
+#define sqlite3ParserTOKENTYPE Token
+typedef union {
+ sqlite3ParserTOKENTYPE yy0;
+ int yy46;
+ struct LikeOp yy72;
+ Expr* yy172;
+ ExprList* yy174;
+ Select* yy219;
+ struct LimitVal yy234;
+ TriggerStep* yy243;
+ struct TrigEvent yy370;
+ SrcList* yy373;
+ struct {int value; int mask;} yy405;
+ Token yy410;
+ IdList* yy432;
+} YYMINORTYPE;
+#ifndef YYSTACKDEPTH
+#define YYSTACKDEPTH 100
+#endif
+#define sqlite3ParserARG_SDECL Parse *pParse;
+#define sqlite3ParserARG_PDECL ,Parse *pParse
+#define sqlite3ParserARG_FETCH Parse *pParse = yypParser->pParse
+#define sqlite3ParserARG_STORE yypParser->pParse = pParse
+#define YYNSTATE 589
+#define YYNRULE 313
+#define YYFALLBACK 1
+#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
+
+/* The yyzerominor constant is used to initialize instances of
+** YYMINORTYPE objects to zero. */
+static const YYMINORTYPE yyzerominor;
+
+/* Next are that tables used to determine what action to take based on the
+** current state and lookahead token. These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.
+**
+** Suppose the action integer is N. Then the action is determined as
+** follows
+**
+** 0 <= N < YYNSTATE Shift N. That is, push the lookahead
+** token onto the stack and goto state N.
+**
+** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE.
+**
+** N == YYNSTATE+YYNRULE A syntax error has occurred.
+**
+** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
+**
+** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
+** slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+** yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol. If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+** yy_action[] A single table containing all actions.
+** yy_lookahead[] A table containing the lookahead for each entry in
+** yy_action. Used to detect hash collisions.
+** yy_shift_ofst[] For each state, the offset into yy_action for
+** shifting terminals.
+** yy_reduce_ofst[] For each state, the offset into yy_action for
+** shifting non-terminals after a reduce.
+** yy_default[] Default action for each state.
+*/
+static const YYACTIONTYPE yy_action[] = {
+ /* 0 */ 292, 903, 124, 588, 409, 172, 2, 418, 61, 61,
+ /* 10 */ 61, 61, 519, 63, 63, 63, 63, 64, 64, 65,
+ /* 20 */ 65, 65, 66, 210, 447, 212, 425, 431, 68, 63,
+ /* 30 */ 63, 63, 63, 64, 64, 65, 65, 65, 66, 210,
+ /* 40 */ 391, 388, 396, 451, 60, 59, 297, 435, 436, 432,
+ /* 50 */ 432, 62, 62, 61, 61, 61, 61, 263, 63, 63,
+ /* 60 */ 63, 63, 64, 64, 65, 65, 65, 66, 210, 292,
+ /* 70 */ 493, 494, 418, 489, 208, 82, 67, 420, 69, 154,
+ /* 80 */ 63, 63, 63, 63, 64, 64, 65, 65, 65, 66,
+ /* 90 */ 210, 67, 462, 69, 154, 425, 431, 574, 264, 58,
+ /* 100 */ 64, 64, 65, 65, 65, 66, 210, 397, 398, 422,
+ /* 110 */ 422, 422, 292, 60, 59, 297, 435, 436, 432, 432,
+ /* 120 */ 62, 62, 61, 61, 61, 61, 317, 63, 63, 63,
+ /* 130 */ 63, 64, 64, 65, 65, 65, 66, 210, 425, 431,
+ /* 140 */ 94, 65, 65, 65, 66, 210, 396, 210, 414, 34,
+ /* 150 */ 56, 298, 442, 443, 410, 418, 60, 59, 297, 435,
+ /* 160 */ 436, 432, 432, 62, 62, 61, 61, 61, 61, 208,
+ /* 170 */ 63, 63, 63, 63, 64, 64, 65, 65, 65, 66,
+ /* 180 */ 210, 292, 372, 524, 295, 572, 113, 408, 522, 451,
+ /* 190 */ 331, 317, 407, 20, 244, 340, 519, 396, 478, 531,
+ /* 200 */ 505, 447, 212, 571, 570, 245, 530, 425, 431, 149,
+ /* 210 */ 150, 397, 398, 414, 41, 211, 151, 533, 488, 489,
+ /* 220 */ 418, 568, 569, 420, 292, 60, 59, 297, 435, 436,
+ /* 230 */ 432, 432, 62, 62, 61, 61, 61, 61, 317, 63,
+ /* 240 */ 63, 63, 63, 64, 64, 65, 65, 65, 66, 210,
+ /* 250 */ 425, 431, 447, 333, 215, 422, 422, 422, 363, 299,
+ /* 260 */ 414, 41, 397, 398, 366, 567, 211, 292, 60, 59,
+ /* 270 */ 297, 435, 436, 432, 432, 62, 62, 61, 61, 61,
+ /* 280 */ 61, 396, 63, 63, 63, 63, 64, 64, 65, 65,
+ /* 290 */ 65, 66, 210, 425, 431, 491, 300, 524, 474, 66,
+ /* 300 */ 210, 214, 474, 229, 411, 286, 534, 20, 449, 523,
+ /* 310 */ 168, 60, 59, 297, 435, 436, 432, 432, 62, 62,
+ /* 320 */ 61, 61, 61, 61, 474, 63, 63, 63, 63, 64,
+ /* 330 */ 64, 65, 65, 65, 66, 210, 209, 480, 317, 77,
+ /* 340 */ 292, 239, 300, 55, 484, 490, 397, 398, 181, 547,
+ /* 350 */ 494, 345, 348, 349, 67, 152, 69, 154, 339, 524,
+ /* 360 */ 414, 35, 350, 241, 221, 370, 425, 431, 579, 20,
+ /* 370 */ 164, 118, 243, 343, 248, 344, 176, 322, 442, 443,
+ /* 380 */ 414, 3, 80, 252, 60, 59, 297, 435, 436, 432,
+ /* 390 */ 432, 62, 62, 61, 61, 61, 61, 174, 63, 63,
+ /* 400 */ 63, 63, 64, 64, 65, 65, 65, 66, 210, 292,
+ /* 410 */ 221, 550, 236, 487, 510, 353, 317, 118, 243, 343,
+ /* 420 */ 248, 344, 176, 181, 317, 532, 345, 348, 349, 252,
+ /* 430 */ 223, 415, 155, 464, 511, 425, 431, 350, 414, 34,
+ /* 440 */ 465, 211, 177, 175, 160, 525, 414, 34, 338, 549,
+ /* 450 */ 449, 323, 168, 60, 59, 297, 435, 436, 432, 432,
+ /* 460 */ 62, 62, 61, 61, 61, 61, 415, 63, 63, 63,
+ /* 470 */ 63, 64, 64, 65, 65, 65, 66, 210, 292, 542,
+ /* 480 */ 335, 517, 504, 541, 456, 572, 302, 19, 331, 144,
+ /* 490 */ 317, 390, 317, 330, 2, 362, 457, 294, 483, 373,
+ /* 500 */ 269, 268, 252, 571, 425, 431, 589, 391, 388, 458,
+ /* 510 */ 208, 495, 414, 49, 414, 49, 303, 586, 894, 230,
+ /* 520 */ 894, 496, 60, 59, 297, 435, 436, 432, 432, 62,
+ /* 530 */ 62, 61, 61, 61, 61, 201, 63, 63, 63, 63,
+ /* 540 */ 64, 64, 65, 65, 65, 66, 210, 292, 317, 181,
+ /* 550 */ 439, 255, 345, 348, 349, 370, 153, 583, 308, 251,
+ /* 560 */ 309, 452, 76, 350, 78, 382, 211, 426, 427, 415,
+ /* 570 */ 414, 27, 319, 425, 431, 440, 1, 22, 586, 893,
+ /* 580 */ 396, 893, 544, 478, 320, 263, 438, 438, 429, 430,
+ /* 590 */ 415, 60, 59, 297, 435, 436, 432, 432, 62, 62,
+ /* 600 */ 61, 61, 61, 61, 237, 63, 63, 63, 63, 64,
+ /* 610 */ 64, 65, 65, 65, 66, 210, 292, 428, 583, 374,
+ /* 620 */ 224, 93, 517, 9, 159, 396, 557, 396, 456, 67,
+ /* 630 */ 396, 69, 154, 399, 400, 401, 320, 328, 438, 438,
+ /* 640 */ 457, 336, 425, 431, 361, 397, 398, 320, 433, 438,
+ /* 650 */ 438, 582, 291, 458, 238, 327, 318, 222, 546, 292,
+ /* 660 */ 60, 59, 297, 435, 436, 432, 432, 62, 62, 61,
+ /* 670 */ 61, 61, 61, 225, 63, 63, 63, 63, 64, 64,
+ /* 680 */ 65, 65, 65, 66, 210, 425, 431, 482, 313, 392,
+ /* 690 */ 397, 398, 397, 398, 207, 397, 398, 825, 273, 517,
+ /* 700 */ 251, 200, 292, 60, 59, 297, 435, 436, 432, 432,
+ /* 710 */ 62, 62, 61, 61, 61, 61, 470, 63, 63, 63,
+ /* 720 */ 63, 64, 64, 65, 65, 65, 66, 210, 425, 431,
+ /* 730 */ 171, 160, 263, 263, 304, 415, 276, 395, 274, 263,
+ /* 740 */ 517, 517, 263, 517, 192, 292, 60, 70, 297, 435,
+ /* 750 */ 436, 432, 432, 62, 62, 61, 61, 61, 61, 379,
+ /* 760 */ 63, 63, 63, 63, 64, 64, 65, 65, 65, 66,
+ /* 770 */ 210, 425, 431, 384, 559, 305, 306, 251, 415, 320,
+ /* 780 */ 560, 438, 438, 561, 540, 360, 540, 387, 292, 196,
+ /* 790 */ 59, 297, 435, 436, 432, 432, 62, 62, 61, 61,
+ /* 800 */ 61, 61, 371, 63, 63, 63, 63, 64, 64, 65,
+ /* 810 */ 65, 65, 66, 210, 425, 431, 396, 275, 251, 251,
+ /* 820 */ 172, 250, 418, 415, 386, 367, 178, 179, 180, 469,
+ /* 830 */ 311, 123, 156, 5, 297, 435, 436, 432, 432, 62,
+ /* 840 */ 62, 61, 61, 61, 61, 317, 63, 63, 63, 63,
+ /* 850 */ 64, 64, 65, 65, 65, 66, 210, 72, 324, 194,
+ /* 860 */ 4, 317, 263, 317, 296, 263, 415, 414, 28, 317,
+ /* 870 */ 257, 317, 321, 72, 324, 317, 4, 119, 165, 177,
+ /* 880 */ 296, 397, 398, 414, 23, 414, 32, 418, 321, 326,
+ /* 890 */ 421, 414, 53, 414, 52, 317, 158, 414, 98, 451,
+ /* 900 */ 317, 263, 317, 277, 317, 326, 378, 471, 261, 317,
+ /* 910 */ 259, 18, 478, 445, 445, 451, 317, 414, 96, 75,
+ /* 920 */ 74, 469, 414, 101, 414, 102, 414, 112, 73, 315,
+ /* 930 */ 316, 414, 114, 420, 294, 75, 74, 481, 414, 16,
+ /* 940 */ 381, 317, 279, 467, 73, 315, 316, 72, 324, 420,
+ /* 950 */ 4, 208, 317, 183, 296, 317, 186, 128, 84, 208,
+ /* 960 */ 8, 341, 321, 414, 99, 422, 422, 422, 423, 424,
+ /* 970 */ 11, 623, 380, 307, 414, 33, 413, 414, 97, 326,
+ /* 980 */ 412, 422, 422, 422, 423, 424, 11, 415, 413, 451,
+ /* 990 */ 415, 162, 412, 317, 499, 500, 226, 227, 228, 104,
+ /* 1000 */ 448, 476, 317, 173, 507, 317, 509, 508, 317, 75,
+ /* 1010 */ 74, 329, 205, 21, 281, 414, 24, 418, 73, 315,
+ /* 1020 */ 316, 282, 317, 420, 414, 54, 460, 414, 115, 317,
+ /* 1030 */ 414, 116, 502, 203, 147, 549, 514, 468, 128, 202,
+ /* 1040 */ 317, 473, 204, 317, 414, 117, 317, 477, 317, 584,
+ /* 1050 */ 317, 414, 25, 317, 249, 422, 422, 422, 423, 424,
+ /* 1060 */ 11, 506, 414, 36, 512, 414, 37, 317, 414, 26,
+ /* 1070 */ 414, 38, 414, 39, 526, 414, 40, 317, 254, 317,
+ /* 1080 */ 128, 317, 418, 317, 256, 377, 278, 268, 585, 414,
+ /* 1090 */ 42, 293, 317, 352, 317, 128, 208, 513, 258, 414,
+ /* 1100 */ 43, 414, 44, 414, 29, 414, 30, 545, 260, 128,
+ /* 1110 */ 317, 553, 317, 173, 414, 45, 414, 46, 317, 262,
+ /* 1120 */ 383, 554, 317, 91, 564, 317, 91, 317, 581, 189,
+ /* 1130 */ 290, 357, 414, 47, 414, 48, 267, 365, 368, 369,
+ /* 1140 */ 414, 31, 270, 271, 414, 10, 272, 414, 50, 414,
+ /* 1150 */ 51, 556, 566, 280, 283, 284, 578, 146, 419, 405,
+ /* 1160 */ 231, 505, 444, 325, 516, 463, 163, 446, 552, 394,
+ /* 1170 */ 466, 563, 246, 515, 518, 520, 402, 403, 404, 7,
+ /* 1180 */ 314, 84, 232, 334, 347, 83, 332, 57, 170, 79,
+ /* 1190 */ 213, 461, 125, 85, 337, 342, 492, 502, 497, 301,
+ /* 1200 */ 498, 416, 105, 219, 247, 218, 503, 501, 233, 220,
+ /* 1210 */ 287, 234, 527, 528, 235, 529, 417, 521, 354, 288,
+ /* 1220 */ 184, 121, 185, 240, 535, 475, 242, 356, 187, 479,
+ /* 1230 */ 188, 358, 537, 88, 190, 548, 364, 193, 132, 376,
+ /* 1240 */ 555, 375, 133, 134, 135, 310, 562, 138, 136, 575,
+ /* 1250 */ 576, 577, 580, 100, 393, 406, 217, 142, 624, 625,
+ /* 1260 */ 103, 141, 265, 166, 167, 434, 71, 453, 441, 437,
+ /* 1270 */ 450, 143, 538, 157, 120, 454, 161, 472, 455, 169,
+ /* 1280 */ 459, 81, 6, 12, 13, 92, 95, 126, 216, 127,
+ /* 1290 */ 111, 485, 486, 17, 86, 346, 106, 122, 253, 107,
+ /* 1300 */ 87, 108, 182, 245, 355, 145, 351, 536, 129, 359,
+ /* 1310 */ 312, 130, 543, 173, 539, 266, 191, 109, 289, 551,
+ /* 1320 */ 195, 14, 131, 198, 197, 558, 137, 199, 139, 140,
+ /* 1330 */ 15, 565, 89, 90, 573, 110, 385, 206, 148, 389,
+ /* 1340 */ 285, 587,
+};
+static const YYCODETYPE yy_lookahead[] = {
+ /* 0 */ 16, 139, 140, 141, 168, 21, 144, 23, 69, 70,
+ /* 10 */ 71, 72, 176, 74, 75, 76, 77, 78, 79, 80,
+ /* 20 */ 81, 82, 83, 84, 78, 79, 42, 43, 73, 74,
+ /* 30 */ 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
+ /* 40 */ 1, 2, 23, 58, 60, 61, 62, 63, 64, 65,
+ /* 50 */ 66, 67, 68, 69, 70, 71, 72, 147, 74, 75,
+ /* 60 */ 76, 77, 78, 79, 80, 81, 82, 83, 84, 16,
+ /* 70 */ 185, 186, 88, 88, 110, 22, 217, 92, 219, 220,
+ /* 80 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ /* 90 */ 84, 217, 218, 219, 220, 42, 43, 238, 188, 46,
+ /* 100 */ 78, 79, 80, 81, 82, 83, 84, 88, 89, 124,
+ /* 110 */ 125, 126, 16, 60, 61, 62, 63, 64, 65, 66,
+ /* 120 */ 67, 68, 69, 70, 71, 72, 147, 74, 75, 76,
+ /* 130 */ 77, 78, 79, 80, 81, 82, 83, 84, 42, 43,
+ /* 140 */ 44, 80, 81, 82, 83, 84, 23, 84, 169, 170,
+ /* 150 */ 19, 164, 165, 166, 23, 23, 60, 61, 62, 63,
+ /* 160 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 110,
+ /* 170 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ /* 180 */ 84, 16, 123, 147, 150, 147, 21, 167, 168, 58,
+ /* 190 */ 211, 147, 156, 157, 92, 216, 176, 23, 147, 176,
+ /* 200 */ 177, 78, 79, 165, 166, 103, 183, 42, 43, 78,
+ /* 210 */ 79, 88, 89, 169, 170, 228, 180, 181, 169, 88,
+ /* 220 */ 88, 98, 99, 92, 16, 60, 61, 62, 63, 64,
+ /* 230 */ 65, 66, 67, 68, 69, 70, 71, 72, 147, 74,
+ /* 240 */ 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
+ /* 250 */ 42, 43, 78, 209, 210, 124, 125, 126, 224, 208,
+ /* 260 */ 169, 170, 88, 89, 230, 227, 228, 16, 60, 61,
+ /* 270 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
+ /* 280 */ 72, 23, 74, 75, 76, 77, 78, 79, 80, 81,
+ /* 290 */ 82, 83, 84, 42, 43, 160, 16, 147, 161, 83,
+ /* 300 */ 84, 210, 161, 153, 169, 158, 156, 157, 161, 162,
+ /* 310 */ 163, 60, 61, 62, 63, 64, 65, 66, 67, 68,
+ /* 320 */ 69, 70, 71, 72, 161, 74, 75, 76, 77, 78,
+ /* 330 */ 79, 80, 81, 82, 83, 84, 192, 200, 147, 131,
+ /* 340 */ 16, 200, 16, 199, 20, 169, 88, 89, 90, 185,
+ /* 350 */ 186, 93, 94, 95, 217, 22, 219, 220, 147, 147,
+ /* 360 */ 169, 170, 104, 200, 84, 147, 42, 43, 156, 157,
+ /* 370 */ 90, 91, 92, 93, 94, 95, 96, 164, 165, 166,
+ /* 380 */ 169, 170, 131, 103, 60, 61, 62, 63, 64, 65,
+ /* 390 */ 66, 67, 68, 69, 70, 71, 72, 155, 74, 75,
+ /* 400 */ 76, 77, 78, 79, 80, 81, 82, 83, 84, 16,
+ /* 410 */ 84, 11, 221, 20, 30, 16, 147, 91, 92, 93,
+ /* 420 */ 94, 95, 96, 90, 147, 181, 93, 94, 95, 103,
+ /* 430 */ 212, 189, 155, 27, 50, 42, 43, 104, 169, 170,
+ /* 440 */ 34, 228, 43, 201, 202, 181, 169, 170, 206, 49,
+ /* 450 */ 161, 162, 163, 60, 61, 62, 63, 64, 65, 66,
+ /* 460 */ 67, 68, 69, 70, 71, 72, 189, 74, 75, 76,
+ /* 470 */ 77, 78, 79, 80, 81, 82, 83, 84, 16, 25,
+ /* 480 */ 211, 147, 20, 29, 12, 147, 102, 19, 211, 21,
+ /* 490 */ 147, 141, 147, 216, 144, 41, 24, 98, 20, 99,
+ /* 500 */ 100, 101, 103, 165, 42, 43, 0, 1, 2, 37,
+ /* 510 */ 110, 39, 169, 170, 169, 170, 182, 19, 20, 190,
+ /* 520 */ 22, 49, 60, 61, 62, 63, 64, 65, 66, 67,
+ /* 530 */ 68, 69, 70, 71, 72, 155, 74, 75, 76, 77,
+ /* 540 */ 78, 79, 80, 81, 82, 83, 84, 16, 147, 90,
+ /* 550 */ 20, 20, 93, 94, 95, 147, 155, 59, 215, 225,
+ /* 560 */ 215, 20, 130, 104, 132, 227, 228, 42, 43, 189,
+ /* 570 */ 169, 170, 16, 42, 43, 20, 19, 22, 19, 20,
+ /* 580 */ 23, 22, 18, 147, 106, 147, 108, 109, 63, 64,
+ /* 590 */ 189, 60, 61, 62, 63, 64, 65, 66, 67, 68,
+ /* 600 */ 69, 70, 71, 72, 147, 74, 75, 76, 77, 78,
+ /* 610 */ 79, 80, 81, 82, 83, 84, 16, 92, 59, 55,
+ /* 620 */ 212, 21, 147, 19, 147, 23, 188, 23, 12, 217,
+ /* 630 */ 23, 219, 220, 7, 8, 9, 106, 186, 108, 109,
+ /* 640 */ 24, 147, 42, 43, 208, 88, 89, 106, 92, 108,
+ /* 650 */ 109, 244, 245, 37, 147, 39, 147, 182, 94, 16,
+ /* 660 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+ /* 670 */ 70, 71, 72, 145, 74, 75, 76, 77, 78, 79,
+ /* 680 */ 80, 81, 82, 83, 84, 42, 43, 80, 142, 143,
+ /* 690 */ 88, 89, 88, 89, 148, 88, 89, 133, 14, 147,
+ /* 700 */ 225, 155, 16, 60, 61, 62, 63, 64, 65, 66,
+ /* 710 */ 67, 68, 69, 70, 71, 72, 114, 74, 75, 76,
+ /* 720 */ 77, 78, 79, 80, 81, 82, 83, 84, 42, 43,
+ /* 730 */ 201, 202, 147, 147, 182, 189, 52, 147, 54, 147,
+ /* 740 */ 147, 147, 147, 147, 155, 16, 60, 61, 62, 63,
+ /* 750 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 213,
+ /* 760 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ /* 770 */ 84, 42, 43, 188, 188, 182, 182, 225, 189, 106,
+ /* 780 */ 188, 108, 109, 188, 99, 100, 101, 241, 16, 155,
+ /* 790 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
+ /* 800 */ 71, 72, 213, 74, 75, 76, 77, 78, 79, 80,
+ /* 810 */ 81, 82, 83, 84, 42, 43, 23, 133, 225, 225,
+ /* 820 */ 21, 225, 23, 189, 239, 236, 99, 100, 101, 22,
+ /* 830 */ 242, 243, 155, 191, 62, 63, 64, 65, 66, 67,
+ /* 840 */ 68, 69, 70, 71, 72, 147, 74, 75, 76, 77,
+ /* 850 */ 78, 79, 80, 81, 82, 83, 84, 16, 17, 22,
+ /* 860 */ 19, 147, 147, 147, 23, 147, 189, 169, 170, 147,
+ /* 870 */ 14, 147, 31, 16, 17, 147, 19, 147, 19, 43,
+ /* 880 */ 23, 88, 89, 169, 170, 169, 170, 88, 31, 48,
+ /* 890 */ 147, 169, 170, 169, 170, 147, 89, 169, 170, 58,
+ /* 900 */ 147, 147, 147, 188, 147, 48, 188, 114, 52, 147,
+ /* 910 */ 54, 19, 147, 124, 125, 58, 147, 169, 170, 78,
+ /* 920 */ 79, 114, 169, 170, 169, 170, 169, 170, 87, 88,
+ /* 930 */ 89, 169, 170, 92, 98, 78, 79, 80, 169, 170,
+ /* 940 */ 91, 147, 188, 22, 87, 88, 89, 16, 17, 92,
+ /* 950 */ 19, 110, 147, 155, 23, 147, 155, 22, 121, 110,
+ /* 960 */ 68, 80, 31, 169, 170, 124, 125, 126, 127, 128,
+ /* 970 */ 129, 112, 123, 208, 169, 170, 107, 169, 170, 48,
+ /* 980 */ 111, 124, 125, 126, 127, 128, 129, 189, 107, 58,
+ /* 990 */ 189, 5, 111, 147, 7, 8, 10, 11, 12, 13,
+ /* 1000 */ 161, 20, 147, 22, 178, 147, 91, 92, 147, 78,
+ /* 1010 */ 79, 147, 26, 19, 28, 169, 170, 23, 87, 88,
+ /* 1020 */ 89, 35, 147, 92, 169, 170, 147, 169, 170, 147,
+ /* 1030 */ 169, 170, 97, 47, 113, 49, 20, 203, 22, 53,
+ /* 1040 */ 147, 147, 56, 147, 169, 170, 147, 147, 147, 20,
+ /* 1050 */ 147, 169, 170, 147, 147, 124, 125, 126, 127, 128,
+ /* 1060 */ 129, 147, 169, 170, 178, 169, 170, 147, 169, 170,
+ /* 1070 */ 169, 170, 169, 170, 147, 169, 170, 147, 20, 147,
+ /* 1080 */ 22, 147, 88, 147, 147, 99, 100, 101, 59, 169,
+ /* 1090 */ 170, 105, 147, 20, 147, 22, 110, 178, 147, 169,
+ /* 1100 */ 170, 169, 170, 169, 170, 169, 170, 20, 147, 22,
+ /* 1110 */ 147, 20, 147, 22, 169, 170, 169, 170, 147, 147,
+ /* 1120 */ 134, 20, 147, 22, 20, 147, 22, 147, 20, 232,
+ /* 1130 */ 22, 233, 169, 170, 169, 170, 147, 147, 147, 147,
+ /* 1140 */ 169, 170, 147, 147, 169, 170, 147, 169, 170, 169,
+ /* 1150 */ 170, 147, 147, 147, 147, 147, 147, 191, 161, 149,
+ /* 1160 */ 193, 177, 229, 223, 161, 172, 6, 229, 194, 146,
+ /* 1170 */ 172, 194, 172, 172, 172, 161, 146, 146, 146, 22,
+ /* 1180 */ 154, 121, 194, 118, 173, 119, 116, 120, 112, 130,
+ /* 1190 */ 222, 152, 152, 98, 115, 98, 171, 97, 171, 40,
+ /* 1200 */ 179, 189, 19, 84, 171, 226, 171, 173, 195, 226,
+ /* 1210 */ 174, 196, 171, 171, 197, 171, 198, 179, 15, 174,
+ /* 1220 */ 151, 60, 151, 204, 152, 205, 204, 152, 151, 205,
+ /* 1230 */ 152, 38, 152, 130, 151, 184, 152, 184, 19, 15,
+ /* 1240 */ 194, 152, 187, 187, 187, 152, 194, 184, 187, 33,
+ /* 1250 */ 152, 152, 137, 159, 1, 20, 175, 214, 112, 112,
+ /* 1260 */ 175, 214, 234, 112, 112, 92, 19, 11, 20, 107,
+ /* 1270 */ 20, 19, 235, 19, 32, 20, 112, 114, 20, 22,
+ /* 1280 */ 20, 22, 117, 22, 117, 237, 237, 19, 44, 20,
+ /* 1290 */ 240, 20, 20, 231, 19, 44, 19, 243, 20, 19,
+ /* 1300 */ 19, 19, 96, 103, 16, 21, 44, 17, 98, 36,
+ /* 1310 */ 246, 45, 45, 22, 51, 133, 98, 19, 5, 1,
+ /* 1320 */ 122, 19, 102, 14, 113, 17, 113, 115, 102, 122,
+ /* 1330 */ 19, 123, 68, 68, 20, 14, 57, 135, 19, 3,
+ /* 1340 */ 136, 4,
+};
+#define YY_SHIFT_USE_DFLT (-62)
+#define YY_SHIFT_MAX 389
+static const short yy_shift_ofst[] = {
+ /* 0 */ 39, 841, 986, -16, 841, 931, 931, 258, 123, -36,
+ /* 10 */ 96, 931, 931, 931, 931, 931, -45, 400, 174, 19,
+ /* 20 */ 132, -54, -54, 53, 165, 208, 251, 324, 393, 462,
+ /* 30 */ 531, 600, 643, 686, 643, 643, 643, 643, 643, 643,
+ /* 40 */ 643, 643, 643, 643, 643, 643, 643, 643, 643, 643,
+ /* 50 */ 643, 643, 729, 772, 772, 857, 931, 931, 931, 931,
+ /* 60 */ 931, 931, 931, 931, 931, 931, 931, 931, 931, 931,
+ /* 70 */ 931, 931, 931, 931, 931, 931, 931, 931, 931, 931,
+ /* 80 */ 931, 931, 931, 931, 931, 931, 931, 931, 931, 931,
+ /* 90 */ 931, 931, 931, 931, 931, 931, -61, -61, 6, 6,
+ /* 100 */ 280, 22, 61, 399, 564, 19, 19, 19, 19, 19,
+ /* 110 */ 19, 19, 216, 132, 63, -62, -62, -62, 131, 326,
+ /* 120 */ 472, 472, 498, 559, 506, 799, 19, 799, 19, 19,
+ /* 130 */ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ /* 140 */ 19, 849, 59, -36, -36, -36, -62, -62, -62, -15,
+ /* 150 */ -15, 333, 459, 478, 557, 530, 541, 616, 602, 793,
+ /* 160 */ 604, 607, 626, 19, 19, 881, 19, 19, 994, 19,
+ /* 170 */ 19, 807, 19, 19, 673, 807, 19, 19, 384, 384,
+ /* 180 */ 384, 19, 19, 673, 19, 19, 673, 19, 454, 685,
+ /* 190 */ 19, 19, 673, 19, 19, 19, 673, 19, 19, 19,
+ /* 200 */ 673, 673, 19, 19, 19, 19, 19, 468, 869, 921,
+ /* 210 */ 132, 789, 789, 432, 406, 406, 406, 836, 406, 132,
+ /* 220 */ 406, 132, 935, 837, 837, 1160, 1160, 1160, 1160, 1157,
+ /* 230 */ -36, 1060, 1065, 1066, 1070, 1067, 1059, 1076, 1076, 1095,
+ /* 240 */ 1079, 1095, 1079, 1097, 1097, 1159, 1097, 1100, 1097, 1183,
+ /* 250 */ 1119, 1119, 1159, 1097, 1097, 1097, 1183, 1203, 1076, 1203,
+ /* 260 */ 1076, 1203, 1076, 1076, 1193, 1103, 1203, 1076, 1161, 1161,
+ /* 270 */ 1219, 1060, 1076, 1224, 1224, 1224, 1224, 1060, 1161, 1219,
+ /* 280 */ 1076, 1216, 1216, 1076, 1076, 1115, -62, -62, -62, -62,
+ /* 290 */ -62, -62, 525, 684, 727, 856, 859, 556, 555, 981,
+ /* 300 */ 102, 987, 915, 1016, 1058, 1073, 1087, 1091, 1101, 1104,
+ /* 310 */ 892, 1108, 1029, 1253, 1235, 1146, 1147, 1151, 1152, 1173,
+ /* 320 */ 1162, 1247, 1248, 1250, 1252, 1256, 1254, 1255, 1257, 1258,
+ /* 330 */ 1260, 1259, 1165, 1261, 1167, 1259, 1163, 1268, 1269, 1164,
+ /* 340 */ 1271, 1272, 1242, 1244, 1275, 1251, 1277, 1278, 1280, 1281,
+ /* 350 */ 1262, 1282, 1206, 1200, 1288, 1290, 1284, 1210, 1273, 1263,
+ /* 360 */ 1266, 1291, 1267, 1182, 1218, 1298, 1313, 1318, 1220, 1264,
+ /* 370 */ 1265, 1198, 1302, 1211, 1309, 1212, 1308, 1213, 1226, 1207,
+ /* 380 */ 1311, 1208, 1314, 1321, 1279, 1202, 1204, 1319, 1336, 1337,
+};
+#define YY_REDUCE_USE_DFLT (-165)
+#define YY_REDUCE_MAX 291
+static const short yy_reduce_ofst[] = {
+ /* 0 */ -138, 277, 546, 137, 401, -21, 44, 36, 38, 242,
+ /* 10 */ -141, 191, 91, 269, 343, 345, -126, 589, 338, 150,
+ /* 20 */ 147, -13, 213, 412, 412, 412, 412, 412, 412, 412,
+ /* 30 */ 412, 412, 412, 412, 412, 412, 412, 412, 412, 412,
+ /* 40 */ 412, 412, 412, 412, 412, 412, 412, 412, 412, 412,
+ /* 50 */ 412, 412, 412, 412, 412, 211, 698, 714, 716, 722,
+ /* 60 */ 724, 728, 748, 753, 755, 757, 762, 769, 794, 805,
+ /* 70 */ 808, 846, 855, 858, 861, 875, 882, 893, 896, 899,
+ /* 80 */ 901, 903, 906, 920, 930, 932, 934, 936, 945, 947,
+ /* 90 */ 963, 965, 971, 975, 978, 980, 412, 412, 412, 412,
+ /* 100 */ 20, 412, 412, 23, 34, 334, 475, 552, 593, 594,
+ /* 110 */ 585, 212, 412, 289, 412, 412, 412, 412, 135, -164,
+ /* 120 */ -115, 164, 407, 407, 350, 141, 51, 163, 596, -90,
+ /* 130 */ 436, 218, 765, 438, 586, 592, 595, 715, 718, 408,
+ /* 140 */ 754, 380, 634, 677, 798, 801, 144, 529, 588, 49,
+ /* 150 */ 176, 244, 264, 329, 457, 329, 329, 451, 477, 494,
+ /* 160 */ 507, 509, 528, 590, 730, 642, 509, 743, 839, 864,
+ /* 170 */ 879, 834, 894, 900, 329, 834, 907, 914, 826, 886,
+ /* 180 */ 919, 927, 937, 329, 951, 961, 329, 972, 897, 898,
+ /* 190 */ 989, 990, 329, 991, 992, 995, 329, 996, 999, 1004,
+ /* 200 */ 329, 329, 1005, 1006, 1007, 1008, 1009, 1010, 966, 967,
+ /* 210 */ 997, 933, 938, 940, 993, 998, 1000, 984, 1001, 1003,
+ /* 220 */ 1002, 1014, 1011, 974, 977, 1023, 1030, 1031, 1032, 1026,
+ /* 230 */ 1012, 988, 1013, 1015, 1017, 1018, 968, 1039, 1040, 1019,
+ /* 240 */ 1020, 1022, 1024, 1025, 1027, 1021, 1033, 1034, 1035, 1036,
+ /* 250 */ 979, 983, 1038, 1041, 1042, 1044, 1045, 1069, 1072, 1071,
+ /* 260 */ 1075, 1077, 1078, 1080, 1028, 1037, 1083, 1084, 1051, 1053,
+ /* 270 */ 1043, 1046, 1089, 1055, 1056, 1057, 1061, 1052, 1063, 1047,
+ /* 280 */ 1093, 1048, 1049, 1098, 1099, 1050, 1094, 1081, 1085, 1062,
+ /* 290 */ 1054, 1064,
+};
+static const YYACTIONTYPE yy_default[] = {
+ /* 0 */ 595, 820, 902, 710, 902, 820, 902, 902, 848, 714,
+ /* 10 */ 877, 818, 902, 902, 902, 902, 792, 902, 848, 902,
+ /* 20 */ 626, 848, 848, 743, 902, 902, 902, 902, 902, 902,
+ /* 30 */ 902, 902, 744, 902, 822, 817, 813, 815, 814, 821,
+ /* 40 */ 745, 734, 741, 748, 726, 861, 750, 751, 757, 758,
+ /* 50 */ 878, 876, 780, 779, 798, 902, 902, 902, 902, 902,
+ /* 60 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 70 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 80 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 90 */ 902, 902, 902, 902, 902, 902, 782, 804, 781, 791,
+ /* 100 */ 619, 783, 784, 679, 614, 902, 902, 902, 902, 902,
+ /* 110 */ 902, 902, 785, 902, 786, 799, 800, 801, 902, 902,
+ /* 120 */ 902, 902, 902, 902, 595, 710, 902, 710, 902, 902,
+ /* 130 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 140 */ 902, 902, 902, 902, 902, 902, 704, 714, 895, 902,
+ /* 150 */ 902, 670, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 160 */ 902, 902, 602, 600, 902, 702, 902, 902, 628, 902,
+ /* 170 */ 902, 712, 902, 902, 717, 718, 902, 902, 902, 902,
+ /* 180 */ 902, 902, 902, 616, 902, 902, 691, 902, 854, 902,
+ /* 190 */ 902, 902, 868, 902, 902, 902, 866, 902, 902, 902,
+ /* 200 */ 693, 753, 834, 902, 881, 883, 902, 902, 702, 711,
+ /* 210 */ 902, 902, 902, 816, 737, 737, 737, 649, 737, 902,
+ /* 220 */ 737, 902, 652, 747, 747, 599, 599, 599, 599, 669,
+ /* 230 */ 902, 747, 738, 740, 730, 742, 902, 719, 719, 727,
+ /* 240 */ 729, 727, 729, 681, 681, 666, 681, 652, 681, 826,
+ /* 250 */ 831, 831, 666, 681, 681, 681, 826, 611, 719, 611,
+ /* 260 */ 719, 611, 719, 719, 858, 860, 611, 719, 683, 683,
+ /* 270 */ 759, 747, 719, 690, 690, 690, 690, 747, 683, 759,
+ /* 280 */ 719, 880, 880, 719, 719, 888, 636, 654, 654, 863,
+ /* 290 */ 895, 900, 902, 902, 902, 902, 766, 902, 902, 902,
+ /* 300 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 310 */ 841, 902, 902, 902, 902, 771, 767, 902, 768, 902,
+ /* 320 */ 696, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 330 */ 902, 819, 902, 731, 902, 739, 902, 902, 902, 902,
+ /* 340 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 350 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 360 */ 856, 857, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 370 */ 902, 902, 902, 902, 902, 902, 902, 902, 902, 902,
+ /* 380 */ 902, 902, 902, 902, 887, 902, 902, 890, 596, 902,
+ /* 390 */ 590, 593, 592, 594, 598, 601, 623, 624, 625, 603,
+ /* 400 */ 604, 605, 606, 607, 608, 609, 615, 617, 635, 637,
+ /* 410 */ 621, 639, 700, 701, 763, 694, 695, 699, 622, 774,
+ /* 420 */ 765, 769, 770, 772, 773, 787, 788, 790, 796, 803,
+ /* 430 */ 806, 789, 794, 795, 797, 802, 805, 697, 698, 809,
+ /* 440 */ 629, 630, 633, 634, 844, 846, 845, 847, 632, 631,
+ /* 450 */ 775, 778, 811, 812, 869, 870, 871, 872, 873, 807,
+ /* 460 */ 720, 810, 793, 732, 735, 736, 733, 703, 713, 722,
+ /* 470 */ 723, 724, 725, 708, 709, 715, 728, 761, 762, 716,
+ /* 480 */ 705, 706, 707, 808, 764, 776, 777, 640, 641, 771,
+ /* 490 */ 642, 643, 644, 682, 685, 686, 687, 645, 664, 667,
+ /* 500 */ 668, 646, 653, 647, 648, 655, 656, 657, 660, 661,
+ /* 510 */ 662, 663, 658, 659, 827, 828, 832, 830, 829, 650,
+ /* 520 */ 651, 665, 638, 627, 620, 671, 674, 675, 676, 677,
+ /* 530 */ 678, 680, 672, 673, 618, 610, 612, 721, 850, 859,
+ /* 540 */ 855, 851, 852, 853, 613, 823, 824, 684, 755, 756,
+ /* 550 */ 849, 862, 864, 760, 865, 867, 892, 688, 689, 692,
+ /* 560 */ 833, 874, 746, 749, 752, 754, 835, 836, 837, 838,
+ /* 570 */ 839, 842, 843, 840, 875, 879, 882, 884, 885, 886,
+ /* 580 */ 889, 891, 896, 897, 898, 901, 899, 597, 591,
+};
+#define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens. If a construct
+** like the following:
+**
+** %fallback ID X Y Z.
+**
+** appears in the grammer, then ID becomes a fallback token for X, Y,
+** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+ 0, /* $ => nothing */
+ 0, /* SEMI => nothing */
+ 23, /* EXPLAIN => ID */
+ 23, /* QUERY => ID */
+ 23, /* PLAN => ID */
+ 23, /* BEGIN => ID */
+ 0, /* TRANSACTION => nothing */
+ 23, /* DEFERRED => ID */
+ 23, /* IMMEDIATE => ID */
+ 23, /* EXCLUSIVE => ID */
+ 0, /* COMMIT => nothing */
+ 23, /* END => ID */
+ 0, /* ROLLBACK => nothing */
+ 0, /* CREATE => nothing */
+ 0, /* TABLE => nothing */
+ 23, /* IF => ID */
+ 0, /* NOT => nothing */
+ 0, /* EXISTS => nothing */
+ 23, /* TEMP => ID */
+ 0, /* LP => nothing */
+ 0, /* RP => nothing */
+ 0, /* AS => nothing */
+ 0, /* COMMA => nothing */
+ 0, /* ID => nothing */
+ 23, /* ABORT => ID */
+ 23, /* AFTER => ID */
+ 23, /* ANALYZE => ID */
+ 23, /* ASC => ID */
+ 23, /* ATTACH => ID */
+ 23, /* BEFORE => ID */
+ 23, /* CASCADE => ID */
+ 23, /* CAST => ID */
+ 23, /* CONFLICT => ID */
+ 23, /* DATABASE => ID */
+ 23, /* DESC => ID */
+ 23, /* DETACH => ID */
+ 23, /* EACH => ID */
+ 23, /* FAIL => ID */
+ 23, /* FOR => ID */
+ 23, /* IGNORE => ID */
+ 23, /* INITIALLY => ID */
+ 23, /* INSTEAD => ID */
+ 23, /* LIKE_KW => ID */
+ 23, /* MATCH => ID */
+ 23, /* KEY => ID */
+ 23, /* OF => ID */
+ 23, /* OFFSET => ID */
+ 23, /* PRAGMA => ID */
+ 23, /* RAISE => ID */
+ 23, /* REPLACE => ID */
+ 23, /* RESTRICT => ID */
+ 23, /* ROW => ID */
+ 23, /* TRIGGER => ID */
+ 23, /* VACUUM => ID */
+ 23, /* VIEW => ID */
+ 23, /* VIRTUAL => ID */
+ 23, /* REINDEX => ID */
+ 23, /* RENAME => ID */
+ 23, /* CTIME_KW => ID */
+ 0, /* ANY => nothing */
+ 0, /* OR => nothing */
+ 0, /* AND => nothing */
+ 0, /* IS => nothing */
+ 0, /* BETWEEN => nothing */
+ 0, /* IN => nothing */
+ 0, /* ISNULL => nothing */
+ 0, /* NOTNULL => nothing */
+ 0, /* NE => nothing */
+ 0, /* EQ => nothing */
+ 0, /* GT => nothing */
+ 0, /* LE => nothing */
+ 0, /* LT => nothing */
+ 0, /* GE => nothing */
+ 0, /* ESCAPE => nothing */
+ 0, /* BITAND => nothing */
+ 0, /* BITOR => nothing */
+ 0, /* LSHIFT => nothing */
+ 0, /* RSHIFT => nothing */
+ 0, /* PLUS => nothing */
+ 0, /* MINUS => nothing */
+ 0, /* STAR => nothing */
+ 0, /* SLASH => nothing */
+ 0, /* REM => nothing */
+ 0, /* CONCAT => nothing */
+ 0, /* COLLATE => nothing */
+ 0, /* UMINUS => nothing */
+ 0, /* UPLUS => nothing */
+ 0, /* BITNOT => nothing */
+ 0, /* STRING => nothing */
+ 0, /* JOIN_KW => nothing */
+ 0, /* CONSTRAINT => nothing */
+ 0, /* DEFAULT => nothing */
+ 0, /* NULL => nothing */
+ 0, /* PRIMARY => nothing */
+ 0, /* UNIQUE => nothing */
+ 0, /* CHECK => nothing */
+ 0, /* REFERENCES => nothing */
+ 0, /* AUTOINCR => nothing */
+ 0, /* ON => nothing */
+ 0, /* DELETE => nothing */
+ 0, /* UPDATE => nothing */
+ 0, /* INSERT => nothing */
+ 0, /* SET => nothing */
+ 0, /* DEFERRABLE => nothing */
+ 0, /* FOREIGN => nothing */
+ 0, /* DROP => nothing */
+ 0, /* UNION => nothing */
+ 0, /* ALL => nothing */
+ 0, /* EXCEPT => nothing */
+ 0, /* INTERSECT => nothing */
+ 0, /* SELECT => nothing */
+ 0, /* DISTINCT => nothing */
+ 0, /* DOT => nothing */
+ 0, /* FROM => nothing */
+ 0, /* JOIN => nothing */
+ 0, /* USING => nothing */
+ 0, /* ORDER => nothing */
+ 0, /* BY => nothing */
+ 0, /* GROUP => nothing */
+ 0, /* HAVING => nothing */
+ 0, /* LIMIT => nothing */
+ 0, /* WHERE => nothing */
+ 0, /* INTO => nothing */
+ 0, /* VALUES => nothing */
+ 0, /* INTEGER => nothing */
+ 0, /* FLOAT => nothing */
+ 0, /* BLOB => nothing */
+ 0, /* REGISTER => nothing */
+ 0, /* VARIABLE => nothing */
+ 0, /* CASE => nothing */
+ 0, /* WHEN => nothing */
+ 0, /* THEN => nothing */
+ 0, /* ELSE => nothing */
+ 0, /* INDEX => nothing */
+ 0, /* ALTER => nothing */
+ 0, /* TO => nothing */
+ 0, /* ADD => nothing */
+ 0, /* COLUMNKW => nothing */
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack. Information stored includes:
+**
+** + The state number for the parser at this level of the stack.
+**
+** + The value of the token stored at this level of the stack.
+** (In other words, the "major" token.)
+**
+** + The semantic value stored at this level of the stack. This is
+** the information used by the action routines in the grammar.
+** It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+ int stateno; /* The state-number */
+ int major; /* The major token value. This is the code
+ ** number for the token at this stack level */
+ YYMINORTYPE minor; /* The user-supplied minor token value. This
+ ** is the value of the token */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+ int yyidx; /* Index of top element in stack */
+ int yyerrcnt; /* Shifts left before out of the error */
+ sqlite3ParserARG_SDECL /* A place to hold %extra_argument */
+#if YYSTACKDEPTH<=0
+ int yystksz; /* Current side of the stack */
+ yyStackEntry *yystack; /* The parser's stack */
+#else
+ yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+#endif
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/*
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message. Tracing is turned off
+** by making either argument NULL
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+SQLITE_PRIVATE void sqlite3ParserTrace(FILE *TraceFILE, char *zTracePrompt){
+ yyTraceFILE = TraceFILE;
+ yyTracePrompt = zTracePrompt;
+ if( yyTraceFILE==0 ) yyTracePrompt = 0;
+ else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required. The following table supplies these names */
+static const char *const yyTokenName[] = {
+ "$", "SEMI", "EXPLAIN", "QUERY",
+ "PLAN", "BEGIN", "TRANSACTION", "DEFERRED",
+ "IMMEDIATE", "EXCLUSIVE", "COMMIT", "END",
+ "ROLLBACK", "CREATE", "TABLE", "IF",
+ "NOT", "EXISTS", "TEMP", "LP",
+ "RP", "AS", "COMMA", "ID",
+ "ABORT", "AFTER", "ANALYZE", "ASC",
+ "ATTACH", "BEFORE", "CASCADE", "CAST",
+ "CONFLICT", "DATABASE", "DESC", "DETACH",
+ "EACH", "FAIL", "FOR", "IGNORE",
+ "INITIALLY", "INSTEAD", "LIKE_KW", "MATCH",
+ "KEY", "OF", "OFFSET", "PRAGMA",
+ "RAISE", "REPLACE", "RESTRICT", "ROW",
+ "TRIGGER", "VACUUM", "VIEW", "VIRTUAL",
+ "REINDEX", "RENAME", "CTIME_KW", "ANY",
+ "OR", "AND", "IS", "BETWEEN",
+ "IN", "ISNULL", "NOTNULL", "NE",
+ "EQ", "GT", "LE", "LT",
+ "GE", "ESCAPE", "BITAND", "BITOR",
+ "LSHIFT", "RSHIFT", "PLUS", "MINUS",
+ "STAR", "SLASH", "REM", "CONCAT",
+ "COLLATE", "UMINUS", "UPLUS", "BITNOT",
+ "STRING", "JOIN_KW", "CONSTRAINT", "DEFAULT",
+ "NULL", "PRIMARY", "UNIQUE", "CHECK",
+ "REFERENCES", "AUTOINCR", "ON", "DELETE",
+ "UPDATE", "INSERT", "SET", "DEFERRABLE",
+ "FOREIGN", "DROP", "UNION", "ALL",
+ "EXCEPT", "INTERSECT", "SELECT", "DISTINCT",
+ "DOT", "FROM", "JOIN", "USING",
+ "ORDER", "BY", "GROUP", "HAVING",
+ "LIMIT", "WHERE", "INTO", "VALUES",
+ "INTEGER", "FLOAT", "BLOB", "REGISTER",
+ "VARIABLE", "CASE", "WHEN", "THEN",
+ "ELSE", "INDEX", "ALTER", "TO",
+ "ADD", "COLUMNKW", "error", "input",
+ "cmdlist", "ecmd", "cmdx", "cmd",
+ "explain", "transtype", "trans_opt", "nm",
+ "create_table", "create_table_args", "temp", "ifnotexists",
+ "dbnm", "columnlist", "conslist_opt", "select",
+ "column", "columnid", "type", "carglist",
+ "id", "ids", "typetoken", "typename",
+ "signed", "plus_num", "minus_num", "carg",
+ "ccons", "term", "expr", "onconf",
+ "sortorder", "autoinc", "idxlist_opt", "refargs",
+ "defer_subclause", "refarg", "refact", "init_deferred_pred_opt",
+ "conslist", "tcons", "idxlist", "defer_subclause_opt",
+ "orconf", "resolvetype", "raisetype", "ifexists",
+ "fullname", "oneselect", "multiselect_op", "distinct",
+ "selcollist", "from", "where_opt", "groupby_opt",
+ "having_opt", "orderby_opt", "limit_opt", "sclp",
+ "as", "seltablist", "stl_prefix", "joinop",
+ "on_opt", "using_opt", "seltablist_paren", "joinop2",
+ "inscollist", "sortlist", "sortitem", "nexprlist",
+ "setlist", "insert_cmd", "inscollist_opt", "itemlist",
+ "exprlist", "likeop", "escape", "between_op",
+ "in_op", "case_operand", "case_exprlist", "case_else",
+ "uniqueflag", "idxitem", "collate", "nmnum",
+ "plus_opt", "number", "trigger_decl", "trigger_cmd_list",
+ "trigger_time", "trigger_event", "foreach_clause", "when_clause",
+ "trigger_cmd", "database_kw_opt", "key_opt", "add_column_fullname",
+ "kwcolumn_opt", "create_vtab", "vtabarglist", "vtabarg",
+ "vtabargtoken", "lp", "anylist",
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *const yyRuleName[] = {
+ /* 0 */ "input ::= cmdlist",
+ /* 1 */ "cmdlist ::= cmdlist ecmd",
+ /* 2 */ "cmdlist ::= ecmd",
+ /* 3 */ "cmdx ::= cmd",
+ /* 4 */ "ecmd ::= SEMI",
+ /* 5 */ "ecmd ::= explain cmdx SEMI",
+ /* 6 */ "explain ::=",
+ /* 7 */ "explain ::= EXPLAIN",
+ /* 8 */ "explain ::= EXPLAIN QUERY PLAN",
+ /* 9 */ "cmd ::= BEGIN transtype trans_opt",
+ /* 10 */ "trans_opt ::=",
+ /* 11 */ "trans_opt ::= TRANSACTION",
+ /* 12 */ "trans_opt ::= TRANSACTION nm",
+ /* 13 */ "transtype ::=",
+ /* 14 */ "transtype ::= DEFERRED",
+ /* 15 */ "transtype ::= IMMEDIATE",
+ /* 16 */ "transtype ::= EXCLUSIVE",
+ /* 17 */ "cmd ::= COMMIT trans_opt",
+ /* 18 */ "cmd ::= END trans_opt",
+ /* 19 */ "cmd ::= ROLLBACK trans_opt",
+ /* 20 */ "cmd ::= create_table create_table_args",
+ /* 21 */ "create_table ::= CREATE temp TABLE ifnotexists nm dbnm",
+ /* 22 */ "ifnotexists ::=",
+ /* 23 */ "ifnotexists ::= IF NOT EXISTS",
+ /* 24 */ "temp ::= TEMP",
+ /* 25 */ "temp ::=",
+ /* 26 */ "create_table_args ::= LP columnlist conslist_opt RP",
+ /* 27 */ "create_table_args ::= AS select",
+ /* 28 */ "columnlist ::= columnlist COMMA column",
+ /* 29 */ "columnlist ::= column",
+ /* 30 */ "column ::= columnid type carglist",
+ /* 31 */ "columnid ::= nm",
+ /* 32 */ "id ::= ID",
+ /* 33 */ "ids ::= ID|STRING",
+ /* 34 */ "nm ::= ID",
+ /* 35 */ "nm ::= STRING",
+ /* 36 */ "nm ::= JOIN_KW",
+ /* 37 */ "type ::=",
+ /* 38 */ "type ::= typetoken",
+ /* 39 */ "typetoken ::= typename",
+ /* 40 */ "typetoken ::= typename LP signed RP",
+ /* 41 */ "typetoken ::= typename LP signed COMMA signed RP",
+ /* 42 */ "typename ::= ids",
+ /* 43 */ "typename ::= typename ids",
+ /* 44 */ "signed ::= plus_num",
+ /* 45 */ "signed ::= minus_num",
+ /* 46 */ "carglist ::= carglist carg",
+ /* 47 */ "carglist ::=",
+ /* 48 */ "carg ::= CONSTRAINT nm ccons",
+ /* 49 */ "carg ::= ccons",
+ /* 50 */ "ccons ::= DEFAULT term",
+ /* 51 */ "ccons ::= DEFAULT LP expr RP",
+ /* 52 */ "ccons ::= DEFAULT PLUS term",
+ /* 53 */ "ccons ::= DEFAULT MINUS term",
+ /* 54 */ "ccons ::= DEFAULT id",
+ /* 55 */ "ccons ::= NULL onconf",
+ /* 56 */ "ccons ::= NOT NULL onconf",
+ /* 57 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc",
+ /* 58 */ "ccons ::= UNIQUE onconf",
+ /* 59 */ "ccons ::= CHECK LP expr RP",
+ /* 60 */ "ccons ::= REFERENCES nm idxlist_opt refargs",
+ /* 61 */ "ccons ::= defer_subclause",
+ /* 62 */ "ccons ::= COLLATE ids",
+ /* 63 */ "autoinc ::=",
+ /* 64 */ "autoinc ::= AUTOINCR",
+ /* 65 */ "refargs ::=",
+ /* 66 */ "refargs ::= refargs refarg",
+ /* 67 */ "refarg ::= MATCH nm",
+ /* 68 */ "refarg ::= ON DELETE refact",
+ /* 69 */ "refarg ::= ON UPDATE refact",
+ /* 70 */ "refarg ::= ON INSERT refact",
+ /* 71 */ "refact ::= SET NULL",
+ /* 72 */ "refact ::= SET DEFAULT",
+ /* 73 */ "refact ::= CASCADE",
+ /* 74 */ "refact ::= RESTRICT",
+ /* 75 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt",
+ /* 76 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt",
+ /* 77 */ "init_deferred_pred_opt ::=",
+ /* 78 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED",
+ /* 79 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE",
+ /* 80 */ "conslist_opt ::=",
+ /* 81 */ "conslist_opt ::= COMMA conslist",
+ /* 82 */ "conslist ::= conslist COMMA tcons",
+ /* 83 */ "conslist ::= conslist tcons",
+ /* 84 */ "conslist ::= tcons",
+ /* 85 */ "tcons ::= CONSTRAINT nm",
+ /* 86 */ "tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf",
+ /* 87 */ "tcons ::= UNIQUE LP idxlist RP onconf",
+ /* 88 */ "tcons ::= CHECK LP expr RP onconf",
+ /* 89 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt",
+ /* 90 */ "defer_subclause_opt ::=",
+ /* 91 */ "defer_subclause_opt ::= defer_subclause",
+ /* 92 */ "onconf ::=",
+ /* 93 */ "onconf ::= ON CONFLICT resolvetype",
+ /* 94 */ "orconf ::=",
+ /* 95 */ "orconf ::= OR resolvetype",
+ /* 96 */ "resolvetype ::= raisetype",
+ /* 97 */ "resolvetype ::= IGNORE",
+ /* 98 */ "resolvetype ::= REPLACE",
+ /* 99 */ "cmd ::= DROP TABLE ifexists fullname",
+ /* 100 */ "ifexists ::= IF EXISTS",
+ /* 101 */ "ifexists ::=",
+ /* 102 */ "cmd ::= CREATE temp VIEW ifnotexists nm dbnm AS select",
+ /* 103 */ "cmd ::= DROP VIEW ifexists fullname",
+ /* 104 */ "cmd ::= select",
+ /* 105 */ "select ::= oneselect",
+ /* 106 */ "select ::= select multiselect_op oneselect",
+ /* 107 */ "multiselect_op ::= UNION",
+ /* 108 */ "multiselect_op ::= UNION ALL",
+ /* 109 */ "multiselect_op ::= EXCEPT|INTERSECT",
+ /* 110 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt",
+ /* 111 */ "distinct ::= DISTINCT",
+ /* 112 */ "distinct ::= ALL",
+ /* 113 */ "distinct ::=",
+ /* 114 */ "sclp ::= selcollist COMMA",
+ /* 115 */ "sclp ::=",
+ /* 116 */ "selcollist ::= sclp expr as",
+ /* 117 */ "selcollist ::= sclp STAR",
+ /* 118 */ "selcollist ::= sclp nm DOT STAR",
+ /* 119 */ "as ::= AS nm",
+ /* 120 */ "as ::= ids",
+ /* 121 */ "as ::=",
+ /* 122 */ "from ::=",
+ /* 123 */ "from ::= FROM seltablist",
+ /* 124 */ "stl_prefix ::= seltablist joinop",
+ /* 125 */ "stl_prefix ::=",
+ /* 126 */ "seltablist ::= stl_prefix nm dbnm as on_opt using_opt",
+ /* 127 */ "seltablist ::= stl_prefix LP seltablist_paren RP as on_opt using_opt",
+ /* 128 */ "seltablist_paren ::= select",
+ /* 129 */ "seltablist_paren ::= seltablist",
+ /* 130 */ "dbnm ::=",
+ /* 131 */ "dbnm ::= DOT nm",
+ /* 132 */ "fullname ::= nm dbnm",
+ /* 133 */ "joinop ::= COMMA|JOIN",
+ /* 134 */ "joinop ::= JOIN_KW JOIN",
+ /* 135 */ "joinop ::= JOIN_KW nm JOIN",
+ /* 136 */ "joinop ::= JOIN_KW nm nm JOIN",
+ /* 137 */ "on_opt ::= ON expr",
+ /* 138 */ "on_opt ::=",
+ /* 139 */ "using_opt ::= USING LP inscollist RP",
+ /* 140 */ "using_opt ::=",
+ /* 141 */ "orderby_opt ::=",
+ /* 142 */ "orderby_opt ::= ORDER BY sortlist",
+ /* 143 */ "sortlist ::= sortlist COMMA sortitem sortorder",
+ /* 144 */ "sortlist ::= sortitem sortorder",
+ /* 145 */ "sortitem ::= expr",
+ /* 146 */ "sortorder ::= ASC",
+ /* 147 */ "sortorder ::= DESC",
+ /* 148 */ "sortorder ::=",
+ /* 149 */ "groupby_opt ::=",
+ /* 150 */ "groupby_opt ::= GROUP BY nexprlist",
+ /* 151 */ "having_opt ::=",
+ /* 152 */ "having_opt ::= HAVING expr",
+ /* 153 */ "limit_opt ::=",
+ /* 154 */ "limit_opt ::= LIMIT expr",
+ /* 155 */ "limit_opt ::= LIMIT expr OFFSET expr",
+ /* 156 */ "limit_opt ::= LIMIT expr COMMA expr",
+ /* 157 */ "cmd ::= DELETE FROM fullname where_opt",
+ /* 158 */ "where_opt ::=",
+ /* 159 */ "where_opt ::= WHERE expr",
+ /* 160 */ "cmd ::= UPDATE orconf fullname SET setlist where_opt",
+ /* 161 */ "setlist ::= setlist COMMA nm EQ expr",
+ /* 162 */ "setlist ::= nm EQ expr",
+ /* 163 */ "cmd ::= insert_cmd INTO fullname inscollist_opt VALUES LP itemlist RP",
+ /* 164 */ "cmd ::= insert_cmd INTO fullname inscollist_opt select",
+ /* 165 */ "cmd ::= insert_cmd INTO fullname inscollist_opt DEFAULT VALUES",
+ /* 166 */ "insert_cmd ::= INSERT orconf",
+ /* 167 */ "insert_cmd ::= REPLACE",
+ /* 168 */ "itemlist ::= itemlist COMMA expr",
+ /* 169 */ "itemlist ::= expr",
+ /* 170 */ "inscollist_opt ::=",
+ /* 171 */ "inscollist_opt ::= LP inscollist RP",
+ /* 172 */ "inscollist ::= inscollist COMMA nm",
+ /* 173 */ "inscollist ::= nm",
+ /* 174 */ "expr ::= term",
+ /* 175 */ "expr ::= LP expr RP",
+ /* 176 */ "term ::= NULL",
+ /* 177 */ "expr ::= ID",
+ /* 178 */ "expr ::= JOIN_KW",
+ /* 179 */ "expr ::= nm DOT nm",
+ /* 180 */ "expr ::= nm DOT nm DOT nm",
+ /* 181 */ "term ::= INTEGER|FLOAT|BLOB",
+ /* 182 */ "term ::= STRING",
+ /* 183 */ "expr ::= REGISTER",
+ /* 184 */ "expr ::= VARIABLE",
+ /* 185 */ "expr ::= expr COLLATE ids",
+ /* 186 */ "expr ::= CAST LP expr AS typetoken RP",
+ /* 187 */ "expr ::= ID LP distinct exprlist RP",
+ /* 188 */ "expr ::= ID LP STAR RP",
+ /* 189 */ "term ::= CTIME_KW",
+ /* 190 */ "expr ::= expr AND expr",
+ /* 191 */ "expr ::= expr OR expr",
+ /* 192 */ "expr ::= expr LT|GT|GE|LE expr",
+ /* 193 */ "expr ::= expr EQ|NE expr",
+ /* 194 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr",
+ /* 195 */ "expr ::= expr PLUS|MINUS expr",
+ /* 196 */ "expr ::= expr STAR|SLASH|REM expr",
+ /* 197 */ "expr ::= expr CONCAT expr",
+ /* 198 */ "likeop ::= LIKE_KW",
+ /* 199 */ "likeop ::= NOT LIKE_KW",
+ /* 200 */ "likeop ::= MATCH",
+ /* 201 */ "likeop ::= NOT MATCH",
+ /* 202 */ "escape ::= ESCAPE expr",
+ /* 203 */ "escape ::=",
+ /* 204 */ "expr ::= expr likeop expr escape",
+ /* 205 */ "expr ::= expr ISNULL|NOTNULL",
+ /* 206 */ "expr ::= expr IS NULL",
+ /* 207 */ "expr ::= expr NOT NULL",
+ /* 208 */ "expr ::= expr IS NOT NULL",
+ /* 209 */ "expr ::= NOT expr",
+ /* 210 */ "expr ::= BITNOT expr",
+ /* 211 */ "expr ::= MINUS expr",
+ /* 212 */ "expr ::= PLUS expr",
+ /* 213 */ "between_op ::= BETWEEN",
+ /* 214 */ "between_op ::= NOT BETWEEN",
+ /* 215 */ "expr ::= expr between_op expr AND expr",
+ /* 216 */ "in_op ::= IN",
+ /* 217 */ "in_op ::= NOT IN",
+ /* 218 */ "expr ::= expr in_op LP exprlist RP",
+ /* 219 */ "expr ::= LP select RP",
+ /* 220 */ "expr ::= expr in_op LP select RP",
+ /* 221 */ "expr ::= expr in_op nm dbnm",
+ /* 222 */ "expr ::= EXISTS LP select RP",
+ /* 223 */ "expr ::= CASE case_operand case_exprlist case_else END",
+ /* 224 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
+ /* 225 */ "case_exprlist ::= WHEN expr THEN expr",
+ /* 226 */ "case_else ::= ELSE expr",
+ /* 227 */ "case_else ::=",
+ /* 228 */ "case_operand ::= expr",
+ /* 229 */ "case_operand ::=",
+ /* 230 */ "exprlist ::= nexprlist",
+ /* 231 */ "exprlist ::=",
+ /* 232 */ "nexprlist ::= nexprlist COMMA expr",
+ /* 233 */ "nexprlist ::= expr",
+ /* 234 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP",
+ /* 235 */ "uniqueflag ::= UNIQUE",
+ /* 236 */ "uniqueflag ::=",
+ /* 237 */ "idxlist_opt ::=",
+ /* 238 */ "idxlist_opt ::= LP idxlist RP",
+ /* 239 */ "idxlist ::= idxlist COMMA idxitem collate sortorder",
+ /* 240 */ "idxlist ::= idxitem collate sortorder",
+ /* 241 */ "idxitem ::= nm",
+ /* 242 */ "collate ::=",
+ /* 243 */ "collate ::= COLLATE ids",
+ /* 244 */ "cmd ::= DROP INDEX ifexists fullname",
+ /* 245 */ "cmd ::= VACUUM",
+ /* 246 */ "cmd ::= VACUUM nm",
+ /* 247 */ "cmd ::= PRAGMA nm dbnm EQ nmnum",
+ /* 248 */ "cmd ::= PRAGMA nm dbnm EQ ON",
+ /* 249 */ "cmd ::= PRAGMA nm dbnm EQ DELETE",
+ /* 250 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
+ /* 251 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP",
+ /* 252 */ "cmd ::= PRAGMA nm dbnm",
+ /* 253 */ "nmnum ::= plus_num",
+ /* 254 */ "nmnum ::= nm",
+ /* 255 */ "plus_num ::= plus_opt number",
+ /* 256 */ "minus_num ::= MINUS number",
+ /* 257 */ "number ::= INTEGER|FLOAT",
+ /* 258 */ "plus_opt ::= PLUS",
+ /* 259 */ "plus_opt ::=",
+ /* 260 */ "cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list END",
+ /* 261 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
+ /* 262 */ "trigger_time ::= BEFORE",
+ /* 263 */ "trigger_time ::= AFTER",
+ /* 264 */ "trigger_time ::= INSTEAD OF",
+ /* 265 */ "trigger_time ::=",
+ /* 266 */ "trigger_event ::= DELETE|INSERT",
+ /* 267 */ "trigger_event ::= UPDATE",
+ /* 268 */ "trigger_event ::= UPDATE OF inscollist",
+ /* 269 */ "foreach_clause ::=",
+ /* 270 */ "foreach_clause ::= FOR EACH ROW",
+ /* 271 */ "when_clause ::=",
+ /* 272 */ "when_clause ::= WHEN expr",
+ /* 273 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI",
+ /* 274 */ "trigger_cmd_list ::=",
+ /* 275 */ "trigger_cmd ::= UPDATE orconf nm SET setlist where_opt",
+ /* 276 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt VALUES LP itemlist RP",
+ /* 277 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt select",
+ /* 278 */ "trigger_cmd ::= DELETE FROM nm where_opt",
+ /* 279 */ "trigger_cmd ::= select",
+ /* 280 */ "expr ::= RAISE LP IGNORE RP",
+ /* 281 */ "expr ::= RAISE LP raisetype COMMA nm RP",
+ /* 282 */ "raisetype ::= ROLLBACK",
+ /* 283 */ "raisetype ::= ABORT",
+ /* 284 */ "raisetype ::= FAIL",
+ /* 285 */ "cmd ::= DROP TRIGGER ifexists fullname",
+ /* 286 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt",
+ /* 287 */ "cmd ::= DETACH database_kw_opt expr",
+ /* 288 */ "key_opt ::=",
+ /* 289 */ "key_opt ::= KEY expr",
+ /* 290 */ "database_kw_opt ::= DATABASE",
+ /* 291 */ "database_kw_opt ::=",
+ /* 292 */ "cmd ::= REINDEX",
+ /* 293 */ "cmd ::= REINDEX nm dbnm",
+ /* 294 */ "cmd ::= ANALYZE",
+ /* 295 */ "cmd ::= ANALYZE nm dbnm",
+ /* 296 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
+ /* 297 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column",
+ /* 298 */ "add_column_fullname ::= fullname",
+ /* 299 */ "kwcolumn_opt ::=",
+ /* 300 */ "kwcolumn_opt ::= COLUMNKW",
+ /* 301 */ "cmd ::= create_vtab",
+ /* 302 */ "cmd ::= create_vtab LP vtabarglist RP",
+ /* 303 */ "create_vtab ::= CREATE VIRTUAL TABLE nm dbnm USING nm",
+ /* 304 */ "vtabarglist ::= vtabarg",
+ /* 305 */ "vtabarglist ::= vtabarglist COMMA vtabarg",
+ /* 306 */ "vtabarg ::=",
+ /* 307 */ "vtabarg ::= vtabarg vtabargtoken",
+ /* 308 */ "vtabargtoken ::= ANY",
+ /* 309 */ "vtabargtoken ::= lp anylist RP",
+ /* 310 */ "lp ::= LP",
+ /* 311 */ "anylist ::=",
+ /* 312 */ "anylist ::= anylist ANY",
+};
+#endif /* NDEBUG */
+
+
+#if YYSTACKDEPTH<=0
+/*
+** Try to increase the size of the parser stack.
+*/
+static void yyGrowStack(yyParser *p){
+ int newSize;
+ yyStackEntry *pNew;
+
+ newSize = p->yystksz*2 + 100;
+ pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
+ if( pNew ){
+ p->yystack = pNew;
+ p->yystksz = newSize;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack grows to %d entries!\n",
+ yyTracePrompt, p->yystksz);
+ }
+#endif
+ }
+}
+#endif
+
+/*
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser. This pointer is used in subsequent calls
+** to sqlite3Parser and sqlite3ParserFree.
+*/
+SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(size_t)){
+ yyParser *pParser;
+ pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+ if( pParser ){
+ pParser->yyidx = -1;
+#if YYSTACKDEPTH<=0
+ yyGrowStack(pParser);
+#endif
+ }
+ return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol. The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){
+ switch( yymajor ){
+ /* Here is inserted the actions which take place when a
+ ** terminal or non-terminal is destroyed. This can happen
+ ** when the symbol is popped from the stack during a
+ ** reduce or during error processing or when a parser is
+ ** being destroyed before it is finished parsing.
+ **
+ ** Note: during a reduce, the only symbols destroyed are those
+ ** which appear on the RHS of the rule, but which are not used
+ ** inside the C code.
+ */
+ case 155: /* select */
+{
+sqlite3SelectDelete((yypminor->yy219));
+}
+ break;
+ case 169: /* term */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 170: /* expr */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 174: /* idxlist_opt */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 182: /* idxlist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 188: /* fullname */
+{
+sqlite3SrcListDelete((yypminor->yy373));
+}
+ break;
+ case 189: /* oneselect */
+{
+sqlite3SelectDelete((yypminor->yy219));
+}
+ break;
+ case 192: /* selcollist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 193: /* from */
+{
+sqlite3SrcListDelete((yypminor->yy373));
+}
+ break;
+ case 194: /* where_opt */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 195: /* groupby_opt */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 196: /* having_opt */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 197: /* orderby_opt */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 199: /* sclp */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 201: /* seltablist */
+{
+sqlite3SrcListDelete((yypminor->yy373));
+}
+ break;
+ case 202: /* stl_prefix */
+{
+sqlite3SrcListDelete((yypminor->yy373));
+}
+ break;
+ case 204: /* on_opt */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 205: /* using_opt */
+{
+sqlite3IdListDelete((yypminor->yy432));
+}
+ break;
+ case 206: /* seltablist_paren */
+{
+sqlite3SelectDelete((yypminor->yy219));
+}
+ break;
+ case 208: /* inscollist */
+{
+sqlite3IdListDelete((yypminor->yy432));
+}
+ break;
+ case 209: /* sortlist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 210: /* sortitem */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 211: /* nexprlist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 212: /* setlist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 214: /* inscollist_opt */
+{
+sqlite3IdListDelete((yypminor->yy432));
+}
+ break;
+ case 215: /* itemlist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 216: /* exprlist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 218: /* escape */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 221: /* case_operand */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 222: /* case_exprlist */
+{
+sqlite3ExprListDelete((yypminor->yy174));
+}
+ break;
+ case 223: /* case_else */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 231: /* trigger_cmd_list */
+{
+sqlite3DeleteTriggerStep((yypminor->yy243));
+}
+ break;
+ case 233: /* trigger_event */
+{
+sqlite3IdListDelete((yypminor->yy370).b);
+}
+ break;
+ case 235: /* when_clause */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ case 236: /* trigger_cmd */
+{
+sqlite3DeleteTriggerStep((yypminor->yy243));
+}
+ break;
+ case 238: /* key_opt */
+{
+sqlite3ExprDelete((yypminor->yy172));
+}
+ break;
+ default: break; /* If no destructor action specified: do nothing */
+ }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+ YYCODETYPE yymajor;
+ yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+ if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+ if( yyTraceFILE && pParser->yyidx>=0 ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[yytos->major]);
+ }
+#endif
+ yymajor = yytos->major;
+ yy_destructor( yymajor, &yytos->minor);
+ pParser->yyidx--;
+ return yymajor;
+}
+
+/*
+** Deallocate and destroy a parser. Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from sqlite3ParserAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+SQLITE_PRIVATE void sqlite3ParserFree(
+ void *p, /* The parser to be deleted */
+ void (*freeProc)(void*) /* Function used to reclaim memory */
+){
+ yyParser *pParser = (yyParser*)p;
+ if( pParser==0 ) return;
+ while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+ free(pParser->yystack);
+#endif
+ (*freeProc)((void*)pParser);
+}
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+ yyParser *pParser, /* The parser */
+ YYCODETYPE iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ if( stateno>YY_SHIFT_MAX || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ assert( iLookAhead!=YYNOCODE );
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+ if( iLookAhead>0 ){
+#ifdef YYFALLBACK
+ int iFallback; /* Fallback token */
+ if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+ }
+#endif
+ return yy_find_shift_action(pParser, iFallback);
+ }
+#endif
+#ifdef YYWILDCARD
+ {
+ int j = i - iLookAhead + YYWILDCARD;
+ if( j>=0 && j<YY_SZ_ACTTAB && yy_lookahead[j]==YYWILDCARD ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]);
+ }
+#endif /* NDEBUG */
+ return yy_action[j];
+ }
+ }
+#endif /* YYWILDCARD */
+ }
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+ int stateno, /* Current state number */
+ YYCODETYPE iLookAhead /* The look-ahead token */
+){
+ int i;
+#ifdef YYERRORSYMBOL
+ if( stateno>YY_REDUCE_MAX ){
+ return yy_default[stateno];
+ }
+#else
+ assert( stateno<=YY_REDUCE_MAX );
+#endif
+ i = yy_reduce_ofst[stateno];
+ assert( i!=YY_REDUCE_USE_DFLT );
+ assert( iLookAhead!=YYNOCODE );
+ i += iLookAhead;
+#ifdef YYERRORSYMBOL
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+ return yy_default[stateno];
+ }
+#else
+ assert( i>=0 && i<YY_SZ_ACTTAB );
+ assert( yy_lookahead[i]==iLookAhead );
+#endif
+ return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
+ sqlite3ParserARG_FETCH;
+ yypParser->yyidx--;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will execute if the parser
+ ** stack every overflows */
+
+ sqlite3ErrorMsg(pParse, "parser stack overflow");
+ pParse->parseError = 1;
+ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument var */
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+ yyParser *yypParser, /* The parser to be shifted */
+ int yyNewState, /* The new state to shift in */
+ int yyMajor, /* The major token to shift in */
+ YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */
+){
+ yyStackEntry *yytos;
+ yypParser->yyidx++;
+#if YYSTACKDEPTH>0
+ if( yypParser->yyidx>=YYSTACKDEPTH ){
+ yyStackOverflow(yypParser, yypMinor);
+ return;
+ }
+#else
+ if( yypParser->yyidx>=yypParser->yystksz ){
+ yyGrowStack(yypParser);
+ if( yypParser->yyidx>=yypParser->yystksz ){
+ yyStackOverflow(yypParser, yypMinor);
+ return;
+ }
+ }
+#endif
+ yytos = &yypParser->yystack[yypParser->yyidx];
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
+ yytos->minor = *yypMinor;
+#ifndef NDEBUG
+ if( yyTraceFILE && yypParser->yyidx>0 ){
+ int i;
+ fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+ fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+ for(i=1; i<=yypParser->yyidx; i++)
+ fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+ fprintf(yyTraceFILE,"\n");
+ }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static const struct {
+ YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
+ unsigned char nrhs; /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+ { 139, 1 },
+ { 140, 2 },
+ { 140, 1 },
+ { 142, 1 },
+ { 141, 1 },
+ { 141, 3 },
+ { 144, 0 },
+ { 144, 1 },
+ { 144, 3 },
+ { 143, 3 },
+ { 146, 0 },
+ { 146, 1 },
+ { 146, 2 },
+ { 145, 0 },
+ { 145, 1 },
+ { 145, 1 },
+ { 145, 1 },
+ { 143, 2 },
+ { 143, 2 },
+ { 143, 2 },
+ { 143, 2 },
+ { 148, 6 },
+ { 151, 0 },
+ { 151, 3 },
+ { 150, 1 },
+ { 150, 0 },
+ { 149, 4 },
+ { 149, 2 },
+ { 153, 3 },
+ { 153, 1 },
+ { 156, 3 },
+ { 157, 1 },
+ { 160, 1 },
+ { 161, 1 },
+ { 147, 1 },
+ { 147, 1 },
+ { 147, 1 },
+ { 158, 0 },
+ { 158, 1 },
+ { 162, 1 },
+ { 162, 4 },
+ { 162, 6 },
+ { 163, 1 },
+ { 163, 2 },
+ { 164, 1 },
+ { 164, 1 },
+ { 159, 2 },
+ { 159, 0 },
+ { 167, 3 },
+ { 167, 1 },
+ { 168, 2 },
+ { 168, 4 },
+ { 168, 3 },
+ { 168, 3 },
+ { 168, 2 },
+ { 168, 2 },
+ { 168, 3 },
+ { 168, 5 },
+ { 168, 2 },
+ { 168, 4 },
+ { 168, 4 },
+ { 168, 1 },
+ { 168, 2 },
+ { 173, 0 },
+ { 173, 1 },
+ { 175, 0 },
+ { 175, 2 },
+ { 177, 2 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 178, 2 },
+ { 178, 2 },
+ { 178, 1 },
+ { 178, 1 },
+ { 176, 3 },
+ { 176, 2 },
+ { 179, 0 },
+ { 179, 2 },
+ { 179, 2 },
+ { 154, 0 },
+ { 154, 2 },
+ { 180, 3 },
+ { 180, 2 },
+ { 180, 1 },
+ { 181, 2 },
+ { 181, 7 },
+ { 181, 5 },
+ { 181, 5 },
+ { 181, 10 },
+ { 183, 0 },
+ { 183, 1 },
+ { 171, 0 },
+ { 171, 3 },
+ { 184, 0 },
+ { 184, 2 },
+ { 185, 1 },
+ { 185, 1 },
+ { 185, 1 },
+ { 143, 4 },
+ { 187, 2 },
+ { 187, 0 },
+ { 143, 8 },
+ { 143, 4 },
+ { 143, 1 },
+ { 155, 1 },
+ { 155, 3 },
+ { 190, 1 },
+ { 190, 2 },
+ { 190, 1 },
+ { 189, 9 },
+ { 191, 1 },
+ { 191, 1 },
+ { 191, 0 },
+ { 199, 2 },
+ { 199, 0 },
+ { 192, 3 },
+ { 192, 2 },
+ { 192, 4 },
+ { 200, 2 },
+ { 200, 1 },
+ { 200, 0 },
+ { 193, 0 },
+ { 193, 2 },
+ { 202, 2 },
+ { 202, 0 },
+ { 201, 6 },
+ { 201, 7 },
+ { 206, 1 },
+ { 206, 1 },
+ { 152, 0 },
+ { 152, 2 },
+ { 188, 2 },
+ { 203, 1 },
+ { 203, 2 },
+ { 203, 3 },
+ { 203, 4 },
+ { 204, 2 },
+ { 204, 0 },
+ { 205, 4 },
+ { 205, 0 },
+ { 197, 0 },
+ { 197, 3 },
+ { 209, 4 },
+ { 209, 2 },
+ { 210, 1 },
+ { 172, 1 },
+ { 172, 1 },
+ { 172, 0 },
+ { 195, 0 },
+ { 195, 3 },
+ { 196, 0 },
+ { 196, 2 },
+ { 198, 0 },
+ { 198, 2 },
+ { 198, 4 },
+ { 198, 4 },
+ { 143, 4 },
+ { 194, 0 },
+ { 194, 2 },
+ { 143, 6 },
+ { 212, 5 },
+ { 212, 3 },
+ { 143, 8 },
+ { 143, 5 },
+ { 143, 6 },
+ { 213, 2 },
+ { 213, 1 },
+ { 215, 3 },
+ { 215, 1 },
+ { 214, 0 },
+ { 214, 3 },
+ { 208, 3 },
+ { 208, 1 },
+ { 170, 1 },
+ { 170, 3 },
+ { 169, 1 },
+ { 170, 1 },
+ { 170, 1 },
+ { 170, 3 },
+ { 170, 5 },
+ { 169, 1 },
+ { 169, 1 },
+ { 170, 1 },
+ { 170, 1 },
+ { 170, 3 },
+ { 170, 6 },
+ { 170, 5 },
+ { 170, 4 },
+ { 169, 1 },
+ { 170, 3 },
+ { 170, 3 },
+ { 170, 3 },
+ { 170, 3 },
+ { 170, 3 },
+ { 170, 3 },
+ { 170, 3 },
+ { 170, 3 },
+ { 217, 1 },
+ { 217, 2 },
+ { 217, 1 },
+ { 217, 2 },
+ { 218, 2 },
+ { 218, 0 },
+ { 170, 4 },
+ { 170, 2 },
+ { 170, 3 },
+ { 170, 3 },
+ { 170, 4 },
+ { 170, 2 },
+ { 170, 2 },
+ { 170, 2 },
+ { 170, 2 },
+ { 219, 1 },
+ { 219, 2 },
+ { 170, 5 },
+ { 220, 1 },
+ { 220, 2 },
+ { 170, 5 },
+ { 170, 3 },
+ { 170, 5 },
+ { 170, 4 },
+ { 170, 4 },
+ { 170, 5 },
+ { 222, 5 },
+ { 222, 4 },
+ { 223, 2 },
+ { 223, 0 },
+ { 221, 1 },
+ { 221, 0 },
+ { 216, 1 },
+ { 216, 0 },
+ { 211, 3 },
+ { 211, 1 },
+ { 143, 11 },
+ { 224, 1 },
+ { 224, 0 },
+ { 174, 0 },
+ { 174, 3 },
+ { 182, 5 },
+ { 182, 3 },
+ { 225, 1 },
+ { 226, 0 },
+ { 226, 2 },
+ { 143, 4 },
+ { 143, 1 },
+ { 143, 2 },
+ { 143, 5 },
+ { 143, 5 },
+ { 143, 5 },
+ { 143, 5 },
+ { 143, 6 },
+ { 143, 3 },
+ { 227, 1 },
+ { 227, 1 },
+ { 165, 2 },
+ { 166, 2 },
+ { 229, 1 },
+ { 228, 1 },
+ { 228, 0 },
+ { 143, 5 },
+ { 230, 11 },
+ { 232, 1 },
+ { 232, 1 },
+ { 232, 2 },
+ { 232, 0 },
+ { 233, 1 },
+ { 233, 1 },
+ { 233, 3 },
+ { 234, 0 },
+ { 234, 3 },
+ { 235, 0 },
+ { 235, 2 },
+ { 231, 3 },
+ { 231, 0 },
+ { 236, 6 },
+ { 236, 8 },
+ { 236, 5 },
+ { 236, 4 },
+ { 236, 1 },
+ { 170, 4 },
+ { 170, 6 },
+ { 186, 1 },
+ { 186, 1 },
+ { 186, 1 },
+ { 143, 4 },
+ { 143, 6 },
+ { 143, 3 },
+ { 238, 0 },
+ { 238, 2 },
+ { 237, 1 },
+ { 237, 0 },
+ { 143, 1 },
+ { 143, 3 },
+ { 143, 1 },
+ { 143, 3 },
+ { 143, 6 },
+ { 143, 6 },
+ { 239, 1 },
+ { 240, 0 },
+ { 240, 1 },
+ { 143, 1 },
+ { 143, 4 },
+ { 241, 7 },
+ { 242, 1 },
+ { 242, 3 },
+ { 243, 0 },
+ { 243, 2 },
+ { 244, 1 },
+ { 244, 3 },
+ { 245, 1 },
+ { 246, 0 },
+ { 246, 2 },
+};
+
+static void yy_accept(yyParser*); /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+ yyParser *yypParser, /* The parser */
+ int yyruleno /* Number of the rule by which to reduce */
+){
+ int yygoto; /* The next state */
+ int yyact; /* The next action */
+ YYMINORTYPE yygotominor; /* The LHS of the rule reduced */
+ yyStackEntry *yymsp; /* The top of the parser's stack */
+ int yysize; /* Amount to pop the stack */
+ sqlite3ParserARG_FETCH;
+ yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+ if( yyTraceFILE && yyruleno>=0
+ && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
+ fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+ yyRuleName[yyruleno]);
+ }
+#endif /* NDEBUG */
+
+ /* Silence complaints from purify about yygotominor being uninitialized
+ ** in some cases when it is copied into the stack after the following
+ ** switch. yygotominor is uninitialized when a rule reduces that does
+ ** not set the value of its left-hand side nonterminal. Leaving the
+ ** value of the nonterminal uninitialized is utterly harmless as long
+ ** as the value is never used. So really the only thing this code
+ ** accomplishes is to quieten purify.
+ **
+ ** 2007-01-16: The wireshark project (www.wireshark.org) reports that
+ ** without this code, their parser segfaults. I'm not sure what there
+ ** parser is doing to make this happen. This is the second bug report
+ ** from wireshark this week. Clearly they are stressing Lemon in ways
+ ** that it has not been previously stressed... (SQLite ticket #2172)
+ */
+ /*memset(&yygotominor, 0, sizeof(yygotominor));*/
+ yygotominor = yyzerominor;
+
+
+ switch( yyruleno ){
+ /* Beginning here are the reduction cases. A typical example
+ ** follows:
+ ** case 0:
+ ** #line <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** break;
+ */
+ case 0: /* input ::= cmdlist */
+ case 1: /* cmdlist ::= cmdlist ecmd */
+ case 2: /* cmdlist ::= ecmd */
+ case 4: /* ecmd ::= SEMI */
+ case 5: /* ecmd ::= explain cmdx SEMI */
+ case 10: /* trans_opt ::= */
+ case 11: /* trans_opt ::= TRANSACTION */
+ case 12: /* trans_opt ::= TRANSACTION nm */
+ case 20: /* cmd ::= create_table create_table_args */
+ case 28: /* columnlist ::= columnlist COMMA column */
+ case 29: /* columnlist ::= column */
+ case 37: /* type ::= */
+ case 44: /* signed ::= plus_num */
+ case 45: /* signed ::= minus_num */
+ case 46: /* carglist ::= carglist carg */
+ case 47: /* carglist ::= */
+ case 48: /* carg ::= CONSTRAINT nm ccons */
+ case 49: /* carg ::= ccons */
+ case 55: /* ccons ::= NULL onconf */
+ case 82: /* conslist ::= conslist COMMA tcons */
+ case 83: /* conslist ::= conslist tcons */
+ case 84: /* conslist ::= tcons */
+ case 85: /* tcons ::= CONSTRAINT nm */
+ case 258: /* plus_opt ::= PLUS */
+ case 259: /* plus_opt ::= */
+ case 269: /* foreach_clause ::= */
+ case 270: /* foreach_clause ::= FOR EACH ROW */
+ case 290: /* database_kw_opt ::= DATABASE */
+ case 291: /* database_kw_opt ::= */
+ case 299: /* kwcolumn_opt ::= */
+ case 300: /* kwcolumn_opt ::= COLUMNKW */
+ case 304: /* vtabarglist ::= vtabarg */
+ case 305: /* vtabarglist ::= vtabarglist COMMA vtabarg */
+ case 307: /* vtabarg ::= vtabarg vtabargtoken */
+ case 311: /* anylist ::= */
+{
+}
+ break;
+ case 3: /* cmdx ::= cmd */
+{ sqlite3FinishCoding(pParse); }
+ break;
+ case 6: /* explain ::= */
+{ sqlite3BeginParse(pParse, 0); }
+ break;
+ case 7: /* explain ::= EXPLAIN */
+{ sqlite3BeginParse(pParse, 1); }
+ break;
+ case 8: /* explain ::= EXPLAIN QUERY PLAN */
+{ sqlite3BeginParse(pParse, 2); }
+ break;
+ case 9: /* cmd ::= BEGIN transtype trans_opt */
+{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy46);}
+ break;
+ case 13: /* transtype ::= */
+{yygotominor.yy46 = TK_DEFERRED;}
+ break;
+ case 14: /* transtype ::= DEFERRED */
+ case 15: /* transtype ::= IMMEDIATE */
+ case 16: /* transtype ::= EXCLUSIVE */
+ case 107: /* multiselect_op ::= UNION */
+ case 109: /* multiselect_op ::= EXCEPT|INTERSECT */
+{yygotominor.yy46 = yymsp[0].major;}
+ break;
+ case 17: /* cmd ::= COMMIT trans_opt */
+ case 18: /* cmd ::= END trans_opt */
+{sqlite3CommitTransaction(pParse);}
+ break;
+ case 19: /* cmd ::= ROLLBACK trans_opt */
+{sqlite3RollbackTransaction(pParse);}
+ break;
+ case 21: /* create_table ::= CREATE temp TABLE ifnotexists nm dbnm */
+{
+ sqlite3StartTable(pParse,&yymsp[-1].minor.yy410,&yymsp[0].minor.yy410,yymsp[-4].minor.yy46,0,0,yymsp[-2].minor.yy46);
+}
+ break;
+ case 22: /* ifnotexists ::= */
+ case 25: /* temp ::= */
+ case 63: /* autoinc ::= */
+ case 77: /* init_deferred_pred_opt ::= */
+ case 79: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
+ case 90: /* defer_subclause_opt ::= */
+ case 101: /* ifexists ::= */
+ case 112: /* distinct ::= ALL */
+ case 113: /* distinct ::= */
+ case 213: /* between_op ::= BETWEEN */
+ case 216: /* in_op ::= IN */
+{yygotominor.yy46 = 0;}
+ break;
+ case 23: /* ifnotexists ::= IF NOT EXISTS */
+ case 24: /* temp ::= TEMP */
+ case 64: /* autoinc ::= AUTOINCR */
+ case 78: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */
+ case 100: /* ifexists ::= IF EXISTS */
+ case 111: /* distinct ::= DISTINCT */
+ case 214: /* between_op ::= NOT BETWEEN */
+ case 217: /* in_op ::= NOT IN */
+{yygotominor.yy46 = 1;}
+ break;
+ case 26: /* create_table_args ::= LP columnlist conslist_opt RP */
+{
+ sqlite3EndTable(pParse,&yymsp[-1].minor.yy410,&yymsp[0].minor.yy0,0);
+}
+ break;
+ case 27: /* create_table_args ::= AS select */
+{
+ sqlite3EndTable(pParse,0,0,yymsp[0].minor.yy219);
+ sqlite3SelectDelete(yymsp[0].minor.yy219);
+}
+ break;
+ case 30: /* column ::= columnid type carglist */
+{
+ yygotominor.yy410.z = yymsp[-2].minor.yy410.z;
+ yygotominor.yy410.n = (pParse->sLastToken.z-yymsp[-2].minor.yy410.z) + pParse->sLastToken.n;
+}
+ break;
+ case 31: /* columnid ::= nm */
+{
+ sqlite3AddColumn(pParse,&yymsp[0].minor.yy410);
+ yygotominor.yy410 = yymsp[0].minor.yy410;
+}
+ break;
+ case 32: /* id ::= ID */
+ case 33: /* ids ::= ID|STRING */
+ case 34: /* nm ::= ID */
+ case 35: /* nm ::= STRING */
+ case 36: /* nm ::= JOIN_KW */
+ case 257: /* number ::= INTEGER|FLOAT */
+{yygotominor.yy410 = yymsp[0].minor.yy0;}
+ break;
+ case 38: /* type ::= typetoken */
+{sqlite3AddColumnType(pParse,&yymsp[0].minor.yy410);}
+ break;
+ case 39: /* typetoken ::= typename */
+ case 42: /* typename ::= ids */
+ case 119: /* as ::= AS nm */
+ case 120: /* as ::= ids */
+ case 131: /* dbnm ::= DOT nm */
+ case 241: /* idxitem ::= nm */
+ case 243: /* collate ::= COLLATE ids */
+ case 253: /* nmnum ::= plus_num */
+ case 254: /* nmnum ::= nm */
+ case 255: /* plus_num ::= plus_opt number */
+ case 256: /* minus_num ::= MINUS number */
+{yygotominor.yy410 = yymsp[0].minor.yy410;}
+ break;
+ case 40: /* typetoken ::= typename LP signed RP */
+{
+ yygotominor.yy410.z = yymsp[-3].minor.yy410.z;
+ yygotominor.yy410.n = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy410.z;
+}
+ break;
+ case 41: /* typetoken ::= typename LP signed COMMA signed RP */
+{
+ yygotominor.yy410.z = yymsp[-5].minor.yy410.z;
+ yygotominor.yy410.n = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy410.z;
+}
+ break;
+ case 43: /* typename ::= typename ids */
+{yygotominor.yy410.z=yymsp[-1].minor.yy410.z; yygotominor.yy410.n=yymsp[0].minor.yy410.n+(yymsp[0].minor.yy410.z-yymsp[-1].minor.yy410.z);}
+ break;
+ case 50: /* ccons ::= DEFAULT term */
+ case 52: /* ccons ::= DEFAULT PLUS term */
+{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy172);}
+ break;
+ case 51: /* ccons ::= DEFAULT LP expr RP */
+{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy172);}
+ break;
+ case 53: /* ccons ::= DEFAULT MINUS term */
+{
+ Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy172, 0, 0);
+ sqlite3AddDefaultValue(pParse,p);
+}
+ break;
+ case 54: /* ccons ::= DEFAULT id */
+{
+ Expr *p = sqlite3PExpr(pParse, TK_STRING, 0, 0, &yymsp[0].minor.yy410);
+ sqlite3AddDefaultValue(pParse,p);
+}
+ break;
+ case 56: /* ccons ::= NOT NULL onconf */
+{sqlite3AddNotNull(pParse, yymsp[0].minor.yy46);}
+ break;
+ case 57: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */
+{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy46,yymsp[0].minor.yy46,yymsp[-2].minor.yy46);}
+ break;
+ case 58: /* ccons ::= UNIQUE onconf */
+{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy46,0,0,0,0);}
+ break;
+ case 59: /* ccons ::= CHECK LP expr RP */
+{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy172);}
+ break;
+ case 60: /* ccons ::= REFERENCES nm idxlist_opt refargs */
+{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy410,yymsp[-1].minor.yy174,yymsp[0].minor.yy46);}
+ break;
+ case 61: /* ccons ::= defer_subclause */
+{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy46);}
+ break;
+ case 62: /* ccons ::= COLLATE ids */
+{sqlite3AddCollateType(pParse, &yymsp[0].minor.yy410);}
+ break;
+ case 65: /* refargs ::= */
+{ yygotominor.yy46 = OE_Restrict * 0x010101; }
+ break;
+ case 66: /* refargs ::= refargs refarg */
+{ yygotominor.yy46 = (yymsp[-1].minor.yy46 & yymsp[0].minor.yy405.mask) | yymsp[0].minor.yy405.value; }
+ break;
+ case 67: /* refarg ::= MATCH nm */
+{ yygotominor.yy405.value = 0; yygotominor.yy405.mask = 0x000000; }
+ break;
+ case 68: /* refarg ::= ON DELETE refact */
+{ yygotominor.yy405.value = yymsp[0].minor.yy46; yygotominor.yy405.mask = 0x0000ff; }
+ break;
+ case 69: /* refarg ::= ON UPDATE refact */
+{ yygotominor.yy405.value = yymsp[0].minor.yy46<<8; yygotominor.yy405.mask = 0x00ff00; }
+ break;
+ case 70: /* refarg ::= ON INSERT refact */
+{ yygotominor.yy405.value = yymsp[0].minor.yy46<<16; yygotominor.yy405.mask = 0xff0000; }
+ break;
+ case 71: /* refact ::= SET NULL */
+{ yygotominor.yy46 = OE_SetNull; }
+ break;
+ case 72: /* refact ::= SET DEFAULT */
+{ yygotominor.yy46 = OE_SetDflt; }
+ break;
+ case 73: /* refact ::= CASCADE */
+{ yygotominor.yy46 = OE_Cascade; }
+ break;
+ case 74: /* refact ::= RESTRICT */
+{ yygotominor.yy46 = OE_Restrict; }
+ break;
+ case 75: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
+ case 76: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
+ case 91: /* defer_subclause_opt ::= defer_subclause */
+ case 93: /* onconf ::= ON CONFLICT resolvetype */
+ case 95: /* orconf ::= OR resolvetype */
+ case 96: /* resolvetype ::= raisetype */
+ case 166: /* insert_cmd ::= INSERT orconf */
+{yygotominor.yy46 = yymsp[0].minor.yy46;}
+ break;
+ case 80: /* conslist_opt ::= */
+{yygotominor.yy410.n = 0; yygotominor.yy410.z = 0;}
+ break;
+ case 81: /* conslist_opt ::= COMMA conslist */
+{yygotominor.yy410 = yymsp[-1].minor.yy0;}
+ break;
+ case 86: /* tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf */
+{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy174,yymsp[0].minor.yy46,yymsp[-2].minor.yy46,0);}
+ break;
+ case 87: /* tcons ::= UNIQUE LP idxlist RP onconf */
+{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy174,yymsp[0].minor.yy46,0,0,0,0);}
+ break;
+ case 88: /* tcons ::= CHECK LP expr RP onconf */
+{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy172);}
+ break;
+ case 89: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt */
+{
+ sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy174, &yymsp[-3].minor.yy410, yymsp[-2].minor.yy174, yymsp[-1].minor.yy46);
+ sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy46);
+}
+ break;
+ case 92: /* onconf ::= */
+ case 94: /* orconf ::= */
+{yygotominor.yy46 = OE_Default;}
+ break;
+ case 97: /* resolvetype ::= IGNORE */
+{yygotominor.yy46 = OE_Ignore;}
+ break;
+ case 98: /* resolvetype ::= REPLACE */
+ case 167: /* insert_cmd ::= REPLACE */
+{yygotominor.yy46 = OE_Replace;}
+ break;
+ case 99: /* cmd ::= DROP TABLE ifexists fullname */
+{
+ sqlite3DropTable(pParse, yymsp[0].minor.yy373, 0, yymsp[-1].minor.yy46);
+}
+ break;
+ case 102: /* cmd ::= CREATE temp VIEW ifnotexists nm dbnm AS select */
+{
+ sqlite3CreateView(pParse, &yymsp[-7].minor.yy0, &yymsp[-3].minor.yy410, &yymsp[-2].minor.yy410, yymsp[0].minor.yy219, yymsp[-6].minor.yy46, yymsp[-4].minor.yy46);
+}
+ break;
+ case 103: /* cmd ::= DROP VIEW ifexists fullname */
+{
+ sqlite3DropTable(pParse, yymsp[0].minor.yy373, 1, yymsp[-1].minor.yy46);
+}
+ break;
+ case 104: /* cmd ::= select */
+{
+ SelectDest dest = {SRT_Callback, 0, 0, 0, 0};
+ sqlite3Select(pParse, yymsp[0].minor.yy219, &dest, 0, 0, 0, 0);
+ sqlite3SelectDelete(yymsp[0].minor.yy219);
+}
+ break;
+ case 105: /* select ::= oneselect */
+ case 128: /* seltablist_paren ::= select */
+{yygotominor.yy219 = yymsp[0].minor.yy219;}
+ break;
+ case 106: /* select ::= select multiselect_op oneselect */
+{
+ if( yymsp[0].minor.yy219 ){
+ yymsp[0].minor.yy219->op = yymsp[-1].minor.yy46;
+ yymsp[0].minor.yy219->pPrior = yymsp[-2].minor.yy219;
+ }else{
+ sqlite3SelectDelete(yymsp[-2].minor.yy219);
+ }
+ yygotominor.yy219 = yymsp[0].minor.yy219;
+}
+ break;
+ case 108: /* multiselect_op ::= UNION ALL */
+{yygotominor.yy46 = TK_ALL;}
+ break;
+ case 110: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
+{
+ yygotominor.yy219 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy174,yymsp[-5].minor.yy373,yymsp[-4].minor.yy172,yymsp[-3].minor.yy174,yymsp[-2].minor.yy172,yymsp[-1].minor.yy174,yymsp[-7].minor.yy46,yymsp[0].minor.yy234.pLimit,yymsp[0].minor.yy234.pOffset);
+}
+ break;
+ case 114: /* sclp ::= selcollist COMMA */
+ case 238: /* idxlist_opt ::= LP idxlist RP */
+{yygotominor.yy174 = yymsp[-1].minor.yy174;}
+ break;
+ case 115: /* sclp ::= */
+ case 141: /* orderby_opt ::= */
+ case 149: /* groupby_opt ::= */
+ case 231: /* exprlist ::= */
+ case 237: /* idxlist_opt ::= */
+{yygotominor.yy174 = 0;}
+ break;
+ case 116: /* selcollist ::= sclp expr as */
+{
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy174,yymsp[-1].minor.yy172,yymsp[0].minor.yy410.n?&yymsp[0].minor.yy410:0);
+}
+ break;
+ case 117: /* selcollist ::= sclp STAR */
+{
+ Expr *p = sqlite3PExpr(pParse, TK_ALL, 0, 0, 0);
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse, yymsp[-1].minor.yy174, p, 0);
+}
+ break;
+ case 118: /* selcollist ::= sclp nm DOT STAR */
+{
+ Expr *pRight = sqlite3PExpr(pParse, TK_ALL, 0, 0, 0);
+ Expr *pLeft = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy410);
+ Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0);
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy174, pDot, 0);
+}
+ break;
+ case 121: /* as ::= */
+{yygotominor.yy410.n = 0;}
+ break;
+ case 122: /* from ::= */
+{yygotominor.yy373 = sqlite3DbMallocZero(pParse->db, sizeof(*yygotominor.yy373));}
+ break;
+ case 123: /* from ::= FROM seltablist */
+{
+ yygotominor.yy373 = yymsp[0].minor.yy373;
+ sqlite3SrcListShiftJoinType(yygotominor.yy373);
+}
+ break;
+ case 124: /* stl_prefix ::= seltablist joinop */
+{
+ yygotominor.yy373 = yymsp[-1].minor.yy373;
+ if( yygotominor.yy373 && yygotominor.yy373->nSrc>0 ) yygotominor.yy373->a[yygotominor.yy373->nSrc-1].jointype = yymsp[0].minor.yy46;
+}
+ break;
+ case 125: /* stl_prefix ::= */
+{yygotominor.yy373 = 0;}
+ break;
+ case 126: /* seltablist ::= stl_prefix nm dbnm as on_opt using_opt */
+{
+ yygotominor.yy373 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy373,&yymsp[-4].minor.yy410,&yymsp[-3].minor.yy410,&yymsp[-2].minor.yy410,0,yymsp[-1].minor.yy172,yymsp[0].minor.yy432);
+}
+ break;
+ case 127: /* seltablist ::= stl_prefix LP seltablist_paren RP as on_opt using_opt */
+{
+ yygotominor.yy373 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy373,0,0,&yymsp[-2].minor.yy410,yymsp[-4].minor.yy219,yymsp[-1].minor.yy172,yymsp[0].minor.yy432);
+ }
+ break;
+ case 129: /* seltablist_paren ::= seltablist */
+{
+ sqlite3SrcListShiftJoinType(yymsp[0].minor.yy373);
+ yygotominor.yy219 = sqlite3SelectNew(pParse,0,yymsp[0].minor.yy373,0,0,0,0,0,0,0);
+ }
+ break;
+ case 130: /* dbnm ::= */
+{yygotominor.yy410.z=0; yygotominor.yy410.n=0;}
+ break;
+ case 132: /* fullname ::= nm dbnm */
+{yygotominor.yy373 = sqlite3SrcListAppend(pParse->db,0,&yymsp[-1].minor.yy410,&yymsp[0].minor.yy410);}
+ break;
+ case 133: /* joinop ::= COMMA|JOIN */
+{ yygotominor.yy46 = JT_INNER; }
+ break;
+ case 134: /* joinop ::= JOIN_KW JOIN */
+{ yygotominor.yy46 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); }
+ break;
+ case 135: /* joinop ::= JOIN_KW nm JOIN */
+{ yygotominor.yy46 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy410,0); }
+ break;
+ case 136: /* joinop ::= JOIN_KW nm nm JOIN */
+{ yygotominor.yy46 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy410,&yymsp[-1].minor.yy410); }
+ break;
+ case 137: /* on_opt ::= ON expr */
+ case 145: /* sortitem ::= expr */
+ case 152: /* having_opt ::= HAVING expr */
+ case 159: /* where_opt ::= WHERE expr */
+ case 174: /* expr ::= term */
+ case 202: /* escape ::= ESCAPE expr */
+ case 226: /* case_else ::= ELSE expr */
+ case 228: /* case_operand ::= expr */
+{yygotominor.yy172 = yymsp[0].minor.yy172;}
+ break;
+ case 138: /* on_opt ::= */
+ case 151: /* having_opt ::= */
+ case 158: /* where_opt ::= */
+ case 203: /* escape ::= */
+ case 227: /* case_else ::= */
+ case 229: /* case_operand ::= */
+{yygotominor.yy172 = 0;}
+ break;
+ case 139: /* using_opt ::= USING LP inscollist RP */
+ case 171: /* inscollist_opt ::= LP inscollist RP */
+{yygotominor.yy432 = yymsp[-1].minor.yy432;}
+ break;
+ case 140: /* using_opt ::= */
+ case 170: /* inscollist_opt ::= */
+{yygotominor.yy432 = 0;}
+ break;
+ case 142: /* orderby_opt ::= ORDER BY sortlist */
+ case 150: /* groupby_opt ::= GROUP BY nexprlist */
+ case 230: /* exprlist ::= nexprlist */
+{yygotominor.yy174 = yymsp[0].minor.yy174;}
+ break;
+ case 143: /* sortlist ::= sortlist COMMA sortitem sortorder */
+{
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy174,yymsp[-1].minor.yy172,0);
+ if( yygotominor.yy174 ) yygotominor.yy174->a[yygotominor.yy174->nExpr-1].sortOrder = yymsp[0].minor.yy46;
+}
+ break;
+ case 144: /* sortlist ::= sortitem sortorder */
+{
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy172,0);
+ if( yygotominor.yy174 && yygotominor.yy174->a ) yygotominor.yy174->a[0].sortOrder = yymsp[0].minor.yy46;
+}
+ break;
+ case 146: /* sortorder ::= ASC */
+ case 148: /* sortorder ::= */
+{yygotominor.yy46 = SQLITE_SO_ASC;}
+ break;
+ case 147: /* sortorder ::= DESC */
+{yygotominor.yy46 = SQLITE_SO_DESC;}
+ break;
+ case 153: /* limit_opt ::= */
+{yygotominor.yy234.pLimit = 0; yygotominor.yy234.pOffset = 0;}
+ break;
+ case 154: /* limit_opt ::= LIMIT expr */
+{yygotominor.yy234.pLimit = yymsp[0].minor.yy172; yygotominor.yy234.pOffset = 0;}
+ break;
+ case 155: /* limit_opt ::= LIMIT expr OFFSET expr */
+{yygotominor.yy234.pLimit = yymsp[-2].minor.yy172; yygotominor.yy234.pOffset = yymsp[0].minor.yy172;}
+ break;
+ case 156: /* limit_opt ::= LIMIT expr COMMA expr */
+{yygotominor.yy234.pOffset = yymsp[-2].minor.yy172; yygotominor.yy234.pLimit = yymsp[0].minor.yy172;}
+ break;
+ case 157: /* cmd ::= DELETE FROM fullname where_opt */
+{sqlite3DeleteFrom(pParse,yymsp[-1].minor.yy373,yymsp[0].minor.yy172);}
+ break;
+ case 160: /* cmd ::= UPDATE orconf fullname SET setlist where_opt */
+{
+ sqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy174,"set list");
+ sqlite3Update(pParse,yymsp[-3].minor.yy373,yymsp[-1].minor.yy174,yymsp[0].minor.yy172,yymsp[-4].minor.yy46);
+}
+ break;
+ case 161: /* setlist ::= setlist COMMA nm EQ expr */
+{yygotominor.yy174 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy174,yymsp[0].minor.yy172,&yymsp[-2].minor.yy410);}
+ break;
+ case 162: /* setlist ::= nm EQ expr */
+{yygotominor.yy174 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy172,&yymsp[-2].minor.yy410);}
+ break;
+ case 163: /* cmd ::= insert_cmd INTO fullname inscollist_opt VALUES LP itemlist RP */
+{sqlite3Insert(pParse, yymsp[-5].minor.yy373, yymsp[-1].minor.yy174, 0, yymsp[-4].minor.yy432, yymsp[-7].minor.yy46);}
+ break;
+ case 164: /* cmd ::= insert_cmd INTO fullname inscollist_opt select */
+{sqlite3Insert(pParse, yymsp[-2].minor.yy373, 0, yymsp[0].minor.yy219, yymsp[-1].minor.yy432, yymsp[-4].minor.yy46);}
+ break;
+ case 165: /* cmd ::= insert_cmd INTO fullname inscollist_opt DEFAULT VALUES */
+{sqlite3Insert(pParse, yymsp[-3].minor.yy373, 0, 0, yymsp[-2].minor.yy432, yymsp[-5].minor.yy46);}
+ break;
+ case 168: /* itemlist ::= itemlist COMMA expr */
+ case 232: /* nexprlist ::= nexprlist COMMA expr */
+{yygotominor.yy174 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy174,yymsp[0].minor.yy172,0);}
+ break;
+ case 169: /* itemlist ::= expr */
+ case 233: /* nexprlist ::= expr */
+{yygotominor.yy174 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy172,0);}
+ break;
+ case 172: /* inscollist ::= inscollist COMMA nm */
+{yygotominor.yy432 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy432,&yymsp[0].minor.yy410);}
+ break;
+ case 173: /* inscollist ::= nm */
+{yygotominor.yy432 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy410);}
+ break;
+ case 175: /* expr ::= LP expr RP */
+{yygotominor.yy172 = yymsp[-1].minor.yy172; sqlite3ExprSpan(yygotominor.yy172,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); }
+ break;
+ case 176: /* term ::= NULL */
+ case 181: /* term ::= INTEGER|FLOAT|BLOB */
+ case 182: /* term ::= STRING */
+{yygotominor.yy172 = sqlite3PExpr(pParse, yymsp[0].major, 0, 0, &yymsp[0].minor.yy0);}
+ break;
+ case 177: /* expr ::= ID */
+ case 178: /* expr ::= JOIN_KW */
+{yygotominor.yy172 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy0);}
+ break;
+ case 179: /* expr ::= nm DOT nm */
+{
+ Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy410);
+ Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy410);
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0);
+}
+ break;
+ case 180: /* expr ::= nm DOT nm DOT nm */
+{
+ Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-4].minor.yy410);
+ Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy410);
+ Expr *temp3 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy410);
+ Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3, 0);
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0);
+}
+ break;
+ case 183: /* expr ::= REGISTER */
+{yygotominor.yy172 = sqlite3RegisterExpr(pParse, &yymsp[0].minor.yy0);}
+ break;
+ case 184: /* expr ::= VARIABLE */
+{
+ Token *pToken = &yymsp[0].minor.yy0;
+ Expr *pExpr = yygotominor.yy172 = sqlite3PExpr(pParse, TK_VARIABLE, 0, 0, pToken);
+ sqlite3ExprAssignVarNumber(pParse, pExpr);
+}
+ break;
+ case 185: /* expr ::= expr COLLATE ids */
+{
+ yygotominor.yy172 = sqlite3ExprSetColl(pParse, yymsp[-2].minor.yy172, &yymsp[0].minor.yy410);
+}
+ break;
+ case 186: /* expr ::= CAST LP expr AS typetoken RP */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_CAST, yymsp[-3].minor.yy172, 0, &yymsp[-1].minor.yy410);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0);
+}
+ break;
+ case 187: /* expr ::= ID LP distinct exprlist RP */
+{
+ if( yymsp[-1].minor.yy174 && yymsp[-1].minor.yy174->nExpr>SQLITE_MAX_FUNCTION_ARG ){
+ sqlite3ErrorMsg(pParse, "too many arguments on function %T", &yymsp[-4].minor.yy0);
+ }
+ yygotominor.yy172 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy174, &yymsp[-4].minor.yy0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0);
+ if( yymsp[-2].minor.yy46 && yygotominor.yy172 ){
+ yygotominor.yy172->flags |= EP_Distinct;
+ }
+}
+ break;
+ case 188: /* expr ::= ID LP STAR RP */
+{
+ yygotominor.yy172 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0);
+}
+ break;
+ case 189: /* term ::= CTIME_KW */
+{
+ /* The CURRENT_TIME, CURRENT_DATE, and CURRENT_TIMESTAMP values are
+ ** treated as functions that return constants */
+ yygotominor.yy172 = sqlite3ExprFunction(pParse, 0,&yymsp[0].minor.yy0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->op = TK_CONST_FUNC;
+ yygotominor.yy172->span = yymsp[0].minor.yy0;
+ }
+}
+ break;
+ case 190: /* expr ::= expr AND expr */
+ case 191: /* expr ::= expr OR expr */
+ case 192: /* expr ::= expr LT|GT|GE|LE expr */
+ case 193: /* expr ::= expr EQ|NE expr */
+ case 194: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */
+ case 195: /* expr ::= expr PLUS|MINUS expr */
+ case 196: /* expr ::= expr STAR|SLASH|REM expr */
+ case 197: /* expr ::= expr CONCAT expr */
+{yygotominor.yy172 = sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy172,yymsp[0].minor.yy172,0);}
+ break;
+ case 198: /* likeop ::= LIKE_KW */
+ case 200: /* likeop ::= MATCH */
+{yygotominor.yy72.eOperator = yymsp[0].minor.yy0; yygotominor.yy72.not = 0;}
+ break;
+ case 199: /* likeop ::= NOT LIKE_KW */
+ case 201: /* likeop ::= NOT MATCH */
+{yygotominor.yy72.eOperator = yymsp[0].minor.yy0; yygotominor.yy72.not = 1;}
+ break;
+ case 204: /* expr ::= expr likeop expr escape */
+{
+ ExprList *pList;
+ pList = sqlite3ExprListAppend(pParse,0, yymsp[-1].minor.yy172, 0);
+ pList = sqlite3ExprListAppend(pParse,pList, yymsp[-3].minor.yy172, 0);
+ if( yymsp[0].minor.yy172 ){
+ pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy172, 0);
+ }
+ yygotominor.yy172 = sqlite3ExprFunction(pParse, pList, &yymsp[-2].minor.yy72.eOperator);
+ if( yymsp[-2].minor.yy72.not ) yygotominor.yy172 = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172, &yymsp[-3].minor.yy172->span, &yymsp[-1].minor.yy172->span);
+ if( yygotominor.yy172 ) yygotominor.yy172->flags |= EP_InfixFunc;
+}
+ break;
+ case 205: /* expr ::= expr ISNULL|NOTNULL */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, yymsp[0].major, yymsp[-1].minor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-1].minor.yy172->span,&yymsp[0].minor.yy0);
+}
+ break;
+ case 206: /* expr ::= expr IS NULL */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_ISNULL, yymsp[-2].minor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-2].minor.yy172->span,&yymsp[0].minor.yy0);
+}
+ break;
+ case 207: /* expr ::= expr NOT NULL */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_NOTNULL, yymsp[-2].minor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-2].minor.yy172->span,&yymsp[0].minor.yy0);
+}
+ break;
+ case 208: /* expr ::= expr IS NOT NULL */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_NOTNULL, yymsp[-3].minor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-3].minor.yy172->span,&yymsp[0].minor.yy0);
+}
+ break;
+ case 209: /* expr ::= NOT expr */
+ case 210: /* expr ::= BITNOT expr */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy172->span);
+}
+ break;
+ case 211: /* expr ::= MINUS expr */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy172->span);
+}
+ break;
+ case 212: /* expr ::= PLUS expr */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_UPLUS, yymsp[0].minor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy172->span);
+}
+ break;
+ case 215: /* expr ::= expr between_op expr AND expr */
+{
+ ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy172, 0);
+ pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy172, 0);
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy172, 0, 0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->pList = pList;
+ }else{
+ sqlite3ExprListDelete(pList);
+ }
+ if( yymsp[-3].minor.yy46 ) yygotominor.yy172 = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-4].minor.yy172->span,&yymsp[0].minor.yy172->span);
+}
+ break;
+ case 218: /* expr ::= expr in_op LP exprlist RP */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy172, 0, 0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->pList = yymsp[-1].minor.yy174;
+ sqlite3ExprSetHeight(yygotominor.yy172);
+ }else{
+ sqlite3ExprListDelete(yymsp[-1].minor.yy174);
+ }
+ if( yymsp[-3].minor.yy46 ) yygotominor.yy172 = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-4].minor.yy172->span,&yymsp[0].minor.yy0);
+ }
+ break;
+ case 219: /* expr ::= LP select RP */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->pSelect = yymsp[-1].minor.yy219;
+ sqlite3ExprSetHeight(yygotominor.yy172);
+ }else{
+ sqlite3SelectDelete(yymsp[-1].minor.yy219);
+ }
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);
+ }
+ break;
+ case 220: /* expr ::= expr in_op LP select RP */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy172, 0, 0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->pSelect = yymsp[-1].minor.yy219;
+ sqlite3ExprSetHeight(yygotominor.yy172);
+ }else{
+ sqlite3SelectDelete(yymsp[-1].minor.yy219);
+ }
+ if( yymsp[-3].minor.yy46 ) yygotominor.yy172 = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-4].minor.yy172->span,&yymsp[0].minor.yy0);
+ }
+ break;
+ case 221: /* expr ::= expr in_op nm dbnm */
+{
+ SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&yymsp[-1].minor.yy410,&yymsp[0].minor.yy410);
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_IN, yymsp[-3].minor.yy172, 0, 0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0);
+ sqlite3ExprSetHeight(yygotominor.yy172);
+ }else{
+ sqlite3SrcListDelete(pSrc);
+ }
+ if( yymsp[-2].minor.yy46 ) yygotominor.yy172 = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy172, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy172,&yymsp[-3].minor.yy172->span,yymsp[0].minor.yy410.z?&yymsp[0].minor.yy410:&yymsp[-1].minor.yy410);
+ }
+ break;
+ case 222: /* expr ::= EXISTS LP select RP */
+{
+ Expr *p = yygotominor.yy172 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0);
+ if( p ){
+ p->pSelect = yymsp[-1].minor.yy219;
+ sqlite3ExprSpan(p,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0);
+ sqlite3ExprSetHeight(yygotominor.yy172);
+ }else{
+ sqlite3SelectDelete(yymsp[-1].minor.yy219);
+ }
+ }
+ break;
+ case 223: /* expr ::= CASE case_operand case_exprlist case_else END */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy172, yymsp[-1].minor.yy172, 0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->pList = yymsp[-2].minor.yy174;
+ sqlite3ExprSetHeight(yygotominor.yy172);
+ }else{
+ sqlite3ExprListDelete(yymsp[-2].minor.yy174);
+ }
+ sqlite3ExprSpan(yygotominor.yy172, &yymsp[-4].minor.yy0, &yymsp[0].minor.yy0);
+}
+ break;
+ case 224: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */
+{
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy174, yymsp[-2].minor.yy172, 0);
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,yygotominor.yy174, yymsp[0].minor.yy172, 0);
+}
+ break;
+ case 225: /* case_exprlist ::= WHEN expr THEN expr */
+{
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy172, 0);
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,yygotominor.yy174, yymsp[0].minor.yy172, 0);
+}
+ break;
+ case 234: /* cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP */
+{
+ sqlite3CreateIndex(pParse, &yymsp[-6].minor.yy410, &yymsp[-5].minor.yy410,
+ sqlite3SrcListAppend(pParse->db,0,&yymsp[-3].minor.yy410,0), yymsp[-1].minor.yy174, yymsp[-9].minor.yy46,
+ &yymsp[-10].minor.yy0, &yymsp[0].minor.yy0, SQLITE_SO_ASC, yymsp[-7].minor.yy46);
+}
+ break;
+ case 235: /* uniqueflag ::= UNIQUE */
+ case 283: /* raisetype ::= ABORT */
+{yygotominor.yy46 = OE_Abort;}
+ break;
+ case 236: /* uniqueflag ::= */
+{yygotominor.yy46 = OE_None;}
+ break;
+ case 239: /* idxlist ::= idxlist COMMA idxitem collate sortorder */
+{
+ Expr *p = 0;
+ if( yymsp[-1].minor.yy410.n>0 ){
+ p = sqlite3PExpr(pParse, TK_COLUMN, 0, 0, 0);
+ sqlite3ExprSetColl(pParse, p, &yymsp[-1].minor.yy410);
+ }
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy174, p, &yymsp[-2].minor.yy410);
+ sqlite3ExprListCheckLength(pParse, yygotominor.yy174, "index");
+ if( yygotominor.yy174 ) yygotominor.yy174->a[yygotominor.yy174->nExpr-1].sortOrder = yymsp[0].minor.yy46;
+}
+ break;
+ case 240: /* idxlist ::= idxitem collate sortorder */
+{
+ Expr *p = 0;
+ if( yymsp[-1].minor.yy410.n>0 ){
+ p = sqlite3PExpr(pParse, TK_COLUMN, 0, 0, 0);
+ sqlite3ExprSetColl(pParse, p, &yymsp[-1].minor.yy410);
+ }
+ yygotominor.yy174 = sqlite3ExprListAppend(pParse,0, p, &yymsp[-2].minor.yy410);
+ sqlite3ExprListCheckLength(pParse, yygotominor.yy174, "index");
+ if( yygotominor.yy174 ) yygotominor.yy174->a[yygotominor.yy174->nExpr-1].sortOrder = yymsp[0].minor.yy46;
+}
+ break;
+ case 242: /* collate ::= */
+{yygotominor.yy410.z = 0; yygotominor.yy410.n = 0;}
+ break;
+ case 244: /* cmd ::= DROP INDEX ifexists fullname */
+{sqlite3DropIndex(pParse, yymsp[0].minor.yy373, yymsp[-1].minor.yy46);}
+ break;
+ case 245: /* cmd ::= VACUUM */
+ case 246: /* cmd ::= VACUUM nm */
+{sqlite3Vacuum(pParse);}
+ break;
+ case 247: /* cmd ::= PRAGMA nm dbnm EQ nmnum */
+{sqlite3Pragma(pParse,&yymsp[-3].minor.yy410,&yymsp[-2].minor.yy410,&yymsp[0].minor.yy410,0);}
+ break;
+ case 248: /* cmd ::= PRAGMA nm dbnm EQ ON */
+ case 249: /* cmd ::= PRAGMA nm dbnm EQ DELETE */
+{sqlite3Pragma(pParse,&yymsp[-3].minor.yy410,&yymsp[-2].minor.yy410,&yymsp[0].minor.yy0,0);}
+ break;
+ case 250: /* cmd ::= PRAGMA nm dbnm EQ minus_num */
+{
+ sqlite3Pragma(pParse,&yymsp[-3].minor.yy410,&yymsp[-2].minor.yy410,&yymsp[0].minor.yy410,1);
+}
+ break;
+ case 251: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */
+{sqlite3Pragma(pParse,&yymsp[-4].minor.yy410,&yymsp[-3].minor.yy410,&yymsp[-1].minor.yy410,0);}
+ break;
+ case 252: /* cmd ::= PRAGMA nm dbnm */
+{sqlite3Pragma(pParse,&yymsp[-1].minor.yy410,&yymsp[0].minor.yy410,0,0);}
+ break;
+ case 260: /* cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list END */
+{
+ Token all;
+ all.z = yymsp[-3].minor.yy410.z;
+ all.n = (yymsp[0].minor.yy0.z - yymsp[-3].minor.yy410.z) + yymsp[0].minor.yy0.n;
+ sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy243, &all);
+}
+ break;
+ case 261: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+{
+ sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy410, &yymsp[-6].minor.yy410, yymsp[-5].minor.yy46, yymsp[-4].minor.yy370.a, yymsp[-4].minor.yy370.b, yymsp[-2].minor.yy373, yymsp[0].minor.yy172, yymsp[-10].minor.yy46, yymsp[-8].minor.yy46);
+ yygotominor.yy410 = (yymsp[-6].minor.yy410.n==0?yymsp[-7].minor.yy410:yymsp[-6].minor.yy410);
+}
+ break;
+ case 262: /* trigger_time ::= BEFORE */
+ case 265: /* trigger_time ::= */
+{ yygotominor.yy46 = TK_BEFORE; }
+ break;
+ case 263: /* trigger_time ::= AFTER */
+{ yygotominor.yy46 = TK_AFTER; }
+ break;
+ case 264: /* trigger_time ::= INSTEAD OF */
+{ yygotominor.yy46 = TK_INSTEAD;}
+ break;
+ case 266: /* trigger_event ::= DELETE|INSERT */
+ case 267: /* trigger_event ::= UPDATE */
+{yygotominor.yy370.a = yymsp[0].major; yygotominor.yy370.b = 0;}
+ break;
+ case 268: /* trigger_event ::= UPDATE OF inscollist */
+{yygotominor.yy370.a = TK_UPDATE; yygotominor.yy370.b = yymsp[0].minor.yy432;}
+ break;
+ case 271: /* when_clause ::= */
+ case 288: /* key_opt ::= */
+{ yygotominor.yy172 = 0; }
+ break;
+ case 272: /* when_clause ::= WHEN expr */
+ case 289: /* key_opt ::= KEY expr */
+{ yygotominor.yy172 = yymsp[0].minor.yy172; }
+ break;
+ case 273: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+{
+ if( yymsp[-2].minor.yy243 ){
+ yymsp[-2].minor.yy243->pLast->pNext = yymsp[-1].minor.yy243;
+ }else{
+ yymsp[-2].minor.yy243 = yymsp[-1].minor.yy243;
+ }
+ yymsp[-2].minor.yy243->pLast = yymsp[-1].minor.yy243;
+ yygotominor.yy243 = yymsp[-2].minor.yy243;
+}
+ break;
+ case 274: /* trigger_cmd_list ::= */
+{ yygotominor.yy243 = 0; }
+ break;
+ case 275: /* trigger_cmd ::= UPDATE orconf nm SET setlist where_opt */
+{ yygotominor.yy243 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-3].minor.yy410, yymsp[-1].minor.yy174, yymsp[0].minor.yy172, yymsp[-4].minor.yy46); }
+ break;
+ case 276: /* trigger_cmd ::= insert_cmd INTO nm inscollist_opt VALUES LP itemlist RP */
+{yygotominor.yy243 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-5].minor.yy410, yymsp[-4].minor.yy432, yymsp[-1].minor.yy174, 0, yymsp[-7].minor.yy46);}
+ break;
+ case 277: /* trigger_cmd ::= insert_cmd INTO nm inscollist_opt select */
+{yygotominor.yy243 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy410, yymsp[-1].minor.yy432, 0, yymsp[0].minor.yy219, yymsp[-4].minor.yy46);}
+ break;
+ case 278: /* trigger_cmd ::= DELETE FROM nm where_opt */
+{yygotominor.yy243 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-1].minor.yy410, yymsp[0].minor.yy172);}
+ break;
+ case 279: /* trigger_cmd ::= select */
+{yygotominor.yy243 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy219); }
+ break;
+ case 280: /* expr ::= RAISE LP IGNORE RP */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0);
+ if( yygotominor.yy172 ){
+ yygotominor.yy172->iColumn = OE_Ignore;
+ sqlite3ExprSpan(yygotominor.yy172, &yymsp[-3].minor.yy0, &yymsp[0].minor.yy0);
+ }
+}
+ break;
+ case 281: /* expr ::= RAISE LP raisetype COMMA nm RP */
+{
+ yygotominor.yy172 = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &yymsp[-1].minor.yy410);
+ if( yygotominor.yy172 ) {
+ yygotominor.yy172->iColumn = yymsp[-3].minor.yy46;
+ sqlite3ExprSpan(yygotominor.yy172, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0);
+ }
+}
+ break;
+ case 282: /* raisetype ::= ROLLBACK */
+{yygotominor.yy46 = OE_Rollback;}
+ break;
+ case 284: /* raisetype ::= FAIL */
+{yygotominor.yy46 = OE_Fail;}
+ break;
+ case 285: /* cmd ::= DROP TRIGGER ifexists fullname */
+{
+ sqlite3DropTrigger(pParse,yymsp[0].minor.yy373,yymsp[-1].minor.yy46);
+}
+ break;
+ case 286: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+{
+ sqlite3Attach(pParse, yymsp[-3].minor.yy172, yymsp[-1].minor.yy172, yymsp[0].minor.yy172);
+}
+ break;
+ case 287: /* cmd ::= DETACH database_kw_opt expr */
+{
+ sqlite3Detach(pParse, yymsp[0].minor.yy172);
+}
+ break;
+ case 292: /* cmd ::= REINDEX */
+{sqlite3Reindex(pParse, 0, 0);}
+ break;
+ case 293: /* cmd ::= REINDEX nm dbnm */
+{sqlite3Reindex(pParse, &yymsp[-1].minor.yy410, &yymsp[0].minor.yy410);}
+ break;
+ case 294: /* cmd ::= ANALYZE */
+{sqlite3Analyze(pParse, 0, 0);}
+ break;
+ case 295: /* cmd ::= ANALYZE nm dbnm */
+{sqlite3Analyze(pParse, &yymsp[-1].minor.yy410, &yymsp[0].minor.yy410);}
+ break;
+ case 296: /* cmd ::= ALTER TABLE fullname RENAME TO nm */
+{
+ sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy373,&yymsp[0].minor.yy410);
+}
+ break;
+ case 297: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column */
+{
+ sqlite3AlterFinishAddColumn(pParse, &yymsp[0].minor.yy410);
+}
+ break;
+ case 298: /* add_column_fullname ::= fullname */
+{
+ sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy373);
+}
+ break;
+ case 301: /* cmd ::= create_vtab */
+{sqlite3VtabFinishParse(pParse,0);}
+ break;
+ case 302: /* cmd ::= create_vtab LP vtabarglist RP */
+{sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);}
+ break;
+ case 303: /* create_vtab ::= CREATE VIRTUAL TABLE nm dbnm USING nm */
+{
+ sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy410, &yymsp[-2].minor.yy410, &yymsp[0].minor.yy410);
+}
+ break;
+ case 306: /* vtabarg ::= */
+{sqlite3VtabArgInit(pParse);}
+ break;
+ case 308: /* vtabargtoken ::= ANY */
+ case 309: /* vtabargtoken ::= lp anylist RP */
+ case 310: /* lp ::= LP */
+ case 312: /* anylist ::= anylist ANY */
+{sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);}
+ break;
+ };
+ yygoto = yyRuleInfo[yyruleno].lhs;
+ yysize = yyRuleInfo[yyruleno].nrhs;
+ yypParser->yyidx -= yysize;
+ yyact = yy_find_reduce_action(yymsp[-yysize].stateno,yygoto);
+ if( yyact < YYNSTATE ){
+#ifdef NDEBUG
+ /* If we are not debugging and the reduce action popped at least
+ ** one element off the stack, then we can push the new element back
+ ** onto the stack here, and skip the stack overflow test in yy_shift().
+ ** That gives a significant speed improvement. */
+ if( yysize ){
+ yypParser->yyidx++;
+ yymsp -= yysize-1;
+ yymsp->stateno = yyact;
+ yymsp->major = yygoto;
+ yymsp->minor = yygotominor;
+ }else
+#endif
+ {
+ yy_shift(yypParser,yyact,yygoto,&yygotominor);
+ }
+ }else{
+ assert( yyact == YYNSTATE + YYNRULE + 1 );
+ yy_accept(yypParser);
+ }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+static void yy_parse_failed(
+ yyParser *yypParser /* The parser */
+){
+ sqlite3ParserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser fails */
+ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+ yyParser *yypParser, /* The parser */
+ int yymajor, /* The major type of the error token */
+ YYMINORTYPE yyminor /* The minor type of the error token */
+){
+ sqlite3ParserARG_FETCH;
+#define TOKEN (yyminor.yy0)
+
+ assert( TOKEN.z[0] ); /* The tokenizer always gives us a token */
+ sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
+ pParse->parseError = 1;
+ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ sqlite3ParserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser accepts */
+ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "sqlite3ParserAlloc" which describes the current state of the parser.
+** The second argument is the major token number. The third is
+** the minor token. The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+SQLITE_PRIVATE void sqlite3Parser(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ sqlite3ParserTOKENTYPE yyminor /* The value for the token */
+ sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */
+){
+ YYMINORTYPE yyminorunion;
+ int yyact; /* The parser action. */
+ int yyendofinput; /* True if we are at the end of input */
+#ifdef YYERRORSYMBOL
+ int yyerrorhit = 0; /* True if yymajor has invoked an error */
+#endif
+ yyParser *yypParser; /* The parser */
+
+ /* (re)initialize the parser, if necessary */
+ yypParser = (yyParser*)yyp;
+ if( yypParser->yyidx<0 ){
+#if YYSTACKDEPTH<=0
+ if( yypParser->yystksz <=0 ){
+ /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/
+ yyminorunion = yyzerominor;
+ yyStackOverflow(yypParser, &yyminorunion);
+ return;
+ }
+#endif
+ yypParser->yyidx = 0;
+ yypParser->yyerrcnt = -1;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+ }
+ yyminorunion.yy0 = yyminor;
+ yyendofinput = (yymajor==0);
+ sqlite3ParserARG_STORE;
+
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+
+ do{
+ yyact = yy_find_shift_action(yypParser,yymajor);
+ if( yyact<YYNSTATE ){
+ assert( !yyendofinput ); /* Impossible to shift the $ token */
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ yymajor = YYNOCODE;
+ }else if( yyact < YYNSTATE + YYNRULE ){
+ yy_reduce(yypParser,yyact-YYNSTATE);
+ }else{
+ assert( yyact == YY_ERROR_ACTION );
+#ifdef YYERRORSYMBOL
+ int yymx;
+#endif
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+ }
+#endif
+#ifdef YYERRORSYMBOL
+ /* A syntax error has occurred.
+ ** The response to an error depends upon whether or not the
+ ** grammar defines an error token "ERROR".
+ **
+ ** This is what we do if the grammar does define ERROR:
+ **
+ ** * Call the %syntax_error function.
+ **
+ ** * Begin popping the stack until we enter a state where
+ ** it is legal to shift the error symbol, then shift
+ ** the error symbol.
+ **
+ ** * Set the error count to three.
+ **
+ ** * Begin accepting and shifting new tokens. No new error
+ ** processing will occur until three tokens have been
+ ** shifted successfully.
+ **
+ */
+ if( yypParser->yyerrcnt<0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yymx = yypParser->yystack[yypParser->yyidx].major;
+ if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+ yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+ yy_destructor(yymajor,&yyminorunion);
+ yymajor = YYNOCODE;
+ }else{
+ while(
+ yypParser->yyidx >= 0 &&
+ yymx != YYERRORSYMBOL &&
+ (yyact = yy_find_reduce_action(
+ yypParser->yystack[yypParser->yyidx].stateno,
+ YYERRORSYMBOL)) >= YYNSTATE
+ ){
+ yy_pop_parser_stack(yypParser);
+ }
+ if( yypParser->yyidx < 0 || yymajor==0 ){
+ yy_destructor(yymajor,&yyminorunion);
+ yy_parse_failed(yypParser);
+ yymajor = YYNOCODE;
+ }else if( yymx!=YYERRORSYMBOL ){
+ YYMINORTYPE u2;
+ u2.YYERRSYMDT = 0;
+ yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+ }
+ }
+ yypParser->yyerrcnt = 3;
+ yyerrorhit = 1;
+#else /* YYERRORSYMBOL is not defined */
+ /* This is what we do if the grammar does not define ERROR:
+ **
+ ** * Report an error message, and throw away the input token.
+ **
+ ** * If the input token is $, then fail the parse.
+ **
+ ** As before, subsequent error messages are suppressed until
+ ** three input tokens have been successfully shifted.
+ */
+ if( yypParser->yyerrcnt<=0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yypParser->yyerrcnt = 3;
+ yy_destructor(yymajor,&yyminorunion);
+ if( yyendofinput ){
+ yy_parse_failed(yypParser);
+ }
+ yymajor = YYNOCODE;
+#endif
+ }
+ }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+ return;
+}
+
+/************** End of parse.c ***********************************************/
+/************** Begin file tokenize.c ****************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** An tokenizer for SQL
+**
+** This file contains C code that splits an SQL input string up into
+** individual tokens and sends those tokens one-by-one over to the
+** parser for analysis.
+**
+** $Id: tokenize.c,v 1.142 2008/04/28 18:46:43 drh Exp $
+*/
+
+/*
+** The charMap() macro maps alphabetic characters into their
+** lower-case ASCII equivalent. On ASCII machines, this is just
+** an upper-to-lower case map. On EBCDIC machines we also need
+** to adjust the encoding. Only alphabetic characters and underscores
+** need to be translated.
+*/
+#ifdef SQLITE_ASCII
+# define charMap(X) sqlite3UpperToLower[(unsigned char)X]
+#endif
+#ifdef SQLITE_EBCDIC
+# define charMap(X) ebcdicToAscii[(unsigned char)X]
+const unsigned char ebcdicToAscii[] = {
+/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, /* 6x */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7x */
+ 0, 97, 98, 99,100,101,102,103,104,105, 0, 0, 0, 0, 0, 0, /* 8x */
+ 0,106,107,108,109,110,111,112,113,114, 0, 0, 0, 0, 0, 0, /* 9x */
+ 0, 0,115,116,117,118,119,120,121,122, 0, 0, 0, 0, 0, 0, /* Ax */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Bx */
+ 0, 97, 98, 99,100,101,102,103,104,105, 0, 0, 0, 0, 0, 0, /* Cx */
+ 0,106,107,108,109,110,111,112,113,114, 0, 0, 0, 0, 0, 0, /* Dx */
+ 0, 0,115,116,117,118,119,120,121,122, 0, 0, 0, 0, 0, 0, /* Ex */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Fx */
+};
+#endif
+
+/*
+** The sqlite3KeywordCode function looks up an identifier to determine if
+** it is a keyword. If it is a keyword, the token code of that keyword is
+** returned. If the input is not a keyword, TK_ID is returned.
+**
+** The implementation of this routine was generated by a program,
+** mkkeywordhash.h, located in the tool subdirectory of the distribution.
+** The output of the mkkeywordhash.c program is written into a file
+** named keywordhash.h and then included into this source file by
+** the #include below.
+*/
+/************** Include keywordhash.h in the middle of tokenize.c ************/
+/************** Begin file keywordhash.h *************************************/
+/***** This file contains automatically generated code ******
+**
+** The code in this file has been automatically generated by
+**
+** $Header: /sqlite/sqlite/tool/mkkeywordhash.c,v 1.31 2007/07/30 18:26:20 rse Exp $
+**
+** The code in this file implements a function that determines whether
+** or not a given identifier is really an SQL keyword. The same thing
+** might be implemented more directly using a hand-written hash table.
+** But by using this automatically generated code, the size of the code
+** is substantially reduced. This is important for embedded applications
+** on platforms with limited memory.
+*/
+/* Hash score: 165 */
+static int keywordCode(const char *z, int n){
+ /* zText[] encodes 775 bytes of keywords in 526 bytes */
+ static const char zText[526] =
+ "BEFOREIGNOREGEXPLAINSTEADDESCAPEACHECKEYCONSTRAINTERSECTABLEFT"
+ "HENDATABASELECTRANSACTIONATURALTERAISELSEXCEPTRIGGEREFERENCES"
+ "UNIQUERYATTACHAVINGROUPDATEMPORARYBEGINNEREINDEXCLUSIVEXISTSBETWEEN"
+ "OTNULLIKECASCADEFERRABLECASECOLLATECREATECURRENT_DATEDELETEDETACH"
+ "IMMEDIATEJOINSERTMATCHPLANALYZEPRAGMABORTVALUESVIRTUALIMITWHEN"
+ "WHERENAMEAFTEREPLACEANDEFAULTAUTOINCREMENTCASTCOLUMNCOMMITCONFLICT"
+ "CROSSCURRENT_TIMESTAMPRIMARYDEFERREDISTINCTDROPFAILFROMFULLGLOB"
+ "YIFINTOFFSETISNULLORDERESTRICTOUTERIGHTROLLBACKROWUNIONUSINGVACUUM"
+ "VIEWINITIALLY";
+ static const unsigned char aHash[127] = {
+ 63, 92, 109, 61, 0, 38, 0, 0, 69, 0, 64, 0, 0,
+ 102, 4, 65, 7, 0, 108, 72, 103, 99, 0, 22, 0, 0,
+ 113, 0, 111, 106, 0, 18, 80, 0, 1, 0, 0, 56, 57,
+ 0, 55, 11, 0, 33, 77, 89, 0, 110, 88, 0, 0, 45,
+ 0, 90, 54, 0, 20, 0, 114, 34, 19, 0, 10, 97, 28,
+ 83, 0, 0, 116, 93, 47, 115, 41, 12, 44, 0, 78, 0,
+ 87, 29, 0, 86, 0, 0, 0, 82, 79, 84, 75, 96, 6,
+ 14, 95, 0, 68, 0, 21, 76, 98, 27, 0, 112, 67, 104,
+ 49, 40, 71, 0, 0, 81, 100, 0, 107, 0, 15, 0, 0,
+ 24, 0, 73, 42, 50, 0, 16, 48, 0, 37,
+ };
+ static const unsigned char aNext[116] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0,
+ 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0,
+ 17, 0, 0, 0, 36, 39, 0, 0, 25, 0, 0, 31, 0,
+ 0, 0, 43, 52, 0, 0, 0, 53, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 51, 0, 0, 0, 0, 26, 0, 8, 46,
+ 2, 0, 0, 0, 0, 0, 0, 0, 3, 58, 66, 0, 13,
+ 0, 91, 85, 0, 94, 0, 74, 0, 0, 62, 0, 35, 101,
+ 0, 0, 105, 23, 30, 60, 70, 0, 0, 59, 0, 0,
+ };
+ static const unsigned char aLen[116] = {
+ 6, 7, 3, 6, 6, 7, 7, 3, 4, 6, 4, 5, 3,
+ 10, 9, 5, 4, 4, 3, 8, 2, 6, 11, 2, 7, 5,
+ 5, 4, 6, 7, 10, 6, 5, 6, 6, 5, 6, 4, 9,
+ 2, 5, 5, 7, 5, 9, 6, 7, 7, 3, 4, 4, 7,
+ 3, 10, 4, 7, 6, 12, 6, 6, 9, 4, 6, 5, 4,
+ 7, 6, 5, 6, 7, 5, 4, 5, 6, 5, 7, 3, 7,
+ 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 8, 8,
+ 2, 4, 4, 4, 4, 4, 2, 2, 4, 6, 2, 3, 6,
+ 5, 8, 5, 5, 8, 3, 5, 5, 6, 4, 9, 3,
+ };
+ static const unsigned short int aOffset[116] = {
+ 0, 2, 2, 6, 10, 13, 18, 23, 25, 26, 31, 33, 37,
+ 40, 47, 55, 58, 61, 63, 65, 70, 71, 76, 85, 86, 91,
+ 95, 99, 102, 107, 113, 123, 126, 131, 136, 141, 144, 148, 148,
+ 152, 157, 160, 164, 166, 169, 177, 183, 189, 189, 192, 195, 199,
+ 200, 204, 214, 218, 225, 231, 243, 249, 255, 264, 266, 272, 277,
+ 279, 286, 291, 296, 302, 308, 313, 317, 320, 326, 330, 337, 339,
+ 346, 348, 350, 359, 363, 369, 375, 383, 388, 388, 404, 411, 418,
+ 419, 426, 430, 434, 438, 442, 445, 447, 449, 452, 452, 455, 458,
+ 464, 468, 476, 480, 485, 493, 496, 501, 506, 512, 516, 521,
+ };
+ static const unsigned char aCode[116] = {
+ TK_BEFORE, TK_FOREIGN, TK_FOR, TK_IGNORE, TK_LIKE_KW,
+ TK_EXPLAIN, TK_INSTEAD, TK_ADD, TK_DESC, TK_ESCAPE,
+ TK_EACH, TK_CHECK, TK_KEY, TK_CONSTRAINT, TK_INTERSECT,
+ TK_TABLE, TK_JOIN_KW, TK_THEN, TK_END, TK_DATABASE,
+ TK_AS, TK_SELECT, TK_TRANSACTION,TK_ON, TK_JOIN_KW,
+ TK_ALTER, TK_RAISE, TK_ELSE, TK_EXCEPT, TK_TRIGGER,
+ TK_REFERENCES, TK_UNIQUE, TK_QUERY, TK_ATTACH, TK_HAVING,
+ TK_GROUP, TK_UPDATE, TK_TEMP, TK_TEMP, TK_OR,
+ TK_BEGIN, TK_JOIN_KW, TK_REINDEX, TK_INDEX, TK_EXCLUSIVE,
+ TK_EXISTS, TK_BETWEEN, TK_NOTNULL, TK_NOT, TK_NULL,
+ TK_LIKE_KW, TK_CASCADE, TK_ASC, TK_DEFERRABLE, TK_CASE,
+ TK_COLLATE, TK_CREATE, TK_CTIME_KW, TK_DELETE, TK_DETACH,
+ TK_IMMEDIATE, TK_JOIN, TK_INSERT, TK_MATCH, TK_PLAN,
+ TK_ANALYZE, TK_PRAGMA, TK_ABORT, TK_VALUES, TK_VIRTUAL,
+ TK_LIMIT, TK_WHEN, TK_WHERE, TK_RENAME, TK_AFTER,
+ TK_REPLACE, TK_AND, TK_DEFAULT, TK_AUTOINCR, TK_TO,
+ TK_IN, TK_CAST, TK_COLUMNKW, TK_COMMIT, TK_CONFLICT,
+ TK_JOIN_KW, TK_CTIME_KW, TK_CTIME_KW, TK_PRIMARY, TK_DEFERRED,
+ TK_DISTINCT, TK_IS, TK_DROP, TK_FAIL, TK_FROM,
+ TK_JOIN_KW, TK_LIKE_KW, TK_BY, TK_IF, TK_INTO,
+ TK_OFFSET, TK_OF, TK_SET, TK_ISNULL, TK_ORDER,
+ TK_RESTRICT, TK_JOIN_KW, TK_JOIN_KW, TK_ROLLBACK, TK_ROW,
+ TK_UNION, TK_USING, TK_VACUUM, TK_VIEW, TK_INITIALLY,
+ TK_ALL,
+ };
+ int h, i;
+ if( n<2 ) return TK_ID;
+ h = ((charMap(z[0])*4) ^
+ (charMap(z[n-1])*3) ^
+ n) % 127;
+ for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){
+ if( aLen[i]==n && sqlite3StrNICmp(&zText[aOffset[i]],z,n)==0 ){
+ return aCode[i];
+ }
+ }
+ return TK_ID;
+}
+SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char *z, int n){
+ return keywordCode((char*)z, n);
+}
+
+/************** End of keywordhash.h *****************************************/
+/************** Continuing where we left off in tokenize.c *******************/
+
+
+/*
+** If X is a character that can be used in an identifier then
+** IdChar(X) will be true. Otherwise it is false.
+**
+** For ASCII, any character with the high-order bit set is
+** allowed in an identifier. For 7-bit characters,
+** sqlite3IsIdChar[X] must be 1.
+**
+** For EBCDIC, the rules are more complex but have the same
+** end result.
+**
+** Ticket #1066. the SQL standard does not allow '$' in the
+** middle of identfiers. But many SQL implementations do.
+** SQLite will allow '$' in identifiers for compatibility.
+** But the feature is undocumented.
+*/
+#ifdef SQLITE_ASCII
+SQLITE_PRIVATE const char sqlite3IsAsciiIdChar[] = {
+/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */
+};
+#define IdChar(C) (((c=C)&0x80)!=0 || (c>0x1f && sqlite3IsAsciiIdChar[c-0x20]))
+#endif
+#ifdef SQLITE_EBCDIC
+SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[] = {
+/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 4x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, /* 5x */
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, /* 6x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, /* 7x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, /* 8x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, /* 9x */
+ 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, /* Ax */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Bx */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Cx */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Dx */
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Ex */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* Fx */
+};
+#define IdChar(C) (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40]))
+#endif
+
+
+/*
+** Return the length of the token that begins at z[0].
+** Store the token type in *tokenType before returning.
+*/
+SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
+ int i, c;
+ switch( *z ){
+ case ' ': case '\t': case '\n': case '\f': case '\r': {
+ for(i=1; isspace(z[i]); i++){}
+ *tokenType = TK_SPACE;
+ return i;
+ }
+ case '-': {
+ if( z[1]=='-' ){
+ for(i=2; (c=z[i])!=0 && c!='\n'; i++){}
+ *tokenType = TK_COMMENT;
+ return i;
+ }
+ *tokenType = TK_MINUS;
+ return 1;
+ }
+ case '(': {
+ *tokenType = TK_LP;
+ return 1;
+ }
+ case ')': {
+ *tokenType = TK_RP;
+ return 1;
+ }
+ case ';': {
+ *tokenType = TK_SEMI;
+ return 1;
+ }
+ case '+': {
+ *tokenType = TK_PLUS;
+ return 1;
+ }
+ case '*': {
+ *tokenType = TK_STAR;
+ return 1;
+ }
+ case '/': {
+ if( z[1]!='*' || z[2]==0 ){
+ *tokenType = TK_SLASH;
+ return 1;
+ }
+ for(i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){}
+ if( c ) i++;
+ *tokenType = TK_COMMENT;
+ return i;
+ }
+ case '%': {
+ *tokenType = TK_REM;
+ return 1;
+ }
+ case '=': {
+ *tokenType = TK_EQ;
+ return 1 + (z[1]=='=');
+ }
+ case '<': {
+ if( (c=z[1])=='=' ){
+ *tokenType = TK_LE;
+ return 2;
+ }else if( c=='>' ){
+ *tokenType = TK_NE;
+ return 2;
+ }else if( c=='<' ){
+ *tokenType = TK_LSHIFT;
+ return 2;
+ }else{
+ *tokenType = TK_LT;
+ return 1;
+ }
+ }
+ case '>': {
+ if( (c=z[1])=='=' ){
+ *tokenType = TK_GE;
+ return 2;
+ }else if( c=='>' ){
+ *tokenType = TK_RSHIFT;
+ return 2;
+ }else{
+ *tokenType = TK_GT;
+ return 1;
+ }
+ }
+ case '!': {
+ if( z[1]!='=' ){
+ *tokenType = TK_ILLEGAL;
+ return 2;
+ }else{
+ *tokenType = TK_NE;
+ return 2;
+ }
+ }
+ case '|': {
+ if( z[1]!='|' ){
+ *tokenType = TK_BITOR;
+ return 1;
+ }else{
+ *tokenType = TK_CONCAT;
+ return 2;
+ }
+ }
+ case ',': {
+ *tokenType = TK_COMMA;
+ return 1;
+ }
+ case '&': {
+ *tokenType = TK_BITAND;
+ return 1;
+ }
+ case '~': {
+ *tokenType = TK_BITNOT;
+ return 1;
+ }
+ case '`':
+ case '\'':
+ case '"': {
+ int delim = z[0];
+ for(i=1; (c=z[i])!=0; i++){
+ if( c==delim ){
+ if( z[i+1]==delim ){
+ i++;
+ }else{
+ break;
+ }
+ }
+ }
+ if( c ){
+ *tokenType = TK_STRING;
+ return i+1;
+ }else{
+ *tokenType = TK_ILLEGAL;
+ return i;
+ }
+ }
+ case '.': {
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ if( !isdigit(z[1]) )
+#endif
+ {
+ *tokenType = TK_DOT;
+ return 1;
+ }
+ /* If the next character is a digit, this is a floating point
+ ** number that begins with ".". Fall thru into the next case */
+ }
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9': {
+ *tokenType = TK_INTEGER;
+ for(i=0; isdigit(z[i]); i++){}
+#ifndef SQLITE_OMIT_FLOATING_POINT
+ if( z[i]=='.' ){
+ i++;
+ while( isdigit(z[i]) ){ i++; }
+ *tokenType = TK_FLOAT;
+ }
+ if( (z[i]=='e' || z[i]=='E') &&
+ ( isdigit(z[i+1])
+ || ((z[i+1]=='+' || z[i+1]=='-') && isdigit(z[i+2]))
+ )
+ ){
+ i += 2;
+ while( isdigit(z[i]) ){ i++; }
+ *tokenType = TK_FLOAT;
+ }
+#endif
+ while( IdChar(z[i]) ){
+ *tokenType = TK_ILLEGAL;
+ i++;
+ }
+ return i;
+ }
+ case '[': {
+ for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){}
+ *tokenType = c==']' ? TK_ID : TK_ILLEGAL;
+ return i;
+ }
+ case '?': {
+ *tokenType = TK_VARIABLE;
+ for(i=1; isdigit(z[i]); i++){}
+ return i;
+ }
+ case '#': {
+ for(i=1; isdigit(z[i]); i++){}
+ if( i>1 ){
+ /* Parameters of the form #NNN (where NNN is a number) are used
+ ** internally by sqlite3NestedParse. */
+ *tokenType = TK_REGISTER;
+ return i;
+ }
+ /* Fall through into the next case if the '#' is not followed by
+ ** a digit. Try to match #AAAA where AAAA is a parameter name. */
+ }
+#ifndef SQLITE_OMIT_TCL_VARIABLE
+ case '$':
+#endif
+ case '@': /* For compatibility with MS SQL Server */
+ case ':': {
+ int n = 0;
+ *tokenType = TK_VARIABLE;
+ for(i=1; (c=z[i])!=0; i++){
+ if( IdChar(c) ){
+ n++;
+#ifndef SQLITE_OMIT_TCL_VARIABLE
+ }else if( c=='(' && n>0 ){
+ do{
+ i++;
+ }while( (c=z[i])!=0 && !isspace(c) && c!=')' );
+ if( c==')' ){
+ i++;
+ }else{
+ *tokenType = TK_ILLEGAL;
+ }
+ break;
+ }else if( c==':' && z[i+1]==':' ){
+ i++;
+#endif
+ }else{
+ break;
+ }
+ }
+ if( n==0 ) *tokenType = TK_ILLEGAL;
+ return i;
+ }
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+ case 'x': case 'X': {
+ if( z[1]=='\'' ){
+ *tokenType = TK_BLOB;
+ for(i=2; (c=z[i])!=0 && c!='\''; i++){
+ if( !isxdigit(c) ){
+ *tokenType = TK_ILLEGAL;
+ }
+ }
+ if( i%2 || !c ) *tokenType = TK_ILLEGAL;
+ if( c ) i++;
+ return i;
+ }
+ /* Otherwise fall through to the next case */
+ }
+#endif
+ default: {
+ if( !IdChar(*z) ){
+ break;
+ }
+ for(i=1; IdChar(z[i]); i++){}
+ *tokenType = keywordCode((char*)z, i);
+ return i;
+ }
+ }
+ *tokenType = TK_ILLEGAL;
+ return 1;
+}
+
+/*
+** Run the parser on the given SQL string. The parser structure is
+** passed in. An SQLITE_ status code is returned. If an error occurs
+** and pzErrMsg!=NULL then an error message might be written into
+** memory obtained from sqlite3_malloc() and *pzErrMsg made to point to that
+** error message. Or maybe not.
+*/
+SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
+ int nErr = 0;
+ int i;
+ void *pEngine;
+ int tokenType;
+ int lastTokenParsed = -1;
+ sqlite3 *db = pParse->db;
+ int mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH];
+
+ if( db->activeVdbeCnt==0 ){
+ db->u1.isInterrupted = 0;
+ }
+ pParse->rc = SQLITE_OK;
+ pParse->zTail = pParse->zSql = zSql;
+ i = 0;
+ pEngine = sqlite3ParserAlloc((void*(*)(size_t))sqlite3_malloc);
+ if( pEngine==0 ){
+ db->mallocFailed = 1;
+ return SQLITE_NOMEM;
+ }
+ assert( pParse->sLastToken.dyn==0 );
+ assert( pParse->pNewTable==0 );
+ assert( pParse->pNewTrigger==0 );
+ assert( pParse->nVar==0 );
+ assert( pParse->nVarExpr==0 );
+ assert( pParse->nVarExprAlloc==0 );
+ assert( pParse->apVarExpr==0 );
+ while( !db->mallocFailed && zSql[i]!=0 ){
+ assert( i>=0 );
+ pParse->sLastToken.z = (u8*)&zSql[i];
+ assert( pParse->sLastToken.dyn==0 );
+ pParse->sLastToken.n = sqlite3GetToken((unsigned char*)&zSql[i],&tokenType);
+ i += pParse->sLastToken.n;
+ if( i>mxSqlLen ){
+ pParse->rc = SQLITE_TOOBIG;
+ break;
+ }
+ switch( tokenType ){
+ case TK_SPACE:
+ case TK_COMMENT: {
+ if( db->u1.isInterrupted ){
+ pParse->rc = SQLITE_INTERRUPT;
+ sqlite3SetString(pzErrMsg, "interrupt", (char*)0);
+ goto abort_parse;
+ }
+ break;
+ }
+ case TK_ILLEGAL: {
+ if( pzErrMsg ){
+ sqlite3_free(*pzErrMsg);
+ *pzErrMsg = sqlite3MPrintf(db, "unrecognized token: \"%T\"",
+ &pParse->sLastToken);
+ }
+ nErr++;
+ goto abort_parse;
+ }
+ case TK_SEMI: {
+ pParse->zTail = &zSql[i];
+ /* Fall thru into the default case */
+ }
+ default: {
+ sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse);
+ lastTokenParsed = tokenType;
+ if( pParse->rc!=SQLITE_OK ){
+ goto abort_parse;
+ }
+ break;
+ }
+ }
+ }
+abort_parse:
+ if( zSql[i]==0 && nErr==0 && pParse->rc==SQLITE_OK ){
+ if( lastTokenParsed!=TK_SEMI ){
+ sqlite3Parser(pEngine, TK_SEMI, pParse->sLastToken, pParse);
+ pParse->zTail = &zSql[i];
+ }
+ sqlite3Parser(pEngine, 0, pParse->sLastToken, pParse);
+ }
+ sqlite3ParserFree(pEngine, sqlite3_free);
+ if( db->mallocFailed ){
+ pParse->rc = SQLITE_NOMEM;
+ }
+ if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){
+ sqlite3SetString(&pParse->zErrMsg, sqlite3ErrStr(pParse->rc), (char*)0);
+ }
+ if( pParse->zErrMsg ){
+ if( pzErrMsg && *pzErrMsg==0 ){
+ *pzErrMsg = pParse->zErrMsg;
+ }else{
+ sqlite3_free(pParse->zErrMsg);
+ }
+ pParse->zErrMsg = 0;
+ nErr++;
+ }
+ if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){
+ sqlite3VdbeDelete(pParse->pVdbe);
+ pParse->pVdbe = 0;
+ }
+#ifndef SQLITE_OMIT_SHARED_CACHE
+ if( pParse->nested==0 ){
+ sqlite3_free(pParse->aTableLock);
+ pParse->aTableLock = 0;
+ pParse->nTableLock = 0;
+ }
+#endif
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ sqlite3_free(pParse->apVtabLock);
+#endif
+
+ if( !IN_DECLARE_VTAB ){
+ /* If the pParse->declareVtab flag is set, do not delete any table
+ ** structure built up in pParse->pNewTable. The calling code (see vtab.c)
+ ** will take responsibility for freeing the Table structure.
+ */
+ sqlite3DeleteTable(pParse->pNewTable);
+ }
+
+ sqlite3DeleteTrigger(pParse->pNewTrigger);
+ sqlite3_free(pParse->apVarExpr);
+ if( nErr>0 && (pParse->rc==SQLITE_OK || pParse->rc==SQLITE_DONE) ){
+ pParse->rc = SQLITE_ERROR;
+ }
+ return nErr;
+}
+
+/************** End of tokenize.c ********************************************/
+/************** Begin file complete.c ****************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** An tokenizer for SQL
+**
+** This file contains C code that implements the sqlite3_complete() API.
+** This code used to be part of the tokenizer.c source file. But by
+** separating it out, the code will be automatically omitted from
+** static links that do not use it.
+**
+** $Id: complete.c,v 1.6 2007/08/27 23:26:59 drh Exp $
+*/
+#ifndef SQLITE_OMIT_COMPLETE
+
+/*
+** This is defined in tokenize.c. We just have to import the definition.
+*/
+#ifndef SQLITE_AMALGAMATION
+#ifdef SQLITE_ASCII
+SQLITE_PRIVATE const char sqlite3IsAsciiIdChar[];
+#define IdChar(C) (((c=C)&0x80)!=0 || (c>0x1f && sqlite3IsAsciiIdChar[c-0x20]))
+#endif
+#ifdef SQLITE_EBCDIC
+SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[];
+#define IdChar(C) (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40]))
+#endif
+#endif /* SQLITE_AMALGAMATION */
+
+
+/*
+** Token types used by the sqlite3_complete() routine. See the header
+** comments on that procedure for additional information.
+*/
+#define tkSEMI 0
+#define tkWS 1
+#define tkOTHER 2
+#define tkEXPLAIN 3
+#define tkCREATE 4
+#define tkTEMP 5
+#define tkTRIGGER 6
+#define tkEND 7
+
+/*
+** Return TRUE if the given SQL string ends in a semicolon.
+**
+** Special handling is require for CREATE TRIGGER statements.
+** Whenever the CREATE TRIGGER keywords are seen, the statement
+** must end with ";END;".
+**
+** This implementation uses a state machine with 7 states:
+**
+** (0) START At the beginning or end of an SQL statement. This routine
+** returns 1 if it ends in the START state and 0 if it ends
+** in any other state.
+**
+** (1) NORMAL We are in the middle of statement which ends with a single
+** semicolon.
+**
+** (2) EXPLAIN The keyword EXPLAIN has been seen at the beginning of
+** a statement.
+**
+** (3) CREATE The keyword CREATE has been seen at the beginning of a
+** statement, possibly preceeded by EXPLAIN and/or followed by
+** TEMP or TEMPORARY
+**
+** (4) TRIGGER We are in the middle of a trigger definition that must be
+** ended by a semicolon, the keyword END, and another semicolon.
+**
+** (5) SEMI We've seen the first semicolon in the ";END;" that occurs at
+** the end of a trigger definition.
+**
+** (6) END We've seen the ";END" of the ";END;" that occurs at the end
+** of a trigger difinition.
+**
+** Transitions between states above are determined by tokens extracted
+** from the input. The following tokens are significant:
+**
+** (0) tkSEMI A semicolon.
+** (1) tkWS Whitespace
+** (2) tkOTHER Any other SQL token.
+** (3) tkEXPLAIN The "explain" keyword.
+** (4) tkCREATE The "create" keyword.
+** (5) tkTEMP The "temp" or "temporary" keyword.
+** (6) tkTRIGGER The "trigger" keyword.
+** (7) tkEND The "end" keyword.
+**
+** Whitespace never causes a state transition and is always ignored.
+**
+** If we compile with SQLITE_OMIT_TRIGGER, all of the computation needed
+** to recognize the end of a trigger can be omitted. All we have to do
+** is look for a semicolon that is not part of an string or comment.
+*/
+SQLITE_API int sqlite3_complete(const char *zSql){
+ u8 state = 0; /* Current state, using numbers defined in header comment */
+ u8 token; /* Value of the next token */
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* A complex statement machine used to detect the end of a CREATE TRIGGER
+ ** statement. This is the normal case.
+ */
+ static const u8 trans[7][8] = {
+ /* Token: */
+ /* State: ** SEMI WS OTHER EXPLAIN CREATE TEMP TRIGGER END */
+ /* 0 START: */ { 0, 0, 1, 2, 3, 1, 1, 1, },
+ /* 1 NORMAL: */ { 0, 1, 1, 1, 1, 1, 1, 1, },
+ /* 2 EXPLAIN: */ { 0, 2, 1, 1, 3, 1, 1, 1, },
+ /* 3 CREATE: */ { 0, 3, 1, 1, 1, 3, 4, 1, },
+ /* 4 TRIGGER: */ { 5, 4, 4, 4, 4, 4, 4, 4, },
+ /* 5 SEMI: */ { 5, 5, 4, 4, 4, 4, 4, 6, },
+ /* 6 END: */ { 0, 6, 4, 4, 4, 4, 4, 4, },
+ };
+#else
+ /* If triggers are not suppored by this compile then the statement machine
+ ** used to detect the end of a statement is much simplier
+ */
+ static const u8 trans[2][3] = {
+ /* Token: */
+ /* State: ** SEMI WS OTHER */
+ /* 0 START: */ { 0, 0, 1, },
+ /* 1 NORMAL: */ { 0, 1, 1, },
+ };
+#endif /* SQLITE_OMIT_TRIGGER */
+
+ while( *zSql ){
+ switch( *zSql ){
+ case ';': { /* A semicolon */
+ token = tkSEMI;
+ break;
+ }
+ case ' ':
+ case '\r':
+ case '\t':
+ case '\n':
+ case '\f': { /* White space is ignored */
+ token = tkWS;
+ break;
+ }
+ case '/': { /* C-style comments */
+ if( zSql[1]!='*' ){
+ token = tkOTHER;
+ break;
+ }
+ zSql += 2;
+ while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; }
+ if( zSql[0]==0 ) return 0;
+ zSql++;
+ token = tkWS;
+ break;
+ }
+ case '-': { /* SQL-style comments from "--" to end of line */
+ if( zSql[1]!='-' ){
+ token = tkOTHER;
+ break;
+ }
+ while( *zSql && *zSql!='\n' ){ zSql++; }
+ if( *zSql==0 ) return state==0;
+ token = tkWS;
+ break;
+ }
+ case '[': { /* Microsoft-style identifiers in [...] */
+ zSql++;
+ while( *zSql && *zSql!=']' ){ zSql++; }
+ if( *zSql==0 ) return 0;
+ token = tkOTHER;
+ break;
+ }
+ case '`': /* Grave-accent quoted symbols used by MySQL */
+ case '"': /* single- and double-quoted strings */
+ case '\'': {
+ int c = *zSql;
+ zSql++;
+ while( *zSql && *zSql!=c ){ zSql++; }
+ if( *zSql==0 ) return 0;
+ token = tkOTHER;
+ break;
+ }
+ default: {
+ int c;
+ if( IdChar((u8)*zSql) ){
+ /* Keywords and unquoted identifiers */
+ int nId;
+ for(nId=1; IdChar(zSql[nId]); nId++){}
+#ifdef SQLITE_OMIT_TRIGGER
+ token = tkOTHER;
+#else
+ switch( *zSql ){
+ case 'c': case 'C': {
+ if( nId==6 && sqlite3StrNICmp(zSql, "create", 6)==0 ){
+ token = tkCREATE;
+ }else{
+ token = tkOTHER;
+ }
+ break;
+ }
+ case 't': case 'T': {
+ if( nId==7 && sqlite3StrNICmp(zSql, "trigger", 7)==0 ){
+ token = tkTRIGGER;
+ }else if( nId==4 && sqlite3StrNICmp(zSql, "temp", 4)==0 ){
+ token = tkTEMP;
+ }else if( nId==9 && sqlite3StrNICmp(zSql, "temporary", 9)==0 ){
+ token = tkTEMP;
+ }else{
+ token = tkOTHER;
+ }
+ break;
+ }
+ case 'e': case 'E': {
+ if( nId==3 && sqlite3StrNICmp(zSql, "end", 3)==0 ){
+ token = tkEND;
+ }else
+#ifndef SQLITE_OMIT_EXPLAIN
+ if( nId==7 && sqlite3StrNICmp(zSql, "explain", 7)==0 ){
+ token = tkEXPLAIN;
+ }else
+#endif
+ {
+ token = tkOTHER;
+ }
+ break;
+ }
+ default: {
+ token = tkOTHER;
+ break;
+ }
+ }
+#endif /* SQLITE_OMIT_TRIGGER */
+ zSql += nId-1;
+ }else{
+ /* Operators and special symbols */
+ token = tkOTHER;
+ }
+ break;
+ }
+ }
+ state = trans[state][token];
+ zSql++;
+ }
+ return state==0;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** This routine is the same as the sqlite3_complete() routine described
+** above, except that the parameter is required to be UTF-16 encoded, not
+** UTF-8.
+*/
+SQLITE_API int sqlite3_complete16(const void *zSql){
+ sqlite3_value *pVal;
+ char const *zSql8;
+ int rc = SQLITE_NOMEM;
+
+ pVal = sqlite3ValueNew(0);
+ sqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zSql8 = sqlite3ValueText(pVal, SQLITE_UTF8);
+ if( zSql8 ){
+ rc = sqlite3_complete(zSql8);
+ }
+ sqlite3ValueFree(pVal);
+ return sqlite3ApiExit(0, rc);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+#endif /* SQLITE_OMIT_COMPLETE */
+
+/************** End of complete.c ********************************************/
+/************** Begin file main.c ********************************************/
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Main file for the SQLite library. The routines in this file
+** implement the programmer interface to the library. Routines in
+** other files are for internal use by SQLite and should not be
+** accessed by users of the library.
+**
+** $Id: main.c,v 1.439 2008/05/13 13:27:34 drh Exp $
+*/
+#ifdef SQLITE_ENABLE_FTS3
+/************** Include fts3.h in the middle of main.c ***********************/
+/************** Begin file fts3.h ********************************************/
+/*
+** 2006 Oct 10
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This header file is used by programs that want to link against the
+** FTS3 library. All it does is declare the sqlite3Fts3Init() interface.
+*/
+
+#if 0
+extern "C" {
+#endif /* __cplusplus */
+
+SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db);
+
+#if 0
+} /* extern "C" */
+#endif /* __cplusplus */
+
+/************** End of fts3.h ************************************************/
+/************** Continuing where we left off in main.c ***********************/
+#endif
+
+/*
+** The version of the library
+*/
+SQLITE_API const char sqlite3_version[] = SQLITE_VERSION;
+SQLITE_API const char *sqlite3_libversion(void){ return sqlite3_version; }
+SQLITE_API int sqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; }
+SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; }
+
+#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
+/*
+** If the following function pointer is not NULL and if
+** SQLITE_ENABLE_IOTRACE is enabled, then messages describing
+** I/O active are written using this function. These messages
+** are intended for debugging activity only.
+*/
+SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*, ...) = 0;
+#endif
+
+/*
+** If the following global variable points to a string which is the
+** name of a directory, then that directory will be used to store
+** temporary files.
+**
+** See also the "PRAGMA temp_store_directory" SQL command.
+*/
+SQLITE_API char *sqlite3_temp_directory = 0;
+
+/*
+** Routine needed to support the testcase() macro.
+*/
+#ifdef SQLITE_COVERAGE_TEST
+SQLITE_PRIVATE void sqlite3Coverage(int x){
+ static int dummy = 0;
+ dummy += x;
+}
+#endif
+
+
+/*
+** Return true if the buffer z[0..n-1] contains all spaces.
+*/
+static int allSpaces(const char *z, int n){
+ while( n>0 && z[n-1]==' ' ){ n--; }
+ return n==0;
+}
+
+/*
+** This is the default collating function named "BINARY" which is always
+** available.
+**
+** If the padFlag argument is not NULL then space padding at the end
+** of strings is ignored. This implements the RTRIM collation.
+*/
+static int binCollFunc(
+ void *padFlag,
+ int nKey1, const void *pKey1,
+ int nKey2, const void *pKey2
+){
+ int rc, n;
+ n = nKey1<nKey2 ? nKey1 : nKey2;
+ rc = memcmp(pKey1, pKey2, n);
+ if( rc==0 ){
+ if( padFlag
+ && allSpaces(((char*)pKey1)+n, nKey1-n)
+ && allSpaces(((char*)pKey2)+n, nKey2-n)
+ ){
+ /* Leave rc unchanged at 0 */
+ }else{
+ rc = nKey1 - nKey2;
+ }
+ }
+ return rc;
+}
+
+/*
+** Another built-in collating sequence: NOCASE.
+**
+** This collating sequence is intended to be used for "case independant
+** comparison". SQLite's knowledge of upper and lower case equivalents
+** extends only to the 26 characters used in the English language.
+**
+** At the moment there is only a UTF-8 implementation.
+*/
+static int nocaseCollatingFunc(
+ void *NotUsed,
+ int nKey1, const void *pKey1,
+ int nKey2, const void *pKey2
+){
+ int r = sqlite3StrNICmp(
+ (const char *)pKey1, (const char *)pKey2, (nKey1<nKey2)?nKey1:nKey2);
+ if( 0==r ){
+ r = nKey1-nKey2;
+ }
+ return r;
+}
+
+/*
+** Return the ROWID of the most recent insert
+*/
+SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){
+ return db->lastRowid;
+}
+
+/*
+** Return the number of changes in the most recent call to sqlite3_exec().
+*/
+SQLITE_API int sqlite3_changes(sqlite3 *db){
+ return db->nChange;
+}
+
+/*
+** Return the number of changes since the database handle was opened.
+*/
+SQLITE_API int sqlite3_total_changes(sqlite3 *db){
+ return db->nTotalChange;
+}
+
+/*
+** Close an existing SQLite database
+*/
+SQLITE_API int sqlite3_close(sqlite3 *db){
+ HashElem *i;
+ int j;
+
+ if( !db ){
+ return SQLITE_OK;
+ }
+ if( !sqlite3SafetyCheckSickOrOk(db) ){
+ return SQLITE_MISUSE;
+ }
+ sqlite3_mutex_enter(db->mutex);
+
+#ifdef SQLITE_SSE
+ {
+ extern void sqlite3SseCleanup(sqlite3*);
+ sqlite3SseCleanup(db);
+ }
+#endif
+
+ sqlite3ResetInternalSchema(db, 0);
+
+ /* If a transaction is open, the ResetInternalSchema() call above
+ ** will not have called the xDisconnect() method on any virtual
+ ** tables in the db->aVTrans[] array. The following sqlite3VtabRollback()
+ ** call will do so. We need to do this before the check for active
+ ** SQL statements below, as the v-table implementation may be storing
+ ** some prepared statements internally.
+ */
+ sqlite3VtabRollback(db);
+
+ /* If there are any outstanding VMs, return SQLITE_BUSY. */
+ if( db->pVdbe ){
+ sqlite3Error(db, SQLITE_BUSY,
+ "Unable to close due to unfinalised statements");
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_BUSY;
+ }
+ assert( sqlite3SafetyCheckSickOrOk(db) );
+
+ for(j=0; j<db->nDb; j++){
+ struct Db *pDb = &db->aDb[j];
+ if( pDb->pBt ){
+ sqlite3BtreeClose(pDb->pBt);
+ pDb->pBt = 0;
+ if( j!=1 ){
+ pDb->pSchema = 0;
+ }
+ }
+ }
+ sqlite3ResetInternalSchema(db, 0);
+ assert( db->nDb<=2 );
+ assert( db->aDb==db->aDbStatic );
+ for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){
+ FuncDef *pFunc, *pNext;
+ for(pFunc = (FuncDef*)sqliteHashData(i); pFunc; pFunc=pNext){
+ pNext = pFunc->pNext;
+ sqlite3_free(pFunc);
+ }
+ }
+
+ for(i=sqliteHashFirst(&db->aCollSeq); i; i=sqliteHashNext(i)){
+ CollSeq *pColl = (CollSeq *)sqliteHashData(i);
+ /* Invoke any destructors registered for collation sequence user data. */
+ for(j=0; j<3; j++){
+ if( pColl[j].xDel ){
+ pColl[j].xDel(pColl[j].pUser);
+ }
+ }
+ sqlite3_free(pColl);
+ }
+ sqlite3HashClear(&db->aCollSeq);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ for(i=sqliteHashFirst(&db->aModule); i; i=sqliteHashNext(i)){
+ Module *pMod = (Module *)sqliteHashData(i);
+ if( pMod->xDestroy ){
+ pMod->xDestroy(pMod->pAux);
+ }
+ sqlite3_free(pMod);
+ }
+ sqlite3HashClear(&db->aModule);
+#endif
+
+ sqlite3HashClear(&db->aFunc);
+ sqlite3Error(db, SQLITE_OK, 0); /* Deallocates any cached error strings. */
+ if( db->pErr ){
+ sqlite3ValueFree(db->pErr);
+ }
+ sqlite3CloseExtensions(db);
+
+ db->magic = SQLITE_MAGIC_ERROR;
+
+ /* The temp-database schema is allocated differently from the other schema
+ ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()).
+ ** So it needs to be freed here. Todo: Why not roll the temp schema into
+ ** the same sqliteMalloc() as the one that allocates the database
+ ** structure?
+ */
+ sqlite3_free(db->aDb[1].pSchema);
+ sqlite3_mutex_leave(db->mutex);
+ db->magic = SQLITE_MAGIC_CLOSED;
+ sqlite3_mutex_free(db->mutex);
+ sqlite3_free(db);
+ return SQLITE_OK;
+}
+
+/*
+** Rollback all database files.
+*/
+SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db){
+ int i;
+ int inTrans = 0;
+ assert( sqlite3_mutex_held(db->mutex) );
+ sqlite3FaultBeginBenign(SQLITE_FAULTINJECTOR_MALLOC);
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt ){
+ if( sqlite3BtreeIsInTrans(db->aDb[i].pBt) ){
+ inTrans = 1;
+ }
+ sqlite3BtreeRollback(db->aDb[i].pBt);
+ db->aDb[i].inTrans = 0;
+ }
+ }
+ sqlite3VtabRollback(db);
+ sqlite3FaultEndBenign(SQLITE_FAULTINJECTOR_MALLOC);
+
+ if( db->flags&SQLITE_InternChanges ){
+ sqlite3ExpirePreparedStatements(db);
+ sqlite3ResetInternalSchema(db, 0);
+ }
+
+ /* If one has been configured, invoke the rollback-hook callback */
+ if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){
+ db->xRollbackCallback(db->pRollbackArg);
+ }
+}
+
+/*
+** Return a static string that describes the kind of error specified in the
+** argument.
+*/
+SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){
+ const char *z;
+ switch( rc & 0xff ){
+ case SQLITE_ROW:
+ case SQLITE_DONE:
+ case SQLITE_OK: z = "not an error"; break;
+ case SQLITE_ERROR: z = "SQL logic error or missing database"; break;
+ case SQLITE_PERM: z = "access permission denied"; break;
+ case SQLITE_ABORT: z = "callback requested query abort"; break;
+ case SQLITE_BUSY: z = "database is locked"; break;
+ case SQLITE_LOCKED: z = "database table is locked"; break;
+ case SQLITE_NOMEM: z = "out of memory"; break;
+ case SQLITE_READONLY: z = "attempt to write a readonly database"; break;
+ case SQLITE_INTERRUPT: z = "interrupted"; break;
+ case SQLITE_IOERR: z = "disk I/O error"; break;
+ case SQLITE_CORRUPT: z = "database disk image is malformed"; break;
+ case SQLITE_FULL: z = "database or disk is full"; break;
+ case SQLITE_CANTOPEN: z = "unable to open database file"; break;
+ case SQLITE_EMPTY: z = "table contains no data"; break;
+ case SQLITE_SCHEMA: z = "database schema has changed"; break;
+ case SQLITE_TOOBIG: z = "String or BLOB exceeded size limit"; break;
+ case SQLITE_CONSTRAINT: z = "constraint failed"; break;
+ case SQLITE_MISMATCH: z = "datatype mismatch"; break;
+ case SQLITE_MISUSE: z = "library routine called out of sequence";break;
+ case SQLITE_NOLFS: z = "large file support is disabled"; break;
+ case SQLITE_AUTH: z = "authorization denied"; break;
+ case SQLITE_FORMAT: z = "auxiliary database format error"; break;
+ case SQLITE_RANGE: z = "bind or column index out of range"; break;
+ case SQLITE_NOTADB: z = "file is encrypted or is not a database";break;
+ default: z = "unknown error"; break;
+ }
+ return z;
+}
+
+/*
+** This routine implements a busy callback that sleeps and tries
+** again until a timeout value is reached. The timeout value is
+** an integer number of milliseconds passed in as the first
+** argument.
+*/
+static int sqliteDefaultBusyCallback(
+ void *ptr, /* Database connection */
+ int count /* Number of times table has been busy */
+){
+#if OS_WIN || (defined(HAVE_USLEEP) && HAVE_USLEEP)
+ static const u8 delays[] =
+ { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
+ static const u8 totals[] =
+ { 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 };
+# define NDELAY (sizeof(delays)/sizeof(delays[0]))
+ sqlite3 *db = (sqlite3 *)ptr;
+ int timeout = db->busyTimeout;
+ int delay, prior;
+
+ assert( count>=0 );
+ if( count < NDELAY ){
+ delay = delays[count];
+ prior = totals[count];
+ }else{
+ delay = delays[NDELAY-1];
+ prior = totals[NDELAY-1] + delay*(count-(NDELAY-1));
+ }
+ if( prior + delay > timeout ){
+ delay = timeout - prior;
+ if( delay<=0 ) return 0;
+ }
+ sqlite3OsSleep(db->pVfs, delay*1000);
+ return 1;
+#else
+ sqlite3 *db = (sqlite3 *)ptr;
+ int timeout = ((sqlite3 *)ptr)->busyTimeout;
+ if( (count+1)*1000 > timeout ){
+ return 0;
+ }
+ sqlite3OsSleep(db->pVfs, 1000000);
+ return 1;
+#endif
+}
+
+/*
+** Invoke the given busy handler.
+**
+** This routine is called when an operation failed with a lock.
+** If this routine returns non-zero, the lock is retried. If it
+** returns 0, the operation aborts with an SQLITE_BUSY error.
+*/
+SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler *p){
+ int rc;
+ if( p==0 || p->xFunc==0 || p->nBusy<0 ) return 0;
+ rc = p->xFunc(p->pArg, p->nBusy);
+ if( rc==0 ){
+ p->nBusy = -1;
+ }else{
+ p->nBusy++;
+ }
+ return rc;
+}
+
+/*
+** This routine sets the busy callback for an Sqlite database to the
+** given callback function with the given argument.
+*/
+SQLITE_API int sqlite3_busy_handler(
+ sqlite3 *db,
+ int (*xBusy)(void*,int),
+ void *pArg
+){
+ sqlite3_mutex_enter(db->mutex);
+ db->busyHandler.xFunc = xBusy;
+ db->busyHandler.pArg = pArg;
+ db->busyHandler.nBusy = 0;
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_OK;
+}
+
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+/*
+** This routine sets the progress callback for an Sqlite database to the
+** given callback function with the given argument. The progress callback will
+** be invoked every nOps opcodes.
+*/
+SQLITE_API void sqlite3_progress_handler(
+ sqlite3 *db,
+ int nOps,
+ int (*xProgress)(void*),
+ void *pArg
+){
+ if( sqlite3SafetyCheckOk(db) ){
+ sqlite3_mutex_enter(db->mutex);
+ if( nOps>0 ){
+ db->xProgress = xProgress;
+ db->nProgressOps = nOps;
+ db->pProgressArg = pArg;
+ }else{
+ db->xProgress = 0;
+ db->nProgressOps = 0;
+ db->pProgressArg = 0;
+ }
+ sqlite3_mutex_leave(db->mutex);
+ }
+}
+#endif
+
+
+/*
+** This routine installs a default busy handler that waits for the
+** specified number of milliseconds before returning 0.
+*/
+SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){
+ if( ms>0 ){
+ db->busyTimeout = ms;
+ sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db);
+ }else{
+ sqlite3_busy_handler(db, 0, 0);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Cause any pending operation to stop at its earliest opportunity.
+*/
+SQLITE_API void sqlite3_interrupt(sqlite3 *db){
+ if( sqlite3SafetyCheckOk(db) ){
+ db->u1.isInterrupted = 1;
+ }
+}
+
+
+/*
+** This function is exactly the same as sqlite3_create_function(), except
+** that it is designed to be called by internal code. The difference is
+** that if a malloc() fails in sqlite3_create_function(), an error code
+** is returned and the mallocFailed flag cleared.
+*/
+SQLITE_PRIVATE int sqlite3CreateFunc(
+ sqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int enc,
+ void *pUserData,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value **),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value **),
+ void (*xFinal)(sqlite3_context*)
+){
+ FuncDef *p;
+ int nName;
+
+ assert( sqlite3_mutex_held(db->mutex) );
+ if( zFunctionName==0 ||
+ (xFunc && (xFinal || xStep)) ||
+ (!xFunc && (xFinal && !xStep)) ||
+ (!xFunc && (!xFinal && xStep)) ||
+ (nArg<-1 || nArg>127) ||
+ (255<(nName = strlen(zFunctionName))) ){
+ sqlite3Error(db, SQLITE_ERROR, "bad parameters");
+ return SQLITE_ERROR;
+ }
+
+#ifndef SQLITE_OMIT_UTF16
+ /* If SQLITE_UTF16 is specified as the encoding type, transform this
+ ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the
+ ** SQLITE_UTF16NATIVE macro. SQLITE_UTF16 is not used internally.
+ **
+ ** If SQLITE_ANY is specified, add three versions of the function
+ ** to the hash table.
+ */
+ if( enc==SQLITE_UTF16 ){
+ enc = SQLITE_UTF16NATIVE;
+ }else if( enc==SQLITE_ANY ){
+ int rc;
+ rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF8,
+ pUserData, xFunc, xStep, xFinal);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF16LE,
+ pUserData, xFunc, xStep, xFinal);
+ }
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ enc = SQLITE_UTF16BE;
+ }
+#else
+ enc = SQLITE_UTF8;
+#endif
+
+ /* Check if an existing function is being overridden or deleted. If so,
+ ** and there are active VMs, then return SQLITE_BUSY. If a function
+ ** is being overridden/deleted but there are no active VMs, allow the
+ ** operation to continue but invalidate all precompiled statements.
+ */
+ p = sqlite3FindFunction(db, zFunctionName, nName, nArg, enc, 0);
+ if( p && p->iPrefEnc==enc && p->nArg==nArg ){
+ if( db->activeVdbeCnt ){
+ sqlite3Error(db, SQLITE_BUSY,
+ "Unable to delete/modify user-function due to active statements");
+ assert( !db->mallocFailed );
+ return SQLITE_BUSY;
+ }else{
+ sqlite3ExpirePreparedStatements(db);
+ }
+ }
+
+ p = sqlite3FindFunction(db, zFunctionName, nName, nArg, enc, 1);
+ assert(p || db->mallocFailed);
+ if( !p ){
+ return SQLITE_NOMEM;
+ }
+ p->flags = 0;
+ p->xFunc = xFunc;
+ p->xStep = xStep;
+ p->xFinalize = xFinal;
+ p->pUserData = pUserData;
+ p->nArg = nArg;
+ return SQLITE_OK;
+}
+
+/*
+** Create new user functions.
+*/
+SQLITE_API int sqlite3_create_function(
+ sqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int enc,
+ void *p,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value **),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value **),
+ void (*xFinal)(sqlite3_context*)
+){
+ int rc;
+ sqlite3_mutex_enter(db->mutex);
+ rc = sqlite3CreateFunc(db, zFunctionName, nArg, enc, p, xFunc, xStep, xFinal);
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_API int sqlite3_create_function16(
+ sqlite3 *db,
+ const void *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *p,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+){
+ int rc;
+ char *zFunc8;
+ sqlite3_mutex_enter(db->mutex);
+ assert( !db->mallocFailed );
+ zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1);
+ rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xFunc, xStep, xFinal);
+ sqlite3_free(zFunc8);
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+#endif
+
+
+/*
+** Declare that a function has been overloaded by a virtual table.
+**
+** If the function already exists as a regular global function, then
+** this routine is a no-op. If the function does not exist, then create
+** a new one that always throws a run-time error.
+**
+** When virtual tables intend to provide an overloaded function, they
+** should call this routine to make sure the global function exists.
+** A global function must exist in order for name resolution to work
+** properly.
+*/
+SQLITE_API int sqlite3_overload_function(
+ sqlite3 *db,
+ const char *zName,
+ int nArg
+){
+ int nName = strlen(zName);
+ int rc;
+ sqlite3_mutex_enter(db->mutex);
+ if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){
+ sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8,
+ 0, sqlite3InvalidFunction, 0, 0);
+ }
+ rc = sqlite3ApiExit(db, SQLITE_OK);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_TRACE
+/*
+** Register a trace function. The pArg from the previously registered trace
+** is returned.
+**
+** A NULL trace function means that no tracing is executes. A non-NULL
+** trace is a pointer to a function that is invoked at the start of each
+** SQL statement.
+*/
+SQLITE_API void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){
+ void *pOld;
+ sqlite3_mutex_enter(db->mutex);
+ pOld = db->pTraceArg;
+ db->xTrace = xTrace;
+ db->pTraceArg = pArg;
+ sqlite3_mutex_leave(db->mutex);
+ return pOld;
+}
+/*
+** Register a profile function. The pArg from the previously registered
+** profile function is returned.
+**
+** A NULL profile function means that no profiling is executes. A non-NULL
+** profile is a pointer to a function that is invoked at the conclusion of
+** each SQL statement that is run.
+*/
+SQLITE_API void *sqlite3_profile(
+ sqlite3 *db,
+ void (*xProfile)(void*,const char*,sqlite_uint64),
+ void *pArg
+){
+ void *pOld;
+ sqlite3_mutex_enter(db->mutex);
+ pOld = db->pProfileArg;
+ db->xProfile = xProfile;
+ db->pProfileArg = pArg;
+ sqlite3_mutex_leave(db->mutex);
+ return pOld;
+}
+#endif /* SQLITE_OMIT_TRACE */
+
+/*** EXPERIMENTAL ***
+**
+** Register a function to be invoked when a transaction comments.
+** If the invoked function returns non-zero, then the commit becomes a
+** rollback.
+*/
+SQLITE_API void *sqlite3_commit_hook(
+ sqlite3 *db, /* Attach the hook to this database */
+ int (*xCallback)(void*), /* Function to invoke on each commit */
+ void *pArg /* Argument to the function */
+){
+ void *pOld;
+ sqlite3_mutex_enter(db->mutex);
+ pOld = db->pCommitArg;
+ db->xCommitCallback = xCallback;
+ db->pCommitArg = pArg;
+ sqlite3_mutex_leave(db->mutex);
+ return pOld;
+}
+
+/*
+** Register a callback to be invoked each time a row is updated,
+** inserted or deleted using this database connection.
+*/
+SQLITE_API void *sqlite3_update_hook(
+ sqlite3 *db, /* Attach the hook to this database */
+ void (*xCallback)(void*,int,char const *,char const *,sqlite_int64),
+ void *pArg /* Argument to the function */
+){
+ void *pRet;
+ sqlite3_mutex_enter(db->mutex);
+ pRet = db->pUpdateArg;
+ db->xUpdateCallback = xCallback;
+ db->pUpdateArg = pArg;
+ sqlite3_mutex_leave(db->mutex);
+ return pRet;
+}
+
+/*
+** Register a callback to be invoked each time a transaction is rolled
+** back by this database connection.
+*/
+SQLITE_API void *sqlite3_rollback_hook(
+ sqlite3 *db, /* Attach the hook to this database */
+ void (*xCallback)(void*), /* Callback function */
+ void *pArg /* Argument to the function */
+){
+ void *pRet;
+ sqlite3_mutex_enter(db->mutex);
+ pRet = db->pRollbackArg;
+ db->xRollbackCallback = xCallback;
+ db->pRollbackArg = pArg;
+ sqlite3_mutex_leave(db->mutex);
+ return pRet;
+}
+
+/*
+** This routine is called to create a connection to a database BTree
+** driver. If zFilename is the name of a file, then that file is
+** opened and used. If zFilename is the magic name ":memory:" then
+** the database is stored in memory (and is thus forgotten as soon as
+** the connection is closed.) If zFilename is NULL then the database
+** is a "virtual" database for transient use only and is deleted as
+** soon as the connection is closed.
+**
+** A virtual database can be either a disk file (that is automatically
+** deleted when the file is closed) or it an be held entirely in memory,
+** depending on the values of the TEMP_STORE compile-time macro and the
+** db->temp_store variable, according to the following chart:
+**
+** TEMP_STORE db->temp_store Location of temporary database
+** ---------- -------------- ------------------------------
+** 0 any file
+** 1 1 file
+** 1 2 memory
+** 1 0 file
+** 2 1 file
+** 2 2 memory
+** 2 0 memory
+** 3 any memory
+*/
+SQLITE_PRIVATE int sqlite3BtreeFactory(
+ const sqlite3 *db, /* Main database when opening aux otherwise 0 */
+ const char *zFilename, /* Name of the file containing the BTree database */
+ int omitJournal, /* if TRUE then do not journal this file */
+ int nCache, /* How many pages in the page cache */
+ int vfsFlags, /* Flags passed through to vfsOpen */
+ Btree **ppBtree /* Pointer to new Btree object written here */
+){
+ int btFlags = 0;
+ int rc;
+
+ assert( sqlite3_mutex_held(db->mutex) );
+ assert( ppBtree != 0);
+ if( omitJournal ){
+ btFlags |= BTREE_OMIT_JOURNAL;
+ }
+ if( db->flags & SQLITE_NoReadlock ){
+ btFlags |= BTREE_NO_READLOCK;
+ }
+ if( zFilename==0 ){
+#if TEMP_STORE==0
+ /* Do nothing */
+#endif
+#ifndef SQLITE_OMIT_MEMORYDB
+#if TEMP_STORE==1
+ if( db->temp_store==2 ) zFilename = ":memory:";
+#endif
+#if TEMP_STORE==2
+ if( db->temp_store!=1 ) zFilename = ":memory:";
+#endif
+#if TEMP_STORE==3
+ zFilename = ":memory:";
+#endif
+#endif /* SQLITE_OMIT_MEMORYDB */
+ }
+
+ if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (zFilename==0 || *zFilename==0) ){
+ vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB;
+ }
+ rc = sqlite3BtreeOpen(zFilename, (sqlite3 *)db, ppBtree, btFlags, vfsFlags);
+ if( rc==SQLITE_OK ){
+ sqlite3BtreeSetCacheSize(*ppBtree, nCache);
+ }
+ return rc;
+}
+
+/*
+** Return UTF-8 encoded English language explanation of the most recent
+** error.
+*/
+SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){
+ const char *z;
+ if( !db ){
+ return sqlite3ErrStr(SQLITE_NOMEM);
+ }
+ if( !sqlite3SafetyCheckSickOrOk(db) || db->errCode==SQLITE_MISUSE ){
+ return sqlite3ErrStr(SQLITE_MISUSE);
+ }
+ sqlite3_mutex_enter(db->mutex);
+ assert( !db->mallocFailed );
+ z = (char*)sqlite3_value_text(db->pErr);
+ if( z==0 ){
+ z = sqlite3ErrStr(db->errCode);
+ }
+ sqlite3_mutex_leave(db->mutex);
+ return z;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Return UTF-16 encoded English language explanation of the most recent
+** error.
+*/
+SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){
+ /* Because all the characters in the string are in the unicode
+ ** range 0x00-0xFF, if we pad the big-endian string with a
+ ** zero byte, we can obtain the little-endian string with
+ ** &big_endian[1].
+ */
+ static const char outOfMemBe[] = {
+ 0, 'o', 0, 'u', 0, 't', 0, ' ',
+ 0, 'o', 0, 'f', 0, ' ',
+ 0, 'm', 0, 'e', 0, 'm', 0, 'o', 0, 'r', 0, 'y', 0, 0, 0
+ };
+ static const char misuseBe [] = {
+ 0, 'l', 0, 'i', 0, 'b', 0, 'r', 0, 'a', 0, 'r', 0, 'y', 0, ' ',
+ 0, 'r', 0, 'o', 0, 'u', 0, 't', 0, 'i', 0, 'n', 0, 'e', 0, ' ',
+ 0, 'c', 0, 'a', 0, 'l', 0, 'l', 0, 'e', 0, 'd', 0, ' ',
+ 0, 'o', 0, 'u', 0, 't', 0, ' ',
+ 0, 'o', 0, 'f', 0, ' ',
+ 0, 's', 0, 'e', 0, 'q', 0, 'u', 0, 'e', 0, 'n', 0, 'c', 0, 'e', 0, 0, 0
+ };
+
+ const void *z;
+ if( !db ){
+ return (void *)(&outOfMemBe[SQLITE_UTF16NATIVE==SQLITE_UTF16LE?1:0]);
+ }
+ if( !sqlite3SafetyCheckSickOrOk(db) || db->errCode==SQLITE_MISUSE ){
+ return (void *)(&misuseBe[SQLITE_UTF16NATIVE==SQLITE_UTF16LE?1:0]);
+ }
+ sqlite3_mutex_enter(db->mutex);
+ assert( !db->mallocFailed );
+ z = sqlite3_value_text16(db->pErr);
+ if( z==0 ){
+ sqlite3ValueSetStr(db->pErr, -1, sqlite3ErrStr(db->errCode),
+ SQLITE_UTF8, SQLITE_STATIC);
+ z = sqlite3_value_text16(db->pErr);
+ }
+ sqlite3ApiExit(0, 0);
+ sqlite3_mutex_leave(db->mutex);
+ return z;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** Return the most recent error code generated by an SQLite routine. If NULL is
+** passed to this function, we assume a malloc() failed during sqlite3_open().
+*/
+SQLITE_API int sqlite3_errcode(sqlite3 *db){
+ if( db && !sqlite3SafetyCheckSickOrOk(db) ){
+ return SQLITE_MISUSE;
+ }
+ if( !db || db->mallocFailed ){
+ return SQLITE_NOMEM;
+ }
+ return db->errCode & db->errMask;
+}
+
+/*
+** Create a new collating function for database "db". The name is zName
+** and the encoding is enc.
+*/
+static int createCollation(
+ sqlite3* db,
+ const char *zName,
+ int enc,
+ void* pCtx,
+ int(*xCompare)(void*,int,const void*,int,const void*),
+ void(*xDel)(void*)
+){
+ CollSeq *pColl;
+ int enc2;
+
+ assert( sqlite3_mutex_held(db->mutex) );
+
+ /* If SQLITE_UTF16 is specified as the encoding type, transform this
+ ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the
+ ** SQLITE_UTF16NATIVE macro. SQLITE_UTF16 is not used internally.
+ */
+ enc2 = enc & ~SQLITE_UTF16_ALIGNED;
+ if( enc2==SQLITE_UTF16 ){
+ enc2 = SQLITE_UTF16NATIVE;
+ }
+
+ if( (enc2&~3)!=0 ){
+ sqlite3Error(db, SQLITE_ERROR, "unknown encoding");
+ return SQLITE_ERROR;
+ }
+
+ /* Check if this call is removing or replacing an existing collation
+ ** sequence. If so, and there are active VMs, return busy. If there
+ ** are no active VMs, invalidate any pre-compiled statements.
+ */
+ pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, strlen(zName), 0);
+ if( pColl && pColl->xCmp ){
+ if( db->activeVdbeCnt ){
+ sqlite3Error(db, SQLITE_BUSY,
+ "Unable to delete/modify collation sequence due to active statements");
+ return SQLITE_BUSY;
+ }
+ sqlite3ExpirePreparedStatements(db);
+
+ /* If collation sequence pColl was created directly by a call to
+ ** sqlite3_create_collation, and not generated by synthCollSeq(),
+ ** then any copies made by synthCollSeq() need to be invalidated.
+ ** Also, collation destructor - CollSeq.xDel() - function may need
+ ** to be called.
+ */
+ if( (pColl->enc & ~SQLITE_UTF16_ALIGNED)==enc2 ){
+ CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName, strlen(zName));
+ int j;
+ for(j=0; j<3; j++){
+ CollSeq *p = &aColl[j];
+ if( p->enc==pColl->enc ){
+ if( p->xDel ){
+ p->xDel(p->pUser);
+ }
+ p->xCmp = 0;
+ }
+ }
+ }
+ }
+
+ pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, strlen(zName), 1);
+ if( pColl ){
+ pColl->xCmp = xCompare;
+ pColl->pUser = pCtx;
+ pColl->xDel = xDel;
+ pColl->enc = enc2 | (enc & SQLITE_UTF16_ALIGNED);
+ }
+ sqlite3Error(db, SQLITE_OK, 0);
+ return SQLITE_OK;
+}
+
+
+/*
+** This array defines hard upper bounds on limit values. The
+** initializer must be kept in sync with the SQLITE_LIMIT_*
+** #defines in sqlite3.h.
+*/
+static const int aHardLimit[] = {
+ SQLITE_MAX_LENGTH,
+ SQLITE_MAX_SQL_LENGTH,
+ SQLITE_MAX_COLUMN,
+ SQLITE_MAX_EXPR_DEPTH,
+ SQLITE_MAX_COMPOUND_SELECT,
+ SQLITE_MAX_VDBE_OP,
+ SQLITE_MAX_FUNCTION_ARG,
+ SQLITE_MAX_ATTACHED,
+ SQLITE_MAX_LIKE_PATTERN_LENGTH,
+ SQLITE_MAX_VARIABLE_NUMBER,
+};
+
+/*
+** Make sure the hard limits are set to reasonable values
+*/
+#if SQLITE_MAX_LENGTH<100
+# error SQLITE_MAX_LENGTH must be at least 100
+#endif
+#if SQLITE_MAX_SQL_LENGTH<100
+# error SQLITE_MAX_SQL_LENGTH must be at least 100
+#endif
+#if SQLITE_MAX_SQL_LENGTH>SQLITE_MAX_LENGTH
+# error SQLITE_MAX_SQL_LENGTH must not be greater than SQLITE_MAX_LENGTH
+#endif
+#if SQLITE_MAX_COMPOUND_SELECT<2
+# error SQLITE_MAX_COMPOUND_SELECT must be at least 2
+#endif
+#if SQLITE_MAX_VDBE_OP<40
+# error SQLITE_MAX_VDBE_OP must be at least 40
+#endif
+#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>255
+# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 255
+#endif
+#if SQLITE_MAX_ATTACH<0 || SQLITE_MAX_ATTACH>30
+# error SQLITE_MAX_ATTACH must be between 0 and 30
+#endif
+#if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
+# error SQLITE_MAX_LIKE_PATTERN_LENGTH must be at least 1
+#endif
+#if SQLITE_MAX_VARIABLE_NUMBER<1
+# error SQLITE_MAX_VARIABLE_NUMBER must be at least 1
+#endif
+
+
+/*
+** Change the value of a limit. Report the old value.
+** If an invalid limit index is supplied, report -1.
+** Make no changes but still report the old value if the
+** new limit is negative.
+**
+** A new lower limit does not shrink existing constructs.
+** It merely prevents new constructs that exceed the limit
+** from forming.
+*/
+SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){
+ int oldLimit;
+ if( limitId<0 || limitId>=SQLITE_N_LIMIT ){
+ return -1;
+ }
+ oldLimit = db->aLimit[limitId];
+ if( newLimit>=0 ){
+ if( newLimit>aHardLimit[limitId] ){
+ newLimit = aHardLimit[limitId];
+ }
+ db->aLimit[limitId] = newLimit;
+ }
+ return oldLimit;
+}
+
+/*
+** This routine does the work of opening a database on behalf of
+** sqlite3_open() and sqlite3_open16(). The database filename "zFilename"
+** is UTF-8 encoded.
+*/
+static int openDatabase(
+ const char *zFilename, /* Database filename UTF-8 encoded */
+ sqlite3 **ppDb, /* OUT: Returned database handle */
+ unsigned flags, /* Operational flags */
+ const char *zVfs /* Name of the VFS to use */
+){
+ sqlite3 *db;
+ int rc;
+ CollSeq *pColl;
+
+ /* Remove harmful bits from the flags parameter */
+ flags &= ~( SQLITE_OPEN_DELETEONCLOSE |
+ SQLITE_OPEN_MAIN_DB |
+ SQLITE_OPEN_TEMP_DB |
+ SQLITE_OPEN_TRANSIENT_DB |
+ SQLITE_OPEN_MAIN_JOURNAL |
+ SQLITE_OPEN_TEMP_JOURNAL |
+ SQLITE_OPEN_SUBJOURNAL |
+ SQLITE_OPEN_MASTER_JOURNAL
+ );
+
+ /* Allocate the sqlite data structure */
+ db = sqlite3MallocZero( sizeof(sqlite3) );
+ if( db==0 ) goto opendb_out;
+ db->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE);
+ if( db->mutex==0 ){
+ sqlite3_free(db);
+ db = 0;
+ goto opendb_out;
+ }
+ sqlite3_mutex_enter(db->mutex);
+ db->errMask = 0xff;
+ db->priorNewRowid = 0;
+ db->nDb = 2;
+ db->magic = SQLITE_MAGIC_BUSY;
+ db->aDb = db->aDbStatic;
+ assert( sizeof(db->aLimit)==sizeof(aHardLimit) );
+ memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit));
+ db->autoCommit = 1;
+ db->nextAutovac = -1;
+ db->nextPagesize = 0;
+ db->flags |= SQLITE_ShortColNames
+#if SQLITE_DEFAULT_FILE_FORMAT<4
+ | SQLITE_LegacyFileFmt
+#endif
+#ifdef SQLITE_ENABLE_LOAD_EXTENSION
+ | SQLITE_LoadExtension
+#endif
+ ;
+ sqlite3HashInit(&db->aFunc, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&db->aCollSeq, SQLITE_HASH_STRING, 0);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ sqlite3HashInit(&db->aModule, SQLITE_HASH_STRING, 0);
+#endif
+
+ db->pVfs = sqlite3_vfs_find(zVfs);
+ if( !db->pVfs ){
+ rc = SQLITE_ERROR;
+ db->magic = SQLITE_MAGIC_SICK;
+ sqlite3Error(db, rc, "no such vfs: %s", zVfs);
+ goto opendb_out;
+ }
+
+ /* Add the default collation sequence BINARY. BINARY works for both UTF-8
+ ** and UTF-16, so add a version for each to avoid any unnecessary
+ ** conversions. The only error that can occur here is a malloc() failure.
+ */
+ createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0);
+ createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0);
+ createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0);
+ createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0);
+ if( db->mallocFailed ){
+ db->magic = SQLITE_MAGIC_SICK;
+ goto opendb_out;
+ }
+ db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 6, 0);
+ assert( db->pDfltColl!=0 );
+
+ /* Also add a UTF-8 case-insensitive collation sequence. */
+ createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
+
+ /* Set flags on the built-in collating sequences */
+ db->pDfltColl->type = SQLITE_COLL_BINARY;
+ pColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "NOCASE", 6, 0);
+ if( pColl ){
+ pColl->type = SQLITE_COLL_NOCASE;
+ }
+
+ /* Open the backend database driver */
+ db->openFlags = flags;
+ rc = sqlite3BtreeFactory(db, zFilename, 0, SQLITE_DEFAULT_CACHE_SIZE,
+ flags | SQLITE_OPEN_MAIN_DB,
+ &db->aDb[0].pBt);
+ if( rc!=SQLITE_OK ){
+ sqlite3Error(db, rc, 0);
+ db->magic = SQLITE_MAGIC_SICK;
+ goto opendb_out;
+ }
+ db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
+ db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
+
+
+ /* The default safety_level for the main database is 'full'; for the temp
+ ** database it is 'NONE'. This matches the pager layer defaults.
+ */
+ db->aDb[0].zName = "main";
+ db->aDb[0].safety_level = 3;
+#ifndef SQLITE_OMIT_TEMPDB
+ db->aDb[1].zName = "temp";
+ db->aDb[1].safety_level = 1;
+#endif
+
+ db->magic = SQLITE_MAGIC_OPEN;
+ if( db->mallocFailed ){
+ goto opendb_out;
+ }
+
+ /* Register all built-in functions, but do not attempt to read the
+ ** database schema yet. This is delayed until the first time the database
+ ** is accessed.
+ */
+ sqlite3Error(db, SQLITE_OK, 0);
+ sqlite3RegisterBuiltinFunctions(db);
+
+ /* Load automatic extensions - extensions that have been registered
+ ** using the sqlite3_automatic_extension() API.
+ */
+ (void)sqlite3AutoLoadExtensions(db);
+ if( sqlite3_errcode(db)!=SQLITE_OK ){
+ goto opendb_out;
+ }
+
+#ifdef SQLITE_ENABLE_FTS1
+ if( !db->mallocFailed ){
+ extern int sqlite3Fts1Init(sqlite3*);
+ rc = sqlite3Fts1Init(db);
+ }
+#endif
+
+#ifdef SQLITE_ENABLE_FTS2
+ if( !db->mallocFailed && rc==SQLITE_OK ){
+ extern int sqlite3Fts2Init(sqlite3*);
+ rc = sqlite3Fts2Init(db);
+ }
+#endif
+
+#ifdef SQLITE_ENABLE_FTS3
+ if( !db->mallocFailed && rc==SQLITE_OK ){
+ rc = sqlite3Fts3Init(db);
+ }
+#endif
+
+#ifdef SQLITE_ENABLE_ICU
+ if( !db->mallocFailed && rc==SQLITE_OK ){
+ extern int sqlite3IcuInit(sqlite3*);
+ rc = sqlite3IcuInit(db);
+ }
+#endif
+ sqlite3Error(db, rc, 0);
+
+ /* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking
+ ** mode. -DSQLITE_DEFAULT_LOCKING_MODE=0 make NORMAL the default locking
+ ** mode. Doing nothing at all also makes NORMAL the default.
+ */
+#ifdef SQLITE_DEFAULT_LOCKING_MODE
+ db->dfltLockMode = SQLITE_DEFAULT_LOCKING_MODE;
+ sqlite3PagerLockingMode(sqlite3BtreePager(db->aDb[0].pBt),
+ SQLITE_DEFAULT_LOCKING_MODE);
+#endif
+
+opendb_out:
+ if( db ){
+ assert( db->mutex!=0 );
+ sqlite3_mutex_leave(db->mutex);
+ }
+ if( SQLITE_NOMEM==(rc = sqlite3_errcode(db)) ){
+ sqlite3_close(db);
+ db = 0;
+ }
+ *ppDb = db;
+ return sqlite3ApiExit(0, rc);
+}
+
+/*
+** Open a new database handle.
+*/
+SQLITE_API int sqlite3_open(
+ const char *zFilename,
+ sqlite3 **ppDb
+){
+ return openDatabase(zFilename, ppDb,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0);
+}
+SQLITE_API int sqlite3_open_v2(
+ const char *filename, /* Database filename (UTF-8) */
+ sqlite3 **ppDb, /* OUT: SQLite db handle */
+ int flags, /* Flags */
+ const char *zVfs /* Name of VFS module to use */
+){
+ return openDatabase(filename, ppDb, flags, zVfs);
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Open a new database handle.
+*/
+SQLITE_API int sqlite3_open16(
+ const void *zFilename,
+ sqlite3 **ppDb
+){
+ char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */
+ sqlite3_value *pVal;
+ int rc = SQLITE_NOMEM;
+
+ assert( zFilename );
+ assert( ppDb );
+ *ppDb = 0;
+ pVal = sqlite3ValueNew(0);
+ sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8);
+ if( zFilename8 ){
+ rc = openDatabase(zFilename8, ppDb,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0);
+ assert( *ppDb || rc==SQLITE_NOMEM );
+ if( rc==SQLITE_OK && !DbHasProperty(*ppDb, 0, DB_SchemaLoaded) ){
+ ENC(*ppDb) = SQLITE_UTF16NATIVE;
+ }
+ }
+ sqlite3ValueFree(pVal);
+
+ return sqlite3ApiExit(0, rc);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** Register a new collation sequence with the database handle db.
+*/
+SQLITE_API int sqlite3_create_collation(
+ sqlite3* db,
+ const char *zName,
+ int enc,
+ void* pCtx,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+){
+ int rc;
+ sqlite3_mutex_enter(db->mutex);
+ assert( !db->mallocFailed );
+ rc = createCollation(db, zName, enc, pCtx, xCompare, 0);
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** Register a new collation sequence with the database handle db.
+*/
+SQLITE_API int sqlite3_create_collation_v2(
+ sqlite3* db,
+ const char *zName,
+ int enc,
+ void* pCtx,
+ int(*xCompare)(void*,int,const void*,int,const void*),
+ void(*xDel)(void*)
+){
+ int rc;
+ sqlite3_mutex_enter(db->mutex);
+ assert( !db->mallocFailed );
+ rc = createCollation(db, zName, enc, pCtx, xCompare, xDel);
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Register a new collation sequence with the database handle db.
+*/
+SQLITE_API int sqlite3_create_collation16(
+ sqlite3* db,
+ const char *zName,
+ int enc,
+ void* pCtx,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+){
+ int rc = SQLITE_OK;
+ char *zName8;
+ sqlite3_mutex_enter(db->mutex);
+ assert( !db->mallocFailed );
+ zName8 = sqlite3Utf16to8(db, zName, -1);
+ if( zName8 ){
+ rc = createCollation(db, zName8, enc, pCtx, xCompare, 0);
+ sqlite3_free(zName8);
+ }
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** Register a collation sequence factory callback with the database handle
+** db. Replace any previously installed collation sequence factory.
+*/
+SQLITE_API int sqlite3_collation_needed(
+ sqlite3 *db,
+ void *pCollNeededArg,
+ void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*)
+){
+ sqlite3_mutex_enter(db->mutex);
+ db->xCollNeeded = xCollNeeded;
+ db->xCollNeeded16 = 0;
+ db->pCollNeededArg = pCollNeededArg;
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_OK;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Register a collation sequence factory callback with the database handle
+** db. Replace any previously installed collation sequence factory.
+*/
+SQLITE_API int sqlite3_collation_needed16(
+ sqlite3 *db,
+ void *pCollNeededArg,
+ void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*)
+){
+ sqlite3_mutex_enter(db->mutex);
+ db->xCollNeeded = 0;
+ db->xCollNeeded16 = xCollNeeded16;
+ db->pCollNeededArg = pCollNeededArg;
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_OK;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+#ifndef SQLITE_OMIT_GLOBALRECOVER
+/*
+** This function is now an anachronism. It used to be used to recover from a
+** malloc() failure, but SQLite now does this automatically.
+*/
+SQLITE_API int sqlite3_global_recover(void){
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** Test to see whether or not the database connection is in autocommit
+** mode. Return TRUE if it is and FALSE if not. Autocommit mode is on
+** by default. Autocommit is disabled by a BEGIN statement and reenabled
+** by the next COMMIT or ROLLBACK.
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){
+ return db->autoCommit;
+}
+
+#ifdef SQLITE_DEBUG
+/*
+** The following routine is subtituted for constant SQLITE_CORRUPT in
+** debugging builds. This provides a way to set a breakpoint for when
+** corruption is first detected.
+*/
+SQLITE_PRIVATE int sqlite3Corrupt(void){
+ return SQLITE_CORRUPT;
+}
+#endif
+
+/*
+** This is a convenience routine that makes sure that all thread-specific
+** data for this thread has been deallocated.
+**
+** SQLite no longer uses thread-specific data so this routine is now a
+** no-op. It is retained for historical compatibility.
+*/
+SQLITE_API void sqlite3_thread_cleanup(void){
+}
+
+/*
+** Return meta information about a specific column of a database table.
+** See comment in sqlite3.h (sqlite.h.in) for details.
+*/
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+SQLITE_API int sqlite3_table_column_metadata(
+ sqlite3 *db, /* Connection handle */
+ const char *zDbName, /* Database name or NULL */
+ const char *zTableName, /* Table name */
+ const char *zColumnName, /* Column name */
+ char const **pzDataType, /* OUTPUT: Declared data type */
+ char const **pzCollSeq, /* OUTPUT: Collation sequence name */
+ int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */
+ int *pPrimaryKey, /* OUTPUT: True if column part of PK */
+ int *pAutoinc /* OUTPUT: True if colums is auto-increment */
+){
+ int rc;
+ char *zErrMsg = 0;
+ Table *pTab = 0;
+ Column *pCol = 0;
+ int iCol;
+
+ char const *zDataType = 0;
+ char const *zCollSeq = 0;
+ int notnull = 0;
+ int primarykey = 0;
+ int autoinc = 0;
+
+ /* Ensure the database schema has been loaded */
+ sqlite3_mutex_enter(db->mutex);
+ (void)sqlite3SafetyOn(db);
+ sqlite3BtreeEnterAll(db);
+ rc = sqlite3Init(db, &zErrMsg);
+ sqlite3BtreeLeaveAll(db);
+ if( SQLITE_OK!=rc ){
+ goto error_out;
+ }
+
+ /* Locate the table in question */
+ pTab = sqlite3FindTable(db, zTableName, zDbName);
+ if( !pTab || pTab->pSelect ){
+ pTab = 0;
+ goto error_out;
+ }
+
+ /* Find the column for which info is requested */
+ if( sqlite3IsRowid(zColumnName) ){
+ iCol = pTab->iPKey;
+ if( iCol>=0 ){
+ pCol = &pTab->aCol[iCol];
+ }
+ }else{
+ for(iCol=0; iCol<pTab->nCol; iCol++){
+ pCol = &pTab->aCol[iCol];
+ if( 0==sqlite3StrICmp(pCol->zName, zColumnName) ){
+ break;
+ }
+ }
+ if( iCol==pTab->nCol ){
+ pTab = 0;
+ goto error_out;
+ }
+ }
+
+ /* The following block stores the meta information that will be returned
+ ** to the caller in local variables zDataType, zCollSeq, notnull, primarykey
+ ** and autoinc. At this point there are two possibilities:
+ **
+ ** 1. The specified column name was rowid", "oid" or "_rowid_"
+ ** and there is no explicitly declared IPK column.
+ **
+ ** 2. The table is not a view and the column name identified an
+ ** explicitly declared column. Copy meta information from *pCol.
+ */
+ if( pCol ){
+ zDataType = pCol->zType;
+ zCollSeq = pCol->zColl;
+ notnull = (pCol->notNull?1:0);
+ primarykey = (pCol->isPrimKey?1:0);
+ autoinc = ((pTab->iPKey==iCol && pTab->autoInc)?1:0);
+ }else{
+ zDataType = "INTEGER";
+ primarykey = 1;
+ }
+ if( !zCollSeq ){
+ zCollSeq = "BINARY";
+ }
+
+error_out:
+ (void)sqlite3SafetyOff(db);
+
+ /* Whether the function call succeeded or failed, set the output parameters
+ ** to whatever their local counterparts contain. If an error did occur,
+ ** this has the effect of zeroing all output parameters.
+ */
+ if( pzDataType ) *pzDataType = zDataType;
+ if( pzCollSeq ) *pzCollSeq = zCollSeq;
+ if( pNotNull ) *pNotNull = notnull;
+ if( pPrimaryKey ) *pPrimaryKey = primarykey;
+ if( pAutoinc ) *pAutoinc = autoinc;
+
+ if( SQLITE_OK==rc && !pTab ){
+ sqlite3SetString(&zErrMsg, "no such table column: ", zTableName, ".",
+ zColumnName, 0);
+ rc = SQLITE_ERROR;
+ }
+ sqlite3Error(db, rc, (zErrMsg?"%s":0), zErrMsg);
+ sqlite3_free(zErrMsg);
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+#endif
+
+/*
+** Sleep for a little while. Return the amount of time slept.
+*/
+SQLITE_API int sqlite3_sleep(int ms){
+ sqlite3_vfs *pVfs;
+ int rc;
+ pVfs = sqlite3_vfs_find(0);
+
+ /* This function works in milliseconds, but the underlying OsSleep()
+ ** API uses microseconds. Hence the 1000's.
+ */
+ rc = (sqlite3OsSleep(pVfs, 1000*ms)/1000);
+ return rc;
+}
+
+/*
+** Enable or disable the extended result codes.
+*/
+SQLITE_API int sqlite3_extended_result_codes(sqlite3 *db, int onoff){
+ sqlite3_mutex_enter(db->mutex);
+ db->errMask = onoff ? 0xffffffff : 0xff;
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_OK;
+}
+
+/*
+** Invoke the xFileControl method on a particular database.
+*/
+SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){
+ int rc = SQLITE_ERROR;
+ int iDb;
+ sqlite3_mutex_enter(db->mutex);
+ if( zDbName==0 ){
+ iDb = 0;
+ }else{
+ for(iDb=0; iDb<db->nDb; iDb++){
+ if( strcmp(db->aDb[iDb].zName, zDbName)==0 ) break;
+ }
+ }
+ if( iDb<db->nDb ){
+ Btree *pBtree = db->aDb[iDb].pBt;
+ if( pBtree ){
+ Pager *pPager;
+ sqlite3_file *fd;
+ sqlite3BtreeEnter(pBtree);
+ pPager = sqlite3BtreePager(pBtree);
+ assert( pPager!=0 );
+ fd = sqlite3PagerFile(pPager);
+ assert( fd!=0 );
+ if( fd->pMethods ){
+ rc = sqlite3OsFileControl(fd, op, pArg);
+ }
+ sqlite3BtreeLeave(pBtree);
+ }
+ }
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** Interface to the testing logic.
+*/
+SQLITE_API int sqlite3_test_control(int op, ...){
+ int rc = 0;
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+ va_list ap;
+ va_start(ap, op);
+ switch( op ){
+ /*
+ ** sqlite3_test_control(FAULT_CONFIG, fault_id, nDelay, nRepeat)
+ **
+ ** Configure a fault injector. The specific fault injector is
+ ** identified by the fault_id argument. (ex: SQLITE_FAULTINJECTOR_MALLOC)
+ ** The fault will occur after a delay of nDelay calls. The fault
+ ** will repeat nRepeat times.
+ */
+ case SQLITE_TESTCTRL_FAULT_CONFIG: {
+ int id = va_arg(ap, int);
+ int nDelay = va_arg(ap, int);
+ int nRepeat = va_arg(ap, int);
+ sqlite3FaultConfig(id, nDelay, nRepeat);
+ break;
+ }
+
+ /*
+ ** sqlite3_test_control(FAULT_FAILURES, fault_id)
+ **
+ ** Return the number of faults (both hard and benign faults) that have
+ ** occurred since the injector identified by fault_id) was last configured.
+ */
+ case SQLITE_TESTCTRL_FAULT_FAILURES: {
+ int id = va_arg(ap, int);
+ rc = sqlite3FaultFailures(id);
+ break;
+ }
+
+ /*
+ ** sqlite3_test_control(FAULT_BENIGN_FAILURES, fault_id)
+ **
+ ** Return the number of benign faults that have occurred since the
+ ** injector identified by fault_id was last configured.
+ */
+ case SQLITE_TESTCTRL_FAULT_BENIGN_FAILURES: {
+ int id = va_arg(ap, int);
+ rc = sqlite3FaultBenignFailures(id);
+ break;
+ }
+
+ /*
+ ** sqlite3_test_control(FAULT_PENDING, fault_id)
+ **
+ ** Return the number of successes that will occur before the next
+ ** scheduled failure on fault injector fault_id.
+ ** If no failures are scheduled, return -1.
+ */
+ case SQLITE_TESTCTRL_FAULT_PENDING: {
+ int id = va_arg(ap, int);
+ rc = sqlite3FaultPending(id);
+ break;
+ }
+
+ /*
+ ** Save the current state of the PRNG.
+ */
+ case SQLITE_TESTCTRL_PRNG_SAVE: {
+ sqlite3PrngSaveState();
+ break;
+ }
+
+ /*
+ ** Restore the state of the PRNG to the last state saved using
+ ** PRNG_SAVE. If PRNG_SAVE has never before been called, then
+ ** this verb acts like PRNG_RESET.
+ */
+ case SQLITE_TESTCTRL_PRNG_RESTORE: {
+ sqlite3PrngRestoreState();
+ break;
+ }
+
+ /*
+ ** Reset the PRNG back to its uninitialized state. The next call
+ ** to sqlite3_randomness() will reseed the PRNG using a single call
+ ** to the xRandomness method of the default VFS.
+ */
+ case SQLITE_TESTCTRL_PRNG_RESET: {
+ sqlite3PrngResetState();
+ break;
+ }
+
+ /*
+ ** sqlite3_test_control(BITVEC_TEST, size, program)
+ **
+ ** Run a test against a Bitvec object of size. The program argument
+ ** is an array of integers that defines the test. Return -1 on a
+ ** memory allocation error, 0 on success, or non-zero for an error.
+ ** See the sqlite3BitvecBuiltinTest() for additional information.
+ */
+ case SQLITE_TESTCTRL_BITVEC_TEST: {
+ int sz = va_arg(ap, int);
+ int *aProg = va_arg(ap, int*);
+ rc = sqlite3BitvecBuiltinTest(sz, aProg);
+ break;
+ }
+ }
+ va_end(ap);
+#endif /* SQLITE_OMIT_BUILTIN_TEST */
+ return rc;
+}
+
+/************** End of main.c ************************************************/
+/************** Begin file fts3.c ********************************************/
+/*
+** 2006 Oct 10
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This is an SQLite module implementing full-text search.
+*/
+
+/*
+** The code in this file is only compiled if:
+**
+** * The FTS3 module is being built as an extension
+** (in which case SQLITE_CORE is not defined), or
+**
+** * The FTS3 module is being built into the core of
+** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
+*/
+
+/* TODO(shess) Consider exporting this comment to an HTML file or the
+** wiki.
+*/
+/* The full-text index is stored in a series of b+tree (-like)
+** structures called segments which map terms to doclists. The
+** structures are like b+trees in layout, but are constructed from the
+** bottom up in optimal fashion and are not updatable. Since trees
+** are built from the bottom up, things will be described from the
+** bottom up.
+**
+**
+**** Varints ****
+** The basic unit of encoding is a variable-length integer called a
+** varint. We encode variable-length integers in little-endian order
+** using seven bits * per byte as follows:
+**
+** KEY:
+** A = 0xxxxxxx 7 bits of data and one flag bit
+** B = 1xxxxxxx 7 bits of data and one flag bit
+**
+** 7 bits - A
+** 14 bits - BA
+** 21 bits - BBA
+** and so on.
+**
+** This is identical to how sqlite encodes varints (see util.c).
+**
+**
+**** Document lists ****
+** A doclist (document list) holds a docid-sorted list of hits for a
+** given term. Doclists hold docids, and can optionally associate
+** token positions and offsets with docids.
+**
+** A DL_POSITIONS_OFFSETS doclist is stored like this:
+**
+** array {
+** varint docid;
+** array { (position list for column 0)
+** varint position; (delta from previous position plus POS_BASE)
+** varint startOffset; (delta from previous startOffset)
+** varint endOffset; (delta from startOffset)
+** }
+** array {
+** varint POS_COLUMN; (marks start of position list for new column)
+** varint column; (index of new column)
+** array {
+** varint position; (delta from previous position plus POS_BASE)
+** varint startOffset;(delta from previous startOffset)
+** varint endOffset; (delta from startOffset)
+** }
+** }
+** varint POS_END; (marks end of positions for this document.
+** }
+**
+** Here, array { X } means zero or more occurrences of X, adjacent in
+** memory. A "position" is an index of a token in the token stream
+** generated by the tokenizer, while an "offset" is a byte offset,
+** both based at 0. Note that POS_END and POS_COLUMN occur in the
+** same logical place as the position element, and act as sentinals
+** ending a position list array.
+**
+** A DL_POSITIONS doclist omits the startOffset and endOffset
+** information. A DL_DOCIDS doclist omits both the position and
+** offset information, becoming an array of varint-encoded docids.
+**
+** On-disk data is stored as type DL_DEFAULT, so we don't serialize
+** the type. Due to how deletion is implemented in the segmentation
+** system, on-disk doclists MUST store at least positions.
+**
+**
+**** Segment leaf nodes ****
+** Segment leaf nodes store terms and doclists, ordered by term. Leaf
+** nodes are written using LeafWriter, and read using LeafReader (to
+** iterate through a single leaf node's data) and LeavesReader (to
+** iterate through a segment's entire leaf layer). Leaf nodes have
+** the format:
+**
+** varint iHeight; (height from leaf level, always 0)
+** varint nTerm; (length of first term)
+** char pTerm[nTerm]; (content of first term)
+** varint nDoclist; (length of term's associated doclist)
+** char pDoclist[nDoclist]; (content of doclist)
+** array {
+** (further terms are delta-encoded)
+** varint nPrefix; (length of prefix shared with previous term)
+** varint nSuffix; (length of unshared suffix)
+** char pTermSuffix[nSuffix];(unshared suffix of next term)
+** varint nDoclist; (length of term's associated doclist)
+** char pDoclist[nDoclist]; (content of doclist)
+** }
+**
+** Here, array { X } means zero or more occurrences of X, adjacent in
+** memory.
+**
+** Leaf nodes are broken into blocks which are stored contiguously in
+** the %_segments table in sorted order. This means that when the end
+** of a node is reached, the next term is in the node with the next
+** greater node id.
+**
+** New data is spilled to a new leaf node when the current node
+** exceeds LEAF_MAX bytes (default 2048). New data which itself is
+** larger than STANDALONE_MIN (default 1024) is placed in a standalone
+** node (a leaf node with a single term and doclist). The goal of
+** these settings is to pack together groups of small doclists while
+** making it efficient to directly access large doclists. The
+** assumption is that large doclists represent terms which are more
+** likely to be query targets.
+**
+** TODO(shess) It may be useful for blocking decisions to be more
+** dynamic. For instance, it may make more sense to have a 2.5k leaf
+** node rather than splitting into 2k and .5k nodes. My intuition is
+** that this might extend through 2x or 4x the pagesize.
+**
+**
+**** Segment interior nodes ****
+** Segment interior nodes store blockids for subtree nodes and terms
+** to describe what data is stored by the each subtree. Interior
+** nodes are written using InteriorWriter, and read using
+** InteriorReader. InteriorWriters are created as needed when
+** SegmentWriter creates new leaf nodes, or when an interior node
+** itself grows too big and must be split. The format of interior
+** nodes:
+**
+** varint iHeight; (height from leaf level, always >0)
+** varint iBlockid; (block id of node's leftmost subtree)
+** optional {
+** varint nTerm; (length of first term)
+** char pTerm[nTerm]; (content of first term)
+** array {
+** (further terms are delta-encoded)
+** varint nPrefix; (length of shared prefix with previous term)
+** varint nSuffix; (length of unshared suffix)
+** char pTermSuffix[nSuffix]; (unshared suffix of next term)
+** }
+** }
+**
+** Here, optional { X } means an optional element, while array { X }
+** means zero or more occurrences of X, adjacent in memory.
+**
+** An interior node encodes n terms separating n+1 subtrees. The
+** subtree blocks are contiguous, so only the first subtree's blockid
+** is encoded. The subtree at iBlockid will contain all terms less
+** than the first term encoded (or all terms if no term is encoded).
+** Otherwise, for terms greater than or equal to pTerm[i] but less
+** than pTerm[i+1], the subtree for that term will be rooted at
+** iBlockid+i. Interior nodes only store enough term data to
+** distinguish adjacent children (if the rightmost term of the left
+** child is "something", and the leftmost term of the right child is
+** "wicked", only "w" is stored).
+**
+** New data is spilled to a new interior node at the same height when
+** the current node exceeds INTERIOR_MAX bytes (default 2048).
+** INTERIOR_MIN_TERMS (default 7) keeps large terms from monopolizing
+** interior nodes and making the tree too skinny. The interior nodes
+** at a given height are naturally tracked by interior nodes at
+** height+1, and so on.
+**
+**
+**** Segment directory ****
+** The segment directory in table %_segdir stores meta-information for
+** merging and deleting segments, and also the root node of the
+** segment's tree.
+**
+** The root node is the top node of the segment's tree after encoding
+** the entire segment, restricted to ROOT_MAX bytes (default 1024).
+** This could be either a leaf node or an interior node. If the top
+** node requires more than ROOT_MAX bytes, it is flushed to %_segments
+** and a new root interior node is generated (which should always fit
+** within ROOT_MAX because it only needs space for 2 varints, the
+** height and the blockid of the previous root).
+**
+** The meta-information in the segment directory is:
+** level - segment level (see below)
+** idx - index within level
+** - (level,idx uniquely identify a segment)
+** start_block - first leaf node
+** leaves_end_block - last leaf node
+** end_block - last block (including interior nodes)
+** root - contents of root node
+**
+** If the root node is a leaf node, then start_block,
+** leaves_end_block, and end_block are all 0.
+**
+**
+**** Segment merging ****
+** To amortize update costs, segments are groups into levels and
+** merged in matches. Each increase in level represents exponentially
+** more documents.
+**
+** New documents (actually, document updates) are tokenized and
+** written individually (using LeafWriter) to a level 0 segment, with
+** incrementing idx. When idx reaches MERGE_COUNT (default 16), all
+** level 0 segments are merged into a single level 1 segment. Level 1
+** is populated like level 0, and eventually MERGE_COUNT level 1
+** segments are merged to a single level 2 segment (representing
+** MERGE_COUNT^2 updates), and so on.
+**
+** A segment merge traverses all segments at a given level in
+** parallel, performing a straightforward sorted merge. Since segment
+** leaf nodes are written in to the %_segments table in order, this
+** merge traverses the underlying sqlite disk structures efficiently.
+** After the merge, all segment blocks from the merged level are
+** deleted.
+**
+** MERGE_COUNT controls how often we merge segments. 16 seems to be
+** somewhat of a sweet spot for insertion performance. 32 and 64 show
+** very similar performance numbers to 16 on insertion, though they're
+** a tiny bit slower (perhaps due to more overhead in merge-time
+** sorting). 8 is about 20% slower than 16, 4 about 50% slower than
+** 16, 2 about 66% slower than 16.
+**
+** At query time, high MERGE_COUNT increases the number of segments
+** which need to be scanned and merged. For instance, with 100k docs
+** inserted:
+**
+** MERGE_COUNT segments
+** 16 25
+** 8 12
+** 4 10
+** 2 6
+**
+** This appears to have only a moderate impact on queries for very
+** frequent terms (which are somewhat dominated by segment merge
+** costs), and infrequent and non-existent terms still seem to be fast
+** even with many segments.
+**
+** TODO(shess) That said, it would be nice to have a better query-side
+** argument for MERGE_COUNT of 16. Also, it is possible/likely that
+** optimizations to things like doclist merging will swing the sweet
+** spot around.
+**
+**
+**
+**** Handling of deletions and updates ****
+** Since we're using a segmented structure, with no docid-oriented
+** index into the term index, we clearly cannot simply update the term
+** index when a document is deleted or updated. For deletions, we
+** write an empty doclist (varint(docid) varint(POS_END)), for updates
+** we simply write the new doclist. Segment merges overwrite older
+** data for a particular docid with newer data, so deletes or updates
+** will eventually overtake the earlier data and knock it out. The
+** query logic likewise merges doclists so that newer data knocks out
+** older data.
+**
+** TODO(shess) Provide a VACUUM type operation to clear out all
+** deletions and duplications. This would basically be a forced merge
+** into a single segment.
+*/
+
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
+
+#if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE)
+# define SQLITE_CORE 1
+#endif
+
+
+/************** Include fts3_hash.h in the middle of fts3.c ******************/
+/************** Begin file fts3_hash.h ***************************************/
+/*
+** 2001 September 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the header file for the generic hash-table implemenation
+** used in SQLite. We've modified it slightly to serve as a standalone
+** hash table implementation for the full-text indexing module.
+**
+*/
+#ifndef _FTS3_HASH_H_
+#define _FTS3_HASH_H_
+
+/* Forward declarations of structures. */
+typedef struct fts3Hash fts3Hash;
+typedef struct fts3HashElem fts3HashElem;
+
+/* A complete hash table is an instance of the following structure.
+** The internals of this structure are intended to be opaque -- client
+** code should not attempt to access or modify the fields of this structure
+** directly. Change this structure only by using the routines below.
+** However, many of the "procedures" and "functions" for modifying and
+** accessing this structure are really macros, so we can't really make
+** this structure opaque.
+*/
+struct fts3Hash {
+ char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */
+ char copyKey; /* True if copy of key made on insert */
+ int count; /* Number of entries in this table */
+ fts3HashElem *first; /* The first element of the array */
+ int htsize; /* Number of buckets in the hash table */
+ struct _fts3ht { /* the hash table */
+ int count; /* Number of entries with this hash */
+ fts3HashElem *chain; /* Pointer to first entry with this hash */
+ } *ht;
+};
+
+/* Each element in the hash table is an instance of the following
+** structure. All elements are stored on a single doubly-linked list.
+**
+** Again, this structure is intended to be opaque, but it can't really
+** be opaque because it is used by macros.
+*/
+struct fts3HashElem {
+ fts3HashElem *next, *prev; /* Next and previous elements in the table */
+ void *data; /* Data associated with this element */
+ void *pKey; int nKey; /* Key associated with this element */
+};
+
+/*
+** There are 2 different modes of operation for a hash table:
+**
+** FTS3_HASH_STRING pKey points to a string that is nKey bytes long
+** (including the null-terminator, if any). Case
+** is respected in comparisons.
+**
+** FTS3_HASH_BINARY pKey points to binary data nKey bytes long.
+** memcmp() is used to compare keys.
+**
+** A copy of the key is made if the copyKey parameter to fts3HashInit is 1.
+*/
+#define FTS3_HASH_STRING 1
+#define FTS3_HASH_BINARY 2
+
+/*
+** Access routines. To delete, insert a NULL pointer.
+*/
+SQLITE_PRIVATE void sqlite3Fts3HashInit(fts3Hash*, int keytype, int copyKey);
+SQLITE_PRIVATE void *sqlite3Fts3HashInsert(fts3Hash*, const void *pKey, int nKey, void *pData);
+SQLITE_PRIVATE void *sqlite3Fts3HashFind(const fts3Hash*, const void *pKey, int nKey);
+SQLITE_PRIVATE void sqlite3Fts3HashClear(fts3Hash*);
+
+/*
+** Shorthand for the functions above
+*/
+#define fts3HashInit sqlite3Fts3HashInit
+#define fts3HashInsert sqlite3Fts3HashInsert
+#define fts3HashFind sqlite3Fts3HashFind
+#define fts3HashClear sqlite3Fts3HashClear
+
+/*
+** Macros for looping over all elements of a hash table. The idiom is
+** like this:
+**
+** fts3Hash h;
+** fts3HashElem *p;
+** ...
+** for(p=fts3HashFirst(&h); p; p=fts3HashNext(p)){
+** SomeStructure *pData = fts3HashData(p);
+** // do something with pData
+** }
+*/
+#define fts3HashFirst(H) ((H)->first)
+#define fts3HashNext(E) ((E)->next)
+#define fts3HashData(E) ((E)->data)
+#define fts3HashKey(E) ((E)->pKey)
+#define fts3HashKeysize(E) ((E)->nKey)
+
+/*
+** Number of entries in a hash table
+*/
+#define fts3HashCount(H) ((H)->count)
+
+#endif /* _FTS3_HASH_H_ */
+
+/************** End of fts3_hash.h *******************************************/
+/************** Continuing where we left off in fts3.c ***********************/
+/************** Include fts3_tokenizer.h in the middle of fts3.c *************/
+/************** Begin file fts3_tokenizer.h **********************************/
+/*
+** 2006 July 10
+**
+** The author disclaims copyright to this source code.
+**
+*************************************************************************
+** Defines the interface to tokenizers used by fulltext-search. There
+** are three basic components:
+**
+** sqlite3_tokenizer_module is a singleton defining the tokenizer
+** interface functions. This is essentially the class structure for
+** tokenizers.
+**
+** sqlite3_tokenizer is used to define a particular tokenizer, perhaps
+** including customization information defined at creation time.
+**
+** sqlite3_tokenizer_cursor is generated by a tokenizer to generate
+** tokens from a particular input.
+*/
+#ifndef _FTS3_TOKENIZER_H_
+#define _FTS3_TOKENIZER_H_
+
+/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time.
+** If tokenizers are to be allowed to call sqlite3_*() functions, then
+** we will need a way to register the API consistently.
+*/
+
+/*
+** Structures used by the tokenizer interface. When a new tokenizer
+** implementation is registered, the caller provides a pointer to
+** an sqlite3_tokenizer_module containing pointers to the callback
+** functions that make up an implementation.
+**
+** When an fts3 table is created, it passes any arguments passed to
+** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the
+** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer
+** implementation. The xCreate() function in turn returns an
+** sqlite3_tokenizer structure representing the specific tokenizer to
+** be used for the fts3 table (customized by the tokenizer clause arguments).
+**
+** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen()
+** method is called. It returns an sqlite3_tokenizer_cursor object
+** that may be used to tokenize a specific input buffer based on
+** the tokenization rules supplied by a specific sqlite3_tokenizer
+** object.
+*/
+typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module;
+typedef struct sqlite3_tokenizer sqlite3_tokenizer;
+typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor;
+
+struct sqlite3_tokenizer_module {
+
+ /*
+ ** Structure version. Should always be set to 0.
+ */
+ int iVersion;
+
+ /*
+ ** Create a new tokenizer. The values in the argv[] array are the
+ ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL
+ ** TABLE statement that created the fts3 table. For example, if
+ ** the following SQL is executed:
+ **
+ ** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2)
+ **
+ ** then argc is set to 2, and the argv[] array contains pointers
+ ** to the strings "arg1" and "arg2".
+ **
+ ** This method should return either SQLITE_OK (0), or an SQLite error
+ ** code. If SQLITE_OK is returned, then *ppTokenizer should be set
+ ** to point at the newly created tokenizer structure. The generic
+ ** sqlite3_tokenizer.pModule variable should not be initialised by
+ ** this callback. The caller will do so.
+ */
+ int (*xCreate)(
+ int argc, /* Size of argv array */
+ const char *const*argv, /* Tokenizer argument strings */
+ sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
+ );
+
+ /*
+ ** Destroy an existing tokenizer. The fts3 module calls this method
+ ** exactly once for each successful call to xCreate().
+ */
+ int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
+
+ /*
+ ** Create a tokenizer cursor to tokenize an input buffer. The caller
+ ** is responsible for ensuring that the input buffer remains valid
+ ** until the cursor is closed (using the xClose() method).
+ */
+ int (*xOpen)(
+ sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
+ const char *pInput, int nBytes, /* Input buffer */
+ sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
+ );
+
+ /*
+ ** Destroy an existing tokenizer cursor. The fts3 module calls this
+ ** method exactly once for each successful call to xOpen().
+ */
+ int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
+
+ /*
+ ** Retrieve the next token from the tokenizer cursor pCursor. This
+ ** method should either return SQLITE_OK and set the values of the
+ ** "OUT" variables identified below, or SQLITE_DONE to indicate that
+ ** the end of the buffer has been reached, or an SQLite error code.
+ **
+ ** *ppToken should be set to point at a buffer containing the
+ ** normalized version of the token (i.e. after any case-folding and/or
+ ** stemming has been performed). *pnBytes should be set to the length
+ ** of this buffer in bytes. The input text that generated the token is
+ ** identified by the byte offsets returned in *piStartOffset and
+ ** *piEndOffset.
+ **
+ ** The buffer *ppToken is set to point at is managed by the tokenizer
+ ** implementation. It is only required to be valid until the next call
+ ** to xNext() or xClose().
+ */
+ /* TODO(shess) current implementation requires pInput to be
+ ** nul-terminated. This should either be fixed, or pInput/nBytes
+ ** should be converted to zInput.
+ */
+ int (*xNext)(
+ sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
+ const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
+ int *piStartOffset, /* OUT: Byte offset of token in input buffer */
+ int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
+ int *piPosition /* OUT: Number of tokens returned before this one */
+ );
+};
+
+struct sqlite3_tokenizer {
+ const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
+ /* Tokenizer implementations will typically add additional fields */
+};
+
+struct sqlite3_tokenizer_cursor {
+ sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
+ /* Tokenizer implementations will typically add additional fields */
+};
+
+#endif /* _FTS3_TOKENIZER_H_ */
+
+/************** End of fts3_tokenizer.h **************************************/
+/************** Continuing where we left off in fts3.c ***********************/
+#ifndef SQLITE_CORE
+ SQLITE_EXTENSION_INIT1
+#endif
+
+
+/* TODO(shess) MAN, this thing needs some refactoring. At minimum, it
+** would be nice to order the file better, perhaps something along the
+** lines of:
+**
+** - utility functions
+** - table setup functions
+** - table update functions
+** - table query functions
+**
+** Put the query functions last because they're likely to reference
+** typedefs or functions from the table update section.
+*/
+
+#if 0
+# define FTSTRACE(A) printf A; fflush(stdout)
+#else
+# define FTSTRACE(A)
+#endif
+
+/*
+** Default span for NEAR operators.
+*/
+#define SQLITE_FTS3_DEFAULT_NEAR_PARAM 10
+
+/* It is not safe to call isspace(), tolower(), or isalnum() on
+** hi-bit-set characters. This is the same solution used in the
+** tokenizer.
+*/
+/* TODO(shess) The snippet-generation code should be using the
+** tokenizer-generated tokens rather than doing its own local
+** tokenization.
+*/
+/* TODO(shess) Is __isascii() a portable version of (c&0x80)==0? */
+static int safe_isspace(char c){
+ return (c&0x80)==0 ? isspace(c) : 0;
+}
+static int safe_tolower(char c){
+ return (c&0x80)==0 ? tolower(c) : c;
+}
+static int safe_isalnum(char c){
+ return (c&0x80)==0 ? isalnum(c) : 0;
+}
+
+typedef enum DocListType {
+ DL_DOCIDS, /* docids only */
+ DL_POSITIONS, /* docids + positions */
+ DL_POSITIONS_OFFSETS /* docids + positions + offsets */
+} DocListType;
+
+/*
+** By default, only positions and not offsets are stored in the doclists.
+** To change this so that offsets are stored too, compile with
+**
+** -DDL_DEFAULT=DL_POSITIONS_OFFSETS
+**
+** If DL_DEFAULT is set to DL_DOCIDS, your table can only be inserted
+** into (no deletes or updates).
+*/
+#ifndef DL_DEFAULT
+# define DL_DEFAULT DL_POSITIONS
+#endif
+
+enum {
+ POS_END = 0, /* end of this position list */
+ POS_COLUMN, /* followed by new column number */
+ POS_BASE
+};
+
+/* MERGE_COUNT controls how often we merge segments (see comment at
+** top of file).
+*/
+#define MERGE_COUNT 16
+
+/* utility functions */
+
+/* CLEAR() and SCRAMBLE() abstract memset() on a pointer to a single
+** record to prevent errors of the form:
+**
+** my_function(SomeType *b){
+** memset(b, '\0', sizeof(b)); // sizeof(b)!=sizeof(*b)
+** }
+*/
+/* TODO(shess) Obvious candidates for a header file. */
+#define CLEAR(b) memset(b, '\0', sizeof(*(b)))
+
+#ifndef NDEBUG
+# define SCRAMBLE(b) memset(b, 0x55, sizeof(*(b)))
+#else
+# define SCRAMBLE(b)
+#endif
+
+/* We may need up to VARINT_MAX bytes to store an encoded 64-bit integer. */
+#define VARINT_MAX 10
+
+/* Write a 64-bit variable-length integer to memory starting at p[0].
+ * The length of data written will be between 1 and VARINT_MAX bytes.
+ * The number of bytes written is returned. */
+static int fts3PutVarint(char *p, sqlite_int64 v){
+ unsigned char *q = (unsigned char *) p;
+ sqlite_uint64 vu = v;
+ do{
+ *q++ = (unsigned char) ((vu & 0x7f) | 0x80);
+ vu >>= 7;
+ }while( vu!=0 );
+ q[-1] &= 0x7f; /* turn off high bit in final byte */
+ assert( q - (unsigned char *)p <= VARINT_MAX );
+ return (int) (q - (unsigned char *)p);
+}
+
+/* Read a 64-bit variable-length integer from memory starting at p[0].
+ * Return the number of bytes read, or 0 on error.
+ * The value is stored in *v. */
+static int fts3GetVarint(const char *p, sqlite_int64 *v){
+ const unsigned char *q = (const unsigned char *) p;
+ sqlite_uint64 x = 0, y = 1;
+ while( (*q & 0x80) == 0x80 ){
+ x += y * (*q++ & 0x7f);
+ y <<= 7;
+ if( q - (unsigned char *)p >= VARINT_MAX ){ /* bad data */
+ assert( 0 );
+ return 0;
+ }
+ }
+ x += y * (*q++);
+ *v = (sqlite_int64) x;
+ return (int) (q - (unsigned char *)p);
+}
+
+static int fts3GetVarint32(const char *p, int *pi){
+ sqlite_int64 i;
+ int ret = fts3GetVarint(p, &i);
+ *pi = (int) i;
+ assert( *pi==i );
+ return ret;
+}
+
+/*******************************************************************/
+/* DataBuffer is used to collect data into a buffer in piecemeal
+** fashion. It implements the usual distinction between amount of
+** data currently stored (nData) and buffer capacity (nCapacity).
+**
+** dataBufferInit - create a buffer with given initial capacity.
+** dataBufferReset - forget buffer's data, retaining capacity.
+** dataBufferDestroy - free buffer's data.
+** dataBufferSwap - swap contents of two buffers.
+** dataBufferExpand - expand capacity without adding data.
+** dataBufferAppend - append data.
+** dataBufferAppend2 - append two pieces of data at once.
+** dataBufferReplace - replace buffer's data.
+*/
+typedef struct DataBuffer {
+ char *pData; /* Pointer to malloc'ed buffer. */
+ int nCapacity; /* Size of pData buffer. */
+ int nData; /* End of data loaded into pData. */
+} DataBuffer;
+
+static void dataBufferInit(DataBuffer *pBuffer, int nCapacity){
+ assert( nCapacity>=0 );
+ pBuffer->nData = 0;
+ pBuffer->nCapacity = nCapacity;
+ pBuffer->pData = nCapacity==0 ? NULL : sqlite3_malloc(nCapacity);
+}
+static void dataBufferReset(DataBuffer *pBuffer){
+ pBuffer->nData = 0;
+}
+static void dataBufferDestroy(DataBuffer *pBuffer){
+ if( pBuffer->pData!=NULL ) sqlite3_free(pBuffer->pData);
+ SCRAMBLE(pBuffer);
+}
+static void dataBufferSwap(DataBuffer *pBuffer1, DataBuffer *pBuffer2){
+ DataBuffer tmp = *pBuffer1;
+ *pBuffer1 = *pBuffer2;
+ *pBuffer2 = tmp;
+}
+static void dataBufferExpand(DataBuffer *pBuffer, int nAddCapacity){
+ assert( nAddCapacity>0 );
+ /* TODO(shess) Consider expanding more aggressively. Note that the
+ ** underlying malloc implementation may take care of such things for
+ ** us already.
+ */
+ if( pBuffer->nData+nAddCapacity>pBuffer->nCapacity ){
+ pBuffer->nCapacity = pBuffer->nData+nAddCapacity;
+ pBuffer->pData = sqlite3_realloc(pBuffer->pData, pBuffer->nCapacity);
+ }
+}
+static void dataBufferAppend(DataBuffer *pBuffer,
+ const char *pSource, int nSource){
+ assert( nSource>0 && pSource!=NULL );
+ dataBufferExpand(pBuffer, nSource);
+ memcpy(pBuffer->pData+pBuffer->nData, pSource, nSource);
+ pBuffer->nData += nSource;
+}
+static void dataBufferAppend2(DataBuffer *pBuffer,
+ const char *pSource1, int nSource1,
+ const char *pSource2, int nSource2){
+ assert( nSource1>0 && pSource1!=NULL );
+ assert( nSource2>0 && pSource2!=NULL );
+ dataBufferExpand(pBuffer, nSource1+nSource2);
+ memcpy(pBuffer->pData+pBuffer->nData, pSource1, nSource1);
+ memcpy(pBuffer->pData+pBuffer->nData+nSource1, pSource2, nSource2);
+ pBuffer->nData += nSource1+nSource2;
+}
+static void dataBufferReplace(DataBuffer *pBuffer,
+ const char *pSource, int nSource){
+ dataBufferReset(pBuffer);
+ dataBufferAppend(pBuffer, pSource, nSource);
+}
+
+/* StringBuffer is a null-terminated version of DataBuffer. */
+typedef struct StringBuffer {
+ DataBuffer b; /* Includes null terminator. */
+} StringBuffer;
+
+static void initStringBuffer(StringBuffer *sb){
+ dataBufferInit(&sb->b, 100);
+ dataBufferReplace(&sb->b, "", 1);
+}
+static int stringBufferLength(StringBuffer *sb){
+ return sb->b.nData-1;
+}
+static char *stringBufferData(StringBuffer *sb){
+ return sb->b.pData;
+}
+static void stringBufferDestroy(StringBuffer *sb){
+ dataBufferDestroy(&sb->b);
+}
+
+static void nappend(StringBuffer *sb, const char *zFrom, int nFrom){
+ assert( sb->b.nData>0 );
+ if( nFrom>0 ){
+ sb->b.nData--;
+ dataBufferAppend2(&sb->b, zFrom, nFrom, "", 1);
+ }
+}
+static void append(StringBuffer *sb, const char *zFrom){
+ nappend(sb, zFrom, strlen(zFrom));
+}
+
+/* Append a list of strings separated by commas. */
+static void appendList(StringBuffer *sb, int nString, char **azString){
+ int i;
+ for(i=0; i<nString; ++i){
+ if( i>0 ) append(sb, ", ");
+ append(sb, azString[i]);
+ }
+}
+
+static int endsInWhiteSpace(StringBuffer *p){
+ return stringBufferLength(p)>0 &&
+ safe_isspace(stringBufferData(p)[stringBufferLength(p)-1]);
+}
+
+/* If the StringBuffer ends in something other than white space, add a
+** single space character to the end.
+*/
+static void appendWhiteSpace(StringBuffer *p){
+ if( stringBufferLength(p)==0 ) return;
+ if( !endsInWhiteSpace(p) ) append(p, " ");
+}
+
+/* Remove white space from the end of the StringBuffer */
+static void trimWhiteSpace(StringBuffer *p){
+ while( endsInWhiteSpace(p) ){
+ p->b.pData[--p->b.nData-1] = '\0';
+ }
+}
+
+/*******************************************************************/
+/* DLReader is used to read document elements from a doclist. The
+** current docid is cached, so dlrDocid() is fast. DLReader does not
+** own the doclist buffer.
+**
+** dlrAtEnd - true if there's no more data to read.
+** dlrDocid - docid of current document.
+** dlrDocData - doclist data for current document (including docid).
+** dlrDocDataBytes - length of same.
+** dlrAllDataBytes - length of all remaining data.
+** dlrPosData - position data for current document.
+** dlrPosDataLen - length of pos data for current document (incl POS_END).
+** dlrStep - step to current document.
+** dlrInit - initial for doclist of given type against given data.
+** dlrDestroy - clean up.
+**
+** Expected usage is something like:
+**
+** DLReader reader;
+** dlrInit(&reader, pData, nData);
+** while( !dlrAtEnd(&reader) ){
+** // calls to dlrDocid() and kin.
+** dlrStep(&reader);
+** }
+** dlrDestroy(&reader);
+*/
+typedef struct DLReader {
+ DocListType iType;
+ const char *pData;
+ int nData;
+
+ sqlite_int64 iDocid;
+ int nElement;
+} DLReader;
+
+static int dlrAtEnd(DLReader *pReader){
+ assert( pReader->nData>=0 );
+ return pReader->nData==0;
+}
+static sqlite_int64 dlrDocid(DLReader *pReader){
+ assert( !dlrAtEnd(pReader) );
+ return pReader->iDocid;
+}
+static const char *dlrDocData(DLReader *pReader){
+ assert( !dlrAtEnd(pReader) );
+ return pReader->pData;
+}
+static int dlrDocDataBytes(DLReader *pReader){
+ assert( !dlrAtEnd(pReader) );
+ return pReader->nElement;
+}
+static int dlrAllDataBytes(DLReader *pReader){
+ assert( !dlrAtEnd(pReader) );
+ return pReader->nData;
+}
+/* TODO(shess) Consider adding a field to track iDocid varint length
+** to make these two functions faster. This might matter (a tiny bit)
+** for queries.
+*/
+static const char *dlrPosData(DLReader *pReader){
+ sqlite_int64 iDummy;
+ int n = fts3GetVarint(pReader->pData, &iDummy);
+ assert( !dlrAtEnd(pReader) );
+ return pReader->pData+n;
+}
+static int dlrPosDataLen(DLReader *pReader){
+ sqlite_int64 iDummy;
+ int n = fts3GetVarint(pReader->pData, &iDummy);
+ assert( !dlrAtEnd(pReader) );
+ return pReader->nElement-n;
+}
+static void dlrStep(DLReader *pReader){
+ assert( !dlrAtEnd(pReader) );
+
+ /* Skip past current doclist element. */
+ assert( pReader->nElement<=pReader->nData );
+ pReader->pData += pReader->nElement;
+ pReader->nData -= pReader->nElement;
+
+ /* If there is more data, read the next doclist element. */
+ if( pReader->nData!=0 ){
+ sqlite_int64 iDocidDelta;
+ int iDummy, n = fts3GetVarint(pReader->pData, &iDocidDelta);
+ pReader->iDocid += iDocidDelta;
+ if( pReader->iType>=DL_POSITIONS ){
+ assert( n<pReader->nData );
+ while( 1 ){
+ n += fts3GetVarint32(pReader->pData+n, &iDummy);
+ assert( n<=pReader->nData );
+ if( iDummy==POS_END ) break;
+ if( iDummy==POS_COLUMN ){
+ n += fts3GetVarint32(pReader->pData+n, &iDummy);
+ assert( n<pReader->nData );
+ }else if( pReader->iType==DL_POSITIONS_OFFSETS ){
+ n += fts3GetVarint32(pReader->pData+n, &iDummy);
+ n += fts3GetVarint32(pReader->pData+n, &iDummy);
+ assert( n<pReader->nData );
+ }
+ }
+ }
+ pReader->nElement = n;
+ assert( pReader->nElement<=pReader->nData );
+ }
+}
+static void dlrInit(DLReader *pReader, DocListType iType,
+ const char *pData, int nData){
+ assert( pData!=NULL && nData!=0 );
+ pReader->iType = iType;
+ pReader->pData = pData;
+ pReader->nData = nData;
+ pReader->nElement = 0;
+ pReader->iDocid = 0;
+
+ /* Load the first element's data. There must be a first element. */
+ dlrStep(pReader);
+}
+static void dlrDestroy(DLReader *pReader){
+ SCRAMBLE(pReader);
+}
+
+#ifndef NDEBUG
+/* Verify that the doclist can be validly decoded. Also returns the
+** last docid found because it is convenient in other assertions for
+** DLWriter.
+*/
+static void docListValidate(DocListType iType, const char *pData, int nData,
+ sqlite_int64 *pLastDocid){
+ sqlite_int64 iPrevDocid = 0;
+ assert( nData>0 );
+ assert( pData!=0 );
+ assert( pData+nData>pData );
+ while( nData!=0 ){
+ sqlite_int64 iDocidDelta;
+ int n = fts3GetVarint(pData, &iDocidDelta);
+ iPrevDocid += iDocidDelta;
+ if( iType>DL_DOCIDS ){
+ int iDummy;
+ while( 1 ){
+ n += fts3GetVarint32(pData+n, &iDummy);
+ if( iDummy==POS_END ) break;
+ if( iDummy==POS_COLUMN ){
+ n += fts3GetVarint32(pData+n, &iDummy);
+ }else if( iType>DL_POSITIONS ){
+ n += fts3GetVarint32(pData+n, &iDummy);
+ n += fts3GetVarint32(pData+n, &iDummy);
+ }
+ assert( n<=nData );
+ }
+ }
+ assert( n<=nData );
+ pData += n;
+ nData -= n;
+ }
+ if( pLastDocid ) *pLastDocid = iPrevDocid;
+}
+#define ASSERT_VALID_DOCLIST(i, p, n, o) docListValidate(i, p, n, o)
+#else
+#define ASSERT_VALID_DOCLIST(i, p, n, o) assert( 1 )
+#endif
+
+/*******************************************************************/
+/* DLWriter is used to write doclist data to a DataBuffer. DLWriter
+** always appends to the buffer and does not own it.
+**
+** dlwInit - initialize to write a given type doclistto a buffer.
+** dlwDestroy - clear the writer's memory. Does not free buffer.
+** dlwAppend - append raw doclist data to buffer.
+** dlwCopy - copy next doclist from reader to writer.
+** dlwAdd - construct doclist element and append to buffer.
+** Only apply dlwAdd() to DL_DOCIDS doclists (else use PLWriter).
+*/
+typedef struct DLWriter {
+ DocListType iType;
+ DataBuffer *b;
+ sqlite_int64 iPrevDocid;
+#ifndef NDEBUG
+ int has_iPrevDocid;
+#endif
+} DLWriter;
+
+static void dlwInit(DLWriter *pWriter, DocListType iType, DataBuffer *b){
+ pWriter->b = b;
+ pWriter->iType = iType;
+ pWriter->iPrevDocid = 0;
+#ifndef NDEBUG
+ pWriter->has_iPrevDocid = 0;
+#endif
+}
+static void dlwDestroy(DLWriter *pWriter){
+ SCRAMBLE(pWriter);
+}
+/* iFirstDocid is the first docid in the doclist in pData. It is
+** needed because pData may point within a larger doclist, in which
+** case the first item would be delta-encoded.
+**
+** iLastDocid is the final docid in the doclist in pData. It is
+** needed to create the new iPrevDocid for future delta-encoding. The
+** code could decode the passed doclist to recreate iLastDocid, but
+** the only current user (docListMerge) already has decoded this
+** information.
+*/
+/* TODO(shess) This has become just a helper for docListMerge.
+** Consider a refactor to make this cleaner.
+*/
+static void dlwAppend(DLWriter *pWriter,
+ const char *pData, int nData,
+ sqlite_int64 iFirstDocid, sqlite_int64 iLastDocid){
+ sqlite_int64 iDocid = 0;
+ char c[VARINT_MAX];
+ int nFirstOld, nFirstNew; /* Old and new varint len of first docid. */
+#ifndef NDEBUG
+ sqlite_int64 iLastDocidDelta;
+#endif
+
+ /* Recode the initial docid as delta from iPrevDocid. */
+ nFirstOld = fts3GetVarint(pData, &iDocid);
+ assert( nFirstOld<nData || (nFirstOld==nData && pWriter->iType==DL_DOCIDS) );
+ nFirstNew = fts3PutVarint(c, iFirstDocid-pWriter->iPrevDocid);
+
+ /* Verify that the incoming doclist is valid AND that it ends with
+ ** the expected docid. This is essential because we'll trust this
+ ** docid in future delta-encoding.
+ */
+ ASSERT_VALID_DOCLIST(pWriter->iType, pData, nData, &iLastDocidDelta);
+ assert( iLastDocid==iFirstDocid-iDocid+iLastDocidDelta );
+
+ /* Append recoded initial docid and everything else. Rest of docids
+ ** should have been delta-encoded from previous initial docid.
+ */
+ if( nFirstOld<nData ){
+ dataBufferAppend2(pWriter->b, c, nFirstNew,
+ pData+nFirstOld, nData-nFirstOld);
+ }else{
+ dataBufferAppend(pWriter->b, c, nFirstNew);
+ }
+ pWriter->iPrevDocid = iLastDocid;
+}
+static void dlwCopy(DLWriter *pWriter, DLReader *pReader){
+ dlwAppend(pWriter, dlrDocData(pReader), dlrDocDataBytes(pReader),
+ dlrDocid(pReader), dlrDocid(pReader));
+}
+static void dlwAdd(DLWriter *pWriter, sqlite_int64 iDocid){
+ char c[VARINT_MAX];
+ int n = fts3PutVarint(c, iDocid-pWriter->iPrevDocid);
+
+ /* Docids must ascend. */
+ assert( !pWriter->has_iPrevDocid || iDocid>pWriter->iPrevDocid );
+ assert( pWriter->iType==DL_DOCIDS );
+
+ dataBufferAppend(pWriter->b, c, n);
+ pWriter->iPrevDocid = iDocid;
+#ifndef NDEBUG
+ pWriter->has_iPrevDocid = 1;
+#endif
+}
+
+/*******************************************************************/
+/* PLReader is used to read data from a document's position list. As
+** the caller steps through the list, data is cached so that varints
+** only need to be decoded once.
+**
+** plrInit, plrDestroy - create/destroy a reader.
+** plrColumn, plrPosition, plrStartOffset, plrEndOffset - accessors
+** plrAtEnd - at end of stream, only call plrDestroy once true.
+** plrStep - step to the next element.
+*/
+typedef struct PLReader {
+ /* These refer to the next position's data. nData will reach 0 when
+ ** reading the last position, so plrStep() signals EOF by setting
+ ** pData to NULL.
+ */
+ const char *pData;
+ int nData;
+
+ DocListType iType;
+ int iColumn; /* the last column read */
+ int iPosition; /* the last position read */
+ int iStartOffset; /* the last start offset read */
+ int iEndOffset; /* the last end offset read */
+} PLReader;
+
+static int plrAtEnd(PLReader *pReader){
+ return pReader->pData==NULL;
+}
+static int plrColumn(PLReader *pReader){
+ assert( !plrAtEnd(pReader) );
+ return pReader->iColumn;
+}
+static int plrPosition(PLReader *pReader){
+ assert( !plrAtEnd(pReader) );
+ return pReader->iPosition;
+}
+static int plrStartOffset(PLReader *pReader){
+ assert( !plrAtEnd(pReader) );
+ return pReader->iStartOffset;
+}
+static int plrEndOffset(PLReader *pReader){
+ assert( !plrAtEnd(pReader) );
+ return pReader->iEndOffset;
+}
+static void plrStep(PLReader *pReader){
+ int i, n;
+
+ assert( !plrAtEnd(pReader) );
+
+ if( pReader->nData==0 ){
+ pReader->pData = NULL;
+ return;
+ }
+
+ n = fts3GetVarint32(pReader->pData, &i);
+ if( i==POS_COLUMN ){
+ n += fts3GetVarint32(pReader->pData+n, &pReader->iColumn);
+ pReader->iPosition = 0;
+ pReader->iStartOffset = 0;
+ n += fts3GetVarint32(pReader->pData+n, &i);
+ }
+ /* Should never see adjacent column changes. */
+ assert( i!=POS_COLUMN );
+
+ if( i==POS_END ){
+ pReader->nData = 0;
+ pReader->pData = NULL;
+ return;
+ }
+
+ pReader->iPosition += i-POS_BASE;
+ if( pReader->iType==DL_POSITIONS_OFFSETS ){
+ n += fts3GetVarint32(pReader->pData+n, &i);
+ pReader->iStartOffset += i;
+ n += fts3GetVarint32(pReader->pData+n, &i);
+ pReader->iEndOffset = pReader->iStartOffset+i;
+ }
+ assert( n<=pReader->nData );
+ pReader->pData += n;
+ pReader->nData -= n;
+}
+
+static void plrInit(PLReader *pReader, DLReader *pDLReader){
+ pReader->pData = dlrPosData(pDLReader);
+ pReader->nData = dlrPosDataLen(pDLReader);
+ pReader->iType = pDLReader->iType;
+ pReader->iColumn = 0;
+ pReader->iPosition = 0;
+ pReader->iStartOffset = 0;
+ pReader->iEndOffset = 0;
+ plrStep(pReader);
+}
+static void plrDestroy(PLReader *pReader){
+ SCRAMBLE(pReader);
+}
+
+/*******************************************************************/
+/* PLWriter is used in constructing a document's position list. As a
+** convenience, if iType is DL_DOCIDS, PLWriter becomes a no-op.
+** PLWriter writes to the associated DLWriter's buffer.
+**
+** plwInit - init for writing a document's poslist.
+** plwDestroy - clear a writer.
+** plwAdd - append position and offset information.
+** plwCopy - copy next position's data from reader to writer.
+** plwTerminate - add any necessary doclist terminator.
+**
+** Calling plwAdd() after plwTerminate() may result in a corrupt
+** doclist.
+*/
+/* TODO(shess) Until we've written the second item, we can cache the
+** first item's information. Then we'd have three states:
+**
+** - initialized with docid, no positions.
+** - docid and one position.
+** - docid and multiple positions.
+**
+** Only the last state needs to actually write to dlw->b, which would
+** be an improvement in the DLCollector case.
+*/
+typedef struct PLWriter {
+ DLWriter *dlw;
+
+ int iColumn; /* the last column written */
+ int iPos; /* the last position written */
+ int iOffset; /* the last start offset written */
+} PLWriter;
+
+/* TODO(shess) In the case where the parent is reading these values
+** from a PLReader, we could optimize to a copy if that PLReader has
+** the same type as pWriter.
+*/
+static void plwAdd(PLWriter *pWriter, int iColumn, int iPos,
+ int iStartOffset, int iEndOffset){
+ /* Worst-case space for POS_COLUMN, iColumn, iPosDelta,
+ ** iStartOffsetDelta, and iEndOffsetDelta.
+ */
+ char c[5*VARINT_MAX];
+ int n = 0;
+
+ /* Ban plwAdd() after plwTerminate(). */
+ assert( pWriter->iPos!=-1 );
+
+ if( pWriter->dlw->iType==DL_DOCIDS ) return;
+
+ if( iColumn!=pWriter->iColumn ){
+ n += fts3PutVarint(c+n, POS_COLUMN);
+ n += fts3PutVarint(c+n, iColumn);
+ pWriter->iColumn = iColumn;
+ pWriter->iPos = 0;
+ pWriter->iOffset = 0;
+ }
+ assert( iPos>=pWriter->iPos );
+ n += fts3PutVarint(c+n, POS_BASE+(iPos-pWriter->iPos));
+ pWriter->iPos = iPos;
+ if( pWriter->dlw->iType==DL_POSITIONS_OFFSETS ){
+ assert( iStartOffset>=pWriter->iOffset );
+ n += fts3PutVarint(c+n, iStartOffset-pWriter->iOffset);
+ pWriter->iOffset = iStartOffset;
+ assert( iEndOffset>=iStartOffset );
+ n += fts3PutVarint(c+n, iEndOffset-iStartOffset);
+ }
+ dataBufferAppend(pWriter->dlw->b, c, n);
+}
+static void plwCopy(PLWriter *pWriter, PLReader *pReader){
+ plwAdd(pWriter, plrColumn(pReader), plrPosition(pReader),
+ plrStartOffset(pReader), plrEndOffset(pReader));
+}
+static void plwInit(PLWriter *pWriter, DLWriter *dlw, sqlite_int64 iDocid){
+ char c[VARINT_MAX];
+ int n;
+
+ pWriter->dlw = dlw;
+
+ /* Docids must ascend. */
+ assert( !pWriter->dlw->has_iPrevDocid || iDocid>pWriter->dlw->iPrevDocid );
+ n = fts3PutVarint(c, iDocid-pWriter->dlw->iPrevDocid);
+ dataBufferAppend(pWriter->dlw->b, c, n);
+ pWriter->dlw->iPrevDocid = iDocid;
+#ifndef NDEBUG
+ pWriter->dlw->has_iPrevDocid = 1;
+#endif
+
+ pWriter->iColumn = 0;
+ pWriter->iPos = 0;
+ pWriter->iOffset = 0;
+}
+/* TODO(shess) Should plwDestroy() also terminate the doclist? But
+** then plwDestroy() would no longer be just a destructor, it would
+** also be doing work, which isn't consistent with the overall idiom.
+** Another option would be for plwAdd() to always append any necessary
+** terminator, so that the output is always correct. But that would
+** add incremental work to the common case with the only benefit being
+** API elegance. Punt for now.
+*/
+static void plwTerminate(PLWriter *pWriter){
+ if( pWriter->dlw->iType>DL_DOCIDS ){
+ char c[VARINT_MAX];
+ int n = fts3PutVarint(c, POS_END);
+ dataBufferAppend(pWriter->dlw->b, c, n);
+ }
+#ifndef NDEBUG
+ /* Mark as terminated for assert in plwAdd(). */
+ pWriter->iPos = -1;
+#endif
+}
+static void plwDestroy(PLWriter *pWriter){
+ SCRAMBLE(pWriter);
+}
+
+/*******************************************************************/
+/* DLCollector wraps PLWriter and DLWriter to provide a
+** dynamically-allocated doclist area to use during tokenization.
+**
+** dlcNew - malloc up and initialize a collector.
+** dlcDelete - destroy a collector and all contained items.
+** dlcAddPos - append position and offset information.
+** dlcAddDoclist - add the collected doclist to the given buffer.
+** dlcNext - terminate the current document and open another.
+*/
+typedef struct DLCollector {
+ DataBuffer b;
+ DLWriter dlw;
+ PLWriter plw;
+} DLCollector;
+
+/* TODO(shess) This could also be done by calling plwTerminate() and
+** dataBufferAppend(). I tried that, expecting nominal performance
+** differences, but it seemed to pretty reliably be worth 1% to code
+** it this way. I suspect it is the incremental malloc overhead (some
+** percentage of the plwTerminate() calls will cause a realloc), so
+** this might be worth revisiting if the DataBuffer implementation
+** changes.
+*/
+static void dlcAddDoclist(DLCollector *pCollector, DataBuffer *b){
+ if( pCollector->dlw.iType>DL_DOCIDS ){
+ char c[VARINT_MAX];
+ int n = fts3PutVarint(c, POS_END);
+ dataBufferAppend2(b, pCollector->b.pData, pCollector->b.nData, c, n);
+ }else{
+ dataBufferAppend(b, pCollector->b.pData, pCollector->b.nData);
+ }
+}
+static void dlcNext(DLCollector *pCollector, sqlite_int64 iDocid){
+ plwTerminate(&pCollector->plw);
+ plwDestroy(&pCollector->plw);
+ plwInit(&pCollector->plw, &pCollector->dlw, iDocid);
+}
+static void dlcAddPos(DLCollector *pCollector, int iColumn, int iPos,
+ int iStartOffset, int iEndOffset){
+ plwAdd(&pCollector->plw, iColumn, iPos, iStartOffset, iEndOffset);
+}
+
+static DLCollector *dlcNew(sqlite_int64 iDocid, DocListType iType){
+ DLCollector *pCollector = sqlite3_malloc(sizeof(DLCollector));
+ dataBufferInit(&pCollector->b, 0);
+ dlwInit(&pCollector->dlw, iType, &pCollector->b);
+ plwInit(&pCollector->plw, &pCollector->dlw, iDocid);
+ return pCollector;
+}
+static void dlcDelete(DLCollector *pCollector){
+ plwDestroy(&pCollector->plw);
+ dlwDestroy(&pCollector->dlw);
+ dataBufferDestroy(&pCollector->b);
+ SCRAMBLE(pCollector);
+ sqlite3_free(pCollector);
+}
+
+
+/* Copy the doclist data of iType in pData/nData into *out, trimming
+** unnecessary data as we go. Only columns matching iColumn are
+** copied, all columns copied if iColumn is -1. Elements with no
+** matching columns are dropped. The output is an iOutType doclist.
+*/
+/* NOTE(shess) This code is only valid after all doclists are merged.
+** If this is run before merges, then doclist items which represent
+** deletion will be trimmed, and will thus not effect a deletion
+** during the merge.
+*/
+static void docListTrim(DocListType iType, const char *pData, int nData,
+ int iColumn, DocListType iOutType, DataBuffer *out){
+ DLReader dlReader;
+ DLWriter dlWriter;
+
+ assert( iOutType<=iType );
+
+ dlrInit(&dlReader, iType, pData, nData);
+ dlwInit(&dlWriter, iOutType, out);
+
+ while( !dlrAtEnd(&dlReader) ){
+ PLReader plReader;
+ PLWriter plWriter;
+ int match = 0;
+
+ plrInit(&plReader, &dlReader);
+
+ while( !plrAtEnd(&plReader) ){
+ if( iColumn==-1 || plrColumn(&plReader)==iColumn ){
+ if( !match ){
+ plwInit(&plWriter, &dlWriter, dlrDocid(&dlReader));
+ match = 1;
+ }
+ plwAdd(&plWriter, plrColumn(&plReader), plrPosition(&plReader),
+ plrStartOffset(&plReader), plrEndOffset(&plReader));
+ }
+ plrStep(&plReader);
+ }
+ if( match ){
+ plwTerminate(&plWriter);
+ plwDestroy(&plWriter);
+ }
+
+ plrDestroy(&plReader);
+ dlrStep(&dlReader);
+ }
+ dlwDestroy(&dlWriter);
+ dlrDestroy(&dlReader);
+}
+
+/* Used by docListMerge() to keep doclists in the ascending order by
+** docid, then ascending order by age (so the newest comes first).
+*/
+typedef struct OrderedDLReader {
+ DLReader *pReader;
+
+ /* TODO(shess) If we assume that docListMerge pReaders is ordered by
+ ** age (which we do), then we could use pReader comparisons to break
+ ** ties.
+ */
+ int idx;
+} OrderedDLReader;
+
+/* Order eof to end, then by docid asc, idx desc. */
+static int orderedDLReaderCmp(OrderedDLReader *r1, OrderedDLReader *r2){
+ if( dlrAtEnd(r1->pReader) ){
+ if( dlrAtEnd(r2->pReader) ) return 0; /* Both atEnd(). */
+ return 1; /* Only r1 atEnd(). */
+ }
+ if( dlrAtEnd(r2->pReader) ) return -1; /* Only r2 atEnd(). */
+
+ if( dlrDocid(r1->pReader)<dlrDocid(r2->pReader) ) return -1;
+ if( dlrDocid(r1->pReader)>dlrDocid(r2->pReader) ) return 1;
+
+ /* Descending on idx. */
+ return r2->idx-r1->idx;
+}
+
+/* Bubble p[0] to appropriate place in p[1..n-1]. Assumes that
+** p[1..n-1] is already sorted.
+*/
+/* TODO(shess) Is this frequent enough to warrant a binary search?
+** Before implementing that, instrument the code to check. In most
+** current usage, I expect that p[0] will be less than p[1] a very
+** high proportion of the time.
+*/
+static void orderedDLReaderReorder(OrderedDLReader *p, int n){
+ while( n>1 && orderedDLReaderCmp(p, p+1)>0 ){
+ OrderedDLReader tmp = p[0];
+ p[0] = p[1];
+ p[1] = tmp;
+ n--;
+ p++;
+ }
+}
+
+/* Given an array of doclist readers, merge their doclist elements
+** into out in sorted order (by docid), dropping elements from older
+** readers when there is a duplicate docid. pReaders is assumed to be
+** ordered by age, oldest first.
+*/
+/* TODO(shess) nReaders must be <= MERGE_COUNT. This should probably
+** be fixed.
+*/
+static void docListMerge(DataBuffer *out,
+ DLReader *pReaders, int nReaders){
+ OrderedDLReader readers[MERGE_COUNT];
+ DLWriter writer;
+ int i, n;
+ const char *pStart = 0;
+ int nStart = 0;
+ sqlite_int64 iFirstDocid = 0, iLastDocid = 0;
+
+ assert( nReaders>0 );
+ if( nReaders==1 ){
+ dataBufferAppend(out, dlrDocData(pReaders), dlrAllDataBytes(pReaders));
+ return;
+ }
+
+ assert( nReaders<=MERGE_COUNT );
+ n = 0;
+ for(i=0; i<nReaders; i++){
+ assert( pReaders[i].iType==pReaders[0].iType );
+ readers[i].pReader = pReaders+i;
+ readers[i].idx = i;
+ n += dlrAllDataBytes(&pReaders[i]);
+ }
+ /* Conservatively size output to sum of inputs. Output should end
+ ** up strictly smaller than input.
+ */
+ dataBufferExpand(out, n);
+
+ /* Get the readers into sorted order. */
+ while( i-->0 ){
+ orderedDLReaderReorder(readers+i, nReaders-i);
+ }
+
+ dlwInit(&writer, pReaders[0].iType, out);
+ while( !dlrAtEnd(readers[0].pReader) ){
+ sqlite_int64 iDocid = dlrDocid(readers[0].pReader);
+
+ /* If this is a continuation of the current buffer to copy, extend
+ ** that buffer. memcpy() seems to be more efficient if it has a
+ ** lots of data to copy.
+ */
+ if( dlrDocData(readers[0].pReader)==pStart+nStart ){
+ nStart += dlrDocDataBytes(readers[0].pReader);
+ }else{
+ if( pStart!=0 ){
+ dlwAppend(&writer, pStart, nStart, iFirstDocid, iLastDocid);
+ }
+ pStart = dlrDocData(readers[0].pReader);
+ nStart = dlrDocDataBytes(readers[0].pReader);
+ iFirstDocid = iDocid;
+ }
+ iLastDocid = iDocid;
+ dlrStep(readers[0].pReader);
+
+ /* Drop all of the older elements with the same docid. */
+ for(i=1; i<nReaders &&
+ !dlrAtEnd(readers[i].pReader) &&
+ dlrDocid(readers[i].pReader)==iDocid; i++){
+ dlrStep(readers[i].pReader);
+ }
+
+ /* Get the readers back into order. */
+ while( i-->0 ){
+ orderedDLReaderReorder(readers+i, nReaders-i);
+ }
+ }
+
+ /* Copy over any remaining elements. */
+ if( nStart>0 ) dlwAppend(&writer, pStart, nStart, iFirstDocid, iLastDocid);
+ dlwDestroy(&writer);
+}
+
+/* Helper function for posListUnion(). Compares the current position
+** between left and right, returning as standard C idiom of <0 if
+** left<right, >0 if left>right, and 0 if left==right. "End" always
+** compares greater.
+*/
+static int posListCmp(PLReader *pLeft, PLReader *pRight){
+ assert( pLeft->iType==pRight->iType );
+ if( pLeft->iType==DL_DOCIDS ) return 0;
+
+ if( plrAtEnd(pLeft) ) return plrAtEnd(pRight) ? 0 : 1;
+ if( plrAtEnd(pRight) ) return -1;
+
+ if( plrColumn(pLeft)<plrColumn(pRight) ) return -1;
+ if( plrColumn(pLeft)>plrColumn(pRight) ) return 1;
+
+ if( plrPosition(pLeft)<plrPosition(pRight) ) return -1;
+ if( plrPosition(pLeft)>plrPosition(pRight) ) return 1;
+ if( pLeft->iType==DL_POSITIONS ) return 0;
+
+ if( plrStartOffset(pLeft)<plrStartOffset(pRight) ) return -1;
+ if( plrStartOffset(pLeft)>plrStartOffset(pRight) ) return 1;
+
+ if( plrEndOffset(pLeft)<plrEndOffset(pRight) ) return -1;
+ if( plrEndOffset(pLeft)>plrEndOffset(pRight) ) return 1;
+
+ return 0;
+}
+
+/* Write the union of position lists in pLeft and pRight to pOut.
+** "Union" in this case meaning "All unique position tuples". Should
+** work with any doclist type, though both inputs and the output
+** should be the same type.
+*/
+static void posListUnion(DLReader *pLeft, DLReader *pRight, DLWriter *pOut){
+ PLReader left, right;
+ PLWriter writer;
+
+ assert( dlrDocid(pLeft)==dlrDocid(pRight) );
+ assert( pLeft->iType==pRight->iType );
+ assert( pLeft->iType==pOut->iType );
+
+ plrInit(&left, pLeft);
+ plrInit(&right, pRight);
+ plwInit(&writer, pOut, dlrDocid(pLeft));
+
+ while( !plrAtEnd(&left) || !plrAtEnd(&right) ){
+ int c = posListCmp(&left, &right);
+ if( c<0 ){
+ plwCopy(&writer, &left);
+ plrStep(&left);
+ }else if( c>0 ){
+ plwCopy(&writer, &right);
+ plrStep(&right);
+ }else{
+ plwCopy(&writer, &left);
+ plrStep(&left);
+ plrStep(&right);
+ }
+ }
+
+ plwTerminate(&writer);
+ plwDestroy(&writer);
+ plrDestroy(&left);
+ plrDestroy(&right);
+}
+
+/* Write the union of doclists in pLeft and pRight to pOut. For
+** docids in common between the inputs, the union of the position
+** lists is written. Inputs and outputs are always type DL_DEFAULT.
+*/
+static void docListUnion(
+ const char *pLeft, int nLeft,
+ const char *pRight, int nRight,
+ DataBuffer *pOut /* Write the combined doclist here */
+){
+ DLReader left, right;
+ DLWriter writer;
+
+ if( nLeft==0 ){
+ if( nRight!=0) dataBufferAppend(pOut, pRight, nRight);
+ return;
+ }
+ if( nRight==0 ){
+ dataBufferAppend(pOut, pLeft, nLeft);
+ return;
+ }
+
+ dlrInit(&left, DL_DEFAULT, pLeft, nLeft);
+ dlrInit(&right, DL_DEFAULT, pRight, nRight);
+ dlwInit(&writer, DL_DEFAULT, pOut);
+
+ while( !dlrAtEnd(&left) || !dlrAtEnd(&right) ){
+ if( dlrAtEnd(&right) ){
+ dlwCopy(&writer, &left);
+ dlrStep(&left);
+ }else if( dlrAtEnd(&left) ){
+ dlwCopy(&writer, &right);
+ dlrStep(&right);
+ }else if( dlrDocid(&left)<dlrDocid(&right) ){
+ dlwCopy(&writer, &left);
+ dlrStep(&left);
+ }else if( dlrDocid(&left)>dlrDocid(&right) ){
+ dlwCopy(&writer, &right);
+ dlrStep(&right);
+ }else{
+ posListUnion(&left, &right, &writer);
+ dlrStep(&left);
+ dlrStep(&right);
+ }
+ }
+
+ dlrDestroy(&left);
+ dlrDestroy(&right);
+ dlwDestroy(&writer);
+}
+
+/*
+** This function is used as part of the implementation of phrase and
+** NEAR matching.
+**
+** pLeft and pRight are DLReaders positioned to the same docid in
+** lists of type DL_POSITION. This function writes an entry to the
+** DLWriter pOut for each position in pRight that is less than
+** (nNear+1) greater (but not equal to or smaller) than a position
+** in pLeft. For example, if nNear is 0, and the positions contained
+** by pLeft and pRight are:
+**
+** pLeft: 5 10 15 20
+** pRight: 6 9 17 21
+**
+** then the docid is added to pOut. If pOut is of type DL_POSITIONS,
+** then a positionids "6" and "21" are also added to pOut.
+**
+** If boolean argument isSaveLeft is true, then positionids are copied
+** from pLeft instead of pRight. In the example above, the positions "5"
+** and "20" would be added instead of "6" and "21".
+*/
+static void posListPhraseMerge(
+ DLReader *pLeft,
+ DLReader *pRight,
+ int nNear,
+ int isSaveLeft,
+ DLWriter *pOut
+){
+ PLReader left, right;
+ PLWriter writer;
+ int match = 0;
+
+ assert( dlrDocid(pLeft)==dlrDocid(pRight) );
+ assert( pOut->iType!=DL_POSITIONS_OFFSETS );
+
+ plrInit(&left, pLeft);
+ plrInit(&right, pRight);
+
+ while( !plrAtEnd(&left) && !plrAtEnd(&right) ){
+ if( plrColumn(&left)<plrColumn(&right) ){
+ plrStep(&left);
+ }else if( plrColumn(&left)>plrColumn(&right) ){
+ plrStep(&right);
+ }else if( plrPosition(&left)>=plrPosition(&right) ){
+ plrStep(&right);
+ }else{
+ if( (plrPosition(&right)-plrPosition(&left))<=(nNear+1) ){
+ if( !match ){
+ plwInit(&writer, pOut, dlrDocid(pLeft));
+ match = 1;
+ }
+ if( !isSaveLeft ){
+ plwAdd(&writer, plrColumn(&right), plrPosition(&right), 0, 0);
+ }else{
+ plwAdd(&writer, plrColumn(&left), plrPosition(&left), 0, 0);
+ }
+ plrStep(&right);
+ }else{
+ plrStep(&left);
+ }
+ }
+ }
+
+ if( match ){
+ plwTerminate(&writer);
+ plwDestroy(&writer);
+ }
+
+ plrDestroy(&left);
+ plrDestroy(&right);
+}
+
+/*
+** Compare the values pointed to by the PLReaders passed as arguments.
+** Return -1 if the value pointed to by pLeft is considered less than
+** the value pointed to by pRight, +1 if it is considered greater
+** than it, or 0 if it is equal. i.e.
+**
+** (*pLeft - *pRight)
+**
+** A PLReader that is in the EOF condition is considered greater than
+** any other. If neither argument is in EOF state, the return value of
+** plrColumn() is used. If the plrColumn() values are equal, the
+** comparison is on the basis of plrPosition().
+*/
+static int plrCompare(PLReader *pLeft, PLReader *pRight){
+ assert(!plrAtEnd(pLeft) || !plrAtEnd(pRight));
+
+ if( plrAtEnd(pRight) || plrAtEnd(pLeft) ){
+ return (plrAtEnd(pRight) ? -1 : 1);
+ }
+ if( plrColumn(pLeft)!=plrColumn(pRight) ){
+ return ((plrColumn(pLeft)<plrColumn(pRight)) ? -1 : 1);
+ }
+ if( plrPosition(pLeft)!=plrPosition(pRight) ){
+ return ((plrPosition(pLeft)<plrPosition(pRight)) ? -1 : 1);
+ }
+ return 0;
+}
+
+/* We have two doclists with positions: pLeft and pRight. Depending
+** on the value of the nNear parameter, perform either a phrase
+** intersection (if nNear==0) or a NEAR intersection (if nNear>0)
+** and write the results into pOut.
+**
+** A phrase intersection means that two documents only match
+** if pLeft.iPos+1==pRight.iPos.
+**
+** A NEAR intersection means that two documents only match if
+** (abs(pLeft.iPos-pRight.iPos)<nNear).
+**
+** If a NEAR intersection is requested, then the nPhrase argument should
+** be passed the number of tokens in the two operands to the NEAR operator
+** combined. For example:
+**
+** Query syntax nPhrase
+** ------------------------------------
+** "A B C" NEAR "D E" 5
+** A NEAR B 2
+**
+** iType controls the type of data written to pOut. If iType is
+** DL_POSITIONS, the positions are those from pRight.
+*/
+static void docListPhraseMerge(
+ const char *pLeft, int nLeft,
+ const char *pRight, int nRight,
+ int nNear, /* 0 for a phrase merge, non-zero for a NEAR merge */
+ int nPhrase, /* Number of tokens in left+right operands to NEAR */
+ DocListType iType, /* Type of doclist to write to pOut */
+ DataBuffer *pOut /* Write the combined doclist here */
+){
+ DLReader left, right;
+ DLWriter writer;
+
+ if( nLeft==0 || nRight==0 ) return;
+
+ assert( iType!=DL_POSITIONS_OFFSETS );
+
+ dlrInit(&left, DL_POSITIONS, pLeft, nLeft);
+ dlrInit(&right, DL_POSITIONS, pRight, nRight);
+ dlwInit(&writer, iType, pOut);
+
+ while( !dlrAtEnd(&left) && !dlrAtEnd(&right) ){
+ if( dlrDocid(&left)<dlrDocid(&right) ){
+ dlrStep(&left);
+ }else if( dlrDocid(&right)<dlrDocid(&left) ){
+ dlrStep(&right);
+ }else{
+ if( nNear==0 ){
+ posListPhraseMerge(&left, &right, 0, 0, &writer);
+ }else{
+ /* This case occurs when two terms (simple terms or phrases) are
+ * connected by a NEAR operator, span (nNear+1). i.e.
+ *
+ * '"terrible company" NEAR widget'
+ */
+ DataBuffer one = {0, 0, 0};
+ DataBuffer two = {0, 0, 0};
+
+ DLWriter dlwriter2;
+ DLReader dr1 = {0, 0, 0, 0, 0};
+ DLReader dr2 = {0, 0, 0, 0, 0};
+
+ dlwInit(&dlwriter2, iType, &one);
+ posListPhraseMerge(&right, &left, nNear-3+nPhrase, 1, &dlwriter2);
+ dlwInit(&dlwriter2, iType, &two);
+ posListPhraseMerge(&left, &right, nNear-1, 0, &dlwriter2);
+
+ if( one.nData) dlrInit(&dr1, iType, one.pData, one.nData);
+ if( two.nData) dlrInit(&dr2, iType, two.pData, two.nData);
+
+ if( !dlrAtEnd(&dr1) || !dlrAtEnd(&dr2) ){
+ PLReader pr1 = {0};
+ PLReader pr2 = {0};
+
+ PLWriter plwriter;
+ plwInit(&plwriter, &writer, dlrDocid(dlrAtEnd(&dr1)?&dr2:&dr1));
+
+ if( one.nData ) plrInit(&pr1, &dr1);
+ if( two.nData ) plrInit(&pr2, &dr2);
+ while( !plrAtEnd(&pr1) || !plrAtEnd(&pr2) ){
+ int iCompare = plrCompare(&pr1, &pr2);
+ switch( iCompare ){
+ case -1:
+ plwCopy(&plwriter, &pr1);
+ plrStep(&pr1);
+ break;
+ case 1:
+ plwCopy(&plwriter, &pr2);
+ plrStep(&pr2);
+ break;
+ case 0:
+ plwCopy(&plwriter, &pr1);
+ plrStep(&pr1);
+ plrStep(&pr2);
+ break;
+ }
+ }
+ plwTerminate(&plwriter);
+ }
+ dataBufferDestroy(&one);
+ dataBufferDestroy(&two);
+ }
+ dlrStep(&left);
+ dlrStep(&right);
+ }
+ }
+
+ dlrDestroy(&left);
+ dlrDestroy(&right);
+ dlwDestroy(&writer);
+}
+
+/* We have two DL_DOCIDS doclists: pLeft and pRight.
+** Write the intersection of these two doclists into pOut as a
+** DL_DOCIDS doclist.
+*/
+static void docListAndMerge(
+ const char *pLeft, int nLeft,
+ const char *pRight, int nRight,
+ DataBuffer *pOut /* Write the combined doclist here */
+){
+ DLReader left, right;
+ DLWriter writer;
+
+ if( nLeft==0 || nRight==0 ) return;
+
+ dlrInit(&left, DL_DOCIDS, pLeft, nLeft);
+ dlrInit(&right, DL_DOCIDS, pRight, nRight);
+ dlwInit(&writer, DL_DOCIDS, pOut);
+
+ while( !dlrAtEnd(&left) && !dlrAtEnd(&right) ){
+ if( dlrDocid(&left)<dlrDocid(&right) ){
+ dlrStep(&left);
+ }else if( dlrDocid(&right)<dlrDocid(&left) ){
+ dlrStep(&right);
+ }else{
+ dlwAdd(&writer, dlrDocid(&left));
+ dlrStep(&left);
+ dlrStep(&right);
+ }
+ }
+
+ dlrDestroy(&left);
+ dlrDestroy(&right);
+ dlwDestroy(&writer);
+}
+
+/* We have two DL_DOCIDS doclists: pLeft and pRight.
+** Write the union of these two doclists into pOut as a
+** DL_DOCIDS doclist.
+*/
+static void docListOrMerge(
+ const char *pLeft, int nLeft,
+ const char *pRight, int nRight,
+ DataBuffer *pOut /* Write the combined doclist here */
+){
+ DLReader left, right;
+ DLWriter writer;
+
+ if( nLeft==0 ){
+ if( nRight!=0 ) dataBufferAppend(pOut, pRight, nRight);
+ return;
+ }
+ if( nRight==0 ){
+ dataBufferAppend(pOut, pLeft, nLeft);
+ return;
+ }
+
+ dlrInit(&left, DL_DOCIDS, pLeft, nLeft);
+ dlrInit(&right, DL_DOCIDS, pRight, nRight);
+ dlwInit(&writer, DL_DOCIDS, pOut);
+
+ while( !dlrAtEnd(&left) || !dlrAtEnd(&right) ){
+ if( dlrAtEnd(&right) ){
+ dlwAdd(&writer, dlrDocid(&left));
+ dlrStep(&left);
+ }else if( dlrAtEnd(&left) ){
+ dlwAdd(&writer, dlrDocid(&right));
+ dlrStep(&right);
+ }else if( dlrDocid(&left)<dlrDocid(&right) ){
+ dlwAdd(&writer, dlrDocid(&left));
+ dlrStep(&left);
+ }else if( dlrDocid(&right)<dlrDocid(&left) ){
+ dlwAdd(&writer, dlrDocid(&right));
+ dlrStep(&right);
+ }else{
+ dlwAdd(&writer, dlrDocid(&left));
+ dlrStep(&left);
+ dlrStep(&right);
+ }
+ }
+
+ dlrDestroy(&left);
+ dlrDestroy(&right);
+ dlwDestroy(&writer);
+}
+
+/* We have two DL_DOCIDS doclists: pLeft and pRight.
+** Write into pOut as DL_DOCIDS doclist containing all documents that
+** occur in pLeft but not in pRight.
+*/
+static void docListExceptMerge(
+ const char *pLeft, int nLeft,
+ const char *pRight, int nRight,
+ DataBuffer *pOut /* Write the combined doclist here */
+){
+ DLReader left, right;
+ DLWriter writer;
+
+ if( nLeft==0 ) return;
+ if( nRight==0 ){
+ dataBufferAppend(pOut, pLeft, nLeft);
+ return;
+ }
+
+ dlrInit(&left, DL_DOCIDS, pLeft, nLeft);
+ dlrInit(&right, DL_DOCIDS, pRight, nRight);
+ dlwInit(&writer, DL_DOCIDS, pOut);
+
+ while( !dlrAtEnd(&left) ){
+ while( !dlrAtEnd(&right) && dlrDocid(&right)<dlrDocid(&left) ){
+ dlrStep(&right);
+ }
+ if( dlrAtEnd(&right) || dlrDocid(&left)<dlrDocid(&right) ){
+ dlwAdd(&writer, dlrDocid(&left));
+ }
+ dlrStep(&left);
+ }
+
+ dlrDestroy(&left);
+ dlrDestroy(&right);
+ dlwDestroy(&writer);
+}
+
+static char *string_dup_n(const char *s, int n){
+ char *str = sqlite3_malloc(n + 1);
+ memcpy(str, s, n);
+ str[n] = '\0';
+ return str;
+}
+
+/* Duplicate a string; the caller must free() the returned string.
+ * (We don't use strdup() since it is not part of the standard C library and
+ * may not be available everywhere.) */
+static char *string_dup(const char *s){
+ return string_dup_n(s, strlen(s));
+}
+
+/* Format a string, replacing each occurrence of the % character with
+ * zDb.zName. This may be more convenient than sqlite_mprintf()
+ * when one string is used repeatedly in a format string.
+ * The caller must free() the returned string. */
+static char *string_format(const char *zFormat,
+ const char *zDb, const char *zName){
+ const char *p;
+ size_t len = 0;
+ size_t nDb = strlen(zDb);
+ size_t nName = strlen(zName);
+ size_t nFullTableName = nDb+1+nName;
+ char *result;
+ char *r;
+
+ /* first compute length needed */
+ for(p = zFormat ; *p ; ++p){
+ len += (*p=='%' ? nFullTableName : 1);
+ }
+ len += 1; /* for null terminator */
+
+ r = result = sqlite3_malloc(len);
+ for(p = zFormat; *p; ++p){
+ if( *p=='%' ){
+ memcpy(r, zDb, nDb);
+ r += nDb;
+ *r++ = '.';
+ memcpy(r, zName, nName);
+ r += nName;
+ } else {
+ *r++ = *p;
+ }
+ }
+ *r++ = '\0';
+ assert( r == result + len );
+ return result;
+}
+
+static int sql_exec(sqlite3 *db, const char *zDb, const char *zName,
+ const char *zFormat){
+ char *zCommand = string_format(zFormat, zDb, zName);
+ int rc;
+ FTSTRACE(("FTS3 sql: %s\n", zCommand));
+ rc = sqlite3_exec(db, zCommand, NULL, 0, NULL);
+ sqlite3_free(zCommand);
+ return rc;
+}
+
+static int sql_prepare(sqlite3 *db, const char *zDb, const char *zName,
+ sqlite3_stmt **ppStmt, const char *zFormat){
+ char *zCommand = string_format(zFormat, zDb, zName);
+ int rc;
+ FTSTRACE(("FTS3 prepare: %s\n", zCommand));
+ rc = sqlite3_prepare_v2(db, zCommand, -1, ppStmt, NULL);
+ sqlite3_free(zCommand);
+ return rc;
+}
+
+/* end utility functions */
+
+/* Forward reference */
+typedef struct fulltext_vtab fulltext_vtab;
+
+/* A single term in a query is represented by an instances of
+** the following structure. Each word which may match against
+** document content is a term. Operators, like NEAR or OR, are
+** not terms. Query terms are organized as a flat list stored
+** in the Query.pTerms array.
+**
+** If the QueryTerm.nPhrase variable is non-zero, then the QueryTerm
+** is the first in a contiguous string of terms that are either part
+** of the same phrase, or connected by the NEAR operator.
+**
+** If the QueryTerm.nNear variable is non-zero, then the token is followed
+** by a NEAR operator with span set to (nNear-1). For example, the
+** following query:
+**
+** The QueryTerm.iPhrase variable stores the index of the token within
+** its phrase, indexed starting at 1, or 1 if the token is not part
+** of any phrase.
+**
+** For example, the data structure used to represent the following query:
+**
+** ... MATCH 'sqlite NEAR/5 google NEAR/2 "search engine"'
+**
+** is:
+**
+** {nPhrase=4, iPhrase=1, nNear=6, pTerm="sqlite"},
+** {nPhrase=0, iPhrase=1, nNear=3, pTerm="google"},
+** {nPhrase=0, iPhrase=1, nNear=0, pTerm="search"},
+** {nPhrase=0, iPhrase=2, nNear=0, pTerm="engine"},
+**
+** compiling the FTS3 syntax to Query structures is done by the parseQuery()
+** function.
+*/
+typedef struct QueryTerm {
+ short int nPhrase; /* How many following terms are part of the same phrase */
+ short int iPhrase; /* This is the i-th term of a phrase. */
+ short int iColumn; /* Column of the index that must match this term */
+ signed char nNear; /* term followed by a NEAR operator with span=(nNear-1) */
+ signed char isOr; /* this term is preceded by "OR" */
+ signed char isNot; /* this term is preceded by "-" */
+ signed char isPrefix; /* this term is followed by "*" */
+ char *pTerm; /* text of the term. '\000' terminated. malloced */
+ int nTerm; /* Number of bytes in pTerm[] */
+} QueryTerm;
+
+
+/* A query string is parsed into a Query structure.
+ *
+ * We could, in theory, allow query strings to be complicated
+ * nested expressions with precedence determined by parentheses.
+ * But none of the major search engines do this. (Perhaps the
+ * feeling is that an parenthesized expression is two complex of
+ * an idea for the average user to grasp.) Taking our lead from
+ * the major search engines, we will allow queries to be a list
+ * of terms (with an implied AND operator) or phrases in double-quotes,
+ * with a single optional "-" before each non-phrase term to designate
+ * negation and an optional OR connector.
+ *
+ * OR binds more tightly than the implied AND, which is what the
+ * major search engines seem to do. So, for example:
+ *
+ * [one two OR three] ==> one AND (two OR three)
+ * [one OR two three] ==> (one OR two) AND three
+ *
+ * A "-" before a term matches all entries that lack that term.
+ * The "-" must occur immediately before the term with in intervening
+ * space. This is how the search engines do it.
+ *
+ * A NOT term cannot be the right-hand operand of an OR. If this
+ * occurs in the query string, the NOT is ignored:
+ *
+ * [one OR -two] ==> one OR two
+ *
+ */
+typedef struct Query {
+ fulltext_vtab *pFts; /* The full text index */
+ int nTerms; /* Number of terms in the query */
+ QueryTerm *pTerms; /* Array of terms. Space obtained from malloc() */
+ int nextIsOr; /* Set the isOr flag on the next inserted term */
+ int nextIsNear; /* Set the isOr flag on the next inserted term */
+ int nextColumn; /* Next word parsed must be in this column */
+ int dfltColumn; /* The default column */
+} Query;
+
+
+/*
+** An instance of the following structure keeps track of generated
+** matching-word offset information and snippets.
+*/
+typedef struct Snippet {
+ int nMatch; /* Total number of matches */
+ int nAlloc; /* Space allocated for aMatch[] */
+ struct snippetMatch { /* One entry for each matching term */
+ char snStatus; /* Status flag for use while constructing snippets */
+ short int iCol; /* The column that contains the match */
+ short int iTerm; /* The index in Query.pTerms[] of the matching term */
+ int iToken; /* The index of the matching document token */
+ short int nByte; /* Number of bytes in the term */
+ int iStart; /* The offset to the first character of the term */
+ } *aMatch; /* Points to space obtained from malloc */
+ char *zOffset; /* Text rendering of aMatch[] */
+ int nOffset; /* strlen(zOffset) */
+ char *zSnippet; /* Snippet text */
+ int nSnippet; /* strlen(zSnippet) */
+} Snippet;
+
+
+typedef enum QueryType {
+ QUERY_GENERIC, /* table scan */
+ QUERY_DOCID, /* lookup by docid */
+ QUERY_FULLTEXT /* QUERY_FULLTEXT + [i] is a full-text search for column i*/
+} QueryType;
+
+typedef enum fulltext_statement {
+ CONTENT_INSERT_STMT,
+ CONTENT_SELECT_STMT,
+ CONTENT_UPDATE_STMT,
+ CONTENT_DELETE_STMT,
+
+ BLOCK_INSERT_STMT,
+ BLOCK_SELECT_STMT,
+ BLOCK_DELETE_STMT,
+
+ SEGDIR_MAX_INDEX_STMT,
+ SEGDIR_SET_STMT,
+ SEGDIR_SELECT_STMT,
+ SEGDIR_SPAN_STMT,
+ SEGDIR_DELETE_STMT,
+ SEGDIR_SELECT_ALL_STMT,
+
+ MAX_STMT /* Always at end! */
+} fulltext_statement;
+
+/* These must exactly match the enum above. */
+/* TODO(shess): Is there some risk that a statement will be used in two
+** cursors at once, e.g. if a query joins a virtual table to itself?
+** If so perhaps we should move some of these to the cursor object.
+*/
+static const char *const fulltext_zStatement[MAX_STMT] = {
+ /* CONTENT_INSERT */ NULL, /* generated in contentInsertStatement() */
+ /* CONTENT_SELECT */ NULL, /* generated in contentSelectStatement() */
+ /* CONTENT_UPDATE */ NULL, /* generated in contentUpdateStatement() */
+ /* CONTENT_DELETE */ "delete from %_content where docid = ?",
+
+ /* BLOCK_INSERT */
+ "insert into %_segments (blockid, block) values (null, ?)",
+ /* BLOCK_SELECT */ "select block from %_segments where blockid = ?",
+ /* BLOCK_DELETE */ "delete from %_segments where blockid between ? and ?",
+
+ /* SEGDIR_MAX_INDEX */ "select max(idx) from %_segdir where level = ?",
+ /* SEGDIR_SET */ "insert into %_segdir values (?, ?, ?, ?, ?, ?)",
+ /* SEGDIR_SELECT */
+ "select start_block, leaves_end_block, root from %_segdir "
+ " where level = ? order by idx",
+ /* SEGDIR_SPAN */
+ "select min(start_block), max(end_block) from %_segdir "
+ " where level = ? and start_block <> 0",
+ /* SEGDIR_DELETE */ "delete from %_segdir where level = ?",
+ /* SEGDIR_SELECT_ALL */
+ "select root, leaves_end_block from %_segdir order by level desc, idx",
+};
+
+/*
+** A connection to a fulltext index is an instance of the following
+** structure. The xCreate and xConnect methods create an instance
+** of this structure and xDestroy and xDisconnect free that instance.
+** All other methods receive a pointer to the structure as one of their
+** arguments.
+*/
+struct fulltext_vtab {
+ sqlite3_vtab base; /* Base class used by SQLite core */
+ sqlite3 *db; /* The database connection */
+ const char *zDb; /* logical database name */
+ const char *zName; /* virtual table name */
+ int nColumn; /* number of columns in virtual table */
+ char **azColumn; /* column names. malloced */
+ char **azContentColumn; /* column names in content table; malloced */
+ sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */
+
+ /* Precompiled statements which we keep as long as the table is
+ ** open.
+ */
+ sqlite3_stmt *pFulltextStatements[MAX_STMT];
+
+ /* Precompiled statements used for segment merges. We run a
+ ** separate select across the leaf level of each tree being merged.
+ */
+ sqlite3_stmt *pLeafSelectStmts[MERGE_COUNT];
+ /* The statement used to prepare pLeafSelectStmts. */
+#define LEAF_SELECT \
+ "select block from %_segments where blockid between ? and ? order by blockid"
+
+ /* These buffer pending index updates during transactions.
+ ** nPendingData estimates the memory size of the pending data. It
+ ** doesn't include the hash-bucket overhead, nor any malloc
+ ** overhead. When nPendingData exceeds kPendingThreshold, the
+ ** buffer is flushed even before the transaction closes.
+ ** pendingTerms stores the data, and is only valid when nPendingData
+ ** is >=0 (nPendingData<0 means pendingTerms has not been
+ ** initialized). iPrevDocid is the last docid written, used to make
+ ** certain we're inserting in sorted order.
+ */
+ int nPendingData;
+#define kPendingThreshold (1*1024*1024)
+ sqlite_int64 iPrevDocid;
+ fts3Hash pendingTerms;
+};
+
+/*
+** When the core wants to do a query, it create a cursor using a
+** call to xOpen. This structure is an instance of a cursor. It
+** is destroyed by xClose.
+*/
+typedef struct fulltext_cursor {
+ sqlite3_vtab_cursor base; /* Base class used by SQLite core */
+ QueryType iCursorType; /* Copy of sqlite3_index_info.idxNum */
+ sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */
+ int eof; /* True if at End Of Results */
+ Query q; /* Parsed query string */
+ Snippet snippet; /* Cached snippet for the current row */
+ int iColumn; /* Column being searched */
+ DataBuffer result; /* Doclist results from fulltextQuery */
+ DLReader reader; /* Result reader if result not empty */
+} fulltext_cursor;
+
+static struct fulltext_vtab *cursor_vtab(fulltext_cursor *c){
+ return (fulltext_vtab *) c->base.pVtab;
+}
+
+static const sqlite3_module fts3Module; /* forward declaration */
+
+/* Return a dynamically generated statement of the form
+ * insert into %_content (docid, ...) values (?, ...)
+ */
+static const char *contentInsertStatement(fulltext_vtab *v){
+ StringBuffer sb;
+ int i;
+
+ initStringBuffer(&sb);
+ append(&sb, "insert into %_content (docid, ");
+ appendList(&sb, v->nColumn, v->azContentColumn);
+ append(&sb, ") values (?");
+ for(i=0; i<v->nColumn; ++i)
+ append(&sb, ", ?");
+ append(&sb, ")");
+ return stringBufferData(&sb);
+}
+
+/* Return a dynamically generated statement of the form
+ * select <content columns> from %_content where docid = ?
+ */
+static const char *contentSelectStatement(fulltext_vtab *v){
+ StringBuffer sb;
+ initStringBuffer(&sb);
+ append(&sb, "SELECT ");
+ appendList(&sb, v->nColumn, v->azContentColumn);
+ append(&sb, " FROM %_content WHERE docid = ?");
+ return stringBufferData(&sb);
+}
+
+/* Return a dynamically generated statement of the form
+ * update %_content set [col_0] = ?, [col_1] = ?, ...
+ * where docid = ?
+ */
+static const char *contentUpdateStatement(fulltext_vtab *v){
+ StringBuffer sb;
+ int i;
+
+ initStringBuffer(&sb);
+ append(&sb, "update %_content set ");
+ for(i=0; i<v->nColumn; ++i) {
+ if( i>0 ){
+ append(&sb, ", ");
+ }
+ append(&sb, v->azContentColumn[i]);
+ append(&sb, " = ?");
+ }
+ append(&sb, " where docid = ?");
+ return stringBufferData(&sb);
+}
+
+/* Puts a freshly-prepared statement determined by iStmt in *ppStmt.
+** If the indicated statement has never been prepared, it is prepared
+** and cached, otherwise the cached version is reset.
+*/
+static int sql_get_statement(fulltext_vtab *v, fulltext_statement iStmt,
+ sqlite3_stmt **ppStmt){
+ assert( iStmt<MAX_STMT );
+ if( v->pFulltextStatements[iStmt]==NULL ){
+ const char *zStmt;
+ int rc;
+ switch( iStmt ){
+ case CONTENT_INSERT_STMT:
+ zStmt = contentInsertStatement(v); break;
+ case CONTENT_SELECT_STMT:
+ zStmt = contentSelectStatement(v); break;
+ case CONTENT_UPDATE_STMT:
+ zStmt = contentUpdateStatement(v); break;
+ default:
+ zStmt = fulltext_zStatement[iStmt];
+ }
+ rc = sql_prepare(v->db, v->zDb, v->zName, &v->pFulltextStatements[iStmt],
+ zStmt);
+ if( zStmt != fulltext_zStatement[iStmt]) sqlite3_free((void *) zStmt);
+ if( rc!=SQLITE_OK ) return rc;
+ } else {
+ int rc = sqlite3_reset(v->pFulltextStatements[iStmt]);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ *ppStmt = v->pFulltextStatements[iStmt];
+ return SQLITE_OK;
+}
+
+/* Like sqlite3_step(), but convert SQLITE_DONE to SQLITE_OK and
+** SQLITE_ROW to SQLITE_ERROR. Useful for statements like UPDATE,
+** where we expect no results.
+*/
+static int sql_single_step(sqlite3_stmt *s){
+ int rc = sqlite3_step(s);
+ return (rc==SQLITE_DONE) ? SQLITE_OK : rc;
+}
+
+/* Like sql_get_statement(), but for special replicated LEAF_SELECT
+** statements.
+*/
+/* TODO(shess) Write version for generic statements and then share
+** that between the cached-statement functions.
+*/
+static int sql_get_leaf_statement(fulltext_vtab *v, int idx,
+ sqlite3_stmt **ppStmt){
+ assert( idx>=0 && idx<MERGE_COUNT );
+ if( v->pLeafSelectStmts[idx]==NULL ){
+ int rc = sql_prepare(v->db, v->zDb, v->zName, &v->pLeafSelectStmts[idx],
+ LEAF_SELECT);
+ if( rc!=SQLITE_OK ) return rc;
+ }else{
+ int rc = sqlite3_reset(v->pLeafSelectStmts[idx]);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ *ppStmt = v->pLeafSelectStmts[idx];
+ return SQLITE_OK;
+}
+
+/* insert into %_content (docid, ...) values ([docid], [pValues])
+** If the docid contains SQL NULL, then a unique docid will be
+** generated.
+*/
+static int content_insert(fulltext_vtab *v, sqlite3_value *docid,
+ sqlite3_value **pValues){
+ sqlite3_stmt *s;
+ int i;
+ int rc = sql_get_statement(v, CONTENT_INSERT_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_value(s, 1, docid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ for(i=0; i<v->nColumn; ++i){
+ rc = sqlite3_bind_value(s, 2+i, pValues[i]);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ return sql_single_step(s);
+}
+
+/* update %_content set col0 = pValues[0], col1 = pValues[1], ...
+ * where docid = [iDocid] */
+static int content_update(fulltext_vtab *v, sqlite3_value **pValues,
+ sqlite_int64 iDocid){
+ sqlite3_stmt *s;
+ int i;
+ int rc = sql_get_statement(v, CONTENT_UPDATE_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ for(i=0; i<v->nColumn; ++i){
+ rc = sqlite3_bind_value(s, 1+i, pValues[i]);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ rc = sqlite3_bind_int64(s, 1+v->nColumn, iDocid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ return sql_single_step(s);
+}
+
+static void freeStringArray(int nString, const char **pString){
+ int i;
+
+ for (i=0 ; i < nString ; ++i) {
+ if( pString[i]!=NULL ) sqlite3_free((void *) pString[i]);
+ }
+ sqlite3_free((void *) pString);
+}
+
+/* select * from %_content where docid = [iDocid]
+ * The caller must delete the returned array and all strings in it.
+ * null fields will be NULL in the returned array.
+ *
+ * TODO: Perhaps we should return pointer/length strings here for consistency
+ * with other code which uses pointer/length. */
+static int content_select(fulltext_vtab *v, sqlite_int64 iDocid,
+ const char ***pValues){
+ sqlite3_stmt *s;
+ const char **values;
+ int i;
+ int rc;
+
+ *pValues = NULL;
+
+ rc = sql_get_statement(v, CONTENT_SELECT_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 1, iDocid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_step(s);
+ if( rc!=SQLITE_ROW ) return rc;
+
+ values = (const char **) sqlite3_malloc(v->nColumn * sizeof(const char *));
+ for(i=0; i<v->nColumn; ++i){
+ if( sqlite3_column_type(s, i)==SQLITE_NULL ){
+ values[i] = NULL;
+ }else{
+ values[i] = string_dup((char*)sqlite3_column_text(s, i));
+ }
+ }
+
+ /* We expect only one row. We must execute another sqlite3_step()
+ * to complete the iteration; otherwise the table will remain locked. */
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_DONE ){
+ *pValues = values;
+ return SQLITE_OK;
+ }
+
+ freeStringArray(v->nColumn, values);
+ return rc;
+}
+
+/* delete from %_content where docid = [iDocid ] */
+static int content_delete(fulltext_vtab *v, sqlite_int64 iDocid){
+ sqlite3_stmt *s;
+ int rc = sql_get_statement(v, CONTENT_DELETE_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 1, iDocid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ return sql_single_step(s);
+}
+
+/* insert into %_segments values ([pData])
+** returns assigned blockid in *piBlockid
+*/
+static int block_insert(fulltext_vtab *v, const char *pData, int nData,
+ sqlite_int64 *piBlockid){
+ sqlite3_stmt *s;
+ int rc = sql_get_statement(v, BLOCK_INSERT_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_blob(s, 1, pData, nData, SQLITE_STATIC);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_ROW ) return SQLITE_ERROR;
+ if( rc!=SQLITE_DONE ) return rc;
+
+ /* blockid column is an alias for rowid. */
+ *piBlockid = sqlite3_last_insert_rowid(v->db);
+ return SQLITE_OK;
+}
+
+/* delete from %_segments
+** where blockid between [iStartBlockid] and [iEndBlockid]
+**
+** Deletes the range of blocks, inclusive, used to delete the blocks
+** which form a segment.
+*/
+static int block_delete(fulltext_vtab *v,
+ sqlite_int64 iStartBlockid, sqlite_int64 iEndBlockid){
+ sqlite3_stmt *s;
+ int rc = sql_get_statement(v, BLOCK_DELETE_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 1, iStartBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 2, iEndBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ return sql_single_step(s);
+}
+
+/* Returns SQLITE_ROW with *pidx set to the maximum segment idx found
+** at iLevel. Returns SQLITE_DONE if there are no segments at
+** iLevel. Otherwise returns an error.
+*/
+static int segdir_max_index(fulltext_vtab *v, int iLevel, int *pidx){
+ sqlite3_stmt *s;
+ int rc = sql_get_statement(v, SEGDIR_MAX_INDEX_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int(s, 1, iLevel);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_step(s);
+ /* Should always get at least one row due to how max() works. */
+ if( rc==SQLITE_DONE ) return SQLITE_DONE;
+ if( rc!=SQLITE_ROW ) return rc;
+
+ /* NULL means that there were no inputs to max(). */
+ if( SQLITE_NULL==sqlite3_column_type(s, 0) ){
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_ROW ) return SQLITE_ERROR;
+ return rc;
+ }
+
+ *pidx = sqlite3_column_int(s, 0);
+
+ /* We expect only one row. We must execute another sqlite3_step()
+ * to complete the iteration; otherwise the table will remain locked. */
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_ROW ) return SQLITE_ERROR;
+ if( rc!=SQLITE_DONE ) return rc;
+ return SQLITE_ROW;
+}
+
+/* insert into %_segdir values (
+** [iLevel], [idx],
+** [iStartBlockid], [iLeavesEndBlockid], [iEndBlockid],
+** [pRootData]
+** )
+*/
+static int segdir_set(fulltext_vtab *v, int iLevel, int idx,
+ sqlite_int64 iStartBlockid,
+ sqlite_int64 iLeavesEndBlockid,
+ sqlite_int64 iEndBlockid,
+ const char *pRootData, int nRootData){
+ sqlite3_stmt *s;
+ int rc = sql_get_statement(v, SEGDIR_SET_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int(s, 1, iLevel);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int(s, 2, idx);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 3, iStartBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 4, iLeavesEndBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 5, iEndBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_blob(s, 6, pRootData, nRootData, SQLITE_STATIC);
+ if( rc!=SQLITE_OK ) return rc;
+
+ return sql_single_step(s);
+}
+
+/* Queries %_segdir for the block span of the segments in level
+** iLevel. Returns SQLITE_DONE if there are no blocks for iLevel,
+** SQLITE_ROW if there are blocks, else an error.
+*/
+static int segdir_span(fulltext_vtab *v, int iLevel,
+ sqlite_int64 *piStartBlockid,
+ sqlite_int64 *piEndBlockid){
+ sqlite3_stmt *s;
+ int rc = sql_get_statement(v, SEGDIR_SPAN_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int(s, 1, iLevel);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_DONE ) return SQLITE_DONE; /* Should never happen */
+ if( rc!=SQLITE_ROW ) return rc;
+
+ /* This happens if all segments at this level are entirely inline. */
+ if( SQLITE_NULL==sqlite3_column_type(s, 0) ){
+ /* We expect only one row. We must execute another sqlite3_step()
+ * to complete the iteration; otherwise the table will remain locked. */
+ int rc2 = sqlite3_step(s);
+ if( rc2==SQLITE_ROW ) return SQLITE_ERROR;
+ return rc2;
+ }
+
+ *piStartBlockid = sqlite3_column_int64(s, 0);
+ *piEndBlockid = sqlite3_column_int64(s, 1);
+
+ /* We expect only one row. We must execute another sqlite3_step()
+ * to complete the iteration; otherwise the table will remain locked. */
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_ROW ) return SQLITE_ERROR;
+ if( rc!=SQLITE_DONE ) return rc;
+ return SQLITE_ROW;
+}
+
+/* Delete the segment blocks and segment directory records for all
+** segments at iLevel.
+*/
+static int segdir_delete(fulltext_vtab *v, int iLevel){
+ sqlite3_stmt *s;
+ sqlite_int64 iStartBlockid, iEndBlockid;
+ int rc = segdir_span(v, iLevel, &iStartBlockid, &iEndBlockid);
+ if( rc!=SQLITE_ROW && rc!=SQLITE_DONE ) return rc;
+
+ if( rc==SQLITE_ROW ){
+ rc = block_delete(v, iStartBlockid, iEndBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ /* Delete the segment directory itself. */
+ rc = sql_get_statement(v, SEGDIR_DELETE_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 1, iLevel);
+ if( rc!=SQLITE_OK ) return rc;
+
+ return sql_single_step(s);
+}
+
+/* TODO(shess) clearPendingTerms() is far down the file because
+** writeZeroSegment() is far down the file because LeafWriter is far
+** down the file. Consider refactoring the code to move the non-vtab
+** code above the vtab code so that we don't need this forward
+** reference.
+*/
+static int clearPendingTerms(fulltext_vtab *v);
+
+/*
+** Free the memory used to contain a fulltext_vtab structure.
+*/
+static void fulltext_vtab_destroy(fulltext_vtab *v){
+ int iStmt, i;
+
+ FTSTRACE(("FTS3 Destroy %p\n", v));
+ for( iStmt=0; iStmt<MAX_STMT; iStmt++ ){
+ if( v->pFulltextStatements[iStmt]!=NULL ){
+ sqlite3_finalize(v->pFulltextStatements[iStmt]);
+ v->pFulltextStatements[iStmt] = NULL;
+ }
+ }
+
+ for( i=0; i<MERGE_COUNT; i++ ){
+ if( v->pLeafSelectStmts[i]!=NULL ){
+ sqlite3_finalize(v->pLeafSelectStmts[i]);
+ v->pLeafSelectStmts[i] = NULL;
+ }
+ }
+
+ if( v->pTokenizer!=NULL ){
+ v->pTokenizer->pModule->xDestroy(v->pTokenizer);
+ v->pTokenizer = NULL;
+ }
+
+ clearPendingTerms(v);
+
+ sqlite3_free(v->azColumn);
+ for(i = 0; i < v->nColumn; ++i) {
+ sqlite3_free(v->azContentColumn[i]);
+ }
+ sqlite3_free(v->azContentColumn);
+ sqlite3_free(v);
+}
+
+/*
+** Token types for parsing the arguments to xConnect or xCreate.
+*/
+#define TOKEN_EOF 0 /* End of file */
+#define TOKEN_SPACE 1 /* Any kind of whitespace */
+#define TOKEN_ID 2 /* An identifier */
+#define TOKEN_STRING 3 /* A string literal */
+#define TOKEN_PUNCT 4 /* A single punctuation character */
+
+/*
+** If X is a character that can be used in an identifier then
+** ftsIdChar(X) will be true. Otherwise it is false.
+**
+** For ASCII, any character with the high-order bit set is
+** allowed in an identifier. For 7-bit characters,
+** isFtsIdChar[X] must be 1.
+**
+** Ticket #1066. the SQL standard does not allow '$' in the
+** middle of identfiers. But many SQL implementations do.
+** SQLite will allow '$' in identifiers for compatibility.
+** But the feature is undocumented.
+*/
+static const char isFtsIdChar[] = {
+/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */
+};
+#define ftsIdChar(C) (((c=C)&0x80)!=0 || (c>0x1f && isFtsIdChar[c-0x20]))
+
+
+/*
+** Return the length of the token that begins at z[0].
+** Store the token type in *tokenType before returning.
+*/
+static int ftsGetToken(const char *z, int *tokenType){
+ int i, c;
+ switch( *z ){
+ case 0: {
+ *tokenType = TOKEN_EOF;
+ return 0;
+ }
+ case ' ': case '\t': case '\n': case '\f': case '\r': {
+ for(i=1; safe_isspace(z[i]); i++){}
+ *tokenType = TOKEN_SPACE;
+ return i;
+ }
+ case '`':
+ case '\'':
+ case '"': {
+ int delim = z[0];
+ for(i=1; (c=z[i])!=0; i++){
+ if( c==delim ){
+ if( z[i+1]==delim ){
+ i++;
+ }else{
+ break;
+ }
+ }
+ }
+ *tokenType = TOKEN_STRING;
+ return i + (c!=0);
+ }
+ case '[': {
+ for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){}
+ *tokenType = TOKEN_ID;
+ return i;
+ }
+ default: {
+ if( !ftsIdChar(*z) ){
+ break;
+ }
+ for(i=1; ftsIdChar(z[i]); i++){}
+ *tokenType = TOKEN_ID;
+ return i;
+ }
+ }
+ *tokenType = TOKEN_PUNCT;
+ return 1;
+}
+
+/*
+** A token extracted from a string is an instance of the following
+** structure.
+*/
+typedef struct FtsToken {
+ const char *z; /* Pointer to token text. Not '\000' terminated */
+ short int n; /* Length of the token text in bytes. */
+} FtsToken;
+
+/*
+** Given a input string (which is really one of the argv[] parameters
+** passed into xConnect or xCreate) split the string up into tokens.
+** Return an array of pointers to '\000' terminated strings, one string
+** for each non-whitespace token.
+**
+** The returned array is terminated by a single NULL pointer.
+**
+** Space to hold the returned array is obtained from a single
+** malloc and should be freed by passing the return value to free().
+** The individual strings within the token list are all a part of
+** the single memory allocation and will all be freed at once.
+*/
+static char **tokenizeString(const char *z, int *pnToken){
+ int nToken = 0;
+ FtsToken *aToken = sqlite3_malloc( strlen(z) * sizeof(aToken[0]) );
+ int n = 1;
+ int e, i;
+ int totalSize = 0;
+ char **azToken;
+ char *zCopy;
+ while( n>0 ){
+ n = ftsGetToken(z, &e);
+ if( e!=TOKEN_SPACE ){
+ aToken[nToken].z = z;
+ aToken[nToken].n = n;
+ nToken++;
+ totalSize += n+1;
+ }
+ z += n;
+ }
+ azToken = (char**)sqlite3_malloc( nToken*sizeof(char*) + totalSize );
+ zCopy = (char*)&azToken[nToken];
+ nToken--;
+ for(i=0; i<nToken; i++){
+ azToken[i] = zCopy;
+ n = aToken[i].n;
+ memcpy(zCopy, aToken[i].z, n);
+ zCopy[n] = 0;
+ zCopy += n+1;
+ }
+ azToken[nToken] = 0;
+ sqlite3_free(aToken);
+ *pnToken = nToken;
+ return azToken;
+}
+
+/*
+** Convert an SQL-style quoted string into a normal string by removing
+** the quote characters. The conversion is done in-place. If the
+** input does not begin with a quote character, then this routine
+** is a no-op.
+**
+** Examples:
+**
+** "abc" becomes abc
+** 'xyz' becomes xyz
+** [pqr] becomes pqr
+** `mno` becomes mno
+*/
+static void dequoteString(char *z){
+ int quote;
+ int i, j;
+ if( z==0 ) return;
+ quote = z[0];
+ switch( quote ){
+ case '\'': break;
+ case '"': break;
+ case '`': break; /* For MySQL compatibility */
+ case '[': quote = ']'; break; /* For MS SqlServer compatibility */
+ default: return;
+ }
+ for(i=1, j=0; z[i]; i++){
+ if( z[i]==quote ){
+ if( z[i+1]==quote ){
+ z[j++] = quote;
+ i++;
+ }else{
+ z[j++] = 0;
+ break;
+ }
+ }else{
+ z[j++] = z[i];
+ }
+ }
+}
+
+/*
+** The input azIn is a NULL-terminated list of tokens. Remove the first
+** token and all punctuation tokens. Remove the quotes from
+** around string literal tokens.
+**
+** Example:
+**
+** input: tokenize chinese ( 'simplifed' , 'mixed' )
+** output: chinese simplifed mixed
+**
+** Another example:
+**
+** input: delimiters ( '[' , ']' , '...' )
+** output: [ ] ...
+*/
+static void tokenListToIdList(char **azIn){
+ int i, j;
+ if( azIn ){
+ for(i=0, j=-1; azIn[i]; i++){
+ if( safe_isalnum(azIn[i][0]) || azIn[i][1] ){
+ dequoteString(azIn[i]);
+ if( j>=0 ){
+ azIn[j] = azIn[i];
+ }
+ j++;
+ }
+ }
+ azIn[j] = 0;
+ }
+}
+
+
+/*
+** Find the first alphanumeric token in the string zIn. Null-terminate
+** this token. Remove any quotation marks. And return a pointer to
+** the result.
+*/
+static char *firstToken(char *zIn, char **pzTail){
+ int n, ttype;
+ while(1){
+ n = ftsGetToken(zIn, &ttype);
+ if( ttype==TOKEN_SPACE ){
+ zIn += n;
+ }else if( ttype==TOKEN_EOF ){
+ *pzTail = zIn;
+ return 0;
+ }else{
+ zIn[n] = 0;
+ *pzTail = &zIn[1];
+ dequoteString(zIn);
+ return zIn;
+ }
+ }
+ /*NOTREACHED*/
+}
+
+/* Return true if...
+**
+** * s begins with the string t, ignoring case
+** * s is longer than t
+** * The first character of s beyond t is not a alphanumeric
+**
+** Ignore leading space in *s.
+**
+** To put it another way, return true if the first token of
+** s[] is t[].
+*/
+static int startsWith(const char *s, const char *t){
+ while( safe_isspace(*s) ){ s++; }
+ while( *t ){
+ if( safe_tolower(*s++)!=safe_tolower(*t++) ) return 0;
+ }
+ return *s!='_' && !safe_isalnum(*s);
+}
+
+/*
+** An instance of this structure defines the "spec" of a
+** full text index. This structure is populated by parseSpec
+** and use by fulltextConnect and fulltextCreate.
+*/
+typedef struct TableSpec {
+ const char *zDb; /* Logical database name */
+ const char *zName; /* Name of the full-text index */
+ int nColumn; /* Number of columns to be indexed */
+ char **azColumn; /* Original names of columns to be indexed */
+ char **azContentColumn; /* Column names for %_content */
+ char **azTokenizer; /* Name of tokenizer and its arguments */
+} TableSpec;
+
+/*
+** Reclaim all of the memory used by a TableSpec
+*/
+static void clearTableSpec(TableSpec *p) {
+ sqlite3_free(p->azColumn);
+ sqlite3_free(p->azContentColumn);
+ sqlite3_free(p->azTokenizer);
+}
+
+/* Parse a CREATE VIRTUAL TABLE statement, which looks like this:
+ *
+ * CREATE VIRTUAL TABLE email
+ * USING fts3(subject, body, tokenize mytokenizer(myarg))
+ *
+ * We return parsed information in a TableSpec structure.
+ *
+ */
+static int parseSpec(TableSpec *pSpec, int argc, const char *const*argv,
+ char**pzErr){
+ int i, n;
+ char *z, *zDummy;
+ char **azArg;
+ const char *zTokenizer = 0; /* argv[] entry describing the tokenizer */
+
+ assert( argc>=3 );
+ /* Current interface:
+ ** argv[0] - module name
+ ** argv[1] - database name
+ ** argv[2] - table name
+ ** argv[3..] - columns, optionally followed by tokenizer specification
+ ** and snippet delimiters specification.
+ */
+
+ /* Make a copy of the complete argv[][] array in a single allocation.
+ ** The argv[][] array is read-only and transient. We can write to the
+ ** copy in order to modify things and the copy is persistent.
+ */
+ CLEAR(pSpec);
+ for(i=n=0; i<argc; i++){
+ n += strlen(argv[i]) + 1;
+ }
+ azArg = sqlite3_malloc( sizeof(char*)*argc + n );
+ if( azArg==0 ){
+ return SQLITE_NOMEM;
+ }
+ z = (char*)&azArg[argc];
+ for(i=0; i<argc; i++){
+ azArg[i] = z;
+ strcpy(z, argv[i]);
+ z += strlen(z)+1;
+ }
+
+ /* Identify the column names and the tokenizer and delimiter arguments
+ ** in the argv[][] array.
+ */
+ pSpec->zDb = azArg[1];
+ pSpec->zName = azArg[2];
+ pSpec->nColumn = 0;
+ pSpec->azColumn = azArg;
+ zTokenizer = "tokenize simple";
+ for(i=3; i<argc; ++i){
+ if( startsWith(azArg[i],"tokenize") ){
+ zTokenizer = azArg[i];
+ }else{
+ z = azArg[pSpec->nColumn] = firstToken(azArg[i], &zDummy);
+ pSpec->nColumn++;
+ }
+ }
+ if( pSpec->nColumn==0 ){
+ azArg[0] = "content";
+ pSpec->nColumn = 1;
+ }
+
+ /*
+ ** Construct the list of content column names.
+ **
+ ** Each content column name will be of the form cNNAAAA
+ ** where NN is the column number and AAAA is the sanitized
+ ** column name. "sanitized" means that special characters are
+ ** converted to "_". The cNN prefix guarantees that all column
+ ** names are unique.
+ **
+ ** The AAAA suffix is not strictly necessary. It is included
+ ** for the convenience of people who might examine the generated
+ ** %_content table and wonder what the columns are used for.
+ */
+ pSpec->azContentColumn = sqlite3_malloc( pSpec->nColumn * sizeof(char *) );
+ if( pSpec->azContentColumn==0 ){
+ clearTableSpec(pSpec);
+ return SQLITE_NOMEM;
+ }
+ for(i=0; i<pSpec->nColumn; i++){
+ char *p;
+ pSpec->azContentColumn[i] = sqlite3_mprintf("c%d%s", i, azArg[i]);
+ for (p = pSpec->azContentColumn[i]; *p ; ++p) {
+ if( !safe_isalnum(*p) ) *p = '_';
+ }
+ }
+
+ /*
+ ** Parse the tokenizer specification string.
+ */
+ pSpec->azTokenizer = tokenizeString(zTokenizer, &n);
+ tokenListToIdList(pSpec->azTokenizer);
+
+ return SQLITE_OK;
+}
+
+/*
+** Generate a CREATE TABLE statement that describes the schema of
+** the virtual table. Return a pointer to this schema string.
+**
+** Space is obtained from sqlite3_mprintf() and should be freed
+** using sqlite3_free().
+*/
+static char *fulltextSchema(
+ int nColumn, /* Number of columns */
+ const char *const* azColumn, /* List of columns */
+ const char *zTableName /* Name of the table */
+){
+ int i;
+ char *zSchema, *zNext;
+ const char *zSep = "(";
+ zSchema = sqlite3_mprintf("CREATE TABLE x");
+ for(i=0; i<nColumn; i++){
+ zNext = sqlite3_mprintf("%s%s%Q", zSchema, zSep, azColumn[i]);
+ sqlite3_free(zSchema);
+ zSchema = zNext;
+ zSep = ",";
+ }
+ zNext = sqlite3_mprintf("%s,%Q HIDDEN", zSchema, zTableName);
+ sqlite3_free(zSchema);
+ zSchema = zNext;
+ zNext = sqlite3_mprintf("%s,docid HIDDEN)", zSchema);
+ sqlite3_free(zSchema);
+ return zNext;
+}
+
+/*
+** Build a new sqlite3_vtab structure that will describe the
+** fulltext index defined by spec.
+*/
+static int constructVtab(
+ sqlite3 *db, /* The SQLite database connection */
+ fts3Hash *pHash, /* Hash table containing tokenizers */
+ TableSpec *spec, /* Parsed spec information from parseSpec() */
+ sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
+ char **pzErr /* Write any error message here */
+){
+ int rc;
+ int n;
+ fulltext_vtab *v = 0;
+ const sqlite3_tokenizer_module *m = NULL;
+ char *schema;
+
+ char const *zTok; /* Name of tokenizer to use for this fts table */
+ int nTok; /* Length of zTok, including nul terminator */
+
+ v = (fulltext_vtab *) sqlite3_malloc(sizeof(fulltext_vtab));
+ if( v==0 ) return SQLITE_NOMEM;
+ CLEAR(v);
+ /* sqlite will initialize v->base */
+ v->db = db;
+ v->zDb = spec->zDb; /* Freed when azColumn is freed */
+ v->zName = spec->zName; /* Freed when azColumn is freed */
+ v->nColumn = spec->nColumn;
+ v->azContentColumn = spec->azContentColumn;
+ spec->azContentColumn = 0;
+ v->azColumn = spec->azColumn;
+ spec->azColumn = 0;
+
+ if( spec->azTokenizer==0 ){
+ return SQLITE_NOMEM;
+ }
+
+ zTok = spec->azTokenizer[0];
+ if( !zTok ){
+ zTok = "simple";
+ }
+ nTok = strlen(zTok)+1;
+
+ m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zTok, nTok);
+ if( !m ){
+ *pzErr = sqlite3_mprintf("unknown tokenizer: %s", spec->azTokenizer[0]);
+ rc = SQLITE_ERROR;
+ goto err;
+ }
+
+ for(n=0; spec->azTokenizer[n]; n++){}
+ if( n ){
+ rc = m->xCreate(n-1, (const char*const*)&spec->azTokenizer[1],
+ &v->pTokenizer);
+ }else{
+ rc = m->xCreate(0, 0, &v->pTokenizer);
+ }
+ if( rc!=SQLITE_OK ) goto err;
+ v->pTokenizer->pModule = m;
+
+ /* TODO: verify the existence of backing tables foo_content, foo_term */
+
+ schema = fulltextSchema(v->nColumn, (const char*const*)v->azColumn,
+ spec->zName);
+ rc = sqlite3_declare_vtab(db, schema);
+ sqlite3_free(schema);
+ if( rc!=SQLITE_OK ) goto err;
+
+ memset(v->pFulltextStatements, 0, sizeof(v->pFulltextStatements));
+
+ /* Indicate that the buffer is not live. */
+ v->nPendingData = -1;
+
+ *ppVTab = &v->base;
+ FTSTRACE(("FTS3 Connect %p\n", v));
+
+ return rc;
+
+err:
+ fulltext_vtab_destroy(v);
+ return rc;
+}
+
+static int fulltextConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVTab,
+ char **pzErr
+){
+ TableSpec spec;
+ int rc = parseSpec(&spec, argc, argv, pzErr);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = constructVtab(db, (fts3Hash *)pAux, &spec, ppVTab, pzErr);
+ clearTableSpec(&spec);
+ return rc;
+}
+
+/* The %_content table holds the text of each document, with
+** the docid column exposed as the SQLite rowid for the table.
+*/
+/* TODO(shess) This comment needs elaboration to match the updated
+** code. Work it into the top-of-file comment at that time.
+*/
+static int fulltextCreate(sqlite3 *db, void *pAux,
+ int argc, const char * const *argv,
+ sqlite3_vtab **ppVTab, char **pzErr){
+ int rc;
+ TableSpec spec;
+ StringBuffer schema;
+ FTSTRACE(("FTS3 Create\n"));
+
+ rc = parseSpec(&spec, argc, argv, pzErr);
+ if( rc!=SQLITE_OK ) return rc;
+
+ initStringBuffer(&schema);
+ append(&schema, "CREATE TABLE %_content(");
+ append(&schema, " docid INTEGER PRIMARY KEY,");
+ appendList(&schema, spec.nColumn, spec.azContentColumn);
+ append(&schema, ")");
+ rc = sql_exec(db, spec.zDb, spec.zName, stringBufferData(&schema));
+ stringBufferDestroy(&schema);
+ if( rc!=SQLITE_OK ) goto out;
+
+ rc = sql_exec(db, spec.zDb, spec.zName,
+ "create table %_segments("
+ " blockid INTEGER PRIMARY KEY,"
+ " block blob"
+ ");"
+ );
+ if( rc!=SQLITE_OK ) goto out;
+
+ rc = sql_exec(db, spec.zDb, spec.zName,
+ "create table %_segdir("
+ " level integer,"
+ " idx integer,"
+ " start_block integer,"
+ " leaves_end_block integer,"
+ " end_block integer,"
+ " root blob,"
+ " primary key(level, idx)"
+ ");");
+ if( rc!=SQLITE_OK ) goto out;
+
+ rc = constructVtab(db, (fts3Hash *)pAux, &spec, ppVTab, pzErr);
+
+out:
+ clearTableSpec(&spec);
+ return rc;
+}
+
+/* Decide how to handle an SQL query. */
+static int fulltextBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
+ fulltext_vtab *v = (fulltext_vtab *)pVTab;
+ int i;
+ FTSTRACE(("FTS3 BestIndex\n"));
+
+ for(i=0; i<pInfo->nConstraint; ++i){
+ const struct sqlite3_index_constraint *pConstraint;
+ pConstraint = &pInfo->aConstraint[i];
+ if( pConstraint->usable ) {
+ if( (pConstraint->iColumn==-1 || pConstraint->iColumn==v->nColumn+1) &&
+ pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ pInfo->idxNum = QUERY_DOCID; /* lookup by docid */
+ FTSTRACE(("FTS3 QUERY_DOCID\n"));
+ } else if( pConstraint->iColumn>=0 && pConstraint->iColumn<=v->nColumn &&
+ pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH ){
+ /* full-text search */
+ pInfo->idxNum = QUERY_FULLTEXT + pConstraint->iColumn;
+ FTSTRACE(("FTS3 QUERY_FULLTEXT %d\n", pConstraint->iColumn));
+ } else continue;
+
+ pInfo->aConstraintUsage[i].argvIndex = 1;
+ pInfo->aConstraintUsage[i].omit = 1;
+
+ /* An arbitrary value for now.
+ * TODO: Perhaps docid matches should be considered cheaper than
+ * full-text searches. */
+ pInfo->estimatedCost = 1.0;
+
+ return SQLITE_OK;
+ }
+ }
+ pInfo->idxNum = QUERY_GENERIC;
+ return SQLITE_OK;
+}
+
+static int fulltextDisconnect(sqlite3_vtab *pVTab){
+ FTSTRACE(("FTS3 Disconnect %p\n", pVTab));
+ fulltext_vtab_destroy((fulltext_vtab *)pVTab);
+ return SQLITE_OK;
+}
+
+static int fulltextDestroy(sqlite3_vtab *pVTab){
+ fulltext_vtab *v = (fulltext_vtab *)pVTab;
+ int rc;
+
+ FTSTRACE(("FTS3 Destroy %p\n", pVTab));
+ rc = sql_exec(v->db, v->zDb, v->zName,
+ "drop table if exists %_content;"
+ "drop table if exists %_segments;"
+ "drop table if exists %_segdir;"
+ );
+ if( rc!=SQLITE_OK ) return rc;
+
+ fulltext_vtab_destroy((fulltext_vtab *)pVTab);
+ return SQLITE_OK;
+}
+
+static int fulltextOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+ fulltext_cursor *c;
+
+ c = (fulltext_cursor *) sqlite3_malloc(sizeof(fulltext_cursor));
+ if( c ){
+ memset(c, 0, sizeof(fulltext_cursor));
+ /* sqlite will initialize c->base */
+ *ppCursor = &c->base;
+ FTSTRACE(("FTS3 Open %p: %p\n", pVTab, c));
+ return SQLITE_OK;
+ }else{
+ return SQLITE_NOMEM;
+ }
+}
+
+
+/* Free all of the dynamically allocated memory held by *q
+*/
+static void queryClear(Query *q){
+ int i;
+ for(i = 0; i < q->nTerms; ++i){
+ sqlite3_free(q->pTerms[i].pTerm);
+ }
+ sqlite3_free(q->pTerms);
+ CLEAR(q);
+}
+
+/* Free all of the dynamically allocated memory held by the
+** Snippet
+*/
+static void snippetClear(Snippet *p){
+ sqlite3_free(p->aMatch);
+ sqlite3_free(p->zOffset);
+ sqlite3_free(p->zSnippet);
+ CLEAR(p);
+}
+/*
+** Append a single entry to the p->aMatch[] log.
+*/
+static void snippetAppendMatch(
+ Snippet *p, /* Append the entry to this snippet */
+ int iCol, int iTerm, /* The column and query term */
+ int iToken, /* Matching token in document */
+ int iStart, int nByte /* Offset and size of the match */
+){
+ int i;
+ struct snippetMatch *pMatch;
+ if( p->nMatch+1>=p->nAlloc ){
+ p->nAlloc = p->nAlloc*2 + 10;
+ p->aMatch = sqlite3_realloc(p->aMatch, p->nAlloc*sizeof(p->aMatch[0]) );
+ if( p->aMatch==0 ){
+ p->nMatch = 0;
+ p->nAlloc = 0;
+ return;
+ }
+ }
+ i = p->nMatch++;
+ pMatch = &p->aMatch[i];
+ pMatch->iCol = iCol;
+ pMatch->iTerm = iTerm;
+ pMatch->iToken = iToken;
+ pMatch->iStart = iStart;
+ pMatch->nByte = nByte;
+}
+
+/*
+** Sizing information for the circular buffer used in snippetOffsetsOfColumn()
+*/
+#define FTS3_ROTOR_SZ (32)
+#define FTS3_ROTOR_MASK (FTS3_ROTOR_SZ-1)
+
+/*
+** Add entries to pSnippet->aMatch[] for every match that occurs against
+** document zDoc[0..nDoc-1] which is stored in column iColumn.
+*/
+static void snippetOffsetsOfColumn(
+ Query *pQuery,
+ Snippet *pSnippet,
+ int iColumn,
+ const char *zDoc,
+ int nDoc
+){
+ const sqlite3_tokenizer_module *pTModule; /* The tokenizer module */
+ sqlite3_tokenizer *pTokenizer; /* The specific tokenizer */
+ sqlite3_tokenizer_cursor *pTCursor; /* Tokenizer cursor */
+ fulltext_vtab *pVtab; /* The full text index */
+ int nColumn; /* Number of columns in the index */
+ const QueryTerm *aTerm; /* Query string terms */
+ int nTerm; /* Number of query string terms */
+ int i, j; /* Loop counters */
+ int rc; /* Return code */
+ unsigned int match, prevMatch; /* Phrase search bitmasks */
+ const char *zToken; /* Next token from the tokenizer */
+ int nToken; /* Size of zToken */
+ int iBegin, iEnd, iPos; /* Offsets of beginning and end */
+
+ /* The following variables keep a circular buffer of the last
+ ** few tokens */
+ unsigned int iRotor = 0; /* Index of current token */
+ int iRotorBegin[FTS3_ROTOR_SZ]; /* Beginning offset of token */
+ int iRotorLen[FTS3_ROTOR_SZ]; /* Length of token */
+
+ pVtab = pQuery->pFts;
+ nColumn = pVtab->nColumn;
+ pTokenizer = pVtab->pTokenizer;
+ pTModule = pTokenizer->pModule;
+ rc = pTModule->xOpen(pTokenizer, zDoc, nDoc, &pTCursor);
+ if( rc ) return;
+ pTCursor->pTokenizer = pTokenizer;
+ aTerm = pQuery->pTerms;
+ nTerm = pQuery->nTerms;
+ if( nTerm>=FTS3_ROTOR_SZ ){
+ nTerm = FTS3_ROTOR_SZ - 1;
+ }
+ prevMatch = 0;
+ while(1){
+ rc = pTModule->xNext(pTCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos);
+ if( rc ) break;
+ iRotorBegin[iRotor&FTS3_ROTOR_MASK] = iBegin;
+ iRotorLen[iRotor&FTS3_ROTOR_MASK] = iEnd-iBegin;
+ match = 0;
+ for(i=0; i<nTerm; i++){
+ int iCol;
+ iCol = aTerm[i].iColumn;
+ if( iCol>=0 && iCol<nColumn && iCol!=iColumn ) continue;
+ if( aTerm[i].nTerm>nToken ) continue;
+ if( !aTerm[i].isPrefix && aTerm[i].nTerm<nToken ) continue;
+ assert( aTerm[i].nTerm<=nToken );
+ if( memcmp(aTerm[i].pTerm, zToken, aTerm[i].nTerm) ) continue;
+ if( aTerm[i].iPhrase>1 && (prevMatch & (1<<i))==0 ) continue;
+ match |= 1<<i;
+ if( i==nTerm-1 || aTerm[i+1].iPhrase==1 ){
+ for(j=aTerm[i].iPhrase-1; j>=0; j--){
+ int k = (iRotor-j) & FTS3_ROTOR_MASK;
+ snippetAppendMatch(pSnippet, iColumn, i-j, iPos-j,
+ iRotorBegin[k], iRotorLen[k]);
+ }
+ }
+ }
+ prevMatch = match<<1;
+ iRotor++;
+ }
+ pTModule->xClose(pTCursor);
+}
+
+/*
+** Remove entries from the pSnippet structure to account for the NEAR
+** operator. When this is called, pSnippet contains the list of token
+** offsets produced by treating all NEAR operators as AND operators.
+** This function removes any entries that should not be present after
+** accounting for the NEAR restriction. For example, if the queried
+** document is:
+**
+** "A B C D E A"
+**
+** and the query is:
+**
+** A NEAR/0 E
+**
+** then when this function is called the Snippet contains token offsets
+** 0, 4 and 5. This function removes the "0" entry (because the first A
+** is not near enough to an E).
+*/
+static void trimSnippetOffsetsForNear(Query *pQuery, Snippet *pSnippet){
+ int ii;
+ int iDir = 1;
+
+ while(iDir>-2) {
+ assert( iDir==1 || iDir==-1 );
+ for(ii=0; ii<pSnippet->nMatch; ii++){
+ int jj;
+ int nNear;
+ struct snippetMatch *pMatch = &pSnippet->aMatch[ii];
+ QueryTerm *pQueryTerm = &pQuery->pTerms[pMatch->iTerm];
+
+ if( (pMatch->iTerm+iDir)<0
+ || (pMatch->iTerm+iDir)>=pQuery->nTerms
+ ){
+ continue;
+ }
+
+ nNear = pQueryTerm->nNear;
+ if( iDir<0 ){
+ nNear = pQueryTerm[-1].nNear;
+ }
+
+ if( pMatch->iTerm>=0 && nNear ){
+ int isOk = 0;
+ int iNextTerm = pMatch->iTerm+iDir;
+ int iPrevTerm = iNextTerm;
+
+ int iEndToken;
+ int iStartToken;
+
+ if( iDir<0 ){
+ int nPhrase = 1;
+ iStartToken = pMatch->iToken;
+ while( (pMatch->iTerm+nPhrase)<pQuery->nTerms
+ && pQuery->pTerms[pMatch->iTerm+nPhrase].iPhrase>1
+ ){
+ nPhrase++;
+ }
+ iEndToken = iStartToken + nPhrase - 1;
+ }else{
+ iEndToken = pMatch->iToken;
+ iStartToken = pMatch->iToken+1-pQueryTerm->iPhrase;
+ }
+
+ while( pQuery->pTerms[iNextTerm].iPhrase>1 ){
+ iNextTerm--;
+ }
+ while( (iPrevTerm+1)<pQuery->nTerms &&
+ pQuery->pTerms[iPrevTerm+1].iPhrase>1
+ ){
+ iPrevTerm++;
+ }
+
+ for(jj=0; isOk==0 && jj<pSnippet->nMatch; jj++){
+ struct snippetMatch *p = &pSnippet->aMatch[jj];
+ if( p->iCol==pMatch->iCol && ((
+ p->iTerm==iNextTerm &&
+ p->iToken>iEndToken &&
+ p->iToken<=iEndToken+nNear
+ ) || (
+ p->iTerm==iPrevTerm &&
+ p->iToken<iStartToken &&
+ p->iToken>=iStartToken-nNear
+ ))){
+ isOk = 1;
+ }
+ }
+ if( !isOk ){
+ for(jj=1-pQueryTerm->iPhrase; jj<=0; jj++){
+ pMatch[jj].iTerm = -1;
+ }
+ ii = -1;
+ iDir = 1;
+ }
+ }
+ }
+ iDir -= 2;
+ }
+}
+
+/*
+** Compute all offsets for the current row of the query.
+** If the offsets have already been computed, this routine is a no-op.
+*/
+static void snippetAllOffsets(fulltext_cursor *p){
+ int nColumn;
+ int iColumn, i;
+ int iFirst, iLast;
+ fulltext_vtab *pFts;
+
+ if( p->snippet.nMatch ) return;
+ if( p->q.nTerms==0 ) return;
+ pFts = p->q.pFts;
+ nColumn = pFts->nColumn;
+ iColumn = (p->iCursorType - QUERY_FULLTEXT);
+ if( iColumn<0 || iColumn>=nColumn ){
+ iFirst = 0;
+ iLast = nColumn-1;
+ }else{
+ iFirst = iColumn;
+ iLast = iColumn;
+ }
+ for(i=iFirst; i<=iLast; i++){
+ const char *zDoc;
+ int nDoc;
+ zDoc = (const char*)sqlite3_column_text(p->pStmt, i+1);
+ nDoc = sqlite3_column_bytes(p->pStmt, i+1);
+ snippetOffsetsOfColumn(&p->q, &p->snippet, i, zDoc, nDoc);
+ }
+
+ trimSnippetOffsetsForNear(&p->q, &p->snippet);
+}
+
+/*
+** Convert the information in the aMatch[] array of the snippet
+** into the string zOffset[0..nOffset-1].
+*/
+static void snippetOffsetText(Snippet *p){
+ int i;
+ int cnt = 0;
+ StringBuffer sb;
+ char zBuf[200];
+ if( p->zOffset ) return;
+ initStringBuffer(&sb);
+ for(i=0; i<p->nMatch; i++){
+ struct snippetMatch *pMatch = &p->aMatch[i];
+ if( pMatch->iTerm>=0 ){
+ /* If snippetMatch.iTerm is less than 0, then the match was
+ ** discarded as part of processing the NEAR operator (see the
+ ** trimSnippetOffsetsForNear() function for details). Ignore
+ ** it in this case
+ */
+ zBuf[0] = ' ';
+ sqlite3_snprintf(sizeof(zBuf)-1, &zBuf[cnt>0], "%d %d %d %d",
+ pMatch->iCol, pMatch->iTerm, pMatch->iStart, pMatch->nByte);
+ append(&sb, zBuf);
+ cnt++;
+ }
+ }
+ p->zOffset = stringBufferData(&sb);
+ p->nOffset = stringBufferLength(&sb);
+}
+
+/*
+** zDoc[0..nDoc-1] is phrase of text. aMatch[0..nMatch-1] are a set
+** of matching words some of which might be in zDoc. zDoc is column
+** number iCol.
+**
+** iBreak is suggested spot in zDoc where we could begin or end an
+** excerpt. Return a value similar to iBreak but possibly adjusted
+** to be a little left or right so that the break point is better.
+*/
+static int wordBoundary(
+ int iBreak, /* The suggested break point */
+ const char *zDoc, /* Document text */
+ int nDoc, /* Number of bytes in zDoc[] */
+ struct snippetMatch *aMatch, /* Matching words */
+ int nMatch, /* Number of entries in aMatch[] */
+ int iCol /* The column number for zDoc[] */
+){
+ int i;
+ if( iBreak<=10 ){
+ return 0;
+ }
+ if( iBreak>=nDoc-10 ){
+ return nDoc;
+ }
+ for(i=0; i<nMatch && aMatch[i].iCol<iCol; i++){}
+ while( i<nMatch && aMatch[i].iStart+aMatch[i].nByte<iBreak ){ i++; }
+ if( i<nMatch ){
+ if( aMatch[i].iStart<iBreak+10 ){
+ return aMatch[i].iStart;
+ }
+ if( i>0 && aMatch[i-1].iStart+aMatch[i-1].nByte>=iBreak ){
+ return aMatch[i-1].iStart;
+ }
+ }
+ for(i=1; i<=10; i++){
+ if( safe_isspace(zDoc[iBreak-i]) ){
+ return iBreak - i + 1;
+ }
+ if( safe_isspace(zDoc[iBreak+i]) ){
+ return iBreak + i + 1;
+ }
+ }
+ return iBreak;
+}
+
+
+
+/*
+** Allowed values for Snippet.aMatch[].snStatus
+*/
+#define SNIPPET_IGNORE 0 /* It is ok to omit this match from the snippet */
+#define SNIPPET_DESIRED 1 /* We want to include this match in the snippet */
+
+/*
+** Generate the text of a snippet.
+*/
+static void snippetText(
+ fulltext_cursor *pCursor, /* The cursor we need the snippet for */
+ const char *zStartMark, /* Markup to appear before each match */
+ const char *zEndMark, /* Markup to appear after each match */
+ const char *zEllipsis /* Ellipsis mark */
+){
+ int i, j;
+ struct snippetMatch *aMatch;
+ int nMatch;
+ int nDesired;
+ StringBuffer sb;
+ int tailCol;
+ int tailOffset;
+ int iCol;
+ int nDoc;
+ const char *zDoc;
+ int iStart, iEnd;
+ int tailEllipsis = 0;
+ int iMatch;
+
+
+ sqlite3_free(pCursor->snippet.zSnippet);
+ pCursor->snippet.zSnippet = 0;
+ aMatch = pCursor->snippet.aMatch;
+ nMatch = pCursor->snippet.nMatch;
+ initStringBuffer(&sb);
+
+ for(i=0; i<nMatch; i++){
+ aMatch[i].snStatus = SNIPPET_IGNORE;
+ }
+ nDesired = 0;
+ for(i=0; i<pCursor->q.nTerms; i++){
+ for(j=0; j<nMatch; j++){
+ if( aMatch[j].iTerm==i ){
+ aMatch[j].snStatus = SNIPPET_DESIRED;
+ nDesired++;
+ break;
+ }
+ }
+ }
+
+ iMatch = 0;
+ tailCol = -1;
+ tailOffset = 0;
+ for(i=0; i<nMatch && nDesired>0; i++){
+ if( aMatch[i].snStatus!=SNIPPET_DESIRED ) continue;
+ nDesired--;
+ iCol = aMatch[i].iCol;
+ zDoc = (const char*)sqlite3_column_text(pCursor->pStmt, iCol+1);
+ nDoc = sqlite3_column_bytes(pCursor->pStmt, iCol+1);
+ iStart = aMatch[i].iStart - 40;
+ iStart = wordBoundary(iStart, zDoc, nDoc, aMatch, nMatch, iCol);
+ if( iStart<=10 ){
+ iStart = 0;
+ }
+ if( iCol==tailCol && iStart<=tailOffset+20 ){
+ iStart = tailOffset;
+ }
+ if( (iCol!=tailCol && tailCol>=0) || iStart!=tailOffset ){
+ trimWhiteSpace(&sb);
+ appendWhiteSpace(&sb);
+ append(&sb, zEllipsis);
+ appendWhiteSpace(&sb);
+ }
+ iEnd = aMatch[i].iStart + aMatch[i].nByte + 40;
+ iEnd = wordBoundary(iEnd, zDoc, nDoc, aMatch, nMatch, iCol);
+ if( iEnd>=nDoc-10 ){
+ iEnd = nDoc;
+ tailEllipsis = 0;
+ }else{
+ tailEllipsis = 1;
+ }
+ while( iMatch<nMatch && aMatch[iMatch].iCol<iCol ){ iMatch++; }
+ while( iStart<iEnd ){
+ while( iMatch<nMatch && aMatch[iMatch].iStart<iStart
+ && aMatch[iMatch].iCol<=iCol ){
+ iMatch++;
+ }
+ if( iMatch<nMatch && aMatch[iMatch].iStart<iEnd
+ && aMatch[iMatch].iCol==iCol ){
+ nappend(&sb, &zDoc[iStart], aMatch[iMatch].iStart - iStart);
+ iStart = aMatch[iMatch].iStart;
+ append(&sb, zStartMark);
+ nappend(&sb, &zDoc[iStart], aMatch[iMatch].nByte);
+ append(&sb, zEndMark);
+ iStart += aMatch[iMatch].nByte;
+ for(j=iMatch+1; j<nMatch; j++){
+ if( aMatch[j].iTerm==aMatch[iMatch].iTerm
+ && aMatch[j].snStatus==SNIPPET_DESIRED ){
+ nDesired--;
+ aMatch[j].snStatus = SNIPPET_IGNORE;
+ }
+ }
+ }else{
+ nappend(&sb, &zDoc[iStart], iEnd - iStart);
+ iStart = iEnd;
+ }
+ }
+ tailCol = iCol;
+ tailOffset = iEnd;
+ }
+ trimWhiteSpace(&sb);
+ if( tailEllipsis ){
+ appendWhiteSpace(&sb);
+ append(&sb, zEllipsis);
+ }
+ pCursor->snippet.zSnippet = stringBufferData(&sb);
+ pCursor->snippet.nSnippet = stringBufferLength(&sb);
+}
+
+
+/*
+** Close the cursor. For additional information see the documentation
+** on the xClose method of the virtual table interface.
+*/
+static int fulltextClose(sqlite3_vtab_cursor *pCursor){
+ fulltext_cursor *c = (fulltext_cursor *) pCursor;
+ FTSTRACE(("FTS3 Close %p\n", c));
+ sqlite3_finalize(c->pStmt);
+ queryClear(&c->q);
+ snippetClear(&c->snippet);
+ if( c->result.nData!=0 ) dlrDestroy(&c->reader);
+ dataBufferDestroy(&c->result);
+ sqlite3_free(c);
+ return SQLITE_OK;
+}
+
+static int fulltextNext(sqlite3_vtab_cursor *pCursor){
+ fulltext_cursor *c = (fulltext_cursor *) pCursor;
+ int rc;
+
+ FTSTRACE(("FTS3 Next %p\n", pCursor));
+ snippetClear(&c->snippet);
+ if( c->iCursorType < QUERY_FULLTEXT ){
+ /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */
+ rc = sqlite3_step(c->pStmt);
+ switch( rc ){
+ case SQLITE_ROW:
+ c->eof = 0;
+ return SQLITE_OK;
+ case SQLITE_DONE:
+ c->eof = 1;
+ return SQLITE_OK;
+ default:
+ c->eof = 1;
+ return rc;
+ }
+ } else { /* full-text query */
+ rc = sqlite3_reset(c->pStmt);
+ if( rc!=SQLITE_OK ) return rc;
+
+ if( c->result.nData==0 || dlrAtEnd(&c->reader) ){
+ c->eof = 1;
+ return SQLITE_OK;
+ }
+ rc = sqlite3_bind_int64(c->pStmt, 1, dlrDocid(&c->reader));
+ dlrStep(&c->reader);
+ if( rc!=SQLITE_OK ) return rc;
+ /* TODO(shess) Handle SQLITE_SCHEMA AND SQLITE_BUSY. */
+ rc = sqlite3_step(c->pStmt);
+ if( rc==SQLITE_ROW ){ /* the case we expect */
+ c->eof = 0;
+ return SQLITE_OK;
+ }
+ /* an error occurred; abort */
+ return rc==SQLITE_DONE ? SQLITE_ERROR : rc;
+ }
+}
+
+
+/* TODO(shess) If we pushed LeafReader to the top of the file, or to
+** another file, term_select() could be pushed above
+** docListOfTerm().
+*/
+static int termSelect(fulltext_vtab *v, int iColumn,
+ const char *pTerm, int nTerm, int isPrefix,
+ DocListType iType, DataBuffer *out);
+
+/* Return a DocList corresponding to the query term *pTerm. If *pTerm
+** is the first term of a phrase query, go ahead and evaluate the phrase
+** query and return the doclist for the entire phrase query.
+**
+** The resulting DL_DOCIDS doclist is stored in pResult, which is
+** overwritten.
+*/
+static int docListOfTerm(
+ fulltext_vtab *v, /* The full text index */
+ int iColumn, /* column to restrict to. No restriction if >=nColumn */
+ QueryTerm *pQTerm, /* Term we are looking for, or 1st term of a phrase */
+ DataBuffer *pResult /* Write the result here */
+){
+ DataBuffer left, right, new;
+ int i, rc;
+
+ /* No phrase search if no position info. */
+ assert( pQTerm->nPhrase==0 || DL_DEFAULT!=DL_DOCIDS );
+
+ /* This code should never be called with buffered updates. */
+ assert( v->nPendingData<0 );
+
+ dataBufferInit(&left, 0);
+ rc = termSelect(v, iColumn, pQTerm->pTerm, pQTerm->nTerm, pQTerm->isPrefix,
+ (0<pQTerm->nPhrase ? DL_POSITIONS : DL_DOCIDS), &left);
+ if( rc ) return rc;
+ for(i=1; i<=pQTerm->nPhrase && left.nData>0; i++){
+ /* If this token is connected to the next by a NEAR operator, and
+ ** the next token is the start of a phrase, then set nPhraseRight
+ ** to the number of tokens in the phrase. Otherwise leave it at 1.
+ */
+ int nPhraseRight = 1;
+ while( (i+nPhraseRight)<=pQTerm->nPhrase
+ && pQTerm[i+nPhraseRight].nNear==0
+ ){
+ nPhraseRight++;
+ }
+
+ dataBufferInit(&right, 0);
+ rc = termSelect(v, iColumn, pQTerm[i].pTerm, pQTerm[i].nTerm,
+ pQTerm[i].isPrefix, DL_POSITIONS, &right);
+ if( rc ){
+ dataBufferDestroy(&left);
+ return rc;
+ }
+ dataBufferInit(&new, 0);
+ docListPhraseMerge(left.pData, left.nData, right.pData, right.nData,
+ pQTerm[i-1].nNear, pQTerm[i-1].iPhrase + nPhraseRight,
+ ((i<pQTerm->nPhrase) ? DL_POSITIONS : DL_DOCIDS),
+ &new);
+ dataBufferDestroy(&left);
+ dataBufferDestroy(&right);
+ left = new;
+ }
+ *pResult = left;
+ return SQLITE_OK;
+}
+
+/* Add a new term pTerm[0..nTerm-1] to the query *q.
+*/
+static void queryAdd(Query *q, const char *pTerm, int nTerm){
+ QueryTerm *t;
+ ++q->nTerms;
+ q->pTerms = sqlite3_realloc(q->pTerms, q->nTerms * sizeof(q->pTerms[0]));
+ if( q->pTerms==0 ){
+ q->nTerms = 0;
+ return;
+ }
+ t = &q->pTerms[q->nTerms - 1];
+ CLEAR(t);
+ t->pTerm = sqlite3_malloc(nTerm+1);
+ memcpy(t->pTerm, pTerm, nTerm);
+ t->pTerm[nTerm] = 0;
+ t->nTerm = nTerm;
+ t->isOr = q->nextIsOr;
+ t->isPrefix = 0;
+ q->nextIsOr = 0;
+ t->iColumn = q->nextColumn;
+ q->nextColumn = q->dfltColumn;
+}
+
+/*
+** Check to see if the string zToken[0...nToken-1] matches any
+** column name in the virtual table. If it does,
+** return the zero-indexed column number. If not, return -1.
+*/
+static int checkColumnSpecifier(
+ fulltext_vtab *pVtab, /* The virtual table */
+ const char *zToken, /* Text of the token */
+ int nToken /* Number of characters in the token */
+){
+ int i;
+ for(i=0; i<pVtab->nColumn; i++){
+ if( memcmp(pVtab->azColumn[i], zToken, nToken)==0
+ && pVtab->azColumn[i][nToken]==0 ){
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*
+** Parse the text at pSegment[0..nSegment-1]. Add additional terms
+** to the query being assemblied in pQuery.
+**
+** inPhrase is true if pSegment[0..nSegement-1] is contained within
+** double-quotes. If inPhrase is true, then the first term
+** is marked with the number of terms in the phrase less one and
+** OR and "-" syntax is ignored. If inPhrase is false, then every
+** term found is marked with nPhrase=0 and OR and "-" syntax is significant.
+*/
+static int tokenizeSegment(
+ sqlite3_tokenizer *pTokenizer, /* The tokenizer to use */
+ const char *pSegment, int nSegment, /* Query expression being parsed */
+ int inPhrase, /* True if within "..." */
+ Query *pQuery /* Append results here */
+){
+ const sqlite3_tokenizer_module *pModule = pTokenizer->pModule;
+ sqlite3_tokenizer_cursor *pCursor;
+ int firstIndex = pQuery->nTerms;
+ int iCol;
+ int nTerm = 1;
+
+ int rc = pModule->xOpen(pTokenizer, pSegment, nSegment, &pCursor);
+ if( rc!=SQLITE_OK ) return rc;
+ pCursor->pTokenizer = pTokenizer;
+
+ while( 1 ){
+ const char *pToken;
+ int nToken, iBegin, iEnd, iPos;
+
+ rc = pModule->xNext(pCursor,
+ &pToken, &nToken,
+ &iBegin, &iEnd, &iPos);
+ if( rc!=SQLITE_OK ) break;
+ if( !inPhrase &&
+ pSegment[iEnd]==':' &&
+ (iCol = checkColumnSpecifier(pQuery->pFts, pToken, nToken))>=0 ){
+ pQuery->nextColumn = iCol;
+ continue;
+ }
+ if( !inPhrase && pQuery->nTerms>0 && nToken==2
+ && pSegment[iBegin+0]=='O'
+ && pSegment[iBegin+1]=='R'
+ ){
+ pQuery->nextIsOr = 1;
+ continue;
+ }
+ if( !inPhrase && pQuery->nTerms>0 && !pQuery->nextIsOr && nToken==4
+ && pSegment[iBegin+0]=='N'
+ && pSegment[iBegin+1]=='E'
+ && pSegment[iBegin+2]=='A'
+ && pSegment[iBegin+3]=='R'
+ ){
+ QueryTerm *pTerm = &pQuery->pTerms[pQuery->nTerms-1];
+ if( (iBegin+6)<nSegment
+ && pSegment[iBegin+4] == '/'
+ && pSegment[iBegin+5]>='0' && pSegment[iBegin+5]<='9'
+ ){
+ pTerm->nNear = (pSegment[iBegin+5] - '0');
+ nToken += 2;
+ if( pSegment[iBegin+6]>='0' && pSegment[iBegin+6]<=9 ){
+ pTerm->nNear = pTerm->nNear * 10 + (pSegment[iBegin+6] - '0');
+ iEnd++;
+ }
+ pModule->xNext(pCursor, &pToken, &nToken, &iBegin, &iEnd, &iPos);
+ } else {
+ pTerm->nNear = SQLITE_FTS3_DEFAULT_NEAR_PARAM;
+ }
+ pTerm->nNear++;
+ continue;
+ }
+
+ queryAdd(pQuery, pToken, nToken);
+ if( !inPhrase && iBegin>0 && pSegment[iBegin-1]=='-' ){
+ pQuery->pTerms[pQuery->nTerms-1].isNot = 1;
+ }
+ if( iEnd<nSegment && pSegment[iEnd]=='*' ){
+ pQuery->pTerms[pQuery->nTerms-1].isPrefix = 1;
+ }
+ pQuery->pTerms[pQuery->nTerms-1].iPhrase = nTerm;
+ if( inPhrase ){
+ nTerm++;
+ }
+ }
+
+ if( inPhrase && pQuery->nTerms>firstIndex ){
+ pQuery->pTerms[firstIndex].nPhrase = pQuery->nTerms - firstIndex - 1;
+ }
+
+ return pModule->xClose(pCursor);
+}
+
+/* Parse a query string, yielding a Query object pQuery.
+**
+** The calling function will need to queryClear() to clean up
+** the dynamically allocated memory held by pQuery.
+*/
+static int parseQuery(
+ fulltext_vtab *v, /* The fulltext index */
+ const char *zInput, /* Input text of the query string */
+ int nInput, /* Size of the input text */
+ int dfltColumn, /* Default column of the index to match against */
+ Query *pQuery /* Write the parse results here. */
+){
+ int iInput, inPhrase = 0;
+ int ii;
+ QueryTerm *aTerm;
+
+ if( zInput==0 ) nInput = 0;
+ if( nInput<0 ) nInput = strlen(zInput);
+ pQuery->nTerms = 0;
+ pQuery->pTerms = NULL;
+ pQuery->nextIsOr = 0;
+ pQuery->nextColumn = dfltColumn;
+ pQuery->dfltColumn = dfltColumn;
+ pQuery->pFts = v;
+
+ for(iInput=0; iInput<nInput; ++iInput){
+ int i;
+ for(i=iInput; i<nInput && zInput[i]!='"'; ++i){}
+ if( i>iInput ){
+ tokenizeSegment(v->pTokenizer, zInput+iInput, i-iInput, inPhrase,
+ pQuery);
+ }
+ iInput = i;
+ if( i<nInput ){
+ assert( zInput[i]=='"' );
+ inPhrase = !inPhrase;
+ }
+ }
+
+ if( inPhrase ){
+ /* unmatched quote */
+ queryClear(pQuery);
+ return SQLITE_ERROR;
+ }
+
+ /* Modify the values of the QueryTerm.nPhrase variables to account for
+ ** the NEAR operator. For the purposes of QueryTerm.nPhrase, phrases
+ ** and tokens connected by the NEAR operator are handled as a single
+ ** phrase. See comments above the QueryTerm structure for details.
+ */
+ aTerm = pQuery->pTerms;
+ for(ii=0; ii<pQuery->nTerms; ii++){
+ if( aTerm[ii].nNear || aTerm[ii].nPhrase ){
+ while (aTerm[ii+aTerm[ii].nPhrase].nNear) {
+ aTerm[ii].nPhrase += (1 + aTerm[ii+aTerm[ii].nPhrase+1].nPhrase);
+ }
+ }
+ }
+
+ return SQLITE_OK;
+}
+
+/* TODO(shess) Refactor the code to remove this forward decl. */
+static int flushPendingTerms(fulltext_vtab *v);
+
+/* Perform a full-text query using the search expression in
+** zInput[0..nInput-1]. Return a list of matching documents
+** in pResult.
+**
+** Queries must match column iColumn. Or if iColumn>=nColumn
+** they are allowed to match against any column.
+*/
+static int fulltextQuery(
+ fulltext_vtab *v, /* The full text index */
+ int iColumn, /* Match against this column by default */
+ const char *zInput, /* The query string */
+ int nInput, /* Number of bytes in zInput[] */
+ DataBuffer *pResult, /* Write the result doclist here */
+ Query *pQuery /* Put parsed query string here */
+){
+ int i, iNext, rc;
+ DataBuffer left, right, or, new;
+ int nNot = 0;
+ QueryTerm *aTerm;
+
+ /* TODO(shess) Instead of flushing pendingTerms, we could query for
+ ** the relevant term and merge the doclist into what we receive from
+ ** the database. Wait and see if this is a common issue, first.
+ **
+ ** A good reason not to flush is to not generate update-related
+ ** error codes from here.
+ */
+
+ /* Flush any buffered updates before executing the query. */
+ rc = flushPendingTerms(v);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* TODO(shess) I think that the queryClear() calls below are not
+ ** necessary, because fulltextClose() already clears the query.
+ */
+ rc = parseQuery(v, zInput, nInput, iColumn, pQuery);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Empty or NULL queries return no results. */
+ if( pQuery->nTerms==0 ){
+ dataBufferInit(pResult, 0);
+ return SQLITE_OK;
+ }
+
+ /* Merge AND terms. */
+ /* TODO(shess) I think we can early-exit if( i>nNot && left.nData==0 ). */
+ aTerm = pQuery->pTerms;
+ for(i = 0; i<pQuery->nTerms; i=iNext){
+ if( aTerm[i].isNot ){
+ /* Handle all NOT terms in a separate pass */
+ nNot++;
+ iNext = i + aTerm[i].nPhrase+1;
+ continue;
+ }
+ iNext = i + aTerm[i].nPhrase + 1;
+ rc = docListOfTerm(v, aTerm[i].iColumn, &aTerm[i], &right);
+ if( rc ){
+ if( i!=nNot ) dataBufferDestroy(&left);
+ queryClear(pQuery);
+ return rc;
+ }
+ while( iNext<pQuery->nTerms && aTerm[iNext].isOr ){
+ rc = docListOfTerm(v, aTerm[iNext].iColumn, &aTerm[iNext], &or);
+ iNext += aTerm[iNext].nPhrase + 1;
+ if( rc ){
+ if( i!=nNot ) dataBufferDestroy(&left);
+ dataBufferDestroy(&right);
+ queryClear(pQuery);
+ return rc;
+ }
+ dataBufferInit(&new, 0);
+ docListOrMerge(right.pData, right.nData, or.pData, or.nData, &new);
+ dataBufferDestroy(&right);
+ dataBufferDestroy(&or);
+ right = new;
+ }
+ if( i==nNot ){ /* first term processed. */
+ left = right;
+ }else{
+ dataBufferInit(&new, 0);
+ docListAndMerge(left.pData, left.nData, right.pData, right.nData, &new);
+ dataBufferDestroy(&right);
+ dataBufferDestroy(&left);
+ left = new;
+ }
+ }
+
+ if( nNot==pQuery->nTerms ){
+ /* We do not yet know how to handle a query of only NOT terms */
+ return SQLITE_ERROR;
+ }
+
+ /* Do the EXCEPT terms */
+ for(i=0; i<pQuery->nTerms; i += aTerm[i].nPhrase + 1){
+ if( !aTerm[i].isNot ) continue;
+ rc = docListOfTerm(v, aTerm[i].iColumn, &aTerm[i], &right);
+ if( rc ){
+ queryClear(pQuery);
+ dataBufferDestroy(&left);
+ return rc;
+ }
+ dataBufferInit(&new, 0);
+ docListExceptMerge(left.pData, left.nData, right.pData, right.nData, &new);
+ dataBufferDestroy(&right);
+ dataBufferDestroy(&left);
+ left = new;
+ }
+
+ *pResult = left;
+ return rc;
+}
+
+/*
+** This is the xFilter interface for the virtual table. See
+** the virtual table xFilter method documentation for additional
+** information.
+**
+** If idxNum==QUERY_GENERIC then do a full table scan against
+** the %_content table.
+**
+** If idxNum==QUERY_DOCID then do a docid lookup for a single entry
+** in the %_content table.
+**
+** If idxNum>=QUERY_FULLTEXT then use the full text index. The
+** column on the left-hand side of the MATCH operator is column
+** number idxNum-QUERY_FULLTEXT, 0 indexed. argv[0] is the right-hand
+** side of the MATCH operator.
+*/
+/* TODO(shess) Upgrade the cursor initialization and destruction to
+** account for fulltextFilter() being called multiple times on the
+** same cursor. The current solution is very fragile. Apply fix to
+** fts3 as appropriate.
+*/
+static int fulltextFilter(
+ sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
+ int idxNum, const char *idxStr, /* Which indexing scheme to use */
+ int argc, sqlite3_value **argv /* Arguments for the indexing scheme */
+){
+ fulltext_cursor *c = (fulltext_cursor *) pCursor;
+ fulltext_vtab *v = cursor_vtab(c);
+ int rc;
+ StringBuffer sb;
+
+ FTSTRACE(("FTS3 Filter %p\n",pCursor));
+
+ initStringBuffer(&sb);
+ append(&sb, "SELECT docid, ");
+ appendList(&sb, v->nColumn, v->azContentColumn);
+ append(&sb, " FROM %_content");
+ if( idxNum!=QUERY_GENERIC ) append(&sb, " WHERE docid = ?");
+ sqlite3_finalize(c->pStmt);
+ rc = sql_prepare(v->db, v->zDb, v->zName, &c->pStmt, stringBufferData(&sb));
+ stringBufferDestroy(&sb);
+ if( rc!=SQLITE_OK ) return rc;
+
+ c->iCursorType = idxNum;
+ switch( idxNum ){
+ case QUERY_GENERIC:
+ break;
+
+ case QUERY_DOCID:
+ rc = sqlite3_bind_int64(c->pStmt, 1, sqlite3_value_int64(argv[0]));
+ if( rc!=SQLITE_OK ) return rc;
+ break;
+
+ default: /* full-text search */
+ {
+ const char *zQuery = (const char *)sqlite3_value_text(argv[0]);
+ assert( idxNum<=QUERY_FULLTEXT+v->nColumn);
+ assert( argc==1 );
+ queryClear(&c->q);
+ if( c->result.nData!=0 ){
+ /* This case happens if the same cursor is used repeatedly. */
+ dlrDestroy(&c->reader);
+ dataBufferReset(&c->result);
+ }else{
+ dataBufferInit(&c->result, 0);
+ }
+ rc = fulltextQuery(v, idxNum-QUERY_FULLTEXT, zQuery, -1, &c->result, &c->q);
+ if( rc!=SQLITE_OK ) return rc;
+ if( c->result.nData!=0 ){
+ dlrInit(&c->reader, DL_DOCIDS, c->result.pData, c->result.nData);
+ }
+ break;
+ }
+ }
+
+ return fulltextNext(pCursor);
+}
+
+/* This is the xEof method of the virtual table. The SQLite core
+** calls this routine to find out if it has reached the end of
+** a query's results set.
+*/
+static int fulltextEof(sqlite3_vtab_cursor *pCursor){
+ fulltext_cursor *c = (fulltext_cursor *) pCursor;
+ return c->eof;
+}
+
+/* This is the xColumn method of the virtual table. The SQLite
+** core calls this method during a query when it needs the value
+** of a column from the virtual table. This method needs to use
+** one of the sqlite3_result_*() routines to store the requested
+** value back in the pContext.
+*/
+static int fulltextColumn(sqlite3_vtab_cursor *pCursor,
+ sqlite3_context *pContext, int idxCol){
+ fulltext_cursor *c = (fulltext_cursor *) pCursor;
+ fulltext_vtab *v = cursor_vtab(c);
+
+ if( idxCol<v->nColumn ){
+ sqlite3_value *pVal = sqlite3_column_value(c->pStmt, idxCol+1);
+ sqlite3_result_value(pContext, pVal);
+ }else if( idxCol==v->nColumn ){
+ /* The extra column whose name is the same as the table.
+ ** Return a blob which is a pointer to the cursor
+ */
+ sqlite3_result_blob(pContext, &c, sizeof(c), SQLITE_TRANSIENT);
+ }else if( idxCol==v->nColumn+1 ){
+ /* The docid column, which is an alias for rowid. */
+ sqlite3_value *pVal = sqlite3_column_value(c->pStmt, 0);
+ sqlite3_result_value(pContext, pVal);
+ }
+ return SQLITE_OK;
+}
+
+/* This is the xRowid method. The SQLite core calls this routine to
+** retrieve the rowid for the current row of the result set. fts3
+** exposes %_content.docid as the rowid for the virtual table. The
+** rowid should be written to *pRowid.
+*/
+static int fulltextRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+ fulltext_cursor *c = (fulltext_cursor *) pCursor;
+
+ *pRowid = sqlite3_column_int64(c->pStmt, 0);
+ return SQLITE_OK;
+}
+
+/* Add all terms in [zText] to pendingTerms table. If [iColumn] > 0,
+** we also store positions and offsets in the hash table using that
+** column number.
+*/
+static int buildTerms(fulltext_vtab *v, sqlite_int64 iDocid,
+ const char *zText, int iColumn){
+ sqlite3_tokenizer *pTokenizer = v->pTokenizer;
+ sqlite3_tokenizer_cursor *pCursor;
+ const char *pToken;
+ int nTokenBytes;
+ int iStartOffset, iEndOffset, iPosition;
+ int rc;
+
+ rc = pTokenizer->pModule->xOpen(pTokenizer, zText, -1, &pCursor);
+ if( rc!=SQLITE_OK ) return rc;
+
+ pCursor->pTokenizer = pTokenizer;
+ while( SQLITE_OK==(rc=pTokenizer->pModule->xNext(pCursor,
+ &pToken, &nTokenBytes,
+ &iStartOffset, &iEndOffset,
+ &iPosition)) ){
+ DLCollector *p;
+ int nData; /* Size of doclist before our update. */
+
+ /* Positions can't be negative; we use -1 as a terminator
+ * internally. Token can't be NULL or empty. */
+ if( iPosition<0 || pToken == NULL || nTokenBytes == 0 ){
+ rc = SQLITE_ERROR;
+ break;
+ }
+
+ p = fts3HashFind(&v->pendingTerms, pToken, nTokenBytes);
+ if( p==NULL ){
+ nData = 0;
+ p = dlcNew(iDocid, DL_DEFAULT);
+ fts3HashInsert(&v->pendingTerms, pToken, nTokenBytes, p);
+
+ /* Overhead for our hash table entry, the key, and the value. */
+ v->nPendingData += sizeof(struct fts3HashElem)+sizeof(*p)+nTokenBytes;
+ }else{
+ nData = p->b.nData;
+ if( p->dlw.iPrevDocid!=iDocid ) dlcNext(p, iDocid);
+ }
+ if( iColumn>=0 ){
+ dlcAddPos(p, iColumn, iPosition, iStartOffset, iEndOffset);
+ }
+
+ /* Accumulate data added by dlcNew or dlcNext, and dlcAddPos. */
+ v->nPendingData += p->b.nData-nData;
+ }
+
+ /* TODO(shess) Check return? Should this be able to cause errors at
+ ** this point? Actually, same question about sqlite3_finalize(),
+ ** though one could argue that failure there means that the data is
+ ** not durable. *ponder*
+ */
+ pTokenizer->pModule->xClose(pCursor);
+ if( SQLITE_DONE == rc ) return SQLITE_OK;
+ return rc;
+}
+
+/* Add doclists for all terms in [pValues] to pendingTerms table. */
+static int insertTerms(fulltext_vtab *v, sqlite_int64 iDocid,
+ sqlite3_value **pValues){
+ int i;
+ for(i = 0; i < v->nColumn ; ++i){
+ char *zText = (char*)sqlite3_value_text(pValues[i]);
+ int rc = buildTerms(v, iDocid, zText, i);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ return SQLITE_OK;
+}
+
+/* Add empty doclists for all terms in the given row's content to
+** pendingTerms.
+*/
+static int deleteTerms(fulltext_vtab *v, sqlite_int64 iDocid){
+ const char **pValues;
+ int i, rc;
+
+ /* TODO(shess) Should we allow such tables at all? */
+ if( DL_DEFAULT==DL_DOCIDS ) return SQLITE_ERROR;
+
+ rc = content_select(v, iDocid, &pValues);
+ if( rc!=SQLITE_OK ) return rc;
+
+ for(i = 0 ; i < v->nColumn; ++i) {
+ rc = buildTerms(v, iDocid, pValues[i], -1);
+ if( rc!=SQLITE_OK ) break;
+ }
+
+ freeStringArray(v->nColumn, pValues);
+ return SQLITE_OK;
+}
+
+/* TODO(shess) Refactor the code to remove this forward decl. */
+static int initPendingTerms(fulltext_vtab *v, sqlite_int64 iDocid);
+
+/* Insert a row into the %_content table; set *piDocid to be the ID of the
+** new row. Add doclists for terms to pendingTerms.
+*/
+static int index_insert(fulltext_vtab *v, sqlite3_value *pRequestDocid,
+ sqlite3_value **pValues, sqlite_int64 *piDocid){
+ int rc;
+
+ rc = content_insert(v, pRequestDocid, pValues); /* execute an SQL INSERT */
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* docid column is an alias for rowid. */
+ *piDocid = sqlite3_last_insert_rowid(v->db);
+ rc = initPendingTerms(v, *piDocid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ return insertTerms(v, *piDocid, pValues);
+}
+
+/* Delete a row from the %_content table; add empty doclists for terms
+** to pendingTerms.
+*/
+static int index_delete(fulltext_vtab *v, sqlite_int64 iRow){
+ int rc = initPendingTerms(v, iRow);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = deleteTerms(v, iRow);
+ if( rc!=SQLITE_OK ) return rc;
+
+ return content_delete(v, iRow); /* execute an SQL DELETE */
+}
+
+/* Update a row in the %_content table; add delete doclists to
+** pendingTerms for old terms not in the new data, add insert doclists
+** to pendingTerms for terms in the new data.
+*/
+static int index_update(fulltext_vtab *v, sqlite_int64 iRow,
+ sqlite3_value **pValues){
+ int rc = initPendingTerms(v, iRow);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Generate an empty doclist for each term that previously appeared in this
+ * row. */
+ rc = deleteTerms(v, iRow);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = content_update(v, pValues, iRow); /* execute an SQL UPDATE */
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Now add positions for terms which appear in the updated row. */
+ return insertTerms(v, iRow, pValues);
+}
+
+/*******************************************************************/
+/* InteriorWriter is used to collect terms and block references into
+** interior nodes in %_segments. See commentary at top of file for
+** format.
+*/
+
+/* How large interior nodes can grow. */
+#define INTERIOR_MAX 2048
+
+/* Minimum number of terms per interior node (except the root). This
+** prevents large terms from making the tree too skinny - must be >0
+** so that the tree always makes progress. Note that the min tree
+** fanout will be INTERIOR_MIN_TERMS+1.
+*/
+#define INTERIOR_MIN_TERMS 7
+#if INTERIOR_MIN_TERMS<1
+# error INTERIOR_MIN_TERMS must be greater than 0.
+#endif
+
+/* ROOT_MAX controls how much data is stored inline in the segment
+** directory.
+*/
+/* TODO(shess) Push ROOT_MAX down to whoever is writing things. It's
+** only here so that interiorWriterRootInfo() and leafWriterRootInfo()
+** can both see it, but if the caller passed it in, we wouldn't even
+** need a define.
+*/
+#define ROOT_MAX 1024
+#if ROOT_MAX<VARINT_MAX*2
+# error ROOT_MAX must have enough space for a header.
+#endif
+
+/* InteriorBlock stores a linked-list of interior blocks while a lower
+** layer is being constructed.
+*/
+typedef struct InteriorBlock {
+ DataBuffer term; /* Leftmost term in block's subtree. */
+ DataBuffer data; /* Accumulated data for the block. */
+ struct InteriorBlock *next;
+} InteriorBlock;
+
+static InteriorBlock *interiorBlockNew(int iHeight, sqlite_int64 iChildBlock,
+ const char *pTerm, int nTerm){
+ InteriorBlock *block = sqlite3_malloc(sizeof(InteriorBlock));
+ char c[VARINT_MAX+VARINT_MAX];
+ int n;
+
+ if( block ){
+ memset(block, 0, sizeof(*block));
+ dataBufferInit(&block->term, 0);
+ dataBufferReplace(&block->term, pTerm, nTerm);
+
+ n = fts3PutVarint(c, iHeight);
+ n += fts3PutVarint(c+n, iChildBlock);
+ dataBufferInit(&block->data, INTERIOR_MAX);
+ dataBufferReplace(&block->data, c, n);
+ }
+ return block;
+}
+
+#ifndef NDEBUG
+/* Verify that the data is readable as an interior node. */
+static void interiorBlockValidate(InteriorBlock *pBlock){
+ const char *pData = pBlock->data.pData;
+ int nData = pBlock->data.nData;
+ int n, iDummy;
+ sqlite_int64 iBlockid;
+
+ assert( nData>0 );
+ assert( pData!=0 );
+ assert( pData+nData>pData );
+
+ /* Must lead with height of node as a varint(n), n>0 */
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>0 );
+ assert( n<nData );
+ pData += n;
+ nData -= n;
+
+ /* Must contain iBlockid. */
+ n = fts3GetVarint(pData, &iBlockid);
+ assert( n>0 );
+ assert( n<=nData );
+ pData += n;
+ nData -= n;
+
+ /* Zero or more terms of positive length */
+ if( nData!=0 ){
+ /* First term is not delta-encoded. */
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>0 );
+ assert( n+iDummy>0);
+ assert( n+iDummy<=nData );
+ pData += n+iDummy;
+ nData -= n+iDummy;
+
+ /* Following terms delta-encoded. */
+ while( nData!=0 ){
+ /* Length of shared prefix. */
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>=0 );
+ assert( n<nData );
+ pData += n;
+ nData -= n;
+
+ /* Length and data of distinct suffix. */
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>0 );
+ assert( n+iDummy>0);
+ assert( n+iDummy<=nData );
+ pData += n+iDummy;
+ nData -= n+iDummy;
+ }
+ }
+}
+#define ASSERT_VALID_INTERIOR_BLOCK(x) interiorBlockValidate(x)
+#else
+#define ASSERT_VALID_INTERIOR_BLOCK(x) assert( 1 )
+#endif
+
+typedef struct InteriorWriter {
+ int iHeight; /* from 0 at leaves. */
+ InteriorBlock *first, *last;
+ struct InteriorWriter *parentWriter;
+
+ DataBuffer term; /* Last term written to block "last". */
+ sqlite_int64 iOpeningChildBlock; /* First child block in block "last". */
+#ifndef NDEBUG
+ sqlite_int64 iLastChildBlock; /* for consistency checks. */
+#endif
+} InteriorWriter;
+
+/* Initialize an interior node where pTerm[nTerm] marks the leftmost
+** term in the tree. iChildBlock is the leftmost child block at the
+** next level down the tree.
+*/
+static void interiorWriterInit(int iHeight, const char *pTerm, int nTerm,
+ sqlite_int64 iChildBlock,
+ InteriorWriter *pWriter){
+ InteriorBlock *block;
+ assert( iHeight>0 );
+ CLEAR(pWriter);
+
+ pWriter->iHeight = iHeight;
+ pWriter->iOpeningChildBlock = iChildBlock;
+#ifndef NDEBUG
+ pWriter->iLastChildBlock = iChildBlock;
+#endif
+ block = interiorBlockNew(iHeight, iChildBlock, pTerm, nTerm);
+ pWriter->last = pWriter->first = block;
+ ASSERT_VALID_INTERIOR_BLOCK(pWriter->last);
+ dataBufferInit(&pWriter->term, 0);
+}
+
+/* Append the child node rooted at iChildBlock to the interior node,
+** with pTerm[nTerm] as the leftmost term in iChildBlock's subtree.
+*/
+static void interiorWriterAppend(InteriorWriter *pWriter,
+ const char *pTerm, int nTerm,
+ sqlite_int64 iChildBlock){
+ char c[VARINT_MAX+VARINT_MAX];
+ int n, nPrefix = 0;
+
+ ASSERT_VALID_INTERIOR_BLOCK(pWriter->last);
+
+ /* The first term written into an interior node is actually
+ ** associated with the second child added (the first child was added
+ ** in interiorWriterInit, or in the if clause at the bottom of this
+ ** function). That term gets encoded straight up, with nPrefix left
+ ** at 0.
+ */
+ if( pWriter->term.nData==0 ){
+ n = fts3PutVarint(c, nTerm);
+ }else{
+ while( nPrefix<pWriter->term.nData &&
+ pTerm[nPrefix]==pWriter->term.pData[nPrefix] ){
+ nPrefix++;
+ }
+
+ n = fts3PutVarint(c, nPrefix);
+ n += fts3PutVarint(c+n, nTerm-nPrefix);
+ }
+
+#ifndef NDEBUG
+ pWriter->iLastChildBlock++;
+#endif
+ assert( pWriter->iLastChildBlock==iChildBlock );
+
+ /* Overflow to a new block if the new term makes the current block
+ ** too big, and the current block already has enough terms.
+ */
+ if( pWriter->last->data.nData+n+nTerm-nPrefix>INTERIOR_MAX &&
+ iChildBlock-pWriter->iOpeningChildBlock>INTERIOR_MIN_TERMS ){
+ pWriter->last->next = interiorBlockNew(pWriter->iHeight, iChildBlock,
+ pTerm, nTerm);
+ pWriter->last = pWriter->last->next;
+ pWriter->iOpeningChildBlock = iChildBlock;
+ dataBufferReset(&pWriter->term);
+ }else{
+ dataBufferAppend2(&pWriter->last->data, c, n,
+ pTerm+nPrefix, nTerm-nPrefix);
+ dataBufferReplace(&pWriter->term, pTerm, nTerm);
+ }
+ ASSERT_VALID_INTERIOR_BLOCK(pWriter->last);
+}
+
+/* Free the space used by pWriter, including the linked-list of
+** InteriorBlocks, and parentWriter, if present.
+*/
+static int interiorWriterDestroy(InteriorWriter *pWriter){
+ InteriorBlock *block = pWriter->first;
+
+ while( block!=NULL ){
+ InteriorBlock *b = block;
+ block = block->next;
+ dataBufferDestroy(&b->term);
+ dataBufferDestroy(&b->data);
+ sqlite3_free(b);
+ }
+ if( pWriter->parentWriter!=NULL ){
+ interiorWriterDestroy(pWriter->parentWriter);
+ sqlite3_free(pWriter->parentWriter);
+ }
+ dataBufferDestroy(&pWriter->term);
+ SCRAMBLE(pWriter);
+ return SQLITE_OK;
+}
+
+/* If pWriter can fit entirely in ROOT_MAX, return it as the root info
+** directly, leaving *piEndBlockid unchanged. Otherwise, flush
+** pWriter to %_segments, building a new layer of interior nodes, and
+** recursively ask for their root into.
+*/
+static int interiorWriterRootInfo(fulltext_vtab *v, InteriorWriter *pWriter,
+ char **ppRootInfo, int *pnRootInfo,
+ sqlite_int64 *piEndBlockid){
+ InteriorBlock *block = pWriter->first;
+ sqlite_int64 iBlockid = 0;
+ int rc;
+
+ /* If we can fit the segment inline */
+ if( block==pWriter->last && block->data.nData<ROOT_MAX ){
+ *ppRootInfo = block->data.pData;
+ *pnRootInfo = block->data.nData;
+ return SQLITE_OK;
+ }
+
+ /* Flush the first block to %_segments, and create a new level of
+ ** interior node.
+ */
+ ASSERT_VALID_INTERIOR_BLOCK(block);
+ rc = block_insert(v, block->data.pData, block->data.nData, &iBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+ *piEndBlockid = iBlockid;
+
+ pWriter->parentWriter = sqlite3_malloc(sizeof(*pWriter->parentWriter));
+ interiorWriterInit(pWriter->iHeight+1,
+ block->term.pData, block->term.nData,
+ iBlockid, pWriter->parentWriter);
+
+ /* Flush additional blocks and append to the higher interior
+ ** node.
+ */
+ for(block=block->next; block!=NULL; block=block->next){
+ ASSERT_VALID_INTERIOR_BLOCK(block);
+ rc = block_insert(v, block->data.pData, block->data.nData, &iBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+ *piEndBlockid = iBlockid;
+
+ interiorWriterAppend(pWriter->parentWriter,
+ block->term.pData, block->term.nData, iBlockid);
+ }
+
+ /* Parent node gets the chance to be the root. */
+ return interiorWriterRootInfo(v, pWriter->parentWriter,
+ ppRootInfo, pnRootInfo, piEndBlockid);
+}
+
+/****************************************************************/
+/* InteriorReader is used to read off the data from an interior node
+** (see comment at top of file for the format).
+*/
+typedef struct InteriorReader {
+ const char *pData;
+ int nData;
+
+ DataBuffer term; /* previous term, for decoding term delta. */
+
+ sqlite_int64 iBlockid;
+} InteriorReader;
+
+static void interiorReaderDestroy(InteriorReader *pReader){
+ dataBufferDestroy(&pReader->term);
+ SCRAMBLE(pReader);
+}
+
+/* TODO(shess) The assertions are great, but what if we're in NDEBUG
+** and the blob is empty or otherwise contains suspect data?
+*/
+static void interiorReaderInit(const char *pData, int nData,
+ InteriorReader *pReader){
+ int n, nTerm;
+
+ /* Require at least the leading flag byte */
+ assert( nData>0 );
+ assert( pData[0]!='\0' );
+
+ CLEAR(pReader);
+
+ /* Decode the base blockid, and set the cursor to the first term. */
+ n = fts3GetVarint(pData+1, &pReader->iBlockid);
+ assert( 1+n<=nData );
+ pReader->pData = pData+1+n;
+ pReader->nData = nData-(1+n);
+
+ /* A single-child interior node (such as when a leaf node was too
+ ** large for the segment directory) won't have any terms.
+ ** Otherwise, decode the first term.
+ */
+ if( pReader->nData==0 ){
+ dataBufferInit(&pReader->term, 0);
+ }else{
+ n = fts3GetVarint32(pReader->pData, &nTerm);
+ dataBufferInit(&pReader->term, nTerm);
+ dataBufferReplace(&pReader->term, pReader->pData+n, nTerm);
+ assert( n+nTerm<=pReader->nData );
+ pReader->pData += n+nTerm;
+ pReader->nData -= n+nTerm;
+ }
+}
+
+static int interiorReaderAtEnd(InteriorReader *pReader){
+ return pReader->term.nData==0;
+}
+
+static sqlite_int64 interiorReaderCurrentBlockid(InteriorReader *pReader){
+ return pReader->iBlockid;
+}
+
+static int interiorReaderTermBytes(InteriorReader *pReader){
+ assert( !interiorReaderAtEnd(pReader) );
+ return pReader->term.nData;
+}
+static const char *interiorReaderTerm(InteriorReader *pReader){
+ assert( !interiorReaderAtEnd(pReader) );
+ return pReader->term.pData;
+}
+
+/* Step forward to the next term in the node. */
+static void interiorReaderStep(InteriorReader *pReader){
+ assert( !interiorReaderAtEnd(pReader) );
+
+ /* If the last term has been read, signal eof, else construct the
+ ** next term.
+ */
+ if( pReader->nData==0 ){
+ dataBufferReset(&pReader->term);
+ }else{
+ int n, nPrefix, nSuffix;
+
+ n = fts3GetVarint32(pReader->pData, &nPrefix);
+ n += fts3GetVarint32(pReader->pData+n, &nSuffix);
+
+ /* Truncate the current term and append suffix data. */
+ pReader->term.nData = nPrefix;
+ dataBufferAppend(&pReader->term, pReader->pData+n, nSuffix);
+
+ assert( n+nSuffix<=pReader->nData );
+ pReader->pData += n+nSuffix;
+ pReader->nData -= n+nSuffix;
+ }
+ pReader->iBlockid++;
+}
+
+/* Compare the current term to pTerm[nTerm], returning strcmp-style
+** results. If isPrefix, equality means equal through nTerm bytes.
+*/
+static int interiorReaderTermCmp(InteriorReader *pReader,
+ const char *pTerm, int nTerm, int isPrefix){
+ const char *pReaderTerm = interiorReaderTerm(pReader);
+ int nReaderTerm = interiorReaderTermBytes(pReader);
+ int c, n = nReaderTerm<nTerm ? nReaderTerm : nTerm;
+
+ if( n==0 ){
+ if( nReaderTerm>0 ) return -1;
+ if( nTerm>0 ) return 1;
+ return 0;
+ }
+
+ c = memcmp(pReaderTerm, pTerm, n);
+ if( c!=0 ) return c;
+ if( isPrefix && n==nTerm ) return 0;
+ return nReaderTerm - nTerm;
+}
+
+/****************************************************************/
+/* LeafWriter is used to collect terms and associated doclist data
+** into leaf blocks in %_segments (see top of file for format info).
+** Expected usage is:
+**
+** LeafWriter writer;
+** leafWriterInit(0, 0, &writer);
+** while( sorted_terms_left_to_process ){
+** // data is doclist data for that term.
+** rc = leafWriterStep(v, &writer, pTerm, nTerm, pData, nData);
+** if( rc!=SQLITE_OK ) goto err;
+** }
+** rc = leafWriterFinalize(v, &writer);
+**err:
+** leafWriterDestroy(&writer);
+** return rc;
+**
+** leafWriterStep() may write a collected leaf out to %_segments.
+** leafWriterFinalize() finishes writing any buffered data and stores
+** a root node in %_segdir. leafWriterDestroy() frees all buffers and
+** InteriorWriters allocated as part of writing this segment.
+**
+** TODO(shess) Document leafWriterStepMerge().
+*/
+
+/* Put terms with data this big in their own block. */
+#define STANDALONE_MIN 1024
+
+/* Keep leaf blocks below this size. */
+#define LEAF_MAX 2048
+
+typedef struct LeafWriter {
+ int iLevel;
+ int idx;
+ sqlite_int64 iStartBlockid; /* needed to create the root info */
+ sqlite_int64 iEndBlockid; /* when we're done writing. */
+
+ DataBuffer term; /* previous encoded term */
+ DataBuffer data; /* encoding buffer */
+
+ /* bytes of first term in the current node which distinguishes that
+ ** term from the last term of the previous node.
+ */
+ int nTermDistinct;
+
+ InteriorWriter parentWriter; /* if we overflow */
+ int has_parent;
+} LeafWriter;
+
+static void leafWriterInit(int iLevel, int idx, LeafWriter *pWriter){
+ CLEAR(pWriter);
+ pWriter->iLevel = iLevel;
+ pWriter->idx = idx;
+
+ dataBufferInit(&pWriter->term, 32);
+
+ /* Start out with a reasonably sized block, though it can grow. */
+ dataBufferInit(&pWriter->data, LEAF_MAX);
+}
+
+#ifndef NDEBUG
+/* Verify that the data is readable as a leaf node. */
+static void leafNodeValidate(const char *pData, int nData){
+ int n, iDummy;
+
+ if( nData==0 ) return;
+ assert( nData>0 );
+ assert( pData!=0 );
+ assert( pData+nData>pData );
+
+ /* Must lead with a varint(0) */
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( iDummy==0 );
+ assert( n>0 );
+ assert( n<nData );
+ pData += n;
+ nData -= n;
+
+ /* Leading term length and data must fit in buffer. */
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>0 );
+ assert( n+iDummy>0 );
+ assert( n+iDummy<nData );
+ pData += n+iDummy;
+ nData -= n+iDummy;
+
+ /* Leading term's doclist length and data must fit. */
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>0 );
+ assert( n+iDummy>0 );
+ assert( n+iDummy<=nData );
+ ASSERT_VALID_DOCLIST(DL_DEFAULT, pData+n, iDummy, NULL);
+ pData += n+iDummy;
+ nData -= n+iDummy;
+
+ /* Verify that trailing terms and doclists also are readable. */
+ while( nData!=0 ){
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>=0 );
+ assert( n<nData );
+ pData += n;
+ nData -= n;
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>0 );
+ assert( n+iDummy>0 );
+ assert( n+iDummy<nData );
+ pData += n+iDummy;
+ nData -= n+iDummy;
+
+ n = fts3GetVarint32(pData, &iDummy);
+ assert( n>0 );
+ assert( iDummy>0 );
+ assert( n+iDummy>0 );
+ assert( n+iDummy<=nData );
+ ASSERT_VALID_DOCLIST(DL_DEFAULT, pData+n, iDummy, NULL);
+ pData += n+iDummy;
+ nData -= n+iDummy;
+ }
+}
+#define ASSERT_VALID_LEAF_NODE(p, n) leafNodeValidate(p, n)
+#else
+#define ASSERT_VALID_LEAF_NODE(p, n) assert( 1 )
+#endif
+
+/* Flush the current leaf node to %_segments, and adding the resulting
+** blockid and the starting term to the interior node which will
+** contain it.
+*/
+static int leafWriterInternalFlush(fulltext_vtab *v, LeafWriter *pWriter,
+ int iData, int nData){
+ sqlite_int64 iBlockid = 0;
+ const char *pStartingTerm;
+ int nStartingTerm, rc, n;
+
+ /* Must have the leading varint(0) flag, plus at least some
+ ** valid-looking data.
+ */
+ assert( nData>2 );
+ assert( iData>=0 );
+ assert( iData+nData<=pWriter->data.nData );
+ ASSERT_VALID_LEAF_NODE(pWriter->data.pData+iData, nData);
+
+ rc = block_insert(v, pWriter->data.pData+iData, nData, &iBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+ assert( iBlockid!=0 );
+
+ /* Reconstruct the first term in the leaf for purposes of building
+ ** the interior node.
+ */
+ n = fts3GetVarint32(pWriter->data.pData+iData+1, &nStartingTerm);
+ pStartingTerm = pWriter->data.pData+iData+1+n;
+ assert( pWriter->data.nData>iData+1+n+nStartingTerm );
+ assert( pWriter->nTermDistinct>0 );
+ assert( pWriter->nTermDistinct<=nStartingTerm );
+ nStartingTerm = pWriter->nTermDistinct;
+
+ if( pWriter->has_parent ){
+ interiorWriterAppend(&pWriter->parentWriter,
+ pStartingTerm, nStartingTerm, iBlockid);
+ }else{
+ interiorWriterInit(1, pStartingTerm, nStartingTerm, iBlockid,
+ &pWriter->parentWriter);
+ pWriter->has_parent = 1;
+ }
+
+ /* Track the span of this segment's leaf nodes. */
+ if( pWriter->iEndBlockid==0 ){
+ pWriter->iEndBlockid = pWriter->iStartBlockid = iBlockid;
+ }else{
+ pWriter->iEndBlockid++;
+ assert( iBlockid==pWriter->iEndBlockid );
+ }
+
+ return SQLITE_OK;
+}
+static int leafWriterFlush(fulltext_vtab *v, LeafWriter *pWriter){
+ int rc = leafWriterInternalFlush(v, pWriter, 0, pWriter->data.nData);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Re-initialize the output buffer. */
+ dataBufferReset(&pWriter->data);
+
+ return SQLITE_OK;
+}
+
+/* Fetch the root info for the segment. If the entire leaf fits
+** within ROOT_MAX, then it will be returned directly, otherwise it
+** will be flushed and the root info will be returned from the
+** interior node. *piEndBlockid is set to the blockid of the last
+** interior or leaf node written to disk (0 if none are written at
+** all).
+*/
+static int leafWriterRootInfo(fulltext_vtab *v, LeafWriter *pWriter,
+ char **ppRootInfo, int *pnRootInfo,
+ sqlite_int64 *piEndBlockid){
+ /* we can fit the segment entirely inline */
+ if( !pWriter->has_parent && pWriter->data.nData<ROOT_MAX ){
+ *ppRootInfo = pWriter->data.pData;
+ *pnRootInfo = pWriter->data.nData;
+ *piEndBlockid = 0;
+ return SQLITE_OK;
+ }
+
+ /* Flush remaining leaf data. */
+ if( pWriter->data.nData>0 ){
+ int rc = leafWriterFlush(v, pWriter);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ /* We must have flushed a leaf at some point. */
+ assert( pWriter->has_parent );
+
+ /* Tenatively set the end leaf blockid as the end blockid. If the
+ ** interior node can be returned inline, this will be the final
+ ** blockid, otherwise it will be overwritten by
+ ** interiorWriterRootInfo().
+ */
+ *piEndBlockid = pWriter->iEndBlockid;
+
+ return interiorWriterRootInfo(v, &pWriter->parentWriter,
+ ppRootInfo, pnRootInfo, piEndBlockid);
+}
+
+/* Collect the rootInfo data and store it into the segment directory.
+** This has the effect of flushing the segment's leaf data to
+** %_segments, and also flushing any interior nodes to %_segments.
+*/
+static int leafWriterFinalize(fulltext_vtab *v, LeafWriter *pWriter){
+ sqlite_int64 iEndBlockid;
+ char *pRootInfo;
+ int rc, nRootInfo;
+
+ rc = leafWriterRootInfo(v, pWriter, &pRootInfo, &nRootInfo, &iEndBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Don't bother storing an entirely empty segment. */
+ if( iEndBlockid==0 && nRootInfo==0 ) return SQLITE_OK;
+
+ return segdir_set(v, pWriter->iLevel, pWriter->idx,
+ pWriter->iStartBlockid, pWriter->iEndBlockid,
+ iEndBlockid, pRootInfo, nRootInfo);
+}
+
+static void leafWriterDestroy(LeafWriter *pWriter){
+ if( pWriter->has_parent ) interiorWriterDestroy(&pWriter->parentWriter);
+ dataBufferDestroy(&pWriter->term);
+ dataBufferDestroy(&pWriter->data);
+}
+
+/* Encode a term into the leafWriter, delta-encoding as appropriate.
+** Returns the length of the new term which distinguishes it from the
+** previous term, which can be used to set nTermDistinct when a node
+** boundary is crossed.
+*/
+static int leafWriterEncodeTerm(LeafWriter *pWriter,
+ const char *pTerm, int nTerm){
+ char c[VARINT_MAX+VARINT_MAX];
+ int n, nPrefix = 0;
+
+ assert( nTerm>0 );
+ while( nPrefix<pWriter->term.nData &&
+ pTerm[nPrefix]==pWriter->term.pData[nPrefix] ){
+ nPrefix++;
+ /* Failing this implies that the terms weren't in order. */
+ assert( nPrefix<nTerm );
+ }
+
+ if( pWriter->data.nData==0 ){
+ /* Encode the node header and leading term as:
+ ** varint(0)
+ ** varint(nTerm)
+ ** char pTerm[nTerm]
+ */
+ n = fts3PutVarint(c, '\0');
+ n += fts3PutVarint(c+n, nTerm);
+ dataBufferAppend2(&pWriter->data, c, n, pTerm, nTerm);
+ }else{
+ /* Delta-encode the term as:
+ ** varint(nPrefix)
+ ** varint(nSuffix)
+ ** char pTermSuffix[nSuffix]
+ */
+ n = fts3PutVarint(c, nPrefix);
+ n += fts3PutVarint(c+n, nTerm-nPrefix);
+ dataBufferAppend2(&pWriter->data, c, n, pTerm+nPrefix, nTerm-nPrefix);
+ }
+ dataBufferReplace(&pWriter->term, pTerm, nTerm);
+
+ return nPrefix+1;
+}
+
+/* Used to avoid a memmove when a large amount of doclist data is in
+** the buffer. This constructs a node and term header before
+** iDoclistData and flushes the resulting complete node using
+** leafWriterInternalFlush().
+*/
+static int leafWriterInlineFlush(fulltext_vtab *v, LeafWriter *pWriter,
+ const char *pTerm, int nTerm,
+ int iDoclistData){
+ char c[VARINT_MAX+VARINT_MAX];
+ int iData, n = fts3PutVarint(c, 0);
+ n += fts3PutVarint(c+n, nTerm);
+
+ /* There should always be room for the header. Even if pTerm shared
+ ** a substantial prefix with the previous term, the entire prefix
+ ** could be constructed from earlier data in the doclist, so there
+ ** should be room.
+ */
+ assert( iDoclistData>=n+nTerm );
+
+ iData = iDoclistData-(n+nTerm);
+ memcpy(pWriter->data.pData+iData, c, n);
+ memcpy(pWriter->data.pData+iData+n, pTerm, nTerm);
+
+ return leafWriterInternalFlush(v, pWriter, iData, pWriter->data.nData-iData);
+}
+
+/* Push pTerm[nTerm] along with the doclist data to the leaf layer of
+** %_segments.
+*/
+static int leafWriterStepMerge(fulltext_vtab *v, LeafWriter *pWriter,
+ const char *pTerm, int nTerm,
+ DLReader *pReaders, int nReaders){
+ char c[VARINT_MAX+VARINT_MAX];
+ int iTermData = pWriter->data.nData, iDoclistData;
+ int i, nData, n, nActualData, nActual, rc, nTermDistinct;
+
+ ASSERT_VALID_LEAF_NODE(pWriter->data.pData, pWriter->data.nData);
+ nTermDistinct = leafWriterEncodeTerm(pWriter, pTerm, nTerm);
+
+ /* Remember nTermDistinct if opening a new node. */
+ if( iTermData==0 ) pWriter->nTermDistinct = nTermDistinct;
+
+ iDoclistData = pWriter->data.nData;
+
+ /* Estimate the length of the merged doclist so we can leave space
+ ** to encode it.
+ */
+ for(i=0, nData=0; i<nReaders; i++){
+ nData += dlrAllDataBytes(&pReaders[i]);
+ }
+ n = fts3PutVarint(c, nData);
+ dataBufferAppend(&pWriter->data, c, n);
+
+ docListMerge(&pWriter->data, pReaders, nReaders);
+ ASSERT_VALID_DOCLIST(DL_DEFAULT,
+ pWriter->data.pData+iDoclistData+n,
+ pWriter->data.nData-iDoclistData-n, NULL);
+
+ /* The actual amount of doclist data at this point could be smaller
+ ** than the length we encoded. Additionally, the space required to
+ ** encode this length could be smaller. For small doclists, this is
+ ** not a big deal, we can just use memmove() to adjust things.
+ */
+ nActualData = pWriter->data.nData-(iDoclistData+n);
+ nActual = fts3PutVarint(c, nActualData);
+ assert( nActualData<=nData );
+ assert( nActual<=n );
+
+ /* If the new doclist is big enough for force a standalone leaf
+ ** node, we can immediately flush it inline without doing the
+ ** memmove().
+ */
+ /* TODO(shess) This test matches leafWriterStep(), which does this
+ ** test before it knows the cost to varint-encode the term and
+ ** doclist lengths. At some point, change to
+ ** pWriter->data.nData-iTermData>STANDALONE_MIN.
+ */
+ if( nTerm+nActualData>STANDALONE_MIN ){
+ /* Push leaf node from before this term. */
+ if( iTermData>0 ){
+ rc = leafWriterInternalFlush(v, pWriter, 0, iTermData);
+ if( rc!=SQLITE_OK ) return rc;
+
+ pWriter->nTermDistinct = nTermDistinct;
+ }
+
+ /* Fix the encoded doclist length. */
+ iDoclistData += n - nActual;
+ memcpy(pWriter->data.pData+iDoclistData, c, nActual);
+
+ /* Push the standalone leaf node. */
+ rc = leafWriterInlineFlush(v, pWriter, pTerm, nTerm, iDoclistData);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Leave the node empty. */
+ dataBufferReset(&pWriter->data);
+
+ return rc;
+ }
+
+ /* At this point, we know that the doclist was small, so do the
+ ** memmove if indicated.
+ */
+ if( nActual<n ){
+ memmove(pWriter->data.pData+iDoclistData+nActual,
+ pWriter->data.pData+iDoclistData+n,
+ pWriter->data.nData-(iDoclistData+n));
+ pWriter->data.nData -= n-nActual;
+ }
+
+ /* Replace written length with actual length. */
+ memcpy(pWriter->data.pData+iDoclistData, c, nActual);
+
+ /* If the node is too large, break things up. */
+ /* TODO(shess) This test matches leafWriterStep(), which does this
+ ** test before it knows the cost to varint-encode the term and
+ ** doclist lengths. At some point, change to
+ ** pWriter->data.nData>LEAF_MAX.
+ */
+ if( iTermData+nTerm+nActualData>LEAF_MAX ){
+ /* Flush out the leading data as a node */
+ rc = leafWriterInternalFlush(v, pWriter, 0, iTermData);
+ if( rc!=SQLITE_OK ) return rc;
+
+ pWriter->nTermDistinct = nTermDistinct;
+
+ /* Rebuild header using the current term */
+ n = fts3PutVarint(pWriter->data.pData, 0);
+ n += fts3PutVarint(pWriter->data.pData+n, nTerm);
+ memcpy(pWriter->data.pData+n, pTerm, nTerm);
+ n += nTerm;
+
+ /* There should always be room, because the previous encoding
+ ** included all data necessary to construct the term.
+ */
+ assert( n<iDoclistData );
+ /* So long as STANDALONE_MIN is half or less of LEAF_MAX, the
+ ** following memcpy() is safe (as opposed to needing a memmove).
+ */
+ assert( 2*STANDALONE_MIN<=LEAF_MAX );
+ assert( n+pWriter->data.nData-iDoclistData<iDoclistData );
+ memcpy(pWriter->data.pData+n,
+ pWriter->data.pData+iDoclistData,
+ pWriter->data.nData-iDoclistData);
+ pWriter->data.nData -= iDoclistData-n;
+ }
+ ASSERT_VALID_LEAF_NODE(pWriter->data.pData, pWriter->data.nData);
+
+ return SQLITE_OK;
+}
+
+/* Push pTerm[nTerm] along with the doclist data to the leaf layer of
+** %_segments.
+*/
+/* TODO(shess) Revise writeZeroSegment() so that doclists are
+** constructed directly in pWriter->data.
+*/
+static int leafWriterStep(fulltext_vtab *v, LeafWriter *pWriter,
+ const char *pTerm, int nTerm,
+ const char *pData, int nData){
+ int rc;
+ DLReader reader;
+
+ dlrInit(&reader, DL_DEFAULT, pData, nData);
+ rc = leafWriterStepMerge(v, pWriter, pTerm, nTerm, &reader, 1);
+ dlrDestroy(&reader);
+
+ return rc;
+}
+
+
+/****************************************************************/
+/* LeafReader is used to iterate over an individual leaf node. */
+typedef struct LeafReader {
+ DataBuffer term; /* copy of current term. */
+
+ const char *pData; /* data for current term. */
+ int nData;
+} LeafReader;
+
+static void leafReaderDestroy(LeafReader *pReader){
+ dataBufferDestroy(&pReader->term);
+ SCRAMBLE(pReader);
+}
+
+static int leafReaderAtEnd(LeafReader *pReader){
+ return pReader->nData<=0;
+}
+
+/* Access the current term. */
+static int leafReaderTermBytes(LeafReader *pReader){
+ return pReader->term.nData;
+}
+static const char *leafReaderTerm(LeafReader *pReader){
+ assert( pReader->term.nData>0 );
+ return pReader->term.pData;
+}
+
+/* Access the doclist data for the current term. */
+static int leafReaderDataBytes(LeafReader *pReader){
+ int nData;
+ assert( pReader->term.nData>0 );
+ fts3GetVarint32(pReader->pData, &nData);
+ return nData;
+}
+static const char *leafReaderData(LeafReader *pReader){
+ int n, nData;
+ assert( pReader->term.nData>0 );
+ n = fts3GetVarint32(pReader->pData, &nData);
+ return pReader->pData+n;
+}
+
+static void leafReaderInit(const char *pData, int nData,
+ LeafReader *pReader){
+ int nTerm, n;
+
+ assert( nData>0 );
+ assert( pData[0]=='\0' );
+
+ CLEAR(pReader);
+
+ /* Read the first term, skipping the header byte. */
+ n = fts3GetVarint32(pData+1, &nTerm);
+ dataBufferInit(&pReader->term, nTerm);
+ dataBufferReplace(&pReader->term, pData+1+n, nTerm);
+
+ /* Position after the first term. */
+ assert( 1+n+nTerm<nData );
+ pReader->pData = pData+1+n+nTerm;
+ pReader->nData = nData-1-n-nTerm;
+}
+
+/* Step the reader forward to the next term. */
+static void leafReaderStep(LeafReader *pReader){
+ int n, nData, nPrefix, nSuffix;
+ assert( !leafReaderAtEnd(pReader) );
+
+ /* Skip previous entry's data block. */
+ n = fts3GetVarint32(pReader->pData, &nData);
+ assert( n+nData<=pReader->nData );
+ pReader->pData += n+nData;
+ pReader->nData -= n+nData;
+
+ if( !leafReaderAtEnd(pReader) ){
+ /* Construct the new term using a prefix from the old term plus a
+ ** suffix from the leaf data.
+ */
+ n = fts3GetVarint32(pReader->pData, &nPrefix);
+ n += fts3GetVarint32(pReader->pData+n, &nSuffix);
+ assert( n+nSuffix<pReader->nData );
+ pReader->term.nData = nPrefix;
+ dataBufferAppend(&pReader->term, pReader->pData+n, nSuffix);
+
+ pReader->pData += n+nSuffix;
+ pReader->nData -= n+nSuffix;
+ }
+}
+
+/* strcmp-style comparison of pReader's current term against pTerm.
+** If isPrefix, equality means equal through nTerm bytes.
+*/
+static int leafReaderTermCmp(LeafReader *pReader,
+ const char *pTerm, int nTerm, int isPrefix){
+ int c, n = pReader->term.nData<nTerm ? pReader->term.nData : nTerm;
+ if( n==0 ){
+ if( pReader->term.nData>0 ) return -1;
+ if(nTerm>0 ) return 1;
+ return 0;
+ }
+
+ c = memcmp(pReader->term.pData, pTerm, n);
+ if( c!=0 ) return c;
+ if( isPrefix && n==nTerm ) return 0;
+ return pReader->term.nData - nTerm;
+}
+
+
+/****************************************************************/
+/* LeavesReader wraps LeafReader to allow iterating over the entire
+** leaf layer of the tree.
+*/
+typedef struct LeavesReader {
+ int idx; /* Index within the segment. */
+
+ sqlite3_stmt *pStmt; /* Statement we're streaming leaves from. */
+ int eof; /* we've seen SQLITE_DONE from pStmt. */
+
+ LeafReader leafReader; /* reader for the current leaf. */
+ DataBuffer rootData; /* root data for inline. */
+} LeavesReader;
+
+/* Access the current term. */
+static int leavesReaderTermBytes(LeavesReader *pReader){
+ assert( !pReader->eof );
+ return leafReaderTermBytes(&pReader->leafReader);
+}
+static const char *leavesReaderTerm(LeavesReader *pReader){
+ assert( !pReader->eof );
+ return leafReaderTerm(&pReader->leafReader);
+}
+
+/* Access the doclist data for the current term. */
+static int leavesReaderDataBytes(LeavesReader *pReader){
+ assert( !pReader->eof );
+ return leafReaderDataBytes(&pReader->leafReader);
+}
+static const char *leavesReaderData(LeavesReader *pReader){
+ assert( !pReader->eof );
+ return leafReaderData(&pReader->leafReader);
+}
+
+static int leavesReaderAtEnd(LeavesReader *pReader){
+ return pReader->eof;
+}
+
+/* loadSegmentLeaves() may not read all the way to SQLITE_DONE, thus
+** leaving the statement handle open, which locks the table.
+*/
+/* TODO(shess) This "solution" is not satisfactory. Really, there
+** should be check-in function for all statement handles which
+** arranges to call sqlite3_reset(). This most likely will require
+** modification to control flow all over the place, though, so for now
+** just punt.
+**
+** Note the the current system assumes that segment merges will run to
+** completion, which is why this particular probably hasn't arisen in
+** this case. Probably a brittle assumption.
+*/
+static int leavesReaderReset(LeavesReader *pReader){
+ return sqlite3_reset(pReader->pStmt);
+}
+
+static void leavesReaderDestroy(LeavesReader *pReader){
+ leafReaderDestroy(&pReader->leafReader);
+ dataBufferDestroy(&pReader->rootData);
+ SCRAMBLE(pReader);
+}
+
+/* Initialize pReader with the given root data (if iStartBlockid==0
+** the leaf data was entirely contained in the root), or from the
+** stream of blocks between iStartBlockid and iEndBlockid, inclusive.
+*/
+static int leavesReaderInit(fulltext_vtab *v,
+ int idx,
+ sqlite_int64 iStartBlockid,
+ sqlite_int64 iEndBlockid,
+ const char *pRootData, int nRootData,
+ LeavesReader *pReader){
+ CLEAR(pReader);
+ pReader->idx = idx;
+
+ dataBufferInit(&pReader->rootData, 0);
+ if( iStartBlockid==0 ){
+ /* Entire leaf level fit in root data. */
+ dataBufferReplace(&pReader->rootData, pRootData, nRootData);
+ leafReaderInit(pReader->rootData.pData, pReader->rootData.nData,
+ &pReader->leafReader);
+ }else{
+ sqlite3_stmt *s;
+ int rc = sql_get_leaf_statement(v, idx, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 1, iStartBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 2, iEndBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_DONE ){
+ pReader->eof = 1;
+ return SQLITE_OK;
+ }
+ if( rc!=SQLITE_ROW ) return rc;
+
+ pReader->pStmt = s;
+ leafReaderInit(sqlite3_column_blob(pReader->pStmt, 0),
+ sqlite3_column_bytes(pReader->pStmt, 0),
+ &pReader->leafReader);
+ }
+ return SQLITE_OK;
+}
+
+/* Step the current leaf forward to the next term. If we reach the
+** end of the current leaf, step forward to the next leaf block.
+*/
+static int leavesReaderStep(fulltext_vtab *v, LeavesReader *pReader){
+ assert( !leavesReaderAtEnd(pReader) );
+ leafReaderStep(&pReader->leafReader);
+
+ if( leafReaderAtEnd(&pReader->leafReader) ){
+ int rc;
+ if( pReader->rootData.pData ){
+ pReader->eof = 1;
+ return SQLITE_OK;
+ }
+ rc = sqlite3_step(pReader->pStmt);
+ if( rc!=SQLITE_ROW ){
+ pReader->eof = 1;
+ return rc==SQLITE_DONE ? SQLITE_OK : rc;
+ }
+ leafReaderDestroy(&pReader->leafReader);
+ leafReaderInit(sqlite3_column_blob(pReader->pStmt, 0),
+ sqlite3_column_bytes(pReader->pStmt, 0),
+ &pReader->leafReader);
+ }
+ return SQLITE_OK;
+}
+
+/* Order LeavesReaders by their term, ignoring idx. Readers at eof
+** always sort to the end.
+*/
+static int leavesReaderTermCmp(LeavesReader *lr1, LeavesReader *lr2){
+ if( leavesReaderAtEnd(lr1) ){
+ if( leavesReaderAtEnd(lr2) ) return 0;
+ return 1;
+ }
+ if( leavesReaderAtEnd(lr2) ) return -1;
+
+ return leafReaderTermCmp(&lr1->leafReader,
+ leavesReaderTerm(lr2), leavesReaderTermBytes(lr2),
+ 0);
+}
+
+/* Similar to leavesReaderTermCmp(), with additional ordering by idx
+** so that older segments sort before newer segments.
+*/
+static int leavesReaderCmp(LeavesReader *lr1, LeavesReader *lr2){
+ int c = leavesReaderTermCmp(lr1, lr2);
+ if( c!=0 ) return c;
+ return lr1->idx-lr2->idx;
+}
+
+/* Assume that pLr[1]..pLr[nLr] are sorted. Bubble pLr[0] into its
+** sorted position.
+*/
+static void leavesReaderReorder(LeavesReader *pLr, int nLr){
+ while( nLr>1 && leavesReaderCmp(pLr, pLr+1)>0 ){
+ LeavesReader tmp = pLr[0];
+ pLr[0] = pLr[1];
+ pLr[1] = tmp;
+ nLr--;
+ pLr++;
+ }
+}
+
+/* Initializes pReaders with the segments from level iLevel, returning
+** the number of segments in *piReaders. Leaves pReaders in sorted
+** order.
+*/
+static int leavesReadersInit(fulltext_vtab *v, int iLevel,
+ LeavesReader *pReaders, int *piReaders){
+ sqlite3_stmt *s;
+ int i, rc = sql_get_statement(v, SEGDIR_SELECT_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int(s, 1, iLevel);
+ if( rc!=SQLITE_OK ) return rc;
+
+ i = 0;
+ while( (rc = sqlite3_step(s))==SQLITE_ROW ){
+ sqlite_int64 iStart = sqlite3_column_int64(s, 0);
+ sqlite_int64 iEnd = sqlite3_column_int64(s, 1);
+ const char *pRootData = sqlite3_column_blob(s, 2);
+ int nRootData = sqlite3_column_bytes(s, 2);
+
+ assert( i<MERGE_COUNT );
+ rc = leavesReaderInit(v, i, iStart, iEnd, pRootData, nRootData,
+ &pReaders[i]);
+ if( rc!=SQLITE_OK ) break;
+
+ i++;
+ }
+ if( rc!=SQLITE_DONE ){
+ while( i-->0 ){
+ leavesReaderDestroy(&pReaders[i]);
+ }
+ return rc;
+ }
+
+ *piReaders = i;
+
+ /* Leave our results sorted by term, then age. */
+ while( i-- ){
+ leavesReaderReorder(pReaders+i, *piReaders-i);
+ }
+ return SQLITE_OK;
+}
+
+/* Merge doclists from pReaders[nReaders] into a single doclist, which
+** is written to pWriter. Assumes pReaders is ordered oldest to
+** newest.
+*/
+/* TODO(shess) Consider putting this inline in segmentMerge(). */
+static int leavesReadersMerge(fulltext_vtab *v,
+ LeavesReader *pReaders, int nReaders,
+ LeafWriter *pWriter){
+ DLReader dlReaders[MERGE_COUNT];
+ const char *pTerm = leavesReaderTerm(pReaders);
+ int i, nTerm = leavesReaderTermBytes(pReaders);
+
+ assert( nReaders<=MERGE_COUNT );
+
+ for(i=0; i<nReaders; i++){
+ dlrInit(&dlReaders[i], DL_DEFAULT,
+ leavesReaderData(pReaders+i),
+ leavesReaderDataBytes(pReaders+i));
+ }
+
+ return leafWriterStepMerge(v, pWriter, pTerm, nTerm, dlReaders, nReaders);
+}
+
+/* Forward ref due to mutual recursion with segdirNextIndex(). */
+static int segmentMerge(fulltext_vtab *v, int iLevel);
+
+/* Put the next available index at iLevel into *pidx. If iLevel
+** already has MERGE_COUNT segments, they are merged to a higher
+** level to make room.
+*/
+static int segdirNextIndex(fulltext_vtab *v, int iLevel, int *pidx){
+ int rc = segdir_max_index(v, iLevel, pidx);
+ if( rc==SQLITE_DONE ){ /* No segments at iLevel. */
+ *pidx = 0;
+ }else if( rc==SQLITE_ROW ){
+ if( *pidx==(MERGE_COUNT-1) ){
+ rc = segmentMerge(v, iLevel);
+ if( rc!=SQLITE_OK ) return rc;
+ *pidx = 0;
+ }else{
+ (*pidx)++;
+ }
+ }else{
+ return rc;
+ }
+ return SQLITE_OK;
+}
+
+/* Merge MERGE_COUNT segments at iLevel into a new segment at
+** iLevel+1. If iLevel+1 is already full of segments, those will be
+** merged to make room.
+*/
+static int segmentMerge(fulltext_vtab *v, int iLevel){
+ LeafWriter writer;
+ LeavesReader lrs[MERGE_COUNT];
+ int i, rc, idx = 0;
+
+ /* Determine the next available segment index at the next level,
+ ** merging as necessary.
+ */
+ rc = segdirNextIndex(v, iLevel+1, &idx);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* TODO(shess) This assumes that we'll always see exactly
+ ** MERGE_COUNT segments to merge at a given level. That will be
+ ** broken if we allow the developer to request preemptive or
+ ** deferred merging.
+ */
+ memset(&lrs, '\0', sizeof(lrs));
+ rc = leavesReadersInit(v, iLevel, lrs, &i);
+ if( rc!=SQLITE_OK ) return rc;
+ assert( i==MERGE_COUNT );
+
+ leafWriterInit(iLevel+1, idx, &writer);
+
+ /* Since leavesReaderReorder() pushes readers at eof to the end,
+ ** when the first reader is empty, all will be empty.
+ */
+ while( !leavesReaderAtEnd(lrs) ){
+ /* Figure out how many readers share their next term. */
+ for(i=1; i<MERGE_COUNT && !leavesReaderAtEnd(lrs+i); i++){
+ if( 0!=leavesReaderTermCmp(lrs, lrs+i) ) break;
+ }
+
+ rc = leavesReadersMerge(v, lrs, i, &writer);
+ if( rc!=SQLITE_OK ) goto err;
+
+ /* Step forward those that were merged. */
+ while( i-->0 ){
+ rc = leavesReaderStep(v, lrs+i);
+ if( rc!=SQLITE_OK ) goto err;
+
+ /* Reorder by term, then by age. */
+ leavesReaderReorder(lrs+i, MERGE_COUNT-i);
+ }
+ }
+
+ for(i=0; i<MERGE_COUNT; i++){
+ leavesReaderDestroy(&lrs[i]);
+ }
+
+ rc = leafWriterFinalize(v, &writer);
+ leafWriterDestroy(&writer);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Delete the merged segment data. */
+ return segdir_delete(v, iLevel);
+
+ err:
+ for(i=0; i<MERGE_COUNT; i++){
+ leavesReaderDestroy(&lrs[i]);
+ }
+ leafWriterDestroy(&writer);
+ return rc;
+}
+
+/* Accumulate the union of *acc and *pData into *acc. */
+static void docListAccumulateUnion(DataBuffer *acc,
+ const char *pData, int nData) {
+ DataBuffer tmp = *acc;
+ dataBufferInit(acc, tmp.nData+nData);
+ docListUnion(tmp.pData, tmp.nData, pData, nData, acc);
+ dataBufferDestroy(&tmp);
+}
+
+/* TODO(shess) It might be interesting to explore different merge
+** strategies, here. For instance, since this is a sorted merge, we
+** could easily merge many doclists in parallel. With some
+** comprehension of the storage format, we could merge all of the
+** doclists within a leaf node directly from the leaf node's storage.
+** It may be worthwhile to merge smaller doclists before larger
+** doclists, since they can be traversed more quickly - but the
+** results may have less overlap, making them more expensive in a
+** different way.
+*/
+
+/* Scan pReader for pTerm/nTerm, and merge the term's doclist over
+** *out (any doclists with duplicate docids overwrite those in *out).
+** Internal function for loadSegmentLeaf().
+*/
+static int loadSegmentLeavesInt(fulltext_vtab *v, LeavesReader *pReader,
+ const char *pTerm, int nTerm, int isPrefix,
+ DataBuffer *out){
+ /* doclist data is accumulated into pBuffers similar to how one does
+ ** increment in binary arithmetic. If index 0 is empty, the data is
+ ** stored there. If there is data there, it is merged and the
+ ** results carried into position 1, with further merge-and-carry
+ ** until an empty position is found.
+ */
+ DataBuffer *pBuffers = NULL;
+ int nBuffers = 0, nMaxBuffers = 0, rc;
+
+ assert( nTerm>0 );
+
+ for(rc=SQLITE_OK; rc==SQLITE_OK && !leavesReaderAtEnd(pReader);
+ rc=leavesReaderStep(v, pReader)){
+ /* TODO(shess) Really want leavesReaderTermCmp(), but that name is
+ ** already taken to compare the terms of two LeavesReaders. Think
+ ** on a better name. [Meanwhile, break encapsulation rather than
+ ** use a confusing name.]
+ */
+ int c = leafReaderTermCmp(&pReader->leafReader, pTerm, nTerm, isPrefix);
+ if( c>0 ) break; /* Past any possible matches. */
+ if( c==0 ){
+ const char *pData = leavesReaderData(pReader);
+ int iBuffer, nData = leavesReaderDataBytes(pReader);
+
+ /* Find the first empty buffer. */
+ for(iBuffer=0; iBuffer<nBuffers; ++iBuffer){
+ if( 0==pBuffers[iBuffer].nData ) break;
+ }
+
+ /* Out of buffers, add an empty one. */
+ if( iBuffer==nBuffers ){
+ if( nBuffers==nMaxBuffers ){
+ DataBuffer *p;
+ nMaxBuffers += 20;
+
+ /* Manual realloc so we can handle NULL appropriately. */
+ p = sqlite3_malloc(nMaxBuffers*sizeof(*pBuffers));
+ if( p==NULL ){
+ rc = SQLITE_NOMEM;
+ break;
+ }
+
+ if( nBuffers>0 ){
+ assert(pBuffers!=NULL);
+ memcpy(p, pBuffers, nBuffers*sizeof(*pBuffers));
+ sqlite3_free(pBuffers);
+ }
+ pBuffers = p;
+ }
+ dataBufferInit(&(pBuffers[nBuffers]), 0);
+ nBuffers++;
+ }
+
+ /* At this point, must have an empty at iBuffer. */
+ assert(iBuffer<nBuffers && pBuffers[iBuffer].nData==0);
+
+ /* If empty was first buffer, no need for merge logic. */
+ if( iBuffer==0 ){
+ dataBufferReplace(&(pBuffers[0]), pData, nData);
+ }else{
+ /* pAcc is the empty buffer the merged data will end up in. */
+ DataBuffer *pAcc = &(pBuffers[iBuffer]);
+ DataBuffer *p = &(pBuffers[0]);
+
+ /* Handle position 0 specially to avoid need to prime pAcc
+ ** with pData/nData.
+ */
+ dataBufferSwap(p, pAcc);
+ docListAccumulateUnion(pAcc, pData, nData);
+
+ /* Accumulate remaining doclists into pAcc. */
+ for(++p; p<pAcc; ++p){
+ docListAccumulateUnion(pAcc, p->pData, p->nData);
+
+ /* dataBufferReset() could allow a large doclist to blow up
+ ** our memory requirements.
+ */
+ if( p->nCapacity<1024 ){
+ dataBufferReset(p);
+ }else{
+ dataBufferDestroy(p);
+ dataBufferInit(p, 0);
+ }
+ }
+ }
+ }
+ }
+
+ /* Union all the doclists together into *out. */
+ /* TODO(shess) What if *out is big? Sigh. */
+ if( rc==SQLITE_OK && nBuffers>0 ){
+ int iBuffer;
+ for(iBuffer=0; iBuffer<nBuffers; ++iBuffer){
+ if( pBuffers[iBuffer].nData>0 ){
+ if( out->nData==0 ){
+ dataBufferSwap(out, &(pBuffers[iBuffer]));
+ }else{
+ docListAccumulateUnion(out, pBuffers[iBuffer].pData,
+ pBuffers[iBuffer].nData);
+ }
+ }
+ }
+ }
+
+ while( nBuffers-- ){
+ dataBufferDestroy(&(pBuffers[nBuffers]));
+ }
+ if( pBuffers!=NULL ) sqlite3_free(pBuffers);
+
+ return rc;
+}
+
+/* Call loadSegmentLeavesInt() with pData/nData as input. */
+static int loadSegmentLeaf(fulltext_vtab *v, const char *pData, int nData,
+ const char *pTerm, int nTerm, int isPrefix,
+ DataBuffer *out){
+ LeavesReader reader;
+ int rc;
+
+ assert( nData>1 );
+ assert( *pData=='\0' );
+ rc = leavesReaderInit(v, 0, 0, 0, pData, nData, &reader);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = loadSegmentLeavesInt(v, &reader, pTerm, nTerm, isPrefix, out);
+ leavesReaderReset(&reader);
+ leavesReaderDestroy(&reader);
+ return rc;
+}
+
+/* Call loadSegmentLeavesInt() with the leaf nodes from iStartLeaf to
+** iEndLeaf (inclusive) as input, and merge the resulting doclist into
+** out.
+*/
+static int loadSegmentLeaves(fulltext_vtab *v,
+ sqlite_int64 iStartLeaf, sqlite_int64 iEndLeaf,
+ const char *pTerm, int nTerm, int isPrefix,
+ DataBuffer *out){
+ int rc;
+ LeavesReader reader;
+
+ assert( iStartLeaf<=iEndLeaf );
+ rc = leavesReaderInit(v, 0, iStartLeaf, iEndLeaf, NULL, 0, &reader);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = loadSegmentLeavesInt(v, &reader, pTerm, nTerm, isPrefix, out);
+ leavesReaderReset(&reader);
+ leavesReaderDestroy(&reader);
+ return rc;
+}
+
+/* Taking pData/nData as an interior node, find the sequence of child
+** nodes which could include pTerm/nTerm/isPrefix. Note that the
+** interior node terms logically come between the blocks, so there is
+** one more blockid than there are terms (that block contains terms >=
+** the last interior-node term).
+*/
+/* TODO(shess) The calling code may already know that the end child is
+** not worth calculating, because the end may be in a later sibling
+** node. Consider whether breaking symmetry is worthwhile. I suspect
+** it is not worthwhile.
+*/
+static void getChildrenContaining(const char *pData, int nData,
+ const char *pTerm, int nTerm, int isPrefix,
+ sqlite_int64 *piStartChild,
+ sqlite_int64 *piEndChild){
+ InteriorReader reader;
+
+ assert( nData>1 );
+ assert( *pData!='\0' );
+ interiorReaderInit(pData, nData, &reader);
+
+ /* Scan for the first child which could contain pTerm/nTerm. */
+ while( !interiorReaderAtEnd(&reader) ){
+ if( interiorReaderTermCmp(&reader, pTerm, nTerm, 0)>0 ) break;
+ interiorReaderStep(&reader);
+ }
+ *piStartChild = interiorReaderCurrentBlockid(&reader);
+
+ /* Keep scanning to find a term greater than our term, using prefix
+ ** comparison if indicated. If isPrefix is false, this will be the
+ ** same blockid as the starting block.
+ */
+ while( !interiorReaderAtEnd(&reader) ){
+ if( interiorReaderTermCmp(&reader, pTerm, nTerm, isPrefix)>0 ) break;
+ interiorReaderStep(&reader);
+ }
+ *piEndChild = interiorReaderCurrentBlockid(&reader);
+
+ interiorReaderDestroy(&reader);
+
+ /* Children must ascend, and if !prefix, both must be the same. */
+ assert( *piEndChild>=*piStartChild );
+ assert( isPrefix || *piStartChild==*piEndChild );
+}
+
+/* Read block at iBlockid and pass it with other params to
+** getChildrenContaining().
+*/
+static int loadAndGetChildrenContaining(
+ fulltext_vtab *v,
+ sqlite_int64 iBlockid,
+ const char *pTerm, int nTerm, int isPrefix,
+ sqlite_int64 *piStartChild, sqlite_int64 *piEndChild
+){
+ sqlite3_stmt *s = NULL;
+ int rc;
+
+ assert( iBlockid!=0 );
+ assert( pTerm!=NULL );
+ assert( nTerm!=0 ); /* TODO(shess) Why not allow this? */
+ assert( piStartChild!=NULL );
+ assert( piEndChild!=NULL );
+
+ rc = sql_get_statement(v, BLOCK_SELECT_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_bind_int64(s, 1, iBlockid);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_DONE ) return SQLITE_ERROR;
+ if( rc!=SQLITE_ROW ) return rc;
+
+ getChildrenContaining(sqlite3_column_blob(s, 0), sqlite3_column_bytes(s, 0),
+ pTerm, nTerm, isPrefix, piStartChild, piEndChild);
+
+ /* We expect only one row. We must execute another sqlite3_step()
+ * to complete the iteration; otherwise the table will remain
+ * locked. */
+ rc = sqlite3_step(s);
+ if( rc==SQLITE_ROW ) return SQLITE_ERROR;
+ if( rc!=SQLITE_DONE ) return rc;
+
+ return SQLITE_OK;
+}
+
+/* Traverse the tree represented by pData[nData] looking for
+** pTerm[nTerm], placing its doclist into *out. This is internal to
+** loadSegment() to make error-handling cleaner.
+*/
+static int loadSegmentInt(fulltext_vtab *v, const char *pData, int nData,
+ sqlite_int64 iLeavesEnd,
+ const char *pTerm, int nTerm, int isPrefix,
+ DataBuffer *out){
+ /* Special case where root is a leaf. */
+ if( *pData=='\0' ){
+ return loadSegmentLeaf(v, pData, nData, pTerm, nTerm, isPrefix, out);
+ }else{
+ int rc;
+ sqlite_int64 iStartChild, iEndChild;
+
+ /* Process pData as an interior node, then loop down the tree
+ ** until we find the set of leaf nodes to scan for the term.
+ */
+ getChildrenContaining(pData, nData, pTerm, nTerm, isPrefix,
+ &iStartChild, &iEndChild);
+ while( iStartChild>iLeavesEnd ){
+ sqlite_int64 iNextStart, iNextEnd;
+ rc = loadAndGetChildrenContaining(v, iStartChild, pTerm, nTerm, isPrefix,
+ &iNextStart, &iNextEnd);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* If we've branched, follow the end branch, too. */
+ if( iStartChild!=iEndChild ){
+ sqlite_int64 iDummy;
+ rc = loadAndGetChildrenContaining(v, iEndChild, pTerm, nTerm, isPrefix,
+ &iDummy, &iNextEnd);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ assert( iNextStart<=iNextEnd );
+ iStartChild = iNextStart;
+ iEndChild = iNextEnd;
+ }
+ assert( iStartChild<=iLeavesEnd );
+ assert( iEndChild<=iLeavesEnd );
+
+ /* Scan through the leaf segments for doclists. */
+ return loadSegmentLeaves(v, iStartChild, iEndChild,
+ pTerm, nTerm, isPrefix, out);
+ }
+}
+
+/* Call loadSegmentInt() to collect the doclist for pTerm/nTerm, then
+** merge its doclist over *out (any duplicate doclists read from the
+** segment rooted at pData will overwrite those in *out).
+*/
+/* TODO(shess) Consider changing this to determine the depth of the
+** leaves using either the first characters of interior nodes (when
+** ==1, we're one level above the leaves), or the first character of
+** the root (which will describe the height of the tree directly).
+** Either feels somewhat tricky to me.
+*/
+/* TODO(shess) The current merge is likely to be slow for large
+** doclists (though it should process from newest/smallest to
+** oldest/largest, so it may not be that bad). It might be useful to
+** modify things to allow for N-way merging. This could either be
+** within a segment, with pairwise merges across segments, or across
+** all segments at once.
+*/
+static int loadSegment(fulltext_vtab *v, const char *pData, int nData,
+ sqlite_int64 iLeavesEnd,
+ const char *pTerm, int nTerm, int isPrefix,
+ DataBuffer *out){
+ DataBuffer result;
+ int rc;
+
+ assert( nData>1 );
+
+ /* This code should never be called with buffered updates. */
+ assert( v->nPendingData<0 );
+
+ dataBufferInit(&result, 0);
+ rc = loadSegmentInt(v, pData, nData, iLeavesEnd,
+ pTerm, nTerm, isPrefix, &result);
+ if( rc==SQLITE_OK && result.nData>0 ){
+ if( out->nData==0 ){
+ DataBuffer tmp = *out;
+ *out = result;
+ result = tmp;
+ }else{
+ DataBuffer merged;
+ DLReader readers[2];
+
+ dlrInit(&readers[0], DL_DEFAULT, out->pData, out->nData);
+ dlrInit(&readers[1], DL_DEFAULT, result.pData, result.nData);
+ dataBufferInit(&merged, out->nData+result.nData);
+ docListMerge(&merged, readers, 2);
+ dataBufferDestroy(out);
+ *out = merged;
+ dlrDestroy(&readers[0]);
+ dlrDestroy(&readers[1]);
+ }
+ }
+ dataBufferDestroy(&result);
+ return rc;
+}
+
+/* Scan the database and merge together the posting lists for the term
+** into *out.
+*/
+static int termSelect(fulltext_vtab *v, int iColumn,
+ const char *pTerm, int nTerm, int isPrefix,
+ DocListType iType, DataBuffer *out){
+ DataBuffer doclist;
+ sqlite3_stmt *s;
+ int rc = sql_get_statement(v, SEGDIR_SELECT_ALL_STMT, &s);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* This code should never be called with buffered updates. */
+ assert( v->nPendingData<0 );
+
+ dataBufferInit(&doclist, 0);
+
+ /* Traverse the segments from oldest to newest so that newer doclist
+ ** elements for given docids overwrite older elements.
+ */
+ while( (rc = sqlite3_step(s))==SQLITE_ROW ){
+ const char *pData = sqlite3_column_blob(s, 0);
+ const int nData = sqlite3_column_bytes(s, 0);
+ const sqlite_int64 iLeavesEnd = sqlite3_column_int64(s, 1);
+ rc = loadSegment(v, pData, nData, iLeavesEnd, pTerm, nTerm, isPrefix,
+ &doclist);
+ if( rc!=SQLITE_OK ) goto err;
+ }
+ if( rc==SQLITE_DONE ){
+ if( doclist.nData!=0 ){
+ /* TODO(shess) The old term_select_all() code applied the column
+ ** restrict as we merged segments, leading to smaller buffers.
+ ** This is probably worthwhile to bring back, once the new storage
+ ** system is checked in.
+ */
+ if( iColumn==v->nColumn) iColumn = -1;
+ docListTrim(DL_DEFAULT, doclist.pData, doclist.nData,
+ iColumn, iType, out);
+ }
+ rc = SQLITE_OK;
+ }
+
+ err:
+ dataBufferDestroy(&doclist);
+ return rc;
+}
+
+/****************************************************************/
+/* Used to hold hashtable data for sorting. */
+typedef struct TermData {
+ const char *pTerm;
+ int nTerm;
+ DLCollector *pCollector;
+} TermData;
+
+/* Orders TermData elements in strcmp fashion ( <0 for less-than, 0
+** for equal, >0 for greater-than).
+*/
+static int termDataCmp(const void *av, const void *bv){
+ const TermData *a = (const TermData *)av;
+ const TermData *b = (const TermData *)bv;
+ int n = a->nTerm<b->nTerm ? a->nTerm : b->nTerm;
+ int c = memcmp(a->pTerm, b->pTerm, n);
+ if( c!=0 ) return c;
+ return a->nTerm-b->nTerm;
+}
+
+/* Order pTerms data by term, then write a new level 0 segment using
+** LeafWriter.
+*/
+static int writeZeroSegment(fulltext_vtab *v, fts3Hash *pTerms){
+ fts3HashElem *e;
+ int idx, rc, i, n;
+ TermData *pData;
+ LeafWriter writer;
+ DataBuffer dl;
+
+ /* Determine the next index at level 0, merging as necessary. */
+ rc = segdirNextIndex(v, 0, &idx);
+ if( rc!=SQLITE_OK ) return rc;
+
+ n = fts3HashCount(pTerms);
+ pData = sqlite3_malloc(n*sizeof(TermData));
+
+ for(i = 0, e = fts3HashFirst(pTerms); e; i++, e = fts3HashNext(e)){
+ assert( i<n );
+ pData[i].pTerm = fts3HashKey(e);
+ pData[i].nTerm = fts3HashKeysize(e);
+ pData[i].pCollector = fts3HashData(e);
+ }
+ assert( i==n );
+
+ /* TODO(shess) Should we allow user-defined collation sequences,
+ ** here? I think we only need that once we support prefix searches.
+ */
+ if( n>1 ) qsort(pData, n, sizeof(*pData), termDataCmp);
+
+ /* TODO(shess) Refactor so that we can write directly to the segment
+ ** DataBuffer, as happens for segment merges.
+ */
+ leafWriterInit(0, idx, &writer);
+ dataBufferInit(&dl, 0);
+ for(i=0; i<n; i++){
+ dataBufferReset(&dl);
+ dlcAddDoclist(pData[i].pCollector, &dl);
+ rc = leafWriterStep(v, &writer,
+ pData[i].pTerm, pData[i].nTerm, dl.pData, dl.nData);
+ if( rc!=SQLITE_OK ) goto err;
+ }
+ rc = leafWriterFinalize(v, &writer);
+
+ err:
+ dataBufferDestroy(&dl);
+ sqlite3_free(pData);
+ leafWriterDestroy(&writer);
+ return rc;
+}
+
+/* If pendingTerms has data, free it. */
+static int clearPendingTerms(fulltext_vtab *v){
+ if( v->nPendingData>=0 ){
+ fts3HashElem *e;
+ for(e=fts3HashFirst(&v->pendingTerms); e; e=fts3HashNext(e)){
+ dlcDelete(fts3HashData(e));
+ }
+ fts3HashClear(&v->pendingTerms);
+ v->nPendingData = -1;
+ }
+ return SQLITE_OK;
+}
+
+/* If pendingTerms has data, flush it to a level-zero segment, and
+** free it.
+*/
+static int flushPendingTerms(fulltext_vtab *v){
+ if( v->nPendingData>=0 ){
+ int rc = writeZeroSegment(v, &v->pendingTerms);
+ if( rc==SQLITE_OK ) clearPendingTerms(v);
+ return rc;
+ }
+ return SQLITE_OK;
+}
+
+/* If pendingTerms is "too big", or docid is out of order, flush it.
+** Regardless, be certain that pendingTerms is initialized for use.
+*/
+static int initPendingTerms(fulltext_vtab *v, sqlite_int64 iDocid){
+ /* TODO(shess) Explore whether partially flushing the buffer on
+ ** forced-flush would provide better performance. I suspect that if
+ ** we ordered the doclists by size and flushed the largest until the
+ ** buffer was half empty, that would let the less frequent terms
+ ** generate longer doclists.
+ */
+ if( iDocid<=v->iPrevDocid || v->nPendingData>kPendingThreshold ){
+ int rc = flushPendingTerms(v);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ if( v->nPendingData<0 ){
+ fts3HashInit(&v->pendingTerms, FTS3_HASH_STRING, 1);
+ v->nPendingData = 0;
+ }
+ v->iPrevDocid = iDocid;
+ return SQLITE_OK;
+}
+
+/* This function implements the xUpdate callback; it is the top-level entry
+ * point for inserting, deleting or updating a row in a full-text table. */
+static int fulltextUpdate(sqlite3_vtab *pVtab, int nArg, sqlite3_value **ppArg,
+ sqlite_int64 *pRowid){
+ fulltext_vtab *v = (fulltext_vtab *) pVtab;
+ int rc;
+
+ FTSTRACE(("FTS3 Update %p\n", pVtab));
+
+ if( nArg<2 ){
+ rc = index_delete(v, sqlite3_value_int64(ppArg[0]));
+ } else if( sqlite3_value_type(ppArg[0]) != SQLITE_NULL ){
+ /* An update:
+ * ppArg[0] = old rowid
+ * ppArg[1] = new rowid
+ * ppArg[2..2+v->nColumn-1] = values
+ * ppArg[2+v->nColumn] = value for magic column (we ignore this)
+ * ppArg[2+v->nColumn+1] = value for docid
+ */
+ sqlite_int64 rowid = sqlite3_value_int64(ppArg[0]);
+ if( sqlite3_value_type(ppArg[1]) != SQLITE_INTEGER ||
+ sqlite3_value_int64(ppArg[1]) != rowid ){
+ rc = SQLITE_ERROR; /* we don't allow changing the rowid */
+ }else if( sqlite3_value_type(ppArg[2+v->nColumn+1]) != SQLITE_INTEGER ||
+ sqlite3_value_int64(ppArg[2+v->nColumn+1]) != rowid ){
+ rc = SQLITE_ERROR; /* we don't allow changing the docid */
+ }else{
+ assert( nArg==2+v->nColumn+2);
+ rc = index_update(v, rowid, &ppArg[2]);
+ }
+ } else {
+ /* An insert:
+ * ppArg[1] = requested rowid
+ * ppArg[2..2+v->nColumn-1] = values
+ * ppArg[2+v->nColumn] = value for magic column (we ignore this)
+ * ppArg[2+v->nColumn+1] = value for docid
+ */
+ sqlite3_value *pRequestDocid = ppArg[2+v->nColumn+1];
+ assert( nArg==2+v->nColumn+2);
+ if( SQLITE_NULL != sqlite3_value_type(pRequestDocid) &&
+ SQLITE_NULL != sqlite3_value_type(ppArg[1]) ){
+ /* TODO(shess) Consider allowing this to work if the values are
+ ** identical. I'm inclined to discourage that usage, though,
+ ** given that both rowid and docid are special columns. Better
+ ** would be to define one or the other as the default winner,
+ ** but should it be fts3-centric (docid) or SQLite-centric
+ ** (rowid)?
+ */
+ rc = SQLITE_ERROR;
+ }else{
+ if( SQLITE_NULL == sqlite3_value_type(pRequestDocid) ){
+ pRequestDocid = ppArg[1];
+ }
+ rc = index_insert(v, pRequestDocid, &ppArg[2], pRowid);
+ }
+ }
+
+ return rc;
+}
+
+static int fulltextSync(sqlite3_vtab *pVtab){
+ FTSTRACE(("FTS3 xSync()\n"));
+ return flushPendingTerms((fulltext_vtab *)pVtab);
+}
+
+static int fulltextBegin(sqlite3_vtab *pVtab){
+ fulltext_vtab *v = (fulltext_vtab *) pVtab;
+ FTSTRACE(("FTS3 xBegin()\n"));
+
+ /* Any buffered updates should have been cleared by the previous
+ ** transaction.
+ */
+ assert( v->nPendingData<0 );
+ return clearPendingTerms(v);
+}
+
+static int fulltextCommit(sqlite3_vtab *pVtab){
+ fulltext_vtab *v = (fulltext_vtab *) pVtab;
+ FTSTRACE(("FTS3 xCommit()\n"));
+
+ /* Buffered updates should have been cleared by fulltextSync(). */
+ assert( v->nPendingData<0 );
+ return clearPendingTerms(v);
+}
+
+static int fulltextRollback(sqlite3_vtab *pVtab){
+ FTSTRACE(("FTS3 xRollback()\n"));
+ return clearPendingTerms((fulltext_vtab *)pVtab);
+}
+
+/*
+** Implementation of the snippet() function for FTS3
+*/
+static void snippetFunc(
+ sqlite3_context *pContext,
+ int argc,
+ sqlite3_value **argv
+){
+ fulltext_cursor *pCursor;
+ if( argc<1 ) return;
+ if( sqlite3_value_type(argv[0])!=SQLITE_BLOB ||
+ sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){
+ sqlite3_result_error(pContext, "illegal first argument to html_snippet",-1);
+ }else{
+ const char *zStart = "<b>";
+ const char *zEnd = "</b>";
+ const char *zEllipsis = "<b>...</b>";
+ memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor));
+ if( argc>=2 ){
+ zStart = (const char*)sqlite3_value_text(argv[1]);
+ if( argc>=3 ){
+ zEnd = (const char*)sqlite3_value_text(argv[2]);
+ if( argc>=4 ){
+ zEllipsis = (const char*)sqlite3_value_text(argv[3]);
+ }
+ }
+ }
+ snippetAllOffsets(pCursor);
+ snippetText(pCursor, zStart, zEnd, zEllipsis);
+ sqlite3_result_text(pContext, pCursor->snippet.zSnippet,
+ pCursor->snippet.nSnippet, SQLITE_STATIC);
+ }
+}
+
+/*
+** Implementation of the offsets() function for FTS3
+*/
+static void snippetOffsetsFunc(
+ sqlite3_context *pContext,
+ int argc,
+ sqlite3_value **argv
+){
+ fulltext_cursor *pCursor;
+ if( argc<1 ) return;
+ if( sqlite3_value_type(argv[0])!=SQLITE_BLOB ||
+ sqlite3_value_bytes(argv[0])!=sizeof(pCursor) ){
+ sqlite3_result_error(pContext, "illegal first argument to offsets",-1);
+ }else{
+ memcpy(&pCursor, sqlite3_value_blob(argv[0]), sizeof(pCursor));
+ snippetAllOffsets(pCursor);
+ snippetOffsetText(&pCursor->snippet);
+ sqlite3_result_text(pContext,
+ pCursor->snippet.zOffset, pCursor->snippet.nOffset,
+ SQLITE_STATIC);
+ }
+}
+
+/*
+** This routine implements the xFindFunction method for the FTS3
+** virtual table.
+*/
+static int fulltextFindFunction(
+ sqlite3_vtab *pVtab,
+ int nArg,
+ const char *zName,
+ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ void **ppArg
+){
+ if( strcmp(zName,"snippet")==0 ){
+ *pxFunc = snippetFunc;
+ return 1;
+ }else if( strcmp(zName,"offsets")==0 ){
+ *pxFunc = snippetOffsetsFunc;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** Rename an fts3 table.
+*/
+static int fulltextRename(
+ sqlite3_vtab *pVtab,
+ const char *zName
+){
+ fulltext_vtab *p = (fulltext_vtab *)pVtab;
+ int rc = SQLITE_NOMEM;
+ char *zSql = sqlite3_mprintf(
+ "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';"
+ "ALTER TABLE %Q.'%q_segments' RENAME TO '%q_segments';"
+ "ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';"
+ , p->zDb, p->zName, zName
+ , p->zDb, p->zName, zName
+ , p->zDb, p->zName, zName
+ );
+ if( zSql ){
+ rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ }
+ return rc;
+}
+
+static const sqlite3_module fts3Module = {
+ /* iVersion */ 0,
+ /* xCreate */ fulltextCreate,
+ /* xConnect */ fulltextConnect,
+ /* xBestIndex */ fulltextBestIndex,
+ /* xDisconnect */ fulltextDisconnect,
+ /* xDestroy */ fulltextDestroy,
+ /* xOpen */ fulltextOpen,
+ /* xClose */ fulltextClose,
+ /* xFilter */ fulltextFilter,
+ /* xNext */ fulltextNext,
+ /* xEof */ fulltextEof,
+ /* xColumn */ fulltextColumn,
+ /* xRowid */ fulltextRowid,
+ /* xUpdate */ fulltextUpdate,
+ /* xBegin */ fulltextBegin,
+ /* xSync */ fulltextSync,
+ /* xCommit */ fulltextCommit,
+ /* xRollback */ fulltextRollback,
+ /* xFindFunction */ fulltextFindFunction,
+ /* xRename */ fulltextRename,
+};
+
+static void hashDestroy(void *p){
+ fts3Hash *pHash = (fts3Hash *)p;
+ sqlite3Fts3HashClear(pHash);
+ sqlite3_free(pHash);
+}
+
+/*
+** The fts3 built-in tokenizers - "simple" and "porter" - are implemented
+** in files fts3_tokenizer1.c and fts3_porter.c respectively. The following
+** two forward declarations are for functions declared in these files
+** used to retrieve the respective implementations.
+**
+** Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed
+** to by the argument to point a the "simple" tokenizer implementation.
+** Function ...PorterTokenizerModule() sets *pModule to point to the
+** porter tokenizer/stemmer implementation.
+*/
+SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule);
+SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule);
+SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const**ppModule);
+
+SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, fts3Hash *, const char *);
+
+/*
+** Initialise the fts3 extension. If this extension is built as part
+** of the sqlite library, then this function is called directly by
+** SQLite. If fts3 is built as a dynamically loadable extension, this
+** function is called by the sqlite3_extension_init() entry point.
+*/
+SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){
+ int rc = SQLITE_OK;
+ fts3Hash *pHash = 0;
+ const sqlite3_tokenizer_module *pSimple = 0;
+ const sqlite3_tokenizer_module *pPorter = 0;
+ const sqlite3_tokenizer_module *pIcu = 0;
+
+ sqlite3Fts3SimpleTokenizerModule(&pSimple);
+ sqlite3Fts3PorterTokenizerModule(&pPorter);
+#ifdef SQLITE_ENABLE_ICU
+ sqlite3Fts3IcuTokenizerModule(&pIcu);
+#endif
+
+ /* Allocate and initialise the hash-table used to store tokenizers. */
+ pHash = sqlite3_malloc(sizeof(fts3Hash));
+ if( !pHash ){
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1);
+ }
+
+ /* Load the built-in tokenizers into the hash table */
+ if( rc==SQLITE_OK ){
+ if( sqlite3Fts3HashInsert(pHash, "simple", 7, (void *)pSimple)
+ || sqlite3Fts3HashInsert(pHash, "porter", 7, (void *)pPorter)
+ || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu))
+ ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ /* Create the virtual table wrapper around the hash-table and overload
+ ** the two scalar functions. If this is successful, register the
+ ** module with sqlite.
+ */
+ if( SQLITE_OK==rc
+ && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))
+ && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1))
+ && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", -1))
+ ){
+ return sqlite3_create_module_v2(
+ db, "fts3", &fts3Module, (void *)pHash, hashDestroy
+ );
+ }
+
+ /* An error has occured. Delete the hash table and return the error code. */
+ assert( rc!=SQLITE_OK );
+ if( pHash ){
+ sqlite3Fts3HashClear(pHash);
+ sqlite3_free(pHash);
+ }
+ return rc;
+}
+
+#if !SQLITE_CORE
+SQLITE_API int sqlite3_extension_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ SQLITE_EXTENSION_INIT2(pApi)
+ return sqlite3Fts3Init(db);
+}
+#endif
+
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
+
+/************** End of fts3.c ************************************************/
+/************** Begin file fts3_hash.c ***************************************/
+/*
+** 2001 September 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This is the implementation of generic hash-tables used in SQLite.
+** We've modified it slightly to serve as a standalone hash table
+** implementation for the full-text indexing module.
+*/
+
+/*
+** The code in this file is only compiled if:
+**
+** * The FTS3 module is being built as an extension
+** (in which case SQLITE_CORE is not defined), or
+**
+** * The FTS3 module is being built into the core of
+** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
+
+
+
+/*
+** Malloc and Free functions
+*/
+static void *fts3HashMalloc(int n){
+ void *p = sqlite3_malloc(n);
+ if( p ){
+ memset(p, 0, n);
+ }
+ return p;
+}
+static void fts3HashFree(void *p){
+ sqlite3_free(p);
+}
+
+/* Turn bulk memory into a hash table object by initializing the
+** fields of the Hash structure.
+**
+** "pNew" is a pointer to the hash table that is to be initialized.
+** keyClass is one of the constants
+** FTS3_HASH_BINARY or FTS3_HASH_STRING. The value of keyClass
+** determines what kind of key the hash table will use. "copyKey" is
+** true if the hash table should make its own private copy of keys and
+** false if it should just use the supplied pointer.
+*/
+SQLITE_PRIVATE void sqlite3Fts3HashInit(fts3Hash *pNew, int keyClass, int copyKey){
+ assert( pNew!=0 );
+ assert( keyClass>=FTS3_HASH_STRING && keyClass<=FTS3_HASH_BINARY );
+ pNew->keyClass = keyClass;
+ pNew->copyKey = copyKey;
+ pNew->first = 0;
+ pNew->count = 0;
+ pNew->htsize = 0;
+ pNew->ht = 0;
+}
+
+/* Remove all entries from a hash table. Reclaim all memory.
+** Call this routine to delete a hash table or to reset a hash table
+** to the empty state.
+*/
+SQLITE_PRIVATE void sqlite3Fts3HashClear(fts3Hash *pH){
+ fts3HashElem *elem; /* For looping over all elements of the table */
+
+ assert( pH!=0 );
+ elem = pH->first;
+ pH->first = 0;
+ fts3HashFree(pH->ht);
+ pH->ht = 0;
+ pH->htsize = 0;
+ while( elem ){
+ fts3HashElem *next_elem = elem->next;
+ if( pH->copyKey && elem->pKey ){
+ fts3HashFree(elem->pKey);
+ }
+ fts3HashFree(elem);
+ elem = next_elem;
+ }
+ pH->count = 0;
+}
+
+/*
+** Hash and comparison functions when the mode is FTS3_HASH_STRING
+*/
+static int fts3StrHash(const void *pKey, int nKey){
+ const char *z = (const char *)pKey;
+ int h = 0;
+ if( nKey<=0 ) nKey = (int) strlen(z);
+ while( nKey > 0 ){
+ h = (h<<3) ^ h ^ *z++;
+ nKey--;
+ }
+ return h & 0x7fffffff;
+}
+static int fts3StrCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( n1!=n2 ) return 1;
+ return strncmp((const char*)pKey1,(const char*)pKey2,n1);
+}
+
+/*
+** Hash and comparison functions when the mode is FTS3_HASH_BINARY
+*/
+static int fts3BinHash(const void *pKey, int nKey){
+ int h = 0;
+ const char *z = (const char *)pKey;
+ while( nKey-- > 0 ){
+ h = (h<<3) ^ h ^ *(z++);
+ }
+ return h & 0x7fffffff;
+}
+static int fts3BinCompare(const void *pKey1, int n1, const void *pKey2, int n2){
+ if( n1!=n2 ) return 1;
+ return memcmp(pKey1,pKey2,n1);
+}
+
+/*
+** Return a pointer to the appropriate hash function given the key class.
+**
+** The C syntax in this function definition may be unfamilar to some
+** programmers, so we provide the following additional explanation:
+**
+** The name of the function is "ftsHashFunction". The function takes a
+** single parameter "keyClass". The return value of ftsHashFunction()
+** is a pointer to another function. Specifically, the return value
+** of ftsHashFunction() is a pointer to a function that takes two parameters
+** with types "const void*" and "int" and returns an "int".
+*/
+static int (*ftsHashFunction(int keyClass))(const void*,int){
+ if( keyClass==FTS3_HASH_STRING ){
+ return &fts3StrHash;
+ }else{
+ assert( keyClass==FTS3_HASH_BINARY );
+ return &fts3BinHash;
+ }
+}
+
+/*
+** Return a pointer to the appropriate hash function given the key class.
+**
+** For help in interpreted the obscure C code in the function definition,
+** see the header comment on the previous function.
+*/
+static int (*ftsCompareFunction(int keyClass))(const void*,int,const void*,int){
+ if( keyClass==FTS3_HASH_STRING ){
+ return &fts3StrCompare;
+ }else{
+ assert( keyClass==FTS3_HASH_BINARY );
+ return &fts3BinCompare;
+ }
+}
+
+/* Link an element into the hash table
+*/
+static void fts3HashInsertElement(
+ fts3Hash *pH, /* The complete hash table */
+ struct _fts3ht *pEntry, /* The entry into which pNew is inserted */
+ fts3HashElem *pNew /* The element to be inserted */
+){
+ fts3HashElem *pHead; /* First element already in pEntry */
+ pHead = pEntry->chain;
+ if( pHead ){
+ pNew->next = pHead;
+ pNew->prev = pHead->prev;
+ if( pHead->prev ){ pHead->prev->next = pNew; }
+ else { pH->first = pNew; }
+ pHead->prev = pNew;
+ }else{
+ pNew->next = pH->first;
+ if( pH->first ){ pH->first->prev = pNew; }
+ pNew->prev = 0;
+ pH->first = pNew;
+ }
+ pEntry->count++;
+ pEntry->chain = pNew;
+}
+
+
+/* Resize the hash table so that it cantains "new_size" buckets.
+** "new_size" must be a power of 2. The hash table might fail
+** to resize if sqliteMalloc() fails.
+*/
+static void fts3Rehash(fts3Hash *pH, int new_size){
+ struct _fts3ht *new_ht; /* The new hash table */
+ fts3HashElem *elem, *next_elem; /* For looping over existing elements */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ assert( (new_size & (new_size-1))==0 );
+ new_ht = (struct _fts3ht *)fts3HashMalloc( new_size*sizeof(struct _fts3ht) );
+ if( new_ht==0 ) return;
+ fts3HashFree(pH->ht);
+ pH->ht = new_ht;
+ pH->htsize = new_size;
+ xHash = ftsHashFunction(pH->keyClass);
+ for(elem=pH->first, pH->first=0; elem; elem = next_elem){
+ int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1);
+ next_elem = elem->next;
+ fts3HashInsertElement(pH, &new_ht[h], elem);
+ }
+}
+
+/* This function (for internal use only) locates an element in an
+** hash table that matches the given key. The hash for this key has
+** already been computed and is passed as the 4th parameter.
+*/
+static fts3HashElem *fts3FindElementByHash(
+ const fts3Hash *pH, /* The pH to be searched */
+ const void *pKey, /* The key we are searching for */
+ int nKey,
+ int h /* The hash for this key. */
+){
+ fts3HashElem *elem; /* Used to loop thru the element list */
+ int count; /* Number of elements left to test */
+ int (*xCompare)(const void*,int,const void*,int); /* comparison function */
+
+ if( pH->ht ){
+ struct _fts3ht *pEntry = &pH->ht[h];
+ elem = pEntry->chain;
+ count = pEntry->count;
+ xCompare = ftsCompareFunction(pH->keyClass);
+ while( count-- && elem ){
+ if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){
+ return elem;
+ }
+ elem = elem->next;
+ }
+ }
+ return 0;
+}
+
+/* Remove a single entry from the hash table given a pointer to that
+** element and a hash on the element's key.
+*/
+static void fts3RemoveElementByHash(
+ fts3Hash *pH, /* The pH containing "elem" */
+ fts3HashElem* elem, /* The element to be removed from the pH */
+ int h /* Hash value for the element */
+){
+ struct _fts3ht *pEntry;
+ if( elem->prev ){
+ elem->prev->next = elem->next;
+ }else{
+ pH->first = elem->next;
+ }
+ if( elem->next ){
+ elem->next->prev = elem->prev;
+ }
+ pEntry = &pH->ht[h];
+ if( pEntry->chain==elem ){
+ pEntry->chain = elem->next;
+ }
+ pEntry->count--;
+ if( pEntry->count<=0 ){
+ pEntry->chain = 0;
+ }
+ if( pH->copyKey && elem->pKey ){
+ fts3HashFree(elem->pKey);
+ }
+ fts3HashFree( elem );
+ pH->count--;
+ if( pH->count<=0 ){
+ assert( pH->first==0 );
+ assert( pH->count==0 );
+ fts3HashClear(pH);
+ }
+}
+
+/* Attempt to locate an element of the hash table pH with a key
+** that matches pKey,nKey. Return the data for this element if it is
+** found, or NULL if there is no match.
+*/
+SQLITE_PRIVATE void *sqlite3Fts3HashFind(const fts3Hash *pH, const void *pKey, int nKey){
+ int h; /* A hash on key */
+ fts3HashElem *elem; /* The element that matches key */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ if( pH==0 || pH->ht==0 ) return 0;
+ xHash = ftsHashFunction(pH->keyClass);
+ assert( xHash!=0 );
+ h = (*xHash)(pKey,nKey);
+ assert( (pH->htsize & (pH->htsize-1))==0 );
+ elem = fts3FindElementByHash(pH,pKey,nKey, h & (pH->htsize-1));
+ return elem ? elem->data : 0;
+}
+
+/* Insert an element into the hash table pH. The key is pKey,nKey
+** and the data is "data".
+**
+** If no element exists with a matching key, then a new
+** element is created. A copy of the key is made if the copyKey
+** flag is set. NULL is returned.
+**
+** If another element already exists with the same key, then the
+** new data replaces the old data and the old data is returned.
+** The key is not copied in this instance. If a malloc fails, then
+** the new data is returned and the hash table is unchanged.
+**
+** If the "data" parameter to this function is NULL, then the
+** element corresponding to "key" is removed from the hash table.
+*/
+SQLITE_PRIVATE void *sqlite3Fts3HashInsert(
+ fts3Hash *pH, /* The hash table to insert into */
+ const void *pKey, /* The key */
+ int nKey, /* Number of bytes in the key */
+ void *data /* The data */
+){
+ int hraw; /* Raw hash value of the key */
+ int h; /* the hash of the key modulo hash table size */
+ fts3HashElem *elem; /* Used to loop thru the element list */
+ fts3HashElem *new_elem; /* New element added to the pH */
+ int (*xHash)(const void*,int); /* The hash function */
+
+ assert( pH!=0 );
+ xHash = ftsHashFunction(pH->keyClass);
+ assert( xHash!=0 );
+ hraw = (*xHash)(pKey, nKey);
+ assert( (pH->htsize & (pH->htsize-1))==0 );
+ h = hraw & (pH->htsize-1);
+ elem = fts3FindElementByHash(pH,pKey,nKey,h);
+ if( elem ){
+ void *old_data = elem->data;
+ if( data==0 ){
+ fts3RemoveElementByHash(pH,elem,h);
+ }else{
+ elem->data = data;
+ }
+ return old_data;
+ }
+ if( data==0 ) return 0;
+ new_elem = (fts3HashElem*)fts3HashMalloc( sizeof(fts3HashElem) );
+ if( new_elem==0 ) return data;
+ if( pH->copyKey && pKey!=0 ){
+ new_elem->pKey = fts3HashMalloc( nKey );
+ if( new_elem->pKey==0 ){
+ fts3HashFree(new_elem);
+ return data;
+ }
+ memcpy((void*)new_elem->pKey, pKey, nKey);
+ }else{
+ new_elem->pKey = (void*)pKey;
+ }
+ new_elem->nKey = nKey;
+ pH->count++;
+ if( pH->htsize==0 ){
+ fts3Rehash(pH,8);
+ if( pH->htsize==0 ){
+ pH->count = 0;
+ fts3HashFree(new_elem);
+ return data;
+ }
+ }
+ if( pH->count > pH->htsize ){
+ fts3Rehash(pH,pH->htsize*2);
+ }
+ assert( pH->htsize>0 );
+ assert( (pH->htsize & (pH->htsize-1))==0 );
+ h = hraw & (pH->htsize-1);
+ fts3HashInsertElement(pH, &pH->ht[h], new_elem);
+ new_elem->data = data;
+ return 0;
+}
+
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
+
+/************** End of fts3_hash.c *******************************************/
+/************** Begin file fts3_porter.c *************************************/
+/*
+** 2006 September 30
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Implementation of the full-text-search tokenizer that implements
+** a Porter stemmer.
+*/
+
+/*
+** The code in this file is only compiled if:
+**
+** * The FTS3 module is being built as an extension
+** (in which case SQLITE_CORE is not defined), or
+**
+** * The FTS3 module is being built into the core of
+** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
+
+
+
+
+/*
+** Class derived from sqlite3_tokenizer
+*/
+typedef struct porter_tokenizer {
+ sqlite3_tokenizer base; /* Base class */
+} porter_tokenizer;
+
+/*
+** Class derived from sqlit3_tokenizer_cursor
+*/
+typedef struct porter_tokenizer_cursor {
+ sqlite3_tokenizer_cursor base;
+ const char *zInput; /* input we are tokenizing */
+ int nInput; /* size of the input */
+ int iOffset; /* current position in zInput */
+ int iToken; /* index of next token to be returned */
+ char *zToken; /* storage for current token */
+ int nAllocated; /* space allocated to zToken buffer */
+} porter_tokenizer_cursor;
+
+
+/* Forward declaration */
+static const sqlite3_tokenizer_module porterTokenizerModule;
+
+
+/*
+** Create a new tokenizer instance.
+*/
+static int porterCreate(
+ int argc, const char * const *argv,
+ sqlite3_tokenizer **ppTokenizer
+){
+ porter_tokenizer *t;
+ t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t));
+ if( t==NULL ) return SQLITE_NOMEM;
+ memset(t, 0, sizeof(*t));
+ *ppTokenizer = &t->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destroy a tokenizer
+*/
+static int porterDestroy(sqlite3_tokenizer *pTokenizer){
+ sqlite3_free(pTokenizer);
+ return SQLITE_OK;
+}
+
+/*
+** Prepare to begin tokenizing a particular string. The input
+** string to be tokenized is zInput[0..nInput-1]. A cursor
+** used to incrementally tokenize this string is returned in
+** *ppCursor.
+*/
+static int porterOpen(
+ sqlite3_tokenizer *pTokenizer, /* The tokenizer */
+ const char *zInput, int nInput, /* String to be tokenized */
+ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
+){
+ porter_tokenizer_cursor *c;
+
+ c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c));
+ if( c==NULL ) return SQLITE_NOMEM;
+
+ c->zInput = zInput;
+ if( zInput==0 ){
+ c->nInput = 0;
+ }else if( nInput<0 ){
+ c->nInput = (int)strlen(zInput);
+ }else{
+ c->nInput = nInput;
+ }
+ c->iOffset = 0; /* start tokenizing at the beginning */
+ c->iToken = 0;
+ c->zToken = NULL; /* no space allocated, yet. */
+ c->nAllocated = 0;
+
+ *ppCursor = &c->base;
+ return SQLITE_OK;
+}
+
+/*
+** Close a tokenization cursor previously opened by a call to
+** porterOpen() above.
+*/
+static int porterClose(sqlite3_tokenizer_cursor *pCursor){
+ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor;
+ sqlite3_free(c->zToken);
+ sqlite3_free(c);
+ return SQLITE_OK;
+}
+/*
+** Vowel or consonant
+*/
+static const char cType[] = {
+ 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 2, 1
+};
+
+/*
+** isConsonant() and isVowel() determine if their first character in
+** the string they point to is a consonant or a vowel, according
+** to Porter ruls.
+**
+** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'.
+** 'Y' is a consonant unless it follows another consonant,
+** in which case it is a vowel.
+**
+** In these routine, the letters are in reverse order. So the 'y' rule
+** is that 'y' is a consonant unless it is followed by another
+** consonent.
+*/
+static int isVowel(const char*);
+static int isConsonant(const char *z){
+ int j;
+ char x = *z;
+ if( x==0 ) return 0;
+ assert( x>='a' && x<='z' );
+ j = cType[x-'a'];
+ if( j<2 ) return j;
+ return z[1]==0 || isVowel(z + 1);
+}
+static int isVowel(const char *z){
+ int j;
+ char x = *z;
+ if( x==0 ) return 0;
+ assert( x>='a' && x<='z' );
+ j = cType[x-'a'];
+ if( j<2 ) return 1-j;
+ return isConsonant(z + 1);
+}
+
+/*
+** Let any sequence of one or more vowels be represented by V and let
+** C be sequence of one or more consonants. Then every word can be
+** represented as:
+**
+** [C] (VC){m} [V]
+**
+** In prose: A word is an optional consonant followed by zero or
+** vowel-consonant pairs followed by an optional vowel. "m" is the
+** number of vowel consonant pairs. This routine computes the value
+** of m for the first i bytes of a word.
+**
+** Return true if the m-value for z is 1 or more. In other words,
+** return true if z contains at least one vowel that is followed
+** by a consonant.
+**
+** In this routine z[] is in reverse order. So we are really looking
+** for an instance of of a consonant followed by a vowel.
+*/
+static int m_gt_0(const char *z){
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ return *z!=0;
+}
+
+/* Like mgt0 above except we are looking for a value of m which is
+** exactly 1
+*/
+static int m_eq_1(const char *z){
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 1;
+ while( isConsonant(z) ){ z++; }
+ return *z==0;
+}
+
+/* Like mgt0 above except we are looking for a value of m>1 instead
+** or m>0
+*/
+static int m_gt_1(const char *z){
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ return *z!=0;
+}
+
+/*
+** Return TRUE if there is a vowel anywhere within z[0..n-1]
+*/
+static int hasVowel(const char *z){
+ while( isConsonant(z) ){ z++; }
+ return *z!=0;
+}
+
+/*
+** Return TRUE if the word ends in a double consonant.
+**
+** The text is reversed here. So we are really looking at
+** the first two characters of z[].
+*/
+static int doubleConsonant(const char *z){
+ return isConsonant(z) && z[0]==z[1] && isConsonant(z+1);
+}
+
+/*
+** Return TRUE if the word ends with three letters which
+** are consonant-vowel-consonent and where the final consonant
+** is not 'w', 'x', or 'y'.
+**
+** The word is reversed here. So we are really checking the
+** first three letters and the first one cannot be in [wxy].
+*/
+static int star_oh(const char *z){
+ return
+ z[0]!=0 && isConsonant(z) &&
+ z[0]!='w' && z[0]!='x' && z[0]!='y' &&
+ z[1]!=0 && isVowel(z+1) &&
+ z[2]!=0 && isConsonant(z+2);
+}
+
+/*
+** If the word ends with zFrom and xCond() is true for the stem
+** of the word that preceeds the zFrom ending, then change the
+** ending to zTo.
+**
+** The input word *pz and zFrom are both in reverse order. zTo
+** is in normal order.
+**
+** Return TRUE if zFrom matches. Return FALSE if zFrom does not
+** match. Not that TRUE is returned even if xCond() fails and
+** no substitution occurs.
+*/
+static int stem(
+ char **pz, /* The word being stemmed (Reversed) */
+ const char *zFrom, /* If the ending matches this... (Reversed) */
+ const char *zTo, /* ... change the ending to this (not reversed) */
+ int (*xCond)(const char*) /* Condition that must be true */
+){
+ char *z = *pz;
+ while( *zFrom && *zFrom==*z ){ z++; zFrom++; }
+ if( *zFrom!=0 ) return 0;
+ if( xCond && !xCond(z) ) return 1;
+ while( *zTo ){
+ *(--z) = *(zTo++);
+ }
+ *pz = z;
+ return 1;
+}
+
+/*
+** This is the fallback stemmer used when the porter stemmer is
+** inappropriate. The input word is copied into the output with
+** US-ASCII case folding. If the input word is too long (more
+** than 20 bytes if it contains no digits or more than 6 bytes if
+** it contains digits) then word is truncated to 20 or 6 bytes
+** by taking 10 or 3 bytes from the beginning and end.
+*/
+static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){
+ int i, mx, j;
+ int hasDigit = 0;
+ for(i=0; i<nIn; i++){
+ int c = zIn[i];
+ if( c>='A' && c<='Z' ){
+ zOut[i] = c - 'A' + 'a';
+ }else{
+ if( c>='0' && c<='9' ) hasDigit = 1;
+ zOut[i] = c;
+ }
+ }
+ mx = hasDigit ? 3 : 10;
+ if( nIn>mx*2 ){
+ for(j=mx, i=nIn-mx; i<nIn; i++, j++){
+ zOut[j] = zOut[i];
+ }
+ i = j;
+ }
+ zOut[i] = 0;
+ *pnOut = i;
+}
+
+
+/*
+** Stem the input word zIn[0..nIn-1]. Store the output in zOut.
+** zOut is at least big enough to hold nIn bytes. Write the actual
+** size of the output word (exclusive of the '\0' terminator) into *pnOut.
+**
+** Any upper-case characters in the US-ASCII character set ([A-Z])
+** are converted to lower case. Upper-case UTF characters are
+** unchanged.
+**
+** Words that are longer than about 20 bytes are stemmed by retaining
+** a few bytes from the beginning and the end of the word. If the
+** word contains digits, 3 bytes are taken from the beginning and
+** 3 bytes from the end. For long words without digits, 10 bytes
+** are taken from each end. US-ASCII case folding still applies.
+**
+** If the input word contains not digits but does characters not
+** in [a-zA-Z] then no stemming is attempted and this routine just
+** copies the input into the input into the output with US-ASCII
+** case folding.
+**
+** Stemming never increases the length of the word. So there is
+** no chance of overflowing the zOut buffer.
+*/
+static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){
+ int i, j, c;
+ char zReverse[28];
+ char *z, *z2;
+ if( nIn<3 || nIn>=sizeof(zReverse)-7 ){
+ /* The word is too big or too small for the porter stemmer.
+ ** Fallback to the copy stemmer */
+ copy_stemmer(zIn, nIn, zOut, pnOut);
+ return;
+ }
+ for(i=0, j=sizeof(zReverse)-6; i<nIn; i++, j--){
+ c = zIn[i];
+ if( c>='A' && c<='Z' ){
+ zReverse[j] = c + 'a' - 'A';
+ }else if( c>='a' && c<='z' ){
+ zReverse[j] = c;
+ }else{
+ /* The use of a character not in [a-zA-Z] means that we fallback
+ ** to the copy stemmer */
+ copy_stemmer(zIn, nIn, zOut, pnOut);
+ return;
+ }
+ }
+ memset(&zReverse[sizeof(zReverse)-5], 0, 5);
+ z = &zReverse[j+1];
+
+
+ /* Step 1a */
+ if( z[0]=='s' ){
+ if(
+ !stem(&z, "sess", "ss", 0) &&
+ !stem(&z, "sei", "i", 0) &&
+ !stem(&z, "ss", "ss", 0)
+ ){
+ z++;
+ }
+ }
+
+ /* Step 1b */
+ z2 = z;
+ if( stem(&z, "dee", "ee", m_gt_0) ){
+ /* Do nothing. The work was all in the test */
+ }else if(
+ (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel))
+ && z!=z2
+ ){
+ if( stem(&z, "ta", "ate", 0) ||
+ stem(&z, "lb", "ble", 0) ||
+ stem(&z, "zi", "ize", 0) ){
+ /* Do nothing. The work was all in the test */
+ }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){
+ z++;
+ }else if( m_eq_1(z) && star_oh(z) ){
+ *(--z) = 'e';
+ }
+ }
+
+ /* Step 1c */
+ if( z[0]=='y' && hasVowel(z+1) ){
+ z[0] = 'i';
+ }
+
+ /* Step 2 */
+ switch( z[1] ){
+ case 'a':
+ stem(&z, "lanoita", "ate", m_gt_0) ||
+ stem(&z, "lanoit", "tion", m_gt_0);
+ break;
+ case 'c':
+ stem(&z, "icne", "ence", m_gt_0) ||
+ stem(&z, "icna", "ance", m_gt_0);
+ break;
+ case 'e':
+ stem(&z, "rezi", "ize", m_gt_0);
+ break;
+ case 'g':
+ stem(&z, "igol", "log", m_gt_0);
+ break;
+ case 'l':
+ stem(&z, "ilb", "ble", m_gt_0) ||
+ stem(&z, "illa", "al", m_gt_0) ||
+ stem(&z, "iltne", "ent", m_gt_0) ||
+ stem(&z, "ile", "e", m_gt_0) ||
+ stem(&z, "ilsuo", "ous", m_gt_0);
+ break;
+ case 'o':
+ stem(&z, "noitazi", "ize", m_gt_0) ||
+ stem(&z, "noita", "ate", m_gt_0) ||
+ stem(&z, "rota", "ate", m_gt_0);
+ break;
+ case 's':
+ stem(&z, "msila", "al", m_gt_0) ||
+ stem(&z, "ssenevi", "ive", m_gt_0) ||
+ stem(&z, "ssenluf", "ful", m_gt_0) ||
+ stem(&z, "ssensuo", "ous", m_gt_0);
+ break;
+ case 't':
+ stem(&z, "itila", "al", m_gt_0) ||
+ stem(&z, "itivi", "ive", m_gt_0) ||
+ stem(&z, "itilib", "ble", m_gt_0);
+ break;
+ }
+
+ /* Step 3 */
+ switch( z[0] ){
+ case 'e':
+ stem(&z, "etaci", "ic", m_gt_0) ||
+ stem(&z, "evita", "", m_gt_0) ||
+ stem(&z, "ezila", "al", m_gt_0);
+ break;
+ case 'i':
+ stem(&z, "itici", "ic", m_gt_0);
+ break;
+ case 'l':
+ stem(&z, "laci", "ic", m_gt_0) ||
+ stem(&z, "luf", "", m_gt_0);
+ break;
+ case 's':
+ stem(&z, "ssen", "", m_gt_0);
+ break;
+ }
+
+ /* Step 4 */
+ switch( z[1] ){
+ case 'a':
+ if( z[0]=='l' && m_gt_1(z+2) ){
+ z += 2;
+ }
+ break;
+ case 'c':
+ if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){
+ z += 4;
+ }
+ break;
+ case 'e':
+ if( z[0]=='r' && m_gt_1(z+2) ){
+ z += 2;
+ }
+ break;
+ case 'i':
+ if( z[0]=='c' && m_gt_1(z+2) ){
+ z += 2;
+ }
+ break;
+ case 'l':
+ if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){
+ z += 4;
+ }
+ break;
+ case 'n':
+ if( z[0]=='t' ){
+ if( z[2]=='a' ){
+ if( m_gt_1(z+3) ){
+ z += 3;
+ }
+ }else if( z[2]=='e' ){
+ stem(&z, "tneme", "", m_gt_1) ||
+ stem(&z, "tnem", "", m_gt_1) ||
+ stem(&z, "tne", "", m_gt_1);
+ }
+ }
+ break;
+ case 'o':
+ if( z[0]=='u' ){
+ if( m_gt_1(z+2) ){
+ z += 2;
+ }
+ }else if( z[3]=='s' || z[3]=='t' ){
+ stem(&z, "noi", "", m_gt_1);
+ }
+ break;
+ case 's':
+ if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){
+ z += 3;
+ }
+ break;
+ case 't':
+ stem(&z, "eta", "", m_gt_1) ||
+ stem(&z, "iti", "", m_gt_1);
+ break;
+ case 'u':
+ if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){
+ z += 3;
+ }
+ break;
+ case 'v':
+ case 'z':
+ if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){
+ z += 3;
+ }
+ break;
+ }
+
+ /* Step 5a */
+ if( z[0]=='e' ){
+ if( m_gt_1(z+1) ){
+ z++;
+ }else if( m_eq_1(z+1) && !star_oh(z+1) ){
+ z++;
+ }
+ }
+
+ /* Step 5b */
+ if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){
+ z++;
+ }
+
+ /* z[] is now the stemmed word in reverse order. Flip it back
+ ** around into forward order and return.
+ */
+ *pnOut = i = strlen(z);
+ zOut[i] = 0;
+ while( *z ){
+ zOut[--i] = *(z++);
+ }
+}
+
+/*
+** Characters that can be part of a token. We assume any character
+** whose value is greater than 0x80 (any UTF character) can be
+** part of a token. In other words, delimiters all must have
+** values of 0x7f or lower.
+*/
+static const char porterIdChar[] = {
+/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */
+};
+#define isDelim(C) (((ch=C)&0x80)==0 && (ch<0x30 || !porterIdChar[ch-0x30]))
+
+/*
+** Extract the next token from a tokenization cursor. The cursor must
+** have been opened by a prior call to porterOpen().
+*/
+static int porterNext(
+ sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */
+ const char **pzToken, /* OUT: *pzToken is the token text */
+ int *pnBytes, /* OUT: Number of bytes in token */
+ int *piStartOffset, /* OUT: Starting offset of token */
+ int *piEndOffset, /* OUT: Ending offset of token */
+ int *piPosition /* OUT: Position integer of token */
+){
+ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor;
+ const char *z = c->zInput;
+
+ while( c->iOffset<c->nInput ){
+ int iStartOffset, ch;
+
+ /* Scan past delimiter characters */
+ while( c->iOffset<c->nInput && isDelim(z[c->iOffset]) ){
+ c->iOffset++;
+ }
+
+ /* Count non-delimiter characters. */
+ iStartOffset = c->iOffset;
+ while( c->iOffset<c->nInput && !isDelim(z[c->iOffset]) ){
+ c->iOffset++;
+ }
+
+ if( c->iOffset>iStartOffset ){
+ int n = c->iOffset-iStartOffset;
+ if( n>c->nAllocated ){
+ c->nAllocated = n+20;
+ c->zToken = sqlite3_realloc(c->zToken, c->nAllocated);
+ if( c->zToken==NULL ) return SQLITE_NOMEM;
+ }
+ porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes);
+ *pzToken = c->zToken;
+ *piStartOffset = iStartOffset;
+ *piEndOffset = c->iOffset;
+ *piPosition = c->iToken++;
+ return SQLITE_OK;
+ }
+ }
+ return SQLITE_DONE;
+}
+
+/*
+** The set of routines that implement the porter-stemmer tokenizer
+*/
+static const sqlite3_tokenizer_module porterTokenizerModule = {
+ 0,
+ porterCreate,
+ porterDestroy,
+ porterOpen,
+ porterClose,
+ porterNext,
+};
+
+/*
+** Allocate a new porter tokenizer. Return a pointer to the new
+** tokenizer in *ppModule
+*/
+SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule(
+ sqlite3_tokenizer_module const**ppModule
+){
+ *ppModule = &porterTokenizerModule;
+}
+
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
+
+/************** End of fts3_porter.c *****************************************/
+/************** Begin file fts3_tokenizer.c **********************************/
+/*
+** 2007 June 22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This is part of an SQLite module implementing full-text search.
+** This particular file implements the generic tokenizer interface.
+*/
+
+/*
+** The code in this file is only compiled if:
+**
+** * The FTS3 module is being built as an extension
+** (in which case SQLITE_CORE is not defined), or
+**
+** * The FTS3 module is being built into the core of
+** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
+
+#ifndef SQLITE_CORE
+ SQLITE_EXTENSION_INIT1
+#endif
+
+
+/*
+** Implementation of the SQL scalar function for accessing the underlying
+** hash table. This function may be called as follows:
+**
+** SELECT <function-name>(<key-name>);
+** SELECT <function-name>(<key-name>, <pointer>);
+**
+** where <function-name> is the name passed as the second argument
+** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer').
+**
+** If the <pointer> argument is specified, it must be a blob value
+** containing a pointer to be stored as the hash data corresponding
+** to the string <key-name>. If <pointer> is not specified, then
+** the string <key-name> must already exist in the has table. Otherwise,
+** an error is returned.
+**
+** Whether or not the <pointer> argument is specified, the value returned
+** is a blob containing the pointer stored as the hash data corresponding
+** to string <key-name> (after the hash-table is updated, if applicable).
+*/
+static void scalarFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ fts3Hash *pHash;
+ void *pPtr = 0;
+ const unsigned char *zName;
+ int nName;
+
+ assert( argc==1 || argc==2 );
+
+ pHash = (fts3Hash *)sqlite3_user_data(context);
+
+ zName = sqlite3_value_text(argv[0]);
+ nName = sqlite3_value_bytes(argv[0])+1;
+
+ if( argc==2 ){
+ void *pOld;
+ int n = sqlite3_value_bytes(argv[1]);
+ if( n!=sizeof(pPtr) ){
+ sqlite3_result_error(context, "argument type mismatch", -1);
+ return;
+ }
+ pPtr = *(void **)sqlite3_value_blob(argv[1]);
+ pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr);
+ if( pOld==pPtr ){
+ sqlite3_result_error(context, "out of memory", -1);
+ return;
+ }
+ }else{
+ pPtr = sqlite3Fts3HashFind(pHash, zName, nName);
+ if( !pPtr ){
+ char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName);
+ sqlite3_result_error(context, zErr, -1);
+ sqlite3_free(zErr);
+ return;
+ }
+ }
+
+ sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
+}
+
+#ifdef SQLITE_TEST
+
+
+/*
+** Implementation of a special SQL scalar function for testing tokenizers
+** designed to be used in concert with the Tcl testing framework. This
+** function must be called with two arguments:
+**
+** SELECT <function-name>(<key-name>, <input-string>);
+** SELECT <function-name>(<key-name>, <pointer>);
+**
+** where <function-name> is the name passed as the second argument
+** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer')
+** concatenated with the string '_test' (e.g. 'fts3_tokenizer_test').
+**
+** The return value is a string that may be interpreted as a Tcl
+** list. For each token in the <input-string>, three elements are
+** added to the returned list. The first is the token position, the
+** second is the token text (folded, stemmed, etc.) and the third is the
+** substring of <input-string> associated with the token. For example,
+** using the built-in "simple" tokenizer:
+**
+** SELECT fts_tokenizer_test('simple', 'I don't see how');
+**
+** will return the string:
+**
+** "{0 i I 1 dont don't 2 see see 3 how how}"
+**
+*/
+static void testFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ fts3Hash *pHash;
+ sqlite3_tokenizer_module *p;
+ sqlite3_tokenizer *pTokenizer = 0;
+ sqlite3_tokenizer_cursor *pCsr = 0;
+
+ const char *zErr = 0;
+
+ const char *zName;
+ int nName;
+ const char *zInput;
+ int nInput;
+
+ const char *zArg = 0;
+
+ const char *zToken;
+ int nToken;
+ int iStart;
+ int iEnd;
+ int iPos;
+
+ Tcl_Obj *pRet;
+
+ assert( argc==2 || argc==3 );
+
+ nName = sqlite3_value_bytes(argv[0]);
+ zName = (const char *)sqlite3_value_text(argv[0]);
+ nInput = sqlite3_value_bytes(argv[argc-1]);
+ zInput = (const char *)sqlite3_value_text(argv[argc-1]);
+
+ if( argc==3 ){
+ zArg = (const char *)sqlite3_value_text(argv[1]);
+ }
+
+ pHash = (fts3Hash *)sqlite3_user_data(context);
+ p = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zName, nName+1);
+
+ if( !p ){
+ char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName);
+ sqlite3_result_error(context, zErr, -1);
+ sqlite3_free(zErr);
+ return;
+ }
+
+ pRet = Tcl_NewObj();
+ Tcl_IncrRefCount(pRet);
+
+ if( SQLITE_OK!=p->xCreate(zArg ? 1 : 0, &zArg, &pTokenizer) ){
+ zErr = "error in xCreate()";
+ goto finish;
+ }
+ pTokenizer->pModule = p;
+ if( SQLITE_OK!=p->xOpen(pTokenizer, zInput, nInput, &pCsr) ){
+ zErr = "error in xOpen()";
+ goto finish;
+ }
+ pCsr->pTokenizer = pTokenizer;
+
+ while( SQLITE_OK==p->xNext(pCsr, &zToken, &nToken, &iStart, &iEnd, &iPos) ){
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(iPos));
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken));
+ zToken = &zInput[iStart];
+ nToken = iEnd-iStart;
+ Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken));
+ }
+
+ if( SQLITE_OK!=p->xClose(pCsr) ){
+ zErr = "error in xClose()";
+ goto finish;
+ }
+ if( SQLITE_OK!=p->xDestroy(pTokenizer) ){
+ zErr = "error in xDestroy()";
+ goto finish;
+ }
+
+finish:
+ if( zErr ){
+ sqlite3_result_error(context, zErr, -1);
+ }else{
+ sqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT);
+ }
+ Tcl_DecrRefCount(pRet);
+}
+
+static
+int registerTokenizer(
+ sqlite3 *db,
+ char *zName,
+ const sqlite3_tokenizer_module *p
+){
+ int rc;
+ sqlite3_stmt *pStmt;
+ const char zSql[] = "SELECT fts3_tokenizer(?, ?)";
+
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
+ sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC);
+ sqlite3_step(pStmt);
+
+ return sqlite3_finalize(pStmt);
+}
+
+static
+int queryTokenizer(
+ sqlite3 *db,
+ char *zName,
+ const sqlite3_tokenizer_module **pp
+){
+ int rc;
+ sqlite3_stmt *pStmt;
+ const char zSql[] = "SELECT fts3_tokenizer(?)";
+
+ *pp = 0;
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){
+ memcpy(pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp));
+ }
+ }
+
+ return sqlite3_finalize(pStmt);
+}
+
+SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule);
+
+/*
+** Implementation of the scalar function fts3_tokenizer_internal_test().
+** This function is used for testing only, it is not included in the
+** build unless SQLITE_TEST is defined.
+**
+** The purpose of this is to test that the fts3_tokenizer() function
+** can be used as designed by the C-code in the queryTokenizer and
+** registerTokenizer() functions above. These two functions are repeated
+** in the README.tokenizer file as an example, so it is important to
+** test them.
+**
+** To run the tests, evaluate the fts3_tokenizer_internal_test() scalar
+** function with no arguments. An assert() will fail if a problem is
+** detected. i.e.:
+**
+** SELECT fts3_tokenizer_internal_test();
+**
+*/
+static void intTestFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int rc;
+ const sqlite3_tokenizer_module *p1;
+ const sqlite3_tokenizer_module *p2;
+ sqlite3 *db = (sqlite3 *)sqlite3_user_data(context);
+
+ /* Test the query function */
+ sqlite3Fts3SimpleTokenizerModule(&p1);
+ rc = queryTokenizer(db, "simple", &p2);
+ assert( rc==SQLITE_OK );
+ assert( p1==p2 );
+ rc = queryTokenizer(db, "nosuchtokenizer", &p2);
+ assert( rc==SQLITE_ERROR );
+ assert( p2==0 );
+ assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") );
+
+ /* Test the storage function */
+ rc = registerTokenizer(db, "nosuchtokenizer", p1);
+ assert( rc==SQLITE_OK );
+ rc = queryTokenizer(db, "nosuchtokenizer", &p2);
+ assert( rc==SQLITE_OK );
+ assert( p2==p1 );
+
+ sqlite3_result_text(context, "ok", -1, SQLITE_STATIC);
+}
+
+#endif
+
+/*
+** Set up SQL objects in database db used to access the contents of
+** the hash table pointed to by argument pHash. The hash table must
+** been initialised to use string keys, and to take a private copy
+** of the key when a value is inserted. i.e. by a call similar to:
+**
+** sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1);
+**
+** This function adds a scalar function (see header comment above
+** scalarFunc() in this file for details) and, if ENABLE_TABLE is
+** defined at compilation time, a temporary virtual table (see header
+** comment above struct HashTableVtab) to the database schema. Both
+** provide read/write access to the contents of *pHash.
+**
+** The third argument to this function, zName, is used as the name
+** of both the scalar and, if created, the virtual table.
+*/
+SQLITE_PRIVATE int sqlite3Fts3InitHashTable(
+ sqlite3 *db,
+ fts3Hash *pHash,
+ const char *zName
+){
+ int rc = SQLITE_OK;
+ void *p = (void *)pHash;
+ const int any = SQLITE_ANY;
+ char *zTest = 0;
+ char *zTest2 = 0;
+
+#ifdef SQLITE_TEST
+ void *pdb = (void *)db;
+ zTest = sqlite3_mprintf("%s_test", zName);
+ zTest2 = sqlite3_mprintf("%s_internal_test", zName);
+ if( !zTest || !zTest2 ){
+ rc = SQLITE_NOMEM;
+ }
+#endif
+
+ if( rc!=SQLITE_OK
+ || (rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0))
+ || (rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0))
+#ifdef SQLITE_TEST
+ || (rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0))
+ || (rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0))
+ || (rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0))
+#endif
+ );
+
+ sqlite3_free(zTest);
+ sqlite3_free(zTest2);
+ return rc;
+}
+
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
+
+/************** End of fts3_tokenizer.c **************************************/
+/************** Begin file fts3_tokenizer1.c *********************************/
+/*
+** 2006 Oct 10
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** Implementation of the "simple" full-text-search tokenizer.
+*/
+
+/*
+** The code in this file is only compiled if:
+**
+** * The FTS3 module is being built as an extension
+** (in which case SQLITE_CORE is not defined), or
+**
+** * The FTS3 module is being built into the core of
+** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
+
+
+
+
+typedef struct simple_tokenizer {
+ sqlite3_tokenizer base;
+ char delim[128]; /* flag ASCII delimiters */
+} simple_tokenizer;
+
+typedef struct simple_tokenizer_cursor {
+ sqlite3_tokenizer_cursor base;
+ const char *pInput; /* input we are tokenizing */
+ int nBytes; /* size of the input */
+ int iOffset; /* current position in pInput */
+ int iToken; /* index of next token to be returned */
+ char *pToken; /* storage for current token */
+ int nTokenAllocated; /* space allocated to zToken buffer */
+} simple_tokenizer_cursor;
+
+
+/* Forward declaration */
+static const sqlite3_tokenizer_module simpleTokenizerModule;
+
+static int simpleDelim(simple_tokenizer *t, unsigned char c){
+ return c<0x80 && t->delim[c];
+}
+
+/*
+** Create a new tokenizer instance.
+*/
+static int simpleCreate(
+ int argc, const char * const *argv,
+ sqlite3_tokenizer **ppTokenizer
+){
+ simple_tokenizer *t;
+
+ t = (simple_tokenizer *) sqlite3_malloc(sizeof(*t));
+ if( t==NULL ) return SQLITE_NOMEM;
+ memset(t, 0, sizeof(*t));
+
+ /* TODO(shess) Delimiters need to remain the same from run to run,
+ ** else we need to reindex. One solution would be a meta-table to
+ ** track such information in the database, then we'd only want this
+ ** information on the initial create.
+ */
+ if( argc>1 ){
+ int i, n = strlen(argv[1]);
+ for(i=0; i<n; i++){
+ unsigned char ch = argv[1][i];
+ /* We explicitly don't support UTF-8 delimiters for now. */
+ if( ch>=0x80 ){
+ sqlite3_free(t);
+ return SQLITE_ERROR;
+ }
+ t->delim[ch] = 1;
+ }
+ } else {
+ /* Mark non-alphanumeric ASCII characters as delimiters */
+ int i;
+ for(i=1; i<0x80; i++){
+ t->delim[i] = !isalnum(i);
+ }
+ }
+
+ *ppTokenizer = &t->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destroy a tokenizer
+*/
+static int simpleDestroy(sqlite3_tokenizer *pTokenizer){
+ sqlite3_free(pTokenizer);
+ return SQLITE_OK;
+}
+
+/*
+** Prepare to begin tokenizing a particular string. The input
+** string to be tokenized is pInput[0..nBytes-1]. A cursor
+** used to incrementally tokenize this string is returned in
+** *ppCursor.
+*/
+static int simpleOpen(
+ sqlite3_tokenizer *pTokenizer, /* The tokenizer */
+ const char *pInput, int nBytes, /* String to be tokenized */
+ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
+){
+ simple_tokenizer_cursor *c;
+
+ c = (simple_tokenizer_cursor *) sqlite3_malloc(sizeof(*c));
+ if( c==NULL ) return SQLITE_NOMEM;
+
+ c->pInput = pInput;
+ if( pInput==0 ){
+ c->nBytes = 0;
+ }else if( nBytes<0 ){
+ c->nBytes = (int)strlen(pInput);
+ }else{
+ c->nBytes = nBytes;
+ }
+ c->iOffset = 0; /* start tokenizing at the beginning */
+ c->iToken = 0;
+ c->pToken = NULL; /* no space allocated, yet. */
+ c->nTokenAllocated = 0;
+
+ *ppCursor = &c->base;
+ return SQLITE_OK;
+}
+
+/*
+** Close a tokenization cursor previously opened by a call to
+** simpleOpen() above.
+*/
+static int simpleClose(sqlite3_tokenizer_cursor *pCursor){
+ simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor;
+ sqlite3_free(c->pToken);
+ sqlite3_free(c);
+ return SQLITE_OK;
+}
+
+/*
+** Extract the next token from a tokenization cursor. The cursor must
+** have been opened by a prior call to simpleOpen().
+*/
+static int simpleNext(
+ sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */
+ const char **ppToken, /* OUT: *ppToken is the token text */
+ int *pnBytes, /* OUT: Number of bytes in token */
+ int *piStartOffset, /* OUT: Starting offset of token */
+ int *piEndOffset, /* OUT: Ending offset of token */
+ int *piPosition /* OUT: Position integer of token */
+){
+ simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor;
+ simple_tokenizer *t = (simple_tokenizer *) pCursor->pTokenizer;
+ unsigned char *p = (unsigned char *)c->pInput;
+
+ while( c->iOffset<c->nBytes ){
+ int iStartOffset;
+
+ /* Scan past delimiter characters */
+ while( c->iOffset<c->nBytes && simpleDelim(t, p[c->iOffset]) ){
+ c->iOffset++;
+ }
+
+ /* Count non-delimiter characters. */
+ iStartOffset = c->iOffset;
+ while( c->iOffset<c->nBytes && !simpleDelim(t, p[c->iOffset]) ){
+ c->iOffset++;
+ }
+
+ if( c->iOffset>iStartOffset ){
+ int i, n = c->iOffset-iStartOffset;
+ if( n>c->nTokenAllocated ){
+ c->nTokenAllocated = n+20;
+ c->pToken = sqlite3_realloc(c->pToken, c->nTokenAllocated);
+ if( c->pToken==NULL ) return SQLITE_NOMEM;
+ }
+ for(i=0; i<n; i++){
+ /* TODO(shess) This needs expansion to handle UTF-8
+ ** case-insensitivity.
+ */
+ unsigned char ch = p[iStartOffset+i];
+ c->pToken[i] = ch<0x80 ? tolower(ch) : ch;
+ }
+ *ppToken = c->pToken;
+ *pnBytes = n;
+ *piStartOffset = iStartOffset;
+ *piEndOffset = c->iOffset;
+ *piPosition = c->iToken++;
+
+ return SQLITE_OK;
+ }
+ }
+ return SQLITE_DONE;
+}
+
+/*
+** The set of routines that implement the simple tokenizer
+*/
+static const sqlite3_tokenizer_module simpleTokenizerModule = {
+ 0,
+ simpleCreate,
+ simpleDestroy,
+ simpleOpen,
+ simpleClose,
+ simpleNext,
+};
+
+/*
+** Allocate a new simple tokenizer. Return a pointer to the new
+** tokenizer in *ppModule
+*/
+SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(
+ sqlite3_tokenizer_module const**ppModule
+){
+ *ppModule = &simpleTokenizerModule;
+}
+
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
+
+/************** End of fts3_tokenizer1.c *************************************/
diff --git a/src/libs/sqlite3/sqlite3.h b/src/libs/sqlite3/sqlite3.h
new file mode 100644
index 00000000..57837cbb
--- /dev/null
+++ b/src/libs/sqlite3/sqlite3.h
@@ -0,0 +1,5638 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This header file defines the interface that the SQLite library
+** presents to client programs. If a C-function, structure, datatype,
+** or constant definition does not appear in this file, then it is
+** not a published API of SQLite, is subject to change without
+** notice, and should not be referenced by programs that use SQLite.
+**
+** Some of the definitions that are in this file are marked as
+** "experimental". Experimental interfaces are normally new
+** features recently added to SQLite. We do not anticipate changes
+** to experimental interfaces but reserve to make minor changes if
+** experience from use "in the wild" suggest such changes are prudent.
+**
+** The official C-language API documentation for SQLite is derived
+** from comments in this file. This file is the authoritative source
+** on how SQLite interfaces are suppose to operate.
+**
+** The name of this file under configuration management is "sqlite.h.in".
+** The makefile makes some minor changes to this file (such as inserting
+** the version number) and changes its name to "sqlite3.h" as
+** part of the build process.
+**
+** @(#) $Id: sqlite.h.in,v 1.312 2008/05/12 12:39:56 drh Exp $
+*/
+#ifndef _SQLITE3_H_
+#define _SQLITE3_H_
+#include <stdarg.h> /* Needed for the definition of va_list */
+
+/*
+** Make sure we can call this stuff from C++.
+*/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*
+** Add the ability to override 'extern'
+*/
+#ifndef SQLITE_EXTERN
+# define SQLITE_EXTERN extern
+#endif
+
+/*
+** Make sure these symbols where not defined by some previous header
+** file.
+*/
+#ifdef SQLITE_VERSION
+# undef SQLITE_VERSION
+#endif
+#ifdef SQLITE_VERSION_NUMBER
+# undef SQLITE_VERSION_NUMBER
+#endif
+
+/*
+** CAPI3REF: Compile-Time Library Version Numbers {F10010}
+**
+** The SQLITE_VERSION and SQLITE_VERSION_NUMBER #defines in
+** the sqlite3.h file specify the version of SQLite with which
+** that header file is associated.
+**
+** The "version" of SQLite is a string of the form "X.Y.Z".
+** The phrase "alpha" or "beta" might be appended after the Z.
+** The X value is major version number always 3 in SQLite3.
+** The X value only changes when backwards compatibility is
+** broken and we intend to never break
+** backwards compatibility. The Y value is the minor version
+** number and only changes when
+** there are major feature enhancements that are forwards compatible
+** but not backwards compatible. The Z value is release number
+** and is incremented with
+** each release but resets back to 0 when Y is incremented.
+**
+** See also: [sqlite3_libversion()] and [sqlite3_libversion_number()].
+**
+** INVARIANTS:
+**
+** {F10011} The SQLITE_VERSION #define in the sqlite3.h header file
+** evaluates to a string literal that is the SQLite version
+** with which the header file is associated.
+**
+** {F10014} The SQLITE_VERSION_NUMBER #define resolves to an integer
+** with the value (X*1000000 + Y*1000 + Z) where X, Y, and
+** Z are the major version, minor version, and release number.
+*/
+#define SQLITE_VERSION "3.5.9"
+#define SQLITE_VERSION_NUMBER 3005009
+
+/*
+** CAPI3REF: Run-Time Library Version Numbers {F10020}
+** KEYWORDS: sqlite3_version
+**
+** These features provide the same information as the [SQLITE_VERSION]
+** and [SQLITE_VERSION_NUMBER] #defines in the header, but are associated
+** with the library instead of the header file. Cautious programmers might
+** include a check in their application to verify that
+** sqlite3_libversion_number() always returns the value
+** [SQLITE_VERSION_NUMBER].
+**
+** The sqlite3_libversion() function returns the same information as is
+** in the sqlite3_version[] string constant. The function is provided
+** for use in DLLs since DLL users usually do not have direct access to string
+** constants within the DLL.
+**
+** INVARIANTS:
+**
+** {F10021} The [sqlite3_libversion_number()] interface returns an integer
+** equal to [SQLITE_VERSION_NUMBER].
+**
+** {F10022} The [sqlite3_version] string constant contains the text of the
+** [SQLITE_VERSION] string.
+**
+** {F10023} The [sqlite3_libversion()] function returns
+** a pointer to the [sqlite3_version] string constant.
+*/
+SQLITE_EXTERN const char sqlite3_version[];
+const char *sqlite3_libversion(void);
+int sqlite3_libversion_number(void);
+
+/*
+** CAPI3REF: Test To See If The Library Is Threadsafe {F10100}
+**
+** SQLite can be compiled with or without mutexes. When
+** the SQLITE_THREADSAFE C preprocessor macro is true, mutexes
+** are enabled and SQLite is threadsafe. When that macro is false,
+** the mutexes are omitted. Without the mutexes, it is not safe
+** to use SQLite from more than one thread.
+**
+** There is a measurable performance penalty for enabling mutexes.
+** So if speed is of utmost importance, it makes sense to disable
+** the mutexes. But for maximum safety, mutexes should be enabled.
+** The default behavior is for mutexes to be enabled.
+**
+** This interface can be used by a program to make sure that the
+** version of SQLite that it is linking against was compiled with
+** the desired setting of the SQLITE_THREADSAFE macro.
+**
+** INVARIANTS:
+**
+** {F10101} The [sqlite3_threadsafe()] function returns nonzero if
+** SQLite was compiled with its mutexes enabled or zero
+** if SQLite was compiled with mutexes disabled.
+*/
+int sqlite3_threadsafe(void);
+
+/*
+** CAPI3REF: Database Connection Handle {F12000}
+** KEYWORDS: {database connection} {database connections}
+**
+** Each open SQLite database is represented by pointer to an instance of the
+** opaque structure named "sqlite3". It is useful to think of an sqlite3
+** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and
+** [sqlite3_open_v2()] interfaces are its constructors
+** and [sqlite3_close()] is its destructor. There are many other interfaces
+** (such as [sqlite3_prepare_v2()], [sqlite3_create_function()], and
+** [sqlite3_busy_timeout()] to name but three) that are methods on this
+** object.
+*/
+typedef struct sqlite3 sqlite3;
+
+
+/*
+** CAPI3REF: 64-Bit Integer Types {F10200}
+** KEYWORDS: sqlite_int64 sqlite_uint64
+**
+** Because there is no cross-platform way to specify 64-bit integer types
+** SQLite includes typedefs for 64-bit signed and unsigned integers.
+**
+** The sqlite3_int64 and sqlite3_uint64 are the preferred type
+** definitions. The sqlite_int64 and sqlite_uint64 types are
+** supported for backwards compatibility only.
+**
+** INVARIANTS:
+**
+** {F10201} The [sqlite_int64] and [sqlite3_int64] types specify a
+** 64-bit signed integer.
+**
+** {F10202} The [sqlite_uint64] and [sqlite3_uint64] types specify
+** a 64-bit unsigned integer.
+*/
+#ifdef SQLITE_INT64_TYPE
+ typedef SQLITE_INT64_TYPE sqlite_int64;
+ typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
+#elif defined(_MSC_VER)
+ typedef __int64 sqlite_int64;
+ typedef unsigned __int64 sqlite_uint64;
+#else
+ typedef long long int sqlite_int64;
+ typedef unsigned long long int sqlite_uint64;
+#endif
+typedef sqlite_int64 sqlite3_int64;
+typedef sqlite_uint64 sqlite3_uint64;
+
+/*
+** If compiling for a processor that lacks floating point support,
+** substitute integer for floating-point
+*/
+#ifdef SQLITE_OMIT_FLOATING_POINT
+# define double sqlite3_int64
+#endif
+
+/*
+** CAPI3REF: Closing A Database Connection {F12010}
+**
+** This routine is the destructor for the [sqlite3] object.
+**
+** Applications should [sqlite3_finalize | finalize] all
+** [prepared statements] and
+** [sqlite3_blob_close | close] all [sqlite3_blob | BLOBs]
+** associated with the [sqlite3] object prior
+** to attempting to close the [sqlite3] object.
+**
+** <todo>What happens to pending transactions? Are they
+** rolled back, or abandoned?</todo>
+**
+** INVARIANTS:
+**
+** {F12011} The [sqlite3_close()] interface destroys an [sqlite3] object
+** allocated by a prior call to [sqlite3_open()],
+** [sqlite3_open16()], or [sqlite3_open_v2()].
+**
+** {F12012} The [sqlite3_close()] function releases all memory used by the
+** connection and closes all open files.
+**
+** {F12013} If the database connection contains
+** [prepared statements] that have not been
+** finalized by [sqlite3_finalize()], then [sqlite3_close()]
+** returns [SQLITE_BUSY] and leaves the connection open.
+**
+** {F12014} Giving sqlite3_close() a NULL pointer is a harmless no-op.
+**
+** LIMITATIONS:
+**
+** {U12015} The parameter to [sqlite3_close()] must be an [sqlite3] object
+** pointer previously obtained from [sqlite3_open()] or the
+** equivalent, or NULL.
+**
+** {U12016} The parameter to [sqlite3_close()] must not have been previously
+** closed.
+*/
+int sqlite3_close(sqlite3 *);
+
+/*
+** The type for a callback function.
+** This is legacy and deprecated. It is included for historical
+** compatibility and is not documented.
+*/
+typedef int (*sqlite3_callback)(void*,int,char**, char**);
+
+/*
+** CAPI3REF: One-Step Query Execution Interface {F12100}
+**
+** The sqlite3_exec() interface is a convenient way of running
+** one or more SQL statements without a lot of C code. The
+** SQL statements are passed in as the second parameter to
+** sqlite3_exec(). The statements are evaluated one by one
+** until either an error or an interrupt is encountered or
+** until they are all done. The 3rd parameter is an optional
+** callback that is invoked once for each row of any query results
+** produced by the SQL statements. The 5th parameter tells where
+** to write any error messages.
+**
+** The sqlite3_exec() interface is implemented in terms of
+** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()].
+** The sqlite3_exec() routine does nothing that cannot be done
+** by [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()].
+** The sqlite3_exec() is just a convenient wrapper.
+**
+** INVARIANTS:
+**
+** {F12101} The [sqlite3_exec()] interface evaluates zero or more UTF-8
+** encoded, semicolon-separated, SQL statements in the
+** zero-terminated string of its 2nd parameter within the
+** context of the [sqlite3] object given in the 1st parameter.
+**
+** {F12104} The return value of [sqlite3_exec()] is SQLITE_OK if all
+** SQL statements run successfully.
+**
+** {F12105} The return value of [sqlite3_exec()] is an appropriate
+** non-zero error code if any SQL statement fails.
+**
+** {F12107} If one or more of the SQL statements handed to [sqlite3_exec()]
+** return results and the 3rd parameter is not NULL, then
+** the callback function specified by the 3rd parameter is
+** invoked once for each row of result.
+**
+** {F12110} If the callback returns a non-zero value then [sqlite3_exec()]
+** will aborted the SQL statement it is currently evaluating,
+** skip all subsequent SQL statements, and return [SQLITE_ABORT].
+** <todo>What happens to *errmsg here? Does the result code for
+** sqlite3_errcode() get set?</todo>
+**
+** {F12113} The [sqlite3_exec()] routine will pass its 4th parameter through
+** as the 1st parameter of the callback.
+**
+** {F12116} The [sqlite3_exec()] routine sets the 2nd parameter of its
+** callback to be the number of columns in the current row of
+** result.
+**
+** {F12119} The [sqlite3_exec()] routine sets the 3rd parameter of its
+** callback to be an array of pointers to strings holding the
+** values for each column in the current result set row as
+** obtained from [sqlite3_column_text()].
+**
+** {F12122} The [sqlite3_exec()] routine sets the 4th parameter of its
+** callback to be an array of pointers to strings holding the
+** names of result columns as obtained from [sqlite3_column_name()].
+**
+** {F12125} If the 3rd parameter to [sqlite3_exec()] is NULL then
+** [sqlite3_exec()] never invokes a callback. All query
+** results are silently discarded.
+**
+** {F12128} If an error occurs while parsing or evaluating any of the SQL
+** statements handed to [sqlite3_exec()] then [sqlite3_exec()] will
+** return an [error code] other than [SQLITE_OK].
+**
+** {F12131} If an error occurs while parsing or evaluating any of the SQL
+** handed to [sqlite3_exec()] and if the 5th parameter (errmsg)
+** to [sqlite3_exec()] is not NULL, then an error message is
+** allocated using the equivalent of [sqlite3_mprintf()] and
+** *errmsg is made to point to that message.
+**
+** {F12134} The [sqlite3_exec()] routine does not change the value of
+** *errmsg if errmsg is NULL or if there are no errors.
+**
+** {F12137} The [sqlite3_exec()] function sets the error code and message
+** accessible via [sqlite3_errcode()], [sqlite3_errmsg()], and
+** [sqlite3_errmsg16()].
+**
+** LIMITATIONS:
+**
+** {U12141} The first parameter to [sqlite3_exec()] must be an valid and open
+** [database connection].
+**
+** {U12142} The database connection must not be closed while
+** [sqlite3_exec()] is running.
+**
+** {U12143} The calling function is should use [sqlite3_free()] to free
+** the memory that *errmsg is left pointing at once the error
+** message is no longer needed.
+**
+** {U12145} The SQL statement text in the 2nd parameter to [sqlite3_exec()]
+** must remain unchanged while [sqlite3_exec()] is running.
+*/
+int sqlite3_exec(
+ sqlite3*, /* An open database */
+ const char *sql, /* SQL to be evaluted */
+ int (*callback)(void*,int,char**,char**), /* Callback function */
+ void *, /* 1st argument to callback */
+ char **errmsg /* Error msg written here */
+);
+
+/*
+** CAPI3REF: Result Codes {F10210}
+** KEYWORDS: SQLITE_OK {error code} {error codes}
+**
+** Many SQLite functions return an integer result code from the set shown
+** here in order to indicates success or failure.
+**
+** See also: [SQLITE_IOERR_READ | extended result codes]
+*/
+#define SQLITE_OK 0 /* Successful result */
+/* beginning-of-error-codes */
+#define SQLITE_ERROR 1 /* SQL error or missing database */
+#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */
+#define SQLITE_PERM 3 /* Access permission denied */
+#define SQLITE_ABORT 4 /* Callback routine requested an abort */
+#define SQLITE_BUSY 5 /* The database file is locked */
+#define SQLITE_LOCKED 6 /* A table in the database is locked */
+#define SQLITE_NOMEM 7 /* A malloc() failed */
+#define SQLITE_READONLY 8 /* Attempt to write a readonly database */
+#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/
+#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */
+#define SQLITE_CORRUPT 11 /* The database disk image is malformed */
+#define SQLITE_NOTFOUND 12 /* NOT USED. Table or record not found */
+#define SQLITE_FULL 13 /* Insertion failed because database is full */
+#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
+#define SQLITE_PROTOCOL 15 /* NOT USED. Database lock protocol error */
+#define SQLITE_EMPTY 16 /* Database is empty */
+#define SQLITE_SCHEMA 17 /* The database schema changed */
+#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
+#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
+#define SQLITE_MISMATCH 20 /* Data type mismatch */
+#define SQLITE_MISUSE 21 /* Library used incorrectly */
+#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */
+#define SQLITE_AUTH 23 /* Authorization denied */
+#define SQLITE_FORMAT 24 /* Auxiliary database format error */
+#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */
+#define SQLITE_NOTADB 26 /* File opened that is not a database file */
+#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
+#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
+/* end-of-error-codes */
+
+/*
+** CAPI3REF: Extended Result Codes {F10220}
+** KEYWORDS: {extended error code} {extended error codes}
+** KEYWORDS: {extended result codes}
+**
+** In its default configuration, SQLite API routines return one of 26 integer
+** [SQLITE_OK | result codes]. However, experience has shown that
+** many of these result codes are too course-grained. They do not provide as
+** much information about problems as programmers might like. In an effort to
+** address this, newer versions of SQLite (version 3.3.8 and later) include
+** support for additional result codes that provide more detailed information
+** about errors. The extended result codes are enabled or disabled
+** for each database connection using the [sqlite3_extended_result_codes()]
+** API.
+**
+** Some of the available extended result codes are listed here.
+** One may expect the number of extended result codes will be expand
+** over time. Software that uses extended result codes should expect
+** to see new result codes in future releases of SQLite.
+**
+** The SQLITE_OK result code will never be extended. It will always
+** be exactly zero.
+**
+** INVARIANTS:
+**
+** {F10223} The symbolic name for an extended result code always contains
+** a related primary result code as a prefix.
+**
+** {F10224} Primary result code names contain a single "_" character.
+**
+** {F10225} Extended result code names contain two or more "_" characters.
+**
+** {F10226} The numeric value of an extended result code contains the
+** numeric value of its corresponding primary result code in
+** its least significant 8 bits.
+*/
+#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
+#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
+#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
+#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8))
+#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8))
+#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8))
+#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8))
+#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8))
+#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8))
+#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8))
+#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8))
+#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8))
+
+/*
+** CAPI3REF: Flags For File Open Operations {F10230}
+**
+** These bit values are intended for use in the
+** 3rd parameter to the [sqlite3_open_v2()] interface and
+** in the 4th parameter to the xOpen method of the
+** [sqlite3_vfs] object.
+*/
+#define SQLITE_OPEN_READONLY 0x00000001
+#define SQLITE_OPEN_READWRITE 0x00000002
+#define SQLITE_OPEN_CREATE 0x00000004
+#define SQLITE_OPEN_DELETEONCLOSE 0x00000008
+#define SQLITE_OPEN_EXCLUSIVE 0x00000010
+#define SQLITE_OPEN_MAIN_DB 0x00000100
+#define SQLITE_OPEN_TEMP_DB 0x00000200
+#define SQLITE_OPEN_TRANSIENT_DB 0x00000400
+#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800
+#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000
+#define SQLITE_OPEN_SUBJOURNAL 0x00002000
+#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000
+
+/*
+** CAPI3REF: Device Characteristics {F10240}
+**
+** The xDeviceCapabilities method of the [sqlite3_io_methods]
+** object returns an integer which is a vector of the these
+** bit values expressing I/O characteristics of the mass storage
+** device that holds the file that the [sqlite3_io_methods]
+** refers to.
+**
+** The SQLITE_IOCAP_ATOMIC property means that all writes of
+** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
+** mean that writes of blocks that are nnn bytes in size and
+** are aligned to an address which is an integer multiple of
+** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means
+** that when data is appended to a file, the data is appended
+** first then the size of the file is extended, never the other
+** way around. The SQLITE_IOCAP_SEQUENTIAL property means that
+** information is written to disk in the same order as calls
+** to xWrite().
+*/
+#define SQLITE_IOCAP_ATOMIC 0x00000001
+#define SQLITE_IOCAP_ATOMIC512 0x00000002
+#define SQLITE_IOCAP_ATOMIC1K 0x00000004
+#define SQLITE_IOCAP_ATOMIC2K 0x00000008
+#define SQLITE_IOCAP_ATOMIC4K 0x00000010
+#define SQLITE_IOCAP_ATOMIC8K 0x00000020
+#define SQLITE_IOCAP_ATOMIC16K 0x00000040
+#define SQLITE_IOCAP_ATOMIC32K 0x00000080
+#define SQLITE_IOCAP_ATOMIC64K 0x00000100
+#define SQLITE_IOCAP_SAFE_APPEND 0x00000200
+#define SQLITE_IOCAP_SEQUENTIAL 0x00000400
+
+/*
+** CAPI3REF: File Locking Levels {F10250}
+**
+** SQLite uses one of these integer values as the second
+** argument to calls it makes to the xLock() and xUnlock() methods
+** of an [sqlite3_io_methods] object.
+*/
+#define SQLITE_LOCK_NONE 0
+#define SQLITE_LOCK_SHARED 1
+#define SQLITE_LOCK_RESERVED 2
+#define SQLITE_LOCK_PENDING 3
+#define SQLITE_LOCK_EXCLUSIVE 4
+
+/*
+** CAPI3REF: Synchronization Type Flags {F10260}
+**
+** When SQLite invokes the xSync() method of an
+** [sqlite3_io_methods] object it uses a combination of
+** these integer values as the second argument.
+**
+** When the SQLITE_SYNC_DATAONLY flag is used, it means that the
+** sync operation only needs to flush data to mass storage. Inode
+** information need not be flushed. The SQLITE_SYNC_NORMAL flag means
+** to use normal fsync() semantics. The SQLITE_SYNC_FULL flag means
+** to use Mac OS-X style fullsync instead of fsync().
+*/
+#define SQLITE_SYNC_NORMAL 0x00002
+#define SQLITE_SYNC_FULL 0x00003
+#define SQLITE_SYNC_DATAONLY 0x00010
+
+
+/*
+** CAPI3REF: OS Interface Open File Handle {F11110}
+**
+** An [sqlite3_file] object represents an open file in the OS
+** interface layer. Individual OS interface implementations will
+** want to subclass this object by appending additional fields
+** for their own use. The pMethods entry is a pointer to an
+** [sqlite3_io_methods] object that defines methods for performing
+** I/O operations on the open file.
+*/
+typedef struct sqlite3_file sqlite3_file;
+struct sqlite3_file {
+ const struct sqlite3_io_methods *pMethods; /* Methods for an open file */
+};
+
+/*
+** CAPI3REF: OS Interface File Virtual Methods Object {F11120}
+**
+** Every file opened by the [sqlite3_vfs] xOpen method contains a pointer to
+** an instance of this object. This object defines the
+** methods used to perform various operations against the open file.
+**
+** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or
+** [SQLITE_SYNC_FULL]. The first choice is the normal fsync().
+* The second choice is an
+** OS-X style fullsync. The SQLITE_SYNC_DATA flag may be ORed in to
+** indicate that only the data of the file and not its inode needs to be
+** synced.
+**
+** The integer values to xLock() and xUnlock() are one of
+** <ul>
+** <li> [SQLITE_LOCK_NONE],
+** <li> [SQLITE_LOCK_SHARED],
+** <li> [SQLITE_LOCK_RESERVED],
+** <li> [SQLITE_LOCK_PENDING], or
+** <li> [SQLITE_LOCK_EXCLUSIVE].
+** </ul>
+** xLock() increases the lock. xUnlock() decreases the lock.
+** The xCheckReservedLock() method looks
+** to see if any database connection, either in this
+** process or in some other process, is holding an RESERVED,
+** PENDING, or EXCLUSIVE lock on the file. It returns true
+** if such a lock exists and false if not.
+**
+** The xFileControl() method is a generic interface that allows custom
+** VFS implementations to directly control an open file using the
+** [sqlite3_file_control()] interface. The second "op" argument
+** is an integer opcode. The third
+** argument is a generic pointer which is intended to be a pointer
+** to a structure that may contain arguments or space in which to
+** write return values. Potential uses for xFileControl() might be
+** functions to enable blocking locks with timeouts, to change the
+** locking strategy (for example to use dot-file locks), to inquire
+** about the status of a lock, or to break stale locks. The SQLite
+** core reserves opcodes less than 100 for its own use.
+** A [SQLITE_FCNTL_LOCKSTATE | list of opcodes] less than 100 is available.
+** Applications that define a custom xFileControl method should use opcodes
+** greater than 100 to avoid conflicts.
+**
+** The xSectorSize() method returns the sector size of the
+** device that underlies the file. The sector size is the
+** minimum write that can be performed without disturbing
+** other bytes in the file. The xDeviceCharacteristics()
+** method returns a bit vector describing behaviors of the
+** underlying device:
+**
+** <ul>
+** <li> [SQLITE_IOCAP_ATOMIC]
+** <li> [SQLITE_IOCAP_ATOMIC512]
+** <li> [SQLITE_IOCAP_ATOMIC1K]
+** <li> [SQLITE_IOCAP_ATOMIC2K]
+** <li> [SQLITE_IOCAP_ATOMIC4K]
+** <li> [SQLITE_IOCAP_ATOMIC8K]
+** <li> [SQLITE_IOCAP_ATOMIC16K]
+** <li> [SQLITE_IOCAP_ATOMIC32K]
+** <li> [SQLITE_IOCAP_ATOMIC64K]
+** <li> [SQLITE_IOCAP_SAFE_APPEND]
+** <li> [SQLITE_IOCAP_SEQUENTIAL]
+** </ul>
+**
+** The SQLITE_IOCAP_ATOMIC property means that all writes of
+** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
+** mean that writes of blocks that are nnn bytes in size and
+** are aligned to an address which is an integer multiple of
+** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means
+** that when data is appended to a file, the data is appended
+** first then the size of the file is extended, never the other
+** way around. The SQLITE_IOCAP_SEQUENTIAL property means that
+** information is written to disk in the same order as calls
+** to xWrite().
+*/
+typedef struct sqlite3_io_methods sqlite3_io_methods;
+struct sqlite3_io_methods {
+ int iVersion;
+ int (*xClose)(sqlite3_file*);
+ int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+ int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
+ int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
+ int (*xSync)(sqlite3_file*, int flags);
+ int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
+ int (*xLock)(sqlite3_file*, int);
+ int (*xUnlock)(sqlite3_file*, int);
+ int (*xCheckReservedLock)(sqlite3_file*);
+ int (*xFileControl)(sqlite3_file*, int op, void *pArg);
+ int (*xSectorSize)(sqlite3_file*);
+ int (*xDeviceCharacteristics)(sqlite3_file*);
+ /* Additional methods may be added in future releases */
+};
+
+/*
+** CAPI3REF: Standard File Control Opcodes {F11310}
+**
+** These integer constants are opcodes for the xFileControl method
+** of the [sqlite3_io_methods] object and to the [sqlite3_file_control()]
+** interface.
+**
+** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This
+** opcode causes the xFileControl method to write the current state of
+** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED],
+** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE])
+** into an integer that the pArg argument points to. This capability
+** is used during testing and only needs to be supported when SQLITE_TEST
+** is defined.
+*/
+#define SQLITE_FCNTL_LOCKSTATE 1
+
+/*
+** CAPI3REF: Mutex Handle {F17110}
+**
+** The mutex module within SQLite defines [sqlite3_mutex] to be an
+** abstract type for a mutex object. The SQLite core never looks
+** at the internal representation of an [sqlite3_mutex]. It only
+** deals with pointers to the [sqlite3_mutex] object.
+**
+** Mutexes are created using [sqlite3_mutex_alloc()].
+*/
+typedef struct sqlite3_mutex sqlite3_mutex;
+
+/*
+** CAPI3REF: OS Interface Object {F11140}
+**
+** An instance of this object defines the interface between the
+** SQLite core and the underlying operating system. The "vfs"
+** in the name of the object stands for "virtual file system".
+**
+** The iVersion field is initially 1 but may be larger for future
+** versions of SQLite. Additional fields may be appended to this
+** object when the iVersion value is increased.
+**
+** The szOsFile field is the size of the subclassed [sqlite3_file]
+** structure used by this VFS. mxPathname is the maximum length of
+** a pathname in this VFS.
+**
+** Registered sqlite3_vfs objects are kept on a linked list formed by
+** the pNext pointer. The [sqlite3_vfs_register()]
+** and [sqlite3_vfs_unregister()] interfaces manage this list
+** in a thread-safe way. The [sqlite3_vfs_find()] interface
+** searches the list.
+**
+** The pNext field is the only field in the sqlite3_vfs
+** structure that SQLite will ever modify. SQLite will only access
+** or modify this field while holding a particular static mutex.
+** The application should never modify anything within the sqlite3_vfs
+** object once the object has been registered.
+**
+** The zName field holds the name of the VFS module. The name must
+** be unique across all VFS modules.
+**
+** {F11141} SQLite will guarantee that the zFilename string passed to
+** xOpen() is a full pathname as generated by xFullPathname() and
+** that the string will be valid and unchanged until xClose() is
+** called. {END} So the [sqlite3_file] can store a pointer to the
+** filename if it needs to remember the filename for some reason.
+**
+** {F11142} The flags argument to xOpen() includes all bits set in
+** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()]
+** or [sqlite3_open16()] is used, then flags includes at least
+** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. {END}
+** If xOpen() opens a file read-only then it sets *pOutFlags to
+** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be
+** set.
+**
+** {F11143} SQLite will also add one of the following flags to the xOpen()
+** call, depending on the object being opened:
+**
+** <ul>
+** <li> [SQLITE_OPEN_MAIN_DB]
+** <li> [SQLITE_OPEN_MAIN_JOURNAL]
+** <li> [SQLITE_OPEN_TEMP_DB]
+** <li> [SQLITE_OPEN_TEMP_JOURNAL]
+** <li> [SQLITE_OPEN_TRANSIENT_DB]
+** <li> [SQLITE_OPEN_SUBJOURNAL]
+** <li> [SQLITE_OPEN_MASTER_JOURNAL]
+** </ul> {END}
+**
+** The file I/O implementation can use the object type flags to
+** changes the way it deals with files. For example, an application
+** that does not care about crash recovery or rollback might make
+** the open of a journal file a no-op. Writes to this journal would
+** also be no-ops, and any attempt to read the journal would return
+** SQLITE_IOERR. Or the implementation might recognize that a database
+** file will be doing page-aligned sector reads and writes in a random
+** order and set up its I/O subsystem accordingly.
+**
+** SQLite might also add one of the following flags to the xOpen
+** method:
+**
+** <ul>
+** <li> [SQLITE_OPEN_DELETEONCLOSE]
+** <li> [SQLITE_OPEN_EXCLUSIVE]
+** </ul>
+**
+** {F11145} The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be
+** deleted when it is closed. {F11146} The [SQLITE_OPEN_DELETEONCLOSE]
+** will be set for TEMP databases, journals and for subjournals.
+** {F11147} The [SQLITE_OPEN_EXCLUSIVE] flag means the file should be opened
+** for exclusive access. This flag is set for all files except
+** for the main database file. {END}
+**
+** {F11148} At least szOsFile bytes of memory are allocated by SQLite
+** to hold the [sqlite3_file] structure passed as the third
+** argument to xOpen. {END} The xOpen method does not have to
+** allocate the structure; it should just fill it in.
+**
+** {F11149} The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS]
+** to test for the existance of a file,
+** or [SQLITE_ACCESS_READWRITE] to test to see
+** if a file is readable and writable, or [SQLITE_ACCESS_READ]
+** to test to see if a file is at least readable. {END} The file can be a
+** directory.
+**
+** {F11150} SQLite will always allocate at least mxPathname+1 bytes for
+** the output buffers for xGetTempname and xFullPathname. {F11151} The exact
+** size of the output buffer is also passed as a parameter to both
+** methods. {END} If the output buffer is not large enough, SQLITE_CANTOPEN
+** should be returned. As this is handled as a fatal error by SQLite,
+** vfs implementations should endeavor to prevent this by setting
+** mxPathname to a sufficiently large value.
+**
+** The xRandomness(), xSleep(), and xCurrentTime() interfaces
+** are not strictly a part of the filesystem, but they are
+** included in the VFS structure for completeness.
+** The xRandomness() function attempts to return nBytes bytes
+** of good-quality randomness into zOut. The return value is
+** the actual number of bytes of randomness obtained. The
+** xSleep() method causes the calling thread to sleep for at
+** least the number of microseconds given. The xCurrentTime()
+** method returns a Julian Day Number for the current date and
+** time.
+*/
+typedef struct sqlite3_vfs sqlite3_vfs;
+struct sqlite3_vfs {
+ int iVersion; /* Structure version number */
+ int szOsFile; /* Size of subclassed sqlite3_file */
+ int mxPathname; /* Maximum file pathname length */
+ sqlite3_vfs *pNext; /* Next registered VFS */
+ const char *zName; /* Name of this virtual file system */
+ void *pAppData; /* Pointer to application-specific data */
+ int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
+ int flags, int *pOutFlags);
+ int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
+ int (*xAccess)(sqlite3_vfs*, const char *zName, int flags);
+ int (*xGetTempname)(sqlite3_vfs*, int nOut, char *zOut);
+ int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
+ void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
+ void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
+ void *(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol);
+ void (*xDlClose)(sqlite3_vfs*, void*);
+ int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
+ int (*xSleep)(sqlite3_vfs*, int microseconds);
+ int (*xCurrentTime)(sqlite3_vfs*, double*);
+ /* New fields may be appended in figure versions. The iVersion
+ ** value will increment whenever this happens. */
+};
+
+/*
+** CAPI3REF: Flags for the xAccess VFS method {F11190}
+**
+** {F11191} These integer constants can be used as the third parameter to
+** the xAccess method of an [sqlite3_vfs] object. {END} They determine
+** what kind of permissions the xAccess method is
+** looking for. {F11192} With SQLITE_ACCESS_EXISTS, the xAccess method
+** simply checks to see if the file exists. {F11193} With
+** SQLITE_ACCESS_READWRITE, the xAccess method checks to see
+** if the file is both readable and writable. {F11194} With
+** SQLITE_ACCESS_READ the xAccess method
+** checks to see if the file is readable.
+*/
+#define SQLITE_ACCESS_EXISTS 0
+#define SQLITE_ACCESS_READWRITE 1
+#define SQLITE_ACCESS_READ 2
+
+/*
+** CAPI3REF: Enable Or Disable Extended Result Codes {F12200}
+**
+** The sqlite3_extended_result_codes() routine enables or disables the
+** [SQLITE_IOERR_READ | extended result codes] feature of SQLite.
+** The extended result codes are disabled by default for historical
+** compatibility.
+**
+** INVARIANTS:
+**
+** {F12201} Each new [database connection] has the
+** [extended result codes] feature
+** disabled by default.
+**
+** {F12202} The [sqlite3_extended_result_codes(D,F)] interface will enable
+** [extended result codes] for the
+** [database connection] D if the F parameter
+** is true, or disable them if F is false.
+*/
+int sqlite3_extended_result_codes(sqlite3*, int onoff);
+
+/*
+** CAPI3REF: Last Insert Rowid {F12220}
+**
+** Each entry in an SQLite table has a unique 64-bit signed
+** integer key called the "rowid". The rowid is always available
+** as an undeclared column named ROWID, OID, or _ROWID_ as long as those
+** names are not also used by explicitly declared columns. If
+** the table has a column of type INTEGER PRIMARY KEY then that column
+** is another alias for the rowid.
+**
+** This routine returns the rowid of the most recent
+** successful INSERT into the database from the database connection
+** shown in the first argument. If no successful inserts
+** have ever occurred on this database connection, zero is returned.
+**
+** If an INSERT occurs within a trigger, then the rowid of the
+** inserted row is returned by this routine as long as the trigger
+** is running. But once the trigger terminates, the value returned
+** by this routine reverts to the last value inserted before the
+** trigger fired.
+**
+** An INSERT that fails due to a constraint violation is not a
+** successful insert and does not change the value returned by this
+** routine. Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK,
+** and INSERT OR ABORT make no changes to the return value of this
+** routine when their insertion fails. When INSERT OR REPLACE
+** encounters a constraint violation, it does not fail. The
+** INSERT continues to completion after deleting rows that caused
+** the constraint problem so INSERT OR REPLACE will always change
+** the return value of this interface.
+**
+** For the purposes of this routine, an insert is considered to
+** be successful even if it is subsequently rolled back.
+**
+** INVARIANTS:
+**
+** {F12221} The [sqlite3_last_insert_rowid()] function returns the
+** rowid of the most recent successful insert done
+** on the same database connection and within the same
+** trigger context, or zero if there have
+** been no qualifying inserts on that connection.
+**
+** {F12223} The [sqlite3_last_insert_rowid()] function returns
+** same value when called from the same trigger context
+** immediately before and after a ROLLBACK.
+**
+** LIMITATIONS:
+**
+** {U12232} If a separate thread does a new insert on the same
+** database connection while the [sqlite3_last_insert_rowid()]
+** function is running and thus changes the last insert rowid,
+** then the value returned by [sqlite3_last_insert_rowid()] is
+** unpredictable and might not equal either the old or the new
+** last insert rowid.
+*/
+sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
+
+/*
+** CAPI3REF: Count The Number Of Rows Modified {F12240}
+**
+** This function returns the number of database rows that were changed
+** or inserted or deleted by the most recently completed SQL statement
+** on the connection specified by the first parameter. Only
+** changes that are directly specified by the INSERT, UPDATE, or
+** DELETE statement are counted. Auxiliary changes caused by
+** triggers are not counted. Use the [sqlite3_total_changes()] function
+** to find the total number of changes including changes caused by triggers.
+**
+** A "row change" is a change to a single row of a single table
+** caused by an INSERT, DELETE, or UPDATE statement. Rows that
+** are changed as side effects of REPLACE constraint resolution,
+** rollback, ABORT processing, DROP TABLE, or by any other
+** mechanisms do not count as direct row changes.
+**
+** A "trigger context" is a scope of execution that begins and
+** ends with the script of a trigger. Most SQL statements are
+** evaluated outside of any trigger. This is the "top level"
+** trigger context. If a trigger fires from the top level, a
+** new trigger context is entered for the duration of that one
+** trigger. Subtriggers create subcontexts for their duration.
+**
+** Calling [sqlite3_exec()] or [sqlite3_step()] recursively does
+** not create a new trigger context.
+**
+** This function returns the number of direct row changes in the
+** most recent INSERT, UPDATE, or DELETE statement within the same
+** trigger context.
+**
+** So when called from the top level, this function returns the
+** number of changes in the most recent INSERT, UPDATE, or DELETE
+** that also occurred at the top level.
+** Within the body of a trigger, the sqlite3_changes() interface
+** can be called to find the number of
+** changes in the most recently completed INSERT, UPDATE, or DELETE
+** statement within the body of the same trigger.
+** However, the number returned does not include in changes
+** caused by subtriggers since they have their own context.
+**
+** SQLite implements the command "DELETE FROM table" without
+** a WHERE clause by dropping and recreating the table. (This is much
+** faster than going through and deleting individual elements from the
+** table.) Because of this optimization, the deletions in
+** "DELETE FROM table" are not row changes and will not be counted
+** by the sqlite3_changes() or [sqlite3_total_changes()] functions.
+** To get an accurate count of the number of rows deleted, use
+** "DELETE FROM table WHERE 1" instead.
+**
+** INVARIANTS:
+**
+** {F12241} The [sqlite3_changes()] function returns the number of
+** row changes caused by the most recent INSERT, UPDATE,
+** or DELETE statement on the same database connection and
+** within the same trigger context, or zero if there have
+** not been any qualifying row changes.
+**
+** LIMITATIONS:
+**
+** {U12252} If a separate thread makes changes on the same database connection
+** while [sqlite3_changes()] is running then the value returned
+** is unpredictable and unmeaningful.
+*/
+int sqlite3_changes(sqlite3*);
+
+/*
+** CAPI3REF: Total Number Of Rows Modified {F12260}
+***
+** This function returns the number of row changes caused
+** by INSERT, UPDATE or DELETE statements since the database handle
+** was opened. The count includes all changes from all trigger
+** contexts. But the count does not include changes used to
+** implement REPLACE constraints, do rollbacks or ABORT processing,
+** or DROP table processing.
+** The changes
+** are counted as soon as the statement that makes them is completed
+** (when the statement handle is passed to [sqlite3_reset()] or
+** [sqlite3_finalize()]).
+**
+** SQLite implements the command "DELETE FROM table" without
+** a WHERE clause by dropping and recreating the table. (This is much
+** faster than going
+** through and deleting individual elements from the table.) Because of
+** this optimization, the change count for "DELETE FROM table" will be
+** zero regardless of the number of elements that were originally in the
+** table. To get an accurate count of the number of rows deleted, use
+** "DELETE FROM table WHERE 1" instead.
+**
+** See also the [sqlite3_changes()] interface.
+**
+** INVARIANTS:
+**
+** {F12261} The [sqlite3_total_changes()] returns the total number
+** of row changes caused by INSERT, UPDATE, and/or DELETE
+** statements on the same [database connection], in any
+** trigger context, since the database connection was
+** created.
+**
+** LIMITATIONS:
+**
+** {U12264} If a separate thread makes changes on the same database connection
+** while [sqlite3_total_changes()] is running then the value
+** returned is unpredictable and unmeaningful.
+*/
+int sqlite3_total_changes(sqlite3*);
+
+/*
+** CAPI3REF: Interrupt A Long-Running Query {F12270}
+**
+** This function causes any pending database operation to abort and
+** return at its earliest opportunity. This routine is typically
+** called in response to a user action such as pressing "Cancel"
+** or Ctrl-C where the user wants a long query operation to halt
+** immediately.
+**
+** It is safe to call this routine from a thread different from the
+** thread that is currently running the database operation. But it
+** is not safe to call this routine with a database connection that
+** is closed or might close before sqlite3_interrupt() returns.
+**
+** If an SQL is very nearly finished at the time when sqlite3_interrupt()
+** is called, then it might not have an opportunity to be interrupted.
+** It might continue to completion.
+** An SQL operation that is interrupted will return
+** [SQLITE_INTERRUPT]. If the interrupted SQL operation is an
+** INSERT, UPDATE, or DELETE that is inside an explicit transaction,
+** then the entire transaction will be rolled back automatically.
+** A call to sqlite3_interrupt() has no effect on SQL statements
+** that are started after sqlite3_interrupt() returns.
+**
+** INVARIANTS:
+**
+** {F12271} The [sqlite3_interrupt()] interface will force all running
+** SQL statements associated with the same database connection
+** to halt after processing at most one additional row of
+** data.
+**
+** {F12272} Any SQL statement that is interrupted by [sqlite3_interrupt()]
+** will return [SQLITE_INTERRUPT].
+**
+** LIMITATIONS:
+**
+** {U12279} If the database connection closes while [sqlite3_interrupt()]
+** is running then bad things will likely happen.
+*/
+void sqlite3_interrupt(sqlite3*);
+
+/*
+** CAPI3REF: Determine If An SQL Statement Is Complete {F10510}
+**
+** These routines are useful for command-line input to determine if the
+** currently entered text seems to form complete a SQL statement or
+** if additional input is needed before sending the text into
+** SQLite for parsing. These routines return true if the input string
+** appears to be a complete SQL statement. A statement is judged to be
+** complete if it ends with a semicolon token and is not a fragment of a
+** CREATE TRIGGER statement. Semicolons that are embedded within
+** string literals or quoted identifier names or comments are not
+** independent tokens (they are part of the token in which they are
+** embedded) and thus do not count as a statement terminator.
+**
+** These routines do not parse the SQL and
+** so will not detect syntactically incorrect SQL.
+**
+** INVARIANTS:
+**
+** {F10511} The sqlite3_complete() and sqlite3_complete16() functions
+** return true (non-zero) if and only if the last
+** non-whitespace token in their input is a semicolon that
+** is not in between the BEGIN and END of a CREATE TRIGGER
+** statement.
+**
+** LIMITATIONS:
+**
+** {U10512} The input to sqlite3_complete() must be a zero-terminated
+** UTF-8 string.
+**
+** {U10513} The input to sqlite3_complete16() must be a zero-terminated
+** UTF-16 string in native byte order.
+*/
+int sqlite3_complete(const char *sql);
+int sqlite3_complete16(const void *sql);
+
+/*
+** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors {F12310}
+**
+** This routine identifies a callback function that might be
+** invoked whenever an attempt is made to open a database table
+** that another thread or process has locked.
+** If the busy callback is NULL, then [SQLITE_BUSY]
+** or [SQLITE_IOERR_BLOCKED]
+** is returned immediately upon encountering the lock.
+** If the busy callback is not NULL, then the
+** callback will be invoked with two arguments. The
+** first argument to the handler is a copy of the void* pointer which
+** is the third argument to this routine. The second argument to
+** the handler is the number of times that the busy handler has
+** been invoked for this locking event. If the
+** busy callback returns 0, then no additional attempts are made to
+** access the database and [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] is returned.
+** If the callback returns non-zero, then another attempt
+** is made to open the database for reading and the cycle repeats.
+**
+** The presence of a busy handler does not guarantee that
+** it will be invoked when there is lock contention.
+** If SQLite determines that invoking the busy handler could result in
+** a deadlock, it will go ahead and return [SQLITE_BUSY] or
+** [SQLITE_IOERR_BLOCKED] instead of invoking the
+** busy handler.
+** Consider a scenario where one process is holding a read lock that
+** it is trying to promote to a reserved lock and
+** a second process is holding a reserved lock that it is trying
+** to promote to an exclusive lock. The first process cannot proceed
+** because it is blocked by the second and the second process cannot
+** proceed because it is blocked by the first. If both processes
+** invoke the busy handlers, neither will make any progress. Therefore,
+** SQLite returns [SQLITE_BUSY] for the first process, hoping that this
+** will induce the first process to release its read lock and allow
+** the second process to proceed.
+**
+** The default busy callback is NULL.
+**
+** The [SQLITE_BUSY] error is converted to [SQLITE_IOERR_BLOCKED]
+** when SQLite is in the middle of a large transaction where all the
+** changes will not fit into the in-memory cache. SQLite will
+** already hold a RESERVED lock on the database file, but it needs
+** to promote this lock to EXCLUSIVE so that it can spill cache
+** pages into the database file without harm to concurrent
+** readers. If it is unable to promote the lock, then the in-memory
+** cache will be left in an inconsistent state and so the error
+** code is promoted from the relatively benign [SQLITE_BUSY] to
+** the more severe [SQLITE_IOERR_BLOCKED]. This error code promotion
+** forces an automatic rollback of the changes. See the
+** <a href="http://www.sqlite.org/cvstrac/wiki?p=CorruptionFollowingBusyError">
+** CorruptionFollowingBusyError</a> wiki page for a discussion of why
+** this is important.
+**
+** There can only be a single busy handler defined for each database
+** connection. Setting a new busy handler clears any previous one.
+** Note that calling [sqlite3_busy_timeout()] will also set or clear
+** the busy handler.
+**
+** INVARIANTS:
+**
+** {F12311} The [sqlite3_busy_handler()] function replaces the busy handler
+** callback in the database connection identified by the 1st
+** parameter with a new busy handler identified by the 2nd and 3rd
+** parameters.
+**
+** {F12312} The default busy handler for new database connections is NULL.
+**
+** {F12314} When two or more database connection share a common cache,
+** the busy handler for the database connection currently using
+** the cache is invoked when the cache encounters a lock.
+**
+** {F12316} If a busy handler callback returns zero, then the SQLite
+** interface that provoked the locking event will return
+** [SQLITE_BUSY].
+**
+** {F12318} SQLite will invokes the busy handler with two argument which
+** are a copy of the pointer supplied by the 3rd parameter to
+** [sqlite3_busy_handler()] and a count of the number of prior
+** invocations of the busy handler for the same locking event.
+**
+** LIMITATIONS:
+**
+** {U12319} A busy handler should not call close the database connection
+** or prepared statement that invoked the busy handler.
+*/
+int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
+
+/*
+** CAPI3REF: Set A Busy Timeout {F12340}
+**
+** This routine sets a [sqlite3_busy_handler | busy handler]
+** that sleeps for a while when a
+** table is locked. The handler will sleep multiple times until
+** at least "ms" milliseconds of sleeping have been done. {F12343} After
+** "ms" milliseconds of sleeping, the handler returns 0 which
+** causes [sqlite3_step()] to return [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED].
+**
+** Calling this routine with an argument less than or equal to zero
+** turns off all busy handlers.
+**
+** There can only be a single busy handler for a particular database
+** connection. If another busy handler was defined
+** (using [sqlite3_busy_handler()]) prior to calling
+** this routine, that other busy handler is cleared.
+**
+** INVARIANTS:
+**
+** {F12341} The [sqlite3_busy_timeout()] function overrides any prior
+** [sqlite3_busy_timeout()] or [sqlite3_busy_handler()] setting
+** on the same database connection.
+**
+** {F12343} If the 2nd parameter to [sqlite3_busy_timeout()] is less than
+** or equal to zero, then the busy handler is cleared so that
+** all subsequent locking events immediately return [SQLITE_BUSY].
+**
+** {F12344} If the 2nd parameter to [sqlite3_busy_timeout()] is a positive
+** number N, then a busy handler is set that repeatedly calls
+** the xSleep() method in the VFS interface until either the
+** lock clears or until the cumulative sleep time reported back
+** by xSleep() exceeds N milliseconds.
+*/
+int sqlite3_busy_timeout(sqlite3*, int ms);
+
+/*
+** CAPI3REF: Convenience Routines For Running Queries {F12370}
+**
+** Definition: A <b>result table</b> is memory data structure created by the
+** [sqlite3_get_table()] interface. A result table records the
+** complete query results from one or more queries.
+**
+** The table conceptually has a number of rows and columns. But
+** these numbers are not part of the result table itself. These
+** numbers are obtained separately. Let N be the number of rows
+** and M be the number of columns.
+**
+** A result table is an array of pointers to zero-terminated
+** UTF-8 strings. There are (N+1)*M elements in the array.
+** The first M pointers point to zero-terminated strings that
+** contain the names of the columns.
+** The remaining entries all point to query results. NULL
+** values are give a NULL pointer. All other values are in
+** their UTF-8 zero-terminated string representation as returned by
+** [sqlite3_column_text()].
+**
+** A result table might consists of one or more memory allocations.
+** It is not safe to pass a result table directly to [sqlite3_free()].
+** A result table should be deallocated using [sqlite3_free_table()].
+**
+** As an example of the result table format, suppose a query result
+** is as follows:
+**
+** <blockquote><pre>
+** Name | Age
+** -----------------------
+** Alice | 43
+** Bob | 28
+** Cindy | 21
+** </pre></blockquote>
+**
+** There are two column (M==2) and three rows (N==3). Thus the
+** result table has 8 entries. Suppose the result table is stored
+** in an array names azResult. Then azResult holds this content:
+**
+** <blockquote><pre>
+** azResult&#91;0] = "Name";
+** azResult&#91;1] = "Age";
+** azResult&#91;2] = "Alice";
+** azResult&#91;3] = "43";
+** azResult&#91;4] = "Bob";
+** azResult&#91;5] = "28";
+** azResult&#91;6] = "Cindy";
+** azResult&#91;7] = "21";
+** </pre></blockquote>
+**
+** The sqlite3_get_table() function evaluates one or more
+** semicolon-separated SQL statements in the zero-terminated UTF-8
+** string of its 2nd parameter. It returns a result table to the
+** pointer given in its 3rd parameter.
+**
+** After the calling function has finished using the result, it should
+** pass the pointer to the result table to sqlite3_free_table() in order to
+** release the memory that was malloc-ed. Because of the way the
+** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling
+** function must not try to call [sqlite3_free()] directly. Only
+** [sqlite3_free_table()] is able to release the memory properly and safely.
+**
+** The sqlite3_get_table() interface is implemented as a wrapper around
+** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access
+** to any internal data structures of SQLite. It uses only the public
+** interface defined here. As a consequence, errors that occur in the
+** wrapper layer outside of the internal [sqlite3_exec()] call are not
+** reflected in subsequent calls to [sqlite3_errcode()] or
+** [sqlite3_errmsg()].
+**
+** INVARIANTS:
+**
+** {F12371} If a [sqlite3_get_table()] fails a memory allocation, then
+** it frees the result table under construction, aborts the
+** query in process, skips any subsequent queries, sets the
+** *resultp output pointer to NULL and returns [SQLITE_NOMEM].
+**
+** {F12373} If the ncolumn parameter to [sqlite3_get_table()] is not NULL
+** then [sqlite3_get_table()] write the number of columns in the
+** result set of the query into *ncolumn if the query is
+** successful (if the function returns SQLITE_OK).
+**
+** {F12374} If the nrow parameter to [sqlite3_get_table()] is not NULL
+** then [sqlite3_get_table()] write the number of rows in the
+** result set of the query into *nrow if the query is
+** successful (if the function returns SQLITE_OK).
+**
+** {F12376} The [sqlite3_get_table()] function sets its *ncolumn value
+** to the number of columns in the result set of the query in the
+** sql parameter, or to zero if the query in sql has an empty
+** result set.
+*/
+int sqlite3_get_table(
+ sqlite3*, /* An open database */
+ const char *sql, /* SQL to be evaluated */
+ char ***pResult, /* Results of the query */
+ int *nrow, /* Number of result rows written here */
+ int *ncolumn, /* Number of result columns written here */
+ char **errmsg /* Error msg written here */
+);
+void sqlite3_free_table(char **result);
+
+/*
+** CAPI3REF: Formatted String Printing Functions {F17400}
+**
+** These routines are workalikes of the "printf()" family of functions
+** from the standard C library.
+**
+** The sqlite3_mprintf() and sqlite3_vmprintf() routines write their
+** results into memory obtained from [sqlite3_malloc()].
+** The strings returned by these two routines should be
+** released by [sqlite3_free()]. Both routines return a
+** NULL pointer if [sqlite3_malloc()] is unable to allocate enough
+** memory to hold the resulting string.
+**
+** In sqlite3_snprintf() routine is similar to "snprintf()" from
+** the standard C library. The result is written into the
+** buffer supplied as the second parameter whose size is given by
+** the first parameter. Note that the order of the
+** first two parameters is reversed from snprintf(). This is an
+** historical accident that cannot be fixed without breaking
+** backwards compatibility. Note also that sqlite3_snprintf()
+** returns a pointer to its buffer instead of the number of
+** characters actually written into the buffer. We admit that
+** the number of characters written would be a more useful return
+** value but we cannot change the implementation of sqlite3_snprintf()
+** now without breaking compatibility.
+**
+** As long as the buffer size is greater than zero, sqlite3_snprintf()
+** guarantees that the buffer is always zero-terminated. The first
+** parameter "n" is the total size of the buffer, including space for
+** the zero terminator. So the longest string that can be completely
+** written will be n-1 characters.
+**
+** These routines all implement some additional formatting
+** options that are useful for constructing SQL statements.
+** All of the usual printf formatting options apply. In addition, there
+** is are "%q", "%Q", and "%z" options.
+**
+** The %q option works like %s in that it substitutes a null-terminated
+** string from the argument list. But %q also doubles every '\'' character.
+** %q is designed for use inside a string literal. By doubling each '\''
+** character it escapes that character and allows it to be inserted into
+** the string.
+**
+** For example, so some string variable contains text as follows:
+**
+** <blockquote><pre>
+** char *zText = "It's a happy day!";
+** </pre></blockquote>
+**
+** One can use this text in an SQL statement as follows:
+**
+** <blockquote><pre>
+** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
+** sqlite3_exec(db, zSQL, 0, 0, 0);
+** sqlite3_free(zSQL);
+** </pre></blockquote>
+**
+** Because the %q format string is used, the '\'' character in zText
+** is escaped and the SQL generated is as follows:
+**
+** <blockquote><pre>
+** INSERT INTO table1 VALUES('It''s a happy day!')
+** </pre></blockquote>
+**
+** This is correct. Had we used %s instead of %q, the generated SQL
+** would have looked like this:
+**
+** <blockquote><pre>
+** INSERT INTO table1 VALUES('It's a happy day!');
+** </pre></blockquote>
+**
+** This second example is an SQL syntax error. As a general rule you
+** should always use %q instead of %s when inserting text into a string
+** literal.
+**
+** The %Q option works like %q except it also adds single quotes around
+** the outside of the total string. Or if the parameter in the argument
+** list is a NULL pointer, %Q substitutes the text "NULL" (without single
+** quotes) in place of the %Q option. {END} So, for example, one could say:
+**
+** <blockquote><pre>
+** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
+** sqlite3_exec(db, zSQL, 0, 0, 0);
+** sqlite3_free(zSQL);
+** </pre></blockquote>
+**
+** The code above will render a correct SQL statement in the zSQL
+** variable even if the zText variable is a NULL pointer.
+**
+** The "%z" formatting option works exactly like "%s" with the
+** addition that after the string has been read and copied into
+** the result, [sqlite3_free()] is called on the input string. {END}
+**
+** INVARIANTS:
+**
+** {F17403} The [sqlite3_mprintf()] and [sqlite3_vmprintf()] interfaces
+** return either pointers to zero-terminated UTF-8 strings held in
+** memory obtained from [sqlite3_malloc()] or NULL pointers if
+** a call to [sqlite3_malloc()] fails.
+**
+** {F17406} The [sqlite3_snprintf()] interface writes a zero-terminated
+** UTF-8 string into the buffer pointed to by the second parameter
+** provided that the first parameter is greater than zero.
+**
+** {F17407} The [sqlite3_snprintf()] interface does not writes slots of
+** its output buffer (the second parameter) outside the range
+** of 0 through N-1 (where N is the first parameter)
+** regardless of the length of the string
+** requested by the format specification.
+**
+*/
+char *sqlite3_mprintf(const char*,...);
+char *sqlite3_vmprintf(const char*, va_list);
+char *sqlite3_snprintf(int,char*,const char*, ...);
+
+/*
+** CAPI3REF: Memory Allocation Subsystem {F17300}
+**
+** The SQLite core uses these three routines for all of its own
+** internal memory allocation needs. "Core" in the previous sentence
+** does not include operating-system specific VFS implementation. The
+** windows VFS uses native malloc and free for some operations.
+**
+** The sqlite3_malloc() routine returns a pointer to a block
+** of memory at least N bytes in length, where N is the parameter.
+** If sqlite3_malloc() is unable to obtain sufficient free
+** memory, it returns a NULL pointer. If the parameter N to
+** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns
+** a NULL pointer.
+**
+** Calling sqlite3_free() with a pointer previously returned
+** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
+** that it might be reused. The sqlite3_free() routine is
+** a no-op if is called with a NULL pointer. Passing a NULL pointer
+** to sqlite3_free() is harmless. After being freed, memory
+** should neither be read nor written. Even reading previously freed
+** memory might result in a segmentation fault or other severe error.
+** Memory corruption, a segmentation fault, or other severe error
+** might result if sqlite3_free() is called with a non-NULL pointer that
+** was not obtained from sqlite3_malloc() or sqlite3_free().
+**
+** The sqlite3_realloc() interface attempts to resize a
+** prior memory allocation to be at least N bytes, where N is the
+** second parameter. The memory allocation to be resized is the first
+** parameter. If the first parameter to sqlite3_realloc()
+** is a NULL pointer then its behavior is identical to calling
+** sqlite3_malloc(N) where N is the second parameter to sqlite3_realloc().
+** If the second parameter to sqlite3_realloc() is zero or
+** negative then the behavior is exactly the same as calling
+** sqlite3_free(P) where P is the first parameter to sqlite3_realloc().
+** Sqlite3_realloc() returns a pointer to a memory allocation
+** of at least N bytes in size or NULL if sufficient memory is unavailable.
+** If M is the size of the prior allocation, then min(N,M) bytes
+** of the prior allocation are copied into the beginning of buffer returned
+** by sqlite3_realloc() and the prior allocation is freed.
+** If sqlite3_realloc() returns NULL, then the prior allocation
+** is not freed.
+**
+** The memory returned by sqlite3_malloc() and sqlite3_realloc()
+** is always aligned to at least an 8 byte boundary. {END}
+**
+** The default implementation
+** of the memory allocation subsystem uses the malloc(), realloc()
+** and free() provided by the standard C library. {F17382} However, if
+** SQLite is compiled with the following C preprocessor macro
+**
+** <blockquote> SQLITE_MEMORY_SIZE=<i>NNN</i> </blockquote>
+**
+** where <i>NNN</i> is an integer, then SQLite create a static
+** array of at least <i>NNN</i> bytes in size and use that array
+** for all of its dynamic memory allocation needs. {END} Additional
+** memory allocator options may be added in future releases.
+**
+** In SQLite version 3.5.0 and 3.5.1, it was possible to define
+** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in
+** implementation of these routines to be omitted. That capability
+** is no longer provided. Only built-in memory allocators can be
+** used.
+**
+** The windows OS interface layer calls
+** the system malloc() and free() directly when converting
+** filenames between the UTF-8 encoding used by SQLite
+** and whatever filename encoding is used by the particular windows
+** installation. Memory allocation errors are detected, but
+** they are reported back as [SQLITE_CANTOPEN] or
+** [SQLITE_IOERR] rather than [SQLITE_NOMEM].
+**
+** INVARIANTS:
+**
+** {F17303} The [sqlite3_malloc(N)] interface returns either a pointer to
+** newly checked-out block of at least N bytes of memory
+** that is 8-byte aligned,
+** or it returns NULL if it is unable to fulfill the request.
+**
+** {F17304} The [sqlite3_malloc(N)] interface returns a NULL pointer if
+** N is less than or equal to zero.
+**
+** {F17305} The [sqlite3_free(P)] interface releases memory previously
+** returned from [sqlite3_malloc()] or [sqlite3_realloc()],
+** making it available for reuse.
+**
+** {F17306} A call to [sqlite3_free(NULL)] is a harmless no-op.
+**
+** {F17310} A call to [sqlite3_realloc(0,N)] is equivalent to a call
+** to [sqlite3_malloc(N)].
+**
+** {F17312} A call to [sqlite3_realloc(P,0)] is equivalent to a call
+** to [sqlite3_free(P)].
+**
+** {F17315} The SQLite core uses [sqlite3_malloc()], [sqlite3_realloc()],
+** and [sqlite3_free()] for all of its memory allocation and
+** deallocation needs.
+**
+** {F17318} The [sqlite3_realloc(P,N)] interface returns either a pointer
+** to a block of checked-out memory of at least N bytes in size
+** that is 8-byte aligned, or a NULL pointer.
+**
+** {F17321} When [sqlite3_realloc(P,N)] returns a non-NULL pointer, it first
+** copies the first K bytes of content from P into the newly allocated
+** where K is the lessor of N and the size of the buffer P.
+**
+** {F17322} When [sqlite3_realloc(P,N)] returns a non-NULL pointer, it first
+** releases the buffer P.
+**
+** {F17323} When [sqlite3_realloc(P,N)] returns NULL, the buffer P is
+** not modified or released.
+**
+** LIMITATIONS:
+**
+** {U17350} The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()]
+** must be either NULL or else a pointer obtained from a prior
+** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that has
+** not been released.
+**
+** {U17351} The application must not read or write any part of
+** a block of memory after it has been released using
+** [sqlite3_free()] or [sqlite3_realloc()].
+**
+*/
+void *sqlite3_malloc(int);
+void *sqlite3_realloc(void*, int);
+void sqlite3_free(void*);
+
+/*
+** CAPI3REF: Memory Allocator Statistics {F17370}
+**
+** SQLite provides these two interfaces for reporting on the status
+** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()]
+** the memory allocation subsystem included within the SQLite.
+**
+** INVARIANTS:
+**
+** {F17371} The [sqlite3_memory_used()] routine returns the
+** number of bytes of memory currently outstanding
+** (malloced but not freed).
+**
+** {F17373} The [sqlite3_memory_highwater()] routine returns the maximum
+** value of [sqlite3_memory_used()]
+** since the highwater mark was last reset.
+**
+** {F17374} The values returned by [sqlite3_memory_used()] and
+** [sqlite3_memory_highwater()] include any overhead
+** added by SQLite in its implementation of [sqlite3_malloc()],
+** but not overhead added by the any underlying system library
+** routines that [sqlite3_malloc()] may call.
+**
+** {F17375} The memory highwater mark is reset to the current value of
+** [sqlite3_memory_used()] if and only if the parameter to
+** [sqlite3_memory_highwater()] is true. The value returned
+** by [sqlite3_memory_highwater(1)] is the highwater mark
+** prior to the reset.
+*/
+sqlite3_int64 sqlite3_memory_used(void);
+sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
+
+/*
+** CAPI3REF: Pseudo-Random Number Generator {F17390}
+**
+** SQLite contains a high-quality pseudo-random number generator (PRNG) used to
+** select random ROWIDs when inserting new records into a table that
+** already uses the largest possible ROWID. The PRNG is also used for
+** the build-in random() and randomblob() SQL functions. This interface allows
+** appliations to access the same PRNG for other purposes.
+**
+** A call to this routine stores N bytes of randomness into buffer P.
+**
+** The first time this routine is invoked (either internally or by
+** the application) the PRNG is seeded using randomness obtained
+** from the xRandomness method of the default [sqlite3_vfs] object.
+** On all subsequent invocations, the pseudo-randomness is generated
+** internally and without recourse to the [sqlite3_vfs] xRandomness
+** method.
+**
+** INVARIANTS:
+**
+** {F17392} The [sqlite3_randomness(N,P)] interface writes N bytes of
+** high-quality pseudo-randomness into buffer P.
+*/
+void sqlite3_randomness(int N, void *P);
+
+/*
+** CAPI3REF: Compile-Time Authorization Callbacks {F12500}
+**
+** This routine registers a authorizer callback with a particular
+** [database connection], supplied in the first argument.
+** The authorizer callback is invoked as SQL statements are being compiled
+** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()],
+** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. At various
+** points during the compilation process, as logic is being created
+** to perform various actions, the authorizer callback is invoked to
+** see if those actions are allowed. The authorizer callback should
+** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the
+** specific action but allow the SQL statement to continue to be
+** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be
+** rejected with an error. If the authorizer callback returns
+** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY]
+** then [sqlite3_prepare_v2()] or equivalent call that triggered
+** the authorizer will fail with an error message.
+**
+** When the callback returns [SQLITE_OK], that means the operation
+** requested is ok. When the callback returns [SQLITE_DENY], the
+** [sqlite3_prepare_v2()] or equivalent call that triggered the
+** authorizer will fail with an error message explaining that
+** access is denied. If the authorizer code is [SQLITE_READ]
+** and the callback returns [SQLITE_IGNORE] then the
+** [prepared statement] statement is constructed to substitute
+** a NULL value in place of the table column that would have
+** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE]
+** return can be used to deny an untrusted user access to individual
+** columns of a table.
+**
+** The first parameter to the authorizer callback is a copy of
+** the third parameter to the sqlite3_set_authorizer() interface.
+** The second parameter to the callback is an integer
+** [SQLITE_COPY | action code] that specifies the particular action
+** to be authorized. The third through sixth
+** parameters to the callback are zero-terminated strings that contain
+** additional details about the action to be authorized.
+**
+** An authorizer is used when [sqlite3_prepare | preparing]
+** SQL statements from an untrusted
+** source, to ensure that the SQL statements do not try to access data
+** that they are not allowed to see, or that they do not try to
+** execute malicious statements that damage the database. For
+** example, an application may allow a user to enter arbitrary
+** SQL queries for evaluation by a database. But the application does
+** not want the user to be able to make arbitrary changes to the
+** database. An authorizer could then be put in place while the
+** user-entered SQL is being [sqlite3_prepare | prepared] that
+** disallows everything except [SELECT] statements.
+**
+** Applications that need to process SQL from untrusted sources
+** might also consider lowering resource limits using [sqlite3_limit()]
+** and limiting database size using the [max_page_count] [PRAGMA]
+** in addition to using an authorizer.
+**
+** Only a single authorizer can be in place on a database connection
+** at a time. Each call to sqlite3_set_authorizer overrides the
+** previous call. Disable the authorizer by installing a NULL callback.
+** The authorizer is disabled by default.
+**
+** Note that the authorizer callback is invoked only during
+** [sqlite3_prepare()] or its variants. Authorization is not
+** performed during statement evaluation in [sqlite3_step()].
+**
+** INVARIANTS:
+**
+** {F12501} The [sqlite3_set_authorizer(D,...)] interface registers a
+** authorizer callback with database connection D.
+**
+** {F12502} The authorizer callback is invoked as SQL statements are
+** being compiled
+**
+** {F12503} If the authorizer callback returns any value other than
+** [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] then
+** the [sqlite3_prepare_v2()] or equivalent call that caused
+** the authorizer callback to run shall fail with an
+** [SQLITE_ERROR] error code and an appropriate error message.
+**
+** {F12504} When the authorizer callback returns [SQLITE_OK], the operation
+** described is coded normally.
+**
+** {F12505} When the authorizer callback returns [SQLITE_DENY], the
+** [sqlite3_prepare_v2()] or equivalent call that caused the
+** authorizer callback to run shall fail
+** with an [SQLITE_ERROR] error code and an error message
+** explaining that access is denied.
+**
+** {F12506} If the authorizer code (the 2nd parameter to the authorizer
+** callback) is [SQLITE_READ] and the authorizer callback returns
+** [SQLITE_IGNORE] then the prepared statement is constructed to
+** insert a NULL value in place of the table column that would have
+** been read if [SQLITE_OK] had been returned.
+**
+** {F12507} If the authorizer code (the 2nd parameter to the authorizer
+** callback) is anything other than [SQLITE_READ], then
+** a return of [SQLITE_IGNORE] has the same effect as [SQLITE_DENY].
+**
+** {F12510} The first parameter to the authorizer callback is a copy of
+** the third parameter to the [sqlite3_set_authorizer()] interface.
+**
+** {F12511} The second parameter to the callback is an integer
+** [SQLITE_COPY | action code] that specifies the particular action
+** to be authorized.
+**
+** {F12512} The third through sixth parameters to the callback are
+** zero-terminated strings that contain
+** additional details about the action to be authorized.
+**
+** {F12520} Each call to [sqlite3_set_authorizer()] overrides the
+** any previously installed authorizer.
+**
+** {F12521} A NULL authorizer means that no authorization
+** callback is invoked.
+**
+** {F12522} The default authorizer is NULL.
+*/
+int sqlite3_set_authorizer(
+ sqlite3*,
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pUserData
+);
+
+/*
+** CAPI3REF: Authorizer Return Codes {F12590}
+**
+** The [sqlite3_set_authorizer | authorizer callback function] must
+** return either [SQLITE_OK] or one of these two constants in order
+** to signal SQLite whether or not the action is permitted. See the
+** [sqlite3_set_authorizer | authorizer documentation] for additional
+** information.
+*/
+#define SQLITE_DENY 1 /* Abort the SQL statement with an error */
+#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
+
+/*
+** CAPI3REF: Authorizer Action Codes {F12550}
+**
+** The [sqlite3_set_authorizer()] interface registers a callback function
+** that is invoked to authorizer certain SQL statement actions. The
+** second parameter to the callback is an integer code that specifies
+** what action is being authorized. These are the integer action codes that
+** the authorizer callback may be passed.
+**
+** These action code values signify what kind of operation is to be
+** authorized. The 3rd and 4th parameters to the authorization
+** callback function will be parameters or NULL depending on which of these
+** codes is used as the second parameter. The 5th parameter to the
+** authorizer callback is the name of the database ("main", "temp",
+** etc.) if applicable. The 6th parameter to the authorizer callback
+** is the name of the inner-most trigger or view that is responsible for
+** the access attempt or NULL if this access attempt is directly from
+** top-level SQL code.
+**
+** INVARIANTS:
+**
+** {F12551} The second parameter to an
+** [sqlite3_set_authorizer | authorizer callback is always an integer
+** [SQLITE_COPY | authorizer code] that specifies what action
+** is being authorized.
+**
+** {F12552} The 3rd and 4th parameters to the
+** [sqlite3_set_authorizer | authorization callback function]
+** will be parameters or NULL depending on which
+** [SQLITE_COPY | authorizer code] is used as the second parameter.
+**
+** {F12553} The 5th parameter to the
+** [sqlite3_set_authorizer | authorizer callback] is the name
+** of the database (example: "main", "temp", etc.) if applicable.
+**
+** {F12554} The 6th parameter to the
+** [sqlite3_set_authorizer | authorizer callback] is the name
+** of the inner-most trigger or view that is responsible for
+** the access attempt or NULL if this access attempt is directly from
+** top-level SQL code.
+*/
+/******************************************* 3rd ************ 4th ***********/
+#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */
+#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */
+#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */
+#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */
+#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */
+#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */
+#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */
+#define SQLITE_CREATE_VIEW 8 /* View Name NULL */
+#define SQLITE_DELETE 9 /* Table Name NULL */
+#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */
+#define SQLITE_DROP_TABLE 11 /* Table Name NULL */
+#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */
+#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */
+#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */
+#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */
+#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */
+#define SQLITE_DROP_VIEW 17 /* View Name NULL */
+#define SQLITE_INSERT 18 /* Table Name NULL */
+#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */
+#define SQLITE_READ 20 /* Table Name Column Name */
+#define SQLITE_SELECT 21 /* NULL NULL */
+#define SQLITE_TRANSACTION 22 /* NULL NULL */
+#define SQLITE_UPDATE 23 /* Table Name Column Name */
+#define SQLITE_ATTACH 24 /* Filename NULL */
+#define SQLITE_DETACH 25 /* Database Name NULL */
+#define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */
+#define SQLITE_REINDEX 27 /* Index Name NULL */
+#define SQLITE_ANALYZE 28 /* Table Name NULL */
+#define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */
+#define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */
+#define SQLITE_FUNCTION 31 /* Function Name NULL */
+#define SQLITE_COPY 0 /* No longer used */
+
+/*
+** CAPI3REF: Tracing And Profiling Functions {F12280}
+**
+** These routines register callback functions that can be used for
+** tracing and profiling the execution of SQL statements.
+**
+** The callback function registered by sqlite3_trace() is invoked at
+** various times when an SQL statement is being run by [sqlite3_step()].
+** The callback returns a UTF-8 rendering of the SQL statement text
+** as the statement first begins executing. Additional callbacks occur
+** as each triggersubprogram is entered. The callbacks for triggers
+** contain a UTF-8 SQL comment that identifies the trigger.
+**
+** The callback function registered by sqlite3_profile() is invoked
+** as each SQL statement finishes. The profile callback contains
+** the original statement text and an estimate of wall-clock time
+** of how long that statement took to run.
+**
+** The sqlite3_profile() API is currently considered experimental and
+** is subject to change or removal in a future release.
+**
+** The trigger reporting feature of the trace callback is considered
+** experimental and is subject to change or removal in future releases.
+** Future versions of SQLite might also add new trace callback
+** invocations.
+**
+** INVARIANTS:
+**
+** {F12281} The callback function registered by [sqlite3_trace()] is
+** whenever an SQL statement first begins to execute and
+** whenever a trigger subprogram first begins to run.
+**
+** {F12282} Each call to [sqlite3_trace()] overrides the previously
+** registered trace callback.
+**
+** {F12283} A NULL trace callback disables tracing.
+**
+** {F12284} The first argument to the trace callback is a copy of
+** the pointer which was the 3rd argument to [sqlite3_trace()].
+**
+** {F12285} The second argument to the trace callback is a
+** zero-terminated UTF8 string containing the original text
+** of the SQL statement as it was passed into [sqlite3_prepare_v2()]
+** or the equivalent, or an SQL comment indicating the beginning
+** of a trigger subprogram.
+**
+** {F12287} The callback function registered by [sqlite3_profile()] is invoked
+** as each SQL statement finishes.
+**
+** {F12288} The first parameter to the profile callback is a copy of
+** the 3rd parameter to [sqlite3_profile()].
+**
+** {F12289} The second parameter to the profile callback is a
+** zero-terminated UTF-8 string that contains the complete text of
+** the SQL statement as it was processed by [sqlite3_prepare_v2()]
+** or the equivalent.
+**
+** {F12290} The third parameter to the profile callback is an estimate
+** of the number of nanoseconds of wall-clock time required to
+** run the SQL statement from start to finish.
+*/
+void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*);
+void *sqlite3_profile(sqlite3*,
+ void(*xProfile)(void*,const char*,sqlite3_uint64), void*);
+
+/*
+** CAPI3REF: Query Progress Callbacks {F12910}
+**
+** This routine configures a callback function - the
+** progress callback - that is invoked periodically during long
+** running calls to [sqlite3_exec()], [sqlite3_step()] and
+** [sqlite3_get_table()]. An example use for this
+** interface is to keep a GUI updated during a large query.
+**
+** If the progress callback returns non-zero, the opertion is
+** interrupted. This feature can be used to implement a
+** "Cancel" button on a GUI dialog box.
+**
+** INVARIANTS:
+**
+** {F12911} The callback function registered by [sqlite3_progress_handler()]
+** is invoked periodically during long running calls to
+** [sqlite3_step()].
+**
+** {F12912} The progress callback is invoked once for every N virtual
+** machine opcodes, where N is the second argument to
+** the [sqlite3_progress_handler()] call that registered
+** the callback. <todo>What if N is less than 1?</todo>
+**
+** {F12913} The progress callback itself is identified by the third
+** argument to [sqlite3_progress_handler()].
+**
+** {F12914} The fourth argument [sqlite3_progress_handler()] is a
+*** void pointer passed to the progress callback
+** function each time it is invoked.
+**
+** {F12915} If a call to [sqlite3_step()] results in fewer than
+** N opcodes being executed,
+** then the progress callback is never invoked. {END}
+**
+** {F12916} Every call to [sqlite3_progress_handler()]
+** overwrites any previously registere progress handler.
+**
+** {F12917} If the progress handler callback is NULL then no progress
+** handler is invoked.
+**
+** {F12918} If the progress callback returns a result other than 0, then
+** the behavior is a if [sqlite3_interrupt()] had been called.
+*/
+void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
+
+/*
+** CAPI3REF: Opening A New Database Connection {F12700}
+**
+** These routines open an SQLite database file whose name
+** is given by the filename argument.
+** The filename argument is interpreted as UTF-8
+** for [sqlite3_open()] and [sqlite3_open_v2()] and as UTF-16
+** in the native byte order for [sqlite3_open16()].
+** An [sqlite3*] handle is usually returned in *ppDb, even
+** if an error occurs. The only exception is if SQLite is unable
+** to allocate memory to hold the [sqlite3] object, a NULL will
+** be written into *ppDb instead of a pointer to the [sqlite3] object.
+** If the database is opened (and/or created)
+** successfully, then [SQLITE_OK] is returned. Otherwise an
+** error code is returned. The
+** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain
+** an English language description of the error.
+**
+** The default encoding for the database will be UTF-8 if
+** [sqlite3_open()] or [sqlite3_open_v2()] is called and
+** UTF-16 in the native byte order if [sqlite3_open16()] is used.
+**
+** Whether or not an error occurs when it is opened, resources
+** associated with the [sqlite3*] handle should be released by passing it
+** to [sqlite3_close()] when it is no longer required.
+**
+** The [sqlite3_open_v2()] interface works like [sqlite3_open()]
+** except that it acccepts two additional parameters for additional control
+** over the new database connection. The flags parameter can be
+** one of:
+**
+** <ol>
+** <li> [SQLITE_OPEN_READONLY]
+** <li> [SQLITE_OPEN_READWRITE]
+** <li> [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
+** </ol>
+**
+** The first value opens the database read-only.
+** If the database does not previously exist, an error is returned.
+** The second option opens
+** the database for reading and writing if possible, or reading only if
+** if the file is write protected. In either case the database
+** must already exist or an error is returned. The third option
+** opens the database for reading and writing and creates it if it does
+** not already exist.
+** The third options is behavior that is always used for [sqlite3_open()]
+** and [sqlite3_open16()].
+**
+** If the 3rd parameter to [sqlite3_open_v2()] is not one of the
+** combinations shown above then the behavior is undefined.
+**
+** If the filename is ":memory:", then an private
+** in-memory database is created for the connection. This in-memory
+** database will vanish when the database connection is closed. Future
+** version of SQLite might make use of additional special filenames
+** that begin with the ":" character. It is recommended that
+** when a database filename really does begin with
+** ":" that you prefix the filename with a pathname like "./" to
+** avoid ambiguity.
+**
+** If the filename is an empty string, then a private temporary
+** on-disk database will be created. This private database will be
+** automatically deleted as soon as the database connection is closed.
+**
+** The fourth parameter to sqlite3_open_v2() is the name of the
+** [sqlite3_vfs] object that defines the operating system
+** interface that the new database connection should use. If the
+** fourth parameter is a NULL pointer then the default [sqlite3_vfs]
+** object is used.
+**
+** <b>Note to windows users:</b> The encoding used for the filename argument
+** of [sqlite3_open()] and [sqlite3_open_v2()] must be UTF-8, not whatever
+** codepage is currently defined. Filenames containing international
+** characters must be converted to UTF-8 prior to passing them into
+** [sqlite3_open()] or [sqlite3_open_v2()].
+**
+** INVARIANTS:
+**
+** {F12701} The [sqlite3_open()], [sqlite3_open16()], and
+** [sqlite3_open_v2()] interfaces create a new
+** [database connection] associated with
+** the database file given in their first parameter.
+**
+** {F12702} The filename argument is interpreted as UTF-8
+** for [sqlite3_open()] and [sqlite3_open_v2()] and as UTF-16
+** in the native byte order for [sqlite3_open16()].
+**
+** {F12703} A successful invocation of [sqlite3_open()], [sqlite3_open16()],
+** or [sqlite3_open_v2()] writes a pointer to a new
+** [database connection] into *ppDb.
+**
+** {F12704} The [sqlite3_open()], [sqlite3_open16()], and
+** [sqlite3_open_v2()] interfaces return [SQLITE_OK] upon success,
+** or an appropriate [error code] on failure.
+**
+** {F12706} The default text encoding for a new database created using
+** [sqlite3_open()] or [sqlite3_open_v2()] will be UTF-8.
+**
+** {F12707} The default text encoding for a new database created using
+** [sqlite3_open16()] will be UTF-16.
+**
+** {F12709} The [sqlite3_open(F,D)] interface is equivalent to
+** [sqlite3_open_v2(F,D,G,0)] where the G parameter is
+** [SQLITE_OPEN_READWRITE]|[SQLITE_OPEN_CREATE].
+**
+** {F12711} If the G parameter to [sqlite3_open_v2(F,D,G,V)] contains the
+** bit value [SQLITE_OPEN_READONLY] then the database is opened
+** for reading only.
+**
+** {F12712} If the G parameter to [sqlite3_open_v2(F,D,G,V)] contains the
+** bit value [SQLITE_OPEN_READWRITE] then the database is opened
+** reading and writing if possible, or for reading only if the
+** file is write protected by the operating system.
+**
+** {F12713} If the G parameter to [sqlite3_open(v2(F,D,G,V)] omits the
+** bit value [SQLITE_OPEN_CREATE] and the database does not
+** previously exist, an error is returned.
+**
+** {F12714} If the G parameter to [sqlite3_open(v2(F,D,G,V)] contains the
+** bit value [SQLITE_OPEN_CREATE] and the database does not
+** previously exist, then an attempt is made to create and
+** initialize the database.
+**
+** {F12717} If the filename argument to [sqlite3_open()], [sqlite3_open16()],
+** or [sqlite3_open_v2()] is ":memory:", then an private,
+** ephemeral, in-memory database is created for the connection.
+** <todo>Is SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE required
+** in sqlite3_open_v2()?</todo>
+**
+** {F12719} If the filename is NULL or an empty string, then a private,
+** ephermeral on-disk database will be created.
+** <todo>Is SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE required
+** in sqlite3_open_v2()?</todo>
+**
+** {F12721} The [database connection] created by
+** [sqlite3_open_v2(F,D,G,V)] will use the
+** [sqlite3_vfs] object identified by the V parameter, or
+** the default [sqlite3_vfs] object is V is a NULL pointer.
+*/
+int sqlite3_open(
+ const char *filename, /* Database filename (UTF-8) */
+ sqlite3 **ppDb /* OUT: SQLite db handle */
+);
+int sqlite3_open16(
+ const void *filename, /* Database filename (UTF-16) */
+ sqlite3 **ppDb /* OUT: SQLite db handle */
+);
+int sqlite3_open_v2(
+ const char *filename, /* Database filename (UTF-8) */
+ sqlite3 **ppDb, /* OUT: SQLite db handle */
+ int flags, /* Flags */
+ const char *zVfs /* Name of VFS module to use */
+);
+
+/*
+** CAPI3REF: Error Codes And Messages {F12800}
+**
+** The sqlite3_errcode() interface returns the numeric
+** [SQLITE_OK | result code] or [SQLITE_IOERR_READ | extended result code]
+** for the most recent failed sqlite3_* API call associated
+** with [sqlite3] handle 'db'. If a prior API call failed but the
+** most recent API call succeeded, the return value from sqlite3_errcode()
+** is undefined.
+**
+** The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
+** text that describes the error, as either UTF8 or UTF16 respectively.
+** Memory to hold the error message string is managed internally.
+** The application does not need to worry with freeing the result.
+** However, the error string might be overwritten or deallocated by
+** subsequent calls to other SQLite interface functions.
+**
+** INVARIANTS:
+**
+** {F12801} The [sqlite3_errcode(D)] interface returns the numeric
+** [SQLITE_OK | result code] or
+** [SQLITE_IOERR_READ | extended result code]
+** for the most recently failed interface call associated
+** with [database connection] D.
+**
+** {F12803} The [sqlite3_errmsg(D)] and [sqlite3_errmsg16(D)]
+** interfaces return English-language text that describes
+** the error in the mostly recently failed interface call,
+** encoded as either UTF8 or UTF16 respectively.
+**
+** {F12807} The strings returned by [sqlite3_errmsg()] and [sqlite3_errmsg16()]
+** are valid until the next SQLite interface call.
+**
+** {F12808} Calls to API routines that do not return an error code
+** (example: [sqlite3_data_count()]) do not
+** change the error code or message returned by
+** [sqlite3_errcode()], [sqlite3_errmsg()], or [sqlite3_errmsg16()].
+**
+** {F12809} Interfaces that are not associated with a specific
+** [database connection] (examples:
+** [sqlite3_mprintf()] or [sqlite3_enable_shared_cache()]
+** do not change the values returned by
+** [sqlite3_errcode()], [sqlite3_errmsg()], or [sqlite3_errmsg16()].
+*/
+int sqlite3_errcode(sqlite3 *db);
+const char *sqlite3_errmsg(sqlite3*);
+const void *sqlite3_errmsg16(sqlite3*);
+
+/*
+** CAPI3REF: SQL Statement Object {F13000}
+** KEYWORDS: {prepared statement} {prepared statements}
+**
+** An instance of this object represent single SQL statements. This
+** object is variously known as a "prepared statement" or a
+** "compiled SQL statement" or simply as a "statement".
+**
+** The life of a statement object goes something like this:
+**
+** <ol>
+** <li> Create the object using [sqlite3_prepare_v2()] or a related
+** function.
+** <li> Bind values to host parameters using
+** [sqlite3_bind_blob | sqlite3_bind_* interfaces].
+** <li> Run the SQL by calling [sqlite3_step()] one or more times.
+** <li> Reset the statement using [sqlite3_reset()] then go back
+** to step 2. Do this zero or more times.
+** <li> Destroy the object using [sqlite3_finalize()].
+** </ol>
+**
+** Refer to documentation on individual methods above for additional
+** information.
+*/
+typedef struct sqlite3_stmt sqlite3_stmt;
+
+/*
+** CAPI3REF: Run-time Limits {F12760}
+**
+** This interface allows the size of various constructs to be limited
+** on a connection by connection basis. The first parameter is the
+** [database connection] whose limit is to be set or queried. The
+** second parameter is one of the [limit categories] that define a
+** class of constructs to be size limited. The third parameter is the
+** new limit for that construct. The function returns the old limit.
+**
+** If the new limit is a negative number, the limit is unchanged.
+** For the limit category of SQLITE_LIMIT_XYZ there is a hard upper
+** bound set by a compile-time C-preprocess macro named SQLITE_MAX_XYZ.
+** (The "_LIMIT_" in the name is changed to "_MAX_".)
+** Attempts to increase a limit above its hard upper bound are
+** silently truncated to the hard upper limit.
+**
+** Run time limits are intended for use in applications that manage
+** both their own internal database and also databases that are controlled
+** by untrusted external sources. An example application might be a
+** webbrowser that has its own databases for storing history and
+** separate databases controlled by javascript applications downloaded
+** off the internet. The internal databases can be given the
+** large, default limits. Databases managed by external sources can
+** be given much smaller limits designed to prevent a denial of service
+** attach. Developers might also want to use the [sqlite3_set_authorizer()]
+** interface to further control untrusted SQL. The size of the database
+** created by an untrusted script can be contained using the
+** [max_page_count] [PRAGMA].
+**
+** This interface is currently considered experimental and is subject
+** to change or removal without prior notice.
+**
+** INVARIANTS:
+**
+** {F12762} A successful call to [sqlite3_limit(D,C,V)] where V is
+** positive changes the
+** limit on the size of construct C in [database connection] D
+** to the lessor of V and the hard upper bound on the size
+** of C that is set at compile-time.
+**
+** {F12766} A successful call to [sqlite3_limit(D,C,V)] where V is negative
+** leaves the state of [database connection] D unchanged.
+**
+** {F12769} A successful call to [sqlite3_limit(D,C,V)] returns the
+** value of the limit on the size of construct C in
+** in [database connection] D as it was prior to the call.
+*/
+int sqlite3_limit(sqlite3*, int id, int newVal);
+
+/*
+** CAPI3REF: Run-Time Limit Categories {F12790}
+** KEYWORDS: {limit category} {limit categories}
+**
+** These constants define various aspects of a [database connection]
+** that can be limited in size by calls to [sqlite3_limit()].
+** The meanings of the various limits are as follows:
+**
+** <dl>
+** <dt>SQLITE_LIMIT_LENGTH</dt>
+** <dd>The maximum size of any
+** string or blob or table row.<dd>
+**
+** <dt>SQLITE_LIMIT_SQL_LENGTH</dt>
+** <dd>The maximum length of an SQL statement.</dd>
+**
+** <dt>SQLITE_LIMIT_COLUMN</dt>
+** <dd>The maximum number of columns in a table definition or in the
+** result set of a SELECT or the maximum number of columns in an index
+** or in an ORDER BY or GROUP BY clause.</dd>
+**
+** <dt>SQLITE_LIMIT_EXPR_DEPTH</dt>
+** <dd>The maximum depth of the parse tree on any expression.</dd>
+**
+** <dt>SQLITE_LIMIT_COMPOUND_SELECT</dt>
+** <dd>The maximum number of terms in a compound SELECT statement.</dd>
+**
+** <dt>SQLITE_LIMIT_VDBE_OP</dt>
+** <dd>The maximum number of instructions in a virtual machine program
+** used to implement an SQL statement.</dd>
+**
+** <dt>SQLITE_LIMIT_FUNCTION_ARG</dt>
+** <dd>The maximum number of arguments on a function.</dd>
+**
+** <dt>SQLITE_LIMIT_ATTACHED</dt>
+** <dd>The maximum number of attached databases.</dd>
+**
+** <dt>SQLITE_LIMIT_LIKE_PATTERN_LENGTH</dt>
+** <dd>The maximum length of the pattern argument to the LIKE or
+** GLOB operators.</dd>
+**
+** <dt>SQLITE_LIMIT_VARIABLE_NUMBER</dt>
+** <dd>The maximum number of variables in an SQL statement that can
+** be bound.</dd>
+** </dl>
+*/
+#define SQLITE_LIMIT_LENGTH 0
+#define SQLITE_LIMIT_SQL_LENGTH 1
+#define SQLITE_LIMIT_COLUMN 2
+#define SQLITE_LIMIT_EXPR_DEPTH 3
+#define SQLITE_LIMIT_COMPOUND_SELECT 4
+#define SQLITE_LIMIT_VDBE_OP 5
+#define SQLITE_LIMIT_FUNCTION_ARG 6
+#define SQLITE_LIMIT_ATTACHED 7
+#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8
+#define SQLITE_LIMIT_VARIABLE_NUMBER 9
+
+/*
+** CAPI3REF: Compiling An SQL Statement {F13010}
+**
+** To execute an SQL query, it must first be compiled into a byte-code
+** program using one of these routines.
+**
+** The first argument "db" is an [database connection]
+** obtained from a prior call to [sqlite3_open()], [sqlite3_open_v2()]
+** or [sqlite3_open16()].
+** The second argument "zSql" is the statement to be compiled, encoded
+** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2()
+** interfaces uses UTF-8 and sqlite3_prepare16() and sqlite3_prepare16_v2()
+** use UTF-16. {END}
+**
+** If the nByte argument is less
+** than zero, then zSql is read up to the first zero terminator.
+** If nByte is non-negative, then it is the maximum number of
+** bytes read from zSql. When nByte is non-negative, the
+** zSql string ends at either the first '\000' or '\u0000' character or
+** the nByte-th byte, whichever comes first. If the caller knows
+** that the supplied string is nul-terminated, then there is a small
+** performance advantage to be had by passing an nByte parameter that
+** is equal to the number of bytes in the input string <i>including</i>
+** the nul-terminator bytes.{END}
+**
+** *pzTail is made to point to the first byte past the end of the
+** first SQL statement in zSql. These routines only compiles the first
+** statement in zSql, so *pzTail is left pointing to what remains
+** uncompiled.
+**
+** *ppStmt is left pointing to a compiled [prepared statement] that can be
+** executed using [sqlite3_step()]. Or if there is an error, *ppStmt is
+** set to NULL. If the input text contains no SQL (if the input
+** is and empty string or a comment) then *ppStmt is set to NULL.
+** {U13018} The calling procedure is responsible for deleting the
+** compiled SQL statement
+** using [sqlite3_finalize()] after it has finished with it.
+**
+** On success, [SQLITE_OK] is returned. Otherwise an
+** [error code] is returned.
+**
+** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
+** recommended for all new programs. The two older interfaces are retained
+** for backwards compatibility, but their use is discouraged.
+** In the "v2" interfaces, the prepared statement
+** that is returned (the [sqlite3_stmt] object) contains a copy of the
+** original SQL text. {END} This causes the [sqlite3_step()] interface to
+** behave a differently in two ways:
+**
+** <ol>
+** <li>
+** If the database schema changes, instead of returning [SQLITE_SCHEMA] as it
+** always used to do, [sqlite3_step()] will automatically recompile the SQL
+** statement and try to run it again. If the schema has changed in
+** a way that makes the statement no longer valid, [sqlite3_step()] will still
+** return [SQLITE_SCHEMA]. But unlike the legacy behavior,
+** [SQLITE_SCHEMA] is now a fatal error. Calling
+** [sqlite3_prepare_v2()] again will not make the
+** error go away. Note: use [sqlite3_errmsg()] to find the text
+** of the parsing error that results in an [SQLITE_SCHEMA] return. {END}
+** </li>
+**
+** <li>
+** When an error occurs,
+** [sqlite3_step()] will return one of the detailed
+** [error codes] or [extended error codes].
+** The legacy behavior was that [sqlite3_step()] would only return a generic
+** [SQLITE_ERROR] result code and you would have to make a second call to
+** [sqlite3_reset()] in order to find the underlying cause of the problem.
+** With the "v2" prepare interfaces, the underlying reason for the error is
+** returned immediately.
+** </li>
+** </ol>
+**
+** INVARIANTS:
+**
+** {F13011} The [sqlite3_prepare(db,zSql,...)] and
+** [sqlite3_prepare_v2(db,zSql,...)] interfaces interpret the
+** text in their zSql parameter as UTF-8.
+**
+** {F13012} The [sqlite3_prepare16(db,zSql,...)] and
+** [sqlite3_prepare16_v2(db,zSql,...)] interfaces interpret the
+** text in their zSql parameter as UTF-16 in the native byte order.
+**
+** {F13013} If the nByte argument to [sqlite3_prepare_v2(db,zSql,nByte,...)]
+** and its variants is less than zero, then SQL text is
+** read from zSql is read up to the first zero terminator.
+**
+** {F13014} If the nByte argument to [sqlite3_prepare_v2(db,zSql,nByte,...)]
+** and its variants is non-negative, then at most nBytes bytes
+** SQL text is read from zSql.
+**
+** {F13015} In [sqlite3_prepare_v2(db,zSql,N,P,pzTail)] and its variants
+** if the zSql input text contains more than one SQL statement
+** and pzTail is not NULL, then *pzTail is made to point to the
+** first byte past the end of the first SQL statement in zSql.
+** <todo>What does *pzTail point to if there is one statement?</todo>
+**
+** {F13016} A successful call to [sqlite3_prepare_v2(db,zSql,N,ppStmt,...)]
+** or one of its variants writes into *ppStmt a pointer to a new
+** [prepared statement] or a pointer to NULL
+** if zSql contains nothing other than whitespace or comments.
+**
+** {F13019} The [sqlite3_prepare_v2()] interface and its variants return
+** [SQLITE_OK] or an appropriate [error code] upon failure.
+**
+** {F13021} Before [sqlite3_prepare(db,zSql,nByte,ppStmt,pzTail)] or its
+** variants returns an error (any value other than [SQLITE_OK])
+** it first sets *ppStmt to NULL.
+*/
+int sqlite3_prepare(
+ sqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL statement, UTF-8 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const char **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+int sqlite3_prepare_v2(
+ sqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL statement, UTF-8 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const char **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+int sqlite3_prepare16(
+ sqlite3 *db, /* Database handle */
+ const void *zSql, /* SQL statement, UTF-16 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const void **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+int sqlite3_prepare16_v2(
+ sqlite3 *db, /* Database handle */
+ const void *zSql, /* SQL statement, UTF-16 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const void **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+
+/*
+** CAPIREF: Retrieving Statement SQL {F13100}
+**
+** This intereface can be used to retrieve a saved copy of the original
+** SQL text used to create a [prepared statement].
+**
+** INVARIANTS:
+**
+** {F13101} If the [prepared statement] passed as
+** the an argument to [sqlite3_sql()] was compiled
+** compiled using either [sqlite3_prepare_v2()] or
+** [sqlite3_prepare16_v2()],
+** then [sqlite3_sql()] function returns a pointer to a
+** zero-terminated string containing a UTF-8 rendering
+** of the original SQL statement.
+**
+** {F13102} If the [prepared statement] passed as
+** the an argument to [sqlite3_sql()] was compiled
+** compiled using either [sqlite3_prepare()] or
+** [sqlite3_prepare16()],
+** then [sqlite3_sql()] function returns a NULL pointer.
+**
+** {F13103} The string returned by [sqlite3_sql(S)] is valid until the
+** [prepared statement] S is deleted using [sqlite3_finalize(S)].
+*/
+const char *sqlite3_sql(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Dynamically Typed Value Object {F15000}
+** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value}
+**
+** SQLite uses the sqlite3_value object to represent all values
+** that can be stored in a database table.
+** SQLite uses dynamic typing for the values it stores.
+** Values stored in sqlite3_value objects can be
+** be integers, floating point values, strings, BLOBs, or NULL.
+**
+** An sqlite3_value object may be either "protected" or "unprotected".
+** Some interfaces require a protected sqlite3_value. Other interfaces
+** will accept either a protected or an unprotected sqlite3_value.
+** Every interface that accepts sqlite3_value arguments specifies
+** whether or not it requires a protected sqlite3_value.
+**
+** The terms "protected" and "unprotected" refer to whether or not
+** a mutex is held. A internal mutex is held for a protected
+** sqlite3_value object but no mutex is held for an unprotected
+** sqlite3_value object. If SQLite is compiled to be single-threaded
+** (with SQLITE_THREADSAFE=0 and with [sqlite3_threadsafe()] returning 0)
+** then there is no distinction between
+** protected and unprotected sqlite3_value objects and they can be
+** used interchangable. However, for maximum code portability it
+** is recommended that applications make the distinction between
+** between protected and unprotected sqlite3_value objects even if
+** they are single threaded.
+**
+** The sqlite3_value objects that are passed as parameters into the
+** implementation of application-defined SQL functions are protected.
+** The sqlite3_value object returned by
+** [sqlite3_column_value()] is unprotected.
+** Unprotected sqlite3_value objects may only be used with
+** [sqlite3_result_value()] and [sqlite3_bind_value()]. All other
+** interfaces that use sqlite3_value require protected sqlite3_value objects.
+*/
+typedef struct Mem sqlite3_value;
+
+/*
+** CAPI3REF: SQL Function Context Object {F16001}
+**
+** The context in which an SQL function executes is stored in an
+** sqlite3_context object. A pointer to an sqlite3_context
+** object is always first parameter to application-defined SQL functions.
+*/
+typedef struct sqlite3_context sqlite3_context;
+
+/*
+** CAPI3REF: Binding Values To Prepared Statements {F13500}
+**
+** In the SQL strings input to [sqlite3_prepare_v2()] and its
+** variants, literals may be replace by a parameter in one
+** of these forms:
+**
+** <ul>
+** <li> ?
+** <li> ?NNN
+** <li> :VVV
+** <li> @VVV
+** <li> $VVV
+** </ul>
+**
+** In the parameter forms shown above NNN is an integer literal,
+** VVV alpha-numeric parameter name.
+** The values of these parameters (also called "host parameter names"
+** or "SQL parameters")
+** can be set using the sqlite3_bind_*() routines defined here.
+**
+** The first argument to the sqlite3_bind_*() routines always
+** is a pointer to the [sqlite3_stmt] object returned from
+** [sqlite3_prepare_v2()] or its variants. The second
+** argument is the index of the parameter to be set. The
+** first parameter has an index of 1. When the same named
+** parameter is used more than once, second and subsequent
+** occurrences have the same index as the first occurrence.
+** The index for named parameters can be looked up using the
+** [sqlite3_bind_parameter_name()] API if desired. The index
+** for "?NNN" parameters is the value of NNN.
+** The NNN value must be between 1 and the compile-time
+** parameter SQLITE_MAX_VARIABLE_NUMBER (default value: 999).
+**
+** The third argument is the value to bind to the parameter.
+**
+** In those
+** routines that have a fourth argument, its value is the number of bytes
+** in the parameter. To be clear: the value is the number of <u>bytes</u>
+** in the value, not the number of characters.
+** If the fourth parameter is negative, the length of the string is
+** number of bytes up to the first zero terminator.
+**
+** The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and
+** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or
+** string after SQLite has finished with it. If the fifth argument is
+** the special value [SQLITE_STATIC], then SQLite assumes that the
+** information is in static, unmanaged space and does not need to be freed.
+** If the fifth argument has the value [SQLITE_TRANSIENT], then
+** SQLite makes its own private copy of the data immediately, before
+** the sqlite3_bind_*() routine returns.
+**
+** The sqlite3_bind_zeroblob() routine binds a BLOB of length N that
+** is filled with zeros. A zeroblob uses a fixed amount of memory
+** (just an integer to hold it size) while it is being processed.
+** Zeroblobs are intended to serve as place-holders for BLOBs whose
+** content is later written using
+** [sqlite3_blob_open | increment BLOB I/O] routines. A negative
+** value for the zeroblob results in a zero-length BLOB.
+**
+** The sqlite3_bind_*() routines must be called after
+** [sqlite3_prepare_v2()] (and its variants) or [sqlite3_reset()] and
+** before [sqlite3_step()].
+** Bindings are not cleared by the [sqlite3_reset()] routine.
+** Unbound parameters are interpreted as NULL.
+**
+** These routines return [SQLITE_OK] on success or an error code if
+** anything goes wrong. [SQLITE_RANGE] is returned if the parameter
+** index is out of range. [SQLITE_NOMEM] is returned if malloc fails.
+** [SQLITE_MISUSE] might be returned if these routines are called on a
+** virtual machine that is the wrong state or which has already been finalized.
+** Detection of misuse is unreliable. Applications should not depend
+** on SQLITE_MISUSE returns. SQLITE_MISUSE is intended to indicate a
+** a logic error in the application. Future versions of SQLite might
+** panic rather than return SQLITE_MISUSE.
+**
+** See also: [sqlite3_bind_parameter_count()],
+** [sqlite3_bind_parameter_name()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13506} The [sqlite3_prepare | SQL statement compiler] recognizes
+** tokens of the forms "?", "?NNN", "$VVV", ":VVV", and "@VVV"
+** as SQL parameters, where NNN is any sequence of one or more
+** digits and where VVV is any sequence of one or more
+** alphanumeric characters or "::" optionally followed by
+** a string containing no spaces and contained within parentheses.
+**
+** {F13509} The initial value of an SQL parameter is NULL.
+**
+** {F13512} The index of an "?" SQL parameter is one larger than the
+** largest index of SQL parameter to the left, or 1 if
+** the "?" is the leftmost SQL parameter.
+**
+** {F13515} The index of an "?NNN" SQL parameter is the integer NNN.
+**
+** {F13518} The index of an ":VVV", "$VVV", or "@VVV" SQL parameter is
+** the same as the index of leftmost occurances of the same
+** parameter, or one more than the largest index over all
+** parameters to the left if this is the first occurrance
+** of this parameter, or 1 if this is the leftmost parameter.
+**
+** {F13521} The [sqlite3_prepare | SQL statement compiler] fail with
+** an [SQLITE_RANGE] error if the index of an SQL parameter
+** is less than 1 or greater than SQLITE_MAX_VARIABLE_NUMBER.
+**
+** {F13524} Calls to [sqlite3_bind_text | sqlite3_bind(S,N,V,...)]
+** associate the value V with all SQL parameters having an
+** index of N in the [prepared statement] S.
+**
+** {F13527} Calls to [sqlite3_bind_text | sqlite3_bind(S,N,...)]
+** override prior calls with the same values of S and N.
+**
+** {F13530} Bindings established by [sqlite3_bind_text | sqlite3_bind(S,...)]
+** persist across calls to [sqlite3_reset(S)].
+**
+** {F13533} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] SQLite binds the first L
+** bytes of the blob or string pointed to by V, when L
+** is non-negative.
+**
+** {F13536} In calls to [sqlite3_bind_text(S,N,V,L,D)] or
+** [sqlite3_bind_text16(S,N,V,L,D)] SQLite binds characters
+** from V through the first zero character when L is negative.
+**
+** {F13539} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] when D is the special
+** constant [SQLITE_STATIC], SQLite assumes that the value V
+** is held in static unmanaged space that will not change
+** during the lifetime of the binding.
+**
+** {F13542} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] when D is the special
+** constant [SQLITE_TRANSIENT], the routine makes a
+** private copy of V value before it returns.
+**
+** {F13545} In calls to [sqlite3_bind_blob(S,N,V,L,D)],
+** [sqlite3_bind_text(S,N,V,L,D)], or
+** [sqlite3_bind_text16(S,N,V,L,D)] when D is a pointer to
+** a function, SQLite invokes that function to destroy the
+** V value after it has finished using the V value.
+**
+** {F13548} In calls to [sqlite3_bind_zeroblob(S,N,V,L)] the value bound
+** is a blob of L bytes, or a zero-length blob if L is negative.
+**
+** {F13551} In calls to [sqlite3_bind_value(S,N,V)] the V argument may
+** be either a [protected sqlite3_value] object or an
+** [unprotected sqlite3_value] object.
+*/
+int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
+int sqlite3_bind_double(sqlite3_stmt*, int, double);
+int sqlite3_bind_int(sqlite3_stmt*, int, int);
+int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
+int sqlite3_bind_null(sqlite3_stmt*, int);
+int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
+int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
+int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
+int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
+
+/*
+** CAPI3REF: Number Of SQL Parameters {F13600}
+**
+** This routine can be used to find the number of SQL parameters
+** in a prepared statement. SQL parameters are tokens of the
+** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as
+** place-holders for values that are [sqlite3_bind_blob | bound]
+** to the parameters at a later time.
+**
+** This routine actually returns the index of the largest parameter.
+** For all forms except ?NNN, this will correspond to the number of
+** unique parameters. If parameters of the ?NNN are used, there may
+** be gaps in the list.
+**
+** See also: [sqlite3_bind_blob|sqlite3_bind()],
+** [sqlite3_bind_parameter_name()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13601} The [sqlite3_bind_parameter_count(S)] interface returns
+** the largest index of all SQL parameters in the
+** [prepared statement] S, or 0 if S
+** contains no SQL parameters.
+*/
+int sqlite3_bind_parameter_count(sqlite3_stmt*);
+
+/*
+** CAPI3REF: Name Of A Host Parameter {F13620}
+**
+** This routine returns a pointer to the name of the n-th
+** SQL parameter in a [prepared statement].
+** SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA"
+** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA"
+** respectively.
+** In other words, the initial ":" or "$" or "@" or "?"
+** is included as part of the name.
+** Parameters of the form "?" without a following integer have no name.
+**
+** The first host parameter has an index of 1, not 0.
+**
+** If the value n is out of range or if the n-th parameter is
+** nameless, then NULL is returned. The returned string is
+** always in the UTF-8 encoding even if the named parameter was
+** originally specified as UTF-16 in [sqlite3_prepare16()] or
+** [sqlite3_prepare16_v2()].
+**
+** See also: [sqlite3_bind_blob|sqlite3_bind()],
+** [sqlite3_bind_parameter_count()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13621} The [sqlite3_bind_parameter_name(S,N)] interface returns
+** a UTF-8 rendering of the name of the SQL parameter in
+** [prepared statement] S having index N, or
+** NULL if there is no SQL parameter with index N or if the
+** parameter with index N is an anonymous parameter "?".
+*/
+const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int);
+
+/*
+** CAPI3REF: Index Of A Parameter With A Given Name {F13640}
+**
+** Return the index of an SQL parameter given its name. The
+** index value returned is suitable for use as the second
+** parameter to [sqlite3_bind_blob|sqlite3_bind()]. A zero
+** is returned if no matching parameter is found. The parameter
+** name must be given in UTF-8 even if the original statement
+** was prepared from UTF-16 text using [sqlite3_prepare16_v2()].
+**
+** See also: [sqlite3_bind_blob|sqlite3_bind()],
+** [sqlite3_bind_parameter_count()], and
+** [sqlite3_bind_parameter_index()].
+**
+** INVARIANTS:
+**
+** {F13641} The [sqlite3_bind_parameter_index(S,N)] interface returns
+** the index of SQL parameter in [prepared statement]
+** S whose name matches the UTF-8 string N, or 0 if there is
+** no match.
+*/
+int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+
+/*
+** CAPI3REF: Reset All Bindings On A Prepared Statement {F13660}
+**
+** Contrary to the intuition of many, [sqlite3_reset()] does not
+** reset the [sqlite3_bind_blob | bindings] on a
+** [prepared statement]. Use this routine to
+** reset all host parameters to NULL.
+**
+** INVARIANTS:
+**
+** {F13661} The [sqlite3_clear_bindings(S)] interface resets all
+** SQL parameter bindings in [prepared statement] S
+** back to NULL.
+*/
+int sqlite3_clear_bindings(sqlite3_stmt*);
+
+/*
+** CAPI3REF: Number Of Columns In A Result Set {F13710}
+**
+** Return the number of columns in the result set returned by the
+** [prepared statement]. This routine returns 0
+** if pStmt is an SQL statement that does not return data (for
+** example an UPDATE).
+**
+** INVARIANTS:
+**
+** {F13711} The [sqlite3_column_count(S)] interface returns the number of
+** columns in the result set generated by the
+** [prepared statement] S, or 0 if S does not generate
+** a result set.
+*/
+int sqlite3_column_count(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Column Names In A Result Set {F13720}
+**
+** These routines return the name assigned to a particular column
+** in the result set of a SELECT statement. The sqlite3_column_name()
+** interface returns a pointer to a zero-terminated UTF8 string
+** and sqlite3_column_name16() returns a pointer to a zero-terminated
+** UTF16 string. The first parameter is the
+** [prepared statement] that implements the SELECT statement.
+** The second parameter is the column number. The left-most column is
+** number 0.
+**
+** The returned string pointer is valid until either the
+** [prepared statement] is destroyed by [sqlite3_finalize()]
+** or until the next call sqlite3_column_name() or sqlite3_column_name16()
+** on the same column.
+**
+** If sqlite3_malloc() fails during the processing of either routine
+** (for example during a conversion from UTF-8 to UTF-16) then a
+** NULL pointer is returned.
+**
+** The name of a result column is the value of the "AS" clause for
+** that column, if there is an AS clause. If there is no AS clause
+** then the name of the column is unspecified and may change from
+** one release of SQLite to the next.
+**
+** INVARIANTS:
+**
+** {F13721} A successful invocation of the [sqlite3_column_name(S,N)]
+** interface returns the name
+** of the Nth column (where 0 is the left-most column) for the
+** result set of [prepared statement] S as a
+** zero-terminated UTF-8 string.
+**
+** {F13723} A successful invocation of the [sqlite3_column_name16(S,N)]
+** interface returns the name
+** of the Nth column (where 0 is the left-most column) for the
+** result set of [prepared statement] S as a
+** zero-terminated UTF-16 string in the native byte order.
+**
+** {F13724} The [sqlite3_column_name()] and [sqlite3_column_name16()]
+** interfaces return a NULL pointer if they are unable to
+** allocate memory memory to hold there normal return strings.
+**
+** {F13725} If the N parameter to [sqlite3_column_name(S,N)] or
+** [sqlite3_column_name16(S,N)] is out of range, then the
+** interfaces returns a NULL pointer.
+**
+** {F13726} The strings returned by [sqlite3_column_name(S,N)] and
+** [sqlite3_column_name16(S,N)] are valid until the next
+** call to either routine with the same S and N parameters
+** or until [sqlite3_finalize(S)] is called.
+**
+** {F13727} When a result column of a [SELECT] statement contains
+** an AS clause, the name of that column is the indentifier
+** to the right of the AS keyword.
+*/
+const char *sqlite3_column_name(sqlite3_stmt*, int N);
+const void *sqlite3_column_name16(sqlite3_stmt*, int N);
+
+/*
+** CAPI3REF: Source Of Data In A Query Result {F13740}
+**
+** These routines provide a means to determine what column of what
+** table in which database a result of a SELECT statement comes from.
+** The name of the database or table or column can be returned as
+** either a UTF8 or UTF16 string. The _database_ routines return
+** the database name, the _table_ routines return the table name, and
+** the origin_ routines return the column name.
+** The returned string is valid until
+** the [prepared statement] is destroyed using
+** [sqlite3_finalize()] or until the same information is requested
+** again in a different encoding.
+**
+** The names returned are the original un-aliased names of the
+** database, table, and column.
+**
+** The first argument to the following calls is a [prepared statement].
+** These functions return information about the Nth column returned by
+** the statement, where N is the second function argument.
+**
+** If the Nth column returned by the statement is an expression
+** or subquery and is not a column value, then all of these functions
+** return NULL. These routine might also return NULL if a memory
+** allocation error occurs. Otherwise, they return the
+** name of the attached database, table and column that query result
+** column was extracted from.
+**
+** As with all other SQLite APIs, those postfixed with "16" return
+** UTF-16 encoded strings, the other functions return UTF-8. {END}
+**
+** These APIs are only available if the library was compiled with the
+** SQLITE_ENABLE_COLUMN_METADATA preprocessor symbol defined.
+**
+** {U13751}
+** If two or more threads call one or more of these routines against the same
+** prepared statement and column at the same time then the results are
+** undefined.
+**
+** INVARIANTS:
+**
+** {F13741} The [sqlite3_column_database_name(S,N)] interface returns either
+** the UTF-8 zero-terminated name of the database from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13742} The [sqlite3_column_database_name16(S,N)] interface returns either
+** the UTF-16 native byte order
+** zero-terminated name of the database from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13743} The [sqlite3_column_table_name(S,N)] interface returns either
+** the UTF-8 zero-terminated name of the table from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13744} The [sqlite3_column_table_name16(S,N)] interface returns either
+** the UTF-16 native byte order
+** zero-terminated name of the table from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13745} The [sqlite3_column_origin_name(S,N)] interface returns either
+** the UTF-8 zero-terminated name of the table column from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13746} The [sqlite3_column_origin_name16(S,N)] interface returns either
+** the UTF-16 native byte order
+** zero-terminated name of the table column from which the
+** Nth result column of [prepared statement] S
+** is extracted, or NULL if the the Nth column of S is a
+** general expression or if unable to allocate memory
+** to store the name.
+**
+** {F13748} The return values from
+** [sqlite3_column_database_name|column metadata interfaces]
+** are valid
+** for the lifetime of the [prepared statement]
+** or until the encoding is changed by another metadata
+** interface call for the same prepared statement and column.
+**
+** LIMITATIONS:
+**
+** {U13751} If two or more threads call one or more
+** [sqlite3_column_database_name|column metadata interfaces]
+** the same [prepared statement] and result column
+** at the same time then the results are undefined.
+*/
+const char *sqlite3_column_database_name(sqlite3_stmt*,int);
+const void *sqlite3_column_database_name16(sqlite3_stmt*,int);
+const char *sqlite3_column_table_name(sqlite3_stmt*,int);
+const void *sqlite3_column_table_name16(sqlite3_stmt*,int);
+const char *sqlite3_column_origin_name(sqlite3_stmt*,int);
+const void *sqlite3_column_origin_name16(sqlite3_stmt*,int);
+
+/*
+** CAPI3REF: Declared Datatype Of A Query Result {F13760}
+**
+** The first parameter is a [prepared statement].
+** If this statement is a SELECT statement and the Nth column of the
+** returned result set of that SELECT is a table column (not an
+** expression or subquery) then the declared type of the table
+** column is returned. If the Nth column of the result set is an
+** expression or subquery, then a NULL pointer is returned.
+** The returned string is always UTF-8 encoded. {END}
+** For example, in the database schema:
+**
+** CREATE TABLE t1(c1 VARIANT);
+**
+** And the following statement compiled:
+**
+** SELECT c1 + 1, c1 FROM t1;
+**
+** Then this routine would return the string "VARIANT" for the second
+** result column (i==1), and a NULL pointer for the first result column
+** (i==0).
+**
+** SQLite uses dynamic run-time typing. So just because a column
+** is declared to contain a particular type does not mean that the
+** data stored in that column is of the declared type. SQLite is
+** strongly typed, but the typing is dynamic not static. Type
+** is associated with individual values, not with the containers
+** used to hold those values.
+**
+** INVARIANTS:
+**
+** {F13761} A successful call to [sqlite3_column_decltype(S,N)]
+** returns a zero-terminated UTF-8 string containing the
+** the declared datatype of the table column that appears
+** as the Nth column (numbered from 0) of the result set to the
+** [prepared statement] S.
+**
+** {F13762} A successful call to [sqlite3_column_decltype16(S,N)]
+** returns a zero-terminated UTF-16 native byte order string
+** containing the declared datatype of the table column that appears
+** as the Nth column (numbered from 0) of the result set to the
+** [prepared statement] S.
+**
+** {F13763} If N is less than 0 or N is greater than or equal to
+** the number of columns in [prepared statement] S
+** or if the Nth column of S is an expression or subquery rather
+** than a table column or if a memory allocation failure
+** occurs during encoding conversions, then
+** calls to [sqlite3_column_decltype(S,N)] or
+** [sqlite3_column_decltype16(S,N)] return NULL.
+*/
+const char *sqlite3_column_decltype(sqlite3_stmt*,int);
+const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
+
+/*
+** CAPI3REF: Evaluate An SQL Statement {F13200}
+**
+** After an [prepared statement] has been prepared with a call
+** to either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or to one of
+** the legacy interfaces [sqlite3_prepare()] or [sqlite3_prepare16()],
+** then this function must be called one or more times to evaluate the
+** statement.
+**
+** The details of the behavior of this sqlite3_step() interface depend
+** on whether the statement was prepared using the newer "v2" interface
+** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy
+** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the
+** new "v2" interface is recommended for new applications but the legacy
+** interface will continue to be supported.
+**
+** In the legacy interface, the return value will be either [SQLITE_BUSY],
+** [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE].
+** With the "v2" interface, any of the other [SQLITE_OK | result code]
+** or [SQLITE_IOERR_READ | extended result code] might be returned as
+** well.
+**
+** [SQLITE_BUSY] means that the database engine was unable to acquire the
+** database locks it needs to do its job. If the statement is a COMMIT
+** or occurs outside of an explicit transaction, then you can retry the
+** statement. If the statement is not a COMMIT and occurs within a
+** explicit transaction then you should rollback the transaction before
+** continuing.
+**
+** [SQLITE_DONE] means that the statement has finished executing
+** successfully. sqlite3_step() should not be called again on this virtual
+** machine without first calling [sqlite3_reset()] to reset the virtual
+** machine back to its initial state.
+**
+** If the SQL statement being executed returns any data, then
+** [SQLITE_ROW] is returned each time a new row of data is ready
+** for processing by the caller. The values may be accessed using
+** the [sqlite3_column_int | column access functions].
+** sqlite3_step() is called again to retrieve the next row of data.
+**
+** [SQLITE_ERROR] means that a run-time error (such as a constraint
+** violation) has occurred. sqlite3_step() should not be called again on
+** the VM. More information may be found by calling [sqlite3_errmsg()].
+** With the legacy interface, a more specific error code (example:
+** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth)
+** can be obtained by calling [sqlite3_reset()] on the
+** [prepared statement]. In the "v2" interface,
+** the more specific error code is returned directly by sqlite3_step().
+**
+** [SQLITE_MISUSE] means that the this routine was called inappropriately.
+** Perhaps it was called on a [prepared statement] that has
+** already been [sqlite3_finalize | finalized] or on one that had
+** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could
+** be the case that the same database connection is being used by two or
+** more threads at the same moment in time.
+**
+** <b>Goofy Interface Alert:</b>
+** In the legacy interface,
+** the sqlite3_step() API always returns a generic error code,
+** [SQLITE_ERROR], following any error other than [SQLITE_BUSY]
+** and [SQLITE_MISUSE]. You must call [sqlite3_reset()] or
+** [sqlite3_finalize()] in order to find one of the specific
+** [error codes] that better describes the error.
+** We admit that this is a goofy design. The problem has been fixed
+** with the "v2" interface. If you prepare all of your SQL statements
+** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead
+** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()], then the
+** more specific [error codes] are returned directly
+** by sqlite3_step(). The use of the "v2" interface is recommended.
+**
+** INVARIANTS:
+**
+** {F13202} If [prepared statement] S is ready to be
+** run, then [sqlite3_step(S)] advances that prepared statement
+** until to completion or until it is ready to return another
+** row of the result set or an interrupt or run-time error occurs.
+**
+** {F15304} When a call to [sqlite3_step(S)] causes the
+** [prepared statement] S to run to completion,
+** the function returns [SQLITE_DONE].
+**
+** {F15306} When a call to [sqlite3_step(S)] stops because it is ready
+** to return another row of the result set, it returns
+** [SQLITE_ROW].
+**
+** {F15308} If a call to [sqlite3_step(S)] encounters an
+** [sqlite3_interrupt|interrupt] or a run-time error,
+** it returns an appropraite error code that is not one of
+** [SQLITE_OK], [SQLITE_ROW], or [SQLITE_DONE].
+**
+** {F15310} If an [sqlite3_interrupt|interrupt] or run-time error
+** occurs during a call to [sqlite3_step(S)]
+** for a [prepared statement] S created using
+** legacy interfaces [sqlite3_prepare()] or
+** [sqlite3_prepare16()] then the function returns either
+** [SQLITE_ERROR], [SQLITE_BUSY], or [SQLITE_MISUSE].
+*/
+int sqlite3_step(sqlite3_stmt*);
+
+/*
+** CAPI3REF: Number of columns in a result set {F13770}
+**
+** Return the number of values in the current row of the result set.
+**
+** INVARIANTS:
+**
+** {F13771} After a call to [sqlite3_step(S)] that returns
+** [SQLITE_ROW], the [sqlite3_data_count(S)] routine
+** will return the same value as the
+** [sqlite3_column_count(S)] function.
+**
+** {F13772} After [sqlite3_step(S)] has returned any value other than
+** [SQLITE_ROW] or before [sqlite3_step(S)] has been
+** called on the [prepared statement] for
+** the first time since it was [sqlite3_prepare|prepared]
+** or [sqlite3_reset|reset], the [sqlite3_data_count(S)]
+** routine returns zero.
+*/
+int sqlite3_data_count(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Fundamental Datatypes {F10265}
+** KEYWORDS: SQLITE_TEXT
+**
+** {F10266}Every value in SQLite has one of five fundamental datatypes:
+**
+** <ul>
+** <li> 64-bit signed integer
+** <li> 64-bit IEEE floating point number
+** <li> string
+** <li> BLOB
+** <li> NULL
+** </ul> {END}
+**
+** These constants are codes for each of those types.
+**
+** Note that the SQLITE_TEXT constant was also used in SQLite version 2
+** for a completely different meaning. Software that links against both
+** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT not
+** SQLITE_TEXT.
+*/
+#define SQLITE_INTEGER 1
+#define SQLITE_FLOAT 2
+#define SQLITE_BLOB 4
+#define SQLITE_NULL 5
+#ifdef SQLITE_TEXT
+# undef SQLITE_TEXT
+#else
+# define SQLITE_TEXT 3
+#endif
+#define SQLITE3_TEXT 3
+
+/*
+** CAPI3REF: Results Values From A Query {F13800}
+**
+** These routines form the "result set query" interface.
+**
+** These routines return information about
+** a single column of the current result row of a query. In every
+** case the first argument is a pointer to the
+** [prepared statement] that is being
+** evaluated (the [sqlite3_stmt*] that was returned from
+** [sqlite3_prepare_v2()] or one of its variants) and
+** the second argument is the index of the column for which information
+** should be returned. The left-most column of the result set
+** has an index of 0.
+**
+** If the SQL statement is not currently point to a valid row, or if the
+** the column index is out of range, the result is undefined.
+** These routines may only be called when the most recent call to
+** [sqlite3_step()] has returned [SQLITE_ROW] and neither
+** [sqlite3_reset()] nor [sqlite3_finalize()] has been call subsequently.
+** If any of these routines are called after [sqlite3_reset()] or
+** [sqlite3_finalize()] or after [sqlite3_step()] has returned
+** something other than [SQLITE_ROW], the results are undefined.
+** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()]
+** are called from a different thread while any of these routines
+** are pending, then the results are undefined.
+**
+** The sqlite3_column_type() routine returns
+** [SQLITE_INTEGER | datatype code] for the initial data type
+** of the result column. The returned value is one of [SQLITE_INTEGER],
+** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value
+** returned by sqlite3_column_type() is only meaningful if no type
+** conversions have occurred as described below. After a type conversion,
+** the value returned by sqlite3_column_type() is undefined. Future
+** versions of SQLite may change the behavior of sqlite3_column_type()
+** following a type conversion.
+**
+** If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes()
+** routine returns the number of bytes in that BLOB or string.
+** If the result is a UTF-16 string, then sqlite3_column_bytes() converts
+** the string to UTF-8 and then returns the number of bytes.
+** If the result is a numeric value then sqlite3_column_bytes() uses
+** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns
+** the number of bytes in that string.
+** The value returned does not include the zero terminator at the end
+** of the string. For clarity: the value returned is the number of
+** bytes in the string, not the number of characters.
+**
+** Strings returned by sqlite3_column_text() and sqlite3_column_text16(),
+** even empty strings, are always zero terminated. The return
+** value from sqlite3_column_blob() for a zero-length blob is an arbitrary
+** pointer, possibly even a NULL pointer.
+**
+** The sqlite3_column_bytes16() routine is similar to sqlite3_column_bytes()
+** but leaves the result in UTF-16 in native byte order instead of UTF-8.
+** The zero terminator is not included in this count.
+**
+** The object returned by [sqlite3_column_value()] is an
+** [unprotected sqlite3_value] object. An unprotected sqlite3_value object
+** may only be used with [sqlite3_bind_value()] and [sqlite3_result_value()].
+** If the [unprotected sqlite3_value] object returned by
+** [sqlite3_column_value()] is used in any other way, including calls
+** to routines like
+** [sqlite3_value_int()], [sqlite3_value_text()], or [sqlite3_value_bytes()],
+** then the behavior is undefined.
+**
+** These routines attempt to convert the value where appropriate. For
+** example, if the internal representation is FLOAT and a text result
+** is requested, [sqlite3_snprintf()] is used internally to do the conversion
+** automatically. The following table details the conversions that
+** are applied:
+**
+** <blockquote>
+** <table border="1">
+** <tr><th> Internal<br>Type <th> Requested<br>Type <th> Conversion
+**
+** <tr><td> NULL <td> INTEGER <td> Result is 0
+** <tr><td> NULL <td> FLOAT <td> Result is 0.0
+** <tr><td> NULL <td> TEXT <td> Result is NULL pointer
+** <tr><td> NULL <td> BLOB <td> Result is NULL pointer
+** <tr><td> INTEGER <td> FLOAT <td> Convert from integer to float
+** <tr><td> INTEGER <td> TEXT <td> ASCII rendering of the integer
+** <tr><td> INTEGER <td> BLOB <td> Same as for INTEGER->TEXT
+** <tr><td> FLOAT <td> INTEGER <td> Convert from float to integer
+** <tr><td> FLOAT <td> TEXT <td> ASCII rendering of the float
+** <tr><td> FLOAT <td> BLOB <td> Same as FLOAT->TEXT
+** <tr><td> TEXT <td> INTEGER <td> Use atoi()
+** <tr><td> TEXT <td> FLOAT <td> Use atof()
+** <tr><td> TEXT <td> BLOB <td> No change
+** <tr><td> BLOB <td> INTEGER <td> Convert to TEXT then use atoi()
+** <tr><td> BLOB <td> FLOAT <td> Convert to TEXT then use atof()
+** <tr><td> BLOB <td> TEXT <td> Add a zero terminator if needed
+** </table>
+** </blockquote>
+**
+** The table above makes reference to standard C library functions atoi()
+** and atof(). SQLite does not really use these functions. It has its
+** on equavalent internal routines. The atoi() and atof() names are
+** used in the table for brevity and because they are familiar to most
+** C programmers.
+**
+** Note that when type conversions occur, pointers returned by prior
+** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or
+** sqlite3_column_text16() may be invalidated.
+** Type conversions and pointer invalidations might occur
+** in the following cases:
+**
+** <ul>
+** <li><p> The initial content is a BLOB and sqlite3_column_text()
+** or sqlite3_column_text16() is called. A zero-terminator might
+** need to be added to the string.</p></li>
+**
+** <li><p> The initial content is UTF-8 text and sqlite3_column_bytes16() or
+** sqlite3_column_text16() is called. The content must be converted
+** to UTF-16.</p></li>
+**
+** <li><p> The initial content is UTF-16 text and sqlite3_column_bytes() or
+** sqlite3_column_text() is called. The content must be converted
+** to UTF-8.</p></li>
+** </ul>
+**
+** Conversions between UTF-16be and UTF-16le are always done in place and do
+** not invalidate a prior pointer, though of course the content of the buffer
+** that the prior pointer points to will have been modified. Other kinds
+** of conversion are done in place when it is possible, but sometime it is
+** not possible and in those cases prior pointers are invalidated.
+**
+** The safest and easiest to remember policy is to invoke these routines
+** in one of the following ways:
+**
+** <ul>
+** <li>sqlite3_column_text() followed by sqlite3_column_bytes()</li>
+** <li>sqlite3_column_blob() followed by sqlite3_column_bytes()</li>
+** <li>sqlite3_column_text16() followed by sqlite3_column_bytes16()</li>
+** </ul>
+**
+** In other words, you should call sqlite3_column_text(), sqlite3_column_blob(),
+** or sqlite3_column_text16() first to force the result into the desired
+** format, then invoke sqlite3_column_bytes() or sqlite3_column_bytes16() to
+** find the size of the result. Do not mix call to sqlite3_column_text() or
+** sqlite3_column_blob() with calls to sqlite3_column_bytes16(). And do not
+** mix calls to sqlite3_column_text16() with calls to sqlite3_column_bytes().
+**
+** The pointers returned are valid until a type conversion occurs as
+** described above, or until [sqlite3_step()] or [sqlite3_reset()] or
+** [sqlite3_finalize()] is called. The memory space used to hold strings
+** and blobs is freed automatically. Do <b>not</b> pass the pointers returned
+** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into
+** [sqlite3_free()].
+**
+** If a memory allocation error occurs during the evaluation of any
+** of these routines, a default value is returned. The default value
+** is either the integer 0, the floating point number 0.0, or a NULL
+** pointer. Subsequent calls to [sqlite3_errcode()] will return
+** [SQLITE_NOMEM].
+**
+** INVARIANTS:
+**
+** {F13803} The [sqlite3_column_blob(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a blob and then returns a
+** pointer to the converted value.
+**
+** {F13806} The [sqlite3_column_bytes(S,N)] interface returns the
+** number of bytes in the blob or string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_column_blob(S,N)] or
+** [sqlite3_column_text(S,N)].
+**
+** {F13809} The [sqlite3_column_bytes16(S,N)] interface returns the
+** number of bytes in the string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_column_text16(S,N)].
+**
+** {F13812} The [sqlite3_column_double(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a floating point value and
+** returns a copy of that value.
+**
+** {F13815} The [sqlite3_column_int(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a 64-bit signed integer and
+** returns the lower 32 bits of that integer.
+**
+** {F13818} The [sqlite3_column_int64(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a 64-bit signed integer and
+** returns a copy of that integer.
+**
+** {F13821} The [sqlite3_column_text(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a zero-terminated UTF-8
+** string and returns a pointer to that string.
+**
+** {F13824} The [sqlite3_column_text16(S,N)] interface converts the
+** Nth column in the current row of the result set for
+** [prepared statement] S into a zero-terminated 2-byte
+** aligned UTF-16 native byte order
+** string and returns a pointer to that string.
+**
+** {F13827} The [sqlite3_column_type(S,N)] interface returns
+** one of [SQLITE_NULL], [SQLITE_INTEGER], [SQLITE_FLOAT],
+** [SQLITE_TEXT], or [SQLITE_BLOB] as appropriate for
+** the Nth column in the current row of the result set for
+** [prepared statement] S.
+**
+** {F13830} The [sqlite3_column_value(S,N)] interface returns a
+** pointer to an [unprotected sqlite3_value] object for the
+** Nth column in the current row of the result set for
+** [prepared statement] S.
+*/
+const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
+int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
+int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
+double sqlite3_column_double(sqlite3_stmt*, int iCol);
+int sqlite3_column_int(sqlite3_stmt*, int iCol);
+sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
+const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
+const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
+int sqlite3_column_type(sqlite3_stmt*, int iCol);
+sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
+
+/*
+** CAPI3REF: Destroy A Prepared Statement Object {F13300}
+**
+** The sqlite3_finalize() function is called to delete a
+** [prepared statement]. If the statement was
+** executed successfully, or not executed at all, then SQLITE_OK is returned.
+** If execution of the statement failed then an
+** [error code] or [extended error code]
+** is returned.
+**
+** This routine can be called at any point during the execution of the
+** [prepared statement]. If the virtual machine has not
+** completed execution when this routine is called, that is like
+** encountering an error or an interrupt. (See [sqlite3_interrupt()].)
+** Incomplete updates may be rolled back and transactions cancelled,
+** depending on the circumstances, and the
+** [error code] returned will be [SQLITE_ABORT].
+**
+** INVARIANTS:
+**
+** {F11302} The [sqlite3_finalize(S)] interface destroys the
+** [prepared statement] S and releases all
+** memory and file resources held by that object.
+**
+** {F11304} If the most recent call to [sqlite3_step(S)] for the
+** [prepared statement] S returned an error,
+** then [sqlite3_finalize(S)] returns that same error.
+*/
+int sqlite3_finalize(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Reset A Prepared Statement Object {F13330}
+**
+** The sqlite3_reset() function is called to reset a
+** [prepared statement] object.
+** back to its initial state, ready to be re-executed.
+** Any SQL statement variables that had values bound to them using
+** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values.
+** Use [sqlite3_clear_bindings()] to reset the bindings.
+**
+** {F11332} The [sqlite3_reset(S)] interface resets the [prepared statement] S
+** back to the beginning of its program.
+**
+** {F11334} If the most recent call to [sqlite3_step(S)] for
+** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE],
+** or if [sqlite3_step(S)] has never before been called on S,
+** then [sqlite3_reset(S)] returns [SQLITE_OK].
+**
+** {F11336} If the most recent call to [sqlite3_step(S)] for
+** [prepared statement] S indicated an error, then
+** [sqlite3_reset(S)] returns an appropriate [error code].
+**
+** {F11338} The [sqlite3_reset(S)] interface does not change the values
+** of any [sqlite3_bind_blob|bindings] on [prepared statement] S.
+*/
+int sqlite3_reset(sqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Create Or Redefine SQL Functions {F16100}
+** KEYWORDS: {function creation routines}
+**
+** These two functions (collectively known as
+** "function creation routines") are used to add SQL functions or aggregates
+** or to redefine the behavior of existing SQL functions or aggregates. The
+** difference only between the two is that the second parameter, the
+** name of the (scalar) function or aggregate, is encoded in UTF-8 for
+** sqlite3_create_function() and UTF-16 for sqlite3_create_function16().
+**
+** The first parameter is the [database connection] to which the SQL
+** function is to be added. If a single
+** program uses more than one [database connection] internally, then SQL
+** functions must be added individually to each [database connection].
+**
+** The second parameter is the name of the SQL function to be created
+** or redefined.
+** The length of the name is limited to 255 bytes, exclusive of the
+** zero-terminator. Note that the name length limit is in bytes, not
+** characters. Any attempt to create a function with a longer name
+** will result in an SQLITE_ERROR error.
+**
+** The third parameter is the number of arguments that the SQL function or
+** aggregate takes. If this parameter is negative, then the SQL function or
+** aggregate may take any number of arguments.
+**
+** The fourth parameter, eTextRep, specifies what
+** [SQLITE_UTF8 | text encoding] this SQL function prefers for
+** its parameters. Any SQL function implementation should be able to work
+** work with UTF-8, UTF-16le, or UTF-16be. But some implementations may be
+** more efficient with one encoding than another. It is allowed to
+** invoke sqlite3_create_function() or sqlite3_create_function16() multiple
+** times with the same function but with different values of eTextRep.
+** When multiple implementations of the same function are available, SQLite
+** will pick the one that involves the least amount of data conversion.
+** If there is only a single implementation which does not care what
+** text encoding is used, then the fourth argument should be
+** [SQLITE_ANY].
+**
+** The fifth parameter is an arbitrary pointer. The implementation
+** of the function can gain access to this pointer using
+** [sqlite3_user_data()].
+**
+** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are
+** pointers to C-language functions that implement the SQL
+** function or aggregate. A scalar SQL function requires an implementation of
+** the xFunc callback only, NULL pointers should be passed as the xStep
+** and xFinal parameters. An aggregate SQL function requires an implementation
+** of xStep and xFinal and NULL should be passed for xFunc. To delete an
+** existing SQL function or aggregate, pass NULL for all three function
+** callback.
+**
+** It is permitted to register multiple implementations of the same
+** functions with the same name but with either differing numbers of
+** arguments or differing perferred text encodings. SQLite will use
+** the implementation most closely matches the way in which the
+** SQL function is used.
+**
+** INVARIANTS:
+**
+** {F16103} The [sqlite3_create_function16()] interface behaves exactly
+** like [sqlite3_create_function()] in every way except that it
+** interprets the zFunctionName argument as
+** zero-terminated UTF-16 native byte order instead of as a
+** zero-terminated UTF-8.
+**
+** {F16106} A successful invocation of
+** the [sqlite3_create_function(D,X,N,E,...)] interface registers
+** or replaces callback functions in [database connection] D
+** used to implement the SQL function named X with N parameters
+** and having a perferred text encoding of E.
+**
+** {F16109} A successful call to [sqlite3_create_function(D,X,N,E,P,F,S,L)]
+** replaces the P, F, S, and L values from any prior calls with
+** the same D, X, N, and E values.
+**
+** {F16112} The [sqlite3_create_function(D,X,...)] interface fails with
+** a return code of [SQLITE_ERROR] if the SQL function name X is
+** longer than 255 bytes exclusive of the zero terminator.
+**
+** {F16118} Either F must be NULL and S and L are non-NULL or else F
+** is non-NULL and S and L are NULL, otherwise
+** [sqlite3_create_function(D,X,N,E,P,F,S,L)] returns [SQLITE_ERROR].
+**
+** {F16121} The [sqlite3_create_function(D,...)] interface fails with an
+** error code of [SQLITE_BUSY] if there exist [prepared statements]
+** associated with the [database connection] D.
+**
+** {F16124} The [sqlite3_create_function(D,X,N,...)] interface fails with an
+** error code of [SQLITE_ERROR] if parameter N (specifying the number
+** of arguments to the SQL function being registered) is less
+** than -1 or greater than 127.
+**
+** {F16127} When N is non-negative, the [sqlite3_create_function(D,X,N,...)]
+** interface causes callbacks to be invoked for the SQL function
+** named X when the number of arguments to the SQL function is
+** exactly N.
+**
+** {F16130} When N is -1, the [sqlite3_create_function(D,X,N,...)]
+** interface causes callbacks to be invoked for the SQL function
+** named X with any number of arguments.
+**
+** {F16133} When calls to [sqlite3_create_function(D,X,N,...)]
+** specify multiple implementations of the same function X
+** and when one implementation has N>=0 and the other has N=(-1)
+** the implementation with a non-zero N is preferred.
+**
+** {F16136} When calls to [sqlite3_create_function(D,X,N,E,...)]
+** specify multiple implementations of the same function X with
+** the same number of arguments N but with different
+** encodings E, then the implementation where E matches the
+** database encoding is preferred.
+**
+** {F16139} For an aggregate SQL function created using
+** [sqlite3_create_function(D,X,N,E,P,0,S,L)] the finializer
+** function L will always be invoked exactly once if the
+** step function S is called one or more times.
+**
+** {F16142} When SQLite invokes either the xFunc or xStep function of
+** an application-defined SQL function or aggregate created
+** by [sqlite3_create_function()] or [sqlite3_create_function16()],
+** then the array of [sqlite3_value] objects passed as the
+** third parameter are always [protected sqlite3_value] objects.
+*/
+int sqlite3_create_function(
+ sqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+);
+int sqlite3_create_function16(
+ sqlite3 *db,
+ const void *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+);
+
+/*
+** CAPI3REF: Text Encodings {F10267}
+**
+** These constant define integer codes that represent the various
+** text encodings supported by SQLite.
+*/
+#define SQLITE_UTF8 1
+#define SQLITE_UTF16LE 2
+#define SQLITE_UTF16BE 3
+#define SQLITE_UTF16 4 /* Use native byte order */
+#define SQLITE_ANY 5 /* sqlite3_create_function only */
+#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */
+
+/*
+** CAPI3REF: Obsolete Functions
+**
+** These functions are all now obsolete. In order to maintain
+** backwards compatibility with older code, we continue to support
+** these functions. However, new development projects should avoid
+** the use of these functions. To help encourage people to avoid
+** using these functions, we are not going to tell you want they do.
+*/
+int sqlite3_aggregate_count(sqlite3_context*);
+int sqlite3_expired(sqlite3_stmt*);
+int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*);
+int sqlite3_global_recover(void);
+void sqlite3_thread_cleanup(void);
+int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),void*,sqlite3_int64);
+
+/*
+** CAPI3REF: Obtaining SQL Function Parameter Values {F15100}
+**
+** The C-language implementation of SQL functions and aggregates uses
+** this set of interface routines to access the parameter values on
+** the function or aggregate.
+**
+** The xFunc (for scalar functions) or xStep (for aggregates) parameters
+** to [sqlite3_create_function()] and [sqlite3_create_function16()]
+** define callbacks that implement the SQL functions and aggregates.
+** The 4th parameter to these callbacks is an array of pointers to
+** [protected sqlite3_value] objects. There is one [sqlite3_value] object for
+** each parameter to the SQL function. These routines are used to
+** extract values from the [sqlite3_value] objects.
+**
+** These routines work only with [protected sqlite3_value] objects.
+** Any attempt to use these routines on an [unprotected sqlite3_value]
+** object results in undefined behavior.
+**
+** These routines work just like the corresponding
+** [sqlite3_column_blob | sqlite3_column_* routines] except that
+** these routines take a single [protected sqlite3_value] object pointer
+** instead of an [sqlite3_stmt*] pointer and an integer column number.
+**
+** The sqlite3_value_text16() interface extracts a UTF16 string
+** in the native byte-order of the host machine. The
+** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces
+** extract UTF16 strings as big-endian and little-endian respectively.
+**
+** The sqlite3_value_numeric_type() interface attempts to apply
+** numeric affinity to the value. This means that an attempt is
+** made to convert the value to an integer or floating point. If
+** such a conversion is possible without loss of information (in other
+** words if the value is a string that looks like a number)
+** then the conversion is done. Otherwise no conversion occurs. The
+** [SQLITE_INTEGER | datatype] after conversion is returned.
+**
+** Please pay particular attention to the fact that the pointer that
+** is returned from [sqlite3_value_blob()], [sqlite3_value_text()], or
+** [sqlite3_value_text16()] can be invalidated by a subsequent call to
+** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()],
+** or [sqlite3_value_text16()].
+**
+** These routines must be called from the same thread as
+** the SQL function that supplied the [sqlite3_value*] parameters.
+**
+**
+** INVARIANTS:
+**
+** {F15103} The [sqlite3_value_blob(V)] interface converts the
+** [protected sqlite3_value] object V into a blob and then returns a
+** pointer to the converted value.
+**
+** {F15106} The [sqlite3_value_bytes(V)] interface returns the
+** number of bytes in the blob or string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_value_blob(V)] or
+** [sqlite3_value_text(V)].
+**
+** {F15109} The [sqlite3_value_bytes16(V)] interface returns the
+** number of bytes in the string (exclusive of the
+** zero terminator on the string) that was returned by the
+** most recent call to [sqlite3_value_text16(V)],
+** [sqlite3_value_text16be(V)], or [sqlite3_value_text16le(V)].
+**
+** {F15112} The [sqlite3_value_double(V)] interface converts the
+** [protected sqlite3_value] object V into a floating point value and
+** returns a copy of that value.
+**
+** {F15115} The [sqlite3_value_int(V)] interface converts the
+** [protected sqlite3_value] object V into a 64-bit signed integer and
+** returns the lower 32 bits of that integer.
+**
+** {F15118} The [sqlite3_value_int64(V)] interface converts the
+** [protected sqlite3_value] object V into a 64-bit signed integer and
+** returns a copy of that integer.
+**
+** {F15121} The [sqlite3_value_text(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated UTF-8
+** string and returns a pointer to that string.
+**
+** {F15124} The [sqlite3_value_text16(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated 2-byte
+** aligned UTF-16 native byte order
+** string and returns a pointer to that string.
+**
+** {F15127} The [sqlite3_value_text16be(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated 2-byte
+** aligned UTF-16 big-endian
+** string and returns a pointer to that string.
+**
+** {F15130} The [sqlite3_value_text16le(V)] interface converts the
+** [protected sqlite3_value] object V into a zero-terminated 2-byte
+** aligned UTF-16 little-endian
+** string and returns a pointer to that string.
+**
+** {F15133} The [sqlite3_value_type(V)] interface returns
+** one of [SQLITE_NULL], [SQLITE_INTEGER], [SQLITE_FLOAT],
+** [SQLITE_TEXT], or [SQLITE_BLOB] as appropriate for
+** the [sqlite3_value] object V.
+**
+** {F15136} The [sqlite3_value_numeric_type(V)] interface converts
+** the [protected sqlite3_value] object V into either an integer or
+** a floating point value if it can do so without loss of
+** information, and returns one of [SQLITE_NULL],
+** [SQLITE_INTEGER], [SQLITE_FLOAT], [SQLITE_TEXT], or
+** [SQLITE_BLOB] as appropriate for
+** the [protected sqlite3_value] object V after the conversion attempt.
+*/
+const void *sqlite3_value_blob(sqlite3_value*);
+int sqlite3_value_bytes(sqlite3_value*);
+int sqlite3_value_bytes16(sqlite3_value*);
+double sqlite3_value_double(sqlite3_value*);
+int sqlite3_value_int(sqlite3_value*);
+sqlite3_int64 sqlite3_value_int64(sqlite3_value*);
+const unsigned char *sqlite3_value_text(sqlite3_value*);
+const void *sqlite3_value_text16(sqlite3_value*);
+const void *sqlite3_value_text16le(sqlite3_value*);
+const void *sqlite3_value_text16be(sqlite3_value*);
+int sqlite3_value_type(sqlite3_value*);
+int sqlite3_value_numeric_type(sqlite3_value*);
+
+/*
+** CAPI3REF: Obtain Aggregate Function Context {F16210}
+**
+** The implementation of aggregate SQL functions use this routine to allocate
+** a structure for storing their state.
+** The first time the sqlite3_aggregate_context() routine is
+** is called for a particular aggregate, SQLite allocates nBytes of memory
+** zeros that memory, and returns a pointer to it.
+** On second and subsequent calls to sqlite3_aggregate_context()
+** for the same aggregate function index, the same buffer is returned.
+** The implementation
+** of the aggregate can use the returned buffer to accumulate data.
+**
+** SQLite automatically frees the allocated buffer when the aggregate
+** query concludes.
+**
+** The first parameter should be a copy of the
+** [sqlite3_context | SQL function context] that is the first
+** parameter to the callback routine that implements the aggregate
+** function.
+**
+** This routine must be called from the same thread in which
+** the aggregate SQL function is running.
+**
+** INVARIANTS:
+**
+** {F16211} The first invocation of [sqlite3_aggregate_context(C,N)] for
+** a particular instance of an aggregate function (for a particular
+** context C) causes SQLite to allocation N bytes of memory,
+** zero that memory, and return a pointer to the allocationed
+** memory.
+**
+** {F16213} If a memory allocation error occurs during
+** [sqlite3_aggregate_context(C,N)] then the function returns 0.
+**
+** {F16215} Second and subsequent invocations of
+** [sqlite3_aggregate_context(C,N)] for the same context pointer C
+** ignore the N parameter and return a pointer to the same
+** block of memory returned by the first invocation.
+**
+** {F16217} The memory allocated by [sqlite3_aggregate_context(C,N)] is
+** automatically freed on the next call to [sqlite3_reset()]
+** or [sqlite3_finalize()] for the [prepared statement] containing
+** the aggregate function associated with context C.
+*/
+void *sqlite3_aggregate_context(sqlite3_context*, int nBytes);
+
+/*
+** CAPI3REF: User Data For Functions {F16240}
+**
+** The sqlite3_user_data() interface returns a copy of
+** the pointer that was the pUserData parameter (the 5th parameter)
+** of the the [sqlite3_create_function()]
+** and [sqlite3_create_function16()] routines that originally
+** registered the application defined function. {END}
+**
+** This routine must be called from the same thread in which
+** the application-defined function is running.
+**
+** INVARIANTS:
+**
+** {F16243} The [sqlite3_user_data(C)] interface returns a copy of the
+** P pointer from the [sqlite3_create_function(D,X,N,E,P,F,S,L)]
+** or [sqlite3_create_function16(D,X,N,E,P,F,S,L)] call that
+** registered the SQL function associated with
+** [sqlite3_context] C.
+*/
+void *sqlite3_user_data(sqlite3_context*);
+
+/*
+** CAPI3REF: Database Connection For Functions {F16250}
+**
+** The sqlite3_context_db_handle() interface returns a copy of
+** the pointer to the [database connection] (the 1st parameter)
+** of the the [sqlite3_create_function()]
+** and [sqlite3_create_function16()] routines that originally
+** registered the application defined function.
+**
+** INVARIANTS:
+**
+** {F16253} The [sqlite3_context_db_handle(C)] interface returns a copy of the
+** D pointer from the [sqlite3_create_function(D,X,N,E,P,F,S,L)]
+** or [sqlite3_create_function16(D,X,N,E,P,F,S,L)] call that
+** registered the SQL function associated with
+** [sqlite3_context] C.
+*/
+sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
+
+/*
+** CAPI3REF: Function Auxiliary Data {F16270}
+**
+** The following two functions may be used by scalar SQL functions to
+** associate meta-data with argument values. If the same value is passed to
+** multiple invocations of the same SQL function during query execution, under
+** some circumstances the associated meta-data may be preserved. This may
+** be used, for example, to add a regular-expression matching scalar
+** function. The compiled version of the regular expression is stored as
+** meta-data associated with the SQL value passed as the regular expression
+** pattern. The compiled regular expression can be reused on multiple
+** invocations of the same function so that the original pattern string
+** does not need to be recompiled on each invocation.
+**
+** The sqlite3_get_auxdata() interface returns a pointer to the meta-data
+** associated by the sqlite3_set_auxdata() function with the Nth argument
+** value to the application-defined function.
+** If no meta-data has been ever been set for the Nth
+** argument of the function, or if the cooresponding function parameter
+** has changed since the meta-data was set, then sqlite3_get_auxdata()
+** returns a NULL pointer.
+**
+** The sqlite3_set_auxdata() interface saves the meta-data
+** pointed to by its 3rd parameter as the meta-data for the N-th
+** argument of the application-defined function. Subsequent
+** calls to sqlite3_get_auxdata() might return this data, if it has
+** not been destroyed.
+** If it is not NULL, SQLite will invoke the destructor
+** function given by the 4th parameter to sqlite3_set_auxdata() on
+** the meta-data when the corresponding function parameter changes
+** or when the SQL statement completes, whichever comes first.
+**
+** SQLite is free to call the destructor and drop meta-data on
+** any parameter of any function at any time. The only guarantee
+** is that the destructor will be called before the metadata is
+** dropped.
+**
+** In practice, meta-data is preserved between function calls for
+** expressions that are constant at compile time. This includes literal
+** values and SQL variables.
+**
+** These routines must be called from the same thread in which
+** the SQL function is running.
+**
+** INVARIANTS:
+**
+** {F16272} The [sqlite3_get_auxdata(C,N)] interface returns a pointer
+** to metadata associated with the Nth parameter of the SQL function
+** whose context is C, or NULL if there is no metadata associated
+** with that parameter.
+**
+** {F16274} The [sqlite3_set_auxdata(C,N,P,D)] interface assigns a metadata
+** pointer P to the Nth parameter of the SQL function with context
+** C.
+**
+** {F16276} SQLite will invoke the destructor D with a single argument
+** which is the metadata pointer P following a call to
+** [sqlite3_set_auxdata(C,N,P,D)] when SQLite ceases to hold
+** the metadata.
+**
+** {F16277} SQLite ceases to hold metadata for an SQL function parameter
+** when the value of that parameter changes.
+**
+** {F16278} When [sqlite3_set_auxdata(C,N,P,D)] is invoked, the destructor
+** is called for any prior metadata associated with the same function
+** context C and parameter N.
+**
+** {F16279} SQLite will call destructors for any metadata it is holding
+** in a particular [prepared statement] S when either
+** [sqlite3_reset(S)] or [sqlite3_finalize(S)] is called.
+*/
+void *sqlite3_get_auxdata(sqlite3_context*, int N);
+void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*));
+
+
+/*
+** CAPI3REF: Constants Defining Special Destructor Behavior {F10280}
+**
+** These are special value for the destructor that is passed in as the
+** final argument to routines like [sqlite3_result_blob()]. If the destructor
+** argument is SQLITE_STATIC, it means that the content pointer is constant
+** and will never change. It does not need to be destroyed. The
+** SQLITE_TRANSIENT value means that the content will likely change in
+** the near future and that SQLite should make its own private copy of
+** the content before returning.
+**
+** The typedef is necessary to work around problems in certain
+** C++ compilers. See ticket #2191.
+*/
+typedef void (*sqlite3_destructor_type)(void*);
+#define SQLITE_STATIC ((sqlite3_destructor_type)0)
+#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
+
+/*
+** CAPI3REF: Setting The Result Of An SQL Function {F16400}
+**
+** These routines are used by the xFunc or xFinal callbacks that
+** implement SQL functions and aggregates. See
+** [sqlite3_create_function()] and [sqlite3_create_function16()]
+** for additional information.
+**
+** These functions work very much like the
+** [sqlite3_bind_blob | sqlite3_bind_*] family of functions used
+** to bind values to host parameters in prepared statements.
+** Refer to the
+** [sqlite3_bind_blob | sqlite3_bind_* documentation] for
+** additional information.
+**
+** The sqlite3_result_blob() interface sets the result from
+** an application defined function to be the BLOB whose content is pointed
+** to by the second parameter and which is N bytes long where N is the
+** third parameter.
+** The sqlite3_result_zeroblob() inerfaces set the result of
+** the application defined function to be a BLOB containing all zero
+** bytes and N bytes in size, where N is the value of the 2nd parameter.
+**
+** The sqlite3_result_double() interface sets the result from
+** an application defined function to be a floating point value specified
+** by its 2nd argument.
+**
+** The sqlite3_result_error() and sqlite3_result_error16() functions
+** cause the implemented SQL function to throw an exception.
+** SQLite uses the string pointed to by the
+** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16()
+** as the text of an error message. SQLite interprets the error
+** message string from sqlite3_result_error() as UTF8. SQLite
+** interprets the string from sqlite3_result_error16() as UTF16 in native
+** byte order. If the third parameter to sqlite3_result_error()
+** or sqlite3_result_error16() is negative then SQLite takes as the error
+** message all text up through the first zero character.
+** If the third parameter to sqlite3_result_error() or
+** sqlite3_result_error16() is non-negative then SQLite takes that many
+** bytes (not characters) from the 2nd parameter as the error message.
+** The sqlite3_result_error() and sqlite3_result_error16()
+** routines make a copy private copy of the error message text before
+** they return. Hence, the calling function can deallocate or
+** modify the text after they return without harm.
+** The sqlite3_result_error_code() function changes the error code
+** returned by SQLite as a result of an error in a function. By default,
+** the error code is SQLITE_ERROR. A subsequent call to sqlite3_result_error()
+** or sqlite3_result_error16() resets the error code to SQLITE_ERROR.
+**
+** The sqlite3_result_toobig() interface causes SQLite
+** to throw an error indicating that a string or BLOB is to long
+** to represent. The sqlite3_result_nomem() interface
+** causes SQLite to throw an exception indicating that the a
+** memory allocation failed.
+**
+** The sqlite3_result_int() interface sets the return value
+** of the application-defined function to be the 32-bit signed integer
+** value given in the 2nd argument.
+** The sqlite3_result_int64() interface sets the return value
+** of the application-defined function to be the 64-bit signed integer
+** value given in the 2nd argument.
+**
+** The sqlite3_result_null() interface sets the return value
+** of the application-defined function to be NULL.
+**
+** The sqlite3_result_text(), sqlite3_result_text16(),
+** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces
+** set the return value of the application-defined function to be
+** a text string which is represented as UTF-8, UTF-16 native byte order,
+** UTF-16 little endian, or UTF-16 big endian, respectively.
+** SQLite takes the text result from the application from
+** the 2nd parameter of the sqlite3_result_text* interfaces.
+** If the 3rd parameter to the sqlite3_result_text* interfaces
+** is negative, then SQLite takes result text from the 2nd parameter
+** through the first zero character.
+** If the 3rd parameter to the sqlite3_result_text* interfaces
+** is non-negative, then as many bytes (not characters) of the text
+** pointed to by the 2nd parameter are taken as the application-defined
+** function result.
+** If the 4th parameter to the sqlite3_result_text* interfaces
+** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that
+** function as the destructor on the text or blob result when it has
+** finished using that result.
+** If the 4th parameter to the sqlite3_result_text* interfaces
+** or sqlite3_result_blob is the special constant SQLITE_STATIC, then
+** SQLite assumes that the text or blob result is constant space and
+** does not copy the space or call a destructor when it has
+** finished using that result.
+** If the 4th parameter to the sqlite3_result_text* interfaces
+** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT
+** then SQLite makes a copy of the result into space obtained from
+** from [sqlite3_malloc()] before it returns.
+**
+** The sqlite3_result_value() interface sets the result of
+** the application-defined function to be a copy the
+** [unprotected sqlite3_value] object specified by the 2nd parameter. The
+** sqlite3_result_value() interface makes a copy of the [sqlite3_value]
+** so that [sqlite3_value] specified in the parameter may change or
+** be deallocated after sqlite3_result_value() returns without harm.
+** A [protected sqlite3_value] object may always be used where an
+** [unprotected sqlite3_value] object is required, so either
+** kind of [sqlite3_value] object can be used with this interface.
+**
+** If these routines are called from within the different thread
+** than the one containing the application-defined function that recieved
+** the [sqlite3_context] pointer, the results are undefined.
+**
+** INVARIANTS:
+**
+** {F16403} The default return value from any SQL function is NULL.
+**
+** {F16406} The [sqlite3_result_blob(C,V,N,D)] interface changes the
+** return value of function C to be a blob that is N bytes
+** in length and with content pointed to by V.
+**
+** {F16409} The [sqlite3_result_double(C,V)] interface changes the
+** return value of function C to be the floating point value V.
+**
+** {F16412} The [sqlite3_result_error(C,V,N)] interface changes the return
+** value of function C to be an exception with error code
+** [SQLITE_ERROR] and a UTF8 error message copied from V up to the
+** first zero byte or until N bytes are read if N is positive.
+**
+** {F16415} The [sqlite3_result_error16(C,V,N)] interface changes the return
+** value of function C to be an exception with error code
+** [SQLITE_ERROR] and a UTF16 native byte order error message
+** copied from V up to the first zero terminator or until N bytes
+** are read if N is positive.
+**
+** {F16418} The [sqlite3_result_error_toobig(C)] interface changes the return
+** value of the function C to be an exception with error code
+** [SQLITE_TOOBIG] and an appropriate error message.
+**
+** {F16421} The [sqlite3_result_error_nomem(C)] interface changes the return
+** value of the function C to be an exception with error code
+** [SQLITE_NOMEM] and an appropriate error message.
+**
+** {F16424} The [sqlite3_result_error_code(C,E)] interface changes the return
+** value of the function C to be an exception with error code E.
+** The error message text is unchanged.
+**
+** {F16427} The [sqlite3_result_int(C,V)] interface changes the
+** return value of function C to be the 32-bit integer value V.
+**
+** {F16430} The [sqlite3_result_int64(C,V)] interface changes the
+** return value of function C to be the 64-bit integer value V.
+**
+** {F16433} The [sqlite3_result_null(C)] interface changes the
+** return value of function C to be NULL.
+**
+** {F16436} The [sqlite3_result_text(C,V,N,D)] interface changes the
+** return value of function C to be the UTF8 string
+** V up to the first zero if N is negative
+** or the first N bytes of V if N is non-negative.
+**
+** {F16439} The [sqlite3_result_text16(C,V,N,D)] interface changes the
+** return value of function C to be the UTF16 native byte order
+** string V up to the first zero if N is
+** negative or the first N bytes of V if N is non-negative.
+**
+** {F16442} The [sqlite3_result_text16be(C,V,N,D)] interface changes the
+** return value of function C to be the UTF16 big-endian
+** string V up to the first zero if N is
+** is negative or the first N bytes or V if N is non-negative.
+**
+** {F16445} The [sqlite3_result_text16le(C,V,N,D)] interface changes the
+** return value of function C to be the UTF16 little-endian
+** string V up to the first zero if N is
+** negative or the first N bytes of V if N is non-negative.
+**
+** {F16448} The [sqlite3_result_value(C,V)] interface changes the
+** return value of function C to be [unprotected sqlite3_value]
+** object V.
+**
+** {F16451} The [sqlite3_result_zeroblob(C,N)] interface changes the
+** return value of function C to be an N-byte blob of all zeros.
+**
+** {F16454} The [sqlite3_result_error()] and [sqlite3_result_error16()]
+** interfaces make a copy of their error message strings before
+** returning.
+**
+** {F16457} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)],
+** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)],
+** [sqlite3_result_text16be(C,V,N,D)], or
+** [sqlite3_result_text16le(C,V,N,D)] is the constant [SQLITE_STATIC]
+** then no destructor is ever called on the pointer V and SQLite
+** assumes that V is immutable.
+**
+** {F16460} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)],
+** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)],
+** [sqlite3_result_text16be(C,V,N,D)], or
+** [sqlite3_result_text16le(C,V,N,D)] is the constant
+** [SQLITE_TRANSIENT] then the interfaces makes a copy of the
+** content of V and retains the copy.
+**
+** {F16463} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)],
+** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)],
+** [sqlite3_result_text16be(C,V,N,D)], or
+** [sqlite3_result_text16le(C,V,N,D)] is some value other than
+** the constants [SQLITE_STATIC] and [SQLITE_TRANSIENT] then
+** SQLite will invoke the destructor D with V as its only argument
+** when it has finished with the V value.
+*/
+void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*));
+void sqlite3_result_double(sqlite3_context*, double);
+void sqlite3_result_error(sqlite3_context*, const char*, int);
+void sqlite3_result_error16(sqlite3_context*, const void*, int);
+void sqlite3_result_error_toobig(sqlite3_context*);
+void sqlite3_result_error_nomem(sqlite3_context*);
+void sqlite3_result_error_code(sqlite3_context*, int);
+void sqlite3_result_int(sqlite3_context*, int);
+void sqlite3_result_int64(sqlite3_context*, sqlite3_int64);
+void sqlite3_result_null(sqlite3_context*);
+void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*));
+void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*));
+void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*));
+void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*));
+void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
+void sqlite3_result_zeroblob(sqlite3_context*, int n);
+
+/*
+** CAPI3REF: Define New Collating Sequences {F16600}
+**
+** These functions are used to add new collation sequences to the
+** [sqlite3*] handle specified as the first argument.
+**
+** The name of the new collation sequence is specified as a UTF-8 string
+** for sqlite3_create_collation() and sqlite3_create_collation_v2()
+** and a UTF-16 string for sqlite3_create_collation16(). In all cases
+** the name is passed as the second function argument.
+**
+** The third argument may be one of the constants [SQLITE_UTF8],
+** [SQLITE_UTF16LE] or [SQLITE_UTF16BE], indicating that the user-supplied
+** routine expects to be passed pointers to strings encoded using UTF-8,
+** UTF-16 little-endian or UTF-16 big-endian respectively. The
+** third argument might also be [SQLITE_UTF16_ALIGNED] to indicate that
+** the routine expects pointers to 16-bit word aligned strings
+** of UTF16 in the native byte order of the host computer.
+**
+** A pointer to the user supplied routine must be passed as the fifth
+** argument. If it is NULL, this is the same as deleting the collation
+** sequence (so that SQLite cannot call it anymore).
+** Each time the application
+** supplied function is invoked, it is passed a copy of the void* passed as
+** the fourth argument to sqlite3_create_collation() or
+** sqlite3_create_collation16() as its first parameter.
+**
+** The remaining arguments to the application-supplied routine are two strings,
+** each represented by a (length, data) pair and encoded in the encoding
+** that was passed as the third argument when the collation sequence was
+** registered. {END} The application defined collation routine should
+** return negative, zero or positive if
+** the first string is less than, equal to, or greater than the second
+** string. i.e. (STRING1 - STRING2).
+**
+** The sqlite3_create_collation_v2() works like sqlite3_create_collation()
+** excapt that it takes an extra argument which is a destructor for
+** the collation. The destructor is called when the collation is
+** destroyed and is passed a copy of the fourth parameter void* pointer
+** of the sqlite3_create_collation_v2().
+** Collations are destroyed when
+** they are overridden by later calls to the collation creation functions
+** or when the [sqlite3*] database handle is closed using [sqlite3_close()].
+**
+** INVARIANTS:
+**
+** {F16603} A successful call to the
+** [sqlite3_create_collation_v2(B,X,E,P,F,D)] interface
+** registers function F as the comparison function used to
+** implement collation X on [database connection] B for
+** databases having encoding E.
+**
+** {F16604} SQLite understands the X parameter to
+** [sqlite3_create_collation_v2(B,X,E,P,F,D)] as a zero-terminated
+** UTF-8 string in which case is ignored for ASCII characters and
+** is significant for non-ASCII characters.
+**
+** {F16606} Successive calls to [sqlite3_create_collation_v2(B,X,E,P,F,D)]
+** with the same values for B, X, and E, override prior values
+** of P, F, and D.
+**
+** {F16609} The destructor D in [sqlite3_create_collation_v2(B,X,E,P,F,D)]
+** is not NULL then it is called with argument P when the
+** collating function is dropped by SQLite.
+**
+** {F16612} A collating function is dropped when it is overloaded.
+**
+** {F16615} A collating function is dropped when the database connection
+** is closed using [sqlite3_close()].
+**
+** {F16618} The pointer P in [sqlite3_create_collation_v2(B,X,E,P,F,D)]
+** is passed through as the first parameter to the comparison
+** function F for all subsequent invocations of F.
+**
+** {F16621} A call to [sqlite3_create_collation(B,X,E,P,F)] is exactly
+** the same as a call to [sqlite3_create_collation_v2()] with
+** the same parameters and a NULL destructor.
+**
+** {F16624} Following a [sqlite3_create_collation_v2(B,X,E,P,F,D)],
+** SQLite uses the comparison function F for all text comparison
+** operations on [database connection] B on text values that
+** use the collating sequence name X.
+**
+** {F16627} The [sqlite3_create_collation16(B,X,E,P,F)] works the same
+** as [sqlite3_create_collation(B,X,E,P,F)] except that the
+** collation name X is understood as UTF-16 in native byte order
+** instead of UTF-8.
+**
+** {F16630} When multiple comparison functions are available for the same
+** collating sequence, SQLite chooses the one whose text encoding
+** requires the least amount of conversion from the default
+** text encoding of the database.
+*/
+int sqlite3_create_collation(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+);
+int sqlite3_create_collation_v2(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*),
+ void(*xDestroy)(void*)
+);
+int sqlite3_create_collation16(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+);
+
+/*
+** CAPI3REF: Collation Needed Callbacks {F16700}
+**
+** To avoid having to register all collation sequences before a database
+** can be used, a single callback function may be registered with the
+** database handle to be called whenever an undefined collation sequence is
+** required.
+**
+** If the function is registered using the sqlite3_collation_needed() API,
+** then it is passed the names of undefined collation sequences as strings
+** encoded in UTF-8. {F16703} If sqlite3_collation_needed16() is used, the names
+** are passed as UTF-16 in machine native byte order. A call to either
+** function replaces any existing callback.
+**
+** When the callback is invoked, the first argument passed is a copy
+** of the second argument to sqlite3_collation_needed() or
+** sqlite3_collation_needed16(). The second argument is the database
+** handle. The third argument is one of [SQLITE_UTF8],
+** [SQLITE_UTF16BE], or [SQLITE_UTF16LE], indicating the most
+** desirable form of the collation sequence function required.
+** The fourth parameter is the name of the
+** required collation sequence.
+**
+** The callback function should register the desired collation using
+** [sqlite3_create_collation()], [sqlite3_create_collation16()], or
+** [sqlite3_create_collation_v2()].
+**
+** INVARIANTS:
+**
+** {F16702} A successful call to [sqlite3_collation_needed(D,P,F)]
+** or [sqlite3_collation_needed16(D,P,F)] causes
+** the [database connection] D to invoke callback F with first
+** parameter P whenever it needs a comparison function for a
+** collating sequence that it does not know about.
+**
+** {F16704} Each successful call to [sqlite3_collation_needed()] or
+** [sqlite3_collation_needed16()] overrides the callback registered
+** on the same [database connection] by prior calls to either
+** interface.
+**
+** {F16706} The name of the requested collating function passed in the
+** 4th parameter to the callback is in UTF-8 if the callback
+** was registered using [sqlite3_collation_needed()] and
+** is in UTF-16 native byte order if the callback was
+** registered using [sqlite3_collation_needed16()].
+**
+**
+*/
+int sqlite3_collation_needed(
+ sqlite3*,
+ void*,
+ void(*)(void*,sqlite3*,int eTextRep,const char*)
+);
+int sqlite3_collation_needed16(
+ sqlite3*,
+ void*,
+ void(*)(void*,sqlite3*,int eTextRep,const void*)
+);
+
+/*
+** Specify the key for an encrypted database. This routine should be
+** called right after sqlite3_open().
+**
+** The code to implement this API is not available in the public release
+** of SQLite.
+*/
+int sqlite3_key(
+ sqlite3 *db, /* Database to be rekeyed */
+ const void *pKey, int nKey /* The key */
+);
+
+/*
+** Change the key on an open database. If the current database is not
+** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the
+** database is decrypted.
+**
+** The code to implement this API is not available in the public release
+** of SQLite.
+*/
+int sqlite3_rekey(
+ sqlite3 *db, /* Database to be rekeyed */
+ const void *pKey, int nKey /* The new key */
+);
+
+/*
+** CAPI3REF: Suspend Execution For A Short Time {F10530}
+**
+** The sqlite3_sleep() function
+** causes the current thread to suspend execution
+** for at least a number of milliseconds specified in its parameter.
+**
+** If the operating system does not support sleep requests with
+** millisecond time resolution, then the time will be rounded up to
+** the nearest second. The number of milliseconds of sleep actually
+** requested from the operating system is returned.
+**
+** SQLite implements this interface by calling the xSleep()
+** method of the default [sqlite3_vfs] object.
+**
+** INVARIANTS:
+**
+** {F10533} The [sqlite3_sleep(M)] interface invokes the xSleep
+** method of the default [sqlite3_vfs|VFS] in order to
+** suspend execution of the current thread for at least
+** M milliseconds.
+**
+** {F10536} The [sqlite3_sleep(M)] interface returns the number of
+** milliseconds of sleep actually requested of the operating
+** system, which might be larger than the parameter M.
+*/
+int sqlite3_sleep(int);
+
+/*
+** CAPI3REF: Name Of The Folder Holding Temporary Files {F10310}
+**
+** If this global variable is made to point to a string which is
+** the name of a folder (a.ka. directory), then all temporary files
+** created by SQLite will be placed in that directory. If this variable
+** is NULL pointer, then SQLite does a search for an appropriate temporary
+** file directory.
+**
+** It is not safe to modify this variable once a database connection
+** has been opened. It is intended that this variable be set once
+** as part of process initialization and before any SQLite interface
+** routines have been call and remain unchanged thereafter.
+*/
+SQLITE_EXTERN char *sqlite3_temp_directory;
+
+/*
+** CAPI3REF: Test To See If The Database Is In Auto-Commit Mode {F12930}
+**
+** The sqlite3_get_autocommit() interfaces returns non-zero or
+** zero if the given database connection is or is not in autocommit mode,
+** respectively. Autocommit mode is on
+** by default. Autocommit mode is disabled by a [BEGIN] statement.
+** Autocommit mode is reenabled by a [COMMIT] or [ROLLBACK].
+**
+** If certain kinds of errors occur on a statement within a multi-statement
+** transactions (errors including [SQLITE_FULL], [SQLITE_IOERR],
+** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the
+** transaction might be rolled back automatically. The only way to
+** find out if SQLite automatically rolled back the transaction after
+** an error is to use this function.
+**
+** INVARIANTS:
+**
+** {F12931} The [sqlite3_get_autocommit(D)] interface returns non-zero or
+** zero if the [database connection] D is or is not in autocommit
+** mode, respectively.
+**
+** {F12932} Autocommit mode is on by default.
+**
+** {F12933} Autocommit mode is disabled by a successful [BEGIN] statement.
+**
+** {F12934} Autocommit mode is enabled by a successful [COMMIT] or [ROLLBACK]
+** statement.
+**
+**
+** LIMITATIONS:
+***
+** {U12936} If another thread changes the autocommit status of the database
+** connection while this routine is running, then the return value
+** is undefined.
+*/
+int sqlite3_get_autocommit(sqlite3*);
+
+/*
+** CAPI3REF: Find The Database Handle Of A Prepared Statement {F13120}
+**
+** The sqlite3_db_handle interface
+** returns the [sqlite3*] database handle to which a
+** [prepared statement] belongs.
+** The database handle returned by sqlite3_db_handle
+** is the same database handle that was
+** the first argument to the [sqlite3_prepare_v2()] or its variants
+** that was used to create the statement in the first place.
+**
+** INVARIANTS:
+**
+** {F13123} The [sqlite3_db_handle(S)] interface returns a pointer
+** to the [database connection] associated with
+** [prepared statement] S.
+*/
+sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
+
+
+/*
+** CAPI3REF: Commit And Rollback Notification Callbacks {F12950}
+**
+** The sqlite3_commit_hook() interface registers a callback
+** function to be invoked whenever a transaction is committed.
+** Any callback set by a previous call to sqlite3_commit_hook()
+** for the same database connection is overridden.
+** The sqlite3_rollback_hook() interface registers a callback
+** function to be invoked whenever a transaction is committed.
+** Any callback set by a previous call to sqlite3_commit_hook()
+** for the same database connection is overridden.
+** The pArg argument is passed through
+** to the callback. If the callback on a commit hook function
+** returns non-zero, then the commit is converted into a rollback.
+**
+** If another function was previously registered, its
+** pArg value is returned. Otherwise NULL is returned.
+**
+** Registering a NULL function disables the callback.
+**
+** For the purposes of this API, a transaction is said to have been
+** rolled back if an explicit "ROLLBACK" statement is executed, or
+** an error or constraint causes an implicit rollback to occur.
+** The rollback callback is not invoked if a transaction is
+** automatically rolled back because the database connection is closed.
+** The rollback callback is not invoked if a transaction is
+** rolled back because a commit callback returned non-zero.
+** <todo> Check on this </todo>
+**
+** These are experimental interfaces and are subject to change.
+**
+** INVARIANTS:
+**
+** {F12951} The [sqlite3_commit_hook(D,F,P)] interface registers the
+** callback function F to be invoked with argument P whenever
+** a transaction commits on [database connection] D.
+**
+** {F12952} The [sqlite3_commit_hook(D,F,P)] interface returns the P
+** argument from the previous call with the same
+** [database connection ] D , or NULL on the first call
+** for a particular [database connection] D.
+**
+** {F12953} Each call to [sqlite3_commit_hook()] overwrites the callback
+** registered by prior calls.
+**
+** {F12954} If the F argument to [sqlite3_commit_hook(D,F,P)] is NULL
+** then the commit hook callback is cancelled and no callback
+** is invoked when a transaction commits.
+**
+** {F12955} If the commit callback returns non-zero then the commit is
+** converted into a rollback.
+**
+** {F12961} The [sqlite3_rollback_hook(D,F,P)] interface registers the
+** callback function F to be invoked with argument P whenever
+** a transaction rolls back on [database connection] D.
+**
+** {F12962} The [sqlite3_rollback_hook(D,F,P)] interface returns the P
+** argument from the previous call with the same
+** [database connection ] D , or NULL on the first call
+** for a particular [database connection] D.
+**
+** {F12963} Each call to [sqlite3_rollback_hook()] overwrites the callback
+** registered by prior calls.
+**
+** {F12964} If the F argument to [sqlite3_rollback_hook(D,F,P)] is NULL
+** then the rollback hook callback is cancelled and no callback
+** is invoked when a transaction rolls back.
+*/
+void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*);
+void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
+
+/*
+** CAPI3REF: Data Change Notification Callbacks {F12970}
+**
+** The sqlite3_update_hook() interface
+** registers a callback function with the database connection identified by the
+** first argument to be invoked whenever a row is updated, inserted or deleted.
+** Any callback set by a previous call to this function for the same
+** database connection is overridden.
+**
+** The second argument is a pointer to the function to invoke when a
+** row is updated, inserted or deleted.
+** The first argument to the callback is
+** a copy of the third argument to sqlite3_update_hook().
+** The second callback
+** argument is one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE],
+** depending on the operation that caused the callback to be invoked.
+** The third and
+** fourth arguments to the callback contain pointers to the database and
+** table name containing the affected row.
+** The final callback parameter is
+** the rowid of the row.
+** In the case of an update, this is the rowid after
+** the update takes place.
+**
+** The update hook is not invoked when internal system tables are
+** modified (i.e. sqlite_master and sqlite_sequence).
+**
+** If another function was previously registered, its pArg value
+** is returned. Otherwise NULL is returned.
+**
+** INVARIANTS:
+**
+** {F12971} The [sqlite3_update_hook(D,F,P)] interface causes callback
+** function F to be invoked with first parameter P whenever
+** a table row is modified, inserted, or deleted on
+** [database connection] D.
+**
+** {F12973} The [sqlite3_update_hook(D,F,P)] interface returns the value
+** of P for the previous call on the same [database connection] D,
+** or NULL for the first call.
+**
+** {F12975} If the update hook callback F in [sqlite3_update_hook(D,F,P)]
+** is NULL then the no update callbacks are made.
+**
+** {F12977} Each call to [sqlite3_update_hook(D,F,P)] overrides prior calls
+** to the same interface on the same [database connection] D.
+**
+** {F12979} The update hook callback is not invoked when internal system
+** tables such as sqlite_master and sqlite_sequence are modified.
+**
+** {F12981} The second parameter to the update callback
+** is one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE],
+** depending on the operation that caused the callback to be invoked.
+**
+** {F12983} The third and fourth arguments to the callback contain pointers
+** to zero-terminated UTF-8 strings which are the names of the
+** database and table that is being updated.
+
+** {F12985} The final callback parameter is the rowid of the row after
+** the change occurs.
+*/
+void *sqlite3_update_hook(
+ sqlite3*,
+ void(*)(void *,int ,char const *,char const *,sqlite3_int64),
+ void*
+);
+
+/*
+** CAPI3REF: Enable Or Disable Shared Pager Cache {F10330}
+**
+** This routine enables or disables the sharing of the database cache
+** and schema data structures between connections to the same database.
+** Sharing is enabled if the argument is true and disabled if the argument
+** is false.
+**
+** Cache sharing is enabled and disabled
+** for an entire process. {END} This is a change as of SQLite version 3.5.0.
+** In prior versions of SQLite, sharing was
+** enabled or disabled for each thread separately.
+**
+** The cache sharing mode set by this interface effects all subsequent
+** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()].
+** Existing database connections continue use the sharing mode
+** that was in effect at the time they were opened.
+**
+** Virtual tables cannot be used with a shared cache. When shared
+** cache is enabled, the [sqlite3_create_module()] API used to register
+** virtual tables will always return an error.
+**
+** This routine returns [SQLITE_OK] if shared cache was
+** enabled or disabled successfully. An [error code]
+** is returned otherwise.
+**
+** Shared cache is disabled by default. But this might change in
+** future releases of SQLite. Applications that care about shared
+** cache setting should set it explicitly.
+**
+** INVARIANTS:
+**
+** {F10331} A successful invocation of [sqlite3_enable_shared_cache(B)]
+** will enable or disable shared cache mode for any subsequently
+** created [database connection] in the same process.
+**
+** {F10336} When shared cache is enabled, the [sqlite3_create_module()]
+** interface will always return an error.
+**
+** {F10337} The [sqlite3_enable_shared_cache(B)] interface returns
+** [SQLITE_OK] if shared cache was enabled or disabled successfully.
+**
+** {F10339} Shared cache is disabled by default.
+*/
+int sqlite3_enable_shared_cache(int);
+
+/*
+** CAPI3REF: Attempt To Free Heap Memory {F17340}
+**
+** The sqlite3_release_memory() interface attempts to
+** free N bytes of heap memory by deallocating non-essential memory
+** allocations held by the database labrary. {END} Memory used
+** to cache database pages to improve performance is an example of
+** non-essential memory. Sqlite3_release_memory() returns
+** the number of bytes actually freed, which might be more or less
+** than the amount requested.
+**
+** INVARIANTS:
+**
+** {F17341} The [sqlite3_release_memory(N)] interface attempts to
+** free N bytes of heap memory by deallocating non-essential
+** memory allocations held by the database labrary.
+**
+** {F16342} The [sqlite3_release_memory(N)] returns the number
+** of bytes actually freed, which might be more or less
+** than the amount requested.
+*/
+int sqlite3_release_memory(int);
+
+/*
+** CAPI3REF: Impose A Limit On Heap Size {F17350}
+**
+** The sqlite3_soft_heap_limit() interface
+** places a "soft" limit on the amount of heap memory that may be allocated
+** by SQLite. If an internal allocation is requested
+** that would exceed the soft heap limit, [sqlite3_release_memory()] is
+** invoked one or more times to free up some space before the allocation
+** is made.
+**
+** The limit is called "soft", because if
+** [sqlite3_release_memory()] cannot
+** free sufficient memory to prevent the limit from being exceeded,
+** the memory is allocated anyway and the current operation proceeds.
+**
+** A negative or zero value for N means that there is no soft heap limit and
+** [sqlite3_release_memory()] will only be called when memory is exhausted.
+** The default value for the soft heap limit is zero.
+**
+** SQLite makes a best effort to honor the soft heap limit.
+** But if the soft heap limit cannot honored, execution will
+** continue without error or notification. This is why the limit is
+** called a "soft" limit. It is advisory only.
+**
+** Prior to SQLite version 3.5.0, this routine only constrained the memory
+** allocated by a single thread - the same thread in which this routine
+** runs. Beginning with SQLite version 3.5.0, the soft heap limit is
+** applied to all threads. The value specified for the soft heap limit
+** is an upper bound on the total memory allocation for all threads. In
+** version 3.5.0 there is no mechanism for limiting the heap usage for
+** individual threads.
+**
+** INVARIANTS:
+**
+** {F16351} The [sqlite3_soft_heap_limit(N)] interface places a soft limit
+** of N bytes on the amount of heap memory that may be allocated
+** using [sqlite3_malloc()] or [sqlite3_realloc()] at any point
+** in time.
+**
+** {F16352} If a call to [sqlite3_malloc()] or [sqlite3_realloc()] would
+** cause the total amount of allocated memory to exceed the
+** soft heap limit, then [sqlite3_release_memory()] is invoked
+** in an attempt to reduce the memory usage prior to proceeding
+** with the memory allocation attempt.
+**
+** {F16353} Calls to [sqlite3_malloc()] or [sqlite3_realloc()] that trigger
+** attempts to reduce memory usage through the soft heap limit
+** mechanism continue even if the attempt to reduce memory
+** usage is unsuccessful.
+**
+** {F16354} A negative or zero value for N in a call to
+** [sqlite3_soft_heap_limit(N)] means that there is no soft
+** heap limit and [sqlite3_release_memory()] will only be
+** called when memory is completely exhausted.
+**
+** {F16355} The default value for the soft heap limit is zero.
+**
+** {F16358} Each call to [sqlite3_soft_heap_limit(N)] overrides the
+** values set by all prior calls.
+*/
+void sqlite3_soft_heap_limit(int);
+
+/*
+** CAPI3REF: Extract Metadata About A Column Of A Table {F12850}
+**
+** This routine
+** returns meta-data about a specific column of a specific database
+** table accessible using the connection handle passed as the first function
+** argument.
+**
+** The column is identified by the second, third and fourth parameters to
+** this function. The second parameter is either the name of the database
+** (i.e. "main", "temp" or an attached database) containing the specified
+** table or NULL. If it is NULL, then all attached databases are searched
+** for the table using the same algorithm as the database engine uses to
+** resolve unqualified table references.
+**
+** The third and fourth parameters to this function are the table and column
+** name of the desired column, respectively. Neither of these parameters
+** may be NULL.
+**
+** Meta information is returned by writing to the memory locations passed as
+** the 5th and subsequent parameters to this function. Any of these
+** arguments may be NULL, in which case the corresponding element of meta
+** information is ommitted.
+**
+** <pre>
+** Parameter Output Type Description
+** -----------------------------------
+**
+** 5th const char* Data type
+** 6th const char* Name of the default collation sequence
+** 7th int True if the column has a NOT NULL constraint
+** 8th int True if the column is part of the PRIMARY KEY
+** 9th int True if the column is AUTOINCREMENT
+** </pre>
+**
+**
+** The memory pointed to by the character pointers returned for the
+** declaration type and collation sequence is valid only until the next
+** call to any sqlite API function.
+**
+** If the specified table is actually a view, then an error is returned.
+**
+** If the specified column is "rowid", "oid" or "_rowid_" and an
+** INTEGER PRIMARY KEY column has been explicitly declared, then the output
+** parameters are set for the explicitly declared column. If there is no
+** explicitly declared IPK column, then the output parameters are set as
+** follows:
+**
+** <pre>
+** data type: "INTEGER"
+** collation sequence: "BINARY"
+** not null: 0
+** primary key: 1
+** auto increment: 0
+** </pre>
+**
+** This function may load one or more schemas from database files. If an
+** error occurs during this process, or if the requested table or column
+** cannot be found, an SQLITE error code is returned and an error message
+** left in the database handle (to be retrieved using sqlite3_errmsg()).
+**
+** This API is only available if the library was compiled with the
+** SQLITE_ENABLE_COLUMN_METADATA preprocessor symbol defined.
+*/
+int sqlite3_table_column_metadata(
+ sqlite3 *db, /* Connection handle */
+ const char *zDbName, /* Database name or NULL */
+ const char *zTableName, /* Table name */
+ const char *zColumnName, /* Column name */
+ char const **pzDataType, /* OUTPUT: Declared data type */
+ char const **pzCollSeq, /* OUTPUT: Collation sequence name */
+ int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */
+ int *pPrimaryKey, /* OUTPUT: True if column part of PK */
+ int *pAutoinc /* OUTPUT: True if column is auto-increment */
+);
+
+/*
+** CAPI3REF: Load An Extension {F12600}
+**
+** {F12601} The sqlite3_load_extension() interface
+** attempts to load an SQLite extension library contained in the file
+** zFile. {F12602} The entry point is zProc. {F12603} zProc may be 0
+** in which case the name of the entry point defaults
+** to "sqlite3_extension_init".
+**
+** {F12604} The sqlite3_load_extension() interface shall
+** return [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong.
+**
+** {F12605}
+** If an error occurs and pzErrMsg is not 0, then the
+** sqlite3_load_extension() interface shall attempt to fill *pzErrMsg with
+** error message text stored in memory obtained from [sqlite3_malloc()].
+** {END} The calling function should free this memory
+** by calling [sqlite3_free()].
+**
+** {F12606}
+** Extension loading must be enabled using [sqlite3_enable_load_extension()]
+** prior to calling this API or an error will be returned.
+*/
+int sqlite3_load_extension(
+ sqlite3 *db, /* Load the extension into this database connection */
+ const char *zFile, /* Name of the shared library containing extension */
+ const char *zProc, /* Entry point. Derived from zFile if 0 */
+ char **pzErrMsg /* Put error message here if not 0 */
+);
+
+/*
+** CAPI3REF: Enable Or Disable Extension Loading {F12620}
+**
+** So as not to open security holes in older applications that are
+** unprepared to deal with extension loading, and as a means of disabling
+** extension loading while evaluating user-entered SQL, the following
+** API is provided to turn the [sqlite3_load_extension()] mechanism on and
+** off. {F12622} It is off by default. {END} See ticket #1863.
+**
+** {F12621} Call the sqlite3_enable_load_extension() routine
+** with onoff==1 to turn extension loading on
+** and call it with onoff==0 to turn it back off again. {END}
+*/
+int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
+
+/*
+** CAPI3REF: Make Arrangements To Automatically Load An Extension {F12640}
+**
+** {F12641} This function
+** registers an extension entry point that is automatically invoked
+** whenever a new database connection is opened using
+** [sqlite3_open()], [sqlite3_open16()], or [sqlite3_open_v2()]. {END}
+**
+** This API can be invoked at program startup in order to register
+** one or more statically linked extensions that will be available
+** to all new database connections.
+**
+** {F12642} Duplicate extensions are detected so calling this routine multiple
+** times with the same extension is harmless.
+**
+** {F12643} This routine stores a pointer to the extension in an array
+** that is obtained from sqlite_malloc(). {END} If you run a memory leak
+** checker on your program and it reports a leak because of this
+** array, then invoke [sqlite3_reset_auto_extension()] prior
+** to shutdown to free the memory.
+**
+** {F12644} Automatic extensions apply across all threads. {END}
+**
+** This interface is experimental and is subject to change or
+** removal in future releases of SQLite.
+*/
+int sqlite3_auto_extension(void *xEntryPoint);
+
+
+/*
+** CAPI3REF: Reset Automatic Extension Loading {F12660}
+**
+** {F12661} This function disables all previously registered
+** automatic extensions. {END} This
+** routine undoes the effect of all prior [sqlite3_auto_extension()]
+** calls.
+**
+** {F12662} This call disabled automatic extensions in all threads. {END}
+**
+** This interface is experimental and is subject to change or
+** removal in future releases of SQLite.
+*/
+void sqlite3_reset_auto_extension(void);
+
+
+/*
+****** EXPERIMENTAL - subject to change without notice **************
+**
+** The interface to the virtual-table mechanism is currently considered
+** to be experimental. The interface might change in incompatible ways.
+** If this is a problem for you, do not use the interface at this time.
+**
+** When the virtual-table mechanism stablizes, we will declare the
+** interface fixed, support it indefinitely, and remove this comment.
+*/
+
+/*
+** Structures used by the virtual table interface
+*/
+typedef struct sqlite3_vtab sqlite3_vtab;
+typedef struct sqlite3_index_info sqlite3_index_info;
+typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor;
+typedef struct sqlite3_module sqlite3_module;
+
+/*
+** CAPI3REF: Virtual Table Object {F18000}
+** KEYWORDS: sqlite3_module
+**
+** A module is a class of virtual tables. Each module is defined
+** by an instance of the following structure. This structure consists
+** mostly of methods for the module.
+*/
+struct sqlite3_module {
+ int iVersion;
+ int (*xCreate)(sqlite3*, void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVTab, char**);
+ int (*xConnect)(sqlite3*, void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVTab, char**);
+ int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
+ int (*xDisconnect)(sqlite3_vtab *pVTab);
+ int (*xDestroy)(sqlite3_vtab *pVTab);
+ int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
+ int (*xClose)(sqlite3_vtab_cursor*);
+ int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv);
+ int (*xNext)(sqlite3_vtab_cursor*);
+ int (*xEof)(sqlite3_vtab_cursor*);
+ int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid);
+ int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *);
+ int (*xBegin)(sqlite3_vtab *pVTab);
+ int (*xSync)(sqlite3_vtab *pVTab);
+ int (*xCommit)(sqlite3_vtab *pVTab);
+ int (*xRollback)(sqlite3_vtab *pVTab);
+ int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
+ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ void **ppArg);
+
+ int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
+};
+
+/*
+** CAPI3REF: Virtual Table Indexing Information {F18100}
+** KEYWORDS: sqlite3_index_info
+**
+** The sqlite3_index_info structure and its substructures is used to
+** pass information into and receive the reply from the xBestIndex
+** method of an sqlite3_module. The fields under **Inputs** are the
+** inputs to xBestIndex and are read-only. xBestIndex inserts its
+** results into the **Outputs** fields.
+**
+** The aConstraint[] array records WHERE clause constraints of the
+** form:
+**
+** column OP expr
+**
+** Where OP is =, &lt;, &lt;=, &gt;, or &gt;=.
+** The particular operator is stored
+** in aConstraint[].op. The index of the column is stored in
+** aConstraint[].iColumn. aConstraint[].usable is TRUE if the
+** expr on the right-hand side can be evaluated (and thus the constraint
+** is usable) and false if it cannot.
+**
+** The optimizer automatically inverts terms of the form "expr OP column"
+** and makes other simplifications to the WHERE clause in an attempt to
+** get as many WHERE clause terms into the form shown above as possible.
+** The aConstraint[] array only reports WHERE clause terms in the correct
+** form that refer to the particular virtual table being queried.
+**
+** Information about the ORDER BY clause is stored in aOrderBy[].
+** Each term of aOrderBy records a column of the ORDER BY clause.
+**
+** The xBestIndex method must fill aConstraintUsage[] with information
+** about what parameters to pass to xFilter. If argvIndex>0 then
+** the right-hand side of the corresponding aConstraint[] is evaluated
+** and becomes the argvIndex-th entry in argv. If aConstraintUsage[].omit
+** is true, then the constraint is assumed to be fully handled by the
+** virtual table and is not checked again by SQLite.
+**
+** The idxNum and idxPtr values are recorded and passed into xFilter.
+** sqlite3_free() is used to free idxPtr if needToFreeIdxPtr is true.
+**
+** The orderByConsumed means that output from xFilter will occur in
+** the correct order to satisfy the ORDER BY clause so that no separate
+** sorting step is required.
+**
+** The estimatedCost value is an estimate of the cost of doing the
+** particular lookup. A full scan of a table with N entries should have
+** a cost of N. A binary search of a table of N entries should have a
+** cost of approximately log(N).
+*/
+struct sqlite3_index_info {
+ /* Inputs */
+ int nConstraint; /* Number of entries in aConstraint */
+ struct sqlite3_index_constraint {
+ int iColumn; /* Column on left-hand side of constraint */
+ unsigned char op; /* Constraint operator */
+ unsigned char usable; /* True if this constraint is usable */
+ int iTermOffset; /* Used internally - xBestIndex should ignore */
+ } *aConstraint; /* Table of WHERE clause constraints */
+ int nOrderBy; /* Number of terms in the ORDER BY clause */
+ struct sqlite3_index_orderby {
+ int iColumn; /* Column number */
+ unsigned char desc; /* True for DESC. False for ASC. */
+ } *aOrderBy; /* The ORDER BY clause */
+
+ /* Outputs */
+ struct sqlite3_index_constraint_usage {
+ int argvIndex; /* if >0, constraint is part of argv to xFilter */
+ unsigned char omit; /* Do not code a test for this constraint */
+ } *aConstraintUsage;
+ int idxNum; /* Number used to identify the index */
+ char *idxStr; /* String, possibly obtained from sqlite3_malloc */
+ int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */
+ int orderByConsumed; /* True if output is already ordered */
+ double estimatedCost; /* Estimated cost of using this index */
+};
+#define SQLITE_INDEX_CONSTRAINT_EQ 2
+#define SQLITE_INDEX_CONSTRAINT_GT 4
+#define SQLITE_INDEX_CONSTRAINT_LE 8
+#define SQLITE_INDEX_CONSTRAINT_LT 16
+#define SQLITE_INDEX_CONSTRAINT_GE 32
+#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+
+/*
+** CAPI3REF: Register A Virtual Table Implementation {F18200}
+**
+** This routine is used to register a new module name with an SQLite
+** connection. Module names must be registered before creating new
+** virtual tables on the module, or before using preexisting virtual
+** tables of the module.
+*/
+int sqlite3_create_module(
+ sqlite3 *db, /* SQLite connection to register module with */
+ const char *zName, /* Name of the module */
+ const sqlite3_module *, /* Methods for the module */
+ void * /* Client data for xCreate/xConnect */
+);
+
+/*
+** CAPI3REF: Register A Virtual Table Implementation {F18210}
+**
+** This routine is identical to the sqlite3_create_module() method above,
+** except that it allows a destructor function to be specified. It is
+** even more experimental than the rest of the virtual tables API.
+*/
+int sqlite3_create_module_v2(
+ sqlite3 *db, /* SQLite connection to register module with */
+ const char *zName, /* Name of the module */
+ const sqlite3_module *, /* Methods for the module */
+ void *, /* Client data for xCreate/xConnect */
+ void(*xDestroy)(void*) /* Module destructor function */
+);
+
+/*
+** CAPI3REF: Virtual Table Instance Object {F18010}
+** KEYWORDS: sqlite3_vtab
+**
+** Every module implementation uses a subclass of the following structure
+** to describe a particular instance of the module. Each subclass will
+** be tailored to the specific needs of the module implementation. The
+** purpose of this superclass is to define certain fields that are common
+** to all module implementations.
+**
+** Virtual tables methods can set an error message by assigning a
+** string obtained from sqlite3_mprintf() to zErrMsg. The method should
+** take care that any prior string is freed by a call to sqlite3_free()
+** prior to assigning a new string to zErrMsg. After the error message
+** is delivered up to the client application, the string will be automatically
+** freed by sqlite3_free() and the zErrMsg field will be zeroed. Note
+** that sqlite3_mprintf() and sqlite3_free() are used on the zErrMsg field
+** since virtual tables are commonly implemented in loadable extensions which
+** do not have access to sqlite3MPrintf() or sqlite3Free().
+*/
+struct sqlite3_vtab {
+ const sqlite3_module *pModule; /* The module for this virtual table */
+ int nRef; /* Used internally */
+ char *zErrMsg; /* Error message from sqlite3_mprintf() */
+ /* Virtual table implementations will typically add additional fields */
+};
+
+/*
+** CAPI3REF: Virtual Table Cursor Object {F18020}
+** KEYWORDS: sqlite3_vtab_cursor
+**
+** Every module implementation uses a subclass of the following structure
+** to describe cursors that point into the virtual table and are used
+** to loop through the virtual table. Cursors are created using the
+** xOpen method of the module. Each module implementation will define
+** the content of a cursor structure to suit its own needs.
+**
+** This superclass exists in order to define fields of the cursor that
+** are common to all implementations.
+*/
+struct sqlite3_vtab_cursor {
+ sqlite3_vtab *pVtab; /* Virtual table of this cursor */
+ /* Virtual table implementations will typically add additional fields */
+};
+
+/*
+** CAPI3REF: Declare The Schema Of A Virtual Table {F18280}
+**
+** The xCreate and xConnect methods of a module use the following API
+** to declare the format (the names and datatypes of the columns) of
+** the virtual tables they implement.
+*/
+int sqlite3_declare_vtab(sqlite3*, const char *zCreateTable);
+
+/*
+** CAPI3REF: Overload A Function For A Virtual Table {F18300}
+**
+** Virtual tables can provide alternative implementations of functions
+** using the xFindFunction method. But global versions of those functions
+** must exist in order to be overloaded.
+**
+** This API makes sure a global version of a function with a particular
+** name and number of parameters exists. If no such function exists
+** before this API is called, a new function is created. The implementation
+** of the new function always causes an exception to be thrown. So
+** the new function is not good for anything by itself. Its only
+** purpose is to be a place-holder function that can be overloaded
+** by virtual tables.
+**
+** This API should be considered part of the virtual table interface,
+** which is experimental and subject to change.
+*/
+int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg);
+
+/*
+** The interface to the virtual-table mechanism defined above (back up
+** to a comment remarkably similar to this one) is currently considered
+** to be experimental. The interface might change in incompatible ways.
+** If this is a problem for you, do not use the interface at this time.
+**
+** When the virtual-table mechanism stabilizes, we will declare the
+** interface fixed, support it indefinitely, and remove this comment.
+**
+****** EXPERIMENTAL - subject to change without notice **************
+*/
+
+/*
+** CAPI3REF: A Handle To An Open BLOB {F17800}
+**
+** An instance of this object represents an open BLOB on which
+** incremental I/O can be preformed.
+** Objects of this type are created by
+** [sqlite3_blob_open()] and destroyed by [sqlite3_blob_close()].
+** The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces
+** can be used to read or write small subsections of the blob.
+** The [sqlite3_blob_bytes()] interface returns the size of the
+** blob in bytes.
+*/
+typedef struct sqlite3_blob sqlite3_blob;
+
+/*
+** CAPI3REF: Open A BLOB For Incremental I/O {F17810}
+**
+** This interfaces opens a handle to the blob located
+** in row iRow, column zColumn, table zTable in database zDb;
+** in other words, the same blob that would be selected by:
+**
+** <pre>
+** SELECT zColumn FROM zDb.zTable WHERE rowid = iRow;
+** </pre> {END}
+**
+** If the flags parameter is non-zero, the blob is opened for
+** read and write access. If it is zero, the blob is opened for read
+** access.
+**
+** Note that the database name is not the filename that contains
+** the database but rather the symbolic name of the database that
+** is assigned when the database is connected using [ATTACH].
+** For the main database file, the database name is "main". For
+** TEMP tables, the database name is "temp".
+**
+** On success, [SQLITE_OK] is returned and the new
+** [sqlite3_blob | blob handle] is written to *ppBlob.
+** Otherwise an error code is returned and
+** any value written to *ppBlob should not be used by the caller.
+** This function sets the database-handle error code and message
+** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()].
+**
+** INVARIANTS:
+**
+** {F17813} A successful invocation of the [sqlite3_blob_open(D,B,T,C,R,F,P)]
+** interface opens an [sqlite3_blob] object P on the blob
+** in column C of table T in database B on [database connection] D.
+**
+** {F17814} A successful invocation of [sqlite3_blob_open(D,...)] starts
+** a new transaction on [database connection] D if that connection
+** is not already in a transaction.
+**
+** {F17816} The [sqlite3_blob_open(D,B,T,C,R,F,P)] interface opens the blob
+** for read and write access if and only if the F parameter
+** is non-zero.
+**
+** {F17819} The [sqlite3_blob_open()] interface returns [SQLITE_OK] on
+** success and an appropriate [error code] on failure.
+**
+** {F17821} If an error occurs during evaluation of [sqlite3_blob_open(D,...)]
+** then subsequent calls to [sqlite3_errcode(D)],
+** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return
+** information approprate for that error.
+*/
+int sqlite3_blob_open(
+ sqlite3*,
+ const char *zDb,
+ const char *zTable,
+ const char *zColumn,
+ sqlite3_int64 iRow,
+ int flags,
+ sqlite3_blob **ppBlob
+);
+
+/*
+** CAPI3REF: Close A BLOB Handle {F17830}
+**
+** Close an open [sqlite3_blob | blob handle].
+**
+** Closing a BLOB shall cause the current transaction to commit
+** if there are no other BLOBs, no pending prepared statements, and the
+** database connection is in autocommit mode.
+** If any writes were made to the BLOB, they might be held in cache
+** until the close operation if they will fit. {END}
+** Closing the BLOB often forces the changes
+** out to disk and so if any I/O errors occur, they will likely occur
+** at the time when the BLOB is closed. {F17833} Any errors that occur during
+** closing are reported as a non-zero return value.
+**
+** The BLOB is closed unconditionally. Even if this routine returns
+** an error code, the BLOB is still closed.
+**
+** INVARIANTS:
+**
+** {F17833} The [sqlite3_blob_close(P)] interface closes an
+** [sqlite3_blob] object P previously opened using
+** [sqlite3_blob_open()].
+**
+** {F17836} Closing an [sqlite3_blob] object using
+** [sqlite3_blob_close()] shall cause the current transaction to
+** commit if there are no other open [sqlite3_blob] objects
+** or [prepared statements] on the same [database connection] and
+** the [database connection] is in
+** [sqlite3_get_autocommit | autocommit mode].
+**
+** {F17839} The [sqlite3_blob_close(P)] interfaces closes the
+** [sqlite3_blob] object P unconditionally, even if
+** [sqlite3_blob_close(P)] returns something other than [SQLITE_OK].
+**
+*/
+int sqlite3_blob_close(sqlite3_blob *);
+
+/*
+** CAPI3REF: Return The Size Of An Open BLOB {F17840}
+**
+** Return the size in bytes of the blob accessible via the open
+** [sqlite3_blob] object in its only argument.
+**
+** INVARIANTS:
+**
+** {F17843} The [sqlite3_blob_bytes(P)] interface returns the size
+** in bytes of the BLOB that the [sqlite3_blob] object P
+** refers to.
+*/
+int sqlite3_blob_bytes(sqlite3_blob *);
+
+/*
+** CAPI3REF: Read Data From A BLOB Incrementally {F17850}
+**
+** This function is used to read data from an open
+** [sqlite3_blob | blob-handle] into a caller supplied buffer.
+** N bytes of data are copied into buffer
+** Z from the open blob, starting at offset iOffset.
+**
+** If offset iOffset is less than N bytes from the end of the blob,
+** [SQLITE_ERROR] is returned and no data is read. If N or iOffset is
+** less than zero [SQLITE_ERROR] is returned and no data is read.
+**
+** On success, SQLITE_OK is returned. Otherwise, an
+** [error code] or an [extended error code] is returned.
+**
+** INVARIANTS:
+**
+** {F17853} The [sqlite3_blob_read(P,Z,N,X)] interface reads N bytes
+** beginning at offset X from
+** the blob that [sqlite3_blob] object P refers to
+** and writes those N bytes into buffer Z.
+**
+** {F17856} In [sqlite3_blob_read(P,Z,N,X)] if the size of the blob
+** is less than N+X bytes, then the function returns [SQLITE_ERROR]
+** and nothing is read from the blob.
+**
+** {F17859} In [sqlite3_blob_read(P,Z,N,X)] if X or N is less than zero
+** then the function returns [SQLITE_ERROR]
+** and nothing is read from the blob.
+**
+** {F17862} The [sqlite3_blob_read(P,Z,N,X)] interface returns [SQLITE_OK]
+** if N bytes where successfully read into buffer Z.
+**
+** {F17865} If the requested read could not be completed,
+** the [sqlite3_blob_read(P,Z,N,X)] interface returns an
+** appropriate [error code] or [extended error code].
+**
+** {F17868} If an error occurs during evaluation of [sqlite3_blob_read(P,...)]
+** then subsequent calls to [sqlite3_errcode(D)],
+** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return
+** information approprate for that error, where D is the
+** database handle that was used to open blob handle P.
+*/
+int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
+
+/*
+** CAPI3REF: Write Data Into A BLOB Incrementally {F17870}
+**
+** This function is used to write data into an open
+** [sqlite3_blob | blob-handle] from a user supplied buffer.
+** n bytes of data are copied from the buffer
+** pointed to by z into the open blob, starting at offset iOffset.
+**
+** If the [sqlite3_blob | blob-handle] passed as the first argument
+** was not opened for writing (the flags parameter to [sqlite3_blob_open()]
+*** was zero), this function returns [SQLITE_READONLY].
+**
+** This function may only modify the contents of the blob; it is
+** not possible to increase the size of a blob using this API.
+** If offset iOffset is less than n bytes from the end of the blob,
+** [SQLITE_ERROR] is returned and no data is written. If n is
+** less than zero [SQLITE_ERROR] is returned and no data is written.
+**
+** On success, SQLITE_OK is returned. Otherwise, an
+** [error code] or an [extended error code] is returned.
+**
+** INVARIANTS:
+**
+** {F17873} The [sqlite3_blob_write(P,Z,N,X)] interface writes N bytes
+** from buffer Z into
+** the blob that [sqlite3_blob] object P refers to
+** beginning at an offset of X into the blob.
+**
+** {F17875} The [sqlite3_blob_write(P,Z,N,X)] interface returns
+** [SQLITE_READONLY] if the [sqlite3_blob] object P was
+** [sqlite3_blob_open | opened] for reading only.
+**
+** {F17876} In [sqlite3_blob_write(P,Z,N,X)] if the size of the blob
+** is less than N+X bytes, then the function returns [SQLITE_ERROR]
+** and nothing is written into the blob.
+**
+** {F17879} In [sqlite3_blob_write(P,Z,N,X)] if X or N is less than zero
+** then the function returns [SQLITE_ERROR]
+** and nothing is written into the blob.
+**
+** {F17882} The [sqlite3_blob_write(P,Z,N,X)] interface returns [SQLITE_OK]
+** if N bytes where successfully written into blob.
+**
+** {F17885} If the requested write could not be completed,
+** the [sqlite3_blob_write(P,Z,N,X)] interface returns an
+** appropriate [error code] or [extended error code].
+**
+** {F17888} If an error occurs during evaluation of [sqlite3_blob_write(D,...)]
+** then subsequent calls to [sqlite3_errcode(D)],
+** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return
+** information approprate for that error.
+*/
+int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
+
+/*
+** CAPI3REF: Virtual File System Objects {F11200}
+**
+** A virtual filesystem (VFS) is an [sqlite3_vfs] object
+** that SQLite uses to interact
+** with the underlying operating system. Most SQLite builds come with a
+** single default VFS that is appropriate for the host computer.
+** New VFSes can be registered and existing VFSes can be unregistered.
+** The following interfaces are provided.
+**
+** The sqlite3_vfs_find() interface returns a pointer to
+** a VFS given its name. Names are case sensitive.
+** Names are zero-terminated UTF-8 strings.
+** If there is no match, a NULL
+** pointer is returned. If zVfsName is NULL then the default
+** VFS is returned.
+**
+** New VFSes are registered with sqlite3_vfs_register().
+** Each new VFS becomes the default VFS if the makeDflt flag is set.
+** The same VFS can be registered multiple times without injury.
+** To make an existing VFS into the default VFS, register it again
+** with the makeDflt flag set. If two different VFSes with the
+** same name are registered, the behavior is undefined. If a
+** VFS is registered with a name that is NULL or an empty string,
+** then the behavior is undefined.
+**
+** Unregister a VFS with the sqlite3_vfs_unregister() interface.
+** If the default VFS is unregistered, another VFS is chosen as
+** the default. The choice for the new VFS is arbitrary.
+**
+** INVARIANTS:
+**
+** {F11203} The [sqlite3_vfs_find(N)] interface returns a pointer to the
+** registered [sqlite3_vfs] object whose name exactly matches
+** the zero-terminated UTF-8 string N, or it returns NULL if
+** there is no match.
+**
+** {F11206} If the N parameter to [sqlite3_vfs_find(N)] is NULL then
+** the function returns a pointer to the default [sqlite3_vfs]
+** object if there is one, or NULL if there is no default
+** [sqlite3_vfs] object.
+**
+** {F11209} The [sqlite3_vfs_register(P,F)] interface registers the
+** well-formed [sqlite3_vfs] object P using the name given
+** by the zName field of the object.
+**
+** {F11212} Using the [sqlite3_vfs_register(P,F)] interface to register
+** the same [sqlite3_vfs] object multiple times is a harmless no-op.
+**
+** {F11215} The [sqlite3_vfs_register(P,F)] interface makes the
+** the [sqlite3_vfs] object P the default [sqlite3_vfs] object
+** if F is non-zero.
+**
+** {F11218} The [sqlite3_vfs_unregister(P)] interface unregisters the
+** [sqlite3_vfs] object P so that it is no longer returned by
+** subsequent calls to [sqlite3_vfs_find()].
+*/
+sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);
+int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);
+int sqlite3_vfs_unregister(sqlite3_vfs*);
+
+/*
+** CAPI3REF: Mutexes {F17000}
+**
+** The SQLite core uses these routines for thread
+** synchronization. Though they are intended for internal
+** use by SQLite, code that links against SQLite is
+** permitted to use any of these routines.
+**
+** The SQLite source code contains multiple implementations
+** of these mutex routines. An appropriate implementation
+** is selected automatically at compile-time. The following
+** implementations are available in the SQLite core:
+**
+** <ul>
+** <li> SQLITE_MUTEX_OS2
+** <li> SQLITE_MUTEX_PTHREAD
+** <li> SQLITE_MUTEX_W32
+** <li> SQLITE_MUTEX_NOOP
+** </ul>
+**
+** The SQLITE_MUTEX_NOOP implementation is a set of routines
+** that does no real locking and is appropriate for use in
+** a single-threaded application. The SQLITE_MUTEX_OS2,
+** SQLITE_MUTEX_PTHREAD, and SQLITE_MUTEX_W32 implementations
+** are appropriate for use on os/2, unix, and windows.
+**
+** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor
+** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex
+** implementation is included with the library. The
+** mutex interface routines defined here become external
+** references in the SQLite library for which implementations
+** must be provided by the application. This facility allows an
+** application that links against SQLite to provide its own mutex
+** implementation without having to modify the SQLite core.
+**
+** {F17011} The sqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. {F17012} If it returns NULL
+** that means that a mutex could not be allocated. {F17013} SQLite
+** will unwind its stack and return an error. {F17014} The argument
+** to sqlite3_mutex_alloc() is one of these integer constants:
+**
+** <ul>
+** <li> SQLITE_MUTEX_FAST
+** <li> SQLITE_MUTEX_RECURSIVE
+** <li> SQLITE_MUTEX_STATIC_MASTER
+** <li> SQLITE_MUTEX_STATIC_MEM
+** <li> SQLITE_MUTEX_STATIC_MEM2
+** <li> SQLITE_MUTEX_STATIC_PRNG
+** <li> SQLITE_MUTEX_STATIC_LRU
+** <li> SQLITE_MUTEX_STATIC_LRU2
+** </ul> {END}
+**
+** {F17015} The first two constants cause sqlite3_mutex_alloc() to create
+** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
+** is used but not necessarily so when SQLITE_MUTEX_FAST is used. {END}
+** The mutex implementation does not need to make a distinction
+** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does
+** not want to. {F17016} But SQLite will only request a recursive mutex in
+** cases where it really needs one. {END} If a faster non-recursive mutex
+** implementation is available on the host platform, the mutex subsystem
+** might return such a mutex in response to SQLITE_MUTEX_FAST.
+**
+** {F17017} The other allowed parameters to sqlite3_mutex_alloc() each return
+** a pointer to a static preexisting mutex. {END} Four static mutexes are
+** used by the current version of SQLite. Future versions of SQLite
+** may add additional static mutexes. Static mutexes are for internal
+** use by SQLite only. Applications that use SQLite mutexes should
+** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or
+** SQLITE_MUTEX_RECURSIVE.
+**
+** {F17018} Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
+** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** returns a different mutex on every call. {F17034} But for the static
+** mutex types, the same mutex is returned on every call that has
+** the same type number. {END}
+**
+** {F17019} The sqlite3_mutex_free() routine deallocates a previously
+** allocated dynamic mutex. {F17020} SQLite is careful to deallocate every
+** dynamic mutex that it allocates. {U17021} The dynamic mutexes must not be in
+** use when they are deallocated. {U17022} Attempting to deallocate a static
+** mutex results in undefined behavior. {F17023} SQLite never deallocates
+** a static mutex. {END}
+**
+** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** to enter a mutex. {F17024} If another thread is already within the mutex,
+** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
+** SQLITE_BUSY. {F17025} The sqlite3_mutex_try() interface returns SQLITE_OK
+** upon successful entry. {F17026} Mutexes created using
+** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread.
+** {F17027} In such cases the,
+** mutex must be exited an equal number of times before another thread
+** can enter. {U17028} If the same thread tries to enter any other
+** kind of mutex more than once, the behavior is undefined.
+** {F17029} SQLite will never exhibit
+** such behavior in its own use of mutexes. {END}
+**
+** Some systems (ex: windows95) do not the operation implemented by
+** sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() will
+** always return SQLITE_BUSY. {F17030} The SQLite core only ever uses
+** sqlite3_mutex_try() as an optimization so this is acceptable behavior. {END}
+**
+** {F17031} The sqlite3_mutex_leave() routine exits a mutex that was
+** previously entered by the same thread. {U17032} The behavior
+** is undefined if the mutex is not currently entered by the
+** calling thread or is not currently allocated. {F17033} SQLite will
+** never do either. {END}
+**
+** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()].
+*/
+sqlite3_mutex *sqlite3_mutex_alloc(int);
+void sqlite3_mutex_free(sqlite3_mutex*);
+void sqlite3_mutex_enter(sqlite3_mutex*);
+int sqlite3_mutex_try(sqlite3_mutex*);
+void sqlite3_mutex_leave(sqlite3_mutex*);
+
+/*
+** CAPI3REF: Mutex Verifcation Routines {F17080}
+**
+** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines
+** are intended for use inside assert() statements. {F17081} The SQLite core
+** never uses these routines except inside an assert() and applications
+** are advised to follow the lead of the core. {F17082} The core only
+** provides implementations for these routines when it is compiled
+** with the SQLITE_DEBUG flag. {U17087} External mutex implementations
+** are only required to provide these routines if SQLITE_DEBUG is
+** defined and if NDEBUG is not defined.
+**
+** {F17083} These routines should return true if the mutex in their argument
+** is held or not held, respectively, by the calling thread. {END}
+**
+** {X17084} The implementation is not required to provided versions of these
+** routines that actually work.
+** If the implementation does not provide working
+** versions of these routines, it should at least provide stubs
+** that always return true so that one does not get spurious
+** assertion failures. {END}
+**
+** {F17085} If the argument to sqlite3_mutex_held() is a NULL pointer then
+** the routine should return 1. {END} This seems counter-intuitive since
+** clearly the mutex cannot be held if it does not exist. But the
+** the reason the mutex does not exist is because the build is not
+** using mutexes. And we do not want the assert() containing the
+** call to sqlite3_mutex_held() to fail, so a non-zero return is
+** the appropriate thing to do. {F17086} The sqlite3_mutex_notheld()
+** interface should also return 1 when given a NULL pointer.
+*/
+int sqlite3_mutex_held(sqlite3_mutex*);
+int sqlite3_mutex_notheld(sqlite3_mutex*);
+
+/*
+** CAPI3REF: Mutex Types {F17001}
+**
+** {F17002} The [sqlite3_mutex_alloc()] interface takes a single argument
+** which is one of these integer constants. {END}
+*/
+#define SQLITE_MUTEX_FAST 0
+#define SQLITE_MUTEX_RECURSIVE 1
+#define SQLITE_MUTEX_STATIC_MASTER 2
+#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */
+#define SQLITE_MUTEX_STATIC_MEM2 4 /* sqlite3_release_memory() */
+#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */
+#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */
+#define SQLITE_MUTEX_STATIC_LRU2 7 /* lru page list */
+
+/*
+** CAPI3REF: Low-Level Control Of Database Files {F11300}
+**
+** {F11301} The [sqlite3_file_control()] interface makes a direct call to the
+** xFileControl method for the [sqlite3_io_methods] object associated
+** with a particular database identified by the second argument. {F11302} The
+** name of the database is the name assigned to the database by the
+** <a href="lang_attach.html">ATTACH</a> SQL command that opened the
+** database. {F11303} To control the main database file, use the name "main"
+** or a NULL pointer. {F11304} The third and fourth parameters to this routine
+** are passed directly through to the second and third parameters of
+** the xFileControl method. {F11305} The return value of the xFileControl
+** method becomes the return value of this routine.
+**
+** {F11306} If the second parameter (zDbName) does not match the name of any
+** open database file, then SQLITE_ERROR is returned. {F11307} This error
+** code is not remembered and will not be recalled by [sqlite3_errcode()]
+** or [sqlite3_errmsg()]. {U11308} The underlying xFileControl method might
+** also return SQLITE_ERROR. {U11309} There is no way to distinguish between
+** an incorrect zDbName and an SQLITE_ERROR return from the underlying
+** xFileControl method. {END}
+**
+** See also: [SQLITE_FCNTL_LOCKSTATE]
+*/
+int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*);
+
+/*
+** CAPI3REF: Testing Interface {F11400}
+**
+** The sqlite3_test_control() interface is used to read out internal
+** state of SQLite and to inject faults into SQLite for testing
+** purposes. The first parameter a operation code that determines
+** the number, meaning, and operation of all subsequent parameters.
+**
+** This interface is not for use by applications. It exists solely
+** for verifying the correct operation of the SQLite library. Depending
+** on how the SQLite library is compiled, this interface might not exist.
+**
+** The details of the operation codes, their meanings, the parameters
+** they take, and what they do are all subject to change without notice.
+** Unlike most of the SQLite API, this function is not guaranteed to
+** operate consistently from one release to the next.
+*/
+int sqlite3_test_control(int op, ...);
+
+/*
+** CAPI3REF: Testing Interface Operation Codes {F11410}
+**
+** These constants are the valid operation code parameters used
+** as the first argument to [sqlite3_test_control()].
+**
+** These parameters and their meansing are subject to change
+** without notice. These values are for testing purposes only.
+** Applications should not use any of these parameters or the
+** [sqlite3_test_control()] interface.
+*/
+#define SQLITE_TESTCTRL_FAULT_CONFIG 1
+#define SQLITE_TESTCTRL_FAULT_FAILURES 2
+#define SQLITE_TESTCTRL_FAULT_BENIGN_FAILURES 3
+#define SQLITE_TESTCTRL_FAULT_PENDING 4
+#define SQLITE_TESTCTRL_PRNG_SAVE 5
+#define SQLITE_TESTCTRL_PRNG_RESTORE 6
+#define SQLITE_TESTCTRL_PRNG_RESET 7
+#define SQLITE_TESTCTRL_BITVEC_TEST 8
+
+
+/*
+** Undo the hack that converts floating point types to integer for
+** builds on processors without floating point support.
+*/
+#ifdef SQLITE_OMIT_FLOATING_POINT
+# undef double
+#endif
+
+#ifdef __cplusplus
+} /* End of the 'extern "C"' block */
+#endif
+#endif
diff --git a/src/libs/themeengine/Makefile.am b/src/libs/themeengine/Makefile.am
new file mode 100644
index 00000000..f2e5f4ce
--- /dev/null
+++ b/src/libs/themeengine/Makefile.am
@@ -0,0 +1,12 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libthemeengine.la
+
+libthemeengine_la_SOURCES = theme.cpp themeengine.cpp texture.cpp
+
+libthemeengine_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/ \
+ $(all_includes)
diff --git a/src/libs/themeengine/texture.cpp b/src/libs/themeengine/texture.cpp
new file mode 100644
index 00000000..e728dd59
--- /dev/null
+++ b/src/libs/themeengine/texture.cpp
@@ -0,0 +1,495 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-26
+ * Description : texture pixmap methods
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * Adapted from fluxbox: Texture/TextureRender
+ *
+ * Texture.cc for Fluxbox Window Manager
+ * Copyright (c) 2002-2003 Henrik Kinnunen <fluxbox@users.sourceforge.net>
+ *
+ * from Image.cc for Blackbox - an X11 Window manager
+ * Copyright (c) 1997 - 2000 Brad Hughes (bhughes@tcac.net)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstring>
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+
+// Local includes.
+
+#include "theme.h"
+#include "texture.h"
+
+namespace Digikam
+{
+
+class TexturePriv
+{
+public:
+
+ TexturePriv()
+ {
+ red = 0;
+ green = 0;
+ blue = 0;
+ }
+
+ bool border;
+
+ unsigned char *red;
+ unsigned char *green;
+ unsigned char *blue;
+
+ int width;
+ int height;
+
+ TQPixmap pixmap;
+
+ TQColor color0;
+ TQColor color1;
+ TQColor borderColor;
+
+ Theme::Bevel bevel;
+ Theme::Gradient gradient;
+};
+
+Texture::Texture(int w, int h, const TQColor& from, const TQColor& to,
+ Theme::Bevel bevel, Theme::Gradient gradient,
+ bool border, const TQColor& borderColor)
+{
+ d = new TexturePriv;
+
+ d->bevel = bevel;
+ d->gradient = gradient;
+ d->border = border;
+ d->borderColor = borderColor;
+
+ if (!border)
+ {
+ d->width = w;
+ d->height = h;
+ }
+ else
+ {
+ d->width = w-2;
+ d->height = h-2;
+ }
+
+ if (d->width <= 0 || d->height <= 0)
+ return;
+
+ if (bevel & Theme::SUNKEN)
+ {
+ d->color0 = to;
+ d->color1 = from;
+ }
+ else
+ {
+ d->color0 = from;
+ d->color1 = to;
+ }
+
+ if (gradient == Theme::SOLID)
+ {
+ doSolid();
+ }
+ else
+ {
+ d->red = new unsigned char[w*h];
+ d->green = new unsigned char[w*h];
+ d->blue = new unsigned char[w*h];
+
+ if (gradient == Theme::HORIZONTAL)
+ doHgradient();
+ else if (gradient == Theme::VERTICAL)
+ doVgradient();
+ else if (gradient == Theme::DIAGONAL)
+ doDgradient();
+
+ if (bevel & Theme::RAISED || bevel & Theme::SUNKEN)
+ doBevel();
+
+ buildImage();
+ }
+}
+
+Texture::~Texture()
+{
+ if (d->red)
+ delete [] d->red;
+ if (d->green)
+ delete [] d->green;
+ if (d->blue)
+ delete [] d->blue;
+
+ delete d;
+}
+
+TQPixmap Texture::renderPixmap() const
+{
+ if (d->width <= 0 || d->height <= 0)
+ return TQPixmap();
+
+ if (!d->border)
+ return d->pixmap;
+
+ TQPixmap pix(d->width+2, d->height+2);
+ bitBlt(&pix, 1, 1, &d->pixmap, 0, 0);
+ TQPainter p(&pix);
+ p.setPen(d->borderColor);
+ p.drawRect(0, 0, d->width+2, d->height+2);
+ p.end();
+
+ return pix;
+}
+
+void Texture::doSolid()
+{
+ d->pixmap.resize(d->width, d->height);
+ TQPainter p(&d->pixmap);
+ p.fillRect(0, 0, d->width, d->height, d->color0);
+ if (d->bevel == Theme::RAISED)
+ {
+ p.setPen(d->color0.light(120));
+ p.drawLine(0, 0, d->width-1, 0); // top
+ p.drawLine(0, 0, 0, d->height-1); // left
+ p.setPen(d->color0.dark(120));
+ p.drawLine(0, d->height-1, d->width-1, d->height-1); // bottom
+ p.drawLine(d->width-1, 0, d->width-1, d->height-1); // right
+ }
+ else if (d->bevel == Theme::SUNKEN)
+ {
+ p.setPen(d->color0.dark(120));
+ p.drawLine(0, 0, d->width-1, 0); // top
+ p.drawLine(0, 0, 0, d->height-1); // left
+ p.setPen(d->color0.light(120));
+ p.drawLine(0, d->height-1, d->width-1, d->height-1); // bottom
+ p.drawLine(d->width-1, 0, d->width-1, d->height-1); // right
+ }
+ p.end();
+}
+
+void Texture::doHgradient()
+{
+ float drx, dgx, dbx,
+ xr = (float) d->color0.red(),
+ xg = (float) d->color0.green(),
+ xb = (float) d->color0.blue();
+ unsigned char *pr = d->red, *pg = d->green, *pb = d->blue;
+
+ int x, y;
+
+ drx = (float) (d->color1.red() - d->color0.red());
+ dgx = (float) (d->color1.green() - d->color0.green());
+ dbx = (float) (d->color1.blue() - d->color0.blue());
+
+ drx /= d->width;
+ dgx /= d->width;
+ dbx /= d->width;
+
+ for (x = 0; x < d->width; x++)
+ {
+ *(pr++) = (unsigned char) (xr);
+ *(pg++) = (unsigned char) (xg);
+ *(pb++) = (unsigned char) (xb);
+
+ xr += drx;
+ xg += dgx;
+ xb += dbx;
+ }
+
+ for (y = 1; y < d->height; y++, pr += d->width, pg += d->width, pb += d->width)
+ {
+ memcpy(pr, d->red, d->width);
+ memcpy(pg, d->green, d->width);
+ memcpy(pb, d->blue, d->width);
+ }
+}
+
+void Texture::doVgradient()
+{
+ float dry, dgy, dby,
+ yr = (float) d->color0.red(),
+ yg = (float) d->color0.green(),
+ yb = (float) d->color0.blue();
+
+ dry = (float) (d->color1.red() - d->color0.red());
+ dgy = (float) (d->color1.green() - d->color0.green());
+ dby = (float) (d->color1.blue() - d->color0.blue());
+
+ dry /= d->height;
+ dgy /= d->height;
+ dby /= d->height;
+
+ unsigned char *pr = d->red, *pg = d->green, *pb = d->blue;
+ int y;
+
+ for (y = 0; y < d->height; y++, pr += d->width, pg += d->width, pb += d->width) {
+ memset(pr, (unsigned char) yr, d->width);
+ memset(pg, (unsigned char) yg, d->width);
+ memset(pb, (unsigned char) yb, d->width);
+
+ yr += dry;
+ yg += dgy;
+ yb += dby;
+ }
+}
+
+void Texture::doDgradient()
+{
+ unsigned int* xtable = new unsigned int[d->width*3];
+ unsigned int* ytable = new unsigned int[d->height*3];
+
+ float drx, dgx, dbx, dry, dgy, dby, yr = 0.0, yg = 0.0, yb = 0.0,
+ xr = (float) d->color0.red(),
+ xg = (float) d->color0.green(),
+ xb = (float) d->color0.blue();
+ unsigned char *pr = d->red, *pg = d->green, *pb = d->blue;
+ unsigned int w = d->width * 2, h = d->height * 2;
+ unsigned int *xt = xtable;
+ unsigned int *yt = ytable;
+
+ int x, y;
+
+ dry = drx = (float) (d->color1.red() - d->color0.red());
+ dgy = dgx = (float) (d->color1.green() - d->color0.green());
+ dby = dbx = (float) (d->color1.blue() - d->color0.blue());
+
+ // Create X table
+ drx /= w;
+ dgx /= w;
+ dbx /= w;
+
+ for (x = 0; x < d->width; x++)
+ {
+ *(xt++) = (unsigned char) (xr);
+ *(xt++) = (unsigned char) (xg);
+ *(xt++) = (unsigned char) (xb);
+
+ xr += drx;
+ xg += dgx;
+ xb += dbx;
+ }
+
+ // Create Y table
+ dry /= h;
+ dgy /= h;
+ dby /= h;
+
+ for (y = 0; y < d->height; y++)
+ {
+ *(yt++) = ((unsigned char) yr);
+ *(yt++) = ((unsigned char) yg);
+ *(yt++) = ((unsigned char) yb);
+
+ yr += dry;
+ yg += dgy;
+ yb += dby;
+ }
+
+ // Combine tables to create gradient
+
+ for (yt = ytable, y = 0; y < d->height; y++, yt += 3)
+ {
+ for (xt = xtable, x = 0; x < d->width; x++)
+ {
+ *(pr++) = *(xt++) + *(yt);
+ *(pg++) = *(xt++) + *(yt + 1);
+ *(pb++) = *(xt++) + *(yt + 2);
+ }
+ }
+
+ delete [] xtable;
+ delete [] ytable;
+}
+
+void Texture::doBevel()
+{
+ unsigned char *pr = d->red, *pg = d->green, *pb = d->blue;
+
+ unsigned char r, g, b, rr ,gg ,bb;
+ unsigned int w = d->width, h = d->height - 1, wh = w * h;
+
+ while (--w)
+ {
+ r = *pr;
+ rr = r + (r >> 1);
+ if (rr < r) rr = ~0;
+ g = *pg;
+ gg = g + (g >> 1);
+ if (gg < g) gg = ~0;
+ b = *pb;
+ bb = b + (b >> 1);
+ if (bb < b) bb = ~0;
+
+ *pr = rr;
+ *pg = gg;
+ *pb = bb;
+
+ r = *(pr + wh);
+ rr = (r >> 2) + (r >> 1);
+ if (rr > r) rr = 0;
+ g = *(pg + wh);
+ gg = (g >> 2) + (g >> 1);
+ if (gg > g) gg = 0;
+ b = *(pb + wh);
+ bb = (b >> 2) + (b >> 1);
+ if (bb > b) bb = 0;
+
+ *((pr++) + wh) = rr;
+ *((pg++) + wh) = gg;
+ *((pb++) + wh) = bb;
+ }
+
+ r = *pr;
+ rr = r + (r >> 1);
+ if (rr < r) rr = ~0;
+ g = *pg;
+ gg = g + (g >> 1);
+ if (gg < g) gg = ~0;
+ b = *pb;
+ bb = b + (b >> 1);
+ if (bb < b) bb = ~0;
+
+ *pr = rr;
+ *pg = gg;
+ *pb = bb;
+
+ r = *(pr + wh);
+ rr = (r >> 2) + (r >> 1);
+ if (rr > r) rr = 0;
+ g = *(pg + wh);
+ gg = (g >> 2) + (g >> 1);
+ if (gg > g) gg = 0;
+ b = *(pb + wh);
+ bb = (b >> 2) + (b >> 1);
+ if (bb > b) bb = 0;
+
+ *(pr + wh) = rr;
+ *(pg + wh) = gg;
+ *(pb + wh) = bb;
+
+ pr = d->red + d->width;
+ pg = d->green + d->width;
+ pb = d->blue + d->width;
+
+ while (--h)
+ {
+ r = *pr;
+ rr = r + (r >> 1);
+ if (rr < r) rr = ~0;
+ g = *pg;
+ gg = g + (g >> 1);
+ if (gg < g) gg = ~0;
+ b = *pb;
+ bb = b + (b >> 1);
+ if (bb < b) bb = ~0;
+
+ *pr = rr;
+ *pg = gg;
+ *pb = bb;
+
+ pr += d->width - 1;
+ pg += d->width - 1;
+ pb += d->width - 1;
+
+ r = *pr;
+ rr = (r >> 2) + (r >> 1);
+ if (rr > r) rr = 0;
+ g = *pg;
+ gg = (g >> 2) + (g >> 1);
+ if (gg > g) gg = 0;
+ b = *pb;
+ bb = (b >> 2) + (b >> 1);
+ if (bb > b) bb = 0;
+
+ *(pr++) = rr;
+ *(pg++) = gg;
+ *(pb++) = bb;
+ }
+
+ r = *pr;
+ rr = r + (r >> 1);
+ if (rr < r) rr = ~0;
+ g = *pg;
+ gg = g + (g >> 1);
+ if (gg < g) gg = ~0;
+ b = *pb;
+ bb = b + (b >> 1);
+ if (bb < b) bb = ~0;
+
+ *pr = rr;
+ *pg = gg;
+ *pb = bb;
+
+ pr += d->width - 1;
+ pg += d->width - 1;
+ pb += d->width - 1;
+
+ r = *pr;
+ rr = (r >> 2) + (r >> 1);
+ if (rr > r) rr = 0;
+ g = *pg;
+ gg = (g >> 2) + (g >> 1);
+ if (gg > g) gg = 0;
+ b = *pb;
+ bb = (b >> 2) + (b >> 1);
+ if (bb > b) bb = 0;
+
+ *pr = rr;
+ *pg = gg;
+ *pb = bb;
+}
+
+void Texture::buildImage()
+{
+ unsigned char *pr = d->red, *pg = d->green, *pb = d->blue;
+
+ TQImage image(d->width, d->height, 32);
+
+ unsigned int* bits = (unsigned int*) image.bits();
+
+ int p;
+ for (p =0; p < d->width*d->height; p++)
+ {
+ *bits = 0xff << 24 | *pr << 16 | *pg << 8 | *pb;
+ bits++;
+ pr++;
+ pg++;
+ pb++;
+ }
+
+ d->pixmap = TQPixmap(image);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/themeengine/texture.h b/src/libs/themeengine/texture.h
new file mode 100644
index 00000000..27ba73ee
--- /dev/null
+++ b/src/libs/themeengine/texture.h
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-26
+ * Description : texture pixmap methods
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * Adapted from fluxbox: Texture/TextureRender
+ *
+ * Texture.hh for Fluxbox Window Manager
+ * Copyright (c) 2002-2003 Henrik Kinnunen <fluxbox@users.sourceforge.net>
+ *
+ * from Image.hh for Blackbox - an X11 Window manager
+ * Copyright (c) 1997 - 2000 Brad Hughes (bhughes@tcac.net)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * ============================================================ */
+
+#ifndef TEXTURE_H
+#define TEXTURE_H
+
+// TQt includes.
+
+#include <tqcolor.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class TexturePriv;
+
+class DIGIKAM_EXPORT Texture
+{
+
+public:
+
+ Texture(int w, int h, const TQColor& from, const TQColor& to,
+ Theme::Bevel bevel, Theme::Gradient gradient,
+ bool border, const TQColor& borderColor);
+ ~Texture();
+
+ TQPixmap renderPixmap() const;
+
+private:
+
+ void doBevel();
+
+ void doSolid();
+ void doHgradient();
+ void doVgradient();
+ void doDgradient();
+
+ void buildImage();
+
+private:
+
+ TexturePriv* d;
+
+};
+
+} // NameSpace Digikam
+
+#endif /* TEXTURE_H */
diff --git a/src/libs/themeengine/theme.cpp b/src/libs/themeengine/theme.cpp
new file mode 100644
index 00000000..8e4b5142
--- /dev/null
+++ b/src/libs/themeengine/theme.cpp
@@ -0,0 +1,181 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-02
+ * Description : theme manager
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "ddebug.h"
+#include "theme.h"
+
+namespace Digikam
+{
+
+Theme::Theme(const TQString& _name, const TQString& _path)
+{
+ name = _name;
+ filePath = _path;
+}
+
+Theme::Theme(const Theme& theme)
+{
+ if (this != &theme)
+ {
+ baseColor = theme.baseColor;
+ textRegColor = theme.textRegColor;
+ textSelColor = theme.textSelColor;
+ textSpecialRegColor = theme.textSpecialRegColor;
+ textSpecialSelColor = theme.textSpecialSelColor;
+
+ bannerColor = theme.bannerColor;
+ bannerColorTo = theme.bannerColorTo;
+ bannerBevel = theme.bannerBevel;
+ bannerGrad = theme.bannerGrad;
+ bannerBorder = theme.bannerBorder;
+ bannerBorderColor = theme.bannerBorderColor;
+
+ thumbRegColor = theme.thumbRegColor;
+ thumbRegColorTo = theme.thumbRegColorTo;
+ thumbRegBevel = theme.thumbRegBevel;
+ thumbRegGrad = theme.thumbRegGrad;
+ thumbRegBorder = theme.thumbRegBorder;
+ thumbRegBorderColor = theme.thumbRegBorderColor;
+
+ thumbSelColor = theme.thumbSelColor;
+ thumbSelColorTo = theme.thumbSelColorTo;
+ thumbSelBevel = theme.thumbSelBevel;
+ thumbSelGrad = theme.thumbSelGrad;
+ thumbSelBorder = theme.thumbSelBorder;
+ thumbSelBorderColor = theme.thumbSelBorderColor;
+
+ listRegColor = theme.listRegColor;
+ listRegColorTo = theme.listRegColorTo;
+ listRegBevel = theme.listRegBevel;
+ listRegGrad = theme.listRegGrad;
+ listRegBorder = theme.listRegBorder;
+ listRegBorderColor = theme.listRegBorderColor;
+
+ listSelColor = theme.listSelColor;
+ listSelColorTo = theme.listSelColorTo;
+ listSelBevel = theme.listSelBevel;
+ listSelGrad = theme.listSelGrad;
+ listSelBorder = theme.listSelBorder;
+ listSelBorderColor = theme.listSelBorderColor;
+ }
+}
+
+Theme& Theme::operator=(const Theme& theme)
+{
+ if (this != &theme)
+ {
+ baseColor = theme.baseColor;
+ textRegColor = theme.textRegColor;
+ textSelColor = theme.textSelColor;
+ textSpecialRegColor = theme.textSpecialRegColor;
+ textSpecialSelColor = theme.textSpecialSelColor;
+
+ bannerColor = theme.bannerColor;
+ bannerColorTo = theme.bannerColorTo;
+ bannerBevel = theme.bannerBevel;
+ bannerGrad = theme.bannerGrad;
+ bannerBorder = theme.bannerBorder;
+ bannerBorderColor = theme.bannerBorderColor;
+
+ thumbRegColor = theme.thumbRegColor;
+ thumbRegColorTo = theme.thumbRegColorTo;
+ thumbRegBevel = theme.thumbRegBevel;
+ thumbRegGrad = theme.thumbRegGrad;
+ thumbRegBorder = theme.thumbRegBorder;
+ thumbRegBorderColor = theme.thumbRegBorderColor;
+
+ thumbSelColor = theme.thumbSelColor;
+ thumbSelColorTo = theme.thumbSelColorTo;
+ thumbSelBevel = theme.thumbSelBevel;
+ thumbSelGrad = theme.thumbSelGrad;
+ thumbSelBorder = theme.thumbSelBorder;
+ thumbSelBorderColor = theme.thumbSelBorderColor;
+
+ listRegColor = theme.listRegColor;
+ listRegColorTo = theme.listRegColorTo;
+ listRegBevel = theme.listRegBevel;
+ listRegGrad = theme.listRegGrad;
+ listRegBorder = theme.listRegBorder;
+ listRegBorderColor = theme.listRegBorderColor;
+
+ listSelColor = theme.listSelColor;
+ listSelColorTo = theme.listSelColorTo;
+ listSelBevel = theme.listSelBevel;
+ listSelGrad = theme.listSelGrad;
+ listSelBorder = theme.listSelBorder;
+ listSelBorderColor = theme.listSelBorderColor;
+ }
+ return *this;
+}
+
+void Theme::print()
+{
+ /*
+ DDebug() << "Theme : " << name << endl;
+
+ DDebug() << "Base Color: " << baseColor << endl;
+ DDebug() << "Text Regular Color: " << textRegColor << endl;
+ DDebug() << "Text Selected Color: " << textSelColor << endl;
+ DDebug() << "Text Special Regular Color: " << textSpecialRegColor << endl;
+ DDebug() << "Text Special Selected Color: " << textSpecialSelColor << endl;
+
+ DDebug() << "Banner Color: " << bannerColor << endl;
+ DDebug() << "Banner ColorTo : " << bannerColorTo << endl;
+ DDebug() << "Banner Bevel : " << bannerBevel << endl;
+ DDebug() << "Banner Gradient : " << bannerGrad << endl;
+ DDebug() << "Banner Border : " << bannerBorder << endl;
+ DDebug() << "Banner Border Color : " << bannerBorderColor << endl;
+
+ DDebug() << "ThumbReg Color: " << thumbRegColor << endl;
+ DDebug() << "ThumbReg ColorTo : " << thumbRegColorTo << endl;
+ DDebug() << "ThumbReg Bevel : " << thumbRegBevel << endl;
+ DDebug() << "ThumbReg Gradient : " << thumbRegGrad << endl;
+ DDebug() << "ThumbReg Border : " << thumbRegBorder << endl;
+ DDebug() << "ThumbReg Border Color : " << thumbRegBorderColor << endl;
+
+ DDebug() << "ThumbSel Color: " << thumbSelColor << endl;
+ DDebug() << "ThumbSel ColorTo : " << thumbSelColorTo << endl;
+ DDebug() << "ThumbSel Bevel : " << thumbSelBevel << endl;
+ DDebug() << "ThumbSel Gradient : " << thumbSelGrad << endl;
+ DDebug() << "ThumbSel Border : " << thumbSelBorder << endl;
+ DDebug() << "ThumbSel Border Color : " << thumbSelBorderColor << endl;
+
+ DDebug() << "ListReg Color: " << listRegColor << endl;
+ DDebug() << "ListReg ColorTo : " << listRegColorTo << endl;
+ DDebug() << "ListReg Bevel : " << listRegBevel << endl;
+ DDebug() << "ListReg Gradient : " << listRegGrad << endl;
+ DDebug() << "ListReg Border : " << listRegBorder << endl;
+ DDebug() << "ListReg Border Color : " << listRegBorderColor << endl;
+
+ DDebug() << "ListSel Color: " << listSelColor << endl;
+ DDebug() << "ListSel ColorTo : " << listSelColorTo << endl;
+ DDebug() << "ListSel Bevel : " << listSelBevel << endl;
+ DDebug() << "ListSel Gradient : " << listSelGrad << endl;
+ DDebug() << "ListSel Border : " << listSelBorder << endl;
+ DDebug() << "ListSel Border Color : " << listSelBorderColor << endl;
+ */
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/themeengine/theme.h b/src/libs/themeengine/theme.h
new file mode 100644
index 00000000..fd80deff
--- /dev/null
+++ b/src/libs/themeengine/theme.h
@@ -0,0 +1,112 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-02
+ * Description : theme manager
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef THEME_H
+#define THEME_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqcolor.h>
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT Theme
+{
+public:
+
+ enum Bevel
+ {
+ FLAT = 0x00001,
+ SUNKEN = 0x00002,
+ RAISED = 0x00004
+ };
+
+ enum Gradient
+ {
+ SOLID = 0x00000,
+ HORIZONTAL = 0x00010,
+ VERTICAL = 0x00020,
+ DIAGONAL = 0x00040
+ };
+
+ Theme(const TQString& _name, const TQString& _path);
+ Theme(const Theme& theme);
+ Theme& operator=(const Theme& theme);
+
+ void print();
+
+ TQString name;
+ TQString filePath;
+
+ TQColor baseColor;
+ TQColor textRegColor;
+ TQColor textSelColor;
+ TQColor textSpecialRegColor;
+ TQColor textSpecialSelColor;
+
+ TQColor bannerColor;
+ TQColor bannerColorTo;
+ Bevel bannerBevel;
+ Gradient bannerGrad;
+ bool bannerBorder;
+ TQColor bannerBorderColor;
+
+ TQColor thumbRegColor;
+ TQColor thumbRegColorTo;
+ Bevel thumbRegBevel;
+ Gradient thumbRegGrad;
+ bool thumbRegBorder;
+ TQColor thumbRegBorderColor;
+
+ TQColor thumbSelColor;
+ TQColor thumbSelColorTo;
+ Bevel thumbSelBevel;
+ Gradient thumbSelGrad;
+ bool thumbSelBorder;
+ TQColor thumbSelBorderColor;
+
+ TQColor listRegColor;
+ TQColor listRegColorTo;
+ Bevel listRegBevel;
+ Gradient listRegGrad;
+ bool listRegBorder;
+ TQColor listRegBorderColor;
+
+ TQColor listSelColor;
+ TQColor listSelColorTo;
+ Bevel listSelBevel;
+ Gradient listSelGrad;
+ bool listSelBorder;
+ TQColor listSelBorderColor;
+};
+
+} // NameSpace Digikam
+
+#endif /* THEME_H */
diff --git a/src/libs/themeengine/themeengine.cpp b/src/libs/themeengine/themeengine.cpp
new file mode 100644
index 00000000..331ffa5d
--- /dev/null
+++ b/src/libs/themeengine/themeengine.cpp
@@ -0,0 +1,1132 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-02
+ * Description : theme engine methods
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdict.h>
+#include <tqptrlist.h>
+#include <tqfileinfo.h>
+#include <tqfile.h>
+#include <tqapplication.h>
+#include <tqpalette.h>
+#include <tqtimer.h>
+#include <tqtextstream.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+#include <kuser.h>
+#include <tdeapplication.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "theme.h"
+#include "texture.h"
+#include "themeengine.h"
+#include "themeengine.moc"
+
+namespace Digikam
+{
+
+class ThemeEnginePriv
+{
+public:
+
+ ThemeEnginePriv()
+ {
+ defaultTheme = 0;
+ currTheme = 0;
+ themeInitiallySet = false;
+ }
+
+ TQPalette defaultPalette;
+
+ TQPtrList<Theme> themeList;
+ TQDict<Theme> themeDict;
+
+ Theme *currTheme;
+ Theme *defaultTheme;
+ bool themeInitiallySet;
+};
+
+ThemeEngine* ThemeEngine::m_instance = 0;
+
+ThemeEngine* ThemeEngine::instance()
+{
+ if (!m_instance)
+ {
+ new ThemeEngine();
+ }
+ return m_instance;
+}
+
+ThemeEngine::ThemeEngine()
+{
+ m_instance = this;
+ TDEGlobal::dirs()->addResourceType("themes",
+ TDEGlobal::dirs()->kde_default("data")
+ + "digikam/themes");
+
+ d = new ThemeEnginePriv;
+
+ d->themeList.setAutoDelete(false);
+ d->themeDict.setAutoDelete(false);
+ d->defaultTheme = new Theme(i18n("Default"), TQString());
+ d->themeList.append(d->defaultTheme);
+ d->themeDict.insert(i18n("Default"), d->defaultTheme);
+ d->currTheme = d->defaultTheme;
+
+ buildDefaultTheme();
+}
+
+ThemeEngine::~ThemeEngine()
+{
+ d->themeList.setAutoDelete(true);
+ d->themeList.clear();
+ delete d;
+ m_instance = 0;
+}
+
+TQColor ThemeEngine::baseColor() const
+{
+ return d->currTheme->baseColor;
+}
+
+TQColor ThemeEngine::thumbSelColor() const
+{
+ return d->currTheme->thumbSelColor;
+}
+
+TQColor ThemeEngine::thumbRegColor() const
+{
+ return d->currTheme->thumbRegColor;
+}
+
+TQColor ThemeEngine::textRegColor() const
+{
+ return d->currTheme->textRegColor;
+}
+
+TQColor ThemeEngine::textSelColor() const
+{
+ return d->currTheme->textSelColor;
+}
+
+TQColor ThemeEngine::textSpecialRegColor() const
+{
+ return d->currTheme->textSpecialRegColor;
+}
+
+TQColor ThemeEngine::textSpecialSelColor() const
+{
+ return d->currTheme->textSpecialSelColor;
+}
+
+TQPixmap ThemeEngine::bannerPixmap(int w, int h)
+{
+ Texture tex(w, h, d->currTheme->bannerColor, d->currTheme->bannerColorTo,
+ d->currTheme->bannerBevel, d->currTheme->bannerGrad,
+ d->currTheme->bannerBorder, d->currTheme->bannerBorderColor);
+ return tex.renderPixmap();
+}
+
+TQPixmap ThemeEngine::thumbRegPixmap(int w, int h)
+{
+ Texture tex(w, h, d->currTheme->thumbRegColor, d->currTheme->thumbRegColorTo,
+ d->currTheme->thumbRegBevel, d->currTheme->thumbRegGrad,
+ d->currTheme->thumbRegBorder, d->currTheme->thumbRegBorderColor);
+ return tex.renderPixmap();
+}
+
+TQPixmap ThemeEngine::thumbSelPixmap(int w, int h)
+{
+ Texture tex(w, h, d->currTheme->thumbSelColor, d->currTheme->thumbSelColorTo,
+ d->currTheme->thumbSelBevel, d->currTheme->thumbSelGrad,
+ d->currTheme->thumbSelBorder, d->currTheme->thumbSelBorderColor);
+ return tex.renderPixmap();
+}
+
+TQPixmap ThemeEngine::listRegPixmap(int w, int h)
+{
+ Texture tex(w, h, d->currTheme->listRegColor, d->currTheme->listRegColorTo,
+ d->currTheme->listRegBevel, d->currTheme->listRegGrad,
+ d->currTheme->listRegBorder, d->currTheme->listRegBorderColor);
+ return tex.renderPixmap();
+}
+
+TQPixmap ThemeEngine::listSelPixmap(int w, int h)
+{
+ Texture tex(w, h, d->currTheme->listSelColor, d->currTheme->listSelColorTo,
+ d->currTheme->listSelBevel, d->currTheme->listSelGrad,
+ d->currTheme->listSelBorder, d->currTheme->listSelBorderColor);
+ return tex.renderPixmap();
+}
+
+void ThemeEngine::scanThemes()
+{
+ d->themeList.remove(d->defaultTheme);
+ d->themeList.setAutoDelete(true);
+ d->themeList.clear();
+ d->themeDict.clear();
+ d->currTheme = 0;
+
+ TQStringList themes = TDEGlobal::dirs()->findAllResources( "themes", TQString(), false, true );
+
+ for (TQStringList::iterator it=themes.begin(); it != themes.end();
+ ++it)
+ {
+ TQFileInfo fi(*it);
+ Theme* theme = new Theme(fi.fileName(), *it);
+ d->themeList.append(theme);
+ d->themeDict.insert(fi.fileName(), theme);
+ }
+
+ d->themeList.append(d->defaultTheme);
+ d->themeDict.insert(i18n("Default"), d->defaultTheme);
+ d->currTheme = d->defaultTheme;
+}
+
+TQStringList ThemeEngine::themeNames() const
+{
+ TQStringList names;
+ for (Theme *t = d->themeList.first(); t; t = d->themeList.next())
+ {
+ names << t->name;
+ }
+ names.sort();
+ return names;
+}
+
+void ThemeEngine::slotChangeTheme(const TQString& name)
+{
+ setCurrentTheme(name);
+}
+
+void ThemeEngine::setCurrentTheme(const TQString& name)
+{
+ Theme* theme = d->themeDict.find(name);
+ if (!theme)
+ {
+ d->currTheme = d->defaultTheme;
+ return;
+ }
+
+ if (d->currTheme == theme && d->themeInitiallySet)
+ return;
+
+ d->currTheme = theme;
+ loadTheme();
+
+ // this is only to ensure that even if the chosen theme is the default theme,
+ // the signalThemeChanged is emitted when themes are loaded in DigikamApp
+ d->themeInitiallySet = true;
+
+ changePalette();
+
+ TQTimer::singleShot(0, this, TQ_SIGNAL(signalThemeChanged()));
+}
+
+void ThemeEngine::setCurrentTheme(const Theme& theme, const TQString& name, bool loadFromDisk)
+{
+ Theme* t = d->themeDict.find(name);
+ if (t)
+ {
+ d->themeDict.remove(name);
+ d->themeList.remove(t);
+ }
+
+ t = new Theme(theme);
+ t->filePath = theme.filePath;
+ d->themeDict.insert(name, t);
+ d->themeList.append(t);
+
+ d->currTheme = t;
+ if (loadFromDisk)
+ loadTheme();
+
+ changePalette();
+
+ TQTimer::singleShot(0, this, TQ_SIGNAL(signalThemeChanged()));
+}
+
+void ThemeEngine::changePalette()
+{
+ // Make palette for all widgets.
+ TQPalette plt;
+
+ if (d->currTheme == d->defaultTheme)
+ plt = d->defaultPalette;
+ else
+ {
+ plt = kapp->palette();
+ int h, s, v;
+ const TQColor fg(ThemeEngine::instance()->textRegColor());
+ const TQColor bg(ThemeEngine::instance()->baseColor());
+ TQColorGroup cg(plt.active());
+
+ /* bg.hsv(&h, &s, &v);
+ v += (v < 128) ? +50 : -50;
+ v &= 255; //ensures 0 <= v < 256
+ d->currTheme->altBase = TQColor(h, s, v, TQColor::Hsv);
+ */
+ fg.hsv(&h, &s, &v);
+ v += (v < 128) ? +150 : -150;
+ v &= 255; //ensures 0 <= v < 256
+ const TQColor highlight(h, s, v, TQColor::Hsv);
+
+ cg.setColor(TQColorGroup::Base, bg);
+ cg.setColor(TQColorGroup::Background, bg.dark(115));
+ cg.setColor(TQColorGroup::Foreground, ThemeEngine::instance()->textRegColor());
+ cg.setColor(TQColorGroup::Highlight, highlight);
+ cg.setColor(TQColorGroup::HighlightedText, ThemeEngine::instance()->textSelColor());
+ cg.setColor(TQColorGroup::Dark, TQt::darkGray);
+
+ cg.setColor(TQColorGroup::Button, bg);
+ cg.setColor(TQColorGroup::ButtonText, ThemeEngine::instance()->textRegColor());
+
+ cg.setColor(TQColorGroup::Text, ThemeEngine::instance()->textRegColor());
+ cg.setColor(TQColorGroup::Link, ThemeEngine::instance()->textSpecialRegColor());
+ cg.setColor(TQColorGroup::LinkVisited, ThemeEngine::instance()->textSpecialSelColor());
+
+ /*
+ cg.setColor(TQColorGroup::Light, ThemeEngine::instance()->textRegColor());
+ cg.setColor(TQColorGroup::Midlight, ThemeEngine::instance()->textRegColor());
+ cg.setColor(TQColorGroup::Mid, ThemeEngine::instance()->textRegColor());
+ cg.setColor(TQColorGroup::Shadow, ThemeEngine::instance()->textRegColor());
+ */
+
+ plt.setActive(cg);
+ plt.setInactive(cg);
+ plt.setDisabled(cg);
+ }
+
+ kapp->setPalette(plt, true);
+}
+
+Theme* ThemeEngine::getCurrentTheme() const
+{
+ return d->currTheme;
+}
+
+TQString ThemeEngine::getCurrentThemeName() const
+{
+ return d->currTheme->name;
+}
+
+void ThemeEngine::buildDefaultTheme()
+{
+ Theme* t = d->defaultTheme;
+
+ d->defaultPalette = kapp->palette();
+ TQColorGroup cg = d->defaultPalette.active();
+
+ t->baseColor = cg.base();
+ t->textRegColor = cg.text();
+ t->textSelColor = cg.highlightedText();
+ t->textSpecialRegColor = TQColor("#0000EF");
+ t->textSpecialSelColor = cg.highlightedText();
+
+ t->bannerColor = cg.highlight();
+ t->bannerColorTo = cg.highlight().dark(120);
+ t->bannerBevel = Theme::FLAT;
+ t->bannerGrad = Theme::SOLID;
+ t->bannerBorder = false;
+ t->bannerBorderColor = TQt::black;
+
+ t->thumbRegColor = cg.base();
+ t->thumbRegColorTo = cg.base();
+ t->thumbRegBevel = Theme::FLAT;
+ t->thumbRegGrad = Theme::SOLID;
+ t->thumbRegBorder = true;
+ t->thumbRegBorderColor = TQColor("#E0E0EF");
+
+ t->thumbSelColor = cg.highlight();
+ t->thumbSelColorTo = cg.highlight();
+ t->thumbSelBevel = Theme::FLAT;
+ t->thumbSelGrad = Theme::SOLID;
+ t->thumbSelBorder = true;
+ t->thumbSelBorderColor = TQColor("#E0E0EF");
+
+ t->listRegColor = cg.base();
+ t->listRegColorTo = cg.base();
+ t->listRegBevel = Theme::FLAT;
+ t->listRegGrad = Theme::SOLID;
+ t->listRegBorder = false;
+ t->listRegBorderColor = TQt::black;
+
+ t->listSelColor = cg.highlight();
+ t->listSelColorTo = cg.highlight();
+ t->listSelBevel = Theme::FLAT;
+ t->listSelGrad = Theme::SOLID;
+ t->listSelBorder = true;
+ t->listSelBorderColor = TQt::black;
+}
+
+bool ThemeEngine::loadTheme()
+{
+ Q_ASSERT( d->currTheme );
+ if (!d->currTheme || d->currTheme == d->defaultTheme)
+ return false;
+
+ Theme *t = d->currTheme;
+
+ // use the default theme as base template to build the themes
+ *(t) = *(d->defaultTheme);
+
+ TQFile themeFile(t->filePath);
+
+ if (!themeFile.open(IO_ReadOnly))
+ return false;
+
+ TQDomDocument xmlDoc;
+ TQString error;
+ int row, col;
+ if (!xmlDoc.setContent(&themeFile, true, &error, &row, &col))
+ {
+ DDebug() << "Theme file: " << t->filePath << endl;
+ DDebug() << error << " :: row=" << row << " , col=" << col << endl;
+ return false;
+ }
+
+ TQDomElement rootElem = xmlDoc.documentElement();
+ if (rootElem.tagName() != TQString::fromLatin1("digikamtheme"))
+ return false;
+
+ TQString resource;
+
+ // -- base ------------------------------------------------------------------------
+
+ resource = resourceValue(rootElem, "BaseColor");
+ if (!resource.isEmpty())
+ t->baseColor = resource;
+
+ resource = resourceValue(rootElem, "TextRegularColor");
+ if (!resource.isEmpty())
+ t->textRegColor = resource;
+
+ resource = resourceValue(rootElem, "TextSelectedColor");
+ if (!resource.isEmpty())
+ t->textSelColor = resource;
+
+ resource = resourceValue(rootElem, "TextSpecialRegularColor");
+ if (!resource.isEmpty())
+ t->textSpecialRegColor = resource;
+
+ resource = resourceValue(rootElem, "TextSpecialSelectedColor");
+ if (!resource.isEmpty())
+ t->textSpecialSelColor = resource;
+
+ // -- banner ------------------------------------------------------------------------
+
+ resource = resourceValue(rootElem, "BannerColor");
+ if (!resource.isEmpty())
+ t->bannerColor = resource;
+
+ resource = resourceValue(rootElem, "BannerColorTo");
+ if (!resource.isEmpty())
+ t->bannerColorTo = resource;
+
+ resource = resourceValue(rootElem, "BannerBevel");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("flat", false))
+ t->bannerBevel = Theme::FLAT;
+ else if (resource.contains("sunken", false))
+ t->bannerBevel = Theme::SUNKEN;
+ else if (resource.contains("raised", false))
+ t->bannerBevel = Theme::RAISED;
+ }
+
+ resource = resourceValue(rootElem, "BannerGradient");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("solid", false))
+ t->bannerGrad = Theme::SOLID;
+ else if (resource.contains("horizontal", false))
+ t->bannerGrad = Theme::HORIZONTAL;
+ else if (resource.contains("vertical", false))
+ t->bannerGrad = Theme::VERTICAL;
+ else if (resource.contains("diagonal", false))
+ t->bannerGrad = Theme::DIAGONAL;
+ }
+
+ resource = resourceValue(rootElem, "BannerBorder");
+ if (!resource.isEmpty())
+ {
+ t->bannerBorder = resource.contains("true", false);
+ }
+
+ resource = resourceValue(rootElem, "BannerBorderColor");
+ if (!resource.isEmpty())
+ {
+ t->bannerBorderColor = resource;
+ }
+
+ // -- thumbnail view ----------------------------------------------------------------
+
+ resource = resourceValue(rootElem, "ThumbnailRegularColor");
+ if (!resource.isEmpty())
+ t->thumbRegColor = resource;
+
+ resource = resourceValue(rootElem, "ThumbnailRegularColorTo");
+ if (!resource.isEmpty())
+ t->thumbRegColorTo = resource;
+
+ resource = resourceValue(rootElem, "ThumbnailRegularBevel");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("flat", false))
+ t->thumbRegBevel = Theme::FLAT;
+ else if (resource.contains("sunken", false))
+ t->thumbRegBevel = Theme::SUNKEN;
+ else if (resource.contains("raised", false))
+ t->thumbRegBevel = Theme::RAISED;
+ }
+
+ resource = resourceValue(rootElem, "ThumbnailRegularGradient");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("solid", false))
+ t->thumbRegGrad = Theme::SOLID;
+ else if (resource.contains("horizontal", false))
+ t->thumbRegGrad = Theme::HORIZONTAL;
+ else if (resource.contains("vertical", false))
+ t->thumbRegGrad = Theme::VERTICAL;
+ else if (resource.contains("diagonal", false))
+ t->thumbRegGrad = Theme::DIAGONAL;
+ }
+
+ resource = resourceValue(rootElem, "ThumbnailRegularBorder");
+ if (!resource.isEmpty())
+ {
+ t->thumbRegBorder = resource.contains("true", false);
+ }
+
+ resource = resourceValue(rootElem, "ThumbnailRegularBorderColor");
+ if (!resource.isEmpty())
+ {
+ t->thumbRegBorderColor = resource;
+ }
+
+ resource = resourceValue(rootElem, "ThumbnailSelectedColor");
+ if (!resource.isEmpty())
+ t->thumbSelColor = resource;
+
+ resource = resourceValue(rootElem, "ThumbnailSelectedColorTo");
+ if (!resource.isEmpty())
+ t->thumbSelColorTo = resource;
+
+ resource = resourceValue(rootElem, "ThumbnailSelectedBevel");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("flat", false))
+ t->thumbSelBevel = Theme::FLAT;
+ else if (resource.contains("sunken", false))
+ t->thumbSelBevel = Theme::SUNKEN;
+ else if (resource.contains("raised", false))
+ t->thumbSelBevel = Theme::RAISED;
+ }
+
+ resource = resourceValue(rootElem, "ThumbnailSelectedGradient");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("solid", false))
+ t->thumbSelGrad = Theme::SOLID;
+ else if (resource.contains("horizontal", false))
+ t->thumbSelGrad = Theme::HORIZONTAL;
+ else if (resource.contains("vertical", false))
+ t->thumbSelGrad = Theme::VERTICAL;
+ else if (resource.contains("diagonal", false))
+ t->thumbSelGrad = Theme::DIAGONAL;
+ }
+
+ resource = resourceValue(rootElem, "ThumbnailSelectedBorder");
+ if (!resource.isEmpty())
+ {
+ t->thumbSelBorder = resource.contains("true", false);
+ }
+
+ resource = resourceValue(rootElem, "ThumbnailSelectedBorderColor");
+ if (!resource.isEmpty())
+ {
+ t->thumbSelBorderColor = resource;
+ }
+
+ // -- listview view ----------------------------------------------------------------
+
+ resource = resourceValue(rootElem, "ListviewRegularColor");
+ if (!resource.isEmpty())
+ t->listRegColor = resource;
+
+ resource = resourceValue(rootElem, "ListviewRegularColorTo");
+ if (!resource.isEmpty())
+ t->listRegColorTo = resource;
+
+ resource = resourceValue(rootElem, "ListviewRegularBevel");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("flat", false))
+ t->listRegBevel = Theme::FLAT;
+ else if (resource.contains("sunken", false))
+ t->listRegBevel = Theme::SUNKEN;
+ else if (resource.contains("raised", false))
+ t->listRegBevel = Theme::RAISED;
+ }
+
+ resource = resourceValue(rootElem, "ListviewRegularGradient");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("solid", false))
+ t->listRegGrad = Theme::SOLID;
+ else if (resource.contains("horizontal", false))
+ t->listRegGrad = Theme::HORIZONTAL;
+ else if (resource.contains("vertical", false))
+ t->listRegGrad = Theme::VERTICAL;
+ else if (resource.contains("diagonal", false))
+ t->listRegGrad = Theme::DIAGONAL;
+ }
+
+ resource = resourceValue(rootElem, "ListviewRegularBorder");
+ if (!resource.isEmpty())
+ {
+ t->listRegBorder = resource.contains("true", false);
+ }
+
+ resource = resourceValue(rootElem, "ListviewRegularBorderColor");
+ if (!resource.isEmpty())
+ {
+ t->listRegBorderColor = resource;
+ }
+
+ resource = resourceValue(rootElem, "ListviewSelectedColor");
+ if (!resource.isEmpty())
+ t->listSelColor = resource;
+
+ resource = resourceValue(rootElem, "ListviewSelectedColorTo");
+ if (!resource.isEmpty())
+ t->listSelColorTo = resource;
+
+ resource = resourceValue(rootElem, "ListviewSelectedBevel");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("flat", false))
+ t->listSelBevel = Theme::FLAT;
+ else if (resource.contains("sunken", false))
+ t->listSelBevel = Theme::SUNKEN;
+ else if (resource.contains("raised", false))
+ t->listSelBevel = Theme::RAISED;
+ }
+
+ resource = resourceValue(rootElem, "ListviewSelectedGradient");
+ if (!resource.isEmpty())
+ {
+ if (resource.contains("solid", false))
+ t->listSelGrad = Theme::SOLID;
+ else if (resource.contains("horizontal", false))
+ t->listSelGrad = Theme::HORIZONTAL;
+ else if (resource.contains("vertical", false))
+ t->listSelGrad = Theme::VERTICAL;
+ else if (resource.contains("diagonal", false))
+ t->listSelGrad = Theme::DIAGONAL;
+ }
+
+ resource = resourceValue(rootElem, "ListviewSelectedBorder");
+ if (!resource.isEmpty())
+ {
+ t->listSelBorder = resource.contains("true", false);
+ }
+
+ resource = resourceValue(rootElem, "ListviewSelectedBorderColor");
+ if (!resource.isEmpty())
+ {
+ t->listSelBorderColor = resource;
+ }
+
+ DDebug() << "Theme file loaded: " << t->filePath << endl;
+ return true;
+}
+
+TQString ThemeEngine::resourceValue(const TQDomElement &rootElem, const TQString& key)
+{
+ for (TQDomNode node = rootElem.firstChild();
+ !node.isNull(); node = node.nextSibling())
+ {
+ TQDomElement e = node.toElement();
+ TQString name = e.tagName();
+ TQString val = e.attribute(TQString::fromLatin1("value"));
+
+ if (key == name)
+ {
+ return val;
+ }
+ }
+
+ return TQString("");
+}
+
+bool ThemeEngine::saveTheme()
+{
+ Q_ASSERT( d->currTheme );
+ if (!d->currTheme)
+ return false;
+
+ Theme *t = d->currTheme;
+
+ TQFileInfo fi(t->filePath);
+
+ TQFile themeFile(fi.filePath());
+
+ if (!themeFile.open(IO_WriteOnly))
+ return false;
+
+ KUser user;
+ TQDomDocument xmlDoc;
+ TQDomElement e;
+ TQString val;
+
+ // header ------------------------------------------------------------------
+
+ xmlDoc.appendChild(xmlDoc.createProcessingInstruction(TQString::fromLatin1("xml"),
+ TQString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"")));
+
+ TQString banner = TQString("\n/* ============================================================"
+ "\n *"
+ "\n * This file is a part of digiKam project"
+ "\n * http://www.digikam.org"
+ "\n *"
+ "\n * Date : %1-%2-%3"
+ "\n * Description : %4 colors theme."
+ "\n *"
+ "\n * Copyright (C) %5 by %6"
+ "\n *"
+ "\n * This program is free software; you can redistribute it"
+ "\n * and/or modify it under the terms of the GNU General"
+ "\n * Public License as published by the Free Software Foundation;"
+ "\n * either version 2, or (at your option)"
+ "\n * any later version."
+ "\n * "
+ "\n * This program is distributed in the hope that it will be useful,"
+ "\n * but WITHOUT ANY WARRANTY; without even the implied warranty of"
+ "\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"
+ "\n * GNU General Public License for more details."
+ "\n *"
+ "\n * ============================================================ */\n")
+ .arg(TQDate::currentDate().year())
+ .arg(TQDate::currentDate().month())
+ .arg(TQDate::currentDate().day())
+ .arg(fi.fileName())
+ .arg(TQDate::currentDate().year())
+ .arg(user.fullName());
+
+ xmlDoc.appendChild(xmlDoc.createComment(banner));
+
+ TQDomElement themeElem = xmlDoc.createElement(TQString::fromLatin1("digikamtheme"));
+ xmlDoc.appendChild(themeElem);
+
+ // base props --------------------------------------------------------------
+
+ e = xmlDoc.createElement(TQString::fromLatin1("name"));
+ e.setAttribute(TQString::fromLatin1("value"), fi.fileName());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("BaseColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->baseColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("TextRegularColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->textRegColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("TextSelectedColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->textSelColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("TextSpecialRegularColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->textSpecialRegColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("TextSpecialSelectedColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->textSpecialSelColor.name()).upper());
+ themeElem.appendChild(e);
+
+ // banner props ------------------------------------------------------------
+
+ e = xmlDoc.createElement(TQString::fromLatin1("BannerColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->bannerColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("BannerColorTo"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->bannerColorTo.name()).upper());
+ themeElem.appendChild(e);
+
+ switch(t->bannerBevel)
+ {
+ case(Theme::FLAT):
+ {
+ val = TQString("FLAT");
+ break;
+ }
+ case(Theme::RAISED):
+ {
+ val = TQString("RAISED");
+ break;
+ }
+ case(Theme::SUNKEN):
+ {
+ val = TQString("SUNKEN");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("BannerBevel"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ switch(t->bannerGrad)
+ {
+ case(Theme::SOLID):
+ {
+ val = TQString("SOLID");
+ break;
+ }
+ case(Theme::HORIZONTAL):
+ {
+ val = TQString("HORIZONTAL");
+ break;
+ }
+ case(Theme::VERTICAL):
+ {
+ val = TQString("VERTICAL");
+ break;
+ }
+ case(Theme::DIAGONAL):
+ {
+ val = TQString("DIAGONAL");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("BannerGradient"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("BannerBorder"));
+ e.setAttribute(TQString::fromLatin1("value"), (t->bannerBorder ? "TRUE" : "FALSE"));
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("BannerBorderColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->bannerBorderColor.name()).upper());
+ themeElem.appendChild(e);
+
+ // thumbnail.regular props -------------------------------------------------
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailRegularColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->thumbRegColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailRegularColorTo"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->thumbRegColorTo.name()).upper());
+ themeElem.appendChild(e);
+
+ switch(t->thumbRegBevel)
+ {
+ case(Theme::FLAT):
+ {
+ val = TQString("FLAT");
+ break;
+ }
+ case(Theme::RAISED):
+ {
+ val = TQString("RAISED");
+ break;
+ }
+ case(Theme::SUNKEN):
+ {
+ val = TQString("SUNKEN");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailRegularBevel"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ switch(t->thumbRegGrad)
+ {
+ case(Theme::SOLID):
+ {
+ val = TQString("SOLID");
+ break;
+ }
+ case(Theme::HORIZONTAL):
+ {
+ val = TQString("HORIZONTAL");
+ break;
+ }
+ case(Theme::VERTICAL):
+ {
+ val = TQString("VERTICAL");
+ break;
+ }
+ case(Theme::DIAGONAL):
+ {
+ val = TQString("DIAGONAL");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailRegularGradient"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailRegularBorder"));
+ e.setAttribute(TQString::fromLatin1("value"), (t->thumbRegBorder ? "TRUE" : "FALSE"));
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailRegularBorderColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->thumbRegBorderColor.name()).upper());
+ themeElem.appendChild(e);
+
+ // thumbnail.selected props -------------------------------------------------
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailSelectedColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->thumbSelColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailSelectedColorTo"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->thumbSelColorTo.name()).upper());
+ themeElem.appendChild(e);
+
+ switch(t->thumbSelBevel)
+ {
+ case(Theme::FLAT):
+ {
+ val = TQString("FLAT");
+ break;
+ }
+ case(Theme::RAISED):
+ {
+ val = TQString("RAISED");
+ break;
+ }
+ case(Theme::SUNKEN):
+ {
+ val = TQString("SUNKEN");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailSelectedBevel"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ switch(t->thumbSelGrad)
+ {
+ case(Theme::SOLID):
+ {
+ val = TQString("SOLID");
+ break;
+ }
+ case(Theme::HORIZONTAL):
+ {
+ val = TQString("HORIZONTAL");
+ break;
+ }
+ case(Theme::VERTICAL):
+ {
+ val = TQString("VERTICAL");
+ break;
+ }
+ case(Theme::DIAGONAL):
+ {
+ val = TQString("DIAGONAL");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailSelectedGradient"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailSelectedBorder"));
+ e.setAttribute(TQString::fromLatin1("value"), (t->thumbSelBorder ? "TRUE" : "FALSE"));
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ThumbnailSelectedBorderColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->thumbSelBorderColor.name()).upper());
+ themeElem.appendChild(e);
+
+ // listview.regular props -------------------------------------------------
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewRegularColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->listRegColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewRegularColorTo"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->listRegColorTo.name()).upper());
+ themeElem.appendChild(e);
+
+ switch(t->listRegBevel)
+ {
+ case(Theme::FLAT):
+ {
+ val = TQString("FLAT");
+ break;
+ }
+ case(Theme::RAISED):
+ {
+ val = TQString("RAISED");
+ break;
+ }
+ case(Theme::SUNKEN):
+ {
+ val = TQString("SUNKEN");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewRegularBevel"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ switch(t->listRegGrad)
+ {
+ case(Theme::SOLID):
+ {
+ val = TQString("SOLID");
+ break;
+ }
+ case(Theme::HORIZONTAL):
+ {
+ val = TQString("HORIZONTAL");
+ break;
+ }
+ case(Theme::VERTICAL):
+ {
+ val = TQString("VERTICAL");
+ break;
+ }
+ case(Theme::DIAGONAL):
+ {
+ val = TQString("DIAGONAL");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewRegularGradient"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewRegularBorder"));
+ e.setAttribute(TQString::fromLatin1("value"), (t->listRegBorder ? "TRUE" : "FALSE"));
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewRegularBorderColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->listRegBorderColor.name()).upper());
+ themeElem.appendChild(e);
+
+ // listview.selected props -------------------------------------------------
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewSelectedColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->listSelColor.name()).upper());
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewSelectedColorTo"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->listSelColorTo.name()).upper());
+ themeElem.appendChild(e);
+
+ switch(t->listSelBevel)
+ {
+ case(Theme::FLAT):
+ {
+ val = TQString("FLAT");
+ break;
+ }
+ case(Theme::RAISED):
+ {
+ val = TQString("RAISED");
+ break;
+ }
+ case(Theme::SUNKEN):
+ {
+ val = TQString("SUNKEN");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewSelectedBevel"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ switch(t->listSelGrad)
+ {
+ case(Theme::SOLID):
+ {
+ val = TQString("SOLID");
+ break;
+ }
+ case(Theme::HORIZONTAL):
+ {
+ val = TQString("HORIZONTAL");
+ break;
+ }
+ case(Theme::VERTICAL):
+ {
+ val = TQString("VERTICAL");
+ break;
+ }
+ case(Theme::DIAGONAL):
+ {
+ val = TQString("DIAGONAL");
+ break;
+ }
+ };
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewSelectedGradient"));
+ e.setAttribute(TQString::fromLatin1("value"), val);
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewSelectedBorder"));
+ e.setAttribute(TQString::fromLatin1("value"), (t->listSelBorder ? "TRUE" : "FALSE"));
+ themeElem.appendChild(e);
+
+ e = xmlDoc.createElement(TQString::fromLatin1("ListviewSelectedBorderColor"));
+ e.setAttribute(TQString::fromLatin1("value"), TQString(t->listSelBorderColor.name()).upper());
+ themeElem.appendChild(e);
+
+ // -------------------------------------------------------------------------
+
+ TQTextStream stream(&themeFile);
+ stream.setEncoding(TQTextStream::UnicodeUTF8);
+ stream << xmlDoc.toString();
+ themeFile.close();
+
+ return true;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/themeengine/themeengine.h b/src/libs/themeengine/themeengine.h
new file mode 100644
index 00000000..33abfb07
--- /dev/null
+++ b/src/libs/themeengine/themeengine.h
@@ -0,0 +1,107 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-02
+ * Description : theme engine methods
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef THEMEENGINE_H
+#define THEMEENGINE_H
+
+// TQt includes.
+
+#include <tqstringlist.h>
+#include <tqobject.h>
+#include <tqcolor.h>
+#include <tqpixmap.h>
+#include <tqdom.h>
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class Theme;
+class ThemeEnginePriv;
+
+class DIGIKAM_EXPORT ThemeEngine : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ ~ThemeEngine();
+ static ThemeEngine* instance();
+
+ void scanThemes();
+ TQStringList themeNames() const;
+ bool saveTheme();
+
+ void setCurrentTheme(const TQString& name);
+ void setCurrentTheme(const Theme& theme, const TQString& name,
+ bool loadFromDisk=false);
+
+ Theme* getCurrentTheme() const;
+ TQString getCurrentThemeName() const;
+
+ TQColor baseColor() const;
+ TQColor thumbSelColor() const;
+ TQColor thumbRegColor() const;
+
+ TQColor textRegColor() const;
+ TQColor textSelColor() const;
+ TQColor textSpecialRegColor() const;
+ TQColor textSpecialSelColor() const;
+
+ TQPixmap bannerPixmap(int w, int h);
+ TQPixmap thumbRegPixmap(int w, int h);
+ TQPixmap thumbSelPixmap(int w, int h);
+ TQPixmap listRegPixmap(int w, int h);
+ TQPixmap listSelPixmap(int w, int h);
+
+private:
+
+ ThemeEngine();
+ static ThemeEngine* m_instance;
+
+ void buildDefaultTheme();
+ bool loadTheme();
+ void changePalette();
+ TQString resourceValue(const TQDomElement &rootElem, const TQString& key);
+
+signals:
+
+ void signalThemeChanged();
+
+public slots:
+
+ void slotChangeTheme(const TQString& name);
+
+private:
+
+ ThemeEnginePriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* THEMEENGINE_H */
diff --git a/src/libs/threadimageio/Makefile.am b/src/libs/threadimageio/Makefile.am
new file mode 100644
index 00000000..a301012e
--- /dev/null
+++ b/src/libs/threadimageio/Makefile.am
@@ -0,0 +1,23 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libthreadimageio.la
+
+libthreadimageio_la_SOURCES = loadsavethread.cpp \
+ managedloadsavethread.cpp \
+ sharedloadsavethread.cpp \
+ previewloadthread.cpp \
+ loadingdescription.cpp \
+ loadsavetask.cpp \
+ previewtask.cpp \
+ loadingcache.cpp \
+ loadingcacheinterface.cpp
+
+libthreadimageio_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/loaders \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ $(LIBKEXIV2_CFLAGS) \
+ -I$(top_srcdir)/src/libs/jpegutils \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) $(all_includes)
diff --git a/src/libs/threadimageio/loadingcache.cpp b/src/libs/threadimageio/loadingcache.cpp
new file mode 100644
index 00000000..04dd3e4f
--- /dev/null
+++ b/src/libs/threadimageio/loadingcache.cpp
@@ -0,0 +1,256 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-11
+ * Description : shared image loading and caching
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqapplication.h>
+#include <tqvariant.h>
+
+// KDE includes.
+
+#include <kdirwatch.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "loadingcache.h"
+#include "loadingcache.moc"
+
+namespace Digikam
+{
+
+class LoadingCachePriv
+{
+public:
+
+ TQCache<DImg> imageCache;
+ TQDict<LoadingProcess> loadingDict;
+ TQMutex mutex;
+ TQWaitCondition condVar;
+ KDirWatch *watch;
+ TQStringList watchedFiles;
+};
+
+
+LoadingCache *LoadingCache::m_instance = 0;
+
+LoadingCache *LoadingCache::cache()
+{
+ if (!m_instance)
+ m_instance = new LoadingCache;
+ return m_instance;
+}
+
+void LoadingCache::cleanUp()
+{
+ if (m_instance)
+ delete m_instance;
+}
+
+
+LoadingCache::LoadingCache()
+{
+ d = new LoadingCachePriv;
+
+ d->imageCache.setAutoDelete(true);
+ // default value: 60 MB of cache
+ setCacheSize(60);
+
+ d->watch = new KDirWatch;
+
+ connect(d->watch, TQ_SIGNAL(dirty(const TQString &)),
+ this, TQ_SLOT(slotFileDirty(const TQString &)));
+}
+
+LoadingCache::~LoadingCache()
+{
+ delete d->watch;
+ delete d;
+ m_instance = 0;
+}
+
+DImg *LoadingCache::retrieveImage(const TQString &cacheKey)
+{
+ return d->imageCache.find(cacheKey);
+}
+
+bool LoadingCache::putImage(const TQString &cacheKey, DImg *img, const TQString &filePath)
+{
+ bool successfulyInserted;
+
+ // use size of image as cache cost, take care for wrapped preview TQImages
+ int cost = img->numBytes();
+ TQVariant attribute(img->attribute("previewTQImage"));
+ if (attribute.isValid())
+ {
+ cost = attribute.toImage().numBytes();
+ }
+
+ if ( d->imageCache.insert(cacheKey, img, cost) )
+ {
+ if (!filePath.isEmpty())
+ {
+ // store file path as attribute for our own use
+ img->setAttribute("loadingCacheFilePath", TQVariant(filePath));
+ }
+ successfulyInserted = true;
+ }
+ else
+ {
+ // need to delete object if it was not successfuly inserted (too large)
+ delete img;
+ successfulyInserted = false;
+ }
+
+ if (!filePath.isEmpty())
+ {
+ // schedule update of file watch
+ // KDirWatch can only be accessed from main thread!
+ TQApplication::postEvent(this, new TQCustomEvent(TQEvent::User));
+ }
+ return successfulyInserted;
+}
+
+void LoadingCache::removeImage(const TQString &cacheKey)
+{
+ d->imageCache.remove(cacheKey);
+}
+
+void LoadingCache::removeImages()
+{
+ d->imageCache.clear();
+}
+
+void LoadingCache::slotFileDirty(const TQString &path)
+{
+ // Signal comes from main thread, we need to lock ourselves.
+ CacheLock lock(this);
+ //DDebug() << "LoadingCache slotFileDirty " << path << endl;
+ for (TQCacheIterator<DImg> it(d->imageCache); it.current(); ++it)
+ {
+ if (it.current()->attribute("loadingCacheFilePath").toString() == path)
+ {
+ //DDebug() << " removing watch and cache entry for " << path << endl;
+ d->imageCache.remove(it.currentKey());
+ d->watch->removeFile(path);
+ d->watchedFiles.remove(path);
+ }
+ }
+}
+
+void LoadingCache::customEvent(TQCustomEvent *)
+{
+ // Event comes from main thread, we need to lock ourselves.
+ CacheLock lock(this);
+
+ // get a list of files in cache that need watch
+ TQStringList toBeAdded;
+ TQStringList toBeRemoved = d->watchedFiles;
+ for (TQCacheIterator<DImg> it(d->imageCache); it.current(); ++it)
+ {
+ TQString watchPath = it.current()->attribute("loadingCacheFilePath").toString();
+ if (!watchPath.isEmpty())
+ {
+ if (!d->watchedFiles.contains(watchPath))
+ toBeAdded.append(watchPath);
+ toBeRemoved.remove(watchPath);
+ }
+ }
+
+ for (TQStringList::iterator it = toBeRemoved.begin(); it != toBeRemoved.end(); ++it)
+ {
+ //DDebug() << "removing watch for " << *it << endl;
+ d->watch->removeFile(*it);
+ d->watchedFiles.remove(*it);
+ }
+
+ for (TQStringList::iterator it = toBeAdded.begin(); it != toBeAdded.end(); ++it)
+ {
+ //DDebug() << "adding watch for " << *it << endl;
+ d->watch->addFile(*it);
+ d->watchedFiles.append(*it);
+ }
+
+}
+
+bool LoadingCache::isCacheable(const DImg *img)
+{
+ // return whether image fits in cache
+ return (uint)d->imageCache.maxCost() >= img->numBytes();
+}
+
+void LoadingCache::addLoadingProcess(LoadingProcess *process)
+{
+ d->loadingDict.insert(process->cacheKey(), process);
+}
+
+LoadingProcess *LoadingCache::retrieveLoadingProcess(const TQString &cacheKey)
+{
+ return d->loadingDict.find(cacheKey);
+}
+
+void LoadingCache::removeLoadingProcess(LoadingProcess *process)
+{
+ d->loadingDict.remove(process->cacheKey());
+}
+
+void LoadingCache::notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description)
+{
+ for (TQDictIterator<LoadingProcess> it(d->loadingDict); it.current(); ++it)
+ {
+ it.current()->notifyNewLoadingProcess(process, description);
+ }
+}
+
+void LoadingCache::setCacheSize(int megabytes)
+{
+ d->imageCache.setMaxCost(megabytes * 1024 * 1024);
+}
+
+//---------------------------------------------------------------------------------------------------
+
+LoadingCache::CacheLock::CacheLock(LoadingCache *cache)
+ : m_cache(cache)
+{
+ m_cache->d->mutex.lock();
+}
+
+LoadingCache::CacheLock::~CacheLock()
+{
+ m_cache->d->mutex.unlock();
+}
+
+void LoadingCache::CacheLock::wakeAll()
+{
+ // obviously the mutex is locked when this function is called
+ m_cache->d->condVar.wakeAll();
+}
+
+void LoadingCache::CacheLock::timedWait()
+{
+ // same as above, the mutex is certainly locked
+ m_cache->d->condVar.wait(&m_cache->d->mutex, 1000);
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/threadimageio/loadingcache.h b/src/libs/threadimageio/loadingcache.h
new file mode 100644
index 00000000..d4689305
--- /dev/null
+++ b/src/libs/threadimageio/loadingcache.h
@@ -0,0 +1,135 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-11
+ * Description : shared image loading and caching
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef LOADING_CACHE_H
+#define LOADING_CACHE_H
+
+#include <tqptrlist.h>
+#include <tqcache.h>
+#include <tqdict.h>
+#include <tqmutex.h>
+
+#include "dimg.h"
+#include "loadsavethread.h"
+
+namespace Digikam
+{
+
+class LoadingProcessListener
+{
+public:
+
+ virtual bool querySendNotifyEvent() = 0;
+ virtual TQObject *eventReceiver() = 0;
+ virtual LoadSaveThread::AccessMode accessMode() = 0;
+
+};
+
+class LoadingProcess
+{
+public:
+
+ virtual bool completed() = 0;
+ virtual TQString filePath() = 0;
+ virtual TQString cacheKey() = 0;
+ virtual void addListener(LoadingProcessListener *listener) = 0;
+ virtual void removeListener(LoadingProcessListener *listener) = 0;
+ virtual void notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description) = 0;
+
+};
+
+class LoadingCachePriv;
+
+class LoadingCache : public TQObject
+{
+
+ TQ_OBJECT
+
+
+public:
+
+ static LoadingCache *cache();
+ static void cleanUp();
+ ~LoadingCache();
+
+ // all functions shall only be called when a CacheLock is held
+ class CacheLock
+ {
+ public:
+ CacheLock(LoadingCache *cache);
+ ~CacheLock();
+ void wakeAll();
+ void timedWait();
+ private:
+ LoadingCache *m_cache;
+ };
+
+ // Retrieves an image for the given string from the cache,
+ // or 0 if no image is found.
+ DImg *retrieveImage(const TQString &cacheKey);
+ // Returns whether the given DImg fits in the cache.
+ bool isCacheable(const DImg *img);
+ // Put image into for given string into the cache.
+ // Returns true if image has been put in the cache, false otherwise.
+ // Ownership of the DImg instance is passed to the cache.
+ // When it cannot be put in the cache it is deleted.
+ // The third parameter specifies a file path that will be watched.
+ // If this file changes, the object will be removed from the cache.
+ bool putImage(const TQString &cacheKey, DImg *img, const TQString &filePath);
+ void removeImage(const TQString &cacheKey);
+ void removeImages();
+
+ // Find the loading process for given cacheKey, or 0 if not found
+ LoadingProcess *retrieveLoadingProcess(const TQString &cacheKey);
+ // Add a loading process to the list. Only one loading process
+ // for the same cache key is registered at a time.
+ void addLoadingProcess(LoadingProcess *process);
+ // Remove loading process for given cache key
+ void removeLoadingProcess(LoadingProcess *process);
+ // Notify all currently registered loading processes
+ void notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description);
+
+ void setCacheSize(int megabytes);
+
+protected:
+
+ virtual void customEvent (TQCustomEvent *event);
+
+private slots:
+
+ void slotFileDirty(const TQString &path);
+
+private:
+
+ static LoadingCache *m_instance;
+
+ LoadingCache();
+
+ friend class CacheLock;
+ LoadingCachePriv *d;
+
+};
+
+} // namespace Digikam
+
+#endif
diff --git a/src/libs/threadimageio/loadingcacheinterface.cpp b/src/libs/threadimageio/loadingcacheinterface.cpp
new file mode 100644
index 00000000..8cfa5ab9
--- /dev/null
+++ b/src/libs/threadimageio/loadingcacheinterface.cpp
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-06
+ * Description : shared image loading and caching
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+
+#include "loadingcacheinterface.h"
+#include "loadingcache.h"
+
+namespace Digikam
+{
+
+void LoadingCacheInterface::cleanUp()
+{
+ LoadingCache::cleanUp();
+}
+
+void LoadingCacheInterface::cleanFromCache(const TQString &filePath)
+{
+ LoadingCache *cache = LoadingCache::cache();
+ LoadingCache::CacheLock lock(cache);
+ TQStringList possibleCacheKeys = LoadingDescription::possibleCacheKeys(filePath);
+ for (TQStringList::iterator it = possibleCacheKeys.begin(); it != possibleCacheKeys.end(); ++it)
+ {
+ cache->removeImage(*it);
+ }
+}
+
+void LoadingCacheInterface::cleanCache()
+{
+ LoadingCache *cache = LoadingCache::cache();
+ LoadingCache::CacheLock lock(cache);
+ cache->removeImages();
+}
+
+void LoadingCacheInterface::putImage(const TQString &filePath, const DImg &img)
+{
+ LoadingCache *cache = LoadingCache::cache();
+ LoadingCache::CacheLock lock(cache);
+ if (cache->isCacheable(&img))
+ {
+ DImg *copy = new DImg(img);
+ copy->detach();
+ cache->putImage(filePath, copy, filePath);
+ }
+}
+
+void LoadingCacheInterface::setCacheOptions(int cacheSize)
+{
+ LoadingCache *cache = LoadingCache::cache();
+ LoadingCache::CacheLock lock(cache);
+ cache->setCacheSize(cacheSize);
+}
+
+} // namespace Digikam
diff --git a/src/libs/threadimageio/loadingcacheinterface.h b/src/libs/threadimageio/loadingcacheinterface.h
new file mode 100644
index 00000000..ab3f4eca
--- /dev/null
+++ b/src/libs/threadimageio/loadingcacheinterface.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-06
+ * Description : shared image loading and caching
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef LOADING_CACHE_INTERFACE_H
+#define LOADING_CACHE_INTERFACE_H
+
+#include <tqstring.h>
+
+#include "digikam_export.h"
+#include "dimg.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT LoadingCacheInterface
+{
+public:
+ // clean up cache at shutdown
+ static void cleanUp();
+ // remove an image from the cache
+ // (e.g. when image has changed on disk)
+ static void cleanFromCache(const TQString &filePath);
+ // remove all images from the cache
+ // (e.g. when loading settings changed)
+ static void cleanCache();
+ // add a copy of the image to cache
+ static void putImage(const TQString &filePath, const DImg &img);
+ // Set cache size in Megabytes.
+ // Set to 0 to disable caching.
+ static void setCacheOptions(int cacheSize);
+};
+
+} // namespace Digikam
+
+#endif
+
diff --git a/src/libs/threadimageio/loadingdescription.cpp b/src/libs/threadimageio/loadingdescription.cpp
new file mode 100644
index 00000000..423ee99c
--- /dev/null
+++ b/src/libs/threadimageio/loadingdescription.cpp
@@ -0,0 +1,152 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-02-03
+ * Description : Loading parameters for multithreaded loading
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "loadingdescription.h"
+
+namespace Digikam
+{
+
+bool LoadingDescription::PreviewParameters::operator==(const PreviewParameters &other) const
+{
+ return isPreview == other.isPreview
+ && size == other.size
+ && exifRotate == other.exifRotate;
+}
+
+LoadingDescription::LoadingDescription(const TQString &filePath)
+ : filePath(filePath)
+{
+ rawDecodingSettings = DRawDecoding();
+}
+
+LoadingDescription::LoadingDescription(const TQString &filePath, DRawDecoding settings)
+ : filePath(filePath), rawDecodingSettings(settings)
+{
+}
+
+LoadingDescription::LoadingDescription(const TQString &filePath, int size, bool exifRotate)
+ : filePath(filePath)
+{
+ rawDecodingSettings = DRawDecoding();
+ previewParameters.isPreview = false;
+ previewParameters.size = size;
+ previewParameters.exifRotate = exifRotate;
+}
+
+TQString LoadingDescription::cacheKey() const
+{
+ // Here we have the knowledge which LoadingDescriptions / RawFileDecodingSettings
+ // must be cached separately.
+ // Current assumption:
+ // Eight-bit images are needed for LightTable, and if 16-bit is enabled,
+ // 16-bit half size images for the histogram sidebar,
+ // and 16-bit full size images for the image editor.
+ // Use previewParameters.size, not isPreview - if it is 0, full loading is used.
+
+ TQString suffix = rawDecodingSettings.sixteenBitsImage ? "-16" : "-8";
+
+ if (rawDecodingSettings.halfSizeColorImage)
+ return filePath + suffix + "-halfSizeColorImage";
+ else if (previewParameters.size)
+ return filePath + suffix + "-previewImage";
+ else
+ return filePath + suffix;
+}
+
+TQStringList LoadingDescription::lookupCacheKeys() const
+{
+ // Build a hierarchy which cache entries may be used for this LoadingDescription.
+ // Typically, the first is the best, but an actual loading operation may use a
+ // lower-quality loading and will effectively only add the last entry of the
+ // list to the cache, although it can accept the first if already available.
+ // Sixteen-bit images cannot be used used instead of eight-bit ones because
+ // color management is needed to display them.
+
+ TQString suffix = rawDecodingSettings.sixteenBitsImage ? "-16" : "-8";
+
+ TQStringList keys;
+ keys.append(filePath + suffix);
+ if (rawDecodingSettings.halfSizeColorImage)
+ keys.append(filePath + suffix + "-halfSizeColorImage");
+ if (previewParameters.size)
+ keys.append(filePath + suffix + "-previewImage");
+ return keys;
+}
+
+bool LoadingDescription::isReducedVersion() const
+{
+ // return true if this loads anything but the full version
+ return rawDecodingSettings.halfSizeColorImage
+ || previewParameters.isPreview;
+}
+
+bool LoadingDescription::operator==(const LoadingDescription &other) const
+{
+ return filePath == other.filePath &&
+ rawDecodingSettings == other.rawDecodingSettings &&
+ previewParameters == other.previewParameters;
+}
+
+bool LoadingDescription::equalsIgnoreReducedVersion(const LoadingDescription &other) const
+{
+ return filePath == other.filePath;
+}
+
+bool LoadingDescription::equalsOrBetterThan(const LoadingDescription &other) const
+{
+ // This method is similar to operator==. But it returns true as well if other
+ // Loads a "better" version than this.
+ // Preview parameters must have the same size, or other has no size restriction.
+ // All raw decoding settings must be equal, only the half size parameter is allowed to vary.
+
+ DRawDecoding fullSize = other.rawDecodingSettings;
+ fullSize.halfSizeColorImage = false;
+
+ return filePath == other.filePath &&
+ (
+ rawDecodingSettings == other.rawDecodingSettings ||
+ rawDecodingSettings == fullSize
+ ) &&
+ (
+ (previewParameters.size == other.previewParameters.size) ||
+ other.previewParameters.size
+ );
+}
+
+TQStringList LoadingDescription::possibleCacheKeys(const TQString &filePath)
+{
+ TQStringList keys;
+ keys.append(filePath + "-16");
+ keys.append(filePath + "-16-halfSizeColorImage");
+ keys.append(filePath + "-16-previewImage");
+ keys.append(filePath + "-8");
+ keys.append(filePath + "-8-halfSizeColorImage");
+ keys.append(filePath + "-8-previewImage");
+ return keys;
+}
+
+
+} // namespace Digikam
+
diff --git a/src/libs/threadimageio/loadingdescription.h b/src/libs/threadimageio/loadingdescription.h
new file mode 100644
index 00000000..848f2b17
--- /dev/null
+++ b/src/libs/threadimageio/loadingdescription.h
@@ -0,0 +1,135 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-16
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef LOADING_DESCRIPTION_H
+#define LOADING_DESCRIPTION_H
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT LoadingDescription
+{
+public:
+
+ class PreviewParameters
+ {
+ public:
+ PreviewParameters()
+ {
+ isPreview = false;
+ size = 0;
+ exifRotate = false;
+ }
+
+ bool isPreview;
+ int size;
+ bool exifRotate;
+
+ bool operator==(const PreviewParameters &other) const;
+ };
+
+ /**
+ * An invalid LoadingDescription
+ */
+ LoadingDescription()
+ {
+ }
+
+ /**
+ * Use this for files that are not raw files.
+ * Stores only the filePath.
+ */
+ LoadingDescription(const TQString &filePath);
+
+ /**
+ * For raw files:
+ * Stores filePath and RawDecodingSettings
+ */
+ LoadingDescription(const TQString &filePath, DRawDecoding settings);
+
+ /**
+ * For preview jobs:
+ * Stores preview max size and exif rotation.
+ * Exif Rotation:
+ * The exif rotation is only a hint.
+ * Call LoadSaveThread::exifRotate to make sure that the image is really
+ * rotated. It is safe to call this method even if the image is rotated.
+ * Raw files:
+ * If size is not 0, the embedded preview will be loaded if available.
+ * If size is 0, DImg based loading will be used with default raw decoding settings.
+ */
+ LoadingDescription(const TQString &filePath, int size, bool exifRotate);
+
+ TQString filePath;
+ DRawDecoding rawDecodingSettings;
+ PreviewParameters previewParameters;
+
+ /**
+ * Return the cache key this description shall be stored as
+ */
+ TQString cacheKey() const;
+ /**
+ * Return all possible cache keys, starting with the best choice,
+ * for which a result may be found in the cache for this description.
+ * Included in the list are better quality versions, if this description is reduced.
+ */
+ TQStringList lookupCacheKeys() const;
+ /**
+ * Returns whether this description describes a loading operation which
+ * loads the image in a reduced version (quality, size etc.)
+ */
+ bool isReducedVersion() const;
+
+ /**
+ * Returns whether the other loading task equals this one
+ */
+ bool operator==(const LoadingDescription &other) const;
+ bool operator!=(const LoadingDescription &other) const
+ { return !operator==(other); }
+ /**
+ * Returns whether the other loading task equals this one
+ * ignoring parameters used to specify a reduced version.
+ */
+ bool equalsIgnoreReducedVersion(const LoadingDescription &other) const;
+
+ /**
+ * Returns whether this loading task equals the other one
+ * or is superior to it, if the other one is a reduced version
+ */
+ bool equalsOrBetterThan(const LoadingDescription &other) const;
+
+ /**
+ * Returns all possible cacheKeys for the given file path
+ * (all cache keys under which the given file could be stored in the cache).
+ */
+ static TQStringList possibleCacheKeys(const TQString &filePath);
+};
+
+} // namespace Digikam
+
+#endif // LOADING_DESCRIPTION_H
diff --git a/src/libs/threadimageio/loadsavetask.cpp b/src/libs/threadimageio/loadsavetask.cpp
new file mode 100644
index 00000000..6879e533
--- /dev/null
+++ b/src/libs/threadimageio/loadsavetask.cpp
@@ -0,0 +1,424 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-17
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#include "loadsavetask.h"
+
+// TQt includes.
+
+#include <tqapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "loadsavethread.h"
+#include "managedloadsavethread.h"
+#include "sharedloadsavethread.h"
+#include "loadingcache.h"
+
+namespace Digikam
+{
+
+void LoadingProgressEvent::notify(LoadSaveThread *thread)
+{
+ thread->loadingProgress(m_loadingDescription, m_progress);
+}
+
+void SavingProgressEvent::notify(LoadSaveThread *thread)
+{
+ thread->savingProgress(m_filePath, m_progress);
+}
+
+void StartedLoadingEvent::notify(LoadSaveThread *thread)
+{
+ thread->imageStartedLoading(m_loadingDescription);
+}
+
+void StartedSavingEvent::notify(LoadSaveThread *thread)
+{
+ thread->imageStartedSaving(m_filePath);
+}
+
+void LoadedEvent::notify(LoadSaveThread *thread)
+{
+ thread->imageLoaded(m_loadingDescription, m_img);
+}
+
+void MoreCompleteLoadingAvailableEvent::notify(LoadSaveThread *thread)
+{
+ thread->moreCompleteLoadingAvailable(m_oldDescription, m_newDescription);
+}
+
+void SavedEvent::notify(LoadSaveThread *thread)
+{
+ thread->imageSaved(m_filePath, m_success);
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void LoadingTask::execute()
+{
+ if (m_loadingTaskStatus == LoadingTaskStatusStopping)
+ return;
+ DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
+ m_thread->taskHasFinished();
+ TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img));
+}
+
+LoadingTask::TaskType LoadingTask::type()
+{
+ return TaskTypeLoading;
+}
+
+void LoadingTask::progressInfo(const DImg *, float progress)
+{
+ if (m_loadingTaskStatus == LoadingTaskStatusLoading)
+ {
+ if (m_thread->querySendNotifyEvent())
+ TQApplication::postEvent(m_thread, new LoadingProgressEvent(m_loadingDescription.filePath, progress));
+ }
+}
+
+bool LoadingTask::continueQuery(const DImg *)
+{
+ return m_loadingTaskStatus != LoadingTaskStatusStopping;
+}
+
+void LoadingTask::setStatus(LoadingTaskStatus status)
+{
+ m_loadingTaskStatus = status;
+}
+
+
+// This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader)
+// is waiting for the process to finish, but the main thread is waiting
+// for the thread to finish and no TDEProcess events are delivered.
+// Remove when porting to TQt4.
+bool LoadingTask::isShuttingDown()
+{
+ return m_thread->isShuttingDown();
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void SharedLoadingTask::execute()
+{
+ if (m_loadingTaskStatus == LoadingTaskStatusStopping)
+ return;
+ // send StartedLoadingEvent from each single Task, not via LoadingProcess list
+ TQApplication::postEvent(m_thread, new StartedLoadingEvent(m_loadingDescription.filePath));
+
+ LoadingCache *cache = LoadingCache::cache();
+ {
+ LoadingCache::CacheLock lock(cache);
+
+ // find possible cached images
+ DImg *cachedImg = 0;
+ TQStringList lookupKeys = m_loadingDescription.lookupCacheKeys();
+ for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
+ if ( (cachedImg = cache->retrieveImage(*it)) )
+ break;
+ }
+
+ if (cachedImg)
+ {
+ // image is found in image cache, loading is successful
+ DImg img(*cachedImg);
+ if (accessMode() == LoadSaveThread::AccessModeReadWrite)
+ img = img.copy();
+ TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img));
+ return;
+ }
+ else
+ {
+ // find possible running loading process
+ m_usedProcess = 0;
+ for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
+ if ( (m_usedProcess = cache->retrieveLoadingProcess(*it)) )
+ {
+ break;
+ }
+ }
+
+ if (m_usedProcess)
+ {
+ // Other process is right now loading this image.
+ // Add this task to the list of listeners and
+ // attach this thread to the other thread, wait until loading
+ // has finished.
+ m_usedProcess->addListener(this);
+ // break loop when either the loading has completed, or this task is being stopped
+ while ( !m_usedProcess->completed() && m_loadingTaskStatus != LoadingTaskStatusStopping )
+ lock.timedWait();
+ // remove listener from process
+ m_usedProcess->removeListener(this);
+ // wake up the process which is waiting until all listeners have removed themselves
+ lock.wakeAll();
+ // set to 0, as checked in setStatus
+ m_usedProcess = 0;
+ //DDebug() << "SharedLoadingTask " << this << ": waited" << endl;
+ return;
+ }
+ else
+ {
+ // Neither in cache, nor currently loading in different thread.
+ // Load it here and now, add this LoadingProcess to cache list.
+ cache->addLoadingProcess(this);
+ // Add this to the list of listeners
+ addListener(this);
+ // for use in setStatus
+ m_usedProcess = this;
+ // Notify other processes that we are now loading this image.
+ // They might be interested - see notifyNewLoadingProcess below
+ cache->notifyNewLoadingProcess(this, m_loadingDescription);
+ }
+ }
+ }
+
+ // load image
+ DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
+
+ bool isCached = false;
+ {
+ LoadingCache::CacheLock lock(cache);
+ // put (valid) image into cache of loaded images
+ if (!img.isNull())
+ isCached = cache->putImage(m_loadingDescription.cacheKey(), new DImg(img), m_loadingDescription.filePath);
+ // remove this from the list of loading processes in cache
+ cache->removeLoadingProcess(this);
+ }
+
+ // following the golden rule to avoid deadlocks, do this when CacheLock is not held
+ m_thread->taskHasFinished();
+
+ {
+ LoadingCache::CacheLock lock(cache);
+ //DDebug() << "SharedLoadingTask " << this << ": image loaded, " << img.isNull() << endl;
+ // indicate that loading has finished so that listeners can stop waiting
+ m_completed = true;
+
+ // Optimize so that no unnecessary copying is done.
+ // If image has been put in cache, the initial copy has been consumed for this.
+ // If image is too large for cache, the initial copy is still available.
+ bool usedInitialCopy = isCached;
+ // dispatch image to all listeners, including this
+ for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next())
+ {
+ // This code sends a copy only when ReadWrite access is requested.
+ // Otherwise, the image from the cache is sent.
+ // As the image in the cache will be deleted from any thread, the explicit sharing
+ // needs to be thread-safe to avoid the risk of memory leaks.
+ // This is the case only for TQt4, so uncomment this code when porting.
+ /*
+ if (l->accessMode() == LoadSaveThread::AccessModeReadWrite)
+ {
+ // If a listener requested ReadWrite access, it gets a deep copy.
+ // DImg is explicitly shared.
+ DImg copy = img.copy();
+ TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription.filePath, copy));
+ }
+ else
+ TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription.filePath, img));
+ */
+ // TQt3: The same copy for all Read listeners (it is assumed that they will delete it only in the main thread),
+ // an extra copy for each ReadWrite listener
+ DImg readerCopy;
+ if (l->accessMode() == LoadSaveThread::AccessModeReadWrite)
+ {
+ // If a listener requested ReadWrite access, it gets a deep copy.
+ // DImg is explicitly shared.
+ DImg copy;
+ if (usedInitialCopy)
+ {
+ copy = img.copy();
+ }
+ else
+ {
+ copy = img;
+ usedInitialCopy = true;
+ }
+ TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, copy));
+ }
+ else
+ {
+ if (readerCopy.isNull())
+ {
+ if (usedInitialCopy)
+ {
+ readerCopy = img.copy();
+ }
+ else
+ {
+ readerCopy = img;
+ usedInitialCopy = true;
+ }
+ }
+ TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, readerCopy));
+ }
+ }
+
+ // remove myself from list of listeners
+ removeListener(this);
+ // wake all listeners waiting on cache condVar, so that they remove themselves
+ lock.wakeAll();
+ // wait until all listeners have removed themselves
+ while (m_listeners.count() != 0)
+ lock.timedWait();
+ // set to 0, as checked in setStatus
+ m_usedProcess = 0;
+ }
+}
+
+void SharedLoadingTask::progressInfo(const DImg *, float progress)
+{
+ if (m_loadingTaskStatus == LoadingTaskStatusLoading)
+ {
+ LoadingCache *cache = LoadingCache::cache();
+ LoadingCache::CacheLock lock(cache);
+
+ for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next())
+ {
+ if (l->querySendNotifyEvent())
+ TQApplication::postEvent(l->eventReceiver(), new LoadingProgressEvent(m_loadingDescription, progress));
+ }
+ }
+}
+
+bool SharedLoadingTask::continueQuery(const DImg *)
+{
+ // If this is called, the thread is currently loading an image.
+ // In shared loading, we cannot stop until all listeners have been removed as well
+ return (m_loadingTaskStatus != LoadingTaskStatusStopping) || (m_listeners.count() != 0);
+}
+
+void SharedLoadingTask::setStatus(LoadingTaskStatus status)
+{
+ m_loadingTaskStatus = status;
+ if (m_loadingTaskStatus == LoadingTaskStatusStopping)
+ {
+ LoadingCache *cache = LoadingCache::cache();
+ LoadingCache::CacheLock lock(cache);
+
+ // check for m_usedProcess, to avoid race condition that it has finished before
+ if (m_usedProcess)
+ {
+ // remove this from list of listeners - check in continueQuery() of active thread
+ m_usedProcess->removeListener(this);
+ // wake all listeners - particularly this - from waiting on cache condvar
+ lock.wakeAll();
+ }
+ }
+}
+
+bool SharedLoadingTask::completed()
+{
+ return m_completed;
+}
+
+TQString SharedLoadingTask::filePath()
+{
+ return m_loadingDescription.filePath;
+}
+
+TQString SharedLoadingTask::cacheKey()
+{
+ return m_loadingDescription.cacheKey();
+}
+
+void SharedLoadingTask::addListener(LoadingProcessListener *listener)
+{
+ m_listeners.append(listener);
+}
+
+void SharedLoadingTask::removeListener(LoadingProcessListener *listener)
+{
+ m_listeners.remove(listener);
+}
+
+void SharedLoadingTask::notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description)
+{
+ // Ok, we are notified that another task has been started in another thread.
+ // We are of course only interested if the task loads the same file,
+ // and we are right now loading a reduced version, and the other task is loading the full version.
+ // In this case, we notify our own thread (a signal to the API user is emitted) of this.
+ // The fact that we are receiving the method call shows that this task is registered with the LoadingCache,
+ // somewhere in between the calls to addLoadingProcess(this) and removeLoadingProcess(this) above.
+ if (process != this &&
+ m_loadingDescription.isReducedVersion() &&
+ m_loadingDescription.equalsIgnoreReducedVersion(description) &&
+ !description.isReducedVersion()
+ )
+ {
+ for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next())
+ {
+ TQApplication::postEvent(l->eventReceiver(), new MoreCompleteLoadingAvailableEvent(m_loadingDescription, description));
+ }
+ }
+}
+
+bool SharedLoadingTask::querySendNotifyEvent()
+{
+ return m_thread->querySendNotifyEvent();
+}
+
+TQObject *SharedLoadingTask::eventReceiver()
+{
+ return m_thread;
+}
+
+LoadSaveThread::AccessMode SharedLoadingTask::accessMode()
+{
+ return m_accessMode;
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void SavingTask::execute()
+{
+ bool success = m_img.save(m_filePath, m_format, this);
+ m_thread->taskHasFinished();
+ TQApplication::postEvent(m_thread, new SavedEvent(m_filePath, success));
+};
+
+LoadingTask::TaskType SavingTask::type()
+{
+ return TaskTypeSaving;
+}
+
+void SavingTask::progressInfo(const DImg *, float progress)
+{
+ if (m_thread->querySendNotifyEvent())
+ TQApplication::postEvent(m_thread, new SavingProgressEvent(m_filePath, progress));
+}
+
+bool SavingTask::continueQuery(const DImg *)
+{
+ return m_savingTaskStatus != SavingTaskStatusStopping;
+}
+
+void SavingTask::setStatus(SavingTaskStatus status)
+{
+ m_savingTaskStatus = status;
+}
+
+} //namespace Digikam
diff --git a/src/libs/threadimageio/loadsavetask.h b/src/libs/threadimageio/loadsavetask.h
new file mode 100644
index 00000000..833f86fa
--- /dev/null
+++ b/src/libs/threadimageio/loadsavetask.h
@@ -0,0 +1,360 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+
+#ifndef LOAD_SAVE_TASK_H
+#define LOAD_SAVE_TASK_H
+
+// TQt includes.
+
+#include <tqevent.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dimgloaderobserver.h"
+#include "loadingdescription.h"
+#include "loadingcache.h"
+
+namespace Digikam
+{
+
+class LoadSaveThread;
+
+class LoadSaveTask
+{
+public:
+
+ LoadSaveTask(LoadSaveThread* thread)
+ : m_thread(thread)
+ {};
+ virtual ~LoadSaveTask() {};
+
+ virtual void execute() = 0;
+
+ enum TaskType
+ {
+ TaskTypeLoading,
+ TaskTypeSaving
+ };
+
+ virtual TaskType type() = 0;
+
+protected:
+
+ LoadSaveThread *m_thread;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class NotifyEvent : public TQCustomEvent
+{
+public:
+
+ static TQEvent::Type notifyEventId()
+ { return TQEvent::User; };
+
+ NotifyEvent() : TQCustomEvent(notifyEventId()) {};
+
+ virtual void notify(LoadSaveThread *thread) = 0;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class ProgressEvent : public NotifyEvent
+{
+public:
+
+ ProgressEvent(float progress)
+ : m_progress(progress)
+ {};
+
+protected:
+
+ float m_progress;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class LoadingProgressEvent : public ProgressEvent
+{
+public:
+
+ LoadingProgressEvent(const LoadingDescription &loadingDescription, float progress)
+ : ProgressEvent(progress),
+ m_loadingDescription(loadingDescription)
+ {};
+
+ virtual void notify(LoadSaveThread *thread);
+
+private:
+
+ LoadingDescription m_loadingDescription;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class SavingProgressEvent : public ProgressEvent
+{
+public:
+
+ SavingProgressEvent(const TQString& filePath, float progress)
+ : ProgressEvent(progress),
+ m_filePath(filePath)
+ {};
+
+ virtual void notify(LoadSaveThread *thread);
+
+private:
+
+ TQString m_filePath;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class StartedLoadingEvent : public NotifyEvent
+{
+public:
+
+ StartedLoadingEvent(const LoadingDescription &loadingDescription)
+ : m_loadingDescription(loadingDescription)
+ {};
+
+ virtual void notify(LoadSaveThread *thread);
+
+private:
+
+ LoadingDescription m_loadingDescription;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class StartedSavingEvent : public NotifyEvent
+{
+public:
+
+ StartedSavingEvent(const TQString& filePath)
+ : m_filePath(filePath)
+ {};
+
+ virtual void notify(LoadSaveThread *thread);
+
+private:
+
+ TQString m_filePath;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class LoadedEvent : public NotifyEvent
+{
+public:
+
+ LoadedEvent(const LoadingDescription &loadingDescription, DImg &img)
+ : m_loadingDescription(loadingDescription), m_img(img)
+ {};
+
+ virtual void notify(LoadSaveThread *thread);
+
+private:
+
+ LoadingDescription m_loadingDescription;
+ DImg m_img;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class MoreCompleteLoadingAvailableEvent : public NotifyEvent
+{
+public:
+
+ MoreCompleteLoadingAvailableEvent(const LoadingDescription &oldLoadingDescription,
+ const LoadingDescription &newLoadingDescription)
+ : m_oldDescription(oldLoadingDescription), m_newDescription(newLoadingDescription)
+ {};
+
+ virtual void notify(LoadSaveThread *thread);
+
+private:
+
+ LoadingDescription m_oldDescription;
+ LoadingDescription m_newDescription;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class LoadingTask : public LoadSaveTask, public DImgLoaderObserver
+{
+public:
+
+ enum LoadingTaskStatus
+ {
+ LoadingTaskStatusLoading,
+ LoadingTaskStatusPreloading,
+ LoadingTaskStatusStopping
+ };
+
+ LoadingTask(LoadSaveThread* thread, LoadingDescription description,
+ LoadingTaskStatus loadingTaskStatus = LoadingTaskStatusLoading)
+ : LoadSaveTask(thread), m_loadingDescription(description), m_loadingTaskStatus(loadingTaskStatus)
+ {}
+
+ // LoadSaveTask
+
+ virtual void execute();
+ virtual TaskType type();
+
+ // DImgLoaderObserver
+
+ virtual void progressInfo(const DImg *, float progress);
+ virtual bool continueQuery(const DImg *);
+ virtual bool isShuttingDown();
+
+ virtual void setStatus(LoadingTaskStatus status);
+
+ LoadingTaskStatus status() const
+ {
+ return m_loadingTaskStatus;
+ }
+
+ TQString filePath() const
+ {
+ return m_loadingDescription.filePath;
+ }
+
+ LoadingDescription loadingDescription() const
+ {
+ return m_loadingDescription;
+ }
+
+protected:
+
+ LoadingDescription m_loadingDescription;
+ LoadingTaskStatus m_loadingTaskStatus;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class SharedLoadingTask : public LoadingTask, public LoadingProcess, public LoadingProcessListener
+{
+public:
+
+ SharedLoadingTask(LoadSaveThread* thread, LoadingDescription description,
+ LoadSaveThread::AccessMode mode = LoadSaveThread::AccessModeReadWrite,
+ LoadingTaskStatus loadingTaskStatus = LoadingTaskStatusLoading)
+ : LoadingTask(thread, description, loadingTaskStatus),
+ m_accessMode(mode), m_completed(false), m_usedProcess(0)
+ {}
+
+ virtual void execute();
+ virtual void progressInfo(const DImg *, float progress);
+ virtual bool continueQuery(const DImg *);
+ virtual void setStatus(LoadingTaskStatus status);
+
+ // LoadingProcess
+
+ virtual bool completed();
+ virtual TQString filePath();
+ virtual TQString cacheKey();
+ virtual void addListener(LoadingProcessListener *listener);
+ virtual void removeListener(LoadingProcessListener *listener);
+ virtual void notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description);
+
+ // LoadingProcessListener
+
+ virtual bool querySendNotifyEvent();
+ virtual TQObject *eventReceiver();
+ virtual LoadSaveThread::AccessMode accessMode();
+
+protected:
+
+ LoadSaveThread::AccessMode m_accessMode;
+ bool m_completed;
+ LoadingProcess *m_usedProcess;
+ TQPtrList<LoadingProcessListener> m_listeners;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class SavedEvent : public NotifyEvent
+{
+public:
+
+ SavedEvent(const TQString &filePath, bool success)
+ : m_filePath(filePath), m_success(success)
+ {};
+
+ virtual void notify(LoadSaveThread *thread);
+
+private:
+
+ TQString m_filePath;
+ bool m_success;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+class SavingTask : public LoadSaveTask, public DImgLoaderObserver
+{
+public:
+
+ enum SavingTaskStatus
+ {
+ SavingTaskStatusSaving,
+ SavingTaskStatusStopping
+ };
+
+ SavingTask(LoadSaveThread* thread, DImg &img, const TQString &filePath, const TQString &format)
+ : LoadSaveTask(thread), m_img(img), m_filePath(filePath), m_format(format)
+ {};
+
+ virtual void execute();
+ virtual TaskType type();
+
+ virtual void progressInfo(const DImg *, float progress);
+ virtual bool continueQuery(const DImg *);
+
+ virtual void setStatus(SavingTaskStatus status);
+
+ SavingTaskStatus status() const
+ {
+ return m_savingTaskStatus;
+ }
+
+ TQString filePath() const
+ {
+ return m_filePath;
+ }
+
+private:
+
+ DImg m_img;
+ TQString m_filePath;
+ TQString m_format;
+ SavingTaskStatus m_savingTaskStatus;
+};
+
+} // namespace Digikam
+
+#endif // LOAD_SAVE_TASK_H
diff --git a/src/libs/threadimageio/loadsavethread.cpp b/src/libs/threadimageio/loadsavethread.cpp
new file mode 100644
index 00000000..188592e9
--- /dev/null
+++ b/src/libs/threadimageio/loadsavethread.cpp
@@ -0,0 +1,331 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-17
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "loadsavethread.h"
+#include "managedloadsavethread.h"
+#include "sharedloadsavethread.h"
+#include "loadsavetask.h"
+
+namespace Digikam
+{
+
+class LoadSaveThreadPriv
+{
+public:
+
+ LoadSaveThreadPriv()
+ {
+ running = true;
+ blockNotification = false;
+ lastTask = 0;
+ }
+
+ bool running;
+ bool blockNotification;
+ LoadSaveTask *lastTask;
+
+ TQTime notificationTime;
+};
+
+//---------------------------------------------------------------------------------------------------
+
+LoadSaveThread::LoadSaveThread()
+{
+ d = new LoadSaveThreadPriv;
+ m_currentTask = 0;
+ m_notificationPolicy = NotificationPolicyTimeLimited;
+
+ start();
+}
+
+LoadSaveThread::~LoadSaveThread()
+{
+ d->running = false;
+ {
+ TQMutexLocker lock(&m_mutex);
+ m_condVar.wakeAll();
+ }
+
+ wait();
+
+ if (d->lastTask)
+ delete d->lastTask;
+ delete d;
+}
+
+void LoadSaveThread::load(LoadingDescription description)
+{
+ TQMutexLocker lock(&m_mutex);
+ m_todo.append(new LoadingTask(this, description));
+ m_condVar.wakeAll();
+}
+
+void LoadSaveThread::save(DImg &image, const TQString& filePath, const TQString &format)
+{
+ TQMutexLocker lock(&m_mutex);
+ m_todo.append(new SavingTask(this, image, filePath, format));
+ m_condVar.wakeAll();
+}
+
+void LoadSaveThread::run()
+{
+ while (d->running)
+ {
+ {
+ TQMutexLocker lock(&m_mutex);
+ if (d->lastTask)
+ {
+ delete d->lastTask;
+ d->lastTask = 0;
+ }
+ m_currentTask = m_todo.getFirst();
+ if (m_currentTask)
+ {
+ m_todo.removeFirst();
+ if (m_notificationPolicy == NotificationPolicyTimeLimited)
+ {
+ // set timing values so that first event is sent only
+ // after an initial time span.
+ d->notificationTime = TQTime::currentTime();
+ d->blockNotification = true;
+ }
+ }
+ else
+ m_condVar.wait(&m_mutex, 1000);
+ }
+ if (m_currentTask)
+ m_currentTask->execute();
+ }
+}
+
+void LoadSaveThread::taskHasFinished()
+{
+ // This function is called by the tasks before they send their final message.
+ // This is to guarantee the user of the API that at least the final message
+ // is sent after load() has been called. This might not been the case
+ // if m_currentTask is currently loading the same image and a race condition
+ // between the return from execute and the next run of the loop above occurs.
+ TQMutexLocker lock(&m_mutex);
+ d->lastTask = m_currentTask;
+ m_currentTask = 0;
+}
+
+void LoadSaveThread::customEvent(TQCustomEvent *event)
+{
+ if (event->type() == NotifyEvent::notifyEventId())
+ {
+ switch (m_notificationPolicy)
+ {
+ case NotificationPolicyDirect:
+ d->blockNotification = false;
+ break;
+ case NotificationPolicyTimeLimited:
+ break;
+ }
+ ((NotifyEvent *)event)->notify(this);
+ }
+}
+
+void LoadSaveThread::setNotificationPolicy(NotificationPolicy notificationPolicy)
+{
+ m_notificationPolicy = notificationPolicy;
+ d->blockNotification = false;
+}
+
+bool LoadSaveThread::querySendNotifyEvent()
+{
+ // This function is called from the thread to ask for permission to send a notify event.
+ switch (m_notificationPolicy)
+ {
+ case NotificationPolicyDirect:
+ // Note that m_blockNotification is not protected by a mutex. However, if there is a
+ // race condition, the worst case is that one event is not sent, which is no problem.
+ if (d->blockNotification)
+ return false;
+ else
+ {
+ d->blockNotification = true;
+ return true;
+ }
+ break;
+ case NotificationPolicyTimeLimited:
+ // Current default time value: 100 millisecs.
+ if (d->blockNotification)
+ d->blockNotification = d->notificationTime.msecsTo(TQTime::currentTime()) < 100;
+
+ if (d->blockNotification)
+ return false;
+ else
+ {
+ d->notificationTime = TQTime::currentTime();
+ d->blockNotification = true;
+ return true;
+ }
+ break;
+ }
+ return false;
+}
+
+
+// This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader)
+// is waiting for the process to finish, but the main thread is waiting
+// for the thread to finish and no TDEProcess events are delivered.
+// Remove when porting to TQt4.
+bool LoadSaveThread::isShuttingDown()
+{
+ // the condition is met after d->running is set to false in the destructor
+ return running() && !d->running;
+}
+
+bool LoadSaveThread::exifRotate(DImg &image, const TQString& filePath)
+{
+ TQVariant attribute(image.attribute("exifRotated"));
+ if (attribute.isValid() && attribute.toBool())
+ return false;
+
+ // Raw files are already rotated properlly by dcraw. Only perform auto-rotation with JPEG/PNG/TIFF file.
+ // We don't have a feedback from dcraw about auto-rotated RAW file during decoding. Return true anyway.
+
+ attribute = image.attribute("fromRawEmbeddedPreview");
+ if (DImg::fileFormat(filePath) == DImg::RAW && !(attribute.isValid() && attribute.toBool()) )
+ {
+ return true;
+ }
+
+ // Rotate thumbnail based on metadata orientation information
+
+ DMetadata metadata(filePath);
+ DMetadata::ImageOrientation orientation = metadata.getImageOrientation();
+
+ bool rotatedOrFlipped = false;
+
+ if(orientation != DMetadata::ORIENTATION_NORMAL)
+ {
+ switch (orientation)
+ {
+ case DMetadata::ORIENTATION_NORMAL:
+ case DMetadata::ORIENTATION_UNSPECIFIED:
+ break;
+
+ case DMetadata::ORIENTATION_HFLIP:
+ image.flip(DImg::HORIZONTAL);
+ rotatedOrFlipped = true;
+ break;
+
+ case DMetadata::ORIENTATION_ROT_180:
+ image.rotate(DImg::ROT180);
+ rotatedOrFlipped = true;
+ break;
+
+ case DMetadata::ORIENTATION_VFLIP:
+ image.flip(DImg::VERTICAL);
+ rotatedOrFlipped = true;
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_HFLIP:
+ image.rotate(DImg::ROT90);
+ image.flip(DImg::HORIZONTAL);
+ rotatedOrFlipped = true;
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90:
+ image.rotate(DImg::ROT90);
+ rotatedOrFlipped = true;
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_VFLIP:
+ image.rotate(DImg::ROT90);
+ image.flip(DImg::VERTICAL);
+ rotatedOrFlipped = true;
+ break;
+
+ case DMetadata::ORIENTATION_ROT_270:
+ image.rotate(DImg::ROT270);
+ rotatedOrFlipped = true;
+ break;
+ }
+ }
+
+ image.setAttribute("exifRotated", true);
+ return rotatedOrFlipped;
+
+ /*
+ if (orientation == DMetadata::ORIENTATION_NORMAL ||
+ orientation == DMetadata::ORIENTATION_UNSPECIFIED)
+ return;
+
+ TQWMatrix matrix;
+
+ switch (orientation)
+ {
+ case DMetadata::ORIENTATION_NORMAL:
+ case DMetadata::ORIENTATION_UNSPECIFIED:
+ break;
+
+ case DMetadata::ORIENTATION_HFLIP:
+ matrix.scale(-1, 1);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_180:
+ matrix.rotate(180);
+ break;
+
+ case DMetadata::ORIENTATION_VFLIP:
+ matrix.scale(1, -1);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_HFLIP:
+ matrix.scale(-1, 1);
+ matrix.rotate(90);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90:
+ matrix.rotate(90);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_VFLIP:
+ matrix.scale(1, -1);
+ matrix.rotate(90);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_270:
+ matrix.rotate(270);
+ break;
+ }
+
+ // transform accordingly
+ thumb = thumb.xForm( matrix );
+ */
+}
+
+
+
+
+} // namespace Digikam
+
+#include "loadsavethread.moc"
diff --git a/src/libs/threadimageio/loadsavethread.h b/src/libs/threadimageio/loadsavethread.h
new file mode 100644
index 00000000..c935e47a
--- /dev/null
+++ b/src/libs/threadimageio/loadsavethread.h
@@ -0,0 +1,184 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-17
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2006 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2006 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef LOAD_SAVE_THREAD_H
+#define LOAD_SAVE_THREAD_H
+
+// TQt includes.
+
+#include <tqthread.h>
+#include <tqobject.h>
+#include <tqmutex.h>
+#include <tqptrlist.h>
+#include <tqdatetime.h>
+#include <tqwaitcondition.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+#include "loadingdescription.h"
+
+namespace Digikam
+{
+
+class LoadSaveThreadPriv;
+class LoadSaveTask;
+
+class DIGIKAM_EXPORT LoadSaveThread : public TQObject, public TQThread
+{
+
+ TQ_OBJECT
+
+
+public:
+
+ enum NotificationPolicy
+ {
+ /** Always send notification, unless the last event is still in the event queue */
+ NotificationPolicyDirect,
+ /**
+ * Always wait for a certain amount of time after the last event sent.
+ * In particular, the first event will be sent only after waiting for this time span.
+ * (Or no event will be sent, when the loading has finished before)
+ * This is the default.
+ */
+ NotificationPolicyTimeLimited
+ };
+
+ // used by SharedLoadSaveThread only
+ enum AccessMode
+ {
+ // image will only be used for reading
+ AccessModeRead,
+ // image data will possibly be changed
+ AccessModeReadWrite
+ };
+
+ LoadSaveThread();
+ /**
+ * Destructor:
+ * The thread will execute all pending tasks and wait for this upon destruction
+ */
+ ~LoadSaveThread();
+
+ /** Append a task to load the given file to the task list */
+ void load(LoadingDescription description);
+ /** Append a task to save the image to the task list */
+ void save(DImg &image, const TQString& filePath, const TQString &format);
+
+ void setNotificationPolicy(NotificationPolicy notificationPolicy);
+
+ bool isShuttingDown();
+
+ /**
+ * Utility to make sure that an image is rotated according to exif tag.
+ * Detects if an image has previously already been rotated: You can
+ * call this method more than one time on the same image.
+ * Returns true if the image has actually been rotated or flipped.
+ * Returns false if a rotation was not needed.
+ */
+ static bool exifRotate(DImg &image, const TQString& filePath);
+
+signals:
+
+ /** This signal is emitted when the loading process begins. */
+ void signalImageStartedLoading(const LoadingDescription &loadingDescription);
+ /**
+ * This signal is emitted whenever new progress info is available
+ * and the notification policy allows emitting the signal.
+ * No progress info will be sent for preloaded images (ManagedLoadSaveThread).
+ */
+ void signalLoadingProgress(const LoadingDescription &loadingDescription, float progress);
+ /**
+ * This signal is emitted when the loading process has finished.
+ * If the process failed, img is null.
+ */
+ void signalImageLoaded(const LoadingDescription &loadingDescription, const DImg& img);
+ /**
+ * This signal is emitted if
+ * - you are doing shared loading (SharedLoadSaveThread)
+ * - you started a loading operation with a LoadingDescription for
+ * a reduced version of the image
+ * - another thread started a loading operation for a more complete version
+ * You may want to cancel the current operation and start with the given loadingDescription
+ */
+ void signalMoreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription,
+ const LoadingDescription &newLoadingDescription);
+
+ void signalImageStartedSaving(const TQString& filePath);
+ void signalSavingProgress(const TQString& filePath, float progress);
+ void signalImageSaved(const TQString& filePath, bool success);
+
+public:
+
+ void imageStartedLoading(const LoadingDescription &loadingDescription)
+ { emit signalImageStartedLoading(loadingDescription); };
+
+ void loadingProgress(const LoadingDescription &loadingDescription, float progress)
+ { emit signalLoadingProgress(loadingDescription, progress); };
+
+ void imageLoaded(const LoadingDescription &loadingDescription, const DImg& img)
+ { emit signalImageLoaded(loadingDescription, img); };
+
+ void moreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription,
+ const LoadingDescription &newLoadingDescription)
+ { emit signalMoreCompleteLoadingAvailable(oldLoadingDescription, newLoadingDescription); }
+
+ void imageStartedSaving(const TQString& filePath)
+ { emit signalImageStartedSaving(filePath); };
+
+ void savingProgress(const TQString& filePath, float progress)
+ { emit signalSavingProgress(filePath, progress); };
+
+ void imageSaved(const TQString& filePath, bool success)
+ { emit signalImageSaved(filePath, success); };
+
+ virtual bool querySendNotifyEvent();
+ virtual void taskHasFinished();
+
+protected:
+
+ virtual void run();
+ virtual void customEvent(TQCustomEvent *event);
+
+ TQMutex m_mutex;
+
+ TQWaitCondition m_condVar;
+
+ TQPtrList<LoadSaveTask> m_todo;
+
+ LoadSaveTask *m_currentTask;
+
+ NotificationPolicy m_notificationPolicy;
+
+private:
+
+ LoadSaveThreadPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif // LOAD_SAVE_THREAD_H
diff --git a/src/libs/threadimageio/managedloadsavethread.cpp b/src/libs/threadimageio/managedloadsavethread.cpp
new file mode 100644
index 00000000..51d9f1b7
--- /dev/null
+++ b/src/libs/threadimageio/managedloadsavethread.cpp
@@ -0,0 +1,362 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "ddebug.h"
+#include "managedloadsavethread.h"
+#include "loadsavetask.h"
+#include "previewtask.h"
+
+namespace Digikam
+{
+
+ManagedLoadSaveThread::ManagedLoadSaveThread()
+{
+ m_terminationPolicy = TerminationPolicyTerminateLoading;
+}
+
+ManagedLoadSaveThread::~ManagedLoadSaveThread()
+{
+ LoadingTask *loadingTask;
+ switch (m_terminationPolicy)
+ {
+ case TerminationPolicyTerminateLoading:
+ {
+ TQMutexLocker lock(&m_mutex);
+ if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)) )
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ removeLoadingTasks(LoadingDescription(TQString()), LoadingTaskFilterAll);
+ break;
+ }
+ case TerminationPolicyTerminatePreloading:
+ {
+ TQMutexLocker lock(&m_mutex);
+ if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)) )
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ removeLoadingTasks(LoadingDescription(TQString()), LoadingTaskFilterPreloading);
+ break;
+ }
+ case TerminationPolicyWait:
+ break;
+ }
+}
+
+LoadingTask *ManagedLoadSaveThread::checkLoadingTask(LoadSaveTask *task, LoadingTaskFilter filter)
+{
+ if (task && task->type() == LoadSaveTask::TaskTypeLoading)
+ {
+ LoadingTask *loadingTask = (LoadingTask *)task;
+ if (filter == LoadingTaskFilterAll)
+ return loadingTask;
+ else if (filter == LoadingTaskFilterPreloading)
+ if (loadingTask->status() == LoadingTask::LoadingTaskStatusPreloading)
+ return loadingTask;
+ }
+ return 0;
+}
+
+LoadingTask *ManagedLoadSaveThread::findExistingTask(const LoadingDescription &loadingDescription)
+{
+ LoadingTask *loadingTask;
+ if (m_currentTask)
+ {
+ if (m_currentTask->type() == LoadSaveTask::TaskTypeLoading)
+ {
+ loadingTask = (LoadingTask *)m_currentTask;
+ LoadingDescription taskDescription = loadingTask->loadingDescription();
+ if (taskDescription == loadingDescription)
+ return loadingTask;
+ }
+ }
+ for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next())
+ {
+ if (task->type() == LoadSaveTask::TaskTypeLoading)
+ {
+ loadingTask = (LoadingTask *)task;
+ if (loadingTask->loadingDescription() == loadingDescription)
+ return loadingTask;
+ }
+ }
+ return 0;
+}
+
+void ManagedLoadSaveThread::setTerminationPolicy(TerminationPolicy terminationPolicy)
+{
+ m_terminationPolicy = terminationPolicy;
+}
+
+void ManagedLoadSaveThread::load(LoadingDescription description, LoadingPolicy policy)
+{
+ load(description, LoadingModeNormal, policy);
+}
+
+void ManagedLoadSaveThread::load(LoadingDescription description, LoadingMode loadingMode, LoadingPolicy policy, AccessMode accessMode)
+{
+ TQMutexLocker lock(&m_mutex);
+ LoadingTask *loadingTask = 0;
+ LoadingTask *existingTask = findExistingTask(description);
+
+ //DDebug() << "ManagedLoadSaveThread::load " << description.filePath << ", policy " << policy << endl;
+ switch(policy)
+ {
+ case LoadingPolicyFirstRemovePrevious:
+ // reuse task if it exists
+ if (existingTask)
+ {
+ existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
+ }
+ // stop current task
+ if (m_currentTask && m_currentTask != existingTask)
+ {
+ if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)) )
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ }
+ //DDebug() << "LoadingPolicyFirstRemovePrevious, Existing task " << existingTask <<
+ //", m_currentTask " << m_currentTask << ", loadingTask " << loadingTask << endl;
+ // remove all loading tasks
+ for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next())
+ {
+ if (task != existingTask && checkLoadingTask(task, LoadingTaskFilterAll))
+ {
+ //DDebug() << "Removing task " << task << " from list" << endl;
+ m_todo.remove();
+ m_todo.prev();
+ }
+ }
+ // append new, exclusive loading task
+ if (existingTask)
+ break;
+ m_todo.append(createLoadingTask(description, false, loadingMode, accessMode));
+ break;
+ case LoadingPolicyPrepend:
+ if (existingTask)
+ {
+ existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
+ }
+ // stop and postpone current task if it is a preloading task
+ if (m_currentTask)
+ {
+ if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)) )
+ {
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ load(loadingTask->filePath(), LoadingPolicyPreload);
+ }
+ }
+ //DDebug() << "LoadingPolicyPrepend, Existing task " << existingTask << ", m_currentTask " << m_currentTask << endl;
+ // prepend new loading task
+ if (existingTask)
+ break;
+ m_todo.prepend(createLoadingTask(description, false, loadingMode, accessMode));
+ break;
+ case LoadingPolicyAppend:
+ if (existingTask)
+ {
+ existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
+ }
+ // stop and postpone current task if it is a preloading task
+ if (m_currentTask)
+ {
+ if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)) )
+ {
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ load(loadingTask->filePath(), LoadingPolicyPreload);
+ }
+ }
+ if (existingTask)
+ break;
+ //DDebug() << "LoadingPolicyAppend, Existing task " << existingTask << ", m_currentTask " << m_currentTask << endl;
+ // append new loading task, put it in front of preloading tasks
+ for (uint i = 0; i<m_todo.count(); i++)
+ {
+ LoadSaveTask *task = m_todo.at(i);
+ if ( (loadingTask = checkLoadingTask(task, LoadingTaskFilterPreloading)) )
+ {
+ m_todo.insert(i, createLoadingTask(description, false, loadingMode, accessMode));
+ break;
+ }
+ }
+ break;
+ case LoadingPolicyPreload:
+ // append to the very end of the list
+ //DDebug() << "LoadingPolicyPreload, Existing task " << existingTask << endl;
+ if (existingTask)
+ break;
+ m_todo.append(createLoadingTask(description, true, loadingMode, accessMode));
+ break;
+ }
+ m_condVar.wakeAll();
+}
+
+void ManagedLoadSaveThread::loadPreview(LoadingDescription description)
+{
+ // This is similar to the LoadingPolicyFirstRemovePrevious policy with normal loading tasks.
+ // Preview threads typically only support preview tasks,
+ // so no need to differentiate with normal loading tasks.
+
+ TQMutexLocker lock(&m_mutex);
+ LoadingTask *loadingTask = 0;
+ LoadingTask *existingTask = findExistingTask(description);
+
+ // reuse task if it exists
+ if (existingTask)
+ {
+ existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
+ }
+ // stop current task
+ if (m_currentTask && m_currentTask != existingTask)
+ {
+ if ( (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)) )
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ }
+ // remove all loading tasks
+ for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next())
+ {
+ if (task != existingTask && checkLoadingTask(task, LoadingTaskFilterAll))
+ {
+ m_todo.remove();
+ m_todo.prev();
+ }
+ }
+ //DDebug()<<"loadPreview for " << description.filePath << " existingTask " << existingTask << " currentTask " << m_currentTask << endl;
+ // append new loading task
+ if (existingTask)
+ return;
+ m_todo.append(new PreviewLoadingTask(this, description));
+ m_condVar.wakeAll();
+}
+
+
+LoadingTask *ManagedLoadSaveThread::createLoadingTask(const LoadingDescription &description,
+ bool preloading, LoadingMode loadingMode, AccessMode accessMode)
+{
+ if (loadingMode == LoadingModeShared)
+ {
+ if (preloading)
+ return new SharedLoadingTask(this, description, accessMode, LoadingTask::LoadingTaskStatusPreloading);
+ else
+ return new SharedLoadingTask(this, description, accessMode);
+ }
+ else
+ {
+ if (preloading)
+ return new LoadingTask(this, description, LoadingTask::LoadingTaskStatusPreloading);
+ else
+ return new LoadingTask(this, description);
+ }
+}
+
+void ManagedLoadSaveThread::stopLoading(const TQString& filePath, LoadingTaskFilter filter)
+{
+ TQMutexLocker lock(&m_mutex);
+ removeLoadingTasks(LoadingDescription(filePath), filter);
+}
+
+void ManagedLoadSaveThread::stopLoading(const LoadingDescription& desc, LoadingTaskFilter filter)
+{
+ TQMutexLocker lock(&m_mutex);
+ removeLoadingTasks(desc, filter);
+}
+
+void ManagedLoadSaveThread::stopSaving(const TQString& filePath)
+{
+ TQMutexLocker lock(&m_mutex);
+
+ // stop current task if it is matching the criteria
+ if (m_currentTask && m_currentTask->type() == LoadSaveTask::TaskTypeSaving)
+ {
+ SavingTask *savingTask = (SavingTask *)m_currentTask;
+ if (filePath.isNull() || savingTask->filePath() == filePath)
+ {
+ savingTask->setStatus(SavingTask::SavingTaskStatusStopping);
+ }
+ }
+
+ // remove relevant tasks from list
+ for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next())
+ {
+ if (task->type() == LoadSaveTask::TaskTypeSaving)
+ {
+ SavingTask *savingTask = (SavingTask *)m_currentTask;
+ if (filePath.isNull() || savingTask->filePath() == filePath)
+ {
+ m_todo.remove();
+ m_todo.prev();
+ }
+ }
+ }
+}
+
+
+void ManagedLoadSaveThread::removeLoadingTasks(const LoadingDescription &description, LoadingTaskFilter filter)
+{
+ LoadingTask *loadingTask;
+
+ // stop current task if it is matching the criteria
+ if ( (loadingTask = checkLoadingTask(m_currentTask, filter)) )
+ {
+ if (description.filePath.isNull() || loadingTask->loadingDescription() == description)
+ {
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ }
+ }
+
+ // remove relevant tasks from list
+ for (LoadSaveTask *task = m_todo.first(); task; task = m_todo.next())
+ {
+ if ( (loadingTask = checkLoadingTask(task, filter)) )
+ {
+ if (description.filePath.isNull() || loadingTask->loadingDescription() == description)
+ {
+ m_todo.remove();
+ m_todo.prev();
+ }
+ }
+ }
+}
+
+void ManagedLoadSaveThread::save(DImg &image, const TQString& filePath, const TQString &format)
+{
+ TQMutexLocker lock(&m_mutex);
+ LoadingTask *loadingTask;
+
+ // stop and postpone current task if it is a preloading task
+ if (m_currentTask && (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)))
+ {
+ loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
+ load(loadingTask->filePath(), LoadingPolicyPreload);
+ }
+ // append new loading task, put it in front of preloading tasks
+ uint i;
+ for (i = 0; i<m_todo.count(); i++)
+ {
+ LoadSaveTask *task = m_todo.at(i);
+ if ( (loadingTask = checkLoadingTask(task, LoadingTaskFilterPreloading)) )
+ break;
+ }
+ m_todo.insert(i, new SavingTask(this, image, filePath, format));
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/threadimageio/managedloadsavethread.h b/src/libs/threadimageio/managedloadsavethread.h
new file mode 100644
index 00000000..ec6a563c
--- /dev/null
+++ b/src/libs/threadimageio/managedloadsavethread.h
@@ -0,0 +1,134 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-16
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MANAGED_LOAD_SAVE_THREAD_H
+#define MANAGED_LOAD_SAVE_THREAD_H
+
+#include "loadsavethread.h"
+
+namespace Digikam
+{
+
+class LoadingTask;
+
+class DIGIKAM_EXPORT ManagedLoadSaveThread : public LoadSaveThread
+{
+
+public:
+
+ // Termination is controlled by setting the TerminationPolicy
+ // Default is TerminationPolicyTerminateLoading
+ ManagedLoadSaveThread();
+ ~ManagedLoadSaveThread();
+
+ enum LoadingPolicy
+ {
+ // Load image immediately, remove and stop all previous loading tasks.
+ LoadingPolicyFirstRemovePrevious,
+ // Prepend loading in front of all other tasks, but wait for the current task to finish.
+ // No other tasks will be removed, preloading tasks will be stopped and postponed.
+ LoadingPolicyPrepend,
+ // Append loading task to the end of the list, but in front of all preloading tasks.
+ // No other tasks will be removed, preloading tasks will be stopped and postponed.
+ // This is similar to the simple load() operation from LoadSaveThread, except for the
+ // special care taken for preloading.
+ LoadingPolicyAppend,
+ // Preload image, i.e. load it with low priority when no other tasks are scheduled.
+ // All other tasks will take precedence, and preloading tasks will be stopped and
+ // postponed when another task is added.
+ // No progress info will be sent for preloaded images
+ LoadingPolicyPreload
+ };
+
+ enum TerminationPolicy
+ {
+ // Wait for saving tasks, stop and remove loading tasks
+ // This is the default.
+ TerminationPolicyTerminateLoading,
+ // Wait for loading and saving tasks, stop and remove preloading tasks
+ TerminationPolicyTerminatePreloading,
+ // Wait for all pending tasks
+ TerminationPolicyWait
+ };
+
+ enum LoadingTaskFilter
+ {
+ // filter all loading tasks
+ LoadingTaskFilterAll,
+ // filter only tasks with preloading policy
+ LoadingTaskFilterPreloading
+ };
+
+ // used by SharedLoadSaveThread only
+ enum LoadingMode
+ {
+ // no sharing of loading process, no caching of image
+ LoadingModeNormal,
+ // loading process is shared, image is cached
+ LoadingModeShared
+ };
+
+ // Append a task to load the given file to the task list.
+ // If there is already a task for the given file, it will possibly be rescheduled,
+ // but no second task will be added.
+ // Only loading tasks will - if required by the policy - be stopped or removed,
+ // saving tasks will not be touched.
+ void load(LoadingDescription description, LoadingPolicy policy = LoadingPolicyAppend);
+
+ // Stop and remove tasks filtered by filePath and policy.
+ // If filePath isNull, applies to all file paths.
+ void stopLoading(const TQString& filePath = TQString(), LoadingTaskFilter filter = LoadingTaskFilterAll);
+
+ // Same than previous method, but Stop and remove tasks filtered by LoadingDescription.
+ void stopLoading(const LoadingDescription& desc, LoadingTaskFilter filter = LoadingTaskFilterAll);
+
+ // Stop and remove saving tasks filtered by filePath.
+ // If filePath isNull, applies to all file paths.
+ void stopSaving(const TQString& filePath = TQString());
+
+ // Append a task to save the image to the task list
+ void save(DImg &image, const TQString& filePath, const TQString &format);
+
+ void setTerminationPolicy(TerminationPolicy terminationPolicy);
+
+protected:
+
+ void load(LoadingDescription description, LoadingMode loadingMode,
+ LoadingPolicy policy = LoadingPolicyAppend, AccessMode mode = AccessModeReadWrite);
+ void loadPreview(LoadingDescription description);
+
+private:
+
+ LoadingTask *checkLoadingTask(class LoadSaveTask *task, LoadingTaskFilter filter);
+ LoadingTask *findExistingTask(const LoadingDescription &description);
+ LoadingTask *createLoadingTask(const LoadingDescription &description, bool preloading, LoadingMode loadingMode, AccessMode accessMode);
+ void removeLoadingTasks(const LoadingDescription &description, LoadingTaskFilter filter);
+
+ TerminationPolicy m_terminationPolicy;
+};
+
+} // namespace Digikam
+
+
+#endif // MANAGED_LOAD_SAVE_THREAD_H
diff --git a/src/libs/threadimageio/previewloadthread.cpp b/src/libs/threadimageio/previewloadthread.cpp
new file mode 100644
index 00000000..d243ab32
--- /dev/null
+++ b/src/libs/threadimageio/previewloadthread.cpp
@@ -0,0 +1,52 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "previewtask.h"
+#include "previewloadthread.h"
+#include "previewloadthread.moc"
+
+namespace Digikam
+{
+
+PreviewLoadThread::PreviewLoadThread()
+{
+}
+
+void PreviewLoadThread::load(LoadingDescription description)
+{
+ description.rawDecodingSettings.sixteenBitsImage = false;
+ ManagedLoadSaveThread::loadPreview(description);
+}
+
+void PreviewLoadThread::loadHighQuality(LoadingDescription description)
+{
+ description.rawDecodingSettings.optimizeTimeLoading();
+ description.rawDecodingSettings.sixteenBitsImage = false;
+ ManagedLoadSaveThread::load(description, LoadingModeShared, LoadingPolicyFirstRemovePrevious);
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/threadimageio/previewloadthread.h b/src/libs/threadimageio/previewloadthread.h
new file mode 100644
index 00000000..59cc6d72
--- /dev/null
+++ b/src/libs/threadimageio/previewloadthread.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-16
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef PREVIEW_LOAD_THREAD_H
+#define PREVIEW_LOAD_THREAD_H
+
+#include "managedloadsavethread.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT PreviewLoadThread : public ManagedLoadSaveThread
+{
+ TQ_OBJECT
+
+
+public:
+
+ PreviewLoadThread();
+
+ /**
+ * Load a preview that is optimized for fast loading.
+ */
+ void load(LoadingDescription description);
+ /**
+ * Load a preview with higher resolution, trading more quality
+ * for less speed.
+ * In the LoadingDescription container, provide "0" as maximum size.
+ */
+ void loadHighQuality(LoadingDescription description);
+
+};
+
+} // namespace Digikam
+
+
+#endif // SHARED_LOAD_SAVE_THREAD_H
diff --git a/src/libs/threadimageio/previewtask.cpp b/src/libs/threadimageio/previewtask.cpp
new file mode 100644
index 00000000..5f277481
--- /dev/null
+++ b/src/libs/threadimageio/previewtask.cpp
@@ -0,0 +1,258 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-12-26
+ * Description : Multithreaded loader for previews
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqapplication.h>
+#include <tqimage.h>
+#include <tqvariant.h>
+#include <tqwmatrix.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/kdcraw.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "jpegutils.h"
+#include "previewloadthread.h"
+#include "previewtask.h"
+
+namespace Digikam
+{
+
+void PreviewLoadingTask::execute()
+{
+ if (m_loadingTaskStatus == LoadingTaskStatusStopping)
+ return;
+
+ LoadingCache *cache = LoadingCache::cache();
+ {
+ LoadingCache::CacheLock lock(cache);
+
+ // find possible cached images
+ DImg *cachedImg = 0;
+ TQStringList lookupKeys = m_loadingDescription.lookupCacheKeys();
+ // lookupCacheKeys returns "best first". Prepend the cache key to make the list "fastest first":
+ // Scaling a full version takes longer!
+ lookupKeys.push_front(m_loadingDescription.cacheKey());
+ for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
+ if ( (cachedImg = cache->retrieveImage(*it)) )
+ break;
+ }
+
+ if (cachedImg)
+ {
+ // image is found in image cache, loading is successful
+
+ DImg img(*cachedImg);
+
+ // rotate if needed - images are unrotated in the cache,
+ // except for RAW images, which are already rotated by dcraw.
+ if (m_loadingDescription.previewParameters.exifRotate)
+ {
+ img = img.copy();
+ LoadSaveThread::exifRotate(img, m_loadingDescription.filePath);
+ }
+
+ TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img));
+ return;
+ }
+ else
+ {
+ // find possible running loading process
+ m_usedProcess = 0;
+ for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
+ if ( (m_usedProcess = cache->retrieveLoadingProcess(*it)) )
+ {
+ break;
+ }
+ }
+ // do not wait on other loading processes?
+ //m_usedProcess = cache->retrieveLoadingProcess(m_loadingDescription.cacheKey());
+
+ if (m_usedProcess)
+ {
+ // Other process is right now loading this image.
+ // Add this task to the list of listeners and
+ // attach this thread to the other thread, wait until loading
+ // has finished.
+ m_usedProcess->addListener(this);
+ // break loop when either the loading has completed, or this task is being stopped
+ while ( !m_usedProcess->completed() && m_loadingTaskStatus != LoadingTaskStatusStopping )
+ lock.timedWait();
+ // remove listener from process
+ m_usedProcess->removeListener(this);
+ // wake up the process which is waiting until all listeners have removed themselves
+ lock.wakeAll();
+ // set to 0, as checked in setStatus
+ m_usedProcess = 0;
+ return;
+ }
+ else
+ {
+ // Neither in cache, nor currently loading in different thread.
+ // Load it here and now, add this LoadingProcess to cache list.
+ cache->addLoadingProcess(this);
+ // Add this to the list of listeners
+ addListener(this);
+ // for use in setStatus
+ m_usedProcess = this;
+ // Notify other processes that we are now loading this image.
+ // They might be interested - see notifyNewLoadingProcess below
+ cache->notifyNewLoadingProcess(this, m_loadingDescription);
+ }
+ }
+ }
+
+ // load image
+ int size = m_loadingDescription.previewParameters.size;
+
+ DImg img;
+ TQImage qimage;
+ bool fromEmbeddedPreview = false;
+
+ // -- Get the image preview --------------------------------
+
+ // First the TQImage-dependent loading methods
+ // Trying to load with dcraw: RAW files.
+ if (KDcrawIface::KDcraw::loadEmbeddedPreview(qimage, m_loadingDescription.filePath))
+ fromEmbeddedPreview = true;
+
+ if (qimage.isNull())
+ {
+ //TODO: Use DImg based loader instead?
+ KDcrawIface::KDcraw::loadHalfPreview(qimage, m_loadingDescription.filePath);
+ }
+
+ // Try to extract Exif/Iptc preview.
+ if (qimage.isNull())
+ {
+ loadImagePreview(qimage, m_loadingDescription.filePath);
+ }
+
+ if (!qimage.isNull())
+ {
+ // convert from TQImage
+ img = DImg(qimage);
+ // mark as embedded preview (for exif rotation)
+ if (fromEmbeddedPreview)
+ img.setAttribute("fromRawEmbeddedPreview", true);
+ // free memory
+ qimage = TQImage();
+ }
+
+ // DImg-dependent loading methods
+ if (img.isNull())
+ {
+ // Set a hint to try to load a JPEG with the fast scale-before-decoding method
+ img.setAttribute("jpegScaledLoadingSize", size);
+ img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
+ }
+
+ if (img.isNull())
+ {
+ DWarning() << "Cannot extract preview for " << m_loadingDescription.filePath << endl;
+ }
+
+ img.convertToEightBit();
+
+ // Reduce size of image:
+ // - only scale down if size is considerably larger
+ // - only scale down, do not scale up
+ TQSize scaledSize = img.size();
+ if (needToScale(scaledSize, size))
+ {
+ scaledSize.scale(size, size, TQSize::ScaleMin);
+ img = img.smoothScale(scaledSize.width(), scaledSize.height());
+ }
+
+ // Scale if hinted, Store previews rotated in the cache (?)
+ if (m_loadingDescription.previewParameters.exifRotate)
+ LoadSaveThread::exifRotate(img, m_loadingDescription.filePath);
+
+ {
+ LoadingCache::CacheLock lock(cache);
+ // put (valid) image into cache of loaded images
+ if (!img.isNull())
+ cache->putImage(m_loadingDescription.cacheKey(), new DImg(img), m_loadingDescription.filePath);
+ // remove this from the list of loading processes in cache
+ cache->removeLoadingProcess(this);
+ }
+
+ // following the golden rule to avoid deadlocks, do this when CacheLock is not held
+ m_thread->taskHasFinished();
+
+ {
+ LoadingCache::CacheLock lock(cache);
+ // indicate that loading has finished so that listeners can stop waiting
+ m_completed = true;
+
+ // dispatch image to all listeners, including this
+ for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next())
+ {
+ TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, img));
+ }
+
+ // remove myself from list of listeners
+ removeListener(this);
+ // wake all listeners waiting on cache condVar, so that they remove themselves
+ lock.wakeAll();
+ // wait until all listeners have removed themselves
+ while (m_listeners.count() != 0)
+ lock.timedWait();
+ // set to 0, as checked in setStatus
+ m_usedProcess = 0;
+ }
+}
+
+bool PreviewLoadingTask::needToScale(const TQSize &imageSize, int previewSize)
+{
+ int maxSize = imageSize.width() > imageSize.height() ? imageSize.width() : imageSize.height();
+ int acceptableUpperSize = lround(1.25 * (double)previewSize);
+ return maxSize >= acceptableUpperSize;
+}
+
+// -- Exif/IPTC preview extraction using Exiv2 --------------------------------------------------------
+
+bool PreviewLoadingTask::loadImagePreview(TQImage& image, const TQString& path)
+{
+ DMetadata metadata(path);
+ if (metadata.getImagePreview(image))
+ {
+ DDebug() << "Use Exif/Iptc preview extraction. Size of image: "
+ << image.width() << "x" << image.height() << endl;
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace Digikam
diff --git a/src/libs/threadimageio/previewtask.h b/src/libs/threadimageio/previewtask.h
new file mode 100644
index 00000000..f5715499
--- /dev/null
+++ b/src/libs/threadimageio/previewtask.h
@@ -0,0 +1,57 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-12-26
+ * Description : Multithreaded loader for previews
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PREVIEW_TASK_H
+#define PREVIEW_TASK_H
+
+// TQt includes.
+
+#include <tqimage.h>
+
+// Local includes.
+
+#include "loadsavetask.h"
+
+namespace Digikam
+{
+
+class PreviewLoadingTask : public SharedLoadingTask
+{
+public:
+
+ PreviewLoadingTask(LoadSaveThread* thread, LoadingDescription description)
+ : SharedLoadingTask(thread, description, LoadSaveThread::AccessModeRead, LoadingTaskStatusLoading)
+ {}
+
+ virtual void execute();
+
+private:
+
+ bool needToScale(const TQSize &imageSize, int previewSize);
+ bool loadImagePreview(TQImage& image, const TQString& path);
+};
+
+} // namespace Digikam
+
+#endif // PREVIEW_TASK_H
diff --git a/src/libs/threadimageio/sharedloadsavethread.cpp b/src/libs/threadimageio/sharedloadsavethread.cpp
new file mode 100644
index 00000000..6a9cccbf
--- /dev/null
+++ b/src/libs/threadimageio/sharedloadsavethread.cpp
@@ -0,0 +1,65 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2005-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "ddebug.h"
+#include "sharedloadsavethread.h"
+#include "loadingcache.h"
+#include "loadsavetask.h"
+
+namespace Digikam
+{
+
+void SharedLoadSaveThread::load(LoadingDescription description, AccessMode mode, LoadingPolicy policy)
+{
+ ManagedLoadSaveThread::load(description, LoadingModeShared, policy, mode);
+}
+
+DImg SharedLoadSaveThread::cacheLookup(const TQString& filePath, AccessMode /*accessMode*/)
+{
+ LoadingCache *cache = LoadingCache::cache();
+ LoadingCache::CacheLock lock(cache);
+ DImg *cachedImg = cache->retrieveImage(filePath);
+ // TQt4: uncomment this code.
+ // See comments in SharedLoadingTask::execute for explanation.
+ /*
+ if (cachedImg)
+ {
+ if (accessMode == AccessModeReadWrite)
+ return cachedImg->copy();
+ else
+ return *cachedImg;
+ }
+ else
+ return DImg();
+ */
+ if (cachedImg)
+ return cachedImg->copy();
+ else
+ return DImg();
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/threadimageio/sharedloadsavethread.h b/src/libs/threadimageio/sharedloadsavethread.h
new file mode 100644
index 00000000..aa03a708
--- /dev/null
+++ b/src/libs/threadimageio/sharedloadsavethread.h
@@ -0,0 +1,43 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-16
+ * Description : image file IO threaded interface.
+ *
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef SHARED_LOAD_SAVE_THREAD_H
+#define SHARED_LOAD_SAVE_THREAD_H
+
+#include "managedloadsavethread.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT SharedLoadSaveThread : public ManagedLoadSaveThread
+{
+public:
+
+ void load(LoadingDescription description, AccessMode mode, LoadingPolicy policy = LoadingPolicyAppend);
+ DImg cacheLookup(const TQString& filePath, AccessMode mode);
+};
+
+} // namespace Digikam
+
+
+#endif // SHARED_LOAD_SAVE_THREAD_H
diff --git a/src/libs/thumbbar/Makefile.am b/src/libs/thumbbar/Makefile.am
new file mode 100644
index 00000000..7e885514
--- /dev/null
+++ b/src/libs/thumbbar/Makefile.am
@@ -0,0 +1,18 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libthumbbar.la
+
+libthumbbar_la_SOURCES = thumbbar.cpp thumbnailjob.cpp
+
+libthumbbar_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(LIBKEXIV2_CFLAGS) \
+ $(all_includes)
+
+digikaminclude_HEADERS = thumbbar.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/thumbbar/thumbbar.cpp b/src/libs/thumbbar/thumbbar.cpp
new file mode 100644
index 00000000..f5b32767
--- /dev/null
+++ b/src/libs/thumbbar/thumbbar.cpp
@@ -0,0 +1,1138 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-22
+ * Description : a bar widget to display image thumbnails
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqdir.h>
+#include <tqpixmap.h>
+#include <tqimage.h>
+#include <tqtimer.h>
+#include <tqpainter.h>
+#include <tqdict.h>
+#include <tqpoint.h>
+#include <tqstylesheet.h>
+#include <tqdatetime.h>
+#include <tqguardedptr.h>
+
+// KDE includes.
+
+#include <kmdcodec.h>
+#include <tdefileitem.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kmimetype.h>
+#include <tdefileitem.h>
+#include <tdeglobal.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "dmetadata.h"
+#include "thumbnailjob.h"
+#include "thumbnailsize.h"
+#include "thumbbar.h"
+#include "thumbbar.moc"
+
+namespace Digikam
+{
+
+class ThumbBarViewPriv
+{
+public:
+
+ ThumbBarViewPriv() :
+ margin(5)
+ {
+ dragging = false;
+ exifRotate = false;
+ clearing = false;
+ toolTip = 0;
+ firstItem = 0;
+ lastItem = 0;
+ currItem = 0;
+ count = 0;
+ thumbJob = 0;
+ tileSize = ThumbnailSize::Small;
+
+ itemDict.setAutoDelete(false);
+ }
+
+ bool clearing;
+ bool exifRotate;
+ bool dragging;
+
+ const int margin;
+ int count;
+ int tileSize;
+ int orientation;
+
+ TQTimer *timer;
+
+ TQPoint dragStartPos;
+
+ ThumbBarItem *firstItem;
+ ThumbBarItem *lastItem;
+ ThumbBarItem *currItem;
+
+ TQDict<ThumbBarItem> itemDict;
+ TQGuardedPtr<ThumbnailJob> thumbJob;
+
+ ThumbBarToolTipSettings toolTipSettings;
+
+ ThumbBarToolTip *toolTip;
+};
+
+// -------------------------------------------------------------------------
+
+class ThumbBarItemPriv
+{
+public:
+
+ ThumbBarItemPriv()
+ {
+ pos = 0;
+ pixmap = 0;
+ next = 0;
+ prev = 0;
+ view = 0;
+ }
+
+ int pos;
+
+ TQPixmap *pixmap;
+
+ KURL url;
+
+ ThumbBarItem *next;
+ ThumbBarItem *prev;
+
+ ThumbBarView *view;
+};
+
+// -------------------------------------------------------------------------
+
+ThumbBarView::ThumbBarView(TQWidget* parent, int orientation, bool exifRotate,
+ ThumbBarToolTipSettings settings)
+ : TQScrollView(parent)
+{
+ d = new ThumbBarViewPriv;
+ d->orientation = orientation;
+ d->exifRotate = exifRotate;
+ d->toolTipSettings = settings;
+ d->toolTip = new ThumbBarToolTip(this);
+ d->timer = new TQTimer(this);
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotUpdate()));
+
+ viewport()->setBackgroundMode(TQt::NoBackground);
+ viewport()->setMouseTracking(true);
+ viewport()->setAcceptDrops(true);
+
+ setFrameStyle(TQFrame::NoFrame);
+ setAcceptDrops(true);
+
+ if (d->orientation ==TQt::Vertical)
+ {
+ setHScrollBarMode(TQScrollView::AlwaysOff);
+ }
+ else
+ {
+ setVScrollBarMode(TQScrollView::AlwaysOff);
+ }
+}
+
+ThumbBarView::~ThumbBarView()
+{
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ clear(false);
+
+ delete d->timer;
+ delete d->toolTip;
+ delete d;
+}
+
+void ThumbBarView::resizeEvent(TQResizeEvent* e)
+{
+ if (!e) return;
+
+ TQScrollView::resizeEvent(e);
+
+ if (d->orientation ==TQt::Vertical)
+ {
+ d->tileSize = width() - 2*d->margin - verticalScrollBar()->sizeHint().width();
+ verticalScrollBar()->setLineStep(d->tileSize);
+ verticalScrollBar()->setPageStep(2*d->tileSize);
+ }
+ else
+ {
+ d->tileSize = height() - 2*d->margin - horizontalScrollBar()->sizeHint().height();
+ horizontalScrollBar()->setLineStep(d->tileSize);
+ horizontalScrollBar()->setPageStep(2*d->tileSize);
+ }
+
+ rearrangeItems();
+ ensureItemVisible(currentItem());
+}
+
+void ThumbBarView::setExifRotate(bool exifRotate)
+{
+ if (d->exifRotate == exifRotate)
+ return;
+
+ d->exifRotate = exifRotate;
+ TQString thumbCacheDir = TQDir::homeDirPath() + "/.thumbnails/";
+
+ for (ThumbBarItem *item = d->firstItem; item; item = item->d->next)
+ {
+ // Remove all current album item thumbs from disk cache.
+
+ TQString uri = "file://" + TQDir::cleanDirPath(item->url().path(-1));
+ KMD5 md5(TQFile::encodeName(uri).data());
+ uri = md5.hexDigest();
+
+ TQString smallThumbPath = thumbCacheDir + "normal/" + uri + ".png";
+ TQString bigThumbPath = thumbCacheDir + "large/" + uri + ".png";
+
+ ::unlink(TQFile::encodeName(smallThumbPath));
+ ::unlink(TQFile::encodeName(bigThumbPath));
+
+ invalidateThumb(item);
+ }
+
+ triggerUpdate();
+}
+
+bool ThumbBarView::getExifRotate()
+{
+ return d->exifRotate;
+}
+
+int ThumbBarView::getOrientation()
+{
+ return d->orientation;
+}
+
+int ThumbBarView::getTileSize()
+{
+ return d->tileSize;
+}
+
+int ThumbBarView::getMargin()
+{
+ return d->margin;
+}
+
+void ThumbBarView::setToolTipSettings(const ThumbBarToolTipSettings &settings)
+{
+ d->toolTipSettings = settings;
+}
+
+ThumbBarToolTipSettings& ThumbBarView::getToolTipSettings()
+{
+ return d->toolTipSettings;
+}
+
+int ThumbBarView::countItems()
+{
+ return d->count;
+}
+
+KURL::List ThumbBarView::itemsURLs()
+{
+ KURL::List urlList;
+ if (!countItems())
+ return urlList;
+
+ for (ThumbBarItem *item = firstItem(); item; item = item->next())
+ urlList.append(item->url());
+
+ return urlList;
+}
+
+void ThumbBarView::clear(bool updateView)
+{
+ d->clearing = true;
+
+ ThumbBarItem *item = d->firstItem;
+ while (item)
+ {
+ ThumbBarItem *tmp = item->d->next;
+ delete item;
+ item = tmp;
+ }
+
+ d->firstItem = 0;
+ d->lastItem = 0;
+ d->count = 0;
+ d->currItem = 0;
+
+ if (updateView)
+ slotUpdate();
+
+ d->clearing = false;
+
+ emit signalItemSelected(0);
+}
+
+void ThumbBarView::triggerUpdate()
+{
+ d->timer->start(0, true);
+}
+
+ThumbBarItem* ThumbBarView::currentItem() const
+{
+ return d->currItem;
+}
+
+ThumbBarItem* ThumbBarView::firstItem() const
+{
+ return d->firstItem;
+}
+
+ThumbBarItem* ThumbBarView::lastItem() const
+{
+ return d->lastItem;
+}
+
+ThumbBarItem* ThumbBarView::findItem(const TQPoint& pos) const
+{
+ int itemPos;
+
+ if (d->orientation ==TQt::Vertical)
+ itemPos = pos.y();
+ else
+ itemPos = pos.x();
+
+ for (ThumbBarItem *item = d->firstItem; item; item = item->d->next)
+ {
+ if (itemPos >= item->d->pos && itemPos <= (item->d->pos+d->tileSize+2*d->margin))
+ {
+ return item;
+ }
+ }
+
+ return 0;
+}
+
+ThumbBarItem* ThumbBarView::findItemByURL(const KURL& url) const
+{
+ for (ThumbBarItem *item = d->firstItem; item; item = item->d->next)
+ {
+ if (item->url().equals(url))
+ {
+ return item;
+ }
+ }
+
+ return 0;
+}
+
+void ThumbBarView::setSelected(ThumbBarItem* item)
+{
+ if (!item) return;
+
+ ensureItemVisible(item);
+ emit signalURLSelected(item->url());
+ emit signalItemSelected(item);
+
+ if (d->currItem == item) return;
+
+ if (d->currItem)
+ {
+ ThumbBarItem* item = d->currItem;
+ d->currItem = 0;
+ item->repaint();
+ }
+
+ d->currItem = item;
+ if (d->currItem)
+ item->repaint();
+}
+
+void ThumbBarView::ensureItemVisible(ThumbBarItem* item)
+{
+ if (item)
+ {
+ // We want the complete thumb visible and the next one.
+ // find the middle of the image and give a margin of 1,5 image
+ // When changed, watch regression for bug 104031
+ if (d->orientation ==TQt::Vertical)
+ ensureVisible(0, (int)(item->d->pos + d->margin + d->tileSize*.5),
+ 0, (int)(d->tileSize*1.5 + 3*d->margin));
+ else
+ ensureVisible((int)(item->d->pos + d->margin + d->tileSize*.5), 0,
+ (int)(d->tileSize*1.5 + 3*d->margin), 0);
+ }
+}
+
+void ThumbBarView::refreshThumbs(const KURL::List& urls)
+{
+ for (KURL::List::const_iterator it = urls.begin() ; it != urls.end() ; ++it)
+ {
+ ThumbBarItem *item = findItemByURL(*it);
+ if (item)
+ {
+ invalidateThumb(item);
+ }
+ }
+}
+
+void ThumbBarView::invalidateThumb(ThumbBarItem* item)
+{
+ if (!item) return;
+
+ if (item->d->pixmap)
+ {
+ delete item->d->pixmap;
+ item->d->pixmap = 0;
+ }
+
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ d->thumbJob = new ThumbnailJob(item->url(), ThumbnailSize::Huge, true, d->exifRotate);
+
+ connect(d->thumbJob, TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnail(const KURL&, const TQPixmap&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalFailed(const KURL&)),
+ this, TQ_SLOT(slotFailedThumbnail(const KURL&)));
+}
+
+void ThumbBarView::viewportPaintEvent(TQPaintEvent* e)
+{
+ int cy, cx, ts, y1, y2, x1, x2;
+ TQPixmap bgPix, tile;
+ TQRect er(e->rect());
+
+ if (d->orientation ==TQt::Vertical)
+ {
+ cy = viewportToContents(er.topLeft()).y();
+
+ bgPix.resize(contentsRect().width(), er.height());
+
+ ts = d->tileSize + 2*d->margin;
+ tile.resize(visibleWidth(), ts);
+
+ y1 = (cy/ts)*ts;
+ y2 = ((y1 + er.height())/ts +1)*ts;
+ }
+ else
+ {
+ cx = viewportToContents(er.topLeft()).x();
+
+ bgPix.resize(er.width(), contentsRect().height());
+
+ ts = d->tileSize + 2*d->margin;
+ tile.resize(ts, visibleHeight());
+
+ x1 = (cx/ts)*ts;
+ x2 = ((x1 + er.width())/ts +1)*ts;
+ }
+
+ bgPix.fill(colorGroup().background());
+
+ for (ThumbBarItem *item = d->firstItem; item; item = item->d->next)
+ {
+ if (d->orientation ==TQt::Vertical)
+ {
+ if (y1 <= item->d->pos && item->d->pos <= y2)
+ {
+ if (item == d->currItem)
+ tile.fill(colorGroup().highlight());
+ else
+ tile.fill(colorGroup().background());
+
+ TQPainter p(&tile);
+ p.setPen(TQt::white);
+ p.drawRect(0, 0, tile.width(), tile.height());
+ p.end();
+
+ if (item->d->pixmap)
+ {
+ TQPixmap pix;
+ pix.convertFromImage(TQImage(item->d->pixmap->convertToImage()).
+ smoothScale(d->tileSize, d->tileSize, TQImage::ScaleMin));
+ int x = (tile.width() - pix.width())/2;
+ int y = (tile.height() - pix.height())/2;
+ bitBlt(&tile, x, y, &pix);
+ }
+
+ bitBlt(&bgPix, 0, item->d->pos - cy, &tile);
+ }
+ }
+ else
+ {
+ if (x1 <= item->d->pos && item->d->pos <= x2)
+ {
+ if (item == d->currItem)
+ tile.fill(colorGroup().highlight());
+ else
+ tile.fill(colorGroup().background());
+
+ TQPainter p(&tile);
+ p.setPen(TQt::white);
+ p.drawRect(0, 0, tile.width(), tile.height());
+ p.end();
+
+ if (item->d->pixmap)
+ {
+ TQPixmap pix;
+ pix.convertFromImage(TQImage(item->d->pixmap->convertToImage()).
+ smoothScale(d->tileSize, d->tileSize, TQImage::ScaleMin));
+ int x = (tile.width() - pix.width())/2;
+ int y = (tile.height()- pix.height())/2;
+ bitBlt(&tile, x, y, &pix);
+ }
+
+ bitBlt(&bgPix, item->d->pos - cx, 0, &tile);
+ }
+ }
+ }
+
+ if (d->orientation ==TQt::Vertical)
+ bitBlt(viewport(), 0, er.y(), &bgPix);
+ else
+ bitBlt(viewport(), er.x(), 0, &bgPix);
+}
+
+void ThumbBarView::contentsMousePressEvent(TQMouseEvent* e)
+{
+ ThumbBarItem* barItem = findItem(e->pos());
+ d->dragging = true;
+ d->dragStartPos = e->pos();
+
+ if (!barItem || barItem == d->currItem)
+ return;
+
+ if (d->currItem)
+ {
+ ThumbBarItem* item = d->currItem;
+ d->currItem = 0;
+ item->repaint();
+ }
+
+ d->currItem = barItem;
+ barItem->repaint();
+}
+
+void ThumbBarView::contentsMouseMoveEvent(TQMouseEvent *e)
+{
+ if (!e) return;
+
+ if (d->dragging && (e->state() & TQt::LeftButton))
+ {
+ if ( findItem(d->dragStartPos) &&
+ (d->dragStartPos - e->pos()).manhattanLength() > TQApplication::startDragDistance() )
+ {
+ startDrag();
+ }
+ return;
+ }
+}
+
+void ThumbBarView::contentsMouseReleaseEvent(TQMouseEvent *e)
+{
+ d->dragging = false;
+ ThumbBarItem *item = findItem(e->pos());
+ if (item)
+ {
+ emit signalURLSelected(item->url());
+ emit signalItemSelected(item);
+ }
+}
+
+void ThumbBarView::contentsWheelEvent(TQWheelEvent *e)
+{
+ e->accept();
+
+ if (e->delta() < 0)
+ {
+ if (e->state() & TQt::ShiftButton)
+ {
+ if (d->orientation ==TQt::Vertical)
+ scrollBy(0, verticalScrollBar()->pageStep());
+ else
+ scrollBy(horizontalScrollBar()->pageStep(), 0);
+ }
+ else
+ {
+ if (d->orientation ==TQt::Vertical)
+ scrollBy(0, verticalScrollBar()->lineStep());
+ else
+ scrollBy(horizontalScrollBar()->lineStep(), 0);
+ }
+ }
+
+ if (e->delta() > 0)
+ {
+ if (e->state() & TQt::ShiftButton)
+ {
+ if (d->orientation ==TQt::Vertical)
+ scrollBy(0, (-1)*verticalScrollBar()->pageStep());
+ else
+ scrollBy((-1)*horizontalScrollBar()->pageStep(), 0);
+ }
+ else
+ {
+ if (d->orientation ==TQt::Vertical)
+ scrollBy(0, (-1)*verticalScrollBar()->lineStep());
+ else
+ scrollBy((-1)*horizontalScrollBar()->lineStep(), 0);
+ }
+ }
+}
+
+void ThumbBarView::startDrag()
+{
+}
+
+void ThumbBarView::insertItem(ThumbBarItem* item)
+{
+ if (!item) return;
+
+ if (!d->firstItem)
+ {
+ d->firstItem = item;
+ d->lastItem = item;
+ item->d->prev = 0;
+ item->d->next = 0;
+ }
+ else
+ {
+ d->lastItem->d->next = item;
+ item->d->prev = d->lastItem;
+ item->d->next = 0;
+ d->lastItem = item;
+
+ }
+
+ if (!d->currItem)
+ {
+ d->currItem = item;
+ emit signalURLSelected(item->url());
+ emit signalItemSelected(item);
+ }
+
+ d->itemDict.insert(item->url().url(), item);
+
+ d->count++;
+ triggerUpdate();
+ emit signalItemAdded();
+}
+
+void ThumbBarView::removeItem(ThumbBarItem* item)
+{
+ if (!item) return;
+
+ d->count--;
+
+ if (item == d->firstItem)
+ {
+ d->firstItem = d->currItem = d->firstItem->d->next;
+ if (d->firstItem)
+ d->firstItem->d->prev = 0;
+ else
+ d->firstItem = d->lastItem = d->currItem = 0;
+ }
+ else if (item == d->lastItem)
+ {
+ d->lastItem = d->currItem = d->lastItem->d->prev;
+ if ( d->lastItem )
+ d->lastItem->d->next = 0;
+ else
+ d->firstItem = d->lastItem = d->currItem = 0;
+ }
+ else
+ {
+ ThumbBarItem *i = item;
+ if (i)
+ {
+ if (i->d->prev )
+ {
+ i->d->prev->d->next = d->currItem = i->d->next;
+ }
+ if ( i->d->next )
+ {
+ i->d->next->d->prev = d->currItem = i->d->prev;
+ }
+ }
+ }
+
+ d->itemDict.remove(item->url().url());
+
+ if (!d->clearing)
+ {
+ triggerUpdate();
+ }
+
+ if (d->count == 0)
+ emit signalItemSelected(0);
+}
+
+void ThumbBarView::rearrangeItems()
+{
+ KURL::List urlList;
+
+ int pos = 0;
+ ThumbBarItem *item = d->firstItem;
+
+ while (item)
+ {
+ item->d->pos = pos;
+ pos += d->tileSize + 2*d->margin;
+ if (!(item->d->pixmap))
+ urlList.append(item->d->url);
+ item = item->d->next;
+ }
+
+ if (d->orientation ==TQt::Vertical)
+ resizeContents(visibleWidth(), d->count*(d->tileSize+2*d->margin));
+ else
+ resizeContents(d->count*(d->tileSize+2*d->margin), visibleHeight());
+
+ if (!urlList.isEmpty())
+ {
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ d->thumbJob = new ThumbnailJob(urlList, ThumbnailSize::Huge, true, d->exifRotate);
+
+ connect(d->thumbJob, TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ this, TQ_SLOT(slotGotThumbnail(const KURL&, const TQPixmap&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalFailed(const KURL&)),
+ this, TQ_SLOT(slotFailedThumbnail(const KURL&)));
+ }
+}
+
+void ThumbBarView::repaintItem(ThumbBarItem* item)
+{
+ if (item)
+ {
+ if (d->orientation ==TQt::Vertical)
+ repaintContents(0, item->d->pos, visibleWidth(), d->tileSize+2*d->margin);
+ else
+ repaintContents(item->d->pos, 0, d->tileSize+2*d->margin, visibleHeight());
+ }
+}
+
+void ThumbBarView::slotUpdate()
+{
+ rearrangeItems();
+ viewport()->update();
+}
+
+void ThumbBarView::slotGotThumbnail(const KURL& url, const TQPixmap& pix)
+{
+ if (!pix.isNull())
+ {
+ ThumbBarItem* item = d->itemDict.find(url.url());
+ if (!item)
+ return;
+
+ if (item->d->pixmap)
+ {
+ delete item->d->pixmap;
+ item->d->pixmap = 0;
+ }
+
+ item->d->pixmap = new TQPixmap(pix);
+ item->repaint();
+ }
+}
+
+void ThumbBarView::slotFailedThumbnail(const KURL& url)
+{
+ ThumbBarItem* item = d->itemDict.find(url.url());
+ if (!item)
+ return;
+
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ TQPixmap pix = iconLoader->loadIcon("image-x-generic", TDEIcon::NoGroup, ThumbnailSize::Huge);
+
+ if (item->d->pixmap)
+ {
+ delete item->d->pixmap;
+ item->d->pixmap = 0;
+ }
+
+ item->d->pixmap = new TQPixmap(pix);
+ item->repaint();
+}
+
+// -------------------------------------------------------------------------
+
+ThumbBarItem::ThumbBarItem(ThumbBarView* view, const KURL& url)
+{
+ d = new ThumbBarItemPriv;
+ d->url = url;
+ d->view = view;
+ d->view->insertItem(this);
+}
+
+ThumbBarItem::~ThumbBarItem()
+{
+ d->view->removeItem(this);
+
+ if (d->pixmap)
+ delete d->pixmap;
+
+ delete d;
+}
+
+KURL ThumbBarItem::url() const
+{
+ return d->url;
+}
+
+ThumbBarItem* ThumbBarItem::next() const
+{
+ return d->next;
+}
+
+ThumbBarItem* ThumbBarItem::prev() const
+{
+ return d->prev;
+}
+
+TQRect ThumbBarItem::rect() const
+{
+ if (d->view->d->orientation == ThumbBarView::Vertical)
+ {
+ return TQRect(0, d->pos,
+ d->view->visibleWidth(),
+ d->view->d->tileSize + 2*d->view->d->margin);
+ }
+ else
+ {
+ return TQRect(d->pos, 0,
+ d->view->d->tileSize + 2*d->view->d->margin,
+ d->view->visibleHeight());
+ }
+}
+
+int ThumbBarItem::position() const
+{
+ return d->pos;
+}
+
+TQPixmap* ThumbBarItem::pixmap() const
+{
+ return d->pixmap;
+}
+
+void ThumbBarItem::repaint()
+{
+ d->view->repaintItem(this);
+}
+
+// -------------------------------------------------------------------------
+
+ThumbBarToolTip::ThumbBarToolTip(ThumbBarView* parent)
+ : TQToolTip(parent->viewport()), m_maxStringLen(30), m_view(parent)
+{
+ m_headBeg = TQString("<tr bgcolor=\"orange\"><td colspan=\"2\">"
+ "<nobr><font size=\"-1\" color=\"black\"><b>");
+ m_headEnd = TQString("</b></font></nobr></td></tr>");
+
+ m_cellBeg = TQString("<tr><td><nobr><font size=\"-1\" color=\"black\">");
+ m_cellMid = TQString("</font></nobr></td>"
+ "<td><nobr><font size=\"-1\" color=\"black\">");
+ m_cellEnd = TQString("</font></nobr></td></tr>");
+
+ m_cellSpecBeg = TQString("<tr><td><nobr><font size=\"-1\" color=\"black\">");
+ m_cellSpecMid = TQString("</font></nobr></td>"
+ "<td><nobr><font size=\"-1\" color=\"steelblue\"><i>");
+ m_cellSpecEnd = TQString("</i></font></nobr></td></tr>");
+}
+
+void ThumbBarToolTip::maybeTip(const TQPoint& pos)
+{
+ if ( !parentWidget() || !m_view) return;
+
+ ThumbBarItem* item = m_view->findItem( m_view->viewportToContents(pos) );
+ if (!item) return;
+
+ if (!m_view->getToolTipSettings().showToolTips) return;
+
+ TQString tipText = tipContent(item);
+ tipText.append(tipContentExtraData(item));
+ tipText.append("</table>");
+
+ TQRect r(item->rect());
+ r = TQRect( m_view->contentsToViewport(r.topLeft()), r.size() );
+
+ tip(r, tipText);
+}
+
+TQString ThumbBarToolTip::tipContent(ThumbBarItem* item)
+{
+ ThumbBarToolTipSettings settings = m_view->getToolTipSettings();
+
+ TQString tipText, str;
+ TQString unavailable(i18n("unavailable"));
+
+ tipText = "<table cellspacing=\"0\" cellpadding=\"0\" width=\"250\" border=\"0\">";
+
+ TQFileInfo fileInfo(item->url().path());
+
+ KFileItem fi(KFileItem::Unknown, KFileItem::Unknown, item->url());
+ DMetadata metaData(item->url().path());
+
+ // -- File properties ----------------------------------------------
+
+ if (settings.showFileName ||
+ settings.showFileDate ||
+ settings.showFileSize ||
+ settings.showImageType ||
+ settings.showImageDim)
+ {
+ tipText += m_headBeg + i18n("File Properties") + m_headEnd;
+
+ if (settings.showFileName)
+ {
+ tipText += m_cellBeg + i18n("Name:") + m_cellMid;
+ tipText += item->url().fileName() + m_cellEnd;
+ }
+
+ if (settings.showFileDate)
+ {
+ TQDateTime modifiedDate = fileInfo.lastModified();
+ str = TDEGlobal::locale()->formatDateTime(modifiedDate, true, true);
+ tipText += m_cellBeg + i18n("Modified:") + m_cellMid + str + m_cellEnd;
+ }
+
+ if (settings.showFileSize)
+ {
+ tipText += m_cellBeg + i18n("Size:") + m_cellMid;
+ str = i18n("%1 (%2)").arg(TDEIO::convertSize(fi.size()))
+ .arg(TDEGlobal::locale()->formatNumber(fi.size(), 0));
+ tipText += str + m_cellEnd;
+ }
+
+ TQSize dims;
+#if KDCRAW_VERSION < 0x000106
+ TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
+#endif
+ TQString ext = fileInfo.extension(false).upper();
+
+ if (!ext.isEmpty() && rawFilesExt.upper().contains(ext))
+ {
+ str = i18n("RAW Image");
+ dims = metaData.getImageDimensions();
+ }
+ else
+ {
+ str = fi.mimeComment();
+
+ KFileMetaInfo meta = fi.metaInfo();
+ if (meta.isValid())
+ {
+ if (meta.containsGroup("Jpeg EXIF Data"))
+ dims = meta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
+ else if (meta.containsGroup("General"))
+ dims = meta.group("General").item("Dimensions").value().toSize();
+ else if (meta.containsGroup("Technical"))
+ dims = meta.group("Technical").item("Dimensions").value().toSize();
+ }
+ }
+
+ if (settings.showImageType)
+ {
+ tipText += m_cellBeg + i18n("Type:") + m_cellMid + str + m_cellEnd;
+ }
+
+ if (settings.showImageDim)
+ {
+ TQString mpixels;
+ mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2);
+ str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)")
+ .arg(dims.width()).arg(dims.height()).arg(mpixels);
+ tipText += m_cellBeg + i18n("Dimensions:") + m_cellMid + str + m_cellEnd;
+ }
+ }
+
+ // -- Photograph Info ----------------------------------------------------
+
+ if (settings.showPhotoMake ||
+ settings.showPhotoDate ||
+ settings.showPhotoFocal ||
+ settings.showPhotoExpo ||
+ settings.showPhotoMode ||
+ settings.showPhotoFlash ||
+ settings.showPhotoWB)
+ {
+ PhotoInfoContainer photoInfo = metaData.getPhotographInformations();
+
+ if (!photoInfo.isEmpty())
+ {
+ TQString metaStr;
+ tipText += m_headBeg + i18n("Photograph Properties") + m_headEnd;
+
+ if (settings.showPhotoMake)
+ {
+ str = TQString("%1 / %2").arg(photoInfo.make.isEmpty() ? unavailable : photoInfo.make)
+ .arg(photoInfo.model.isEmpty() ? unavailable : photoInfo.model);
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ metaStr += m_cellBeg + i18n("Make/Model:") + m_cellMid + TQStyleSheet::escape( str ) + m_cellEnd;
+ }
+
+ if (settings.showPhotoDate)
+ {
+ if (photoInfo.dateTime.isValid())
+ {
+ str = TDEGlobal::locale()->formatDateTime(photoInfo.dateTime, true, true);
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ metaStr += m_cellBeg + i18n("Created:") + m_cellMid + TQStyleSheet::escape( str ) + m_cellEnd;
+ }
+ else
+ metaStr += m_cellBeg + i18n("Created:") + m_cellMid + TQStyleSheet::escape( unavailable ) + m_cellEnd;
+ }
+
+ if (settings.showPhotoFocal)
+ {
+ str = photoInfo.aperture.isEmpty() ? unavailable : photoInfo.aperture;
+
+ if (photoInfo.focalLength35mm.isEmpty())
+ str += TQString(" / %1").arg(photoInfo.focalLength.isEmpty() ? unavailable : photoInfo.focalLength);
+ else
+ str += TQString(" / %1").arg(i18n("%1 (35mm: %2)").arg(photoInfo.focalLength).arg(photoInfo.focalLength35mm));
+
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ metaStr += m_cellBeg + i18n("Aperture/Focal:") + m_cellMid + TQStyleSheet::escape( str ) + m_cellEnd;
+ }
+
+ if (settings.showPhotoExpo)
+ {
+ str = TQString("%1 / %2").arg(photoInfo.exposureTime.isEmpty() ? unavailable : photoInfo.exposureTime)
+ .arg(photoInfo.sensitivity.isEmpty() ? unavailable : i18n("%1 ISO").arg(photoInfo.sensitivity));
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ metaStr += m_cellBeg + i18n("Exposure/Sensitivity:") + m_cellMid + TQStyleSheet::escape( str ) + m_cellEnd;
+ }
+
+ if (settings.showPhotoMode)
+ {
+
+ if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ str = unavailable;
+ else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty())
+ str = photoInfo.exposureMode;
+ else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty())
+ str = photoInfo.exposureProgram;
+ else
+ str = TQString("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram);
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ metaStr += m_cellBeg + i18n("Mode/Program:") + m_cellMid + TQStyleSheet::escape( str ) + m_cellEnd;
+ }
+
+ if (settings.showPhotoFlash)
+ {
+ str = photoInfo.flash.isEmpty() ? unavailable : photoInfo.flash;
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ metaStr += m_cellBeg + i18n("Flash:") + m_cellMid + TQStyleSheet::escape( str ) + m_cellEnd;
+ }
+
+ if (settings.showPhotoWB)
+ {
+ str = photoInfo.whiteBalance.isEmpty() ? unavailable : photoInfo.whiteBalance;
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ metaStr += m_cellBeg + i18n("White Balance:") + m_cellMid + TQStyleSheet::escape( str ) + m_cellEnd;
+ }
+
+ tipText += metaStr;
+ }
+ }
+
+ return tipText;
+}
+
+TQString ThumbBarToolTip::breakString(const TQString& input)
+{
+ TQString str = input.simplifyWhiteSpace();
+ str = TQStyleSheet::escape(str);
+ const uint maxLen = m_maxStringLen;
+
+ if (str.length() <= maxLen)
+ return str;
+
+ TQString br;
+
+ uint i = 0;
+ uint count = 0;
+
+ while (i < str.length())
+ {
+ if (count >= maxLen && str[i].isSpace())
+ {
+ count = 0;
+ br.append("<br>");
+ }
+ else
+ {
+ br.append(str[i]);
+ }
+
+ i++;
+ count++;
+ }
+
+ return br;
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/thumbbar/thumbbar.h b/src/libs/thumbbar/thumbbar.h
new file mode 100644
index 00000000..48ac574d
--- /dev/null
+++ b/src/libs/thumbbar/thumbbar.h
@@ -0,0 +1,239 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-22
+ * Description : a bar widget to display image thumbnails
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef THUMBBAR_H
+#define THUMBBAR_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqscrollview.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ThumbBarItem;
+class ThumbBarToolTip;
+class ThumbBarViewPriv;
+class ThumbBarItemPriv;
+
+class DIGIKAM_EXPORT ThumbBarToolTipSettings
+{
+public:
+
+ ThumbBarToolTipSettings()
+ {
+ showToolTips = true;
+ showFileName = true;
+ showFileDate = false;
+ showFileSize = false;
+ showImageType = false;
+ showImageDim = true;
+ showPhotoMake = true;
+ showPhotoDate = true;
+ showPhotoFocal = true;
+ showPhotoExpo = true;
+ showPhotoMode = true;
+ showPhotoFlash = false;
+ showPhotoWB = false;
+ };
+
+ bool showToolTips;
+ bool showFileName;
+ bool showFileDate;
+ bool showFileSize;
+ bool showImageType;
+ bool showImageDim;
+ bool showPhotoMake;
+ bool showPhotoDate;
+ bool showPhotoFocal;
+ bool showPhotoExpo;
+ bool showPhotoMode;
+ bool showPhotoFlash;
+ bool showPhotoWB;
+};
+
+// -------------------------------------------------------------------------
+
+class DIGIKAM_EXPORT ThumbBarView : public TQScrollView
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum Orientation
+ {
+ Horizontal=0,
+ Vertical
+ };
+
+public:
+
+ ThumbBarView(TQWidget* parent, int orientation=Vertical, bool exifRotate=true,
+ ThumbBarToolTipSettings settings=ThumbBarToolTipSettings());
+ virtual ~ThumbBarView();
+
+ int countItems();
+ KURL::List itemsURLs();
+
+ void clear(bool updateView=true);
+ void triggerUpdate();
+
+ void removeItem(ThumbBarItem* item);
+
+ void setSelected(ThumbBarItem* item);
+ void ensureItemVisible(ThumbBarItem* item);
+
+ void setExifRotate(bool exifRotate);
+ bool getExifRotate();
+
+ void setToolTipSettings(const ThumbBarToolTipSettings &settings);
+ ThumbBarToolTipSettings& getToolTipSettings();
+
+ ThumbBarItem* currentItem() const;
+ ThumbBarItem* firstItem() const;
+ ThumbBarItem* lastItem() const;
+ ThumbBarItem* findItem(const TQPoint& pos) const;
+ ThumbBarItem* findItemByURL(const KURL& url) const;
+
+ void refreshThumbs(const KURL::List& urls);
+ void invalidateThumb(ThumbBarItem* item);
+
+signals:
+
+ void signalItemSelected(ThumbBarItem*);
+ void signalURLSelected(const KURL&);
+ void signalItemAdded();
+
+protected:
+
+ int getOrientation();
+ int getTileSize();
+ int getMargin();
+
+ void resizeEvent(TQResizeEvent*);
+ void contentsMousePressEvent(TQMouseEvent*);
+ void contentsMouseMoveEvent(TQMouseEvent*);
+ void contentsMouseReleaseEvent(TQMouseEvent*);
+ void contentsWheelEvent(TQWheelEvent*);
+
+ void insertItem(ThumbBarItem* item);
+ void rearrangeItems();
+ void repaintItem(ThumbBarItem* item);
+
+ virtual void viewportPaintEvent(TQPaintEvent*);
+ virtual void startDrag();
+
+protected slots:
+
+ void slotUpdate();
+
+private slots:
+
+ void slotGotThumbnail(const KURL&, const TQPixmap&);
+ void slotFailedThumbnail(const KURL&);
+
+private:
+
+ ThumbBarViewPriv* d;
+
+ friend class ThumbBarItem;
+};
+
+// -------------------------------------------------------------------------
+
+class DIGIKAM_EXPORT ThumbBarItem
+{
+public:
+
+ ThumbBarItem(ThumbBarView *view, const KURL& url);
+ virtual ~ThumbBarItem();
+
+ KURL url() const;
+
+ ThumbBarItem* next() const;
+ ThumbBarItem* prev() const;
+ int position() const;
+ TQRect rect() const;
+ TQPixmap* pixmap() const;
+
+ void repaint();
+
+private:
+
+ ThumbBarItemPriv* d;
+
+ friend class ThumbBarView;
+};
+
+// -------------------------------------------------------------------------
+
+class DIGIKAM_EXPORT ThumbBarToolTip : public TQToolTip
+{
+
+public:
+
+ ThumbBarToolTip(ThumbBarView *parent);
+ virtual ~ThumbBarToolTip(){};
+
+protected:
+
+ const uint m_maxStringLen;
+
+ TQString m_headBeg;
+ TQString m_headEnd;
+ TQString m_cellBeg;
+ TQString m_cellMid;
+ TQString m_cellEnd;
+ TQString m_cellSpecBeg;
+ TQString m_cellSpecMid;
+ TQString m_cellSpecEnd;
+
+ ThumbBarView *m_view;
+
+protected:
+
+ TQString breakString(const TQString& input);
+
+ virtual TQString tipContentExtraData(ThumbBarItem*){ return TQString(); };
+
+private:
+
+ void maybeTip(const TQPoint& pos);
+ TQString tipContent(ThumbBarItem* item);
+};
+
+} // NameSpace Digikam
+
+#endif /* THUMBBAR_H */
diff --git a/src/libs/thumbbar/thumbnailjob.cpp b/src/libs/thumbbar/thumbnailjob.cpp
new file mode 100644
index 00000000..dcabb393
--- /dev/null
+++ b/src/libs/thumbbar/thumbnailjob.cpp
@@ -0,0 +1,318 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-10-14
+ * Description : digiKam TDEIO thumbnails generator interface
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqcolor.h>
+#include <tqdatastream.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "thumbnailjob.h"
+#include "thumbnailjob.moc"
+
+namespace Digikam
+{
+
+class ThumbnailJobPriv
+{
+public:
+
+ bool highlight;
+ bool exifRotate;
+ bool running;
+
+ int size;
+
+ // Shared memory segment Id. The segment is allocated to a size
+ // of extent x extent x 4 (32 bit image) on first need.
+ int shmid;
+
+ // And the data area
+ uchar *shmaddr;
+
+ KURL curr_url;
+ KURL next_url;
+ KURL::List urlList;
+};
+
+ThumbnailJob::ThumbnailJob(const KURL& url, int size,
+ bool highlight, bool exifRotate)
+ : TDEIO::Job(false)
+{
+ d = new ThumbnailJobPriv;
+
+ d->urlList.append(url);
+
+ d->size = size;
+ d->highlight = highlight;
+ d->exifRotate = exifRotate;
+ d->curr_url = d->urlList.first();
+ d->next_url = d->curr_url;
+ d->running = false;
+ d->shmid = -1;
+ d->shmaddr = 0;
+
+ processNext();
+}
+
+ThumbnailJob::ThumbnailJob(const KURL::List& urlList, int size,
+ bool highlight, bool exifRotate)
+ : TDEIO::Job(false)
+{
+ d = new ThumbnailJobPriv;
+
+ d->urlList = urlList;
+ d->size = size;
+ d->highlight = highlight;
+ d->running = false;
+ d->exifRotate = exifRotate;
+ d->curr_url = d->urlList.first();
+ d->next_url = d->curr_url;
+ d->shmid = -1;
+ d->shmaddr = 0;
+
+ processNext();
+}
+
+ThumbnailJob::~ThumbnailJob()
+{
+ if (d->shmaddr)
+ {
+ shmdt((char*)d->shmaddr);
+ shmctl(d->shmid, IPC_RMID, 0);
+ }
+
+ delete d;
+}
+
+void ThumbnailJob::addItem(const KURL& url)
+{
+ d->urlList.append(url);
+
+ if (!d->running && subjobs.isEmpty())
+ processNext();
+}
+
+void ThumbnailJob::addItems(const KURL::List& urlList)
+{
+ for (KURL::List::const_iterator it = urlList.begin();
+ it != urlList.end(); ++it)
+ {
+ d->urlList.append(*it);
+ }
+
+ if (!d->running && subjobs.isEmpty())
+ processNext();
+}
+
+bool ThumbnailJob::setNextItemToLoad(const KURL& url)
+{
+ KURL::List::const_iterator it = d->urlList.find(url);
+ if (it != d->urlList.end())
+ {
+ d->next_url = *it;
+ return true;
+ }
+
+ return false;
+}
+
+void ThumbnailJob::removeItem(const KURL& url)
+{
+ d->urlList.remove(url);
+}
+
+void ThumbnailJob::processNext()
+{
+ if (d->urlList.isEmpty())
+ {
+ d->running = false;
+ emit signalCompleted();
+ return;
+ }
+
+ KURL::List::iterator it = d->urlList.find(d->next_url);
+ if (it == d->urlList.end())
+ {
+ it = d->urlList.begin();
+ }
+
+ d->curr_url = *it;
+ it = d->urlList.remove(it);
+ if (it != d->urlList.end())
+ {
+ d->next_url = *it;
+ }
+ else
+ {
+ d->next_url = KURL();
+ }
+
+ KURL url(d->curr_url);
+ url.setProtocol("digikamthumbnail");
+
+ TDEIO::TransferJob *job = TDEIO::get(url, false, false);
+ job->addMetaData("size", TQString::number(d->size));
+ createShmSeg();
+
+ if (d->shmid != -1)
+ job->addMetaData("shmid", TQString::number(d->shmid));
+
+ // Rotate thumbnail accordindly with Exif rotation tag if necessary.
+ if (d->exifRotate)
+ job->addMetaData("exif", "yes");
+
+ connect(job, TQ_SIGNAL(data(TDEIO::Job *, const TQByteArray &)),
+ this, TQ_SLOT(slotThumbData(TDEIO::Job *, const TQByteArray &)));
+
+ addSubjob(job);
+ d->running = true;
+}
+
+void ThumbnailJob::slotResult(TDEIO::Job *job)
+{
+ subjobs.remove(job);
+ Q_ASSERT( subjobs.isEmpty() );
+
+ if (job->error())
+ {
+ emit signalFailed(d->curr_url);
+ }
+
+ d->running = false;
+ processNext();
+}
+
+void ThumbnailJob::createShmSeg()
+{
+ if (d->shmid == -1)
+ {
+ if (d->shmaddr)
+ {
+ shmdt((char*)d->shmaddr);
+ shmctl(d->shmid, IPC_RMID, 0);
+ }
+
+ d->shmid = shmget(IPC_PRIVATE, 256 * 256 * 4, IPC_CREAT|0600);
+ if (d->shmid != -1)
+ {
+ d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
+ if (d->shmaddr == (uchar *)-1)
+ {
+ shmctl(d->shmid, IPC_RMID, 0);
+ d->shmaddr = 0;
+ d->shmid = -1;
+ }
+ }
+ else
+ d->shmaddr = 0;
+ }
+}
+
+void ThumbnailJob::slotThumbData(TDEIO::Job*, const TQByteArray &data)
+{
+ if (data.isEmpty())
+ return;
+
+ TQImage thumb;
+ TQDataStream stream(data, IO_ReadOnly);
+ if (d->shmaddr)
+ {
+ int width, height, depth;
+ stream >> width >> height >> depth;
+ thumb = TQImage(d->shmaddr, width, height, depth,
+ 0, 0, TQImage::IgnoreEndian);
+
+ // The buffer supplied to the TQImage constructor above must remain valid
+ // throughout the lifetime of the object.
+ // This is not true, the shared memory will be freed or reused.
+ // If we pass the object around, we must do a deep copy.
+ thumb = thumb.copy();
+ }
+ else
+ {
+ stream >> thumb;
+ }
+
+ if (thumb.isNull())
+ {
+ DWarning() << k_funcinfo << "thumbnail is null" << endl;
+ emit signalFailed(d->curr_url);
+ return;
+ }
+
+ emitThumbnail(thumb);
+}
+
+void ThumbnailJob::emitThumbnail(TQImage& thumb)
+{
+ if (thumb.isNull())
+ {
+ return;
+ }
+
+ TQPixmap pix(thumb);
+
+ int w = pix.width();
+ int h = pix.height();
+
+ // highlight only when requested and when thumbnail
+ // width and height are greater than 10
+ if (d->highlight && (w >= 10 && h >= 10))
+ {
+ TQPainter p(&pix);
+ p.setPen(TQPen(TQColor(0,0,0),1));
+ p.drawRect(0,0,w,h);
+ p.end();
+ }
+
+ emit signalThumbnail(d->curr_url, pix);
+}
+
+} // namespace Digikam
+
+
diff --git a/src/libs/thumbbar/thumbnailjob.h b/src/libs/thumbbar/thumbnailjob.h
new file mode 100644
index 00000000..cc7080e3
--- /dev/null
+++ b/src/libs/thumbbar/thumbnailjob.h
@@ -0,0 +1,88 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-10-14
+ * Description : digiKam TDEIO thumbnails generator interface
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef THUMBNAILJOB_H
+#define THUMBNAILJOB_H
+
+// TQt includes.
+
+#include <tqcstring.h>
+
+// KDE includes.
+
+#include <tdeio/job.h>
+#include <kurl.h>
+
+class TQPixmap;
+class TQImage;
+
+namespace Digikam
+{
+
+class ThumbnailJobPriv;
+
+class ThumbnailJob : public TDEIO::Job
+{
+ TQ_OBJECT
+
+
+public:
+
+ ThumbnailJob(const KURL& url, int size,
+ bool highlight=true, bool exifRotate=false);
+ ThumbnailJob(const KURL::List& urlList, int size,
+ bool highlight=true, bool exifRotate=false);
+ ~ThumbnailJob();
+
+ void addItem(const KURL& url);
+ void addItems(const KURL::List& urlList);
+
+ bool setNextItemToLoad(const KURL& url);
+ void removeItem(const KURL& url);
+
+signals:
+
+ void signalThumbnail(const KURL& url, const TQPixmap& pix);
+ void signalCompleted();
+ void signalFailed(const KURL& url);
+
+private:
+
+ void processNext();
+ void emitThumbnail(TQImage& thumb);
+ void createShmSeg();
+
+protected slots:
+
+ void slotResult(TDEIO::Job *job);
+ void slotThumbData(TDEIO::Job *job, const TQByteArray &data);
+
+private:
+
+ ThumbnailJobPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* THUMBNAILJOB_H */
diff --git a/src/libs/whitebalance/Makefile.am b/src/libs/whitebalance/Makefile.am
new file mode 100644
index 00000000..0325e16a
--- /dev/null
+++ b/src/libs/whitebalance/Makefile.am
@@ -0,0 +1,16 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libwhitebalance.la
+
+libwhitebalance_la_SOURCES = whitebalance.cpp
+
+libwhitebalance_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/digikam \
+ $(all_includes)
+
+digikaminclude_HEADERS = whitebalance.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/whitebalance/blackbody.h b/src/libs/whitebalance/blackbody.h
new file mode 100644
index 00000000..9a004d00
--- /dev/null
+++ b/src/libs/whitebalance/blackbody.h
@@ -0,0 +1,539 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-11
+ * Description : White Balance Black Body array calibrate
+ * in Kelvin.
+ *
+ * Copyright (C) 2004-2005 by Pawel T. Jochym <Jochym jochym at ifj edu pl>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BLACKBODY_H
+#define BLACKBODY_H
+
+namespace Digikam
+{
+
+static const float blackBodyWhiteBalance[][3] =
+{
+ /* 2000 */ { 1.0000, 0.5977, 0.0120 },
+ /* 2010 */ { 1.0000, 0.6021, 0.0140 },
+ /* 2020 */ { 1.0000, 0.6064, 0.0160 },
+ /* 2030 */ { 1.0000, 0.6107, 0.0181 },
+ /* 2040 */ { 1.0000, 0.6150, 0.0202 },
+ /* 2050 */ { 1.0000, 0.6193, 0.0223 },
+ /* 2060 */ { 1.0000, 0.6236, 0.0244 },
+ /* 2070 */ { 1.0000, 0.6278, 0.0265 },
+ /* 2080 */ { 1.0000, 0.6320, 0.0287 },
+ /* 2090 */ { 1.0000, 0.6362, 0.0309 },
+ /* 2100 */ { 1.0000, 0.6403, 0.0331 },
+ /* 2110 */ { 1.0000, 0.6444, 0.0353 },
+ /* 2120 */ { 1.0000, 0.6485, 0.0375 },
+ /* 2130 */ { 1.0000, 0.6526, 0.0398 },
+ /* 2140 */ { 1.0000, 0.6566, 0.0421 },
+ /* 2150 */ { 1.0000, 0.6606, 0.0444 },
+ /* 2160 */ { 1.0000, 0.6646, 0.0467 },
+ /* 2170 */ { 1.0000, 0.6686, 0.0491 },
+ /* 2180 */ { 1.0000, 0.6725, 0.0514 },
+ /* 2190 */ { 1.0000, 0.6764, 0.0538 },
+ /* 2200 */ { 1.0000, 0.6803, 0.0562 },
+ /* 2210 */ { 1.0000, 0.6842, 0.0586 },
+ /* 2220 */ { 1.0000, 0.6880, 0.0611 },
+ /* 2230 */ { 1.0000, 0.6918, 0.0635 },
+ /* 2240 */ { 1.0000, 0.6956, 0.0660 },
+ /* 2250 */ { 1.0000, 0.6994, 0.0685 },
+ /* 2260 */ { 1.0000, 0.7031, 0.0710 },
+ /* 2270 */ { 1.0000, 0.7068, 0.0735 },
+ /* 2280 */ { 1.0000, 0.7105, 0.0761 },
+ /* 2290 */ { 1.0000, 0.7142, 0.0786 },
+ /* 2300 */ { 1.0000, 0.7178, 0.0812 },
+ /* 2310 */ { 1.0000, 0.7214, 0.0838 },
+ /* 2320 */ { 1.0000, 0.7250, 0.0864 },
+ /* 2330 */ { 1.0000, 0.7286, 0.0890 },
+ /* 2340 */ { 1.0000, 0.7321, 0.0917 },
+ /* 2350 */ { 1.0000, 0.7356, 0.0943 },
+ /* 2360 */ { 1.0000, 0.7391, 0.0970 },
+ /* 2370 */ { 1.0000, 0.7426, 0.0997 },
+ /* 2380 */ { 1.0000, 0.7461, 0.1024 },
+ /* 2390 */ { 1.0000, 0.7495, 0.1051 },
+ /* 2400 */ { 1.0000, 0.7529, 0.1079 },
+ /* 2410 */ { 1.0000, 0.7563, 0.1106 },
+ /* 2420 */ { 1.0000, 0.7596, 0.1134 },
+ /* 2430 */ { 1.0000, 0.7630, 0.1162 },
+ /* 2440 */ { 1.0000, 0.7663, 0.1189 },
+ /* 2450 */ { 1.0000, 0.7696, 0.1217 },
+ /* 2460 */ { 1.0000, 0.7728, 0.1246 },
+ /* 2470 */ { 1.0000, 0.7761, 0.1274 },
+ /* 2480 */ { 1.0000, 0.7793, 0.1302 },
+ /* 2490 */ { 1.0000, 0.7825, 0.1331 },
+ /* 2500 */ { 1.0000, 0.7857, 0.1360 },
+ /* 2510 */ { 1.0000, 0.7889, 0.1388 },
+ /* 2520 */ { 1.0000, 0.7920, 0.1417 },
+ /* 2530 */ { 1.0000, 0.7951, 0.1446 },
+ /* 2540 */ { 1.0000, 0.7982, 0.1476 },
+ /* 2550 */ { 1.0000, 0.8013, 0.1505 },
+ /* 2560 */ { 1.0000, 0.8043, 0.1534 },
+ /* 2570 */ { 1.0000, 0.8074, 0.1564 },
+ /* 2580 */ { 1.0000, 0.8104, 0.1593 },
+ /* 2590 */ { 1.0000, 0.8134, 0.1623 },
+ /* 2600 */ { 1.0000, 0.8163, 0.1653 },
+ /* 2610 */ { 1.0000, 0.8193, 0.1683 },
+ /* 2620 */ { 1.0000, 0.8222, 0.1713 },
+ /* 2630 */ { 1.0000, 0.8251, 0.1743 },
+ /* 2640 */ { 1.0000, 0.8280, 0.1773 },
+ /* 2650 */ { 1.0000, 0.8309, 0.1804 },
+ /* 2660 */ { 1.0000, 0.8337, 0.1834 },
+ /* 2670 */ { 1.0000, 0.8365, 0.1865 },
+ /* 2680 */ { 1.0000, 0.8393, 0.1895 },
+ /* 2690 */ { 1.0000, 0.8421, 0.1926 },
+ /* 2700 */ { 1.0000, 0.8449, 0.1957 },
+ /* 2710 */ { 1.0000, 0.8476, 0.1988 },
+ /* 2720 */ { 1.0000, 0.8504, 0.2019 },
+ /* 2730 */ { 1.0000, 0.8531, 0.2050 },
+ /* 2740 */ { 1.0000, 0.8558, 0.2081 },
+ /* 2750 */ { 1.0000, 0.8585, 0.2112 },
+ /* 2760 */ { 1.0000, 0.8611, 0.2144 },
+ /* 2770 */ { 1.0000, 0.8637, 0.2175 },
+ /* 2780 */ { 1.0000, 0.8664, 0.2206 },
+ /* 2790 */ { 1.0000, 0.8690, 0.2238 },
+ /* 2800 */ { 1.0000, 0.8715, 0.2269 },
+ /* 2810 */ { 1.0000, 0.8741, 0.2301 },
+ /* 2820 */ { 1.0000, 0.8766, 0.2333 },
+ /* 2830 */ { 1.0000, 0.8792, 0.2365 },
+ /* 2840 */ { 1.0000, 0.8817, 0.2397 },
+ /* 2850 */ { 1.0000, 0.8842, 0.2429 },
+ /* 2860 */ { 1.0000, 0.8866, 0.2461 },
+ /* 2870 */ { 1.0000, 0.8891, 0.2493 },
+ /* 2880 */ { 1.0000, 0.8915, 0.2525 },
+ /* 2890 */ { 1.0000, 0.8940, 0.2557 },
+ /* 2900 */ { 1.0000, 0.8964, 0.2589 },
+ /* 2910 */ { 1.0000, 0.8987, 0.2621 },
+ /* 2920 */ { 1.0000, 0.9011, 0.2654 },
+ /* 2930 */ { 1.0000, 0.9035, 0.2686 },
+ /* 2940 */ { 1.0000, 0.9058, 0.2719 },
+ /* 2950 */ { 1.0000, 0.9081, 0.2751 },
+ /* 2960 */ { 1.0000, 0.9104, 0.2784 },
+ /* 2970 */ { 1.0000, 0.9127, 0.2816 },
+ /* 2980 */ { 1.0000, 0.9150, 0.2849 },
+ /* 2990 */ { 1.0000, 0.9172, 0.2882 },
+ /* 3000 */ { 1.0000, 0.9195, 0.2914 },
+ /* 3010 */ { 1.0000, 0.9217, 0.2947 },
+ /* 3020 */ { 1.0000, 0.9239, 0.2980 },
+ /* 3030 */ { 1.0000, 0.9261, 0.3013 },
+ /* 3040 */ { 1.0000, 0.9283, 0.3046 },
+ /* 3050 */ { 1.0000, 0.9304, 0.3079 },
+ /* 3060 */ { 1.0000, 0.9326, 0.3112 },
+ /* 3070 */ { 1.0000, 0.9347, 0.3145 },
+ /* 3080 */ { 1.0000, 0.9368, 0.3178 },
+ /* 3090 */ { 1.0000, 0.9389, 0.3211 },
+ /* 3100 */ { 1.0000, 0.9410, 0.3244 },
+ /* 3110 */ { 1.0000, 0.9430, 0.3277 },
+ /* 3120 */ { 1.0000, 0.9451, 0.3310 },
+ /* 3130 */ { 1.0000, 0.9471, 0.3343 },
+ /* 3140 */ { 1.0000, 0.9492, 0.3376 },
+ /* 3150 */ { 1.0000, 0.9512, 0.3410 },
+ /* 3160 */ { 1.0000, 0.9532, 0.3443 },
+ /* 3170 */ { 1.0000, 0.9551, 0.3476 },
+ /* 3180 */ { 1.0000, 0.9571, 0.3509 },
+ /* 3190 */ { 1.0000, 0.9590, 0.3543 },
+ /* 3200 */ { 1.0000, 0.9610, 0.3576 },
+ /* 3210 */ { 1.0000, 0.9629, 0.3609 },
+ /* 3220 */ { 1.0000, 0.9648, 0.3643 },
+ /* 3230 */ { 1.0000, 0.9667, 0.3676 },
+ /* 3240 */ { 1.0000, 0.9686, 0.3709 },
+ /* 3250 */ { 1.0000, 0.9705, 0.3743 },
+ /* 3260 */ { 1.0000, 0.9723, 0.3776 },
+ /* 3270 */ { 1.0000, 0.9741, 0.3810 },
+ /* 3280 */ { 1.0000, 0.9760, 0.3843 },
+ /* 3290 */ { 1.0000, 0.9778, 0.3876 },
+ /* 3300 */ { 1.0000, 0.9796, 0.3910 },
+ /* 3310 */ { 1.0000, 0.9814, 0.3943 },
+ /* 3320 */ { 1.0000, 0.9831, 0.3977 },
+ /* 3330 */ { 1.0000, 0.9849, 0.4010 },
+ /* 3340 */ { 1.0000, 0.9867, 0.4044 },
+ /* 3350 */ { 1.0000, 0.9884, 0.4077 },
+ /* 3360 */ { 1.0000, 0.9901, 0.4111 },
+ /* 3370 */ { 1.0000, 0.9918, 0.4144 },
+ /* 3380 */ { 1.0000, 0.9935, 0.4177 },
+ /* 3390 */ { 1.0000, 0.9952, 0.4211 },
+ /* 3400 */ { 1.0000, 0.9969, 0.4244 },
+ /* 3410 */ { 1.0000, 0.9985, 0.4278 },
+ /* 3420 */ { 1.0000, 1.0000, 0.4311 },
+ /* 3430 */ { 1.0000, 1.0000, 0.4345 },
+ /* 3440 */ { 1.0000, 1.0000, 0.4378 },
+ /* 3450 */ { 1.0000, 1.0000, 0.4412 },
+ /* 3460 */ { 1.0000, 1.0000, 0.4445 },
+ /* 3470 */ { 1.0000, 1.0000, 0.4479 },
+ /* 3480 */ { 0.9992, 1.0000, 0.4512 },
+ /* 3490 */ { 0.9977, 1.0000, 0.4545 },
+ /* 3500 */ { 0.9962, 1.0000, 0.4579 },
+ /* 3510 */ { 0.9947, 1.0000, 0.4612 },
+ /* 3520 */ { 0.9932, 1.0000, 0.4646 },
+ /* 3530 */ { 0.9918, 1.0000, 0.4679 },
+ /* 3540 */ { 0.9903, 1.0000, 0.4712 },
+ /* 3550 */ { 0.9888, 1.0000, 0.4746 },
+ /* 3560 */ { 0.9874, 1.0000, 0.4779 },
+ /* 3570 */ { 0.9859, 1.0000, 0.4812 },
+ /* 3580 */ { 0.9845, 1.0000, 0.4846 },
+ /* 3590 */ { 0.9830, 1.0000, 0.4879 },
+ /* 3600 */ { 0.9816, 1.0000, 0.4912 },
+ /* 3610 */ { 0.9802, 1.0000, 0.4945 },
+ /* 3620 */ { 0.9788, 1.0000, 0.4979 },
+ /* 3630 */ { 0.9773, 1.0000, 0.5012 },
+ /* 3640 */ { 0.9759, 1.0000, 0.5045 },
+ /* 3650 */ { 0.9745, 1.0000, 0.5078 },
+ /* 3660 */ { 0.9731, 1.0000, 0.5111 },
+ /* 3670 */ { 0.9717, 1.0000, 0.5144 },
+ /* 3680 */ { 0.9703, 1.0000, 0.5178 },
+ /* 3690 */ { 0.9689, 1.0000, 0.5211 },
+ /* 3700 */ { 0.9676, 1.0000, 0.5244 },
+ /* 3710 */ { 0.9662, 1.0000, 0.5277 },
+ /* 3720 */ { 0.9648, 1.0000, 0.5310 },
+ /* 3730 */ { 0.9634, 1.0000, 0.5343 },
+ /* 3740 */ { 0.9621, 1.0000, 0.5376 },
+ /* 3750 */ { 0.9607, 1.0000, 0.5409 },
+ /* 3760 */ { 0.9594, 1.0000, 0.5442 },
+ /* 3770 */ { 0.9580, 1.0000, 0.5474 },
+ /* 3780 */ { 0.9567, 1.0000, 0.5507 },
+ /* 3790 */ { 0.9553, 1.0000, 0.5540 },
+ /* 3800 */ { 0.9540, 1.0000, 0.5573 },
+ /* 3810 */ { 0.9527, 1.0000, 0.5606 },
+ /* 3820 */ { 0.9514, 1.0000, 0.5638 },
+ /* 3830 */ { 0.9500, 1.0000, 0.5671 },
+ /* 3840 */ { 0.9487, 1.0000, 0.5704 },
+ /* 3850 */ { 0.9474, 1.0000, 0.5736 },
+ /* 3860 */ { 0.9461, 1.0000, 0.5769 },
+ /* 3870 */ { 0.9448, 1.0000, 0.5802 },
+ /* 3880 */ { 0.9435, 1.0000, 0.5834 },
+ /* 3890 */ { 0.9422, 1.0000, 0.5867 },
+ /* 3900 */ { 0.9409, 1.0000, 0.5899 },
+ /* 3910 */ { 0.9397, 1.0000, 0.5932 },
+ /* 3920 */ { 0.9384, 1.0000, 0.5964 },
+ /* 3930 */ { 0.9371, 1.0000, 0.5996 },
+ /* 3940 */ { 0.9358, 1.0000, 0.6029 },
+ /* 3950 */ { 0.9346, 1.0000, 0.6061 },
+ /* 3960 */ { 0.9333, 1.0000, 0.6093 },
+ /* 3970 */ { 0.9321, 1.0000, 0.6126 },
+ /* 3980 */ { 0.9308, 1.0000, 0.6158 },
+ /* 3990 */ { 0.9296, 1.0000, 0.6190 },
+ /* 4000 */ { 0.9283, 1.0000, 0.6222 },
+ /* 4010 */ { 0.9271, 1.0000, 0.6254 },
+ /* 4020 */ { 0.9259, 1.0000, 0.6286 },
+ /* 4030 */ { 0.9247, 1.0000, 0.6318 },
+ /* 4040 */ { 0.9234, 1.0000, 0.6350 },
+ /* 4050 */ { 0.9222, 1.0000, 0.6382 },
+ /* 4060 */ { 0.9210, 1.0000, 0.6414 },
+ /* 4070 */ { 0.9198, 1.0000, 0.6446 },
+ /* 4080 */ { 0.9186, 1.0000, 0.6478 },
+ /* 4090 */ { 0.9174, 1.0000, 0.6509 },
+ /* 4100 */ { 0.9162, 1.0000, 0.6541 },
+ /* 4110 */ { 0.9150, 1.0000, 0.6573 },
+ /* 4120 */ { 0.9138, 1.0000, 0.6605 },
+ /* 4130 */ { 0.9126, 1.0000, 0.6636 },
+ /* 4140 */ { 0.9115, 1.0000, 0.6668 },
+ /* 4150 */ { 0.9103, 1.0000, 0.6699 },
+ /* 4160 */ { 0.9091, 1.0000, 0.6731 },
+ /* 4170 */ { 0.9080, 1.0000, 0.6762 },
+ /* 4180 */ { 0.9068, 1.0000, 0.6794 },
+ /* 4190 */ { 0.9056, 1.0000, 0.6825 },
+ /* 4200 */ { 0.9045, 1.0000, 0.6856 },
+ /* 4210 */ { 0.9033, 1.0000, 0.6887 },
+ /* 4220 */ { 0.9022, 1.0000, 0.6919 },
+ /* 4230 */ { 0.9011, 1.0000, 0.6950 },
+ /* 4240 */ { 0.8999, 1.0000, 0.6981 },
+ /* 4250 */ { 0.8988, 1.0000, 0.7012 },
+ /* 4260 */ { 0.8974, 1.0000, 0.7041 },
+ /* 4270 */ { 0.8960, 1.0000, 0.7070 },
+ /* 4280 */ { 0.8946, 1.0000, 0.7098 },
+ /* 4290 */ { 0.8932, 1.0000, 0.7127 },
+ /* 4300 */ { 0.8918, 1.0000, 0.7155 },
+ /* 4310 */ { 0.8904, 1.0000, 0.7184 },
+ /* 4320 */ { 0.8890, 1.0000, 0.7212 },
+ /* 4330 */ { 0.8876, 1.0000, 0.7240 },
+ /* 4340 */ { 0.8862, 1.0000, 0.7269 },
+ /* 4350 */ { 0.8849, 1.0000, 0.7297 },
+ /* 4360 */ { 0.8835, 1.0000, 0.7325 },
+ /* 4370 */ { 0.8821, 1.0000, 0.7353 },
+ /* 4380 */ { 0.8808, 1.0000, 0.7381 },
+ /* 4390 */ { 0.8795, 1.0000, 0.7409 },
+ /* 4400 */ { 0.8781, 1.0000, 0.7437 },
+ /* 4410 */ { 0.8768, 1.0000, 0.7465 },
+ /* 4420 */ { 0.8755, 1.0000, 0.7493 },
+ /* 4430 */ { 0.8742, 1.0000, 0.7521 },
+ /* 4440 */ { 0.8729, 1.0000, 0.7549 },
+ /* 4450 */ { 0.8716, 1.0000, 0.7576 },
+ /* 4460 */ { 0.8703, 1.0000, 0.7604 },
+ /* 4470 */ { 0.8690, 1.0000, 0.7632 },
+ /* 4480 */ { 0.8677, 1.0000, 0.7659 },
+ /* 4490 */ { 0.8664, 1.0000, 0.7687 },
+ /* 4500 */ { 0.8652, 1.0000, 0.7714 },
+ /* 4510 */ { 0.8639, 1.0000, 0.7742 },
+ /* 4520 */ { 0.8627, 1.0000, 0.7769 },
+ /* 4530 */ { 0.8614, 1.0000, 0.7797 },
+ /* 4540 */ { 0.8602, 1.0000, 0.7824 },
+ /* 4550 */ { 0.8589, 1.0000, 0.7851 },
+ /* 4560 */ { 0.8577, 1.0000, 0.7879 },
+ /* 4570 */ { 0.8565, 1.0000, 0.7906 },
+ /* 4580 */ { 0.8553, 1.0000, 0.7933 },
+ /* 4590 */ { 0.8541, 1.0000, 0.7960 },
+ /* 4600 */ { 0.8529, 1.0000, 0.7987 },
+ /* 4610 */ { 0.8517, 1.0000, 0.8014 },
+ /* 4620 */ { 0.8505, 1.0000, 0.8041 },
+ /* 4630 */ { 0.8493, 1.0000, 0.8068 },
+ /* 4640 */ { 0.8481, 1.0000, 0.8095 },
+ /* 4650 */ { 0.8469, 1.0000, 0.8122 },
+ /* 4660 */ { 0.8458, 1.0000, 0.8148 },
+ /* 4670 */ { 0.8446, 1.0000, 0.8175 },
+ /* 4680 */ { 0.8434, 1.0000, 0.8202 },
+ /* 4690 */ { 0.8423, 1.0000, 0.8228 },
+ /* 4700 */ { 0.8411, 1.0000, 0.8255 },
+ /* 4710 */ { 0.8400, 1.0000, 0.8282 },
+ /* 4720 */ { 0.8389, 1.0000, 0.8308 },
+ /* 4730 */ { 0.8377, 1.0000, 0.8335 },
+ /* 4740 */ { 0.8366, 1.0000, 0.8361 },
+ /* 4750 */ { 0.8355, 1.0000, 0.8387 },
+ /* 4760 */ { 0.8344, 1.0000, 0.8414 },
+ /* 4770 */ { 0.8333, 1.0000, 0.8440 },
+ /* 4780 */ { 0.8322, 1.0000, 0.8466 },
+ /* 4790 */ { 0.8311, 1.0000, 0.8492 },
+ /* 4800 */ { 0.8300, 1.0000, 0.8518 },
+ /* 4810 */ { 0.8289, 1.0000, 0.8544 },
+ /* 4820 */ { 0.8278, 1.0000, 0.8570 },
+ /* 4830 */ { 0.8268, 1.0000, 0.8596 },
+ /* 4840 */ { 0.8257, 1.0000, 0.8622 },
+ /* 4850 */ { 0.8246, 1.0000, 0.8648 },
+ /* 4860 */ { 0.8236, 1.0000, 0.8674 },
+ /* 4870 */ { 0.8225, 1.0000, 0.8700 },
+ /* 4880 */ { 0.8215, 1.0000, 0.8725 },
+ /* 4890 */ { 0.8204, 1.0000, 0.8751 },
+ /* 4900 */ { 0.8194, 1.0000, 0.8777 },
+ /* 4910 */ { 0.8183, 1.0000, 0.8802 },
+ /* 4920 */ { 0.8173, 1.0000, 0.8828 },
+ /* 4930 */ { 0.8163, 1.0000, 0.8853 },
+ /* 4940 */ { 0.8153, 1.0000, 0.8879 },
+ /* 4950 */ { 0.8143, 1.0000, 0.8904 },
+ /* 4960 */ { 0.8132, 1.0000, 0.8930 },
+ /* 4970 */ { 0.8122, 1.0000, 0.8955 },
+ /* 4980 */ { 0.8112, 1.0000, 0.8980 },
+ /* 4990 */ { 0.8102, 1.0000, 0.9005 },
+ /* 5000 */ { 0.8093, 1.0000, 0.9031 },
+ /* 5010 */ { 0.8083, 1.0000, 0.9056 },
+ /* 5020 */ { 0.8073, 1.0000, 0.9081 },
+ /* 5030 */ { 0.8063, 1.0000, 0.9106 },
+ /* 5040 */ { 0.8053, 1.0000, 0.9131 },
+ /* 5050 */ { 0.8044, 1.0000, 0.9156 },
+ /* 5060 */ { 0.8034, 1.0000, 0.9181 },
+ /* 5070 */ { 0.8024, 1.0000, 0.9206 },
+ /* 5080 */ { 0.8015, 1.0000, 0.9230 },
+ /* 5090 */ { 0.8005, 1.0000, 0.9255 },
+ /* 5100 */ { 0.7996, 1.0000, 0.9280 },
+ /* 5110 */ { 0.7986, 1.0000, 0.9305 },
+ /* 5120 */ { 0.7977, 1.0000, 0.9329 },
+ /* 5130 */ { 0.7968, 1.0000, 0.9354 },
+ /* 5140 */ { 0.7958, 1.0000, 0.9378 },
+ /* 5150 */ { 0.7949, 1.0000, 0.9403 },
+ /* 5160 */ { 0.7940, 1.0000, 0.9427 },
+ /* 5170 */ { 0.7931, 1.0000, 0.9452 },
+ /* 5180 */ { 0.7922, 1.0000, 0.9476 },
+ /* 5190 */ { 0.7913, 1.0000, 0.9500 },
+ /* 5200 */ { 0.7904, 1.0000, 0.9524 },
+ /* 5210 */ { 0.7895, 1.0000, 0.9549 },
+ /* 5220 */ { 0.7886, 1.0000, 0.9573 },
+ /* 5230 */ { 0.7877, 1.0000, 0.9597 },
+ /* 5240 */ { 0.7868, 1.0000, 0.9621 },
+ /* 5250 */ { 0.7859, 1.0000, 0.9645 },
+ /* 5260 */ { 0.7850, 1.0000, 0.9669 },
+ /* 5270 */ { 0.7841, 1.0000, 0.9693 },
+ /* 5280 */ { 0.7833, 1.0000, 0.9717 },
+ /* 5290 */ { 0.7824, 1.0000, 0.9741 },
+ /* 5300 */ { 0.7815, 1.0000, 0.9764 },
+ /* 5310 */ { 0.7807, 1.0000, 0.9788 },
+ /* 5320 */ { 0.7798, 1.0000, 0.9812 },
+ /* 5330 */ { 0.7790, 1.0000, 0.9835 },
+ /* 5340 */ { 0.7781, 1.0000, 0.9859 },
+ /* 5350 */ { 0.7773, 1.0000, 0.9883 },
+ /* 5360 */ { 0.7764, 1.0000, 0.9906 },
+ /* 5370 */ { 0.7756, 1.0000, 0.9930 },
+ /* 5380 */ { 0.7748, 1.0000, 0.9953 },
+ /* 5390 */ { 0.7739, 1.0000, 0.9976 },
+ /* 5400 */ { 0.7731, 1.0000, 1.0000 },
+ /* 5410 */ { 0.7723, 1.0000, 1.0000 },
+ /* 5420 */ { 0.7715, 1.0000, 1.0000 },
+ /* 5430 */ { 0.7706, 1.0000, 1.0000 },
+ /* 5440 */ { 0.7698, 1.0000, 1.0000 },
+ /* 5450 */ { 0.7690, 1.0000, 1.0000 },
+ /* 5460 */ { 0.7682, 1.0000, 1.0000 },
+ /* 5470 */ { 0.7674, 1.0000, 1.0000 },
+ /* 5480 */ { 0.7666, 1.0000, 1.0000 },
+ /* 5490 */ { 0.7658, 1.0000, 1.0000 },
+ /* 5500 */ { 0.7650, 1.0000, 1.0000 },
+ /* 5510 */ { 0.7642, 1.0000, 1.0000 },
+ /* 5520 */ { 0.7634, 1.0000, 1.0000 },
+ /* 5530 */ { 0.7627, 1.0000, 1.0000 },
+ /* 5540 */ { 0.7619, 1.0000, 1.0000 },
+ /* 5550 */ { 0.7611, 1.0000, 1.0000 },
+ /* 5560 */ { 0.7603, 1.0000, 1.0000 },
+ /* 5570 */ { 0.7596, 1.0000, 1.0000 },
+ /* 5580 */ { 0.7588, 1.0000, 1.0000 },
+ /* 5590 */ { 0.7580, 1.0000, 1.0000 },
+ /* 5600 */ { 0.7573, 1.0000, 1.0000 },
+ /* 5610 */ { 0.7565, 1.0000, 1.0000 },
+ /* 5620 */ { 0.7558, 1.0000, 1.0000 },
+ /* 5630 */ { 0.7550, 1.0000, 1.0000 },
+ /* 5640 */ { 0.7543, 1.0000, 1.0000 },
+ /* 5650 */ { 0.7535, 1.0000, 1.0000 },
+ /* 5660 */ { 0.7528, 1.0000, 1.0000 },
+ /* 5670 */ { 0.7521, 1.0000, 1.0000 },
+ /* 5680 */ { 0.7513, 1.0000, 1.0000 },
+ /* 5690 */ { 0.7506, 1.0000, 1.0000 },
+ /* 5700 */ { 0.7499, 1.0000, 1.0000 },
+ /* 5710 */ { 0.7492, 1.0000, 1.0000 },
+ /* 5720 */ { 0.7484, 1.0000, 1.0000 },
+ /* 5730 */ { 0.7477, 1.0000, 1.0000 },
+ /* 5740 */ { 0.7470, 1.0000, 1.0000 },
+ /* 5750 */ { 0.7456, 1.0000, 1.0000 },
+ /* 5760 */ { 0.7436, 1.0000, 1.0000 },
+ /* 5770 */ { 0.7416, 1.0000, 1.0000 },
+ /* 5780 */ { 0.7396, 1.0000, 1.0000 },
+ /* 5790 */ { 0.7377, 1.0000, 1.0000 },
+ /* 5800 */ { 0.7358, 1.0000, 1.0000 },
+ /* 5810 */ { 0.7338, 1.0000, 1.0000 },
+ /* 5820 */ { 0.7319, 1.0000, 1.0000 },
+ /* 5830 */ { 0.7300, 1.0000, 1.0000 },
+ /* 5840 */ { 0.7281, 1.0000, 1.0000 },
+ /* 5850 */ { 0.7263, 1.0000, 1.0000 },
+ /* 5860 */ { 0.7244, 1.0000, 1.0000 },
+ /* 5870 */ { 0.7225, 1.0000, 1.0000 },
+ /* 5880 */ { 0.7207, 1.0000, 1.0000 },
+ /* 5890 */ { 0.7189, 1.0000, 1.0000 },
+ /* 5900 */ { 0.7170, 1.0000, 1.0000 },
+ /* 5910 */ { 0.7152, 1.0000, 1.0000 },
+ /* 5920 */ { 0.7134, 1.0000, 1.0000 },
+ /* 5930 */ { 0.7116, 1.0000, 1.0000 },
+ /* 5940 */ { 0.7099, 1.0000, 1.0000 },
+ /* 5950 */ { 0.7081, 1.0000, 1.0000 },
+ /* 5960 */ { 0.7063, 1.0000, 1.0000 },
+ /* 5970 */ { 0.7046, 1.0000, 1.0000 },
+ /* 5980 */ { 0.7029, 1.0000, 1.0000 },
+ /* 5990 */ { 0.7011, 1.0000, 1.0000 },
+ /* 6000 */ { 0.6994, 1.0000, 1.0000 },
+ /* 6010 */ { 0.6977, 1.0000, 1.0000 },
+ /* 6020 */ { 0.6960, 1.0000, 1.0000 },
+ /* 6030 */ { 0.6943, 1.0000, 1.0000 },
+ /* 6040 */ { 0.6926, 1.0000, 1.0000 },
+ /* 6050 */ { 0.6910, 1.0000, 1.0000 },
+ /* 6060 */ { 0.6893, 1.0000, 1.0000 },
+ /* 6070 */ { 0.6877, 1.0000, 1.0000 },
+ /* 6080 */ { 0.6860, 1.0000, 1.0000 },
+ /* 6090 */ { 0.6844, 1.0000, 1.0000 },
+ /* 6100 */ { 0.6828, 1.0000, 1.0000 },
+ /* 6110 */ { 0.6812, 1.0000, 1.0000 },
+ /* 6120 */ { 0.6796, 1.0000, 1.0000 },
+ /* 6130 */ { 0.6780, 1.0000, 1.0000 },
+ /* 6140 */ { 0.6764, 1.0000, 1.0000 },
+ /* 6150 */ { 0.6748, 1.0000, 1.0000 },
+ /* 6160 */ { 0.6733, 1.0000, 1.0000 },
+ /* 6170 */ { 0.6717, 1.0000, 1.0000 },
+ /* 6180 */ { 0.6702, 1.0000, 1.0000 },
+ /* 6190 */ { 0.6686, 1.0000, 1.0000 },
+ /* 6200 */ { 0.6671, 1.0000, 1.0000 },
+ /* 6210 */ { 0.6656, 1.0000, 1.0000 },
+ /* 6220 */ { 0.6641, 1.0000, 1.0000 },
+ /* 6230 */ { 0.6626, 1.0000, 1.0000 },
+ /* 6240 */ { 0.6611, 1.0000, 1.0000 },
+ /* 6250 */ { 0.6596, 1.0000, 1.0000 },
+ /* 6260 */ { 0.6581, 1.0000, 1.0000 },
+ /* 6270 */ { 0.6566, 1.0000, 1.0000 },
+ /* 6280 */ { 0.6552, 1.0000, 1.0000 },
+ /* 6290 */ { 0.6537, 1.0000, 1.0000 },
+ /* 6300 */ { 0.6523, 1.0000, 1.0000 },
+ /* 6310 */ { 0.6508, 1.0000, 1.0000 },
+ /* 6320 */ { 0.6494, 1.0000, 1.0000 },
+ /* 6330 */ { 0.6480, 1.0000, 1.0000 },
+ /* 6340 */ { 0.6465, 1.0000, 1.0000 },
+ /* 6350 */ { 0.6451, 1.0000, 1.0000 },
+ /* 6360 */ { 0.6437, 1.0000, 1.0000 },
+ /* 6370 */ { 0.6423, 1.0000, 1.0000 },
+ /* 6380 */ { 0.6410, 1.0000, 1.0000 },
+ /* 6390 */ { 0.6396, 1.0000, 1.0000 },
+ /* 6400 */ { 0.6382, 1.0000, 1.0000 },
+ /* 6410 */ { 0.6368, 1.0000, 1.0000 },
+ /* 6420 */ { 0.6355, 1.0000, 1.0000 },
+ /* 6430 */ { 0.6341, 1.0000, 1.0000 },
+ /* 6440 */ { 0.6328, 1.0000, 1.0000 },
+ /* 6450 */ { 0.6315, 1.0000, 1.0000 },
+ /* 6460 */ { 0.6301, 1.0000, 1.0000 },
+ /* 6470 */ { 0.6288, 1.0000, 1.0000 },
+ /* 6480 */ { 0.6275, 1.0000, 1.0000 },
+ /* 6490 */ { 0.6262, 1.0000, 1.0000 },
+ /* 6500 */ { 0.6249, 1.0000, 1.0000 },
+ /* 6510 */ { 0.6236, 1.0000, 1.0000 },
+ /* 6520 */ { 0.6223, 1.0000, 1.0000 },
+ /* 6530 */ { 0.6210, 1.0000, 1.0000 },
+ /* 6540 */ { 0.6198, 1.0000, 1.0000 },
+ /* 6550 */ { 0.6185, 1.0000, 1.0000 },
+ /* 6560 */ { 0.6173, 1.0000, 1.0000 },
+ /* 6570 */ { 0.6160, 1.0000, 1.0000 },
+ /* 6580 */ { 0.6148, 1.0000, 1.0000 },
+ /* 6590 */ { 0.6135, 1.0000, 1.0000 },
+ /* 6600 */ { 0.6123, 1.0000, 1.0000 },
+ /* 6610 */ { 0.6111, 1.0000, 1.0000 },
+ /* 6620 */ { 0.6098, 1.0000, 1.0000 },
+ /* 6630 */ { 0.6086, 1.0000, 1.0000 },
+ /* 6640 */ { 0.6074, 1.0000, 1.0000 },
+ /* 6650 */ { 0.6062, 1.0000, 1.0000 },
+ /* 6660 */ { 0.6050, 1.0000, 1.0000 },
+ /* 6670 */ { 0.6038, 1.0000, 1.0000 },
+ /* 6680 */ { 0.6026, 1.0000, 1.0000 },
+ /* 6690 */ { 0.6015, 1.0000, 1.0000 },
+ /* 6700 */ { 0.6003, 1.0000, 1.0000 },
+ /* 6710 */ { 0.5991, 1.0000, 1.0000 },
+ /* 6720 */ { 0.5980, 1.0000, 1.0000 },
+ /* 6730 */ { 0.5968, 1.0000, 1.0000 },
+ /* 6740 */ { 0.5957, 1.0000, 1.0000 },
+ /* 6750 */ { 0.5945, 1.0000, 1.0000 },
+ /* 6760 */ { 0.5934, 1.0000, 1.0000 },
+ /* 6770 */ { 0.5923, 1.0000, 1.0000 },
+ /* 6780 */ { 0.5911, 1.0000, 1.0000 },
+ /* 6790 */ { 0.5900, 0.9992, 1.0000 },
+ /* 6800 */ { 0.5889, 0.9981, 1.0000 },
+ /* 6810 */ { 0.5878, 0.9970, 1.0000 },
+ /* 6820 */ { 0.5867, 0.9959, 1.0000 },
+ /* 6830 */ { 0.5856, 0.9948, 1.0000 },
+ /* 6840 */ { 0.5845, 0.9937, 1.0000 },
+ /* 6850 */ { 0.5834, 0.9926, 1.0000 },
+ /* 6860 */ { 0.5823, 0.9915, 1.0000 },
+ /* 6870 */ { 0.5813, 0.9904, 1.0000 },
+ /* 6880 */ { 0.5802, 0.9893, 1.0000 },
+ /* 6890 */ { 0.5791, 0.9882, 1.0000 },
+ /* 6900 */ { 0.5781, 0.9871, 1.0000 },
+ /* 6910 */ { 0.5770, 0.9861, 1.0000 },
+ /* 6920 */ { 0.5760, 0.9850, 1.0000 },
+ /* 6930 */ { 0.5749, 0.9839, 1.0000 },
+ /* 6940 */ { 0.5739, 0.9829, 1.0000 },
+ /* 6950 */ { 0.5728, 0.9818, 1.0000 },
+ /* 6960 */ { 0.5718, 0.9808, 1.0000 },
+ /* 6970 */ { 0.5708, 0.9797, 1.0000 },
+ /* 6980 */ { 0.5698, 0.9787, 1.0000 },
+ /* 6990 */ { 0.5687, 0.9776, 1.0000 },
+ /* 7000 */ { 0.5677, 0.9766, 1.0000 },
+};
+
+} // NameSpace Digikam
+
+#endif /* BLACKBODY_H */
diff --git a/src/libs/whitebalance/whitebalance.cpp b/src/libs/whitebalance/whitebalance.cpp
new file mode 100644
index 00000000..9e74708d
--- /dev/null
+++ b/src/libs/whitebalance/whitebalance.cpp
@@ -0,0 +1,382 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-16-01
+ * Description : white balance color correction.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Guillaume Castagnino <casta at xwing dot info>
+ *
+ * Some parts are inspired from RawPhoto implementation copyrighted
+ * 2004-2005 by Pawel T. Jochym <jochym at ifj edu pl>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqcolor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "whitebalance.h"
+
+namespace Digikam
+{
+
+class WhiteBalancePriv
+{
+
+public:
+
+ WhiteBalancePriv()
+ {
+ // Obsolete in algorithm since over/under exposure indicators
+ // are implemented directly with preview widget.
+ WBind = false;
+ overExp = false;
+
+ clipSat = true;
+ mr = 1.0;
+ mg = 1.0;
+ mb = 1.0;
+ BP = 0;
+
+ // Neutral color temperature settings.
+ dark = 0.5;
+ black = 0.0;
+ exposition = 0.0;
+ gamma = 1.0;
+ saturation = 1.0;
+ green = 1.0;
+ temperature = 6500.0;
+ }
+
+ bool clipSat;
+ bool overExp;
+ bool WBind;
+
+ double saturation;
+ double temperature;
+ double gamma;
+ double black;
+ double exposition;
+ double dark;
+ double green;
+
+ int BP;
+ int WP;
+
+ uint rgbMax;
+
+ float curve[65536];
+ float mr;
+ float mg;
+ float mb;
+};
+
+WhiteBalance::WhiteBalance(bool sixteenBit)
+{
+ d = new WhiteBalancePriv;
+ d->WP = sixteenBit ? 65536 : 256;
+ d->rgbMax = sixteenBit ? 65536 : 256;
+}
+
+WhiteBalance::~WhiteBalance()
+{
+ delete d;
+}
+
+void WhiteBalance::whiteBalance(uchar *data, int width, int height, bool sixteenBit,
+ double black, double exposition,
+ double temperature, double green, double dark,
+ double gamma, double saturation)
+{
+ d->temperature = temperature;
+ d->green = green;
+ d->dark = dark;
+ d->black = black;
+ d->exposition = exposition;
+ d->gamma = gamma;
+ d->saturation = saturation;
+
+ // Set final lut.
+ setRGBmult();
+ d->mr = d->mb = 1.0;
+ if (d->clipSat) d->mg = 1.0;
+ setLUTv();
+ setRGBmult();
+
+ // Apply White balance adjustments.
+ adjustWhiteBalance(data, width, height, sixteenBit);
+}
+
+void WhiteBalance::autoWBAdjustementFromColor(const TQColor &tc, double &temperature, double &green)
+{
+ // Calculate Temperature and Green component from color picked.
+
+ double tmin, tmax, mBR;
+ float mr, mg, mb;
+
+ DDebug() << "Sums: R:" << tc.red() << " G:" << tc.green() << " B:" << tc.blue() << endl;
+
+ /* This is a dichotomic search based on Blue and Red layers ratio
+ to find the matching temperature
+ Adapted from ufraw (0.12.1) RGB_to_Temperature
+ */
+ tmin = 2000.0;
+ tmax = 12000.0;
+ mBR = (double)tc.blue() / (double)tc.red();
+ green = 1.0;
+ for (temperature = (tmin+tmax)/2; tmax-tmin > 10; temperature = (tmin+tmax)/2)
+ {
+ DDebug() << "Intermediate Temperature (K):" << temperature << endl;
+ setRGBmult(temperature, green, mr, mg, mb);
+ if (mr/mb > mBR)
+ tmax = temperature;
+ else
+ tmin = temperature;
+ }
+ // Calculate the green level to neutralize picture
+ green = (mr / mg) / ((double)tc.green() / (double)tc.red());
+
+ DDebug() << "Temperature (K):" << temperature << endl;
+ DDebug() << "Green component:" << green << endl;
+}
+
+void WhiteBalance::autoExposureAdjustement(uchar* data, int width, int height, bool sb,
+ double &black, double &expo)
+{
+ // Create an histogram of original image.
+
+ ImageHistogram *histogram = new ImageHistogram(data, width, height, sb);
+
+ // Calculate optimal exposition and black level
+
+ int i;
+ double sum, stop;
+ uint rgbMax = sb ? 65536 : 256;
+
+ // Cutoff at 0.5% of the histogram.
+
+ stop = width * height / 200;
+
+ for (i = rgbMax, sum = 0; (i >= 0) && (sum < stop); i--)
+ sum += histogram->getValue(Digikam::ImageHistogram::ValueChannel, i);
+
+ expo = -log((float)(i+1) / rgbMax) / log(2);
+ DDebug() << "White level at:" << i << endl;
+
+ for (i = 1, sum = 0; (i < (int)rgbMax) && (sum < stop); i++)
+ sum += histogram->getValue(Digikam::ImageHistogram::ValueChannel, i);
+
+ black = (double)i / rgbMax;
+ black /= 2;
+
+ DDebug() << "Black:" << black << " Exposition:" << expo << endl;
+
+ delete histogram;
+}
+
+void WhiteBalance::setRGBmult(double &temperature, double &green, float &mr, float &mg, float &mb)
+{
+ float mi;
+ double xD, yD, X, Y, Z;
+
+ if ( temperature > 12000 ) temperature = 12000.0;
+
+ /* Here starts the code picked from ufraw (0.12.1)
+ to convert Temperature + green multiplier to RGB multipliers
+ */
+ /* Convert between Temperature and RGB.
+ * Base on information from http://www.brucelindbloom.com/
+ * The fit for D-illuminant between 4000K and 12000K are from CIE
+ * The generalization to 2000K < T < 4000K and the blackbody fits
+ * are my own and should be taken with a grain of salt.
+ */
+ const double XYZ_to_RGB[3][3] = {
+ { 3.24071, -0.969258, 0.0556352 },
+ {-1.53726, 1.87599, -0.203996 },
+ {-0.498571, 0.0415557, 1.05707 } };
+ // Fit for CIE Daylight illuminant
+ if (temperature <= 4000)
+ {
+ xD = 0.27475e9/(temperature*temperature*temperature)
+ - 0.98598e6/(temperature*temperature)
+ + 1.17444e3/temperature + 0.145986;
+ }
+ else if (temperature <= 7000)
+ {
+ xD = -4.6070e9/(temperature*temperature*temperature)
+ + 2.9678e6/(temperature*temperature)
+ + 0.09911e3/temperature + 0.244063;
+ }
+ else
+ {
+ xD = -2.0064e9/(temperature*temperature*temperature)
+ + 1.9018e6/(temperature*temperature)
+ + 0.24748e3/temperature + 0.237040;
+ }
+ yD = -3*xD*xD + 2.87*xD - 0.275;
+
+ X = xD/yD;
+ Y = 1;
+ Z = (1-xD-yD)/yD;
+ mr = X*XYZ_to_RGB[0][0] + Y*XYZ_to_RGB[1][0] + Z*XYZ_to_RGB[2][0];
+ mg = X*XYZ_to_RGB[0][1] + Y*XYZ_to_RGB[1][1] + Z*XYZ_to_RGB[2][1];
+ mb = X*XYZ_to_RGB[0][2] + Y*XYZ_to_RGB[1][2] + Z*XYZ_to_RGB[2][2];
+ /* End of the code picked to ufraw
+ */
+
+ // Apply green multiplier
+ mg = mg / green;
+
+ mr = 1.0 / mr;
+ mg = 1.0 / mg;
+ mb = 1.0 / mb;
+
+ // Normalize to at least 1.0, so we are not dimming colors only bumping.
+ mi = TQMIN(mr, TQMIN(mg, mb));
+ mr /= mi;
+ mg /= mi;
+ mb /= mi;
+}
+
+void WhiteBalance::setRGBmult()
+{
+ setRGBmult(d->temperature, d->green, d->mr, d->mg, d->mb);
+}
+
+void WhiteBalance::setLUTv()
+{
+ double b = d->mg * pow(2, d->exposition);
+ d->BP = (uint)(d->rgbMax * d->black);
+ d->WP = (uint)(d->rgbMax / b);
+
+ if (d->WP - d->BP < 1) d->WP = d->BP + 1;
+
+ DDebug() << "T(K): " << d->temperature
+ << " => R:" << d->mr
+ << " G:" << d->mg
+ << " B:" << d->mb
+ << " BP:" << d->BP
+ << " WP:" << d->WP
+ << endl;
+
+ d->curve[0] = 0;
+
+ // We will try to reproduce the same Gamma effect here than BCG tool.
+ double gamma;
+ if (d->gamma >= 1.0)
+ gamma = 0.335*(2.0-d->gamma) + 0.665;
+ else
+ gamma = 1.8*(2.0-d->gamma) - 0.8;
+
+ for (int i = 1; i < (int)d->rgbMax; i++)
+ {
+ float x = (float)(i - d->BP)/(d->WP - d->BP);
+ d->curve[i] = (i < d->BP) ? 0 : (d->rgbMax-1) * pow((double)x, gamma);
+ d->curve[i] *= (1 - d->dark * exp(-x * x / 0.002));
+ d->curve[i] /= (float)i;
+ }
+}
+
+void WhiteBalance::adjustWhiteBalance(uchar *data, int width, int height, bool sixteenBit)
+{
+ uint i, j;
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue;
+ uchar *ptr = data;
+
+ for (j = 0 ; j < (uint)(width*height) ; j++)
+ {
+ int v, rv[3];
+
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ rv[0] = (int)(blue * d->mb);
+ rv[1] = (int)(green * d->mg);
+ rv[2] = (int)(red * d->mr);
+ v = TQMAX(rv[0], rv[1]);
+ v = TQMAX(v, rv[2]);
+
+ if (d->clipSat) v = TQMIN(v, (int)d->rgbMax-1);
+ i = v;
+
+ ptr[0] = (uchar)pixelColor(rv[0], i, v);
+ ptr[1] = (uchar)pixelColor(rv[1], i, v);
+ ptr[2] = (uchar)pixelColor(rv[2], i, v);
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue;
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (j = 0 ; j < (uint)(width*height) ; j++)
+ {
+ int v, rv[3];
+
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ rv[0] = (int)(blue * d->mb);
+ rv[1] = (int)(green * d->mg);
+ rv[2] = (int)(red * d->mr);
+ v = TQMAX(rv[0], rv[1]);
+ v = TQMAX(v, rv[2]);
+
+ if (d->clipSat) v = TQMIN(v, (int)d->rgbMax-1);
+ i = v;
+
+ ptr[0] = pixelColor(rv[0], i, v);
+ ptr[1] = pixelColor(rv[1], i, v);
+ ptr[2] = pixelColor(rv[2], i, v);
+ ptr += 4;
+ }
+ }
+}
+
+unsigned short WhiteBalance::pixelColor(int colorMult, int index, int value)
+{
+ int r = (d->clipSat && colorMult > (int)d->rgbMax) ? d->rgbMax : colorMult;
+
+ if (value > d->BP && d->overExp && value > d->WP)
+ {
+ if (d->WBind)
+ r = (colorMult > d->WP) ? 0 : r;
+ else
+ r = 0;
+ }
+
+ return( (unsigned short)CLAMP((int)((index - d->saturation*(index - r)) * d->curve[index]),
+ 0, (int)(d->rgbMax-1)) );
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/whitebalance/whitebalance.h b/src/libs/whitebalance/whitebalance.h
new file mode 100644
index 00000000..466fdc67
--- /dev/null
+++ b/src/libs/whitebalance/whitebalance.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-16-01
+ * Description : white balance color correction.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Guillaume Castagnino <casta at xwing dot info>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef WHITEBALANCE_H
+#define WHITEBALANCE_H
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+class TQColor;
+
+namespace Digikam
+{
+
+class WhiteBalancePriv;
+
+class DIGIKAM_EXPORT WhiteBalance
+{
+
+public:
+
+ WhiteBalance(bool sixteenBit);
+ ~WhiteBalance();
+
+ void whiteBalance(uchar *data, int width, int height, bool sixteenBit,
+ double black=0.0, double exposition=0.0,
+ double temperature=6500.0, double green=1.0, double dark=0.5,
+ double gamma=1.0, double saturation=1.0);
+
+ static void autoExposureAdjustement(uchar* data, int width, int height, bool sb,
+ double &black, double &expo);
+
+ static void autoWBAdjustementFromColor(const TQColor &tc, double &temperature, double &green);
+
+private:
+
+ void setRGBmult();
+ static void setRGBmult(double &temperature, double &green, float &mr, float &mg, float &mb);
+ void setLUTv();
+ void adjustWhiteBalance(uchar *data, int width, int height, bool sixteenBit);
+ inline unsigned short pixelColor(int colorMult, int index, int value);
+
+private:
+
+ WhiteBalancePriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* WHITEBALANCE_H */
diff --git a/src/libs/widgets/Makefile.am b/src/libs/widgets/Makefile.am
new file mode 100644
index 00000000..699a4438
--- /dev/null
+++ b/src/libs/widgets/Makefile.am
@@ -0,0 +1,12 @@
+SUBDIRS = metadata imageplugins common iccprofiles
+
+noinst_LTLIBRARIES = libwidgets.la
+
+libwidgets_la_SOURCES =
+
+libwidgets_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+libwidgets_la_LIBADD = $(top_builddir)/src/libs/widgets/metadata/libmetadatawidgets.la \
+ $(top_builddir)/src/libs/widgets/iccprofiles/libiccprofileswidgets.la \
+ $(top_builddir)/src/libs/widgets/imageplugins/libimagepluginswidgets.la \
+ $(top_builddir)/src/libs/widgets/common/libcommonwidgets.la
diff --git a/src/libs/widgets/common/Makefile.am b/src/libs/widgets/common/Makefile.am
new file mode 100644
index 00000000..287f60d8
--- /dev/null
+++ b/src/libs/widgets/common/Makefile.am
@@ -0,0 +1,25 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libcommonwidgets.la
+
+libcommonwidgets_la_SOURCES = histogramwidget.cpp colorgradientwidget.cpp curveswidget.cpp dlogoaction.cpp \
+ sidebar.cpp squeezedcombobox.cpp filesaveoptionsbox.cpp dpopupmenu.cpp \
+ statuszoombar.cpp statusnavigatebar.cpp statusprogressbar.cpp searchtextbar.cpp \
+ dcursortracker.cpp paniconwidget.cpp previewwidget.cpp splashscreen.cpp statusled.cpp
+
+libcommonwidgets_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/loaders \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+
+digikaminclude_HEADERS = histogramwidget.h colorgradientwidget.h curveswidget.h sidebar.h dlogoaction.h \
+ squeezedcombobox.h dpopupmenu.h statuszoombar.h statusnavigatebar.h searchtextbar.h \
+ statusprogressbar.h dcursortracker.h paniconwidget.h previewwidget.h statusled.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/widgets/common/colorgradientwidget.cpp b/src/libs/widgets/common/colorgradientwidget.cpp
new file mode 100644
index 00000000..df4c96ca
--- /dev/null
+++ b/src/libs/widgets/common/colorgradientwidget.cpp
@@ -0,0 +1,161 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-28
+ * Description : a color gradient widget
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier<caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqpainter.h>
+#include <tqdrawutil.h>
+
+// KDE includes.
+
+#include <kimageeffect.h>
+
+// Local includes.
+
+#include "colorgradientwidget.h"
+#include "colorgradientwidget.moc"
+
+namespace Digikam
+{
+
+class ColorGradientWidgetPriv
+{
+
+public:
+
+ ColorGradientWidgetPriv(){}
+
+ int orientation;
+
+ TQColor color1;
+ TQColor color2;
+};
+
+ColorGradientWidget::ColorGradientWidget(int o, int size, TQWidget *parent)
+ : TQFrame(parent, 0, TQt::WDestructiveClose)
+{
+ d = new ColorGradientWidgetPriv;
+ d->orientation = o;
+
+ setFrameStyle(TQFrame::Box|TQFrame::Plain);
+ setLineWidth(1);
+
+ if ( d->orientation ==TQt::Horizontal )
+ setFixedHeight( size );
+ else
+ setFixedWidth( size );
+
+ d->color1.setRgb( 0, 0, 0 );
+ d->color2.setRgb( 255, 255, 255 );
+}
+
+ColorGradientWidget::~ColorGradientWidget()
+{
+ delete d;
+}
+
+void ColorGradientWidget::setColors( const TQColor &col1, const TQColor &col2 )
+{
+ d->color1 = col1;
+ d->color2 = col2;
+ update();
+}
+
+void ColorGradientWidget::drawContents(TQPainter *p)
+{
+ TQImage image(contentsRect().width(), contentsRect().height(), 32);
+
+ TQColor col, color1, color2;
+ float scale;
+
+ // Widget is disable : drawing grayed frame.
+ if ( !isEnabled() )
+ {
+ color1 = palette().disabled().foreground();
+ color2 = palette().disabled().background();
+ }
+ else
+ {
+ color1 = d->color1;
+ color2 = d->color2;
+ }
+
+ int redDiff = color2.red() - color1.red();
+ int greenDiff = color2.green() - color1.green();
+ int blueDiff = color2.blue() - color1.blue();
+
+ if ( d->orientation ==TQt::Vertical )
+ {
+ for ( int y = 0; y < image.height(); y++ )
+ {
+ scale = 1.0 * y / image.height();
+ col.setRgb( color1.red() + int(redDiff * scale),
+ color1.green() + int(greenDiff * scale),
+ color1.blue() + int(blueDiff * scale) );
+
+ unsigned int *p = (uint *) image.scanLine( y );
+
+ for ( int x = 0; x < image.width(); x++ )
+ *p++ = col.rgb();
+ }
+ }
+ else
+ {
+ unsigned int *p = (uint *) image.scanLine( 0 );
+
+ for ( int x = 0; x < image.width(); x++ )
+ {
+ scale = 1.0 * x / image.width();
+ col.setRgb( color1.red() + int(redDiff * scale),
+ color1.green() + int(greenDiff * scale),
+ color1.blue() + int(blueDiff * scale) );
+ *p++ = col.rgb();
+ }
+
+ for ( int y = 1; y < image.height(); y++ )
+ {
+ memcpy( image.scanLine( y ), image.scanLine( y - 1),
+ sizeof( unsigned int ) * image.width() );
+ }
+ }
+
+ const int psize = 256;
+ TQColor ditherPalette[psize];
+
+ for ( int s = 0; s < psize; s++ )
+ {
+ ditherPalette[s].setRgb( color1.red() + redDiff * s / psize,
+ color1.green() + greenDiff * s / psize,
+ color1.blue() + blueDiff * s / psize );
+ }
+
+ KImageEffect::dither(image, ditherPalette, psize);
+
+ TQPixmap pm;
+ pm.convertFromImage(image);
+ p->drawPixmap(contentsRect(), pm);
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/widgets/common/colorgradientwidget.h b/src/libs/widgets/common/colorgradientwidget.h
new file mode 100644
index 00000000..aafc3df6
--- /dev/null
+++ b/src/libs/widgets/common/colorgradientwidget.h
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-28
+ * Description : a color gradient widget
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef COLORGRADIENTWIDGET_H
+#define COLORGRADIENTWIDGET_H
+
+// KDE includes.
+
+#include <tqframe.h>
+#include <tqcolor.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ColorGradientWidgetPriv;
+
+class DIGIKAM_EXPORT ColorGradientWidget : public TQFrame
+{
+TQ_OBJECT
+
+
+public:
+
+ enum Orientation
+ {
+ Horizontal=0,
+ Vertical
+ };
+
+public:
+
+ ColorGradientWidget( int o, int size, TQWidget *parent=0 );
+
+ ~ColorGradientWidget();
+
+ void setColors( const TQColor &col1, const TQColor &col2 );
+
+protected:
+
+ void drawContents(TQPainter *);
+
+private:
+
+ ColorGradientWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* COLORGRADIENTWIDGET_H */
diff --git a/src/libs/widgets/common/curveswidget.cpp b/src/libs/widgets/common/curveswidget.cpp
new file mode 100644
index 00000000..281aecc9
--- /dev/null
+++ b/src/libs/widgets/common/curveswidget.cpp
@@ -0,0 +1,838 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : a widget to draw histogram curves
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdlib>
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqpoint.h>
+#include <tqpen.h>
+#include <tqevent.h>
+#include <tqtimer.h>
+#include <tqrect.h>
+#include <tqcolor.h>
+#include <tqfont.h>
+#include <tqfontmetrics.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+
+// Digikam includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "imagecurves.h"
+
+// Local includes.
+
+#include "curveswidget.h"
+#include "curveswidget.moc"
+
+namespace Digikam
+{
+
+class CurvesWidgetPriv
+{
+public:
+
+ enum RepaintType
+ {
+ HistogramDataLoading = 0, // Image Data loading in progress.
+ HistogramNone, // No current histogram values calculation.
+ HistogramStarted, // Histogram values calculation started.
+ HistogramCompleted, // Histogram values calculation completed.
+ HistogramFailed // Histogram values calculation failed.
+ };
+
+ CurvesWidgetPriv()
+ {
+ blinkTimer = 0;
+ curves = 0;
+ grabPoint = -1;
+ last = 0;
+ guideVisible = false;
+ xMouseOver = -1;
+ yMouseOver = -1;
+ clearFlag = HistogramNone;
+ pos = 0;
+ }
+
+ int clearFlag; // Clear drawing zone with message.
+ int leftMost;
+ int rightMost;
+ int grabPoint;
+ int last;
+ int xMouseOver;
+ int yMouseOver;
+ int pos; // Position of animation during loading/calculation.
+
+ bool sixteenBits;
+ bool readOnlyMode;
+ bool guideVisible;
+
+ DColor colorGuide;
+
+ TQTimer *blinkTimer;
+
+ ImageCurves *curves; // Curves data instance.
+
+};
+
+CurvesWidget::CurvesWidget(int w, int h, TQWidget *parent, bool readOnly)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new CurvesWidgetPriv;
+
+ setup(w, h, readOnly);
+}
+
+CurvesWidget::CurvesWidget(int w, int h,
+ uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits,
+ TQWidget *parent, bool readOnly)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new CurvesWidgetPriv;
+
+ setup(w, h, readOnly);
+ updateData(i_data, i_w, i_h, i_sixteenBits);
+}
+
+CurvesWidget::~CurvesWidget()
+{
+ d->blinkTimer->stop();
+
+ if (m_imageHistogram)
+ delete m_imageHistogram;
+
+ if (d->curves)
+ delete d->curves;
+
+ delete d;
+}
+
+void CurvesWidget::setup(int w, int h, bool readOnly)
+{
+ d->readOnlyMode = readOnly;
+ d->curves = new ImageCurves(true);
+ m_channelType = ValueHistogram;
+ m_scaleType = LogScaleHistogram;
+ m_imageHistogram = 0;
+
+ setMouseTracking(true);
+ setPaletteBackgroundColor(colorGroup().background());
+ setMinimumSize(w, h);
+
+ d->blinkTimer = new TQTimer( this );
+
+ connect(d->blinkTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotBlinkTimerDone()));
+}
+
+void CurvesWidget::updateData(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits)
+{
+ stopHistogramComputation();
+
+ d->sixteenBits = i_sixteenBits;
+
+ // Remove old histogram data from memory.
+ if (m_imageHistogram)
+ delete m_imageHistogram;
+
+ // Calc new histogram data
+ m_imageHistogram = new ImageHistogram(i_data, i_w, i_h, i_sixteenBits, this);
+
+ if (d->curves)
+ delete d->curves;
+
+ d->curves = new ImageCurves(i_sixteenBits);
+ reset();
+}
+
+void CurvesWidget::reset()
+{
+ if (d->curves)
+ d->curves->curvesReset();
+
+ d->grabPoint = -1;
+ d->guideVisible = false;
+ repaint(false);
+}
+
+ImageCurves* CurvesWidget::curves() const
+{
+ return d->curves;
+}
+
+void CurvesWidget::setDataLoading()
+{
+ if (d->clearFlag != CurvesWidgetPriv::HistogramDataLoading)
+ {
+ setCursor(KCursor::waitCursor());
+ d->clearFlag = CurvesWidgetPriv::HistogramDataLoading;
+ d->pos = 0;
+ d->blinkTimer->start(100);
+ }
+}
+
+void CurvesWidget::setLoadingFailed()
+{
+ d->clearFlag = CurvesWidgetPriv::HistogramFailed;
+ d->pos = 0;
+ d->blinkTimer->stop();
+ repaint(false);
+ setCursor(KCursor::arrowCursor());
+}
+
+void CurvesWidget::setCurveGuide(const DColor& color)
+{
+ d->guideVisible = true;
+ d->colorGuide = color;
+ repaint(false);
+}
+
+void CurvesWidget::curveTypeChanged()
+{
+ switch (d->curves->getCurveType(m_channelType))
+ {
+ case ImageCurves::CURVE_SMOOTH:
+
+ // pick representative points from the curve and make them control points
+
+ for (int i = 0; i <= 8; i++)
+ {
+ int index = CLAMP(i * m_imageHistogram->getHistogramSegment()/8,
+ 0, m_imageHistogram->getHistogramSegment()-1);
+
+ d->curves->setCurvePoint( m_channelType,
+ i * 2, TQPoint(index,
+ d->curves->getCurveValue(m_channelType,
+ index)) );
+ }
+
+ d->curves->curvesCalculateCurve(m_channelType);
+ break;
+
+ case ImageCurves::CURVE_FREE:
+ break;
+ }
+
+ repaint(false);
+ emit signalCurvesChanged();
+}
+
+void CurvesWidget::customEvent(TQCustomEvent *event)
+{
+ if (!event) return;
+
+ ImageHistogram::EventData *ed = (ImageHistogram::EventData*) event->data();
+
+ if (!ed) return;
+
+ if (ed->starting)
+ {
+ setCursor(KCursor::waitCursor());
+ d->clearFlag = CurvesWidgetPriv::HistogramStarted;
+ d->blinkTimer->start(200);
+ repaint(false);
+ }
+ else
+ {
+ if (ed->success)
+ {
+ // Repaint histogram
+ d->clearFlag = CurvesWidgetPriv::HistogramCompleted;
+ d->blinkTimer->stop();
+ repaint(false);
+ setCursor(KCursor::arrowCursor());
+ }
+ else
+ {
+ d->clearFlag = CurvesWidgetPriv::HistogramFailed;
+ d->blinkTimer->stop();
+ repaint(false);
+ setCursor(KCursor::arrowCursor());
+ emit signalHistogramComputationFailed();
+ }
+ }
+
+ delete ed;
+}
+
+void CurvesWidget::stopHistogramComputation()
+{
+ if (m_imageHistogram)
+ m_imageHistogram->stopCalcHistogramValues();
+
+ d->blinkTimer->stop();
+ d->pos = 0;
+}
+
+void CurvesWidget::slotBlinkTimerDone()
+{
+ repaint(false);
+ d->blinkTimer->start(200);
+}
+
+void CurvesWidget::paintEvent(TQPaintEvent*)
+{
+ if (d->clearFlag == CurvesWidgetPriv::HistogramDataLoading ||
+ d->clearFlag == CurvesWidgetPriv::HistogramStarted)
+ {
+ // In first, we draw an animation.
+
+ int asize = 24;
+ TQPixmap anim(asize, asize);
+ TQPainter p2;
+ p2.begin(&anim, this);
+ p2.fillRect(0, 0, asize, asize, palette().active().background());
+ p2.translate(asize/2, asize/2);
+
+ d->pos = (d->pos + 10) % 360;
+ p2.setPen(TQPen(palette().active().text()));
+ p2.rotate(d->pos);
+ for ( int i=0 ; i<12 ; i++ )
+ {
+ p2.drawLine(asize/2-5, 0, asize/2-2, 0);
+ p2.rotate(30);
+ }
+ p2.end();
+
+ // ... and we render busy text.
+
+ TQPixmap pm(size());
+ TQPainter p1;
+ p1.begin(&pm, this);
+ p1.fillRect(0, 0, width(), height(), palette().active().background());
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+ p1.drawPixmap(width()/2 - asize /2, asize, anim);
+ p1.setPen(TQPen(palette().active().text()));
+
+ if (d->clearFlag == CurvesWidgetPriv::HistogramDataLoading)
+ p1.drawText(0, 0, width(), height(), TQt::AlignCenter,
+ i18n("Loading image..."));
+ else
+ p1.drawText(0, 0, width(), height(), TQt::AlignCenter,
+ i18n("Histogram calculation..."));
+
+ p1.end();
+ bitBlt(this, 0, 0, &pm);
+ return;
+ }
+
+ if (d->clearFlag == CurvesWidgetPriv::HistogramFailed)
+ {
+ TQPixmap pm(size());
+ TQPainter p1;
+ p1.begin(&pm, this);
+ p1.fillRect(0, 0, width(), height(), palette().active().background());
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+ p1.setPen(TQPen(palette().active().text()));
+ p1.drawText(0, 0, width(), height(), TQt::AlignCenter,
+ i18n("Histogram\ncalculation\nfailed."));
+ p1.end();
+ bitBlt(this, 0, 0, &pm);
+ return;
+ }
+
+ if (!m_imageHistogram) return;
+
+ int x, y;
+ int wWidth = width();
+ int wHeight = height();
+ double max;
+ class ImageHistogram *histogram = m_imageHistogram;
+
+ x = 0;
+ y = 0;
+ max = 0.0;
+
+ switch(m_channelType)
+ {
+ case CurvesWidget::GreenChannelHistogram: // Green channel.
+ max = histogram->getMaximum(ImageHistogram::GreenChannel);
+ break;
+
+ case CurvesWidget::BlueChannelHistogram: // Blue channel.
+ max = histogram->getMaximum(ImageHistogram::BlueChannel);
+ break;
+
+ case CurvesWidget::RedChannelHistogram: // Red channel.
+ max = histogram->getMaximum(ImageHistogram::RedChannel);
+ break;
+
+ case CurvesWidget::AlphaChannelHistogram: // Alpha channel.
+ max = histogram->getMaximum(ImageHistogram::AlphaChannel);
+ break;
+
+ case CurvesWidget::ValueHistogram: // Luminosity.
+ max = histogram->getMaximum(ImageHistogram::ValueChannel);
+ break;
+ }
+
+ switch (m_scaleType)
+ {
+ case CurvesWidget::LinScaleHistogram:
+ break;
+
+ case CurvesWidget::LogScaleHistogram:
+ if (max > 0.0)
+ max = log (max);
+ else
+ max = 1.0;
+ break;
+ }
+
+ // Drawing selection or all histogram values.
+ // A TQPixmap is used for enable the double buffering.
+
+ TQPixmap pm(size());
+ TQPainter p1;
+ p1.begin(&pm, this);
+
+ int curvePrevVal = 0;
+
+ for (x = 0 ; x < wWidth ; x++)
+ {
+ double value = 0.0;
+ int i, j;
+ int curveVal;
+
+ i = (x * histogram->getHistogramSegment()) / wWidth;
+ j = ((x + 1) * histogram->getHistogramSegment()) / wWidth;
+
+ curveVal = d->curves->getCurveValue(m_channelType, i);
+
+ do
+ {
+ double v = 0.0;
+
+ switch(m_channelType)
+ {
+ case CurvesWidget::RedChannelHistogram: // Red channel.
+ v = histogram->getValue(ImageHistogram::RedChannel, i++);
+ break;
+
+ case CurvesWidget::GreenChannelHistogram: // Green channel.
+ v = histogram->getValue(ImageHistogram::GreenChannel, i++);
+ break;
+
+ case CurvesWidget::BlueChannelHistogram: // Blue channel.
+ v = histogram->getValue(ImageHistogram::BlueChannel, i++);
+ break;
+
+ case CurvesWidget::AlphaChannelHistogram: // Alpha channel.
+ v = histogram->getValue(ImageHistogram::AlphaChannel, i++);
+ break;
+
+ case CurvesWidget::ValueHistogram: // Luminosity.
+ v = histogram->getValue(ImageHistogram::ValueChannel, i++);
+ break;
+ }
+
+ if (v > value)
+ value = v;
+ }
+ while (i < j);
+
+ switch (m_scaleType)
+ {
+ case CurvesWidget::LinScaleHistogram:
+ y = (int) ((wHeight * value) / max);
+ break;
+
+ case CurvesWidget::LogScaleHistogram:
+ if (value <= 0.0) value = 1.0;
+ y = (int) ((wHeight * log (value)) / max);
+ break;
+
+ default:
+ y = 0;
+ break;
+ }
+
+ // Drawing histogram
+
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - y);
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - y, x, 0);
+
+ // Drawing curves.
+
+ p1.setPen(TQPen(palette().active().link(), 2, TQt::SolidLine));
+ p1.drawLine(x - 1, wHeight - ((curvePrevVal * wHeight) / histogram->getHistogramSegment()),
+ x, wHeight - ((curveVal * wHeight) / histogram->getHistogramSegment()));
+
+ curvePrevVal = curveVal;
+ }
+
+ // Drawing curves points.
+
+ if (!d->readOnlyMode && d->curves->getCurveType(m_channelType) == ImageCurves::CURVE_SMOOTH)
+ {
+ p1.setPen(TQPen(TQt::red, 3, TQt::SolidLine));
+
+ for (int p = 0 ; p < 17 ; p++)
+ {
+ TQPoint curvePoint = d->curves->getCurvePoint(m_channelType, p);
+
+ if (curvePoint.x() >= 0)
+ {
+ p1.drawEllipse( ((curvePoint.x() * wWidth) / histogram->getHistogramSegment()) - 2,
+ wHeight - 2 - ((curvePoint.y() * wHeight) / histogram->getHistogramSegment()),
+ 4, 4 );
+ }
+ }
+ }
+
+ // Drawing black/middle/highlight tone grid separators.
+
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(wWidth/4, 0, wWidth/4, wHeight);
+ p1.drawLine(wWidth/2, 0, wWidth/2, wHeight);
+ p1.drawLine(3*wWidth/4, 0, 3*wWidth/4, wHeight);
+ p1.drawLine(0, wHeight/4, wWidth, wHeight/4);
+ p1.drawLine(0, wHeight/2, wWidth, wHeight/2);
+ p1.drawLine(0, 3*wHeight/4, wWidth, 3*wHeight/4);
+
+ // Drawing X,Y point position dragged by mouse over widget.
+
+ p1.setPen(TQPen(TQt::red, 1, TQt::DotLine));
+
+ if (d->xMouseOver != -1 && d->yMouseOver != -1)
+ {
+ TQString string = i18n("x:%1\ny:%2").arg(d->xMouseOver).arg(d->yMouseOver);
+ TQFontMetrics fontMt(string);
+ TQRect rect = fontMt.boundingRect(0, 0, wWidth, wHeight, 0, string);
+ rect.moveRight(wWidth);
+ rect.moveBottom(wHeight);
+ p1.drawText(rect, TQt::AlignLeft||TQt::AlignTop, string);
+ }
+
+ // Drawing color guide.
+
+ int guidePos;
+
+ if (d->guideVisible)
+ {
+ switch(m_channelType)
+ {
+ case CurvesWidget::RedChannelHistogram:
+ guidePos = d->colorGuide.red();
+ break;
+
+ case CurvesWidget::GreenChannelHistogram:
+ guidePos = d->colorGuide.green();
+ break;
+
+ case CurvesWidget::BlueChannelHistogram:
+ guidePos = d->colorGuide.blue();
+ break;
+
+ case CurvesWidget::ValueHistogram:
+ guidePos = TQMAX(TQMAX(d->colorGuide.red(), d->colorGuide.green()), d->colorGuide.blue());
+ break;
+
+ default: // Alpha.
+ guidePos = -1;
+ break;
+ }
+
+ if (guidePos != -1)
+ {
+ int xGuide = (guidePos * wWidth) / histogram->getHistogramSegment();
+ p1.drawLine(xGuide, 0, xGuide, wHeight);
+
+ TQString string = i18n("x:%1").arg(guidePos);
+ TQFontMetrics fontMt( string );
+ TQRect rect = fontMt.boundingRect(0, 0, wWidth, wHeight, 0, string);
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ rect.moveTop(1);
+
+ if (xGuide < wWidth/2)
+ {
+ rect.moveLeft(xGuide);
+ p1.fillRect(rect, TQBrush(TQColor(250, 250, 255)));
+ p1.drawRect(rect);
+ rect.moveLeft(xGuide+3);
+ p1.drawText(rect, TQt::AlignLeft, string);
+ }
+ else
+ {
+ rect.moveRight(xGuide);
+ p1.fillRect(rect, TQBrush(TQColor(250, 250, 255)));
+ p1.drawRect(rect);
+ rect.moveRight(xGuide-3);
+ p1.drawText(rect, TQt::AlignRight, string);
+ }
+ }
+ }
+
+ // Drawing frame.
+
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+
+ p1.end();
+ bitBlt(this, 0, 0, &pm);
+}
+
+void CurvesWidget::mousePressEvent(TQMouseEvent *e)
+{
+ if (d->readOnlyMode || !m_imageHistogram) return;
+
+ int i;
+ int closest_point;
+ int distance;
+
+ if (e->button() != TQt::LeftButton || d->clearFlag == CurvesWidgetPriv::HistogramStarted)
+ return;
+
+ int x = CLAMP((int)(e->pos().x() *
+ ((float)(m_imageHistogram->getHistogramSegment()-1) / (float)width())),
+ 0, m_imageHistogram->getHistogramSegment()-1 );
+ int y = CLAMP((int)(e->pos().y() *
+ ((float)(m_imageHistogram->getHistogramSegment()-1) / (float)height())),
+ 0, m_imageHistogram->getHistogramSegment()-1 );
+
+ distance = 65536;
+
+ for (i = 0, closest_point = 0 ; i < 17 ; i++)
+ {
+ int xcurvepoint = d->curves->getCurvePointX(m_channelType, i);
+
+ if (xcurvepoint != -1)
+ {
+ if (abs (x - xcurvepoint) < distance)
+ {
+ distance = abs (x - xcurvepoint);
+ closest_point = i;
+ }
+ }
+ }
+
+ int delta = m_imageHistogram->getHistogramSegment()/16;
+ if (distance > 8)
+ closest_point = (x + delta/2) / delta;
+
+ setCursor(KCursor::crossCursor());
+
+ switch(d->curves->getCurveType(m_channelType))
+ {
+ case ImageCurves::CURVE_SMOOTH:
+ {
+ // Determine the leftmost and rightmost points.
+
+ d->leftMost = -1;
+
+ for (i = closest_point - 1 ; i >= 0 ; i--)
+ {
+ if (d->curves->getCurvePointX(m_channelType, i) != -1)
+ {
+ d->leftMost = d->curves->getCurvePointX(m_channelType, i);
+ break;
+ }
+ }
+
+ d->rightMost = m_imageHistogram->getHistogramSegment();
+
+ for (i = closest_point + 1 ; i < 17 ; i++)
+ {
+ if (d->curves->getCurvePointX(m_channelType, i) != -1)
+ {
+ d->rightMost = d->curves->getCurvePointX(m_channelType, i);
+ break;
+ }
+ }
+
+ d->grabPoint = closest_point;
+ d->curves->setCurvePoint(m_channelType, d->grabPoint,
+ TQPoint(x, m_imageHistogram->getHistogramSegment() - y));
+
+ break;
+ }
+
+ case ImageCurves::CURVE_FREE:
+ {
+
+ d->curves->setCurveValue(m_channelType, x, m_imageHistogram->getHistogramSegment() - y);
+ d->grabPoint = x;
+ d->last = y;
+ break;
+ }
+ }
+
+ d->curves->curvesCalculateCurve(m_channelType);
+ repaint(false);
+}
+
+void CurvesWidget::mouseReleaseEvent(TQMouseEvent *e)
+{
+ if (d->readOnlyMode || !m_imageHistogram) return;
+
+ if (e->button() != TQt::LeftButton || d->clearFlag == CurvesWidgetPriv::HistogramStarted)
+ return;
+
+ setCursor(KCursor::arrowCursor());
+ d->grabPoint = -1;
+ d->curves->curvesCalculateCurve(m_channelType);
+ repaint(false);
+ emit signalCurvesChanged();
+}
+
+void CurvesWidget::mouseMoveEvent(TQMouseEvent *e)
+{
+ if (d->readOnlyMode || !m_imageHistogram) return;
+
+ int i;
+ int closest_point;
+ int x1, x2, y1, y2;
+ int distance;
+
+ if (d->clearFlag == CurvesWidgetPriv::HistogramStarted)
+ return;
+
+ int x = CLAMP( (int)(e->pos().x()*((float)(m_imageHistogram->getHistogramSegment()-1)/(float)width())),
+ 0, m_imageHistogram->getHistogramSegment()-1 );
+ int y = CLAMP( (int)(e->pos().y()*((float)(m_imageHistogram->getHistogramSegment()-1)/(float)height())),
+ 0, m_imageHistogram->getHistogramSegment()-1 );
+
+ distance = 65536;
+
+ for (i = 0, closest_point = 0 ; i < 17 ; i++)
+ {
+ if (d->curves->getCurvePointX(m_channelType, i) != -1)
+ {
+ if (abs (x - d->curves->getCurvePointX(m_channelType, i)) < distance)
+ {
+ distance = abs (x - d->curves->getCurvePointX(m_channelType, i));
+ closest_point = i;
+ }
+ }
+ }
+
+ int delta = m_imageHistogram->getHistogramSegment()/16;
+ if (distance > 8)
+ closest_point = (x + delta/2) / delta;
+
+ switch ( d->curves->getCurveType(m_channelType) )
+ {
+ case ImageCurves::CURVE_SMOOTH:
+ {
+ if (d->grabPoint == -1) // If no point is grabbed...
+ {
+ if ( d->curves->getCurvePointX(m_channelType, closest_point) != -1 )
+ setCursor(KCursor::arrowCursor());
+ else
+ setCursor(KCursor::crossCursor());
+ }
+ else // Else, drag the grabbed point
+ {
+ setCursor(KCursor::crossCursor());
+
+ d->curves->setCurvePointX(m_channelType, d->grabPoint, -1);
+
+ if (x > d->leftMost && x < d->rightMost)
+ {
+ closest_point = (x + delta/2) / delta;
+
+ if (d->curves->getCurvePointX(m_channelType, closest_point) == -1)
+ d->grabPoint = closest_point;
+
+ d->curves->setCurvePoint(m_channelType, d->grabPoint,
+ TQPoint(x, m_imageHistogram->getHistogramSegment()-1 - y));
+ }
+
+ d->curves->curvesCalculateCurve(m_channelType);
+ emit signalCurvesChanged();
+ }
+
+ break;
+ }
+
+ case ImageCurves::CURVE_FREE:
+ {
+ if (d->grabPoint != -1)
+ {
+ if (d->grabPoint > x)
+ {
+ x1 = x;
+ x2 = d->grabPoint;
+ y1 = y;
+ y2 = d->last;
+ }
+ else
+ {
+ x1 = d->grabPoint;
+ x2 = x;
+ y1 = d->last;
+ y2 = y;
+ }
+
+ if (x2 != x1)
+ {
+ for (i = x1 ; i <= x2 ; i++)
+ d->curves->setCurveValue(m_channelType, i,
+ m_imageHistogram->getHistogramSegment()-1 - (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1)));
+ }
+ else
+ {
+ d->curves->setCurveValue(m_channelType, x, m_imageHistogram->getHistogramSegment()-1 - y);
+ }
+
+ d->grabPoint = x;
+ d->last = y;
+ }
+
+ emit signalCurvesChanged();
+
+ break;
+ }
+ }
+
+ d->xMouseOver = x;
+ d->yMouseOver = m_imageHistogram->getHistogramSegment()-1 - y;
+ emit signalMouseMoved(d->xMouseOver, d->yMouseOver);
+ repaint(false);
+}
+
+void CurvesWidget::leaveEvent(TQEvent*)
+{
+ d->xMouseOver = -1;
+ d->yMouseOver = -1;
+ emit signalMouseMoved(d->xMouseOver, d->yMouseOver);
+ repaint(false);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/common/curveswidget.h b/src/libs/widgets/common/curveswidget.h
new file mode 100644
index 00000000..c9cfed4a
--- /dev/null
+++ b/src/libs/widgets/common/curveswidget.h
@@ -0,0 +1,132 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-01
+ * Description : a widget to draw histogram curves
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CURVESWIDGET_H
+#define CURVESWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "dcolor.h"
+#include "digikam_export.h"
+
+class TQCustomEvent;
+
+namespace Digikam
+{
+
+class ImageHistogram;
+class ImageCurves;
+class CurvesWidgetPriv;
+
+class DIGIKAM_EXPORT CurvesWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ enum HistogramType
+ {
+ ValueHistogram = 0, // Luminosity.
+ RedChannelHistogram, // Red channel.
+ GreenChannelHistogram, // Green channel.
+ BlueChannelHistogram, // Blue channel.
+ AlphaChannelHistogram, // Alpha channel.
+ };
+
+ enum HistogramScale
+ {
+ LinScaleHistogram=0, // Linear scale.
+ LogScaleHistogram // Logarithmic scale.
+ };
+
+public:
+
+ CurvesWidget(int w, int h, TQWidget *parent, bool readOnly=false);
+
+ CurvesWidget(int w, int h, // Widget size.
+ uchar *i_data, uint i_w, uint i_h, // Full image info.
+ bool i_sixteenBits, // 8 or 16 bits image.
+ TQWidget *parent=0, // Parent widget instance.
+ bool readOnly=false); // If true : widget with full edition mode capabilities.
+ // If false : display curve data only without edition.
+
+ ~CurvesWidget();
+
+ void setup(int w, int h, bool readOnly);
+ void updateData(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits);
+
+ // Stop current histogram computations.
+ void stopHistogramComputation();
+
+ void setDataLoading();
+ void setLoadingFailed();
+
+ void reset();
+ void curveTypeChanged();
+ void setCurveGuide(const DColor& color);
+
+ ImageCurves* curves() const;
+
+public:
+
+ int m_channelType; // Channel type to draw.
+ int m_scaleType; // Scale to use for drawing.
+
+ ImageHistogram *m_imageHistogram;
+
+signals:
+
+ void signalMouseMoved( int x, int y );
+ void signalCurvesChanged();
+ void signalHistogramComputationDone();
+ void signalHistogramComputationFailed();
+
+protected slots:
+
+ void slotBlinkTimerDone();
+
+protected:
+
+ void paintEvent(TQPaintEvent*);
+ void mousePressEvent(TQMouseEvent*);
+ void mouseReleaseEvent(TQMouseEvent*);
+ void mouseMoveEvent(TQMouseEvent*);
+ void leaveEvent(TQEvent*);
+
+private:
+
+ void customEvent(TQCustomEvent *event);
+
+private:
+
+ CurvesWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* CURVESWIDGET_H */
diff --git a/src/libs/widgets/common/dcursortracker.cpp b/src/libs/widgets/common/dcursortracker.cpp
new file mode 100644
index 00000000..bb9490cb
--- /dev/null
+++ b/src/libs/widgets/common/dcursortracker.cpp
@@ -0,0 +1,109 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-23-03
+ * Description : a tool tip widget witch follow cursor movements
+ * Tool tip content is displayed without delay.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqevent.h>
+#include <tqtooltip.h>
+
+// Local includes.
+
+#include "dcursortracker.h"
+
+namespace Digikam
+{
+
+DCursorTracker::DCursorTracker(const TQString& txt, TQWidget *parent)
+ : TQLabel(txt, 0, "", WX11BypassWM)
+{
+ parent->setMouseTracking(true);
+ parent->installEventFilter(this);
+ setEnable(true);
+}
+
+/**
+ * Overload to make sure the widget size is correct
+ */
+void DCursorTracker::setText(const TQString& txt)
+{
+ TQLabel::setText(txt);
+ adjustSize();
+}
+
+void DCursorTracker::setEnable(bool b)
+{
+ m_enable = b;
+}
+
+bool DCursorTracker::eventFilter(TQObject *object, TQEvent *e)
+{
+ TQWidget *widget = static_cast<TQWidget*>(object);
+
+ switch (e->type())
+ {
+ case TQEvent::MouseMove:
+ {
+ TQMouseEvent *event = static_cast<TQMouseEvent*>(e);
+ if (m_enable && (widget->rect().contains(event->pos()) ||
+ (event->stateAfter() & TQt::LeftButton)))
+ {
+ show();
+ TQPoint p = widget->mapToGlobal(TQPoint(widget->width()/2, 0));
+ move(p.x()-width()/2, p.y()-height());
+ }
+ else
+ {
+ hide();
+ }
+ break;
+ }
+
+ case TQEvent::MouseButtonRelease:
+ {
+ TQMouseEvent* event = static_cast<TQMouseEvent*>(e);
+ if ( !widget->rect().contains(event->pos()) )
+ {
+ hide();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+DTipTracker::DTipTracker(const TQString& txt, TQWidget *parent)
+ : DCursorTracker(txt, parent)
+{
+ setPalette(TQToolTip::palette());
+ setFrameStyle(TQFrame::Plain | TQFrame::Box);
+ setLineWidth(1);
+ setAlignment(AlignAuto | AlignTop);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/dcursortracker.h b/src/libs/widgets/common/dcursortracker.h
new file mode 100644
index 00000000..33d322e1
--- /dev/null
+++ b/src/libs/widgets/common/dcursortracker.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-23-03
+ * Description : a tool tip widget witch follow cursor movements
+ * Tool tip content is displayed without delay.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DCURSOR_TRACKER_H
+#define DCURSOR_TRACKER_H
+
+// TQt includes.
+
+#include <tqlabel.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+/**
+ * This class implements a decoration-less window which will follow the cursor
+ * when it's over a specified widget.
+ */
+class DIGIKAM_EXPORT DCursorTracker : public TQLabel
+{
+
+public:
+
+ DCursorTracker(const TQString& txt, TQWidget *parent);
+
+ void setText(const TQString& txt);
+ void setEnable(bool b);
+
+protected:
+
+ bool eventFilter(TQObject*, TQEvent*);
+
+private:
+
+ bool m_enable;
+};
+
+
+/**
+ * A specialized CursorTracker class, which looks like a tool tip.
+ */
+class DTipTracker : public DCursorTracker
+{
+
+public:
+
+ DTipTracker(const TQString& txt, TQWidget *parent);
+};
+
+} // namespace Digikam
+
+#endif /* DCURSOR_TRACKER_H */
diff --git a/src/libs/widgets/common/dlogoaction.cpp b/src/libs/widgets/common/dlogoaction.cpp
new file mode 100644
index 00000000..60cfd77f
--- /dev/null
+++ b/src/libs/widgets/common/dlogoaction.cpp
@@ -0,0 +1,96 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-27-08
+ * Description : an tool bar action object to display logo
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtooltip.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kurllabel.h>
+#include <tdetoolbar.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "dlogoaction.h"
+
+namespace Digikam
+{
+
+DLogoAction::DLogoAction(TQObject* parent, const char* name)
+ : TDEAction(parent, name)
+{
+ setText("digikam.org");
+ setIcon("digikam");
+}
+
+int DLogoAction::plug(TQWidget *widget, int index)
+{
+ if (kapp && !kapp->authorizeTDEAction(name()))
+ return -1;
+
+ if ( widget->inherits( "TDEToolBar" ) )
+ {
+ TDEToolBar *bar = (TDEToolBar *)widget;
+ int id = getToolButtonID();
+ KURLLabel *pixmapLogo = new KURLLabel(Digikam::webProjectUrl(), TQString(), bar);
+ pixmapLogo->setMargin(0);
+ pixmapLogo->setScaledContents(false);
+ pixmapLogo->setSizePolicy(TQSizePolicy(TQSizePolicy::Minimum, TQSizePolicy::Minimum));
+ TQToolTip::add(pixmapLogo, i18n("Visit digiKam project website"));
+ TDEGlobal::dirs()->addResourceType("banner-digikam", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("banner-digikam", "banner-digikam.png");
+ pixmapLogo->setPixmap(TQPixmap( directory + "banner-digikam.png" ));
+ pixmapLogo->setFocusPolicy(TQWidget::NoFocus);
+
+ bar->insertWidget(id, pixmapLogo->width(), pixmapLogo);
+ bar->alignItemRight(id);
+
+ addContainer(bar, id);
+
+ connect(bar, TQ_SIGNAL(destroyed()),
+ this, TQ_SLOT(slotDestroyed()));
+
+ connect(pixmapLogo, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(slotProcessURL(const TQString&)));
+
+ return containerCount() - 1;
+ }
+
+ int containerId = TDEAction::plug( widget, index );
+
+ return containerId;
+}
+
+void DLogoAction::slotProcessURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/dlogoaction.h b/src/libs/widgets/common/dlogoaction.h
new file mode 100644
index 00000000..11a0052d
--- /dev/null
+++ b/src/libs/widgets/common/dlogoaction.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-27-08
+ * Description : an tool bar action object to display logo
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DLOGO_ACTION_H
+#define DLOGO_ACTION_H
+
+// KDE includes.
+
+#include <tdeaction.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DLogoAction : public TDEAction
+{
+ TQ_OBJECT
+
+
+public:
+
+ DLogoAction(TQObject* parent, const char* name=0);
+
+ virtual int plug(TQWidget *widget, int index=-1);
+
+private slots:
+
+ void slotProcessURL(const TQString&);
+};
+
+} // namespace Digikam
+
+#endif /* DLOGO_ACTION_H */
diff --git a/src/libs/widgets/common/dpopupmenu.cpp b/src/libs/widgets/common/dpopupmenu.cpp
new file mode 100644
index 00000000..f41404ce
--- /dev/null
+++ b/src/libs/widgets/common/dpopupmenu.cpp
@@ -0,0 +1,197 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-11-11
+ * Description : a popup menu with a decorative graphic banner
+ * at the left border.
+ *
+ * Copyright (C) 1996-2000 the kicker authors.
+ * Copyright (C) 2005 Mark Kretschmann <markey@web.de>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqstyle.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kiconeffect.h>
+#include <tdeapplication.h>
+#include <kstandarddirs.h>
+#include <tdeaboutdata.h>
+
+// Local includes.
+
+#include "dpopupmenu.h"
+
+namespace Digikam
+{
+
+static TQImage s_dpopupmenu_sidePixmap;
+static TQColor s_dpopupmenu_sidePixmapColor;
+
+DPopupMenu::DPopupMenu(TQWidget* parent, const char* name)
+ : TDEPopupMenu(parent, name)
+{
+ // Must be initialized so that we know the size on first invocation
+ if ( s_dpopupmenu_sidePixmap.isNull() )
+ generateSidePixmap();
+}
+
+DPopupMenu::~DPopupMenu()
+{
+}
+
+void DPopupMenu::generateSidePixmap()
+{
+ const TQColor newColor = calcPixmapColor();
+
+ if ( newColor != s_dpopupmenu_sidePixmapColor )
+ {
+ s_dpopupmenu_sidePixmapColor = newColor;
+
+ if (TDEApplication::kApplication()->aboutData()->appName() == TQString("digikam"))
+ s_dpopupmenu_sidePixmap.load( locate( "data","digikam/data/menusidepixmap.png" ) );
+ else
+ s_dpopupmenu_sidePixmap.load( locate( "data","showfoto/menusidepixmap.png" ) );
+
+ TDEIconEffect::colorize(s_dpopupmenu_sidePixmap, newColor, 1.0);
+ }
+}
+
+int DPopupMenu::sidePixmapWidth() const
+{
+ return s_dpopupmenu_sidePixmap.width();
+}
+
+TQRect DPopupMenu::sideImageRect() const
+{
+ return TQStyle::visualRect(TQRect(frameWidth(), frameWidth(),
+ s_dpopupmenu_sidePixmap.width(),
+ height() - 2*frameWidth()),
+ this);
+}
+
+TQColor DPopupMenu::calcPixmapColor()
+{
+ TQColor color;
+ TQColor activeTitle = TQApplication::palette().active().background();
+ TQColor inactiveTitle = TQApplication::palette().inactive().background();
+
+ // figure out which color is most suitable for recoloring to
+ int h1, s1, v1, h2, s2, v2, h3, s3, v3;
+ activeTitle.hsv(&h1, &s1, &v1);
+ inactiveTitle.hsv(&h2, &s2, &v2);
+ TQApplication::palette().active().background().hsv(&h3, &s3, &v3);
+
+ if ( (kAbs(h1-h3)+kAbs(s1-s3)+kAbs(v1-v3) < kAbs(h2-h3)+kAbs(s2-s3)+kAbs(v2-v3)) &&
+ ((kAbs(h1-h3)+kAbs(s1-s3)+kAbs(v1-v3) < 32) || (s1 < 32)) && (s2 > s1))
+ color = inactiveTitle;
+ else
+ color = activeTitle;
+
+ // limit max/min brightness
+ int r, g, b;
+ color.rgb(&r, &g, &b);
+ int gray = tqGray(r, g, b);
+ if (gray > 180)
+ {
+ r = (r - (gray - 180) < 0 ? 0 : r - (gray - 180));
+ g = (g - (gray - 180) < 0 ? 0 : g - (gray - 180));
+ b = (b - (gray - 180) < 0 ? 0 : b - (gray - 180));
+ }
+ else if (gray < 76)
+ {
+ r = (r + (76 - gray) > 255 ? 255 : r + (76 - gray));
+ g = (g + (76 - gray) > 255 ? 255 : g + (76 - gray));
+ b = (b + (76 - gray) > 255 ? 255 : b + (76 - gray));
+ }
+ color.setRgb(r, g, b);
+
+ return color;
+}
+
+void DPopupMenu::setMinimumSize(const TQSize & s)
+{
+ TDEPopupMenu::setMinimumSize(s.width() + s_dpopupmenu_sidePixmap.width(), s.height());
+}
+
+void DPopupMenu::setMaximumSize(const TQSize & s)
+{
+ TDEPopupMenu::setMaximumSize(s.width() + s_dpopupmenu_sidePixmap.width(), s.height());
+}
+
+void DPopupMenu::setMinimumSize(int w, int h)
+{
+ TDEPopupMenu::setMinimumSize(w + s_dpopupmenu_sidePixmap.width(), h);
+}
+
+void DPopupMenu::setMaximumSize(int w, int h)
+{
+ TDEPopupMenu::setMaximumSize(w + s_dpopupmenu_sidePixmap.width(), h);
+}
+
+void DPopupMenu::resizeEvent(TQResizeEvent * e)
+{
+ TDEPopupMenu::resizeEvent(e);
+
+ setFrameRect(TQStyle::visualRect(TQRect(s_dpopupmenu_sidePixmap.width(), 0,
+ width() - s_dpopupmenu_sidePixmap.width(), height()),
+ this ) );
+}
+
+//Workaround TQt3.3.x sizing bug, by ensuring we're always wide enough.
+void DPopupMenu::resize(int width, int height)
+{
+ width = kMax(width, maximumSize().width());
+ TDEPopupMenu::resize(width, height);
+}
+
+void DPopupMenu::paintEvent(TQPaintEvent* e)
+{
+ generateSidePixmap();
+
+ TQPainter p( this );
+
+ TQRect r = sideImageRect();
+ r.setTop(r.bottom()-s_dpopupmenu_sidePixmap.height()+1);
+ if ( r.intersects( e->rect() ) )
+ {
+ TQRect drawRect = r.intersect(e->rect()).intersect(sideImageRect());
+ TQRect pixRect = drawRect;
+ pixRect.moveBy(-r.left(), -r.top());
+ p.drawImage(drawRect.topLeft(), s_dpopupmenu_sidePixmap, pixRect);
+ }
+
+ p.setClipRegion(e->region());
+
+ //NOTE: The order is important here. drawContents() must be called before drawPrimitive(),
+ // otherwise we get rendering glitches.
+
+ drawContents(&p);
+
+ style().drawPrimitive(TQStyle::PE_PanelPopup, &p,
+ TQRect(0, 0, width(), height()),
+ colorGroup(), TQStyle::Style_Default,
+ TQStyleOption( frameWidth(), 0));
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/dpopupmenu.h b/src/libs/widgets/common/dpopupmenu.h
new file mode 100644
index 00000000..f3576580
--- /dev/null
+++ b/src/libs/widgets/common/dpopupmenu.h
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-11-11
+ * Description : a popup menu with a decorative graphic banner
+ * at the left border.
+ *
+ * Copyright (C) 1996-2000 the kicker authors.
+ * Copyright (C) 2005 Mark Kretschmann <markey@web.de>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DPOPUPMENU_H
+#define DPOPUPMENU_H
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqimage.h>
+#include <tqrect.h>
+
+// KDE includes.
+
+#include <tdepopupmenu.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQSize;
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT DPopupMenu : public TDEPopupMenu
+{
+
+public:
+
+ DPopupMenu(TQWidget *parent=0, const char *name=0);
+ ~DPopupMenu();
+
+ int sidePixmapWidth() const;
+
+private:
+
+ /** Loads and prepares the sidebar image */
+ void generateSidePixmap();
+
+ /** Returns the available size for the image */
+ TQRect sideImageRect() const;
+
+ /** Calculates a color that matches the current colorscheme */
+ TQColor calcPixmapColor();
+
+ void setMinimumSize(const TQSize& s);
+ void setMaximumSize(const TQSize& s);
+ void setMinimumSize(int w, int h);
+ void setMaximumSize(int w, int h);
+
+ void resizeEvent(TQResizeEvent* e);
+ void resize(int width, int height);
+
+ void paintEvent(TQPaintEvent* e);
+};
+
+} // namespace Digikam
+
+#endif /*DPOPUPMENU_H*/
diff --git a/src/libs/widgets/common/filesaveoptionsbox.cpp b/src/libs/widgets/common/filesaveoptionsbox.cpp
new file mode 100644
index 00000000..26985fb5
--- /dev/null
+++ b/src/libs/widgets/common/filesaveoptionsbox.cpp
@@ -0,0 +1,182 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : a stack of widgets to set image file save
+ * options into image editor.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqwidget.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <kimageio.h>
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <knuminput.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <tdefiledialog.h>
+
+// Local includes.
+
+#include "jpegsettings.h"
+#include "pngsettings.h"
+#include "tiffsettings.h"
+#include "jp2ksettings.h"
+#include "filesaveoptionsbox.h"
+#include "filesaveoptionsbox.moc"
+
+namespace Digikam
+{
+
+class FileSaveOptionsBoxPriv
+{
+
+public:
+
+ FileSaveOptionsBoxPriv()
+ {
+ noneOptions = 0;
+ JPEGOptions = 0;
+ PNGOptions = 0;
+ TIFFOptions = 0;
+ JPEG2000Options = 0;
+ }
+
+ TQWidget *noneOptions;
+
+ TQGridLayout *noneGrid;
+
+ TQLabel *labelNone;
+
+ JPEGSettings *JPEGOptions;
+
+ PNGSettings *PNGOptions;
+
+ TIFFSettings *TIFFOptions;
+
+ JP2KSettings *JPEG2000Options;
+};
+
+FileSaveOptionsBox::FileSaveOptionsBox(TQWidget *parent)
+ : TQWidgetStack(parent, 0, TQt::WDestructiveClose)
+{
+ d = new FileSaveOptionsBoxPriv;
+
+ //-- NONE Settings ------------------------------------------------------
+
+ d->noneOptions = new TQWidget(this);
+ d->noneGrid = new TQGridLayout(d->noneOptions, 1, 1, KDialog::spacingHint());
+ d->labelNone = new TQLabel(i18n("No options available"), d->noneOptions);
+ d->noneGrid->addMultiCellWidget(d->labelNone, 0, 0, 0, 1);
+
+ //-- JPEG Settings ------------------------------------------------------
+
+ d->JPEGOptions = new JPEGSettings(this);
+
+ //-- PNG Settings -------------------------------------------------------
+
+ d->PNGOptions = new PNGSettings(this);
+
+ //-- TIFF Settings ------------------------------------------------------
+
+ d->TIFFOptions = new TIFFSettings(this);
+
+ //-- JPEG 2000 Settings -------------------------------------------------
+
+ d->JPEG2000Options = new JP2KSettings(this);
+
+ //-----------------------------------------------------------------------
+
+ addWidget(d->noneOptions, DImg::NONE);
+ addWidget(d->JPEGOptions, DImg::JPEG);
+ addWidget(d->PNGOptions, DImg::PNG);
+ addWidget(d->TIFFOptions, DImg::TIFF);
+ addWidget(d->JPEG2000Options, DImg::JP2K);
+
+ //-----------------------------------------------------------------------
+
+ readSettings();
+}
+
+FileSaveOptionsBox::~FileSaveOptionsBox()
+{
+ delete d;
+}
+
+void FileSaveOptionsBox::slotImageFileSelected(const TQString& file)
+{
+ TQString format = TQImageIO::imageFormat(file);
+ toggleFormatOptions(format);
+}
+
+void FileSaveOptionsBox::slotImageFileFormatChanged(const TQString& filter)
+{
+ TQString format = KImageIO::typeForMime(filter).upper();
+ toggleFormatOptions(format);
+}
+
+void FileSaveOptionsBox::toggleFormatOptions(const TQString& format)
+{
+ if (format == TQString("JPEG"))
+ raiseWidget(DImg::JPEG);
+ else if (format == TQString("PNG"))
+ raiseWidget(DImg::PNG);
+ else if (format == TQString("TIFF"))
+ raiseWidget(DImg::TIFF);
+ else if (format == TQString("JP2"))
+ raiseWidget(DImg::JP2K);
+ else
+ raiseWidget(DImg::NONE);
+}
+
+void FileSaveOptionsBox::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ config->writeEntry("JPEGCompression", d->JPEGOptions->getCompressionValue());
+ config->writeEntry("JPEGSubSampling", d->JPEGOptions->getSubSamplingValue());
+ config->writeEntry("PNGCompression", d->PNGOptions->getCompressionValue());
+ config->writeEntry("TIFFCompression", d->TIFFOptions->getCompression());
+ config->writeEntry("JPEG2000Compression", d->JPEG2000Options->getCompressionValue());
+ config->writeEntry("JPEG2000LossLess", d->JPEG2000Options->getLossLessCompression());
+ config->sync();
+}
+
+void FileSaveOptionsBox::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ d->JPEGOptions->setCompressionValue( config->readNumEntry("JPEGCompression", 75) );
+ d->JPEGOptions->setSubSamplingValue( config->readNumEntry("JPEGSubSampling", 1) ); // Medium subsampling
+ d->PNGOptions->setCompressionValue( config->readNumEntry("PNGCompression", 9) );
+ d->TIFFOptions->setCompression(config->readBoolEntry("TIFFCompression", false));
+ d->JPEG2000Options->setCompressionValue( config->readNumEntry("JPEG2000Compression", 75) );
+ d->JPEG2000Options->setLossLessCompression( config->readBoolEntry("JPEG2000LossLess", true) );
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/filesaveoptionsbox.h b/src/libs/widgets/common/filesaveoptionsbox.h
new file mode 100644
index 00000000..9d9f84fc
--- /dev/null
+++ b/src/libs/widgets/common/filesaveoptionsbox.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-02
+ * Description : a stack of widgets to set image file save
+ * options into image editor.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef FILESAVEOPTIONSBOX_H
+#define FILESAVEOPTIONSBOX_H
+
+// KDE includes.
+
+#include <tqwidgetstack.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class FileSaveOptionsBoxPriv;
+
+class DIGIKAM_EXPORT FileSaveOptionsBox : public TQWidgetStack
+{
+TQ_OBJECT
+
+
+public:
+
+ FileSaveOptionsBox(TQWidget *parent=0);
+ ~FileSaveOptionsBox();
+
+ void applySettings();
+
+public slots:
+
+ void slotImageFileFormatChanged(const TQString&);
+ void slotImageFileSelected(const TQString&);
+
+private:
+
+ void toggleFormatOptions(const TQString& format);
+ void readSettings();
+
+private:
+
+ FileSaveOptionsBoxPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* FILESAVEOPTIONSBOX_H */
diff --git a/src/libs/widgets/common/histogramwidget.cpp b/src/libs/widgets/common/histogramwidget.cpp
new file mode 100644
index 00000000..b3017201
--- /dev/null
+++ b/src/libs/widgets/common/histogramwidget.cpp
@@ -0,0 +1,1089 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-21
+ * Description : a widget to display an image histogram.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Some code parts are inspired from from gimp 2.0
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqevent.h>
+#include <tqtimer.h>
+#include <tqcolor.h>
+#include <tqbrush.h>
+#include <tqrect.h>
+#include <tqfont.h>
+#include <tqfontmetrics.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "histogramwidget.h"
+#include "histogramwidget.moc"
+
+namespace Digikam
+{
+
+class HistogramWidgetPriv
+{
+public:
+
+ enum RepaintType
+ {
+ HistogramNone = 0, // No current histogram values calculation.
+ HistogramDataLoading, // The image is being loaded
+ HistogramStarted, // Histogram values calculation started.
+ HistogramCompleted, // Histogram values calculation completed.
+ HistogramFailed // Histogram values calculation failed.
+ };
+
+ HistogramWidgetPriv()
+ {
+ blinkTimer = 0;
+ sixteenBits = false;
+ inSelected = false;
+ clearFlag = HistogramNone;
+ xmin = 0.0;
+ xmax = 0.0;
+ range = 255;
+ guideVisible = false;
+ inInitialRepaintWait = false;
+ pos = 0;
+ }
+
+ // Current selection information.
+ double xmin;
+ double xminOrg;
+ double xmax;
+ int range;
+ int clearFlag; // Clear drawing zone with message.
+ int pos; // Position of animation during loading/calculation.
+
+ bool sixteenBits;
+ bool guideVisible; // Display color guide.
+ bool statisticsVisible; // Display tooltip histogram statistics.
+ bool inSelected;
+ bool selectMode; // If true, a part of the histogram can be selected !
+ bool showProgress; // If true, a message will be displayed during histogram computation,
+ // else nothing (limit flicker effect in widget especially for small
+ // image/computation time).
+ bool inInitialRepaintWait;
+
+ TQTimer *blinkTimer;
+
+ DColor colorGuide;
+};
+
+// Constructor without image data (needed to use updateData() method after instance created).
+
+HistogramWidget::HistogramWidget(int w, int h,
+ TQWidget *parent, bool selectMode,
+ bool showProgress, bool statisticsVisible)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new HistogramWidgetPriv;
+ setup(w, h, selectMode, showProgress, statisticsVisible);
+
+ m_imageHistogram = 0L;
+ m_selectionHistogram = 0L;
+}
+
+// Constructor without image selection.
+
+HistogramWidget::HistogramWidget(int w, int h,
+ uchar *i_data, uint i_w, uint i_h,
+ bool i_sixteenBits,
+ TQWidget *parent, bool selectMode,
+ bool showProgress, bool statisticsVisible)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new HistogramWidgetPriv;
+ d->sixteenBits = i_sixteenBits;
+ setup(w, h, selectMode, showProgress, statisticsVisible);
+
+ m_imageHistogram = new ImageHistogram(i_data, i_w, i_h, i_sixteenBits, this);
+ m_selectionHistogram = 0L;
+}
+
+// Constructor with image selection.
+
+HistogramWidget::HistogramWidget(int w, int h,
+ uchar *i_data, uint i_w, uint i_h,
+ uchar *s_data, uint s_w, uint s_h,
+ bool i_sixteenBits,
+ TQWidget *parent, bool selectMode,
+ bool showProgress, bool statisticsVisible)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new HistogramWidgetPriv;
+ d->sixteenBits = i_sixteenBits;
+ setup(w, h, selectMode, showProgress, statisticsVisible);
+
+ m_imageHistogram = new ImageHistogram(i_data, i_w, i_h, i_sixteenBits, this);
+ m_selectionHistogram = new ImageHistogram(s_data, s_w, s_h, i_sixteenBits, this);
+}
+
+HistogramWidget::~HistogramWidget()
+{
+ d->blinkTimer->stop();
+
+ if (m_imageHistogram)
+ delete m_imageHistogram;
+
+ if (m_selectionHistogram)
+ delete m_selectionHistogram;
+
+ delete d;
+}
+
+void HistogramWidget::setup(int w, int h, bool selectMode, bool showProgress, bool statisticsVisible)
+{
+ m_channelType = ValueHistogram;
+ m_scaleType = LogScaleHistogram;
+ m_colorType = RedColor;
+ m_renderingType = FullImageHistogram;
+ d->statisticsVisible = statisticsVisible;
+ d->selectMode = selectMode;
+ d->showProgress = showProgress;
+
+ setMouseTracking(true);
+ setMinimumSize(w, h);
+
+ d->blinkTimer = new TQTimer( this );
+
+ connect( d->blinkTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotBlinkTimerDone()) );
+}
+
+void HistogramWidget::setHistogramGuideByColor(const DColor& color)
+{
+ d->guideVisible = true;
+ d->colorGuide = color;
+ repaint(false);
+}
+
+void HistogramWidget::reset()
+{
+ d->guideVisible = false;
+ repaint(false);
+}
+
+void HistogramWidget::customEvent(TQCustomEvent *event)
+{
+ if (!event) return;
+
+ ImageHistogram::EventData *ed = (ImageHistogram::EventData*) event->data();
+
+ if (!ed) return;
+
+ if (ed->histogram != m_imageHistogram && ed->histogram != m_selectionHistogram)
+ return;
+
+ if (ed->starting)
+ {
+ setCursor( KCursor::waitCursor() );
+ d->clearFlag = HistogramWidgetPriv::HistogramStarted;
+ if (!d->inInitialRepaintWait)
+ {
+ if (d->clearFlag != HistogramWidgetPriv::HistogramDataLoading)
+ {
+ // enter initial repaint wait, repaint only after waiting
+ // a short time so that very fast computation does not create flicker
+ d->inInitialRepaintWait = true;
+ d->blinkTimer->start( 100 );
+ }
+ else
+ {
+ // after the initial repaint, we can repaint immediately
+ repaint(false);
+ d->blinkTimer->start( 200 );
+ }
+ }
+ }
+ else
+ {
+ if (ed->success)
+ {
+ // Repaint histogram
+ d->clearFlag = HistogramWidgetPriv::HistogramCompleted;
+ d->blinkTimer->stop();
+ d->inInitialRepaintWait = false;
+ setCursor( KCursor::arrowCursor() );
+
+ // Send signals to refresh information if necessary.
+ // The signals may trigger multiple repaints, avoid this,
+ // we repaint once afterwards.
+ setUpdatesEnabled(false);
+
+ notifyValuesChanged();
+ emit signalHistogramComputationDone(d->sixteenBits);
+
+ setUpdatesEnabled(true);
+ repaint(false);
+ }
+ else
+ {
+ d->clearFlag = HistogramWidgetPriv::HistogramFailed;
+ d->blinkTimer->stop();
+ d->inInitialRepaintWait = false;
+ repaint(false);
+ setCursor( KCursor::arrowCursor() );
+ // Remove old histogram data from memory.
+ if (m_imageHistogram)
+ {
+ delete m_imageHistogram;
+ m_imageHistogram = 0;
+ }
+ if (m_selectionHistogram)
+ {
+ delete m_selectionHistogram;
+ m_selectionHistogram = 0;
+ }
+ emit signalHistogramComputationFailed();
+ }
+ }
+
+ delete ed;
+}
+
+void HistogramWidget::setDataLoading()
+{
+ if (d->clearFlag != HistogramWidgetPriv::HistogramDataLoading)
+ {
+ setCursor( KCursor::waitCursor() );
+ d->clearFlag = HistogramWidgetPriv::HistogramDataLoading;
+ // enter initial repaint wait, repaint only after waiting
+ // a short time so that very fast computation does not create flicker
+ d->inInitialRepaintWait = true;
+ d->pos = 0;
+ d->blinkTimer->start( 100 );
+ }
+}
+
+void HistogramWidget::setLoadingFailed()
+{
+ d->clearFlag = HistogramWidgetPriv::HistogramFailed;
+ d->pos = 0;
+ d->blinkTimer->stop();
+ d->inInitialRepaintWait = false;
+ repaint(false);
+ setCursor( KCursor::arrowCursor() );
+}
+
+void HistogramWidget::stopHistogramComputation()
+{
+ if (m_imageHistogram)
+ m_imageHistogram->stopCalcHistogramValues();
+
+ if (m_selectionHistogram)
+ m_selectionHistogram->stopCalcHistogramValues();
+
+ d->blinkTimer->stop();
+ d->pos = 0;
+}
+
+void HistogramWidget::updateData(uchar *i_data, uint i_w, uint i_h,
+ bool i_sixteenBits,
+ uchar *s_data, uint s_w, uint s_h,
+ bool showProgress)
+{
+ d->showProgress = showProgress;
+ d->sixteenBits = i_sixteenBits;
+
+ // We are deleting the histogram data, so we must not use it to draw any more.
+ d->clearFlag = HistogramWidgetPriv::HistogramNone;
+
+ // Do not using ImageHistogram::getHistogramSegment()
+ // method here because histogram hasn't yet been computed.
+ d->range = d->sixteenBits ? 65535 : 255;
+ emit signalMaximumValueChanged( d->range );
+
+
+ // Remove old histogram data from memory.
+ if (m_imageHistogram)
+ delete m_imageHistogram;
+
+ if (m_selectionHistogram)
+ delete m_selectionHistogram;
+
+ // Calc new histogram data
+ m_imageHistogram = new ImageHistogram(i_data, i_w, i_h, i_sixteenBits, this);
+
+ if (s_data && s_w && s_h)
+ m_selectionHistogram = new ImageHistogram(s_data, s_w, s_h, i_sixteenBits, this);
+ else
+ m_selectionHistogram = 0L;
+}
+
+void HistogramWidget::updateSelectionData(uchar *s_data, uint s_w, uint s_h,
+ bool i_sixteenBits,
+ bool showProgress)
+{
+ d->showProgress = showProgress;
+
+ // Remove old histogram data from memory.
+
+ if (m_selectionHistogram)
+ delete m_selectionHistogram;
+
+ // Calc new histogram data
+ m_selectionHistogram = new ImageHistogram(s_data, s_w, s_h, i_sixteenBits, this);
+}
+
+void HistogramWidget::slotBlinkTimerDone()
+{
+ d->inInitialRepaintWait = false;
+ repaint(false);
+ d->blinkTimer->start( 200 );
+}
+
+void HistogramWidget::paintEvent(TQPaintEvent*)
+{
+ // Widget is disabled, not initialized,
+ // or loading, but no message shall be drawn:
+ // Drawing grayed frame.
+ if ( !isEnabled() ||
+ d->clearFlag == HistogramWidgetPriv::HistogramNone ||
+ (!d->showProgress && (d->clearFlag == HistogramWidgetPriv::HistogramStarted ||
+ d->clearFlag == HistogramWidgetPriv::HistogramDataLoading))
+ )
+ {
+ TQPixmap pm(size());
+ TQPainter p1;
+ p1.begin(&pm, this);
+ p1.fillRect(0, 0, size().width(), size().height(), palette().disabled().background());
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+ p1.setPen(TQPen(palette().disabled().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+ p1.end();
+ bitBlt(this, 0, 0, &pm);
+ return;
+ }
+ // Image data is loading or histogram is being computed:
+ // Draw message.
+ else if ( d->showProgress &&
+ (d->clearFlag == HistogramWidgetPriv::HistogramStarted ||
+ d->clearFlag == HistogramWidgetPriv::HistogramDataLoading)
+ )
+ {
+ // In first, we draw an animation.
+
+ int asize = 24;
+ TQPixmap anim(asize, asize);
+ TQPainter p2;
+ p2.begin(&anim, this);
+ p2.fillRect(0, 0, asize, asize, palette().active().background());
+ p2.translate(asize/2, asize/2);
+
+ d->pos = (d->pos + 10) % 360;
+ p2.setPen(TQPen(palette().active().text()));
+ p2.rotate(d->pos);
+ for ( int i=0 ; i<12 ; i++ )
+ {
+ p2.drawLine(asize/2-5, 0, asize/2-2, 0);
+ p2.rotate(30);
+ }
+ p2.end();
+
+ // ... and we render busy text.
+
+ TQPixmap pm(size());
+ TQPainter p1;
+ p1.begin(&pm, this);
+ p1.fillRect(0, 0, width(), height(), palette().active().background());
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+ p1.drawPixmap(width()/2 - asize /2, asize, anim);
+ p1.setPen(TQPen(palette().active().text()));
+
+ if (d->clearFlag == HistogramWidgetPriv::HistogramDataLoading)
+ p1.drawText(0, 0, width(), height(), TQt::AlignCenter,
+ i18n("Loading image..."));
+ else
+ p1.drawText(0, 0, width(), height(), TQt::AlignCenter,
+ i18n("Histogram calculation..."));
+ p1.end();
+
+ bitBlt(this, 0, 0, &pm);
+ return;
+ }
+ // Histogram computation failed:
+ // Draw message.
+ else if (d->clearFlag == HistogramWidgetPriv::HistogramFailed)
+ {
+ TQPixmap pm(size());
+ TQPainter p1;
+ p1.begin(&pm, this);
+ p1.fillRect(0, 0, width(), height(), palette().active().background());
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+ p1.setPen(TQPen(palette().active().text()));
+ p1.drawText(0, 0, width(), height(), TQt::AlignCenter,
+ i18n("Histogram\ncalculation\nfailed."));
+ p1.end();
+ bitBlt(this, 0, 0, &pm);
+ return;
+ }
+
+ int x, y;
+ int yr, yg, yb; // For all color channels.
+ int wWidth = width();
+ int wHeight = height();
+ double max;
+ class ImageHistogram *histogram;
+
+ if (m_renderingType == ImageSelectionHistogram && m_selectionHistogram)
+ histogram = m_selectionHistogram;
+ else
+ histogram = m_imageHistogram;
+
+ if (!histogram)
+ return;
+
+ x = 0; y = 0;
+ yr = 0; yg = 0; yb = 0;
+ max = 0.0;
+
+ switch(m_channelType)
+ {
+ case HistogramWidget::GreenChannelHistogram: // Green channel.
+ max = histogram->getMaximum(ImageHistogram::GreenChannel);
+ break;
+
+ case HistogramWidget::BlueChannelHistogram: // Blue channel.
+ max = histogram->getMaximum(ImageHistogram::BlueChannel);
+ break;
+
+ case HistogramWidget::RedChannelHistogram: // Red channel.
+ max = histogram->getMaximum(ImageHistogram::RedChannel);
+ break;
+
+ case HistogramWidget::AlphaChannelHistogram: // Alpha channel.
+ max = histogram->getMaximum(ImageHistogram::AlphaChannel);
+ break;
+
+ case HistogramWidget::ColorChannelsHistogram: // All color channels.
+ max = TQMAX (TQMAX (histogram->getMaximum(ImageHistogram::RedChannel),
+ histogram->getMaximum(ImageHistogram::GreenChannel)),
+ histogram->getMaximum(ImageHistogram::BlueChannel));
+ break;
+
+ case HistogramWidget::ValueHistogram: // Luminosity.
+ max = histogram->getMaximum(ImageHistogram::ValueChannel);
+ break;
+ }
+
+ switch (m_scaleType)
+ {
+ case HistogramWidget::LinScaleHistogram:
+ break;
+
+ case HistogramWidget::LogScaleHistogram:
+ if (max > 0.0)
+ max = log (max);
+ else
+ max = 1.0;
+ break;
+ }
+
+ // A TQPixmap is used to enable the double buffering.
+
+ TQPixmap pm(size());
+ TQPainter p1;
+ p1.begin(&pm, this);
+ p1.fillRect(0, 0, width(), height(), palette().active().background());
+
+ // Drawing selection or all histogram values.
+
+ for (x = 0 ; x < wWidth ; x++)
+ {
+ double value = 0.0;
+ double value_r = 0.0, value_g = 0.0, value_b = 0.0; // For all color channels.
+ int i, j;
+
+ i = (x * histogram->getHistogramSegment()) / wWidth;
+ j = ((x + 1) * histogram->getHistogramSegment()) / wWidth;
+
+ do
+ {
+ double v;
+ double vr, vg, vb; // For all color channels.
+
+ v = 0.0;
+ vr = 0.0; vg = 0.0; vb = 0.0;
+
+ switch(m_channelType)
+ {
+ case HistogramWidget::GreenChannelHistogram: // Green channel.
+ v = histogram->getValue(ImageHistogram::GreenChannel, i++);
+ break;
+
+ case HistogramWidget::BlueChannelHistogram: // Blue channel.
+ v = histogram->getValue(ImageHistogram::BlueChannel, i++);
+ break;
+
+ case HistogramWidget::RedChannelHistogram: // Red channel.
+ v = histogram->getValue(ImageHistogram::RedChannel, i++);
+ break;
+
+ case HistogramWidget::AlphaChannelHistogram: // Alpha channel.
+ v = histogram->getValue(ImageHistogram::AlphaChannel, i++);
+ break;
+
+ case HistogramWidget::ColorChannelsHistogram: // All color channels.
+ vr = histogram->getValue(ImageHistogram::RedChannel, i++);
+ vg = histogram->getValue(ImageHistogram::GreenChannel, i);
+ vb = histogram->getValue(ImageHistogram::BlueChannel, i);
+ break;
+
+ case HistogramWidget::ValueHistogram: // Luminosity.
+ v = histogram->getValue(ImageHistogram::ValueChannel, i++);
+ break;
+ }
+
+ if ( m_channelType != HistogramWidget::ColorChannelsHistogram )
+ {
+ if (v > value)
+ value = v;
+ }
+ else
+ {
+ if (vr > value_r)
+ value_r = vr;
+ if (vg > value_g)
+ value_g = vg;
+ if (vb > value_b)
+ value_b = vb;
+ }
+ }
+ while (i < j);
+
+ if ( m_channelType != HistogramWidget::ColorChannelsHistogram )
+ {
+ switch (m_scaleType)
+ {
+ case HistogramWidget::LinScaleHistogram:
+ y = (int) ((wHeight * value) / max);
+ break;
+
+ case HistogramWidget::LogScaleHistogram:
+ if (value <= 0.0) value = 1.0;
+ y = (int) ((wHeight * log (value)) / max);
+ break;
+
+ default:
+ y = 0;
+ break;
+ }
+ }
+ else
+ {
+ switch (m_scaleType)
+ {
+ case HistogramWidget::LinScaleHistogram:
+ yr = (int) ((wHeight * value_r) / max);
+ yg = (int) ((wHeight * value_g) / max);
+ yb = (int) ((wHeight * value_b) / max);
+ break;
+
+ case HistogramWidget::LogScaleHistogram:
+ if (value_r <= 0.0) value_r = 1.0;
+ if (value_g <= 0.0) value_g = 1.0;
+ if (value_b <= 0.0) value_b = 1.0;
+ yr = (int) ((wHeight * log (value_r)) / max);
+ yg = (int) ((wHeight * log (value_g)) / max);
+ yb = (int) ((wHeight * log (value_b)) / max);
+ break;
+
+ default:
+ yr = 0;
+ yg = 0;
+ yb = 0;
+ break;
+ }
+ }
+
+ // Drawing the histogram + selection or only the histogram.
+
+ if ( m_channelType != HistogramWidget::ColorChannelsHistogram )
+ {
+ if ( d->selectMode == true ) // Selection mode enable ?
+ {
+ if ( x >= (int)(d->xmin * wWidth) && x <= (int)(d->xmax * wWidth) )
+ {
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - y);
+ }
+ else
+ {
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - y);
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - y, x, 0);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+ }
+ }
+ else
+ {
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - y);
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - y, x, 0);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+ }
+ }
+ else
+ {
+ if ( d->selectMode == true ) // Histogram selection mode enable ?
+ {
+ if ( x >= (int)(d->xmin * wWidth) && x <= (int)(d->xmax * wWidth) )
+ {
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+
+ // Witch color must be used on the foreground with all colors channel mode?
+ switch (m_colorType)
+ {
+ case HistogramWidget::RedColor:
+ p1.drawLine(x, wHeight, x, wHeight - yr);
+ break;
+
+ case HistogramWidget::GreenColor:
+ p1.drawLine(x, wHeight, x, wHeight - yg);
+ break;
+
+ default:
+ p1.drawLine(x, wHeight, x, wHeight - yb);
+ break;
+ }
+ }
+ else
+ {
+ // Which color must be used on the foreground with all colors channel mode?
+ switch (m_colorType)
+ {
+ case HistogramWidget::RedColor:
+ p1.setPen(TQPen(TQt::green, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yg);
+ p1.setPen(TQPen(TQt::blue, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yb);
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yr);
+
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - TQMAX(TQMAX(yr, yg), yb), x, 0);
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - yg -1, x, wHeight - yg);
+ p1.drawLine(x, wHeight - yb -1, x, wHeight - yb);
+ p1.drawLine(x, wHeight - yr -1, x, wHeight - yr);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+
+ break;
+
+ case HistogramWidget::GreenColor:
+ p1.setPen(TQPen(TQt::blue, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yb);
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yr);
+ p1.setPen(TQPen(TQt::green, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yg);
+
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - TQMAX(TQMAX(yr, yg), yb), x, 0);
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - yb -1, x, wHeight - yb);
+ p1.drawLine(x, wHeight - yr -1, x, wHeight - yr);
+ p1.drawLine(x, wHeight - yg -1, x, wHeight - yg);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+
+ break;
+
+ default:
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yr);
+ p1.setPen(TQPen(TQt::green, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yg);
+ p1.setPen(TQPen(TQt::blue, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yb);
+
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - TQMAX(TQMAX(yr, yg), yb), x, 0);
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - yr -1, x, wHeight - yr);
+ p1.drawLine(x, wHeight - yg -1, x, wHeight - yg);
+ p1.drawLine(x, wHeight - yb -1, x, wHeight - yb);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Which color must be used on the foreground with all colors channel mode?
+ switch (m_colorType)
+ {
+ case HistogramWidget::RedColor:
+ p1.setPen(TQPen(TQt::green, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yg);
+ p1.setPen(TQPen(TQt::blue, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yb);
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yr);
+
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - TQMAX(TQMAX(yr, yg), yb), x, 0);
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - yg -1, x, wHeight - yg);
+ p1.drawLine(x, wHeight - yb -1, x, wHeight - yb);
+ p1.drawLine(x, wHeight - yr -1, x, wHeight - yr);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+
+ break;
+
+ case HistogramWidget::GreenColor:
+ p1.setPen(TQPen(TQt::blue, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yb);
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yr);
+ p1.setPen(TQPen(TQt::green, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yg);
+
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - TQMAX(TQMAX(yr, yg), yb), x, 0);
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - yb -1, x, wHeight - yb);
+ p1.drawLine(x, wHeight - yr -1, x, wHeight - yr);
+ p1.drawLine(x, wHeight - yg -1, x, wHeight - yg);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+
+ break;
+
+ default:
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yr);
+ p1.setPen(TQPen(TQt::green, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yg);
+ p1.setPen(TQPen(TQt::blue, 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, wHeight - yb);
+
+ p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - TQMAX(TQMAX(yr, yg), yb), x, 0);
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight - yr -1, x, wHeight - yr);
+ p1.drawLine(x, wHeight - yg -1, x, wHeight - yg);
+ p1.drawLine(x, wHeight - yb -1, x, wHeight - yb);
+
+ if ( x == wWidth/4 || x == wWidth/2 || x == 3*wWidth/4 )
+ {
+ p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine));
+ p1.drawLine(x, wHeight, x, 0);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Drawing color guide.
+
+ p1.setPen(TQPen(TQt::red, 1, TQt::DotLine));
+ int guidePos;
+
+ if (d->guideVisible)
+ {
+ switch(m_channelType)
+ {
+ case HistogramWidget::RedChannelHistogram:
+ guidePos = d->colorGuide.red();
+ break;
+
+ case HistogramWidget::GreenChannelHistogram:
+ guidePos = d->colorGuide.green();
+ break;
+
+ case HistogramWidget::BlueChannelHistogram:
+ guidePos = d->colorGuide.blue();
+ break;
+
+ case HistogramWidget::ValueHistogram:
+ guidePos = TQMAX(TQMAX(d->colorGuide.red(), d->colorGuide.green()), d->colorGuide.blue());
+ break;
+
+ case HistogramWidget::ColorChannelsHistogram:
+ {
+ switch(m_channelType)
+ {
+ case HistogramWidget::RedChannelHistogram:
+ guidePos = d->colorGuide.red();
+ break;
+
+ case HistogramWidget::GreenChannelHistogram:
+ guidePos = d->colorGuide.green();
+ break;
+
+ case HistogramWidget::BlueChannelHistogram:
+ guidePos = d->colorGuide.blue();
+ break;
+ }
+ }
+
+ default:
+ guidePos = d->colorGuide.alpha();
+ break;
+ }
+
+ if (guidePos != -1)
+ {
+ int xGuide = (guidePos * wWidth) / histogram->getHistogramSegment();
+ p1.drawLine(xGuide, 0, xGuide, wHeight);
+
+ TQString string = i18n("x:%1").arg(guidePos);
+ TQFontMetrics fontMt( string );
+ TQRect rect = fontMt.boundingRect(0, 0, wWidth, wHeight, 0, string);
+ p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+ rect.moveTop(1);
+
+ if (xGuide < wWidth/2)
+ {
+ rect.moveLeft(xGuide);
+ p1.fillRect(rect, TQBrush(TQColor(250, 250, 255)) );
+ p1.drawRect(rect);
+ rect.moveLeft(xGuide+3);
+ p1.drawText(rect, TQt::AlignLeft, string);
+ }
+ else
+ {
+ rect.moveRight(xGuide);
+ p1.fillRect(rect, TQBrush(TQColor(250, 250, 255)) );
+ p1.drawRect(rect);
+ rect.moveRight(xGuide-3);
+ p1.drawText(rect, TQt::AlignRight, string);
+ }
+ }
+ }
+
+ if (d->statisticsVisible)
+ {
+ TQString tipText, value;
+ TQString cellBeg("<tr><td><nobr><font size=-1>");
+ TQString cellMid("</font></nobr></td><td><nobr><font size=-1>");
+ TQString cellEnd("</font></nobr></td></tr>");
+ tipText = "<table cellspacing=0 cellpadding=0>";
+
+ tipText += cellBeg + i18n("Mean:") + cellMid;
+ double mean = histogram->getMean(m_channelType, 0, histogram->getHistogramSegment()-1);
+ tipText += value.setNum(mean, 'f', 1) + cellEnd;
+
+ tipText += cellBeg + i18n("Pixels:") + cellMid;
+ double pixels = histogram->getPixels();
+ tipText += value.setNum((float)pixels, 'f', 0) + cellEnd;
+
+ tipText += cellBeg + i18n("Std dev.:") + cellMid;
+ double stddev = histogram->getStdDev(m_channelType, 0, histogram->getHistogramSegment()-1);
+ tipText += value.setNum(stddev, 'f', 1) + cellEnd;
+
+ tipText += cellBeg + i18n("Count:") + cellMid;
+ double counts = histogram->getCount(m_channelType, 0, histogram->getHistogramSegment()-1);
+ tipText += value.setNum((float)counts, 'f', 0) + cellEnd;
+
+ tipText += cellBeg + i18n("Median:") + cellMid;
+ double median = histogram->getMedian(m_channelType, 0, histogram->getHistogramSegment()-1);
+ tipText += value.setNum(median, 'f', 1) + cellEnd;
+
+ tipText += cellBeg + i18n("Percent:") + cellMid;
+ double percentile = (pixels > 0 ? (100.0 * counts / pixels) : 0.0);
+ tipText += value.setNum(percentile, 'f', 1) + cellEnd;
+
+ tipText += "</table>";
+
+ TQToolTip::add( this, tipText);
+ }
+
+ p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine));
+ p1.drawRect(0, 0, width(), height());
+ p1.end();
+ bitBlt(this, 0, 0, &pm);
+}
+
+void HistogramWidget::mousePressEvent(TQMouseEvent* e)
+{
+ if ( d->selectMode == true && d->clearFlag == HistogramWidgetPriv::HistogramCompleted )
+ {
+ if (!d->inSelected)
+ {
+ d->inSelected = true;
+ repaint(false);
+ }
+
+ d->xmin = ((double)e->pos().x()) / ((double)width());
+ d->xminOrg = d->xmin;
+ notifyValuesChanged();
+ //emit signalValuesChanged( (int)(d->xmin * d->range), );
+ d->xmax = 0.0;
+ }
+}
+
+void HistogramWidget::mouseReleaseEvent(TQMouseEvent*)
+{
+ if ( d->selectMode == true && d->clearFlag == HistogramWidgetPriv::HistogramCompleted )
+ {
+ d->inSelected = false;
+ // Only single click without mouse move? Remove selection.
+ if (d->xmax == 0.0)
+ {
+ d->xmin = 0.0;
+ //emit signalMinValueChanged( 0 );
+ //emit signalMaxValueChanged( d->range );
+ notifyValuesChanged();
+ repaint(false);
+ }
+ }
+}
+
+void HistogramWidget::mouseMoveEvent(TQMouseEvent *e)
+{
+ if ( d->selectMode == true && d->clearFlag == HistogramWidgetPriv::HistogramCompleted )
+ {
+ setCursor( KCursor::crossCursor() );
+
+ if (d->inSelected)
+ {
+ double max = ((double)e->pos().x()) / ((double)width());
+ //int max = (int)(e->pos().x()*((float)m_imageHistogram->getHistogramSegment()/(float)width()));
+
+ if (max < d->xminOrg)
+ {
+ d->xmax = d->xminOrg;
+ d->xmin = max;
+ //emit signalMinValueChanged( (int)(d->xmin * d->range) );
+ }
+ else
+ {
+ d->xmin = d->xminOrg;
+ d->xmax = max;
+ }
+
+ notifyValuesChanged();
+ //emit signalMaxValueChanged( d->xmax == 0.0 ? d->range : (int)(d->xmax * d->range) );
+
+ repaint(false);
+ }
+ }
+}
+
+void HistogramWidget::notifyValuesChanged()
+{
+ emit signalIntervalChanged( (int)(d->xmin * d->range), d->xmax == 0.0 ? d->range : (int)(d->xmax * d->range) );
+}
+
+void HistogramWidget::slotMinValueChanged( int min )
+{
+ if ( d->selectMode == true && d->clearFlag == HistogramWidgetPriv::HistogramCompleted )
+ {
+ if (min == 0 && d->xmax == 1.0)
+ {
+ // everything is selected means no selection
+ d->xmin = 0.0;
+ d->xmax = 0.0;
+ }
+ if (min >= 0 && min < d->range)
+ {
+ d->xmin = ((double)min)/d->range;
+ }
+ repaint(false);
+ }
+}
+
+void HistogramWidget::slotMaxValueChanged(int max)
+{
+ if ( d->selectMode == true && d->clearFlag == HistogramWidgetPriv::HistogramCompleted )
+ {
+ if (d->xmin == 0.0 && max == d->range)
+ {
+ // everything is selected means no selection
+ d->xmin = 0.0;
+ d->xmax = 0.0;
+ }
+ else if (max > 0 && max <= d->range)
+ {
+ d->xmax = ((double)max)/d->range;
+ }
+ repaint(false);
+ }
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/histogramwidget.h b/src/libs/widgets/common/histogramwidget.h
new file mode 100644
index 00000000..26157170
--- /dev/null
+++ b/src/libs/widgets/common/histogramwidget.h
@@ -0,0 +1,177 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-21
+ * Description : a widget to display an image histogram.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef HISTOGRAMWIDGET_H
+#define HISTOGRAMWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "dcolor.h"
+#include "digikam_export.h"
+
+class TQCustomEvent;
+
+namespace Digikam
+{
+
+class ImageHistogram;
+class HistogramWidgetPriv;
+
+class DIGIKAM_EXPORT HistogramWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ enum HistogramType
+ {
+ ValueHistogram = 0, // Luminosity.
+ RedChannelHistogram, // Red channel.
+ GreenChannelHistogram, // Green channel.
+ BlueChannelHistogram, // Blue channel.
+ AlphaChannelHistogram, // Alpha channel.
+ ColorChannelsHistogram // All color channels.
+ };
+
+ enum HistogramScale
+ {
+ LinScaleHistogram=0, // Linear scale.
+ LogScaleHistogram // Logarithmic scale.
+ };
+
+ enum HistogramAllColorMode
+ {
+ RedColor=0, // Red color to foreground in All Colors Channel mode.
+ GreenColor, // Green color to foreground in All Colors Channel mode.
+ BlueColor // Blue color to foreground in All Colors Channel mode.
+ };
+
+ enum HistogramRenderingType
+ {
+ FullImageHistogram=0, // Full image histogram rendering.
+ ImageSelectionHistogram // Image selection histogram rendering.
+ };
+
+public:
+
+ /** Constructor without image data. Needed to use updateData() method after to create instance.*/
+ HistogramWidget(int w, int h, // Widget size.
+ TQWidget *parent=0, bool selectMode=true,
+ bool showProgress=true,
+ bool statisticsVisible=false);
+
+ /** Constructor with image data and without image selection data.*/
+ HistogramWidget(int w, int h, // Widget size.
+ uchar *i_data, uint i_w, uint i_h, // Full image info.
+ bool i_sixteenBits, // 8 or 16 bits image.
+ TQWidget *parent=0, bool selectMode=true,
+ bool showProgress=true,
+ bool statisticsVisible=false);
+
+ /** Constructor with image data and image selection data.*/
+ HistogramWidget(int w, int h, // Widget size.
+ uchar *i_data, uint i_w, uint i_h, // Full image info.
+ uchar *s_data, uint s_w, uint s_h, // Image selection info.
+ bool i_sixteenBits, // 8 or 16 bits image.
+ TQWidget *parent=0, bool selectMode=true,
+ bool showProgress=true,
+ bool statisticsVisible=false);
+
+ void setup(int w, int h, bool selectMode=true,
+ bool showProgress=true,
+ bool statisticsVisible=false);
+
+ ~HistogramWidget();
+
+ /** Stop current histogram computations.*/
+ void stopHistogramComputation(void);
+
+ /** Update full image histogram data methods.*/
+ void updateData(uchar *i_data, uint i_w, uint i_h,
+ bool i_sixteenBits, // 8 or 16 bits image.
+ uchar *s_data=0, uint s_w=0, uint s_h=0,
+ bool showProgress=true);
+
+ /** Update image selection histogram data methods.*/
+ void updateSelectionData(uchar *s_data, uint s_w, uint s_h,
+ bool i_sixteenBits, // 8 or 16 bits image.
+ bool showProgress=true);
+
+ void setDataLoading();
+ void setLoadingFailed();
+
+ void setHistogramGuideByColor(const DColor& color);
+
+ void reset();
+
+public:
+
+ int m_channelType; // Channel type to draw.
+ int m_scaleType; // Scale to use for drawing.
+ int m_colorType; // Color to use for drawing in All Colors Channel mode.
+ int m_renderingType; // Using full image or image selection for histogram rendering.
+
+ ImageHistogram *m_imageHistogram; // Full image.
+ ImageHistogram *m_selectionHistogram; // Image selection.
+
+signals:
+
+ void signalIntervalChanged(int min, int max);
+ void signalMaximumValueChanged(int);
+ void signalHistogramComputationDone(bool);
+ void signalHistogramComputationFailed();
+
+public slots:
+
+ void slotMinValueChanged(int min);
+ void slotMaxValueChanged(int max);
+
+protected slots:
+
+ void slotBlinkTimerDone();
+
+protected:
+
+ void paintEvent(TQPaintEvent*);
+ void mousePressEvent(TQMouseEvent*);
+ void mouseReleaseEvent(TQMouseEvent*);
+ void mouseMoveEvent(TQMouseEvent*);
+
+private :
+
+ void customEvent(TQCustomEvent*);
+ void notifyValuesChanged();
+
+private:
+
+ HistogramWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* HISTOGRAMWIDGET_H */
diff --git a/src/libs/widgets/common/paniconwidget.cpp b/src/libs/widgets/common/paniconwidget.cpp
new file mode 100644
index 00000000..d5549691
--- /dev/null
+++ b/src/libs/widgets/common/paniconwidget.cpp
@@ -0,0 +1,324 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-22
+ * Description : a generic widget to display a panel to choose
+ * a rectangular image area.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpen.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "paniconwidget.h"
+#include "paniconwidget.moc"
+
+namespace Digikam
+{
+
+class PanIconWidgetPriv
+{
+
+public:
+
+ PanIconWidgetPriv()
+ {
+ moveSelection = false;
+ }
+
+ bool moveSelection;
+
+ int xpos;
+ int ypos;
+
+ TQRect regionSelection; // Original size image selection.
+
+ TQImage image;
+};
+
+PanIconWidget::PanIconWidget(TQWidget *parent, WFlags flags)
+ : TQWidget(parent, 0, flags)
+{
+ d = new PanIconWidgetPriv;
+ m_flicker = false;
+ m_timerID = 0;
+ m_pixmap = 0;
+ m_zoomFactor = 1.0;
+
+ setBackgroundMode(TQt::NoBackground);
+ setMouseTracking(true);
+}
+
+PanIconWidget::~PanIconWidget()
+{
+ if (m_timerID) killTimer(m_timerID);
+
+ if (m_pixmap) delete m_pixmap;
+
+ delete d;
+}
+
+void PanIconWidget::setImage(int previewWidth, int previewHeight, const TQImage& image)
+{
+ TQSize sz(image.width(), image.height());
+ sz.scale(previewWidth, previewHeight, TQSize::ScaleMin);
+ m_pixmap = new TQPixmap(previewWidth, previewHeight);
+ m_width = sz.width();
+ m_height = sz.height();
+ d->image = image.smoothScale(sz.width(), sz.height());
+ m_orgWidth = image.width();
+ m_orgHeight = image.height();
+ m_zoomedOrgWidth = image.width();
+ m_zoomedOrgHeight = image.height();
+ setFixedSize(m_width, m_height);
+
+ m_rect = TQRect(width()/2-m_width/2, height()/2-m_height/2, m_width, m_height);
+ updatePixmap();
+ m_timerID = startTimer(800);
+}
+
+void PanIconWidget::setImage(int previewWidth, int previewHeight, const DImg& image)
+{
+ DImg img(image);
+ setImage(previewWidth, previewHeight, img.copyTQImage());
+}
+
+void PanIconWidget::slotZoomFactorChanged(double factor)
+{
+ if (m_zoomFactor == factor) return;
+ m_zoomFactor = factor;
+ m_zoomedOrgWidth = (int)(m_orgWidth * factor);
+ m_zoomedOrgHeight = (int)(m_orgHeight * factor);
+ updatePixmap();
+ repaint(false);
+}
+
+void PanIconWidget::setRegionSelection(const TQRect& regionSelection)
+{
+ d->regionSelection = regionSelection;
+ m_localRegionSelection.setX( m_rect.x() + (int)((float)d->regionSelection.x() *
+ ((float)m_width / (float)m_zoomedOrgWidth)) );
+
+ m_localRegionSelection.setY( m_rect.y() + (int)((float)d->regionSelection.y() *
+ ((float)m_height / (float)m_zoomedOrgHeight)) );
+
+ m_localRegionSelection.setWidth( (int)((float)d->regionSelection.width() *
+ ((float)m_width / (float)m_zoomedOrgWidth)) );
+
+ m_localRegionSelection.setHeight( (int)((float)d->regionSelection.height() *
+ ((float)m_height / (float)m_zoomedOrgHeight)) );
+
+ updatePixmap();
+ repaint(false);
+}
+
+TQRect PanIconWidget::getRegionSelection()
+{
+ return (d->regionSelection);
+}
+
+void PanIconWidget::setCursorToLocalRegionSelectionCenter()
+{
+ TQCursor::setPos(mapToGlobal(m_localRegionSelection.center()));
+}
+
+void PanIconWidget::setCenterSelection()
+{
+ setRegionSelection(TQRect(
+ (int)(((float)m_zoomedOrgWidth / 2.0) - ((float)d->regionSelection.width() / 2.0)),
+ (int)(((float)m_zoomedOrgHeight / 2.0) - ((float)d->regionSelection.height() / 2.0)),
+ d->regionSelection.width(),
+ d->regionSelection.height()));
+}
+
+void PanIconWidget::regionSelectionMoved(bool targetDone)
+{
+ if (targetDone)
+ {
+ updatePixmap();
+ repaint(false);
+ }
+
+ int x = (int)lround( ((float)m_localRegionSelection.x() - (float)m_rect.x() ) *
+ ((float)m_zoomedOrgWidth / (float)m_width) );
+
+ int y = (int)lround( ((float)m_localRegionSelection.y() - (float)m_rect.y() ) *
+ ((float)m_zoomedOrgHeight / (float)m_height) );
+
+ int w = (int)lround( (float)m_localRegionSelection.width() *
+ ((float)m_zoomedOrgWidth / (float)m_width) );
+
+ int h = (int)lround( (float)m_localRegionSelection.height() *
+ ((float)m_zoomedOrgHeight / (float)m_height) );
+
+ d->regionSelection.setX(x);
+ d->regionSelection.setY(y);
+ d->regionSelection.setWidth(w);
+ d->regionSelection.setHeight(h);
+
+ emit signalSelectionMoved( d->regionSelection, targetDone );
+}
+
+void PanIconWidget::updatePixmap()
+{
+ // Drawing background and image.
+ m_pixmap->fill(colorGroup().background());
+ bitBlt(m_pixmap, m_rect.x(), m_rect.y(), &d->image, 0, 0);
+
+ TQPainter p(m_pixmap);
+
+ // Drawing selection border
+
+ if (m_flicker) p.setPen(TQPen(TQt::white, 1, TQt::SolidLine));
+ else p.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+
+ p.drawRect(m_localRegionSelection.x(),
+ m_localRegionSelection.y(),
+ m_localRegionSelection.width(),
+ m_localRegionSelection.height());
+
+ if (m_flicker) p.setPen(TQPen(TQt::red, 1, TQt::DotLine));
+ else p.setPen(TQPen(TQt::white, 1, TQt::DotLine));
+
+ p.drawRect(m_localRegionSelection.x(),
+ m_localRegionSelection.y(),
+ m_localRegionSelection.width(),
+ m_localRegionSelection.height());
+
+ p.end();
+}
+
+void PanIconWidget::paintEvent(TQPaintEvent*)
+{
+ bitBlt(this, 0, 0, m_pixmap);
+}
+
+void PanIconWidget::setMouseFocus()
+{
+ raise();
+ d->xpos = m_localRegionSelection.center().x();
+ d->ypos = m_localRegionSelection.center().y();
+ d->moveSelection = true;
+ setCursor( KCursor::sizeAllCursor() );
+ emit signalSelectionTakeFocus();
+}
+
+void PanIconWidget::hideEvent(TQHideEvent *e)
+{
+ TQWidget::hideEvent(e);
+
+ if ( d->moveSelection )
+ {
+ d->moveSelection = false;
+ setCursor( KCursor::arrowCursor() );
+ emit signalHiden();
+ }
+}
+
+void PanIconWidget::mousePressEvent ( TQMouseEvent * e )
+{
+ if ( (e->button() == TQt::LeftButton || e->button() == TQt::MidButton) &&
+ m_localRegionSelection.contains( e->x(), e->y() ) )
+ {
+ d->xpos = e->x();
+ d->ypos = e->y();
+ d->moveSelection = true;
+ setCursor( KCursor::sizeAllCursor() );
+ emit signalSelectionTakeFocus();
+ }
+}
+
+void PanIconWidget::mouseMoveEvent ( TQMouseEvent * e )
+{
+ if ( d->moveSelection &&
+ (e->state() == TQt::LeftButton || e->state() == TQt::MidButton) )
+ {
+ int newxpos = e->x();
+ int newypos = e->y();
+
+ m_localRegionSelection.moveBy (newxpos - d->xpos, newypos - d->ypos);
+
+ d->xpos = newxpos;
+ d->ypos = newypos;
+
+ // Perform normalization of selection area.
+
+ if (m_localRegionSelection.left() < m_rect.left())
+ m_localRegionSelection.moveLeft(m_rect.left());
+
+ if (m_localRegionSelection.top() < m_rect.top())
+ m_localRegionSelection.moveTop(m_rect.top());
+
+ if (m_localRegionSelection.right() > m_rect.right())
+ m_localRegionSelection.moveRight(m_rect.right());
+
+ if (m_localRegionSelection.bottom() > m_rect.bottom())
+ m_localRegionSelection.moveBottom(m_rect.bottom());
+
+ updatePixmap();
+ repaint(false);
+ regionSelectionMoved(false);
+ return;
+ }
+ else
+ {
+ if ( m_localRegionSelection.contains( e->x(), e->y() ) )
+ setCursor( KCursor::handCursor() );
+ else
+ setCursor( KCursor::arrowCursor() );
+ }
+}
+
+void PanIconWidget::mouseReleaseEvent ( TQMouseEvent * )
+{
+ if ( d->moveSelection )
+ {
+ d->moveSelection = false;
+ setCursor( KCursor::arrowCursor() );
+ regionSelectionMoved(true);
+ }
+}
+
+void PanIconWidget::timerEvent(TQTimerEvent * e)
+{
+ if (e->timerId() == m_timerID)
+ {
+ m_flicker = !m_flicker;
+ updatePixmap();
+ repaint(false);
+ }
+ else
+ TQWidget::timerEvent(e);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/common/paniconwidget.h b/src/libs/widgets/common/paniconwidget.h
new file mode 100644
index 00000000..40b0758e
--- /dev/null
+++ b/src/libs/widgets/common/paniconwidget.h
@@ -0,0 +1,120 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-22
+ * Description : a generic widget to display a panel to choose
+ * a rectangular image area.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PANICONWIDGET_H
+#define PANICONWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqrect.h>
+#include <tqimage.h>
+
+// Local includes.
+
+#include "dimg.h"
+
+namespace Digikam
+{
+
+class ImagePanIconWidget;
+class PanIconWidgetPriv;
+
+class PanIconWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ PanIconWidget(TQWidget *parent=0, WFlags flags=TQt::WDestructiveClose);
+ ~PanIconWidget();
+
+ void setImage(int previewWidth, int previewHeight, const TQImage& image);
+ void setImage(int previewWidth, int previewHeight, const DImg& image);
+
+ void setRegionSelection(const TQRect& regionSelection);
+ TQRect getRegionSelection();
+ void setCenterSelection();
+
+ void setCursorToLocalRegionSelectionCenter();
+ void setMouseFocus();
+
+signals:
+
+ // Used with ImagePreview widget.
+ // Emit when selection have been moved with mouse. 'targetDone' booleen
+ // value is used for indicate if the mouse have been released.
+ void signalSelectionMoved(const TQRect& rect, bool targetDone );
+
+ void signalSelectionTakeFocus();
+
+ void signalHiden();
+
+public slots:
+
+ void slotZoomFactorChanged(double);
+
+protected:
+
+ void hideEvent(TQHideEvent*);
+ void paintEvent(TQPaintEvent*);
+ void mousePressEvent(TQMouseEvent*);
+ void mouseReleaseEvent(TQMouseEvent*);
+ void mouseMoveEvent(TQMouseEvent*);
+ void timerEvent(TQTimerEvent*);
+
+ /** Recalculate the target selection position and emit 'signalSelectionMoved'.*/
+ void regionSelectionMoved(bool targetDone);
+
+ virtual void updatePixmap();
+
+protected:
+
+ bool m_flicker;
+
+ int m_timerID;
+ int m_width;
+ int m_height;
+ int m_zoomedOrgWidth;
+ int m_zoomedOrgHeight;
+ int m_orgWidth;
+ int m_orgHeight;
+
+ double m_zoomFactor;
+
+ TQRect m_rect;
+ TQRect m_localRegionSelection; // Thumbnail size selection.
+
+ TQPixmap *m_pixmap;
+
+
+private:
+
+ PanIconWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* PANICONWIDGET_H */
diff --git a/src/libs/widgets/common/previewwidget.cpp b/src/libs/widgets/common/previewwidget.cpp
new file mode 100644
index 00000000..0fe5cb29
--- /dev/null
+++ b/src/libs/widgets/common/previewwidget.cpp
@@ -0,0 +1,640 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-13
+ * Description : a widget to display an image preview
+ *
+ * Copyright (C) 2006-2008 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqcache.h>
+#include <tqpainter.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqrect.h>
+#include <tqtimer.h>
+#include <tqguardedptr.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "previewwidget.h"
+#include "previewwidget.moc"
+
+namespace Digikam
+{
+
+class PreviewWidgetPriv
+{
+public:
+
+ PreviewWidgetPriv() :
+ tileSize(128), zoomMultiplier(1.2)
+ {
+ midButtonX = 0;
+ midButtonY = 0;
+ autoZoom = false;
+ fullScreen = false;
+ zoom = 1.0;
+ minZoom = 0.1;
+ maxZoom = 12.0;
+ zoomWidth = 0;
+ zoomHeight = 0;
+ tileTmpPix = new TQPixmap(tileSize, tileSize);
+
+ tileCache.setMaxCost((10*1024*1024)/(tileSize*tileSize*4));
+ tileCache.setAutoDelete(true);
+ }
+
+ bool autoZoom;
+ bool fullScreen;
+
+ const int tileSize;
+ int midButtonX;
+ int midButtonY;
+ int zoomWidth;
+ int zoomHeight;
+
+ double zoom;
+ double minZoom;
+ double maxZoom;
+ const double zoomMultiplier;
+
+ TQPoint centerZoomPoint;
+
+ TQRect pixmapRect;
+
+ TQCache<TQPixmap> tileCache;
+
+ TQPixmap* tileTmpPix;
+
+ TQColor bgColor;
+};
+
+PreviewWidget::PreviewWidget(TQWidget *parent)
+ : TQScrollView(parent, 0, TQt::WDestructiveClose)
+{
+ d = new PreviewWidgetPriv;
+ d->bgColor.setRgb(0, 0, 0);
+ m_movingInProgress = false;
+
+ viewport()->setBackgroundMode(TQt::NoBackground);
+ viewport()->setMouseTracking(false);
+
+ horizontalScrollBar()->setLineStep( 1 );
+ horizontalScrollBar()->setPageStep( 1 );
+ verticalScrollBar()->setLineStep( 1 );
+ verticalScrollBar()->setPageStep( 1 );
+
+ setFrameStyle(TQFrame::GroupBoxPanel|TQFrame::Plain);
+ setMargin(0);
+ setLineWidth(1);
+}
+
+PreviewWidget::~PreviewWidget()
+{
+ delete d->tileTmpPix;
+ delete d;
+}
+
+void PreviewWidget::setBackgroundColor(const TQColor& color)
+{
+ if (d->bgColor == color)
+ return;
+
+ d->bgColor = color;
+ viewport()->update();
+}
+
+void PreviewWidget::slotReset()
+{
+ d->tileCache.clear();
+ resetPreview();
+}
+
+TQRect PreviewWidget::previewRect()
+{
+ return d->pixmapRect;
+}
+
+int PreviewWidget::tileSize()
+{
+ return d->tileSize;
+}
+
+int PreviewWidget::zoomWidth()
+{
+ return d->zoomWidth;
+}
+
+int PreviewWidget::zoomHeight()
+{
+ return d->zoomHeight;
+}
+
+double PreviewWidget::zoomMax()
+{
+ return d->maxZoom;
+}
+
+double PreviewWidget::zoomMin()
+{
+ return d->minZoom;
+}
+
+void PreviewWidget::setZoomMax(double z)
+{
+ d->maxZoom = ceilf(z * 10000.0) / 10000.0;
+}
+
+void PreviewWidget::setZoomMin(double z)
+{
+ d->minZoom = floor(z * 10000.0) / 10000.0;
+}
+
+bool PreviewWidget::maxZoom()
+{
+ return (d->zoom >= d->maxZoom);
+}
+
+bool PreviewWidget::minZoom()
+{
+ return (d->zoom <= d->minZoom);
+}
+
+double PreviewWidget::snapZoom(double zoom)
+{
+ // If the zoom value gets changed from d->zoom to zoom
+ // across 50%, 100% or fit-to-window, then return the
+ // the corresponding special value. Otherwise zoom is returned unchanged.
+ double fit = calcAutoZoomFactor(ZoomInOrOut);
+ TQValueList<double> snapValues;
+ snapValues.append(0.5);
+ snapValues.append(1.0);
+ snapValues.append(fit);
+ qHeapSort(snapValues);
+ TQValueList<double>::const_iterator it;
+
+ if (d->zoom < zoom)
+ {
+ for(it = snapValues.constBegin(); it != snapValues.constEnd(); ++it)
+ {
+ double z = *it;
+ if ((d->zoom < z) && (zoom > z))
+ {
+ zoom = z;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for(it = snapValues.constEnd(); it != snapValues.constBegin(); --it)
+ {
+ double z = *it;
+ if ((d->zoom > z) && (zoom < z))
+ {
+ zoom = z;
+ break;
+ }
+ }
+ }
+
+ return zoom;
+}
+
+void PreviewWidget::slotIncreaseZoom()
+{
+ double zoom = d->zoom * d->zoomMultiplier;
+ zoom = snapZoom(zoom > zoomMax() ? zoomMax() : zoom);
+ setZoomFactor(zoom);
+}
+
+void PreviewWidget::slotDecreaseZoom()
+{
+ double zoom = d->zoom / d->zoomMultiplier;
+ zoom = snapZoom(zoom < zoomMin() ? zoomMin() : zoom);
+ setZoomFactor(zoom);
+}
+
+void PreviewWidget::setZoomFactorSnapped(double zoom)
+{
+ double fit = calcAutoZoomFactor(ZoomInOrOut);
+ if (fabs(zoom-1.0) < 0.05)
+ {
+ zoom = 1.0;
+ }
+ if (fabs(zoom-0.5) < 0.05)
+ {
+ zoom = 0.5;
+ }
+ if (fabs(zoom-fit) < 0.05)
+ {
+ zoom = fit;
+ }
+
+ setZoomFactor(zoom);
+}
+
+void PreviewWidget::setZoomFactor(double zoom)
+{
+ setZoomFactor(zoom, false);
+}
+
+void PreviewWidget::setZoomFactor(double zoom, bool centerView)
+{
+ // Zoom using center of canvas and given zoom factor.
+
+ double oldZoom = d->zoom;
+ double cpx, cpy;
+
+ if (d->centerZoomPoint.isNull())
+ {
+ // center on current center
+ // store old center pos
+ cpx = contentsX() + visibleWidth() / 2.0;
+ cpy = contentsY() + visibleHeight() / 2.0;
+
+ cpx = ( cpx / d->tileSize ) * floor(d->tileSize / d->zoom);
+ cpy = ( cpy / d->tileSize ) * floor(d->tileSize / d->zoom);
+ }
+ else
+ {
+ // keep mouse pointer position constant
+ // store old content pos
+ cpx = contentsX();
+ cpy = contentsY();
+ }
+
+ // To limit precision of zoom value and reduce error with check of max/min zoom.
+ d->zoom = floor(zoom * 10000.0) / 10000.0;
+ d->zoomWidth = (int)(previewWidth() * d->zoom);
+ d->zoomHeight = (int)(previewHeight() * d->zoom);
+
+ updateContentsSize();
+
+ // adapt step size to zoom factor. Overall, using a finer step size than scrollbar default.
+ int step = TQMAX(2, 2*lround(d->zoom));
+ horizontalScrollBar()->setLineStep( step );
+ horizontalScrollBar()->setPageStep( step * 10 );
+ verticalScrollBar()->setLineStep( step );
+ verticalScrollBar()->setPageStep( step * 10 );
+
+ viewport()->setUpdatesEnabled(false);
+ if (d->centerZoomPoint.isNull())
+ {
+ cpx = ( cpx * d->tileSize ) / floor(d->tileSize / d->zoom);
+ cpy = ( cpy * d->tileSize ) / floor(d->tileSize / d->zoom);
+
+ if (centerView)
+ {
+ cpx = d->zoomWidth/2.0;
+ cpy = d->zoomHeight/2.0;
+ }
+
+ center((int)cpx, (int)(cpy));
+ }
+ else
+ {
+ cpx = d->zoom * d->centerZoomPoint.x() / oldZoom - d->centerZoomPoint.x() + cpx;
+ cpy = d->zoom * d->centerZoomPoint.y() / oldZoom - d->centerZoomPoint.y() + cpy;
+
+ setContentsPos((int)cpx, (int)(cpy));
+ }
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+
+ zoomFactorChanged(d->zoom);
+}
+
+double PreviewWidget::zoomFactor()
+{
+ return d->zoom;
+}
+
+bool PreviewWidget::isFitToWindow()
+{
+ return d->autoZoom;
+}
+
+void PreviewWidget::fitToWindow()
+{
+ updateAutoZoom();
+ updateContentsSize();
+ zoomFactorChanged(d->zoom);
+ viewport()->update();
+}
+
+void PreviewWidget::toggleFitToWindow()
+{
+ d->autoZoom = !d->autoZoom;
+
+ if (d->autoZoom)
+ {
+ updateAutoZoom();
+ }
+ else
+ {
+ d->zoom = 1.0;
+ zoomFactorChanged(d->zoom);
+ }
+
+ updateContentsSize();
+ viewport()->update();
+}
+
+void PreviewWidget::toggleFitToWindowOr100()
+{
+ // If the current zoom is 100%, then fit to window.
+ if (d->zoom == 1.0)
+ {
+ fitToWindow();
+ }
+ else
+ {
+ setZoomFactor(1.0, true);
+ }
+}
+
+void PreviewWidget::updateAutoZoom(AutoZoomMode mode)
+{
+ d->zoom = calcAutoZoomFactor(mode);
+ d->zoomWidth = (int)(previewWidth() * d->zoom);
+ d->zoomHeight = (int)(previewHeight() * d->zoom);
+
+ zoomFactorChanged(d->zoom);
+}
+
+double PreviewWidget::calcAutoZoomFactor(AutoZoomMode mode)
+{
+ if (previewIsNull()) return d->zoom;
+
+ double srcWidth = previewWidth();
+ double srcHeight = previewHeight();
+ double dstWidth = contentsRect().width();
+ double dstHeight = contentsRect().height();
+
+ double zoom = TQMIN(dstWidth/srcWidth, dstHeight/srcHeight);
+ // limit precision as above
+ zoom = floor(zoom * 10000.0) / 10000.0;
+ if (mode == ZoomInOrOut)
+ // fit to available space, scale up or down
+ return zoom;
+ else
+ // ZoomInOnly: accept that an image is smaller than available space, dont scale up
+ return TQMIN(1.0, zoom);
+}
+
+void PreviewWidget::updateContentsSize()
+{
+ viewport()->setUpdatesEnabled(false);
+
+ if (visibleWidth() > d->zoomWidth || visibleHeight() > d->zoomHeight)
+ {
+ // Center the image
+ int centerx = contentsRect().width()/2;
+ int centery = contentsRect().height()/2;
+ int xoffset = int(centerx - d->zoomWidth/2);
+ int yoffset = int(centery - d->zoomHeight/2);
+ xoffset = TQMAX(xoffset, 0);
+ yoffset = TQMAX(yoffset, 0);
+
+ d->pixmapRect = TQRect(xoffset, yoffset, d->zoomWidth, d->zoomHeight);
+ }
+ else
+ {
+ d->pixmapRect = TQRect(0, 0, d->zoomWidth, d->zoomHeight);
+ }
+
+ d->tileCache.clear();
+ setContentsSize();
+ viewport()->setUpdatesEnabled(true);
+}
+
+void PreviewWidget::setContentsSize()
+{
+ resizeContents(d->zoomWidth, d->zoomHeight);
+}
+
+void PreviewWidget::resizeEvent(TQResizeEvent* e)
+{
+ if (!e) return;
+
+ TQScrollView::resizeEvent(e);
+
+ if (d->autoZoom)
+ updateAutoZoom();
+
+ updateContentsSize();
+
+ // No need to repaint. its called
+ // automatically after resize
+
+ // To be sure than corner widget used to pan image will be hide/show
+ // accordinly with resize event.
+ zoomFactorChanged(d->zoom);
+}
+
+void PreviewWidget::viewportPaintEvent(TQPaintEvent *e)
+{
+ TQRect er(e->rect());
+ er = TQRect(TQMAX(er.x() - 1, 0),
+ TQMAX(er.y() - 1, 0),
+ TQMIN(er.width() + 2, contentsRect().width()),
+ TQMIN(er.height() + 2, contentsRect().height()));
+
+ bool antialias = (d->zoom <= 1.0) ? true : false;
+
+ TQRect o_cr(viewportToContents(er.topLeft()), viewportToContents(er.bottomRight()));
+ TQRect cr = o_cr;
+
+ TQRegion clipRegion(er);
+ cr = d->pixmapRect.intersect(cr);
+
+ if (!cr.isEmpty() && !previewIsNull())
+ {
+ clipRegion -= TQRect(contentsToViewport(cr.topLeft()), cr.size());
+
+ TQRect pr = TQRect(cr.x() - d->pixmapRect.x(), cr.y() - d->pixmapRect.y(),
+ cr.width(), cr.height());
+
+ int x1 = (int)floor((double)pr.x() / (double)d->tileSize) * d->tileSize;
+ int y1 = (int)floor((double)pr.y() / (double)d->tileSize) * d->tileSize;
+ int x2 = (int)ceilf((double)pr.right() / (double)d->tileSize) * d->tileSize;
+ int y2 = (int)ceilf((double)pr.bottom() / (double)d->tileSize) * d->tileSize;
+
+ TQPixmap pix(d->tileSize, d->tileSize);
+ int sx, sy, sw, sh;
+ int step = (int)floor(d->tileSize / d->zoom);
+
+ for (int j = y1 ; j < y2 ; j += d->tileSize)
+ {
+ for (int i = x1 ; i < x2 ; i += d->tileSize)
+ {
+ TQString key = TQString("%1,%2").arg(i).arg(j);
+ TQPixmap *pix = d->tileCache.find(key);
+
+ if (!pix)
+ {
+ if (antialias)
+ {
+ pix = new TQPixmap(d->tileSize, d->tileSize);
+ d->tileCache.insert(key, pix);
+ }
+ else
+ {
+ pix = d->tileTmpPix;
+ }
+
+ pix->fill(d->bgColor);
+
+ sx = (int)floor((double)i / d->tileSize ) * step;
+ sy = (int)floor((double)j / d->tileSize ) * step;
+ sw = step;
+ sh = step;
+
+ paintPreview(pix, sx, sy, sw, sh);
+ }
+
+ TQRect r(i, j, d->tileSize, d->tileSize);
+ TQRect ir = pr.intersect(r);
+ TQPoint pt(contentsToViewport(TQPoint(ir.x() + d->pixmapRect.x(),
+ ir.y() + d->pixmapRect.y())));
+
+ bitBlt(viewport(), pt.x(), pt.y(),
+ pix,
+ ir.x()-r.x(), ir.y()-r.y(),
+ ir.width(), ir.height());
+ }
+ }
+ }
+
+ TQPainter p(viewport());
+ p.setClipRegion(clipRegion);
+ p.fillRect(er, d->bgColor);
+ p.end();
+
+ viewportPaintExtraData();
+}
+
+void PreviewWidget::contentsMousePressEvent(TQMouseEvent *e)
+{
+ if (!e || e->button() == TQt::RightButton)
+ return;
+
+ m_movingInProgress = false;
+
+ if (e->button() == TQt::LeftButton)
+ {
+ emit signalLeftButtonClicked();
+ }
+ else if (e->button() == TQt::MidButton)
+ {
+ if (visibleWidth() < d->zoomWidth ||
+ visibleHeight() < d->zoomHeight)
+ {
+ m_movingInProgress = true;
+ d->midButtonX = e->x();
+ d->midButtonY = e->y();
+ viewport()->repaint(false);
+ viewport()->setCursor(TQt::SizeAllCursor);
+ }
+ return;
+ }
+
+ viewport()->setMouseTracking(false);
+}
+
+void PreviewWidget::contentsMouseMoveEvent(TQMouseEvent *e)
+{
+ if (!e) return;
+
+ if (e->state() & TQt::MidButton)
+ {
+ if (m_movingInProgress)
+ {
+ scrollBy(d->midButtonX - e->x(),
+ d->midButtonY - e->y());
+ emit signalContentsMovedEvent(false);
+ }
+ }
+}
+
+void PreviewWidget::contentsMouseReleaseEvent(TQMouseEvent *e)
+{
+ if (!e) return;
+
+ m_movingInProgress = false;
+
+ if (e->button() == TQt::MidButton)
+ {
+ emit signalContentsMovedEvent(true);
+ viewport()->unsetCursor();
+ viewport()->repaint(false);
+ }
+
+ if (e->button() == TQt::RightButton)
+ {
+ emit signalRightButtonClicked();
+ }
+}
+
+void PreviewWidget::contentsWheelEvent(TQWheelEvent *e)
+{
+ e->accept();
+
+ if (e->state() & TQt::ShiftButton)
+ {
+ if (e->delta() < 0)
+ emit signalShowNextImage();
+ else if (e->delta() > 0)
+ emit signalShowPrevImage();
+ return;
+ }
+ else if (e->state() & TQt::ControlButton)
+ {
+ // When zooming with the mouse-wheel, the image center is kept fixed.
+ d->centerZoomPoint = e->pos();
+ if (e->delta() < 0 && !minZoom())
+ slotDecreaseZoom();
+ else if (e->delta() > 0 && !maxZoom())
+ slotIncreaseZoom();
+ d->centerZoomPoint = TQPoint();
+ return;
+ }
+
+ TQScrollView::contentsWheelEvent(e);
+}
+
+void PreviewWidget::zoomFactorChanged(double zoom)
+{
+ emit signalZoomFactorChanged(zoom);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/common/previewwidget.h b/src/libs/widgets/common/previewwidget.h
new file mode 100644
index 00000000..03369a42
--- /dev/null
+++ b/src/libs/widgets/common/previewwidget.h
@@ -0,0 +1,131 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-13
+ * Description : a widget to display an image preview
+ *
+ * Copyright (C) 2006-2007 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef PREVIEWWIDGET_H
+#define PREVIEWWIDGET_H
+
+// TQt includes.
+
+#include <tqscrollview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQPainter;
+class TQPixmap;
+class TQColor;
+
+namespace Digikam
+{
+
+class PreviewWidgetPriv;
+
+class DIGIKAM_EXPORT PreviewWidget : public TQScrollView
+{
+TQ_OBJECT
+
+
+public:
+
+ PreviewWidget(TQWidget *parent=0);
+ ~PreviewWidget();
+
+ void setZoomFactor(double z);
+ void setZoomFactor(double z, bool centerView);
+ void setZoomFactorSnapped(double z);
+
+ void setBackgroundColor(const TQColor& color);
+ void fitToWindow();
+ bool isFitToWindow();
+ void toggleFitToWindow();
+ void toggleFitToWindowOr100();
+
+ int zoomWidth();
+ int zoomHeight();
+ bool maxZoom();
+ bool minZoom();
+ double snapZoom(double zoom);
+
+ double zoomFactor();
+ double zoomMax();
+ double zoomMin();
+ void setZoomMax(double z);
+ void setZoomMin(double z);
+
+signals:
+
+ void signalRightButtonClicked();
+ void signalLeftButtonClicked();
+ void signalShowNextImage();
+ void signalShowPrevImage();
+ void signalZoomFactorChanged(double);
+ void signalContentsMovedEvent(bool);
+
+public slots:
+
+ void slotIncreaseZoom();
+ void slotDecreaseZoom();
+ void slotReset();
+
+protected:
+
+ bool m_movingInProgress;
+
+protected:
+
+ enum AutoZoomMode
+ {
+ ZoomInOrOut,
+ ZoomInOnly
+ };
+
+ double calcAutoZoomFactor(AutoZoomMode mode = ZoomInOrOut);
+ int tileSize();
+ void updateAutoZoom(AutoZoomMode mode = ZoomInOrOut);
+ void updateContentsSize();
+ TQRect previewRect();
+
+ virtual void resizeEvent(TQResizeEvent *);
+ virtual void viewportPaintEvent(TQPaintEvent *);
+ virtual void contentsMousePressEvent(TQMouseEvent *);
+ virtual void contentsMouseMoveEvent(TQMouseEvent *);
+ virtual void contentsMouseReleaseEvent(TQMouseEvent *);
+ virtual void contentsWheelEvent(TQWheelEvent *);
+ virtual void setContentsSize();
+ virtual void viewportPaintExtraData(){};
+ virtual int previewWidth()=0;
+ virtual int previewHeight()=0;
+ virtual bool previewIsNull()=0;
+ virtual void resetPreview()=0;
+ virtual void paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh)=0;
+ virtual void zoomFactorChanged(double zoom);
+
+private:
+
+ PreviewWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* PREVIEWWIDGET_H */
diff --git a/src/libs/widgets/common/searchtextbar.cpp b/src/libs/widgets/common/searchtextbar.cpp
new file mode 100644
index 00000000..1a81c03b
--- /dev/null
+++ b/src/libs/widgets/common/searchtextbar.cpp
@@ -0,0 +1,260 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-11-25
+ * Description : a bar used to search a string.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqpalette.h>
+#include <tqpainter.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqtoolbutton.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <kdialogbase.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "searchtextbar.h"
+#include "searchtextbar.moc"
+
+namespace Digikam
+{
+
+class DLineEditPriv
+{
+public:
+
+ DLineEditPriv()
+ {
+ drawMsg = true;
+ }
+
+ bool drawMsg;
+
+ TQString message;
+};
+
+DLineEdit::DLineEdit(const TQString &msg, TQWidget *parent)
+ : KLineEdit(parent)
+{
+ d = new DLineEditPriv;
+ setMessage(msg);
+}
+
+DLineEdit::~DLineEdit()
+{
+ delete d;
+}
+
+TQString DLineEdit::message() const
+{
+ return d->message;
+}
+
+void DLineEdit::setMessage(const TQString &msg)
+{
+ d->message = msg;
+ repaint();
+}
+
+void DLineEdit::setText(const TQString &txt)
+{
+ d->drawMsg = txt.isEmpty();
+ repaint();
+ KLineEdit::setText(txt);
+}
+
+void DLineEdit::drawContents(TQPainter *p)
+{
+ KLineEdit::drawContents(p);
+
+ if (d->drawMsg && !hasFocus())
+ {
+ TQPen tmp = p->pen();
+ p->setPen(palette().color(TQPalette::Disabled, TQColorGroup::Text));
+ TQRect cr = contentsRect();
+
+ // Add two pixel margin on the left side
+ cr.rLeft() += 3;
+ p->drawText(cr, AlignAuto | AlignVCenter, d->message);
+ p->setPen( tmp );
+ }
+}
+
+void DLineEdit::dropEvent(TQDropEvent *e)
+{
+ d->drawMsg = false;
+ KLineEdit::dropEvent(e);
+}
+
+void DLineEdit::focusInEvent(TQFocusEvent *e)
+{
+ if (d->drawMsg)
+ {
+ d->drawMsg = false;
+ repaint();
+ }
+ TQLineEdit::focusInEvent(e);
+}
+
+void DLineEdit::focusOutEvent(TQFocusEvent *e)
+{
+ if (text().isEmpty())
+ {
+ d->drawMsg = true;
+ repaint();
+ }
+ TQLineEdit::focusOutEvent(e);
+}
+
+// ---------------------------------------------------------------------
+
+class SearchTextBarPriv
+{
+public:
+
+ SearchTextBarPriv()
+ {
+ textQueryCompletion = false;
+ searchEdit = 0;
+ clearButton = 0;
+ }
+
+ bool textQueryCompletion;
+
+ TQToolButton *clearButton;
+
+ DLineEdit *searchEdit;
+};
+
+SearchTextBar::SearchTextBar(TQWidget *parent, const char* name, const TQString &msg)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new SearchTextBarPriv;
+ setFocusPolicy(TQWidget::NoFocus);
+ setName(name);
+
+ TQHBoxLayout *hlay = new TQHBoxLayout(this);
+
+ d->clearButton = new TQToolButton(this);
+ d->clearButton->setEnabled(false);
+ d->clearButton->setAutoRaise(true);
+ d->clearButton->setIconSet(kapp->iconLoader()->loadIcon("clear_left",
+ TDEIcon::Toolbar, TDEIcon::SizeSmall));
+
+ d->searchEdit = new DLineEdit(msg, this);
+ TDECompletion *kcom = new TDECompletion;
+ kcom->setOrder(TDECompletion::Sorted);
+ d->searchEdit->setCompletionObject(kcom, true);
+ d->searchEdit->setAutoDeleteCompletionObject(true);
+ d->searchEdit->setSizePolicy(TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Minimum));
+
+ hlay->setSpacing(0);
+ hlay->setMargin(0);
+ hlay->addWidget(d->searchEdit);
+ hlay->addWidget(d->clearButton);
+
+ connect(d->clearButton, TQ_SIGNAL(clicked()),
+ d->searchEdit, TQ_SLOT(clear()));
+
+ connect(d->searchEdit, TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotTextChanged(const TQString&)));
+
+ TDEConfig *config = kapp->config();
+ config->setGroup(name + TQString(" Search Text Tool"));
+ d->searchEdit->setCompletionMode((TDEGlobalSettings::Completion)config->readNumEntry("AutoCompletionMode",
+ (int)TDEGlobalSettings::CompletionAuto));
+}
+
+SearchTextBar::~SearchTextBar()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(name() + TQString(" Search Text Tool"));
+ config->writeEntry("AutoCompletionMode", (int)d->searchEdit->completionMode());
+ config->sync();
+
+ delete d;
+}
+
+void SearchTextBar::setEnableTextQueryCompletion(bool b)
+{
+ d->textQueryCompletion = b;
+}
+
+bool SearchTextBar::textQueryCompletion() const
+{
+ return d->textQueryCompletion;
+}
+
+void SearchTextBar::setText(const TQString& text)
+{
+ d->searchEdit->setText(text);
+}
+
+TQString SearchTextBar::text() const
+{
+ return d->searchEdit->text();
+}
+
+DLineEdit *SearchTextBar::lineEdit() const
+{
+ return d->searchEdit;
+}
+
+void SearchTextBar::slotTextChanged(const TQString& text)
+{
+ if (d->searchEdit->text().isEmpty())
+ d->searchEdit->unsetPalette();
+
+ d->clearButton->setEnabled(text.isEmpty() ? false : true);
+
+ emit signalTextChanged(text);
+}
+
+void SearchTextBar::slotSearchResult(bool match)
+{
+ if (d->searchEdit->text().isEmpty())
+ {
+ d->searchEdit->unsetPalette();
+ return;
+ }
+
+ TQPalette pal = d->searchEdit->palette();
+ pal.setColor(TQPalette::Active, TQColorGroup::Base,
+ match ? TQColor(200, 255, 200) :
+ TQColor(255, 200, 200));
+ pal.setColor(TQPalette::Active, TQColorGroup::Text, TQt::black);
+ d->searchEdit->setPalette(pal);
+
+ // If search result match the text query, we put the text
+ // in auto-completion history.
+ if (d->textQueryCompletion && match)
+ d->searchEdit->completionObject()->addItem(d->searchEdit->text());
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/searchtextbar.h b/src/libs/widgets/common/searchtextbar.h
new file mode 100644
index 00000000..04ef9947
--- /dev/null
+++ b/src/libs/widgets/common/searchtextbar.h
@@ -0,0 +1,111 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-11-25
+ * Description : a bar used to search a string.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SEARCH_TEXT_BAR_H
+#define SEARCH_TEXT_BAR_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <klineedit.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DLineEditPriv;
+class SearchTextBarPriv;
+
+class DIGIKAM_EXPORT DLineEdit : public KLineEdit
+{
+ TQ_OBJECT
+
+
+public:
+
+ DLineEdit(const TQString &msg, TQWidget *parent);
+ ~DLineEdit();
+
+ void setMessage(const TQString &msg);
+ TQString message() const;
+
+ void setText(const TQString& txt);
+
+protected:
+
+ void drawContents(TQPainter *p);
+ void dropEvent(TQDropEvent *e);
+ void focusInEvent(TQFocusEvent *e);
+ void focusOutEvent(TQFocusEvent *e);
+
+private :
+
+ DLineEditPriv* d;
+};
+
+class DIGIKAM_EXPORT SearchTextBar : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ SearchTextBar(TQWidget *parent, const char* name, const TQString &msg=i18n("Search..."));
+ ~SearchTextBar();
+
+ void setText(const TQString& text);
+ TQString text() const;
+
+ void setEnableTextQueryCompletion(bool b);
+ bool textQueryCompletion() const;
+
+ DLineEdit *lineEdit() const;
+
+signals:
+
+ void signalTextChanged(const TQString&);
+
+public slots:
+
+ void slotSearchResult(bool);
+
+private slots:
+
+ void slotTextChanged(const TQString&);
+
+private :
+
+ SearchTextBarPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* SEARCH_TEXT_BAR_H */
diff --git a/src/libs/widgets/common/sidebar.cpp b/src/libs/widgets/common/sidebar.cpp
new file mode 100644
index 00000000..a1bcd752
--- /dev/null
+++ b/src/libs/widgets/common/sidebar.cpp
@@ -0,0 +1,363 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-22
+ * Description : a widget to manage sidebar in gui.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file sidebar.cpp */
+
+// TQt includes.
+
+#include <tqsplitter.h>
+#include <tqwidgetstack.h>
+#include <tqdatastream.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <tdeversion.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "sidebar.h"
+#include "sidebar.moc"
+
+namespace Digikam
+{
+
+class SidebarPriv
+{
+public:
+
+ SidebarPriv()
+ {
+ minimizedDefault = false;
+ minimized = false;
+ isMinimized = false;
+
+ tabs = 0;
+ activeTab = -1;
+ minSize = 0;
+ maxSize = 0;
+ dragSwitchId = -1;
+
+ stack = 0;
+ splitter = 0;
+ dragSwitchTimer = 0;
+ }
+
+ bool minimizedDefault;
+ bool minimized;
+ bool isMinimized; // Backup of minimized status (used with Fullscreen)
+
+ int tabs;
+ int activeTab;
+ int minSize;
+ int maxSize;
+ int dragSwitchId;
+
+ TQWidgetStack *stack;
+ TQSplitter *splitter;
+ TQSize bigSize;
+ TQTimer *dragSwitchTimer;
+
+ Sidebar::Side side;
+};
+
+Sidebar::Sidebar(TQWidget *parent, const char *name, Side side, bool minimizedDefault)
+ : KMultiTabBar(KMultiTabBar::Vertical, parent, name)
+{
+ d = new SidebarPriv;
+ d->minimizedDefault = minimizedDefault;
+ d->side = side;
+ d->dragSwitchTimer = new TQTimer(this);
+
+ connect(d->dragSwitchTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotDragSwitchTimer()));
+}
+
+Sidebar::~Sidebar()
+{
+ saveViewState();
+ delete d;
+}
+
+void Sidebar::updateMinimumWidth()
+{
+ int width = 0;
+ for (int i = 0; i < d->tabs; i++)
+ {
+ TQWidget *w = d->stack->widget(i);
+ if (w && w->width() > width)
+ width = w->width();
+ }
+ d->stack->setMinimumWidth(width);
+}
+
+void Sidebar::setSplitter(TQSplitter *sp)
+{
+#if KDE_IS_VERSION(3,3,0)
+ setStyle(KMultiTabBar::VSNET);
+#else
+ setStyle(KMultiTabBar::KDEV3);
+#endif
+
+ d->splitter = sp;
+ d->stack = new TQWidgetStack(d->splitter);
+
+ if(d->side == Left)
+ setPosition(KMultiTabBar::Left);
+ else
+ setPosition(KMultiTabBar::Right);
+}
+
+TQSplitter* Sidebar::splitter() const
+{
+ return d->splitter;
+}
+
+void Sidebar::loadViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(TQString("%1").arg(name()));
+
+ int tab = config->readNumEntry("ActiveTab", 0);
+ bool minimized = config->readBoolEntry("Minimized", d->minimizedDefault);
+
+ // validate
+ if(tab >= d->tabs || tab < 0)
+ tab = 0;
+
+ if (minimized)
+ {
+ d->activeTab = tab;
+ //setTab(d->activeTab, true);
+ d->stack->raiseWidget(d->activeTab);
+ emit signalChangedTab(d->stack->visibleWidget());
+ }
+ else
+ {
+ d->activeTab = -1;
+ }
+
+ clicked(tab);
+}
+
+void Sidebar::saveViewState()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(TQString("%1").arg(name()));
+ config->writeEntry("ActiveTab", d->activeTab);
+ config->writeEntry("Minimized", d->minimized);
+ config->sync();
+}
+
+void Sidebar::backup()
+{
+ d->isMinimized = d->minimized;
+
+ if (!d->isMinimized)
+ shrink();
+
+ KMultiTabBar::hide();
+}
+
+void Sidebar::restore()
+{
+ if (!d->isMinimized)
+ expand();
+
+ KMultiTabBar::show();
+}
+
+void Sidebar::appendTab(TQWidget *w, const TQPixmap &pic, const TQString &title)
+{
+ w->reparent(d->stack, TQPoint(0, 0));
+ KMultiTabBar::appendTab(pic, d->tabs, title);
+ d->stack->addWidget(w, d->tabs);
+
+ tab(d->tabs)->setAcceptDrops(true);
+ tab(d->tabs)->installEventFilter(this);
+
+ connect(tab(d->tabs), TQ_SIGNAL(clicked(int)),
+ this, TQ_SLOT(clicked(int)));
+
+ d->tabs++;
+}
+
+void Sidebar::deleteTab(TQWidget *w)
+{
+ int tab = d->stack->id(w);
+ if(tab < 0)
+ return;
+
+ if(tab == d->activeTab)
+ d->activeTab = -1;
+
+ d->stack->removeWidget(d->stack->widget(tab));
+ removeTab(tab);
+ d->tabs--;
+ updateMinimumWidth();
+
+ //TODO show another widget
+}
+
+void Sidebar::clicked(int tab)
+{
+ if(tab >= d->tabs || tab < 0)
+ return;
+
+ if(tab == d->activeTab)
+ {
+ d->stack->isHidden() ? expand() : shrink();
+ }
+ else
+ {
+ if(d->activeTab >= 0)
+ setTab(d->activeTab, false);
+
+ d->activeTab = tab;
+ setTab(d->activeTab, true);
+ d->stack->raiseWidget(d->activeTab);
+
+ if(d->minimized)
+ expand();
+
+ emit signalChangedTab(d->stack->visibleWidget());
+ }
+}
+
+void Sidebar::setActiveTab(TQWidget *w)
+{
+ int tab = d->stack->id(w);
+ if(tab < 0)
+ return;
+
+ if(d->activeTab >= 0)
+ setTab(d->activeTab, false);
+
+ d->activeTab = tab;
+ setTab(d->activeTab, true);
+ d->stack->raiseWidget(d->activeTab);
+
+ if(d->minimized)
+ expand();
+
+ emit signalChangedTab(d->stack->visibleWidget());
+}
+
+TQWidget* Sidebar::getActiveTab()
+{
+ return d->stack->visibleWidget();
+}
+
+void Sidebar::shrink()
+{
+ d->minimized = true;
+ d->bigSize = size();
+ d->minSize = minimumWidth();
+ d->maxSize = maximumWidth();
+
+ d->stack->hide();
+
+ KMultiTabBarTab* tab = tabs()->first();
+ if (tab)
+ setFixedWidth(tab->width());
+ else
+ setFixedWidth(width());
+
+ emit signalViewChanged();
+}
+
+void Sidebar::expand()
+{
+ d->minimized = false;
+ d->stack->show();
+ resize(d->bigSize);
+ setMinimumWidth(d->minSize);
+ setMaximumWidth(d->maxSize);
+ emit signalViewChanged();
+}
+
+bool Sidebar::isExpanded()
+{
+ return !d->minimized;
+}
+
+bool Sidebar::eventFilter(TQObject *obj, TQEvent *ev)
+{
+ TQPtrList<KMultiTabBarTab>* pTabs = tabs();
+
+ for (TQPtrListIterator<KMultiTabBarTab> it(*pTabs); it.current(); ++it)
+ {
+ if ( obj == *it )
+ {
+ if ( ev->type() == TQEvent::DragEnter)
+ {
+ TQDragEnterEvent *e = static_cast<TQDragEnterEvent *>(ev);
+ enterEvent(e);
+ e->accept(true);
+ return false;
+ }
+ else if (ev->type() == TQEvent::DragMove)
+ {
+ if (!d->dragSwitchTimer->isActive())
+ {
+ d->dragSwitchTimer->start(800, true);
+ d->dragSwitchId = (*it)->id();
+ }
+ return false;
+ }
+ else if (ev->type() == TQEvent::DragLeave)
+ {
+ d->dragSwitchTimer->stop();
+ TQDragLeaveEvent *e = static_cast<TQDragLeaveEvent *>(ev);
+ leaveEvent(e);
+ return false;
+ }
+ else if (ev->type() == TQEvent::Drop)
+ {
+ d->dragSwitchTimer->stop();
+ TQDropEvent *e = static_cast<TQDropEvent *>(ev);
+ leaveEvent(e);
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ // Else, pass the event on to the parent class
+ return KMultiTabBar::eventFilter(obj, ev);
+}
+
+void Sidebar::slotDragSwitchTimer()
+{
+ clicked(d->dragSwitchId);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/sidebar.h b/src/libs/widgets/common/sidebar.h
new file mode 100644
index 00000000..8d2dc519
--- /dev/null
+++ b/src/libs/widgets/common/sidebar.h
@@ -0,0 +1,178 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-03-22
+ * Description : a widget to manage sidebar in gui.
+ *
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.net>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file sidebar.h */
+
+#ifndef _SIDEBAR_H_
+#define _SIDEBAR_H_
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// KDE includes.
+
+#include <tdemultitabbar.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQSplitter;
+
+namespace Digikam
+{
+
+class SidebarPriv;
+
+/**
+ * This class handles a sidebar view
+ */
+class DIGIKAM_EXPORT Sidebar : public KMultiTabBar
+{
+ TQ_OBJECT
+
+
+public:
+
+ /**
+ * The side where the bar should be visible
+ */
+ enum Side
+ {
+ Left,
+ Right
+ };
+
+ /**
+ * Creates a new sidebar
+ * @param parent sidebar's parent
+ * @param name the name of the widget is used to store its state to config
+ * @param side where the sidebar should be displayed. At the left or right border.
+ * @param minimizedDefault hide the sidebar when the program is started the first time?
+ */
+ Sidebar(TQWidget *parent, const char *name, Side side=Left, bool mimimizedDefault=false);
+ virtual ~Sidebar();
+
+ /**
+ * The width of the widget stack can be changed by a TQSplitter.
+ * @param sp sets the splitter, which should handle the width. The splitter normally
+ * is part of the main view.
+ */
+ void setSplitter(TQSplitter *sp);
+ void setSplitterSizePolicy(TQSizePolicy p);
+
+ TQSplitter* splitter() const;
+
+ /**
+ * Appends a new tab to the sidebar
+ * @param w widget which is activated by this tab
+ * @param pic icon which is shown in this tab
+ * @param title text which is shown it this tab
+ */
+ void appendTab(TQWidget *w, const TQPixmap &pic, const TQString &title);
+
+ /**
+ * Deletes a tab from the tabbar
+ */
+ void deleteTab(TQWidget *w);
+
+ /**
+ * Activates a tab
+ */
+ void setActiveTab(TQWidget *w);
+
+ /**
+ * Returns the currently activated tab, or 0 if no tab is active
+ */
+ TQWidget* getActiveTab();
+
+ /**
+ * Hides the sidebar (display only the activation buttons)
+ */
+ void shrink();
+
+ /**
+ * redisplays the whole sidebar
+ */
+ void expand();
+
+ /**
+ * load the last view state from disk
+ */
+ void loadViewState();
+
+ /**
+ * hide sidebar and backup minimized state.
+ */
+ void backup();
+
+ /**
+ * show sidebar and restore minimized state.
+ */
+ void restore();
+
+ /**
+ * return the visible status of current sidebar tab.
+ */
+ bool isExpanded();
+
+private:
+
+ /**
+ * save the view state to disk
+ */
+ void saveViewState();
+ bool eventFilter(TQObject *o, TQEvent *e);
+ void updateMinimumWidth();
+
+private slots:
+
+ /**
+ * Activates a tab
+ */
+ void clicked(int tab);
+
+ void slotDragSwitchTimer();
+
+signals:
+
+ /**
+ * is emitted, when another tab is activated
+ */
+ void signalChangedTab(TQWidget *w);
+
+ /**
+ * is emitted, when tab is shrink or expanded
+ */
+ void signalViewChanged();
+
+private:
+
+ SidebarPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // _SIDEBAR_H_
diff --git a/src/libs/widgets/common/splashscreen.cpp b/src/libs/widgets/common/splashscreen.cpp
new file mode 100644
index 00000000..7a8cbba1
--- /dev/null
+++ b/src/libs/widgets/common/splashscreen.cpp
@@ -0,0 +1,160 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-10
+ * Description : a widget to display spash with progress bar
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtimer.h>
+#include <tqfont.h>
+#include <tqstring.h>
+#include <tqcolor.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kstandarddirs.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "splashscreen.h"
+#include "splashscreen.moc"
+
+namespace Digikam
+{
+
+class SplashScreenPriv
+{
+public:
+
+ SplashScreenPriv()
+ {
+ state = 0;
+ progressBarSize = 3;
+ state = 0;
+ color = TQt::black;
+ alignment = TQt::AlignLeft;
+ }
+
+ int state;
+ int progressBarSize;
+ int alignment;
+
+ TQString string;
+
+ TQColor color;
+};
+
+SplashScreen::SplashScreen(const TQString& splash, WFlags f)
+ : KSplashScreen(TQPixmap(locate("appdata", splash)), f)
+{
+ d = new SplashScreenPriv;
+
+ TQTimer *timer = new TQTimer(this);
+
+ connect(timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(animate()));
+
+ timer->start(150);
+}
+
+SplashScreen::~SplashScreen()
+{
+ delete d;
+}
+
+void SplashScreen::animate()
+{
+ d->state = ((d->state + 1) % (2*d->progressBarSize-1));
+ repaint();
+}
+
+void SplashScreen::setColor(const TQColor& color)
+{
+ d->color = color;
+}
+void SplashScreen::setAlignment(int alignment)
+{
+ d->alignment = alignment;
+}
+
+void SplashScreen::message(const TQString& message)
+{
+ d->string = message;
+ TQSplashScreen::message(d->string, d->alignment, d->color);
+ animate();
+}
+
+void SplashScreen::drawContents(TQPainter* painter)
+{
+ int position;
+ TQColor basecolor(155, 192, 231);
+
+ // Draw background circles
+ painter->setPen(NoPen);
+ painter->setBrush(TQColor(225, 234, 231));
+ painter->drawEllipse(21, 7, 9, 9);
+ painter->drawEllipse(32, 7, 9, 9);
+ painter->drawEllipse(43, 7, 9, 9);
+
+ // Draw animated circles, increments are chosen
+ // to get close to background's color
+ // (didn't work well with TQColor::light function)
+ for (int i=0; i < d->progressBarSize; i++)
+ {
+ position = (d->state+i)%(2*d->progressBarSize-1);
+ if (position < 3)
+ {
+ painter->setBrush(TQColor(basecolor.red() -18*i,
+ basecolor.green()-28*i,
+ basecolor.blue() -10*i));
+
+ painter->drawEllipse(21+position*11, 7, 9, 9);
+ }
+ }
+
+ painter->setPen(d->color);
+
+ TQFont fnt(TDEGlobalSettings::generalFont());
+ int fntSize = fnt.pointSize();
+ if (fntSize > 0)
+ {
+ fnt.setPointSize(fntSize-2);
+ }
+ else
+ {
+ fntSize = fnt.pixelSize();
+ fnt.setPixelSize(fntSize-2);
+ }
+ painter->setFont(fnt);
+
+ TQRect r = rect();
+ r.setRect( r.x() + 59, r.y() + 5, r.width() - 10, r.height() - 10 );
+
+ // Draw message at given position, limited to 43 chars
+ // If message is too long, string is truncated
+ if (d->string.length() > 40) {d->string.truncate(39); d->string += "...";}
+ painter->drawText(r, d->alignment, d->string);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/splashscreen.h b/src/libs/widgets/common/splashscreen.h
new file mode 100644
index 00000000..d2d4cf45
--- /dev/null
+++ b/src/libs/widgets/common/splashscreen.h
@@ -0,0 +1,74 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-10
+ * Description : a widget to display spash with progress bar
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SPLASHSCREEN_H
+#define SPLASHSCREEN_H
+
+// TQt includes.
+
+#include <tqpainter.h>
+
+// KDE includes.
+
+#include <ksplashscreen.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class SplashScreenPriv;
+
+class DIGIKAM_EXPORT SplashScreen : public KSplashScreen
+{
+TQ_OBJECT
+
+
+public:
+
+ SplashScreen(const TQString& splash, WFlags f=0);
+ virtual ~SplashScreen();
+
+ void setAlignment(int alignment);
+ void setColor(const TQColor& color);
+
+protected:
+
+ void drawContents (TQPainter *);
+
+public slots:
+
+ void animate();
+ void message(const TQString &message);
+
+private:
+
+ SplashScreenPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* SPLASHSCREEN_H */
diff --git a/src/libs/widgets/common/squeezedcombobox.cpp b/src/libs/widgets/common/squeezedcombobox.cpp
new file mode 100644
index 00000000..b70da094
--- /dev/null
+++ b/src/libs/widgets/common/squeezedcombobox.cpp
@@ -0,0 +1,198 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description : a combo box with a width not depending of text
+ * content size
+ *
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file squeezedcombobox.cpp */
+
+// TQt includes.
+
+#include <tqlistbox.h>
+#include <tqcombobox.h>
+#include <tqpair.h>
+#include <tqtimer.h>
+#include <tqvaluelist.h>
+#include <tqstyle.h>
+#include <tqapplication.h>
+#include <tqtooltip.h>
+#include <tqmap.h>
+
+// Local includes.
+
+#include "squeezedcombobox.h"
+#include "squeezedcombobox.moc"
+
+namespace Digikam
+{
+
+class SqueezedComboBoxPriv
+{
+public:
+
+ SqueezedComboBoxPriv()
+ {
+ timer = 0;
+ tooltip = 0;
+ }
+
+ TQMap<int, TQString> originalItems;
+
+ TQTimer *timer;
+
+ SqueezedComboBoxTip *tooltip;
+};
+
+SqueezedComboBox::SqueezedComboBox(TQWidget *parent, const char *name)
+ : TQComboBox(parent, name)
+{
+ d = new SqueezedComboBoxPriv;
+ d->timer = new TQTimer(this);
+
+ // See B.K.O #138747 : always for TQComboBox instance to use a TQListbox to
+ // render content independently of Widget style used.
+ setListBox(new TQListBox(this));
+
+ d->tooltip = new SqueezedComboBoxTip(listBox()->viewport(), this);
+ setMinimumWidth(100);
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotTimeOut()));
+
+ connect(this, TQ_SIGNAL(activated( int )),
+ this, TQ_SLOT(slotUpdateToolTip( int )));
+}
+
+SqueezedComboBox::~SqueezedComboBox()
+{
+ delete d->tooltip;
+ delete d->timer;
+ delete d;
+}
+
+TQSize SqueezedComboBox::sizeHint() const
+{
+ constPolish();
+ TQFontMetrics fm = fontMetrics();
+
+ int maxW = count() ? 18 : 7 * fm.width(TQChar('x')) + 18;
+ int maxH = TQMAX( fm.lineSpacing(), 14 ) + 2;
+
+ return style().sizeFromContents(TQStyle::CT_ComboBox, this,
+ TQSize(maxW, maxH)).expandedTo(TQApplication::globalStrut());
+}
+
+void SqueezedComboBox::insertSqueezedItem(const TQString& newItem, int index)
+{
+ d->originalItems[index] = newItem;
+ insertItem( squeezeText(newItem), index );
+
+ // if this is the first item, set the tooltip.
+ if (index == 0)
+ slotUpdateToolTip(0);
+}
+
+void SqueezedComboBox::insertSqueezedList(const TQStringList& newItems, int index)
+{
+ for(TQStringList::const_iterator it = newItems.begin() ; it != newItems.end() ; ++it)
+ {
+ insertSqueezedItem(*it, index);
+ index++;
+ }
+}
+
+void SqueezedComboBox::resizeEvent(TQResizeEvent *)
+{
+ d->timer->start(200, true);
+}
+
+void SqueezedComboBox::slotTimeOut()
+{
+ TQMapIterator<int,TQString> it;
+ for (it = d->originalItems.begin() ; it != d->originalItems.end();
+ ++it)
+ {
+ changeItem(squeezeText(it.data()), it.key());
+ }
+}
+
+TQString SqueezedComboBox::squeezeText(const TQString& original)
+{
+ // not the complete widgetSize is usable. Need to compensate for that.
+ int widgetSize = width()-30;
+ TQFontMetrics fm(fontMetrics());
+
+ // If we can fit the full text, return that.
+ if (fm.width(original) < widgetSize)
+ return(original);
+
+ // We need to squeeze.
+ TQString sqItem = original; // prevent empty return value;
+ widgetSize = widgetSize-fm.width("...");
+ for (uint i = 0 ; i != original.length(); ++i)
+ {
+ if ((int)fm.width(original.right(i)) > widgetSize)
+ {
+ sqItem = TQString(original.left(i) + "...");
+ break;
+ }
+ }
+ return sqItem;
+}
+
+void SqueezedComboBox::slotUpdateToolTip(int index)
+{
+ TQToolTip::remove(this);
+ TQToolTip::add(this, d->originalItems[index]);
+}
+
+TQString SqueezedComboBox::itemHighlighted()
+{
+ int curItem = listBox()->currentItem();
+ return d->originalItems[curItem];
+}
+
+// ------------------------------------------------------------------------
+
+SqueezedComboBoxTip::SqueezedComboBoxTip(TQWidget *parent, SqueezedComboBox *name)
+ : TQToolTip( parent )
+{
+ m_originalWidget = name;
+}
+
+void SqueezedComboBoxTip::maybeTip(const TQPoint &pos)
+{
+ TQListBox* listBox = m_originalWidget->listBox();
+ if (!listBox)
+ return;
+
+ TQListBoxItem* selectedItem = listBox->itemAt( pos );
+ if (selectedItem)
+ {
+ TQRect positionToolTip = listBox->itemRect(selectedItem);
+ TQString toolTipText = m_originalWidget->itemHighlighted();
+ if (!toolTipText.isNull())
+ tip(positionToolTip, toolTipText);
+ }
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/squeezedcombobox.h b/src/libs/widgets/common/squeezedcombobox.h
new file mode 100644
index 00000000..7baaeca1
--- /dev/null
+++ b/src/libs/widgets/common/squeezedcombobox.h
@@ -0,0 +1,164 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-01-01
+ * Description : a combo box with a width not depending of text
+ * content size
+ *
+ * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/** @file squeezedcombobox.h */
+
+#ifndef SQUEEZEDCOMBOBOX_H
+#define SQUEEZEDCOMBOBOX_H
+
+// TQt includes.
+
+#include <tqcombobox.h>
+#include <tqtooltip.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class SqueezedComboBoxPriv;
+
+/** @class SqueezedComboBox
+ *
+ * This widget is a TQComboBox, but then a little bit
+ * different. It only shows the right part of the items
+ * depending on de size of the widget. When it is not
+ * possible to show the complete item, it will be shortened
+ * and "..." will be prepended.
+ *
+ * @image html squeezedcombobox.png "This is how it looks"
+ * @author Tom Albers
+ */
+class DIGIKAM_EXPORT SqueezedComboBox : public TQComboBox
+{
+ TQ_OBJECT
+
+
+public:
+
+ /**
+ * Constructor
+ * @param parent parent widget
+ * @param name name to give to the widget
+ */
+ SqueezedComboBox(TQWidget *parent = 0, const char *name = 0 );
+
+ /**
+ * destructor
+ */
+ virtual ~SqueezedComboBox();
+
+ /**
+ * This inserts a item to the list. See TQComboBox::insertItem()
+ * for details. Please do not use TQComboBox::insertItem() to this
+ * widget, as that will fail.
+ * @param newItem the original (long version) of the item which needs
+ * to be added to the combobox
+ * @param index the position in the widget.
+ */
+ void insertSqueezedItem(const TQString& newItem, int index);
+
+ /**
+ * This inserts items to the list. See TQComboBox::insertStringList()
+ * for details. Please do not use TQComboBox::insertStringList() to this
+ * widget, as that will fail.
+ * @param newItems the originals (long version) of the items which needs
+ * to be added to the combobox
+ * @param index the position in the widget.
+ */
+ void insertSqueezedList(const TQStringList& newItems, int index);
+
+ /**
+ * This method returns the full text (not squeezed) of the currently
+ * highlighted item.
+ * @return full text of the highlighted item
+ */
+ TQString itemHighlighted();
+
+ /**
+ * Sets the sizeHint() of this widget.
+ */
+ virtual TQSize sizeHint() const;
+
+private slots:
+
+ void slotTimeOut();
+ void slotUpdateToolTip(int index);
+
+private:
+
+ void resizeEvent(TQResizeEvent *);
+ TQString squeezeText(const TQString& original);
+
+private:
+
+ SqueezedComboBoxPriv *d;
+};
+
+// ----------------------------------------------------------------
+
+/** @class SqueezedComboBoxTip
+ * This class shows a tooltip for a SqueezedComboBox
+ * the tooltip will contain the full text and helps
+ * the user find the correct entry. It is automatically
+ * activated when starting a SqueezedComboBox. This is
+ * inherited from TQToolTip
+ *
+ * @author Tom Albers
+ */
+class SqueezedComboBoxTip : public TQToolTip
+{
+
+public:
+ /**
+ * Constructor. An example call (as done in
+ * SqueezedComboBox::SqueezedComboBox):
+ * @code
+ * t = new SqueezedComboBoxTip( this->listBox()->viewport(), this );
+ * @endcode
+ *
+ * @param parent parent widget (viewport)
+ * @param name parent widget
+ */
+ SqueezedComboBoxTip(TQWidget *parent, SqueezedComboBox *name);
+
+protected:
+ /**
+ * Reimplemented version from TQToolTip which shows the
+ * tooltip when needed.
+ * @param pos the point where the mouse currently is
+ */
+ void maybeTip(const TQPoint& pos);
+
+private:
+
+ SqueezedComboBox *m_originalWidget;
+};
+
+} // namespace Digikam
+
+#endif // SQUEEZEDCOMBOBOX_H
diff --git a/src/libs/widgets/common/statusled.cpp b/src/libs/widgets/common/statusled.cpp
new file mode 100644
index 00000000..e0475057
--- /dev/null
+++ b/src/libs/widgets/common/statusled.cpp
@@ -0,0 +1,84 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-02-15
+ * Description : a led indicator.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdeglobalsettings.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "statusled.h"
+#include "statusled.moc"
+
+namespace Digikam
+{
+
+StatusLed::StatusLed(TQWidget *parent)
+ : TQLabel(parent)
+{
+ setLedColor(Gray);
+ setFocusPolicy(TQWidget::NoFocus);
+}
+
+StatusLed::~StatusLed()
+{
+}
+
+void StatusLed::setLedColor(LedColor color)
+{
+ m_color = color;
+
+ TQString file;
+ switch(m_color)
+ {
+ case Green:
+ file = TQString("indicator-green");
+ break;
+
+ case Red:
+ file = TQString("indicator-red");
+ break;
+
+ default:
+ file = TQString("indicator-gray");
+ break;
+ }
+
+ TDEGlobal::dirs()->addResourceType(file.ascii(), TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir(file.ascii(), file + TQString(".png"));
+ setPixmap(TQPixmap(directory + file + TQString(".png")));
+}
+
+StatusLed::LedColor StatusLed::ledColor() const
+{
+ return m_color;
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/widgets/common/statusled.h b/src/libs/widgets/common/statusled.h
new file mode 100644
index 00000000..2fd94cb2
--- /dev/null
+++ b/src/libs/widgets/common/statusled.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-02-15
+ * Description : a led indicator.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef STATUS_LED_H
+#define STATUS_LED_H
+
+// TQt includes.
+
+#include <tqlabel.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class StatusNavigateBarPriv;
+
+class DIGIKAM_EXPORT StatusLed : public TQLabel
+{
+TQ_OBJECT
+
+
+public:
+
+ enum LedColor
+ {
+ Gray=0,
+ Green,
+ Red
+ };
+
+public:
+
+ StatusLed(TQWidget *parent=0);
+ ~StatusLed();
+
+ LedColor ledColor() const;
+
+public slots:
+
+ void setLedColor(LedColor color);
+
+private:
+
+ LedColor m_color;
+};
+
+} // namespace Digikam
+
+#endif /* STATUS_LED_H */
diff --git a/src/libs/widgets/common/statusnavigatebar.cpp b/src/libs/widgets/common/statusnavigatebar.cpp
new file mode 100644
index 00000000..0ebcaf84
--- /dev/null
+++ b/src/libs/widgets/common/statusnavigatebar.cpp
@@ -0,0 +1,172 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-30
+ * Description : a button bar to navigate between album items
+ * using status bar.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqtoolbutton.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <kiconloader.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "statusnavigatebar.h"
+#include "statusnavigatebar.moc"
+
+namespace Digikam
+{
+
+class StatusNavigateBarPriv
+{
+public:
+
+ StatusNavigateBarPriv()
+ {
+ firstButton = 0;
+ prevButton = 0;
+ nextButton = 0;
+ lastButton = 0;
+ itemType = StatusNavigateBar::ItemCurrent;
+ }
+
+ int itemType;
+
+ TQToolButton *firstButton;
+ TQToolButton *prevButton;
+ TQToolButton *nextButton;
+ TQToolButton *lastButton;
+};
+
+StatusNavigateBar::StatusNavigateBar(TQWidget *parent)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new StatusNavigateBarPriv;
+ setFocusPolicy(TQWidget::NoFocus);
+
+ TQHBoxLayout *lay = new TQHBoxLayout(this);
+
+ d->firstButton = new TQToolButton(this);
+ d->firstButton->setFocusPolicy(TQWidget::NoFocus);
+ d->firstButton->setAutoRaise(true);
+ d->firstButton->setIconSet(SmallIconSet("go-first"));
+ TQToolTip::add(d->firstButton, i18n("Go to the first item"));
+
+ d->prevButton = new TQToolButton(this);
+ d->prevButton->setFocusPolicy(TQWidget::NoFocus);
+ d->prevButton->setAutoRaise(true);
+ d->prevButton->setIconSet(SmallIconSet("back"));
+ TQToolTip::add(d->prevButton, i18n("Go to the previous item"));
+
+ d->nextButton = new TQToolButton(this);
+ d->nextButton->setFocusPolicy(TQWidget::NoFocus);
+ d->nextButton->setAutoRaise(true);
+ d->nextButton->setIconSet(SmallIconSet("forward"));
+ TQToolTip::add(d->nextButton, i18n("Go to the next item"));
+
+ d->lastButton = new TQToolButton(this);
+ d->lastButton->setFocusPolicy(TQWidget::NoFocus);
+ d->lastButton->setAutoRaise(true);
+ d->lastButton->setIconSet(SmallIconSet("go-last"));
+ TQToolTip::add(d->lastButton, i18n("Go to the last item"));
+
+ lay->addWidget(d->firstButton);
+ lay->addWidget(d->prevButton);
+ lay->addWidget(d->nextButton);
+ lay->addWidget(d->lastButton);
+
+ connect(d->firstButton, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalFirstItem()));
+
+ connect(d->prevButton, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalPrevItem()));
+
+ connect(d->nextButton, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalNextItem()));
+
+ connect(d->lastButton, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalLastItem()));
+}
+
+StatusNavigateBar::~StatusNavigateBar()
+{
+ delete d;
+}
+
+void StatusNavigateBar::setNavigateBarState(bool hasPrev, bool hasNext)
+{
+ if (hasPrev && hasNext)
+ setButtonsState(ItemCurrent);
+ else if (!hasPrev && hasNext)
+ setButtonsState(ItemFirst);
+ else if (hasPrev && !hasNext)
+ setButtonsState(ItemLast);
+ else
+ setButtonsState(NoNavigation);
+}
+
+void StatusNavigateBar::setButtonsState(int itemType)
+{
+ d->itemType = itemType;
+
+ if (d->itemType == ItemFirst)
+ {
+ d->firstButton->setEnabled(false);
+ d->prevButton->setEnabled(false);
+ d->nextButton->setEnabled(true);
+ d->lastButton->setEnabled(true);
+ }
+ else if (d->itemType == ItemLast)
+ {
+ d->firstButton->setEnabled(true);
+ d->prevButton->setEnabled(true);
+ d->nextButton->setEnabled(false);
+ d->lastButton->setEnabled(false);
+ }
+ else if (d->itemType == ItemCurrent)
+ {
+ d->firstButton->setEnabled(true);
+ d->prevButton->setEnabled(true);
+ d->nextButton->setEnabled(true);
+ d->lastButton->setEnabled(true);
+ }
+ else if (d->itemType == NoNavigation)
+ {
+ d->firstButton->setEnabled(false);
+ d->prevButton->setEnabled(false);
+ d->nextButton->setEnabled(false);
+ d->lastButton->setEnabled(false);
+ }
+}
+
+int StatusNavigateBar::getButtonsState()
+{
+ return (d->itemType);
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/widgets/common/statusnavigatebar.h b/src/libs/widgets/common/statusnavigatebar.h
new file mode 100644
index 00000000..7972f740
--- /dev/null
+++ b/src/libs/widgets/common/statusnavigatebar.h
@@ -0,0 +1,80 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-30
+ * Description : a button bar to navigate between album items
+ * using status bar.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef STATUS_NAVIGATE_BAR_H
+#define STATUS_NAVIGATE_BAR_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class StatusNavigateBarPriv;
+
+class DIGIKAM_EXPORT StatusNavigateBar : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ enum CurrentItemPosition
+ {
+ ItemCurrent=0,
+ ItemFirst,
+ ItemLast,
+ NoNavigation
+ };
+
+public:
+
+ StatusNavigateBar(TQWidget *parent=0);
+ ~StatusNavigateBar();
+
+ void setNavigateBarState(bool hasPrev, bool hasNext);
+ void setButtonsState(int itemType);
+ int getButtonsState();
+
+signals:
+
+ void signalFirstItem(void);
+ void signalPrevItem(void);
+ void signalNextItem(void);
+ void signalLastItem(void);
+
+private :
+
+ StatusNavigateBarPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* STATUS_NAVIGATE_BAR_H */
diff --git a/src/libs/widgets/common/statusprogressbar.cpp b/src/libs/widgets/common/statusprogressbar.cpp
new file mode 100644
index 00000000..215f81fe
--- /dev/null
+++ b/src/libs/widgets/common/statusprogressbar.cpp
@@ -0,0 +1,171 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-24
+ * Description : a progress bar used to display file access
+ * progress or a text in status bar.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqwidget.h>
+#include <tqpushbutton.h>
+
+// KDE includes.
+
+#include <ksqueezedtextlabel.h>
+#include <kprogress.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <kcursor.h>
+
+// Local includes.
+
+#include "statusprogressbar.h"
+#include "statusprogressbar.moc"
+
+namespace Digikam
+{
+
+class StatusProgressBarPriv
+{
+
+public:
+
+ enum WidgetStackEnum
+ {
+ TextLabel=0,
+ ProgressBar
+ };
+
+ StatusProgressBarPriv()
+ {
+ textLabel = 0;
+ progressBar = 0;
+ progressWidget = 0;
+ cancelButton = 0;
+ }
+
+
+ TQWidget *progressWidget;
+
+ TQPushButton *cancelButton;
+
+ KSqueezedTextLabel *textLabel;
+
+ KProgress *progressBar;
+};
+
+StatusProgressBar::StatusProgressBar(TQWidget *parent)
+ : TQWidgetStack(parent, 0, TQt::WDestructiveClose)
+{
+ d = new StatusProgressBarPriv;
+ setFocusPolicy(TQWidget::NoFocus);
+
+ d->textLabel = new KSqueezedTextLabel(this);
+ d->progressWidget = new TQWidget(this);
+ TQHBoxLayout *hBox = new TQHBoxLayout(d->progressWidget);
+ d->progressBar = new KProgress(d->progressWidget);
+ setProgressTotalSteps(100);
+ d->cancelButton = new TQPushButton(d->progressWidget);
+ d->cancelButton->setFocusPolicy(TQWidget::NoFocus);
+ d->cancelButton->setSizePolicy( TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum ) );
+ d->cancelButton->setPixmap(SmallIcon("cancel"));
+
+ // Parent widget will probably have the wait cursor set.
+ // Set arrow cursor to indicate the button can be clicked
+ d->cancelButton->setCursor(KCursor::arrowCursor());
+
+ hBox->addWidget(d->progressBar);
+ hBox->addWidget(d->cancelButton);
+
+ addWidget(d->textLabel, StatusProgressBarPriv::TextLabel);
+ addWidget(d->progressWidget, StatusProgressBarPriv::ProgressBar);
+
+ connect( d->cancelButton, TQ_SIGNAL( clicked() ),
+ this, TQ_SIGNAL( signalCancelButtonPressed() ) );
+
+ progressBarMode(TextMode);
+}
+
+StatusProgressBar::~StatusProgressBar()
+{
+ delete d;
+}
+
+void StatusProgressBar::setText(const TQString& text)
+{
+ d->textLabel->setText(text);
+}
+
+void StatusProgressBar::setAlignment(int a)
+{
+ d->textLabel->setAlignment(a);
+}
+
+int StatusProgressBar::progressValue()
+{
+ return d->progressBar->progress();
+}
+
+void StatusProgressBar::setProgressValue(int v)
+{
+ d->progressBar->setProgress(v);
+}
+
+void StatusProgressBar::setProgressTotalSteps(int v)
+{
+ d->progressBar->setTotalSteps(v);
+}
+
+int StatusProgressBar::progressTotalSteps()
+{
+ return d->progressBar->totalSteps();
+}
+
+void StatusProgressBar::setProgressText(const TQString& text)
+{
+ d->progressBar->setFormat( text + TQString ("%p%") );
+ d->progressBar->update();
+}
+
+void StatusProgressBar::progressBarMode(int mode, const TQString& text)
+{
+ if ( mode == TextMode)
+ {
+ raiseWidget(StatusProgressBarPriv::TextLabel);
+ setProgressValue(0);
+ setText( text );
+ }
+ else if ( mode == ProgressBarMode )
+ {
+ d->cancelButton->hide();
+ raiseWidget(StatusProgressBarPriv::ProgressBar);
+ setProgressText( text );
+ }
+ else // CancelProgressBarMode
+ {
+ d->cancelButton->show();
+ raiseWidget(StatusProgressBarPriv::ProgressBar);
+ setProgressText( text );
+ }
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/common/statusprogressbar.h b/src/libs/widgets/common/statusprogressbar.h
new file mode 100644
index 00000000..e227a6de
--- /dev/null
+++ b/src/libs/widgets/common/statusprogressbar.h
@@ -0,0 +1,87 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-01-24
+ * Description : a progress bar used to display file access
+ * progress or a text in status bar.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef STATUSPROGRESSBAR_H
+#define STATUSPROGRESSBAR_H
+
+// KDE includes.
+
+#include <tqwidgetstack.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class StatusProgressBarPriv;
+
+class DIGIKAM_EXPORT StatusProgressBar : public TQWidgetStack
+{
+TQ_OBJECT
+
+
+public:
+
+ enum StatusProgressBarMode
+ {
+ TextMode=0,
+ ProgressBarMode,
+ CancelProgressBarMode
+ };
+
+public:
+
+ StatusProgressBar(TQWidget *parent=0);
+ ~StatusProgressBar();
+
+ void setAlignment(int a);
+
+ void progressBarMode(int mode, const TQString& text=TQString());
+
+ int progressValue();
+
+ int progressTotalSteps();
+ void setProgressTotalSteps(int v);
+
+public slots:
+
+ void setText(const TQString& text);
+ void setProgressValue(int v);
+ void setProgressText(const TQString& text);
+
+signals:
+
+ void signalCancelButtonPressed();
+
+private:
+
+ StatusProgressBarPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* STATUSPROGRESSBAR_H */
diff --git a/src/libs/widgets/common/statuszoombar.cpp b/src/libs/widgets/common/statuszoombar.cpp
new file mode 100644
index 00000000..44302481
--- /dev/null
+++ b/src/libs/widgets/common/statuszoombar.cpp
@@ -0,0 +1,208 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-04-15
+ * Description : a zoom bar used in status bar.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtoolbutton.h>
+#include <tqtimer.h>
+#include <tqslider.h>
+#include <tqtooltip.h>
+#include <tqevent.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "thumbnailsize.h"
+#include "dcursortracker.h"
+#include "statuszoombar.h"
+#include "statuszoombar.moc"
+
+namespace Digikam
+{
+
+TQSliderReverseWheel::TQSliderReverseWheel(TQWidget *parent)
+ : TQSlider(parent)
+{
+ // empty, we just need to re-implement wheelEvent to reverse the wheel
+}
+
+TQSliderReverseWheel::~TQSliderReverseWheel()
+{
+}
+
+void TQSliderReverseWheel::wheelEvent(TQWheelEvent * e)
+{
+ if ( e->orientation() != orientation() && !rect().contains(e->pos()) )
+ return;
+
+ static float offset = 0;
+ static TQSlider* offset_owner = 0;
+ if (offset_owner != this){
+ offset_owner = this;
+ offset = 0;
+ }
+ // note: different sign in front of e->delta vs. original implementation
+ offset += e->delta()*TQMAX(pageStep(),lineStep())/120;
+ if (TQABS(offset)<1)
+ return;
+ setValue( value() + int(offset) );
+ offset -= int(offset);
+ e->accept();
+}
+
+// ----------------------------------------------------------------------
+
+class StatusZoomBarPriv
+{
+
+public:
+
+ StatusZoomBarPriv()
+ {
+ zoomTracker = 0;
+ zoomMinusButton = 0;
+ zoomPlusButton = 0;
+ zoomSlider = 0;
+ zoomTimer = 0;
+ }
+
+ TQToolButton *zoomPlusButton;
+ TQToolButton *zoomMinusButton;
+
+ TQTimer *zoomTimer;
+
+ TQSlider *zoomSlider;
+
+ DTipTracker *zoomTracker;
+};
+
+StatusZoomBar::StatusZoomBar(TQWidget *parent)
+ : TQHBox(parent, 0, TQt::WDestructiveClose)
+{
+ d = new StatusZoomBarPriv;
+ setFocusPolicy(TQWidget::NoFocus);
+
+ d->zoomMinusButton = new TQToolButton(this);
+ d->zoomMinusButton->setAutoRaise(true);
+ d->zoomMinusButton->setFocusPolicy(TQWidget::NoFocus);
+ d->zoomMinusButton->setIconSet(SmallIconSet("zoom-out"));
+ TQToolTip::add(d->zoomMinusButton, i18n("Zoom Out"));
+
+ d->zoomSlider = new TQSliderReverseWheel(this);
+ d->zoomSlider->setMinValue(ThumbnailSize::Small);
+ d->zoomSlider->setMaxValue(ThumbnailSize::Huge);
+ d->zoomSlider->setPageStep(ThumbnailSize::Step);
+ d->zoomSlider->setValue(ThumbnailSize::Medium);
+ d->zoomSlider->setOrientation(TQt::Horizontal);
+ d->zoomSlider->setLineStep(ThumbnailSize::Step);
+ d->zoomSlider->setMaximumHeight(fontMetrics().height()+2);
+ d->zoomSlider->setFixedWidth(120);
+ d->zoomSlider->setFocusPolicy(TQWidget::NoFocus);
+
+ d->zoomPlusButton = new TQToolButton(this);
+ d->zoomPlusButton->setAutoRaise(true);
+ d->zoomPlusButton->setIconSet(SmallIconSet("zoom-in"));
+ d->zoomPlusButton->setFocusPolicy(TQWidget::NoFocus);
+ TQToolTip::add(d->zoomPlusButton, i18n("Zoom In"));
+
+ d->zoomTracker = new DTipTracker("", d->zoomSlider);
+
+ // -------------------------------------------------------------
+
+ connect(d->zoomMinusButton, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalZoomMinusClicked()));
+
+ connect(d->zoomPlusButton, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalZoomPlusClicked()));
+
+ connect(d->zoomSlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SIGNAL(signalZoomSliderChanged(int)));
+
+ connect(d->zoomSlider, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotZoomSliderChanged(int)));
+
+ connect(d->zoomSlider, TQ_SIGNAL(sliderReleased()),
+ this, TQ_SLOT(slotZoomSliderReleased()));
+}
+
+StatusZoomBar::~StatusZoomBar()
+{
+ if (d->zoomTimer)
+ delete d->zoomTimer;
+
+ delete d->zoomTracker;
+ delete d;
+}
+
+void StatusZoomBar::slotZoomSliderChanged(int)
+{
+ if (d->zoomTimer)
+ {
+ d->zoomTimer->stop();
+ delete d->zoomTimer;
+ }
+
+ d->zoomTimer = new TQTimer( this );
+ connect(d->zoomTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotDelayedZoomSliderChanged()) );
+ d->zoomTimer->start(300, true);
+}
+
+void StatusZoomBar::slotDelayedZoomSliderChanged()
+{
+ emit signalDelayedZoomSliderChanged(d->zoomSlider->value());
+}
+
+void StatusZoomBar::slotZoomSliderReleased()
+{
+ emit signalZoomSliderReleased(d->zoomSlider->value());
+}
+
+void StatusZoomBar::setZoomSliderValue(int v)
+{
+ d->zoomSlider->blockSignals(true);
+ d->zoomSlider->setValue(v);
+ d->zoomSlider->blockSignals(false);
+}
+
+void StatusZoomBar::setZoomTrackerText(const TQString& text)
+{
+ d->zoomTracker->setText(text);
+}
+
+void StatusZoomBar::setEnableZoomPlus(bool e)
+{
+ d->zoomPlusButton->setEnabled(e);
+}
+
+void StatusZoomBar::setEnableZoomMinus(bool e)
+{
+ d->zoomMinusButton->setEnabled(e);
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/widgets/common/statuszoombar.h b/src/libs/widgets/common/statuszoombar.h
new file mode 100644
index 00000000..adf69416
--- /dev/null
+++ b/src/libs/widgets/common/statuszoombar.h
@@ -0,0 +1,100 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-04-15
+ * Description : a zoom bar used in status bar.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef STATUSSTATUSBAR_H
+#define STATUSSTATUSBAR_H
+
+// TQt includes.
+
+#include <tqslider.h>
+#include <tqevent.h>
+
+// KDE includes.
+
+#include <tqhbox.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class StatusZoomBarPriv;
+
+
+class DIGIKAM_EXPORT TQSliderReverseWheel : public TQSlider
+{
+
+public:
+
+ TQSliderReverseWheel(TQWidget *parent=0);
+ ~TQSliderReverseWheel();
+
+private:
+
+ void wheelEvent(TQWheelEvent *e);
+};
+
+// ----------------------------------------------------------------------
+
+class DIGIKAM_EXPORT StatusZoomBar : public TQHBox
+{
+
+TQ_OBJECT
+
+
+public:
+
+ StatusZoomBar( TQWidget *parent=0 );
+ ~StatusZoomBar();
+
+ void setEnableZoomPlus(bool e);
+ void setEnableZoomMinus(bool e);
+
+ void setZoomSliderValue(int v);
+ void setZoomTrackerText(const TQString& text);
+
+signals:
+
+ void signalZoomPlusClicked();
+ void signalZoomMinusClicked();
+ void signalZoomSliderChanged(int);
+ void signalDelayedZoomSliderChanged(int);
+ void signalZoomSliderReleased(int);
+
+private slots:
+
+ void slotZoomSliderChanged(int);
+ void slotDelayedZoomSliderChanged();
+ void slotZoomSliderReleased();
+
+private:
+
+ StatusZoomBarPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* STATUSSTATUSBAR_H */
diff --git a/src/libs/widgets/iccprofiles/Makefile.am b/src/libs/widgets/iccprofiles/Makefile.am
new file mode 100644
index 00000000..8888d326
--- /dev/null
+++ b/src/libs/widgets/iccprofiles/Makefile.am
@@ -0,0 +1,23 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libiccprofileswidgets.la
+
+libiccprofileswidgets_la_SOURCES = iccprofilewidget.cpp cietonguewidget.cpp iccpreviewwidget.cpp
+
+libiccprofileswidgets_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+libiccprofileswidgets_la_LIBADD = $(top_builddir)/src/libs/lprof/liblprof.la
+
+INCLUDES = -I$(top_srcdir)/src/libs/widgets/metadata \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/lprof \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(LIBKEXIV2_CFLAGS) \
+ $(all_includes)
+
+digikaminclude_HEADERS = cietonguewidget.h iccprofilewidget.h iccpreviewwidget.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/widgets/iccprofiles/cietonguewidget.cpp b/src/libs/widgets/iccprofiles/cietonguewidget.cpp
new file mode 100644
index 00000000..2ec738b3
--- /dev/null
+++ b/src/libs/widgets/iccprofiles/cietonguewidget.cpp
@@ -0,0 +1,816 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-10
+ * Description : a widget to display CIE tongue from
+ * an ICC profile.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Any source code are inspired from lprof project and
+ * Copyright (C) 1998-2001 Marti Maria
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqimage.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqfile.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "lcmsprf.h"
+#include "cietonguewidget.h"
+#include "cietonguewidget.moc"
+
+namespace Digikam
+{
+
+ // The following table gives the CIE colour matching functions
+ // \bar{x}(\lambda), \bar{y}(\lambda), and \bar{z}(\lambda), for
+ // wavelengths \lambda at 5 nanometre increments from 380 nm through
+ // 780 nm. This table is used in conjunction with Planck's law for
+ // the energy spectrum of a black body at a given temperature to plot
+ // the black body curve on the CIE chart.
+
+ // The following table gives the spectral chromaticity co-ordinates
+ // x(\lambda) and y(\lambda) for wavelengths in 5 nanometre increments
+ // from 380 nm through 780 nm. These co-ordinates represent the
+ // position in the CIE x-y space of pure spectral colours of the given
+ // wavelength, and thus define the outline of the CIE "tongue"
+ // diagram.
+
+ static const double spectral_chromaticity[81][3] =
+ {
+ { 0.1741, 0.0050 }, // 380 nm
+ { 0.1740, 0.0050 },
+ { 0.1738, 0.0049 },
+ { 0.1736, 0.0049 },
+ { 0.1733, 0.0048 },
+ { 0.1730, 0.0048 },
+ { 0.1726, 0.0048 },
+ { 0.1721, 0.0048 },
+ { 0.1714, 0.0051 },
+ { 0.1703, 0.0058 },
+ { 0.1689, 0.0069 },
+ { 0.1669, 0.0086 },
+ { 0.1644, 0.0109 },
+ { 0.1611, 0.0138 },
+ { 0.1566, 0.0177 },
+ { 0.1510, 0.0227 },
+ { 0.1440, 0.0297 },
+ { 0.1355, 0.0399 },
+ { 0.1241, 0.0578 },
+ { 0.1096, 0.0868 },
+ { 0.0913, 0.1327 },
+ { 0.0687, 0.2007 },
+ { 0.0454, 0.2950 },
+ { 0.0235, 0.4127 },
+ { 0.0082, 0.5384 },
+ { 0.0039, 0.6548 },
+ { 0.0139, 0.7502 },
+ { 0.0389, 0.8120 },
+ { 0.0743, 0.8338 },
+ { 0.1142, 0.8262 },
+ { 0.1547, 0.8059 },
+ { 0.1929, 0.7816 },
+ { 0.2296, 0.7543 },
+ { 0.2658, 0.7243 },
+ { 0.3016, 0.6923 },
+ { 0.3373, 0.6589 },
+ { 0.3731, 0.6245 },
+ { 0.4087, 0.5896 },
+ { 0.4441, 0.5547 },
+ { 0.4788, 0.5202 },
+ { 0.5125, 0.4866 },
+ { 0.5448, 0.4544 },
+ { 0.5752, 0.4242 },
+ { 0.6029, 0.3965 },
+ { 0.6270, 0.3725 },
+ { 0.6482, 0.3514 },
+ { 0.6658, 0.3340 },
+ { 0.6801, 0.3197 },
+ { 0.6915, 0.3083 },
+ { 0.7006, 0.2993 },
+ { 0.7079, 0.2920 },
+ { 0.7140, 0.2859 },
+ { 0.7190, 0.2809 },
+ { 0.7230, 0.2770 },
+ { 0.7260, 0.2740 },
+ { 0.7283, 0.2717 },
+ { 0.7300, 0.2700 },
+ { 0.7311, 0.2689 },
+ { 0.7320, 0.2680 },
+ { 0.7327, 0.2673 },
+ { 0.7334, 0.2666 },
+ { 0.7340, 0.2660 },
+ { 0.7344, 0.2656 },
+ { 0.7346, 0.2654 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 },
+ { 0.7347, 0.2653 } // 780 nm
+ };
+
+class CIETongueWidgetPriv
+{
+public:
+
+ CIETongueWidgetPriv()
+ {
+ hMonitorProfile = 0;
+ hXYZProfile = 0;
+ hXFORM = 0;
+
+ Measurement.Patches = 0;
+ Measurement.Allowed = 0;
+ blinkTimer = 0;
+ pos = 0;
+
+ profileDataAvailable = true;
+ loadingImageMode = false;
+ loadingImageSucess = false;
+ }
+
+ bool profileDataAvailable;
+ bool loadingImageMode;
+ bool loadingImageSucess;
+
+ double gridside;
+
+ int xBias;
+ int yBias;
+ int pxcols;
+ int pxrows;
+ int pos; // Position of animation during loading/calculation.
+
+ TQPainter painter;
+ TQPixmap pixmap;
+ TQTimer *blinkTimer;
+
+ cmsHPROFILE hMonitorProfile;
+ cmsHPROFILE hXYZProfile;
+ cmsHTRANSFORM hXFORM;
+ cmsCIExyYTRIPLE Primaries;
+ cmsCIEXYZ MediaWhite;
+
+ MEASUREMENT Measurement;
+};
+
+CIETongueWidget::CIETongueWidget(int w, int h, TQWidget *parent, cmsHPROFILE hMonitor)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new CIETongueWidgetPriv;
+ d->blinkTimer = new TQTimer( this );
+ setMinimumSize(w, h);
+ cmsErrorAction(LCMS_ERROR_SHOW);
+
+ if (hMonitor)
+ d->hMonitorProfile = hMonitor;
+ else
+ d->hMonitorProfile = cmsCreate_sRGBProfile();
+
+ d->hXYZProfile = cmsCreateXYZProfile();
+ d->hXFORM = cmsCreateTransform(d->hXYZProfile, TYPE_XYZ_16,
+ d->hMonitorProfile, TYPE_RGB_8,
+ INTENT_PERCEPTUAL, 0);
+
+ connect(d->blinkTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotBlinkTimerDone()));
+}
+
+CIETongueWidget::~CIETongueWidget()
+{
+ if (d->Measurement.Patches)
+ free(d->Measurement.Patches);
+
+ if (d->Measurement.Allowed)
+ free(d->Measurement.Allowed);
+
+ cmsDeleteTransform(d->hXFORM);
+ cmsCloseProfile(d->hXYZProfile);
+ cmsCloseProfile(d->hMonitorProfile);
+ delete d;
+}
+
+int CIETongueWidget::grids(double val) const
+{
+ return (int) floor(val * d->gridside + 0.5);
+}
+
+bool CIETongueWidget::setProfileData(const TQByteArray &profileData)
+{
+ if (!profileData.isEmpty())
+ {
+ cmsHPROFILE hProfile = cmsOpenProfileFromMem(const_cast<char*>(profileData.data()),
+ (DWORD)profileData.size());
+
+ if (!hProfile)
+ {
+ d->profileDataAvailable = false;
+ d->loadingImageSucess = false;
+ }
+ else
+ {
+ setProfile(hProfile);
+ cmsCloseProfile(hProfile);
+ d->profileDataAvailable = true;
+ d->loadingImageSucess = true;
+ }
+ }
+ else
+ {
+ d->profileDataAvailable = false;
+ d->loadingImageSucess = false;
+ }
+
+ d->loadingImageMode = false;
+
+ d->blinkTimer->stop();
+ repaint(false);
+ return (d->profileDataAvailable);
+}
+
+bool CIETongueWidget::setProfileFromFile(const KURL& file)
+{
+ if (!file.isEmpty() && file.isValid())
+ {
+ cmsHPROFILE hProfile = cmsOpenProfileFromFile(TQFile::encodeName(file.path()), "r");
+
+ if (!hProfile)
+ {
+ d->profileDataAvailable = false;
+ d->loadingImageSucess = false;
+ }
+ else
+ {
+ setProfile(hProfile);
+ cmsCloseProfile(hProfile);
+ d->profileDataAvailable = true;
+ d->loadingImageSucess = true;
+ }
+ }
+ else
+ {
+ d->profileDataAvailable = false;
+ d->loadingImageSucess = false;
+ }
+
+ d->blinkTimer->stop();
+ repaint(false);
+ return (d->profileDataAvailable);
+}
+
+void CIETongueWidget::setProfile(cmsHPROFILE hProfile)
+{
+ // Get the white point.
+
+ ZeroMemory(&(d->MediaWhite), sizeof(cmsCIEXYZ));
+ cmsTakeMediaWhitePoint(&(d->MediaWhite), hProfile);
+ cmsCIExyY White;
+ cmsXYZ2xyY(&White, &(d->MediaWhite));
+
+ // Get the colorant matrix.
+
+ ZeroMemory(&(d->Primaries), sizeof(cmsCIExyYTRIPLE));
+
+ if (cmsIsTag(hProfile, icSigRedColorantTag) &&
+ cmsIsTag(hProfile, icSigGreenColorantTag) &&
+ cmsIsTag(hProfile, icSigBlueColorantTag))
+ {
+ MAT3 Mat;
+
+ if (cmsReadICCMatrixRGB2XYZ(&Mat, hProfile))
+ {
+ // Undo chromatic adaptation
+ if (cmsAdaptMatrixFromD50(&Mat, &White))
+ {
+ cmsCIEXYZ tmp;
+
+ tmp.X = Mat.v[0].n[0];
+ tmp.Y = Mat.v[1].n[0];
+ tmp.Z = Mat.v[2].n[0];
+
+ // ScaleToWhite(&MediaWhite, &tmp);
+ cmsXYZ2xyY(&(d->Primaries.Red), &tmp);
+
+ tmp.X = Mat.v[0].n[1];
+ tmp.Y = Mat.v[1].n[1];
+ tmp.Z = Mat.v[2].n[1];
+ // ScaleToWhite(&MediaWhite, &tmp);
+ cmsXYZ2xyY(&(d->Primaries.Green), &tmp);
+
+ tmp.X = Mat.v[0].n[2];
+ tmp.Y = Mat.v[1].n[2];
+ tmp.Z = Mat.v[2].n[2];
+ // ScaleToWhite(&MediaWhite, &tmp);
+ cmsXYZ2xyY(&(d->Primaries.Blue), &tmp);
+ }
+ }
+ }
+
+ // Get target data stored in profile
+
+ ZeroMemory(&(d->Measurement), sizeof(MEASUREMENT));
+ char* CharTarget;
+ size_t CharTargetSize;
+
+ if (cmsTakeCharTargetData(hProfile, &CharTarget, &CharTargetSize))
+ {
+ LCMSHANDLE hSheet = cmsxIT8LoadFromMem(CharTarget, CharTargetSize);
+ if (hSheet != NULL)
+ {
+ cmsxPCollLoadFromSheet(&(d->Measurement), hSheet);
+ cmsxIT8Free(hSheet);
+ cmsxPCollValidatePatches(&(d->Measurement), PATCH_HAS_XYZ|PATCH_HAS_RGB);
+ }
+ }
+}
+
+void CIETongueWidget::mapPoint(int& icx, int& icy, LPcmsCIExyY xyY)
+{
+ icx = (int) floor((xyY->x * (d->pxcols - 1)) + .5);
+ icy = (int) floor(((d->pxrows - 1) - xyY->y * (d->pxrows - 1)) + .5);
+}
+
+void CIETongueWidget::biasedLine(int x1, int y1, int x2, int y2)
+{
+ d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2);
+}
+
+void CIETongueWidget::biasedText(int x, int y, TQString Txt)
+{
+ d->painter.drawText(TQPoint(d->xBias + x, y), Txt);
+}
+
+TQRgb CIETongueWidget::colorByCoord(double x, double y)
+{
+ // Get xyz components scaled from coordinates
+
+ double cx = ((double) x) / (d->pxcols - 1);
+ double cy = 1.0 - ((double) y) / (d->pxrows - 1);
+ double cz = 1.0 - cx - cy;
+
+ // Project xyz to XYZ space. Note that in this
+ // particular case we are substituting XYZ with xyz
+
+ cmsCIEXYZ XYZ = { cx , cy , cz };
+
+ WORD XYZW[3];
+ BYTE RGB[3];
+
+ cmsFloat2XYZEncoded(XYZW, &XYZ);
+ cmsDoTransform(d->hXFORM, XYZW, RGB, 1);
+
+ return tqRgb(RGB[0], RGB[1], RGB[2]);
+}
+
+void CIETongueWidget::outlineTongue()
+{
+ int lx = 0, ly = 0;
+ int fx=0, fy=0;
+
+ for (int x = 380; x <= 700; x += 5)
+ {
+ int ix = (x - 380) / 5;
+
+ cmsCIExyY p = {spectral_chromaticity[ix][0],
+ spectral_chromaticity[ix][1], 1};
+
+ int icx, icy;
+ mapPoint(icx, icy, &p);
+
+ if (x > 380)
+ {
+ biasedLine(lx, ly, icx, icy);
+ }
+ else
+ {
+ fx = icx;
+ fy = icy;
+ }
+
+ lx = icx;
+ ly = icy;
+ }
+
+ biasedLine(lx, ly, fx, fy);
+}
+
+void CIETongueWidget::fillTongue()
+{
+
+ TQImage Img = d->pixmap.convertToImage();
+
+ int x;
+
+ for (int y = 0; y < d->pxrows; y++)
+ {
+ int xe = 0;
+
+ // Find horizontal extents of tongue on this line.
+
+ for (x = 0; x < d->pxcols; x++)
+ {
+ if ((TQColor) Img.pixel(x + d->xBias, y) != TQt::black)
+ {
+ for (xe = d->pxcols - 1; xe >= x; xe--)
+ {
+ if ((TQColor) Img.pixel(xe + d->xBias, y) != TQt::black)
+ {
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (x < d->pxcols)
+ {
+ for ( ; x <= xe; x++)
+ {
+ TQRgb Color = colorByCoord(x, y);
+ Img.setPixel(x + d->xBias, y, Color);
+ }
+ }
+ }
+
+ d->pixmap.convertFromImage(Img, TQPixmap::AvoidDither );
+}
+
+void CIETongueWidget::drawTongueAxis()
+{
+ TQFont font;
+ font.setPointSize(6);
+ d->painter.setFont(font);
+
+ d->painter.setPen(tqRgb(255, 255, 255));
+
+ biasedLine(0, 0, 0, d->pxrows - 1);
+ biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1);
+
+ for (int y = 1; y <= 9; y += 1)
+ {
+ TQString s;
+ int xstart = (y * (d->pxcols - 1)) / 10;
+ int ystart = (y * (d->pxrows - 1)) / 10;
+
+ s.sprintf("0.%d", y);
+ biasedLine(xstart, d->pxrows - grids(1), xstart, d->pxrows - grids(4));
+ biasedText(xstart - grids(11), d->pxrows + grids(15), s);
+
+ s.sprintf("0.%d", 10 - y);
+ biasedLine(0, ystart, grids(3), ystart);
+ biasedText(grids(-25), ystart + grids(5), s);
+ }
+}
+
+void CIETongueWidget::drawTongueGrid()
+{
+ d->painter.setPen(tqRgb(80, 80, 80));
+
+ for (int y = 1; y <= 9; y += 1)
+ {
+ int xstart = (y * (d->pxcols - 1)) / 10;
+ int ystart = (y * (d->pxrows - 1)) / 10;
+
+ biasedLine(xstart, grids(4), xstart, d->pxrows - grids(4) - 1);
+ biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart);
+ }
+}
+
+void CIETongueWidget::drawLabels()
+{
+ TQFont font;
+ font.setPointSize(5);
+ d->painter.setFont(font);
+
+ for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10)
+ {
+ TQString wl;
+ int bx = 0, by = 0, tx, ty;
+
+ if (x < 520)
+ {
+ bx = grids(-22);
+ by = grids(2);
+ }
+ else if (x < 535)
+ {
+ bx = grids(-8);
+ by = grids(-6);
+ }
+ else
+ {
+ bx = grids(4);
+ }
+
+ int ix = (x - 380) / 5;
+
+ cmsCIExyY p = {spectral_chromaticity[ix][0],
+ spectral_chromaticity[ix][1], 1};
+
+ int icx, icy;
+ mapPoint(icx, icy, &p);
+
+ tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0));
+ ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2)));
+
+ d->painter.setPen(tqRgb(255, 255, 255));
+ biasedLine(icx, icy, tx, ty);
+
+ TQRgb Color = colorByCoord(icx, icy);
+ d->painter.setPen(Color);
+
+ wl.sprintf("%d", x);
+ biasedText(icx+bx, icy+by, wl);
+ }
+}
+
+void CIETongueWidget::drawSmallElipse(LPcmsCIExyY xyY, BYTE r, BYTE g, BYTE b, int sz)
+{
+ int icx, icy;
+
+ mapPoint(icx, icy, xyY);
+ d->painter.setPen(tqRgb(r, g, b));
+ d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz);
+}
+
+void CIETongueWidget::drawPatches()
+{
+ for (int i=0; i < d->Measurement.nPatches; i++)
+ {
+ LPPATCH p = d->Measurement.Patches + i;
+
+ if (d->Measurement.Allowed[i])
+ {
+ LPcmsCIEXYZ XYZ = &p ->XYZ;
+ cmsCIExyY xyY;
+ cmsXYZ2xyY(&xyY, XYZ);
+
+ drawSmallElipse(&xyY, 0, 0, 0, 4);
+
+ if (p->dwFlags & PATCH_HAS_XYZ_PROOF)
+ {
+ if (p->XYZ.Y < 0.03)
+ continue;
+
+ if (p->XYZProof.Y < 0.03)
+ continue;
+
+ cmsCIExyY Pt;
+ cmsXYZ2xyY(&Pt, &p->XYZProof);
+ int icx1, icx2, icy1, icy2;
+
+ mapPoint(icx1, icy1, &xyY);
+ mapPoint(icx2, icy2, &Pt);
+
+ if (icx2 < 5 || icy2 < 5 || icx1 < 5 || icy1 < 5)
+ continue;
+
+ d->painter.setPen(tqRgb(255, 255, 0));
+ biasedLine(icx1, icy1, icx2, icy2);
+ }
+ }
+ }
+}
+
+void CIETongueWidget::drawColorantTriangle()
+{
+ drawSmallElipse(&(d->Primaries.Red), 255, 128, 128, 6);
+ drawSmallElipse(&(d->Primaries.Green), 128, 255, 128, 6);
+ drawSmallElipse(&(d->Primaries.Blue), 128, 128, 255, 6);
+
+ int x1, y1, x2, y2, x3, y3;
+
+ mapPoint(x1, y1, &(d->Primaries.Red));
+ mapPoint(x2, y2, &(d->Primaries.Green));
+ mapPoint(x3, y3, &(d->Primaries.Blue));
+
+ d->painter.setPen(tqRgb(255, 255, 255));
+
+ biasedLine(x1, y1, x2, y2);
+ biasedLine(x2, y2, x3, y3);
+ biasedLine(x3, y3, x1, y1);
+}
+
+void CIETongueWidget::sweep_sRGB()
+{
+ int r, g, b;
+ cmsHPROFILE hXYZ, hsRGB;
+
+ hXYZ = cmsCreateXYZProfile();
+ hsRGB = cmsCreate_sRGBProfile();
+
+ cmsHTRANSFORM xform = cmsCreateTransform(hsRGB, TYPE_RGB_16, hXYZ, TYPE_XYZ_16,
+ INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_NOTPRECALC);
+ WORD RGB[3], XYZ[3];
+ cmsCIEXYZ xyz, MediaWhite;
+ cmsCIExyY xyY, WhitePt;
+ int x1, y1;
+
+ cmsTakeMediaWhitePoint(&MediaWhite, hsRGB);
+ cmsXYZ2xyY(&WhitePt, &MediaWhite);
+
+ for (r=0; r < 65536; r += 1024)
+ {
+ for (g=0; g < 65536; g += 1024)
+ {
+ for (b=0; b < 65536; b += 1024)
+ {
+ RGB[0] = r; RGB[1] = g; RGB[2] = b;
+ cmsDoTransform(xform, RGB, XYZ, 1);
+ cmsXYZEncoded2Float(&xyz, XYZ);
+ cmsXYZ2xyY(&xyY, &xyz);
+ mapPoint(x1, y1, &xyY);
+ d->painter.drawPoint(x1 + d->xBias, y1);
+ }
+ }
+ }
+
+ cmsDeleteTransform(xform);
+ cmsCloseProfile(hXYZ);
+ cmsCloseProfile(hsRGB);
+}
+
+void CIETongueWidget::drawWhitePoint()
+{
+ cmsCIExyY Whitem_pntxyY;
+ cmsXYZ2xyY(&Whitem_pntxyY, &(d->MediaWhite));
+ drawSmallElipse(&Whitem_pntxyY, 255, 255, 255, 8);
+}
+
+void CIETongueWidget::loadingStarted()
+{
+ d->pos = 0;
+ d->loadingImageMode = true;
+ d->loadingImageSucess = false;
+ repaint(false);
+ d->blinkTimer->start(200);
+}
+
+void CIETongueWidget::loadingFailed()
+{
+ d->blinkTimer->stop();
+ d->pos = 0;
+ d->loadingImageMode = false;
+ d->loadingImageSucess = false;
+ repaint(false);
+}
+
+void CIETongueWidget::paintEvent(TQPaintEvent*)
+{
+ d->pixmap = TQPixmap(size());
+ d->pixmap.setOptimization(TQPixmap::BestOptim);
+
+ // Widget is disable : drawing grayed frame.
+
+ if ( !isEnabled() )
+ {
+ d->painter.begin(&d->pixmap);
+ d->painter.fillRect(0, 0, size().width(), size().height(), palette().disabled().background());
+ d->painter.setPen(TQPen(palette().disabled().foreground(), 1, TQt::SolidLine));
+ d->painter.drawRect(0, 0, width(), height());
+ d->painter.end();
+ bitBlt(this, 0, 0, &d->pixmap);
+ return;
+ }
+
+ // Loading image mode.
+
+ if (d->loadingImageMode && !d->loadingImageSucess)
+ {
+ // In first, we draw an animation.
+
+ int asize = 24;
+ TQPixmap anim(asize, asize);
+ TQPainter p2;
+ p2.begin(&anim, this);
+ p2.fillRect(0, 0, asize, asize, palette().active().background());
+ p2.translate(asize/2, asize/2);
+
+ d->pos = (d->pos + 10) % 360;
+ p2.setPen(TQPen(palette().active().text()));
+ p2.rotate(d->pos);
+ for ( int i=0 ; i<12 ; i++ )
+ {
+ p2.drawLine(asize/2-5, 0, asize/2-2, 0);
+ p2.rotate(30);
+ }
+ p2.end();
+
+ // ... and we render busy text.
+
+ d->painter.begin(&d->pixmap);
+ d->painter.fillRect(0, 0, size().width(), size().height(), palette().active().background());
+ d->painter.drawPixmap(width()/2 - asize /2, asize, anim);
+ d->painter.setPen(TQPen(palette().active().text(), 1, TQt::SolidLine));
+ d->painter.drawRect(0, 0, width(), height());
+ d->painter.drawText(0, 0, size().width(), size().height(), TQt::AlignCenter,
+ i18n("Loading image..."));
+
+ d->painter.end();
+ bitBlt(this, 0, 0, &d->pixmap);
+ return;
+ }
+
+ // No profile data to show.
+
+ if (!d->profileDataAvailable || (!d->loadingImageMode && !d->loadingImageSucess))
+ {
+ d->painter.begin(&d->pixmap);
+ d->painter.fillRect(0, 0, size().width(), size().height(), palette().active().background());
+ d->painter.setPen(TQPen(palette().active().text(), 1, TQt::SolidLine));
+ d->painter.drawRect(0, 0, width(), height());
+ d->painter.drawText(0, 0, size().width(), size().height(), TQt::AlignCenter,
+ i18n("No profile available..."));
+
+ d->painter.end();
+ bitBlt(this, 0, 0, &d->pixmap);
+ return;
+ }
+
+ // Draw the CIE tongue curve.
+
+ d->pixmap.fill(TQt::black);
+ d->painter.begin(&d->pixmap);
+
+ int pixcols = d->pixmap.width();
+ int pixrows = d->pixmap.height();
+
+ d->gridside = (TQMIN(pixcols, pixrows)) / 512.0;
+ d->xBias = grids(32);
+ d->yBias = grids(20);
+ d->pxcols = pixcols - d->xBias;
+ d->pxrows = pixrows - d->yBias;
+
+ d->painter.setBackgroundColor(tqRgb(0, 0, 0));
+ d->painter.setPen(tqRgb(255, 255, 255));
+
+ outlineTongue();
+ fillTongue();
+
+ drawTongueAxis();
+ drawLabels();
+ drawTongueGrid();
+
+ if (d->MediaWhite.Y > 0.0)
+ drawWhitePoint();
+
+ if (d->Primaries.Red.Y != 0.0)
+ drawColorantTriangle();
+
+ if (d->Measurement.Patches && d->Measurement.Allowed)
+ drawPatches();
+
+ d->painter.end();
+
+ bitBlt(this, 0, 0, &d->pixmap);
+}
+
+void CIETongueWidget::slotBlinkTimerDone()
+{
+ repaint(false);
+ d->blinkTimer->start( 200 );
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/iccprofiles/cietonguewidget.h b/src/libs/widgets/iccprofiles/cietonguewidget.h
new file mode 100644
index 00000000..9fc8503d
--- /dev/null
+++ b/src/libs/widgets/iccprofiles/cietonguewidget.h
@@ -0,0 +1,115 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-10
+ * Description : a widget to display CIE tongue from
+ * an ICC profile.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * Any source code are inspired from lprof project and
+ * Copyright (C) 1998-2001 Marti Maria
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef CIETONGUEWIDGET_H
+#define CIETONGUEWIDGET_H
+
+#include <config.h>
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqcolor.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// lcms includes.
+
+#include LCMS_HEADER
+#if LCMS_VERSION < 114
+#define cmsTakeCopyright(profile) "Unknown"
+#endif // LCMS_VERSION < 114
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class CIETongueWidgetPriv;
+
+class DIGIKAM_EXPORT CIETongueWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ CIETongueWidget(int w, int h, TQWidget *parent=0, cmsHPROFILE hMonitor=0);
+ ~CIETongueWidget();
+
+ bool setProfileData(const TQByteArray& profileData=TQByteArray());
+ bool setProfileFromFile(const KURL& file=KURL());
+
+ void loadingStarted();
+ void loadingFailed();
+
+protected:
+
+ int grids(double val) const;
+
+ void outlineTongue();
+ void fillTongue();
+ void drawTongueAxis();
+ void drawTongueGrid();
+ void drawLabels();
+
+ TQRgb colorByCoord(double x, double y);
+ void drawSmallElipse(LPcmsCIExyY xyY, BYTE r, BYTE g, BYTE b, int sz);
+
+ void paintEvent( TQPaintEvent * );
+
+private:
+
+ void drawColorantTriangle();
+ void drawWhitePoint();
+ void drawPatches();
+
+ void mapPoint(int& icx, int& icy, LPcmsCIExyY xyY);
+ void biasedLine(int x1, int y1, int x2, int y2);
+ void biasedText(int x, int y, TQString Txt);
+
+ void sweep_sRGB();
+
+ void setProfile(cmsHPROFILE hProfile);
+
+private slots:
+
+ void slotBlinkTimerDone();
+
+private :
+
+ CIETongueWidgetPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif /* CIETONGUEWIDGET_H */
diff --git a/src/libs/widgets/iccprofiles/iccpreviewwidget.cpp b/src/libs/widgets/iccprofiles/iccpreviewwidget.cpp
new file mode 100644
index 00000000..3398b37f
--- /dev/null
+++ b/src/libs/widgets/iccprofiles/iccpreviewwidget.cpp
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-12
+ * Description : a widget to display ICC profiles descriptions
+ * in file dialog preview.
+ *
+ * Copyright (C) 2006-2007 by Francisco J. Cruz <fj.cruz@supercable.es>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfileinfo.h>
+#include <tqlayout.h>
+#include <tqvgroupbox.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "iccprofilewidget.h"
+#include "iccpreviewwidget.h"
+#include "iccpreviewwidget.moc"
+
+namespace Digikam
+{
+
+ICCPreviewWidget::ICCPreviewWidget(TQWidget *parent)
+ : KPreviewWidgetBase( parent )
+{
+ TQVBoxLayout *layout = new TQVBoxLayout( this );
+ TQVGroupBox *box = new TQVGroupBox( this );
+ box->setInsideMargin(0);
+ box->setFrameStyle(TQFrame::NoFrame|TQFrame::Plain);
+ m_iccProfileWidget = new ICCProfileWidget(box);
+ layout->addWidget( box );
+}
+
+ICCPreviewWidget::~ICCPreviewWidget()
+{
+}
+
+void ICCPreviewWidget::showPreview( const KURL &url)
+{
+ clearPreview();
+ TQFileInfo fInfo(url.path());
+
+ if ( url.isLocalFile() && fInfo.isFile() && fInfo.isReadable() )
+ {
+ DDebug() << url << " is a readble local file" << endl;
+ m_iccProfileWidget->loadFromURL(url);
+ }
+ else
+ {
+ DDebug() << url << " is not a readable local file" << endl;
+ }
+}
+
+void ICCPreviewWidget::clearPreview()
+{
+ m_iccProfileWidget->loadFromURL(KURL());
+}
+
+} // namespace Digikam
+
+
diff --git a/src/libs/widgets/iccprofiles/iccpreviewwidget.h b/src/libs/widgets/iccprofiles/iccpreviewwidget.h
new file mode 100644
index 00000000..89b1f4b5
--- /dev/null
+++ b/src/libs/widgets/iccprofiles/iccpreviewwidget.h
@@ -0,0 +1,71 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-12
+ * Description : a widget to display ICC profiles descriptions
+ * in file dialog preview.
+ *
+ * Copyright (C) 2006-2007 by Francisco J. Cruz <fj.cruz@supercable.es>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICCPREVIEWWIDGET_H
+#define ICCPREVIEWWIDGET_H
+
+// KDE includes.
+
+#include <kpreviewwidgetbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class KURL;
+
+namespace Digikam
+{
+
+class ICCProfileWidget;
+
+class DIGIKAM_EXPORT ICCPreviewWidget : public KPreviewWidgetBase
+{
+
+TQ_OBJECT
+
+
+public:
+
+ ICCPreviewWidget(TQWidget *parent);
+ ~ICCPreviewWidget();
+
+public slots:
+
+ virtual void showPreview(const KURL &url);
+
+protected:
+
+ virtual void clearPreview();
+ virtual void virtual_hook(int, void*){};
+
+private :
+
+ ICCProfileWidget *m_iccProfileWidget;
+
+};
+
+} // namespace Digikam
+
+#endif /* ICCPREVIEWWIDGET_H */
diff --git a/src/libs/widgets/iccprofiles/iccprofilewidget.cpp b/src/libs/widgets/iccprofiles/iccprofilewidget.cpp
new file mode 100644
index 00000000..2e5152f7
--- /dev/null
+++ b/src/libs/widgets/iccprofiles/iccprofilewidget.cpp
@@ -0,0 +1,448 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-23
+ * Description : a tab widget to display ICC profile infos
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#include <config.h>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqwhatsthis.h>
+#include <tqlabel.h>
+#include <tqmap.h>
+#include <tqhbox.h>
+#include <tqfile.h>
+#include <tqcombobox.h>
+#include <tqgroupbox.h>
+#include <tqmap.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+// Lcms includes.
+
+#include LCMS_HEADER
+#if LCMS_VERSION < 114
+#define cmsTakeCopyright(profile) "Unknown"
+#endif // LCMS_VERSION < 114
+
+// Local includes.
+
+#include "ddebug.h"
+#include "metadatalistview.h"
+#include "cietonguewidget.h"
+#include "iccprofilewidget.h"
+#include "iccprofilewidget.moc"
+
+namespace Digikam
+{
+
+static const char* ICCHumanList[] =
+{
+ "ColorSpace",
+ "Copyright",
+ "DeviceClass",
+ "Name",
+ "Description",
+ "RenderingIntent",
+ "-1"
+};
+
+// This entry list is only require for compatibility with MetadataWidget implementation.
+static const char* ICCEntryList[] =
+{
+ "Header",
+ "-1"
+};
+
+class ICCTagInfo
+{
+
+public:
+
+ ICCTagInfo(){}
+
+ ICCTagInfo(const TQString& title, const TQString& description)
+ : m_title(title), m_description(description){}
+
+ TQString title() const { return m_title; }
+ TQString description() const { return m_description; }
+
+private:
+
+ TQString m_title;
+ TQString m_description;
+};
+
+typedef TQMap<TQString, ICCTagInfo> ICCTagInfoMap;
+
+class ICCProfileWidgetPriv
+{
+
+public:
+
+ ICCProfileWidgetPriv()
+ {
+ cieTongue = 0;
+ }
+
+ TQStringList tagsfilter;
+ TQStringList keysFilter;
+
+ CIETongueWidget *cieTongue;
+
+ ICCTagInfoMap iccTagsDescription;
+};
+
+
+ICCProfileWidget::ICCProfileWidget(TQWidget* parent, const char* name, int w, int h)
+ : MetadataWidget(parent, name)
+{
+ cmsErrorAction(LCMS_ERROR_SHOW);
+
+ d = new ICCProfileWidgetPriv;
+
+ // Set the translated ICC tags titles/descriptions list
+ d->iccTagsDescription["Icc.Header.Name"] = ICCTagInfo(i18n("Name"),
+ i18n("The ICC profile product name"));
+ d->iccTagsDescription["Icc.Header.Description"] = ICCTagInfo(i18n("Description"),
+ i18n("The ICC profile product description"));
+ d->iccTagsDescription["Icc.Header.Information"] = ICCTagInfo(i18n("Information"),
+ i18n("Additional ICC profile information"));
+ d->iccTagsDescription["Icc.Header.Manufacturer"] = ICCTagInfo(i18n("Manufacturer"),
+ i18n("Raw information about the ICC profile manufacturer"));
+ d->iccTagsDescription["Icc.Header.Model"] = ICCTagInfo(i18n("Model"),
+ i18n("Raw information about the ICC profile model"));
+ d->iccTagsDescription["Icc.Header.Copyright"] = ICCTagInfo(i18n("Copyright"),
+ i18n("Raw information about the ICC profile copyright"));
+ d->iccTagsDescription["Icc.Header.ProfileID"] = ICCTagInfo(i18n("Profile ID"),
+ i18n("The ICC profile ID number"));
+ d->iccTagsDescription["Icc.Header.ColorSpace"] = ICCTagInfo(i18n("Color Space"),
+ i18n("The color space used by the ICC profile"));
+ d->iccTagsDescription["Icc.Header.ConnectionSpace"] = ICCTagInfo(i18n("Connection Space"),
+ i18n("The connection space used by the ICC profile"));
+ d->iccTagsDescription["Icc.Header.DeviceClass"] = ICCTagInfo(i18n("Device Class"),
+ i18n("The ICC profile device class"));
+ d->iccTagsDescription["Icc.Header.RenderingIntent"] = ICCTagInfo(i18n("Rendering Intent"),
+ i18n("The ICC profile rendering intent"));
+ d->iccTagsDescription["Icc.Header.ProfileVersion"] = ICCTagInfo(i18n("Profile Version"),
+ i18n("The ICC version used to record the profile"));
+ d->iccTagsDescription["Icc.Header.CMMFlags"] = ICCTagInfo(i18n("CMM Flags"),
+ i18n("The ICC profile color management flags"));
+
+ // Set the list of keys and tags filters.
+ for (int i=0 ; TQString(ICCEntryList[i]) != TQString("-1") ; i++)
+ d->keysFilter << ICCEntryList[i];
+
+ for (int i=0 ; TQString(ICCHumanList[i]) != TQString("-1") ; i++)
+ d->tagsfilter << ICCHumanList[i];
+
+ // Add CIE tongue graph to the widget area
+
+ d->cieTongue = new CIETongueWidget(w, h, this);
+ TQWhatsThis::add( d->cieTongue, i18n("<p>This area contains a CIE or chromaticity diagram. "
+ "A CIE diagram is a representation of all the colors "
+ "that a person with normal vision can see. This is represented "
+ "by the colored sail-shaped area. In addition you will see a "
+ "triangle that is superimposed on the diagram outlined in white. "
+ "This triangle represents the outer boundaries of the color space "
+ "of the device that is characterized by the inspected profile. "
+ "This is called the device gamut.<p>"
+ "In addition there are black dots and yellow lines on the diagram. "
+ "Each black dot represents one of the measurement points that were "
+ "used to create this profile. The yellow line represents the "
+ "amount that each point is corrected by the profile, and the "
+ "direction of this correction."));
+
+ setUserAreaWidget(d->cieTongue);
+ decodeMetadata();
+}
+
+ICCProfileWidget::~ICCProfileWidget()
+{
+ delete d;
+}
+
+void ICCProfileWidget::setDataLoading()
+{
+ d->cieTongue->loadingStarted();
+}
+
+void ICCProfileWidget::setLoadingFailed()
+{
+ d->cieTongue->loadingFailed();
+}
+
+TQString ICCProfileWidget::getMetadataTitle()
+{
+ return i18n("ICC Color Profile Information");
+}
+
+bool ICCProfileWidget::loadFromURL(const KURL& url)
+{
+ setFileName(url.path());
+
+ if (url.isEmpty())
+ {
+ setMetadata();
+ d->cieTongue->setProfileData();
+ return false;
+ }
+ else
+ {
+ TQFile file(url.path());
+ if ( !file.open(IO_ReadOnly) )
+ {
+ setMetadata();
+ d->cieTongue->setProfileData();
+ return false;
+ }
+
+ TQByteArray iccData(file.size());
+ TQDataStream stream( &file );
+ stream.readRawBytes(iccData.data(), iccData.size());
+ file.close();
+
+ if (iccData.isEmpty())
+ {
+ setMetadata();
+ d->cieTongue->setProfileData();
+ return false;
+ }
+ else
+ {
+ setMetadata(iccData);
+ d->cieTongue->setProfileData(iccData);
+ }
+ }
+
+ return true;
+}
+
+bool ICCProfileWidget::decodeMetadata()
+{
+ TQByteArray iccData = getMetadata();
+ if (iccData.isNull())
+ return false;
+
+ d->cieTongue->setProfileData(iccData);
+
+ cmsHPROFILE hProfile = cmsOpenProfileFromMem(iccData.data(), (DWORD)iccData.size());
+
+ if (!hProfile)
+ {
+ DDebug() << "Cannot parse ICC profile tags using LCMS" << endl;
+ return false;
+ }
+
+ DMetadata::MetaDataMap metaDataMap;
+
+ if ( !TQString(cmsTakeProductName(hProfile)).isEmpty() )
+ metaDataMap.insert("Icc.Header.Name", TQString(cmsTakeProductName(hProfile)).replace("\n", " "));
+
+ if ( !TQString(cmsTakeProductDesc(hProfile)).isEmpty() )
+ metaDataMap.insert("Icc.Header.Description", TQString(cmsTakeProductDesc(hProfile)).replace("\n", " "));
+
+ if ( !TQString(cmsTakeProductInfo(hProfile)).isEmpty() )
+ metaDataMap.insert("Icc.Header.Information", TQString(cmsTakeProductInfo(hProfile)).replace("\n", " "));
+
+ if ( !TQString(cmsTakeManufacturer(hProfile)).isEmpty() )
+ metaDataMap.insert("Icc.Header.Manufacturer", TQString(cmsTakeManufacturer(hProfile)).replace("\n", " "));
+
+ if ( !TQString(cmsTakeModel(hProfile)).isEmpty() )
+ metaDataMap.insert("Icc.Header.Model", TQString(cmsTakeModel(hProfile)).replace("\n", " "));
+
+ if ( !TQString(cmsTakeCopyright(hProfile)).isEmpty() )
+ metaDataMap.insert("Icc.Header.Copyright", TQString(cmsTakeCopyright(hProfile)).replace("\n", " "));
+
+ metaDataMap.insert("Icc.Header.ProfileID", TQString::number((uint)*cmsTakeProfileID(hProfile)));
+ metaDataMap.insert("Icc.Header.ProfileVersion", TQString::number((uint)cmsGetProfileICCversion(hProfile)));
+ metaDataMap.insert("Icc.Header.CMMFlags", TQString::number((uint)cmsTakeHeaderFlags(hProfile)));
+
+ TQString colorSpace;
+ switch (cmsGetColorSpace(hProfile))
+ {
+ case icSigLabData:
+ colorSpace = i18n("Lab");
+ break;
+ case icSigLuvData:
+ colorSpace = i18n("Luv");
+ break;
+ case icSigRgbData:
+ colorSpace = i18n("RGB");
+ break;
+ case icSigGrayData:
+ colorSpace = i18n("GRAY");
+ break;
+ case icSigHsvData:
+ colorSpace = i18n("HSV");
+ break;
+ case icSigHlsData:
+ colorSpace = i18n("HLS");
+ break;
+ case icSigCmykData:
+ colorSpace = i18n("CMYK");
+ break;
+ case icSigCmyData:
+ colorSpace= i18n("CMY");
+ break;
+ default:
+ colorSpace = i18n("Unknown");
+ break;
+ }
+ metaDataMap.insert("Icc.Header.ColorSpace", colorSpace);
+
+ TQString connectionSpace;
+ switch (cmsGetPCS(hProfile))
+ {
+ case icSigLabData:
+ connectionSpace = i18n("Lab");
+ break;
+ case icSigLuvData:
+ connectionSpace = i18n("Luv");
+ break;
+ case icSigRgbData:
+ connectionSpace = i18n("RGB");
+ break;
+ case icSigGrayData:
+ connectionSpace = i18n("GRAY");
+ break;
+ case icSigHsvData:
+ connectionSpace = i18n("HSV");
+ break;
+ case icSigHlsData:
+ connectionSpace = i18n("HLS");
+ break;
+ case icSigCmykData:
+ connectionSpace = i18n("CMYK");
+ break;
+ case icSigCmyData:
+ connectionSpace= i18n("CMY");
+ break;
+ default:
+ connectionSpace = i18n("Unknown");
+ break;
+ }
+ metaDataMap.insert("Icc.Header.ConnectionSpace", connectionSpace);
+
+ TQString device;
+ switch ((int)cmsGetDeviceClass(hProfile))
+ {
+ case icSigInputClass:
+ device = i18n("Input device");
+ break;
+ case icSigDisplayClass:
+ device = i18n("Display device");
+ break;
+ case icSigOutputClass:
+ device = i18n("Output device");
+ break;
+ case icSigColorSpaceClass:
+ device = i18n("Color space");
+ break;
+ case icSigLinkClass:
+ device = i18n("Link device");
+ break;
+ case icSigAbstractClass:
+ device = i18n("Abstract");
+ break;
+ case icSigNamedColorClass:
+ device = i18n("Named color");
+ break;
+ default:
+ device = i18n("Unknown");
+ break;
+ }
+ metaDataMap.insert("Icc.Header.DeviceClass", device);
+
+ TQString intent;
+ switch (cmsTakeRenderingIntent(hProfile))
+ {
+ case 0:
+ intent = i18n("Perceptual");
+ break;
+ case 1:
+ intent = i18n("Relative Colorimetric");
+ break;
+ case 2:
+ intent = i18n("Saturation");
+ break;
+ case 3:
+ intent = i18n("Absolute Colorimetric");
+ break;
+ default:
+ intent = i18n("Unknown");
+ break;
+ }
+ metaDataMap.insert("Icc.Header.RenderingIntent", intent);
+
+ cmsCloseProfile(hProfile);
+
+ // Update all metadata contents.
+ setMetadataMap(metaDataMap);
+ return true;
+}
+
+void ICCProfileWidget::buildView()
+{
+ if (getMode() == SIMPLE)
+ {
+ setIfdList(getMetadataMap(), d->keysFilter, d->tagsfilter);
+ }
+ else
+ {
+ setIfdList(getMetadataMap(), d->keysFilter, TQStringList());
+ }
+
+ MetadataWidget::buildView();
+}
+
+TQString ICCProfileWidget::getTagTitle(const TQString& key)
+{
+ ICCTagInfoMap::Iterator it = d->iccTagsDescription.find(key);
+ if (it != d->iccTagsDescription.end())
+ return(it.data().title());
+
+ return key.section('.', 2, 2);
+}
+
+void ICCProfileWidget::slotSaveMetadataToFile()
+{
+ KURL url = saveMetadataToFile(i18n("ICC color profile File to Save"),
+ TQString("*.icc *.icm|"+i18n("ICC Files (*.icc; *.icm)")));
+ storeMetadataToFile(url);
+}
+
+TQString ICCProfileWidget::getTagDescription(const TQString& key)
+{
+ ICCTagInfoMap::Iterator it = d->iccTagsDescription.find(key);
+ if (it != d->iccTagsDescription.end())
+ return(it.data().description());
+
+ return key.section('.', 2, 2);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/iccprofiles/iccprofilewidget.h b/src/libs/widgets/iccprofiles/iccprofilewidget.h
new file mode 100644
index 00000000..d753f30e
--- /dev/null
+++ b/src/libs/widgets/iccprofiles/iccprofilewidget.h
@@ -0,0 +1,79 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-06-23
+ * Description : a tab widget to display ICC profile infos
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICCPROFILEWIDGET_H
+#define ICCPROFILEWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "metadatawidget.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ICCProfileWidgetPriv;
+
+class DIGIKAM_EXPORT ICCProfileWidget : public MetadataWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ ICCProfileWidget(TQWidget* parent, const char* name=0, int w=256, int h=256);
+ ~ICCProfileWidget();
+
+ bool loadFromURL(const KURL& url);
+
+ TQString getTagDescription(const TQString& key);
+ TQString getTagTitle(const TQString& key);
+
+ TQString getMetadataTitle();
+
+ void setLoadingFailed();
+ void setDataLoading();
+
+protected slots:
+
+ virtual void slotSaveMetadataToFile();
+
+private:
+
+ bool decodeMetadata();
+ void buildView();
+
+private:
+
+ ICCProfileWidgetPriv *d;
+
+};
+
+} // namespace Digikam
+
+#endif /* ICCPROFILEWIDGET_H */
diff --git a/src/libs/widgets/imageplugins/Makefile.am b/src/libs/widgets/imageplugins/Makefile.am
new file mode 100644
index 00000000..d223cb05
--- /dev/null
+++ b/src/libs/widgets/imageplugins/Makefile.am
@@ -0,0 +1,22 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libimagepluginswidgets.la
+
+libimagepluginswidgets_la_SOURCES = imageregionwidget.cpp imagepaniconwidget.cpp imageguidewidget.cpp \
+ imagewidget.cpp listboxpreviewitem.cpp imagepanelwidget.cpp
+
+libimagepluginswidgets_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+digikaminclude_HEADERS = imageregionwidget.h imagepaniconwidget.h \
+ imagepanelwidget.h imageguidewidget.h \
+ listboxpreviewitem.h imagewidget.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/libs/widgets/imageplugins/imageguidewidget.cpp b/src/libs/widgets/imageplugins/imageguidewidget.cpp
new file mode 100644
index 00000000..4ed7f254
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imageguidewidget.cpp
@@ -0,0 +1,625 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-16
+ * Description : a widget to display an image with guides
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqregion.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqpixmap.h>
+#include <tqtooltip.h>
+#include <tqtimer.h>
+#include <tqrect.h>
+#include <tqbrush.h>
+#include <tqfont.h>
+#include <tqfontmetrics.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "imageiface.h"
+#include "imageguidewidget.h"
+#include "imageguidewidget.moc"
+
+namespace Digikam
+{
+
+class ImageGuideWidgetPriv
+{
+public:
+
+ ImageGuideWidgetPriv()
+ {
+ pixmap = 0;
+ iface = 0;
+ flicker = 0;
+ timerID = 0;
+ focus = false;
+ onMouseMovePreviewToggled = true;
+ renderingPreviewMode = ImageGuideWidget::NoPreviewMode;
+ underExposureIndicator = false;
+ overExposureIndicator = false;
+ }
+
+ bool sixteenBit;
+ bool focus;
+ bool spotVisible;
+ bool onMouseMovePreviewToggled;
+ bool underExposureIndicator;
+ bool overExposureIndicator;
+
+ int width;
+ int height;
+ int timerID;
+ int guideMode;
+ int guideSize;
+ int flicker;
+ int renderingPreviewMode;
+
+ // Current spot position in preview coordinates.
+ TQPoint spot;
+
+ TQRect rect;
+
+ TQColor guideColor;
+
+ TQPixmap *pixmap;
+
+ ImageIface *iface;
+
+ DImg preview;
+};
+
+ImageGuideWidget::ImageGuideWidget(int w, int h, TQWidget *parent,
+ bool spotVisible, int guideMode,
+ const TQColor& guideColor, int guideSize,
+ bool blink, bool useImageSelection)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new ImageGuideWidgetPriv;
+ d->spotVisible = spotVisible;
+ d->guideMode = guideMode;
+ d->guideColor = guideColor;
+ d->guideSize = guideSize;
+
+ setBackgroundMode(TQt::NoBackground);
+ setMinimumSize(w, h);
+ setMouseTracking(true);
+
+ d->iface = new ImageIface(w, h);
+ d->iface->setPreviewType(useImageSelection);
+ uchar *data = d->iface->getPreviewImage();
+ d->width = d->iface->previewWidth();
+ d->height = d->iface->previewHeight();
+ bool sixteenBit = d->iface->previewSixteenBit();
+ bool hasAlpha = d->iface->previewHasAlpha();
+ d->preview = DImg(d->width, d->height, sixteenBit, hasAlpha, data);
+ d->preview.setICCProfil( d->iface->getOriginalImg()->getICCProfil() );
+
+ delete [] data;
+
+ d->pixmap = new TQPixmap(w, h);
+ d->rect = TQRect(w/2-d->width/2, h/2-d->height/2, d->width, d->height);
+
+ resetSpotPosition();
+ setSpotVisible(d->spotVisible, blink);
+}
+
+ImageGuideWidget::~ImageGuideWidget()
+{
+ delete d->iface;
+
+ if (d->timerID)
+ killTimer(d->timerID);
+
+ if (d->pixmap)
+ delete d->pixmap;
+
+ delete d;
+}
+
+ImageIface* ImageGuideWidget::imageIface()
+{
+ return d->iface;
+}
+
+void ImageGuideWidget::slotToggleUnderExposure(bool u)
+{
+ d->underExposureIndicator = u;
+ updatePreview();
+}
+
+void ImageGuideWidget::slotToggleOverExposure(bool o)
+{
+ d->overExposureIndicator = o;
+ updatePreview();
+}
+
+void ImageGuideWidget::resetSpotPosition()
+{
+ d->spot.setX( d->width / 2 );
+ d->spot.setY( d->height / 2 );
+ updatePreview();
+}
+
+void ImageGuideWidget::slotChangeRenderingPreviewMode(int mode)
+{
+ d->renderingPreviewMode = mode;
+ updatePreview();
+}
+
+int ImageGuideWidget::getRenderingPreviewMode()
+{
+ return (d->renderingPreviewMode);
+}
+
+TQPoint ImageGuideWidget::getSpotPosition()
+{
+ return (TQPoint( (int)((float)d->spot.x() * (float)d->iface->originalWidth() / (float)d->width),
+ (int)((float)d->spot.y() * (float)d->iface->originalHeight() / (float)d->height)));
+}
+
+DColor ImageGuideWidget::getSpotColor(int getColorFrom)
+{
+ if (getColorFrom == OriginalImage) // Get point color from full original image
+ return (d->iface->getColorInfoFromOriginalImage(getSpotPosition()));
+ else if (getColorFrom == PreviewImage) // Get point color from full preview image
+ return (d->iface->getColorInfoFromPreviewImage(d->spot));
+
+ // In other cases, get point color from preview target image
+ return (d->iface->getColorInfoFromTargetPreviewImage(d->spot));
+}
+
+void ImageGuideWidget::setSpotVisible(bool spotVisible, bool blink)
+{
+ d->spotVisible = spotVisible;
+
+ if (blink)
+ {
+ if (d->spotVisible)
+ d->timerID = startTimer(800);
+ else
+ {
+ killTimer(d->timerID);
+ d->timerID = 0;
+ }
+ }
+
+ updatePreview();
+}
+
+void ImageGuideWidget::slotChangeGuideColor(const TQColor &color)
+{
+ d->guideColor = color;
+ updatePreview();
+}
+
+void ImageGuideWidget::slotChangeGuideSize(int size)
+{
+ d->guideSize = size;
+ updatePreview();
+}
+
+void ImageGuideWidget::updatePixmap()
+{
+ TQPainter p(d->pixmap);
+ TQString text;
+ TQRect textRect, fontRect;
+ TQFontMetrics fontMt = p.fontMetrics();
+ p.setPen(TQPen(TQt::red, 1)) ;
+
+ d->pixmap->fill(colorGroup().background());
+
+ if (d->renderingPreviewMode == PreviewOriginalImage ||
+ (d->renderingPreviewMode == PreviewToggleOnMouseOver && d->onMouseMovePreviewToggled == false ))
+ {
+ p.drawPixmap(d->rect, d->iface->convertToPixmap(d->preview));
+
+ text = i18n("Original");
+ fontRect = fontMt.boundingRect(0, 0, d->rect.width(), d->rect.height(), 0, text);
+ textRect.setTopLeft(TQPoint(d->rect.x() + 20, d->rect.y() + 20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2 ) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+ }
+ else if (d->renderingPreviewMode == PreviewTargetImage || d->renderingPreviewMode == NoPreviewMode ||
+ (d->renderingPreviewMode == PreviewToggleOnMouseOver && d->onMouseMovePreviewToggled == true ))
+ {
+ d->iface->paint(d->pixmap, d->rect.x(), d->rect.y(),
+ d->rect.width(), d->rect.height(),
+ d->underExposureIndicator, d->overExposureIndicator);
+
+ if (d->renderingPreviewMode == PreviewTargetImage ||
+ d->renderingPreviewMode == PreviewToggleOnMouseOver)
+ {
+ text = i18n("Target");
+ fontRect = fontMt.boundingRect(0, 0, d->rect.width(), d->rect.height(), 0, text);
+ textRect.setTopLeft(TQPoint(d->rect.x() + 20, d->rect.y() + 20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2 ) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+ }
+ }
+ else if (d->renderingPreviewMode == PreviewBothImagesVert ||
+ d->renderingPreviewMode == PreviewBothImagesVertCont)
+ {
+ if (d->renderingPreviewMode == PreviewBothImagesVert)
+ {
+ // Drawing the original image.
+ p.drawPixmap(d->rect, d->iface->convertToPixmap(d->preview));
+
+ // Drawing the target image under the original.
+ d->iface->paint(d->pixmap,
+ d->rect.x()+d->rect.width()/2,
+ d->rect.y(),
+ d->rect.width()/2,
+ d->rect.height(),
+ d->underExposureIndicator,
+ d->overExposureIndicator);
+ }
+ else
+ {
+ // Drawing the target image.
+ d->iface->paint(d->pixmap,
+ d->rect.x(),
+ d->rect.y(),
+ d->rect.width(),
+ d->rect.height(),
+ d->underExposureIndicator,
+ d->overExposureIndicator);
+
+ // Drawing the original image under the target.
+ p.drawPixmap(d->rect.x(), d->rect.y(), d->iface->convertToPixmap(d->preview),
+ 0, 0, d->rect.width()/2, d->rect.height());
+ }
+
+ // Drawing the information and others stuff.
+ p.fillRect(d->rect.right(), 0, width(), height(), colorGroup().background());
+
+ p.setPen(TQPen(TQt::white, 2, TQt::SolidLine));
+ p.drawLine(d->rect.x()+d->rect.width()/2-1,
+ d->rect.y(),
+ d->rect.x()+d->rect.width()/2-1,
+ d->rect.y()+d->rect.height());
+ p.setPen(TQPen(TQt::red, 2, TQt::DotLine));
+ p.drawLine(d->rect.x()+d->rect.width()/2-1,
+ d->rect.y(),
+ d->rect.x()+d->rect.width()/2-1,
+ d->rect.y()+d->rect.height());
+
+ p.setPen(TQPen(TQt::red, 1)) ;
+
+ text = i18n("Target");
+ fontRect = fontMt.boundingRect(0, 0, d->rect.width(), d->rect.height(), 0, text);
+ textRect.setTopLeft(TQPoint(d->rect.x() + d->rect.width()/2 + 20,
+ d->rect.y() + 20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+
+ text = i18n("Original");
+ fontRect = fontMt.boundingRect(0, 0, d->rect.width(), d->rect.height(), 0, text);
+ textRect.setTopLeft(TQPoint(d->rect.x() + 20, d->rect.y() + 20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2 ) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+ }
+ else if (d->renderingPreviewMode == PreviewBothImagesHorz ||
+ d->renderingPreviewMode == PreviewBothImagesHorzCont)
+ {
+ if (d->renderingPreviewMode == PreviewBothImagesHorz)
+ {
+ // Drawing the original image.
+ p.drawPixmap(d->rect, d->iface->convertToPixmap(d->preview));
+
+ // Drawing the target image under the original.
+ d->iface->paint(d->pixmap,
+ d->rect.x(),
+ d->rect.y()+d->rect.height()/2,
+ d->rect.width(),
+ d->rect.height()/2,
+ d->underExposureIndicator,
+ d->overExposureIndicator);
+ }
+ else
+ {
+ // Drawing the target image.
+ d->iface->paint(d->pixmap,
+ d->rect.x(),
+ d->rect.y(),
+ d->rect.width(),
+ d->rect.height(),
+ d->underExposureIndicator,
+ d->overExposureIndicator);
+
+ // Drawing the original image under the target.
+ p.drawPixmap(d->rect.x(), d->rect.y(), d->iface->convertToPixmap(d->preview),
+ 0, 0, d->rect.width(), d->rect.height()/2);
+ }
+
+ p.fillRect(0, d->rect.bottom(), width(), height(), colorGroup().background());
+
+ p.setPen(TQPen(TQt::white, 2, TQt::SolidLine));
+ p.drawLine(d->rect.x(),
+ d->rect.y()+d->rect.height()/2-1,
+ d->rect.x()+d->rect.width(),
+ d->rect.y()+d->rect.height()/2-1);
+ p.setPen(TQPen(TQt::red, 2, TQt::DotLine));
+ p.drawLine(d->rect.x(),
+ d->rect.y()+d->rect.height()/2-1,
+ d->rect.x()+d->rect.width(),
+ d->rect.y()+d->rect.height()/2-1);
+
+ p.setPen(TQPen(TQt::red, 1)) ;
+
+ text = i18n("Target");
+ fontRect = fontMt.boundingRect(0, 0, d->rect.width(), d->rect.height(), 0, text);
+ textRect.setTopLeft(TQPoint(d->rect.x() + 20,
+ d->rect.y() + d->rect.height()/2 + 20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+
+ text = i18n("Original");
+ fontRect = fontMt.boundingRect(0, 0, d->rect.width(), d->rect.height(), 0, text);
+ textRect.setTopLeft(TQPoint(d->rect.x() + 20, d->rect.y() + 20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2 ) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+ }
+
+ if (d->spotVisible)
+ {
+ // Adapt spot from image coordinate to widget coordinate.
+ int xspot = d->spot.x() + d->rect.x();
+ int yspot = d->spot.y() + d->rect.y();
+
+ switch (d->guideMode)
+ {
+ case HVGuideMode:
+ {
+ p.setPen(TQPen(TQt::white, d->guideSize, TQt::SolidLine));
+ p.drawLine(xspot, d->rect.top() + d->flicker, xspot, d->rect.bottom() - d->flicker);
+ p.drawLine(d->rect.left() + d->flicker, yspot, d->rect.right() - d->flicker, yspot);
+ p.setPen(TQPen(d->guideColor, d->guideSize, TQt::DotLine));
+ p.drawLine(xspot, d->rect.top() + d->flicker, xspot, d->rect.bottom() - d->flicker);
+ p.drawLine(d->rect.left() + d->flicker, yspot, d->rect.right() - d->flicker, yspot);
+ break;
+ }
+
+ case PickColorMode:
+ {
+ p.setPen(TQPen(d->guideColor, 1, TQt::SolidLine));
+ p.drawLine(xspot-10, yspot-10, xspot+10, yspot+10);
+ p.drawLine(xspot+10, yspot-10, xspot-10, yspot+10);
+ p.setPen(TQPen(d->guideColor, 3, TQt::SolidLine));
+ p.drawEllipse( xspot-5, yspot-5, 11, 11 );
+
+ if (d->flicker%2 != 0)
+ {
+ p.setPen(TQPen(TQt::white, 1, TQt::SolidLine));
+ p.drawEllipse( xspot-5, yspot-5, 11, 11 );
+ }
+
+ break;
+ }
+ }
+ }
+
+ p.end();
+}
+
+void ImageGuideWidget::paintEvent(TQPaintEvent*)
+{
+ bitBlt(this, 0, 0, d->pixmap);
+}
+
+void ImageGuideWidget::updatePreview()
+{
+ updatePixmap();
+ repaint(false);
+}
+
+void ImageGuideWidget::timerEvent(TQTimerEvent* e)
+{
+ if (e->timerId() == d->timerID)
+ {
+ if (d->flicker == 5) d->flicker=0;
+ else d->flicker++;
+ updatePreview();
+ }
+ else
+ TQWidget::timerEvent(e);
+}
+
+void ImageGuideWidget::resizeEvent(TQResizeEvent* e)
+{
+ blockSignals(true);
+ delete d->pixmap;
+ int w = e->size().width();
+ int h = e->size().height();
+ int old_w = d->width;
+ int old_h = d->height;
+
+ uchar *data = d->iface->setPreviewImageSize(w, h);
+ d->width = d->iface->previewWidth();
+ d->height = d->iface->previewHeight();
+ bool sixteenBit = d->iface->previewSixteenBit();
+ bool hasAlpha = d->iface->previewHasAlpha();
+ d->preview = DImg(d->width, d->height, sixteenBit, hasAlpha, data);
+ d->preview.setICCProfil( d->iface->getOriginalImg()->getICCProfil() );
+
+ delete [] data;
+
+ d->pixmap = new TQPixmap(w, h);
+ d->rect = TQRect(w/2-d->width/2, h/2-d->height/2, d->width, d->height);
+
+ d->spot.setX((int)((float)d->spot.x() * ( (float)d->width / (float)old_w)));
+ d->spot.setY((int)((float)d->spot.y() * ( (float)d->height / (float)old_h)));
+ updatePixmap();
+ blockSignals(false);
+ emit signalResized();
+}
+
+void ImageGuideWidget::mousePressEvent(TQMouseEvent* e)
+{
+ if ( !d->focus && e->button() == TQt::LeftButton &&
+ d->rect.contains( e->x(), e->y() ) && d->spotVisible )
+ {
+ d->focus = true;
+ d->spot.setX(e->x()-d->rect.x());
+ d->spot.setY(e->y()-d->rect.y());
+ updatePreview();
+ }
+}
+
+void ImageGuideWidget::mouseReleaseEvent(TQMouseEvent* e)
+{
+ if ( d->rect.contains( e->x(), e->y() ) && d->focus && d->spotVisible)
+ {
+ d->focus = false;
+ updatePreview();
+ d->spot.setX(e->x()-d->rect.x());
+ d->spot.setY(e->y()-d->rect.y());
+
+ DColor color;
+ TQPoint point = getSpotPosition();
+
+ if (d->renderingPreviewMode == PreviewOriginalImage)
+ {
+ color = getSpotColor(OriginalImage);
+ emit spotPositionChangedFromOriginal( color, d->spot );
+ }
+ else if (d->renderingPreviewMode == PreviewTargetImage || d->renderingPreviewMode == NoPreviewMode)
+ {
+ color = getSpotColor(TargetPreviewImage);
+ emit spotPositionChangedFromTarget( color, d->spot );
+ }
+ else if (d->renderingPreviewMode == PreviewBothImagesVert)
+ {
+ if (d->spot.x() > d->rect.width()/2)
+ {
+ color = getSpotColor(TargetPreviewImage);
+ emit spotPositionChangedFromTarget(color, TQPoint(d->spot.x() - d->rect.width()/2,
+ d->spot.y()));
+ }
+ else
+ {
+ color = getSpotColor(OriginalImage);
+ emit spotPositionChangedFromOriginal( color, d->spot );
+ }
+ }
+ else if (d->renderingPreviewMode == PreviewBothImagesVertCont)
+ {
+ if (d->spot.x() > d->rect.width()/2)
+ {
+ color = getSpotColor(TargetPreviewImage);
+ emit spotPositionChangedFromTarget( color, d->spot);
+ }
+ else
+ {
+ color = getSpotColor(OriginalImage);
+ emit spotPositionChangedFromOriginal( color, d->spot );
+ }
+ }
+ else if (d->renderingPreviewMode == PreviewBothImagesHorz)
+ {
+ if (d->spot.y() > d->rect.height()/2)
+ {
+ color = getSpotColor(TargetPreviewImage);
+ emit spotPositionChangedFromTarget(color, TQPoint(d->spot.x(),
+ d->spot.y() - d->rect.height()/2 ));
+ }
+ else
+ {
+ color = getSpotColor(OriginalImage);
+ emit spotPositionChangedFromOriginal( color, d->spot );
+ }
+ }
+ else if (d->renderingPreviewMode == PreviewBothImagesHorzCont)
+ {
+ if (d->spot.y() > d->rect.height()/2)
+ {
+ color = getSpotColor(TargetPreviewImage);
+ emit spotPositionChangedFromTarget( color, d->spot);
+ }
+ else
+ {
+ color = getSpotColor(OriginalImage);
+ emit spotPositionChangedFromOriginal( color, d->spot );
+ }
+ }
+ }
+}
+
+void ImageGuideWidget::mouseMoveEvent(TQMouseEvent* e)
+{
+ if ( d->rect.contains( e->x(), e->y() ) && !d->focus && d->spotVisible )
+ {
+ setCursor( KCursor::crossCursor() );
+ }
+ else if ( d->rect.contains( e->x(), e->y() ) && d->focus && d->spotVisible )
+ {
+ d->spot.setX(e->x()-d->rect.x());
+ d->spot.setY(e->y()-d->rect.y());
+ }
+ else
+ {
+ unsetCursor();
+ }
+}
+
+void ImageGuideWidget::enterEvent(TQEvent*)
+{
+ if ( !d->focus && d->renderingPreviewMode == PreviewToggleOnMouseOver )
+ {
+ d->onMouseMovePreviewToggled = false;
+ updatePixmap();
+ repaint(false);
+ }
+}
+
+void ImageGuideWidget::leaveEvent(TQEvent*)
+{
+ if ( !d->focus && d->renderingPreviewMode == PreviewToggleOnMouseOver )
+ {
+ d->onMouseMovePreviewToggled = true;
+ updatePixmap();
+ repaint(false);
+ }
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/imageplugins/imageguidewidget.h b/src/libs/widgets/imageplugins/imageguidewidget.h
new file mode 100644
index 00000000..48d6d246
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imageguidewidget.h
@@ -0,0 +1,132 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-20
+ * Description : a widget to display an image with guides
+ *
+ * Copyright (C) 2004-2008 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEGUIDEWIDGET_H
+#define IMAGEGUIDEWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqpoint.h>
+#include <tqcolor.h>
+
+// Local includes.
+
+#include "dcolor.h"
+#include "digikam_export.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+
+class DColor;
+class ImageIface;
+class ImageGuideWidgetPriv;
+
+class DIGIKAM_EXPORT ImageGuideWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ enum GuideToolMode
+ {
+ HVGuideMode=0,
+ PickColorMode
+ };
+
+ enum RenderingPreviewMode
+ {
+ PreviewOriginalImage=0, // Original image only.
+ PreviewBothImagesHorz, // Horizontal with original and target duplicated.
+ PreviewBothImagesVert, // Vertical with original and target duplicated.
+ PreviewBothImagesHorzCont, // Horizontal with original and target in contiguous.
+ PreviewBothImagesVertCont, // Vertical with original and target in contiguous.
+ PreviewTargetImage, // Target image only.
+ PreviewToggleOnMouseOver, // Original image if mouse is over image area, else target image.
+ NoPreviewMode // Target image only without information displayed.
+ };
+
+ enum ColorPointSrc
+ {
+ OriginalImage=0,
+ PreviewImage,
+ TargetPreviewImage
+ };
+
+public:
+
+ ImageGuideWidget(int w, int h, TQWidget *parent=0,
+ bool spotVisible=true, int guideMode=HVGuideMode,
+ const TQColor& guideColor=TQt::red, int guideSize=1,
+ bool blink=false, bool useImageSelection=false);
+ ~ImageGuideWidget();
+
+ ImageIface* imageIface();
+
+ TQPoint getSpotPosition();
+ DColor getSpotColor(int getColorFrom);
+ void setSpotVisible(bool spotVisible, bool blink=false);
+ int getRenderingPreviewMode();
+ void resetSpotPosition();
+ void updatePreview();
+
+public slots:
+
+ void slotChangeGuideColor(const TQColor &color);
+ void slotChangeGuideSize(int size);
+ void slotChangeRenderingPreviewMode(int mode);
+ void slotToggleUnderExposure(bool);
+ void slotToggleOverExposure(bool);
+
+signals:
+
+ void spotPositionChangedFromOriginal(const Digikam::DColor &color, const TQPoint &position);
+ void spotPositionChangedFromTarget(const Digikam::DColor &color, const TQPoint &position);
+ void signalResized();
+
+protected:
+
+ void paintEvent(TQPaintEvent*);
+ void resizeEvent(TQResizeEvent*);
+ void timerEvent(TQTimerEvent*);
+ void mousePressEvent(TQMouseEvent*);
+ void mouseReleaseEvent(TQMouseEvent*);
+ void mouseMoveEvent(TQMouseEvent*);
+ void enterEvent(TQEvent*);
+ void leaveEvent(TQEvent*);
+
+private:
+
+ void updatePixmap();
+
+private:
+
+ ImageGuideWidgetPriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEGUIDEWIDGET_H */
diff --git a/src/libs/widgets/imageplugins/imagepanelwidget.cpp b/src/libs/widgets/imageplugins/imagepanelwidget.cpp
new file mode 100644
index 00000000..4551e09e
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagepanelwidget.cpp
@@ -0,0 +1,335 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-01
+ * Description : a widget to draw a control panel image tool.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqframe.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpixmap.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqtimer.h>
+#include <tqhbuttongroup.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kdialog.h>
+#include <tdelocale.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdeconfig.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "thumbnailsize.h"
+#include "imageregionwidget.h"
+#include "imagepaniconwidget.h"
+#include "imagepanelwidget.h"
+#include "imagepanelwidget.moc"
+
+namespace Digikam
+{
+
+class ImagePanelWidgetPriv
+{
+public:
+
+ ImagePanelWidgetPriv()
+ {
+ imagePanIconWidget = 0;
+ imageRegionWidget = 0;
+ separateView = 0;
+ }
+
+ TQString settingsSection;
+
+ TQHButtonGroup *separateView;
+
+ ImagePanIconWidget *imagePanIconWidget;
+
+ ImageRegionWidget *imageRegionWidget;
+};
+
+ImagePanelWidget::ImagePanelWidget(uint w, uint h, const TQString& settingsSection,
+ ImagePanIconWidget *pan, TQWidget *parent, int separateViewMode)
+ : TQWidget(parent, 0, TQt::WDestructiveClose)
+{
+ d = new ImagePanelWidgetPriv;
+ d->settingsSection = settingsSection;
+ d->imagePanIconWidget = pan;
+ TQGridLayout *grid = new TQGridLayout(this, 2, 3);
+
+ // -------------------------------------------------------------
+
+ TQFrame *preview = new TQFrame(this);
+ TQVBoxLayout* l1 = new TQVBoxLayout(preview, 5, 0);
+ d->imageRegionWidget = new ImageRegionWidget(w, h, preview, false);
+ d->imageRegionWidget->setFrameStyle(TQFrame::NoFrame);
+ preview->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQWhatsThis::add( d->imageRegionWidget, i18n("<p>Here you can see the original clip image "
+ "which will be used for the preview computation."
+ "<p>Click and drag the mouse cursor in the "
+ "image to change the clip focus."));
+ l1->addWidget(d->imageRegionWidget, 0);
+
+ // -------------------------------------------------------------
+
+ TQString directory;
+ d->separateView = new TQHButtonGroup(this);
+ d->separateView->setExclusive(true);
+ d->separateView->setInsideMargin( 0 );
+ d->separateView->setFrameShape(TQFrame::NoFrame);
+
+ if (separateViewMode == SeparateViewDuplicate ||
+ separateViewMode == SeparateViewAll)
+ {
+ TQPushButton *duplicateHorButton = new TQPushButton( d->separateView );
+ d->separateView->insert(duplicateHorButton, ImageRegionWidget::SeparateViewDuplicateHorz);
+ TDEGlobal::dirs()->addResourceType("duplicatebothhorz", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("duplicatebothhorz", "duplicatebothhorz.png");
+ duplicateHorButton->setPixmap( TQPixmap( directory + "duplicatebothhorz.png" ) );
+ duplicateHorButton->setToggleButton(true);
+ TQWhatsThis::add( duplicateHorButton, i18n("<p>If you enable this option, you will separate the preview area "
+ "horizontally, displaying the original and target image "
+ "at the same time. The target is duplicated from the original "
+ "below the red dashed line." ) );
+
+ TQPushButton *duplicateVerButton = new TQPushButton( d->separateView );
+ d->separateView->insert(duplicateVerButton, ImageRegionWidget::SeparateViewDuplicateVert);
+ TDEGlobal::dirs()->addResourceType("duplicatebothvert", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("duplicatebothvert", "duplicatebothvert.png");
+ duplicateVerButton->setPixmap( TQPixmap( directory + "duplicatebothvert.png" ) );
+ duplicateVerButton->setToggleButton(true);
+ TQWhatsThis::add( duplicateVerButton, i18n("<p>If you enable this option, you will separate the preview area "
+ "vertically, displaying the original and target image "
+ "at the same time. The target is duplicated from the original to "
+ "the right of the red dashed line." ) );
+ }
+
+ if (separateViewMode == SeparateViewNormal ||
+ separateViewMode == SeparateViewAll)
+ {
+ TQPushButton *separateHorButton = new TQPushButton( d->separateView );
+ d->separateView->insert(separateHorButton, ImageRegionWidget::SeparateViewHorizontal);
+ TDEGlobal::dirs()->addResourceType("bothhorz", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("bothhorz", "bothhorz.png");
+ separateHorButton->setPixmap( TQPixmap( directory + "bothhorz.png" ) );
+ separateHorButton->setToggleButton(true);
+ TQWhatsThis::add( separateHorButton, i18n( "<p>If you enable this option, you will separate the preview area "
+ "horizontally, displaying the original and target image "
+ "at the same time. The original is above the "
+ "red dashed line, the target below it." ) );
+
+ TQPushButton *separateVerButton = new TQPushButton( d->separateView );
+ d->separateView->insert(separateVerButton, ImageRegionWidget::SeparateViewVertical);
+ TDEGlobal::dirs()->addResourceType("bothvert", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("bothvert", "bothvert.png");
+ separateVerButton->setPixmap( TQPixmap( directory + "bothvert.png" ) );
+ separateVerButton->setToggleButton(true);
+ TQWhatsThis::add( separateVerButton, i18n( "<p>If you enable this option, you will separate the preview area "
+ "vertically, displaying the original and target image "
+ "at the same time. The original is to the left of the "
+ "red dashed line, the target to the right of it." ) );
+ }
+
+ TQPushButton *noSeparateButton = new TQPushButton( d->separateView );
+ d->separateView->insert(noSeparateButton, ImageRegionWidget::SeparateViewNone);
+ TDEGlobal::dirs()->addResourceType("target", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("target", "target.png");
+ noSeparateButton->setPixmap( TQPixmap( directory + "target.png" ) );
+ noSeparateButton->setToggleButton(true);
+ TQWhatsThis::add( noSeparateButton, i18n( "<p>If you enable this option, the preview area will not "
+ "be separated." ) );
+
+ // -------------------------------------------------------------
+
+ grid->addMultiCellWidget(preview, 0, 1, 0, 3);
+ grid->addMultiCellWidget(d->separateView, 2, 2, 3, 3);
+ grid->setRowStretch(1, 10);
+ grid->setColStretch(1, 10);
+ grid->setMargin(0);
+ grid->setSpacing(KDialog::spacingHint());
+
+ // -------------------------------------------------------------
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotInitGui()));
+
+ // -------------------------------------------------------------
+
+ connect(d->imageRegionWidget, TQ_SIGNAL(signalContentsMovedEvent(bool)),
+ this, TQ_SLOT(slotOriginalImageRegionChanged(bool)));
+
+ connect(d->imagePanIconWidget, TQ_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
+ this, TQ_SLOT(slotSetImageRegionPosition(const TQRect&, bool)));
+
+ connect(d->imagePanIconWidget, TQ_SIGNAL(signalSelectionTakeFocus()),
+ this, TQ_SLOT(slotPanIconTakeFocus()));
+
+ connect(d->separateView, TQ_SIGNAL(released(int)),
+ d->imagePanIconWidget, TQ_SLOT(slotSeparateViewToggled(int)));
+
+ connect(d->separateView, TQ_SIGNAL(released(int)),
+ d->imageRegionWidget, TQ_SLOT(slotSeparateViewToggled(int)));
+}
+
+ImagePanelWidget::~ImagePanelWidget()
+{
+ writeSettings();
+ delete d;
+}
+
+ImageRegionWidget *ImagePanelWidget::previewWidget() const
+{
+ return d->imageRegionWidget;
+}
+
+void ImagePanelWidget::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->settingsSection);
+ int mode = config->readNumEntry("Separate View", ImageRegionWidget::SeparateViewDuplicateVert);
+ mode = TQMAX(ImageRegionWidget::SeparateViewHorizontal, mode);
+ mode = TQMIN(ImageRegionWidget::SeparateViewDuplicateHorz, mode);
+
+ d->imageRegionWidget->blockSignals(true);
+ d->imagePanIconWidget->blockSignals(true);
+ d->separateView->blockSignals(true);
+ d->imageRegionWidget->slotSeparateViewToggled( mode );
+ d->imagePanIconWidget->slotSeparateViewToggled( mode );
+ d->separateView->setButton( mode );
+ d->imageRegionWidget->blockSignals(false);
+ d->imagePanIconWidget->blockSignals(false);
+ d->separateView->blockSignals(false);
+}
+
+void ImagePanelWidget::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->settingsSection);
+ config->writeEntry( "Separate View", d->separateView->selectedId() );
+ config->sync();
+}
+
+void ImagePanelWidget::slotOriginalImageRegionChanged(bool target)
+{
+ d->imagePanIconWidget->slotZoomFactorChanged(d->imageRegionWidget->zoomFactor());
+ TQRect rect = getOriginalImageRegion();
+ d->imagePanIconWidget->setRegionSelection(rect);
+ updateSelectionInfo(rect);
+
+ if (target)
+ {
+ d->imageRegionWidget->backupPixmapRegion();
+ emit signalOriginalClipFocusChanged();
+ }
+}
+
+void ImagePanelWidget::slotZoomSliderChanged(int size)
+{
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->imageRegionWidget->zoomMin();
+ double zmax = d->imageRegionWidget->zoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ double z = a*size+b;
+
+ d->imageRegionWidget->setZoomFactorSnapped(z);
+}
+
+void ImagePanelWidget::resizeEvent(TQResizeEvent *)
+{
+ emit signalResized();
+}
+
+void ImagePanelWidget::slotInitGui()
+{
+ readSettings();
+ setCenterImageRegionPosition();
+ slotOriginalImageRegionChanged(true);
+}
+
+void ImagePanelWidget::setPanIconHighLightPoints(const TQPointArray& pt)
+{
+ d->imageRegionWidget->setHighLightPoints(pt);
+ d->imagePanIconWidget->setHighLightPoints(pt);
+}
+
+void ImagePanelWidget::slotPanIconTakeFocus()
+{
+ d->imageRegionWidget->restorePixmapRegion();
+}
+
+void ImagePanelWidget::setEnable(bool b)
+{
+ d->imageRegionWidget->setEnabled(b);
+ d->separateView->setEnabled(b);
+}
+
+TQRect ImagePanelWidget::getOriginalImageRegion()
+{
+ return ( d->imageRegionWidget->getImageRegion() );
+}
+
+TQRect ImagePanelWidget::getOriginalImageRegionToRender()
+{
+ return ( d->imageRegionWidget->getImageRegionToRender() );
+}
+
+DImg ImagePanelWidget::getOriginalRegionImage()
+{
+ return ( d->imageRegionWidget->getImageRegionImage() );
+}
+
+void ImagePanelWidget::setPreviewImage(DImg img)
+{
+ d->imageRegionWidget->updatePreviewImage(&img);
+ d->imageRegionWidget->repaintContents(false);
+}
+
+void ImagePanelWidget::setCenterImageRegionPosition()
+{
+ d->imageRegionWidget->setCenterContentsPosition();
+}
+
+void ImagePanelWidget::slotSetImageRegionPosition(const TQRect& rect, bool targetDone)
+{
+ d->imageRegionWidget->setContentsPosition(rect.x(), rect.y(), targetDone);
+}
+
+void ImagePanelWidget::updateSelectionInfo(const TQRect& rect)
+{
+ TQToolTip::add( d->imagePanIconWidget,
+ i18n("<nobr>(%1,%2)(%3x%4)</nobr>")
+ .arg(rect.left()).arg(rect.top())
+ .arg(rect.width()).arg(rect.height()));
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/imageplugins/imagepanelwidget.h b/src/libs/widgets/imageplugins/imagepanelwidget.h
new file mode 100644
index 00000000..32179da9
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagepanelwidget.h
@@ -0,0 +1,117 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-01
+ * Description : a widget to draw a control panel image tool.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPANELWIDGET_H
+#define IMAGEPANELWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqimage.h>
+#include <tqrect.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "digikam_export.h"
+
+class KProgress;
+
+namespace Digikam
+{
+
+class ImagePanelWidgetPriv;
+class ImageRegionWidget;
+class ImagePanIconWidget;
+
+class DIGIKAM_EXPORT ImagePanelWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ enum SeparateViewOptions
+ {
+ SeparateViewNormal=0,
+ SeparateViewDuplicate,
+ SeparateViewAll
+ };
+
+public:
+
+ ImagePanelWidget(uint w, uint h, const TQString& settingsSection, ImagePanIconWidget *pan,
+ TQWidget *parent=0, int separateViewMode=SeparateViewAll);
+ ~ImagePanelWidget();
+
+ TQRect getOriginalImageRegion();
+ TQRect getOriginalImageRegionToRender();
+ DImg getOriginalRegionImage();
+ void setPreviewImage(DImg img);
+ void setCenterImageRegionPosition();
+
+ void setEnable(bool b);
+
+ void setPanIconHighLightPoints(const TQPointArray& pt);
+
+ void writeSettings();
+
+ ImageRegionWidget *previewWidget() const;
+
+signals:
+
+ void signalOriginalClipFocusChanged();
+ void signalResized();
+
+public slots:
+
+ // Set the top/Left conner clip position.
+ void slotSetImageRegionPosition(const TQRect& rect, bool targetDone);
+
+ // Slot used when the original image clip focus is changed by the user.
+ void slotOriginalImageRegionChanged(bool target);
+
+protected:
+
+ void resizeEvent(TQResizeEvent *e);
+
+private slots:
+
+ void slotPanIconTakeFocus();
+ void slotInitGui();
+ void slotZoomSliderChanged(int);
+
+private:
+
+ void updateSelectionInfo(const TQRect& rect);
+ void readSettings();
+
+private:
+
+ ImagePanelWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEPANNELWIDGET_H */
diff --git a/src/libs/widgets/imageplugins/imagepaniconwidget.cpp b/src/libs/widgets/imageplugins/imagepaniconwidget.cpp
new file mode 100644
index 00000000..38564228
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagepaniconwidget.cpp
@@ -0,0 +1,198 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-22
+ * Description : a widget to display a panel to choose
+ * a rectangular image area.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpen.h>
+#include <tqtimer.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imageregionwidget.h"
+#include "imagepaniconwidget.h"
+#include "imagepaniconwidget.moc"
+
+namespace Digikam
+{
+
+class ImagePanIconWidgetPriv
+{
+public:
+
+ ImagePanIconWidgetPriv()
+ {
+ iface = 0;
+ data = 0;
+ separateView = ImageRegionWidget::SeparateViewNone;
+ }
+
+ uchar *data;
+
+ int separateView;
+
+ TQPointArray hightlightPoints;
+
+ ImageIface *iface;
+};
+
+ImagePanIconWidget::ImagePanIconWidget(int w, int h, TQWidget *parent, WFlags flags)
+ : PanIconWidget(parent, flags)
+{
+ d = new ImagePanIconWidgetPriv;
+
+ d->iface = new ImageIface(w, h);
+ d->data = d->iface->getPreviewImage();
+ m_width = d->iface->previewWidth();
+ m_height = d->iface->previewHeight();
+ m_orgWidth = d->iface->originalWidth();
+ m_orgHeight = d->iface->originalHeight();
+ m_zoomedOrgWidth = d->iface->originalWidth();
+ m_zoomedOrgHeight = d->iface->originalHeight();
+ m_pixmap = new TQPixmap(w, h);
+
+ setFixedSize(m_width, m_height);
+
+ m_rect = TQRect(width()/2-m_width/2, height()/2-m_height/2, m_width, m_height);
+ updatePixmap();
+ m_timerID = startTimer(800);
+}
+
+ImagePanIconWidget::~ImagePanIconWidget()
+{
+ delete d->iface;
+ delete [] d->data;
+ delete d;
+}
+
+void ImagePanIconWidget::setHighLightPoints(const TQPointArray& pointsList)
+{
+ d->hightlightPoints = pointsList;
+ updatePixmap();
+ repaint(false);
+}
+
+void ImagePanIconWidget::updatePixmap()
+{
+ // Drawing background and image.
+ m_pixmap->fill(colorGroup().background());
+ d->iface->paint(m_pixmap, m_rect.x(), m_rect.y(), m_rect.width(), m_rect.height());
+
+ TQPainter p(m_pixmap);
+
+ // Drawing HighLighted points.
+
+ if (!d->hightlightPoints.isEmpty())
+ {
+ TQPoint pt;
+
+ for (int i = 0 ; i < d->hightlightPoints.count() ; i++)
+ {
+ pt = d->hightlightPoints.point(i);
+ pt.setX((int)(pt.x() * (float)(m_width) / (float)d->iface->originalWidth()));
+ pt.setY((int)(pt.y() * (float)(m_height) / (float)d->iface->originalHeight()));
+ p.setPen(TQPen(TQt::black, 1, TQt::SolidLine));
+ p.drawLine(pt.x(), pt.y()-1, pt.x(), pt.y()+1);
+ p.drawLine(pt.x()-1, pt.y(), pt.x()+1, pt.y());
+ p.setPen(TQPen(TQt::white, 1, TQt::SolidLine));
+ p.drawPoint(pt.x()-1, pt.y()-1);
+ p.drawPoint(pt.x()+1, pt.y()+1);
+ p.drawPoint(pt.x()-1, pt.y()+1);
+ p.drawPoint(pt.x()+1, pt.y()-1);
+ }
+ }
+
+ // Drawing selection border
+
+ if (m_flicker) p.setPen(TQPen(TQt::white, 1, TQt::SolidLine));
+ else p.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+
+ p.drawRect(m_localRegionSelection.x(),
+ m_localRegionSelection.y(),
+ m_localRegionSelection.width(),
+ m_localRegionSelection.height());
+
+ if (m_flicker) p.setPen(TQPen(TQt::red, 1, TQt::DotLine));
+ else p.setPen(TQPen(TQt::white, 1, TQt::DotLine));
+
+ p.drawRect(m_localRegionSelection.x(),
+ m_localRegionSelection.y(),
+ m_localRegionSelection.width(),
+ m_localRegionSelection.height());
+
+ if (d->separateView == ImageRegionWidget::SeparateViewVertical)
+ {
+ if (m_flicker) p.setPen(TQPen(TQt::white, 1, TQt::SolidLine));
+ else p.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+
+ p.drawLine(m_localRegionSelection.topLeft().x() + m_localRegionSelection.width()/2,
+ m_localRegionSelection.topLeft().y(),
+ m_localRegionSelection.bottomLeft().x() + m_localRegionSelection.width()/2,
+ m_localRegionSelection.bottomLeft().y());
+
+ if (m_flicker) p.setPen(TQPen(TQt::red, 1, TQt::DotLine));
+ else p.setPen(TQPen(TQt::white, 1, TQt::DotLine));
+
+ p.drawLine(m_localRegionSelection.topLeft().x() + m_localRegionSelection.width()/2,
+ m_localRegionSelection.topLeft().y() + 1,
+ m_localRegionSelection.bottomLeft().x() + m_localRegionSelection.width()/2,
+ m_localRegionSelection.bottomLeft().y() - 1);
+ }
+ else if (d->separateView == ImageRegionWidget::SeparateViewHorizontal)
+ {
+ if (m_flicker) p.setPen(TQPen(TQt::white, 1, TQt::SolidLine));
+ else p.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
+
+ p.drawLine(m_localRegionSelection.topLeft().x(),
+ m_localRegionSelection.topLeft().y() + m_localRegionSelection.height()/2,
+ m_localRegionSelection.topRight().x(),
+ m_localRegionSelection.topRight().y() + m_localRegionSelection.height()/2);
+
+ if (m_flicker) p.setPen(TQPen(TQt::red, 1, TQt::DotLine));
+ else p.setPen(TQPen(TQt::white, 1, TQt::DotLine));
+
+ p.drawLine(m_localRegionSelection.topLeft().x() + 1,
+ m_localRegionSelection.topLeft().y() + m_localRegionSelection.height()/2,
+ m_localRegionSelection.topRight().x() - 1,
+ m_localRegionSelection.topRight().y() + m_localRegionSelection.height()/2);
+ }
+
+ p.end();
+}
+
+void ImagePanIconWidget::slotSeparateViewToggled(int t)
+{
+ d->separateView = t;
+ updatePixmap();
+ repaint(false);
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/imageplugins/imagepaniconwidget.h b/src/libs/widgets/imageplugins/imagepaniconwidget.h
new file mode 100644
index 00000000..e7d6ffb7
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagepaniconwidget.h
@@ -0,0 +1,68 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-22
+ * Description : a widget to display a panel to choose
+ * a rectangular image area.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPANICONWIDGET_H
+#define IMAGEPANICONWIDGET_H
+
+// TQt includes.
+
+#include <tqpointarray.h>
+
+// Local includes.
+
+#include "paniconwidget.h"
+
+namespace Digikam
+{
+
+class ImagePanIconWidgetPriv;
+
+class ImagePanIconWidget : public PanIconWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ ImagePanIconWidget(int width, int height, TQWidget *parent=0, WFlags flags=TQt::WDestructiveClose);
+ ~ImagePanIconWidget();
+
+ void setHighLightPoints(const TQPointArray& pointsList);
+
+public slots:
+
+ void slotSeparateViewToggled(int t);
+
+private:
+
+ void updatePixmap();
+
+private:
+
+ ImagePanIconWidgetPriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEPANICONWIDGET_H */
diff --git a/src/libs/widgets/imageplugins/imagepannelwidget.cpp b/src/libs/widgets/imageplugins/imagepannelwidget.cpp
new file mode 100644
index 00000000..cebf0f3c
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagepannelwidget.cpp
@@ -0,0 +1,477 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-01
+ * Description : a widget to draw a control pannel image tool.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqframe.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpixmap.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqtimer.h>
+#include <tqhbuttongroup.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kdialog.h>
+#include <tdelocale.h>
+#include <kcursor.h>
+#include <kprogress.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdeconfig.h>
+#include <kstandarddirs.h>
+#include <kseparator.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "sidebar.h"
+#include "statuszoombar.h"
+#include "thumbnailsize.h"
+#include "imageregionwidget.h"
+#include "imagepaniconwidget.h"
+#include "imagepannelwidget.h"
+#include "imagepannelwidget.moc"
+
+namespace Digikam
+{
+
+class ImagePannelWidgetPriv
+{
+public:
+
+ ImagePannelWidgetPriv()
+ {
+ imageRegionWidget = 0;
+ imagePanIconWidget = 0;
+ mainLayout = 0;
+ separateView = 0;
+ progressBar = 0;
+ settingsSideBar = 0;
+ splitter = 0;
+ settingsLayout = 0;
+ settings = 0;
+ previewWidget = 0;
+ zoomBar = 0;
+ }
+
+ TQGridLayout *mainLayout;
+
+ TQHButtonGroup *separateView;
+
+ TQString settingsSection;
+
+ TQWidget *settings;
+ TQWidget *previewWidget;
+
+ TQVBoxLayout *settingsLayout;
+
+ TQSplitter *splitter;
+
+ KProgress *progressBar;
+
+ ImageRegionWidget *imageRegionWidget;
+ ImagePanIconWidget *imagePanIconWidget;
+
+ Sidebar *settingsSideBar;
+
+ StatusZoomBar *zoomBar;
+};
+
+ImagePannelWidget::ImagePannelWidget(uint w, uint h, const TQString& settingsSection,
+ TQWidget *parent, int separateViewMode)
+ : TQHBox(parent, 0, TQt::WDestructiveClose)
+{
+ d = new ImagePannelWidgetPriv;
+ d->settingsSection = settingsSection;
+ d->splitter = new TQSplitter(this);
+ d->previewWidget = new TQWidget(d->splitter);
+ d->mainLayout = new TQGridLayout( d->previewWidget, 2, 3, 0, KDialog::spacingHint());
+
+ d->splitter->setFrameStyle( TQFrame::NoFrame );
+ d->splitter->setFrameShadow( TQFrame::Plain );
+ d->splitter->setFrameShape( TQFrame::NoFrame );
+ d->splitter->setOpaqueResize(false);
+
+ // -------------------------------------------------------------
+
+ TQFrame *preview = new TQFrame(d->previewWidget);
+ TQVBoxLayout* l1 = new TQVBoxLayout(preview, 5, 0);
+ d->imageRegionWidget = new ImageRegionWidget(w, h, preview, false);
+ d->imageRegionWidget->setFrameStyle(TQFrame::NoFrame);
+ preview->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQWhatsThis::add( d->imageRegionWidget, i18n("<p>Here you can see the original clip image "
+ "which will be used for the preview computation."
+ "<p>Click and drag the mouse cursor in the "
+ "image to change the clip focus."));
+ l1->addWidget(d->imageRegionWidget, 0);
+
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+ d->previewWidget->setSizePolicy(rightSzPolicy);
+
+ // -------------------------------------------------------------
+
+ d->zoomBar = new StatusZoomBar(d->previewWidget);
+ TQWhatsThis::add( d->zoomBar, i18n("<p>Here set the zoom factor of the preview area.") );
+
+ // -------------------------------------------------------------
+
+ TQString directory;
+ d->separateView = new TQHButtonGroup(d->previewWidget);
+ d->separateView->setExclusive(true);
+ d->separateView->setInsideMargin( 0 );
+ d->separateView->setFrameShape(TQFrame::NoFrame);
+
+ if (separateViewMode == SeparateViewDuplicate ||
+ separateViewMode == SeparateViewAll)
+ {
+ TQPushButton *duplicateHorButton = new TQPushButton( d->separateView );
+ d->separateView->insert(duplicateHorButton, ImageRegionWidget::SeparateViewDuplicateHorz);
+ TDEGlobal::dirs()->addResourceType("duplicatebothhorz", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("duplicatebothhorz", "duplicatebothhorz.png");
+ duplicateHorButton->setPixmap( TQPixmap( directory + "duplicatebothhorz.png" ) );
+ duplicateHorButton->setToggleButton(true);
+ TQWhatsThis::add( duplicateHorButton, i18n("<p>If you enable this option, you will separate the preview area "
+ "horizontally, displaying the original and target image "
+ "at the same time. The target is duplicated from the original "
+ "below the red dashed line." ) );
+
+ TQPushButton *duplicateVerButton = new TQPushButton( d->separateView );
+ d->separateView->insert(duplicateVerButton, ImageRegionWidget::SeparateViewDuplicateVert);
+ TDEGlobal::dirs()->addResourceType("duplicatebothvert", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("duplicatebothvert", "duplicatebothvert.png");
+ duplicateVerButton->setPixmap( TQPixmap( directory + "duplicatebothvert.png" ) );
+ duplicateVerButton->setToggleButton(true);
+ TQWhatsThis::add( duplicateVerButton, i18n("<p>If you enable this option, you will separate the preview area "
+ "vertically, displaying the original and target image "
+ "at the same time. The target is duplicated from the original to "
+ "the right of the red dashed line." ) );
+ }
+
+ if (separateViewMode == SeparateViewNormal ||
+ separateViewMode == SeparateViewAll)
+ {
+ TQPushButton *separateHorButton = new TQPushButton( d->separateView );
+ d->separateView->insert(separateHorButton, ImageRegionWidget::SeparateViewHorizontal);
+ TDEGlobal::dirs()->addResourceType("bothhorz", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("bothhorz", "bothhorz.png");
+ separateHorButton->setPixmap( TQPixmap( directory + "bothhorz.png" ) );
+ separateHorButton->setToggleButton(true);
+ TQWhatsThis::add( separateHorButton, i18n( "<p>If you enable this option, you will separate the preview area "
+ "horizontally, displaying the original and target image "
+ "at the same time. The original is above the "
+ "red dashed line, the target below it." ) );
+
+ TQPushButton *separateVerButton = new TQPushButton( d->separateView );
+ d->separateView->insert(separateVerButton, ImageRegionWidget::SeparateViewVertical);
+ TDEGlobal::dirs()->addResourceType("bothvert", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("bothvert", "bothvert.png");
+ separateVerButton->setPixmap( TQPixmap( directory + "bothvert.png" ) );
+ separateVerButton->setToggleButton(true);
+ TQWhatsThis::add( separateVerButton, i18n( "<p>If you enable this option, you will separate the preview area "
+ "vertically, displaying the original and target image "
+ "at the same time. The original is to the left of the "
+ "red dashed line, the target to the right of it." ) );
+ }
+
+ TQPushButton *noSeparateButton = new TQPushButton( d->separateView );
+ d->separateView->insert(noSeparateButton, ImageRegionWidget::SeparateViewNone);
+ TDEGlobal::dirs()->addResourceType("target", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("target", "target.png");
+ noSeparateButton->setPixmap( TQPixmap( directory + "target.png" ) );
+ noSeparateButton->setToggleButton(true);
+ TQWhatsThis::add( noSeparateButton, i18n( "<p>If you enable this option, the preview area will not "
+ "be separated." ) );
+
+ // -------------------------------------------------------------
+
+ d->progressBar = new KProgress(100, d->previewWidget);
+ TQWhatsThis::add(d->progressBar ,i18n("<p>This is the percentage of the task which has been completed up to this point."));
+ d->progressBar->setProgress(0);
+ d->progressBar->setMaximumHeight( fontMetrics().height() );
+
+ // -------------------------------------------------------------
+
+ d->mainLayout->addMultiCellWidget(preview, 0, 1, 0, 3);
+ d->mainLayout->addMultiCellWidget(d->zoomBar, 2, 2, 0, 0);
+ d->mainLayout->addMultiCellWidget(d->progressBar, 2, 2, 2, 2);
+ d->mainLayout->addMultiCellWidget(d->separateView, 2, 2, 3, 3);
+
+ d->mainLayout->setRowStretch(1, 10);
+ d->mainLayout->setColStretch(1, 10);
+
+ // -------------------------------------------------------------
+
+ TQString sbName(d->settingsSection + TQString(" Image Plugin Sidebar"));
+ d->settingsSideBar = new Sidebar(this, sbName.ascii(), Sidebar::Right);
+ d->settingsSideBar->setSplitter(d->splitter);
+
+ d->settings = new TQWidget(d->settingsSideBar);
+ d->settingsLayout = new TQVBoxLayout(d->settings);
+
+ TQFrame *frame3 = new TQFrame(d->settings);
+ frame3->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* l3 = new TQVBoxLayout(frame3, 5, 0);
+ d->imagePanIconWidget = new ImagePanIconWidget(360, 240, frame3);
+ TQWhatsThis::add( d->imagePanIconWidget, i18n("<p>Here you can see the original image panel "
+ "which can help you to select the clip preview."
+ "<p>Click and drag the mouse cursor in the "
+ "red rectangle to change the clip focus."));
+ l3->addWidget(d->imagePanIconWidget, 0, TQt::AlignCenter);
+
+ d->settingsLayout->addWidget(frame3, 0, TQt::AlignHCenter);
+ d->settingsLayout->addSpacing(KDialog::spacingHint());
+
+ d->settingsSideBar->appendTab(d->settings, SmallIcon("configure"), i18n("Settings"));
+ d->settingsSideBar->loadViewState();
+
+ // -------------------------------------------------------------
+
+ setProgressVisible(false);
+ TQTimer::singleShot(0, this, TQ_SLOT(slotInitGui()));
+
+ // -------------------------------------------------------------
+
+ connect(d->imageRegionWidget, TQ_SIGNAL(signalContentsMovedEvent(bool)),
+ this, TQ_SLOT(slotOriginalImageRegionChanged(bool)));
+
+ connect(d->imagePanIconWidget, TQ_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
+ this, TQ_SLOT(slotSetImageRegionPosition(const TQRect&, bool)));
+
+ connect(d->imagePanIconWidget, TQ_SIGNAL(signalSelectionTakeFocus()),
+ this, TQ_SLOT(slotPanIconTakeFocus()));
+
+ connect(d->separateView, TQ_SIGNAL(released(int)),
+ d->imageRegionWidget, TQ_SLOT(slotSeparateViewToggled(int)));
+
+ connect(d->separateView, TQ_SIGNAL(released(int)),
+ d->imagePanIconWidget, TQ_SLOT(slotSeparateViewToggled(int)));
+
+ connect(d->zoomBar, TQ_SIGNAL(signalZoomMinusClicked()),
+ d->imageRegionWidget, TQ_SLOT(slotDecreaseZoom()));
+
+ connect(d->zoomBar, TQ_SIGNAL(signalZoomPlusClicked()),
+ d->imageRegionWidget, TQ_SLOT(slotIncreaseZoom()));
+
+ connect(d->zoomBar, TQ_SIGNAL(signalZoomSliderReleased(int)),
+ this, TQ_SLOT(slotZoomSliderChanged(int)));
+}
+
+ImagePannelWidget::~ImagePannelWidget()
+{
+ writeSettings();
+ delete d->settingsSideBar;
+ delete d;
+}
+
+void ImagePannelWidget::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->settingsSection);
+ int mode = config->readNumEntry("Separate View", ImageRegionWidget::SeparateViewDuplicateVert);
+ mode = TQMAX(ImageRegionWidget::SeparateViewHorizontal, mode);
+ mode = TQMIN(ImageRegionWidget::SeparateViewDuplicateHorz, mode);
+
+ d->imageRegionWidget->blockSignals(true);
+ d->imagePanIconWidget->blockSignals(true);
+ d->separateView->blockSignals(true);
+ d->imageRegionWidget->slotSeparateViewToggled( mode );
+ d->imagePanIconWidget->slotSeparateViewToggled( mode );
+ d->separateView->setButton( mode );
+ d->imageRegionWidget->blockSignals(false);
+ d->imagePanIconWidget->blockSignals(false);
+ d->separateView->blockSignals(false);
+}
+
+void ImagePannelWidget::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->settingsSection);
+ config->writeEntry( "Separate View", d->separateView->selectedId() );
+ config->sync();
+}
+
+void ImagePannelWidget::slotOriginalImageRegionChanged(bool target)
+{
+ slotZoomFactorChanged(d->imageRegionWidget->zoomFactor());
+ TQRect rect = getOriginalImageRegion();
+ d->imagePanIconWidget->setRegionSelection(rect);
+ updateSelectionInfo(rect);
+
+ if (target)
+ {
+ d->imageRegionWidget->backupPixmapRegion();
+ emit signalOriginalClipFocusChanged();
+ }
+}
+
+void ImagePannelWidget::slotZoomFactorChanged(double zoom)
+{
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->imageRegionWidget->zoomMin();
+ double zmax = d->imageRegionWidget->zoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ int size = (int)((zoom - b) /a);
+
+ d->zoomBar->setZoomSliderValue(size);
+ d->zoomBar->setZoomTrackerText(i18n("zoom: %1%").arg((int)(zoom*100.0)));
+
+ d->zoomBar->setEnableZoomPlus(true);
+ d->zoomBar->setEnableZoomMinus(true);
+
+ if (d->imageRegionWidget->maxZoom())
+ d->zoomBar->setEnableZoomPlus(false);
+
+ if (d->imageRegionWidget->minZoom())
+ d->zoomBar->setEnableZoomMinus(false);
+
+ d->imagePanIconWidget->slotZoomFactorChanged(zoom);
+}
+
+void ImagePannelWidget::slotZoomSliderChanged(int size)
+{
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->imageRegionWidget->zoomMin();
+ double zmax = d->imageRegionWidget->zoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ double z = a*size+b;
+
+ d->imageRegionWidget->setZoomFactorSnapped(z);
+}
+
+KProgress *ImagePannelWidget::progressBar()
+{
+ return d->progressBar;
+}
+
+void ImagePannelWidget::resizeEvent(TQResizeEvent *)
+{
+ emit signalResized();
+}
+
+void ImagePannelWidget::slotInitGui()
+{
+ readSettings();
+ setCenterImageRegionPosition();
+ slotOriginalImageRegionChanged(true);
+}
+
+void ImagePannelWidget::setPanIconHighLightPoints(const TQPointArray& pt)
+{
+ d->imageRegionWidget->setHighLightPoints(pt);
+ d->imagePanIconWidget->setHighLightPoints(pt);
+}
+
+void ImagePannelWidget::slotPanIconTakeFocus()
+{
+ d->imageRegionWidget->restorePixmapRegion();
+}
+
+void ImagePannelWidget::setUserAreaWidget(TQWidget *w)
+{
+ w->reparent( d->settings, TQPoint(0, 0) );
+ d->settingsLayout->addSpacing(KDialog::spacingHint());
+ d->settingsLayout->addWidget(w);
+ d->settingsLayout->addStretch();
+}
+
+void ImagePannelWidget::setEnable(bool b)
+{
+ d->imageRegionWidget->setEnabled(b);
+ d->imagePanIconWidget->setEnabled(b);
+ d->separateView->setEnabled(b);
+ d->zoomBar->setEnabled(b);
+}
+
+void ImagePannelWidget::setProgress(int val)
+{
+ d->progressBar->setValue(val);
+}
+
+void ImagePannelWidget::setProgressVisible(bool b)
+{
+ if (b) d->progressBar->show();
+ else d->progressBar->hide();
+}
+
+void ImagePannelWidget::setProgressWhatsThis(const TQString& desc)
+{
+ TQWhatsThis::add( d->progressBar, desc);
+}
+
+void ImagePannelWidget::setPreviewImageWaitCursor(bool enable)
+{
+ if ( enable )
+ d->imageRegionWidget->setCursor( KCursor::waitCursor() );
+ else
+ d->imageRegionWidget->unsetCursor();
+}
+
+TQRect ImagePannelWidget::getOriginalImageRegion()
+{
+ return ( d->imageRegionWidget->getImageRegion() );
+}
+
+TQRect ImagePannelWidget::getOriginalImageRegionToRender()
+{
+ return ( d->imageRegionWidget->getImageRegionToRender() );
+}
+
+DImg ImagePannelWidget::getOriginalRegionImage()
+{
+ return ( d->imageRegionWidget->getImageRegionImage() );
+}
+
+void ImagePannelWidget::setPreviewImage(DImg img)
+{
+ d->imageRegionWidget->updatePreviewImage(&img);
+}
+
+void ImagePannelWidget::setCenterImageRegionPosition()
+{
+ d->imageRegionWidget->setCenterContentsPosition();
+}
+
+void ImagePannelWidget::slotSetImageRegionPosition(const TQRect& rect, bool targetDone)
+{
+ d->imageRegionWidget->setContentsPosition(rect.x(), rect.y(), targetDone);
+}
+
+void ImagePannelWidget::updateSelectionInfo(const TQRect& rect)
+{
+ TQToolTip::add( d->imagePanIconWidget,
+ i18n("<nobr>(%1,%2)(%3x%4)</nobr>")
+ .arg(rect.left()).arg(rect.top())
+ .arg(rect.width()).arg(rect.height()));
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/imageplugins/imagepannelwidget.h b/src/libs/widgets/imageplugins/imagepannelwidget.h
new file mode 100644
index 00000000..a3429887
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagepannelwidget.h
@@ -0,0 +1,123 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-01
+ * Description : a widget to draw a control pannel image tool.
+ *
+ * Copyright (C) 2005-2008 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPANNELWIDGET_H
+#define IMAGEPANNELWIDGET_H
+
+// TQt includes.
+
+#include <tqhbox.h>
+#include <tqimage.h>
+#include <tqrect.h>
+#include <tqstring.h>
+
+// Local includes
+
+#include "dimg.h"
+#include "digikam_export.h"
+
+class KProgress;
+
+namespace Digikam
+{
+
+class ImagePannelWidgetPriv;
+class ImageRegionWidget;
+
+class DIGIKAM_EXPORT ImagePannelWidget : public TQHBox
+{
+TQ_OBJECT
+
+
+public:
+
+ enum SeparateViewOptions
+ {
+ SeparateViewNormal=0,
+ SeparateViewDuplicate,
+ SeparateViewAll
+ };
+
+public:
+
+ ImagePannelWidget(uint w, uint h, const TQString& settingsSection, TQWidget *parent=0,
+ int separateViewMode=SeparateViewAll);
+ ~ImagePannelWidget();
+
+ TQRect getOriginalImageRegion();
+ TQRect getOriginalImageRegionToRender();
+ DImg getOriginalRegionImage();
+ void setPreviewImage(DImg img);
+ void setPreviewImageWaitCursor(bool enable);
+ void setCenterImageRegionPosition();
+
+ void setEnable(bool b);
+
+ void setProgress(int val);
+ void setProgressVisible(bool b);
+ void setProgressWhatsThis(const TQString& desc);
+
+ void setUserAreaWidget(TQWidget *w);
+
+ void setPanIconHighLightPoints(const TQPointArray& pt);
+
+ KProgress *progressBar();
+
+signals:
+
+ void signalOriginalClipFocusChanged();
+ void signalResized();
+
+public slots:
+
+ // Set the top/Left conner clip position.
+ void slotSetImageRegionPosition(const TQRect& rect, bool targetDone);
+
+ // Slot used when the original image clip focus is changed by the user.
+ void slotOriginalImageRegionChanged(bool target);
+
+protected:
+
+ void resizeEvent(TQResizeEvent *e);
+
+private slots:
+
+ void slotPanIconTakeFocus();
+ void slotInitGui();
+ void slotZoomSliderChanged(int);
+ void slotZoomFactorChanged(double);
+
+private:
+
+ void updateSelectionInfo(const TQRect& rect);
+ void readSettings();
+ void writeSettings();
+
+private:
+
+ ImagePannelWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEPANNELWIDGET_H */
diff --git a/src/libs/widgets/imageplugins/imageregionwidget.cpp b/src/libs/widgets/imageplugins/imageregionwidget.cpp
new file mode 100644
index 00000000..c1392cc2
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imageregionwidget.cpp
@@ -0,0 +1,473 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-17
+ * Description : a widget to draw an image clip region.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqtimer.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqimage.h>
+#include <tqbrush.h>
+#include <tqfont.h>
+#include <tqfontmetrics.h>
+#include <tqpointarray.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdeglobal.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imageiface.h"
+#include "imageregionwidget.h"
+#include "imageregionwidget.moc"
+
+namespace Digikam
+{
+
+class ImageRegionWidgetPriv
+{
+
+public:
+
+ ImageRegionWidgetPriv()
+ {
+ iface = 0;
+ separateView = ImageRegionWidget::SeparateViewVertical;
+ }
+
+ int separateView;
+ int xpos;
+ int ypos;
+
+ TQPixmap pixmapRegion; // Pixmap of current region to render.
+
+ TQPointArray hightlightPoints;
+
+ DImg image; // Entire content image to render pixmap.
+
+ ImageIface *iface;
+};
+
+ImageRegionWidget::ImageRegionWidget(int wp, int hp, TQWidget *parent, bool scrollBar)
+ : PreviewWidget(parent)
+{
+ d = new ImageRegionWidgetPriv;
+ d->iface = new ImageIface(0, 0);
+ d->image = d->iface->getOriginalImg()->copy();
+
+ setMinimumSize(wp, hp);
+ setBackgroundColor(colorGroup().background());
+
+ if( !scrollBar )
+ {
+ setVScrollBarMode( TQScrollView::AlwaysOff );
+ setHScrollBarMode( TQScrollView::AlwaysOff );
+ }
+
+ connect(this, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SLOT(slotZoomFactorChanged()));
+}
+
+ImageRegionWidget::~ImageRegionWidget()
+{
+ if (d->iface) delete d->iface;
+ delete d;
+}
+
+void ImageRegionWidget::resizeEvent(TQResizeEvent* e)
+{
+ if (!e) return;
+
+ TQScrollView::resizeEvent(e);
+
+ // NOTE: We will always adapt the min. zoom factor to the visible size of canvas
+
+ double srcWidth = previewWidth();
+ double srcHeight = previewHeight();
+ double dstWidth = contentsRect().width();
+ double dstHeight = contentsRect().height();
+ double zoom = TQMAX(dstWidth/srcWidth, dstHeight/srcHeight);
+
+ setZoomMin(zoom);
+ setZoomMax(zoom*12.0);
+ setZoomFactor(zoom);
+}
+
+int ImageRegionWidget::previewWidth()
+{
+ return d->image.width();
+}
+
+int ImageRegionWidget::previewHeight()
+{
+ return d->image.height();
+}
+
+bool ImageRegionWidget::previewIsNull()
+{
+ return d->image.isNull();
+}
+
+void ImageRegionWidget::resetPreview()
+{
+ d->image.reset();
+}
+
+void ImageRegionWidget::paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh)
+{
+ DImg img = d->image.smoothScaleSection(sx, sy, sw, sh, tileSize(), tileSize());
+ TQPixmap pix2 = d->iface->convertToPixmap(img);
+ bitBlt(pix, 0, 0, &pix2, 0, 0);
+}
+
+void ImageRegionWidget::setHighLightPoints(const TQPointArray& pointsList)
+{
+ d->hightlightPoints = pointsList;
+ repaintContents(false);
+}
+
+void ImageRegionWidget::slotZoomFactorChanged()
+{
+ emit signalContentsMovedEvent(true);
+}
+
+void ImageRegionWidget::slotSeparateViewToggled(int mode)
+{
+ d->separateView = mode;
+ updateContentsSize();
+ slotZoomFactorChanged();
+}
+
+TQRect ImageRegionWidget::getImageRegion()
+{
+ TQRect region;
+
+ switch (d->separateView)
+ {
+ case SeparateViewVertical:
+ case SeparateViewHorizontal:
+ case SeparateViewNone:
+ region = TQRect(contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ break;
+ case SeparateViewDuplicateVert:
+ region = TQRect(contentsX(), contentsY(), visibleWidth()/2, visibleHeight());
+ break;
+ case SeparateViewDuplicateHorz:
+ region = TQRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()/2);
+ break;
+ }
+
+ return region;
+}
+
+void ImageRegionWidget::viewportPaintExtraData()
+{
+ if (!m_movingInProgress && !d->pixmapRegion.isNull())
+ {
+ TQPainter p(viewport());
+ TQRect region = getLocalTargetImageRegion();
+ TQRect rt(contentsToViewport(region.topLeft()), contentsToViewport(region.bottomRight()));
+
+ region = getLocalImageRegionToRender();
+ TQRect ro(contentsToViewport(region.topLeft()), contentsToViewport(region.bottomRight()));
+
+ bitBlt(viewport(), rt.x(), rt.y(), &d->pixmapRegion, 0, 0, rt.width(), rt.height());
+
+ // Drawing separate view.
+
+ switch (d->separateView)
+ {
+ case SeparateViewVertical:
+ case SeparateViewDuplicateVert:
+ {
+ p.setPen(TQPen(TQt::white, 2, TQt::SolidLine));
+ p.drawLine(rt.topLeft().x(), rt.topLeft().y(),
+ rt.bottomLeft().x(), rt.bottomLeft().y());
+ p.setPen(TQPen(TQt::red, 2, TQt::DotLine));
+ p.drawLine(rt.topLeft().x(), rt.topLeft().y()+1,
+ rt.bottomLeft().x(), rt.bottomLeft().y()-1);
+
+ p.setPen(TQPen(TQt::red, 1)) ;
+ TQFontMetrics fontMt = p.fontMetrics();
+
+ TQString text(i18n("Target"));
+ TQRect textRect;
+ TQRect fontRect = fontMt.boundingRect(0, 0, contentsWidth(), contentsHeight(), 0, text);
+ textRect.setTopLeft(TQPoint(rt.topLeft().x()+20, rt.topLeft().y()+20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+
+ text = i18n("Original");
+ fontRect = fontMt.boundingRect(0, 0, contentsWidth(), contentsHeight(), 0, text);
+
+ if (d->separateView == SeparateViewVertical)
+ ro.moveBy(-ro.width(), 0);
+
+ textRect.setTopLeft(TQPoint(ro.topLeft().x()+20, ro.topLeft().y()+20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2 ) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+ break;
+ }
+ case SeparateViewHorizontal:
+ case SeparateViewDuplicateHorz:
+ {
+ p.setPen(TQPen(TQt::white, 2, TQt::SolidLine));
+ p.drawLine(rt.topLeft().x()+1, rt.topLeft().y(),
+ rt.topRight().x()-1, rt.topRight().y());
+ p.setPen(TQPen(TQt::red, 2, TQt::DotLine));
+ p.drawLine(rt.topLeft().x(), rt.topLeft().y(),
+ rt.topRight().x(), rt.topRight().y());
+
+ p.setPen(TQPen(TQt::red, 1)) ;
+ TQFontMetrics fontMt = p.fontMetrics();
+
+ TQString text(i18n("Target"));
+ TQRect textRect;
+ TQRect fontRect = fontMt.boundingRect(0, 0, contentsWidth(), contentsHeight(), 0, text);
+ textRect.setTopLeft(TQPoint(rt.topLeft().x()+20, rt.topLeft().y()+20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+
+ text = i18n("Original");
+ fontRect = fontMt.boundingRect(0, 0, contentsWidth(), contentsHeight(), 0, text);
+
+ if (d->separateView == SeparateViewHorizontal)
+ ro.moveBy(0, -ro.height());
+
+ textRect.setTopLeft(TQPoint(ro.topLeft().x()+20, ro.topLeft().y()+20));
+ textRect.setSize( TQSize(fontRect.width()+2, fontRect.height()+2 ) );
+ p.fillRect(textRect, TQBrush(TQColor(250, 250, 255)) );
+ p.drawRect(textRect);
+ p.drawText(textRect, TQt::AlignCenter, text);
+ break;
+ }
+ }
+
+ // Drawing HighLighted points.
+
+ if (!d->hightlightPoints.isEmpty())
+ {
+ TQPoint pt;
+ TQRect hpArea;
+
+ for (int i = 0 ; i < d->hightlightPoints.count() ; i++)
+ {
+ pt = d->hightlightPoints.point(i);
+
+ if ( getImageRegionToRender().contains(pt) )
+ {
+ int x = (int)(((double)pt.x() * tileSize()) / floor(tileSize() / zoomFactor()));
+ int y = (int)(((double)pt.y() * tileSize()) / floor(tileSize() / zoomFactor()));
+
+ TQPoint hp(contentsToViewport(TQPoint(x, y)));
+ hpArea.setSize(TQSize((int)(16*zoomFactor()), (int)(16*zoomFactor())));
+ hpArea.moveCenter(hp);
+
+ p.setPen(TQPen(TQt::white, 2, TQt::SolidLine));
+ p.drawLine(hp.x(), hpArea.y(),
+ hp.x(), hp.y()-(int)(3*zoomFactor()));
+ p.drawLine(hp.x(), hp.y()+(int)(3*zoomFactor()),
+ hp.x(), hpArea.bottom());
+ p.drawLine(hpArea.x(), hp.y(),
+ hp.x()-(int)(3*zoomFactor()), hp.y());
+ p.drawLine(hp.x()+(int)(3*zoomFactor()), hp.y(),
+ hpArea.right(), hp.y());
+
+ p.setPen(TQPen(TQt::red, 2, TQt::DotLine));
+ p.drawLine(hp.x(), hpArea.y(),
+ hp.x(), hp.y()-(int)(3*zoomFactor()));
+ p.drawLine(hp.x(), hp.y()+(int)(3*zoomFactor()),
+ hp.x(), hpArea.bottom());
+ p.drawLine(hpArea.x(), hp.y(),
+ hp.x()-(int)(3*zoomFactor()), hp.y());
+ p.drawLine(hp.x()+(int)(3*zoomFactor()), hp.y(),
+ hpArea.right(), hp.y());
+ }
+ }
+ }
+ p.end();
+ }
+}
+
+void ImageRegionWidget::setCenterContentsPosition()
+{
+ center(contentsWidth()/2, contentsHeight()/2);
+ slotZoomFactorChanged();
+}
+
+void ImageRegionWidget::setContentsPosition(int x, int y, bool targetDone)
+{
+ if( targetDone )
+ m_movingInProgress = false;
+
+ setContentsPos(x, y);
+
+ if( targetDone )
+ slotZoomFactorChanged();
+}
+
+void ImageRegionWidget::backupPixmapRegion()
+{
+ d->pixmapRegion = TQPixmap();
+}
+
+void ImageRegionWidget::restorePixmapRegion()
+{
+ m_movingInProgress = true;
+ viewport()->repaint(false);
+}
+
+void ImageRegionWidget::updatePreviewImage(DImg *img)
+{
+ DImg image = img->copy();
+ TQRect r = getLocalImageRegionToRender();
+ image.resize(r.width(), r.height());
+
+ // Because image plugins are tool witch only work on image data, the DImg container
+ // do not contain metadata from original image. About Color Managed View, we need to
+ // restore the embedded ICC color profile.
+ image.setICCProfil(d->image.getICCProfil());
+ d->pixmapRegion = d->iface->convertToPixmap(image);
+}
+
+DImg ImageRegionWidget::getImageRegionImage()
+{
+ return (d->image.copy(getImageRegionToRender()));
+}
+
+TQRect ImageRegionWidget::getImageRegionToRender()
+{
+ TQRect r = getLocalImageRegionToRender();
+
+ int x = (int)(((double)r.x() / tileSize()) * floor(tileSize() / zoomFactor()));
+ int y = (int)(((double)r.y() / tileSize()) * floor(tileSize() / zoomFactor()));
+ int w = (int)(((double)r.width() / tileSize()) * floor(tileSize() / zoomFactor()));
+ int h = (int)(((double)r.height() / tileSize()) * floor(tileSize() / zoomFactor()));
+
+ TQRect rect(x, y, w, h);
+ return (rect);
+}
+
+TQRect ImageRegionWidget::getLocalImageRegionToRender()
+{
+ TQRect region;
+
+ if (d->separateView == SeparateViewVertical)
+ {
+ region = TQRect((int)ceilf(contentsX()+visibleWidth()/2.0), contentsY(),
+ (int)ceilf(visibleWidth()/2.0), visibleHeight());
+ }
+ else if (d->separateView == SeparateViewHorizontal)
+ {
+ region = TQRect(contentsX(), (int)ceilf(contentsY()+visibleHeight()/2.0),
+ visibleWidth(), (int)ceilf(visibleHeight()/2.0));
+ }
+ else if (d->separateView == SeparateViewDuplicateVert)
+ {
+ region = TQRect(contentsX(), contentsY(),
+ (int)ceilf(visibleWidth()/2.0), visibleHeight());
+ }
+ else if (d->separateView == SeparateViewDuplicateHorz)
+ {
+ region = TQRect(contentsX(), contentsY(),
+ visibleWidth(), (int)ceilf(visibleHeight()/2.0));
+ }
+ else
+ {
+ region = TQRect(contentsX(), contentsY(),
+ visibleWidth(), visibleHeight());
+ }
+
+ return (region);
+}
+
+TQRect ImageRegionWidget::getLocalTargetImageRegion()
+{
+ TQRect region = getLocalImageRegionToRender();
+
+ if (d->separateView == SeparateViewDuplicateVert)
+ region.moveBy(region.width(), 0);
+ else if (d->separateView == SeparateViewDuplicateHorz)
+ region.moveBy(0, region.height());
+
+ return region;
+}
+
+void ImageRegionWidget::setContentsSize()
+{
+ switch (d->separateView)
+ {
+ case SeparateViewVertical:
+ case SeparateViewHorizontal:
+ case SeparateViewNone:
+ {
+ PreviewWidget::setContentsSize();
+ break;
+ }
+ case SeparateViewDuplicateVert:
+ {
+ resizeContents(zoomWidth()+visibleWidth()/2, zoomHeight());
+ break;
+ }
+ case SeparateViewDuplicateHorz:
+ {
+ resizeContents(zoomWidth(), zoomHeight()+visibleHeight()/2);
+ break;
+ }
+ default:
+ DWarning() << "Unknown separation view specified" << endl;
+ }
+}
+
+void ImageRegionWidget::contentsWheelEvent(TQWheelEvent *e)
+{
+ e->accept();
+
+ if (e->state() & TQt::ControlButton)
+ {
+ if (e->delta() < 0 && !maxZoom())
+ slotIncreaseZoom();
+ else if (e->delta() > 0 && !minZoom())
+ slotDecreaseZoom();
+ return;
+ }
+}
+
+} // NameSpace Digikam
diff --git a/src/libs/widgets/imageplugins/imageregionwidget.h b/src/libs/widgets/imageplugins/imageregionwidget.h
new file mode 100644
index 00000000..8555703d
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imageregionwidget.h
@@ -0,0 +1,115 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-17
+ * Description : a widget to draw an image clip region.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEREGIONWIDGET_H
+#define IMAGEREGIONWIDGET_H
+
+// TQt includes.
+
+#include <tqrect.h>
+
+// Local includes.
+
+#include "previewwidget.h"
+#include "dimg.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImageRegionWidgetPriv;
+
+class DIGIKAM_EXPORT ImageRegionWidget : public PreviewWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ enum SeparateViewMode
+ {
+ SeparateViewHorizontal=0,
+ SeparateViewVertical,
+ SeparateViewNone,
+ SeparateViewDuplicateVert,
+ SeparateViewDuplicateHorz
+ };
+
+public:
+
+ ImageRegionWidget(int wp, int hp, TQWidget *parent=0, bool scrollBar=true);
+ ~ImageRegionWidget();
+
+ void setContentsPosition(int x, int y, bool targetDone);
+ void setCenterContentsPosition();
+
+ /** To get image region including original or/and target area depending of separate view mode.
+ The region is given using not scaled image unit.*/
+ TQRect getImageRegion();
+
+ /** To get target image region area to render */
+ TQRect getImageRegionToRender();
+
+ /** To get target image region image to use for render operations */
+ DImg getImageRegionImage();
+
+ void updatePreviewImage(DImg *img);
+
+ void backupPixmapRegion();
+ void restorePixmapRegion();
+
+ void setHighLightPoints(const TQPointArray& pointsList);
+ void drawSeparateView();
+
+public slots:
+
+ void slotSeparateViewToggled(int mode);
+
+private slots:
+
+ void slotZoomFactorChanged();
+
+private:
+
+ void updatePixmap(DImg& img);
+ TQRect getLocalTargetImageRegion();
+ TQRect getLocalImageRegionToRender();
+ void viewportPaintExtraData();
+ int previewWidth();
+ int previewHeight();
+ bool previewIsNull();
+ void resetPreview();
+ void setContentsSize();
+ void resizeEvent(TQResizeEvent *);
+ void contentsWheelEvent(TQWheelEvent *);
+
+ inline void paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh);
+
+private:
+
+ ImageRegionWidgetPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGEREGIONWIDGET_H */
diff --git a/src/libs/widgets/imageplugins/imagewidget.cpp b/src/libs/widgets/imageplugins/imagewidget.cpp
new file mode 100644
index 00000000..ab73bbf7
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagewidget.cpp
@@ -0,0 +1,347 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-01
+ * Description : a widget to display an image preview with some
+ * modes to compare effect results.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqhbuttongroup.h>
+#include <tqpushbutton.h>
+
+// KDE includes.
+
+#include <ksqueezedtextlabel.h>
+#include <kdialog.h>
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeconfig.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagewidget.h"
+#include "imagewidget.moc"
+
+namespace Digikam
+{
+
+class ImageWidgetPriv
+{
+public:
+
+ ImageWidgetPriv()
+ {
+ spotInfoLabel = 0;
+ previewButtons = 0;
+ underExposureButton = 0;
+ overExposureButton = 0;
+ previewWidget = 0;
+ }
+
+ TQString settingsSection;
+
+ TQHButtonGroup *previewButtons;
+
+ TQPushButton *underExposureButton;
+ TQPushButton *overExposureButton;
+
+ KSqueezedTextLabel *spotInfoLabel;
+
+ ImageGuideWidget *previewWidget;
+};
+
+ImageWidget::ImageWidget(const TQString& settingsSection, TQWidget *parent,
+ const TQString& previewWhatsThis, bool prevModeOptions,
+ int guideMode, bool guideVisible, bool useImageSelection)
+ : TQWidget(parent)
+{
+ d = new ImageWidgetPriv;
+ d->settingsSection = settingsSection;
+
+ // -------------------------------------------------------------
+
+ TQGridLayout* grid = new TQGridLayout(this, 2, 3);
+
+ d->spotInfoLabel = new KSqueezedTextLabel(this);
+ d->spotInfoLabel->setAlignment(TQt::AlignRight);
+
+ // -------------------------------------------------------------
+
+ d->previewButtons = new TQHButtonGroup(this);
+ d->previewButtons->setExclusive(true);
+ d->previewButtons->setInsideMargin(0);
+ d->previewButtons->setFrameShape(TQFrame::NoFrame);
+
+ TQPushButton *previewOriginalButton = new TQPushButton( d->previewButtons );
+ d->previewButtons->insert(previewOriginalButton, ImageGuideWidget::PreviewOriginalImage);
+ TDEGlobal::dirs()->addResourceType("original", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("original", "original.png");
+ previewOriginalButton->setPixmap( TQPixmap( directory + "original.png" ) );
+ previewOriginalButton->setToggleButton(true);
+ TQWhatsThis::add( previewOriginalButton, i18n( "<p>If you enable this option, you will see "
+ "the original image." ) );
+
+ TQPushButton *previewBothButtonVert = new TQPushButton( d->previewButtons );
+ d->previewButtons->insert(previewBothButtonVert, ImageGuideWidget::PreviewBothImagesVertCont);
+ TDEGlobal::dirs()->addResourceType("bothvert", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("bothvert", "bothvert.png");
+ previewBothButtonVert->setPixmap( TQPixmap( directory + "bothvert.png" ) );
+ previewBothButtonVert->setToggleButton(true);
+ TQWhatsThis::add( previewBothButtonVert, i18n( "<p>If you enable this option, the preview area will "
+ "split vertically. "
+ "A contiguous area of the image will be shown, "
+ "with one half from the original image, "
+ "the other half from the target image.") );
+
+ TQPushButton *previewBothButtonHorz = new TQPushButton( d->previewButtons );
+ d->previewButtons->insert(previewBothButtonHorz, ImageGuideWidget::PreviewBothImagesHorzCont);
+ TDEGlobal::dirs()->addResourceType("bothhorz", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("bothhorz", "bothhorz.png");
+ previewBothButtonHorz->setPixmap( TQPixmap( directory + "bothhorz.png" ) );
+ previewBothButtonHorz->setToggleButton(true);
+ TQWhatsThis::add( previewBothButtonHorz, i18n( "<p>If you enable this option, the preview area will "
+ "split horizontally. "
+ "A contiguous area of the image will be shown, "
+ "with one half from the original image, "
+ "the other half from the target image.") );
+
+ TQPushButton *previewDuplicateBothButtonVert = new TQPushButton( d->previewButtons );
+ d->previewButtons->insert(previewDuplicateBothButtonVert, ImageGuideWidget::PreviewBothImagesVert);
+ TDEGlobal::dirs()->addResourceType("duplicatebothvert", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("duplicatebothvert", "duplicatebothvert.png");
+ previewDuplicateBothButtonVert->setPixmap( TQPixmap( directory + "duplicatebothvert.png" ) );
+ previewDuplicateBothButtonVert->setToggleButton(true);
+ TQWhatsThis::add( previewDuplicateBothButtonVert, i18n( "<p>If you enable this option, the preview area will "
+ "split vertically. "
+ "The same part of the original and the target image "
+ "will be shown side by side.") );
+
+ TQPushButton *previewDupplicateBothButtonHorz = new TQPushButton( d->previewButtons );
+ d->previewButtons->insert(previewDupplicateBothButtonHorz, ImageGuideWidget::PreviewBothImagesHorz);
+ TDEGlobal::dirs()->addResourceType("duplicatebothhorz", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("duplicatebothhorz", "duplicatebothhorz.png");
+ previewDupplicateBothButtonHorz->setPixmap( TQPixmap( directory + "duplicatebothhorz.png" ) );
+ previewDupplicateBothButtonHorz->setToggleButton(true);
+ TQWhatsThis::add( previewDupplicateBothButtonHorz, i18n( "<p>If you enable this option, the preview area will "
+ "split horizontally. "
+ "The same part of the original and the target image "
+ "will be shown side by side.") );
+
+ TQPushButton *previewtargetButton = new TQPushButton( d->previewButtons );
+ d->previewButtons->insert(previewtargetButton, ImageGuideWidget::PreviewTargetImage);
+ TDEGlobal::dirs()->addResourceType("target", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("target", "target.png");
+ previewtargetButton->setPixmap( TQPixmap( directory + "target.png" ) );
+ previewtargetButton->setToggleButton(true);
+ TQWhatsThis::add( previewtargetButton, i18n( "<p>If you enable this option, you will see "
+ "the target image." ) );
+
+ TQPushButton *previewToggleMouseOverButton = new TQPushButton( d->previewButtons );
+ d->previewButtons->insert(previewToggleMouseOverButton, ImageGuideWidget::PreviewToggleOnMouseOver);
+ TDEGlobal::dirs()->addResourceType("togglemouseover", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("togglemouseover", "togglemouseover.png");
+ previewToggleMouseOverButton->setPixmap( TQPixmap( directory + "togglemouseover.png" ) );
+ previewToggleMouseOverButton->setToggleButton(true);
+ TQWhatsThis::add( previewToggleMouseOverButton, i18n( "<p>If you enable this option, you will see "
+ "the original image when the mouse is over image area, "
+ "else the target image." ) );
+
+ // -------------------------------------------------------------
+
+ TQHButtonGroup *exposureButtons = new TQHButtonGroup(this);
+ exposureButtons->setInsideMargin(0);
+ exposureButtons->setFrameShape(TQFrame::NoFrame);
+
+ d->underExposureButton = new TQPushButton(exposureButtons);
+ exposureButtons->insert(d->underExposureButton, UnderExposure);
+ d->underExposureButton->setPixmap(SmallIcon("underexposure"));
+ d->underExposureButton->setToggleButton(true);
+ TQWhatsThis::add(d->underExposureButton, i18n("<p>Set this option to display black "
+ "overlaid on the preview. This will help you to avoid "
+ "under-exposing the image." ) );
+
+ d->overExposureButton = new TQPushButton(exposureButtons);
+ exposureButtons->insert(d->overExposureButton, OverExposure);
+ d->overExposureButton->setPixmap(SmallIcon("overexposure"));
+ d->overExposureButton->setToggleButton(true);
+ TQWhatsThis::add(d->overExposureButton, i18n("<p>Set this option on display white "
+ "overlaid on the preview. This will help you to avoid "
+ "over-exposing the image." ) );
+
+ // -------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(this);
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* l = new TQVBoxLayout(frame, 5, 0);
+ d->previewWidget = new ImageGuideWidget(480, 320, frame, guideVisible,
+ guideMode, TQt::red, 1, false,
+ useImageSelection);
+ TQWhatsThis::add( d->previewWidget, previewWhatsThis);
+ l->addWidget(d->previewWidget, 0);
+
+ // -------------------------------------------------------------
+
+ grid->addMultiCellWidget(d->previewButtons, 1, 1, 0, 0);
+ grid->addMultiCellWidget(d->spotInfoLabel, 1, 1, 1, 1);
+ grid->addMultiCellWidget(exposureButtons, 1, 1, 2, 2);
+ grid->addMultiCellWidget(frame, 3, 3, 0, 2);
+ grid->setColSpacing(1, KDialog::spacingHint());
+ grid->setRowSpacing(0, KDialog::spacingHint());
+ grid->setRowSpacing(2, KDialog::spacingHint());
+ grid->setRowStretch(3, 10);
+ grid->setColStretch(1, 10);
+
+ // -------------------------------------------------------------
+
+ connect(d->previewWidget, TQ_SIGNAL(signalResized()),
+ this, TQ_SIGNAL(signalResized()));
+
+ connect(d->previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )));
+
+ connect(d->previewWidget, TQ_SIGNAL(spotPositionChangedFromOriginal( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotUpdateSpotInfo( const Digikam::DColor &, const TQPoint & )));
+
+ connect(d->previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )));
+
+ connect(d->previewWidget, TQ_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )),
+ this, TQ_SLOT(slotUpdateSpotInfo( const Digikam::DColor &, const TQPoint & )));
+
+ connect(d->previewButtons, TQ_SIGNAL(released(int)),
+ d->previewWidget, TQ_SLOT(slotChangeRenderingPreviewMode(int)));
+
+ connect(d->underExposureButton, TQ_SIGNAL(toggled(bool)),
+ d->previewWidget, TQ_SLOT(slotToggleUnderExposure(bool)));
+
+ connect(d->overExposureButton, TQ_SIGNAL(toggled(bool)),
+ d->previewWidget, TQ_SLOT(slotToggleOverExposure(bool)));
+
+ // -------------------------------------------------------------
+
+ if (prevModeOptions)
+ readSettings();
+ else
+ {
+ setRenderingPreviewMode(ImageGuideWidget::NoPreviewMode);
+ d->spotInfoLabel->hide();
+ d->previewButtons->hide();
+ exposureButtons->hide();
+ }
+}
+
+ImageWidget::~ImageWidget()
+{
+ writeSettings();
+ delete d;
+}
+
+ImageIface* ImageWidget::imageIface()
+{
+ return d->previewWidget->imageIface();
+}
+
+void ImageWidget::updatePreview()
+{
+ d->previewWidget->updatePreview();
+}
+
+void ImageWidget::slotChangeGuideColor(const TQColor &color)
+{
+ d->previewWidget->slotChangeGuideColor(color);
+}
+
+void ImageWidget::slotChangeGuideSize(int size)
+{
+ d->previewWidget->slotChangeGuideSize(size);
+}
+
+void ImageWidget::resetSpotPosition()
+{
+ d->previewWidget->resetSpotPosition();
+}
+
+TQPoint ImageWidget::getSpotPosition()
+{
+ return ( d->previewWidget->getSpotPosition() );
+}
+
+DColor ImageWidget::getSpotColor(int getColorFrom)
+{
+ return ( d->previewWidget->getSpotColor(getColorFrom) );
+}
+
+void ImageWidget::setSpotVisible(bool spotVisible, bool blink)
+{
+ d->previewWidget->setSpotVisible(spotVisible, blink);
+}
+
+int ImageWidget::getRenderingPreviewMode()
+{
+ return ( d->previewWidget->getRenderingPreviewMode() );
+}
+
+void ImageWidget::setRenderingPreviewMode(int mode)
+{
+ d->previewButtons->setButton(mode);
+ d->previewWidget->slotChangeRenderingPreviewMode(mode);
+}
+
+void ImageWidget::slotUpdateSpotInfo(const Digikam::DColor &col, const TQPoint &point)
+{
+ DColor color = col;
+ d->spotInfoLabel->setText(i18n("(%1,%2) RGBA:%3,%4,%5,%6")
+ .arg(point.x()).arg(point.y())
+ .arg(color.red()).arg(color.green())
+ .arg(color.blue()).arg(color.alpha()) );
+}
+
+void ImageWidget::readSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->settingsSection);
+
+ d->underExposureButton->setOn(config->readBoolEntry("Under Exposure Indicator", false));
+ d->overExposureButton->setOn(config->readBoolEntry("Over Exposure Indicator", false));
+
+ int mode = config->readNumEntry("Separate View", ImageGuideWidget::PreviewBothImagesVertCont);
+ mode = TQMAX(ImageGuideWidget::PreviewOriginalImage, mode);
+ mode = TQMIN(ImageGuideWidget::NoPreviewMode, mode);
+ setRenderingPreviewMode(mode);
+}
+
+void ImageWidget::writeSettings()
+{
+ TDEConfig *config = kapp->config();
+ config->setGroup(d->settingsSection);
+ config->writeEntry("Separate View", getRenderingPreviewMode());
+ config->writeEntry("Under Exposure Indicator", d->underExposureButton->isOn());
+ config->writeEntry("Over Exposure Indicator", d->overExposureButton->isOn());
+ config->sync();
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/imageplugins/imagewidget.h b/src/libs/widgets/imageplugins/imagewidget.h
new file mode 100644
index 00000000..ebdf92e9
--- /dev/null
+++ b/src/libs/widgets/imageplugins/imagewidget.h
@@ -0,0 +1,106 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-01
+ * Description : a widget to display an image preview with some
+ * modes to compare effect results.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEWIDGET_H
+#define IMAGEWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqpoint.h>
+#include <tqcolor.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "dcolor.h"
+#include "imageguidewidget.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImageIface;
+class ImageWidgetPriv;
+
+class DIGIKAM_EXPORT ImageWidget : public TQWidget
+{
+TQ_OBJECT
+
+
+public:
+
+ enum ExposureIndicator
+ {
+ UnderExposure=0,
+ OverExposure
+ };
+
+public:
+
+ ImageWidget(const TQString& settingsSection, TQWidget *parent=0,
+ const TQString& previewWhatsThis=TQString(), bool prevModeOptions=true,
+ int guideMode=ImageGuideWidget::PickColorMode,
+ bool guideVisible=true, bool useImageSelection=false);
+ ~ImageWidget();
+
+ ImageIface* imageIface();
+
+ TQPoint getSpotPosition();
+ DColor getSpotColor(int getColorFrom);
+ void setSpotVisible(bool spotVisible, bool blink=false);
+ int getRenderingPreviewMode();
+ void resetSpotPosition();
+ void updatePreview();
+ void writeSettings();
+
+ void setRenderingPreviewMode(int mode);
+
+public slots:
+
+ void slotChangeGuideColor(const TQColor &color);
+ void slotChangeGuideSize(int size);
+
+signals:
+
+ void spotPositionChangedFromOriginal( const Digikam::DColor &color, const TQPoint &position );
+ void spotPositionChangedFromTarget( const Digikam::DColor &color, const TQPoint &position );
+ void signalResized();
+
+private slots:
+
+ void slotUpdateSpotInfo(const Digikam::DColor &col, const TQPoint &point);
+
+private:
+
+ void readSettings();
+
+private:
+
+ ImageWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* IMAGEWIDGET_H */
diff --git a/src/libs/widgets/imageplugins/listboxpreviewitem.cpp b/src/libs/widgets/imageplugins/listboxpreviewitem.cpp
new file mode 100644
index 00000000..8ba63a61
--- /dev/null
+++ b/src/libs/widgets/imageplugins/listboxpreviewitem.cpp
@@ -0,0 +1,62 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-10-05
+ * Description : a TQListBoxItem which can display an image preview
+ * as a thumbnail and a customized qwhatsthis class
+ * for listbox items
+ *
+ * Copyright (C) 2006-2007 by Guillaume Laurent <glaurent@telegraph-road.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "listboxpreviewitem.h"
+
+namespace Digikam
+{
+
+int ListBoxPreviewItem::height(const TQListBox *lb) const
+{
+ int height = TQListBoxPixmap::height(lb);
+ return TQMAX(height, pixmap()->height() + 5);
+}
+
+int ListBoxPreviewItem::width(const TQListBox *lb) const
+{
+ int width = TQListBoxPixmap::width(lb);
+ return TQMAX(width, pixmap()->width() + 5);
+}
+
+// -------------------------------------------------------------------
+
+TQString ListBoxWhatsThis::text(const TQPoint &p)
+{
+ TQListBoxItem* item = m_listBox->itemAt(p);
+
+ if (item != 0)
+ return m_itemWhatsThisMap[item];
+
+ return TQString();
+}
+
+void ListBoxWhatsThis::add(TQListBoxItem* item, const TQString& text)
+{
+ m_itemWhatsThisMap[item] = text;
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/imageplugins/listboxpreviewitem.h b/src/libs/widgets/imageplugins/listboxpreviewitem.h
new file mode 100644
index 00000000..50b769c6
--- /dev/null
+++ b/src/libs/widgets/imageplugins/listboxpreviewitem.h
@@ -0,0 +1,80 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-10-05
+ * Description : a TQListBoxItem which can display an image preview
+ * as a thumbnail and a customized qwhatsthis class
+ * for listbox items
+ *
+ * Copyright (C) 2006-2007 by Guillaume Laurent <glaurent@telegraph-road.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef LISTBOXPREVIEWITEM_H
+#define LISTBOXPREVIEWITEM_H
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqlistbox.h>
+#include <tqwhatsthis.h>
+#include <tqpixmap.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT ListBoxPreviewItem : public TQListBoxPixmap
+{
+
+public:
+
+ ListBoxPreviewItem(TQListBox *listbox, const TQPixmap &pix, const TQString &text)
+ : TQListBoxPixmap(listbox, pix, text) {};
+
+ ListBoxPreviewItem(const TQPixmap &pix, const TQString &text)
+ : TQListBoxPixmap(pix, text) {};
+
+ virtual int height ( const TQListBox * lb ) const;
+ virtual int width ( const TQListBox * lb ) const;
+};
+
+/**
+ * A qwhatthis class which can be pointed to a specific item
+ * in a TQListBox rather than the TQListBox itself
+ *
+ */
+class DIGIKAM_EXPORT ListBoxWhatsThis : public TQWhatsThis
+{
+
+public:
+
+ ListBoxWhatsThis(TQListBox* w) : TQWhatsThis(w), m_listBox(w) {}
+ virtual TQString text (const TQPoint &);
+ void add(TQListBoxItem*, const TQString& text);
+
+protected:
+
+ TQMap<TQListBoxItem*, TQString> m_itemWhatsThisMap;
+ TQListBox *m_listBox;
+};
+
+} // namespace Digikam
+
+#endif // LISTBOXPREVIEWITEM_H
diff --git a/src/libs/widgets/metadata/Makefile.am b/src/libs/widgets/metadata/Makefile.am
new file mode 100644
index 00000000..4fe62356
--- /dev/null
+++ b/src/libs/widgets/metadata/Makefile.am
@@ -0,0 +1,17 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libmetadatawidgets.la
+
+libmetadatawidgets_la_SOURCES = metadatalistview.cpp metadatalistviewitem.cpp metadatawidget.cpp \
+ iptcwidget.cpp exifwidget.cpp mdkeylistviewitem.cpp \
+ makernotewidget.cpp gpswidget.cpp worldmapwidget.cpp
+
+libmetadatawidgets_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+INCLUDES = -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/digikam \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
diff --git a/src/libs/widgets/metadata/exifwidget.cpp b/src/libs/widgets/metadata/exifwidget.cpp
new file mode 100644
index 00000000..7761c250
--- /dev/null
+++ b/src/libs/widgets/metadata/exifwidget.cpp
@@ -0,0 +1,185 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : a widget to display Standard Exif metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "metadatalistview.h"
+#include "exifwidget.h"
+#include "exifwidget.moc"
+
+namespace Digikam
+{
+
+static const char* ExifHumanList[] =
+{
+ "Make",
+ "Model",
+ "DateTime",
+ "ImageDescription",
+ "Copyright",
+ "ShutterSpeedValue",
+ "ApertureValue",
+ "ExposureProgram",
+ "ExposureMode",
+ "ExposureBiasValue",
+ "ExposureTime",
+ "WhiteBalance",
+ "ISOSpeedRatings",
+ "FocalLength",
+ "SubjectDistance",
+ "MeteringMode",
+ "Contrast",
+ "Saturation",
+ "Sharpness",
+ "LightSource",
+ "Flash",
+ "FNumber",
+ "-1"
+};
+
+// Standard Exif Entry list from to less important to the most important for photograph.
+// This will not including GPS information because they are displayed on another tab.
+static const char* StandardExifEntryList[] =
+{
+ "Iop",
+ "Thumbnail",
+ "SubImage1",
+ "SubImage2",
+ "Image",
+ "Photo",
+ "-1"
+};
+
+ExifWidget::ExifWidget(TQWidget* parent, const char* name)
+ : MetadataWidget(parent, name)
+{
+ view()->setSortColumn(-1);
+
+ for (int i=0 ; TQString(StandardExifEntryList[i]) != TQString("-1") ; i++)
+ m_keysFilter << StandardExifEntryList[i];
+
+ for (int i=0 ; TQString(ExifHumanList[i]) != TQString("-1") ; i++)
+ m_tagsfilter << ExifHumanList[i];
+}
+
+ExifWidget::~ExifWidget()
+{
+}
+
+TQString ExifWidget::getMetadataTitle()
+{
+ return i18n("Standard EXIF Tags");
+}
+
+bool ExifWidget::loadFromURL(const KURL& url)
+{
+ setFileName(url.path());
+
+ if (url.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ {
+ DMetadata metadata(url.path());
+ TQByteArray exifData = metadata.getExif();
+
+ if (exifData.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ setMetadata(exifData);
+ }
+
+ return true;
+}
+
+bool ExifWidget::decodeMetadata()
+{
+ DMetadata metaData;
+ if (!metaData.setExif(getMetadata()))
+ return false;
+
+ // Update all metadata contents.
+ setMetadataMap(metaData.getExifTagsDataList(m_keysFilter));
+ return true;
+}
+
+void ExifWidget::buildView()
+{
+ if (getMode() == SIMPLE)
+ {
+ setIfdList(getMetadataMap(), m_keysFilter, m_tagsfilter);
+ }
+ else
+ {
+ setIfdList(getMetadataMap(), m_keysFilter, TQStringList());
+ }
+
+ MetadataWidget::buildView();
+}
+
+TQString ExifWidget::getTagTitle(const TQString& key)
+{
+ DMetadata meta;
+ TQString title = meta.getExifTagTitle(key.ascii());
+
+ if (title.isEmpty())
+ return key.section('.', -1);
+
+ return title;
+}
+
+TQString ExifWidget::getTagDescription(const TQString& key)
+{
+ DMetadata meta;
+ TQString desc = meta.getExifTagDescription(key.ascii());
+
+ if (desc.isEmpty())
+ return i18n("No description available");
+
+ return desc;
+}
+
+void ExifWidget::slotSaveMetadataToFile()
+{
+ KURL url = saveMetadataToFile(i18n("EXIF File to Save"),
+ TQString("*.exif|"+i18n("EXIF binary Files (*.exif)")));
+ storeMetadataToFile(url);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/metadata/exifwidget.h b/src/libs/widgets/metadata/exifwidget.h
new file mode 100644
index 00000000..ba66d085
--- /dev/null
+++ b/src/libs/widgets/metadata/exifwidget.h
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : a widget to display Standard Exif metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EXIFWIDGET_H
+#define EXIFWIDGET_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Local includes.
+
+#include "metadatawidget.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT ExifWidget : public MetadataWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ ExifWidget(TQWidget* parent, const char* name=0);
+ ~ExifWidget();
+
+ bool loadFromURL(const KURL& url);
+
+ TQString getTagDescription(const TQString& key);
+ TQString getTagTitle(const TQString& key);
+
+ TQString getMetadataTitle();
+
+protected slots:
+
+ virtual void slotSaveMetadataToFile();
+
+private:
+
+ bool decodeMetadata();
+ void buildView();
+
+private:
+
+ TQStringList m_tagsfilter;
+ TQStringList m_keysFilter;
+};
+
+} // namespace Digikam
+
+#endif /* EXIFWIDGET_H */
diff --git a/src/libs/widgets/metadata/gpswidget.cpp b/src/libs/widgets/metadata/gpswidget.cpp
new file mode 100644
index 00000000..033423e9
--- /dev/null
+++ b/src/libs/widgets/metadata/gpswidget.cpp
@@ -0,0 +1,340 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-22
+ * Description : a tab widget to display GPS info
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+/*
+Any good explainations about GPS (in French) can be found at this url :
+http://www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=16593
+*/
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqmap.h>
+#include <tqhbox.h>
+#include <tqfile.h>
+#include <tqcombobox.h>
+#include <tqgroupbox.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "metadatalistview.h"
+#include "worldmapwidget.h"
+#include "gpswidget.h"
+#include "gpswidget.moc"
+
+namespace Digikam
+{
+
+static const char* ExifGPSHumanList[] =
+{
+ "GPSLatitude",
+ "GPSLongitude",
+ "GPSAltitude",
+ "-1"
+};
+
+// Standard Exif Entry list from to less important to the most important for photograph.
+static const char* StandardExifGPSEntryList[] =
+{
+ "GPSInfo",
+ "-1"
+};
+
+class GPSWidgetPriv
+{
+
+public:
+
+ GPSWidgetPriv()
+ {
+ detailsButton = 0;
+ detailsCombo = 0;
+ map = 0;
+ }
+
+ TQStringList tagsfilter;
+ TQStringList keysFilter;
+
+ TQPushButton *detailsButton;
+
+ TQComboBox *detailsCombo;
+
+ WorldMapWidget *map;
+};
+
+GPSWidget::GPSWidget(TQWidget* parent, const char* name)
+ : MetadataWidget(parent, name)
+{
+ d = new GPSWidgetPriv;
+
+ for (int i=0 ; TQString(StandardExifGPSEntryList[i]) != TQString("-1") ; i++)
+ d->keysFilter << StandardExifGPSEntryList[i];
+
+ for (int i=0 ; TQString(ExifGPSHumanList[i]) != TQString("-1") ; i++)
+ d->tagsfilter << ExifGPSHumanList[i];
+
+ // --------------------------------------------------------
+
+ TQWidget *gpsInfo = new TQWidget(this);
+ TQGridLayout *layout = new TQGridLayout(gpsInfo, 3, 2);
+ d->map = new WorldMapWidget(256, 256, gpsInfo);
+
+ // --------------------------------------------------------
+
+ TQGroupBox* box2 = new TQGroupBox( 0, TQt::Vertical, gpsInfo );
+ box2->setInsideMargin(0);
+ box2->setInsideSpacing(0);
+ box2->setFrameStyle( TQFrame::NoFrame );
+ TQGridLayout* box2Layout = new TQGridLayout( box2->layout(), 0, 2, KDialog::spacingHint() );
+
+ d->detailsCombo = new TQComboBox( false, box2 );
+ d->detailsButton = new TQPushButton(i18n("More Info..."), box2);
+ d->detailsCombo->insertItem(TQString("MapQuest"), MapQuest);
+ d->detailsCombo->insertItem(TQString("Google Maps"), GoogleMaps);
+ d->detailsCombo->insertItem(TQString("MSN Maps"), MsnMaps);
+ d->detailsCombo->insertItem(TQString("MultiMap"), MultiMap);
+
+ box2Layout->addMultiCellWidget( d->detailsCombo, 0, 0, 0, 0 );
+ box2Layout->addMultiCellWidget( d->detailsButton, 0, 0, 1, 1 );
+ box2Layout->setColStretch(2, 10);
+
+ // --------------------------------------------------------
+
+ layout->addMultiCellWidget(d->map, 0, 0, 0, 2);
+ layout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 1, 1, 0, 2);
+ layout->addMultiCellWidget(box2, 2, 2, 0, 0);
+ layout->setColStretch(2, 10);
+ layout->setRowStretch(3, 10);
+
+ // --------------------------------------------------------
+
+ connect(d->detailsButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotGPSDetails()));
+
+ setUserAreaWidget(gpsInfo);
+ decodeMetadata();
+}
+
+GPSWidget::~GPSWidget()
+{
+ delete d;
+}
+
+int GPSWidget::getWebGPSLocator()
+{
+ return ( d->detailsCombo->currentItem() );
+}
+
+void GPSWidget::setWebGPSLocator(int locator)
+{
+ d->detailsCombo->setCurrentItem(locator);
+}
+
+void GPSWidget::slotGPSDetails()
+{
+ TQString val, url;
+
+ switch( getWebGPSLocator() )
+ {
+ case MapQuest:
+ {
+ url.append("http://www.mapquest.com/maps/map.adp?searchtype=address"
+ "&formtype=address&latlongtype=decimal");
+ url.append("&latitude=");
+ url.append(val.setNum(d->map->getLatitude(), 'g', 12));
+ url.append("&longitude=");
+ url.append(val.setNum(d->map->getLongitude(), 'g', 12));
+ break;
+ }
+
+ case GoogleMaps:
+ {
+ url.append("http://maps.google.com/?q=");
+ url.append(val.setNum(d->map->getLatitude(), 'g', 12));
+ url.append(",");
+ url.append(val.setNum(d->map->getLongitude(), 'g', 12));
+ url.append("&spn=0.05,0.05&t=h&om=1&hl=en");
+ break;
+ }
+
+ case MsnMaps:
+ {
+ url.append("http://maps.msn.com/map.aspx?");
+ url.append("&lats1=");
+ url.append(val.setNum(d->map->getLatitude(), 'g', 12));
+ url.append("&lons1=");
+ url.append(val.setNum(d->map->getLongitude(), 'g', 12));
+ url.append("&name=HERE");
+ url.append("&alts1=7");
+ break;
+ }
+
+ case MultiMap:
+ {
+ url.append("http://www.multimap.com/map/browse.cgi?");
+ url.append("lat=");
+ url.append(val.setNum(d->map->getLatitude(), 'g', 12));
+ url.append("&lon=");
+ url.append(val.setNum(d->map->getLongitude(), 'g', 12));
+ url.append("&scale=10000");
+ url.append("&icon=x");
+ break;
+ }
+ }
+
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+TQString GPSWidget::getMetadataTitle()
+{
+ return i18n("Global Positioning System Information");
+}
+
+bool GPSWidget::loadFromURL(const KURL& url)
+{
+ setFileName(url.path());
+
+ if (url.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ {
+ DMetadata metadata(url.path());
+ TQByteArray exifData = metadata.getExif();
+
+ if (exifData.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ setMetadata(exifData);
+ }
+
+ return true;
+}
+
+bool GPSWidget::decodeMetadata()
+{
+ DMetadata metaData;
+ if (!metaData.setExif(getMetadata()))
+ {
+ setMetadataEmpty();
+ return false;
+ }
+
+ // Update all metadata contents.
+ setMetadataMap(metaData.getExifTagsDataList(d->keysFilter));
+
+ bool ret = decodeGPSPosition();
+ if (!ret)
+ {
+ setMetadataEmpty();
+ return false;
+ }
+
+ d->map->setEnabled(true);
+ d->detailsButton->setEnabled(true);
+ d->detailsCombo->setEnabled(true);
+ return true;
+}
+
+void GPSWidget::setMetadataEmpty()
+{
+ MetadataWidget::setMetadataEmpty();
+ d->map->setEnabled(false);
+ d->detailsButton->setEnabled(false);
+ d->detailsCombo->setEnabled(false);
+}
+
+void GPSWidget::buildView()
+{
+ if (getMode() == SIMPLE)
+ {
+ setIfdList(getMetadataMap(), d->keysFilter, d->tagsfilter);
+ }
+ else
+ {
+ setIfdList(getMetadataMap(), d->keysFilter, TQStringList());
+ }
+
+ MetadataWidget::buildView();
+}
+
+TQString GPSWidget::getTagTitle(const TQString& key)
+{
+ DMetadata meta;
+ TQString title = meta.getExifTagTitle(key.ascii());
+
+ if (title.isEmpty())
+ return key.section('.', -1);
+
+ return title;
+}
+
+TQString GPSWidget::getTagDescription(const TQString& key)
+{
+ DMetadata meta;
+ TQString desc = meta.getExifTagDescription(key.ascii());
+
+ if (desc.isEmpty())
+ return i18n("No description available");
+
+ return desc;
+}
+
+bool GPSWidget::decodeGPSPosition()
+{
+ double latitude=0.0, longitude=0.0, altitude=0.0;
+
+ DMetadata meta;
+ meta.setExif(getMetadata());
+
+ if (meta.getGPSInfo(altitude, latitude, longitude))
+ d->map->setGPSPosition(latitude, longitude);
+ else
+ return false;
+
+ return true;
+}
+
+void GPSWidget::slotSaveMetadataToFile()
+{
+ KURL url = saveMetadataToFile(i18n("EXIF File to Save"),
+ TQString("*.exif|"+i18n("EXIF binary Files (*.exif)")));
+ storeMetadataToFile(url);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/metadata/gpswidget.h b/src/libs/widgets/metadata/gpswidget.h
new file mode 100644
index 00000000..fd787d67
--- /dev/null
+++ b/src/libs/widgets/metadata/gpswidget.h
@@ -0,0 +1,95 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-22
+ * Description : a tab widget to display GPS info
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef GPSWIDGET_H
+#define GPSWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "metadatawidget.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class GPSWidgetPriv;
+class WorldMapWidget;
+
+class DIGIKAM_EXPORT GPSWidget : public MetadataWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum WebGPSLocator
+ {
+ MapQuest = 0,
+ GoogleMaps,
+ MsnMaps,
+ MultiMap
+ };
+
+public:
+
+ GPSWidget(TQWidget* parent, const char* name=0);
+ ~GPSWidget();
+
+ bool loadFromURL(const KURL& url);
+
+ TQString getTagDescription(const TQString& key);
+ TQString getTagTitle(const TQString& key);
+
+ TQString getMetadataTitle();
+
+ int getWebGPSLocator();
+ void setWebGPSLocator(int locator);
+
+protected slots:
+
+ virtual void slotSaveMetadataToFile();
+
+private slots:
+
+ void slotGPSDetails();
+
+private:
+
+ bool decodeMetadata();
+ void buildView();
+ bool decodeGPSPosition();
+ virtual void setMetadataEmpty();
+
+private:
+
+ GPSWidgetPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* GPSWIDGET_H */
diff --git a/src/libs/widgets/metadata/iptcwidget.cpp b/src/libs/widgets/metadata/iptcwidget.cpp
new file mode 100644
index 00000000..b0bcb009
--- /dev/null
+++ b/src/libs/widgets/metadata/iptcwidget.cpp
@@ -0,0 +1,167 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : A widget to display IPTC metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "iptcwidget.h"
+#include "iptcwidget.moc"
+
+namespace Digikam
+{
+
+static const char* IptcHumanList[] =
+{
+ "Caption",
+ "City",
+ "Contact",
+ "Copyright",
+ "Credit",
+ "DateCreated",
+ "Headline",
+ "Keywords",
+ "ProvinceState",
+ "Source",
+ "Urgency",
+ "Writer",
+ "-1"
+};
+
+static const char* StandardIptcEntryList[] =
+{
+ "Envelope",
+ "Application2",
+ "-1"
+};
+
+IptcWidget::IptcWidget(TQWidget* parent, const char* name)
+ : MetadataWidget(parent, name)
+{
+ for (int i=0 ; TQString(StandardIptcEntryList[i]) != TQString("-1") ; i++)
+ m_keysFilter << StandardIptcEntryList[i];
+
+ for (int i=0 ; TQString(IptcHumanList[i]) != TQString("-1") ; i++)
+ m_tagsfilter << IptcHumanList[i];
+}
+
+IptcWidget::~IptcWidget()
+{
+}
+
+TQString IptcWidget::getMetadataTitle()
+{
+ return i18n("IPTC Records");
+}
+
+bool IptcWidget::loadFromURL(const KURL& url)
+{
+ setFileName(url.filename());
+
+ if (url.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ {
+ DMetadata metadata(url.path());
+ TQByteArray iptcData = metadata.getIptc();
+
+ if (iptcData.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ setMetadata(iptcData);
+ }
+
+ return true;
+}
+
+bool IptcWidget::decodeMetadata()
+{
+ DMetadata metaData;
+ if (!metaData.setIptc(getMetadata()))
+ return false;
+
+ // Update all metadata contents.
+ setMetadataMap(metaData.getIptcTagsDataList(m_keysFilter));
+ return true;
+}
+
+void IptcWidget::buildView()
+{
+ if (getMode() == SIMPLE)
+ {
+ setIfdList(getMetadataMap(), m_tagsfilter);
+ }
+ else
+ {
+ setIfdList(getMetadataMap());
+ }
+
+ MetadataWidget::buildView();
+}
+
+TQString IptcWidget::getTagTitle(const TQString& key)
+{
+ DMetadata meta;
+ TQString title = meta.getIptcTagTitle(key.ascii());
+
+ if (title.isEmpty())
+ return key.section('.', -1);
+
+ return title;
+}
+
+TQString IptcWidget::getTagDescription(const TQString& key)
+{
+ DMetadata meta;
+ TQString desc = meta.getIptcTagDescription(key.ascii());
+
+ if (desc.isEmpty())
+ return i18n("No description available");
+
+ return desc;
+}
+
+void IptcWidget::slotSaveMetadataToFile()
+{
+ KURL url = saveMetadataToFile(i18n("IPTC File to Save"),
+ TQString("*.iptc|"+i18n("IPTC binary Files (*.iptc)")));
+ storeMetadataToFile(url);
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/widgets/metadata/iptcwidget.h b/src/libs/widgets/metadata/iptcwidget.h
new file mode 100644
index 00000000..58f1449e
--- /dev/null
+++ b/src/libs/widgets/metadata/iptcwidget.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : A widget to display IPTC metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IPTCWIDGET_H
+#define IPTCWIDGET_H
+
+// Local includes.
+
+#include "metadatawidget.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT IptcWidget : public MetadataWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ IptcWidget(TQWidget* parent, const char* name=0);
+ ~IptcWidget();
+
+ bool loadFromURL(const KURL& url);
+
+ TQString getTagDescription(const TQString& key);
+ TQString getTagTitle(const TQString& key);
+
+ TQString getMetadataTitle();
+
+protected slots:
+
+ virtual void slotSaveMetadataToFile();
+
+private:
+
+ bool decodeMetadata();
+ void buildView();
+
+private:
+
+ TQStringList m_tagsfilter;
+ TQStringList m_keysFilter;
+};
+
+} // namespace Digikam
+
+#endif /* IPTCWIDGET_H */
diff --git a/src/libs/widgets/metadata/makernotewidget.cpp b/src/libs/widgets/metadata/makernotewidget.cpp
new file mode 100644
index 00000000..ddd1fe3c
--- /dev/null
+++ b/src/libs/widgets/metadata/makernotewidget.cpp
@@ -0,0 +1,210 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : a widget to display non standard Exif metadata
+ * used by camera makers
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqmap.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dmetadata.h"
+#include "makernotewidget.h"
+#include "makernotewidget.moc"
+
+namespace Digikam
+{
+
+// This list mix differents tags name used by camera makers.
+static const char* MakerNoteHumanList[] =
+{
+ "AFFocusPos",
+ "AFMode",
+ "AFPoint",
+ "AutofocusMode",
+ "ColorMode",
+ "ColorTemperature",
+ "Contrast",
+ "DigitalZoom",
+ "ExposureMode",
+ "ExposureProgram",
+ "ExposureCompensation",
+ "ExposureManualBias",
+ "Flash",
+ "FlashBias",
+ "FlashMode",
+ "FlashType",
+ "FlashDevice",
+ "FNumber",
+ "Focus"
+ "FocusDistance",
+ "FocusMode",
+ "FocusSetting",
+ "FocusType",
+ "Hue",
+ "HueAdjustment",
+ "ImageStabilizer",
+ "ImageStabilization",
+ "InternalFlash",
+ "ISOSelection",
+ "ISOSpeed",
+ "Lens",
+ "LensType",
+ "LensRange",
+ "Macro",
+ "MacroFocus",
+ "MeteringMode",
+ "NoiseReduction",
+ "OwnerName",
+ "Quality",
+ "Tone",
+ "ToneComp",
+ "Saturation",
+ "Sharpness",
+ "ShootingMode",
+ "ShutterSpeedValue",
+ "SpotMode",
+ "SubjectDistance",
+ "WhiteBalance",
+ "WhiteBalanceBias",
+ "-1"
+};
+
+static const char* ExifEntryListToIgnore[] =
+{
+ "GPSInfo",
+ "Iop",
+ "Thumbnail",
+ "SubImage1",
+ "SubImage2",
+ "Image",
+ "Photo",
+ "-1"
+};
+
+MakerNoteWidget::MakerNoteWidget(TQWidget* parent, const char* name)
+ : MetadataWidget(parent, name)
+{
+ for (int i=0 ; TQString(ExifEntryListToIgnore[i]) != TQString("-1") ; i++)
+ m_keysFilter << ExifEntryListToIgnore[i];
+
+ for (int i=0 ; TQString(MakerNoteHumanList[i]) != TQString("-1") ; i++)
+ m_tagsfilter << MakerNoteHumanList[i];
+}
+
+MakerNoteWidget::~MakerNoteWidget()
+{
+}
+
+TQString MakerNoteWidget::getMetadataTitle()
+{
+ return i18n("MakerNote EXIF Tags");
+}
+
+bool MakerNoteWidget::loadFromURL(const KURL& url)
+{
+ setFileName(url.path());
+
+ if (url.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ {
+ DMetadata metadata(url.path());
+ TQByteArray exifData = metadata.getExif();
+
+ if (exifData.isEmpty())
+ {
+ setMetadata();
+ return false;
+ }
+ else
+ setMetadata(exifData);
+ }
+
+ return true;
+}
+
+bool MakerNoteWidget::decodeMetadata()
+{
+ DMetadata metaData;
+ if (!metaData.setExif(getMetadata()))
+ return false;
+
+ // Update all metadata contents.
+ setMetadataMap(metaData.getExifTagsDataList(m_keysFilter, true));
+ return true;
+}
+
+void MakerNoteWidget::buildView()
+{
+ if (getMode() == SIMPLE)
+ {
+ setIfdList(getMetadataMap(), m_tagsfilter);
+ }
+ else
+ {
+ setIfdList(getMetadataMap());
+ }
+
+ MetadataWidget::buildView();
+}
+
+TQString MakerNoteWidget::getTagTitle(const TQString& key)
+{
+ DMetadata meta;
+ TQString title = meta.getExifTagTitle(key.ascii());
+
+ if (title.isEmpty())
+ return key.section('.', -1);
+
+ return title;
+}
+
+TQString MakerNoteWidget::getTagDescription(const TQString& key)
+{
+ DMetadata meta;
+ TQString desc = meta.getExifTagDescription(key.ascii());
+
+ if (desc.isEmpty())
+ return i18n("No description available");
+
+ return desc;
+}
+
+void MakerNoteWidget::slotSaveMetadataToFile()
+{
+ KURL url = saveMetadataToFile(i18n("EXIF File to Save"),
+ TQString("*.exif|"+i18n("EXIF binary Files (*.exif)")));
+ storeMetadataToFile(url);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/metadata/makernotewidget.h b/src/libs/widgets/metadata/makernotewidget.h
new file mode 100644
index 00000000..f6549591
--- /dev/null
+++ b/src/libs/widgets/metadata/makernotewidget.h
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : a widget to display non standard Exif metadata
+ * used by camera makers
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MARKERNOTEWIDGET_H
+#define MARKERNOTEWIDGET_H
+
+// Local includes.
+
+#include "metadatawidget.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT MakerNoteWidget : public MetadataWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ MakerNoteWidget(TQWidget* parent, const char* name=0);
+ ~MakerNoteWidget();
+
+ bool loadFromURL(const KURL& url);
+
+ TQString getTagDescription(const TQString& key);
+ TQString getTagTitle(const TQString& key);
+
+ TQString getMetadataTitle();
+
+protected slots:
+
+ virtual void slotSaveMetadataToFile();
+
+private:
+
+ bool decodeMetadata();
+ void buildView();
+
+private:
+
+ TQStringList m_tagsfilter;
+ TQStringList m_keysFilter;
+};
+
+} // namespace Digikam
+
+#endif /* MARKERNOTEWIDGET_H */
diff --git a/src/libs/widgets/metadata/mdkeylistviewitem.cpp b/src/libs/widgets/metadata/mdkeylistviewitem.cpp
new file mode 100644
index 00000000..27963eaf
--- /dev/null
+++ b/src/libs/widgets/metadata/mdkeylistviewitem.cpp
@@ -0,0 +1,94 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-21
+ * Description : a generic list view item widget to
+ * display metadata key like a title
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpalette.h>
+#include <tqfont.h>
+#include <tqpainter.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "themeengine.h"
+#include "ddebug.h"
+#include "mdkeylistviewitem.h"
+
+namespace Digikam
+{
+
+MdKeyListViewItem::MdKeyListViewItem(TQListView *parent, const TQString& key)
+ : TQListViewItem(parent)
+{
+ m_decryptedKey = key;
+
+ // Standard Exif key descriptions.
+ if (key == "Iop") m_decryptedKey = i18n("Interoperability");
+ else if (key == "Image") m_decryptedKey = i18n("Image Information");
+ else if (key == "Photo") m_decryptedKey = i18n("Photograph Information");
+ else if (key == "GPSInfo") m_decryptedKey = i18n("Global Positioning System");
+ else if (key == "Thumbnail") m_decryptedKey = i18n("Embedded Thumbnail");
+
+ // Standard IPTC key descriptions.
+ else if (key == "Envelope") m_decryptedKey = i18n("IIM Envelope");
+ else if (key == "Application2") m_decryptedKey = i18n("IIM Application 2");
+
+ setOpen(true);
+ setSelected(false);
+ setSelectable(false);
+}
+
+MdKeyListViewItem::~MdKeyListViewItem()
+{
+}
+
+TQString MdKeyListViewItem::getMdKey()
+{
+ return m_decryptedKey;
+}
+
+void MdKeyListViewItem::paintCell(TQPainter* p, const TQColorGroup&,
+ int column, int, int)
+{
+ p->save();
+ TQFont fn(p->font());
+ fn.setBold(true);
+ fn.setItalic(false);
+ p->setFont(fn);
+ p->setPen(ThemeEngine::instance()->textSelColor());
+ int width = listView()->contentsWidth();
+ TQRect rect(0, 0, width, fn.weight());
+
+ if (column == 1)
+ rect.moveLeft(-width/2);
+
+ p->fillRect(rect, ThemeEngine::instance()->thumbSelColor());
+ p->drawText(rect, TQt::AlignHCenter, m_decryptedKey);
+ p->restore();
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/metadata/mdkeylistviewitem.h b/src/libs/widgets/metadata/mdkeylistviewitem.h
new file mode 100644
index 00000000..aeaf7e4c
--- /dev/null
+++ b/src/libs/widgets/metadata/mdkeylistviewitem.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-21
+ * Description : a generic list view item widget to
+ * display metadata key like a title
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MDKEYLISTVIEWITEM_H
+#define MDKEYLISTVIEWITEM_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlistview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQPainter;
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT MdKeyListViewItem : public TQListViewItem
+{
+
+public:
+
+ MdKeyListViewItem(TQListView *parent, const TQString& key);
+ ~MdKeyListViewItem();
+
+ TQString getMdKey();
+
+protected:
+
+ void paintCell(TQPainter*, const TQColorGroup &, int, int, int);
+
+private:
+
+ TQString m_decryptedKey;
+};
+
+} // namespace Digikam
+
+#endif /* MDKEYLISTVIEWITEM_H */
diff --git a/src/libs/widgets/metadata/metadatalistview.cpp b/src/libs/widgets/metadata/metadatalistview.cpp
new file mode 100644
index 00000000..612eed1e
--- /dev/null
+++ b/src/libs/widgets/metadata/metadatalistview.cpp
@@ -0,0 +1,283 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-21
+ * Description : a generic list view widget to
+ * display metadata
+ *
+ * Copyright (c) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtimer.h>
+#include <tqptrlist.h>
+#include <tqpalette.h>
+#include <tqheader.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "mdkeylistviewitem.h"
+#include "metadatalistviewitem.h"
+#include "metadatalistview.h"
+#include "metadatalistview.moc"
+
+namespace Digikam
+{
+
+MetadataListView::MetadataListView(TQWidget* parent)
+ : TQListView(parent)
+{
+ header()->hide();
+ addColumn("Name"); // No need i18n here.
+ addColumn("Value"); // No need i18n here.
+ setItemMargin(0);
+ setAllColumnsShowFocus(true);
+ setResizeMode(TQListView::AllColumns);
+ // Vertical scroll bar is always disable to give more
+ // free space to metadata content
+ setVScrollBarMode(TQScrollView::AlwaysOff);
+
+ m_parent = dynamic_cast<MetadataWidget *>(parent);
+
+ connect(this, TQ_SIGNAL(selectionChanged(TQListViewItem*)),
+ this, TQ_SLOT(slotSelectionChanged(TQListViewItem*)));
+}
+
+MetadataListView::~MetadataListView()
+{
+}
+
+TQString MetadataListView::getCurrentItemKey()
+{
+ if (currentItem())
+ {
+ if (currentItem()->isSelectable())
+ {
+ MetadataListViewItem *item = static_cast<MetadataListViewItem *>(currentItem());
+ return item->getKey();
+ }
+ }
+
+ return TQString();
+}
+
+void MetadataListView::setCurrentItemByKey(TQString itemKey)
+{
+ if (itemKey.isNull())
+ return;
+
+ TQListViewItemIterator it(this);
+ while ( it.current() )
+ {
+ if ( it.current()->isSelectable() )
+ {
+ MetadataListViewItem *item = dynamic_cast<MetadataListViewItem *>(it.current());
+
+ if (item->getKey() == itemKey)
+ {
+ setSelected(item, true);
+ ensureItemVisible(item);
+ m_selectedItemKey = itemKey;
+ return;
+ }
+ }
+
+ ++it;
+ }
+}
+
+void MetadataListView::slotSelectionChanged(TQListViewItem *item)
+{
+ if (!item)
+ return;
+
+ MetadataListViewItem* viewItem = static_cast<MetadataListViewItem *>(item);
+ m_selectedItemKey = viewItem->getKey();
+ TQString tagValue = viewItem->getValue().simplifyWhiteSpace();
+ TQString tagTitle = m_parent->getTagTitle(m_selectedItemKey);
+ TQString tagDesc = m_parent->getTagDescription(m_selectedItemKey);
+ if (tagValue.length() > 128)
+ {
+ tagValue.truncate(128);
+ tagValue.append("...");
+ }
+
+ TQWhatsThis::add(this, i18n("<b>Title: </b><p>%1<p>"
+ "<b>Value: </b><p>%2<p>"
+ "<b>Description: </b><p>%3")
+ .arg(tagTitle)
+ .arg(tagValue)
+ .arg(tagDesc));
+}
+
+void MetadataListView::setIfdList(const DMetadata::MetaDataMap& ifds, const TQStringList& tagsfilter)
+{
+ clear();
+
+ uint subItems = 0;
+ TQString ifDItemName;
+ MdKeyListViewItem *parentifDItem = 0;
+
+ for (DMetadata::MetaDataMap::const_iterator it = ifds.begin(); it != ifds.end(); ++it)
+ {
+ // We checking if we have changed of ifDName
+ TQString currentIfDName = it.key().section('.', 1, 1);
+
+ if ( currentIfDName != ifDItemName )
+ {
+ ifDItemName = currentIfDName;
+
+ // Check if the current IfD have any items. If no remove it before to toggle to the next IfD.
+ if ( subItems == 0 && parentifDItem)
+ delete parentifDItem;
+
+ parentifDItem = new MdKeyListViewItem(this, currentIfDName);
+ subItems = 0;
+ }
+
+ // We ignore all unknown tags if necessary.
+ if (!it.key().section('.', 2, 2).startsWith("0x"))
+ {
+ if (!tagsfilter.isEmpty())
+ {
+ // We using the filter to make a more user friendly output (Simple Mode)
+
+ if (tagsfilter.contains(it.key().section('.', 2, 2)))
+ {
+ TQString tagTitle = m_parent->getTagTitle(it.key());
+ new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.data());
+ subItems++;
+ }
+ }
+ else
+ {
+ // We don't filter the output (Complete Mode)
+
+ TQString tagTitle = m_parent->getTagTitle(it.key());
+ new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.data());
+ subItems++;
+ }
+ }
+ }
+
+ // To check if the last IfD have any items...
+ if ( subItems == 0 && parentifDItem)
+ delete parentifDItem;
+
+ setCurrentItemByKey(m_selectedItemKey);
+ TQTimer::singleShot( 0, this, TQ_SLOT( triggerUpdate() ) );
+}
+
+void MetadataListView::setIfdList(const DMetadata::MetaDataMap& ifds, const TQStringList& keysFilter,
+ const TQStringList& tagsFilter)
+{
+ clear();
+
+ uint subItems = 0;
+ MdKeyListViewItem *parentifDItem = 0;
+
+ for (TQStringList::const_iterator itKeysFilter = keysFilter.begin();
+ itKeysFilter != keysFilter.end();
+ ++itKeysFilter)
+ {
+ subItems = 0;
+ parentifDItem = new MdKeyListViewItem(this, *itKeysFilter);
+
+ DMetadata::MetaDataMap::const_iterator it = ifds.end();
+
+ while(1)
+ {
+ if ( *itKeysFilter == it.key().section('.', 1, 1) )
+ {
+ // We ignore all unknown tags if necessary.
+ if (!it.key().section('.', 2, 2).startsWith("0x"))
+ {
+ if (!tagsFilter.isEmpty())
+ {
+ // We using the filter to make a more user friendly output (Simple Mode)
+
+ if (tagsFilter.contains(it.key().section('.', 2, 2)))
+ {
+ TQString tagTitle = m_parent->getTagTitle(it.key());
+ new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.data());
+ subItems++;
+ }
+ }
+ else
+ {
+ // We don't filter the output (Complete Mode)
+
+ TQString tagTitle = m_parent->getTagTitle(it.key());
+ new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.data());
+ subItems++;
+ }
+ }
+ }
+
+ if (it == ifds.begin()) break;
+ --it;
+ }
+
+ // We checking if the last IfD have any items. If no, we remove it.
+ if ( subItems == 0 && parentifDItem)
+ delete parentifDItem;
+ }
+
+ setCurrentItemByKey(m_selectedItemKey);
+ TQTimer::singleShot( 0, this, TQ_SLOT( triggerUpdate() ) );
+}
+
+void MetadataListView::viewportResizeEvent(TQResizeEvent* e)
+{
+ TQListView::viewportResizeEvent(e);
+ TQTimer::singleShot( 0, this, TQ_SLOT( triggerUpdate() ) );
+}
+
+void MetadataListView::slotSearchTextChanged(const TQString& filter)
+{
+ bool query = false;
+ TQString search = filter.lower();
+
+ TQListViewItemIterator it(this);
+ for ( ; it.current(); ++it )
+ {
+ MetadataListViewItem *item = dynamic_cast<MetadataListViewItem*>(it.current());
+ if (item)
+ {
+ if (item->text(0).lower().contains(search) ||
+ item->text(1).lower().contains(search))
+ {
+ query = true;
+ item->setVisible(true);
+ }
+ else
+ {
+ item->setVisible(false);
+ }
+ }
+ }
+
+ emit signalTextFilterMatch(query);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/metadata/metadatalistview.h b/src/libs/widgets/metadata/metadatalistview.h
new file mode 100644
index 00000000..aefe6498
--- /dev/null
+++ b/src/libs/widgets/metadata/metadatalistview.h
@@ -0,0 +1,85 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-21
+ * Description : a generic list view widget to
+ * display metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef METADATALISTVIEW_H
+#define METADATALISTVIEW_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqptrlist.h>
+#include <tqmap.h>
+#include <tqlistview.h>
+
+// Local includes.
+
+#include "metadatawidget.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT MetadataListView : public TQListView
+{
+ TQ_OBJECT
+
+
+public:
+
+ MetadataListView(TQWidget* parent);
+ ~MetadataListView();
+
+ TQString getCurrentItemKey();
+ void setCurrentItemByKey(TQString itemKey);
+
+ void setIfdList(const DMetadata::MetaDataMap& ifds, const TQStringList& tagsfilter);
+ void setIfdList(const DMetadata::MetaDataMap& ifds, const TQStringList& keysFilter,
+ const TQStringList& tagsFilter);
+
+signals:
+
+ void signalTextFilterMatch(bool);
+
+public slots:
+
+ void slotSearchTextChanged(const TQString&);
+
+protected:
+
+ void viewportResizeEvent(TQResizeEvent*);
+
+private slots:
+
+ void slotSelectionChanged(TQListViewItem *item);
+
+private:
+
+ TQString m_selectedItemKey;
+
+ MetadataWidget *m_parent;
+};
+
+} // namespace Digikam
+
+#endif // METADATALISTVIEW_H
diff --git a/src/libs/widgets/metadata/metadatalistviewitem.cpp b/src/libs/widgets/metadata/metadatalistviewitem.cpp
new file mode 100644
index 00000000..f18056b5
--- /dev/null
+++ b/src/libs/widgets/metadata/metadatalistviewitem.cpp
@@ -0,0 +1,75 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-21
+ * Description : a generic list view item widget to
+ * display metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpalette.h>
+#include <tqfont.h>
+#include <tqpainter.h>
+
+// Local includes.
+
+#include "metadatalistviewitem.h"
+
+namespace Digikam
+{
+
+MetadataListViewItem::MetadataListViewItem(TQListViewItem *parent, const TQString& key,
+ const TQString& title, const TQString& value)
+ : TQListViewItem(parent)
+{
+ m_key = key;
+
+ setSelectable(true);
+ setText(0, title);
+
+ TQString tagVal = value.simplifyWhiteSpace();
+ if (tagVal.length() > 128)
+ {
+ tagVal.truncate(128);
+ tagVal.append("...");
+ }
+ setText(1, tagVal);
+}
+
+MetadataListViewItem::~MetadataListViewItem()
+{
+}
+
+TQString MetadataListViewItem::getKey()
+{
+ return m_key;
+}
+
+TQString MetadataListViewItem::getTitle()
+{
+ return text(0);
+}
+
+TQString MetadataListViewItem::getValue()
+{
+ return text(1);
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/metadata/metadatalistviewitem.h b/src/libs/widgets/metadata/metadatalistviewitem.h
new file mode 100644
index 00000000..4a860d5b
--- /dev/null
+++ b/src/libs/widgets/metadata/metadatalistviewitem.h
@@ -0,0 +1,59 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-21
+ * Description : a generic list view item widget to
+ * display metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef METADATALISTVIEWITEM_H
+#define METADATALISTVIEWITEM_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlistview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT MetadataListViewItem : public TQListViewItem
+{
+public:
+
+ MetadataListViewItem(TQListViewItem *parent, const TQString& key,
+ const TQString& title, const TQString& value);
+ ~MetadataListViewItem();
+
+ TQString getKey();
+ TQString getTitle();
+ TQString getValue();
+
+private:
+
+ TQString m_key;
+};
+
+} // namespace Digikam
+
+#endif /* METADATALISTVIEWITEM_H */
diff --git a/src/libs/widgets/metadata/metadatawidget.cpp b/src/libs/widgets/metadata/metadatawidget.cpp
new file mode 100644
index 00000000..d0bd5b19
--- /dev/null
+++ b/src/libs/widgets/metadata/metadatawidget.cpp
@@ -0,0 +1,454 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-22
+ * Description : a generic widget to display metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqmap.h>
+#include <tqfile.h>
+#include <tqmime.h>
+#include <tqheader.h>
+#include <tqwhatsthis.h>
+#include <tqpainter.h>
+#include <tqhbuttongroup.h>
+#include <tqpushbutton.h>
+#include <tqlabel.h>
+#include <tqdragobject.h>
+#include <tqclipboard.h>
+#include <tqsimplerichtext.h>
+#include <tqpaintdevicemetrics.h>
+#include <tqstylesheet.h>
+#include <tqlistview.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+#include <tdelocale.h>
+#include <tdefiledialog.h>
+#include <tdeglobalsettings.h>
+#include <kprinter.h>
+#include <tdeglobal.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "metadatalistview.h"
+#include "mdkeylistviewitem.h"
+#include "searchtextbar.h"
+#include "metadatawidget.h"
+#include "metadatawidget.moc"
+
+namespace Digikam
+{
+
+class MetadataWidgetPriv
+{
+
+public:
+
+ MetadataWidgetPriv()
+ {
+ toolButtons = 0;
+ levelButtons = 0;
+ view = 0;
+ mainLayout = 0;
+ searchBar = 0;
+ }
+
+ TQGridLayout *mainLayout;
+
+ TQHButtonGroup *toolButtons;
+ TQHButtonGroup *levelButtons;
+
+ TQByteArray metadata;
+
+ TQString fileName;
+
+ MetadataListView *view;
+
+ SearchTextBar *searchBar;
+
+ DMetadata::MetaDataMap metaDataMap;
+};
+
+MetadataWidget::MetadataWidget(TQWidget* parent, const char* name)
+ : TQWidget(parent, name)
+{
+ d = new MetadataWidgetPriv;
+
+ d->mainLayout = new TQGridLayout(this, 3, 4, KDialog::spacingHint(), KDialog::spacingHint());
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+
+ d->levelButtons = new TQHButtonGroup(this);
+ d->levelButtons->setInsideMargin( 0 );
+ d->levelButtons->setExclusive(true);
+ d->levelButtons->setFrameShape(TQFrame::NoFrame);
+
+ TQPushButton *simpleLevel = new TQPushButton( d->levelButtons );
+ simpleLevel->setPixmap( iconLoader->loadIcon( "text-vnd.tde.ascii", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ simpleLevel->setToggleButton(true);
+ TQWhatsThis::add( simpleLevel, i18n( "Switch the tags view to a simple human-readable list" ) );
+ TQToolTip::add( simpleLevel, i18n( "Simple list" ));
+ d->levelButtons->insert(simpleLevel, SIMPLE);
+
+ TQPushButton *fullLevel = new TQPushButton( d->levelButtons );
+ fullLevel->setPixmap( iconLoader->loadIcon( "text-x-generic", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ fullLevel->setToggleButton(true);
+ TQWhatsThis::add( fullLevel, i18n( "Switch the tags view to a full list" ) );
+ TQToolTip::add( fullLevel, i18n( "Full list" ));
+ d->levelButtons->insert(fullLevel, FULL);
+
+ d->toolButtons = new TQHButtonGroup(this);
+ d->toolButtons->setInsideMargin( 0 );
+ d->toolButtons->setFrameShape(TQFrame::NoFrame);
+
+ TQPushButton *saveMetadata = new TQPushButton( d->toolButtons );
+ saveMetadata->setPixmap( iconLoader->loadIcon( "document-save", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ TQWhatsThis::add( saveMetadata, i18n( "Save metadata to a binary file" ) );
+ TQToolTip::add( saveMetadata, i18n( "Save metadata" ));
+ d->toolButtons->insert(saveMetadata);
+
+ TQPushButton *printMetadata = new TQPushButton( d->toolButtons );
+ printMetadata->setPixmap( iconLoader->loadIcon( "document-print", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ TQWhatsThis::add( printMetadata, i18n( "Print metadata to printer" ) );
+ TQToolTip::add( printMetadata, i18n( "Print metadata" ));
+ d->toolButtons->insert(printMetadata);
+
+ TQPushButton *copy2ClipBoard = new TQPushButton( d->toolButtons );
+ copy2ClipBoard->setPixmap( iconLoader->loadIcon( "edit-copy", (TDEIcon::Group)TDEIcon::Toolbar ) );
+ TQWhatsThis::add( copy2ClipBoard, i18n( "Copy metadata to clipboard" ) );
+ TQToolTip::add( copy2ClipBoard, i18n( "Copy metadata to clipboard" ));
+ d->toolButtons->insert(copy2ClipBoard);
+
+ d->view = new MetadataListView(this);
+ TQString barName = TQString(name) + "SearchBar";
+ d->searchBar = new SearchTextBar(this, barName.ascii());
+
+ // -----------------------------------------------------------------
+
+ d->mainLayout->addMultiCellWidget(d->levelButtons, 0, 0, 0, 1);
+ d->mainLayout->addMultiCellWidget(d->toolButtons, 0, 0, 4, 4);
+ d->mainLayout->addMultiCellWidget(d->view, 1, 1, 0, 4);
+ d->mainLayout->addMultiCellWidget(d->searchBar, 2, 2, 0, 4);
+ d->mainLayout->setRowStretch(1, 10);
+ d->mainLayout->setColStretch(3, 10);
+
+ // -----------------------------------------------------------------
+
+ connect(d->levelButtons, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotModeChanged(int)));
+
+ connect(copy2ClipBoard, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotCopy2Clipboard()));
+
+ connect(printMetadata, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotPrintMetadata()));
+
+ connect(saveMetadata, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotSaveMetadataToFile()));
+
+ connect(d->searchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ d->view, TQ_SLOT(slotSearchTextChanged(const TQString&)));
+
+ connect(d->view, TQ_SIGNAL(signalTextFilterMatch(bool)),
+ d->searchBar, TQ_SLOT(slotSearchResult(bool)));
+}
+
+MetadataWidget::~MetadataWidget()
+{
+ delete d;
+}
+
+MetadataListView* MetadataWidget::view()
+{
+ return d->view;
+}
+
+void MetadataWidget::enabledToolButtons(bool b)
+{
+ d->toolButtons->setEnabled(b);
+}
+
+bool MetadataWidget::setMetadata(const TQByteArray& data)
+{
+ d->metadata = data;
+
+ // Cleanup all metadata contents.
+ setMetadataMap();
+
+ if (d->metadata.isEmpty())
+ {
+ setMetadataEmpty();
+ return false;
+ }
+
+ // Try to decode current metadata.
+ if (decodeMetadata())
+ enabledToolButtons(true);
+ else
+ enabledToolButtons(false);
+
+ // Refresh view using decoded metadata.
+ buildView();
+
+ return true;
+}
+
+void MetadataWidget::setMetadataEmpty()
+{
+ d->view->clear();
+ enabledToolButtons(false);
+}
+
+const TQByteArray& MetadataWidget::getMetadata()
+{
+ return d->metadata;
+}
+
+bool MetadataWidget::storeMetadataToFile(const KURL& url)
+{
+ if( url.isEmpty() )
+ return false;
+
+ TQFile file(url.path());
+ if ( !file.open(IO_WriteOnly) )
+ return false;
+
+ TQDataStream stream( &file );
+ stream.writeRawBytes(d->metadata.data(), d->metadata.size());
+ file.close();
+ return true;
+}
+
+void MetadataWidget::setMetadataMap(const DMetadata::MetaDataMap& data)
+{
+ d->metaDataMap = data;
+}
+
+const DMetadata::MetaDataMap& MetadataWidget::getMetadataMap()
+{
+ return d->metaDataMap;
+}
+
+void MetadataWidget::setIfdList(const DMetadata::MetaDataMap &ifds, const TQStringList& tagsFilter)
+{
+ d->view->setIfdList(ifds, tagsFilter);
+}
+
+void MetadataWidget::setIfdList(const DMetadata::MetaDataMap &ifds, const TQStringList& keysFilter,
+ const TQStringList& tagsFilter)
+{
+ d->view->setIfdList(ifds, keysFilter, tagsFilter);
+}
+
+void MetadataWidget::slotModeChanged(int)
+{
+ buildView();
+}
+
+void MetadataWidget::slotCopy2Clipboard()
+{
+ TQString textmetadata = i18n("File name: %1 (%2)").arg(d->fileName).arg(getMetadataTitle());
+ TQListViewItemIterator it( d->view );
+
+ while ( it.current() )
+ {
+ if ( !it.current()->isSelectable() )
+ {
+ MdKeyListViewItem *item = dynamic_cast<MdKeyListViewItem *>(it.current());
+ textmetadata.append("\n\n>>> ");
+ textmetadata.append(item->getMdKey());
+ textmetadata.append(" <<<\n\n");
+ }
+ else
+ {
+ TQListViewItem *item = it.current();
+ textmetadata.append(item->text(0));
+ textmetadata.append(" : ");
+ textmetadata.append(item->text(1));
+ textmetadata.append("\n");
+ }
+
+ ++it;
+ }
+
+ TQApplication::clipboard()->setData(new TQTextDrag(textmetadata), TQClipboard::Clipboard);
+}
+
+void MetadataWidget::slotPrintMetadata()
+{
+ TQString textmetadata = i18n("<p><big><big><b>File name: %1 (%2)</b></big></big>")
+ .arg(d->fileName)
+ .arg(getMetadataTitle());
+ TQListViewItemIterator it( d->view );
+
+ while ( it.current() )
+ {
+ if ( !it.current()->isSelectable() )
+ {
+ MdKeyListViewItem *item = dynamic_cast<MdKeyListViewItem *>(it.current());
+ textmetadata.append("<br><br><b>");
+ textmetadata.append(item->getMdKey());
+ textmetadata.append("</b><br><br>");
+ }
+ else
+ {
+ TQListViewItem *item = it.current();
+ textmetadata.append(item->text(0));
+ textmetadata.append(" : <i>");
+ textmetadata.append(item->text(1));
+ textmetadata.append("</i><br>");
+ }
+
+ ++it;
+ }
+
+ textmetadata.append("</p>");
+
+ KPrinter printer;
+ printer.setFullPage( true );
+
+ if ( printer.setup( this ) )
+ {
+ TQPainter p( &printer );
+
+ if ( !p.device() )
+ return;
+
+ TQPaintDeviceMetrics metrics(p.device());
+ int dpiy = metrics.logicalDpiY();
+ int margin = (int) ( (2/2.54)*dpiy ); // 2 cm margins
+ TQRect view( margin, margin, metrics.width() - 2*margin, metrics.height() - 2*margin );
+ TQFont font(TDEApplication::font());
+ font.setPointSize( 10 ); // we define 10pt to be a nice base size for printing
+ TQSimpleRichText richText( textmetadata, font,
+ TQString(),
+ TQStyleSheet::defaultSheet(),
+ TQMimeSourceFactory::defaultFactory(),
+ view.height() );
+ richText.setWidth( &p, view.width() );
+ int page = 1;
+
+ do
+ {
+ richText.draw( &p, margin, margin, view, colorGroup() );
+ view.moveBy( 0, view.height() );
+ p.translate( 0 , -view.height() );
+ p.setFont( font );
+ p.drawText( view.right() - p.fontMetrics().width( TQString::number( page ) ),
+ view.bottom() + p.fontMetrics().ascent() + 5, TQString::number( page ) );
+
+ if ( view.top() - margin >= richText.height() )
+ break;
+
+ printer.newPage();
+ page++;
+ }
+ while (true);
+ }
+}
+
+KURL MetadataWidget::saveMetadataToFile(const TQString& caption, const TQString& fileFilter)
+{
+ KFileDialog fileSaveDialog(TDEGlobalSettings::documentPath(),
+ TQString(),
+ this,
+ "MetadataFileSaveDialog",
+ false);
+
+ fileSaveDialog.setOperationMode(KFileDialog::Saving);
+ fileSaveDialog.setMode(KFile::File);
+ fileSaveDialog.setSelection(d->fileName);
+ fileSaveDialog.setCaption(caption);
+ fileSaveDialog.setFilter(fileFilter);
+
+ // Check for cancel.
+ if ( fileSaveDialog.exec() == KFileDialog::Accepted )
+ return fileSaveDialog.selectedURL().path();
+
+ return KURL();
+}
+
+void MetadataWidget::setMode(int mode)
+{
+ if (d->levelButtons->selectedId() == mode)
+ return;
+
+ d->levelButtons->setButton(mode);
+ buildView();
+}
+
+int MetadataWidget::getMode()
+{
+ int level = d->levelButtons->selectedId();
+ return level;
+}
+
+TQString MetadataWidget::getCurrentItemKey() const
+{
+ return d->view->getCurrentItemKey();
+}
+
+void MetadataWidget::setCurrentItemByKey(const TQString& itemKey)
+{
+ d->view->setCurrentItemByKey(itemKey);
+}
+
+bool MetadataWidget::loadFromData(const TQString& fileName, const TQByteArray& data)
+{
+ setFileName(fileName);
+ return(setMetadata(data));
+}
+
+TQString MetadataWidget::getTagTitle(const TQString&)
+{
+ return TQString();
+}
+
+TQString MetadataWidget::getTagDescription(const TQString&)
+{
+ return TQString();
+}
+
+void MetadataWidget::setFileName(const TQString& fileName)
+{
+ d->fileName = fileName;
+}
+
+void MetadataWidget::setUserAreaWidget(TQWidget *w)
+{
+ TQVBoxLayout *vLayout = new TQVBoxLayout( KDialog::spacingHint() );
+ vLayout->addWidget(w);
+ vLayout->addStretch();
+ d->mainLayout->addMultiCellLayout(vLayout, 3, 3, 0, 4);
+}
+
+void MetadataWidget::buildView()
+{
+ d->view->slotSearchTextChanged(d->searchBar->text());
+}
+
+} // namespace Digikam
diff --git a/src/libs/widgets/metadata/metadatawidget.h b/src/libs/widgets/metadata/metadatawidget.h
new file mode 100644
index 00000000..5d2feb64
--- /dev/null
+++ b/src/libs/widgets/metadata/metadatawidget.h
@@ -0,0 +1,120 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-22
+ * Description : a generic widget to display metadata
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef METADATAWIDGET_H
+#define METADATAWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "dmetadata.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class MetadataListView;
+class MetadataWidgetPriv;
+
+class DIGIKAM_EXPORT MetadataWidget : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum Mode
+ {
+ SIMPLE=0,
+ FULL
+ };
+
+public:
+
+ MetadataWidget(TQWidget* parent, const char* name=0);
+ ~MetadataWidget();
+
+ int getMode();
+ void setMode(int mode);
+
+ TQString getCurrentItemKey() const;
+ void setCurrentItemByKey(const TQString& itemKey);
+
+ void setUserAreaWidget(TQWidget *w);
+
+ virtual TQString getTagTitle(const TQString& key);
+ virtual TQString getTagDescription(const TQString& key);
+
+ virtual bool loadFromData(const TQString &fileName, const TQByteArray& data=TQByteArray());
+ virtual bool loadFromURL(const KURL& url)=0;
+
+private slots:
+
+ void slotModeChanged(int);
+ void slotCopy2Clipboard();
+ void slotPrintMetadata();
+
+protected slots:
+
+ virtual void slotSaveMetadataToFile()=0;
+
+protected:
+
+ void enabledToolButtons(bool);
+ void setFileName(const TQString& fileName);
+ MetadataListView* view();
+
+ bool setMetadata(const TQByteArray& data=TQByteArray());
+ const TQByteArray& getMetadata();
+
+ void setMetadataMap(const DMetadata::MetaDataMap& data=DMetadata::MetaDataMap());
+ const DMetadata::MetaDataMap& getMetadataMap();
+
+ void setIfdList(const DMetadata::MetaDataMap &ifds, const TQStringList& tagsFilter=TQStringList());
+ void setIfdList(const DMetadata::MetaDataMap &ifds, const TQStringList& keysFilter,
+ const TQStringList& tagsFilter);
+
+ KURL saveMetadataToFile(const TQString& caption, const TQString& fileFilter);
+ bool storeMetadataToFile(const KURL& url);
+
+ virtual void buildView();
+ virtual bool decodeMetadata()=0;
+ virtual TQString getMetadataTitle()=0;
+ virtual void setMetadataEmpty();
+
+private:
+
+ MetadataWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* METADATAWIDGET_H */
diff --git a/src/libs/widgets/metadata/worldmapwidget.cpp b/src/libs/widgets/metadata/worldmapwidget.cpp
new file mode 100644
index 00000000..811e9148
--- /dev/null
+++ b/src/libs/widgets/metadata/worldmapwidget.cpp
@@ -0,0 +1,211 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : a widget to display GPS info on a world map
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqstring.h>
+#include <tqpixmap.h>
+#include <tqlabel.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <kstaticdeleter.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "worldmapwidget.h"
+#include "worldmapwidget.moc"
+
+namespace Digikam
+{
+
+class WorldMapWidgetPriv
+{
+
+public:
+
+ WorldMapWidgetPriv()
+ {
+ latitude = 0;
+ longitude = 0;
+ latLonPos = 0;
+ }
+
+ int xPos;
+ int yPos;
+ int xMousePos;
+ int yMousePos;
+
+ double latitude;
+ double longitude;
+
+ TQLabel *latLonPos;
+
+ static TQPixmap *worldMap;
+};
+
+static KStaticDeleter<TQPixmap> pixmapDeleter;
+
+TQPixmap *WorldMapWidgetPriv::worldMap = 0;
+
+WorldMapWidget::WorldMapWidget(int w, int h, TQWidget *parent)
+ : TQScrollView(parent, 0, TQt::WDestructiveClose)
+{
+ d = new WorldMapWidgetPriv;
+
+ setVScrollBarMode(TQScrollView::AlwaysOff);
+ setHScrollBarMode(TQScrollView::AlwaysOff);
+ viewport()->setMouseTracking(true);
+ viewport()->setPaletteBackgroundColor(colorGroup().background());
+ setMinimumWidth(w);
+ setMaximumHeight(h);
+ resizeContents(worldMapPixmap().width(), worldMapPixmap().height());
+
+ d->latLonPos = new TQLabel(viewport());
+ d->latLonPos->setMaximumHeight(fontMetrics().height());
+ d->latLonPos->setAlignment(TQt::AlignHCenter | TQt::AlignVCenter);
+ d->latLonPos->setFrameStyle(TQFrame::Panel | TQFrame::Sunken);
+ addChild(d->latLonPos);
+}
+
+WorldMapWidget::~WorldMapWidget()
+{
+ delete d;
+}
+
+TQPixmap &WorldMapWidget::worldMapPixmap()
+{
+ if (!d->worldMap)
+ {
+ TDEGlobal::dirs()->addResourceType("worldmap", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("worldmap", "worldmap.jpg");
+ pixmapDeleter.setObject(d->worldMap, new TQPixmap(directory + "worldmap.jpg"));
+ }
+ return *d->worldMap;
+}
+
+double WorldMapWidget::getLatitude()
+{
+ return d->latitude;
+}
+
+double WorldMapWidget::getLongitude()
+{
+ return d->longitude;
+}
+
+void WorldMapWidget::setEnabled(bool b)
+{
+ if (!b)
+ d->latLonPos->hide();
+ else
+ d->latLonPos->show();
+
+ TQScrollView::setEnabled(b);
+}
+
+void WorldMapWidget::setGPSPosition(double lat, double lng)
+{
+ d->latitude = lat;
+ d->longitude = lng;
+
+ double latMid = contentsHeight() / 2.0;
+ double longMid = contentsWidth() / 2.0;
+
+ double latOffset = ( d->latitude * latMid ) / 90.0;
+ double longOffset = ( d->longitude * longMid ) / 180.0;
+
+ d->xPos = (int)(longMid + longOffset);
+ d->yPos = (int)(latMid - latOffset);
+
+ repaintContents(false);
+ center(d->xPos, d->yPos);
+
+ TQString la, lo;
+ d->latLonPos->setText(TQString("(%1, %2)").arg(la.setNum(d->latitude, 'f', 2))
+ .arg(lo.setNum(d->longitude, 'f', 2)));
+
+ moveChild(d->latLonPos, contentsX()+10, contentsY()+10);
+}
+
+void WorldMapWidget::drawContents(TQPainter *p, int x, int y, int w, int h)
+{
+ if (isEnabled())
+ {
+ p->drawPixmap(x, y, worldMapPixmap(), x, y, w, h);
+ p->setPen(TQPen(TQt::white, 0, TQt::SolidLine));
+ p->drawLine(d->xPos, 0, d->xPos, contentsHeight());
+ p->drawLine(0, d->yPos, contentsWidth(), d->yPos);
+ p->setPen(TQPen(TQt::red, 0, TQt::DotLine));
+ p->drawLine(d->xPos, 0, d->xPos, contentsHeight());
+ p->drawLine(0, d->yPos, contentsWidth(), d->yPos);
+ p->setPen( TQt::red );
+ p->setBrush( TQt::red );
+ p->drawEllipse( d->xPos-2, d->yPos-2, 4, 4 );
+ }
+ else
+ {
+ p->fillRect(x, y, w, h, palette().disabled().background());
+ }
+}
+
+void WorldMapWidget::contentsMousePressEvent ( TQMouseEvent * e )
+{
+ if ( e->button() == TQt::LeftButton )
+ {
+ d->xMousePos = e->x();
+ d->yMousePos = e->y();
+ setCursor( KCursor::sizeAllCursor() );
+ }
+}
+
+void WorldMapWidget::contentsMouseReleaseEvent ( TQMouseEvent * )
+{
+ unsetCursor();
+}
+
+void WorldMapWidget::contentsMouseMoveEvent( TQMouseEvent * e )
+{
+ if ( e->state() == TQt::LeftButton )
+ {
+ uint newxpos = e->x();
+ uint newypos = e->y();
+
+ scrollBy (-(newxpos - d->xMousePos), -(newypos - d->yMousePos));
+ repaintContents(false);
+
+ d->xMousePos = newxpos - (newxpos-d->xMousePos);
+ d->yMousePos = newypos - (newypos-d->yMousePos);
+ return;
+ }
+
+ setCursor( KCursor::handCursor() );
+}
+
+} // namespace Digikam
+
diff --git a/src/libs/widgets/metadata/worldmapwidget.h b/src/libs/widgets/metadata/worldmapwidget.h
new file mode 100644
index 00000000..bfb8ad8f
--- /dev/null
+++ b/src/libs/widgets/metadata/worldmapwidget.h
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-02-20
+ * Description : a widget to display GPS info on a world map
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef WORLDMAPWIDGET_H
+#define WORLDMAPWIDGET_H
+
+// TQt includes.
+
+#include <tqscrollview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class WorldMapWidgetPriv;
+
+class DIGIKAM_EXPORT WorldMapWidget : public TQScrollView
+{
+TQ_OBJECT
+
+
+public:
+
+ WorldMapWidget(int w, int h, TQWidget *parent);
+ ~WorldMapWidget();
+
+ void setGPSPosition(double lat, double lng);
+
+ double getLatitude();
+ double getLongitude();
+ void setEnabled(bool);
+
+private:
+
+ void drawContents(TQPainter *p, int x, int y, int w, int h);
+ void contentsMousePressEvent ( TQMouseEvent * e );
+ void contentsMouseReleaseEvent ( TQMouseEvent * e );
+ void contentsMouseMoveEvent( TQMouseEvent * e );
+
+ TQPixmap &worldMapPixmap();
+
+private:
+
+ WorldMapWidgetPriv *d;
+
+};
+
+} // namespace Digikam
+
+#endif /* WORLDMAPWIDGET_H */
diff --git a/src/showfoto/Makefile.am b/src/showfoto/Makefile.am
new file mode 100644
index 00000000..9f5f5064
--- /dev/null
+++ b/src/showfoto/Makefile.am
@@ -0,0 +1,42 @@
+SUBDIRS = setup
+
+METASOURCES = AUTO
+
+bin_PROGRAMS = showfoto
+
+INCLUDES = -I$(top_srcdir)/src/showfoto/setup \
+ -I$(top_srcdir)/src/utilities/setup \
+ -I$(top_srcdir)/src/libs/thumbbar \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/imageproperties \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/utilities/slideshow \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/tools \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) $(all_includes)
+
+showfoto_SOURCES = main.cpp showfoto.cpp
+
+showfoto_LDADD = $(top_builddir)/src/showfoto/setup/libsetup.la \
+ $(top_builddir)/src/libs/imageproperties/libimagepropertiesshowfoto.la \
+ $(top_builddir)/src/libs/thumbbar/libthumbbar.la \
+ $(top_builddir)/src/utilities/imageeditor/editor/libshowfoto.la \
+ $(top_builddir)/src/utilities/slideshow/libslideshow.la \
+ $(top_builddir)/src/libs/threadimageio/libthreadimageio.la \
+ $(top_builddir)/src/libs/themeengine/libthemeengine.la \
+ $(top_builddir)/src/libs/jpegutils/libjpegutils.la \
+ $(LIB_TDEUTILS) $(LIB_TDEPARTS) $(LIBJPEG)
+
+showfoto_LDFLAGS = $(LIBKEXIV2_LIBS) $(LIBKDCRAW_LIBS) $(KDE_RPATH) $(all_libraries)
+
+rcdir = $(kde_datadir)/showfoto
+rc_DATA = showfotoui.rc
+
+xdg_apps_DATA = showfoto.desktop
diff --git a/src/showfoto/main.cpp b/src/showfoto/main.cpp
new file mode 100644
index 00000000..0be2a542
--- /dev/null
+++ b/src/showfoto/main.cpp
@@ -0,0 +1,95 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-22
+ * Description : showfoto is a stand alone version of image
+ * editor with no support of digiKam database.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdecmdlineargs.h>
+#include <tdeaboutdata.h>
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeglobal.h>
+#include <kimageio.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "showfoto.h"
+
+static TDECmdLineOptions options[] =
+{
+ { "+[file(s) or folder(s)]", I18N_NOOP("File(s) or folder(s) to open"), 0 },
+ TDECmdLineLastOption
+};
+
+int main(int argc, char *argv[])
+{
+ TQString libInfo = Digikam::libraryInfo();
+
+ TQString Description = Digikam::showFotoDescription();
+
+ TDEAboutData aboutData( "showfoto",
+ I18N_NOOP("showFoto"),
+ showfoto_version,
+ Description.latin1(),
+ TDEAboutData::License_GPL,
+ Digikam::copyright(),
+ 0,
+ Digikam::webProjectUrl());
+
+ aboutData.setOtherText(libInfo.latin1());
+
+ Digikam::authorsRegistration(aboutData);
+
+ TDECmdLineArgs::init( argc, argv, &aboutData );
+ TDECmdLineArgs::addCmdLineOptions( options );
+
+ TDEApplication app;
+ KImageIO::registerFormats();
+
+ KURL::List urlList;
+ TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();
+ for(int i = 0; i < args->count(); i++)
+ {
+ urlList.append(args->url(i));
+ }
+ args->clear();
+
+ ShowFoto::ShowFoto *w = new ShowFoto::ShowFoto(urlList);
+ app.setMainWidget(w);
+ w->show();
+
+ TDEGlobal::locale()->setMainCatalogue("digikam");
+ TDEGlobal::locale()->insertCatalogue("libkdcraw");
+
+ int ret = app.exec();
+
+ delete w;
+ return ret;
+}
diff --git a/src/showfoto/setup/Makefile.am b/src/showfoto/setup/Makefile.am
new file mode 100644
index 00000000..d2767031
--- /dev/null
+++ b/src/showfoto/setup/Makefile.am
@@ -0,0 +1,14 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/utilities/setup \
+ -I$(top_srcdir)/src/digikam \
+ $(all_includes)
+
+noinst_LTLIBRARIES = libsetup.la
+
+libsetup_la_SOURCES = setup.cpp setupeditor.cpp setuptooltip.cpp
+
+libsetup_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+libsetup_la_LIBADD = $(top_builddir)/src/utilities/setup/libshowfotosetup.la
+
diff --git a/src/showfoto/setup/setup.cpp b/src/showfoto/setup/setup.cpp
new file mode 100644
index 00000000..85ec82d2
--- /dev/null
+++ b/src/showfoto/setup/setup.cpp
@@ -0,0 +1,153 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-02
+ * Description : showfoto setup dialog.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtabwidget.h>
+#include <tqapplication.h>
+#include <tqframe.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "setuptooltip.h"
+#include "setupeditor.h"
+#include "setupdcraw.h"
+#include "setupiofiles.h"
+#include "setupslideshow.h"
+#include "setupicc.h"
+#include "setup.h"
+#include "setup.moc"
+
+namespace ShowFoto
+{
+
+class SetupPrivate
+{
+public:
+
+ SetupPrivate()
+ {
+ editorPage = 0;
+ toolTipPage = 0;
+ dcrawPage = 0;
+ iofilesPage = 0;
+ slideshowPage = 0;
+ iccPage = 0;
+ page_editor = 0;
+ page_toolTip = 0;
+ page_dcraw = 0;
+ page_iofiles = 0;
+ page_slideshow = 0;
+ page_icc = 0;
+ }
+
+ TQFrame *page_editor;
+ TQFrame *page_toolTip;
+ TQFrame *page_dcraw;
+ TQFrame *page_iofiles;
+ TQFrame *page_slideshow;
+ TQFrame *page_icc;
+
+ SetupEditor *editorPage;
+ SetupToolTip *toolTipPage;
+
+ Digikam::SetupDcraw *dcrawPage;
+ Digikam::SetupIOFiles *iofilesPage;
+ Digikam::SetupSlideShow *slideshowPage;
+ Digikam::SetupICC *iccPage;
+};
+
+Setup::Setup(TQWidget* parent, const char* name, Setup::Page page)
+ : KDialogBase(IconList, i18n("Configure"), Help|Ok|Cancel, Ok, parent,
+ name, true, true )
+{
+ d = new SetupPrivate;
+ setHelp("setupdialog.anchor", "showfoto");
+
+ d->page_editor = addPage(i18n("General"), i18n("General Settings"),
+ BarIcon("showfoto", TDEIcon::SizeMedium));
+ d->editorPage = new SetupEditor(d->page_editor);
+
+ d->page_toolTip = addPage(i18n("Tool Tip"), i18n("Thumbbar Items Tool Tip Settings"),
+ BarIcon("filetypes", TDEIcon::SizeMedium));
+ d->toolTipPage = new SetupToolTip(d->page_toolTip);
+
+ d->page_dcraw = addPage(i18n("RAW decoding"), i18n("RAW Files Decoding Settings"),
+ BarIcon("kdcraw", TDEIcon::SizeMedium));
+ d->dcrawPage = new Digikam::SetupDcraw(d->page_dcraw);
+
+ d->page_icc = addPage(i18n("Color Management"), i18n("Color Management Settings"),
+ BarIcon("colorize", TDEIcon::SizeMedium));
+ d->iccPage = new Digikam::SetupICC(d->page_icc, this);
+
+ d->page_iofiles = addPage(i18n("Save Images"), i18n("Save Images' Files' Settings"),
+ BarIcon("document-save", TDEIcon::SizeMedium));
+ d->iofilesPage = new Digikam::SetupIOFiles(d->page_iofiles);
+
+ d->page_slideshow = addPage(i18n("Slide Show"), i18n("Slide Show Settings"),
+ BarIcon("slideshow", TDEIcon::SizeMedium));
+ d->slideshowPage = new Digikam::SetupSlideShow(d->page_slideshow);
+
+ connect(this, TQ_SIGNAL(okClicked()),
+ this, TQ_SLOT(slotOkClicked()) );
+
+ if (page != LastPageUsed)
+ showPage((int) page);
+ else
+ {
+ TDEConfig* config = kapp->config();
+ config->setGroup("General Settings");
+ showPage(config->readNumEntry("Setup Page", EditorPage));
+ }
+
+ show();
+}
+
+Setup::~Setup()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("General Settings");
+ config->writeEntry("Setup Page", activePageIndex());
+ config->sync();
+ delete d;
+}
+
+void Setup::slotOkClicked()
+{
+ d->editorPage->applySettings();
+ d->toolTipPage->applySettings();
+ d->dcrawPage->applySettings();
+ d->iofilesPage->applySettings();
+ d->slideshowPage->applySettings();
+ d->iccPage->applySettings();
+ close();
+}
+
+} // namespace ShowFoto
diff --git a/src/showfoto/setup/setup.h b/src/showfoto/setup/setup.h
new file mode 100644
index 00000000..8f97acbd
--- /dev/null
+++ b/src/showfoto/setup/setup.h
@@ -0,0 +1,78 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-02
+ * Description : showfoto setup dialog.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUP_H
+#define SETUP_H
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+class TQFrame;
+
+namespace Digikam
+{
+class SetupIOFiles;
+class SetupSlideShow;
+class SetupICC;
+}
+
+namespace ShowFoto
+{
+
+class SetupEditor;
+class SetupPrivate;
+
+class Setup : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum Page
+ {
+ LastPageUsed = -1,
+ EditorPage=0,
+ ToolTipPage,
+ DcrawPage,
+ IOFilesPage,
+ SlideshowPage,
+ ICCPage
+ };
+
+ Setup(TQWidget* parent=0, const char* name=0, Page page=LastPageUsed);
+ ~Setup();
+
+private slots:
+
+ void slotOkClicked();
+
+private:
+
+ SetupPrivate* d;
+};
+
+} // namespace ShowFoto
+
+#endif /* SETUP_H */
diff --git a/src/showfoto/setup/setupeditor.cpp b/src/showfoto/setup/setupeditor.cpp
new file mode 100644
index 00000000..19bab8fb
--- /dev/null
+++ b/src/showfoto/setup/setupeditor.cpp
@@ -0,0 +1,246 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-02
+ * Description : setup showfoto tab.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqcolor.h>
+#include <tqhbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <kcolorbutton.h>
+#include <knuminput.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "setupeditor.h"
+#include "setupeditor.moc"
+
+namespace ShowFoto
+{
+
+class SetupEditorPriv
+{
+public:
+
+ SetupEditorPriv()
+ {
+ backgroundColor = 0;
+ hideToolBar = 0;
+ hideThumbBar = 0;
+ horizontalThumbBar = 0;
+ showSplash = 0;
+ useTrash = 0;
+ exifRotateBox = 0;
+ exifSetOrientationBox = 0;
+ overExposureColor = 0;
+ underExposureColor = 0;
+ themebackgroundColor = 0;
+ colorBox = 0;
+ sortOrderComboBox = 0;
+ sortReverse = 0;
+ useRawImportTool = 0;
+ }
+
+ TQHBox *colorBox;
+
+ TQCheckBox *sortReverse;
+ TQCheckBox *hideToolBar;
+ TQCheckBox *hideThumbBar;
+ TQCheckBox *horizontalThumbBar;
+ TQCheckBox *showSplash;
+ TQCheckBox *useTrash;
+ TQCheckBox *exifRotateBox;
+ TQCheckBox *exifSetOrientationBox;
+ TQCheckBox *themebackgroundColor;
+ TQCheckBox *useRawImportTool;
+
+ TQComboBox *sortOrderComboBox;
+
+ KColorButton *backgroundColor;
+ KColorButton *underExposureColor;
+ KColorButton *overExposureColor;
+};
+
+SetupEditor::SetupEditor(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupEditorPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint() );
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *interfaceOptionsGroup = new TQVGroupBox(i18n("Interface Options"), parent);
+
+ d->themebackgroundColor = new TQCheckBox(i18n("&Use theme background color"),
+ interfaceOptionsGroup);
+
+ TQWhatsThis::add( d->themebackgroundColor, i18n("<p>Enable this option to use background theme "
+ "color in image editor area") );
+
+ d->colorBox = new TQHBox(interfaceOptionsGroup);
+ TQLabel *backgroundColorlabel = new TQLabel( i18n("&Background color:"), d->colorBox);
+ d->backgroundColor = new KColorButton(d->colorBox);
+ backgroundColorlabel->setBuddy(d->backgroundColor);
+ TQWhatsThis::add( d->backgroundColor, i18n("<p>Select background color to use "
+ "for image editor area.") );
+
+ d->hideToolBar = new TQCheckBox(i18n("H&ide toolbar in fullscreen mode"), interfaceOptionsGroup);
+ d->hideThumbBar = new TQCheckBox(i18n("Hide &thumbbar in fullscreen mode"), interfaceOptionsGroup);
+ d->horizontalThumbBar = new TQCheckBox(i18n("Use &horizontal thumbbar (need to restart showFoto)"), interfaceOptionsGroup);
+ TQWhatsThis::add( d->horizontalThumbBar, i18n("<p>If this option is enabled, the thumbnails bar will be displayed horizontally behind "
+ "the image area. You need to restart showFoto for this option take effect.<p>"));
+ d->useTrash = new TQCheckBox(i18n("&Deleting items should move them to trash"), interfaceOptionsGroup);
+ d->showSplash = new TQCheckBox(i18n("&Show splash screen at startup"), interfaceOptionsGroup);
+
+ d->useRawImportTool = new TQCheckBox(i18n("Use Raw Import Tool to handle Raw image"), interfaceOptionsGroup);
+ TQWhatsThis::add(d->useRawImportTool, i18n("<p>Set on this option to use Raw Import "
+ "tool before to load a Raw image, "
+ "to customize indeep decoding settings."));
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *exposureOptionsGroup = new TQVGroupBox(i18n("Exposure Indicators"), parent);
+
+ TQHBox *underExpoBox = new TQHBox(exposureOptionsGroup);
+ TQLabel *underExpoColorlabel = new TQLabel( i18n("&Under-exposure color:"), underExpoBox);
+ d->underExposureColor = new KColorButton(underExpoBox);
+ underExpoColorlabel->setBuddy(d->underExposureColor);
+ TQWhatsThis::add( d->underExposureColor, i18n("<p>Customize color used in image editor to identify "
+ "under-exposed pixels.") );
+
+ TQHBox *overExpoBox = new TQHBox(exposureOptionsGroup);
+ TQLabel *overExpoColorlabel = new TQLabel( i18n("&Over-exposure color:"), overExpoBox);
+ d->overExposureColor = new KColorButton(overExpoBox);
+ overExpoColorlabel->setBuddy(d->overExposureColor);
+ TQWhatsThis::add( d->overExposureColor, i18n("<p>Customize color used in image editor to identify "
+ "over-exposed pixels.") );
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *ExifGroupOptions = new TQVGroupBox(i18n("EXIF Actions"), parent);
+
+ d->exifRotateBox = new TQCheckBox(ExifGroupOptions);
+ d->exifRotateBox->setText(i18n("Show images/thumbs &rotated according to orientation tag"));
+
+ d->exifSetOrientationBox = new TQCheckBox(ExifGroupOptions);
+ d->exifSetOrientationBox->setText(i18n("Set orientation tag to normal after rotate/flip"));
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *sortOptionsGroup = new TQVGroupBox(i18n("Sort order for images"), parent);
+
+ TQHBox* sortBox = new TQHBox(sortOptionsGroup);
+ new TQLabel(i18n("Sort images by:"), sortBox);
+ d->sortOrderComboBox = new TQComboBox(false, sortBox);
+ d->sortOrderComboBox->insertItem(i18n("File Date"), 0);
+ d->sortOrderComboBox->insertItem(i18n("File Name"), 1);
+ d->sortOrderComboBox->insertItem(i18n("File size"), 2);
+ TQWhatsThis::add(d->sortOrderComboBox, i18n("<p>Here, select whether newly-loaded "
+ "images are sorted by file-date, file-name, or file-size."));
+
+ d->sortReverse = new TQCheckBox(i18n("Reverse ordering"), sortOptionsGroup);
+ TQWhatsThis::add(d->sortReverse, i18n("<p>If this option is enabled, newly-loaded "
+ "images will be sorted in descending order."));
+
+ // --------------------------------------------------------
+
+ layout->addWidget(interfaceOptionsGroup);
+ layout->addWidget(exposureOptionsGroup);
+ layout->addWidget(ExifGroupOptions);
+ layout->addWidget(sortOptionsGroup);
+ layout->addStretch();
+
+ // --------------------------------------------------------
+
+ connect(d->themebackgroundColor, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotThemeBackgroundColor(bool)));
+
+ readSettings();
+}
+
+SetupEditor::~SetupEditor()
+{
+ delete d;
+}
+
+void SetupEditor::slotThemeBackgroundColor(bool e)
+{
+ d->colorBox->setEnabled(!e);
+}
+
+void SetupEditor::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ TQColor Black(TQt::black);
+ TQColor White(TQt::white);
+ config->setGroup("ImageViewer Settings");
+ d->themebackgroundColor->setChecked(config->readBoolEntry("UseThemeBackgroundColor", true));
+ d->backgroundColor->setColor( config->readColorEntry("BackgroundColor", &Black ) );
+ d->hideToolBar->setChecked(config->readBoolEntry("FullScreen Hide ToolBar", false));
+ d->hideThumbBar->setChecked(config->readBoolEntry("FullScreenHideThumbBar", true));
+ d->horizontalThumbBar->setChecked(config->readBoolEntry("HorizontalThumbbar", false));
+ d->useTrash->setChecked(config->readBoolEntry("DeleteItem2Trash", false));
+ d->showSplash->setChecked(config->readBoolEntry("ShowSplash", true));
+ d->exifRotateBox->setChecked(config->readBoolEntry("EXIF Rotate", true));
+ d->exifSetOrientationBox->setChecked(config->readBoolEntry("EXIF Set Orientation", true));
+ d->underExposureColor->setColor(config->readColorEntry("UnderExposureColor", &White));
+ d->overExposureColor->setColor(config->readColorEntry("OverExposureColor", &Black));
+ d->sortOrderComboBox->setCurrentItem(config->readNumEntry("SortOrder", 0));
+ d->sortReverse->setChecked(config->readBoolEntry("ReverseSort", false));
+ d->useRawImportTool->setChecked(config->readBoolEntry("UseRawImportTool", false));
+}
+
+void SetupEditor::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ config->writeEntry("UseThemeBackgroundColor", d->themebackgroundColor->isChecked());
+ config->writeEntry("BackgroundColor", d->backgroundColor->color());
+ config->writeEntry("FullScreen Hide ToolBar", d->hideToolBar->isChecked());
+ config->writeEntry("FullScreenHideThumbBar", d->hideThumbBar->isChecked());
+ config->writeEntry("HorizontalThumbbar", d->horizontalThumbBar->isChecked());
+ config->writeEntry("DeleteItem2Trash", d->useTrash->isChecked());
+ config->writeEntry("ShowSplash", d->showSplash->isChecked());
+ config->writeEntry("EXIF Rotate", d->exifRotateBox->isChecked());
+ config->writeEntry("EXIF Set Orientation", d->exifSetOrientationBox->isChecked());
+ config->writeEntry("UnderExposureColor", d->underExposureColor->color());
+ config->writeEntry("OverExposureColor", d->overExposureColor->color());
+ config->writeEntry("SortOrder", d->sortOrderComboBox->currentItem());
+ config->writeEntry("ReverseSort", d->sortReverse->isChecked());
+ config->writeEntry("UseRawImportTool", d->useRawImportTool->isChecked());
+ config->sync();
+}
+
+} // namespace ShowFoto
diff --git a/src/showfoto/setup/setupeditor.h b/src/showfoto/setup/setupeditor.h
new file mode 100644
index 00000000..877af8de
--- /dev/null
+++ b/src/showfoto/setup/setupeditor.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-02
+ * Description : setup showfoto tab.
+ *
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPEDITOR_H
+#define SETUPEDITOR_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace ShowFoto
+{
+
+class SetupEditorPriv;
+
+class SetupEditor : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupEditor(TQWidget* parent=0);
+ ~SetupEditor();
+
+ void applySettings();
+
+private slots:
+
+ void slotThemeBackgroundColor(bool);
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupEditorPriv* d;
+};
+
+} // namespace ShowFoto
+
+#endif /* SETUPEDITOR_H */
diff --git a/src/showfoto/setup/setuptooltip.cpp b/src/showfoto/setup/setuptooltip.cpp
new file mode 100644
index 00000000..d3a08678
--- /dev/null
+++ b/src/showfoto/setup/setuptooltip.cpp
@@ -0,0 +1,228 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-09
+ * Description : tool tip contents configuration setup tab
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqvgroupbox.h>
+#include <tqcheckbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialogbase.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+
+// Local includes.
+
+#include "setuptooltip.h"
+#include "setuptooltip.moc"
+
+namespace ShowFoto
+{
+
+class SetupToolTipPriv
+{
+public:
+
+ SetupToolTipPriv()
+ {
+ showToolTipsBox = 0;
+
+ showFileNameBox = 0;
+ showFileDateBox = 0;
+ showFileSizeBox = 0;
+ showImageTypeBox = 0;
+ showImageDimBox = 0;
+
+ showPhotoMakeBox = 0;
+ showPhotoDateBox = 0;
+ showPhotoFocalBox = 0;
+ showPhotoExpoBox = 0;
+ showPhotoModeBox = 0;
+ showPhotoFlashBox = 0;
+ showPhotoWbBox = 0;
+
+ fileSettingBox = 0;
+ photoSettingBox = 0;
+ }
+
+ TQCheckBox *showToolTipsBox;
+
+ TQCheckBox *showFileNameBox;
+ TQCheckBox *showFileDateBox;
+ TQCheckBox *showFileSizeBox;
+ TQCheckBox *showImageTypeBox;
+ TQCheckBox *showImageDimBox;
+
+ TQCheckBox *showPhotoMakeBox;
+ TQCheckBox *showPhotoDateBox;
+ TQCheckBox *showPhotoFocalBox;
+ TQCheckBox *showPhotoExpoBox;
+ TQCheckBox *showPhotoModeBox;
+ TQCheckBox *showPhotoFlashBox;
+ TQCheckBox *showPhotoWbBox;
+
+ TQVGroupBox *fileSettingBox;
+ TQVGroupBox *photoSettingBox;
+};
+
+SetupToolTip::SetupToolTip(TQWidget* parent)
+ : TQWidget(parent)
+{
+ d = new SetupToolTipPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint() );
+
+ d->showToolTipsBox = new TQCheckBox(i18n("Show Thumbbar items toolti&ps"), parent);
+ TQWhatsThis::add( d->showToolTipsBox, i18n("<p>Set this option to display image information when "
+ "the mouse hovers over a thumbbar item."));
+
+ layout->addWidget(d->showToolTipsBox);
+
+ // --------------------------------------------------------
+
+ d->fileSettingBox = new TQVGroupBox(i18n("File/Image Information"), parent);
+
+ d->showFileNameBox = new TQCheckBox(i18n("Show file name"), d->fileSettingBox);
+ TQWhatsThis::add( d->showFileNameBox, i18n("<p>Set this option to display the image file name."));
+
+ d->showFileDateBox = new TQCheckBox(i18n("Show file date"), d->fileSettingBox);
+ TQWhatsThis::add( d->showFileDateBox, i18n("<p>Set this option to display the image file date."));
+
+ d->showFileSizeBox = new TQCheckBox(i18n("Show file size"), d->fileSettingBox);
+ TQWhatsThis::add( d->showFileSizeBox, i18n("<p>Set this option to display the image file size."));
+
+ d->showImageTypeBox = new TQCheckBox(i18n("Show image type"), d->fileSettingBox);
+ TQWhatsThis::add( d->showImageTypeBox, i18n("<p>Set this option to display the image type."));
+
+ d->showImageDimBox = new TQCheckBox(i18n("Show image dimensions"), d->fileSettingBox);
+ TQWhatsThis::add( d->showImageDimBox, i18n("<p>Set this option to display the image dimensions in pixels."));
+
+ layout->addWidget(d->fileSettingBox);
+
+ // --------------------------------------------------------
+
+ d->photoSettingBox = new TQVGroupBox(i18n("Photograph Information"), parent);
+
+ d->showPhotoMakeBox = new TQCheckBox(i18n("Show camera make and model"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoMakeBox, i18n("<p>Set this option to display the make and model of the "
+ "camera with which the image has been taken."));
+
+ d->showPhotoDateBox = new TQCheckBox(i18n("Show camera date"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoDateBox, i18n("<p>Set this option to display the date when the image was taken."));
+
+ d->showPhotoFocalBox = new TQCheckBox(i18n("Show camera aperture and focal length"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoFocalBox, i18n("<p>Set this option to display the camera aperture and focal settings "
+ "used to take the image."));
+
+ d->showPhotoExpoBox = new TQCheckBox(i18n("Show camera exposure and sensitivity"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoExpoBox, i18n("<p>Set this option to display the camera exposure and sensitivity "
+ "used to take the image."));
+
+ d->showPhotoModeBox = new TQCheckBox(i18n("Show camera mode and program"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoModeBox, i18n("<p>Set this option to display the camera mode and program "
+ "used to take the image."));
+
+ d->showPhotoFlashBox = new TQCheckBox(i18n("Show camera flash settings"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoFlashBox, i18n("<p>Set this option to display the camera flash settings "
+ "used to take the image."));
+
+ d->showPhotoWbBox = new TQCheckBox(i18n("Show camera white balance settings"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoWbBox, i18n("<p>Set this option to display the camera white balance settings "
+ "used to take the image."));
+
+ layout->addWidget(d->photoSettingBox);
+ layout->addStretch();
+
+ // --------------------------------------------------------
+
+ connect(d->showToolTipsBox, TQ_SIGNAL(toggled(bool)),
+ d->fileSettingBox, TQ_SLOT(setEnabled(bool)));
+
+ connect(d->showToolTipsBox, TQ_SIGNAL(toggled(bool)),
+ d->photoSettingBox, TQ_SLOT(setEnabled(bool)));
+
+ // --------------------------------------------------------
+
+ readSettings();
+ adjustSize();
+}
+
+SetupToolTip::~SetupToolTip()
+{
+ delete d;
+}
+
+void SetupToolTip::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ d->showToolTipsBox->setChecked(config->readBoolEntry("Show ToolTips", true));
+
+ d->showFileNameBox->setChecked(config->readBoolEntry("ToolTips Show File Name", true));
+ d->showFileDateBox->setChecked(config->readBoolEntry("ToolTips Show File Date", false));
+ d->showFileSizeBox->setChecked(config->readBoolEntry("ToolTips Show File Size", false));
+ d->showImageTypeBox->setChecked(config->readBoolEntry("ToolTips Show Image Type", false));
+ d->showImageDimBox->setChecked(config->readBoolEntry("ToolTips Show Image Dim", true));
+
+ d->showPhotoMakeBox->setChecked(config->readBoolEntry("ToolTips Show Photo Make", true));
+ d->showPhotoDateBox->setChecked(config->readBoolEntry("ToolTips Show Photo Date", true));
+ d->showPhotoFocalBox->setChecked(config->readBoolEntry("ToolTips Show Photo Focal", true));
+ d->showPhotoExpoBox->setChecked(config->readBoolEntry("ToolTips Show Photo Expo", true));
+ d->showPhotoModeBox->setChecked(config->readBoolEntry("ToolTips Show Photo Mode", true));
+ d->showPhotoFlashBox->setChecked(config->readBoolEntry("ToolTips Show Photo Flash", false));
+ d->showPhotoWbBox->setChecked(config->readBoolEntry("ToolTips Show Photo WB", false));
+
+ d->fileSettingBox->setEnabled(d->showToolTipsBox->isChecked());
+ d->photoSettingBox->setEnabled(d->showToolTipsBox->isChecked());
+}
+
+void SetupToolTip::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ config->writeEntry("Show ToolTips", d->showToolTipsBox->isChecked());
+
+ config->writeEntry("ToolTips Show File Name", d->showFileNameBox->isChecked());
+ config->writeEntry("ToolTips Show File Date", d->showFileDateBox->isChecked());
+ config->writeEntry("ToolTips Show File Size", d->showFileSizeBox->isChecked());
+ config->writeEntry("ToolTips Show Image Type", d->showImageTypeBox->isChecked());
+ config->writeEntry("ToolTips Show Image Dim", d->showImageDimBox->isChecked());
+
+ config->writeEntry("ToolTips Show Photo Make", d->showPhotoMakeBox->isChecked());
+ config->writeEntry("ToolTips Show Photo Date", d->showPhotoDateBox->isChecked());
+ config->writeEntry("ToolTips Show Photo Focal", d->showPhotoFocalBox->isChecked());
+ config->writeEntry("ToolTips Show Photo Expo", d->showPhotoExpoBox->isChecked());
+ config->writeEntry("ToolTips Show Photo Mode", d->showPhotoModeBox->isChecked());
+ config->writeEntry("ToolTips Show Photo Flash", d->showPhotoFlashBox->isChecked());
+ config->writeEntry("ToolTips Show Photo WB", d->showPhotoWbBox->isChecked());
+
+ config->sync();
+}
+
+} // namespace ShowFoto
+
diff --git a/src/showfoto/setup/setuptooltip.h b/src/showfoto/setup/setuptooltip.h
new file mode 100644
index 00000000..ebd73f6e
--- /dev/null
+++ b/src/showfoto/setup/setuptooltip.h
@@ -0,0 +1,59 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-09
+ * Description : tool tip contents configuration setup tab
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPTOOLTIP_H
+#define SETUPTOOLTIP_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace ShowFoto
+{
+
+class SetupToolTipPriv;
+
+class SetupToolTip : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupToolTip(TQWidget* parent = 0);
+ ~SetupToolTip();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupToolTipPriv* d;
+};
+
+} // namespace ShowFoto
+
+#endif // SETUPTOOLTIP_H
diff --git a/src/showfoto/showfoto.cpp b/src/showfoto/showfoto.cpp
new file mode 100644
index 00000000..42575833
--- /dev/null
+++ b/src/showfoto/showfoto.cpp
@@ -0,0 +1,1240 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-22
+ * Description : stand alone digiKam image editor GUI
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2006 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ * Copyright (C) 2008 by Arnd Baecker <arnd dot baecker at web dot de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqsplitter.h>
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqfile.h>
+#include <tqcursor.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdeaction.h>
+#include <kstdaction.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdefiledialog.h>
+#include <tdemenubar.h>
+#include <kimageio.h>
+#include <tdeaccel.h>
+#include <tdeversion.h>
+#include <tdemessagebox.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+#include <kiconloader.h>
+#include <tdeio/netaccess.h>
+#include <tdeio/job.h>
+#include <kprotocolinfo.h>
+#include <tdeglobalsettings.h>
+#include <tdetoolbar.h>
+#include <kstatusbar.h>
+#include <kprogress.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dpopupmenu.h"
+#include "dmetadata.h"
+#include "canvas.h"
+#include "thumbbar.h"
+#include "imagepropertiessidebar.h"
+#include "imageplugin.h"
+#include "imagepluginloader.h"
+#include "imagedialog.h"
+#include "dimginterface.h"
+#include "splashscreen.h"
+#include "slideshow.h"
+#include "setup.h"
+#include "setupicc.h"
+#include "statusprogressbar.h"
+#include "iccsettingscontainer.h"
+#include "iofilesettingscontainer.h"
+#include "loadingcacheinterface.h"
+#include "savingcontextcontainer.h"
+#include "themeengine.h"
+#include "editorstackview.h"
+#include "showfoto.h"
+#include "showfoto.moc"
+
+namespace ShowFoto
+{
+
+class ShowFotoPriv
+{
+public:
+
+ ShowFotoPriv()
+ {
+ currentItem = 0;
+ itemsNb = 0;
+ splash = 0;
+ BCGAction = 0;
+ showBarAction = 0;
+ openFilesInFolderAction = 0;
+ fileOpenAction = 0;
+ thumbBar = 0;
+ rightSidebar = 0;
+ splash = 0;
+ itemsNb = 0;
+ vSplitter = 0;
+ deleteItem2Trash = true;
+ fullScreenHideThumbBar = true;
+ validIccPath = true;
+ }
+
+ bool fullScreenHideThumbBar;
+ bool deleteItem2Trash;
+ bool validIccPath;
+
+ int itemsNb;
+
+ TQSplitter *vSplitter;
+
+ KURL lastOpenedDirectory;
+
+ TDEToggleAction *showBarAction;
+
+ TDEAction *openFilesInFolderAction;
+ TDEAction *fileOpenAction;
+
+ TDEActionMenu *BCGAction;
+
+ Digikam::ThumbBarView *thumbBar;
+ Digikam::ThumbBarItem *currentItem;
+ Digikam::ImagePropertiesSideBar *rightSidebar;
+ Digikam::SplashScreen *splash;
+};
+
+ShowFoto::ShowFoto(const KURL::List& urlList)
+ : Digikam::EditorWindow( "Showfoto" )
+{
+ d = new ShowFotoPriv();
+
+ // -- Show splash at start ----------------------------
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ TDEGlobal::dirs()->addResourceType("data", TDEGlobal::dirs()->kde_default("data") + "digikam");
+ TDEGlobal::iconLoader()->addAppDir("digikam");
+
+ if(config->readBoolEntry("ShowSplash", true) && !kapp->isRestored())
+ {
+ d->splash = new Digikam::SplashScreen("showfoto-splash.png");
+ d->splash->show();
+ }
+
+ // Check ICC profiles repository availability
+
+ if(d->splash)
+ d->splash->message(i18n("Checking ICC repository"));
+
+ d->validIccPath = Digikam::SetupICC::iccRepositoryIsValid();
+
+#if KDCRAW_VERSION < 0x000106
+ // Check witch dcraw version available
+
+ if(d->splash)
+ d->splash->message(i18n("Checking dcraw version"));
+
+ KDcrawIface::DcrawBinary::instance()->checkSystem();
+#endif
+
+ // Populate Themes
+
+ if(d->splash)
+ d->splash->message(i18n("Loading themes"));
+
+ Digikam::ThemeEngine::instance()->scanThemes();
+
+ // -- Build the GUI -----------------------------------
+
+ setupUserArea();
+ setupStatusBar();
+ setupActions();
+
+ // Load image plugins to GUI
+
+ m_imagePluginLoader = new Digikam::ImagePluginLoader(this, d->splash);
+ loadImagePlugins();
+
+ // If plugin core is not available, plug BCG actions to collection instead.
+
+ if ( !m_imagePluginLoader->pluginLibraryIsLoaded("digikamimageplugin_core") )
+ {
+ d->BCGAction = new TDEActionMenu(i18n("Brightness/Contrast/Gamma"), 0, 0, "showfoto_bcg");
+ d->BCGAction->setDelayed(false);
+
+ TDEAction *incGammaAction = new TDEAction(i18n("Increase Gamma"), 0, ALT+Key_G,
+ this, TQ_SLOT(slotChangeBCG()),
+ actionCollection(), "gamma_plus");
+ TDEAction *decGammaAction = new TDEAction(i18n("Decrease Gamma"), 0, ALT+SHIFT+Key_G,
+ this, TQ_SLOT(slotChangeBCG()),
+ actionCollection(), "gamma_minus");
+ TDEAction *incBrightAction = new TDEAction(i18n("Increase Brightness"), 0, ALT+Key_B,
+ this, TQ_SLOT(slotChangeBCG()),
+ actionCollection(), "brightness_plus");
+ TDEAction *decBrightAction = new TDEAction(i18n("Decrease Brightness"), 0, ALT+SHIFT+Key_B,
+ this, TQ_SLOT(slotChangeBCG()),
+ actionCollection(), "brightness_minus");
+ TDEAction *incContrastAction = new TDEAction(i18n("Increase Contrast"), 0, ALT+Key_C,
+ this, TQ_SLOT(slotChangeBCG()),
+ actionCollection(), "contrast_plus");
+ TDEAction *decContrastAction = new TDEAction(i18n("Decrease Contrast"), 0, ALT+SHIFT+Key_C,
+ this, TQ_SLOT(slotChangeBCG()),
+ actionCollection(), "contrast_minus");
+
+ d->BCGAction->insert(incBrightAction);
+ d->BCGAction->insert(decBrightAction);
+ d->BCGAction->insert(incContrastAction);
+ d->BCGAction->insert(decContrastAction);
+ d->BCGAction->insert(incGammaAction);
+ d->BCGAction->insert(decGammaAction);
+
+ TQPtrList<TDEAction> bcg_actions;
+ bcg_actions.append( d->BCGAction );
+ unplugActionList( "showfoto_bcg" );
+ plugActionList( "showfoto_bcg", bcg_actions );
+ }
+
+ // Create context menu.
+
+ setupContextMenu();
+
+ // Make signals/slots connections
+
+ setupConnections();
+
+ // -- Read settings --------------------------------
+
+ readSettings();
+ applySettings();
+ setAutoSaveSettings("ImageViewer Settings");
+
+ // -- Load current items ---------------------------
+
+ for (KURL::List::const_iterator it = urlList.begin();
+ it != urlList.end(); ++it)
+ {
+ KURL url = *it;
+ if (url.isLocalFile())
+ {
+ TQFileInfo fi(url.path());
+ if (fi.isDir())
+ {
+ // Local Dir
+ openFolder(url);
+ }
+ else
+ {
+ // Local file
+ new Digikam::ThumbBarItem(d->thumbBar, url);
+ d->lastOpenedDirectory=(*it);
+ }
+ }
+ else
+ {
+ // Remote file.
+ new Digikam::ThumbBarItem(d->thumbBar, url);
+ d->lastOpenedDirectory=(*it);
+ }
+ }
+
+ if ( urlList.isEmpty() )
+ {
+ emit signalNoCurrentItem();
+ toggleActions(false);
+ toggleNavigation(0);
+ }
+ else
+ {
+ toggleNavigation(1);
+ toggleActions(true);
+ }
+}
+
+ShowFoto::~ShowFoto()
+{
+ unLoadImagePlugins();
+
+ delete m_imagePluginLoader;
+ delete d->thumbBar;
+ delete d->rightSidebar;
+ delete d;
+}
+
+Digikam::Sidebar* ShowFoto::rightSideBar() const
+{
+ return dynamic_cast<Digikam::Sidebar*>(d->rightSidebar);
+}
+
+bool ShowFoto::queryClose()
+{
+ // wait if a save operation is currently running
+ if (!waitForSavingToComplete())
+ return false;
+
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return false;
+
+ // put right side bar in a defined state
+ emit signalNoCurrentItem();
+ m_canvas->resetImage();
+
+ return true;
+}
+
+bool ShowFoto::queryExit()
+{
+ saveSettings();
+ return true;
+}
+
+void ShowFoto::show()
+{
+ // Remove Splashscreen.
+
+ if(d->splash)
+ {
+ d->splash->finish(this);
+ delete d->splash;
+ d->splash = 0;
+ }
+
+ // Display application window.
+
+ TDEMainWindow::show();
+
+ // Report errors from ICC repository path.
+
+ TDEConfig* config = kapp->config();
+ if(!d->validIccPath)
+ {
+ TQString message = i18n("<qt><p>The ICC profile path seems to be invalid.</p>"
+ "<p>If you want to set it now, select \"Yes\", otherwise "
+ "select \"No\". In this case, \"Color Management\" feature "
+ "will be disabled until you solve this issue</p></qt>");
+
+ if (KMessageBox::warningYesNo(this, message) == KMessageBox::Yes)
+ {
+ if (!setup(true))
+ {
+ config->setGroup("Color Management");
+ config->writeEntry("EnableCM", false);
+ config->sync();
+ }
+ }
+ else
+ {
+ config->setGroup("Color Management");
+ config->writeEntry("EnableCM", false);
+ config->sync();
+ }
+ }
+
+#if KDCRAW_VERSION < 0x000106
+ // Report errors from dcraw detection.
+
+ KDcrawIface::DcrawBinary::instance()->checkReport();
+#endif
+}
+
+void ShowFoto::setupConnections()
+{
+ setupStandardConnections();
+
+ connect(d->thumbBar, TQ_SIGNAL(signalURLSelected(const KURL&)),
+ this, TQ_SLOT(slotOpenURL(const KURL&)));
+
+ connect(d->thumbBar, TQ_SIGNAL(signalItemAdded()),
+ this, TQ_SLOT(slotUpdateItemInfo()));
+
+ connect(this, TQ_SIGNAL(signalSelectionChanged(const TQRect &)),
+ d->rightSidebar, TQ_SLOT(slotImageSelectionChanged(const TQRect &)));
+
+ connect(this, TQ_SIGNAL(signalNoCurrentItem()),
+ d->rightSidebar, TQ_SLOT(slotNoCurrentItem()));
+}
+
+void ShowFoto::setupUserArea()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ TQWidget* widget = new TQWidget(this);
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+
+ if(!config->readBoolEntry("HorizontalThumbbar", false)) // Vertical thumbbar layout
+ {
+ TQHBoxLayout *hlay = new TQHBoxLayout(widget);
+ m_splitter = new TQSplitter(widget);
+ d->thumbBar = new Digikam::ThumbBarView(m_splitter, Digikam::ThumbBarView::Vertical);
+ m_stackView = new Digikam::EditorStackView(m_splitter);
+ m_canvas = new Digikam::Canvas(m_stackView);
+ m_canvas->setSizePolicy(rightSzPolicy);
+
+ d->rightSidebar = new Digikam::ImagePropertiesSideBar(widget, "ShowFoto Sidebar Right", m_splitter,
+ Digikam::Sidebar::Right);
+
+ hlay->addWidget(m_splitter);
+ hlay->addWidget(d->rightSidebar);
+ }
+ else // Horizontal thumbbar layout
+ {
+ m_splitter = new TQSplitter(TQt::Horizontal, widget);
+ TQWidget* widget2 = new TQWidget(m_splitter);
+ TQVBoxLayout *vlay = new TQVBoxLayout(widget2);
+ d->vSplitter = new TQSplitter(TQt::Vertical, widget2);
+ m_stackView = new Digikam::EditorStackView(d->vSplitter);
+ m_canvas = new Digikam::Canvas(m_stackView);
+ d->thumbBar = new Digikam::ThumbBarView(d->vSplitter, Digikam::ThumbBarView::Horizontal);
+
+ m_canvas->setSizePolicy(rightSzPolicy);
+
+ d->vSplitter->setFrameStyle( TQFrame::NoFrame );
+ d->vSplitter->setFrameShadow( TQFrame::Plain );
+ d->vSplitter->setFrameShape( TQFrame::NoFrame );
+ d->vSplitter->setOpaqueResize(false);
+
+ vlay->addWidget(d->vSplitter);
+
+ TQHBoxLayout *hlay = new TQHBoxLayout(widget);
+ d->rightSidebar = new Digikam::ImagePropertiesSideBar(widget, "ShowFoto Sidebar Right", m_splitter,
+ Digikam::Sidebar::Right);
+ hlay->addWidget(m_splitter);
+ hlay->addWidget(d->rightSidebar);
+ }
+
+ m_canvas->makeDefaultEditingCanvas();
+ m_stackView->setCanvas(m_canvas);
+ m_stackView->setViewMode(Digikam::EditorStackView::CanvasMode);
+
+ m_splitter->setFrameStyle( TQFrame::NoFrame );
+ m_splitter->setFrameShadow( TQFrame::Plain );
+ m_splitter->setFrameShape( TQFrame::NoFrame );
+ m_splitter->setOpaqueResize(false);
+ setCentralWidget(widget);
+ d->rightSidebar->loadViewState();
+}
+
+void ShowFoto::setupActions()
+{
+ setupStandardActions();
+
+ // Provides a menu entry that allows showing/hiding the toolbar(s)
+ setStandardToolBarMenuEnabled(true);
+
+ // Provides a menu entry that allows showing/hiding the statusbar
+ createStandardStatusBarAction();
+
+ // Extra 'File' menu actions ---------------------------------------------
+
+ d->fileOpenAction = KStdAction::open(this, TQ_SLOT(slotOpenFile()),
+ actionCollection(), "showfoto_open_file");
+
+ d->openFilesInFolderAction = new TDEAction(i18n("Open folder"),
+ "folder_image",
+ CTRL+SHIFT+Key_O,
+ this,
+ TQ_SLOT(slotOpenFilesInFolder()),
+ actionCollection(),
+ "showfoto_open_folder");
+
+ KStdAction::quit(this, TQ_SLOT(close()), actionCollection(), "showfoto_quit");
+
+ // Extra 'View' menu actions ---------------------------------------------
+
+ d->showBarAction = new TDEToggleAction(i18n("Show Thumbnails"), 0,
+ CTRL+Key_T,
+ this, TQ_SLOT(slotToggleShowBar()),
+ actionCollection(), "shofoto_showthumbs");
+
+ // --- Create the gui --------------------------------------------------------------
+
+ createGUI("showfotoui.rc", false);
+
+ setupStandardAccelerators();
+}
+
+void ShowFoto::readSettings()
+{
+ readStandardSettings();
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ d->showBarAction->setChecked(config->readBoolEntry("Show Thumbnails", true));
+ slotToggleShowBar();
+
+ d->lastOpenedDirectory.setPath( config->readEntry("Last Opened Directory",
+ TDEGlobalSettings::documentPath()) );
+
+ TQSizePolicy szPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+ if(config->hasKey("Vertical Splitter Sizes") && d->vSplitter)
+ d->vSplitter->setSizes(config->readIntListEntry("Vertical Splitter Sizes"));
+ else
+ m_canvas->setSizePolicy(szPolicy);
+
+ Digikam::ThemeEngine::instance()->setCurrentTheme(config->readEntry("Theme", i18n("Default")));
+}
+
+void ShowFoto::saveSettings()
+{
+ saveStandardSettings();
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ config->writeEntry("Last Opened Directory", d->lastOpenedDirectory.path() );
+ config->writeEntry("Show Thumbnails", d->showBarAction->isChecked());
+
+ if (d->vSplitter)
+ config->writeEntry("Vertical Splitter Sizes", d->vSplitter->sizes());
+
+ config->writeEntry("Theme", Digikam::ThemeEngine::instance()->getCurrentThemeName());
+
+ config->sync();
+}
+
+void ShowFoto::applySettings()
+{
+ applyStandardSettings();
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ // Current image deleted go to trash ?
+ d->deleteItem2Trash = config->readBoolEntry("DeleteItem2Trash", true);
+ if (d->deleteItem2Trash)
+ {
+ m_fileDeleteAction->setIcon("edittrash");
+ m_fileDeleteAction->setText(i18n("Move to Trash"));
+ }
+ else
+ {
+ m_fileDeleteAction->setIcon("edit-delete");
+ m_fileDeleteAction->setText(i18n("Delete File"));
+ }
+
+ bool exifRotate = config->readBoolEntry("EXIF Rotate", true);
+ m_canvas->setExifOrient(exifRotate);
+ d->thumbBar->setExifRotate(exifRotate);
+
+ m_setExifOrientationTag = config->readBoolEntry("EXIF Set Orientation", true);
+
+ d->fullScreenHideThumbBar = config->readBoolEntry("FullScreenHideThumbBar", true);
+
+ Digikam::ThumbBarToolTipSettings settings;
+ settings.showToolTips = config->readBoolEntry("Show ToolTips", true);
+ settings.showFileName = config->readBoolEntry("ToolTips Show File Name", true);
+ settings.showFileDate = config->readBoolEntry("ToolTips Show File Date", false);
+ settings.showFileSize = config->readBoolEntry("ToolTips Show File Size", false);
+ settings.showImageType = config->readBoolEntry("ToolTips Show Image Type", false);
+ settings.showImageDim = config->readBoolEntry("ToolTips Show Image Dim", true);
+ settings.showPhotoMake = config->readBoolEntry("ToolTips Show Photo Make", true);
+ settings.showPhotoDate = config->readBoolEntry("ToolTips Show Photo Date", true);
+ settings.showPhotoFocal = config->readBoolEntry("ToolTips Show Photo Focal", true);
+ settings.showPhotoExpo = config->readBoolEntry("ToolTips Show Photo Expo", true);
+ settings.showPhotoMode = config->readBoolEntry("ToolTips Show Photo Mode", true);
+ settings.showPhotoFlash = config->readBoolEntry("ToolTips Show Photo Flash", false);
+ settings.showPhotoWB = config->readBoolEntry("ToolTips Show Photo WB", false);
+ d->thumbBar->setToolTipSettings(settings);
+}
+
+void ShowFoto::slotOpenFile()
+{
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return;
+
+ KURL::List urls = Digikam::ImageDialog::getImageURLs(this, d->lastOpenedDirectory);
+
+ if (!urls.isEmpty())
+ {
+ d->currentItem = 0;
+ d->thumbBar->clear();
+
+ for (KURL::List::const_iterator it = urls.begin();
+ it != urls.end(); ++it)
+ {
+ new Digikam::ThumbBarItem(d->thumbBar, *it);
+ d->lastOpenedDirectory=(*it);
+ }
+
+ toggleActions(true);
+ }
+}
+
+void ShowFoto::slotOpenURL(const KURL& url)
+{
+ if(d->currentItem && !promptUserSave(d->currentItem->url()))
+ {
+ d->thumbBar->blockSignals(true);
+ d->thumbBar->setSelected(d->currentItem);
+ d->thumbBar->blockSignals(false);
+ return;
+ }
+
+ d->currentItem = d->thumbBar->currentItem();
+ if(!d->currentItem)
+ return;
+
+ TQString localFile;
+#if KDE_IS_VERSION(3,2,0)
+ TDEIO::NetAccess::download(url, localFile, this);
+#else
+ TDEIO::NetAccess::download(url, localFile);
+#endif
+
+ m_canvas->load(localFile, m_IOFileSettings);
+
+ // TODO : add preload here like in ImageWindow::slotLoadCurrent() ???
+}
+
+void ShowFoto::toggleGUI2FullScreen()
+{
+ if (m_fullScreen)
+ {
+ d->rightSidebar->restore();
+
+ // If show Thumbbar option is checked, restore it.
+ if (d->showBarAction->isChecked())
+ d->thumbBar->show();
+ }
+ else
+ {
+ d->rightSidebar->backup();
+
+ // If Hide Thumbbar option is checked, catch it if necessary.
+ if (d->showBarAction->isChecked())
+ {
+ if (d->fullScreenHideThumbBar)
+ d->thumbBar->hide();
+ }
+ }
+}
+
+void ShowFoto::slotToggleShowBar()
+{
+ if (d->showBarAction->isChecked())
+ d->thumbBar->show();
+ else
+ d->thumbBar->hide();
+}
+
+void ShowFoto::slotChangeBCG()
+{
+ TQString name;
+ if (sender())
+ name = sender()->name();
+
+ if (name == "gamma_plus")
+ {
+ m_canvas->increaseGamma();
+ }
+ else if (name == "gamma_minus")
+ {
+ m_canvas->decreaseGamma();
+ }
+ else if (name == "brightness_plus")
+ {
+ m_canvas->increaseBrightness();
+ }
+ else if (name == "brightness_minus")
+ {
+ m_canvas->decreaseBrightness();
+ }
+ else if (name == "contrast_plus")
+ {
+ m_canvas->increaseContrast();
+ }
+ else if (name == "contrast_minus")
+ {
+ m_canvas->decreaseContrast();
+ }
+}
+
+void ShowFoto::slotChanged()
+{
+ TQString mpixels;
+ TQSize dims(m_canvas->imageWidth(), m_canvas->imageHeight());
+ mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2);
+ TQString str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)")
+ .arg(dims.width()).arg(dims.height()).arg(mpixels);
+ m_resLabel->setText(str);
+
+ if (d->currentItem)
+ {
+ if (d->currentItem->url().isValid())
+ {
+ TQRect sel = m_canvas->getSelectedArea();
+ Digikam::DImg* img = m_canvas->interface()->getImg();
+ d->rightSidebar->itemChanged(d->currentItem->url(), sel, img);
+ }
+ }
+}
+
+void ShowFoto::slotUndoStateChanged(bool moreUndo, bool moreRedo, bool canSave)
+{
+ m_revertAction->setEnabled(canSave);
+ m_undoAction->setEnabled(moreUndo);
+ m_redoAction->setEnabled(moreRedo);
+ m_saveAction->setEnabled(canSave);
+
+ if (!moreUndo)
+ m_rotatedOrFlipped = false;
+}
+
+void ShowFoto::toggleActions(bool val)
+{
+ toggleStandardActions(val);
+
+ // if BCG actions exists then toggle it.
+ if (d->BCGAction)
+ d->BCGAction->setEnabled(val);
+}
+
+void ShowFoto::slotFilePrint()
+{
+ printImage(d->currentItem->url());
+}
+
+bool ShowFoto::setup(bool iccSetupPage)
+{
+ Setup setup(this, 0, iccSetupPage ? Setup::ICCPage : Setup::LastPageUsed);
+
+ if (setup.exec() != TQDialog::Accepted)
+ return false;
+
+ kapp->config()->sync();
+
+ applySettings();
+
+ if ( d->itemsNb == 0 )
+ {
+ slotUpdateItemInfo();
+ toggleActions(false);
+ }
+
+ return true;
+}
+
+void ShowFoto::slotUpdateItemInfo(void)
+{
+ d->itemsNb = d->thumbBar->countItems();
+
+ m_rotatedOrFlipped = false;
+ int index = 0;
+ TQString text;
+
+ if (d->itemsNb > 0)
+ {
+ index = 1;
+
+ for (Digikam::ThumbBarItem *item = d->thumbBar->firstItem(); item; item = item->next())
+ {
+ if (item->url().equals(d->currentItem->url()))
+ {
+ break;
+ }
+ index++;
+ }
+
+ text = d->currentItem->url().filename() +
+ i18n(" (%2 of %3)")
+ .arg(TQString::number(index))
+ .arg(TQString::number(d->itemsNb));
+
+ setCaption(d->currentItem->url().directory());
+ }
+ else
+ {
+ text = "";
+ setCaption("");
+ }
+
+ m_nameLabel->setText(text);
+
+ toggleNavigation( index );
+}
+
+void ShowFoto::slotOpenFolder(const KURL& url)
+{
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return;
+
+ m_canvas->load(TQString(), m_IOFileSettings);
+ d->thumbBar->clear(true);
+ emit signalNoCurrentItem();
+ d->currentItem = 0;
+ openFolder(url);
+ toggleActions(true);
+ toggleNavigation(1);
+}
+
+void ShowFoto::openFolder(const KURL& url)
+{
+ if (!url.isValid() || !url.isLocalFile())
+ return;
+
+ // Parse KDE image IO mime types registration to get files filter pattern.
+
+ TQStringList mimeTypes = KImageIO::mimeTypes(KImageIO::Reading);
+ TQString filter;
+
+ for (TQStringList::ConstIterator it = mimeTypes.begin() ; it != mimeTypes.end() ; ++it)
+ {
+ TQString format = KImageIO::typeForMime(*it);
+ filter.append ("*.");
+ filter.append (format);
+ filter.append (" ");
+ }
+
+ // Because KImageIO return only *.JPEG and *.TIFF mime types.
+ if ( filter.contains("*.TIFF") )
+ filter.append (" *.TIF");
+ if ( filter.contains("*.JPEG") )
+ {
+ filter.append (" *.JPG");
+ filter.append (" *.JPE");
+ }
+
+ // Added RAW files estentions supported by dcraw program and
+ // defines to digikam/libs/dcraw/rawfiles.h
+ filter.append (" ");
+#if KDCRAW_VERSION < 0x000106
+ filter.append ( TQString(KDcrawIface::DcrawBinary::instance()->rawFiles()) );
+#else
+ filter.append ( TQString(KDcrawIface::KDcraw::rawFiles()) );
+#endif
+ filter.append (" ");
+
+ TQString patterns = filter.lower();
+ patterns.append (" ");
+ patterns.append (filter.upper());
+
+ DDebug() << "patterns=" << patterns << endl;
+
+ // Get all image files from directory.
+
+ TQDir dir(url.path(), patterns);
+ dir.setFilter ( TQDir::Files | TQDir::NoSymLinks );
+
+ if (!dir.exists())
+ return;
+
+ // Determine sort ordering for the entries from configuration setting:
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ int flag;
+
+ switch(config->readNumEntry("SortOrder", 0))
+ {
+ case 1:
+ flag = TQDir::Name; // Ordering by file name.
+ break;
+ case 2:
+ flag = TQDir::Size; // Ordering by file size.
+ break;
+ default:
+ flag = TQDir::Time; // Ordering by file date.
+ break;
+ }
+
+ // Disabled reverse in the settings leads e.g. to increasing dates
+ // Note, that this is just the opposite to the sort order for TQDir.
+
+ if (!config->readBoolEntry("ReverseSort", false))
+ flag = flag | TQDir::Reversed;
+
+ dir.setSorting(flag);
+
+ const TQFileInfoList* fileinfolist = dir.entryInfoList();
+ if (!fileinfolist || fileinfolist->isEmpty())
+ {
+ KMessageBox::sorry(this, i18n("There are no images in this folder."));
+ return;
+ }
+
+ TQFileInfoListIterator it(*fileinfolist);
+ TQFileInfo* fi;
+
+ // And open all items in image editor.
+
+ while( (fi = it.current() ) )
+ {
+ new Digikam::ThumbBarItem( d->thumbBar, KURL(fi->filePath()) );
+ ++it;
+ }
+}
+
+void ShowFoto::slotOpenFilesInFolder()
+{
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return;
+
+ KURL url(KFileDialog::getExistingDirectory(d->lastOpenedDirectory.directory(),
+ this, i18n("Open Images From Folder")));
+
+ if (!url.isEmpty())
+ {
+ d->lastOpenedDirectory = url;
+ slotOpenFolder(url);
+ }
+}
+
+void ShowFoto::slotFirst()
+{
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return;
+
+ d->thumbBar->setSelected( d->thumbBar->firstItem() );
+ d->currentItem = d->thumbBar->firstItem();
+}
+
+void ShowFoto::slotLast()
+{
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return;
+
+ d->thumbBar->setSelected( d->thumbBar->lastItem() );
+ d->currentItem = d->thumbBar->lastItem();
+}
+
+void ShowFoto::slotForward()
+{
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return;
+
+ Digikam::ThumbBarItem* curr = d->thumbBar->currentItem();
+ if (curr && curr->next())
+ {
+ d->thumbBar->setSelected(curr->next());
+ d->currentItem = d->thumbBar->currentItem();
+ }
+}
+
+void ShowFoto::slotBackward()
+{
+ if (d->currentItem && !promptUserSave(d->currentItem->url()))
+ return;
+
+ Digikam::ThumbBarItem* curr = d->thumbBar->currentItem();
+ if (curr && curr->prev())
+ {
+ d->thumbBar->setSelected(curr->prev());
+ d->currentItem = d->thumbBar->currentItem();
+ }
+}
+
+void ShowFoto::toggleNavigation(int index)
+{
+ if ( d->itemsNb == 0 || d->itemsNb == 1 )
+ {
+ m_backwardAction->setEnabled(false);
+ m_forwardAction->setEnabled(false);
+ m_firstAction->setEnabled(false);
+ m_lastAction->setEnabled(false);
+ }
+ else
+ {
+ m_backwardAction->setEnabled(true);
+ m_forwardAction->setEnabled(true);
+ m_firstAction->setEnabled(true);
+ m_lastAction->setEnabled(true);
+ }
+
+ if (index == 1)
+ {
+ m_backwardAction->setEnabled(false);
+ m_firstAction->setEnabled(false);
+ }
+
+ if (index == d->itemsNb)
+ {
+ m_forwardAction->setEnabled(false);
+ m_lastAction->setEnabled(false);
+ }
+}
+
+void ShowFoto::slotLoadingStarted(const TQString& filename)
+{
+ Digikam::EditorWindow::slotLoadingStarted(filename);
+
+ // Here we disable specific actions on showfoto.
+ d->openFilesInFolderAction->setEnabled(false);
+ d->fileOpenAction->setEnabled(false);
+}
+
+void ShowFoto::slotLoadingFinished(const TQString& filename, bool success)
+{
+ Digikam::EditorWindow::slotLoadingFinished(filename, success);
+
+ // Here we re-enable specific actions on showfoto.
+ d->openFilesInFolderAction->setEnabled(true);
+ d->fileOpenAction->setEnabled(true);
+}
+
+void ShowFoto::slotSavingStarted(const TQString& filename)
+{
+ Digikam::EditorWindow::slotSavingStarted(filename);
+
+ // Here we disable specific actions on showfoto.
+ d->openFilesInFolderAction->setEnabled(false);
+ d->fileOpenAction->setEnabled(false);
+}
+
+void ShowFoto::finishSaving(bool success)
+{
+ Digikam::EditorWindow::finishSaving(success);
+
+ // Here we re-enable specific actions on showfoto.
+ d->openFilesInFolderAction->setEnabled(true);
+ d->fileOpenAction->setEnabled(true);
+}
+
+void ShowFoto::saveIsComplete()
+{
+ Digikam::LoadingCacheInterface::putImage(m_savingContext->destinationURL.path(), m_canvas->currentImage());
+ d->thumbBar->invalidateThumb(d->currentItem);
+}
+
+void ShowFoto::saveAsIsComplete()
+{
+ m_canvas->switchToLastSaved(m_savingContext->destinationURL.path());
+ Digikam::LoadingCacheInterface::putImage(m_savingContext->destinationURL.path(), m_canvas->currentImage());
+
+ // Add the file to the list of thumbbar images if it's not there already
+ Digikam::ThumbBarItem* foundItem = d->thumbBar->findItemByURL(m_savingContext->destinationURL);
+ d->thumbBar->invalidateThumb(foundItem);
+
+ if (!foundItem)
+ foundItem = new Digikam::ThumbBarItem(d->thumbBar, m_savingContext->destinationURL);
+
+ // shortcut slotOpenURL
+ d->thumbBar->blockSignals(true);
+ d->thumbBar->setSelected(foundItem);
+ d->thumbBar->blockSignals(false);
+ d->currentItem = foundItem;
+}
+
+bool ShowFoto::save()
+{
+ if (!d->currentItem)
+ {
+ DWarning() << k_funcinfo << "This should not happen" << endl;
+ return true;
+ }
+
+ if (!d->currentItem->url().isLocalFile())
+ {
+ return false;
+ }
+
+ startingSave(d->currentItem->url());
+ return true;
+}
+
+bool ShowFoto::saveAs()
+{
+ if (!d->currentItem)
+ {
+ DWarning() << k_funcinfo << "This should not happen" << endl;
+ return false;
+ }
+
+ return ( startingSaveAs(d->currentItem->url()) );
+}
+
+void ShowFoto::slotDeleteCurrentItem()
+{
+ KURL urlCurrent(d->currentItem->url());
+
+ if (!d->deleteItem2Trash)
+ {
+ TQString warnMsg(i18n("About to delete file \"%1\"\nAre you sure?")
+ .arg(urlCurrent.filename()));
+ if (KMessageBox::warningContinueCancel(this,
+ warnMsg,
+ i18n("Warning"),
+ i18n("Delete"))
+ != KMessageBox::Continue)
+ {
+ return;
+ }
+ else
+ {
+ TDEIO::Job* job = TDEIO::del( urlCurrent );
+ connect( job, TQ_SIGNAL(result( TDEIO::Job* )),
+ this, TQ_SLOT(slotDeleteCurrentItemResult( TDEIO::Job*)) );
+ }
+ }
+ else
+ {
+ KURL dest("trash:/");
+
+ if (!KProtocolInfo::isKnownProtocol(dest))
+ {
+ dest = TDEGlobalSettings::trashPath();
+ }
+
+ TDEIO::Job* job = TDEIO::move( urlCurrent, dest );
+ connect( job, TQ_SIGNAL(result( TDEIO::Job* )),
+ this, TQ_SLOT(slotDeleteCurrentItemResult( TDEIO::Job*)) );
+ }
+}
+
+void ShowFoto::slotDeleteCurrentItemResult( TDEIO::Job * job )
+{
+ if (job->error() != 0)
+ {
+ TQString errMsg(job->errorString());
+ KMessageBox::error(this, errMsg);
+ return;
+ }
+
+ // No error, remove item in thumbbar.
+
+ Digikam::ThumbBarItem *item2remove = d->currentItem;
+ Digikam::ThumbBarItem *nextItem = 0;
+
+ for (Digikam::ThumbBarItem *item = d->thumbBar->firstItem(); item; item = item->next())
+ {
+ if (item->url().equals(item2remove->url()))
+ {
+ // Find item next to the current item
+ nextItem = item->next();
+ d->thumbBar->removeItem(item);
+ d->currentItem = 0;
+ break;
+ }
+ }
+
+ d->itemsNb = d->thumbBar->countItems();
+
+ // Disable menu actions and SideBar if no current image.
+
+ if ( d->itemsNb == 0 )
+ {
+ emit signalNoCurrentItem();
+ slotUpdateItemInfo();
+ toggleActions(false);
+ m_canvas->load(TQString(), m_IOFileSettings);
+ }
+ else
+ {
+ // If there is an image after the deleted one, make that selected.
+ if (nextItem)
+ d->thumbBar->setSelected(nextItem);
+
+ d->currentItem = d->thumbBar->currentItem();
+ slotOpenURL(d->currentItem->url());
+ }
+}
+
+void ShowFoto::slotContextMenu()
+{
+ m_contextMenu->exec(TQCursor::pos());
+}
+
+void ShowFoto::slideShow(bool startWithCurrent, Digikam::SlideShowSettings& settings)
+{
+ if (!d->thumbBar->countItems()) return;
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ settings.exifRotate = config->readBoolEntry("EXIF Rotate", true);
+ settings.fileList = d->thumbBar->itemsURLs();
+
+ int i = 0;
+ float cnt = settings.fileList.count();
+ Digikam::DMetadata meta;
+ m_cancelSlideShow = false;
+
+ m_nameLabel->progressBarMode(Digikam::StatusProgressBar::CancelProgressBarMode,
+ i18n("Preparing slideshow. Please wait..."));
+
+ for (KURL::List::Iterator it = settings.fileList.begin() ;
+ !m_cancelSlideShow && (it != settings.fileList.end()) ; ++it)
+ {
+ Digikam::SlidePictureInfo pictInfo;
+ meta.load((*it).path());
+ pictInfo.comment = meta.getImageComment();
+ pictInfo.photoInfo = meta.getPhotographInformations();
+ settings.pictInfoMap.insert(*it, pictInfo);
+
+ m_nameLabel->setProgressValue((int)((i++/cnt)*100.0));
+ kapp->processEvents();
+ }
+
+ m_nameLabel->progressBarMode(Digikam::StatusProgressBar::TextMode, TQString());
+
+ if (!m_cancelSlideShow)
+ {
+ Digikam::SlideShow *slide = new Digikam::SlideShow(settings);
+ if (startWithCurrent)
+ slide->setCurrent(d->currentItem->url());
+
+ slide->show();
+ }
+}
+
+void ShowFoto::slotRevert()
+{
+ if(!promptUserSave(d->currentItem->url()))
+ return;
+
+ m_canvas->slotRestore();
+}
+
+} // namespace ShowFoto
diff --git a/src/showfoto/showfoto.desktop b/src/showfoto/showfoto.desktop
new file mode 100644
index 00000000..a4492c35
--- /dev/null
+++ b/src/showfoto/showfoto.desktop
@@ -0,0 +1,151 @@
+[Desktop Entry]
+Name=ShowFoto
+Name[ast]=ShowFoto
+Name[bg]=ShowFoto
+Name[bs]=ShowFoto
+Name[ca]=ShowFoto
+Name[ca@valencia]=ShowFoto
+Name[cs]=ShowFoto
+Name[da]=ShowFoto
+Name[de]=ShowFoto
+Name[el]=ShowFoto
+Name[en_GB]=ShowFoto
+Name[es]=ShowFoto
+Name[et]=ShowFoto
+Name[eu]=ShowFoto
+Name[fi]=ShowFoto
+Name[fr]=ShowFoto
+Name[ga]=ShowFoto
+Name[gl]=ShowFoto
+Name[he]=ShowFoto
+Name[hne]=सो-फोटो
+Name[hr]=ShowFoto
+Name[hu]=ShowFoto
+Name[is]=ShowFoto
+Name[it]=ShowFoto
+Name[ja]=ShowFoto
+Name[km]=ShowFoto
+Name[lt]=ShowFoto
+Name[lv]=ShowFoto
+Name[mr]=शो-फोटो
+Name[nb]=ShowFoto
+Name[nds]=ShowFoto
+Name[ne]=फोटो देखाउनुहोस्
+Name[nl]=ShowFoto
+Name[pa]=ਫੋਟੋ ਵੇਖੋ
+Name[pl]=ShowFoto
+Name[pt]=ShowFoto
+Name[pt_BR]=ShowFoto
+Name[ro]=ShowFoto
+Name[ru]=ShowFoto
+Name[sk]=ShowFoto
+Name[sl]=ShowFoto
+Name[sq]=ShowFoto
+Name[sv]=Showfoto
+Name[th]=ShowFoto
+Name[tr]=ShowFoto
+Name[ug]=ShowFoto
+Name[uk]=ShowFoto
+Name[vi]=ShowFoto
+Name[xx]=xxShowFotoxx
+Name[zh_CN]=ShowFoto
+Name[zh_TW]=ShowFoto
+GenericName=Photo Viewer and Editor
+GenericName[ast]=Visor ya editor de semeyes
+GenericName[bg]=Програма за преглед и редакция на снимки
+GenericName[bs]=Preglednik i editor slika
+GenericName[ca]=Visualitzador de fotografies i editor
+GenericName[ca@valencia]=Visualitzador de fotografies i editor
+GenericName[cs]=Prohlížeč a editor fotografií
+GenericName[da]=Fotovisning og -redigering
+GenericName[de]=Fotobetrachter und Editor
+GenericName[el]=Εφαρμογή προβολής και επεξεργασίας φωτογραφιών
+GenericName[en_GB]=Photo Viewer and Editor
+GenericName[es]=Visor y editor de fotografías
+GenericName[et]=Fotode näitaja ja redaktor
+GenericName[eu]=Argazkien ikustaile eta editorea
+GenericName[fi]=Valokuvien katselu ja käsittely
+GenericName[fr]=Afficheur et éditeur de photos
+GenericName[gl]=Visor e editor de fotos
+GenericName[hne]=फोटो देखइया अउ संपादक
+GenericName[hr]=Preglednik i uređivač fotografija
+GenericName[hu]=Fényképmegjelenítő és szerkesztő
+GenericName[is]=Ljósmyndaskoðari og ritill
+GenericName[it]=Visore ed editor di foto
+GenericName[ja]=フォトビューア/エディタ
+GenericName[km]=កម្មវិធី​មើល និង​កែសម្រួល​រូបថត
+GenericName[lt]=Nuotraukų žiūryklė ir redaktorius
+GenericName[lv]=Fotogrāfiju skatītājs un redaktors
+GenericName[mr]=फोटो प्रदर्शक व संपादक
+GenericName[nb]=Fotovisning og -redigering
+GenericName[nds]=Fotokieker un -editor
+GenericName[nl]=Fotoviewer en -bewerker
+GenericName[pa]=ਫੋਟੋ ਦਰਸ਼ਕ ਅਤੇ ਐਡੀਟਰ
+GenericName[pl]=Przeglądarka i edytor zdjęć
+GenericName[pt]=Visualizador e Editor de Imagens
+GenericName[pt_BR]=Visualizador e editor de fotos
+GenericName[ro]=Vizualizator și editor de fotografii
+GenericName[ru]=Просмотр и редактирование фотографий
+GenericName[sk]=Prehliadač a editor fotografií
+GenericName[sl]=Pregledovalnik in urejevalnik fotografij
+GenericName[sv]=Fotovisning och editor
+GenericName[th]=เครื่องมือแสดงและและแก้ไขภาพถ่าย
+GenericName[tr]=Fotoğraf Görüntüleyici ve Düzenleyici
+GenericName[uk]=Переглядач і редактор фотографій
+GenericName[x-test]=xxPhoto Viewer and Editorxx
+GenericName[zh_CN]=照片查看器和编辑器
+GenericName[zh_TW]=照片檢視器與編輯器
+Comment=Manage your photographs like a professional with the power of open source
+Comment[ast]=Xestiona les tos semeyes como un profesional usando software llibre
+Comment[bg]=Обработвайте снимките си професионално с помощта на свободния софтуер
+Comment[bs]=Upravljajte fotografijama kao profesionalac snagom otvorenog izvornog koda
+Comment[ca]=Gestioneu les vostres fotografies com un professional amb la potència del programari lliure
+Comment[ca@valencia]=Gestioneu les vostres fotografies com un professional amb la potència del programari lliure
+Comment[cs]=Spravujte své fotografie jako profesionál pomocí open source
+Comment[da]=Håndtér dine fotografier som en professionel med kraften fra open source
+Comment[de]=Verwalten Sie Ihre Bilder wie ein Profi mit allen Möglichkeiten von Open Source.
+Comment[el]=Διαχειριστείτε τις φωτογραφίες σας όπως οι επαγγελματίες με τη δύναμη του ελεύθερου λογισμικού
+Comment[en_GB]=Manage your photographs like a professional with the power of open source
+Comment[es]=Gestione sus fotos como un profesional usando software libre
+Comment[et]=Fotode haldamine profina avatud lähtekoodiga tarkvara võimsust kasutades
+Comment[eu]=Kudeatu zure argazkiak profesional baten antzera iturburu irekiaren indarrarekin
+Comment[fi]=Ammattilaistasoinen avoimen lähdekoodin ohjelma valokuvien hallintaan
+Comment[fr]=Gérez vos photos comme un professionnel avec la puissance de l'« Open Source »
+Comment[gl]=Xestione as súas fotografías como un profesional coa potencia do software de fontes abertas
+Comment[hne]=ओपन सोर्स के पावर से आप मन अपन फोटो ल प्रोफेसनल जइसन मैनेज कर सकथो
+Comment[hr]=Upravljajte svojim fotografijama kao profesionalac uz moć slobodnog softvera
+Comment[hu]=Kezelje fényképeit profiként a nyílt forrás erejével
+Comment[is]=Sýslaðu með ljósmyndirnar þínar í krafti opins hugbúnaðar
+Comment[it]=Gestisci le tue fotografie come un professionista con la forza del software libero
+Comment[ja]=オープンソースの力であなたの写真をプロのように管理します
+Comment[km]=គ្រប់គ្រង​​រូបថត​របស់​អ្នក​ ដូច​ជា​អ្នក​អាជីព​ដែល​មាន​ថាម​ពល​លើ​កម្មវិធី​ប្រភព​កូដ​ចំហ
+Comment[lt]=Tvarkykite savo nuotraukas kaip profesionalas su atviro kodo jėga
+Comment[lv]=Pārvaldiet savas fotogrāfijas kā profesionālis, izmantojot atvērtā pirmkoda spēku
+Comment[mr]=ओपन सोर्सच्या बळाने तुमचे फोटो व्यावसायिकाप्रमाणे व्यवस्थापीत करा
+Comment[nb]=Håndter dine fotografier som en proff med kraften i åpen programvare
+Comment[nds]=Pleeg Dien Fotos as'n Fachmann. - Mit de Knööv vun Apenborn.
+Comment[nl]=Beheer uw foto's als een professional met de kracht van opensource
+Comment[pa]=ਆਪਣੀਆਂ ਫੋਟੋਆਂ ਨੂੰ ਇੱਕ ਪਰੋਫੈਸ਼ਨਲ ਵਾਂਗ ਓਪਨ ਸੋਰਸ ਦੀ ਮੱਦਦ ਨਾਲ ਸੰਭਾਲੋ
+Comment[pl]=Zarządzaj swoimi zdjęciami jak profesjonalista z mocą open source
+Comment[pt]=Faça a gestão das suas fotografias como um profissional, graças ao 'software' livre
+Comment[pt_BR]=Gerencie suas fotografias como um profissional, com o poder do código aberto
+Comment[ro]=Gestionați-vă fotografiile ca un profesionist profitînd de puterea aplicațiilor cu sursă deschisă
+Comment[ru]=Управляйте своей коллекцией фотографий на профессиональном уровне с помощью свободного программного обеспечения
+Comment[sk]=Spravujte svoje fotografie ako profesionál pomocou open source
+Comment[sl]=Upravljajte s svojimi fotografijami kot profesionalec z močjo odprte kode
+Comment[sq]=Menaxho fotografitë e tua si një profesionist me fuqinë e burimit të hapur
+Comment[sv]=Hantera dina fotografier som ett proffs med kraftfull öppen källkod
+Comment[th]=จัดการภาพถ่ายของคุณดั่งมืออาชีพ ด้วยพลังสร้างสรรค์ของโอเพนซอร์ส
+Comment[tr]=Açık kaynağın gücüyle fotoğraflarınızı bir profesyonel gibi düzenleyin
+Comment[ug]=ئوچۇق مەنبەلىك يۇمشاق دېتاللارنىڭ كۈچىدىن پايدىلىنىپ سۈرەتلىرىڭىزنى كەسپىي خادىملاردەك باشقۇرالايسىز
+Comment[uk]=Керуйте вашими фотографіями як професіонал за допомогою вільного програмного забезпечення
+Comment[x-test]=xxManage your photographs like a professional with the power of open sourcexx
+Comment[zh_CN]=专业照片管理 彰显开源力量
+Comment[zh_TW]=運用開放原始碼的力量像專業人士般管理您的照片
+Exec=showfoto %i -caption "%c" %U
+Icon=showfoto
+Type=Application
+MimeType=image/gif;image/x-xpm;image/x-xbm;image/jpeg;image/png;image/webp;image/tiff;image/x-bmp;image/x-psd;image/x-eim;image/x-portable-bitmap;image/x-portable-pixmap;image/x-portable-greymap;image/x-raw;
+Terminal=false
+Categories=Qt;TDE;Graphics;
+X-DocPath=showfoto/index.html
diff --git a/src/showfoto/showfoto.h b/src/showfoto/showfoto.h
new file mode 100644
index 00000000..8bd3618e
--- /dev/null
+++ b/src/showfoto/showfoto.h
@@ -0,0 +1,135 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-11-22
+ * Description : stand alone digiKam image editor GUI
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2006 by Tom Albers <tomalbers@kde.nl>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef SHOWFOTO_H
+#define SHOWFOTO_H
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "editorwindow.h"
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace Digikam
+{
+class SlideshowSettings;
+}
+
+namespace ShowFoto
+{
+
+class ShowFotoPriv;
+
+class ShowFoto : public Digikam::EditorWindow
+{
+ TQ_OBJECT
+
+
+public:
+
+ ShowFoto(const KURL::List& urlList);
+ ~ShowFoto();
+
+ virtual void show();
+ bool setup(bool iccSetupPage=false);
+
+private:
+
+ bool queryClose();
+ bool queryExit();
+
+ void setupActions();
+ void setupConnections();
+ void setupUserArea();
+
+ void readSettings();
+ void saveSettings();
+ void applySettings();
+
+ void toggleActions(bool val);
+
+ void toggleGUI2FullScreen();
+
+ void toggleNavigation(int index);
+
+ bool save();
+ bool saveAs();
+ void finishSaving(bool success);
+
+ void saveIsComplete();
+ void saveAsIsComplete();
+
+ void slideShow(bool startWithCurrent, Digikam::SlideShowSettings& settings);
+
+ void openFolder(const KURL& url);
+
+ Digikam::Sidebar* rightSideBar() const;
+
+private slots:
+
+ void slotForward();
+ void slotBackward();
+ void slotLast();
+ void slotFirst();
+ void slotFilePrint();
+
+ void slotOpenFile();
+ void slotOpenURL(const KURL& url);
+ void slotOpenFolder(const KURL& url);
+ void slotOpenFilesInFolder();
+ void slotDeleteCurrentItem();
+
+ void slotToggleShowBar();
+ void slotChangeBCG();
+
+ void slotChanged();
+ void slotUndoStateChanged(bool, bool, bool);
+ void slotUpdateItemInfo();
+
+ void slotDeleteCurrentItemResult( TDEIO::Job * job );
+
+ void slotLoadingStarted(const TQString &filename);
+ void slotLoadingFinished(const TQString &filename, bool success);
+ void slotSavingStarted(const TQString &filename);
+
+ void slotContextMenu();
+ void slotRevert();
+
+private:
+
+ ShowFotoPriv* d;
+};
+
+} // namespace ShowFoto
+
+#endif /* SHOWFOTO_H */
diff --git a/src/showfoto/showfotoui.rc b/src/showfoto/showfotoui.rc
new file mode 100644
index 00000000..7e8e497d
--- /dev/null
+++ b/src/showfoto/showfotoui.rc
@@ -0,0 +1,131 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<gui version="29" name="showfoto" >
+
+<MenuBar>
+
+ <Menu name="file" ><text>&amp;File</text>
+ <Action name="showfoto_open_file" />
+ <Action name="showfoto_open_folder" />
+ <Separator/>
+ <Action name="editorwindow_backward" />
+ <Action name="editorwindow_forward" />
+ <Separator/>
+ <Action name="editorwindow_first" />
+ <Action name="editorwindow_last" />
+ <Separator/>
+ <Action name="editorwindow_print" />
+ <Separator/>
+ <Action name="editorwindow_save" />
+ <Action name="editorwindow_saveas" />
+ <Action name="editorwindow_revert" />
+ <Separator/>
+ <Action name="editorwindow_delete" />
+ <Separator/>
+ <Action name="showfoto_quit" />
+ </Menu>
+
+ <Menu name="Edit" ><text>&amp;Edit</text>
+ <Action name="editorwindow_copy" />
+ <Separator/>
+ <Action name="editorwindow_undo" />
+ <Action name="editorwindow_redo" />
+ <Separator/>
+ <Action name="editorwindow_selectAll" />
+ <Action name="editorwindow_selectNone" />
+ </Menu>
+
+ <Menu name="View" ><text>&amp;View</text>
+ <Action name="editorwindow_fullscreen" />
+ <Action name="editorwindow_slideshow" />
+ <Separator/>
+ <Action name="editorwindow_zoomplus" />
+ <Action name="editorwindow_zoomminus" />
+ <Action name="editorwindow_zoomto100percents" />
+ <Action name="editorwindow_zoomfit2window" />
+ <Action name="editorwindow_zoomfit2select" />
+ <Separator/>
+ <Action name="shofoto_showthumbs" />
+ <Separator/>
+ <Action name="editorwindow_underexposure" />
+ <Action name="editorwindow_overexposure" />
+ <Action name="editorwindow_cmview" />
+ </Menu>
+
+ <Menu name="Color" ><text>&amp;Color</text>
+ </Menu>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ <ActionList name="showfoto_bcg" />
+ </Menu>
+
+ <Menu name="Transform" ><text>Tra&amp;nsform</text>
+ <Action name="editorwindow_rotate_left" />
+ <Action name="editorwindow_rotate_right" />
+ <Separator/>
+ <Action name="editorwindow_flip_horiz" />
+ <Action name="editorwindow_flip_vert" />
+ <Separator/>
+ <Action name="editorwindow_crop" />
+ <Action name="editorwindow_resize" />
+ </Menu>
+
+ <Menu name="Decorate" ><text>&amp;Decorate</text>
+ </Menu>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ </Menu>
+
+ <Menu name="help" ><text>&amp;Help</text>
+ <Action name="editorwindow_rawcameralist"/>
+ <Action name="editorwindow_donatemoney" />
+ <Action name="editorwindow_contribute" />
+ </Menu>
+
+ <Merge/>
+
+ <Menu name="settings" noMerge="1"><Text>&amp;Settings</Text>
+ <Merge name="StandardToolBarMenuHandler" />
+ <Action name="options_show_menubar"/>
+ <Action name="options_show_statusbar"/>
+ <Action name="options_show_toolbar"/>
+ <Separator/>
+ <Action name="theme_menu" />
+ <Action name="options_configure_keybinding" />
+ <Action name="options_configure_toolbars" />
+ <Action name="options_configure" />
+ </Menu>
+
+</MenuBar>
+
+<ToolBar name="ToolBar"><text>Main Toolbar</text>
+ <Action name="editorwindow_first" />
+ <Action name="editorwindow_backward" />
+ <Action name="editorwindow_forward" />
+ <Action name="editorwindow_last" />
+ <Separator/>
+ <Action name="showfoto_open_file" />
+ <Action name="showfoto_open_folder" />
+ <Action name="editorwindow_save" />
+ <Action name="editorwindow_saveas" />
+ <Action name="editorwindow_undo" />
+ <Action name="editorwindow_redo" />
+ <Action name="editorwindow_revert" />
+ <Separator/>
+ <Action name="editorwindow_zoomplus" />
+ <Action name="editorwindow_zoomto" />
+ <Action name="editorwindow_zoomminus" />
+ <Action name="editorwindow_zoomfit2window" />
+ <Action name="editorwindow_zoomfit2select" />
+ <Separator/>
+ <Action name="editorwindow_rotate_left" />
+ <Action name="editorwindow_rotate_right" />
+ <Action name="editorwindow_crop" />
+ <Separator/>
+ <Action name="editorwindow_fullscreen" />
+ <Action name="editorwindow_slideshow" />
+ <Merge />
+</ToolBar>
+
+<ActionProperties/>
+
+</gui>
diff --git a/src/tdeioslave/Makefile.am b/src/tdeioslave/Makefile.am
new file mode 100644
index 00000000..28b8a2ff
--- /dev/null
+++ b/src/tdeioslave/Makefile.am
@@ -0,0 +1,72 @@
+METASOURCES = AUTO
+
+if with_included_sqlite3
+ LIB_SQLITE3_LOCAL = $(top_builddir)/src/libs/sqlite3/libsqlite3.la
+ SQLITE3_INCLUDES = -I$(top_srcdir)/src/libs/sqlite3
+endif
+
+INCLUDES = -I$(top_srcdir)/src/libs/jpegutils \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/digikam \
+ $(SQLITE3_INCLUDES) \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+kde_module_LTLIBRARIES = tdeio_digikamthumbnail.la tdeio_digikamtags.la \
+ tdeio_digikamalbums.la tdeio_digikamdates.la \
+ tdeio_digikamsearch.la
+
+protocol_DATA = digikamthumbnail.protocol digikamtags.protocol \
+ digikamalbums.protocol digikamdates.protocol \
+ digikamsearch.protocol
+
+protocoldir = $(kde_servicesdir)
+
+# NOTE: if local libsqlite3 is used LIB_SQLITE3 is null.
+# if shared libsqlite3 is used LIB_SQLITE3_LOCAL is null.
+
+# -- digikam TDEIO image thumbnails generator ----------------------------------------
+
+tdeio_digikamthumbnail_la_SOURCES = digikamthumbnail.cpp
+
+tdeio_digikamthumbnail_la_LIBADD = $(LIB_TDEIO) $(LIBJPEG) $(LIBPNG) \
+ $(top_builddir)/src/libs/jpegutils/libjpegutils.la \
+ $(top_builddir)/src/libs/dimg/libdimg.la
+
+tdeio_digikamthumbnail_la_LDFLAGS = -module -avoid-version $(KDE_PLUGIN) $(all_libraries) -ltdecore -ltdeui $(LIB_TQT) -ltdefx
+
+# -- digikam tags TDEIO --------------------------------------------------------------
+
+tdeio_digikamtags_la_SOURCES = digikamtags.cpp sqlitedb.cpp
+
+tdeio_digikamtags_la_LIBADD = $(LIB_TDEIO) $(LIB_SQLITE3) $(LIB_SQLITE3_LOCAL)
+
+tdeio_digikamtags_la_LDFLAGS = -module -avoid-version $(all_libraries) $(KDE_PLUGIN) -ltdecore -ltdeui $(LIB_TQT) -ltdefx
+
+# -- digikam albums TDEIO ------------------------------------------------------------
+
+tdeio_digikamalbums_la_SOURCES = digikamalbums.cpp sqlitedb.cpp
+
+tdeio_digikamalbums_la_LIBADD = $(top_builddir)/src/libs/jpegutils/libjpegutils.la \
+ $(top_builddir)/src/libs/dimg/libdimg.la \
+ $(LIB_TDEIO) $(LIB_SQLITE3) $(LIB_SQLITE3_LOCAL)
+
+
+tdeio_digikamalbums_la_LDFLAGS = -module -avoid-version $(all_libraries) $(KDE_PLUGIN) -ltdecore -ltdeui $(LIB_TQT) -ltdefx
+
+# -- digikam dates TDEIO -------------------------------------------------------------
+
+tdeio_digikamdates_la_SOURCES = digikamdates.cpp sqlitedb.cpp
+
+tdeio_digikamdates_la_LIBADD = $(LIB_TDEIO) $(LIB_SQLITE3) $(LIB_SQLITE3_LOCAL)
+
+tdeio_digikamdates_la_LDFLAGS = -module -avoid-version $(all_libraries) $(KDE_PLUGIN) -ltdecore -ltdeui $(LIB_TQT) -ltdefx
+
+# -- digikam search TDEIO ------------------------------------------------------------
+
+tdeio_digikamsearch_la_SOURCES = digikamsearch.cpp sqlitedb.cpp
+
+tdeio_digikamsearch_la_LIBADD = $(LIB_TDEIO) $(LIB_SQLITE3) $(LIB_SQLITE3_LOCAL)
+
+tdeio_digikamsearch_la_LDFLAGS = -module -avoid-version $(all_libraries) $(KDE_PLUGIN) -ltdecore -ltdeui $(LIB_TQT) -ltdefx
diff --git a/src/tdeioslave/digikamalbums.cpp b/src/tdeioslave/digikamalbums.cpp
new file mode 100644
index 00000000..c16080af
--- /dev/null
+++ b/src/tdeioslave/digikamalbums.cpp
@@ -0,0 +1,1969 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a tdeio-slave to process file operations on
+ * digiKam albums.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * Lots of the file io code is copied from KDE file tdeioslave.
+ * Copyright for the KDE file tdeioslave follows:
+ * Copyright (C) 2000-2002 Stephan Kulow <coolo@kde.org>
+ * Copyright (C) 2000-2002 David Faure <faure@kde.org>
+ * Copyright (C) 2000-2002 Waldo Bastian <bastian@kde.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#define MAX_IPC_SIZE (1024*32)
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <utime.h>
+}
+
+// C++ includes.
+
+#include <cstdlib>
+#include <cstdio>
+#include <ctime>
+#include <cerrno>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+#include <tqdatastream.h>
+#include <tqregexp.h>
+#include <tqdir.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <kinstance.h>
+#include <tdefilemetainfo.h>
+#include <kmimetype.h>
+#include <kdebug.h>
+#include <tdeio/global.h>
+#include <tdeio/ioslave_defaults.h>
+#include <klargefile.h>
+#include <tdeversion.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "dmetadata.h"
+#include "sqlitedb.h"
+#include "digikam_export.h"
+#include "digikamalbums.h"
+
+tdeio_digikamalbums::tdeio_digikamalbums(const TQCString &pool_socket,
+ const TQCString &app_socket)
+ : SlaveBase("tdeio_digikamalbums", pool_socket, app_socket)
+{
+}
+
+tdeio_digikamalbums::~tdeio_digikamalbums()
+{
+}
+
+static TQValueList<TQRegExp> makeFilterList( const TQString &filter )
+{
+ TQValueList<TQRegExp> regExps;
+ if ( filter.isEmpty() )
+ return regExps;
+
+ TQChar sep( ';' );
+ int i = filter.find( sep, 0 );
+ if ( i == -1 && filter.find( ' ', 0 ) != -1 )
+ sep = TQChar( ' ' );
+
+ TQStringList list = TQStringList::split( sep, filter );
+ TQStringList::Iterator it = list.begin();
+ while ( it != list.end() ) {
+ regExps << TQRegExp( (*it).stripWhiteSpace(), false, true );
+ ++it;
+ }
+ return regExps;
+}
+
+static bool matchFilterList( const TQValueList<TQRegExp>& filters,
+ const TQString &fileName )
+{
+ TQValueList<TQRegExp>::ConstIterator rit = filters.begin();
+ while ( rit != filters.end() ) {
+ if ( (*rit).exactMatch(fileName) )
+ return true;
+ ++rit;
+ }
+ return false;
+}
+
+void tdeio_digikamalbums::special(const TQByteArray& data)
+{
+ bool folders = (metaData("folders") == "yes");
+
+ TQString libraryPath;
+ KURL kurl;
+ TQString url;
+ TQString urlWithTrailingSlash;
+ TQString filter;
+ int getDimensions;
+ int scan = 0;
+ int recurseAlbums;
+ int recurseTags;
+
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> libraryPath;
+ ds >> kurl;
+ ds >> filter;
+ ds >> getDimensions;
+ ds >> recurseAlbums;
+ ds >> recurseTags;
+ if (!ds.atEnd())
+ ds >> scan;
+
+ libraryPath = TQDir::cleanDirPath(libraryPath);
+
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(libraryPath);
+ }
+
+ url = kurl.path();
+
+ if (scan)
+ {
+ scanAlbum(url);
+ finished();
+ return;
+ }
+
+ TQValueList<TQRegExp> regex = makeFilterList(filter);
+ TQByteArray ba;
+
+ if (folders) // Special mode to stats all album items
+ {
+ TQMap<int, int> albumsStatMap;
+ TQStringList values, allAbumIDs;
+ int albumID;
+
+ // initialize allAbumIDs with all existing albums from db to prevent
+ // wrong album image counters
+ m_sqlDB.execSql(TQString("SELECT id from Albums"), &allAbumIDs);
+
+ for ( TQStringList::iterator it = allAbumIDs.begin(); it != allAbumIDs.end(); ++it)
+ {
+ albumID = (*it).toInt();
+ albumsStatMap.insert(albumID, 0);
+ }
+
+ // now we can count the images assigned to albums
+ m_sqlDB.execSql(TQString("SELECT dirid, Images.name FROM Images "
+ "WHERE Images.dirid IN (SELECT DISTINCT id FROM Albums)"), &values);
+
+ for ( TQStringList::iterator it = values.begin(); it != values.end(); )
+ {
+ albumID = (*it).toInt();
+ ++it;
+
+ if ( matchFilterList( regex, *it ) )
+ {
+ TQMap<int, int>::iterator it2 = albumsStatMap.find(albumID);
+ if ( it2 == albumsStatMap.end() )
+ albumsStatMap.insert(albumID, 1);
+ else
+ albumsStatMap.replace(albumID, it2.data() + 1);
+ }
+
+ ++it;
+ }
+
+ TQDataStream os(ba, IO_WriteOnly);
+ os << albumsStatMap;
+ }
+ else
+ {
+ TQStringList albumvalues;
+ if (recurseAlbums)
+ {
+ // Search for albums and sub-albums:
+ // For this, get the path with a trailing "/".
+ // Otherwise albums on the same level like "Paris", "Paris 2006",
+ // would be found in addition to "Paris/*".
+ urlWithTrailingSlash = kurl.path(1);
+
+ m_sqlDB.execSql(TQString("SELECT DISTINCT id, url FROM Albums WHERE url='%1' OR url LIKE '%2\%';")
+ .arg(escapeString(url)).arg(escapeString(urlWithTrailingSlash)), &albumvalues);
+ }
+ else
+ {
+ // Search for albums
+
+ m_sqlDB.execSql(TQString("SELECT DISTINCT id, url FROM Albums WHERE url='%1';")
+ .arg(escapeString(url)), &albumvalues);
+ }
+
+ TQDataStream* os = new TQDataStream(ba, IO_WriteOnly);
+
+ TQString base;
+ TQ_LLONG id;
+ TQString name;
+ TQString date;
+ TQSize dims;
+
+ struct stat stbuf;
+
+ TQStringList values;
+ TQString albumurl;
+ int albumid;
+
+ // Loop over all albums:
+ int count = 0 ;
+ for (TQStringList::iterator albumit = albumvalues.begin(); albumit != albumvalues.end();)
+ {
+ albumid = (*albumit).toLongLong();
+ ++albumit;
+ albumurl = *albumit;
+ ++albumit;
+
+ base = libraryPath + albumurl + '/';
+
+ values.clear();
+ m_sqlDB.execSql(TQString("SELECT id, name, datetime FROM Images "
+ "WHERE dirid = %1;")
+ .arg(albumid), &values);
+
+ // Loop over all images in each album (specified by its albumid).
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ id = (*it).toLongLong();
+ ++it;
+ name = *it;
+ ++it;
+ date = *it;
+ ++it;
+
+ if (!matchFilterList(regex, name))
+ continue;
+
+ if (::stat(TQFile::encodeName(base + name), &stbuf) != 0)
+ continue;
+
+ dims = TQSize();
+ if (getDimensions)
+ {
+ TQFileInfo fileInfo(base + name);
+#if KDCRAW_VERSION < 0x000106
+ TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
+#endif
+ TQString ext = fileInfo.extension(false).upper();
+
+ if (!ext.isEmpty() && rawFilesExt.upper().contains(ext))
+ {
+ Digikam::DMetadata metaData(base + name);
+ dims = metaData.getImageDimensions();
+ }
+ else
+ {
+ KFileMetaInfo metaInfo(base + name);
+ if (metaInfo.isValid())
+ {
+ if (metaInfo.containsGroup("Jpeg EXIF Data"))
+ {
+ dims = metaInfo.group("Jpeg EXIF Data").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("General"))
+ {
+ dims = metaInfo.group("General").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("Technical"))
+ {
+ dims = metaInfo.group("Technical").
+ item("Dimensions").value().toSize();
+ }
+ }
+ }
+ }
+
+ *os << id;
+ *os << albumid;
+ *os << name;
+ *os << date;
+ *os << static_cast<size_t>(stbuf.st_size);
+ *os << dims;
+
+ count++;
+
+ // Send images in batches of 200.
+ if (count > 200)
+ {
+ delete os;
+ os = 0;
+
+ SlaveBase::data(ba);
+ ba.resize(0);
+
+ count = 0;
+ os = new TQDataStream(ba, IO_WriteOnly);
+ }
+ }
+ count++;
+ }
+ }
+
+ SlaveBase::data(ba);
+
+ finished();
+}
+
+static int write_all(int fd, const char *buf, size_t len)
+{
+ while (len > 0)
+ {
+ ssize_t written = write(fd, buf, len);
+ if (written < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ buf += written;
+ len -= written;
+ }
+ return 0;
+}
+
+void tdeio_digikamalbums::get( const KURL& url )
+{
+// Code duplication from file:// ioslave
+ kdDebug() << k_funcinfo << " : " << url << endl;
+
+ // get the libraryPath
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // no need to open the db. we don't need to read/write to it
+
+ TQCString path(TQFile::encodeName(libraryPath + url.path()));
+ KDE_struct_stat buff;
+ if ( KDE_stat( path.data(), &buff ) == -1 )
+ {
+ if ( errno == EACCES )
+ error( TDEIO::ERR_ACCESS_DENIED, url.url() );
+ else
+ error( TDEIO::ERR_DOES_NOT_EXIST, url.url() );
+ return;
+ }
+
+ if ( S_ISDIR( buff.st_mode ) )
+ {
+ error( TDEIO::ERR_IS_DIRECTORY, url.url() );
+ return;
+ }
+
+ if ( !S_ISREG( buff.st_mode ) )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.url() );
+ return;
+ }
+
+ int fd = KDE_open( path.data(), O_RDONLY);
+ if ( fd < 0 )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.url() );
+ return;
+ }
+
+ // Determine the mimetype of the file to be retrieved, and emit it.
+ // This is mandatory in all slaves (for KRun/BrowserRun to work).
+ KMimeType::Ptr mt = KMimeType::findByURL( libraryPath + url.path(), buff.st_mode,
+ true);
+ emit mimeType( mt->name() );
+
+ totalSize( buff.st_size );
+
+ char buffer[ MAX_IPC_SIZE ];
+ TQByteArray array;
+ TDEIO::filesize_t processed_size = 0;
+
+ while (1)
+ {
+ int n = ::read( fd, buffer, MAX_IPC_SIZE );
+ if (n == -1)
+ {
+ if (errno == EINTR)
+ continue;
+ error( TDEIO::ERR_COULD_NOT_READ, url.url());
+ close(fd);
+ return;
+ }
+ if (n == 0)
+ break; // Finished
+
+ array.setRawData(buffer, n);
+ data( array );
+ array.resetRawData(buffer, n);
+
+ processed_size += n;
+ processedSize( processed_size );
+ }
+
+ data( TQByteArray() );
+ close( fd );
+
+ processedSize( buff.st_size );
+ finished();
+}
+
+void tdeio_digikamalbums::put(const KURL& url, int permissions, bool overwrite, bool /*resume*/)
+{
+// Code duplication from file:// ioslave
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ // get the libraryPath
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // open the db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ // build the album list
+ buildAlbumList();
+
+ // get the parent album
+ AlbumInfo album = findAlbum(url.directory());
+ if (album.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(url.directory()));
+ return;
+ }
+
+
+ TQString dest = libraryPath + url.path();
+ TQCString _dest( TQFile::encodeName(dest));
+
+ // check if the original file exists and we are not allowed to overwrite it
+ KDE_struct_stat buff;
+ bool origExists = (KDE_lstat( _dest.data(), &buff ) != -1);
+ if ( origExists && !overwrite)
+ {
+ if (S_ISDIR(buff.st_mode))
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, url.url() );
+ else
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, url.url() );
+ return;
+ }
+
+ // get the permissions we are supposed to set
+ mode_t initialPerms;
+ if (permissions != -1)
+ initialPerms = permissions | S_IWUSR | S_IRUSR;
+ else
+ initialPerms = 0666;
+
+ // open the destination file
+ int fd = KDE_open(_dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialPerms);
+ if ( fd < 0 )
+ {
+ kdWarning() << "####################### COULD NOT OPEN " << dest << endl;
+ if ( errno == EACCES )
+ error( TDEIO::ERR_WRITE_ACCESS_DENIED, url.url() );
+ else
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, url.url() );
+ return;
+ }
+
+ int result;
+
+ // Loop until we get 0 (end of data)
+ do
+ {
+ TQByteArray buffer;
+ dataReq();
+ result = readData( buffer );
+
+ if (result >= 0)
+ {
+ if (write_all( fd, buffer.data(), buffer.size()))
+ {
+ if ( errno == ENOSPC ) // disk full
+ {
+ error( TDEIO::ERR_DISK_FULL, url.url());
+ result = -1;
+ }
+ else
+ {
+ kdWarning() << "Couldn't write. Error:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, url.url());
+ result = -1;
+ }
+ }
+ }
+ }
+ while ( result > 0 );
+
+ // An error occurred deal with it.
+ if (result < 0)
+ {
+ kdDebug() << "Error during 'put'. Aborting." << endl;
+
+ close(fd);
+ remove(_dest);
+ return;
+ }
+
+ // close the file
+ if ( close(fd) )
+ {
+ kdWarning() << "Error when closing file descriptor:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, url.url());
+ return;
+ }
+
+ // set final permissions
+ if ( permissions != -1 )
+ {
+ if (::chmod(_dest.data(), permissions) != 0)
+ {
+ // couldn't chmod. Eat the error if the filesystem apparently doesn't support it.
+ if ( TDEIO::testFileSystemFlag( _dest, TDEIO::SupportsChmod ) )
+ warning( i18n( "Could not change permissions for\n%1" ).arg( url.url() ) );
+ }
+ }
+
+ // set modification time
+ const TQString mtimeStr = metaData( "modified" );
+ if ( !mtimeStr.isEmpty() ) {
+ TQDateTime dt = TQDateTime::fromString( mtimeStr, TQt::ISODate );
+ if ( dt.isValid() ) {
+ KDE_struct_stat dest_statbuf;
+ if (KDE_stat( _dest.data(), &dest_statbuf ) == 0) {
+ struct utimbuf utbuf;
+ utbuf.actime = dest_statbuf.st_atime; // access time, unchanged
+ utbuf.modtime = dt.toTime_t(); // modification time
+ kdDebug() << k_funcinfo << "setting modtime to " << utbuf.modtime << endl;
+ utime( _dest.data(), &utbuf );
+ }
+ }
+
+ }
+
+ // First check if the file is already in database
+ if (!findImage(album.id, url.fileName()))
+ {
+ // Now insert the file into the database
+ addImage(album.id, m_libraryPath + url.path());
+ }
+
+ // We have done our job => finish
+ finished();
+}
+
+void tdeio_digikamalbums::copy( const KURL &src, const KURL &dst, int mode, bool overwrite )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << "Src: " << src.path() << ", Dst: " << dst.path() << endl;
+
+ // get the album library path
+ TQString libraryPath = src.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // check that the src and dst album library paths match
+ TQString dstLibraryPath = dst.user();
+ if (libraryPath != dstLibraryPath)
+ {
+ error(TDEIO::ERR_UNKNOWN,
+ TQString("Source and Destination have different Album Library Paths. ") +
+ TQString("Src: ") + src.user() +
+ TQString(", Dest: ") + dst.user());
+ return;
+ }
+
+ // open the db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ // build the album list
+ buildAlbumList();
+
+ // find the src parent album
+ AlbumInfo srcAlbum = findAlbum(src.directory());
+ if (srcAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, TQString("Source album %1 not found in database")
+ .arg(src.directory()));
+ return;
+ }
+
+ // find the dst parent album
+ AlbumInfo dstAlbum = findAlbum(dst.directory());
+ if (dstAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, TQString("Destination album %1 not found in database")
+ .arg(dst.directory()));
+ return;
+ }
+
+ // if the filename is .digikam_properties, we have been asked to copy the
+ // metadata of the src album to the dst album
+ if (src.fileName() == ".digikam_properties")
+ {
+ // no duplication in AlbumDB?
+ // copy metadata of album to destination album
+ m_sqlDB.execSql( TQString("UPDATE Albums SET date='%1', caption='%2', "
+ "collection='%3', icon=%4 ")
+ .arg(srcAlbum.date.toString(TQt::ISODate),
+ escapeString(srcAlbum.caption),
+ escapeString(srcAlbum.collection),
+ TQString::number(srcAlbum.icon)) +
+ TQString( " WHERE id=%1" )
+ .arg(dstAlbum.id) );
+ finished();
+ return;
+ }
+
+ TQCString _src( TQFile::encodeName(libraryPath + src.path()));
+ TQCString _dst( TQFile::encodeName(libraryPath + dst.path()));
+
+ // stat the src file
+ KDE_struct_stat buff_src;
+ if ( KDE_stat( _src.data(), &buff_src ) == -1 )
+ {
+ if ( errno == EACCES )
+ error( TDEIO::ERR_ACCESS_DENIED, src.url() );
+ else
+ error( TDEIO::ERR_DOES_NOT_EXIST, src.url() );
+ return;
+ }
+
+ // bail out if its a directory
+ if ( S_ISDIR( buff_src.st_mode ) )
+ {
+ error( TDEIO::ERR_IS_DIRECTORY, src.url() );
+ return;
+ }
+
+ // bail out if its a socket or fifo
+ if ( S_ISFIFO( buff_src.st_mode ) || S_ISSOCK ( buff_src.st_mode ) )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.url() );
+ return;
+ }
+
+ // stat the dst file
+ KDE_struct_stat buff_dest;
+ bool dest_exists = ( KDE_lstat( _dst.data(), &buff_dest ) != -1 );
+ if ( dest_exists )
+ {
+ // bail out if its a directory
+ if (S_ISDIR(buff_dest.st_mode))
+ {
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, dst.url() );
+ return;
+ }
+
+ // if !overwrite bail out
+ if (!overwrite)
+ {
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, dst.url() );
+ return;
+ }
+
+ // If the destination is a symlink and overwrite is true,
+ // remove the symlink first to prevent the scenario where
+ // the symlink actually points to current source!
+ if (overwrite && S_ISLNK(buff_dest.st_mode))
+ {
+ remove( _dst.data() );
+ }
+ }
+
+ // now open the src file
+ int src_fd = KDE_open( _src.data(), O_RDONLY);
+ if ( src_fd < 0 )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.path() );
+ return;
+ }
+
+ // get the permissions we are supposed to set
+ mode_t initialMode;
+ if (mode != -1)
+ initialMode = mode | S_IWUSR;
+ else
+ initialMode = 0666;
+
+ // open the destination file
+ int dest_fd = KDE_open(_dst.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode);
+ if ( dest_fd < 0 )
+ {
+ kdDebug() << "###### COULD NOT WRITE " << dst.url() << endl;
+ if ( errno == EACCES )
+ {
+ error( TDEIO::ERR_WRITE_ACCESS_DENIED, dst.url() );
+ }
+ else
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, dst.url() );
+ }
+ close(src_fd);
+ return;
+ }
+
+ // emit the total size for copying
+ totalSize( buff_src.st_size );
+
+ TDEIO::filesize_t processed_size = 0;
+ char buffer[ MAX_IPC_SIZE ];
+ int n;
+
+ while (1)
+ {
+ // read in chunks of MAX_IPC_SIZE
+ n = ::read( src_fd, buffer, MAX_IPC_SIZE );
+
+ if (n == -1)
+ {
+ if (errno == EINTR)
+ continue;
+ error( TDEIO::ERR_COULD_NOT_READ, src.path());
+ close(src_fd);
+ close(dest_fd);
+ return;
+ }
+
+ // Finished ?
+ if (n == 0)
+ break;
+
+ // write to the destination file
+ if (write_all( dest_fd, buffer, n))
+ {
+ close(src_fd);
+ close(dest_fd);
+
+ if ( errno == ENOSPC ) // disk full
+ {
+ error( TDEIO::ERR_DISK_FULL, dst.url());
+ remove( _dst.data() );
+ }
+ else
+ {
+ kdWarning() << "Couldn't write[2]. Error:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, dst.url());
+ }
+ return;
+ }
+
+ processedSize( processed_size );
+ }
+
+
+ close( src_fd );
+
+ if (close( dest_fd))
+ {
+ kdWarning() << "Error when closing file descriptor[2]:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, dst.url());
+ return;
+ }
+
+ // set final permissions
+ if ( mode != -1 )
+ {
+ if (::chmod(_dst.data(), mode) != 0)
+ {
+ // Eat the error if the filesystem apparently doesn't support chmod.
+ if ( TDEIO::testFileSystemFlag( _dst, TDEIO::SupportsChmod ) )
+ warning( i18n( "Could not change permissions for\n%1" ).arg( dst.url() ) );
+ }
+ }
+
+ // copy access and modification time
+ struct utimbuf ut;
+ ut.actime = buff_src.st_atime;
+ ut.modtime = buff_src.st_mtime;
+ if ( ::utime( _dst.data(), &ut ) != 0 )
+ {
+ kdWarning() << TQString::fromLatin1("Couldn't preserve access and modification time for\n%1")
+ .arg( dst.url() ) << endl;
+ }
+
+ // now copy the metadata over
+ copyImage(srcAlbum.id, src.fileName(), dstAlbum.id, dst.fileName());
+
+ processedSize( buff_src.st_size );
+ finished();
+}
+
+void tdeio_digikamalbums::rename( const KURL& src, const KURL& dst, bool overwrite )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << "Src: " << src << ", Dst: " << dst << endl;
+
+ // if the filename is .digikam_properties fake that we renamed it
+ if (src.fileName() == ".digikam_properties")
+ {
+ finished();
+ return;
+ }
+
+ TQString libraryPath = src.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ TQString dstLibraryPath = dst.user();
+ if (libraryPath != dstLibraryPath)
+ {
+ error(TDEIO::ERR_UNKNOWN,
+ i18n("Source and Destination have different Album Library Paths.\n"
+ "Source: %1\n"
+ "Destination: %2")
+ .arg(src.user())
+ .arg(dst.user()));
+ return;
+ }
+
+ // open album db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ TQCString csrc( TQFile::encodeName(libraryPath + src.path()));
+ TQCString cdst( TQFile::encodeName(libraryPath + dst.path()));
+
+ // stat the source file/folder
+ KDE_struct_stat buff_src;
+ if ( KDE_stat( csrc.data(), &buff_src ) == -1 )
+ {
+ if ( errno == EACCES )
+ error( TDEIO::ERR_ACCESS_DENIED, src.url() );
+ else
+ error( TDEIO::ERR_DOES_NOT_EXIST, src.url() );
+ return;
+ }
+
+ // stat the destination file/folder
+ KDE_struct_stat buff_dest;
+ bool dest_exists = ( KDE_stat( cdst.data(), &buff_dest ) != -1 );
+ if ( dest_exists )
+ {
+ if (S_ISDIR(buff_dest.st_mode))
+ {
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, dst.url() );
+ return;
+ }
+
+ if (!overwrite)
+ {
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, dst.url() );
+ return;
+ }
+ }
+
+
+ // build album list
+ buildAlbumList();
+
+ AlbumInfo srcAlbum, dstAlbum;
+
+ // check if we are renaming an album or a image
+ bool renamingAlbum = S_ISDIR(buff_src.st_mode);
+
+ if (renamingAlbum)
+ {
+ srcAlbum = findAlbum(src.path());
+ if (srcAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(src.url()));
+ return;
+ }
+ }
+ else
+ {
+ srcAlbum = findAlbum(src.directory());
+ if (srcAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(src.directory()));
+ return;
+ }
+
+ dstAlbum = findAlbum(dst.directory());
+ if (dstAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Destination album %1 not found in database")
+ .arg(dst.directory()));
+ return;
+ }
+ }
+
+ // actually rename the file/folder
+ if ( ::rename(csrc.data(), cdst.data()))
+ {
+ if (( errno == EACCES ) || (errno == EPERM))
+ {
+ TQFileInfo toCheck(libraryPath + src.path());
+ if (!toCheck.isWritable())
+ error( TDEIO::ERR_CANNOT_RENAME_ORIGINAL, src.path() );
+ else
+ error( TDEIO::ERR_ACCESS_DENIED, dst.path() );
+ }
+ else if (errno == EXDEV)
+ {
+ error( TDEIO::ERR_UNSUPPORTED_ACTION, i18n("This file/folder is on a different "
+ "filesystem through symlinks. "
+ "Moving/Renaming files between "
+ "them is currently unsupported "));
+ }
+ else if (errno == EROFS)
+ { // The file is on a read-only filesystem
+ error( TDEIO::ERR_CANNOT_DELETE, src.url() );
+ }
+ else {
+ error( TDEIO::ERR_CANNOT_RENAME, src.url() );
+ }
+ return;
+ }
+
+ // renaming done. now update the database
+ if (renamingAlbum)
+ {
+ renameAlbum(srcAlbum.url, dst.path());
+ }
+ else
+ {
+ renameImage(srcAlbum.id, src.fileName(),
+ dstAlbum.id, dst.fileName());
+ }
+
+ finished();
+}
+
+void tdeio_digikamalbums::stat( const KURL& url )
+{
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ TDEIO::UDSEntry entry;
+ if (!createUDSEntry(libraryPath + url.path(), entry))
+ {
+ error(TDEIO::ERR_DOES_NOT_EXIST, url.path(-1));
+ return;
+ }
+
+ statEntry(entry);
+ finished();
+}
+
+void tdeio_digikamalbums::listDir( const KURL& url )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.path() << endl;
+
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ kdWarning() << "Album Library Path not supplied to tdeioslave" << endl;
+ return;
+ }
+
+ KDE_struct_stat stbuf;
+ TQString path = libraryPath + url.path();
+ if (KDE_stat(TQFile::encodeName(path), &stbuf) != 0)
+ {
+ error(TDEIO::ERR_DOES_NOT_EXIST, url.path(-1));
+ return;
+ }
+
+ TQDir dir(path);
+ if (!dir.isReadable())
+ {
+ error( TDEIO::ERR_CANNOT_ENTER_DIRECTORY, url.path());
+ return;
+ }
+
+ const TQFileInfoList *list = dir.entryInfoList(TQDir::All|TQDir::Hidden);
+ TQFileInfoListIterator it( *list );
+ TQFileInfo *fi;
+
+ TDEIO::UDSEntry entry;
+ createDigikamPropsUDSEntry(entry);
+ listEntry(entry, false);
+ while ((fi = it.current()) != 0)
+ {
+ if (fi->fileName() != "." && fi->fileName() != ".." || fi->extension(true) == "digikamtempfile.tmp")
+ {
+ createUDSEntry(fi->absFilePath(), entry);
+ listEntry(entry, false);
+ }
+ ++it;
+ }
+
+ entry.clear();
+ listEntry(entry, true);
+ finished();
+}
+
+void tdeio_digikamalbums::mkdir( const KURL& url, int permissions )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ TQString path = libraryPath + url.path();
+ TQCString _path( TQFile::encodeName(path));
+
+ KDE_struct_stat buff;
+ if ( KDE_stat( _path, &buff ) == -1 )
+ {
+ if ( ::mkdir( _path.data(), 0777 /*umask will be applied*/ ) != 0 )
+ {
+ if ( errno == EACCES )
+ {
+ error( TDEIO::ERR_ACCESS_DENIED, path );
+ return;
+ }
+ else if ( errno == ENOSPC )
+ {
+ error( TDEIO::ERR_DISK_FULL, path );
+ return;
+ }
+ else
+ {
+ error( TDEIO::ERR_COULD_NOT_MKDIR, path );
+ return;
+ }
+ }
+ else
+ {
+ // code similar to AlbumDB::addAlbum
+ m_sqlDB.execSql( TQString("REPLACE INTO Albums (url, date) "
+ "VALUES('%1','%2')")
+ .arg(escapeString(url.path()),
+ TQDate::currentDate().toString(TQt::ISODate)) );
+
+ if ( permissions != -1 )
+ {
+ if ( ::chmod( _path.data(), permissions ) == -1 )
+ error( TDEIO::ERR_CANNOT_CHMOD, path );
+ else
+ finished();
+ }
+ else
+ finished();
+ return;
+ }
+ }
+
+ if ( S_ISDIR( buff.st_mode ) )
+ {
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, path );
+ return;
+ }
+
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, path );
+}
+
+void tdeio_digikamalbums::chmod( const KURL& url, int permissions )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ // get the album library path
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ TQCString path( TQFile::encodeName(libraryPath + url.path()));
+ if ( ::chmod( path.data(), permissions ) == -1 )
+ error( TDEIO::ERR_CANNOT_CHMOD, url.url() );
+ else
+ finished();
+}
+
+void tdeio_digikamalbums::del( const KURL& url, bool isfile)
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ // get the album library path
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // open the db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ // build the album list
+ buildAlbumList();
+
+ TQCString path( TQFile::encodeName(libraryPath + url.path()));
+
+ if (isfile)
+ {
+ kdDebug( ) << "Deleting file "<< url.url() << endl;
+
+ // if the filename is .digikam_properties fake that we deleted it
+ if (url.fileName() == ".digikam_properties")
+ {
+ finished();
+ return;
+ }
+
+ // find the Album to which this file belongs.
+ AlbumInfo album = findAlbum(url.directory());
+ if (album.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(url.directory()));
+ return;
+ }
+
+ // actually delete the file
+ if ( unlink( path.data() ) == -1 )
+ {
+ if ((errno == EACCES) || (errno == EPERM))
+ error( TDEIO::ERR_ACCESS_DENIED, url.url());
+ else if (errno == EISDIR)
+ error( TDEIO::ERR_IS_DIRECTORY, url.url());
+ else
+ error( TDEIO::ERR_CANNOT_DELETE, url.url() );
+ return;
+ }
+
+ // successful deletion. now remove file entry from the database
+ delImage(album.id, url.fileName());
+ }
+ else
+ {
+ kdDebug( ) << "Deleting directory " << url.url() << endl;
+
+ // find the corresponding album entry
+ AlbumInfo album = findAlbum(url.path());
+ if (album.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(url.path()));
+ return;
+ }
+
+ if ( ::rmdir( path.data() ) == -1 )
+ {
+ // TODO handle symlink delete
+
+ if ((errno == EACCES) || (errno == EPERM))
+ {
+ error( TDEIO::ERR_ACCESS_DENIED, url.url());
+ return;
+ }
+ else
+ {
+ kdDebug() << "could not rmdir " << perror << endl;
+ error( TDEIO::ERR_COULD_NOT_RMDIR, url.url() );
+ return;
+ }
+ }
+
+ // successful deletion. now remove album entry from the database
+ delAlbum(album.id);
+ }
+
+ finished();
+
+}
+
+bool tdeio_digikamalbums::createUDSEntry(const TQString& path, TDEIO::UDSEntry& entry)
+{
+ entry.clear();
+
+ KDE_struct_stat stbuf;
+ if (KDE_stat(TQFile::encodeName(path), &stbuf) != 0)
+ return false;
+
+ TDEIO::UDSAtom atom;
+
+ atom.m_uds = TDEIO::UDS_FILE_TYPE;
+ atom.m_long = stbuf.st_mode & S_IFMT;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS;
+ atom.m_long = stbuf.st_mode & 07777;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_SIZE;
+ atom.m_long = stbuf.st_size;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_MODIFICATION_TIME;
+ atom.m_long = stbuf.st_mtime;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS_TIME;
+ atom.m_long = stbuf.st_atime;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_NAME;
+ atom.m_str = TQFileInfo(path).fileName();
+ entry.append(atom);
+
+ /*
+ // If we provide the local path, a TDEIO::CopyJob will optimize away
+ // the use of our custom digikamalbums:/ ioslave, which breaks
+ // copying the database entry:
+ // Disabling this as a temporary solution for bug #137282
+ // This code is intended as a fix for bug #122653.
+#if KDE_IS_VERSION(3,4,0)
+ atom.m_uds = TDEIO::UDS_LOCAL_PATH;
+ atom.m_str = path;
+ entry.append(atom);
+#endif
+ */
+
+ return true;
+}
+
+void tdeio_digikamalbums::createDigikamPropsUDSEntry(TDEIO::UDSEntry& entry)
+{
+ entry.clear();
+
+ TDEIO::UDSAtom atom;
+
+ atom.m_uds = TDEIO::UDS_FILE_TYPE;
+ atom.m_long = S_IFREG;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS;
+ atom.m_long = 00666;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_SIZE;
+ atom.m_long = 0;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_MODIFICATION_TIME;
+ atom.m_long = TQDateTime::currentDateTime().toTime_t();
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS_TIME;
+ atom.m_long = TQDateTime::currentDateTime().toTime_t();
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_NAME;
+ atom.m_str = ".digikam_properties";
+ entry.append(atom);
+}
+
+void tdeio_digikamalbums::buildAlbumList()
+{
+// simplified from AlbumDB::scanAlbums()
+ m_albumList.clear();
+
+ TQStringList values;
+ m_sqlDB.execSql( TQString("SELECT id, url, date, caption, collection, icon "
+ "FROM Albums;"), &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ AlbumInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.url = *it;
+ ++it;
+ info.date = TQDate::fromString(*it, TQt::ISODate);
+ ++it;
+ info.caption = *it;
+ ++it;
+ info.collection = *it;
+ ++it;
+ info.icon = (*it).toLongLong();
+ ++it;
+
+ m_albumList.append(info);
+ }
+}
+
+AlbumInfo tdeio_digikamalbums::findAlbum(const TQString& url, bool addIfNotExists)
+{
+// similar to AlbumDB::getOrCreateAlbumId
+ AlbumInfo album;
+ for (TQValueList<AlbumInfo>::const_iterator it = m_albumList.begin();
+ it != m_albumList.end(); ++it)
+ {
+ if ((*it).url == url)
+ {
+ album = *it;
+ return album;
+ }
+ }
+
+ album.id = -1;
+
+ if (addIfNotExists)
+ {
+ TQFileInfo fi(m_libraryPath + url);
+ if (!fi.exists() || !fi.isDir())
+ return album;
+
+ m_sqlDB.execSql(TQString("INSERT INTO Albums (url, date) "
+ "VALUES('%1', '%2')")
+ .arg(escapeString(url),
+ fi.lastModified().date().toString(TQt::ISODate)));
+
+ album.id = m_sqlDB.lastInsertedRow();
+ album.url = url;
+ album.date = fi.lastModified().date();
+ album.icon = 0;
+
+ m_albumList.append(album);
+ }
+
+ return album;
+}
+
+void tdeio_digikamalbums::delAlbum(int albumID)
+{
+// code duplication from AlbumDB::deleteAlbum
+ m_sqlDB.execSql(TQString("DELETE FROM Albums WHERE id='%1'")
+ .arg(albumID));
+}
+
+void tdeio_digikamalbums::renameAlbum(const TQString& oldURL, const TQString& newURL)
+{
+// similar to AlbumDB::setAlbumURL, but why more extended?
+ // first update the url of the album which was renamed
+
+ m_sqlDB.execSql( TQString("UPDATE Albums SET url='%1' WHERE url='%2'")
+ .arg(escapeString(newURL),
+ escapeString(oldURL)));
+
+ // now find the list of all subalbums which need to be updated
+ TQStringList values;
+ m_sqlDB.execSql( TQString("SELECT url FROM Albums WHERE url LIKE '%1/%';")
+ .arg(oldURL), &values );
+
+ // and update their url
+ TQString newChildURL;
+ for (TQStringList::iterator it = values.begin(); it != values.end(); ++it)
+ {
+ newChildURL = *it;
+ newChildURL.replace(oldURL, newURL);
+ m_sqlDB.execSql(TQString("UPDATE Albums SET url='%1' WHERE url='%2'")
+ .arg(escapeString(newChildURL),
+ escapeString(*it)));
+ }
+}
+
+bool tdeio_digikamalbums::findImage(int albumID, const TQString& name) const
+{
+// no similar method in AlbumDB?
+ TQStringList values;
+
+ m_sqlDB.execSql( TQString("SELECT name FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(name)),
+ &values );
+
+ return !(values.isEmpty());
+}
+
+// from albuminfo.h
+class TagInfo
+{
+public:
+
+ typedef TQValueList<TagInfo> List;
+
+ int id;
+ int pid;
+ TQString name;
+ TQString icon;
+};
+
+void tdeio_digikamalbums::addImage(int albumID, const TQString& filePath)
+{
+// Code duplication: ScanLib::storeItemInDatabase, AlbumDB::addItem,
+// AlbumDB::setItemRating, AlbumDB::addItemTag, AlbumDB::addTag
+
+ // from ScanLib::storeItemInDatabase
+ TQString comment;
+ TQDateTime datetime;
+ int rating = 0;
+
+ Digikam::DMetadata metadata(filePath);
+
+ // Trying to get comments from image :
+ // In first, from standard JPEG comments, or
+ // In second, from EXIF comments tag, or
+ // In third, from IPTC comments tag.
+
+ comment = metadata.getImageComment();
+
+ // Trying to get date and time from image :
+ // In first, from EXIF date & time tags, or
+ // In second, from IPTC date & time tags.
+
+ datetime = metadata.getImageDateTime();
+
+ // Trying to get image rating from IPTC Urgency tag.
+ rating = metadata.getImageRating();
+
+ if (!datetime.isValid())
+ {
+ TQFileInfo info(filePath);
+ datetime = info.lastModified();
+ }
+
+ // Try to get image tags from IPTC keywords tags.
+ TQStringList keywordsList = metadata.getImageKeywords();
+
+ // from AlbumDB::addItem
+ m_sqlDB.execSql(TQString("REPLACE INTO Images "
+ "(dirid, name, datetime, caption) "
+ "VALUES(%1, '%2', '%3', '%4')")
+ .arg(TQString::number(albumID),
+ escapeString(TQFileInfo(filePath).fileName()),
+ datetime.toString(TQt::ISODate),
+ escapeString(comment)));
+
+ TQ_LLONG imageID = m_sqlDB.lastInsertedRow();
+
+ // from AlbumDB::setItemRating
+ if (imageID != -1 && rating != -1)
+ {
+ m_sqlDB.execSql(TQString("REPLACE INTO ImageProperties "
+ "(imageid, property, value) "
+ "VALUES(%1, '%2', '%3');")
+ .arg(imageID)
+ .arg("Rating")
+ .arg(rating) );
+ }
+
+ // Set existing tags in database or create new tags if not exist.
+
+ if ( imageID != -1 && !keywordsList.isEmpty() )
+ {
+ TQStringList keywordsList2Create;
+
+ // Create a list of the tags currently in database
+
+ TagInfo::List tagsList;
+
+ TQStringList values;
+ m_sqlDB.execSql( "SELECT id, pid, name FROM Tags;", &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ TagInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.pid = (*it).toInt();
+ ++it;
+ info.name = *it;
+ ++it;
+ tagsList.append(info);
+ }
+
+ // For every tag in keywordsList, scan taglist to check if tag already exists.
+
+ for (TQStringList::iterator kwd = keywordsList.begin();
+ kwd != keywordsList.end(); ++kwd )
+ {
+ // split full tag "url" into list of single tag names
+ TQStringList tagHierarchy = TQStringList::split('/', *kwd);
+ if (tagHierarchy.isEmpty())
+ continue;
+
+ // last entry in list is the actual tag name
+ bool foundTag = false;
+ TQString tagName = tagHierarchy.back();
+ tagHierarchy.pop_back();
+
+ for (TagInfo::List::iterator tag = tagsList.begin();
+ tag != tagsList.end(); ++tag )
+ {
+ // There might be multiple tags with the same name, but in different
+ // hierarchies. We must check them all until we find the correct hierarchy
+ if ((*tag).name == tagName)
+ {
+ int parentID = (*tag).pid;
+
+ // Check hierarchy, from bottom to top
+ bool foundParentTag = true;
+ TQStringList::iterator parentTagName = tagHierarchy.end();
+
+ while (foundParentTag && parentTagName != tagHierarchy.begin())
+ {
+ --parentTagName;
+
+ foundParentTag = false;
+
+ for (TagInfo::List::iterator parentTag = tagsList.begin();
+ parentTag != tagsList.end(); ++parentTag )
+ {
+ // check if name is the same, and if ID is identical
+ // to the parent ID we got from the child tag
+ if ( (*parentTag).id == parentID &&
+ (*parentTag).name == (*parentTagName) )
+ {
+ parentID = (*parentTag).pid;
+ foundParentTag = true;
+ break;
+ }
+ }
+
+ // If we traversed the list without a match,
+ // foundParentTag will be false, the while loop breaks.
+ }
+
+ // If we managed to traverse the full hierarchy,
+ // we have our tag.
+ if (foundParentTag)
+ {
+ // from AlbumDB::addItemTag
+ m_sqlDB.execSql( TQString("REPLACE INTO ImageTags (imageid, tagid) "
+ "VALUES(%1, %2);")
+ .arg(imageID)
+ .arg((*tag).id) );
+ foundTag = true;
+ break;
+ }
+ }
+ }
+
+ if (!foundTag)
+ keywordsList2Create.append(*kwd);
+ }
+
+ // If tags do not exist in database, create them.
+
+ if (!keywordsList2Create.isEmpty())
+ {
+ for (TQStringList::iterator kwd = keywordsList2Create.begin();
+ kwd != keywordsList2Create.end(); ++kwd )
+ {
+ // split full tag "url" into list of single tag names
+ TQStringList tagHierarchy = TQStringList::split('/', *kwd);
+
+ if (tagHierarchy.isEmpty())
+ continue;
+
+ int parentTagID = 0;
+ int tagID = 0;
+ bool parentTagExisted = true;
+
+ // Traverse hierarchy from top to bottom
+ for (TQStringList::iterator tagName = tagHierarchy.begin();
+ tagName != tagHierarchy.end(); ++tagName)
+ {
+ tagID = 0;
+
+ // if the parent tag did not exist, we need not check if the child exists
+ if (parentTagExisted)
+ {
+ for (TagInfo::List::iterator tag = tagsList.begin();
+ tag != tagsList.end(); ++tag )
+ {
+ // find the tag with tag name according to tagHierarchy,
+ // and parent ID identical to the ID of the tag we found in
+ // the previous run.
+ if ((*tag).name == (*tagName) && (*tag).pid == parentTagID)
+ {
+ tagID = (*tag).id;
+ break;
+ }
+ }
+ }
+
+ if (tagID != 0)
+ {
+ // tag already found in DB
+ parentTagID = tagID;
+ continue;
+ }
+
+ // Tag does not yet exist in DB, add it
+ // from AlbumDB::addTag
+ m_sqlDB.execSql( TQString("INSERT INTO Tags (pid, name, icon) "
+ "VALUES( %1, '%2', 0)")
+ .arg(parentTagID)
+ .arg(escapeString(*tagName)));
+ tagID = m_sqlDB.lastInsertedRow();
+
+ if (tagID == -1)
+ {
+ // Something is wrong in database. Abort.
+ break;
+ }
+
+ // append to our list of existing tags (for following keywords)
+ TagInfo info;
+ info.id = tagID;
+ info.pid = parentTagID;
+ info.name = (*tagName);
+ tagsList.append(info);
+
+ parentTagID = tagID;
+ parentTagExisted = false;
+ }
+
+ // from AlbumDB::addItemTag
+ m_sqlDB.execSql( TQString("REPLACE INTO ImageTags (imageid, tagid) "
+ "VALUES(%1, %2);")
+ .arg(imageID)
+ .arg(tagID) );
+ }
+ }
+ }
+}
+
+void tdeio_digikamalbums::delImage(int albumID, const TQString& name)
+{
+// code duplication from AlbumDB::deleteItem
+ m_sqlDB.execSql( TQString("DELETE FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(name)) );
+}
+
+void tdeio_digikamalbums::renameImage(int oldAlbumID, const TQString& oldName,
+ int newAlbumID, const TQString& newName)
+{
+// code duplication from AlbumDB::deleteItem, AlbumDB::moveItem
+ // first delete any stale entries for the destination file
+ m_sqlDB.execSql( TQString("DELETE FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(newAlbumID)
+ .arg(escapeString(newName)) );
+
+ // now update the dirid and/or name of the file
+ m_sqlDB.execSql( TQString("UPDATE Images SET dirid=%1, name='%2' "
+ "WHERE dirid=%3 AND name='%4';")
+ .arg(TQString::number(newAlbumID),
+ escapeString(newName),
+ TQString::number(oldAlbumID),
+ escapeString(oldName)) );
+}
+
+void tdeio_digikamalbums::copyImage(int srcAlbumID, const TQString& srcName,
+ int dstAlbumID, const TQString& dstName)
+{
+// code duplication from AlbumDB::copyItem
+ // check for src == dest
+ if (srcAlbumID == dstAlbumID && srcName == dstName)
+ {
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, dstName );
+ return;
+ }
+
+ // find id of src image
+ TQStringList values;
+ m_sqlDB.execSql( TQString("SELECT id FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(TQString::number(srcAlbumID), escapeString(srcName)),
+ &values);
+
+ if (values.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source image %1 not found in database")
+ .arg(srcName));
+ return;
+ }
+
+ int srcId = values[0].toInt();
+
+ // first delete any stale entries for the destination file
+ m_sqlDB.execSql( TQString("DELETE FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(TQString::number(dstAlbumID), escapeString(dstName)) );
+
+ // copy entry in Images table
+ m_sqlDB.execSql( TQString("INSERT INTO Images (dirid, name, caption, datetime) "
+ "SELECT %1, '%2', caption, datetime FROM Images "
+ "WHERE id=%3;")
+ .arg(TQString::number(dstAlbumID), escapeString(dstName),
+ TQString::number(srcId)) );
+
+ int dstId = m_sqlDB.lastInsertedRow();
+
+ // copy tags
+ m_sqlDB.execSql( TQString("INSERT INTO ImageTags (imageid, tagid) "
+ "SELECT %1, tagid FROM ImageTags "
+ "WHERE imageid=%2;")
+ .arg(TQString::number(dstId), TQString::number(srcId)) );
+
+ // copy properties (rating)
+ m_sqlDB.execSql( TQString("INSERT INTO ImageProperties (imageid, property, value) "
+ "SELECT %1, property, value FROM ImageProperties "
+ "WHERE imageid=%2;")
+ .arg(TQString::number(dstId), TQString::number(srcId)) );
+}
+
+void tdeio_digikamalbums::scanAlbum(const TQString& url)
+{
+ scanOneAlbum(url);
+ removeInvalidAlbums();
+}
+
+void tdeio_digikamalbums::scanOneAlbum(const TQString& url)
+{
+ TQDir dir(m_libraryPath + url);
+ if (!dir.exists() || !dir.isReadable())
+ {
+ return;
+ }
+
+ TQString subURL = url;
+ if (!url.endsWith("/"))
+ subURL += '/';
+ subURL = escapeString( subURL);
+
+ {
+ // scan albums
+
+ TQStringList currAlbumList;
+ m_sqlDB.execSql( TQString("SELECT url FROM Albums WHERE ") +
+ TQString("url LIKE '") + subURL + TQString("%' ") +
+ TQString("AND url NOT LIKE '") + subURL + TQString("%/%' "),
+ &currAlbumList );
+
+
+ const TQFileInfoList* infoList = dir.entryInfoList(TQDir::Dirs);
+ if (!infoList)
+ return;
+
+ TQFileInfoListIterator it(*infoList);
+ TQFileInfo* fi;
+
+ TQStringList newAlbumList;
+ while ((fi = it.current()) != 0)
+ {
+ ++it;
+
+ if (fi->fileName() == "." || fi->fileName() == "..")
+ {
+ continue;
+ }
+
+ TQString u = TQDir::cleanDirPath(url + '/' + fi->fileName());
+
+ if (currAlbumList.contains(u))
+ {
+ continue;
+ }
+
+ newAlbumList.append(u);
+ }
+
+ for (TQStringList::iterator it = newAlbumList.begin();
+ it != newAlbumList.end(); ++it)
+ {
+ kdDebug() << "New Album: " << *it << endl;
+
+ TQFileInfo fi(m_libraryPath + *it);
+ m_sqlDB.execSql(TQString("INSERT INTO Albums (url, date) "
+ "VALUES('%1', '%2')")
+ .arg(escapeString(*it),
+ fi.lastModified().date().toString(TQt::ISODate)));
+
+ scanAlbum(*it);
+ }
+ }
+
+ if (url != "/")
+ {
+ // scan files
+
+ TQStringList values;
+
+ m_sqlDB.execSql( TQString("SELECT id FROM Albums WHERE url='%1'")
+ .arg(escapeString(url)), &values );
+ if (values.isEmpty())
+ return;
+
+ int albumID = values.first().toInt();
+
+ TQStringList currItemList;
+ m_sqlDB.execSql( TQString("SELECT name FROM Images WHERE dirid=%1")
+ .arg(albumID), &currItemList );
+
+ const TQFileInfoList* infoList = dir.entryInfoList(TQDir::Files);
+ if (!infoList)
+ return;
+
+ TQFileInfoListIterator it(*infoList);
+ TQFileInfo* fi;
+
+ // add any new files we find to the db
+ while ((fi = it.current()) != 0)
+ {
+ ++it;
+
+ // ignore temp files we created ourselves
+ if (fi->extension(true) == "digikamtempfile.tmp")
+ {
+ continue;
+ }
+
+ if (currItemList.contains(fi->fileName()))
+ {
+ currItemList.remove(fi->fileName());
+ continue;
+ }
+
+ addImage(albumID, m_libraryPath + url + '/' + fi->fileName());
+ }
+
+ // currItemList now contains deleted file list. remove them from db
+ for (TQStringList::iterator it = currItemList.begin();
+ it != currItemList.end(); ++it)
+ {
+ delImage(albumID, *it);
+ }
+ }
+}
+
+void tdeio_digikamalbums::removeInvalidAlbums()
+{
+ TQStringList urlList;
+
+ m_sqlDB.execSql(TQString("SELECT url FROM Albums;"),
+ &urlList);
+
+ m_sqlDB.execSql("BEGIN TRANSACTION");
+
+ struct stat stbuf;
+
+ for (TQStringList::iterator it = urlList.begin();
+ it != urlList.end(); ++it)
+ {
+ if (::stat(TQFile::encodeName(m_libraryPath + *it), &stbuf) == 0)
+ continue;
+
+ kdDebug() << "Deleted Album: " << *it << endl;
+ m_sqlDB.execSql(TQString("DELETE FROM Albums WHERE url='%1'")
+ .arg(escapeString(*it)));
+ }
+
+ m_sqlDB.execSql("COMMIT TRANSACTION");
+}
+
+/* TDEIO slave registration */
+
+extern "C"
+{
+ DIGIKAM_EXPORT int kdemain(int argc, char **argv)
+ {
+ TDELocale::setMainCatalogue("digikam");
+ TDEInstance instance( "tdeio_digikamalbums" );
+ TDEGlobal::locale();
+
+ if (argc != 4) {
+ kdDebug() << "Usage: tdeio_digikamalbums protocol domain-socket1 domain-socket2"
+ << endl;
+ exit(-1);
+ }
+
+ tdeio_digikamalbums slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ return 0;
+ }
+}
diff --git a/src/tdeioslave/digikamalbums.h b/src/tdeioslave/digikamalbums.h
new file mode 100644
index 00000000..c75e8c9c
--- /dev/null
+++ b/src/tdeioslave/digikamalbums.h
@@ -0,0 +1,108 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a dio-slave to process file operations on
+ * digiKam albums.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * Lots of the file io code is copied from KDE file tdeioslave.
+ * Copyright for the KDE file tdeioslave follows:
+ * Copyright (C) 2000-2002 Stephan Kulow <coolo@kde.org>
+ * Copyright (C) 2000-2002 David Faure <faure@kde.org>
+ * Copyright (C) 2000-2002 Waldo Bastian <bastian@kde.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ * ============================================================ */
+
+#ifndef DIGIKAMALBUMS_H
+#define DIGIKAMALBUMS_H
+
+// TQt includes.
+
+#include <tqvaluelist.h>
+#include <tqdatetime.h>
+#include <sqlitedb.h>
+
+// KDE includes.
+
+#include <tdeio/slavebase.h>
+
+class TQStringList;
+
+class AlbumInfo
+{
+public:
+
+ int id;
+ TQ_LLONG icon;
+ TQString url;
+ TQString caption;
+ TQString collection;
+ TQDate date;
+};
+
+class tdeio_digikamalbums : public TDEIO::SlaveBase
+{
+
+public:
+
+ tdeio_digikamalbums(const TQCString &pool_socket,
+ const TQCString &app_socket);
+ ~tdeio_digikamalbums();
+
+ void special(const TQByteArray& data);
+
+ void get( const KURL& url );
+ void put( const KURL& url, int _mode, bool _overwrite, bool _resume );
+ void copy( const KURL &src, const KURL &dest, int mode, bool overwrite );
+ void rename( const KURL &src, const KURL &dest, bool overwrite );
+
+ void stat( const KURL& url );
+ void listDir( const KURL& url );
+ void mkdir( const KURL& url, int permissions );
+ void chmod( const KURL& url, int permissions );
+ void del( const KURL& url, bool isfile);
+
+private:
+
+ bool createUDSEntry(const TQString& path, TDEIO::UDSEntry& entry);
+ void createDigikamPropsUDSEntry(TDEIO::UDSEntry& entry);
+
+ void buildAlbumList();
+ AlbumInfo findAlbum(const TQString& url, bool addIfNotExists=true);
+ void delAlbum(int albumID);
+ void renameAlbum(const TQString& oldURL, const TQString& newURL);
+ bool findImage(int albumID, const TQString& name) const;
+ void addImage(int albumID, const TQString& filePath);
+ void delImage(int albumID, const TQString& name);
+ void renameImage(int oldAlbumID, const TQString& oldName,
+ int newAlbumID, const TQString& newName);
+ void copyImage(int srcAlbumID, const TQString& srcName,
+ int dstAlbumID, const TQString& dstName);
+
+ void scanAlbum(const TQString& url);
+ void scanOneAlbum(const TQString& url);
+ void removeInvalidAlbums();
+
+private:
+
+ SqliteDB m_sqlDB;
+ TQString m_libraryPath;
+ TQValueList<AlbumInfo> m_albumList;
+};
+
+
+#endif /* DIGIKAMALBUMS_H */
diff --git a/src/tdeioslave/digikamalbums.protocol b/src/tdeioslave/digikamalbums.protocol
new file mode 100644
index 00000000..e2b094af
--- /dev/null
+++ b/src/tdeioslave/digikamalbums.protocol
@@ -0,0 +1,45 @@
+[Protocol]
+exec=tdeio_digikamalbums
+protocol=digikamalbums
+input=stream
+output=stream
+listing=Name,Type,Date,AccessDate,Access
+reading=true
+writing=true
+makedir=true
+linking=false
+deleting=true
+moving=true
+maxInstances=1
+Description=digiKam albums tdeioslave
+Description[br]=tdeioslave an albomoù evit Digikam
+Description[ca]=Kioslave d'àlbums del digiKam
+Description[da]=digiKam albummer-tdeioslave
+Description[de]=digiKam Ein-/Ausgabemodul für digiKam-Alben
+Description[el]=tdeioslave Άλμπουμ του digiKam
+Description[es]=tdeioslave de digiKam para álbumes
+Description[et]=DigiKami albumite TDEIO-moodul
+Description[fa]=tdeioslave آلبومهای digiKam
+Description[fi]=digiKamin tdeioslave-palvelu albumeille
+Description[gl]=Kioslave de albuns de digiKam
+Description[hr]=digiKam tdeioslave za albume
+Description[is]=digiKam albúma tdeioslave
+Description[it]=Kioslave degli album di digiKam
+Description[ja]=digiKam アルバム tdeioslave
+Description[nds]=In-/Utgaavmoduul för Alben vun digiKam
+Description[nl]=Digikam Albums tdeioslave
+Description[pa]=ਡਿਜ਼ੀਕੈਮ ਐਲਬਮ tdeioslave
+Description[pl]=Wtyczka protokołu albumów digiKam
+Description[pt]='tdeioslave' de álbuns do digiKam
+Description[pt_BR]='tdeioslave' de álbuns do digiKam
+Description[ru]=Digikam tdeioslave - альбомы
+Description[sk]=tdeioslave albumov digiKamu
+Description[sr]=tdeioslave digiKam-ових албума
+Description[sr@Latn]=tdeioslave digiKam-ovih albuma
+Description[sv]=I/O-slav för Digikams album
+Description[tr]=digiKam albümler tdeioslave
+Description[uk]=Підлеглий В/В альбомів для Digikam
+Description[vi]=tdeioslave tập ảnh digiKam
+Description[xx]=xxdigiKam albums tdeioslavexx
+Class=:local
+Icon=digikam
diff --git a/src/tdeioslave/digikamdates.cpp b/src/tdeioslave/digikamdates.cpp
new file mode 100644
index 00000000..9e7f4e27
--- /dev/null
+++ b/src/tdeioslave/digikamdates.cpp
@@ -0,0 +1,313 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a tdeio-slave to process date query on
+ * digiKam albums.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+}
+
+// C++ includes.
+
+#include <cstdlib>
+#include <cstdio>
+#include <ctime>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqdatastream.h>
+#include <tqregexp.h>
+#include <tqbuffer.h>
+
+// KDE includes.
+
+#include <tdeio/global.h>
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <kinstance.h>
+#include <tdefilemetainfo.h>
+#include <kdebug.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "digikamdates.h"
+
+tdeio_digikamdates::tdeio_digikamdates(const TQCString &pool_socket,
+ const TQCString &app_socket)
+ : SlaveBase("tdeio_digikamdates", pool_socket, app_socket)
+{
+}
+
+tdeio_digikamdates::~tdeio_digikamdates()
+{
+}
+
+static TQValueList<TQRegExp> makeFilterList( const TQString &filter )
+{
+ TQValueList<TQRegExp> regExps;
+ if ( filter.isEmpty() )
+ return regExps;
+
+ TQChar sep( ';' );
+ int i = filter.find( sep, 0 );
+ if ( i == -1 && filter.find( ' ', 0 ) != -1 )
+ sep = TQChar( ' ' );
+
+ TQStringList list = TQStringList::split( sep, filter );
+ TQStringList::Iterator it = list.begin();
+ while ( it != list.end() ) {
+ regExps << TQRegExp( (*it).stripWhiteSpace(), false, true );
+ ++it;
+ }
+ return regExps;
+}
+
+static bool matchFilterList( const TQValueList<TQRegExp>& filters,
+ const TQString &fileName )
+{
+ TQValueList<TQRegExp>::ConstIterator rit = filters.begin();
+ while ( rit != filters.end() ) {
+ if ( (*rit).exactMatch(fileName) )
+ return true;
+ ++rit;
+ }
+ return false;
+}
+
+void tdeio_digikamdates::special(const TQByteArray& data)
+{
+ bool folders = (metaData("folders") == "yes");
+
+ TQString libraryPath;
+ KURL kurl;
+ TQString url;
+ TQString filter;
+ int getDimensions;
+ int recurseAlbums;
+ int recurseTags;
+
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> libraryPath;
+ ds >> kurl;
+ ds >> filter;
+ ds >> getDimensions;
+ ds >> recurseAlbums;
+ ds >> recurseTags;
+
+ url = kurl.path();
+
+ TQValueList<TQRegExp> regex = makeFilterList(filter);
+
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_db.closeDB();
+ m_db.openDB(libraryPath);
+ }
+
+ TQByteArray ba;
+
+ if (folders) // Special mode to stats all dates from collection
+ {
+ TQMap<TQDateTime, int> datesStatMap;
+ TQStringList values;
+ TQString name, dateStr;
+ TQDateTime dateTime;
+
+ m_db.execSql( "SELECT name, datetime FROM Images;", &values );
+
+ for ( TQStringList::iterator it = values.begin(); it != values.end(); )
+ {
+ name = *it;
+ ++it;
+ dateStr = *it;
+ ++it;
+
+ if ( !matchFilterList( regex, name ) )
+ continue;
+
+ dateTime = TQDateTime::fromString( dateStr, TQt::ISODate );
+ if ( !dateTime.isValid() )
+ continue;
+
+ TQMap<TQDateTime, int>::iterator it2 = datesStatMap.find(dateTime);
+ if ( it2 == datesStatMap.end() )
+ {
+ datesStatMap.insert( dateTime, 1 );
+ }
+ else
+ {
+ datesStatMap.replace( dateTime, it2.data() + 1 );
+ }
+ }
+
+ TQDataStream os(ba, IO_WriteOnly);
+ os << datesStatMap;
+ }
+ else
+ {
+ TQStringList subpaths = TQStringList::split("/", url, false);
+ if (subpaths.count() == 4)
+ {
+ int yrStart = TQString(subpaths[0]).toInt();
+ int moStart = TQString(subpaths[1]).toInt();
+ int yrEnd = TQString(subpaths[2]).toInt();
+ int moEnd = TQString(subpaths[3]).toInt();
+
+ TQString moStartStr, moEndStr;
+ moStartStr.sprintf("%.2d", moStart);
+ moEndStr.sprintf("%.2d", moEnd);
+
+ TQStringList values;
+
+ m_db.execSql(TQString("SELECT Images.id, Images.name, Images.dirid, \n "
+ " Images.datetime, Albums.url \n "
+ "FROM Images, Albums \n "
+ "WHERE Images.datetime < '%1-%2-01' \n "
+ "AND Images.datetime >= '%3-%4-01' \n "
+ "AND Albums.id=Images.dirid \n "
+ "ORDER BY Albums.id;")
+ .arg(yrEnd, 4)
+ .arg(moEndStr, 2)
+ .arg(yrStart, 4)
+ .arg(moStartStr, 2),
+ &values, 0, false);
+
+ TQ_LLONG imageid;
+ TQString name;
+ TQString path;
+ int dirid;
+ TQString date;
+ TQString purl;
+ TQSize dims;
+ struct stat stbuf;
+
+ int count = 0;
+ TQDataStream* os = new TQDataStream(ba, IO_WriteOnly);
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ imageid = (*it).toLongLong();
+ ++it;
+ name = *it;
+ ++it;
+ dirid = (*it).toInt();
+ ++it;
+ date = *it;
+ ++it;
+ purl = *it;
+ ++it;
+
+ if (!matchFilterList(regex, name))
+ continue;
+
+ path = m_libraryPath + purl + '/' + name;
+ if (::stat(TQFile::encodeName(path), &stbuf) != 0)
+ continue;
+
+ dims = TQSize();
+ if (getDimensions)
+ {
+ KFileMetaInfo metaInfo(path);
+ if (metaInfo.isValid())
+ {
+ if (metaInfo.containsGroup("Jpeg EXIF Data"))
+ {
+ dims = metaInfo.group("Jpeg EXIF Data").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("General"))
+ {
+ dims = metaInfo.group("General").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("Technical"))
+ {
+ dims = metaInfo.group("Technical").
+ item("Dimensions").value().toSize();
+ }
+ }
+ }
+
+ *os << imageid;
+ *os << dirid;
+ *os << name;
+ *os << date;
+ *os << static_cast<size_t>(stbuf.st_size);
+ *os << dims;
+
+ count++;
+
+ if (count > 200)
+ {
+ delete os;
+ os = 0;
+
+ SlaveBase::data(ba);
+ ba.resize(0);
+
+ count = 0;
+ os = new TQDataStream(ba, IO_WriteOnly);
+ }
+ }
+
+ delete os;
+ }
+ }
+
+ SlaveBase::data(ba);
+
+ finished();
+}
+
+/* TDEIO slave registration */
+
+extern "C"
+{
+ DIGIKAM_EXPORT int kdemain(int argc, char **argv)
+ {
+ TDELocale::setMainCatalogue("digikam");
+ TDEInstance instance( "tdeio_digikamdates" );
+ TDEGlobal::locale();
+
+ if (argc != 4) {
+ kdDebug() << "Usage: tdeio_digikamdates protocol domain-socket1 domain-socket2"
+ << endl;
+ exit(-1);
+ }
+
+ tdeio_digikamdates slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ return 0;
+ }
+}
+
diff --git a/src/tdeioslave/digikamdates.h b/src/tdeioslave/digikamdates.h
new file mode 100644
index 00000000..4ba75749
--- /dev/null
+++ b/src/tdeioslave/digikamdates.h
@@ -0,0 +1,54 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a tdeio-slave to process date query on
+ * digiKam albums.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ * ============================================================ */
+
+#ifndef DIGIKAMDATES_H
+#define DIGIKAMDATES_H
+
+// KDE includes.
+
+#include <tdeio/slavebase.h>
+
+// Local includes.
+
+#include "sqlitedb.h"
+
+class TQStringList;
+
+class tdeio_digikamdates : public TDEIO::SlaveBase
+{
+
+public:
+
+ tdeio_digikamdates(const TQCString &pool_socket,
+ const TQCString &app_socket);
+ ~tdeio_digikamdates();
+
+ void special(const TQByteArray& data);
+
+private:
+
+ SqliteDB m_db;
+ TQString m_libraryPath;
+};
+
+#endif /* DIGIKAMDATES_H */
diff --git a/src/tdeioslave/digikamdates.protocol b/src/tdeioslave/digikamdates.protocol
new file mode 100644
index 00000000..a21352bd
--- /dev/null
+++ b/src/tdeioslave/digikamdates.protocol
@@ -0,0 +1,44 @@
+[Protocol]
+exec=tdeio_digikamdates
+protocol=digikamdates
+input=stream
+output=stream
+listing=Name,Type
+reading=false
+writing=false
+makedir=false
+linking=false
+deleting=false
+moving=false
+maxInstances=1
+Description=digiKam dates tdeioslave
+Description[ca]=Kioslave de dates del digiKam
+Description[da]=Digikam datoer-tdeioslave
+Description[de]=digiKam Ein-/Ausgabemodul für digiKam-Datumsangaben
+Description[el]=tdeioslave ημερομηνιών του digiKam
+Description[es]=tdeioslave de digiKam para fechas
+Description[et]=DigiKami kuupäevade TDEIO-moodul
+Description[fa]=tdeioslave تاریخهای digiKam
+Description[fi]=digiKamin tdeioslave-palvelu päiväyksille
+Description[gl]=Kioslave de datas de digiKam
+Description[hr]=digiKam tdeioslave za datume
+Description[is]=digiKam dagsetninga tdeioslave
+Description[it]=Kioslave delle date di digiKam
+Description[ja]=digiKam 日付 tdeioslave
+Description[nds]=In-/Utgaavmoduul för Daten vun digiKam
+Description[nl]=Digikam Datums tdeioslave
+Description[pa]=ਡਿਜ਼ੀਕੈਮ ਮਿਤੀ tdeioslave
+Description[pl]=Wtyczka protokołu dat digiKama
+Description[pt]='tdeioslave' de datas do digiKam
+Description[pt_BR]='tdeioslave' de datas do digiKam
+Description[ru]=Digikam tdeioslave - даты
+Description[sk]=tdeioslave dátumov digiKamu
+Description[sr]=tdeioslave digiKam-ових датума
+Description[sr@Latn]=tdeioslave digiKam-ovih datuma
+Description[sv]=I/O-slav för Digikams datum
+Description[tr]=digiKam tarihler tdeioslave
+Description[uk]=Підлеглий В/В дат для digiKam
+Description[vi]=tdeioslave ngày tháng digiKam
+Description[xx]=xxdigiKam dates tdeioslavexx
+Class=:local
+Icon=digikam
diff --git a/src/tdeioslave/digikamsearch.cpp b/src/tdeioslave/digikamsearch.cpp
new file mode 100644
index 00000000..225a900f
--- /dev/null
+++ b/src/tdeioslave/digikamsearch.cpp
@@ -0,0 +1,734 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a tdeio-slave to process search on digiKam albums
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <utime.h>
+}
+
+// C++ includes.
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstdio>
+#include <ctime>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqdatastream.h>
+#include <tqtextstream.h>
+#include <tqregexp.h>
+#include <tqdir.h>
+#include <tqvariant.h>
+#include <tqmap.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <kcalendarsystem.h>
+#include <kinstance.h>
+#include <tdefilemetainfo.h>
+#include <kmimetype.h>
+#include <kdebug.h>
+#include <tdeio/global.h>
+#include <tdeio/ioslave_defaults.h>
+#include <klargefile.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "digikamsearch.h"
+
+tdeio_digikamsearch::tdeio_digikamsearch(const TQCString &pool_socket,
+ const TQCString &app_socket)
+ : SlaveBase("tdeio_digikamsearch", pool_socket, app_socket)
+{
+ // build a lookup table for month names
+ const KCalendarSystem* cal = TDEGlobal::locale()->calendar();
+ for (int i=1; i<=12; ++i)
+ {
+ m_shortMonths[i-1] = cal->monthName(i, 2000, true).lower();
+ m_longMonths[i-1] = cal->monthName(i, 2000, false).lower();
+ }
+}
+
+tdeio_digikamsearch::~tdeio_digikamsearch()
+{
+}
+
+static TQValueList<TQRegExp> makeFilterList( const TQString &filter )
+{
+ TQValueList<TQRegExp> regExps;
+ if ( filter.isEmpty() )
+ return regExps;
+
+ TQChar sep( ';' );
+ int i = filter.find( sep, 0 );
+ if ( i == -1 && filter.find( ' ', 0 ) != -1 )
+ sep = TQChar( ' ' );
+
+ TQStringList list = TQStringList::split( sep, filter );
+ TQStringList::Iterator it = list.begin();
+ while ( it != list.end() )
+ {
+ regExps << TQRegExp( (*it).stripWhiteSpace(), false, true );
+ ++it;
+ }
+ return regExps;
+}
+
+static bool matchFilterList( const TQValueList<TQRegExp>& filters,
+ const TQString &fileName )
+{
+ TQValueList<TQRegExp>::ConstIterator rit = filters.begin();
+ while ( rit != filters.end() )
+ {
+ if ( (*rit).exactMatch(fileName) )
+ return true;
+ ++rit;
+ }
+ return false;
+}
+
+void tdeio_digikamsearch::special(const TQByteArray& data)
+{
+ TQString libraryPath;
+ KURL url;
+ TQString filter;
+ int getDimensions;
+ int listingType = 0;
+ int recurseAlbums;
+ int recurseTags;
+
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> libraryPath;
+ ds >> url;
+ ds >> filter;
+ ds >> getDimensions;
+ ds >> recurseAlbums;
+ ds >> recurseTags;
+
+ if (!ds.atEnd())
+ ds >> listingType;
+
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_db.closeDB();
+ m_db.openDB(libraryPath);
+ }
+
+ TQValueList<TQRegExp> regex = makeFilterList(filter);
+ TQByteArray ba;
+
+ if (listingType == 0)
+ {
+ TQString sqlQuery;
+
+ // query head
+ sqlQuery = "SELECT Images.id, Images.name, Images.dirid, Images.datetime, Albums.url "
+ "FROM Images, Albums LEFT JOIN ImageProperties ON Images.id = Imageproperties.imageid "
+ "WHERE ( ";
+
+ // query body
+ sqlQuery += buildQuery(url);
+
+ // query tail
+ sqlQuery += " ) ";
+ sqlQuery += " AND (Albums.id=Images.dirid); ";
+
+ TQStringList values;
+ TQString errMsg;
+ if (!m_db.execSql(sqlQuery, &values))
+ {
+ error(TDEIO::ERR_INTERNAL, errMsg);
+ return;
+ }
+
+ TQ_LLONG imageid;
+ TQString name;
+ TQString path;
+ int dirid;
+ TQString date;
+ TQString purl;
+ TQSize dims;
+ struct stat stbuf;
+
+ int count = 0;
+ TQDataStream* os = new TQDataStream(ba, IO_WriteOnly);
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ imageid = (*it).toLongLong();
+ ++it;
+ name = *it;
+ ++it;
+ dirid = (*it).toInt();
+ ++it;
+ date = *it;
+ ++it;
+ purl = *it;
+ ++it;
+
+ if (!matchFilterList(regex, name))
+ continue;
+
+ path = m_libraryPath + purl + '/' + name;
+ if (::stat(TQFile::encodeName(path), &stbuf) != 0)
+ continue;
+
+ dims = TQSize();
+ if (getDimensions)
+ {
+ KFileMetaInfo metaInfo(path);
+ if (metaInfo.isValid())
+ {
+ if (metaInfo.containsGroup("Jpeg EXIF Data"))
+ {
+ dims = metaInfo.group("Jpeg EXIF Data").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("General"))
+ {
+ dims = metaInfo.group("General").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("Technical"))
+ {
+ dims = metaInfo.group("Technical").
+ item("Dimensions").value().toSize();
+ }
+ }
+ }
+
+ *os << imageid;
+ *os << dirid;
+ *os << name;
+ *os << date;
+ *os << static_cast<size_t>(stbuf.st_size);
+ *os << dims;
+
+ count++;
+
+ if (count > 200)
+ {
+ delete os;
+ os = 0;
+
+ SlaveBase::data(ba);
+ ba.resize(0);
+
+ count = 0;
+ os = new TQDataStream(ba, IO_WriteOnly);
+ }
+ }
+
+ delete os;
+ }
+ else
+ {
+ TQString sqlQuery;
+
+ // query head
+ sqlQuery = "SELECT Albums.url||'/'||Images.name "
+ "FROM Images, Albums LEFT JOIN ImageProperties on Images.id = ImageProperties.imageid "
+ "WHERE ( ";
+
+ // query body
+ sqlQuery += buildQuery(url);
+
+ // query tail
+ sqlQuery += " ) ";
+ sqlQuery += " AND (Albums.id=Images.dirid) ";
+ sqlQuery += " LIMIT 500;";
+
+ TQStringList values;
+ TQString errMsg;
+ if (!m_db.execSql(sqlQuery, &values, &errMsg))
+ {
+ error(TDEIO::ERR_INTERNAL, errMsg);
+ return;
+ }
+
+ TQDataStream ds(ba, IO_WriteOnly);
+ for (TQStringList::iterator it = values.begin(); it != values.end(); ++it)
+ {
+ if (matchFilterList(regex, *it))
+ {
+ ds << m_libraryPath + *it;
+ }
+ }
+ }
+
+ SlaveBase::data(ba);
+
+ finished();
+}
+
+TQString tdeio_digikamsearch::buildQuery(const KURL& url) const
+{
+ int count = url.queryItem("count").toInt();
+ if (count <= 0)
+ return TQString();
+
+ TQMap<int, RuleType> rulesMap;
+
+ for (int i=1; i<=count; i++)
+ {
+ RuleType rule;
+
+ TQString key = url.queryItem(TQString::number(i) + ".key").lower();
+ TQString op = url.queryItem(TQString::number(i) + ".op").lower();
+
+ if (key == "album")
+ {
+ rule.key = ALBUM;
+ }
+ else if (key == "albumname")
+ {
+ rule.key = ALBUMNAME;
+ }
+ else if (key == "albumcaption")
+ {
+ rule.key = ALBUMCAPTION;
+ }
+ else if (key == "albumcollection")
+ {
+ rule.key = ALBUMCOLLECTION;
+ }
+ else if (key == "imagename")
+ {
+ rule.key = IMAGENAME;
+ }
+ else if (key == "imagecaption")
+ {
+ rule.key = IMAGECAPTION;
+ }
+ else if (key == "imagedate")
+ {
+ rule.key = IMAGEDATE;
+ }
+ else if (key == "tag")
+ {
+ rule.key = TAG;
+ }
+ else if (key == "tagname")
+ {
+ rule.key = TAGNAME;
+ }
+ else if (key == "keyword")
+ {
+ rule.key = KEYWORD;
+ }
+ else if (key == "rating")
+ {
+ rule.key = RATING;
+ }
+ else
+ {
+ kdWarning() << "Unknown rule type: " << key << " passed to tdeioslave"
+ << endl;
+ continue;
+ }
+
+ if (op == "eq")
+ rule.op = EQ;
+ else if (op == "ne")
+ rule.op = NE;
+ else if (op == "lt")
+ rule.op = LT;
+ else if (op == "lte")
+ rule.op = LTE;
+ else if (op == "gt")
+ rule.op = GT;
+ else if (op == "gte")
+ rule.op = GTE;
+ else if (op == "like")
+ rule.op = LIKE;
+ else if (op == "nlike")
+ rule.op = NLIKE;
+ else
+ {
+ kdWarning() << "Unknown op type: " << op << " passed to tdeioslave"
+ << endl;
+ continue;
+ }
+
+ rule.val = url.queryItem(TQString::number(i) + ".val");
+
+ rulesMap.insert(i, rule);
+ }
+
+ TQString sqlQuery;
+
+ TQStringList strList = TQStringList::split(" ", url.path());
+ for ( TQStringList::Iterator it = strList.begin(); it != strList.end(); ++it )
+ {
+ bool ok;
+ int num = (*it).toInt(&ok);
+ if (ok)
+ {
+ RuleType rule = rulesMap[num];
+ if (rule.key == KEYWORD)
+ {
+ bool exact;
+ TQString possDate = possibleDate(rule.val, exact);
+ if (!possDate.isEmpty())
+ {
+ rule.key = IMAGEDATE;
+ rule.val = possDate;
+ if (exact)
+ {
+ rule.op = EQ;
+ }
+ else
+ {
+ rule.op = LIKE;
+ }
+
+ sqlQuery += subQuery(rule.key, rule.op, rule.val);
+ }
+ else
+ {
+ TQValueList<SKey> todo;
+ todo.append( ALBUMNAME );
+ todo.append( IMAGENAME );
+ todo.append( TAGNAME );
+ todo.append( ALBUMCAPTION );
+ todo.append( ALBUMCOLLECTION );
+ todo.append( IMAGECAPTION );
+ todo.append( RATING );
+
+ sqlQuery += '(';
+ TQValueListIterator<SKey> it;
+ it = todo.begin();
+ while ( it != todo.end() )
+ {
+ sqlQuery += subQuery(*it, rule.op, rule.val);
+ ++it;
+ if ( it != todo.end() )
+ sqlQuery += " OR ";
+ }
+ sqlQuery += ')';
+ }
+ }
+ else
+ {
+ sqlQuery += subQuery(rule.key, rule.op, rule.val);
+ }
+ }
+ else
+ {
+ sqlQuery += ' ' + *it + ' ';
+ }
+ }
+
+ return sqlQuery;
+}
+
+TQString tdeio_digikamsearch::subQuery(enum tdeio_digikamsearch::SKey key,
+ enum tdeio_digikamsearch::SOperator op,
+ const TQString& val) const
+{
+ TQString query;
+
+ switch (key)
+ {
+ case(ALBUM):
+ {
+ if (op == EQ || op == NE)
+ query = " (Images.dirid $$##$$ $$@@$$) ";
+ else // LIKE AND NLIKE
+ query = " (Images.dirid IN "
+ " (SELECT a.id FROM Albums a, Albums b "
+ " WHERE a.url $$##$$ '%' || b.url || '%' AND b.id = $$@@$$))";
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+ break;
+ }
+ case(ALBUMNAME):
+ {
+ query = " (Images.dirid IN "
+ " (SELECT id FROM Albums WHERE url $$##$$ $$@@$$)) ";
+ break;
+ }
+ case(ALBUMCAPTION):
+ {
+ query = " (Images.dirid IN "
+ " (SELECT id FROM Albums WHERE caption $$##$$ $$@@$$)) ";
+ break;
+ }
+ case(ALBUMCOLLECTION):
+ {
+ query = " (Images.dirid IN "
+ " (SELECT id FROM Albums WHERE collection $$##$$ $$@@$$)) ";
+ break;
+ }
+ case(TAG):
+ {
+ if (op == EQ)
+ query = " (Images.id IN "
+ " (SELECT imageid FROM ImageTags "
+ " WHERE tagid = $$@@$$)) ";
+ else if (op == NE)
+ query = " (Images.id NOT IN "
+ " (SELECT imageid FROM ImageTags "
+ " WHERE tagid = $$@@$$)) ";
+ else if (op == LIKE)
+ query = " (Images.id IN "
+ " (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
+ " WHERE TagsTree.pid = $$@@$$ or ImageTags.tagid = $$@@$$ )) ";
+ else // op == NLIKE
+ query = " (Images.id NOT IN "
+ " (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
+ " WHERE TagsTree.pid = $$@@$$ or ImageTags.tagid = $$@@$$ )) ";
+
+ // query = " (Images.id IN "
+ // " (SELECT imageid FROM ImageTags "
+ // " WHERE tagid $$##$$ $$@@$$)) ";
+
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+
+ break;
+ }
+ case(TAGNAME):
+ {
+ if (op == EQ)
+ query = " (Images.id IN "
+ " (SELECT imageid FROM ImageTags "
+ " WHERE tagid IN "
+ " (SELECT id FROM Tags WHERE name = $$@@$$))) ";
+ else if (op == NE)
+ query = " (Images.id NOT IN "
+ " (SELECT imageid FROM ImageTags "
+ " WHERE tagid IN "
+ " (SELECT id FROM Tags WHERE name = $$@@$$))) ";
+ else if (op == LIKE)
+ query = " (Images.id IN "
+ " (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
+ " WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) "
+ " OR ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) )) ";
+ else // op == NLIKE
+ query = " (Images.id NOT IN "
+ " (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
+ " WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) "
+ " OR ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) )) ";
+
+// query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+// + TQString::fromLatin1("'"));
+
+ break;
+ }
+ case(IMAGENAME):
+ {
+ query = " (Images.name $$##$$ $$@@$$) ";
+ break;
+ }
+ case(IMAGECAPTION):
+ {
+ query = " (Images.caption $$##$$ $$@@$$) ";
+ break;
+ }
+ case(IMAGEDATE):
+ {
+ query = " (Images.datetime $$##$$ $$@@$$) ";
+ break;
+ }
+ case (KEYWORD):
+ {
+ kdWarning() << "KEYWORD Detected which is not possible" << endl;
+ break;
+ }
+ case(RATING):
+ {
+ // For searches for `rating=0`, `rating>=0`, `rating<=0`,
+ // `rating <c`, `rating<=c`, `rating<>c` with c=1,2,3,4,5,
+ // special care has to be taken: Images which were never rated
+ // have no ImageProperties.property='Rating', but
+ // need to be treated like having a rating of 0.
+ // This is achieved by including all images which do
+ // not have property='Rating'.
+ if ( ( val=="0" and (op==EQ or op==GTE or op==LTE) )
+ or (val!="0" and (op==LT or op==LTE or op==NE) ) ) {
+ query = " ( (ImageProperties.value $$##$$ $$@@$$ and ImageProperties.property='Rating') or (Images.id NOT IN (SELECT imageid FROM ImageProperties WHERE property='Rating') ) )";
+ } else {
+ query = " (ImageProperties.value $$##$$ $$@@$$ and ImageProperties.property='Rating') ";
+ }
+ break;
+ }
+
+ }
+
+ if (key != TAG)
+ {
+ switch (op)
+ {
+ case(EQ):
+ {
+ query.replace("$$##$$", "=");
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+ break;
+ }
+ case(NE):
+ {
+ query.replace("$$##$$", "<>");
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+ break;
+ }
+ case(LT):
+ {
+ query.replace("$$##$$", "<");
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+ break;
+ }
+ case(GT):
+ {
+ query.replace("$$##$$", ">");
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+ break;
+ }
+ case(LTE):
+ {
+ query.replace("$$##$$", "<=");
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+ break;
+ }
+ case(GTE):
+ {
+ query.replace("$$##$$", ">=");
+ query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
+ + TQString::fromLatin1("'"));
+ break;
+ }
+ case(LIKE):
+ {
+ query.replace("$$##$$", "LIKE");
+ query.replace("$$@@$$", TQString::fromLatin1("'%") + escapeString(val)
+ + TQString::fromLatin1("%'"));
+ break;
+ }
+ case(NLIKE):
+ {
+ query.replace("$$##$$", "NOT LIKE");
+ query.replace("$$@@$$", TQString::fromLatin1("'%") + escapeString(val)
+ + TQString::fromLatin1("%'"));
+ break;
+ }
+ }
+ }
+
+ // special case for imagedate. If the key is imagedate and the operator is EQ,
+ // we need to split it into two rules
+ if (key == IMAGEDATE && op == EQ)
+ {
+ TQDate date = TQDate::fromString(val, TQt::ISODate);
+ if (!date.isValid())
+ return query;
+
+ query = TQString(" (Images.datetime > '%1' AND Images.datetime < '%2') ")
+ .arg(date.addDays(-1).toString(TQt::ISODate))
+ .arg(date.addDays( 1).toString(TQt::ISODate));
+ }
+
+ return query;
+}
+
+/* TDEIO slave registration */
+
+extern "C"
+{
+ DIGIKAM_EXPORT int kdemain(int argc, char **argv)
+ {
+ TDELocale::setMainCatalogue("digikam");
+ TDEInstance instance( "tdeio_digikamsearch" );
+ TDEGlobal::locale();
+
+ if (argc != 4)
+ {
+ kdDebug() << "Usage: tdeio_digikamsearch protocol domain-socket1 domain-socket2"
+ << endl;
+ exit(-1);
+ }
+
+ tdeio_digikamsearch slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ return 0;
+ }
+}
+
+TQString tdeio_digikamsearch::possibleDate(const TQString& str, bool& exact) const
+{
+ TQDate date = TQDate::fromString(str, TQt::ISODate);
+ if (date.isValid())
+ {
+ exact = true;
+ return date.toString(TQt::ISODate);
+ }
+
+ exact = false;
+
+ bool ok;
+ int num = str.toInt(&ok);
+ if (ok)
+ {
+ // ok. its an int, does it look like a year?
+ if (1970 <= num && num <= TQDate::currentDate().year())
+ {
+ // very sure its a year
+ return TQString("%1-%-%").arg(num);
+ }
+ }
+ else
+ {
+ // hmm... not a year. is it a particular month?
+ for (int i=1; i<=12; i++)
+ {
+ if (str.lower() == m_shortMonths[i-1] ||
+ str.lower() == m_longMonths[i-1])
+ {
+ TQString monGlob;
+ monGlob.sprintf("%.2d", i);
+ monGlob = "%-" + monGlob + "-%";
+ return monGlob;
+ }
+ }
+ }
+
+ return TQString();
+}
diff --git a/src/tdeioslave/digikamsearch.h b/src/tdeioslave/digikamsearch.h
new file mode 100644
index 00000000..a17a6c82
--- /dev/null
+++ b/src/tdeioslave/digikamsearch.h
@@ -0,0 +1,100 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a tdeio-slave to process search on digiKam albums
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ * ============================================================ */
+
+#ifndef DIGIKAMSEARCH_H
+#define DIGIKAMSEARCH_H
+
+// KDE includes.
+
+#include <tdeio/slavebase.h>
+
+// Local includes.
+
+#include "sqlitedb.h"
+
+class TQStringList;
+
+class tdeio_digikamsearch : public TDEIO::SlaveBase
+{
+
+public:
+
+ enum SKey
+ {
+ ALBUM = 0,
+ ALBUMNAME,
+ ALBUMCAPTION,
+ ALBUMCOLLECTION,
+ TAG,
+ TAGNAME,
+ IMAGENAME,
+ IMAGECAPTION,
+ IMAGEDATE,
+ KEYWORD,
+ RATING
+ };
+
+ enum SOperator
+ {
+ EQ = 0,
+ NE,
+ LT,
+ GT,
+ LIKE,
+ NLIKE,
+ LTE,
+ GTE
+ };
+
+public:
+
+ tdeio_digikamsearch(const TQCString &pool_socket, const TQCString &app_socket);
+ ~tdeio_digikamsearch();
+
+ void special(const TQByteArray& data);
+
+private:
+
+ TQString buildQuery(const KURL& url) const;
+
+ TQString subQuery(enum SKey key, enum SOperator op, const TQString& val) const;
+
+ TQString possibleDate(const TQString& str, bool& exact) const;
+
+private:
+
+ class RuleType
+ {
+ public:
+
+ SKey key;
+ SOperator op;
+ TQString val;
+ };
+
+ SqliteDB m_db;
+ TQString m_libraryPath;
+ TQString m_longMonths[12];
+ TQString m_shortMonths[12];
+};
+
+#endif /* DIGIKAMSEARCH_H */
diff --git a/src/tdeioslave/digikamsearch.protocol b/src/tdeioslave/digikamsearch.protocol
new file mode 100644
index 00000000..be2f3d4a
--- /dev/null
+++ b/src/tdeioslave/digikamsearch.protocol
@@ -0,0 +1,38 @@
+[Protocol]
+exec=tdeio_digikamsearch
+protocol=digikamsearch
+input=none
+output=filesystem,stream
+maxInstances=1
+Description=digiKam search tdeioslave
+Description[br]=tdeioslave klask evit Digikam
+Description[ca]=Kioslave de cerca del digiKam
+Description[da]=Digikam søge-tdeioslave
+Description[de]=digiKam Ein-/Ausgabemodul für digiKam-Suchen
+Description[el]=tdeioslave αναζήτησης του digiKam
+Description[es]=tdeioslave de digiKam de búsqueda
+Description[et]=DigiKami otsimise TDEIO-moodul
+Description[fa]=tdeioslave جستجوی digiKam
+Description[fi]=digiKamin tdeioslave-palvelu hauille
+Description[gl]=Kioslave de procura de digiKam
+Description[hr]=digiKam tdeioslave za pretraživanje
+Description[is]=digiKam leitar tdeioslave
+Description[it]=Kioslave delle ricerche di digiKam
+Description[ja]=digiKam 検索 tdeioslave
+Description[nds]=In-/Utgaavmoduul för de Söök vun digiKam
+Description[nl]=Digikam Zoeken tdeioslave
+Description[pa]=ਡਿਜ਼ੀਕੈਮ ਖੋਜ tdeioslave
+Description[pl]=Wtyczka protokołu do przeszukiwania digiKama
+Description[pt]='tdeioslave' de procura do digiKam
+Description[pt_BR]='tdeioslave' de procura do digiKam
+Description[ru]=Digikam tdeioslave - поиск
+Description[sk]=tdeioslave vyhľadávaní digiKamu
+Description[sr]=tdeioslave digiKam-ових тражења
+Description[sr@Latn]=tdeioslave digiKam-ovih traženja
+Description[sv]=I/O-slav för Digikams sökning
+Description[tr]=digiKam arama tdeioslave
+Description[uk]=Підлеглий В/В пошуку для digiKam
+Description[vi]=tdeioslave tìm kiếm digiKam
+Description[xx]=xxdigiKam search tdeioslavexx
+Class=:local
+Icon=digikam
diff --git a/src/tdeioslave/digikamtags.cpp b/src/tdeioslave/digikamtags.cpp
new file mode 100644
index 00000000..3fd14987
--- /dev/null
+++ b/src/tdeioslave/digikamtags.cpp
@@ -0,0 +1,325 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tdeio-slave to process tag query on
+ * digiKam albums.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+}
+
+// C++ includes.
+
+#include <cstdlib>
+#include <cstdio>
+#include <ctime>
+
+// TQt incudes.
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+#include <tqstring.h>
+#include <tqdir.h>
+#include <tqregexp.h>
+
+// KDE includes.
+
+#include <kinstance.h>
+#include <kdebug.h>
+#include <kurl.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+#include <tdeio/global.h>
+#include <tdefilemetainfo.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "digikamtags.h"
+
+tdeio_digikamtagsProtocol::tdeio_digikamtagsProtocol(const TQCString &pool_socket,
+ const TQCString &app_socket)
+ : SlaveBase("tdeio_digikamtags", pool_socket, app_socket)
+{
+}
+
+tdeio_digikamtagsProtocol::~tdeio_digikamtagsProtocol()
+{
+}
+
+static TQValueList<TQRegExp> makeFilterList( const TQString &filter )
+{
+ TQValueList<TQRegExp> regExps;
+ if ( filter.isEmpty() )
+ return regExps;
+
+ TQChar sep( ';' );
+ int i = filter.find( sep, 0 );
+ if ( i == -1 && filter.find( ' ', 0 ) != -1 )
+ sep = TQChar( ' ' );
+
+ TQStringList list = TQStringList::split( sep, filter );
+ TQStringList::Iterator it = list.begin();
+ while ( it != list.end() ) {
+ regExps << TQRegExp( (*it).stripWhiteSpace(), false, true );
+ ++it;
+ }
+ return regExps;
+}
+
+static bool matchFilterList( const TQValueList<TQRegExp>& filters,
+ const TQString &fileName )
+{
+ TQValueList<TQRegExp>::ConstIterator rit = filters.begin();
+ while ( rit != filters.end() ) {
+ if ( (*rit).exactMatch(fileName) )
+ return true;
+ ++rit;
+ }
+ return false;
+}
+
+void tdeio_digikamtagsProtocol::special(const TQByteArray& data)
+{
+ bool folders = (metaData("folders") == "yes");
+
+ TQString libraryPath;
+ KURL kurl;
+ TQString url;
+ TQString filter;
+ int getDimensions;
+ int tagID;
+ int recurseAlbums;
+ int recurseTags;
+
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> libraryPath;
+ ds >> kurl;
+ ds >> filter;
+ ds >> getDimensions;
+ ds >> recurseAlbums;
+ ds >> recurseTags;
+
+ url = kurl.path();
+
+ TQValueList<TQRegExp> regex = makeFilterList(filter);
+
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_db.closeDB();
+ m_db.openDB(libraryPath);
+ }
+
+ TQByteArray ba;
+
+ if (folders) // Special mode to stats all tag items
+ {
+ TQMap<int, int> tagsStatMap;
+ int tagID, imageID;
+ TQStringList values, allTagIDs;
+
+ // initialize allTagIDs with all existing tags from db to prevent
+ // wrong tag counters
+ m_db.execSql(TQString("SELECT id from Tags"), &allTagIDs);
+
+ for ( TQStringList::iterator it = allTagIDs.begin(); it != allTagIDs.end(); ++it)
+ {
+ tagID = (*it).toInt();
+ tagsStatMap.insert(tagID, 0);
+ }
+
+ // now we can count the tags assigned to images
+ m_db.execSql(TQString("SELECT ImageTags.tagid, Images.name FROM ImageTags, Images "
+ "WHERE ImageTags.imageid=Images.id"), &values);
+
+ for ( TQStringList::iterator it = values.begin(); it != values.end(); )
+ {
+ tagID = (*it).toInt();
+ ++it;
+
+ if ( matchFilterList( regex, *it ) )
+ {
+ TQMap<int, int>::iterator it2 = tagsStatMap.find(tagID);
+ if ( it2 == tagsStatMap.end() )
+ tagsStatMap.insert(tagID, 1);
+ else
+ tagsStatMap.replace(tagID, it2.data() + 1);
+ }
+
+ ++it;
+ }
+
+ TQDataStream os(ba, IO_WriteOnly);
+ os << tagsStatMap;
+ }
+ else
+ {
+ tagID = TQStringList::split('/',url).last().toInt();
+
+ TQStringList values;
+
+ if (recurseTags)
+ {
+ // Obtain all images with the given tag, or with this tag as a parent.
+ m_db.execSql( TQString( "SELECT DISTINCT Images.id, Images.name, Images.dirid, \n "
+ " Images.datetime, Albums.url \n "
+ " FROM Images, Albums \n "
+ " WHERE Images.id IN \n "
+ " (SELECT imageid FROM ImageTags \n "
+ " WHERE tagid=%1 \n "
+ " OR tagid IN (SELECT id FROM TagsTree WHERE pid=%2)) \n "
+ " AND Albums.id=Images.dirid \n " )
+ .arg(tagID)
+ .arg(tagID), &values );
+ }
+ else
+ {
+ // Obtain all images with the given tag
+ m_db.execSql( TQString( "SELECT DISTINCT Images.id, Images.name, Images.dirid, \n "
+ " Images.datetime, Albums.url \n "
+ " FROM Images, Albums \n "
+ " WHERE Images.id IN \n "
+ " (SELECT imageid FROM ImageTags \n "
+ " WHERE tagid=%1) \n "
+ " AND Albums.id=Images.dirid \n " )
+ .arg(tagID), &values );
+ }
+
+ TQ_LLONG imageid;
+ TQString name;
+ TQString path;
+ int dirid;
+ TQString date;
+ TQString purl;
+ TQSize dims;
+
+ int count = 0;
+ TQDataStream* os = new TQDataStream(ba, IO_WriteOnly);
+
+ struct stat stbuf;
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ imageid = (*it).toLongLong();
+ ++it;
+ name = *it;
+ ++it;
+ dirid = (*it).toInt();
+ ++it;
+ date = *it;
+ ++it;
+ purl = *it;
+ ++it;
+
+ if (!matchFilterList(regex, name))
+ continue;
+
+ path = m_libraryPath + purl + '/' + name;
+ if (::stat(TQFile::encodeName(path), &stbuf) != 0)
+ continue;
+
+ dims = TQSize();
+ if (getDimensions)
+ {
+ KFileMetaInfo metaInfo(path);
+ if (metaInfo.isValid())
+ {
+ if (metaInfo.containsGroup("Jpeg EXIF Data"))
+ {
+ dims = metaInfo.group("Jpeg EXIF Data").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("General"))
+ {
+ dims = metaInfo.group("General").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("Technical"))
+ {
+ dims = metaInfo.group("Technical").
+ item("Dimensions").value().toSize();
+ }
+ }
+ }
+
+ *os << imageid;
+ *os << dirid;
+ *os << name;
+ *os << date;
+ *os << static_cast<size_t>(stbuf.st_size);
+ *os << dims;
+
+ count++;
+
+ if (count > 200)
+ {
+ delete os;
+ os = 0;
+
+ SlaveBase::data(ba);
+ ba.resize(0);
+
+ count = 0;
+ os = new TQDataStream(ba, IO_WriteOnly);
+ }
+ }
+
+ delete os;
+ }
+
+ SlaveBase::data(ba);
+
+ finished();
+}
+
+/* TDEIO slave registration */
+
+extern "C"
+{
+ DIGIKAM_EXPORT int kdemain(int argc, char **argv)
+ {
+ TDELocale::setMainCatalogue("digikam");
+ TDEInstance instance( "tdeio_digikamtags" );
+ ( void ) TDEGlobal::locale();
+
+ kdDebug() << "*** tdeio_digikamtag started ***" << endl;
+
+ if (argc != 4) {
+ kdDebug() << "Usage: tdeio_digikamtags protocol domain-socket1 domain-socket2"
+ << endl;
+ exit(-1);
+ }
+
+ tdeio_digikamtagsProtocol slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ kdDebug() << "*** tdeio_digikamtags finished ***" << endl;
+ return 0;
+ }
+}
+
diff --git a/src/tdeioslave/digikamtags.h b/src/tdeioslave/digikamtags.h
new file mode 100644
index 00000000..9f9099d4
--- /dev/null
+++ b/src/tdeioslave/digikamtags.h
@@ -0,0 +1,60 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-09
+ * Description : a tdeio-slave to process tag query on
+ * digiKam albums.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIGIKAMTAGS_H
+#define DIGIKAMTAGS_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdeio/slavebase.h>
+
+// Local includes.
+
+#include "sqlitedb.h"
+
+class KURL;
+class TQCString;
+
+class tdeio_digikamtagsProtocol : public TDEIO::SlaveBase
+{
+public:
+
+ tdeio_digikamtagsProtocol(const TQCString &pool_socket,
+ const TQCString &app_socket);
+ virtual ~tdeio_digikamtagsProtocol();
+
+ void special(const TQByteArray& data);
+
+private:
+
+ SqliteDB m_db;
+ TQString m_libraryPath;
+};
+
+
+#endif /* DIGIKAMTAGS_H */
diff --git a/src/tdeioslave/digikamtags.protocol b/src/tdeioslave/digikamtags.protocol
new file mode 100644
index 00000000..88c33e65
--- /dev/null
+++ b/src/tdeioslave/digikamtags.protocol
@@ -0,0 +1,44 @@
+[Protocol]
+exec=tdeio_digikamtags
+protocol=digikamtags
+input=none
+output=filesystem
+listing=Name,Type
+reading=true
+writing=false
+makedir=false
+linking=false
+deleting=false
+moving=false
+maxInstances=1
+Description=digikam tags tdeioslave
+Description[ca]=Kioslave d'etiquetes del digiKam
+Description[da]=Digikam mærker-tdeioslave
+Description[de]=digiKam Ein-/Ausgabemodul für digiKam-Stichwörter
+Description[el]=tdeioslave ετικετών του digiKam
+Description[es]=tdeioslave de digiKampara etiquetas
+Description[et]=DigiKami siltide TDEIO-moodul
+Description[fa]=tdeioslave برچسبهای digikam
+Description[fi]=digiKamin tdeioslave-palvelu tunnisteille
+Description[gl]=Kioslave de marcas de digiKam
+Description[hr]=digiKam tdeioslave za oznake
+Description[is]=Digikam merkja tdeioslave
+Description[it]=Kioslave dei tag di digiKam
+Description[ja]=digiKam タグ tdeioslave
+Description[nds]=In-/Utgaavmoduul för Betekers vun digiKam
+Description[nl]=Digikam Tags tdeioslave
+Description[pa]=ਡਿਜ਼ੀਕੈਮ ਟੈਗ tdeioslave
+Description[pl]=Wtyczka protokołu do znaczników digiKama
+Description[pt]='tdeioslave' de marcas do digiKam
+Description[pt_BR]='tdeioslave' de marcas do digiKam
+Description[ru]=Digikam tdeioslave - метки
+Description[sk]=tdeioslave tagov digiKamu
+Description[sr]=tdeioslave Digikam-ових ознака
+Description[sr@Latn]=tdeioslave Digikam-ovih oznaka
+Description[sv]=I/O-slav för Digikams etiketter
+Description[tr]=digiKam etiketler tdeioslave
+Description[uk]=Підлеглий В/В міток для digiKam
+Description[vi]=tdeioslave thẻ digiKam
+Description[xx]=xxdigikam tags tdeioslavexx
+Class=:local
+Icon=digikam
diff --git a/src/tdeioslave/digikamthumbnail.cpp b/src/tdeioslave/digikamthumbnail.cpp
new file mode 100644
index 00000000..3dd4df28
--- /dev/null
+++ b/src/tdeioslave/digikamthumbnail.cpp
@@ -0,0 +1,635 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-15
+ * Description : digiKam TDEIO slave to get image thumbnails.
+ * This tdeio-slave support this freedesktop
+ * specification about thumbnails mamagement:
+ * http://jens.triq.net/thumbnail-spec
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define XMD_H
+#define PNG_BYTES_TO_CHECK 4
+
+// C++ includes.
+
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+
+// TQt includes.
+
+#include <tqcstring.h>
+#include <tqstring.h>
+#include <tqimage.h>
+#include <tqdatastream.h>
+#include <tqfile.h>
+#include <tqfileinfo.h>
+#include <tqdir.h>
+#include <tqwmatrix.h>
+#include <tqregexp.h>
+#include <tqapplication.h>
+
+// KDE includes.
+
+#include <kdebug.h>
+#include <kurl.h>
+#include <kinstance.h>
+#include <kimageio.h>
+#include <tdelocale.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+#include <kmdcodec.h>
+#include <tdetempfile.h>
+#include <ktrader.h>
+#include <klibloader.h>
+#include <kmimetype.h>
+#include <kprocess.h>
+#include <tdeio/global.h>
+#include <tdeio/thumbcreator.h>
+#include <tdefilemetainfo.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "dimg.h"
+#include "dmetadata.h"
+#include "jpegutils.h"
+#include "digikamthumbnail.h"
+#include "digikam_export.h"
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/time.h>
+#include <png.h>
+}
+
+using namespace TDEIO;
+using namespace Digikam;
+
+tdeio_digikamthumbnailProtocol::tdeio_digikamthumbnailProtocol(int argc, char** argv)
+ : SlaveBase("tdeio_digikamthumbnail", argv[2], argv[3])
+{
+ argc_ = argc;
+ argv_ = argv;
+ app_ = 0;
+ digiKamFingerPrint = TQString("Digikam Thumbnail Generator");
+ createThumbnailDirs();
+}
+
+tdeio_digikamthumbnailProtocol::~tdeio_digikamthumbnailProtocol()
+{
+}
+
+void tdeio_digikamthumbnailProtocol::get(const KURL& url)
+{
+ int size = metaData("size").toInt();
+ bool exif = (metaData("exif") == "yes");
+
+ cachedSize_ = (size <= 128) ? 128 : 256;
+
+ if (cachedSize_ <= 0)
+ {
+ error(TDEIO::ERR_INTERNAL, i18n("No or invalid size specified"));
+ kdWarning() << "No or invalid size specified" << endl;
+ return;
+ }
+
+ // generate the thumbnail path
+ TQString uri = KURL(url.path()).url(); // "file://" uri
+ KMD5 md5( TQFile::encodeName(uri).data() );
+ TQString thumbPath = (cachedSize_ == 128) ? smallThumbPath_ : bigThumbPath_;
+ thumbPath += TQFile::encodeName( md5.hexDigest() + ".png" ).data();
+
+ TQImage img;
+ bool regenerate = true;
+
+ // stat the original file
+ struct stat st;
+ if (::stat(TQFile::encodeName(url.path(-1)), &st) != 0)
+ {
+ error(TDEIO::ERR_INTERNAL, i18n("File does not exist"));
+ return;
+ }
+
+ // NOTE: if thumbnail have not been generated by digiKam (konqueror for example),
+ // force to recompute it, else we use it.
+
+ img = loadPNG(thumbPath);
+ if (!img.isNull())
+ {
+ if (img.text("Thumb::MTime") == TQString::number(st.st_mtime) &&
+ img.text("Software") == digiKamFingerPrint)
+ regenerate = false;
+ }
+
+ if (regenerate)
+ {
+ // To speed-up thumb extraction, we trying to load image using the file extension.
+ if ( !loadByExtension(img, url.path()) )
+ {
+ // Try JPEG loading : JPEG files without using Exif Thumb.
+ if ( !loadJPEG(img, url.path()) )
+ {
+ // Try to load with dcraw : RAW files.
+ if (!KDcrawIface::KDcraw::loadDcrawPreview(img, url.path()) )
+ {
+ // Try to load with DImg : TIFF, PNG, etc.
+ if (!loadDImg(img, url.path()) )
+ {
+ // Try to load with KDE thumbcreators : video files and others stuff.
+ loadKDEThumbCreator(img, url.path());
+ }
+ }
+ }
+ }
+
+ if (img.isNull())
+ {
+ error(TDEIO::ERR_INTERNAL, i18n("Cannot create thumbnail for %1")
+ .arg(url.prettyURL()));
+ kdWarning() << "Cannot create thumbnail for " << url.path() << endl;
+ return;
+ }
+
+ if (TQMAX(img.width(),img.height()) != cachedSize_)
+ img = img.smoothScale(cachedSize_, cachedSize_, TQImage::ScaleMin);
+
+ if (img.depth() != 32)
+ img = img.convertDepth(32);
+
+ if (exif)
+ exifRotate(url.path(), img);
+
+ img.setText(TQString("Thumb::URI").latin1(), 0, uri);
+ img.setText(TQString("Thumb::MTime").latin1(), 0, TQString::number(st.st_mtime));
+ img.setText(TQString("Software").latin1(), 0, digiKamFingerPrint);
+
+ KTempFile temp(thumbPath + "-digikam-", ".png");
+ if (temp.status() == 0)
+ {
+ img.save(temp.name(), "PNG", 0);
+ ::rename(TQFile::encodeName(temp.name()),
+ TQFile::encodeName(thumbPath));
+ }
+ }
+
+ img = img.smoothScale(size, size, TQImage::ScaleMin);
+
+ if (img.isNull())
+ {
+ error(TDEIO::ERR_INTERNAL, "Thumbnail is null");
+ return;
+ }
+
+ TQByteArray imgData;
+ TQDataStream stream( imgData, IO_WriteOnly );
+
+ const TQString shmid = metaData("shmid");
+ if (shmid.isEmpty())
+ {
+ stream << img;
+ }
+ else
+ {
+ void *shmaddr = shmat(shmid.toInt(), 0, 0);
+
+ if (shmaddr == (void *)-1)
+ {
+ error(TDEIO::ERR_INTERNAL, "Failed to attach to shared memory segment " + shmid);
+ kdWarning() << "Failed to attach to shared memory segment " << shmid << endl;
+ return;
+ }
+
+ if (img.width() * img.height() > cachedSize_ * cachedSize_)
+ {
+ error(TDEIO::ERR_INTERNAL, "Image is too big for the shared memory segment");
+ kdWarning() << "Image is too big for the shared memory segment" << endl;
+ shmdt((char*)shmaddr);
+ return;
+ }
+
+ stream << img.width() << img.height() << img.depth();
+ memcpy(shmaddr, img.bits(), img.numBytes());
+ shmdt((char*)shmaddr);
+ }
+
+ data(imgData);
+ finished();
+}
+
+bool tdeio_digikamthumbnailProtocol::loadByExtension(TQImage& image, const TQString& path)
+{
+ TQFileInfo fileInfo(path);
+ if (!fileInfo.exists())
+ return false;
+
+ // Try to use embedded preview image from metadata.
+ DMetadata metadata(path);
+ if (metadata.getImagePreview(image))
+ {
+ kdDebug() << "Use Exif/Iptc preview extraction. Size of image: "
+ << image.width() << "x" << image.height() << endl;
+ return true;
+ }
+
+ // Else, use the right way depending of image file extension.
+ TQString ext = fileInfo.extension(false).upper();
+#if KDCRAW_VERSION < 0x000106
+ TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
+#endif
+
+ if (!ext.isEmpty())
+ {
+ if (ext == TQString("JPEG") || ext == TQString("JPG") || ext == TQString("JPE"))
+ return (loadJPEG(image, path));
+ else if (ext == TQString("PNG"))
+ return (loadDImg(image, path));
+ else if (ext == TQString("TIFF") || ext == TQString("TIF"))
+ return (loadDImg(image, path));
+ else if (rawFilesExt.upper().contains(ext))
+ return (KDcrawIface::KDcraw::loadDcrawPreview(image, path));
+ }
+
+ return false;
+}
+
+bool tdeio_digikamthumbnailProtocol::loadJPEG(TQImage& image, const TQString& path)
+{
+ return Digikam::loadJPEGScaled(image, path, cachedSize_);
+}
+
+void tdeio_digikamthumbnailProtocol::exifRotate(const TQString& filePath, TQImage& thumb)
+{
+ // Rotate thumbnail based on metadata orientation information
+
+ DMetadata metadata(filePath);
+ DMetadata::ImageOrientation orientation = metadata.getImageOrientation();
+
+ if (orientation == DMetadata::ORIENTATION_NORMAL ||
+ orientation == DMetadata::ORIENTATION_UNSPECIFIED)
+ return;
+
+ TQWMatrix matrix;
+
+ switch (orientation)
+ {
+ case DMetadata::ORIENTATION_NORMAL:
+ case DMetadata::ORIENTATION_UNSPECIFIED:
+ break;
+
+ case DMetadata::ORIENTATION_HFLIP:
+ matrix.scale(-1, 1);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_180:
+ matrix.rotate(180);
+ break;
+
+ case DMetadata::ORIENTATION_VFLIP:
+ matrix.scale(1, -1);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_HFLIP:
+ matrix.scale(-1, 1);
+ matrix.rotate(90);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90:
+ matrix.rotate(90);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_VFLIP:
+ matrix.scale(1, -1);
+ matrix.rotate(90);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_270:
+ matrix.rotate(270);
+ break;
+ }
+
+ // transform accordingly
+ thumb = thumb.xForm(matrix);
+}
+
+TQImage tdeio_digikamthumbnailProtocol::loadPNG(const TQString& path)
+{
+ png_uint_32 w32, h32;
+ int w, h;
+ bool has_alpha;
+ bool has_grey;
+ FILE *f;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ int bit_depth, color_type, interlace_type;
+
+ has_alpha = 0;
+ has_grey = 0;
+
+ TQImage qimage;
+
+ f = fopen(path.latin1(), "rb");
+ if (!f)
+ return qimage;
+
+ unsigned char buf[PNG_BYTES_TO_CHECK];
+
+ fread(buf, 1, PNG_BYTES_TO_CHECK, f);
+ if (png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK))
+ {
+ fclose(f);
+ return qimage;
+ }
+ rewind(f);
+
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr)
+ {
+ fclose(f);
+ return qimage;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ {
+ png_destroy_read_struct(&png_ptr, NULL, NULL);
+ fclose(f);
+ return qimage;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ {
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ fclose(f);
+ return qimage;
+ }
+
+ png_init_io(png_ptr, f);
+ png_read_info(png_ptr, info_ptr);
+ png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) (&w32),
+ (png_uint_32 *) (&h32), &bit_depth, &color_type,
+ &interlace_type, NULL, NULL);
+
+ w = w32;
+ h = h32;
+
+ qimage.create(w, h, 32);
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ png_set_expand(png_ptr);
+
+ if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ has_alpha = 1;
+
+ if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ has_alpha = 1;
+ has_grey = 1;
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY)
+ has_grey = 1;
+
+ unsigned char **lines;
+ int i;
+
+ if (has_alpha)
+ png_set_expand(png_ptr);
+
+ if (TQImage::systemByteOrder() == TQImage::LittleEndian)
+ {
+ png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
+ png_set_bgr(png_ptr);
+ }
+ else
+ {
+ png_set_swap_alpha(png_ptr);
+ png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE);
+ }
+
+ /* 16bit color -> 8bit color */
+ if ( bit_depth == 16 )
+ png_set_strip_16(png_ptr);
+
+ /* pack all pixels to byte boundaires */
+
+ png_set_packing(png_ptr);
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
+ png_set_expand(png_ptr);
+
+ lines = (unsigned char **)malloc(h * sizeof(unsigned char *));
+ if (!lines)
+ {
+ png_read_end(png_ptr, info_ptr);
+ png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
+ fclose(f);
+ return qimage;
+ }
+
+ if (has_grey)
+ {
+ png_set_gray_to_rgb(png_ptr);
+ if (png_get_bit_depth(png_ptr, info_ptr) < 8)
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+ }
+
+ int sizeOfUint = sizeof(unsigned int);
+ for (i = 0 ; i < h ; i++)
+ lines[i] = ((unsigned char *)(qimage.bits())) + (i * w * sizeOfUint);
+
+ png_read_image(png_ptr, lines);
+ free(lines);
+
+ png_textp text_ptr;
+ int num_text=0;
+ png_get_text(png_ptr,info_ptr,&text_ptr,&num_text);
+ while (num_text--)
+ {
+ qimage.setText(text_ptr->key,0,text_ptr->text);
+ text_ptr++;
+ }
+
+ png_read_end(png_ptr, info_ptr);
+ png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
+ fclose(f);
+
+ return qimage;
+}
+
+// -- Load using DImg ---------------------------------------------------------------------
+
+bool tdeio_digikamthumbnailProtocol::loadDImg(TQImage& image, const TQString& path)
+{
+ Digikam::DImg dimg_im;
+
+ // to disable raw loader - does not work from ioslave
+ dimg_im.setAttribute("noeventloop", true);
+
+ if (!dimg_im.load(path))
+ {
+ return false;
+ }
+
+ image = dimg_im.copyTQImage();
+ org_width_ = image.width();
+ org_height_ = image.height();
+
+ if ( TQMAX(org_width_, org_height_) != cachedSize_ )
+ {
+ TQSize sz(dimg_im.width(), dimg_im.height());
+ sz.scale(cachedSize_, cachedSize_, TQSize::ScaleMin);
+ image.scale(sz.width(), sz.height());
+ }
+
+ new_width_ = image.width();
+ new_height_ = image.height();
+
+ image.setAlphaBuffer(true) ;
+
+ return true;
+}
+
+// -- Load using KDE API ---------------------------------------------------------------------
+
+bool tdeio_digikamthumbnailProtocol::loadKDEThumbCreator(TQImage& image, const TQString& path)
+{
+ // this sucks royally. some of the thumbcreators need an instance of
+ // app running so that they can use pixmap. till they get their
+ // code fixed, we will have to create a qapp instance.
+ if (!app_)
+ app_ = new TQApplication(argc_, argv_);
+
+ TQString mimeType = KMimeType::findByURL(path)->name();
+ if (mimeType.isEmpty())
+ {
+ kdDebug() << "Mimetype not found" << endl;
+ return false;
+ }
+
+ TQString mimeTypeAlt = mimeType.replace(TQRegExp("/.*"), "/*");
+
+ TQString plugin;
+
+ TDETrader::OfferList plugins = TDETrader::self()->query("ThumbCreator");
+ for (TDETrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
+ {
+ TQStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
+ for (TQStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
+ {
+ if ((*mt) == mimeType || (*mt) == mimeTypeAlt)
+ {
+ plugin=(*it)->library();
+ break;
+ }
+ }
+
+ if (!plugin.isEmpty())
+ break;
+ }
+
+ if (plugin.isEmpty())
+ {
+ kdDebug() << "No relevant plugin found " << endl;
+ return false;
+ }
+
+ KLibrary *library = KLibLoader::self()->library(TQFile::encodeName(plugin));
+ if (!library)
+ {
+ kdDebug() << "Plugin library not found " << plugin << endl;
+ return false;
+ }
+
+ ThumbCreator *creator = 0;
+ newCreator create = (newCreator)library->symbol("new_creator");
+ if (create)
+ creator = create();
+
+ if (!creator)
+ {
+ kdDebug() << "Cannot load ThumbCreator " << plugin << endl;
+ return false;
+ }
+
+ if (!creator->create(path, cachedSize_, cachedSize_, image))
+ {
+ kdDebug() << "Cannot create thumbnail for " << path << endl;
+ delete creator;
+ return false;
+ }
+
+ delete creator;
+ return true;
+}
+
+void tdeio_digikamthumbnailProtocol::createThumbnailDirs()
+{
+ TQString path = TQDir::homeDirPath() + "/.thumbnails/";
+
+ smallThumbPath_ = path + "normal/";
+ bigThumbPath_ = path + "large/";
+
+ TDEStandardDirs::makeDir(smallThumbPath_, 0700);
+ TDEStandardDirs::makeDir(bigThumbPath_, 0700);
+}
+
+// -- TDEIO slave registration ---------------------------------------------------------------------
+
+extern "C"
+{
+ DIGIKAM_EXPORT int kdemain(int argc, char **argv)
+ {
+ TDELocale::setMainCatalogue("digikam");
+ TDEInstance instance( "tdeio_digikamthumbnail" );
+ ( void ) TDEGlobal::locale();
+
+ if (argc != 4)
+ {
+ kdDebug() << "Usage: tdeio_digikamthumbnail protocol domain-socket1 domain-socket2"
+ << endl;
+ exit(-1);
+ }
+
+ KImageIO::registerFormats();
+
+ tdeio_digikamthumbnailProtocol slave(argc, argv);
+ slave.dispatchLoop();
+
+ return 0;
+ }
+}
diff --git a/src/tdeioslave/digikamthumbnail.h b/src/tdeioslave/digikamthumbnail.h
new file mode 100644
index 00000000..a83db66b
--- /dev/null
+++ b/src/tdeioslave/digikamthumbnail.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-15
+ * Description : digiKam TDEIO slave to get image thumbnails.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef _digikamthumbnail_H_
+#define _digikamthumbnail_H_
+
+// KDE includes.
+
+#include <tdeio/slavebase.h>
+
+class TQString;
+class TQImage;
+class TQApplication;
+
+class KURL;
+
+class tdeio_digikamthumbnailProtocol : public TDEIO::SlaveBase
+{
+
+public:
+
+ tdeio_digikamthumbnailProtocol(int argc, char** argv);
+ virtual ~tdeio_digikamthumbnailProtocol();
+ virtual void get(const KURL& url);
+
+private:
+
+ bool loadByExtension(TQImage& image, const TQString& path);
+ bool loadJPEG(TQImage& image, const TQString& path);
+ void exifRotate(const TQString& filePath, TQImage& thumb);
+ TQImage loadPNG(const TQString& path);
+ bool loadDImg(TQImage& image, const TQString& path);
+ bool loadKDEThumbCreator(TQImage& image, const TQString& path);
+ void createThumbnailDirs();
+
+private:
+
+ int cachedSize_;
+
+ int org_width_;
+ int org_height_;
+ int new_width_;
+ int new_height_;
+
+ int argc_;
+ char **argv_;
+
+ TQString digiKamFingerPrint;
+ TQString smallThumbPath_;
+ TQString bigThumbPath_;
+
+ TQApplication *app_;
+};
+
+#endif // _digikamthumbnail_H_
diff --git a/src/tdeioslave/digikamthumbnail.protocol b/src/tdeioslave/digikamthumbnail.protocol
new file mode 100644
index 00000000..3980f895
--- /dev/null
+++ b/src/tdeioslave/digikamthumbnail.protocol
@@ -0,0 +1,38 @@
+[Protocol]
+exec=tdeio_digikamthumbnail
+protocol=digikamthumbnail
+input=stream
+output=stream
+reading=true
+source=false
+Description=digiKam thumbnail tdeioslave
+Description[br]=tdeioslave ar skeudennigoù evit Digikam
+Description[ca]=Kioslave de miniatures del digiKam
+Description[da]=Digikam miniature-tdeioslave
+Description[de]=digiKam Ein-/Ausgabemodul für digiKam-Miniaturbilder
+Description[el]=tdeioslave εικόνων επισκόπησης του digiKam
+Description[es]=tdeioslave de digiKam de miniaturas
+Description[et]=DigiKami pisipiltide TDEIO-moodul
+Description[fa]=tdeioslave ریزنقش digiKam
+Description[fi]=digiKamin tdeioslave-palvelu pienoiskuville
+Description[gl]=Kioslave de miniaturas de digiKam
+Description[hr]=digiKam tdeioslave za sličice
+Description[is]=Digikam smámynda tdeioslave
+Description[it]=Kioslave delle miniature di digiKam
+Description[ja]=digiKam サムネイル tdeioslave
+Description[nds]=In-/Utgaavmoduul för Vöransichtbiller vun digiKam
+Description[nl]=Digikam Miniatuur tdeioslave
+Description[pa]=ਡਿਜ਼ੀਕੈਮ ਥੰਮਨੇਲ tdeioslave
+Description[pl]=Wtyczka protokołu miniaturek digiKama
+Description[pt]='tdeioslave' de miniaturas do digiKam
+Description[pt_BR]='tdeioslave' de miniaturas do digiKam
+Description[ru]=Digikam tdeioslave - миниатюры
+Description[sk]=tdeioslave miniatúr digiKamu
+Description[sr]=tdeioslave digiKam-ових умањених приказа
+Description[sr@Latn]=tdeioslave digiKam-ovih umanjenih prikaza
+Description[sv]=I/O-slav för Digikams miniatyrbilder
+Description[tr]=digiKam küçükresim tdeioslave
+Description[uk]=Підлеглий В/В мініатюр для digiKam
+Description[vi]=tdeioslave ảnh mẫu digiKam
+Description[xx]=xxdigiKam thumbnail tdeioslavexx
+Icon=digikam
diff --git a/src/tdeioslave/sqlitedb.cpp b/src/tdeioslave/sqlitedb.cpp
new file mode 100644
index 00000000..3df6b80b
--- /dev/null
+++ b/src/tdeioslave/sqlitedb.cpp
@@ -0,0 +1,197 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-05
+ * Description : TQSlite DB interface.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h" // Needed for NFS_HACK
+#endif
+
+// TQt includes.
+
+#include <tqstringlist.h>
+#include <tqdir.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <tdeio/global.h>
+#include <kdebug.h>
+
+// SQlite includes.
+
+#include "sqlite3.h"
+
+// Local includes.
+
+#include "sqlitedb.h"
+
+SqliteDB::SqliteDB()
+{
+ m_db = 0;
+}
+
+SqliteDB::~SqliteDB()
+{
+ closeDB();
+}
+
+void SqliteDB::openDB(const TQString& directory)
+{
+ if (m_db)
+ {
+ closeDB();
+ }
+
+ TQString dbPath = directory + "/digikam3.db";
+
+#ifdef NFS_HACK
+ dbPath = TQDir::homeDirPath() + "/.trinity/share/apps/digikam/" +
+ TDEIO::encodeFileName(TQDir::cleanDirPath(dbPath));
+#endif
+
+ sqlite3_open(TQFile::encodeName(dbPath), &m_db);
+ if (m_db == 0)
+ {
+ kdWarning() << "Cannot open database: "
+ << sqlite3_errmsg(m_db)
+ << endl;
+ }
+}
+
+void SqliteDB::closeDB()
+{
+ if (m_db)
+ {
+ sqlite3_close(m_db);
+ m_db = 0;
+ }
+}
+
+bool SqliteDB::execSql(const TQString& sql, TQStringList* const values,
+ TQString* errMsg, bool debug ) const
+{
+ if ( debug )
+ kdDebug() << "SQL-query: " << sql << endl;
+
+ if ( !m_db )
+ {
+ kdWarning() << k_funcinfo << "SQLite pointer == NULL"
+ << endl;
+ if (errMsg)
+ {
+ *errMsg = TQString::fromLatin1("SQLite database not open");
+ }
+ return false;
+ }
+
+ const char* tail;
+ sqlite3_stmt* stmt;
+ int error;
+
+ //compile SQL program to virtual machine
+ error = sqlite3_prepare(m_db, sql.utf8(), -1, &stmt, &tail);
+ if ( error != SQLITE_OK )
+ {
+ kdWarning() << k_funcinfo
+ << "sqlite_compile error: "
+ << sqlite3_errmsg(m_db)
+ << " on query: "
+ << sql << endl;
+ if (errMsg)
+ {
+ *errMsg = TQString::fromLatin1("sqlite_compile error: ") +
+ TQString::fromLatin1(sqlite3_errmsg(m_db)) +
+ TQString::fromLatin1(" on query: ") +
+ sql;
+ }
+ return false;
+ }
+
+ int cols = sqlite3_column_count(stmt);
+
+ while ( true )
+ {
+ error = sqlite3_step( stmt );
+
+ if ( error == SQLITE_DONE || error == SQLITE_ERROR )
+ break;
+
+ //iterate over columns
+ for ( int i = 0; values && i < cols; i++ )
+ {
+ *values << TQString::fromUtf8( (const char*)sqlite3_column_text( stmt, i ) );
+ }
+ }
+
+ sqlite3_finalize( stmt );
+
+ if ( error != SQLITE_DONE )
+ {
+ kdWarning() << "sqlite_step error: "
+ << sqlite3_errmsg( m_db )
+ << " on query: "
+ << sql << endl;
+ if (errMsg)
+ {
+ *errMsg = TQString::fromLatin1("sqlite_step error: ") +
+ TQString::fromLatin1(sqlite3_errmsg(m_db)) +
+ TQString::fromLatin1(" on query: ") +
+ sql;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+void SqliteDB::setSetting( const TQString& keyword, const TQString& value )
+{
+ execSql( TQString("REPLACE into Settings VALUES ('%1','%2');")
+ .arg( escapeString(keyword) )
+ .arg( escapeString(value) ));
+}
+
+TQString SqliteDB::getSetting( const TQString& keyword )
+{
+ TQStringList values;
+ execSql( TQString("SELECT value FROM Settings "
+ "WHERE keyword='%1';")
+ .arg(escapeString(keyword)),
+ &values );
+
+ if (values.isEmpty())
+ return TQString();
+ else
+ return values[0];
+}
+
+extern TQString escapeString(const TQString& str)
+{
+ TQString st(str);
+ st.replace( "'", "''" );
+ return st;
+}
+
+TQ_LLONG SqliteDB::lastInsertedRow() const
+{
+ return sqlite3_last_insert_rowid(m_db);
+}
diff --git a/src/tdeioslave/sqlitedb.h b/src/tdeioslave/sqlitedb.h
new file mode 100644
index 00000000..a3b793f8
--- /dev/null
+++ b/src/tdeioslave/sqlitedb.h
@@ -0,0 +1,58 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-05
+ * Description : TQSlite DB interface.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ * ============================================================ */
+
+#ifndef SQLITEDB_H
+#define SQLITEDB_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+class TQStringList;
+
+class SqliteDB
+{
+
+public:
+
+ SqliteDB();
+ ~SqliteDB();
+
+ void openDB(const TQString& directory);
+ void closeDB();
+
+ bool execSql(const TQString& sql, TQStringList* const values = 0,
+ TQString* const errMsg = 0, bool debug = false) const;
+
+ TQ_LLONG lastInsertedRow() const;
+
+ void setSetting( const TQString& keyword, const TQString& value );
+ TQString getSetting( const TQString& keyword );
+
+private:
+
+ mutable struct sqlite3* m_db;
+};
+
+extern TQString escapeString(const TQString& str);
+
+#endif /* SQLITEDB_H */
diff --git a/src/themedesigner/Makefile.am b/src/themedesigner/Makefile.am
new file mode 100644
index 00000000..98c9dce6
--- /dev/null
+++ b/src/themedesigner/Makefile.am
@@ -0,0 +1,19 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/imageproperties \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) $(all_includes)
+
+bin_PROGRAMS = digikamthemedesigner
+
+digikamthemedesigner_SOURCES = main.cpp mainwindow.cpp themedicongroupitem.cpp \
+ themediconitem.cpp themediconview.cpp
+
+digikamthemedesigner_LDADD = $(LIB_TQT) $(LIB_TDECORE) $(LIB_TDEUI) \
+ $(top_builddir)/src/digikam/libdigikam.la
+
+digikamthemedesigner_LDFLAGS = $(KDE_RPATH) $(all_libraries) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor $(LIB_TDEUTILS)
diff --git a/src/themedesigner/main.cpp b/src/themedesigner/main.cpp
new file mode 100644
index 00000000..27bc7459
--- /dev/null
+++ b/src/themedesigner/main.cpp
@@ -0,0 +1,75 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-12
+ * Description : main program from digiKam theme designer
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <tdeaboutdata.h>
+#include <tdecmdlineargs.h>
+#include <tdeglobal.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "mainwindow.h"
+
+static const char *description = Digikam::themeDesignerDescription();
+
+static TDECmdLineOptions options[] =
+{
+ { "+[URL]", I18N_NOOP( "Document to open." ), 0 },
+ TDECmdLineLastOption
+};
+
+int main(int argc, char** argv)
+{
+ TDEAboutData aboutData("digikamthemedesigner",
+ I18N_NOOP("digiKam Theme Designer"),
+ digikam_version,
+ description,
+ TDEAboutData::License_GPL,
+ Digikam::copyright(),
+ 0,
+ Digikam::webProjectUrl());
+
+ Digikam::authorsRegistration(aboutData);
+
+ TDECmdLineArgs::init(argc, argv, &aboutData);
+ TDECmdLineArgs::addCmdLineOptions(options);
+
+ TDEGlobal::locale()->setMainCatalogue( "digikam" );
+
+ TDEApplication app;
+ Digikam::MainWindow *im = new Digikam::MainWindow();
+ app.setMainWidget(im);
+ im->resize(800, 600);
+ im->show();
+
+ return app.exec();
+}
diff --git a/src/themedesigner/mainwindow.cpp b/src/themedesigner/mainwindow.cpp
new file mode 100644
index 00000000..f51a4f64
--- /dev/null
+++ b/src/themedesigner/mainwindow.cpp
@@ -0,0 +1,585 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-14
+ * Description : main digiKam theme designer window
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpushbutton.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqcombobox.h>
+#include <tqcheckbox.h>
+#include <tqframe.h>
+#include <tqsplitter.h>
+#include <tqheader.h>
+#include <tqlayout.h>
+#include <tqfileinfo.h>
+#include <tqtextstream.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kcolordialog.h>
+#include <kcolorbutton.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdefiledialog.h>
+#include <tdemessagebox.h>
+#include <kuser.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "folderview.h"
+#include "folderitem.h"
+#include "themediconview.h"
+#include "imagepropertiestab.h"
+#include "themeengine.h"
+#include "theme.h"
+#include "mainwindow.h"
+#include "mainwindow.moc"
+
+namespace Digikam
+{
+
+MainWindow::MainWindow()
+ : TQWidget(0, 0, WDestructiveClose)
+{
+ setCaption(i18n("digiKam Theme Designer"));
+
+ AlbumSettings *albumSettings = new AlbumSettings();
+ albumSettings->readSettings();
+
+ // Initialize theme engine ------------------------------------
+
+ ThemeEngine::instance()->scanThemes();
+ m_theme = new Theme(*(ThemeEngine::instance()->getCurrentTheme()));
+
+ // Actual views ------------------------------------------------
+
+ TQGridLayout* layout = new TQGridLayout(this);
+
+ TQSplitter* splitter = new TQSplitter(this);
+ splitter->setOrientation( TQt::Horizontal );
+ splitter->setSizePolicy(TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding));
+
+ m_folderView = new FolderView(splitter);
+ m_iconView = new ThemedIconView(splitter);
+ m_propView = new ImagePropertiesTab(splitter, false);
+
+ // Property Editor ---------------------------------------------
+
+ TQGroupBox *groupBox = new TQGroupBox(this);
+ TQVBoxLayout* vlay = new TQVBoxLayout(groupBox);
+
+ TQLabel* label1 = new TQLabel("Property: ", groupBox);
+ m_propertyCombo = new TQComboBox(groupBox);
+
+ m_bevelLabel = new TQLabel("Bevel: ", groupBox);
+ m_bevelCombo = new TQComboBox(groupBox);
+
+ m_gradientLabel = new TQLabel("Gradient: ", groupBox);
+ m_gradientCombo = new TQComboBox(groupBox);
+
+ m_begColorLabel = new TQLabel("Start Color: ", groupBox);
+ m_begColorBtn = new KColorButton(groupBox);
+
+ m_endColorLabel = new TQLabel("End Color: ", groupBox);
+ m_endColorBtn = new KColorButton(groupBox);
+
+ m_addBorderCheck = new TQCheckBox("Add Border", groupBox);
+
+ m_borderColorLabel = new TQLabel("Border Color: ", groupBox);
+ m_borderColorBtn = new KColorButton(groupBox);
+
+ vlay->setAlignment(TQt::AlignTop);
+ vlay->setSpacing(5);
+ vlay->setMargin(5);
+ vlay->addWidget(label1);
+ vlay->addWidget(m_propertyCombo);
+ vlay->addWidget(m_bevelLabel);
+ vlay->addWidget(m_bevelCombo);
+ vlay->addWidget(m_gradientLabel);
+ vlay->addWidget(m_gradientCombo);
+ vlay->addWidget(m_begColorLabel);
+ vlay->addWidget(m_begColorBtn);
+ vlay->addWidget(m_endColorLabel);
+ vlay->addWidget(m_endColorBtn);
+ vlay->addWidget( m_addBorderCheck );
+ vlay->addWidget(m_borderColorLabel);
+ vlay->addWidget(m_borderColorBtn);
+ vlay->addItem(new TQSpacerItem(10, 10, TQSizePolicy::Minimum, TQSizePolicy::Expanding));
+
+ layout->setMargin(5);
+ layout->setSpacing(5);
+ layout->addWidget(splitter, 0, 0);
+ layout->addWidget(groupBox, 0, 1);
+
+ // -------------------------------------------------------------
+
+ m_propertyCombo->insertItem( "Base", BASE);
+ m_propertyCombo->insertItem( "Regular Text", REGULARTEXT);
+ m_propertyCombo->insertItem( "Selected Text", SELECTEDTEXT);
+ m_propertyCombo->insertItem( "Special Regular Text", REGULARSPECIALTEXT);
+ m_propertyCombo->insertItem( "Special Selected Text", SELECTEDSPECIALTEXT);
+ m_propertyCombo->insertItem( "Banner", BANNER);
+ m_propertyCombo->insertItem( "Thumbnail Regular", THUMBNAILREGULAR);
+ m_propertyCombo->insertItem( "Thumbnail Selected", THUMBNAILSELECTED);
+ m_propertyCombo->insertItem( "ListView Regular", LISTVIEWREGULAR);
+ m_propertyCombo->insertItem( "ListView Selected", LISTVIEWSELECTED);
+
+ m_bevelCombo->insertItem( "Flat", FLAT);
+ m_bevelCombo->insertItem( "Raised", RAISED);
+ m_bevelCombo->insertItem( "Sunken", SUNKEN );
+
+ m_gradientCombo->insertItem( "Solid", SOLID);
+ m_gradientCombo->insertItem( "Horizontal", HORIZONTAL);
+ m_gradientCombo->insertItem( "Vertical", VERTICAL );
+ m_gradientCombo->insertItem( "Diagonal", DIAGONAL );
+
+ m_bevelMap[FLAT] = Theme::FLAT;
+ m_bevelMap[RAISED] = Theme::RAISED;
+ m_bevelMap[SUNKEN] = Theme::SUNKEN;
+
+ m_gradientMap[SOLID] = Theme::SOLID;
+ m_gradientMap[HORIZONTAL] = Theme::HORIZONTAL;
+ m_gradientMap[VERTICAL] = Theme::VERTICAL;
+ m_gradientMap[DIAGONAL] = Theme::DIAGONAL;
+
+ m_bevelReverseMap[Theme::FLAT] = FLAT;
+ m_bevelReverseMap[Theme::RAISED] = RAISED;
+ m_bevelReverseMap[Theme::SUNKEN] = SUNKEN;
+
+ m_gradientReverseMap[Theme::SOLID] = SOLID;
+ m_gradientReverseMap[Theme::HORIZONTAL] = HORIZONTAL;
+ m_gradientReverseMap[Theme::VERTICAL] = VERTICAL;
+ m_gradientReverseMap[Theme::DIAGONAL] = DIAGONAL;
+
+ m_begColorBtn->setColor(TQt::black);
+ m_endColorBtn->setColor(TQt::black);
+ m_borderColorBtn->setColor(TQt::black);
+
+ connect(m_propertyCombo, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotPropertyChanged()));
+ connect(m_bevelCombo, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotUpdateTheme()));
+ connect(m_gradientCombo, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotUpdateTheme()));
+
+ connect(m_begColorBtn, TQ_SIGNAL(changed(const TQColor&)),
+ this, TQ_SLOT(slotUpdateTheme()));
+ connect(m_endColorBtn, TQ_SIGNAL(changed(const TQColor&)),
+ this, TQ_SLOT(slotUpdateTheme()));
+ connect(m_addBorderCheck, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotUpdateTheme()));
+ connect(m_borderColorBtn, TQ_SIGNAL(changed(const TQColor&)),
+ this, TQ_SLOT(slotUpdateTheme()));
+
+ // Bottom button bar -------------------------------------------------------
+
+ TQHBoxLayout* buttonLayout = new TQHBoxLayout(0);
+ buttonLayout->setMargin(5);
+ buttonLayout->setSpacing(5);
+ buttonLayout->addItem(new TQSpacerItem(10, 10, TQSizePolicy::Expanding,
+ TQSizePolicy::Minimum));
+
+ TQPushButton* loadButton = new TQPushButton( this );
+ loadButton->setText( "&Load" );
+ buttonLayout->addWidget( loadButton );
+
+ TQPushButton* saveButton = new TQPushButton( this );
+ saveButton->setText( "&Save" );
+ buttonLayout->addWidget( saveButton );
+
+ TQPushButton* closeButton = new TQPushButton( this );
+ closeButton->setText( "&Close" );
+ buttonLayout->addWidget( closeButton );
+
+ layout->addMultiCellLayout(buttonLayout, 1, 1, 0, 1);
+
+ connect(loadButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotLoad()));
+ connect(saveButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotSave()));
+ connect(closeButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(close()));
+
+ // ------------------------------------------------------------------------
+
+ m_folderView->addColumn("My Albums");
+ m_folderView->setResizeMode(TQListView::LastColumn);
+ m_folderView->setRootIsDecorated(true);
+
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+ for (int i=0; i<10; i++)
+ {
+ FolderItem* folderItem = new FolderItem(m_folderView, TQString("Album %1").arg(i));
+ folderItem->setPixmap(0, iconLoader->loadIcon("folder", TDEIcon::NoGroup,
+ 32, TDEIcon::DefaultState, 0, true));
+ if (i == 2)
+ {
+ m_folderView->setSelected(folderItem, true);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+
+ slotPropertyChanged();
+ slotUpdateTheme();
+}
+
+MainWindow::~MainWindow()
+{
+}
+
+void MainWindow::slotLoad()
+{
+ TQString path = KFileDialog::getOpenFileName(TQString(), TQString(),
+ this);
+ if (path.isEmpty())
+ return;
+
+ TQFileInfo fi(path);
+ m_theme->name = fi.fileName();
+ m_theme->filePath = path;
+
+ ThemeEngine::instance()->setCurrentTheme(*m_theme, m_theme->name, true);
+ *m_theme = *(ThemeEngine::instance()->getCurrentTheme());
+ slotPropertyChanged();
+}
+
+void MainWindow::slotSave()
+{
+ TQString path = KFileDialog::getSaveFileName(TQString(), TQString(), this);
+ if (path.isEmpty())
+ return;
+
+ TQFile file(path);
+ if (!file.open(IO_WriteOnly))
+ {
+ KMessageBox::error(this, "Failed to open file for writing");
+ return;
+ }
+
+ TQFileInfo fi(path);
+ m_theme->name = fi.fileName();
+ m_theme->filePath = path;
+
+ ThemeEngine::instance()->setCurrentTheme(*m_theme, m_theme->name, false);
+ ThemeEngine::instance()->saveTheme();
+}
+
+void MainWindow::slotPropertyChanged()
+{
+ m_bevelCombo->blockSignals(true);
+ m_gradientCombo->blockSignals(true);
+ m_begColorBtn->blockSignals(true);
+ m_endColorBtn->blockSignals(true);
+ m_addBorderCheck->blockSignals(true);
+ m_borderColorBtn->blockSignals(true);
+
+ m_bevelCombo->setEnabled(false);
+ m_bevelLabel->setEnabled(false);
+ m_gradientCombo->setEnabled(false);
+ m_gradientLabel->setEnabled(false);
+ m_begColorBtn->setEnabled(false);
+ m_begColorLabel->setEnabled(false);
+ m_endColorBtn->setEnabled(false);
+ m_endColorLabel->setEnabled(false);
+ m_addBorderCheck->setEnabled(false);
+ m_borderColorBtn->setEnabled(false);
+ m_borderColorLabel->setEnabled(false);
+
+ switch(m_propertyCombo->currentItem())
+ {
+ case(BASE):
+ {
+ m_begColorLabel->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_begColorBtn->setColor(m_theme->baseColor);
+ break;
+ }
+ case(REGULARTEXT):
+ {
+ m_begColorLabel->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_begColorBtn->setColor(m_theme->textRegColor);
+ break;
+ }
+ case(SELECTEDTEXT):
+ {
+ m_begColorLabel->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_begColorBtn->setColor(m_theme->textSelColor);
+ break;
+ }
+ case(REGULARSPECIALTEXT):
+ {
+ m_begColorLabel->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_begColorBtn->setColor(m_theme->textSpecialRegColor);
+ break;
+ }
+ case(SELECTEDSPECIALTEXT):
+ {
+ m_begColorLabel->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_begColorBtn->setColor(m_theme->textSpecialSelColor);
+ break;
+ }
+ case(BANNER):
+ {
+ m_bevelCombo->setEnabled(true);
+ m_gradientCombo->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_endColorBtn->setEnabled(true);
+ m_addBorderCheck->setEnabled(true);
+ m_borderColorBtn->setEnabled(true);
+ m_bevelLabel->setEnabled(true);
+ m_gradientLabel->setEnabled(true);
+ m_begColorLabel->setEnabled(true);
+ m_endColorLabel->setEnabled(true);
+ m_borderColorLabel->setEnabled(true);
+
+ m_bevelCombo->setCurrentItem(m_bevelReverseMap[m_theme->bannerBevel]);
+ m_gradientCombo->setCurrentItem(m_gradientReverseMap[m_theme->bannerGrad]);
+
+ m_begColorBtn->setColor(m_theme->bannerColor);
+ m_endColorBtn->setColor(m_theme->bannerColorTo);
+
+ m_addBorderCheck->setChecked(m_theme->bannerBorder);
+ m_borderColorBtn->setColor(m_theme->bannerBorderColor);
+
+ break;
+ }
+ case(THUMBNAILREGULAR):
+ {
+ m_bevelCombo->setEnabled(true);
+ m_gradientCombo->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_endColorBtn->setEnabled(true);
+ m_addBorderCheck->setEnabled(true);
+ m_borderColorBtn->setEnabled(true);
+ m_bevelLabel->setEnabled(true);
+ m_gradientLabel->setEnabled(true);
+ m_begColorLabel->setEnabled(true);
+ m_endColorLabel->setEnabled(true);
+ m_borderColorLabel->setEnabled(true);
+
+ m_bevelCombo->setCurrentItem(m_bevelReverseMap[m_theme->thumbRegBevel]);
+ m_gradientCombo->setCurrentItem(m_gradientReverseMap[m_theme->thumbRegGrad]);
+
+ m_begColorBtn->setColor(m_theme->thumbRegColor);
+ m_endColorBtn->setColor(m_theme->thumbRegColorTo);
+
+ m_addBorderCheck->setChecked(m_theme->thumbRegBorder);
+ m_borderColorBtn->setColor(m_theme->thumbRegBorderColor);
+
+ break;
+ }
+ case(THUMBNAILSELECTED):
+ {
+ m_bevelCombo->setEnabled(true);
+ m_gradientCombo->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_endColorBtn->setEnabled(true);
+ m_addBorderCheck->setEnabled(true);
+ m_borderColorBtn->setEnabled(true);
+ m_bevelLabel->setEnabled(true);
+ m_gradientLabel->setEnabled(true);
+ m_begColorLabel->setEnabled(true);
+ m_endColorLabel->setEnabled(true);
+ m_borderColorLabel->setEnabled(true);
+
+ m_bevelCombo->setCurrentItem(m_bevelReverseMap[m_theme->thumbSelBevel]);
+ m_gradientCombo->setCurrentItem(m_gradientReverseMap[m_theme->thumbSelGrad]);
+
+ m_begColorBtn->setColor(m_theme->thumbSelColor);
+ m_endColorBtn->setColor(m_theme->thumbSelColorTo);
+
+ m_addBorderCheck->setChecked(m_theme->thumbSelBorder);
+ m_borderColorBtn->setColor(m_theme->thumbSelBorderColor);
+
+ break;
+ }
+ case(LISTVIEWREGULAR):
+ {
+ m_bevelCombo->setEnabled(true);
+ m_gradientCombo->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_endColorBtn->setEnabled(true);
+ m_addBorderCheck->setEnabled(true);
+ m_borderColorBtn->setEnabled(true);
+ m_bevelLabel->setEnabled(true);
+ m_gradientLabel->setEnabled(true);
+ m_begColorLabel->setEnabled(true);
+ m_endColorLabel->setEnabled(true);
+ m_borderColorLabel->setEnabled(true);
+
+ m_bevelCombo->setCurrentItem(m_bevelReverseMap[m_theme->listRegBevel]);
+ m_gradientCombo->setCurrentItem(m_gradientReverseMap[m_theme->listRegGrad]);
+
+ m_begColorBtn->setColor(m_theme->listRegColor);
+ m_endColorBtn->setColor(m_theme->listRegColorTo);
+
+ m_addBorderCheck->setChecked(m_theme->listRegBorder);
+ m_borderColorBtn->setColor(m_theme->listRegBorderColor);
+
+ break;
+ }
+ case(LISTVIEWSELECTED):
+ {
+ m_bevelCombo->setEnabled(true);
+ m_gradientCombo->setEnabled(true);
+ m_begColorBtn->setEnabled(true);
+ m_endColorBtn->setEnabled(true);
+ m_addBorderCheck->setEnabled(true);
+ m_borderColorBtn->setEnabled(true);
+ m_bevelLabel->setEnabled(true);
+ m_gradientLabel->setEnabled(true);
+ m_begColorLabel->setEnabled(true);
+ m_endColorLabel->setEnabled(true);
+ m_borderColorLabel->setEnabled(true);
+
+ m_bevelCombo->setCurrentItem(m_bevelReverseMap[m_theme->listSelBevel]);
+ m_gradientCombo->setCurrentItem(m_gradientReverseMap[m_theme->listSelGrad]);
+
+ m_begColorBtn->setColor(m_theme->listSelColor);
+ m_endColorBtn->setColor(m_theme->listSelColorTo);
+
+ m_addBorderCheck->setChecked(m_theme->listSelBorder);
+ m_borderColorBtn->setColor(m_theme->listSelBorderColor);
+
+ break;
+ }
+ };
+
+ m_bevelCombo->blockSignals(false);
+ m_gradientCombo->blockSignals(false);
+ m_begColorBtn->blockSignals(false);
+ m_endColorBtn->blockSignals(false);
+ m_addBorderCheck->blockSignals(false);
+ m_borderColorBtn->blockSignals(false);
+}
+
+void MainWindow::slotUpdateTheme()
+{
+ switch(m_propertyCombo->currentItem())
+ {
+ case(BASE):
+ {
+ m_theme->baseColor = m_begColorBtn->color();
+ break;
+ }
+ case(REGULARTEXT):
+ {
+ m_theme->textRegColor = m_begColorBtn->color();
+ break;
+ }
+ case(SELECTEDTEXT):
+ {
+ m_theme->textSelColor = m_begColorBtn->color();
+ break;
+ }
+ case(REGULARSPECIALTEXT):
+ {
+ m_theme->textSpecialRegColor = m_begColorBtn->color();
+ break;
+ }
+ case(SELECTEDSPECIALTEXT):
+ {
+ m_theme->textSpecialSelColor = m_begColorBtn->color();
+ break;
+ }
+ case(BANNER):
+ {
+ m_theme->bannerBevel = (Theme::Bevel) m_bevelMap[m_bevelCombo->currentItem()];
+ m_theme->bannerGrad = (Theme::Gradient) m_gradientMap[m_gradientCombo->currentItem()];
+
+ m_theme->bannerColor = m_begColorBtn->color();
+ m_theme->bannerColorTo = m_endColorBtn->color();
+
+ m_theme->bannerBorder = m_addBorderCheck->isChecked();
+ m_theme->bannerBorderColor = m_borderColorBtn->color();
+
+ break;
+ }
+ case(THUMBNAILREGULAR):
+ {
+ m_theme->thumbRegBevel = (Theme::Bevel) m_bevelMap[m_bevelCombo->currentItem()];
+ m_theme->thumbRegGrad = (Theme::Gradient) m_gradientMap[m_gradientCombo->currentItem()];
+
+ m_theme->thumbRegColor = m_begColorBtn->color();
+ m_theme->thumbRegColorTo = m_endColorBtn->color();
+
+ m_theme->thumbRegBorder = m_addBorderCheck->isChecked();
+ m_theme->thumbRegBorderColor = m_borderColorBtn->color();
+
+ break;
+ }
+ case(THUMBNAILSELECTED):
+ {
+ m_theme->thumbSelBevel = (Theme::Bevel) m_bevelMap[m_bevelCombo->currentItem()];
+ m_theme->thumbSelGrad = (Theme::Gradient) m_gradientMap[m_gradientCombo->currentItem()];
+
+ m_theme->thumbSelColor = m_begColorBtn->color();
+ m_theme->thumbSelColorTo = m_endColorBtn->color();
+
+ m_theme->thumbSelBorder = m_addBorderCheck->isChecked();
+ m_theme->thumbSelBorderColor = m_borderColorBtn->color();
+
+ break;
+ }
+ case(LISTVIEWREGULAR):
+ {
+ m_theme->listRegBevel = (Theme::Bevel) m_bevelMap[m_bevelCombo->currentItem()];
+ m_theme->listRegGrad = (Theme::Gradient) m_gradientMap[m_gradientCombo->currentItem()];
+
+ m_theme->listRegColor = m_begColorBtn->color();
+ m_theme->listRegColorTo = m_endColorBtn->color();
+
+ m_theme->listRegBorder = m_addBorderCheck->isChecked();
+ m_theme->listRegBorderColor = m_borderColorBtn->color();
+
+ break;
+ }
+ case(LISTVIEWSELECTED):
+ {
+ m_theme->listSelBevel = (Theme::Bevel) m_bevelMap[m_bevelCombo->currentItem()];
+ m_theme->listSelGrad = (Theme::Gradient) m_gradientMap[m_gradientCombo->currentItem()];
+
+ m_theme->listSelColor = m_begColorBtn->color();
+ m_theme->listSelColorTo = m_endColorBtn->color();
+
+ m_theme->listSelBorder = m_addBorderCheck->isChecked();
+ m_theme->listSelBorderColor = m_borderColorBtn->color();
+
+ break;
+ }
+ };
+
+ ThemeEngine::instance()->setCurrentTheme(*m_theme, "Digikam ThemeEditor Theme");
+}
+
+} // NameSpace Digikam
diff --git a/src/themedesigner/mainwindow.h b/src/themedesigner/mainwindow.h
new file mode 100644
index 00000000..087dac20
--- /dev/null
+++ b/src/themedesigner/mainwindow.h
@@ -0,0 +1,125 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-14
+ * Description : main digiKam theme designer window
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqmap.h>
+
+class TQComboBox;
+class TQCheckBox;
+class TQLabel;
+class KColorButton;
+
+namespace Digikam
+{
+
+class ImagePropertiesTab;
+class FolderView;
+class ThemedIconView;
+class Theme;
+
+class MainWindow : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum PROPERTY
+ {
+ BASE = 0,
+ REGULARTEXT,
+ SELECTEDTEXT,
+ REGULARSPECIALTEXT,
+ SELECTEDSPECIALTEXT,
+ BANNER,
+ THUMBNAILREGULAR,
+ THUMBNAILSELECTED,
+ LISTVIEWREGULAR,
+ LISTVIEWSELECTED
+ };
+
+ enum BEVEL
+ {
+ FLAT = 0,
+ RAISED,
+ SUNKEN
+ };
+
+ enum GRADIENT
+ {
+ SOLID = 0,
+ HORIZONTAL,
+ VERTICAL,
+ DIAGONAL
+ };
+
+public:
+
+ MainWindow();
+ ~MainWindow();
+
+private slots:
+
+ void slotLoad();
+ void slotSave();
+ void slotPropertyChanged();
+ void slotUpdateTheme();
+
+private:
+
+ TQLabel *m_bevelLabel;
+ TQLabel *m_gradientLabel;
+ TQLabel *m_begColorLabel;
+ TQLabel *m_endColorLabel;
+ TQLabel *m_borderColorLabel;
+
+ TQComboBox *m_propertyCombo;
+ TQComboBox *m_bevelCombo;
+ TQComboBox *m_gradientCombo;
+
+ TQCheckBox *m_addBorderCheck;
+
+ TQMap<int,int> m_bevelMap;
+ TQMap<int,int> m_bevelReverseMap;
+ TQMap<int,int> m_gradientMap;
+ TQMap<int,int> m_gradientReverseMap;
+
+ KColorButton *m_endColorBtn;
+ KColorButton *m_begColorBtn;
+ KColorButton *m_borderColorBtn;
+
+ FolderView *m_folderView;
+ ThemedIconView *m_iconView;
+ ImagePropertiesTab *m_propView;
+ Theme *m_theme;
+};
+
+} // NameSpace Digikam
+
+#endif // MAINWINDOW_H
diff --git a/src/themedesigner/themedicongroupitem.cpp b/src/themedesigner/themedicongroupitem.cpp
new file mode 100644
index 00000000..1da37fd3
--- /dev/null
+++ b/src/themedesigner/themedicongroupitem.cpp
@@ -0,0 +1,105 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-15
+ * Description : themed icon group item
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqpainter.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "themeengine.h"
+#include "themediconview.h"
+#include "themedicongroupitem.h"
+
+namespace Digikam
+{
+
+ThemedIconGroupItem::ThemedIconGroupItem(ThemedIconView* view)
+ : IconGroupItem(view), m_view(view)
+{
+}
+
+ThemedIconGroupItem::~ThemedIconGroupItem()
+{
+}
+
+void ThemedIconGroupItem::paintBanner()
+{
+ TQRect r(0, 0, rect().width(), rect().height());
+
+ TQPixmap pix(m_view->bannerPixmap());
+
+ TQFont fn(m_view->font());
+ fn.setBold(true);
+ int fnSize = fn.pointSize();
+ bool usePointSize;
+ if (fnSize > 0)
+ {
+ fn.setPointSize(fnSize+2);
+ usePointSize = true;
+ }
+ else
+ {
+ fnSize = fn.pixelSize();
+ fn.setPixelSize(fnSize+2);
+ usePointSize = false;
+ }
+
+ TQPainter p(&pix);
+ p.setPen(ThemeEngine::instance()->textSelColor());
+ p.setFont(fn);
+
+ TQRect tr;
+ p.drawText(5, 5, r.width(), r.height(),
+ TQt::AlignLeft | TQt::AlignTop, i18n("Album Banner"),
+ -1, &tr);
+
+ r.setY(tr.height() + 2);
+
+ if (usePointSize)
+ fn.setPointSize(m_view->font().pointSize());
+ else
+ fn.setPixelSize(m_view->font().pixelSize());
+
+ fn.setBold(false);
+ p.setFont(fn);
+
+ p.drawText(5, r.y(), r.width(), r.height(),
+ TQt::AlignLeft | TQt::AlignVCenter, i18n("July 2007 - 10 Items"));
+
+ p.end();
+
+ r = rect();
+ r = TQRect(iconView()->contentsToViewport(TQPoint(r.x(), r.y())),
+ TQSize(r.width(), r.height()));
+
+ bitBlt(iconView()->viewport(), r.x(), r.y(), &pix,
+ 0, 0, r.width(), r.height());
+}
+
+} // NameSpace Digikam
diff --git a/src/themedesigner/themedicongroupitem.h b/src/themedesigner/themedicongroupitem.h
new file mode 100644
index 00000000..a54f2bc6
--- /dev/null
+++ b/src/themedesigner/themedicongroupitem.h
@@ -0,0 +1,54 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-15
+ * Description : themed icon group item
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef THEMEDICONGROUPITEM_H
+#define THEMEDICONGROUPITEM_H
+
+// Local includes.
+
+#include "icongroupitem.h"
+
+namespace Digikam
+{
+
+class ThemedIconView;
+
+class ThemedIconGroupItem : public IconGroupItem
+{
+public:
+
+ ThemedIconGroupItem(ThemedIconView* view);
+ ~ThemedIconGroupItem();
+
+protected:
+
+ void paintBanner();
+
+private:
+
+ ThemedIconView* m_view;
+};
+
+} // NameSpace Digikam
+
+#endif /* THEMEDICONGROUPITEM_H */
diff --git a/src/themedesigner/themediconitem.cpp b/src/themedesigner/themediconitem.cpp
new file mode 100644
index 00000000..2ed7df80
--- /dev/null
+++ b/src/themedesigner/themediconitem.cpp
@@ -0,0 +1,195 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-15
+ * Description : themed icon item
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpalette.h>
+#include <tqpen.h>
+#include <tqfontmetrics.h>
+#include <tqfont.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "themeengine.h"
+#include "themediconview.h"
+#include "themediconitem.h"
+
+namespace Digikam
+{
+
+static void dateToString(const TQDateTime& datetime, TQString& str)
+{
+ str = TDEGlobal::locale()->formatDateTime(datetime, true, false);
+}
+
+static TQString squeezedText(TQPainter* p, int width, const TQString& text)
+{
+ TQString fullText(text);
+ fullText.replace("\n"," ");
+ TQFontMetrics fm(p->fontMetrics());
+ int textWidth = fm.width(fullText);
+ if (textWidth > width)
+ {
+ // start with the dots only
+ TQString squeezedText = "...";
+ int squeezedWidth = fm.width(squeezedText);
+
+ // estimate how many letters we can add to the dots on both sides
+ int letters = fullText.length() * (width - squeezedWidth) / textWidth;
+ if (width < squeezedWidth) letters=1;
+ squeezedText = fullText.left(letters) + "...";
+ squeezedWidth = fm.width(squeezedText);
+
+ if (squeezedWidth < width)
+ {
+ // we estimated too short
+ // add letters while text < label
+ do
+ {
+ letters++;
+ squeezedText = fullText.left(letters) + "...";
+ squeezedWidth = fm.width(squeezedText);
+ }
+ while (squeezedWidth < width);
+
+ letters--;
+ squeezedText = fullText.left(letters) + "...";
+ }
+ else if (squeezedWidth > width)
+ {
+ // we estimated too long
+ // remove letters while text > label
+ do
+ {
+ letters--;
+ squeezedText = fullText.left(letters) + "...";
+ squeezedWidth = fm.width(squeezedText);
+ }
+ while (letters && squeezedWidth > width);
+ }
+
+ if (letters >= 5)
+ {
+ return squeezedText;
+ }
+ }
+
+ return fullText;
+}
+
+ThemedIconItem::ThemedIconItem(IconGroupItem* parent)
+ : IconItem(parent)
+{
+}
+
+ThemedIconItem::~ThemedIconItem()
+{
+}
+
+void ThemedIconItem::paintItem()
+{
+ ThemedIconView* view = (ThemedIconView*)iconView();
+
+ TQPixmap pix;
+ TQRect r;
+
+ if (isSelected())
+ pix = *(view->itemBaseSelPixmap());
+ else
+ pix = *(view->itemBaseRegPixmap());
+
+ ThemeEngine* te = ThemeEngine::instance();
+
+ TQPainter p(&pix);
+ p.setPen(isSelected() ? te->textSelColor() : te->textRegColor());
+
+ {
+ r = view->itemPixmapRect();
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+ TQPixmap thumbnail = iconLoader->loadIcon("colors", TDEIcon::NoGroup,
+ 100, TDEIcon::DefaultState, 0, true);
+
+ p.drawPixmap(r.x() + (r.width()-thumbnail.width())/2,
+ r.y() + (r.height()-thumbnail.height())/2,
+ thumbnail);
+ }
+
+ r = view->itemNameRect();
+ p.setFont(view->itemFontReg());
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(), "IMG_00.JPG"));
+
+ p.setFont(view->itemFontCom());
+ r = view->itemCommentsRect();
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(), i18n("Photo caption")));
+
+ p.setFont(view->itemFontXtra());
+ {
+ TQDateTime date = TQDateTime::currentDateTime();
+
+ r = view->itemDateRect();
+ p.setFont(view->itemFontXtra());
+ TQString str;
+ dateToString(date, str);
+ p.drawText(r, TQt::AlignCenter, squeezedText(&p, r.width(), str));
+ }
+
+ p.setFont(view->itemFontCom());
+ p.setPen(isSelected() ? te->textSpecialSelColor() : te->textSpecialRegColor());
+
+ {
+ TQString tags = i18n("Events, Places, Vacation");
+
+ r = view->itemTagRect();
+ p.drawText(r, TQt::AlignCenter,
+ squeezedText(&p, r.width(), tags));
+ }
+
+
+ if (this == view->currentItem())
+ {
+ p.setPen(TQPen(isSelected() ? te->textSelColor() : te->textRegColor(),
+ 0, TQt::DotLine));
+ p.drawRect(1, 1, pix.width()-2, pix.height()-2);
+ }
+
+ p.end();
+
+ r = rect();
+ r = TQRect(view->contentsToViewport(TQPoint(r.x(), r.y())),
+ TQSize(r.width(), r.height()));
+
+ bitBlt(view->viewport(), r.x(), r.y(), &pix,
+ 0, 0, r.width(), r.height());
+}
+
+} // NameSpace Digikam
diff --git a/src/themedesigner/themediconitem.h b/src/themedesigner/themediconitem.h
new file mode 100644
index 00000000..0d54cceb
--- /dev/null
+++ b/src/themedesigner/themediconitem.h
@@ -0,0 +1,48 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-15
+ * Description : themed icon item
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef THEMEDICONITEM_H
+#define THEMEDICONITEM_H
+
+// Local includes.
+
+#include "iconitem.h"
+
+namespace Digikam
+{
+
+class ThemedIconItem : public IconItem
+{
+public:
+
+ ThemedIconItem(IconGroupItem* parent);
+ ~ThemedIconItem();
+
+protected:
+
+ void paintItem();
+};
+
+} // NameSpace Digikam
+
+#endif /* THEMEDICONITEM_H */
diff --git a/src/themedesigner/themediconview.cpp b/src/themedesigner/themediconview.cpp
new file mode 100644
index 00000000..ef5759e6
--- /dev/null
+++ b/src/themedesigner/themediconview.cpp
@@ -0,0 +1,304 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-15
+ * Description : themed icon view
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "themeengine.h"
+#include "themedicongroupitem.h"
+#include "themediconitem.h"
+#include "themediconview.h"
+#include "themediconview.moc"
+
+namespace Digikam
+{
+
+class ThemedIconViewPriv
+{
+public:
+
+ int thumbSize;
+
+ TQRect itemRect;
+ TQRect itemDateRect;
+ TQRect itemPixmapRect;
+ TQRect itemNameRect;
+ TQRect itemCommentsRect;
+ TQRect itemResolutionRect;
+ TQRect itemSizeRect;
+ TQRect itemTagRect;
+ TQRect bannerRect;
+
+ TQPixmap itemRegPixmap;
+ TQPixmap itemSelPixmap;
+ TQPixmap bannerPixmap;
+
+ TQFont fnReg;
+ TQFont fnCom;
+ TQFont fnXtra;
+};
+
+ThemedIconView::ThemedIconView(TQWidget* parent)
+ : IconView(parent)
+{
+ d = new ThemedIconViewPriv;
+ d->thumbSize = 128;
+
+
+ ThemedIconGroupItem* groupItem = new ThemedIconGroupItem(this);
+ for (int i=0; i<10; i++)
+ {
+ ThemedIconItem* item = new ThemedIconItem(groupItem);
+ if (i > 0 && i < 3)
+ item->setSelected(true, false);
+ }
+
+ updateBannerRectPixmap();
+ updateItemRectsPixmap();
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+}
+
+ThemedIconView::~ThemedIconView()
+{
+ delete d;
+}
+
+TQRect ThemedIconView::itemRect() const
+{
+ return d->itemRect;
+}
+
+TQRect ThemedIconView::itemDateRect() const
+{
+ return d->itemDateRect;
+}
+
+TQRect ThemedIconView::itemPixmapRect() const
+{
+ return d->itemPixmapRect;
+}
+
+TQRect ThemedIconView::itemNameRect() const
+{
+ return d->itemNameRect;
+}
+
+TQRect ThemedIconView::itemCommentsRect() const
+{
+ return d->itemCommentsRect;
+}
+
+TQRect ThemedIconView::itemResolutionRect() const
+{
+ return d->itemResolutionRect;
+}
+
+TQRect ThemedIconView::itemTagRect() const
+{
+ return d->itemTagRect;
+}
+
+TQRect ThemedIconView::itemSizeRect() const
+{
+ return d->itemSizeRect;
+}
+
+TQRect ThemedIconView::bannerRect() const
+{
+ return d->bannerRect;
+}
+
+TQPixmap* ThemedIconView::itemBaseRegPixmap() const
+{
+ return &d->itemRegPixmap;
+}
+
+TQPixmap* ThemedIconView::itemBaseSelPixmap() const
+{
+ return &d->itemSelPixmap;
+}
+
+TQPixmap ThemedIconView::bannerPixmap() const
+{
+ return d->bannerPixmap;
+}
+
+TQFont ThemedIconView::itemFontReg() const
+{
+ return d->fnReg;
+}
+
+TQFont ThemedIconView::itemFontCom() const
+{
+ return d->fnCom;
+}
+
+TQFont ThemedIconView::itemFontXtra() const
+{
+ return d->fnXtra;
+}
+
+void ThemedIconView::slotThemeChanged()
+{
+ updateBannerRectPixmap();
+ updateItemRectsPixmap();
+
+ viewport()->update();
+}
+
+void ThemedIconView::resizeEvent(TQResizeEvent* e)
+{
+ IconView::resizeEvent(e);
+
+ if (d->bannerRect.width() != frameRect().width())
+ updateBannerRectPixmap();
+}
+
+void ThemedIconView::updateBannerRectPixmap()
+{
+ d->bannerRect = TQRect(0, 0, 0, 0);
+
+ // Title --------------------------------------------------------
+ TQFont fn(font());
+ int fnSize = fn.pointSize();
+ bool usePointSize;
+ if (fnSize > 0)
+ {
+ fn.setPointSize(fnSize+2);
+ usePointSize = true;
+ }
+ else
+ {
+ fnSize = fn.pixelSize();
+ fn.setPixelSize(fnSize+2);
+ usePointSize = false;
+ }
+
+ fn.setBold(true);
+ TQFontMetrics fm(fn);
+ TQRect tr = fm.boundingRect(0, 0, frameRect().width(),
+ 0xFFFFFFFF, TQt::AlignLeft | TQt::AlignVCenter,
+ "XXX");
+ d->bannerRect.setHeight(tr.height());
+
+ if (usePointSize)
+ fn.setPointSize(font().pointSize());
+ else
+ fn.setPixelSize(font().pixelSize());
+
+ fn.setBold(false);
+ fm = TQFontMetrics(fn);
+
+ tr = fm.boundingRect(0, 0, frameRect().width(),
+ 0xFFFFFFFF, TQt::AlignLeft | TQt::AlignVCenter,
+ "XXX");
+
+ d->bannerRect.setHeight(d->bannerRect.height() + tr.height() + 10);
+ d->bannerRect.setWidth(frameRect().width());
+
+ d->bannerPixmap = ThemeEngine::instance()->bannerPixmap(d->bannerRect.width(),
+ d->bannerRect.height());
+}
+
+void ThemedIconView::updateItemRectsPixmap()
+{
+ d->itemRect = TQRect(0,0,0,0);
+ d->itemDateRect = TQRect(0,0,0,0);
+ d->itemPixmapRect = TQRect(0,0,0,0);
+ d->itemNameRect = TQRect(0,0,0,0);
+ d->itemCommentsRect = TQRect(0,0,0,0);
+ d->itemResolutionRect = TQRect(0,0,0,0);
+ d->itemSizeRect = TQRect(0,0,0,0);
+ d->itemTagRect = TQRect(0,0,0,0);
+
+ d->fnReg = font();
+ d->fnCom = font();
+ d->fnXtra = font();
+ d->fnCom.setItalic(true);
+
+ int fnSz = d->fnReg.pointSize();
+ if (fnSz > 0)
+ {
+ d->fnCom.setPointSize(fnSz-1);
+ d->fnXtra.setPointSize(fnSz-2);
+ }
+ else
+ {
+ fnSz = d->fnReg.pixelSize();
+ d->fnCom.setPixelSize(fnSz-1);
+ d->fnXtra.setPixelSize(fnSz-2);
+ }
+
+ int margin = 5;
+ int w = d->thumbSize + 2*margin;
+
+ TQFontMetrics fm(d->fnReg);
+ TQRect oneRowRegRect = fm.boundingRect(0, 0, w, 0xFFFFFFFF,
+ TQt::AlignTop | TQt::AlignHCenter,
+ "XXXXXXXXX");
+ fm = TQFontMetrics(d->fnCom);
+ TQRect oneRowComRect = fm.boundingRect(0, 0, w, 0xFFFFFFFF,
+ TQt::AlignTop | TQt::AlignHCenter,
+ "XXXXXXXXX");
+ fm = TQFontMetrics(d->fnXtra);
+ TQRect oneRowXtraRect = fm.boundingRect(0, 0, w, 0xFFFFFFFF,
+ TQt::AlignTop | TQt::AlignHCenter,
+ "XXXXXXXXX");
+
+ int y = margin;
+
+ d->itemPixmapRect = TQRect(margin, y, w, d->thumbSize+margin);
+ y = d->itemPixmapRect.bottom();
+
+ {
+ d->itemNameRect = TQRect(margin, y, w, oneRowRegRect.height());
+ y = d->itemNameRect.bottom();
+ }
+
+ {
+ d->itemCommentsRect = TQRect(margin, y, w, oneRowComRect.height());
+ y = d->itemCommentsRect.bottom();
+ }
+
+ {
+ d->itemDateRect = TQRect(margin, y, w, oneRowXtraRect.height());
+ y = d->itemDateRect.bottom();
+ }
+
+ {
+ d->itemTagRect = TQRect(margin, y, w, oneRowComRect.height());
+ y = d->itemTagRect.bottom();
+ }
+
+ d->itemRect = TQRect(0, 0, w+2*margin, y+margin);
+
+ d->itemRegPixmap = ThemeEngine::instance()->thumbRegPixmap(d->itemRect.width(),
+ d->itemRect.height());
+
+ d->itemSelPixmap = ThemeEngine::instance()->thumbSelPixmap(d->itemRect.width(),
+ d->itemRect.height());
+}
+
+} // NameSpace Digikam
diff --git a/src/themedesigner/themediconview.h b/src/themedesigner/themediconview.h
new file mode 100644
index 00000000..ef2e3e15
--- /dev/null
+++ b/src/themedesigner/themediconview.h
@@ -0,0 +1,86 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-07-15
+ * Description : themed icon view
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef THEMEDICONVIEW_H
+#define THEMEDICONVIEW_H
+
+// TQt includes.
+
+#include <tqpixmap.h>
+
+// Local includes.
+
+#include "iconview.h"
+
+namespace Digikam
+{
+
+class ThemedIconViewPriv;
+
+class ThemedIconView : public IconView
+{
+ TQ_OBJECT
+
+
+public:
+
+ ThemedIconView(TQWidget* parent);
+ ~ThemedIconView();
+
+ TQRect itemRect() const;
+ TQRect itemDateRect() const;
+ TQRect itemPixmapRect() const;
+ TQRect itemNameRect() const;
+ TQRect itemCommentsRect() const;
+ TQRect itemResolutionRect() const;
+ TQRect itemSizeRect() const;
+ TQRect itemTagRect() const;
+ TQRect bannerRect() const;
+
+ TQPixmap* itemBaseRegPixmap() const;
+ TQPixmap* itemBaseSelPixmap() const;
+ TQPixmap bannerPixmap() const;
+
+ TQFont itemFontReg() const;
+ TQFont itemFontCom() const;
+ TQFont itemFontXtra() const;
+
+protected:
+
+ void resizeEvent(TQResizeEvent* e);
+
+private:
+
+ void updateBannerRectPixmap();
+ void updateItemRectsPixmap();
+
+ ThemedIconViewPriv* d;
+
+private slots:
+
+ void slotThemeChanged();
+};
+
+} // NameSpace Digikam
+
+#endif /* THEMEDICONVIEW_H */
diff --git a/src/tips b/src/tips
new file mode 100644
index 00000000..b78a2462
--- /dev/null
+++ b/src/tips
@@ -0,0 +1,267 @@
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that digiKam supports <b>Drag'n'Drop</b>? So you can easily move images from Konqueror to digiKam or from digiKam to K3b simply by using "Drag'n'Drop".
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can use nested albums in digiKam.
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can view the <b>Exif</b>, <b>MakerNotes</b>, and <b>IPTC</b> photo information by using the <b>Metadata</b> sidebar tab?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that each photo has a context menu that can be reached by clicking on it with the right mouse button?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that albums in digiKam are folders in your Album Library? So you can easily import your photos by simply copying them to your Album Library.
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can customize your digiKam toolbars using Settings -> Configure Toolbars...?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can reach other users through the digiKam-users mailing list? Subscribe to it at <a href="https://mail.kde.org/mailman/listinfo/digikam-users">this url</a>.
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that digiKam has multiple plugins with extra features like <b>HTML export</b>, <b>Archive to CD</b>, <b>Slideshow</b>,... and that you are welcome to write your own plugins? You can find more information at <a href="http://extragear.kde.org/apps/kipi">this url</a>.
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td><img src="hicolor/32x32/apps/digikam.png"></td>
+<td>
+... that you have direct access to the TDE Gamma Configuration using Tools -> Gamma Adjustment?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can print images using the Print Wizard? You can start it using Album -> Export -> Print Wizard.
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td><img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can use the <b>PNG</b> file format instead of <b>TIFF</b> for good compression without losing image quality?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png"></td>
+<td>
+... that most of the settings in a dialog box have <b>What's this?</b> information available, which can be reached via the right mouse button?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|General">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that because the PNG file format uses a lossless compression algorithm, you can use the max compression level with this file format?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|ImageEditor">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can print the current image opened in the digiKam image editor?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|ImageEditor">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you can edit the comments of the current image opened in the digiKam image editor using <b>Comments and Tags</b> sidebar tab?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
+
+<tip category="Digikam|ImageEditor">
+<html>
+<p>
+<table border="0" cellpadding="0" cellspacing="10" align="center">
+<tr>
+<td>
+<img src="hicolor/32x32/apps/digikam.png">
+</td>
+<td>
+... that you could use &lt;Page Down&gt; and &lt;Page Up&gt; on your keyboard to switch between photos in the image editor?
+</td>
+</tr>
+</table>
+</p>
+</html>
+</tip>
diff --git a/src/utilities/Makefile.am b/src/utilities/Makefile.am
new file mode 100644
index 00000000..fe4c1532
--- /dev/null
+++ b/src/utilities/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = imageeditor setup cameragui hotplug scripts batch slideshow lighttable
diff --git a/src/utilities/batch/Makefile.am b/src/utilities/batch/Makefile.am
new file mode 100644
index 00000000..355643da
--- /dev/null
+++ b/src/utilities/batch/Makefile.am
@@ -0,0 +1,20 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/thumbbar \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ $(LIBKDCRAW_CFLAGS) \
+ $(LIBKEXIV2_CFLAGS) \
+ $(all_includes)
+
+noinst_LTLIBRARIES = libbatch.la
+
+libbatch_la_SOURCES = batchthumbsgenerator.cpp batchalbumssyncmetadata.cpp \
+ imageinfojob.cpp imageinfoalbumsjob.cpp batchsyncmetadata.cpp
+
+libbatch_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+
diff --git a/src/utilities/batch/batchalbumssyncmetadata.cpp b/src/utilities/batch/batchalbumssyncmetadata.cpp
new file mode 100644
index 00000000..fba18d34
--- /dev/null
+++ b/src/utilities/batch/batchalbumssyncmetadata.cpp
@@ -0,0 +1,183 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-22-01
+ * Description : batch sync pictures metadata from all Albums
+ * with digiKam database
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqtimer.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albummanager.h"
+#include "imageinfojob.h"
+#include "metadatahub.h"
+#include "batchalbumssyncmetadata.h"
+#include "batchalbumssyncmetadata.moc"
+
+namespace Digikam
+{
+
+class BatchAlbumsSyncMetadataPriv
+{
+public:
+
+ BatchAlbumsSyncMetadataPriv()
+ {
+ cancel = false;
+ imageInfoJob = 0;
+ palbumList = AlbumManager::instance()->allPAlbums();
+ duration.start();
+ }
+
+ bool cancel;
+
+ TQTime duration;
+
+ ImageInfoJob *imageInfoJob;
+
+ AlbumList palbumList;
+ AlbumList::Iterator albumsIt;
+};
+
+BatchAlbumsSyncMetadata::BatchAlbumsSyncMetadata(TQWidget* parent)
+ : DProgressDlg(parent)
+{
+ d = new BatchAlbumsSyncMetadataPriv;
+ d->imageInfoJob = new ImageInfoJob();
+ setValue(0);
+ setCaption(i18n("Sync All Images' Metadata"));
+ setLabel(i18n("<b>Syncing the metadata of all images with the digiKam database. Please wait...</b>"));
+ setButtonText(i18n("&Abort"));
+ resize(600, 300);
+ TQTimer::singleShot(500, this, TQ_SLOT(slotStart()));
+}
+
+BatchAlbumsSyncMetadata::~BatchAlbumsSyncMetadata()
+{
+ delete d;
+}
+
+void BatchAlbumsSyncMetadata::slotStart()
+{
+ setTitle(i18n("Parsing all albums"));
+ setTotalSteps(d->palbumList.count());
+
+ connect(d->imageInfoJob, TQ_SIGNAL(signalItemsInfo(const ImageInfoList&)),
+ this, TQ_SLOT(slotAlbumParsed(const ImageInfoList&)));
+
+ connect(d->imageInfoJob, TQ_SIGNAL(signalCompleted()),
+ this, TQ_SLOT(slotComplete()));
+
+ d->albumsIt = d->palbumList.begin();
+ parseAlbum();
+}
+
+void BatchAlbumsSyncMetadata::parseAlbum()
+{
+ if (d->albumsIt == d->palbumList.end()) // All is done.
+ {
+ TQTime t;
+ t = t.addMSecs(d->duration.elapsed());
+ setLabel(i18n("<b>The metadata of all images has been synchronized with the digiKam database.</b>"));
+ setTitle(i18n("Duration: %1").arg(t.toString()));
+ setButtonText(i18n("&Close"));
+ advance(1);
+ abort();
+ }
+ else if (!(*d->albumsIt)->isRoot())
+ {
+ d->imageInfoJob->allItemsFromAlbum(*d->albumsIt);
+ DDebug() << "Sync Items from Album :" << (*d->albumsIt)->kurl().directory() << endl;
+ }
+ else
+ {
+ d->albumsIt++;
+ parseAlbum();
+ }
+}
+
+void BatchAlbumsSyncMetadata::slotAlbumParsed(const ImageInfoList& list)
+{
+ TQPixmap pix = TDEApplication::kApplication()->iconLoader()->loadIcon(
+ "folder_image", TDEIcon::NoGroup, 32);
+
+ ImageInfoList imageInfoList = list;
+
+ if (!imageInfoList.isEmpty())
+ {
+ addedAction(pix, imageInfoList.first()->kurl().directory());
+
+ for (ImageInfo *info = imageInfoList.first(); info; info = imageInfoList.next())
+ {
+ MetadataHub fileHub;
+ // read in from database
+ fileHub.load(info);
+ // write out to file DMetadata
+ fileHub.write(info->filePath());
+ }
+ }
+
+ advance(1);
+ d->albumsIt++;
+ parseAlbum();
+}
+
+void BatchAlbumsSyncMetadata::slotComplete()
+{
+ advance(1);
+ d->albumsIt++;
+ parseAlbum();
+}
+
+void BatchAlbumsSyncMetadata::slotCancel()
+{
+ abort();
+ done(Cancel);
+}
+
+void BatchAlbumsSyncMetadata::closeEvent(TQCloseEvent *e)
+{
+ abort();
+ e->accept();
+}
+
+void BatchAlbumsSyncMetadata::abort()
+{
+ d->cancel = true;
+ d->imageInfoJob->stop();
+ emit signalComplete();
+}
+
+} // namespace Digikam
+
+
diff --git a/src/utilities/batch/batchalbumssyncmetadata.h b/src/utilities/batch/batchalbumssyncmetadata.h
new file mode 100644
index 00000000..8363ce4d
--- /dev/null
+++ b/src/utilities/batch/batchalbumssyncmetadata.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-22-01
+ * Description : batch sync pictures metadata with
+ * digiKam database
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BATCHALBUMSSYNCMETADATA_H
+#define BATCHALBUMSSYNCMETADATA_H
+
+// Local includes.
+
+#include "imageinfo.h"
+#include "dprogressdlg.h"
+
+class TQWidget;
+
+class KURL;
+
+namespace Digikam
+{
+
+class BatchAlbumsSyncMetadataPriv;
+
+class BatchAlbumsSyncMetadata : public DProgressDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ BatchAlbumsSyncMetadata(TQWidget* parent);
+ ~BatchAlbumsSyncMetadata();
+
+signals:
+
+ void signalComplete();
+
+private:
+
+ void abort();
+ void parseAlbum();
+
+protected:
+
+ void closeEvent(TQCloseEvent *e);
+
+protected slots:
+
+ void slotCancel();
+
+private slots:
+
+ void slotStart();
+ void slotAlbumParsed(const ImageInfoList&);
+ void slotComplete();
+
+private:
+
+ BatchAlbumsSyncMetadataPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* BATCHALBUMSSYNCMETADATA_H */
diff --git a/src/utilities/batch/batchsyncmetadata.cpp b/src/utilities/batch/batchsyncmetadata.cpp
new file mode 100644
index 00000000..50e2ad7a
--- /dev/null
+++ b/src/utilities/batch/batchsyncmetadata.cpp
@@ -0,0 +1,166 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-22-01
+ * Description : batch sync pictures metadata from all Albums
+ * with digiKam database
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "imageinfojob.h"
+#include "metadatahub.h"
+#include "statusprogressbar.h"
+#include "batchsyncmetadata.h"
+#include "batchsyncmetadata.moc"
+
+namespace Digikam
+{
+
+class BatchSyncMetadataPriv
+{
+public:
+
+ BatchSyncMetadataPriv()
+ {
+ cancel = false;
+ imageInfoJob = new ImageInfoJob();
+ album = 0;
+ count = 0;
+ imageInfo = 0;
+ }
+
+ bool cancel;
+
+ int count;
+
+ Album *album;
+
+ ImageInfoJob *imageInfoJob;
+
+ ImageInfoList imageInfoList;
+
+ ImageInfo *imageInfo;
+};
+
+BatchSyncMetadata::BatchSyncMetadata(TQObject* parent, Album *album)
+ : TQObject(parent)
+{
+ d = new BatchSyncMetadataPriv;
+ d->album = album;
+}
+
+BatchSyncMetadata::BatchSyncMetadata(TQObject* parent, const ImageInfoList& list)
+ : TQObject(parent)
+{
+ d = new BatchSyncMetadataPriv;
+ d->imageInfoList = list;
+}
+
+BatchSyncMetadata::~BatchSyncMetadata()
+{
+ delete d;
+}
+
+void BatchSyncMetadata::parseAlbum()
+{
+ d->imageInfoJob->allItemsFromAlbum(d->album);
+
+ connect(d->imageInfoJob, TQ_SIGNAL(signalItemsInfo(const ImageInfoList&)),
+ this, TQ_SLOT(slotAlbumParsed(const ImageInfoList&)));
+
+ connect(d->imageInfoJob, TQ_SIGNAL(signalCompleted()),
+ this, TQ_SLOT(slotComplete()));
+}
+
+void BatchSyncMetadata::slotComplete()
+{
+ if (d->imageInfoList.isEmpty())
+ complete();
+}
+
+void BatchSyncMetadata::slotAlbumParsed(const ImageInfoList& list)
+{
+ d->imageInfoList = list;
+ parseList();
+}
+
+void BatchSyncMetadata::parseList()
+{
+ emit signalProgressBarMode(StatusProgressBar::CancelProgressBarMode,
+ i18n("Synchonizing images' Metadata with database. Please wait..."));
+
+ d->imageInfo = d->imageInfoList.first();
+ parsePicture();
+}
+
+void BatchSyncMetadata::parsePicture()
+{
+ if (!d->imageInfo) // All is done.
+ {
+ complete();
+ slotAbort();
+ }
+ else if (d->cancel)
+ {
+ complete();
+ }
+ else
+ {
+ MetadataHub fileHub;
+ // read in from database
+ fileHub.load(d->imageInfo);
+ // write out to file DMetadata
+ fileHub.write(d->imageInfo->filePath());
+
+ emit signalProgressValue((int)((d->count++/(float)d->imageInfoList.count())*100.0));
+
+ d->imageInfo = d->imageInfoList.next();
+
+ kapp->processEvents();
+ parsePicture();
+ }
+}
+
+void BatchSyncMetadata::slotAbort()
+{
+ d->cancel = true;
+ d->imageInfoJob->stop();
+}
+
+void BatchSyncMetadata::complete()
+{
+ emit signalProgressBarMode(StatusProgressBar::TextMode, TQString());
+ emit signalComplete();
+}
+
+} // namespace Digikam
+
+
diff --git a/src/utilities/batch/batchsyncmetadata.h b/src/utilities/batch/batchsyncmetadata.h
new file mode 100644
index 00000000..c9b69ac7
--- /dev/null
+++ b/src/utilities/batch/batchsyncmetadata.h
@@ -0,0 +1,89 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-22-01
+ * Description : batch sync pictures metadata with
+ * digiKam database
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BATCHSYNCMETADATA_H
+#define BATCHSYNCMETADATA_H
+
+// TQt includes.
+
+#include <tqobject.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+
+class KURL;
+
+namespace Digikam
+{
+
+class Album;
+class BatchSyncMetadataPriv;
+
+class BatchSyncMetadata : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ /** Constructor witch sync all metatada pictures from an Album */
+ BatchSyncMetadata(TQObject* parent, Album *album);
+
+ /** Constructor witch sync all metatada from an images list */
+ BatchSyncMetadata(TQObject* parent, const ImageInfoList& list);
+
+ ~BatchSyncMetadata();
+
+ void parseList();
+ void parseAlbum();
+
+signals:
+
+ void signalComplete();
+ void signalProgressValue(int);
+ void signalProgressBarMode(int, const TQString&);
+
+public slots:
+
+ void slotAbort();
+
+private:
+
+ void parsePicture();
+ void complete();
+
+private slots:
+
+ void slotAlbumParsed(const ImageInfoList&);
+ void slotComplete();
+
+private:
+
+ BatchSyncMetadataPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* BATCHSYNCMETADATA_H */
diff --git a/src/utilities/batch/batchthumbsgenerator.cpp b/src/utilities/batch/batchthumbsgenerator.cpp
new file mode 100644
index 00000000..f0bd0103
--- /dev/null
+++ b/src/utilities/batch/batchthumbsgenerator.cpp
@@ -0,0 +1,233 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-30-08
+ * Description : batch thumbnails generator
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+}
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqtimer.h>
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqdatetime.h>
+
+// KDE includes.
+
+#include <kmdcodec.h>
+#include <tdelocale.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "thumbnailjob.h"
+#include "batchthumbsgenerator.h"
+#include "batchthumbsgenerator.moc"
+
+namespace Digikam
+{
+
+class BatchThumbsGeneratorPriv
+{
+public:
+
+ BatchThumbsGeneratorPriv()
+ {
+ cancel = false;
+ thumbJob = 0;
+ duration.start();
+ }
+
+ bool cancel;
+
+ TQTime duration;
+
+ TQGuardedPtr<ThumbnailJob> thumbJob;
+};
+
+BatchThumbsGenerator::BatchThumbsGenerator(TQWidget* parent)
+ : DProgressDlg(parent)
+{
+ d = new BatchThumbsGeneratorPriv;
+ setValue(0);
+ setCaption(i18n("Thumbnails processing"));
+ setLabel(i18n("<b>Updating thumbnails database. Please wait...</b>"));
+ setButtonText(i18n("&Abort"));
+ TQTimer::singleShot(500, this, TQ_SLOT(slotRebuildThumbs128()));
+ resize(600, 300);
+}
+
+BatchThumbsGenerator::~BatchThumbsGenerator()
+{
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ delete d;
+}
+
+void BatchThumbsGenerator::slotRebuildThumbs128()
+{
+ setTitle(i18n("Processing small thumbs"));
+ rebuildAllThumbs(128);
+
+ connect(this, TQ_SIGNAL(signalRebuildThumbsDone()),
+ this, TQ_SLOT(slotRebuildThumbs256()));
+}
+
+void BatchThumbsGenerator::slotRebuildThumbs256()
+{
+ setTitle(i18n("Processing large thumbs"));
+ rebuildAllThumbs(256);
+
+ disconnect(this, TQ_SIGNAL(signalRebuildThumbsDone()),
+ this, TQ_SLOT(slotRebuildThumbs256()));
+
+ connect(this, TQ_SIGNAL(signalRebuildThumbsDone()),
+ this, TQ_SLOT(slotRebuildAllThumbComplete()));
+}
+
+void BatchThumbsGenerator::slotRebuildAllThumbComplete()
+{
+ TQTime t;
+ t = t.addMSecs(d->duration.elapsed());
+ setLabel(i18n("<b>The thumbnails database has been updated.</b>"));
+ setTitle(i18n("Duration: %1").arg(t.toString()));
+ setButtonText(i18n("&Close"));
+}
+
+void BatchThumbsGenerator::rebuildAllThumbs(int size)
+{
+ TQStringList allPicturesPath;
+ TQString thumbCacheDir = TQDir::homeDirPath() + "/.thumbnails/";
+ TQString filesFilter = AlbumSettings::instance()->getAllFileFilter();
+ bool exifRotate = AlbumSettings::instance()->getExifRotate();
+ AlbumDB *db = AlbumManager::instance()->albumDB();
+ AlbumList palbumList = AlbumManager::instance()->allPAlbums();
+
+ // Get all digiKam albums collection pictures path.
+
+ for (AlbumList::Iterator it = palbumList.begin();
+ !d->cancel && (it != palbumList.end()); ++it )
+ {
+ // Don't use the root album
+ if ((*it)->isRoot())
+ continue;
+
+ db->beginTransaction();
+ TQStringList albumItemsPath = db->getItemURLsInAlbum((*it)->id());
+ db->commitTransaction();
+
+ TQStringList pathSorted;
+ for (TQStringList::iterator it2 = albumItemsPath.begin();
+ !d->cancel && (it2 != albumItemsPath.end()); ++it2)
+ {
+ TQFileInfo fi(*it2);
+ if (filesFilter.contains(fi.extension(false)))
+ pathSorted.append(*it2);
+ }
+
+ allPicturesPath += pathSorted;
+ }
+
+ setTotalSteps(allPicturesPath.count()*2);
+
+ // Remove all current album item thumbs from disk cache.
+
+ for (TQStringList::iterator it = allPicturesPath.begin();
+ !d->cancel && (it != allPicturesPath.end()); ++it)
+ {
+ TQString uri = "file://" + TQDir::cleanDirPath(*it);
+ KMD5 md5(TQFile::encodeName(uri).data());
+ uri = md5.hexDigest();
+
+ TQString smallThumbPath = thumbCacheDir + "normal/" + uri + ".png";
+ TQString bigThumbPath = thumbCacheDir + "large/" + uri + ".png";
+
+ if (size <= 128)
+ ::unlink(TQFile::encodeName(smallThumbPath));
+ else
+ ::unlink(TQFile::encodeName(bigThumbPath));
+ }
+
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ d->thumbJob = new ThumbnailJob(KURL::List(allPicturesPath), size, true, exifRotate);
+
+ connect(d->thumbJob, TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
+ this, TQ_SLOT(slotRebuildThumbDone(const KURL&, const TQPixmap&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalFailed(const KURL&)),
+ this, TQ_SLOT(slotRebuildThumbDone(const KURL&)));
+
+ connect(d->thumbJob, TQ_SIGNAL(signalCompleted()),
+ this, TQ_SIGNAL(signalRebuildThumbsDone()));
+}
+
+void BatchThumbsGenerator::slotRebuildThumbDone(const KURL& url, const TQPixmap& pix)
+{
+ addedAction(pix, url.path());
+ advance(1);
+}
+
+void BatchThumbsGenerator::slotCancel()
+{
+ abort();
+ done(Cancel);
+}
+
+void BatchThumbsGenerator::closeEvent(TQCloseEvent *e)
+{
+ abort();
+ e->accept();
+}
+
+void BatchThumbsGenerator::abort()
+{
+ d->cancel = true;
+
+ if (!d->thumbJob.isNull())
+ {
+ d->thumbJob->kill();
+ d->thumbJob = 0;
+ }
+
+ emit signalRebuildAllThumbsDone();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/batch/batchthumbsgenerator.h b/src/utilities/batch/batchthumbsgenerator.h
new file mode 100644
index 00000000..091c2783
--- /dev/null
+++ b/src/utilities/batch/batchthumbsgenerator.h
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-30-08
+ * Description : batch thumbnails generator
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef BATCHTHUMBSGENERATOR_H
+#define BATCHTHUMBSGENERATOR_H
+
+// Local includes.
+
+#include "dprogressdlg.h"
+
+class TQWidget;
+class TQPixmap;
+
+class KURL;
+
+namespace Digikam
+{
+
+class BatchThumbsGeneratorPriv;
+
+class BatchThumbsGenerator : public DProgressDlg
+{
+ TQ_OBJECT
+
+
+public:
+
+ BatchThumbsGenerator(TQWidget* parent);
+ ~BatchThumbsGenerator();
+
+signals:
+
+ void signalRebuildThumbsDone();
+ void signalRebuildAllThumbsDone();
+
+private:
+
+ void rebuildAllThumbs(int size);
+ void abort();
+
+protected:
+
+ void closeEvent(TQCloseEvent *e);
+
+protected slots:
+
+ void slotCancel();
+
+private slots:
+
+ void slotRebuildThumbs128();
+ void slotRebuildThumbs256();
+ void slotRebuildThumbDone(const KURL& url, const TQPixmap& pix=TQPixmap());
+ void slotRebuildAllThumbComplete();
+
+private:
+
+ BatchThumbsGeneratorPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* BATCHTHUMBSGENERATOR_H */
diff --git a/src/utilities/batch/imageinfoalbumsjob.cpp b/src/utilities/batch/imageinfoalbumsjob.cpp
new file mode 100644
index 00000000..4d0e3c55
--- /dev/null
+++ b/src/utilities/batch/imageinfoalbumsjob.cpp
@@ -0,0 +1,125 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-14-02
+ * Description : interface to get image info from an albums list.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albummanager.h"
+#include "imageinfojob.h"
+#include "imageinfoalbumsjob.h"
+#include "imageinfoalbumsjob.moc"
+
+namespace Digikam
+{
+
+class ImageInfoAlbumsJobPriv
+{
+public:
+
+ ImageInfoAlbumsJobPriv(){}
+
+ AlbumList albumsList;
+ AlbumList::Iterator albumIt;
+
+ ImageInfoList itemsList;
+
+ ImageInfoJob imageInfoJob;
+};
+
+ImageInfoAlbumsJob::ImageInfoAlbumsJob()
+{
+ d = new ImageInfoAlbumsJobPriv;
+
+ connect(&d->imageInfoJob, TQ_SIGNAL(signalItemsInfo(const ImageInfoList&)),
+ this, TQ_SLOT(slotItemsInfo(const ImageInfoList&)));
+
+ connect(&d->imageInfoJob, TQ_SIGNAL(signalCompleted()),
+ this, TQ_SLOT(slotComplete()));
+}
+
+ImageInfoAlbumsJob::~ImageInfoAlbumsJob()
+{
+ delete d;
+}
+
+void ImageInfoAlbumsJob::allItemsFromAlbums(const AlbumList& albumsList)
+{
+ if (albumsList.isEmpty())
+ return;
+
+ d->albumsList = albumsList;
+ d->albumIt = d->albumsList.begin();
+ parseAlbum();
+}
+
+void ImageInfoAlbumsJob::parseAlbum()
+{
+ d->imageInfoJob.allItemsFromAlbum(*d->albumIt);
+}
+
+void ImageInfoAlbumsJob::stop()
+{
+ d->imageInfoJob.stop();
+ d->albumsList.clear();
+}
+
+void ImageInfoAlbumsJob::slotItemsInfo(const ImageInfoList& items)
+{
+ ImageInfo* item;
+ for (ImageInfoListIterator it(items); (item = it.current()); ++it)
+ d->itemsList.append(item);
+
+ ++d->albumIt;
+ if (d->albumIt == d->albumsList.end())
+ {
+ stop();
+ emit signalCompleted(d->itemsList);
+ return;
+ }
+
+ parseAlbum();
+}
+
+void ImageInfoAlbumsJob::slotComplete()
+{
+ ++d->albumIt;
+ if (d->albumIt == d->albumsList.end())
+ {
+ stop();
+ emit signalCompleted(d->itemsList);
+ return;
+ }
+
+ parseAlbum();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/batch/imageinfoalbumsjob.h b/src/utilities/batch/imageinfoalbumsjob.h
new file mode 100644
index 00000000..7b9f477f
--- /dev/null
+++ b/src/utilities/batch/imageinfoalbumsjob.h
@@ -0,0 +1,80 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-14-02
+ * Description : interface to get image info from an albums list.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEINFOALBUMSJOB_H
+#define IMAGEINFOALBUMSJOB_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqcstring.h>
+
+// Local includes.
+
+#include "albummanager.h"
+#include "imageinfo.h"
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace Digikam
+{
+
+class ImageInfoAlbumsJobPriv;
+
+class ImageInfoAlbumsJob : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageInfoAlbumsJob();
+ ~ImageInfoAlbumsJob();
+
+ void allItemsFromAlbums(const AlbumList& albumsList);
+ void stop();
+
+signals:
+
+ void signalCompleted(const ImageInfoList& items);
+
+private slots:
+
+ void slotItemsInfo(const ImageInfoList&);
+ void slotComplete();
+
+private:
+
+ void parseAlbum();
+
+private:
+
+ ImageInfoAlbumsJobPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* IMAGEINFOALBUMSJOB_H */
diff --git a/src/utilities/batch/imageinfojob.cpp b/src/utilities/batch/imageinfojob.cpp
new file mode 100644
index 00000000..58660697
--- /dev/null
+++ b/src/utilities/batch/imageinfojob.cpp
@@ -0,0 +1,163 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-22-01
+ * Description : digikamalbum TDEIO slave interface to get image
+ * info from database.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqdatastream.h>
+
+// KDE includes.
+
+#include <tdeio/job.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "imageinfojob.h"
+#include "imageinfojob.moc"
+
+namespace Digikam
+{
+
+class ImageInfoJobPriv
+{
+public:
+
+ ImageInfoJobPriv()
+ {
+ job = 0;
+
+ AlbumSettings *settings = AlbumSettings::instance();
+ imagefilter = settings->getImageFileFilter().lower() +
+ settings->getImageFileFilter().upper() +
+ settings->getRawFileFilter().lower() +
+ settings->getRawFileFilter().upper();
+ }
+
+ TQString imagefilter;
+
+ TDEIO::TransferJob *job;
+};
+
+ImageInfoJob::ImageInfoJob()
+{
+ d = new ImageInfoJobPriv;
+}
+
+ImageInfoJob::~ImageInfoJob()
+{
+ delete d;
+}
+
+void ImageInfoJob::allItemsFromAlbum(Album *album)
+{
+ if (d->job)
+ {
+ d->job->kill();
+ d->job = 0;
+ }
+
+ if (!album)
+ return;
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << AlbumManager::instance()->getLibraryPath();
+ ds << album->kurl();
+ ds << d->imagefilter;
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+
+ // Protocol = digikamalbums -> tdeio_digikamalbums
+ d->job = new TDEIO::TransferJob(album->kurl(), TDEIO::CMD_SPECIAL,
+ ba, TQByteArray(), false);
+
+ connect(d->job, TQ_SIGNAL(result(TDEIO::Job*)),
+ this, TQ_SLOT(slotResult(TDEIO::Job*)));
+
+ connect(d->job, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
+ this, TQ_SLOT(slotData(TDEIO::Job*, const TQByteArray&)));
+}
+
+void ImageInfoJob::stop()
+{
+ if (d->job)
+ {
+ d->job->kill();
+ d->job = 0;
+ }
+}
+
+void ImageInfoJob::slotResult(TDEIO::Job* job)
+{
+ d->job = 0;
+
+ if (job->error())
+ {
+ DWarning() << "Failed to list url: " << job->errorString() << endl;
+ return;
+ }
+
+ emit signalCompleted();
+}
+
+void ImageInfoJob::slotData(TDEIO::Job*, const TQByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ TQ_LLONG imageID;
+ int albumID;
+ TQString name;
+ TQString date;
+ size_t size;
+ TQSize dims;
+ ImageInfoList itemsList;
+ TQDataStream ds(data, IO_ReadOnly);
+
+ while (!ds.atEnd())
+ {
+ ds >> imageID;
+ ds >> albumID;
+ ds >> name;
+ ds >> date;
+ ds >> size;
+ ds >> dims;
+
+ ImageInfo* info = new ImageInfo(imageID, albumID, name,
+ TQDateTime::fromString(date, TQt::ISODate),
+ size, dims);
+
+ itemsList.append(info);
+ }
+
+ emit signalItemsInfo(itemsList);
+}
+
+} // namespace Digikam
diff --git a/src/utilities/batch/imageinfojob.h b/src/utilities/batch/imageinfojob.h
new file mode 100644
index 00000000..0227a628
--- /dev/null
+++ b/src/utilities/batch/imageinfojob.h
@@ -0,0 +1,78 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-22-01
+ * Description : digikamalbum TDEIO slave interface to get image
+ * info from database.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEINFOJOB_H
+#define IMAGEINFOJOB_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqcstring.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+
+namespace TDEIO
+{
+class Job;
+}
+
+namespace Digikam
+{
+
+class Album;
+class ImageInfoJobPriv;
+
+class ImageInfoJob : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageInfoJob();
+ ~ImageInfoJob();
+
+ void allItemsFromAlbum(Album *album);
+ void stop();
+
+signals:
+
+ void signalItemsInfo(const ImageInfoList& items);
+ void signalCompleted();
+
+private slots:
+
+ void slotResult(TDEIO::Job* job);
+ void slotData(TDEIO::Job* job, const TQByteArray& data);
+
+private:
+
+ ImageInfoJobPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* IMAGEINFOJOB_H */
diff --git a/src/utilities/cameragui/Makefile.am b/src/utilities/cameragui/Makefile.am
new file mode 100644
index 00000000..83f810f4
--- /dev/null
+++ b/src/utilities/cameragui/Makefile.am
@@ -0,0 +1,30 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libcameragui.la
+
+# NOTE from Gilles (06-12-06): gpcamera.cpp must be placed on the top of source file list
+# to unbreak compilation with './configure -enable-final' option. I suspect a problem with
+# Gphoto2 C Ansi header.
+libcameragui_la_SOURCES = gpcamera.cpp cameraui.cpp cameraiconview.cpp \
+ cameraiconitem.cpp cameracontroller.cpp \
+ camerafolderview.cpp camerafolderitem.cpp \
+ animwidget.cpp renamecustomizer.cpp \
+ dkcamera.cpp umscamera.cpp gpiteminfo.cpp \
+ camerainfodialog.cpp albumselectdialog.cpp \
+ camerafolderdialog.cpp freespacewidget.cpp
+
+libcameragui_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+libcameragui_la_LIBADD = $(top_builddir)/src/libs/imageproperties/libimagepropertiescamgui.la \
+ $(LIB_GPHOTO) $(LIBJPEG)
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/libs/jpegutils \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/imageproperties \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ $(LIBKEXIV2_CFLAGS) \
+ $(GPHOTO_CFLAGS) $(LIBKDCRAW_CFLAGS) $(all_includes)
diff --git a/src/utilities/cameragui/albumselectdialog.cpp b/src/utilities/cameragui/albumselectdialog.cpp
new file mode 100644
index 00000000..486c4711
--- /dev/null
+++ b/src/utilities/cameragui/albumselectdialog.cpp
@@ -0,0 +1,417 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-16
+ * Description : a dialog to select a target album to download
+ * pictures from camera
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqframe.h>
+#include <tqlayout.h>
+#include <tqpopupmenu.h>
+#include <tqcursor.h>
+#include <tqdatetime.h>
+#include <tqmap.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdeaction.h>
+#include <kinputdialog.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "folderview.h"
+#include "folderitem.h"
+#include "album.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "searchtextbar.h"
+#include "albumselectdialog.h"
+#include "albumselectdialog.moc"
+
+namespace Digikam
+{
+
+class AlbumSelectDialogPrivate
+{
+
+public:
+
+ AlbumSelectDialogPrivate()
+ {
+ allowRootSelection = false;
+ folderView = 0;
+ searchBar = 0;
+ }
+
+ bool allowRootSelection;
+
+ TQString newAlbumString;
+
+ TQMap<FolderItem*, PAlbum*> albumMap;
+
+ FolderView *folderView;
+
+ SearchTextBar *searchBar;
+};
+
+AlbumSelectDialog::AlbumSelectDialog(TQWidget* parent, PAlbum* albumToSelect,
+ const TQString& header,
+ const TQString& newAlbumString,
+ bool allowRootSelection )
+ : KDialogBase(Plain, i18n("Select Album"),
+ Help|User1|Ok|Cancel, Ok,
+ parent, 0, true, true,
+ i18n("&New Album"))
+{
+ d = new AlbumSelectDialogPrivate;
+ setHelp("targetalbumdialog.anchor", "digikam");
+ enableButtonOK(false);
+
+ d->allowRootSelection = allowRootSelection;
+ d->newAlbumString = newAlbumString;
+
+ // -------------------------------------------------------------
+
+ TQGridLayout* grid = new TQGridLayout(plainPage(), 2, 1, 0, spacingHint());
+
+ TQLabel *logo = new TQLabel(plainPage());
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 128, TDEIcon::DefaultState, 0, true));
+
+ TQLabel *message = new TQLabel(plainPage());
+ if (!header.isEmpty())
+ message->setText(header);
+
+ d->folderView = new FolderView(plainPage());
+ d->folderView->addColumn(i18n("My Albums"));
+ d->folderView->setColumnWidthMode( 0, TQListView::Maximum );
+ d->folderView->setResizeMode( TQListView::AllColumns );
+ d->folderView->setRootIsDecorated(true);
+
+ d->searchBar = new SearchTextBar(plainPage(), "AlbumSelectDialogSearchBar");
+
+ // -------------------------------------------------------------
+
+ TQPixmap icon = iconLoader->loadIcon("folder", TDEIcon::NoGroup,
+ AlbumSettings::instance()->getDefaultTreeIconSize(), TDEIcon::DefaultState, 0, true);
+
+ AlbumList aList = AlbumManager::instance()->allPAlbums();
+
+ for (AlbumList::const_iterator it = aList.begin(); it != aList.end(); ++it)
+ {
+ PAlbum* album = (PAlbum*)(*it);
+
+ FolderItem* viewItem = 0;
+
+ if (album->isRoot())
+ {
+ viewItem = new FolderItem(d->folderView, album->title());
+ viewItem->setOpen(true);
+ }
+ else
+ {
+ FolderItem* parentItem = (FolderItem*)(album->parent()->extraData(d->folderView));
+
+ if (!parentItem)
+ {
+ DWarning() << "Failed to find parent for Album "
+ << album->title() << endl;
+ continue;
+ }
+
+ viewItem = new FolderItem(parentItem, album->title());
+ }
+
+ if (viewItem)
+ {
+ viewItem->setPixmap(0, icon);
+ album->setExtraData(d->folderView, viewItem);
+ d->albumMap.insert(viewItem, album);
+
+ if (album == albumToSelect)
+ {
+ viewItem->setOpen(true);
+ d->folderView->setSelected(viewItem, true);
+ d->folderView->ensureItemVisible(viewItem);
+ }
+ }
+ }
+
+ // -------------------------------------------------------------
+
+ grid->addMultiCellWidget(logo, 0, 0, 0, 0);
+ grid->addMultiCellWidget(message, 1, 1, 0, 0);
+ grid->addMultiCellWidget(d->folderView, 0, 2, 1, 1);
+ grid->addMultiCellWidget(d->searchBar, 3, 3, 1, 1);
+ grid->setRowStretch(2, 10);
+
+ // -------------------------------------------------------------
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumAdded(Album*)),
+ this, TQ_SLOT(slotAlbumAdded(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumDeleted(Album*)),
+ this, TQ_SLOT(slotAlbumDeleted(Album*)));
+
+ connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumsCleared()),
+ this, TQ_SLOT(slotAlbumsCleared()));
+
+ connect(d->folderView, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+
+ connect(d->folderView, TQ_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
+ this, TQ_SLOT(slotContextMenu(TQListViewItem*, const TQPoint&, int)));
+
+ connect(d->searchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ this, TQ_SLOT(slotSearchTextChanged(const TQString&)));
+
+ // -------------------------------------------------------------
+
+ resize(650, 650);
+ slotSelectionChanged();
+}
+
+AlbumSelectDialog::~AlbumSelectDialog()
+{
+ delete d;
+}
+
+void AlbumSelectDialog::slotAlbumAdded(Album* album)
+{
+ if (!album || album->type() != Album::PHYSICAL)
+ return;
+
+ FolderItem* parentItem = (FolderItem*)(album->parent()->extraData(d->folderView));
+
+ if (!parentItem)
+ {
+ DWarning() << "Failed to find parent for Album "
+ << album->title() << endl;
+ return;
+ }
+
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+ TQPixmap icon = iconLoader->loadIcon("folder", TDEIcon::NoGroup,
+ AlbumSettings::instance()->getDefaultTreeIconSize(),
+ TDEIcon::DefaultState, 0, true);
+
+ FolderItem* viewItem = new FolderItem(parentItem, album->title());
+ viewItem->setPixmap(0, icon);
+ album->setExtraData(d->folderView, viewItem);
+ d->albumMap.insert(viewItem, (PAlbum*)album);
+}
+
+void AlbumSelectDialog::slotAlbumDeleted(Album* album)
+{
+ if (!album || album->type() != Album::PHYSICAL)
+ return;
+
+ FolderItem* viewItem = (FolderItem*)(album->extraData(d->folderView));
+
+ if (viewItem)
+ {
+ delete viewItem;
+ album->removeExtraData(d->folderView);
+ d->albumMap.remove(viewItem);
+ }
+}
+
+void AlbumSelectDialog::slotAlbumsCleared()
+{
+ d->folderView->clear();
+}
+
+void AlbumSelectDialog::slotSelectionChanged()
+{
+ TQListViewItem* selItem = 0;
+ TQListViewItemIterator it(d->folderView);
+
+ while (it.current())
+ {
+ if (it.current()->isSelected())
+ {
+ selItem = it.current();
+ break;
+ }
+ ++it;
+ }
+
+ if (!selItem || (selItem == d->folderView->firstChild()) &&
+ !d->allowRootSelection)
+ {
+ enableButtonOK(false);
+ return;
+ }
+
+ enableButtonOK(true);
+}
+
+void AlbumSelectDialog::slotContextMenu(TQListViewItem *, const TQPoint &, int)
+{
+ TQPopupMenu popmenu(d->folderView);
+ TDEAction *action = new TDEAction(i18n( "Create New Album" ),
+ "albumfolder-new", 0, this,
+ TQ_SLOT( slotUser1() ),
+ &popmenu);
+ action->plug(&popmenu);
+ popmenu.exec(TQCursor::pos());
+}
+
+void AlbumSelectDialog::slotUser1()
+{
+ TQListViewItem* item = d->folderView->currentItem();
+ if (!item)
+ item = d->folderView->firstChild();
+
+ if (!item)
+ return;
+
+ PAlbum* album = d->albumMap[(FolderItem*)item];
+ if (!album)
+ return;
+
+ bool ok;
+ TQString newAlbumName = KInputDialog::getText(i18n("New Album Name"),
+ i18n("Creating new album in '%1'\n"
+ "Enter album name:")
+ .arg(album->prettyURL()),
+ d->newAlbumString, &ok, this);
+ if (!ok)
+ return;
+
+ TQString errMsg;
+ PAlbum* newAlbum = AlbumManager::instance()->createPAlbum(album, newAlbumName,
+ TQString(), TQDate::currentDate(),
+ TQString(), errMsg);
+ if (!newAlbum)
+ {
+ KMessageBox::error(this, errMsg);
+ return;
+ }
+
+ FolderItem* newItem = (FolderItem*)newAlbum->extraData(d->folderView);
+ if (newItem)
+ {
+ d->folderView->ensureItemVisible(newItem);
+ d->folderView->setSelected(newItem, true);
+ }
+}
+
+PAlbum* AlbumSelectDialog::selectAlbum(TQWidget* parent,
+ PAlbum* albumToSelect,
+ const TQString& header,
+ const TQString& newAlbumString,
+ bool allowRootSelection )
+{
+ AlbumSelectDialog dlg(parent, albumToSelect,
+ header, newAlbumString,
+ allowRootSelection);
+
+ if (dlg.exec() != KDialogBase::Accepted)
+ return 0;
+
+ FolderItem* item = (FolderItem*) dlg.d->folderView->currentItem();
+ if (!item || (item == dlg.d->folderView->firstChild()) &&
+ !allowRootSelection)
+ {
+ return 0;
+ }
+
+ return dlg.d->albumMap[item];
+}
+
+void AlbumSelectDialog::slotSearchTextChanged(const TQString& filter)
+{
+ TQString search = filter.lower();
+
+ bool atleastOneMatch = false;
+
+ AlbumList pList = AlbumManager::instance()->allPAlbums();
+ for (AlbumList::iterator it = pList.begin(); it != pList.end(); ++it)
+ {
+ PAlbum* palbum = (PAlbum*)(*it);
+
+ // don't touch the root Album
+ if (palbum->isRoot())
+ continue;
+
+ bool match = palbum->title().lower().contains(search);
+ if (!match)
+ {
+ // check if any of the parents match the search
+ Album* parent = palbum->parent();
+ while (parent && !parent->isRoot())
+ {
+ if (parent->title().lower().contains(search))
+ {
+ match = true;
+ break;
+ }
+
+ parent = parent->parent();
+ }
+ }
+
+ if (!match)
+ {
+ // check if any of the children match the search
+ AlbumIterator it(palbum);
+ while (it.current())
+ {
+ if ((*it)->title().lower().contains(search))
+ {
+ match = true;
+ break;
+ }
+ ++it;
+ }
+ }
+
+ FolderItem* viewItem = (FolderItem*) palbum->extraData(d->folderView);
+
+ if (match)
+ {
+ atleastOneMatch = true;
+
+ if (viewItem)
+ viewItem->setVisible(true);
+ }
+ else
+ {
+ if (viewItem)
+ {
+ viewItem->setVisible(false);
+ }
+ }
+ }
+
+ d->searchBar->slotSearchResult(atleastOneMatch);
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/albumselectdialog.h b/src/utilities/cameragui/albumselectdialog.h
new file mode 100644
index 00000000..aea53319
--- /dev/null
+++ b/src/utilities/cameragui/albumselectdialog.h
@@ -0,0 +1,80 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-06-16
+ * Description : a dialog to select a target album to download
+ * pictures from camera
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ALBUMSELECTDIALOG_H
+#define ALBUMSELECTDIALOG_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+namespace Digikam
+{
+
+class PAlbum;
+class AlbumSelectDialogPrivate;
+
+class AlbumSelectDialog : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ AlbumSelectDialog(TQWidget* parent, PAlbum* albumToSelect,
+ const TQString& header=TQString(),
+ const TQString& newAlbumString=TQString(),
+ bool allowRootSelection=false);
+ ~AlbumSelectDialog();
+
+
+ static PAlbum* selectAlbum(TQWidget* parent,
+ PAlbum* albumToSelect,
+ const TQString& header=TQString(),
+ const TQString& newAlbumString=TQString(),
+ bool allowRootSelection=false);
+
+private slots:
+
+ void slotAlbumAdded(Album*);
+ void slotAlbumDeleted(Album*);
+ void slotAlbumsCleared();
+ void slotSelectionChanged();
+ void slotContextMenu(TQListViewItem *item, const TQPoint&, int);
+ void slotUser1();
+ void slotSearchTextChanged(const TQString&);
+
+private:
+
+ AlbumSelectDialogPrivate *d;
+};
+
+} // namespace Digikam
+
+#endif /* ALBUMSELECTDIALOG_H */
diff --git a/src/utilities/cameragui/animwidget.cpp b/src/utilities/cameragui/animwidget.cpp
new file mode 100644
index 00000000..1d869cd9
--- /dev/null
+++ b/src/utilities/cameragui/animwidget.cpp
@@ -0,0 +1,131 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-21
+ * Description : an animated busy widget
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpalette.h>
+#include <tqcolor.h>
+#include <tqtimer.h>
+
+// Local includes.
+
+#include "animwidget.h"
+#include "animwidget.moc"
+
+namespace Digikam
+{
+
+class AnimWidgetPriv
+{
+public:
+
+ AnimWidgetPriv()
+ {
+ timer = 0;
+ pos = 0;
+ }
+
+ int pos;
+ int size;
+
+ TQTimer *timer;
+
+ TQPixmap pix;
+};
+
+AnimWidget::AnimWidget(TQWidget* parent, int size)
+ : TQWidget(parent, 0, WResizeNoErase|WRepaintNoErase)
+{
+ d = new AnimWidgetPriv;
+ setBackgroundMode(TQt::NoBackground);
+
+ d->size = size;
+ d->pix = TQPixmap(d->size, d->size);
+ setFixedSize(d->size, d->size);
+
+ d->timer = new TQTimer(this);
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotTimeout()));
+}
+
+AnimWidget::~AnimWidget()
+{
+ delete d;
+}
+
+void AnimWidget::start()
+{
+ d->pos = 0;
+ d->timer->start(100);
+}
+
+void AnimWidget::stop()
+{
+ d->pos = 0;
+ d->timer->stop();
+ repaint();
+}
+
+void AnimWidget::paintEvent(TQPaintEvent*)
+{
+ d->pix.fill(colorGroup().background());
+ TQPainter p(&d->pix);
+
+ p.translate(d->size/2, d->size/2);
+
+ if (d->timer->isActive())
+ {
+ p.setPen(TQPen(colorGroup().text()));
+ p.rotate( d->pos );
+ }
+ else
+ {
+ p.setPen(TQPen(colorGroup().dark()));
+ }
+
+ for ( int i=0 ; i<12 ; i++ )
+ {
+ p.drawLine(d->size/2-4, 0, d->size/2-2, 0);
+ p.rotate(30);
+ }
+
+ p.end();
+ bitBlt(this, 0, 0, &d->pix);
+}
+
+void AnimWidget::slotTimeout()
+{
+ d->pos = (d->pos + 10) % 360;
+ repaint();
+}
+
+bool AnimWidget::running() const
+{
+ return d->timer->isActive();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/animwidget.h b/src/utilities/cameragui/animwidget.h
new file mode 100644
index 00000000..6a93f410
--- /dev/null
+++ b/src/utilities/cameragui/animwidget.h
@@ -0,0 +1,66 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-21
+ * Description : an animated busy widget
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ANIMWIDGET_H
+#define ANIMWIDGET_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class AnimWidgetPriv;
+
+class AnimWidget : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ AnimWidget(TQWidget* parent, int size=28);
+ ~AnimWidget();
+
+ void start();
+ void stop();
+ bool running() const;
+
+protected:
+
+ void paintEvent(TQPaintEvent*);
+
+private slots:
+
+ void slotTimeout();
+
+private:
+
+ AnimWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* ANIMWIDGET_H */
diff --git a/src/utilities/cameragui/cameracontroller.cpp b/src/utilities/cameragui/cameracontroller.cpp
new file mode 100644
index 00000000..34afa8ab
--- /dev/null
+++ b/src/utilities/cameragui/cameracontroller.cpp
@@ -0,0 +1,1227 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-17
+ * Description : digital camera controller
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+extern "C"
+{
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <typeinfo>
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqthread.h>
+#include <tqmutex.h>
+#include <tqwaitcondition.h>
+#include <tqevent.h>
+#include <tqapplication.h>
+#include <tqdeepcopy.h>
+#include <tqvariant.h>
+#include <tqimage.h>
+#include <tqdatastream.h>
+#include <tqfile.h>
+#include <tqtimer.h>
+#include <tqregexp.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kurl.h>
+#include <tdemessagebox.h>
+#include <tdeio/renamedlg.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "thumbnailsize.h"
+#include "imagewindow.h"
+#include "gpcamera.h"
+#include "umscamera.h"
+#include "dmetadata.h"
+#include "jpegutils.h"
+#include "mtqueue.h"
+#include "cameracontroller.h"
+#include "cameracontroller.moc"
+
+namespace Digikam
+{
+
+class CameraThread;
+
+class CameraCommand
+{
+public:
+
+ enum Action
+ {
+ gp_none = 0,
+ gp_connect,
+ gp_cancel,
+ gp_cameraInformations,
+ gp_listfolders,
+ gp_listfiles,
+ gp_download,
+ gp_upload,
+ gp_delete,
+ gp_lock,
+ gp_thumbnail,
+ gp_exif,
+ gp_open
+ };
+
+ Action action;
+ TQStringVariantMap map;
+};
+
+class CameraEvent : public TQCustomEvent
+{
+public:
+
+ enum State
+ {
+ gp_connected = 0,
+ gp_busy,
+ gp_listedfolders,
+ gp_listedfiles,
+ gp_downloadstarted,
+ gp_downloaded,
+ gp_downloadFailed,
+ gp_opened,
+ gp_uploaded,
+ gp_uploadFailed,
+ gp_deleted,
+ gp_deleteFailed,
+ gp_locked,
+ gp_lockFailed,
+ gp_thumbnailed,
+ gp_exif,
+ gp_cameraInformations,
+ gp_infomsg,
+ gp_errormsg
+ };
+
+ CameraEvent(State state) :
+ TQCustomEvent(TQEvent::User+state)
+ {}
+
+ bool result;
+ TQString msg;
+ TQStringVariantMap map;
+};
+
+class CameraControllerPriv
+{
+public:
+
+ CameraControllerPriv()
+ {
+ close = false;
+ overwriteAll = false;
+ skipAll = false;
+ canceled = false;
+ downloadTotal = 0;
+ parent = 0;
+ timer = 0;
+ camera = 0;
+ thread = 0;
+ }
+
+ bool close;
+ bool overwriteAll;
+ bool skipAll;
+ bool canceled;
+
+ int downloadTotal;
+
+ TQWidget *parent;
+
+ TQTimer *timer;
+
+ CameraThread *thread;
+
+ DKCamera *camera;
+
+ MTQueue<CameraCommand> cmdQueue;
+};
+
+class CameraThread : public TQThread
+{
+public:
+
+ CameraThread(CameraController* controller);
+ ~CameraThread();
+
+ void sendBusy(bool busy);
+ void sendError(const TQString& msg);
+ void sendInfo(const TQString& msg);
+
+protected:
+
+ void run();
+
+private:
+
+ CameraControllerPriv *d;
+
+ TQObject *parent;
+};
+
+CameraThread::CameraThread(CameraController* controller)
+ : d(controller->d), parent(controller)
+{
+}
+
+CameraThread::~CameraThread()
+{
+}
+
+void CameraThread::run()
+{
+ if (d->close)
+ return;
+
+ sendBusy(true);
+
+ CameraCommand* cmd = d->cmdQueue.dequeue();
+ if (cmd)
+ {
+ switch (cmd->action)
+ {
+ case(CameraCommand::gp_connect):
+ {
+ sendInfo(i18n("Connecting to camera..."));
+
+ bool result = d->camera->doConnect();
+
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_connected);
+ event->result = result;
+ TQApplication::postEvent(parent, event);
+
+ if (result)
+ sendInfo(i18n("Connection established"));
+ else
+ sendInfo(i18n("Connection failed"));
+
+ break;
+ }
+ case(CameraCommand::gp_cameraInformations):
+ {
+ sendInfo(i18n("Getting camera information..."));
+
+ TQString summary, manual, about;
+
+ d->camera->cameraSummary(summary);
+ d->camera->cameraManual(manual);
+ d->camera->cameraAbout(about);
+
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_cameraInformations);
+ event->map.insert("summary", TQVariant(summary));
+ event->map.insert("manual", TQVariant(manual));
+ event->map.insert("about", TQVariant(about));
+ TQApplication::postEvent(parent, event);
+ break;
+ }
+ case(CameraCommand::gp_listfolders):
+ {
+ sendInfo(i18n("Listing folders..."));
+
+ TQStringList folderList;
+ folderList.append(d->camera->path());
+ d->camera->getAllFolders(d->camera->path(), folderList);
+
+ /* TODO: ugly hack since qt <= 3.1.2 does not define
+ TQStringList with TQDeepCopy as a friend. */
+ TQValueList<TQString> flist(folderList);
+
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_listedfolders);
+ event->map.insert("folders", TQVariant(flist));
+ TQApplication::postEvent(parent, event);
+
+ sendInfo(i18n("The folders have been listed."));
+
+ break;
+ }
+ case(CameraCommand::gp_listfiles):
+ {
+ TQString folder = cmd->map["folder"].asString();
+
+ sendInfo(i18n("The files in %1 have been listed.").arg(folder));
+
+ GPItemInfoList itemsList;
+ // setting getImageDimensions to false is a huge speedup for UMSCamera
+ if (!d->camera->getItemsInfoList(folder, itemsList, false))
+ {
+ sendError(i18n("Failed to list files in %1").arg(folder));
+ }
+
+ if (!itemsList.isEmpty())
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_listedfiles);
+ event->map.insert("folder", TQVariant(folder));
+
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << itemsList;
+
+ event->map.insert("files", TQVariant(ba));
+ TQApplication::postEvent(parent, event);
+ }
+
+ sendInfo(i18n("Listing files in %1 is complete").arg(folder));
+
+ break;
+ }
+ case(CameraCommand::gp_thumbnail):
+ {
+ TQString folder = cmd->map["folder"].asString();
+ TQString file = cmd->map["file"].asString();
+
+ sendInfo(i18n("Getting thumbnails..."));
+
+ TQImage thumbnail;
+ d->camera->getThumbnail(folder, file, thumbnail);
+
+ if (!thumbnail.isNull())
+ {
+ thumbnail = thumbnail.smoothScale(ThumbnailSize::Huge, ThumbnailSize::Huge, TQImage::ScaleMin);
+
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_thumbnailed);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ event->map.insert("thumbnail", TQVariant(thumbnail));
+ TQApplication::postEvent(parent, event);
+ }
+
+ break;
+ }
+ case(CameraCommand::gp_exif):
+ {
+ TQString folder = cmd->map["folder"].asString();
+ TQString file = cmd->map["file"].asString();
+
+ sendInfo(i18n("Getting EXIF information for %1/%2...").arg(folder).arg(file));
+
+ char* edata = 0;
+ int esize = 0;
+ d->camera->getExif(folder, file, &edata, esize);
+
+ if (edata || esize)
+ {
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds.writeRawBytes(edata, esize);
+ delete [] edata;
+
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_exif);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ event->map.insert("exifSize", TQVariant(esize));
+ event->map.insert("exifData", TQVariant(ba));
+ TQApplication::postEvent(parent, event);
+ }
+ break;
+ }
+ case(CameraCommand::gp_download):
+ {
+ TQString folder = cmd->map["folder"].asString();
+ TQString file = cmd->map["file"].asString();
+ TQString dest = cmd->map["dest"].asString();
+ bool autoRotate = cmd->map["autoRotate"].asBool();
+ bool fixDateTime = cmd->map["fixDateTime"].asBool();
+ TQDateTime newDateTime = cmd->map["newDateTime"].asDateTime();
+ bool setPhotographerId = cmd->map["setPhotographerId"].asBool();
+ TQString author = cmd->map["author"].asString();
+ TQString authorTitle = cmd->map["authorTitle"].asString();
+ bool setCredits = cmd->map["setCredits"].asBool();
+ TQString credit = cmd->map["credit"].asString();
+ TQString source = cmd->map["source"].asString();
+ TQString copyright = cmd->map["copyright"].asString();
+ bool convertJpeg = cmd->map["convertJpeg"].asBool();
+ TQString losslessFormat = cmd->map["losslessFormat"].asString();
+ sendInfo(i18n("Downloading file %1...").arg(file));
+
+ // download to a temp file
+
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_downloadstarted);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ event->map.insert("dest", TQVariant(dest));
+ TQApplication::postEvent(parent, event);
+
+ KURL tempURL(dest);
+ tempURL = tempURL.upURL();
+ tempURL.addPath( TQString(".digikam-camera-tmp1-%1").arg(getpid()).append(file));
+ DDebug() << "Downloading: " << file << " using (" << tempURL.path() << ")" << endl;
+ TQString temp = tempURL.path();
+
+ bool result = d->camera->downloadItem(folder, file, tempURL.path());
+
+ if (result && isJpegImage(tempURL.path()))
+ {
+ if (autoRotate)
+ {
+ DDebug() << "Exif autorotate: " << file << " using (" << tempURL.path() << ")" << endl;
+ sendInfo(i18n("EXIF rotating file %1...").arg(file));
+ exifRotate(tempURL.path(), file);
+ }
+
+ if (fixDateTime || setPhotographerId || setCredits)
+ {
+ DDebug() << "Set Metadata from: " << file << " using (" << tempURL.path() << ")" << endl;
+ sendInfo(i18n("Setting Metadata tags to file %1...").arg(file));
+ DMetadata metadata(tempURL.path());
+
+ if (fixDateTime)
+ metadata.setImageDateTime(newDateTime, true);
+
+ if (setPhotographerId)
+ metadata.setImagePhotographerId(author, authorTitle);
+
+ if (setCredits)
+ metadata.setImageCredits(credit, source, copyright);
+
+ metadata.applyChanges();
+ }
+
+ // Convert Jpeg file to lossless format if necessary,
+ // and move converted image to destination.
+
+ if (convertJpeg)
+ {
+ DDebug() << "Convert to LossLess: " << file << " using (" << tempURL.path() << ")" << endl;
+ sendInfo(i18n("Converting %1 to lossless file format...").arg(file));
+
+ KURL tempURL2(dest);
+ tempURL2 = tempURL2.upURL();
+ tempURL2.addPath( TQString(".digikam-camera-tmp2-%1").arg(getpid()).append(file));
+ temp = tempURL2.path();
+
+ if (!jpegConvert(tempURL.path(), tempURL2.path(), file, losslessFormat))
+ {
+ // convert failed. delete the temp file
+ unlink(TQFile::encodeName(tempURL.path()));
+ unlink(TQFile::encodeName(tempURL2.path()));
+ result = false;
+ }
+ else
+ {
+ // Else remove only the first temp file.
+ unlink(TQFile::encodeName(tempURL.path()));
+ }
+ }
+ }
+
+ if (result)
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_downloaded);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ event->map.insert("dest", TQVariant(dest));
+ event->map.insert("temp", TQVariant(temp));
+ TQApplication::postEvent(parent, event);
+ }
+ else
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_downloadFailed);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ event->map.insert("dest", TQVariant(dest));
+ TQApplication::postEvent(parent, event);
+ }
+ break;
+ }
+ case(CameraCommand::gp_open):
+ {
+ TQString folder = cmd->map["folder"].asString();
+ TQString file = cmd->map["file"].asString();
+ TQString dest = cmd->map["dest"].asString();
+
+ sendInfo(i18n("Retrieving file %1 from camera...").arg(file));
+
+ bool result = d->camera->downloadItem(folder, file, dest);
+
+ if (result)
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_opened);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ event->map.insert("dest", TQVariant(dest));
+ TQApplication::postEvent(parent, event);
+ }
+ else
+ {
+ sendError(i18n("Failed to retrieve file %1 from camera").arg(file));
+ }
+ break;
+ }
+ case(CameraCommand::gp_upload):
+ {
+ TQString folder = cmd->map["destFolder"].asString();
+
+ // We will using the same source file name to create the dest file
+ // name in camera.
+ TQString file = cmd->map["destFile"].asString();
+
+ // The source file path to download in camera.
+ TQString src = cmd->map["srcFilePath"].asString();
+
+ sendInfo(i18n("Uploading file %1 to camera...").arg(file));
+
+ GPItemInfo itemsInfo;
+
+ bool result = d->camera->uploadItem(folder, file, src, itemsInfo);
+
+ if (result)
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_uploaded);
+ TQByteArray ba;
+ TQDataStream ds(ba, IO_WriteOnly);
+ ds << itemsInfo;
+ event->map.insert("info", TQVariant(ba));
+
+ TQApplication::postEvent(parent, event);
+ }
+ else
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_uploadFailed);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ event->map.insert("src", TQVariant(src));
+ TQApplication::postEvent(parent, event);
+ }
+ break;
+ }
+ case(CameraCommand::gp_delete):
+ {
+ TQString folder = cmd->map["folder"].asString();
+ TQString file = cmd->map["file"].asString();
+
+ sendInfo(i18n("Deleting file %1...").arg(file));
+
+ bool result = d->camera->deleteItem(folder, file);
+
+ if (result)
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_deleted);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ TQApplication::postEvent(parent, event);
+ }
+ else
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_deleteFailed);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ TQApplication::postEvent(parent, event);
+ }
+ break;
+ }
+ case(CameraCommand::gp_lock):
+ {
+ TQString folder = cmd->map["folder"].asString();
+ TQString file = cmd->map["file"].asString();
+ bool lock = cmd->map["lock"].asBool();
+
+ sendInfo(i18n("Toggle lock file %1...").arg(file));
+
+ bool result = d->camera->setLockItem(folder, file, lock);
+
+ if (result)
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_locked);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ TQApplication::postEvent(parent, event);
+ }
+ else
+ {
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_lockFailed);
+ event->map.insert("folder", TQVariant(folder));
+ event->map.insert("file", TQVariant(file));
+ TQApplication::postEvent(parent, event);
+ }
+ break;
+ }
+ default:
+ DWarning() << k_funcinfo << " unknown action specified" << endl;
+ }
+
+ delete cmd;
+ }
+
+ sendBusy(false);
+}
+
+void CameraThread::sendBusy(bool val)
+{
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_busy);
+ event->result = val;
+ TQApplication::postEvent(parent, event);
+}
+
+void CameraThread::sendError(const TQString& msg)
+{
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_errormsg);
+ event->msg = msg;
+ TQApplication::postEvent(parent, event);
+}
+
+void CameraThread::sendInfo(const TQString& msg)
+{
+ CameraEvent* event = new CameraEvent(CameraEvent::gp_infomsg);
+ event->msg = msg;
+ TQApplication::postEvent(parent, event);
+}
+
+
+//-- Camera Controller ------------------------------------------------------
+
+
+CameraController::CameraController(TQWidget* parent, const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path)
+ : TQObject(parent)
+{
+ d = new CameraControllerPriv;
+ d->parent = parent;
+ d->canceled = false;
+ d->close = false;
+ d->overwriteAll = false;
+ d->skipAll = false;
+ d->downloadTotal = 0;
+ d->camera = 0;
+
+ // URL parsing (c) Stephan Kulow
+ if (path.startsWith("camera:/"))
+ {
+ KURL url(path);
+ DDebug() << "path " << path << " " << url << " " << url.host() << endl;
+ TQString xport = url.host();
+ if (xport.startsWith("usb:"))
+ {
+ DDebug() << "xport " << xport << endl;
+ TQRegExp x = TQRegExp("(usb:[0-9,]*)");
+
+ if (x.search(xport) != -1)
+ {
+ TQString usbport = x.cap(1);
+ DDebug() << "USB " << xport << " " << usbport << endl;
+ // if ((xport == usbport) || ((count == 1) && (xport == "usb:"))) {
+ // model = xmodel;
+ d->camera = new GPCamera(title, url.user(), "usb:", "/");
+ // }
+ }
+ }
+ }
+
+ if (!d->camera)
+ {
+ if (model.lower() == "directory browse")
+ d->camera = new UMSCamera(title, model, port, path);
+ else
+ d->camera = new GPCamera(title, model, port, path);
+ }
+
+ d->thread = new CameraThread(this);
+ d->timer = new TQTimer(this);
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotProcessNext()));
+
+ d->timer->start(50, false);
+}
+
+CameraController::~CameraController()
+{
+ if (d->timer->isActive())
+ {
+ d->timer->stop();
+ delete d->timer;
+ }
+
+ d->camera->cancel();
+ d->canceled = true;
+ d->close = true;
+
+ while (d->thread->running())
+ d->thread->wait();
+
+ delete d->thread;
+ delete d->camera;
+ delete d;
+}
+
+TQString CameraController::getCameraPath()
+{
+ if (!d->camera) return TQString();
+ return d->camera->path();
+}
+
+TQString CameraController::getCameraTitle()
+{
+ if (!d->camera) return TQString();
+ return d->camera->title();
+}
+
+void CameraController::slotConnect()
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_connect;
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::listFolders()
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_listfolders;
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::listFiles(const TQString& folder)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_listfiles;
+ cmd->map.insert("folder", TQVariant(folder));
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::getThumbnail(const TQString& folder, const TQString& file)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_thumbnail;
+ cmd->map.insert("folder", TQVariant(folder));
+ cmd->map.insert("file", TQVariant(file));
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::getExif(const TQString& folder, const TQString& file)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_exif;
+ cmd->map.insert("folder", TQVariant(folder));
+ cmd->map.insert("file", TQVariant(file));
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::getCameraInformations()
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_cameraInformations;
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::upload(const TQFileInfo& srcFileInfo, const TQString& destFile, const TQString& destFolder)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_upload;
+ cmd->map.insert("srcFilePath", TQVariant(srcFileInfo.filePath()));
+ cmd->map.insert("destFile", TQVariant(destFile));
+ cmd->map.insert("destFolder", TQVariant(destFolder));
+ d->cmdQueue.enqueue(cmd);
+ DDebug() << "Uploading '" << srcFileInfo.filePath() << "' into camera : '" << destFolder <<
+ "' (" << destFile << ")" << endl;
+}
+
+void CameraController::downloadPrep()
+{
+ d->overwriteAll = false;
+ d->skipAll = false;
+ d->downloadTotal = 0;
+}
+
+void CameraController::download(DownloadSettingsContainer downloadSettings)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_download;
+ cmd->map.insert("folder", TQVariant(downloadSettings.folder));
+ cmd->map.insert("file", TQVariant(downloadSettings.file));
+ cmd->map.insert("dest", TQVariant(downloadSettings.dest));
+ cmd->map.insert("autoRotate", TQVariant(downloadSettings.autoRotate));
+ cmd->map.insert("fixDateTime", TQVariant(downloadSettings.fixDateTime));
+ cmd->map.insert("newDateTime", TQVariant(downloadSettings.newDateTime));
+ cmd->map.insert("setPhotographerId", TQVariant(downloadSettings.setPhotographerId));
+ cmd->map.insert("author", TQVariant(downloadSettings.author));
+ cmd->map.insert("authorTitle", TQVariant(downloadSettings.authorTitle));
+ cmd->map.insert("setCredits", TQVariant(downloadSettings.setCredits));
+ cmd->map.insert("credit", TQVariant(downloadSettings.credit));
+ cmd->map.insert("source", TQVariant(downloadSettings.source));
+ cmd->map.insert("copyright", TQVariant(downloadSettings.copyright));
+ cmd->map.insert("convertJpeg", TQVariant(downloadSettings.convertJpeg));
+ cmd->map.insert("losslessFormat", TQVariant(downloadSettings.losslessFormat));
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::deleteFile(const TQString& folder, const TQString& file)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_delete;
+ cmd->map.insert("folder", TQVariant(folder));
+ cmd->map.insert("file", TQVariant(file));
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::lockFile(const TQString& folder, const TQString& file, bool lock)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_lock;
+ cmd->map.insert("folder", TQVariant(folder));
+ cmd->map.insert("file", TQVariant(file));
+ cmd->map.insert("lock", TQVariant(lock));
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::openFile(const TQString& folder, const TQString& file)
+{
+ d->canceled = false;
+ CameraCommand *cmd = new CameraCommand;
+ cmd->action = CameraCommand::gp_open;
+ cmd->map.insert("folder", TQVariant(folder));
+ cmd->map.insert("file", TQVariant(file));
+ cmd->map.insert("dest", TQVariant(locateLocal("tmp", file)));
+ d->cmdQueue.enqueue(cmd);
+}
+
+void CameraController::slotCancel()
+{
+ d->canceled = true;
+ d->cmdQueue.flush();
+ d->camera->cancel();
+}
+
+void CameraController::customEvent(TQCustomEvent* e)
+{
+ CameraEvent* event = dynamic_cast<CameraEvent*>(e);
+ if (!event)
+ {
+ return;
+ }
+
+ switch(event->type()-TQEvent::User)
+ {
+ case (CameraEvent::gp_connected) :
+ {
+ emit signalConnected(event->result);
+ break;
+ }
+ case (CameraEvent::gp_cameraInformations) :
+ {
+ TQString summary = TQDeepCopy<TQString>(event->map["summary"].asString());
+ TQString manual = TQDeepCopy<TQString>(event->map["manual"].asString());
+ TQString about = TQDeepCopy<TQString>(event->map["about"].asString());
+ emit signalCameraInformations(summary, manual, about);
+ break;
+ }
+ case (CameraEvent::gp_errormsg) :
+ {
+ emit signalErrorMsg(TQDeepCopy<TQString>(event->msg));
+ break;
+ }
+ case (CameraEvent::gp_busy) :
+ {
+ if (event->result)
+ emit signalBusy(true);
+ break;
+ }
+ case (CameraEvent::gp_infomsg) :
+ {
+ if (!d->canceled)
+ emit signalInfoMsg(TQDeepCopy<TQString>(event->msg));
+ break;
+ }
+ case (CameraEvent::gp_listedfolders) :
+ {
+ /* TODO: ugly hack since qt <= 3.1.2 does not define
+ TQStringList with TQDeepCopy as a friend. */
+ TQValueList<TQVariant> flist = TQDeepCopy< TQValueList<TQVariant> >(event->map["folders"].toList());
+
+ TQStringList folderList;
+ TQValueList<TQVariant>::Iterator it;
+ for (it = flist.begin(); it != flist.end(); ++it )
+ {
+ folderList.append(TQDeepCopy<TQString>((*it).asString()));
+ }
+
+ emit signalFolderList(folderList);
+ break;
+ }
+ case (CameraEvent::gp_listedfiles) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQByteArray ba = TQDeepCopy<TQByteArray>(event->map["files"].asByteArray());
+ TQDataStream ds(ba, IO_ReadOnly);
+ GPItemInfoList items;
+ ds >> items;
+ emit signalFileList(items);
+ break;
+ }
+ case (CameraEvent::gp_thumbnailed) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ TQImage thumb = TQDeepCopy<TQImage>(event->map["thumbnail"].asImage());
+ emit signalThumbnail(folder, file, thumb);
+ break;
+ }
+ case (CameraEvent::gp_exif) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ TQByteArray ba = TQDeepCopy<TQByteArray>(event->map["exifData"].asByteArray());
+ emit signalExifData(ba);
+ break;
+ }
+ case (CameraEvent::gp_downloadstarted) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ emit signalDownloaded(folder, file, GPItemInfo::DownloadStarted);
+ break;
+ }
+ case (CameraEvent::gp_downloaded) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ TQString dest = TQDeepCopy<TQString>(event->map["dest"].asString());
+ TQString temp = TQDeepCopy<TQString>(event->map["temp"].asString());
+
+ d->timer->stop();
+
+ bool skip = false;
+ bool cancel = false;
+ bool overwrite = false;
+
+ // Check if dest file already exist.
+
+ if (!d->overwriteAll)
+ {
+ TQFileInfo info(dest);
+
+ while (info.exists())
+ {
+ if (d->skipAll)
+ {
+ skip = true;
+ break;
+ }
+
+ TDEIO::RenameDlg dlg(d->parent, i18n("Rename File"),
+ folder + TQString("/") + file, dest,
+ TDEIO::RenameDlg_Mode(TDEIO::M_MULTI | TDEIO::M_OVERWRITE | TDEIO::M_SKIP));
+
+ int result = dlg.exec();
+ dest = dlg.newDestURL().path();
+ info = TQFileInfo(dest);
+
+ switch (result)
+ {
+ case TDEIO::R_CANCEL:
+ {
+ cancel = true;
+ break;
+ }
+ case TDEIO::R_SKIP:
+ {
+ skip = true;
+ break;
+ }
+ case TDEIO::R_AUTO_SKIP:
+ {
+ d->skipAll = true;
+ skip = true;
+ break;
+ }
+ case TDEIO::R_OVERWRITE:
+ {
+ overwrite = true;
+ break;
+ }
+ case TDEIO::R_OVERWRITE_ALL:
+ {
+ d->overwriteAll = true;
+ overwrite = true;
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (cancel || skip || overwrite)
+ break;
+ }
+ }
+
+ if (cancel)
+ {
+ unlink(TQFile::encodeName(temp));
+ slotCancel();
+ d->timer->start(50);
+ emit signalSkipped(folder, file);
+ return;
+ }
+ else if (skip)
+ {
+ unlink(TQFile::encodeName(temp));
+ d->timer->start(50);
+ emit signalInfoMsg(i18n("Skipped file %1").arg(file));
+ emit signalSkipped(folder, file);
+ return;
+ }
+
+ // move the file to the destination file
+ if (rename(TQFile::encodeName(temp), TQFile::encodeName(dest)) != 0)
+ {
+ // rename failed. delete the temp file
+ unlink(TQFile::encodeName(temp));
+ d->timer->start(50);
+ emit signalDownloaded(folder, file, GPItemInfo::DownloadFailed);
+ }
+ else
+ {
+ d->timer->start(50);
+ emit signalDownloaded(folder, file, GPItemInfo::DownloadedYes);
+ }
+ break;
+ }
+ case (CameraEvent::gp_downloadFailed) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+
+ d->timer->stop();
+
+ TQString msg = i18n("Failed to download file \"%1\".").arg(file);
+
+ if (!d->canceled)
+ {
+ if (d->cmdQueue.isEmpty())
+ {
+ KMessageBox::error(d->parent, msg);
+ }
+ else
+ {
+ msg += i18n(" Do you want to continue?");
+ int result = KMessageBox::warningContinueCancel(d->parent, msg);
+ if (result != KMessageBox::Continue)
+ slotCancel();
+ }
+ }
+
+ d->timer->start(50);
+ emit signalDownloaded(folder, file, GPItemInfo::DownloadFailed);
+ break;
+ }
+ case (CameraEvent::gp_uploaded) :
+ {
+ TQByteArray ba = TQDeepCopy<TQByteArray>(event->map["info"].asByteArray());
+ TQDataStream ds(ba, IO_ReadOnly);
+ GPItemInfo itemInfo;
+ ds >> itemInfo;
+
+ emit signalUploaded(itemInfo);
+ break;
+ }
+ case (CameraEvent::gp_uploadFailed) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ TQString src = TQDeepCopy<TQString>(event->map["src"].asString());
+
+ d->timer->stop();
+
+ TQString msg = i18n("Failed to upload file \"%1\".").arg(file);
+
+ if (!d->canceled)
+ {
+ if (d->cmdQueue.isEmpty())
+ {
+ KMessageBox::error(d->parent, msg);
+ }
+ else
+ {
+ msg += i18n(" Do you want to continue?");
+ int result = KMessageBox::warningContinueCancel(d->parent, msg);
+ if (result != KMessageBox::Continue)
+ slotCancel();
+ }
+ }
+
+ d->timer->start(50);
+ break;
+ }
+ case (CameraEvent::gp_deleted) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ emit signalDeleted(folder, file, true);
+ break;
+ }
+ case (CameraEvent::gp_deleteFailed) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+
+ d->timer->stop();
+ emit signalDeleted(folder, file, false);
+
+ TQString msg = i18n("Failed to delete file \"%1\".").arg(file);
+
+ if (!d->canceled)
+ {
+ if (d->cmdQueue.isEmpty())
+ {
+ KMessageBox::error(d->parent, msg);
+ }
+ else
+ {
+ msg += i18n(" Do you want to continue?");
+ int result = KMessageBox::warningContinueCancel(d->parent, msg);
+ if (result != KMessageBox::Continue)
+ slotCancel();
+ }
+ }
+
+ d->timer->start(50);
+ break;
+ }
+ case (CameraEvent::gp_locked) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ emit signalLocked(folder, file, true);
+ break;
+ }
+ case (CameraEvent::gp_lockFailed) :
+ {
+ TQString folder = TQDeepCopy<TQString>(event->map["folder"].asString());
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+
+ d->timer->stop();
+ emit signalLocked(folder, file, false);
+
+ TQString msg = i18n("Failed to toggle lock file \"%1\".").arg(file);
+
+ if (!d->canceled)
+ {
+ if (d->cmdQueue.isEmpty())
+ {
+ KMessageBox::error(d->parent, msg);
+ }
+ else
+ {
+ msg += i18n(" Do you want to continue?");
+ int result = KMessageBox::warningContinueCancel(d->parent, msg);
+ if (result != KMessageBox::Continue)
+ slotCancel();
+ }
+ }
+
+ d->timer->start(50);
+ break;
+ }
+ case (CameraEvent::gp_opened) :
+ {
+ TQString file = TQDeepCopy<TQString>(event->map["file"].asString());
+ TQString dest = TQDeepCopy<TQString>(event->map["dest"].asString());
+
+ KURL url(dest);
+ KURL::List urlList;
+ urlList << url;
+
+ ImageWindow *im = ImageWindow::imagewindow();
+ im->loadURL(urlList, url, i18n("Camera \"%1\"").arg(d->camera->model()), false);
+
+ if (im->isHidden())
+ im->show();
+ else
+ im->raise();
+
+ im->setFocus();
+ break;
+ }
+ default:
+ {
+ DWarning() << k_funcinfo << "Unknown event" << endl;
+ }
+ }
+}
+
+void CameraController::slotProcessNext()
+{
+ if (d->thread->running())
+ return;
+
+ if (d->cmdQueue.isEmpty())
+ {
+ emit signalBusy(false);
+ return;
+ }
+
+ d->timer->stop();
+ emit signalBusy(true);
+
+ CameraCommand* cmd = d->cmdQueue.head();
+
+ TQString folder;
+ TQString file;
+ TQString dest;
+
+ if ((cmd->action == CameraCommand::gp_exif) &&
+ (typeid(*(d->camera)) == typeid(UMSCamera)))
+ {
+ folder = TQDeepCopy<TQString>(cmd->map["folder"].asString());
+ file = TQDeepCopy<TQString>(cmd->map["file"].asString());
+
+ emit signalExifFromFile(folder, file);
+
+ d->cmdQueue.dequeue();
+ d->timer->start(50, false);
+ return;
+ }
+
+ if (cmd->action == CameraCommand::gp_download)
+ {
+ folder = TQDeepCopy<TQString>(cmd->map["folder"].asString());
+ file = TQDeepCopy<TQString>(cmd->map["file"].asString());
+ dest = TQDeepCopy<TQString>(cmd->map["dest"].asString());
+ cmd->map["dest"] = TQVariant(TQDeepCopy<TQString>(dest));
+ }
+
+ d->thread->start();
+ d->timer->start(50, false);
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/cameracontroller.h b/src/utilities/cameragui/cameracontroller.h
new file mode 100644
index 00000000..45e7fede
--- /dev/null
+++ b/src/utilities/cameragui/cameracontroller.h
@@ -0,0 +1,111 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-17
+ * Description : digital camera controller
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERACONTROLLER_H
+#define CAMERACONTROLLER_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqstring.h>
+#include <tqfileinfo.h>
+
+// Local includes.
+
+#include "downloadsettingscontainer.h"
+#include "gpiteminfo.h"
+
+namespace Digikam
+{
+
+class CameraControllerPriv;
+
+class CameraController : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ CameraController(TQWidget* parent, const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path);
+ ~CameraController();
+
+ void listFolders();
+ void listFiles(const TQString& folder);
+ void getThumbnail(const TQString& folder, const TQString& file);
+ void getExif(const TQString& folder, const TQString& file);
+ void getCameraInformations();
+ TQString getCameraPath();
+ TQString getCameraTitle();
+
+ void downloadPrep();
+ void download(DownloadSettingsContainer downloadSettings);
+ void upload(const TQFileInfo& srcFileInfo, const TQString& destFile, const TQString& destFolder);
+ void deleteFile(const TQString& folder, const TQString& file);
+ void lockFile(const TQString& folder, const TQString& file, bool lock);
+ void openFile(const TQString& folder, const TQString& file);
+
+signals:
+
+ void signalBusy(bool val);
+ void signalInfoMsg(const TQString& msg);
+ void signalErrorMsg(const TQString& msg);
+ void signalCameraInformations(const TQString& summary, const TQString& manual, const TQString& about);
+
+ void signalConnected(bool val);
+ void signalFolderList(const TQStringList& folderList);
+ void signalFileList(const GPItemInfoList& infoList);
+ void signalUploaded(const GPItemInfo& itemInfo);
+ void signalDownloaded(const TQString& folder, const TQString& file, int status);
+ void signalSkipped(const TQString& folder, const TQString& file);
+ void signalDeleted(const TQString& folder, const TQString& file, bool status);
+ void signalLocked(const TQString& folder, const TQString& file, bool status);
+ void signalThumbnail(const TQString& folder, const TQString& file, const TQImage& thumb);
+ void signalExifFromFile(const TQString& folder, const TQString& file);
+ void signalExifData(const TQByteArray& exifData);
+
+protected:
+
+ void customEvent(TQCustomEvent* e);
+
+public slots:
+
+ void slotCancel();
+ void slotConnect();
+
+private slots:
+
+ void slotProcessNext();
+
+private:
+
+ CameraControllerPriv *d;
+
+ friend class CameraThread;
+};
+
+} // namespace Digikam
+
+#endif /* CAMERACONTROLLER_H */
diff --git a/src/utilities/cameragui/camerafolderdialog.cpp b/src/utilities/cameragui/camerafolderdialog.cpp
new file mode 100644
index 00000000..93feb0ab
--- /dev/null
+++ b/src/utilities/cameragui/camerafolderdialog.cpp
@@ -0,0 +1,138 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-24
+ * Description : a dialog to select a camera folders.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "cameraiconview.h"
+#include "camerafolderitem.h"
+#include "camerafolderview.h"
+#include "camerafolderdialog.h"
+#include "camerafolderdialog.moc"
+
+namespace Digikam
+{
+
+CameraFolderDialog::CameraFolderDialog(TQWidget *parent, CameraIconView *cameraView,
+ const TQStringList& cameraFolderList,
+ const TQString& cameraName, const TQString& rootPath)
+ : KDialogBase(parent, 0, true,
+ i18n("%1 - Select Camera Folder").arg(cameraName),
+ Help|Ok|Cancel, Ok, true)
+{
+ setHelp("camerainterface.anchor", "digikam");
+ enableButtonOK(false);
+
+ m_rootPath = rootPath;
+
+ TQFrame *page = makeMainWidget();
+ TQGridLayout* grid = new TQGridLayout(page, 2, 1, 0, spacingHint());
+
+ m_folderView = new CameraFolderView(page);
+ TQLabel *logo = new TQLabel(page);
+ TQLabel *message = new TQLabel(page);
+
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 128, TDEIcon::DefaultState, 0, true));
+ message->setText(i18n("<p>Please select the camera folder "
+ "where you want to upload the images.</p>"));
+
+ grid->addMultiCellWidget(logo, 0, 0, 0, 0);
+ grid->addMultiCellWidget(message, 1, 1, 0, 0);
+ grid->addMultiCellWidget(m_folderView, 0, 2, 1, 1);
+ grid->setRowStretch(2, 10);
+
+ m_folderView->addVirtualFolder(cameraName);
+ m_folderView->addRootFolder("/", cameraView->countItemsByFolder(rootPath));
+
+ for (TQStringList::const_iterator it = cameraFolderList.begin();
+ it != cameraFolderList.end(); ++it)
+ {
+ TQString folder(*it);
+ if (folder.startsWith(rootPath) && rootPath != TQString("/"))
+ folder.remove(0, rootPath.length());
+
+ if (folder != TQString("/") && !folder.isEmpty())
+ {
+ TQString root = folder.section( '/', 0, -2 );
+ if (root.isEmpty()) root = TQString("/");
+
+ TQString sub = folder.section( '/', -1 );
+ m_folderView->addFolder(root, sub, cameraView->countItemsByFolder(*it));
+ DDebug() << "Camera folder: '" << folder << "' (root='" << root << "', sub='" <<sub <<"')" << endl;
+ }
+ }
+
+ connect(m_folderView, TQ_SIGNAL(signalFolderChanged(CameraFolderItem*)),
+ this, TQ_SLOT(slotFolderPathSelectionChanged(CameraFolderItem*)));
+
+ resize(500, 500);
+}
+
+CameraFolderDialog::~CameraFolderDialog()
+{
+}
+
+TQString CameraFolderDialog::selectedFolderPath()
+{
+ TQListViewItem *item = m_folderView->currentItem();
+ if (!item) return TQString();
+
+ CameraFolderItem *folderItem = static_cast<CameraFolderItem *>(item);
+ if (folderItem->isVirtualFolder())
+ return TQString(m_rootPath);
+
+ // Case of Gphoto2 cameras. No need to duplicate root '/'.
+ if (m_rootPath == TQString("/"))
+ return(folderItem->folderPath());
+
+ return(m_rootPath + folderItem->folderPath());
+}
+
+void CameraFolderDialog::slotFolderPathSelectionChanged(CameraFolderItem* item)
+{
+ if (item)
+ {
+ enableButtonOK(true);
+ DDebug() << "Camera folder path: " << selectedFolderPath() << endl;
+ }
+ else
+ {
+ enableButtonOK(false);
+ }
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/cameragui/camerafolderdialog.h b/src/utilities/cameragui/camerafolderdialog.h
new file mode 100644
index 00000000..efc7586a
--- /dev/null
+++ b/src/utilities/cameragui/camerafolderdialog.h
@@ -0,0 +1,68 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-24
+ * Description : a dialog to select a camera folders.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAFOLDERDIALOG_H
+#define CAMERAFOLDERDIALOG_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+namespace Digikam
+{
+
+class CameraIconView;
+class CameraFolderView;
+class CameraFolderItem;
+
+class CameraFolderDialog : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ CameraFolderDialog(TQWidget *parent, CameraIconView *cameraView, const TQStringList& cameraFolderList,
+ const TQString& cameraName, const TQString& rootPath);
+ ~CameraFolderDialog();
+
+ TQString selectedFolderPath();
+
+private slots:
+
+ void slotFolderPathSelectionChanged(CameraFolderItem* item);
+
+private:
+
+ TQString m_rootPath;
+
+ CameraFolderView *m_folderView;
+};
+
+} // namespace Digikam
+
+#endif /* CAMERAFOLDERDIALOG_H */
diff --git a/src/utilities/cameragui/camerafolderitem.cpp b/src/utilities/cameragui/camerafolderitem.cpp
new file mode 100644
index 00000000..f53508a6
--- /dev/null
+++ b/src/utilities/cameragui/camerafolderitem.cpp
@@ -0,0 +1,108 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-23
+ * Description : A widget to display a camera folder.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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 bythe Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "camerafolderitem.h"
+
+namespace Digikam
+{
+
+class CameraFolderItemPriv
+{
+public:
+
+ CameraFolderItemPriv()
+ {
+ count = 0;
+ }
+
+ bool virtualFolder;
+
+ int count;
+
+ TQString folderName;
+ TQString folderPath;
+ TQString name;
+};
+
+CameraFolderItem::CameraFolderItem(TQListView* parent, const TQString& name, const TQPixmap& pixmap)
+ : TQListViewItem(parent, name)
+{
+ d = new CameraFolderItemPriv;
+ d->virtualFolder = true;
+ d->name = name;
+ setPixmap(0, pixmap);
+}
+
+CameraFolderItem::CameraFolderItem(TQListViewItem* parent, const TQString& folderName,
+ const TQString& folderPath, const TQPixmap& pixmap)
+ : TQListViewItem(parent, folderName)
+{
+ d = new CameraFolderItemPriv;
+ d->folderName = folderName;
+ d->folderPath = folderPath;
+ d->virtualFolder = false;
+ d->name = folderName;
+ setPixmap(0, pixmap);
+}
+
+CameraFolderItem::~CameraFolderItem()
+{
+ delete d;
+}
+
+bool CameraFolderItem::isVirtualFolder()
+{
+ return d->virtualFolder;
+}
+
+TQString CameraFolderItem::folderName()
+{
+ return d->folderName;
+}
+
+TQString CameraFolderItem::folderPath()
+{
+ return d->folderPath;
+}
+
+void CameraFolderItem::changeCount(int val)
+{
+ d->count += val;
+ setText(0, TQString("%1 (%2)").arg(d->name).arg(TQString::number(d->count)));
+}
+
+void CameraFolderItem::setCount(int val)
+{
+ d->count = val;
+ setText(0, TQString("%1 (%2)").arg(d->name).arg(TQString::number(d->count)));
+}
+
+int CameraFolderItem::count()
+{
+ return d->count;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/camerafolderitem.h b/src/utilities/cameragui/camerafolderitem.h
new file mode 100644
index 00000000..dde2c5c6
--- /dev/null
+++ b/src/utilities/cameragui/camerafolderitem.h
@@ -0,0 +1,69 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-23
+ * Description : A widget to display a camera folder.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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 bythe Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAFOLDERITEM_H
+#define CAMERAFOLDERITEM_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlistview.h>
+
+// KDE includes.
+
+#include <kiconloader.h>
+
+namespace Digikam
+{
+
+class CameraFolderItemPriv;
+
+class CameraFolderItem : public TQListViewItem
+{
+
+public:
+
+ CameraFolderItem(TQListView* parent, const TQString& name,
+ const TQPixmap& pixmap=SmallIcon("folder"));
+
+ CameraFolderItem(TQListViewItem* parent, const TQString& folderName, const TQString& folderPath,
+ const TQPixmap& pixmap=SmallIcon("folder"));
+
+ ~CameraFolderItem();
+
+ TQString folderName();
+ TQString folderPath();
+ bool isVirtualFolder();
+ void changeCount(int val);
+ void setCount(int val);
+ int count();
+
+private:
+
+ CameraFolderItemPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* CAMERAFOLDERITEM_H */
diff --git a/src/utilities/cameragui/camerafolderview.cpp b/src/utilities/cameragui/camerafolderview.cpp
new file mode 100644
index 00000000..877c4b51
--- /dev/null
+++ b/src/utilities/cameragui/camerafolderview.cpp
@@ -0,0 +1,169 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-23
+ * Description : A widget to display a list of camera folders.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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 bythe Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "camerafolderitem.h"
+#include "camerafolderview.h"
+#include "camerafolderview.moc"
+
+namespace Digikam
+{
+
+class CameraFolderViewPriv
+{
+public:
+
+ CameraFolderViewPriv()
+ {
+ virtualFolder = 0;
+ rootFolder = 0;
+ cameraName = TQString("Camera");
+ }
+
+ TQString cameraName;
+
+ CameraFolderItem *virtualFolder;
+ CameraFolderItem *rootFolder;
+};
+
+CameraFolderView::CameraFolderView(TQWidget* parent)
+ : TQListView(parent)
+{
+ d = new CameraFolderViewPriv;
+
+ addColumn(i18n("Camera Folders"));
+ setColumnWidthMode( 0, TQListView::Maximum );
+ setResizeMode( TQListView::AllColumns );
+ setSelectionMode(TQListView::Single);
+
+ connect(this, TQ_SIGNAL(currentChanged(TQListViewItem*)),
+ this, TQ_SLOT(slotCurrentChanged(TQListViewItem*)));
+
+ connect(this, TQ_SIGNAL(clicked(TQListViewItem*)),
+ this, TQ_SLOT(slotCurrentChanged(TQListViewItem*)));
+}
+
+CameraFolderView::~CameraFolderView()
+{
+ delete d;
+}
+
+void CameraFolderView::addVirtualFolder(const TQString& name, const TQPixmap& pixmap)
+{
+ d->cameraName = name;
+ d->virtualFolder = new CameraFolderItem(this, d->cameraName, pixmap);
+ d->virtualFolder->setOpen(true);
+ d->virtualFolder->setSelected(false);
+ d->virtualFolder->setSelectable(false);
+}
+
+void CameraFolderView::addRootFolder(const TQString& folder, int nbItems, const TQPixmap& pixmap)
+{
+ d->rootFolder = new CameraFolderItem(d->virtualFolder, folder, folder, pixmap);
+ d->rootFolder->setOpen(true);
+ d->rootFolder->setCount(nbItems);
+}
+
+CameraFolderItem* CameraFolderView::addFolder(const TQString& folder, const TQString& subFolder,
+ int nbItems, const TQPixmap& pixmap)
+{
+ CameraFolderItem *parentItem = findFolder(folder);
+
+ DDebug() << "CameraFolderView: Adding Subfolder " << subFolder
+ << " of folder " << folder << endl;
+
+ if (parentItem)
+ {
+ TQString path(folder);
+
+ if (!folder.endsWith("/"))
+ path += '/';
+
+ path += subFolder;
+ CameraFolderItem* item = new CameraFolderItem(parentItem, subFolder, path, pixmap);
+
+ DDebug() << "CameraFolderView: Added ViewItem with path "
+ << item->folderPath() << endl;
+
+ item->setCount(nbItems);
+ item->setOpen(true);
+ return item;
+ }
+ else
+ {
+ DWarning() << "CameraFolderView: Couldn't find parent for subFolder "
+ << subFolder << " of folder " << folder << endl;
+ return 0;
+ }
+}
+
+CameraFolderItem* CameraFolderView::findFolder(const TQString& folderPath)
+{
+
+ TQListViewItemIterator it(this);
+ for ( ; it.current(); ++it)
+ {
+ CameraFolderItem* item = static_cast<CameraFolderItem*>(it.current());
+
+ if (item->folderPath() == folderPath)
+ return item;
+ }
+
+ return 0;
+}
+
+void CameraFolderView::slotCurrentChanged(TQListViewItem* item)
+{
+ if (!item)
+ emit signalFolderChanged(0);
+ else
+ emit signalFolderChanged(static_cast<CameraFolderItem *>(item));
+}
+
+CameraFolderItem* CameraFolderView::virtualFolder()
+{
+ return d->virtualFolder;
+}
+
+CameraFolderItem* CameraFolderView::rootFolder()
+{
+ return d->rootFolder;
+}
+
+void CameraFolderView::clear()
+{
+ TQListView::clear();
+ d->virtualFolder = 0;
+ d->rootFolder = 0;
+ emit signalCleared();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/camerafolderview.h b/src/utilities/cameragui/camerafolderview.h
new file mode 100644
index 00000000..1b02fc9c
--- /dev/null
+++ b/src/utilities/cameragui/camerafolderview.h
@@ -0,0 +1,83 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-23
+ * Description : A widget to display a list of camera folders.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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 bythe Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAFOLDERVIEW_H
+#define CAMERAFOLDERVIEW_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlistview.h>
+
+// KDE includes.
+
+#include <kiconloader.h>
+
+namespace Digikam
+{
+
+class CameraFolderItem;
+class CameraFolderViewPriv;
+
+class CameraFolderView : public TQListView
+{
+ TQ_OBJECT
+
+
+public:
+
+ CameraFolderView(TQWidget* parent);
+ ~CameraFolderView();
+
+ void addVirtualFolder(const TQString& name, const TQPixmap& pixmap=SmallIcon("camera-photo"));
+ void addRootFolder(const TQString& folder, int nbItems, const TQPixmap& pixmap=SmallIcon("folder"));
+
+ CameraFolderItem* addFolder(const TQString& folder, const TQString& subFolder, int nbItems,
+ const TQPixmap& pixmap=SmallIcon("folder"));
+
+ CameraFolderItem* findFolder(const TQString& folderPath);
+
+ CameraFolderItem* virtualFolder();
+ CameraFolderItem* rootFolder();
+
+ virtual void clear();
+
+signals:
+
+ void signalFolderChanged(CameraFolderItem*);
+ void signalCleared();
+
+private slots:
+
+ void slotCurrentChanged(TQListViewItem*);
+
+private:
+
+ CameraFolderViewPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif /* CAMERAFOLDERVIEW_H */
diff --git a/src/utilities/cameragui/cameraiconitem.cpp b/src/utilities/cameragui/cameraiconitem.cpp
new file mode 100644
index 00000000..c68f7855
--- /dev/null
+++ b/src/utilities/cameragui/cameraiconitem.cpp
@@ -0,0 +1,302 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-21
+ * Description : camera icon view item
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "iconview.h"
+#include "thumbnailsize.h"
+#include "albumiconitem.h"
+#include "gpiteminfo.h"
+#include "themeengine.h"
+#include "cameraiconview.h"
+#include "cameraiconitem.h"
+
+namespace Digikam
+{
+
+class CameraIconViewItemPriv
+{
+
+public:
+
+ CameraIconViewItemPriv()
+ {
+ itemInfo = 0;
+ }
+
+ TQString downloadName;
+
+ TQPixmap pixmap;
+ TQPixmap thumbnail;
+
+ TQRect pixRect;
+ TQRect textRect;
+ TQRect extraRect;
+
+ GPItemInfo *itemInfo;
+};
+
+CameraIconViewItem::CameraIconViewItem(IconGroupItem* parent, const GPItemInfo& itemInfo,
+ const TQImage& thumbnail, const TQString& downloadName)
+ : IconItem(parent)
+{
+ d = new CameraIconViewItemPriv;
+ d->itemInfo = new GPItemInfo(itemInfo);
+ d->downloadName = downloadName;
+ setThumbnail(thumbnail);
+}
+
+CameraIconViewItem::~CameraIconViewItem()
+{
+ delete d->itemInfo;
+ delete d;
+}
+
+void CameraIconViewItem::setThumbnail(const TQImage& thumbnail)
+{
+ d->thumbnail = TQPixmap(thumbnail);
+}
+
+GPItemInfo* CameraIconViewItem::itemInfo() const
+{
+ return d->itemInfo;
+}
+
+void CameraIconViewItem::paintItem()
+{
+ CameraIconView* view = (CameraIconView*)iconView();
+ TQFont fn(view->font());
+
+ TQPixmap pix;
+ TQRect r(rect());
+
+ if (isSelected())
+ pix = *(view->itemBaseSelPixmap());
+ else
+ pix = *(view->itemBaseRegPixmap());
+
+ ThemeEngine* te = ThemeEngine::instance();
+
+ TQPainter p(&pix);
+
+ TQString itemName = AlbumIconItem::squeezedText(&p, r.width()-5, d->itemInfo->name);
+ TQString downloadName = AlbumIconItem::squeezedText(&p, r.width()-5, d->downloadName);
+ calcRect(itemName, downloadName);
+
+ p.setPen(isSelected() ? te->textSelColor() : te->textRegColor());
+
+ p.drawPixmap(d->pixRect.x() + (d->pixRect.width() - d->pixmap.width()) /2,
+ d->pixRect.y() + (d->pixRect.height() - d->pixmap.height()) /2,
+ d->pixmap);
+
+ p.drawText(d->textRect, TQt::AlignHCenter|TQt::AlignTop, itemName);
+
+ if (!d->downloadName.isEmpty())
+ {
+ if (fn.pointSize() > 0)
+ fn.setPointSize(TQMAX(fn.pointSize()-2, 6));
+
+ p.setFont(fn);
+ p.setPen(isSelected() ? te->textSpecialSelColor() : te->textSpecialRegColor());
+ p.drawText(d->extraRect, TQt::AlignHCenter|TQt::AlignTop, downloadName);
+ }
+
+ if (this == iconView()->currentItem())
+ {
+ p.setPen(TQPen(isSelected() ? TQt::white : TQt::black, 1, TQt::DotLine));
+ p.drawRect(0, 0, r.width(), r.height());
+ }
+
+ // Draw download status icon.
+ TQPixmap downloaded;
+
+ switch (d->itemInfo->downloaded)
+ {
+ case GPItemInfo::NewPicture:
+ {
+ downloaded = TQPixmap(view->newPicturePixmap());
+ break;
+ }
+ case GPItemInfo::DownloadedYes:
+ {
+ downloaded = SmallIcon( "button_ok" );
+ break;
+ }
+ case GPItemInfo::DownloadStarted:
+ {
+ downloaded = SmallIcon( "system-run" );
+ break;
+ }
+ case GPItemInfo::DownloadFailed:
+ {
+ downloaded = SmallIcon( "button_cancel" );
+ break;
+ }
+ /* TODO: see B.K.O #107316 : disable temporally the unknow download status until
+ a new method to identify the already downloaded pictures from camera is
+ implemented.
+
+ case GPItemInfo::DownloadUnknow:
+ {
+ downloaded = view->unknowPicturePixmap();
+ break;
+ }
+ */
+ }
+
+ if (!downloaded.isNull())
+ p.drawPixmap(rect().width() - downloaded.width() - 5, 5, downloaded);
+
+ // If camera item is locked (read only), draw a "Lock" icon.
+ if (d->itemInfo->writePermissions == 0)
+ p.drawPixmap(5, 5, SmallIcon( "encrypted" ));
+
+ p.end();
+
+ r = TQRect(view->contentsToViewport(TQPoint(r.x(), r.y())),
+ TQSize(r.width(), r.height()));
+
+ bitBlt(view->viewport(), r.x(), r.y(), &pix);
+}
+
+void CameraIconViewItem::setDownloadName(const TQString& downloadName)
+{
+ d->downloadName = downloadName;
+ repaint();
+}
+
+TQString CameraIconViewItem::getDownloadName() const
+{
+ return d->downloadName;
+}
+
+void CameraIconViewItem::setDownloaded(int status)
+{
+ d->itemInfo->downloaded = status;
+ repaint();
+}
+
+bool CameraIconViewItem::isDownloaded() const
+{
+ return (d->itemInfo->downloaded == GPItemInfo::DownloadedYes);
+}
+
+void CameraIconViewItem::toggleLock()
+{
+ if (d->itemInfo->writePermissions == 0)
+ d->itemInfo->writePermissions = 1;
+ else
+ d->itemInfo->writePermissions = 0;
+
+ repaint();
+}
+
+void CameraIconViewItem::calcRect(const TQString& itemName, const TQString& downloadName)
+{
+ CameraIconView* view = (CameraIconView*)iconView();
+ int thumbSize = view->thumbnailSize().size();
+ d->pixmap = TQPixmap(d->thumbnail.convertToImage().smoothScale(thumbSize, thumbSize, TQImage::ScaleMin));
+ d->pixRect = TQRect(0,0,0,0);
+ d->textRect = TQRect(0,0,0,0);
+ d->extraRect = TQRect(0,0,0,0);
+ TQRect itemRect = rect();
+ itemRect.moveTopLeft(TQPoint(0, 0));
+
+ d->pixRect.setWidth(thumbSize);
+ d->pixRect.setHeight(thumbSize);
+
+ TQFontMetrics fm(iconView()->font());
+ TQRect r = TQRect(fm.boundingRect(0, 0, thumbSize, 0xFFFFFFFF,
+ TQt::AlignHCenter | TQt::AlignTop,
+ itemName));
+ d->textRect.setWidth(r.width());
+ d->textRect.setHeight(r.height());
+
+ if (!d->downloadName.isEmpty())
+ {
+ TQFont fn(iconView()->font());
+ if (fn.pointSize() > 0)
+ {
+ fn.setPointSize(TQMAX(fn.pointSize()-2, 6));
+ }
+
+ fm = TQFontMetrics(fn);
+ r = TQRect(fm.boundingRect(0, 0, thumbSize, 0xFFFFFFFF,
+ TQt::AlignHCenter | TQt::WordBreak,
+ downloadName));
+ d->extraRect.setWidth(r.width());
+ d->extraRect.setHeight(r.height());
+
+ d->textRect.setWidth(TQMAX(d->textRect.width(), d->extraRect.width()));
+ d->textRect.setHeight(d->textRect.height() + d->extraRect.height());
+ }
+
+ int w = TQMAX(d->textRect.width(), d->pixRect.width() );
+ int h = d->textRect.height() + d->pixRect.height() ;
+
+ itemRect.setWidth(w+4);
+ itemRect.setHeight(h+4);
+
+ // Center the pix and text rect
+ d->pixRect = TQRect(2, 2, d->pixRect.width(), d->pixRect.height());
+ d->textRect = TQRect((itemRect.width() - d->textRect.width())/2,
+ itemRect.height() - d->textRect.height(),
+ d->textRect.width(), d->textRect.height());
+
+ if (!d->extraRect.isEmpty())
+ {
+ d->extraRect = TQRect((itemRect.width() - d->extraRect.width())/2,
+ itemRect.height() - d->extraRect.height(),
+ d->extraRect.width(), d->extraRect.height());
+ }
+}
+
+TQRect CameraIconViewItem::clickToOpenRect()
+{
+ TQRect r(rect());
+
+ if (d->pixmap.isNull())
+ {
+ TQRect pixRect(d->pixRect);
+ pixRect.moveBy(r.x(), r.y());
+ return pixRect;
+ }
+
+ TQRect pixRect(d->pixRect.x() + (d->pixRect.width() - d->pixmap.width())/2,
+ d->pixRect.y() + (d->pixRect.height() - d->pixmap.height())/2,
+ d->pixmap.width(), d->pixmap.height());
+ pixRect.moveBy(r.x(), r.y());
+ return pixRect;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/cameraiconitem.h b/src/utilities/cameragui/cameraiconitem.h
new file mode 100644
index 00000000..961bc1a3
--- /dev/null
+++ b/src/utilities/cameragui/cameraiconitem.h
@@ -0,0 +1,81 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-21
+ * Description : camera icon view item
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAICONITEM_H
+#define CAMERAICONITEM_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqimage.h>
+
+// Local includes.
+
+#include "iconitem.h"
+
+namespace Digikam
+{
+
+class GPItemInfo;
+class CameraIconViewItemPriv;
+
+class CameraIconViewItem : public IconItem
+{
+
+public:
+
+ CameraIconViewItem(IconGroupItem* parent, const GPItemInfo& itemInfo,
+ const TQImage& thumbnail, const TQString& downloadName=TQString());
+ ~CameraIconViewItem();
+
+ void setThumbnail(const TQImage& thumbnail);
+
+ void setDownloadName(const TQString& downloadName);
+ TQString getDownloadName() const;
+ void setDownloaded(int status);
+ bool isDownloaded() const;
+
+ void toggleLock();
+
+ GPItemInfo* itemInfo() const;
+
+ // reimplemented from IconItem
+ virtual TQRect clickToOpenRect();
+
+protected:
+
+ virtual void paintItem();
+
+private:
+
+ void calcRect(const TQString& itemName, const TQString& downloadName);
+
+private:
+
+ CameraIconViewItemPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* CAMERAICONITEM_H */
diff --git a/src/utilities/cameragui/cameraiconview.cpp b/src/utilities/cameragui/cameraiconview.cpp
new file mode 100644
index 00000000..ba496217
--- /dev/null
+++ b/src/utilities/cameragui/cameraiconview.cpp
@@ -0,0 +1,900 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-18
+ * Description : camera icon view
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+#include <tqtimer.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqcursor.h>
+#include <tqfontmetrics.h>
+#include <tqfont.h>
+#include <tqdragobject.h>
+#include <tqclipboard.h>
+
+// KDE includes.
+
+#include <kurldrag.h>
+#include <kmimetype.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeaction.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "themeengine.h"
+#include "thumbnailsize.h"
+#include "gpiteminfo.h"
+#include "renamecustomizer.h"
+#include "icongroupitem.h"
+#include "dpopupmenu.h"
+#include "dragobjects.h"
+#include "cameraui.h"
+#include "cameraiconitem.h"
+#include "cameraiconview.h"
+#include "cameraiconview.moc"
+
+namespace Digikam
+{
+
+class CameraIconViewPriv
+{
+public:
+
+ CameraIconViewPriv()
+ {
+ renamer = 0;
+ groupItem = 0;
+ cameraUI = 0;
+ thumbSize = ThumbnailSize::Large;
+ pixmapNewPicture = TQPixmap(newPicture_xpm);
+ pixmapUnknowPicture = TQPixmap(unknowPicture_xpm);
+ }
+
+ static const char *newPicture_xpm[];
+ static const char *unknowPicture_xpm[];
+
+ TQDict<CameraIconViewItem> itemDict;
+
+ TQRect itemRect;
+
+ TQPixmap itemRegPixmap;
+ TQPixmap itemSelPixmap;
+ TQPixmap pixmapNewPicture;
+ TQPixmap pixmapUnknowPicture;
+
+ RenameCustomizer *renamer;
+
+ IconGroupItem *groupItem;
+
+ ThumbnailSize thumbSize;
+
+ CameraUI *cameraUI;
+};
+
+const char *CameraIconViewPriv::newPicture_xpm[] =
+{
+ "13 13 8 1",
+ " c None",
+ ". c #232300",
+ "+ c #F6F611",
+ "@ c #000000",
+ "# c #DBDA4D",
+ "$ c #FFFF00",
+ "% c #AAA538",
+ "& c #E8E540",
+ " . ",
+ " . .+. . ",
+ " @#@ .$. .#. ",
+ " @$@#$#@$. ",
+ " @$%&%$@ ",
+ " ..#%&&&%#.. ",
+ ".+$$&&&&&$$+@",
+ " ..#%&&&%#@@ ",
+ " @$%&%$@ ",
+ " .$@#$#@$. ",
+ " @#. @$@ @#. ",
+ " . @+@ . ",
+ " @ "
+};
+
+const char *CameraIconViewPriv::unknowPicture_xpm[] =
+{
+ "16 16 78 1",
+ " g None",
+ ". g #777777",
+ "+ g #7A7A7A",
+ "@ g #8C8C8C",
+ "# g #787878",
+ "$ g #707070",
+ "% g #878787",
+ "& g #C3C3C3",
+ "* g #EAEAEA",
+ "= g #E4E4E4",
+ "- g #E2E2E2",
+ "; g #E6E6E6",
+ "> g #CECECE",
+ ", g #888888",
+ "' g #6B6B6B",
+ ") g #969696",
+ "! g #DEDEDE",
+ "~ g #D8D8D8",
+ "{ g #FFFFFF",
+ "] g #F2F2F2",
+ "^ g #DFDFDF",
+ "/ g #9D9D9D",
+ "( g #686868",
+ "_ g #848484",
+ ": g #D0D0D0",
+ "< g #F1F1F1",
+ "[ g #F0F0F0",
+ "} g #EBEBEB",
+ "| g #FDFDFD",
+ "1 g #DDDDDD",
+ "2 g #D4D4D4",
+ "3 g #838383",
+ "4 g #ABABAB",
+ "5 g #C8C8C8",
+ "6 g #CCCCCC",
+ "7 g #F4F4F4",
+ "8 g #D6D6D6",
+ "9 g #E8E8E8",
+ "0 g #C4C4C4",
+ "a g #A4A4A4",
+ "b g #656565",
+ "c g #B4B4B4",
+ "d g #B9B9B9",
+ "e g #BDBDBD",
+ "f g #B7B7B7",
+ "g g #898989",
+ "h g #6D6D6D",
+ "i g #808080",
+ "j g #AAAAAA",
+ "k g #A9A9A9",
+ "l g #737373",
+ "m g #7F7F7F",
+ "n g #9A9A9A",
+ "o g #D3D3D3",
+ "p g #909090",
+ "q g #727272",
+ "r g #8F8F8F",
+ "s g #8E8E8E",
+ "t g #8D8D8D",
+ "u g #EEEEEE",
+ "v g #FAFAFA",
+ "w g #929292",
+ "x g #C5C5C5",
+ "y g #5F5F5F",
+ "z g #989898",
+ "A g #CFCFCF",
+ "B g #9C9C9C",
+ "C g #A0A0A0",
+ "D g #FEFEFE",
+ "E g #ACACAC",
+ "F g #5E5E5E",
+ "G g #868686",
+ "H g #AFAFAF",
+ "I g #C1C1C1",
+ "J g #818181",
+ "K g #7E7E7E",
+ "L g #7B7B7B",
+ "M g #636363",
+ " ",
+ " .+@@#$ ",
+ " .%&*=-;>,' ",
+ " .)!~={{]^-/( ",
+ " _::<{[}|{123 ",
+ " .456{7558{90ab ",
+ " +cde96df={&g,h ",
+ " ijjjjjk;{=@,,l ",
+ " mnnnnno{-pgggq ",
+ " #rprstuvwtttt' ",
+ " $tpppp6xpppp@y ",
+ " mnnnzA~Bnnn. ",
+ " 'taaCD{Eaa,F ",
+ " (GjHI0HjJF ",
+ " (K,,LM ",
+ " "
+};
+
+CameraIconView::CameraIconView(CameraUI* ui, TQWidget* parent)
+ : IconView(parent)
+{
+ d = new CameraIconViewPriv;
+ d->cameraUI = ui;
+ d->groupItem = new IconGroupItem(this);
+
+ setHScrollBarMode(TQScrollView::AlwaysOff);
+ setMinimumSize(400, 300);
+
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+
+ // ----------------------------------------------------------------
+
+ connect(this, TQ_SIGNAL(signalSelectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+
+ connect(this, TQ_SIGNAL(signalNewSelection(bool)),
+ this, TQ_SLOT(slotUpdateDownloadNames(bool)));
+
+ connect(this, TQ_SIGNAL(signalRightButtonClicked(IconItem*, const TQPoint&)),
+ this, TQ_SLOT(slotContextMenu(IconItem*, const TQPoint&)));
+
+ connect(this, TQ_SIGNAL(signalRightButtonClicked(const TQPoint &)),
+ this, TQ_SLOT(slotRightButtonClicked(const TQPoint &)));
+
+ connect(this, TQ_SIGNAL(signalDoubleClicked(IconItem*)),
+ this, TQ_SLOT(slotDoubleClicked(IconItem*)));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ // ----------------------------------------------------------------
+
+ updateItemRectsPixmap();
+ slotThemeChanged();
+}
+
+CameraIconView::~CameraIconView()
+{
+ clear();
+ delete d;
+}
+
+TQPixmap* CameraIconView::itemBaseRegPixmap() const
+{
+ return &d->itemRegPixmap;
+}
+
+TQPixmap* CameraIconView::itemBaseSelPixmap() const
+{
+ return &d->itemSelPixmap;
+}
+
+TQPixmap CameraIconView::newPicturePixmap() const
+{
+ return d->pixmapNewPicture;
+}
+
+TQPixmap CameraIconView::unknowPicturePixmap() const
+{
+ return d->pixmapUnknowPicture;
+}
+
+void CameraIconView::setRenameCustomizer(RenameCustomizer* renamer)
+{
+ d->renamer = renamer;
+
+ connect(d->renamer, TQ_SIGNAL(signalChanged()),
+ this, TQ_SLOT(slotDownloadNameChanged()));
+}
+
+void CameraIconView::addItem(const GPItemInfo& info)
+{
+ TQImage thumb;
+ // Just to have a generic image thumb from desktop with KDE < 3.5.0
+ KMimeType::Ptr mime = KMimeType::mimeType(info.mime == TQString("image/x-raw") ?
+ TQString("image/tiff") : info.mime);
+
+ if (mime)
+ {
+ thumb = TQImage(mime->pixmap(TDEIcon::Desktop, ThumbnailSize::Huge, TDEIcon::DefaultState)
+ .convertToImage());
+ }
+ else
+ {
+ TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
+ thumb = iconLoader->loadIcon("application-x-zerosize", TDEIcon::Desktop,
+ ThumbnailSize::Huge, TDEIcon::DefaultState, 0, true)
+ .convertToImage();
+ }
+
+ TQString downloadName;
+
+ if (d->renamer)
+ {
+ if (!d->renamer->useDefault())
+ {
+ downloadName = getTemplatedName( &info, d->itemDict.count() );
+ }
+ else
+ {
+ downloadName = getCasedName( d->renamer->changeCase(), &info);
+ }
+ }
+
+ CameraIconViewItem* item = new CameraIconViewItem(d->groupItem, info, thumb, downloadName);
+ d->itemDict.insert(info.folder+info.name, item);
+}
+
+void CameraIconView::removeItem(const TQString& folder, const TQString& file)
+{
+ CameraIconViewItem* item = d->itemDict.find(folder+file);
+ if (!item)
+ return;
+ d->itemDict.remove(folder+file);
+
+ setDelayedRearrangement(true);
+ delete item;
+ setDelayedRearrangement(false);
+}
+
+CameraIconViewItem* CameraIconView::findItem(const TQString& folder, const TQString& file)
+{
+ return d->itemDict.find(folder+file);
+}
+
+int CameraIconView::countItemsByFolder(TQString folder)
+{
+ int count = 0;
+ if (folder.endsWith("/")) folder.truncate(folder.length()-1);
+
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ CameraIconViewItem* iconItem = static_cast<CameraIconViewItem*>(item);
+ TQString itemFolder = iconItem->itemInfo()->folder;
+ if (itemFolder.endsWith("/")) itemFolder.truncate(itemFolder.length()-1);
+
+ if (folder == itemFolder)
+ count++;
+ }
+
+ return count;
+}
+
+void CameraIconView::setThumbnail(const TQString& folder, const TQString& filename, const TQImage& image)
+{
+ CameraIconViewItem* item = d->itemDict.find(folder+filename);
+ if (!item)
+ return;
+
+ item->setThumbnail(image);
+ item->repaint();
+}
+
+void CameraIconView::ensureItemVisible(CameraIconViewItem *item)
+{
+ IconView::ensureItemVisible(item);
+}
+
+void CameraIconView::ensureItemVisible(const GPItemInfo& itemInfo)
+{
+ ensureItemVisible(itemInfo.folder, itemInfo.name);
+}
+
+void CameraIconView::ensureItemVisible(const TQString& folder, const TQString& file)
+{
+ CameraIconViewItem* item = d->itemDict.find(folder+file);
+ if (!item)
+ return;
+
+ ensureItemVisible(item);
+}
+
+void CameraIconView::slotDownloadNameChanged()
+{
+ bool hasSelection = false;
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ if (item->isSelected())
+ {
+ hasSelection = true;
+ break;
+ }
+ }
+
+ // connected to slotUpdateDownloadNames, and used externally
+ emit signalNewSelection(hasSelection);
+}
+
+void CameraIconView::slotUpdateDownloadNames(bool hasSelection)
+{
+ bool useDefault = true;
+ int startIndex = 0;
+
+ if (d->renamer)
+ {
+ useDefault = d->renamer->useDefault();
+ startIndex = d->renamer->startIndex() -1;
+ }
+
+ bool convertLossLessJpeg = d->cameraUI->convertLosslessJpegFiles();
+ TQString losslessFormat = d->cameraUI->losslessFormat();
+
+ viewport()->setUpdatesEnabled(false);
+
+ // NOTE: see B.K.O #182352: ordering of item count must be adapted sort of icon view,
+ // since items are ordered from the most rescent to the older one.
+
+ if (hasSelection)
+ {
+ // Camera items selection.
+
+ for (IconItem* item = lastItem(); item; item = item->prevItem())
+ {
+ TQString downloadName;
+ CameraIconViewItem* viewItem = static_cast<CameraIconViewItem*>(item);
+
+ if (item->isSelected())
+ {
+ if (!useDefault)
+ downloadName = getTemplatedName( viewItem->itemInfo(), startIndex );
+ else
+ downloadName = getCasedName( d->renamer->changeCase(), viewItem->itemInfo() );
+
+ startIndex++;
+ }
+
+ if (convertLossLessJpeg && !downloadName.isEmpty())
+ {
+ TQFileInfo fi(downloadName);
+ TQString ext = fi.extension(false).upper();
+ if (ext == TQString("JPEG") || ext == TQString("JPG") || ext == TQString("JPE"))
+ {
+ downloadName.truncate(downloadName.length() - ext.length());
+ downloadName.append(losslessFormat.lower());
+ }
+ }
+
+ viewItem->setDownloadName( downloadName );
+ }
+ }
+ else
+ {
+ // No camera item selection.
+
+ for (IconItem* item = lastItem(); item; item = item->prevItem())
+ {
+ TQString downloadName;
+ CameraIconViewItem* viewItem = static_cast<CameraIconViewItem*>(item);
+
+ if (!useDefault)
+ downloadName = getTemplatedName( viewItem->itemInfo(), startIndex );
+ else
+ downloadName = getCasedName( d->renamer->changeCase(), viewItem->itemInfo() );
+
+ if (convertLossLessJpeg)
+ {
+ TQFileInfo fi(downloadName);
+ TQString ext = fi.extension(false).upper();
+ if (ext == TQString("JPEG") || ext == TQString("JPG") || ext == TQString("JPE"))
+ {
+ downloadName.truncate(downloadName.length() - ext.length());
+ downloadName.append(losslessFormat.lower());
+ }
+ }
+
+ viewItem->setDownloadName( downloadName );
+ startIndex++;
+ }
+ }
+
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+}
+
+TQString CameraIconView::defaultDownloadName(CameraIconViewItem *viewItem)
+{
+ RenameCustomizer::Case renamecase = RenameCustomizer::NONE;
+ if (d->renamer)
+ renamecase = d->renamer->changeCase();
+
+ return getCasedName( renamecase, viewItem->itemInfo() );
+}
+
+TQString CameraIconView::getTemplatedName(const GPItemInfo* itemInfo, int position)
+{
+ TQString ext = itemInfo->name;
+ int pos = ext.findRev('.');
+ if (pos < 0)
+ ext = "";
+ else
+ ext = ext.right( ext.length() - pos );
+
+ TQDateTime mtime;
+ mtime.setTime_t(itemInfo->mtime);
+
+ return d->renamer->newName(mtime, position+1, ext);
+}
+
+TQString CameraIconView::getCasedName(const RenameCustomizer::Case ccase,
+ const GPItemInfo* itemInfo)
+{
+ TQString dname;
+
+ switch (ccase)
+ {
+ case(RenameCustomizer::UPPER):
+ {
+ dname = itemInfo->name.upper();
+ break;
+ }
+ case(RenameCustomizer::LOWER):
+ {
+ dname = itemInfo->name.lower();
+ break;
+ }
+ default:
+ {
+ dname = itemInfo->name;
+ break;
+ }
+ };
+
+ return dname;
+}
+
+void CameraIconView::slotSelectionChanged()
+{
+ bool selected = false;
+ CameraIconViewItem* camItem = 0;
+
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ if (item->isSelected())
+ {
+ camItem = static_cast<CameraIconViewItem*>(item);
+ selected = true;
+ break;
+ }
+ }
+
+ emit signalNewSelection(selected);
+ emit signalSelected(camItem, selected);
+
+ viewport()->update();
+}
+
+void CameraIconView::slotContextMenu(IconItem * item, const TQPoint&)
+{
+ if (!item)
+ return;
+
+ // don't popup context menu if the camera is busy
+ if (d->cameraUI->isBusy())
+ return;
+
+ CameraIconViewItem* camItem = static_cast<CameraIconViewItem*>(item);
+
+ DPopupMenu menu(this);
+ menu.insertItem(SmallIcon("editimage"), i18n("&View"), 0);
+ menu.insertSeparator(-1);
+ menu.insertItem(SmallIcon("go-down"),i18n("Download"), 1);
+ menu.insertItem(SmallIcon("go-down"),i18n("Download && Delete"), 4);
+ menu.insertItem(SmallIcon("encrypted"), i18n("Toggle lock"), 3);
+ menu.insertSeparator(-1);
+ menu.insertItem(SmallIcon("edit-delete"), i18n("Delete"), 2);
+
+ int result = menu.exec(TQCursor::pos());
+
+ switch (result)
+ {
+ case(0):
+ {
+ emit signalFileView(camItem);
+ break;
+ }
+ case(1):
+ {
+ emit signalDownload();
+ break;
+ }
+ case(2):
+ {
+ emit signalDelete();
+ break;
+ }
+ case(3):
+ {
+ emit signalToggleLock();
+ break;
+ }
+ case(4):
+ {
+ emit signalDownloadAndDelete();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void CameraIconView::slotDoubleClicked(IconItem* item)
+{
+ if (!item)
+ return;
+
+ if (d->cameraUI->isBusy())
+ return;
+
+ emit signalFileView(static_cast<CameraIconViewItem*>(item));
+}
+
+void CameraIconView::slotSelectAll()
+{
+ selectAll();
+}
+
+void CameraIconView::slotSelectNone()
+{
+ clearSelection();
+}
+
+void CameraIconView::slotSelectInvert()
+{
+ invertSelection();
+}
+
+void CameraIconView::slotSelectNew()
+{
+ blockSignals(true);
+ clearSelection();
+
+ for (IconItem* item = firstItem(); item;
+ item = item->nextItem())
+ {
+ CameraIconViewItem* viewItem = static_cast<CameraIconViewItem*>(item);
+ if (viewItem->itemInfo()->downloaded == GPItemInfo::NewPicture)
+ {
+ viewItem->setSelected(true, false);
+ }
+ }
+
+ blockSignals(false);
+ emit signalSelectionChanged();
+}
+
+void CameraIconView::startDrag()
+{
+ TQStringList lst;
+
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ if (!item->isSelected())
+ continue;
+
+ CameraIconViewItem* iconItem = static_cast<CameraIconViewItem*>(item);
+ TQString itemPath = iconItem->itemInfo()->folder + iconItem->itemInfo()->name;
+ lst.append(itemPath);
+ }
+
+ TQDragObject * drag = new CameraItemListDrag(lst, d->cameraUI);
+ if (drag)
+ {
+ TQPixmap icon(DesktopIcon("image-x-generic", 48));
+ int w = icon.width();
+ int h = icon.height();
+
+ TQPixmap pix(w+4,h+4);
+ TQString text(TQString::number(lst.count()));
+
+ TQPainter p(&pix);
+ p.fillRect(0, 0, w+4, h+4, TQColor(TQt::white));
+ p.setPen(TQPen(TQt::black, 1));
+ p.drawRect(0, 0, w+4, h+4);
+ p.drawPixmap(2, 2, icon);
+ TQRect r = p.boundingRect(2,2,w,h,TQt::AlignLeft|TQt::AlignTop,text);
+ r.setWidth(TQMAX(r.width(),r.height()));
+ r.setHeight(TQMAX(r.width(),r.height()));
+ p.fillRect(r, TQColor(0,80,0));
+ p.setPen(TQt::white);
+ TQFont f(font());
+ f.setBold(true);
+ p.setFont(f);
+ p.drawText(r, TQt::AlignCenter, text);
+ p.end();
+
+ drag->setPixmap(pix);
+ drag->drag();
+ }
+}
+
+void CameraIconView::contentsDropEvent(TQDropEvent *event)
+{
+ // don't popup context menu if the camera is busy
+ if (d->cameraUI->isBusy())
+ return;
+
+ if ( (!TQUriDrag::canDecode(event) && !CameraDragObject::canDecode(event) )
+ || event->source() == this)
+ {
+ event->ignore();
+ return;
+ }
+
+ KURL::List srcURLs;
+ KURLDrag::decode(event, srcURLs);
+ uploadItemPopupMenu(srcURLs);
+}
+
+void CameraIconView::slotRightButtonClicked(const TQPoint&)
+{
+ // don't popup context menu if the camera is busy
+ if (d->cameraUI->isBusy())
+ return;
+
+ TQMimeSource *data = kapp->clipboard()->data(TQClipboard::Clipboard);
+ if(!data || !TQUriDrag::canDecode(data))
+ return;
+
+ KURL::List srcURLs;
+ KURLDrag::decode(data, srcURLs);
+ uploadItemPopupMenu(srcURLs);
+}
+
+void CameraIconView::uploadItemPopupMenu(const KURL::List& srcURLs)
+{
+ TDEPopupMenu popMenu(this);
+ popMenu.insertTitle(SmallIcon("digikam"), d->cameraUI->cameraTitle());
+ popMenu.insertItem( SmallIcon("goto"), i18n("&Upload to camera"), 10 );
+ popMenu.insertSeparator(-1);
+ popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") );
+
+ popMenu.setMouseTracking(true);
+ int id = popMenu.exec(TQCursor::pos());
+ switch(id)
+ {
+ case 10:
+ {
+ emit signalUpload(srcURLs);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+TQRect CameraIconView::itemRect() const
+{
+ return d->itemRect;
+}
+
+void CameraIconView::setThumbnailSize(const ThumbnailSize& thumbSize)
+{
+ if ( d->thumbSize != thumbSize)
+ {
+ d->thumbSize = thumbSize;
+ updateItemRectsPixmap();
+ triggerRearrangement();
+ }
+}
+
+ThumbnailSize CameraIconView::thumbnailSize() const
+{
+ return d->thumbSize;
+}
+
+void CameraIconView::updateItemRectsPixmap()
+{
+ int thumbSize = d->thumbSize.size();
+
+ TQRect pixRect;
+ TQRect textRect;
+ TQRect extraRect;
+
+ pixRect.setWidth(thumbSize);
+ pixRect.setHeight(thumbSize);
+
+ TQFontMetrics fm(font());
+ TQRect r = TQRect(fm.boundingRect(0, 0, thumbSize, 0xFFFFFFFF,
+ TQt::AlignHCenter | TQt::AlignTop,
+ "XXXXXXXXX"));
+ textRect.setWidth(r.width());
+ textRect.setHeight(r.height());
+
+ TQFont fn(font());
+ if (fn.pointSize() > 0)
+ {
+ fn.setPointSize(TQMAX(fn.pointSize()-2, 6));
+ }
+
+ fm = TQFontMetrics(fn);
+ r = TQRect(fm.boundingRect(0, 0, thumbSize, 0xFFFFFFFF,
+ TQt::AlignHCenter | TQt::AlignTop,
+ "XXXXXXXXX"));
+ extraRect.setWidth(r.width());
+ extraRect.setHeight(r.height());
+
+ r = TQRect();
+ r.setWidth(TQMAX(TQMAX(pixRect.width(), textRect.width()), extraRect.width()) + 4);
+ r.setHeight(pixRect.height() + textRect.height() + extraRect.height() + 4);
+
+ d->itemRect = r;
+
+ d->itemRegPixmap = ThemeEngine::instance()->thumbRegPixmap(d->itemRect.width(),
+ d->itemRect.height());
+
+ d->itemSelPixmap = ThemeEngine::instance()->thumbSelPixmap(d->itemRect.width(),
+ d->itemRect.height());
+}
+
+void CameraIconView::slotThemeChanged()
+{
+ updateItemRectsPixmap();
+ viewport()->update();
+}
+
+int CameraIconView::itemsDownloaded()
+{
+ int downloaded = 0;
+
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ CameraIconViewItem* iconItem = static_cast<CameraIconViewItem*>(item);
+
+ if (iconItem->itemInfo()->downloaded == GPItemInfo::DownloadedYes)
+ downloaded++;
+ }
+
+ return downloaded;
+}
+
+void CameraIconView::itemsSelectionSizeInfo(unsigned long& fSizeKB, unsigned long& dSizeKB)
+{
+ long long fSize = 0; // Files size
+ long long dSize = 0; // Estimated space requires to download and process files.
+ for (IconItem* item = firstItem(); item; item = item->nextItem())
+ {
+ if (item->isSelected())
+ {
+ CameraIconViewItem* iconItem = static_cast<CameraIconViewItem*>(item);
+ long long size = iconItem->itemInfo()->size;
+ if (size < 0) // -1 if size is not provided by camera
+ continue;
+ fSize += size;
+
+ if (iconItem->itemInfo()->mime == TQString("image/jpeg"))
+ {
+ if (d->cameraUI->convertLosslessJpegFiles())
+ {
+ // Estimated size is aroud 5 x original size when JPEG=>PNG.
+ dSize += size*5;
+ }
+ else if (d->cameraUI->autoRotateJpegFiles())
+ {
+ // We need a double size to perform rotation.
+ dSize += size*2;
+ }
+ else
+ {
+ // Real file size is added.
+ dSize += size;
+ }
+ }
+ else
+ dSize += size;
+
+ }
+ }
+
+ fSizeKB = fSize / 1024;
+ dSizeKB = dSize / 1024;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/cameraiconview.h b/src/utilities/cameragui/cameraiconview.h
new file mode 100644
index 00000000..f621fedf
--- /dev/null
+++ b/src/utilities/cameragui/cameraiconview.h
@@ -0,0 +1,141 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-18
+ * Description : camera icon view
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAICONVIEW_H
+#define CAMERAICONVIEW_H
+
+// TQt includes.
+
+#include <tqdict.h>
+#include <tqrect.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "iconview.h"
+#include "renamecustomizer.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+
+class ThumbnailSize;
+class GPItemInfo;
+class RenameCustomizer;
+class CameraUI;
+class CameraIconViewItem;
+class CameraIconViewPriv;
+
+class CameraIconView : public IconView
+{
+ TQ_OBJECT
+
+
+public:
+
+ CameraIconView(CameraUI* ui, TQWidget* parent);
+ ~CameraIconView();
+
+ void setRenameCustomizer(RenameCustomizer* renamer);
+
+ void addItem(const GPItemInfo& itemInfo);
+ void removeItem(const TQString& folder, const TQString& file);
+ void setThumbnail(const TQString& folder, const TQString& filename, const TQImage& image);
+
+ void ensureItemVisible(CameraIconViewItem *item);
+ void ensureItemVisible(const GPItemInfo& itemInfo);
+ void ensureItemVisible(const TQString& folder, const TQString& file);
+
+ void setThumbnailSize(const ThumbnailSize& thumbSize);
+ ThumbnailSize thumbnailSize() const;
+
+ CameraIconViewItem* findItem(const TQString& folder, const TQString& file);
+
+ int countItemsByFolder(TQString folder);
+ int itemsDownloaded();
+
+ TQPixmap* itemBaseRegPixmap() const;
+ TQPixmap* itemBaseSelPixmap() const;
+ TQPixmap newPicturePixmap() const;
+ TQPixmap unknowPicturePixmap() const;
+
+ virtual TQRect itemRect() const;
+
+ TQString defaultDownloadName(CameraIconViewItem *item);
+
+ void itemsSelectionSizeInfo(unsigned long& fSize, unsigned long& dSize);
+
+signals:
+
+ void signalSelected(CameraIconViewItem*, bool);
+ void signalFileView(CameraIconViewItem*);
+
+ void signalUpload(const KURL::List&);
+ void signalDownload();
+ void signalDownloadAndDelete();
+ void signalDelete();
+ void signalToggleLock();
+ void signalNewSelection(bool);
+
+public slots:
+
+ void slotDownloadNameChanged();
+ void slotSelectionChanged();
+ void slotSelectAll();
+ void slotSelectNone();
+ void slotSelectInvert();
+ void slotSelectNew();
+
+private slots:
+
+ void slotRightButtonClicked(const TQPoint& pos);
+ void slotContextMenu(IconItem* item, const TQPoint& pos);
+ void slotDoubleClicked(IconItem* item);
+ void slotThemeChanged();
+ void slotUpdateDownloadNames(bool hasSelection);
+
+protected:
+
+ void startDrag();
+ void contentsDropEvent(TQDropEvent *event);
+ void updateItemRectsPixmap();
+
+private:
+
+ TQString getTemplatedName(const GPItemInfo* itemInfo, int position);
+ TQString getCasedName(const RenameCustomizer::Case ccase, const GPItemInfo* itemInfo);
+ void uploadItemPopupMenu(const KURL::List& srcURLs);
+
+private:
+
+ CameraIconViewPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* CAMERAICONVIEW_H */
diff --git a/src/utilities/cameragui/camerainfodialog.cpp b/src/utilities/cameragui/camerainfodialog.cpp
new file mode 100644
index 00000000..ba565a59
--- /dev/null
+++ b/src/utilities/cameragui/camerainfodialog.cpp
@@ -0,0 +1,85 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-28
+ * Description : a dialog to display camera information.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqtextedit.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "camerainfodialog.h"
+
+namespace Digikam
+{
+
+CameraInfoDialog::CameraInfoDialog(TQWidget *parent, const TQString& summary, const TQString& manual,
+ const TQString& about)
+ : KDialogBase(IconList, i18n("Camera Information"), Help|Ok, Ok, parent, 0, true, true)
+{
+ setHelp("digitalstillcamera.anchor", "digikam");
+ resize(500, 400);
+
+ // ----------------------------------------------------------
+
+ TQFrame *p1 = addPage( i18n("Summary"), i18n("Camera Summary"), BarIcon("contents2", TDEIcon::SizeMedium) );
+ TQVBoxLayout *p1layout = new TQVBoxLayout( p1, 0, 6 );
+
+ TQTextEdit *summaryView = new TQTextEdit(summary, TQString(), p1);
+ summaryView->setWordWrap(TQTextEdit::WidgetWidth);
+ summaryView->setReadOnly(true);
+ p1layout->addWidget(summaryView);
+
+ // ----------------------------------------------------------
+
+ TQFrame *p2 = addPage( i18n("Manual"), i18n("Camera Manual"), BarIcon("contents", TDEIcon::SizeMedium) );
+ TQVBoxLayout *p2layout = new TQVBoxLayout( p2, 0, 6 );
+
+ TQTextEdit *manualView = new TQTextEdit(manual, TQString(), p2);
+ manualView->setWordWrap(TQTextEdit::WidgetWidth);
+ manualView->setReadOnly(true);
+ p2layout->addWidget(manualView);
+
+ // ----------------------------------------------------------
+
+ TQFrame *p3 = addPage( i18n("About"), i18n("About Driver"), BarIcon("camera-photo", TDEIcon::SizeMedium) );
+ TQVBoxLayout *p3layout = new TQVBoxLayout( p3, 0, 6 );
+
+ TQTextEdit *aboutView = new TQTextEdit(about, TQString(), p3);
+ aboutView->setWordWrap(TQTextEdit::WidgetWidth);
+ aboutView->setReadOnly(true);
+ p3layout->addWidget(aboutView);
+}
+
+CameraInfoDialog::~CameraInfoDialog()
+{
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/camerainfodialog.h b/src/utilities/cameragui/camerainfodialog.h
new file mode 100644
index 00000000..7ec3120f
--- /dev/null
+++ b/src/utilities/cameragui/camerainfodialog.h
@@ -0,0 +1,50 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-28
+ * Description : a dialog to display camera information.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAINFODIALOG_H
+#define CAMERAINFODIALOG_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+namespace Digikam
+{
+
+class CameraInfoDialog : public KDialogBase
+{
+public:
+
+ CameraInfoDialog(TQWidget *parent, const TQString& summary, const TQString& manual,
+ const TQString& about);
+ ~CameraInfoDialog();
+};
+
+} // namespace Digikam
+
+#endif /* CAMERAINFODIALOG_H */
diff --git a/src/utilities/cameragui/cameraui.cpp b/src/utilities/cameragui/cameraui.cpp
new file mode 100644
index 00000000..8b531ea2
--- /dev/null
+++ b/src/utilities/cameragui/cameraui.cpp
@@ -0,0 +1,1734 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-16
+ * Description : Camera interface dialog
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define CAMERA_INFO_MENU_ID 255
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqlayout.h>
+#include <tqpushbutton.h>
+#include <tqtoolbutton.h>
+#include <tqiconview.h>
+#include <tqvbox.h>
+#include <tqhbox.h>
+#include <tqpopupmenu.h>
+#include <tqsplitter.h>
+#include <tqpixmap.h>
+#include <tqcombobox.h>
+#include <tqtoolbox.h>
+#include <tqframe.h>
+#include <tqvbuttongroup.h>
+#include <tqradiobutton.h>
+#include <tqcheckbox.h>
+#include <tqlineedit.h>
+#include <tqtooltip.h>
+#include <tqtimer.h>
+#include <tqwhatsthis.h>
+#include <tqfile.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <tdefiledialog.h>
+#include <kimageio.h>
+#include <tdeaboutdata.h>
+#include <tdemessagebox.h>
+#include <kprogress.h>
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+#include <khelpmenu.h>
+#include <kcalendarsystem.h>
+#include <kurllabel.h>
+#include <ksqueezedtextlabel.h>
+
+#if KDE_IS_VERSION(3,2,0)
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "daboutdata.h"
+#include "ddebug.h"
+#include "thumbnailsize.h"
+#include "kdatetimeedit.h"
+#include "sidebar.h"
+#include "scanlib.h"
+#include "downloadsettingscontainer.h"
+#include "imagepropertiessidebarcamgui.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "album.h"
+#include "albumselectdialog.h"
+#include "renamecustomizer.h"
+#include "animwidget.h"
+#include "freespacewidget.h"
+#include "camerafolderdialog.h"
+#include "camerainfodialog.h"
+#include "cameraiconview.h"
+#include "cameraiconitem.h"
+#include "cameracontroller.h"
+#include "cameralist.h"
+#include "cameratype.h"
+#include "cameraui.h"
+#include "cameraui.moc"
+
+namespace Digikam
+{
+
+class CameraUIPriv
+{
+public:
+
+ enum SettingsTab
+ {
+ RENAMEFILEPAGE=0,
+ AUTOALBUMPAGE,
+ ONFLYPAGE
+ };
+
+ enum DateFormatOptions
+ {
+ IsoDateFormat=0,
+ TextDateFormat,
+ LocalDateFormat
+ };
+
+ CameraUIPriv()
+ {
+ deleteAfter = false;
+ busy = false;
+ closed = false;
+ helpMenu = 0;
+ advBox = 0;
+ downloadMenu = 0;
+ deleteMenu = 0;
+ imageMenu = 0;
+ cancelBtn = 0;
+ splitter = 0;
+ rightSidebar = 0;
+ fixDateTimeCheck = 0;
+ autoRotateCheck = 0;
+ autoAlbumDateCheck = 0;
+ autoAlbumExtCheck = 0;
+ status = 0;
+ progress = 0;
+ controller = 0;
+ view = 0;
+ renameCustomizer = 0;
+ anim = 0;
+ dateTimeEdit = 0;
+ setPhotographerId = 0;
+ setCredits = 0;
+ losslessFormat = 0;
+ convertJpegCheck = 0;
+ formatLabel = 0;
+ folderDateLabel = 0;
+ folderDateFormat = 0;
+ freeSpaceWidget = 0;
+ }
+
+ bool deleteAfter;
+ bool busy;
+ bool closed;
+
+ TQString cameraTitle;
+
+ TQStringList currentlyDeleting;
+ TQStringList foldersToScan;
+ TQStringList cameraFolderList;
+
+ TQPopupMenu *downloadMenu;
+ TQPopupMenu *deleteMenu;
+ TQPopupMenu *imageMenu;
+
+ TQToolButton *cancelBtn;
+
+ TQToolBox *advBox;
+
+ TQCheckBox *autoRotateCheck;
+ TQCheckBox *autoAlbumDateCheck;
+ TQCheckBox *autoAlbumExtCheck;
+ TQCheckBox *fixDateTimeCheck;
+ TQCheckBox *setPhotographerId;
+ TQCheckBox *setCredits;
+ TQCheckBox *convertJpegCheck;
+
+ TQLabel *formatLabel;
+ TQLabel *folderDateLabel;
+
+ TQComboBox *losslessFormat;
+ TQComboBox *folderDateFormat;
+
+ TQSplitter *splitter;
+
+ TQDateTime lastAccess;
+
+ KProgress *progress;
+
+ KSqueezedTextLabel *status;
+
+ KURL lastDestURL;
+
+ KHelpMenu *helpMenu;
+
+ KDateTimeEdit *dateTimeEdit;
+
+ CameraController *controller;
+
+ CameraIconView *view;
+
+ RenameCustomizer *renameCustomizer;
+
+ AnimWidget *anim;
+
+ ImagePropertiesSideBarCamGui *rightSidebar;
+
+ FreeSpaceWidget *freeSpaceWidget;
+};
+
+CameraUI::CameraUI(TQWidget* /*parent*/, const TQString& cameraTitle,
+ const TQString& model, const TQString& port,
+ const TQString& path, const TQDateTime lastAccess)
+ : KDialogBase(Plain, cameraTitle,
+ Help|User1|User2|User3|Close, Close,
+ 0, // B.K.O # 116485: no parent for this modal dialog.
+ 0, false, true,
+ i18n("D&elete"),
+ i18n("&Download"),
+ i18n("&Images"))
+{
+ d = new CameraUIPriv;
+ d->lastAccess = lastAccess;
+ d->cameraTitle = cameraTitle;
+ setHelp("camerainterface.anchor", "digikam");
+
+ // -------------------------------------------------------------------------
+
+ TQGridLayout* viewBoxLayout = new TQGridLayout(plainPage(), 2, 7);
+
+ TQHBox* widget = new TQHBox(plainPage());
+ d->splitter = new TQSplitter(widget);
+ d->view = new CameraIconView(this, d->splitter);
+
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+ d->view->setSizePolicy(rightSzPolicy);
+
+ d->rightSidebar = new ImagePropertiesSideBarCamGui(widget, "CameraGui Sidebar Right", d->splitter,
+ Sidebar::Right, true);
+ d->splitter->setFrameStyle( TQFrame::NoFrame );
+ d->splitter->setFrameShadow( TQFrame::Plain );
+ d->splitter->setFrameShape( TQFrame::NoFrame );
+ d->splitter->setOpaqueResize(false);
+
+ // -------------------------------------------------------------------------
+
+ d->advBox = new TQToolBox(d->rightSidebar);
+ d->renameCustomizer = new RenameCustomizer(d->advBox, d->cameraTitle);
+ d->view->setRenameCustomizer(d->renameCustomizer);
+
+ TQWhatsThis::add( d->advBox, i18n("<p>Set how digiKam will rename files as they are downloaded."));
+
+ d->advBox->insertItem(CameraUIPriv::RENAMEFILEPAGE, d->renameCustomizer,
+ SmallIconSet("fileimport"), i18n("File Renaming Options"));
+
+ // -- Albums Auto-creation options -----------------------------------------
+
+ TQWidget* albumBox = new TQWidget(d->advBox);
+ TQVBoxLayout* albumVlay = new TQVBoxLayout(albumBox, marginHint(), spacingHint());
+ d->autoAlbumExtCheck = new TQCheckBox(i18n("Extension-based sub-albums"), albumBox);
+ d->autoAlbumDateCheck = new TQCheckBox(i18n("Date-based sub-albums"), albumBox);
+ TQHBox *hbox1 = new TQHBox(albumBox);
+ d->folderDateLabel = new TQLabel(i18n("Date format:"), hbox1);
+ d->folderDateFormat = new TQComboBox(hbox1);
+ d->folderDateFormat->insertItem(i18n("ISO"), CameraUIPriv::IsoDateFormat);
+ d->folderDateFormat->insertItem(i18n("Full Text"), CameraUIPriv::TextDateFormat);
+ d->folderDateFormat->insertItem(i18n("Local Settings"), CameraUIPriv::LocalDateFormat);
+ albumVlay->addWidget(d->autoAlbumExtCheck);
+ albumVlay->addWidget(d->autoAlbumDateCheck);
+ albumVlay->addWidget(hbox1);
+ albumVlay->addStretch();
+
+ TQWhatsThis::add( albumBox, i18n("<p>Set how digiKam creates albums automatically when downloading."));
+ TQWhatsThis::add( d->autoAlbumExtCheck, i18n("<p>Enable this option if you want to download your "
+ "pictures into automatically created file extension-based sub-albums of the destination "
+ "album. This way, you can separate JPEG and RAW files as they are downloaded from your camera."));
+ TQWhatsThis::add( d->autoAlbumDateCheck, i18n("<p>Enable this option if you want to "
+ "download your pictures into automatically created file date-based sub-albums "
+ "of the destination album."));
+ TQWhatsThis::add( d->folderDateFormat, i18n("<p>Select your preferred date format used to "
+ "create new albums. The options available are:<p>"
+ "<b>ISO</b>: the date format is in accordance with ISO 8601 "
+ "(YYYY-MM-DD). E.g.: <i>2006-08-24</i><p>"
+ "<b>Full Text</b>: the date format is in a user-readable string. "
+ "E.g.: <i>Thu Aug 24 2006</i><p>"
+ "<b>Local Settings</b>: the date format depending on TDE control panel settings.<p>"));
+
+ d->advBox->insertItem(CameraUIPriv::AUTOALBUMPAGE, albumBox, SmallIconSet("folder-new"),
+ i18n("Auto-creation of Albums"));
+
+ // -- On the Fly options ---------------------------------------------------
+
+ TQWidget* onFlyBox = new TQWidget(d->advBox);
+ TQVBoxLayout* onFlyVlay = new TQVBoxLayout(onFlyBox, marginHint(), spacingHint());
+ d->setPhotographerId = new TQCheckBox(i18n("Set default photographer identity"), onFlyBox);
+ d->setCredits = new TQCheckBox(i18n("Set default credit and copyright"), onFlyBox);
+ d->fixDateTimeCheck = new TQCheckBox(i18n("Fix internal date && time"), onFlyBox);
+ d->dateTimeEdit = new KDateTimeEdit(onFlyBox, "datepicker");
+ d->autoRotateCheck = new TQCheckBox(i18n("Auto-rotate/flip image"), onFlyBox);
+ d->convertJpegCheck = new TQCheckBox(i18n("Convert to lossless file format"), onFlyBox);
+ TQHBox *hbox2 = new TQHBox(onFlyBox);
+ d->formatLabel = new TQLabel(i18n("New image format:"), hbox2);
+ d->losslessFormat = new TQComboBox(hbox2);
+ d->losslessFormat->insertItem("PNG", 0);
+ onFlyVlay->addWidget(d->setPhotographerId);
+ onFlyVlay->addWidget(d->setCredits);
+ onFlyVlay->addWidget(d->fixDateTimeCheck);
+ onFlyVlay->addWidget(d->dateTimeEdit);
+ onFlyVlay->addWidget(d->autoRotateCheck);
+ onFlyVlay->addWidget(d->convertJpegCheck);
+ onFlyVlay->addWidget(hbox2);
+ onFlyVlay->addStretch();
+
+ TQWhatsThis::add( onFlyBox, i18n("<p>Set here all options to fix/transform JPEG files automatically "
+ "as they are downloaded."));
+ TQWhatsThis::add( d->autoRotateCheck, i18n("<p>Enable this option if you want images automatically "
+ "rotated or flipped using EXIF information provided by the camera."));
+ TQWhatsThis::add( d->setPhotographerId, i18n("<p>Enable this option to store the default "
+ "photographer identity in the IPTC tags using digiKam's metadata settings."));
+ TQWhatsThis::add( d->setCredits, i18n("<p>Enable this option to store the default credit "
+ "and copyright information in the IPTC tags using digiKam's metadata settings."));
+ TQWhatsThis::add( d->fixDateTimeCheck, i18n("<p>Enable this option to set date and time metadata "
+ "tags to the right values if your camera does not set "
+ "these tags correctly when pictures are taken. The values will "
+ "be saved in the DateTimeDigitized and DateTimeCreated EXIF/IPTC fields."));
+ TQWhatsThis::add( d->convertJpegCheck, i18n("<p>Enable this option to automatically convert "
+ "all JPEG files to a lossless image format. <b>Note:</b> Image conversion can take a "
+ "while on a slow computer."));
+ TQWhatsThis::add( d->losslessFormat, i18n("<p>Select your preferred lossless image file format to "
+ "convert to. <b>Note:</b> All metadata will be preserved during the conversion."));
+
+ d->advBox->insertItem(CameraUIPriv::ONFLYPAGE, onFlyBox, SmallIconSet("system-run"),
+ i18n("On the Fly Operations (JPEG only)"));
+
+ d->rightSidebar->appendTab(d->advBox, SmallIcon("configure"), i18n("Settings"));
+ d->rightSidebar->loadViewState();
+
+ // -------------------------------------------------------------------------
+
+ d->cancelBtn = new TQToolButton(plainPage());
+ d->cancelBtn->setSizePolicy( TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum ) );
+ d->cancelBtn->setPixmap( SmallIcon( "cancel" ) );
+ d->cancelBtn->setEnabled(false);
+
+ d->status = new KSqueezedTextLabel(plainPage());
+ d->progress = new KProgress(plainPage());
+ d->progress->setMaximumHeight( fontMetrics().height()+4 );
+ d->progress->hide();
+
+ TQWidget *frame = new TQWidget(plainPage());
+ TQHBoxLayout* layout = new TQHBoxLayout(frame);
+ frame->setSizePolicy(TQSizePolicy(TQSizePolicy::Minimum, TQSizePolicy::Minimum));
+
+ KURLLabel *pixmapLogo = new KURLLabel( Digikam::webProjectUrl(), TQString(), frame );
+ pixmapLogo->setMargin(0);
+ pixmapLogo->setScaledContents( false );
+ pixmapLogo->setSizePolicy(TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum));
+ TQToolTip::add(pixmapLogo, i18n("Visit digiKam project website"));
+ TDEGlobal::dirs()->addResourceType("logo-digikam", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-digikam", "logo-digikam.png");
+ pixmapLogo->setPixmap( TQPixmap( directory + "logo-digikam.png" ) );
+ pixmapLogo->setFocusPolicy(TQWidget::NoFocus);
+
+ d->anim = new AnimWidget(frame, pixmapLogo->height()-2);
+
+ layout->setMargin(0);
+ layout->setSpacing(0);
+ layout->addWidget( pixmapLogo );
+ layout->addWidget( d->anim );
+
+ d->freeSpaceWidget = new FreeSpaceWidget(plainPage(), 100);
+
+ viewBoxLayout->addMultiCellWidget(widget, 0, 0, 0, 7);
+ viewBoxLayout->addMultiCellWidget(d->cancelBtn, 2, 2, 0, 0);
+ viewBoxLayout->addMultiCellWidget(d->status, 2, 2, 2, 2);
+ viewBoxLayout->addMultiCellWidget(d->progress, 2, 2, 3, 3);
+ viewBoxLayout->addMultiCellWidget(d->freeSpaceWidget, 2, 2, 5, 5);
+ viewBoxLayout->addMultiCellWidget(frame, 2, 2, 7, 7);
+ viewBoxLayout->setRowSpacing(1, spacingHint());
+ viewBoxLayout->setColSpacing(1, spacingHint());
+ viewBoxLayout->setColSpacing(4, spacingHint());
+ viewBoxLayout->setColSpacing(6, spacingHint());
+ viewBoxLayout->setColStretch( 0, 0 );
+ viewBoxLayout->setColStretch( 1, 0 );
+ viewBoxLayout->setColStretch( 2, 3 );
+ viewBoxLayout->setColStretch( 3, 1 );
+ viewBoxLayout->setColStretch( 4, 0 );
+ viewBoxLayout->setColStretch( 5, 0 );
+ viewBoxLayout->setColStretch( 6, 0 );
+ viewBoxLayout->setColStretch( 7, 0 );
+
+ // -------------------------------------------------------------------------
+
+ d->imageMenu = new TQPopupMenu(this);
+ d->imageMenu->insertItem(i18n("Select &All"), d->view, TQ_SLOT(slotSelectAll()), CTRL+Key_A, 0);
+ d->imageMenu->insertItem(i18n("Select N&one"), d->view, TQ_SLOT(slotSelectNone()), CTRL+Key_U, 1);
+ d->imageMenu->insertItem(i18n("&Invert Selection"), d->view, TQ_SLOT(slotSelectInvert()), CTRL+Key_Asterisk, 2);
+ d->imageMenu->insertSeparator();
+ d->imageMenu->insertItem(i18n("Select &New Items"), d->view, TQ_SLOT(slotSelectNew()), 0, 3);
+ d->imageMenu->insertSeparator();
+ d->imageMenu->insertItem(i18n("Increase Thumbnail Size"), this, TQ_SLOT(slotIncreaseThumbSize()), CTRL+Key_Plus, 4);
+ d->imageMenu->insertItem(i18n("Decrease Thumbnail Size"), this, TQ_SLOT(slotDecreaseThumbSize()), CTRL+Key_Minus, 5);
+ d->imageMenu->insertSeparator();
+ d->imageMenu->insertItem(i18n("Toggle Lock"), this, TQ_SLOT(slotToggleLock()), 0, 6);
+ actionButton(User3)->setPopup(d->imageMenu);
+
+ // -------------------------------------------------------------------------
+
+ d->downloadMenu = new TQPopupMenu(this);
+ d->downloadMenu->insertItem(i18n("Download Selected"),
+ this, TQ_SLOT(slotDownloadSelected()), 0, 0);
+ d->downloadMenu->insertItem(i18n("Download All"),
+ this, TQ_SLOT(slotDownloadAll()), 0, 1);
+ d->downloadMenu->insertSeparator();
+ d->downloadMenu->insertItem(i18n("Download/Delete Selected"),
+ this, TQ_SLOT(slotDownloadAndDeleteSelected()), 0, 2);
+ d->downloadMenu->insertItem(i18n("Download/Delete All"),
+ this, TQ_SLOT(slotDownloadAndDeleteAll()), 0, 3);
+ d->downloadMenu->insertSeparator();
+ d->downloadMenu->insertItem(i18n("Upload..."),
+ this, TQ_SLOT(slotUpload()), 0, 4);
+ d->downloadMenu->setItemEnabled(0, false);
+ d->downloadMenu->setItemEnabled(2, false);
+ actionButton(User2)->setPopup(d->downloadMenu);
+
+ // -------------------------------------------------------------------------
+
+ d->deleteMenu = new TQPopupMenu(this);
+ d->deleteMenu->insertItem(i18n("Delete Selected"), this, TQ_SLOT(slotDeleteSelected()), 0, 0);
+ d->deleteMenu->insertItem(i18n("Delete All"), this, TQ_SLOT(slotDeleteAll()), 0, 1);
+ d->deleteMenu->setItemEnabled(0, false);
+ actionButton(User1)->setPopup(d->deleteMenu);
+
+ // -------------------------------------------------------------------------
+
+ TQPushButton *helpButton = actionButton( Help );
+ d->helpMenu = new KHelpMenu(this, kapp->aboutData(), false);
+ d->helpMenu->menu()->insertItem(SmallIcon("camera-photo"), i18n("Camera Information"),
+ this, TQ_SLOT(slotInformations()), 0, CAMERA_INFO_MENU_ID, 0);
+ helpButton->setPopup( d->helpMenu->menu() );
+
+ // -------------------------------------------------------------------------
+
+ connect(d->autoAlbumDateCheck, TQ_SIGNAL(toggled(bool)),
+ d->folderDateFormat, TQ_SLOT(setEnabled(bool)));
+
+ connect(d->autoAlbumDateCheck, TQ_SIGNAL(toggled(bool)),
+ d->folderDateLabel, TQ_SLOT(setEnabled(bool)));
+
+ connect(d->convertJpegCheck, TQ_SIGNAL(toggled(bool)),
+ d->losslessFormat, TQ_SLOT(setEnabled(bool)));
+
+ connect(d->convertJpegCheck, TQ_SIGNAL(toggled(bool)),
+ d->formatLabel, TQ_SLOT(setEnabled(bool)));
+
+ connect(d->convertJpegCheck, TQ_SIGNAL(toggled(bool)),
+ d->view, TQ_SLOT(slotDownloadNameChanged()));
+
+ connect(d->fixDateTimeCheck, TQ_SIGNAL(toggled(bool)),
+ d->dateTimeEdit, TQ_SLOT(setEnabled(bool)));
+
+ connect(pixmapLogo, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(slotProcessURL(const TQString&)));
+
+ // -------------------------------------------------------------------------
+
+ connect(d->view, TQ_SIGNAL(signalSelected(CameraIconViewItem*, bool)),
+ this, TQ_SLOT(slotItemsSelected(CameraIconViewItem*, bool)));
+
+ connect(d->view, TQ_SIGNAL(signalFileView(CameraIconViewItem*)),
+ this, TQ_SLOT(slotFileView(CameraIconViewItem*)));
+
+ connect(d->view, TQ_SIGNAL(signalUpload(const KURL::List&)),
+ this, TQ_SLOT(slotUploadItems(const KURL::List&)));
+
+ connect(d->view, TQ_SIGNAL(signalDownload()),
+ this, TQ_SLOT(slotDownloadSelected()));
+
+ connect(d->view, TQ_SIGNAL(signalDownloadAndDelete()),
+ this, TQ_SLOT(slotDownloadAndDeleteSelected()));
+
+ connect(d->view, TQ_SIGNAL(signalDelete()),
+ this, TQ_SLOT(slotDeleteSelected()));
+
+ connect(d->view, TQ_SIGNAL(signalToggleLock()),
+ this, TQ_SLOT(slotToggleLock()));
+
+ connect(d->view, TQ_SIGNAL(signalNewSelection(bool)),
+ this, TQ_SLOT(slotNewSelection(bool)));
+
+ // -------------------------------------------------------------------------
+
+ connect(d->rightSidebar, TQ_SIGNAL(signalFirstItem()),
+ this, TQ_SLOT(slotFirstItem()));
+
+ connect(d->rightSidebar, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SLOT(slotNextItem()));
+
+ connect(d->rightSidebar, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SLOT(slotPrevItem()));
+
+ connect(d->rightSidebar, TQ_SIGNAL(signalLastItem()),
+ this, TQ_SLOT(slotLastItem()));
+
+ // -------------------------------------------------------------------------
+
+ connect(d->cancelBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotCancelButton()));
+
+ // -- Read settings & Check free space availability on album root path -----
+
+ readSettings();
+
+ // -- camera controller ----------------------------------------------------
+
+ d->controller = new CameraController(this, d->cameraTitle, model, port, path);
+
+ connect(d->controller, TQ_SIGNAL(signalConnected(bool)),
+ this, TQ_SLOT(slotConnected(bool)));
+
+ connect(d->controller, TQ_SIGNAL(signalInfoMsg(const TQString&)),
+ d->status, TQ_SLOT(setText(const TQString&)));
+
+ connect(d->controller, TQ_SIGNAL(signalErrorMsg(const TQString&)),
+ this, TQ_SLOT(slotErrorMsg(const TQString&)));
+
+ connect(d->controller, TQ_SIGNAL(signalCameraInformations(const TQString&, const TQString&, const TQString&)),
+ this, TQ_SLOT(slotCameraInformations(const TQString&, const TQString&, const TQString&)));
+
+ connect(d->controller, TQ_SIGNAL(signalBusy(bool)),
+ this, TQ_SLOT(slotBusy(bool)));
+
+ connect(d->controller, TQ_SIGNAL(signalFolderList(const TQStringList&)),
+ this, TQ_SLOT(slotFolderList(const TQStringList&)));
+
+ connect(d->controller, TQ_SIGNAL(signalFileList(const GPItemInfoList&)),
+ this, TQ_SLOT(slotFileList(const GPItemInfoList&)));
+
+ connect(d->controller, TQ_SIGNAL(signalThumbnail(const TQString&, const TQString&, const TQImage&)),
+ this, TQ_SLOT(slotThumbnail(const TQString&, const TQString&, const TQImage&)));
+
+ connect(d->controller, TQ_SIGNAL(signalDownloaded(const TQString&, const TQString&, int)),
+ this, TQ_SLOT(slotDownloaded(const TQString&, const TQString&, int)));
+
+ connect(d->controller, TQ_SIGNAL(signalSkipped(const TQString&, const TQString&)),
+ this, TQ_SLOT(slotSkipped(const TQString&, const TQString&)));
+
+ connect(d->controller, TQ_SIGNAL(signalDeleted(const TQString&, const TQString&, bool)),
+ this, TQ_SLOT(slotDeleted(const TQString&, const TQString&, bool)));
+
+ connect(d->controller, TQ_SIGNAL(signalLocked(const TQString&, const TQString&, bool)),
+ this, TQ_SLOT(slotLocked(const TQString&, const TQString&, bool)));
+
+ connect(d->controller, TQ_SIGNAL(signalExifFromFile(const TQString&, const TQString&)),
+ this, TQ_SLOT(slotExifFromFile(const TQString&, const TQString&)));
+
+ connect(d->controller, TQ_SIGNAL(signalExifData(const TQByteArray&)),
+ this, TQ_SLOT(slotExifFromData(const TQByteArray&)));
+
+ connect(d->controller, TQ_SIGNAL(signalUploaded(const GPItemInfo&)),
+ this, TQ_SLOT(slotUploaded(const GPItemInfo&)));
+
+ // -------------------------------------------------------------------------
+
+ d->view->setFocus();
+ TQTimer::singleShot(0, d->controller, TQ_SLOT(slotConnect()));
+}
+
+CameraUI::~CameraUI()
+{
+ delete d->rightSidebar;
+ delete d->controller;
+ delete d;
+}
+
+void CameraUI::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Camera Settings");
+ d->advBox->setCurrentIndex(config->readNumEntry("Settings Tab", CameraUIPriv::RENAMEFILEPAGE));
+ d->autoRotateCheck->setChecked(config->readBoolEntry("AutoRotate", true));
+ d->autoAlbumDateCheck->setChecked(config->readBoolEntry("AutoAlbumDate", false));
+ d->autoAlbumExtCheck->setChecked(config->readBoolEntry("AutoAlbumExt", false));
+ d->fixDateTimeCheck->setChecked(config->readBoolEntry("FixDateTime", false));
+ d->setPhotographerId->setChecked(config->readBoolEntry("SetPhotographerId", false));
+ d->setCredits->setChecked(config->readBoolEntry("SetCredits", false));
+ d->convertJpegCheck->setChecked(config->readBoolEntry("ConvertJpeg", false));
+ d->losslessFormat->setCurrentItem(config->readNumEntry("LossLessFormat", 0)); // PNG by default
+ d->folderDateFormat->setCurrentItem(config->readNumEntry("FolderDateFormat", CameraUIPriv::IsoDateFormat));
+
+ d->view->setThumbnailSize(ThumbnailSize((ThumbnailSize::Size)config->readNumEntry("ThumbnailSize",
+ ThumbnailSize::Large)));
+
+ if(config->hasKey("Splitter Sizes"))
+ d->splitter->setSizes(config->readIntListEntry("Splitter Sizes"));
+
+ d->dateTimeEdit->setEnabled(d->fixDateTimeCheck->isChecked());
+ d->losslessFormat->setEnabled(convertLosslessJpegFiles());
+ d->formatLabel->setEnabled(convertLosslessJpegFiles());
+ d->folderDateFormat->setEnabled(d->autoAlbumDateCheck->isChecked());
+ d->folderDateLabel->setEnabled(d->autoAlbumDateCheck->isChecked());
+
+ resize(configDialogSize("Camera Settings"));
+}
+
+void CameraUI::saveSettings()
+{
+ saveDialogSize("Camera Settings");
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("Camera Settings");
+ config->writeEntry("Settings Tab", d->advBox->currentIndex());
+ config->writeEntry("AutoRotate", d->autoRotateCheck->isChecked());
+ config->writeEntry("AutoAlbumDate", d->autoAlbumDateCheck->isChecked());
+ config->writeEntry("AutoAlbumExt", d->autoAlbumExtCheck->isChecked());
+ config->writeEntry("FixDateTime", d->fixDateTimeCheck->isChecked());
+ config->writeEntry("SetPhotographerId", d->setPhotographerId->isChecked());
+ config->writeEntry("SetCredits", d->setCredits->isChecked());
+ config->writeEntry("ConvertJpeg", convertLosslessJpegFiles());
+ config->writeEntry("LossLessFormat", d->losslessFormat->currentItem());
+ config->writeEntry("ThumbnailSize", d->view->thumbnailSize().size());
+ config->writeEntry("Splitter Sizes", d->splitter->sizes());
+ config->writeEntry("FolderDateFormat", d->folderDateFormat->currentItem());
+ config->sync();
+}
+
+void CameraUI::slotProcessURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+bool CameraUI::isBusy() const
+{
+ return d->busy;
+}
+
+bool CameraUI::isClosed() const
+{
+ return d->closed;
+}
+
+bool CameraUI::convertLosslessJpegFiles() const
+{
+ return d->convertJpegCheck->isChecked();
+}
+
+bool CameraUI::autoRotateJpegFiles() const
+{
+ return d->autoRotateCheck->isChecked();
+}
+
+TQString CameraUI::losslessFormat()
+{
+ return d->losslessFormat->currentText();
+}
+
+TQString CameraUI::cameraTitle() const
+{
+ return d->cameraTitle;
+}
+
+void CameraUI::slotCancelButton()
+{
+ d->status->setText(i18n("Cancelling current operation, please wait..."));
+ d->progress->hide();
+ TQTimer::singleShot(0, d->controller, TQ_SLOT(slotCancel()));
+ d->currentlyDeleting.clear();
+}
+
+void CameraUI::closeEvent(TQCloseEvent* e)
+{
+ if (dialogClosed())
+ e->accept();
+ else
+ e->ignore();
+}
+
+void CameraUI::slotClose()
+{
+ if (dialogClosed())
+ reject();
+}
+
+bool CameraUI::dialogClosed()
+{
+ if (d->closed)
+ return true;
+
+ if (isBusy())
+ {
+ if (KMessageBox::questionYesNo(this,
+ i18n("Do you want to close the dialog "
+ "and cancel the current operation?"))
+ == KMessageBox::No)
+ return false;
+ }
+
+ d->status->setText(i18n("Disconnecting from camera, please wait..."));
+ d->progress->hide();
+
+ if (isBusy())
+ {
+ d->controller->slotCancel();
+ // will be read in slotBusy later and finishDialog
+ // will be called only when everything is finished
+ d->closed = true;
+ }
+ else
+ {
+ d->closed = true;
+ finishDialog();
+ }
+
+ return true;
+}
+
+void CameraUI::finishDialog()
+{
+ // Look if an item have been downloaded to computer during camera gui session.
+ // If yes, update the lastAccess date property of camera in digiKam camera list.
+
+ if (d->view->itemsDownloaded() > 0)
+ {
+ CameraList* clist = CameraList::instance();
+ if (clist)
+ clist->changeCameraAccessTime(d->cameraTitle, TQDateTime::TQDateTime::currentDateTime());
+ }
+
+ // When a directory is created, a watch is put on it to spot new files
+ // but it can occur that the file is copied there before the watch is
+ // completely setup. That is why as an extra safeguard run scanlib
+ // over the folders we used. Bug: 119201
+
+ d->status->setText(i18n("Scanning for new files, please wait..."));
+ ScanLib sLib;
+ for (TQStringList::iterator it = d->foldersToScan.begin();
+ it != d->foldersToScan.end(); ++it)
+ {
+ //DDebug() << "Scanning " << (*it) << endl;
+ sLib.findMissingItems( (*it) );
+ }
+
+ // Never call finalScan after deleteLater() - ScanLib will call processEvent(),
+ // and the delete event may be executed!
+ deleteLater();
+
+ if(!d->lastDestURL.isEmpty())
+ emit signalLastDestination(d->lastDestURL);
+
+ saveSettings();
+}
+
+void CameraUI::slotBusy(bool val)
+{
+ if (!val)
+ {
+ if (!d->busy)
+ return;
+
+ d->busy = false;
+ d->cancelBtn->setEnabled(false);
+ d->view->viewport()->setEnabled(true);
+
+ d->advBox->setEnabled(true);
+ // B.K.O #127614: The Focus need to be restored in custom prefix widget.
+ //commenting this out again: If we do not disable, no need to restore focus
+ //d->renameCustomizer->restoreFocus();
+
+ enableButton(User3, true);
+ enableButton(User2, true);
+ enableButton(User1, true);
+ d->helpMenu->menu()->setItemEnabled(CAMERA_INFO_MENU_ID, true);
+
+ d->anim->stop();
+ d->status->setText(i18n("Ready"));
+ d->progress->hide();
+
+ // like WDestructiveClose, but after camera controller operation has safely finished
+ if (d->closed)
+ {
+ finishDialog();
+ }
+ }
+ else
+ {
+ if (d->busy)
+ return;
+
+ if (!d->anim->running())
+ d->anim->start();
+
+ d->busy = true;
+ d->cancelBtn->setEnabled(true);
+
+ // Has camera icon view item selection is used to control download post processing,
+ // all selection actions are disable when camera interface is busy.
+ d->view->viewport()->setEnabled(false);
+
+ // Settings tab is disabled in slotDownload, selectively when downloading
+ // Fast dis/enabling would create the impression of flicker, e.g. when retrieving EXIF from camera
+ //d->advBox->setEnabled(false);
+
+ enableButton(User3, false);
+ enableButton(User2, false);
+ enableButton(User1, false);
+ d->helpMenu->menu()->setItemEnabled(CAMERA_INFO_MENU_ID, false);
+ }
+}
+
+void CameraUI::slotIncreaseThumbSize()
+{
+ int thumbSize = d->view->thumbnailSize().size();
+ if (thumbSize >= ThumbnailSize::Huge) return;
+
+ thumbSize += ThumbnailSize::Step;
+
+ if (thumbSize >= ThumbnailSize::Huge)
+ {
+ d->imageMenu->setItemEnabled(4, false);
+ }
+ d->imageMenu->setItemEnabled(5, true);
+
+ d->view->setThumbnailSize(thumbSize);
+}
+
+void CameraUI::slotDecreaseThumbSize()
+{
+ int thumbSize = d->view->thumbnailSize().size();
+ if (thumbSize <= ThumbnailSize::Small) return;
+
+ thumbSize -= ThumbnailSize::Step;
+
+ if (thumbSize <= ThumbnailSize::Small)
+ {
+ d->imageMenu->setItemEnabled(5, false);
+ }
+ d->imageMenu->setItemEnabled(4, true);
+
+ d->view->setThumbnailSize(thumbSize);
+}
+
+void CameraUI::slotConnected(bool val)
+{
+ if (!val)
+ {
+ if (KMessageBox::warningYesNo(this,
+ i18n("Failed to connect to the camera. "
+ "Please make sure it is connected "
+ "properly and turned on. "
+ "Would you like to try again?"),
+ i18n("Connection Failed"),
+ i18n("Retry"),
+ i18n("Abort"))
+ == KMessageBox::Yes)
+ TQTimer::singleShot(0, d->controller, TQ_SLOT(slotConnect()));
+ else
+ close();
+ }
+ else
+ {
+ d->controller->listFolders();
+ }
+}
+
+void CameraUI::slotFolderList(const TQStringList& folderList)
+{
+ if (d->closed)
+ return;
+
+ d->progress->setProgress(0);
+ d->progress->setTotalSteps(0);
+ d->progress->show();
+
+ d->cameraFolderList = folderList;
+ for (TQStringList::const_iterator it = folderList.begin();
+ it != folderList.end(); ++it)
+ {
+ d->controller->listFiles(*it);
+ }
+}
+
+void CameraUI::slotFileList(const GPItemInfoList& fileList)
+{
+ if (d->closed)
+ return;
+
+ if (fileList.empty())
+ return;
+
+ kdDebug() << fileList.count() << endl;
+
+ // We sort the map by time stamp
+ // and we remove internal camera files which are not image/video/sounds.
+ TQStringList fileNames, fileExts;
+ TQFileInfo info;
+
+ // JVC camera (see B.K.O #133185).
+ fileNames.append("mgr_data");
+ fileNames.append("pgr_mgr");
+
+ // HP Photosmart camera (see B.K.O #156338).
+ fileExts.append("dsp");
+
+ // Minolta camera in PTP mode
+ fileExts.append("dps");
+
+ // We sort the map by time stamp.
+ GPItemInfoList sfileList;
+ GPItemInfoList::const_iterator it;
+ GPItemInfoList::iterator it2;
+
+ for(it = fileList.begin() ; it != fileList.end() ; ++it)
+ {
+ info.setFile((*it).name);
+ if (!fileNames.contains(info.fileName().lower()) &&
+ !fileExts.contains(info.extension(false).lower()))
+ {
+ kdDebug() << info.fileName() << " : " << (*it).mtime << endl;
+
+ for(it2 = sfileList.begin() ; it2 != sfileList.end() ; ++it2)
+ if ((*it2).mtime <= (*it).mtime) break;
+
+ sfileList.insert(it2, *it);
+ }
+ }
+
+ if (sfileList.empty())
+ return;
+
+ kdDebug() << sfileList.count() << endl;
+
+ GPItemInfoList::const_iterator it3 = sfileList.begin();
+
+ do
+ {
+ GPItemInfo item = *it3;
+
+ if (item.mtime > (time_t)d->lastAccess.toTime_t() && item.downloaded == GPItemInfo::DownloadUnknow)
+ item.downloaded = GPItemInfo::NewPicture;
+
+ d->view->addItem(item);
+ d->controller->getThumbnail(item.folder, item.name);
+ ++it3;
+ }
+ while(it3 != sfileList.end());
+
+ d->progress->setTotalSteps(d->progress->totalSteps() + fileList.count());
+}
+
+void CameraUI::slotThumbnail(const TQString& folder, const TQString& file,
+ const TQImage& thumbnail)
+{
+ d->view->setThumbnail(folder, file, thumbnail);
+ int curr = d->progress->progress();
+ d->progress->setProgress(curr+1);
+}
+
+void CameraUI::slotInformations()
+{
+ if (d->busy)
+ return;
+
+ d->controller->getCameraInformations();
+}
+
+void CameraUI::slotCameraInformations(const TQString& summary, const TQString& manual, const TQString& about)
+{
+ CameraInfoDialog *infoDlg = new CameraInfoDialog(this, summary, manual, about);
+ infoDlg->show();
+}
+
+void CameraUI::slotErrorMsg(const TQString& msg)
+{
+ KMessageBox::error(this, msg);
+}
+
+void CameraUI::slotUpload()
+{
+ if (d->busy)
+ return;
+
+ TQString fileformats;
+
+ TQStringList patternList = TQStringList::split('\n', KImageIO::pattern(KImageIO::Reading));
+
+ // All Images from list must been always the first entry given by KDE API
+ TQString allPictures = patternList[0];
+
+ // Add RAW file format to All Images" type mime and remplace current.
+#if KDCRAW_VERSION < 0x000106
+ allPictures.insert(allPictures.find("|"), TQString(KDcrawIface::DcrawBinary::instance()->rawFiles()));
+#else
+ allPictures.insert(allPictures.find("|"), TQString(KDcrawIface::KDcraw::rawFiles()));
+#endif
+ patternList.remove(patternList[0]);
+ patternList.prepend(allPictures);
+
+ // Added RAW file formats supported by dcraw program like a type mime.
+ // Nota: we cannot use here "image/x-raw" type mime from KDE because it uncomplete
+ // or unavailable(dcraw_0)(see file #121242 in B.K.O).
+#if KDCRAW_VERSION < 0x000106
+ patternList.append(TQString("\n%1|Camera RAW files").arg(TQString(KDcrawIface::DcrawBinary::instance()->rawFiles())));
+#else
+ patternList.append(TQString("\n%1|Camera RAW files").arg(TQString(KDcrawIface::KDcraw::rawFiles())));
+#endif
+
+ fileformats = patternList.join("\n");
+
+ DDebug () << "fileformats=" << fileformats << endl;
+
+ KURL::List urls = KFileDialog::getOpenURLs(AlbumManager::instance()->getLibraryPath(),
+ fileformats, this, i18n("Select Image to Upload"));
+ if (!urls.isEmpty())
+ slotUploadItems(urls);
+}
+
+void CameraUI::slotUploadItems(const KURL::List& urls)
+{
+ if (d->busy)
+ return;
+
+ if (urls.isEmpty())
+ return;
+
+ CameraFolderDialog dlg(this, d->view, d->cameraFolderList, d->controller->getCameraTitle(),
+ d->controller->getCameraPath());
+
+ if (dlg.exec() != TQDialog::Accepted)
+ return;
+
+ TQString cameraFolder = dlg.selectedFolderPath();
+
+ for (KURL::List::const_iterator it = urls.begin() ; it != urls.end() ; ++it)
+ {
+ TQFileInfo fi((*it).path());
+ if (!fi.exists()) continue;
+ if (fi.isDir()) continue;
+
+ TQString ext = TQString(".") + fi.extension();
+ TQString name = fi.fileName();
+ name.truncate(fi.fileName().length() - ext.length());
+
+ bool ok;
+
+ while (d->view->findItem(cameraFolder, name + ext))
+ {
+ TQString msg(i18n("Camera Folder <b>%1</b> already contains item <b>%2</b><br>"
+ "Please enter a new file name (without extension):")
+ .arg(cameraFolder).arg(fi.fileName()));
+#if KDE_IS_VERSION(3,2,0)
+ name = KInputDialog::getText(i18n("File already exists"), msg, name, &ok, this);
+
+#else
+ name = KLineEditDlg::getText(i18n("File already exists"), msg, name, &ok, this);
+#endif
+ if (!ok)
+ return;
+ }
+
+ d->controller->upload(fi, name + ext, cameraFolder);
+ }
+}
+
+void CameraUI::slotUploaded(const GPItemInfo& itemInfo)
+{
+ if (d->closed)
+ return;
+
+ d->view->addItem(itemInfo);
+ d->controller->getThumbnail(itemInfo.folder, itemInfo.name);
+}
+
+void CameraUI::slotDownloadSelected()
+{
+ slotDownload(true, false);
+}
+
+void CameraUI::slotDownloadAndDeleteSelected()
+{
+ slotDownload(true, true);
+}
+
+void CameraUI::slotDownloadAll()
+{
+ slotDownload(false, false);
+}
+
+void CameraUI::slotDownloadAndDeleteAll()
+{
+ slotDownload(false, true);
+}
+
+void CameraUI::slotDownload(bool onlySelected, bool deleteAfter, Album *album)
+{
+ // See B.K.O #143934: force to select all items to prevent problem
+ // when !renameCustomizer->useDefault() ==> iconItem->getDownloadName()
+ // can return an empty string in this case because it depends on selection.
+ if (!onlySelected)
+ d->view->slotSelectAll();
+
+ // See B.K.O #139519: Always check free space available before to
+ // download items selection from camera.
+ unsigned long fSize = 0;
+ unsigned long dSize = 0;
+ d->view->itemsSelectionSizeInfo(fSize, dSize);
+ if (d->freeSpaceWidget->isValid() && (dSize >= d->freeSpaceWidget->kBAvail()))
+ {
+ KMessageBox::error(this, i18n("There is no enough free space on Album Library Path "
+ "to download and process selected pictures from camera.\n\n"
+ "Estimated space require: %1\n"
+ "Available free space: %2")
+ .arg(TDEIO::convertSizeFromKB(dSize))
+ .arg(TDEIO::convertSizeFromKB(d->freeSpaceWidget->kBAvail())));
+ return;
+ }
+
+ TQString newDirName;
+ IconItem* firstItem = d->view->firstItem();
+ if (firstItem)
+ {
+ CameraIconViewItem* iconItem = static_cast<CameraIconViewItem*>(firstItem);
+
+ TQDateTime dateTime;
+ dateTime.setTime_t(iconItem->itemInfo()->mtime);
+
+ switch(d->folderDateFormat->currentItem())
+ {
+ case CameraUIPriv::TextDateFormat:
+ newDirName = dateTime.date().toString(TQt::TextDate);
+ break;
+ case CameraUIPriv::LocalDateFormat:
+ newDirName = dateTime.date().toString(TQt::LocalDate);
+ break;
+ default: // IsoDateFormat
+ newDirName = dateTime.date().toString(TQt::ISODate);
+ break;
+ }
+ }
+
+ // -- Get the destination album from digiKam library if necessary ---------------
+
+ if (!album)
+ {
+ AlbumManager* man = AlbumManager::instance();
+ album = man->currentAlbum();
+
+ if (album && album->type() != Album::PHYSICAL)
+ album = 0;
+
+ TQString header(i18n("<p>Please select the destination album from the digiKam library to "
+ "import the camera pictures into.</p>"));
+
+ album = AlbumSelectDialog::selectAlbum(this, (PAlbum*)album, header, newDirName,
+ d->autoAlbumDateCheck->isChecked());
+
+ if (!album) return;
+ }
+
+ PAlbum *pAlbum = dynamic_cast<PAlbum*>(album);
+ if (!pAlbum) return;
+
+ // -- Prepare downloading of camera items ------------------------
+
+ KURL url;
+ url.setPath(pAlbum->folderPath());
+
+ d->controller->downloadPrep();
+
+ DownloadSettingsContainer downloadSettings;
+ TQString downloadName;
+ time_t mtime;
+ int total = 0;
+
+ downloadSettings.autoRotate = d->autoRotateCheck->isChecked();
+ downloadSettings.fixDateTime = d->fixDateTimeCheck->isChecked();
+ downloadSettings.newDateTime = d->dateTimeEdit->dateTime();
+ downloadSettings.setPhotographerId = d->setPhotographerId->isChecked();
+ downloadSettings.setCredits = d->setCredits->isChecked();
+ downloadSettings.convertJpeg = convertLosslessJpegFiles();
+ downloadSettings.losslessFormat = losslessFormat();
+
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (settings)
+ {
+ downloadSettings.author = settings->getIptcAuthor();
+ downloadSettings.authorTitle = settings->getIptcAuthorTitle();
+ downloadSettings.credit = settings->getIptcCredit();
+ downloadSettings.source = settings->getIptcSource();
+ downloadSettings.copyright = settings->getIptcCopyright();
+ }
+
+ // -- Download camera items -------------------------------
+ // Since we show camera items in reverse order, downloading need to be done also in reverse order.
+
+ for (IconItem* item = d->view->lastItem(); item;
+ item = item->prevItem())
+ {
+ if (onlySelected && !(item->isSelected()))
+ continue;
+
+ CameraIconViewItem* iconItem = static_cast<CameraIconViewItem*>(item);
+ downloadSettings.folder = iconItem->itemInfo()->folder;
+ downloadSettings.file = iconItem->itemInfo()->name;
+ downloadName = iconItem->getDownloadName();
+ mtime = iconItem->itemInfo()->mtime;
+
+ KURL u(url);
+ TQString errMsg;
+ TQDateTime dateTime;
+ dateTime.setTime_t(mtime);
+
+ // Auto sub-albums creation based on file date.
+
+ if (d->autoAlbumDateCheck->isChecked())
+ {
+ TQString dirName;
+
+ switch(d->folderDateFormat->currentItem())
+ {
+ case CameraUIPriv::TextDateFormat:
+ dirName = dateTime.date().toString(TQt::TextDate);
+ break;
+ case CameraUIPriv::LocalDateFormat:
+ dirName = dateTime.date().toString(TQt::LocalDate);
+ break;
+ default: // IsoDateFormat
+ dirName = dateTime.date().toString(TQt::ISODate);
+ break;
+ }
+ // See B.K.O #136927 : we need to support file system which do not
+ // handle upper case properly.
+ dirName = dirName.lower();
+ if (!createAutoAlbum(url, dirName, dateTime.date(), errMsg))
+ {
+ KMessageBox::error(this, errMsg);
+ return;
+ }
+
+ u.addPath(dirName);
+ }
+
+ // Auto sub-albums creation based on file extensions.
+
+ if (d->autoAlbumExtCheck->isChecked())
+ {
+ // We use the target file name to compute sub-albums name to take a care about
+ // convertion on the fly option.
+ TQFileInfo fi(downloadName);
+
+ TQString subAlbum = fi.extension(false).upper();
+ if (fi.extension(false).upper() == TQString("JPEG") ||
+ fi.extension(false).upper() == TQString("JPE"))
+ subAlbum = TQString("JPG");
+ if (fi.extension(false).upper() == TQString("TIFF"))
+ subAlbum = TQString("TIF");
+ if (fi.extension(false).upper() == TQString("MPEG") ||
+ fi.extension(false).upper() == TQString("MPE") ||
+ fi.extension(false).upper() == TQString("MPO"))
+ subAlbum = TQString("MPG");
+
+ // See B.K.O #136927 : we need to support file system which do not
+ // handle upper case properly.
+ subAlbum = subAlbum.lower();
+ if (!createAutoAlbum(u, subAlbum, dateTime.date(), errMsg))
+ {
+ KMessageBox::error(this, errMsg);
+ return;
+ }
+
+ u.addPath(subAlbum);
+ }
+
+ d->foldersToScan.append(u.path());
+ u.addPath(downloadName.isEmpty() ? downloadSettings.file : downloadName);
+
+ downloadSettings.dest = u.path();
+
+ d->controller->download(downloadSettings);
+ addFileExtension(TQFileInfo(u.path()).extension(false));
+ total++;
+ }
+
+ if (total <= 0)
+ return;
+
+ d->lastDestURL = url;
+ d->progress->setProgress(0);
+ d->progress->setTotalSteps(total);
+ d->progress->show();
+
+ // disable settings tab here instead of slotBusy:
+ // Only needs to be disabled while downloading
+ d->advBox->setEnabled(false);
+
+ d->deleteAfter = deleteAfter;
+}
+
+void CameraUI::slotDownloaded(const TQString& folder, const TQString& file, int status)
+{
+ CameraIconViewItem* iconItem = d->view->findItem(folder, file);
+ if (iconItem)
+ iconItem->setDownloaded(status);
+
+ if (status == GPItemInfo::DownloadedYes || status == GPItemInfo::DownloadFailed)
+ {
+ int curr = d->progress->progress();
+ d->progress->setProgress(curr+1);
+ }
+
+ // Download all items is complete.
+ if (d->progress->progress() == d->progress->totalSteps())
+ {
+ if (d->deleteAfter)
+ deleteItems(true, true);
+ }
+}
+
+void CameraUI::slotSkipped(const TQString& folder, const TQString& file)
+{
+ CameraIconViewItem* iconItem = d->view->findItem(folder, file);
+ if (iconItem)
+ iconItem->setDownloaded(GPItemInfo::DownloadedNo);
+
+ int curr = d->progress->progress();
+ d->progress->setProgress(curr+1);
+}
+
+void CameraUI::slotToggleLock()
+{
+ int count = 0;
+ for (IconItem* item = d->view->firstItem(); item;
+ item = item->nextItem())
+ {
+ CameraIconViewItem* iconItem = static_cast<CameraIconViewItem*>(item);
+ if (iconItem->isSelected())
+ {
+ TQString folder = iconItem->itemInfo()->folder;
+ TQString file = iconItem->itemInfo()->name;
+ int writePerm = iconItem->itemInfo()->writePermissions;
+ bool lock = true;
+
+ // If item is currently locked, unlock it.
+ if (writePerm == 0)
+ lock = false;
+
+ d->controller->lockFile(folder, file, lock);
+ count++;
+ }
+ }
+
+ if (count > 0)
+ {
+ d->progress->setProgress(0);
+ d->progress->setTotalSteps(count);
+ d->progress->show();
+ }
+}
+
+void CameraUI::slotLocked(const TQString& folder, const TQString& file, bool status)
+{
+ if (status)
+ {
+ CameraIconViewItem* iconItem = d->view->findItem(folder, file);
+ if (iconItem)
+ {
+ iconItem->toggleLock();
+ //if (iconItem->isSelected())
+ // slotItemsSelected(iconItem, true);
+ }
+ }
+
+ int curr = d->progress->progress();
+ d->progress->setProgress(curr+1);
+}
+
+void CameraUI::checkItem4Deletion(CameraIconViewItem* iconItem, TQStringList& folders, TQStringList& files,
+ TQStringList& deleteList, TQStringList& lockedList)
+{
+ if (iconItem->itemInfo()->writePermissions != 0) // Item not locked ?
+ {
+ TQString folder = iconItem->itemInfo()->folder;
+ TQString file = iconItem->itemInfo()->name;
+ folders.append(folder);
+ files.append(file);
+ deleteList.append(folder + TQString("/") + file);
+ }
+ else
+ {
+ lockedList.append(iconItem->itemInfo()->name);
+ }
+}
+
+void CameraUI::deleteItems(bool onlySelected, bool onlyDownloaded)
+{
+ TQStringList folders;
+ TQStringList files;
+ TQStringList deleteList;
+ TQStringList lockedList;
+
+ for (IconItem* item = d->view->firstItem(); item; item = item->nextItem())
+ {
+ CameraIconViewItem* iconItem = dynamic_cast<CameraIconViewItem*>(item);
+ if (iconItem)
+ {
+ if (onlySelected)
+ {
+ if (iconItem->isSelected())
+ {
+ if (onlyDownloaded)
+ {
+ if (iconItem->isDownloaded())
+ checkItem4Deletion(iconItem, folders, files, deleteList, lockedList);
+ }
+ else
+ {
+ checkItem4Deletion(iconItem, folders, files, deleteList, lockedList);
+ }
+ }
+ }
+ else // All items
+ {
+ if (onlyDownloaded)
+ {
+ if (iconItem->isDownloaded())
+ checkItem4Deletion(iconItem, folders, files, deleteList, lockedList);
+ }
+ else
+ {
+ checkItem4Deletion(iconItem, folders, files, deleteList, lockedList);
+ }
+ }
+ }
+ }
+
+ // If we want to delete some locked files, just give a feedback to user.
+ if (!lockedList.isEmpty())
+ {
+ TQString infoMsg(i18n("The items listed below are locked by camera (read-only). "
+ "These items will not be deleted. If you really want to delete these items, "
+ "please unlock them and try again."));
+ KMessageBox::informationList(this, infoMsg, lockedList, i18n("Information"));
+ }
+
+ if (folders.isEmpty())
+ return;
+
+ TQString warnMsg(i18n("About to delete this image. "
+ "Deleted files are unrecoverable. "
+ "Are you sure?",
+ "About to delete these %n images. "
+ "Deleted files are unrecoverable. "
+ "Are you sure?",
+ deleteList.count()));
+ if (KMessageBox::warningContinueCancelList(this, warnMsg,
+ deleteList,
+ i18n("Warning"),
+ i18n("Delete"))
+ == KMessageBox::Continue)
+ {
+ TQStringList::iterator itFolder = folders.begin();
+ TQStringList::iterator itFile = files.begin();
+
+ d->progress->setProgress(0);
+ d->progress->setTotalSteps(deleteList.count());
+ d->progress->show();
+
+ for ( ; itFolder != folders.end(); ++itFolder, ++itFile)
+ {
+ d->controller->deleteFile(*itFolder, *itFile);
+ // the currentlyDeleting list is used to prevent loading items which
+ // will immenently be deleted into the sidebar and wasting time
+ d->currentlyDeleting.append(*itFolder + *itFile);
+ }
+ }
+}
+
+void CameraUI::slotDeleteSelected()
+{
+ deleteItems(true, false);
+}
+
+void CameraUI::slotDeleteAll()
+{
+ deleteItems(false, false);
+}
+
+void CameraUI::slotDeleted(const TQString& folder, const TQString& file, bool status)
+{
+ if (status)
+ {
+ d->view->removeItem(folder, file);
+ // do this after removeItem, which will signal to slotItemsSelected, which checks for the list
+ d->currentlyDeleting.remove(folder + file);
+ }
+
+ int curr = d->progress->progress();
+ d->progress->setProgress(curr+1);
+}
+
+void CameraUI::slotFileView(CameraIconViewItem* item)
+{
+ d->controller->openFile(item->itemInfo()->folder, item->itemInfo()->name);
+}
+
+void CameraUI::slotExifFromFile(const TQString& folder, const TQString& file)
+{
+ CameraIconViewItem* item = d->view->findItem(folder, file);
+ if (!item)
+ return;
+
+ d->rightSidebar->itemChanged(item->itemInfo(), folder + TQString("/") + file,
+ TQByteArray(), d->view, item);
+}
+
+void CameraUI::slotExifFromData(const TQByteArray& exifData)
+{
+ CameraIconViewItem* item = dynamic_cast<CameraIconViewItem*>(d->view->currentItem());
+
+ if (!item)
+ return;
+
+ KURL url(item->itemInfo()->folder + '/' + item->itemInfo()->name);
+
+ // Sometimes, GPhoto2 drivers return complete APP1 JFIF section. Exiv2 cannot
+ // decode (yet) exif metadata from APP1. We will find Exif header to get data at this place
+ // to please with Exiv2...
+
+ DDebug() << "Size of Exif metadata from camera = " << exifData.size() << endl;
+ char exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
+
+ if (!exifData.isEmpty())
+ {
+ int i = exifData.find(*exifHeader);
+ if (i != -1)
+ {
+ DDebug() << "Exif header found at position " << i << endl;
+ i = i + sizeof(exifHeader);
+ TQByteArray data(exifData.size()-i);
+ memcpy(data.data(), exifData.data()+i, data.size());
+ d->rightSidebar->itemChanged(item->itemInfo(), url, data, d->view, item);
+ return;
+ }
+ }
+
+ d->rightSidebar->itemChanged(item->itemInfo(), url, exifData, d->view, item);
+}
+
+void CameraUI::slotNewSelection(bool hasSelection)
+{
+ if (!d->renameCustomizer->useDefault())
+ {
+ d->downloadMenu->setItemEnabled(0, hasSelection);
+ d->downloadMenu->setItemEnabled(2, hasSelection);
+ }
+ else
+ {
+ d->downloadMenu->setItemEnabled(0, hasSelection);
+ d->downloadMenu->setItemEnabled(2, hasSelection);
+ }
+
+ unsigned long fSize = 0;
+ unsigned long dSize = 0;
+ d->view->itemsSelectionSizeInfo(fSize, dSize);
+ d->freeSpaceWidget->setEstimatedDSizeKb(dSize);
+}
+
+void CameraUI::slotItemsSelected(CameraIconViewItem* item, bool selected)
+{
+ d->downloadMenu->setItemEnabled(0, selected);
+ d->downloadMenu->setItemEnabled(2, selected);
+ d->deleteMenu->setItemEnabled(0, selected);
+
+ if (selected)
+ {
+ // if selected item is in the list of item which will be deleted, set no current item
+ if (d->currentlyDeleting.find(item->itemInfo()->folder + item->itemInfo()->name)
+ == d->currentlyDeleting.end())
+ {
+ KURL url(item->itemInfo()->folder + '/' + item->itemInfo()->name);
+ d->rightSidebar->itemChanged(item->itemInfo(), url, TQByteArray(), d->view, item);
+ d->controller->getExif(item->itemInfo()->folder, item->itemInfo()->name);
+ }
+ else
+ {
+ d->rightSidebar->slotNoCurrentItem();
+ }
+ }
+ else
+ d->rightSidebar->slotNoCurrentItem();
+}
+
+bool CameraUI::createAutoAlbum(const KURL& parentURL, const TQString& sub,
+ const TQDate& date, TQString& errMsg)
+{
+ KURL u(parentURL);
+ u.addPath(sub);
+
+ // first stat to see if the album exists
+ TQFileInfo info(u.path());
+ if (info.exists())
+ {
+ // now check if its really a directory
+ if (info.isDir())
+ return true;
+ else
+ {
+ errMsg = i18n("A file with same name (%1) exists in folder %2")
+ .arg(sub)
+ .arg(parentURL.path());
+ return false;
+ }
+ }
+
+ // looks like the directory does not exist, try to create it
+
+ AlbumManager* aman = AlbumManager::instance();
+ PAlbum* parent = aman->findPAlbum(parentURL);
+ if (!parent)
+ {
+ errMsg = i18n("Failed to find Album for path '%1'")
+ .arg(parentURL.path());
+ return false;
+ }
+
+ return aman->createPAlbum(parent, sub, TQString(""), date, TQString(""), errMsg);
+}
+
+void CameraUI::addFileExtension(const TQString& ext)
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings)
+ return;
+
+ if (settings->getImageFileFilter().upper().contains(ext.upper()) ||
+ settings->getMovieFileFilter().upper().contains(ext.upper()) ||
+ settings->getAudioFileFilter().upper().contains(ext.upper()) ||
+ settings->getRawFileFilter().upper().contains(ext.upper()))
+ return;
+
+ settings->setImageFileFilter(settings->getImageFileFilter() + TQString(" *.") + ext);
+ emit signalAlbumSettingsChanged();
+}
+
+void CameraUI::slotFirstItem()
+{
+ CameraIconViewItem *currItem = dynamic_cast<CameraIconViewItem*>(d->view->firstItem());
+ d->view->clearSelection();
+ d->view->updateContents();
+ if (currItem)
+ d->view->setCurrentItem(currItem);
+}
+
+void CameraUI::slotPrevItem()
+{
+ CameraIconViewItem *currItem = dynamic_cast<CameraIconViewItem*>(d->view->currentItem());
+ d->view->clearSelection();
+ d->view->updateContents();
+ if (currItem)
+ d->view->setCurrentItem(currItem->prevItem());
+}
+
+void CameraUI::slotNextItem()
+{
+ CameraIconViewItem *currItem = dynamic_cast<CameraIconViewItem*>(d->view->currentItem());
+ d->view->clearSelection();
+ d->view->updateContents();
+ if (currItem)
+ d->view->setCurrentItem(currItem->nextItem());
+}
+
+void CameraUI::slotLastItem(void)
+{
+ CameraIconViewItem *currItem = dynamic_cast<CameraIconViewItem*>(d->view->lastItem());
+ d->view->clearSelection();
+ d->view->updateContents();
+ if (currItem)
+ d->view->setCurrentItem(currItem);
+}
+
+// Backport KDialog::keyPressEvent() implementation from KDELibs to ignore Enter/Return Key events
+// to prevent any conflicts between dialog keys events and SpinBox keys events.
+
+void CameraUI::keyPressEvent(TQKeyEvent *e)
+{
+ if ( e->state() == 0 )
+ {
+ switch ( e->key() )
+ {
+ case Key_Escape:
+ e->accept();
+ reject();
+ break;
+ case Key_Enter:
+ case Key_Return:
+ e->ignore();
+ break;
+ default:
+ e->ignore();
+ return;
+ }
+ }
+ else
+ {
+ // accept the dialog when Ctrl-Return is pressed
+ if ( e->state() == ControlButton &&
+ (e->key() == Key_Return || e->key() == Key_Enter) )
+ {
+ e->accept();
+ accept();
+ }
+ else
+ {
+ e->ignore();
+ }
+ }
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/cameraui.h b/src/utilities/cameragui/cameraui.h
new file mode 100644
index 00000000..df9c7885
--- /dev/null
+++ b/src/utilities/cameragui/cameraui.h
@@ -0,0 +1,155 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-16
+ * Description : Camera interface dialog
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERAUI_H
+#define CAMERAUI_H
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqstring.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "gpiteminfo.h"
+
+namespace Digikam
+{
+
+class Album;
+class CameraIconViewItem;
+class CameraUIPriv;
+
+class CameraUI : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ CameraUI(TQWidget* parent, const TQString& cameraTitle,
+ const TQString& model, const TQString& port,
+ const TQString& path, const TQDateTime lastAccess);
+ ~CameraUI();
+
+ bool isBusy() const;
+ bool isClosed() const;
+
+ bool autoRotateJpegFiles() const;
+
+ /** Get status of JPEG conversion files to lossless format during download.*/
+ bool convertLosslessJpegFiles() const;
+ TQString losslessFormat();
+
+ TQString cameraTitle() const;
+
+signals:
+
+ void signalLastDestination(const KURL&);
+ void signalAlbumSettingsChanged();
+
+public slots:
+
+ void slotDownload(bool onlySelected, bool deleteAfter, Album *album=0);
+
+protected:
+
+ void closeEvent(TQCloseEvent* e);
+ void keyPressEvent(TQKeyEvent *e);
+
+private:
+
+ void readSettings();
+ void saveSettings();
+ bool dialogClosed();
+ bool createAutoAlbum(const KURL& parentURL, const TQString& sub,
+ const TQDate& date, TQString& errMsg);
+ void addFileExtension(const TQString& ext);
+ void finishDialog();
+ void deleteItems(bool onlySelected, bool onlyDownloaded);
+ void checkItem4Deletion(CameraIconViewItem* iconItem, TQStringList& folders, TQStringList& files,
+ TQStringList& deleteList, TQStringList& lockedList);
+
+private slots:
+
+ void slotClose();
+ void slotCancelButton();
+ void slotProcessURL(const TQString& url);
+
+ void slotConnected(bool val);
+ void slotBusy(bool val);
+ void slotErrorMsg(const TQString& msg);
+ void slotInformations();
+ void slotCameraInformations(const TQString&, const TQString&, const TQString&);
+
+ void slotFolderList(const TQStringList& folderList);
+ void slotFileList(const GPItemInfoList& fileList);
+ void slotThumbnail(const TQString&, const TQString&, const TQImage&);
+
+ void slotIncreaseThumbSize();
+ void slotDecreaseThumbSize();
+
+ void slotUpload();
+ void slotUploadItems(const KURL::List&);
+ void slotDownloadSelected();
+ void slotDownloadAll();
+ void slotDeleteSelected();
+ void slotDownloadAndDeleteSelected();
+ void slotDeleteAll();
+ void slotDownloadAndDeleteAll();
+ void slotToggleLock();
+
+ void slotFileView(CameraIconViewItem* item);
+
+ void slotUploaded(const GPItemInfo&);
+ void slotDownloaded(const TQString&, const TQString&, int);
+ void slotSkipped(const TQString&, const TQString&);
+ void slotDeleted(const TQString&, const TQString&, bool);
+ void slotLocked(const TQString&, const TQString&, bool);
+
+ void slotNewSelection(bool);
+ void slotItemsSelected(CameraIconViewItem* item, bool selected);
+
+ void slotExifFromFile(const TQString& folder, const TQString& file);
+ void slotExifFromData(const TQByteArray& exifData);
+
+ void slotFirstItem(void);
+ void slotPrevItem(void);
+ void slotNextItem(void);
+ void slotLastItem(void);
+
+private:
+
+ CameraUIPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* CAMERAUI_H */
diff --git a/src/utilities/cameragui/dkcamera.cpp b/src/utilities/cameragui/dkcamera.cpp
new file mode 100644
index 00000000..8f318855
--- /dev/null
+++ b/src/utilities/cameragui/dkcamera.cpp
@@ -0,0 +1,113 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-21
+ * Description : abstract camera interface class
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdeepcopy.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "dkcamera.h"
+
+namespace Digikam
+{
+
+DKCamera::DKCamera(const TQString& title, const TQString& model, const TQString& port, const TQString& path)
+{
+ m_title = title;
+ m_model = model;
+ m_port = port;
+ m_path = path;
+
+ AlbumSettings* settings = AlbumSettings::instance();
+ m_imageFilter = TQDeepCopy<TQString>(settings->getImageFileFilter());
+ m_movieFilter = TQDeepCopy<TQString>(settings->getMovieFileFilter());
+ m_audioFilter = TQDeepCopy<TQString>(settings->getAudioFileFilter());
+ m_rawFilter = TQDeepCopy<TQString>(settings->getRawFileFilter());
+
+ m_imageFilter = m_imageFilter.lower();
+ m_movieFilter = m_movieFilter.lower();
+ m_audioFilter = m_audioFilter.lower();
+ m_rawFilter = m_rawFilter.lower();
+}
+
+DKCamera::~DKCamera()
+{
+}
+
+TQString DKCamera::title() const
+{
+ return m_title;
+}
+
+TQString DKCamera::model() const
+{
+ return m_model;
+}
+
+TQString DKCamera::port() const
+{
+ return m_port;
+}
+
+TQString DKCamera::path() const
+{
+ return m_path;
+}
+
+TQString DKCamera::mimeType(const TQString& fileext) const
+{
+ if (fileext.isEmpty()) return TQString();
+
+ TQString ext = fileext;
+ TQString mime;
+
+ // Massage known variations of known mimetypes into KDE specific ones
+ if (ext == "jpg" || ext == "jpe")
+ ext = "jpeg";
+ else if (ext == "tif")
+ ext = "tiff";
+
+ if (m_rawFilter.contains(ext))
+ {
+ mime = TQString("image/x-raw");
+ }
+ else if (m_imageFilter.contains(ext))
+ {
+ mime = TQString("image/") + ext;
+ }
+ else if (m_movieFilter.contains(ext))
+ {
+ mime = TQString("video/") + ext;
+ }
+ else if (m_audioFilter.contains(ext))
+ {
+ mime = TQString("audio/") + ext;
+ }
+
+ return mime;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/dkcamera.h b/src/utilities/cameragui/dkcamera.h
new file mode 100644
index 00000000..2ef76723
--- /dev/null
+++ b/src/utilities/cameragui/dkcamera.h
@@ -0,0 +1,97 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-21
+ * Description : abstract camera interface class
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DKCAMERA_H
+#define DKCAMERA_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Local includes.
+
+#include "gpiteminfo.h"
+
+class TQStringList;
+class TQImage;
+
+namespace Digikam
+{
+
+class DKCamera
+{
+public:
+
+ DKCamera(const TQString& title, const TQString& model, const TQString& port, const TQString& path);
+ virtual ~DKCamera();
+
+ virtual bool doConnect() = 0;
+ virtual void cancel() = 0;
+
+ virtual void getAllFolders(const TQString& folder, TQStringList& subFolderList) = 0;
+
+ /// If getImageDimensions is false, the camera shall set width and height to -1
+ /// if the values are not immediately available
+ virtual bool getItemsInfoList(const TQString& folder, GPItemInfoList& infoList, bool getImageDimensions = true) = 0;
+
+ virtual bool getThumbnail(const TQString& folder, const TQString& itemName, TQImage& thumbnail) = 0;
+ virtual bool getExif(const TQString& folder, const TQString& itemName, char **edata, int& esize) = 0;
+
+ virtual bool downloadItem(const TQString& folder, const TQString& itemName, const TQString& saveFile) = 0;
+ virtual bool deleteItem(const TQString& folder, const TQString& itemName) = 0;
+ virtual bool uploadItem(const TQString& folder, const TQString& itemName, const TQString& localFile,
+ GPItemInfo& itemInfo, bool getImageDimensions=true) = 0;
+ virtual bool cameraSummary(TQString& summary) = 0;
+ virtual bool cameraManual(TQString& manual) = 0;
+ virtual bool cameraAbout(TQString& about) = 0;
+
+ virtual bool setLockItem(const TQString& folder, const TQString& itemName, bool lock) = 0;
+
+ TQString title() const;
+ TQString model() const;
+ TQString port() const;
+ TQString path() const;
+
+protected:
+
+ TQString mimeType(const TQString& fileext) const;
+
+protected:
+
+ TQString m_imageFilter;
+ TQString m_movieFilter;
+ TQString m_audioFilter;
+ TQString m_rawFilter;
+
+private:
+
+ TQString m_title;
+ TQString m_model;
+ TQString m_port;
+ TQString m_path;
+};
+
+} // namespace Digikam
+
+#endif /* DKCAMERA_H */
diff --git a/src/utilities/cameragui/downloadsettingscontainer.h b/src/utilities/cameragui/downloadsettingscontainer.h
new file mode 100644
index 00000000..b3e59501
--- /dev/null
+++ b/src/utilities/cameragui/downloadsettingscontainer.h
@@ -0,0 +1,79 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-21-07
+ * Description : Camera item download settings container.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DOWNLOADSETTINGSCONTAINER_H
+#define DOWNLOADSETTINGSCONTAINER_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqdatetime.h>
+
+namespace Digikam
+{
+
+class DownloadSettingsContainer
+{
+
+public:
+
+ DownloadSettingsContainer()
+ {
+ autoRotate = true;
+ fixDateTime = false;
+ setPhotographerId = false;
+ setCredits = false;
+ convertJpeg = false;
+ };
+
+ ~DownloadSettingsContainer(){};
+
+public:
+
+ bool autoRotate;
+ bool fixDateTime;
+ bool setPhotographerId;
+ bool setCredits;
+ bool convertJpeg;
+
+ TQDateTime newDateTime;
+
+ // File path to download.
+ TQString folder;
+ TQString file;
+ TQString dest;
+
+ // New format to convert Jpeg files.
+ TQString losslessFormat;
+
+ // IPTC settings
+ TQString author;
+ TQString authorTitle;
+ TQString credit;
+ TQString source;
+ TQString copyright;
+};
+
+} // namespace Digikam
+
+#endif // DOWNLOADSETTINGSCONTAINER_H
diff --git a/src/utilities/cameragui/freespacewidget.cpp b/src/utilities/cameragui/freespacewidget.cpp
new file mode 100644
index 00000000..0d7a26b1
--- /dev/null
+++ b/src/utilities/cameragui/freespacewidget.cpp
@@ -0,0 +1,252 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-31
+ * Description : a widget to display free space for a mount-point.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+#include <tqpalette.h>
+#include <tqcolor.h>
+#include <tqtimer.h>
+#include <tqfont.h>
+#include <tqfontmetrics.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <tdelocale.h>
+#include <kdiskfreesp.h>
+#include <tdeio/global.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "freespacewidget.h"
+#include "freespacewidget.moc"
+
+namespace Digikam
+{
+
+class FreeSpaceWidgetPriv
+{
+public:
+
+ FreeSpaceWidgetPriv()
+ {
+ timer = 0;
+ isValid = false;
+ kBSize = 0;
+ kBUsed = 0;
+ kBAvail = 0;
+ dSizeKb = 0;
+ percentUsed = 0;
+ }
+
+ bool isValid;
+
+ int percentUsed;
+
+ unsigned long dSizeKb;
+ unsigned long kBSize;
+ unsigned long kBUsed;
+ unsigned long kBAvail;
+
+ TQString mountPoint;
+
+ TQTimer *timer;
+
+ TQPixmap pix;
+};
+
+FreeSpaceWidget::FreeSpaceWidget(TQWidget* parent, int width)
+ : TQWidget(parent, 0, WResizeNoErase|WRepaintNoErase)
+{
+ d = new FreeSpaceWidgetPriv;
+ setBackgroundMode(TQt::NoBackground);
+ setFixedWidth(width);
+ setMaximumHeight(fontMetrics().height()+4);
+ slotTimeout();
+
+ d->timer = new TQTimer(this);
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotTimeout()));
+
+ d->timer->start(10000);
+}
+
+FreeSpaceWidget::~FreeSpaceWidget()
+{
+ d->timer->stop();
+ delete d->timer;
+ delete d;
+}
+
+void FreeSpaceWidget::setEstimatedDSizeKb(unsigned long dSize)
+{
+ d->dSizeKb = dSize;
+ updatePixmap();
+ repaint();
+}
+
+unsigned long FreeSpaceWidget::estimatedDSizeKb()
+{
+ return d->dSizeKb;
+}
+
+bool FreeSpaceWidget::isValid()
+{
+ return d->isValid;
+}
+
+int FreeSpaceWidget::percentUsed()
+{
+ return d->percentUsed;
+}
+
+unsigned long FreeSpaceWidget::kBSize()
+{
+ return d->kBSize;
+}
+
+unsigned long FreeSpaceWidget::kBUsed()
+{
+ return d->kBUsed;
+}
+
+unsigned long FreeSpaceWidget::kBAvail()
+{
+ return d->kBAvail;
+}
+
+TQString FreeSpaceWidget::mountPoint()
+{
+ return d->mountPoint;
+}
+
+void FreeSpaceWidget::updatePixmap()
+{
+ TQPixmap fimgPix = SmallIcon("folder_image");
+ d->pix = TQPixmap(size());
+ d->pix.fill(colorGroup().background());
+
+ TQPainter p(&d->pix);
+ p.setPen(colorGroup().mid());
+ p.drawRect(0, 0, d->pix.width(), d->pix.height());
+ p.drawPixmap(2, d->pix.height()/2-fimgPix.height()/2,
+ fimgPix, 0, 0, fimgPix.width(), fimgPix.height());
+
+ if (isValid())
+ {
+ // We will compute the estimated % of space size used to download and process.
+ unsigned long eUsedKb = d->dSizeKb + d->kBUsed;
+ int peUsed = (int)(100.0*((double)eUsedKb/(double)d->kBSize));
+ int pClamp = peUsed > 100 ? 100 : peUsed;
+ p.setBrush(peUsed > 95 ? TQt::red : TQt::darkGreen);
+ p.setPen(TQt::white);
+ TQRect gRect(fimgPix.height()+2, 1,
+ (int)(((double)d->pix.width()-2.0-fimgPix.width()-2.0)*(pClamp/100.0)),
+ d->pix.height()-2);
+ p.drawRect(gRect);
+
+ TQRect tRect(fimgPix.height()+2, 1, d->pix.width()-2-fimgPix.width()-2, d->pix.height()-2);
+ TQString text = TQString("%1%").arg(peUsed);
+ p.setPen(colorGroup().text());
+ TQFontMetrics fontMt = p.fontMetrics();
+ TQRect fontRect = fontMt.boundingRect(tRect.x(), tRect.y(),
+ tRect.width(), tRect.height(), 0, text);
+ p.drawText(tRect, TQt::AlignCenter, text);
+
+ TQString tipText, value;
+ TQString header = i18n("Album Library");
+ TQString headBeg("<tr bgcolor=\"orange\"><td colspan=\"2\">"
+ "<nobr><font size=\"-1\" color=\"black\"><b>");
+ TQString headEnd("</b></font></nobr></td></tr>");
+ TQString cellBeg("<tr><td><nobr><font size=-1>");
+ TQString cellMid("</font></nobr></td><td><nobr><font size=-1>");
+ TQString cellEnd("</font></nobr></td></tr>");
+ tipText = "<table cellspacing=0 cellpadding=0>";
+ tipText += headBeg + header + headEnd;
+
+ if (d->dSizeKb > 0)
+ {
+ tipText += cellBeg + i18n("Capacity:") + cellMid;
+ tipText += TDEIO::convertSizeFromKB(d->kBSize) + cellEnd;
+
+ tipText += cellBeg + i18n("Available:") + cellMid;
+ tipText += TDEIO::convertSizeFromKB(d->kBAvail) + cellEnd;
+
+ tipText += cellBeg + i18n("Require:") + cellMid;
+ tipText += TDEIO::convertSizeFromKB(d->dSizeKb) + cellEnd;
+ }
+ else
+ {
+ tipText += cellBeg + i18n("Capacity:") + cellMid;
+ tipText += TDEIO::convertSizeFromKB(d->kBSize) + cellEnd;
+
+ tipText += cellBeg + i18n("Available:") + cellMid;
+ tipText += TDEIO::convertSizeFromKB(d->kBAvail) + cellEnd;
+ }
+
+ tipText += "</table>";
+
+ TQWhatsThis::add(this, tipText);
+ TQToolTip::add(this, tipText);
+ }
+
+ p.end();
+}
+
+void FreeSpaceWidget::paintEvent(TQPaintEvent*)
+{
+ bitBlt(this, 0, 0, &d->pix);
+}
+
+void FreeSpaceWidget::slotTimeout()
+{
+ TQString mountPoint = TDEIO::findPathMountPoint(AlbumSettings::instance()->getAlbumLibraryPath());
+ KDiskFreeSp *job = new KDiskFreeSp;
+ connect(job, TQ_SIGNAL(foundMountPoint(const unsigned long&, const unsigned long&,
+ const unsigned long&, const TQString&)),
+ this, TQ_SLOT(slotAvailableFreeSpace(const unsigned long&, const unsigned long&,
+ const unsigned long&, const TQString&)));
+ job->readDF(mountPoint);
+}
+
+void FreeSpaceWidget::slotAvailableFreeSpace(const unsigned long& kBSize, const unsigned long& kBUsed,
+ const unsigned long& kBAvail, const TQString& mountPoint)
+{
+ d->mountPoint = mountPoint;
+ d->kBSize = kBSize;
+ d->kBUsed = kBUsed;
+ d->kBAvail = kBAvail;
+ d->percentUsed = 100 - (int)(100.0*kBAvail/kBSize);
+ d->isValid = true;
+ updatePixmap();
+ repaint();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/freespacewidget.h b/src/utilities/cameragui/freespacewidget.h
new file mode 100644
index 00000000..2111791a
--- /dev/null
+++ b/src/utilities/cameragui/freespacewidget.h
@@ -0,0 +1,75 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-08-31
+ * Description : a widget to display free space for a mount-point.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef FREESPACEWIDGET_H
+#define FREESPACEWIDGET_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class FreeSpaceWidgetPriv;
+
+class FreeSpaceWidget : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ FreeSpaceWidget(TQWidget* parent, int width);
+ ~FreeSpaceWidget();
+
+ void setEstimatedDSizeKb(unsigned long dSize);
+ unsigned long estimatedDSizeKb();
+
+ bool isValid() ;
+ int percentUsed();
+ unsigned long kBSize();
+ unsigned long kBUsed();
+ unsigned long kBAvail();
+ TQString mountPoint();
+
+protected:
+
+ void paintEvent(TQPaintEvent*);
+ void updatePixmap();
+
+private slots:
+
+ void slotTimeout();
+ void slotAvailableFreeSpace(const unsigned long& kBSize, const unsigned long& kBUsed,
+ const unsigned long& kBAvail, const TQString& mountPoint);
+
+private:
+
+ FreeSpaceWidgetPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* FREESPACEWIDGET_H */
diff --git a/src/utilities/cameragui/gpcamera.cpp b/src/utilities/cameragui/gpcamera.cpp
new file mode 100644
index 00000000..e03d92d0
--- /dev/null
+++ b/src/utilities/cameragui/gpcamera.cpp
@@ -0,0 +1,1223 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-21
+ * Description : Gphoto2 camera interface
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <iostream>
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqstringlist.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqdom.h>
+#include <tqfile.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// C Ansi includes.
+extern "C"
+{
+#include <gphoto2.h>
+}
+
+// Local includes.
+
+#include "ddebug.h"
+#include "gpcamera.h"
+
+namespace Digikam
+{
+
+class GPCameraPrivate
+{
+
+public:
+
+ GPCameraPrivate()
+ {
+ camera = 0;
+ }
+
+ bool cameraInitialized;
+
+ bool thumbnailSupport;
+ bool deleteSupport;
+ bool uploadSupport;
+ bool mkDirSupport;
+ bool delDirSupport;
+
+ TQString model;
+ TQString port;
+ TQString globalPath;
+
+ Camera *camera;
+ CameraAbilities cameraAbilities;
+};
+
+class GPStatus
+{
+
+public:
+
+ GPStatus()
+ {
+ context = gp_context_new();
+ cancel = false;
+ gp_context_set_cancel_func(context, cancel_func, 0);
+ }
+
+ ~GPStatus()
+ {
+ gp_context_unref(context);
+ cancel = false;
+ }
+
+ GPContext *context;
+ static bool cancel;
+
+ static GPContextFeedback cancel_func(GPContext *, void *)
+ {
+ return (cancel ? GP_CONTEXT_FEEDBACK_CANCEL :
+ GP_CONTEXT_FEEDBACK_OK);
+ }
+};
+
+bool GPStatus::cancel = false;
+
+GPCamera::GPCamera(const TQString& title, const TQString& model, const TQString& port, const TQString& path)
+ : DKCamera(title, model, port, path)
+{
+ m_status = 0;
+
+ d = new GPCameraPrivate;
+ d->camera = 0;
+ d->model = model;
+ d->port = port;
+ d->globalPath = path;
+ d->cameraInitialized = false;
+ d->thumbnailSupport = false;
+ d->deleteSupport = false;
+ d->uploadSupport = false;
+ d->mkDirSupport = false;
+ d->delDirSupport = false;
+}
+
+GPCamera::~GPCamera()
+{
+ if (d->camera)
+ {
+ gp_camera_unref(d->camera);
+ d->camera = 0;
+ }
+
+ delete d;
+}
+
+TQString GPCamera::model() const
+{
+ return d->model;
+}
+
+TQString GPCamera::port() const
+{
+ return d->port;
+}
+
+TQString GPCamera::path() const
+{
+ return d->globalPath;
+}
+
+bool GPCamera::thumbnailSupport()
+{
+ return d->thumbnailSupport;
+}
+
+bool GPCamera::deleteSupport()
+{
+ return d->deleteSupport;
+}
+
+bool GPCamera::uploadSupport()
+{
+ return d->uploadSupport;
+}
+
+bool GPCamera::mkDirSupport()
+{
+ return d->mkDirSupport;
+}
+
+bool GPCamera::delDirSupport()
+{
+ return d->delDirSupport;
+}
+
+bool GPCamera::doConnect()
+{
+ int errorCode;
+ // -- first step - setup the camera --------------------
+
+ if (d->camera)
+ {
+ gp_camera_unref(d->camera);
+ d->camera = 0;
+ }
+
+ CameraAbilitiesList *abilList;
+ GPPortInfoList *infoList;
+ GPPortInfo info;
+
+ gp_camera_new(&d->camera);
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus();
+
+ gp_abilities_list_new(&abilList);
+ gp_abilities_list_load(abilList, m_status->context);
+ gp_port_info_list_new(&infoList);
+ gp_port_info_list_load(infoList);
+
+ delete m_status;
+ m_status = 0;
+
+ int modelNum = -1, portNum = -1;
+ modelNum = gp_abilities_list_lookup_model(abilList, d->model.latin1());
+ portNum = gp_port_info_list_lookup_path (infoList, d->port.latin1());
+
+ gp_abilities_list_get_abilities(abilList, modelNum, &d->cameraAbilities);
+
+ errorCode = gp_camera_set_abilities(d->camera, d->cameraAbilities);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to set camera Abilities!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_camera_unref(d->camera);
+ d->camera = 0;
+ gp_abilities_list_free(abilList);
+ gp_port_info_list_free(infoList);
+ return false;
+ }
+
+ if (d->model != "Directory Browse")
+ {
+ gp_port_info_list_get_info(infoList, portNum, &info);
+ errorCode = gp_camera_set_port_info(d->camera, info);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to set camera port!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_camera_unref(d->camera);
+ d->camera = 0;
+ gp_abilities_list_free (abilList);
+ gp_port_info_list_free (infoList);
+ return false;
+ }
+ }
+
+ gp_abilities_list_free (abilList);
+ gp_port_info_list_free (infoList);
+
+ if (d->cameraAbilities.file_operations &
+ GP_FILE_OPERATION_PREVIEW)
+ d->thumbnailSupport = true;
+
+ if (d->cameraAbilities.file_operations &
+ GP_FILE_OPERATION_DELETE)
+ d->deleteSupport = true;
+
+ if (d->cameraAbilities.folder_operations &
+ GP_FOLDER_OPERATION_PUT_FILE)
+ d->uploadSupport = true;
+
+ if (d->cameraAbilities.folder_operations &
+ GP_FOLDER_OPERATION_MAKE_DIR)
+ d->mkDirSupport = true;
+
+ if (d->cameraAbilities.folder_operations &
+ GP_FOLDER_OPERATION_REMOVE_DIR)
+ d->delDirSupport = true;
+
+ // -- Now try to initialize the camera -----------------
+
+ m_status = new GPStatus();
+
+ // Try and initialize the camera to see if its connected
+ errorCode = gp_camera_init(d->camera, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to initialize camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_camera_unref(d->camera);
+ d->camera = 0;
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+
+ d->cameraInitialized = true;
+ return true;
+}
+
+void GPCamera::cancel()
+{
+ if (!m_status)
+ return;
+ m_status->cancel = true;
+}
+
+void GPCamera::getAllFolders(const TQString& rootFolder,
+ TQStringList& folderList)
+{
+ TQStringList subfolders;
+ getSubFolders(rootFolder, subfolders);
+
+ for (TQStringList::iterator it = subfolders.begin();
+ it != subfolders.end(); ++it)
+ {
+ *it = rootFolder + TQString(rootFolder.endsWith("/") ? "" : "/") + (*it);
+ folderList.append(*it);
+ }
+
+ for (TQStringList::iterator it = subfolders.begin();
+ it != subfolders.end(); ++it)
+ {
+ getAllFolders(*it, folderList);
+ }
+}
+
+bool GPCamera::getSubFolders(const TQString& folder, TQStringList& subFolderList)
+{
+ int errorCode;
+ CameraList *clist;
+ gp_list_new(&clist);
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+ m_status = new GPStatus();
+
+ errorCode = gp_camera_folder_list_folders(d->camera, TQFile::encodeName(folder), clist, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get folders list from camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_list_unref(clist);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+
+ int count = gp_list_count(clist);
+ for (int i = 0 ; i < count ; i++)
+ {
+ const char* subFolder;
+ errorCode = gp_list_get_name(clist, i, &subFolder);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get folder name from camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_list_unref(clist);
+ return false;
+ }
+
+ subFolderList.append(TQFile::decodeName(subFolder));
+ }
+
+ gp_list_unref(clist);
+ return true;
+}
+
+bool GPCamera::getItemsList(const TQString& folder, TQStringList& itemsList)
+{
+ int errorCode;
+ CameraList *clist;
+ const char *cname;
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+ m_status = new GPStatus;
+
+ gp_list_new(&clist);
+
+ errorCode = gp_camera_folder_list_files(d->camera, TQFile::encodeName(folder), clist, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get folder files list from camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_list_unref(clist);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ int count = gp_list_count(clist);
+ for (int i = 0 ; i < count ; i++)
+ {
+ errorCode = gp_list_get_name(clist, i, &cname);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get file name from camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_list_unref(clist);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ itemsList.append(TQFile::decodeName(cname));
+ }
+
+ gp_list_unref(clist);
+
+ delete m_status;
+ m_status = 0;
+
+ return true;
+}
+
+bool GPCamera::getItemsInfoList(const TQString& folder, GPItemInfoList& items, bool /*getImageDimensions*/)
+{
+ int errorCode;
+ CameraList *clist;
+ const char *cname;
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+ m_status = new GPStatus;
+
+ gp_list_new(&clist);
+
+ errorCode = gp_camera_folder_list_files(d->camera, TQFile::encodeName(folder), clist, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get folder files list from camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_list_unref(clist);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ int count = gp_list_count(clist);
+ for (int i = 0 ; i < count ; i++)
+ {
+ errorCode = gp_list_get_name(clist, i, &cname);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get file name from camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_list_unref(clist);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ GPItemInfo itemInfo;
+
+ itemInfo.name = TQFile::decodeName(cname);
+ itemInfo.folder = folder;
+
+ CameraFileInfo info;
+ gp_camera_file_get_info(d->camera, TQFile::encodeName(folder),
+ cname, &info, m_status->context);
+
+ itemInfo.mtime = -1;
+ itemInfo.mime = "";
+ itemInfo.size = -1;
+ itemInfo.width = -1;
+ itemInfo.height = -1;
+ itemInfo.downloaded = GPItemInfo::DownloadUnknow;
+ itemInfo.readPermissions = -1;
+ itemInfo.writePermissions = -1;
+
+ /* The mime type returned by Gphoto2 is dummy with all RAW files.
+ if (info.file.fields & GP_FILE_INFO_TYPE)
+ itemInfo.mime = info.file.type;*/
+
+ itemInfo.mime = mimeType(TQString(itemInfo.name.section('.', -1)).lower());
+
+ if (info.file.fields & GP_FILE_INFO_MTIME)
+ itemInfo.mtime = info.file.mtime;
+
+ if (info.file.fields & GP_FILE_INFO_SIZE)
+ itemInfo.size = info.file.size;
+
+ if (info.file.fields & GP_FILE_INFO_WIDTH)
+ itemInfo.width = info.file.width;
+
+ if (info.file.fields & GP_FILE_INFO_HEIGHT)
+ itemInfo.height = info.file.height;
+
+ if (info.file.fields & GP_FILE_INFO_STATUS)
+ {
+ if (info.file.status == GP_FILE_STATUS_DOWNLOADED)
+ itemInfo.downloaded = GPItemInfo::DownloadedYes;
+ }
+
+ if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
+ {
+ if (info.file.permissions & GP_FILE_PERM_READ)
+ itemInfo.readPermissions = 1;
+ else
+ itemInfo.readPermissions = 0;
+ if (info.file.permissions & GP_FILE_PERM_DELETE)
+ itemInfo.writePermissions = 1;
+ else
+ itemInfo.writePermissions = 0;
+ }
+
+ items.append(itemInfo);
+ }
+
+ gp_list_unref(clist);
+
+ delete m_status;
+ m_status = 0;
+
+ return true;
+}
+
+bool GPCamera::getThumbnail(const TQString& folder, const TQString& itemName, TQImage& thumbnail)
+{
+ int errorCode;
+ CameraFile *cfile;
+ const char *data;
+ unsigned long int size;
+
+ gp_file_new(&cfile);
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_file_get(d->camera, TQFile::encodeName(folder),
+ TQFile::encodeName(itemName),
+ GP_FILE_TYPE_PREVIEW,
+ cfile, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get camera item!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+
+ errorCode = gp_file_get_data_and_size(cfile, &data, &size);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get thumbnail from camera item!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ return false;
+ }
+
+ thumbnail.loadFromData((const uchar*) data, (uint) size);
+
+ gp_file_unref(cfile);
+ return true;
+}
+
+bool GPCamera::getExif(const TQString& folder, const TQString& itemName,
+ char **edata, int& esize)
+{
+ int errorCode;
+ CameraFile *cfile;
+ const char *data;
+ unsigned long int size;
+
+ gp_file_new(&cfile);
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_file_get(d->camera, TQFile::encodeName(folder),
+ TQFile::encodeName(itemName),
+ GP_FILE_TYPE_EXIF,
+ cfile, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get camera item!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+
+ errorCode = gp_file_get_data_and_size(cfile, &data, &size);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get Exif data from camera item!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ return false;
+ }
+
+ *edata = new char[size];
+ esize = size;
+ memcpy(*edata, data, size);
+
+ gp_file_unref(cfile);
+ return true;
+}
+
+bool GPCamera::downloadItem(const TQString& folder, const TQString& itemName,
+ const TQString& saveFile)
+{
+ int errorCode;
+ CameraFile *cfile;
+
+ gp_file_new(&cfile);
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_file_get(d->camera, TQFile::encodeName(folder),
+ TQFile::encodeName(itemName),
+ GP_FILE_TYPE_NORMAL, cfile,
+ m_status->context);
+ if ( errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get camera item!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+
+ errorCode = gp_file_save(cfile, TQFile::encodeName(saveFile));
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to save camera item!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ return false;
+ }
+
+ gp_file_unref(cfile);
+ return true;
+}
+
+bool GPCamera::setLockItem(const TQString& folder, const TQString& itemName, bool lock)
+{
+ int errorCode;
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ CameraFileInfo info;
+ errorCode = gp_camera_file_get_info(d->camera, TQFile::encodeName(folder),
+ TQFile::encodeName(itemName), &info, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get camera item properties!" << endl;
+ printGphotoErrorDescription(errorCode);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
+ {
+ if (lock)
+ {
+ // Lock the file to set read only flag
+ info.file.permissions = (CameraFilePermissions)GP_FILE_PERM_READ;
+ }
+ else
+ {
+ // Unlock the file to set read/write flag
+ info.file.permissions = (CameraFilePermissions)(GP_FILE_PERM_READ | GP_FILE_PERM_DELETE);
+ }
+ }
+
+ // Some gphoto2 drivers need to have only the right flag at on to process properties update in camera.
+ info.file.fields = GP_FILE_INFO_PERMISSIONS;
+ info.preview.fields = GP_FILE_INFO_NONE;
+ info.audio.fields = GP_FILE_INFO_NONE;
+
+ errorCode = gp_camera_file_set_info(d->camera, TQFile::encodeName(folder),
+ TQFile::encodeName(itemName), info, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to set camera item lock properties!" << endl;
+ printGphotoErrorDescription(errorCode);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+ return true;
+}
+
+bool GPCamera::deleteItem(const TQString& folder, const TQString& itemName)
+{
+ int errorCode;
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_file_delete(d->camera, TQFile::encodeName(folder),
+ TQFile::encodeName(itemName),
+ m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to delete camera item!" << endl;
+ printGphotoErrorDescription(errorCode);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+
+ return true;
+}
+
+// recursively delete all items
+bool GPCamera::deleteAllItems(const TQString& folder)
+{
+ int errorCode;
+ TQStringList folderList;
+
+ // Get all subfolders in this folder
+ getSubFolders(folder, folderList);
+
+ if (folderList.count() > 0)
+ {
+ for (unsigned int i = 0 ; i < folderList.count() ; i++)
+ {
+ TQString subFolder(folder);
+
+ if (!subFolder.endsWith("/"))
+ subFolder += '/';
+
+ subFolder += folderList[i];
+ deleteAllItems(subFolder);
+ }
+ }
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_folder_delete_all(d->camera, TQFile::encodeName(folder),
+ m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to delete camera folder!" << endl;
+ printGphotoErrorDescription(errorCode);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ delete m_status;
+ m_status = 0;
+
+ return true;
+}
+
+bool GPCamera::uploadItem(const TQString& folder, const TQString& itemName, const TQString& localFile,
+ GPItemInfo& itemInfo, bool /*getImageDimensions*/)
+{
+ int errorCode;
+ CameraFile *cfile;
+
+ errorCode = gp_file_new(&cfile);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to init new camera file instance!" << endl;
+ printGphotoErrorDescription(errorCode);
+ return false;
+ }
+
+ errorCode = gp_file_open(cfile, TQFile::encodeName(localFile));
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to open file!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ return false;
+ }
+
+ errorCode = gp_file_set_name(cfile, TQFile::encodeName(itemName));
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to rename item from camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ return false;
+ }
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+#ifdef HAVE_GPHOTO25
+ errorCode = gp_camera_folder_put_file(d->camera,
+ TQFile::encodeName(folder),
+ TQFile::encodeName(itemName),
+ GP_FILE_TYPE_NORMAL,
+ cfile,
+ m_status->context);
+#else
+ errorCode = gp_camera_folder_put_file(d->camera,
+ TQFile::encodeName(folder),
+ cfile,
+ m_status->context);
+#endif
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to upload item to camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ // Get new camera item information.
+
+ itemInfo.name = itemName;
+ itemInfo.folder = folder;
+
+ CameraFileInfo info;
+ errorCode = gp_camera_file_get_info(d->camera, TQFile::encodeName(folder),
+ TQFile::encodeName(itemName), &info, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get camera item information!" << endl;
+ printGphotoErrorDescription(errorCode);
+ gp_file_unref(cfile);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ itemInfo.mtime = -1;
+ itemInfo.mime = "";
+ itemInfo.size = -1;
+ itemInfo.width = -1;
+ itemInfo.height = -1;
+ itemInfo.downloaded = GPItemInfo::DownloadUnknow;
+ itemInfo.readPermissions = -1;
+ itemInfo.writePermissions = -1;
+
+ /* The mime type returned by Gphoto2 is dummy with all RAW files.
+ if (info.file.fields & GP_FILE_INFO_TYPE)
+ itemInfo.mime = info.file.type;*/
+
+ itemInfo.mime = mimeType(TQString(itemInfo.name.section('.', -1)).lower());
+
+ if (info.file.fields & GP_FILE_INFO_MTIME)
+ itemInfo.mtime = info.file.mtime;
+
+ if (info.file.fields & GP_FILE_INFO_SIZE)
+ itemInfo.size = info.file.size;
+
+ if (info.file.fields & GP_FILE_INFO_WIDTH)
+ itemInfo.width = info.file.width;
+
+ if (info.file.fields & GP_FILE_INFO_HEIGHT)
+ itemInfo.height = info.file.height;
+
+ if (info.file.fields & GP_FILE_INFO_STATUS)
+ {
+ if (info.file.status == GP_FILE_STATUS_DOWNLOADED)
+ itemInfo.downloaded = GPItemInfo::DownloadedYes;
+ else
+ itemInfo.downloaded = GPItemInfo::DownloadedNo;
+ }
+
+ if (info.file.fields & GP_FILE_INFO_PERMISSIONS)
+ {
+ if (info.file.permissions & GP_FILE_PERM_READ)
+ itemInfo.readPermissions = 1;
+ else
+ itemInfo.readPermissions = 0;
+ if (info.file.permissions & GP_FILE_PERM_DELETE)
+ itemInfo.writePermissions = 1;
+ else
+ itemInfo.writePermissions = 0;
+ }
+
+ gp_file_unref(cfile);
+ delete m_status;
+ m_status = 0;
+ return true;
+}
+
+bool GPCamera::cameraSummary(TQString& summary)
+{
+ int errorCode;
+ CameraText sum;
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_get_summary(d->camera, &sum, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get camera summary!" << endl;
+ printGphotoErrorDescription(errorCode);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ summary = i18n("Title: %1\n"
+ "Model: %2\n"
+ "Port: %3\n"
+ "Path: %4\n\n"
+ "Thumbnails: %5\n"
+ "Delete items: %6\n"
+ "Upload items: %7\n"
+ "Create directories: %8\n"
+ "Delete directories: %9\n\n")
+ .arg(title())
+ .arg(model())
+ .arg(port())
+ .arg(path())
+ .arg(thumbnailSupport() ? i18n("yes") : i18n("no"))
+ .arg(deleteSupport() ? i18n("yes") : i18n("no"))
+ .arg(uploadSupport() ? i18n("yes") : i18n("no"))
+ .arg(mkDirSupport() ? i18n("yes") : i18n("no"))
+ .arg(delDirSupport() ? i18n("yes") : i18n("no"));
+
+ summary.append(TQString(sum.text));
+
+ delete m_status;
+ m_status = 0;
+ return true;
+}
+
+bool GPCamera::cameraManual(TQString& manual)
+{
+ int errorCode;
+ CameraText man;
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_get_manual(d->camera, &man, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get camera manual!" << endl;
+ printGphotoErrorDescription(errorCode);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ manual = TQString(man.text);
+
+ delete m_status;
+ m_status = 0;
+ return true;
+}
+
+bool GPCamera::cameraAbout(TQString& about)
+{
+ int errorCode;
+ CameraText abt;
+
+ if (m_status)
+ {
+ delete m_status;
+ m_status = 0;
+ }
+
+ m_status = new GPStatus;
+
+ errorCode = gp_camera_get_about(d->camera, &abt, m_status->context);
+ if (errorCode != GP_OK)
+ {
+ DDebug() << "Failed to get information about camera!" << endl;
+ printGphotoErrorDescription(errorCode);
+ delete m_status;
+ m_status = 0;
+ return false;
+ }
+
+ about = TQString(abt.text);
+ about.append(i18n("\n\nTo report problems about this driver, please contact "
+ "the gphoto2 team at:\n\nhttp://gphoto.org/bugs"));
+
+ delete m_status;
+ m_status = 0;
+ return true;
+}
+
+// -- Static methods ---------------------------------------------------------------------
+
+void GPCamera::printGphotoErrorDescription(int errorCode)
+{
+ DDebug() << "Libgphoto2 error: " << gp_result_as_string(errorCode)
+ << " (" << errorCode << ")" << endl;
+}
+
+void GPCamera::getSupportedCameras(int& count, TQStringList& clist)
+{
+ clist.clear();
+ count = 0;
+
+ CameraAbilitiesList *abilList;
+ CameraAbilities abil;
+ GPContext *context;
+
+ context = gp_context_new();
+
+ gp_abilities_list_new( &abilList );
+ gp_abilities_list_load( abilList, context );
+
+ count = gp_abilities_list_count( abilList );
+ if ( count < 0 )
+ {
+ DDebug() << "Failed to get list of cameras!" << endl;
+ printGphotoErrorDescription(count);
+ gp_context_unref( context );
+ return;
+ }
+ else
+ {
+ for (int i = 0 ; i < count ; i++)
+ {
+ gp_abilities_list_get_abilities( abilList, i, &abil );
+ const char *cname = abil.model;
+ clist.append( TQString( cname ) );
+ }
+ }
+
+ gp_abilities_list_free( abilList );
+ gp_context_unref( context );
+}
+
+void GPCamera::getSupportedPorts(TQStringList& plist)
+{
+ GPPortInfoList *list;
+ GPPortInfo info;
+
+ plist.clear();
+
+ gp_port_info_list_new( &list );
+ gp_port_info_list_load( list );
+
+ int numPorts = gp_port_info_list_count( list );
+ if ( numPorts < 0)
+ {
+ DDebug() << "Failed to get list of port!" << endl;
+ printGphotoErrorDescription(numPorts);
+ gp_port_info_list_free( list );
+ return;
+ }
+ else
+ {
+ for (int i = 0 ; i < numPorts ; i++)
+ {
+ gp_port_info_list_get_info( list, i, &info );
+#ifdef HAVE_GPHOTO25
+ char *xpath;
+ gp_port_info_get_name( info, &xpath );
+ plist.append( xpath );
+#else
+ plist.append( info.path );
+#endif
+ }
+ }
+
+ gp_port_info_list_free( list );
+}
+
+void GPCamera::getCameraSupportedPorts(const TQString& model, TQStringList& plist)
+{
+ int i = 0;
+ plist.clear();
+
+ CameraAbilities abilities;
+ CameraAbilitiesList *abilList;
+ GPContext *context;
+
+ context = gp_context_new();
+
+ gp_abilities_list_new (&abilList);
+ gp_abilities_list_load (abilList, context);
+ i = gp_abilities_list_lookup_model (abilList, model.local8Bit().data());
+ gp_abilities_list_get_abilities (abilList, i, &abilities);
+ gp_abilities_list_free (abilList);
+
+ if (abilities.port & GP_PORT_SERIAL)
+ plist.append("serial");
+
+ if (abilities.port & GP_PORT_USB)
+ plist.append("usb");
+
+ gp_context_unref( context );
+}
+
+int GPCamera::autoDetect(TQString& model, TQString& port)
+{
+ CameraList *camList;
+ CameraAbilitiesList *abilList;
+ GPPortInfoList *infoList;
+ const char *camModel_, *camPort_;
+ GPContext *context;
+
+ context = gp_context_new();
+ gp_list_new(&camList);
+
+ gp_abilities_list_new(&abilList);
+ gp_abilities_list_load(abilList, context);
+ gp_port_info_list_new(&infoList);
+ gp_port_info_list_load(infoList);
+ gp_abilities_list_detect(abilList, infoList, camList, context);
+ gp_abilities_list_free(abilList);
+ gp_port_info_list_free(infoList);
+
+ gp_context_unref(context);
+
+ int count = gp_list_count(camList);
+
+ if (count <= 0)
+ {
+ DDebug() << "Failed to autodetect camera!" << endl;
+ printGphotoErrorDescription(count);
+ gp_list_free(camList);
+ return -1;
+ }
+
+ camModel_ = 0;
+ camPort_ = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (gp_list_get_name(camList, i, &camModel_) != GP_OK)
+ {
+ DDebug() << "Failed to autodetect camera!" << endl;
+ gp_list_free(camList);
+ return -1;
+ }
+
+ if (gp_list_get_value(camList, i, &camPort_) != GP_OK)
+ {
+ DDebug() << "Failed to autodetect camera!" << endl;
+ gp_list_free(camList);
+ return -1;
+ }
+
+ if (camModel_ && camPort_)
+ {
+ model = TQString::fromLatin1(camModel_);
+ port = TQString::fromLatin1(camPort_);
+ gp_list_free(camList);
+ return 0;
+ }
+ }
+
+ DDebug() << "Failed to autodetect camera!" << endl;
+ gp_list_free(camList);
+ return -1;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/gpcamera.h b/src/utilities/cameragui/gpcamera.h
new file mode 100644
index 00000000..7071e972
--- /dev/null
+++ b/src/utilities/cameragui/gpcamera.h
@@ -0,0 +1,107 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-21
+ * Description : Gphoto2 camera interface
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef GPCAMERA_H
+#define GPCAMERA_H
+
+// Local includes.
+
+#include "dkcamera.h"
+
+class TQImage;
+
+namespace Digikam
+{
+
+class GPCameraPrivate;
+class GPStatus;
+
+// Gphoto2 camera Implementation of abstract type DKCamera
+
+class GPCamera : public DKCamera
+{
+
+public:
+
+ GPCamera(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path);
+ ~GPCamera();
+
+ bool thumbnailSupport();
+ bool deleteSupport();
+ bool uploadSupport();
+ bool mkDirSupport();
+ bool delDirSupport();
+
+ bool doConnect();
+
+ void cancel();
+
+ void getAllFolders(const TQString& folder, TQStringList& subFolderList);
+ bool getSubFolders(const TQString& folder, TQStringList& subFolderList);
+ bool getItemsList(const TQString& folder, TQStringList& itemsList);
+ bool getItemsInfoList(const TQString& folder, GPItemInfoList& items, bool getImageDimensions = true);
+ bool getThumbnail(const TQString& folder, const TQString& itemName, TQImage& thumbnail);
+ bool getExif(const TQString& folder, const TQString& itemName, char **edata, int& esize);
+
+ bool setLockItem(const TQString& folder, const TQString& itemName, bool lock);
+
+ bool downloadItem(const TQString& folder, const TQString& itemName, const TQString& saveFile);
+ bool deleteItem(const TQString& folder, const TQString& itemName);
+
+ // recursively delete all items
+ bool deleteAllItems(const TQString& folder);
+
+ bool uploadItem(const TQString& folder, const TQString& itemName, const TQString& localFile,
+ GPItemInfo& itemInfo, bool getImageDimensions=true);
+
+ bool cameraSummary(TQString& summary);
+ bool cameraManual(TQString& manual);
+ bool cameraAbout(TQString& about);
+
+ TQString model() const;
+ TQString port() const;
+ TQString path() const;
+
+ // Public static methods shared with Camera Setup
+
+ static int autoDetect(TQString& model, TQString& port);
+ static void getSupportedCameras(int& count, TQStringList& clist);
+ static void getSupportedPorts(TQStringList& plist);
+ static void getCameraSupportedPorts(const TQString& model, TQStringList& plist);
+
+private:
+
+ int setup();
+ static void printGphotoErrorDescription(int errorCode);
+
+private:
+
+ GPCameraPrivate *d;
+ GPStatus *m_status;
+};
+
+} // namespace Digikam
+
+#endif /* GPCAMERA_H */
diff --git a/src/utilities/cameragui/gpiteminfo.cpp b/src/utilities/cameragui/gpiteminfo.cpp
new file mode 100644
index 00000000..3a1d53e1
--- /dev/null
+++ b/src/utilities/cameragui/gpiteminfo.cpp
@@ -0,0 +1,68 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-19
+ * Description : camera item info container
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatastream.h>
+
+// Local includes.
+
+#include "gpiteminfo.h"
+
+namespace Digikam
+{
+
+TQDataStream& operator<<( TQDataStream& ds, const GPItemInfo& info)
+{
+ ds << info.name;
+ ds << info.folder;
+ ds << info.mtime;
+ ds << info.mime;
+ ds << info.size;
+ ds << info.width;
+ ds << info.height;
+ ds << info.downloaded;
+ ds << info.readPermissions;
+ ds << info.writePermissions;
+
+ return ds;
+}
+
+TQDataStream& operator>>(TQDataStream& ds, GPItemInfo& info)
+{
+ ds >> info.name;
+ ds >> info.folder;
+ ds >> info.mtime;
+ ds >> info.mime;
+ ds >> info.size;
+ ds >> info.width;
+ ds >> info.height;
+ ds >> info.downloaded;
+ ds >> info.readPermissions;
+ ds >> info.writePermissions;
+
+ return ds;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/gpiteminfo.h b/src/utilities/cameragui/gpiteminfo.h
new file mode 100644
index 00000000..02b28693
--- /dev/null
+++ b/src/utilities/cameragui/gpiteminfo.h
@@ -0,0 +1,80 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-18
+ * Description : camera item info container
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef GPITEMINFO_H
+#define GPITEMINFO_H
+
+// C++ includes.
+
+#include <ctime>
+
+// TQt includes.
+
+#include <tqvaluelist.h>
+#include <tqcstring.h>
+
+class TQDataStream;
+
+namespace Digikam
+{
+
+class GPItemInfo
+{
+
+public:
+
+ enum DownloadStatus
+ {
+ DownloadUnknow = -1,
+ DownloadedNo = 0,
+ DownloadedYes = 1,
+ DownloadFailed = 2,
+ DownloadStarted = 3,
+ NewPicture = 4
+ };
+
+public:
+
+ long size;
+ int width;
+ int height;
+ int downloaded; // See DownloadStatus enum.
+ int readPermissions;
+ int writePermissions;
+
+ TQString name;
+ TQString folder;
+ TQString mime;
+
+ time_t mtime;
+};
+
+TQDataStream& operator<<( TQDataStream &, const GPItemInfo & );
+TQDataStream& operator>>( TQDataStream &, GPItemInfo & );
+
+typedef TQValueList<GPItemInfo> GPItemInfoList;
+
+} // namespace Digikam
+
+#endif /* GPITEMINFO_H */
diff --git a/src/utilities/cameragui/mtqueue.h b/src/utilities/cameragui/mtqueue.h
new file mode 100644
index 00000000..0fb80e5c
--- /dev/null
+++ b/src/utilities/cameragui/mtqueue.h
@@ -0,0 +1,116 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-30
+ * Description : camera download multi-threading handler.
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef COMMAND_QUEUE_H
+#define COMMAND_QUEUE_H
+
+// TQt includes.
+
+#include <tqptrqueue.h>
+#include <tqmutex.h>
+
+namespace Digikam
+{
+
+template<class Type> class MTQueue
+{
+
+public:
+
+ MTQueue()
+ {
+ queue_.setAutoDelete(true);
+ }
+
+ ~MTQueue()
+ {
+ flush();
+ }
+
+ bool isEmpty()
+ {
+ mutex_.lock();
+ bool empty = queue_.isEmpty();
+ mutex_.unlock();
+ return empty;
+ }
+
+ void flush()
+ {
+ mutex_.lock();
+ queue_.clear();
+ mutex_.unlock();
+ }
+
+ void enqueue(Type * t)
+ {
+ mutex_.lock();
+ queue_.enqueue(t);
+ mutex_.unlock();
+ }
+
+ Type * dequeue()
+ {
+ mutex_.lock();
+ Type * i = queue_.dequeue();
+ mutex_.unlock();
+ return i;
+ }
+
+ Type * head(bool lock=true)
+ {
+ if (lock)
+ mutex_.lock();
+ Type * i = queue_.head();
+ if (lock)
+ mutex_.unlock();
+ return i;
+ }
+
+ int count()
+ {
+ mutex_.lock();
+ int c = queue_.count();
+ mutex_.unlock();
+ return c;
+ }
+
+ void lock()
+ {
+ mutex_.lock();
+ }
+
+ void unlock()
+ {
+ mutex_.unlock();
+ }
+
+private:
+
+ TQPtrQueue<Type> queue_;
+ TQMutex mutex_;
+};
+
+} // namespace Digikam
+
+#endif // COMMAND_QUEUE_H
diff --git a/src/utilities/cameragui/renamecustomizer.cpp b/src/utilities/cameragui/renamecustomizer.cpp
new file mode 100644
index 00000000..a906ccb1
--- /dev/null
+++ b/src/utilities/cameragui/renamecustomizer.cpp
@@ -0,0 +1,532 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-19
+ * Description : a options group to set renaming files
+ * operations during camera downloading
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdatetime.h>
+#include <tqlayout.h>
+#include <tqradiobutton.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqhbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqtimer.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <klineedit.h>
+#include <knuminput.h>
+#include <kdialogbase.h>
+#if KDE_IS_VERSION(3,2,0)
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// Local includes.
+
+#include "renamecustomizer.h"
+#include "renamecustomizer.moc"
+
+namespace Digikam
+{
+
+class RenameCustomizerPriv
+{
+public:
+
+ enum DateFormatOptions
+ {
+ DigikamStandard = 0,
+ IsoDateFormat,
+ TextDateFormat,
+ LocalDateFormat,
+ Advanced
+ };
+
+ RenameCustomizerPriv()
+ {
+ renameDefault = 0;
+ renameCustom = 0;
+ renameDefaultBox = 0;
+ renameCustomBox = 0;
+ renameDefaultCase = 0;
+ renameDefaultCaseType = 0;
+ addDateTimeBox = 0;
+ addCameraNameBox = 0;
+ addSeqNumberBox = 0;
+ changedTimer = 0;
+ renameCustomPrefix = 0;
+ renameCustomSuffix = 0;
+ startIndexLabel = 0;
+ startIndexInput = 0;
+ focusedWidget = 0;
+ dateTimeButton = 0;
+ dateTimeLabel = 0;
+ dateTimeFormat = 0;
+}
+
+ TQWidget *focusedWidget;
+
+ TQString cameraTitle;
+
+ TQRadioButton *renameDefault;
+ TQRadioButton *renameCustom;
+
+ TQGroupBox *renameDefaultBox;
+ TQGroupBox *renameCustomBox;
+
+ TQLabel *renameDefaultCase;
+ TQLabel *startIndexLabel;
+ TQLabel *dateTimeLabel;
+
+ TQComboBox *renameDefaultCaseType;
+ TQComboBox *dateTimeFormat;
+
+ TQCheckBox *addDateTimeBox;
+ TQCheckBox *addCameraNameBox;
+ TQCheckBox *addSeqNumberBox;
+
+ TQPushButton *dateTimeButton;
+ TQString dateTimeFormatString;
+
+ TQTimer *changedTimer;
+
+ KLineEdit *renameCustomPrefix;
+ KLineEdit *renameCustomSuffix;
+
+ KIntNumInput *startIndexInput;
+};
+
+RenameCustomizer::RenameCustomizer(TQWidget* parent, const TQString& cameraTitle)
+ : TQButtonGroup(parent)
+{
+ d = new RenameCustomizerPriv;
+ d->changedTimer = new TQTimer(this);
+ d->cameraTitle = cameraTitle;
+
+ setFrameStyle( TQFrame::NoFrame );
+ setRadioButtonExclusive(true);
+ setColumnLayout(0, TQt::Vertical);
+ TQGridLayout* mainLayout = new TQGridLayout(layout(), 4, 1);
+
+ // ----------------------------------------------------------------
+
+ d->renameDefault = new TQRadioButton(i18n("Camera filenames"), this);
+ TQWhatsThis::add( d->renameDefault, i18n("<p>Turn on this option to use camera "
+ "provided image filenames without modifications."));
+ mainLayout->addMultiCellWidget(d->renameDefault, 0, 0, 0, 1);
+
+ d->renameDefaultBox = new TQGroupBox( this );
+ d->renameDefaultBox->setFrameStyle(TQFrame::NoFrame|TQFrame::Plain);
+ d->renameDefaultBox->setInsideMargin(0);
+ d->renameDefaultBox->setColumnLayout(0, TQt::Vertical);
+
+ d->renameDefaultCase = new TQLabel( i18n("Change case to:"), d->renameDefaultBox );
+ d->renameDefaultCase->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Preferred );
+
+ d->renameDefaultCaseType = new TQComboBox( d->renameDefaultBox );
+ d->renameDefaultCaseType->insertItem(i18n("Leave as Is"), 0);
+ d->renameDefaultCaseType->insertItem(i18n("Upper"), 1);
+ d->renameDefaultCaseType->insertItem(i18n("Lower"), 2);
+ d->renameDefaultCaseType->setSizePolicy(TQSizePolicy::Minimum, TQSizePolicy::Preferred);
+ TQWhatsThis::add( d->renameDefaultCaseType, i18n("<p>Set the method to use to change the case "
+ "of image filenames."));
+
+ TQHBoxLayout* boxLayout1 = new TQHBoxLayout( d->renameDefaultBox->layout() );
+ boxLayout1->addSpacing( 10 );
+ boxLayout1->addWidget( d->renameDefaultCase );
+ boxLayout1->addWidget( d->renameDefaultCaseType );
+
+ mainLayout->addMultiCellWidget(d->renameDefaultBox, 1, 1, 0, 1);
+
+ // -------------------------------------------------------------
+
+ d->renameCustom = new TQRadioButton(i18n("Customize"), this);
+ mainLayout->addMultiCellWidget(d->renameCustom, 2, 2, 0, 1);
+ TQWhatsThis::add( d->renameCustom, i18n("<p>Turn on this option to customize image filenames "
+ "during download."));
+
+ d->renameCustomBox = new TQGroupBox(this);
+ d->renameCustomBox->setFrameStyle(TQFrame::NoFrame|TQFrame::Plain);
+ d->renameCustomBox->setInsideMargin(0);
+ d->renameCustomBox->setColumnLayout(0, TQt::Vertical);
+
+ TQGridLayout* renameCustomBoxLayout = new TQGridLayout(d->renameCustomBox->layout(),
+ 6, 2, KDialogBase::spacingHint());
+ renameCustomBoxLayout->setColSpacing( 0, 10 );
+
+ TQLabel* prefixLabel = new TQLabel(i18n("Prefix:"), d->renameCustomBox);
+ renameCustomBoxLayout->addMultiCellWidget(prefixLabel, 0, 0, 1, 1);
+ d->renameCustomPrefix = new KLineEdit(d->renameCustomBox);
+ d->focusedWidget = d->renameCustomPrefix;
+ renameCustomBoxLayout->addMultiCellWidget(d->renameCustomPrefix, 0, 0, 2, 2);
+ TQWhatsThis::add( d->renameCustomPrefix, i18n("<p>Set the prefix which will be added to "
+ "image filenames."));
+
+ TQLabel* suffixLabel = new TQLabel(i18n("Suffix:"), d->renameCustomBox);
+ renameCustomBoxLayout->addMultiCellWidget(suffixLabel, 1, 1, 1, 1);
+ d->renameCustomSuffix = new KLineEdit(d->renameCustomBox);
+ renameCustomBoxLayout->addMultiCellWidget(d->renameCustomSuffix, 1, 1, 2, 2);
+ TQWhatsThis::add( d->renameCustomSuffix, i18n("<p>Set the suffix which will be added to "
+ "image filenames."));
+
+ d->addDateTimeBox = new TQCheckBox( i18n("Add Date && Time"), d->renameCustomBox );
+ renameCustomBoxLayout->addMultiCellWidget(d->addDateTimeBox, 2, 2, 1, 2);
+ TQWhatsThis::add( d->addDateTimeBox, i18n("<p>Set this option to add the camera provided date and time."));
+
+ TQWidget *dateTimeWidget = new TQWidget(d->renameCustomBox);
+ d->dateTimeLabel = new TQLabel(i18n("Date format:"), dateTimeWidget);
+ d->dateTimeFormat = new TQComboBox(dateTimeWidget);
+ d->dateTimeFormat->insertItem(i18n("Standard"), RenameCustomizerPriv::DigikamStandard);
+ d->dateTimeFormat->insertItem(i18n("ISO"), RenameCustomizerPriv::IsoDateFormat);
+ d->dateTimeFormat->insertItem(i18n("Full Text"), RenameCustomizerPriv::TextDateFormat);
+ d->dateTimeFormat->insertItem(i18n("Local Settings"), RenameCustomizerPriv::LocalDateFormat);
+ d->dateTimeFormat->insertItem(i18n("Advanced..."), RenameCustomizerPriv::Advanced);
+ TQWhatsThis::add( d->dateTimeFormat, i18n("<p>Select your preferred date format for "
+ "creating new albums. The options available are:</p>"
+ "<p><b>Standard</b>: the date format that has been used as a standard by digiKam. "
+ "E.g.: <i>20060824T142618</i></p>"
+ "<p/><b>ISO</b>: the date format according to ISO 8601 "
+ "(YYYY-MM-DD). E.g.: <i>2006-08-24T14:26:18</i></p>"
+ "<p><b>Full Text</b>: the date format is a user-readable string. "
+ "E.g.: <i>Thu Aug 24 14:26:18 2006</i></p>"
+ "<p><b>Local Settings</b>: the date format depending on TDE control panel settings.</p>"
+ "<p><b>Advanced:</b> allows the user to specify a custom date format.</p>"));
+ d->dateTimeButton = new TQPushButton(SmallIcon("configure"), TQString(), dateTimeWidget);
+ TQSizePolicy policy = d->dateTimeButton->sizePolicy();
+ policy.setHorData(TQSizePolicy::Maximum);
+ d->dateTimeButton->setSizePolicy(policy);
+ TQHBoxLayout *boxLayout2 = new TQHBoxLayout(dateTimeWidget);
+ boxLayout2->addWidget(d->dateTimeLabel);
+ boxLayout2->addWidget(d->dateTimeFormat);
+ boxLayout2->addWidget(d->dateTimeButton);
+ renameCustomBoxLayout->addMultiCellWidget(dateTimeWidget, 3, 3, 1, 2);
+
+ d->addCameraNameBox = new TQCheckBox( i18n("Add Camera Name"), d->renameCustomBox );
+ renameCustomBoxLayout->addMultiCellWidget(d->addCameraNameBox, 4, 4, 1, 2);
+ TQWhatsThis::add( d->addCameraNameBox, i18n("<p>Set this option to add the camera name."));
+
+ d->addSeqNumberBox = new TQCheckBox( i18n("Add Sequence Number"), d->renameCustomBox );
+ renameCustomBoxLayout->addMultiCellWidget(d->addSeqNumberBox, 5, 5, 1, 2);
+ TQWhatsThis::add( d->addSeqNumberBox, i18n("<p>Set this option to add a sequence number "
+ "starting with the index set below."));
+
+ d->startIndexLabel = new TQLabel( i18n("Start Index:"), d->renameCustomBox );
+ d->startIndexInput = new KIntNumInput(1, d->renameCustomBox);
+ d->startIndexInput->setRange(1, 900000, 1, false);
+ TQWhatsThis::add( d->startIndexInput, i18n("<p>Set the starting index value used to rename "
+ "files with a sequence number."));
+
+ renameCustomBoxLayout->addMultiCellWidget(d->startIndexLabel, 6, 6, 1, 1);
+ renameCustomBoxLayout->addMultiCellWidget(d->startIndexInput, 6, 6, 2, 2);
+
+ mainLayout->addMultiCellWidget(d->renameCustomBox, 3, 3, 0, 1);
+ mainLayout->setRowStretch(4, 10);
+
+ // -- setup connections -------------------------------------------------
+
+ connect(this, TQ_SIGNAL(clicked(int)),
+ this, TQ_SLOT(slotRadioButtonClicked(int)));
+
+ connect(d->renameCustomPrefix, TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotRenameOptionsChanged()));
+
+ connect(d->renameCustomSuffix, TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotRenameOptionsChanged()));
+
+ connect(d->addDateTimeBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotRenameOptionsChanged()));
+
+ connect(d->addCameraNameBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotRenameOptionsChanged()));
+
+ connect(d->addSeqNumberBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotRenameOptionsChanged()));
+
+ connect(d->renameDefaultCaseType, TQ_SIGNAL(activated(const TQString&)),
+ this, TQ_SLOT(slotRenameOptionsChanged()));
+
+ connect(d->startIndexInput, TQ_SIGNAL(valueChanged (int)),
+ this, TQ_SLOT(slotRenameOptionsChanged()));
+
+ connect(d->changedTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SIGNAL(signalChanged()));
+
+ connect(d->dateTimeButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotDateTimeButtonClicked()));
+
+ connect(d->dateTimeFormat, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotDateTimeFormatChanged(int)));
+
+ connect(d->addDateTimeBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotDateTimeBoxToggled(bool)));
+
+ // -- initial values ---------------------------------------------------
+
+ readSettings();
+
+ // signal to this not yet connected when readSettings is called? Don't know
+ slotDateTimeBoxToggled(d->addDateTimeBox->isChecked());
+}
+
+RenameCustomizer::~RenameCustomizer()
+{
+ delete d->changedTimer;
+ saveSettings();
+ delete d;
+}
+
+bool RenameCustomizer::useDefault() const
+{
+ return d->renameDefault->isChecked();
+}
+
+int RenameCustomizer::startIndex() const
+{
+ return d->startIndexInput->value();
+}
+
+TQString RenameCustomizer::newName(const TQDateTime &dateTime, int index, const TQString &extension) const
+{
+ if (d->renameDefault->isChecked())
+ return TQString();
+ else
+ {
+ TQString name(d->renameCustomPrefix->text());
+
+ // use the "T" as a delimiter between date and time
+ TQString date;
+ switch (d->dateTimeFormat->currentItem())
+ {
+ case RenameCustomizerPriv::DigikamStandard:
+ date = dateTime.toString("yyyyMMddThhmmss");
+ break;
+ case RenameCustomizerPriv::TextDateFormat:
+ date = dateTime.toString(TQt::TextDate);
+ break;
+ case RenameCustomizerPriv::LocalDateFormat:
+ date = dateTime.toString(TQt::LocalDate);
+ break;
+ case RenameCustomizerPriv::IsoDateFormat:
+ date = dateTime.toString(TQt::ISODate);
+ break;
+ case RenameCustomizerPriv::Advanced:
+ date = dateTime.toString(d->dateTimeFormatString);
+ break;
+ }
+
+ // it seems that TQString::number does not support padding with zeros
+ TQString seq;
+ seq.sprintf("-%06d", index);
+
+ if (d->addDateTimeBox->isChecked())
+ name += date;
+
+ if (d->addSeqNumberBox->isChecked())
+ name += seq;
+
+ if (d->addCameraNameBox->isChecked())
+ name += TQString("-%1").arg(d->cameraTitle.simplifyWhiteSpace().replace(" ", ""));
+
+ name += d->renameCustomSuffix->text();
+ name += extension;
+
+ return name;
+ }
+}
+
+RenameCustomizer::Case RenameCustomizer::changeCase() const
+{
+ RenameCustomizer::Case type = NONE;
+
+ if (d->renameDefaultCaseType->currentItem() == 1)
+ type=UPPER;
+ if (d->renameDefaultCaseType->currentItem() == 2)
+ type=LOWER;
+
+ return type;
+}
+
+void RenameCustomizer::slotRadioButtonClicked(int)
+{
+ TQRadioButton* btn = dynamic_cast<TQRadioButton*>(selected());
+ if (!btn)
+ return;
+
+ d->renameCustomBox->setEnabled( btn != d->renameDefault );
+ d->renameDefaultBox->setEnabled( btn == d->renameDefault );
+ slotRenameOptionsChanged();
+}
+
+void RenameCustomizer::slotRenameOptionsChanged()
+{
+ d->focusedWidget = focusWidget();
+
+ if (d->addSeqNumberBox->isChecked())
+ {
+ d->startIndexInput->setEnabled(true);
+ d->startIndexLabel->setEnabled(true);
+ }
+ else
+ {
+ d->startIndexInput->setEnabled(false);
+ d->startIndexLabel->setEnabled(false);
+ }
+
+ d->changedTimer->start(500, true);
+}
+
+void RenameCustomizer::slotDateTimeBoxToggled(bool on)
+{
+ d->dateTimeLabel->setEnabled(on);
+ d->dateTimeFormat->setEnabled(on);
+ d->dateTimeButton->setEnabled(on
+ && d->dateTimeFormat->currentItem() == RenameCustomizerPriv::Advanced);
+ slotRenameOptionsChanged();
+}
+
+void RenameCustomizer::slotDateTimeFormatChanged(int index)
+{
+ if (index == RenameCustomizerPriv::Advanced)
+ {
+ d->dateTimeButton->setEnabled(true);
+ //d->dateTimeButton->show();
+ //slotDateTimeButtonClicked();
+ }
+ else
+ {
+ d->dateTimeButton->setEnabled(false);
+ //d->dateTimeButton->hide();
+ }
+ slotRenameOptionsChanged();
+}
+
+void RenameCustomizer::slotDateTimeButtonClicked()
+{
+ bool ok;
+ TQString message = i18n("<qt><p>Enter the format for date and time.</p>"
+ "<p>Use <i>dd</i> for the day, "
+ "<i>MM</i> for the month, "
+ "<i>yyyy</i> for the year, "
+ "<i>hh</i> for the hour, "
+ "<i>mm</i> for the minute, "
+ "<i>ss</i> for the second.</p>"
+ "<p>Examples: <i>yyyyMMddThhmmss</i> "
+ "for 20060824T142418,<br>"
+ "<i>yyyy-MM-dd hh:mm:ss</i> "
+ "for 2006-08-24 14:24:18.</p></qt>");
+
+#if KDE_IS_VERSION(3,2,0)
+ TQString newFormat = KInputDialog::getText(i18n("Change Date and Time Format"),
+ message,
+ d->dateTimeFormatString, &ok, this);
+#else
+ TQString newFormat = KLineEditDlg::getText(i18n("Change Date and Time Format"),
+ message,
+ d->dateTimeFormatString, &ok, this);
+#endif
+
+ if (!ok)
+ return;
+
+ d->dateTimeFormatString = newFormat;
+ slotRenameOptionsChanged();
+}
+
+void RenameCustomizer::readSettings()
+{
+ TDEConfig* config = kapp->config();
+
+ config->setGroup("Camera Settings");
+ bool def = config->readBoolEntry("Rename Use Default", true);
+ bool addSeqNumb = config->readBoolEntry("Add Sequence Number", true);
+ bool adddateTime = config->readBoolEntry("Add Date Time", false);
+ bool addCamName = config->readBoolEntry("Add Camera Name", false);
+ int chcaseT = config->readNumEntry("Case Type", NONE);
+ TQString prefix = config->readEntry("Rename Prefix", i18n("photo"));
+ TQString suffix = config->readEntry("Rename Postfix", TQString());
+ int startIndex = config->readNumEntry("Rename Start Index", 1);
+ int dateTime = config->readNumEntry("Date Time Format", RenameCustomizerPriv::IsoDateFormat);
+ TQString format = config->readEntry("Date Time Format String", "yyyyMMddThhmmss");
+
+ if (def)
+ {
+ d->renameDefault->setChecked(true);
+ d->renameCustom->setChecked(false);
+ d->renameCustomBox->setEnabled(false);
+ d->renameDefaultBox->setEnabled(true);
+ }
+ else
+ {
+ d->renameDefault->setChecked(false);
+ d->renameCustom->setChecked(true);
+ d->renameCustomBox->setEnabled(true);
+ d->renameDefaultBox->setEnabled(false);
+ }
+
+ d->addDateTimeBox->setChecked(adddateTime);
+ d->addCameraNameBox->setChecked(addCamName);
+ d->addSeqNumberBox->setChecked(addSeqNumb);
+ d->renameDefaultCaseType->setCurrentItem(chcaseT);
+ d->renameCustomPrefix->setText(prefix);
+ d->renameCustomSuffix->setText(suffix);
+ d->startIndexInput->setValue(startIndex);
+ d->dateTimeFormat->setCurrentItem(dateTime);
+ d->dateTimeFormatString = format;
+ slotRenameOptionsChanged();
+}
+
+void RenameCustomizer::saveSettings()
+{
+ TDEConfig* config = kapp->config();
+
+ config->setGroup("Camera Settings");
+ config->writeEntry("Rename Use Default", d->renameDefault->isChecked());
+ config->writeEntry("Add Camera Name", d->addCameraNameBox->isChecked());
+ config->writeEntry("Add Date Time", d->addDateTimeBox->isChecked());
+ config->writeEntry("Add Sequence Number", d->addSeqNumberBox->isChecked());
+ config->writeEntry("Case Type", d->renameDefaultCaseType->currentItem());
+ config->writeEntry("Rename Prefix", d->renameCustomPrefix->text());
+ config->writeEntry("Rename Suffix", d->renameCustomSuffix->text());
+ config->writeEntry("Rename Start Index", d->startIndexInput->value());
+ config->writeEntry("Date Time Format", d->dateTimeFormat->currentItem());
+ config->writeEntry("Date Time Format String", d->dateTimeFormatString);
+ config->sync();
+}
+
+void RenameCustomizer::restoreFocus()
+{
+ d->focusedWidget->setFocus();
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/cameragui/renamecustomizer.h b/src/utilities/cameragui/renamecustomizer.h
new file mode 100644
index 00000000..47ad925b
--- /dev/null
+++ b/src/utilities/cameragui/renamecustomizer.h
@@ -0,0 +1,91 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-09-19
+ * Description : a options group to set renaming files
+ * operations during camera downloading
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RENAMECUSTOMIZER_H
+#define RENAMECUSTOMIZER_H
+
+// TQt includes.
+
+#include <tqbuttongroup.h>
+
+class TQDateTime;
+
+namespace Digikam
+{
+
+class RenameCustomizerPriv;
+
+class RenameCustomizer : public TQButtonGroup
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum Case
+ {
+ NONE = 0,
+ UPPER,
+ LOWER
+ };
+
+ RenameCustomizer(TQWidget* parent, const TQString& cameraTitle);
+ ~RenameCustomizer();
+
+ void setUseDefault(bool val);
+ bool useDefault() const;
+ TQString newName(const TQDateTime &date, int index, const TQString &extension) const;
+ Case changeCase() const;
+ int startIndex() const;
+
+signals:
+
+ void signalChanged();
+
+public slots:
+
+ void restoreFocus();
+
+private:
+
+ void readSettings();
+ void saveSettings();
+
+private slots:
+
+ void slotRadioButtonClicked(int);
+ void slotRenameOptionsChanged();
+ void slotDateTimeBoxToggled(bool);
+ void slotDateTimeFormatChanged(int);
+ void slotDateTimeButtonClicked();
+
+private:
+
+ RenameCustomizerPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* RENAMECUSTOMIZER_H */
diff --git a/src/utilities/cameragui/umscamera.cpp b/src/utilities/cameragui/umscamera.cpp
new file mode 100644
index 00000000..537c29ba
--- /dev/null
+++ b/src/utilities/cameragui/umscamera.cpp
@@ -0,0 +1,519 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-21
+ * Description : USB Mass Storage camera interface
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utime.h>
+}
+
+// TQt includes.
+
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqfile.h>
+#include <tqstringlist.h>
+#include <tqdeepcopy.h>
+#include <tqwmatrix.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdefilemetainfo.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/kdcraw.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "dmetadata.h"
+#include "umscamera.h"
+
+namespace Digikam
+{
+
+UMSCamera::UMSCamera(const TQString& title, const TQString& model, const TQString& port, const TQString& path)
+ : DKCamera(title, model, port, path)
+{
+ m_cancel = false;
+}
+
+UMSCamera::~UMSCamera()
+{
+}
+
+bool UMSCamera::doConnect()
+{
+ return true;
+}
+
+void UMSCamera::cancel()
+{
+ // set the cancel flag
+ m_cancel = true;
+}
+
+void UMSCamera::getAllFolders(const TQString& folder, TQStringList& subFolderList)
+{
+ m_cancel = false;
+ subFolderList.clear();
+ subFolderList.append(folder);
+ listFolders(folder, subFolderList);
+}
+
+bool UMSCamera::getItemsInfoList(const TQString& folder, GPItemInfoList& infoList, bool getImageDimensions)
+{
+ m_cancel = false;
+ infoList.clear();
+
+ TQDir dir(folder);
+ dir.setFilter(TQDir::Files);
+
+ const TQFileInfoList *list = dir.entryInfoList();
+ if (!list)
+ return false;
+
+ TQFileInfoListIterator it(*list);
+ TQFileInfo *fi;
+ TQFileInfo thmlo, thmup;
+ DMetadata meta;
+
+ while ((fi = it.current()) != 0 && !m_cancel)
+ {
+ ++it;
+
+ TQString mime = mimeType(fi->extension(false).lower());
+
+ if (!mime.isEmpty())
+ {
+ TQSize dims;
+ TQDateTime dt;
+ GPItemInfo info;
+ thmlo.setFile(folder + TQString("/") + fi->baseName() + TQString(".thm"));
+ thmup.setFile(folder + TQString("/") + fi->baseName() + TQString(".THM"));
+
+ if (thmlo.exists())
+ {
+ // Try thumbnail sidecar files with lowercase extension.
+ meta.load(thmlo.filePath());
+ dt = meta.getImageDateTime();
+ dims = meta.getImageDimensions();
+ }
+ else if (thmup.exists())
+ {
+ // Try thumbnail sidecar files with uppercase extension.
+ meta.load(thmup.filePath());
+ dt = meta.getImageDateTime();
+ dims = meta.getImageDimensions();
+ }
+ else if (mime == TQString("image/x-raw"))
+ {
+ // If no thumbnail sidecar file available , try to load image metadata with Raw files.
+ meta.load(fi->filePath());
+ dt = meta.getImageDateTime();
+ dims = meta.getImageDimensions();
+ }
+ else
+ {
+ meta.load(fi->filePath());
+ dt = meta.getImageDateTime();
+ dims = meta.getImageDimensions();
+
+ if (dims.isNull())
+ {
+ // In all others case, try KFileMetaInfo.
+ KFileMetaInfo kmeta(fi->filePath());
+ if (kmeta.isValid())
+ {
+ if (kmeta.containsGroup("Jpeg EXIF Data"))
+ dims = kmeta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
+ else if (kmeta.containsGroup("General"))
+ dims = kmeta.group("General").item("Dimensions").value().toSize();
+ else if (kmeta.containsGroup("Technical"))
+ dims = kmeta.group("Technical").item("Dimensions").value().toSize();
+ }
+ }
+ }
+
+ if (dt.isNull())
+ {
+ // If date is not found in metadata, use file time stamp
+ dt = fi->created();
+ }
+
+ info.name = fi->fileName();
+ info.folder = !folder.endsWith("/") ? folder + TQString("/") : folder;
+ info.mime = mime;
+ info.mtime = dt.toTime_t();
+ info.size = fi->size();
+ info.width = getImageDimensions ? dims.width() : -1;
+ info.height = getImageDimensions ? dims.height() : -1;
+ info.downloaded = GPItemInfo::DownloadUnknow;
+ info.readPermissions = fi->isReadable();
+ info.writePermissions = fi->isWritable();
+
+ infoList.append(info);
+ }
+ }
+
+ return true;
+}
+
+bool UMSCamera::getThumbnail(const TQString& folder, const TQString& itemName, TQImage& thumbnail)
+{
+ m_cancel = false;
+
+ // JPEG files: try to get thumbnail from Exif data.
+
+ DMetadata metadata(TQFile::encodeName(folder + TQString("/") + itemName));
+ thumbnail = metadata.getExifThumbnail(true);
+ if (!thumbnail.isNull())
+ return true;
+
+ // RAW files : try to extract embedded thumbnail using dcraw
+
+ KDcrawIface::KDcraw::loadDcrawPreview(thumbnail, TQString(folder + TQString("/") + itemName));
+ if (!thumbnail.isNull())
+ return true;
+
+ // THM files: try to get thumbnail from '.thm' files if we didn't manage to get
+ // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW/video files.
+ // Using this way speed up thumbnailization and limit data transfered between camera and computer.
+ // NOTE: the thumbnail extracted with this method can provide a poor quality preview.
+
+ TQFileInfo fi(folder + TQString("/") + itemName);
+
+ if (thumbnail.load(folder + TQString("/") + fi.baseName() + TQString(".thm"))) // Lowercase
+ {
+ if (!thumbnail.isNull())
+ return true;
+ }
+ else if (thumbnail.load(folder + TQString("/") + fi.baseName() + TQString(".THM"))) // Uppercase
+ {
+ if (!thumbnail.isNull())
+ return true;
+ }
+
+ // Finaly, we trying to get thumbnail using DImg API (slow).
+
+ DImg dimgThumb(TQCString(TQFile::encodeName(folder + TQString("/") + itemName)));
+
+ if (!dimgThumb.isNull())
+ {
+ thumbnail = dimgThumb.copyTQImage();
+ return true;
+ }
+
+ return false;
+}
+
+bool UMSCamera::getExif(const TQString&, const TQString&, char **, int&)
+{
+ // not necessary to implement this. read it directly from the file
+ // (done in camera controller)
+ DWarning() << "exif implemented yet in camera controller" << endl;
+ return false;
+}
+
+bool UMSCamera::downloadItem(const TQString& folder, const TQString& itemName, const TQString& saveFile)
+{
+ m_cancel = false;
+ TQString src = folder + TQString("/") + itemName;
+ TQString dest = saveFile;
+
+ TQFile sFile(src);
+ TQFile dFile(dest);
+
+ if ( !sFile.open(IO_ReadOnly) )
+ {
+ DWarning() << "Failed to open source file for reading: "
+ << src << endl;
+ return false;
+ }
+
+ if ( !dFile.open(IO_WriteOnly) )
+ {
+ sFile.close();
+ DWarning() << "Failed to open dest file for writing: "
+ << dest << endl;
+ return false;
+ }
+
+ const int MAX_IPC_SIZE = (1024*32);
+ char buffer[MAX_IPC_SIZE];
+
+ TQ_LONG len;
+ while ((len = sFile.readBlock(buffer, MAX_IPC_SIZE)) != 0 && !m_cancel)
+ {
+ if (len == -1 || dFile.writeBlock(buffer, (TQ_ULONG)len) != len)
+ {
+ sFile.close();
+ dFile.close();
+ return false;
+ }
+ }
+
+ sFile.close();
+ dFile.close();
+
+ // set the file modification time of the downloaded file to that
+ // of the original file
+ struct stat st;
+ ::stat(TQFile::encodeName(src), &st);
+
+ struct utimbuf ut;
+ ut.modtime = st.st_mtime;
+ ut.actime = st.st_atime;
+
+ ::utime(TQFile::encodeName(dest), &ut);
+
+ return true;
+}
+
+bool UMSCamera::setLockItem(const TQString& folder, const TQString& itemName, bool lock)
+{
+ TQString src = folder + TQString("/") + itemName;
+
+ if (lock)
+ {
+ // Lock the file to set read only flag
+ if (::chmod(TQFile::encodeName(src), S_IREAD) == -1)
+ return false;
+ }
+ else
+ {
+ // Unlock the file to set read/write flag
+ if (::chmod(TQFile::encodeName(src), S_IREAD | S_IWRITE) == -1)
+ return false;
+ }
+
+ return true;
+}
+
+bool UMSCamera::deleteItem(const TQString& folder, const TQString& itemName)
+{
+ m_cancel = false;
+
+ // Any camera provide THM (thumbnail) file with real image. We need to remove it also.
+
+ TQFileInfo fi(folder + TQString("/") + itemName);
+
+ TQFileInfo thmLo(folder + TQString("/") + fi.baseName() + ".thm"); // Lowercase
+
+ if (thmLo.exists())
+ ::unlink(TQFile::encodeName(thmLo.filePath()));
+
+ TQFileInfo thmUp(folder + TQString("/") + fi.baseName() + ".THM"); // Uppercase
+
+ if (thmUp.exists())
+ ::unlink(TQFile::encodeName(thmUp.filePath()));
+
+ // Remove the real image.
+ return (::unlink(TQFile::encodeName(folder + TQString("/") + itemName)) == 0);
+}
+
+bool UMSCamera::uploadItem(const TQString& folder, const TQString& itemName, const TQString& localFile,
+ GPItemInfo& info, bool getImageDimensions)
+{
+ m_cancel = false;
+ TQString dest = folder + TQString("/") + itemName;
+ TQString src = localFile;
+
+ TQFile sFile(src);
+ TQFile dFile(dest);
+
+ if ( !sFile.open(IO_ReadOnly) )
+ {
+ DWarning() << "Failed to open source file for reading: "
+ << src << endl;
+ return false;
+ }
+
+ if ( !dFile.open(IO_WriteOnly) )
+ {
+ sFile.close();
+ DWarning() << "Failed to open dest file for writing: "
+ << dest << endl;
+ return false;
+ }
+
+ const int MAX_IPC_SIZE = (1024*32);
+ char buffer[MAX_IPC_SIZE];
+
+ TQ_LONG len;
+ while ((len = sFile.readBlock(buffer, MAX_IPC_SIZE)) != 0 && !m_cancel)
+ {
+ if (len == -1 || dFile.writeBlock(buffer, (TQ_ULONG)len) == -1)
+ {
+ sFile.close();
+ dFile.close();
+ return false;
+ }
+ }
+
+ sFile.close();
+ dFile.close();
+
+ // set the file modification time of the uploaded file to that
+ // of the original file
+ struct stat st;
+ ::stat(TQFile::encodeName(src), &st);
+
+ struct utimbuf ut;
+ ut.modtime = st.st_mtime;
+ ut.actime = st.st_atime;
+
+ ::utime(TQFile::encodeName(dest), &ut);
+
+ // Get new camera item information.
+
+ DMetadata meta;
+ TQFileInfo fi(dest);
+ TQString mime = mimeType(fi.extension(false).lower());
+
+ if (!mime.isEmpty())
+ {
+ TQSize dims;
+ TQDateTime dt;
+
+ if (mime == TQString("image/x-raw"))
+ {
+ // Try to load image metadata with Raw files.
+ meta.load(fi.filePath());
+ dt = meta.getImageDateTime();
+ dims = meta.getImageDimensions();
+ }
+ else
+ {
+ meta.load(fi.filePath());
+ dt = meta.getImageDateTime();
+ dims = meta.getImageDimensions();
+
+ if (dims.isNull())
+ {
+ // In all others case, try KFileMetaInfo.
+ KFileMetaInfo kmeta(fi.filePath());
+ if (kmeta.isValid())
+ {
+ if (kmeta.containsGroup("Jpeg EXIF Data"))
+ dims = kmeta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
+ else if (kmeta.containsGroup("General"))
+ dims = kmeta.group("General").item("Dimensions").value().toSize();
+ else if (kmeta.containsGroup("Technical"))
+ dims = kmeta.group("Technical").item("Dimensions").value().toSize();
+ }
+ }
+ }
+
+ if (dt.isNull())
+ {
+ // If date is not found in metadata, use file time stamp
+ dt = fi.created();
+ }
+
+ info.name = fi.fileName();
+ info.folder = !folder.endsWith("/") ? folder + TQString("/") : folder;
+ info.mime = mime;
+ info.mtime = dt.toTime_t();
+ info.size = fi.size();
+ info.width = getImageDimensions ? dims.width() : -1;
+ info.height = getImageDimensions ? dims.height() : -1;
+ info.downloaded = GPItemInfo::DownloadUnknow;
+ info.readPermissions = fi.isReadable();
+ info.writePermissions = fi.isWritable();
+ }
+
+ return true;
+}
+
+void UMSCamera::listFolders(const TQString& folder, TQStringList& subFolderList)
+{
+ if (m_cancel)
+ return;
+
+ TQDir dir(folder);
+ dir.setFilter(TQDir::Dirs|TQDir::Executable);
+
+ const TQFileInfoList *list = dir.entryInfoList();
+ if (!list)
+ return;
+
+ TQFileInfoListIterator it( *list );
+ TQFileInfo *fi;
+
+ while ((fi = it.current()) != 0 && !m_cancel)
+ {
+ ++it;
+
+ if (fi->fileName() == "." || fi->fileName() == "..")
+ continue;
+
+ TQString subfolder = folder + TQString(folder.endsWith("/") ? "" : "/") + fi->fileName();
+ subFolderList.append(subfolder);
+ listFolders(subfolder, subFolderList);
+ }
+}
+
+bool UMSCamera::cameraSummary(TQString& summary)
+{
+ summary = TQString(i18n("<b>Mounted Camera</b> driver for USB/IEEE1394 mass storage cameras and "
+ "Flash disk card readers.<br><br>"));
+
+ summary.append(i18n("Title: %1<br>"
+ "Model: %2<br>"
+ "Port: %3<br>"
+ "Path: %4<br>")
+ .arg(title())
+ .arg(model())
+ .arg(port())
+ .arg(path()));
+ return true;
+}
+
+bool UMSCamera::cameraManual(TQString& manual)
+{
+ manual = TQString(i18n("For more information about the <b>Mounted Camera</b> driver, "
+ "please read <b>Supported Digital Still "
+ "Cameras</b> section in the digiKam manual."));
+ return true;
+}
+
+bool UMSCamera::cameraAbout(TQString& about)
+{
+ about = TQString(i18n("The <b>Mounted Camera</b> driver is a simple interface to a camera disk "
+ "mounted locally on your system.<br><br>"
+ "It doesn't use libgphoto2 drivers.<br><br>"
+ "To report any problems with this driver, please contact the digiKam team at:<br><br>"
+ "http://www.digikam.org/?q=contact"));
+ return true;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/cameragui/umscamera.h b/src/utilities/cameragui/umscamera.h
new file mode 100644
index 00000000..697ef615
--- /dev/null
+++ b/src/utilities/cameragui/umscamera.h
@@ -0,0 +1,78 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-12-21
+ * Description : USB Mass Storage camera interface
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef UMSCAMERA_H
+#define UMSCAMERA_H
+
+// TQt includes.
+
+#include <tqstringlist.h>
+
+// Local includes.
+
+#include "dkcamera.h"
+
+namespace Digikam
+{
+
+class UMSCameraPriv;
+
+class UMSCamera : public DKCamera
+{
+public:
+
+ UMSCamera(const TQString& title, const TQString& model, const TQString& port, const TQString& path);
+ ~UMSCamera();
+
+ bool doConnect();
+ void cancel();
+
+ void getAllFolders(const TQString& folder, TQStringList& subFolderList);
+ bool getItemsInfoList(const TQString& folder, GPItemInfoList& infoList, bool getImageDimensions=true);
+ bool getThumbnail(const TQString& folder, const TQString& itemName, TQImage& thumbnail);
+ bool getExif(const TQString& folder, const TQString& itemName, char **edata, int& esize);
+
+ bool setLockItem(const TQString& folder, const TQString& itemName, bool lock);
+
+ bool downloadItem(const TQString& folder, const TQString& itemName, const TQString& saveFile);
+ bool deleteItem(const TQString& folder, const TQString& itemName);
+ bool uploadItem(const TQString& folder, const TQString& itemName, const TQString& localFile,
+ GPItemInfo& info, bool getImageDimensions=true);
+
+ bool cameraSummary(TQString& summary);
+ bool cameraManual(TQString& manual);
+ bool cameraAbout(TQString& about);
+
+private:
+
+ void listFolders(const TQString& folder, TQStringList& subFolderList);
+
+private:
+
+ bool m_cancel;
+};
+
+} // namespace Digikam
+
+#endif /* UMSCAMERA_H */
diff --git a/src/utilities/hotplug/Makefile.am b/src/utilities/hotplug/Makefile.am
new file mode 100644
index 00000000..b0b9e23a
--- /dev/null
+++ b/src/utilities/hotplug/Makefile.am
@@ -0,0 +1,7 @@
+konqservicemenudir = $(kde_datadir)/konqueror/servicemenus
+konqservicemenu_DATA = digikam-download.desktop digikam-gphoto2-camera.desktop digikam-mount-and-download.desktop
+
+helperdir = $(digikamhelper_dir)
+helper_SCRIPTS = digikam-camera
+
+#EXTRA_DIST = $(servicemenu_DATA) $(helper_SCRIPTS)
diff --git a/src/utilities/hotplug/configure.in.in b/src/utilities/hotplug/configure.in.in
new file mode 100644
index 00000000..b36be1e8
--- /dev/null
+++ b/src/utilities/hotplug/configure.in.in
@@ -0,0 +1,7 @@
+KDE_EXPAND_MAKEVAR(digikamhelper_dir, kde_datadir/digikam/utils)
+AC_SUBST(digikamhelper_dir)
+
+AC_OUTPUT(src/utilities/hotplug/digikam-download.desktop)
+AC_OUTPUT(src/utilities/hotplug/digikam-gphoto2-camera.desktop)
+AC_OUTPUT(src/utilities/hotplug/digikam-mount-and-download.desktop)
+
diff --git a/src/utilities/hotplug/digikam-camera b/src/utilities/hotplug/digikam-camera
new file mode 100755
index 00000000..10b2fe3f
--- /dev/null
+++ b/src/utilities/hotplug/digikam-camera
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+action="$1"; shift;
+
+case "$action" in
+detect)
+ cmdoption=--detect-camera
+ dcopcall=detectCamera
+ ;;
+storage)
+ cmdoption=--download-from
+ dcopcall=downloadFrom
+ args="$@"
+ ;;
+*)
+ echo "${0##*/}: wrong action. Usage"
+ echo " ${0##*/} detect # for gphoto2 supported cameras"
+ echo " ${0##*/} storage <url> # for usbdisk or directries with images"
+ exit 1
+ ;;
+esac
+
+for app in `dcop`; do
+ case "$app" in
+ digikam-*)
+ echo "recycling running $app: $dcopcall $@"
+ if test -z "$args"; then
+ exec dcop "$app" camera "$dcopcall"
+ else
+ exec dcop "$app" camera "$dcopcall" "$args"
+ fi
+ ;;
+ esac
+done;
+echo "starting digikam with $cmdoption $args"
+if test -z "$args"; then
+ exec digikam "$cmdoption"
+else
+ exec digikam "$cmdoption" "$args"
+fi
diff --git a/src/utilities/hotplug/digikam-download.desktop.in b/src/utilities/hotplug/digikam-download.desktop.in
new file mode 100644
index 00000000..55a29fdb
--- /dev/null
+++ b/src/utilities/hotplug/digikam-download.desktop.in
@@ -0,0 +1,27 @@
+[Desktop Action digiKam Download]
+Exec=@digikamhelper_dir@/digikam-camera storage %u
+Icon=digikam
+Name=Download Photos with digiKam
+Name[ca]=Descàrrega de fotos amb el digiKam
+Name[de]=Fotos mit digiKam herunterladen
+Name[es]=Descargar fotos con digiKam
+Name[et]=Fotode allalaadimine digiKamiga
+Name[fi]=Lataa valokuvat digiKamilla
+Name[fr]=Télécharger les photos avec digiKam
+Name[is]=Hala niður myndum með digiKam
+Name[it]=Scarica foto con digiKam
+Name[ja]=digiKam で写真をダウンロード
+Name[nds]=Fotos mit digiKam daalladen
+Name[nl]=Foto's downloaden met digiKam
+Name[pl]=Pobierz zdjęcia programem digiKam
+Name[pt]=Obter Fotografias com o digiKam
+Name[pt_BR]=Obter Fotografias com o digiKam
+Name[sk]=Stiahnuť fotky pomocou digiKam
+Name[sr]=Преузми слике помоћу digiKam-а
+Name[sr@Latn]=Преузми слике помоћу digiKam-а
+Name[sv]=Ladda ner foton med Digikam
+Name[xx]=xxDownload Photos with digiKamxx
+
+[Desktop Entry]
+Actions=digiKam Download
+X-TDE-ServiceTypes=media/removable_mounted,media/camera_mounted
diff --git a/src/utilities/hotplug/digikam-gphoto2-camera.desktop.in b/src/utilities/hotplug/digikam-gphoto2-camera.desktop.in
new file mode 100644
index 00000000..f2c4a42a
--- /dev/null
+++ b/src/utilities/hotplug/digikam-gphoto2-camera.desktop.in
@@ -0,0 +1,27 @@
+[Desktop Action digiKam Detect and Download]
+Exec=@digikamhelper_dir@/digikam-camera detect %u
+Icon=digikam
+Name=digiKam Detect and Download
+Name[ca]=Detecta i descarrega amb el digiKam
+Name[de]=Finden und Herunterladen mit digiKam
+Name[es]=Detectar y descargar con digiKam
+Name[et]=*Fotode tuvastamine ja allalaadimine digiKamiga
+Name[fi]=Tunnista kamera ja lataa kuvat digiKamilla
+Name[fr]=Détecter et télécharger avec digiKam
+Name[is]=digiKam Finna og Niðurhala
+Name[it]=Rileva e scarica con digiKam
+Name[ja]=digiKam 検出とダウンロード
+Name[nds]=digiKam - Opdecken un daalladen
+Name[nl]=digiKam-detectie en download
+Name[pl]=Wykrycie i pobieranie digiKamem
+Name[pt]=Detectar e Transferir com o digiKam
+Name[pt_BR]=Detectar e Transferir com o digiKam
+Name[sk]=digiKam Nájsť a stiahnuť
+Name[sr]=digiKam-ово Препознај и преузми
+Name[sr@Latn]=digiKam-ово Препознај и преузми
+Name[sv]=Digikam detektering och nerladdning
+Name[xx]=xxdigiKam Detect and Downloadxx
+
+[Desktop Entry]
+Actions=digiKam Detect and Download
+X-TDE-ServiceTypes=media/gphoto2camera
diff --git a/src/utilities/hotplug/digikam-mount-and-download.desktop.in b/src/utilities/hotplug/digikam-mount-and-download.desktop.in
new file mode 100644
index 00000000..2b773a15
--- /dev/null
+++ b/src/utilities/hotplug/digikam-mount-and-download.desktop.in
@@ -0,0 +1,27 @@
+[Desktop Action digiKam Mount and Download]
+Exec=@digikamhelper_dir@/digikam-camera storage %u
+Icon=digikam
+Name=Download Photos with digiKam
+Name[ca]=Descàrrega de fotos amb el digiKam
+Name[de]=Fotos mit digiKam herunterladen
+Name[es]=Descargar fotos con digiKam
+Name[et]=Fotode allalaadimine digiKamiga
+Name[fi]=Lataa valokuvat digiKamilla
+Name[fr]=Télécharger les photos avec digiKam
+Name[is]=Hala niður myndum með digiKam
+Name[it]=Scarica foto con digiKam
+Name[ja]=digiKam で写真をダウンロード
+Name[nds]=Fotos mit digiKam daalladen
+Name[nl]=Foto's downloaden met digiKam
+Name[pl]=Pobierz zdjęcia programem digiKam
+Name[pt]=Obter Fotografias com o digiKam
+Name[pt_BR]=Obter Fotografias com o digiKam
+Name[sk]=Stiahnuť fotky pomocou digiKam
+Name[sr]=Преузми слике помоћу digiKam-а
+Name[sr@Latn]=Преузми слике помоћу digiKam-а
+Name[sv]=Ladda ner foton med Digikam
+Name[xx]=xxDownload Photos with digiKamxx
+
+[Desktop Entry]
+Actions=digiKam Mount and Download
+X-TDE-ServiceTypes=media/removable_unmounted,media/camera_unmounted
diff --git a/src/utilities/imageeditor/Makefile.am b/src/utilities/imageeditor/Makefile.am
new file mode 100644
index 00000000..c324c683
--- /dev/null
+++ b/src/utilities/imageeditor/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = canvas tools rawimport editor
diff --git a/src/utilities/imageeditor/canvas/Makefile.am b/src/utilities/imageeditor/canvas/Makefile.am
new file mode 100644
index 00000000..740f854e
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/Makefile.am
@@ -0,0 +1,28 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdimgcanvas.la
+
+libdimgcanvas_la_SOURCES = dimginterface.cpp colorcorrectiondlg.cpp \
+ canvas.cpp undocache.cpp \
+ undoaction.cpp undomanager.cpp \
+ imagepluginloader.cpp imageplugin.cpp
+
+libdimgcanvas_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TIFF)
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/utilities/splashscreen \
+ -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/utilities/imageeditor/rawimport \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) $(all_includes)
+
+digikaminclude_HEADERS = imageplugin.h
+
+digikamincludedir = $(includedir)/digikam
diff --git a/src/utilities/imageeditor/canvas/canvas.cpp b/src/utilities/imageeditor/canvas/canvas.cpp
new file mode 100644
index 00000000..89758c3d
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/canvas.cpp
@@ -0,0 +1,1421 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-09
+ * Description : image editor canvas management class
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+#include <cmath>
+
+// TQt includes.
+
+#include <tqtooltip.h>
+#include <tqfile.h>
+#include <tqstring.h>
+#include <tqevent.h>
+#include <tqpoint.h>
+#include <tqpainter.h>
+#include <tqpen.h>
+#include <tqpixmap.h>
+#include <tqstyle.h>
+#include <tqapplication.h>
+#include <tqcursor.h>
+#include <tqimage.h>
+#include <tqregion.h>
+#include <tqtimer.h>
+#include <tqcache.h>
+#include <tqcolor.h>
+#include <tqdragobject.h>
+#include <tqclipboard.h>
+#include <tqtoolbutton.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <kdatetbl.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "imagepaniconwidget.h"
+#include "dimginterface.h"
+#include "iccsettingscontainer.h"
+#include "exposurecontainer.h"
+#include "iofilesettingscontainer.h"
+#include "loadingcacheinterface.h"
+#include "canvas.h"
+#include "canvas.moc"
+
+namespace Digikam
+{
+
+class CanvasPrivate
+{
+
+public:
+
+ CanvasPrivate() :
+ tileSize(128), minZoom(0.1), maxZoom(12.0), zoomMultiplier(1.2)
+ {
+ rubber = 0;
+ pressedMoved = false;
+ pressedMoving = false;
+ ltActive = false;
+ rtActive = false;
+ lbActive = false;
+ rbActive = false;
+ midButtonPressed = false;
+ midButtonX = 0;
+ midButtonY = 0;
+ panIconPopup = 0;
+ panIconWidget = 0;
+ cornerButton = 0;
+ parent = 0;
+ im = 0;
+ rubber = 0;
+ autoZoom = false;
+ fullScreen = false;
+ zoom = 1.0;
+ tileTmpPix = new TQPixmap(tileSize, tileSize);
+
+ tileCache.setMaxCost((10*1024*1024)/(tileSize*tileSize*4));
+ tileCache.setAutoDelete(true);
+ }
+
+ bool autoZoom;
+ bool fullScreen;
+ bool pressedMoved;
+ bool pressedMoving;
+ bool ltActive;
+ bool rtActive;
+ bool lbActive;
+ bool rbActive;
+ bool midButtonPressed;
+
+ const int tileSize;
+ int midButtonX;
+ int midButtonY;
+
+ double zoom;
+ const double minZoom;
+ const double maxZoom;
+ const double zoomMultiplier;
+
+ TQToolButton *cornerButton;
+
+ TQRect *rubber;
+ TQRect pixmapRect;
+
+ TQCache<TQPixmap> tileCache;
+
+ TQPixmap* tileTmpPix;
+ TQPixmap qcheck;
+
+ TQColor bgColor;
+
+ TQWidget *parent;
+
+ TDEPopupFrame *panIconPopup;
+
+ DImgInterface *im;
+
+ ImagePanIconWidget *panIconWidget;
+};
+
+Canvas::Canvas(TQWidget *parent)
+ : TQScrollView(parent)
+{
+ d = new CanvasPrivate;
+ d->im = new DImgInterface();
+ d->parent = parent;
+ d->bgColor.setRgb(0, 0, 0);
+
+ d->qcheck.resize(16, 16);
+ TQPainter p(&d->qcheck);
+ p.fillRect(0, 0, 8, 8, TQColor(144, 144, 144));
+ p.fillRect(8, 8, 8, 8, TQColor(144, 144, 144));
+ p.fillRect(0, 8, 8, 8, TQColor(100, 100, 100));
+ p.fillRect(8, 0, 8, 8, TQColor(100, 100, 100));
+ p.end();
+
+ d->cornerButton = new TQToolButton(this);
+ d->cornerButton->setIconSet(SmallIcon("move"));
+ d->cornerButton->hide();
+ TQToolTip::add(d->cornerButton, i18n("Pan the image to a region"));
+ setCornerWidget(d->cornerButton);
+
+ viewport()->setBackgroundMode(TQt::NoBackground);
+ viewport()->setMouseTracking(false);
+ setFrameStyle( TQFrame::NoFrame );
+
+ // ------------------------------------------------------------
+
+ connect(this, TQ_SIGNAL(signalZoomChanged(double)),
+ this, TQ_SLOT(slotZoomChanged(double)));
+
+ connect(d->cornerButton, TQ_SIGNAL(pressed()),
+ this, TQ_SLOT(slotCornerButtonPressed()));
+
+ connect(d->im, TQ_SIGNAL(signalModified()),
+ this, TQ_SLOT(slotModified()));
+
+ connect(d->im, TQ_SIGNAL(signalUndoStateChanged(bool, bool, bool)),
+ this, TQ_SIGNAL(signalUndoStateChanged(bool, bool, bool)));
+
+ connect(d->im, TQ_SIGNAL(signalLoadingStarted(const TQString&)),
+ this, TQ_SIGNAL(signalLoadingStarted(const TQString&)));
+
+ connect(d->im, TQ_SIGNAL(signalImageLoaded(const TQString&, bool)),
+ this, TQ_SLOT(slotImageLoaded(const TQString&, bool)));
+
+ connect(d->im, TQ_SIGNAL(signalImageSaved(const TQString&, bool)),
+ this, TQ_SLOT(slotImageSaved(const TQString&, bool)));
+
+ connect(d->im, TQ_SIGNAL(signalLoadingProgress(const TQString&, float)),
+ this, TQ_SIGNAL(signalLoadingProgress(const TQString&, float)));
+
+ connect(d->im, TQ_SIGNAL(signalSavingProgress(const TQString&, float)),
+ this, TQ_SIGNAL(signalSavingProgress(const TQString&, float)));
+
+ connect(this, TQ_SIGNAL(signalSelected(bool)),
+ this, TQ_SLOT(slotSelected()));
+}
+
+Canvas::~Canvas()
+{
+ delete d->tileTmpPix;
+ delete d->im;
+
+ if (d->rubber)
+ delete d->rubber;
+
+ delete d;
+}
+
+void Canvas::resetImage()
+{
+ reset();
+ viewport()->setUpdatesEnabled(false);
+ d->im->resetImage();
+}
+
+void Canvas::reset()
+{
+ if (d->rubber)
+ {
+ delete d->rubber;
+ d->rubber = 0;
+ if (d->im->imageValid())
+ emit signalSelected(false);
+ }
+
+ d->tileCache.clear();
+}
+
+void Canvas::load(const TQString& filename, IOFileSettingsContainer *IOFileSettings)
+{
+ reset();
+
+ viewport()->setUpdatesEnabled(false);
+
+ d->im->load( filename, IOFileSettings, d->parent );
+
+ emit signalPrepareToLoad();
+}
+
+void Canvas::slotImageLoaded(const TQString& filePath, bool success)
+{
+ d->zoom = 1.0;
+ d->im->zoom(d->zoom);
+
+ if (d->autoZoom)
+ updateAutoZoom();
+
+ updateContentsSize(true);
+
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+
+ emit signalZoomChanged(d->zoom);
+
+ emit signalLoadingFinished(filePath, success);
+}
+
+void Canvas::preload(const TQString& /*filename*/)
+{
+// d->im->preload(filename);
+}
+
+/*
+These code part are unused and untested
+void Canvas::save(const TQString& filename, IOFileSettingsContainer *IOFileSettings)
+{
+ d->im->save(filename, IOFileSettings);
+ emit signalSavingStarted(filename);
+}
+
+void Canvas::saveAs(const TQString& filename,IOFileSettingsContainer *IOFileSettings,
+ const TQString& mimeType)
+{
+ d->im->saveAs(filename, IOFileSettings, mimeType);
+ emit signalSavingStarted(filename);
+}
+*/
+
+void Canvas::saveAs(const TQString& filename, IOFileSettingsContainer *IOFileSettings,
+ bool setExifOrientationTag, const TQString& mimeType)
+{
+ d->im->saveAs(filename, IOFileSettings, setExifOrientationTag, mimeType);
+ emit signalSavingStarted(filename);
+}
+
+void Canvas::slotImageSaved(const TQString& filePath, bool success)
+{
+ emit signalSavingFinished(filePath, success);
+}
+
+void Canvas::switchToLastSaved(const TQString& newFilename)
+{
+ d->im->switchToLastSaved(newFilename);
+}
+
+void Canvas::abortSaving()
+{
+ d->im->abortSaving();
+}
+
+void Canvas::setModified()
+{
+ d->im->setModified();
+}
+
+void Canvas::readMetadataFromFile(const TQString &file)
+{
+ d->im->readMetadataFromFile(file);
+}
+
+void Canvas::clearUndoHistory()
+{
+ d->im->clearUndoManager();
+}
+
+void Canvas::setUndoHistoryOrigin()
+{
+ d->im->setUndoManagerOrigin();
+}
+
+void Canvas::updateUndoState()
+{
+ d->im->updateUndoState();
+}
+
+DImg Canvas::currentImage()
+{
+ return DImg(*d->im->getImg());
+}
+
+TQString Canvas::currentImageFileFormat()
+{
+ return d->im->getImageFormat();
+}
+
+TQString Canvas::currentImageFilePath()
+{
+ return d->im->getImageFilePath();
+}
+
+int Canvas::imageWidth()
+{
+ return d->im->origWidth();
+}
+
+int Canvas::imageHeight()
+{
+ return d->im->origHeight();
+}
+
+bool Canvas::isReadOnly()
+{
+ return d->im->isReadOnly();
+}
+
+TQRect Canvas::getSelectedArea()
+{
+ int x, y, w, h;
+ d->im->getSelectedArea(x, y, w, h);
+ return ( TQRect(x, y, w, h) );
+}
+
+DImgInterface *Canvas::interface() const
+{
+ return d->im;
+}
+
+void Canvas::makeDefaultEditingCanvas()
+{
+ DImgInterface::setDefaultInterface(d->im);
+}
+
+double Canvas::calcAutoZoomFactor()
+{
+ if (!d->im->imageValid()) return d->zoom;
+
+ double srcWidth = d->im->origWidth();
+ double srcHeight = d->im->origHeight();
+ double dstWidth = contentsRect().width();
+ double dstHeight = contentsRect().height();
+ return TQMIN(dstWidth/srcWidth, dstHeight/srcHeight);
+}
+
+void Canvas::updateAutoZoom()
+{
+ d->zoom = calcAutoZoomFactor();
+ d->im->zoom(d->zoom);
+ emit signalZoomChanged(d->zoom);
+}
+
+void Canvas::updateContentsSize(bool deleteRubber)
+{
+ viewport()->setUpdatesEnabled(false);
+
+ if (deleteRubber && d->rubber)
+ {
+ delete d->rubber;
+ d->rubber = 0;
+ d->ltActive = false;
+ d->rtActive = false;
+ d->lbActive = false;
+ d->rbActive = false;
+ d->pressedMoved = false;
+ viewport()->unsetCursor();
+ viewport()->setMouseTracking(false);
+ if (d->im->imageValid())
+ emit signalSelected(false);
+ }
+
+ int wZ = d->im->width();
+ int hZ = d->im->height();
+
+ if (visibleWidth() > wZ || visibleHeight() > hZ)
+ {
+ // Center the image
+ int centerx = contentsRect().width()/2;
+ int centery = contentsRect().height()/2;
+ int xoffset = int(centerx - wZ/2);
+ int yoffset = int(centery - hZ/2);
+ xoffset = TQMAX(xoffset, 0);
+ yoffset = TQMAX(yoffset, 0);
+
+ d->pixmapRect = TQRect(xoffset, yoffset, wZ, hZ);
+ }
+ else
+ {
+ d->pixmapRect = TQRect(0, 0, wZ, hZ);
+ }
+
+ if (!deleteRubber && d->rubber)
+ {
+ int xSel, ySel, wSel, hSel;
+ d->im->getSelectedArea(xSel, ySel, wSel, hSel);
+ xSel = (int)((xSel * d->tileSize) / floor(d->tileSize / d->zoom));
+ ySel = (int)((ySel * d->tileSize) / floor(d->tileSize / d->zoom));
+ wSel = (int)((wSel * d->tileSize) / floor(d->tileSize / d->zoom));
+ hSel = (int)((hSel * d->tileSize) / floor(d->tileSize / d->zoom));
+ d->rubber->setX(xSel);
+ d->rubber->setY(ySel);
+ d->rubber->setWidth(wSel);
+ d->rubber->setHeight(hSel);
+ d->rubber->moveBy(d->pixmapRect.x(), d->pixmapRect.y());
+ }
+
+ d->tileCache.clear();
+ resizeContents(wZ, hZ);
+ viewport()->setUpdatesEnabled(true);
+}
+
+void Canvas::resizeEvent(TQResizeEvent* e)
+{
+ if (!e)
+ return;
+
+ TQScrollView::resizeEvent(e);
+
+ if (d->autoZoom)
+ updateAutoZoom();
+
+ updateContentsSize(false);
+
+ // No need to repaint. its called
+ // automatically after resize
+
+ // To be sure than corner widget used to pan image will be hide/show
+ // accordinly with resize event.
+ slotZoomChanged(d->zoom);
+}
+
+void Canvas::viewportPaintEvent(TQPaintEvent *e)
+{
+ TQRect er(e->rect());
+ er = TQRect(TQMAX(er.x() - 1, 0),
+ TQMAX(er.y() - 1, 0),
+ TQMIN(er.width() + 2, contentsRect().width()),
+ TQMIN(er.height() + 2, contentsRect().height()));
+
+ paintViewport(er, (d->zoom <= 1.0) ? true : false);
+}
+
+void Canvas::paintViewport(const TQRect& er, bool antialias)
+{
+ TQRect o_cr(viewportToContents(er.topLeft()), viewportToContents(er.bottomRight()));
+ TQRect cr = o_cr;
+
+ TQRegion clipRegion(er);
+ cr = d->pixmapRect.intersect(cr);
+
+ if (!cr.isEmpty() && d->im->imageValid())
+ {
+ clipRegion -= TQRect(contentsToViewport(cr.topLeft()), cr.size());
+
+ TQRect pr = TQRect(cr.x() - d->pixmapRect.x(), cr.y() - d->pixmapRect.y(),
+ cr.width(), cr.height());
+
+ int x1 = (int)floor((double)pr.x() / (double)d->tileSize) * d->tileSize;
+ int y1 = (int)floor((double)pr.y() / (double)d->tileSize) * d->tileSize;
+ int x2 = (int)ceilf((double)pr.right() / (double)d->tileSize) * d->tileSize;
+ int y2 = (int)ceilf((double)pr.bottom() / (double)d->tileSize) * d->tileSize;
+
+ TQPixmap pix(d->tileSize, d->tileSize);
+ int sx, sy, sw, sh;
+ int step = (int)floor(d->tileSize / d->zoom);
+
+ bool hasRubber = (d->rubber && d->pressedMoved && d->pressedMoving && d->rubber->intersects(pr));
+ if (hasRubber)
+ {
+ // remove rubber
+ drawRubber();
+ }
+
+ for (int j = y1 ; j < y2 ; j += d->tileSize)
+ {
+ for (int i = x1 ; i < x2 ; i += d->tileSize)
+ {
+ TQString key = TQString("%1,%2").arg(i).arg(j);
+ TQPixmap *pix = d->tileCache.find(key);
+
+ if (!pix)
+ {
+ if (antialias)
+ {
+ pix = new TQPixmap(d->tileSize, d->tileSize);
+ d->tileCache.insert(key, pix);
+ }
+ else
+ {
+ pix = d->tileTmpPix;
+ }
+
+ if (d->im->hasAlpha())
+ {
+ TQPainter p(pix);
+ p.drawTiledPixmap(0, 0, d->tileSize, d->tileSize,
+ d->qcheck, 0, 0);
+ p.end();
+ }
+ else
+ {
+ pix->fill(d->bgColor);
+ }
+
+ // NOTE : with implementations <= 0.9.1, the canvas doesn't work properly using high zoom level (> 500).
+ // The sx, sy, sw, sh values haven't be computed properly and "tile" artefacts been appears
+ // over the image. Look the example here:
+ // http://digikam3rdparty.free.fr/Screenshots/editorhighzoomartefact.png
+ // Note than these "tile" artifacts are not the real tiles of canvas.
+ // The new implementation below fix this problem to handle properly the areas to
+ // use from the source image to generate the canvas pixmap tiles.
+
+ sx = (int)floor((double)i / d->tileSize) * step;
+ sy = (int)floor((double)j / d->tileSize) * step;
+ sw = step;
+ sh = step;
+
+ if (d->rubber && d->pressedMoved && !d->pressedMoving)
+ {
+ TQRect rr(d->rubber->normalize());
+ TQRect r(i, j, d->tileSize, d->tileSize);
+
+ d->im->paintOnDevice(pix, sx, sy, sw, sh,
+ 0, 0, d->tileSize, d->tileSize,
+ rr.x() - i - d->pixmapRect.x(),
+ rr.y() - j - d->pixmapRect.y(),
+ rr.width(), rr.height(),
+ antialias);
+
+ rr.moveBy(-i -d->pixmapRect.x(), -j -d->pixmapRect.y());
+
+ TQPainter p(pix);
+ p.setPen(TQPen(TQColor(250, 250, 255), 1));
+ p.drawRect(rr);
+ if (rr.width() >= 10 && rr.height() >= 10)
+ {
+ p.drawRect(rr.x(), rr.y(), 5, 5);
+ p.drawRect(rr.x(), rr.y()+rr.height()-5, 5, 5);
+ p.drawRect(rr.x()+rr.width()-5, rr.y()+rr.height()-5, 5, 5);
+ p.drawRect(rr.x()+rr.width()-5, rr.y(), 5, 5);
+ }
+ p.end();
+ }
+ else
+ {
+ d->im->paintOnDevice(pix, sx, sy, sw, sh,
+ 0, 0, d->tileSize, d->tileSize,
+ antialias);
+ }
+ }
+
+ TQRect r(i, j, d->tileSize, d->tileSize);
+ TQRect ir = pr.intersect(r);
+ TQPoint pt(contentsToViewport(TQPoint(ir.x() + d->pixmapRect.x(),
+ ir.y() + d->pixmapRect.y())));
+
+ bitBlt(viewport(), pt.x(), pt.y(),
+ pix,
+ ir.x()-r.x(), ir.y()-r.y(),
+ ir.width(), ir.height());
+ }
+ }
+
+ if (hasRubber)
+ {
+ // restore rubber
+ drawRubber();
+ }
+ }
+
+ TQPainter painter(viewport());
+ painter.setClipRegion(clipRegion);
+ painter.fillRect(er, d->bgColor);
+ painter.end();
+}
+
+void Canvas::drawRubber()
+{
+ if (!d->rubber || !d->im->imageValid())
+ return;
+
+ TQPainter p(viewport());
+ p.setRasterOp(TQt::NotROP );
+ p.setPen(TQPen(TQt::color0, 1));
+ p.setBrush(NoBrush);
+
+ TQRect r(d->rubber->normalize());
+ r = TQRect(contentsToViewport(TQPoint(r.x(), r.y())), r.size());
+
+ TQPoint pnt(r.x(), r.y());
+
+ style().drawPrimitive(TQStyle::PE_FocusRect, &p,
+ TQRect(pnt.x(), pnt.y(), r.width(), r.height()),
+ colorGroup(), TQStyle::Style_Default,
+ TQStyleOption(colorGroup().base()));
+ p.end();
+}
+
+void Canvas::contentsMousePressEvent(TQMouseEvent *e)
+{
+ if (!e || e->button() == TQt::RightButton)
+ return;
+
+ d->midButtonPressed = false;
+
+ if (e->button() == TQt::LeftButton)
+ {
+ if (d->ltActive || d->rtActive ||
+ d->lbActive || d->rbActive)
+ {
+ Q_ASSERT( d->rubber );
+ if (!d->rubber)
+ return;
+
+ // Set diagonally opposite corner as anchor
+
+ TQRect r(d->rubber->normalize());
+
+ if (d->ltActive)
+ {
+ d->rubber->setTopLeft(r.bottomRight());
+ d->rubber->setBottomRight(r.topLeft());
+ }
+ else if (d->rtActive)
+ {
+ d->rubber->setTopLeft(r.bottomLeft());
+ d->rubber->setBottomRight(r.topRight());
+ }
+ else if (d->lbActive)
+ {
+ d->rubber->setTopLeft(r.topRight());
+ d->rubber->setBottomRight(r.bottomLeft());
+ }
+ else if (d->rbActive)
+ {
+ d->rubber->setTopLeft(r.topLeft());
+ d->rubber->setBottomRight(r.bottomLeft());
+ }
+
+ viewport()->setMouseTracking(false);
+ d->pressedMoved = false;
+ d->pressedMoving = true;
+
+ d->tileCache.clear();
+ viewport()->repaint(false);
+
+ return;
+ }
+ }
+ else if (e->button() == TQt::MidButton)
+ {
+ if (visibleWidth() < d->im->width() ||
+ visibleHeight() < d->im->height())
+ {
+ viewport()->setCursor(TQt::SizeAllCursor);
+ d->midButtonPressed = true;
+ d->midButtonX = e->x();
+ d->midButtonY = e->y();
+ }
+ return;
+ }
+
+ if (d->rubber)
+ {
+ delete d->rubber;
+ d->rubber = 0;
+ }
+
+ d->rubber = new TQRect(e->x(), e->y(), 0, 0);
+
+ if (d->pressedMoved)
+ {
+ d->tileCache.clear();
+ viewport()->update();
+ }
+
+ d->pressedMoved = false;
+ d->pressedMoving = true;
+
+ viewport()->setMouseTracking(false);
+}
+
+void Canvas::contentsMouseMoveEvent(TQMouseEvent *e)
+{
+ if (!e)
+ return;
+
+ if (e->state() & TQt::MidButton)
+ {
+ if (d->midButtonPressed)
+ {
+ scrollBy(d->midButtonX - e->x(),
+ d->midButtonY - e->y());
+ }
+ }
+ else if (!viewport()->hasMouseTracking())
+ {
+ if (!d->rubber)
+ return;
+
+ if (e->state() != TQt::LeftButton &&
+ !(d->ltActive || d->rtActive ||
+ d->lbActive || d->rbActive))
+ return;
+
+ // Clear old rubber.
+ if (d->pressedMoved)
+ drawRubber();
+
+ // Move content if necessary.
+ blockSignals(true);
+ setUpdatesEnabled(false);
+ ensureVisible(e->x(), e->y(), 10, 10);
+ setUpdatesEnabled(true);
+ blockSignals(false);
+
+ // draw the new rubber position.
+ int r, b;
+ r = (e->x() > d->pixmapRect.left()) ? e->x() : d->pixmapRect.left();
+ r = (r < d->pixmapRect.right()) ? r : d->pixmapRect.right();
+ b = (e->y() > d->pixmapRect.top()) ? e->y() : d->pixmapRect.top();
+ b = (b < d->pixmapRect.bottom()) ? b : d->pixmapRect.bottom();
+ d->rubber->setRight(r);
+ d->rubber->setBottom(b);
+ drawRubber();
+
+ d->pressedMoved = true;
+ d->pressedMoving = true;
+
+ // To refresh editor status bar with current selection.
+ emit signalSelectionChanged(calcSeletedArea());
+ }
+ else
+ {
+ if (!d->rubber)
+ return;
+
+ TQRect r(d->rubber->normalize());
+
+ TQRect lt(r.x()-5, r.y()-5, 10, 10);
+ TQRect rt(r.x()+r.width()-5, r.y()-5, 10, 10);
+ TQRect lb(r.x()-5, r.y()+r.height()-5, 10, 10);
+ TQRect rb(r.x()+r.width()-5, r.y()+r.height()-5, 10, 10);
+
+ d->ltActive = false;
+ d->rtActive = false;
+ d->lbActive = false;
+ d->rbActive = false;
+
+ if (lt.contains(e->x(), e->y()))
+ {
+ viewport()->setCursor(TQt::SizeFDiagCursor);
+ d->ltActive = true;
+ }
+ else if (rb.contains(e->x(), e->y()))
+ {
+ viewport()->setCursor(TQt::SizeFDiagCursor);
+ d->rbActive = true;
+ }
+ else if (lb.contains(e->x(), e->y()))
+ {
+ viewport()->setCursor(TQt::SizeBDiagCursor);
+ d->lbActive = true;
+ }
+ else if (rt.contains(e->x(), e->y()))
+ {
+ viewport()->setCursor(TQt::SizeBDiagCursor);
+ d->rtActive = true;
+ }
+ else
+ viewport()->unsetCursor();
+ }
+}
+
+void Canvas::contentsMouseReleaseEvent(TQMouseEvent *e)
+{
+ if (!e)
+ return;
+
+ d->midButtonPressed = false;
+
+ if (d->pressedMoving)
+ {
+ d->pressedMoving = false;
+ viewport()->update();
+ }
+
+ if (d->pressedMoved && d->rubber)
+ {
+ // Normalize rubber rectangle to always have the selection into the image
+ TQRect rec = d->rubber->normalize();
+
+ if (rec.left() < d->pixmapRect.left()) rec.setLeft(d->pixmapRect.left());
+ if (rec.right() > d->pixmapRect.right()) rec.setRight(d->pixmapRect.right());
+ if (rec.top() < d->pixmapRect.top()) rec.setTop(d->pixmapRect.top());
+ if (rec.bottom() > d->pixmapRect.bottom()) rec.setBottom(d->pixmapRect.bottom());
+
+ d->rubber->setLeft(rec.left());
+ d->rubber->setRight(rec.right());
+ d->rubber->setTop(rec.top());
+ d->rubber->setBottom(rec.bottom());
+
+ d->tileCache.clear();
+ viewport()->setMouseTracking(true);
+ if (d->im->imageValid())
+ emit signalSelected(true);
+ }
+ else
+ {
+ d->ltActive = false;
+ d->rtActive = false;
+ d->lbActive = false;
+ d->rbActive = false;
+ viewport()->setMouseTracking(false);
+ viewport()->unsetCursor();
+ if (d->im->imageValid())
+ emit signalSelected(false);
+ }
+
+ if (e->button() != TQt::LeftButton)
+ {
+ viewport()->unsetCursor();
+ }
+
+ if (e->button() == TQt::RightButton)
+ {
+ emit signalRightButtonClicked();
+ }
+}
+
+void Canvas::contentsWheelEvent(TQWheelEvent *e)
+{
+ e->accept();
+
+ if (e->state() & TQt::ShiftButton)
+ {
+ if (e->delta() < 0)
+ emit signalShowNextImage();
+ else if (e->delta() > 0)
+ emit signalShowPrevImage();
+ return;
+ }
+ else if (e->state() & TQt::ControlButton)
+ {
+ if (e->delta() < 0)
+ slotDecreaseZoom();
+ else if (e->delta() > 0)
+ slotIncreaseZoom();
+ return;
+ }
+
+ TQScrollView::contentsWheelEvent(e);
+}
+
+bool Canvas::maxZoom()
+{
+ return ((d->zoom * d->zoomMultiplier) >= d->maxZoom);
+}
+
+bool Canvas::minZoom()
+{
+ return ((d->zoom / d->zoomMultiplier) <= d->minZoom);
+}
+
+bool Canvas::exifRotated()
+{
+ return d->im->exifRotated();
+}
+
+double Canvas::snapZoom(double zoom)
+{
+ // If the zoom value gets changed from d->zoom to zoom
+ // across 50%, 100% or fit-to-window, then return the
+ // the corresponding special value. Otherwise zoom is returned unchanged.
+ double fit = calcAutoZoomFactor();
+ TQValueList<double> snapValues;
+ snapValues.append(0.5);
+ snapValues.append(1.0);
+ snapValues.append(fit);
+
+ qHeapSort(snapValues);
+ TQValueList<double>::const_iterator it;
+
+ if (d->zoom < zoom)
+ {
+ for(it = snapValues.constBegin(); it != snapValues.constEnd(); ++it)
+ {
+ double z = *it;
+ if ((d->zoom < z) && (zoom > z))
+ {
+ zoom = z;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // We need to go through the list in reverse order,
+ // however, tqCopyBackward does not seem to work here, so
+ // a simple for loop over integers is used instead.
+ for(int i=snapValues.size()-1; i>=0; i--)
+ {
+ double z = snapValues[i];
+ if ((d->zoom > z) && (zoom < z))
+ {
+ zoom = z;
+ break;
+ }
+ }
+ }
+
+ return zoom;
+}
+
+void Canvas::slotIncreaseZoom()
+{
+ if (maxZoom())
+ return;
+
+ double zoom = d->zoom * d->zoomMultiplier;
+ zoom = snapZoom(zoom);
+ setZoomFactor(zoom);
+}
+
+void Canvas::slotDecreaseZoom()
+{
+ if (minZoom())
+ return;
+
+ double zoom = d->zoom / d->zoomMultiplier;
+ zoom = snapZoom(zoom);
+ setZoomFactor(zoom);
+}
+
+void Canvas::setZoomFactorSnapped(double zoom)
+{
+ double fit = calcAutoZoomFactor();
+
+ if (fabs(zoom-fit) < 0.05)
+ {
+ // If 1.0 or 0.5 are even closer to zoom than fit, then choose these.
+ if (fabs(zoom-fit) > fabs(zoom-1.0) )
+ {
+ zoom = 1.0;
+ }
+ else if (fabs(zoom-fit) > fabs(zoom-0.5) )
+ {
+ zoom = 0.5;
+ }
+ else
+ {
+ zoom = fit;
+ }
+ }
+ else
+ {
+ if (fabs(zoom-1.0) < 0.05)
+ {
+ zoom = 1.0;
+ }
+ if (fabs(zoom-0.5) < 0.05)
+ {
+ zoom = 0.5;
+ }
+ }
+ setZoomFactor(zoom);
+}
+
+double Canvas::zoomFactor()
+{
+ return d->zoom;
+}
+
+void Canvas::setZoomFactor(double zoom)
+{
+ if (d->autoZoom)
+ {
+ d->autoZoom = false;
+ emit signalToggleOffFitToWindow();
+ }
+
+ // Zoom using center of canvas and given zoom factor.
+
+ double cpx = contentsX() + visibleWidth() / 2.0;
+ double cpy = contentsY() + visibleHeight() / 2.0;
+
+ cpx = (cpx / d->tileSize) * floor(d->tileSize / d->zoom);
+ cpy = (cpy / d->tileSize) * floor(d->tileSize / d->zoom);
+
+ d->zoom = zoom;
+
+ d->im->zoom(d->zoom);
+ updateContentsSize(false);
+
+ viewport()->setUpdatesEnabled(false);
+ center((int)((cpx * d->tileSize) / floor(d->tileSize / d->zoom)),
+ (int)((cpy * d->tileSize) / floor(d->tileSize / d->zoom)));
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+
+ emit signalZoomChanged(d->zoom);
+}
+
+void Canvas::fitToSelect()
+{
+ int xSel, ySel, wSel, hSel;
+ d->im->getSelectedArea(xSel, ySel, wSel, hSel);
+
+ if (wSel && hSel )
+ {
+ // If selected area, use center of selection
+ // and recompute zoom factor accordinly.
+ double cpx = xSel + wSel / 2.0;
+ double cpy = ySel + hSel / 2.0;
+
+ double srcWidth = wSel;
+ double srcHeight = hSel;
+ double dstWidth = contentsRect().width();
+ double dstHeight = contentsRect().height();
+
+ d->zoom = TQMIN(dstWidth/srcWidth, dstHeight/srcHeight);
+
+ d->autoZoom = false;
+ emit signalToggleOffFitToWindow();
+ d->im->zoom(d->zoom);
+ updateContentsSize(true);
+
+ viewport()->setUpdatesEnabled(false);
+ center((int)((cpx * d->tileSize) / floor(d->tileSize / d->zoom)),
+ (int)((cpy * d->tileSize) / floor(d->tileSize / d->zoom)));
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+
+ emit signalZoomChanged(d->zoom);
+ }
+}
+
+bool Canvas::fitToWindow()
+{
+ return d->autoZoom;
+}
+
+void Canvas::toggleFitToWindow()
+{
+ d->autoZoom = !d->autoZoom;
+
+ if (d->autoZoom)
+ updateAutoZoom();
+ else
+ {
+ d->zoom = 1.0;
+ emit signalZoomChanged(d->zoom);
+ }
+
+ d->im->zoom(d->zoom);
+ updateContentsSize(false);
+ slotZoomChanged(d->zoom);
+ viewport()->update();
+}
+
+void Canvas::slotRotate90()
+{
+ d->im->rotate90();
+}
+
+void Canvas::slotRotate180()
+{
+ d->im->rotate180();
+}
+
+void Canvas::slotRotate270()
+{
+ d->im->rotate270();
+}
+
+void Canvas::slotFlipHoriz()
+{
+ d->im->flipHoriz();
+}
+
+void Canvas::slotFlipVert()
+{
+ d->im->flipVert();
+}
+
+void Canvas::slotCrop()
+{
+ int x, y, w, h;
+ d->im->getSelectedArea(x, y, w, h);
+
+ if (!w && !h ) // No current selection.
+ return;
+
+ d->im->crop(x, y, w, h);
+}
+
+void Canvas::resizeImage(int w, int h)
+{
+ d->im->resize(w, h);
+}
+
+void Canvas::setBackgroundColor(const TQColor& color)
+{
+ if (d->bgColor == color)
+ return;
+
+ d->bgColor = color;
+ viewport()->update();
+}
+
+void Canvas::setICCSettings(ICCSettingsContainer *cmSettings)
+{
+ d->im->setICCSettings(cmSettings);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::setExposureSettings(ExposureSettingsContainer *expoSettings)
+{
+ d->im->setExposureSettings(expoSettings);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::setExifOrient(bool exifOrient)
+{
+ d->im->setExifOrient(exifOrient);
+ viewport()->update();
+}
+
+void Canvas::increaseGamma()
+{
+ d->im->changeGamma(1);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::decreaseGamma()
+{
+ d->im->changeGamma(-1);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::increaseBrightness()
+{
+ d->im->changeBrightness(1);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::decreaseBrightness()
+{
+ d->im->changeBrightness(-1);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::increaseContrast()
+{
+ d->im->changeContrast(5);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::decreaseContrast()
+{
+ d->im->changeContrast(-5);
+ d->tileCache.clear();
+ viewport()->update();
+}
+
+void Canvas::slotRestore()
+{
+ d->im->restore();
+}
+
+void Canvas::slotUndo(int steps)
+{
+ while(steps > 0)
+ {
+ d->im->undo();
+ --steps;
+ }
+}
+
+void Canvas::getUndoHistory(TQStringList &titles)
+{
+ d->im->getUndoHistory(titles);
+}
+
+void Canvas::getRedoHistory(TQStringList &titles)
+{
+ d->im->getRedoHistory(titles);
+}
+
+void Canvas::slotRedo(int steps)
+{
+ while(steps > 0)
+ {
+ d->im->redo();
+ --steps;
+ }
+}
+
+void Canvas::slotCopy()
+{
+ int x, y, w, h;
+ d->im->getSelectedArea(x, y, w, h);
+
+ if (!w && !h ) // No current selection.
+ return;
+
+ TQApplication::setOverrideCursor (TQt::waitCursor);
+ uchar* data = d->im->getImageSelection();
+ DImg selDImg = DImg(w, h, d->im->sixteenBit(), d->im->hasAlpha(), data);
+ delete [] data;
+
+ TQImage selImg = selDImg.copyTQImage();
+ TQApplication::clipboard()->setData(new TQImageDrag(selImg), TQClipboard::Clipboard);
+ TQApplication::restoreOverrideCursor ();
+}
+
+void Canvas::slotSelected()
+{
+ int x=0, y=0, w=0, h=0;
+
+ if (d->rubber && d->pressedMoved)
+ {
+ TQRect sel = calcSeletedArea();
+ x = sel.x();
+ y = sel.y();
+ w = sel.width();
+ h = sel.height();
+ }
+
+ d->im->setSelectedArea(x, y, w, h);
+}
+
+TQRect Canvas::calcSeletedArea()
+{
+ int x=0, y=0, w=0, h=0;
+ TQRect r(d->rubber->normalize());
+
+ if (r.isValid())
+ {
+ r.moveBy(- d->pixmapRect.x(), - d->pixmapRect.y());
+
+ x = (int)(((double)r.x() / d->tileSize) * floor(d->tileSize / d->zoom));
+ y = (int)(((double)r.y() / d->tileSize) * floor(d->tileSize / d->zoom));
+ w = (int)(((double)r.width() / d->tileSize) * floor(d->tileSize / d->zoom));
+ h = (int)(((double)r.height() / d->tileSize) * floor(d->tileSize / d->zoom));
+
+ x = TQMIN(imageWidth(), TQMAX(x, 0));
+ y = TQMIN(imageHeight(), TQMAX(y, 0));
+ w = TQMIN(imageWidth(), TQMAX(w, 0));
+ h = TQMIN(imageHeight(), TQMAX(h, 0));
+
+ // Avoid empty selection by rubberband - at least mark one pixel
+ // At high zoom factors, the rubberband may operate at subpixel level!
+ if (w == 0)
+ w = 1;
+ if (h == 0)
+ h = 1;
+ }
+
+ return TQRect(x, y, w, h);
+}
+
+void Canvas::slotModified()
+{
+ if (d->autoZoom)
+ updateAutoZoom();
+ d->im->zoom(d->zoom);
+
+ updateContentsSize(true);
+ viewport()->update();
+
+ // To be sure than corner widget used to pan image will be hide/show
+ // accordinly with new image size (if changed).
+ slotZoomChanged(d->zoom);
+
+ emit signalChanged();
+}
+
+void Canvas::slotCornerButtonPressed()
+{
+ if (d->panIconPopup)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ }
+
+ d->panIconPopup = new TDEPopupFrame(this);
+ ImagePanIconWidget *pan = new ImagePanIconWidget(180, 120, d->panIconPopup);
+ d->panIconPopup->setMainWidget(pan);
+
+ TQRect r((int)(contentsX() / d->zoom), (int)(contentsY() / d->zoom),
+ (int)(visibleWidth() / d->zoom), (int)(visibleHeight() / d->zoom));
+ pan->setRegionSelection(r);
+ pan->setMouseFocus();
+
+ connect(pan, TQ_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
+ this, TQ_SLOT(slotPanIconSelectionMoved(const TQRect&, bool)));
+
+ connect(pan, TQ_SIGNAL(signalHiden()),
+ this, TQ_SLOT(slotPanIconHiden()));
+
+ TQPoint g = mapToGlobal(viewport()->pos());
+ g.setX(g.x()+ viewport()->size().width());
+ g.setY(g.y()+ viewport()->size().height());
+ d->panIconPopup->popup(TQPoint(g.x() - d->panIconPopup->width(),
+ g.y() - d->panIconPopup->height()));
+
+ pan->setCursorToLocalRegionSelectionCenter();
+}
+
+void Canvas::slotPanIconHiden()
+{
+ d->cornerButton->blockSignals(true);
+ d->cornerButton->animateClick();
+ d->cornerButton->blockSignals(false);
+}
+
+void Canvas::slotPanIconSelectionMoved(const TQRect& r, bool b)
+{
+ setContentsPos((int)(r.x()*d->zoom), (int)(r.y()*d->zoom));
+
+ if (b)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ slotPanIconHiden();
+ }
+}
+
+void Canvas::slotZoomChanged(double /*zoom*/)
+{
+ updateScrollBars();
+
+ if (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())
+ d->cornerButton->show();
+ else
+ d->cornerButton->hide();
+}
+
+void Canvas::slotSelectAll()
+{
+ if (d->rubber)
+ {
+ delete d->rubber;
+ d->rubber = 0;
+ }
+
+ d->rubber = new TQRect(d->pixmapRect);
+ d->pressedMoved = true;
+ d->tileCache.clear();
+ viewport()->setMouseTracking(true);
+ viewport()->update();
+
+ if (d->im->imageValid())
+ emit signalSelected(true);
+}
+
+void Canvas::slotSelectNone()
+{
+ reset();
+ viewport()->update();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/canvas/canvas.h b/src/utilities/imageeditor/canvas/canvas.h
new file mode 100644
index 00000000..c772098d
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/canvas.h
@@ -0,0 +1,209 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-09
+ * Description : image editor canvas management class
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CANVAS_H
+#define CANVAS_H
+
+// TQt includes.
+
+#include <tqscrollview.h>
+#include <tqrect.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "dimg.h"
+
+class TQString;
+class TQStringList;
+class TQPixmap;
+class TQPaintEvent;
+class TQResizeEvent;
+class TQWheelEvent;
+class TQKeyEvent;
+class TQColor;
+
+namespace Digikam
+{
+
+class CanvasPrivate;
+class DImgInterface;
+class ExposureSettingsContainer;
+class ICCSettingsContainer;
+class IOFileSettingsContainer;
+
+class DIGIKAM_EXPORT Canvas : public TQScrollView
+{
+ TQ_OBJECT
+
+
+public:
+
+ Canvas(TQWidget *parent=0);
+ ~Canvas();
+
+ void load(const TQString& filename, IOFileSettingsContainer *IOFileSettings);
+ void preload(const TQString& filename);
+
+ void saveAs(const TQString& filename, IOFileSettingsContainer *IOFileSettings,
+ bool setExifOrientationTag, const TQString& mimeType=TQString());
+ void resetImage();
+ void switchToLastSaved(const TQString& newFilename);
+ void abortSaving();
+ void setModified();
+ void readMetadataFromFile(const TQString &file);
+ void clearUndoHistory();
+ void setUndoHistoryOrigin();
+ void updateUndoState();
+ DImg currentImage();
+ TQString currentImageFileFormat();
+ TQString currentImageFilePath();
+
+ DImgInterface *interface() const;
+ void makeDefaultEditingCanvas();
+
+ double snapZoom(double z);
+ void setZoomFactorSnapped(double zoom);
+
+ double zoomFactor();
+ void setZoomFactor(double z);
+ bool fitToWindow();
+ bool maxZoom();
+ bool minZoom();
+ bool exifRotated();
+ int imageWidth();
+ int imageHeight();
+ TQRect getSelectedArea();
+
+ // If current image file format is only available in read only,
+ // typicially all RAW image file formats.
+ bool isReadOnly();
+
+ void resizeImage(int w, int h);
+
+ void setBackgroundColor(const TQColor& color);
+ void setICCSettings(ICCSettingsContainer *cmSettings);
+ void setExposureSettings(ExposureSettingsContainer *expoSettings);
+
+ void setExifOrient(bool exifOrient);
+
+ void increaseGamma();
+ void decreaseGamma();
+ void increaseBrightness();
+ void decreaseBrightness();
+ void increaseContrast();
+ void decreaseContrast();
+
+ void getUndoHistory(TQStringList &titles);
+ void getRedoHistory(TQStringList &titles);
+
+ void toggleFitToWindow();
+ void fitToSelect();
+
+signals:
+
+ void signalZoomChanged(double zoom);
+ void signalMaxZoom();
+ void signalMinZoom();
+ void signalChanged();
+ void signalUndoStateChanged(bool, bool, bool);
+ void signalSelected(bool);
+ void signalRightButtonClicked();
+ void signalShowNextImage();
+ void signalShowPrevImage();
+ void signalPrepareToLoad();
+ void signalLoadingStarted(const TQString &filename);
+ void signalLoadingFinished(const TQString &filename, bool success);
+ void signalLoadingProgress(const TQString& filePath, float progress);
+ void signalSavingStarted(const TQString &filename);
+ void signalSavingFinished(const TQString &filename, bool success);
+ void signalSavingProgress(const TQString& filePath, float progress);
+ void signalSelectionChanged(const TQRect&);
+ void signalToggleOffFitToWindow();
+
+public slots:
+
+ void slotIncreaseZoom();
+ void slotDecreaseZoom();
+
+ // image modifiers
+ void slotRotate90();
+ void slotRotate180();
+ void slotRotate270();
+
+ void slotFlipHoriz();
+ void slotFlipVert();
+
+ void slotCrop();
+
+ void slotRestore();
+ void slotUndo(int steps=1);
+ void slotRedo(int steps=1);
+
+ void slotCopy();
+
+ void slotSelectAll();
+ void slotSelectNone();
+
+protected:
+
+ void resizeEvent(TQResizeEvent* e);
+ void viewportPaintEvent(TQPaintEvent *e);
+ void contentsMousePressEvent(TQMouseEvent *e);
+ void contentsMouseMoveEvent(TQMouseEvent *e);
+ void contentsMouseReleaseEvent(TQMouseEvent *e);
+ void contentsWheelEvent(TQWheelEvent *e);
+
+private:
+
+ TQRect calcSeletedArea();
+ double calcAutoZoomFactor();
+ void updateAutoZoom();
+ void updateContentsSize(bool deleteRubber);
+
+ void drawRubber();
+ void paintViewport(const TQRect& er, bool antialias);
+
+ void reset();
+
+private slots:
+
+ void slotSelected();
+ void slotModified();
+ void slotImageLoaded(const TQString& filePath, bool success);
+ void slotImageSaved(const TQString& filePath, bool success);
+ void slotCornerButtonPressed();
+ void slotZoomChanged(double);
+ void slotPanIconSelectionMoved(const TQRect&, bool);
+ void slotPanIconHiden();
+
+private:
+
+ CanvasPrivate *d;
+};
+
+} // namespace Digikam
+
+#endif /* CANVAS_H */
+
diff --git a/src/utilities/imageeditor/canvas/colorcorrectiondlg.cpp b/src/utilities/imageeditor/canvas/colorcorrectiondlg.cpp
new file mode 100644
index 00000000..1403773f
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/colorcorrectiondlg.cpp
@@ -0,0 +1,186 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-05-15
+ * Description : a dialog to see preview ICC color correction
+ * before to apply color profile.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqstring.h>
+#include <tqfileinfo.h>
+#include <tqpushbutton.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <kseparator.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "icctransform.h"
+#include "iccprofileinfodlg.h"
+#include "colorcorrectiondlg.h"
+#include "colorcorrectiondlg.moc"
+
+namespace Digikam
+{
+
+ColorCorrectionDlg::ColorCorrectionDlg(TQWidget* parent, DImg *preview,
+ IccTransform *iccTrans, const TQString& file)
+ : KDialogBase(parent, "", true, TQString(),
+ Help|Ok|Apply|Cancel, Ok, true)
+{
+ m_iccTrans = iccTrans;
+ m_parent = parent;
+ setHelp("iccprofile.anchor", "digikam");
+ setButtonText(Ok, i18n("Convert"));
+ setButtonTip(Ok, i18n("Apply the default color workspace profile to the image"));
+ setButtonText(Cancel, i18n("Do Nothing"));
+ setButtonTip(Cancel, i18n("Do not change the image"));
+ setButtonText(Apply, i18n("Assign"));
+ setButtonTip(Apply, i18n("Only embed the color workspace profile in the image, don't change the image"));
+
+ TQFileInfo fi(file);
+ setCaption(fi.fileName());
+
+ TQWidget *page = new TQWidget(this);
+ TQGridLayout* grid = new TQGridLayout(page, 3, 2, 0, KDialog::spacingHint());
+
+ TQLabel *originalTitle = new TQLabel(i18n("Original Image:"), page);
+ TQLabel *previewOriginal = new TQLabel(page);
+ TQLabel *targetTitle = new TQLabel(i18n("Corrected Image:"), page);
+ TQLabel *previewTarget = new TQLabel(page);
+ TQLabel *logo = new TQLabel(page);
+ TQLabel *message = new TQLabel(page);
+ TQLabel *currentProfileTitle = new TQLabel(i18n("Current workspace color profile:"), page);
+ TQLabel *currentProfileDesc = new TQLabel(TQString("<b>%1</b>").arg(m_iccTrans->getOutpoutProfileDescriptor()), page);
+ TQPushButton *currentProfInfo = new TQPushButton(i18n("Info..."), page);
+ TQLabel *embeddedProfileTitle = new TQLabel(i18n("Embedded color profile:"), page);
+ TQLabel *embeddedProfileDesc = new TQLabel(TQString("<b>%1</b>").arg(m_iccTrans->getEmbeddedProfileDescriptor()), page);
+ TQPushButton *embeddedProfInfo = new TQPushButton(i18n("Info..."), page);
+ KSeparator *line = new KSeparator(TQt::Horizontal, page);
+
+ if (m_iccTrans->embeddedProfile().isEmpty())
+ {
+ message->setText(i18n("<p>This image has not been assigned a color profile.</p>"
+ "<p>Do you want to convert it to your workspace color profile?</p>"));
+
+ line->hide();
+ embeddedProfileTitle->hide();
+ embeddedProfileDesc->hide();
+ embeddedProfInfo->hide();
+ }
+ else
+ {
+ message->setText(i18n("<p>This image has been assigned to a color profile that does not "
+ "match your default workspace color profile.</p>"
+ "<p>Do you want to convert it to your workspace color profile?</p>"));
+ }
+
+ previewOriginal->setPixmap(preview->convertToPixmap());
+ previewTarget->setPixmap(preview->convertToPixmap(m_iccTrans));
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 128, TDEIcon::DefaultState, 0, true));
+
+ grid->addMultiCellWidget(originalTitle, 0, 0, 0, 0);
+ grid->addMultiCellWidget(previewOriginal, 1, 1, 0, 0);
+ grid->addMultiCellWidget(targetTitle, 2, 2, 0, 0);
+ grid->addMultiCellWidget(previewTarget, 3, 3, 0, 0);
+
+ TQVBoxLayout *vlay = new TQVBoxLayout( KDialog::spacingHint() );
+ vlay->addWidget(logo);
+ vlay->addWidget(message);
+
+ vlay->addWidget(new KSeparator(TQt::Horizontal, page));
+ vlay->addWidget(currentProfileTitle);
+ vlay->addWidget(currentProfileDesc);
+
+ TQHBoxLayout *hlay1 = new TQHBoxLayout( KDialog::spacingHint() );
+ hlay1->addWidget(currentProfInfo);
+ hlay1->addStretch();
+ vlay->addLayout(hlay1);
+
+ vlay->addWidget(line);
+ vlay->addWidget(embeddedProfileTitle);
+ vlay->addWidget(embeddedProfileDesc);
+
+ TQHBoxLayout *hlay2 = new TQHBoxLayout( KDialog::spacingHint() );
+ hlay2->addWidget(embeddedProfInfo);
+ hlay2->addStretch();
+ vlay->addLayout(hlay2);
+ vlay->addStretch();
+
+ grid->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(),
+ TQSizePolicy::Minimum, TQSizePolicy::Expanding), 0, 3, 1, 1);
+ grid->addMultiCellLayout(vlay, 0, 3, 2, 2);
+
+ setMainWidget(page);
+
+ // --------------------------------------------------------------------
+
+ connect(currentProfInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotCurrentProfInfo()) );
+
+ connect(embeddedProfInfo, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotEmbeddedProfInfo()) );
+
+ connect(this, TQ_SIGNAL(applyClicked()),
+ this, TQ_SLOT(slotApplyClicked()));
+}
+
+ColorCorrectionDlg::~ColorCorrectionDlg()
+{
+}
+
+void ColorCorrectionDlg::slotCurrentProfInfo()
+{
+ if (m_iccTrans->outputProfile().isEmpty())
+ return;
+
+ ICCProfileInfoDlg infoDlg(m_parent, TQString(), m_iccTrans->outputProfile());
+ infoDlg.exec();
+}
+
+void ColorCorrectionDlg::slotEmbeddedProfInfo()
+{
+ if (m_iccTrans->embeddedProfile().isEmpty())
+ return;
+
+ ICCProfileInfoDlg infoDlg(m_parent, TQString(), m_iccTrans->embeddedProfile());
+ infoDlg.exec();
+}
+
+void ColorCorrectionDlg::slotApplyClicked()
+{
+ DDebug() << "colorcorrectiondlg: Apply pressed" << endl;
+ done(-1);
+}
+
+} // NameSpace Digikam
+
diff --git a/src/utilities/imageeditor/canvas/colorcorrectiondlg.h b/src/utilities/imageeditor/canvas/colorcorrectiondlg.h
new file mode 100644
index 00000000..7cc4c19d
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/colorcorrectiondlg.h
@@ -0,0 +1,72 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-05-15
+ * Description : a dialog to see preview ICC color correction
+ * before to apply color profile.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef COLORCORRECTIONDLG_H
+#define COLORCORRECTIONDLG_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class IccTransform;
+class DImg;
+
+class DIGIKAM_EXPORT ColorCorrectionDlg : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ColorCorrectionDlg(TQWidget *parent, DImg *preview,
+ IccTransform *iccTrans, const TQString& file);
+ ~ColorCorrectionDlg();
+
+private slots:
+
+ void slotCurrentProfInfo();
+ void slotEmbeddedProfInfo();
+ void slotApplyClicked();
+
+private:
+
+ TQWidget *m_parent;
+
+ IccTransform *m_iccTrans;
+};
+
+} // Namespace Digikam
+
+#endif /* COLORCORRECTIONDLG_H */
diff --git a/src/utilities/imageeditor/canvas/dimginterface.cpp b/src/utilities/imageeditor/canvas/dimginterface.cpp
new file mode 100644
index 00000000..8ce1c114
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/dimginterface.cpp
@@ -0,0 +1,1270 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-15
+ * Description : DImg interface for image editor
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define OPACITY 0.7
+#define RCOL 0xAA
+#define GCOL 0xAA
+#define BCOL 0xAA
+
+// C++ includes.
+
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqimage.h>
+#include <tqpixmap.h>
+#include <tqbitmap.h>
+#include <tqcolor.h>
+#include <tqfile.h>
+#include <tqvariant.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdemessagebox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "bcgmodifier.h"
+#include "colorcorrectiondlg.h"
+#include "undomanager.h"
+#include "undoaction.h"
+#include "iccsettingscontainer.h"
+#include "icctransform.h"
+#include "exposurecontainer.h"
+#include "iofilesettingscontainer.h"
+#include "rawimport.h"
+#include "editortooliface.h"
+#include "sharedloadsavethread.h"
+#include "dmetadata.h"
+#include "dimginterface.h"
+#include "dimginterface.moc"
+
+namespace Digikam
+{
+
+class UndoManager;
+
+class DImgInterfacePrivate
+{
+
+public:
+
+ DImgInterfacePrivate()
+ {
+ parent = 0;
+ undoMan = 0;
+ cmSettings = 0;
+ expoSettings = 0;
+ iofileSettings = 0;
+ thread = 0;
+ width = 0;
+ height = 0;
+ origWidth = 0;
+ origHeight = 0;
+ selX = 0;
+ selY = 0;
+ selW = 0;
+ selH = 0;
+ zoom = 1.0;
+ exifOrient = false;
+ valid = false;
+ rotatedOrFlipped = false;
+ }
+
+ bool valid;
+ bool rotatedOrFlipped;
+ bool exifOrient;
+ bool changedBCG;
+
+ int width;
+ int height;
+ int origWidth;
+ int origHeight;
+ int selX;
+ int selY;
+ int selW;
+ int selH;
+
+ float gamma;
+ float brightness;
+ float contrast;
+
+ double zoom;
+
+ // Used by ICC color profile dialog.
+ TQWidget *parent;
+
+ TQString filename;
+ TQString savingFilename;
+
+ DImg image;
+
+ UndoManager *undoMan;
+
+ BCGModifier cmod;
+
+ ICCSettingsContainer *cmSettings;
+
+ ExposureSettingsContainer *expoSettings;
+
+ IOFileSettingsContainer *iofileSettings;
+
+ SharedLoadSaveThread *thread;
+
+ IccTransform monitorICCtrans;
+};
+
+DImgInterface* DImgInterface::m_defaultInterface = 0;
+
+DImgInterface* DImgInterface::defaultInterface()
+{
+ return m_defaultInterface;
+}
+
+void DImgInterface::setDefaultInterface(DImgInterface *defaultInterface)
+{
+ m_defaultInterface = defaultInterface;
+}
+
+DImgInterface::DImgInterface()
+ : TQObject()
+{
+ d = new DImgInterfacePrivate;
+
+ d->undoMan = new UndoManager(this);
+ d->thread = new SharedLoadSaveThread;
+
+ connect( d->thread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg&)),
+ this, TQ_SLOT(slotImageLoaded(const LoadingDescription &, const DImg&)) );
+
+ connect( d->thread, TQ_SIGNAL(signalImageSaved(const TQString&, bool)),
+ this, TQ_SLOT(slotImageSaved(const TQString&, bool)) );
+
+ connect( d->thread, TQ_SIGNAL(signalLoadingProgress(const LoadingDescription &, float)),
+ this, TQ_SLOT(slotLoadingProgress(const LoadingDescription &, float)) );
+
+ connect( d->thread, TQ_SIGNAL(signalSavingProgress(const TQString&, float)),
+ this, TQ_SLOT(slotSavingProgress(const TQString&, float)) );
+}
+
+DImgInterface::~DImgInterface()
+{
+ delete d->undoMan;
+ delete d->thread;
+ delete d;
+ if (m_defaultInterface == this)
+ m_defaultInterface = 0;
+}
+
+void DImgInterface::load(const TQString& filename, IOFileSettingsContainer *iofileSettings,
+ TQWidget *parent)
+{
+ // store here in case filename == d->fileName, and is then reset by resetValues
+ TQString newFileName = filename;
+
+ resetValues();
+
+ d->filename = newFileName;
+ d->iofileSettings = iofileSettings;
+ d->parent = parent;
+
+ if (d->iofileSettings->useRAWImport && DImg::fileFormat(d->filename) == DImg::RAW)
+ {
+ RawImport *rawImport = new RawImport(KURL(d->filename), this);
+ EditorToolIface::editorToolIface()->loadTool(rawImport);
+
+ connect(rawImport, TQ_SIGNAL(okClicked()),
+ this, TQ_SLOT(slotUseRawImportSettings()));
+
+ connect(rawImport, TQ_SIGNAL(cancelClicked()),
+ this, TQ_SLOT(slotUseDefaultSettings()));
+ }
+ else
+ {
+ slotUseDefaultSettings();
+ }
+}
+
+void DImgInterface::slotUseRawImportSettings()
+{
+ RawImport *rawImport = dynamic_cast<RawImport*>(EditorToolIface::editorToolIface()->currentTool());
+
+ d->thread->load(LoadingDescription(d->filename,
+ rawImport->rawDecodingSettings()),
+ SharedLoadSaveThread::AccessModeReadWrite,
+ SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious);
+ emit signalLoadingStarted(d->filename);
+
+ EditorToolIface::editorToolIface()->unLoadTool();
+}
+
+void DImgInterface::slotUseDefaultSettings()
+{
+ d->thread->load(LoadingDescription(d->filename,
+ d->iofileSettings->rawDecodingSettings),
+ SharedLoadSaveThread::AccessModeReadWrite,
+ SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious);
+ emit signalLoadingStarted(d->filename);
+
+ EditorToolIface::editorToolIface()->unLoadTool();
+}
+
+void DImgInterface::resetImage()
+{
+ EditorToolIface::editorToolIface()->unLoadTool();
+ resetValues();
+ d->image.reset();
+}
+
+void DImgInterface::resetValues()
+{
+ d->valid = false;
+ d->filename = TQString();
+ d->width = 0;
+ d->height = 0;
+ d->origWidth = 0;
+ d->origHeight = 0;
+ d->selX = 0;
+ d->selY = 0;
+ d->selW = 0;
+ d->selH = 0;
+ d->gamma = 1.0;
+ d->contrast = 1.0;
+ d->brightness = 0.0;
+ d->changedBCG = false;
+ d->iofileSettings = 0;
+ d->parent = 0;
+
+ d->cmod.reset();
+ d->undoMan->clear();
+}
+
+void DImgInterface::setICCSettings(ICCSettingsContainer *cmSettings)
+{
+ d->cmSettings = cmSettings;
+ d->monitorICCtrans.setProfiles(d->cmSettings->workspaceSetting, d->cmSettings->monitorSetting);
+}
+
+void DImgInterface::setExposureSettings(ExposureSettingsContainer *expoSettings)
+{
+ d->expoSettings = expoSettings;
+}
+
+void DImgInterface::slotImageLoaded(const LoadingDescription &loadingDescription, const DImg& img)
+{
+ const TQString &fileName = loadingDescription.filePath;
+
+ if (fileName != d->filename)
+ return;
+
+ bool valRet = false;
+ d->image = img;
+
+ if (!d->image.isNull())
+ {
+ d->origWidth = d->image.width();
+ d->origHeight = d->image.height();
+ d->valid = true;
+ d->width = d->origWidth;
+ d->height = d->origHeight;
+ valRet = true;
+
+ // Raw files are already rotated properlly by dcraw. Only perform auto-rotation with JPEG/PNG/TIFF file.
+ // We don't have a feedback from dcraw about auto-rotated RAW file during decoding. Well set transformed
+ // flag as well.
+
+ if (d->image.attribute("format").toString() == TQString("RAW"))
+ d->rotatedOrFlipped = true;
+
+ if (d->exifOrient &&
+ (d->image.attribute("format").toString() == TQString("JPEG") ||
+ d->image.attribute("format").toString() == TQString("PNG") ||
+ d->image.attribute("format").toString() == TQString("TIFF")))
+ exifRotate(d->filename);
+
+ if (d->cmSettings->enableCMSetting)
+ {
+ if (TQFile::exists(d->cmSettings->workspaceSetting))
+ {
+ IccTransform trans;
+ TQByteArray fakeProfile;
+
+ // First possibility: image has no embedded profile
+ if(d->image.getICCProfil().isNull())
+ {
+ // Ask or apply?
+ if (d->cmSettings->askOrApplySetting)
+ {
+ if (d->parent) d->parent->setCursor( KCursor::waitCursor() );
+ trans.setProfiles(TQFile::encodeName(d->cmSettings->inputSetting),
+ TQFile::encodeName(d->cmSettings->workspaceSetting));
+
+ // NOTE: If Input color profile do not exist, using built-in sRGB intead.
+ trans.apply(d->image, fakeProfile, d->cmSettings->renderingSetting,
+ d->cmSettings->BPCSetting, false,
+ TQFile::exists(d->cmSettings->inputSetting));
+
+ d->image.getICCProfilFromFile(TQFile::encodeName(d->cmSettings->workspaceSetting));
+ if (d->parent) d->parent->unsetCursor();
+ }
+ else
+ {
+ // To repaint image in canvas before to ask about to apply ICC profile.
+ emit signalImageLoaded(d->filename, valRet);
+
+ DImg preview = d->image.smoothScale(240, 180, TQSize::ScaleMin);
+ trans.setProfiles(TQFile::encodeName(d->cmSettings->inputSetting),
+ TQFile::encodeName(d->cmSettings->workspaceSetting));
+ ColorCorrectionDlg dlg(d->parent, &preview, &trans, fileName);
+
+ switch (dlg.exec())
+ {
+ case TQDialog::Accepted:
+ if (d->parent) d->parent->setCursor( KCursor::waitCursor() );
+
+ // NOTE: If Input color profile do not exist, using built-in sRGB intead.
+ trans.apply(d->image, fakeProfile, d->cmSettings->renderingSetting,
+ d->cmSettings->BPCSetting, false,
+ TQFile::exists(d->cmSettings->inputSetting));
+
+ d->image.getICCProfilFromFile(TQFile::encodeName(d->cmSettings->workspaceSetting));
+ if (d->parent) d->parent->unsetCursor();
+ break;
+ case -1:
+ if (d->parent) d->parent->setCursor( KCursor::waitCursor() );
+ d->image.getICCProfilFromFile(TQFile::encodeName(d->cmSettings->workspaceSetting));
+ if (d->parent) d->parent->unsetCursor();
+ DDebug() << "dimginterface.cpp: Apply pressed" << endl;
+ break;
+ }
+ }
+ }
+ // Second possibility: image has an embedded profile
+ else
+ {
+ trans.getEmbeddedProfile( d->image );
+
+ // Ask or apply?
+ if (d->cmSettings->askOrApplySetting)
+ {
+ if (d->parent) d->parent->setCursor( KCursor::waitCursor() );
+ trans.setProfiles(TQFile::encodeName(d->cmSettings->workspaceSetting));
+ trans.apply(d->image, fakeProfile, d->cmSettings->renderingSetting,
+ d->cmSettings->BPCSetting, false, false);
+ if (d->parent) d->parent->unsetCursor();
+ }
+ else
+ {
+ if (trans.getEmbeddedProfileDescriptor()
+ != trans.getProfileDescription( d->cmSettings->workspaceSetting ))
+ {
+ // Embedded profile and default workspace profile are different: ask to user!
+
+ DDebug() << "Embedded profile: " << trans.getEmbeddedProfileDescriptor() << endl;
+
+ // To repaint image in canvas before to ask about to apply ICC profile.
+ emit signalImageLoaded(d->filename, valRet);
+
+ DImg preview = d->image.smoothScale(240, 180, TQSize::ScaleMin);
+ trans.setProfiles(TQFile::encodeName(d->cmSettings->workspaceSetting));
+ ColorCorrectionDlg dlg(d->parent, &preview, &trans, fileName);
+
+ switch (dlg.exec())
+ {
+ case TQDialog::Accepted:
+ if (d->parent) d->parent->setCursor( KCursor::waitCursor() );
+ trans.apply(d->image, fakeProfile, d->cmSettings->renderingSetting,
+ d->cmSettings->BPCSetting, false, false);
+ d->image.getICCProfilFromFile(TQFile::encodeName(d->cmSettings->workspaceSetting));
+ if (d->parent) d->parent->unsetCursor();
+ break;
+ case -1:
+ if (d->parent) d->parent->setCursor( KCursor::waitCursor() );
+ d->image.getICCProfilFromFile(TQFile::encodeName(d->cmSettings->workspaceSetting));
+ if (d->parent) d->parent->unsetCursor();
+ DDebug() << "dimginterface.cpp: Apply pressed" << endl;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ TQString message = i18n("Cannot find the ICC color-space profile file. "
+ "The ICC profiles path seems to be invalid. "
+ "No color transform will be applied. "
+ "Please check the \"Color Management\" "
+ "configuration in digiKam's setup to verify the ICC path.");
+ KMessageBox::information(d->parent, message);
+ }
+ }
+ }
+ else
+ {
+ valRet = false;
+ }
+
+ emit signalImageLoaded(d->filename, valRet);
+ setModified();
+}
+
+void DImgInterface::slotLoadingProgress(const LoadingDescription &loadingDescription, float progress)
+{
+ if (loadingDescription.filePath == d->filename)
+ emit signalLoadingProgress(loadingDescription.filePath, progress);
+}
+
+bool DImgInterface::exifRotated()
+{
+ return d->rotatedOrFlipped;
+}
+
+void DImgInterface::exifRotate(const TQString& filename)
+{
+ // Rotate image based on EXIF rotate tag
+
+ DMetadata metadata(filename);
+ DMetadata::ImageOrientation orientation = metadata.getImageOrientation();
+
+ if(orientation != DMetadata::ORIENTATION_NORMAL)
+ {
+ switch (orientation)
+ {
+ case DMetadata::ORIENTATION_NORMAL:
+ case DMetadata::ORIENTATION_UNSPECIFIED:
+ break;
+
+ case DMetadata::ORIENTATION_HFLIP:
+ flipHoriz(false);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_180:
+ rotate180(false);
+ break;
+
+ case DMetadata::ORIENTATION_VFLIP:
+ flipVert(false);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_HFLIP:
+ rotate90(false);
+ flipHoriz(false);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90:
+ rotate90(false);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_90_VFLIP:
+ rotate90(false);
+ flipVert(false);
+ break;
+
+ case DMetadata::ORIENTATION_ROT_270:
+ rotate270(false);
+ break;
+ }
+
+ d->rotatedOrFlipped = true;
+ }
+}
+
+void DImgInterface::setExifOrient(bool exifOrient)
+{
+ d->exifOrient = exifOrient;
+}
+
+void DImgInterface::undo()
+{
+ if (!d->undoMan->anyMoreUndo())
+ {
+ emit signalUndoStateChanged(false, d->undoMan->anyMoreRedo(), !d->undoMan->isAtOrigin());
+ return;
+ }
+
+ d->undoMan->undo();
+ emit signalUndoStateChanged(d->undoMan->anyMoreUndo(), d->undoMan->anyMoreRedo(), !d->undoMan->isAtOrigin());
+}
+
+void DImgInterface::redo()
+{
+ if (!d->undoMan->anyMoreRedo())
+ {
+ emit signalUndoStateChanged(d->undoMan->anyMoreUndo(), false, !d->undoMan->isAtOrigin());
+ return;
+ }
+
+ d->undoMan->redo();
+ emit signalUndoStateChanged(d->undoMan->anyMoreUndo(), d->undoMan->anyMoreRedo(), !d->undoMan->isAtOrigin());
+}
+
+void DImgInterface::restore()
+{
+ d->undoMan->clear();
+ load(d->filename, d->iofileSettings);
+}
+
+/*
+This code is unused and untested
+void DImgInterface::save(const TQString& file, IOFileSettingsContainer *iofileSettings)
+{
+ d->cmod.reset();
+ d->cmod.setGamma(d->gamma);
+ d->cmod.setBrightness(d->brightness);
+ d->cmod.setContrast(d->contrast);
+ d->cmod.applyBCG(d->image);
+
+ d->cmod.reset();
+ d->gamma = 1.0;
+ d->contrast = 1.0;
+ d->brightness = 0.0;
+
+ TQString currentMimeType(TQImageIO::imageFormat(d->filename));
+
+ d->needClearUndoManager = true;
+
+ saveAction(file, iofileSettings, currentMimeType);
+}
+*/
+
+void DImgInterface::saveAs(const TQString& fileName, IOFileSettingsContainer *iofileSettings,
+ bool setExifOrientationTag, const TQString& givenMimeType)
+{
+ // No need to toggle off undo, redo or save action during saving using
+ // signalUndoStateChanged(), this is will done by GUI implementation directly.
+
+ if (d->changedBCG)
+ {
+ d->cmod.reset();
+ d->cmod.setGamma(d->gamma);
+ d->cmod.setBrightness(d->brightness);
+ d->cmod.setContrast(d->contrast);
+ d->cmod.applyBCG(d->image);
+ }
+
+ // Try hard to find a mimetype.
+ TQString mimeType = givenMimeType;
+
+ // This is possibly empty
+ if (mimeType.isEmpty())
+ mimeType = getImageFormat();
+
+ DDebug() << "Saving to :" << TQFile::encodeName(fileName).data() << " ("
+ << mimeType << ")" << endl;
+
+ // JPEG file format.
+ if ( mimeType.upper() == TQString("JPG") || mimeType.upper() == TQString("JPEG") ||
+ mimeType.upper() == TQString("JPE"))
+ {
+ d->image.setAttribute("quality", iofileSettings->JPEGCompression);
+ d->image.setAttribute("subsampling", iofileSettings->JPEGSubSampling);
+ }
+
+ // PNG file format.
+ if ( mimeType.upper() == TQString("PNG") )
+ d->image.setAttribute("quality", iofileSettings->PNGCompression);
+
+ // TIFF file format.
+ if ( mimeType.upper() == TQString("TIFF") || mimeType.upper() == TQString("TIF") )
+ d->image.setAttribute("compress", iofileSettings->TIFFCompression);
+
+ // JPEG 2000 file format.
+ if ( mimeType.upper() == TQString("JP2") || mimeType.upper() == TQString("JPX") ||
+ mimeType.upper() == TQString("JPC") || mimeType.upper() == TQString("PGX"))
+ {
+ if (iofileSettings->JPEG2000LossLess)
+ d->image.setAttribute("quality", 100); // LossLess compression
+ else
+ d->image.setAttribute("quality", iofileSettings->JPEG2000Compression);
+ }
+
+ d->savingFilename = fileName;
+
+ // Get image Exif/Iptc data.
+ DMetadata meta;
+ meta.setExif(d->image.getExif());
+ meta.setIptc(d->image.getIptc());
+
+ // Update Iptc preview.
+ // NOTE: see B.K.O #130525. a JPEG segment is limited to 64K. If the IPTC byte array is
+ // bigger than 64K duing of image preview tag size, the target JPEG image will be
+ // broken. Note that IPTC image preview tag is limited to 256K!!!
+ // There is no limitation with TIFF and PNG about IPTC byte array size.
+
+ TQImage preview = d->image.smoothScale(1280, 1024, TQSize::ScaleMin).copyTQImage();
+
+ if ( mimeType.upper() != TQString("JPG") && mimeType.upper() != TQString("JPEG") &&
+ mimeType.upper() != TQString("JPE"))
+ {
+ // Non JPEG file, we update IPTC preview
+ meta.setImagePreview(preview);
+ }
+ else
+ {
+ // JPEG file, we remove IPTC preview.
+ meta.removeIptcTag("Iptc.Application2.Preview");
+ meta.removeIptcTag("Iptc.Application2.PreviewFormat");
+ meta.removeIptcTag("Iptc.Application2.PreviewVersion");
+ }
+
+ // Update Exif thumbnail.
+ TQImage thumb = preview.smoothScale(160, 120, TQImage::ScaleMin);
+ meta.setExifThumbnail(thumb);
+
+ // Update Exif Image dimensions.
+ meta.setImageDimensions(d->image.size());
+
+ // Update Exif Document Name tag with the original file name.
+ meta.setExifTagString("Exif.Image.DocumentName", getImageFileName());
+
+ // Update Exif Orientation tag if necessary.
+ if( setExifOrientationTag )
+ meta.setImageOrientation(DMetadata::ORIENTATION_NORMAL);
+
+ // Store new Exif/Iptc data into image.
+ d->image.setExif(meta.getExif());
+ d->image.setIptc(meta.getIptc());
+
+ d->thread->save(d->image, fileName, mimeType);
+}
+
+void DImgInterface::slotImageSaved(const TQString& filePath, bool success)
+{
+ if (filePath != d->savingFilename)
+ return;
+
+ if (!success)
+ DWarning() << "error saving image '" << TQFile::encodeName(filePath).data() << endl;
+
+ emit signalImageSaved(filePath, success);
+ emit signalUndoStateChanged(d->undoMan->anyMoreUndo(), d->undoMan->anyMoreRedo(), !d->undoMan->isAtOrigin());
+}
+
+void DImgInterface::slotSavingProgress(const TQString& filePath, float progress)
+{
+ if (filePath == d->savingFilename)
+ emit signalSavingProgress(filePath, progress);
+}
+
+void DImgInterface::abortSaving()
+{
+ // failure will be reported by a signal
+ d->thread->stopSaving(d->savingFilename);
+}
+
+void DImgInterface::switchToLastSaved(const TQString& newFilename)
+{
+ // Higher level wants to use the current DImg object to represent the file
+ // it has previously been saved to.
+ d->filename = newFilename;
+
+ // Currently the only place where a DImg is connected to the file it originates from
+ // is the format attribute.
+ TQString savedformat = d->image.attribute("savedformat").toString();
+ if (!savedformat.isEmpty())
+ d->image.setAttribute("format", savedformat);
+}
+
+void DImgInterface::setModified()
+{
+ emit signalModified();
+ emit signalUndoStateChanged(d->undoMan->anyMoreUndo(), d->undoMan->anyMoreRedo(), !d->undoMan->isAtOrigin());
+}
+
+void DImgInterface::readMetadataFromFile(const TQString &file)
+{
+ DMetadata meta(file);
+
+ //TODO: code is essentially the same as DImgLoader::readMetadata,
+ // put both in a common place.
+ if (!meta.getComments().isNull())
+ d->image.setComments(meta.getComments());
+ if (!meta.getExif().isNull())
+ d->image.setExif(meta.getExif());
+ if (!meta.getIptc().isNull())
+ d->image.setIptc(meta.getIptc());
+}
+
+void DImgInterface::clearUndoManager()
+{
+ d->undoMan->clear();
+ d->undoMan->setOrigin();
+ emit signalUndoStateChanged(false, false, false);
+}
+
+void DImgInterface::setUndoManagerOrigin()
+{
+ d->undoMan->setOrigin();
+ emit signalUndoStateChanged(d->undoMan->anyMoreUndo(), d->undoMan->anyMoreRedo(), !d->undoMan->isAtOrigin());
+}
+
+void DImgInterface::updateUndoState()
+{
+ emit signalUndoStateChanged(d->undoMan->anyMoreUndo(), d->undoMan->anyMoreRedo(), !d->undoMan->isAtOrigin());
+}
+
+bool DImgInterface::imageValid()
+{
+ return !d->image.isNull();
+}
+
+int DImgInterface::width()
+{
+ return d->width;
+}
+
+int DImgInterface::height()
+{
+ return d->height;
+}
+
+int DImgInterface::origWidth()
+{
+ return d->origWidth;
+}
+
+int DImgInterface::origHeight()
+{
+ return d->origHeight;
+}
+
+int DImgInterface::bytesDepth()
+{
+ return d->image.bytesDepth();
+}
+
+bool DImgInterface::sixteenBit()
+{
+ return d->image.sixteenBit();
+}
+
+bool DImgInterface::hasAlpha()
+{
+ return d->image.hasAlpha();
+}
+
+bool DImgInterface::isReadOnly()
+{
+ if (d->image.isNull())
+ return true;
+ else
+ return d->image.isReadOnly();
+}
+
+void DImgInterface::setSelectedArea(int x, int y, int w, int h)
+{
+ d->selX = x;
+ d->selY = y;
+ d->selW = w;
+ d->selH = h;
+}
+
+void DImgInterface::getSelectedArea(int& x, int& y, int& w, int& h)
+{
+ x = d->selX;
+ y = d->selY;
+ w = d->selW;
+ h = d->selH;
+}
+
+void DImgInterface::paintOnDevice(TQPaintDevice* p,
+ int sx, int sy, int sw, int sh,
+ int dx, int dy, int dw, int dh,
+ int /*antialias*/)
+{
+ if (d->image.isNull())
+ return;
+
+ DImg img = d->image.smoothScaleSection(sx, sy, sw, sh, dw, dh);
+ d->cmod.applyBCG(img);
+ img.convertDepth(32);
+
+ if (d->cmSettings->enableCMSetting && d->cmSettings->managedViewSetting)
+ {
+ TQPixmap pix(img.convertToPixmap(&d->monitorICCtrans));
+ bitBlt(p, dx, dy, &pix, 0, 0);
+ }
+ else
+ {
+ TQPixmap pix(img.convertToPixmap());
+ bitBlt(p, dx, dy, &pix, 0, 0);
+ }
+
+ // Show the Over/Under exposure pixels indicators
+
+ if (d->expoSettings->underExposureIndicator || d->expoSettings->overExposureIndicator)
+ {
+ TQImage pureColorMask = d->image.copy(sx, sy, sw, sh).pureColorMask(d->expoSettings);
+ TQPixmap pixMask(pureColorMask.scale(dw, dh));
+ bitBlt(p, dx, dy, &pixMask, 0, 0);
+ }
+}
+
+void DImgInterface::paintOnDevice(TQPaintDevice* p,
+ int sx, int sy, int sw, int sh,
+ int dx, int dy, int dw, int dh,
+ int mx, int my, int mw, int mh,
+ int /*antialias*/)
+{
+ if (d->image.isNull())
+ return;
+
+ DImg img = d->image.smoothScaleSection(sx, sy, sw, sh, dw, dh);
+ d->cmod.applyBCG(img);
+ img.convertDepth(32);
+
+ uint* data = (uint*)img.bits();
+ uchar r, g, b, a;
+
+ for (int j=0; j < (int)img.height(); j++)
+ {
+ for (int i=0; i < (int)img.width(); i++)
+ {
+ if (i < (mx-dx) || i > (mx-dx+mw-1) ||
+ j < (my-dy) || j > (my-dy+mh-1))
+ {
+ a = (*data >> 24) & 0xff;
+ r = (*data >> 16) & 0xff;
+ g = (*data >> 8) & 0xff;
+ b = (*data ) & 0xff;
+
+ r += (uchar)((RCOL - r) * OPACITY);
+ g += (uchar)((GCOL - g) * OPACITY);
+ b += (uchar)((BCOL - b) * OPACITY);
+
+ *data = (a << 24) | (r << 16) | (g << 8) | b;
+ }
+
+ data++;
+ }
+ }
+
+ if (d->cmSettings->enableCMSetting && d->cmSettings->managedViewSetting)
+ {
+ TQPixmap pix(img.convertToPixmap(&d->monitorICCtrans));
+ bitBlt(p, dx, dy, &pix, 0, 0);
+ }
+ else
+ {
+ TQPixmap pix(img.convertToPixmap());
+ bitBlt(p, dx, dy, &pix, 0, 0);
+ }
+
+ // Show the Over/Under exposure pixels indicators
+
+ if (d->expoSettings->underExposureIndicator || d->expoSettings->overExposureIndicator)
+ {
+ TQImage pureColorMask = d->image.copy(sx, sy, sw, sh).pureColorMask(d->expoSettings);
+ TQPixmap pixMask(pureColorMask.scale(dw, dh));
+ bitBlt(p, dx, dy, &pixMask, 0, 0);
+ }
+}
+
+void DImgInterface::zoom(double val)
+{
+ d->zoom = val;
+ d->width = (int)(d->origWidth * val);
+ d->height = (int)(d->origHeight * val);
+}
+
+void DImgInterface::rotate90(bool saveUndo)
+{
+ if (saveUndo)
+ {
+ d->undoMan->addAction(new UndoActionRotate(this, UndoActionRotate::R90));
+ }
+
+ d->image.rotate(DImg::ROT90);
+ d->origWidth = d->image.width();
+ d->origHeight = d->image.height();
+
+ setModified();
+}
+
+void DImgInterface::rotate180(bool saveUndo)
+{
+ if (saveUndo)
+ {
+ d->undoMan->addAction(new UndoActionRotate(this, UndoActionRotate::R180));
+ }
+
+ d->image.rotate(DImg::ROT180);
+ d->origWidth = d->image.width();
+ d->origHeight = d->image.height();
+
+ setModified();
+}
+
+void DImgInterface::rotate270(bool saveUndo)
+{
+ if (saveUndo)
+ {
+ d->undoMan->addAction(new UndoActionRotate(this, UndoActionRotate::R270));
+ }
+
+ d->image.rotate(DImg::ROT270);
+ d->origWidth = d->image.width();
+ d->origHeight = d->image.height();
+
+ setModified();
+}
+
+void DImgInterface::flipHoriz(bool saveUndo)
+{
+ if (saveUndo)
+ {
+ d->undoMan->addAction(new UndoActionFlip(this, UndoActionFlip::Horizontal));
+ }
+
+ d->image.flip(DImg::HORIZONTAL);
+
+ setModified();
+}
+
+void DImgInterface::flipVert(bool saveUndo)
+{
+ if (saveUndo)
+ {
+ d->undoMan->addAction(new UndoActionFlip(this, UndoActionFlip::Vertical));
+ }
+
+ d->image.flip(DImg::VERTICAL);
+
+ setModified();
+}
+
+void DImgInterface::crop(int x, int y, int w, int h)
+{
+ d->undoMan->addAction(new UndoActionIrreversible(this, "Crop"));
+
+ d->image.crop(x, y, w, h);
+
+ d->origWidth = d->image.width();
+ d->origHeight = d->image.height();
+
+ setModified();
+}
+
+void DImgInterface::resize(int w, int h)
+{
+ d->undoMan->addAction(new UndoActionIrreversible(this, "Resize"));
+
+ d->image.resize(w, h);
+
+ d->origWidth = d->image.width();
+ d->origHeight = d->image.height();
+
+ setModified();
+}
+
+void DImgInterface::changeGamma(double gamma)
+{
+ d->undoMan->addAction(new UndoActionBCG(this, d->gamma, d->brightness,
+ d->contrast, gamma, d->brightness,
+ d->contrast));
+
+ d->gamma += gamma/10.0;
+
+ d->cmod.reset();
+ d->cmod.setGamma(d->gamma);
+ d->cmod.setBrightness(d->brightness);
+ d->cmod.setContrast(d->contrast);
+ d->changedBCG = true;
+
+ setModified();
+}
+
+void DImgInterface::changeBrightness(double brightness)
+{
+ d->undoMan->addAction(new UndoActionBCG(this, d->gamma, d->brightness,
+ d->contrast, d->gamma, brightness,
+ d->contrast));
+
+ d->brightness += brightness/100.0;
+
+ d->cmod.reset();
+ d->cmod.setGamma(d->gamma);
+ d->cmod.setBrightness(d->brightness);
+ d->cmod.setContrast(d->contrast);
+ d->changedBCG = true;
+
+ setModified();
+}
+
+void DImgInterface::changeContrast(double contrast)
+{
+ d->undoMan->addAction(new UndoActionBCG(this, d->gamma, d->brightness,
+ d->contrast, d->gamma, d->brightness,
+ contrast));
+
+ d->contrast += contrast/100.0;
+
+ d->cmod.reset();
+ d->cmod.setGamma(d->gamma);
+ d->cmod.setBrightness(d->brightness);
+ d->cmod.setContrast(d->contrast);
+ d->changedBCG = true;
+
+ setModified();
+}
+
+void DImgInterface::changeBCG(double gamma, double brightness, double contrast)
+{
+ d->gamma = gamma;
+ d->brightness = brightness;
+ d->contrast = contrast;
+
+ d->cmod.reset();
+ d->cmod.setGamma(d->gamma);
+ d->cmod.setBrightness(d->brightness);
+ d->cmod.setContrast(d->contrast);
+ d->changedBCG = true;
+
+ setModified();
+}
+
+void DImgInterface::setBCG(double brightness, double contrast, double gamma)
+{
+ d->undoMan->addAction(new UndoActionIrreversible(this, "Brightness, Contrast, Gamma"));
+
+ d->cmod.reset();
+ d->cmod.setGamma(gamma);
+ d->cmod.setBrightness(brightness);
+ d->cmod.setContrast(contrast);
+ d->cmod.applyBCG(d->image);
+
+ d->cmod.reset();
+ d->gamma = 1.0;
+ d->contrast = 1.0;
+ d->brightness = 0.0;
+ d->changedBCG = false;
+
+ setModified();
+}
+
+void DImgInterface::convertDepth(int depth)
+{
+ d->undoMan->addAction(new UndoActionIrreversible(this, "Convert Color Depth"));
+ d->image.convertDepth(depth);
+
+ setModified();
+}
+
+DImg* DImgInterface::getImg()
+{
+ if (!d->image.isNull())
+ {
+ return &d->image;
+ }
+ else
+ {
+ DWarning() << k_funcinfo << "d->image is NULL" << endl;
+ return 0;
+ }
+}
+
+uchar* DImgInterface::getImage()
+{
+ if (!d->image.isNull())
+ {
+ return d->image.bits();
+ }
+ else
+ {
+ DWarning() << k_funcinfo << "d->image is NULL" << endl;
+ return 0;
+ }
+}
+
+void DImgInterface::putImage(const TQString &caller, uchar* data, int w, int h)
+{
+ putImage(caller, data, w, h, d->image.sixteenBit());
+}
+
+void DImgInterface::putImage(const TQString &caller, uchar* data, int w, int h, bool sixteenBit)
+{
+ d->undoMan->addAction(new UndoActionIrreversible(this, caller));
+ putImage(data, w, h, sixteenBit);
+}
+
+void DImgInterface::putImage(uchar* data, int w, int h)
+{
+ putImage(data, w, h, d->image.sixteenBit());
+}
+
+void DImgInterface::putImage(uchar* data, int w, int h, bool sixteenBit)
+{
+ if (d->image.isNull())
+ {
+ DWarning() << k_funcinfo << "d->image is NULL" << endl;
+ return;
+ }
+
+ if (!data)
+ {
+ DWarning() << k_funcinfo << "New image is NULL" << endl;
+ return;
+ }
+
+ if (w == -1 && h == -1)
+ {
+ // New image size
+ w = d->origWidth;
+ h = d->origHeight;
+ }
+ else
+ {
+ // New image size == original size
+ d->origWidth = w;
+ d->origHeight = h;
+ }
+
+ //DDebug() << k_funcinfo << data << " " << w << " " << h << endl;
+ d->image.putImageData(w, h, sixteenBit, d->image.hasAlpha(), data);
+
+ setModified();
+}
+
+void DImgInterface::setEmbeddedICCToOriginalImage( TQString profilePath)
+{
+ if (d->image.isNull())
+ {
+ DWarning() << k_funcinfo << "d->image is NULL" << endl;
+ return;
+ }
+
+ DDebug() << k_funcinfo << "Embedding profile: " << profilePath << endl;
+ d->image.getICCProfilFromFile( TQFile::encodeName(profilePath));
+ setModified();
+}
+
+uchar* DImgInterface::getImageSelection()
+{
+ if (!d->selW || !d->selH)
+ return 0;
+
+ if (!d->image.isNull())
+ {
+ DImg im = d->image.copy(d->selX, d->selY, d->selW, d->selH);
+ return im.stripImageData();
+ }
+
+ return 0;
+}
+
+void DImgInterface::putImageSelection(const TQString &caller, uchar* data)
+{
+ if (!data || d->image.isNull())
+ return;
+
+ d->undoMan->addAction(new UndoActionIrreversible(this, caller));
+
+ d->image.bitBltImage(data, 0, 0, d->selW, d->selH, d->selX, d->selY, d->selW, d->selH, d->image.bytesDepth());
+
+ setModified();
+}
+
+void DImgInterface::getUndoHistory(TQStringList &titles)
+{
+ d->undoMan->getUndoHistory(titles);
+}
+
+void DImgInterface::getRedoHistory(TQStringList &titles)
+{
+ d->undoMan->getRedoHistory(titles);
+}
+
+TQByteArray DImgInterface::getEmbeddedICC()
+{
+ return d->image.getICCProfil();
+}
+
+TQByteArray DImgInterface::getExif()
+{
+ return d->image.getExif();
+}
+
+TQByteArray DImgInterface::getIptc()
+{
+ return d->image.getIptc();
+}
+
+TQString DImgInterface::getImageFilePath()
+{
+ return d->filename;
+}
+
+TQString DImgInterface::getImageFileName()
+{
+ return d->filename.section( '/', -1 );
+}
+
+TQString DImgInterface::getImageFormat()
+{
+ if (d->image.isNull())
+ return TQString();
+
+ TQString mimeType = d->image.attribute("format").toString();
+ // It is a bug in the loader if format attribute is not given
+ if (mimeType.isEmpty())
+ {
+ DWarning() << "DImg object does not contain attribute \"format\"" << endl;
+ mimeType = TQImageIO::imageFormat(d->filename);
+ }
+ return mimeType;
+}
+
+ICCSettingsContainer* DImgInterface::getICCSettings()
+{
+ return d->cmSettings;
+}
+
+TQPixmap DImgInterface::convertToPixmap(DImg& img)
+{
+ if (d->cmSettings->enableCMSetting && d->cmSettings->managedViewSetting)
+ return img.convertToPixmap(&d->monitorICCtrans);
+
+ return img.convertToPixmap();
+}
+
+TQColor DImgInterface::underExposureColor()
+{
+ return d->expoSettings->underExposureColor;
+}
+
+TQColor DImgInterface::overExposureColor()
+{
+ return d->expoSettings->overExposureColor;
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/imageeditor/canvas/dimginterface.h b/src/utilities/imageeditor/canvas/dimginterface.h
new file mode 100644
index 00000000..9a41eb05
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/dimginterface.h
@@ -0,0 +1,200 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-01-15
+ * Description : DImg interface for image editor
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef DIMGINTERFACE_H
+#define DIMGINTERFACE_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "dimg.h"
+
+class TQWidget;
+class TQPixmap;
+
+namespace Digikam
+{
+
+class ICCSettingsContainer;
+class ExposureSettingsContainer;
+class IOFileSettingsContainer;
+class LoadingDescription;
+class DImgInterfacePrivate;
+
+class DIGIKAM_EXPORT DImgInterface : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ static DImgInterface* defaultInterface();
+ static void setDefaultInterface(DImgInterface *defaultInterface);
+
+ DImgInterface();
+ ~DImgInterface();
+
+ void load(const TQString& filename, IOFileSettingsContainer *iofileSettings, TQWidget *parent=0);
+
+ void setICCSettings(ICCSettingsContainer *cmSettings);
+ void setExposureSettings(ExposureSettingsContainer *expoSettings);
+ void setExifOrient(bool exifOrient);
+
+ void undo();
+ void redo();
+ void restore();
+
+ void saveAs(const TQString& file, IOFileSettingsContainer *iofileSettings,
+ bool setExifOrientationTag, const TQString& mimeType=TQString());
+
+ void switchToLastSaved(const TQString& newFilename);
+ void abortSaving();
+ void setModified();
+ void readMetadataFromFile(const TQString &file);
+ void clearUndoManager();
+ void setUndoManagerOrigin();
+ void updateUndoState();
+ void resetImage();
+
+ void zoom(double val);
+
+ void paintOnDevice(TQPaintDevice* p,
+ int sx, int sy, int sw, int sh,
+ int dx, int dy, int dw, int dh,
+ int antialias);
+ void paintOnDevice(TQPaintDevice* p,
+ int sx, int sy, int sw, int sh,
+ int dx, int dy, int dw, int dh,
+ int mx, int my, int mw, int mh,
+ int antialias);
+
+ bool imageValid();
+ int width();
+ int height();
+ int origWidth();
+ int origHeight();
+ int bytesDepth();
+ bool hasAlpha();
+ bool sixteenBit();
+ bool exifRotated();
+ bool isReadOnly();
+
+ void setSelectedArea(int x, int y, int w, int h);
+ void getSelectedArea(int& x, int& y, int& w, int& h);
+
+ void rotate90(bool saveUndo=true);
+ void rotate180(bool saveUndo=true);
+ void rotate270(bool saveUndo=true);
+
+ void flipHoriz(bool saveUndo=true);
+ void flipVert(bool saveUndo=true);
+
+ void crop(int x, int y, int w, int h);
+
+ void resize(int w, int h);
+
+ void changeGamma(double gamma);
+ void changeBrightness(double brightness);
+ void changeContrast(double contrast);
+ void changeBCG(double gamma, double brightness, double contrast);
+
+ void setBCG(double brightness, double contrast, double gamma);
+
+ void convertDepth(int depth);
+
+ void getUndoHistory(TQStringList &titles);
+ void getRedoHistory(TQStringList &titles);
+
+ DImg* getImg();
+ uchar* getImage();
+
+ void putImage(uchar* data, int w, int h);
+ void putImage(uchar* data, int w, int h, bool sixteenBit);
+ void putImage(const TQString &caller, uchar* data, int w, int h);
+ void putImage(const TQString &caller, uchar* data, int w, int h, bool sixteenBit);
+
+ uchar* getImageSelection();
+ void putImageSelection(const TQString &caller, uchar* data);
+
+ void setEmbeddedICCToOriginalImage( TQString profilePath);
+
+ /** Convert a DImg image to a pixmap for screen using color
+ managemed view if necessary */
+ TQPixmap convertToPixmap(DImg& img);
+
+ TQByteArray getEmbeddedICC();
+ TQByteArray getExif();
+ TQByteArray getIptc();
+
+ ICCSettingsContainer *getICCSettings();
+
+ TQString getImageFileName();
+ TQString getImageFilePath();
+ TQString getImageFormat();
+
+ TQColor underExposureColor();
+ TQColor overExposureColor();
+
+protected slots:
+
+ void slotImageLoaded(const LoadingDescription &loadingDescription, const DImg& img);
+ void slotImageSaved(const TQString& filePath, bool success);
+ void slotLoadingProgress(const LoadingDescription &loadingDescription, float progress);
+ void slotSavingProgress(const TQString& filePath, float progress);
+
+signals:
+
+ void signalModified();
+ void signalUndoStateChanged(bool moreUndo, bool moreRedo, bool canSave);
+ void signalLoadingStarted(const TQString& filename);
+ void signalLoadingProgress(const TQString& filePath, float progress);
+ void signalImageLoaded(const TQString& filePath, bool success);
+ void signalSavingProgress(const TQString& filePath, float progress);
+ void signalImageSaved(const TQString& filePath, bool success);
+
+private slots:
+
+ void slotUseRawImportSettings();
+ void slotUseDefaultSettings();
+
+private:
+
+ void exifRotate(const TQString& filename);
+ void resetValues();
+
+private:
+
+ static DImgInterface *m_defaultInterface;
+
+ DImgInterfacePrivate *d;
+};
+
+} // namespace Digikam
+
+#endif /* DIMGINTERFACE_H */
diff --git a/src/utilities/imageeditor/canvas/iccsettingscontainer.h b/src/utilities/imageeditor/canvas/iccsettingscontainer.h
new file mode 100644
index 00000000..eadb12fb
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/iccsettingscontainer.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-12-08
+ * Description : ICC Settings Container.
+ *
+ * Copyright (C) 2005-2007 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef ICCSETTINGSCONTAINER_H
+#define ICCSETTINGSCONTAINER_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT ICCSettingsContainer
+{
+
+public:
+
+ ICCSettingsContainer()
+ {
+ enableCMSetting = false; // NOTE: by default, ICC color management is disable.
+
+ askOrApplySetting = false;
+ BPCSetting = false;
+ managedViewSetting = false;
+
+ renderingSetting = 0;
+
+ workspaceSetting = TQString();
+ monitorSetting = TQString();
+ inputSetting = TQString();
+ proofSetting = TQString();
+ };
+
+ ~ICCSettingsContainer(){};
+
+public:
+
+ bool enableCMSetting;
+
+ // FALSE -> apply
+ // TRUE -> ask
+ bool askOrApplySetting;
+ bool BPCSetting;
+ bool managedViewSetting;
+
+ int renderingSetting;
+
+ TQString workspaceSetting;
+ TQString monitorSetting;
+ TQString inputSetting;
+ TQString proofSetting;
+};
+
+} // namespace Digikam
+
+#endif // ICCSETTINGSCONTAINER_H
diff --git a/src/utilities/imageeditor/canvas/imageplugin.cpp b/src/utilities/imageeditor/canvas/imageplugin.cpp
new file mode 100644
index 00000000..b330cb5a
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/imageplugin.cpp
@@ -0,0 +1,68 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : image plugins interface for image editor
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "editortool.h"
+#include "editortooliface.h"
+#include "imageplugin.h"
+#include "imageplugin.moc"
+
+namespace Digikam
+{
+
+ImagePlugin::ImagePlugin(TQObject *parent, const char* name)
+ : TQObject(parent, name)
+{
+}
+
+ImagePlugin::~ImagePlugin()
+{
+}
+
+void ImagePlugin::setEnabledSelectionActions(bool)
+{
+}
+
+void ImagePlugin::setEnabledActions(bool)
+{
+}
+
+void ImagePlugin::loadTool(EditorTool* tool)
+{
+ EditorToolIface::editorToolIface()->loadTool(tool);
+
+ connect(tool, TQ_SIGNAL(okClicked()),
+ this, TQ_SLOT(slotToolDone()));
+
+ connect(tool, TQ_SIGNAL(cancelClicked()),
+ this, TQ_SLOT(slotToolDone()));
+}
+
+void ImagePlugin::slotToolDone()
+{
+ EditorToolIface::editorToolIface()->unLoadTool();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/canvas/imageplugin.h b/src/utilities/imageeditor/canvas/imageplugin.h
new file mode 100644
index 00000000..ef506256
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/imageplugin.h
@@ -0,0 +1,70 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : image plugins interface for image editor.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGIN_H
+#define IMAGEPLUGIN_H
+
+// TQt includes.
+
+#include <tqobject.h>
+
+// KDE includes.
+
+#include <kxmlguiclient.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQWidget;
+
+namespace Digikam
+{
+
+class EditorTool;
+
+class DIGIKAM_EXPORT ImagePlugin : public TQObject, public KXMLGUIClient
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImagePlugin(TQObject *parent, const char* name=0);
+ virtual ~ImagePlugin();
+
+ virtual void setEnabledSelectionActions(bool enable);
+ virtual void setEnabledActions(bool enable);
+
+ void loadTool(EditorTool* tool);
+
+private slots:
+
+ void slotToolDone();
+};
+
+} //namespace Digikam
+
+#endif /* IMAGEPLUGIN_H */
+
diff --git a/src/utilities/imageeditor/canvas/imagepluginloader.cpp b/src/utilities/imageeditor/canvas/imagepluginloader.cpp
new file mode 100644
index 00000000..43c8a16c
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/imagepluginloader.cpp
@@ -0,0 +1,278 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : image plugins loader for image editor.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// KDE includes.
+
+#include <ktrader.h>
+#include <tdeparts/componentfactory.h>
+#include <tdeapplication.h>
+#include <tdelocale.h>
+#include <kxmlguiclient.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "splashscreen.h"
+#include "imagepluginloader.h"
+
+namespace Digikam
+{
+
+// List of obsolete image plugins name.
+
+static const char* ObsoleteImagePluginsList[] =
+{
+ "digikamimageplugin_blowup", // Merged with "Resize" tool since 0.9.2.
+ "digikamimageplugin_solarize", // Renamed "ColorFx" since 0.9.2.
+ "digikamimageplugin_unsharp", // Merged with "Sharpen" tool since 0.9.2.
+ "digikamimageplugin_refocus", // Merged with "Sharpen" tool since 0.9.2.
+ "digikamimageplugin_despeckle", // Renamed "Noise Reduction" since 0.9.2.
+ "-1"
+};
+
+class ImagePluginLoaderPrivate
+{
+
+public:
+
+ typedef TQPair<TQString, ImagePlugin*> PluginType;
+ typedef TQValueList< PluginType > PluginList;
+
+public:
+
+ ImagePluginLoaderPrivate()
+ {
+ splash = 0;
+
+ for (int i=0 ; TQString(ObsoleteImagePluginsList[i]) != TQString("-1") ; i++)
+ obsoleteImagePluginsList << ObsoleteImagePluginsList[i];
+ }
+
+ TQStringList obsoleteImagePluginsList;
+
+ SplashScreen *splash;
+
+ PluginList pluginList;
+};
+
+ImagePluginLoader* ImagePluginLoader::m_instance=0;
+
+ImagePluginLoader* ImagePluginLoader::instance()
+{
+ return m_instance;
+}
+
+ImagePluginLoader::ImagePluginLoader(TQObject *parent, SplashScreen *splash)
+ : TQObject(parent)
+{
+ m_instance = this;
+ d = new ImagePluginLoaderPrivate;
+ d->splash = splash;
+
+ TQStringList imagePluginsList2Load;
+
+ TDETrader::OfferList offers = TDETrader::self()->query("Digikam/ImagePlugin");
+ TDETrader::OfferList::ConstIterator iter;
+
+ for (iter = offers.begin() ; iter != offers.end() ; ++iter)
+ {
+ KService::Ptr service = *iter;
+ if (!d->obsoleteImagePluginsList.contains(service->library()))
+ imagePluginsList2Load.append(service->library());
+ }
+
+ loadPluginsFromList(imagePluginsList2Load);
+}
+
+ImagePluginLoader::~ImagePluginLoader()
+{
+ delete d;
+ m_instance = 0;
+}
+
+void ImagePluginLoader::loadPluginsFromList(const TQStringList& list)
+{
+ if (d->splash)
+ d->splash->message(i18n("Loading Image Plugins"));
+
+ TDETrader::OfferList offers = TDETrader::self()->query("Digikam/ImagePlugin");
+ TDETrader::OfferList::ConstIterator iter;
+
+ int cpt = 0;
+
+ // Load plugin core at the first time.
+
+ for(iter = offers.begin(); iter != offers.end(); ++iter)
+ {
+ KService::Ptr service = *iter;
+ ImagePlugin *plugin;
+
+ if (service->library() == "digikamimageplugin_core")
+ {
+ if (!pluginIsLoaded(service->name()) )
+ {
+ int error=-1;
+ plugin = KParts::ComponentFactory::createInstanceFromService<ImagePlugin>(
+ service, this, service->name().local8Bit(), 0, &error);
+
+ if (plugin && (dynamic_cast<KXMLGUIClient*>(plugin) != 0))
+ {
+ d->pluginList.append(ImagePluginLoaderPrivate::PluginType(service->name(), plugin));
+
+ DDebug() << "ImagePluginLoader: Loaded plugin " << service->name() << endl;
+
+ ++cpt;
+ }
+ else
+ {
+ DWarning() << "ImagePluginLoader:: createInstanceFromLibrary returned 0 for "
+ << service->name()
+ << " (" << service->library() << ")"
+ << " with error code "
+ << error << endl;
+ if (error == KParts::ComponentFactory::ErrNoLibrary)
+ DWarning() << "KLibLoader says: "
+ << KLibLoader::self()->lastErrorMessage() << endl;
+ }
+ }
+ break;
+ }
+ }
+
+ // Load all other image plugins after (make a coherant menu construction in Image Editor).
+
+ for (iter = offers.begin(); iter != offers.end(); ++iter)
+ {
+ KService::Ptr service = *iter;
+ ImagePlugin *plugin;
+
+ if (!list.contains(service->library()) && service->library() != "digikamimageplugin_core")
+ {
+ if ((plugin = pluginIsLoaded(service->name())) != NULL)
+ d->pluginList.remove(ImagePluginLoaderPrivate::PluginType(service->name(),plugin));
+ }
+ else
+ {
+ if( pluginIsLoaded(service->name()) )
+ continue;
+ else
+ {
+ int error=-1;
+ plugin = KParts::ComponentFactory::createInstanceFromService<ImagePlugin>(
+ service, this, service->name().local8Bit(), 0);
+
+ if (plugin && (dynamic_cast<KXMLGUIClient*>(plugin) != 0))
+ {
+ d->pluginList.append(ImagePluginLoaderPrivate::PluginType(service->name(), plugin));
+
+ DDebug() << "ImagePluginLoader: Loaded plugin " << service->name() << endl;
+
+ ++cpt;
+ }
+ else
+ {
+ DWarning() << "ImagePluginLoader:: createInstanceFromLibrary returned 0 for "
+ << service->name()
+ << " (" << service->library() << ")"
+ << " with error code "
+ << error << endl;
+ if (error == KParts::ComponentFactory::ErrNoLibrary)
+ DWarning() << "KLibLoader says: "
+ << KLibLoader::self()->lastErrorMessage() << endl;
+ }
+ }
+ }
+ }
+
+ d->splash = 0; // Splashcreen is only lanched at the first time.
+ // If user change plugins list to use in setup, don't try to
+ // use the old splashscreen instance.
+}
+
+ImagePlugin* ImagePluginLoader::pluginIsLoaded(const TQString& name)
+{
+ if ( d->pluginList.isEmpty() )
+ return 0;
+
+ for (ImagePluginLoaderPrivate::PluginList::iterator it = d->pluginList.begin();
+ it != d->pluginList.end(); ++it)
+ {
+ if ((*it).first == name )
+ return (*it).second;
+ }
+
+ return 0;
+}
+
+ImagePlugin* ImagePluginLoader::pluginInstance(const TQString& libraryName)
+{
+ TDETrader::OfferList offers = TDETrader::self()->query("Digikam/ImagePlugin");
+ TDETrader::OfferList::ConstIterator iter;
+
+ for(iter = offers.begin(); iter != offers.end(); ++iter)
+ {
+ KService::Ptr service = *iter;
+
+ if(service->library() == libraryName)
+ {
+ return ( pluginIsLoaded(service->name()) );
+ }
+ }
+
+ return 0;
+}
+
+bool ImagePluginLoader::pluginLibraryIsLoaded(const TQString& libraryName)
+{
+ TDETrader::OfferList offers = TDETrader::self()->query("Digikam/ImagePlugin");
+ TDETrader::OfferList::ConstIterator iter;
+
+ for(iter = offers.begin(); iter != offers.end(); ++iter)
+ {
+ KService::Ptr service = *iter;
+
+ if(service->library() == libraryName)
+ {
+ if( pluginIsLoaded(service->name()) )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+TQPtrList<ImagePlugin> ImagePluginLoader::pluginList()
+{
+ TQPtrList<ImagePlugin> list;
+
+ for (ImagePluginLoaderPrivate::PluginList::iterator it = d->pluginList.begin();
+ it != d->pluginList.end(); ++it)
+ {
+ list.append((*it).second);
+ }
+
+ return list;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/canvas/imagepluginloader.h b/src/utilities/imageeditor/canvas/imagepluginloader.h
new file mode 100644
index 00000000..55ffe933
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/imagepluginloader.h
@@ -0,0 +1,79 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-04
+ * Description : image plugins loader for image editor.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPLUGINLOADER_H
+#define IMAGEPLUGINLOADER_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqptrlist.h>
+#include <tqstring.h>
+#include <tqvaluelist.h>
+#include <tqpair.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "imageplugin.h"
+
+namespace Digikam
+{
+
+class SplashScreen;
+class ImagePluginLoaderPrivate;
+
+class DIGIKAM_EXPORT ImagePluginLoader : public TQObject
+{
+
+public:
+
+ ImagePluginLoader(TQObject *parent, SplashScreen *splash=0);
+ ~ImagePluginLoader();
+
+ static ImagePluginLoader* instance();
+
+ TQPtrList<ImagePlugin> pluginList();
+ void loadPluginsFromList(const TQStringList& list);
+
+ // Return true if plugin library is loaded in memory.
+ // 'libraryName' is internal plugin library name not i18n.
+ bool pluginLibraryIsLoaded(const TQString& libraryName);
+
+ ImagePlugin* pluginInstance(const TQString& libraryName);
+
+private:
+
+ ImagePlugin* pluginIsLoaded(const TQString& name);
+
+private:
+
+ static ImagePluginLoader *m_instance;
+
+ ImagePluginLoaderPrivate *d;
+};
+
+} // namespace Digikam
+
+#endif /* IMAGEPLUGINLOADER_H */
diff --git a/src/utilities/imageeditor/canvas/iofilesettingscontainer.h b/src/utilities/imageeditor/canvas/iofilesettingscontainer.h
new file mode 100644
index 00000000..360299b3
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/iofilesettingscontainer.h
@@ -0,0 +1,84 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-03
+ * Description : IO file Settings Container.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IOFILESETTINGSCONTAINER_H
+#define IOFILESETTINGSCONTAINER_H
+
+// Local includes.
+
+#include "drawdecoding.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT IOFileSettingsContainer
+{
+
+public:
+
+ IOFileSettingsContainer()
+ {
+ JPEGCompression = 75;
+ JPEGSubSampling = 1; // Medium subsampling
+ PNGCompression = 9;
+ TIFFCompression = false;
+ JPEG2000Compression = 75;
+ JPEG2000LossLess = true;
+ useRAWImport = true;
+ };
+
+ ~IOFileSettingsContainer(){};
+
+public:
+
+ // JPEG quality value.
+ int JPEGCompression;
+
+ // JPEG chroma subsampling value.
+ int JPEGSubSampling;
+
+ // PNG compression value.
+ int PNGCompression;
+
+ // TIFF deflat compression.
+ bool TIFFCompression;
+
+ // JPEG2000 quality value.
+ int JPEG2000Compression;
+
+ // JPEG2000 lossless compression.
+ bool JPEG2000LossLess;
+
+ // Use Raw Import tool to load a RAW picture.
+ bool useRAWImport;
+
+ // ------------------------------------------------------
+ // RAW File decoding options :
+
+ DRawDecoding rawDecodingSettings;
+};
+
+} // namespace Digikam
+
+#endif // IOFILESETTINGSCONTAINER_H
diff --git a/src/utilities/imageeditor/canvas/undoaction.cpp b/src/utilities/imageeditor/canvas/undoaction.cpp
new file mode 100644
index 00000000..bb4ff756
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/undoaction.cpp
@@ -0,0 +1,185 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-06
+ * Description : undo actions manager for image editor.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimginterface.h"
+#include "undoaction.h"
+
+namespace Digikam
+{
+
+UndoAction::UndoAction(DImgInterface* iface)
+ : m_iface(iface)
+{
+ m_title = i18n("unknown");
+}
+
+UndoAction::~UndoAction()
+{
+}
+
+TQString UndoAction::getTitle() const
+{
+ return m_title;
+}
+
+UndoActionRotate::UndoActionRotate(DImgInterface* iface,
+ UndoActionRotate::Angle angle)
+ : UndoAction(iface), m_angle(angle)
+{
+ switch(m_angle)
+ {
+ case(R90):
+ m_title = i18n("Rotate 90 Degrees");
+ break;
+ case(R180):
+ m_title = i18n("Rotate 180 Degrees");
+ break;
+ case(R270):
+ m_title = i18n("Rotate 270 Degrees");
+ break;
+ }
+}
+
+UndoActionRotate::~UndoActionRotate()
+{
+}
+
+void UndoActionRotate::rollBack()
+{
+ switch(m_angle)
+ {
+ case(R90):
+ m_iface->rotate270(false);
+ return;
+ case(R180):
+ m_iface->rotate180(false);
+ return;
+ case(R270):
+ m_iface->rotate90(false);
+ return;
+ default:
+ DWarning() << "Unknown rotate angle specified" << endl;
+ }
+}
+
+void UndoActionRotate::execute()
+{
+ switch(m_angle)
+ {
+ case R90:
+ m_iface->rotate90(false);
+ return;
+ case R180:
+ m_iface->rotate180(false);
+ return;
+ case R270:
+ m_iface->rotate270(false);
+ return;
+ default:
+ DWarning() << "Unknown rotate angle specified" << endl;
+ }
+}
+
+UndoActionFlip::UndoActionFlip(DImgInterface* iface,
+ UndoActionFlip::Direction dir)
+ : UndoAction(iface), m_dir(dir)
+{
+ if(m_dir ==TQt::Horizontal)
+ m_title = i18n("Flip Horizontal");
+ else if(m_dir ==TQt::Vertical)
+ m_title = i18n("Flip Vertical");
+}
+
+UndoActionFlip::~UndoActionFlip()
+{
+}
+
+void UndoActionFlip::rollBack()
+{
+ switch(m_dir)
+ {
+ case TQt::Horizontal:
+ m_iface->flipHoriz(false);
+ return;
+ case TQt::Vertical:
+ m_iface->flipVert(false);
+ return;
+ default:
+ DWarning() << "Unknown flip direction specified" << endl;
+ }
+}
+
+void UndoActionFlip::execute()
+{
+ rollBack();
+}
+
+UndoActionBCG::UndoActionBCG(DImgInterface* iface,
+ double oldGamma, double oldBrightness,
+ double oldContrast, double newGamma,
+ double newBrightness, double newContrast)
+ : UndoAction(iface), m_oldGamma(oldGamma), m_oldBrightness(oldBrightness),
+ m_oldContrast(oldContrast), m_newGamma(newGamma), m_newBrightness(newBrightness),
+ m_newContrast(newContrast)
+{
+ m_title = i18n("Brightness,Contrast,Gamma");
+}
+
+UndoActionBCG::~UndoActionBCG()
+{
+}
+
+void UndoActionBCG::rollBack()
+{
+ m_iface->changeBCG(m_oldGamma, m_oldBrightness, m_oldContrast);
+}
+
+void UndoActionBCG::execute()
+{
+ m_iface->changeBCG(m_newGamma, m_newBrightness, m_newContrast);
+}
+
+UndoActionIrreversible::UndoActionIrreversible(DImgInterface* iface,
+ const TQString &title)
+ : UndoAction(iface)
+{
+ m_title = title;
+}
+
+UndoActionIrreversible::~UndoActionIrreversible()
+{
+}
+
+void UndoActionIrreversible::rollBack()
+{
+}
+
+void UndoActionIrreversible::execute()
+{
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/canvas/undoaction.h b/src/utilities/imageeditor/canvas/undoaction.h
new file mode 100644
index 00000000..5f29486e
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/undoaction.h
@@ -0,0 +1,144 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-06
+ * Description : undo actions manager for image editor.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+#ifndef UNDOACTION_H
+#define UNDOACTION_H
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImgInterface;
+
+class DIGIKAM_EXPORT UndoAction
+{
+
+public:
+
+ UndoAction(DImgInterface* iface);
+ virtual ~UndoAction();
+
+ virtual void rollBack() = 0;
+ virtual void execute() = 0;
+
+ TQString getTitle() const;
+
+protected:
+
+ DImgInterface *m_iface;
+ TQString m_title;
+};
+
+class DIGIKAM_EXPORT UndoActionRotate : public UndoAction
+{
+
+public:
+
+ enum Angle
+ {
+ R90,
+ R180,
+ R270
+ };
+
+ UndoActionRotate(DImgInterface* iface, Angle angle);
+ ~UndoActionRotate();
+
+ void rollBack();
+ void execute();
+
+private:
+
+ int m_angle;
+};
+
+class DIGIKAM_EXPORT UndoActionFlip : public UndoAction
+{
+
+public:
+
+ enum Direction
+ {
+ Horizontal,
+ Vertical
+ };
+
+ UndoActionFlip(DImgInterface* iface, Direction dir);
+ ~UndoActionFlip();
+
+ void rollBack();
+ void execute();
+
+private:
+
+ int m_dir;
+};
+
+class DIGIKAM_EXPORT UndoActionBCG : public UndoAction
+{
+
+public:
+
+ UndoActionBCG(DImgInterface* iface,
+ double oldGamma, double oldBrightness,
+ double oldContrast, double newGamma,
+ double newBrightness, double newContrast);
+ ~UndoActionBCG();
+
+ void rollBack();
+ void execute();
+
+private:
+
+ double m_oldGamma;
+ double m_oldBrightness;
+ double m_oldContrast;
+ double m_newGamma;
+ double m_newBrightness;
+ double m_newContrast;
+};
+
+class DIGIKAM_EXPORT UndoActionIrreversible : public UndoAction
+{
+
+public:
+
+ UndoActionIrreversible(DImgInterface* iface,
+ const TQString &caller=i18n("Unknown"));
+ ~UndoActionIrreversible();
+
+ void rollBack();
+ void execute();
+};
+
+} // namespace Digikam
+
+#endif /* UNDOACTION_H */
diff --git a/src/utilities/imageeditor/canvas/undocache.cpp b/src/utilities/imageeditor/canvas/undocache.cpp
new file mode 100644
index 00000000..5f36ae75
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/undocache.cpp
@@ -0,0 +1,178 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-05
+ * Description : undo cache manager for image editor
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+}
+
+// TQt includes.
+
+#include <tqcstring.h>
+#include <tqstring.h>
+#include <tqfile.h>
+#include <tqdatastream.h>
+#include <tqstringlist.h>
+
+// KDE includes.
+
+#include <kstandarddirs.h>
+#include <tdeaboutdata.h>
+#include <kinstance.h>
+#include <tdeglobal.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "undocache.h"
+
+namespace Digikam
+{
+
+class UndoCachePriv
+{
+public:
+
+ TQString cachePrefix;
+ TQStringList cacheFilenames;
+};
+
+UndoCache::UndoCache()
+{
+ d = new UndoCachePriv;
+
+ TQString cacheDir;
+ cacheDir = locateLocal("cache",
+ TDEGlobal::instance()->aboutData()->programName() + '/');
+
+ d->cachePrefix = TQString("%1undocache-%2")
+ .arg(cacheDir)
+ .arg(getpid());
+}
+
+UndoCache::~UndoCache()
+{
+ clear();
+ delete d;
+}
+
+/**
+ * delete all cache files
+ */
+void UndoCache::clear()
+{
+ for (TQStringList::iterator it = d->cacheFilenames.begin();
+ it != d->cacheFilenames.end(); ++it)
+ {
+ ::unlink(TQFile::encodeName(*it));
+ }
+
+ d->cacheFilenames.clear();
+}
+
+/**
+ * write the data into a cache file
+ */
+bool UndoCache::putData(int level, int w, int h, int bytesDepth, uchar* data)
+{
+ TQString cacheFile = TQString("%1-%2.bin")
+ .arg(d->cachePrefix)
+ .arg(level);
+
+ TQFile file(cacheFile);
+
+ if (file.exists() || !file.open(IO_WriteOnly))
+ return false;
+
+ TQDataStream ds(&file);
+ ds << w;
+ ds << h;
+ ds << bytesDepth;
+
+ TQByteArray ba(w*h*bytesDepth);
+ memcpy (ba.data(), data, w*h*bytesDepth);
+ ds << ba;
+
+ file.close();
+
+ d->cacheFilenames.append(cacheFile);
+
+ return true;
+}
+
+/**
+ * get the data from a cache file
+ */
+uchar* UndoCache::getData(int level, int& w, int& h, int& bytesDepth, bool del)
+{
+ TQString cacheFile = TQString("%1-%2.bin")
+ .arg(d->cachePrefix)
+ .arg(level);
+
+ TQFile file(cacheFile);
+ if (!file.open(IO_ReadOnly))
+ return 0;
+
+ TQDataStream ds(&file);
+ ds >> w;
+ ds >> h;
+ ds >> bytesDepth;
+
+ uchar *data = new uchar[w*h*bytesDepth];
+ if (!data)
+ return 0;
+
+ TQByteArray ba(w*h*bytesDepth);
+ ds >> ba;
+ memcpy (data, ba.data(), w*h*bytesDepth);
+
+ file.close();
+
+ if(del)
+ {
+ ::unlink(TQFile::encodeName(cacheFile));
+ d->cacheFilenames.remove(cacheFile);
+ }
+
+ return data;
+}
+
+/**
+ * delete a cache file
+ */
+void UndoCache::erase(int level)
+{
+ TQString cacheFile = TQString("%1-%2.bin")
+ .arg(d->cachePrefix)
+ .arg(level);
+
+ if(d->cacheFilenames.find(cacheFile) == d->cacheFilenames.end())
+ return;
+
+ ::unlink(TQFile::encodeName(cacheFile));
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/canvas/undocache.h b/src/utilities/imageeditor/canvas/undocache.h
new file mode 100644
index 00000000..732c7c3e
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/undocache.h
@@ -0,0 +1,62 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-05
+ * Description : undo cache manager for image editor.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005 by Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+#ifndef UNDOCACHE_H
+#define UNDOCACHE_H
+
+// TQt includes.
+
+#include <tqglobal.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class UndoCachePriv;
+
+class DIGIKAM_EXPORT UndoCache
+{
+
+public:
+
+ UndoCache();
+ ~UndoCache();
+
+ void clear();
+ bool putData(int level, int w, int h, int bytesDepth, uchar* data);
+ uchar *getData(int level, int& w, int& h, int& bytesDepth, bool del=true);
+
+ void erase(int level);
+
+private:
+
+ UndoCachePriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* UNDOCACHE_H */
diff --git a/src/utilities/imageeditor/canvas/undomanager.cpp b/src/utilities/imageeditor/canvas/undomanager.cpp
new file mode 100644
index 00000000..87085d5d
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/undomanager.cpp
@@ -0,0 +1,253 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-06
+ * Description : an image editor actions undo/redo manager
+ *
+ * Copyright (C) 2005-2006 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2006 Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <typeinfo>
+#include <climits>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimginterface.h"
+#include "undoaction.h"
+#include "undocache.h"
+#include "undomanager.h"
+
+namespace Digikam
+{
+
+class UndoManagerPriv
+{
+
+public:
+
+ UndoManagerPriv()
+ {
+ dimgiface = 0;
+ undoCache = 0;
+ origin = 0;
+ }
+
+ TQValueList<UndoAction*> undoActions;
+ TQValueList<UndoAction*> redoActions;
+ int origin;
+
+ UndoCache *undoCache;
+
+ DImgInterface *dimgiface;
+};
+
+UndoManager::UndoManager(DImgInterface* iface)
+{
+ d = new UndoManagerPriv;
+ d->dimgiface = iface;
+ d->undoCache = new UndoCache;
+}
+
+UndoManager::~UndoManager()
+{
+ clear(true);
+ delete d->undoCache;
+ delete d;
+}
+
+void UndoManager::addAction(UndoAction* action)
+{
+ if (!action)
+ return;
+
+ // All redo actions are invalid now
+ clearRedoActions();
+
+ d->undoActions.push_back(action);
+
+ if (typeid(*action) == typeid(UndoActionIrreversible))
+ {
+ int w = d->dimgiface->origWidth();
+ int h = d->dimgiface->origHeight();
+ int bytesDepth = d->dimgiface->bytesDepth();
+ uchar* data = d->dimgiface->getImage();
+
+ d->undoCache->putData(d->undoActions.size(), w, h, bytesDepth, data);
+ }
+
+ // if origin is at one of the redo action that are now invalid,
+ // it is no longer reachable
+ if (d->origin < 0)
+ d->origin = INT_MAX;
+ else
+ d->origin++;
+}
+
+void UndoManager::undo()
+{
+ if (d->undoActions.isEmpty())
+ return;
+
+ UndoAction* action = d->undoActions.back();
+
+ if (typeid(*action) == typeid(UndoActionIrreversible))
+ {
+ // Save the current state for the redo operation
+
+ int w = d->dimgiface->origWidth();
+ int h = d->dimgiface->origHeight();
+ int bytesDepth = d->dimgiface->bytesDepth();
+ uchar* data = d->dimgiface->getImage();
+
+ d->undoCache->putData(d->undoActions.size() + 1, w, h, bytesDepth, data);
+
+ // And now, undo the action
+
+ int newW, newH, newBytesDepth;
+ uchar *newData = d->undoCache->getData(d->undoActions.size(), newW, newH, newBytesDepth, false);
+ if (newData)
+ {
+ d->dimgiface->putImage(newData, newW, newH, newBytesDepth == 8 ? true : false);
+ delete [] newData;
+ }
+ }
+ else
+ {
+ action->rollBack();
+ }
+
+ d->undoActions.pop_back();
+ d->redoActions.push_back(action);
+ d->origin--;
+}
+
+void UndoManager::redo()
+{
+ if(d->redoActions.isEmpty())
+ return;
+
+ UndoAction *action = d->redoActions.back();
+
+ if(typeid(*action) == typeid(UndoActionIrreversible))
+ {
+ int w, h, bytesDepth;
+ uchar *data = d->undoCache->getData(d->undoActions.size() + 2, w, h, bytesDepth, false);
+ if (data)
+ {
+ d->dimgiface->putImage(data, w, h, bytesDepth == 8 ? true : false);
+ delete[] data;
+ }
+ }
+ else
+ {
+ action->execute();
+ }
+
+ d->redoActions.pop_back();
+ d->undoActions.push_back(action);
+ d->origin++;
+}
+
+void UndoManager::clear(bool clearCache)
+{
+ clearUndoActions();
+ clearRedoActions();
+ setOrigin();
+
+ if(clearCache)
+ d->undoCache->clear();
+}
+
+void UndoManager::clearUndoActions()
+{
+ UndoAction *action;
+ TQValueList<UndoAction*>::iterator it;
+
+ for(it = d->undoActions.begin(); it != d->undoActions.end(); ++it)
+ {
+ action = *it;
+ delete action;
+ }
+ d->undoActions.clear();
+}
+
+void UndoManager::clearRedoActions()
+{
+ if(!anyMoreRedo())
+ return;
+
+ UndoAction *action;
+ TQValueList<UndoAction*>::iterator it;
+
+ // get the level of the first redo action
+ int level = d->undoActions.size() + 1;
+ for(it = d->redoActions.begin(); it != d->redoActions.end(); ++it)
+ {
+ action = *it;
+ d->undoCache->erase(level);
+ delete action;
+ level++;
+ }
+ d->undoCache->erase(level);
+ d->redoActions.clear();
+}
+
+bool UndoManager::anyMoreUndo()
+{
+ return !d->undoActions.isEmpty();
+}
+
+bool UndoManager::anyMoreRedo()
+{
+ return !d->redoActions.isEmpty();
+}
+
+void UndoManager::getUndoHistory(TQStringList &titles)
+{
+ TQValueList<UndoAction*>::iterator it;
+
+ for(it = d->undoActions.begin(); it != d->undoActions.end(); ++it)
+ {
+ titles.push_front((*it)->getTitle());
+ }
+}
+
+void UndoManager::getRedoHistory(TQStringList &titles)
+{
+ TQValueList<UndoAction*>::iterator it;
+
+ for(it = d->redoActions.begin(); it != d->redoActions.end(); ++it)
+ {
+ titles.push_front((*it)->getTitle());
+ }
+}
+
+bool UndoManager::isAtOrigin()
+{
+ return d->origin == 0;
+}
+
+void UndoManager::setOrigin()
+{
+ d->origin = 0;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/canvas/undomanager.h b/src/utilities/imageeditor/canvas/undomanager.h
new file mode 100644
index 00000000..a36ba12f
--- /dev/null
+++ b/src/utilities/imageeditor/canvas/undomanager.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-02-06
+ * Description : an image editor actions undo/redo manager
+ *
+ * Copyright (C) 2005-2006 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2006 by Joern Ahrens <joern.ahrens@kdemail.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, 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.
+ *
+ * ============================================================ */
+
+#ifndef UNDOMANAGER_H
+#define UNDOMANAGER_H
+
+// TQt includes.
+
+#include <tqstringlist.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImgInterface;
+class UndoManagerPriv;
+class UndoAction;
+
+class DIGIKAM_EXPORT UndoManager
+{
+
+public:
+
+ UndoManager(DImgInterface* iface);
+ ~UndoManager();
+
+ void undo();
+ void redo();
+
+ void clear(bool clearCache=true);
+ bool anyMoreUndo();
+ bool anyMoreRedo();
+ void getUndoHistory(TQStringList &titles);
+ void getRedoHistory(TQStringList &titles);
+ bool isAtOrigin();
+ void setOrigin();
+
+ void addAction(UndoAction* action);
+
+private:
+
+ void clearUndoActions();
+ void clearRedoActions();
+
+private:
+
+ UndoManagerPriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* UNDOMANAGER_H */
diff --git a/src/utilities/imageeditor/editor/Makefile.am b/src/utilities/imageeditor/editor/Makefile.am
new file mode 100644
index 00000000..71031991
--- /dev/null
+++ b/src/utilities/imageeditor/editor/Makefile.am
@@ -0,0 +1,51 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdimgeditor.la libshowfoto.la
+
+libdimgeditor_la_SOURCES = editorwindow.cpp imageiface.cpp imagewindow.cpp editorstackview.cpp \
+ editortooliface.cpp editortool.cpp editortoolsettings.cpp
+
+libdimgeditor_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TDEPRINT)
+
+libdimgeditor_la_LIBADD = $(top_builddir)/src/utilities/imageeditor/tools/libdimgeditortools.la \
+ $(top_builddir)/src/utilities/imageeditor/rawimport/librawimport.la
+
+libshowfoto_la_SOURCES = editorwindow.cpp imageiface.cpp editorstackview.cpp \
+ editortooliface.cpp editortool.cpp editortoolsettings.cpp
+
+libshowfoto_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TDEPRINT)
+
+libshowfoto_la_LIBADD = $(top_builddir)/src/libs/dimg/libdimg.la \
+ $(top_builddir)/src/libs/dialogs/libdialogshowfoto.la \
+ $(top_builddir)/src/libs/widgets/libwidgets.la \
+ $(top_builddir)/src/libs/greycstoration/libgreycstoration.la \
+ $(top_builddir)/src/utilities/imageeditor/canvas/libdimgcanvas.la \
+ $(top_builddir)/src/utilities/imageeditor/tools/libdimgeditortools.la \
+ $(top_builddir)/src/utilities/imageeditor/rawimport/librawimport.la
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/imageplugins \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/imageproperties \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/utilities/setup \
+ -I$(top_srcdir)/src/utilities/slideshow \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/utilities/imageeditor/tools \
+ -I$(top_builddir)/src/libs/dialogs \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) $(all_includes)
+
+digikaminclude_HEADERS = imageiface.h
+
+digikamincludedir = $(includedir)/digikam
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = digikamimagewindowui.rc
+
+kde_servicetypes_DATA = digikamimageplugin.desktop
+
diff --git a/src/utilities/imageeditor/editor/digikamimageplugin.desktop b/src/utilities/imageeditor/editor/digikamimageplugin.desktop
new file mode 100644
index 00000000..31bef621
--- /dev/null
+++ b/src/utilities/imageeditor/editor/digikamimageplugin.desktop
@@ -0,0 +1,39 @@
+[Desktop Entry]
+Encoding=UTF-8
+Type=ServiceType
+X-TDE-ServiceType=Digikam/ImagePlugin
+X-TDE-DerivedName=Digikam ImagePlugin
+Comment=A digiKam Image Plugin
+Comment[bg]=Приставка за снимки в digiKam
+Comment[br]=Ul lugent skeudenn digiKam
+Comment[ca]=Un connector d'imatges del digiKam
+Comment[cs]=Modul pro digiKam
+Comment[da]=Et Digikam billed-plugin
+Comment[de]=Ein Bild-Modul von digiKam
+Comment[el]=Ένα πρόσθετο εικόνας digiKam
+Comment[es]=Plugin de digiKam para imágenes
+Comment[et]=DigiKami pildiplugin
+Comment[fa]=یک وصلۀ تصویر digiKam
+Comment[fi]=digiKam-liitännäinen
+Comment[fr]=Un module externe pour digiKam
+Comment[gl]=Un plugin de Imaxe de digiKam
+Comment[hr]=digiKam dodatak za slike
+Comment[is]=digiKam ljósmynda-íforrit
+Comment[it]=Un plugin per le immagini di digiKam
+Comment[ja]=digiKam 画像プラグイン
+Comment[ms]=Plugin Imej digiKam
+Comment[nds]=En digiKam-Bildmoduul
+Comment[nl]=Een plugin voor Digikam
+Comment[pa]=ਇੱਕ ਡਿਜ਼ੀਕੈਮ ਚਿੱਤਰ ਪਲੱਗਇਨ
+Comment[pl]=Wtyczka obrazu digiKama
+Comment[pt]=Um 'Plugin' de Imagem do digiKam
+Comment[pt_BR]=Plugin de imagem digiKam
+Comment[ru]=Модуль изображений digiKam
+Comment[sk]=DigiKam obrázkový plugin
+Comment[sr]=digiKam-ов сликовни прикључак
+Comment[sr@Latn]=digiKam-ov slikovni priključak
+Comment[sv]=Ett bildinsticksprogram för Digikam
+Comment[tr]=Bir digiKam Resim Eklentisi
+Comment[uk]=Втулок зображень digiKam
+Comment[vi]=Một phần bổ sung ảnh digiKam
+Comment[xx]=xxA digiKam Image Pluginxx
diff --git a/src/utilities/imageeditor/editor/digikamimagewindowui.rc b/src/utilities/imageeditor/editor/digikamimagewindowui.rc
new file mode 100644
index 00000000..2c0314ad
--- /dev/null
+++ b/src/utilities/imageeditor/editor/digikamimagewindowui.rc
@@ -0,0 +1,125 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<gui version="24" name="digikamimagewindow" >
+
+<MenuBar>
+
+ <Menu name="File" ><text>&amp;File</text>
+ <Action name="editorwindow_backward" />
+ <Action name="editorwindow_forward" />
+ <Separator/>
+ <Action name="editorwindow_first" />
+ <Action name="editorwindow_last" />
+ <Separator/>
+ <Action name="editorwindow_print" />
+ <Separator/>
+ <Action name="editorwindow_save" />
+ <Action name="editorwindow_saveas" />
+ <Action name="editorwindow_revert" />
+ <Separator/>
+ <Action name="editorwindow_delete" />
+ <Separator/>
+ <Action name="editorwindow_close" />
+ </Menu>
+
+ <Menu name="Edit" ><text>&amp;Edit</text>
+ <Action name="editorwindow_copy" />
+ <Separator/>
+ <Action name="editorwindow_undo" />
+ <Action name="editorwindow_redo" />
+ <Separator/>
+ <Action name="editorwindow_selectAll" />
+ <Action name="editorwindow_selectNone" />
+ </Menu>
+
+ <Menu name="View" ><text>&amp;View</text>
+ <Action name="editorwindow_fullscreen" />
+ <Action name="editorwindow_slideshow" />
+ <Separator/>
+ <Action name="editorwindow_zoomplus" />
+ <Action name="editorwindow_zoomminus" />
+ <Action name="editorwindow_zoomto100percents" />
+ <Action name="editorwindow_zoomfit2window" />
+ <Action name="editorwindow_zoomfit2select" />
+ <Separator/>
+ <Action name="editorwindow_underexposure" />
+ <Action name="editorwindow_overexposure" />
+ <Action name="editorwindow_cmview" />
+ </Menu>
+
+ <Menu name="Color" ><text>&amp;Color</text>
+ </Menu>
+
+ <Menu name="Enhance" ><text>Enh&amp;ance</text>
+ </Menu>
+
+ <Menu name="Transform" ><text>Tra&amp;nsform</text>
+ <Action name="editorwindow_rotate_left" />
+ <Action name="editorwindow_rotate_right" />
+ <Separator/>
+ <Action name="editorwindow_flip_horiz" />
+ <Action name="editorwindow_flip_vert" />
+ <Separator/>
+ <Action name="editorwindow_crop" />
+ <Action name="editorwindow_resize" />
+ </Menu>
+
+ <Menu name="Decorate" ><text>&amp;Decorate</text>
+ </Menu>
+
+ <Menu name="Filters" ><text>F&amp;ilters</text>
+ </Menu>
+
+ <Menu name="help" ><text>&amp;Help</text>
+ <Action name="editorwindow_rawcameralist"/>
+ <Action name="editorwindow_donatemoney" />
+ <Action name="editorwindow_contribute" />
+ </Menu>
+
+ <Merge/>
+
+ <Menu name="settings" noMerge="1"><Text>&amp;Settings</Text>
+ <Merge name="StandardToolBarMenuHandler" />
+ <Action name="options_show_menubar"/>
+ <Action name="options_show_statusbar"/>
+ <Action name="options_show_toolbar"/>
+ <Separator/>
+ <Action name="theme_menu" />
+ <Action name="options_configure_keybinding"/>
+ <Action name="options_configure_toolbars"/>
+ <Action name="options_configure" />
+ </Menu>
+
+</MenuBar>
+
+<ToolBar name="ToolBar" ><text>Main Toolbar</text>
+ <Action name="editorwindow_first" />
+ <Action name="editorwindow_backward" />
+ <Action name="editorwindow_forward" />
+ <Action name="editorwindow_last" />
+ <Separator/>
+ <Action name="editorwindow_save" />
+ <Action name="editorwindow_saveas" />
+ <Action name="editorwindow_undo" />
+ <Action name="editorwindow_redo" />
+ <Action name="editorwindow_revert" />
+ <Separator/>
+ <Action name="editorwindow_zoomplus" />
+ <Action name="editorwindow_zoomto" />
+ <Action name="editorwindow_zoomminus" />
+ <Action name="editorwindow_zoomfit2window" />
+ <Action name="editorwindow_zoomfit2select" />
+ <Separator/>
+ <Action name="editorwindow_rotate_left" />
+ <Action name="editorwindow_rotate_right" />
+ <Action name="editorwindow_crop" />
+ <Separator/>
+ <Action name="editorwindow_fullscreen" />
+ <Action name="editorwindow_slideshow" />
+ <Merge />
+ <WeakSeparator/>
+ <Action name="logo_action" />
+</ToolBar>
+
+<ActionProperties/>
+
+</gui>
diff --git a/src/utilities/imageeditor/editor/editorstackview.cpp b/src/utilities/imageeditor/editor/editorstackview.cpp
new file mode 100644
index 00000000..61550300
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editorstackview.cpp
@@ -0,0 +1,233 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : A widget stack to embed editor view.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "previewwidget.h"
+#include "imageregionwidget.h"
+#include "imagepanelwidget.h"
+#include "canvas.h"
+#include "editorstackview.h"
+#include "editorstackview.moc"
+
+namespace Digikam
+{
+
+class EditorStackViewPriv
+{
+
+public:
+
+ EditorStackViewPriv()
+ {
+ canvas = 0;
+ toolView = 0;
+ }
+
+ TQWidget *toolView;
+ Canvas *canvas;
+};
+
+EditorStackView::EditorStackView(TQWidget *parent)
+ : TQWidgetStack(parent, 0, TQt::WDestructiveClose)
+{
+ d = new EditorStackViewPriv;
+}
+
+EditorStackView::~EditorStackView()
+{
+ delete d;
+}
+
+void EditorStackView::setCanvas(Canvas* canvas)
+{
+ if (d->canvas) return;
+
+ d->canvas = canvas;
+ addWidget(d->canvas, CanvasMode);
+
+ connect(d->canvas, TQ_SIGNAL(signalZoomChanged(double)),
+ this, TQ_SLOT(slotZoomChanged(double)));
+}
+
+Canvas* EditorStackView::canvas() const
+{
+ return d->canvas;
+}
+
+void EditorStackView::setToolView(TQWidget* view)
+{
+ if (d->toolView)
+ removeWidget(d->toolView);
+
+ d->toolView = view;
+
+ if (d->toolView)
+ addWidget(d->toolView, ToolViewMode);
+
+ PreviewWidget *preview = previewWidget();
+ if (preview)
+ {
+ connect(preview, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SLOT(slotZoomChanged(double)));
+ }
+}
+
+TQWidget* EditorStackView::toolView() const
+{
+ return d->toolView;
+}
+
+int EditorStackView::viewMode()
+{
+ return id(visibleWidget());
+}
+
+void EditorStackView::setViewMode(int mode)
+{
+ if (mode != CanvasMode && mode != ToolViewMode)
+ return;
+
+ raiseWidget(mode);
+}
+
+void EditorStackView::increaseZoom()
+{
+ if (viewMode() == CanvasMode)
+ {
+ d->canvas->slotIncreaseZoom();
+ }
+ else
+ {
+ PreviewWidget *preview = previewWidget();
+ if (preview)
+ preview->slotIncreaseZoom();
+ }
+}
+
+void EditorStackView::decreaseZoom()
+{
+ if (viewMode() == CanvasMode)
+ {
+ d->canvas->slotDecreaseZoom();
+ }
+ else
+ {
+ PreviewWidget *preview = previewWidget();
+ if (preview)
+ preview->slotDecreaseZoom();
+ }
+}
+
+void EditorStackView::toggleFitToWindow()
+{
+ if (viewMode() == CanvasMode)
+ {
+ d->canvas->toggleFitToWindow();
+ }
+ else
+ {
+ PreviewWidget *preview = previewWidget();
+ if (preview)
+ preview->toggleFitToWindow();
+ }
+}
+
+void EditorStackView::fitToSelect()
+{
+ if (viewMode() == CanvasMode)
+ {
+ d->canvas->fitToSelect();
+ }
+}
+
+void EditorStackView::zoomTo100Percents()
+{
+ if (viewMode() == CanvasMode)
+ {
+ if (d->canvas->zoomFactor() == 1.0)
+ d->canvas->toggleFitToWindow();
+ else
+ d->canvas->setZoomFactor(1.0);
+ }
+ else
+ {
+ PreviewWidget *preview = previewWidget();
+ if (preview)
+ {
+ if (preview->zoomFactor() == 1.0)
+ preview->toggleFitToWindow();
+ else
+ preview->setZoomFactor(1.0);
+ }
+ }
+}
+
+void EditorStackView::setZoomFactor(double zoom)
+{
+ if (viewMode() == CanvasMode)
+ {
+ d->canvas->setZoomFactor(zoom);
+ }
+ else
+ {
+ PreviewWidget *preview = previewWidget();
+ if (preview)
+ preview->setZoomFactor(zoom);
+ }
+}
+
+void EditorStackView::slotZoomChanged(double zoom)
+{
+ bool max, min;
+
+ if (viewMode() == CanvasMode)
+ {
+ max = d->canvas->maxZoom();
+ min = d->canvas->minZoom();
+ emit signalZoomChanged(max, min, zoom);
+ }
+ else
+ {
+ PreviewWidget *preview = previewWidget();
+ if (preview)
+ {
+ max = preview->maxZoom();
+ min = preview->minZoom();
+ emit signalZoomChanged(max, min, zoom);
+ }
+ }
+}
+
+PreviewWidget* EditorStackView::previewWidget() const
+{
+ PreviewWidget *preview = dynamic_cast<PreviewWidget*>(d->toolView);
+ if (preview) return preview;
+
+ ImagePanelWidget *panel = dynamic_cast<ImagePanelWidget*>(d->toolView);
+ if (panel) return (dynamic_cast<PreviewWidget*>(panel->previewWidget()));
+
+ return 0;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/editor/editorstackview.h b/src/utilities/imageeditor/editor/editorstackview.h
new file mode 100644
index 00000000..e428fcd7
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editorstackview.h
@@ -0,0 +1,97 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : A widget stack to embed editor view.
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EDITORSTACKVIEW_H
+#define EDITORSTACKVIEW_H
+
+// KDE includes.
+
+#include <tqwidgetstack.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class PreviewWidget;
+class Canvas;
+class EditorStackViewPriv;
+
+class DIGIKAM_EXPORT EditorStackView : public TQWidgetStack
+{
+TQ_OBJECT
+
+
+public:
+
+ enum StackViewMode
+ {
+ CanvasMode=0,
+ ToolViewMode
+ };
+
+public:
+
+ EditorStackView(TQWidget *parent=0);
+ ~EditorStackView();
+
+ void setCanvas(Canvas* canvas);
+ Canvas *canvas() const;
+
+ void setToolView(TQWidget* view);
+ TQWidget *toolView() const;
+
+ int viewMode();
+ void setViewMode(int mode);
+
+ void increaseZoom();
+ void decreaseZoom();
+ void toggleFitToWindow();
+ void fitToSelect();
+ void zoomTo100Percents();
+ void setZoomFactor(double zoom);
+
+ /** Two widgets are embedded in Editor Tool to perform preview with panning and zooming:
+ a PreviewWidget derivated class or ImagePanelWidget.
+ This method try to find the right PreviewWidget instance accordingly else return 0.
+ */
+ PreviewWidget* previewWidget() const;
+
+signals:
+
+ void signalZoomChanged(bool isMax, bool isMin, double zoom);
+
+private slots:
+
+ void slotZoomChanged(double);
+
+private:
+
+ EditorStackViewPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* EDITORSTACKVIEW_H */
diff --git a/src/utilities/imageeditor/editor/editortool.cpp b/src/utilities/imageeditor/editor/editortool.cpp
new file mode 100644
index 00000000..b840e673
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editortool.cpp
@@ -0,0 +1,436 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : editor tool template class.
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqtimer.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagewidget.h"
+#include "imageguidewidget.h"
+#include "imagepanelwidget.h"
+#include "dimgthreadedfilter.h"
+#include "editortoolsettings.h"
+#include "editortooliface.h"
+#include "editortool.h"
+#include "editortool.moc"
+
+namespace Digikam
+{
+
+class EditorToolPriv
+{
+
+public:
+
+ EditorToolPriv()
+ {
+ timer = 0;
+ view = 0;
+ settings = 0;
+ }
+
+ TQString helpAnchor;
+ TQString name;
+
+ TQWidget *view;
+
+ TQPixmap icon;
+
+ TQTimer *timer;
+
+ EditorToolSettings *settings;
+};
+
+EditorTool::EditorTool(TQObject *parent)
+ : TQObject(parent)
+{
+ d = new EditorToolPriv;
+ d->timer = new TQTimer(this);
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotEffect()));
+}
+
+EditorTool::~EditorTool()
+{
+ delete d;
+}
+
+void EditorTool::init()
+{
+ TQTimer::singleShot(0, this, TQ_SLOT(slotInit()));
+}
+
+TQPixmap EditorTool::toolIcon() const
+{
+ return d->icon;
+}
+
+void EditorTool::setToolIcon(const TQPixmap& icon)
+{
+ d->icon = icon;
+}
+
+TQString EditorTool::toolName() const
+{
+ return d->name;
+}
+
+void EditorTool::setToolName(const TQString& name)
+{
+ d->name = name;
+}
+
+TQWidget* EditorTool::toolView() const
+{
+ return d->view;
+}
+
+void EditorTool::setToolView(TQWidget *view)
+{
+ d->view = view;
+ // Will be unblocked in slotInit()
+ // This will prevent resize event signals emit during tool init.
+ d->view->blockSignals(true);
+}
+
+EditorToolSettings* EditorTool::toolSettings() const
+{
+ return d->settings;
+}
+
+void EditorTool::setToolSettings(EditorToolSettings *settings)
+{
+ d->settings = settings;
+
+ connect(d->settings, TQ_SIGNAL(signalOkClicked()),
+ this, TQ_SLOT(slotOk()));
+
+ connect(d->settings, TQ_SIGNAL(signalCancelClicked()),
+ this, TQ_SLOT(slotCancel()));
+
+ connect(d->settings, TQ_SIGNAL(signalDefaultClicked()),
+ this, TQ_SLOT(slotResetSettings()));
+
+ connect(d->settings, TQ_SIGNAL(signalSaveAsClicked()),
+ this, TQ_SLOT(slotSaveAsSettings()));
+
+ connect(d->settings, TQ_SIGNAL(signalLoadClicked()),
+ this, TQ_SLOT(slotLoadSettings()));
+
+ connect(d->settings, TQ_SIGNAL(signalTryClicked()),
+ this, TQ_SLOT(slotEffect()));
+
+ // Will be unblocked in slotInit()
+ // This will prevent signals emit during tool init.
+ d->settings->blockSignals(true);
+}
+
+void EditorTool::slotInit()
+{
+ readSettings();
+ // Unlock signals from preview and settings widgets when init is done.
+ d->view->blockSignals(false);
+ d->settings->blockSignals(false);
+}
+
+void EditorTool::setToolHelp(const TQString& anchor)
+{
+ d->helpAnchor = anchor;
+ // TODO: use this anchor with editor Help menu
+}
+
+TQString EditorTool::toolHelp() const
+{
+ if (d->helpAnchor.isEmpty())
+ return (name() + TQString(".anchor"));
+
+ return d->helpAnchor;
+}
+
+void EditorTool::setBusy(bool state)
+{
+ d->settings->setBusy(state);
+}
+
+void EditorTool::readSettings()
+{
+ d->settings->readSettings();
+}
+
+void EditorTool::writeSettings()
+{
+ d->settings->writeSettings();
+}
+
+void EditorTool::slotResetSettings()
+{
+ d->settings->resetSettings();
+}
+
+void EditorTool::slotTimer()
+{
+ d->timer->start(500, true);
+}
+
+void EditorTool::slotOk()
+{
+ writeSettings();
+ finalRendering();
+ emit okClicked();
+}
+
+void EditorTool::slotCancel()
+{
+ writeSettings();
+ emit cancelClicked();
+}
+
+// ----------------------------------------------------------------
+
+class EditorToolThreadedPriv
+{
+
+public:
+
+ EditorToolThreadedPriv()
+ {
+ threadedFilter = 0;
+ currentRenderingMode = EditorToolThreaded::NoneRendering;
+ }
+
+ EditorToolThreaded::RenderingMode currentRenderingMode;
+
+ TQString progressMess;
+
+ DImgThreadedFilter *threadedFilter;
+};
+
+EditorToolThreaded::EditorToolThreaded(TQObject *parent)
+ : EditorTool(parent)
+{
+ d = new EditorToolThreadedPriv;
+}
+
+EditorToolThreaded::~EditorToolThreaded()
+{
+ delete d->threadedFilter;
+ delete d;
+}
+
+EditorToolThreaded::RenderingMode EditorToolThreaded::renderingMode() const
+{
+ return d->currentRenderingMode;
+}
+
+void EditorToolThreaded::setProgressMessage(const TQString& mess)
+{
+ d->progressMess = mess;
+}
+
+DImgThreadedFilter* EditorToolThreaded::filter() const
+{
+ return d->threadedFilter;
+}
+
+void EditorToolThreaded::setFilter(DImgThreadedFilter *filter)
+{
+ d->threadedFilter = filter;
+}
+
+void EditorToolThreaded::slotResized()
+{
+ if (d->currentRenderingMode == EditorToolThreaded::FinalRendering)
+ {
+ toolView()->update();
+ return;
+ }
+ else if (d->currentRenderingMode == EditorToolThreaded::PreviewRendering)
+ {
+ if (filter())
+ filter()->stopComputation();
+ }
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotEffect()));
+}
+
+void EditorToolThreaded::slotAbort()
+{
+ d->currentRenderingMode = EditorToolThreaded::NoneRendering;
+
+ if (filter())
+ filter()->stopComputation();
+
+ EditorToolIface::editorToolIface()->setToolStopProgress();
+
+ toolSettings()->enableButton(EditorToolSettings::Ok, true);
+ toolSettings()->enableButton(EditorToolSettings::Load, true);
+ toolSettings()->enableButton(EditorToolSettings::SaveAs, true);
+ toolSettings()->enableButton(EditorToolSettings::Try, true);
+ toolSettings()->enableButton(EditorToolSettings::Default, true);
+
+ renderingFinished();
+}
+
+void EditorToolThreaded::customEvent(TQCustomEvent *e)
+{
+ if (!e) return;
+
+ DImgThreadedFilter::EventData *ed = (DImgThreadedFilter::EventData*)e->data();
+
+ if (!ed) return;
+
+ if (ed->starting) // Computation in progress !
+ {
+ EditorToolIface::editorToolIface()->setToolProgress(ed->progress);
+ }
+ else
+ {
+ if (ed->success) // Computation Completed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case EditorToolThreaded::PreviewRendering:
+ {
+ DDebug() << "Preview " << toolName() << " completed..." << endl;
+ putPreviewData();
+ slotAbort();
+ break;
+ }
+
+ case EditorToolThreaded::FinalRendering:
+ {
+ DDebug() << "Final" << toolName() << " completed..." << endl;
+ putFinalData();
+ EditorToolIface::editorToolIface()->setToolStopProgress();
+ kapp->restoreOverrideCursor();
+ emit okClicked();
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ else // Computation Failed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case EditorToolThreaded::PreviewRendering:
+ {
+ DDebug() << "Preview " << toolName() << " failed..." << endl;
+ slotAbort();
+ break;
+ }
+
+ case EditorToolThreaded::FinalRendering:
+ default:
+ break;
+ }
+ }
+ }
+
+ delete ed;
+}
+
+void EditorToolThreaded::setToolView(TQWidget *view)
+{
+ EditorTool::setToolView(view);
+
+ if (dynamic_cast<ImageWidget*>(view) || dynamic_cast<ImageGuideWidget*>(view) ||
+ dynamic_cast<ImagePanelWidget*>(view))
+ {
+ connect(view, TQ_SIGNAL(signalResized()),
+ this, TQ_SLOT(slotResized()));
+ }
+}
+
+void EditorToolThreaded::slotOk()
+{
+ writeSettings();
+
+ d->currentRenderingMode = EditorToolThreaded::FinalRendering;
+ DDebug() << "Final " << toolName() << " started..." << endl;
+ writeSettings();
+
+ toolSettings()->enableButton(EditorToolSettings::Ok, false);
+ toolSettings()->enableButton(EditorToolSettings::SaveAs, false);
+ toolSettings()->enableButton(EditorToolSettings::Load, false);
+ toolSettings()->enableButton(EditorToolSettings::Default, false);
+ toolSettings()->enableButton(EditorToolSettings::Try, false);
+
+ EditorToolIface::editorToolIface()->setToolStartProgress(d->progressMess.isEmpty() ? toolName() : d->progressMess);
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+
+ if (d->threadedFilter)
+ {
+ delete d->threadedFilter;
+ d->threadedFilter = 0;
+ }
+
+ prepareFinal();
+}
+
+void EditorToolThreaded::slotEffect()
+{
+ // Computation already in process.
+ if (d->currentRenderingMode != EditorToolThreaded::NoneRendering)
+ return;
+
+ d->currentRenderingMode = EditorToolThreaded::PreviewRendering;
+ DDebug() << "Preview " << toolName() << " started..." << endl;
+
+ toolSettings()->enableButton(EditorToolSettings::Ok, false);
+ toolSettings()->enableButton(EditorToolSettings::SaveAs, false);
+ toolSettings()->enableButton(EditorToolSettings::Load, false);
+ toolSettings()->enableButton(EditorToolSettings::Default, false);
+ toolSettings()->enableButton(EditorToolSettings::Try, false);
+
+ EditorToolIface::editorToolIface()->setToolStartProgress(d->progressMess.isEmpty() ? toolName() : d->progressMess);
+
+ if (d->threadedFilter)
+ {
+ delete d->threadedFilter;
+ d->threadedFilter = 0;
+ }
+
+ prepareEffect();
+}
+
+void EditorToolThreaded::slotCancel()
+{
+ writeSettings();
+ slotAbort();
+ kapp->restoreOverrideCursor();
+ emit cancelClicked();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/editor/editortool.h b/src/utilities/imageeditor/editor/editortool.h
new file mode 100644
index 00000000..120ff9f0
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editortool.h
@@ -0,0 +1,164 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : editor tool template class.
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EDITORTOOL_H
+#define EDITORTOOL_H
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqstring.h>
+#include <tqpixmap.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DImgThreadedFilter;
+class EditorToolSettings;
+class EditorToolPriv;
+
+class DIGIKAM_EXPORT EditorTool : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ EditorTool(TQObject *parent);
+ virtual ~EditorTool();
+
+ void init();
+
+ TQString toolHelp() const;
+ TQString toolName() const;
+ TQPixmap toolIcon() const;
+ TQWidget* toolView() const;
+ EditorToolSettings* toolSettings() const;
+
+signals:
+
+ void okClicked();
+ void cancelClicked();
+
+protected:
+
+ void setToolHelp(const TQString& anchor);
+ void setToolName(const TQString& name);
+ void setToolIcon(const TQPixmap& icon);
+
+ virtual void setToolView(TQWidget *view);
+ virtual void setToolSettings(EditorToolSettings *settings);
+ virtual void setBusy(bool);
+ virtual void readSettings();
+ virtual void writeSettings();
+ virtual void finalRendering(){};
+
+protected slots:
+
+ void slotTimer();
+
+ virtual void slotOk();
+ virtual void slotCancel();
+ virtual void slotInit();
+ virtual void slotLoadSettings(){};
+ virtual void slotSaveAsSettings(){};
+ virtual void slotResetSettings();
+ virtual void slotEffect(){};
+
+private:
+
+ EditorToolPriv *d;
+};
+
+// -----------------------------------------------------------------
+
+class EditorToolThreadedPriv;
+
+class DIGIKAM_EXPORT EditorToolThreaded : public EditorTool
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum RenderingMode
+ {
+ NoneRendering=0,
+ PreviewRendering,
+ FinalRendering
+ };
+
+public:
+
+ EditorToolThreaded(TQObject *parent);
+ virtual ~EditorToolThreaded();
+
+ /** Set the small text to show in editor status progress bar during
+ tool computation. If it's not set, tool name is used instead.
+ */
+ void setProgressMessage(const TQString& mess);
+
+ /** return the current tool rendering mode.
+ */
+ RenderingMode renderingMode() const;
+
+public slots:
+
+ virtual void slotAbort();
+
+protected:
+
+ DImgThreadedFilter* filter() const;
+ void setFilter(DImgThreadedFilter *filter);
+
+ void customEvent(TQCustomEvent *event);
+
+ virtual void setToolView(TQWidget *view);
+ virtual void prepareEffect(){};
+ virtual void prepareFinal(){};
+ virtual void putPreviewData(){};
+ virtual void putFinalData(){};
+ virtual void renderingFinished(){};
+
+protected slots:
+
+ virtual void slotOk();
+ virtual void slotCancel();
+ virtual void slotEffect();
+
+private slots:
+
+ void slotResized();
+
+private:
+
+ EditorToolThreadedPriv *d;
+};
+
+} //namespace Digikam
+
+#endif /* IMAGEPLUGIN_H */
diff --git a/src/utilities/imageeditor/editor/editortooliface.cpp b/src/utilities/imageeditor/editor/editortooliface.cpp
new file mode 100644
index 00000000..f666bbf2
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editortooliface.cpp
@@ -0,0 +1,147 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : Image editor interface used by editor tools.
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "sidebar.h"
+#include "canvas.h"
+#include "statusprogressbar.h"
+#include "editortool.h"
+#include "editortoolsettings.h"
+#include "editorstackview.h"
+#include "editorwindow.h"
+#include "editortooliface.h"
+#include "editortooliface.moc"
+
+namespace Digikam
+{
+
+class EditorToolIfacePriv
+{
+
+public:
+
+ EditorToolIfacePriv()
+ {
+ prevTab = 0;
+ editor = 0;
+ tool = 0;
+ }
+
+ TQWidget *prevTab;
+
+ EditorTool *tool;
+
+ EditorWindow *editor;
+};
+
+EditorToolIface* EditorToolIface::m_iface = 0;
+
+EditorToolIface* EditorToolIface::editorToolIface()
+{
+ return m_iface;
+}
+
+EditorToolIface::EditorToolIface(EditorWindow *editor)
+ : TQObject()
+{
+ d = new EditorToolIfacePriv;
+ d->editor = editor;
+ m_iface = this;
+}
+
+EditorToolIface::~EditorToolIface()
+{
+ delete d;
+ if (m_iface == this)
+ m_iface = 0;
+}
+
+EditorTool* EditorToolIface::currentTool() const
+{
+ return d->tool;
+}
+
+void EditorToolIface::loadTool(EditorTool* tool)
+{
+ if (d->tool) unLoadTool();
+
+ d->tool = tool;
+ d->editor->editorStackView()->setToolView(d->tool->toolView());
+ d->editor->editorStackView()->setViewMode(EditorStackView::ToolViewMode);
+ d->prevTab = d->editor->rightSideBar()->getActiveTab();
+ d->editor->rightSideBar()->appendTab(d->tool->toolSettings(), d->tool->toolIcon(), d->tool->toolName());
+ d->editor->rightSideBar()->setActiveTab(d->tool->toolSettings());
+ d->editor->toggleActions(false);
+
+ // If editor tool has zoomable preview, switch on zoom actions.
+ if (d->editor->editorStackView()->previewWidget())
+ d->editor->toggleZoomActions(true);
+}
+
+void EditorToolIface::unLoadTool()
+{
+ if (!d->tool) return;
+
+ d->editor->editorStackView()->setViewMode(EditorStackView::CanvasMode);
+ d->editor->editorStackView()->setToolView(0);
+ d->editor->rightSideBar()->deleteTab(d->tool->toolSettings());
+ d->editor->rightSideBar()->setActiveTab(d->prevTab);
+ d->editor->toggleActions(true);
+ // To restore canvas zoom level in zoom combobox.
+ if (!d->editor->editorStackView()->canvas()->fitToWindow())
+ d->editor->editorStackView()->setZoomFactor(d->editor->editorStackView()->canvas()->zoomFactor());
+ delete d->tool;
+ d->tool = 0;
+}
+
+void EditorToolIface::setToolStartProgress(const TQString& toolName)
+{
+ d->editor->setToolStartProgress(toolName);
+ if (d->editor->editorStackView()->previewWidget())
+ d->editor->toggleZoomActions(false);
+}
+
+void EditorToolIface::setToolProgress(int progress)
+{
+ d->editor->setToolProgress(progress);
+}
+
+void EditorToolIface::setToolStopProgress()
+{
+ d->editor->setToolStopProgress();
+ if (d->editor->editorStackView()->previewWidget())
+ d->editor->toggleZoomActions(true);
+}
+
+void EditorToolIface::slotToolAborted()
+{
+ EditorToolThreaded *tool = dynamic_cast<EditorToolThreaded*>(d->tool);
+ if (tool) tool->slotAbort();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/editor/editortooliface.h b/src/utilities/imageeditor/editor/editortooliface.h
new file mode 100644
index 00000000..ce8708cf
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editortooliface.h
@@ -0,0 +1,76 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : Image editor interface used by editor tools.
+ *
+ * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EDITORTOOLIFACE_H
+#define EDITORTOOLIFACE_H
+
+// TQt includes.
+
+#include <tqobject.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class EditorTool;
+class EditorWindow;
+class EditorToolIfacePriv;
+
+class DIGIKAM_EXPORT EditorToolIface : public TQObject
+{
+ TQ_OBJECT
+
+
+public:
+
+ static EditorToolIface* editorToolIface();
+
+ EditorToolIface(EditorWindow *editor);
+ ~EditorToolIface();
+
+ EditorTool* currentTool() const;
+
+ void loadTool(EditorTool* tool);
+ void unLoadTool();
+
+ void setToolStartProgress(const TQString& toolName);
+ void setToolProgress(int progress);
+ void setToolStopProgress();
+
+public slots:
+
+ void slotToolAborted();
+
+private:
+
+ static EditorToolIface *m_iface;
+
+ EditorToolIfacePriv *d;
+};
+
+} // namespace Digikam
+
+#endif /* EDITORTOOLIFACE_H */
diff --git a/src/utilities/imageeditor/editor/editortoolsettings.cpp b/src/utilities/imageeditor/editor/editortoolsettings.cpp
new file mode 100644
index 00000000..3ac45ccf
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editortoolsettings.cpp
@@ -0,0 +1,331 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-21
+ * Description : Editor tool settings template box
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqhbox.h>
+#include <tqvbox.h>
+#include <tqlabel.h>
+#include <tqlayout.h>
+#include <tqstring.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kcolorbutton.h>
+#include <kdialog.h>
+#include <tdeglobalsettings.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <kpushbutton.h>
+#include <kstandarddirs.h>
+#include <kstdguiitem.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagepaniconwidget.h"
+#include "editortoolsettings.h"
+#include "editortoolsettings.moc"
+
+using namespace KDcrawIface;
+
+namespace Digikam
+{
+
+class EditorToolSettingsPriv
+{
+
+public:
+
+ EditorToolSettingsPriv()
+ {
+ okBtn = 0;
+ cancelBtn = 0;
+ tryBtn = 0;
+ defaultBtn = 0;
+ mainVBox = 0;
+ plainPage = 0;
+ btnBox1 = 0;
+ btnBox2 = 0;
+ btnBox3 = 0;
+ saveAsBtn = 0;
+ loadBtn = 0;
+ guideBox = 0;
+ guideColorBt = 0;
+ guideSize = 0;
+ panIconView = 0;
+ }
+
+ TQHBox *btnBox1;
+ TQHBox *btnBox2;
+ TQHBox *btnBox3;
+ TQHBox *guideBox;
+
+ TQVBox *mainVBox;
+ TQWidget *plainPage;
+
+ KPushButton *okBtn;
+ KPushButton *cancelBtn;
+ KPushButton *tryBtn;
+ KPushButton *defaultBtn;
+ KPushButton *saveAsBtn;
+ KPushButton *loadBtn;
+
+ KColorButton *guideColorBt;
+
+ ImagePanIconWidget *panIconView;
+
+ RIntNumInput *guideSize;
+};
+
+EditorToolSettings::EditorToolSettings(int buttonMask, int toolMask, TQWidget *parent)
+ : TQScrollView(parent)
+{
+ d = new EditorToolSettingsPriv;
+
+ viewport()->setBackgroundMode(TQt::PaletteBackground);
+ setResizePolicy(TQScrollView::AutoOneFit);
+ setFrameStyle(TQFrame::NoFrame);
+
+ d->mainVBox = new TQVBox(viewport());
+ addChild(d->mainVBox);
+
+ // ---------------------------------------------------------------
+
+ TQFrame *frame = new TQFrame(d->mainVBox);
+ frame->setFrameStyle(TQFrame::Panel|TQFrame::Sunken);
+ TQVBoxLayout* vlay = new TQVBoxLayout(frame, 5, 0);
+ d->panIconView = new ImagePanIconWidget(360, 240, frame);
+ TQWhatsThis::add(d->panIconView, i18n("<p>Here you can see the original image panel "
+ "which can help you to select the clip preview."
+ "<p>Click and drag the mouse cursor in the "
+ "red rectangle to change the clip focus."));
+ vlay->addWidget(d->panIconView, 0, TQt::AlignCenter);
+
+ if (!(toolMask & PanIcon))
+ frame->hide();
+
+ // ---------------------------------------------------------------
+
+ d->plainPage = new TQWidget(d->mainVBox);
+ d->guideBox = new TQHBox(d->mainVBox);
+ d->btnBox1 = new TQHBox(d->mainVBox);
+ d->btnBox2 = new TQHBox(d->mainVBox);
+
+ // ---------------------------------------------------------------
+
+ new TQLabel(i18n("Guide:"), d->guideBox);
+ TQLabel *space4 = new TQLabel(d->guideBox);
+ d->guideColorBt = new KColorButton(TQColor(TQt::red), d->guideBox);
+ TQWhatsThis::add(d->guideColorBt, i18n("<p>Set here the color used to draw guides dashed-lines."));
+ d->guideSize = new RIntNumInput(d->guideBox);
+ d->guideSize->setRange(1, 5, 1);
+ d->guideSize->setDefaultValue(1);
+ TQWhatsThis::add(d->guideSize, i18n("<p>Set here the width in pixels used to draw guides dashed-lines."));
+
+ d->guideBox->setStretchFactor(space4, 10);
+ d->guideBox->setSpacing(spacingHint());
+ d->guideBox->setMargin(0);
+
+ if (!(toolMask & ColorGuide))
+ d->guideBox->hide();
+
+ // ---------------------------------------------------------------
+
+ d->defaultBtn = new KPushButton(d->btnBox1);
+ d->defaultBtn->setGuiItem(KStdGuiItem::defaults());
+ d->defaultBtn->setIconSet(SmallIconSet("reload_page"));
+ TQToolTip::add(d->defaultBtn, i18n("<p>Reset all settings to their default values."));
+ if (!(buttonMask & Default))
+ d->defaultBtn->hide();
+
+ TQLabel *space = new TQLabel(d->btnBox1);
+
+ d->okBtn = new KPushButton(d->btnBox1);
+ d->okBtn->setGuiItem(KStdGuiItem::ok());
+ if (!(buttonMask & Ok))
+ d->okBtn->hide();
+
+ d->cancelBtn = new KPushButton(d->btnBox1);
+ d->cancelBtn->setGuiItem(KStdGuiItem::cancel());
+ if (!(buttonMask & Cancel))
+ d->cancelBtn->hide();
+
+ d->btnBox1->setStretchFactor(space, 10);
+ d->btnBox1->setSpacing(spacingHint());
+ d->btnBox1->setMargin(0);
+
+ if (!(buttonMask & Default) && !(buttonMask & Ok) && !(buttonMask & Cancel))
+ d->btnBox1->hide();
+
+ // ---------------------------------------------------------------
+
+ d->loadBtn = new KPushButton(d->btnBox2);
+ d->loadBtn->setGuiItem(KStdGuiItem::open());
+ d->loadBtn->setText(i18n("Load..."));
+ TQToolTip::add(d->loadBtn, i18n("<p>Load all parameters from settings text file."));
+ if (!(buttonMask & Load))
+ d->loadBtn->hide();
+
+ d->saveAsBtn = new KPushButton(d->btnBox2);
+ d->saveAsBtn->setGuiItem(KStdGuiItem::saveAs());
+ TQToolTip::add(d->saveAsBtn, i18n("<p>Save all parameters to settings text file."));
+ if (!(buttonMask & SaveAs))
+ d->saveAsBtn->hide();
+
+ TQLabel *space2 = new TQLabel(d->btnBox2);
+
+ d->tryBtn = new KPushButton(d->btnBox2);
+ d->tryBtn->setGuiItem(KStdGuiItem::apply());
+ d->tryBtn->setText(i18n("Try"));
+ TQToolTip::add(d->tryBtn, i18n("<p>Try all settings."));
+ if (!(buttonMask & Try))
+ d->tryBtn->hide();
+
+ d->btnBox2->setStretchFactor(space2, 10);
+ d->btnBox2->setSpacing(spacingHint());
+ d->btnBox2->setMargin(0);
+
+ if (!(buttonMask & Load) && !(buttonMask & SaveAs) && !(buttonMask & Try))
+ d->btnBox2->hide();
+
+ // ---------------------------------------------------------------
+
+ connect(d->okBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalOkClicked()));
+
+ connect(d->cancelBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalCancelClicked()));
+
+ connect(d->tryBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalTryClicked()));
+
+ connect(d->defaultBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalDefaultClicked()));
+
+ connect(d->saveAsBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalSaveAsClicked()));
+
+ connect(d->loadBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalLoadClicked()));
+
+ connect(d->guideColorBt, TQ_SIGNAL(changed(const TQColor&)),
+ this, TQ_SIGNAL(signalColorGuideChanged()));
+
+ connect(d->guideSize, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SIGNAL(signalColorGuideChanged()));
+}
+
+EditorToolSettings::~EditorToolSettings()
+{
+ delete d;
+}
+
+TQSize EditorToolSettings::minimumSizeHint() const
+{
+ // Editor Tools usually require a larger horizontal space than other widgets in right side bar
+ // Set scroll area to a horizontal minimum size sufficient for the settings.
+ // Do not touch vertical size hint.
+ // Limit to 40% of the desktop width.
+ TQSize hint = TQScrollView::minimumSizeHint();
+ TQRect desktopRect = TDEGlobalSettings::desktopGeometry(d->mainVBox);
+ hint.setWidth(TQMIN(d->mainVBox->minimumSizeHint().width(), desktopRect.width() * 2 / 5));
+ return hint;
+}
+
+int EditorToolSettings::marginHint()
+{
+ return KDialog::marginHint();
+}
+
+int EditorToolSettings::spacingHint()
+{
+ return KDialog::spacingHint();
+}
+
+TQWidget *EditorToolSettings::plainPage() const
+{
+ return d->plainPage;
+}
+
+ImagePanIconWidget* EditorToolSettings::panIconView() const
+{
+ return d->panIconView;
+}
+
+KPushButton* EditorToolSettings::button(int buttonCode) const
+{
+ if (buttonCode & Default)
+ return d->defaultBtn;
+
+ if (buttonCode & Try)
+ return d->tryBtn;
+
+ if (buttonCode & Ok)
+ return d->okBtn;
+
+ if (buttonCode & Cancel)
+ return d->cancelBtn;
+
+ if (buttonCode & Load)
+ return d->loadBtn;
+
+ if (buttonCode & SaveAs)
+ return d->saveAsBtn;
+
+ return 0;
+}
+
+void EditorToolSettings::enableButton(int buttonCode, bool state)
+{
+ KPushButton *btn = button(buttonCode);
+ if (btn) btn->setEnabled(state);
+}
+
+TQColor EditorToolSettings::guideColor() const
+{
+ return d->guideColorBt->color();
+}
+
+void EditorToolSettings::setGuideColor(const TQColor& color)
+{
+ d->guideColorBt->setColor(color);
+}
+
+int EditorToolSettings::guideSize() const
+{
+ return d->guideSize->value();
+}
+
+void EditorToolSettings::setGuideSize(int size)
+{
+ d->guideSize->setValue(size);
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/imageeditor/editor/editortoolsettings.h b/src/utilities/imageeditor/editor/editortoolsettings.h
new file mode 100644
index 00000000..062b2e34
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editortoolsettings.h
@@ -0,0 +1,110 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-21
+ * Description : Editor tool settings template box
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EDITORTOOLSETTINGS_H
+#define EDITORTOOLSETTINGS_H
+
+// TQt includes.
+
+#include <tqscrollview.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class KPushButton;
+
+namespace Digikam
+{
+
+class ImagePanIconWidget;
+class EditorToolSettingsPriv;
+
+class DIGIKAM_EXPORT EditorToolSettings : public TQScrollView
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum ButtonCode
+ {
+ Default = 0x00000001,
+ Try = 0x00000002,
+ Ok = 0x00000004,
+ Cancel = 0x00000008,
+ SaveAs = 0x00000010,
+ Load = 0x00000020
+ };
+
+ enum ToolCode
+ {
+ NoTool = 0x00000001,
+ ColorGuide = 0x00000002,
+ PanIcon = 0x00000004
+ };
+
+public:
+
+ EditorToolSettings(int buttonMask, int toolMask=NoTool, TQWidget *parent=0);
+ ~EditorToolSettings();
+
+ virtual void setBusy(bool){};
+ virtual void writeSettings(){};
+ virtual void readSettings(){};
+ virtual void resetSettings(){};
+
+ int marginHint();
+ int spacingHint();
+
+ TQWidget *plainPage() const;
+
+ TQColor guideColor() const;
+ void setGuideColor(const TQColor& color);
+
+ int guideSize() const;
+ void setGuideSize(int size);
+
+ ImagePanIconWidget* panIconView() const;
+ KPushButton* button(int buttonCode) const;
+ void enableButton(int buttonCode, bool state);
+
+ virtual TQSize minimumSizeHint() const;
+
+signals:
+
+ void signalOkClicked();
+ void signalCancelClicked();
+ void signalTryClicked();
+ void signalDefaultClicked();
+ void signalSaveAsClicked();
+ void signalLoadClicked();
+ void signalColorGuideChanged();
+
+private:
+
+ EditorToolSettingsPriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif // EDITORTOOLSETTINGS_H
diff --git a/src/utilities/imageeditor/editor/editorwindow.cpp b/src/utilities/imageeditor/editor/editorwindow.cpp
new file mode 100644
index 00000000..eed7cfea
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editorwindow.cpp
@@ -0,0 +1,1932 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : main image editor GUI implementation
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+}
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqlabel.h>
+#include <tqdockarea.h>
+#include <tqlayout.h>
+#include <tqtooltip.h>
+#include <tqtoolbutton.h>
+#include <tqsplitter.h>
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqfile.h>
+#include <tqcursor.h>
+#include <tqtimer.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <kprinter.h>
+#include <kkeydialog.h>
+#include <tdeversion.h>
+#include <tdeaction.h>
+#include <kedittoolbar.h>
+#include <tdeaboutdata.h>
+#include <kcursor.h>
+#include <kstdaction.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <tdelocale.h>
+#include <tdefiledialog.h>
+#include <tdemenubar.h>
+#include <kimageio.h>
+#include <tdeaccel.h>
+#include <tdemessagebox.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+#include <kiconloader.h>
+#include <tdeio/netaccess.h>
+#include <tdeio/job.h>
+#include <kprotocolinfo.h>
+#include <tdeglobalsettings.h>
+#include <tdetoolbar.h>
+#include <kstatusbar.h>
+#include <kprogress.h>
+#include <twin.h>
+#include <kcombobox.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dpopupmenu.h"
+#include "canvas.h"
+#include "dimginterface.h"
+#include "imagedialog.h"
+#include "imageplugin.h"
+#include "imagepluginloader.h"
+#include "imageresize.h"
+#include "imageprint.h"
+#include "filesaveoptionsbox.h"
+#include "statusprogressbar.h"
+#include "iccsettingscontainer.h"
+#include "exposurecontainer.h"
+#include "iofilesettingscontainer.h"
+#include "savingcontextcontainer.h"
+#include "loadingcacheinterface.h"
+#include "slideshowsettings.h"
+#include "themeengine.h"
+#include "rawcameradlg.h"
+#include "editorstackview.h"
+#include "editortooliface.h"
+#include "editorwindowprivate.h"
+#include "editorwindow.h"
+#include "editorwindow.moc"
+
+void tqt_enter_modal( TQWidget *widget );
+void tqt_leave_modal( TQWidget *widget );
+
+namespace Digikam
+{
+
+EditorWindow::EditorWindow(const char *name)
+ : TDEMainWindow(0, name, WType_TopLevel)
+{
+ d = new EditorWindowPriv;
+
+ m_themeMenuAction = 0;
+ m_contextMenu = 0;
+ m_canvas = 0;
+ m_imagePluginLoader = 0;
+ m_undoAction = 0;
+ m_redoAction = 0;
+ m_fullScreenAction = 0;
+ m_saveAction = 0;
+ m_saveAsAction = 0;
+ m_revertAction = 0;
+ m_fileDeleteAction = 0;
+ m_forwardAction = 0;
+ m_backwardAction = 0;
+ m_firstAction = 0;
+ m_lastAction = 0;
+ m_undoAction = 0;
+ m_redoAction = 0;
+ m_stackView = 0;
+ m_fullScreen = false;
+ m_rotatedOrFlipped = false;
+ m_setExifOrientationTag = true;
+ m_cancelSlideShow = false;
+
+ // Settings containers instance.
+
+ d->ICCSettings = new ICCSettingsContainer();
+ d->exposureSettings = new ExposureSettingsContainer();
+ d->toolIface = new EditorToolIface(this);
+ m_IOFileSettings = new IOFileSettingsContainer();
+ m_savingContext = new SavingContextContainer();
+}
+
+EditorWindow::~EditorWindow()
+{
+ delete m_canvas;
+ delete m_IOFileSettings;
+ delete m_savingContext;
+ delete d->ICCSettings;
+ delete d->exposureSettings;
+ delete d;
+}
+
+EditorStackView* EditorWindow::editorStackView() const
+{
+ return m_stackView;
+}
+
+void EditorWindow::setupContextMenu()
+{
+ m_contextMenu = new DPopupMenu(this);
+ TDEActionCollection *ac = actionCollection();
+ if( ac->action("editorwindow_backward") ) ac->action("editorwindow_backward")->plug(m_contextMenu);
+ if( ac->action("editorwindow_forward") ) ac->action("editorwindow_forward")->plug(m_contextMenu);
+ m_contextMenu->insertSeparator();
+ if( ac->action("editorwindow_slideshow") ) ac->action("editorwindow_slideshow")->plug(m_contextMenu);
+ if( ac->action("editorwindow_rotate_left") ) ac->action("editorwindow_rotate_left")->plug(m_contextMenu);
+ if( ac->action("editorwindow_rotate_right") ) ac->action("editorwindow_rotate_right")->plug(m_contextMenu);
+ if( ac->action("editorwindow_crop") ) ac->action("editorwindow_crop")->plug(m_contextMenu);
+ m_contextMenu->insertSeparator();
+ if( ac->action("editorwindow_delete") ) ac->action("editorwindow_delete")->plug(m_contextMenu);
+}
+
+void EditorWindow::setupStandardConnections()
+{
+ // -- Canvas connections ------------------------------------------------
+
+ connect(m_canvas, TQ_SIGNAL(signalToggleOffFitToWindow()),
+ this, TQ_SLOT(slotToggleOffFitToWindow()));
+
+ connect(m_canvas, TQ_SIGNAL(signalShowNextImage()),
+ this, TQ_SLOT(slotForward()));
+
+ connect(m_canvas, TQ_SIGNAL(signalShowPrevImage()),
+ this, TQ_SLOT(slotBackward()));
+
+ connect(m_canvas, TQ_SIGNAL(signalRightButtonClicked()),
+ this, TQ_SLOT(slotContextMenu()));
+
+ connect(m_stackView, TQ_SIGNAL(signalZoomChanged(bool, bool, double)),
+ this, TQ_SLOT(slotZoomChanged(bool, bool, double)));
+
+ connect(m_canvas, TQ_SIGNAL(signalChanged()),
+ this, TQ_SLOT(slotChanged()));
+
+ connect(m_canvas, TQ_SIGNAL(signalUndoStateChanged(bool, bool, bool)),
+ this, TQ_SLOT(slotUndoStateChanged(bool, bool, bool)));
+
+ connect(m_canvas, TQ_SIGNAL(signalSelected(bool)),
+ this, TQ_SLOT(slotSelected(bool)));
+
+ connect(m_canvas, TQ_SIGNAL(signalPrepareToLoad()),
+ this, TQ_SLOT(slotPrepareToLoad()));
+
+ connect(m_canvas, TQ_SIGNAL(signalLoadingStarted(const TQString &)),
+ this, TQ_SLOT(slotLoadingStarted(const TQString &)));
+
+ connect(m_canvas, TQ_SIGNAL(signalLoadingFinished(const TQString &, bool)),
+ this, TQ_SLOT(slotLoadingFinished(const TQString &, bool)));
+
+ connect(m_canvas, TQ_SIGNAL(signalLoadingProgress(const TQString &, float)),
+ this, TQ_SLOT(slotLoadingProgress(const TQString &, float)));
+
+ connect(m_canvas, TQ_SIGNAL(signalSavingStarted(const TQString&)),
+ this, TQ_SLOT(slotSavingStarted(const TQString&)));
+
+ connect(m_canvas, TQ_SIGNAL(signalSavingFinished(const TQString&, bool)),
+ this, TQ_SLOT(slotSavingFinished(const TQString&, bool)));
+
+ connect(m_canvas, TQ_SIGNAL(signalSavingProgress(const TQString&, float)),
+ this, TQ_SLOT(slotSavingProgress(const TQString&, float)));
+
+ connect(m_canvas, TQ_SIGNAL(signalSelectionChanged(const TQRect&)),
+ this, TQ_SLOT(slotSelectionChanged(const TQRect&)));
+
+ // -- if rotating/flipping set the rotatedflipped flag to true -----------
+
+ connect(d->rotateLeftAction, TQ_SIGNAL(activated()),
+ this, TQ_SLOT(slotRotatedOrFlipped()));
+
+ connect(d->rotateRightAction, TQ_SIGNAL(activated()),
+ this, TQ_SLOT(slotRotatedOrFlipped()));
+
+ connect(d->flipHorizAction, TQ_SIGNAL(activated()),
+ this, TQ_SLOT(slotRotatedOrFlipped()));
+
+ connect(d->flipVertAction, TQ_SIGNAL(activated()),
+ this, TQ_SLOT(slotRotatedOrFlipped()));
+
+ // -- status bar connections --------------------------------------
+
+ connect(m_nameLabel, TQ_SIGNAL(signalCancelButtonPressed()),
+ this, TQ_SLOT(slotNameLabelCancelButtonPressed()));
+
+ connect(m_nameLabel, TQ_SIGNAL(signalCancelButtonPressed()),
+ d->toolIface, TQ_SLOT(slotToolAborted()));
+}
+
+void EditorWindow::setupStandardActions()
+{
+ // -- Standard 'File' menu actions ---------------------------------------------
+
+ m_backwardAction = KStdAction::back(this, TQ_SLOT(slotBackward()),
+ actionCollection(), "editorwindow_backward");
+
+ m_forwardAction = KStdAction::forward(this, TQ_SLOT(slotForward()),
+ actionCollection(), "editorwindow_forward");
+
+ m_firstAction = new TDEAction(i18n("&First"), "go-first",
+ TDEStdAccel::shortcut( TDEStdAccel::Home),
+ this, TQ_SLOT(slotFirst()),
+ actionCollection(), "editorwindow_first");
+
+ m_lastAction = new TDEAction(i18n("&Last"), "go-last",
+ TDEStdAccel::shortcut( TDEStdAccel::End),
+ this, TQ_SLOT(slotLast()),
+ actionCollection(), "editorwindow_last");
+
+ m_saveAction = KStdAction::save(this, TQ_SLOT(slotSave()),
+ actionCollection(), "editorwindow_save");
+
+ m_saveAsAction = KStdAction::saveAs(this, TQ_SLOT(slotSaveAs()),
+ actionCollection(), "editorwindow_saveas");
+
+ m_revertAction = KStdAction::revert(this, TQ_SLOT(slotRevert()),
+ actionCollection(), "editorwindow_revert");
+
+ m_saveAction->setEnabled(false);
+ m_saveAsAction->setEnabled(false);
+ m_revertAction->setEnabled(false);
+
+ d->filePrintAction = new TDEAction(i18n("Print Image..."), "document-print",
+ CTRL+Key_P,
+ this, TQ_SLOT(slotFilePrint()),
+ actionCollection(), "editorwindow_print");
+
+ m_fileDeleteAction = new TDEAction(i18n("Move to Trash"), "edittrash",
+ Key_Delete,
+ this, TQ_SLOT(slotDeleteCurrentItem()),
+ actionCollection(), "editorwindow_delete");
+
+ KStdAction::close(this, TQ_SLOT(close()), actionCollection(), "editorwindow_close");
+
+ // -- Standard 'Edit' menu actions ---------------------------------------------
+
+ d->copyAction = KStdAction::copy(m_canvas, TQ_SLOT(slotCopy()),
+ actionCollection(), "editorwindow_copy");
+
+ d->copyAction->setEnabled(false);
+
+ m_undoAction = new TDEToolBarPopupAction(i18n("Undo"), "edit-undo",
+ TDEStdAccel::shortcut(TDEStdAccel::Undo),
+ m_canvas, TQ_SLOT(slotUndo()),
+ actionCollection(), "editorwindow_undo");
+
+ connect(m_undoAction->popupMenu(), TQ_SIGNAL(aboutToShow()),
+ this, TQ_SLOT(slotAboutToShowUndoMenu()));
+
+ connect(m_undoAction->popupMenu(), TQ_SIGNAL(activated(int)),
+ m_canvas, TQ_SLOT(slotUndo(int)));
+
+ m_undoAction->setEnabled(false);
+
+ m_redoAction = new TDEToolBarPopupAction(i18n("Redo"), "edit-redo",
+ TDEStdAccel::shortcut(TDEStdAccel::Redo),
+ m_canvas, TQ_SLOT(slotRedo()),
+ actionCollection(), "editorwindow_redo");
+
+ connect(m_redoAction->popupMenu(), TQ_SIGNAL(aboutToShow()),
+ this, TQ_SLOT(slotAboutToShowRedoMenu()));
+
+ connect(m_redoAction->popupMenu(), TQ_SIGNAL(activated(int)),
+ m_canvas, TQ_SLOT(slotRedo(int)));
+
+ m_redoAction->setEnabled(false);
+
+ d->selectAllAction = new TDEAction(i18n("Select All"),
+ 0,
+ CTRL+Key_A,
+ m_canvas,
+ TQ_SLOT(slotSelectAll()),
+ actionCollection(),
+ "editorwindow_selectAll");
+
+ d->selectNoneAction = new TDEAction(i18n("Select None"),
+ 0,
+ CTRL+SHIFT+Key_A,
+ m_canvas,
+ TQ_SLOT(slotSelectNone()),
+ actionCollection(),
+ "editorwindow_selectNone");
+
+ // -- Standard 'View' menu actions ---------------------------------------------
+
+ d->zoomPlusAction = KStdAction::zoomIn(this, TQ_SLOT(slotIncreaseZoom()),
+ actionCollection(), "editorwindow_zoomplus");
+
+ d->zoomMinusAction = KStdAction::zoomOut(this, TQ_SLOT(slotDecreaseZoom()),
+ actionCollection(), "editorwindow_zoomminus");
+
+ d->zoomTo100percents = new TDEAction(i18n("Zoom to 100%"), "zoom-original",
+ ALT+CTRL+Key_0, // NOTE: Photoshop 7 use ALT+CTRL+0.
+ this, TQ_SLOT(slotZoomTo100Percents()),
+ actionCollection(), "editorwindow_zoomto100percents");
+
+
+ d->zoomFitToWindowAction = new TDEToggleAction(i18n("Fit to &Window"), "view_fit_window",
+ CTRL+SHIFT+Key_E, // NOTE: Gimp 2 use CTRL+SHIFT+E.
+ this, TQ_SLOT(slotToggleFitToWindow()),
+ actionCollection(), "editorwindow_zoomfit2window");
+
+ d->zoomFitToSelectAction = new TDEAction(i18n("Fit to &Selection"), "zoom-fit-best",
+ ALT+CTRL+Key_S, this, TQ_SLOT(slotFitToSelect()),
+ actionCollection(), "editorwindow_zoomfit2select");
+ d->zoomFitToSelectAction->setEnabled(false);
+ d->zoomFitToSelectAction->setWhatsThis(i18n("This option can be used to zoom the image to the "
+ "current selection area."));
+
+ d->zoomCombo = new KComboBox(true);
+ d->zoomCombo->setDuplicatesEnabled(false);
+ d->zoomCombo->setFocusPolicy(TQWidget::ClickFocus);
+ d->zoomCombo->setInsertionPolicy(TQComboBox::NoInsertion);
+ d->zoomComboAction = new KWidgetAction(d->zoomCombo, i18n("Zoom"), 0, 0, 0,
+ actionCollection(), "editorwindow_zoomto");
+
+ d->zoomCombo->insertItem(TQString("10%"));
+ d->zoomCombo->insertItem(TQString("25%"));
+ d->zoomCombo->insertItem(TQString("50%"));
+ d->zoomCombo->insertItem(TQString("75%"));
+ d->zoomCombo->insertItem(TQString("100%"));
+ d->zoomCombo->insertItem(TQString("150%"));
+ d->zoomCombo->insertItem(TQString("200%"));
+ d->zoomCombo->insertItem(TQString("300%"));
+ d->zoomCombo->insertItem(TQString("450%"));
+ d->zoomCombo->insertItem(TQString("600%"));
+ d->zoomCombo->insertItem(TQString("800%"));
+ d->zoomCombo->insertItem(TQString("1200%"));
+
+ connect(d->zoomCombo, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotZoomSelected()) );
+
+ connect(d->zoomCombo, TQ_SIGNAL(returnPressed(const TQString&)),
+ this, TQ_SLOT(slotZoomTextChanged(const TQString &)) );
+
+ // Do not use std KDE action for full screen because action text is too large for app. toolbar.
+ m_fullScreenAction = new TDEToggleAction(i18n("Full Screen"), "view-fullscreen",
+ CTRL+SHIFT+Key_F, this,
+ TQ_SLOT(slotToggleFullScreen()),
+ actionCollection(), "editorwindow_fullscreen");
+ m_fullScreenAction->setWhatsThis(i18n("Toggle the window to full screen mode"));
+
+ d->slideShowAction = new TDEAction(i18n("Slideshow"), "slideshow", Key_F9,
+ this, TQ_SLOT(slotToggleSlideShow()),
+ actionCollection(),"editorwindow_slideshow");
+
+ d->viewUnderExpoAction = new TDEToggleAction(i18n("Under-Exposure Indicator"), "underexposure",
+ Key_F10, this,
+ TQ_SLOT(slotToggleUnderExposureIndicator()),
+ actionCollection(),"editorwindow_underexposure");
+
+ d->viewOverExpoAction = new TDEToggleAction(i18n("Over-Exposure Indicator"), "overexposure",
+ Key_F11, this,
+ TQ_SLOT(slotToggleOverExposureIndicator()),
+ actionCollection(),"editorwindow_overexposure");
+
+ d->viewCMViewAction = new TDEToggleAction(i18n("Color Managed View"), "tv",
+ Key_F12, this,
+ TQ_SLOT(slotToggleColorManagedView()),
+ actionCollection(),"editorwindow_cmview");
+
+ // -- Standard 'Transform' menu actions ---------------------------------------------
+
+ d->resizeAction = new TDEAction(i18n("&Resize..."), "resize_image", 0,
+ this, TQ_SLOT(slotResize()),
+ actionCollection(), "editorwindow_resize");
+
+ d->cropAction = new TDEAction(i18n("Crop"), "crop",
+ CTRL+Key_X,
+ m_canvas, TQ_SLOT(slotCrop()),
+ actionCollection(), "editorwindow_crop");
+
+ d->cropAction->setEnabled(false);
+ d->cropAction->setWhatsThis(i18n("This option can be used to crop the image. "
+ "Select a region of the image to enable this action."));
+
+ // -- Standard 'Flip' menu actions ---------------------------------------------
+
+ d->flipHorizAction = new TDEAction(i18n("Flip Horizontally"), "mirror", CTRL+Key_Asterisk,
+ m_canvas, TQ_SLOT(slotFlipHoriz()),
+ actionCollection(), "editorwindow_flip_horiz");
+ d->flipHorizAction->setEnabled(false);
+
+ d->flipVertAction = new TDEAction(i18n("Flip Vertically"), "flip", CTRL+Key_Slash,
+ m_canvas, TQ_SLOT(slotFlipVert()),
+ actionCollection(), "editorwindow_flip_vert");
+ d->flipVertAction->setEnabled(false);
+
+ // -- Standard 'Rotate' menu actions ----------------------------------------
+
+ d->rotateLeftAction = new TDEAction(i18n("Rotate Left"),
+ "object-rotate-left", SHIFT+CTRL+Key_Left,
+ m_canvas, TQ_SLOT(slotRotate270()),
+ actionCollection(),
+ "editorwindow_rotate_left");
+ d->rotateLeftAction->setEnabled(false);
+ d->rotateRightAction = new TDEAction(i18n("Rotate Right"),
+ "object-rotate-right", SHIFT+CTRL+Key_Right,
+ m_canvas, TQ_SLOT(slotRotate90()),
+ actionCollection(),
+ "editorwindow_rotate_right");
+ d->rotateRightAction->setEnabled(false);
+
+ // -- Standard 'Configure' menu actions ----------------------------------------
+
+ d->showMenuBarAction = KStdAction::showMenubar(this, TQ_SLOT(slotShowMenuBar()), actionCollection());
+
+ KStdAction::keyBindings(this, TQ_SLOT(slotEditKeys()), actionCollection());
+ KStdAction::configureToolbars(this, TQ_SLOT(slotConfToolbars()), actionCollection());
+ KStdAction::preferences(this, TQ_SLOT(slotSetup()), actionCollection());
+
+ // -----------------------------------------------------------------------------------------
+
+ m_themeMenuAction = new TDESelectAction(i18n("&Themes"), 0, actionCollection(), "theme_menu");
+ m_themeMenuAction->setItems(ThemeEngine::instance()->themeNames());
+
+ connect(m_themeMenuAction, TQ_SIGNAL(activated(const TQString&)),
+ this, TQ_SLOT(slotChangeTheme(const TQString&)));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ // -- Standard 'Help' menu actions ---------------------------------------------
+
+ d->donateMoneyAction = new TDEAction(i18n("Donate..."),
+ 0, 0,
+ this, TQ_SLOT(slotDonateMoney()),
+ actionCollection(),
+ "editorwindow_donatemoney");
+
+ d->contributeAction = new TDEAction(i18n("Contribute..."),
+ 0, 0,
+ this, TQ_SLOT(slotContribute()),
+ actionCollection(),
+ "editorwindow_contribute");
+
+ d->rawCameraListAction = new TDEAction(i18n("Supported RAW Cameras"),
+ "kdcraw",
+ 0,
+ this,
+ TQ_SLOT(slotRawCameraList()),
+ actionCollection(),
+ "editorwindow_rawcameralist");
+}
+
+void EditorWindow::setupStandardAccelerators()
+{
+ d->accelerators = new TDEAccel(this);
+
+ d->accelerators->insert("Exit fullscreen", i18n("Exit Fullscreen mode"),
+ i18n("Exit out of the fullscreen mode"),
+ Key_Escape, this, TQ_SLOT(slotEscapePressed()),
+ false, true);
+
+ d->accelerators->insert("Next Image Key_Space", i18n("Next Image"),
+ i18n("Load Next Image"),
+ Key_Space, this, TQ_SLOT(slotForward()),
+ false, true);
+
+ d->accelerators->insert("Previous Image SHIFT+Key_Space", i18n("Previous Image"),
+ i18n("Load Previous Image"),
+ SHIFT+Key_Space, this, TQ_SLOT(slotBackward()),
+ false, true);
+
+ d->accelerators->insert("Previous Image Key_Backspace", i18n("Previous Image"),
+ i18n("Load Previous Image"),
+ Key_Backspace, this, TQ_SLOT(slotBackward()),
+ false, true);
+
+ d->accelerators->insert("Next Image Key_Next", i18n("Next Image"),
+ i18n("Load Next Image"),
+ Key_Next, this, TQ_SLOT(slotForward()),
+ false, true);
+
+ d->accelerators->insert("Previous Image Key_Prior", i18n("Previous Image"),
+ i18n("Load Previous Image"),
+ Key_Prior, this, TQ_SLOT(slotBackward()),
+ false, true);
+
+ d->accelerators->insert("Zoom Plus Key_Plus", i18n("Zoom In"),
+ i18n("Zoom in on Image"),
+ Key_Plus, this, TQ_SLOT(slotIncreaseZoom()),
+ false, true);
+
+ d->accelerators->insert("Zoom Plus Key_Minus", i18n("Zoom Out"),
+ i18n("Zoom out of Image"),
+ Key_Minus, this, TQ_SLOT(slotDecreaseZoom()),
+ false, true);
+
+ d->accelerators->insert("Redo CTRL+Key_Y", i18n("Redo"),
+ i18n("Redo Last action"),
+ CTRL+Key_Y, m_canvas, TQ_SLOT(slotRedo()),
+ false, true);
+}
+
+void EditorWindow::setupStatusBar()
+{
+ m_nameLabel = new StatusProgressBar(statusBar());
+ m_nameLabel->setAlignment(TQt::AlignCenter);
+ m_nameLabel->setMaximumHeight(fontMetrics().height()+2);
+ statusBar()->addWidget(m_nameLabel, 100);
+
+ d->selectLabel = new TQLabel(i18n("No selection"), statusBar());
+ d->selectLabel->setAlignment(TQt::AlignCenter);
+ d->selectLabel->setMaximumHeight(fontMetrics().height()+2);
+ statusBar()->addWidget(d->selectLabel, 100);
+ TQToolTip::add(d->selectLabel, i18n("Information about current selection area"));
+
+ m_resLabel = new TQLabel(statusBar());
+ m_resLabel->setAlignment(TQt::AlignCenter);
+ m_resLabel->setMaximumHeight(fontMetrics().height()+2);
+ statusBar()->addWidget(m_resLabel, 100);
+ TQToolTip::add(m_resLabel, i18n("Information about image size"));
+
+ d->underExposureIndicator = new TQToolButton(statusBar());
+ d->underExposureIndicator->setIconSet(SmallIcon("underexposure"));
+ d->underExposureIndicator->setToggleButton(true);
+ statusBar()->addWidget(d->underExposureIndicator, 1);
+
+ d->overExposureIndicator = new TQToolButton(statusBar());
+ d->overExposureIndicator->setIconSet(SmallIcon("overexposure"));
+ d->overExposureIndicator->setToggleButton(true);
+ statusBar()->addWidget(d->overExposureIndicator, 1);
+
+ d->cmViewIndicator = new TQToolButton(statusBar());
+ d->cmViewIndicator->setIconSet(SmallIcon("tv"));
+ d->cmViewIndicator->setToggleButton(true);
+ statusBar()->addWidget(d->cmViewIndicator, 1);
+
+ connect(d->underExposureIndicator, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotToggleUnderExposureIndicator()));
+
+ connect(d->overExposureIndicator, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotToggleOverExposureIndicator()));
+
+ connect(d->cmViewIndicator, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotToggleColorManagedView()));
+}
+
+void EditorWindow::printImage(KURL url)
+{
+ uchar* ptr = m_canvas->interface()->getImage();
+ int w = m_canvas->interface()->origWidth();
+ int h = m_canvas->interface()->origHeight();
+ bool hasAlpha = m_canvas->interface()->hasAlpha();
+ bool sixteenBit = m_canvas->interface()->sixteenBit();
+
+ if (!ptr || !w || !h)
+ return;
+
+ DImg image(w, h, sixteenBit, hasAlpha, ptr);
+
+ KPrinter printer;
+ TQString appName = TDEApplication::kApplication()->aboutData()->appName();
+ printer.setDocName( url.filename() );
+ printer.setCreator( appName );
+#if KDE_IS_VERSION(3,2,0)
+ printer.setUsePrinterResolution(true);
+#endif
+
+ KPrinter::addDialogPage( new ImageEditorPrintDialogPage(image, this, TQString(appName.append(" page")).ascii() ));
+
+ if ( printer.setup( this, i18n("Print %1").arg(printer.docName().section('/', -1)) ) )
+ {
+ ImagePrint printOperations(image, printer, url.filename());
+ if (!printOperations.printImageWithTQt())
+ {
+ KMessageBox::error(this, i18n("Failed to print file: '%1'")
+ .arg(url.filename()));
+ }
+ }
+}
+
+void EditorWindow::slotEditKeys()
+{
+ KKeyDialog dialog(true, this);
+ dialog.insert( actionCollection(), i18n( "General" ) );
+
+ TQPtrList<ImagePlugin> pluginList = ImagePluginLoader::instance()->pluginList();
+
+ for (ImagePlugin* plugin = pluginList.first();
+ plugin; plugin = pluginList.next())
+ {
+ if (plugin)
+ {
+ dialog.insert( plugin->actionCollection(), plugin->name() );
+ }
+ }
+
+ dialog.configure();
+}
+
+void EditorWindow::slotResize()
+{
+ ImageResize dlg(this);
+ dlg.exec();
+}
+
+void EditorWindow::slotAboutToShowUndoMenu()
+{
+ m_undoAction->popupMenu()->clear();
+ TQStringList titles;
+ m_canvas->getUndoHistory(titles);
+
+ if(!titles.isEmpty())
+ {
+ int id = 1;
+ TQStringList::Iterator iter = titles.begin();
+ for(; iter != titles.end(); ++iter,++id)
+ {
+ m_undoAction->popupMenu()->insertItem(*iter, id);
+ }
+ }
+}
+
+void EditorWindow::slotAboutToShowRedoMenu()
+{
+ m_redoAction->popupMenu()->clear();
+ TQStringList titles;
+ m_canvas->getRedoHistory(titles);
+
+ if(!titles.isEmpty())
+ {
+ int id = 1;
+ TQStringList::Iterator iter = titles.begin();
+ for(; iter != titles.end(); ++iter,++id)
+ {
+ m_redoAction->popupMenu()->insertItem(*iter, id);
+ }
+ }
+}
+
+void EditorWindow::slotConfToolbars()
+{
+ saveMainWindowSettings(TDEGlobal::config(), "ImageViewer Settings");
+ KEditToolbar dlg(factory(), this);
+
+ connect(&dlg, TQ_SIGNAL(newToolbarConfig()),
+ this, TQ_SLOT(slotNewToolbarConfig()));
+
+ dlg.exec();
+}
+
+void EditorWindow::slotNewToolbarConfig()
+{
+ applyMainWindowSettings(TDEGlobal::config(), "ImageViewer Settings");
+}
+
+void EditorWindow::slotIncreaseZoom()
+{
+ m_stackView->increaseZoom();
+}
+
+void EditorWindow::slotDecreaseZoom()
+{
+ m_stackView->decreaseZoom();
+}
+
+void EditorWindow::slotToggleFitToWindow()
+{
+ d->zoomPlusAction->setEnabled(true);
+ d->zoomComboAction->setEnabled(true);
+ d->zoomMinusAction->setEnabled(true);
+ m_stackView->toggleFitToWindow();
+}
+
+void EditorWindow::slotFitToSelect()
+{
+ d->zoomPlusAction->setEnabled(true);
+ d->zoomComboAction->setEnabled(true);
+ d->zoomMinusAction->setEnabled(true);
+ m_stackView->fitToSelect();
+}
+
+void EditorWindow::slotZoomTo100Percents()
+{
+ d->zoomPlusAction->setEnabled(true);
+ d->zoomComboAction->setEnabled(true);
+ d->zoomMinusAction->setEnabled(true);
+ m_stackView->zoomTo100Percents();
+}
+
+void EditorWindow::slotZoomSelected()
+{
+ TQString txt = d->zoomCombo->currentText();
+ txt = txt.left(txt.find('%'));
+ slotZoomTextChanged(txt);
+}
+
+void EditorWindow::slotZoomTextChanged(const TQString &txt)
+{
+ bool r = false;
+ double zoom = TDEGlobal::locale()->readNumber(txt, &r) / 100.0;
+ if (r && zoom > 0.0)
+ m_stackView->setZoomFactor(zoom);
+}
+
+void EditorWindow::slotZoomChanged(bool isMax, bool isMin, double zoom)
+{
+ d->zoomPlusAction->setEnabled(!isMax);
+ d->zoomMinusAction->setEnabled(!isMin);
+
+ d->zoomCombo->blockSignals(true);
+ d->zoomCombo->setCurrentText(TQString::number(lround(zoom*100.0)) + TQString("%"));
+ d->zoomCombo->blockSignals(false);
+}
+
+void EditorWindow::slotToggleOffFitToWindow()
+{
+ d->zoomFitToWindowAction->blockSignals(true);
+ d->zoomFitToWindowAction->setChecked(false);
+ d->zoomFitToWindowAction->blockSignals(false);
+}
+
+void EditorWindow::slotEscapePressed()
+{
+ if (m_fullScreen)
+ m_fullScreenAction->activate();
+}
+
+void EditorWindow::plugActionAccel(TDEAction* action)
+{
+ if (!action)
+ return;
+
+ d->accelerators->insert(action->text(),
+ action->text(),
+ action->whatsThis(),
+ action->shortcut(),
+ action,
+ TQ_SLOT(activate()));
+}
+
+void EditorWindow::unplugActionAccel(TDEAction* action)
+{
+ d->accelerators->remove(action->text());
+}
+
+void EditorWindow::loadImagePlugins()
+{
+ TQPtrList<ImagePlugin> pluginList = m_imagePluginLoader->pluginList();
+
+ for (ImagePlugin* plugin = pluginList.first();
+ plugin; plugin = pluginList.next())
+ {
+ if (plugin)
+ {
+ guiFactory()->addClient(plugin);
+ plugin->setEnabledSelectionActions(false);
+ }
+ else
+ DDebug() << "Invalid plugin to add!" << endl;
+ }
+}
+
+void EditorWindow::unLoadImagePlugins()
+{
+ TQPtrList<ImagePlugin> pluginList = m_imagePluginLoader->pluginList();
+
+ for (ImagePlugin* plugin = pluginList.first();
+ plugin; plugin = pluginList.next())
+ {
+ if (plugin)
+ {
+ guiFactory()->removeClient(plugin);
+ plugin->setEnabledSelectionActions(false);
+ }
+ }
+}
+
+void EditorWindow::readStandardSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ // Restore full screen Mode ?
+
+ if (config->readBoolEntry("FullScreen", false))
+ {
+ m_fullScreenAction->activate();
+ m_fullScreen = true;
+ }
+
+ // Restore Auto zoom action ?
+ bool autoZoom = config->readBoolEntry("AutoZoom", true);
+ if (autoZoom)
+ d->zoomFitToWindowAction->activate();
+}
+
+void EditorWindow::applyStandardSettings()
+{
+ TDEConfig* config = kapp->config();
+
+ // -- Settings for Color Management stuff ----------------------------------------------
+
+ config->setGroup("Color Management");
+
+ d->ICCSettings->enableCMSetting = config->readBoolEntry("EnableCM", false);
+ d->ICCSettings->askOrApplySetting = config->readBoolEntry("BehaviourICC", false);
+ d->ICCSettings->BPCSetting = config->readBoolEntry("BPCAlgorithm",false);
+ d->ICCSettings->managedViewSetting = config->readBoolEntry("ManagedView", false);
+ d->ICCSettings->renderingSetting = config->readNumEntry("RenderingIntent");
+ d->ICCSettings->inputSetting = config->readPathEntry("InProfileFile", TQString());
+ d->ICCSettings->workspaceSetting = config->readPathEntry("WorkProfileFile", TQString());
+ d->ICCSettings->monitorSetting = config->readPathEntry("MonitorProfileFile", TQString());
+ d->ICCSettings->proofSetting = config->readPathEntry("ProofProfileFile", TQString());
+
+ d->viewCMViewAction->setEnabled(d->ICCSettings->enableCMSetting);
+ d->viewCMViewAction->setChecked(d->ICCSettings->managedViewSetting);
+ d->cmViewIndicator->setEnabled(d->ICCSettings->enableCMSetting);
+ d->cmViewIndicator->setOn(d->ICCSettings->managedViewSetting);
+ setColorManagedViewIndicatorToolTip(d->ICCSettings->enableCMSetting, d->ICCSettings->managedViewSetting);
+ m_canvas->setICCSettings(d->ICCSettings);
+
+ // -- JPEG, PNG, TIFF JPEG2000 files format settings --------------------------------------
+
+ config->setGroup("ImageViewer Settings");
+
+ // JPEG quality slider settings : 1 - 100 ==> libjpeg settings : 25 - 100.
+ m_IOFileSettings->JPEGCompression = (int)((75.0/100.0)*
+ (float)config->readNumEntry("JPEGCompression", 75)
+ + 26.0 - (75.0/100.0));
+
+ m_IOFileSettings->JPEGSubSampling = config->readNumEntry("JPEGSubSampling", 1); // Medium subsampling
+
+ // PNG compression slider settings : 1 - 9 ==> libpng settings : 100 - 1.
+ m_IOFileSettings->PNGCompression = (int)(((1.0-100.0)/8.0)*
+ (float)config->readNumEntry("PNGCompression", 1)
+ + 100.0 - ((1.0-100.0)/8.0));
+
+ // TIFF compression setting.
+ m_IOFileSettings->TIFFCompression = config->readBoolEntry("TIFFCompression", false);
+
+ // JPEG2000 quality slider settings : 1 - 100
+ m_IOFileSettings->JPEG2000Compression = config->readNumEntry("JPEG2000Compression", 100);
+
+ // JPEG2000 LossLess setting.
+ m_IOFileSettings->JPEG2000LossLess = config->readBoolEntry("JPEG2000LossLess", true);
+
+ // -- RAW images decoding settings ------------------------------------------------------
+
+ // If digiKam Color Management is enable, no need to correct color of decoded RAW image,
+ // else, sRGB color workspace will be used.
+
+ if (d->ICCSettings->enableCMSetting)
+ m_IOFileSettings->rawDecodingSettings.outputColorSpace = DRawDecoding::RAWCOLOR;
+ else
+ m_IOFileSettings->rawDecodingSettings.outputColorSpace = DRawDecoding::SRGB;
+
+ m_IOFileSettings->rawDecodingSettings.sixteenBitsImage = config->readBoolEntry("SixteenBitsImage", false);
+ m_IOFileSettings->rawDecodingSettings.whiteBalance = (DRawDecoding::WhiteBalance)config->readNumEntry("WhiteBalance",
+ DRawDecoding::CAMERA);
+ m_IOFileSettings->rawDecodingSettings.customWhiteBalance = config->readNumEntry("CustomWhiteBalance", 6500);
+ m_IOFileSettings->rawDecodingSettings.customWhiteBalanceGreen = config->readDoubleNumEntry("CustomWhiteBalanceGreen", 1.0);
+ m_IOFileSettings->rawDecodingSettings.RGBInterpolate4Colors = config->readBoolEntry("RGBInterpolate4Colors", false);
+ m_IOFileSettings->rawDecodingSettings.DontStretchPixels = config->readBoolEntry("DontStretchPixels", false);
+ m_IOFileSettings->rawDecodingSettings.enableNoiseReduction = config->readBoolEntry("EnableNoiseReduction", false);
+ m_IOFileSettings->rawDecodingSettings.unclipColors = config->readNumEntry("UnclipColors", 0);
+ m_IOFileSettings->rawDecodingSettings.RAWQuality = (DRawDecoding::DecodingQuality)config->readNumEntry("RAWQuality",
+ DRawDecoding::BILINEAR);
+ m_IOFileSettings->rawDecodingSettings.NRThreshold = config->readNumEntry("NRThreshold", 100);
+ m_IOFileSettings->rawDecodingSettings.enableCACorrection = config->readBoolEntry("EnableCACorrection", false);
+ m_IOFileSettings->rawDecodingSettings.caMultiplier[0] = config->readDoubleNumEntry("caRedMultiplier", 1.0);
+ m_IOFileSettings->rawDecodingSettings.caMultiplier[1] = config->readDoubleNumEntry("caBlueMultiplier", 1.0);
+ m_IOFileSettings->rawDecodingSettings.brightness = config->readDoubleNumEntry("RAWBrightness", 1.0);
+ m_IOFileSettings->rawDecodingSettings.medianFilterPasses = config->readNumEntry("MedianFilterPasses", 0);
+
+ m_IOFileSettings->useRAWImport = config->readBoolEntry("UseRawImportTool", false);
+
+ // -- GUI Settings -------------------------------------------------------
+
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+ if(config->hasKey("Splitter Sizes"))
+ m_splitter->setSizes(config->readIntListEntry("Splitter Sizes"));
+ else
+ m_canvas->setSizePolicy(rightSzPolicy);
+
+ d->fullScreenHideToolBar = config->readBoolEntry("FullScreen Hide ToolBar", false);
+
+ slotThemeChanged();
+
+ // -- Exposure Indicators Settings ---------------------------------------
+
+ TQColor black(TQt::black);
+ TQColor white(TQt::white);
+ d->exposureSettings->underExposureIndicator = config->readBoolEntry("UnderExposureIndicator", false);
+ d->exposureSettings->overExposureIndicator = config->readBoolEntry("OverExposureIndicator", false);
+ d->exposureSettings->underExposureColor = config->readColorEntry("UnderExposureColor", &white);
+ d->exposureSettings->overExposureColor = config->readColorEntry("OverExposureColor", &black);
+
+ d->viewUnderExpoAction->setChecked(d->exposureSettings->underExposureIndicator);
+ d->viewOverExpoAction->setChecked(d->exposureSettings->overExposureIndicator);
+ d->underExposureIndicator->setOn(d->exposureSettings->underExposureIndicator);
+ d->overExposureIndicator->setOn(d->exposureSettings->overExposureIndicator);
+ setUnderExposureToolTip(d->exposureSettings->underExposureIndicator);
+ setOverExposureToolTip(d->exposureSettings->overExposureIndicator);
+ m_canvas->setExposureSettings(d->exposureSettings);
+}
+
+void EditorWindow::saveStandardSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ config->writeEntry("AutoZoom", d->zoomFitToWindowAction->isChecked());
+ config->writeEntry("Splitter Sizes", m_splitter->sizes());
+
+ config->writeEntry("FullScreen", m_fullScreenAction->isChecked());
+ config->writeEntry("UnderExposureIndicator", d->exposureSettings->underExposureIndicator);
+ config->writeEntry("OverExposureIndicator", d->exposureSettings->overExposureIndicator);
+
+ config->sync();
+}
+
+/** Method used by Editor Tools. Only Zoom+ and Zoom- are currently supported.
+ TODO: Fix this behavour when editor tool preview widgets will be factored.
+ */
+void EditorWindow::toggleZoomActions(bool val)
+{
+ d->zoomMinusAction->setEnabled(val);
+ d->zoomPlusAction->setEnabled(val);
+}
+
+void EditorWindow::toggleStandardActions(bool val)
+{
+ d->zoomComboAction->setEnabled(val);
+ d->zoomTo100percents->setEnabled(val);
+ d->zoomFitToWindowAction->setEnabled(val);
+ d->zoomFitToSelectAction->setEnabled(val);
+ toggleZoomActions(val);
+
+ d->rotateLeftAction->setEnabled(val);
+ d->rotateRightAction->setEnabled(val);
+ d->flipHorizAction->setEnabled(val);
+ d->flipVertAction->setEnabled(val);
+ d->filePrintAction->setEnabled(val);
+ d->resizeAction->setEnabled(val);
+ m_fileDeleteAction->setEnabled(val);
+ m_saveAsAction->setEnabled(val);
+ d->selectAllAction->setEnabled(val);
+ d->selectNoneAction->setEnabled(val);
+ d->slideShowAction->setEnabled(val);
+
+ // these actions are special: They are turned off if val is false,
+ // but if val is true, they may be turned on or off.
+ if (val)
+ {
+ // Trigger sending of signalUndoStateChanged
+ // Note that for saving and loading, this is not necessary
+ // because the signal will be sent later anyway.
+ m_canvas->updateUndoState();
+ }
+ else
+ {
+ m_saveAction->setEnabled(val);
+ m_undoAction->setEnabled(val);
+ m_redoAction->setEnabled(val);
+ }
+
+ TQPtrList<ImagePlugin> pluginList = m_imagePluginLoader->pluginList();
+
+ for (ImagePlugin* plugin = pluginList.first();
+ plugin; plugin = pluginList.next())
+ {
+ if (plugin)
+ {
+ plugin->setEnabledActions(val);
+ }
+ }
+}
+
+void EditorWindow::slotToggleFullScreen()
+{
+ if (m_fullScreen) // out of fullscreen
+ {
+ m_canvas->setBackgroundColor(m_bgColor);
+
+ setWindowState( windowState() & ~WindowFullScreen );
+ menuBar()->show();
+ statusBar()->show();
+ leftDock()->show();
+ rightDock()->show();
+ topDock()->show();
+ bottomDock()->show();
+
+ TQObject* obj = child("ToolBar","TDEToolBar");
+
+ if (obj)
+ {
+ TDEToolBar* toolBar = static_cast<TDEToolBar*>(obj);
+
+ if (m_fullScreenAction->isPlugged(toolBar) && d->removeFullScreenButton)
+ m_fullScreenAction->unplug(toolBar);
+
+ if (toolBar->isHidden())
+ showToolBars();
+ }
+
+ // -- remove the gui action accels ----
+
+ unplugActionAccel(m_forwardAction);
+ unplugActionAccel(m_backwardAction);
+ unplugActionAccel(m_firstAction);
+ unplugActionAccel(m_lastAction);
+ unplugActionAccel(m_saveAction);
+ unplugActionAccel(m_saveAsAction);
+ unplugActionAccel(d->zoomPlusAction);
+ unplugActionAccel(d->zoomMinusAction);
+ unplugActionAccel(d->zoomFitToWindowAction);
+ unplugActionAccel(d->zoomFitToSelectAction);
+ unplugActionAccel(d->cropAction);
+ unplugActionAccel(d->filePrintAction);
+ unplugActionAccel(m_fileDeleteAction);
+ unplugActionAccel(d->selectAllAction);
+ unplugActionAccel(d->selectNoneAction);
+
+ toggleGUI2FullScreen();
+ m_fullScreen = false;
+ }
+ else // go to fullscreen
+ {
+ m_canvas->setBackgroundColor(TQColor(TQt::black));
+
+ // hide the menubar and the statusbar
+ menuBar()->hide();
+ statusBar()->hide();
+ topDock()->hide();
+ leftDock()->hide();
+ rightDock()->hide();
+ bottomDock()->hide();
+
+ TQObject* obj = child("ToolBar","TDEToolBar");
+
+ if (obj)
+ {
+ TDEToolBar* toolBar = static_cast<TDEToolBar*>(obj);
+
+ if (d->fullScreenHideToolBar)
+ {
+ hideToolBars();
+ }
+ else
+ {
+ showToolBars();
+
+ if ( !m_fullScreenAction->isPlugged(toolBar) )
+ {
+ m_fullScreenAction->plug(toolBar);
+ d->removeFullScreenButton=true;
+ }
+ else
+ {
+ // If FullScreen button is enable in toolbar settings
+ // We don't remove it when we out of fullscreen mode.
+ d->removeFullScreenButton=false;
+ }
+ }
+ }
+
+ // -- Insert all the gui actions into the accel --
+
+ plugActionAccel(m_forwardAction);
+ plugActionAccel(m_backwardAction);
+ plugActionAccel(m_firstAction);
+ plugActionAccel(m_lastAction);
+ plugActionAccel(m_saveAction);
+ plugActionAccel(m_saveAsAction);
+ plugActionAccel(d->zoomPlusAction);
+ plugActionAccel(d->zoomMinusAction);
+ plugActionAccel(d->zoomFitToWindowAction);
+ plugActionAccel(d->zoomFitToSelectAction);
+ plugActionAccel(d->cropAction);
+ plugActionAccel(d->filePrintAction);
+ plugActionAccel(m_fileDeleteAction);
+ plugActionAccel(d->selectAllAction);
+ plugActionAccel(d->selectNoneAction);
+
+ toggleGUI2FullScreen();
+ showFullScreen();
+ m_fullScreen = true;
+ }
+}
+
+void EditorWindow::slotRotatedOrFlipped()
+{
+ m_rotatedOrFlipped = true;
+}
+
+void EditorWindow::slotLoadingProgress(const TQString&, float progress)
+{
+ m_nameLabel->setProgressValue((int)(progress*100.0));
+}
+
+void EditorWindow::slotSavingProgress(const TQString&, float progress)
+{
+ m_nameLabel->setProgressValue((int)(progress*100.0));
+}
+
+bool EditorWindow::promptForOverWrite()
+{
+ TQFileInfo fi(m_canvas->currentImageFilePath());
+ TQString warnMsg(i18n("About to overwrite file \"%1\"\nAre you sure?")
+ .arg(fi.fileName()));
+ return (KMessageBox::warningContinueCancel(this,
+ warnMsg,
+ i18n("Warning"),
+ i18n("Overwrite"),
+ "editorWindowSaveOverwrite")
+ == KMessageBox::Continue);
+}
+
+bool EditorWindow::promptUserSave(const KURL& url)
+{
+ if (m_saveAction->isEnabled())
+ {
+ // if window is iconified, show it
+ if (isMinimized())
+ {
+ KWin::deIconifyWindow(winId());
+ }
+
+ int result = KMessageBox::warningYesNoCancel(this,
+ i18n("The image '%1' has been modified.\n"
+ "Do you want to save it?")
+ .arg(url.filename()),
+ TQString(),
+ KStdGuiItem::save(),
+ KStdGuiItem::discard());
+
+ if (result == KMessageBox::Yes)
+ {
+ bool saving = false;
+
+ if (m_canvas->isReadOnly())
+ saving = saveAs();
+ else if (promptForOverWrite())
+ saving = save();
+
+ // save and saveAs return false if they were cancelled and did not enter saving at all
+ // In this case, do not call enter_loop because exit_loop will not be called.
+ if (saving)
+ {
+ // Waiting for asynchronous image file saving operation runing in separate thread.
+ m_savingContext->synchronizingState = SavingContextContainer::SynchronousSaving;
+ enter_loop();
+ m_savingContext->synchronizingState = SavingContextContainer::NormalSaving;
+ return m_savingContext->synchronousSavingResult;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (result == KMessageBox::No)
+ {
+ m_saveAction->setEnabled(false);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool EditorWindow::waitForSavingToComplete()
+{
+ // avoid reentrancy - return false means we have reentered the loop already.
+ if (m_savingContext->synchronizingState == SavingContextContainer::SynchronousSaving)
+ return false;
+
+ if (m_savingContext->savingState != SavingContextContainer::SavingStateNone)
+ {
+ // Waiting for asynchronous image file saving operation runing in separate thread.
+ m_savingContext->synchronizingState = SavingContextContainer::SynchronousSaving;
+ KMessageBox::queuedMessageBox(this,
+ KMessageBox::Information,
+ i18n("Please wait while the image is being saved..."));
+ enter_loop();
+ m_savingContext->synchronizingState = SavingContextContainer::NormalSaving;
+ }
+ return true;
+}
+
+void EditorWindow::enter_loop()
+{
+ TQWidget dummy(0, 0, WType_Dialog | WShowModal);
+ dummy.setFocusPolicy( TQWidget::NoFocus );
+ tqt_enter_modal(&dummy);
+ tqApp->enter_loop();
+ tqt_leave_modal(&dummy);
+}
+
+void EditorWindow::slotSelected(bool val)
+{
+ // Update menu actions.
+ d->cropAction->setEnabled(val);
+ d->zoomFitToSelectAction->setEnabled(val);
+ d->copyAction->setEnabled(val);
+
+ for (ImagePlugin* plugin = m_imagePluginLoader->pluginList().first();
+ plugin; plugin = m_imagePluginLoader->pluginList().next())
+ {
+ if (plugin)
+ {
+ plugin->setEnabledSelectionActions(val);
+ }
+ }
+
+ TQRect sel = m_canvas->getSelectedArea();
+ // Update histogram into sidebar.
+ emit signalSelectionChanged(sel);
+
+ // Update status bar
+ if (val)
+ d->selectLabel->setText(TQString("(%1, %2) (%3 x %4)").arg(sel.x()).arg(sel.y())
+ .arg(sel.width()).arg(sel.height()));
+ else
+ d->selectLabel->setText(i18n("No selection"));
+}
+
+void EditorWindow::hideToolBars()
+{
+ TQPtrListIterator<TDEToolBar> it = toolBarIterator();
+ TDEToolBar* bar;
+
+ for(;it.current()!=0L; ++it)
+ {
+ bar = it.current();
+
+ if (bar->area())
+ bar->area()->hide();
+ else
+ bar->hide();
+ }
+}
+
+void EditorWindow::showToolBars()
+{
+ TQPtrListIterator<TDEToolBar> it = toolBarIterator();
+ TDEToolBar* bar;
+
+ for( ; it.current()!=0L ; ++it)
+ {
+ bar = it.current();
+
+ if (bar->area())
+ bar->area()->show();
+ else
+ bar->show();
+ }
+}
+
+void EditorWindow::slotPrepareToLoad()
+{
+ // Disable actions as appropriate during loading
+ emit signalNoCurrentItem();
+ toggleActions(false);
+ slotUpdateItemInfo();
+}
+
+void EditorWindow::slotLoadingStarted(const TQString& /*filename*/)
+{
+ setCursor( KCursor::waitCursor() );
+
+ m_nameLabel->progressBarMode(StatusProgressBar::ProgressBarMode, i18n("Loading: "));
+}
+
+void EditorWindow::slotLoadingFinished(const TQString& filename, bool success)
+{
+ m_nameLabel->progressBarMode(StatusProgressBar::TextMode);
+ slotUpdateItemInfo();
+
+ // Enable actions as appropriate after loading
+ // No need to re-enable image properties sidebar here, it's will be done
+ // automatically by a signal from canvas
+ toggleActions(success);
+ unsetCursor();
+
+ // Note: in showfoto, we using a null filename to clear canvas.
+ if (!success && filename != TQString())
+ {
+ TQFileInfo fi(filename);
+ TQString message = i18n("Failed to load image \"%1\"").arg(fi.fileName());
+ KMessageBox::error(this, message);
+ DWarning() << "Failed to load image " << fi.fileName() << endl;
+ }
+}
+
+void EditorWindow::slotNameLabelCancelButtonPressed()
+{
+ // If we saving an image...
+ if (m_savingContext->savingState != SavingContextContainer::SavingStateNone)
+ {
+ m_savingContext->abortingSaving = true;
+ m_canvas->abortSaving();
+ }
+
+ // If we preparing SlideShow...
+ m_cancelSlideShow = true;
+}
+
+void EditorWindow::slotSave()
+{
+ if (m_canvas->isReadOnly())
+ saveAs();
+ else if (promptForOverWrite())
+ save();
+}
+
+void EditorWindow::slotSavingStarted(const TQString& /*filename*/)
+{
+ setCursor( KCursor::waitCursor() );
+
+ // Disable actions as appropriate during saving
+ emit signalNoCurrentItem();
+ toggleActions(false);
+
+ m_nameLabel->progressBarMode(StatusProgressBar::CancelProgressBarMode, i18n("Saving: "));
+}
+
+void EditorWindow::slotSavingFinished(const TQString& filename, bool success)
+{
+ if (m_savingContext->savingState == SavingContextContainer::SavingStateSave)
+ {
+ // from save()
+ m_savingContext->savingState = SavingContextContainer::SavingStateNone;
+
+ if (!success)
+ {
+ if (!m_savingContext->abortingSaving)
+ {
+ KMessageBox::error(this, i18n("Failed to save file\n\"%1\"\nto\n\"%2\".")
+ .arg(m_savingContext->destinationURL.filename())
+ .arg(m_savingContext->destinationURL.path()));
+ }
+ finishSaving(false);
+ return;
+ }
+
+ DDebug() << "renaming to " << m_savingContext->destinationURL.path() << endl;
+
+ if (!moveFile())
+ {
+ finishSaving(false);
+ return;
+ }
+
+ m_canvas->setUndoHistoryOrigin();
+
+ // remove image from cache since it has changed
+ LoadingCacheInterface::cleanFromCache(m_savingContext->destinationURL.path());
+ // this won't be in the cache, but does not hurt to do it anyway
+ LoadingCacheInterface::cleanFromCache(filename);
+
+ // restore state of disabled actions. saveIsComplete can start any other task
+ // (loading!) which might itself in turn change states
+ finishSaving(true);
+
+ saveIsComplete();
+
+ // Take all actions necessary to update information and re-enable sidebar
+ slotChanged();
+ }
+ else if (m_savingContext->savingState == SavingContextContainer::SavingStateSaveAs)
+ {
+ m_savingContext->savingState = SavingContextContainer::SavingStateNone;
+
+ // from saveAs()
+ if (!success)
+ {
+ if (!m_savingContext->abortingSaving)
+ {
+ KMessageBox::error(this, i18n("Failed to save file\n\"%1\"\nto\n\"%2\".")
+ .arg(m_savingContext->destinationURL.filename())
+ .arg(m_savingContext->destinationURL.path()));
+ }
+ finishSaving(false);
+ return;
+ }
+
+ // Only try to write exif if both src and destination are jpeg files
+
+ DDebug() << "renaming to " << m_savingContext->destinationURL.path() << endl;
+
+ if (!moveFile())
+ {
+ finishSaving(false);
+ return;
+ }
+
+ m_canvas->setUndoHistoryOrigin();
+
+ LoadingCacheInterface::cleanFromCache(m_savingContext->destinationURL.path());
+ LoadingCacheInterface::cleanFromCache(filename);
+
+ finishSaving(true);
+ saveAsIsComplete();
+
+ // Take all actions necessary to update information and re-enable sidebar
+ slotChanged();
+ }
+}
+
+void EditorWindow::finishSaving(bool success)
+{
+ m_savingContext->synchronousSavingResult = success;
+
+ if (m_savingContext->saveTempFile)
+ {
+ delete m_savingContext->saveTempFile;
+ m_savingContext->saveTempFile = 0;
+ }
+
+ // Exit of internal TQt event loop to unlock promptUserSave() method.
+ if (m_savingContext->synchronizingState == SavingContextContainer::SynchronousSaving)
+ tqApp->exit_loop();
+
+ // Enable actions as appropriate after saving
+ toggleActions(true);
+ unsetCursor();
+
+ m_nameLabel->progressBarMode(StatusProgressBar::TextMode);
+
+ // On error, continue using current image
+ if (!success)
+ {
+ m_canvas->switchToLastSaved(m_savingContext->srcURL.path());
+ }
+}
+
+void EditorWindow::startingSave(const KURL& url)
+{
+ // avoid any reentrancy. Should be impossible anyway since actions will be disabled.
+ if (m_savingContext->savingState != SavingContextContainer::SavingStateNone)
+ return;
+
+ if (!checkPermissions(url))
+ return;
+
+ m_savingContext->srcURL = url;
+ m_savingContext->destinationURL = m_savingContext->srcURL;
+ m_savingContext->destinationExisted = true;
+ m_savingContext->originalFormat = m_canvas->currentImageFileFormat();
+ m_savingContext->format = m_savingContext->originalFormat;
+ m_savingContext->abortingSaving = false;
+ m_savingContext->savingState = SavingContextContainer::SavingStateSave;
+ // use magic file extension which tells the digikamalbums ioslave to ignore the file
+ m_savingContext->saveTempFile = new KTempFile(m_savingContext->srcURL.directory(false),
+ ".digikamtempfile.tmp");
+ m_savingContext->saveTempFile->setAutoDelete(true);
+
+ m_canvas->saveAs(m_savingContext->saveTempFile->name(), m_IOFileSettings,
+ m_setExifOrientationTag && (m_rotatedOrFlipped || m_canvas->exifRotated()));
+}
+
+bool EditorWindow::startingSaveAs(const KURL& url)
+{
+ if (m_savingContext->savingState != SavingContextContainer::SavingStateNone)
+ return false;
+
+ TQString mimetypes = KImageIO::mimeTypes(KImageIO::Writing).join(" ");
+ mimetypes.append(" image/tiff");
+ DDebug () << "mimetypes=" << mimetypes << endl;
+
+ m_savingContext->srcURL = url;
+
+ FileSaveOptionsBox *options = new FileSaveOptionsBox();
+ KFileDialog imageFileSaveDialog(m_savingContext->srcURL.isLocalFile() ?
+ m_savingContext->srcURL.directory() : TQDir::homeDirPath(),
+ TQString(),
+ this,
+ "imageFileSaveDialog",
+ false,
+ options);
+
+ connect(&imageFileSaveDialog, TQ_SIGNAL(filterChanged(const TQString &)),
+ options, TQ_SLOT(slotImageFileFormatChanged(const TQString &)));
+
+ connect(&imageFileSaveDialog, TQ_SIGNAL(fileSelected(const TQString &)),
+ options, TQ_SLOT(slotImageFileSelected(const TQString &)));
+
+ ImageDialogPreview *preview = new ImageDialogPreview(&imageFileSaveDialog);
+ imageFileSaveDialog.setPreviewWidget(preview);
+ imageFileSaveDialog.setOperationMode(KFileDialog::Saving);
+ imageFileSaveDialog.setMode(KFile::File);
+ imageFileSaveDialog.setCaption(i18n("New Image File Name"));
+ imageFileSaveDialog.setFilter(mimetypes);
+
+ TQFileInfo info(m_savingContext->srcURL.fileName());
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ TQString ext = config->readEntry("LastSavedImageTypeMime", "png");
+ TQString fileName = info.baseName(false) + TQString(".") + ext;
+ imageFileSaveDialog.setSelection(fileName);
+
+ // Start dialog and check if canceled.
+ if ( imageFileSaveDialog.exec() != KFileDialog::Accepted )
+ return false;
+
+ // Update file save settings in editor instance.
+ options->applySettings();
+ applyStandardSettings();
+
+ KURL newURL = imageFileSaveDialog.selectedURL();
+
+ // Check if target image format have been selected from Combo List of SaveAs dialog.
+ m_savingContext->format = KImageIO::typeForMime(imageFileSaveDialog.currentMimeFilter());
+
+ if ( m_savingContext->format.isEmpty() )
+ {
+ // Else, check if target image format have been add to target image file name using extension.
+
+ TQFileInfo fi(newURL.path());
+ m_savingContext->format = fi.extension(false);
+
+ if ( m_savingContext->format.isEmpty() )
+ {
+ // If format is empty then file format is same as that of the original file.
+ m_savingContext->format = TQImageIO::imageFormat(m_savingContext->srcURL.path());
+ }
+ else
+ {
+ // Else, check if format from file name extension is include on file mime type list.
+
+ TQString imgExtPattern;
+ TQStringList imgExtList = TQStringList::split(" ", mimetypes);
+ for (TQStringList::ConstIterator it = imgExtList.begin() ; it != imgExtList.end() ; ++it)
+ {
+ imgExtPattern.append (KImageIO::typeForMime(*it).upper());
+ imgExtPattern.append (" ");
+ }
+ imgExtPattern.append (" TIF TIFF");
+ if ( imgExtPattern.contains("JPEG") )
+ {
+ imgExtPattern.append (" JPG");
+ imgExtPattern.append (" JPE");
+ }
+
+ if ( !imgExtPattern.contains( m_savingContext->format.upper() ) )
+ {
+ KMessageBox::error(this, i18n("Target image file format \"%1\" unsupported.")
+ .arg(m_savingContext->format));
+ DWarning() << k_funcinfo << "target image file format " << m_savingContext->format << " unsupported!" << endl;
+ return false;
+ }
+ }
+ }
+
+ if (!newURL.isValid())
+ {
+ KMessageBox::error(this, i18n("Failed to save file\n\"%1\" to\n\"%2\".")
+ .arg(newURL.filename())
+ .arg(newURL.path().section('/', -2, -2)));
+ DWarning() << k_funcinfo << "target URL is not valid !" << endl;
+ return false;
+ }
+
+ config->writeEntry("LastSavedImageTypeMime", m_savingContext->format);
+ config->sync();
+
+ // if new and original url are equal use slotSave() ------------------------------
+
+ KURL currURL(m_savingContext->srcURL);
+ currURL.cleanPath();
+ newURL.cleanPath();
+
+ if (currURL.equals(newURL))
+ {
+ slotSave();
+ return false;
+ }
+
+ // Check for overwrite ----------------------------------------------------------
+
+ TQFileInfo fi(newURL.path());
+ m_savingContext->destinationExisted = fi.exists();
+ if ( m_savingContext->destinationExisted )
+ {
+ int result =
+
+ KMessageBox::warningYesNo( this, i18n("A file named \"%1\" already "
+ "exists. Are you sure you want "
+ "to overwrite it?")
+ .arg(newURL.filename()),
+ i18n("Overwrite File?"),
+ i18n("Overwrite"),
+ KStdGuiItem::cancel() );
+
+ if (result != KMessageBox::Yes)
+ return false;
+
+ // There will be two message boxes if the file is not writable.
+ // This may be controversial, and it may be changed, but it was a deliberate decision.
+ if (!checkPermissions(newURL))
+ return false;
+ }
+
+ // Now do the actual saving -----------------------------------------------------
+
+ // use magic file extension which tells the digikamalbums ioslave to ignore the file
+ m_savingContext->saveTempFile = new KTempFile(newURL.directory(false), ".digikamtempfile.tmp");
+ m_savingContext->destinationURL = newURL;
+ m_savingContext->originalFormat = m_canvas->currentImageFileFormat();
+ m_savingContext->savingState = SavingContextContainer::SavingStateSaveAs;
+ m_savingContext->saveTempFile->setAutoDelete(true);
+ m_savingContext->abortingSaving = false;
+
+ m_canvas->saveAs(m_savingContext->saveTempFile->name(), m_IOFileSettings,
+ m_setExifOrientationTag && (m_rotatedOrFlipped || m_canvas->exifRotated()),
+ m_savingContext->format.lower());
+
+ return true;
+}
+
+bool EditorWindow::checkPermissions(const KURL& url)
+{
+ //TODO: Check that the permissions can actually be changed
+ // if write permissions are not available.
+
+ TQFileInfo fi(url.path());
+
+ if (fi.exists() && !fi.isWritable())
+ {
+ int result =
+
+ KMessageBox::warningYesNo( this, i18n("You do not have write permissions "
+ "for the file named \"%1\". "
+ "Are you sure you want "
+ "to overwrite it?")
+ .arg(url.filename()),
+ i18n("Overwrite File?"),
+ i18n("Overwrite"),
+ KStdGuiItem::cancel() );
+
+ if (result != KMessageBox::Yes)
+ return false;
+ }
+
+ return true;
+}
+
+bool EditorWindow::moveFile()
+{
+ TQCString dstFileName = TQFile::encodeName(m_savingContext->destinationURL.path());
+
+ // Store old permissions:
+ // Just get the current umask.
+ mode_t curr_umask = umask(S_IREAD | S_IWRITE);
+ // Restore the umask.
+ umask(curr_umask);
+
+ // For new files respect the umask setting.
+ mode_t filePermissions = (S_IREAD | S_IWRITE | S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP) & ~curr_umask;
+
+ // For existing files, use the mode of the original file.
+ if (m_savingContext->destinationExisted)
+ {
+ struct stat stbuf;
+ if (::stat(dstFileName, &stbuf) == 0)
+ {
+ filePermissions = stbuf.st_mode;
+ }
+ }
+
+ // rename tmp file to dest
+ if (::rename(TQFile::encodeName(m_savingContext->saveTempFile->name()), dstFileName) != 0)
+ {
+ KMessageBox::error(this, i18n("Failed to overwrite original file"),
+ i18n("Error Saving File"));
+ return false;
+ }
+
+ // restore permissions
+ if (::chmod(dstFileName, filePermissions) != 0)
+ {
+ DWarning() << "Failed to restore file permissions for file " << dstFileName << endl;
+ }
+
+ return true;
+}
+
+void EditorWindow::slotToggleColorManagedView()
+{
+ d->cmViewIndicator->blockSignals(true);
+ d->viewCMViewAction->blockSignals(true);
+ bool cmv = false;
+ if (d->ICCSettings->enableCMSetting)
+ {
+ cmv = !d->ICCSettings->managedViewSetting;
+ d->ICCSettings->managedViewSetting = cmv;
+ m_canvas->setICCSettings(d->ICCSettings);
+
+ // Save Color Managed View setting in config file. For performance
+ // reason, no need to flush file, it cached in memory and will be flushed
+ // to disk at end of session.
+ TDEConfig* config = kapp->config();
+ config->setGroup("Color Management");
+ config->writeEntry("ManagedView", cmv);
+ }
+
+ d->cmViewIndicator->setOn(cmv);
+ d->viewCMViewAction->setChecked(cmv);
+ setColorManagedViewIndicatorToolTip(d->ICCSettings->enableCMSetting, cmv);
+ d->cmViewIndicator->blockSignals(false);
+ d->viewCMViewAction->blockSignals(false);
+}
+
+void EditorWindow::setColorManagedViewIndicatorToolTip(bool available, bool cmv)
+{
+ TQToolTip::remove(d->cmViewIndicator);
+ TQString tooltip;
+ if (available)
+ {
+ if (cmv)
+ tooltip = i18n("Color Managed View is enabled");
+ else
+ tooltip = i18n("Color Managed View is disabled");
+ }
+ else
+ {
+ tooltip = i18n("Color Management is not configured, so the Color Managed View is not available");
+ }
+ TQToolTip::add(d->cmViewIndicator, tooltip);
+}
+
+void EditorWindow::slotToggleUnderExposureIndicator()
+{
+ d->underExposureIndicator->blockSignals(true);
+ d->viewUnderExpoAction->blockSignals(true);
+ bool uei = !d->exposureSettings->underExposureIndicator;
+ d->underExposureIndicator->setOn(uei);
+ d->viewUnderExpoAction->setChecked(uei);
+ d->exposureSettings->underExposureIndicator = uei;
+ m_canvas->setExposureSettings(d->exposureSettings);
+ setUnderExposureToolTip(uei);
+ d->underExposureIndicator->blockSignals(false);
+ d->viewUnderExpoAction->blockSignals(false);
+}
+
+void EditorWindow::setUnderExposureToolTip(bool uei)
+{
+ TQToolTip::remove(d->underExposureIndicator);
+ TQToolTip::add(d->underExposureIndicator,
+ uei ? i18n("Under-Exposure indicator is enabled")
+ : i18n("Under-Exposure indicator is disabled"));
+}
+
+void EditorWindow::slotToggleOverExposureIndicator()
+{
+ d->overExposureIndicator->blockSignals(true);
+ d->viewOverExpoAction->blockSignals(true);
+ bool oei = !d->exposureSettings->overExposureIndicator;
+ d->overExposureIndicator->setOn(oei);
+ d->viewOverExpoAction->setChecked(oei);
+ d->exposureSettings->overExposureIndicator = oei;
+ m_canvas->setExposureSettings(d->exposureSettings);
+ setOverExposureToolTip(oei);
+ d->overExposureIndicator->blockSignals(false);
+ d->viewOverExpoAction->blockSignals(false);
+}
+
+void EditorWindow::setOverExposureToolTip(bool oei)
+{
+ TQToolTip::remove(d->overExposureIndicator);
+ TQToolTip::add(d->overExposureIndicator,
+ oei ? i18n("Over-Exposure indicator is enabled")
+ : i18n("Over-Exposure indicator is disabled"));
+}
+
+void EditorWindow::slotDonateMoney()
+{
+ TDEApplication::kApplication()->invokeBrowser("http://www.digikam.org/?q=donation");
+}
+
+void EditorWindow::slotContribute()
+{
+ TDEApplication::kApplication()->invokeBrowser("http://www.digikam.org/?q=contrib");
+}
+
+void EditorWindow::slotToggleSlideShow()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ bool startWithCurrent = config->readBoolEntry("SlideShowStartCurrent", false);
+
+ SlideShowSettings settings;
+ settings.delay = config->readNumEntry("SlideShowDelay", 5) * 1000;
+ settings.printName = config->readBoolEntry("SlideShowPrintName", true);
+ settings.printDate = config->readBoolEntry("SlideShowPrintDate", false);
+ settings.printApertureFocal = config->readBoolEntry("SlideShowPrintApertureFocal", false);
+ settings.printExpoSensitivity = config->readBoolEntry("SlideShowPrintExpoSensitivity", false);
+ settings.printMakeModel = config->readBoolEntry("SlideShowPrintMakeModel", false);
+ settings.printComment = config->readBoolEntry("SlideShowPrintComment", false);
+ settings.loop = config->readBoolEntry("SlideShowLoop", false);
+ slideShow(startWithCurrent, settings);
+}
+
+void EditorWindow::slotSelectionChanged(const TQRect& sel)
+{
+ d->selectLabel->setText(TQString("(%1, %2) (%3 x %4)").arg(sel.x()).arg(sel.y())
+ .arg(sel.width()).arg(sel.height()));
+}
+
+void EditorWindow::slotRawCameraList()
+{
+ RawCameraDlg dlg(this);
+ dlg.exec();
+}
+
+void EditorWindow::slotThemeChanged()
+{
+ TQStringList themes(ThemeEngine::instance()->themeNames());
+ int index = themes.findIndex(ThemeEngine::instance()->getCurrentThemeName());
+ if (index == -1)
+ index = themes.findIndex(i18n("Default"));
+
+ m_themeMenuAction->setCurrentItem(index);
+
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+
+ if (!config->readBoolEntry("UseThemeBackgroundColor", true))
+ m_bgColor = config->readColorEntry("BackgroundColor", &TQt::black);
+ else
+ m_bgColor = ThemeEngine::instance()->baseColor();
+
+ m_canvas->setBackgroundColor(m_bgColor);
+}
+
+void EditorWindow::slotChangeTheme(const TQString& theme)
+{
+ ThemeEngine::instance()->slotChangeTheme(theme);
+}
+
+void EditorWindow::setToolStartProgress(const TQString& toolName)
+{
+ m_nameLabel->setProgressValue(0);
+ m_nameLabel->progressBarMode(StatusProgressBar::CancelProgressBarMode, TQString("%1: ").arg(toolName));
+}
+
+void EditorWindow::setToolProgress(int progress)
+{
+ m_nameLabel->setProgressValue(progress);
+}
+
+void EditorWindow::setToolStopProgress()
+{
+ m_nameLabel->setProgressValue(0);
+ m_nameLabel->progressBarMode(StatusProgressBar::TextMode);
+ slotUpdateItemInfo();
+}
+
+
+void EditorWindow::slotShowMenuBar()
+{
+ if (menuBar()->isVisible())
+ menuBar()->hide();
+ else
+ menuBar()->show();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/editor/editorwindow.h b/src/utilities/imageeditor/editor/editorwindow.h
new file mode 100644
index 00000000..cbb06221
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editorwindow.h
@@ -0,0 +1,263 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : main image editor GUI implementation
+ *
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EDITORWINDOW_H
+#define EDITORWINDOW_H
+
+// TQt includes.
+
+#include <tqcolor.h>
+#include <tqstring.h>
+#include <tqrect.h>
+
+// KDE includes.
+
+#include <tdemainwindow.h>
+#include <kurl.h>
+
+// Local includes.
+
+#include "sidebar.h"
+#include "digikam_export.h"
+
+class TQSplitter;
+class TQPopupMenu;
+class TQLabel;
+
+class TDEToolBarPopupAction;
+class TDEToggleAction;
+class TDEAction;
+class TDESelectAction;
+
+namespace Digikam
+{
+
+class Sidebar;
+class DPopupMenu;
+class Canvas;
+class ImagePluginLoader;
+class IOFileSettingsContainer;
+class SavingContextContainer;
+class StatusProgressBar;
+class SlideShowSettings;
+class EditorStackView;
+class EditorWindowPriv;
+
+class DIGIKAM_EXPORT EditorWindow : public TDEMainWindow
+{
+ TQ_OBJECT
+
+
+public:
+
+ EditorWindow(const char *name);
+ ~EditorWindow();
+
+ virtual void applySettings(){};
+ virtual bool setup(bool iccSetupPage=false)=0;
+
+signals:
+
+ void signalSelectionChanged( const TQRect & );
+ void signalNoCurrentItem();
+
+protected:
+
+ bool m_cancelSlideShow;
+ bool m_fullScreen;
+ bool m_rotatedOrFlipped;
+ bool m_setExifOrientationTag;
+
+ TQLabel *m_resLabel;
+
+ TQColor m_bgColor;
+
+ TQSplitter *m_splitter;
+
+ TDEAction *m_saveAction;
+ TDEAction *m_saveAsAction;
+ TDEAction *m_revertAction;
+ TDEAction *m_fileDeleteAction;
+ TDEAction *m_forwardAction;
+ TDEAction *m_backwardAction;
+ TDEAction *m_firstAction;
+ TDEAction *m_lastAction;
+
+ TDEToggleAction *m_fullScreenAction;
+
+ TDESelectAction *m_themeMenuAction;
+
+ TDEToolBarPopupAction *m_undoAction;
+ TDEToolBarPopupAction *m_redoAction;
+
+ DPopupMenu *m_contextMenu;
+ EditorStackView *m_stackView;
+ Canvas *m_canvas;
+ ImagePluginLoader *m_imagePluginLoader;
+ StatusProgressBar *m_nameLabel;
+ IOFileSettingsContainer *m_IOFileSettings;
+ SavingContextContainer *m_savingContext;
+
+protected:
+
+ void saveStandardSettings();
+ void readStandardSettings();
+ void applyStandardSettings();
+
+ void setupStandardConnections();
+ void setupStandardActions();
+ void setupStandardAccelerators();
+ void setupStatusBar();
+ void setupContextMenu();
+ void toggleStandardActions(bool val);
+ void toggleZoomActions(bool val);
+
+ void printImage(KURL url);
+
+ void plugActionAccel(TDEAction* action);
+ void unplugActionAccel(TDEAction* action);
+
+ void unLoadImagePlugins();
+ void loadImagePlugins();
+
+ bool promptForOverWrite();
+ bool promptUserSave(const KURL& url);
+ bool waitForSavingToComplete();
+ void startingSave(const KURL& url);
+ bool startingSaveAs(const KURL& url);
+ bool checkPermissions(const KURL& url);
+ bool moveFile();
+
+ EditorStackView* editorStackView() const;
+
+ virtual void finishSaving(bool success);
+
+ virtual void readSettings() { readStandardSettings(); };
+ virtual void saveSettings() { saveStandardSettings(); };
+ virtual void toggleActions(bool val) { toggleStandardActions(val); };
+ virtual void toggleGUI2FullScreen() {};
+
+ virtual void slideShow(bool startWithCurrent, SlideShowSettings& settings)=0;
+
+ virtual void setupConnections()=0;
+ virtual void setupActions()=0;
+ virtual void setupUserArea()=0;
+ virtual bool saveAs()=0;
+ virtual bool save()=0;
+
+ virtual void saveIsComplete()=0;
+ virtual void saveAsIsComplete()=0;
+
+ virtual Sidebar *rightSideBar() const=0;
+
+protected slots:
+
+ void slotSave();
+ void slotSaveAs() { saveAs(); };
+
+ void slotEditKeys();
+ void slotResize();
+
+ void slotAboutToShowUndoMenu();
+ void slotAboutToShowRedoMenu();
+
+ void slotConfToolbars();
+ void slotNewToolbarConfig();
+
+ void slotToggleFullScreen();
+ void slotEscapePressed();
+
+ void slotSelected(bool);
+
+ void slotLoadingProgress(const TQString& filePath, float progress);
+ void slotSavingProgress(const TQString& filePath, float progress);
+
+ void slotNameLabelCancelButtonPressed();
+
+ void slotThemeChanged();
+
+ virtual void slotLoadingStarted(const TQString& filename);
+ virtual void slotLoadingFinished(const TQString &filename, bool success);
+ virtual void slotSavingStarted(const TQString &filename);
+
+ virtual void slotSetup(){ setup(); };
+ virtual void slotChangeTheme(const TQString& theme);
+
+ virtual void slotFilePrint()=0;
+ virtual void slotDeleteCurrentItem()=0;
+ virtual void slotBackward()=0;
+ virtual void slotForward()=0;
+ virtual void slotFirst()=0;
+ virtual void slotLast()=0;
+ virtual void slotUpdateItemInfo()=0;
+ virtual void slotChanged()=0;
+ virtual void slotContextMenu()=0;
+ virtual void slotRevert()=0;
+
+private slots:
+
+ void slotToggleUnderExposureIndicator();
+ void slotToggleOverExposureIndicator();
+ void slotToggleColorManagedView();
+ void slotRotatedOrFlipped();
+ void slotSavingFinished(const TQString &filename, bool success);
+ void slotDonateMoney();
+ void slotContribute();
+ void slotToggleSlideShow();
+ void slotZoomTo100Percents();
+ void slotZoomSelected();
+ void slotZoomTextChanged(const TQString &);
+ void slotZoomChanged(bool isMax, bool isMin, double zoom);
+ void slotSelectionChanged(const TQRect& sel);
+ void slotToggleFitToWindow();
+ void slotToggleOffFitToWindow();
+ void slotFitToSelect();
+ void slotIncreaseZoom();
+ void slotDecreaseZoom();
+ void slotRawCameraList();
+ void slotPrepareToLoad();
+ void slotShowMenuBar();
+
+private:
+
+ void enter_loop();
+ void hideToolBars();
+ void showToolBars();
+ void setColorManagedViewIndicatorToolTip(bool available, bool cmv);
+ void setUnderExposureToolTip(bool uei);
+ void setOverExposureToolTip(bool oei);
+
+ void setToolStartProgress(const TQString& toolName);
+ void setToolProgress(int progress);
+ void setToolStopProgress();
+
+private:
+
+ EditorWindowPriv *d;
+
+ friend class EditorToolIface;
+};
+
+} // namespace Digikam
+
+#endif /* EDITORWINDOW_H */
diff --git a/src/utilities/imageeditor/editor/editorwindowprivate.h b/src/utilities/imageeditor/editor/editorwindowprivate.h
new file mode 100644
index 00000000..df07d0bd
--- /dev/null
+++ b/src/utilities/imageeditor/editor/editorwindowprivate.h
@@ -0,0 +1,143 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : main image editor GUI implementation
+ * private data.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef EDITORWINDOWPRIVATE_H
+#define EDITORWINDOWPRIVATE_H
+
+class TQToolButton;
+class TQLabel;
+
+class KComboBox;
+class TDEAction;
+class TDEToggleAction;
+class KWidgetAction;
+class TDESelectAction;
+class TDEActionMenu;
+class TDEAccel;
+
+namespace Digikam
+{
+
+class EditorToolIface;
+class ExposureSettingsContainer;
+class ICCSettingsContainer;
+
+class EditorWindowPriv
+{
+
+public:
+
+ EditorWindowPriv()
+ {
+ removeFullScreenButton = false;
+ fullScreenHideToolBar = false;
+ selectLabel = 0;
+ donateMoneyAction = 0;
+ accelerators = 0;
+ viewCMViewAction = 0;
+ filePrintAction = 0;
+ copyAction = 0;
+ resizeAction = 0;
+ cropAction = 0;
+ rotateLeftAction = 0;
+ rotateRightAction = 0;
+ flipHorizAction = 0;
+ flipVertAction = 0;
+ ICCSettings = 0;
+ exposureSettings = 0;
+ underExposureIndicator = 0;
+ overExposureIndicator = 0;
+ cmViewIndicator = 0;
+ viewUnderExpoAction = 0;
+ viewOverExpoAction = 0;
+ slideShowAction = 0;
+ zoomFitToWindowAction = 0;
+ zoomFitToSelectAction = 0;
+ zoomPlusAction = 0;
+ zoomMinusAction = 0;
+ zoomTo100percents = 0;
+ zoomCombo = 0;
+ zoomComboAction = 0;
+ selectAllAction = 0;
+ selectNoneAction = 0;
+ rawCameraListAction = 0;
+ contributeAction = 0;
+ toolIface = 0;
+ showMenuBarAction = 0;
+ }
+
+ ~EditorWindowPriv()
+ {
+ }
+
+ bool removeFullScreenButton;
+ bool fullScreenHideToolBar;
+
+ TQLabel *selectLabel;
+
+ TQToolButton *cmViewIndicator;
+ TQToolButton *underExposureIndicator;
+ TQToolButton *overExposureIndicator;
+
+ TDEAction *rawCameraListAction;
+ TDEAction *donateMoneyAction;
+ TDEAction *contributeAction;
+ TDEAction *filePrintAction;
+ TDEAction *copyAction;
+ TDEAction *resizeAction;
+ TDEAction *cropAction;
+ TDEAction *zoomPlusAction;
+ TDEAction *zoomMinusAction;
+ TDEAction *zoomTo100percents;
+ TDEAction *zoomFitToSelectAction;
+ TDEAction *rotateLeftAction;
+ TDEAction *rotateRightAction;
+ TDEAction *flipHorizAction;
+ TDEAction *flipVertAction;
+ TDEAction *slideShowAction;
+ TDEAction *selectAllAction;
+ TDEAction *selectNoneAction;
+
+ TDEToggleAction *zoomFitToWindowAction;
+ TDEToggleAction *viewCMViewAction;
+ TDEToggleAction *viewUnderExpoAction;
+ TDEToggleAction *viewOverExpoAction;
+ TDEToggleAction *showMenuBarAction;
+
+ KWidgetAction *zoomComboAction;
+
+ KComboBox *zoomCombo;
+
+ TDEAccel *accelerators;
+
+ ICCSettingsContainer *ICCSettings;
+
+ ExposureSettingsContainer *exposureSettings;
+
+ EditorToolIface *toolIface;
+};
+
+} // NameSpace Digikam
+
+#endif /* EDITORWINDOWPRIVATE_H */
diff --git a/src/utilities/imageeditor/editor/imageiface.cpp b/src/utilities/imageeditor/editor/imageiface.cpp
new file mode 100644
index 00000000..56cb559c
--- /dev/null
+++ b/src/utilities/imageeditor/editor/imageiface.cpp
@@ -0,0 +1,444 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : image data interface for image plugins
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqsize.h>
+#include <tqpixmap.h>
+#include <tqbitmap.h>
+#include <tqpainter.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "exposurecontainer.h"
+#include "iccsettingscontainer.h"
+#include "icctransform.h"
+#include "dimginterface.h"
+#include "bcgmodifier.h"
+#include "dmetadata.h"
+#include "imageiface.h"
+
+namespace Digikam
+{
+
+class ImageIfacePriv
+{
+public:
+
+ ImageIfacePriv()
+ {
+ usePreviewSelection = false;
+ previewWidth = 0;
+ previewHeight = 0;
+ }
+
+ bool usePreviewSelection;
+
+ int originalWidth;
+ int originalHeight;
+ int originalBytesDepth;
+
+ int constrainWidth;
+ int constrainHeight;
+
+ int previewWidth;
+ int previewHeight;
+
+ TQPixmap qcheck;
+ TQPixmap qpix;
+ TQBitmap qmask;
+
+ DImg previewImage;
+ DImg targetPreviewImage;
+};
+
+ImageIface::ImageIface(int w, int h)
+{
+ d = new ImageIfacePriv;
+
+ d->constrainWidth = w;
+ d->constrainHeight = h;
+
+ d->originalWidth = DImgInterface::defaultInterface()->origWidth();
+ d->originalHeight = DImgInterface::defaultInterface()->origHeight();
+ d->originalBytesDepth = DImgInterface::defaultInterface()->bytesDepth();
+
+ d->qpix.setMask(d->qmask);
+ d->qcheck.resize(8, 8);
+
+ TQPainter p;
+ p.begin(&d->qcheck);
+ p.fillRect(0, 0, 4, 4, TQColor(144,144,144));
+ p.fillRect(4, 4, 4, 4, TQColor(144,144,144));
+ p.fillRect(0, 4, 4, 4, TQColor(100,100,100));
+ p.fillRect(4, 0, 4, 4, TQColor(100,100,100));
+ p.end();
+}
+
+ImageIface::~ImageIface()
+{
+ delete d;
+}
+
+void ImageIface::setPreviewType(bool useSelect)
+{
+ d->usePreviewSelection = useSelect;
+}
+
+bool ImageIface::previewType()
+{
+ return d->usePreviewSelection;
+}
+
+DColor ImageIface::getColorInfoFromOriginalImage(const TQPoint& point)
+{
+ if ( !DImgInterface::defaultInterface()->getImage() || point.x() > originalWidth() || point.y() > originalHeight() )
+ {
+ DWarning() << k_funcinfo << "Coordinate out of range or no image data available!" << endl;
+ return DColor();
+ }
+
+ return DImgInterface::defaultInterface()->getImg()->getPixelColor(point.x(), point.y());
+}
+
+DColor ImageIface::getColorInfoFromPreviewImage(const TQPoint& point)
+{
+ if ( d->previewImage.isNull() || point.x() > previewWidth() || point.y() > previewHeight() )
+ {
+ DWarning() << k_funcinfo << "Coordinate out of range or no image data available!" << endl;
+ return DColor();
+ }
+
+ return d->previewImage.getPixelColor(point.x(), point.y());
+}
+
+DColor ImageIface::getColorInfoFromTargetPreviewImage(const TQPoint& point)
+{
+ if ( d->targetPreviewImage.isNull() || point.x() > previewWidth() || point.y() > previewHeight() )
+ {
+ DWarning() << k_funcinfo << "Coordinate out of range or no image data available!" << endl;
+ return DColor();
+ }
+
+ return d->targetPreviewImage.getPixelColor(point.x(), point.y());
+}
+
+uchar* ImageIface::setPreviewImageSize(int w, int h) const
+{
+ d->previewImage.reset();
+ d->targetPreviewImage.reset();
+
+ d->constrainWidth = w;
+ d->constrainHeight = h;
+
+ return (getPreviewImage());
+}
+
+uchar* ImageIface::getPreviewImage() const
+{
+ if (d->previewImage.isNull())
+ {
+ DImg *im = 0;
+
+ if (!d->usePreviewSelection)
+ {
+ im = DImgInterface::defaultInterface()->getImg();
+ if (!im || im->isNull())
+ return 0;
+ }
+ else
+ {
+ int x, y, w, h;
+ bool s = DImgInterface::defaultInterface()->sixteenBit();
+ bool a = DImgInterface::defaultInterface()->hasAlpha();
+ uchar *data = DImgInterface::defaultInterface()->getImageSelection();
+ DImgInterface::defaultInterface()->getSelectedArea(x, y, w, h);
+ im = new DImg(w, h, s, a, data, true);
+ delete [] data;
+
+ if (!im)
+ return 0;
+
+ if (im->isNull())
+ {
+ delete im;
+ return 0;
+ }
+ }
+
+ TQSize sz(im->width(), im->height());
+ sz.scale(d->constrainWidth, d->constrainHeight, TQSize::ScaleMin);
+
+ d->previewImage = im->smoothScale(sz.width(), sz.height());
+ d->previewWidth = d->previewImage.width();
+ d->previewHeight = d->previewImage.height();
+
+ // only create another copy if needed, in putPreviewImage
+ d->targetPreviewImage = d->previewImage;
+
+ d->qmask.resize(d->previewWidth, d->previewHeight);
+ d->qpix.resize(d->previewWidth, d->previewHeight);
+
+ if (d->usePreviewSelection)
+ delete im;
+ }
+
+ DImg previewData = d->previewImage.copyImageData();
+ return previewData.stripImageData();
+}
+
+uchar* ImageIface::getOriginalImage() const
+{
+ DImg *im = DImgInterface::defaultInterface()->getImg();
+
+ if (!im || im->isNull())
+ return 0;
+
+ DImg origData = im->copyImageData();
+ return origData.stripImageData();
+}
+
+DImg* ImageIface::getOriginalImg() const
+{
+ return DImgInterface::defaultInterface()->getImg();
+}
+
+uchar* ImageIface::getImageSelection() const
+{
+ return DImgInterface::defaultInterface()->getImageSelection();
+}
+
+void ImageIface::putPreviewImage(uchar* data)
+{
+ if (!data)
+ return;
+
+ if (d->targetPreviewImage == d->previewImage)
+ {
+ d->targetPreviewImage = DImg(d->previewImage.width(), d->previewImage.height(),
+ d->previewImage.sixteenBit(), d->previewImage.hasAlpha(), data);
+ d->targetPreviewImage.setICCProfil( d->previewImage.getICCProfil() );
+ }
+ else
+ {
+ d->targetPreviewImage.putImageData(data);
+ }
+}
+
+void ImageIface::putOriginalImage(const TQString &caller, uchar* data, int w, int h)
+{
+ if (!data)
+ return;
+
+ DImgInterface::defaultInterface()->putImage(caller, data, w, h);
+}
+
+void ImageIface::setEmbeddedICCToOriginalImage(const TQString& profilePath)
+{
+ DImgInterface::defaultInterface()->setEmbeddedICCToOriginalImage( profilePath );
+}
+
+void ImageIface::putImageSelection(const TQString &caller, uchar* data)
+{
+ if (!data)
+ return;
+
+ DImgInterface::defaultInterface()->putImageSelection(caller, data);
+}
+
+int ImageIface::previewWidth()
+{
+ return d->previewWidth;
+}
+
+int ImageIface::previewHeight()
+{
+ return d->previewHeight;
+}
+
+bool ImageIface::previewSixteenBit()
+{
+ return originalSixteenBit();
+}
+
+bool ImageIface::previewHasAlpha()
+{
+ return originalHasAlpha();
+}
+
+int ImageIface::originalWidth()
+{
+ return DImgInterface::defaultInterface()->origWidth();
+}
+
+int ImageIface::originalHeight()
+{
+ return DImgInterface::defaultInterface()->origHeight();
+}
+
+bool ImageIface::originalSixteenBit()
+{
+ return DImgInterface::defaultInterface()->sixteenBit();
+}
+
+bool ImageIface::originalHasAlpha()
+{
+ return DImgInterface::defaultInterface()->hasAlpha();
+}
+
+int ImageIface::selectedWidth()
+{
+ int x, y, w, h;
+ DImgInterface::defaultInterface()->getSelectedArea(x, y, w, h);
+ return w;
+}
+
+int ImageIface::selectedHeight()
+{
+ int x, y, w, h;
+ DImgInterface::defaultInterface()->getSelectedArea(x, y, w, h);
+ return h;
+}
+
+int ImageIface::selectedXOrg()
+{
+ int x, y, w, h;
+ DImgInterface::defaultInterface()->getSelectedArea(x, y, w, h);
+ return x;
+}
+
+int ImageIface::selectedYOrg()
+{
+ int x, y, w, h;
+ DImgInterface::defaultInterface()->getSelectedArea(x, y, w, h);
+ return y;
+}
+
+void ImageIface::setPreviewBCG(double brightness, double contrast, double gamma)
+{
+ DImg preview = d->targetPreviewImage.copyImageData();
+ BCGModifier cmod;
+ cmod.setGamma(gamma);
+ cmod.setBrightness(brightness);
+ cmod.setContrast(contrast);
+ cmod.applyBCG(preview);
+ putPreviewImage(preview.bits());
+}
+
+void ImageIface::setOriginalBCG(double brightness, double contrast, double gamma)
+{
+ DImgInterface::defaultInterface()->setBCG(brightness, contrast, gamma);
+}
+
+void ImageIface::convertOriginalColorDepth(int depth)
+{
+ DImgInterface::defaultInterface()->convertDepth(depth);
+}
+
+TQPixmap ImageIface::convertToPixmap(DImg& img)
+{
+ return DImgInterface::defaultInterface()->convertToPixmap(img);
+}
+
+TQByteArray ImageIface::getEmbeddedICCFromOriginalImage()
+{
+ return DImgInterface::defaultInterface()->getEmbeddedICC();
+}
+
+TQByteArray ImageIface::getExifFromOriginalImage()
+{
+ return DImgInterface::defaultInterface()->getExif();
+}
+
+TQByteArray ImageIface::getIptcFromOriginalImage()
+{
+ return DImgInterface::defaultInterface()->getIptc();
+}
+
+PhotoInfoContainer ImageIface::getPhotographInformations() const
+{
+ DMetadata meta;
+ meta.setExif(DImgInterface::defaultInterface()->getExif());
+ meta.setIptc(DImgInterface::defaultInterface()->getIptc());
+ return meta.getPhotographInformations();
+}
+
+void ImageIface::paint(TQPaintDevice* device, int x, int y, int w, int h,
+ bool underExposure, bool overExposure)
+{
+ if ( !d->targetPreviewImage.isNull() )
+ {
+ if (d->targetPreviewImage.hasAlpha())
+ {
+ TQPainter p(&d->qpix);
+ p.drawTiledPixmap(0, 0, d->qpix.width(), d->qpix.height(), d->qcheck);
+ p.end();
+ }
+
+ TQPixmap pixImage;
+ ICCSettingsContainer *iccSettings = DImgInterface::defaultInterface()->getICCSettings();
+
+ if (iccSettings)
+ {
+ IccTransform monitorICCtrans;
+ monitorICCtrans.setProfiles(iccSettings->workspaceSetting, iccSettings->monitorSetting);
+
+ if (iccSettings->enableCMSetting && iccSettings->managedViewSetting)
+ {
+ pixImage = d->targetPreviewImage.convertToPixmap(&monitorICCtrans);
+ }
+ else
+ {
+ pixImage = d->targetPreviewImage.convertToPixmap();
+ }
+ }
+ else
+ {
+ pixImage = d->targetPreviewImage.convertToPixmap();
+ }
+
+ bitBlt(&d->qpix, 0, 0, static_cast<TQPaintDevice*>(&pixImage), 0, 0, w, h, TQt::CopyROP, false);
+
+ // Show the Over/Under exposure pixels indicators
+
+ if (underExposure || overExposure)
+ {
+ ExposureSettingsContainer expoSettings;
+ expoSettings.underExposureIndicator = underExposure;
+ expoSettings.overExposureIndicator = overExposure;
+ expoSettings.underExposureColor = DImgInterface::defaultInterface()->underExposureColor();
+ expoSettings.overExposureColor = DImgInterface::defaultInterface()->overExposureColor();
+
+ TQImage pureColorMask = d->targetPreviewImage.pureColorMask(&expoSettings);
+ TQPixmap pixMask(pureColorMask);
+ bitBlt(&d->qpix, 0, 0, static_cast<TQPaintDevice*>(&pixMask), 0, 0, w, h, TQt::CopyROP, false);
+ }
+ }
+
+ bitBlt(device, x, y, static_cast<TQPaintDevice*>(&d->qpix), 0, 0, -1, -1, TQt::CopyROP, false);
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/editor/imageiface.h b/src/utilities/imageeditor/editor/imageiface.h
new file mode 100644
index 00000000..272c62a5
--- /dev/null
+++ b/src/utilities/imageeditor/editor/imageiface.h
@@ -0,0 +1,198 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-14
+ * Description : image data interface for image plugins
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEIFACE_H
+#define IMAGEIFACE_H
+
+// TQt includes.
+
+#include <tqglobal.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "dcolor.h"
+#include "photoinfocontainer.h"
+#include "digikam_export.h"
+
+#define MAX3(a, b, c) (TQMAX(TQMAX(a,b),b))
+#define MIN3(a, b, c) (TQMIN(TQMIN(a,b),b))
+#define ROUND(x) ((int) ((x) + 0.5))
+
+class TQPaintDevice;
+
+namespace Digikam
+{
+
+class ImageIfacePriv;
+
+class DIGIKAM_EXPORT ImageIface
+{
+public:
+
+ ImageIface(int w=0, int h=0);
+ ~ImageIface();
+
+ /** Use this method to use the current selection in editor instead the full
+ image to render the preview.
+ */
+ void setPreviewType(bool useSelect=false);
+
+ /** Return 'true' if the preview is rendered using the current selection in editor.
+ Return 'false' if the preview is rendered using the full image in editor.
+ */
+ bool previewType();
+
+ /** Return image data for the current, scaled preview image.
+ The preview...() methods provide the characteristics of the data
+ (width, heigh, sixteen bit, alpha).
+ Ownership of the returned buffer is passed to the caller.
+ */
+ uchar* getPreviewImage() const;
+
+ /** Return image data for the current original image selection.
+ The selectionWidth(), selectionHeight(), originalSixteenBit()
+ and originalHasAlpha() methods provide the characteristics of the data.
+ Ownership of the returned buffer is passed to the caller.
+ */
+ uchar* getImageSelection() const;
+
+ /** Return image data for the original image.
+ The preview...() methods provide the characteristics of the data.
+ Ownership of the returned buffer is passed to the caller.
+ */
+ uchar* getOriginalImage() const;
+
+ /** Return a pointer to the DImg object representing the original image.
+ This object may not be modified or stored. Make copies if you need.
+ */
+ DImg* getOriginalImg() const;
+
+ /** Replace the image data of the original image with the given data.
+ The characteristics of the data must match the characteristics of
+ the original image as returned by the original...() methods,
+ respectively the given width and height parameters.
+ No ownership of the data pointer is assumed.
+ If w == -1 and h == -1, the size is unchanged.
+ Caller is an i18n'ed string that will be shown as the undo/redo action name.
+ */
+ void putOriginalImage(const TQString &caller, uchar* data, int w=-1, int h=-1);
+
+ /** Embed the Color Profile we have used in ICC plugin when this option is
+ selected
+ */
+ void setEmbeddedICCToOriginalImage(const TQString& profilePath);
+
+ /** Replace the data of the current original image selection with the given data.
+ The characteristics of the data must match the characteristics of the current
+ selection as returned by the selectionWidth(), selectionHeight(),
+ originalSixteenBit() and originalHasAlpha() methods.
+ No ownership of the data pointer is assumed.
+ Caller is an i18n'ed string that will be shown as the undo/redo action name.
+ */
+ void putImageSelection(const TQString &caller, uchar* data);
+
+ /** Replace the stored target preview data with the given data.
+ The characteristics of the data must match the characteristics of the current
+ as returned by the preview...() methods.
+ The target preview data is used by the paint() and
+ getColorInfoFromTargetPreviewImage() methods.
+ The data returned by getPreviewImage() is unaffected.
+ No ownership of the data pointer is assumed.
+ */
+ void putPreviewImage(uchar* data);
+
+ /** Get colors from original, (unchanged) preview
+ or target preview (set by putPreviewImage) image.
+ */
+
+ DColor getColorInfoFromOriginalImage(const TQPoint& point);
+ DColor getColorInfoFromPreviewImage(const TQPoint& point);
+ DColor getColorInfoFromTargetPreviewImage(const TQPoint& point);
+
+ /** Original image information.*/
+ int originalWidth();
+ int originalHeight();
+ bool originalSixteenBit();
+ bool originalHasAlpha();
+
+ /** Original image metadata.*/
+ TQByteArray getEmbeddedICCFromOriginalImage();
+ TQByteArray getExifFromOriginalImage();
+ TQByteArray getIptcFromOriginalImage();
+
+ /** Get photograph information from original image.*/
+ PhotoInfoContainer getPhotographInformations() const;
+
+ /** Standard methods to get/set preview information.*/
+ int previewWidth();
+ int previewHeight();
+ bool previewHasAlpha();
+ bool previewSixteenBit();
+
+ /** Sets preview size and returns new preview data as with getPreviewImage.
+ The parameters are only hints, previewWidth() and previewHeight()
+ may differ from w and h.
+ */
+ uchar* setPreviewImageSize(int w, int h) const;
+
+ /** Standard methods to get image selection information.*/
+ int selectedWidth();
+ int selectedHeight();
+
+ /** Get selected (X, Y) position on the top/left corner of the original image.*/
+ int selectedXOrg();
+ int selectedYOrg();
+
+ /** Set BCG correction for preview and original image */
+ void setPreviewBCG(double brightness, double contrast, double gamma);
+ void setOriginalBCG(double brightness, double contrast, double gamma);
+
+ /** Convert depth of original image */
+ void convertOriginalColorDepth(int depth);
+
+ /** Convert a DImg image to a pixmap for screen using color
+ managemed view if necessary */
+ TQPixmap convertToPixmap(DImg& img);
+
+ /** Paint the current target preview image (or the preview image,
+ if putPreviewImage has not been called) on the given paint device.
+ at x|y, with given maximum width and height.
+ */
+ void paint(TQPaintDevice* device, int x, int y, int w, int h,
+ bool underExposure=false, bool overExposure=false);
+
+private:
+
+ ImageIfacePriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* IMAGEIFACE_H */
diff --git a/src/utilities/imageeditor/editor/imagewindow.cpp b/src/utilities/imageeditor/editor/imagewindow.cpp
new file mode 100644
index 00000000..2c10a6f6
--- /dev/null
+++ b/src/utilities/imageeditor/editor/imagewindow.cpp
@@ -0,0 +1,1263 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-12
+ * Description : digiKam image editor GUI
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cstdio>
+
+// TQt includes.
+
+#include <tqcursor.h>
+#include <tqtimer.h>
+#include <tqlabel.h>
+#include <tqimage.h>
+#include <tqsplitter.h>
+#include <tqpainter.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <kstandarddirs.h>
+#include <tdeapplication.h>
+#include <tdemessagebox.h>
+#include <tdetempfile.h>
+#include <kimageio.h>
+#include <tdefiledialog.h>
+#include <tdeversion.h>
+#include <tdemenubar.h>
+#include <tdetoolbar.h>
+#include <tdeaccel.h>
+#include <tdeaction.h>
+#include <tdestdaccel.h>
+#include <kstdaction.h>
+#include <kstdguiitem.h>
+#include <kstatusbar.h>
+#include <kprogress.h>
+#include <twin.h>
+
+// Local includes.
+
+#include "constants.h"
+#include "ddebug.h"
+#include "dlogoaction.h"
+#include "dpopupmenu.h"
+#include "dragobjects.h"
+#include "canvas.h"
+#include "dimginterface.h"
+#include "dimg.h"
+#include "dmetadata.h"
+#include "imageplugin.h"
+#include "imagepluginloader.h"
+#include "imageprint.h"
+#include "albummanager.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albumsettings.h"
+#include "syncjob.h"
+#include "imageinfo.h"
+#include "imagepropertiessidebardb.h"
+#include "tagspopupmenu.h"
+#include "ratingpopupmenu.h"
+#include "slideshow.h"
+#include "setup.h"
+#include "iccsettingscontainer.h"
+#include "iofilesettingscontainer.h"
+#include "loadingcacheinterface.h"
+#include "savingcontextcontainer.h"
+#include "statusprogressbar.h"
+#include "imageattributeswatch.h"
+#include "deletedialog.h"
+#include "metadatahub.h"
+#include "themeengine.h"
+#include "editorstackview.h"
+#include "imagewindow.h"
+#include "imagewindow.moc"
+
+namespace Digikam
+{
+
+class ImageWindowPriv
+{
+
+public:
+
+ ImageWindowPriv()
+ {
+ allowSaving = true;
+ star0 = 0;
+ star1 = 0;
+ star2 = 0;
+ star3 = 0;
+ star4 = 0;
+ star5 = 0;
+ fileDeletePermanentlyAction = 0;
+ fileDeletePermanentlyDirectlyAction = 0;
+ fileTrashDirectlyAction = 0;
+ imageInfoCurrent = 0;
+ rightSidebar = 0;
+ }
+
+ // If image editor is launched by camera interface, current
+ // image cannot be saved.
+ bool allowSaving;
+
+ KURL::List urlList;
+ KURL urlCurrent;
+
+ // Rating actions.
+ TDEAction *star0;
+ TDEAction *star1;
+ TDEAction *star2;
+ TDEAction *star3;
+ TDEAction *star4;
+ TDEAction *star5;
+
+ // Delete actions
+ TDEAction *fileDeletePermanentlyAction;
+ TDEAction *fileDeletePermanentlyDirectlyAction;
+ TDEAction *fileTrashDirectlyAction;
+
+ ImageInfoList imageInfoList;
+ ImageInfo *imageInfoCurrent;
+
+ ImagePropertiesSideBarDB *rightSidebar;
+};
+
+ImageWindow* ImageWindow::m_instance = 0;
+
+ImageWindow* ImageWindow::imagewindow()
+{
+ if (!m_instance)
+ new ImageWindow();
+
+ return m_instance;
+}
+
+bool ImageWindow::imagewindowCreated()
+{
+ return m_instance;
+}
+
+ImageWindow::ImageWindow()
+ : EditorWindow( "Image Editor" )
+{
+ d = new ImageWindowPriv;
+ m_instance = this;
+ setAcceptDrops(true);
+
+ // -- Build the GUI -------------------------------
+
+ setupUserArea();
+ setupStatusBar();
+ setupActions();
+
+ // Load image plugins to GUI
+
+ m_imagePluginLoader = ImagePluginLoader::instance();
+ loadImagePlugins();
+
+ // Create context menu.
+
+ setupContextMenu();
+
+ // Make signals/slots connections
+
+ setupConnections();
+
+ // -- Read settings --------------------------------
+
+ readSettings();
+ applySettings();
+ setAutoSaveSettings("ImageViewer Settings");
+
+ //-------------------------------------------------------------
+
+ d->rightSidebar->loadViewState();
+ d->rightSidebar->populateTags();
+}
+
+ImageWindow::~ImageWindow()
+{
+ m_instance = 0;
+
+ unLoadImagePlugins();
+
+ // No need to delete m_imagePluginLoader instance here, it will be done by main interface.
+
+ delete d->rightSidebar;
+ delete d;
+}
+
+Sidebar* ImageWindow::rightSideBar() const
+{
+ return dynamic_cast<Sidebar*>(d->rightSidebar);
+}
+
+void ImageWindow::closeEvent(TQCloseEvent* e)
+{
+ if (!e)
+ return;
+
+ if (!queryClose())
+ return;
+
+ // put right side bar in a defined state
+ emit signalNoCurrentItem();
+
+ m_canvas->resetImage();
+
+ saveSettings();
+
+ e->accept();
+}
+
+bool ImageWindow::queryClose()
+{
+ // Note: we reimplement closeEvent above for this window.
+ // Additionally, queryClose is called from DigikamApp.
+
+ // wait if a save operation is currently running
+ if (!waitForSavingToComplete())
+ return false;
+
+ return promptUserSave(d->urlCurrent);
+}
+
+void ImageWindow::setupConnections()
+{
+ setupStandardConnections();
+
+ // To toggle properly keyboards shortcuts from comments & tags side bar tab.
+
+ connect(d->rightSidebar, TQ_SIGNAL(signalNextItem()),
+ this, TQ_SLOT(slotForward()));
+
+ connect(d->rightSidebar, TQ_SIGNAL(signalPrevItem()),
+ this, TQ_SLOT(slotBackward()));
+
+ connect(this, TQ_SIGNAL(signalSelectionChanged( const TQRect &)),
+ d->rightSidebar, TQ_SLOT(slotImageSelectionChanged( const TQRect &)));
+
+ connect(this, TQ_SIGNAL(signalNoCurrentItem()),
+ d->rightSidebar, TQ_SLOT(slotNoCurrentItem()));
+
+ ImageAttributesWatch *watch = ImageAttributesWatch::instance();
+
+ connect(watch, TQ_SIGNAL(signalFileMetadataChanged(const KURL &)),
+ this, TQ_SLOT(slotFileMetadataChanged(const KURL &)));
+}
+
+void ImageWindow::setupUserArea()
+{
+ TQWidget* widget = new TQWidget(this);
+ TQHBoxLayout *lay = new TQHBoxLayout(widget);
+
+ m_splitter = new TQSplitter(widget);
+ m_stackView = new EditorStackView(m_splitter);
+ m_canvas = new Canvas(m_stackView);
+ m_stackView->setCanvas(m_canvas);
+ m_stackView->setViewMode(EditorStackView::CanvasMode);
+
+ m_canvas->makeDefaultEditingCanvas();
+
+ TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1);
+ m_canvas->setSizePolicy(rightSzPolicy);
+
+ d->rightSidebar = new ImagePropertiesSideBarDB(widget, "ImageEditor Right Sidebar", m_splitter,
+ Sidebar::Right, true);
+ lay->addWidget(m_splitter);
+ lay->addWidget(d->rightSidebar);
+
+ m_splitter->setFrameStyle( TQFrame::NoFrame );
+ m_splitter->setFrameShadow( TQFrame::Plain );
+ m_splitter->setFrameShape( TQFrame::NoFrame );
+ m_splitter->setOpaqueResize(false);
+ setCentralWidget(widget);
+}
+
+void ImageWindow::setupActions()
+{
+ setupStandardActions();
+
+ // Provides a menu entry that allows showing/hiding the toolbar(s)
+ setStandardToolBarMenuEnabled(true);
+
+ // Provides a menu entry that allows showing/hiding the statusbar
+ createStandardStatusBarAction();
+
+ // -- Rating actions ---------------------------------------------------------------
+
+ d->star0 = new TDEAction(i18n("Assign Rating \"No Stars\""), CTRL+Key_0,
+ this, TQ_SLOT(slotAssignRatingNoStar()),
+ actionCollection(), "imageview_ratenostar");
+ d->star1 = new TDEAction(i18n("Assign Rating \"One Star\""), CTRL+Key_1,
+ this, TQ_SLOT(slotAssignRatingOneStar()),
+ actionCollection(), "imageview_rateonestar");
+ d->star2 = new TDEAction(i18n("Assign Rating \"Two Stars\""), CTRL+Key_2,
+ this, TQ_SLOT(slotAssignRatingTwoStar()),
+ actionCollection(), "imageview_ratetwostar");
+ d->star3 = new TDEAction(i18n("Assign Rating \"Three Stars\""), CTRL+Key_3,
+ this, TQ_SLOT(slotAssignRatingThreeStar()),
+ actionCollection(), "imageview_ratethreestar");
+ d->star4 = new TDEAction(i18n("Assign Rating \"Four Stars\""), CTRL+Key_4,
+ this, TQ_SLOT(slotAssignRatingFourStar()),
+ actionCollection(), "imageview_ratefourstar");
+ d->star5 = new TDEAction(i18n("Assign Rating \"Five Stars\""), CTRL+Key_5,
+ this, TQ_SLOT(slotAssignRatingFiveStar()),
+ actionCollection(), "imageview_ratefivestar");
+
+ // -- Special Delete actions ---------------------------------------------------------------
+
+ // Pop up dialog to ask user whether to permanently delete
+ d->fileDeletePermanentlyAction = new TDEAction(i18n("Delete File Permanently"),
+ "edit-delete",
+ SHIFT+Key_Delete,
+ this,
+ TQ_SLOT(slotDeleteCurrentItemPermanently()),
+ actionCollection(),
+ "image_delete_permanently");
+
+ // These two actions are hidden, no menu entry, no toolbar entry, no shortcut.
+ // Power users may add them.
+ d->fileDeletePermanentlyDirectlyAction = new TDEAction(i18n("Delete Permanently without Confirmation"),
+ "edit-delete",
+ 0,
+ this,
+ TQ_SLOT(slotDeleteCurrentItemPermanentlyDirectly()),
+ actionCollection(),
+ "image_delete_permanently_directly");
+
+ d->fileTrashDirectlyAction = new TDEAction(i18n("Move to Trash without Confirmation"),
+ "edittrash",
+ 0,
+ this,
+ TQ_SLOT(slotTrashCurrentItemDirectly()),
+ actionCollection(),
+ "image_trash_directly");
+
+ // ---------------------------------------------------------------------------------
+
+ new DLogoAction(actionCollection(), "logo_action");
+
+ createGUI("digikamimagewindowui.rc", false);
+
+ setupStandardAccelerators();
+}
+
+void ImageWindow::applySettings()
+{
+ applyStandardSettings();
+
+ AlbumSettings *settings = AlbumSettings::instance();
+ m_canvas->setExifOrient(settings->getExifRotate());
+ m_setExifOrientationTag = settings->getExifSetOrientation();
+ refreshView();
+}
+
+void ImageWindow::refreshView()
+{
+ d->rightSidebar->refreshTagsView();
+}
+
+void ImageWindow::loadURL(const KURL::List& urlList, const KURL& urlCurrent,
+ const TQString& caption, bool allowSaving)
+{
+ if (!promptUserSave(d->urlCurrent))
+ return;
+
+ d->urlList = urlList;
+ d->urlCurrent = urlCurrent;
+ d->imageInfoList = ImageInfoList();
+ d->imageInfoCurrent = 0;
+
+ loadCurrentList(caption, allowSaving);
+}
+
+void ImageWindow::loadImageInfos(const ImageInfoList &imageInfoList, ImageInfo *imageInfoCurrent,
+ const TQString& caption, bool allowSaving)
+{
+ // The ownership of objects of imageInfoList is passed to us.
+ // imageInfoCurrent is contained in imageInfoList.
+
+ // Very first thing is to check for changes, user may choose to cancel operation
+ if (!promptUserSave(d->urlCurrent))
+ {
+ // delete objects from list
+ for (ImageInfoList::iterator it = imageInfoList.begin(); it != imageInfoList.end(); ++it)
+ delete *it;
+ return;
+ }
+
+ // take over ImageInfo list
+ d->imageInfoList = imageInfoList;
+ d->imageInfoCurrent = imageInfoCurrent;
+
+ d->imageInfoList.setAutoDelete(true);
+
+ // create URL list
+ d->urlList = KURL::List();
+
+ ImageInfoListIterator it(d->imageInfoList);
+ ImageInfo *info;
+ for (; (info = it.current()); ++it)
+ {
+ d->urlList.append(info->kurl());
+ }
+
+ d->urlCurrent = d->imageInfoCurrent->kurl();
+
+ loadCurrentList(caption, allowSaving);
+}
+
+void ImageWindow::loadCurrentList(const TQString& caption, bool allowSaving)
+{
+ // this method contains the code shared by loadURL and loadImageInfos
+
+ // if window is iconified, show it
+ if (isMinimized())
+ {
+ KWin::deIconifyWindow(winId());
+ }
+
+ if (!caption.isEmpty())
+ setCaption(i18n("Image Editor - %1").arg(caption));
+ else
+ setCaption(i18n("Image Editor"));
+
+ d->allowSaving = allowSaving;
+
+ m_saveAction->setEnabled(false);
+ m_revertAction->setEnabled(false);
+ m_undoAction->setEnabled(false);
+ m_redoAction->setEnabled(false);
+
+ TQTimer::singleShot(0, this, TQ_SLOT(slotLoadCurrent()));
+}
+
+void ImageWindow::slotLoadCurrent()
+{
+ KURL::List::iterator it = d->urlList.find(d->urlCurrent);
+
+ if (it != d->urlList.end())
+ {
+ m_canvas->load(d->urlCurrent.path(), m_IOFileSettings);
+
+ ++it;
+ if (it != d->urlList.end())
+ m_canvas->preload((*it).path());
+ }
+
+ // Do this _after_ the canvas->load(), so that the main view histogram does not load
+ // a smaller version if a raw image, and after that the DImgInterface loads the full version.
+ // So first let DImgInterface create its loading task, only then any external objects.
+ setViewToURL(d->urlCurrent);
+}
+
+void ImageWindow::setViewToURL(const KURL &url)
+{
+ emit signalURLChanged(url);
+}
+
+void ImageWindow::slotForward()
+{
+ if(!promptUserSave(d->urlCurrent))
+ return;
+
+ KURL::List::iterator it = d->urlList.find(d->urlCurrent);
+ int index = d->imageInfoList.find(d->imageInfoCurrent);
+
+ if (it != d->urlList.end())
+ {
+ if (d->urlCurrent != d->urlList.last())
+ {
+ KURL urlNext = *(++it);
+ d->imageInfoCurrent = d->imageInfoList.at(index + 1);
+ d->urlCurrent = urlNext;
+ slotLoadCurrent();
+ }
+ }
+}
+
+void ImageWindow::slotBackward()
+{
+ if(!promptUserSave(d->urlCurrent))
+ return;
+
+ KURL::List::iterator it = d->urlList.find(d->urlCurrent);
+ int index = d->imageInfoList.find(d->imageInfoCurrent);
+
+ if (it != d->urlList.begin())
+ {
+ if (d->urlCurrent != d->urlList.first())
+ {
+ KURL urlPrev = *(--it);
+ d->imageInfoCurrent = d->imageInfoList.at(index - 1);
+ d->urlCurrent = urlPrev;
+ slotLoadCurrent();
+ }
+ }
+}
+
+void ImageWindow::slotFirst()
+{
+ if(!promptUserSave(d->urlCurrent))
+ return;
+
+ d->urlCurrent = d->urlList.first();
+ d->imageInfoCurrent = d->imageInfoList.first();
+ slotLoadCurrent();
+}
+
+void ImageWindow::slotLast()
+{
+ if(!promptUserSave(d->urlCurrent))
+ return;
+
+ d->urlCurrent = d->urlList.last();
+ d->imageInfoCurrent = d->imageInfoList.last();
+ slotLoadCurrent();
+}
+
+void ImageWindow::slotContextMenu()
+{
+ if (m_contextMenu)
+ {
+ RatingPopupMenu *ratingMenu = 0;
+ TagsPopupMenu *assignTagsMenu = 0;
+ TagsPopupMenu *removeTagsMenu = 0;
+ int separatorID1 = -1;
+ int separatorID2 = -1;
+
+ if (d->imageInfoCurrent)
+ {
+ // Bulk assignment/removal of tags --------------------------
+
+ TQ_LLONG id = d->imageInfoCurrent->id();
+ TQValueList<TQ_LLONG> idList;
+ idList.append(id);
+
+ assignTagsMenu = new TagsPopupMenu(idList, 1000, TagsPopupMenu::ASSIGN);
+ removeTagsMenu = new TagsPopupMenu(idList, 2000, TagsPopupMenu::REMOVE);
+
+ separatorID1 = m_contextMenu->insertSeparator();
+
+ m_contextMenu->insertItem(i18n("Assign Tag"), assignTagsMenu);
+ int i = m_contextMenu->insertItem(i18n("Remove Tag"), removeTagsMenu);
+
+ connect(assignTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotAssignTag(int)));
+
+ connect(removeTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotRemoveTag(int)));
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ if (!db->hasTags( idList ))
+ m_contextMenu->setItemEnabled(i, false);
+
+ separatorID2 = m_contextMenu->insertSeparator();
+
+ // Assign Star Rating -------------------------------------------
+
+ ratingMenu = new RatingPopupMenu();
+
+ connect(ratingMenu, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotAssignRating(int)));
+
+ m_contextMenu->insertItem(i18n("Assign Rating"), ratingMenu);
+ }
+
+ m_contextMenu->exec(TQCursor::pos());
+
+ if (separatorID1 != -1)
+ m_contextMenu->removeItem(separatorID1);
+ if (separatorID2 != -1)
+ m_contextMenu->removeItem(separatorID2);
+
+ delete assignTagsMenu;
+ delete removeTagsMenu;
+ delete ratingMenu;
+ }
+}
+
+void ImageWindow::slotChanged()
+{
+ TQString mpixels;
+ TQSize dims(m_canvas->imageWidth(), m_canvas->imageHeight());
+ mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2);
+ TQString str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)")
+ .arg(dims.width()).arg(dims.height()).arg(mpixels);
+ m_resLabel->setText(str);
+
+ if (d->urlCurrent.isValid())
+ {
+ KURL u(d->urlCurrent.directory());
+
+ DImg* img = m_canvas->interface()->getImg();
+
+ if (d->imageInfoCurrent)
+ {
+ d->rightSidebar->itemChanged(d->imageInfoCurrent,
+ m_canvas->getSelectedArea(), img);
+ }
+ else
+ {
+ d->rightSidebar->itemChanged(d->urlCurrent, m_canvas->getSelectedArea(), img);
+ }
+ }
+}
+
+void ImageWindow::slotUndoStateChanged(bool moreUndo, bool moreRedo, bool canSave)
+{
+ m_revertAction->setEnabled(canSave);
+ m_undoAction->setEnabled(moreUndo);
+ m_redoAction->setEnabled(moreRedo);
+
+ if (d->allowSaving)
+ m_saveAction->setEnabled(canSave);
+
+ if (!moreUndo)
+ m_rotatedOrFlipped = false;
+}
+
+void ImageWindow::slotAssignTag(int tagID)
+{
+ if (d->imageInfoCurrent)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfoCurrent);
+ hub.setTag(tagID, true);
+ hub.write(d->imageInfoCurrent, MetadataHub::PartialWrite);
+ hub.write(d->imageInfoCurrent->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void ImageWindow::slotRemoveTag(int tagID)
+{
+ if (d->imageInfoCurrent)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfoCurrent);
+ hub.setTag(tagID, false);
+ hub.write(d->imageInfoCurrent, MetadataHub::PartialWrite);
+ hub.write(d->imageInfoCurrent->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void ImageWindow::slotAssignRatingNoStar()
+{
+ slotAssignRating(0);
+}
+
+void ImageWindow::slotAssignRatingOneStar()
+{
+ slotAssignRating(1);
+}
+
+void ImageWindow::slotAssignRatingTwoStar()
+{
+ slotAssignRating(2);
+}
+
+void ImageWindow::slotAssignRatingThreeStar()
+{
+ slotAssignRating(3);
+}
+
+void ImageWindow::slotAssignRatingFourStar()
+{
+ slotAssignRating(4);
+}
+
+void ImageWindow::slotAssignRatingFiveStar()
+{
+ slotAssignRating(5);
+}
+
+void ImageWindow::slotAssignRating(int rating)
+{
+ rating = TQMIN(RatingMax, TQMAX(RatingMin, rating));
+ if (d->imageInfoCurrent)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfoCurrent);
+ hub.setRating(rating);
+ hub.write(d->imageInfoCurrent, MetadataHub::PartialWrite);
+ hub.write(d->imageInfoCurrent->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void ImageWindow::slotUpdateItemInfo()
+{
+ uint index = d->urlList.findIndex(d->urlCurrent);
+
+ m_rotatedOrFlipped = false;
+
+ TQString text = d->urlCurrent.filename() + i18n(" (%2 of %3)")
+ .arg(TQString::number(index+1))
+ .arg(TQString::number(d->urlList.count()));
+ m_nameLabel->setText(text);
+
+ if (d->urlList.count() == 1)
+ {
+ m_backwardAction->setEnabled(false);
+ m_forwardAction->setEnabled(false);
+ m_firstAction->setEnabled(false);
+ m_lastAction->setEnabled(false);
+ }
+ else
+ {
+ m_backwardAction->setEnabled(true);
+ m_forwardAction->setEnabled(true);
+ m_firstAction->setEnabled(true);
+ m_lastAction->setEnabled(true);
+ }
+
+ if (index == 0)
+ {
+ m_backwardAction->setEnabled(false);
+ m_firstAction->setEnabled(false);
+ }
+
+ if (index == d->urlList.count()-1)
+ {
+ m_forwardAction->setEnabled(false);
+ m_lastAction->setEnabled(false);
+ }
+
+ // Disable some menu actions if the current root image URL
+ // is not include in the digiKam Albums library database.
+ // This is necessary when ImageEditor is opened from cameraclient.
+
+ KURL u(d->urlCurrent.directory());
+ PAlbum *palbum = AlbumManager::instance()->findPAlbum(u);
+
+ if (!palbum)
+ {
+ m_fileDeleteAction->setEnabled(false);
+ }
+ else
+ {
+ m_fileDeleteAction->setEnabled(true);
+ }
+}
+
+bool ImageWindow::setup(bool iccSetupPage)
+{
+ Setup setup(this, 0, iccSetupPage ? Setup::IccProfiles : Setup::LastPageUsed);
+
+ if (setup.exec() != TQDialog::Accepted)
+ return false;
+
+ kapp->config()->sync();
+
+ applySettings();
+ return true;
+}
+
+void ImageWindow::toggleGUI2FullScreen()
+{
+ if (m_fullScreen)
+ d->rightSidebar->restore();
+ else
+ d->rightSidebar->backup();
+}
+
+void ImageWindow::saveIsComplete()
+{
+ // With save(), we do not reload the image but just continue using the data.
+ // This means that a saving operation does not lead to quality loss for
+ // subsequent editing operations.
+
+ // put image in cache, the LoadingCacheInterface cares for the details
+ LoadingCacheInterface::putImage(m_savingContext->destinationURL.path(), m_canvas->currentImage());
+
+ // notify main app that file changed
+ emit signalFileModified(m_savingContext->destinationURL);
+
+ // all that is done in slotLoadCurrent, except for loading
+ KURL::List::iterator it = d->urlList.find(d->urlCurrent);
+ setViewToURL(*it);
+
+ if (++it != d->urlList.end())
+ {
+ m_canvas->preload((*it).path());
+ }
+ //slotLoadCurrent();
+}
+
+void ImageWindow::saveAsIsComplete()
+{
+ // Nothing to be done if operating without database
+ if (!d->imageInfoCurrent)
+ return;
+
+ // Find the src and dest albums ------------------------------------------
+
+ KURL srcDirURL(TQDir::cleanDirPath(m_savingContext->srcURL.directory()));
+ PAlbum* srcAlbum = AlbumManager::instance()->findPAlbum(srcDirURL);
+
+ KURL dstDirURL(TQDir::cleanDirPath(m_savingContext->destinationURL.directory()));
+ PAlbum* dstAlbum = AlbumManager::instance()->findPAlbum(dstDirURL);
+
+ if (dstAlbum && srcAlbum)
+ {
+ // Now copy the metadata of the original file to the new file ------------
+
+ ImageInfo newInfo(d->imageInfoCurrent->copyItem(dstAlbum, m_savingContext->destinationURL.fileName()));
+
+ if ( d->urlList.find(m_savingContext->destinationURL) == d->urlList.end() )
+ { // The image file did not exist in the list.
+ KURL::List::iterator it = d->urlList.find(m_savingContext->srcURL);
+ int index = d->urlList.findIndex(m_savingContext->srcURL);
+ d->urlList.insert(it, m_savingContext->destinationURL);
+ d->imageInfoCurrent = new ImageInfo(newInfo);
+ d->imageInfoList.insert(index, d->imageInfoCurrent);
+ }
+ else if (d->urlCurrent != m_savingContext->destinationURL)
+ {
+ for (ImageInfo *info = d->imageInfoList.first(); info; info = d->imageInfoList.next())
+ {
+ if (info->kurl() == m_savingContext->destinationURL)
+ {
+ d->imageInfoCurrent = new ImageInfo(newInfo);
+ // setAutoDelete is true
+ d->imageInfoList.replace(d->imageInfoList.at(), d->imageInfoCurrent);
+ break;
+ }
+ }
+ }
+
+ d->urlCurrent = m_savingContext->destinationURL;
+ m_canvas->switchToLastSaved(m_savingContext->destinationURL.path());
+
+ slotUpdateItemInfo();
+
+ // If the DImg is put in the cache under the new name, this means the new file will not be reloaded.
+ // This may irritate users who want to check for quality loss in lossy formats.
+ // In any case, only do that if the format did not change - too many assumptions otherwise (see bug #138949).
+ if (m_savingContext->originalFormat == m_savingContext->format)
+ LoadingCacheInterface::putImage(m_savingContext->destinationURL.path(), m_canvas->currentImage());
+
+ // notify main app that file changed or a file is added
+ if(m_savingContext->destinationExisted)
+ emit signalFileModified(m_savingContext->destinationURL);
+ else
+ emit signalFileAdded(m_savingContext->destinationURL);
+
+ // all that is done in slotLoadCurrent, except for loading
+ KURL::List::iterator it = d->urlList.find(d->urlCurrent);
+
+ if (it != d->urlList.end())
+ {
+ setViewToURL(*it);
+ m_canvas->preload((*++it).path());
+ }
+ }
+ else
+ {
+ //TODO: make the user aware that the new path has not been used as new current filename
+ // because it is outside the digikam album hierachy
+ }
+}
+
+bool ImageWindow::save()
+{
+ // Sanity check. Just to be homogenous with SaveAs.
+ if (d->imageInfoCurrent)
+ {
+ // Write metadata from database to DImg
+ MetadataHub hub;
+ hub.load(d->imageInfoCurrent);
+ DImg image(m_canvas->currentImage());
+ hub.write(image, MetadataHub::FullWrite);
+ }
+
+ startingSave(d->urlCurrent);
+ return true;
+}
+
+bool ImageWindow::saveAs()
+{
+ // If image editor is started from CameraGUI, there is no ImageInfo instance to use.
+ if (d->imageInfoCurrent)
+ {
+ // Write metadata from database to DImg
+ MetadataHub hub;
+ hub.load(d->imageInfoCurrent);
+ DImg image(m_canvas->currentImage());
+ hub.write(image, MetadataHub::FullWrite);
+ }
+
+ return ( startingSaveAs(d->urlCurrent) );
+}
+
+void ImageWindow::slotDeleteCurrentItem()
+{
+ deleteCurrentItem(true, false);
+}
+
+void ImageWindow::slotDeleteCurrentItemPermanently()
+{
+ deleteCurrentItem(true, true);
+}
+
+void ImageWindow::slotDeleteCurrentItemPermanentlyDirectly()
+{
+ deleteCurrentItem(false, true);
+}
+
+void ImageWindow::slotTrashCurrentItemDirectly()
+{
+ deleteCurrentItem(false, false);
+}
+
+void ImageWindow::deleteCurrentItem(bool ask, bool permanently)
+{
+ // This function implements all four of the above slots.
+ // The meaning of permanently differs depending on the value of ask
+
+ KURL u;
+ u.setPath(d->urlCurrent.directory());
+ PAlbum *palbum = AlbumManager::instance()->findPAlbum(u);
+
+ // if available, provide a digikamalbums:// URL to TDEIO
+ KURL kioURL;
+ if (d->imageInfoCurrent)
+ kioURL = d->imageInfoCurrent->kurlForKIO();
+ else
+ kioURL = d->urlCurrent;
+ KURL fileURL = d->urlCurrent;
+
+ if (!palbum)
+ return;
+
+ bool useTrash;
+
+ if (ask)
+ {
+ bool preselectDeletePermanently = permanently;
+
+ DeleteDialog dialog(this);
+
+ KURL::List urlList;
+ urlList.append(d->urlCurrent);
+ if (!dialog.confirmDeleteList(urlList,
+ DeleteDialogMode::Files,
+ preselectDeletePermanently ?
+ DeleteDialogMode::NoChoiceDeletePermanently : DeleteDialogMode::NoChoiceTrash))
+ return;
+
+ useTrash = !dialog.shouldDelete();
+ }
+ else
+ {
+ useTrash = !permanently;
+ }
+
+ // bring all (sidebar) to a defined state without letting them sit on the deleted file
+ emit signalNoCurrentItem();
+
+ // trash does not like non-local URLs, put is not implemented
+ if (useTrash)
+ kioURL = fileURL;
+
+ if (!SyncJob::del(kioURL, useTrash))
+ {
+ TQString errMsg(SyncJob::lastErrorMsg());
+ KMessageBox::error(this, errMsg, errMsg);
+ return;
+ }
+
+ emit signalFileDeleted(d->urlCurrent);
+
+ KURL CurrentToRemove = d->urlCurrent;
+ KURL::List::iterator it = d->urlList.find(d->urlCurrent);
+ int index = d->imageInfoList.find(d->imageInfoCurrent);
+
+ if (it != d->urlList.end())
+ {
+ if (d->urlCurrent != d->urlList.last())
+ {
+ // Try to get the next image in the current Album...
+
+ KURL urlNext = *(++it);
+ d->urlCurrent = urlNext;
+ d->imageInfoCurrent = d->imageInfoList.at(index + 1);
+ d->urlList.remove(CurrentToRemove);
+ d->imageInfoList.remove(index);
+ slotLoadCurrent();
+ return;
+ }
+ else if (d->urlCurrent != d->urlList.first())
+ {
+ // Try to get the previous image in the current Album.
+
+ KURL urlPrev = *(--it);
+ d->urlCurrent = urlPrev;
+ d->imageInfoCurrent = d->imageInfoList.at(index - 1);
+ d->urlList.remove(CurrentToRemove);
+ d->imageInfoList.remove(index);
+ slotLoadCurrent();
+ return;
+ }
+ }
+
+ // No image in the current Album -> Quit ImageEditor...
+
+ KMessageBox::information(this,
+ i18n("There is no image to show in the current album.\n"
+ "The image editor will be closed."),
+ i18n("No Image in Current Album"));
+
+ close();
+}
+
+void ImageWindow::slotFileMetadataChanged(const KURL &url)
+{
+ if (url == d->urlCurrent)
+ {
+ m_canvas->readMetadataFromFile(url.path());
+ }
+}
+
+void ImageWindow::slotFilePrint()
+{
+ printImage(d->urlCurrent);
+};
+
+void ImageWindow::slideShow(bool startWithCurrent, SlideShowSettings& settings)
+{
+ float cnt;
+ DMetadata meta;
+ int i = 0;
+ m_cancelSlideShow = false;
+ settings.exifRotate = AlbumSettings::instance()->getExifRotate();
+
+ if (!d->imageInfoList.isEmpty())
+ {
+ // We have started image editor from Album GUI. we get picture comments from database.
+
+ m_nameLabel->progressBarMode(StatusProgressBar::CancelProgressBarMode,
+ i18n("Preparing slideshow. Please wait..."));
+
+ cnt = (float)d->imageInfoList.count();
+
+ for (ImageInfo *info = d->imageInfoList.first() ;
+ !m_cancelSlideShow && info ; info = d->imageInfoList.next())
+ {
+ SlidePictureInfo pictInfo;
+ pictInfo.comment = info->caption();
+
+ // Perform optimizations: only read pictures metadata if necessary.
+ if (settings.printApertureFocal || settings.printExpoSensitivity || settings.printMakeModel)
+ {
+ meta.load(info->kurl().path());
+ pictInfo.photoInfo = meta.getPhotographInformations();
+ }
+
+ // In case of dateTime extraction from metadata failed
+ pictInfo.photoInfo.dateTime = info->dateTime();
+ settings.pictInfoMap.insert(info->kurl(), pictInfo);
+
+ m_nameLabel->setProgressValue((int)((i++/cnt)*100.0));
+ kapp->processEvents();
+ }
+ }
+ else
+ {
+ // We have started image editor from Camera GUI. we get picture comments from metadata.
+
+ m_nameLabel->progressBarMode(StatusProgressBar::CancelProgressBarMode,
+ i18n("Preparing slideshow. Please wait..."));
+
+ cnt = (float)d->urlList.count();
+
+ for (KURL::List::Iterator it = d->urlList.begin() ;
+ !m_cancelSlideShow && (it != d->urlList.end()) ; ++it)
+ {
+ SlidePictureInfo pictInfo;
+ meta.load((*it).path());
+ pictInfo.comment = meta.getImageComment();
+ pictInfo.photoInfo = meta.getPhotographInformations();
+ settings.pictInfoMap.insert(*it, pictInfo);
+
+ m_nameLabel->setProgressValue((int)((i++/cnt)*100.0));
+ kapp->processEvents();
+ }
+ }
+
+ m_nameLabel->progressBarMode(StatusProgressBar::TextMode, TQString());
+
+ if (!m_cancelSlideShow)
+ {
+ settings.exifRotate = AlbumSettings::instance()->getExifRotate();
+ settings.fileList = d->urlList;
+
+ SlideShow *slide = new SlideShow(settings);
+ if (startWithCurrent)
+ slide->setCurrent(d->urlCurrent);
+
+ slide->show();
+ }
+}
+
+void ImageWindow::dragMoveEvent(TQDragMoveEvent *e)
+{
+ int albumID;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+ KURL::List urls;
+ KURL::List kioURLs;
+
+ if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs) ||
+ AlbumDrag::decode(e, urls, albumID) ||
+ TagDrag::canDecode(e))
+ {
+ e->accept();
+ return;
+ }
+
+ e->ignore();
+}
+
+void ImageWindow::dropEvent(TQDropEvent *e)
+{
+ int albumID;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+ KURL::List urls;
+ KURL::List kioURLs;
+
+ if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
+ {
+ ImageInfoList imageInfoList;
+
+ for (TQValueList<int>::const_iterator it = imageIDs.begin();
+ it != imageIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ imageInfoList.append(info);
+ }
+
+ if (imageInfoList.isEmpty())
+ {
+ e->ignore();
+ return;
+ }
+
+ TQString ATitle;
+ AlbumManager* man = AlbumManager::instance();
+ PAlbum* palbum = man->findPAlbum(albumIDs.first());
+ if (palbum) ATitle = palbum->title();
+
+ TAlbum* talbum = man->findTAlbum(albumIDs.first());
+ if (talbum) ATitle = talbum->title();
+
+ loadImageInfos(imageInfoList, imageInfoList.first(),
+ i18n("Album \"%1\"").arg(ATitle), true);
+ e->accept();
+ }
+ else if (AlbumDrag::decode(e, urls, albumID))
+ {
+ AlbumManager* man = AlbumManager::instance();
+ TQValueList<TQ_LLONG> itemIDs = man->albumDB()->getItemIDsInAlbum(albumID);
+ ImageInfoList imageInfoList;
+
+ for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
+ it != itemIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ imageInfoList.append(info);
+ }
+
+ if (imageInfoList.isEmpty())
+ {
+ e->ignore();
+ return;
+ }
+
+ TQString ATitle;
+ PAlbum* palbum = man->findPAlbum(albumIDs.first());
+ if (palbum) ATitle = palbum->title();
+
+ loadImageInfos(imageInfoList, imageInfoList.first(),
+ i18n("Album \"%1\"").arg(ATitle), true);
+ e->accept();
+ }
+ else if(TagDrag::canDecode(e))
+ {
+ TQByteArray ba = e->encodedData("digikam/tag-id");
+ TQDataStream ds(ba, IO_ReadOnly);
+ int tagID;
+ ds >> tagID;
+
+ AlbumManager* man = AlbumManager::instance();
+ TQValueList<TQ_LLONG> itemIDs = man->albumDB()->getItemIDsInTag(tagID, true);
+ ImageInfoList imageInfoList;
+
+ for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
+ it != itemIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ imageInfoList.append(info);
+ }
+
+ if (imageInfoList.isEmpty())
+ {
+ e->ignore();
+ return;
+ }
+
+ TQString ATitle;
+ TAlbum* talbum = man->findTAlbum(tagID);
+ if (talbum) ATitle = talbum->title();
+
+ loadImageInfos(imageInfoList, imageInfoList.first(),
+ i18n("Album \"%1\"").arg(ATitle), true);
+ e->accept();
+ }
+ else
+ {
+ e->ignore();
+ }
+}
+
+void ImageWindow::slotRevert()
+{
+ if(!promptUserSave(d->urlCurrent))
+ return;
+
+ m_canvas->slotRestore();
+}
+
+void ImageWindow::slotChangeTheme(const TQString& theme)
+{
+ AlbumSettings::instance()->setCurrentTheme(theme);
+ ThemeEngine::instance()->slotChangeTheme(theme);
+}
+
+} // namespace Digikam
diff --git a/src/utilities/imageeditor/editor/imagewindow.h b/src/utilities/imageeditor/editor/imagewindow.h
new file mode 100644
index 00000000..389518a8
--- /dev/null
+++ b/src/utilities/imageeditor/editor/imagewindow.h
@@ -0,0 +1,155 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-02-12
+ * Description : digiKam image editor GUI
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEWINDOW_H
+#define IMAGEWINDOW_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "editorwindow.h"
+#include "imageinfo.h"
+
+class TQDragMoveEvent;
+class TQDropEvent;
+
+namespace Digikam
+{
+
+class AlbumIconView;
+class ImageWindowPriv;
+class SlideShowSettings;
+
+class ImageWindow : public EditorWindow
+{
+ TQ_OBJECT
+
+
+public:
+
+ ~ImageWindow();
+
+ void loadURL(const KURL::List& urlList, const KURL& urlCurrent,
+ const TQString& caption=TQString(),
+ bool allowSaving=true);
+
+ void loadImageInfos(const ImageInfoList &imageInfoList,
+ ImageInfo *imageInfoCurrent,
+ const TQString& caption, bool allowSaving);
+
+ static ImageWindow* imagewindow();
+ static bool imagewindowCreated();
+
+ void applySettings();
+ void refreshView();
+ bool setup(bool iccSetupPage=false);
+
+ bool queryClose();
+
+signals:
+
+ void signalFileDeleted(const KURL& url);
+ void signalFileAdded(const KURL& url);
+ void signalFileModified(const KURL& url);
+ void signalURLChanged(const KURL& url);
+
+private:
+
+ void loadCurrentList(const TQString& caption, bool allowSaving);
+ void closeEvent(TQCloseEvent* e);
+
+ void dragMoveEvent(TQDragMoveEvent *e);
+ void dropEvent(TQDropEvent *e);
+
+ void setupActions();
+ void setupConnections();
+ void setupUserArea();
+ void toggleGUI2FullScreen();
+
+ bool save();
+ bool saveAs();
+
+ void saveIsComplete();
+ void saveAsIsComplete();
+ void setViewToURL(const KURL &url);
+ void deleteCurrentItem(bool ask, bool permanently);
+
+ void slideShow(bool startWithCurrent, SlideShowSettings& settings);
+
+ Sidebar* rightSideBar() const;
+
+ ImageWindow();
+
+private slots:
+
+ void slotForward();
+ void slotBackward();
+ void slotFirst();
+ void slotLast();
+ void slotFilePrint();
+
+ void slotLoadCurrent();
+ void slotDeleteCurrentItem();
+ void slotDeleteCurrentItemPermanently();
+ void slotDeleteCurrentItemPermanentlyDirectly();
+ void slotTrashCurrentItemDirectly();
+
+ void slotChanged();
+ void slotUndoStateChanged(bool, bool, bool);
+ void slotUpdateItemInfo();
+
+ void slotContextMenu();
+ void slotRevert();
+
+ void slotAssignTag(int tagID);
+ void slotRemoveTag(int tagID);
+
+ void slotAssignRatingNoStar();
+ void slotAssignRatingOneStar();
+ void slotAssignRatingTwoStar();
+ void slotAssignRatingThreeStar();
+ void slotAssignRatingFourStar();
+ void slotAssignRatingFiveStar();
+ void slotAssignRating(int rating);
+
+ void slotFileMetadataChanged(const KURL &);
+ void slotChangeTheme(const TQString& theme);
+
+private:
+
+ ImageWindowPriv *d;
+
+ static ImageWindow *m_instance;
+};
+
+} // namespace Digikam
+
+#endif /* IMAGEWINDOW_H */
diff --git a/src/utilities/imageeditor/editor/savingcontextcontainer.h b/src/utilities/imageeditor/editor/savingcontextcontainer.h
new file mode 100644
index 00000000..c747a357
--- /dev/null
+++ b/src/utilities/imageeditor/editor/savingcontextcontainer.h
@@ -0,0 +1,89 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-20
+ * Description : image editor GUI saving context container
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+#ifndef SAVINGCONTEXTCONTAINER_H
+#define SAVINGCONTEXTCONTAINER_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <tdetempfile.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT SavingContextContainer
+{
+
+public:
+
+ SavingContextContainer()
+ {
+ savingState = SavingStateNone;
+ synchronizingState = NormalSaving;
+ saveTempFile = 0;
+ destinationExisted = false;
+ synchronousSavingResult = false;
+ abortingSaving = false;
+ }
+
+ enum SavingState
+ {
+ SavingStateNone,
+ SavingStateSave,
+ SavingStateSaveAs
+ };
+
+ enum SynchronizingState
+ {
+ NormalSaving,
+ SynchronousSaving
+ };
+
+ SavingState savingState;
+ SynchronizingState synchronizingState;
+ bool synchronousSavingResult;
+ bool destinationExisted;
+ bool abortingSaving;
+
+ TQString originalFormat;
+ TQString format;
+
+ KURL srcURL;
+ KURL destinationURL;
+
+ KTempFile *saveTempFile;
+};
+
+} // namespace Digikam
+
+#endif /* SAVINGCONTEXTCONTAINER_H */
diff --git a/src/utilities/imageeditor/rawimport/Makefile.am b/src/utilities/imageeditor/rawimport/Makefile.am
new file mode 100644
index 00000000..19071935
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/Makefile.am
@@ -0,0 +1,27 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = librawimport.la
+
+librawimport_la_SOURCES = rawpreview.cpp rawsettingsbox.cpp rawimport.cpp \
+ rawpostprocessing.cpp
+
+librawimport_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TDEPRINT)
+
+INCLUDES= -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_srcdir)/src/libs/histogram \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/levels \
+ -I$(top_srcdir)/src/libs/whitebalance \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/curves \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/widgets/iccprofiles \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/libs/themeengine \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
diff --git a/src/utilities/imageeditor/rawimport/rawimport.cpp b/src/utilities/imageeditor/rawimport/rawimport.cpp
new file mode 100644
index 00000000..a9254ce8
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawimport.cpp
@@ -0,0 +1,223 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : Raw import tool
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlayout.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <kcursor.h>
+#include <tdelocale.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "drawdecoding.h"
+#include "histogramwidget.h"
+#include "curveswidget.h"
+#include "imagehistogram.h"
+#include "rawsettingsbox.h"
+#include "rawpostprocessing.h"
+#include "editortooliface.h"
+#include "rawpreview.h"
+#include "rawimport.h"
+#include "rawimport.moc"
+
+namespace Digikam
+{
+
+class RawImportPriv
+{
+public:
+
+ RawImportPriv()
+ {
+ previewWidget = 0;
+ settingsBox = 0;
+ }
+
+ RawSettingsBox *settingsBox;
+
+ RawPreview *previewWidget;
+};
+
+RawImport::RawImport(const KURL& url, TQObject *parent)
+ : EditorToolThreaded(parent)
+{
+ d = new RawImportPriv;
+ d->previewWidget = new RawPreview(url, 0);
+ d->settingsBox = new RawSettingsBox(url, 0);
+
+ setToolName(i18n("Raw Import"));
+ setToolIcon(SmallIcon("kdcraw"));
+ setProgressMessage(i18n("Post Processing"));
+ setToolView(d->previewWidget);
+ setToolSettings(d->settingsBox);
+
+ init();
+}
+
+RawImport::~RawImport()
+{
+ delete d;
+}
+
+void RawImport::slotInit()
+{
+ EditorToolThreaded::slotInit();
+
+ // ---------------------------------------------------------------
+
+ connect(d->previewWidget, TQ_SIGNAL(signalLoadingStarted()),
+ this, TQ_SLOT(slotLoadingStarted()));
+
+ connect(d->previewWidget, TQ_SIGNAL(signalDemosaicedImage()),
+ this, TQ_SLOT(slotDemosaicedImage()));
+
+ connect(d->previewWidget, TQ_SIGNAL(signalLoadingStarted()),
+ this, TQ_SLOT(slotLoadingStarted()));
+
+ connect(d->previewWidget, TQ_SIGNAL(signalLoadingProgress(float)),
+ this, TQ_SLOT(slotLoadingProgress(float)));
+
+ connect(d->previewWidget, TQ_SIGNAL(signalLoadingFailed()),
+ this, TQ_SLOT(slotLoadingFailed()));
+
+ connect(d->settingsBox, TQ_SIGNAL(signalDemosaicingChanged()),
+ this, TQ_SLOT(slotDemosaicingChanged()));
+
+ connect(d->settingsBox, TQ_SIGNAL(signalPostProcessingChanged()),
+ this, TQ_SLOT(slotTimer()));
+
+ connect(d->settingsBox, TQ_SIGNAL(signalUpdatePreview()),
+ this, TQ_SLOT(slotUpdatePreview()));
+
+ connect(d->settingsBox, TQ_SIGNAL(signalAbortPreview()),
+ this, TQ_SLOT(slotAbort()));
+
+ // ---------------------------------------------------------------
+
+ setBusy(true);
+ slotUpdatePreview();
+}
+
+void RawImport::setBusy(bool val)
+{
+ if (val) d->previewWidget->setCursor(KCursor::waitCursor());
+ else d->previewWidget->unsetCursor();
+ d->settingsBox->setBusy(val);
+}
+
+DRawDecoding RawImport::rawDecodingSettings()
+{
+ return d->settingsBox->settings();
+}
+
+void RawImport::slotUpdatePreview()
+{
+ DRawDecoding settings = rawDecodingSettings();
+ // We will load an half size image to speed up preview computing.
+ settings.halfSizeColorImage = true;
+
+ d->previewWidget->setDecodingSettings(settings);
+}
+
+void RawImport::slotAbort()
+{
+ // If preview loading, don't play with threaded filter interface.
+ if (renderingMode() == EditorToolThreaded::NoneRendering)
+ {
+ d->previewWidget->cancelLoading();
+ d->settingsBox->histogram()->stopHistogramComputation();
+ EditorToolIface::editorToolIface()->setToolStopProgress();
+ setBusy(false);
+ return;
+ }
+
+ EditorToolThreaded::slotAbort();
+}
+
+void RawImport::slotLoadingStarted()
+{
+ d->settingsBox->enableUpdateBtn(false);
+ d->settingsBox->histogram()->setDataLoading();
+ d->settingsBox->curve()->setDataLoading();
+ EditorToolIface::editorToolIface()->setToolStartProgress(i18n("Raw Decoding"));
+ setBusy(true);
+}
+
+void RawImport::slotDemosaicedImage()
+{
+ d->settingsBox->setDemosaicedImage(d->previewWidget->demosaicedImage());
+ slotEffect();
+}
+
+void RawImport::prepareEffect()
+{
+ DImg postImg = d->previewWidget->demosaicedImage();
+ setFilter(dynamic_cast<DImgThreadedFilter*>(new RawPostProcessing(&postImg, this, rawDecodingSettings())));
+}
+
+void RawImport::putPreviewData()
+{
+ d->previewWidget->setPostProcessedImage(filter()->getTargetImage());
+ d->settingsBox->setPostProcessedImage(d->previewWidget->postProcessedImage());
+ EditorToolIface::editorToolIface()->setToolStopProgress();
+ setBusy(false);
+}
+
+void RawImport::slotLoadingFailed()
+{
+ d->settingsBox->histogram()->setLoadingFailed();
+ EditorToolIface::editorToolIface()->setToolStopProgress();
+ setBusy(false);
+}
+
+void RawImport::slotDemosaicingChanged()
+{
+ d->settingsBox->enableUpdateBtn(true);
+}
+
+void RawImport::slotLoadingProgress(float v)
+{
+ EditorToolIface::editorToolIface()->setToolProgress((int)(v*100));
+}
+
+void RawImport::slotOk()
+{
+ EditorTool::slotOk();
+}
+
+void RawImport::slotCancel()
+{
+ EditorTool::slotCancel();
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/imageeditor/rawimport/rawimport.h b/src/utilities/imageeditor/rawimport/rawimport.h
new file mode 100644
index 00000000..85255bc8
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawimport.h
@@ -0,0 +1,88 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-20
+ * Description : Raw import tool
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAWIMPORTDLG_H
+#define RAWIMPORTDLG_H
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "editortool.h"
+#include "dimg.h"
+#include "digikam_export.h"
+
+namespace KDcrawIface
+{
+class RawDecodingSettings;
+}
+
+namespace Digikam
+{
+
+class RawImportPriv;
+
+class DIGIKAM_EXPORT RawImport : public EditorToolThreaded
+{
+ TQ_OBJECT
+
+
+public:
+
+ RawImport(const KURL& url, TQObject *parent);
+ ~RawImport();
+
+ DRawDecoding rawDecodingSettings();
+
+private:
+
+ void setBusy(bool busy);
+ void prepareEffect();
+ void putPreviewData();
+
+private slots:
+
+ void slotInit();
+
+ void slotLoadingStarted();
+ void slotDemosaicedImage();
+ void slotLoadingFailed();
+ void slotLoadingProgress(float);
+
+ void slotUpdatePreview();
+ void slotAbort();
+
+ void slotDemosaicingChanged();
+
+ void slotOk();
+ void slotCancel();
+
+private:
+
+ RawImportPriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif // RAWIMPORTDLG_H
diff --git a/src/utilities/imageeditor/rawimport/rawpostprocessing.cpp b/src/utilities/imageeditor/rawimport/rawpostprocessing.cpp
new file mode 100644
index 00000000..d45dd5e4
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawpostprocessing.cpp
@@ -0,0 +1,137 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-13-08
+ * Description : Raw post processing corrections.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "imagecurves.h"
+#include "imagelevels.h"
+#include "bcgmodifier.h"
+#include "whitebalance.h"
+#include "dimgimagefilters.h"
+#include "rawpostprocessing.h"
+
+namespace Digikam
+{
+
+RawPostProcessing::RawPostProcessing(DImg *orgImage, TQObject *parent, const DRawDecoding& settings)
+ : DImgThreadedFilter(orgImage, parent, "RawPostProcessing")
+{
+ m_customRawSettings = settings;
+ initFilter();
+}
+
+RawPostProcessing::RawPostProcessing(DImgThreadedFilter *parentFilter,
+ const DImg &orgImage, const DImg &destImage,
+ int progressBegin, int progressEnd, const DRawDecoding& settings)
+ : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd,
+ parentFilter->filterName() + ": RawPostProcessing")
+{
+ m_customRawSettings = settings;
+ filterImage();
+}
+
+void RawPostProcessing::filterImage()
+{
+ rawPostProcessing();
+}
+
+void RawPostProcessing::rawPostProcessing()
+{
+ if (!m_orgImage.bits() || !m_orgImage.width() || !m_orgImage.height())
+ {
+ DWarning() << ("RawPostProcessing::rawPostProcessing: no image m_orgImage.bits() available!")
+ << endl;
+ return;
+ }
+
+ if (!m_customRawSettings.postProcessingSettingsIsDirty())
+ {
+ m_destImage = m_orgImage;
+ return;
+ }
+
+ postProgress(15);
+
+ if (m_customRawSettings.exposureComp != 0.0 || m_customRawSettings.saturation != 1.0)
+ {
+ WhiteBalance wb(m_orgImage.sixteenBit());
+ wb.whiteBalance(m_orgImage.bits(), m_orgImage.width(), m_orgImage.height(), m_orgImage.sixteenBit(),
+ 0.0, // black
+ m_customRawSettings.exposureComp, // exposure
+ 6500.0, // temperature (neutral)
+ 1.0, // green
+ 0.5, // dark
+ 1.0, // gamma
+ m_customRawSettings.saturation); // saturation
+ }
+ postProgress(30);
+
+ if (m_customRawSettings.lightness != 0.0 || m_customRawSettings.contrast != 1.0 || m_customRawSettings.gamma != 1.0)
+ {
+ BCGModifier bcg;
+ bcg.setBrightness(m_customRawSettings.lightness);
+ bcg.setContrast(m_customRawSettings.contrast);
+ bcg.setGamma(m_customRawSettings.gamma);
+ bcg.applyBCG(m_orgImage.bits(), m_orgImage.width(), m_orgImage.height(), m_orgImage.sixteenBit());
+ }
+ postProgress(45);
+
+ if (!m_customRawSettings.curveAdjust.isEmpty())
+ {
+ DImg tmp(m_orgImage.width(), m_orgImage.height(), m_orgImage.sixteenBit());
+ ImageCurves curves(m_orgImage.sixteenBit());
+ curves.setCurvePoints(ImageHistogram::ValueChannel, m_customRawSettings.curveAdjust);
+ curves.curvesCalculateCurve(ImageHistogram::ValueChannel);
+ curves.curvesLutSetup(ImageHistogram::AlphaChannel);
+ curves.curvesLutProcess(m_orgImage.bits(), tmp.bits(), m_orgImage.width(), m_orgImage.height());
+ memcpy(m_orgImage.bits(), tmp.bits(), tmp.numBytes());
+ }
+ postProgress(60);
+
+ if (!m_customRawSettings.levelsAdjust.isEmpty())
+ {
+ DImg tmp(m_orgImage.width(), m_orgImage.height(), m_orgImage.sixteenBit());
+ ImageLevels levels(m_orgImage.sixteenBit());
+ int j=0;
+ for (int i = 0 ; i < 4; i++)
+ {
+ levels.setLevelLowInputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ levels.setLevelHighInputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ levels.setLevelLowOutputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ levels.setLevelHighOutputValue(i, m_customRawSettings.levelsAdjust[j++]);
+ }
+
+ levels.levelsLutSetup(ImageHistogram::AlphaChannel);
+ levels.levelsLutProcess(m_orgImage.bits(), tmp.bits(), m_orgImage.width(), m_orgImage.height());
+ memcpy(m_orgImage.bits(), tmp.bits(), tmp.numBytes());
+ }
+ postProgress(75);
+
+ m_destImage = m_orgImage;
+
+ postProgress(100);
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/imageeditor/rawimport/rawpostprocessing.h b/src/utilities/imageeditor/rawimport/rawpostprocessing.h
new file mode 100644
index 00000000..3b3c7761
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawpostprocessing.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-13-08
+ * Description : Raw post processing corrections.
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAWPOSTPROCESSING_H
+#define RAWPOSTPROCESSING_H
+
+// Digikam includes.
+
+#include "digikam_export.h"
+
+// Local includes.
+
+#include "dimgthreadedfilter.h"
+
+namespace Digikam
+{
+
+class DIGIKAM_EXPORT RawPostProcessing : public DImgThreadedFilter
+{
+
+public:
+
+ RawPostProcessing(DImg *orgImage, TQObject *parent=0, const DRawDecoding& settings=DRawDecoding());
+
+ // Constructor for slave mode: execute immediately in current thread with specified master filter
+ RawPostProcessing(DImgThreadedFilter *parentFilter, const DImg &orgImage, const DImg &destImage,
+ int progressBegin=0, int progressEnd=100, const DRawDecoding& settings=DRawDecoding());
+
+ ~RawPostProcessing(){};
+
+private:
+
+ virtual void filterImage();
+ void rawPostProcessing();
+
+private:
+
+ DRawDecoding m_customRawSettings;
+};
+
+} // NameSpace Digikam
+
+#endif /* RAWPOSTPROCESSING_H */
diff --git a/src/utilities/imageeditor/rawimport/rawpreview.cpp b/src/utilities/imageeditor/rawimport/rawpreview.cpp
new file mode 100644
index 00000000..3207ba14
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawpreview.cpp
@@ -0,0 +1,336 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-04
+ * Description : RAW postProcessedImg widget.
+ *
+ * Copyright (C) 2008 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqpainter.h>
+#include <tqtoolbutton.h>
+#include <tqtooltip.h>
+#include <tqpixmap.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kcursor.h>
+#include <kdatetbl.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "paniconwidget.h"
+#include "managedloadsavethread.h"
+#include "loadingdescription.h"
+#include "themeengine.h"
+#include "rawpreview.h"
+#include "rawpreview.moc"
+
+namespace Digikam
+{
+
+class RawPreviewPriv
+{
+public:
+
+ RawPreviewPriv()
+ {
+ panIconPopup = 0;
+ panIconWidget = 0;
+ cornerButton = 0;
+ thread = 0;
+ url = 0;
+ currentFitWindowZoom = 0;
+ }
+
+ double currentFitWindowZoom;
+
+ TQToolButton *cornerButton;
+
+ TDEPopupFrame *panIconPopup;
+
+ KURL url;
+
+ PanIconWidget *panIconWidget;
+
+ DImg demosaicedImg;
+
+ DImg postProcessedImg;
+
+ DRawDecoding settings;
+
+ ManagedLoadSaveThread *thread;
+
+ LoadingDescription loadingDesc;
+};
+
+RawPreview::RawPreview(const KURL& url, TQWidget *parent)
+ : PreviewWidget(parent)
+{
+ d = new RawPreviewPriv;
+ d->thread = new ManagedLoadSaveThread;
+ d->url = url;
+
+ setMinimumWidth(500);
+ setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);
+
+ d->cornerButton = new TQToolButton(this);
+ d->cornerButton->setIconSet(SmallIcon("move"));
+ d->cornerButton->hide();
+ TQToolTip::add(d->cornerButton, i18n("Pan the image to a region"));
+ setCornerWidget(d->cornerButton);
+
+ // ------------------------------------------------------------
+
+ connect(d->thread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription&, const DImg&)),
+ this, TQ_SLOT(slotImageLoaded(const LoadingDescription&, const DImg&)));
+
+ connect(d->thread, TQ_SIGNAL(signalLoadingProgress(const LoadingDescription&, float)),
+ this, TQ_SLOT(slotLoadingProgress(const LoadingDescription&, float)));
+
+ connect(d->cornerButton, TQ_SIGNAL(pressed()),
+ this, TQ_SLOT(slotCornerButtonPressed()));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ // ------------------------------------------------------------
+
+ slotReset();
+}
+
+RawPreview::~RawPreview()
+{
+ delete d;
+}
+
+void RawPreview::setPostProcessedImage(const DImg& image)
+{
+ d->postProcessedImg = image;
+
+ updateZoomAndSize(false);
+
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+}
+
+DImg& RawPreview::postProcessedImage() const
+{
+ return d->postProcessedImg;
+}
+
+DImg& RawPreview::demosaicedImage() const
+{
+ return d->demosaicedImg;
+}
+
+void RawPreview::setDecodingSettings(const DRawDecoding& settings)
+{
+ // Save post processing settings.
+ d->settings = settings;
+
+ // All post processing settings will be used after demosaicing.
+ DRawDecoding demosaisedSettings = settings;
+ demosaisedSettings.resetPostProcessingSettings();
+
+ d->loadingDesc = LoadingDescription(d->url.path(), demosaisedSettings);
+ d->thread->load(d->loadingDesc, ManagedLoadSaveThread::LoadingPolicyFirstRemovePrevious);
+ emit signalLoadingStarted();
+}
+
+void RawPreview::cancelLoading()
+{
+ d->thread->stopLoading(d->loadingDesc);
+}
+
+void RawPreview::slotLoadingProgress(const LoadingDescription& description, float progress)
+{
+ if (description.filePath != d->loadingDesc.filePath)
+ return;
+
+ emit signalLoadingProgress(progress);
+}
+
+void RawPreview::slotImageLoaded(const LoadingDescription& description, const DImg& image)
+{
+ if (description.filePath != d->loadingDesc.filePath)
+ return;
+
+ if (image.isNull())
+ {
+ TQPixmap pix(visibleWidth(), visibleHeight());
+ pix.fill(ThemeEngine::instance()->baseColor());
+ TQPainter p(&pix);
+ p.setPen(TQPen(ThemeEngine::instance()->textRegColor()));
+ p.drawText(0, 0, pix.width(), pix.height(),
+ TQt::AlignCenter|TQt::WordBreak,
+ i18n("Cannot decode RAW image for\n\"%1\"")
+ .arg(TQFileInfo(d->loadingDesc.filePath).fileName()));
+ p.end();
+ // three copies - but the image is small
+ setPostProcessedImage(DImg(pix.convertToImage()));
+ emit signalLoadingFailed();
+ }
+ else
+ {
+ d->demosaicedImg = image;
+ emit signalDemosaicedImage();
+ // NOTE: we will apply all Raw post processing corrections into RawImport class.
+ }
+}
+
+void RawPreview::slotThemeChanged()
+{
+ setBackgroundColor(ThemeEngine::instance()->baseColor());
+}
+
+void RawPreview::slotCornerButtonPressed()
+{
+ if (d->panIconPopup)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ }
+
+ d->panIconPopup = new TDEPopupFrame(this);
+ PanIconWidget *pan = new PanIconWidget(d->panIconPopup);
+ pan->setImage(180, 120, postProcessedImage());
+ d->panIconPopup->setMainWidget(pan);
+
+ TQRect r((int)(contentsX() / zoomFactor()), (int)(contentsY() / zoomFactor()),
+ (int)(visibleWidth() / zoomFactor()), (int)(visibleHeight() / zoomFactor()));
+ pan->setRegionSelection(r);
+ pan->setMouseFocus();
+
+ connect(pan, TQ_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
+ this, TQ_SLOT(slotPanIconSelectionMoved(const TQRect&, bool)));
+
+ connect(pan, TQ_SIGNAL(signalHiden()),
+ this, TQ_SLOT(slotPanIconHiden()));
+
+ TQPoint g = mapToGlobal(viewport()->pos());
+ g.setX(g.x()+ viewport()->size().width());
+ g.setY(g.y()+ viewport()->size().height());
+ d->panIconPopup->popup(TQPoint(g.x() - d->panIconPopup->width(),
+ g.y() - d->panIconPopup->height()));
+
+ pan->setCursorToLocalRegionSelectionCenter();
+}
+
+void RawPreview::slotPanIconHiden()
+{
+ d->cornerButton->blockSignals(true);
+ d->cornerButton->animateClick();
+ d->cornerButton->blockSignals(false);
+}
+
+void RawPreview::slotPanIconSelectionMoved(const TQRect& r, bool b)
+{
+ setContentsPos((int)(r.x()*zoomFactor()), (int)(r.y()*zoomFactor()));
+
+ if (b)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ slotPanIconHiden();
+ }
+}
+
+void RawPreview::zoomFactorChanged(double zoom)
+{
+ updateScrollBars();
+
+ if (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())
+ d->cornerButton->show();
+ else
+ d->cornerButton->hide();
+
+ PreviewWidget::zoomFactorChanged(zoom);
+}
+
+void RawPreview::resizeEvent(TQResizeEvent* e)
+{
+ if (!e) return;
+
+ TQScrollView::resizeEvent(e);
+
+ if (!d->loadingDesc.filePath.isEmpty())
+ d->cornerButton->hide();
+
+ updateZoomAndSize(false);
+}
+
+void RawPreview::updateZoomAndSize(bool alwaysFitToWindow)
+{
+ // Set zoom for fit-in-window as minimum, but dont scale up images
+ // that are smaller than the available space, only scale down.
+ double zoom = calcAutoZoomFactor(ZoomInOnly);
+ setZoomMin(zoom);
+ setZoomMax(zoom*12.0);
+
+ // Is currently the zoom factor set to fit to window? Then set it again to fit the new size.
+ if (zoomFactor() < zoom || alwaysFitToWindow || zoomFactor() == d->currentFitWindowZoom)
+ {
+ setZoomFactor(zoom);
+ }
+
+ // store which zoom factor means it is fit to window
+ d->currentFitWindowZoom = zoom;
+
+ updateContentsSize();
+}
+
+int RawPreview::previewWidth()
+{
+ return d->postProcessedImg.width();
+}
+
+int RawPreview::previewHeight()
+{
+ return d->postProcessedImg.height();
+}
+
+bool RawPreview::previewIsNull()
+{
+ return d->postProcessedImg.isNull();
+}
+
+void RawPreview::resetPreview()
+{
+ d->postProcessedImg = DImg();
+ d->loadingDesc = LoadingDescription();
+
+ updateZoomAndSize(false);
+}
+
+void RawPreview::paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh)
+{
+ DImg img = d->postProcessedImg.smoothScaleSection(sx, sy, sw, sh, tileSize(), tileSize());
+ TQPixmap pix2 = img.convertToPixmap();
+ bitBlt(pix, 0, 0, &pix2, 0, 0);
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/imageeditor/rawimport/rawpreview.h b/src/utilities/imageeditor/rawimport/rawpreview.h
new file mode 100644
index 00000000..6c7e5379
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawpreview.h
@@ -0,0 +1,108 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-04
+ * Description : RAW preview widget.
+ *
+ * Copyright (C) 2008 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAWPREVIEW_H
+#define RAWPREVIEW_H
+
+// TQt includes.
+
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "previewwidget.h"
+#include "digikam_export.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+
+class LoadingDescription;
+class RawPreviewPriv;
+
+class DIGIKAM_EXPORT RawPreview : public PreviewWidget
+{
+
+TQ_OBJECT
+
+
+public:
+
+ RawPreview(const KURL& url, TQWidget *parent);
+ ~RawPreview();
+
+ DImg& demosaicedImage() const;
+ DImg& postProcessedImage() const;
+
+ void setDecodingSettings(const DRawDecoding& settings);
+ void setPostProcessedImage(const DImg& image);
+
+ void cancelLoading();
+
+signals:
+
+ void signalLoadingStarted();
+ void signalLoadingProgress(float);
+ void signalLoadingFailed();
+ void signalDemosaicedImage();
+ void signalPostProcessedImage();
+
+protected:
+
+ void resizeEvent(TQResizeEvent* e);
+
+private slots:
+
+ void slotLoadingProgress(const LoadingDescription& description, float progress);
+ void slotImageLoaded(const LoadingDescription& description, const DImg &image);
+ void slotThemeChanged();
+ void slotCornerButtonPressed();
+ void slotPanIconSelectionMoved(const TQRect&, bool);
+ void slotPanIconHiden();
+
+private:
+
+ void setdemosaicedImg(const DImg& image);
+ void postProcessing(const DRawDecoding& settings);
+ int previewWidth();
+ int previewHeight();
+ bool previewIsNull();
+ void resetPreview();
+ void zoomFactorChanged(double zoom);
+ void updateZoomAndSize(bool alwaysFitToWindow);
+ inline void paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh);
+
+private:
+
+ RawPreviewPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* RAWPREVIEW_H */
diff --git a/src/utilities/imageeditor/rawimport/rawsettingsbox.cpp b/src/utilities/imageeditor/rawimport/rawsettingsbox.cpp
new file mode 100644
index 00000000..bf0ee67e
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawsettingsbox.cpp
@@ -0,0 +1,741 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-11
+ * Description : Raw import settings box
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqlayout.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqhbuttongroup.h>
+#include <tqcombobox.h>
+#include <tqlabel.h>
+#include <tqvbox.h>
+#include <tqtoolbutton.h>
+#include <tqtoolbox.h>
+#include <tqpushbutton.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <ktabwidget.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeconfig.h>
+#include <kstandarddirs.h>
+#include <tdefiledialog.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/dcrawsettingswidget.h>
+#include <libkdcraw/rnuminput.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagedialog.h"
+#include "imagehistogram.h"
+#include "imagecurves.h"
+#include "iccpreviewwidget.h"
+#include "histogramwidget.h"
+#include "curveswidget.h"
+#include "colorgradientwidget.h"
+#include "rawsettingsbox.h"
+#include "rawsettingsbox.moc"
+
+using namespace KDcrawIface;
+
+namespace Digikam
+{
+
+class RawSettingsBoxPriv
+{
+public:
+
+ enum ColorChannel
+ {
+ LuminosityChannel=0,
+ RedChannel,
+ GreenChannel,
+ BlueChannel,
+ ColorChannels
+ };
+
+ enum AllColorsColorType
+ {
+ AllColorsRed=0,
+ AllColorsGreen,
+ AllColorsBlue
+ };
+
+public:
+
+ RawSettingsBoxPriv()
+ {
+ channelCB = 0;
+ colorsCB = 0;
+ scaleBG = 0;
+ hGradient = 0;
+ histogramWidget = 0;
+ infoBox = 0;
+ advExposureBox = 0;
+ gammaLabel = 0;
+ gammaInput = 0;
+ saturationLabel = 0;
+ saturationInput = 0;
+ fineExposureLabel = 0;
+ fineExposureInput = 0;
+ contrastInput = 0;
+ contrastLabel = 0;
+ curveBox = 0;
+ curveWidget = 0;
+ resetCurveBtn = 0;
+ decodingSettingsBox = 0;
+ postProcessSettingsBox = 0;
+ tabView = 0;
+ abortBtn = 0;
+ updateBtn = 0;
+ rawdecodingBox = 0;
+ brightnessLabel = 0;
+ brightnessInput = 0;
+ }
+
+ TQWidget *advExposureBox;
+ TQWidget *curveBox;
+ TQWidget *rawdecodingBox;
+
+ TQComboBox *channelCB;
+ TQComboBox *colorsCB;
+
+ TQLabel *brightnessLabel;
+ TQLabel *contrastLabel;
+ TQLabel *gammaLabel;
+ TQLabel *saturationLabel;
+ TQLabel *fineExposureLabel;
+
+ TQHButtonGroup *scaleBG;
+
+ TQPushButton *abortBtn;
+ TQPushButton *updateBtn;
+
+ TQToolButton *resetCurveBtn;
+
+ TQToolBox *postProcessSettingsBox;
+
+ KTabWidget *tabView;
+
+ ColorGradientWidget *hGradient;
+
+ CurvesWidget *curveWidget;
+
+ HistogramWidget *histogramWidget;
+
+ ImageDialogPreview *infoBox;
+
+ RIntNumInput *contrastInput;
+ RIntNumInput *brightnessInput;
+
+ RDoubleNumInput *gammaInput;
+ RDoubleNumInput *saturationInput;
+ RDoubleNumInput *fineExposureInput;
+
+ DcrawSettingsWidget *decodingSettingsBox;
+};
+
+RawSettingsBox::RawSettingsBox(const KURL& url, TQWidget *parent)
+ : EditorToolSettings(Default|Ok|Cancel, NoTool, parent)
+{
+ d = new RawSettingsBoxPriv;
+
+ // ---------------------------------------------------------------
+
+ TQGridLayout* gridSettings = new TQGridLayout(plainPage(), 5, 4);
+
+ TQLabel *label1 = new TQLabel(i18n("Channel:"), plainPage());
+ label1->setAlignment( TQt::AlignRight | TQt::AlignVCenter );
+ d->channelCB = new TQComboBox(false, plainPage());
+ d->channelCB->insertItem( i18n("Luminosity") );
+ d->channelCB->insertItem( i18n("Red") );
+ d->channelCB->insertItem( i18n("Green") );
+ d->channelCB->insertItem( i18n("Blue") );
+ d->channelCB->insertItem( i18n("Colors") );
+ TQWhatsThis::add(d->channelCB, i18n("<p>Select the histogram channel to display here:<p>"
+ "<b>Luminosity</b>: display the image's luminosity values.<p>"
+ "<b>Red</b>: display the red image-channel values.<p>"
+ "<b>Green</b>: display the green image-channel values.<p>"
+ "<b>Blue</b>: display the blue image-channel values.<p>"
+ "<b>Colors</b>: Display all color channel values at the same time."));
+
+ d->scaleBG = new TQHButtonGroup(plainPage());
+ d->scaleBG->setExclusive(true);
+ d->scaleBG->setFrameShape(TQFrame::NoFrame);
+ d->scaleBG->setInsideMargin( 0 );
+ TQWhatsThis::add(d->scaleBG, i18n("<p>Select the histogram scale here.<p>"
+ "If the image's maximal counts are small, you can use the linear scale.<p>"
+ "Logarithmic scale can be used when the maximal counts are big; "
+ "if it is used, all values (small and large) will be visible on the graph."));
+
+ TQPushButton *linHistoButton = new TQPushButton( d->scaleBG );
+ TQToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
+ d->scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
+ linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) );
+ linHistoButton->setToggleButton(true);
+
+ TQPushButton *logHistoButton = new TQPushButton( d->scaleBG );
+ TQToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
+ d->scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram);
+ TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
+ logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) );
+ logHistoButton->setToggleButton(true);
+
+ TQLabel *label10 = new TQLabel(i18n("Colors:"), plainPage());
+ label10->setAlignment( TQt::AlignRight | TQt::AlignVCenter );
+ d->colorsCB = new TQComboBox(false, plainPage());
+ d->colorsCB->insertItem( i18n("Red") );
+ d->colorsCB->insertItem( i18n("Green") );
+ d->colorsCB->insertItem( i18n("Blue") );
+ d->colorsCB->setEnabled( false );
+ TQWhatsThis::add( d->colorsCB, i18n("<p>Select the main color displayed with Colors Channel mode here:<p>"
+ "<b>Red</b>: Draw the red image channel in the foreground.<p>"
+ "<b>Green</b>: Draw the green image channel in the foreground.<p>"
+ "<b>Blue</b>: Draw the blue image channel in the foreground.<p>"));
+
+ // ---------------------------------------------------------------
+
+ TQVBox *histoBox = new TQVBox(plainPage());
+ d->histogramWidget = new HistogramWidget(256, 140, histoBox, false, true, true);
+ TQWhatsThis::add(d->histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
+ "of the selected image channel. This one is re-computed at any "
+ "settings changes."));
+ TQLabel *space = new TQLabel(histoBox);
+ space->setFixedHeight(1);
+ d->hGradient = new ColorGradientWidget( ColorGradientWidget::Horizontal, 10, histoBox );
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ // ---------------------------------------------------------------
+
+ d->tabView = new KTabWidget(plainPage());
+ d->rawdecodingBox = new TQWidget(d->tabView);
+ TQGridLayout* rawGrid = new TQGridLayout(d->rawdecodingBox, 1, 2);
+ d->decodingSettingsBox = new DcrawSettingsWidget(d->rawdecodingBox, true, true, false);
+
+ KFileDialog *inputDlg = d->decodingSettingsBox->inputProfileUrlEdit()->fileDialog();
+ inputDlg->setPreviewWidget(new ICCPreviewWidget(inputDlg));
+
+ KFileDialog *outputDlg = d->decodingSettingsBox->outputProfileUrlEdit()->fileDialog();
+ outputDlg->setPreviewWidget(new ICCPreviewWidget(outputDlg));
+
+ d->abortBtn = new TQPushButton(d->rawdecodingBox);
+ d->abortBtn->setText(i18n("Abort"));
+ d->abortBtn->setIconSet(SmallIconSet("process-stop"));
+ d->abortBtn->setEnabled(false);
+ TQToolTip::add(d->abortBtn, i18n("Abort the current Raw image preview."));
+
+ d->updateBtn = new TQPushButton(d->rawdecodingBox);
+ d->updateBtn->setText(i18n("Update"));
+ d->updateBtn->setIconSet(SmallIconSet("reload_page"));
+ d->updateBtn->setEnabled(false);
+ TQToolTip::add(d->updateBtn, i18n("Generate a Raw image preview using current settings."));
+
+ rawGrid->addMultiCellWidget(d->decodingSettingsBox, 0, 0, 0, 2);
+ rawGrid->addMultiCellWidget(d->abortBtn, 1, 1, 0, 0);
+ rawGrid->addMultiCellWidget(d->updateBtn, 1, 1, 2, 2);
+ rawGrid->setColStretch(1, 10);
+ rawGrid->setSpacing(spacingHint());
+ rawGrid->setMargin(spacingHint());
+
+ // ---------------------------------------------------------------
+
+ d->postProcessSettingsBox = new TQToolBox(d->tabView);
+ d->infoBox = new ImageDialogPreview(d->postProcessSettingsBox);
+ d->infoBox->showPreview(url);
+
+ // ---------------------------------------------------------------
+
+ d->advExposureBox = new TQWidget(d->postProcessSettingsBox);
+ TQGridLayout* advExposureLayout = new TQGridLayout(d->advExposureBox, 5, 2);
+
+ d->brightnessLabel = new TQLabel(i18n("Brightness:"), d->advExposureBox);
+ d->brightnessInput = new RIntNumInput(d->advExposureBox);
+ d->brightnessInput->setRange(-100, 100, 1);
+ d->brightnessInput->setDefaultValue(0);
+ TQWhatsThis::add(d->brightnessInput->input(), i18n("<p>Set here the brightness adjustment of the image."));
+
+ d->contrastLabel = new TQLabel(i18n("Contrast:"), d->advExposureBox);
+ d->contrastInput = new RIntNumInput(d->advExposureBox);
+ d->contrastInput->setRange(-100, 100, 1);
+ d->contrastInput->setDefaultValue(0);
+ TQWhatsThis::add(d->contrastInput->input(), i18n("<p>Set here the contrast adjustment of the image."));
+
+ d->gammaLabel = new TQLabel(i18n("Gamma:"), d->advExposureBox);
+ d->gammaInput = new RDoubleNumInput(d->advExposureBox);
+ d->gammaInput->setPrecision(2);
+ d->gammaInput->setRange(0.1, 3.0, 0.01);
+ d->gammaInput->setDefaultValue(1.0);
+ TQWhatsThis::add(d->gammaInput->input(), i18n("Set here the gamma adjustement of the image"));
+
+ d->saturationLabel = new TQLabel(i18n("Saturation:"), d->advExposureBox);
+ d->saturationInput = new RDoubleNumInput(d->advExposureBox);
+ d->saturationInput->setPrecision(2);
+ d->saturationInput->setRange(0.0, 2.0, 0.01);
+ d->saturationInput->setDefaultValue(1.0);
+ TQWhatsThis::add(d->saturationInput->input(), i18n("<p>Set here the color saturation correction."));
+
+ d->fineExposureLabel = new TQLabel(i18n("Exposure (E.V):"), d->advExposureBox);
+ d->fineExposureInput = new RDoubleNumInput(d->advExposureBox);
+ d->fineExposureInput->setPrecision(2);
+ d->fineExposureInput->setRange(-3.0, 3.0, 0.1);
+ d->fineExposureInput->setDefaultValue(0.0);
+ TQWhatsThis::add(d->fineExposureInput->input(), i18n("<p>This value in E.V will be used to perform "
+ "an exposure compensation of the image."));
+
+ advExposureLayout->addMultiCellWidget(d->brightnessLabel, 0, 0, 0, 0);
+ advExposureLayout->addMultiCellWidget(d->brightnessInput, 0, 0, 1, 2);
+ advExposureLayout->addMultiCellWidget(d->contrastLabel, 1, 1, 0, 0);
+ advExposureLayout->addMultiCellWidget(d->contrastInput, 1, 1, 1, 2);
+ advExposureLayout->addMultiCellWidget(d->gammaLabel, 2, 2, 0, 0);
+ advExposureLayout->addMultiCellWidget(d->gammaInput, 2, 2, 1, 2);
+ advExposureLayout->addMultiCellWidget(d->saturationLabel, 3, 3, 0, 0);
+ advExposureLayout->addMultiCellWidget(d->saturationInput, 3, 3, 1, 2);
+ advExposureLayout->addMultiCellWidget(d->fineExposureLabel, 4, 4, 0, 0);
+ advExposureLayout->addMultiCellWidget(d->fineExposureInput, 4, 4, 1, 2);
+ advExposureLayout->setRowStretch(5, 10);
+ advExposureLayout->setSpacing(0);
+ advExposureLayout->setMargin(spacingHint());
+
+ // ---------------------------------------------------------------
+
+ d->curveBox = new TQWidget(d->postProcessSettingsBox);
+ TQGridLayout* curveLayout = new TQGridLayout(d->curveBox, 3, 2);
+
+ ColorGradientWidget* vGradient = new ColorGradientWidget(ColorGradientWidget::Vertical, 10, d->curveBox);
+ vGradient->setColors( TQColor( "white" ), TQColor( "black" ) );
+
+ TQLabel *spacev = new TQLabel(d->curveBox);
+ spacev->setFixedWidth(1);
+
+ d->curveWidget = new CurvesWidget(256, 192, d->curveBox);
+ TQWhatsThis::add(d->curveWidget, i18n("<p>This is the curve adjustment of the image luminosity"));
+
+ d->resetCurveBtn = new TQToolButton(d->curveBox);
+ d->resetCurveBtn->setFixedSize(11, 11);
+ d->resetCurveBtn->setIconSet(SmallIconSet("reload_page", 8));
+ d->resetCurveBtn->setFocusPolicy(TQWidget::NoFocus);
+ d->resetCurveBtn->setAutoRaise(true);
+ TQToolTip::add(d->resetCurveBtn, i18n("Reset curve to linear"));
+
+ TQLabel *spaceh = new TQLabel(d->curveBox);
+ spaceh->setFixedHeight(1);
+
+ ColorGradientWidget *hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, d->curveBox);
+ hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+
+ curveLayout->addMultiCellWidget(vGradient, 0, 0, 0, 0);
+ curveLayout->addMultiCellWidget(spacev, 0, 0, 1, 1);
+ curveLayout->addMultiCellWidget(d->curveWidget, 0, 0, 2, 2);
+ curveLayout->addMultiCellWidget(spaceh, 1, 1, 2, 2);
+ curveLayout->addMultiCellWidget(d->resetCurveBtn, 1, 2, 0, 1);
+ curveLayout->addMultiCellWidget(hGradient, 2, 2, 2, 2);
+ curveLayout->setRowStretch(3, 10);
+ curveLayout->setSpacing(0);
+ curveLayout->setMargin(spacingHint());
+
+ // ---------------------------------------------------------------
+
+ d->postProcessSettingsBox->addItem(d->advExposureBox, i18n("Exposure"));
+ d->postProcessSettingsBox->addItem(d->curveBox, i18n("Luminosity Curve"));
+ d->postProcessSettingsBox->setItemIconSet(0, SmallIconSet("contrast"));
+ d->postProcessSettingsBox->setItemIconSet(1, SmallIconSet("adjustcurves"));
+
+ d->decodingSettingsBox->setItemIconSet(DcrawSettingsWidget::DEMOSAICING, SmallIconSet("kdcraw"));
+ d->decodingSettingsBox->setItemIconSet(DcrawSettingsWidget::WHITEBALANCE, SmallIconSet("whitebalance"));
+ d->decodingSettingsBox->setItemIconSet(DcrawSettingsWidget::CORRECTIONS, SmallIconSet("lensdistortion"));
+ d->decodingSettingsBox->setItemIconSet(DcrawSettingsWidget::COLORMANAGEMENT, SmallIconSet("colormanagement"));
+ d->decodingSettingsBox->updateMinimumWidth();
+
+ d->tabView->insertTab(d->rawdecodingBox, i18n("Raw Decoding"), 0);
+ d->tabView->insertTab(d->postProcessSettingsBox, i18n("Post Processing"), 1);
+ d->tabView->insertTab(d->infoBox, i18n("Info"), 2);
+
+ // ---------------------------------------------------------------
+
+ button(Default)->setText(i18n("Reset"));
+ button(Default)->setIconSet(SmallIconSet("reload_page"));
+ TQToolTip::add(button(Default), i18n("<p>Reset all settings to default values."));
+
+ button(Ok)->setText(i18n("Import"));
+ button(Ok)->setIconSet(SmallIconSet("ok"));
+ TQToolTip::add(button(Ok), i18n("<p>Import image to editor using current settings."));
+
+ button(Cancel)->setText(i18n("Use Default"));
+ button(Cancel)->setIconSet(SmallIconSet("go-home"));
+ TQToolTip::add(button(Cancel), i18n("<p>Use general Raw decoding settings to load this image in editor."));
+
+ // ---------------------------------------------------------------
+
+ gridSettings->addMultiCellWidget(label1, 0, 0, 0, 0);
+ gridSettings->addMultiCellWidget(d->channelCB, 0, 0, 1, 1);
+ gridSettings->addMultiCellWidget(d->scaleBG, 0, 0, 4, 4);
+ gridSettings->addMultiCellWidget(label10, 1, 1, 0, 0);
+ gridSettings->addMultiCellWidget(d->colorsCB, 1, 1, 1, 1);
+ gridSettings->addMultiCellWidget(histoBox, 2, 3, 0, 4);
+ gridSettings->addMultiCellWidget(d->tabView, 4, 4, 0, 4);
+ gridSettings->setRowStretch(5, 10);
+ gridSettings->setColStretch(2, 10);
+ gridSettings->setSpacing(spacingHint());
+ gridSettings->setMargin(0);
+
+ // ---------------------------------------------------------------
+
+ connect(d->channelCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotChannelChanged(int)));
+
+ connect(d->scaleBG, TQ_SIGNAL(released(int)),
+ this, TQ_SLOT(slotScaleChanged(int)));
+
+ connect(d->colorsCB, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotColorsChanged(int)));
+
+ connect(d->resetCurveBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotResetCurve()));
+
+ connect(d->updateBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalUpdatePreview()));
+
+ connect(d->abortBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalAbortPreview()));
+
+ connect(d->decodingSettingsBox, TQ_SIGNAL(signalSettingsChanged()),
+ this, TQ_SIGNAL(signalDemosaicingChanged()));
+
+ connect(d->curveWidget, TQ_SIGNAL(signalCurvesChanged()),
+ this, TQ_SIGNAL(signalPostProcessingChanged()));
+
+ connect(d->brightnessInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SIGNAL(signalPostProcessingChanged()));
+
+ connect(d->contrastInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SIGNAL(signalPostProcessingChanged()));
+
+ connect(d->gammaInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SIGNAL(signalPostProcessingChanged()));
+
+ connect(d->saturationInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SIGNAL(signalPostProcessingChanged()));
+
+ connect(d->fineExposureInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SIGNAL(signalPostProcessingChanged()));
+}
+
+RawSettingsBox::~RawSettingsBox()
+{
+ delete d->curveWidget;
+ delete d;
+}
+
+void RawSettingsBox::enableUpdateBtn(bool b)
+{
+ d->updateBtn->setEnabled(b);
+}
+
+void RawSettingsBox::setBusy(bool b)
+{
+ d->decodingSettingsBox->setEnabled(!b);
+ d->abortBtn->setEnabled(b);
+}
+
+void RawSettingsBox::setDemosaicedImage(DImg& img)
+{
+ d->curveWidget->stopHistogramComputation();
+ d->curveWidget->updateData(img.bits(), img.width(), img.height(), img.sixteenBit());
+}
+
+void RawSettingsBox::setPostProcessedImage(DImg& img)
+{
+ d->histogramWidget->stopHistogramComputation();
+ d->histogramWidget->updateData(img.bits(), img.width(), img.height(), img.sixteenBit());
+}
+
+void RawSettingsBox::resetSettings()
+{
+ d->decodingSettingsBox->setDefaultSettings();
+ d->brightnessInput->slotReset();
+ d->contrastInput->slotReset();
+ d->gammaInput->slotReset();
+ d->saturationInput->slotReset();
+ d->fineExposureInput->slotReset();
+ slotResetCurve();
+}
+
+void RawSettingsBox::slotResetCurve()
+{
+ d->curveWidget->reset();
+ emit signalPostProcessingChanged();
+}
+
+HistogramWidget* RawSettingsBox::histogram() const
+{
+ return d->histogramWidget;
+}
+
+CurvesWidget* RawSettingsBox::curve() const
+{
+ return d->curveWidget;
+}
+
+void RawSettingsBox::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("RAW Import Settings");
+
+ d->channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", RawSettingsBoxPriv::LuminosityChannel));
+ d->scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram));
+ d->colorsCB->setCurrentItem(config->readNumEntry("Histogram Color", RawSettingsBoxPriv::AllColorsRed));
+
+ d->decodingSettingsBox->setSixteenBits(config->readBoolEntry("SixteenBitsImage", false));
+ d->decodingSettingsBox->setWhiteBalance((DRawDecoding::WhiteBalance)
+ config->readNumEntry("White Balance",
+ DRawDecoding::CAMERA));
+ d->decodingSettingsBox->setCustomWhiteBalance(config->readNumEntry("Custom White Balance", 6500));
+ d->decodingSettingsBox->setCustomWhiteBalanceGreen(config->readDoubleNumEntry("Custom White Balance Green", 1.0));
+ d->decodingSettingsBox->setFourColor(config->readBoolEntry("Four Color RGB", false));
+ d->decodingSettingsBox->setUnclipColor(config->readNumEntry("Unclip Color", 0));
+ d->decodingSettingsBox->setDontStretchPixels(config->readBoolEntry("Dont Stretch Pixels", false));
+ d->decodingSettingsBox->setNoiseReduction(config->readBoolEntry("Use Noise Reduction", false));
+ d->decodingSettingsBox->setUseBlackPoint(config->readBoolEntry("Use Black Point", false));
+ d->decodingSettingsBox->setBlackPoint(config->readNumEntry("Black Point", 0));
+ d->decodingSettingsBox->setUseWhitePoint(config->readBoolEntry("Use White Point", false));
+ d->decodingSettingsBox->setWhitePoint(config->readNumEntry("White Point", 0));
+ d->decodingSettingsBox->setMedianFilterPasses(config->readNumEntry("Median Filter Passes", 0));
+ d->decodingSettingsBox->setNRThreshold(config->readNumEntry("NR Threshold", 100));
+ d->decodingSettingsBox->setUseCACorrection(config->readBoolEntry("EnableCACorrection", false));
+ d->decodingSettingsBox->setcaRedMultiplier(config->readDoubleNumEntry("caRedMultiplier", 1.0));
+ d->decodingSettingsBox->setcaBlueMultiplier(config->readDoubleNumEntry("caBlueMultiplier", 1.0));
+
+ d->decodingSettingsBox->setQuality(
+ (DRawDecoding::DecodingQuality)config->readNumEntry("Decoding Quality",
+ (int)(DRawDecoding::BILINEAR)));
+
+ d->decodingSettingsBox->setInputColorSpace(
+ (DRawDecoding::InputColorSpace)config->readNumEntry("Input Color Space",
+ (int)(DRawDecoding::NOINPUTCS)));
+
+ d->decodingSettingsBox->setOutputColorSpace(
+ (DRawDecoding::OutputColorSpace)config->readNumEntry("Output Color Space",
+ (int)(DRawDecoding::SRGB)));
+
+ d->decodingSettingsBox->setInputColorProfile(config->readPathEntry("Input Color Profile", TQString()));
+ d->decodingSettingsBox->setOutputColorProfile(config->readPathEntry("Output Color Profile", TQString()));
+
+ d->brightnessInput->setValue(config->readNumEntry("Brightness", 0));
+ d->contrastInput->setValue(config->readNumEntry("Contrast", 0));
+ d->gammaInput->setValue(config->readDoubleNumEntry("Gamma", 1.0));
+ d->saturationInput->setValue(config->readDoubleNumEntry("Saturation", 1.0));
+ d->fineExposureInput->setValue(config->readDoubleNumEntry("FineExposure", 0.0));
+
+ d->curveWidget->reset();
+
+ for (int j = 0 ; j <= 17 ; j++)
+ {
+ TQPoint disable(-1, -1);
+ TQPoint p = config->readPointEntry(TQString("CurveAjustmentPoint%1").arg(j), &disable);
+ if (!d->decodingSettingsBox->sixteenBits() && p != disable)
+ {
+ // Restore point as 16 bits depth.
+ p.setX(p.x()/255);
+ p.setY(p.y()/255);
+ }
+ d->curveWidget->curves()->setCurvePoint(ImageHistogram::ValueChannel, j, p);
+ }
+ d->curveWidget->curves()->curvesCalculateCurve(ImageHistogram::ValueChannel);
+
+ d->tabView->setCurrentPage(config->readNumEntry("Settings Page", 0));
+ d->decodingSettingsBox->setCurrentIndex(config->readNumEntry("Decoding Settings Tab", DcrawSettingsWidget::DEMOSAICING));
+ d->postProcessSettingsBox->setCurrentIndex(config->readNumEntry("Post Processing Settings Tab", 0));
+
+ slotChannelChanged(d->channelCB->currentItem());
+ slotScaleChanged(d->scaleBG->selectedId());
+ slotColorsChanged(d->colorsCB->currentItem());
+}
+
+void RawSettingsBox::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("RAW Import Settings");
+
+ config->writeEntry("Histogram Channel", d->channelCB->currentItem());
+ config->writeEntry("Histogram Scale", d->scaleBG->selectedId());
+ config->writeEntry("Histogram Color", d->colorsCB->currentItem());
+
+ config->writeEntry("SixteenBitsImage", d->decodingSettingsBox->sixteenBits());
+ config->writeEntry("White Balance", d->decodingSettingsBox->whiteBalance());
+ config->writeEntry("Custom White Balance", d->decodingSettingsBox->customWhiteBalance());
+ config->writeEntry("Custom White Balance Green", d->decodingSettingsBox->customWhiteBalanceGreen());
+ config->writeEntry("Four Color RGB", d->decodingSettingsBox->useFourColor());
+ config->writeEntry("Unclip Color", d->decodingSettingsBox->unclipColor());
+ config->writeEntry("Dont Stretch Pixels", d->decodingSettingsBox->useDontStretchPixels());
+ config->writeEntry("Use Noise Reduction", d->decodingSettingsBox->useNoiseReduction());
+ config->writeEntry("Use Black Point", d->decodingSettingsBox->useBlackPoint());
+ config->writeEntry("Black Point", d->decodingSettingsBox->blackPoint());
+ config->writeEntry("Use White Point", d->decodingSettingsBox->useWhitePoint());
+ config->writeEntry("White Point", d->decodingSettingsBox->whitePoint());
+ config->writeEntry("MedianFilterPasses", d->decodingSettingsBox->medianFilterPasses());
+ config->writeEntry("NR Threshold", d->decodingSettingsBox->NRThreshold());
+ config->writeEntry("EnableCACorrection", d->decodingSettingsBox->useCACorrection());
+ config->writeEntry("caRedMultiplier", d->decodingSettingsBox->caRedMultiplier());
+ config->writeEntry("caBlueMultiplier", d->decodingSettingsBox->caBlueMultiplier());
+ config->writeEntry("Decoding Quality", (int)d->decodingSettingsBox->quality());
+ config->writeEntry("Input Color Space", (int)d->decodingSettingsBox->inputColorSpace());
+ config->writeEntry("Output Color Space", (int)d->decodingSettingsBox->outputColorSpace());
+ config->writeEntry("Input Color Profile", d->decodingSettingsBox->inputColorProfile());
+ config->writeEntry("Output Color Profile", d->decodingSettingsBox->outputColorProfile());
+
+ config->writeEntry("Brightness", d->brightnessInput->value());
+ config->writeEntry("Contrast", d->contrastInput->value());
+ config->writeEntry("Gamma", d->gammaInput->value());
+ config->writeEntry("Saturation", d->saturationInput->value());
+ config->writeEntry("FineExposure", d->fineExposureInput->value());
+
+ for (int j = 0 ; j <= 17 ; j++)
+ {
+ TQPoint p = d->curveWidget->curves()->getCurvePoint(ImageHistogram::ValueChannel, j);
+ if (!d->curveWidget->curves()->isSixteenBits())
+ {
+ // Store point as 16 bits depth.
+ p.setX(p.x()*255);
+ p.setY(p.y()*255);
+ }
+ config->writeEntry(TQString("CurveAjustmentPoint%1").arg(j), p);
+ }
+
+ config->writeEntry("Settings Page", d->tabView->currentPage());
+ config->writeEntry("Decoding Settings Tab", d->decodingSettingsBox->currentIndex());
+ config->writeEntry("Post Processing Settings Tab", d->postProcessSettingsBox->currentIndex());
+ config->sync();
+}
+
+DRawDecoding RawSettingsBox::settings()
+{
+ DRawDecoding settings;
+ settings.sixteenBitsImage = d->decodingSettingsBox->sixteenBits();
+ settings.whiteBalance = d->decodingSettingsBox->whiteBalance();
+ settings.customWhiteBalance = d->decodingSettingsBox->customWhiteBalance();
+ settings.customWhiteBalanceGreen = d->decodingSettingsBox->customWhiteBalanceGreen();
+ settings.RGBInterpolate4Colors = d->decodingSettingsBox->useFourColor();
+ settings.unclipColors = d->decodingSettingsBox->unclipColor();
+ settings.DontStretchPixels = d->decodingSettingsBox->useDontStretchPixels();
+ settings.enableNoiseReduction = d->decodingSettingsBox->useNoiseReduction();
+ settings.enableBlackPoint = d->decodingSettingsBox->useBlackPoint();
+ settings.blackPoint = d->decodingSettingsBox->blackPoint();
+ settings.enableWhitePoint = d->decodingSettingsBox->useWhitePoint();
+ settings.whitePoint = d->decodingSettingsBox->whitePoint();
+ settings.medianFilterPasses = d->decodingSettingsBox->medianFilterPasses();
+ settings.NRThreshold = d->decodingSettingsBox->NRThreshold();
+ settings.enableCACorrection = d->decodingSettingsBox->useCACorrection();
+ settings.caMultiplier[0] = d->decodingSettingsBox->caRedMultiplier();
+ settings.caMultiplier[1] = d->decodingSettingsBox->caBlueMultiplier();
+ settings.RAWQuality = d->decodingSettingsBox->quality();
+ settings.inputColorSpace = d->decodingSettingsBox->inputColorSpace();
+ settings.outputColorSpace = d->decodingSettingsBox->outputColorSpace();
+ settings.inputProfile = d->decodingSettingsBox->inputColorProfile();
+ settings.outputProfile = d->decodingSettingsBox->outputColorProfile();
+
+ settings.lightness = (double)d->brightnessInput->value()/250.0;
+ settings.contrast = (double)(d->contrastInput->value()/100.0) + 1.00;
+ settings.gamma = d->gammaInput->value();
+ settings.saturation = d->saturationInput->value();
+ settings.exposureComp = d->fineExposureInput->value();
+
+ if (d->curveWidget->curves()->isDirty())
+ settings.curveAdjust = d->curveWidget->curves()->getCurvePoints(ImageHistogram::ValueChannel);
+
+ return settings;
+}
+
+void RawSettingsBox::slotChannelChanged(int channel)
+{
+ switch(channel)
+ {
+ case RawSettingsBoxPriv::LuminosityChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::ValueHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case RawSettingsBoxPriv::RedChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "red" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case RawSettingsBoxPriv::GreenChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "green" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case RawSettingsBoxPriv::BlueChannel:
+ d->histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) );
+ d->colorsCB->setEnabled(false);
+ break;
+
+ case RawSettingsBoxPriv::ColorChannels:
+ d->histogramWidget->m_channelType = HistogramWidget::ColorChannelsHistogram;
+ d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) );
+ d->colorsCB->setEnabled(true);
+ break;
+ }
+
+ d->histogramWidget->repaint(false);
+}
+
+void RawSettingsBox::slotScaleChanged(int scale)
+{
+ d->histogramWidget->m_scaleType = scale;
+ d->histogramWidget->repaint(false);
+}
+
+void RawSettingsBox::slotColorsChanged(int color)
+{
+ switch(color)
+ {
+ case RawSettingsBoxPriv::AllColorsGreen:
+ d->histogramWidget->m_colorType = HistogramWidget::GreenColor;
+ break;
+
+ case RawSettingsBoxPriv::AllColorsBlue:
+ d->histogramWidget->m_colorType = HistogramWidget::BlueColor;
+ break;
+
+ default: // Red.
+ d->histogramWidget->m_colorType = HistogramWidget::RedColor;
+ break;
+ }
+
+ d->histogramWidget->repaint(false);
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/imageeditor/rawimport/rawsettingsbox.h b/src/utilities/imageeditor/rawimport/rawsettingsbox.h
new file mode 100644
index 00000000..546ce1e5
--- /dev/null
+++ b/src/utilities/imageeditor/rawimport/rawsettingsbox.h
@@ -0,0 +1,91 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2008-08-11
+ * Description : Raw import settings box
+ *
+ * Copyright (C) 2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef RAWSETTINGSBOX_H
+#define RAWSETTINGSBOX_H
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "editortoolsettings.h"
+#include "dimg.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class HistogramWidget;
+class CurvesWidget;
+class RawSettingsBoxPriv;
+
+class DIGIKAM_EXPORT RawSettingsBox : public EditorToolSettings
+{
+ TQ_OBJECT
+
+
+public:
+
+ RawSettingsBox(const KURL& url, TQWidget *parent);
+ ~RawSettingsBox();
+
+ void setBusy(bool b);
+
+ HistogramWidget* histogram() const;
+ CurvesWidget* curve() const;
+ DRawDecoding settings();
+
+ void writeSettings();
+ void readSettings();
+
+ void setDemosaicedImage(DImg& img);
+ void setPostProcessedImage(DImg& img);
+
+ void enableUpdateBtn(bool b);
+
+ void resetSettings();
+
+signals:
+
+ void signalUpdatePreview();
+ void signalAbortPreview();
+ void signalDemosaicingChanged();
+ void signalPostProcessingChanged();
+
+private slots:
+
+ void slotChannelChanged(int channel);
+ void slotScaleChanged(int scale);
+ void slotColorsChanged(int color);
+
+ void slotResetCurve();
+
+private:
+
+ RawSettingsBoxPriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif // RAWSETTINGSBOX_H
diff --git a/src/utilities/imageeditor/tools/Makefile.am b/src/utilities/imageeditor/tools/Makefile.am
new file mode 100644
index 00000000..b76207ff
--- /dev/null
+++ b/src/utilities/imageeditor/tools/Makefile.am
@@ -0,0 +1,19 @@
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libdimgeditortools.la
+
+libdimgeditortools_la_SOURCES = imageresize.cpp imageprint.cpp
+
+libdimgeditortools_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TDEPRINT)
+
+INCLUDES= -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/greycstoration \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
diff --git a/src/utilities/imageeditor/tools/imageprint.cpp b/src/utilities/imageeditor/tools/imageprint.cpp
new file mode 100644
index 00000000..5cab236b
--- /dev/null
+++ b/src/utilities/imageeditor/tools/imageprint.cpp
@@ -0,0 +1,814 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-13
+ * Description : image editor printing interface.
+ *
+ * Copyright (C) 2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * KeepRatio and Alignment options imported from Gwenview program.
+ * Copyright (c) 2003 Angelo Naselli <anaselli at linux dot it>
+ *
+ * Original printing code from Kuickshow program.
+ * Copyright (C) 2002 Carsten Pfeiffer <pfeiffer at kde.org>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqobject.h>
+#include <tqpixmap.h>
+#include <tqlayout.h>
+#include <tqgroupbox.h>
+#include <tqbuttongroup.h>
+#include <tqstring.h>
+#include <tqsize.h>
+#include <tqcursor.h>
+#include <tqlabel.h>
+#include <tqhbox.h>
+#include <tqcheckbox.h>
+#include <tqfont.h>
+#include <tqgrid.h>
+#include <tqimage.h>
+#include <tqpaintdevicemetrics.h>
+#include <tqpainter.h>
+#include <tqradiobutton.h>
+#include <tqvbuttongroup.h>
+#include <tqcolor.h>
+#include <tqcombobox.h>
+#include <tqstyle.h>
+#include <tqpushbutton.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <kstandarddirs.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kimageio.h>
+#include <kcombobox.h>
+#include <tdeglobalsettings.h>
+#include <knuminput.h>
+#include <kprinter.h>
+#include <tdetempfile.h>
+#include <kpropertiesdialog.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "editorwindow.h"
+#include "icctransform.h"
+#include "imageprint.h"
+#include "imageprint.moc"
+
+namespace Digikam
+{
+
+class ImagePrintPrivate
+{
+
+public:
+
+ ImagePrintPrivate(){}
+
+ TQString filename;
+ TQString inProfilePath;
+ TQString outputProfilePath;
+
+ DImg image;
+};
+
+ImagePrint::ImagePrint(DImg& image, KPrinter& printer, const TQString& filename)
+ : m_printer(printer)
+{
+ d = new ImagePrintPrivate();
+ d->image = image;
+ d->filename = filename;
+}
+
+ImagePrint::~ImagePrint()
+{
+ delete d;
+}
+
+bool ImagePrint::printImageWithTQt()
+{
+ if ( d->image.isNull() )
+ {
+ DWarning() << "Supplied Image for printing is null" << endl;
+ return false;
+ }
+
+ TQString t = "true";
+ TQString f = "false";
+
+ if (m_printer.option( "app-imageeditor-color-managed") != f)
+ {
+ IccTransform *transform = new IccTransform();
+ readSettings();
+
+ if (d->image.getICCProfil().isNull())
+ {
+ transform->setProfiles( d->inProfilePath, d->outputProfilePath );
+ }
+ else
+ {
+ transform->setProfiles(d->outputProfilePath);
+ }
+
+ transform->apply( d->image );
+ }
+
+ TQImage image2Print = d->image.copyTQImage();
+
+ // Black & white print ?
+ if ( m_printer.option( "app-imageeditor-blackwhite" ) != f)
+ {
+ image2Print = image2Print.convertDepth( 1, TQt::MonoOnly |
+ TQt::ThresholdDither |
+ TQt::AvoidDither );
+ }
+
+ TQPainter p;
+ p.begin( &m_printer );
+
+ TQPaintDeviceMetrics metrics( &m_printer );
+ p.setFont( TDEGlobalSettings::generalFont() );
+ TQFontMetrics fm = p.fontMetrics();
+
+ int w, h; // will be set to the width and height of the printer
+ // when the orientation is decided.
+ w = metrics.width();
+ h = metrics.height();
+ int filenameOffset = 0;
+
+ TQSize size = image2Print.size();
+
+ bool printFilename = m_printer.option( "app-imageeditor-printFilename" ) != f;
+ if ( printFilename )
+ {
+ // filename goes into one line!
+ filenameOffset = fm.lineSpacing() + 14;
+ h -= filenameOffset;
+ }
+
+ if ( m_printer.option( "app-imageeditor-scaleToFit" ) != f )
+ {
+ if ( m_printer.option( "app-imageeditor-auto-rotate" ) == t )
+ m_printer.setOrientation( size.width() <= size.height() ? KPrinter::Portrait
+ : KPrinter::Landscape );
+
+ // Scale image to fit pagesize
+ size.scale( w, h, TQSize::ScaleMin );
+ }
+ else
+ {
+ int unit = (m_printer.option("app-imageeditor-scale-unit").isEmpty() ?
+ ImageEditorPrintDialogPage::DK_INCHES : m_printer.option("app-imageeditor-scale-unit").toInt());
+ double inches = 1;
+
+ if (unit == ImageEditorPrintDialogPage::DK_MILLIMETERS)
+ {
+ inches = 1/25.4;
+ }
+ else if (unit == ImageEditorPrintDialogPage::DK_CENTIMETERS)
+ {
+ inches = 1/2.54;
+ }
+
+ double wImg = (m_printer.option("app-imageeditor-scale-width").isEmpty() ?
+ 1 : m_printer.option("app-imageeditor-scale-width").toDouble()) * inches;
+ double hImg = (m_printer.option("app-imageeditor-scale-height").isEmpty() ?
+ 1 : m_printer.option("app-imageeditor-scale-height").toDouble()) * inches;
+ size.setWidth( int(wImg * m_printer.resolution()) );
+ size.setHeight( int(hImg * m_printer.resolution()) );
+
+ if ( m_printer.option( "app-imageeditor-auto-rotate" ) == t )
+ m_printer.setOrientation( wImg <= hImg ? KPrinter::Portrait : KPrinter::Landscape );
+
+ if (size.width() > w || size.height() > h)
+ {
+ int resp = KMessageBox::warningYesNoCancel(TDEApplication::kApplication()->mainWidget(),
+ i18n("The image will not fit on the page, what do you want to do?"),
+ TQString(),KStdGuiItem::cont(),
+ i18n("Shrink") );
+
+ if (resp==KMessageBox::Cancel)
+ {
+ m_printer.abort();
+ // no need to return false, user decided to abort
+ return true;
+ }
+ else if (resp == KMessageBox::No)
+ { // Shrink
+ size.scale(w, h, TQSize::ScaleMin);
+ }
+ }
+ }
+
+ // Align image.
+ int alignment = (m_printer.option("app-imageeditor-alignment").isEmpty() ?
+ TQt::AlignCenter : m_printer.option("app-imageeditor-alignment").toInt());
+
+ int x = 0;
+ int y = 0;
+
+ // x - alignment
+ if ( alignment & TQt::AlignHCenter )
+ x = (w - size.width())/2;
+ else if ( alignment & TQt::AlignLeft )
+ x = 0;
+ else if ( alignment & TQt::AlignRight )
+ x = w - size.width();
+
+ // y - alignment
+ if ( alignment & TQt::AlignVCenter )
+ y = (h - size.height())/2;
+ else if ( alignment & TQt::AlignTop )
+ y = 0;
+ else if ( alignment & TQt::AlignBottom )
+ y = h - size.height();
+
+ // Perform the actual drawing.
+ p.drawImage( TQRect( x, y, size.width(), size.height()), image2Print );
+
+ if ( printFilename )
+ {
+ TQString fname = minimizeString( d->filename, fm, w );
+
+ if ( !fname.isEmpty() )
+ {
+ int fw = fm.width( fname );
+ int x = (w - fw)/2;
+ int y = metrics.height() - filenameOffset/2;
+ p.drawText( x, y, fname );
+ }
+ }
+
+ p.end();
+
+ return true;
+}
+
+TQString ImagePrint::minimizeString( TQString text, const TQFontMetrics& metrics,
+ int maxWidth )
+{
+ // no sense to cut that tiny little string
+ if ( text.length() <= 5 )
+ return TQString();
+
+ bool changed = false;
+
+ while ( metrics.width( text ) > maxWidth )
+ {
+ int mid = text.length() / 2;
+ // remove 2 characters in the middle
+ text.remove( mid, 2 );
+ changed = true;
+ }
+
+ // add "..." in the middle
+ if ( changed )
+ {
+ int mid = text.length() / 2;
+
+ // sanity check
+ if ( mid <= 5 )
+ return TQString();
+
+ text.replace( mid - 1, 3, "..." );
+ }
+
+ return text;
+}
+
+void ImagePrint::readSettings()
+{
+ TDEConfig* config = kapp->config();
+
+ config->setGroup("Color Management");
+
+ d->inProfilePath = config->readPathEntry("WorkSpaceProfile");
+ d->outputProfilePath = config->readPathEntry("ProofProfileFile");
+}
+
+// Image print dialog class -------------------------------------------------------------
+
+class ImageEditorPrintDialogPagePrivate
+{
+
+public:
+
+ ImageEditorPrintDialogPagePrivate()
+ {
+ cmEnabled = false;
+ position = 0;
+ keepRatio = 0;
+ scaleToFit = 0;
+ scale = 0;
+ addFileName = 0;
+ blackwhite = 0;
+ autoRotate = 0;
+ colorManaged = 0;
+ cmPreferences = 0;
+ parent = 0;
+ width = 0;
+ height = 0;
+ units = 0;
+ }
+
+ bool cmEnabled;
+
+ TQRadioButton *scaleToFit;
+ TQRadioButton *scale;
+
+ TQCheckBox *keepRatio;
+ TQCheckBox *addFileName;
+ TQCheckBox *blackwhite;
+ TQCheckBox *autoRotate;
+ TQCheckBox *colorManaged;
+
+ TQPushButton *cmPreferences;
+
+ TQWidget *parent;
+
+ KDoubleNumInput *width;
+ KDoubleNumInput *height;
+
+ KComboBox *position;
+ KComboBox *units;
+
+ DImg image;
+
+ ImageEditorPrintDialogPage::Unit previousUnit;
+};
+
+ImageEditorPrintDialogPage::ImageEditorPrintDialogPage(DImg& image, TQWidget *parent, const char *name )
+ : KPrintDialogPage( parent, name )
+{
+ d = new ImageEditorPrintDialogPagePrivate;
+ d->image = image;
+ d->parent = parent;
+ setTitle( i18n("Image Settings") );
+
+ readSettings();
+
+ TQVBoxLayout *layout = new TQVBoxLayout( this );
+ layout->setMargin( KDialog::marginHint() );
+ layout->setSpacing( KDialog::spacingHint() );
+
+ // ------------------------------------------------------------------------
+
+ TQHBoxLayout *layout2 = new TQHBoxLayout( layout );
+ layout2->setSpacing(3);
+
+ TQLabel* textLabel = new TQLabel( this, "Image position:" );
+ textLabel->setText( i18n( "Image position:" ) );
+ layout2->addWidget( textLabel );
+ d->position = new KComboBox( false, this, "Print position" );
+ d->position->clear();
+ d->position->insertItem( i18n( "Top-Left" ) );
+ d->position->insertItem( i18n( "Top-Central" ) );
+ d->position->insertItem( i18n( "Top-Right" ) );
+ d->position->insertItem( i18n( "Central-Left" ) );
+ d->position->insertItem( i18n( "Central" ) );
+ d->position->insertItem( i18n( "Central-Right" ) );
+ d->position->insertItem( i18n( "Bottom-Left" ) );
+ d->position->insertItem( i18n( "Bottom-Central" ) );
+ d->position->insertItem( i18n( "Bottom-Right" ) );
+ layout2->addWidget( d->position );
+ TQSpacerItem *spacer1 = new TQSpacerItem( 101, 21, TQSizePolicy::Expanding, TQSizePolicy::Minimum );
+ layout2->addItem( spacer1 );
+
+ d->addFileName = new TQCheckBox( i18n("Print fi&lename below image"), this);
+ d->addFileName->setChecked( false );
+ layout->addWidget( d->addFileName );
+
+ d->blackwhite = new TQCheckBox ( i18n("Print image in &black and white"), this);
+ d->blackwhite->setChecked( false );
+ layout->addWidget (d->blackwhite );
+
+ d->autoRotate = new TQCheckBox( i18n("&Auto-rotate page"), this );
+ d->autoRotate->setChecked( false );
+ layout->addWidget( d->autoRotate );
+
+ // ------------------------------------------------------------------------
+
+ TQHBox *cmbox = new TQHBox(this);
+
+ d->colorManaged = new TQCheckBox(i18n("Use Color Management for Printing"), cmbox);
+ d->colorManaged->setChecked( false );
+
+ d->cmPreferences = new TQPushButton(i18n("Settings..."), cmbox);
+
+ TQWidget *space = new TQWidget(cmbox);
+ cmbox->setStretchFactor(space, 10);
+ cmbox->setSpacing(KDialog::spacingHint());
+
+ layout->addWidget(cmbox);
+
+ // ------------------------------------------------------------------------
+
+ TQVButtonGroup *group = new TQVButtonGroup( i18n("Scaling"), this );
+ group->setRadioButtonExclusive( true );
+ layout->addWidget( group );
+
+ d->scaleToFit = new TQRadioButton( i18n("Scale image to &fit"), group );
+ d->scaleToFit->setChecked( true );
+
+ d->scale = new TQRadioButton( i18n("Print e&xact size: "), group );
+
+ TQHBox *hb = new TQHBox( group );
+ hb->setSpacing( KDialog::spacingHint() );
+ TQWidget *w = new TQWidget(hb);
+ w->setFixedWidth(d->scale->style().subRect( TQStyle::SR_RadioButtonIndicator, d->scale ).width());
+
+ d->width = new KDoubleNumInput( hb, "exact width" );
+ d->width->setMinValue( 1 );
+
+ new TQLabel( "x", hb );
+
+ d->height = new KDoubleNumInput( hb, "exact height" );
+ d->height->setMinValue( 1 );
+
+ d->units = new KComboBox( false, hb, "unit combobox" );
+ d->units->insertItem( i18n("Millimeters") );
+ d->units->insertItem( i18n("Centimeters") );
+ d->units->insertItem( i18n("Inches") );
+
+ d->keepRatio = new TQCheckBox( i18n("Keep ratio"), hb);
+
+ w = new TQWidget(hb);
+ hb->setStretchFactor( w, 1 );
+ d->previousUnit = DK_MILLIMETERS;
+
+ // ------------------------------------------------------------------------
+
+ connect( d->colorManaged, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotAlertSettings( bool )) );
+
+ connect( d->cmPreferences, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotSetupDlg()) );
+
+ connect( d->scale, TQ_SIGNAL( toggled( bool )),
+ this, TQ_SLOT( toggleScaling( bool )));
+
+ connect(d->width, TQ_SIGNAL( valueChanged( double )),
+ this, TQ_SLOT( slotWidthChanged( double )));
+
+ connect(d->height, TQ_SIGNAL( valueChanged( double )),
+ this, TQ_SLOT( slotHeightChanged( double )));
+
+ connect(d->keepRatio, TQ_SIGNAL( toggled( bool )),
+ this, TQ_SLOT( toggleRatio( bool )));
+
+ connect(d->units, TQ_SIGNAL(activated(const TQString &)),
+ this, TQ_SLOT(slotUnitChanged(const TQString& )));
+}
+
+ImageEditorPrintDialogPage::~ImageEditorPrintDialogPage()
+{
+ delete d;
+}
+
+void ImageEditorPrintDialogPage::getOptions( TQMap<TQString,TQString>& opts, bool /*incldef*/ )
+{
+ TQString t = "true";
+ TQString f = "false";
+
+ opts["app-imageeditor-alignment"] = TQString::number(getPosition(d->position->currentText()));
+ opts["app-imageeditor-printFilename"] = d->addFileName->isChecked() ? t : f;
+ opts["app-imageeditor-blackwhite"] = d->blackwhite->isChecked() ? t : f;
+ opts["app-imageeditor-scaleToFit"] = d->scaleToFit->isChecked() ? t : f;
+ opts["app-imageeditor-scale"] = d->scale->isChecked() ? t : f;
+ opts["app-imageeditor-scale-unit"] = TQString::number(stringToUnit(d->units->currentText()));
+ opts["app-imageeditor-scale-width"] = TQString::number( d->width->value() );
+ opts["app-imageeditor-scale-height"] = TQString::number( d->height->value() );
+ opts["app-imageeditor-scale-KeepRatio"] = d->keepRatio->isChecked() ? t : f;
+ opts["app-imageeditor-auto-rotate"] = d->autoRotate->isChecked() ? t : f;
+ opts["app-imageeditor-color-managed"] = d->colorManaged->isChecked() ? t : f;
+}
+
+void ImageEditorPrintDialogPage::setOptions( const TQMap<TQString,TQString>& opts )
+{
+ TQString t = "true";
+ TQString f = "false";
+ TQString stVal;
+ bool ok;
+ double dVal;
+ int iVal;
+
+ iVal = opts["app-imageeditor-alignment"].toInt( &ok );
+ if (ok)
+ {
+ stVal = setPosition(iVal);
+ d->position->setCurrentItem(stVal);
+ }
+
+ d->addFileName->setChecked( opts["app-imageeditor-printFilename"] != f );
+ // This sound strange, but if I copy the code on the line above, the checkbox
+ // was always checked. And this is not the wanted behavior. So, with this works.
+ // KPrint magic ;-)
+ d->blackwhite->setChecked ( false );
+ d->scaleToFit->setChecked( opts["app-imageeditor-scaleToFit"] != f );
+ d->scale->setChecked( opts["app-imageeditor-scale"] == t );
+ d->autoRotate->setChecked( opts["app-imageeditor-auto-rotate"] == t );
+
+ d->colorManaged->setChecked( false );
+
+ Unit unit = static_cast<Unit>( opts["app-imageeditor-scale-unit"].toInt( &ok ) );
+ if (ok)
+ {
+ stVal = unitToString(unit);
+ d->units->setCurrentItem(stVal);
+ d->previousUnit = unit;
+ }
+ else
+ {
+ //for back compatibility
+ d->units->setCurrentItem(i18n("Millimeters"));
+ }
+
+ dVal = opts["app-imageeditor-scale-width"].toDouble( &ok );
+
+ if ( ok )
+ d->width->setValue( dVal );
+
+ dVal = opts["app-imageeditor-scale-height"].toDouble( &ok );
+
+ if ( ok )
+ d->height->setValue( dVal );
+
+ if ( d->scale->isChecked() == d->scaleToFit->isChecked() )
+ d->scaleToFit->setChecked( !d->scale->isChecked() );
+
+ d->keepRatio->setChecked( opts["app-imageeditor-scale-KeepRatio"] == t );
+
+}
+int ImageEditorPrintDialogPage::getPosition(const TQString& align)
+{
+ int alignment;
+
+ if (align == i18n("Central-Left"))
+ {
+ alignment = TQt::AlignLeft | TQt::AlignVCenter;
+ }
+ else if (align == i18n("Central-Right"))
+ {
+ alignment = TQt::AlignRight | TQt::AlignVCenter;
+ }
+ else if (align == i18n("Top-Left"))
+ {
+ alignment = TQt::AlignTop | TQt::AlignLeft;
+ }
+ else if (align == i18n("Top-Right"))
+ {
+ alignment = TQt::AlignTop | TQt::AlignRight;
+ }
+ else if (align == i18n("Bottom-Left"))
+ {
+ alignment = TQt::AlignBottom | TQt::AlignLeft;
+ }
+ else if (align == i18n("Bottom-Right"))
+ {
+ alignment = TQt::AlignBottom | TQt::AlignRight;
+ }
+ else if (align == i18n("Top-Central"))
+ {
+ alignment = TQt::AlignTop | TQt::AlignHCenter;
+ }
+ else if (align == i18n("Bottom-Central"))
+ {
+ alignment = TQt::AlignBottom | TQt::AlignHCenter;
+ }
+ else
+ {
+ // Central
+ alignment = TQt::AlignCenter; // TQt::AlignHCenter || TQt::AlignVCenter
+ }
+
+ return alignment;
+}
+
+TQString ImageEditorPrintDialogPage::setPosition(int align)
+{
+ TQString alignment;
+
+ if (align == (TQt::AlignLeft | TQt::AlignVCenter))
+ {
+ alignment = i18n("Central-Left");
+ }
+ else if (align == (TQt::AlignRight | TQt::AlignVCenter))
+ {
+ alignment = i18n("Central-Right");
+ }
+ else if (align == (TQt::AlignTop | TQt::AlignLeft))
+ {
+ alignment = i18n("Top-Left");
+ }
+ else if (align == (TQt::AlignTop | TQt::AlignRight))
+ {
+ alignment = i18n("Top-Right");
+ }
+ else if (align == (TQt::AlignBottom | TQt::AlignLeft))
+ {
+ alignment = i18n("Bottom-Left");
+ }
+ else if (align == (TQt::AlignBottom | TQt::AlignRight))
+ {
+ alignment = i18n("Bottom-Right");
+ }
+ else if (align == (TQt::AlignTop | TQt::AlignHCenter))
+ {
+ alignment = i18n("Top-Central");
+ }
+ else if (align == (TQt::AlignBottom | TQt::AlignHCenter))
+ {
+ alignment = i18n("Bottom-Central");
+ }
+ else
+ {
+ // Central: TQt::AlignCenter or (TQt::AlignHCenter || TQt::AlignVCenter)
+ alignment = i18n("Central");
+ }
+
+ return alignment;
+}
+
+void ImageEditorPrintDialogPage::toggleScaling( bool enable )
+{
+ d->width->setEnabled( enable );
+ d->height->setEnabled( enable );
+ d->units->setEnabled( enable );
+ d->keepRatio->setEnabled( enable );
+}
+
+void ImageEditorPrintDialogPage::slotHeightChanged (double value)
+{
+ d->width->blockSignals(true);
+ d->height->blockSignals(true);
+
+ if (d->keepRatio->isChecked())
+ {
+ double width = (d->image.width() * value) / d->image.height();
+ d->width->setValue( width ? width : 1.);
+ }
+ d->height->setValue(value);
+
+ d->width->blockSignals(false);
+ d->height->blockSignals(false);
+}
+
+void ImageEditorPrintDialogPage::slotWidthChanged (double value)
+{
+ d->width->blockSignals(true);
+ d->height->blockSignals(true);
+
+ if (d->keepRatio->isChecked())
+ {
+ double height = (d->image.height() * value) / d->image.width();
+ d->height->setValue( height ? height : 1);
+ }
+ d->width->setValue(value);
+
+ d->width->blockSignals(false);
+ d->height->blockSignals(false);
+}
+
+void ImageEditorPrintDialogPage::toggleRatio( bool enable )
+{
+ if (!enable) return;
+ // choosing a startup value of 15x10 cm (common photo dimention)
+ // mContent->mHeight->value() or mContent->mWidth->value()
+ // are usually empty at startup and hxw (0x0) is not good IMO keeping ratio
+ double hValue, wValue;
+ if (d->image.height() > d->image.width())
+ {
+ hValue = d->height->value();
+ if (!hValue) hValue = 150*unitToMM(d->previousUnit);
+ wValue = (d->image.width() * hValue)/ d->image.height();
+ }
+ else
+ {
+ wValue = d->width->value();
+ if (!wValue) wValue = 150*unitToMM(d->previousUnit);
+ hValue = (d->image.height() * wValue)/ d->image.width();
+ }
+
+ d->width->blockSignals(true);
+ d->height->blockSignals(true);
+
+ d->width->setValue(wValue);
+ d->height->setValue(hValue);
+
+ d->width->blockSignals(false);
+ d->height->blockSignals(false);
+}
+
+void ImageEditorPrintDialogPage::slotUnitChanged(const TQString& string)
+{
+ Unit newUnit = stringToUnit(string);
+ double ratio = unitToMM(d->previousUnit) / unitToMM(newUnit);
+
+ d->width->blockSignals(true);
+ d->height->blockSignals(true);
+
+ d->width->setValue( d->width->value() * ratio);
+ d->height->setValue( d->height->value() * ratio);
+
+ d->width->blockSignals(false);
+ d->height->blockSignals(false);
+
+ d->previousUnit = newUnit;
+}
+
+void ImageEditorPrintDialogPage::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Color Management");
+ d->cmEnabled = config->readBoolEntry("EnableCM", false);
+}
+
+void ImageEditorPrintDialogPage::slotSetupDlg()
+{
+ EditorWindow* editor = dynamic_cast<EditorWindow*>(d->parent);
+ editor->setup(true);
+}
+
+void ImageEditorPrintDialogPage::slotAlertSettings( bool t)
+{
+ if (t && !d->cmEnabled)
+ {
+ TQString message = i18n("<p>Color Management is disabled.</p> \
+ <p>You can enable it now by clicking on the \"Settings\" button.</p>");
+ KMessageBox::information(this, message);
+ d->colorManaged->setChecked(!t);
+ }
+}
+
+double ImageEditorPrintDialogPage::unitToMM(Unit unit)
+{
+ if (unit == DK_MILLIMETERS)
+ {
+ return 1.;
+ }
+ else if (unit == DK_CENTIMETERS)
+ {
+ return 10.;
+ }
+ else
+ { //DK_INCHES
+ return 25.4;
+ }
+}
+
+ImageEditorPrintDialogPage::Unit ImageEditorPrintDialogPage::stringToUnit(const TQString& unit)
+{
+ if (unit == i18n("Millimeters"))
+ {
+ return DK_MILLIMETERS;
+ }
+ else if (unit == i18n("Centimeters"))
+ {
+ return DK_CENTIMETERS;
+ }
+ else
+ {//Inches
+ return DK_INCHES;
+ }
+}
+
+TQString ImageEditorPrintDialogPage::unitToString(Unit unit)
+{
+ if (unit == DK_MILLIMETERS)
+ {
+ return i18n("Millimeters");
+ }
+ else if (unit == DK_CENTIMETERS)
+ {
+ return i18n("Centimeters");
+ }
+ else
+ { //DK_INCHES
+ return i18n("Inches");
+ }
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/imageeditor/tools/imageprint.h b/src/utilities/imageeditor/tools/imageprint.h
new file mode 100644
index 00000000..66276701
--- /dev/null
+++ b/src/utilities/imageeditor/tools/imageprint.h
@@ -0,0 +1,122 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-07-13
+ * Description : image editor printing interface.
+ *
+ * Copyright (C) 2006 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGEPRINT_H
+#define IMAGEPRINT_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <kprinter.h>
+#include <tdeprint/kprintdialogpage.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImagePrintPrivate;
+
+class DIGIKAM_EXPORT ImagePrint
+{
+public:
+
+ ImagePrint(DImg& image, KPrinter& printer, const TQString& fileName);
+ ~ImagePrint();
+
+ bool printImageWithTQt();
+
+private:
+
+ TQString minimizeString(TQString text, const TQFontMetrics& metrics, int maxWidth);
+ void readSettings();
+
+private:
+
+ KPrinter& m_printer;
+
+ ImagePrintPrivate *d;
+};
+
+//-----------------------------------------------------------------------------
+
+class ImageEditorPrintDialogPagePrivate;
+
+class DIGIKAM_EXPORT ImageEditorPrintDialogPage : public KPrintDialogPage
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum Unit
+ {
+ DK_MILLIMETERS = 1,
+ DK_CENTIMETERS,
+ DK_INCHES
+ };
+
+ static inline double unitToMM(Unit unit);
+ static inline Unit stringToUnit(const TQString& unit);
+ static inline TQString unitToString(Unit unit);
+
+public:
+
+ ImageEditorPrintDialogPage(DImg& image, TQWidget *parent=0L, const char *name=0);
+ ~ImageEditorPrintDialogPage();
+
+ virtual void getOptions(TQMap<TQString,TQString>& opts, bool incldef = false);
+ virtual void setOptions(const TQMap<TQString,TQString>& opts);
+
+private slots:
+
+ void toggleScaling( bool enable );
+ void toggleRatio( bool enable );
+ void slotUnitChanged(const TQString& string);
+ void slotHeightChanged(double value);
+ void slotWidthChanged(double value);
+ void slotSetupDlg();
+ void slotAlertSettings(bool t);
+
+private:
+
+ void readSettings();
+ int getPosition(const TQString& align);
+ TQString setPosition(int align);
+
+private:
+
+ ImageEditorPrintDialogPagePrivate *d;
+};
+
+} // namespace Digikam
+
+#endif // IMAGEPRINT_H
diff --git a/src/utilities/imageeditor/tools/imageresize.cpp b/src/utilities/imageeditor/tools/imageresize.cpp
new file mode 100644
index 00000000..c779b71e
--- /dev/null
+++ b/src/utilities/imageeditor/tools/imageresize.cpp
@@ -0,0 +1,650 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-07
+ * Description : a tool to resize an image
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// C++ includes.
+
+#include <cmath>
+
+// TQt includes.
+
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqtooltip.h>
+#include <tqwhatsthis.h>
+#include <tqlayout.h>
+#include <tqframe.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+#include <tqtabwidget.h>
+#include <tqtimer.h>
+#include <tqevent.h>
+#include <tqpixmap.h>
+#include <tqbrush.h>
+#include <tqfile.h>
+#include <tqimage.h>
+
+// KDE includes.
+
+#include <kseparator.h>
+#include <kcursor.h>
+#include <kurllabel.h>
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdeapplication.h>
+#include <tdefiledialog.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <tdemessagebox.h>
+#include <knuminput.h>
+#include <tdeglobalsettings.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/rnuminput.h>
+
+// Digikam includes.
+
+#include "dimg.h"
+#include "ddebug.h"
+#include "imageiface.h"
+#include "dimgthreadedfilter.h"
+#include "greycstorationiface.h"
+#include "greycstorationwidget.h"
+#include "greycstorationsettings.h"
+
+// Local includes.
+
+#include "imageresize.h"
+#include "imageresize.moc"
+
+using namespace KDcrawIface;
+
+namespace Digikam
+{
+
+class ImageResizePriv
+{
+public:
+
+ enum RunningMode
+ {
+ NoneRendering=0,
+ FinalRendering
+ };
+
+ ImageResizePriv()
+ {
+ currentRenderingMode = NoneRendering;
+ parent = 0;
+ preserveRatioBox = 0;
+ useGreycstorationBox = 0;
+ mainTab = 0;
+ wInput = 0;
+ hInput = 0;
+ wpInput = 0;
+ hpInput = 0;
+ progressBar = 0;
+ greycstorationIface = 0;
+ settingsWidget = 0;
+ cimgLogoLabel = 0;
+ restorationTips = 0;
+ }
+
+ int currentRenderingMode;
+ int orgWidth;
+ int orgHeight;
+ int prevW;
+ int prevH;
+
+ double prevWP;
+ double prevHP;
+
+ TQWidget *parent;
+
+ TQLabel *restorationTips;
+
+ TQCheckBox *preserveRatioBox;
+ TQCheckBox *useGreycstorationBox;
+
+ TQTabWidget *mainTab;
+
+ RIntNumInput *wInput;
+ RIntNumInput *hInput;
+
+ RDoubleNumInput *wpInput;
+ RDoubleNumInput *hpInput;
+
+ KProgress *progressBar;
+
+ KURLLabel *cimgLogoLabel;
+
+ GreycstorationIface *greycstorationIface;
+ GreycstorationWidget *settingsWidget;
+};
+
+ImageResize::ImageResize(TQWidget* parent)
+ : KDialogBase(Plain, i18n("Resize Image"),
+ Help|Default|User2|User3|Ok|Cancel, Ok,
+ parent, 0, true, false,
+ TQString(),
+ i18n("&Save As..."),
+ i18n("&Load..."))
+{
+ d = new ImageResizePriv;
+ d->parent = parent;
+ setHelp("resizetool.anchor", "digikam");
+ TQString whatsThis;
+ setButtonWhatsThis( Default, i18n("<p>Reset all filter parameters to their default values.") );
+ setButtonWhatsThis( User3, i18n("<p>Load all filter parameters from settings text file.") );
+ setButtonWhatsThis( User2, i18n("<p>Save all filter parameters to settings text file.") );
+ enableButton(Ok, false);
+
+ ImageIface iface(0, 0);
+ d->orgWidth = iface.originalWidth();
+ d->orgHeight = iface.originalHeight();
+ d->prevW = d->orgWidth;
+ d->prevH = d->orgHeight;
+ d->prevWP = 100.0;
+ d->prevHP = 100.0;
+
+ // -------------------------------------------------------------
+
+ TQVBoxLayout *vlay = new TQVBoxLayout(plainPage(), 0, spacingHint());
+ d->mainTab = new TQTabWidget( plainPage() );
+
+ TQWidget* firstPage = new TQWidget( d->mainTab );
+ TQGridLayout* grid = new TQGridLayout( firstPage, 8, 2, spacingHint());
+ d->mainTab->addTab( firstPage, i18n("New Size") );
+
+ TQLabel *label1 = new TQLabel(i18n("Width:"), firstPage);
+ d->wInput = new RIntNumInput(firstPage);
+ d->wInput->setRange(1, TQMAX(d->orgWidth * 10, 9999), 1);
+ d->wInput->setName("d->wInput");
+ d->wInput->setDefaultValue(d->orgWidth);
+ TQWhatsThis::add( d->wInput, i18n("<p>Set here the new image width in pixels."));
+
+ TQLabel *label2 = new TQLabel(i18n("Height:"), firstPage);
+ d->hInput = new RIntNumInput(firstPage);
+ d->hInput->setRange(1, TQMAX(d->orgHeight * 10, 9999), 1);
+ d->hInput->setName("d->hInput");
+ d->hInput->setDefaultValue(d->orgHeight);
+ TQWhatsThis::add( d->hInput, i18n("<p>Set here the new image height in pixels."));
+
+ TQLabel *label3 = new TQLabel(i18n("Width (%):"), firstPage);
+ d->wpInput = new RDoubleNumInput(firstPage);
+ d->wpInput->setRange(1.0, 999.0, 1.0);
+ d->wpInput->setName("d->wpInput");
+ d->wpInput->setDefaultValue(100.0);
+ TQWhatsThis::add( d->wpInput, i18n("<p>Set here the new image width in percent."));
+
+ TQLabel *label4 = new TQLabel(i18n("Height (%):"), firstPage);
+ d->hpInput = new RDoubleNumInput(firstPage);
+ d->hpInput->setRange(1.0, 999.0, 1.0);
+ d->hpInput->setName("d->hpInput");
+ d->hpInput->setDefaultValue(100.0);
+ TQWhatsThis::add( d->hpInput, i18n("<p>Set here the new image height in percent."));
+
+ d->preserveRatioBox = new TQCheckBox(i18n("Maintain aspect ratio"), firstPage);
+ TQWhatsThis::add( d->preserveRatioBox, i18n("<p>Enable this option to maintain aspect "
+ "ratio with new image sizes."));
+
+ d->cimgLogoLabel = new KURLLabel(firstPage);
+ d->cimgLogoLabel->setText(TQString());
+ d->cimgLogoLabel->setURL("http://cimg.sourceforge.net");
+ TDEGlobal::dirs()->addResourceType("logo-cimg", TDEGlobal::dirs()->kde_default("data") +
+ "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-cimg", "logo-cimg.png");
+ d->cimgLogoLabel->setPixmap( TQPixmap( directory + "logo-cimg.png" ) );
+ TQToolTip::add(d->cimgLogoLabel, i18n("Visit CImg library website"));
+
+ d->useGreycstorationBox = new TQCheckBox(i18n("Restore photograph"), firstPage);
+ TQWhatsThis::add( d->useGreycstorationBox, i18n("<p>Enable this option to restore photograph content. "
+ "This way is usefull to scale-up an image to an huge size. "
+ "Warning: this process can take a while."));
+
+ d->restorationTips = new TQLabel(i18n("<b>Note: use Restoration Mode to only scale-up an image to huge size. "
+ "Warning, this process can take a while.</b>"), firstPage);
+
+ d->progressBar = new KProgress(100, firstPage);
+ d->progressBar->setValue(0);
+ TQWhatsThis::add(d->progressBar, i18n("<p>This shows the current progress when you use Restoration mode."));
+
+ grid->addMultiCellWidget(d->preserveRatioBox, 0, 0, 0, 2);
+ grid->addMultiCellWidget(label1, 1, 1, 0, 0);
+ grid->addMultiCellWidget(d->wInput, 1, 1, 1, 2);
+ grid->addMultiCellWidget(label2, 2, 2, 0, 0);
+ grid->addMultiCellWidget(d->hInput, 2, 2, 1, 2);
+ grid->addMultiCellWidget(label3, 3, 3, 0, 0);
+ grid->addMultiCellWidget(d->wpInput, 3, 3, 1, 2);
+ grid->addMultiCellWidget(label4, 4, 4, 0, 0);
+ grid->addMultiCellWidget(d->hpInput, 4, 4, 1, 2);
+ grid->addMultiCellWidget(new KSeparator(firstPage), 5, 5, 0, 2);
+ grid->addMultiCellWidget(d->cimgLogoLabel, 6, 8, 0, 0);
+ grid->addMultiCellWidget(d->useGreycstorationBox, 6, 6, 1, 2);
+ grid->addMultiCellWidget(d->restorationTips, 7, 7, 1, 2);
+ grid->addMultiCellWidget(d->progressBar, 8, 8, 1, 2);
+ grid->setRowStretch(8, 10);
+
+ // -------------------------------------------------------------
+
+ d->settingsWidget = new GreycstorationWidget(d->mainTab);
+ vlay->addWidget(d->mainTab);
+
+ // -------------------------------------------------------------
+
+ adjustSize();
+ disableResize();
+ TQTimer::singleShot(0, this, TQ_SLOT(readUserSettings()));
+
+ // -------------------------------------------------------------
+
+ connect(d->cimgLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processCImgURL(const TQString&)));
+
+ connect(d->wInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotValuesChanged()));
+
+ connect(d->hInput, TQ_SIGNAL(valueChanged(int)),
+ this, TQ_SLOT(slotValuesChanged()));
+
+ connect(d->wpInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotValuesChanged()));
+
+ connect(d->hpInput, TQ_SIGNAL(valueChanged(double)),
+ this, TQ_SLOT(slotValuesChanged()));
+
+ connect(d->useGreycstorationBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotRestorationToggled(bool)) );
+
+ // -------------------------------------------------------------
+
+ Digikam::GreycstorationSettings defaults;
+ defaults.setResizeDefaultSettings();
+ d->settingsWidget->setDefaultSettings(defaults);
+}
+
+ImageResize::~ImageResize()
+{
+ if (d->greycstorationIface)
+ delete d->greycstorationIface;
+
+ delete d;
+}
+
+void ImageResize::slotRestorationToggled(bool b)
+{
+ d->settingsWidget->setEnabled(b);
+ d->progressBar->setEnabled(b);
+ d->cimgLogoLabel->setEnabled(b);
+ enableButton(User2, b);
+ enableButton(User3, b);
+}
+
+void ImageResize::readUserSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("resize Tool Dialog");
+
+ GreycstorationSettings settings;
+ GreycstorationSettings defaults;
+ defaults.setResizeDefaultSettings();
+
+ settings.fastApprox = config->readBoolEntry("FastApprox", defaults.fastApprox);
+ settings.interp = config->readNumEntry("Interpolation", defaults.interp);
+ settings.amplitude = config->readDoubleNumEntry("Amplitude", defaults.amplitude);
+ settings.sharpness = config->readDoubleNumEntry("Sharpness", defaults.sharpness);
+ settings.anisotropy = config->readDoubleNumEntry("Anisotropy", defaults.anisotropy);
+ settings.alpha = config->readDoubleNumEntry("Alpha", defaults.alpha);
+ settings.sigma = config->readDoubleNumEntry("Sigma", defaults.sigma);
+ settings.gaussPrec = config->readDoubleNumEntry("GaussPrec", defaults.gaussPrec);
+ settings.dl = config->readDoubleNumEntry("Dl", defaults.dl);
+ settings.da = config->readDoubleNumEntry("Da", defaults.da);
+ settings.nbIter = config->readNumEntry("Iteration", defaults.nbIter);
+ settings.tile = config->readNumEntry("Tile", defaults.tile);
+ settings.btile = config->readNumEntry("BTile", defaults.btile);
+ d->settingsWidget->setSettings(settings);
+
+ d->useGreycstorationBox->setChecked(config->readBoolEntry("RestorePhotograph", false));
+ slotRestorationToggled(d->useGreycstorationBox->isChecked());
+
+ d->preserveRatioBox->blockSignals(true);
+ d->wInput->blockSignals(true);
+ d->hInput->blockSignals(true);
+ d->wpInput->blockSignals(true);
+ d->hpInput->blockSignals(true);
+
+ d->preserveRatioBox->setChecked(true);
+ d->wInput->slotReset();
+ d->hInput->slotReset();
+ d->wpInput->slotReset();
+ d->hpInput->slotReset();
+
+ d->preserveRatioBox->blockSignals(false);
+ d->wInput->blockSignals(false);
+ d->hInput->blockSignals(false);
+ d->wpInput->blockSignals(false);
+ d->hpInput->blockSignals(false);
+}
+
+void ImageResize::writeUserSettings()
+{
+ GreycstorationSettings settings = d->settingsWidget->getSettings();
+ TDEConfig* config = kapp->config();
+ config->setGroup("resize Tool Dialog");
+ config->writeEntry("FastApprox", settings.fastApprox);
+ config->writeEntry("Interpolation", settings.interp);
+ config->writeEntry("Amplitude", settings.amplitude);
+ config->writeEntry("Sharpness", settings.sharpness);
+ config->writeEntry("Anisotropy", settings.anisotropy);
+ config->writeEntry("Alpha", settings.alpha);
+ config->writeEntry("Sigma", settings.sigma);
+ config->writeEntry("GaussPrec", settings.gaussPrec);
+ config->writeEntry("Dl", settings.dl);
+ config->writeEntry("Da", settings.da);
+ config->writeEntry("Iteration", settings.nbIter);
+ config->writeEntry("Tile", settings.tile);
+ config->writeEntry("BTile", settings.btile);
+ config->writeEntry("RestorePhotograph", d->useGreycstorationBox->isChecked());
+ config->sync();
+}
+
+void ImageResize::slotDefault()
+{
+ GreycstorationSettings settings;
+ settings.setResizeDefaultSettings();
+
+ d->settingsWidget->setSettings(settings);
+ d->useGreycstorationBox->setChecked(false);
+ slotRestorationToggled(d->useGreycstorationBox->isChecked());
+
+ d->preserveRatioBox->blockSignals(true);
+ d->wInput->blockSignals(true);
+ d->hInput->blockSignals(true);
+ d->wpInput->blockSignals(true);
+ d->hpInput->blockSignals(true);
+ d->preserveRatioBox->setChecked(true);
+ d->wInput->setValue(d->orgWidth);
+ d->hInput->setValue(d->orgHeight);
+ d->wpInput->setValue(100.0);
+ d->hpInput->setValue(100.0);
+ d->preserveRatioBox->blockSignals(false);
+ d->wInput->blockSignals(false);
+ d->hInput->blockSignals(false);
+ d->wpInput->blockSignals(false);
+ d->hpInput->blockSignals(false);
+}
+
+void ImageResize::slotValuesChanged()
+{
+ enableButton(Ok, true);
+ d->wInput->blockSignals(true);
+ d->hInput->blockSignals(true);
+ d->wpInput->blockSignals(true);
+ d->hpInput->blockSignals(true);
+
+ TQString s(sender()->name());
+
+ if (s == "d->wInput")
+ {
+ double val = d->wInput->value();
+ double wp = val/(double)(d->orgWidth) * 100.0;
+ d->wpInput->setValue(wp);
+
+ if (d->preserveRatioBox->isChecked())
+ {
+ d->hpInput->setValue(wp);
+ int h = (int)(wp*d->orgHeight/100);
+ d->hInput->setValue(h);
+ }
+ }
+ else if (s == "d->hInput")
+ {
+ double val = d->hInput->value();
+ double hp = val/(double)(d->orgHeight) * 100.0;
+ d->hpInput->setValue(hp);
+
+ if (d->preserveRatioBox->isChecked())
+ {
+ d->wpInput->setValue(hp);
+ int w = (int)(hp*d->orgWidth/100);
+ d->wInput->setValue(w);
+ }
+ }
+ else if (s == "d->wpInput")
+ {
+ double val = d->wpInput->value();
+ int w = (int)(val*d->orgWidth/100);
+ d->wInput->setValue(w);
+
+ if (d->preserveRatioBox->isChecked())
+ {
+ d->hpInput->setValue(val);
+ int h = (int)(val*d->orgHeight/100);
+ d->hInput->setValue(h);
+ }
+ }
+ else if (s == "d->hpInput")
+ {
+ double val = d->hpInput->value();
+ int h = (int)(val*d->orgHeight/100);
+ d->hInput->setValue(h);
+
+ if (d->preserveRatioBox->isChecked())
+ {
+ d->wpInput->setValue(val);
+ int w = (int)(val*d->orgWidth/100);
+ d->wInput->setValue(w);
+ }
+ }
+
+ d->prevW = d->wInput->value();
+ d->prevH = d->hInput->value();
+ d->prevWP = d->wpInput->value();
+ d->prevHP = d->hpInput->value();
+
+ d->wInput->blockSignals(false);
+ d->hInput->blockSignals(false);
+ d->wpInput->blockSignals(false);
+ d->hpInput->blockSignals(false);
+}
+
+void ImageResize::slotCancel()
+{
+ if (d->currentRenderingMode != ImageResizePriv::NoneRendering)
+ {
+ d->greycstorationIface->stopComputation();
+ d->parent->unsetCursor();
+ }
+
+ done(Cancel);
+}
+
+void ImageResize::processCImgURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void ImageResize::closeEvent(TQCloseEvent *e)
+{
+ if (d->currentRenderingMode != ImageResizePriv::NoneRendering)
+ {
+ d->greycstorationIface->stopComputation();
+ d->parent->unsetCursor();
+ }
+
+ e->accept();
+}
+
+void ImageResize::slotOk()
+{
+ if (d->prevW != d->wInput->value() || d->prevH != d->hInput->value() ||
+ d->prevWP != d->wpInput->value() || d->prevHP != d->hpInput->value())
+ slotValuesChanged();
+
+ d->currentRenderingMode = ImageResizePriv::FinalRendering;
+ d->mainTab->setCurrentPage(0);
+ d->settingsWidget->setEnabled(false);
+ d->preserveRatioBox->setEnabled(false);
+ d->useGreycstorationBox->setEnabled(false);
+ d->wInput->setEnabled(false);
+ d->hInput->setEnabled(false);
+ d->wpInput->setEnabled(false);
+ d->hpInput->setEnabled(false);
+ enableButton(Ok, false);
+ enableButton(Default, false);
+ enableButton(User2, false);
+ enableButton(User3, false);
+
+ d->parent->setCursor( KCursor::waitCursor() );
+ writeUserSettings();
+ ImageIface iface(0, 0);
+ uchar *data = iface.getOriginalImage();
+ DImg image = DImg(iface.originalWidth(), iface.originalHeight(),
+ iface.originalSixteenBit(), iface.originalHasAlpha(), data);
+ delete [] data;
+
+ if (d->useGreycstorationBox->isChecked())
+ {
+ d->progressBar->setValue(0);
+ d->progressBar->setEnabled(true);
+
+ if (d->greycstorationIface)
+ {
+ delete d->greycstorationIface;
+ d->greycstorationIface = 0;
+ }
+
+ d->greycstorationIface = new GreycstorationIface(
+ &image, d->settingsWidget->getSettings(),
+ GreycstorationIface::Resize,
+ d->wInput->value(),
+ d->hInput->value(),
+ 0, this);
+ }
+ else
+ {
+ // See B.K.O #152192: CImg resize() sound like bugous or unadapted
+ // to resize image without good quality.
+
+ image.resize(d->wInput->value(), d->hInput->value());
+ iface.putOriginalImage(i18n("Resize"), image.bits(),
+ image.width(), image.height());
+ d->parent->unsetCursor();
+ accept();
+ }
+}
+
+void ImageResize::customEvent(TQCustomEvent *event)
+{
+ if (!event) return;
+
+ GreycstorationIface::EventData *data = (GreycstorationIface::EventData*) event->data();
+
+ if (!data) return;
+
+ if (data->starting) // Computation in progress !
+ {
+ d->progressBar->setValue(data->progress);
+ }
+ else
+ {
+ if (data->success) // Computation Completed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case ImageResizePriv::FinalRendering:
+ {
+ DDebug() << "Final resizing completed..." << endl;
+
+ ImageIface iface(0, 0);
+ DImg resizedImage = d->greycstorationIface->getTargetImage();
+
+ iface.putOriginalImage(i18n("Resize"), resizedImage.bits(),
+ resizedImage.width(), resizedImage.height());
+ d->parent->unsetCursor();
+ accept();
+ break;
+ }
+ }
+ }
+ else // Computation Failed !
+ {
+ switch (d->currentRenderingMode)
+ {
+ case ImageResizePriv::FinalRendering:
+ break;
+ }
+ }
+ }
+
+ delete data;
+}
+
+void ImageResize::slotUser3()
+{
+ KURL loadBlowupFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Resizing Settings File to Load")) );
+ if( loadBlowupFile.isEmpty() )
+ return;
+
+ TQFile file(loadBlowupFile.path());
+
+ if ( file.open(IO_ReadOnly) )
+ {
+ if (!d->settingsWidget->loadSettings(file, TQString("# Photograph Resizing Configuration File")))
+ {
+ KMessageBox::error(this,
+ i18n("\"%1\" is not a Photograph Resizing settings text file.")
+ .arg(loadBlowupFile.fileName()));
+ file.close();
+ return;
+ }
+ }
+ else
+ KMessageBox::error(this, i18n("Cannot load settings from the Photograph Resizing text file."));
+
+ file.close();
+}
+
+void ImageResize::slotUser2()
+{
+ KURL saveBlowupFile = KFileDialog::getSaveURL(TDEGlobalSettings::documentPath(),
+ TQString( "*" ), this,
+ TQString( i18n("Photograph Resizing Settings File to Save")) );
+ if( saveBlowupFile.isEmpty() )
+ return;
+
+ TQFile file(saveBlowupFile.path());
+
+ if ( file.open(IO_WriteOnly) )
+ d->settingsWidget->saveSettings(file, TQString("# Photograph Resizing Configuration File"));
+ else
+ KMessageBox::error(this, i18n("Cannot save settings to the Photograph Resizing text file."));
+
+ file.close();
+}
+
+} // NameSpace Digikam
+
diff --git a/src/utilities/imageeditor/tools/imageresize.h b/src/utilities/imageeditor/tools/imageresize.h
new file mode 100644
index 00000000..3f8b1c23
--- /dev/null
+++ b/src/utilities/imageeditor/tools/imageresize.h
@@ -0,0 +1,82 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-07
+ * Description : a tool to resize an image
+ *
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef IMAGE_RESIZE_H
+#define IMAGE_RESIZE_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class ImageResizePriv;
+
+class DIGIKAM_EXPORT ImageResize : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ ImageResize(TQWidget* parent);
+ ~ImageResize();
+
+protected:
+
+ void closeEvent(TQCloseEvent *e);
+
+private:
+
+ void customEvent(TQCustomEvent *event);
+ void writeUserSettings();
+
+private slots:
+
+ void slotOk();
+ void slotCancel();
+ void slotDefault();
+ void slotUser2();
+ void slotUser3();
+ void processCImgURL(const TQString&);
+ void slotValuesChanged();
+ void readUserSettings();
+ void slotRestorationToggled(bool);
+
+private:
+
+ ImageResizePriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif /* IMAGE_RESIZE_H */
diff --git a/src/utilities/lighttable/Makefile.am b/src/utilities/lighttable/Makefile.am
new file mode 100644
index 00000000..df38b5f9
--- /dev/null
+++ b/src/utilities/lighttable/Makefile.am
@@ -0,0 +1,28 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/thumbbar \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/dimg/filters \
+ -I$(top_srcdir)/src/libs/imageproperties \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ -I$(top_srcdir)/src/utilities/setup \
+ -I$(top_srcdir)/src/utilities/slideshow \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/utilities/imageeditor/editor \
+ -I$(top_builddir)/src/libs/dialogs \
+ $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) $(all_includes)
+
+noinst_LTLIBRARIES = liblighttable.la
+
+liblighttable_la_SOURCES = lighttablebar.cpp lighttablewindow.cpp lighttablepreview.cpp \
+ lighttableview.cpp
+
+liblighttable_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+rcdir = $(kde_datadir)/digikam
+rc_DATA = lighttablewindowui.rc
diff --git a/src/utilities/lighttable/lighttablebar.cpp b/src/utilities/lighttable/lighttablebar.cpp
new file mode 100644
index 00000000..cfb935ab
--- /dev/null
+++ b/src/utilities/lighttable/lighttablebar.cpp
@@ -0,0 +1,881 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-04-11
+ * Description : light table thumbs bar
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpixmap.h>
+#include <tqpainter.h>
+#include <tqimage.h>
+#include <tqcursor.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdepopupmenu.h>
+#include <kstandarddirs.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "dragobjects.h"
+#include "imageattributeswatch.h"
+#include "metadatahub.h"
+#include "ratingpopupmenu.h"
+#include "themeengine.h"
+#include "lighttablebar.h"
+#include "lighttablebar.moc"
+
+namespace Digikam
+{
+
+class LightTableBarPriv
+{
+
+public:
+
+ LightTableBarPriv()
+ {
+ navigateByPair = false;
+ toolTip = 0;
+ }
+
+ bool navigateByPair;
+
+ TQPixmap ratingPixmap;
+
+ LightTableBarToolTip *toolTip;
+};
+
+class LightTableBarItemPriv
+{
+
+public:
+
+ LightTableBarItemPriv()
+ {
+ onLeftPanel = false;
+ onRightPanel = false;
+ info = 0;
+ }
+
+ bool onLeftPanel;
+ bool onRightPanel;
+
+ ImageInfo *info;
+};
+
+LightTableBar::LightTableBar(TQWidget* parent, int orientation, bool exifRotate)
+ : ThumbBarView(parent, orientation, exifRotate)
+{
+ d = new LightTableBarPriv;
+ setMouseTracking(true);
+ readToolTipSettings();
+ d->toolTip = new LightTableBarToolTip(this);
+
+ // -- Load rating Pixmap ------------------------------------------
+
+ TDEGlobal::dirs()->addResourceType("digikam_rating", TDEGlobal::dirs()->kde_default("data")
+ + "digikam/data");
+ TQString ratingPixPath = TDEGlobal::dirs()->findResourceDir("digikam_rating", "rating.png");
+ ratingPixPath += "/rating.png";
+ d->ratingPixmap = TQPixmap(ratingPixPath);
+
+ TQPainter painter(&d->ratingPixmap);
+ painter.fillRect(0, 0, d->ratingPixmap.width(), d->ratingPixmap.height(),
+ ThemeEngine::instance()->textSpecialRegColor());
+ painter.end();
+
+ if (orientation ==TQt::Vertical)
+ setMinimumWidth(d->ratingPixmap.width()*5 + 6 + 2*getMargin());
+ else
+ setMinimumHeight(d->ratingPixmap.width()*5 + 6 + 2*getMargin());
+
+ // ----------------------------------------------------------------
+
+ ImageAttributesWatch *watch = ImageAttributesWatch::instance();
+
+ connect(watch, TQ_SIGNAL(signalImageRatingChanged(TQ_LLONG)),
+ this, TQ_SLOT(slotImageRatingChanged(TQ_LLONG)));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ connect(this, TQ_SIGNAL(signalItemSelected(ThumbBarItem*)),
+ this, TQ_SLOT(slotItemSelected(ThumbBarItem*)));
+}
+
+LightTableBar::~LightTableBar()
+{
+ delete d->toolTip;
+ delete d;
+}
+
+void LightTableBar::setNavigateByPair(bool b)
+{
+ d->navigateByPair = b;
+}
+
+void LightTableBar::slotImageRatingChanged(TQ_LLONG imageId)
+{
+ for (ThumbBarItem *item = firstItem(); item; item = item->next())
+ {
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+ if (ltItem->info()->id() == imageId)
+ {
+ triggerUpdate();
+ return;
+ }
+ }
+}
+
+void LightTableBar::contentsMouseReleaseEvent(TQMouseEvent *e)
+{
+ if (!e) return;
+
+ ThumbBarView::contentsMouseReleaseEvent(e);
+
+ TQPoint pos = TQCursor::pos();
+ LightTableBarItem *item = findItemByPos(e->pos());
+
+ RatingPopupMenu *ratingMenu = 0;
+
+ if (e->button() == TQt::RightButton)
+ {
+ TDEPopupMenu popmenu(this);
+
+ if (item)
+ {
+ popmenu.insertItem(SmallIcon("go-previous"), i18n("Show on left panel"), 10);
+ popmenu.insertItem(SmallIcon("go-next"), i18n("Show on right panel"), 11);
+ popmenu.insertItem(SmallIcon("editimage"), i18n("Edit"), 12);
+
+ if (d->navigateByPair)
+ {
+ popmenu.setItemEnabled(10, false);
+ popmenu.setItemEnabled(11, false);
+ }
+
+ popmenu.insertSeparator();
+ popmenu.insertItem(SmallIcon("window-close"), i18n("Remove item"), 13);
+ }
+
+ int totalItems = itemsURLs().count();
+ popmenu.insertItem(SmallIcon("editshred"), i18n("Clear all"), 14);
+ popmenu.setItemEnabled(14, totalItems ? true : false);
+
+ if (item)
+ {
+ popmenu.insertSeparator();
+
+ // Assign Star Rating -------------------------------------------
+
+ ratingMenu = new RatingPopupMenu();
+
+ connect(ratingMenu, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotAssignRating(int)));
+
+ popmenu.insertItem(i18n("Assign Rating"), ratingMenu);
+ }
+
+ switch(popmenu.exec(pos))
+ {
+ case 10: // Left panel
+ {
+ emit signalSetItemOnLeftPanel(item->info());
+ break;
+ }
+ case 11: // Right panel
+ {
+ emit signalSetItemOnRightPanel(item->info());
+ break;
+ }
+ case 12: // Edit
+ {
+ emit signalEditItem(item->info());
+ break;
+ }
+ case 13: // Remove
+ {
+ emit signalRemoveItem(item->info());
+ break;
+ }
+ case 14: // Clear All
+ {
+ emit signalClearAll();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ delete ratingMenu;
+}
+
+void LightTableBar::slotAssignRating(int rating)
+{
+ rating = TQMIN(5, TQMAX(0, rating));
+ ImageInfo *info = currentItemImageInfo();
+ if (info)
+ {
+ MetadataHub hub;
+ hub.load(info);
+ hub.setRating(rating);
+ hub.write(info, MetadataHub::PartialWrite);
+ hub.write(info->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void LightTableBar::slotAssignRatingNoStar()
+{
+ slotAssignRating(0);
+}
+
+void LightTableBar::slotAssignRatingOneStar()
+{
+ slotAssignRating(1);
+}
+
+void LightTableBar::slotAssignRatingTwoStar()
+{
+ slotAssignRating(2);
+}
+
+void LightTableBar::slotAssignRatingThreeStar()
+{
+ slotAssignRating(3);
+}
+
+void LightTableBar::slotAssignRatingFourStar()
+{
+ slotAssignRating(4);
+}
+
+void LightTableBar::slotAssignRatingFiveStar()
+{
+ slotAssignRating(5);
+}
+
+void LightTableBar::setOnLeftPanel(const ImageInfo* info)
+{
+ for (ThumbBarItem *item = firstItem(); item; item = item->next())
+ {
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+ if (ltItem)
+ {
+ if (info)
+ {
+ if (ltItem->info()->id() == info->id())
+ {
+ ltItem->setOnLeftPanel(true);
+ repaintItem(item);
+ }
+ else if (ltItem->isOnLeftPanel() == true)
+ {
+ ltItem->setOnLeftPanel(false);
+ repaintItem(item);
+ }
+ }
+ else if (ltItem->isOnLeftPanel() == true)
+ {
+ ltItem->setOnLeftPanel(false);
+ repaintItem(item);
+ }
+ }
+ }
+}
+
+void LightTableBar::setOnRightPanel(const ImageInfo* info)
+{
+ for (ThumbBarItem *item = firstItem(); item; item = item->next())
+ {
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+ if (ltItem)
+ {
+ if (info)
+ {
+ if (ltItem->info()->id() == info->id())
+ {
+ ltItem->setOnRightPanel(true);
+ repaintItem(item);
+ }
+ else if (ltItem->isOnRightPanel() == true)
+ {
+ ltItem->setOnRightPanel(false);
+ repaintItem(item);
+ }
+ }
+ else if (ltItem->isOnRightPanel() == true)
+ {
+ ltItem->setOnRightPanel(false);
+ repaintItem(item);
+ }
+ }
+ }
+}
+
+void LightTableBar::slotItemSelected(ThumbBarItem* item)
+{
+ if (item)
+ {
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+ if (ltItem)
+ {
+ emit signalLightTableBarItemSelected(ltItem->info());
+ return;
+ }
+ }
+
+ emit signalLightTableBarItemSelected(0);
+}
+
+ImageInfo* LightTableBar::currentItemImageInfo() const
+{
+ if (currentItem())
+ {
+ LightTableBarItem *item = dynamic_cast<LightTableBarItem*>(currentItem());
+ return item->info();
+ }
+
+ return 0;
+}
+
+ImageInfoList LightTableBar::itemsImageInfoList()
+{
+ ImageInfoList list;
+
+ for (ThumbBarItem *item = firstItem(); item; item = item->next())
+ {
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+ if (ltItem)
+ {
+ ImageInfo *info = new ImageInfo(*(ltItem->info()));
+ list.append(info);
+ }
+ }
+
+ return list;
+}
+
+void LightTableBar::setSelectedItem(LightTableBarItem* ltItem)
+{
+ ThumbBarItem *item = static_cast<ThumbBarItem*>(ltItem);
+ if (item) ThumbBarView::setSelected(item);
+}
+
+void LightTableBar::removeItem(const ImageInfo* info)
+{
+ if (!info) return;
+
+ LightTableBarItem* ltItem = findItemByInfo(info);
+ ThumbBarItem *item = static_cast<ThumbBarItem*>(ltItem);
+ if (item) ThumbBarView::removeItem(item);
+}
+
+LightTableBarItem* LightTableBar::findItemByInfo(const ImageInfo* info) const
+{
+ if (info)
+ {
+ for (ThumbBarItem *item = firstItem(); item; item = item->next())
+ {
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+ if (ltItem)
+ {
+ if (ltItem->info()->id() == info->id())
+ return ltItem;
+ }
+ }
+ }
+ return 0;
+}
+
+LightTableBarItem* LightTableBar::findItemByPos(const TQPoint& pos) const
+{
+ ThumbBarItem *item = findItem(pos);
+ if (item)
+ {
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+ return ltItem;
+ }
+
+ return 0;
+}
+
+void LightTableBar::readToolTipSettings()
+{
+ AlbumSettings* albumSettings = AlbumSettings::instance();
+ if (!albumSettings) return;
+
+ Digikam::ThumbBarToolTipSettings settings;
+ settings.showToolTips = albumSettings->getShowToolTips();
+ settings.showFileName = albumSettings->getToolTipsShowFileName();
+ settings.showFileDate = albumSettings->getToolTipsShowFileDate();
+ settings.showFileSize = albumSettings->getToolTipsShowFileSize();
+ settings.showImageType = albumSettings->getToolTipsShowImageType();
+ settings.showImageDim = albumSettings->getToolTipsShowImageDim();
+ settings.showPhotoMake = albumSettings->getToolTipsShowPhotoMake();
+ settings.showPhotoDate = albumSettings->getToolTipsShowPhotoDate();
+ settings.showPhotoFocal = albumSettings->getToolTipsShowPhotoFocal();
+ settings.showPhotoExpo = albumSettings->getToolTipsShowPhotoExpo();
+ settings.showPhotoMode = albumSettings->getToolTipsShowPhotoMode();
+ settings.showPhotoFlash = albumSettings->getToolTipsShowPhotoFlash();
+ settings.showPhotoWB = albumSettings->getToolTipsShowPhotoWB();
+ setToolTipSettings(settings);
+}
+
+void LightTableBar::viewportPaintEvent(TQPaintEvent* e)
+{
+ ThemeEngine* te = ThemeEngine::instance();
+ TQRect er(e->rect());
+ TQPixmap bgPix;
+
+ if (countItems() > 0)
+ {
+ int cy, cx, ts, y1, y2, x1, x2;
+ TQPixmap tile;
+
+ if (getOrientation() ==TQt::Vertical)
+ {
+ cy = viewportToContents(er.topLeft()).y();
+
+ bgPix.resize(contentsRect().width(), er.height());
+
+ ts = getTileSize() + 2*getMargin();
+ tile.resize(visibleWidth(), ts);
+
+ y1 = (cy/ts)*ts;
+ y2 = ((y1 + er.height())/ts +1)*ts;
+ }
+ else
+ {
+ cx = viewportToContents(er.topLeft()).x();
+
+ bgPix.resize(er.width(), contentsRect().height());
+
+ ts = getTileSize() + 2*getMargin();
+ tile.resize(ts, visibleHeight());
+
+ x1 = (cx/ts)*ts;
+ x2 = ((x1 + er.width())/ts +1)*ts;
+ }
+
+ bgPix.fill(te->baseColor());
+
+ for (ThumbBarItem *item = firstItem(); item; item = item->next())
+ {
+ if (getOrientation() ==TQt::Vertical)
+ {
+ if (y1 <= item->position() && item->position() <= y2)
+ {
+ if (item == currentItem())
+ tile = te->thumbSelPixmap(tile.width(), tile.height());
+ else
+ tile = te->thumbRegPixmap(tile.width(), tile.height());
+
+ TQPainter p(&tile);
+ if (item == currentItem())
+ {
+ p.setPen(TQPen(te->textSelColor(), 3));
+ p.drawRect(2, 2, tile.width()-2, tile.height()-2);
+ }
+ else
+ {
+ p.setPen(TQPen(te->textRegColor(), 1));
+ p.drawRect(0, 0, tile.width(), tile.height());
+ }
+ p.end();
+
+ if (item->pixmap())
+ {
+ TQPixmap pix;
+ pix.convertFromImage(TQImage(item->pixmap()->convertToImage()).
+ smoothScale(getTileSize(), getTileSize(), TQImage::ScaleMin));
+ int x = (tile.width() - pix.width())/2;
+ int y = (tile.height() - pix.height())/2;
+ bitBlt(&tile, x, y, &pix);
+
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+
+ if (ltItem->isOnLeftPanel())
+ {
+ TQPixmap lPix = SmallIcon("go-previous");
+ bitBlt(&tile, getMargin(), getMargin(), &lPix);
+ }
+ if (ltItem->isOnRightPanel())
+ {
+ TQPixmap rPix = SmallIcon("go-next");
+ bitBlt(&tile, tile.width() - getMargin() - rPix.width(), getMargin(), &rPix);
+ }
+
+ TQRect r(0, tile.height()-getMargin()-d->ratingPixmap.height(),
+ tile.width(), d->ratingPixmap.height());
+ int rating = ltItem->info()->rating();
+ int xr = (r.width() - rating * d->ratingPixmap.width())/2;
+ int wr = rating * d->ratingPixmap.width();
+ TQPainter p(&tile);
+ p.drawTiledPixmap(xr, r.y(), wr, r.height(), d->ratingPixmap);
+ }
+
+ bitBlt(&bgPix, 0, item->position() - cy, &tile);
+ }
+ }
+ else
+ {
+ if (x1 <= item->position() && item->position() <= x2)
+ {
+ if (item == currentItem())
+ tile = te->thumbSelPixmap(tile.width(), tile.height());
+ else
+ tile = te->thumbRegPixmap(tile.width(), tile.height());
+
+ TQPainter p(&tile);
+ if (item == currentItem())
+ {
+ p.setPen(TQPen(te->textSelColor(), 2));
+ p.drawRect(1, 1, tile.width()-1, tile.height()-1);
+ }
+ else
+ {
+ p.setPen(TQPen(te->textRegColor(), 1));
+ p.drawRect(0, 0, tile.width(), tile.height());
+ }
+ p.end();
+
+ if (item->pixmap())
+ {
+ TQPixmap pix;
+ pix.convertFromImage(TQImage(item->pixmap()->convertToImage()).
+ smoothScale(getTileSize(), getTileSize(), TQImage::ScaleMin));
+ int x = (tile.width() - pix.width())/2;
+ int y = (tile.height()- pix.height())/2;
+ bitBlt(&tile, x, y, &pix);
+
+ LightTableBarItem *ltItem = dynamic_cast<LightTableBarItem*>(item);
+
+ if (ltItem->isOnLeftPanel())
+ {
+ TQPixmap lPix = SmallIcon("go-previous");
+ bitBlt(&tile, getMargin(), getMargin(), &lPix);
+ }
+ if (ltItem->isOnRightPanel())
+ {
+ TQPixmap rPix = SmallIcon("go-next");
+ bitBlt(&tile, tile.width() - getMargin() - rPix.width(), getMargin(), &rPix);
+ }
+
+ TQRect r(0, tile.height()-getMargin()-d->ratingPixmap.height(),
+ tile.width(), d->ratingPixmap.height());
+ int rating = ltItem->info()->rating();
+ int xr = (r.width() - rating * d->ratingPixmap.width())/2;
+ int wr = rating * d->ratingPixmap.width();
+ TQPainter p(&tile);
+ p.drawTiledPixmap(xr, r.y(), wr, r.height(), d->ratingPixmap);
+ }
+
+ bitBlt(&bgPix, item->position() - cx, 0, &tile);
+ }
+ }
+ }
+
+ if (getOrientation() ==TQt::Vertical)
+ bitBlt(viewport(), 0, er.y(), &bgPix);
+ else
+ bitBlt(viewport(), er.x(), 0, &bgPix);
+ }
+ else
+ {
+ bgPix.resize(contentsRect().width(), contentsRect().height());
+ bgPix.fill(te->baseColor());
+ TQPainter p(&bgPix);
+ p.setPen(TQPen(te->textRegColor()));
+ p.drawText(0, 0, bgPix.width(), bgPix.height(),
+ TQt::AlignCenter|TQt::WordBreak,
+ i18n("Drag and drop images here"));
+ p.end();
+ bitBlt(viewport(), 0, 0, &bgPix);
+ }
+}
+
+void LightTableBar::startDrag()
+{
+ if (!currentItem()) return;
+
+ KURL::List urls;
+ KURL::List kioURLs;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+
+ LightTableBarItem *item = dynamic_cast<LightTableBarItem*>(currentItem());
+
+ urls.append(item->info()->kurl());
+ kioURLs.append(item->info()->kurlForKIO());
+ imageIDs.append(item->info()->id());
+ albumIDs.append(item->info()->albumID());
+
+ TQPixmap icon(DesktopIcon("image-x-generic", 48));
+ int w = icon.width();
+ int h = icon.height();
+
+ TQPixmap pix(w+4,h+4);
+ TQPainter p(&pix);
+ p.fillRect(0, 0, w+4, h+4, TQColor(TQt::white));
+ p.setPen(TQPen(TQt::black, 1));
+ p.drawRect(0, 0, w+4, h+4);
+ p.drawPixmap(2, 2, icon);
+ p.end();
+
+ TQDragObject* drag = 0;
+
+ drag = new ItemDrag(urls, kioURLs, albumIDs, imageIDs, this);
+ if (drag)
+ {
+ drag->setPixmap(pix);
+ drag->drag();
+ }
+}
+
+void LightTableBar::contentsDragMoveEvent(TQDragMoveEvent *e)
+{
+ int albumID;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+ KURL::List urls;
+ KURL::List kioURLs;
+
+ if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs) ||
+ AlbumDrag::decode(e, urls, albumID) ||
+ TagDrag::canDecode(e))
+ {
+ e->accept();
+ return;
+ }
+
+ e->ignore();
+}
+
+void LightTableBar::contentsDropEvent(TQDropEvent *e)
+{
+ int albumID;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+ KURL::List urls;
+ KURL::List kioURLs;
+
+ if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
+ {
+ ImageInfoList imageInfoList;
+
+ for (TQValueList<int>::const_iterator it = imageIDs.begin();
+ it != imageIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ if (!findItemByInfo(info))
+ {
+ imageInfoList.append(info);
+ }
+ else
+ {
+ delete info;
+ }
+ }
+
+ emit signalDroppedItems(imageInfoList);
+ e->accept();
+ }
+ else if (AlbumDrag::decode(e, urls, albumID))
+ {
+ TQValueList<TQ_LLONG> itemIDs = AlbumManager::instance()->albumDB()->getItemIDsInAlbum(albumID);
+ ImageInfoList imageInfoList;
+
+ for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
+ it != itemIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ if (!findItemByInfo(info))
+ {
+ imageInfoList.append(info);
+ }
+ else
+ {
+ delete info;
+ }
+ }
+
+ emit signalDroppedItems(imageInfoList);
+ e->accept();
+ }
+ else if(TagDrag::canDecode(e))
+ {
+ TQByteArray ba = e->encodedData("digikam/tag-id");
+ TQDataStream ds(ba, IO_ReadOnly);
+ int tagID;
+ ds >> tagID;
+
+ AlbumManager* man = AlbumManager::instance();
+ TQValueList<TQ_LLONG> itemIDs = man->albumDB()->getItemIDsInTag(tagID, true);
+ ImageInfoList imageInfoList;
+
+ for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
+ it != itemIDs.end(); ++it)
+ {
+ ImageInfo *info = new ImageInfo(*it);
+ if (!findItemByInfo(info))
+ {
+ imageInfoList.append(info);
+ }
+ else
+ {
+ delete info;
+ }
+ }
+
+ emit signalDroppedItems(imageInfoList);
+ e->accept();
+ }
+ else
+ {
+ e->ignore();
+ }
+}
+
+void LightTableBar::slotThemeChanged()
+{
+ TDEGlobal::dirs()->addResourceType("digikam_rating", TDEGlobal::dirs()->kde_default("data")
+ + "digikam/data");
+ TQString ratingPixPath = TDEGlobal::dirs()->findResourceDir("digikam_rating", "rating.png");
+ ratingPixPath += "/rating.png";
+ d->ratingPixmap = TQPixmap(ratingPixPath);
+
+ TQPainter painter(&d->ratingPixmap);
+ painter.fillRect(0, 0, d->ratingPixmap.width(), d->ratingPixmap.height(),
+ ThemeEngine::instance()->textSpecialRegColor());
+ painter.end();
+
+ slotUpdate();
+}
+
+// -------------------------------------------------------------------------
+
+LightTableBarItem::LightTableBarItem(LightTableBar *view, ImageInfo *info)
+ : ThumbBarItem(view, info->kurl())
+{
+ d = new LightTableBarItemPriv;
+ d->info = info;
+}
+
+LightTableBarItem::~LightTableBarItem()
+{
+ delete d;
+}
+
+ImageInfo* LightTableBarItem::info() const
+{
+ return d->info;
+}
+
+void LightTableBarItem::setOnLeftPanel(bool on)
+{
+ d->onLeftPanel = on;
+}
+
+void LightTableBarItem::setOnRightPanel(bool on)
+{
+ d->onRightPanel = on;
+}
+
+bool LightTableBarItem::isOnLeftPanel() const
+{
+ return d->onLeftPanel;
+}
+
+bool LightTableBarItem::isOnRightPanel() const
+{
+ return d->onRightPanel;
+}
+
+// -------------------------------------------------------------------------
+
+LightTableBarToolTip::LightTableBarToolTip(ThumbBarView *parent)
+ : ThumbBarToolTip(parent)
+{
+}
+
+TQString LightTableBarToolTip::tipContentExtraData(ThumbBarItem *item)
+{
+ TQString tip, str;
+ AlbumSettings* settings = AlbumSettings::instance();
+ ImageInfo* info = static_cast<LightTableBarItem *>(item)->info();
+
+ if (settings)
+ {
+ if (settings->getToolTipsShowAlbumName() ||
+ settings->getToolTipsShowComments() ||
+ settings->getToolTipsShowTags() ||
+ settings->getToolTipsShowRating())
+ {
+ tip += m_headBeg + i18n("digiKam Properties") + m_headEnd;
+
+ if (settings->getToolTipsShowAlbumName())
+ {
+ PAlbum* album = info->album();
+ if (album)
+ tip += m_cellSpecBeg + i18n("Album:") + m_cellSpecMid +
+ album->url().remove(0, 1) + m_cellSpecEnd;
+ }
+
+ if (settings->getToolTipsShowComments())
+ {
+ str = info->caption();
+ if (str.isEmpty()) str = TQString("---");
+ tip += m_cellSpecBeg + i18n("Caption:") + m_cellSpecMid + breakString(str) + m_cellSpecEnd;
+ }
+
+ if (settings->getToolTipsShowTags())
+ {
+ TQStringList tagPaths = info->tagPaths(false);
+
+ str = tagPaths.join(", ");
+ if (str.isEmpty()) str = TQString("---");
+ if (str.length() > m_maxStringLen) str = str.left(m_maxStringLen-3) + "...";
+ tip += m_cellSpecBeg + i18n("Tags:") + m_cellSpecMid + str + m_cellSpecEnd;
+ }
+
+ if (settings->getToolTipsShowRating())
+ {
+ str.fill( '*', info->rating() );
+ if (str.isEmpty()) str = TQString("---");
+ tip += m_cellSpecBeg + i18n("Rating:") + m_cellSpecMid + str + m_cellSpecEnd;
+ }
+ }
+ }
+
+ return tip;
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/lighttable/lighttablebar.h b/src/utilities/lighttable/lighttablebar.h
new file mode 100644
index 00000000..b64e93a6
--- /dev/null
+++ b/src/utilities/lighttable/lighttablebar.h
@@ -0,0 +1,153 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-04-11
+ * Description : light table thumbs bar
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef LIGHTTABLEBAR_H
+#define LIGHTTABLEBAR_H
+
+// Local includes.
+
+#include "thumbbar.h"
+#include "imageinfo.h"
+#include "digikam_export.h"
+
+class TQDragMoveEvent;
+class TQDropEvent;
+class TQMouseEvent;
+class TQPaintEvent;
+
+class KURL;
+
+namespace Digikam
+{
+
+class LightTableBarItem;
+class LightTableBarItemPriv;
+class LightTableBarPriv;
+
+class DIGIKAM_EXPORT LightTableBar : public ThumbBarView
+{
+ TQ_OBJECT
+
+
+public:
+
+ LightTableBar(TQWidget* parent, int orientation=Vertical, bool exifRotate=true);
+ ~LightTableBar();
+
+ ImageInfo* currentItemImageInfo() const;
+ ImageInfoList itemsImageInfoList();
+
+ void setSelectedItem(LightTableBarItem* ltItem);
+
+ LightTableBarItem* findItemByInfo(const ImageInfo* info) const;
+ LightTableBarItem* findItemByPos(const TQPoint& pos) const;
+
+ /** Read tool tip settings from Album Settings instance */
+ void readToolTipSettings();
+
+ void setOnLeftPanel(const ImageInfo* info);
+ void setOnRightPanel(const ImageInfo* info);
+
+ void removeItem(const ImageInfo* info);
+
+ void setNavigateByPair(bool b);
+
+signals:
+
+ void signalLightTableBarItemSelected(ImageInfo*);
+ void signalSetItemOnLeftPanel(ImageInfo*);
+ void signalSetItemOnRightPanel(ImageInfo*);
+ void signalEditItem(ImageInfo*);
+ void signalRemoveItem(ImageInfo*);
+ void signalClearAll();
+ void signalDroppedItems(const ImageInfoList&);
+
+private:
+
+ void viewportPaintEvent(TQPaintEvent*);
+ void contentsMouseReleaseEvent(TQMouseEvent*);
+ void startDrag();
+ void contentsDragMoveEvent(TQDragMoveEvent*);
+ void contentsDropEvent(TQDropEvent*);
+
+private slots:
+
+ void slotImageRatingChanged(TQ_LLONG);
+ void slotItemSelected(ThumbBarItem*);
+
+ void slotAssignRatingNoStar();
+ void slotAssignRatingOneStar();
+ void slotAssignRatingTwoStar();
+ void slotAssignRatingThreeStar();
+ void slotAssignRatingFourStar();
+ void slotAssignRatingFiveStar();
+ void slotAssignRating(int);
+
+ void slotThemeChanged();
+
+private:
+
+ LightTableBarPriv *d;
+
+ friend class LightTableBarItem;
+};
+
+// -------------------------------------------------------------------------
+
+class DIGIKAM_EXPORT LightTableBarItem : public ThumbBarItem
+{
+public:
+
+ LightTableBarItem(LightTableBar *view, ImageInfo *info);
+ ~LightTableBarItem();
+
+ ImageInfo* info() const;
+
+ void setOnLeftPanel(bool on);
+ void setOnRightPanel(bool on);
+ bool isOnLeftPanel() const;
+ bool isOnRightPanel() const;
+
+private:
+
+ LightTableBarItemPriv *d;
+
+ friend class LightTableBar;
+};
+
+// -------------------------------------------------------------------------
+
+class DIGIKAM_EXPORT LightTableBarToolTip : public ThumbBarToolTip
+{
+public:
+
+ LightTableBarToolTip(ThumbBarView *parent);
+
+private:
+
+ TQString tipContentExtraData(ThumbBarItem *item);
+};
+
+} // NameSpace Digikam
+
+#endif /* LIGHTTABLEBAR_H */
diff --git a/src/utilities/lighttable/lighttablepreview.cpp b/src/utilities/lighttable/lighttablepreview.cpp
new file mode 100644
index 00000000..100427ec
--- /dev/null
+++ b/src/utilities/lighttable/lighttablepreview.cpp
@@ -0,0 +1,777 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-21-12
+ * Description : digiKam light table preview item.
+ *
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqpainter.h>
+#include <tqcursor.h>
+#include <tqstring.h>
+#include <tqvaluevector.h>
+#include <tqfileinfo.h>
+#include <tqtoolbutton.h>
+#include <tqtooltip.h>
+#include <tqpixmap.h>
+#include <tqdrawutil.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+#include <tdelocale.h>
+#include <kservice.h>
+#include <krun.h>
+#include <ktrader.h>
+#include <kmimetype.h>
+#include <kcursor.h>
+#include <kdatetbl.h>
+#include <kiconloader.h>
+#include <kprocess.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "dimg.h"
+#include "ddebug.h"
+#include "albumdb.h"
+#include "constants.h"
+#include "albummanager.h"
+#include "albumsettings.h"
+#include "dragobjects.h"
+#include "dmetadata.h"
+#include "dpopupmenu.h"
+#include "metadatahub.h"
+#include "paniconwidget.h"
+#include "previewloadthread.h"
+#include "loadingdescription.h"
+#include "tagspopupmenu.h"
+#include "ratingpopupmenu.h"
+#include "themeengine.h"
+#include "lighttablepreview.h"
+#include "lighttablepreview.moc"
+
+namespace Digikam
+{
+
+class LightTablePreviewPriv
+{
+public:
+
+ LightTablePreviewPriv()
+ {
+ panIconPopup = 0;
+ panIconWidget = 0;
+ cornerButton = 0;
+ previewThread = 0;
+ previewPreloadThread = 0;
+ imageInfo = 0;
+ hasPrev = false;
+ hasNext = false;
+ selected = false;
+ dragAndDropEnabled = true;
+ loadFullImageSize = false;
+ currentFitWindowZoom = 0;
+ previewSize = 1024;
+ }
+
+ bool hasPrev;
+ bool hasNext;
+ bool selected;
+ bool dragAndDropEnabled;
+ bool loadFullImageSize;
+
+ int previewSize;
+
+ double currentFitWindowZoom;
+
+ TQString path;
+ TQString nextPath;
+ TQString previousPath;
+
+ TQToolButton *cornerButton;
+
+ TDEPopupFrame *panIconPopup;
+
+ PanIconWidget *panIconWidget;
+
+ DImg preview;
+
+ ImageInfo *imageInfo;
+
+ PreviewLoadThread *previewThread;
+ PreviewLoadThread *previewPreloadThread;
+};
+
+LightTablePreview::LightTablePreview(TQWidget *parent)
+ : PreviewWidget(parent)
+{
+ d = new LightTablePreviewPriv;
+
+ // get preview size from screen size, but limit from VGA to WTQXGA
+ d->previewSize = TQMAX(TDEApplication::desktop()->height(),
+ TDEApplication::desktop()->width());
+ if (d->previewSize < 640)
+ d->previewSize = 640;
+ if (d->previewSize > 2560)
+ d->previewSize = 2560;
+
+ viewport()->setAcceptDrops(true);
+ setAcceptDrops(true);
+
+ slotThemeChanged();
+ setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);
+
+ d->cornerButton = new TQToolButton(this);
+ d->cornerButton->setIconSet(SmallIcon("move"));
+ d->cornerButton->hide();
+ TQToolTip::add(d->cornerButton, i18n("Pan the image"));
+ setCornerWidget(d->cornerButton);
+
+ setLineWidth(5);
+ setSelected(false);
+
+ // ------------------------------------------------------------
+
+ connect(d->cornerButton, TQ_SIGNAL(pressed()),
+ this, TQ_SLOT(slotCornerButtonPressed()));
+
+ connect(this, TQ_SIGNAL(signalRightButtonClicked()),
+ this, TQ_SLOT(slotContextMenu()));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ // ------------------------------------------------------------
+
+ slotReset();
+}
+
+LightTablePreview::~LightTablePreview()
+{
+ delete d->previewThread;
+ delete d->previewPreloadThread;
+ delete d;
+}
+
+void LightTablePreview::setLoadFullImageSize(bool b)
+{
+ d->loadFullImageSize = b;
+ reload();
+}
+
+void LightTablePreview::setDragAndDropEnabled(bool b)
+{
+ d->dragAndDropEnabled = b;
+}
+
+void LightTablePreview::setDragAndDropMessage()
+{
+ if (d->dragAndDropEnabled)
+ {
+ TQPixmap pix(visibleWidth(), visibleHeight());
+ pix.fill(ThemeEngine::instance()->baseColor());
+ TQPainter p(&pix);
+ p.setPen(TQPen(ThemeEngine::instance()->textRegColor()));
+ p.drawText(0, 0, pix.width(), pix.height(),
+ TQt::AlignCenter|TQt::WordBreak,
+ i18n("Drag and drop an image here"));
+ p.end();
+ setImage(pix.convertToImage());
+ }
+}
+
+void LightTablePreview::setImage(const DImg& image)
+{
+ d->preview = image;
+
+ updateZoomAndSize(true);
+
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+}
+
+DImg& LightTablePreview::getImage() const
+{
+ return d->preview;
+}
+
+TQSize LightTablePreview::getImageSize()
+{
+ return d->preview.size();
+}
+
+void LightTablePreview::reload()
+{
+ // cache is cleaned from AlbumIconView::refreshItems
+ setImagePath(d->path);
+}
+
+void LightTablePreview::setPreviousNextPaths(const TQString& previous, const TQString &next)
+{
+ d->nextPath = next;
+ d->previousPath = previous;
+}
+
+void LightTablePreview::setImagePath(const TQString& path)
+{
+ setCursor( KCursor::waitCursor() );
+
+ d->path = path;
+ d->nextPath = TQString();
+ d->previousPath = TQString();
+
+ if (d->path.isEmpty())
+ {
+ slotReset();
+ unsetCursor();
+ return;
+ }
+
+ if (!d->previewThread)
+ {
+ d->previewThread = new PreviewLoadThread();
+ connect(d->previewThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
+ this, TQ_SLOT(slotGotImagePreview(const LoadingDescription &, const DImg&)));
+ }
+ if (!d->previewPreloadThread)
+ {
+ d->previewPreloadThread = new PreviewLoadThread();
+ connect(d->previewPreloadThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
+ this, TQ_SLOT(slotNextPreload()));
+ }
+
+ if (d->loadFullImageSize)
+ d->previewThread->loadHighQuality(LoadingDescription(path, 0, AlbumSettings::instance()->getExifRotate()));
+ else
+ d->previewThread->load(LoadingDescription(path, d->previewSize, AlbumSettings::instance()->getExifRotate()));
+}
+
+void LightTablePreview::slotGotImagePreview(const LoadingDescription &description, const DImg& preview)
+{
+ if (description.filePath != d->path)
+ return;
+
+ if (preview.isNull())
+ {
+ TQPixmap pix(visibleWidth(), visibleHeight());
+ pix.fill(ThemeEngine::instance()->baseColor());
+ TQPainter p(&pix);
+ TQFileInfo info(d->path);
+ p.setPen(TQPen(ThemeEngine::instance()->textRegColor()));
+ p.drawText(0, 0, pix.width(), pix.height(),
+ TQt::AlignCenter|TQt::WordBreak,
+ i18n("Unable to display preview for\n\"%1\"")
+ .arg(info.fileName()));
+ p.end();
+ setImage(DImg(pix.convertToImage()));
+
+ emit signalPreviewLoaded(false);
+ }
+ else
+ {
+ DImg img(preview);
+ if (AlbumSettings::instance()->getExifRotate())
+ d->previewThread->exifRotate(img, description.filePath);
+ setImage(img);
+ emit signalPreviewLoaded(true);
+ }
+
+ unsetCursor();
+ slotNextPreload();
+}
+
+void LightTablePreview::slotNextPreload()
+{
+ TQString loadPath;
+ if (!d->nextPath.isNull())
+ {
+ loadPath = d->nextPath;
+ d->nextPath = TQString();
+ }
+ else if (!d->previousPath.isNull())
+ {
+ loadPath = d->previousPath;
+ d->previousPath = TQString();
+ }
+ else
+ return;
+
+ d->previewPreloadThread->load(LoadingDescription(loadPath, d->previewSize,
+ AlbumSettings::instance()->getExifRotate()));
+}
+
+void LightTablePreview::setImageInfo(ImageInfo* info, ImageInfo *previous, ImageInfo *next)
+{
+ d->imageInfo = info;
+ d->hasPrev = previous;
+ d->hasNext = next;
+
+ if (d->imageInfo)
+ setImagePath(info->filePath());
+ else
+ {
+ setImagePath();
+ setSelected(false);
+ }
+
+ setPreviousNextPaths(previous ? previous->filePath() : TQString(),
+ next ? next->filePath() : TQString());
+}
+
+ImageInfo* LightTablePreview::getImageInfo() const
+{
+ return d->imageInfo;
+}
+
+void LightTablePreview::slotContextMenu()
+{
+ RatingPopupMenu *ratingMenu = 0;
+ TagsPopupMenu *assignTagsMenu = 0;
+ TagsPopupMenu *removeTagsMenu = 0;
+
+ if (!d->imageInfo)
+ return;
+
+ //-- Open With Actions ------------------------------------
+
+ KURL url(d->imageInfo->kurl().path());
+ KMimeType::Ptr mimePtr = KMimeType::findByURL(url, 0, true, true);
+
+ TQValueVector<KService::Ptr> serviceVector;
+ TDETrader::OfferList offers = TDETrader::self()->query(mimePtr->name(), "Type == 'Application'");
+
+ TQPopupMenu openWithMenu;
+
+ TDETrader::OfferList::Iterator iter;
+ KService::Ptr ptr;
+ int index = 100;
+
+ for( iter = offers.begin(); iter != offers.end(); ++iter )
+ {
+ ptr = *iter;
+ openWithMenu.insertItem( ptr->pixmap(TDEIcon::Small), ptr->name(), index++);
+ serviceVector.push_back(ptr);
+ }
+
+ DPopupMenu popmenu(this);
+
+ //-- Zoom actions -----------------------------------------------
+
+ popmenu.insertItem(SmallIcon("viewmag"), i18n("Zoom in"), 17);
+ popmenu.insertItem(SmallIcon("zoom-out"), i18n("Zoom out"), 18);
+ popmenu.insertItem(SmallIcon("view_fit_window"), i18n("Fit to &Window"), 19);
+
+ //-- Edit actions -----------------------------------------------
+
+ popmenu.insertSeparator();
+ popmenu.insertItem(SmallIcon("slideshow"), i18n("SlideShow"), 16);
+ popmenu.insertItem(SmallIcon("editimage"), i18n("Edit..."), 12);
+ popmenu.insertItem(i18n("Open With"), &openWithMenu, 13);
+
+ //-- Trash action -------------------------------------------
+
+ popmenu.insertSeparator();
+ popmenu.insertItem(SmallIcon("edittrash"), i18n("Move to Trash"), 14);
+
+ // Bulk assignment/removal of tags --------------------------
+
+ TQ_LLONG id = d->imageInfo->id();
+ TQValueList<TQ_LLONG> idList;
+ idList.append(id);
+
+ assignTagsMenu = new TagsPopupMenu(idList, 1000, TagsPopupMenu::ASSIGN);
+ removeTagsMenu = new TagsPopupMenu(idList, 2000, TagsPopupMenu::REMOVE);
+
+ popmenu.insertSeparator();
+
+ popmenu.insertItem(i18n("Assign Tag"), assignTagsMenu);
+ int i = popmenu.insertItem(i18n("Remove Tag"), removeTagsMenu);
+
+ connect(assignTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotAssignTag(int)));
+
+ connect(removeTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
+ this, TQ_SLOT(slotRemoveTag(int)));
+
+ AlbumDB* db = AlbumManager::instance()->albumDB();
+ if (!db->hasTags( idList ))
+ popmenu.setItemEnabled(i, false);
+
+ popmenu.insertSeparator();
+
+ // Assign Star Rating -------------------------------------------
+
+ ratingMenu = new RatingPopupMenu();
+
+ connect(ratingMenu, TQ_SIGNAL(activated(int)),
+ this, TQ_SLOT(slotAssignRating(int)));
+
+ popmenu.insertItem(i18n("Assign Rating"), ratingMenu);
+
+ // --------------------------------------------------------
+
+ int idm = popmenu.exec(TQCursor::pos());
+
+ switch(idm)
+ {
+ case 12: // Edit...
+ {
+ emit signalEditItem(d->imageInfo);
+ break;
+ }
+
+ case 14: // Move to trash
+ {
+ emit signalDeleteItem(d->imageInfo);
+ break;
+ }
+
+ case 16: // SlideShow
+ {
+ emit signalSlideShow();
+ break;
+ }
+
+ case 17: // Zoom in
+ {
+ slotIncreaseZoom();
+ break;
+ }
+
+ case 18: // Zoom out
+ {
+ slotDecreaseZoom();
+ break;
+ }
+
+ case 19: // Fit to window
+ {
+ fitToWindow();
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ // Open With...
+ if (idm >= 100 && idm < 1000)
+ {
+ KService::Ptr imageServicePtr = serviceVector[idm-100];
+ KRun::run(*imageServicePtr, url);
+ }
+
+ serviceVector.clear();
+ delete assignTagsMenu;
+ delete removeTagsMenu;
+ delete ratingMenu;
+}
+
+void LightTablePreview::slotAssignTag(int tagID)
+{
+ if (d->imageInfo)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfo);
+ hub.setTag(tagID, true);
+ hub.write(d->imageInfo, MetadataHub::PartialWrite);
+ hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void LightTablePreview::slotRemoveTag(int tagID)
+{
+ if (d->imageInfo)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfo);
+ hub.setTag(tagID, false);
+ hub.write(d->imageInfo, MetadataHub::PartialWrite);
+ hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void LightTablePreview::slotAssignRating(int rating)
+{
+ rating = TQMIN(RatingMax, TQMAX(RatingMin, rating));
+ if (d->imageInfo)
+ {
+ MetadataHub hub;
+ hub.load(d->imageInfo);
+ hub.setRating(rating);
+ hub.write(d->imageInfo, MetadataHub::PartialWrite);
+ hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
+ }
+}
+
+void LightTablePreview::slotThemeChanged()
+{
+ setBackgroundColor(ThemeEngine::instance()->baseColor());
+ frameChanged();
+}
+
+void LightTablePreview::slotCornerButtonPressed()
+{
+ if (d->panIconPopup)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ }
+
+ d->panIconPopup = new TDEPopupFrame(this);
+ PanIconWidget *pan = new PanIconWidget(d->panIconPopup);
+ pan->setImage(180, 120, getImage());
+ d->panIconPopup->setMainWidget(pan);
+
+ TQRect r((int)(contentsX() / zoomFactor()), (int)(contentsY() / zoomFactor()),
+ (int)(visibleWidth() / zoomFactor()), (int)(visibleHeight() / zoomFactor()));
+ pan->setRegionSelection(r);
+ pan->setMouseFocus();
+
+ connect(pan, TQ_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
+ this, TQ_SLOT(slotPanIconSelectionMoved(const TQRect&, bool)));
+
+ connect(pan, TQ_SIGNAL(signalHiden()),
+ this, TQ_SLOT(slotPanIconHiden()));
+
+ TQPoint g = mapToGlobal(viewport()->pos());
+ g.setX(g.x()+ viewport()->size().width());
+ g.setY(g.y()+ viewport()->size().height());
+ d->panIconPopup->popup(TQPoint(g.x() - d->panIconPopup->width(),
+ g.y() - d->panIconPopup->height()));
+
+ pan->setCursorToLocalRegionSelectionCenter();
+}
+
+void LightTablePreview::slotPanIconHiden()
+{
+ d->cornerButton->blockSignals(true);
+ d->cornerButton->animateClick();
+ d->cornerButton->blockSignals(false);
+}
+
+void LightTablePreview::slotPanIconSelectionMoved(const TQRect& r, bool b)
+{
+ setContentsPos((int)(r.x()*zoomFactor()), (int)(r.y()*zoomFactor()));
+
+ if (b)
+ {
+ d->panIconPopup->hide();
+ delete d->panIconPopup;
+ d->panIconPopup = 0;
+ slotPanIconHiden();
+ }
+}
+
+void LightTablePreview::zoomFactorChanged(double zoom)
+{
+ updateScrollBars();
+
+ if (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())
+ d->cornerButton->show();
+ else
+ d->cornerButton->hide();
+
+ PreviewWidget::zoomFactorChanged(zoom);
+}
+
+void LightTablePreview::resizeEvent(TQResizeEvent* e)
+{
+ if (!e) return;
+
+ TQScrollView::resizeEvent(e);
+
+ if (!d->imageInfo)
+ {
+ d->cornerButton->hide();
+ setDragAndDropMessage();
+ }
+
+ updateZoomAndSize(false);
+}
+
+void LightTablePreview::updateZoomAndSize(bool alwaysFitToWindow)
+{
+ // Set zoom for fit-in-window as minimum, but dont scale up images
+ // that are smaller than the available space, only scale down.
+ double zoom = calcAutoZoomFactor(ZoomInOnly);
+ setZoomMin(zoom);
+ setZoomMax(zoom*12.0);
+
+ // Is currently the zoom factor set to fit to window? Then set it again to fit the new size.
+ if (zoomFactor() < zoom || alwaysFitToWindow || zoomFactor() == d->currentFitWindowZoom)
+ {
+ setZoomFactor(zoom);
+ }
+
+ // store which zoom factor means it is fit to window
+ d->currentFitWindowZoom = zoom;
+
+ updateContentsSize();
+}
+
+int LightTablePreview::previewWidth()
+{
+ return d->preview.width();
+}
+
+int LightTablePreview::previewHeight()
+{
+ return d->preview.height();
+}
+
+bool LightTablePreview::previewIsNull()
+{
+ return d->preview.isNull();
+}
+
+void LightTablePreview::resetPreview()
+{
+ d->preview = DImg();
+ d->path = TQString();
+ d->imageInfo = 0;
+
+ setDragAndDropMessage();
+ updateZoomAndSize(true);
+ viewport()->setUpdatesEnabled(true);
+ viewport()->update();
+ emit signalPreviewLoaded(false);
+}
+
+void LightTablePreview::paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh)
+{
+ DImg img = d->preview.smoothScaleSection(sx, sy, sw, sh, tileSize(), tileSize());
+ TQPixmap pix2 = img.convertToPixmap();
+ bitBlt(pix, 0, 0, &pix2, 0, 0);
+}
+
+void LightTablePreview::contentsDragMoveEvent(TQDragMoveEvent *e)
+{
+ if (d->dragAndDropEnabled)
+ {
+ int albumID;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+ KURL::List urls;
+ KURL::List kioURLs;
+
+ if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs) ||
+ AlbumDrag::decode(e, urls, albumID) ||
+ TagDrag::canDecode(e))
+ {
+ e->accept();
+ return;
+ }
+ }
+
+ e->ignore();
+}
+
+void LightTablePreview::contentsDropEvent(TQDropEvent *e)
+{
+ if (d->dragAndDropEnabled)
+ {
+ int albumID;
+ TQValueList<int> albumIDs;
+ TQValueList<int> imageIDs;
+ KURL::List urls;
+ KURL::List kioURLs;
+ ImageInfoList list;
+
+ if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
+ {
+ for (TQValueList<int>::const_iterator it = imageIDs.begin();
+ it != imageIDs.end(); ++it)
+ {
+ list.append(new ImageInfo(*it));
+ }
+
+ emit signalDroppedItems(list);
+ e->accept();
+ return;
+ }
+ else if (AlbumDrag::decode(e, urls, albumID))
+ {
+ TQValueList<TQ_LLONG> itemIDs = AlbumManager::instance()->albumDB()->getItemIDsInAlbum(albumID);
+
+ for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
+ it != itemIDs.end(); ++it)
+ {
+ list.append(new ImageInfo(*it));
+ }
+
+ emit signalDroppedItems(list);
+ e->accept();
+ return;
+ }
+ else if(TagDrag::canDecode(e))
+ {
+ TQByteArray ba = e->encodedData("digikam/tag-id");
+ TQDataStream ds(ba, IO_ReadOnly);
+ int tagID;
+ ds >> tagID;
+
+ AlbumManager* man = AlbumManager::instance();
+ TQValueList<TQ_LLONG> itemIDs = man->albumDB()->getItemIDsInTag(tagID, true);
+ ImageInfoList imageInfoList;
+
+ for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
+ it != itemIDs.end(); ++it)
+ {
+ list.append(new ImageInfo(*it));
+ }
+
+ emit signalDroppedItems(list);
+ e->accept();
+ return;
+ }
+ }
+
+ e->ignore();
+}
+
+void LightTablePreview::setSelected(bool sel)
+{
+ if (d->selected != sel)
+ {
+ d->selected = sel;
+ frameChanged();
+ }
+}
+
+bool LightTablePreview::isSelected()
+{
+ return d->selected;
+}
+
+void LightTablePreview::drawFrame(TQPainter *p)
+{
+ if (d->selected)
+ {
+ qDrawPlainRect(p, frameRect(), ThemeEngine::instance()->thumbSelColor(), lineWidth());
+ qDrawPlainRect(p, frameRect(), ThemeEngine::instance()->textSelColor(), 2);
+ }
+ else
+ qDrawPlainRect(p, frameRect(), ThemeEngine::instance()->baseColor(), lineWidth());
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/lighttable/lighttablepreview.h b/src/utilities/lighttable/lighttablepreview.h
new file mode 100644
index 00000000..3549fa1c
--- /dev/null
+++ b/src/utilities/lighttable/lighttablepreview.h
@@ -0,0 +1,125 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-21-12
+ * Description : digiKam light table preview item.
+ *
+ * Copyright (C) 2006-2008 Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef LIGHTTABLEPREVIEW_H
+#define LIGHTTABLEPREVIEW_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqimage.h>
+#include <tqsize.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+#include "previewwidget.h"
+#include "digikam_export.h"
+
+class TQPixmap;
+
+namespace Digikam
+{
+
+class DImg;
+class LoadingDescription;
+class LightTablePreviewPriv;
+
+class DIGIKAM_EXPORT LightTablePreview : public PreviewWidget
+{
+
+TQ_OBJECT
+
+
+public:
+
+ LightTablePreview(TQWidget *parent=0);
+ ~LightTablePreview();
+
+ void setLoadFullImageSize(bool b);
+
+ void setImage(const DImg& image);
+ DImg& getImage() const;
+
+ TQSize getImageSize();
+
+ void setImageInfo(ImageInfo* info=0, ImageInfo *previous=0, ImageInfo *next=0);
+ ImageInfo* getImageInfo() const;
+
+ void reload();
+ void setImagePath(const TQString& path=TQString());
+ void setPreviousNextPaths(const TQString& previous, const TQString &next);
+
+ void setSelected(bool sel);
+ bool isSelected();
+
+ void setDragAndDropEnabled(bool b);
+ void setDragAndDropMessage();
+
+signals:
+
+ void signalDroppedItems(const ImageInfoList&);
+ void signalDeleteItem(ImageInfo*);
+ void signalEditItem(ImageInfo*);
+ void signalPreviewLoaded(bool success);
+ void signalSlideShow();
+
+protected:
+
+ void resizeEvent(TQResizeEvent* e);
+ void drawFrame(TQPainter *p);
+
+private slots:
+
+ void slotGotImagePreview(const LoadingDescription &loadingDescription, const DImg &image);
+ void slotNextPreload();
+ void slotContextMenu();
+ void slotAssignTag(int tagID);
+ void slotRemoveTag(int tagID);
+ void slotAssignRating(int rating);
+ void slotThemeChanged();
+ void slotCornerButtonPressed();
+ void slotPanIconSelectionMoved(const TQRect&, bool);
+ void slotPanIconHiden();
+
+private:
+
+ int previewWidth();
+ int previewHeight();
+ bool previewIsNull();
+ void resetPreview();
+ void zoomFactorChanged(double zoom);
+ void updateZoomAndSize(bool alwaysFitToWindow);
+ inline void paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh);
+
+ void contentsDragMoveEvent(TQDragMoveEvent*);
+ void contentsDropEvent(TQDropEvent*);
+
+private:
+
+ LightTablePreviewPriv* d;
+};
+
+} // NameSpace Digikam
+
+#endif /* LIGHTTABLEPREVIEW_H */
diff --git a/src/utilities/lighttable/lighttableview.cpp b/src/utilities/lighttable/lighttableview.cpp
new file mode 100644
index 00000000..51f45ab5
--- /dev/null
+++ b/src/utilities/lighttable/lighttableview.cpp
@@ -0,0 +1,446 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-03-05
+ * Description : a widget to display 2 preview image on
+ * lightable to compare pictures.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "thumbnailsize.h"
+#include "lighttablepreview.h"
+#include "lighttableview.h"
+#include "lighttableview.moc"
+
+namespace Digikam
+{
+
+class LightTableViewPriv
+{
+public:
+
+ LightTableViewPriv()
+ {
+ syncPreview = false;
+ leftLoading = false;
+ rightLoading = false;
+ leftPreview = 0;
+ rightPreview = 0;
+ grid = 0;
+ }
+
+ bool syncPreview;
+ bool leftLoading; // To not sync right panel during left loading.
+ bool rightLoading; // To not sync left panel during right loading.
+
+ TQGridLayout *grid;
+
+ LightTablePreview *leftPreview;
+ LightTablePreview *rightPreview;
+};
+
+LightTableView::LightTableView(TQWidget *parent)
+ : TQFrame(parent, 0, TQt::WDestructiveClose)
+{
+ d = new LightTableViewPriv;
+
+ setFrameStyle(TQFrame::NoFrame);
+ setMargin(0);
+ setLineWidth(0);
+
+ d->grid = new TQGridLayout(this, 1, 1, 0, 1);
+ d->leftPreview = new LightTablePreview(this);
+ d->rightPreview = new LightTablePreview(this);
+
+ d->grid->addMultiCellWidget(d->leftPreview, 0, 0, 0, 0);
+ d->grid->addMultiCellWidget(d->rightPreview, 0, 0, 1, 1);
+
+ d->grid->setColStretch(0, 10),
+ d->grid->setColStretch(1, 10),
+ d->grid->setRowStretch(0, 10),
+
+ // Left panel connections ------------------------------------------------
+
+ connect(d->leftPreview, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SIGNAL(signalLeftZoomFactorChanged(double)));
+
+ connect(d->leftPreview, TQ_SIGNAL(contentsMoving(int, int)),
+ this, TQ_SLOT(slotLeftContentsMoved(int, int)));
+
+ connect(d->leftPreview, TQ_SIGNAL(signalSlideShow()),
+ this, TQ_SIGNAL(signalSlideShow()));
+
+ connect(d->leftPreview, TQ_SIGNAL(signalDeleteItem(ImageInfo*)),
+ this, TQ_SIGNAL(signalDeleteItem(ImageInfo*)));
+
+ connect(d->leftPreview, TQ_SIGNAL(signalEditItem(ImageInfo*)),
+ this, TQ_SIGNAL(signalEditItem(ImageInfo*)));
+
+ connect(d->leftPreview, TQ_SIGNAL(signalDroppedItems(const ImageInfoList&)),
+ this, TQ_SIGNAL(signalLeftDroppedItems(const ImageInfoList&)));
+
+ connect(d->leftPreview, TQ_SIGNAL(signalPreviewLoaded(bool)),
+ this, TQ_SLOT(slotLeftPreviewLoaded(bool)));
+
+ connect(d->leftPreview, TQ_SIGNAL(signalLeftButtonClicked()),
+ this, TQ_SIGNAL(signalLeftPanelLeftButtonClicked()));
+
+ // Right panel connections ------------------------------------------------
+
+ connect(d->rightPreview, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SIGNAL(signalRightZoomFactorChanged(double)));
+
+ connect(d->rightPreview, TQ_SIGNAL(contentsMoving(int, int)),
+ this, TQ_SLOT(slotRightContentsMoved(int, int)));
+
+ connect(d->rightPreview, TQ_SIGNAL(signalDeleteItem(ImageInfo*)),
+ this, TQ_SIGNAL(signalDeleteItem(ImageInfo*)));
+
+ connect(d->rightPreview, TQ_SIGNAL(signalEditItem(ImageInfo*)),
+ this, TQ_SIGNAL(signalEditItem(ImageInfo*)));
+
+ connect(d->rightPreview, TQ_SIGNAL(signalDroppedItems(const ImageInfoList&)),
+ this, TQ_SIGNAL(signalRightDroppedItems(const ImageInfoList&)));
+
+ connect(d->rightPreview, TQ_SIGNAL(signalSlideShow()),
+ this, TQ_SIGNAL(signalSlideShow()));
+
+ connect(d->rightPreview, TQ_SIGNAL(signalPreviewLoaded(bool)),
+ this, TQ_SLOT(slotRightPreviewLoaded(bool)));
+
+ connect(d->rightPreview, TQ_SIGNAL(signalLeftButtonClicked()),
+ this, TQ_SIGNAL(signalRightPanelLeftButtonClicked()));
+}
+
+LightTableView::~LightTableView()
+{
+ delete d;
+}
+
+void LightTableView::setLoadFullImageSize(bool b)
+{
+ d->leftPreview->setLoadFullImageSize(b);
+ d->rightPreview->setLoadFullImageSize(b);
+}
+
+void LightTableView::setSyncPreview(bool sync)
+{
+ d->syncPreview = sync;
+
+ // Left panel like a reference to resync preview.
+ if (d->syncPreview)
+ slotLeftContentsMoved(d->leftPreview->contentsX(), d->leftPreview->contentsY());
+}
+
+void LightTableView::setNavigateByPair(bool b)
+{
+ d->leftPreview->setDragAndDropEnabled(!b);
+ d->rightPreview->setDragAndDropEnabled(!b);
+}
+
+void LightTableView::slotDecreaseZoom()
+{
+ if (d->syncPreview)
+ {
+ slotDecreaseLeftZoom();
+ return;
+ }
+
+ if (d->leftPreview->isSelected())
+ slotDecreaseLeftZoom();
+ else if (d->rightPreview->isSelected())
+ slotDecreaseRightZoom();
+}
+
+void LightTableView::slotIncreaseZoom()
+{
+ if (d->syncPreview)
+ {
+ slotIncreaseLeftZoom();
+ return;
+ }
+
+ if (d->leftPreview->isSelected())
+ slotIncreaseLeftZoom();
+ else if (d->rightPreview->isSelected())
+ slotIncreaseRightZoom();
+}
+
+void LightTableView::slotDecreaseLeftZoom()
+{
+ d->leftPreview->slotDecreaseZoom();
+}
+
+void LightTableView::slotIncreaseLeftZoom()
+{
+ d->leftPreview->slotIncreaseZoom();
+}
+
+void LightTableView::slotDecreaseRightZoom()
+{
+ d->rightPreview->slotDecreaseZoom();
+}
+
+void LightTableView::slotIncreaseRightZoom()
+{
+ d->rightPreview->slotIncreaseZoom();
+}
+
+void LightTableView::setLeftZoomFactor(double z)
+{
+ d->leftPreview->setZoomFactor(z);
+}
+
+void LightTableView::setRightZoomFactor(double z)
+{
+ d->rightPreview->setZoomFactor(z);
+}
+
+void LightTableView::fitToWindow()
+{
+ d->leftPreview->fitToWindow();
+ d->rightPreview->fitToWindow();
+}
+
+void LightTableView::toggleFitToWindowOr100()
+{
+ // If we are currently precisely at 100%, then fit to window,
+ // otherwise zoom to a centered 100% view.
+ if ((d->leftPreview->zoomFactor()==1.0) &&
+ (d->rightPreview->zoomFactor()==1.0))
+ {
+ fitToWindow();
+ }
+ else
+ {
+ d->leftPreview->setZoomFactor(1.0, true);
+ d->rightPreview->setZoomFactor(1.0, true);
+ }
+}
+
+double LightTableView::leftZoomMax()
+{
+ return d->leftPreview->zoomMax();
+}
+
+double LightTableView::leftZoomMin()
+{
+ return d->leftPreview->zoomMin();
+}
+
+bool LightTableView::leftMaxZoom()
+{
+ return d->leftPreview->maxZoom();
+}
+
+bool LightTableView::leftMinZoom()
+{
+ return d->leftPreview->minZoom();
+}
+
+double LightTableView::rightZoomMax()
+{
+ return d->rightPreview->zoomMax();
+}
+
+double LightTableView::rightZoomMin()
+{
+ return d->rightPreview->zoomMin();
+}
+
+bool LightTableView::rightMaxZoom()
+{
+ return d->rightPreview->maxZoom();
+}
+
+bool LightTableView::rightMinZoom()
+{
+ return d->rightPreview->minZoom();
+}
+
+void LightTableView::slotLeftZoomSliderChanged(int size)
+{
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->leftPreview->zoomMin();
+ double zmax = d->leftPreview->zoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ double z = a*size+b;
+
+ d->leftPreview->setZoomFactorSnapped(z);
+}
+
+void LightTableView::slotRightZoomSliderChanged(int size)
+{
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->rightPreview->zoomMin();
+ double zmax = d->rightPreview->zoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ double z = a*size+b;
+
+ d->rightPreview->setZoomFactorSnapped(z);
+}
+
+void LightTableView::leftReload()
+{
+ d->leftPreview->reload();
+}
+
+void LightTableView::rightReload()
+{
+ d->rightPreview->reload();
+}
+
+void LightTableView::slotLeftContentsMoved(int x, int y)
+{
+ if (d->syncPreview && !d->leftLoading)
+ {
+ disconnect(d->rightPreview, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SIGNAL(signalRightZoomFactorChanged(double)));
+
+ disconnect(d->rightPreview, TQ_SIGNAL(contentsMoving(int, int)),
+ this, TQ_SLOT(slotRightContentsMoved(int, int)));
+
+ setRightZoomFactor(d->leftPreview->zoomFactor());
+ emit signalRightZoomFactorChanged(d->leftPreview->zoomFactor());
+ d->rightPreview->setContentsPos(x, y);
+
+ connect(d->rightPreview, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SIGNAL(signalRightZoomFactorChanged(double)));
+
+ connect(d->rightPreview, TQ_SIGNAL(contentsMoving(int, int)),
+ this, TQ_SLOT(slotRightContentsMoved(int, int)));
+ }
+}
+
+void LightTableView::slotRightContentsMoved(int x, int y)
+{
+ if (d->syncPreview && !d->rightLoading)
+ {
+ disconnect(d->leftPreview, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SIGNAL(signalLeftZoomFactorChanged(double)));
+
+ disconnect(d->leftPreview, TQ_SIGNAL(contentsMoving(int, int)),
+ this, TQ_SLOT(slotLeftContentsMoved(int, int)));
+
+
+ setLeftZoomFactor(d->rightPreview->zoomFactor());
+ emit signalLeftZoomFactorChanged(d->rightPreview->zoomFactor());
+ d->leftPreview->setContentsPos(x, y);
+
+ connect(d->leftPreview, TQ_SIGNAL(signalZoomFactorChanged(double)),
+ this, TQ_SIGNAL(signalLeftZoomFactorChanged(double)));
+
+ connect(d->leftPreview, TQ_SIGNAL(contentsMoving(int, int)),
+ this, TQ_SLOT(slotLeftContentsMoved(int, int)));
+ }
+}
+
+ImageInfo* LightTableView::leftImageInfo() const
+{
+ return d->leftPreview->getImageInfo();
+}
+
+ImageInfo* LightTableView::rightImageInfo() const
+{
+ return d->rightPreview->getImageInfo();
+}
+
+void LightTableView::setLeftImageInfo(ImageInfo* info)
+{
+ d->leftLoading = true;
+ d->leftPreview->setImageInfo(info);
+}
+
+void LightTableView::setRightImageInfo(ImageInfo* info)
+{
+ d->rightLoading = true;
+ d->rightPreview->setImageInfo(info);
+}
+
+void LightTableView::slotLeftPreviewLoaded(bool success)
+{
+ checkForSyncPreview();
+ d->leftLoading = false;
+ slotRightContentsMoved(d->rightPreview->contentsX(),
+ d->rightPreview->contentsY());
+
+ emit signalLeftPreviewLoaded(success);
+}
+
+void LightTableView::slotRightPreviewLoaded(bool success)
+{
+ checkForSyncPreview();
+ d->rightLoading = false;
+ slotLeftContentsMoved(d->leftPreview->contentsX(),
+ d->leftPreview->contentsY());
+
+ emit signalRightPreviewLoaded(success);
+}
+
+void LightTableView::checkForSyncPreview()
+{
+ if (d->leftPreview->getImageInfo() && d->rightPreview->getImageInfo() &&
+ d->leftPreview->getImageSize() == d->rightPreview->getImageSize())
+ {
+ d->syncPreview = true;
+ }
+ else
+ {
+ d->syncPreview = false;
+ }
+
+ emit signalToggleOnSyncPreview(d->syncPreview);
+}
+
+void LightTableView::checkForSelection(ImageInfo* info)
+{
+ if (!info)
+ {
+ d->leftPreview->setSelected(false);
+ d->rightPreview->setSelected(false);
+ return;
+ }
+
+ if (d->leftPreview->getImageInfo())
+ {
+ d->leftPreview->setSelected(d->leftPreview->getImageInfo()->id() == info->id());
+ }
+
+ if (d->rightPreview->getImageInfo())
+ {
+ d->rightPreview->setSelected(d->rightPreview->getImageInfo()->id() == info->id());
+ }
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/lighttable/lighttableview.h b/src/utilities/lighttable/lighttableview.h
new file mode 100644
index 00000000..273b7072
--- /dev/null
+++ b/src/utilities/lighttable/lighttableview.h
@@ -0,0 +1,137 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-03-05
+ * Description : a widget to display 2 preview image on
+ * lightable to compare pictures.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef LIGHTTABLEVIEW_H
+#define LIGHTTABLEVIEW_H
+
+// TQt includes.
+
+#include <tqframe.h>
+#include <tqstring.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class LightTableViewPriv;
+
+class DIGIKAM_EXPORT LightTableView : public TQFrame
+{
+
+TQ_OBJECT
+
+
+public:
+
+ LightTableView(TQWidget *parent=0);
+ ~LightTableView();
+
+ void setSyncPreview(bool sync);
+ void setNavigateByPair(bool b);
+
+ void setLeftImageInfo(ImageInfo* info=0);
+ void setRightImageInfo(ImageInfo* info=0);
+
+ ImageInfo* leftImageInfo() const;
+ ImageInfo* rightImageInfo() const;
+
+ void setLoadFullImageSize(bool b);
+
+ void setLeftZoomFactor(double z);
+ void setRightZoomFactor(double z);
+
+ void checkForSelection(ImageInfo* info);
+
+ double leftZoomMax();
+ double leftZoomMin();
+
+ double rightZoomMax();
+ double rightZoomMin();
+
+ bool leftMaxZoom();
+ bool leftMinZoom();
+
+ bool rightMaxZoom();
+ bool rightMinZoom();
+
+ void leftReload();
+ void rightReload();
+
+ void fitToWindow();
+ void toggleFitToWindowOr100();
+
+signals:
+
+ void signalLeftPreviewLoaded(bool);
+ void signalRightPreviewLoaded(bool);
+
+ void signalLeftZoomFactorChanged(double);
+ void signalRightZoomFactorChanged(double);
+
+ void signalLeftDroppedItems(const ImageInfoList&);
+ void signalRightDroppedItems(const ImageInfoList&);
+
+ void signalLeftPanelLeftButtonClicked();
+ void signalRightPanelLeftButtonClicked();
+
+ void signalSlideShow();
+ void signalDeleteItem(ImageInfo*);
+ void signalEditItem(ImageInfo*);
+ void signalToggleOnSyncPreview(bool);
+
+public slots:
+
+ void slotDecreaseZoom();
+ void slotIncreaseZoom();
+ void slotDecreaseLeftZoom();
+ void slotIncreaseLeftZoom();
+ void slotLeftZoomSliderChanged(int);
+
+ void slotDecreaseRightZoom();
+ void slotIncreaseRightZoom();
+ void slotRightZoomSliderChanged(int);
+
+private slots:
+
+ void slotLeftContentsMoved(int, int);
+ void slotRightContentsMoved(int, int);
+ void slotLeftPreviewLoaded(bool);
+ void slotRightPreviewLoaded(bool);
+
+private :
+
+ void checkForSyncPreview();
+
+private :
+
+ LightTableViewPriv* d;
+};
+
+} // namespace Digikam
+
+#endif /* LIGHTTABLEVIEW_H */
diff --git a/src/utilities/lighttable/lighttablewindow.cpp b/src/utilities/lighttable/lighttablewindow.cpp
new file mode 100644
index 00000000..58409aef
--- /dev/null
+++ b/src/utilities/lighttable/lighttablewindow.cpp
@@ -0,0 +1,1676 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-03-05
+ * Description : digiKam light table GUI
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqdockarea.h>
+
+// KDE includes.
+
+#include <kkeydialog.h>
+#include <kedittoolbar.h>
+#include <tdeversion.h>
+#include <tdelocale.h>
+#include <twin.h>
+#include <tdemessagebox.h>
+#include <tdeapplication.h>
+#include <tdeconfig.h>
+#include <kstatusbar.h>
+#include <tdemenubar.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dlogoaction.h"
+#include "themeengine.h"
+#include "dimg.h"
+#include "dmetadata.h"
+#include "albumsettings.h"
+#include "albummanager.h"
+#include "deletedialog.h"
+#include "imagewindow.h"
+#include "slideshow.h"
+#include "setup.h"
+#include "syncjob.h"
+#include "thumbnailsize.h"
+#include "rawcameradlg.h"
+#include "lighttablepreview.h"
+#include "lighttablewindowprivate.h"
+#include "lighttablewindow.h"
+#include "lighttablewindow.moc"
+
+namespace Digikam
+{
+
+LightTableWindow* LightTableWindow::m_instance = 0;
+
+LightTableWindow* LightTableWindow::lightTableWindow()
+{
+ if (!m_instance)
+ new LightTableWindow();
+
+ return m_instance;
+}
+
+bool LightTableWindow::lightTableWindowCreated()
+{
+ return m_instance;
+}
+
+LightTableWindow::LightTableWindow()
+ : TDEMainWindow(0, "lighttable", WType_TopLevel)
+{
+ d = new LightTableWindowPriv;
+ m_instance = this;
+
+ setCaption(i18n("Light Table"));
+
+ // -- Build the GUI -------------------------------
+
+ setupUserArea();
+ setupStatusBar();
+ setupActions();
+ setupAccelerators();
+
+ // Make signals/slots connections
+
+ setupConnections();
+
+ //-------------------------------------------------------------
+
+ d->leftSidebar->loadViewState();
+ d->rightSidebar->loadViewState();
+ d->leftSidebar->populateTags();
+ d->rightSidebar->populateTags();
+
+ readSettings();
+ applySettings();
+ setAutoSaveSettings("LightTable Settings");
+}
+
+LightTableWindow::~LightTableWindow()
+{
+ m_instance = 0;
+
+ delete d->barView;
+ delete d->rightSidebar;
+ delete d->leftSidebar;
+ delete d;
+}
+
+void LightTableWindow::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("LightTable Settings");
+
+ if(config->hasKey("Vertical Splitter Sizes"))
+ d->vSplitter->setSizes(config->readIntListEntry("Vertical Splitter Sizes"));
+
+ if(config->hasKey("Horizontal Splitter Sizes"))
+ d->hSplitter->setSizes(config->readIntListEntry("Horizontal Splitter Sizes"));
+
+ d->navigateByPairAction->setChecked(config->readBoolEntry("Navigate By Pair", false));
+ slotToggleNavigateByPair();
+}
+
+void LightTableWindow::writeSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("LightTable Settings");
+ config->writeEntry("Vertical Splitter Sizes", d->vSplitter->sizes());
+ config->writeEntry("Horizontal Splitter Sizes", d->hSplitter->sizes());
+ config->writeEntry("Navigate By Pair", d->navigateByPairAction->isChecked());
+ config->sync();
+}
+
+void LightTableWindow::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("LightTable Settings");
+
+ d->autoLoadOnRightPanel = config->readBoolEntry("Auto Load Right Panel", true);
+ d->autoSyncPreview = config->readBoolEntry("Auto Sync Preview", true);
+ d->fullScreenHideToolBar = config->readBoolEntry("FullScreen Hide ToolBar", false);
+ d->previewView->setLoadFullImageSize(config->readBoolEntry("Load Full Image size", false));
+ refreshView();
+}
+
+void LightTableWindow::refreshView()
+{
+ d->leftSidebar->refreshTagsView();
+ d->rightSidebar->refreshTagsView();
+}
+
+void LightTableWindow::closeEvent(TQCloseEvent* e)
+{
+ if (!e) return;
+
+ writeSettings();
+
+ e->accept();
+}
+
+void LightTableWindow::setupUserArea()
+{
+ TQWidget* mainW = new TQWidget(this);
+ d->hSplitter = new TQSplitter(TQt::Horizontal, mainW);
+ TQHBoxLayout *hlay = new TQHBoxLayout(mainW);
+ d->leftSidebar = new ImagePropertiesSideBarDB(mainW,
+ "LightTable Left Sidebar", d->hSplitter,
+ Sidebar::Left, true);
+
+ TQWidget* centralW = new TQWidget(d->hSplitter);
+ TQVBoxLayout *vlay = new TQVBoxLayout(centralW);
+ d->vSplitter = new TQSplitter(TQt::Vertical, centralW);
+ d->barView = new LightTableBar(d->vSplitter, ThumbBarView::Horizontal,
+ AlbumSettings::instance()->getExifRotate());
+ d->previewView = new LightTableView(d->vSplitter);
+ vlay->addWidget(d->vSplitter);
+
+ d->rightSidebar = new ImagePropertiesSideBarDB(mainW,
+ "LightTable Right Sidebar", d->hSplitter,
+ Sidebar::Right, true);
+
+ hlay->addWidget(d->leftSidebar);
+ hlay->addWidget(d->hSplitter);
+ hlay->addWidget(d->rightSidebar);
+
+ d->hSplitter->setFrameStyle( TQFrame::NoFrame );
+ d->hSplitter->setFrameShadow( TQFrame::Plain );
+ d->hSplitter->setFrameShape( TQFrame::NoFrame );
+ d->hSplitter->setOpaqueResize(false);
+ d->vSplitter->setFrameStyle( TQFrame::NoFrame );
+ d->vSplitter->setFrameShadow( TQFrame::Plain );
+ d->vSplitter->setFrameShape( TQFrame::NoFrame );
+ d->vSplitter->setOpaqueResize(false);
+
+ setCentralWidget(mainW);
+}
+
+void LightTableWindow::setupStatusBar()
+{
+ d->leftZoomBar = new StatusZoomBar(statusBar());
+ d->leftZoomBar->setMaximumHeight(fontMetrics().height()+2);
+ statusBar()->addWidget(d->leftZoomBar, 1);
+ d->leftZoomBar->setEnabled(false);
+
+ d->statusProgressBar = new StatusProgressBar(statusBar());
+ d->statusProgressBar->setAlignment(TQt::AlignCenter);
+ d->statusProgressBar->setMaximumHeight(fontMetrics().height()+2);
+ statusBar()->addWidget(d->statusProgressBar, 100);
+
+ d->rightZoomBar = new StatusZoomBar(statusBar());
+ d->rightZoomBar->setMaximumHeight(fontMetrics().height()+2);
+ statusBar()->addWidget(d->rightZoomBar, 1);
+ d->rightZoomBar->setEnabled(false);
+}
+
+void LightTableWindow::setupConnections()
+{
+ connect(d->statusProgressBar, TQ_SIGNAL(signalCancelButtonPressed()),
+ this, TQ_SLOT(slotProgressBarCancelButtonPressed()));
+
+ connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
+ this, TQ_SLOT(slotThemeChanged()));
+
+ // Thumbs bar connections ---------------------------------------
+
+ connect(d->barView, TQ_SIGNAL(signalSetItemOnLeftPanel(ImageInfo*)),
+ this, TQ_SLOT(slotSetItemOnLeftPanel(ImageInfo*)));
+
+ connect(d->barView, TQ_SIGNAL(signalSetItemOnRightPanel(ImageInfo*)),
+ this, TQ_SLOT(slotSetItemOnRightPanel(ImageInfo*)));
+
+ connect(d->barView, TQ_SIGNAL(signalRemoveItem(ImageInfo*)),
+ this, TQ_SLOT(slotRemoveItem(ImageInfo*)));
+
+ connect(d->barView, TQ_SIGNAL(signalEditItem(ImageInfo*)),
+ this, TQ_SLOT(slotEditItem(ImageInfo*)));
+
+ connect(d->barView, TQ_SIGNAL(signalClearAll()),
+ this, TQ_SLOT(slotClearItemsList()));
+
+ connect(d->barView, TQ_SIGNAL(signalLightTableBarItemSelected(ImageInfo*)),
+ this, TQ_SLOT(slotItemSelected(ImageInfo*)));
+
+ connect(d->barView, TQ_SIGNAL(signalDroppedItems(const ImageInfoList&)),
+ this, TQ_SLOT(slotThumbbarDroppedItems(const ImageInfoList&)));
+
+ // Zoom bars connections -----------------------------------------
+
+ connect(d->leftZoomBar, TQ_SIGNAL(signalZoomMinusClicked()),
+ d->previewView, TQ_SLOT(slotDecreaseLeftZoom()));
+
+ connect(d->leftZoomBar, TQ_SIGNAL(signalZoomPlusClicked()),
+ d->previewView, TQ_SLOT(slotIncreaseLeftZoom()));
+
+ connect(d->leftZoomBar, TQ_SIGNAL(signalZoomSliderChanged(int)),
+ d->previewView, TQ_SLOT(slotLeftZoomSliderChanged(int)));
+
+ connect(d->rightZoomBar, TQ_SIGNAL(signalZoomMinusClicked()),
+ d->previewView, TQ_SLOT(slotDecreaseRightZoom()));
+
+ connect(d->rightZoomBar, TQ_SIGNAL(signalZoomPlusClicked()),
+ d->previewView, TQ_SLOT(slotIncreaseRightZoom()));
+
+ connect(d->rightZoomBar, TQ_SIGNAL(signalZoomSliderChanged(int)),
+ d->previewView, TQ_SLOT(slotRightZoomSliderChanged(int)));
+
+ // View connections ---------------------------------------------
+
+ connect(d->previewView, TQ_SIGNAL(signalLeftZoomFactorChanged(double)),
+ this, TQ_SLOT(slotLeftZoomFactorChanged(double)));
+
+ connect(d->previewView, TQ_SIGNAL(signalRightZoomFactorChanged(double)),
+ this, TQ_SLOT(slotRightZoomFactorChanged(double)));
+
+ connect(d->previewView, TQ_SIGNAL(signalEditItem(ImageInfo*)),
+ this, TQ_SLOT(slotEditItem(ImageInfo*)));
+
+ connect(d->previewView, TQ_SIGNAL(signalDeleteItem(ImageInfo*)),
+ this, TQ_SLOT(slotDeleteItem(ImageInfo*)));
+
+ connect(d->previewView, TQ_SIGNAL(signalSlideShow()),
+ this, TQ_SLOT(slotToggleSlideShow()));
+
+ connect(d->previewView, TQ_SIGNAL(signalLeftDroppedItems(const ImageInfoList&)),
+ this, TQ_SLOT(slotLeftDroppedItems(const ImageInfoList&)));
+
+ connect(d->previewView, TQ_SIGNAL(signalRightDroppedItems(const ImageInfoList&)),
+ this, TQ_SLOT(slotRightDroppedItems(const ImageInfoList&)));
+
+ connect(d->previewView, TQ_SIGNAL(signalToggleOnSyncPreview(bool)),
+ this, TQ_SLOT(slotToggleOnSyncPreview(bool)));
+
+ connect(d->previewView, TQ_SIGNAL(signalLeftPreviewLoaded(bool)),
+ this, TQ_SLOT(slotLeftPreviewLoaded(bool)));
+
+ connect(d->previewView, TQ_SIGNAL(signalRightPreviewLoaded(bool)),
+ this, TQ_SLOT(slotRightPreviewLoaded(bool)));
+
+ connect(d->previewView, TQ_SIGNAL(signalLeftPanelLeftButtonClicked()),
+ this, TQ_SLOT(slotLeftPanelLeftButtonClicked()));
+
+ connect(d->previewView, TQ_SIGNAL(signalRightPanelLeftButtonClicked()),
+ this, TQ_SLOT(slotRightPanelLeftButtonClicked()));
+}
+
+void LightTableWindow::setupActions()
+{
+ // -- Standard 'File' menu actions ---------------------------------------------
+
+ d->backwardAction = KStdAction::back(this, TQ_SLOT(slotBackward()),
+ actionCollection(), "lighttable_backward");
+ d->backwardAction->setEnabled(false);
+
+ d->forwardAction = KStdAction::forward(this, TQ_SLOT(slotForward()),
+ actionCollection(), "lighttable_forward");
+ d->forwardAction->setEnabled(false);
+
+ d->firstAction = new TDEAction(i18n("&First"), "go-first",
+ TDEStdAccel::shortcut( TDEStdAccel::Home),
+ this, TQ_SLOT(slotFirst()),
+ actionCollection(), "lighttable_first");
+ d->firstAction->setEnabled(false);
+
+ d->lastAction = new TDEAction(i18n("&Last"), "go-last",
+ TDEStdAccel::shortcut( TDEStdAccel::End),
+ this, TQ_SLOT(slotLast()),
+ actionCollection(), "lighttable_last");
+ d->lastAction->setEnabled(false);
+
+ d->setItemLeftAction = new TDEAction(i18n("On Left"), "go-previous",
+ CTRL+Key_L, this, TQ_SLOT(slotSetItemLeft()),
+ actionCollection(), "lighttable_setitemleft");
+ d->setItemLeftAction->setEnabled(false);
+ d->setItemLeftAction->setWhatsThis(i18n("Show item on left panel"));
+
+ d->setItemRightAction = new TDEAction(i18n("On Right"), "go-next",
+ CTRL+Key_R, this, TQ_SLOT(slotSetItemRight()),
+ actionCollection(), "lighttable_setitemright");
+ d->setItemRightAction->setEnabled(false);
+ d->setItemRightAction->setWhatsThis(i18n("Show item on right panel"));
+
+ d->editItemAction = new TDEAction(i18n("Edit"), "editimage",
+ Key_F4, this, TQ_SLOT(slotEditItem()),
+ actionCollection(), "lighttable_edititem");
+ d->editItemAction->setEnabled(false);
+
+ d->removeItemAction = new TDEAction(i18n("Remove item from LightTable"), "window-close",
+ CTRL+Key_K, this, TQ_SLOT(slotRemoveItem()),
+ actionCollection(), "lighttable_removeitem");
+ d->removeItemAction->setEnabled(false);
+
+ d->clearListAction = new TDEAction(i18n("Remove all items from LightTable"), "editshred",
+ CTRL+SHIFT+Key_K, this, TQ_SLOT(slotClearItemsList()),
+ actionCollection(), "lighttable_clearlist");
+ d->clearListAction->setEnabled(false);
+
+ d->fileDeleteAction = new TDEAction(i18n("Move to Trash"), "edittrash",
+ Key_Delete,
+ this, TQ_SLOT(slotDeleteItem()),
+ actionCollection(), "lighttable_filedelete");
+ d->fileDeleteAction->setEnabled(false);
+
+ KStdAction::close(this, TQ_SLOT(close()), actionCollection(), "lighttable_close");
+
+ // -- Standard 'View' menu actions ---------------------------------------------
+
+ d->syncPreviewAction = new TDEToggleAction(i18n("Synchronize"), "goto",
+ CTRL+SHIFT+Key_Y, this,
+ TQ_SLOT(slotToggleSyncPreview()),
+ actionCollection(), "lighttable_syncpreview");
+ d->syncPreviewAction->setEnabled(false);
+ d->syncPreviewAction->setWhatsThis(i18n("Synchronize preview from left and right panels"));
+
+ d->navigateByPairAction = new TDEToggleAction(i18n("By Pair"), "preferences-system",
+ CTRL+SHIFT+Key_P, this,
+ TQ_SLOT(slotToggleNavigateByPair()),
+ actionCollection(), "lighttable_navigatebypair");
+ d->navigateByPairAction->setEnabled(false);
+ d->navigateByPairAction->setWhatsThis(i18n("Navigate by pair with all items"));
+
+ d->zoomPlusAction = KStdAction::zoomIn(d->previewView, TQ_SLOT(slotIncreaseZoom()),
+ actionCollection(), "lighttable_zoomplus");
+ d->zoomPlusAction->setEnabled(false);
+
+ d->zoomMinusAction = KStdAction::zoomOut(d->previewView, TQ_SLOT(slotDecreaseZoom()),
+ actionCollection(), "lighttable_zoomminus");
+ d->zoomMinusAction->setEnabled(false);
+
+ d->zoomTo100percents = new TDEAction(i18n("Zoom to 100%"), "zoom-original",
+ ALT+CTRL+Key_0, // NOTE: Photoshop 7 use ALT+CTRL+0.
+ this, TQ_SLOT(slotZoomTo100Percents()),
+ actionCollection(), "lighttable_zoomto100percents");
+
+ d->zoomFitToWindowAction = new TDEAction(i18n("Fit to &Window"), "view_fit_window",
+ CTRL+SHIFT+Key_E, this, TQ_SLOT(slotFitToWindow()),
+ actionCollection(), "lighttable_zoomfit2window");
+
+ // Do not use std KDE action for full screen because action text is too large for app. toolbar.
+ d->fullScreenAction = new TDEToggleAction(i18n("Full Screen"), "view-fullscreen",
+ CTRL+SHIFT+Key_F, this,
+ TQ_SLOT(slotToggleFullScreen()),
+ actionCollection(), "lighttable_fullscreen");
+ d->fullScreenAction->setWhatsThis(i18n("Toggle the window to full screen mode"));
+
+ d->slideShowAction = new TDEAction(i18n("Slideshow"), "slideshow", Key_F9,
+ this, TQ_SLOT(slotToggleSlideShow()),
+ actionCollection(),"lighttable_slideshow");
+
+ // -- Standard 'Configure' menu actions ----------------------------------------
+
+ d->showMenuBarAction = KStdAction::showMenubar(this, TQ_SLOT(slotShowMenuBar()), actionCollection());
+
+ KStdAction::keyBindings(this, TQ_SLOT(slotEditKeys()), actionCollection());
+ KStdAction::configureToolbars(this, TQ_SLOT(slotConfToolbars()), actionCollection());
+ KStdAction::preferences(this, TQ_SLOT(slotSetup()), actionCollection());
+
+ // -----------------------------------------------------------------------------------------
+
+ d->themeMenuAction = new TDESelectAction(i18n("&Themes"), 0, actionCollection(), "theme_menu");
+ connect(d->themeMenuAction, TQ_SIGNAL(activated(const TQString&)),
+ this, TQ_SLOT(slotChangeTheme(const TQString&)));
+
+ d->themeMenuAction->setItems(ThemeEngine::instance()->themeNames());
+ slotThemeChanged();
+
+ // -- Standard 'Help' menu actions ---------------------------------------------
+
+
+ d->donateMoneyAction = new TDEAction(i18n("Donate..."),
+ 0, 0,
+ this, TQ_SLOT(slotDonateMoney()),
+ actionCollection(),
+ "lighttable_donatemoney");
+
+ d->contributeAction = new TDEAction(i18n("Contribute..."),
+ 0, 0,
+ this, TQ_SLOT(slotContribute()),
+ actionCollection(),
+ "lighttable_contribute");
+
+ d->rawCameraListAction = new TDEAction(i18n("Supported RAW Cameras"),
+ "kdcraw",
+ 0,
+ this,
+ TQ_SLOT(slotRawCameraList()),
+ actionCollection(),
+ "lighttable_rawcameralist");
+
+
+ // Provides a menu entry that allows showing/hiding the toolbar(s)
+ setStandardToolBarMenuEnabled(true);
+
+ // Provides a menu entry that allows showing/hiding the statusbar
+ createStandardStatusBarAction();
+
+ // -- Rating actions ---------------------------------------------------------------
+
+ d->star0 = new TDEAction(i18n("Assign Rating \"No Stars\""), CTRL+Key_0,
+ d->barView, TQ_SLOT(slotAssignRatingNoStar()),
+ actionCollection(), "lighttable_ratenostar");
+ d->star1 = new TDEAction(i18n("Assign Rating \"One Star\""), CTRL+Key_1,
+ d->barView, TQ_SLOT(slotAssignRatingOneStar()),
+ actionCollection(), "lighttable_rateonestar");
+ d->star2 = new TDEAction(i18n("Assign Rating \"Two Stars\""), CTRL+Key_2,
+ d->barView, TQ_SLOT(slotAssignRatingTwoStar()),
+ actionCollection(), "lighttable_ratetwostar");
+ d->star3 = new TDEAction(i18n("Assign Rating \"Three Stars\""), CTRL+Key_3,
+ d->barView, TQ_SLOT(slotAssignRatingThreeStar()),
+ actionCollection(), "lighttable_ratethreestar");
+ d->star4 = new TDEAction(i18n("Assign Rating \"Four Stars\""), CTRL+Key_4,
+ d->barView, TQ_SLOT(slotAssignRatingFourStar()),
+ actionCollection(), "lighttable_ratefourstar");
+ d->star5 = new TDEAction(i18n("Assign Rating \"Five Stars\""), CTRL+Key_5,
+ d->barView, TQ_SLOT(slotAssignRatingFiveStar()),
+ actionCollection(), "lighttable_ratefivestar");
+
+ // ---------------------------------------------------------------------------------
+
+ new DLogoAction(actionCollection(), "logo_action");
+
+ createGUI("lighttablewindowui.rc", false);
+}
+
+void LightTableWindow::setupAccelerators()
+{
+ d->accelerators = new TDEAccel(this);
+
+ d->accelerators->insert("Exit fullscreen", i18n("Exit Fullscreen mode"),
+ i18n("Exit fullscreen viewing mode"),
+ Key_Escape, this, TQ_SLOT(slotEscapePressed()),
+ false, true);
+
+ d->accelerators->insert("Next Image Key_Space", i18n("Next Image"),
+ i18n("Load Next Image"),
+ Key_Space, this, TQ_SLOT(slotForward()),
+ false, true);
+
+ d->accelerators->insert("Previous Image SHIFT+Key_Space", i18n("Previous Image"),
+ i18n("Load Previous Image"),
+ SHIFT+Key_Space, this, TQ_SLOT(slotBackward()),
+ false, true);
+
+ d->accelerators->insert("Previous Image Key_Backspace", i18n("Previous Image"),
+ i18n("Load Previous Image"),
+ Key_Backspace, this, TQ_SLOT(slotBackward()),
+ false, true);
+
+ d->accelerators->insert("Next Image Key_Next", i18n("Next Image"),
+ i18n("Load Next Image"),
+ Key_Next, this, TQ_SLOT(slotForward()),
+ false, true);
+
+ d->accelerators->insert("Previous Image Key_Prior", i18n("Previous Image"),
+ i18n("Load Previous Image"),
+ Key_Prior, this, TQ_SLOT(slotBackward()),
+ false, true);
+
+ d->accelerators->insert("Zoom Plus Key_Plus", i18n("Zoom in"),
+ i18n("Zoom in on image"),
+ Key_Plus, d->previewView, TQ_SLOT(slotIncreaseZoom()),
+ false, true);
+
+ d->accelerators->insert("Zoom Plus Key_Minus", i18n("Zoom out"),
+ i18n("Zoom out from image"),
+ Key_Minus, d->previewView, TQ_SLOT(slotDecreaseZoom()),
+ false, true);
+}
+
+// Deal with items dropped onto the thumbbar (e.g. from the Album view)
+void LightTableWindow::slotThumbbarDroppedItems(const ImageInfoList& list)
+{
+ // Setting the third parameter of loadImageInfos to true
+ // means that the images are added to the presently available images.
+ loadImageInfos(list, 0, true);
+}
+
+// We get here either
+// - via CTRL+L (from the albumview)
+// a) digikamapp.cpp: CTRL+key_L leads to slotImageLightTable())
+// b) digikamview.cpp: void DigikamView::slotImageLightTable()
+// calls d->iconView->insertToLightTable(list, info);
+// c) albumiconview.cpp: AlbumIconView::insertToLightTable
+// calls ltview->loadImageInfos(list, current);
+// - via drag&drop, i.e. calls issued by the ...Dropped... routines
+void LightTableWindow::loadImageInfos(const ImageInfoList &list,
+ ImageInfo *imageInfoCurrent,
+ bool addTo)
+{
+ // Clear all items before adding new images to the light table.
+ if (!addTo)
+ slotClearItemsList();
+
+ ImageInfoList l = list;
+
+ if (!imageInfoCurrent)
+ imageInfoCurrent = l.first();
+
+ AlbumSettings *settings = AlbumSettings::instance();
+ if (!settings) return;
+
+ TQString imagefilter = settings->getImageFileFilter().lower() +
+ settings->getImageFileFilter().upper();
+
+#if KDCRAW_VERSION < 0x000106
+ if (KDcrawIface::DcrawBinary::instance()->versionIsRight())
+ {
+ // add raw files only if dcraw is available
+ imagefilter += settings->getRawFileFilter().lower() +
+ settings->getRawFileFilter().upper();
+ }
+#else
+ imagefilter += settings->getRawFileFilter().lower() +
+ settings->getRawFileFilter().upper();
+#endif
+
+ d->barView->blockSignals(true);
+ for (TQPtrList<ImageInfo>::const_iterator it = l.begin(); it != l.end(); ++it)
+ {
+ TQString fileExtension = (*it)->kurl().fileName().section( '.', -1 );
+
+ if ( imagefilter.find(fileExtension) != -1 &&
+ !d->barView->findItemByInfo(*it) )
+ {
+ new LightTableBarItem(d->barView, *it);
+ }
+ }
+ d->barView->blockSignals(false);
+
+ // if window is iconified, show it
+ if (isMinimized())
+ {
+ KWin::deIconifyWindow(winId());
+ }
+
+ refreshStatusBar();
+}
+
+void LightTableWindow::refreshStatusBar()
+{
+ switch (d->barView->countItems())
+ {
+ case 0:
+ d->statusProgressBar->progressBarMode(StatusProgressBar::TextMode,
+ i18n("No item on Light Table"));
+ break;
+ case 1:
+ d->statusProgressBar->progressBarMode(StatusProgressBar::TextMode,
+ i18n("1 item on Light Table"));
+ break;
+ default:
+ d->statusProgressBar->progressBarMode(StatusProgressBar::TextMode,
+ i18n("%1 items on Light Table")
+ .arg(d->barView->countItems()));
+ break;
+ }
+}
+
+void LightTableWindow::slotItemsUpdated(const KURL::List& urls)
+{
+ d->barView->refreshThumbs(urls);
+
+ for (KURL::List::const_iterator it = urls.begin() ; it != urls.end() ; ++it)
+ {
+ if (d->previewView->leftImageInfo())
+ {
+ if (d->previewView->leftImageInfo()->kurl() == *it)
+ {
+ d->previewView->leftReload();
+ d->leftSidebar->itemChanged(d->previewView->leftImageInfo());
+ }
+ }
+
+ if (d->previewView->rightImageInfo())
+ {
+ if (d->previewView->rightImageInfo()->kurl() == *it)
+ {
+ d->previewView->rightReload();
+ d->rightSidebar->itemChanged(d->previewView->rightImageInfo());
+ }
+ }
+ }
+}
+
+void LightTableWindow::slotLeftPanelLeftButtonClicked()
+{
+ if (d->navigateByPairAction->isChecked()) return;
+
+ d->barView->setSelectedItem(d->barView->findItemByInfo(d->previewView->leftImageInfo()));
+}
+
+void LightTableWindow::slotRightPanelLeftButtonClicked()
+{
+ // With navigate by pair option, only the left panel can be selected.
+ if (d->navigateByPairAction->isChecked()) return;
+
+ d->barView->setSelectedItem(d->barView->findItemByInfo(d->previewView->rightImageInfo()));
+}
+
+void LightTableWindow::slotLeftPreviewLoaded(bool b)
+{
+ d->leftZoomBar->setEnabled(b);
+
+ if (b)
+ {
+ d->previewView->checkForSelection(d->barView->currentItemImageInfo());
+ d->barView->setOnLeftPanel(d->previewView->leftImageInfo());
+
+ LightTableBarItem *item = d->barView->findItemByInfo(d->previewView->leftImageInfo());
+ if (item) item->setOnLeftPanel(true);
+
+ if (d->navigateByPairAction->isChecked() && item)
+ {
+ LightTableBarItem* next = dynamic_cast<LightTableBarItem*>(item->next());
+ if (next)
+ {
+ d->barView->setOnRightPanel(next->info());
+ slotSetItemOnRightPanel(next->info());
+ }
+ else
+ {
+ LightTableBarItem* first = dynamic_cast<LightTableBarItem*>(d->barView->firstItem());
+ slotSetItemOnRightPanel(first ? first->info() : 0);
+ }
+ }
+ }
+}
+
+void LightTableWindow::slotRightPreviewLoaded(bool b)
+{
+ d->rightZoomBar->setEnabled(b);
+ if (b)
+ {
+ d->previewView->checkForSelection(d->barView->currentItemImageInfo());
+ d->barView->setOnRightPanel(d->previewView->rightImageInfo());
+
+ LightTableBarItem *item = d->barView->findItemByInfo(d->previewView->rightImageInfo());
+ if (item) item->setOnRightPanel(true);
+ }
+}
+
+void LightTableWindow::slotItemSelected(ImageInfo* info)
+{
+ if (info)
+ {
+ d->setItemLeftAction->setEnabled(true);
+ d->setItemRightAction->setEnabled(true);
+ d->editItemAction->setEnabled(true);
+ d->removeItemAction->setEnabled(true);
+ d->clearListAction->setEnabled(true);
+ d->fileDeleteAction->setEnabled(true);
+ d->backwardAction->setEnabled(true);
+ d->forwardAction->setEnabled(true);
+ d->firstAction->setEnabled(true);
+ d->lastAction->setEnabled(true);
+ d->syncPreviewAction->setEnabled(true);
+ d->zoomPlusAction->setEnabled(true);
+ d->zoomMinusAction->setEnabled(true);
+ d->navigateByPairAction->setEnabled(true);
+ d->slideShowAction->setEnabled(true);
+
+ LightTableBarItem* curr = d->barView->findItemByInfo(info);
+ if (curr)
+ {
+ if (!curr->prev())
+ {
+// d->backwardAction->setEnabled(false);
+ d->firstAction->setEnabled(false);
+ }
+
+ if (!curr->next())
+ {
+// d->forwardAction->setEnabled(false);
+ d->lastAction->setEnabled(false);
+ }
+
+ if (d->navigateByPairAction->isChecked())
+ {
+ d->setItemLeftAction->setEnabled(false);
+ d->setItemRightAction->setEnabled(false);
+
+ d->barView->setOnLeftPanel(info);
+ slotSetItemOnLeftPanel(info);
+ }
+ else if (d->autoLoadOnRightPanel && !curr->isOnLeftPanel())
+ {
+ d->barView->setOnRightPanel(info);
+ slotSetItemOnRightPanel(info);
+ }
+ }
+ }
+ else
+ {
+ d->setItemLeftAction->setEnabled(false);
+ d->setItemRightAction->setEnabled(false);
+ d->editItemAction->setEnabled(false);
+ d->removeItemAction->setEnabled(false);
+ d->clearListAction->setEnabled(false);
+ d->fileDeleteAction->setEnabled(false);
+ d->backwardAction->setEnabled(false);
+ d->forwardAction->setEnabled(false);
+ d->firstAction->setEnabled(false);
+ d->lastAction->setEnabled(false);
+ d->zoomPlusAction->setEnabled(false);
+ d->zoomMinusAction->setEnabled(false);
+ d->syncPreviewAction->setEnabled(false);
+ d->navigateByPairAction->setEnabled(false);
+ d->slideShowAction->setEnabled(false);
+ }
+
+ d->previewView->checkForSelection(info);
+}
+
+// Deal with one (or more) items dropped onto the left panel
+void LightTableWindow::slotLeftDroppedItems(const ImageInfoList& list)
+{
+ ImageInfo *info = *(list.begin());
+ // add the image to the existing images
+ loadImageInfos(list, info, true);
+
+ // We will check if first item from list is already stored in thumbbar
+ // Note that the thumbbar stores all ImageInfo reference
+ // in memory for preview object.
+ LightTableBarItem *item = d->barView->findItemByInfo(info);
+ if (item)
+ {
+ slotSetItemOnLeftPanel(item->info());
+ // One approach is to make this item the current one, via
+ // d->barView->setSelectedItem(item);
+ // However, this is not good, because this also sets
+ // the right thumb to the same image.
+ // Therefore we use setLeftRightItems if there is more than
+ // one item in the list of dropped images.
+ }
+}
+
+// Deal with one (or more) items dropped onto the right panel
+void LightTableWindow::slotRightDroppedItems(const ImageInfoList& list)
+{
+ ImageInfo *info = *(list.begin());
+ // add the image to the existing images
+ loadImageInfos(list, info, true);
+
+ // We will check if first item from list is already stored in thumbbar
+ // Note that the thumbbar stores all ImageInfo reference
+ // in memory for preview object.
+ LightTableBarItem *item = d->barView->findItemByInfo(info);
+ if (item)
+ {
+ slotSetItemOnRightPanel(item->info());
+ // Make this item the current one.
+ d->barView->setSelectedItem(item);
+ }
+}
+
+// Set the images for the left and right panel.
+void LightTableWindow::setLeftRightItems(const ImageInfoList &list, bool addTo)
+{
+ ImageInfoList l = list;
+
+ if (l.count() == 0)
+ return;
+
+ ImageInfo *info = l.first();
+ LightTableBarItem *ltItem = d->barView->findItemByInfo(info);
+
+ if (l.count() == 1 && !addTo)
+ {
+ // Just one item; this is used for the left panel.
+ d->barView->setOnLeftPanel(info);
+ slotSetItemOnLeftPanel(info);
+ d->barView->setSelectedItem(ltItem);
+ d->barView->ensureItemVisible(ltItem);
+ return;
+ }
+
+ if (ltItem)
+ {
+ // The first item is used for the left panel.
+ if (!addTo)
+ {
+ d->barView->setOnLeftPanel(info);
+ slotSetItemOnLeftPanel(info);
+ }
+
+ // The subsequent item is used for the right panel.
+ LightTableBarItem* next = dynamic_cast<LightTableBarItem*>(ltItem->next());
+ if (next && !addTo)
+ {
+ d->barView->setOnRightPanel(next->info());
+ slotSetItemOnRightPanel(next->info());
+ if (!d->navigateByPairAction->isChecked())
+ {
+ d->barView->setSelectedItem(next);
+ // ensure that the selected item is visible
+ // FIXME: this does not work:
+ d->barView->ensureItemVisible(next);
+ }
+ }
+
+ // If navigate by pairs is active, the left panel item is selected.
+ // (Fixes parts of bug #150296)
+ if (d->navigateByPairAction->isChecked())
+ {
+ d->barView->setSelectedItem(ltItem);
+ d->barView->ensureItemVisible(ltItem);
+ }
+ }
+}
+
+void LightTableWindow::slotSetItemLeft()
+{
+ if (d->barView->currentItemImageInfo())
+ {
+ slotSetItemOnLeftPanel(d->barView->currentItemImageInfo());
+ }
+}
+
+void LightTableWindow::slotSetItemRight()
+{
+ if (d->barView->currentItemImageInfo())
+ {
+ slotSetItemOnRightPanel(d->barView->currentItemImageInfo());
+ }
+}
+
+void LightTableWindow::slotSetItemOnLeftPanel(ImageInfo* info)
+{
+ d->previewView->setLeftImageInfo(info);
+ if (info)
+ d->leftSidebar->itemChanged(info);
+ else
+ d->leftSidebar->slotNoCurrentItem();
+}
+
+void LightTableWindow::slotSetItemOnRightPanel(ImageInfo* info)
+{
+ d->previewView->setRightImageInfo(info);
+ if (info)
+ d->rightSidebar->itemChanged(info);
+ else
+ d->rightSidebar->slotNoCurrentItem();
+}
+
+void LightTableWindow::slotClearItemsList()
+{
+ if (d->previewView->leftImageInfo())
+ {
+ d->previewView->setLeftImageInfo();
+ d->leftSidebar->slotNoCurrentItem();
+ }
+
+ if (d->previewView->rightImageInfo())
+ {
+ d->previewView->setRightImageInfo();
+ d->rightSidebar->slotNoCurrentItem();
+ }
+
+ d->barView->clear();
+ refreshStatusBar();
+}
+
+void LightTableWindow::slotDeleteItem()
+{
+ if (d->barView->currentItemImageInfo())
+ slotDeleteItem(d->barView->currentItemImageInfo());
+}
+
+void LightTableWindow::slotDeleteItem(ImageInfo* info)
+{
+ bool ask = true;
+ bool permanently = false;
+
+ KURL u = info->kurl();
+ PAlbum *palbum = AlbumManager::instance()->findPAlbum(u.directory());
+ if (!palbum)
+ return;
+
+ // Provide a digikamalbums:// URL to TDEIO
+ KURL kioURL = info->kurlForKIO();
+ KURL fileURL = u;
+
+ bool useTrash;
+
+ if (ask)
+ {
+ bool preselectDeletePermanently = permanently;
+
+ DeleteDialog dialog(this);
+
+ KURL::List urlList;
+ urlList.append(u);
+ if (!dialog.confirmDeleteList(urlList,
+ DeleteDialogMode::Files,
+ preselectDeletePermanently ?
+ DeleteDialogMode::NoChoiceDeletePermanently : DeleteDialogMode::NoChoiceTrash))
+ return;
+
+ useTrash = !dialog.shouldDelete();
+ }
+ else
+ {
+ useTrash = !permanently;
+ }
+
+ // trash does not like non-local URLs, put is not implemented
+ if (useTrash)
+ kioURL = fileURL;
+
+ if (!SyncJob::del(kioURL, useTrash))
+ {
+ TQString errMsg(SyncJob::lastErrorMsg());
+ KMessageBox::error(this, errMsg, errMsg);
+ return;
+ }
+
+ emit signalFileDeleted(u);
+
+ slotRemoveItem(info);
+}
+
+void LightTableWindow::slotRemoveItem()
+{
+ if (d->barView->currentItemImageInfo())
+ slotRemoveItem(d->barView->currentItemImageInfo());
+}
+
+void LightTableWindow::slotRemoveItem(ImageInfo* info)
+{
+ // When either the image from the left or right panel is removed,
+ // there are various situations to account for.
+ // To describe them, 4 images A B C D are used
+ // and the subscript _L and _ R mark the currently
+ // active item on the left and right panel
+
+ bool leftPanelActive = false;
+ ImageInfo *curr_linfo = d->previewView->leftImageInfo();
+ ImageInfo *curr_rinfo = d->previewView->rightImageInfo();
+ ImageInfo *new_linfo = 0;
+ ImageInfo *new_rinfo = 0;
+
+ TQ_LLONG infoId = info->id();
+
+ // First determine the next images to the current left and right image:
+ ImageInfo *next_linfo = 0;
+ ImageInfo *next_rinfo = 0;
+
+ if (curr_linfo)
+ {
+ LightTableBarItem *ltItem = d->barView->findItemByInfo(curr_linfo);
+ if (ltItem)
+ {
+ LightTableBarItem* next = dynamic_cast<LightTableBarItem*>(ltItem->next());
+ if (next)
+ {
+ next_linfo = next->info();
+ }
+ }
+ }
+
+ if (curr_rinfo)
+ {
+ LightTableBarItem *ltItem = d->barView->findItemByInfo(curr_rinfo);
+ if (ltItem)
+ {
+ LightTableBarItem* next = dynamic_cast<LightTableBarItem*>(ltItem->next());
+ if (next)
+ {
+ next_rinfo = next->info();
+ }
+ }
+ }
+
+ d->barView->removeItem(info);
+
+ // Make sure that next_linfo and next_rinfo are still available:
+ if (!d->barView->findItemByInfo(next_linfo))
+ {
+ next_linfo = 0;
+ }
+ if (!d->barView->findItemByInfo(next_rinfo))
+ {
+ next_rinfo = 0;
+ }
+
+ // removal of the left panel item?
+ if (curr_linfo)
+ {
+ if ( curr_linfo->id() == infoId )
+ {
+ leftPanelActive = true;
+ // Delete the item A_L of the left panel:
+ // 1) A_L B_R C D -> B_L C_R D
+ // 2) A_L B C_R D -> B C_L D_R
+ // 3) A_L B C D_R -> B_R C D_L
+ // 4) A_L B_R -> A_L
+ // some more corner cases:
+ // 5) A B_L C_R D -> A C_L D_R
+ // 6) A B_L C_R -> A_R C_L
+ // 7) A_LR B C D -> B_L C_R D (does not yet work)
+ // I.e. in 3) we wrap around circularly.
+
+ // When removing the left panel image,
+ // put the right panel image into the left panel.
+ // Check if this one is not the same (i.e. also removed).
+ if (curr_rinfo)
+ {
+ if (curr_rinfo->id() != infoId)
+ {
+ new_linfo = curr_rinfo;
+ // Set the right panel to the next image:
+ new_rinfo = next_rinfo;
+ // set the right panel active
+ leftPanelActive = false;
+ }
+ }
+ }
+ }
+
+ // removal of the right panel item?
+ if (curr_rinfo)
+ {
+ if (curr_rinfo->id() == infoId)
+ {
+ // Leave the left panel as the current one
+ new_linfo = curr_linfo;
+ // Set the right panel to the next image
+ new_rinfo = next_rinfo;
+ }
+ }
+
+ // Now we deal with the corner cases, where no left or right item exists.
+ // If the right panel would be set, but not the left-one, then swap
+ if (!new_linfo && new_rinfo)
+ {
+ new_linfo = new_rinfo;
+ new_rinfo = 0;
+ leftPanelActive = true;
+ }
+
+ if (!new_linfo)
+ {
+ if (d->barView->countItems() > 0)
+ {
+ LightTableBarItem* first = dynamic_cast<LightTableBarItem*>(d->barView->firstItem());
+ new_linfo = first->info();
+ }
+ }
+
+
+ // Make sure that new_linfo and new_rinfo exist.
+ // This addresses a crash occuring if the last image is removed
+ // in the navigate by pairs mode.
+ if (!d->barView->findItemByInfo(new_linfo))
+ {
+ new_linfo = 0;
+ }
+ if (!d->barView->findItemByInfo(new_rinfo))
+ {
+ new_rinfo = 0;
+ }
+
+ // no right item defined?
+ if (!new_rinfo)
+ {
+ // If there are at least two items, we can find reasonable right image.
+ if (d->barView->countItems() > 1)
+ {
+ // See if there is an item next to the left one:
+ LightTableBarItem *ltItem = d->barView->findItemByInfo(new_linfo);
+ LightTableBarItem* next = 0;
+ // re-check if ltItem is really set
+ if (ltItem)
+ {
+ next = dynamic_cast<LightTableBarItem*>(ltItem->next());
+ }
+ if (next)
+ {
+ new_rinfo = next->info();
+ }
+ else
+ {
+ // If there is no item to the right of new_linfo
+ // then we can choose the first item for new_rinfo
+ // (as we made sure that there are at least two items)
+ LightTableBarItem* first = dynamic_cast<LightTableBarItem*>(d->barView->firstItem());
+ new_rinfo = first->info();
+ }
+ }
+ }
+
+ // Check if left and right are set to the same
+ if (new_linfo && new_rinfo)
+ {
+ if (new_linfo->id() == new_rinfo->id())
+ {
+ // Only keep the left one
+ new_rinfo = 0;
+ }
+ }
+
+ // If the right panel would be set, but not the left-one, then swap
+ // (note that this has to be done here again!)
+ if (!new_linfo && new_rinfo)
+ {
+ new_linfo = new_rinfo;
+ new_rinfo = 0;
+ leftPanelActive = true;
+ }
+
+ // set the image for the left panel
+ if (new_linfo)
+ {
+ d->barView->setOnLeftPanel(new_linfo);
+ slotSetItemOnLeftPanel(new_linfo);
+
+ // make this the selected item if the left was active before
+ if ( leftPanelActive)
+ {
+ LightTableBarItem *ltItem = d->barView->findItemByInfo(new_linfo);
+ d->barView->setSelectedItem(ltItem);
+ }
+ }
+ else
+ {
+ d->previewView->setLeftImageInfo();
+ d->leftSidebar->slotNoCurrentItem();
+ }
+
+ // set the image for the right panel
+ if (new_rinfo)
+ {
+ d->barView->setOnRightPanel(new_rinfo);
+ slotSetItemOnRightPanel(new_rinfo);
+ // make this the selected item if the left was active before
+ if (!leftPanelActive)
+ {
+ LightTableBarItem *ltItem = d->barView->findItemByInfo(new_rinfo);
+ d->barView->setSelectedItem(ltItem);
+ }
+ }
+ else
+ {
+ d->previewView->setRightImageInfo();
+ d->rightSidebar->slotNoCurrentItem();
+ }
+
+ refreshStatusBar();
+}
+
+void LightTableWindow::slotEditItem()
+{
+ if (d->barView->currentItemImageInfo())
+ slotEditItem(d->barView->currentItemImageInfo());
+}
+
+void LightTableWindow::slotEditItem(ImageInfo* info)
+{
+ ImageWindow *im = ImageWindow::imagewindow();
+ ImageInfoList list = d->barView->itemsImageInfoList();
+
+ im->loadImageInfos(list, info, i18n("Light Table"), true);
+
+ if (im->isHidden())
+ im->show();
+ else
+ im->raise();
+
+ im->setFocus();
+}
+
+void LightTableWindow::slotZoomTo100Percents()
+{
+ d->previewView->toggleFitToWindowOr100();
+}
+
+void LightTableWindow::slotFitToWindow()
+{
+ d->previewView->fitToWindow();
+}
+
+void LightTableWindow::slotToggleSlideShow()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ bool startWithCurrent = config->readBoolEntry("SlideShowStartCurrent", false);
+
+ SlideShowSettings settings;
+ settings.exifRotate = AlbumSettings::instance()->getExifRotate();
+ settings.delay = config->readNumEntry("SlideShowDelay", 5) * 1000;
+ settings.printName = config->readBoolEntry("SlideShowPrintName", true);
+ settings.printDate = config->readBoolEntry("SlideShowPrintDate", false);
+ settings.printApertureFocal = config->readBoolEntry("SlideShowPrintApertureFocal", false);
+ settings.printExpoSensitivity = config->readBoolEntry("SlideShowPrintExpoSensitivity", false);
+ settings.printMakeModel = config->readBoolEntry("SlideShowPrintMakeModel", false);
+ settings.printComment = config->readBoolEntry("SlideShowPrintComment", false);
+ settings.loop = config->readBoolEntry("SlideShowLoop", false);
+ slideShow(startWithCurrent, settings);
+}
+
+void LightTableWindow::slideShow(bool startWithCurrent, SlideShowSettings& settings)
+{
+ if (!d->barView->countItems()) return;
+
+ DMetadata meta;
+ int i = 0;
+ d->cancelSlideShow = false;
+
+ d->statusProgressBar->progressBarMode(StatusProgressBar::CancelProgressBarMode,
+ i18n("Preparing slideshow. Please wait..."));
+
+ ImageInfoList list = d->barView->itemsImageInfoList();
+
+ for (ImageInfo *info = list.first() ; !d->cancelSlideShow && info ; info = list.next())
+ {
+ SlidePictureInfo pictInfo;
+ pictInfo.comment = info->caption();
+
+ // Perform optimizations: only read pictures metadata if necessary.
+ if (settings.printApertureFocal || settings.printExpoSensitivity || settings.printMakeModel)
+ {
+ meta.load(info->kurl().path());
+ pictInfo.photoInfo = meta.getPhotographInformations();
+ }
+
+ // In case of dateTime extraction from metadata failed
+ pictInfo.photoInfo.dateTime = info->dateTime();
+ settings.pictInfoMap.insert(info->kurl(), pictInfo);
+ settings.fileList.append(info->kurl());
+
+ d->statusProgressBar->setProgressValue((int)((i++/(float)list.count())*100.0));
+ kapp->processEvents();
+ }
+
+ d->statusProgressBar->progressBarMode(StatusProgressBar::TextMode, TQString());
+ refreshStatusBar();
+
+ if (!d->cancelSlideShow)
+ {
+ settings.exifRotate = AlbumSettings::instance()->getExifRotate();
+
+ SlideShow *slide = new SlideShow(settings);
+ if (startWithCurrent)
+ slide->setCurrent(d->barView->currentItemImageInfo()->kurl());
+
+ slide->show();
+ }
+}
+
+void LightTableWindow::slotProgressBarCancelButtonPressed()
+{
+ d->cancelSlideShow = true;
+}
+
+void LightTableWindow::slotToggleFullScreen()
+{
+ if (d->fullScreen) // out of fullscreen
+ {
+
+ setWindowState( windowState() & ~WindowFullScreen );
+ menuBar()->show();
+ statusBar()->show();
+ leftDock()->show();
+ rightDock()->show();
+ topDock()->show();
+ bottomDock()->show();
+
+ TQObject* obj = child("ToolBar","TDEToolBar");
+
+ if (obj)
+ {
+ TDEToolBar* toolBar = static_cast<TDEToolBar*>(obj);
+
+ if (d->fullScreenAction->isPlugged(toolBar) && d->removeFullScreenButton)
+ d->fullScreenAction->unplug(toolBar);
+
+ if (toolBar->isHidden())
+ showToolBars();
+ }
+
+ // -- remove the gui action accels ----
+
+ unplugActionAccel(d->zoomFitToWindowAction);
+
+ if (d->fullScreen)
+ {
+ d->leftSidebar->restore();
+ d->rightSidebar->restore();
+ }
+ else
+ {
+ d->leftSidebar->backup();
+ d->rightSidebar->backup();
+ }
+
+ d->fullScreen = false;
+ }
+ else // go to fullscreen
+ {
+ // hide the menubar and the statusbar
+ menuBar()->hide();
+ statusBar()->hide();
+ topDock()->hide();
+ leftDock()->hide();
+ rightDock()->hide();
+ bottomDock()->hide();
+
+ TQObject* obj = child("ToolBar","TDEToolBar");
+
+ if (obj)
+ {
+ TDEToolBar* toolBar = static_cast<TDEToolBar*>(obj);
+
+ if (d->fullScreenHideToolBar)
+ {
+ hideToolBars();
+ }
+ else
+ {
+ showToolBars();
+
+ if ( !d->fullScreenAction->isPlugged(toolBar) )
+ {
+ d->fullScreenAction->plug(toolBar);
+ d->removeFullScreenButton=true;
+ }
+ else
+ {
+ // If FullScreen button is enable in toolbar settings
+ // We don't remove it when we out of fullscreen mode.
+ d->removeFullScreenButton=false;
+ }
+ }
+ }
+
+ // -- Insert all the gui actions into the accel --
+
+ plugActionAccel(d->zoomFitToWindowAction);
+
+ if (d->fullScreen)
+ {
+ d->leftSidebar->restore();
+ d->rightSidebar->restore();
+ }
+ else
+ {
+ d->leftSidebar->backup();
+ d->rightSidebar->backup();
+ }
+
+ showFullScreen();
+ d->fullScreen = true;
+ }
+}
+
+void LightTableWindow::slotEscapePressed()
+{
+ if (d->fullScreen)
+ d->fullScreenAction->activate();
+}
+
+void LightTableWindow::showToolBars()
+{
+ TQPtrListIterator<TDEToolBar> it = toolBarIterator();
+ TDEToolBar* bar;
+
+ for( ; it.current()!=0L ; ++it)
+ {
+ bar=it.current();
+
+ if (bar->area())
+ bar->area()->show();
+ else
+ bar->show();
+ }
+}
+
+void LightTableWindow::hideToolBars()
+{
+ TQPtrListIterator<TDEToolBar> it = toolBarIterator();
+ TDEToolBar* bar;
+
+ for( ; it.current()!=0L ; ++it)
+ {
+ bar=it.current();
+
+ if (bar->area())
+ bar->area()->hide();
+ else
+ bar->hide();
+ }
+}
+
+void LightTableWindow::plugActionAccel(TDEAction* action)
+{
+ if (!action)
+ return;
+
+ d->accelerators->insert(action->text(),
+ action->text(),
+ action->whatsThis(),
+ action->shortcut(),
+ action,
+ TQ_SLOT(activate()));
+}
+
+void LightTableWindow::unplugActionAccel(TDEAction* action)
+{
+ d->accelerators->remove(action->text());
+}
+
+void LightTableWindow::slotDonateMoney()
+{
+ TDEApplication::kApplication()->invokeBrowser("http://www.digikam.org/?q=donation");
+}
+
+void LightTableWindow::slotContribute()
+{
+ TDEApplication::kApplication()->invokeBrowser("http://www.digikam.org/?q=contrib");
+}
+
+void LightTableWindow::slotEditKeys()
+{
+ KKeyDialog dialog(true, this);
+ dialog.insert( actionCollection(), i18n( "General" ) );
+ dialog.configure();
+}
+
+void LightTableWindow::slotConfToolbars()
+{
+ saveMainWindowSettings(TDEGlobal::config(), "LightTable Settings");
+ KEditToolbar dlg(factory(), this);
+
+ connect(&dlg, TQ_SIGNAL(newToolbarConfig()),
+ this, TQ_SLOT(slotNewToolbarConfig()));
+
+ dlg.exec();
+}
+
+void LightTableWindow::slotNewToolbarConfig()
+{
+ applyMainWindowSettings(TDEGlobal::config(), "LightTable Settings");
+}
+
+void LightTableWindow::slotSetup()
+{
+ Setup setup(this, 0);
+
+ if (setup.exec() != TQDialog::Accepted)
+ return;
+
+ kapp->config()->sync();
+
+ applySettings();
+}
+
+void LightTableWindow::slotLeftZoomFactorChanged(double zoom)
+{
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->previewView->leftZoomMin();
+ double zmax = d->previewView->leftZoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ int size = (int)((zoom - b) /a);
+
+ d->leftZoomBar->setZoomSliderValue(size);
+ d->leftZoomBar->setZoomTrackerText(i18n("zoom: %1%").arg((int)(zoom*100.0)));
+
+ d->leftZoomBar->setEnableZoomPlus(true);
+ d->leftZoomBar->setEnableZoomMinus(true);
+
+ if (d->previewView->leftMaxZoom())
+ d->leftZoomBar->setEnableZoomPlus(false);
+
+ if (d->previewView->leftMinZoom())
+ d->leftZoomBar->setEnableZoomMinus(false);
+}
+
+void LightTableWindow::slotRightZoomFactorChanged(double zoom)
+{
+ double h = (double)ThumbnailSize::Huge;
+ double s = (double)ThumbnailSize::Small;
+ double zmin = d->previewView->rightZoomMin();
+ double zmax = d->previewView->rightZoomMax();
+ double b = (zmin-(zmax*s/h))/(1-s/h);
+ double a = (zmax-b)/h;
+ int size = (int)((zoom - b) /a);
+
+ d->rightZoomBar->setZoomSliderValue(size);
+ d->rightZoomBar->setZoomTrackerText(i18n("zoom: %1%").arg((int)(zoom*100.0)));
+
+ d->rightZoomBar->setEnableZoomPlus(true);
+ d->rightZoomBar->setEnableZoomMinus(true);
+
+ if (d->previewView->rightMaxZoom())
+ d->rightZoomBar->setEnableZoomPlus(false);
+
+ if (d->previewView->rightMinZoom())
+ d->rightZoomBar->setEnableZoomMinus(false);
+}
+
+void LightTableWindow::slotToggleSyncPreview()
+{
+ d->previewView->setSyncPreview(d->syncPreviewAction->isChecked());
+}
+
+void LightTableWindow::slotToggleOnSyncPreview(bool t)
+{
+ d->syncPreviewAction->setEnabled(t);
+
+ if (!t)
+ {
+ d->syncPreviewAction->setChecked(false);
+ }
+ else
+ {
+ if (d->autoSyncPreview)
+ d->syncPreviewAction->setChecked(true);
+ }
+}
+
+void LightTableWindow::slotBackward()
+{
+ ThumbBarItem* curr = d->barView->currentItem();
+ ThumbBarItem* last = d->barView->lastItem();
+ if (curr)
+ {
+ if (curr->prev())
+ d->barView->setSelected(curr->prev());
+ else
+ d->barView->setSelected(last);
+ }
+}
+
+void LightTableWindow::slotForward()
+{
+ ThumbBarItem* curr = d->barView->currentItem();
+ ThumbBarItem* first = d->barView->firstItem();
+ if (curr)
+ {
+ if (curr->next())
+ d->barView->setSelected(curr->next());
+ else
+ d->barView->setSelected(first);
+ }
+}
+
+void LightTableWindow::slotFirst()
+{
+ d->barView->setSelected( d->barView->firstItem() );
+}
+
+void LightTableWindow::slotLast()
+{
+ d->barView->setSelected( d->barView->lastItem() );
+}
+
+void LightTableWindow::slotToggleNavigateByPair()
+{
+ d->barView->setNavigateByPair(d->navigateByPairAction->isChecked());
+ d->previewView->setNavigateByPair(d->navigateByPairAction->isChecked());
+ slotItemSelected(d->barView->currentItemImageInfo());
+}
+
+void LightTableWindow::slotRawCameraList()
+{
+ RawCameraDlg dlg(this);
+ dlg.exec();
+}
+
+void LightTableWindow::slotThemeChanged()
+{
+ TQStringList themes(ThemeEngine::instance()->themeNames());
+ int index = themes.findIndex(AlbumSettings::instance()->getCurrentTheme());
+ if (index == -1)
+ index = themes.findIndex(i18n("Default"));
+
+ d->themeMenuAction->setCurrentItem(index);
+}
+
+void LightTableWindow::slotChangeTheme(const TQString& theme)
+{
+ AlbumSettings::instance()->setCurrentTheme(theme);
+ ThemeEngine::instance()->slotChangeTheme(theme);
+}
+
+void LightTableWindow::slotShowMenuBar()
+{
+ if (menuBar()->isVisible())
+ menuBar()->hide();
+ else
+ menuBar()->show();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/lighttable/lighttablewindow.h b/src/utilities/lighttable/lighttablewindow.h
new file mode 100644
index 00000000..f080e2d1
--- /dev/null
+++ b/src/utilities/lighttable/lighttablewindow.h
@@ -0,0 +1,162 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-03-05
+ * Description : digiKam light table GUI
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef LIGHTTABLEWINDOW_H
+#define LIGHTTABLEWINDOW_H
+
+// TQt includes.
+
+#include <tqstring.h>
+
+// KDE includes.
+
+#include <kurl.h>
+#include <tdemainwindow.h>
+
+// Local includes.
+
+#include "imageinfo.h"
+
+class TDEAction;
+
+namespace Digikam
+{
+
+class SlideShowSettings;
+class ThumbBarItem;
+class LightTableWindowPriv;
+
+class LightTableWindow : public TDEMainWindow
+{
+ TQ_OBJECT
+
+
+public:
+
+ ~LightTableWindow();
+
+ static LightTableWindow *lightTableWindow();
+ static bool lightTableWindowCreated();
+
+ void loadImageInfos(const ImageInfoList &list, ImageInfo *imageInfoCurrent, bool addTo);
+ void setLeftRightItems(const ImageInfoList &list, bool addTo);
+ void applySettings();
+ void refreshView();
+
+signals:
+
+ void signalFileDeleted(const KURL&);
+
+public slots:
+
+ void slotItemsUpdated(const KURL::List&);
+
+private:
+
+ void closeEvent(TQCloseEvent* e);
+ void setupActions();
+ void setupConnections();
+ void setupUserArea();
+ void setupStatusBar();
+ void setupAccelerators();
+ void slideShow(bool startWithCurrent, SlideShowSettings& settings);
+ void showToolBars();
+ void hideToolBars();
+ void plugActionAccel(TDEAction* action);
+ void unplugActionAccel(TDEAction* action);
+ void readSettings();
+ void writeSettings();
+ void refreshStatusBar();
+
+ LightTableWindow();
+
+private slots:
+
+ void slotBackward();
+ void slotForward();
+ void slotFirst();
+ void slotLast();
+
+ void slotSetItemLeft();
+ void slotSetItemRight();
+ void slotSetItemOnLeftPanel(ImageInfo*);
+ void slotSetItemOnRightPanel(ImageInfo*);
+ void slotLeftDroppedItems(const ImageInfoList&);
+ void slotRightDroppedItems(const ImageInfoList&);
+
+ void slotLeftPanelLeftButtonClicked();
+ void slotRightPanelLeftButtonClicked();
+
+ void slotLeftPreviewLoaded(bool);
+ void slotRightPreviewLoaded(bool);
+
+ void slotLeftZoomFactorChanged(double);
+ void slotRightZoomFactorChanged(double);
+
+ void slotToggleOnSyncPreview(bool);
+ void slotToggleSyncPreview();
+ void slotToggleNavigateByPair();
+
+ void slotEditItem();
+ void slotEditItem(ImageInfo*);
+
+ void slotDeleteItem();
+ void slotDeleteItem(ImageInfo*);
+
+ void slotRemoveItem();
+ void slotRemoveItem(ImageInfo*);
+
+ void slotItemSelected(ImageInfo*);
+ void slotClearItemsList();
+
+ void slotThumbbarDroppedItems(const ImageInfoList&);
+
+ void slotZoomTo100Percents();
+ void slotFitToWindow();
+
+ void slotProgressBarCancelButtonPressed();
+ void slotToggleSlideShow();
+ void slotToggleFullScreen();
+ void slotEscapePressed();
+ void slotDonateMoney();
+ void slotContribute();
+ void slotRawCameraList();
+ void slotEditKeys();
+ void slotConfToolbars();
+ void slotShowMenuBar();
+ void slotNewToolbarConfig();
+ void slotSetup();
+
+ void slotThemeChanged();
+ void slotChangeTheme(const TQString& theme);
+
+private:
+
+ LightTableWindowPriv *d;
+
+ static LightTableWindow *m_instance;
+};
+
+} // namespace Digikam
+
+#endif /* LIGHTTABLEWINDOW_H */
diff --git a/src/utilities/lighttable/lighttablewindowprivate.h b/src/utilities/lighttable/lighttablewindowprivate.h
new file mode 100644
index 00000000..a96e0b08
--- /dev/null
+++ b/src/utilities/lighttable/lighttablewindowprivate.h
@@ -0,0 +1,158 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-03-05
+ * Description : digiKam light table GUI
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqsplitter.h>
+
+// KDE includes.
+
+#include <tdeaction.h>
+#include <tdeaccel.h>
+
+// Local includes.
+
+#include "imagepropertiessidebardb.h"
+#include "statusprogressbar.h"
+#include "statuszoombar.h"
+#include "lighttableview.h"
+#include "lighttablebar.h"
+
+namespace Digikam
+{
+
+class LightTableWindowPriv
+{
+
+public:
+
+ LightTableWindowPriv()
+ {
+ autoLoadOnRightPanel = true;
+ autoSyncPreview = true;
+ fullScreenHideToolBar = false;
+ fullScreen = false;
+ removeFullScreenButton = false;
+ cancelSlideShow = false;
+ star0 = 0;
+ star1 = 0;
+ star2 = 0;
+ star3 = 0;
+ star4 = 0;
+ star5 = 0;
+ accelerators = 0;
+ leftSidebar = 0;
+ rightSidebar = 0;
+ previewView = 0;
+ barView = 0;
+ hSplitter = 0;
+ vSplitter = 0;
+ syncPreviewAction = 0;
+ clearListAction = 0;
+ setItemLeftAction = 0;
+ setItemRightAction = 0;
+ editItemAction = 0;
+ removeItemAction = 0;
+ fileDeleteAction = 0;
+ slideShowAction = 0;
+ fullScreenAction = 0;
+ donateMoneyAction = 0;
+ zoomFitToWindowAction = 0;
+ zoomTo100percents = 0;
+ zoomPlusAction = 0;
+ zoomMinusAction = 0;
+ statusProgressBar = 0;
+ leftZoomBar = 0;
+ rightZoomBar = 0;
+ forwardAction = 0;
+ backwardAction = 0;
+ firstAction = 0;
+ lastAction = 0;
+ navigateByPairAction = 0;
+ rawCameraListAction = 0;
+ themeMenuAction = 0;
+ contributeAction = 0;
+ showMenuBarAction = 0;
+ }
+
+ bool autoLoadOnRightPanel;
+ bool autoSyncPreview;
+ bool fullScreenHideToolBar;
+ bool fullScreen;
+ bool removeFullScreenButton;
+ bool cancelSlideShow;
+
+ TQSplitter *hSplitter;
+ TQSplitter *vSplitter;
+
+ // Rating actions.
+ TDEAction *star0;
+ TDEAction *star1;
+ TDEAction *star2;
+ TDEAction *star3;
+ TDEAction *star4;
+ TDEAction *star5;
+
+ TDEAction *forwardAction;
+ TDEAction *backwardAction;
+ TDEAction *firstAction;
+ TDEAction *lastAction;
+
+ TDEAction *setItemLeftAction;
+ TDEAction *setItemRightAction;
+ TDEAction *clearListAction;
+ TDEAction *editItemAction;
+ TDEAction *removeItemAction;
+ TDEAction *fileDeleteAction;
+ TDEAction *slideShowAction;
+ TDEAction *donateMoneyAction;
+ TDEAction *contributeAction;
+ TDEAction *zoomPlusAction;
+ TDEAction *zoomMinusAction;
+ TDEAction *zoomTo100percents;
+ TDEAction *zoomFitToWindowAction;
+ TDEAction *rawCameraListAction;
+
+ TDESelectAction *themeMenuAction;
+
+ TDEToggleAction *fullScreenAction;
+ TDEToggleAction *syncPreviewAction;
+ TDEToggleAction *navigateByPairAction;
+ TDEToggleAction *showMenuBarAction;
+
+ TDEAccel *accelerators;
+
+ LightTableBar *barView;
+
+ LightTableView *previewView;
+
+ StatusZoomBar *leftZoomBar;
+ StatusZoomBar *rightZoomBar;
+
+ StatusProgressBar *statusProgressBar;
+
+ ImagePropertiesSideBarDB *leftSidebar;
+ ImagePropertiesSideBarDB *rightSidebar;
+};
+
+} // namespace Digikam
diff --git a/src/utilities/lighttable/lighttablewindowui.rc b/src/utilities/lighttable/lighttablewindowui.rc
new file mode 100644
index 00000000..ae7ce721
--- /dev/null
+++ b/src/utilities/lighttable/lighttablewindowui.rc
@@ -0,0 +1,83 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<gui version="24" name="lighttablewindow" >
+
+<MenuBar>
+
+ <Menu name="File" ><text>&amp;File</text>
+ <Action name="lighttable_first" />
+ <Action name="lighttable_backward" />
+ <Action name="lighttable_forward" />
+ <Action name="lighttable_last" />
+ <Separator/>
+ <Action name="lighttable_setitemleft" />
+ <Action name="lighttable_setitemright" />
+ <Action name="lighttable_edititem" />
+ <Separator/>
+ <Action name="lighttable_removeitem" />
+ <Action name="lighttable_clearlist" />
+ <Separator/>
+ <Action name="lighttable_filedelete" />
+ <Separator/>
+ <Action name="lighttable_close" />
+ </Menu>
+
+ <Menu name="View" ><text>&amp;View</text>
+ <Action name="lighttable_fullscreen" />
+ <Action name="lighttable_slideshow" />
+ <Separator/>
+ <Action name="lighttable_syncpreview" />
+ <Action name="lighttable_navigatebypair" />
+ <Separator/>
+ <Action name="lighttable_zoomplus" />
+ <Action name="lighttable_zoomminus" />
+ <Action name="lighttable_zoomto100percents" />
+ <Action name="lighttable_zoomfit2window" />
+ </Menu>
+
+ <Menu name="help" ><text>&amp;Help</text>
+ <Action name="lighttable_rawcameralist"/>
+ <Action name="lighttable_donatemoney" />
+ <Action name="lighttable_contribute" />
+ </Menu>
+
+ <Merge/>
+
+ <Menu name="settings" noMerge="1"><Text>&amp;Settings</Text>
+ <Merge name="StandardToolBarMenuHandler" />
+ <Action name="options_show_menubar"/>
+ <Action name="options_show_statusbar"/>
+ <Action name="options_show_toolbar"/>
+ <Separator/>
+ <Action name="theme_menu" />
+ <Action name="options_configure_keybinding"/>
+ <Action name="options_configure_toolbars"/>
+ <Action name="options_configure" />
+ </Menu>
+
+</MenuBar>
+
+<ToolBar name="ToolBar" ><text>Main Toolbar</text>
+ <Action name="lighttable_first" />
+ <Action name="lighttable_backward" />
+ <Action name="lighttable_forward" />
+ <Action name="lighttable_last" />
+ <Separator/>
+ <Action name="lighttable_zoomplus" />
+ <Action name="lighttable_zoomminus" />
+ <Action name="lighttable_zoomfit2window" />
+ <Separator/>
+ <Action name="lighttable_setitemleft" />
+ <Action name="lighttable_setitemright" />
+ <Action name="lighttable_syncpreview" />
+ <Action name="lighttable_navigatebypair" />
+ <Separator/>
+ <Action name="lighttable_fullscreen" />
+ <Action name="lighttable_slideshow" />
+ <Merge />
+ <WeakSeparator/>
+ <Action name="logo_action" />
+</ToolBar>
+
+<ActionProperties/>
+
+</gui>
diff --git a/src/utilities/scripts/Makefile.am b/src/utilities/scripts/Makefile.am
new file mode 100644
index 00000000..198f6c0d
--- /dev/null
+++ b/src/utilities/scripts/Makefile.am
@@ -0,0 +1,4 @@
+####### This script name should probably be more 'namespaced'. Think about distros putting everything in /usr/bin...
+bin_SCRIPTS = digitaglinktree
+man_MANS = digitaglinktree.1
+
diff --git a/src/utilities/scripts/digitaglinktree b/src/utilities/scripts/digitaglinktree
new file mode 100644
index 00000000..8d0d12c9
--- /dev/null
+++ b/src/utilities/scripts/digitaglinktree
@@ -0,0 +1,568 @@
+#!/usr/bin/perl
+
+$version="1.6.3";
+# Author: Rainer Krienke, krienke@uni-koblenz.de
+#
+# Description:
+#
+# This script will create a linktree for all photos in a digikam
+# database that have tags set on them. Tags are used in digikam to
+# create virtual folders containing images that all have one or more tags.
+# Since other programs do not know about these virtual folders this script
+# maps these virtual folders into the filesystem by creating a directory
+# for each tag name and then linking from the tag dir to the tagged image in the
+# digikam photo directory.
+#
+# Call this script like:
+# digitaglinktree -r /home/user/photos -l /home/user/photos/tags \
+# -d /home/user/photos/digikam.db
+
+
+# Changes
+#
+# 1.1->1.2:
+# Support for hierarchical tags was added. The script can either create
+# a hierarchical directory structure for these tags (default) or treat them
+# as tags beeing on the same level resulting in a flat dir structure (-f)
+#
+# 1.2->1.3
+# Added support for multiple tags assigned to one photo. Up to now only
+# one tag (the last one found) was used.
+#
+# 1.3->1.4
+# Bug fix, links with same name in one tag directory were no resolved
+# which led to "file exists" errors when trying to create the symbolic link.
+#
+# 1.4-> 1.6, 2006/08/03
+# Added an option (-A) to archive photos and tag structure in an extra directory
+# 1.6-> 1.6.1 2006/08/15
+# Added an option (-C) for archive mode (-A). Added more information to
+# the manual page.
+# 1.6.1-> 1.6.2 2008/11/24 (Peter Muehlenpfordt, muehlenp@gmx.de)
+# Bug fix, subtags with the same name in different branches were merged
+# into one directory.
+# 1.6.2 -> 1.6.3 2008/11/25 Rainer Krienke, krienke@uni-koblenz.de
+# Fixed a bug that shows in some later perl interpreter versions when
+# accessing array references with $# operator. If $r is a ref to an array
+# then $#{@r} was ok in the past but now $#{r] is correct, perhaps it was
+# always correct and the fact that $#{@r} worked was just good luck ....
+#
+
+
+# ---------------------------------------------------------------------------
+# LICENSE:
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, 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 Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+# ---------------------------------------------------------------------------
+
+
+use Getopt::Std;
+use File::Spec;
+use File::Path;
+
+# Please adapt the sqlite version to your digikam version.
+# Do not forget to also install the correct sqlite version:
+# Digikam 0.7x needs sqlite2
+# Digikam 0.8x needs sqlite3
+
+#$SQLITE="/usr/bin/sqlite";
+$SQLITE="/usr/bin/sqlite3";
+$archiveDirPhotos="Photos";
+$archiveDirLinks="Tags";
+
+#
+$scriptAuthor="krienke@uni-koblenz.de";
+
+#
+# check if sqlite can open the database
+#
+sub checkVersion{
+ my($database)=shift;
+ my(@data, $res);
+
+ if( ! -r $SQLITE ){
+ warn "Cannot start sqlite executable \"$SQLITE\". \n",
+ "Please check if the executable searched is really installed.\n",
+ "You should also check if the installed version is correct. The name\n",
+ "of the executable to search for can be set in the head of this script.\n\n";
+ exit(1);
+ }
+
+ @data=`$SQLITE $database .tables 2>&1`; # Try to get the tables information
+
+ if( $? != 0 || grep(/Error:\s*file is encrypted/i, @data) != 0 ){
+ warn "Cannot open database \"$database\". Have you installed a current\n",
+ "version of sqlite? Depending on you digikam version you have to \n",
+ "install and use sqlite2 (for digikam 0.7x) or sqlite3 (for digikam >= 0.8 )\n",
+ "Please install the correct version and set the SQLITE variable in the\n",
+ "script head accordingly \n\n";
+ exit(1);
+ }
+
+ $res=grep(/TagsTree/, @data);
+ if( $res==0 ){
+ return("7") # digikam version 0.7
+ }else{
+ return("8"); # digikam version 0.8
+ }
+}
+
+
+#
+# Get all images that have tags from digikam database
+# refPathNames will contain all photos including path having tags
+# The key in refPathNames is the path, value is photos name
+#
+sub getTaggedImages{
+ my($refImg, $database, $rootDir, $refPaths, $refPathNames)=@_;
+ my($i, $image, $path, $tag, $status, $id, $pid, $tmp);
+ my($sqliteCmd, @data, $vers, @tags, %tagPath, $tagCount, $refTag);
+
+ $vers=checkVersion($database); # Find out version of digikam
+
+ # Get tag information from database
+ $sqliteCmd="\"select id,pid,name from tags;\"";
+
+ @data=`$SQLITE $database $sqliteCmd`;
+ $status=$?;
+ if( $status == 0 ){
+ foreach $i ( @data ){
+ chomp($i);
+ ($id, $pid, $tag)=split(/\|/, $i);
+ # map tag id data into array
+ $tags[$id]->{"tag"}="$tag";
+ $tags[$id]->{"pid"}="$pid";
+ }
+
+ # Now build up the path for tags having parents
+ # Eg: a tag people might have a subtag friends: people
+ # |->friends
+ # We want to find out the complete path for the subtag. For friends this
+ # would be the path "people/friends". Images with tag friends would then be
+ # linked in the directory <tagsroot>/people/friends/
+ for($i=0; $i<=$#tags; $i++){
+ $pid=$tags[$i]->{"pid"}; # Get parent tag id of current tag
+ $tag=$tags[$i]->{"tag"}; # Start constructing tag path
+ if( $pid == 0 ){
+ $tagPath{$i}=$tag;
+ next;
+ }
+
+ while( $pid != 0){
+ $tag=$tags[$pid]->{"tag"} . "/$tag"; # add parents tag name to path
+ $pid=$tags[$pid]->{"pid"}; # look if parent has another parent
+ }
+ # Store path constructed
+ $tagPath{$i}=$tag;
+ #warn "+ $tag \n";
+ }
+ }else{
+ print "Error running SQL-Command \"$sqliteCmd\" on database \"$database\"\n";
+ }
+
+
+ if( $vers==7 ){
+ # SQL to get all tagged images from digikam DB with names of tags
+ $sqliteCmd="\"select ImageTags.name,Albums.Url,ImageTags.tagid from " .
+ " ImageTags, Albums,Tags " .
+ " where Albums.id = ImageTags.dirid; \"";
+
+ }elsif( $vers == 8 ){
+ # SQL to get all tagged images from digikam DB with names of tags
+ $sqliteCmd="\"select Images.name,Albums.url,ImageTags.tagid from" .
+ " Images,Albums,ImageTags where " .
+ " Images.dirid=Albums.id AND Images.id=ImageTags.imageid; \"";
+ }else{
+ warn "Unsupported digikam database version. Contact $scriptAuthor \n\n";
+ exit(1);
+ }
+
+ @data=`$SQLITE $database $sqliteCmd`;
+ $status=$?;
+ if( $status == 0 ){
+ foreach $i ( @data ){
+ chomp($i);
+ ($image, $path, $tag)=split(/\|/, $i);
+ $refPaths->{"$rootDir$path"}=1;
+ $refPathNames->{"$rootDir$path/$image"}=1;
+
+ $tmp=$path;
+ #warn "- $rootDir, $path, $image \n";
+ # Enter all subpaths in $path as defined too
+ do{
+ $tmp=~s/\/[^\/]+$//;
+ $refPaths->{"$rootDir$tmp"}=1;
+ } while (length($tmp));
+
+
+ $refImg->{"$path/$image"}->{"path"}=$path;
+ $refImg->{"$path/$image"}->{"image"}=$image;
+ $refTag=$refImg->{"$path/$image"}->{"tag"};
+ #
+ # One image can have several tags assigned. So we manage
+ # a list of tags for each image
+ $tagCount=$#{$refTag}+1;
+ # The tags name
+ $refImg->{"$path/$image"}->{"tag"}->[$tagCount]=$tag;
+ # tags path for sub(sub+)tags
+ $refImg->{"$path/$image"}->{"tagpath"}->[$tagCount]=$tagPath{$tag};
+ }
+ }else{
+ print "Error running SQL-Command \"$sqliteCmd\" on database \"$database\"\n";
+ }
+
+ return($status);
+}
+
+
+#
+# Create a directory with tag-subdirectories containing links to photos having
+# tags set. Links can be absolute or relative as well as hard or soft.
+#
+sub createLinkTree{
+ my($refImages)=shift;
+ my($photoRootDir, $linktreeRootDir, $opt_flat, $opt_absolute, $linkMode)=@_;
+ my( $i, $j, $cmd, $path, $image, $tag, $qpath, $qtag, $qimage, $linkName,
+ $tmp, $ret, $sourceDir, $destDir);
+ my($qtagPath, $relPath);
+
+ if( -d "$linktreeRootDir" ){
+ # Remove old backuplinktree
+ system("rm -rf ${linktreeRootDir}.bak");
+ if( $? ){
+ die "Cannot run \"rm -rf ${linktreeRootDir}.bak\"\n";
+ }
+
+ # rename current tree to .bak
+ $ret=rename("$linktreeRootDir", "${linktreeRootDir}.bak" );
+ if( !$ret ){
+ die "Cannot rename \"$linktreeRootDir\" to \"${linktreeRootDir}.bak\"\n";
+ }
+ }
+
+ # Create new to level directory to hold linktree
+ $ret=mkdir("$linktreeRootDir");
+ if( !$ret ){
+ die "Cannot mkdir \"$linktreeRootDir\"\n";
+ }
+ # Iterate over all tagged images and create links
+ foreach $i (keys(%$refImages)){
+ # Check that the images in the linktrees themselves are
+ # ignored in the process of link creation. If we do not do this
+ # we might get into trouble when the linktree root is inside the
+ # digikam root dir and so the image links inside the liktree directory
+ # are indexed by digikam.
+ next if( -l "${photoRootDir}$i" );
+
+ $path=$refImages->{$i}->{"path"};
+ $image=$refImages->{$i}->{"image"};
+ #$qimage=quotemeta($image);
+ #$qpath=quotemeta($path);
+
+ # Handle all of the tags for the current photo
+ for( $j=0; $j<=$#{$refImages->{$i}->{"tag"}}; $j++){
+ $tag=$refImages->{$i}->{"tagpath"}->[$j];
+ #$qtagPath=quotemeta($tagPath);
+
+ # For tags that have subtags there is a path defined in qtagPath
+ # describing the parentrelationship as directory path like "friends/family/brothers"
+ # If it is defined we want to use this path instead of the flat tag name like "brothers"
+ # Doing so results in a directory hirachy that maps the tags parent relationships
+ # into the filesystem.
+ if( $opt_flat ){
+ # For flat option just use the last subdirectory
+ $tag=~s/^.+\///;
+ }
+ # Create subdirectories needed
+ if( ! -d "${linktreeRootDir}/$tag" ){
+ $ret=mkpath("${linktreeRootDir}/$tag", 0, 0755);
+
+ if( !$ret ){
+ die "Cannot mkdir \"${linktreeRootDir}/$tag\"\n";
+ }
+ }
+ # Avoid conflicts for images with the same name in one tag directory
+ $linkName=$image;
+ $tmp=$image;
+ while( -r "${linktreeRootDir}/$tag/$tmp" ){
+ $linkName="_" . $linkName;
+ $tmp="_" . $tmp;
+ }
+
+ if(length($opt_absolute)){
+ # Create link
+ $sourceDir="${photoRootDir}$path/$image";
+ $destDir="${linktreeRootDir}/$tag/$linkName";
+ }else{
+ # Get relative path from absolute one
+ # $rel=File::Spec->abs2rel( $path, $base ) ;
+ $relPath="";
+ $relPath=File::Spec->abs2rel("${photoRootDir}$path", "${linktreeRootDir}/$tag" ) ;
+ if( !length($relPath)){
+ die "Cannot create relative path from \"${linktreeRootDir}/$tag\" to \"${photoRootDir}$path\" . Abort.\n";
+ }
+ # Create link
+ #print "-> $relPath\n";
+ $sourceDir="$relPath/$image";
+ $destDir="${linktreeRootDir}/$tag/$linkName";
+ }
+
+ if( $linkMode eq "symbolic" ){
+ $ret=symlink($sourceDir, $destDir);
+ if( !$ret ){
+ $ret="$!";
+ warn "Warning: Failed symbolic linking \"$sourceDir\" to \"$destDir\": ",
+ "\"$ret\"\n";
+ }
+ }elsif( $linkMode eq "hard" ){
+ $ret=link($sourceDir, $destDir);
+ if( !$ret ){
+ $ret="$!";
+ warn "Warning: Failed hard linking \"$sourceDir\" to \"$destDir\": ",
+ "\"$ret\"\n";
+ }
+ }else{
+ die "$0: Illegal linkMode: \"$linkMode\". Abort.\n";
+ }
+
+ }# for
+ }
+}
+
+
+
+#
+# Clone a directory into another directory.
+# source and destination have to exist
+# Files in $srcDir will symbolically or hard linked
+# to the $cloneDir according to $cloneMode which can
+# be symbolic for symbolic links or hard for hardlinks
+# refPathNames contains path for all photos having tags set
+#
+sub cloneDir{
+ local($srcDir, $cloneDir, $linkMode, $cloneMode, $refPaths, $refPathNames)=@_;
+ local(@entries, $ki, $path, $name, $ret);
+
+ # Read directory entries
+ opendir( DIR, $srcDir ) || warn "Cannot read directory $srcDir \n";
+ local(@entries)=readdir(DIR);
+ closedir(DIR);
+
+ foreach $k (@entries){
+ next if( $k eq '.' );
+ next if( $k eq '..');
+ next if( $k =~ /digikam.*\.db/o );
+
+ #warn "-> $srcDir, $k, $cloneDir\n";
+ # Recurse into directories
+ if( -d "$srcDir/$k" ){
+ # if cloneMode is "minimal" then only clone directories and files
+ # with photos that have tags set.
+ if( $cloneMode ne "minimal" ||
+ defined($refPaths->{"$srcDir/$k"}) ){
+ #warn "* $srcDir/$k defined \n";
+ mkdir("$cloneDir/$k", 0755);
+ if( $? ){
+ die "Cannot run \"mkdir $cloneDir/$k\"\n";
+ }
+ cloneDir("$srcDir/$k", "$cloneDir/$k", $linkMode,
+ $cloneMode, $refPaths, $refPathNames );
+ }
+ }else{
+ # if cloneMode is "minimal" then only clone directories and files
+ # with photos that have tags set.
+ if( $cloneMode ne "minimal" ||
+ defined($refPathNames->{"$srcDir/$k"}) ){
+ # link files
+ if( $linkMode eq "symbolic" ){
+ $ret=symlink( "$srcDir/$k", "$cloneDir/$k");
+ if( !$ret ){
+ $ret="$!";
+ warn "Warning: In cloning failed symbolic linking \"$srcDir/$k\" to \"$cloneDir/$k\": ",
+ "\"$ret\"\n";
+ }
+ }elsif( $linkMode eq "hard" ){
+ $ret=link( "$srcDir/$k", "$cloneDir/$k");
+ if( !$ret ){
+ $ret="$!";
+ warn "Warning: In cloning failed hard linking \"$srcDir/$k\" to \"$cloneDir/$k\": ",
+ "\"$ret\"\n";
+ }
+ }else{
+ die "$0: Illegal cloneLinkMode: $linkMode set. Aborting.\n";
+ }
+ }
+ }
+ }
+}
+
+
+#
+# Manage cloning of a complete directory. If the clone directory exists
+# a .bak will be created
+# Files will be linked (hard/symbolic) according to cloneLinkMode which
+# should be "symbolic" or "hard"
+# cloneMode can "minimal" or anything else. minimal means only to clone
+# those files and directories that have tags set. Anything else means
+# to clone the whole photo tree with all files
+#
+sub runClone{
+ my($rootDir, $cloneDir, $photoDir, $cloneMode, $cloneLinkMode,
+ $refPaths, $refPathNames)=@_;
+ my($ret);
+ my($dev_r,$dev_A, $ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks);
+
+ if( -d "$cloneDir" ){
+ # Remove old backuplinktree
+ system("rm -rf ${cloneDir}.bak");
+ if( $? ){
+ die "Cannot run \"rm -rf ${cloneDir}.bak\"\n";
+ }
+
+ # rename current tree to .bak
+ $ret=rename("$cloneDir", "${cloneDir}.bak" );
+ if( !$ret ){
+ die "Cannot rename \"$cloneDir\" to \"${cloneDir}.bak\"\n";
+ }
+ }
+ $ret=mkdir("$cloneDir", 0755);
+ if( !$ret ){
+ die "Cannot run \"mkdir $cloneDir\"\n";
+ }
+ $ret=mkdir("$cloneDir/$photoDir", 0755);
+ if( !$ret ){
+ die "Cannot run \"mkdir $cloneDir/$photoDir\"\n";
+ }
+
+ # Check if root and archive dir are on same filesystem
+ ($dev_r,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks) = stat($rootDir);
+ ($dev_A,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks) = stat($cloneDir);
+ #
+ if( $dev_r != $dev_A ){
+ warn "Error: \"$rootDir\" and \"$cloneDir\" are not on the same filesystem. Please select ",
+ "an archive directory on the same filesystem like digikams root directory! Abort.\n";
+ exit 1;
+ }
+
+ # Create clone of all entries in $rootDir into $clonedir
+ cloneDir($rootDir, "$cloneDir/$photoDir", $cloneLinkMode,
+ $cloneMode, $refPaths, $refPathNames);
+}
+
+
+#
+# print out usage
+#
+sub usage{
+ print "$0 -r <photoroot> \n",
+ " -l <taglinkdir> | -A <archivedir> \n",
+ " -d <digikamdatabasefile> \n",
+ " [-H|-f|-a|-v|-C] \n",
+ " -r <rootdir> path to digikams root directory containing all photos\n",
+ " -l <tagdir> path to directory where the tag directory structure with \n",
+ " links to the original images should be created \n",
+ " -d <dbfile> full path to the digikam database file \n",
+ " -A <ardir> Create selfcontained archive of photos and tags in directory\n",
+ " ardir. rootdir and arDir have to be on the *same* filesystem!\n",
+ " -C If -A <archdir> was given this option will put hardlinks of all\n",
+ " photos in the \"$archiveDirPhotos\" directory not only of those with tags.\n",
+ " -a Create absolute symbolic links instead of relative ones \n",
+ " -H Use hard links instead of symbolic links in linktree. \n",
+ " -f If there are hierarchical tags (tags that have subtags) \n",
+ " create a flat tag directory hierarchy. So directories for\n",
+ " subtags are at the same directory level like their parent tags\n",
+ " -h Print this help \n",
+ " -v Print scripts version number \n";
+
+ print "Exit.\n\n";
+ exit 1;
+}
+
+
+# ------------------------------------------------------------------
+# main
+# ------------------------------------------------------------------
+$ret=getopts('HhCar:l:d:A:fv');
+
+if( defined($opt_h) ){
+ usage(0);
+}
+
+if( defined($opt_v) ){
+ print "Version: $version\n";
+ exit 0;
+}
+
+usage if( !length($opt_r) || (!length($opt_l) && ! length($opt_A))
+ || !length($opt_d) );
+
+$opt_f=0 if( ! length($opt_f) );
+
+if( ! -d $opt_r ){
+ print "Invalid digikam photoroot directory \"$opt_r\". Exit.\n";
+ exit 1;
+}
+if( ! -f $opt_d ){
+ print "Digikam database \"$opt_d\" not found. Exit.\n";
+ exit 1;
+}
+
+# Remove trailing /
+$opt_r=~s/\/$//;
+$opt_l=~s/\/$//;
+#
+$rootDir=$opt_r;
+$linkDir=$opt_l;
+
+if( length($opt_C) ){
+ $cloneMode="full"; # Clone all files/dirs
+}else{
+ $cloneMode="minimal"; # Only clone dirs and files haviong tags not all files
+}
+
+if( length($opt_H) ){
+ $linkMode="hard"; # type of link from linktree to phototree
+}else{
+ $linkMode="symbolic"; # type of link from linktree to phototree
+}
+
+# Extract data needed from database
+$ret=getTaggedImages(\%images, $opt_d, $opt_r, \%paths, \%pathNames);
+
+# Handle Archiving photos and tag structure
+# digikams photo dir will be clone as a whole by using
+# either soft or hard links ($cloneMode)
+if( length($opt_A) ){
+ #
+ runClone($opt_r, $opt_A, $archiveDirPhotos, $cloneMode, "hard", \%paths, \%pathNames);
+ $rootDir="$opt_A/$archiveDirPhotos";
+ $linkDir="$opt_A/$archiveDirLinks";
+}
+
+if( !$ret && ( length($opt_l) || length($opt_A)) ){
+ # When doing hard links we always use absolute linking
+ if( $linkMode eq "hard" ){
+ warn "Will use absolute hard links for linktree in archive mode...\n" if( !length($opt_a) );
+ $opt_a="1";
+ }
+
+ # Create the link trees for all tagged images
+ createLinkTree(\%images, $rootDir, $linkDir, $opt_f, $opt_a, $linkMode);
+}
diff --git a/src/utilities/scripts/digitaglinktree.1 b/src/utilities/scripts/digitaglinktree.1
new file mode 100644
index 00000000..fdfe9e09
--- /dev/null
+++ b/src/utilities/scripts/digitaglinktree.1
@@ -0,0 +1,182 @@
+.\" -*-Nroff-*-
+.\"
+.TH digitaglinktree 1 "16 Aug 2006 " " " "Linux User's Manual"
+.SH NAME
+digitaglinktree \- Export tag structure of photos in digikam to the filesystem.
+.SH SYNOPSIS
+.B digitaglinktree
+.B -r\fI rootdir\fR
+
+.B -l\fI taglinkdir\fR
+|
+.B -A\fI archivedir\fR
+
+.B -d\fI database\fR
+
+.B [-H|-f|-a|-v|-C]
+
+.SH DESCRIPTION
+.B "digitaglinktree "
+will create a linktree for all photos in a digikam database that have tags set
+on them. Tags (like eg. "family", "events", ...) are used in digikam to create
+virtual folders containing images that all have one or more tags assigned.
+Please note: Photos that have no tags at all assigned are silently ignored by
+this script. The program will not modify or even touch your original photos
+managed by digikam.
+
+
+The script can be used in two ways: If you call it using
+Option -l \fItaglinkdir\fR the script will create the user specified
+directory \fItaglinkdir\fR and inside this directory it will create sub
+directories for digikam tags set on the photos. Inside these subdirectories it
+will finally place symbolic or hard links (see -H) to photos having the tags
+in question. As a result you will see the tags of your photos as folders and in
+these folders you will find links to your original photos.
+
+
+In this way you can access the collection of all images that share a certain tag
+by changing directory to the folder with the tags name created by this script.
+This allows you e.g. to run JAlbum a photo album software that needs to find the
+pictures to be put into a web album in the filesystem because JAlbum cannot
+access digikams virtual folders directly.
+
+
+The second way of calling this script is the so called archive-mode by setting
+option -A \fIarchiveDir\fR.
+
+Archive mode is thought for people who want to archive tagged photos
+independently of digikams root directories and the photos therein. This way you
+can put your photos and their tag structure in eg. a tar archive and send it to
+a friend, who can look at the photos via their tag structure. In this mode the
+script creates the directory given as parameter to -A and in this directory two
+more subdirectories. One named Photos and a second named Tags. The Photos
+directory contains hard links to your original photos, and the Tags directory
+contains a subdirectory for each Tag used by any of your photos. Inside this
+subdirectory there are links (either symbolic or hard links) to the files in the
+Photos directory. This way the archive directory needs nearly no additional
+space on your harddisk and you have an archive that allows you or a friend to
+easily look at the photos tag structure.
+
+Another benefit from using this script is that you have kind of a backup of your
+tag settings for all of your photos. The backup is simply the directory
+structure containing links to the original images that wear the tags.
+This could become important if
+for whatever reason the digikam.db file gets corrupted or even lost.
+
+.PP
+.SH "COMMAND\-LINE OPTIONS"
+.TP
+\fB \-r \fIrootdir\fR
+\fIrootdir\fR denotes the digikam base directory containing all your photos.
+
+.TP
+\fB \-l\fI taglinkdir\fR
+Parameter \fI taglinkdir\fR denotes a directory in which the tag structure of
+all your photos stored in
+rootdir will be exported to by creating subdirectories for each tag and placing
+symbolic links in these subdirectories that point to the original photo wearing
+the tags. If calling the script with option -l\fI taglinkDir\fR you also have
+to specify options -r \fIrootdir\fR as well as -d \fIdatabase\fR.
+
+.TP
+\fB \-A \fIarchivedirectory\fR
+\fIarchivedirectory\fR denotes a directory into which the script will export the photos and their tag
+structure. -A has to be used together with option -r \fIrootdir\fR as well as
+-d\fI database\fR else the script will terminate. Inside the archive directory
+the script will create a Photos and a Tags directory. It will put hard links in
+the Photos directory that point to your original photos. By using hard links
+you are independent of changes in your digikam root directory but on the other
+hand you are limited to one filesystem. So the directory given by
+-r \fIrootdir\fR and the directory specified for -A \fIarchivedir\fR have to be one
+the same filesystem. The Tags subdirectory will contain links to the files in
+the Photos directory. This way you have one archive directory that is completely
+self contained. You can tar it, send it to a friend or just put it somewhere
+for archivel or backup purposes. Usually only those photos will be archived that
+have a digikam tag set on them. By using option -C however you can perform a
+complete archive. See -C for more infos.
+
+.TP
+\fB \-d \fIdatabase\fR
+\fIdatabase\fR is the complete path including the filename to digikams photo database which
+usually can be found in digikams root directory. The files name is usually
+digikam.db .
+
+.TP
+\fB \-C\fR
+When the script is called with option -A \fIarchivedir\fR only those photos
+will be archived (by placing links) in the Photos subdirectory of
+\fIarchivedir\fR that have at least one digikam tag set. By setting option -C all
+photos will be archived to \fIarchivedir\fR no matter if they have a tag set
+or not. Note: This only changes the contents of the Photos subdirectory not of
+the Tags subdirectory in the \fIarchivedir\fR directory.
+
+.TP
+\fB \-a \fR
+By default the script will try to create relative symbolic links from the
+directory \fItaglinkdir\fR set by option -l to the photo files under
+\fIrootdir\fR given by option -r. Using this option will result in absolute symbolic
+links beeing created instead of relative ones.
+
+.TP
+\fB \-H \fR
+By default the script will create soft (symbolic) links from the Tag-Tree to the
+photos. By setting option -H the script will use hard links instead. Please note
+that hard links can only be created inside one filesystem. So your photos and the Tag tree
+have to be one the same filesystem. If not you will see a warning about this problem and the script
+will not run.
+
+.TP
+\fB \-f \fR
+In digikam photos can have hierachical tags (tags that have subtags). In this case
+digitaglinktree would by default add a directory for the tag and a subdirectory for
+each of the subtags of this tag. By setting \fB \-f \fR a subtag is treated like a
+regular tag just as its parent tag so digitaglinktree will create all subdirectories
+for tags and subtags at the same level independent of the tag - subtag hierarchy.
+
+.TP
+\fB \-v \fR
+Prints the scripts version number and exits.
+
+
+.SH CONFIGURATION
+
+The script has to know which version of database is beeing used by digikam.
+The script needs this information to find the correct sqlite binary to
+start queries to the database.
+.sp
+You have to configure the script by setting the path to the sqlite binary that
+is used by the script to query the digikam database digikam.db. Since older
+digikam version use sqlite in version 2, but later digikam 0.80 versions
+needs sqlite version 3 you have to take care to install the correct version of
+sqlite for the installed digikam version and to set the path to the correct
+sqlite executable in the scripts head:
+.sp
+Choose
+
+$SQLITE="/usr/bin/sqlite3";
+
+for digikam version 0.8x and 0.9x and
+
+$SQLITE="/usr/bin/sqlite";
+
+for digikam version 0.7x.
+
+.SH EXAMPLE
+
+A call to digitaglinktree is shown below:
+
+digiTagLinktree -r /home/user/photos -l /home/user/photos/tags \
+ -d /home/user/photos/digikam.db
+
+In this example digikams photo root denoted by -r is /home/user/photos where all of the photos
+can be found that are managed by digikam. The option -l /home/user/photos/tags
+tells the script that all the subdirectories and symbolic links will be placed in
+the directory /home/user/photos/tags. Because the link directory is
+below digikams root directory in this example, you will see a new album in digikam
+after running the script that contains the exported tag structure with all the photos inside.
+Since only links are used here this tag structure does hardly need any additional space on your
+harddisk.
+
+.SH AUTHORS
+.B digitaglinktree
+was written by Rainer Krienke <krienke at uni-koblenz.de>
diff --git a/src/utilities/setup/Makefile.am b/src/utilities/setup/Makefile.am
new file mode 100644
index 00000000..bd6fd66e
--- /dev/null
+++ b/src/utilities/setup/Makefile.am
@@ -0,0 +1,29 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/utilities/cameragui \
+ -I$(top_srcdir)/src/utilities/batch \
+ -I$(top_srcdir)/src/utilities/imageeditor/canvas \
+ -I$(top_srcdir)/src/libs/dialogs \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dimg/loaders \
+ -I$(top_srcdir)/src/libs/widgets/common \
+ $(LIBKIPI_CFLAGS) $(LIBKDCRAW_CFLAGS) $(all_includes)
+
+noinst_LTLIBRARIES = libsetup.la libshowfotosetup.la
+
+libsetup_la_SOURCES = cameraselection.cpp setupcamera.cpp \
+ setupmime.cpp setupplugins.cpp setupidentity.cpp \
+ setupgeneral.cpp setup.cpp \
+ setupcollections.cpp setupmetadata.cpp \
+ setupeditor.cpp setupmisc.cpp setupicc.cpp \
+ setupiofiles.cpp setupdcraw.cpp setupslideshow.cpp \
+ setuptooltip.cpp setuplighttable.cpp
+
+libsetup_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_GPHOTO)
+
+libshowfotosetup_la_SOURCES = setupiofiles.cpp setupdcraw.cpp \
+ setupicc.cpp setupslideshow.cpp
+
+libshowfotosetup_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
diff --git a/src/utilities/setup/cameraselection.cpp b/src/utilities/setup/cameraselection.cpp
new file mode 100644
index 00000000..2cf2635c
--- /dev/null
+++ b/src/utilities/setup/cameraselection.cpp
@@ -0,0 +1,494 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-10
+ * Description : Camera type selection dialog
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqcombobox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqradiobutton.h>
+#include <tqlistview.h>
+#include <tqvbuttongroup.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <kiconloader.h>
+#include <tdeglobalsettings.h>
+#include <kactivelabel.h>
+#include <kurlrequester.h>
+#include <tdelocale.h>
+#include <klineedit.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "searchtextbar.h"
+#include "gpcamera.h"
+#include "cameraselection.h"
+#include "cameraselection.moc"
+
+namespace Digikam
+{
+
+class CameraSelectionPriv
+{
+public:
+
+ CameraSelectionPriv()
+ {
+ listView = 0;
+ titleEdit = 0;
+ portButtonGroup = 0;
+ usbButton = 0;
+ serialButton = 0;
+ portPathLabel = 0;
+ portPathComboBox = 0;
+ umsMountURL = 0;
+ searchBar = 0;
+ }
+
+ TQVButtonGroup *portButtonGroup;
+
+ TQRadioButton *usbButton;
+ TQRadioButton *serialButton;
+
+ TQLabel *portPathLabel;
+
+ TQComboBox *portPathComboBox;
+
+ TQString UMSCameraNameActual;
+ TQString UMSCameraNameShown;
+ TQString PTPCameraNameShown;
+
+ TQStringList serialPortList;
+
+ TQListView *listView;
+
+ KLineEdit *titleEdit;
+
+ KURLRequester *umsMountURL;
+
+ SearchTextBar *searchBar;
+};
+
+CameraSelection::CameraSelection( TQWidget* parent )
+ : KDialogBase(Plain, i18n("Camera Configuration"),
+ Help|Ok|Cancel, Ok, parent, 0, true, true)
+{
+ d = new CameraSelectionPriv;
+
+ kapp->setOverrideCursor(KCursor::waitCursor());
+ setHelp("cameraselection.anchor", "digikam");
+ d->UMSCameraNameActual = TQString("Directory Browse"); // Don't be i18n!
+ d->UMSCameraNameShown = i18n("Mounted Camera");
+ d->PTPCameraNameShown = TQString("USB PTP Class Camera");
+
+ TQGridLayout* mainBoxLayout = new TQGridLayout(plainPage(), 6, 1, 0, KDialog::spacingHint());
+ mainBoxLayout->setColStretch(0, 10);
+ mainBoxLayout->setRowStretch(6, 10);
+
+ // --------------------------------------------------------------
+
+ d->listView = new TQListView(plainPage());
+ d->listView->addColumn(i18n("Camera List"));
+ d->listView->setAllColumnsShowFocus(true);
+ d->listView->setResizeMode(TQListView::LastColumn);
+ d->listView->setMinimumWidth(350);
+ TQWhatsThis::add(d->listView, i18n("<p>Select the camera name that you want to use. All "
+ "default settings on the right panel "
+ "will be set automatically.</p><p>This list has been generated "
+ "using the gphoto2 library installed on your computer.</p>"));
+
+ d->searchBar = new SearchTextBar(plainPage(), "CameraSelectionSearchBar");
+
+ // --------------------------------------------------------------
+
+ TQVGroupBox* titleBox = new TQVGroupBox( i18n("Camera Title"), plainPage() );
+ d->titleEdit = new KLineEdit( titleBox );
+ TQWhatsThis::add(d->titleEdit, i18n("<p>Set here the name used in digiKam interface to "
+ "identify this camera.</p>"));
+
+ // --------------------------------------------------------------
+
+ d->portButtonGroup = new TQVButtonGroup( i18n("Camera Port Type"), plainPage() );
+ d->portButtonGroup->setRadioButtonExclusive( true );
+
+ d->usbButton = new TQRadioButton( d->portButtonGroup );
+ d->usbButton->setText( i18n( "USB" ) );
+ TQWhatsThis::add(d->usbButton, i18n("<p>Select this option if your camera is connected to your "
+ "computer using an USB cable.</p>"));
+
+ d->serialButton = new TQRadioButton( d->portButtonGroup );
+ d->serialButton->setText( i18n( "Serial" ) );
+ TQWhatsThis::add(d->serialButton, i18n("<p>Select this option if your camera is connected to your "
+ "computer using a serial cable.</p>"));
+
+ // --------------------------------------------------------------
+
+ TQVGroupBox* portPathBox = new TQVGroupBox( i18n( "Camera Port Path" ), plainPage() );
+ d->portPathLabel = new TQLabel( portPathBox);
+ d->portPathLabel->setText( i18n( "Note: only for serial port camera" ) );
+
+ d->portPathComboBox = new TQComboBox( false, portPathBox );
+ d->portPathComboBox->setDuplicatesEnabled( false );
+ TQWhatsThis::add(d->portPathComboBox, i18n("<p>Select the serial port to use on your computer. "
+ "This option is only required if you use a serial camera.</p>"));
+
+ // --------------------------------------------------------------
+
+ TQVGroupBox* umsMountBox = new TQVGroupBox(i18n("Camera Mount Path"), plainPage());
+
+ TQLabel* umsMountLabel = new TQLabel( umsMountBox );
+ umsMountLabel->setText(i18n("Note: only for USB/IEEE mass storage camera"));
+
+ d->umsMountURL = new KURLRequester( TQString("/mnt/camera"), umsMountBox);
+ d->umsMountURL->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly);
+ TQWhatsThis::add(d->umsMountURL, i18n("<p>Set here the mount path to use on your computer. This "
+ "option is only required if you use a <b>USB Mass Storage</b> "
+ "camera.</p>"));
+
+ // --------------------------------------------------------------
+
+ TQGroupBox* box2 = new TQGroupBox( 0, TQt::Vertical, plainPage() );
+ box2->setFrameStyle( TQFrame::NoFrame );
+ TQGridLayout* box2Layout = new TQGridLayout( box2->layout(), 1, 5 );
+
+ TQLabel* logo = new TQLabel( box2 );
+
+ TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader();
+ logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 64,
+ TDEIcon::DefaultState, 0, true));
+
+ KActiveLabel* link = new KActiveLabel(box2);
+ link->setText(i18n("<p>To set a <b>USB Mass Storage</b> camera<br>"
+ "(which looks like a removable drive when mounted on your desktop), please<br>"
+ "use <a href=\"umscamera\">%1</a> from camera list.</p>")
+ .arg(d->UMSCameraNameShown));
+
+ KActiveLabel* link2 = new KActiveLabel(box2);
+ link2->setText(i18n("<p>To set a <b>Generic PTP USB Device</b><br>"
+ "(which uses the Picture Transfer Protocol), please<br>"
+ "use <a href=\"ptpcamera\">%1</a> from the camera list.</p>")
+ .arg(d->PTPCameraNameShown));
+
+ KActiveLabel* explanation = new KActiveLabel(box2);
+ explanation->setText(i18n("<p>A complete list of camera settings to use is<br>"
+ "available at <a href='http://www.teaser.fr/~hfiguiere/linux/digicam.html'>"
+ "this url</a>.</p>"));
+
+ box2Layout->addMultiCellWidget(logo, 0, 0, 0, 0);
+ box2Layout->addMultiCellWidget(link, 0, 1, 1, 1);
+ box2Layout->addMultiCellWidget(link2, 2, 3, 1, 1);
+ box2Layout->addMultiCellWidget(explanation, 4, 5, 1, 1);
+
+ // --------------------------------------------------------------
+
+ mainBoxLayout->addMultiCellWidget(d->listView, 0, 5, 0, 0);
+ mainBoxLayout->addMultiCellWidget(d->searchBar, 6, 6, 0, 0);
+ mainBoxLayout->addMultiCellWidget(titleBox, 0, 0, 1, 1);
+ mainBoxLayout->addMultiCellWidget(d->portButtonGroup, 1, 1, 1, 1);
+ mainBoxLayout->addMultiCellWidget(portPathBox, 2, 2, 1, 1);
+ mainBoxLayout->addMultiCellWidget(umsMountBox, 3, 3, 1, 1);
+ mainBoxLayout->addMultiCellWidget(box2, 4, 5, 1, 1);
+
+ // Connections --------------------------------------------------
+
+ disconnect(link, TQ_SIGNAL(linkClicked(const TQString &)),
+ link, TQ_SLOT(openLink(const TQString &)));
+
+ connect(link, TQ_SIGNAL(linkClicked(const TQString &)),
+ this, TQ_SLOT(slotUMSCameraLinkUsed()));
+
+ disconnect(link2, TQ_SIGNAL(linkClicked(const TQString &)),
+ link2, TQ_SLOT(openLink(const TQString &)));
+
+ connect(link2, TQ_SIGNAL(linkClicked(const TQString &)),
+ this, TQ_SLOT(slotPTPCameraLinkUsed()));
+
+ connect(d->listView, TQ_SIGNAL(selectionChanged(TQListViewItem *)),
+ this, TQ_SLOT(slotSelectionChanged(TQListViewItem *)));
+
+ connect(d->portButtonGroup, TQ_SIGNAL(clicked(int)),
+ this, TQ_SLOT(slotPortChanged()));
+
+ connect(this, TQ_SIGNAL(okClicked()),
+ this, TQ_SLOT(slotOkClicked()));
+
+ connect(d->searchBar, TQ_SIGNAL(signalTextChanged(const TQString&)),
+ this, TQ_SLOT(slotSearchTextChanged(const TQString&)));
+
+ // Initialize --------------------------------------------------
+
+ getCameraList();
+ getSerialPortList();
+ kapp->restoreOverrideCursor();
+}
+
+CameraSelection::~CameraSelection()
+{
+ delete d;
+}
+
+void CameraSelection::slotUMSCameraLinkUsed()
+{
+ TQListViewItem *item = d->listView->findItem(d->UMSCameraNameShown, 0);
+ if (item)
+ {
+ d->listView->setCurrentItem(item);
+ d->listView->ensureItemVisible(item);
+ }
+}
+
+void CameraSelection::slotPTPCameraLinkUsed()
+{
+ TQListViewItem *item = d->listView->findItem(d->PTPCameraNameShown, 0);
+ if (item)
+ {
+ d->listView->setCurrentItem(item);
+ d->listView->ensureItemVisible(item);
+ }
+}
+
+void CameraSelection::setCamera(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path)
+{
+ TQString camModel(model);
+
+ if (camModel == d->UMSCameraNameActual)
+ camModel = d->UMSCameraNameShown;
+
+ TQListViewItem* item = d->listView->findItem(camModel, 0);
+ if (!item) return;
+
+ d->listView->setSelected(item, true);
+ d->listView->ensureItemVisible(item);
+
+ d->titleEdit->setText(title);
+
+ if (port.contains("usb"))
+ {
+ d->usbButton->setChecked(true);
+ slotPortChanged();
+ }
+ else if (port.contains("serial"))
+ {
+ d->serialButton->setChecked(true);
+
+ for (int i=0; i<d->portPathComboBox->count(); i++)
+ {
+ if (port == d->portPathComboBox->text(i))
+ {
+ d->portPathComboBox->setCurrentItem(i);
+ break;
+ }
+ }
+ slotPortChanged();
+ }
+
+ d->umsMountURL->setURL(path);
+}
+
+void CameraSelection::getCameraList()
+{
+ int count = 0;
+ TQStringList clist;
+ TQString cname;
+
+ GPCamera::getSupportedCameras(count, clist);
+
+ for (int i = 0 ; i < count ; i++)
+ {
+ cname = clist[i];
+ if (cname == d->UMSCameraNameActual)
+ new TQListViewItem(d->listView, d->UMSCameraNameShown);
+ else
+ new TQListViewItem(d->listView, cname);
+ }
+}
+
+void CameraSelection::getSerialPortList()
+{
+ TQStringList plist;
+
+ GPCamera::getSupportedPorts(plist);
+
+ d->serialPortList.clear();
+
+ for (unsigned int i=0; i<plist.count(); i++)
+ {
+ if ((plist[i]).startsWith("serial:"))
+ d->serialPortList.append(plist[i]);
+ }
+}
+
+void CameraSelection::slotSelectionChanged(TQListViewItem *item)
+{
+ if (!item) return;
+
+ TQString model(item->text(0));
+
+ if (model == d->UMSCameraNameShown)
+ {
+ model = d->UMSCameraNameActual;
+
+ d->titleEdit->setText(model);
+
+ d->serialButton->setEnabled(true);
+ d->serialButton->setChecked(false);
+ d->serialButton->setEnabled(false);
+ d->usbButton->setEnabled(true);
+ d->usbButton->setChecked(false);
+ d->usbButton->setEnabled(false);
+ d->portPathComboBox->setEnabled(true);
+ d->portPathComboBox->insertItem(TQString("NONE"), 0);
+ d->portPathComboBox->setEnabled(false);
+
+ d->umsMountURL->setEnabled(true);
+ d->umsMountURL->clear();
+ d->umsMountURL->setURL(TQString("/mnt/camera"));
+ return;
+ }
+ else
+ {
+ d->umsMountURL->setEnabled(true);
+ d->umsMountURL->clear();
+ d->umsMountURL->setURL(TQString("/"));
+ d->umsMountURL->setEnabled(false);
+ }
+
+ d->titleEdit->setText(model);
+
+ TQStringList plist;
+ GPCamera::getCameraSupportedPorts(model, plist);
+
+ if (plist.contains("serial"))
+ {
+ d->serialButton->setEnabled(true);
+ d->serialButton->setChecked(true);
+ }
+ else
+ {
+ d->serialButton->setEnabled(true);
+ d->serialButton->setChecked(false);
+ d->serialButton->setEnabled(false);
+ }
+
+ if (plist.contains("usb"))
+ {
+ d->usbButton->setEnabled(true);
+ d->usbButton->setChecked(true);
+ }
+ else
+ {
+ d->usbButton->setEnabled(true);
+ d->usbButton->setChecked(false);
+ d->usbButton->setEnabled(false);
+ }
+
+ slotPortChanged();
+}
+
+void CameraSelection::slotPortChanged()
+{
+ if (d->usbButton->isChecked())
+ {
+ d->portPathComboBox->setEnabled(true);
+ d->portPathComboBox->clear();
+ d->portPathComboBox->insertItem( TQString("usb:"), 0 );
+ d->portPathComboBox->setEnabled(false);
+ return;
+ }
+
+ if (d->serialButton->isChecked())
+ {
+ d->portPathComboBox->setEnabled(true);
+ d->portPathComboBox->clear();
+ d->portPathComboBox->insertStringList(d->serialPortList);
+ }
+}
+
+TQString CameraSelection::currentTitle()
+{
+ return d->titleEdit->text();
+}
+
+TQString CameraSelection::currentModel()
+{
+ TQListViewItem* item = d->listView->currentItem();
+ if (!item)
+ return TQString();
+
+ TQString model(item->text(0));
+ if (model == d->UMSCameraNameShown)
+ model = d->UMSCameraNameActual;
+
+ return model;
+}
+
+TQString CameraSelection::currentPortPath()
+{
+ return d->portPathComboBox->currentText();
+}
+
+TQString CameraSelection::currentCameraPath()
+{
+ return d->umsMountURL->url();
+}
+
+void CameraSelection::slotOkClicked()
+{
+ emit signalOkClicked(currentTitle(), currentModel(),
+ currentPortPath(), currentCameraPath());
+}
+
+void CameraSelection::slotSearchTextChanged(const TQString& filter)
+{
+ bool query = false;
+ TQString search = filter.lower();
+
+ TQListViewItemIterator it(d->listView);
+
+ for ( ; it.current(); ++it )
+ {
+ TQListViewItem *item = it.current();
+
+ if (item->text(0).lower().contains(search))
+ {
+ query = true;
+ item->setVisible(true);
+ }
+ else
+ {
+ item->setVisible(false);
+ }
+ }
+
+ d->searchBar->slotSearchResult(query);
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/cameraselection.h b/src/utilities/setup/cameraselection.h
new file mode 100644
index 00000000..0458f13b
--- /dev/null
+++ b/src/utilities/setup/cameraselection.h
@@ -0,0 +1,88 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-10
+ * Description : Camera type selection dialog
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef CAMERASELECTION_H
+#define CAMERASELECTION_H
+
+// TQt includes.
+
+#include <tqstring.h>
+#include <tqstringlist.h>
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+class TQListViewItem;
+
+namespace Digikam
+{
+
+class CameraSelectionPriv;
+
+class CameraSelection : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ CameraSelection(TQWidget *parent=0);
+ ~CameraSelection();
+
+ void setCamera(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path);
+
+ TQString currentTitle();
+ TQString currentModel();
+ TQString currentPortPath();
+ TQString currentCameraPath();
+
+signals:
+
+ void signalOkClicked(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path);
+
+private:
+
+ void getCameraList();
+ void getSerialPortList();
+
+private slots:
+
+ void slotPTPCameraLinkUsed();
+ void slotUMSCameraLinkUsed();
+ void slotSelectionChanged(TQListViewItem *item);
+ void slotPortChanged();
+ void slotOkClicked();
+ void slotSearchTextChanged(const TQString&);
+
+private:
+
+ CameraSelectionPriv *d;
+};
+
+} // namespace Digikam
+
+#endif // CAMERASELECTION_H
diff --git a/src/utilities/setup/setup.cpp b/src/utilities/setup/setup.cpp
new file mode 100644
index 00000000..bd47796f
--- /dev/null
+++ b/src/utilities/setup/setup.cpp
@@ -0,0 +1,266 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-03
+ * Description : digiKam setup dialog.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtabwidget.h>
+#include <tqapplication.h>
+#include <tqframe.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <tdemessagebox.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "batchthumbsgenerator.h"
+#include "setupgeneral.h"
+#include "setuptooltip.h"
+#include "setupmetadata.h"
+#include "setupidentity.h"
+#include "setupcollections.h"
+#include "setupmime.h"
+#include "setuplighttable.h"
+#include "setupeditor.h"
+#include "setupdcraw.h"
+#include "setupiofiles.h"
+#include "setupslideshow.h"
+#include "setupicc.h"
+#include "setupplugins.h"
+#include "setupcamera.h"
+#include "setupmisc.h"
+#include "setup.h"
+#include "setup.moc"
+
+namespace Digikam
+{
+
+class SetupPrivate
+{
+public:
+
+ SetupPrivate()
+ {
+ page_general = 0;
+ page_tooltip = 0;
+ page_metadata = 0;
+ page_identity = 0;
+ page_collections = 0;
+ page_mime = 0;
+ page_lighttable = 0;
+ page_editor = 0;
+ page_dcraw = 0;
+ page_iofiles = 0;
+ page_slideshow = 0;
+ page_icc = 0;
+ page_plugins = 0;
+ page_camera = 0;
+ page_misc = 0;
+
+ generalPage = 0;
+ tooltipPage = 0;
+ metadataPage = 0;
+ identityPage = 0;
+ collectionsPage = 0;
+ mimePage = 0;
+ lighttablePage = 0;
+ editorPage = 0;
+ dcrawPage = 0;
+ iofilesPage = 0;
+ slideshowPage = 0;
+ iccPage = 0;
+ cameraPage = 0;
+ miscPage = 0;
+ pluginsPage = 0;
+ }
+
+ TQFrame *page_general;
+ TQFrame *page_tooltip;
+ TQFrame *page_metadata;
+ TQFrame *page_identity;
+ TQFrame *page_collections;
+ TQFrame *page_mime;
+ TQFrame *page_lighttable;
+ TQFrame *page_editor;
+ TQFrame *page_dcraw;
+ TQFrame *page_iofiles;
+ TQFrame *page_slideshow;
+ TQFrame *page_icc;
+ TQFrame *page_plugins;
+ TQFrame *page_camera;
+ TQFrame *page_misc;
+
+ SetupGeneral *generalPage;
+ SetupToolTip *tooltipPage;
+ SetupMetadata *metadataPage;
+ SetupIdentity *identityPage;
+ SetupCollections *collectionsPage;
+ SetupMime *mimePage;
+ SetupLightTable *lighttablePage;
+ SetupEditor *editorPage;
+ SetupDcraw *dcrawPage;
+ SetupIOFiles *iofilesPage;
+ SetupSlideShow *slideshowPage;
+ SetupICC *iccPage;
+ SetupCamera *cameraPage;
+ SetupMisc *miscPage;
+ SetupPlugins *pluginsPage;
+};
+
+Setup::Setup(TQWidget* parent, const char* name, Setup::Page page)
+ : KDialogBase(IconList, i18n("Configure"), Help|Ok|Cancel, Ok, parent,
+ name, true, true )
+{
+ d = new SetupPrivate;
+ setHelp("setupdialog.anchor", "digikam");
+
+ d->page_general = addPage(i18n("Albums"), i18n("Album Settings"),
+ BarIcon("folder_image", TDEIcon::SizeMedium));
+ d->generalPage = new SetupGeneral(d->page_general, this);
+
+ d->page_collections = addPage(i18n("Collections"), i18n("Album Collections"),
+ BarIcon("document-open", TDEIcon::SizeMedium));
+ d->collectionsPage = new SetupCollections(d->page_collections);
+
+ d->page_identity = addPage(i18n("Identity"), i18n("Default IPTC identity information"),
+ BarIcon("identity", TDEIcon::SizeMedium));
+ d->identityPage = new SetupIdentity(d->page_identity);
+
+ d->page_metadata = addPage(i18n("Metadata"), i18n("Embedded Image Information Management"),
+ BarIcon("exifinfo", TDEIcon::SizeMedium));
+ d->metadataPage = new SetupMetadata(d->page_metadata);
+
+ d->page_tooltip = addPage(i18n("Tool Tip"), i18n("Album Items Tool Tip Settings"),
+ BarIcon("filetypes", TDEIcon::SizeMedium));
+ d->tooltipPage = new SetupToolTip(d->page_tooltip);
+
+ d->page_mime = addPage(i18n("Mime Types"), i18n("File (MIME) Types Settings"),
+ BarIcon("preferences-system", TDEIcon::SizeMedium));
+ d->mimePage = new SetupMime(d->page_mime);
+
+ d->page_lighttable = addPage(i18n("Light Table"), i18n("Light Table Settings"),
+ BarIcon("lighttable", TDEIcon::SizeMedium));
+ d->lighttablePage = new SetupLightTable(d->page_lighttable);
+
+ d->page_editor = addPage(i18n("Image Editor"), i18n("Image Editor General Settings"),
+ BarIcon("image-x-generic", TDEIcon::SizeMedium));
+ d->editorPage = new SetupEditor(d->page_editor);
+
+ d->page_iofiles = addPage(i18n("Save Images"), i18n("Image Editor: Settings for Saving Images Files"),
+ BarIcon("document-save", TDEIcon::SizeMedium));
+ d->iofilesPage = new SetupIOFiles(d->page_iofiles);
+
+ d->page_dcraw = addPage(i18n("RAW decoding"), i18n("RAW Files Decoding Settings"),
+ BarIcon("kdcraw", TDEIcon::SizeMedium));
+ d->dcrawPage = new SetupDcraw(d->page_dcraw);
+
+ d->page_icc = addPage(i18n("Color Management"), i18n("Image Editor Color Management Settings"),
+ BarIcon("colorize", TDEIcon::SizeMedium));
+ d->iccPage = new SetupICC(d->page_icc, this);
+
+ d->page_plugins = addPage(i18n("Kipi Plugins"), i18n("Main Interface Plug-in Settings"),
+ BarIcon("kipi", TDEIcon::SizeMedium));
+ d->pluginsPage = new SetupPlugins(d->page_plugins);
+
+ d->page_slideshow = addPage(i18n("Slide Show"), i18n("Slide Show Settings"),
+ BarIcon("slideshow", TDEIcon::SizeMedium));
+ d->slideshowPage = new SetupSlideShow(d->page_slideshow);
+
+ d->page_camera = addPage(i18n("Cameras"), i18n("Camera Settings"),
+ BarIcon("digitalcam", TDEIcon::SizeMedium));
+ d->cameraPage = new SetupCamera(d->page_camera);
+
+ d->page_misc = addPage(i18n("Miscellaneous"), i18n("Miscellaneous Settings"),
+ BarIcon("misc", TDEIcon::SizeMedium));
+ d->miscPage = new SetupMisc(d->page_misc);
+
+ connect(this, TQ_SIGNAL(okClicked()),
+ this, TQ_SLOT(slotOkClicked()) );
+
+ if (page != LastPageUsed)
+ showPage((int) page);
+ else
+ {
+ TDEConfig* config = kapp->config();
+ config->setGroup("General Settings");
+ showPage(config->readNumEntry("Setup Page", General));
+ }
+
+ show();
+}
+
+Setup::~Setup()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("General Settings");
+ config->writeEntry("Setup Page", activePageIndex());
+ config->sync();
+ delete d;
+}
+
+void Setup::slotOkClicked()
+{
+ d->generalPage->applySettings();
+ d->tooltipPage->applySettings();
+ d->metadataPage->applySettings();
+ d->identityPage->applySettings();
+ d->collectionsPage->applySettings();
+ d->mimePage->applySettings();
+ d->cameraPage->applySettings();
+ d->lighttablePage->applySettings();
+ d->editorPage->applySettings();
+ d->dcrawPage->applySettings();
+ d->iofilesPage->applySettings();
+ d->slideshowPage->applySettings();
+ d->iccPage->applySettings();
+ d->miscPage->applySettings();
+
+ if (d->metadataPage->exifAutoRotateAsChanged())
+ {
+ TQString msg = i18n("The Exif auto-rotate thumbnails option has been changed.\n"
+ "Do you want to rebuild all albums' items' thumbnails now?\n\n"
+ "Note: thumbnail processing can take a while! You can start "
+ "this job later from the \"Tools\" menu.");
+ int result = KMessageBox::warningYesNo(this, msg);
+ if (result != KMessageBox::Yes)
+ return;
+
+ BatchThumbsGenerator *thumbsGenerator = new BatchThumbsGenerator(this);
+ thumbsGenerator->exec();
+ }
+
+ close();
+}
+
+SetupPlugins* Setup::kipiPluginsPage()
+{
+ return d->pluginsPage;
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setup.h b/src/utilities/setup/setup.h
new file mode 100644
index 00000000..ce5e57b1
--- /dev/null
+++ b/src/utilities/setup/setup.h
@@ -0,0 +1,81 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-03
+ * Description : digiKam setup dialog.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi at pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUP_H
+#define SETUP_H
+
+// KDE includes.
+
+#include <kdialogbase.h>
+
+namespace Digikam
+{
+
+class SetupPlugins;
+class SetupPrivate;
+
+class Setup : public KDialogBase
+{
+ TQ_OBJECT
+
+
+public:
+
+ enum Page
+ {
+ LastPageUsed = -1,
+ General = 0,
+ ToolTip,
+ Metadata,
+ Identify,
+ Collections,
+ Mime,
+ LightTable,
+ Editor,
+ Dcraw,
+ IOFiles,
+ Slideshow,
+ IccProfiles,
+ KipiPlugins,
+ Camera,
+ Miscellaneous
+ };
+
+ Setup(TQWidget* parent=0, const char* name=0, Page page=LastPageUsed);
+ ~Setup();
+
+ SetupPlugins *kipiPluginsPage();
+
+private slots:
+
+ void slotOkClicked();
+
+private:
+
+ SetupPrivate* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUP_H
diff --git a/src/utilities/setup/setupcamera.cpp b/src/utilities/setup/setupcamera.cpp
new file mode 100644
index 00000000..1b8d0bb8
--- /dev/null
+++ b/src/utilities/setup/setupcamera.cpp
@@ -0,0 +1,315 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-10
+ * Description : camera setup tab.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqgroupbox.h>
+#include <tqpushbutton.h>
+#include <tqlayout.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqdatetime.h>
+#include <tqlistview.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <kurllabel.h>
+#include <kiconloader.h>
+#include <tdeglobalsettings.h>
+#include <kstandarddirs.h>
+#include <kcursor.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "cameraselection.h"
+#include "cameralist.h"
+#include "cameratype.h"
+#include "gpcamera.h"
+#include "setupcamera.h"
+#include "setupcamera.moc"
+
+namespace Digikam
+{
+class SetupCameraPriv
+{
+public:
+
+ SetupCameraPriv()
+ {
+ listView = 0;
+ addButton = 0;
+ removeButton = 0;
+ editButton = 0;
+ autoDetectButton = 0;
+ }
+
+ TQPushButton *addButton;
+ TQPushButton *removeButton;
+ TQPushButton *editButton;
+ TQPushButton *autoDetectButton;
+
+ TQListView *listView;
+};
+
+SetupCamera::SetupCamera( TQWidget* parent )
+ : TQWidget( parent )
+{
+ d = new SetupCameraPriv;
+
+ TQVBoxLayout *mainLayout = new TQVBoxLayout(parent);
+ TQGridLayout* groupBoxLayout = new TQGridLayout( this, 2, 5, 0, KDialog::spacingHint() );
+
+ d->listView = new TQListView( this );
+ d->listView->addColumn( i18n("Title") );
+ d->listView->addColumn( i18n("Model") );
+ d->listView->addColumn( i18n("Port") );
+ d->listView->addColumn( i18n("Path") );
+ d->listView->addColumn( "Last Access Date", 0 ); // No i18n here. Hidden column with the last access date.
+ d->listView->setAllColumnsShowFocus(true);
+ TQWhatsThis::add( d->listView, i18n("<p>Here you can see the digital camera list used by digiKam "
+ "via the Gphoto interface."));
+
+ // -------------------------------------------------------------
+
+ d->addButton = new TQPushButton( this );
+ d->removeButton = new TQPushButton( this );
+ d->editButton = new TQPushButton( this );
+ d->autoDetectButton = new TQPushButton( this );
+
+ d->addButton->setText( i18n( "&Add..." ) );
+ d->addButton->setIconSet(SmallIcon("add"));
+ d->removeButton->setText( i18n( "&Remove" ) );
+ d->removeButton->setIconSet(SmallIcon("remove"));
+ d->editButton->setText( i18n( "&Edit..." ) );
+ d->editButton->setIconSet(SmallIcon("configure"));
+ d->autoDetectButton->setText( i18n( "Auto-&Detect" ) );
+ d->autoDetectButton->setIconSet(SmallIcon("edit-find"));
+ d->removeButton->setEnabled(false);
+ d->editButton->setEnabled(false);
+
+ TQSpacerItem* spacer = new TQSpacerItem( 20, 20, TQSizePolicy::Minimum, TQSizePolicy::Expanding );
+
+ KURLLabel *gphotoLogoLabel = new KURLLabel(this);
+ gphotoLogoLabel->setText(TQString());
+ gphotoLogoLabel->setURL("http://www.gphoto.org");
+ TDEGlobal::dirs()->addResourceType("logo-gphoto", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-gphoto", "logo-gphoto.png");
+ gphotoLogoLabel->setPixmap( TQPixmap( directory + "logo-gphoto.png" ) );
+ TQToolTip::add(gphotoLogoLabel, i18n("Visit Gphoto project website"));
+
+ groupBoxLayout->setAlignment( TQt::AlignTop );
+ groupBoxLayout->addMultiCellWidget( d->listView, 0, 5, 0, 0 );
+ groupBoxLayout->addWidget( d->addButton, 0, 1 );
+ groupBoxLayout->addWidget( d->removeButton, 1, 1 );
+ groupBoxLayout->addWidget( d->editButton, 2, 1 );
+ groupBoxLayout->addWidget( d->autoDetectButton, 3, 1 );
+ groupBoxLayout->addItem( spacer, 4, 1 );
+ groupBoxLayout->addWidget( gphotoLogoLabel, 5, 1 );
+
+ adjustSize();
+ mainLayout->addWidget(this);
+
+ // -------------------------------------------------------------
+
+ connect(gphotoLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processGphotoURL(const TQString&)));
+
+ connect(d->listView, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotSelectionChanged()));
+
+ connect(d->addButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAddCamera()));
+
+ connect(d->removeButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRemoveCamera()));
+
+ connect(d->editButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotEditCamera()));
+
+ connect(d->autoDetectButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAutoDetectCamera()));
+
+ // Add cameras --------------------------------------
+
+ CameraList* clist = CameraList::instance();
+
+ if (clist)
+ {
+ TQPtrList<CameraType>* cl = clist->cameraList();
+
+ for (CameraType *ctype = cl->first(); ctype;
+ ctype = cl->next())
+ {
+ new TQListViewItem(d->listView, ctype->title(), ctype->model(),
+ ctype->port(), ctype->path(),
+ ctype->lastAccess().toString(TQt::ISODate));
+ }
+ }
+}
+
+SetupCamera::~SetupCamera()
+{
+ delete d;
+}
+
+void SetupCamera::processGphotoURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void SetupCamera::slotSelectionChanged()
+{
+ TQListViewItem *item = d->listView->selectedItem();
+
+ if (!item)
+ {
+ d->removeButton->setEnabled(false);
+ d->editButton->setEnabled(false);
+ return;
+ }
+
+ d->removeButton->setEnabled(true);
+ d->editButton->setEnabled(true);
+}
+
+void SetupCamera::slotAddCamera()
+{
+ CameraSelection *select = new CameraSelection;
+
+ connect(select, TQ_SIGNAL(signalOkClicked(const TQString&, const TQString&,
+ const TQString&, const TQString&)),
+ this, TQ_SLOT(slotAddedCamera(const TQString&, const TQString&,
+ const TQString&, const TQString&)));
+
+ select->show();
+}
+
+void SetupCamera::slotRemoveCamera()
+{
+ TQListViewItem *item = d->listView->currentItem();
+ if (!item) return;
+
+ delete item;
+}
+
+void SetupCamera::slotEditCamera()
+{
+ TQListViewItem *item = d->listView->currentItem();
+ if (!item) return;
+
+ CameraSelection *select = new CameraSelection;
+ select->setCamera(item->text(0), item->text(1), item->text(2), item->text(3));
+
+ connect(select, TQ_SIGNAL(signalOkClicked(const TQString&, const TQString&,
+ const TQString&, const TQString&)),
+ this, TQ_SLOT(slotEditedCamera(const TQString&, const TQString&,
+ const TQString&, const TQString&)));
+
+ select->show();
+}
+
+void SetupCamera::slotAutoDetectCamera()
+{
+ TQString model, port;
+
+ kapp->setOverrideCursor( KCursor::waitCursor() );
+ int ret = GPCamera::autoDetect(model, port);
+ kapp->restoreOverrideCursor();
+
+ if (ret != 0)
+ {
+ KMessageBox::error(this,i18n("Failed to auto-detect camera.\n"
+ "Please check if your camera is turned on "
+ "and retry or try setting it manually."));
+ return;
+ }
+
+ // NOTE: See note in digikam/digikam/cameralist.cpp
+ if (port.startsWith("usb:"))
+ port = "usb:";
+
+ if (d->listView->findItem(model, 1))
+ {
+ KMessageBox::information(this, i18n("Camera '%1' (%2) is already in list.").arg(model).arg(port));
+ }
+ else
+ {
+ KMessageBox::information(this, i18n("Found camera '%1' (%2) and added it to the list.")
+ .arg(model).arg(port));
+ new TQListViewItem(d->listView, model, model, port, "/",
+ TQDateTime::currentDateTime().toString(TQt::ISODate));
+ }
+}
+
+void SetupCamera::slotAddedCamera(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path)
+{
+ new TQListViewItem(d->listView, title, model, port, path,
+ TQDateTime::currentDateTime().toString(TQt::ISODate));
+}
+
+void SetupCamera::slotEditedCamera(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path)
+{
+ TQListViewItem *item = d->listView->currentItem();
+ if (!item) return;
+
+ item->setText(0, title);
+ item->setText(1, model);
+ item->setText(2, port);
+ item->setText(3, path);
+}
+
+void SetupCamera::applySettings()
+{
+ CameraList* clist = CameraList::instance();
+
+ if (clist)
+ {
+ clist->clear();
+
+ TQListViewItemIterator it(d->listView);
+
+ for ( ; it.current(); ++it )
+ {
+ TQListViewItem *item = it.current();
+ TQDateTime lastAccess = TQDateTime::currentDateTime();
+
+ if (!item->text(4).isEmpty())
+ lastAccess = TQDateTime::fromString(item->text(4), TQt::ISODate);
+
+ CameraType *ctype = new CameraType(item->text(0), item->text(1), item->text(2),
+ item->text(3), lastAccess);
+ clist->insert(ctype);
+ }
+
+ clist->save();
+ }
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setupcamera.h b/src/utilities/setup/setupcamera.h
new file mode 100644
index 00000000..f0df8a71
--- /dev/null
+++ b/src/utilities/setup/setupcamera.h
@@ -0,0 +1,73 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-10
+ * Description : camera setup tab.
+ *
+ * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPCAMERA_H
+#define SETUPCAMERA_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupCameraPriv;
+
+class SetupCamera : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupCamera( TQWidget* parent = 0 );
+ ~SetupCamera();
+
+ void applySettings();
+
+private slots:
+
+ void processGphotoURL(const TQString& url);
+
+ void slotSelectionChanged();
+
+ void slotAddCamera();
+ void slotRemoveCamera();
+ void slotEditCamera();
+ void slotAutoDetectCamera();
+
+ void slotAddedCamera(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path);
+ void slotEditedCamera(const TQString& title, const TQString& model,
+ const TQString& port, const TQString& path);
+
+private:
+
+ SetupCameraPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif // SETUPCAMERA_H
diff --git a/src/utilities/setup/setupcollections.cpp b/src/utilities/setup/setupcollections.cpp
new file mode 100644
index 00000000..10a25c31
--- /dev/null
+++ b/src/utilities/setup/setupcollections.cpp
@@ -0,0 +1,218 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-01-02
+ * Description : collection setup tab.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqvbuttongroup.h>
+#include <tqvgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqradiobutton.h>
+#include <tqcheckbox.h>
+#include <tqlabel.h>
+#include <tqlineedit.h>
+#include <tqpushbutton.h>
+#include <tqdir.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelistbox.h>
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <tdefiledialog.h>
+#include <kurl.h>
+#include <tdemessagebox.h>
+#include <kiconloader.h>
+#include <tdeversion.h>
+
+#if KDE_IS_VERSION(3,2,0)
+#include <kinputdialog.h>
+#else
+#include <klineeditdlg.h>
+#endif
+
+// Local includes.
+
+#include "thumbnailsize.h"
+#include "albumsettings.h"
+#include "setupcollections.h"
+#include "setupcollections.moc"
+
+namespace Digikam
+{
+
+class SetupCollectionsPriv
+{
+public:
+
+ SetupCollectionsPriv()
+ {
+ albumCollectionBox = 0;
+ addCollectionButton = 0;
+ delCollectionButton = 0;
+ }
+
+ TQListBox *albumCollectionBox;
+
+ TQPushButton *addCollectionButton;
+ TQPushButton *delCollectionButton;
+};
+
+SetupCollections::SetupCollections(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupCollectionsPriv;
+
+ TQVBoxLayout *mainLayout = new TQVBoxLayout(parent);
+ TQGridLayout *collectionGroupLayout = new TQGridLayout( this, 2, 5, 0, KDialog::spacingHint() );
+
+ // --------------------------------------------------------
+
+ d->albumCollectionBox = new TDEListBox(this);
+ TQWhatsThis::add( d->albumCollectionBox, i18n("<p>You can add or remove Album "
+ "collection types here to improve how "
+ "your Albums are sorted in digiKam."));
+
+ d->albumCollectionBox->setVScrollBarMode(TQScrollView::AlwaysOn);
+
+ d->addCollectionButton = new TQPushButton( i18n("&Add..."), this);
+ d->delCollectionButton = new TQPushButton( i18n("&Delete"), this);
+
+ d->addCollectionButton->setIconSet(SmallIcon("add"));
+ d->delCollectionButton->setIconSet(SmallIcon("remove"));
+ d->delCollectionButton->setEnabled(false);
+
+ TQSpacerItem* spacer = new TQSpacerItem( 20, 20, TQSizePolicy::Minimum, TQSizePolicy::Expanding );
+
+ collectionGroupLayout->setAlignment( TQt::AlignTop );
+ collectionGroupLayout->addMultiCellWidget( d->albumCollectionBox, 0, 4, 0, 0 );
+ collectionGroupLayout->addWidget( d->addCollectionButton, 0, 1);
+ collectionGroupLayout->addWidget( d->delCollectionButton, 1, 1);
+ collectionGroupLayout->addItem( spacer, 4, 1 );
+
+ // --------------------------------------------------------
+
+ connect(d->albumCollectionBox, TQ_SIGNAL(selectionChanged()),
+ this, TQ_SLOT(slotCollectionSelectionChanged()));
+
+ connect(d->addCollectionButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotAddCollection()));
+
+ connect(d->delCollectionButton, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotDelCollection()));
+
+ // --------------------------------------------------------
+
+ readSettings();
+ adjustSize();
+ mainLayout->addWidget(this);
+}
+
+SetupCollections::~SetupCollections()
+{
+ delete d;
+}
+
+void SetupCollections::applySettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ if (!settings) return;
+
+ TQStringList collectionList;
+
+ for (TQListBoxItem *item = d->albumCollectionBox->firstItem();
+ item; item = item->next())
+ {
+ collectionList.append(item->text());
+ }
+
+ settings->setAlbumCollectionNames(collectionList);
+
+ settings->saveSettings();
+}
+
+void SetupCollections::readSettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ if (!settings) return;
+
+ d->albumCollectionBox->insertStringList(settings->getAlbumCollectionNames());
+}
+
+void SetupCollections::slotCollectionSelectionChanged()
+{
+ if (d->albumCollectionBox->currentItem() != -1)
+ d->delCollectionButton->setEnabled(true);
+ else
+ d->delCollectionButton->setEnabled(false);
+}
+
+void SetupCollections::slotAddCollection()
+{
+ bool ok;
+
+#if KDE_IS_VERSION(3,2,0)
+ TQString newCollection =
+ KInputDialog::getText(i18n("New Collection Name"),
+ i18n("Enter new collection name:"),
+ TQString(), &ok, this);
+#else
+ TQString newCollection =
+ KLineEditDlg::getText(i18n("New Collection Name"),
+ i18n("Enter new collection name:"),
+ TQString(), &ok, this);
+#endif
+
+ if (!ok) return;
+
+ bool found = false;
+ for (TQListBoxItem *item = d->albumCollectionBox->firstItem();
+ item; item = item->next())
+ {
+ if (newCollection == item->text())
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ d->albumCollectionBox->insertItem(newCollection);
+}
+
+void SetupCollections::slotDelCollection()
+{
+ int index = d->albumCollectionBox->currentItem();
+ if (index == -1)
+ return;
+
+ TQListBoxItem* item = d->albumCollectionBox->item(index);
+ if (!item) return;
+ delete item;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupcollections.h b/src/utilities/setup/setupcollections.h
new file mode 100644
index 00000000..c65b93e1
--- /dev/null
+++ b/src/utilities/setup/setupcollections.h
@@ -0,0 +1,66 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-01-02
+ * Description : collection setup tab.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPCOLLECTIONS_H
+#define SETUPCOLLECTIONS_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupCollectionsPriv;
+
+class SetupCollections : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupCollections(TQWidget* parent = 0);
+ ~SetupCollections();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private slots:
+
+ void slotCollectionSelectionChanged();
+ void slotAddCollection();
+ void slotDelCollection();
+
+private:
+
+ SetupCollectionsPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif // SETUPCOLLECTIONS_H
diff --git a/src/utilities/setup/setupdcraw.cpp b/src/utilities/setup/setupdcraw.cpp
new file mode 100644
index 00000000..57f9cb2d
--- /dev/null
+++ b/src/utilities/setup/setupdcraw.cpp
@@ -0,0 +1,150 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-02-06
+ * Description : setup RAW decoding settings.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqcolor.h>
+#include <tqhbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcheckbox.h>
+#include <tqcombobox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kiconloader.h>
+#include <kdialog.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/dcrawsettingswidget.h>
+
+// Local includes.
+
+#include "drawdecoding.h"
+#include "setupdcraw.h"
+#include "setupdcraw.moc"
+
+using namespace KDcrawIface;
+
+namespace Digikam
+{
+
+class SetupDcrawPriv
+{
+public:
+
+
+ SetupDcrawPriv()
+ {
+ dcrawSettings = 0;
+ }
+
+ KDcrawIface::DcrawSettingsWidget *dcrawSettings;
+};
+
+SetupDcraw::SetupDcraw(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupDcrawPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout(parent, 0, KDialog::spacingHint());
+ d->dcrawSettings = new DcrawSettingsWidget(parent, DcrawSettingsWidget::SIXTEENBITS);
+ d->dcrawSettings->setItemIconSet(0, SmallIconSet("kdcraw"));
+ d->dcrawSettings->setItemIconSet(1, SmallIconSet("whitebalance"));
+ d->dcrawSettings->setItemIconSet(2, SmallIconSet("lensdistortion"));
+ layout->addWidget(d->dcrawSettings);
+ layout->addStretch();
+
+ connect(d->dcrawSettings, TQ_SIGNAL(signalSixteenBitsImageToggled(bool)),
+ this, TQ_SLOT(slotSixteenBitsImageToggled(bool)));
+
+ readSettings();
+}
+
+SetupDcraw::~SetupDcraw()
+{
+ delete d;
+}
+
+void SetupDcraw::slotSixteenBitsImageToggled(bool)
+{
+ // Dcraw do not provide a way to set brigness of image in 16 bits color depth.
+ // We always set on this option. We drive brightness adjustment in digiKam Raw image loader.
+ d->dcrawSettings->setEnabledBrightnessSettings(true);
+}
+
+void SetupDcraw::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ config->writeEntry("SixteenBitsImage", d->dcrawSettings->sixteenBits());
+ config->writeEntry("WhiteBalance", d->dcrawSettings->whiteBalance());
+ config->writeEntry("CustomWhiteBalance", d->dcrawSettings->customWhiteBalance());
+ config->writeEntry("CustomWhiteBalanceGreen", d->dcrawSettings->customWhiteBalanceGreen());
+ config->writeEntry("RGBInterpolate4Colors", d->dcrawSettings->useFourColor());
+ config->writeEntry("DontStretchPixels", d->dcrawSettings->useDontStretchPixels());
+ config->writeEntry("EnableNoiseReduction", d->dcrawSettings->useNoiseReduction());
+ config->writeEntry("NRThreshold", d->dcrawSettings->NRThreshold());
+ config->writeEntry("EnableCACorrection", d->dcrawSettings->useCACorrection());
+ config->writeEntry("caRedMultiplier", d->dcrawSettings->caRedMultiplier());
+ config->writeEntry("caBlueMultiplier", d->dcrawSettings->caBlueMultiplier());
+ config->writeEntry("UnclipColors", d->dcrawSettings->unclipColor());
+ config->writeEntry("RAWBrightness", d->dcrawSettings->brightness());
+ config->writeEntry("RAWQuality", d->dcrawSettings->quality());
+ config->writeEntry("MedianFilterPasses", d->dcrawSettings->medianFilterPasses());
+ config->sync();
+}
+
+void SetupDcraw::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ d->dcrawSettings->setSixteenBits(config->readBoolEntry("SixteenBitsImage", false));
+ d->dcrawSettings->setNoiseReduction(config->readBoolEntry("EnableNoiseReduction", false));
+ d->dcrawSettings->setNRThreshold(config->readNumEntry("NRThreshold", 100));
+ d->dcrawSettings->setUseCACorrection(config->readBoolEntry("EnableCACorrection", false));
+ d->dcrawSettings->setcaRedMultiplier(config->readDoubleNumEntry("caRedMultiplier", 1.0));
+ d->dcrawSettings->setcaBlueMultiplier(config->readDoubleNumEntry("caBlueMultiplier", 1.0));
+ d->dcrawSettings->setDontStretchPixels(config->readBoolEntry("DontStretchPixels", false));
+ d->dcrawSettings->setUnclipColor(config->readNumEntry("UnclipColors", 0));
+ d->dcrawSettings->setWhiteBalance((DRawDecoding::WhiteBalance)
+ config->readNumEntry("WhiteBalance",
+ DRawDecoding::CAMERA));
+ d->dcrawSettings->setCustomWhiteBalance(config->readNumEntry("CustomWhiteBalance", 6500));
+ d->dcrawSettings->setCustomWhiteBalanceGreen(config->readDoubleNumEntry("CustomWhiteBalanceGreen", 1.0));
+ d->dcrawSettings->setFourColor(config->readBoolEntry("RGBInterpolate4Colors", false));
+ d->dcrawSettings->setQuality((DRawDecoding::DecodingQuality)
+ config->readNumEntry("RAWQuality",
+ DRawDecoding::BILINEAR));
+ d->dcrawSettings->setBrightness(config->readDoubleNumEntry("RAWBrightness", 1.0));
+ d->dcrawSettings->setMedianFilterPasses(config->readNumEntry("MedianFilterPasses", 0));
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupdcraw.h b/src/utilities/setup/setupdcraw.h
new file mode 100644
index 00000000..835199f4
--- /dev/null
+++ b/src/utilities/setup/setupdcraw.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-02-06
+ * Description : setup RAW decoding settings.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPDCRAW_H
+#define SETUPDCRAW_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class SetupDcrawPriv;
+
+class DIGIKAM_EXPORT SetupDcraw : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupDcraw(TQWidget* parent = 0);
+ ~SetupDcraw();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private slots:
+
+ void slotSixteenBitsImageToggled(bool);
+
+private:
+
+ SetupDcrawPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPDCRAW_H
diff --git a/src/utilities/setup/setupeditor.cpp b/src/utilities/setup/setupeditor.cpp
new file mode 100644
index 00000000..337b5655
--- /dev/null
+++ b/src/utilities/setup/setupeditor.cpp
@@ -0,0 +1,176 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-03
+ * Description : setup Image Editor tab.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqcolor.h>
+#include <tqhbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <kcolorbutton.h>
+#include <knuminput.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "setupeditor.h"
+#include "setupeditor.moc"
+
+namespace Digikam
+{
+class SetupEditorPriv
+{
+public:
+
+ SetupEditorPriv()
+ {
+ hideToolBar = 0;
+ themebackgroundColor = 0;
+ backgroundColor = 0;
+ colorBox = 0;
+ overExposureColor = 0;
+ underExposureColor = 0;
+ useRawImportTool = 0;
+ }
+
+ TQHBox *colorBox;
+
+ TQCheckBox *hideToolBar;
+ TQCheckBox *themebackgroundColor;
+ TQCheckBox *useRawImportTool;
+
+ KColorButton *backgroundColor;
+ KColorButton *underExposureColor;
+ KColorButton *overExposureColor;
+};
+
+SetupEditor::SetupEditor(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupEditorPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint() );
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *interfaceOptionsGroup = new TQVGroupBox(i18n("Interface Options"), parent);
+
+ d->themebackgroundColor = new TQCheckBox(i18n("&Use theme background color"), interfaceOptionsGroup);
+
+ TQWhatsThis::add(d->themebackgroundColor, i18n("<p>Enable this option to use background theme "
+ "color in image editor area"));
+
+ d->colorBox = new TQHBox(interfaceOptionsGroup);
+
+ TQLabel *backgroundColorlabel = new TQLabel(i18n("&Background color:"), d->colorBox);
+
+ d->backgroundColor = new KColorButton(d->colorBox);
+ backgroundColorlabel->setBuddy(d->backgroundColor);
+ TQWhatsThis::add(d->backgroundColor, i18n("<p>Customize background color to use "
+ "in image editor area."));
+
+ d->hideToolBar = new TQCheckBox(i18n("H&ide toolbar in fullscreen mode"), interfaceOptionsGroup);
+
+ d->useRawImportTool = new TQCheckBox(i18n("Use Raw Import Tool to handle Raw image"), interfaceOptionsGroup);
+ TQWhatsThis::add(d->useRawImportTool, i18n("<p>Set on this option to use Raw Import "
+ "tool before to load a Raw image, "
+ "to customize indeep decoding settings."));
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *exposureOptionsGroup = new TQVGroupBox(i18n("Exposure Indicators"), parent);
+
+ TQHBox *underExpoBox = new TQHBox(exposureOptionsGroup);
+ TQLabel *underExpoColorlabel = new TQLabel( i18n("&Under-exposure color:"), underExpoBox);
+ d->underExposureColor = new KColorButton(underExpoBox);
+ underExpoColorlabel->setBuddy(d->underExposureColor);
+ TQWhatsThis::add(d->underExposureColor, i18n("<p>Customize the color used in image editor to identify "
+ "under-exposed pixels."));
+
+ TQHBox *overExpoBox = new TQHBox(exposureOptionsGroup);
+ TQLabel *overExpoColorlabel = new TQLabel( i18n("&Over-exposure color:"), overExpoBox);
+ d->overExposureColor = new KColorButton(overExpoBox);
+ overExpoColorlabel->setBuddy(d->overExposureColor);
+ TQWhatsThis::add(d->overExposureColor, i18n("<p>Customize the color used in image editor to identify "
+ "over-exposed pixels."));
+
+ // --------------------------------------------------------
+
+ layout->addWidget(interfaceOptionsGroup);
+ layout->addWidget(exposureOptionsGroup);
+ layout->addStretch();
+
+ // --------------------------------------------------------
+
+ connect(d->themebackgroundColor, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotThemeBackgroundColor(bool)));
+
+ readSettings();
+}
+
+SetupEditor::~SetupEditor()
+{
+ delete d;
+}
+
+void SetupEditor::slotThemeBackgroundColor(bool e)
+{
+ d->colorBox->setEnabled(!e);
+}
+
+void SetupEditor::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ TQColor Black(TQt::black);
+ TQColor White(TQt::white);
+ config->setGroup("ImageViewer Settings");
+ d->themebackgroundColor->setChecked(config->readBoolEntry("UseThemeBackgroundColor", true));
+ d->backgroundColor->setColor(config->readColorEntry("BackgroundColor", &Black));
+ d->hideToolBar->setChecked(config->readBoolEntry("FullScreen Hide ToolBar", false));
+ d->underExposureColor->setColor(config->readColorEntry("UnderExposureColor", &White));
+ d->overExposureColor->setColor(config->readColorEntry("OverExposureColor", &Black));
+ d->useRawImportTool->setChecked(config->readBoolEntry("UseRawImportTool", false));
+}
+
+void SetupEditor::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ config->writeEntry("UseThemeBackgroundColor", d->themebackgroundColor->isChecked());
+ config->writeEntry("BackgroundColor", d->backgroundColor->color());
+ config->writeEntry("FullScreen Hide ToolBar", d->hideToolBar->isChecked());
+ config->writeEntry("UnderExposureColor", d->underExposureColor->color());
+ config->writeEntry("OverExposureColor", d->overExposureColor->color());
+ config->writeEntry("UseRawImportTool", d->useRawImportTool->isChecked());
+ config->sync();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupeditor.h b/src/utilities/setup/setupeditor.h
new file mode 100644
index 00000000..bc7793ad
--- /dev/null
+++ b/src/utilities/setup/setupeditor.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-03
+ * Description : setup Image Editor tab.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPEDITOR_H
+#define SETUPEDITOR_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupEditorPriv;
+
+class SetupEditor : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupEditor(TQWidget* parent = 0);
+ ~SetupEditor();
+
+ void applySettings();
+
+private slots:
+
+ void slotThemeBackgroundColor(bool);
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupEditorPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPEDITOR_H
diff --git a/src/utilities/setup/setupgeneral.cpp b/src/utilities/setup/setupgeneral.cpp
new file mode 100644
index 00000000..2a8db22c
--- /dev/null
+++ b/src/utilities/setup/setupgeneral.cpp
@@ -0,0 +1,313 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-01
+ * Description : general configuration setup tab
+ *
+ * Copyright (C) 2003-2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqcombobox.h>
+#include <tqvbuttongroup.h>
+#include <tqvgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqradiobutton.h>
+#include <tqcheckbox.h>
+#include <tqlabel.h>
+#include <tqdir.h>
+#include <tqlistbox.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqfileinfo.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialogbase.h>
+#include <tdefiledialog.h>
+#include <kurl.h>
+#include <tdemessagebox.h>
+#include <kurlrequester.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "setupgeneral.h"
+#include "setupgeneral.moc"
+
+namespace Digikam
+{
+
+class SetupGeneralPriv
+{
+public:
+
+ SetupGeneralPriv()
+ {
+ albumPathEdit = 0;
+ iconTreeThumbSize = 0;
+ iconTreeThumbLabel = 0;
+ iconShowNameBox = 0;
+ iconShowSizeBox = 0;
+ iconShowDateBox = 0;
+ iconShowModDateBox = 0;
+ iconShowResolutionBox = 0;
+ iconShowCommentsBox = 0;
+ iconShowTagsBox = 0;
+ iconShowRatingBox = 0;
+ rightClickActionComboBox = 0;
+ previewLoadFullImageSize = 0;
+ showFolderTreeViewItemsCount = 0;
+ }
+
+ TQLabel *iconTreeThumbLabel;
+
+ TQCheckBox *iconShowNameBox;
+ TQCheckBox *iconShowSizeBox;
+ TQCheckBox *iconShowDateBox;
+ TQCheckBox *iconShowModDateBox;
+ TQCheckBox *iconShowResolutionBox;
+ TQCheckBox *iconShowCommentsBox;
+ TQCheckBox *iconShowTagsBox;
+ TQCheckBox *iconShowRatingBox;
+ TQCheckBox *previewLoadFullImageSize;
+ TQCheckBox *showFolderTreeViewItemsCount;
+
+ TQComboBox *iconTreeThumbSize;
+ TQComboBox *rightClickActionComboBox;
+
+ KURLRequester *albumPathEdit;
+
+ KDialogBase *mainDialog;
+};
+
+SetupGeneral::SetupGeneral(TQWidget* parent, KDialogBase* dialog )
+ : TQWidget(parent)
+{
+ d = new SetupGeneralPriv;
+ d->mainDialog = dialog;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint() );
+
+ // --------------------------------------------------------
+
+ TQHGroupBox *albumPathBox = new TQHGroupBox(parent);
+ albumPathBox->setTitle(i18n("Album &Library Path"));
+
+ d->albumPathEdit = new KURLRequester(albumPathBox);
+ d->albumPathEdit->setMode(KFile::Directory | KFile::LocalOnly | KFile::ExistingOnly);
+ TQToolTip::add( d->albumPathEdit, i18n("<p>Here you can set the main path to the digiKam album "
+ "library in your computer."
+ "<p>Write access is required for this path and do not use a "
+ "remote path here, like an NFS mounted file system."));
+
+ connect(d->albumPathEdit, TQ_SIGNAL(urlSelected(const TQString &)),
+ this, TQ_SLOT(slotChangeAlbumPath(const TQString &)));
+
+ connect(d->albumPathEdit, TQ_SIGNAL(textChanged(const TQString&)),
+ this, TQ_SLOT(slotPathEdited(const TQString&)) );
+
+ layout->addWidget(albumPathBox);
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *iconTextGroup = new TQVGroupBox(i18n("Thumbnail Information"), parent);
+
+ d->iconShowNameBox = new TQCheckBox(i18n("Show file &name"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowNameBox, i18n("<p>Set this option to show the file name below the image thumbnail."));
+
+ d->iconShowSizeBox = new TQCheckBox(i18n("Show file si&ze"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowSizeBox, i18n("<p>Set this option to show the file size below the image thumbnail."));
+
+ d->iconShowDateBox = new TQCheckBox(i18n("Show camera creation &date"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowDateBox, i18n("<p>Set this option to show the camera creation date "
+ "below the image thumbnail."));
+
+ d->iconShowModDateBox = new TQCheckBox(i18n("Show file &modification date"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowModDateBox, i18n("<p>Set this option to show the file modification date "
+ "below the image thumbnail."));
+
+ d->iconShowCommentsBox = new TQCheckBox(i18n("Show digiKam &captions"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowCommentsBox, i18n("<p>Set this option to show the digiKam captions "
+ "below the image thumbnail."));
+
+ d->iconShowTagsBox = new TQCheckBox(i18n("Show digiKam &tags"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowTagsBox, i18n("<p>Set this option to show the digiKam tags "
+ "below the image thumbnail."));
+
+ d->iconShowRatingBox = new TQCheckBox(i18n("Show digiKam &rating"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowRatingBox, i18n("<p>Set this option to show the digiKam rating "
+ "below the image thumbnail."));
+
+ d->iconShowResolutionBox = new TQCheckBox(i18n("Show ima&ge dimensions (warning: slow)"), iconTextGroup);
+ TQWhatsThis::add( d->iconShowResolutionBox, i18n("<p>Set this option to show the image size in pixels "
+ "below the image thumbnail."));
+
+ layout->addWidget(iconTextGroup);
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *interfaceOptionsGroup = new TQVGroupBox(i18n("Interface Options"), parent);
+ interfaceOptionsGroup->setColumnLayout(0, TQt::Vertical );
+ interfaceOptionsGroup->layout()->setMargin(KDialog::marginHint());
+ TQGridLayout* ifaceSettingsLayout = new TQGridLayout(interfaceOptionsGroup->layout(), 3, 4, KDialog::spacingHint());
+
+ d->iconTreeThumbLabel = new TQLabel(i18n("Sidebar thumbnail size:"), interfaceOptionsGroup);
+ d->iconTreeThumbSize = new TQComboBox(false, interfaceOptionsGroup);
+ d->iconTreeThumbSize->insertItem("16");
+ d->iconTreeThumbSize->insertItem("22");
+ d->iconTreeThumbSize->insertItem("32");
+ d->iconTreeThumbSize->insertItem("48");
+ TQToolTip::add( d->iconTreeThumbSize, i18n("<p>Set this option to configure the size "
+ "in pixels of the thumbnails in digiKam's sidebars. "
+ "This option will take effect when you restart "
+ "digiKam."));
+ ifaceSettingsLayout->addMultiCellWidget(d->iconTreeThumbLabel, 0, 0, 0, 0);
+ ifaceSettingsLayout->addMultiCellWidget(d->iconTreeThumbSize, 0, 0, 1, 1);
+
+ d->showFolderTreeViewItemsCount = new TQCheckBox(i18n("Show count of items in all tree-view"), interfaceOptionsGroup);
+ ifaceSettingsLayout->addMultiCellWidget(d->showFolderTreeViewItemsCount, 1, 1, 0, 4);
+
+
+ TQLabel *rightClickLabel = new TQLabel(i18n("Thumbnail click action:"), interfaceOptionsGroup);
+ d->rightClickActionComboBox = new TQComboBox(false, interfaceOptionsGroup);
+ d->rightClickActionComboBox->insertItem(i18n("Show embedded preview"), AlbumSettings::ShowPreview);
+ d->rightClickActionComboBox->insertItem(i18n("Start image editor"), AlbumSettings::StartEditor);
+ TQToolTip::add( d->rightClickActionComboBox, i18n("<p>Here, choose what should happen when you "
+ "click on a thumbnail."));
+ ifaceSettingsLayout->addMultiCellWidget(rightClickLabel, 2 ,2, 0, 0);
+ ifaceSettingsLayout->addMultiCellWidget(d->rightClickActionComboBox, 2, 2, 1, 4);
+
+ d->previewLoadFullImageSize = new TQCheckBox(i18n("Embedded preview loads full image size"), interfaceOptionsGroup);
+ TQWhatsThis::add( d->previewLoadFullImageSize, i18n("<p>Set this option to load the full image size "
+ "with an embedded preview, instead a reduced one. Because this option will take more time "
+ "to load images, use it only if you have a fast computer."));
+ ifaceSettingsLayout->addMultiCellWidget(d->previewLoadFullImageSize, 3, 3, 0, 4);
+
+ layout->addWidget(interfaceOptionsGroup);
+
+ // --------------------------------------------------------
+
+ layout->addStretch();
+
+ readSettings();
+ adjustSize();
+}
+
+SetupGeneral::~SetupGeneral()
+{
+ delete d;
+}
+
+void SetupGeneral::applySettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings) return;
+
+ settings->setAlbumLibraryPath(d->albumPathEdit->url());
+
+ settings->setDefaultTreeIconSize(d->iconTreeThumbSize->currentText().toInt());
+ settings->setIconShowName(d->iconShowNameBox->isChecked());
+ settings->setIconShowTags(d->iconShowTagsBox->isChecked());
+ settings->setIconShowSize(d->iconShowSizeBox->isChecked());
+ settings->setIconShowDate(d->iconShowDateBox->isChecked());
+ settings->setIconShowModDate(d->iconShowModDateBox->isChecked());
+ settings->setIconShowResolution(d->iconShowResolutionBox->isChecked());
+ settings->setIconShowComments(d->iconShowCommentsBox->isChecked());
+ settings->setIconShowRating(d->iconShowRatingBox->isChecked());
+
+ settings->setItemRightClickAction((AlbumSettings::ItemRightClickAction)
+ d->rightClickActionComboBox->currentItem());
+
+ settings->setPreviewLoadFullImageSize(d->previewLoadFullImageSize->isChecked());
+ settings->setShowFolderTreeViewItemsCount(d->showFolderTreeViewItemsCount->isChecked());
+ settings->saveSettings();
+}
+
+void SetupGeneral::readSettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ if (!settings) return;
+
+ d->albumPathEdit->setURL(settings->getAlbumLibraryPath());
+
+ if (settings->getDefaultTreeIconSize() == 16)
+ d->iconTreeThumbSize->setCurrentItem(0);
+ else if (settings->getDefaultTreeIconSize() == 22)
+ d->iconTreeThumbSize->setCurrentItem(1);
+ else if (settings->getDefaultTreeIconSize() == 32)
+ d->iconTreeThumbSize->setCurrentItem(2);
+ else
+ d->iconTreeThumbSize->setCurrentItem(3);
+
+ d->iconShowNameBox->setChecked(settings->getIconShowName());
+ d->iconShowTagsBox->setChecked(settings->getIconShowTags());
+ d->iconShowSizeBox->setChecked(settings->getIconShowSize());
+ d->iconShowDateBox->setChecked(settings->getIconShowDate());
+ d->iconShowModDateBox->setChecked(settings->getIconShowModDate());
+ d->iconShowResolutionBox->setChecked(settings->getIconShowResolution());
+ d->iconShowCommentsBox->setChecked(settings->getIconShowComments());
+ d->iconShowRatingBox->setChecked(settings->getIconShowRating());
+
+ d->rightClickActionComboBox->setCurrentItem((int)settings->getItemRightClickAction());
+
+ d->previewLoadFullImageSize->setChecked(settings->getPreviewLoadFullImageSize());
+ d->showFolderTreeViewItemsCount->setChecked(settings->getShowFolderTreeViewItemsCount());
+}
+
+void SetupGeneral::slotChangeAlbumPath(const TQString &result)
+{
+ if (KURL(result).equals(KURL(TQDir::homeDirPath()), true))
+ {
+ KMessageBox::sorry(0, i18n("Sorry you can't use your home directory as album library."));
+ return;
+ }
+
+ TQFileInfo targetPath(result);
+
+ if (!result.isEmpty() && !targetPath.isWritable())
+ {
+ KMessageBox::information(0, i18n("No write access for this path.\n"
+ "Warning: the caption and tag features will not work."));
+ }
+}
+
+void SetupGeneral::slotPathEdited(const TQString& newPath)
+{
+ if (newPath.isEmpty())
+ {
+ d->mainDialog->enableButtonOK(false);
+ return;
+ }
+
+ if (!newPath.startsWith("/"))
+ {
+ d->albumPathEdit->setURL(TQDir::homeDirPath() + '/' + newPath);
+ }
+
+ TQFileInfo targetPath(newPath);
+ TQDir dir(newPath);
+ d->mainDialog->enableButtonOK(dir.exists() && dir.path() != TQDir::homeDirPath());
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setupgeneral.h b/src/utilities/setup/setupgeneral.h
new file mode 100644
index 00000000..768c4154
--- /dev/null
+++ b/src/utilities/setup/setupgeneral.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-02-01
+ * Description : general configuration setup tab
+ *
+ * Copyright (C) 2003-2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPGENERAL_H
+#define SETUPGENERAL_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+class KDialogBase;
+
+namespace Digikam
+{
+
+class SetupGeneralPriv;
+
+class SetupGeneral : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupGeneral(TQWidget* parent = 0, KDialogBase* dialog = 0);
+ ~SetupGeneral();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private slots:
+
+ void slotChangeAlbumPath(const TQString &);
+ void slotPathEdited(const TQString&);
+
+private:
+
+ SetupGeneralPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPGENERAL_H
diff --git a/src/utilities/setup/setupicc.cpp b/src/utilities/setup/setupicc.cpp
new file mode 100644
index 00000000..b08750c1
--- /dev/null
+++ b/src/utilities/setup/setupicc.cpp
@@ -0,0 +1,727 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-24
+ * Description : Color management setup tab.
+ *
+ * Copyright (C) 2005-2007 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#include <config.h>
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqvbuttongroup.h>
+#include <tqvgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqcheckbox.h>
+#include <tqradiobutton.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqiconset.h>
+#include <tqpixmap.h>
+#include <tqpushbutton.h>
+#include <tqstringlist.h>
+#include <tqmap.h>
+#include <tqdir.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <kdialogbase.h>
+#include <kurlrequester.h>
+#include <klineedit.h>
+#include <tdeconfig.h>
+#include <kcombobox.h>
+#include <tdeapplication.h>
+#include <tdemessagebox.h>
+#include <kurllabel.h>
+#include <kiconloader.h>
+#include <tdeglobalsettings.h>
+#include <kstandarddirs.h>
+
+// lcms includes.
+
+#include LCMS_HEADER
+#if LCMS_VERSION < 114
+#define cmsTakeCopyright(profile) "Unknown"
+#endif // LCMS_VERSION < 114
+
+// Local includes.
+
+#include "ddebug.h"
+#include "squeezedcombobox.h"
+#include "iccprofileinfodlg.h"
+#include "albumsettings.h"
+#include "setupicc.h"
+#include "setupicc.moc"
+
+namespace Digikam
+{
+
+class SetupICCPriv
+{
+public:
+
+ SetupICCPriv()
+ {
+ enableColorManagement = 0;
+ bpcAlgorithm = 0;
+ managedView = 0;
+ defaultApplyICC = 0;
+ defaultAskICC = 0;
+ defaultPathKU = 0;
+ inProfilesKC = 0;
+ workProfilesKC = 0;
+ proofProfilesKC = 0;
+ monitorProfilesKC = 0;
+ renderingIntentKC = 0;
+ infoWorkProfiles = 0;
+ infoMonitorProfiles = 0;
+ infoInProfiles = 0;
+ infoProofProfiles = 0;
+ behaviourGB = 0;
+ defaultPathGB = 0;
+ profilesGB = 0;
+ advancedSettingsGB = 0;
+ monitorIcon = 0;
+ monitorProfiles = 0;
+ }
+
+ TQLabel *monitorIcon;
+ TQLabel *monitorProfiles;
+
+ TQCheckBox *enableColorManagement;
+ TQCheckBox *bpcAlgorithm;
+ TQCheckBox *managedView;
+
+ TQRadioButton *defaultApplyICC;
+ TQRadioButton *defaultAskICC;
+
+ TQPushButton *infoWorkProfiles;
+ TQPushButton *infoMonitorProfiles;
+ TQPushButton *infoInProfiles;
+ TQPushButton *infoProofProfiles;
+
+ TQVGroupBox *behaviourGB;
+ TQHGroupBox *defaultPathGB;
+ TQGroupBox *profilesGB;
+ TQVGroupBox *advancedSettingsGB;
+
+ // Maps to store profile descriptions and profile file path
+ TQMap<TQString, TQString> inICCPath;
+ TQMap<TQString, TQString> workICCPath;
+ TQMap<TQString, TQString> proofICCPath;
+ TQMap<TQString, TQString> monitorICCPath;
+
+ KURLRequester *defaultPathKU;
+
+ KComboBox *renderingIntentKC;
+
+ KDialogBase *mainDialog;
+
+ SqueezedComboBox *inProfilesKC;
+ SqueezedComboBox *workProfilesKC;
+ SqueezedComboBox *proofProfilesKC;
+ SqueezedComboBox *monitorProfilesKC;
+};
+
+SetupICC::SetupICC(TQWidget* parent, KDialogBase* dialog )
+ : TQWidget(parent)
+{
+ d = new SetupICCPriv();
+ d->mainDialog = dialog;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint());
+
+ // --------------------------------------------------------
+
+ TQGroupBox *colorPolicy = new TQGroupBox(0, TQt::Horizontal, i18n("Color Management Policy"), parent);
+ TQGridLayout* grid = new TQGridLayout( colorPolicy->layout(), 1, 2, KDialog::spacingHint());
+
+ d->enableColorManagement = new TQCheckBox(colorPolicy);
+ d->enableColorManagement->setText(i18n("Enable Color Management"));
+ TQWhatsThis::add( d->enableColorManagement, i18n("<ul><li>Checked: Color Management is enabled</li>"
+ "<li>Unchecked: Color Management is disabled</li></ul>"));
+
+ KURLLabel *lcmsLogoLabel = new KURLLabel(colorPolicy);
+ lcmsLogoLabel->setText(TQString());
+ lcmsLogoLabel->setURL("http://www.littlecms.com");
+ TDEGlobal::dirs()->addResourceType("logo-lcms", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-lcms", "logo-lcms.png");
+ lcmsLogoLabel->setPixmap( TQPixmap( directory + "logo-lcms.png" ) );
+ TQToolTip::add(lcmsLogoLabel, i18n("Visit Little CMS project website"));
+
+ d->behaviourGB = new TQVGroupBox(i18n("Behavior"), colorPolicy);
+ TQButtonGroup *behaviourOptions = new TQButtonGroup(2, TQt::Vertical, d->behaviourGB);
+ behaviourOptions->setFrameStyle( TQFrame::NoFrame );
+ behaviourOptions->setInsideMargin(0);
+
+ d->defaultApplyICC = new TQRadioButton(behaviourOptions);
+ d->defaultApplyICC->setText(i18n("Apply when opening an image in the Image Editor"));
+ TQWhatsThis::add( d->defaultApplyICC, i18n("<p>If this option is enabled, digiKam applies the "
+ "Workspace default color profile to an image, without prompting you about missing "
+ "embedded profiles or embedded profiles different from the workspace profile.</p>"));
+
+ d->defaultAskICC = new TQRadioButton(behaviourOptions);
+ d->defaultAskICC->setText(i18n("Ask when opening an image in the Image Editor"));
+ TQWhatsThis::add( d->defaultAskICC, i18n("<p>If this option is enabled, digiKam asks to user "
+ "before it applies the Workspace default color profile to an image which has no "
+ "embedded profile or, if the image has an embedded profile, when it's not the same "
+ "as the workspace profile.</p>"));
+
+ grid->addMultiCellWidget(d->enableColorManagement, 0, 0, 0, 0);
+ grid->addMultiCellWidget(lcmsLogoLabel, 0, 0, 2, 2);
+ grid->addMultiCellWidget(d->behaviourGB, 1, 1, 0, 2);
+ grid->setColStretch(1, 10);
+
+ layout->addWidget(colorPolicy);
+
+ // --------------------------------------------------------
+
+ d->defaultPathGB = new TQHGroupBox(parent);
+ d->defaultPathGB->setTitle(i18n("Color Profiles Directory"));
+
+ d->defaultPathKU = new KURLRequester(d->defaultPathGB);
+ d->defaultPathKU->lineEdit()->setReadOnly(true);
+ d->defaultPathKU->setMode(KFile::Directory | KFile::LocalOnly | KFile::ExistingOnly);
+ TQWhatsThis::add( d->defaultPathKU, i18n("<p>Default path to the color profiles folder. "
+ "You must store all your color profiles in this directory.</p>"));
+
+ layout->addWidget(d->defaultPathGB);
+
+ // --------------------------------------------------------
+
+ d->profilesGB = new TQGroupBox(0, TQt::Horizontal, i18n("ICC Profiles Settings"), parent);
+ TQGridLayout* grid2 = new TQGridLayout( d->profilesGB->layout(), 4, 3, KDialog::spacingHint());
+ grid2->setColStretch(2, 10);
+
+ d->managedView = new TQCheckBox(d->profilesGB);
+ d->managedView->setText(i18n("Use color managed view (warning: slow)"));
+ TQWhatsThis::add( d->managedView, i18n("<p>Turn on this option if "
+ "you want to use your <b>Monitor Color Profile</b> to show your pictures in "
+ "the Image Editor window with a color correction adapted to your monitor. "
+ "Warning: this option can take a while to render "
+ "pictures on the screen, especially with a slow computer.</p>"));
+
+ d->monitorIcon = new TQLabel(d->profilesGB);
+ d->monitorIcon->setPixmap(SmallIcon("tv"));
+ d->monitorProfiles = new TQLabel(i18n("Monitor:"), d->profilesGB);
+ d->monitorProfilesKC = new SqueezedComboBox(d->profilesGB);
+ d->monitorProfiles->setBuddy(d->monitorProfilesKC);
+ TQWhatsThis::add( d->monitorProfilesKC, i18n("<p>Select the color profile for your monitor. "
+ "You need to enable the <b>Use color managed view</b> option to use this profile.</p>"));
+ d->infoMonitorProfiles = new TQPushButton(i18n("Info..."), d->profilesGB);
+ TQWhatsThis::add( d->infoMonitorProfiles, i18n("<p>You can use this button to get more detailed "
+ "information about the selected monitor profile.</p>"));
+
+ grid2->addMultiCellWidget(d->managedView, 0, 0, 0, 3);
+ grid2->addMultiCellWidget(d->monitorIcon, 1, 1, 0, 0);
+ grid2->addMultiCellWidget(d->monitorProfiles, 1, 1, 1, 1);
+ grid2->addMultiCellWidget(d->monitorProfilesKC, 1, 1, 2, 2);
+ grid2->addMultiCellWidget(d->infoMonitorProfiles, 1, 1, 3, 3);
+
+ TQLabel *workIcon = new TQLabel(d->profilesGB);
+ workIcon->setPixmap(SmallIcon("input-tablet"));
+ TQLabel *workProfiles = new TQLabel(i18n("Workspace:"), d->profilesGB);
+ d->workProfilesKC = new SqueezedComboBox(d->profilesGB);
+ workProfiles->setBuddy(d->workProfilesKC);
+ TQWhatsThis::add( d->workProfilesKC, i18n("<p>All the images will be converted to the color "
+ "space of this profile, so you must select a profile appropriate for editing.</p>"
+ "<p>These color profiles are device independent.</p>"));
+ d->infoWorkProfiles = new TQPushButton(i18n("Info..."), d->profilesGB);
+ TQWhatsThis::add( d->infoWorkProfiles, i18n("<p>You can use this button to get more detailed "
+ "information about the selected workspace profile.</p>"));
+
+ grid2->addMultiCellWidget(workIcon, 2, 2, 0, 0);
+ grid2->addMultiCellWidget(workProfiles, 2, 2, 1, 1);
+ grid2->addMultiCellWidget(d->workProfilesKC, 2, 2, 2, 2);
+ grid2->addMultiCellWidget(d->infoWorkProfiles, 2, 2, 3, 3);
+
+ TQLabel *inIcon = new TQLabel(d->profilesGB);
+ inIcon->setPixmap(SmallIcon("camera-photo"));
+ TQLabel *inProfiles = new TQLabel(i18n("Input:"), d->profilesGB);
+ d->inProfilesKC = new SqueezedComboBox(d->profilesGB);
+ inProfiles->setBuddy(d->inProfilesKC);
+ TQWhatsThis::add( d->inProfilesKC, i18n("<p>You must select the profile for your input device "
+ "(usually, your camera, scanner...)</p>"));
+ d->infoInProfiles = new TQPushButton(i18n("Info..."), d->profilesGB);
+ TQWhatsThis::add( d->infoInProfiles, i18n("<p>You can use this button to get more detailed "
+ "information about the selected input profile.</p>"));
+
+ grid2->addMultiCellWidget(inIcon, 3, 3, 0, 0);
+ grid2->addMultiCellWidget(inProfiles, 3, 3, 1, 1);
+ grid2->addMultiCellWidget(d->inProfilesKC, 3, 3, 2, 2);
+ grid2->addMultiCellWidget(d->infoInProfiles, 3, 3, 3, 3);
+
+ TQLabel *proofIcon = new TQLabel(d->profilesGB);
+ proofIcon->setPixmap(SmallIcon("printer"));
+ TQLabel *proofProfiles = new TQLabel(i18n("Soft proof:"), d->profilesGB);
+ d->proofProfilesKC = new SqueezedComboBox(d->profilesGB);
+ proofProfiles->setBuddy(d->proofProfilesKC);
+ TQWhatsThis::add( d->proofProfilesKC, i18n("<p>You must select the profile for your output device "
+ "(usually, your printer). This profile will be used to do a soft proof, so you will "
+ "be able to preview how an image will be rendered via an output device.</p>"));
+ d->infoProofProfiles = new TQPushButton(i18n("Info..."), d->profilesGB);
+ TQWhatsThis::add( d->infoProofProfiles, i18n("<p>You can use this button to get more detailed "
+ "information about the selected soft proof profile.</p>"));
+
+ grid2->addMultiCellWidget(proofIcon, 4, 4, 0, 0);
+ grid2->addMultiCellWidget(proofProfiles, 4, 4, 1, 1);
+ grid2->addMultiCellWidget(d->proofProfilesKC, 4, 4, 2, 2);
+ grid2->addMultiCellWidget(d->infoProofProfiles, 4, 4, 3, 3);
+
+ layout->addWidget(d->profilesGB);
+
+ // --------------------------------------------------------
+
+ d->advancedSettingsGB = new TQVGroupBox(i18n("Advanced Settings"), parent);
+
+ d->bpcAlgorithm = new TQCheckBox(d->advancedSettingsGB);
+ d->bpcAlgorithm->setText(i18n("Use black point compensation"));
+ TQWhatsThis::add( d->bpcAlgorithm, i18n("<p><b>Black Point Compensation</b> is a way to make "
+ "adjustments between the maximum "
+ "black levels of digital files and the black capabilities of various "
+ "digital devices.</p>"));
+
+ TQHBox *hbox2 = new TQHBox(d->advancedSettingsGB);
+ TQLabel *lablel = new TQLabel(hbox2);
+ lablel->setText(i18n("Rendering Intents:"));
+
+ d->renderingIntentKC = new KComboBox(false, hbox2);
+ d->renderingIntentKC->insertItem("Perceptual");
+ d->renderingIntentKC->insertItem("Relative Colorimetric");
+ d->renderingIntentKC->insertItem("Saturation");
+ d->renderingIntentKC->insertItem("Absolute Colorimetric");
+ TQWhatsThis::add( d->renderingIntentKC, i18n("<ul><li><p><b>Perceptual intent</b> causes the full gamut of the image to be "
+ "compressed or expanded to fill the gamut of the destination device, so that gray balance is "
+ "preserved but colorimetric accuracy may not be preserved.</p>"
+ "<p>In other words, if certain colors in an image fall outside of the range of colors that the output "
+ "device can render, the image intent will cause all the colors in the image to be adjusted so that "
+ "the every color in the image falls within the range that can be rendered and so that the relationship "
+ "between colors is preserved as much as possible.</p>"
+ "<p>This intent is most suitable for display of photographs and images, and is the default intent.</p></li>"
+ "<li><p><b>Absolute Colorimetric intent</b> causes any colors that fall outside the range that the output device "
+ "can render are adjusted to the closest color that can be rendered, while all other colors are "
+ "left unchanged.</p>"
+ "<p>This intent preserves the white point and is most suitable for spot colors (Pantone, TruMatch, "
+ "logo colors, ...).</p></li>"
+ "<li><p><b>Relative Colorimetric intent</b> is defined such that any colors that fall outside the range that the "
+ "output device can render are adjusted to the closest color that can be rendered, while all other colors "
+ "are left unchanged. Proof intent does not preserve the white point.</p></li>"
+ "<li><p><b>Saturation intent</b> preserves the saturation of colors in the image at the possible expense of "
+ "hue and lightness.</p>"
+ "<p>Implementation of this intent remains somewhat problematic, and the ICC is still working on methods to "
+ "achieve the desired effects.</p>"
+ "<p>This intent is most suitable for business graphics such as charts, where it is more important that the "
+ "colors be vivid and contrast well with each other rather than a specific color.</p></li></ul>"));
+
+ layout->addWidget(d->advancedSettingsGB);
+ layout->addStretch();
+
+ // --------------------------------------------------------
+
+ connect(d->managedView, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotToggleManagedView(bool)));
+
+ connect(lcmsLogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processLCMSURL(const TQString&)));
+
+ connect(d->enableColorManagement, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotToggledWidgets(bool)));
+
+ connect(d->infoProofProfiles, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotClickedProof()) );
+
+ connect(d->infoInProfiles, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotClickedIn()) );
+
+ connect(d->infoMonitorProfiles, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotClickedMonitor()) );
+
+ connect(d->infoWorkProfiles, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotClickedWork()));
+
+ connect(d->defaultPathKU, TQ_SIGNAL(urlSelected(const TQString&)),
+ this, TQ_SLOT(slotFillCombos(const TQString&)));
+
+ // --------------------------------------------------------
+
+ adjustSize();
+ readSettings();
+ slotToggledWidgets(d->enableColorManagement->isChecked());
+ slotToggleManagedView(d->managedView->isChecked());
+}
+
+SetupICC::~SetupICC()
+{
+ delete d;
+}
+
+void SetupICC::processLCMSURL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void SetupICC::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Color Management");
+
+ config->writeEntry("EnableCM", d->enableColorManagement->isChecked());
+
+ if (!d->enableColorManagement->isChecked())
+ return; // No need to write settings in this case.
+
+ if (d->defaultApplyICC->isChecked())
+ config->writeEntry("BehaviourICC", true);
+ else
+ config->writeEntry("BehaviourICC", false);
+
+ config->writePathEntry("DefaultPath", d->defaultPathKU->url());
+ config->writeEntry("WorkSpaceProfile", d->workProfilesKC->currentItem());
+ config->writeEntry("MonitorProfile", d->monitorProfilesKC->currentItem());
+ config->writeEntry("InProfile", d->inProfilesKC->currentItem());
+ config->writeEntry("ProofProfile", d->proofProfilesKC->currentItem());
+ config->writeEntry("BPCAlgorithm", d->bpcAlgorithm->isChecked());
+ config->writeEntry("RenderingIntent", d->renderingIntentKC->currentItem());
+ config->writeEntry("ManagedView", d->managedView->isChecked());
+
+ config->writePathEntry("InProfileFile",
+ *(d->inICCPath.find(d->inProfilesKC->itemHighlighted())));
+ config->writePathEntry("WorkProfileFile",
+ *(d->workICCPath.find(d->workProfilesKC->itemHighlighted())));
+ config->writePathEntry("MonitorProfileFile",
+ *(d->monitorICCPath.find(d->monitorProfilesKC->itemHighlighted())));
+ config->writePathEntry("ProofProfileFile",
+ *(d->proofICCPath.find(d->proofProfilesKC->itemHighlighted())));
+}
+
+void SetupICC::readSettings(bool restore)
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Color Management");
+
+ if (!restore)
+ d->enableColorManagement->setChecked(config->readBoolEntry("EnableCM", false));
+
+ d->defaultPathKU->setURL(config->readPathEntry("DefaultPath", TQString()));
+ d->bpcAlgorithm->setChecked(config->readBoolEntry("BPCAlgorithm", false));
+ d->renderingIntentKC->setCurrentItem(config->readNumEntry("RenderingIntent", 0));
+ d->managedView->setChecked(config->readBoolEntry("ManagedView", false));
+
+ if (config->readBoolEntry("BehaviourICC"))
+ d->defaultApplyICC->setChecked(true);
+ else
+ d->defaultAskICC->setChecked(true);
+
+ fillCombos(d->defaultPathKU->url(), false);
+
+ d->workProfilesKC->setCurrentItem(config->readNumEntry("WorkSpaceProfile", 0));
+ d->monitorProfilesKC->setCurrentItem(config->readNumEntry("MonitorProfile", 0));
+ d->inProfilesKC->setCurrentItem(config->readNumEntry("InProfile", 0));
+ d->proofProfilesKC->setCurrentItem(config->readNumEntry("ProofProfile", 0));
+}
+
+void SetupICC::slotFillCombos(const TQString& path)
+{
+ fillCombos(path, true);
+}
+
+void SetupICC::fillCombos(const TQString& path, bool report)
+{
+ if (!d->enableColorManagement->isChecked())
+ return;
+
+ d->inProfilesKC->clear();
+ d->monitorProfilesKC->clear();
+ d->workProfilesKC->clear();
+ d->proofProfilesKC->clear();
+ d->inICCPath.clear();
+ d->workICCPath.clear();
+ d->proofICCPath.clear();
+ d->monitorICCPath.clear();
+ TQDir dir(path);
+
+ if (path.isEmpty() || !dir.exists() || !dir.isReadable())
+ {
+ if (report)
+ KMessageBox::sorry(this, i18n("<p>You must set a correct default "
+ "path for your ICC color profiles files.</p>"));
+
+ d->mainDialog->enableButtonOK(false);
+ return;
+ }
+ d->mainDialog->enableButtonOK(true);
+
+ // Look the ICC profile path repository set by user.
+ TQDir userProfilesDir(path, "*.icc;*.icm", TQDir::Files);
+ const TQFileInfoList* usersFiles = userProfilesDir.entryInfoList();
+ DDebug() << "Scanning ICC profiles from user repository: " << path << endl;
+
+ if ( !parseProfilesfromDir(usersFiles) )
+ {
+ if (report)
+ {
+ TQString message = i18n("<p>Sorry, there are no ICC profiles files in ");
+ message.append(path);
+ message.append(i18n("</p>"));
+ KMessageBox::sorry(this, message);
+ }
+
+ DDebug() << "No ICC profile files found!!!" << endl;
+ d->mainDialog->enableButtonOK(false);
+ return;
+ }
+
+ // Look the ICC color-space profile path include with digiKam dist.
+ TDEGlobal::dirs()->addResourceType("profiles", TDEGlobal::dirs()->kde_default("data") + "digikam/profiles");
+ TQString digiKamProfilesPath = TDEGlobal::dirs()->findResourceDir("profiles", "srgb.icm");
+ TQDir digiKamProfilesDir(digiKamProfilesPath, "*.icc;*.icm", TQDir::Files);
+ const TQFileInfoList* digiKamFiles = digiKamProfilesDir.entryInfoList();
+ DDebug() << "Scanning ICC profiles included with digiKam: " << digiKamProfilesPath << endl;
+ parseProfilesfromDir(digiKamFiles);
+
+ d->monitorProfilesKC->insertSqueezedList(d->monitorICCPath.keys(), 0);
+ if (d->monitorICCPath.keys().isEmpty())
+ {
+ d->managedView->setEnabled(false);
+ d->managedView->setChecked(false);
+ }
+ else
+ {
+ d->managedView->setEnabled(true);
+ }
+
+ d->inProfilesKC->insertSqueezedList(d->inICCPath.keys(), 0);
+ d->proofProfilesKC->insertSqueezedList(d->proofICCPath.keys(), 0);
+
+ d->workProfilesKC->insertSqueezedList(d->workICCPath.keys(), 0);
+ if (d->workICCPath.keys().isEmpty())
+ {
+ // If there is no workspace icc profiles available,
+ // the CM is broken and cannot be used.
+ d->mainDialog->enableButtonOK(false);
+ return;
+ }
+
+ d->mainDialog->enableButtonOK(true);
+}
+
+bool SetupICC::parseProfilesfromDir(const TQFileInfoList* files)
+{
+ cmsHPROFILE tmpProfile=0;
+ bool findIccFiles=false;
+
+ if (files)
+ {
+ TQFileInfoListIterator it(*files);
+ TQFileInfo *fileInfo=0;
+
+ while ((fileInfo = it.current()) != 0)
+ {
+ if (fileInfo->isFile() && fileInfo->isReadable())
+ {
+ TQString fileName = fileInfo->filePath();
+ tmpProfile = cmsOpenProfileFromFile(TQFile::encodeName(fileName), "r");
+
+ if (tmpProfile == NULL)
+ {
+ DDebug() << "Error: Parsed profile is NULL (invalid profile); " << fileName << endl;
+ cmsCloseProfile(tmpProfile);
+ ++it;
+ TQString message = i18n("<p>The following profile is invalid:</p><p><b>");
+ message.append(fileName);
+ message.append("</b></p><p>To avoid this message remove it from color profiles repository</p>");
+ message.append("<p>Do you want digiKam do it for you?</p>");
+ if (KMessageBox::warningYesNo(this, message, i18n("Invalid Profile")) == 3)
+ {
+ if (TQFile::remove(fileName))
+ {
+ KMessageBox::information(this, i18n("Invalid color profile has been removed"));
+ }
+ else
+ {
+ KMessageBox::information(this, i18n("<p>digiKam has failed to remove the invalid color profile</p><p>You have to do it manually</p>"));
+ }
+ }
+
+ continue;
+ }
+
+ TQString profileDescription = TQString((cmsTakeProductDesc(tmpProfile)));
+
+ switch ((int)cmsGetDeviceClass(tmpProfile))
+ {
+ case icSigInputClass:
+ {
+ if (TQString(cmsTakeProductDesc(tmpProfile)).isEmpty())
+ d->inICCPath.insert(fileName, fileName);
+ else
+ d->inICCPath.insert(TQString(cmsTakeProductDesc(tmpProfile)), fileName);
+
+ DDebug() << "ICC file: " << fileName << " ==> Input device class ("
+ << cmsGetDeviceClass(tmpProfile) << ")" << endl;
+ findIccFiles = true;
+ break;
+ }
+ case icSigDisplayClass:
+ {
+ if (TQString(cmsTakeProductDesc(tmpProfile)).isEmpty())
+ {
+ d->monitorICCPath.insert(fileName, fileName);
+ d->workICCPath.insert(fileName, fileName);
+ }
+ else
+ {
+ d->monitorICCPath.insert(TQString(cmsTakeProductDesc(tmpProfile)), fileName);
+ d->workICCPath.insert(TQString(cmsTakeProductDesc(tmpProfile)), fileName);
+ }
+
+ DDebug() << "ICC file: " << fileName << " ==> Monitor device class ("
+ << cmsGetDeviceClass(tmpProfile) << ")" << endl;
+ findIccFiles = true;
+ break;
+ }
+ case icSigOutputClass:
+ {
+ if (TQString(cmsTakeProductDesc(tmpProfile)).isEmpty())
+ d->proofICCPath.insert(fileName, fileName);
+ else
+ d->proofICCPath.insert(TQString(cmsTakeProductDesc(tmpProfile)), fileName);
+
+ DDebug() << "ICC file: " << fileName << " ==> Output device class ("
+ << cmsGetDeviceClass(tmpProfile) << ")" << endl;
+ findIccFiles = true;
+ break;
+ }
+ case icSigColorSpaceClass:
+ {
+ if (TQString(cmsTakeProductDesc(tmpProfile)).isEmpty())
+ {
+ d->inICCPath.insert(fileName, fileName);
+ d->workICCPath.insert(fileName, fileName);
+ }
+ else
+ {
+ d->inICCPath.insert(TQString(cmsTakeProductDesc(tmpProfile)), fileName);
+ d->workICCPath.insert(TQString(cmsTakeProductDesc(tmpProfile)), fileName);
+ }
+
+ DDebug() << "ICC file: " << fileName << " ==> WorkingSpace device class ("
+ << cmsGetDeviceClass(tmpProfile) << ")" << endl;
+ findIccFiles = true;
+ break;
+ }
+ default:
+ {
+ DDebug() << "ICC file: " << fileName << " ==> UNKNOW device class ("
+ << cmsGetDeviceClass(tmpProfile) << ")" << endl;
+ break;
+ }
+ }
+
+ cmsCloseProfile(tmpProfile);
+ }
+ ++it;
+ }
+ }
+
+ return findIccFiles;
+}
+
+void SetupICC::slotToggledWidgets(bool t)
+{
+ d->behaviourGB->setEnabled(t);
+ d->defaultPathGB->setEnabled(t);
+ d->profilesGB->setEnabled(t);
+ d->advancedSettingsGB->setEnabled(t);
+
+ if (t)
+ {
+ readSettings(true);
+ slotToggleManagedView(d->managedView->isChecked());
+ }
+ else
+ d->mainDialog->enableButtonOK(true);
+}
+
+void SetupICC::slotClickedWork()
+{
+ profileInfo(*(d->workICCPath.find(d->workProfilesKC->itemHighlighted())));
+}
+
+void SetupICC::slotClickedIn()
+{
+ profileInfo(*(d->inICCPath.find(d->inProfilesKC->itemHighlighted())));
+}
+
+void SetupICC::slotClickedMonitor()
+{
+ profileInfo(*(d->monitorICCPath.find(d->monitorProfilesKC->itemHighlighted())));
+}
+
+void SetupICC::slotClickedProof()
+{
+ profileInfo(*(d->proofICCPath.find(d->proofProfilesKC->itemHighlighted())));
+}
+
+void SetupICC::profileInfo(const TQString& profile)
+{
+ if (profile.isEmpty())
+ {
+ KMessageBox::error(this, i18n("Sorry, there is not any selected profile"), i18n("Profile Error"));
+ return;
+ }
+
+ ICCProfileInfoDlg infoDlg(this, profile);
+ infoDlg.exec();
+}
+
+void SetupICC::slotToggleManagedView(bool b)
+{
+ d->monitorIcon->setEnabled(b);
+ d->monitorProfiles->setEnabled(b);
+ d->monitorProfilesKC->setEnabled(b);
+ d->infoMonitorProfiles->setEnabled(b);
+}
+
+bool SetupICC::iccRepositoryIsValid()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("Color Management");
+
+ // If color management is disable, no need to check anymore.
+ if (!config->readBoolEntry("EnableCM", false))
+ return true;
+
+ // To be valid, the ICC profiles repository must exist and be readable.
+
+ TQDir tmpPath(config->readPathEntry("DefaultPath", TQString()));
+ DDebug() << "ICC profiles repository is: " << tmpPath.dirName() << endl;
+
+ if ( tmpPath.exists() && tmpPath.isReadable() )
+ return true;
+
+ return false;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupicc.h b/src/utilities/setup/setupicc.h
new file mode 100644
index 00000000..8eaaece1
--- /dev/null
+++ b/src/utilities/setup/setupicc.h
@@ -0,0 +1,86 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-11-24
+ * Description : Color management setup tab.
+ *
+ * Copyright (C) 2005-2007 by F.J. Cruz <fj.cruz@supercable.es>
+ * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPICC_H
+#define SETUPICC_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+#include <tqmap.h>
+#include <tqdir.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class KDialogBase;
+
+namespace Digikam
+{
+
+class SetupICCPriv;
+
+class DIGIKAM_EXPORT SetupICC : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupICC(TQWidget* parent = 0, KDialogBase* dialog = 0);
+ ~SetupICC();
+
+ void applySettings();
+
+ static bool iccRepositoryIsValid();
+
+private:
+
+ void readSettings(bool restore=false);
+ void fillCombos(const TQString& path, bool report);
+ void enableWidgets();
+ void disableWidgets();
+ void profileInfo(const TQString&);
+ bool parseProfilesfromDir(const TQFileInfoList* files);
+
+private slots:
+
+ void processLCMSURL(const TQString&);
+ void slotToggledWidgets(bool);
+ void slotToggleManagedView(bool);
+ void slotFillCombos(const TQString&);
+ void slotClickedIn();
+ void slotClickedWork();
+ void slotClickedMonitor();
+ void slotClickedProof();
+
+private:
+
+ SetupICCPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPICC_H
diff --git a/src/utilities/setup/setupidentity.cpp b/src/utilities/setup/setupidentity.cpp
new file mode 100644
index 00000000..ecbfdfbf
--- /dev/null
+++ b/src/utilities/setup/setupidentity.cpp
@@ -0,0 +1,217 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-04
+ * Description : default IPTC identity setup tab.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqhgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqvalidator.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <klineedit.h>
+#include <kactivelabel.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "setupidentity.h"
+#include "setupidentity.moc"
+
+namespace Digikam
+{
+
+class SetupIdentityPriv
+{
+public:
+
+ SetupIdentityPriv()
+ {
+ authorEdit = 0;
+ authorTitleEdit = 0;
+ creditEdit = 0;
+ sourceEdit = 0;
+ copyrightEdit = 0;
+ }
+
+ KLineEdit *authorEdit;
+ KLineEdit *authorTitleEdit;
+ KLineEdit *creditEdit;
+ KLineEdit *sourceEdit;
+ KLineEdit *copyrightEdit;
+};
+
+SetupIdentity::SetupIdentity(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupIdentityPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint() );
+
+ // --------------------------------------------------------
+
+ // IPTC only accept printable Ascii char.
+ TQRegExp asciiRx("[\x20-\x7F]+$");
+ TQValidator *asciiValidator = new TQRegExpValidator(asciiRx, this);
+
+ TQGroupBox *photographerIdGroup = new TQGroupBox(0, TQt::Horizontal, i18n("Photographer and Copyright Information"), parent);
+ TQGridLayout* grid = new TQGridLayout( photographerIdGroup->layout(), 1, 1, KDialog::spacingHint());
+
+ TQLabel *label1 = new TQLabel(i18n("Author:"), photographerIdGroup);
+ d->authorEdit = new KLineEdit(photographerIdGroup);
+ d->authorEdit->setValidator(asciiValidator);
+ d->authorEdit->setMaxLength(32);
+ label1->setBuddy(d->authorEdit);
+ grid->addMultiCellWidget(label1, 0, 0, 0, 0);
+ grid->addMultiCellWidget(d->authorEdit, 0, 0, 1, 1);
+ TQWhatsThis::add( d->authorEdit, i18n("<p>This field should contain your name, or the name of the person who created the photograph. "
+ "If it is not appropriate to add the name of the photographer (for example, if the identify of "
+ "the photographer needs to be protected) the name of a company or organization can also be used. "
+ "Once saved, this field should not be changed by anyone. This field does not support the use of "
+ "commas or semi-colons as separator. \nThis field is limited to 32 ASCII characters.</p>"));
+
+ TQLabel *label2 = new TQLabel(i18n("Author Title:"), photographerIdGroup);
+ d->authorTitleEdit = new KLineEdit(photographerIdGroup);
+ d->authorTitleEdit->setValidator(asciiValidator);
+ d->authorTitleEdit->setMaxLength(32);
+ label2->setBuddy(d->authorTitleEdit);
+ grid->addMultiCellWidget(label2, 1, 1, 0, 0);
+ grid->addMultiCellWidget(d->authorTitleEdit, 1, 1, 1, 1);
+ TQWhatsThis::add( d->authorTitleEdit, i18n("<p>This field should contain the job title of the photographer. Examples might include "
+ "titles such as: Staff Photographer, Freelance Photographer, or Independent Commercial "
+ "Photographer. Since this is a qualifier for the Author field, the Author field must also "
+ "be filled out. \nThis field is limited to 32 ASCII characters.</p>"));
+
+ // --------------------------------------------------------
+
+ TQGroupBox *creditsGroup = new TQGroupBox(0, TQt::Horizontal, i18n("Credit and Copyright"), parent);
+ TQGridLayout* grid2 = new TQGridLayout( creditsGroup->layout(), 2, 1, KDialog::spacingHint());
+
+ TQLabel *label3 = new TQLabel(i18n("Credit:"), creditsGroup);
+ d->creditEdit = new KLineEdit(creditsGroup);
+ d->creditEdit->setValidator(asciiValidator);
+ d->creditEdit->setMaxLength(32);
+ label3->setBuddy(d->creditEdit);
+ grid2->addMultiCellWidget(label3, 0, 0, 0, 0);
+ grid2->addMultiCellWidget(d->creditEdit, 0, 0, 1, 1);
+ TQWhatsThis::add( d->creditEdit, i18n("<p>(synonymous to Provider): Use the Provider field to identify who is providing the photograph. "
+ "This does not necessarily have to be the author. If a photographer is working for a news agency "
+ "such as Reuters or the Associated Press, these organizations could be listed here as they are "
+ "\"providing\" the image for use by others. If the image is a stock photograph, then the group "
+ "(agency) involved in supplying the image should be listed here. "
+ "\nThis field is limited to 32 ASCII characters.</p>"));
+
+ TQLabel *label4 = new TQLabel(i18n("Source:"), creditsGroup);
+ d->sourceEdit = new KLineEdit(creditsGroup);
+ d->sourceEdit->setValidator(asciiValidator);
+ d->sourceEdit->setMaxLength(32);
+ label4->setBuddy(d->sourceEdit);
+ grid2->addMultiCellWidget(label4, 1, 1, 0, 0);
+ grid2->addMultiCellWidget(d->sourceEdit, 1, 1, 1, 1);
+ TQWhatsThis::add( d->sourceEdit, i18n("<p>The Source field should be used to identify the original owner or copyright holder of the "
+ "photograph. The value of this field should never be changed after the information is entered "
+ "following the image's creation. While not yet enforced by the custom panels, you should consider "
+ "this to be a \"write-once\" field. The source could be an individual, an agency, or a "
+ "member of an agency. To aid in later searches, it is suggested to separate any slashes "
+ "\"/\" with a blank space. Use the form \"photographer / agency\" rather than "
+ "\"photographer/agency.\" Source may also be different from Creator and from the names "
+ "listed in the Copyright Notice.\nThis field is limited to 32 ASCII characters.</p>"));
+
+ TQLabel *label5 = new TQLabel(i18n("Copyright:"), creditsGroup);
+ d->copyrightEdit = new KLineEdit(creditsGroup);
+ d->copyrightEdit->setValidator(asciiValidator);
+ d->copyrightEdit->setMaxLength(128);
+ label5->setBuddy(d->copyrightEdit);
+ grid2->addMultiCellWidget(label5, 2, 2, 0, 0);
+ grid2->addMultiCellWidget(d->copyrightEdit, 2, 2, 1, 1);
+ TQWhatsThis::add( d->copyrightEdit, i18n("<p>The Copyright Notice should contain any necessary copyright notice for claiming the intellectual "
+ "property, and should identify the current owner(s) of the copyright for the photograph. Usually, "
+ "this would be the photographer, but if the image was done by an employee or as work-for-hire, "
+ "then the agency or company should be listed. Use the form appropriate to your country. USA: "
+ "&copy; {date of first publication} name of copyright owner, as in \"&copy;2005 John Doe.\" "
+ "Note, the word \"copyright\" or the abbreviation \"copr\" may be used in place of the &copy; symbol. "
+ "In some foreign countries only the copyright symbol is recognized and the abbreviation does not work. "
+ "Furthermore the copyright symbol must be a full circle with a \"c\" inside; using something like (c) "
+ "where the parentheses form a partial circle is not sufficient. For additional protection worldwide, "
+ "use of the phrase, \"all rights reserved\" following the notice above is encouraged. \nIn Europe "
+ "you would use: Copyright {Year} {Copyright owner}, all rights reserved. \nIn Japan, for maximum "
+ "protection, the following three items should appear in the copyright field of the IPTC Core: "
+ "(a) the word, Copyright; (b) year of the first publication; and (c) name of the author. "
+ "You may also wish to include the phrase \"all rights reserved.\"\n"
+ "This field is limited to 128 ASCII characters.</p>"));
+
+ // --------------------------------------------------------
+
+ KActiveLabel *note = new KActiveLabel(i18n("<b>Note: These informations are used to set "
+ "<b><a href='http://en.wikipedia.org/wiki/IPTC'>IPTC</a></b> tags contents. "
+ "IPTC text tags only support the printable "
+ "<b><a href='http://en.wikipedia.org/wiki/Ascii'>ASCII</a></b> "
+ "characters set and limit strings size. "
+ "Use contextual help for details.</b>"), parent);
+
+ // --------------------------------------------------------
+
+ layout->addWidget(photographerIdGroup);
+ layout->addWidget(creditsGroup);
+ layout->addWidget(note);
+ layout->addStretch();
+
+ readSettings();
+}
+
+SetupIdentity::~SetupIdentity()
+{
+ delete d;
+}
+
+void SetupIdentity::applySettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings) return;
+
+ settings->setIptcAuthor(d->authorEdit->text());
+ settings->setIptcAuthorTitle(d->authorTitleEdit->text());
+ settings->setIptcCredit(d->creditEdit->text());
+ settings->setIptcSource(d->sourceEdit->text());
+ settings->setIptcCopyright(d->copyrightEdit->text());
+ settings->saveSettings();
+}
+
+void SetupIdentity::readSettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings) return;
+
+ d->authorEdit->setText(settings->getIptcAuthor());
+ d->authorTitleEdit->setText(settings->getIptcAuthorTitle());
+ d->creditEdit->setText(settings->getIptcCredit());
+ d->sourceEdit->setText(settings->getIptcSource());
+ d->copyrightEdit->setText(settings->getIptcCopyright());
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setupidentity.h b/src/utilities/setup/setupidentity.h
new file mode 100644
index 00000000..ae26dc77
--- /dev/null
+++ b/src/utilities/setup/setupidentity.h
@@ -0,0 +1,60 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-04
+ * Description : default IPTC identity setup tab.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUP_IDENTITY_H
+#define SETUP_IDENTITY_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupIdentityPriv;
+
+class SetupIdentity : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupIdentity(TQWidget* parent = 0);
+ ~SetupIdentity();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupIdentityPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif // SETUP_IDENTITY_H
diff --git a/src/utilities/setup/setupiofiles.cpp b/src/utilities/setup/setupiofiles.cpp
new file mode 100644
index 00000000..6cdf23de
--- /dev/null
+++ b/src/utilities/setup/setupiofiles.cpp
@@ -0,0 +1,137 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-23
+ * Description : setup image editor output files settings.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+#include <kseparator.h>
+
+// Local includes.
+
+#include "jpegsettings.h"
+#include "pngsettings.h"
+#include "tiffsettings.h"
+#include "jp2ksettings.h"
+#include "setupiofiles.h"
+#include "setupiofiles.moc"
+
+namespace Digikam
+{
+
+class SetupIOFilesPriv
+{
+public:
+
+
+ SetupIOFilesPriv()
+ {
+ JPEGOptions = 0;
+ PNGOptions = 0;
+ TIFFOptions = 0;
+ JPEG2000Options = 0;
+ }
+
+ JPEGSettings *JPEGOptions;
+
+ PNGSettings *PNGOptions;
+
+ TIFFSettings *TIFFOptions;
+
+ JP2KSettings *JPEG2000Options;
+};
+
+SetupIOFiles::SetupIOFiles(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupIOFilesPriv;
+
+ TQVBoxLayout* vbox = new TQVBoxLayout(parent);
+
+ //-- JPEG Settings ------------------------------------------------------
+
+ d->JPEGOptions = new JPEGSettings(parent);
+ KSeparator *line1 = new KSeparator(TQt::Horizontal, parent);
+ vbox->addWidget(d->JPEGOptions);
+ vbox->addWidget(line1);
+
+ //-- PNG Settings -------------------------------------------------------
+
+ d->PNGOptions = new PNGSettings(parent);
+ KSeparator *line2 = new KSeparator(TQt::Horizontal, parent);
+ vbox->addWidget(d->PNGOptions);
+ vbox->addWidget(line2);
+
+ //-- TIFF Settings ------------------------------------------------------
+
+ d->TIFFOptions = new TIFFSettings(parent);
+ KSeparator *line3 = new KSeparator(TQt::Horizontal, parent);
+ vbox->addWidget(d->TIFFOptions);
+ vbox->addWidget(line3);
+
+ //-- JPEG 2000 Settings -------------------------------------------------
+
+ d->JPEG2000Options = new JP2KSettings(parent);
+ vbox->addWidget(d->JPEG2000Options);
+
+ vbox->addStretch(10);
+ readSettings();
+}
+
+SetupIOFiles::~SetupIOFiles()
+{
+ delete d;
+}
+
+void SetupIOFiles::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ config->writeEntry("JPEGCompression", d->JPEGOptions->getCompressionValue());
+ config->writeEntry("JPEGSubSampling", d->JPEGOptions->getSubSamplingValue());
+ config->writeEntry("PNGCompression", d->PNGOptions->getCompressionValue());
+ config->writeEntry("TIFFCompression", d->TIFFOptions->getCompression());
+ config->writeEntry("JPEG2000Compression", d->JPEG2000Options->getCompressionValue());
+ config->writeEntry("JPEG2000LossLess", d->JPEG2000Options->getLossLessCompression());
+ config->sync();
+}
+
+void SetupIOFiles::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("ImageViewer Settings");
+ d->JPEGOptions->setCompressionValue( config->readNumEntry("JPEGCompression", 75) );
+ d->JPEGOptions->setSubSamplingValue( config->readNumEntry("JPEGSubSampling", 1) ); // Medium subsampling
+ d->PNGOptions->setCompressionValue( config->readNumEntry("PNGCompression", 9) );
+ d->TIFFOptions->setCompression(config->readBoolEntry("TIFFCompression", false));
+ d->JPEG2000Options->setCompressionValue( config->readNumEntry("JPEG2000Compression", 75) );
+ d->JPEG2000Options->setLossLessCompression( config->readBoolEntry("JPEG2000LossLess", true) );
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupiofiles.h b/src/utilities/setup/setupiofiles.h
new file mode 100644
index 00000000..060dbba1
--- /dev/null
+++ b/src/utilities/setup/setupiofiles.h
@@ -0,0 +1,63 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-01-23
+ * Description : setup image editor output files settings.
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPIOFILES_H
+#define SETUPIOFILES_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class SetupIOFilesPriv;
+
+class DIGIKAM_EXPORT SetupIOFiles : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupIOFiles(TQWidget* parent = 0);
+ ~SetupIOFiles();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupIOFilesPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPIOFILES_H
diff --git a/src/utilities/setup/setuplighttable.cpp b/src/utilities/setup/setuplighttable.cpp
new file mode 100644
index 00000000..0ed01eab
--- /dev/null
+++ b/src/utilities/setup/setuplighttable.cpp
@@ -0,0 +1,133 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-05-11
+ * Description : setup Light Table tab.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqcolor.h>
+#include <tqhbox.h>
+#include <tqvgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "setuplighttable.h"
+#include "setuplighttable.moc"
+
+namespace Digikam
+{
+class SetupLightTablePriv
+{
+public:
+
+ SetupLightTablePriv()
+ {
+ hideToolBar = 0;
+ autoSyncPreview = 0;
+ autoLoadOnRightPanel = 0;
+ loadFullImageSize = 0;
+ }
+
+ TQCheckBox *hideToolBar;
+ TQCheckBox *autoSyncPreview;
+ TQCheckBox *autoLoadOnRightPanel;
+ TQCheckBox *loadFullImageSize;
+};
+
+SetupLightTable::SetupLightTable(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupLightTablePriv;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint() );
+
+ // --------------------------------------------------------
+
+ TQVGroupBox *interfaceOptionsGroup = new TQVGroupBox(i18n("Interface Options"), parent);
+
+
+ d->autoSyncPreview = new TQCheckBox(i18n("Synchronize panels automatically"), interfaceOptionsGroup);
+ TQWhatsThis::add( d->autoSyncPreview, i18n("<p>Set this option to automatically synchronize "
+ "zooming and panning between left and right panels if the images have "
+ "the same size."));
+
+ d->autoLoadOnRightPanel = new TQCheckBox(i18n("Selecting a thumbbar item loads image to the right panel"),
+ interfaceOptionsGroup);
+ TQWhatsThis::add( d->autoLoadOnRightPanel, i18n("<p>Set this option to automatically load an image "
+ "into the right panel when the corresponding item is selected on the thumbbar."));
+
+ d->loadFullImageSize = new TQCheckBox(i18n("Load full image size"), interfaceOptionsGroup);
+ TQWhatsThis::add( d->loadFullImageSize, i18n("<p>Set this option to load full image size "
+ "into the preview panel instead of a reduced size. Because this option will take more time "
+ "to load images, use it only if you have a fast computer."));
+
+ d->hideToolBar = new TQCheckBox(i18n("H&ide toolbar in fullscreen mode"), interfaceOptionsGroup);
+
+ // --------------------------------------------------------
+
+ layout->addWidget(interfaceOptionsGroup);
+ layout->addStretch();
+
+ // --------------------------------------------------------
+
+ readSettings();
+}
+
+SetupLightTable::~SetupLightTable()
+{
+ delete d;
+}
+
+void SetupLightTable::readSettings()
+{
+ TDEConfig* config = kapp->config();
+ TQColor Black(TQt::black);
+ TQColor White(TQt::white);
+ config->setGroup("LightTable Settings");
+ d->hideToolBar->setChecked(config->readBoolEntry("FullScreen Hide ToolBar", false));
+ d->autoSyncPreview->setChecked(config->readBoolEntry("Auto Sync Preview", true));
+ d->autoLoadOnRightPanel->setChecked(config->readBoolEntry("Auto Load Right Panel", true));
+ d->loadFullImageSize->setChecked(config->readBoolEntry("Load Full Image size", false));
+}
+
+void SetupLightTable::applySettings()
+{
+ TDEConfig* config = kapp->config();
+ config->setGroup("LightTable Settings");
+ config->writeEntry("FullScreen Hide ToolBar", d->hideToolBar->isChecked());
+ config->writeEntry("Auto Sync Preview", d->autoSyncPreview->isChecked());
+ config->writeEntry("Auto Load Right Panel", d->autoLoadOnRightPanel->isChecked());
+ config->writeEntry("Load Full Image size", d->loadFullImageSize->isChecked());
+ config->sync();
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setuplighttable.h b/src/utilities/setup/setuplighttable.h
new file mode 100644
index 00000000..20af9a64
--- /dev/null
+++ b/src/utilities/setup/setuplighttable.h
@@ -0,0 +1,59 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-05-11
+ * Description : setup Light Table tab.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPLIGHTTABLE_H
+#define SETUPLIGHTTABLE_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupLightTablePriv;
+
+class SetupLightTable : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupLightTable(TQWidget* parent = 0);
+ ~SetupLightTable();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupLightTablePriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPLIGHTTABLE_H
diff --git a/src/utilities/setup/setupmetadata.cpp b/src/utilities/setup/setupmetadata.cpp
new file mode 100644
index 00000000..e4f74ede
--- /dev/null
+++ b/src/utilities/setup/setupmetadata.cpp
@@ -0,0 +1,238 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-08-03
+ * Description : setup Metadata tab.
+ *
+ * Copyright (C) 2003-2004 by Ralf Holzer <ralf at well.com>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqvbuttongroup.h>
+#include <tqvgroupbox.h>
+#include <tqhgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqcheckbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqtooltip.h>
+#include <tqhbox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kactivelabel.h>
+#include <kdialog.h>
+#include <kurllabel.h>
+#include <kiconloader.h>
+#include <tdeglobalsettings.h>
+#include <kstandarddirs.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "setupmetadata.h"
+#include "setupmetadata.moc"
+
+namespace Digikam
+{
+
+class SetupMetadataPriv
+{
+public:
+
+ SetupMetadataPriv()
+ {
+ ExifAutoRotateAsChanged = false;
+ saveCommentsBox = 0;
+ ExifRotateBox = 0;
+ ExifSetOrientationBox = 0;
+ saveRatingBox = 0;
+ saveTagsIptcBox = 0;
+ saveDateTimeBox = 0;
+ savePhotographerIdIptcBox = 0;
+ saveCreditsIptcBox = 0;
+ }
+
+ bool ExifAutoRotateAsChanged;
+ bool ExifAutoRotateOrg;
+
+ TQCheckBox *saveCommentsBox;
+ TQCheckBox *ExifRotateBox;
+ TQCheckBox *ExifSetOrientationBox;
+ TQCheckBox *saveRatingBox;
+ TQCheckBox *saveTagsIptcBox;
+ TQCheckBox *saveDateTimeBox;
+ TQCheckBox *savePhotographerIdIptcBox;
+ TQCheckBox *saveCreditsIptcBox;
+};
+
+SetupMetadata::SetupMetadata(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupMetadataPriv;
+ TQVBoxLayout *mainLayout = new TQVBoxLayout(parent, 0, KDialog::spacingHint());
+
+ // --------------------------------------------------------
+
+ TQGroupBox *ExifGroup = new TQGroupBox(1, TQt::Horizontal, i18n("EXIF Actions"), parent);
+
+ d->ExifRotateBox = new TQCheckBox(ExifGroup);
+ d->ExifRotateBox->setText(i18n("Show images/thumbs &rotated according to orientation tag"));
+
+ d->ExifSetOrientationBox = new TQCheckBox(ExifGroup);
+ d->ExifSetOrientationBox->setText(i18n("Set orientation tag to normal after rotate/flip"));
+
+ // --------------------------------------------------------
+
+ TQGroupBox *IptcGroup = new TQGroupBox(1, TQt::Horizontal, i18n("IPTC Actions"), parent);
+
+ d->saveTagsIptcBox = new TQCheckBox(IptcGroup);
+ d->saveTagsIptcBox->setText(i18n("&Save image tags as \"Keywords\" tag"));
+ TQWhatsThis::add( d->saveTagsIptcBox, i18n("<p>Turn this option on to store the image tags "
+ "in the IPTC <i>Keywords</i> tag."));
+
+ d->savePhotographerIdIptcBox = new TQCheckBox(IptcGroup);
+ d->savePhotographerIdIptcBox->setText(i18n("&Save default photographer identity as tags"));
+ TQWhatsThis::add( d->savePhotographerIdIptcBox, i18n("<p>Turn this option on to store the default "
+ "photographer identity in the IPTC tags. You can set this "
+ "value in the Identity setup page."));
+
+ d->saveCreditsIptcBox = new TQCheckBox(IptcGroup);
+ d->saveCreditsIptcBox->setText(i18n("&Save default credit and copyright identity as tags"));
+ TQWhatsThis::add( d->saveCreditsIptcBox, i18n("<p>Turn this option on to store the default "
+ "credit and copyright identity in the IPTC tags. "
+ "You can set this value in the Identity setup page."));
+
+ // --------------------------------------------------------
+
+ TQGroupBox *commonGroup = new TQGroupBox(1, TQt::Horizontal, i18n("Common Metadata Actions"), parent);
+
+ d->saveCommentsBox = new TQCheckBox(commonGroup);
+ d->saveCommentsBox->setText(i18n("&Save image captions as embedded text"));
+ TQWhatsThis::add( d->saveCommentsBox, i18n("<p>Turn this option on to store image captions "
+ "in the JFIF section, EXIF tag, and IPTC tag."));
+
+ d->saveDateTimeBox = new TQCheckBox(commonGroup);
+ d->saveDateTimeBox->setText(i18n("&Save image timestamps as tags"));
+ TQWhatsThis::add( d->saveDateTimeBox, i18n("<p>Turn this option on to store the image date and time "
+ "in the EXIF and IPTC tags."));
+
+ d->saveRatingBox = new TQCheckBox(commonGroup);
+ d->saveRatingBox->setText(i18n("&Save image rating as tags"));
+ TQWhatsThis::add( d->saveRatingBox, i18n("<p>Turn this option on to store the image rating "
+ "in the EXIF tag and IPTC <i>Urgency</i> tag."));
+
+ // --------------------------------------------------------
+
+ TQHBox *hbox = new TQHBox(parent);
+
+ KURLLabel *exiv2LogoLabel = new KURLLabel(hbox);
+ exiv2LogoLabel->setText(TQString());
+ exiv2LogoLabel->setURL("http://www.exiv2.org");
+ TDEGlobal::dirs()->addResourceType("logo-exiv2", TDEGlobal::dirs()->kde_default("data") + "digikam/data");
+ TQString directory = TDEGlobal::dirs()->findResourceDir("logo-exiv2", "logo-exiv2.png");
+ exiv2LogoLabel->setPixmap( TQPixmap( directory + "logo-exiv2.png" ) );
+ TQToolTip::add(exiv2LogoLabel, i18n("Visit Exiv2 project website"));
+
+ KActiveLabel* explanation = new KActiveLabel(hbox);
+ explanation->setText(i18n("<p><b><a href='http://en.wikipedia.org/wiki/Exif'>EXIF</a></b> is "
+ "a standard used by most digital cameras today to store technical "
+ "informations about photograph.</p>"
+ "<p><b><a href='http://en.wikipedia.org/wiki/IPTC'>IPTC</a></b> is "
+ "an standard used in digital photography to store "
+ "photographer informations in pictures.</p>"));
+
+ mainLayout->addWidget(ExifGroup);
+ mainLayout->addWidget(IptcGroup);
+ mainLayout->addWidget(commonGroup);
+ mainLayout->addSpacing(KDialog::spacingHint());
+ mainLayout->addWidget(hbox);
+ mainLayout->addStretch();
+
+ readSettings();
+
+ // --------------------------------------------------------
+
+ connect(exiv2LogoLabel, TQ_SIGNAL(leftClickedURL(const TQString&)),
+ this, TQ_SLOT(processExiv2URL(const TQString&)));
+
+ connect(d->ExifRotateBox, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotExifAutoRotateToggled(bool)));
+}
+
+SetupMetadata::~SetupMetadata()
+{
+ delete d;
+}
+
+void SetupMetadata::processExiv2URL(const TQString& url)
+{
+ TDEApplication::kApplication()->invokeBrowser(url);
+}
+
+void SetupMetadata::applySettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings) return;
+
+ settings->setExifRotate(d->ExifRotateBox->isChecked());
+ settings->setExifSetOrientation(d->ExifSetOrientationBox->isChecked());
+ settings->setSaveComments(d->saveCommentsBox->isChecked());
+ settings->setSaveDateTime(d->saveDateTimeBox->isChecked());
+ settings->setSaveRating(d->saveRatingBox->isChecked());
+ settings->setSaveIptcTags(d->saveTagsIptcBox->isChecked());
+ settings->setSaveIptcPhotographerId(d->savePhotographerIdIptcBox->isChecked());
+ settings->setSaveIptcCredits(d->saveCreditsIptcBox->isChecked());
+
+ settings->saveSettings();
+}
+
+void SetupMetadata::readSettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings) return;
+
+ d->ExifAutoRotateOrg = settings->getExifRotate();
+ d->ExifRotateBox->setChecked(d->ExifAutoRotateOrg);
+ d->ExifSetOrientationBox->setChecked(settings->getExifSetOrientation());
+ d->saveCommentsBox->setChecked(settings->getSaveComments());
+ d->saveDateTimeBox->setChecked(settings->getSaveDateTime());
+ d->saveRatingBox->setChecked(settings->getSaveRating());
+ d->saveTagsIptcBox->setChecked(settings->getSaveIptcTags());
+ d->savePhotographerIdIptcBox->setChecked(settings->getSaveIptcPhotographerId());
+ d->saveCreditsIptcBox->setChecked(settings->getSaveIptcCredits());
+}
+
+bool SetupMetadata::exifAutoRotateAsChanged()
+{
+ return d->ExifAutoRotateAsChanged;
+}
+
+void SetupMetadata::slotExifAutoRotateToggled(bool b)
+{
+ if ( b != d->ExifAutoRotateOrg)
+ d->ExifAutoRotateAsChanged = true;
+ else
+ d->ExifAutoRotateAsChanged = false;
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupmetadata.h b/src/utilities/setup/setupmetadata.h
new file mode 100644
index 00000000..45511065
--- /dev/null
+++ b/src/utilities/setup/setupmetadata.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-08-03
+ * Description : setup Metadata tab.
+ *
+ * Copyright (C) 2003-2004 by Ralf Holzer <ralf at well.com>
+ * Copyright (C) 2003-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPMETADATA_H
+#define SETUPMETADATA_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupMetadataPriv;
+
+class SetupMetadata : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupMetadata(TQWidget* parent = 0);
+ ~SetupMetadata();
+
+ void applySettings();
+
+ bool exifAutoRotateAsChanged();
+
+private:
+
+ void readSettings();
+
+private slots:
+
+ void processExiv2URL(const TQString&);
+ void slotExifAutoRotateToggled(bool);
+
+private:
+
+ SetupMetadataPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPMETADATA_H
diff --git a/src/utilities/setup/setupmime.cpp b/src/utilities/setup/setupmime.cpp
new file mode 100644
index 00000000..6ac069e3
--- /dev/null
+++ b/src/utilities/setup/setupmime.cpp
@@ -0,0 +1,280 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-05-03
+ * Description : mime types setup tab
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqhbox.h>
+#include <tqhgroupbox.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqlineedit.h>
+#include <tqwhatsthis.h>
+#include <tqtoolbutton.h>
+#include <tqtooltip.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <klineeditdlg.h>
+#include <kiconloader.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "setupmime.h"
+#include "setupmime.moc"
+
+namespace Digikam
+{
+
+class SetupMimePriv
+{
+public:
+
+ SetupMimePriv()
+ {
+ imageFileFilterEdit = 0;
+ movieFileFilterEdit = 0;
+ audioFileFilterEdit = 0;
+ rawFileFilterEdit = 0;
+ revertImageFileFilterBtn = 0;
+ revertMovieFileFilterBtn = 0;
+ revertAudioFileFilterBtn = 0;
+ revertRawFileFilterBtn = 0;
+ }
+
+ TQToolButton *revertImageFileFilterBtn;
+ TQToolButton *revertMovieFileFilterBtn;
+ TQToolButton *revertAudioFileFilterBtn;
+ TQToolButton *revertRawFileFilterBtn;
+
+ TQLineEdit *imageFileFilterEdit;
+ TQLineEdit *movieFileFilterEdit;
+ TQLineEdit *audioFileFilterEdit;
+ TQLineEdit *rawFileFilterEdit;
+};
+
+SetupMime::SetupMime(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupMimePriv;
+ TQVBoxLayout *layout = new TQVBoxLayout(parent, 0, KDialog::spacingHint());
+
+ // --------------------------------------------------------
+
+ TQGroupBox *imageFileFilterBox = new TQGroupBox(0, TQt::Horizontal, i18n("Image Files"), parent);
+ TQGridLayout* grid1 = new TQGridLayout(imageFileFilterBox->layout(), 1, 1, KDialog::spacingHint());
+
+ TQLabel *logoLabel1 = new TQLabel(imageFileFilterBox);
+ logoLabel1->setPixmap(DesktopIcon("image-x-generic"));
+
+ TQLabel *imageFileFilterLabel = new TQLabel(imageFileFilterBox);
+ imageFileFilterLabel->setText(i18n("Show only &image files with extensions:"));
+
+ TQHBox *hbox1 = new TQHBox(imageFileFilterBox);
+ d->imageFileFilterEdit = new TQLineEdit(hbox1);
+ TQWhatsThis::add( d->imageFileFilterEdit, i18n("<p>Here you can set the extensions of image files "
+ "to be displayed in Albums (such as JPEG or TIFF); "
+ "clicking on these files will "
+ "open them in the digiKam Image Editor."));
+ imageFileFilterLabel->setBuddy(d->imageFileFilterEdit);
+ hbox1->setStretchFactor(d->imageFileFilterEdit, 10);
+
+ d->revertImageFileFilterBtn = new TQToolButton(hbox1);
+ d->revertImageFileFilterBtn->setIconSet(SmallIcon("reload_page"));
+ TQToolTip::add(d->revertImageFileFilterBtn, i18n("Revert to default settings"));
+
+ grid1->addMultiCellWidget(logoLabel1, 0, 1, 0, 0);
+ grid1->addMultiCellWidget(imageFileFilterLabel, 0, 0, 1, 1);
+ grid1->addMultiCellWidget(hbox1, 1, 1, 1, 1);
+ grid1->setColStretch(1, 10);
+
+ layout->addWidget(imageFileFilterBox);
+
+ // --------------------------------------------------------
+
+ TQGroupBox *movieFileFilterBox = new TQGroupBox(0, TQt::Horizontal, i18n("Movie Files"), parent);
+ TQGridLayout* grid2 = new TQGridLayout(movieFileFilterBox->layout(), 1, 1, KDialog::spacingHint());
+
+ TQLabel *logoLabel2 = new TQLabel(movieFileFilterBox);
+ logoLabel2->setPixmap(DesktopIcon("video-x-generic"));
+
+ TQLabel *movieFileFilterLabel = new TQLabel(movieFileFilterBox);
+ movieFileFilterLabel->setText(i18n("Show only &movie files with extensions:"));
+
+ TQHBox *hbox2 = new TQHBox(movieFileFilterBox);
+ d->movieFileFilterEdit = new TQLineEdit(hbox2);
+ TQWhatsThis::add( d->movieFileFilterEdit, i18n("<p>Here you can set the extensions of movie files "
+ "to be displayed in Albums (such as MPEG or AVI); "
+ "clicking on these files will "
+ "open them with the default TDE movie player."));
+ movieFileFilterLabel->setBuddy(d->movieFileFilterEdit);
+ hbox2->setStretchFactor(d->movieFileFilterEdit, 10);
+
+ d->revertMovieFileFilterBtn = new TQToolButton(hbox2);
+ d->revertMovieFileFilterBtn->setIconSet(SmallIcon("reload_page"));
+ TQToolTip::add(d->revertMovieFileFilterBtn, i18n("Revert to default settings"));
+
+ grid2->addMultiCellWidget(logoLabel2, 0, 1, 0, 0);
+ grid2->addMultiCellWidget(movieFileFilterLabel, 0, 0, 1, 1);
+ grid2->addMultiCellWidget(hbox2, 1, 1, 1, 1);
+ grid2->setColStretch(1, 10);
+
+ layout->addWidget(movieFileFilterBox);
+
+ // --------------------------------------------------------
+
+ TQGroupBox *audioFileFilterBox = new TQGroupBox(0, TQt::Horizontal, i18n("Audio Files"), parent);
+ TQGridLayout* grid3 = new TQGridLayout(audioFileFilterBox->layout(), 1, 1, KDialog::spacingHint());
+
+ TQLabel *logoLabel3 = new TQLabel(audioFileFilterBox);
+ logoLabel3->setPixmap(DesktopIcon("audio-x-generic"));
+
+ TQLabel *audioFileFilterLabel = new TQLabel(audioFileFilterBox);
+ audioFileFilterLabel->setText(i18n("Show only &audio files with extensions:"));
+
+ TQHBox *hbox3 = new TQHBox(audioFileFilterBox);
+ d->audioFileFilterEdit = new TQLineEdit(hbox3);
+ TQWhatsThis::add( d->audioFileFilterEdit, i18n("<p>Here you can set the extensions of audio files "
+ "to be displayed in Albums (such as MP3 or OGG); "
+ "clicking on these files will "
+ "open them with the default TDE audio player."));
+ audioFileFilterLabel->setBuddy(d->audioFileFilterEdit);
+ hbox3->setStretchFactor(d->audioFileFilterEdit, 10);
+
+ d->revertAudioFileFilterBtn = new TQToolButton(hbox3);
+ d->revertAudioFileFilterBtn->setIconSet(SmallIcon("reload_page"));
+ TQToolTip::add(d->revertAudioFileFilterBtn, i18n("Revert to default settings"));
+
+ grid3->addMultiCellWidget(logoLabel3, 0, 1, 0, 0);
+ grid3->addMultiCellWidget(audioFileFilterLabel, 0, 0, 1, 1);
+ grid3->addMultiCellWidget(hbox3, 1, 1, 1, 1);
+ grid3->setColStretch(1, 10);
+
+ layout->addWidget(audioFileFilterBox);
+
+ // --------------------------------------------------------
+
+ TQGroupBox *rawFileFilterBox = new TQGroupBox(0, TQt::Horizontal, i18n("RAW Files"), parent);
+ TQGridLayout* grid4 = new TQGridLayout(rawFileFilterBox->layout(), 1, 1, KDialog::spacingHint());
+
+ TQLabel *logoLabel4 = new TQLabel(rawFileFilterBox);
+ logoLabel4->setPixmap(DesktopIcon("kdcraw"));
+
+ TQLabel *rawFileFilterLabel = new TQLabel(rawFileFilterBox);
+ rawFileFilterLabel->setText(i18n("Show only &RAW files with extensions:"));
+
+ TQHBox *hbox4 = new TQHBox(rawFileFilterBox);
+ d->rawFileFilterEdit = new TQLineEdit(hbox4);
+ TQWhatsThis::add( d->rawFileFilterEdit, i18n("<p>Here you can set the extensions of RAW image files "
+ "to be displayed in Albums (such as CRW, for Canon cameras, "
+ "or NEF, for Nikon cameras)."));
+ rawFileFilterLabel->setBuddy(d->rawFileFilterEdit);
+ hbox4->setStretchFactor(d->rawFileFilterEdit, 10);
+
+ d->revertRawFileFilterBtn = new TQToolButton(hbox4);
+ d->revertRawFileFilterBtn->setIconSet(SmallIcon("reload_page"));
+ TQToolTip::add(d->revertRawFileFilterBtn, i18n("Revert to default settings"));
+
+ grid4->addMultiCellWidget(logoLabel4, 0, 1, 0, 0);
+ grid4->addMultiCellWidget(rawFileFilterLabel, 0, 0, 1, 1);
+ grid4->addMultiCellWidget(hbox4, 1, 1, 1, 1);
+ grid4->setColStretch(1, 10);
+
+ layout->addWidget(rawFileFilterBox);
+ layout->addStretch();
+
+ // --------------------------------------------------------
+
+ connect(d->revertImageFileFilterBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRevertImageFileFilter()));
+
+ connect(d->revertMovieFileFilterBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRevertMovieFileFilter()));
+
+ connect(d->revertAudioFileFilterBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRevertAudioFileFilter()));
+
+ connect(d->revertRawFileFilterBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotRevertRawFileFilter()));
+
+ // --------------------------------------------------------
+
+ readSettings();
+}
+
+SetupMime::~SetupMime()
+{
+ delete d;
+}
+
+void SetupMime::applySettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ if (!settings) return;
+
+ settings->setImageFileFilter(d->imageFileFilterEdit->text());
+ settings->setMovieFileFilter(d->movieFileFilterEdit->text());
+ settings->setAudioFileFilter(d->audioFileFilterEdit->text());
+ settings->setRawFileFilter(d->rawFileFilterEdit->text());
+
+ settings->saveSettings();
+}
+
+void SetupMime::readSettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ if (!settings) return;
+
+ d->imageFileFilterEdit->setText(settings->getImageFileFilter());
+ d->movieFileFilterEdit->setText(settings->getMovieFileFilter());
+ d->audioFileFilterEdit->setText(settings->getAudioFileFilter());
+ d->rawFileFilterEdit->setText(settings->getRawFileFilter());
+}
+
+void SetupMime::slotRevertImageFileFilter()
+{
+ d->imageFileFilterEdit->setText(AlbumSettings::instance()->getDefaultImageFileFilter());
+}
+
+void SetupMime::slotRevertMovieFileFilter()
+{
+ d->movieFileFilterEdit->setText(AlbumSettings::instance()->getDefaultMovieFileFilter());
+}
+
+void SetupMime::slotRevertAudioFileFilter()
+{
+ d->audioFileFilterEdit->setText(AlbumSettings::instance()->getDefaultAudioFileFilter());
+}
+
+void SetupMime::slotRevertRawFileFilter()
+{
+ d->rawFileFilterEdit->setText(AlbumSettings::instance()->getDefaultRawFileFilter());
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setupmime.h b/src/utilities/setup/setupmime.h
new file mode 100644
index 00000000..3bff1525
--- /dev/null
+++ b/src/utilities/setup/setupmime.h
@@ -0,0 +1,66 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2003-05-03
+ * Description : mime types setup tab.
+ *
+ * Copyright (C) 2004-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPMIME_H
+#define SETUPMIME_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupMimePriv;
+
+class SetupMime : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupMime(TQWidget* parent = 0);
+ ~SetupMime();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private slots:
+
+ void slotRevertImageFileFilter();
+ void slotRevertMovieFileFilter();
+ void slotRevertAudioFileFilter();
+ void slotRevertRawFileFilter();
+
+private:
+
+ SetupMimePriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPMIME_H
diff --git a/src/utilities/setup/setupmisc.cpp b/src/utilities/setup/setupmisc.cpp
new file mode 100644
index 00000000..8dc16e96
--- /dev/null
+++ b/src/utilities/setup/setupmisc.cpp
@@ -0,0 +1,124 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-23
+ * Description : mics configuration setup tab
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqvgroupbox.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "setupmisc.h"
+
+namespace Digikam
+{
+
+class SetupMiscPriv
+{
+public:
+
+ SetupMiscPriv()
+ {
+ showSplashCheck = 0;
+ showTrashDeleteDialogCheck = 0;
+ sidebarApplyDirectlyCheck = 0;
+ scanAtStart = 0;
+ }
+
+ TQCheckBox* showSplashCheck;
+ TQCheckBox* showTrashDeleteDialogCheck;
+ TQCheckBox* sidebarApplyDirectlyCheck;
+ TQCheckBox* scanAtStart;
+};
+
+SetupMisc::SetupMisc(TQWidget* parent)
+ : TQWidget( parent )
+{
+ d = new SetupMiscPriv;
+
+ TQVBoxLayout *mainLayout = new TQVBoxLayout(parent);
+ TQVBoxLayout *layout = new TQVBoxLayout( this, 0, KDialog::spacingHint() );
+
+ // --------------------------------------------------------
+
+ d->showTrashDeleteDialogCheck = new TQCheckBox(i18n("Show confirmation dialog when moving items to the &trash"), this);
+ layout->addWidget(d->showTrashDeleteDialogCheck);
+
+ // --------------------------------------------------------
+
+ d->sidebarApplyDirectlyCheck = new TQCheckBox(i18n("Apply changes in the &right sidebar without confirmation"), this);
+ layout->addWidget(d->sidebarApplyDirectlyCheck);
+
+ // --------------------------------------------------------
+
+ d->showSplashCheck = new TQCheckBox(i18n("&Show splash screen at startup"), this);
+ layout->addWidget(d->showSplashCheck);
+
+ // --------------------------------------------------------
+
+ d->scanAtStart = new TQCheckBox(i18n("&Scan for new items on startup (slows down startup)"), this);
+ layout->addWidget(d->scanAtStart);
+
+ // --------------------------------------------------------
+
+ layout->addStretch();
+ readSettings();
+ adjustSize();
+ mainLayout->addWidget(this);
+}
+
+SetupMisc::~SetupMisc()
+{
+ delete d;
+}
+
+void SetupMisc::applySettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ settings->setShowSplashScreen(d->showSplashCheck->isChecked());
+ settings->setShowTrashDeleteDialog(d->showTrashDeleteDialogCheck->isChecked());
+ settings->setApplySidebarChangesDirectly(d->sidebarApplyDirectlyCheck->isChecked());
+ settings->setScanAtStart(d->scanAtStart->isChecked());
+ settings->saveSettings();
+}
+
+void SetupMisc::readSettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ d->showSplashCheck->setChecked(settings->getShowSplashScreen());
+ d->showTrashDeleteDialogCheck->setChecked(settings->getShowTrashDeleteDialog());
+ d->sidebarApplyDirectlyCheck->setChecked(settings->getApplySidebarChangesDirectly());
+ d->scanAtStart->setChecked(settings->getScanAtStart());
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupmisc.h b/src/utilities/setup/setupmisc.h
new file mode 100644
index 00000000..92918ba4
--- /dev/null
+++ b/src/utilities/setup/setupmisc.h
@@ -0,0 +1,58 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-08-23
+ * Description : mics configuration setup tab
+ *
+ * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPMISC_H
+#define SETUPMISC_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupMiscPriv;
+
+class SetupMisc : public TQWidget
+{
+public:
+
+ SetupMisc(TQWidget* parent);
+ ~SetupMisc();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupMiscPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif /* SETUPMISC_H */
diff --git a/src/utilities/setup/setupplugins.cpp b/src/utilities/setup/setupplugins.cpp
new file mode 100644
index 00000000..e0d9c494
--- /dev/null
+++ b/src/utilities/setup/setupplugins.cpp
@@ -0,0 +1,104 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-01-02
+ * Description : setup Kipi plugins tab.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqstring.h>
+#include <tqgroupbox.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+
+// libkipi includes.
+
+#include <libkipi/pluginloader.h>
+#include <libkipi/version.h>
+
+// Local includes.
+
+#include "setupplugins.h"
+#include "setupplugins.moc"
+
+namespace Digikam
+{
+
+class SetupPluginsPriv
+{
+public:
+
+ SetupPluginsPriv()
+ {
+ pluginsNumber = 0;
+ kipiConfig = 0;
+ }
+
+ TQLabel* pluginsNumber;
+
+ KIPI::ConfigWidget* kipiConfig;
+};
+
+SetupPlugins::SetupPlugins(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupPluginsPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout(parent);
+ d->pluginsNumber = new TQLabel(parent);
+ d->pluginsNumber->setAlignment(TQt::AlignLeft | TQt::AlignVCenter);
+
+ d->kipiConfig = KIPI::PluginLoader::instance()->configWidget( parent );
+ TQString pluginsListHelp = i18n("<p>A list of available Kipi plugins appears below.");
+ TQWhatsThis::add(d->kipiConfig, pluginsListHelp);
+
+ layout->addWidget(d->pluginsNumber);
+ layout->addWidget(d->kipiConfig);
+ layout->setMargin(0);
+ layout->setSpacing(KDialog::spacingHint());
+}
+
+SetupPlugins::~SetupPlugins()
+{
+ delete d;
+}
+
+void SetupPlugins::initPlugins(int kipiPluginsNumber)
+{
+ d->pluginsNumber->setText(i18n("1 Kipi plugin found",
+ "%n Kipi plugins found",
+ kipiPluginsNumber));
+}
+
+void SetupPlugins::applyPlugins()
+{
+ d->kipiConfig->apply();
+}
+
+} // namespace Digikam
diff --git a/src/utilities/setup/setupplugins.h b/src/utilities/setup/setupplugins.h
new file mode 100644
index 00000000..31b71121
--- /dev/null
+++ b/src/utilities/setup/setupplugins.h
@@ -0,0 +1,56 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-01-02
+ * Description : setup Kipi plugins tab.
+ *
+ * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPPLUGINS_H
+#define SETUPPLUGINS_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupPluginsPriv;
+
+class SetupPlugins : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupPlugins(TQWidget* parent = 0);
+ ~SetupPlugins();
+
+ void initPlugins(int kipiPluginsNumber);
+ void applyPlugins();
+
+private:
+
+ SetupPluginsPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPPLUGINS_H
diff --git a/src/utilities/setup/setupslideshow.cpp b/src/utilities/setup/setupslideshow.cpp
new file mode 100644
index 00000000..970d9c67
--- /dev/null
+++ b/src/utilities/setup/setupslideshow.cpp
@@ -0,0 +1,165 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-21
+ * Description : setup tab for slideshow options.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqlabel.h>
+#include <tqwhatsthis.h>
+#include <tqcheckbox.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialog.h>
+#include <knuminput.h>
+#include <tdeconfig.h>
+#include <tdeapplication.h>
+
+// Local includes.
+
+#include "setupslideshow.h"
+#include "setupslideshow.moc"
+
+namespace Digikam
+{
+
+class SetupSlideShowPriv
+{
+public:
+
+ SetupSlideShowPriv()
+ {
+ delayInput = 0;
+ startWithCurrent = 0;
+ loopMode = 0;
+ printName = 0;
+ printDate = 0;
+ printApertureFocal = 0;
+ printExpoSensitivity = 0;
+ printMakeModel = 0;
+ printComment = 0;
+ }
+
+ TQCheckBox *startWithCurrent;
+ TQCheckBox *loopMode;
+ TQCheckBox *printName;
+ TQCheckBox *printDate;
+ TQCheckBox *printApertureFocal;
+ TQCheckBox *printExpoSensitivity;
+ TQCheckBox *printMakeModel;
+ TQCheckBox *printComment;
+
+ KIntNumInput *delayInput;
+};
+
+SetupSlideShow::SetupSlideShow(TQWidget* parent )
+ : TQWidget(parent)
+{
+ d = new SetupSlideShowPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent );
+
+ d->delayInput = new KIntNumInput(5, parent);
+ d->delayInput->setRange(1, 3600, 1, true );
+ d->delayInput->setLabel( i18n("&Delay between images:"), AlignLeft|AlignTop );
+ TQWhatsThis::add( d->delayInput, i18n("<p>The delay, in seconds, between images."));
+
+ d->startWithCurrent = new TQCheckBox(i18n("Start with current image"), parent);
+ TQWhatsThis::add( d->startWithCurrent, i18n("<p>If this option is enabled, slideshow will be started "
+ "with currently selected image."));
+
+ d->loopMode = new TQCheckBox(i18n("Display in a loop"), parent);
+ TQWhatsThis::add( d->loopMode, i18n("<p>Run the slideshow in endless repetition."));
+
+ d->printName = new TQCheckBox(i18n("Print image file name"), parent);
+ TQWhatsThis::add( d->printName, i18n("<p>Print the image file name at the bottom of the screen."));
+
+ d->printDate = new TQCheckBox(i18n("Print image creation date"), parent);
+ TQWhatsThis::add( d->printDate, i18n("<p>Print the image creation time/date at the bottom of the screen."));
+
+ d->printApertureFocal = new TQCheckBox(i18n("Print camera aperture and focal length"), parent);
+ TQWhatsThis::add( d->printApertureFocal, i18n("<p>Print the camera aperture and focal length at the bottom of the screen."));
+
+ d->printExpoSensitivity = new TQCheckBox(i18n("Print camera exposure and sensitivity"), parent);
+ TQWhatsThis::add( d->printExpoSensitivity, i18n("<p>Print the camera exposure and sensitivity at the bottom of the screen."));
+
+ d->printMakeModel = new TQCheckBox(i18n("Print camera make and model"), parent);
+ TQWhatsThis::add( d->printMakeModel, i18n("<p>Print the camera make and model at the bottom of the screen."));
+
+ d->printComment = new TQCheckBox(i18n("Print image caption"), parent);
+ TQWhatsThis::add( d->printComment, i18n("<p>Print the image caption at the bottom of the screen."));
+
+ layout->addWidget(d->delayInput);
+ layout->addWidget(d->startWithCurrent);
+ layout->addWidget(d->loopMode);
+ layout->addWidget(d->printName);
+ layout->addWidget(d->printDate);
+ layout->addWidget(d->printApertureFocal);
+ layout->addWidget(d->printExpoSensitivity);
+ layout->addWidget(d->printMakeModel);
+ layout->addWidget(d->printComment);
+ layout->addStretch();
+
+ readSettings();
+}
+
+SetupSlideShow::~SetupSlideShow()
+{
+ delete d;
+}
+
+void SetupSlideShow::applySettings()
+{
+ TDEConfig* config = kapp->config();
+
+ config->setGroup("ImageViewer Settings");
+ config->writeEntry("SlideShowDelay", d->delayInput->value());
+ config->writeEntry("SlideShowStartCurrent", d->startWithCurrent->isChecked());
+ config->writeEntry("SlideShowLoop", d->loopMode->isChecked());
+ config->writeEntry("SlideShowPrintName", d->printName->isChecked());
+ config->writeEntry("SlideShowPrintDate", d->printDate->isChecked());
+ config->writeEntry("SlideShowPrintApertureFocal", d->printApertureFocal->isChecked());
+ config->writeEntry("SlideShowPrintExpoSensitivity", d->printExpoSensitivity->isChecked());
+ config->writeEntry("SlideShowPrintMakeModel", d->printMakeModel->isChecked());
+ config->writeEntry("SlideShowPrintComment", d->printComment->isChecked());
+ config->sync();
+}
+
+void SetupSlideShow::readSettings()
+{
+ TDEConfig* config = kapp->config();
+
+ config->setGroup("ImageViewer Settings");
+ d->delayInput->setValue(config->readNumEntry("SlideShowDelay", 5));
+ d->startWithCurrent->setChecked(config->readBoolEntry("SlideShowStartCurrent", false));
+ d->loopMode->setChecked(config->readBoolEntry("SlideShowLoop", false));
+ d->printName->setChecked(config->readBoolEntry("SlideShowPrintName", true));
+ d->printDate->setChecked(config->readBoolEntry("SlideShowPrintDate", false));
+ d->printApertureFocal->setChecked(config->readBoolEntry("SlideShowPrintApertureFocal", false));
+ d->printExpoSensitivity->setChecked(config->readBoolEntry("SlideShowPrintExpoSensitivity", false));
+ d->printMakeModel->setChecked(config->readBoolEntry("SlideShowPrintMakeModel", false));
+ d->printComment->setChecked(config->readBoolEntry("SlideShowPrintComment", false));
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setupslideshow.h b/src/utilities/setup/setupslideshow.h
new file mode 100644
index 00000000..9ba68035
--- /dev/null
+++ b/src/utilities/setup/setupslideshow.h
@@ -0,0 +1,64 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-05-21
+ * Description : setup tab for slideshow options.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPSLIDESHOW_H
+#define SETUPSLIDESHOW_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+class SetupSlideShowPriv;
+
+class DIGIKAM_EXPORT SetupSlideShow : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupSlideShow(TQWidget* parent = 0);
+ ~SetupSlideShow();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupSlideShowPriv* d;
+
+};
+
+} // namespace Digikam
+
+#endif /* SETUPSLIDESHOW_H */
diff --git a/src/utilities/setup/setuptooltip.cpp b/src/utilities/setup/setuptooltip.cpp
new file mode 100644
index 00000000..2a5c62c8
--- /dev/null
+++ b/src/utilities/setup/setuptooltip.cpp
@@ -0,0 +1,272 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-09
+ * Description : album item tool tip configuration setup tab
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqlayout.h>
+#include <tqvgroupbox.h>
+#include <tqcheckbox.h>
+#include <tqwhatsthis.h>
+
+// KDE includes.
+
+#include <tdelocale.h>
+#include <kdialogbase.h>
+
+// Local includes.
+
+#include "albumsettings.h"
+#include "setuptooltip.h"
+#include "setuptooltip.moc"
+
+namespace Digikam
+{
+
+class SetupToolTipPriv
+{
+public:
+
+ SetupToolTipPriv()
+ {
+ showToolTipsBox = 0;
+
+ showFileNameBox = 0;
+ showFileDateBox = 0;
+ showFileSizeBox = 0;
+ showImageTypeBox = 0;
+ showImageDimBox = 0;
+
+ showPhotoMakeBox = 0;
+ showPhotoDateBox = 0;
+ showPhotoFocalBox = 0;
+ showPhotoExpoBox = 0;
+ showPhotoModeBox = 0;
+ showPhotoFlashBox = 0;
+ showPhotoWbBox = 0;
+
+ showAlbumNameBox = 0;
+ showCommentsBox = 0;
+ showTagsBox = 0;
+ showRatingBox = 0;
+
+ fileSettingBox = 0;
+ photoSettingBox = 0;
+ digikamSettingBox = 0;
+ }
+
+ TQCheckBox *showToolTipsBox;
+
+ TQCheckBox *showFileNameBox;
+ TQCheckBox *showFileDateBox;
+ TQCheckBox *showFileSizeBox;
+ TQCheckBox *showImageTypeBox;
+ TQCheckBox *showImageDimBox;
+
+ TQCheckBox *showPhotoMakeBox;
+ TQCheckBox *showPhotoDateBox;
+ TQCheckBox *showPhotoFocalBox;
+ TQCheckBox *showPhotoExpoBox;
+ TQCheckBox *showPhotoModeBox;
+ TQCheckBox *showPhotoFlashBox;
+ TQCheckBox *showPhotoWbBox;
+
+ TQCheckBox *showAlbumNameBox;
+ TQCheckBox *showCommentsBox;
+ TQCheckBox *showTagsBox;
+ TQCheckBox *showRatingBox;
+
+ TQVGroupBox *fileSettingBox;
+ TQVGroupBox *photoSettingBox;
+ TQVGroupBox *digikamSettingBox;
+};
+
+SetupToolTip::SetupToolTip(TQWidget* parent)
+ : TQWidget(parent)
+{
+ d = new SetupToolTipPriv;
+ TQVBoxLayout *layout = new TQVBoxLayout( parent, 0, KDialog::spacingHint() );
+
+ d->showToolTipsBox = new TQCheckBox(i18n("Show album items toolti&ps"), parent);
+ TQWhatsThis::add( d->showToolTipsBox, i18n("<p>Set this option to display image information when "
+ "the mouse hovers over an album item."));
+
+ layout->addWidget(d->showToolTipsBox);
+
+ // --------------------------------------------------------
+
+ d->fileSettingBox = new TQVGroupBox(i18n("File/Image Information"), parent);
+
+ d->showFileNameBox = new TQCheckBox(i18n("Show file name"), d->fileSettingBox);
+ TQWhatsThis::add( d->showFileNameBox, i18n("<p>Set this option to display the image file name."));
+
+ d->showFileDateBox = new TQCheckBox(i18n("Show file date"), d->fileSettingBox);
+ TQWhatsThis::add( d->showFileDateBox, i18n("<p>Set this option to display the image file date."));
+
+ d->showFileSizeBox = new TQCheckBox(i18n("Show file size"), d->fileSettingBox);
+ TQWhatsThis::add( d->showFileSizeBox, i18n("<p>Set this option to display the image file size."));
+
+ d->showImageTypeBox = new TQCheckBox(i18n("Show image type"), d->fileSettingBox);
+ TQWhatsThis::add( d->showImageTypeBox, i18n("<p>Set this option to display the image type."));
+
+ d->showImageDimBox = new TQCheckBox(i18n("Show image dimensions"), d->fileSettingBox);
+ TQWhatsThis::add( d->showImageDimBox, i18n("<p>Set this option to display the image dimensions in pixels."));
+
+ layout->addWidget(d->fileSettingBox);
+
+ // --------------------------------------------------------
+
+ d->photoSettingBox = new TQVGroupBox(i18n("Photograph Information"), parent);
+
+ d->showPhotoMakeBox = new TQCheckBox(i18n("Show camera make and model"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoMakeBox, i18n("<p>Set this option to display the make and model of the "
+ "camera with which the image has been taken."));
+
+ d->showPhotoDateBox = new TQCheckBox(i18n("Show camera date"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoDateBox, i18n("<p>Set this option to display the date when the image was taken."));
+
+ d->showPhotoFocalBox = new TQCheckBox(i18n("Show camera aperture and focal"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoFocalBox, i18n("<p>Set this option to display the camera aperture and focal settings "
+ "used to take the image."));
+
+ d->showPhotoExpoBox = new TQCheckBox(i18n("Show camera exposure and sensitivity"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoExpoBox, i18n("<p>Set this option to display the camera exposure and sensitivity "
+ "used to take the image."));
+
+ d->showPhotoModeBox = new TQCheckBox(i18n("Show camera mode and program"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoModeBox, i18n("<p>Set this option to display the camera mode and program "
+ "used to take the image."));
+
+ d->showPhotoFlashBox = new TQCheckBox(i18n("Show camera flash settings"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoFlashBox, i18n("<p>Set this option to display the camera flash settings "
+ "used to take the image."));
+
+ d->showPhotoWbBox = new TQCheckBox(i18n("Show camera white balance settings"), d->photoSettingBox);
+ TQWhatsThis::add( d->showPhotoWbBox, i18n("<p>Set this option to display the camera white balance settings "
+ "used to take the image."));
+
+ layout->addWidget(d->photoSettingBox);
+
+ // --------------------------------------------------------
+
+ d->digikamSettingBox = new TQVGroupBox(i18n("digiKam Information"), parent);
+
+ d->showAlbumNameBox = new TQCheckBox(i18n("Show album name"), d->digikamSettingBox);
+ TQWhatsThis::add( d->showAlbumNameBox, i18n("<p>Set this option to display the album name."));
+
+ d->showCommentsBox = new TQCheckBox(i18n("Show image caption"), d->digikamSettingBox);
+ TQWhatsThis::add( d->showCommentsBox, i18n("<p>Set this option to display the image captions."));
+
+ d->showTagsBox = new TQCheckBox(i18n("Show image tags"), d->digikamSettingBox);
+ TQWhatsThis::add( d->showTagsBox, i18n("<p>Set this option to display the image tags."));
+
+ d->showRatingBox = new TQCheckBox(i18n("Show image rating"), d->digikamSettingBox);
+ TQWhatsThis::add( d->showRatingBox, i18n("<p>Set this option to display the image rating."));
+
+ layout->addWidget(d->digikamSettingBox);
+ layout->addStretch();
+
+ // --------------------------------------------------------
+
+ connect(d->showToolTipsBox, TQ_SIGNAL(toggled(bool)),
+ d->fileSettingBox, TQ_SLOT(setEnabled(bool)));
+
+ connect(d->showToolTipsBox, TQ_SIGNAL(toggled(bool)),
+ d->photoSettingBox, TQ_SLOT(setEnabled(bool)));
+
+ connect(d->showToolTipsBox, TQ_SIGNAL(toggled(bool)),
+ d->digikamSettingBox, TQ_SLOT(setEnabled(bool)));
+
+ // --------------------------------------------------------
+
+ readSettings();
+ adjustSize();
+}
+
+SetupToolTip::~SetupToolTip()
+{
+ delete d;
+}
+
+void SetupToolTip::applySettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+ if (!settings) return;
+
+ settings->setShowToolTips(d->showToolTipsBox->isChecked());
+
+ settings->setToolTipsShowFileName(d->showFileNameBox->isChecked());
+ settings->setToolTipsShowFileDate(d->showFileDateBox->isChecked());
+ settings->setToolTipsShowFileSize(d->showFileSizeBox->isChecked());
+ settings->setToolTipsShowImageType(d->showImageTypeBox->isChecked());
+ settings->setToolTipsShowImageDim(d->showImageDimBox->isChecked());
+
+ settings->setToolTipsShowPhotoMake(d->showPhotoMakeBox->isChecked());
+ settings->setToolTipsShowPhotoDate(d->showPhotoDateBox->isChecked());
+ settings->setToolTipsShowPhotoFocal(d->showPhotoFocalBox->isChecked());
+ settings->setToolTipsShowPhotoExpo(d->showPhotoExpoBox->isChecked());
+ settings->setToolTipsShowPhotoMode(d->showPhotoModeBox->isChecked());
+ settings->setToolTipsShowPhotoFlash(d->showPhotoFlashBox->isChecked());
+ settings->setToolTipsShowPhotoWB(d->showPhotoWbBox->isChecked());
+
+ settings->setToolTipsShowAlbumName(d->showAlbumNameBox->isChecked());
+ settings->setToolTipsShowComments(d->showCommentsBox->isChecked());
+ settings->setToolTipsShowTags(d->showTagsBox->isChecked());
+ settings->setToolTipsShowRating(d->showRatingBox->isChecked());
+
+ settings->saveSettings();
+}
+
+void SetupToolTip::readSettings()
+{
+ AlbumSettings* settings = AlbumSettings::instance();
+
+ if (!settings) return;
+
+ d->showToolTipsBox->setChecked(settings->getShowToolTips());
+
+ d->showFileNameBox->setChecked(settings->getToolTipsShowFileName());
+ d->showFileDateBox->setChecked(settings->getToolTipsShowFileDate());
+ d->showFileSizeBox->setChecked(settings->getToolTipsShowFileSize());
+ d->showImageTypeBox->setChecked(settings->getToolTipsShowImageType());
+ d->showImageDimBox->setChecked(settings->getToolTipsShowImageDim());
+
+ d->showPhotoMakeBox->setChecked(settings->getToolTipsShowPhotoMake());
+ d->showPhotoDateBox->setChecked(settings->getToolTipsShowPhotoDate());
+ d->showPhotoFocalBox->setChecked(settings->getToolTipsShowPhotoFocal());
+ d->showPhotoExpoBox->setChecked(settings->getToolTipsShowPhotoExpo());
+ d->showPhotoModeBox->setChecked(settings->getToolTipsShowPhotoMode());
+ d->showPhotoFlashBox->setChecked(settings->getToolTipsShowPhotoFlash());
+ d->showPhotoWbBox->setChecked(settings->getToolTipsShowPhotoWB());
+
+ d->showAlbumNameBox->setChecked(settings->getToolTipsShowAlbumName());
+ d->showCommentsBox->setChecked(settings->getToolTipsShowComments());
+ d->showTagsBox->setChecked(settings->getToolTipsShowTags());
+ d->showRatingBox->setChecked(settings->getToolTipsShowRating());
+
+ d->fileSettingBox->setEnabled(d->showToolTipsBox->isChecked());
+ d->photoSettingBox->setEnabled(d->showToolTipsBox->isChecked());
+ d->digikamSettingBox->setEnabled(d->showToolTipsBox->isChecked());
+}
+
+} // namespace Digikam
+
diff --git a/src/utilities/setup/setuptooltip.h b/src/utilities/setup/setuptooltip.h
new file mode 100644
index 00000000..8474de78
--- /dev/null
+++ b/src/utilities/setup/setuptooltip.h
@@ -0,0 +1,59 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2006-07-09
+ * Description : album item tool tip configuration setup tab
+ *
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SETUPTOOLTIP_H
+#define SETUPTOOLTIP_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+namespace Digikam
+{
+
+class SetupToolTipPriv;
+
+class SetupToolTip : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SetupToolTip(TQWidget* parent = 0);
+ ~SetupToolTip();
+
+ void applySettings();
+
+private:
+
+ void readSettings();
+
+private:
+
+ SetupToolTipPriv* d;
+};
+
+} // namespace Digikam
+
+#endif // SETUPTOOLTIP_H
diff --git a/src/utilities/slideshow/Makefile.am b/src/utilities/slideshow/Makefile.am
new file mode 100644
index 00000000..5340e80c
--- /dev/null
+++ b/src/utilities/slideshow/Makefile.am
@@ -0,0 +1,17 @@
+METASOURCES = AUTO
+
+INCLUDES = -I$(top_srcdir)/src/digikam \
+ -I$(top_srcdir)/src/libs/dimg \
+ -I$(top_srcdir)/src/libs/dmetadata \
+ -I$(top_srcdir)/src/libs/themeengine \
+ -I$(top_srcdir)/src/libs/threadimageio \
+ $(LIBKDCRAW_CFLAGS) \
+ $(all_includes)
+
+noinst_LTLIBRARIES = libslideshow.la
+
+libslideshow_la_SOURCES = toolbar.cpp slideshow.cpp
+
+libslideshow_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+
diff --git a/src/utilities/slideshow/slideshow.cpp b/src/utilities/slideshow/slideshow.cpp
new file mode 100644
index 00000000..33cf9edc
--- /dev/null
+++ b/src/utilities/slideshow/slideshow.cpp
@@ -0,0 +1,679 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : slide show tool using preview of pictures.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#define MAXSTRINGLEN 80
+
+// TQt includes.
+
+#include <tqtimer.h>
+#include <tqpixmap.h>
+#include <tqdesktopwidget.h>
+#include <tqevent.h>
+#include <tqcursor.h>
+#include <tqpainter.h>
+#include <tqfont.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+#include <tdeversion.h>
+#include <tdeglobalsettings.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "dimg.h"
+#include "toolbar.h"
+#include "previewloadthread.h"
+#include "slideshow.h"
+#include "slideshow.moc"
+
+namespace Digikam
+{
+
+class SlideShowPriv
+{
+public:
+
+ SlideShowPriv()
+ {
+ previewThread = 0;
+ mouseMoveTimer = 0;
+ timer = 0;
+ toolBar = 0;
+ fileIndex = -1;
+ endOfShow = false;
+ pause = false;
+ }
+
+ bool endOfShow;
+ bool pause;
+
+ int deskX;
+ int deskY;
+ int deskWidth;
+ int deskHeight;
+ int fileIndex;
+
+ TQTimer *mouseMoveTimer; // To hide cursor when not moved.
+ TQTimer *timer;
+
+ TQPixmap pixmap;
+
+ DImg preview;
+
+ KURL currentImage;
+
+ PreviewLoadThread *previewThread;
+ PreviewLoadThread *previewPreloadThread;
+
+ ToolBar *toolBar;
+
+ SlideShowSettings settings;
+};
+
+SlideShow::SlideShow(const SlideShowSettings& settings)
+ : TQWidget(0, 0, WStyle_StaysOnTop | WType_Popup |
+ WX11BypassWM | WDestructiveClose)
+{
+ d = new SlideShowPriv;
+ d->settings = settings;
+
+ // ---------------------------------------------------------------
+
+#if KDE_IS_VERSION(3,2,0)
+ TQRect deskRect = TDEGlobalSettings::desktopGeometry(this);
+ d->deskX = deskRect.x();
+ d->deskY = deskRect.y();
+ d->deskWidth = deskRect.width();
+ d->deskHeight = deskRect.height();
+#else
+ TQRect deskRect = TQApplication::desktop()->screenGeometry(this);
+ d->deskX = deskRect.x();
+ d->deskY = deskRect.y();
+ d->deskWidth = deskRect.width();
+ d->deskHeight = deskRect.height();
+#endif
+
+ move(d->deskX, d->deskY);
+ resize(d->deskWidth, d->deskHeight);
+ setPaletteBackgroundColor(TQt::black);
+
+ // ---------------------------------------------------------------
+
+ d->toolBar = new ToolBar(this);
+ d->toolBar->hide();
+ if (!d->settings.loop)
+ d->toolBar->setEnabledPrev(false);
+
+ connect(d->toolBar, TQ_SIGNAL(signalPause()),
+ this, TQ_SLOT(slotPause()));
+
+ connect(d->toolBar, TQ_SIGNAL(signalPlay()),
+ this, TQ_SLOT(slotPlay()));
+
+ connect(d->toolBar, TQ_SIGNAL(signalNext()),
+ this, TQ_SLOT(slotNext()));
+
+ connect(d->toolBar, TQ_SIGNAL(signalPrev()),
+ this, TQ_SLOT(slotPrev()));
+
+ connect(d->toolBar, TQ_SIGNAL(signalClose()),
+ this, TQ_SLOT(slotClose()));
+
+ // ---------------------------------------------------------------
+
+ d->previewThread = new PreviewLoadThread();
+ d->previewPreloadThread = new PreviewLoadThread();
+ d->timer = new TQTimer(this);
+ d->mouseMoveTimer = new TQTimer(this);
+
+ connect(d->previewThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
+ this, TQ_SLOT(slotGotImagePreview(const LoadingDescription &, const DImg&)));
+
+ connect(d->mouseMoveTimer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotMouseMoveTimeOut()));
+
+ connect(d->timer, TQ_SIGNAL(timeout()),
+ this, TQ_SLOT(slotTimeOut()));
+
+ d->timer->start(10, true);
+
+ // ---------------------------------------------------------------
+
+ setMouseTracking(true);
+ slotMouseMoveTimeOut();
+}
+
+SlideShow::~SlideShow()
+{
+ d->timer->stop();
+ d->mouseMoveTimer->stop();
+
+ delete d->timer;
+ delete d->mouseMoveTimer;
+ delete d->previewThread;
+ delete d->previewPreloadThread;
+ delete d;
+}
+
+void SlideShow::setCurrent(const KURL& url)
+{
+ int index = d->settings.fileList.findIndex(url);
+ if (index != -1)
+ {
+ d->currentImage = url;
+ d->fileIndex = index-1;
+ }
+}
+
+void SlideShow::slotTimeOut()
+{
+ loadNextImage();
+}
+
+void SlideShow::loadNextImage()
+{
+ d->fileIndex++;
+ int num = d->settings.fileList.count();
+
+ if (d->fileIndex >= num)
+ {
+ if (d->settings.loop)
+ {
+ d->fileIndex = 0;
+ }
+ }
+
+ if (!d->settings.loop)
+ {
+ d->toolBar->setEnabledPrev(d->fileIndex > 0);
+ d->toolBar->setEnabledNext(d->fileIndex < num-1);
+ }
+
+ if (d->fileIndex < num)
+ {
+ d->currentImage = d->settings.fileList[d->fileIndex];
+ d->previewThread->load(LoadingDescription(d->currentImage.path(),
+ TQMAX(d->deskWidth, d->deskHeight), d->settings.exifRotate));
+ }
+ else
+ {
+ d->currentImage = KURL();
+ d->preview = DImg();
+ updatePixmap();
+ update();
+ }
+
+}
+
+void SlideShow::loadPrevImage()
+{
+ d->fileIndex--;
+ int num = d->settings.fileList.count();
+
+ if (d->fileIndex < 0)
+ {
+ if (d->settings.loop)
+ {
+ d->fileIndex = num-1;
+ }
+ }
+
+ if (!d->settings.loop)
+ {
+ d->toolBar->setEnabledPrev(d->fileIndex > 0);
+ d->toolBar->setEnabledNext(d->fileIndex < num-1);
+ }
+
+ if (d->fileIndex >= 0)
+ {
+ d->currentImage = d->settings.fileList[d->fileIndex];
+ d->previewThread->load(LoadingDescription(d->currentImage.path(),
+ TQMAX(d->deskWidth, d->deskHeight), d->settings.exifRotate));
+ }
+ else
+ {
+ d->currentImage = KURL();
+ d->preview = DImg();
+ updatePixmap();
+ update();
+ }
+
+}
+
+void SlideShow::slotGotImagePreview(const LoadingDescription&, const DImg& preview)
+{
+ d->preview = preview;
+
+ updatePixmap();
+ update();
+
+ if (!d->endOfShow)
+ {
+ if (!d->pause)
+ d->timer->start(d->settings.delay, true);
+ preloadNextImage();
+ }
+}
+
+void SlideShow::preloadNextImage()
+{
+ int index = d->fileIndex + 1;
+ int num = d->settings.fileList.count();
+
+ if (index >= num)
+ {
+ if (d->settings.loop)
+ {
+ index = 0;
+ }
+ }
+
+ if (index < num)
+ {
+ d->previewPreloadThread->load(LoadingDescription(d->settings.fileList[index].path(),
+ TQMAX(d->deskWidth, d->deskHeight), d->settings.exifRotate));
+ }
+}
+
+void SlideShow::updatePixmap()
+{
+ d->pixmap = TQPixmap(size());
+ d->pixmap.fill(TQt::black);
+ TQPainter p(&(d->pixmap));
+
+ if (!d->currentImage.path().isEmpty())
+ {
+ if (!d->preview.isNull())
+ {
+ // Preview extraction is complete... Draw the image.
+
+ TQPixmap pix(d->preview.smoothScale(width(), height(), TQSize::ScaleMin).convertToPixmap());
+ p.drawPixmap((width()-pix.width())/2,
+ (height()-pix.height())/2, pix,
+ 0, 0, pix.width(), pix.height());
+
+ TQString str;
+ PhotoInfoContainer photoInfo = d->settings.pictInfoMap[d->currentImage].photoInfo;
+ int offset = 0;
+
+ // Display the Comments.
+
+ if (d->settings.printComment)
+ {
+ str = d->settings.pictInfoMap[d->currentImage].comment;
+ printComments(p, offset, str);
+ }
+
+ // Display the Make and Model.
+
+ if (d->settings.printMakeModel)
+ {
+ str = TQString();
+
+ if (!photoInfo.make.isEmpty())
+ str = photoInfo.make;
+
+ if (!photoInfo.model.isEmpty())
+ {
+ if (!photoInfo.make.isEmpty())
+ str += TQString(" / ");
+
+ str += photoInfo.model;
+ }
+
+ printInfoText(p, offset, str);
+ }
+
+ // Display the Exposure and Sensitivity.
+
+ if (d->settings.printExpoSensitivity)
+ {
+ str = TQString();
+
+ if (!photoInfo.exposureTime.isEmpty())
+ str = photoInfo.exposureTime;
+
+ if (!photoInfo.sensitivity.isEmpty())
+ {
+ if (!photoInfo.exposureTime.isEmpty())
+ str += TQString(" / ");
+
+ str += i18n("%1 ISO").arg(photoInfo.sensitivity);
+ }
+
+ printInfoText(p, offset, str);
+ }
+
+ // Display the Aperture and Focal.
+
+ if (d->settings.printApertureFocal)
+ {
+ str = TQString();
+
+ if (!photoInfo.aperture.isEmpty())
+ str = photoInfo.aperture;
+
+ if (photoInfo.focalLength35mm.isEmpty())
+ {
+ if (!photoInfo.focalLength.isEmpty())
+ {
+ if (!photoInfo.aperture.isEmpty())
+ str += TQString(" / ");
+
+ str += photoInfo.focalLength;
+ }
+ }
+ else
+ {
+ if (!photoInfo.aperture.isEmpty())
+ str += TQString(" / ");
+
+ if (!photoInfo.focalLength.isEmpty())
+ str += TQString("%1 (35mm: %2)").arg(photoInfo.focalLength).arg(photoInfo.focalLength35mm);
+ else
+ str += TQString("35mm: %1)").arg(photoInfo.focalLength35mm);
+ }
+
+ printInfoText(p, offset, str);
+ }
+
+ // Display the Creation Date.
+
+ if (d->settings.printDate)
+ {
+ if (photoInfo.dateTime.isValid())
+ {
+ str = TDEGlobal::locale()->formatDateTime(photoInfo.dateTime, true, true);
+ printInfoText(p, offset, str);
+ }
+ }
+
+ // Display the image File Name.
+
+ if (d->settings.printName)
+ {
+ str = TQString("%1 (%2/%3)").arg(d->currentImage.filename())
+ .arg(TQString::number(d->fileIndex + 1))
+ .arg(TQString::number(d->settings.fileList.count()));
+
+ printInfoText(p, offset, str);
+ }
+ }
+ else
+ {
+ // ...or preview extraction is failed.
+
+ p.setPen(TQt::white);
+ p.drawText(0, 0, d->pixmap.width(), d->pixmap.height(),
+ TQt::AlignCenter|TQt::WordBreak,
+ i18n("Cannot display image\n\"%1\"")
+ .arg(d->currentImage.fileName()));
+ }
+ }
+ else
+ {
+ // End of Slide Show.
+
+ TQPixmap logo = kapp->iconLoader()->loadIcon("digikam", TDEIcon::NoGroup, 128,
+ TDEIcon::DefaultState, 0, true);
+
+ TQFont fn(font());
+ fn.setPointSize(fn.pointSize()+10);
+ fn.setBold(true);
+
+ p.setFont(fn);
+ p.setPen(TQt::white);
+ p.drawPixmap(50, 100, logo);
+ p.drawText(60 + logo.width(), 100 + logo.height()/3, i18n("SlideShow Completed."));
+ p.drawText(60 + logo.width(), 100 + 2*logo.height()/3, i18n("Click To Exit..."));
+
+ d->endOfShow = true;
+ d->toolBar->setEnabledPlay(false);
+ d->toolBar->setEnabledNext(false);
+ d->toolBar->setEnabledPrev(false);
+ }
+}
+
+void SlideShow::printInfoText(TQPainter &p, int &offset, const TQString& str)
+{
+ if (!str.isEmpty())
+ {
+ offset += 20;
+ p.setPen(TQt::black);
+ for (int x=9; x<=11; x++)
+ for (int y=offset+1; y>=offset-1; y--)
+ p.drawText(x, height()-y, str);
+
+ p.setPen(TQt::white);
+ p.drawText(10, height()-offset, str);
+ }
+}
+
+void SlideShow::printComments(TQPainter &p, int &offset, const TQString& comments)
+{
+ TQStringList commentsByLines;
+
+ uint commentsIndex = 0; // Comments TQString index
+
+ while (commentsIndex < comments.length())
+ {
+ TQString newLine;
+ bool breakLine = false; // End Of Line found
+ uint currIndex; // Comments TQString current index
+
+ // Check miminal lines dimension
+
+ uint commentsLinesLengthLocal = MAXSTRINGLEN;
+
+ for (currIndex = commentsIndex; currIndex < comments.length() && !breakLine; currIndex++ )
+ {
+ if( comments[currIndex] == TQChar('\n') || comments[currIndex].isSpace() )
+ breakLine = true;
+ }
+
+ if (commentsLinesLengthLocal <= (currIndex - commentsIndex))
+ commentsLinesLengthLocal = (currIndex - commentsIndex);
+
+ breakLine = false;
+
+ for (currIndex = commentsIndex ; currIndex <= commentsIndex + commentsLinesLengthLocal &&
+ currIndex < comments.length() && !breakLine ;
+ currIndex++ )
+ {
+ breakLine = (comments[currIndex] == TQChar('\n')) ? true : false;
+
+ if (breakLine)
+ newLine.append(TQString(" "));
+ else
+ newLine.append(comments[currIndex]);
+ }
+
+ commentsIndex = currIndex; // The line is ended
+
+ if (commentsIndex != comments.length())
+ {
+ while (!newLine.endsWith(" "))
+ {
+ newLine.truncate(newLine.length() - 1);
+ commentsIndex--;
+ }
+ }
+
+ commentsByLines.prepend(newLine.stripWhiteSpace());
+ }
+
+ for (int i = 0 ; i < (int)commentsByLines.count() ; i++ )
+ {
+ printInfoText(p, offset, commentsByLines[i]);
+ }
+}
+
+void SlideShow::paintEvent(TQPaintEvent *)
+{
+ bitBlt(this, 0, 0, static_cast<TQPaintDevice*>(&d->pixmap),
+ 0, 0, d->pixmap.width(),
+ d->pixmap.height(), TQt::CopyROP, true);
+}
+
+void SlideShow::slotPause()
+{
+ d->timer->stop();
+ d->pause = true;
+
+ if (d->toolBar->isHidden())
+ {
+ int w = d->toolBar->width();
+ d->toolBar->move(d->deskWidth-w-1,0);
+ d->toolBar->show();
+ }
+}
+
+void SlideShow::slotPlay()
+{
+ d->toolBar->hide();
+ d->pause = false;
+ slotTimeOut();
+}
+
+void SlideShow::slotPrev()
+{
+ loadPrevImage();
+}
+
+void SlideShow::slotNext()
+{
+ loadNextImage();
+}
+
+void SlideShow::slotClose()
+{
+ close();
+}
+
+void SlideShow::wheelEvent(TQWheelEvent * e)
+{
+ if (e->delta() < 0)
+ {
+ d->timer->stop();
+ d->pause = true;
+ d->toolBar->setPaused(true);
+ slotNext();
+ }
+
+ if (e->delta() > 0 && d->fileIndex-1 >= 0)
+ {
+ d->timer->stop();
+ d->pause = true;
+ d->toolBar->setPaused(true);
+ slotPrev();
+ }
+}
+
+void SlideShow::mousePressEvent(TQMouseEvent *e)
+{
+ if (d->endOfShow)
+ close();
+
+ if (e->button() == TQt::LeftButton)
+ {
+ d->timer->stop();
+ d->pause = true;
+ d->toolBar->setPaused(true);
+ slotNext();
+ }
+ else if (e->button() == TQt::RightButton && d->fileIndex-1 >= 0)
+ {
+ d->timer->stop();
+ d->pause = true;
+ d->toolBar->setPaused(true);
+ slotPrev();
+ }
+}
+
+void SlideShow::keyPressEvent(TQKeyEvent *event)
+{
+ if (!event)
+ return;
+
+ d->toolBar->keyPressEvent(event);
+}
+
+void SlideShow::mouseMoveEvent(TQMouseEvent *e)
+{
+ setCursor(TQCursor(TQt::ArrowCursor));
+ d->mouseMoveTimer->start(1000, true);
+
+ if (!d->toolBar->canHide())
+ return;
+
+ TQPoint pos(e->pos());
+
+ if ((pos.y() > (d->deskY+20)) &&
+ (pos.y() < (d->deskY+d->deskHeight-20-1)))
+ {
+ if (d->toolBar->isHidden())
+ return;
+ else
+ d->toolBar->hide();
+ return;
+ }
+
+ int w = d->toolBar->width();
+ int h = d->toolBar->height();
+
+ if (pos.y() < (d->deskY+20))
+ {
+ if (pos.x() <= (d->deskX+d->deskWidth/2))
+ // position top left
+ d->toolBar->move(d->deskX, d->deskY);
+ else
+ // position top right
+ d->toolBar->move(d->deskX+d->deskWidth-w-1, d->deskY);
+ }
+ else
+ {
+ if (pos.x() <= (d->deskX+d->deskWidth/2))
+ // position bot left
+ d->toolBar->move(d->deskX, d->deskY+d->deskHeight-h-1);
+ else
+ // position bot right
+ d->toolBar->move(d->deskX+d->deskWidth-w-1, d->deskY+d->deskHeight-h-1);
+ }
+ d->toolBar->show();
+}
+
+void SlideShow::slotMouseMoveTimeOut()
+{
+ TQPoint pos(TQCursor::pos());
+ if ((pos.y() < (d->deskY+20)) ||
+ (pos.y() > (d->deskY+d->deskHeight-20-1)))
+ return;
+
+ setCursor(TQCursor(TQt::BlankCursor));
+}
+
+} // NameSpace Digikam
diff --git a/src/utilities/slideshow/slideshow.h b/src/utilities/slideshow/slideshow.h
new file mode 100644
index 00000000..dc15ce1c
--- /dev/null
+++ b/src/utilities/slideshow/slideshow.h
@@ -0,0 +1,91 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : slide show tool using preview of pictures.
+ *
+ * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SLIDE_SHOW_H
+#define SLIDE_SHOW_H
+
+// TQt includes.
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+#include "loadingdescription.h"
+#include "slideshowsettings.h"
+
+namespace Digikam
+{
+
+class DImg;
+class SlideShowPriv;
+
+class DIGIKAM_EXPORT SlideShow : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ SlideShow(const SlideShowSettings& settings);
+ ~SlideShow();
+
+ void setCurrent(const KURL& url);
+
+protected:
+
+ void paintEvent(TQPaintEvent *);
+ void mousePressEvent(TQMouseEvent *);
+ void mouseMoveEvent(TQMouseEvent *);
+ void keyPressEvent(TQKeyEvent *);
+ void wheelEvent(TQWheelEvent *);
+
+private slots:
+
+ void slotTimeOut();
+ void slotMouseMoveTimeOut();
+ void slotGotImagePreview(const LoadingDescription &, const DImg &);
+
+ void slotPause();
+ void slotPlay();
+ void slotPrev();
+ void slotNext();
+ void slotClose();
+
+private:
+
+ void loadNextImage();
+ void loadPrevImage();
+ void preloadNextImage();
+ void updatePixmap();
+ void printInfoText(TQPainter &p, int &offset, const TQString& str);
+ void printComments(TQPainter &p, int &offset, const TQString& comments);
+
+private:
+
+ SlideShowPriv *d;
+};
+
+} // NameSpace Digikam
+
+#endif /* SLIDE_SHOW_H */
diff --git a/src/utilities/slideshow/slideshowsettings.h b/src/utilities/slideshow/slideshowsettings.h
new file mode 100644
index 00000000..85057f27
--- /dev/null
+++ b/src/utilities/slideshow/slideshowsettings.h
@@ -0,0 +1,125 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-02-13
+ * Description : slide show settings container.
+ *
+ * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef SLIDESHOWSETTINGSCONTAINER_H
+#define SLIDESHOWSETTINGSCONTAINER_H
+
+// TQt includes.
+
+#include <tqmap.h>
+
+// KDE includes.
+
+#include <kurl.h>
+
+// Local includes.
+
+#include "photoinfocontainer.h"
+#include "digikam_export.h"
+
+namespace Digikam
+{
+
+/** This class contain the information of one picture to slide */
+class DIGIKAM_EXPORT SlidePictureInfo
+{
+
+public:
+
+ SlidePictureInfo(){};
+
+ ~SlidePictureInfo(){};
+
+public:
+
+ /** Image Comment */
+ TQString comment;
+
+ /** Exif photo info of picture */
+ PhotoInfoContainer photoInfo;
+};
+
+// --------------------------------------------------------------------------------
+
+/** This class contain all settings to perform a slide show of a group of pictures */
+class DIGIKAM_EXPORT SlideShowSettings
+{
+
+public:
+
+ SlideShowSettings()
+ {
+ exifRotate = true;
+ printName = true;
+ printDate = false;
+ printComment = false;
+ printApertureFocal = false;
+ printMakeModel = false;
+ printExpoSensitivity = false;
+ loop = false;
+ delay = 5;
+ };
+
+ ~SlideShowSettings(){};
+
+public:
+
+ // Global Slide Show Settings
+
+ /** Auto-rotate image accordinly with Exif Rotation tag */
+ bool exifRotate;
+
+ /** Print picture file name during slide */
+ bool printName;
+
+ /** Print picture creation date during slide */
+ bool printDate;
+
+ /** Print camera Aperture and Focal during slide */
+ bool printApertureFocal;
+
+ /** Print camera Make and Model during slide */
+ bool printMakeModel;
+
+ /** Print camera Exposure and Sensitivity during slide */
+ bool printExpoSensitivity;
+
+ /** Print picture comment during slide */
+ bool printComment;
+
+ /** Slide pictures in loop */
+ bool loop;
+
+ /** Delay in seconds */
+ int delay;
+
+ /** List of pictures URL to slide */
+ KURL::List fileList;
+
+ /** Map of pictures information to slide */
+ TQMap<KURL, SlidePictureInfo> pictInfoMap;
+};
+
+} // namespace Digikam
+
+#endif // SLIDESHOWSETTINGSCONTAINER_H
diff --git a/src/utilities/slideshow/toolbar.cpp b/src/utilities/slideshow/toolbar.cpp
new file mode 100644
index 00000000..dbecd4ad
--- /dev/null
+++ b/src/utilities/slideshow/toolbar.cpp
@@ -0,0 +1,217 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-10-05
+ * Description : a tool bar for slideshow
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+// TQt includes.
+
+#include <tqtoolbutton.h>
+#include <tqlayout.h>
+#include <tqpixmap.h>
+
+// KDE includes.
+
+#include <tdeapplication.h>
+#include <kiconloader.h>
+#include <tdelocale.h>
+
+// Local includes.
+
+#include "toolbar.h"
+#include "toolbar.moc"
+
+namespace Digikam
+{
+
+class ToolBarPriv
+{
+public:
+
+ ToolBarPriv()
+ {
+ playBtn = 0;
+ stopBtn = 0;
+ nextBtn = 0;
+ prevBtn = 0;
+ canHide = true;
+ }
+
+ bool canHide;
+
+ TQToolButton *playBtn;
+ TQToolButton *stopBtn;
+ TQToolButton *nextBtn;
+ TQToolButton *prevBtn;
+};
+
+ToolBar::ToolBar(TQWidget* parent)
+ : TQWidget(parent)
+{
+ d = new ToolBarPriv;
+
+ TQHBoxLayout* lay = new TQHBoxLayout(this);
+ d->playBtn = new TQToolButton(this);
+ d->prevBtn = new TQToolButton(this);
+ d->nextBtn = new TQToolButton(this);
+ d->stopBtn = new TQToolButton(this);
+ d->playBtn->setToggleButton(true);
+
+ TDEIconLoader* loader = kapp->iconLoader();
+ d->playBtn->setIconSet(loader->loadIcon("media-playback-pause", TDEIcon::NoGroup, 22));
+ d->prevBtn->setIconSet(loader->loadIcon("back", TDEIcon::NoGroup, 22));
+ d->nextBtn->setIconSet(loader->loadIcon("forward", TDEIcon::NoGroup, 22));
+ d->stopBtn->setIconSet(loader->loadIcon("process-stop", TDEIcon::NoGroup, 22));
+
+ lay->addWidget(d->playBtn);
+ lay->addWidget(d->prevBtn);
+ lay->addWidget(d->nextBtn);
+ lay->addWidget(d->stopBtn);
+
+ setBackgroundMode(TQt::NoBackground);
+ adjustSize();
+ setSizePolicy(TQSizePolicy::Fixed, TQSizePolicy::Fixed);
+
+ connect(d->playBtn, TQ_SIGNAL(toggled(bool)),
+ this, TQ_SLOT(slotPlayBtnToggled()));
+
+ connect(d->nextBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotNexPrevClicked()));
+
+ connect(d->prevBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SLOT(slotNexPrevClicked()));
+
+ connect(d->nextBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalNext()));
+
+ connect(d->prevBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalPrev()));
+
+ connect(d->stopBtn, TQ_SIGNAL(clicked()),
+ this, TQ_SIGNAL(signalClose()));
+}
+
+ToolBar::~ToolBar()
+{
+ delete d;
+}
+
+bool ToolBar::canHide() const
+{
+ return d->canHide;
+}
+
+bool ToolBar::isPaused() const
+{
+ return d->playBtn->isOn();
+}
+
+void ToolBar::setPaused(bool val)
+{
+ if (val == isPaused())
+ return;
+
+ d->playBtn->setOn(val);
+ slotPlayBtnToggled();
+}
+
+void ToolBar::setEnabledPlay(bool val)
+{
+ d->playBtn->setEnabled(val);
+}
+
+void ToolBar::setEnabledNext(bool val)
+{
+ d->nextBtn->setEnabled(val);
+}
+
+void ToolBar::setEnabledPrev(bool val)
+{
+ d->prevBtn->setEnabled(val);
+}
+
+void ToolBar::slotPlayBtnToggled()
+{
+ if (d->playBtn->isOn())
+ {
+ d->canHide = false;
+ TDEIconLoader* loader = kapp->iconLoader();
+ d->playBtn->setIconSet(loader->loadIcon("media-playback-start", TDEIcon::NoGroup, 22));
+ emit signalPause();
+ }
+ else
+ {
+ d->canHide = true;
+ TDEIconLoader* loader = kapp->iconLoader();
+ d->playBtn->setIconSet(loader->loadIcon("media-playback-pause", TDEIcon::NoGroup, 22));
+ emit signalPlay();
+ }
+}
+
+void ToolBar::slotNexPrevClicked()
+{
+ if (!d->playBtn->isOn())
+ {
+ d->playBtn->setOn(true);
+ d->canHide = false;
+ TDEIconLoader* loader = kapp->iconLoader();
+ d->playBtn->setIconSet(loader->loadIcon("media-playback-start", TDEIcon::NoGroup, 22));
+ emit signalPause();
+ }
+}
+
+void ToolBar::keyPressEvent(TQKeyEvent *event)
+{
+ switch(event->key())
+ {
+ case(TQt::Key_Space):
+ {
+ if (d->playBtn->isEnabled())
+ d->playBtn->animateClick();
+ break;
+ }
+ case(TQt::Key_Prior):
+ {
+ if (d->prevBtn->isEnabled())
+ d->prevBtn->animateClick();
+ break;
+ }
+ case(TQt::Key_Next):
+ {
+ if (d->nextBtn->isEnabled())
+ d->nextBtn->animateClick();
+ break;
+ }
+ case(TQt::Key_Escape):
+ {
+ if (d->stopBtn->isEnabled())
+ d->stopBtn->animateClick();
+ break;
+ }
+ default:
+ break;
+ }
+
+ event->accept();
+}
+
+} // Namespace Digikam
+
diff --git a/src/utilities/slideshow/toolbar.h b/src/utilities/slideshow/toolbar.h
new file mode 100644
index 00000000..97262c12
--- /dev/null
+++ b/src/utilities/slideshow/toolbar.h
@@ -0,0 +1,85 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-10-05
+ * Description : a tool bar for slideshow
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot 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, 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.
+ *
+ * ============================================================ */
+
+#ifndef TOOL_BAR_H
+#define TOOL_BAR_H
+
+#include <tqwidget.h>
+
+// Local includes.
+
+#include "digikam_export.h"
+
+class TQToolButton;
+
+namespace Digikam
+{
+
+class ToolBarPriv;
+
+class DIGIKAM_EXPORT ToolBar : public TQWidget
+{
+ TQ_OBJECT
+
+
+public:
+
+ ToolBar(TQWidget* parent);
+ ~ToolBar();
+
+ bool canHide() const;
+ bool isPaused() const;
+ void setPaused(bool val);
+
+ void setEnabledPlay(bool val);
+ void setEnabledNext(bool val);
+ void setEnabledPrev(bool val);
+
+protected:
+
+ void keyPressEvent(TQKeyEvent *event);
+
+signals:
+
+ void signalNext();
+ void signalPrev();
+ void signalClose();
+ void signalPlay();
+ void signalPause();
+
+private slots:
+
+ void slotPlayBtnToggled();
+ void slotNexPrevClicked();
+
+private:
+
+ ToolBarPriv *d;
+
+ friend class SlideShow;
+};
+
+} // Namespace Digikam
+
+#endif /* TOOL_BAR_H */